[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\njobs:\n  build:\n    docker:\n      - image: debian:trixie\n    steps:\n      - run: &install_deps\n          name: Install Deps\n          command: |\n            whoami\n            apt-get update\n            apt-get install -y build-essential sbcl  libssh2-1-dev ssh git sudo curl pkg-config\n            apt-get install -y imagemagick libmagickwand-dev\n            apt-get install -y openjdk-21-jdk-headless \n      - checkout\n      - run:\n          name: SBCL tests\n          command: |\n            make test-sb\n      - run:\n          name: build-cli\n          command: |\n            sbcl --script scripts/build-cli.lisp\n      - run:\n          name: Upload assets for Screenshots\n          command: |\n            make upload-screenshots-oss\n\n  launch:\n    docker:\n      - image: debian:trixie\n    steps:\n      - run:\n          <<: *install_deps\n      - checkout\n      - run:\n          name: Launch Screenshotbot\n          command: |\n            mkdir -p ~/.config/screenshotbot/object-store/current/\n            sbcl --script launch.lisp --verify-store\n\n  build_cli:\n    docker:\n      - image: debian:trixie\n    steps:\n      - run:\n          <<: *install_deps\n      - checkout\n      - run:\n          name: Build CLI\n          command: |\n            sbcl --script scripts/build-cli.lisp\n\n\nworkflows:\n  tests:\n    jobs:\n      - build\n  launch:\n    jobs:\n      - launch\n  build_cli:\n    jobs:\n      - build_cli\n"
  },
  {
    "path": ".dockerignore",
    "content": "bazel-*\n*.pyc\n.#*\nlog/*\nsupervise\njipr/static/binary/\njipr/static/releases/\npictures for sam\ngallery-uploads\nweb-bin\n.web-bin-copy\n*ansi-term*\nsystem-index.txt\n.sass-cache\nrepl.lisp-repl\n*.64ufasl\n*.fasl\nconfig.lisp\nbuild\n/assets\nscreenshotbot-installer.sh\nanalytics-log-file.log\n*.lx64fsl\n.git\nbuck-out\nquicklisp\nsrc\nlocal-projects\ntools\nlog\nanalytics-log-file.log\n.buckd"
  },
  {
    "path": ".gitattributes",
    "content": "*.lisp text eol=lf\n*.txt text eol=lf"
  },
  {
    "path": ".gitignore",
    "content": "bazel-*\n*.pyc\n.#*\nlog/*\nsupervise\njipr/static/binary/\njipr/static/releases/\npictures for sam\ngallery-uploads\nweb-bin\n.web-bin-copy\n*ansi-term*\nsystem-index.txt\n.sass-cache\nrepl.lisp-repl\n*.64ufasl\n*.64ofasl\n*.fasl\n/config.lisp\n/build\n/assets\nscreenshotbot-installer.sh\nanalytics-log-file.log\n*.lx64fsl\n.moma.yml\n*.64xfasl\nnohup.out\nsandbox\n*~\n*.64yfasl\n\n# competitive programming\ninput\nbuck-out\n.buckd\ndummy-input\n.buckconfig.local\nstatic-web-output\namazon-acceptance-test.zip\ntmp-upload\n.secrets\n.qlot\n.vagrant\n.env\na.out\ncl-cron.log\n\n# HubSpot config file\nhubspot.config.yml\n\nscreenshotbot-cli\n\n/pixel-diff\n\n.DS_Store\npixel-diff.app\npixel-diff.zip\npixel-diff-notarization.zip\npixel-diff.lwheap\nansible.log\n\nnfs-disk.vdi\n\n# Arcanist phutil cache files\n.phutil_module_cache\n\nscreenshotbot*.deb\n\nvagrant-efs\n*fasl"
  },
  {
    "path": "Dockerfile",
    "content": "FROM debian:trixie AS magick_base\n\n\nRUN apt-get update && apt-get install -y libyaml-dev git-core libpng-dev zlib1g-dev libpng16-16  zlib1g gcc makeself exiftool build-essential logrotate  sbcl pkg-config imagemagick libmagickwand-dev\n\nRUN apt-get update && apt-get install -y openjdk-21-jdk-headless\n\nFROM magick_base AS builder\n\n# Update copybara config if you change this line\n\nWORKDIR /app\n\nENTRYPOINT [\"sbcl\", \"--script\", \"launch.lisp\"]\nCMD [\"--object-store\", \"/data/\", \"--start-slynk\", \"--slynk-port\", \"4005\", \"--slynk-loopback-interface\", \"0.0.0.0\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright 2018-Present Modern Interpreters Inc.\n#\n# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not distributed with this\n# file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nSBCL_C_FLAGS=--dynamic-space-size 2048\nsbcl=build/sbcl-console\nCACHE_KEY=12\nSBCL_CORE=sbcl $(SBCL_C_FLAGS) --no-userinit\nCCL_DEFAULT_DIRECTORY?=/opt/software/ccl\nCCL_CORE=$(CCL_DEFAULT_DIRECTORY)/lx86cl64\nCCL_IMAGE=build/ccl-console\ntests= \\\n\t./test-stuff.lisp \\\n\t./test-stuff2.lisp\n\nARCH?=\nARCH_CMD=\n\nifeq ($(ARCH),x86_64)\n\tARCH_CMD=arch -$(ARCH)\nendif\n\nLW_PREFIX=/opt/software/lispworks\n\nifeq ($(UNAME),Linux)\ndefine timeout\ntimeout $1\nendef\ndefine timeout\nendef\nelse\n\n\n\nendif\n\ndefine wild_src\n$(call FIND,$(SRC_DIRS), $(1))\nendef\n\nJIPR=../jippo\nSRC_DIRS=src local-projects third-party scripts quicklisp\nLISP_FILES=$(call wild_src, '*.lisp') $(call wild_src, '*.asd') $(call will_src,'*.c') $(call wild_src,'*.cpp')\n\nJS_FILES=$(call wild_src, '*.js')\nCSS_FILES=$(call wild_src, '*.css') $(call wild_src, '*.scss')\n\nLW_SCRIPT=PATH=/opt/homebrew/bin:$$PATH $(call timeout,15m) $(LW) -quiet -build\nSBCL_SCRIPT=$(sbcl) --script\nTMPFILE=$(shell mktemp)\nJAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/\nSBCL_SCRIPT=$(call timeout,5m) $(sbcl) --script\nCCL_SCRIPT=CCL_DEFAULT_DIRECTORY=$(CCL_DEFAULT_DIRECTORY) $(CCL_CORE) -b -I $(CCL_IMAGE)\n\nQUICKLISP=quicklisp/dists/quicklisp/\nCOPYBARA_CMD=java -jar scripts/copybara_deploy.jar\n\ndefine COPYBARA\n( $(COPYBARA_CMD) copy.bara.sky $1 | tee build/cb-output ) || grep \"No new changes to import\" build/cb-output\nendef\n\nifeq ($(OS), Windows_NT)\n\tUNAME=Windows\n\tMKDIR=mkdir -pf\n\tTOUCH=powershell -command New-Item\n\tFIND=$(shell gci -r $2 | Select FullName)\n\tRM=echo\nelse\n\tUNAME=$(shell uname -s)\n\tMKDIR=mkdir -p\n\tTOUCH=touch\n\tFIND=$(shell find $(1) -name $(2))\n\tRM=rm -f\nendif\n\nCYGWIN=$(findstring CYGWIN,$(UNAME))\nDISTINFO=quicklisp/dists/quicklisp/distinfo.txt\nARC=build/arc/bin/arc\n\nREVISION_ID=$(shell echo '{\"ids\":[\"$(DIFF_ID)\"]}' | $(ARC) call-conduit differential.querydiffs -- | jq -r '.[\"response\"][\"$(DIFF_ID)\"][\"revisionID\"]')\nQUICKLISP_DEPS=$(call FIND,quicklisp,'*.txt') $(call FIND,quicklisp,'*.lisp')\nIMAGE_DEPS=scripts/build-image.lisp scripts/asdf.lisp $(DISTINFO) scripts/prepare-image.lisp scripts/init.lisp scripts/asdf.lisp $(QUICKLISP_DEPS)\n\ndefine ht_check_tests\n\tTMP=$(TMPFILE) && $1 | tee  $$TMP &&  grep \"all tests PASSED\" $$TMP && rm $$TMP\nendef\n\ninclude scripts/lispworks-versions.mk\n\nifneq (\"$(wildcard scripts/common.mk)\", \"\")\n\tinclude scripts/common.mk\nendif\n\n\nall:\n\ttrue\n\nsubmodule:\n#\tgit submodule init\n# git submodule update\n\n\n\nbuild/cache-key: .PHONY\nifneq ($(OS),Windows_NT)\n\tif ! [ -e build/cache-key ] || ! [ x`cat build/cache-key` = x$(CACHE_KEY) ] ; then \\\n\t\techo \"Cleaning build/ directory\" ; \\\n\t\trm -rf build/asdf-cache build/slime-fasls ; \\\n\t\trm -rf quicklisp/dists/quicklisp/software ; \\\n\t\tmkdir build ; \\\n\t\techo $(CACHE_KEY) > $@ ; \\\n\tfi\nelse\n# TODO: detect the cache-key correctly, currently you have to manually delete this file to reset\n\tif not exist build mkdir build\n\tif not exist $@ ( echo $(CACHE_KEY) > $@ )\nendif\n\n\n.PHONY:\n\nupdate-quicklisp: .PHONY\n\t$(SBCL_CORE) --eval '(load \"quicklisp/setup.lisp\")' --eval '(ql:update-all-dists :prompt nil)' --quit\n\nupdate-quicklisp-client: .PHONY\n\t$(SBCL_CORE) --eval '(load \"quicklisp/setup.lisp\")' --eval '(ql:update-client :prompt nil)' --quit\n\n\nstart-dev: $(sbcl)\n\t$(sbcl) --script ./start-dev.lisp\n\ninstall-dep: install-dep.lisp $(sbcl)\n\t$(sbcl) --script ./install-dep.lisp\n\nshow-info:\n\tgit status\n\tenv\n\nclean-sys-index:\n\trm -f system-index.txt\n\trm -rfv local-projects/quicklisp\n\trm -rfv */system-index.txt\n\trm -f quicklisp/local-projects/system-index.txt\n\ntests:| show-info clean-sys-index test-parts selenium-tests conditional-copybara\n\ntest-parts: test-sb test-lw test-ccl test-store\n\ntest-sb: submodule $(sbcl) build/affected-files.txt\n\t$(sbcl) $(SBCL_C_FLAGS) --script ./scripts/jenkins.lisp\n\ntest-ccl: submodule $(CCL_IMAGE)\n\t$(CCL_SCRIPT) ./scripts/jenkins.lisp\n\ntest-lw: submodule $(LW) build/affected-files.txt\n\t$(LW_SCRIPT) ./scripts/jenkins.lisp\n\ntest-store: submodule $(LW)\n\t$(LW_SCRIPT) ./scripts/run-store-tests.lisp\n\nbuild/.keep: | build/cache-key $(DISTINFO)\n\t$(TOUCH) $@\n\nclean-fasl: .PHONY\n\tfind src -name *.64ufasl -delete\n\nclean: clean-fasl\n\trm -rf build\n\nhunchentoot-tests-sb: $(sbcl)\n\t$(call ht_check_tests,$(SBCL_SCRIPT) scripts/run-hunchentoot-tests.lisp)\n\nhunchentoot-tests-lw: $(LW)\n\t$(call ht_check_tests,$(LW_SCRIPT) scripts/run-hunchentoot-tests.lisp)\n\nscreenshotbot-flow:\n\n\tadb shell settings put global hidden_api_policy_p_apps 1\n\tadb shell settings put global hidden_api_policy_pre_p_apps 1\n\tadb shell settings put global hidden_api_policy 1\n\tcd ~/builds/silkwrmsdk && ./gradlew :core:publishToMavenLocal :plugin:publishToMavenLocal\n\t#\tcd ~/builds/screenshotbot-example && ./gradlew :connectedDebugAndroidTest\n\tcd ~/builds/screenshotbot-example && ./gradlew -i :debugAndroidTestScreenshotbot\n\nscreenshotbot-tests: $(LW) .PHONY\n\t$(LW_SCRIPT) ./scripts/jenkins.lisp -system screenshotbot/tests,screenshotbot.pro/tests,screenshotbot.sdk/tests\n\nsdk-tests: $(LW) .PHONY\n\t$(LW_SCRIPT) ./scripts/jenkins.lisp -system screenshotbot.sdk/tests -no-jvm\n\n$(LW): build/.keep $(IMAGE_DEPS) \n\techo in here\n# $$PWD is workaround over LW issue #42471\n\t$(ARCH_CMD) $(LW_CORE) -build scripts/build-image.lisp\n\ttest -f $(LW)\n\n$(sbcl): build/.keep $(IMAGE_DEPS) .PHONY Makefile\n\t$(SBCL_CORE) --script scripts/build-image.lisp\n\nselenium-tests: $(LW)\n\t# rm -rf src/screenshotbot/selenium-output\n\t# $(LW_SCRIPT) scripts/run-selenium-tests.lisp\n\nselenium-tests-without-x: $(LW)\n\t$(LW_SCRIPT) scripts/run-selenium-tests.lisp\n\n$(CCL_IMAGE): build/.keep $(IMAGE_DEPS)\n\trm -f $@\n\t$(CCL_CORE) -l scripts/build-image.lisp\n\tchmod a+x $@\n\n\nupdate-ip: $(sbcl)\n\t$(SBCL_SCRIPT) update-ip.lisp\n\ncopybara: .PHONY build\n\t# This is on arnold's jenkins server. Disregard for OSS use.\n\tssh-add ~/.ssh/id_rsa_screenshotbot_oss\n\t$(call COPYBARA)\n\ncopybara-slite: .PHONY build\n\tssh-add ~/.ssh/id_rsa_slite\n\t$(call COPYBARA,slite)\n\n\ncopybara-quick-patch: .PHONY build\n\tssh-add ~/.ssh/id_rsa_quick_patch\n\t$(call COPYBARA,quick-patch)\n\nconditional-copybara: validate-copybara\n\tif [ x$$DIFF_ID = x ] ; then \\\n\t   make all-copybara ; \\\n\tfi\n\nall-copybara:\n\tssh-agent $(MAKE) copybara\n\tssh-agent $(MAKE) copybara-slite\n\tssh-agent $(MAKE) copybara-quick-patch\n\nvalidate-copybara: .PHONY\n\t$(COPYBARA_CMD) validate copy.bara.sky\n\nupdate-harbormaster-pass: $(sbcl)\n\t$(SBCL_SCRIPT) ./scripts/update-phabricator.lisp pass\n\nupdate-harbormaster-fail: $(sbcl)\n\t$(SBCL_SCRIPT) ./scripts/update-phabricator.lisp fail\n\n\nautoland:\n\tif ( echo \"{\\\"revision_id\\\":\\\"$(REVISION_ID)\\\"}\" | $(ARC) call-conduit differential.getcommitmessage -- | grep \"#autoland\" ) ; then \\\n\t \t$(MAKE) -s actually-land ; \\\n\tfi\n\nactually-land:\n\tgit status\n\techo \"Landing...\"\n\t$(ARC) land  --\n\nsrc/java/libs: .PHONY\n\tcd src/java && make libs\n\nupload-mac-intel-sdk:\n\tARCH=\"x86_64\" make upload-sdk\n\n\nupload-screenshots-oss: .PHONY\n\tcurl https://screenshotbot.io/recorder.sh | bash\n\tSCREENSHOTBOT_CLI_V2=1 ~/screenshotbot/recorder ci static-website --directory src/screenshotbot/static-web-output/ --main-branch master --channel screenshotbot-oss --repo-url 'git@github.com:screenshotbot/screenshotbot-oss.git' --main-branch main\n\nemacs-tests:\n\tcd src/emacs && make all\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n    <img src=\"https://screenshotbot.io/assets/images/logo-dark.svg\" width=\"40%\" />\n</p>\n\n# Screenshotbot: Screenshot Testing Service\n\n[![tdrhq](https://circleci.com/gh/screenshotbot/screenshotbot-oss.svg?style=shield)](https://app.circleci.com/pipelines/github/screenshotbot/screenshotbot-oss?branch=main)\n[![Screenshots](https://screenshotbot.io/badge?org=5fd16bcf4f4b3822fd0000e1&channel=screenshotbot-oss&branch=master&cache-key=2)](https://screenshotbot.io/active-run?org=5fd16bcf4f4b3822fd0000e1&channel=screenshotbot-oss&branch=master)\n\n\nScreenshotbot is a Screenshot Testing service. Screenshotbot will\nconnect your existing Android, iOS or Selenium tests to track how\nscreenshots change over time, notifying you on Pull Requests, Jira\netc. We provide several integrations to common Code Review and Task\nManagement platforms, and have more in the pipeline.\n\nScreenshotbot-oss powers our own commercial platform\n[screenshotbot.io](https://screenshotbot.io).\n\n\n<p align=\"center\">\n   <img src=\"https://cdn.screenshotbot.io/assets/images/new-landing/landing-hero.png\" width=\"60%\" />\n</p>\n\n\n## Quick installation with Docker\n\n```\n$ docker-compose up --build\n```\n\nIf you need to modify the `config.lisp`, modify it before running this\ncommand. In the future we'll provide live reloading of config.lisp for\ndocker, but at the moment that's only available when not using docker.\n\n## Quick Installation in the cloud\n\nIf you want a publicly accessible instance, complete with HTTPS, we\nhave a script that will help you set it up in your cloud.\n\nSee [this wiki page](https://github.com/screenshotbot/screenshotbot-oss/wiki/Quick-installation-in-the-cloud) for details.\n\n## Configuration\n\nScreenshotbot has integrations with various external tools,\ne.g. GitHub, Jira, SSO etc. Most of these platforms require some\nkind of API key to access their APIs, and must be configured with\nScreenshotbot before you can use them.\n\nFor simplicity and maintainability, we don't have complex GUIs to\nmodify these _site-admin_ configurations. Instead each of these\nintegrations are exposed as plugins that must be configured with basic\nCommon Lisp code. The configuration can be hot-reloaded.\n\nScreenshotbot looks for a file called `config.lisp` in both the\ngit-root, and in `~/.config/screenshotbot/`. If found, it loads this\nfile as the configuration.\n\nSee [Updating\nconfig.lisp](https://github.com/screenshotbot/screenshotbot-oss/wiki/Updating-config.lisp)\nfor a more thorough discussion.\n\n### Becoming a Site-Admin\n\nAfter installing Screenshotbot, we recommend setting up one user as a\nsite-admin. The site-admin gets special administrative powers that\nwill be required for hot-reloading config files, and hot-reloading\nupdates. We might also build more configuration powers for site-admins\nin the future.\n\nAfter signing up and logging in, go to\n`https://<domain>/site-admin/self-promotion`. Follow the steps. You'll\nneed shell access to the directory with the Screenshotbot\ninstallation. You'll now have access to an Admin menu on the bottom\nleft.\n\n\n## Calling Screenshotbot from your CI jobs\n\nFirst, you'll need to generate an API key inside Screenshotbot. You'll\nuse this to access the API or the CLI tools.\n\nNext you need to build the CLI tool for your platform. Common Lisp is\na compiled language, so in general you'll need different binaries for\ndifferent platforms (Linux, Mac or Windows; Intel vs ARM). You can\ndownload pre-built binaries for Linux and Mac from\nhttps://screenshotbot.io/recorder.sh.\n\nTo create a binary on a specific platform, call the script\n`scripts/build-cli.lisp`. For instance, if you're using SBCL to build the CLI,\nit will look like:\n\n```\n $ sbcl --script scripts/build-cli.lisp\n```\n\nThis will generate a screenshotbot-cli executable script. Copy it to a\nlocation from which it can be dowloading during your CI runs, or check\nit in to your repository. (As of this writing SBCL generates a binary\nthat is 105MB in size, and 24MB zipped; CCL 100MB/21MB excluding core;\nLispWorks 25MB/4.4MB. LispWorks has extra features to remove unused\ncode.)\n\nFor an example use of this executable see:\nhttps://github.com/tdrhq/fast-example/blob/master/.circleci/config.yml.\nYou'll also have to pass the `--hostname` argument, which will be the\nURL of your Screenshotbot installation.\n\n## Setting up SSO\n\nScreenshotbot comes with an in-built email/password authentication\nsystem, and also supports OpenID Connect out of the box. We also have\nin-built connectors for Google OAuth restricted to domains, which\nmight be easier for smaller companies.\n\nSee [Configuring\nSSO](https://github.com/screenshotbot/screenshotbot-oss/wiki/Configuring-SSO)\nfor a thorough discussion.\n\n\n## Feature Status\n\nNot all the features on [screenshotbot.io](https://screenshotbot.io)\nare available in this OSS repository. We are in the process of moving\nmost integrations here, but that will depend on community interest.\n| Feature               | LispWorks    | CCL          | SBCL              | screenshotbot.io  (Enterprise) |\n|:---------------------:|:------------:|:------------:|:-----------------:|:------------------------------:|\n| **SSO/OAuth**         |              |              |                   |                                |\n| User / Email          | Supported    | Supported    | Supported         | Supported                      |\n| OpenID Connect        | Supported    | Supported    | Supported         | Supported                      |\n| SAML                  | Via Keycloak | Via Keycloak | Via Keycloak      | Supported                      |\n| **VCS Integrations**  |              |              |                   |                                |\n| GitHub                | Supported    | Supported    | Supported         | Supported                      |\n| GitLab                | Supported    | Supported    | Supported         | Supported                      |\n| Phabricator           | Supported    | Supported    | Supported         | Supported                      |\n| BitBucket             | Supported    | Supported    | Supported         | Supported                      |\n| Azure DevOps          | Supported    | Supported    | Supported         | Supported                      |\n| **Tasks Integration** |              |              |                   |                                |\n| Slack                 | Supported    | Supported    | Supported         | Supported                      |\n| Email                 | Supported    | Supported    | Supported         | Supported                      |\n| Jira                  | Planned      | Planned      | Not supported [1] | Supported                      |\n| Trello                | Planned      | Planned      | Not supported [1] | Supported                      |\n| Asana                 | Planned      | Planned      | Not supported [1] | Planned                        |\n| **Annotations** [2]   | Planned      | Planned      | Planned           | Supported                      |\n| Jira                  | Planned      | Planned      | Not supported [1] | Supported                      |\n\nFootnotes:\n\n1. Not supported because SBCL doesn't support Java\n\n2. Annotations allow you to create tasks directly from Screenshotbot\n\n## Upgrading\n\nIn most cases, upgrading will be done via hot-reloading. As a\nsite-admin, you can `git pull` on the repository, on the shell, go to\ngo `https://<domain>/admin` and hit `Reload`. This will bring the new\ncode live without any downtime.\n\nSmall catch: Our database is stored is in-memory (with transactions\nlogged to disk for recovery). Hot-reloading code can force schema\nchanges. For instance, if a field is deleted between two major\nversions, hot reloading will cause that field to be lost forever (but\nthere are snapshots of old versions of the database for recovery). In\ngeneral we'll try to guarantee that between minor versions, on\nreleased commits, as long as you're upgrading (as opposed to\ndowngrading), we'll be able to auto-migrate any schema cleanly.\n\nYou can also upgrade by killing the Lisp process and restarting it. If\nyou do so, we recommend hitting `Snapshot` on the admin menu before\nkilling the Lisp process. However killing the Lisp process can cause a\nminor downtime. You can work around this by using a tool called\n`socketmaster`, but the description of that tool is beyond the scope\nof this document.\n\n## Contributing\n\nWe welcome Pull Requests!\n\nKeep in mind, we'll do the code review on GitHub, but we'll merge it\nvia our internal Phabricator instance. The source of truth for the\ncode is in our internal mono-repo, which is copied over to the OSS\ncode via Copybara, similar to the process that Google and Facebook\nuse. We have open sourced many other projects where the source of\ntruth is GitHub, but Screenshotbot is an actively-developed complex\napplication that makes this difficult.\n\nWe might reject large new features if we think it adds too much\nmaintenance overhead for us. Bug-fixes are always welcome.\n\n## Open Source Support\n\nWe want the Open Source version of Screenshotbot to be successful. That being\nsaid, we are a very small company, so it's difficult for us to dedicate\na lot of resources specifically for keeping Open Source supported across multiple\nplatforms, multiple deployment styles, etc.\n\nYou can help us! Just be patient with us when we ask for specifics about your installation.\nSend us any feedback---good, bad or neutral---to arnold@screenshotbot.io.\nGet other\nteams to use Screenshotbot, either open-source or paid. The more users we have the\neasier it is for us to catch bugs early and stream-line the open source experience.\n\nGive us a star on GitHub!\n\nWe aren't currently accepting donations, but consider convincing your\nteam to pay us for support. If you're a small team or an individual\ndeveloper, just send us an email and we'll make sure you have a plan\nyou can afford. Most of our code is open-source, so by paying us\nyou're directly supporting our open-source contributions.\n\n## Security\n\nWe take security at Screenshotbot very seriously. We partner with an\nexternal security research firm, [Pensive\nSecurity](https://pensivesecurity.io) to perform annual penetration\ntests. The latest Penetration Test reports can be requested on our\n[Trust and Security](https://trust.screenshotbot.io) dashboard.\n\n\n## Authors\n\nScreenshotbot is built and maintained by Arnold Noronha\n(arnold@screenshotbot.io). I also wrote\n[screenshot-tests-for-android](https://github.com/facebook/screenshot-tests-for-android),\nthe de-facto screenshot testing library for Android.\n\n## License\n\nScreenshot is licensed under the Mozilla Public License, v2.\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3.9\"\nservices:\n  screenshotbot:\n    build:\n      context: ${SB_OSS_CONTEXT-.}\n      dockerfile: ${SB_OSS_DOCKERFILE-Dockerfile}\n\n    ports:\n      - \"4091:4091\"\n    volumes:\n      - screenshotbot-oss:/data\n      - ${SB_OSS_CONTEXT-.}:/app\n      - screenshotbot-build:/app/build\n\nvolumes:\n  screenshotbot-oss:\n  screenshotbot-build:\n# test\n"
  },
  {
    "path": "launch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(load \"scripts/prepare-image\")\n(load \"scripts/init\")\n\n#+ccl\n(ql:quickload \"jvm\")\n\n#+ccl\n(jvm:jvm-init)\n\n(ql:quickload \"server\")\n(ql:quickload \"server/slynk\")\n(ql:quickload \"screenshotbot/all\")\n\n(screenshotbot/config:load-config)\n\n(unless (member \"compile\" (uiop:command-line-arguments) :test 'string=)\n  (server:main :acceptor screenshotbot/server:*acceptor*))\n\n(uiop:quit)\n"
  },
  {
    "path": "quicklisp/.gitignore",
    "content": "cache\ndists/quicklisp/software\ndists/quicklisp/archives\ndists/quicklisp/installed\ndists/quicklisp/*.cdb\nretired/\ntmp\n*fasl*\n"
  },
  {
    "path": "quicklisp/asdf.lisp",
    "content": ";;; -*- mode: Lisp; Base: 10 ; Syntax: ANSI-Common-Lisp ; buffer-read-only: t; -*-\n;;; This is ASDF 3.2.1: Another System Definition Facility.\n;;;\n;;; Feedback, bug reports, and patches are all welcome:\n;;; please mail to <asdf-devel@common-lisp.net>.\n;;; Note first that the canonical source for ASDF is presently\n;;; <URL:http://common-lisp.net/project/asdf/>.\n;;;\n;;; If you obtained this copy from anywhere else, and you experience\n;;; trouble using it, or find bugs, you may want to check at the\n;;; location above for a more recent version (and for documentation\n;;; and test files, if your copy came without them) before reporting\n;;; bugs.  There are usually two \"supported\" revisions - the git master\n;;; branch is the latest development version, whereas the git release\n;;; branch may be slightly older but is considered `stable'\n\n;;; -- LICENSE START\n;;; (This is the MIT / X Consortium license as taken from\n;;;  http://www.opensource.org/licenses/mit-license.html on or about\n;;;  Monday; July 13, 2009)\n;;;\n;;; Copyright (c) 2001-2016 Daniel Barlow and contributors\n;;;\n;;; Permission is hereby granted, free of charge, to any person obtaining\n;;; a copy of this software and associated documentation files (the\n;;; \"Software\"), to deal in the Software without restriction, including\n;;; without limitation the rights to use, copy, modify, merge, publish,\n;;; distribute, sublicense, and/or sell copies of the Software, and to\n;;; permit persons to whom the Software is furnished to do so, subject to\n;;; the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n;;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n;;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n;;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n;;;\n;;; -- LICENSE END\n\n;;; The problem with writing a defsystem replacement is bootstrapping:\n;;; we can't use defsystem to compile it.  Hence, all in one file.\n\n;;;; ---------------------------------------------------------------------------\n;;;; Handle ASDF package upgrade, including implementation-dependent magic.\n;;\n;; See https://bugs.launchpad.net/asdf/+bug/485687\n;;\n\n(defpackage :uiop/package\n  ;; CAUTION: we must handle the first few packages specially for hot-upgrade.\n  ;; This package definition MUST NOT change unless its name too changes;\n  ;; if/when it changes, don't forget to add new functions missing from below.\n  ;; Until then, uiop/package is frozen to forever\n  ;; import and export the same exact symbols as for ASDF 2.27.\n  ;; Any other symbol must be import-from'ed and re-export'ed in a different package.\n  (:use :common-lisp)\n  (:export\n   #:find-package* #:find-symbol* #:symbol-call\n   #:intern* #:export* #:import* #:shadowing-import* #:shadow* #:make-symbol* #:unintern*\n   #:symbol-shadowing-p #:home-package-p\n   #:symbol-package-name #:standard-common-lisp-symbol-p\n   #:reify-package #:unreify-package #:reify-symbol #:unreify-symbol\n   #:nuke-symbol-in-package #:nuke-symbol #:rehome-symbol\n   #:ensure-package-unused #:delete-package*\n   #:package-names #:packages-from-names #:fresh-package-name #:rename-package-away\n   #:package-definition-form #:parse-define-package-form\n   #:ensure-package #:define-package))\n\n(in-package :uiop/package)\n\n;;;; General purpose package utilities\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun find-package* (package-designator &optional (error t))\n    (let ((package (find-package package-designator)))\n      (cond\n        (package package)\n        (error (error \"No package named ~S\" (string package-designator)))\n        (t nil))))\n  (defun find-symbol* (name package-designator &optional (error t))\n    \"Find a symbol in a package of given string'ified NAME;\nunlike CL:FIND-SYMBOL, work well with 'modern' case sensitive syntax\nby letting you supply a symbol or keyword for the name;\nalso works well when the package is not present.\nIf optional ERROR argument is NIL, return NIL instead of an error\nwhen the symbol is not found.\"\n    (block nil\n      (let ((package (find-package* package-designator error)))\n        (when package ;; package error handled by find-package* already\n          (multiple-value-bind (symbol status) (find-symbol (string name) package)\n            (cond\n              (status (return (values symbol status)))\n              (error (error \"There is no symbol ~S in package ~S\" name (package-name package))))))\n        (values nil nil))))\n  (defun symbol-call (package name &rest args)\n    \"Call a function associated with symbol of given name in given package,\nwith given ARGS. Useful when the call is read before the package is loaded,\nor when loading the package is optional.\"\n    (apply (find-symbol* name package) args))\n  (defun intern* (name package-designator &optional (error t))\n    (intern (string name) (find-package* package-designator error)))\n  (defun export* (name package-designator)\n    (let* ((package (find-package* package-designator))\n           (symbol (intern* name package)))\n      (export (or symbol (list symbol)) package)))\n  (defun import* (symbol package-designator)\n    (import (or symbol (list symbol)) (find-package* package-designator)))\n  (defun shadowing-import* (symbol package-designator)\n    (shadowing-import (or symbol (list symbol)) (find-package* package-designator)))\n  (defun shadow* (name package-designator)\n    (shadow (list (string name)) (find-package* package-designator)))\n  (defun make-symbol* (name)\n    (etypecase name\n      (string (make-symbol name))\n      (symbol (copy-symbol name))))\n  (defun unintern* (name package-designator &optional (error t))\n    (block nil\n      (let ((package (find-package* package-designator error)))\n        (when package\n          (multiple-value-bind (symbol status) (find-symbol* name package error)\n            (cond\n              (status (unintern symbol package)\n                      (return (values symbol status)))\n              (error (error \"symbol ~A not present in package ~A\"\n                            (string symbol) (package-name package))))))\n        (values nil nil))))\n  (defun symbol-shadowing-p (symbol package)\n    (and (member symbol (package-shadowing-symbols package)) t))\n  (defun home-package-p (symbol package)\n    (and package (let ((sp (symbol-package symbol)))\n                   (and sp (let ((pp (find-package* package)))\n                             (and pp (eq sp pp))))))))\n\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun symbol-package-name (symbol)\n    (let ((package (symbol-package symbol)))\n      (and package (package-name package))))\n  (defun standard-common-lisp-symbol-p (symbol)\n    (multiple-value-bind (sym status) (find-symbol* symbol :common-lisp nil)\n      (and (eq sym symbol) (eq status :external))))\n  (defun reify-package (package &optional package-context)\n    (if (eq package package-context) t\n        (etypecase package\n          (null nil)\n          ((eql (find-package :cl)) :cl)\n          (package (package-name package)))))\n  (defun unreify-package (package &optional package-context)\n    (etypecase package\n      (null nil)\n      ((eql t) package-context)\n      ((or symbol string) (find-package package))))\n  (defun reify-symbol (symbol &optional package-context)\n    (etypecase symbol\n      ((or keyword (satisfies standard-common-lisp-symbol-p)) symbol)\n      (symbol (vector (symbol-name symbol)\n                      (reify-package (symbol-package symbol) package-context)))))\n  (defun unreify-symbol (symbol &optional package-context)\n    (etypecase symbol\n      (symbol symbol)\n      ((simple-vector 2)\n       (let* ((symbol-name (svref symbol 0))\n              (package-foo (svref symbol 1))\n              (package (unreify-package package-foo package-context)))\n         (if package (intern* symbol-name package)\n             (make-symbol* symbol-name)))))))\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defvar *all-package-happiness* '())\n  (defvar *all-package-fishiness* (list t))\n  (defun record-fishy (info)\n    ;;(format t \"~&FISHY: ~S~%\" info)\n    (push info *all-package-fishiness*))\n  (defmacro when-package-fishiness (&body body)\n    `(when *all-package-fishiness* ,@body))\n  (defmacro note-package-fishiness (&rest info)\n    `(when-package-fishiness (record-fishy (list ,@info)))))\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  #+(or clisp clozure)\n  (defun get-setf-function-symbol (symbol)\n    #+clisp (let ((sym (get symbol 'system::setf-function)))\n              (if sym (values sym :setf-function)\n                  (let ((sym (get symbol 'system::setf-expander)))\n                    (if sym (values sym :setf-expander)\n                        (values nil nil)))))\n    #+clozure (gethash symbol ccl::%setf-function-names%))\n  #+(or clisp clozure)\n  (defun set-setf-function-symbol (new-setf-symbol symbol &optional kind)\n    #+clisp (assert (member kind '(:setf-function :setf-expander)))\n    #+clozure (assert (eq kind t))\n    #+clisp\n    (cond\n      ((null new-setf-symbol)\n       (remprop symbol 'system::setf-function)\n       (remprop symbol 'system::setf-expander))\n      ((eq kind :setf-function)\n       (setf (get symbol 'system::setf-function) new-setf-symbol))\n      ((eq kind :setf-expander)\n       (setf (get symbol 'system::setf-expander) new-setf-symbol))\n      (t (error \"invalid kind of setf-function ~S for ~S to be set to ~S\"\n                kind symbol new-setf-symbol)))\n    #+clozure\n    (progn\n      (gethash symbol ccl::%setf-function-names%) new-setf-symbol\n      (gethash new-setf-symbol ccl::%setf-function-name-inverses%) symbol))\n  #+(or clisp clozure)\n  (defun create-setf-function-symbol (symbol)\n    #+clisp (system::setf-symbol symbol)\n    #+clozure (ccl::construct-setf-function-name symbol))\n  (defun set-dummy-symbol (symbol reason other-symbol)\n    (setf (get symbol 'dummy-symbol) (cons reason other-symbol)))\n  (defun make-dummy-symbol (symbol)\n    (let ((dummy (copy-symbol symbol)))\n      (set-dummy-symbol dummy 'replacing symbol)\n      (set-dummy-symbol symbol 'replaced-by dummy)\n      dummy))\n  (defun dummy-symbol (symbol)\n    (get symbol 'dummy-symbol))\n  (defun get-dummy-symbol (symbol)\n    (let ((existing (dummy-symbol symbol)))\n      (if existing (values (cdr existing) (car existing))\n          (make-dummy-symbol symbol))))\n  (defun nuke-symbol-in-package (symbol package-designator)\n    (let ((package (find-package* package-designator))\n          (name (symbol-name symbol)))\n      (multiple-value-bind (sym stat) (find-symbol name package)\n        (when (and (member stat '(:internal :external)) (eq symbol sym))\n          (if (symbol-shadowing-p symbol package)\n              (shadowing-import* (get-dummy-symbol symbol) package)\n              (unintern* symbol package))))))\n  (defun nuke-symbol (symbol &optional (packages (list-all-packages)))\n    #+(or clisp clozure)\n    (multiple-value-bind (setf-symbol kind)\n        (get-setf-function-symbol symbol)\n      (when kind (nuke-symbol setf-symbol)))\n    (loop :for p :in packages :do (nuke-symbol-in-package symbol p)))\n  (defun rehome-symbol (symbol package-designator)\n    \"Changes the home package of a symbol, also leaving it present in its old home if any\"\n    (let* ((name (symbol-name symbol))\n           (package (find-package* package-designator))\n           (old-package (symbol-package symbol))\n           (old-status (and old-package (nth-value 1 (find-symbol name old-package))))\n           (shadowing (and old-package (symbol-shadowing-p symbol old-package) (make-symbol name))))\n      (multiple-value-bind (overwritten-symbol overwritten-symbol-status) (find-symbol name package)\n        (unless (eq package old-package)\n          (let ((overwritten-symbol-shadowing-p\n                  (and overwritten-symbol-status\n                       (symbol-shadowing-p overwritten-symbol package))))\n            (note-package-fishiness\n             :rehome-symbol name\n             (when old-package (package-name old-package)) old-status (and shadowing t)\n             (package-name package) overwritten-symbol-status overwritten-symbol-shadowing-p)\n            (when old-package\n              (if shadowing\n                  (shadowing-import* shadowing old-package))\n              (unintern* symbol old-package))\n            (cond\n              (overwritten-symbol-shadowing-p\n               (shadowing-import* symbol package))\n              (t\n               (when overwritten-symbol-status\n                 (unintern* overwritten-symbol package))\n               (import* symbol package)))\n            (if shadowing\n                (shadowing-import* symbol old-package)\n                (import* symbol old-package))\n            #+(or clisp clozure)\n            (multiple-value-bind (setf-symbol kind)\n                (get-setf-function-symbol symbol)\n              (when kind\n                (let* ((setf-function (fdefinition setf-symbol))\n                       (new-setf-symbol (create-setf-function-symbol symbol)))\n                  (note-package-fishiness\n                   :setf-function\n                   name (package-name package)\n                   (symbol-name setf-symbol) (symbol-package-name setf-symbol)\n                   (symbol-name new-setf-symbol) (symbol-package-name new-setf-symbol))\n                  (when (symbol-package setf-symbol)\n                    (unintern* setf-symbol (symbol-package setf-symbol)))\n                  (setf (fdefinition new-setf-symbol) setf-function)\n                  (set-setf-function-symbol new-setf-symbol symbol kind))))\n            #+(or clisp clozure)\n            (multiple-value-bind (overwritten-setf foundp)\n                (get-setf-function-symbol overwritten-symbol)\n              (when foundp\n                (unintern overwritten-setf)))\n            (when (eq old-status :external)\n              (export* symbol old-package))\n            (when (eq overwritten-symbol-status :external)\n              (export* symbol package))))\n        (values overwritten-symbol overwritten-symbol-status))))\n  (defun ensure-package-unused (package)\n    (loop :for p :in (package-used-by-list package) :do\n      (unuse-package package p)))\n  (defun delete-package* (package &key nuke)\n    (let ((p (find-package package)))\n      (when p\n        (when nuke (do-symbols (s p) (when (home-package-p s p) (nuke-symbol s))))\n        (ensure-package-unused p)\n        (delete-package package))))\n  (defun package-names (package)\n    (cons (package-name package) (package-nicknames package)))\n  (defun packages-from-names (names)\n    (remove-duplicates (remove nil (mapcar #'find-package names)) :from-end t))\n  (defun fresh-package-name (&key (prefix :%TO-BE-DELETED)\n                               separator\n                               (index (random most-positive-fixnum)))\n    (loop :for i :from index\n          :for n = (format nil \"~A~@[~A~D~]\" prefix (and (plusp i) (or separator \"\")) i)\n          :thereis (and (not (find-package n)) n)))\n  (defun rename-package-away (p &rest keys &key prefix &allow-other-keys)\n    (let ((new-name\n            (apply 'fresh-package-name\n                   :prefix (or prefix (format nil \"__~A__\" (package-name p))) keys)))\n      (record-fishy (list :rename-away (package-names p) new-name))\n      (rename-package p new-name))))\n\n\n;;; Communicable representation of symbol and package information\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun package-definition-form (package-designator\n                                  &key (nicknamesp t) (usep t)\n                                    (shadowp t) (shadowing-import-p t)\n                                    (exportp t) (importp t) internp (error t))\n    (let* ((package (or (find-package* package-designator error)\n                        (return-from package-definition-form nil)))\n           (name (package-name package))\n           (nicknames (package-nicknames package))\n           (use (mapcar #'package-name (package-use-list package)))\n           (shadow ())\n           (shadowing-import (make-hash-table :test 'equal))\n           (import (make-hash-table :test 'equal))\n           (export ())\n           (intern ()))\n      (when package\n        (loop :for sym :being :the :symbols :in package\n              :for status = (nth-value 1 (find-symbol* sym package)) :do\n                (ecase status\n                  ((nil :inherited))\n                  ((:internal :external)\n                   (let* ((name (symbol-name sym))\n                          (external (eq status :external))\n                          (home (symbol-package sym))\n                          (home-name (package-name home))\n                          (imported (not (eq home package)))\n                          (shadowing (symbol-shadowing-p sym package)))\n                     (cond\n                       ((and shadowing imported)\n                        (push name (gethash home-name shadowing-import)))\n                       (shadowing\n                        (push name shadow))\n                       (imported\n                        (push name (gethash home-name import))))\n                     (cond\n                       (external\n                        (push name export))\n                       (imported)\n                       (t (push name intern)))))))\n        (labels ((sort-names (names)\n                   (sort (copy-list names) #'string<))\n                 (table-keys (table)\n                   (loop :for k :being :the :hash-keys :of table :collect k))\n                 (when-relevant (key value)\n                   (when value (list (cons key value))))\n                 (import-options (key table)\n                   (loop :for i :in (sort-names (table-keys table))\n                         :collect `(,key ,i ,@(sort-names (gethash i table))))))\n          `(defpackage ,name\n             ,@(when-relevant :nicknames (and nicknamesp (sort-names nicknames)))\n             (:use ,@(and usep (sort-names use)))\n             ,@(when-relevant :shadow (and shadowp (sort-names shadow)))\n             ,@(import-options :shadowing-import-from (and shadowing-import-p shadowing-import))\n             ,@(import-options :import-from (and importp import))\n             ,@(when-relevant :export (and exportp (sort-names export)))\n             ,@(when-relevant :intern (and internp (sort-names intern)))))))))\n\n\n;;; ensure-package, define-package\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun ensure-shadowing-import (name to-package from-package shadowed imported)\n    (check-type name string)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (let ((import-me (find-symbol* name from-package)))\n      (multiple-value-bind (existing status) (find-symbol name to-package)\n        (cond\n          ((gethash name shadowed)\n           (unless (eq import-me existing)\n             (error \"Conflicting shadowings for ~A\" name)))\n          (t\n           (setf (gethash name shadowed) t)\n           (setf (gethash name imported) t)\n           (unless (or (null status)\n                       (and (member status '(:internal :external))\n                            (eq existing import-me)\n                            (symbol-shadowing-p existing to-package)))\n             (note-package-fishiness\n              :shadowing-import name\n              (package-name from-package)\n              (or (home-package-p import-me from-package) (symbol-package-name import-me))\n              (package-name to-package) status\n              (and status (or (home-package-p existing to-package) (symbol-package-name existing)))))\n           (shadowing-import* import-me to-package))))))\n  (defun ensure-imported (import-me into-package &optional from-package)\n    (check-type import-me symbol)\n    (check-type into-package package)\n    (check-type from-package (or null package))\n    (let ((name (symbol-name import-me)))\n      (multiple-value-bind (existing status) (find-symbol name into-package)\n        (cond\n          ((not status)\n           (import* import-me into-package))\n          ((eq import-me existing))\n          (t\n           (let ((shadowing-p (symbol-shadowing-p existing into-package)))\n             (note-package-fishiness\n              :ensure-imported name\n              (and from-package (package-name from-package))\n              (or (home-package-p import-me from-package) (symbol-package-name import-me))\n              (package-name into-package)\n              status\n              (and status (or (home-package-p existing into-package) (symbol-package-name existing)))\n              shadowing-p)\n             (cond\n               ((or shadowing-p (eq status :inherited))\n                (shadowing-import* import-me into-package))\n               (t\n                (unintern* existing into-package)\n                (import* import-me into-package))))))))\n    (values))\n  (defun ensure-import (name to-package from-package shadowed imported)\n    (check-type name string)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (multiple-value-bind (import-me import-status) (find-symbol name from-package)\n      (when (null import-status)\n        (note-package-fishiness\n         :import-uninterned name (package-name from-package) (package-name to-package))\n        (setf import-me (intern* name from-package)))\n      (multiple-value-bind (existing status) (find-symbol name to-package)\n        (cond\n          ((and imported (gethash name imported))\n           (unless (and status (eq import-me existing))\n             (error \"Can't import ~S from both ~S and ~S\"\n                    name (package-name (symbol-package existing)) (package-name from-package))))\n          ((gethash name shadowed)\n           (error \"Can't both shadow ~S and import it from ~S\" name (package-name from-package)))\n          (t\n           (setf (gethash name imported) t))))\n      (ensure-imported import-me to-package from-package)))\n  (defun ensure-inherited (name symbol to-package from-package mixp shadowed imported inherited)\n    (check-type name string)\n    (check-type symbol symbol)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type mixp (member nil t)) ; no cl:boolean on Genera\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (check-type inherited hash-table)\n    (multiple-value-bind (existing status) (find-symbol name to-package)\n      (let* ((sp (symbol-package symbol))\n             (in (gethash name inherited))\n             (xp (and status (symbol-package existing))))\n        (when (null sp)\n          (note-package-fishiness\n           :import-uninterned name\n           (package-name from-package) (package-name to-package) mixp)\n          (import* symbol from-package)\n          (setf sp (package-name from-package)))\n        (cond\n          ((gethash name shadowed))\n          (in\n           (unless (equal sp (first in))\n             (if mixp\n                 (ensure-shadowing-import name to-package (second in) shadowed imported)\n                 (error \"Can't inherit ~S from ~S, it is inherited from ~S\"\n                        name (package-name sp) (package-name (first in))))))\n          ((gethash name imported)\n           (unless (eq symbol existing)\n             (error \"Can't inherit ~S from ~S, it is imported from ~S\"\n                    name (package-name sp) (package-name xp))))\n          (t\n           (setf (gethash name inherited) (list sp from-package))\n           (when (and status (not (eq sp xp)))\n             (let ((shadowing (symbol-shadowing-p existing to-package)))\n               (note-package-fishiness\n                :inherited name\n                (package-name from-package)\n                (or (home-package-p symbol from-package) (symbol-package-name symbol))\n                (package-name to-package)\n                (or (home-package-p existing to-package) (symbol-package-name existing)))\n               (if shadowing (ensure-shadowing-import name to-package from-package shadowed imported)\n                   (unintern* existing to-package)))))))))\n  (defun ensure-mix (name symbol to-package from-package shadowed imported inherited)\n    (check-type name string)\n    (check-type symbol symbol)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (check-type inherited hash-table)\n    (unless (gethash name shadowed)\n      (multiple-value-bind (existing status) (find-symbol name to-package)\n        (let* ((sp (symbol-package symbol))\n               (im (gethash name imported))\n               (in (gethash name inherited)))\n          (cond\n            ((or (null status)\n                 (and status (eq symbol existing))\n                 (and in (eq sp (first in))))\n             (ensure-inherited name symbol to-package from-package t shadowed imported inherited))\n            (in\n             (remhash name inherited)\n             (ensure-shadowing-import name to-package (second in) shadowed imported))\n            (im\n             (error \"Symbol ~S import from ~S~:[~; actually ~:[uninterned~;~:*from ~S~]~] conflicts with existing symbol in ~S~:[~; actually ~:[uninterned~;from ~:*~S~]~]\"\n                    name (package-name from-package)\n                    (home-package-p symbol from-package) (symbol-package-name symbol)\n                    (package-name to-package)\n                    (home-package-p existing to-package) (symbol-package-name existing)))\n            (t\n             (ensure-inherited name symbol to-package from-package t shadowed imported inherited)))))))\n\n  (defun recycle-symbol (name recycle exported)\n    ;; Takes a symbol NAME (a string), a list of package designators for RECYCLE\n    ;; packages, and a hash-table of names (strings) of symbols scheduled to be\n    ;; EXPORTED from the package being defined. It returns two values, the\n    ;; symbol found (if any, or else NIL), and a boolean flag indicating whether\n    ;; a symbol was found. The caller (DEFINE-PACKAGE) will then do the\n    ;; re-homing of the symbol, etc.\n    (check-type name string)\n    (check-type recycle list)\n    (check-type exported hash-table)\n    (when (gethash name exported) ;; don't bother recycling private symbols\n      (let (recycled foundp)\n        (dolist (r recycle (values recycled foundp))\n          (multiple-value-bind (symbol status) (find-symbol name r)\n            (when (and status (home-package-p symbol r))\n              (cond\n                (foundp\n                 ;; (nuke-symbol symbol)) -- even simple variable names like O or C will do that.\n                 (note-package-fishiness :recycled-duplicate name (package-name foundp) (package-name r)))\n                (t\n                 (setf recycled symbol foundp r)))))))))\n  (defun symbol-recycled-p (sym recycle)\n    (check-type sym symbol)\n    (check-type recycle list)\n    (and (member (symbol-package sym) recycle) t))\n  (defun ensure-symbol (name package intern recycle shadowed imported inherited exported)\n    (check-type name string)\n    (check-type package package)\n    (check-type intern (member nil t)) ; no cl:boolean on Genera\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (check-type inherited hash-table)\n    (unless (or (gethash name shadowed)\n                (gethash name imported)\n                (gethash name inherited))\n      (multiple-value-bind (existing status)\n          (find-symbol name package)\n        (multiple-value-bind (recycled previous) (recycle-symbol name recycle exported)\n          (cond\n            ((and status (eq existing recycled) (eq previous package)))\n            (previous\n             (rehome-symbol recycled package))\n            ((and status (eq package (symbol-package existing))))\n            (t\n             (when status\n               (note-package-fishiness\n                :ensure-symbol name\n                (reify-package (symbol-package existing) package)\n                status intern)\n               (unintern existing))\n             (when intern\n               (intern* name package))))))))\n  (declaim (ftype (function (t t t &optional t) t) ensure-exported))\n  (defun ensure-exported-to-user (name symbol to-package &optional recycle)\n    (check-type name string)\n    (check-type symbol symbol)\n    (check-type to-package package)\n    (check-type recycle list)\n    (assert (equal name (symbol-name symbol)))\n    (multiple-value-bind (existing status) (find-symbol name to-package)\n      (unless (and status (eq symbol existing))\n        (let ((accessible\n                (or (null status)\n                    (let ((shadowing (symbol-shadowing-p existing to-package))\n                          (recycled (symbol-recycled-p existing recycle)))\n                      (unless (and shadowing (not recycled))\n                        (note-package-fishiness\n                         :ensure-export name (symbol-package-name symbol)\n                         (package-name to-package)\n                         (or (home-package-p existing to-package) (symbol-package-name existing))\n                         status shadowing)\n                        (if (or (eq status :inherited) shadowing)\n                            (shadowing-import* symbol to-package)\n                            (unintern existing to-package))\n                        t)))))\n          (when (and accessible (eq status :external))\n            (ensure-exported name symbol to-package recycle))))))\n  (defun ensure-exported (name symbol from-package &optional recycle)\n    (dolist (to-package (package-used-by-list from-package))\n      (ensure-exported-to-user name symbol to-package recycle))\n    (unless (eq from-package (symbol-package symbol))\n      (ensure-imported symbol from-package))\n    (export* name from-package))\n  (defun ensure-export (name from-package &optional recycle)\n    (multiple-value-bind (symbol status) (find-symbol* name from-package)\n      (unless (eq status :external)\n        (ensure-exported name symbol from-package recycle))))\n  (defun ensure-package (name &key\n                                nicknames documentation use\n                                shadow shadowing-import-from\n                                import-from export intern\n                                recycle mix reexport\n                                unintern)\n    #+genera (declare (ignore documentation))\n    (let* ((package-name (string name))\n           (nicknames (mapcar #'string nicknames))\n           (names (cons package-name nicknames))\n           (previous (packages-from-names names))\n           (discarded (cdr previous))\n           (to-delete ())\n           (package (or (first previous) (make-package package-name :nicknames nicknames)))\n           (recycle (packages-from-names recycle))\n           (use (mapcar 'find-package* use))\n           (mix (mapcar 'find-package* mix))\n           (reexport (mapcar 'find-package* reexport))\n           (shadow (mapcar 'string shadow))\n           (export (mapcar 'string export))\n           (intern (mapcar 'string intern))\n           (unintern (mapcar 'string unintern))\n           (shadowed (make-hash-table :test 'equal)) ; string to bool\n           (imported (make-hash-table :test 'equal)) ; string to bool\n           (exported (make-hash-table :test 'equal)) ; string to bool\n           ;; string to list home package and use package:\n           (inherited (make-hash-table :test 'equal)))\n      (when-package-fishiness (record-fishy package-name))\n      #-genera\n      (when documentation (setf (documentation package t) documentation))\n      (loop :for p :in (set-difference (package-use-list package) (append mix use))\n            :do (note-package-fishiness :over-use name (package-names p))\n                (unuse-package p package))\n      (loop :for p :in discarded\n            :for n = (remove-if #'(lambda (x) (member x names :test 'equal))\n                                (package-names p))\n            :do (note-package-fishiness :nickname name (package-names p))\n                (cond (n (rename-package p (first n) (rest n)))\n                      (t (rename-package-away p)\n                         (push p to-delete))))\n      (rename-package package package-name nicknames)\n      (dolist (name unintern)\n        (multiple-value-bind (existing status) (find-symbol name package)\n          (when status\n            (unless (eq status :inherited)\n              (note-package-fishiness\n               :unintern (package-name package) name (symbol-package-name existing) status)\n              (unintern* name package nil)))))\n      (dolist (name export)\n        (setf (gethash name exported) t))\n      (dolist (p reexport)\n        (do-external-symbols (sym p)\n          (setf (gethash (string sym) exported) t)))\n      (do-external-symbols (sym package)\n        (let ((name (symbol-name sym)))\n          (unless (gethash name exported)\n            (note-package-fishiness\n             :over-export (package-name package) name\n             (or (home-package-p sym package) (symbol-package-name sym)))\n            (unexport sym package))))\n      (dolist (name shadow)\n        (setf (gethash name shadowed) t)\n        (multiple-value-bind (existing status) (find-symbol name package)\n          (multiple-value-bind (recycled previous) (recycle-symbol name recycle exported)\n            (let ((shadowing (and status (symbol-shadowing-p existing package))))\n              (cond\n                ((eq previous package))\n                (previous\n                 (rehome-symbol recycled package))\n                ((or (member status '(nil :inherited))\n                     (home-package-p existing package)))\n                (t\n                 (let ((dummy (make-symbol name)))\n                   (note-package-fishiness\n                    :shadow-imported (package-name package) name\n                    (symbol-package-name existing) status shadowing)\n                   (shadowing-import* dummy package)\n                   (import* dummy package)))))))\n        (shadow* name package))\n      (loop :for (p . syms) :in shadowing-import-from\n            :for pp = (find-package* p) :do\n              (dolist (sym syms) (ensure-shadowing-import (string sym) package pp shadowed imported)))\n      (loop :for p :in mix\n            :for pp = (find-package* p) :do\n              (do-external-symbols (sym pp) (ensure-mix (symbol-name sym) sym package pp shadowed imported inherited)))\n      (loop :for (p . syms) :in import-from\n            :for pp = (find-package p) :do\n              (dolist (sym syms) (ensure-import (symbol-name sym) package pp shadowed imported)))\n      (dolist (p (append use mix))\n        (do-external-symbols (sym p) (ensure-inherited (string sym) sym package p nil shadowed imported inherited))\n        (use-package p package))\n      (loop :for name :being :the :hash-keys :of exported :do\n        (ensure-symbol name package t recycle shadowed imported inherited exported)\n        (ensure-export name package recycle))\n      (dolist (name intern)\n        (ensure-symbol name package t recycle shadowed imported inherited exported))\n      (do-symbols (sym package)\n        (ensure-symbol (symbol-name sym) package nil recycle shadowed imported inherited exported))\n      (map () 'delete-package* to-delete)\n      package)))\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun parse-define-package-form (package clauses)\n    (loop\n      :with use-p = nil :with recycle-p = nil\n      :with documentation = nil\n      :for (kw . args) :in clauses\n      :when (eq kw :nicknames) :append args :into nicknames :else\n      :when (eq kw :documentation)\n        :do (cond\n              (documentation (error \"define-package: can't define documentation twice\"))\n              ((or (atom args) (cdr args)) (error \"define-package: bad documentation\"))\n              (t (setf documentation (car args)))) :else\n      :when (eq kw :use) :append args :into use :and :do (setf use-p t) :else\n      :when (eq kw :shadow) :append args :into shadow :else\n      :when (eq kw :shadowing-import-from) :collect args :into shadowing-import-from :else\n      :when (eq kw :import-from) :collect args :into import-from :else\n      :when (eq kw :export) :append args :into export :else\n      :when (eq kw :intern) :append args :into intern :else\n      :when (eq kw :recycle) :append args :into recycle :and :do (setf recycle-p t) :else\n      :when (eq kw :mix) :append args :into mix :else\n      :when (eq kw :reexport) :append args :into reexport :else\n      :when (eq kw :use-reexport) :append args :into use :and :append args :into reexport\n        :and :do (setf use-p t) :else\n      :when (eq kw :mix-reexport) :append args :into mix :and :append args :into reexport\n        :and :do (setf use-p t) :else\n      :when (eq kw :unintern) :append args :into unintern :else\n        :do (error \"unrecognized define-package keyword ~S\" kw)\n      :finally (return `(,package\n                         :nicknames ,nicknames :documentation ,documentation\n                         :use ,(if use-p use '(:common-lisp))\n                         :shadow ,shadow :shadowing-import-from ,shadowing-import-from\n                         :import-from ,import-from :export ,export :intern ,intern\n                         :recycle ,(if recycle-p recycle (cons package nicknames))\n                         :mix ,mix :reexport ,reexport :unintern ,unintern)))))\n\n(defmacro define-package (package &rest clauses)\n  \"DEFINE-PACKAGE takes a PACKAGE and a number of CLAUSES, of the form\n\\(KEYWORD . ARGS\\).\nDEFINE-PACKAGE supports the following keywords:\nUSE, SHADOW, SHADOWING-IMPORT-FROM, IMPORT-FROM, EXPORT, INTERN -- as per CL:DEFPACKAGE.\nRECYCLE -- Recycle the package's exported symbols from the specified packages,\nin order.  For every symbol scheduled to be exported by the DEFINE-PACKAGE,\neither through an :EXPORT option or a :REEXPORT option, if the symbol exists in\none of the :RECYCLE packages, the first such symbol is re-homed to the package\nbeing defined.\nFor the sake of idempotence, it is important that the package being defined\nshould appear in first position if it already exists, and even if it doesn't,\nahead of any package that is not going to be deleted afterwards and never\ncreated again. In short, except for special cases, always make it the first\npackage on the list if the list is not empty.\nMIX -- Takes a list of package designators.  MIX behaves like\n\\(:USE PKG1 PKG2 ... PKGn\\) but additionally uses :SHADOWING-IMPORT-FROM to\nresolve conflicts in favor of the first found symbol.  It may still yield\nan error if there is a conflict with an explicitly :IMPORT-FROM symbol.\nREEXPORT -- Takes a list of package designators.  For each package, p, in the list,\nexport symbols with the same name as those exported from p.  Note that in the case\nof shadowing, etc. the symbols with the same name may not be the same symbols.\nUNINTERN -- Remove symbols here from PACKAGE.\"\n  (let ((ensure-form\n          `(apply 'ensure-package ',(parse-define-package-form package clauses))))\n    `(progn\n       #+(or clasp ecl gcl mkcl) (defpackage ,package (:use))\n       (eval-when (:compile-toplevel :load-toplevel :execute)\n         ,ensure-form))))\n;;;; -------------------------------------------------------------------------\n;;;; Handle compatibility with multiple implementations.\n;;; This file is for papering over the deficiencies and peculiarities\n;;; of various Common Lisp implementations.\n;;; For implementation-specific access to the system, see os.lisp instead.\n;;; A few functions are defined here, but actually exported from utility;\n;;; from this package only common-lisp symbols are exported.\n\n(uiop/package:define-package :uiop/common-lisp\n  (:nicknames :uoip/cl)\n  (:use :uiop/package)\n  (:use-reexport #-genera :common-lisp #+genera :future-common-lisp)\n  #+allegro (:intern #:*acl-warn-save*)\n  #+cormanlisp (:shadow #:user-homedir-pathname)\n  #+cormanlisp\n  (:export\n   #:logical-pathname #:translate-logical-pathname\n   #:make-broadcast-stream #:file-namestring)\n  #+genera (:shadowing-import-from :scl #:boolean)\n  #+genera (:export #:boolean #:ensure-directories-exist #:read-sequence #:write-sequence)\n  #+(or mcl cmucl) (:shadow #:user-homedir-pathname))\n(in-package :uiop/common-lisp)\n\n#-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mcl mkcl sbcl scl xcl)\n(error \"ASDF is not supported on your implementation. Please help us port it.\")\n\n;; (declaim (optimize (speed 1) (debug 3) (safety 3))) ; DON'T: trust implementation defaults.\n\n\n;;;; Early meta-level tweaks\n\n#+(or allegro clasp clisp clozure cmucl ecl mkcl sbcl)\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (when (and #+allegro (member :ics *features*)\n             #+(or clasp clisp cmucl ecl mkcl) (member :unicode *features*)\n             #+clozure (member :openmcl-unicode-strings *features*)\n             #+sbcl (member :sb-unicode *features*))\n    ;; Check for unicode at runtime, so that a hypothetical FASL compiled with unicode\n    ;; but loaded in a non-unicode setting (e.g. on Allegro) won't tell a lie.\n    (pushnew :asdf-unicode *features*)))\n\n#+allegro\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  ;; We need to disable autoloading BEFORE any mention of package ASDF.\n  ;; In particular, there must NOT be a mention of package ASDF in the defpackage of this file\n  ;; or any previous file.\n  (setf excl::*autoload-package-name-alist*\n        (remove \"asdf\" excl::*autoload-package-name-alist*\n                :test 'equalp :key 'car))\n  (defparameter *acl-warn-save*\n    (when (boundp 'excl:*warn-on-nested-reader-conditionals*)\n      excl:*warn-on-nested-reader-conditionals*))\n  (when (boundp 'excl:*warn-on-nested-reader-conditionals*)\n    (setf excl:*warn-on-nested-reader-conditionals* nil))\n  (setf *print-readably* nil))\n\n#+clasp\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (setf *load-verbose* nil)\n  (defun use-ecl-byte-compiler-p () nil))\n\n#+clozure (in-package :ccl)\n#+(and clozure windows-target) ;; See http://trac.clozure.com/ccl/ticket/1117\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (unless (fboundp 'external-process-wait)\n    (in-development-mode\n     (defun external-process-wait (proc)\n       (when (and (external-process-pid proc) (eq (external-process-%status proc) :running))\n         (with-interrupts-enabled\n             (wait-on-semaphore (external-process-completed proc))))\n       (values (external-process-%exit-code proc)\n               (external-process-%status proc))))))\n#+clozure (in-package :uiop/common-lisp) ;; back in this package.\n\n#+cmucl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (setf ext:*gc-verbose* nil)\n  (defun user-homedir-pathname ()\n    (first (ext:search-list (cl:user-homedir-pathname)))))\n\n#+cormanlisp\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (deftype logical-pathname () nil)\n  (defun make-broadcast-stream () *error-output*)\n  (defun translate-logical-pathname (x) x)\n  (defun user-homedir-pathname (&optional host)\n    (declare (ignore host))\n    (parse-namestring (format nil \"~A\\\\\" (cl:user-homedir-pathname))))\n  (defun file-namestring (p)\n    (setf p (pathname p))\n    (format nil \"~@[~A~]~@[.~A~]\" (pathname-name p) (pathname-type p))))\n\n#+ecl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (setf *load-verbose* nil)\n  (defun use-ecl-byte-compiler-p () (and (member :ecl-bytecmp *features*) t))\n  (unless (use-ecl-byte-compiler-p) (require :cmp)))\n\n#+gcl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (unless (member :ansi-cl *features*)\n    (error \"ASDF only supports GCL in ANSI mode. Aborting.~%\"))\n  (setf compiler::*compiler-default-type* (pathname \"\")\n        compiler::*lsp-ext* \"\")\n  #.(let ((code ;; Only support very recent GCL 2.7.0 from November 2013 or later.\n            (cond\n              #+gcl\n              ((or (< system::*gcl-major-version* 2)\n                   (and (= system::*gcl-major-version* 2)\n                        (< system::*gcl-minor-version* 7)))\n               '(error \"GCL 2.7 or later required to use ASDF\")))))\n      (eval code)\n      code))\n\n#+genera\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (unless (fboundp 'lambda)\n    (defmacro lambda (&whole form &rest bvl-decls-and-body)\n      (declare (ignore bvl-decls-and-body)(zwei::indentation 1 1))\n      `#',(cons 'lisp::lambda (cdr form))))\n  (unless (fboundp 'ensure-directories-exist)\n    (defun ensure-directories-exist (path)\n      (fs:create-directories-recursively (pathname path))))\n  (unless (fboundp 'read-sequence)\n    (defun read-sequence (sequence stream &key (start 0) end)\n      (scl:send stream :string-in nil sequence start end)))\n  (unless (fboundp 'write-sequence)\n    (defun write-sequence (sequence stream &key (start 0) end)\n      (scl:send stream :string-out sequence start end)\n      sequence)))\n\n#+lispworks\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  ;; lispworks 3 and earlier cannot be checked for so we always assume\n  ;; at least version 4\n  (unless (member :lispworks4 *features*)\n    (pushnew :lispworks5+ *features*)\n    (unless (member :lispworks5 *features*)\n      (pushnew :lispworks6+ *features*)\n      (unless (member :lispworks6 *features*)\n        (pushnew :lispworks7+ *features*)))))\n\n#.(or #+mcl ;; the #$ doesn't work on other lisps, even protected by #+mcl, so we use this trick\n      (read-from-string\n       \"(eval-when (:load-toplevel :compile-toplevel :execute)\n          (ccl:define-entry-point (_getenv \\\"getenv\\\") ((name :string)) :string)\n          (ccl:define-entry-point (_system \\\"system\\\") ((name :string)) :int)\n          ;; Note: ASDF may expect user-homedir-pathname to provide\n          ;; the pathname of the current user's home directory, whereas\n          ;; MCL by default provides the directory from which MCL was started.\n          ;; See http://code.google.com/p/mcl/wiki/Portability\n          (defun user-homedir-pathname ()\n            (ccl::findfolder #$kuserdomain #$kCurrentUserFolderType))\n          (defun probe-posix (posix-namestring)\n            \\\"If a file exists for the posix namestring, return the pathname\\\"\n            (ccl::with-cstrs ((cpath posix-namestring))\n              (ccl::rlet ((is-dir :boolean)\n                          (fsref :fsref))\n                (when (eq #$noerr (#_fspathmakeref cpath fsref is-dir))\n                  (ccl::%path-from-fsref fsref is-dir))))))\"))\n\n#+mkcl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (require :cmp)\n  (setq clos::*redefine-class-in-place* t)) ;; Make sure we have strict ANSI class redefinition semantics\n\n\n;;;; Looping\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defmacro loop* (&rest rest)\n    #-genera `(loop ,@rest)\n    #+genera `(lisp:loop ,@rest))) ;; In genera, CL:LOOP can't destructure, so we use LOOP*. Sigh.\n\n\n;;;; compatfmt: avoid fancy format directives when unsupported\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun frob-substrings (string substrings &optional frob)\n    \"for each substring in SUBSTRINGS, find occurrences of it within STRING\nthat don't use parts of matched occurrences of previous strings, and\nFROB them, that is to say, remove them if FROB is NIL,\nreplace by FROB if FROB is a STRING, or if FROB is a FUNCTION,\ncall FROB with the match and a function that emits a string in the output.\nReturn a string made of the parts not omitted or emitted by FROB.\"\n    (declare (optimize (speed 0) (safety #-gcl 3 #+gcl 0) (debug 3)))\n    (let ((length (length string)) (stream nil))\n      (labels ((emit-string (x &optional (start 0) (end (length x)))\n                 (when (< start end)\n                   (unless stream (setf stream (make-string-output-stream)))\n                   (write-string x stream :start start :end end)))\n               (emit-substring (start end)\n                 (when (and (zerop start) (= end length))\n                   (return-from frob-substrings string))\n                 (emit-string string start end))\n               (recurse (substrings start end)\n                 (cond\n                   ((>= start end))\n                   ((null substrings) (emit-substring start end))\n                   (t (let* ((sub-spec (first substrings))\n                             (sub (if (consp sub-spec) (car sub-spec) sub-spec))\n                             (fun (if (consp sub-spec) (cdr sub-spec) frob))\n                             (found (search sub string :start2 start :end2 end))\n                             (more (rest substrings)))\n                        (cond\n                          (found\n                           (recurse more start found)\n                           (etypecase fun\n                             (null)\n                             (string (emit-string fun))\n                             (function (funcall fun sub #'emit-string)))\n                           (recurse substrings (+ found (length sub)) end))\n                          (t\n                           (recurse more start end))))))))\n        (recurse substrings 0 length))\n      (if stream (get-output-stream-string stream) \"\")))\n\n  (defmacro compatfmt (format)\n    #+(or gcl genera)\n    (frob-substrings format `(\"~3i~_\" #+genera ,@'(\"~@<\" \"~@;\" \"~@:>\" \"~:>\")))\n    #-(or gcl genera) format))\n;;;; -------------------------------------------------------------------------\n;;;; General Purpose Utilities for ASDF\n\n(uiop/package:define-package :uiop/utility\n  (:use :uiop/common-lisp :uiop/package)\n  ;; import and reexport a few things defined in :uiop/common-lisp\n  (:import-from :uiop/common-lisp #:compatfmt #:loop* #:frob-substrings\n   #+(or clasp ecl) #:use-ecl-byte-compiler-p #+mcl #:probe-posix)\n  (:export #:compatfmt #:loop* #:frob-substrings #:compatfmt\n   #+(or clasp ecl) #:use-ecl-byte-compiler-p #+mcl #:probe-posix)\n  (:export\n   ;; magic helper to define debugging functions:\n   #:uiop-debug #:load-uiop-debug-utility #:*uiop-debug-utility*\n   #:with-upgradability ;; (un)defining functions in an upgrade-friendly way\n   #:defun* #:defgeneric*\n   #:nest #:if-let ;; basic flow control\n   #:parse-body ;; macro definition helper\n   #:while-collecting #:appendf #:length=n-p #:ensure-list ;; lists\n   #:remove-plist-keys #:remove-plist-key ;; plists\n   #:emptyp ;; sequences\n   #:+non-base-chars-exist-p+ ;; characters\n   #:+max-character-type-index+ #:character-type-index #:+character-types+\n   #:base-string-p #:strings-common-element-type #:reduce/strcat #:strcat ;; strings\n   #:first-char #:last-char #:split-string #:stripln #:+cr+ #:+lf+ #:+crlf+\n   #:string-prefix-p #:string-enclosed-p #:string-suffix-p\n   #:standard-case-symbol-name #:find-standard-case-symbol ;; symbols\n   #:coerce-class ;; CLOS\n   #:stamp< #:stamps< #:stamp*< #:stamp<= ;; stamps\n   #:earlier-stamp #:stamps-earliest #:earliest-stamp\n   #:later-stamp #:stamps-latest #:latest-stamp #:latest-stamp-f\n   #:list-to-hash-set #:ensure-gethash ;; hash-table\n   #:ensure-function #:access-at #:access-at-count ;; functions\n   #:call-function #:call-functions #:register-hook-function\n   #:lexicographic< #:lexicographic<= ;; version\n   #:simple-style-warning #:style-warn ;; simple style warnings\n   #:match-condition-p #:match-any-condition-p ;; conditions\n   #:call-with-muffled-conditions #:with-muffled-conditions\n   #:not-implemented-error #:parameter-error))\n(in-package :uiop/utility)\n\n;;;; Defining functions in a way compatible with hot-upgrade:\n;; DEFUN* and DEFGENERIC* use FMAKUNBOUND to delete any previous fdefinition,\n;; thus replacing the function without warning or error\n;; even if the signature and/or generic-ness of the function has changed.\n;; For a generic function, this invalidates any previous DEFMETHOD.\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (macrolet\n      ((defdef (def* def)\n         `(defmacro ,def* (name formals &rest rest)\n            (destructuring-bind (name &key (supersede t))\n                (if (or (atom name) (eq (car name) 'setf))\n                    (list name :supersede nil)\n                    name)\n              (declare (ignorable supersede))\n              `(progn\n                 ;; We usually try to do it only for the functions that need it,\n                 ;; which happens in asdf/upgrade - however, for ECL, we need this hammer.\n                 ,@(when supersede\n                     `((fmakunbound ',name)))\n                 ,@(when (and #+(or clasp ecl) (symbolp name)) ; fails for setf functions on ecl\n                     `((declaim (notinline ,name))))\n                 (,',def ,name ,formals ,@rest))))))\n    (defdef defgeneric* defgeneric)\n    (defdef defun* defun))\n  (defmacro with-upgradability ((&optional) &body body)\n    \"Evaluate BODY at compile- load- and run- times, with DEFUN and DEFGENERIC modified\nto also declare the functions NOTINLINE and to accept a wrapping the function name\nspecification into a list with keyword argument SUPERSEDE (which defaults to T if the name\nis not wrapped, and NIL if it is wrapped). If SUPERSEDE is true, call UNDEFINE-FUNCTION\nto supersede any previous definition.\"\n    `(eval-when (:compile-toplevel :load-toplevel :execute)\n       ,@(loop :for form :in body :collect\n               (if (consp form)\n                   (destructuring-bind (car . cdr) form\n                     (case car\n                       ((defun) `(defun* ,@cdr))\n                       ((defgeneric) `(defgeneric* ,@cdr))\n                       (otherwise form)))\n                   form)))))\n\n;;; Magic debugging help. See contrib/debug.lisp\n(with-upgradability ()\n  (defvar *uiop-debug-utility*\n    '(or (ignore-errors\n          (symbol-call :asdf :system-relative-pathname :uiop \"contrib/debug.lisp\"))\n      (symbol-call :uiop/pathname :subpathname (user-homedir-pathname) \"common-lisp/asdf/uiop/contrib/debug.lisp\"))\n    \"form that evaluates to the pathname to your favorite debugging utilities\")\n\n  (defmacro uiop-debug (&rest keys)\n    `(eval-when (:compile-toplevel :load-toplevel :execute)\n       (load-uiop-debug-utility ,@keys)))\n\n  (defun load-uiop-debug-utility (&key package utility-file)\n    (let* ((*package* (if package (find-package package) *package*))\n           (keyword (read-from-string\n                     (format nil \":DBG-~:@(~A~)\" (package-name *package*)))))\n      (unless (member keyword *features*)\n        (let* ((utility-file (or utility-file *uiop-debug-utility*))\n               (file (ignore-errors (probe-file (eval utility-file)))))\n          (if file (load file)\n              (error \"Failed to locate debug utility file: ~S\" utility-file)))))))\n\n;;; Flow control\n(with-upgradability ()\n  (defmacro nest (&rest things)\n    \"Macro to do keep code nesting and indentation under control.\" ;; Thanks to mbaringer\n    (reduce #'(lambda (outer inner) `(,@outer ,inner))\n            things :from-end t))\n\n  (defmacro if-let (bindings &body (then-form &optional else-form)) ;; from alexandria\n    ;; bindings can be (var form) or ((var1 form1) ...)\n    (let* ((binding-list (if (and (consp bindings) (symbolp (car bindings)))\n                             (list bindings)\n                             bindings))\n           (variables (mapcar #'car binding-list)))\n      `(let ,binding-list\n         (if (and ,@variables)\n             ,then-form\n             ,else-form)))))\n\n;;; Macro definition helper\n(with-upgradability ()\n  (defun parse-body (body &key documentation whole) ;; from alexandria\n    \"Parses BODY into (values remaining-forms declarations doc-string).\nDocumentation strings are recognized only if DOCUMENTATION is true.\nSyntax errors in body are signalled and WHOLE is used in the signal\narguments when given.\"\n    (let ((doc nil)\n          (decls nil)\n          (current nil))\n      (tagbody\n       :declarations\n         (setf current (car body))\n         (when (and documentation (stringp current) (cdr body))\n           (if doc\n               (error \"Too many documentation strings in ~S.\" (or whole body))\n               (setf doc (pop body)))\n           (go :declarations))\n         (when (and (listp current) (eql (first current) 'declare))\n           (push (pop body) decls)\n           (go :declarations)))\n      (values body (nreverse decls) doc))))\n\n\n;;; List manipulation\n(with-upgradability ()\n  (defmacro while-collecting ((&rest collectors) &body body)\n    \"COLLECTORS should be a list of names for collections.  A collector\ndefines a function that, when applied to an argument inside BODY, will\nadd its argument to the corresponding collection.  Returns multiple values,\na list for each collection, in order.\n   E.g.,\n\\(while-collecting \\(foo bar\\)\n           \\(dolist \\(x '\\(\\(a 1\\) \\(b 2\\) \\(c 3\\)\\)\\)\n             \\(foo \\(first x\\)\\)\n             \\(bar \\(second x\\)\\)\\)\\)\nReturns two values: \\(A B C\\) and \\(1 2 3\\).\"\n    (let ((vars (mapcar #'(lambda (x) (gensym (symbol-name x))) collectors))\n          (initial-values (mapcar (constantly nil) collectors)))\n      `(let ,(mapcar #'list vars initial-values)\n         (flet ,(mapcar #'(lambda (c v) `(,c (x) (push x ,v) (values))) collectors vars)\n           ,@body\n           (values ,@(mapcar #'(lambda (v) `(reverse ,v)) vars))))))\n\n  (define-modify-macro appendf (&rest args)\n    append \"Append onto list\") ;; only to be used on short lists.\n\n  (defun length=n-p (x n) ;is it that (= (length x) n) ?\n    (check-type n (integer 0 *))\n    (loop\n      :for l = x :then (cdr l)\n      :for i :downfrom n :do\n        (cond\n          ((zerop i) (return (null l)))\n          ((not (consp l)) (return nil)))))\n\n  (defun ensure-list (x)\n    (if (listp x) x (list x))))\n\n\n;;; Remove a key from a plist, i.e. for keyword argument cleanup\n(with-upgradability ()\n  (defun remove-plist-key (key plist)\n    \"Remove a single key from a plist\"\n    (loop* :for (k v) :on plist :by #'cddr\n           :unless (eq k key)\n           :append (list k v)))\n\n  (defun remove-plist-keys (keys plist)\n    \"Remove a list of keys from a plist\"\n    (loop* :for (k v) :on plist :by #'cddr\n           :unless (member k keys)\n           :append (list k v))))\n\n\n;;; Sequences\n(with-upgradability ()\n  (defun emptyp (x)\n    \"Predicate that is true for an empty sequence\"\n    (or (null x) (and (vectorp x) (zerop (length x))))))\n\n\n;;; Characters\n(with-upgradability ()\n  ;; base-char != character on ECL, LW, SBCL, Genera.\n  ;; NB: We assume a total order on character types.\n  ;; If that's not true... this code will need to be updated.\n  (defparameter +character-types+ ;; assuming a simple hierarchy\n    #.(coerce (loop* :for (type next) :on\n                     '(;; In SCL, all characters seem to be 16-bit base-char\n                       ;; Yet somehow character fails to be a subtype of base-char\n                       #-scl base-char\n                       ;; LW6 has BASE-CHAR < SIMPLE-CHAR < CHARACTER\n                       ;; LW7 has BASE-CHAR < BMP-CHAR < SIMPLE-CHAR = CHARACTER\n                       #+lispworks7+ lw:bmp-char\n                       #+lispworks lw:simple-char\n                       character)\n                     :unless (and next (subtypep next type))\n                     :collect type) 'vector))\n  (defparameter +max-character-type-index+ (1- (length +character-types+)))\n  (defconstant +non-base-chars-exist-p+ (plusp +max-character-type-index+))\n  (when +non-base-chars-exist-p+ (pushnew :non-base-chars-exist-p *features*)))\n\n(with-upgradability ()\n  (defun character-type-index (x)\n    (declare (ignorable x))\n    #.(case +max-character-type-index+\n        (0 0)\n        (1 '(etypecase x\n             (character (if (typep x 'base-char) 0 1))\n             (symbol (if (subtypep x 'base-char) 0 1))))\n        (otherwise\n         '(or (position-if (etypecase x\n                             (character #'(lambda (type) (typep x type)))\n                             (symbol #'(lambda (type) (subtypep x type))))\n               +character-types+)\n           (error \"Not a character or character type: ~S\" x))))))\n\n\n;;; Strings\n(with-upgradability ()\n  (defun base-string-p (string)\n    \"Does the STRING only contain BASE-CHARs?\"\n    (declare (ignorable string))\n    (and #+non-base-chars-exist-p (eq 'base-char (array-element-type string))))\n\n  (defun strings-common-element-type (strings)\n    \"What least subtype of CHARACTER can contain all the elements of all the STRINGS?\"\n    (declare (ignorable strings))\n    #.(if +non-base-chars-exist-p+\n          `(aref +character-types+\n            (loop :with index = 0 :for s :in strings :do\n              (flet ((consider (i)\n                       (cond ((= i ,+max-character-type-index+) (return i))\n                             ,@(when (> +max-character-type-index+ 1) `(((> i index) (setf index i)))))))\n                (cond\n                  ((emptyp s)) ;; NIL or empty string\n                  ((characterp s) (consider (character-type-index s)))\n                  ((stringp s) (let ((string-type-index\n                                       (character-type-index (array-element-type s))))\n                                 (unless (>= index string-type-index)\n                                   (loop :for c :across s :for i = (character-type-index c)\n                                         :do (consider i)\n                                         ,@(when (> +max-character-type-index+ 1)\n                                             `((when (= i string-type-index) (return))))))))\n                  (t (error \"Invalid string designator ~S for ~S\" s 'strings-common-element-type))))\n                  :finally (return index)))\n          ''character))\n\n  (defun reduce/strcat (strings &key key start end)\n    \"Reduce a list as if by STRCAT, accepting KEY START and END keywords like REDUCE.\nNIL is interpreted as an empty string. A character is interpreted as a string of length one.\"\n    (when (or start end) (setf strings (subseq strings start end)))\n    (when key (setf strings (mapcar key strings)))\n    (loop :with output = (make-string (loop :for s :in strings\n                                            :sum (if (characterp s) 1 (length s)))\n                                      :element-type (strings-common-element-type strings))\n          :with pos = 0\n          :for input :in strings\n          :do (etypecase input\n                (null)\n                (character (setf (char output pos) input) (incf pos))\n                (string (replace output input :start1 pos) (incf pos (length input))))\n          :finally (return output)))\n\n  (defun strcat (&rest strings)\n    \"Concatenate strings.\nNIL is interpreted as an empty string, a character as a string of length one.\"\n    (reduce/strcat strings))\n\n  (defun first-char (s)\n    \"Return the first character of a non-empty string S, or NIL\"\n    (and (stringp s) (plusp (length s)) (char s 0)))\n\n  (defun last-char (s)\n    \"Return the last character of a non-empty string S, or NIL\"\n    (and (stringp s) (plusp (length s)) (char s (1- (length s)))))\n\n  (defun split-string (string &key max (separator '(#\\Space #\\Tab)))\n    \"Split STRING into a list of components separated by\nany of the characters in the sequence SEPARATOR.\nIf MAX is specified, then no more than max(1,MAX) components will be returned,\nstarting the separation from the end, e.g. when called with arguments\n \\\"a.b.c.d.e\\\" :max 3 :separator \\\".\\\" it will return (\\\"a.b.c\\\" \\\"d\\\" \\\"e\\\").\"\n    (block ()\n      (let ((list nil) (words 0) (end (length string)))\n        (when (zerop end) (return nil))\n        (flet ((separatorp (char) (find char separator))\n               (done () (return (cons (subseq string 0 end) list))))\n          (loop\n            :for start = (if (and max (>= words (1- max)))\n                             (done)\n                             (position-if #'separatorp string :end end :from-end t))\n            :do (when (null start) (done))\n                (push (subseq string (1+ start) end) list)\n                (incf words)\n                (setf end start))))))\n\n  (defun string-prefix-p (prefix string)\n    \"Does STRING begin with PREFIX?\"\n    (let* ((x (string prefix))\n           (y (string string))\n           (lx (length x))\n           (ly (length y)))\n      (and (<= lx ly) (string= x y :end2 lx))))\n\n  (defun string-suffix-p (string suffix)\n    \"Does STRING end with SUFFIX?\"\n    (let* ((x (string string))\n           (y (string suffix))\n           (lx (length x))\n           (ly (length y)))\n      (and (<= ly lx) (string= x y :start1 (- lx ly)))))\n\n  (defun string-enclosed-p (prefix string suffix)\n    \"Does STRING begin with PREFIX and end with SUFFIX?\"\n    (and (string-prefix-p prefix string)\n         (string-suffix-p string suffix)))\n\n  (defvar +cr+ (coerce #(#\\Return) 'string))\n  (defvar +lf+ (coerce #(#\\Linefeed) 'string))\n  (defvar +crlf+ (coerce #(#\\Return #\\Linefeed) 'string))\n\n  (defun stripln (x)\n    \"Strip a string X from any ending CR, LF or CRLF.\nReturn two values, the stripped string and the ending that was stripped,\nor the original value and NIL if no stripping took place.\nSince our STRCAT accepts NIL as empty string designator,\nthe two results passed to STRCAT always reconstitute the original string\"\n    (check-type x string)\n    (block nil\n      (flet ((c (end) (when (string-suffix-p x end)\n                        (return (values (subseq x 0 (- (length x) (length end))) end)))))\n        (when x (c +crlf+) (c +lf+) (c +cr+) (values x nil)))))\n\n  (defun standard-case-symbol-name (name-designator)\n    \"Given a NAME-DESIGNATOR for a symbol, if it is a symbol, convert it to a string using STRING;\nif it is a string, use STRING-UPCASE on an ANSI CL platform, or STRING on a so-called \\\"modern\\\"\nplatform such as Allegro with modern syntax.\"\n    (check-type name-designator (or string symbol))\n    (cond\n      ((or (symbolp name-designator) #+allegro (eq excl:*current-case-mode* :case-sensitive-lower))\n       (string name-designator))\n      ;; Should we be doing something on CLISP?\n      (t (string-upcase name-designator))))\n\n  (defun find-standard-case-symbol (name-designator package-designator &optional (error t))\n    \"Find a symbol designated by NAME-DESIGNATOR in a package designated by PACKAGE-DESIGNATOR,\nwhere STANDARD-CASE-SYMBOL-NAME is used to transform them if these designators are strings.\nIf optional ERROR argument is NIL, return NIL instead of an error when the symbol is not found.\"\n    (find-symbol* (standard-case-symbol-name name-designator)\n                  (etypecase package-designator\n                    ((or package symbol) package-designator)\n                    (string (standard-case-symbol-name package-designator)))\n                  error)))\n\n;;; stamps: a REAL or a boolean where NIL=-infinity, T=+infinity\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute)\n  (deftype stamp () '(or real boolean)))\n(with-upgradability ()\n  (defun stamp< (x y)\n    (etypecase x\n      (null (and y t))\n      ((eql t) nil)\n      (real (etypecase y\n              (null nil)\n              ((eql t) t)\n              (real (< x y))))))\n  (defun stamps< (list) (loop :for y :in list :for x = nil :then y :always (stamp< x y)))\n  (defun stamp*< (&rest list) (stamps< list))\n  (defun stamp<= (x y) (not (stamp< y x)))\n  (defun earlier-stamp (x y) (if (stamp< x y) x y))\n  (defun stamps-earliest (list) (reduce 'earlier-stamp list :initial-value t))\n  (defun earliest-stamp (&rest list) (stamps-earliest list))\n  (defun later-stamp (x y) (if (stamp< x y) y x))\n  (defun stamps-latest (list) (reduce 'later-stamp list :initial-value nil))\n  (defun latest-stamp (&rest list) (stamps-latest list))\n  (define-modify-macro latest-stamp-f (&rest stamps) latest-stamp))\n\n\n;;; Function designators\n(with-upgradability ()\n  (defun ensure-function (fun &key (package :cl))\n    \"Coerce the object FUN into a function.\n\nIf FUN is a FUNCTION, return it.\nIf the FUN is a non-sequence literal constant, return constantly that,\ni.e. for a boolean keyword character number or pathname.\nOtherwise if FUN is a non-literally constant symbol, return its FDEFINITION.\nIf FUN is a CONS, return the function that applies its CAR\nto the appended list of the rest of its CDR and the arguments,\nunless the CAR is LAMBDA, in which case the expression is evaluated.\nIf FUN is a string, READ a form from it in the specified PACKAGE (default: CL)\nand EVAL that in a (FUNCTION ...) context.\"\n    (etypecase fun\n      (function fun)\n      ((or boolean keyword character number pathname) (constantly fun))\n      (hash-table #'(lambda (x) (gethash x fun)))\n      (symbol (fdefinition fun))\n      (cons (if (eq 'lambda (car fun))\n                (eval fun)\n                #'(lambda (&rest args) (apply (car fun) (append (cdr fun) args)))))\n      (string (eval `(function ,(with-standard-io-syntax\n                                  (let ((*package* (find-package package)))\n                                    (read-from-string fun))))))))\n\n  (defun access-at (object at)\n    \"Given an OBJECT and an AT specifier, list of successive accessors,\ncall each accessor on the result of the previous calls.\nAn accessor may be an integer, meaning a call to ELT,\na keyword, meaning a call to GETF,\nNIL, meaning identity,\na function or other symbol, meaning itself,\nor a list of a function designator and arguments, interpreted as per ENSURE-FUNCTION.\nAs a degenerate case, the AT specifier may be an atom of a single such accessor\ninstead of a list.\"\n    (flet ((access (object accessor)\n             (etypecase accessor\n               (function (funcall accessor object))\n               (integer (elt object accessor))\n               (keyword (getf object accessor))\n               (null object)\n               (symbol (funcall accessor object))\n               (cons (funcall (ensure-function accessor) object)))))\n      (if (listp at)\n          (dolist (accessor at object)\n            (setf object (access object accessor)))\n          (access object at))))\n\n  (defun access-at-count (at)\n    \"From an AT specification, extract a COUNT of maximum number\nof sub-objects to read as per ACCESS-AT\"\n    (cond\n      ((integerp at)\n       (1+ at))\n      ((and (consp at) (integerp (first at)))\n       (1+ (first at)))))\n\n  (defun call-function (function-spec &rest arguments)\n    \"Call the function designated by FUNCTION-SPEC as per ENSURE-FUNCTION,\nwith the given ARGUMENTS\"\n    (apply (ensure-function function-spec) arguments))\n\n  (defun call-functions (function-specs)\n    \"For each function in the list FUNCTION-SPECS, in order, call the function as per CALL-FUNCTION\"\n    (map () 'call-function function-specs))\n\n  (defun register-hook-function (variable hook &optional call-now-p)\n    \"Push the HOOK function (a designator as per ENSURE-FUNCTION) onto the hook VARIABLE.\nWhen CALL-NOW-P is true, also call the function immediately.\"\n    (pushnew hook (symbol-value variable) :test 'equal)\n    (when call-now-p (call-function hook))))\n\n\n;;; CLOS\n(with-upgradability ()\n  (defun coerce-class (class &key (package :cl) (super t) (error 'error))\n    \"Coerce CLASS to a class that is subclass of SUPER if specified,\nor invoke ERROR handler as per CALL-FUNCTION.\n\nA keyword designates the name a symbol, which when found in either PACKAGE, designates a class.\n-- for backward compatibility, *PACKAGE* is also accepted for now, but this may go in the future.\nA string is read as a symbol while in PACKAGE, the symbol designates a class.\n\nA class object designates itself.\nNIL designates itself (no class).\nA symbol otherwise designates a class by name.\"\n    (let* ((normalized\n            (typecase class\n              (keyword (or (find-symbol* class package nil)\n                           (find-symbol* class *package* nil)))\n              (string (symbol-call :uiop :safe-read-from-string class :package package))\n              (t class)))\n           (found\n            (etypecase normalized\n              ((or standard-class built-in-class) normalized)\n              ((or null keyword) nil)\n              (symbol (find-class normalized nil nil))))\n           (super-class\n            (etypecase super\n              ((or standard-class built-in-class) super)\n              ((or null keyword) nil)\n              (symbol (find-class super nil nil)))))\n      #+allegro (when found (mop:finalize-inheritance found))\n      (or (and found\n               (or (eq super t) (#-cormanlisp subtypep #+cormanlisp cl::subclassp found super-class))\n               found)\n          (call-function error \"Can't coerce ~S to a ~:[class~;subclass of ~:*~S~]\" class super)))))\n\n\n;;; Hash-tables\n(with-upgradability ()\n  (defun ensure-gethash (key table default)\n    \"Lookup the TABLE for a KEY as by GETHASH, but if not present,\ncall the (possibly constant) function designated by DEFAULT as per CALL-FUNCTION,\nset the corresponding entry to the result in the table.\nReturn two values: the entry after its optional computation, and whether it was found\"\n    (multiple-value-bind (value foundp) (gethash key table)\n      (values\n       (if foundp\n           value\n           (setf (gethash key table) (call-function default)))\n       foundp)))\n\n  (defun list-to-hash-set (list &aux (h (make-hash-table :test 'equal)))\n    \"Convert a LIST into hash-table that has the same elements when viewed as a set,\nup to the given equality TEST\"\n    (dolist (x list h) (setf (gethash x h) t))))\n\n\n;;; Lexicographic comparison of lists of numbers\n(with-upgradability ()\n  (defun lexicographic< (element< x y)\n    \"Lexicographically compare two lists of using the function element< to compare elements.\nelement< is a strict total order; the resulting order on X and Y will also be strict.\"\n    (cond ((null y) nil)\n          ((null x) t)\n          ((funcall element< (car x) (car y)) t)\n          ((funcall element< (car y) (car x)) nil)\n          (t (lexicographic< element< (cdr x) (cdr y)))))\n\n  (defun lexicographic<= (element< x y)\n    \"Lexicographically compare two lists of using the function element< to compare elements.\nelement< is a strict total order; the resulting order on X and Y will be a non-strict total order.\"\n    (not (lexicographic< element< y x))))\n\n\n;;; Simple style warnings\n(with-upgradability ()\n  (define-condition simple-style-warning\n      #+sbcl (sb-int:simple-style-warning) #-sbcl (simple-condition style-warning)\n    ())\n\n  (defun style-warn (datum &rest arguments)\n    (etypecase datum\n      (string (warn (make-condition 'simple-style-warning :format-control datum :format-arguments arguments)))\n      (symbol (assert (subtypep datum 'style-warning)) (apply 'warn datum arguments))\n      (style-warning (apply 'warn datum arguments)))))\n\n\n;;; Condition control\n\n(with-upgradability ()\n  (defparameter +simple-condition-format-control-slot+\n    #+abcl 'system::format-control\n    #+allegro 'excl::format-control\n    #+(or clasp ecl mkcl) 'si::format-control\n    #+clisp 'system::$format-control\n    #+clozure 'ccl::format-control\n    #+(or cmucl scl) 'conditions::format-control\n    #+(or gcl lispworks) 'conditions::format-string\n    #+sbcl 'sb-kernel:format-control\n    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl lispworks mkcl sbcl scl) nil\n    \"Name of the slot for FORMAT-CONTROL in simple-condition\")\n\n  (defun match-condition-p (x condition)\n    \"Compare received CONDITION to some pattern X:\na symbol naming a condition class,\na simple vector of length 2, arguments to find-symbol* with result as above,\nor a string describing the format-control of a simple-condition.\"\n    (etypecase x\n      (symbol (typep condition x))\n      ((simple-vector 2)\n       (ignore-errors (typep condition (find-symbol* (svref x 0) (svref x 1) nil))))\n      (function (funcall x condition))\n      (string (and (typep condition 'simple-condition)\n                   ;; On SBCL, it's always set and the check triggers a warning\n                   #+(or allegro clozure cmucl lispworks scl)\n                   (slot-boundp condition +simple-condition-format-control-slot+)\n                   (ignore-errors (equal (simple-condition-format-control condition) x))))))\n\n  (defun match-any-condition-p (condition conditions)\n    \"match CONDITION against any of the patterns of CONDITIONS supplied\"\n    (loop :for x :in conditions :thereis (match-condition-p x condition)))\n\n  (defun call-with-muffled-conditions (thunk conditions)\n    \"calls the THUNK in a context where the CONDITIONS are muffled\"\n    (handler-bind ((t #'(lambda (c) (when (match-any-condition-p c conditions)\n                                      (muffle-warning c)))))\n      (funcall thunk)))\n\n  (defmacro with-muffled-conditions ((conditions) &body body)\n    \"Shorthand syntax for CALL-WITH-MUFFLED-CONDITIONS\"\n    `(call-with-muffled-conditions #'(lambda () ,@body) ,conditions)))\n\n;;; Conditions\n\n(with-upgradability ()\n  (define-condition not-implemented-error (error)\n    ((functionality :initarg :functionality)\n     (format-control :initarg :format-control)\n     (format-arguments :initarg :format-arguments))\n    (:report (lambda (condition stream)\n               (format stream \"Not (currently) implemented on ~A: ~S~@[ ~?~]\"\n                       (nth-value 1 (symbol-call :uiop :implementation-type))\n                       (slot-value condition 'functionality)\n                       (slot-value condition 'format-control)\n                       (slot-value condition 'format-arguments)))))\n\n  (defun not-implemented-error (functionality &optional format-control &rest format-arguments)\n    \"Signal an error because some FUNCTIONALITY is not implemented in the current version\nof the software on the current platform; it may or may not be implemented in different combinations\nof version of the software and of the underlying platform. Optionally, report a formatted error\nmessage.\"\n    (error 'not-implemented-error\n           :functionality functionality\n           :format-control format-control\n           :format-arguments format-arguments))\n\n  (define-condition parameter-error (error)\n    ((functionality :initarg :functionality)\n     (format-control :initarg :format-control)\n     (format-arguments :initarg :format-arguments))\n    (:report (lambda (condition stream)\n               (apply 'format stream\n                       (slot-value condition 'format-control)\n                       (slot-value condition 'functionality)\n                       (slot-value condition 'format-arguments)))))\n\n  ;; Note that functionality MUST be passed as the second argument to parameter-error, just after\n  ;; the format-control. If you want it to not appear in first position in actual message, use\n  ;; ~* and ~:* to adjust parameter order.\n  (defun parameter-error (format-control functionality &rest format-arguments)\n    \"Signal an error because some FUNCTIONALITY or its specific implementation on a given underlying\nplatform does not accept a given parameter or combination of parameters. Report a formatted error\nmessage, that takes the functionality as its first argument (that can be skipped with ~*).\"\n    (error 'parameter-error\n           :functionality functionality\n           :format-control format-control\n           :format-arguments format-arguments)))\n\n(uiop/package:define-package :uiop/version\n  (:recycle :uiop/version :uiop/utility :asdf)\n  (:use :uiop/common-lisp :uiop/package :uiop/utility)\n  (:export\n   #:*uiop-version*\n   #:parse-version #:unparse-version #:version< #:version<= ;; version support, moved from uiop/utility\n   #:next-version\n   #:deprecated-function-condition #:deprecated-function-name ;; deprecation control\n   #:deprecated-function-style-warning #:deprecated-function-warning\n   #:deprecated-function-error #:deprecated-function-should-be-deleted\n   #:version-deprecation #:with-deprecation))\n(in-package :uiop/version)\n\n(with-upgradability ()\n  (defparameter *uiop-version* \"3.2.1\")\n\n  (defun unparse-version (version-list)\n    \"From a parsed version (a list of natural numbers), compute the version string\"\n    (format nil \"~{~D~^.~}\" version-list))\n\n  (defun parse-version (version-string &optional on-error)\n    \"Parse a VERSION-STRING as a series of natural numbers separated by dots.\nReturn a (non-null) list of integers if the string is valid;\notherwise return NIL.\n\nWhen invalid, ON-ERROR is called as per CALL-FUNCTION before to return NIL,\nwith format arguments explaining why the version is invalid.\nON-ERROR is also called if the version is not canonical\nin that it doesn't print back to itself, but the list is returned anyway.\"\n    (block nil\n      (unless (stringp version-string)\n        (call-function on-error \"~S: ~S is not a string\" 'parse-version version-string)\n        (return))\n      (unless (loop :for prev = nil :then c :for c :across version-string\n                    :always (or (digit-char-p c)\n                                (and (eql c #\\.) prev (not (eql prev #\\.))))\n                    :finally (return (and c (digit-char-p c))))\n        (call-function on-error \"~S: ~S doesn't follow asdf version numbering convention\"\n                       'parse-version version-string)\n        (return))\n      (let* ((version-list\n               (mapcar #'parse-integer (split-string version-string :separator \".\")))\n             (normalized-version (unparse-version version-list)))\n        (unless (equal version-string normalized-version)\n          (call-function on-error \"~S: ~S contains leading zeros\" 'parse-version version-string))\n        version-list)))\n\n  (defun next-version (version)\n    \"When VERSION is not nil, it is a string, then parse it as a version, compute the next version\nand return it as a string.\"\n    (when version\n      (let ((version-list (parse-version version)))\n        (incf (car (last version-list)))\n        (unparse-version version-list))))\n\n  (defun version< (version1 version2)\n    \"Given two version strings, return T if the second is strictly newer\"\n    (let ((v1 (parse-version version1 nil))\n          (v2 (parse-version version2 nil)))\n      (lexicographic< '< v1 v2)))\n\n  (defun version<= (version1 version2)\n    \"Given two version strings, return T if the second is newer or the same\"\n    (not (version< version2 version1))))\n\n\n(with-upgradability ()\n  (define-condition deprecated-function-condition (condition)\n    ((name :initarg :name :reader deprecated-function-name)))\n  (define-condition deprecated-function-style-warning (deprecated-function-condition style-warning) ())\n  (define-condition deprecated-function-warning (deprecated-function-condition warning) ())\n  (define-condition deprecated-function-error (deprecated-function-condition error) ())\n  (define-condition deprecated-function-should-be-deleted (deprecated-function-condition error) ())\n\n  (defun deprecated-function-condition-kind (type)\n    (ecase type\n      ((deprecated-function-style-warning) :style-warning)\n      ((deprecated-function-warning) :warning)\n      ((deprecated-function-error) :error)\n      ((deprecated-function-should-be-deleted) :delete)))\n\n  (defmethod print-object ((c deprecated-function-condition) stream)\n    (let ((name (deprecated-function-name c)))\n      (cond\n        (*print-readably*\n         (let ((fmt \"#.(make-condition '~S :name ~S)\")\n               (args (list (type-of c) name)))\n           (if *read-eval*\n               (apply 'format stream fmt args)\n               (error \"Can't print ~?\" fmt args))))\n        (*print-escape*\n         (print-unreadable-object (c stream :type t) (format stream \":name ~S\" name)))\n        (t\n         (let ((*package* (find-package :cl))\n               (type (type-of c)))\n           (format stream\n                   (if (eq type 'deprecated-function-should-be-deleted)\n                       \"~A: Still defining deprecated function~:P ~{~S~^ ~} that promised to delete\"\n                       \"~A: Using deprecated function ~S -- please update your code to use a newer API.~\n~@[~%The docstring for this function says:~%~A~%~]\")\n                   type name (when (symbolp name) (documentation name 'function))))))))\n\n  (defun notify-deprecated-function (status name)\n    (ecase status\n      ((nil) nil)\n      ((:style-warning) (style-warn 'deprecated-function-style-warning :name name))\n      ((:warning) (warn 'deprecated-function-warning :name name))\n      ((:error) (cerror \"USE FUNCTION ANYWAY\" 'deprecated-function-error :name name))))\n\n  (defun version-deprecation (version &key (style-warning nil)\n                                        (warning (next-version style-warning))\n                                        (error (next-version warning))\n                                        (delete (next-version error)))\n    \"Given a VERSION string, and the starting versions for notifying the programmer of\nvarious levels of deprecation, return the current level of deprecation as per WITH-DEPRECATION\nthat is the highest level that has a declared version older than the specified version.\nEach start version for a level of deprecation can be specified by a keyword argument, or\nif left unspecified, will be the NEXT-VERSION of the immediate lower level of deprecation.\"\n    (cond\n      ((and delete (version<= delete version)) :delete)\n      ((and error (version<= error version)) :error)\n      ((and warning (version<= warning version)) :warning)\n      ((and style-warning (version<= style-warning version)) :style-warning)))\n\n  (defmacro with-deprecation ((level) &body definitions)\n    \"Given a deprecation LEVEL (a form to be EVAL'ed at macro-expansion time), instrument the\nDEFUN and DEFMETHOD forms in DEFINITIONS to notify the programmer of the deprecation of the function\nwhen it is compiled or called.\n\nIncreasing levels (as result from evaluating LEVEL) are: NIL (not deprecated yet),\n:STYLE-WARNING (a style warning is issued when used), :WARNING (a full warning is issued when used),\n:ERROR (a continuable error instead), and :DELETE (it's an error if the code is still there while\nat that level).\n\nForms other than DEFUN and DEFMETHOD are not instrumented, and you can protect a DEFUN or DEFMETHOD\nfrom instrumentation by enclosing it in a PROGN.\"\n    (let ((level (eval level)))\n      (check-type level (member nil :style-warning :warning :error :delete))\n      (when (eq level :delete)\n        (error 'deprecated-function-should-be-deleted :name\n               (mapcar 'second\n                       (remove-if-not #'(lambda (x) (member x '(defun defmethod)))\n                                      definitions :key 'first))))\n      (labels ((instrument (name head body whole)\n                 (if level\n                     (let ((notifiedp\n                            (intern (format nil \"*~A-~A-~A-~A*\"\n                                            :deprecated-function level name :notified-p))))\n                       (multiple-value-bind (remaining-forms declarations doc-string)\n                           (parse-body body :documentation t :whole whole)\n                         `(progn\n                            (defparameter ,notifiedp nil)\n                            ;; tell some implementations to use the compiler-macro\n                            (declaim (inline ,name))\n                            (define-compiler-macro ,name (&whole form &rest args)\n                              (declare (ignore args))\n                              (notify-deprecated-function ,level ',name)\n                              form)\n                            (,@head ,@(when doc-string (list doc-string)) ,@declarations\n                                    (unless ,notifiedp\n                                      (setf ,notifiedp t)\n                                      (notify-deprecated-function ,level ',name))\n                                    ,@remaining-forms))))\n                     `(progn\n                        (eval-when (:compile-toplevel :load-toplevel :execute)\n                          (setf (compiler-macro-function ',name) nil))\n                        (declaim (notinline ,name))\n                        (,@head ,@body)))))\n        `(progn\n           ,@(loop :for form :in definitions :collect\n               (cond\n                 ((and (consp form) (eq (car form) 'defun))\n                  (instrument (second form) (subseq form 0 3) (subseq form 3) form))\n                 ((and (consp form) (eq (car form) 'defmethod))\n                  (let ((body-start (if (listp (third form)) 3 4)))\n                    (instrument (second form)\n                                (subseq form 0 body-start)\n                                (subseq form body-start)\n                                form)))\n                 (t\n                  form))))))))\n;;;; ---------------------------------------------------------------------------\n;;;; Access to the Operating System\n\n(uiop/package:define-package :uiop/os\n  (:use :uiop/common-lisp :uiop/package :uiop/utility)\n  (:export\n   #:featurep #:os-unix-p #:os-macosx-p #:os-windows-p #:os-genera-p #:detect-os ;; features\n   #:os-cond\n   #:getenv #:getenvp ;; environment variables\n   #:implementation-identifier ;; implementation identifier\n   #:implementation-type #:*implementation-type*\n   #:operating-system #:architecture #:lisp-version-string\n   #:hostname #:getcwd #:chdir\n   ;; Windows shortcut support\n   #:read-null-terminated-string #:read-little-endian\n   #:parse-file-location-info #:parse-windows-shortcut))\n(in-package :uiop/os)\n\n;;; Features\n(with-upgradability ()\n  (defun featurep (x &optional (*features* *features*))\n    \"Checks whether a feature expression X is true with respect to the *FEATURES* set,\nas per the CLHS standard for #+ and #-. Beware that just like the CLHS,\nwe assume symbols from the KEYWORD package are used, but that unless you're using #+/#-\nyour reader will not have magically used the KEYWORD package, so you need specify\nkeywords explicitly.\"\n    (cond\n      ((atom x) (and (member x *features*) t))\n      ((eq :not (car x)) (assert (null (cddr x))) (not (featurep (cadr x))))\n      ((eq :or (car x)) (some #'featurep (cdr x)))\n      ((eq :and (car x)) (every #'featurep (cdr x)))\n      (t (parameter-error \"~S: malformed feature specification ~S\" 'featurep x))))\n\n  ;; Starting with UIOP 3.1.5, these are runtime tests.\n  ;; You may bind *features* with a copy of what your target system offers to test its properties.\n  (defun os-macosx-p ()\n    \"Is the underlying operating system MacOS X?\"\n    ;; OS-MACOSX is not mutually exclusive with OS-UNIX,\n    ;; in fact the former implies the latter.\n    (featurep '(:or :darwin (:and :allegro :macosx) (:and :clisp :macos))))\n\n  (defun os-unix-p ()\n    \"Is the underlying operating system some Unix variant?\"\n    (or (featurep '(:or :unix :cygwin)) (os-macosx-p)))\n\n  (defun os-windows-p ()\n    \"Is the underlying operating system Microsoft Windows?\"\n    (and (not (os-unix-p)) (featurep '(:or :win32 :windows :mswindows :mingw32 :mingw64))))\n\n  (defun os-genera-p ()\n    \"Is the underlying operating system Genera (running on a Symbolics Lisp Machine)?\"\n    (featurep :genera))\n\n  (defun os-oldmac-p ()\n    \"Is the underlying operating system an (emulated?) MacOS 9 or earlier?\"\n    (featurep :mcl))\n\n  (defun os-haiku-p ()\n    \"Is the underlying operating system Haiku?\"\n    (featurep :haiku))\n\n  (defun detect-os ()\n    \"Detects the current operating system. Only needs be run at compile-time,\nexcept on ABCL where it might change between FASL compilation and runtime.\"\n    (loop* :with o\n           :for (feature . detect) :in '((:os-unix . os-unix-p) (:os-macosx . os-macosx-p)\n                                         (:os-windows . os-windows-p)\n                                         (:genera . os-genera-p) (:os-oldmac . os-oldmac-p)\n                                         (:haiku . os-haiku-p))\n           :when (and (or (not o) (eq feature :os-macosx)) (funcall detect))\n           :do (setf o feature) (pushnew feature *features*)\n           :else :do (setf *features* (remove feature *features*))\n           :finally\n           (return (or o (error \"Congratulations for trying ASDF on an operating system~%~\nthat is neither Unix, nor Windows, nor Genera, nor even old MacOS.~%Now you port it.\")))))\n\n  (defmacro os-cond (&rest clauses)\n    #+abcl `(cond ,@clauses)\n    #-abcl (loop* :for (test . body) :in clauses :when (eval test) :return `(progn ,@body)))\n\n  (detect-os))\n\n;;;; Environment variables: getting them, and parsing them.\n(with-upgradability ()\n  (defun getenv (x)\n    \"Query the environment, as in C getenv.\nBeware: may return empty string if a variable is present but empty;\nuse getenvp to return NIL in such a case.\"\n    (declare (ignorable x))\n    #+(or abcl clasp clisp ecl xcl) (ext:getenv x)\n    #+allegro (sys:getenv x)\n    #+clozure (ccl:getenv x)\n    #+cmucl (unix:unix-getenv x)\n    #+scl (cdr (assoc x ext:*environment-list* :test #'string=))\n    #+cormanlisp\n    (let* ((buffer (ct:malloc 1))\n           (cname (ct:lisp-string-to-c-string x))\n           (needed-size (win:getenvironmentvariable cname buffer 0))\n           (buffer1 (ct:malloc (1+ needed-size))))\n      (prog1 (if (zerop (win:getenvironmentvariable cname buffer1 needed-size))\n                 nil\n                 (ct:c-string-to-lisp-string buffer1))\n        (ct:free buffer)\n        (ct:free buffer1)))\n    #+gcl (system:getenv x)\n    #+genera nil\n    #+lispworks (lispworks:environment-variable x)\n    #+mcl (ccl:with-cstrs ((name x))\n            (let ((value (_getenv name)))\n              (unless (ccl:%null-ptr-p value)\n                (ccl:%get-cstring value))))\n    #+mkcl (#.(or (find-symbol* 'getenv :si nil) (find-symbol* 'getenv :mk-ext nil)) x)\n    #+sbcl (sb-ext:posix-getenv x)\n    #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mcl mkcl sbcl scl xcl)\n    (not-implemented-error 'getenv))\n\n  (defsetf getenv (x) (val)\n    \"Set an environment variable.\"\n      (declare (ignorable x val))\n    #+allegro `(setf (sys:getenv ,x) ,val)\n    #+clisp `(system::setenv ,x ,val)\n    #+clozure `(ccl:setenv ,x ,val)\n    #+cmucl `(unix:unix-setenv ,x ,val 1)\n    #+ecl `(ext:setenv ,x ,val)\n    #+lispworks `(hcl:setenv ,x ,val)\n    #+mkcl `(mkcl:setenv ,x ,val)\n    #+sbcl `(progn (require :sb-posix) (symbol-call :sb-posix :setenv ,x ,val 1))\n    #-(or allegro clisp clozure cmucl ecl lispworks mkcl sbcl)\n    '(not-implemented-error '(setf getenv)))\n\n  (defun getenvp (x)\n    \"Predicate that is true if the named variable is present in the libc environment,\nthen returning the non-empty string value of the variable\"\n    (let ((g (getenv x))) (and (not (emptyp g)) g))))\n\n\n;;;; implementation-identifier\n;;\n;; produce a string to identify current implementation.\n;; Initially stolen from SLIME's SWANK, completely rewritten since.\n;; We're back to runtime checking, for the sake of e.g. ABCL.\n\n(with-upgradability ()\n  (defun first-feature (feature-sets)\n    \"A helper for various feature detection functions\"\n    (dolist (x feature-sets)\n      (multiple-value-bind (short long feature-expr)\n          (if (consp x)\n              (values (first x) (second x) (cons :or (rest x)))\n              (values x x x))\n        (when (featurep feature-expr)\n          (return (values short long))))))\n\n  (defun implementation-type ()\n    \"The type of Lisp implementation used, as a short UIOP-standardized keyword\"\n    (first-feature\n     '(:abcl (:acl :allegro) (:ccl :clozure) :clisp (:corman :cormanlisp)\n       (:cmu :cmucl :cmu) :clasp :ecl :gcl\n       (:lwpe :lispworks-personal-edition) (:lw :lispworks)\n       :mcl :mkcl :sbcl :scl (:smbx :symbolics) :xcl)))\n\n  (defvar *implementation-type* (implementation-type)\n    \"The type of Lisp implementation used, as a short UIOP-standardized keyword\")\n\n  (defun operating-system ()\n    \"The operating system of the current host\"\n    (first-feature\n     '(:cygwin\n       (:win :windows :mswindows :win32 :mingw32) ;; try cygwin first!\n       (:linux :linux :linux-target) ;; for GCL at least, must appear before :bsd\n       (:macosx :macosx :darwin :darwin-target :apple) ; also before :bsd\n       (:solaris :solaris :sunos)\n       (:bsd :bsd :freebsd :netbsd :openbsd :dragonfly)\n       :unix\n       :genera)))\n\n  (defun architecture ()\n    \"The CPU architecture of the current host\"\n    (first-feature\n     '((:x64 :x86-64 :x86_64 :x8664-target :amd64 (:and :word-size=64 :pc386))\n       (:x86 :x86 :i386 :i486 :i586 :i686 :pentium3 :pentium4 :pc386 :iapx386 :x8632-target)\n       (:ppc64 :ppc64 :ppc64-target) (:ppc32 :ppc32 :ppc32-target :ppc :powerpc)\n       :hppa64 :hppa :sparc64 (:sparc32 :sparc32 :sparc)\n       :mipsel :mipseb :mips :alpha (:arm :arm :arm-target) :imach\n       ;; Java comes last: if someone uses C via CFFI or otherwise JNA or JNI,\n       ;; we may have to segregate the code still by architecture.\n       (:java :java :java-1.4 :java-1.5 :java-1.6 :java-1.7))))\n\n  #+clozure\n  (defun ccl-fasl-version ()\n    ;; the fasl version is target-dependent from CCL 1.8 on.\n    (or (let ((s 'ccl::target-fasl-version))\n          (and (fboundp s) (funcall s)))\n        (and (boundp 'ccl::fasl-version)\n             (symbol-value 'ccl::fasl-version))\n        (error \"Can't determine fasl version.\")))\n\n  (defun lisp-version-string ()\n    \"return a string that identifies the current Lisp implementation version\"\n    (let ((s (lisp-implementation-version)))\n      (car ; as opposed to OR, this idiom prevents some unreachable code warning\n       (list\n        #+allegro\n        (format nil \"~A~@[~A~]~@[~A~]~@[~A~]\"\n                excl::*common-lisp-version-number*\n                ;; M means \"modern\", as opposed to ANSI-compatible mode (which I consider default)\n                (and (eq excl:*current-case-mode* :case-sensitive-lower) \"M\")\n                ;; Note if not using International ACL\n                ;; see http://www.franz.com/support/documentation/8.1/doc/operators/excl/ics-target-case.htm\n                (excl:ics-target-case (:-ics \"8\"))\n                (and (member :smp *features*) \"S\"))\n        #+armedbear (format nil \"~a-fasl~a\" s system::*fasl-version*)\n        #+clisp\n        (subseq s 0 (position #\\space s)) ; strip build information (date, etc.)\n        #+clozure\n        (format nil \"~d.~d-f~d\" ; shorten for windows\n                ccl::*openmcl-major-version*\n                ccl::*openmcl-minor-version*\n                (logand (ccl-fasl-version) #xFF))\n        #+cmucl (substitute #\\- #\\/ s)\n        #+scl (format nil \"~A~A\" s\n                      ;; ANSI upper case vs lower case.\n                      (ecase ext:*case-mode* (:upper \"\") (:lower \"l\")))\n        #+ecl (format nil \"~A~@[-~A~]\" s\n                      (let ((vcs-id (ext:lisp-implementation-vcs-id)))\n                        (unless (equal vcs-id \"UNKNOWN\")\n                          (subseq vcs-id 0 (min (length vcs-id) 8)))))\n        #+gcl (subseq s (1+ (position #\\space s)))\n        #+genera\n        (multiple-value-bind (major minor) (sct:get-system-version \"System\")\n          (format nil \"~D.~D\" major minor))\n        #+mcl (subseq s 8) ; strip the leading \"Version \"\n        ;; seems like there should be a shorter way to do this, like ACALL.\n        #+mkcl (or\n                (let ((fname (find-symbol* '#:git-describe-this-mkcl :mkcl nil)))\n                  (when (and fname (fboundp fname))\n                    (funcall fname)))\n                s)\n        s))))\n\n  (defun implementation-identifier ()\n    \"Return a string that identifies the ABI of the current implementation,\nsuitable for use as a directory name to segregate Lisp FASLs, C dynamic libraries, etc.\"\n    (substitute-if\n     #\\_ #'(lambda (x) (find x \" /:;&^\\\\|?<>(){}[]$#`'\\\"\"))\n     (format nil \"~(~a~@{~@[-~a~]~}~)\"\n             (or (implementation-type) (lisp-implementation-type))\n             (lisp-version-string)\n             (or (operating-system) (software-type))\n             (or (architecture) (machine-type))))))\n\n\n;;;; Other system information\n\n(with-upgradability ()\n  (defun hostname ()\n    \"return the hostname of the current host\"\n    ;; Note: untested on RMCL\n    #+(or abcl clasp clozure cmucl ecl genera lispworks mcl mkcl sbcl scl xcl) (machine-instance)\n    #+cormanlisp \"localhost\" ;; is there a better way? Does it matter?\n    #+allegro (symbol-call :excl.osi :gethostname)\n    #+clisp (first (split-string (machine-instance) :separator \" \"))\n    #+gcl (system:gethostname)))\n\n\n;;; Current directory\n(with-upgradability ()\n\n  #+cmucl\n  (defun parse-unix-namestring* (unix-namestring)\n    \"variant of LISP::PARSE-UNIX-NAMESTRING that returns a pathname object\"\n    (multiple-value-bind (host device directory name type version)\n        (lisp::parse-unix-namestring unix-namestring 0 (length unix-namestring))\n      (make-pathname :host (or host lisp::*unix-host*) :device device\n                     :directory directory :name name :type type :version version)))\n\n  (defun getcwd ()\n    \"Get the current working directory as per POSIX getcwd(3), as a pathname object\"\n    (or #+(or abcl genera xcl) (truename *default-pathname-defaults*) ;; d-p-d is canonical!\n        #+allegro (excl::current-directory)\n        #+clisp (ext:default-directory)\n        #+clozure (ccl:current-directory)\n        #+(or cmucl scl) (#+cmucl parse-unix-namestring* #+scl lisp::parse-unix-namestring\n                        (strcat (nth-value 1 (unix:unix-current-directory)) \"/\"))\n        #+cormanlisp (pathname (pl::get-current-directory)) ;; Q: what type does it return?\n        #+(or clasp ecl) (ext:getcwd)\n        #+gcl (let ((*default-pathname-defaults* #p\"\")) (truename #p\"\"))\n        #+lispworks (hcl:get-working-directory)\n        #+mkcl (mk-ext:getcwd)\n        #+sbcl (sb-ext:parse-native-namestring (sb-unix:posix-getcwd/))\n        #+xcl (extensions:current-directory)\n        (not-implemented-error 'getcwd)))\n\n  (defun chdir (x)\n    \"Change current directory, as per POSIX chdir(2), to a given pathname object\"\n    (if-let (x (pathname x))\n      #+(or abcl genera xcl) (setf *default-pathname-defaults* (truename x)) ;; d-p-d is canonical!\n      #+allegro (excl:chdir x)\n      #+clisp (ext:cd x)\n      #+clozure (setf (ccl:current-directory) x)\n      #+(or cmucl scl) (unix:unix-chdir (ext:unix-namestring x))\n      #+cormanlisp (unless (zerop (win32::_chdir (namestring x)))\n                     (error \"Could not set current directory to ~A\" x))\n      #+(or clasp ecl) (ext:chdir x)\n      #+gcl (system:chdir x)\n      #+lispworks (hcl:change-directory x)\n      #+mkcl (mk-ext:chdir x)\n      #+sbcl (progn (require :sb-posix) (symbol-call :sb-posix :chdir (sb-ext:native-namestring x)))\n      #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mkcl sbcl scl xcl)\n      (not-implemented-error 'chdir))))\n\n\n;;;; -----------------------------------------------------------------\n;;;; Windows shortcut support.  Based on:\n;;;;\n;;;; Jesse Hager: The Windows Shortcut File Format.\n;;;; http://www.wotsit.org/list.asp?fc=13\n\n#-(or clisp genera) ; CLISP doesn't need it, and READ-SEQUENCE annoys old Genera that doesn't need it\n(with-upgradability ()\n  (defparameter *link-initial-dword* 76)\n  (defparameter *link-guid* #(1 20 2 0 0 0 0 0 192 0 0 0 0 0 0 70))\n\n  (defun read-null-terminated-string (s)\n    \"Read a null-terminated string from an octet stream S\"\n    ;; note: doesn't play well with UNICODE\n    (with-output-to-string (out)\n      (loop :for code = (read-byte s)\n            :until (zerop code)\n            :do (write-char (code-char code) out))))\n\n  (defun read-little-endian (s &optional (bytes 4))\n    \"Read a number in little-endian format from an byte (octet) stream S,\nthe number having BYTES octets (defaulting to 4).\"\n    (loop :for i :from 0 :below bytes\n          :sum (ash (read-byte s) (* 8 i))))\n\n  (defun parse-file-location-info (s)\n    \"helper to parse-windows-shortcut\"\n    (let ((start (file-position s))\n          (total-length (read-little-endian s))\n          (end-of-header (read-little-endian s))\n          (fli-flags (read-little-endian s))\n          (local-volume-offset (read-little-endian s))\n          (local-offset (read-little-endian s))\n          (network-volume-offset (read-little-endian s))\n          (remaining-offset (read-little-endian s)))\n      (declare (ignore total-length end-of-header local-volume-offset))\n      (unless (zerop fli-flags)\n        (cond\n          ((logbitp 0 fli-flags)\n           (file-position s (+ start local-offset)))\n          ((logbitp 1 fli-flags)\n           (file-position s (+ start\n                               network-volume-offset\n                               #x14))))\n        (strcat (read-null-terminated-string s)\n                (progn\n                  (file-position s (+ start remaining-offset))\n                  (read-null-terminated-string s))))))\n\n  (defun parse-windows-shortcut (pathname)\n    \"From a .lnk windows shortcut, extract the pathname linked to\"\n    ;; NB: doesn't do much checking & doesn't look like it will work well with UNICODE.\n    (with-open-file (s pathname :element-type '(unsigned-byte 8))\n      (handler-case\n          (when (and (= (read-little-endian s) *link-initial-dword*)\n                     (let ((header (make-array (length *link-guid*))))\n                       (read-sequence header s)\n                       (equalp header *link-guid*)))\n            (let ((flags (read-little-endian s)))\n              (file-position s 76)        ;skip rest of header\n              (when (logbitp 0 flags)\n                ;; skip shell item id list\n                (let ((length (read-little-endian s 2)))\n                  (file-position s (+ length (file-position s)))))\n              (cond\n                ((logbitp 1 flags)\n                 (parse-file-location-info s))\n                (t\n                 (when (logbitp 2 flags)\n                   ;; skip description string\n                   (let ((length (read-little-endian s 2)))\n                     (file-position s (+ length (file-position s)))))\n                 (when (logbitp 3 flags)\n                   ;; finally, our pathname\n                   (let* ((length (read-little-endian s 2))\n                          (buffer (make-array length)))\n                     (read-sequence buffer s)\n                     (map 'string #'code-char buffer)))))))\n        (end-of-file (c)\n          (declare (ignore c))\n          nil)))))\n\n\n;;;; -------------------------------------------------------------------------\n;;;; Portability layer around Common Lisp pathnames\n;; This layer allows for portable manipulation of pathname objects themselves,\n;; which all is necessary prior to any access the filesystem or environment.\n\n(uiop/package:define-package :uiop/pathname\n  (:nicknames :asdf/pathname) ;; deprecated. Used by ceramic\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os)\n  (:export\n   ;; Making and merging pathnames, portably\n   #:normalize-pathname-directory-component #:denormalize-pathname-directory-component\n   #:merge-pathname-directory-components #:*unspecific-pathname-type* #:make-pathname*\n   #:make-pathname-component-logical #:make-pathname-logical\n   #:merge-pathnames*\n   #:nil-pathname #:*nil-pathname* #:with-pathname-defaults\n   ;; Predicates\n   #:pathname-equal #:logical-pathname-p #:physical-pathname-p #:physicalize-pathname\n   #:absolute-pathname-p #:relative-pathname-p #:hidden-pathname-p #:file-pathname-p\n   ;; Directories\n   #:pathname-directory-pathname #:pathname-parent-directory-pathname\n   #:directory-pathname-p #:ensure-directory-pathname\n   ;; Parsing filenames\n   #:split-name-type #:parse-unix-namestring #:unix-namestring\n   #:split-unix-namestring-directory-components\n   ;; Absolute and relative pathnames\n   #:subpathname #:subpathname*\n   #:ensure-absolute-pathname\n   #:pathname-root #:pathname-host-pathname\n   #:subpathp #:enough-pathname #:with-enough-pathname #:call-with-enough-pathname\n   ;; Checking constraints\n   #:ensure-pathname ;; implemented in filesystem.lisp to accommodate for existence constraints\n   ;; Wildcard pathnames\n   #:*wild* #:*wild-file* #:*wild-file-for-directory* #:*wild-directory*\n   #:*wild-inferiors* #:*wild-path* #:wilden\n   ;; Translate a pathname\n   #:relativize-directory-component #:relativize-pathname-directory\n   #:directory-separator-for-host #:directorize-pathname-host-device\n   #:translate-pathname*\n   #:*output-translation-function*))\n(in-package :uiop/pathname)\n\n;;; Normalizing pathnames across implementations\n\n(with-upgradability ()\n  (defun normalize-pathname-directory-component (directory)\n    \"Convert the DIRECTORY component from a format usable by the underlying\nimplementation's MAKE-PATHNAME and other primitives to a CLHS-standard format\nthat is a list and not a string.\"\n    (cond\n      #-(or cmucl sbcl scl) ;; these implementations already normalize directory components.\n      ((stringp directory) `(:absolute ,directory))\n      ((or (null directory)\n           (and (consp directory) (member (first directory) '(:absolute :relative))))\n       directory)\n      #+gcl\n      ((consp directory)\n       (cons :relative directory))\n      (t\n       (parameter-error (compatfmt \"~@<~S: Unrecognized pathname directory component ~S~@:>\")\n                        'normalize-pathname-directory-component directory))))\n\n  (defun denormalize-pathname-directory-component (directory-component)\n    \"Convert the DIRECTORY-COMPONENT from a CLHS-standard format to a format usable\nby the underlying implementation's MAKE-PATHNAME and other primitives\"\n    directory-component)\n\n  (defun merge-pathname-directory-components (specified defaults)\n    \"Helper for MERGE-PATHNAMES* that handles directory components\"\n    (let ((directory (normalize-pathname-directory-component specified)))\n      (ecase (first directory)\n        ((nil) defaults)\n        (:absolute specified)\n        (:relative\n         (let ((defdir (normalize-pathname-directory-component defaults))\n               (reldir (cdr directory)))\n           (cond\n             ((null defdir)\n              directory)\n             ((not (eq :back (first reldir)))\n              (append defdir reldir))\n             (t\n              (loop :with defabs = (first defdir)\n                    :with defrev = (reverse (rest defdir))\n                    :while (and (eq :back (car reldir))\n                                (or (and (eq :absolute defabs) (null defrev))\n                                    (stringp (car defrev))))\n                    :do (pop reldir) (pop defrev)\n                    :finally (return (cons defabs (append (reverse defrev) reldir)))))))))))\n\n  ;; Giving :unspecific as :type argument to make-pathname is not portable.\n  ;; See CLHS make-pathname and 19.2.2.2.3.\n  ;; This will be :unspecific if supported, or NIL if not.\n  (defparameter *unspecific-pathname-type*\n    #+(or abcl allegro clozure cmucl genera lispworks sbcl scl) :unspecific\n    #+(or clasp clisp ecl mkcl gcl xcl #|These haven't been tested:|# cormanlisp mcl) nil\n    \"Unspecific type component to use with the underlying implementation's MAKE-PATHNAME\")\n\n  (defun make-pathname* (&rest keys &key directory host device name type version defaults\n                                      #+scl &allow-other-keys)\n    \"Takes arguments like CL:MAKE-PATHNAME in the CLHS, and\n   tries hard to make a pathname that will actually behave as documented,\n   despite the peculiarities of each implementation. DEPRECATED: just use MAKE-PATHNAME.\"\n    (declare (ignore host device directory name type version defaults))\n    (apply 'make-pathname keys))\n\n  (defun make-pathname-component-logical (x)\n    \"Make a pathname component suitable for use in a logical-pathname\"\n    (typecase x\n      ((eql :unspecific) nil)\n      #+clisp (string (string-upcase x))\n      #+clisp (cons (mapcar 'make-pathname-component-logical x))\n      (t x)))\n\n  (defun make-pathname-logical (pathname host)\n    \"Take a PATHNAME's directory, name, type and version components,\nand make a new pathname with corresponding components and specified logical HOST\"\n    (make-pathname\n     :host host\n     :directory (make-pathname-component-logical (pathname-directory pathname))\n     :name (make-pathname-component-logical (pathname-name pathname))\n     :type (make-pathname-component-logical (pathname-type pathname))\n     :version (make-pathname-component-logical (pathname-version pathname))))\n\n  (defun merge-pathnames* (specified &optional (defaults *default-pathname-defaults*))\n    \"MERGE-PATHNAMES* is like MERGE-PATHNAMES except that\nif the SPECIFIED pathname does not have an absolute directory,\nthen the HOST and DEVICE both come from the DEFAULTS, whereas\nif the SPECIFIED pathname does have an absolute directory,\nthen the HOST and DEVICE both come from the SPECIFIED pathname.\nThis is what users want on a modern Unix or Windows operating system,\nunlike the MERGE-PATHNAMES behavior.\nAlso, if either argument is NIL, then the other argument is returned unmodified;\nthis is unlike MERGE-PATHNAMES which always merges with a pathname,\nby default *DEFAULT-PATHNAME-DEFAULTS*, which cannot be NIL.\"\n    (when (null specified) (return-from merge-pathnames* defaults))\n    (when (null defaults) (return-from merge-pathnames* specified))\n    #+scl\n    (ext:resolve-pathname specified defaults)\n    #-scl\n    (let* ((specified (pathname specified))\n           (defaults (pathname defaults))\n           (directory (normalize-pathname-directory-component (pathname-directory specified)))\n           (name (or (pathname-name specified) (pathname-name defaults)))\n           (type (or (pathname-type specified) (pathname-type defaults)))\n           (version (or (pathname-version specified) (pathname-version defaults))))\n      (labels ((unspecific-handler (p)\n                 (if (typep p 'logical-pathname) #'make-pathname-component-logical #'identity)))\n        (multiple-value-bind (host device directory unspecific-handler)\n            (ecase (first directory)\n              ((:absolute)\n               (values (pathname-host specified)\n                       (pathname-device specified)\n                       directory\n                       (unspecific-handler specified)))\n              ((nil :relative)\n               (values (pathname-host defaults)\n                       (pathname-device defaults)\n                       (merge-pathname-directory-components directory (pathname-directory defaults))\n                       (unspecific-handler defaults))))\n          (make-pathname :host host :device device :directory directory\n                         :name (funcall unspecific-handler name)\n                         :type (funcall unspecific-handler type)\n                         :version (funcall unspecific-handler version))))))\n\n  (defun logical-pathname-p (x)\n    \"is X a logical-pathname?\"\n    (typep x 'logical-pathname))\n\n  (defun physical-pathname-p (x)\n    \"is X a pathname that is not a logical-pathname?\"\n    (and (pathnamep x) (not (logical-pathname-p x))))\n\n  (defun physicalize-pathname (x)\n    \"if X is a logical pathname, use translate-logical-pathname on it.\"\n    ;; Ought to be the same as translate-logical-pathname, except the latter borks on CLISP\n    (let ((p (when x (pathname x))))\n      (if (logical-pathname-p p) (translate-logical-pathname p) p)))\n\n  (defun nil-pathname (&optional (defaults *default-pathname-defaults*))\n    \"A pathname that is as neutral as possible for use as defaults\nwhen merging, making or parsing pathnames\"\n    ;; 19.2.2.2.1 says a NIL host can mean a default host;\n    ;; see also \"valid physical pathname host\" in the CLHS glossary, that suggests\n    ;; strings and lists of strings or :unspecific\n    ;; But CMUCL decides to die on NIL.\n    ;; MCL has issues with make-pathname, nil and defaulting\n    (declare (ignorable defaults))\n    #.`(make-pathname :directory nil :name nil :type nil :version nil\n                      :device (or #+(and mkcl os-unix) :unspecific)\n                      :host (or #+cmucl lisp::*unix-host* #+(and mkcl os-unix) \"localhost\")\n                      #+scl ,@'(:scheme nil :scheme-specific-part nil\n                                :username nil :password nil :parameters nil :query nil :fragment nil)\n                      ;; the default shouldn't matter, but we really want something physical\n                      #-mcl ,@'(:defaults defaults)))\n\n  (defvar *nil-pathname* (nil-pathname (physicalize-pathname (user-homedir-pathname)))\n    \"A pathname that is as neutral as possible for use as defaults\nwhen merging, making or parsing pathnames\")\n\n  (defmacro with-pathname-defaults ((&optional defaults) &body body)\n    \"Execute BODY in a context where the *DEFAULT-PATHNAME-DEFAULTS* is as specified,\nwhere leaving the defaults NIL or unspecified means a (NIL-PATHNAME), except\non ABCL, Genera and XCL, where it remains unchanged for it doubles as current-directory.\"\n    `(let ((*default-pathname-defaults*\n             ,(or defaults\n                  #-(or abcl genera xcl) '*nil-pathname*\n                  #+(or abcl genera xcl) '*default-pathname-defaults*)))\n       ,@body)))\n\n\n;;; Some pathname predicates\n(with-upgradability ()\n  (defun pathname-equal (p1 p2)\n    \"Are the two pathnames P1 and P2 reasonably equal in the paths they denote?\"\n    (when (stringp p1) (setf p1 (pathname p1)))\n    (when (stringp p2) (setf p2 (pathname p2)))\n    (flet ((normalize-component (x)\n             (unless (member x '(nil :unspecific :newest (:relative)) :test 'equal)\n               x)))\n      (macrolet ((=? (&rest accessors)\n                   (flet ((frob (x)\n                            (reduce 'list (cons 'normalize-component accessors)\n                                    :initial-value x :from-end t)))\n                     `(equal ,(frob 'p1) ,(frob 'p2)))))\n        (or (and (null p1) (null p2))\n            (and (pathnamep p1) (pathnamep p2)\n                 (and (=? pathname-host)\n                      #-(and mkcl os-unix) (=? pathname-device)\n                      (=? normalize-pathname-directory-component pathname-directory)\n                      (=? pathname-name)\n                      (=? pathname-type)\n                      #-mkcl (=? pathname-version)))))))\n\n  (defun absolute-pathname-p (pathspec)\n    \"If PATHSPEC is a pathname or namestring object that parses as a pathname\npossessing an :ABSOLUTE directory component, return the (parsed) pathname.\nOtherwise return NIL\"\n    (and pathspec\n         (typep pathspec '(or null pathname string))\n         (let ((pathname (pathname pathspec)))\n           (and (eq :absolute (car (normalize-pathname-directory-component\n                                    (pathname-directory pathname))))\n                pathname))))\n\n  (defun relative-pathname-p (pathspec)\n    \"If PATHSPEC is a pathname or namestring object that parses as a pathname\npossessing a :RELATIVE or NIL directory component, return the (parsed) pathname.\nOtherwise return NIL\"\n    (and pathspec\n         (typep pathspec '(or null pathname string))\n         (let* ((pathname (pathname pathspec))\n                (directory (normalize-pathname-directory-component\n                            (pathname-directory pathname))))\n           (when (or (null directory) (eq :relative (car directory)))\n             pathname))))\n\n  (defun hidden-pathname-p (pathname)\n    \"Return a boolean that is true if the pathname is hidden as per Unix style,\ni.e. its name starts with a dot.\"\n    (and pathname (equal (first-char (pathname-name pathname)) #\\.)))\n\n  (defun file-pathname-p (pathname)\n    \"Does PATHNAME represent a file, i.e. has a non-null NAME component?\n\nAccepts NIL, a string (converted through PARSE-NAMESTRING) or a PATHNAME.\n\nNote that this does _not_ check to see that PATHNAME points to an\nactually-existing file.\n\nReturns the (parsed) PATHNAME when true\"\n    (when pathname\n      (let ((pathname (pathname pathname)))\n        (unless (and (member (pathname-name pathname) '(nil :unspecific \"\") :test 'equal)\n                     (member (pathname-type pathname) '(nil :unspecific \"\") :test 'equal))\n          pathname)))))\n\n\n;;; Directory pathnames\n(with-upgradability ()\n  (defun pathname-directory-pathname (pathname)\n    \"Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME,\nand NIL NAME, TYPE and VERSION components\"\n    (when pathname\n      (make-pathname :name nil :type nil :version nil :defaults pathname)))\n\n  (defun pathname-parent-directory-pathname (pathname)\n    \"Returns a new pathname that corresponds to the parent of the current pathname's directory,\ni.e. removing one level of depth in the DIRECTORY component. e.g. if pathname is\nUnix pathname /foo/bar/baz/file.type then return /foo/bar/\"\n    (when pathname\n      (make-pathname :name nil :type nil :version nil\n                     :directory (merge-pathname-directory-components\n                                 '(:relative :back) (pathname-directory pathname))\n                     :defaults pathname)))\n\n  (defun directory-pathname-p (pathname)\n    \"Does PATHNAME represent a directory?\n\nA directory-pathname is a pathname _without_ a filename. The three\nways that the filename components can be missing are for it to be NIL,\n:UNSPECIFIC or the empty string.\n\nNote that this does _not_ check to see that PATHNAME points to an\nactually-existing directory.\"\n    (when pathname\n      ;; I tried using Allegro's excl:file-directory-p, but this cannot be done,\n      ;; because it rejects apparently legal pathnames as\n      ;; ill-formed. [2014/02/10:rpg]\n      (let ((pathname (pathname pathname)))\n        (flet ((check-one (x)\n                 (member x '(nil :unspecific) :test 'equal)))\n          (and (not (wild-pathname-p pathname))\n               (check-one (pathname-name pathname))\n               (check-one (pathname-type pathname))\n               t)))))\n\n  (defun ensure-directory-pathname (pathspec &optional (on-error 'error))\n    \"Converts the non-wild pathname designator PATHSPEC to directory form.\"\n    (cond\n      ((stringp pathspec)\n       (ensure-directory-pathname (pathname pathspec)))\n      ((not (pathnamep pathspec))\n       (call-function on-error (compatfmt \"~@<Invalid pathname designator ~S~@:>\") pathspec))\n      ((wild-pathname-p pathspec)\n       (call-function on-error (compatfmt \"~@<Can't reliably convert wild pathname ~3i~_~S~@:>\") pathspec))\n      ((directory-pathname-p pathspec)\n       pathspec)\n      (t\n       (handler-case\n           (make-pathname :directory (append (or (normalize-pathname-directory-component\n                                                  (pathname-directory pathspec))\n                                                 (list :relative))\n                                             (list (file-namestring pathspec)))\n                          :name nil :type nil :version nil :defaults pathspec)\n         (error (c) (call-function on-error (compatfmt \"~@<error while trying to create a directory pathname for ~S: ~A~@:>\") pathspec c)))))))\n\n\n;;; Parsing filenames\n(with-upgradability ()\n  (declaim (ftype function ensure-pathname)) ; forward reference\n\n  (defun split-unix-namestring-directory-components\n      (unix-namestring &key ensure-directory dot-dot)\n    \"Splits the path string UNIX-NAMESTRING, returning four values:\nA flag that is either :absolute or :relative, indicating\n   how the rest of the values are to be interpreted.\nA directory path --- a list of strings and keywords, suitable for\n   use with MAKE-PATHNAME when prepended with the flag value.\n   Directory components with an empty name or the name . are removed.\n   Any directory named .. is read as DOT-DOT, or :BACK if it's NIL (not :UP).\nA last-component, either a file-namestring including type extension,\n   or NIL in the case of a directory pathname.\nA flag that is true iff the unix-style-pathname was just\n   a file-namestring without / path specification.\nENSURE-DIRECTORY forces the namestring to be interpreted as a directory pathname:\nthe third return value will be NIL, and final component of the namestring\nwill be treated as part of the directory path.\n\nAn empty string is thus read as meaning a pathname object with all fields nil.\n\nNote that colon characters #\\: will NOT be interpreted as host specification.\nAbsolute pathnames are only appropriate on Unix-style systems.\n\nThe intention of this function is to support structured component names,\ne.g., \\(:file \\\"foo/bar\\\"\\), which will be unpacked to relative pathnames.\"\n    (check-type unix-namestring string)\n    (check-type dot-dot (member nil :back :up))\n    (if (and (not (find #\\/ unix-namestring)) (not ensure-directory)\n             (plusp (length unix-namestring)))\n        (values :relative () unix-namestring t)\n        (let* ((components (split-string unix-namestring :separator \"/\"))\n               (last-comp (car (last components))))\n          (multiple-value-bind (relative components)\n              (if (equal (first components) \"\")\n                  (if (equal (first-char unix-namestring) #\\/)\n                      (values :absolute (cdr components))\n                      (values :relative nil))\n                  (values :relative components))\n            (setf components (remove-if #'(lambda (x) (member x '(\"\" \".\") :test #'equal))\n                                        components))\n            (setf components (substitute (or dot-dot :back) \"..\" components :test #'equal))\n            (cond\n              ((equal last-comp \"\")\n               (values relative components nil nil)) ; \"\" already removed from components\n              (ensure-directory\n               (values relative components nil nil))\n              (t\n               (values relative (butlast components) last-comp nil)))))))\n\n  (defun split-name-type (filename)\n    \"Split a filename into two values NAME and TYPE that are returned.\nWe assume filename has no directory component.\nThe last . if any separates name and type from from type,\nexcept that if there is only one . and it is in first position,\nthe whole filename is the NAME with an empty type.\nNAME is always a string.\nFor an empty type, *UNSPECIFIC-PATHNAME-TYPE* is returned.\"\n    (check-type filename string)\n    (assert (plusp (length filename)))\n    (destructuring-bind (name &optional (type *unspecific-pathname-type*))\n        (split-string filename :max 2 :separator \".\")\n      (if (equal name \"\")\n          (values filename *unspecific-pathname-type*)\n          (values name type))))\n\n  (defun parse-unix-namestring (name &rest keys &key type defaults dot-dot ensure-directory\n                                &allow-other-keys)\n    \"Coerce NAME into a PATHNAME using standard Unix syntax.\n\nUnix syntax is used whether or not the underlying system is Unix;\non such non-Unix systems it is reliably usable only for relative pathnames.\nThis function is especially useful to manipulate relative pathnames portably,\nwhere it is of crucial to possess a portable pathname syntax independent of the underlying OS.\nThis is what PARSE-UNIX-NAMESTRING provides, and why we use it in ASDF.\n\nWhen given a PATHNAME object, just return it untouched.\nWhen given NIL, just return NIL.\nWhen given a non-null SYMBOL, first downcase its name and treat it as a string.\nWhen given a STRING, portably decompose it into a pathname as below.\n\n#\\\\/ separates directory components.\n\nThe last #\\\\/-separated substring is interpreted as follows:\n1- If TYPE is :DIRECTORY or ENSURE-DIRECTORY is true,\n the string is made the last directory component, and NAME and TYPE are NIL.\n if the string is empty, it's the empty pathname with all slots NIL.\n2- If TYPE is NIL, the substring is a file-namestring, and its NAME and TYPE\n are separated by SPLIT-NAME-TYPE.\n3- If TYPE is a string, it is the given TYPE, and the whole string is the NAME.\n\nDirectory components with an empty name or the name \\\".\\\" are removed.\nAny directory named \\\"..\\\" is read as DOT-DOT,\nwhich must be one of :BACK or :UP and defaults to :BACK.\n\nHOST, DEVICE and VERSION components are taken from DEFAULTS,\nwhich itself defaults to *NIL-PATHNAME*, also used if DEFAULTS is NIL.\nNo host or device can be specified in the string itself,\nwhich makes it unsuitable for absolute pathnames outside Unix.\n\nFor relative pathnames, these components (and hence the defaults) won't matter\nif you use MERGE-PATHNAMES* but will matter if you use MERGE-PATHNAMES,\nwhich is an important reason to always use MERGE-PATHNAMES*.\n\nArbitrary keys are accepted, and the parse result is passed to ENSURE-PATHNAME\nwith those keys, removing TYPE DEFAULTS and DOT-DOT.\nWhen you're manipulating pathnames that are supposed to make sense portably\neven though the OS may not be Unixish, we recommend you use :WANT-RELATIVE T\nto throw an error if the pathname is absolute\"\n    (block nil\n      (check-type type (or null string (eql :directory)))\n      (when ensure-directory\n        (setf type :directory))\n      (etypecase name\n        ((or null pathname) (return name))\n        (symbol\n         (setf name (string-downcase name)))\n        (string))\n      (multiple-value-bind (relative path filename file-only)\n          (split-unix-namestring-directory-components\n           name :dot-dot dot-dot :ensure-directory (eq type :directory))\n        (multiple-value-bind (name type)\n            (cond\n              ((or (eq type :directory) (null filename))\n               (values nil nil))\n              (type\n               (values filename type))\n              (t\n               (split-name-type filename)))\n          (apply 'ensure-pathname\n                 (make-pathname\n                  :directory (unless file-only (cons relative path))\n                  :name name :type type\n                  :defaults (or #-mcl defaults *nil-pathname*))\n                 (remove-plist-keys '(:type :dot-dot :defaults) keys))))))\n\n  (defun unix-namestring (pathname)\n    \"Given a non-wild PATHNAME, return a Unix-style namestring for it.\nIf the PATHNAME is NIL or a STRING, return it unchanged.\n\nThis only considers the DIRECTORY, NAME and TYPE components of the pathname.\nThis is a portable solution for representing relative pathnames,\nBut unless you are running on a Unix system, it is not a general solution\nto representing native pathnames.\n\nAn error is signaled if the argument is not NULL, a STRING or a PATHNAME,\nor if it is a PATHNAME but some of its components are not recognized.\"\n    (etypecase pathname\n      ((or null string) pathname)\n      (pathname\n       (with-output-to-string (s)\n         (flet ((err () (parameter-error \"~S: invalid unix-namestring ~S\"\n                                         'unix-namestring pathname)))\n           (let* ((dir (normalize-pathname-directory-component (pathname-directory pathname)))\n                  (name (pathname-name pathname))\n                  (name (and (not (eq name :unspecific)) name))\n                  (type (pathname-type pathname))\n                  (type (and (not (eq type :unspecific)) type)))\n             (cond\n               ((member dir '(nil :unspecific)))\n               ((eq dir '(:relative)) (princ \"./\" s))\n               ((consp dir)\n                (destructuring-bind (relabs &rest dirs) dir\n                  (or (member relabs '(:relative :absolute)) (err))\n                  (when (eq relabs :absolute) (princ #\\/ s))\n                  (loop :for x :in dirs :do\n                    (cond\n                      ((member x '(:back :up)) (princ \"../\" s))\n                      ((equal x \"\") (err))\n                      ;;((member x '(\".\" \"..\") :test 'equal) (err))\n                      ((stringp x) (format s \"~A/\" x))\n                      (t (err))))))\n               (t (err)))\n             (cond\n               (name\n                (unless (and (stringp name) (or (null type) (stringp type))) (err))\n                (format s \"~A~@[.~A~]\" name type))\n               (t\n                (or (null type) (err)))))))))))\n\n;;; Absolute and relative pathnames\n(with-upgradability ()\n  (defun subpathname (pathname subpath &key type)\n    \"This function takes a PATHNAME and a SUBPATH and a TYPE.\nIf SUBPATH is already a PATHNAME object (not namestring),\nand is an absolute pathname at that, it is returned unchanged;\notherwise, SUBPATH is turned into a relative pathname with given TYPE\nas per PARSE-UNIX-NAMESTRING with :WANT-RELATIVE T :TYPE TYPE,\nthen it is merged with the PATHNAME-DIRECTORY-PATHNAME of PATHNAME.\"\n    (or (and (pathnamep subpath) (absolute-pathname-p subpath))\n        (merge-pathnames* (parse-unix-namestring subpath :type type :want-relative t)\n                          (pathname-directory-pathname pathname))))\n\n  (defun subpathname* (pathname subpath &key type)\n    \"returns NIL if the base pathname is NIL, otherwise like SUBPATHNAME.\"\n    (and pathname\n         (subpathname (ensure-directory-pathname pathname) subpath :type type)))\n\n  (defun pathname-root (pathname)\n    \"return the root directory for the host and device of given PATHNAME\"\n    (make-pathname :directory '(:absolute)\n                   :name nil :type nil :version nil\n                   :defaults pathname ;; host device, and on scl, *some*\n                   ;; scheme-specific parts: port username password, not others:\n                   . #.(or #+scl '(:parameters nil :query nil :fragment nil))))\n\n  (defun pathname-host-pathname (pathname)\n    \"return a pathname with the same host as given PATHNAME, and all other fields NIL\"\n    (make-pathname :directory nil\n                   :name nil :type nil :version nil :device nil\n                   :defaults pathname ;; host device, and on scl, *some*\n                   ;; scheme-specific parts: port username password, not others:\n                   . #.(or #+scl '(:parameters nil :query nil :fragment nil))))\n\n  (defun ensure-absolute-pathname (path &optional defaults (on-error 'error))\n    \"Given a pathname designator PATH, return an absolute pathname as specified by PATH\nconsidering the DEFAULTS, or, if not possible, use CALL-FUNCTION on the specified ON-ERROR behavior,\nwith a format control-string and other arguments as arguments\"\n    (cond\n      ((absolute-pathname-p path))\n      ((stringp path) (ensure-absolute-pathname (pathname path) defaults on-error))\n      ((not (pathnamep path)) (call-function on-error \"not a valid pathname designator ~S\" path))\n      ((let ((default-pathname (if (pathnamep defaults) defaults (call-function defaults))))\n         (or (if (absolute-pathname-p default-pathname)\n                 (absolute-pathname-p (merge-pathnames* path default-pathname))\n                 (call-function on-error \"Default pathname ~S is not an absolute pathname\"\n                                default-pathname))\n             (call-function on-error \"Failed to merge ~S with ~S into an absolute pathname\"\n                            path default-pathname))))\n      (t (call-function on-error\n                        \"Cannot ensure ~S is evaluated as an absolute pathname with defaults ~S\"\n                        path defaults))))\n\n  (defun subpathp (maybe-subpath base-pathname)\n    \"if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that\nwhen used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH.\"\n    (and (pathnamep maybe-subpath) (pathnamep base-pathname)\n         (absolute-pathname-p maybe-subpath) (absolute-pathname-p base-pathname)\n         (directory-pathname-p base-pathname) (not (wild-pathname-p base-pathname))\n         (pathname-equal (pathname-root maybe-subpath) (pathname-root base-pathname))\n         (with-pathname-defaults (*nil-pathname*)\n           (let ((enough (enough-namestring maybe-subpath base-pathname)))\n             (and (relative-pathname-p enough) (pathname enough))))))\n\n  (defun enough-pathname (maybe-subpath base-pathname)\n    \"if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that\nwhen used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH.\"\n    (let ((sub (when maybe-subpath (pathname maybe-subpath)))\n          (base (when base-pathname (ensure-absolute-pathname (pathname base-pathname)))))\n      (or (and base (subpathp sub base)) sub)))\n\n  (defun call-with-enough-pathname (maybe-subpath defaults-pathname thunk)\n    \"In a context where *DEFAULT-PATHNAME-DEFAULTS* is bound to DEFAULTS-PATHNAME (if not null,\nor else to its current value), call THUNK with ENOUGH-PATHNAME for MAYBE-SUBPATH\ngiven DEFAULTS-PATHNAME as a base pathname.\"\n    (let ((enough (enough-pathname maybe-subpath defaults-pathname))\n          (*default-pathname-defaults* (or defaults-pathname *default-pathname-defaults*)))\n      (funcall thunk enough)))\n\n  (defmacro with-enough-pathname ((pathname-var &key (pathname pathname-var)\n                                                  (defaults *default-pathname-defaults*))\n                                  &body body)\n    \"Shorthand syntax for CALL-WITH-ENOUGH-PATHNAME\"\n    `(call-with-enough-pathname ,pathname ,defaults #'(lambda (,pathname-var) ,@body))))\n\n\n;;; Wildcard pathnames\n(with-upgradability ()\n  (defparameter *wild* (or #+cormanlisp \"*\" :wild)\n    \"Wild component for use with MAKE-PATHNAME\")\n  (defparameter *wild-directory-component* (or :wild)\n    \"Wild directory component for use with MAKE-PATHNAME\")\n  (defparameter *wild-inferiors-component* (or :wild-inferiors)\n    \"Wild-inferiors directory component for use with MAKE-PATHNAME\")\n  (defparameter *wild-file*\n    (make-pathname :directory nil :name *wild* :type *wild*\n                   :version (or #-(or allegro abcl xcl) *wild*))\n    \"A pathname object with wildcards for matching any file with TRANSLATE-PATHNAME\")\n  (defparameter *wild-file-for-directory*\n    (make-pathname :directory nil :name *wild* :type (or #-(or clisp gcl) *wild*)\n                   :version (or #-(or allegro abcl clisp gcl xcl) *wild*))\n    \"A pathname object with wildcards for matching any file with DIRECTORY\")\n  (defparameter *wild-directory*\n    (make-pathname :directory `(:relative ,*wild-directory-component*)\n                   :name nil :type nil :version nil)\n    \"A pathname object with wildcards for matching any subdirectory\")\n  (defparameter *wild-inferiors*\n    (make-pathname :directory `(:relative ,*wild-inferiors-component*)\n                   :name nil :type nil :version nil)\n    \"A pathname object with wildcards for matching any recursive subdirectory\")\n  (defparameter *wild-path*\n    (merge-pathnames* *wild-file* *wild-inferiors*)\n    \"A pathname object with wildcards for matching any file in any recursive subdirectory\")\n\n  (defun wilden (path)\n    \"From a pathname, return a wildcard pathname matching any file in any subdirectory of given pathname's directory\"\n    (merge-pathnames* *wild-path* path)))\n\n\n;;; Translate a pathname\n(with-upgradability ()\n  (defun relativize-directory-component (directory-component)\n    \"Given the DIRECTORY-COMPONENT of a pathname, return an otherwise similar relative directory component\"\n    (let ((directory (normalize-pathname-directory-component directory-component)))\n      (cond\n        ((stringp directory)\n         (list :relative directory))\n        ((eq (car directory) :absolute)\n         (cons :relative (cdr directory)))\n        (t\n         directory))))\n\n  (defun relativize-pathname-directory (pathspec)\n    \"Given a PATHNAME, return a relative pathname with otherwise the same components\"\n    (let ((p (pathname pathspec)))\n      (make-pathname\n       :directory (relativize-directory-component (pathname-directory p))\n       :defaults p)))\n\n  (defun directory-separator-for-host (&optional (pathname *default-pathname-defaults*))\n    \"Given a PATHNAME, return the character used to delimit directory names on this host and device.\"\n    (let ((foo (make-pathname :directory '(:absolute \"FOO\") :defaults pathname)))\n      (last-char (namestring foo))))\n\n  #-scl\n  (defun directorize-pathname-host-device (pathname)\n    \"Given a PATHNAME, return a pathname that has representations of its HOST and DEVICE components\nadded to its DIRECTORY component. This is useful for output translations.\"\n    (os-cond\n     ((os-unix-p)\n      (when (physical-pathname-p pathname)\n        (return-from directorize-pathname-host-device pathname))))\n    (let* ((root (pathname-root pathname))\n           (wild-root (wilden root))\n           (absolute-pathname (merge-pathnames* pathname root))\n           (separator (directory-separator-for-host root))\n           (root-namestring (namestring root))\n           (root-string\n             (substitute-if #\\/\n                            #'(lambda (x) (or (eql x #\\:)\n                                              (eql x separator)))\n                            root-namestring)))\n      (multiple-value-bind (relative path filename)\n          (split-unix-namestring-directory-components root-string :ensure-directory t)\n        (declare (ignore relative filename))\n        (let ((new-base (make-pathname :defaults root :directory `(:absolute ,@path))))\n          (translate-pathname absolute-pathname wild-root (wilden new-base))))))\n\n  #+scl\n  (defun directorize-pathname-host-device (pathname)\n    (let ((scheme (ext:pathname-scheme pathname))\n          (host (pathname-host pathname))\n          (port (ext:pathname-port pathname))\n          (directory (pathname-directory pathname)))\n      (flet ((specificp (x) (and x (not (eq x :unspecific)))))\n        (if (or (specificp port)\n                (and (specificp host) (plusp (length host)))\n                (specificp scheme))\n            (let ((prefix \"\"))\n              (when (specificp port)\n                (setf prefix (format nil \":~D\" port)))\n              (when (and (specificp host) (plusp (length host)))\n                (setf prefix (strcat host prefix)))\n              (setf prefix (strcat \":\" prefix))\n              (when (specificp scheme)\n                (setf prefix (strcat scheme prefix)))\n              (assert (and directory (eq (first directory) :absolute)))\n              (make-pathname :directory `(:absolute ,prefix ,@(rest directory))\n                             :defaults pathname)))\n        pathname)))\n\n  (defun* (translate-pathname*) (path absolute-source destination &optional root source)\n    \"A wrapper around TRANSLATE-PATHNAME to be used by the ASDF output-translations facility.\nPATH is the pathname to be translated.\nABSOLUTE-SOURCE is an absolute pathname to use as source for translate-pathname,\nDESTINATION is either a function, to be called with PATH and ABSOLUTE-SOURCE,\nor a relative pathname, to be merged with ROOT and used as destination for translate-pathname\nor an absolute pathname, to be used as destination for translate-pathname.\nIn that last case, if ROOT is non-NIL, PATH is first transformated by DIRECTORIZE-PATHNAME-HOST-DEVICE.\"\n    (declare (ignore source))\n    (cond\n      ((functionp destination)\n       (funcall destination path absolute-source))\n      ((eq destination t)\n       path)\n      ((not (pathnamep destination))\n       (parameter-error \"~S: Invalid destination\" 'translate-pathname*))\n      ((not (absolute-pathname-p destination))\n       (translate-pathname path absolute-source (merge-pathnames* destination root)))\n      (root\n       (translate-pathname (directorize-pathname-host-device path) absolute-source destination))\n      (t\n       (translate-pathname path absolute-source destination))))\n\n  (defvar *output-translation-function* 'identity\n    \"Hook for output translations.\n\nThis function needs to be idempotent, so that actions can work\nwhether their inputs were translated or not,\nwhich they will be if we are composing operations. e.g. if some\ncreate-lisp-op creates a lisp file from some higher-level input,\nyou need to still be able to use compile-op on that lisp file.\"))\n;;;; -------------------------------------------------------------------------\n;;;; Portability layer around Common Lisp filesystem access\n\n(uiop/package:define-package :uiop/filesystem\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os :uiop/pathname)\n  (:export\n   ;; Native namestrings\n   #:native-namestring #:parse-native-namestring\n   ;; Probing the filesystem\n   #:truename* #:safe-file-write-date #:probe-file* #:directory-exists-p #:file-exists-p\n   #:directory* #:filter-logical-directory-results #:directory-files #:subdirectories\n   #:collect-sub*directories\n   ;; Resolving symlinks somewhat\n   #:truenamize #:resolve-symlinks #:*resolve-symlinks* #:resolve-symlinks*\n   ;; merging with cwd\n   #:get-pathname-defaults #:call-with-current-directory #:with-current-directory\n   ;; Environment pathnames\n   #:inter-directory-separator #:split-native-pathnames-string\n   #:getenv-pathname #:getenv-pathnames\n   #:getenv-absolute-directory #:getenv-absolute-directories\n   #:lisp-implementation-directory #:lisp-implementation-pathname-p\n   ;; Simple filesystem operations\n   #:ensure-all-directories-exist\n   #:rename-file-overwriting-target\n   #:delete-file-if-exists #:delete-empty-directory #:delete-directory-tree))\n(in-package :uiop/filesystem)\n\n;;; Native namestrings, as seen by the operating system calls rather than Lisp\n(with-upgradability ()\n  (defun native-namestring (x)\n    \"From a non-wildcard CL pathname, a return namestring suitable for passing to the operating system\"\n    (when x\n      (let ((p (pathname x)))\n        #+clozure (with-pathname-defaults () (ccl:native-translated-namestring p)) ; see ccl bug 978\n        #+(or cmucl scl) (ext:unix-namestring p nil)\n        #+sbcl (sb-ext:native-namestring p)\n        #-(or clozure cmucl sbcl scl)\n        (os-cond\n         ((os-unix-p) (unix-namestring p))\n         (t (namestring p))))))\n\n  (defun parse-native-namestring (string &rest constraints &key ensure-directory &allow-other-keys)\n    \"From a native namestring suitable for use by the operating system, return\na CL pathname satisfying all the specified constraints as per ENSURE-PATHNAME\"\n    (check-type string (or string null))\n    (let* ((pathname\n             (when string\n               (with-pathname-defaults ()\n                 #+clozure (ccl:native-to-pathname string)\n                 #+cmucl (uiop/os::parse-unix-namestring* string)\n                 #+sbcl (sb-ext:parse-native-namestring string)\n                 #+scl (lisp::parse-unix-namestring string)\n                 #-(or clozure cmucl sbcl scl)\n                 (os-cond\n                  ((os-unix-p) (parse-unix-namestring string :ensure-directory ensure-directory))\n                  (t (parse-namestring string))))))\n           (pathname\n             (if ensure-directory\n                 (and pathname (ensure-directory-pathname pathname))\n                 pathname)))\n      (apply 'ensure-pathname pathname constraints))))\n\n\n;;; Probing the filesystem\n(with-upgradability ()\n  (defun truename* (p)\n    \"Nicer variant of TRUENAME that plays well with NIL, avoids logical pathname contexts, and tries both files and directories\"\n    (when p\n      (when (stringp p) (setf p (with-pathname-defaults () (parse-namestring p))))\n      (values\n       (or (ignore-errors (truename p))\n           ;; this is here because trying to find the truename of a directory pathname WITHOUT supplying\n           ;; a trailing directory separator, causes an error on some lisps.\n           #+(or clisp gcl) (if-let (d (ensure-directory-pathname p nil)) (ignore-errors (truename d)))))))\n\n  (defun safe-file-write-date (pathname)\n    \"Safe variant of FILE-WRITE-DATE that may return NIL rather than raise an error.\"\n    ;; If FILE-WRITE-DATE returns NIL, it's possible that\n    ;; the user or some other agent has deleted an input file.\n    ;; Also, generated files will not exist at the time planning is done\n    ;; and calls compute-action-stamp which calls safe-file-write-date.\n    ;; So it is very possible that we can't get a valid file-write-date,\n    ;; and we can survive and we will continue the planning\n    ;; as if the file were very old.\n    ;; (or should we treat the case in a different, special way?)\n    (and pathname\n         (handler-case (file-write-date (physicalize-pathname pathname))\n           (file-error () nil))))\n\n  (defun probe-file* (p &key truename)\n    \"when given a pathname P (designated by a string as per PARSE-NAMESTRING),\nprobes the filesystem for a file or directory with given pathname.\nIf it exists, return its truename if TRUENAME is true,\nor the original (parsed) pathname if it is false (the default).\"\n    (values\n     (ignore-errors\n      (setf p (funcall 'ensure-pathname p\n                       :namestring :lisp\n                       :ensure-physical t\n                       :ensure-absolute t :defaults 'get-pathname-defaults\n                       :want-non-wild t\n                       :on-error nil))\n      (when p\n        #+allegro\n        (probe-file p :follow-symlinks truename)\n        #+gcl\n        (if truename\n            (truename* p)\n            (let ((kind (car (si::stat p))))\n              (when (eq kind :link)\n                (setf kind (ignore-errors (car (si::stat (truename* p))))))\n              (ecase kind\n                ((nil) nil)\n                ((:file :link)\n                 (cond\n                   ((file-pathname-p p) p)\n                   ((directory-pathname-p p)\n                    (subpathname p (car (last (pathname-directory p)))))))\n                (:directory (ensure-directory-pathname p)))))\n        #+clisp\n        #.(let* ((fs (or #-os-windows (find-symbol* '#:file-stat :posix nil)))\n                 (pp (find-symbol* '#:probe-pathname :ext nil)))\n            `(if truename\n                 ,(if pp\n                      `(values (,pp p))\n                      '(or (truename* p)\n                        (truename* (ignore-errors (ensure-directory-pathname p)))))\n                 ,(cond\n                    (fs `(and (,fs p) p))\n                    (pp `(nth-value 1 (,pp p)))\n                    (t '(or (and (truename* p) p)\n                         (if-let (d (ensure-directory-pathname p))\n                          (and (truename* d) d)))))))\n        #-(or allegro clisp gcl)\n        (if truename\n            (probe-file p)\n            (and\n             #+(or cmucl scl) (unix:unix-stat (ext:unix-namestring p))\n             #+(and lispworks os-unix) (system:get-file-stat p)\n             #+sbcl (sb-unix:unix-stat (sb-ext:native-namestring p))\n             #-(or cmucl (and lispworks os-unix) sbcl scl) (file-write-date p)\n             p))))))\n\n  (defun directory-exists-p (x)\n    \"Is X the name of a directory that exists on the filesystem?\"\n    #+allegro\n    (excl:probe-directory x)\n    #+clisp\n    (handler-case (ext:probe-directory x)\n           (sys::simple-file-error ()\n             nil))\n    #-(or allegro clisp)\n    (let ((p (probe-file* x :truename t)))\n      (and (directory-pathname-p p) p)))\n\n  (defun file-exists-p (x)\n    \"Is X the name of a file that exists on the filesystem?\"\n    (let ((p (probe-file* x :truename t)))\n      (and (file-pathname-p p) p)))\n\n  (defun directory* (pathname-spec &rest keys &key &allow-other-keys)\n    \"Return a list of the entries in a directory by calling DIRECTORY.\nTry to override the defaults to not resolving symlinks, if implementation allows.\"\n    (apply 'directory pathname-spec\n           (append keys '#.(or #+allegro '(:directories-are-files nil :follow-symbolic-links nil)\n                               #+(or clozure digitool) '(:follow-links nil)\n                               #+clisp '(:circle t :if-does-not-exist :ignore)\n                               #+(or cmucl scl) '(:follow-links nil :truenamep nil)\n                               #+lispworks '(:link-transparency nil)\n                               #+sbcl (when (find-symbol* :resolve-symlinks '#:sb-impl nil)\n                                        '(:resolve-symlinks nil))))))\n\n  (defun filter-logical-directory-results (directory entries merger)\n    \"If DIRECTORY isn't a logical pathname, return ENTRIES. If it is,\ngiven ENTRIES in the DIRECTORY, remove the entries which are physical yet\nwhen transformed by MERGER have a different TRUENAME.\nAlso remove duplicates as may appear with some translation rules.\nThis function is used as a helper to DIRECTORY-FILES to avoid invalid entries\nwhen using logical-pathnames.\"\n    (if (logical-pathname-p directory)\n        (remove-duplicates ;; on CLISP, querying ~/ will return duplicates\n         ;; Try hard to not resolve logical-pathname into physical pathnames;\n         ;; otherwise logical-pathname users/lovers will be disappointed.\n         ;; If directory* could use some implementation-dependent magic,\n         ;; we will have logical pathnames already; otherwise,\n         ;; we only keep pathnames for which specifying the name and\n         ;; translating the LPN commute.\n         (loop :for f :in entries\n               :for p = (or (and (logical-pathname-p f) f)\n                            (let* ((u (ignore-errors (call-function merger f))))\n                              ;; The first u avoids a cumbersome (truename u) error.\n                              ;; At this point f should already be a truename,\n                              ;; but isn't quite in CLISP, for it doesn't have :version :newest\n                              (and u (equal (truename* u) (truename* f)) u)))\n           :when p :collect p)\n         :test 'pathname-equal)\n        entries))\n\n  (defun directory-files (directory &optional (pattern *wild-file-for-directory*))\n    \"Return a list of the files in a directory according to the PATTERN.\nSubdirectories should NOT be returned.\n  PATTERN defaults to a pattern carefully chosen based on the implementation;\noverride the default at your own risk.\n  DIRECTORY-FILES tries NOT to resolve symlinks if the implementation permits this,\nbut the behavior in presence of symlinks is not portable. Use IOlib to handle such situations.\"\n    (let ((dir (pathname directory)))\n      (when (logical-pathname-p dir)\n        ;; Because of the filtering we do below,\n        ;; logical pathnames have restrictions on wild patterns.\n        ;; Not that the results are very portable when you use these patterns on physical pathnames.\n        (when (wild-pathname-p dir)\n          (parameter-error \"~S: Invalid wild pattern in logical directory ~S\"\n                           'directory-files directory))\n        (unless (member (pathname-directory pattern) '(() (:relative)) :test 'equal)\n          (parameter-error \"~S: Invalid file pattern ~S for logical directory ~S\" 'directory-files pattern directory))\n        (setf pattern (make-pathname-logical pattern (pathname-host dir))))\n      (let* ((pat (merge-pathnames* pattern dir))\n             (entries (ignore-errors (directory* pat))))\n        (remove-if 'directory-pathname-p\n                   (filter-logical-directory-results\n                    directory entries\n                    #'(lambda (f)\n                        (make-pathname :defaults dir\n                                       :name (make-pathname-component-logical (pathname-name f))\n                                       :type (make-pathname-component-logical (pathname-type f))\n                                       :version (make-pathname-component-logical (pathname-version f)))))))))\n\n  (defun subdirectories (directory)\n    \"Given a DIRECTORY pathname designator, return a list of the subdirectories under it.\nThe behavior in presence of symlinks is not portable. Use IOlib to handle such situations.\"\n    (let* ((directory (ensure-directory-pathname directory))\n           #-(or abcl cormanlisp genera xcl)\n           (wild (merge-pathnames*\n                  #-(or abcl allegro cmucl lispworks sbcl scl xcl)\n                  *wild-directory*\n                  #+(or abcl allegro cmucl lispworks sbcl scl xcl) \"*.*\"\n                  directory))\n           (dirs\n             #-(or abcl cormanlisp genera xcl)\n             (ignore-errors\n              (directory* wild . #.(or #+clozure '(:directories t :files nil)\n                                       #+mcl '(:directories t))))\n             #+(or abcl xcl) (system:list-directory directory)\n             #+cormanlisp (cl::directory-subdirs directory)\n             #+genera (handler-case (fs:directory-list directory) (fs:directory-not-found () nil)))\n           #+(or abcl allegro cmucl genera lispworks sbcl scl xcl)\n           (dirs (loop :for x :in dirs\n                       :for d = #+(or abcl xcl) (extensions:probe-directory x)\n                       #+allegro (excl:probe-directory x)\n                       #+(or cmucl sbcl scl) (directory-pathname-p x)\n                       #+genera (getf (cdr x) :directory)\n                       #+lispworks (lw:file-directory-p x)\n                       :when d :collect #+(or abcl allegro xcl) (ensure-directory-pathname d)\n                         #+genera (ensure-directory-pathname (first x))\n                       #+(or cmucl lispworks sbcl scl) x)))\n      (filter-logical-directory-results\n       directory dirs\n       (let ((prefix (or (normalize-pathname-directory-component (pathname-directory directory))\n                         '(:absolute)))) ; because allegro returns NIL for #p\"FOO:\"\n         #'(lambda (d)\n             (let ((dir (normalize-pathname-directory-component (pathname-directory d))))\n               (and (consp dir) (consp (cdr dir))\n                    (make-pathname\n                     :defaults directory :name nil :type nil :version nil\n                     :directory (append prefix (make-pathname-component-logical (last dir)))))))))))\n\n  (defun collect-sub*directories (directory collectp recursep collector)\n    \"Given a DIRECTORY, when COLLECTP returns true when CALL-FUNCTION'ed with the directory,\ncall-function the COLLECTOR function designator on the directory,\nand recurse each of its subdirectories on which the RECURSEP returns true when CALL-FUNCTION'ed with them.\nThis function will thus let you traverse a filesystem hierarchy,\nsuperseding the functionality of CL-FAD:WALK-DIRECTORY.\nThe behavior in presence of symlinks is not portable. Use IOlib to handle such situations.\"\n    (when (call-function collectp directory)\n      (call-function collector directory)\n      (dolist (subdir (subdirectories directory))\n        (when (call-function recursep subdir)\n          (collect-sub*directories subdir collectp recursep collector))))))\n\n;;; Resolving symlinks somewhat\n(with-upgradability ()\n  (defun truenamize (pathname)\n    \"Resolve as much of a pathname as possible\"\n    (block nil\n      (when (typep pathname '(or null logical-pathname)) (return pathname))\n      (let ((p pathname))\n        (unless (absolute-pathname-p p)\n          (setf p (or (absolute-pathname-p (ensure-absolute-pathname p 'get-pathname-defaults nil))\n                      (return p))))\n        (when (logical-pathname-p p) (return p))\n        (let ((found (probe-file* p :truename t)))\n          (when found (return found)))\n        (let* ((directory (normalize-pathname-directory-component (pathname-directory p)))\n               (up-components (reverse (rest directory)))\n               (down-components ()))\n          (assert (eq :absolute (first directory)))\n          (loop :while up-components :do\n            (if-let (parent\n                     (ignore-errors\n                      (probe-file* (make-pathname :directory `(:absolute ,@(reverse up-components))\n                                                  :name nil :type nil :version nil :defaults p))))\n              (if-let (simplified\n                       (ignore-errors\n                        (merge-pathnames*\n                         (make-pathname :directory `(:relative ,@down-components)\n                                        :defaults p)\n                         (ensure-directory-pathname parent))))\n                (return simplified)))\n            (push (pop up-components) down-components)\n            :finally (return p))))))\n\n  (defun resolve-symlinks (path)\n    \"Do a best effort at resolving symlinks in PATH, returning a partially or totally resolved PATH.\"\n    #-allegro (truenamize path)\n    #+allegro\n    (if (physical-pathname-p path)\n        (or (ignore-errors (excl:pathname-resolve-symbolic-links path)) path)\n        path))\n\n  (defvar *resolve-symlinks* t\n    \"Determine whether or not ASDF resolves symlinks when defining systems.\nDefaults to T.\")\n\n  (defun resolve-symlinks* (path)\n    \"RESOLVE-SYMLINKS in PATH iff *RESOLVE-SYMLINKS* is T (the default).\"\n    (if *resolve-symlinks*\n        (and path (resolve-symlinks path))\n        path)))\n\n\n;;; Check pathname constraints\n(with-upgradability ()\n  (defun ensure-pathname\n      (pathname &key\n                  on-error\n                  defaults type dot-dot namestring\n                  empty-is-nil\n                  want-pathname\n                  want-logical want-physical ensure-physical\n                  want-relative want-absolute ensure-absolute ensure-subpath\n                  want-non-wild want-wild wilden\n                  want-file want-directory ensure-directory\n                  want-existing ensure-directories-exist\n                  truename resolve-symlinks truenamize\n       &aux (p pathname)) ;; mutable working copy, preserve original\n    \"Coerces its argument into a PATHNAME,\noptionally doing some transformations and checking specified constraints.\n\nIf the argument is NIL, then NIL is returned unless the WANT-PATHNAME constraint is specified.\n\nIf the argument is a STRING, it is first converted to a pathname via\nPARSE-UNIX-NAMESTRING, PARSE-NAMESTRING or PARSE-NATIVE-NAMESTRING respectively\ndepending on the NAMESTRING argument being :UNIX, :LISP or :NATIVE respectively,\nor else by using CALL-FUNCTION on the NAMESTRING argument;\nif :UNIX is specified (or NIL, the default, which specifies the same thing),\nthen PARSE-UNIX-NAMESTRING it is called with the keywords\nDEFAULTS TYPE DOT-DOT ENSURE-DIRECTORY WANT-RELATIVE, and\nthe result is optionally merged into the DEFAULTS if ENSURE-ABSOLUTE is true.\n\nThe pathname passed or resulting from parsing the string\nis then subjected to all the checks and transformations below are run.\n\nEach non-nil constraint argument can be one of the symbols T, ERROR, CERROR or IGNORE.\nThe boolean T is an alias for ERROR.\nERROR means that an error will be raised if the constraint is not satisfied.\nCERROR means that an continuable error will be raised if the constraint is not satisfied.\nIGNORE means just return NIL instead of the pathname.\n\nThe ON-ERROR argument, if not NIL, is a function designator (as per CALL-FUNCTION)\nthat will be called with the the following arguments:\na generic format string for ensure pathname, the pathname,\nthe keyword argument corresponding to the failed check or transformation,\na format string for the reason ENSURE-PATHNAME failed,\nand a list with arguments to that format string.\nIf ON-ERROR is NIL, ERROR is used instead, which does the right thing.\nYou could also pass (CERROR \\\"CONTINUE DESPITE FAILED CHECK\\\").\n\nThe transformations and constraint checks are done in this order,\nwhich is also the order in the lambda-list:\n\nEMPTY-IS-NIL returns NIL if the argument is an empty string.\nWANT-PATHNAME checks that pathname (after parsing if needed) is not null.\nOtherwise, if the pathname is NIL, ensure-pathname returns NIL.\nWANT-LOGICAL checks that pathname is a LOGICAL-PATHNAME\nWANT-PHYSICAL checks that pathname is not a LOGICAL-PATHNAME\nENSURE-PHYSICAL ensures that pathname is physical via TRANSLATE-LOGICAL-PATHNAME\nWANT-RELATIVE checks that pathname has a relative directory component\nWANT-ABSOLUTE checks that pathname does have an absolute directory component\nENSURE-ABSOLUTE merges with the DEFAULTS, then checks again\nthat the result absolute is an absolute pathname indeed.\nENSURE-SUBPATH checks that the pathname is a subpath of the DEFAULTS.\nWANT-FILE checks that pathname has a non-nil FILE component\nWANT-DIRECTORY checks that pathname has nil FILE and TYPE components\nENSURE-DIRECTORY uses ENSURE-DIRECTORY-PATHNAME to interpret\nany file and type components as being actually a last directory component.\nWANT-NON-WILD checks that pathname is not a wild pathname\nWANT-WILD checks that pathname is a wild pathname\nWILDEN merges the pathname with **/*.*.* if it is not wild\nWANT-EXISTING checks that a file (or directory) exists with that pathname.\nENSURE-DIRECTORIES-EXIST creates any parent directory with ENSURE-DIRECTORIES-EXIST.\nTRUENAME replaces the pathname by its truename, or errors if not possible.\nRESOLVE-SYMLINKS replaces the pathname by a variant with symlinks resolved by RESOLVE-SYMLINKS.\nTRUENAMIZE uses TRUENAMIZE to resolve as many symlinks as possible.\"\n    (block nil\n      (flet ((report-error (keyword description &rest arguments)\n               (call-function (or on-error 'error)\n                              \"Invalid pathname ~S: ~*~?\"\n                              pathname keyword description arguments)))\n        (macrolet ((err (constraint &rest arguments)\n                     `(report-error ',(intern* constraint :keyword) ,@arguments))\n                   (check (constraint condition &rest arguments)\n                     `(when ,constraint\n                        (unless ,condition (err ,constraint ,@arguments))))\n                   (transform (transform condition expr)\n                     `(when ,transform\n                        (,@(if condition `(when ,condition) '(progn))\n                         (setf p ,expr)))))\n          (etypecase p\n            ((or null pathname))\n            (string\n             (when (and (emptyp p) empty-is-nil)\n               (return-from ensure-pathname nil))\n             (setf p (case namestring\n                       ((:unix nil)\n                        (parse-unix-namestring\n                         p :defaults defaults :type type :dot-dot dot-dot\n                           :ensure-directory ensure-directory :want-relative want-relative))\n                       ((:native)\n                        (parse-native-namestring p))\n                       ((:lisp)\n                        (parse-namestring p))\n                       (t\n                        (call-function namestring p))))))\n          (etypecase p\n            (pathname)\n            (null\n             (check want-pathname (pathnamep p) \"Expected a pathname, not NIL\")\n             (return nil)))\n          (check want-logical (logical-pathname-p p) \"Expected a logical pathname\")\n          (check want-physical (physical-pathname-p p) \"Expected a physical pathname\")\n          (transform ensure-physical () (physicalize-pathname p))\n          (check ensure-physical (physical-pathname-p p) \"Could not translate to a physical pathname\")\n          (check want-relative (relative-pathname-p p) \"Expected a relative pathname\")\n          (check want-absolute (absolute-pathname-p p) \"Expected an absolute pathname\")\n          (transform ensure-absolute (not (absolute-pathname-p p))\n                     (ensure-absolute-pathname p defaults (list #'report-error :ensure-absolute \"~@?\")))\n          (check ensure-absolute (absolute-pathname-p p)\n                 \"Could not make into an absolute pathname even after merging with ~S\" defaults)\n          (check ensure-subpath (absolute-pathname-p defaults)\n                 \"cannot be checked to be a subpath of non-absolute pathname ~S\" defaults)\n          (check ensure-subpath (subpathp p defaults) \"is not a sub pathname of ~S\" defaults)\n          (check want-file (file-pathname-p p) \"Expected a file pathname\")\n          (check want-directory (directory-pathname-p p) \"Expected a directory pathname\")\n          (transform ensure-directory (not (directory-pathname-p p)) (ensure-directory-pathname p))\n          (check want-non-wild (not (wild-pathname-p p)) \"Expected a non-wildcard pathname\")\n          (check want-wild (wild-pathname-p p) \"Expected a wildcard pathname\")\n          (transform wilden (not (wild-pathname-p p)) (wilden p))\n          (when want-existing\n            (let ((existing (probe-file* p :truename truename)))\n              (if existing\n                  (when truename\n                    (return existing))\n                  (err want-existing \"Expected an existing pathname\"))))\n          (when ensure-directories-exist (ensure-directories-exist p))\n          (when truename\n            (let ((truename (truename* p)))\n              (if truename\n                  (return truename)\n                  (err truename \"Can't get a truename for pathname\"))))\n          (transform resolve-symlinks () (resolve-symlinks p))\n          (transform truenamize () (truenamize p))\n          p)))))\n\n\n;;; Pathname defaults\n(with-upgradability ()\n  (defun get-pathname-defaults (&optional (defaults *default-pathname-defaults*))\n    \"Find the actual DEFAULTS to use for pathnames, including\nresolving them with respect to GETCWD if the DEFAULTS were relative\"\n    (or (absolute-pathname-p defaults)\n        (merge-pathnames* defaults (getcwd))))\n\n  (defun call-with-current-directory (dir thunk)\n    \"call the THUNK in a context where the current directory was changed to DIR, if not NIL.\nNote that this operation is usually NOT thread-safe.\"\n    (if dir\n        (let* ((dir (resolve-symlinks* (get-pathname-defaults (pathname-directory-pathname dir))))\n               (cwd (getcwd))\n               (*default-pathname-defaults* dir))\n          (chdir dir)\n          (unwind-protect\n               (funcall thunk)\n            (chdir cwd)))\n        (funcall thunk)))\n\n  (defmacro with-current-directory ((&optional dir) &body body)\n    \"Call BODY while the POSIX current working directory is set to DIR\"\n    `(call-with-current-directory ,dir #'(lambda () ,@body))))\n\n\n;;; Environment pathnames\n(with-upgradability ()\n  (defun inter-directory-separator ()\n    \"What character does the current OS conventionally uses to separate directories?\"\n    (os-cond ((os-unix-p) #\\:) (t #\\;)))\n\n  (defun split-native-pathnames-string (string &rest constraints &key &allow-other-keys)\n    \"Given a string of pathnames specified in native OS syntax, separate them in a list,\ncheck constraints and normalize each one as per ENSURE-PATHNAME,\nwhere an empty string denotes NIL.\"\n    (loop :for namestring :in (split-string string :separator (string (inter-directory-separator)))\n          :collect (unless (emptyp namestring) (apply 'parse-native-namestring namestring constraints))))\n\n  (defun getenv-pathname (x &rest constraints &key ensure-directory want-directory on-error &allow-other-keys)\n    \"Extract a pathname from a user-configured environment variable, as per native OS,\ncheck constraints and normalize as per ENSURE-PATHNAME.\"\n    ;; For backward compatibility with ASDF 2, want-directory implies ensure-directory\n    (apply 'parse-native-namestring (getenvp x)\n           :ensure-directory (or ensure-directory want-directory)\n           :on-error (or on-error\n                         `(error \"In (~S ~S), invalid pathname ~*~S: ~*~?\" getenv-pathname ,x))\n           constraints))\n  (defun getenv-pathnames (x &rest constraints &key on-error &allow-other-keys)\n    \"Extract a list of pathname from a user-configured environment variable, as per native OS,\ncheck constraints and normalize each one as per ENSURE-PATHNAME.\n       Any empty entries in the environment variable X will be returned as NILs.\"\n    (unless (getf constraints :empty-is-nil t)\n      (parameter-error \"Cannot have EMPTY-IS-NIL false for ~S\" 'getenv-pathnames))\n    (apply 'split-native-pathnames-string (getenvp x)\n           :on-error (or on-error\n                         `(error \"In (~S ~S), invalid pathname ~*~S: ~*~?\" getenv-pathnames ,x))\n           :empty-is-nil t\n           constraints))\n  (defun getenv-absolute-directory (x)\n    \"Extract an absolute directory pathname from a user-configured environment variable,\nas per native OS\"\n    (getenv-pathname x :want-absolute t :ensure-directory t))\n  (defun getenv-absolute-directories (x)\n    \"Extract a list of absolute directories from a user-configured environment variable,\nas per native OS.  Any empty entries in the environment variable X will be returned as\nNILs.\"\n    (getenv-pathnames x :want-absolute t :ensure-directory t))\n\n  (defun lisp-implementation-directory (&key truename)\n    \"Where are the system files of the current installation of the CL implementation?\"\n    (declare (ignorable truename))\n    (let ((dir\n            #+abcl extensions:*lisp-home*\n            #+(or allegro clasp ecl mkcl) #p\"SYS:\"\n            #+clisp custom:*lib-directory*\n            #+clozure #p\"ccl:\"\n            #+cmucl (ignore-errors (pathname-parent-directory-pathname (truename #p\"modules:\")))\n            #+gcl system::*system-directory*\n            #+lispworks lispworks:*lispworks-directory*\n            #+sbcl (if-let (it (find-symbol* :sbcl-homedir-pathname :sb-int nil))\n                     (funcall it)\n                     (getenv-pathname \"SBCL_HOME\" :ensure-directory t))\n            #+scl (ignore-errors (pathname-parent-directory-pathname (truename #p\"file://modules/\")))\n            #+xcl ext:*xcl-home*))\n      (if (and dir truename)\n          (truename* dir)\n          dir)))\n\n  (defun lisp-implementation-pathname-p (pathname)\n    \"Is the PATHNAME under the current installation of the CL implementation?\"\n    ;; Other builtin systems are those under the implementation directory\n    (and (when pathname\n           (if-let (impdir (lisp-implementation-directory))\n             (or (subpathp pathname impdir)\n                 (when *resolve-symlinks*\n                   (if-let (truename (truename* pathname))\n                     (if-let (trueimpdir (truename* impdir))\n                       (subpathp truename trueimpdir)))))))\n         t)))\n\n\n;;; Simple filesystem operations\n(with-upgradability ()\n  (defun ensure-all-directories-exist (pathnames)\n    \"Ensure that for every pathname in PATHNAMES, we ensure its directories exist\"\n    (dolist (pathname pathnames)\n      (when pathname\n        (ensure-directories-exist (physicalize-pathname pathname)))))\n\n  (defun delete-file-if-exists (x)\n    \"Delete a file X if it already exists\"\n    (when x (handler-case (delete-file x) (file-error () nil))))\n\n  (defun rename-file-overwriting-target (source target)\n    \"Rename a file, overwriting any previous file with the TARGET name,\nin an atomic way if the implementation allows.\"\n    (let ((source (ensure-pathname source :namestring :lisp :ensure-physical t :want-file t))\n          (target (ensure-pathname target :namestring :lisp :ensure-physical t :want-file t)))\n      #+clisp ;; in recent enough versions of CLISP, :if-exists :overwrite would make it atomic\n      (progn (funcall 'require \"syscalls\")\n             (symbol-call :posix :copy-file source target :method :rename))\n      #+(and sbcl os-windows) (delete-file-if-exists target) ;; not atomic\n      #-clisp\n      (rename-file source target\n                   #+(or clasp clozure ecl) :if-exists\n                   #+clozure :rename-and-delete #+(or clasp ecl) t)))\n\n  (defun delete-empty-directory (directory-pathname)\n    \"Delete an empty directory\"\n    #+(or abcl digitool gcl) (delete-file directory-pathname)\n    #+allegro (excl:delete-directory directory-pathname)\n    #+clisp (ext:delete-directory directory-pathname)\n    #+clozure (ccl::delete-empty-directory directory-pathname)\n    #+(or cmucl scl) (multiple-value-bind (ok errno)\n                       (unix:unix-rmdir (native-namestring directory-pathname))\n                     (unless ok\n                       #+cmucl (error \"Error number ~A when trying to delete directory ~A\"\n                                    errno directory-pathname)\n                       #+scl (error \"~@<Error deleting ~S: ~A~@:>\"\n                                    directory-pathname (unix:get-unix-error-msg errno))))\n    #+cormanlisp (win32:delete-directory directory-pathname)\n    #+(or clasp ecl) (si:rmdir directory-pathname)\n    #+genera (fs:delete-directory directory-pathname)\n    #+lispworks (lw:delete-directory directory-pathname)\n    #+mkcl (mkcl:rmdir directory-pathname)\n    #+sbcl #.(if-let (dd (find-symbol* :delete-directory :sb-ext nil))\n               `(,dd directory-pathname) ;; requires SBCL 1.0.44 or later\n               `(progn (require :sb-posix) (symbol-call :sb-posix :rmdir directory-pathname)))\n    #+xcl (symbol-call :uiop :run-program `(\"rmdir\" ,(native-namestring directory-pathname)))\n    #-(or abcl allegro clasp clisp clozure cmucl cormanlisp digitool ecl gcl genera lispworks mkcl sbcl scl xcl)\n    (not-implemented-error 'delete-empty-directory \"(on your platform)\")) ; genera\n\n  (defun delete-directory-tree (directory-pathname &key (validate nil validatep) (if-does-not-exist :error))\n    \"Delete a directory including all its recursive contents, aka rm -rf.\n\nTo reduce the risk of infortunate mistakes, DIRECTORY-PATHNAME must be\na physical non-wildcard directory pathname (not namestring).\n\nIf the directory does not exist, the IF-DOES-NOT-EXIST argument specifies what happens:\nif it is :ERROR (the default), an error is signaled, whereas if it is :IGNORE, nothing is done.\n\nFurthermore, before any deletion is attempted, the DIRECTORY-PATHNAME must pass\nthe validation function designated (as per ENSURE-FUNCTION) by the VALIDATE keyword argument\nwhich in practice is thus compulsory, and validates by returning a non-NIL result.\nIf you're suicidal or extremely confident, just use :VALIDATE T.\"\n    (check-type if-does-not-exist (member :error :ignore))\n    (cond\n      ((not (and (pathnamep directory-pathname) (directory-pathname-p directory-pathname)\n                 (physical-pathname-p directory-pathname) (not (wild-pathname-p directory-pathname))))\n       (parameter-error \"~S was asked to delete ~S but it is not a physical non-wildcard directory pathname\"\n              'delete-directory-tree directory-pathname))\n      ((not validatep)\n       (parameter-error \"~S was asked to delete ~S but was not provided a validation predicate\"\n              'delete-directory-tree directory-pathname))\n      ((not (call-function validate directory-pathname))\n       (parameter-error \"~S was asked to delete ~S but it is not valid ~@[according to ~S~]\"\n              'delete-directory-tree directory-pathname validate))\n      ((not (directory-exists-p directory-pathname))\n       (ecase if-does-not-exist\n         (:error\n          (error \"~S was asked to delete ~S but the directory does not exist\"\n              'delete-directory-tree directory-pathname))\n         (:ignore nil)))\n      #-(or allegro cmucl clozure genera sbcl scl)\n      ((os-unix-p) ;; On Unix, don't recursively walk the directory and delete everything in Lisp,\n       ;; except on implementations where we can prevent DIRECTORY from following symlinks;\n       ;; instead spawn a standard external program to do the dirty work.\n       (symbol-call :uiop :run-program `(\"rm\" \"-rf\" ,(native-namestring directory-pathname))))\n      (t\n       ;; On supported implementation, call supported system functions\n       #+allegro (symbol-call :excl.osi :delete-directory-and-files\n                              directory-pathname :if-does-not-exist if-does-not-exist)\n       #+clozure (ccl:delete-directory directory-pathname)\n       #+genera (fs:delete-directory directory-pathname :confirm nil)\n       #+sbcl #.(if-let (dd (find-symbol* :delete-directory :sb-ext nil))\n                  `(,dd directory-pathname :recursive t) ;; requires SBCL 1.0.44 or later\n                  '(error \"~S requires SBCL 1.0.44 or later\" 'delete-directory-tree))\n       ;; Outside Unix or on CMUCL and SCL that can avoid following symlinks,\n       ;; do things the hard way.\n       #-(or allegro clozure genera sbcl)\n       (let ((sub*directories\n               (while-collecting (c)\n                 (collect-sub*directories directory-pathname t t #'c))))\n             (dolist (d (nreverse sub*directories))\n               (map () 'delete-file (directory-files d))\n               (delete-empty-directory d)))))))\n;;;; ---------------------------------------------------------------------------\n;;;; Utilities related to streams\n\n(uiop/package:define-package :uiop/stream\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os :uiop/pathname :uiop/filesystem)\n  (:export\n   #:*default-stream-element-type*\n   #:*stdin* #:setup-stdin #:*stdout* #:setup-stdout #:*stderr* #:setup-stderr\n   #:detect-encoding #:*encoding-detection-hook* #:always-default-encoding\n   #:encoding-external-format #:*encoding-external-format-hook* #:default-encoding-external-format\n   #:*default-encoding* #:*utf-8-external-format*\n   #:with-safe-io-syntax #:call-with-safe-io-syntax #:safe-read-from-string\n   #:with-output #:output-string #:with-input #:input-string\n   #:with-input-file #:call-with-input-file #:with-output-file #:call-with-output-file\n   #:null-device-pathname #:call-with-null-input #:with-null-input\n   #:call-with-null-output #:with-null-output\n   #:finish-outputs #:format! #:safe-format!\n   #:copy-stream-to-stream #:concatenate-files #:copy-file\n   #:slurp-stream-string #:slurp-stream-lines #:slurp-stream-line\n   #:slurp-stream-forms #:slurp-stream-form\n   #:read-file-string #:read-file-line #:read-file-lines #:safe-read-file-line\n   #:read-file-forms #:read-file-form #:safe-read-file-form\n   #:eval-input #:eval-thunk #:standard-eval-thunk\n   #:println #:writeln\n   #:file-stream-p #:file-or-synonym-stream-p\n   ;; Temporary files\n   #:*temporary-directory* #:temporary-directory #:default-temporary-directory\n   #:setup-temporary-directory\n   #:call-with-temporary-file #:with-temporary-file\n   #:add-pathname-suffix #:tmpize-pathname\n   #:call-with-staging-pathname #:with-staging-pathname))\n(in-package :uiop/stream)\n\n(with-upgradability ()\n  (defvar *default-stream-element-type*\n    (or #+(or abcl cmucl cormanlisp scl xcl) 'character\n        #+lispworks 'lw:simple-char\n        :default)\n    \"default element-type for open (depends on the current CL implementation)\")\n\n  (defvar *stdin* *standard-input*\n    \"the original standard input stream at startup\")\n\n  (defun setup-stdin ()\n    (setf *stdin*\n          #.(or #+clozure 'ccl::*stdin*\n                #+(or cmucl scl) 'system:*stdin*\n                #+(or clasp ecl) 'ext::+process-standard-input+\n                #+sbcl 'sb-sys:*stdin*\n                '*standard-input*)))\n\n  (defvar *stdout* *standard-output*\n    \"the original standard output stream at startup\")\n\n  (defun setup-stdout ()\n    (setf *stdout*\n          #.(or #+clozure 'ccl::*stdout*\n                #+(or cmucl scl) 'system:*stdout*\n                #+(or clasp ecl) 'ext::+process-standard-output+\n                #+sbcl 'sb-sys:*stdout*\n                '*standard-output*)))\n\n  (defvar *stderr* *error-output*\n    \"the original error output stream at startup\")\n\n  (defun setup-stderr ()\n    (setf *stderr*\n          #.(or #+allegro 'excl::*stderr*\n                #+clozure 'ccl::*stderr*\n                #+(or cmucl scl) 'system:*stderr*\n                #+(or clasp ecl) 'ext::+process-error-output+\n                #+sbcl 'sb-sys:*stderr*\n                '*error-output*)))\n\n  ;; Run them now. In image.lisp, we'll register them to be run at image restart.\n  (setup-stdin) (setup-stdout) (setup-stderr))\n\n\n;;; Encodings (mostly hooks only; full support requires asdf-encodings)\n(with-upgradability ()\n  (defparameter *default-encoding*\n    ;; preserve explicit user changes to something other than the legacy default :default\n    (or (if-let (previous (and (boundp '*default-encoding*) (symbol-value '*default-encoding*)))\n          (unless (eq previous :default) previous))\n        :utf-8)\n    \"Default encoding for source files.\nThe default value :utf-8 is the portable thing.\nThe legacy behavior was :default.\nIf you (asdf:load-system :asdf-encodings) then\nyou will have autodetection via *encoding-detection-hook* below,\nreading emacs-style -*- coding: utf-8 -*- specifications,\nand falling back to utf-8 or latin1 if nothing is specified.\")\n\n  (defparameter *utf-8-external-format*\n    (if (featurep :asdf-unicode)\n        (or #+clisp charset:utf-8 :utf-8)\n        :default)\n    \"Default :external-format argument to pass to CL:OPEN and also\nCL:LOAD or CL:COMPILE-FILE to best process a UTF-8 encoded file.\nOn modern implementations, this will decode UTF-8 code points as CL characters.\nOn legacy implementations, it may fall back on some 8-bit encoding,\nwith non-ASCII code points being read as several CL characters;\nhopefully, if done consistently, that won't affect program behavior too much.\")\n\n  (defun always-default-encoding (pathname)\n    \"Trivial function to use as *encoding-detection-hook*,\nalways 'detects' the *default-encoding*\"\n    (declare (ignore pathname))\n    *default-encoding*)\n\n  (defvar *encoding-detection-hook* #'always-default-encoding\n    \"Hook for an extension to define a function to automatically detect a file's encoding\")\n\n  (defun detect-encoding (pathname)\n    \"Detects the encoding of a specified file, going through user-configurable hooks\"\n    (if (and pathname (not (directory-pathname-p pathname)) (probe-file* pathname))\n        (funcall *encoding-detection-hook* pathname)\n        *default-encoding*))\n\n  (defun default-encoding-external-format (encoding)\n    \"Default, ignorant, function to transform a character ENCODING as a\nportable keyword to an implementation-dependent EXTERNAL-FORMAT specification.\nLoad system ASDF-ENCODINGS to hook in a better one.\"\n    (case encoding\n      (:default :default) ;; for backward-compatibility only. Explicit usage discouraged.\n      (:utf-8 *utf-8-external-format*)\n      (otherwise\n       (cerror \"Continue using :external-format :default\" (compatfmt \"~@<Your ASDF component is using encoding ~S but it isn't recognized. Your system should :defsystem-depends-on (:asdf-encodings).~:>\") encoding)\n       :default)))\n\n  (defvar *encoding-external-format-hook*\n    #'default-encoding-external-format\n    \"Hook for an extension (e.g. ASDF-ENCODINGS) to define a better mapping\nfrom non-default encodings to and implementation-defined external-format's\")\n\n  (defun encoding-external-format (encoding)\n    \"Transform a portable ENCODING keyword to an implementation-dependent EXTERNAL-FORMAT,\ngoing through all the proper hooks.\"\n    (funcall *encoding-external-format-hook* (or encoding *default-encoding*))))\n\n\n;;; Safe syntax\n(with-upgradability ()\n  (defvar *standard-readtable* (with-standard-io-syntax *readtable*)\n    \"The standard readtable, implementing the syntax specified by the CLHS.\nIt must never be modified, though only good implementations will even enforce that.\")\n\n  (defmacro with-safe-io-syntax ((&key (package :cl)) &body body)\n    \"Establish safe CL reader options around the evaluation of BODY\"\n    `(call-with-safe-io-syntax #'(lambda () (let ((*package* (find-package ,package))) ,@body))))\n\n  (defun call-with-safe-io-syntax (thunk &key (package :cl))\n    (with-standard-io-syntax\n      (let ((*package* (find-package package))\n            (*read-default-float-format* 'double-float)\n            (*print-readably* nil)\n            (*read-eval* nil))\n        (funcall thunk))))\n\n  (defun safe-read-from-string (string &key (package :cl) (eof-error-p t) eof-value (start 0) end preserve-whitespace)\n    \"Read from STRING using a safe syntax, as per WITH-SAFE-IO-SYNTAX\"\n    (with-safe-io-syntax (:package package)\n      (read-from-string string eof-error-p eof-value :start start :end end :preserve-whitespace preserve-whitespace))))\n\n;;; Output helpers\n(with-upgradability ()\n  (defun call-with-output-file (pathname thunk\n                                &key\n                                  (element-type *default-stream-element-type*)\n                                  (external-format *utf-8-external-format*)\n                                  (if-exists :error)\n                                  (if-does-not-exist :create))\n    \"Open FILE for input with given recognizes options, call THUNK with the resulting stream.\nOther keys are accepted but discarded.\"\n    (with-open-file (s pathname :direction :output\n                                :element-type element-type\n                                :external-format external-format\n                                :if-exists if-exists\n                                :if-does-not-exist if-does-not-exist)\n      (funcall thunk s)))\n\n  (defmacro with-output-file ((var pathname &rest keys\n                               &key element-type external-format if-exists if-does-not-exist)\n                              &body body)\n    (declare (ignore element-type external-format if-exists if-does-not-exist))\n    `(call-with-output-file ,pathname #'(lambda (,var) ,@body) ,@keys))\n\n  (defun call-with-output (output function &key keys)\n    \"Calls FUNCTION with an actual stream argument,\nbehaving like FORMAT with respect to how stream designators are interpreted:\nIf OUTPUT is a STREAM, use it as the stream.\nIf OUTPUT is NIL, use a STRING-OUTPUT-STREAM as the stream, and return the resulting string.\nIf OUTPUT is T, use *STANDARD-OUTPUT* as the stream.\nIf OUTPUT is a STRING with a fill-pointer, use it as a string-output-stream.\nIf OUTPUT is a PATHNAME, open the file and write to it, passing KEYS to WITH-OUTPUT-FILE\n-- this latter as an extension since ASDF 3.1.\nOtherwise, signal an error.\"\n    (etypecase output\n      (null\n       (with-output-to-string (stream) (funcall function stream)))\n      ((eql t)\n       (funcall function *standard-output*))\n      (stream\n       (funcall function output))\n      (string\n       (assert (fill-pointer output))\n       (with-output-to-string (stream output) (funcall function stream)))\n      (pathname\n       (apply 'call-with-output-file output function keys))))\n\n  (defmacro with-output ((output-var &optional (value output-var)) &body body)\n    \"Bind OUTPUT-VAR to an output stream, coercing VALUE (default: previous binding of OUTPUT-VAR)\nas per FORMAT, and evaluate BODY within the scope of this binding.\"\n    `(call-with-output ,value #'(lambda (,output-var) ,@body)))\n\n  (defun output-string (string &optional output)\n    \"If the desired OUTPUT is not NIL, print the string to the output; otherwise return the string\"\n    (if output\n        (with-output (output) (princ string output))\n        string)))\n\n\n;;; Input helpers\n(with-upgradability ()\n  (defun call-with-input-file (pathname thunk\n                               &key\n                                 (element-type *default-stream-element-type*)\n                                 (external-format *utf-8-external-format*)\n                                 (if-does-not-exist :error))\n    \"Open FILE for input with given recognizes options, call THUNK with the resulting stream.\nOther keys are accepted but discarded.\"\n    (with-open-file (s pathname :direction :input\n                                :element-type element-type\n                                :external-format external-format\n                                :if-does-not-exist if-does-not-exist)\n      (funcall thunk s)))\n\n  (defmacro with-input-file ((var pathname &rest keys\n                              &key element-type external-format if-does-not-exist)\n                             &body body)\n    (declare (ignore element-type external-format if-does-not-exist))\n    `(call-with-input-file ,pathname #'(lambda (,var) ,@body) ,@keys))\n\n  (defun call-with-input (input function &key keys)\n    \"Calls FUNCTION with an actual stream argument, interpreting\nstream designators like READ, but also coercing strings to STRING-INPUT-STREAM,\nand PATHNAME to FILE-STREAM.\nIf INPUT is a STREAM, use it as the stream.\nIf INPUT is NIL, use a *STANDARD-INPUT* as the stream.\nIf INPUT is T, use *TERMINAL-IO* as the stream.\nIf INPUT is a STRING, use it as a string-input-stream.\nIf INPUT is a PATHNAME, open it, passing KEYS to WITH-INPUT-FILE\n-- the latter is an extension since ASDF 3.1.\nOtherwise, signal an error.\"\n    (etypecase input\n      (null (funcall function *standard-input*))\n      ((eql t) (funcall function *terminal-io*))\n      (stream (funcall function input))\n      (string (with-input-from-string (stream input) (funcall function stream)))\n      (pathname (apply 'call-with-input-file input function keys))))\n\n  (defmacro with-input ((input-var &optional (value input-var)) &body body)\n    \"Bind INPUT-VAR to an input stream, coercing VALUE (default: previous binding of INPUT-VAR)\nas per CALL-WITH-INPUT, and evaluate BODY within the scope of this binding.\"\n    `(call-with-input ,value #'(lambda (,input-var) ,@body)))\n\n  (defun input-string (&optional input)\n    \"If the desired INPUT is a string, return that string; otherwise slurp the INPUT into a string\nand return that\"\n    (if (stringp input)\n        input\n        (with-input (input) (funcall 'slurp-stream-string input)))))\n\n;;; Null device\n(with-upgradability ()\n  (defun null-device-pathname ()\n    \"Pathname to a bit bucket device that discards any information written to it\nand always returns EOF when read from\"\n    (os-cond\n      ((os-unix-p) #p\"/dev/null\")\n      ((os-windows-p) #p\"NUL\") ;; Q: how many Lisps accept the #p\"NUL:\" syntax?\n      (t (error \"No /dev/null on your OS\"))))\n  (defun call-with-null-input (fun &rest keys &key element-type external-format if-does-not-exist)\n    \"Call FUN with an input stream from the null device; pass keyword arguments to OPEN.\"\n    (declare (ignore element-type external-format if-does-not-exist))\n    (apply 'call-with-input-file (null-device-pathname) fun keys))\n  (defmacro with-null-input ((var &rest keys\n                              &key element-type external-format if-does-not-exist)\n                             &body body)\n    (declare (ignore element-type external-format if-does-not-exist))\n    \"Evaluate BODY in a context when VAR is bound to an input stream accessing the null device.\nPass keyword arguments to OPEN.\"\n    `(call-with-null-input #'(lambda (,var) ,@body) ,@keys))\n  (defun call-with-null-output (fun\n                                &key (element-type *default-stream-element-type*)\n                                  (external-format *utf-8-external-format*)\n                                  (if-exists :overwrite)\n                                  (if-does-not-exist :error))\n    \"Call FUN with an output stream to the null device; pass keyword arguments to OPEN.\"\n    (call-with-output-file\n     (null-device-pathname) fun\n     :element-type element-type :external-format external-format\n     :if-exists if-exists :if-does-not-exist if-does-not-exist))\n  (defmacro with-null-output ((var &rest keys\n                              &key element-type external-format if-does-not-exist if-exists)\n                              &body body)\n    \"Evaluate BODY in a context when VAR is bound to an output stream accessing the null device.\nPass keyword arguments to OPEN.\"\n    (declare (ignore element-type external-format if-exists if-does-not-exist))\n    `(call-with-null-output #'(lambda (,var) ,@body) ,@keys)))\n\n;;; Ensure output buffers are flushed\n(with-upgradability ()\n  (defun finish-outputs (&rest streams)\n    \"Finish output on the main output streams as well as any specified one.\nUseful for portably flushing I/O before user input or program exit.\"\n    ;; CCL notably buffers its stream output by default.\n    (dolist (s (append streams\n                       (list *stdout* *stderr* *error-output* *standard-output* *trace-output*\n                             *debug-io* *terminal-io* *query-io*)))\n      (ignore-errors (finish-output s)))\n    (values))\n\n  (defun format! (stream format &rest args)\n    \"Just like format, but call finish-outputs before and after the output.\"\n    (finish-outputs stream)\n    (apply 'format stream format args)\n    (finish-outputs stream))\n\n  (defun safe-format! (stream format &rest args)\n    \"Variant of FORMAT that is safe against both\ndangerous syntax configuration and errors while printing.\"\n    (with-safe-io-syntax ()\n      (ignore-errors (apply 'format! stream format args))\n      (finish-outputs stream)))) ; just in case format failed\n\n\n;;; Simple Whole-Stream processing\n(with-upgradability ()\n  (defun copy-stream-to-stream (input output &key element-type buffer-size linewise prefix)\n    \"Copy the contents of the INPUT stream into the OUTPUT stream.\nIf LINEWISE is true, then read and copy the stream line by line, with an optional PREFIX.\nOtherwise, using WRITE-SEQUENCE using a buffer of size BUFFER-SIZE.\"\n    (with-open-stream (input input)\n      (if linewise\n          (loop* :for (line eof) = (multiple-value-list (read-line input nil nil))\n                 :while line :do\n                 (when prefix (princ prefix output))\n                 (princ line output)\n                 (unless eof (terpri output))\n                 (finish-output output)\n                 (when eof (return)))\n          (loop\n            :with buffer-size = (or buffer-size 8192)\n            :with buffer = (make-array (list buffer-size) :element-type (or element-type 'character))\n            :for end = (read-sequence buffer input)\n            :until (zerop end)\n            :do (write-sequence buffer output :end end)\n                (when (< end buffer-size) (return))))))\n\n  (defun concatenate-files (inputs output)\n    \"create a new OUTPUT file the contents of which a the concatenate of the INPUTS files.\"\n    (with-open-file (o output :element-type '(unsigned-byte 8)\n                              :direction :output :if-exists :rename-and-delete)\n      (dolist (input inputs)\n        (with-open-file (i input :element-type '(unsigned-byte 8)\n                                 :direction :input :if-does-not-exist :error)\n          (copy-stream-to-stream i o :element-type '(unsigned-byte 8))))))\n\n  (defun copy-file (input output)\n    \"Copy contents of the INPUT file to the OUTPUT file\"\n    ;; Not available on LW personal edition or LW 6.0 on Mac: (lispworks:copy-file i f)\n    #+allegro\n    (excl.osi:copy-file input output)\n    #+ecl\n    (ext:copy-file input output)\n    #-(or allegro ecl)\n    (concatenate-files (list input) output))\n\n  (defun slurp-stream-string (input &key (element-type 'character) stripped)\n    \"Read the contents of the INPUT stream as a string\"\n    (let ((string\n            (with-open-stream (input input)\n              (with-output-to-string (output)\n                (copy-stream-to-stream input output :element-type element-type)))))\n      (if stripped (stripln string) string)))\n\n  (defun slurp-stream-lines (input &key count)\n    \"Read the contents of the INPUT stream as a list of lines, return those lines.\n\nNote: relies on the Lisp's READ-LINE, but additionally removes any remaining CR\nfrom the line-ending if the file or stream had CR+LF but Lisp only removed LF.\n\nRead no more than COUNT lines.\"\n    (check-type count (or null integer))\n    (with-open-stream (input input)\n      (loop :for n :from 0\n            :for l = (and (or (not count) (< n count))\n                          (read-line input nil nil))\n            ;; stripln: to remove CR when the OS sends CRLF and Lisp only remove LF\n            :while l :collect (stripln l))))\n\n  (defun slurp-stream-line (input &key (at 0))\n    \"Read the contents of the INPUT stream as a list of lines,\nthen return the ACCESS-AT of that list of lines using the AT specifier.\nPATH defaults to 0, i.e. return the first line.\nPATH is typically an integer, or a list of an integer and a function.\nIf PATH is NIL, it will return all the lines in the file.\n\nThe stream will not be read beyond the Nth lines,\nwhere N is the index specified by path\nif path is either an integer or a list that starts with an integer.\"\n    (access-at (slurp-stream-lines input :count (access-at-count at)) at))\n\n  (defun slurp-stream-forms (input &key count)\n    \"Read the contents of the INPUT stream as a list of forms,\nand return those forms.\n\nIf COUNT is null, read to the end of the stream;\nif COUNT is an integer, stop after COUNT forms were read.\n\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (check-type count (or null integer))\n    (loop :with eof = '#:eof\n          :for n :from 0\n          :for form = (if (and count (>= n count))\n                          eof\n                          (read-preserving-whitespace input nil eof))\n          :until (eq form eof) :collect form))\n\n  (defun slurp-stream-form (input &key (at 0))\n    \"Read the contents of the INPUT stream as a list of forms,\nthen return the ACCESS-AT of these forms following the AT.\nAT defaults to 0, i.e. return the first form.\nAT is typically a list of integers.\nIf AT is NIL, it will return all the forms in the file.\n\nThe stream will not be read beyond the Nth form,\nwhere N is the index specified by path,\nif path is either an integer or a list that starts with an integer.\n\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (access-at (slurp-stream-forms input :count (access-at-count at)) at))\n\n  (defun read-file-string (file &rest keys)\n    \"Open FILE with option KEYS, read its contents as a string\"\n    (apply 'call-with-input-file file 'slurp-stream-string keys))\n\n  (defun read-file-lines (file &rest keys)\n    \"Open FILE with option KEYS, read its contents as a list of lines\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file 'slurp-stream-lines keys))\n\n  (defun read-file-line (file &rest keys &key (at 0) &allow-other-keys)\n    \"Open input FILE with option KEYS (except AT),\nand read its contents as per SLURP-STREAM-LINE with given AT specifier.\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file\n           #'(lambda (input) (slurp-stream-line input :at at))\n           (remove-plist-key :at keys)))\n\n  (defun read-file-forms (file &rest keys &key count &allow-other-keys)\n    \"Open input FILE with option KEYS (except COUNT),\nand read its contents as per SLURP-STREAM-FORMS with given COUNT.\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file\n           #'(lambda (input) (slurp-stream-forms input :count count))\n           (remove-plist-key :count keys)))\n\n  (defun read-file-form (file &rest keys &key (at 0) &allow-other-keys)\n    \"Open input FILE with option KEYS (except AT),\nand read its contents as per SLURP-STREAM-FORM with given AT specifier.\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file\n           #'(lambda (input) (slurp-stream-form input :at at))\n           (remove-plist-key :at keys)))\n\n  (defun safe-read-file-line (pathname &rest keys &key (package :cl) &allow-other-keys)\n    \"Reads the specified line from the top of a file using a safe standardized syntax.\nExtracts the line using READ-FILE-LINE,\nwithin an WITH-SAFE-IO-SYNTAX using the specified PACKAGE.\"\n    (with-safe-io-syntax (:package package)\n      (apply 'read-file-line pathname (remove-plist-key :package keys))))\n\n  (defun safe-read-file-form (pathname &rest keys &key (package :cl) &allow-other-keys)\n    \"Reads the specified form from the top of a file using a safe standardized syntax.\nExtracts the form using READ-FILE-FORM,\nwithin an WITH-SAFE-IO-SYNTAX using the specified PACKAGE.\"\n    (with-safe-io-syntax (:package package)\n      (apply 'read-file-form pathname (remove-plist-key :package keys))))\n\n  (defun eval-input (input)\n    \"Portably read and evaluate forms from INPUT, return the last values.\"\n    (with-input (input)\n      (loop :with results :with eof ='#:eof\n            :for form = (read input nil eof)\n            :until (eq form eof)\n            :do (setf results (multiple-value-list (eval form)))\n            :finally (return (values-list results)))))\n\n  (defun eval-thunk (thunk)\n    \"Evaluate a THUNK of code:\nIf a function, FUNCALL it without arguments.\nIf a constant literal and not a sequence, return it.\nIf a cons or a symbol, EVAL it.\nIf a string, repeatedly read and evaluate from it, returning the last values.\"\n    (etypecase thunk\n      ((or boolean keyword number character pathname) thunk)\n      ((or cons symbol) (eval thunk))\n      (function (funcall thunk))\n      (string (eval-input thunk))))\n\n  (defun standard-eval-thunk (thunk &key (package :cl))\n    \"Like EVAL-THUNK, but in a more standardized evaluation context.\"\n    ;; Note: it's \"standard-\" not \"safe-\", because evaluation is never safe.\n    (when thunk\n      (with-safe-io-syntax (:package package)\n        (let ((*read-eval* t))\n          (eval-thunk thunk))))))\n\n(with-upgradability ()\n  (defun println (x &optional (stream *standard-output*))\n    \"Variant of PRINC that also calls TERPRI afterwards\"\n    (princ x stream) (terpri stream) (finish-output stream) (values))\n\n  (defun writeln (x &rest keys &key (stream *standard-output*) &allow-other-keys)\n    \"Variant of WRITE that also calls TERPRI afterwards\"\n    (apply 'write x keys) (terpri stream) (finish-output stream) (values)))\n\n\n;;; Using temporary files\n(with-upgradability ()\n  (defun default-temporary-directory ()\n    \"Return a default directory to use for temporary files\"\n    (os-cond\n      ((os-unix-p)\n       (or (getenv-pathname \"TMPDIR\" :ensure-directory t)\n           (parse-native-namestring \"/tmp/\")))\n      ((os-windows-p)\n       (getenv-pathname \"TEMP\" :ensure-directory t))\n      (t (subpathname (user-homedir-pathname) \"tmp/\"))))\n\n  (defvar *temporary-directory* nil \"User-configurable location for temporary files\")\n\n  (defun temporary-directory ()\n    \"Return a directory to use for temporary files\"\n    (or *temporary-directory* (default-temporary-directory)))\n\n  (defun setup-temporary-directory ()\n    \"Configure a default temporary directory to use.\"\n    (setf *temporary-directory* (default-temporary-directory))\n    #+gcl (setf system::*tmp-dir* *temporary-directory*))\n\n  (defun call-with-temporary-file\n      (thunk &key\n               (want-stream-p t) (want-pathname-p t) (direction :io) keep after\n               directory (type \"tmp\" typep) prefix (suffix (when typep \"-tmp\"))\n               (element-type *default-stream-element-type*)\n               (external-format *utf-8-external-format*))\n    \"Call a THUNK with stream and/or pathname arguments identifying a temporary file.\n\nThe temporary file's pathname will be based on concatenating\nPREFIX (or \\\"tmp\\\" if it's NIL), a random alphanumeric string,\nand optional SUFFIX (defaults to \\\"-tmp\\\" if a type was provided)\nand TYPE (defaults to \\\"tmp\\\", using a dot as separator if not NIL),\nwithin DIRECTORY (defaulting to the TEMPORARY-DIRECTORY) if the PREFIX isn't absolute.\n\nThe file will be open with specified DIRECTION (defaults to :IO),\nELEMENT-TYPE (defaults to *DEFAULT-STREAM-ELEMENT-TYPE*) and\nEXTERNAL-FORMAT (defaults to *UTF-8-EXTERNAL-FORMAT*).\nIf WANT-STREAM-P is true (the defaults to T), then THUNK will then be CALL-FUNCTION'ed\nwith the stream and the pathname (if WANT-PATHNAME-P is true, defaults to T),\nand stream will be closed after the THUNK exits (either normally or abnormally).\nIf WANT-STREAM-P is false, then WANT-PATHAME-P must be true, and then\nTHUNK is only CALL-FUNCTION'ed after the stream is closed, with the pathname as argument.\nUpon exit of THUNK, the AFTER thunk if defined is CALL-FUNCTION'ed with the pathname as argument.\nIf AFTER is defined, its results are returned, otherwise, the results of THUNK are returned.\nFinally, the file will be deleted, unless the KEEP argument when CALL-FUNCTION'ed returns true.\"\n    #+xcl (declare (ignorable typep))\n    (check-type direction (member :output :io))\n    (assert (or want-stream-p want-pathname-p))\n    (loop\n      :with prefix-pn = (ensure-absolute-pathname\n                         (or prefix \"tmp\")\n                         (or (ensure-pathname\n                              directory\n                              :namestring :native\n                              :ensure-directory t\n                              :ensure-physical t)\n                             #'temporary-directory))\n      :with prefix-nns = (native-namestring prefix-pn)\n      :with results = (progn (ensure-directories-exist prefix-pn)\n                             ())\n      :for counter :from (random (expt 36 #-gcl 8 #+gcl 5))\n      :for pathname = (parse-native-namestring\n                       (format nil \"~A~36R~@[~A~]~@[.~A~]\"\n                               prefix-nns counter suffix (unless (eq type :unspecific) type)))\n      :for okp = nil :do\n        ;; TODO: on Unix, do something about umask\n        ;; TODO: on Unix, audit the code so we make sure it uses O_CREAT|O_EXCL\n        ;; TODO: on Unix, use CFFI and mkstemp --\n        ;; except UIOP is precisely meant to not depend on CFFI or on anything! Grrrr.\n        ;; Can we at least design some hook?\n        (unwind-protect\n             (progn\n               (ensure-directories-exist pathname)\n               (with-open-file (stream pathname\n                                       :direction direction\n                                       :element-type element-type\n                                       :external-format external-format\n                                       :if-exists nil :if-does-not-exist :create)\n                 (when stream\n                   (setf okp pathname)\n                   (when want-stream-p\n                     ;; Note: can't return directly from within with-open-file\n                     ;; or the non-local return causes the file creation to be undone.\n                     (setf results (multiple-value-list\n                                    (if want-pathname-p\n                                        (funcall thunk stream pathname)\n                                        (funcall thunk stream)))))))\n               (cond\n                 ((not okp) nil)\n                 (after (return (call-function after okp)))\n                 ((and want-pathname-p (not want-stream-p)) (return (call-function thunk okp)))\n                 (t (return (values-list results)))))\n          (when (and okp (not (call-function keep)))\n            (ignore-errors (delete-file-if-exists okp))))))\n\n  (defmacro with-temporary-file ((&key (stream (gensym \"STREAM\") streamp)\n                                    (pathname (gensym \"PATHNAME\") pathnamep)\n                                    directory prefix suffix type\n                                    keep direction element-type external-format)\n                                 &body body)\n    \"Evaluate BODY where the symbols specified by keyword arguments\nSTREAM and PATHNAME (if respectively specified) are bound corresponding\nto a newly created temporary file ready for I/O, as per CALL-WITH-TEMPORARY-FILE.\nAt least one of STREAM or PATHNAME must be specified.\nIf the STREAM is not specified, it will be closed before the BODY is evaluated.\nIf STREAM is specified, then the :CLOSE-STREAM label if it appears in the BODY,\nseparates forms run before and after the stream is closed.\nThe values of the last form of the BODY (not counting the separating :CLOSE-STREAM) are returned.\nUpon success, the KEEP form is evaluated and the file is is deleted unless it evaluates to TRUE.\"\n    (check-type stream symbol)\n    (check-type pathname symbol)\n    (assert (or streamp pathnamep))\n    (let* ((afterp (position :close-stream body))\n           (before (if afterp (subseq body 0 afterp) body))\n           (after (when afterp (subseq body (1+ afterp))))\n           (beforef (gensym \"BEFORE\"))\n           (afterf (gensym \"AFTER\")))\n      `(flet (,@(when before\n                  `((,beforef (,@(when streamp `(,stream)) ,@(when pathnamep `(,pathname)))\n                       ,@(when after `((declare (ignorable ,pathname))))\n                       ,@before)))\n              ,@(when after\n                  (assert pathnamep)\n                  `((,afterf (,pathname) ,@after))))\n         #-gcl (declare (dynamic-extent ,@(when before `(#',beforef)) ,@(when after `(#',afterf))))\n         (call-with-temporary-file\n          ,(when before `#',beforef)\n          :want-stream-p ,streamp\n          :want-pathname-p ,pathnamep\n          ,@(when direction `(:direction ,direction))\n          ,@(when directory `(:directory ,directory))\n          ,@(when prefix `(:prefix ,prefix))\n          ,@(when suffix `(:suffix ,suffix))\n          ,@(when type `(:type ,type))\n          ,@(when keep `(:keep ,keep))\n          ,@(when after `(:after #',afterf))\n          ,@(when element-type `(:element-type ,element-type))\n          ,@(when external-format `(:external-format ,external-format))))))\n\n  (defun get-temporary-file (&key directory prefix suffix type)\n    (with-temporary-file (:pathname pn :keep t\n                          :directory directory :prefix prefix :suffix suffix :type type)\n      pn))\n\n  ;; Temporary pathnames in simple cases where no contention is assumed\n  (defun add-pathname-suffix (pathname suffix &rest keys)\n    \"Add a SUFFIX to the name of a PATHNAME, return a new pathname.\nFurther KEYS can be passed to MAKE-PATHNAME.\"\n    (apply 'make-pathname :name (strcat (pathname-name pathname) suffix)\n                          :defaults pathname keys))\n\n  (defun tmpize-pathname (x)\n    \"Return a new pathname modified from X by adding a trivial random suffix.\nA new empty file with said temporary pathname is created, to ensure there is no\nclash with any concurrent process attempting the same thing.\"\n    (let* ((px (ensure-pathname x :ensure-physical t))\n           (prefix (if-let (n (pathname-name px)) (strcat n \"-tmp\") \"tmp\"))\n           (directory (pathname-directory-pathname px)))\n      (get-temporary-file :directory directory :prefix prefix :type (pathname-type px))))\n\n  (defun call-with-staging-pathname (pathname fun)\n    \"Calls FUN with a staging pathname, and atomically\nrenames the staging pathname to the PATHNAME in the end.\nNB: this protects only against failure of the program, not against concurrent attempts.\nFor the latter case, we ought pick a random suffix and atomically open it.\"\n    (let* ((pathname (pathname pathname))\n           (staging (tmpize-pathname pathname)))\n      (unwind-protect\n           (multiple-value-prog1\n               (funcall fun staging)\n             (rename-file-overwriting-target staging pathname))\n        (delete-file-if-exists staging))))\n\n  (defmacro with-staging-pathname ((pathname-var &optional (pathname-value pathname-var)) &body body)\n    \"Trivial syntax wrapper for CALL-WITH-STAGING-PATHNAME\"\n    `(call-with-staging-pathname ,pathname-value #'(lambda (,pathname-var) ,@body))))\n\n(with-upgradability ()\n  (defun file-stream-p (stream)\n    (typep stream 'file-stream))\n  (defun file-or-synonym-stream-p (stream)\n    (or (file-stream-p stream)\n        (and (typep stream 'synonym-stream)\n             (file-or-synonym-stream-p\n              (symbol-value (synonym-stream-symbol stream)))))))\n;;;; -------------------------------------------------------------------------\n;;;; Starting, Stopping, Dumping a Lisp image\n\n(uiop/package:define-package :uiop/image\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/pathname :uiop/stream :uiop/os)\n  (:export\n   #:*image-dumped-p* #:raw-command-line-arguments #:*command-line-arguments*\n   #:command-line-arguments #:raw-command-line-arguments #:setup-command-line-arguments #:argv0\n   #:*lisp-interaction*\n   #:fatal-condition #:fatal-condition-p\n   #:handle-fatal-condition\n   #:call-with-fatal-condition-handler #:with-fatal-condition-handler\n   #:*image-restore-hook* #:*image-prelude* #:*image-entry-point*\n   #:*image-postlude* #:*image-dump-hook*\n   #:quit #:die #:raw-print-backtrace #:print-backtrace #:print-condition-backtrace\n   #:shell-boolean-exit\n   #:register-image-restore-hook #:register-image-dump-hook\n   #:call-image-restore-hook #:call-image-dump-hook\n   #:restore-image #:dump-image #:create-image\n))\n(in-package :uiop/image)\n\n(with-upgradability ()\n  (defvar *lisp-interaction* t\n    \"Is this an interactive Lisp environment, or is it batch processing?\")\n\n  (defvar *command-line-arguments* nil\n    \"Command-line arguments\")\n\n  (defvar *image-dumped-p* nil ; may matter as to how to get to command-line-arguments\n    \"Is this a dumped image? As a standalone executable?\")\n\n  (defvar *image-restore-hook* nil\n    \"Functions to call (in reverse order) when the image is restored\")\n\n  (defvar *image-restored-p* nil\n    \"Has the image been restored? A boolean, or :in-progress while restoring, :in-regress while dumping\")\n\n  (defvar *image-prelude* nil\n    \"a form to evaluate, or string containing forms to read and evaluate\nwhen the image is restarted, but before the entry point is called.\")\n\n  (defvar *image-entry-point* nil\n    \"a function with which to restart the dumped image when execution is restored from it.\")\n\n  (defvar *image-postlude* nil\n    \"a form to evaluate, or string containing forms to read and evaluate\nbefore the image dump hooks are called and before the image is dumped.\")\n\n  (defvar *image-dump-hook* nil\n    \"Functions to call (in order) when before an image is dumped\")\n\n  (deftype fatal-condition ()\n    `(and serious-condition #+clozure (not ccl:process-reset))))\n\n;;; Exiting properly or im-\n(with-upgradability ()\n  (defun quit (&optional (code 0) (finish-output t))\n    \"Quits from the Lisp world, with the given exit status if provided.\nThis is designed to abstract away the implementation specific quit forms.\"\n    (when finish-output ;; essential, for ClozureCL, and for standard compliance.\n      (finish-outputs))\n    #+(or abcl xcl) (ext:quit :status code)\n    #+allegro (excl:exit code :quiet t)\n    #+(or clasp ecl) (si:quit code)\n    #+clisp (ext:quit code)\n    #+clozure (ccl:quit code)\n    #+cormanlisp (win32:exitprocess code)\n    #+(or cmucl scl) (unix:unix-exit code)\n    #+gcl (system:quit code)\n    #+genera (error \"~S: You probably don't want to Halt Genera. (code: ~S)\" 'quit code)\n    #+lispworks (lispworks:quit :status code :confirm nil :return nil :ignore-errors-p t)\n    #+mcl (progn code (ccl:quit)) ;; or should we use FFI to call libc's exit(3) ?\n    #+mkcl (mk-ext:quit :exit-code code)\n    #+sbcl #.(let ((exit (find-symbol* :exit :sb-ext nil))\n                   (quit (find-symbol* :quit :sb-ext nil)))\n               (cond\n                 (exit `(,exit :code code :abort (not finish-output)))\n                 (quit `(,quit :unix-status code :recklessly-p (not finish-output)))))\n    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl genera lispworks mcl mkcl sbcl scl xcl)\n    (not-implemented-error 'quit \"(called with exit code ~S)\" code))\n\n  (defun die (code format &rest arguments)\n    \"Die in error with some error message\"\n    (with-safe-io-syntax ()\n      (ignore-errors\n       (format! *stderr* \"~&~?~&\" format arguments)))\n    (quit code))\n\n  (defun raw-print-backtrace (&key (stream *debug-io*) count condition)\n    \"Print a backtrace, directly accessing the implementation\"\n    (declare (ignorable stream count condition))\n    #+abcl\n    (loop :for i :from 0\n          :for frame :in (sys:backtrace (or count most-positive-fixnum)) :do\n            (safe-format! stream \"~&~D: ~A~%\" i frame))\n    #+allegro\n    (let ((*terminal-io* stream)\n          (*standard-output* stream)\n          (tpl:*zoom-print-circle* *print-circle*)\n          (tpl:*zoom-print-level* *print-level*)\n          (tpl:*zoom-print-length* *print-length*))\n      (tpl:do-command \"zoom\"\n        :from-read-eval-print-loop nil\n        :count (or count t)\n        :all t))\n    #+(or clasp ecl mkcl)\n    (let* ((top (si:ihs-top))\n           (repeats (if count (min top count) top))\n           (backtrace (loop :for ihs :from 0 :below top\n                            :collect (list (si::ihs-fun ihs)\n                                           (si::ihs-env ihs)))))\n      (loop :for i :from 0 :below repeats\n            :for frame :in (nreverse backtrace) :do\n              (safe-format! stream \"~&~D: ~S~%\" i frame)))\n    #+clisp\n    (system::print-backtrace :out stream :limit count)\n    #+(or clozure mcl)\n    (let ((*debug-io* stream))\n      #+clozure (ccl:print-call-history :count count :start-frame-number 1)\n      #+mcl (ccl:print-call-history :detailed-p nil)\n      (finish-output stream))\n    #+(or cmucl scl)\n    (let ((debug:*debug-print-level* *print-level*)\n          (debug:*debug-print-length* *print-length*))\n      (debug:backtrace (or count most-positive-fixnum) stream))\n    #+gcl\n    (let ((*debug-io* stream))\n      (ignore-errors\n       (with-safe-io-syntax ()\n         (if condition\n             (conditions::condition-backtrace condition)\n             (system::simple-backtrace)))))\n    #+lispworks\n    (let ((dbg::*debugger-stack*\n            (dbg::grab-stack nil :how-many (or count most-positive-fixnum)))\n          (*debug-io* stream)\n          (dbg:*debug-print-level* *print-level*)\n          (dbg:*debug-print-length* *print-length*))\n      (dbg:bug-backtrace nil))\n    #+sbcl\n    (sb-debug:print-backtrace :stream stream :count (or count most-positive-fixnum))\n    #+xcl\n    (loop :for i :from 0 :below (or count most-positive-fixnum)\n          :for frame :in (extensions:backtrace-as-list) :do\n            (safe-format! stream \"~&~D: ~S~%\" i frame)))\n\n  (defun print-backtrace (&rest keys &key stream count condition)\n    \"Print a backtrace\"\n    (declare (ignore stream count condition))\n    (with-safe-io-syntax (:package :cl)\n      (let ((*print-readably* nil)\n            (*print-circle* t)\n            (*print-miser-width* 75)\n            (*print-length* nil)\n            (*print-level* nil)\n            (*print-pretty* t))\n        (ignore-errors (apply 'raw-print-backtrace keys)))))\n\n  (defun print-condition-backtrace (condition &key (stream *stderr*) count)\n    \"Print a condition after a backtrace triggered by that condition\"\n    ;; We print the condition *after* the backtrace,\n    ;; for the sake of who sees the backtrace at a terminal.\n    ;; It is up to the caller to print the condition *before*, with some context.\n    (print-backtrace :stream stream :count count :condition condition)\n    (when condition\n      (safe-format! stream \"~&Above backtrace due to this condition:~%~A~&\"\n                    condition)))\n\n  (defun fatal-condition-p (condition)\n    \"Is the CONDITION fatal?\"\n    (typep condition 'fatal-condition))\n\n  (defun handle-fatal-condition (condition)\n    \"Handle a fatal CONDITION:\ndepending on whether *LISP-INTERACTION* is set, enter debugger or die\"\n    (cond\n      (*lisp-interaction*\n       (invoke-debugger condition))\n      (t\n       (safe-format! *stderr* \"~&Fatal condition:~%~A~%\" condition)\n       (print-condition-backtrace condition :stream *stderr*)\n       (die 99 \"~A\" condition))))\n\n  (defun call-with-fatal-condition-handler (thunk)\n    \"Call THUNK in a context where fatal conditions are appropriately handled\"\n    (handler-bind ((fatal-condition #'handle-fatal-condition))\n      (funcall thunk)))\n\n  (defmacro with-fatal-condition-handler ((&optional) &body body)\n    \"Execute BODY in a context where fatal conditions are appropriately handled\"\n    `(call-with-fatal-condition-handler #'(lambda () ,@body)))\n\n  (defun shell-boolean-exit (x)\n    \"Quit with a return code that is 0 iff argument X is true\"\n    (quit (if x 0 1))))\n\n\n;;; Using image hooks\n(with-upgradability ()\n  (defun register-image-restore-hook (hook &optional (call-now-p t))\n    \"Regiter a hook function to be run when restoring a dumped image\"\n    (register-hook-function '*image-restore-hook* hook call-now-p))\n\n  (defun register-image-dump-hook (hook &optional (call-now-p nil))\n    \"Register a the hook function to be run before to dump an image\"\n    (register-hook-function '*image-dump-hook* hook call-now-p))\n\n  (defun call-image-restore-hook ()\n    \"Call the hook functions registered to be run when restoring a dumped image\"\n    (call-functions (reverse *image-restore-hook*)))\n\n  (defun call-image-dump-hook ()\n    \"Call the hook functions registered to be run before to dump an image\"\n    (call-functions *image-dump-hook*)))\n\n\n;;; Proper command-line arguments\n(with-upgradability ()\n  (defun raw-command-line-arguments ()\n    \"Find what the actual command line for this process was.\"\n    #+abcl ext:*command-line-argument-list* ; Use 1.0.0 or later!\n    #+allegro (sys:command-line-arguments) ; default: :application t\n    #+(or clasp ecl) (loop :for i :from 0 :below (si:argc) :collect (si:argv i))\n    #+clisp (coerce (ext:argv) 'list)\n    #+clozure ccl:*command-line-argument-list*\n    #+(or cmucl scl) extensions:*command-line-strings*\n    #+gcl si:*command-args*\n    #+(or genera mcl) nil\n    #+lispworks sys:*line-arguments-list*\n    #+mkcl (loop :for i :from 0 :below (mkcl:argc) :collect (mkcl:argv i))\n    #+sbcl sb-ext:*posix-argv*\n    #+xcl system:*argv*\n    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl genera lispworks mcl mkcl sbcl scl xcl)\n    (not-implemented-error 'raw-command-line-arguments))\n\n  (defun command-line-arguments (&optional (arguments (raw-command-line-arguments)))\n    \"Extract user arguments from command-line invocation of current process.\nAssume the calling conventions of a generated script that uses --\nif we are not called from a directly executable image.\"\n    (block nil\n      #+abcl (return arguments)\n      ;; SBCL and Allegro already separate user arguments from implementation arguments.\n      #-(or sbcl allegro)\n      (unless (eq *image-dumped-p* :executable)\n        ;; LispWorks command-line processing isn't transparent to the user\n        ;; unless you create a standalone executable; in that case,\n        ;; we rely on cl-launch or some other script to set the arguments for us.\n        #+lispworks (return *command-line-arguments*)\n        ;; On other implementations, on non-standalone executables,\n        ;; we trust cl-launch or whichever script starts the program\n        ;; to use -- as a delimiter between implementation arguments and user arguments.\n        #-lispworks (setf arguments (member \"--\" arguments :test 'string-equal)))\n      (rest arguments)))\n\n  (defun argv0 ()\n    \"On supported implementations (most that matter), or when invoked by a proper wrapper script,\nreturn a string that for the name with which the program was invoked, i.e. argv[0] in C.\nOtherwise, return NIL.\"\n    (cond\n      ((eq *image-dumped-p* :executable) ; yes, this ARGV0 is our argv0 !\n       ;; NB: not currently available on ABCL, Corman, Genera, MCL\n       (or #+(or allegro clisp clozure cmucl gcl lispworks sbcl scl xcl)\n           (first (raw-command-line-arguments))\n           #+(or clasp ecl) (si:argv 0) #+mkcl (mkcl:argv 0)))\n      (t ;; argv[0] is the name of the interpreter.\n       ;; The wrapper script can export __CL_ARGV0. cl-launch does as of 4.0.1.8.\n       (getenvp \"__CL_ARGV0\"))))\n\n  (defun setup-command-line-arguments ()\n    (setf *command-line-arguments* (command-line-arguments)))\n\n  (defun restore-image (&key\n                          (lisp-interaction *lisp-interaction*)\n                          (restore-hook *image-restore-hook*)\n                          (prelude *image-prelude*)\n                          (entry-point *image-entry-point*)\n                          (if-already-restored '(cerror \"RUN RESTORE-IMAGE ANYWAY\")))\n    \"From a freshly restarted Lisp image, restore the saved Lisp environment\nby setting appropriate variables, running various hooks, and calling any specified entry point.\n\nIf the image has already been restored or is already being restored, as per *IMAGE-RESTORED-P*,\ncall the IF-ALREADY-RESTORED error handler (by default, a continuable error), and do return\nimmediately to the surrounding restore process if allowed to continue.\n\nThen, comes the restore process itself:\nFirst, call each function in the RESTORE-HOOK,\nin the order they were registered with REGISTER-IMAGE-RESTORE-HOOK.\nSecond, evaluate the prelude, which is often Lisp text that is read,\nas per EVAL-INPUT.\nThird, call the ENTRY-POINT function, if any is specified, with no argument.\n\nThe restore process happens in a WITH-FATAL-CONDITION-HANDLER, so that if LISP-INTERACTION is NIL,\nany unhandled error leads to a backtrace and an exit with an error status.\nIf LISP-INTERACTION is NIL, the process also exits when no error occurs:\nif neither restart nor entry function is provided, the program will exit with status 0 (success);\nif a function was provided, the program will exit after the function returns (if it returns),\nwith status 0 if and only if the primary return value of result is generalized boolean true,\nand with status 1 if this value is NIL.\n\nIf LISP-INTERACTION is true, unhandled errors will take you to the debugger, and the result\nof the function will be returned rather than interpreted as a boolean designating an exit code.\"\n    (when *image-restored-p*\n      (if if-already-restored\n          (call-function if-already-restored \"Image already ~:[being ~;~]restored\"\n                         (eq *image-restored-p* t))\n          (return-from restore-image)))\n    (with-fatal-condition-handler ()\n      (setf *lisp-interaction* lisp-interaction)\n      (setf *image-restore-hook* restore-hook)\n      (setf *image-prelude* prelude)\n      (setf *image-restored-p* :in-progress)\n      (call-image-restore-hook)\n      (standard-eval-thunk prelude)\n      (setf *image-restored-p* t)\n      (let ((results (multiple-value-list\n                      (if entry-point\n                          (call-function entry-point)\n                          t))))\n        (if lisp-interaction\n            (values-list results)\n            (shell-boolean-exit (first results)))))))\n\n\n;;; Dumping an image\n\n(with-upgradability ()\n  (defun dump-image (filename &key output-name executable\n                                (postlude *image-postlude*)\n                                (dump-hook *image-dump-hook*)\n                                #+clozure prepend-symbols #+clozure (purify t)\n                                #+sbcl compression\n                                #+(and sbcl os-windows) application-type)\n    \"Dump an image of the current Lisp environment at pathname FILENAME, with various options.\n\nFirst, finalize the image, by evaluating the POSTLUDE as per EVAL-INPUT, then calling each of\n the functions in DUMP-HOOK, in reverse order of registration by REGISTER-DUMP-HOOK.\n\nIf EXECUTABLE is true, create an standalone executable program that calls RESTORE-IMAGE on startup.\n\nPass various implementation-defined options, such as PREPEND-SYMBOLS and PURITY on CCL,\nor COMPRESSION on SBCL, and APPLICATION-TYPE on SBCL/Windows.\"\n    ;; Note: at least SBCL saves only global values of variables in the heap image,\n    ;; so make sure things you want to dump are NOT just local bindings shadowing the global values.\n    (declare (ignorable filename output-name executable))\n    (setf *image-dumped-p* (if executable :executable t))\n    (setf *image-restored-p* :in-regress)\n    (setf *image-postlude* postlude)\n    (standard-eval-thunk *image-postlude*)\n    (setf *image-dump-hook* dump-hook)\n    (call-image-dump-hook)\n    (setf *image-restored-p* nil)\n    #-(or clisp clozure (and cmucl executable) lispworks sbcl scl)\n    (when executable\n      (not-implemented-error 'dump-image \"dumping an executable\"))\n    #+allegro\n    (progn\n      (sys:resize-areas :global-gc t :pack-heap t :sift-old-areas t :tenure t) ; :new 5000000\n      (excl:dumplisp :name filename :suppress-allegro-cl-banner t))\n    #+clisp\n    (apply #'ext:saveinitmem filename\n           :quiet t\n           :start-package *package*\n           :keep-global-handlers nil\n           :executable (if executable 0 t) ;--- requires clisp 2.48 or later, still catches --clisp-x\n           (when executable\n             (list\n              ;; :parse-options nil ;--- requires a non-standard patch to clisp.\n              :norc t :script nil :init-function #'restore-image)))\n    #+clozure\n    (flet ((dump (prepend-kernel)\n             (ccl:save-application filename :prepend-kernel prepend-kernel :purify purify\n                                            :toplevel-function (when executable #'restore-image))))\n      ;;(setf ccl::*application* (make-instance 'ccl::lisp-development-system))\n      (if prepend-symbols\n          (with-temporary-file (:prefix \"ccl-symbols-\" :direction :output :pathname path)\n            (require 'elf)\n            (funcall (fdefinition 'ccl::write-elf-symbols-to-file) path)\n            (dump path))\n          (dump t)))\n    #+(or cmucl scl)\n    (progn\n      (ext:gc :full t)\n      (setf ext:*batch-mode* nil)\n      (setf ext::*gc-run-time* 0)\n      (apply 'ext:save-lisp filename\n             :allow-other-keys t ;; hush SCL and old versions of CMUCL\n             #+(and cmucl executable) :executable #+(and cmucl executable) t\n             (when executable '(:init-function restore-image :process-command-line nil\n                                :quiet t :load-init-file nil :site-init nil))))\n    #+gcl\n    (progn\n      (si::set-hole-size 500) (si::gbc nil) (si::sgc-on t)\n      (si::save-system filename))\n    #+lispworks\n    (if executable\n        (lispworks:deliver 'restore-image filename 0 :interface nil)\n        (hcl:save-image filename :environment nil))\n    #+sbcl\n    (progn\n      ;;(sb-pcl::precompile-random-code-segments) ;--- it is ugly slow at compile-time (!) when the initial core is a big CLOS program. If you want it, do it yourself\n      (setf sb-ext::*gc-run-time* 0)\n      (apply 'sb-ext:save-lisp-and-die filename\n             :executable t ;--- always include the runtime that goes with the core\n             (append\n              (when compression (list :compression compression))\n              ;;--- only save runtime-options for standalone executables\n              (when executable (list :toplevel #'restore-image :save-runtime-options t))\n              #+(and sbcl os-windows) ;; passing :application-type :gui will disable the console window.\n              ;; the default is :console - only works with SBCL 1.1.15 or later.\n              (when application-type (list :application-type application-type)))))\n    #-(or allegro clisp clozure cmucl gcl lispworks sbcl scl)\n    (not-implemented-error 'dump-image))\n\n  (defun create-image (destination lisp-object-files\n                       &key kind output-name prologue-code epilogue-code extra-object-files\n                         (prelude () preludep) (postlude () postludep)\n                         (entry-point () entry-point-p) build-args no-uiop)\n    (declare (ignorable destination lisp-object-files extra-object-files kind output-name\n                        prologue-code epilogue-code prelude preludep postlude postludep\n                        entry-point entry-point-p build-args no-uiop))\n    \"On ECL, create an executable at pathname DESTINATION from the specified OBJECT-FILES and options\"\n    ;; Is it meaningful to run these in the current environment?\n    ;; only if we also track the object files that constitute the \"current\" image,\n    ;; and otherwise simulate dump-image, including quitting at the end.\n    #-(or clasp ecl mkcl) (not-implemented-error 'create-image)\n    #+(or clasp ecl mkcl)\n    (let ((epilogue-code\n           (if no-uiop\n               epilogue-code\n               (let ((forms\n                      (append\n                       (when epilogue-code `(,epilogue-code))\n                       (when postludep `((setf *image-postlude* ',postlude)))\n                       (when preludep `((setf *image-prelude* ',prelude)))\n                       (when entry-point-p `((setf *image-entry-point* ',entry-point)))\n                       (case kind\n                         ((:image)\n                          (setf kind :program) ;; to ECL, it's just another program.\n                          `((setf *image-dumped-p* t)\n                            (si::top-level #+(or clasp ecl) t) (quit)))\n                         ((:program)\n                          `((setf *image-dumped-p* :executable)\n                            (shell-boolean-exit\n                             (restore-image))))))))\n                 (when forms `(progn ,@forms))))))\n      #+(or clasp ecl mkcl)\n      (check-type kind (member :dll :shared-library :lib :static-library\n                               :fasl :fasb :program))\n      (apply #+clasp 'cmp:builder #+clasp kind\n             #+(or ecl mkcl)\n             (ecase kind\n               ((:dll :shared-library)\n                #+ecl 'c::build-shared-library #+mkcl 'compiler:build-shared-library)\n               ((:lib :static-library)\n                #+ecl 'c::build-static-library #+mkcl 'compiler:build-static-library)\n               ((:fasl #+ecl :fasb)\n                #+ecl 'c::build-fasl #+mkcl 'compiler:build-fasl)\n               #+mkcl ((:fasb) 'compiler:build-bundle)\n               ((:program)\n                #+ecl 'c::build-program #+mkcl 'compiler:build-program))\n             (pathname destination)\n             #+(or clasp ecl) :lisp-files #+mkcl :lisp-object-files\n             (append lisp-object-files #+(or clasp ecl) extra-object-files)\n             #+ecl :init-name\n             #+ecl (c::compute-init-name (or output-name destination)\n                                         :kind (if (eq kind :fasb) :fasl kind))\n             (append\n              (when prologue-code `(:prologue-code ,prologue-code))\n              (when epilogue-code `(:epilogue-code ,epilogue-code))\n              #+mkcl (when extra-object-files `(:object-files ,extra-object-files))\n              build-args)))))\n\n\n;;; Some universal image restore hooks\n(with-upgradability ()\n  (map () 'register-image-restore-hook\n       '(setup-stdin setup-stdout setup-stderr\n         setup-command-line-arguments setup-temporary-directory\n         #+abcl detect-os)))\n;;;; -------------------------------------------------------------------------\n;;;; Support to build (compile and load) Lisp files\n\n(uiop/package:define-package :uiop/lisp-build\n  (:nicknames :asdf/lisp-build) ;; OBSOLETE, used by slime/contrib/swank-asdf.lisp\n  (:use :uiop/common-lisp :uiop/package :uiop/utility\n   :uiop/os :uiop/pathname :uiop/filesystem :uiop/stream :uiop/image)\n  (:export\n   ;; Variables\n   #:*compile-file-warnings-behaviour* #:*compile-file-failure-behaviour*\n   #:*output-translation-function*\n   #:*optimization-settings* #:*previous-optimization-settings*\n   #:*base-build-directory*\n   #:compile-condition #:compile-file-error #:compile-warned-error #:compile-failed-error\n   #:compile-warned-warning #:compile-failed-warning\n   #:check-lisp-compile-results #:check-lisp-compile-warnings\n   #:*uninteresting-conditions* #:*usual-uninteresting-conditions*\n   #:*uninteresting-compiler-conditions* #:*uninteresting-loader-conditions*\n   ;; Types\n   #+sbcl #:sb-grovel-unknown-constant-condition\n   ;; Functions & Macros\n   #:get-optimization-settings #:proclaim-optimization-settings #:with-optimization-settings\n   #:call-with-muffled-compiler-conditions #:with-muffled-compiler-conditions\n   #:call-with-muffled-loader-conditions #:with-muffled-loader-conditions\n   #:reify-simple-sexp #:unreify-simple-sexp\n   #:reify-deferred-warnings #:unreify-deferred-warnings\n   #:reset-deferred-warnings #:save-deferred-warnings #:check-deferred-warnings\n   #:with-saved-deferred-warnings #:warnings-file-p #:warnings-file-type #:*warnings-file-type*\n   #:enable-deferred-warnings-check #:disable-deferred-warnings-check\n   #:current-lisp-file-pathname #:load-pathname\n   #:lispize-pathname #:compile-file-type #:call-around-hook\n   #:compile-file* #:compile-file-pathname* #:*compile-check*\n   #:load* #:load-from-string #:combine-fasls)\n  (:intern #:defaults #:failure-p #:warnings-p #:s #:y #:body))\n(in-package :uiop/lisp-build)\n\n(with-upgradability ()\n  (defvar *compile-file-warnings-behaviour*\n    (or #+clisp :ignore :warn)\n    \"How should ASDF react if it encounters a warning when compiling a file?\nValid values are :error, :warn, and :ignore.\")\n\n  (defvar *compile-file-failure-behaviour*\n    (or #+(or mkcl sbcl) :error #+clisp :ignore :warn)\n    \"How should ASDF react if it encounters a failure (per the ANSI spec of COMPILE-FILE)\nwhen compiling a file, which includes any non-style-warning warning.\nValid values are :error, :warn, and :ignore.\nNote that ASDF ALWAYS raises an error if it fails to create an output file when compiling.\")\n\n  (defvar *base-build-directory* nil\n    \"When set to a non-null value, it should be an absolute directory pathname,\nwhich will serve as the *DEFAULT-PATHNAME-DEFAULTS* around a COMPILE-FILE,\nwhat more while the input-file is shortened if possible to ENOUGH-PATHNAME relative to it.\nThis can help you produce more deterministic output for FASLs.\"))\n\n;;; Optimization settings\n(with-upgradability ()\n  (defvar *optimization-settings* nil\n    \"Optimization settings to be used by PROCLAIM-OPTIMIZATION-SETTINGS\")\n  (defvar *previous-optimization-settings* nil\n    \"Optimization settings saved by PROCLAIM-OPTIMIZATION-SETTINGS\")\n  (defparameter +optimization-variables+\n    ;; TODO: allegro genera corman mcl\n    (or #+(or abcl xcl) '(system::*speed* system::*space* system::*safety* system::*debug*)\n        #+clisp '() ;; system::*optimize* is a constant hash-table! (with non-constant contents)\n        #+clozure '(ccl::*nx-speed* ccl::*nx-space* ccl::*nx-safety*\n                    ccl::*nx-debug* ccl::*nx-cspeed*)\n        #+(or cmucl scl) '(c::*default-cookie*)\n        #+clasp '()\n        #+ecl (unless (use-ecl-byte-compiler-p) '(c::*speed* c::*space* c::*safety* c::*debug*))\n        #+gcl '(compiler::*speed* compiler::*space* compiler::*compiler-new-safety* compiler::*debug*)\n        #+lispworks '(compiler::*optimization-level*)\n        #+mkcl '(si::*speed* si::*space* si::*safety* si::*debug*)\n        #+sbcl '(sb-c::*policy*)))\n  (defun get-optimization-settings ()\n    \"Get current compiler optimization settings, ready to PROCLAIM again\"\n    #-(or abcl allegro clasp clisp clozure cmucl ecl lispworks mkcl sbcl scl xcl)\n    (warn \"~S does not support ~S. Please help me fix that.\"\n          'get-optimization-settings (implementation-type))\n    #+(or abcl allegro clasp clisp clozure cmucl ecl lispworks mkcl sbcl scl xcl)\n    (let ((settings '(speed space safety debug compilation-speed #+(or cmucl scl) c::brevity)))\n      #.`(loop #+(or allegro clozure)\n               ,@'(:with info = #+allegro (sys:declaration-information 'optimize)\n                   #+clozure (ccl:declaration-information 'optimize nil))\n               :for x :in settings\n               ,@(or #+(or abcl clasp ecl gcl mkcl xcl) '(:for v :in +optimization-variables+))\n               :for y = (or #+(or allegro clozure) (second (assoc x info)) ; normalize order\n                            #+clisp (gethash x system::*optimize* 1)\n                            #+(or abcl clasp ecl mkcl xcl) (symbol-value v)\n                            #+(or cmucl scl) (slot-value c::*default-cookie*\n                                                       (case x (compilation-speed 'c::cspeed)\n                                                             (otherwise x)))\n                            #+lispworks (slot-value compiler::*optimization-level* x)\n                            #+sbcl (sb-c::policy-quality sb-c::*policy* x))\n               :when y :collect (list x y))))\n  (defun proclaim-optimization-settings ()\n    \"Proclaim the optimization settings in *OPTIMIZATION-SETTINGS*\"\n    (proclaim `(optimize ,@*optimization-settings*))\n    (let ((settings (get-optimization-settings)))\n      (unless (equal *previous-optimization-settings* settings)\n        (setf *previous-optimization-settings* settings))))\n  (defmacro with-optimization-settings ((&optional (settings *optimization-settings*)) &body body)\n    #+(or allegro clisp)\n    (let ((previous-settings (gensym \"PREVIOUS-SETTINGS\")))\n      `(let ((,previous-settings (get-optimization-settings)))\n         ,@(when settings `((proclaim `(optimize ,@,settings))))\n         (unwind-protect (progn ,@body)\n           (proclaim `(optimize ,@,previous-settings)))))\n    #-(or allegro clisp)\n    `(let ,(loop :for v :in +optimization-variables+ :collect `(,v ,v))\n       ,@(when settings `((proclaim `(optimize ,@,settings))))\n       ,@body)))\n\n\n;;; Condition control\n(with-upgradability ()\n  #+sbcl\n  (progn\n    (defun sb-grovel-unknown-constant-condition-p (c)\n      \"Detect SB-GROVEL unknown-constant conditions on older versions of SBCL\"\n      (and (typep c 'sb-int:simple-style-warning)\n           (string-enclosed-p\n            \"Couldn't grovel for \"\n            (simple-condition-format-control c)\n            \" (unknown to the C compiler).\")))\n    (deftype sb-grovel-unknown-constant-condition ()\n      '(and style-warning (satisfies sb-grovel-unknown-constant-condition-p))))\n\n  (defvar *usual-uninteresting-conditions*\n    (append\n     ;;#+clozure '(ccl:compiler-warning)\n     #+cmucl '(\"Deleting unreachable code.\")\n     #+lispworks '(\"~S being redefined in ~A (previously in ~A).\"\n                   \"~S defined more than once in ~A.\") ;; lispworks gets confused by eval-when.\n     #+sbcl\n     '(sb-c::simple-compiler-note\n       \"&OPTIONAL and &KEY found in the same lambda list: ~S\"\n       #+sb-eval sb-kernel:lexical-environment-too-complex\n       sb-kernel:undefined-alien-style-warning\n       sb-grovel-unknown-constant-condition ; defined above.\n       sb-ext:implicit-generic-function-warning ;; Controversial.\n       sb-int:package-at-variance\n       sb-kernel:uninteresting-redefinition\n       ;; BEWARE: the below four are controversial to include here.\n       sb-kernel:redefinition-with-defun\n       sb-kernel:redefinition-with-defgeneric\n       sb-kernel:redefinition-with-defmethod\n       sb-kernel::redefinition-with-defmacro) ; not exported by old SBCLs\n     '(\"No generic function ~S present when encountering macroexpansion of defmethod. Assuming it will be an instance of standard-generic-function.\")) ;; from closer2mop\n    \"A suggested value to which to set or bind *uninteresting-conditions*.\")\n\n  (defvar *uninteresting-conditions* '()\n    \"Conditions that may be skipped while compiling or loading Lisp code.\")\n  (defvar *uninteresting-compiler-conditions* '()\n    \"Additional conditions that may be skipped while compiling Lisp code.\")\n  (defvar *uninteresting-loader-conditions*\n    (append\n     '(\"Overwriting already existing readtable ~S.\" ;; from named-readtables\n       #(#:finalizers-off-warning :asdf-finalizers)) ;; from asdf-finalizers\n     #+clisp '(clos::simple-gf-replacing-method-warning))\n    \"Additional conditions that may be skipped while loading Lisp code.\"))\n\n;;;; ----- Filtering conditions while building -----\n(with-upgradability ()\n  (defun call-with-muffled-compiler-conditions (thunk)\n    \"Call given THUNK in a context where uninteresting conditions and compiler conditions are muffled\"\n    (call-with-muffled-conditions\n     thunk (append *uninteresting-conditions* *uninteresting-compiler-conditions*)))\n  (defmacro with-muffled-compiler-conditions ((&optional) &body body)\n    \"Trivial syntax for CALL-WITH-MUFFLED-COMPILER-CONDITIONS\"\n    `(call-with-muffled-compiler-conditions #'(lambda () ,@body)))\n  (defun call-with-muffled-loader-conditions (thunk)\n    \"Call given THUNK in a context where uninteresting conditions and loader conditions are muffled\"\n    (call-with-muffled-conditions\n     thunk (append *uninteresting-conditions* *uninteresting-loader-conditions*)))\n  (defmacro with-muffled-loader-conditions ((&optional) &body body)\n    \"Trivial syntax for CALL-WITH-MUFFLED-LOADER-CONDITIONS\"\n    `(call-with-muffled-loader-conditions #'(lambda () ,@body))))\n\n\n;;;; Handle warnings and failures\n(with-upgradability ()\n  (define-condition compile-condition (condition)\n    ((context-format\n      :initform nil :reader compile-condition-context-format :initarg :context-format)\n     (context-arguments\n      :initform nil :reader compile-condition-context-arguments :initarg :context-arguments)\n     (description\n      :initform nil :reader compile-condition-description :initarg :description))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<~A~@[ while ~?~]~@:>\")\n                       (or (compile-condition-description c) (type-of c))\n                       (compile-condition-context-format c)\n                       (compile-condition-context-arguments c)))))\n  (define-condition compile-file-error (compile-condition error) ())\n  (define-condition compile-warned-warning (compile-condition warning) ())\n  (define-condition compile-warned-error (compile-condition error) ())\n  (define-condition compile-failed-warning (compile-condition warning) ())\n  (define-condition compile-failed-error (compile-condition error) ())\n\n  (defun check-lisp-compile-warnings (warnings-p failure-p\n                                                  &optional context-format context-arguments)\n    \"Given the warnings or failures as resulted from COMPILE-FILE or checking deferred warnings,\nraise an error or warning as appropriate\"\n    (when failure-p\n      (case *compile-file-failure-behaviour*\n        (:warn (warn 'compile-failed-warning\n                     :description \"Lisp compilation failed\"\n                     :context-format context-format\n                     :context-arguments context-arguments))\n        (:error (error 'compile-failed-error\n                       :description \"Lisp compilation failed\"\n                       :context-format context-format\n                       :context-arguments context-arguments))\n        (:ignore nil)))\n    (when warnings-p\n      (case *compile-file-warnings-behaviour*\n        (:warn (warn 'compile-warned-warning\n                     :description \"Lisp compilation had style-warnings\"\n                     :context-format context-format\n                     :context-arguments context-arguments))\n        (:error (error 'compile-warned-error\n                       :description \"Lisp compilation had style-warnings\"\n                       :context-format context-format\n                       :context-arguments context-arguments))\n        (:ignore nil))))\n\n  (defun check-lisp-compile-results (output warnings-p failure-p\n                                             &optional context-format context-arguments)\n    \"Given the results of COMPILE-FILE, raise an error or warning as appropriate\"\n    (unless output\n      (error 'compile-file-error :context-format context-format :context-arguments context-arguments))\n    (check-lisp-compile-warnings warnings-p failure-p context-format context-arguments)))\n\n\n;;;; Deferred-warnings treatment, originally implemented by Douglas Katzman.\n;;;\n;;; To support an implementation, three functions must be implemented:\n;;; reify-deferred-warnings unreify-deferred-warnings reset-deferred-warnings\n;;; See their respective docstrings.\n(with-upgradability ()\n  (defun reify-simple-sexp (sexp)\n    \"Given a simple SEXP, return a representation of it as a portable SEXP.\nSimple means made of symbols, numbers, characters, simple-strings, pathnames, cons cells.\"\n    (etypecase sexp\n      (symbol (reify-symbol sexp))\n      ((or number character simple-string pathname) sexp)\n      (cons (cons (reify-simple-sexp (car sexp)) (reify-simple-sexp (cdr sexp))))\n      (simple-vector (vector (mapcar 'reify-simple-sexp (coerce sexp 'list))))))\n\n  (defun unreify-simple-sexp (sexp)\n    \"Given the portable output of REIFY-SIMPLE-SEXP, return the simple SEXP it represents\"\n    (etypecase sexp\n      ((or symbol number character simple-string pathname) sexp)\n      (cons (cons (unreify-simple-sexp (car sexp)) (unreify-simple-sexp (cdr sexp))))\n      ((simple-vector 2) (unreify-symbol sexp))\n      ((simple-vector 1) (coerce (mapcar 'unreify-simple-sexp (aref sexp 0)) 'vector))))\n\n  #+clozure\n  (progn\n    (defun reify-source-note (source-note)\n      (when source-note\n        (with-accessors ((source ccl::source-note-source) (filename ccl:source-note-filename)\n                         (start-pos ccl:source-note-start-pos) (end-pos ccl:source-note-end-pos)) source-note\n          (declare (ignorable source))\n          (list :filename filename :start-pos start-pos :end-pos end-pos\n                #|:source (reify-source-note source)|#))))\n    (defun unreify-source-note (source-note)\n      (when source-note\n        (destructuring-bind (&key filename start-pos end-pos source) source-note\n          (ccl::make-source-note :filename filename :start-pos start-pos :end-pos end-pos\n                                 :source (unreify-source-note source)))))\n    (defun unsymbolify-function-name (name)\n      (if-let (setfed (gethash name ccl::%setf-function-name-inverses%))\n        `(setf ,setfed)\n        name))\n    (defun symbolify-function-name (name)\n      (if (and (consp name) (eq (first name) 'setf))\n          (let ((setfed (second name)))\n            (gethash setfed ccl::%setf-function-names%))\n          name))\n    (defun reify-function-name (function-name)\n      (let ((name (or (first function-name) ;; defun: extract the name\n                      (let ((sec (second function-name)))\n                        (or (and (atom sec) sec) ; scoped method: drop scope\n                            (first sec)))))) ; method: keep gf name, drop method specializers\n        (list name)))\n    (defun unreify-function-name (function-name)\n      function-name)\n    (defun nullify-non-literals (sexp)\n      (typecase sexp\n        ((or number character simple-string symbol pathname) sexp)\n        (cons (cons (nullify-non-literals (car sexp))\n                    (nullify-non-literals (cdr sexp))))\n        (t nil)))\n    (defun reify-deferred-warning (deferred-warning)\n      (with-accessors ((warning-type ccl::compiler-warning-warning-type)\n                       (args ccl::compiler-warning-args)\n                       (source-note ccl:compiler-warning-source-note)\n                       (function-name ccl:compiler-warning-function-name)) deferred-warning\n        (list :warning-type warning-type :function-name (reify-function-name function-name)\n              :source-note (reify-source-note source-note)\n              :args (destructuring-bind (fun &rest more)\n                        args\n                      (cons (unsymbolify-function-name fun)\n                            (nullify-non-literals more))))))\n    (defun unreify-deferred-warning (reified-deferred-warning)\n      (destructuring-bind (&key warning-type function-name source-note args)\n          reified-deferred-warning\n        (make-condition (or (cdr (ccl::assq warning-type ccl::*compiler-whining-conditions*))\n                            'ccl::compiler-warning)\n                        :function-name (unreify-function-name function-name)\n                        :source-note (unreify-source-note source-note)\n                        :warning-type warning-type\n                        :args (destructuring-bind (fun . more) args\n                                (cons (symbolify-function-name fun) more))))))\n  #+(or cmucl scl)\n  (defun reify-undefined-warning (warning)\n    ;; Extracting undefined-warnings from the compilation-unit\n    ;; To be passed through the above reify/unreify link, it must be a \"simple-sexp\"\n    (list*\n     (c::undefined-warning-kind warning)\n     (c::undefined-warning-name warning)\n     (c::undefined-warning-count warning)\n     (mapcar\n      #'(lambda (frob)\n          ;; the lexenv slot can be ignored for reporting purposes\n          `(:enclosing-source ,(c::compiler-error-context-enclosing-source frob)\n            :source ,(c::compiler-error-context-source frob)\n            :original-source ,(c::compiler-error-context-original-source frob)\n            :context ,(c::compiler-error-context-context frob)\n            :file-name ,(c::compiler-error-context-file-name frob) ; a pathname\n            :file-position ,(c::compiler-error-context-file-position frob) ; an integer\n            :original-source-path ,(c::compiler-error-context-original-source-path frob)))\n      (c::undefined-warning-warnings warning))))\n\n  #+sbcl\n  (defun reify-undefined-warning (warning)\n    ;; Extracting undefined-warnings from the compilation-unit\n    ;; To be passed through the above reify/unreify link, it must be a \"simple-sexp\"\n    (list*\n     (sb-c::undefined-warning-kind warning)\n     (sb-c::undefined-warning-name warning)\n     (sb-c::undefined-warning-count warning)\n     (mapcar\n      #'(lambda (frob)\n          ;; the lexenv slot can be ignored for reporting purposes\n          `(:enclosing-source ,(sb-c::compiler-error-context-enclosing-source frob)\n            :source ,(sb-c::compiler-error-context-source frob)\n            :original-source ,(sb-c::compiler-error-context-original-source frob)\n            :context ,(sb-c::compiler-error-context-context frob)\n            :file-name ,(sb-c::compiler-error-context-file-name frob) ; a pathname\n            :file-position ,(sb-c::compiler-error-context-file-position frob) ; an integer\n            :original-source-path ,(sb-c::compiler-error-context-original-source-path frob)))\n      (sb-c::undefined-warning-warnings warning))))\n\n  (defun reify-deferred-warnings ()\n    \"return a portable S-expression, portably readable and writeable in any Common Lisp implementation\nusing READ within a WITH-SAFE-IO-SYNTAX, that represents the warnings currently deferred by\nWITH-COMPILATION-UNIT. One of three functions required for deferred-warnings support in ASDF.\"\n    #+allegro\n    (list :functions-defined excl::.functions-defined.\n          :functions-called excl::.functions-called.)\n    #+clozure\n    (mapcar 'reify-deferred-warning\n            (if-let (dw ccl::*outstanding-deferred-warnings*)\n              (let ((mdw (ccl::ensure-merged-deferred-warnings dw)))\n                (ccl::deferred-warnings.warnings mdw))))\n    #+(or cmucl scl)\n    (when lisp::*in-compilation-unit*\n      ;; Try to send nothing through the pipe if nothing needs to be accumulated\n      `(,@(when c::*undefined-warnings*\n            `((c::*undefined-warnings*\n               ,@(mapcar #'reify-undefined-warning c::*undefined-warnings*))))\n        ,@(loop :for what :in '(c::*compiler-error-count*\n                                c::*compiler-warning-count*\n                                c::*compiler-note-count*)\n                :for value = (symbol-value what)\n                :when (plusp value)\n                  :collect `(,what . ,value))))\n    #+sbcl\n    (when sb-c::*in-compilation-unit*\n      ;; Try to send nothing through the pipe if nothing needs to be accumulated\n      `(,@(when sb-c::*undefined-warnings*\n            `((sb-c::*undefined-warnings*\n               ,@(mapcar #'reify-undefined-warning sb-c::*undefined-warnings*))))\n        ,@(loop :for what :in '(sb-c::*aborted-compilation-unit-count*\n                                sb-c::*compiler-error-count*\n                                sb-c::*compiler-warning-count*\n                                sb-c::*compiler-style-warning-count*\n                                sb-c::*compiler-note-count*)\n                :for value = (symbol-value what)\n                :when (plusp value)\n                  :collect `(,what . ,value)))))\n\n  (defun unreify-deferred-warnings (reified-deferred-warnings)\n    \"given a S-expression created by REIFY-DEFERRED-WARNINGS, reinstantiate the corresponding\ndeferred warnings as to be handled at the end of the current WITH-COMPILATION-UNIT.\nHandle any warning that has been resolved already,\nsuch as an undefined function that has been defined since.\nOne of three functions required for deferred-warnings support in ASDF.\"\n    (declare (ignorable reified-deferred-warnings))\n    #+allegro\n    (destructuring-bind (&key functions-defined functions-called)\n        reified-deferred-warnings\n      (setf excl::.functions-defined.\n            (append functions-defined excl::.functions-defined.)\n            excl::.functions-called.\n            (append functions-called excl::.functions-called.)))\n    #+clozure\n    (let ((dw (or ccl::*outstanding-deferred-warnings*\n                  (setf ccl::*outstanding-deferred-warnings* (ccl::%defer-warnings t)))))\n      (appendf (ccl::deferred-warnings.warnings dw)\n               (mapcar 'unreify-deferred-warning reified-deferred-warnings)))\n    #+(or cmucl scl)\n    (dolist (item reified-deferred-warnings)\n      ;; Each item is (symbol . adjustment) where the adjustment depends on the symbol.\n      ;; For *undefined-warnings*, the adjustment is a list of initargs.\n      ;; For everything else, it's an integer.\n      (destructuring-bind (symbol . adjustment) item\n        (case symbol\n          ((c::*undefined-warnings*)\n           (setf c::*undefined-warnings*\n                 (nconc (mapcan\n                         #'(lambda (stuff)\n                             (destructuring-bind (kind name count . rest) stuff\n                               (unless (case kind (:function (fboundp name)))\n                                 (list\n                                  (c::make-undefined-warning\n                                   :name name\n                                   :kind kind\n                                   :count count\n                                   :warnings\n                                   (mapcar #'(lambda (x)\n                                               (apply #'c::make-compiler-error-context x))\n                                           rest))))))\n                         adjustment)\n                        c::*undefined-warnings*)))\n          (otherwise\n           (set symbol (+ (symbol-value symbol) adjustment))))))\n    #+sbcl\n    (dolist (item reified-deferred-warnings)\n      ;; Each item is (symbol . adjustment) where the adjustment depends on the symbol.\n      ;; For *undefined-warnings*, the adjustment is a list of initargs.\n      ;; For everything else, it's an integer.\n      (destructuring-bind (symbol . adjustment) item\n        (case symbol\n          ((sb-c::*undefined-warnings*)\n           (setf sb-c::*undefined-warnings*\n                 (nconc (mapcan\n                         #'(lambda (stuff)\n                             (destructuring-bind (kind name count . rest) stuff\n                               (unless (case kind (:function (fboundp name)))\n                                 (list\n                                  (sb-c::make-undefined-warning\n                                   :name name\n                                   :kind kind\n                                   :count count\n                                   :warnings\n                                   (mapcar #'(lambda (x)\n                                               (apply #'sb-c::make-compiler-error-context x))\n                                           rest))))))\n                         adjustment)\n                        sb-c::*undefined-warnings*)))\n          (otherwise\n           (set symbol (+ (symbol-value symbol) adjustment)))))))\n\n  (defun reset-deferred-warnings ()\n    \"Reset the set of deferred warnings to be handled at the end of the current WITH-COMPILATION-UNIT.\nOne of three functions required for deferred-warnings support in ASDF.\"\n    #+allegro\n    (setf excl::.functions-defined. nil\n          excl::.functions-called. nil)\n    #+clozure\n    (if-let (dw ccl::*outstanding-deferred-warnings*)\n      (let ((mdw (ccl::ensure-merged-deferred-warnings dw)))\n        (setf (ccl::deferred-warnings.warnings mdw) nil)))\n    #+(or cmucl scl)\n    (when lisp::*in-compilation-unit*\n      (setf c::*undefined-warnings* nil\n            c::*compiler-error-count* 0\n            c::*compiler-warning-count* 0\n            c::*compiler-note-count* 0))\n    #+sbcl\n    (when sb-c::*in-compilation-unit*\n      (setf sb-c::*undefined-warnings* nil\n            sb-c::*aborted-compilation-unit-count* 0\n            sb-c::*compiler-error-count* 0\n            sb-c::*compiler-warning-count* 0\n            sb-c::*compiler-style-warning-count* 0\n            sb-c::*compiler-note-count* 0)))\n\n  (defun save-deferred-warnings (warnings-file)\n    \"Save forward reference conditions so they may be issued at a latter time,\npossibly in a different process.\"\n    (with-open-file (s warnings-file :direction :output :if-exists :supersede\n                       :element-type *default-stream-element-type*\n                       :external-format *utf-8-external-format*)\n      (with-safe-io-syntax ()\n        (write (reify-deferred-warnings) :stream s :pretty t :readably t)\n        (terpri s))))\n\n  (defun warnings-file-type (&optional implementation-type)\n    \"The pathname type for warnings files on given IMPLEMENTATION-TYPE,\nwhere NIL designates the current one\"\n    (case (or implementation-type *implementation-type*)\n      ((:acl :allegro) \"allegro-warnings\")\n      ;;((:clisp) \"clisp-warnings\")\n      ((:cmu :cmucl) \"cmucl-warnings\")\n      ((:sbcl) \"sbcl-warnings\")\n      ((:clozure :ccl) \"ccl-warnings\")\n      ((:scl) \"scl-warnings\")))\n\n  (defvar *warnings-file-type* nil\n    \"Pathname type for warnings files, or NIL if disabled\")\n\n  (defun enable-deferred-warnings-check ()\n    \"Enable the saving of deferred warnings\"\n    (setf *warnings-file-type* (warnings-file-type)))\n\n  (defun disable-deferred-warnings-check ()\n    \"Disable the saving of deferred warnings\"\n    (setf *warnings-file-type* nil))\n\n  (defun warnings-file-p (file &optional implementation-type)\n    \"Is FILE a saved warnings file for the given IMPLEMENTATION-TYPE?\nIf that given type is NIL, use the currently configured *WARNINGS-FILE-TYPE* instead.\"\n    (if-let (type (if implementation-type\n                      (warnings-file-type implementation-type)\n                      *warnings-file-type*))\n      (equal (pathname-type file) type)))\n\n  (defun check-deferred-warnings (files &optional context-format context-arguments)\n    \"Given a list of FILES containing deferred warnings saved by CALL-WITH-SAVED-DEFERRED-WARNINGS,\nre-intern and raise any warnings that are still meaningful.\"\n    (let ((file-errors nil)\n          (failure-p nil)\n          (warnings-p nil))\n      (handler-bind\n          ((warning #'(lambda (c)\n                        (setf warnings-p t)\n                        (unless (typep c 'style-warning)\n                          (setf failure-p t)))))\n        (with-compilation-unit (:override t)\n          (reset-deferred-warnings)\n          (dolist (file files)\n            (unreify-deferred-warnings\n             (handler-case (safe-read-file-form file)\n               (error (c)\n                 ;;(delete-file-if-exists file) ;; deleting forces rebuild but prevents debugging\n                 (push c file-errors)\n                 nil))))))\n      (dolist (error file-errors) (error error))\n      (check-lisp-compile-warnings\n       (or failure-p warnings-p) failure-p context-format context-arguments)))\n\n  #|\n  Mini-guide to adding support for deferred warnings on an implementation.\n\n  First, look at what such a warning looks like:\n\n  (describe\n  (handler-case\n  (and (eval '(lambda () (some-undefined-function))) nil)\n  (t (c) c)))\n\n  Then you can grep for the condition type in your compiler sources\n  and see how to catch those that have been deferred,\n  and/or read, clear and restore the deferred list.\n\n  Also look at\n  (macroexpand-1 '(with-compilation-unit () foo))\n  |#\n\n  (defun call-with-saved-deferred-warnings (thunk warnings-file &key source-namestring)\n    \"If WARNINGS-FILE is not nil, record the deferred-warnings around a call to THUNK\nand save those warnings to the given file for latter use,\npossibly in a different process. Otherwise just call THUNK.\"\n    (declare (ignorable source-namestring))\n    (if warnings-file\n        (with-compilation-unit (:override t #+sbcl :source-namestring #+sbcl source-namestring)\n          (unwind-protect\n               (let (#+sbcl (sb-c::*undefined-warnings* nil))\n                 (multiple-value-prog1\n                     (funcall thunk)\n                   (save-deferred-warnings warnings-file)))\n            (reset-deferred-warnings)))\n        (funcall thunk)))\n\n  (defmacro with-saved-deferred-warnings ((warnings-file &key source-namestring) &body body)\n    \"Trivial syntax for CALL-WITH-SAVED-DEFERRED-WARNINGS\"\n    `(call-with-saved-deferred-warnings\n      #'(lambda () ,@body) ,warnings-file :source-namestring ,source-namestring)))\n\n\n;;; from ASDF\n(with-upgradability ()\n  (defun current-lisp-file-pathname ()\n    \"Portably return the PATHNAME of the current Lisp source file being compiled or loaded\"\n    (or *compile-file-pathname* *load-pathname*))\n\n  (defun load-pathname ()\n    \"Portably return the LOAD-PATHNAME of the current source file or fasl\"\n    *load-pathname*) ;; magic no longer needed for GCL.\n\n  (defun lispize-pathname (input-file)\n    \"From a INPUT-FILE pathname, return a corresponding .lisp source pathname\"\n    (make-pathname :type \"lisp\" :defaults input-file))\n\n  (defun compile-file-type (&rest keys)\n    \"pathname TYPE for lisp FASt Loading files\"\n    (declare (ignorable keys))\n    #-(or clasp ecl mkcl) (load-time-value (pathname-type (compile-file-pathname \"foo.lisp\")))\n    #+(or clasp ecl mkcl) (pathname-type (apply 'compile-file-pathname \"foo\" keys)))\n\n  (defun call-around-hook (hook function)\n    \"Call a HOOK around the execution of FUNCTION\"\n    (call-function (or hook 'funcall) function))\n\n  (defun compile-file-pathname* (input-file &rest keys &key output-file &allow-other-keys)\n    \"Variant of COMPILE-FILE-PATHNAME that works well with COMPILE-FILE*\"\n    (let* ((keys\n             (remove-plist-keys `(#+(or (and allegro (not (version>= 8 2)))) :external-format\n                                    ,@(unless output-file '(:output-file))) keys)))\n      (if (absolute-pathname-p output-file)\n          ;; what cfp should be doing, w/ mp* instead of mp\n          (let* ((type (pathname-type (apply 'compile-file-type keys)))\n                 (defaults (make-pathname\n                            :type type :defaults (merge-pathnames* input-file))))\n            (merge-pathnames* output-file defaults))\n          (funcall *output-translation-function*\n                   (apply 'compile-file-pathname input-file keys)))))\n\n  (defvar *compile-check* nil\n    \"A hook for user-defined compile-time invariants\")\n\n  (defun* (compile-file*) (input-file &rest keys\n                                      &key (compile-check *compile-check*) output-file warnings-file\n                                      #+clisp lib-file #+(or clasp ecl mkcl) object-file #+sbcl emit-cfasl\n                                      &allow-other-keys)\n    \"This function provides a portable wrapper around COMPILE-FILE.\nIt ensures that the OUTPUT-FILE value is only returned and\nthe file only actually created if the compilation was successful,\neven though your implementation may not do that. It also checks an optional\nuser-provided consistency function COMPILE-CHECK to determine success;\nit will call this function if not NIL at the end of the compilation\nwith the arguments sent to COMPILE-FILE*, except with :OUTPUT-FILE TMP-FILE\nwhere TMP-FILE is the name of a temporary output-file.\nIt also checks two flags (with legacy british spelling from ASDF1),\n*COMPILE-FILE-FAILURE-BEHAVIOUR* and *COMPILE-FILE-WARNINGS-BEHAVIOUR*\nwith appropriate implementation-dependent defaults,\nand if a failure (respectively warnings) are reported by COMPILE-FILE,\nit will consider that an error unless the respective behaviour flag\nis one of :SUCCESS :WARN :IGNORE.\nIf WARNINGS-FILE is defined, deferred warnings are saved to that file.\nOn ECL or MKCL, it creates both the linkable object and loadable fasl files.\nOn implementations that erroneously do not recognize standard keyword arguments,\nit will filter them appropriately.\"\n    #+(or clasp ecl)\n    (when (and object-file (equal (compile-file-type) (pathname object-file)))\n      (format t \"Whoa, some funky ASDF upgrade switched ~S calling convention for ~S and ~S~%\"\n              'compile-file* output-file object-file)\n      (rotatef output-file object-file))\n    (let* ((keywords (remove-plist-keys\n                      `(:output-file :compile-check :warnings-file\n                                     #+clisp :lib-file #+(or clasp ecl mkcl) :object-file) keys))\n           (output-file\n             (or output-file\n                 (apply 'compile-file-pathname* input-file :output-file output-file keywords)))\n           (physical-output-file (physicalize-pathname output-file))\n           #+(or clasp ecl)\n           (object-file\n             (unless (use-ecl-byte-compiler-p)\n               (or object-file\n                   #+ecl (compile-file-pathname output-file :type :object)\n                   #+clasp (compile-file-pathname output-file :output-type :object))))\n           #+mkcl\n           (object-file\n             (or object-file\n                 (compile-file-pathname output-file :fasl-p nil)))\n           (tmp-file (tmpize-pathname physical-output-file))\n           #+sbcl\n           (cfasl-file (etypecase emit-cfasl\n                         (null nil)\n                         ((eql t) (make-pathname :type \"cfasl\" :defaults physical-output-file))\n                         (string (parse-namestring emit-cfasl))\n                         (pathname emit-cfasl)))\n           #+sbcl\n           (tmp-cfasl (when cfasl-file (make-pathname :type \"cfasl\" :defaults tmp-file)))\n           #+clisp\n           (tmp-lib (make-pathname :type \"lib\" :defaults tmp-file)))\n      (multiple-value-bind (output-truename warnings-p failure-p)\n          (with-enough-pathname (input-file :defaults *base-build-directory*)\n            (with-saved-deferred-warnings (warnings-file :source-namestring (namestring input-file))\n              (with-muffled-compiler-conditions ()\n                (or #-(or clasp ecl mkcl)\n                    (apply 'compile-file input-file :output-file tmp-file\n                           #+sbcl (if emit-cfasl (list* :emit-cfasl tmp-cfasl keywords) keywords)\n                           #-sbcl keywords)\n                    #+ecl (apply 'compile-file input-file :output-file\n                                (if object-file\n                                    (list* object-file :system-p t keywords)\n                                    (list* tmp-file keywords)))\n                    #+clasp (apply 'compile-file input-file :output-file\n                                  (if object-file\n                                      (list* object-file :output-type :object #|:system-p t|# keywords)\n                                      (list* tmp-file keywords)))\n                    #+mkcl (apply 'compile-file input-file\n                                  :output-file object-file :fasl-p nil keywords)))))\n        (cond\n          ((and output-truename\n                (flet ((check-flag (flag behaviour)\n                         (or (not flag) (member behaviour '(:success :warn :ignore)))))\n                  (and (check-flag failure-p *compile-file-failure-behaviour*)\n                       (check-flag warnings-p *compile-file-warnings-behaviour*)))\n                (progn\n                  #+(or clasp ecl mkcl)\n                  (when (and #+(or clasp ecl) object-file)\n                    (setf output-truename\n                          (compiler::build-fasl tmp-file\n                           #+(or clasp ecl) :lisp-files #+mkcl :lisp-object-files (list object-file))))\n                  (or (not compile-check)\n                      (apply compile-check input-file\n                             :output-file output-truename\n                             keywords))))\n           (delete-file-if-exists physical-output-file)\n           (when output-truename\n             #+clasp (when output-truename (rename-file-overwriting-target tmp-file output-truename))\n             ;; see CLISP bug 677\n             #+clisp\n             (progn\n               (setf tmp-lib (make-pathname :type \"lib\" :defaults output-truename))\n               (unless lib-file (setf lib-file (make-pathname :type \"lib\" :defaults physical-output-file)))\n               (rename-file-overwriting-target tmp-lib lib-file))\n             #+sbcl (when cfasl-file (rename-file-overwriting-target tmp-cfasl cfasl-file))\n             (rename-file-overwriting-target output-truename physical-output-file)\n             (setf output-truename (truename physical-output-file)))\n           #+clasp (delete-file-if-exists tmp-file)\n           #+clisp (progn (delete-file-if-exists tmp-file) ;; this one works around clisp BUG 677\n                          (delete-file-if-exists tmp-lib))) ;; this one is \"normal\" defensive cleanup\n          (t ;; error or failed check\n           (delete-file-if-exists output-truename)\n           #+clisp (delete-file-if-exists tmp-lib)\n           #+sbcl (delete-file-if-exists tmp-cfasl)\n           (setf output-truename nil)))\n        (values output-truename warnings-p failure-p))))\n\n  (defun load* (x &rest keys &key &allow-other-keys)\n    \"Portable wrapper around LOAD that properly handles loading from a stream.\"\n    (with-muffled-loader-conditions ()\n      (etypecase x\n        ((or pathname string #-(or allegro clozure genera) stream #+clozure file-stream)\n         (apply 'load x keys))\n        ;; Genera can't load from a string-input-stream\n        ;; ClozureCL 1.6 can only load from file input stream\n        ;; Allegro 5, I don't remember but it must have been broken when I tested.\n        #+(or allegro clozure genera)\n        (stream ;; make do this way\n         (let ((*package* *package*)\n               (*readtable* *readtable*)\n               (*load-pathname* nil)\n               (*load-truename* nil))\n           (eval-input x))))))\n\n  (defun load-from-string (string)\n    \"Portably read and evaluate forms from a STRING.\"\n    (with-input-from-string (s string) (load* s))))\n\n;;; Links FASLs together\n(with-upgradability ()\n  (defun combine-fasls (inputs output)\n    \"Combine a list of FASLs INPUTS into a single FASL OUTPUT\"\n    #-(or abcl allegro clisp clozure cmucl lispworks sbcl scl xcl)\n    (not-implemented-error 'combine-fasls \"~%inputs: ~S~%output: ~S\" inputs output)\n    #+abcl (funcall 'sys::concatenate-fasls inputs output) ; requires ABCL 1.2.0\n    #+(or allegro clisp cmucl sbcl scl xcl) (concatenate-files inputs output)\n    #+clozure (ccl:fasl-concatenate output inputs :if-exists :supersede)\n    #+lispworks\n    (let (fasls)\n      (unwind-protect\n           (progn\n             (loop :for i :in inputs\n                   :for n :from 1\n                   :for f = (add-pathname-suffix\n                             output (format nil \"-FASL~D\" n))\n                   :do (copy-file i f)\n                       (push f fasls))\n             (ignore-errors (lispworks:delete-system :fasls-to-concatenate))\n             (eval `(scm:defsystem :fasls-to-concatenate\n                      (:default-pathname ,(pathname-directory-pathname output))\n                      :members\n                      ,(loop :for f :in (reverse fasls)\n                             :collect `(,(namestring f) :load-only t))))\n             (scm:concatenate-system output :fasls-to-concatenate :force t))\n        (loop :for f :in fasls :do (ignore-errors (delete-file f)))\n        (ignore-errors (lispworks:delete-system :fasls-to-concatenate))))))\n;;;; -------------------------------------------------------------------------\n;;;; launch-program - semi-portably spawn asynchronous subprocesses\n\n(uiop/package:define-package :uiop/launch-program\n  (:use :uiop/common-lisp :uiop/package :uiop/utility\n   :uiop/pathname :uiop/os :uiop/filesystem :uiop/stream)\n  (:export\n   ;;; Escaping the command invocation madness\n   #:easy-sh-character-p #:escape-sh-token #:escape-sh-command\n   #:escape-windows-token #:escape-windows-command\n   #:escape-shell-token #:escape-shell-command\n   #:escape-token #:escape-command\n\n   ;;; launch-program\n   #:launch-program\n   #:close-streams #:process-alive-p #:terminate-process #:wait-process\n   #:process-info-error-output #:process-info-input #:process-info-output #:process-info-pid))\n(in-package :uiop/launch-program)\n\n;;;; ----- Escaping strings for the shell -----\n(with-upgradability ()\n  (defun requires-escaping-p (token &key good-chars bad-chars)\n    \"Does this token require escaping, given the specification of\neither good chars that don't need escaping or bad chars that do need escaping,\nas either a recognizing function or a sequence of characters.\"\n    (some\n     (cond\n       ((and good-chars bad-chars)\n        (parameter-error \"~S: only one of good-chars and bad-chars can be provided\"\n                         'requires-escaping-p))\n       ((typep good-chars 'function)\n        (complement good-chars))\n       ((typep bad-chars 'function)\n        bad-chars)\n       ((and good-chars (typep good-chars 'sequence))\n        #'(lambda (c) (not (find c good-chars))))\n       ((and bad-chars (typep bad-chars 'sequence))\n        #'(lambda (c) (find c bad-chars)))\n       (t (parameter-error \"~S: no good-char criterion\" 'requires-escaping-p)))\n     token))\n\n  (defun escape-token (token &key stream quote good-chars bad-chars escaper)\n    \"Call the ESCAPER function on TOKEN string if it needs escaping as per\nREQUIRES-ESCAPING-P using GOOD-CHARS and BAD-CHARS, otherwise output TOKEN,\nusing STREAM as output (or returning result as a string if NIL)\"\n    (if (requires-escaping-p token :good-chars good-chars :bad-chars bad-chars)\n        (with-output (stream)\n          (apply escaper token stream (when quote `(:quote ,quote))))\n        (output-string token stream)))\n\n  (defun escape-windows-token-within-double-quotes (x &optional s)\n    \"Escape a string token X within double-quotes\nfor use within a MS Windows command-line, outputing to S.\"\n    (labels ((issue (c) (princ c s))\n             (issue-backslash (n) (loop :repeat n :do (issue #\\\\))))\n      (loop\n        :initially (issue #\\\") :finally (issue #\\\")\n        :with l = (length x) :with i = 0\n        :for i+1 = (1+ i) :while (< i l) :do\n          (case (char x i)\n            ((#\\\") (issue-backslash 1) (issue #\\\") (setf i i+1))\n            ((#\\\\)\n             (let* ((j (and (< i+1 l) (position-if-not\n                                       #'(lambda (c) (eql c #\\\\)) x :start i+1)))\n                    (n (- (or j l) i)))\n               (cond\n                 ((null j)\n                  (issue-backslash (* 2 n)) (setf i l))\n                 ((and (< j l) (eql (char x j) #\\\"))\n                  (issue-backslash (1+ (* 2 n))) (issue #\\\") (setf i (1+ j)))\n                 (t\n                  (issue-backslash n) (setf i j)))))\n            (otherwise\n             (issue (char x i)) (setf i i+1))))))\n\n  (defun easy-windows-character-p (x)\n    \"Is X an \\\"easy\\\" character that does not require quoting by the shell?\"\n    (or (alphanumericp x) (find x \"+-_.,@:/=\")))\n\n  (defun escape-windows-token (token &optional s)\n    \"Escape a string TOKEN within double-quotes if needed\nfor use within a MS Windows command-line, outputing to S.\"\n    (escape-token token :stream s :good-chars #'easy-windows-character-p :quote nil\n                        :escaper 'escape-windows-token-within-double-quotes))\n\n  (defun escape-sh-token-within-double-quotes (x s &key (quote t))\n    \"Escape a string TOKEN within double-quotes\nfor use within a POSIX Bourne shell, outputing to S;\nomit the outer double-quotes if key argument :QUOTE is NIL\"\n    (when quote (princ #\\\" s))\n    (loop :for c :across x :do\n      (when (find c \"$`\\\\\\\"\") (princ #\\\\ s))\n      (princ c s))\n    (when quote (princ #\\\" s)))\n\n  (defun easy-sh-character-p (x)\n    \"Is X an \\\"easy\\\" character that does not require quoting by the shell?\"\n    (or (alphanumericp x) (find x \"+-_.,%@:/=\")))\n\n  (defun escape-sh-token (token &optional s)\n    \"Escape a string TOKEN within double-quotes if needed\nfor use within a POSIX Bourne shell, outputing to S.\"\n    (escape-token token :stream s :quote #\\\" :good-chars #'easy-sh-character-p\n                        :escaper 'escape-sh-token-within-double-quotes))\n\n  (defun escape-shell-token (token &optional s)\n    \"Escape a token for the current operating system shell\"\n    (os-cond\n      ((os-unix-p) (escape-sh-token token s))\n      ((os-windows-p) (escape-windows-token token s))))\n\n  (defun escape-command (command &optional s\n                                  (escaper 'escape-shell-token))\n    \"Given a COMMAND as a list of tokens, return a string of the\nspaced, escaped tokens, using ESCAPER to escape.\"\n    (etypecase command\n      (string (output-string command s))\n      (list (with-output (s)\n              (loop :for first = t :then nil :for token :in command :do\n                (unless first (princ #\\space s))\n                (funcall escaper token s))))))\n\n  (defun escape-windows-command (command &optional s)\n    \"Escape a list of command-line arguments into a string suitable for parsing\nby CommandLineToArgv in MS Windows\"\n    ;; http://msdn.microsoft.com/en-us/library/bb776391(v=vs.85).aspx\n    ;; http://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.85).aspx\n    (escape-command command s 'escape-windows-token))\n\n  (defun escape-sh-command (command &optional s)\n    \"Escape a list of command-line arguments into a string suitable for parsing\nby /bin/sh in POSIX\"\n    (escape-command command s 'escape-sh-token))\n\n  (defun escape-shell-command (command &optional stream)\n    \"Escape a command for the current operating system's shell\"\n    (escape-command command stream 'escape-shell-token)))\n\n\n(with-upgradability ()\n  ;;; Internal helpers for run-program\n  (defun %normalize-io-specifier (specifier &optional role)\n    \"Normalizes a portable I/O specifier for LAUNCH-PROGRAM into an implementation-dependent\nargument to pass to the internal RUN-PROGRAM\"\n    (declare (ignorable role))\n    (typecase specifier\n      (null (or #+(or allegro lispworks) (null-device-pathname)))\n      (string (parse-native-namestring specifier))\n      (pathname specifier)\n      (stream specifier)\n      ((eql :stream) :stream)\n      ((eql :interactive)\n       #+(or allegro lispworks) nil\n       #+clisp :terminal\n       #+(or abcl clozure cmucl ecl mkcl sbcl scl) t\n       #-(or abcl clozure cmucl ecl mkcl sbcl scl allegro lispworks clisp)\n       (not-implemented-error :interactive-output\n                              \"On this lisp implementation, cannot interpret ~a value of ~a\"\n                              specifier role))\n      ((eql :output)\n       (cond ((eq role :error-output)\n              #+(or abcl allegro clozure cmucl ecl lispworks mkcl sbcl scl)\n              :output\n              #-(or abcl allegro clozure cmucl ecl lispworks mkcl sbcl scl)\n              (not-implemented-error :error-output-redirect\n                                     \"Can't send ~a to ~a on this lisp implementation.\"\n                                     role specifier))\n             (t (parameter-error \"~S IO specifier invalid for ~S\" specifier role))))\n      (otherwise\n       (parameter-error \"Incorrect I/O specifier ~S for ~S\"\n                        specifier role))))\n\n  (defun %interactivep (input output error-output)\n    (member :interactive (list input output error-output)))\n\n  (defun %signal-to-exit-code (signum)\n    (+ 128 signum))\n\n  #+mkcl\n  (defun %mkcl-signal-to-number (signal)\n    (require :mk-unix)\n    (symbol-value (find-symbol signal :mk-unix)))\n\n  (defclass process-info ()\n    ((process :initform nil)\n     (input-stream :initform nil)\n     (output-stream :initform nil)\n     (bidir-stream :initform nil)\n     (error-output-stream :initform nil)\n     ;; For backward-compatibility, to maintain the property (zerop\n     ;; exit-code) <-> success, an exit in response to a signal is\n     ;; encoded as 128+signum.\n     (exit-code :initform nil)\n     ;; If the platform allows it, distinguish exiting with a code\n     ;; >128 from exiting in response to a signal by setting this code\n     (signal-code :initform nil)))\n\n;;;---------------------------------------------------------------------------\n;;; The following two helper functions take care of handling the IF-EXISTS and\n;;; IF-DOES-NOT-EXIST arguments for RUN-PROGRAM. In particular, they process the\n;;; :ERROR, :APPEND, and :SUPERSEDE arguments *here*, allowing the master\n;;; function to treat input and output files unconditionally for reading and\n;;; writing.\n;;;---------------------------------------------------------------------------\n\n  (defun %handle-if-exists (file if-exists)\n    (when (or (stringp file) (pathnamep file))\n      (ecase if-exists\n        ((:append :supersede :error)\n         (with-open-file (dummy file :direction :output :if-exists if-exists)\n           (declare (ignorable dummy)))))))\n\n  (defun %handle-if-does-not-exist (file if-does-not-exist)\n    (when (or (stringp file) (pathnamep file))\n      (ecase if-does-not-exist\n        ((:create :error)\n         (with-open-file (dummy file :direction :probe\n                                :if-does-not-exist if-does-not-exist)\n           (declare (ignorable dummy)))))))\n\n  (defun process-info-error-output (process-info)\n    (slot-value process-info 'error-output-stream))\n  (defun process-info-input (process-info)\n    (or (slot-value process-info 'bidir-stream)\n        (slot-value process-info 'input-stream)))\n  (defun process-info-output (process-info)\n    (or (slot-value process-info 'bidir-stream)\n        (slot-value process-info 'output-stream)))\n\n  (defun process-info-pid (process-info)\n    (let ((process (slot-value process-info 'process)))\n      (declare (ignorable process))\n      #+abcl (symbol-call :sys :process-pid process)\n      #+allegro process\n      #+clozure (ccl:external-process-id process)\n      #+ecl (ext:external-process-pid process)\n      #+(or cmucl scl) (ext:process-pid process)\n      #+lispworks7+ (sys:pipe-pid process)\n      #+(and lispworks (not lispworks7+)) process\n      #+mkcl (mkcl:process-id process)\n      #+sbcl (sb-ext:process-pid process)\n      #-(or abcl allegro clozure cmucl ecl mkcl lispworks sbcl scl)\n      (not-implemented-error 'process-info-pid)))\n\n  (defun %process-status (process-info)\n    (if-let (exit-code (slot-value process-info 'exit-code))\n      (return-from %process-status\n        (if-let (signal-code (slot-value process-info 'signal-code))\n          (values :signaled signal-code)\n          (values :exited exit-code))))\n    #-(or allegro clozure cmucl ecl lispworks mkcl sbcl scl)\n    (not-implemented-error '%process-status)\n    (if-let (process (slot-value process-info 'process))\n      (multiple-value-bind (status code)\n          (progn\n            #+allegro (multiple-value-bind (exit-code pid signal)\n                          (sys:reap-os-subprocess :pid process :wait nil)\n                        (assert pid)\n                        (cond ((null exit-code) :running)\n                              ((null signal) (values :exited exit-code))\n                              (t (values :signaled signal))))\n            #+clozure (ccl:external-process-status process)\n            #+(or cmucl scl) (let ((status (ext:process-status process)))\n                               (values status (if (member status '(:exited :signaled))\n                                                  (ext:process-exit-code process))))\n            #+ecl (ext:external-process-status process)\n            #+lispworks\n            ;; a signal is only returned on LispWorks 7+\n            (multiple-value-bind (exit-code signal)\n                (funcall #+lispworks7+ #'sys:pipe-exit-status\n                         #-lispworks7+ #'sys:pid-exit-status\n                         process :wait nil)\n              (cond ((null exit-code) :running)\n                    ((null signal) (values :exited exit-code))\n                    (t (values :signaled signal))))\n            #+mkcl (let ((status (mk-ext:process-status process))\n                         (code (mk-ext:process-exit-code process)))\n                     (if (stringp code)\n                         (values :signaled (%mkcl-signal-to-number code))\n                         (values status code)))\n            #+sbcl (let ((status (sb-ext:process-status process)))\n                     (values status (if (member status '(:exited :signaled))\n                                        (sb-ext:process-exit-code process)))))\n        (case status\n          (:exited (setf (slot-value process-info 'exit-code) code))\n          (:signaled (let ((%code (%signal-to-exit-code code)))\n                       (setf (slot-value process-info 'exit-code) %code\n                             (slot-value process-info 'signal-code) code))))\n        (values status code))))\n\n  (defun process-alive-p (process-info)\n    \"Check if a process has yet to exit.\"\n    (unless (slot-value process-info 'exit-code)\n      #+abcl (sys:process-alive-p (slot-value process-info 'process))\n      #+(or cmucl scl) (ext:process-alive-p (slot-value process-info 'process))\n      #+sbcl (sb-ext:process-alive-p (slot-value process-info 'process))\n      #-(or abcl cmucl sbcl scl) (member (%process-status process-info)\n                                         '(:running :sleeping))))\n\n  (defun wait-process (process-info)\n    \"Wait for the process to terminate, if it is still running.\nOtherwise, return immediately. An exit code (a number) will be\nreturned, with 0 indicating success, and anything else indicating\nfailure. If the process exits after receiving a signal, the exit code\nwill be the sum of 128 and the (positive) numeric signal code. A second\nvalue may be returned in this case: the numeric signal code itself.\nAny asynchronously spawned process requires this function to be run\nbefore it is garbage-collected in order to free up resources that\nmight otherwise be irrevocably lost.\"\n    (if-let (exit-code (slot-value process-info 'exit-code))\n      (if-let (signal-code (slot-value process-info 'signal-code))\n        (values exit-code signal-code)\n        exit-code)\n      (let ((process (slot-value process-info 'process)))\n        #-(or abcl allegro clozure cmucl ecl lispworks mkcl sbcl scl)\n        (not-implemented-error 'wait-process)\n        (when process\n          ;; 1- wait\n          #+clozure (ccl::external-process-wait process)\n          #+(or cmucl scl) (ext:process-wait process)\n          #+sbcl (sb-ext:process-wait process)\n          ;; 2- extract result\n          (multiple-value-bind (exit-code signal-code)\n              (progn\n                #+abcl (sys:process-wait process)\n                #+allegro (multiple-value-bind (exit-code pid signal)\n                              (sys:reap-os-subprocess :pid process :wait t)\n                            (assert pid)\n                            (values exit-code signal))\n                #+clozure (multiple-value-bind (status code)\n                              (ccl:external-process-status process)\n                            (if (eq status :signaled)\n                                (values nil code)\n                                code))\n                #+(or cmucl scl) (let ((status (ext:process-status process))\n                                       (code (ext:process-exit-code process)))\n                                   (if (eq status :signaled)\n                                       (values nil code)\n                                       code))\n                #+ecl (multiple-value-bind (status code)\n                          (ext:external-process-wait process t)\n                        (if (eq status :signaled)\n                            (values nil code)\n                            code))\n                #+lispworks (funcall #+lispworks7+ #'sys:pipe-exit-status\n                                     #-lispworks7+ #'sys:pid-exit-status\n                                     process :wait t)\n                #+mkcl (let ((code (mkcl:join-process process)))\n                         (if (stringp code)\n                             (values nil (%mkcl-signal-to-number code))\n                             code))\n                #+sbcl (let ((status (sb-ext:process-status process))\n                             (code (sb-ext:process-exit-code process)))\n                         (if (eq status :signaled)\n                             (values nil code)\n                             code)))\n            (if signal-code\n                (let ((%exit-code (%signal-to-exit-code signal-code)))\n                  (setf (slot-value process-info 'exit-code) %exit-code\n                        (slot-value process-info 'signal-code) signal-code)\n                  (values %exit-code signal-code))\n                (progn (setf (slot-value process-info 'exit-code) exit-code)\n                       exit-code)))))))\n\n  ;; WARNING: For signals other than SIGTERM and SIGKILL this may not\n  ;; do what you expect it to. Sending SIGSTOP to a process spawned\n  ;; via LAUNCH-PROGRAM, e.g., will stop the shell /bin/sh that is used\n  ;; to run the command (via `sh -c command`) but not the actual\n  ;; command.\n  #+os-unix\n  (defun %posix-send-signal (process-info signal)\n    #+allegro (excl.osi:kill (slot-value process-info 'process) signal)\n    #+clozure (ccl:signal-external-process (slot-value process-info 'process)\n                                           signal :error-if-exited nil)\n    #+(or cmucl scl) (ext:process-kill (slot-value process-info 'process) signal)\n    #+sbcl (sb-ext:process-kill (slot-value process-info 'process) signal)\n    #-(or allegro clozure cmucl sbcl scl)\n    (if-let (pid (process-info-pid process-info))\n      (symbol-call :uiop :run-program\n                   (format nil \"kill -~a ~a\" signal pid) :ignore-error-status t)))\n\n  ;;; this function never gets called on Windows, but the compiler cannot tell\n  ;;; that. [2016/09/25:rpg]\n  #+os-windows\n  (defun %posix-send-signal (process-info signal)\n    (declare (ignore process-info signal))\n    (values))\n\n  (defun terminate-process (process-info &key urgent)\n    \"Cause the process to exit. To that end, the process may or may\nnot be sent a signal, which it will find harder (or even impossible)\nto ignore if URGENT is T. On some platforms, it may also be subject to\nrace conditions.\"\n    (declare (ignorable urgent))\n    #+abcl (sys:process-kill (slot-value process-info 'process))\n    #+clasp (mp:process-kill (slot-value process-info 'process))\n    ;; On ECL, this will only work on versions later than 2016-09-06,\n    ;; but we still want to compile on earlier versions, so we use symbol-call\n    #+ecl (symbol-call :ext :terminate-process (slot-value process-info 'process) urgent)\n    #+lispworks7+ (sys:pipe-kill-process (slot-value process-info 'process))\n    #+mkcl (mk-ext:terminate-process (slot-value process-info 'process)\n                                     :force urgent)\n    #-(or abcl clasp ecl lispworks7+ mkcl)\n    (os-cond\n     ((os-unix-p) (%posix-send-signal process-info (if urgent 9 15)))\n     ((os-windows-p) (if-let (pid (process-info-pid process-info))\n                       (symbol-call :uiop :run-program\n                                    (format nil \"taskkill ~:[~;/f ~]/pid ~a\" urgent pid)\n                                    :ignore-error-status t)))\n     (t (not-implemented-error 'terminate-process))))\n\n  (defun close-streams (process-info)\n    \"Close any stream that the process might own. Needs to be run\nwhenever streams were requested by passing :stream to :input, :output,\nor :error-output.\"\n    (dolist (stream\n              (cons (slot-value process-info 'error-output-stream)\n                    (if-let (bidir-stream (slot-value process-info 'bidir-stream))\n                      (list bidir-stream)\n                      (list (slot-value process-info 'input-stream)\n                            (slot-value process-info 'output-stream)))))\n      (when stream (close stream))))\n\n  (defun launch-program (command &rest keys\n                         &key\n                           input (if-input-does-not-exist :error)\n                           output (if-output-exists :supersede)\n                           error-output (if-error-output-exists :supersede)\n                           (element-type #-clozure *default-stream-element-type*\n                                         #+clozure 'character)\n                           (external-format *utf-8-external-format*)\n                           directory\n                           #+allegro separate-streams\n                           &allow-other-keys)\n    \"Launch program specified by COMMAND,\neither a list of strings specifying a program and list of arguments,\nor a string specifying a shell command (/bin/sh on Unix, CMD.EXE on\nWindows) _asynchronously_.\n\nIf OUTPUT is a pathname, a string designating a pathname, or NIL (the\ndefault) designating the null device, the file at that path is used as\noutput.\nIf it's :INTERACTIVE, output is inherited from the current process;\nbeware that this may be different from your *STANDARD-OUTPUT*, and\nunder SLIME will be on your *inferior-lisp* buffer.  If it's T, output\ngoes to your current *STANDARD-OUTPUT* stream.  If it's :STREAM, a new\nstream will be made available that can be accessed via\nPROCESS-INFO-OUTPUT and read from. Otherwise, OUTPUT should be a value\nthat the underlying lisp implementation knows how to handle.\n\nIF-OUTPUT-EXISTS, which is only meaningful if OUTPUT is a string or a\npathname, can take the values :ERROR, :APPEND, and :SUPERSEDE (the\ndefault). The meaning of these values and their effect on the case\nwhere OUTPUT does not exist, is analogous to the IF-EXISTS parameter\nto OPEN with :DIRECTION :OUTPUT.\n\nERROR-OUTPUT is similar to OUTPUT. T designates the *ERROR-OUTPUT*,\n:OUTPUT means redirecting the error output to the output stream,\nand :STREAM causes a stream to be made available via\nPROCESS-INFO-ERROR-OUTPUT.\n\nIF-ERROR-OUTPUT-EXISTS is similar to IF-OUTPUT-EXIST, except that it\naffects ERROR-OUTPUT rather than OUTPUT.\n\nINPUT is similar to OUTPUT, except that T designates the\n*STANDARD-INPUT* and a stream requested through the :STREAM keyword\nwould be available through PROCESS-INFO-INPUT.\n\nIF-INPUT-DOES-NOT-EXIST, which is only meaningful if INPUT is a string\nor a pathname, can take the values :CREATE and :ERROR (the\ndefault). The meaning of these values is analogous to the\nIF-DOES-NOT-EXIST parameter to OPEN with :DIRECTION :INPUT.\n\nELEMENT-TYPE and EXTERNAL-FORMAT are passed on to your Lisp\nimplementation, when applicable, for creation of the output stream.\n\nLAUNCH-PROGRAM returns a PROCESS-INFO object.\"\n    #-(or abcl allegro clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl)\n    (progn command keys input output error-output directory element-type external-format\n           if-input-does-not-exist if-output-exists if-error-output-exists ;; ignore\n           (not-implemented-error 'launch-program))\n    #+allegro\n    (when (some #'(lambda (stream)\n                    (and (streamp stream)\n                         (not (file-stream-p stream))))\n                (list input output error-output))\n      (parameter-error \"~S: Streams passed as I/O parameters need to be file streams on this lisp\"\n                       'launch-program))\n    #+(or abcl clisp lispworks)\n    (when (some #'streamp (list input output error-output))\n      (parameter-error \"~S: I/O parameters cannot be foreign streams on this lisp\"\n                       'launch-program))\n    #+clisp\n    (unless (eq error-output :interactive)\n      (parameter-error \"~S: The only admissible value for ~S is ~S on this lisp\"\n                       'launch-program :error-output :interactive))\n    #+ecl\n    (when (some #'(lambda (stream)\n                    (and (streamp stream)\n                         (not (file-or-synonym-stream-p stream))))\n                (list input output error-output))\n      (parameter-error \"~S: Streams passed as I/O parameters need to be (synonymous with) file streams on this lisp\"\n                       'launch-program))\n    #+(or abcl allegro clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl)\n    (nest\n     (progn ;; see comments for these functions\n       (%handle-if-does-not-exist input if-input-does-not-exist)\n       (%handle-if-exists output if-output-exists)\n       (%handle-if-exists error-output if-error-output-exists))\n     #+ecl (let ((*standard-input* *stdin*)\n                 (*standard-output* *stdout*)\n                 (*error-output* *stderr*)))\n     (let ((process-info (make-instance 'process-info))\n           (input (%normalize-io-specifier input :input))\n           (output (%normalize-io-specifier output :output))\n           (error-output (%normalize-io-specifier error-output :error-output))\n           #+(and allegro os-windows) (interactive (%interactivep input output error-output))\n           (command\n            (etypecase command\n              #+os-unix (string `(\"/bin/sh\" \"-c\" ,command))\n              #+os-unix (list command)\n              #+os-windows\n              (string\n               ;; NB: On other Windows implementations, this is utterly bogus\n               ;; except in the most trivial cases where no quoting is needed.\n               ;; Use at your own risk.\n               #-(or allegro clisp clozure ecl)\n               (nest\n                #+(or ecl sbcl) (unless (find-symbol* :escape-arguments #+ecl :ext #+sbcl :sb-impl nil))\n                (parameter-error \"~S doesn't support string commands on Windows on this Lisp\"\n                                 'launch-program command))\n               ;; NB: We add cmd /c here. Behavior without going through cmd is not well specified\n               ;; when the command contains spaces or special characters:\n               ;; IIUC, the system will use space as a separator,\n               ;; but the C++ argv-decoding libraries won't, and\n               ;; you're supposed to use an extra argument to CreateProcess to bridge the gap,\n               ;; yet neither allegro nor clisp provide access to that argument.\n               #+(or allegro clisp) (strcat \"cmd /c \" command)\n               ;; On ClozureCL for Windows, we assume you are using\n               ;; r15398 or later in 1.9 or later,\n               ;; so that bug 858 is fixed http://trac.clozure.com/ccl/ticket/858\n               ;; On ECL, commit 2040629 https://gitlab.com/embeddable-common-lisp/ecl/issues/304\n               ;; On SBCL, we assume the patch from fcae0fd (to be part of SBCL 1.3.13)\n               #+(or clozure ecl sbcl) (cons \"cmd\" (strcat \"/c \" command)))\n              #+os-windows\n              (list\n               #+allegro (escape-windows-command command)\n               #-allegro command)))))\n     #+(or abcl (and allegro os-unix) clozure cmucl ecl mkcl sbcl)\n     (let ((program (car command))\n           #-allegro (arguments (cdr command))))\n     #+(and (or ecl sbcl) os-windows)\n     (multiple-value-bind (arguments escape-arguments)\n         (if (listp arguments)\n             (values arguments t)\n             (values (list arguments) nil)))\n     #-(or allegro mkcl sbcl) (with-current-directory (directory))\n     (multiple-value-bind\n       #+(or abcl clozure cmucl sbcl scl) (process)\n       #+allegro (in-or-io out-or-err err-or-pid pid-or-nil)\n       #+ecl (stream code process)\n       #+lispworks (io-or-pid err-or-nil #-lispworks7+ pid-or-nil)\n       #+mkcl (stream process code)\n       #.`(apply\n           #+abcl 'sys:run-program\n           #+allegro ,@'('excl:run-shell-command\n                         #+os-unix (coerce (cons program command) 'vector)\n                         #+os-windows command)\n           #+clozure 'ccl:run-program\n           #+(or cmucl ecl scl) 'ext:run-program\n           #+lispworks ,@'('system:run-shell-command `(\"/usr/bin/env\" ,@command)) ; full path needed\n           #+mkcl 'mk-ext:run-program\n           #+sbcl 'sb-ext:run-program\n           #+(or abcl clozure cmucl ecl mkcl sbcl) ,@'(program arguments)\n           #+(and (or ecl sbcl) os-windows) ,@'(:escape-arguments escape-arguments)\n           :input input :if-input-does-not-exist :error\n           :output output :if-output-exists :append\n           ,(or #+(or allegro lispworks) :error-output :error) error-output\n           ,(or #+(or allegro lispworks) :if-error-output-exists :if-error-exists) :append\n           :wait nil :element-type element-type :external-format external-format\n           :allow-other-keys t\n           #+allegro ,@`(:directory directory\n                         #+os-windows ,@'(:show-window (if interactive nil :hide)))\n           #+lispworks ,@'(:save-exit-status t)\n           #+mkcl ,@'(:directory (native-namestring directory))\n           #-sbcl keys ;; on SBCL, don't pass :directory nil but remove it from the keys\n           #+sbcl ,@'(:search t (if directory keys (remove-plist-key :directory keys)))))\n     (labels ((prop (key value) (setf (slot-value process-info key) value)))\n       #+allegro\n       (cond\n         (separate-streams\n          (prop 'process pid-or-nil)\n          (when (eq input :stream) (prop 'input-stream in-or-io))\n          (when (eq output :stream) (prop 'output-stream out-or-err))\n          (when (eq error-output :stream) (prop 'error-stream err-or-pid)))\n         (t\n          (prop 'process err-or-pid)\n          (ecase (+ (if (eq input :stream) 1 0) (if (eq output :stream) 2 0))\n            (0)\n            (1 (prop 'input-stream in-or-io))\n            (2 (prop 'output-stream in-or-io))\n            (3 (prop 'bidir-stream in-or-io)))\n          (when (eq error-output :stream)\n            (prop 'error-stream out-or-err))))\n       #+(or abcl clozure cmucl sbcl scl)\n       (progn\n         (prop 'process process)\n         (when (eq input :stream)\n           (nest\n            (prop 'input-stream)\n            #+abcl (symbol-call :sys :process-input)\n            #+clozure (ccl:external-process-input-stream)\n            #+(or cmucl scl) (ext:process-input)\n            #+sbcl (sb-ext:process-input)\n            process))\n         (when (eq output :stream)\n           (nest\n            (prop 'output-stream)\n            #+abcl (symbol-call :sys :process-output)\n            #+clozure (ccl:external-process-output-stream)\n            #+(or cmucl scl) (ext:process-output)\n            #+sbcl (sb-ext:process-output)\n            process))\n         (when (eq error-output :stream)\n           (nest\n            (prop 'error-output-stream)\n            #+abcl (symbol-call :sys :process-error)\n            #+clozure (ccl:external-process-error-stream)\n            #+(or cmucl scl) (ext:process-error)\n            #+sbcl (sb-ext:process-error)\n            process)))\n       #+(or ecl mkcl)\n       (let ((mode (+ (if (eq input :stream) 1 0) (if (eq output :stream) 2 0))))\n         code ;; ignore\n         (unless (zerop mode)\n           (prop (case mode (1 'input-stream) (2 'output-stream) (3 'bidir-stream)) stream))\n         (prop 'process process))\n       #+lispworks\n       (let ((mode (+ (if (eq input :stream) 1 0) (if (eq output :stream) 2 0))))\n         (cond\n           ((or (plusp mode) (eq error-output :stream))\n            (prop 'process #+lispworks7+ io-or-pid #-lispworks7+ pid-or-nil)\n            (when (plusp mode)\n              (prop (ecase mode\n                      (1 'input-stream)\n                      (2 'output-stream)\n                      (3 'bidir-stream)) io-or-pid))\n            (when (eq error-output :stream)\n              (prop 'error-stream err-or-nil)))\n           ;; lispworks6 returns (pid), lispworks7 returns (io err pid) of which we keep io\n           (t (prop 'process io-or-pid)))))\n     process-info)))\n\n;;;; -------------------------------------------------------------------------\n;;;; run-program initially from xcvb-driver.\n\n(uiop/package:define-package :uiop/run-program\n  (:nicknames :asdf/run-program) ; OBSOLETE. Used by cl-sane, printv.\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/version\n   :uiop/pathname :uiop/os :uiop/filesystem :uiop/stream :uiop/launch-program)\n  (:export\n   #:run-program\n   #:slurp-input-stream #:vomit-output-stream\n   #:subprocess-error\n   #:subprocess-error-code #:subprocess-error-command #:subprocess-error-process)\n  (:import-from :uiop/launch-program\n   #:%handle-if-does-not-exist #:%handle-if-exists #:%interactivep\n   #:input-stream #:output-stream #:error-output-stream))\n(in-package :uiop/run-program)\n\n;;;; Slurping a stream, typically the output of another program\n(with-upgradability ()\n  (defun call-stream-processor (fun processor stream)\n    \"Given FUN (typically SLURP-INPUT-STREAM or VOMIT-OUTPUT-STREAM,\na PROCESSOR specification which is either an atom or a list specifying\na processor an keyword arguments, call the specified processor with\nthe given STREAM as input\"\n    (if (consp processor)\n        (apply fun (first processor) stream (rest processor))\n        (funcall fun processor stream)))\n\n  (defgeneric slurp-input-stream (processor input-stream &key)\n    (:documentation\n     \"SLURP-INPUT-STREAM is a generic function with two positional arguments\nPROCESSOR and INPUT-STREAM and additional keyword arguments, that consumes (slurps)\nthe contents of the INPUT-STREAM and processes them according to a method\nspecified by PROCESSOR.\n\nBuilt-in methods include the following:\n* if PROCESSOR is a function, it is called with the INPUT-STREAM as its argument\n* if PROCESSOR is a list, its first element should be a function.  It will be applied to a cons of the\n  INPUT-STREAM and the rest of the list.  That is (x . y) will be treated as\n    \\(APPLY x <stream> y\\)\n* if PROCESSOR is an output-stream, the contents of INPUT-STREAM is copied to the output-stream,\n  per copy-stream-to-stream, with appropriate keyword arguments.\n* if PROCESSOR is the symbol CL:STRING or the keyword :STRING, then the contents of INPUT-STREAM\n  are returned as a string, as per SLURP-STREAM-STRING.\n* if PROCESSOR is the keyword :LINES then the INPUT-STREAM will be handled by SLURP-STREAM-LINES.\n* if PROCESSOR is the keyword :LINE then the INPUT-STREAM will be handled by SLURP-STREAM-LINE.\n* if PROCESSOR is the keyword :FORMS then the INPUT-STREAM will be handled by SLURP-STREAM-FORMS.\n* if PROCESSOR is the keyword :FORM then the INPUT-STREAM will be handled by SLURP-STREAM-FORM.\n* if PROCESSOR is T, it is treated the same as *standard-output*. If it is NIL, NIL is returned.\n\nProgrammers are encouraged to define their own methods for this generic function.\"))\n\n  #-genera\n  (defmethod slurp-input-stream ((function function) input-stream &key)\n    (funcall function input-stream))\n\n  (defmethod slurp-input-stream ((list cons) input-stream &key)\n    (apply (first list) input-stream (rest list)))\n\n  #-genera\n  (defmethod slurp-input-stream ((output-stream stream) input-stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (copy-stream-to-stream\n     input-stream output-stream\n     :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n\n  (defmethod slurp-input-stream ((x (eql 'string)) stream &key stripped)\n    (slurp-stream-string stream :stripped stripped))\n\n  (defmethod slurp-input-stream ((x (eql :string)) stream &key stripped)\n    (slurp-stream-string stream :stripped stripped))\n\n  (defmethod slurp-input-stream ((x (eql :lines)) stream &key count)\n    (slurp-stream-lines stream :count count))\n\n  (defmethod slurp-input-stream ((x (eql :line)) stream &key (at 0))\n    (slurp-stream-line stream :at at))\n\n  (defmethod slurp-input-stream ((x (eql :forms)) stream &key count)\n    (slurp-stream-forms stream :count count))\n\n  (defmethod slurp-input-stream ((x (eql :form)) stream &key (at 0))\n    (slurp-stream-form stream :at at))\n\n  (defmethod slurp-input-stream ((x (eql t)) stream &rest keys &key &allow-other-keys)\n    (apply 'slurp-input-stream *standard-output* stream keys))\n\n  (defmethod slurp-input-stream ((x null) (stream t) &key)\n    nil)\n\n  (defmethod slurp-input-stream ((pathname pathname) input\n                                 &key\n                                   (element-type *default-stream-element-type*)\n                                   (external-format *utf-8-external-format*)\n                                   (if-exists :rename-and-delete)\n                                   (if-does-not-exist :create)\n                                   buffer-size\n                                   linewise)\n    (with-output-file (output pathname\n                              :element-type element-type\n                              :external-format external-format\n                              :if-exists if-exists\n                              :if-does-not-exist if-does-not-exist)\n      (copy-stream-to-stream\n       input output\n       :element-type element-type :buffer-size buffer-size :linewise linewise)))\n\n  (defmethod slurp-input-stream (x stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (declare (ignorable stream linewise prefix element-type buffer-size))\n    (cond\n      #+genera\n      ((functionp x) (funcall x stream))\n      #+genera\n      ((output-stream-p x)\n       (copy-stream-to-stream\n        stream x\n        :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n      (t\n       (parameter-error \"Invalid ~S destination ~S\" 'slurp-input-stream x)))))\n\n;;;; Vomiting a stream, typically into the input of another program.\n(with-upgradability ()\n  (defgeneric vomit-output-stream (processor output-stream &key)\n    (:documentation\n     \"VOMIT-OUTPUT-STREAM is a generic function with two positional arguments\nPROCESSOR and OUTPUT-STREAM and additional keyword arguments, that produces (vomits)\nsome content onto the OUTPUT-STREAM, according to a method specified by PROCESSOR.\n\nBuilt-in methods include the following:\n* if PROCESSOR is a function, it is called with the OUTPUT-STREAM as its argument\n* if PROCESSOR is a list, its first element should be a function.\n  It will be applied to a cons of the OUTPUT-STREAM and the rest of the list.\n  That is (x . y) will be treated as \\(APPLY x <stream> y\\)\n* if PROCESSOR is an input-stream, its contents will be copied the OUTPUT-STREAM,\n  per copy-stream-to-stream, with appropriate keyword arguments.\n* if PROCESSOR is a string, its contents will be printed to the OUTPUT-STREAM.\n* if PROCESSOR is T, it is treated the same as *standard-input*. If it is NIL, nothing is done.\n\nProgrammers are encouraged to define their own methods for this generic function.\"))\n\n  #-genera\n  (defmethod vomit-output-stream ((function function) output-stream &key)\n    (funcall function output-stream))\n\n  (defmethod vomit-output-stream ((list cons) output-stream &key)\n    (apply (first list) output-stream (rest list)))\n\n  #-genera\n  (defmethod vomit-output-stream ((input-stream stream) output-stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (copy-stream-to-stream\n     input-stream output-stream\n     :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n\n  (defmethod vomit-output-stream ((x string) stream &key fresh-line terpri)\n    (princ x stream)\n    (when fresh-line (fresh-line stream))\n    (when terpri (terpri stream))\n    (values))\n\n  (defmethod vomit-output-stream ((x (eql t)) stream &rest keys &key &allow-other-keys)\n    (apply 'vomit-output-stream *standard-input* stream keys))\n\n  (defmethod vomit-output-stream ((x null) (stream t) &key)\n    (values))\n\n  (defmethod vomit-output-stream ((pathname pathname) input\n                                 &key\n                                   (element-type *default-stream-element-type*)\n                                   (external-format *utf-8-external-format*)\n                                   (if-exists :rename-and-delete)\n                                   (if-does-not-exist :create)\n                                   buffer-size\n                                   linewise)\n    (with-output-file (output pathname\n                              :element-type element-type\n                              :external-format external-format\n                              :if-exists if-exists\n                              :if-does-not-exist if-does-not-exist)\n      (copy-stream-to-stream\n       input output\n       :element-type element-type :buffer-size buffer-size :linewise linewise)))\n\n  (defmethod vomit-output-stream (x stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (declare (ignorable stream linewise prefix element-type buffer-size))\n    (cond\n      #+genera\n      ((functionp x) (funcall x stream))\n      #+genera\n      ((input-stream-p x)\n       (copy-stream-to-stream\n        x stream\n        :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n      (t\n       (parameter-error \"Invalid ~S source ~S\" 'vomit-output-stream x)))))\n\n\n;;;; Run-program: synchronously run a program in a subprocess, handling input, output and error-output.\n(with-upgradability ()\n  (define-condition subprocess-error (error)\n    ((code :initform nil :initarg :code :reader subprocess-error-code)\n     (command :initform nil :initarg :command :reader subprocess-error-command)\n     (process :initform nil :initarg :process :reader subprocess-error-process))\n    (:report (lambda (condition stream)\n               (format stream \"Subprocess ~@[~S~% ~]~@[with command ~S~% ~]exited with error~@[ code ~D~]\"\n                       (subprocess-error-process condition)\n                       (subprocess-error-command condition)\n                       (subprocess-error-code condition)))))\n\n  (defun %check-result (exit-code &key command process ignore-error-status)\n    (unless ignore-error-status\n      (unless (eql exit-code 0)\n        (cerror \"IGNORE-ERROR-STATUS\"\n                'subprocess-error :command command :code exit-code :process process)))\n    exit-code)\n\n  (defun %active-io-specifier-p (specifier)\n    \"Determines whether a run-program I/O specifier requires Lisp-side processing\nvia SLURP-INPUT-STREAM or VOMIT-OUTPUT-STREAM (return T),\nor whether it's already taken care of by the implementation's underlying run-program.\"\n    (not (typep specifier '(or null string pathname (member :interactive :output)\n                            #+(or cmucl (and sbcl os-unix) scl) (or stream (eql t))\n                            #+lispworks file-stream))))\n\n  (defun %run-program (command &rest keys &key &allow-other-keys)\n    \"DEPRECATED. Use LAUNCH-PROGRAM instead.\"\n    (apply 'launch-program command keys))\n\n  (defun %call-with-program-io (gf tval stream-easy-p fun direction spec activep returner\n                                &key\n                                  (element-type #-clozure *default-stream-element-type* #+clozure 'character)\n                                  (external-format *utf-8-external-format*) &allow-other-keys)\n    ;; handle redirection for run-program and system\n    ;; SPEC is the specification for the subprocess's input or output or error-output\n    ;; TVAL is the value used if the spec is T\n    ;; GF is the generic function to call to handle arbitrary values of SPEC\n    ;; STREAM-EASY-P is T if we're going to use a RUN-PROGRAM that copies streams in the background\n    ;; (it's only meaningful on CMUCL, SBCL, SCL that actually do it)\n    ;; DIRECTION is :INPUT, :OUTPUT or :ERROR-OUTPUT for the direction of this io argument\n    ;; FUN is a function of the new reduced spec and an activity function to call with a stream\n    ;; when the subprocess is active and communicating through that stream.\n    ;; ACTIVEP is a boolean true if we will get to run code while the process is running\n    ;; ELEMENT-TYPE and EXTERNAL-FORMAT control what kind of temporary file we may open.\n    ;; RETURNER is a function called with the value of the activity.\n    ;; --- TODO (fare@tunes.org): handle if-output-exists and such when doing it the hard way.\n    (declare (ignorable stream-easy-p))\n    (let* ((actual-spec (if (eq spec t) tval spec))\n           (activity-spec (if (eq actual-spec :output)\n                              (ecase direction\n                                ((:input :output)\n                                 (parameter-error \"~S does not allow ~S as a ~S spec\"\n                                                  'run-program :output direction))\n                                ((:error-output)\n                                 nil))\n                              actual-spec)))\n      (labels ((activity (stream)\n                 (call-function returner (call-stream-processor gf activity-spec stream)))\n               (easy-case ()\n                 (funcall fun actual-spec nil))\n               (hard-case ()\n                 (if activep\n                     (funcall fun :stream #'activity)\n                     (with-temporary-file (:pathname tmp)\n                       (ecase direction\n                         (:input\n                          (with-output-file (s tmp :if-exists :overwrite\n                                               :external-format external-format\n                                               :element-type element-type)\n                            (activity s))\n                          (funcall fun tmp nil))\n                         ((:output :error-output)\n                          (multiple-value-prog1 (funcall fun tmp nil)\n                            (with-input-file (s tmp\n                                               :external-format external-format\n                                               :element-type element-type)\n                              (activity s)))))))))\n        (typecase activity-spec\n          ((or null string pathname (eql :interactive))\n           (easy-case))\n          #+(or cmucl (and sbcl os-unix) scl) ;; streams are only easy on implementations that try very hard\n          (stream\n           (if stream-easy-p (easy-case) (hard-case)))\n          (t\n           (hard-case))))))\n\n  (defmacro place-setter (place)\n    (when place\n      (let ((value (gensym)))\n        `#'(lambda (,value) (setf ,place ,value)))))\n\n  (defmacro with-program-input (((reduced-input-var\n                                  &optional (input-activity-var (gensym) iavp))\n                                 input-form &key setf stream-easy-p active keys) &body body)\n    `(apply '%call-with-program-io 'vomit-output-stream *standard-input* ,stream-easy-p\n            #'(lambda (,reduced-input-var ,input-activity-var)\n                ,@(unless iavp `((declare (ignore ,input-activity-var))))\n                ,@body)\n            :input ,input-form ,active (place-setter ,setf) ,keys))\n\n  (defmacro with-program-output (((reduced-output-var\n                                  &optional (output-activity-var (gensym) oavp))\n                                  output-form &key setf stream-easy-p active keys) &body body)\n    `(apply '%call-with-program-io 'slurp-input-stream *standard-output* ,stream-easy-p\n            #'(lambda (,reduced-output-var ,output-activity-var)\n                ,@(unless oavp `((declare (ignore ,output-activity-var))))\n                ,@body)\n            :output ,output-form ,active (place-setter ,setf) ,keys))\n\n  (defmacro with-program-error-output (((reduced-error-output-var\n                                         &optional (error-output-activity-var (gensym) eoavp))\n                                        error-output-form &key setf stream-easy-p active keys)\n                                       &body body)\n    `(apply '%call-with-program-io 'slurp-input-stream *error-output* ,stream-easy-p\n            #'(lambda (,reduced-error-output-var ,error-output-activity-var)\n                ,@(unless eoavp `((declare (ignore ,error-output-activity-var))))\n                ,@body)\n            :error-output ,error-output-form ,active (place-setter ,setf) ,keys))\n\n  (defun %use-launch-program (command &rest keys\n                           &key input output error-output ignore-error-status &allow-other-keys)\n    ;; helper for RUN-PROGRAM when using LAUNCH-PROGRAM\n    #+(or cormanlisp gcl (and lispworks os-windows) mcl xcl)\n    (progn\n      command keys input output error-output ignore-error-status ;; ignore\n      (not-implemented-error '%use-launch-program))\n    (when (member :stream (list input output error-output))\n      (parameter-error \"~S: ~S is not allowed as synchronous I/O redirection argument\"\n                       'run-program :stream))\n    (let* ((active-input-p (%active-io-specifier-p input))\n           (active-output-p (%active-io-specifier-p output))\n           (active-error-output-p (%active-io-specifier-p error-output))\n           (activity\n             (cond\n               (active-output-p :output)\n               (active-input-p :input)\n               (active-error-output-p :error-output)\n               (t nil)))\n           output-result error-output-result exit-code process-info)\n      (with-program-output ((reduced-output output-activity)\n                            output :keys keys :setf output-result\n                            :stream-easy-p t :active (eq activity :output))\n        (with-program-error-output ((reduced-error-output error-output-activity)\n                                    error-output :keys keys :setf error-output-result\n                                    :stream-easy-p t :active (eq activity :error-output))\n          (with-program-input ((reduced-input input-activity)\n                               input :keys keys\n                               :stream-easy-p t :active (eq activity :input))\n            (setf process-info\n                  (apply 'launch-program command\n                         :input reduced-input :output reduced-output\n                         :error-output (if (eq error-output :output) :output reduced-error-output)\n                         keys))\n            (labels ((get-stream (stream-name &optional fallbackp)\n                       (or (slot-value process-info stream-name)\n                           (when fallbackp\n                             (slot-value process-info 'bidir-stream))))\n                     (run-activity (activity stream-name &optional fallbackp)\n                       (if-let (stream (get-stream stream-name fallbackp))\n                         (funcall activity stream)\n                         (error 'subprocess-error\n                                :code `(:missing ,stream-name)\n                                :command command :process process-info))))\n              (unwind-protect\n                   (ecase activity\n                     ((nil))\n                     (:input (run-activity input-activity 'input-stream t))\n                     (:output (run-activity output-activity 'output-stream t))\n                     (:error-output (run-activity error-output-activity 'error-output-stream)))\n                (close-streams process-info)\n                (setf exit-code (wait-process process-info)))))))\n      (%check-result exit-code\n                     :command command :process process-info\n                     :ignore-error-status ignore-error-status)\n      (values output-result error-output-result exit-code)))\n\n  (defun %normalize-system-command (command) ;; helper for %USE-SYSTEM\n    (etypecase command\n      (string command)\n      (list (escape-shell-command\n             (os-cond\n              ((os-unix-p) (cons \"exec\" command))\n              (t command))))))\n\n  (defun %redirected-system-command (command in out err directory) ;; helper for %USE-SYSTEM\n    (flet ((redirect (spec operator)\n             (let ((pathname\n                     (typecase spec\n                       (null (null-device-pathname))\n                       (string (parse-native-namestring spec))\n                       (pathname spec)\n                       ((eql :output)\n                        (unless (equal operator \" 2>>\")\n                          (parameter-error \"~S: only the ~S argument can be ~S\"\n                                           'run-program :error-output :output))\n                        (return-from redirect '(\" 2>&1\"))))))\n               (when pathname\n                 (list operator \" \"\n                       (escape-shell-token (native-namestring pathname)))))))\n      (let* ((redirections (append (redirect in \" <\") (redirect out \" >>\") (redirect err \" 2>>\")))\n             (normalized (%normalize-system-command command))\n             (directory (or directory #+(or abcl xcl) (getcwd)))\n             (chdir (when directory\n                      (let ((dir-arg (escape-shell-token (native-namestring directory))))\n                        (os-cond\n                         ((os-unix-p) `(\"cd \" ,dir-arg \" ; \"))\n                         ((os-windows-p) `(\"cd /d \" ,dir-arg \" & \")))))))\n        (reduce/strcat\n         (os-cond\n          ((os-unix-p) `(,@(when redirections `(\"exec \" ,@redirections \" ; \")) ,@chdir ,normalized))\n          ((os-windows-p) `(,@chdir ,@redirections \" \" ,normalized)))))))\n\n  (defun %system (command &rest keys &key directory\n                                       input (if-input-does-not-exist :error)\n                                       output (if-output-exists :supersede)\n                                       error-output (if-error-output-exists :supersede)\n                                       &allow-other-keys)\n    \"A portable abstraction of a low-level call to libc's system().\"\n    (declare (ignorable keys directory input if-input-does-not-exist output\n                        if-output-exists error-output if-error-output-exists))\n    #+(or abcl allegro clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl)\n    (let (#+(or abcl ecl mkcl)\n            (version (parse-version\n                      #-abcl\n                      (lisp-implementation-version)\n                      #+abcl\n                      (second (split-string (implementation-identifier) :separator '(#\\-))))))\n      (nest\n       #+abcl (unless (lexicographic< '< version '(1 4 0)))\n       #+ecl (unless (lexicographic<= '< version '(16 0 0)))\n       #+mkcl (unless (lexicographic<= '< version '(1 1 9)))\n       (return-from %system\n         (wait-process\n          (apply 'launch-program (%normalize-system-command command) keys)))))\n    #+(or abcl clasp clisp cormanlisp ecl gcl genera (and lispworks os-windows) mkcl xcl)\n    (let ((%command (%redirected-system-command command input output error-output directory)))\n      ;; see comments for these functions\n      (%handle-if-does-not-exist input if-input-does-not-exist)\n      (%handle-if-exists output if-output-exists)\n      (%handle-if-exists error-output if-error-output-exists)\n      #+abcl (ext:run-shell-command %command)\n      #+(or clasp ecl) (let ((*standard-input* *stdin*)\n                             (*standard-output* *stdout*)\n                             (*error-output* *stderr*))\n                         (ext:system %command))\n      #+clisp\n      (let ((raw-exit-code\n             (or\n              #.`(#+os-windows ,@'(ext:run-shell-command %command)\n                  #+os-unix ,@'(ext:run-program \"/bin/sh\" :arguments `(\"-c\" ,%command))\n                  :wait t :input :terminal :output :terminal)\n              0)))\n        (if (minusp raw-exit-code)\n            (- 128 raw-exit-code)\n            raw-exit-code))\n      #+cormanlisp (win32:system %command)\n      #+gcl (system:system %command)\n      #+genera (not-implemented-error '%system)\n      #+(and lispworks os-windows)\n      (system:call-system %command :current-directory directory :wait t)\n      #+mcl (ccl::with-cstrs ((%%command %command)) (_system %%command))\n      #+mkcl (mkcl:system %command)\n      #+xcl (system:%run-shell-command %command)))\n\n  (defun %use-system (command &rest keys\n                      &key input output error-output ignore-error-status &allow-other-keys)\n    ;; helper for RUN-PROGRAM when using %system\n    (let (output-result error-output-result exit-code)\n      (with-program-output ((reduced-output)\n                            output :keys keys :setf output-result)\n        (with-program-error-output ((reduced-error-output)\n                                    error-output :keys keys :setf error-output-result)\n          (with-program-input ((reduced-input) input :keys keys)\n            (setf exit-code (apply '%system command\n                                   :input reduced-input :output reduced-output\n                                   :error-output reduced-error-output keys)))))\n      (%check-result exit-code\n                     :command command\n                     :ignore-error-status ignore-error-status)\n      (values output-result error-output-result exit-code)))\n\n  (defun run-program (command &rest keys\n                       &key ignore-error-status (force-shell nil force-shell-suppliedp)\n                         input (if-input-does-not-exist :error)\n                         output (if-output-exists :supersede)\n                         error-output (if-error-output-exists :supersede)\n                         (element-type #-clozure *default-stream-element-type* #+clozure 'character)\n                         (external-format *utf-8-external-format*)\n                       &allow-other-keys)\n    \"Run program specified by COMMAND,\neither a list of strings specifying a program and list of arguments,\nor a string specifying a shell command (/bin/sh on Unix, CMD.EXE on Windows);\n_synchronously_ process its output as specified and return the processing results\nwhen the program and its output processing are complete.\n\nAlways call a shell (rather than directly execute the command when possible)\nif FORCE-SHELL is specified.  Similarly, never call a shell if FORCE-SHELL is\nspecified to be NIL.\n\nSignal a continuable SUBPROCESS-ERROR if the process wasn't successful (exit-code 0),\nunless IGNORE-ERROR-STATUS is specified.\n\nIf OUTPUT is a pathname, a string designating a pathname, or NIL (the default)\ndesignating the null device, the file at that path is used as output.\nIf it's :INTERACTIVE, output is inherited from the current process;\nbeware that this may be different from your *STANDARD-OUTPUT*,\nand under SLIME will be on your *inferior-lisp* buffer.\nIf it's T, output goes to your current *STANDARD-OUTPUT* stream.\nOtherwise, OUTPUT should be a value that is a suitable first argument to\nSLURP-INPUT-STREAM (qv.), or a list of such a value and keyword arguments.\nIn this case, RUN-PROGRAM will create a temporary stream for the program output;\nthe program output, in that stream, will be processed by a call to SLURP-INPUT-STREAM,\nusing OUTPUT as the first argument (or the first element of OUTPUT, and the rest as keywords).\nThe primary value resulting from that call (or NIL if no call was needed)\nwill be the first value returned by RUN-PROGRAM.\nE.g., using :OUTPUT :STRING will have it return the entire output stream as a string.\nAnd using :OUTPUT '(:STRING :STRIPPED T) will have it return the same string\nstripped of any ending newline.\n\nIF-OUTPUT-EXISTS, which is only meaningful if OUTPUT is a string or a\npathname, can take the values :ERROR, :APPEND, and :SUPERSEDE (the\ndefault). The meaning of these values and their effect on the case\nwhere OUTPUT does not exist, is analogous to the IF-EXISTS parameter\nto OPEN with :DIRECTION :OUTPUT.\n\nERROR-OUTPUT is similar to OUTPUT, except that the resulting value is returned\nas the second value of RUN-PROGRAM. T designates the *ERROR-OUTPUT*.\nAlso :OUTPUT means redirecting the error output to the output stream,\nin which case NIL is returned.\n\nIF-ERROR-OUTPUT-EXISTS is similar to IF-OUTPUT-EXIST, except that it\naffects ERROR-OUTPUT rather than OUTPUT.\n\nINPUT is similar to OUTPUT, except that VOMIT-OUTPUT-STREAM is used,\nno value is returned, and T designates the *STANDARD-INPUT*.\n\nIF-INPUT-DOES-NOT-EXIST, which is only meaningful if INPUT is a string\nor a pathname, can take the values :CREATE and :ERROR (the\ndefault). The meaning of these values is analogous to the\nIF-DOES-NOT-EXIST parameter to OPEN with :DIRECTION :INPUT.\n\nELEMENT-TYPE and EXTERNAL-FORMAT are passed on\nto your Lisp implementation, when applicable, for creation of the output stream.\n\nOne and only one of the stream slurping or vomiting may or may not happen\nin parallel in parallel with the subprocess,\ndepending on options and implementation,\nand with priority being given to output processing.\nOther streams are completely produced or consumed\nbefore or after the subprocess is spawned, using temporary files.\n\nRUN-PROGRAM returns 3 values:\n0- the result of the OUTPUT slurping if any, or NIL\n1- the result of the ERROR-OUTPUT slurping if any, or NIL\n2- either 0 if the subprocess exited with success status,\nor an indication of failure via the EXIT-CODE of the process\"\n    (declare (ignorable input output error-output if-input-does-not-exist if-output-exists\n                        if-error-output-exists element-type external-format ignore-error-status))\n    #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl lispworks mcl mkcl sbcl scl xcl)\n    (not-implemented-error 'run-program)\n    (apply (if (or force-shell\n                   ;; Per doc string, set FORCE-SHELL to T if we get command as a string.\n                   ;; But don't override user's specified preference. [2015/06/29:rpg]\n                   (and (stringp command)\n                        (or (not force-shell-suppliedp)\n                            #-(or allegro clisp clozure sbcl) (os-cond ((os-windows-p) t))))\n                   #+(or clasp clisp cormanlisp gcl (and lispworks os-windows) mcl xcl) t\n                   ;; A race condition in ECL <= 16.0.0 prevents using ext:run-program\n                   #+ecl #.(if-let (ver (parse-version (lisp-implementation-version)))\n                                   (lexicographic<= '< ver '(16 0 0)))\n                   #+(and lispworks os-unix) (%interactivep input output error-output))\n               '%use-system '%use-launch-program)\n           command keys)))\n\n;;;; ---------------------------------------------------------------------------\n;;;; Generic support for configuration files\n\n(uiop/package:define-package :uiop/configuration\n  (:recycle :uiop/configuration :asdf/configuration) ;; necessary to upgrade from 2.27.\n  (:use :uiop/common-lisp :uiop/utility\n   :uiop/os :uiop/pathname :uiop/filesystem :uiop/stream :uiop/image :uiop/lisp-build)\n  (:export\n   #:user-configuration-directories #:system-configuration-directories ;; implemented in backward-driver\n   #:in-first-directory #:in-user-configuration-directory #:in-system-configuration-directory ;; idem\n   #:get-folder-path\n   #:xdg-data-home #:xdg-config-home #:xdg-data-dirs #:xdg-config-dirs\n   #:xdg-cache-home #:xdg-runtime-dir #:system-config-pathnames\n   #:filter-pathname-set #:xdg-data-pathnames #:xdg-config-pathnames\n   #:find-preferred-file #:xdg-data-pathname #:xdg-config-pathname\n   #:validate-configuration-form #:validate-configuration-file #:validate-configuration-directory\n   #:configuration-inheritance-directive-p\n   #:report-invalid-form #:invalid-configuration #:*ignored-configuration-form* #:*user-cache*\n   #:*clear-configuration-hook* #:clear-configuration #:register-clear-configuration-hook\n   #:resolve-location #:location-designator-p #:location-function-p #:*here-directory*\n   #:resolve-relative-location #:resolve-absolute-location #:upgrade-configuration))\n(in-package :uiop/configuration)\n\n(with-upgradability ()\n  (define-condition invalid-configuration ()\n    ((form :reader condition-form :initarg :form)\n     (location :reader condition-location :initarg :location)\n     (format :reader condition-format :initarg :format)\n     (arguments :reader condition-arguments :initarg :arguments :initform nil))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<~? (will be skipped)~@:>\")\n                       (condition-format c)\n                       (list* (condition-form c) (condition-location c)\n                              (condition-arguments c))))))\n\n  (defun configuration-inheritance-directive-p (x)\n    \"Is X a configuration inheritance directive?\"\n    (let ((kw '(:inherit-configuration :ignore-inherited-configuration)))\n      (or (member x kw)\n          (and (length=n-p x 1) (member (car x) kw)))))\n\n  (defun report-invalid-form (reporter &rest args)\n    \"Report an invalid form according to REPORTER and various ARGS\"\n    (etypecase reporter\n      (null\n       (apply 'error 'invalid-configuration args))\n      (function\n       (apply reporter args))\n      ((or symbol string)\n       (apply 'error reporter args))\n      (cons\n       (apply 'apply (append reporter args)))))\n\n  (defvar *ignored-configuration-form* nil\n    \"Have configuration forms been ignored while parsing the configuration?\")\n\n  (defun validate-configuration-form (form tag directive-validator\n                                            &key location invalid-form-reporter)\n    \"Validate a configuration FORM. By default it will raise an error if the\nFORM is not valid.  Otherwise it will return the validated form.\n     Arguments control the behavior:\n     The configuration FORM should be of the form (TAG . <rest>)\n     Each element of <rest> will be checked by first seeing if it's a configuration inheritance\ndirective (see CONFIGURATION-INHERITANCE-DIRECTIVE-P) then invoking DIRECTIVE-VALIDATOR\non it.\n     In the event of an invalid form, INVALID-FORM-REPORTER will be used to control\nreporting (see REPORT-INVALID-FORM) with LOCATION providing information about where\nthe configuration form appeared.\"\n    (unless (and (consp form) (eq (car form) tag))\n      (setf *ignored-configuration-form* t)\n      (report-invalid-form invalid-form-reporter :form form :location location)\n      (return-from validate-configuration-form nil))\n    (loop :with inherit = 0 :with ignore-invalid-p = nil :with x = (list tag)\n          :for directive :in (cdr form)\n          :when (cond\n                  ((configuration-inheritance-directive-p directive)\n                   (incf inherit) t)\n                  ((eq directive :ignore-invalid-entries)\n                   (setf ignore-invalid-p t) t)\n                  ((funcall directive-validator directive)\n                   t)\n                  (ignore-invalid-p\n                   nil)\n                  (t\n                   (setf *ignored-configuration-form* t)\n                   (report-invalid-form invalid-form-reporter :form directive :location location)\n                   nil))\n            :do (push directive x)\n          :finally\n             (unless (= inherit 1)\n               (report-invalid-form invalid-form-reporter\n                                    :form form :location location\n                                    ;; we throw away the form and location arguments, hence the ~2*\n                                    ;; this is necessary because of the report in INVALID-CONFIGURATION\n                                    :format (compatfmt \"~@<Invalid source registry ~S~@[ in ~S~]. ~\n                                                        One and only one of ~S or ~S is required.~@:>\")\n                                    :arguments '(:inherit-configuration :ignore-inherited-configuration)))\n             (return (nreverse x))))\n\n  (defun validate-configuration-file (file validator &key description)\n    \"Validate a configuration FILE.  The configuration file should have only one s-expression\nin it, which will be checked with the VALIDATOR FORM.  DESCRIPTION argument used for error\nreporting.\"\n    (let ((forms (read-file-forms file)))\n      (unless (length=n-p forms 1)\n        (error (compatfmt \"~@<One and only one form allowed for ~A. Got: ~3i~_~S~@:>~%\")\n               description forms))\n      (funcall validator (car forms) :location file)))\n\n  (defun validate-configuration-directory (directory tag validator &key invalid-form-reporter)\n    \"Map the VALIDATOR across the .conf files in DIRECTORY, the TAG will\nbe applied to the results to yield a configuration form.  Current\nvalues of TAG include :source-registry and :output-translations.\"\n    (let ((files (sort (ignore-errors ;; SORT w/o COPY-LIST is OK: DIRECTORY returns a fresh list\n                        (remove-if\n                         'hidden-pathname-p\n                         (directory* (make-pathname :name *wild* :type \"conf\" :defaults directory))))\n                       #'string< :key #'namestring)))\n      `(,tag\n        ,@(loop :for file :in files :append\n                                    (loop :with ignore-invalid-p = nil\n                                          :for form :in (read-file-forms file)\n                                          :when (eq form :ignore-invalid-entries)\n                                            :do (setf ignore-invalid-p t)\n                                          :else\n                                            :when (funcall validator form)\n                                              :collect form\n                                          :else\n                                            :when ignore-invalid-p\n                                              :do (setf *ignored-configuration-form* t)\n                                          :else\n                                            :do (report-invalid-form invalid-form-reporter :form form :location file)))\n        :inherit-configuration)))\n\n  (defun resolve-relative-location (x &key ensure-directory wilden)\n    \"Given a designator X for an relative location, resolve it to a pathname.\"\n    (ensure-pathname\n     (etypecase x\n       (null nil)\n       (pathname x)\n       (string (parse-unix-namestring\n                x :ensure-directory ensure-directory))\n       (cons\n        (if (null (cdr x))\n            (resolve-relative-location\n             (car x) :ensure-directory ensure-directory :wilden wilden)\n            (let* ((car (resolve-relative-location\n                         (car x) :ensure-directory t :wilden nil)))\n              (merge-pathnames*\n               (resolve-relative-location\n                (cdr x) :ensure-directory ensure-directory :wilden wilden)\n               car))))\n       ((eql :*/) *wild-directory*)\n       ((eql :**/) *wild-inferiors*)\n       ((eql :*.*.*) *wild-file*)\n       ((eql :implementation)\n        (parse-unix-namestring\n         (implementation-identifier) :ensure-directory t))\n       ((eql :implementation-type)\n        (parse-unix-namestring\n         (string-downcase (implementation-type)) :ensure-directory t))\n       ((eql :hostname)\n        (parse-unix-namestring (hostname) :ensure-directory t)))\n     :wilden (and wilden (not (pathnamep x)) (not (member x '(:*/ :**/ :*.*.*))))\n     :want-relative t))\n\n  (defvar *here-directory* nil\n    \"This special variable is bound to the currect directory during calls to\nPROCESS-SOURCE-REGISTRY in order that we be able to interpret the :here\ndirective.\")\n\n  (defvar *user-cache* nil\n    \"A specification as per RESOLVE-LOCATION of where the user keeps his FASL cache\")\n\n  (defun resolve-absolute-location (x &key ensure-directory wilden)\n    \"Given a designator X for an absolute location, resolve it to a pathname\"\n    (ensure-pathname\n     (etypecase x\n       (null nil)\n       (pathname x)\n       (string\n        (let ((p #-mcl (parse-namestring x)\n                 #+mcl (probe-posix x)))\n          #+mcl (unless p (error \"POSIX pathname ~S does not exist\" x))\n          (if ensure-directory (ensure-directory-pathname p) p)))\n       (cons\n        (return-from resolve-absolute-location\n          (if (null (cdr x))\n              (resolve-absolute-location\n               (car x) :ensure-directory ensure-directory :wilden wilden)\n              (merge-pathnames*\n               (resolve-relative-location\n                (cdr x) :ensure-directory ensure-directory :wilden wilden)\n               (resolve-absolute-location\n                (car x) :ensure-directory t :wilden nil)))))\n       ((eql :root)\n        ;; special magic! we return a relative pathname,\n        ;; but what it means to the output-translations is\n        ;; \"relative to the root of the source pathname's host and device\".\n        (return-from resolve-absolute-location\n          (let ((p (make-pathname :directory '(:relative))))\n            (if wilden (wilden p) p))))\n       ((eql :home) (user-homedir-pathname))\n       ((eql :here) (resolve-absolute-location\n                     (or *here-directory* (pathname-directory-pathname (load-pathname)))\n                     :ensure-directory t :wilden nil))\n       ((eql :user-cache) (resolve-absolute-location\n                           *user-cache* :ensure-directory t :wilden nil)))\n     :wilden (and wilden (not (pathnamep x)))\n     :resolve-symlinks *resolve-symlinks*\n     :want-absolute t))\n\n  ;; Try to override declaration in previous versions of ASDF.\n  (declaim (ftype (function (t &key (:directory boolean) (:wilden boolean)\n                               (:ensure-directory boolean)) t) resolve-location))\n\n  (defun* (resolve-location) (x &key ensure-directory wilden directory)\n    \"Resolve location designator X into a PATHNAME\"\n    ;; :directory backward compatibility, until 2014-01-16: accept directory as well as ensure-directory\n    (loop* :with dirp = (or directory ensure-directory)\n           :with (first . rest) = (if (atom x) (list x) x)\n           :with path = (or (resolve-absolute-location\n                             first :ensure-directory (and (or dirp rest) t)\n                                   :wilden (and wilden (null rest)))\n                            (return nil))\n           :for (element . morep) :on rest\n           :for dir = (and (or morep dirp) t)\n           :for wild = (and wilden (not morep))\n           :for sub = (merge-pathnames*\n                       (resolve-relative-location\n                        element :ensure-directory dir :wilden wild)\n                       path)\n           :do (setf path (if (absolute-pathname-p sub) (resolve-symlinks* sub) sub))\n           :finally (return path)))\n\n  (defun location-designator-p (x)\n    \"Is X a designator for a location?\"\n    ;; NIL means \"skip this entry\", or as an output translation, same as translation input.\n    ;; T means \"any input\" for a translation, or as output, same as translation input.\n    (flet ((absolute-component-p (c)\n             (typep c '(or string pathname\n                        (member :root :home :here :user-cache))))\n           (relative-component-p (c)\n             (typep c '(or string pathname\n                        (member :*/ :**/ :*.*.* :implementation :implementation-type)))))\n      (or (typep x 'boolean)\n          (absolute-component-p x)\n          (and (consp x) (absolute-component-p (first x)) (every #'relative-component-p (rest x))))))\n\n  (defun location-function-p (x)\n    \"Is X the specification of a location function?\"\n    ;; Location functions are allowed in output translations, and notably used by ABCL for JAR file support.\n    (and (length=n-p x 2) (eq (car x) :function)))\n\n  (defvar *clear-configuration-hook* '())\n\n  (defun register-clear-configuration-hook (hook-function &optional call-now-p)\n    \"Register a function to be called when clearing configuration\"\n    (register-hook-function '*clear-configuration-hook* hook-function call-now-p))\n\n  (defun clear-configuration ()\n    \"Call the functions in *CLEAR-CONFIGURATION-HOOK*\"\n    (call-functions *clear-configuration-hook*))\n\n  (register-image-dump-hook 'clear-configuration)\n\n  (defun upgrade-configuration ()\n    \"If a previous version of ASDF failed to read some configuration, try again now.\"\n    (when *ignored-configuration-form*\n      (clear-configuration)\n      (setf *ignored-configuration-form* nil)))\n\n\n  (defun get-folder-path (folder)\n    \"Semi-portable implementation of a subset of LispWorks' sys:get-folder-path,\nthis function tries to locate the Windows FOLDER for one of\n:LOCAL-APPDATA, :APPDATA or :COMMON-APPDATA.\n     Returns NIL when the folder is not defined (e.g., not on Windows).\"\n    (or #+(and lispworks os-windows) (sys:get-folder-path folder)\n        ;; read-windows-registry HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\AppData\n        (ecase folder\n          (:local-appdata (or (getenv-absolute-directory \"LOCALAPPDATA\")\n                              (subpathname* (get-folder-path :appdata) \"Local\")))\n          (:appdata (getenv-absolute-directory \"APPDATA\"))\n          (:common-appdata (or (getenv-absolute-directory \"ALLUSERSAPPDATA\")\n                               (subpathname* (getenv-absolute-directory \"ALLUSERSPROFILE\") \"Application Data/\"))))))\n\n\n  ;; Support for the XDG Base Directory Specification\n  (defun xdg-data-home (&rest more)\n    \"Returns an absolute pathname for the directory containing user-specific data files.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (resolve-absolute-location\n     `(,(or (getenv-absolute-directory \"XDG_DATA_HOME\")\n            (os-cond\n             ((os-windows-p) (get-folder-path :local-appdata))\n             (t (subpathname (user-homedir-pathname) \".local/share/\"))))\n       ,more)))\n\n  (defun xdg-config-home (&rest more)\n    \"Returns a pathname for the directory containing user-specific configuration files.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (resolve-absolute-location\n     `(,(or (getenv-absolute-directory \"XDG_CONFIG_HOME\")\n            (os-cond\n             ((os-windows-p) (xdg-data-home \"config/\"))\n             (t (subpathname (user-homedir-pathname) \".config/\"))))\n       ,more)))\n\n  (defun xdg-data-dirs (&rest more)\n    \"The preference-ordered set of additional paths to search for data files.\nReturns a list of absolute directory pathnames.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (mapcar #'(lambda (d) (resolve-location `(,d ,more)))\n            (or (remove nil (getenv-absolute-directories \"XDG_DATA_DIRS\"))\n                (os-cond\n                 ((os-windows-p) (mapcar 'get-folder-path '(:appdata :common-appdata)))\n                 (t (mapcar 'parse-unix-namestring '(\"/usr/local/share/\" \"/usr/share/\")))))))\n\n  (defun xdg-config-dirs (&rest more)\n    \"The preference-ordered set of additional base paths to search for configuration files.\nReturns a list of absolute directory pathnames.\nMORE may contain specifications for a subpath relative to these directories:\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (mapcar #'(lambda (d) (resolve-location `(,d ,more)))\n            (or (remove nil (getenv-absolute-directories \"XDG_CONFIG_DIRS\"))\n                (os-cond\n                 ((os-windows-p) (xdg-data-dirs \"config/\"))\n                 (t (mapcar 'parse-unix-namestring '(\"/etc/xdg/\")))))))\n\n  (defun xdg-cache-home (&rest more)\n    \"The base directory relative to which user specific non-essential data files should be stored.\nReturns an absolute directory pathname.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (resolve-absolute-location\n     `(,(or (getenv-absolute-directory \"XDG_CACHE_HOME\")\n            (os-cond\n             ((os-windows-p) (xdg-data-home \"cache/\"))\n             (t (subpathname* (user-homedir-pathname) \".cache/\"))))\n       ,more)))\n\n  (defun xdg-runtime-dir (&rest more)\n    \"Pathname for user-specific non-essential runtime files and other file objects,\nsuch as sockets, named pipes, etc.\nReturns an absolute directory pathname.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    ;; The XDG spec says that if not provided by the login system, the application should\n    ;; issue a warning and provide a replacement. UIOP is not equipped to do that and returns NIL.\n    (resolve-absolute-location `(,(getenv-absolute-directory \"XDG_RUNTIME_DIR\") ,more)))\n\n  ;;; NOTE: modified the docstring because \"system user configuration\n  ;;; directories\" seems self-contradictory. I'm not sure my wording is right.\n  (defun system-config-pathnames (&rest more)\n    \"Return a list of directories where are stored the system's default user configuration information.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (declare (ignorable more))\n    (os-cond\n     ((os-unix-p) (list (resolve-absolute-location `(,(parse-unix-namestring \"/etc/\") ,more))))))\n\n  (defun filter-pathname-set (dirs)\n    \"Parse strings as unix namestrings and remove duplicates and non absolute-pathnames in a list.\"\n    (remove-duplicates (remove-if-not #'absolute-pathname-p dirs) :from-end t :test 'equal))\n\n  (defun xdg-data-pathnames (&rest more)\n    \"Return a list of absolute pathnames for application data directories.  With APP,\nreturns directory for data for that application, without APP, returns the set of directories\nfor storing all application configurations.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (filter-pathname-set\n     `(,(xdg-data-home more)\n       ,@(xdg-data-dirs more))))\n\n  (defun xdg-config-pathnames (&rest more)\n    \"Return a list of pathnames for application configuration.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (filter-pathname-set\n     `(,(xdg-config-home more)\n       ,@(xdg-config-dirs more))))\n\n  (defun find-preferred-file (files &key (direction :input))\n    \"Find first file in the list of FILES that exists (for direction :input or :probe)\nor just the first one (for direction :output or :io).\n    Note that when we say \\\"file\\\" here, the files in question may be directories.\"\n    (find-if (ecase direction ((:probe :input) 'probe-file*) ((:output :io) 'identity)) files))\n\n  (defun xdg-data-pathname (&optional more (direction :input))\n    (find-preferred-file (xdg-data-pathnames more) :direction direction))\n\n  (defun xdg-config-pathname (&optional more (direction :input))\n    (find-preferred-file (xdg-config-pathnames more) :direction direction))\n\n  (defun compute-user-cache ()\n    \"Compute (and return) the location of the default user-cache for translate-output\nobjects. Side-effects for cached file location computation.\"\n    (setf *user-cache* (xdg-cache-home \"common-lisp\" :implementation)))\n  (register-image-restore-hook 'compute-user-cache))\n;;; -------------------------------------------------------------------------\n;;; Hacks for backward-compatibility with older versions of UIOP\n\n(uiop/package:define-package :uiop/backward-driver\n  (:recycle :uiop/backward-driver :asdf/backward-driver :uiop)\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/version\n   :uiop/pathname :uiop/stream :uiop/os :uiop/image\n   :uiop/run-program :uiop/lisp-build :uiop/configuration)\n  (:export\n   #:coerce-pathname\n   #:user-configuration-directories #:system-configuration-directories\n   #:in-first-directory #:in-user-configuration-directory #:in-system-configuration-directory\n   #:version-compatible-p))\n(in-package :uiop/backward-driver)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n(with-deprecation ((version-deprecation *uiop-version* :style-warning \"3.2\"))\n  ;; Backward compatibility with ASDF 2.000 to 2.26\n\n  ;; For backward-compatibility only, for people using internals\n  ;; Reported users in quicklisp 2015-11: hu.dwim.asdf (removed in next release)\n  ;; Will be removed after 2015-12.\n  (defun coerce-pathname (name &key type defaults)\n    \"DEPRECATED. Please use UIOP:PARSE-UNIX-NAMESTRING instead.\"\n    (parse-unix-namestring name :type type :defaults defaults))\n\n  ;; Backward compatibility for ASDF 2.27 to 3.1.4\n  (defun user-configuration-directories ()\n    \"Return the current user's list of user configuration directories\nfor configuring common-lisp.\nDEPRECATED. Use UIOP:XDG-CONFIG-PATHNAMES instead.\"\n    (xdg-config-pathnames \"common-lisp\"))\n  (defun system-configuration-directories ()\n    \"Return the list of system configuration directories for common-lisp.\nDEPRECATED. Use UIOP:CONFIG-SYSTEM-PATHNAMES instead.\"\n    (system-config-pathnames \"common-lisp\"))\n  (defun in-first-directory (dirs x &key (direction :input))\n    \"Finds the first appropriate file named X in the list of DIRS for I/O\nin DIRECTION \\(which may be :INPUT, :OUTPUT, :IO, or :PROBE).\nIf direction is :INPUT or :PROBE, will return the first extant file named\nX in one of the DIRS.\nIf direction is :OUTPUT or :IO, will simply return the file named X in the\nfirst element of DIRS that exists. DEPRECATED.\"\n    (find-preferred-file\n     (mapcar #'(lambda (dir) (subpathname (ensure-directory-pathname dir) x)) dirs)\n     :direction direction))\n  (defun in-user-configuration-directory (x &key (direction :input))\n    \"Return the file named X in the user configuration directory for common-lisp.\nDEPRECATED.\"\n    (xdg-config-pathname `(\"common-lisp\" ,x) direction))\n  (defun in-system-configuration-directory (x &key (direction :input))\n    \"Return the pathname for the file named X under the system configuration directory\nfor common-lisp. DEPRECATED.\"\n    (find-preferred-file (system-config-pathnames \"common-lisp\" x) :direction direction))\n\n\n  ;; Backward compatibility with ASDF 1 to ASDF 2.32\n\n  (defun version-compatible-p (provided-version required-version)\n    \"Is the provided version a compatible substitution for the required-version?\nIf major versions differ, it's not compatible.\nIf they are equal, then any later version is compatible,\nwith later being determined by a lexicographical comparison of minor numbers.\nDEPRECATED.\"\n    (let ((x (parse-version provided-version nil))\n          (y (parse-version required-version nil)))\n      (and x y (= (car x) (car y)) (lexicographic<= '< (cdr y) (cdr x)))))))\n\n;;;; ---------------------------------------------------------------------------\n;;;; Re-export all the functionality in UIOP\n\n(uiop/package:define-package :uiop/driver\n  (:nicknames :uiop :asdf/driver) ;; asdf/driver is obsolete (uiop isn't);\n  ;; but asdf/driver is still used by swap-bytes, static-vectors.\n  (:use :uiop/common-lisp)\n   ;; NB: not reexporting uiop/common-lisp\n   ;; which include all of CL with compatibility modifications on select platforms,\n   ;; that could cause potential conflicts for packages that would :use (cl uiop)\n   ;; or :use (closer-common-lisp uiop), etc.\n  (:use-reexport\n   :uiop/package :uiop/utility :uiop/version\n   :uiop/os :uiop/pathname :uiop/filesystem :uiop/stream :uiop/image\n   :uiop/launch-program :uiop/run-program\n   :uiop/lisp-build :uiop/configuration :uiop/backward-driver))\n\n;; Provide both lowercase and uppercase, to satisfy more people.\n(provide \"uiop\") (provide \"UIOP\")\n;;;; -------------------------------------------------------------------------\n;;;; Handle upgrade as forward- and backward-compatibly as possible\n;; See https://bugs.launchpad.net/asdf/+bug/485687\n\n(uiop/package:define-package :asdf/upgrade\n  (:recycle :asdf/upgrade :asdf)\n  (:use :uiop/common-lisp :uiop)\n  (:export\n   #:asdf-version #:*previous-asdf-versions* #:*asdf-version*\n   #:asdf-message #:*verbose-out*\n   #:upgrading-p #:when-upgrading #:upgrade-asdf #:defparameter*\n   #:*post-upgrade-cleanup-hook* #:cleanup-upgraded-asdf\n   ;; There will be no symbol left behind!\n   #:with-asdf-deprecation\n   #:intern*)\n  (:import-from :uiop/package #:intern* #:find-symbol*))\n(in-package :asdf/upgrade)\n\n;;; Special magic to detect if this is an upgrade\n\n(with-upgradability ()\n  (defun asdf-version ()\n    \"Exported interface to the version of ASDF currently installed. A string.\nYou can compare this string with e.g.: (ASDF:VERSION-SATISFIES (ASDF:ASDF-VERSION) \\\"3.4.5.67\\\").\"\n    (when (find-package :asdf)\n      (or (symbol-value (find-symbol (string :*asdf-version*) :asdf))\n          (let* ((revsym (find-symbol (string :*asdf-revision*) :asdf))\n                 (rev (and revsym (boundp revsym) (symbol-value revsym))))\n            (etypecase rev\n              (string rev)\n              (cons (format nil \"~{~D~^.~}\" rev))\n              (null \"1.0\"))))))\n  ;; This (private) variable contains a list of versions of previously loaded variants of ASDF,\n  ;; from which ASDF was upgraded.\n  ;; Important: define *p-a-v* /before/ *a-v* so that they initialize correctly.\n  (defvar *previous-asdf-versions*\n    (let ((previous (asdf-version)))\n      (when previous\n        ;; Punt on upgrade from ASDF1 or ASDF2, by renaming (or deleting) the package.\n        (when (version< previous \"2.27\") ;; 2.27 is the first to have the :asdf3 feature.\n          (let ((away (format nil \"~A-~A\" :asdf previous)))\n            (rename-package :asdf away)\n            (when *load-verbose*\n              (format t \"~&; Renamed old ~A package away to ~A~%\" :asdf away))))\n        (list previous))))\n  ;; This public variable will be bound shortly to the currently loaded version of ASDF.\n  (defvar *asdf-version* nil)\n  ;; We need to clear systems from versions older than the one in this (private) parameter.\n  ;; The latest incompatible defclass is 2.32.13 renaming a slot in component,\n  ;; or 3.2.0.2 for CCL (incompatibly changing some superclasses).\n  ;; the latest incompatible gf change is in 3.1.7.20 (see redefined-functions below).\n  (defparameter *oldest-forward-compatible-asdf-version* \"3.2.0.2\")\n  ;; Semi-private variable: a designator for a stream on which to output ASDF progress messages\n  (defvar *verbose-out* nil)\n  ;; Private function by which ASDF outputs progress messages and warning messages:\n  (defun asdf-message (format-string &rest format-args)\n    (when *verbose-out* (apply 'format *verbose-out* format-string format-args)))\n  ;; Private hook for functions to run after ASDF has upgraded itself from an older variant:\n  (defvar *post-upgrade-cleanup-hook* ())\n  ;; Private function to detect whether the current upgrade counts as an incompatible\n  ;; data schema upgrade implying the need to drop data.\n  (defun upgrading-p (&optional (oldest-compatible-version *oldest-forward-compatible-asdf-version*))\n    (and *previous-asdf-versions*\n         (version< (first *previous-asdf-versions*) oldest-compatible-version)))\n  ;; Private variant of defparameter that works in presence of incompatible upgrades:\n  ;; behaves like defvar in a compatible upgrade (e.g. reloading system after simple code change),\n  ;; but behaves like defparameter if in presence of an incompatible upgrade.\n  (defmacro defparameter* (var value &optional docstring (version *oldest-forward-compatible-asdf-version*))\n    (let* ((name (string-trim \"*\" var))\n           (valfun (intern (format nil \"%~A-~A-~A\" :compute name :value))))\n      `(progn\n         (defun ,valfun () ,value)\n         (defvar ,var (,valfun) ,@(ensure-list docstring))\n         (when (upgrading-p ,version)\n           (setf ,var (,valfun))))))\n  ;; Private macro to declare sections of code that are only compiled and run when upgrading.\n  ;; The use of eval portably ensures that the code will not have adverse compile-time side-effects,\n  ;; whereas the use of handler-bind portably ensures that it will not issue warnings when it runs.\n  (defmacro when-upgrading ((&key (version *oldest-forward-compatible-asdf-version*)\n                               (upgrading-p `(upgrading-p ,version)) when) &body body)\n    \"A wrapper macro for code that should only be run when upgrading a\npreviously-loaded version of ASDF.\"\n    `(with-upgradability ()\n       (when (and ,upgrading-p ,@(when when `(,when)))\n         (handler-bind ((style-warning #'muffle-warning))\n           (eval '(progn ,@body))))))\n  ;; Only now can we safely update the version.\n  (let* (;; For bug reporting sanity, please always bump this version when you modify this file.\n         ;; Please also modify asdf.asd to reflect this change. make bump-version v=3.4.5.67.8\n         ;; can help you do these changes in synch (look at the source for documentation).\n         ;; Relying on its automation, the version is now redundantly present on top of asdf.lisp.\n         ;; \"3.4\" would be the general branch for major version 3, minor version 4.\n         ;; \"3.4.5\" would be an official release in the 3.4 branch.\n         ;; \"3.4.5.67\" would be a development version in the official branch, on top of 3.4.5.\n         ;; \"3.4.5.0.8\" would be your eighth local modification of official release 3.4.5\n         ;; \"3.4.5.67.8\" would be your eighth local modification of development version 3.4.5.67\n         (asdf-version \"3.2.1\")\n         (existing-version (asdf-version)))\n    (setf *asdf-version* asdf-version)\n    (when (and existing-version (not (equal asdf-version existing-version)))\n      (push existing-version *previous-asdf-versions*)\n      (when (or *verbose-out* *load-verbose*)\n        (format (or *verbose-out* *trace-output*)\n                (compatfmt \"~&~@<; ~@;Upgrading ASDF ~@[from version ~A ~]to version ~A~@:>~%\")\n                existing-version asdf-version)))))\n\n;;; Upon upgrade, specially frob some functions and classes that are being incompatibly redefined\n(when-upgrading ()\n  (let ((redefined-functions ;; List of functions that changes incompatibly since 2.27:\n         ;; gf signature changed (should NOT happen), defun that became a generic function,\n         ;; method removed that will mess up with new ones (especially :around :before :after,\n         ;; more specific or call-next-method'ed method) and/or semantics otherwise modified. Oops.\n         ;; NB: it's too late to do anything about functions in UIOP!\n         ;; If you introduce some critical incompatibility there, you must change the function name.\n         ;; Note that we don't need do anything about functions that changed incompatibly\n         ;; from ASDF 2.26 or earlier: we wholly punt on the entire ASDF package in such an upgrade.\n         ;; Also note that we don't include the defgeneric=>defun, because they are\n         ;; done directly with defun* and need not trigger a punt on data.\n         ;; See discussion at https://gitlab.common-lisp.net/asdf/asdf/merge_requests/36\n         '(#:component-depends-on #:input-files ;; methods removed before 3.1.2\n           #:find-component ;; gf modified in 3.1.7.20\n           ))\n        (redefined-classes\n         ;; redefining the classes causes interim circularities\n         ;; with the old ASDF during upgrade, and many implementations bork\n         #-clozure ()\n         #+clozure\n         '((#:compile-concatenated-source-op (#:operation) ())\n           (#:compile-bundle-op (#:operation) ())\n           (#:concatenate-source-op (#:operation) ())\n           (#:dll-op (#:operation) ())\n           (#:lib-op (#:operation) ())\n           (#:monolithic-compile-bundle-op (#:operation) ())\n           (#:monolithic-concatenate-source-op (#:operation) ()))))\n    (loop :for name :in redefined-functions\n      :for sym = (find-symbol* name :asdf nil)\n      :do (when sym (fmakunbound sym)))\n    (labels ((asym (x) (multiple-value-bind (s p)\n                           (if (consp x) (values (car x) (cadr x)) (values x :asdf))\n                         (find-symbol* s p nil)))\n             (asyms (l) (mapcar #'asym l)))\n      (loop* :for (name superclasses slots) :in redefined-classes\n             :for sym = (find-symbol* name :asdf nil)\n             :when (and sym (find-class sym))\n             :do (eval `(defclass ,sym ,(asyms superclasses) ,(asyms slots)))))))\n\n\n;;; Self-upgrade functions\n(with-upgradability ()\n  ;; This private function is called at the end of asdf/footer and ensures that,\n  ;; *if* this loading of ASDF was an upgrade, then all registered cleanup functions will be called.\n  (defun cleanup-upgraded-asdf (&optional (old-version (first *previous-asdf-versions*)))\n    (let ((new-version (asdf-version)))\n      (unless (equal old-version new-version)\n        (push new-version *previous-asdf-versions*)\n        (when old-version\n          (if (version<= new-version old-version)\n              (error (compatfmt \"~&~@<; ~@;Downgraded ASDF from version ~A to version ~A~@:>~%\")\n                     old-version new-version)\n              (asdf-message (compatfmt \"~&~@<; ~@;Upgraded ASDF from version ~A to version ~A~@:>~%\")\n                            old-version new-version))\n          ;; In case the previous version was too old to be forward-compatible, clear systems.\n          ;; TODO: if needed, we may have to define a separate hook to run\n          ;; in case of forward-compatible upgrade.\n          ;; Or to move the tests forward-compatibility test inside each hook function?\n          (unless (version<= *oldest-forward-compatible-asdf-version* old-version)\n            (call-functions (reverse *post-upgrade-cleanup-hook*)))\n          t))))\n\n  (defun upgrade-asdf ()\n    \"Try to upgrade of ASDF. If a different version was used, return T.\n   We need do that before we operate on anything that may possibly depend on ASDF.\"\n    (let ((*load-print* nil)\n          (*compile-print* nil))\n      (handler-bind (((or style-warning) #'muffle-warning))\n        (symbol-call :asdf :load-system :asdf :verbose nil))))\n\n  (defmacro with-asdf-deprecation ((&rest keys &key &allow-other-keys) &body body)\n    `(with-upgradability ()\n       (with-deprecation ((version-deprecation *asdf-version* ,@keys))\n         ,@body))))\n;;;; -------------------------------------------------------------------------\n;;;; Session cache\n\n(uiop/package:define-package :asdf/cache\n  (:use :uiop/common-lisp :uiop :asdf/upgrade)\n  (:export #:get-file-stamp #:compute-file-stamp #:register-file-stamp\n           #:set-asdf-cache-entry #:unset-asdf-cache-entry #:consult-asdf-cache\n           #:do-asdf-cache #:normalize-namestring\n           #:call-with-asdf-cache #:with-asdf-cache #:*asdf-cache*\n           #:clear-configuration-and-retry #:retry))\n(in-package :asdf/cache)\n\n;;; The ASDF session cache is used to memoize some computations. It is instrumental in achieving:\n;; * Consistency in the view of the world relied on by ASDF within a given session.\n;;   Inconsistencies in file stamps, system definitions, etc., could cause infinite loops\n;;   (a.k.a. stack overflows) and other erratic behavior.\n;; * Speed and reliability of ASDF, with fewer side-effects from access to the filesystem, and\n;;   no expensive recomputations of transitive dependencies for some input-files or output-files.\n;; * Testability of ASDF with the ability to fake timestamps without actually touching files.\n\n(with-upgradability ()\n  ;; The session cache variable.\n  ;; NIL when outside a session, an equal hash-table when inside a session.\n  (defvar *asdf-cache* nil)\n\n  ;; Set a session cache entry for KEY to a list of values VALUE-LIST, when inside a session.\n  ;; Return those values.\n  (defun set-asdf-cache-entry (key value-list)\n    (values-list (if *asdf-cache*\n                     (setf (gethash key *asdf-cache*) value-list)\n                     value-list)))\n\n  ;; Unset the session cache entry for KEY, when inside a session.\n  (defun unset-asdf-cache-entry (key)\n    (when *asdf-cache*\n      (remhash key *asdf-cache*)))\n\n  ;; Consult the session cache entry for KEY if present and in a session;\n  ;; if not present, compute it by calling the THUNK,\n  ;; and set the session cache entry accordingly, if in a session.\n  ;; Return the values from the cache and/or the thunk computation.\n  (defun consult-asdf-cache (key &optional thunk)\n    (if *asdf-cache*\n        (multiple-value-bind (results foundp) (gethash key *asdf-cache*)\n          (if foundp\n              (values-list results)\n              (set-asdf-cache-entry key (multiple-value-list (call-function thunk)))))\n        (call-function thunk)))\n\n  ;; Syntactic sugar for consult-asdf-cache\n  (defmacro do-asdf-cache (key &body body)\n    `(consult-asdf-cache ,key #'(lambda () ,@body)))\n\n  ;; Compute inside a ASDF session with a cache.\n  ;; First, make sure an ASDF session is underway, by binding the session cache variable\n  ;; to a new hash-table if it's currently null (or even if it isn't, if OVERRIDE is true).\n  ;; Second, if a new session was started, establish restarts for retrying the overall computation.\n  ;; Finally, consult the cache if a KEY was specified with the THUNK as a fallback when the cache\n  ;; entry isn't found, or just call the THUNK if no KEY was specified.\n  (defun call-with-asdf-cache (thunk &key override key)\n    (let ((fun (if key #'(lambda () (consult-asdf-cache key thunk)) thunk)))\n      (if (and *asdf-cache* (not override))\n          (funcall fun)\n          (loop\n            (restart-case\n                (let ((*asdf-cache* (make-hash-table :test 'equal)))\n                  (return (funcall fun)))\n              (retry ()\n                :report (lambda (s)\n                          (format s (compatfmt \"~@<Retry ASDF operation.~@:>\"))))\n              (clear-configuration-and-retry ()\n                :report (lambda (s)\n                          (format s (compatfmt \"~@<Retry ASDF operation after resetting the configuration.~@:>\")))\n                (clear-configuration)))))))\n\n  ;; Syntactic sugar for call-with-asdf-cache\n  (defmacro with-asdf-cache ((&key key override) &body body)\n    `(call-with-asdf-cache #'(lambda () ,@body) :override ,override :key ,key))\n\n\n  ;;; Define specific accessor for file (date) stamp.\n\n  ;; Normalize a namestring for use as a key in the session cache.\n  (defun normalize-namestring (pathname)\n    (let ((resolved (resolve-symlinks*\n                     (ensure-absolute-pathname\n                      (physicalize-pathname pathname)\n                      'get-pathname-defaults))))\n      (with-pathname-defaults () (namestring resolved))))\n\n  ;; Compute the file stamp for a normalized namestring\n  (defun compute-file-stamp (normalized-namestring)\n    (with-pathname-defaults ()\n      (safe-file-write-date normalized-namestring)))\n\n  ;; Override the time STAMP associated to a given FILE in the session cache.\n  ;; If no STAMP is specified, recompute a new one from the filesystem.\n  (defun register-file-stamp (file &optional (stamp nil stampp))\n    (let* ((namestring (normalize-namestring file))\n           (stamp (if stampp stamp (compute-file-stamp namestring))))\n      (set-asdf-cache-entry `(get-file-stamp ,namestring) (list stamp))))\n\n  ;; Get or compute a memoized stamp for given FILE from the session cache.\n  (defun get-file-stamp (file)\n    (when file\n      (let ((namestring (normalize-namestring file)))\n        (do-asdf-cache `(get-file-stamp ,namestring) (compute-file-stamp namestring))))))\n\n;;;; -------------------------------------------------------------------------\n;;;; Components\n\n(uiop/package:define-package :asdf/component\n  (:recycle :asdf/component :asdf/defsystem :asdf/find-system :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade)\n  (:export\n   #:component #:component-find-path\n   #:component-name #:component-pathname #:component-relative-pathname\n   #:component-parent #:component-system #:component-parent-pathname\n   #:child-component #:parent-component #:module\n   #:file-component\n   #:source-file #:c-source-file #:java-source-file\n   #:static-file #:doc-file #:html-file\n   #:file-type\n   #:source-file-type #:source-file-explicit-type ;; backward-compatibility\n   #:component-in-order-to #:component-sideway-dependencies\n   #:component-if-feature #:around-compile-hook\n   #:component-description #:component-long-description\n   #:component-version #:version-satisfies\n   #:component-inline-methods ;; backward-compatibility only. DO NOT USE!\n   #:component-operation-times ;; For internal use only.\n   ;; portable ASDF encoding and implementation-specific external-format\n   #:component-external-format #:component-encoding\n   #:component-children-by-name #:component-children #:compute-children-by-name\n   #:component-build-operation\n   #:module-default-component-class\n   #:module-components ;; backward-compatibility. DO NOT USE.\n   #:sub-components\n\n   ;; conditions\n   #:system-definition-error ;; top level, moved here because this is the earliest place for it.\n   #:duplicate-names\n\n   ;; Internals we'd like to share with the ASDF package, especially for upgrade purposes\n   #:name #:version #:description #:long-description #:author #:maintainer #:licence\n   #:components-by-name #:components #:children #:children-by-name\n   #:default-component-class #:source-file\n   #:defsystem-depends-on ; This symbol retained for backward compatibility.\n   #:sideway-dependencies #:if-feature #:in-order-to #:inline-methods\n   #:relative-pathname #:absolute-pathname #:operation-times #:around-compile\n   #:%encoding #:properties #:component-properties #:parent))\n(in-package :asdf/component)\n\n(with-upgradability ()\n  (defgeneric component-name (component)\n    (:documentation \"Name of the COMPONENT, unique relative to its parent\"))\n  (defgeneric component-system (component)\n    (:documentation \"Top-level system containing the COMPONENT\"))\n  (defgeneric component-pathname (component)\n    (:documentation \"Pathname of the COMPONENT if any, or NIL.\"))\n  (defgeneric component-relative-pathname (component)\n    ;; in ASDF4, rename that to component-specified-pathname ?\n    (:documentation \"Specified pathname of the COMPONENT,\nintended to be merged with the pathname of that component's parent if any, using merged-pathnames*.\nDespite the function's name, the return value can be an absolute pathname, in which case the merge\nwill leave it unmodified.\"))\n  (defgeneric component-external-format (component)\n    (:documentation \"The external-format of the COMPONENT.\nBy default, deduced from the COMPONENT-ENCODING.\"))\n  (defgeneric component-encoding (component)\n    (:documentation \"The encoding of the COMPONENT. By default, only :utf-8 is supported.\nUse asdf-encodings to support more encodings.\"))\n  (defgeneric version-satisfies (component version)\n    (:documentation \"Check whether a COMPONENT satisfies the constraint of being at least as recent\nas the specified VERSION, which must be a string of dot-separated natural numbers, or NIL.\"))\n  (defgeneric component-version (component)\n    (:documentation \"Return the version of a COMPONENT, which must be a string of dot-separated\nnatural numbers, or NIL.\"))\n  (defgeneric (setf component-version) (new-version component)\n    (:documentation \"Updates the version of a COMPONENT, which must be a string of dot-separated\nnatural numbers, or NIL.\"))\n  (defgeneric component-parent (component)\n    (:documentation \"The parent of a child COMPONENT,\nor NIL for top-level components (a.k.a. systems)\"))\n  ;; NIL is a designator for the absence of a component, in which case the parent is also absent.\n  (defmethod component-parent ((component null)) nil)\n\n  ;; Deprecated: Backward compatible way of computing the FILE-TYPE of a component.\n  ;; TODO: find users, have them stop using that, remove it for ASDF4.\n  (defgeneric source-file-type (component system)\n    (:documentation \"DEPRECATED. Use the FILE-TYPE of a COMPONENT instead.\"))\n\n  (define-condition system-definition-error (error) ()\n    ;; [this use of :report should be redundant, but unfortunately it's not.\n    ;; cmucl's lisp::output-instance prefers the kernel:slot-class-print-function\n    ;; over print-object; this is always conditions::%print-condition for\n    ;; condition objects, which in turn does inheritance of :report options at\n    ;; run-time.  fortunately, inheritance means we only need this kludge here in\n    ;; order to fix all conditions that build on it.  -- rgr, 28-Jul-02.]\n    #+cmucl (:report print-object))\n\n  (define-condition duplicate-names (system-definition-error)\n    ((name :initarg :name :reader duplicate-names-name))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while defining system: multiple components are given same name ~S~@:>\")\n                       (duplicate-names-name c))))))\n\n\n(with-upgradability ()\n  (defclass component ()\n    ((name :accessor component-name :initarg :name :type string :documentation\n           \"Component name: designator for a string composed of portable pathname characters\")\n     ;; We might want to constrain version with\n     ;; :type (and string (satisfies parse-version))\n     ;; but we cannot until we fix all systems that don't use it correctly!\n     (version :accessor component-version :initarg :version :initform nil)\n     (description :accessor component-description :initarg :description :initform nil)\n     (long-description :accessor component-long-description :initarg :long-description :initform nil)\n     (sideway-dependencies :accessor component-sideway-dependencies :initform nil)\n     (if-feature :accessor component-if-feature :initform nil :initarg :if-feature)\n     ;; In the ASDF object model, dependencies exist between *actions*,\n     ;; where an action is a pair of an operation and a component.\n     ;; Dependencies are represented as alists of operations\n     ;; to a list where each entry is a pair of an operation and a list of component specifiers.\n     ;; Up until ASDF 2.26.9, there used to be two kinds of dependencies:\n     ;; in-order-to and do-first, each stored in its own slot. Now there is only in-order-to.\n     ;; in-order-to used to represent things that modify the filesystem (such as compiling a fasl)\n     ;; and do-first things that modify the current image (such as loading a fasl).\n     ;; These are now unified because we now correctly propagate timestamps between dependencies.\n     ;; Happily, no one seems to have used do-first too much (especially since until ASDF 2.017,\n     ;; anything you specified was overridden by ASDF itself anyway), but the name in-order-to remains.\n     ;; The names are bad, but they have been the official API since Dan Barlow's ASDF 1.52!\n     ;; LispWorks's defsystem has caused-by and requires for in-order-to and do-first respectively.\n     ;; Maybe rename the slots in ASDF? But that's not very backward-compatible.\n     ;; See our ASDF 2 paper for more complete explanations.\n     (in-order-to :initform nil :initarg :in-order-to\n                  :accessor component-in-order-to)\n     ;; Methods defined using the \"inline\" style inside a defsystem form:\n     ;; we store them here so we can delete them when the system is re-evaluated.\n     (inline-methods :accessor component-inline-methods :initform nil)\n     ;; ASDF4: rename it from relative-pathname to specified-pathname. It need not be relative.\n     ;; There is no initform and no direct accessor for this specified pathname,\n     ;; so we only access the information through appropriate methods, after it has been processed.\n     ;; Unhappily, some braindead systems directly access the slot. Make them stop before ASDF4.\n     (relative-pathname :initarg :pathname)\n     ;; The absolute-pathname is computed based on relative-pathname and parent pathname.\n     ;; The slot is but a cache used by component-pathname.\n     (absolute-pathname)\n     (operation-times :initform (make-hash-table)\n                      :accessor component-operation-times)\n     (around-compile :initarg :around-compile)\n     ;; Properties are for backward-compatibility with ASDF2 only. DO NOT USE!\n     (properties :accessor component-properties :initarg :properties\n                 :initform nil)\n     (%encoding :accessor %component-encoding :initform nil :initarg :encoding)\n     ;; For backward-compatibility, this slot is part of component rather than of child-component. ASDF4: stop it.\n     (parent :initarg :parent :initform nil :reader component-parent)\n     (build-operation\n      :initarg :build-operation :initform nil :reader component-build-operation))\n    (:documentation \"Base class for all components of a build\"))\n\n  (defun component-find-path (component)\n    \"Return a path from a root system to the COMPONENT.\nThe return value is a list of component NAMES; a list of strings.\"\n    (check-type component (or null component))\n    (reverse\n     (loop :for c = component :then (component-parent c)\n           :while c :collect (component-name c))))\n\n  (defmethod print-object ((c component) stream)\n    (print-unreadable-object (c stream :type t :identity nil)\n      (format stream \"~{~S~^ ~}\" (component-find-path c))))\n\n  (defmethod component-system ((component component))\n    (if-let (system (component-parent component))\n      (component-system system)\n      component)))\n\n\n;;;; Component hierarchy within a system\n;; The tree typically but not necessarily follows the filesystem hierarchy.\n(with-upgradability ()\n  (defclass child-component (component) ()\n    (:documentation \"A CHILD-COMPONENT is a COMPONENT that may be part of\na PARENT-COMPONENT.\"))\n\n  (defclass file-component (child-component)\n    ((type :accessor file-type :initarg :type)) ; no default\n    (:documentation \"a COMPONENT that represents a file\"))\n  (defclass source-file (file-component)\n    ((type :accessor source-file-explicit-type ;; backward-compatibility\n           :initform nil))) ;; NB: many systems have come to rely on this default.\n  (defclass c-source-file (source-file)\n    ((type :initform \"c\")))\n  (defclass java-source-file (source-file)\n    ((type :initform \"java\")))\n  (defclass static-file (source-file)\n    ((type :initform nil))\n    (:documentation \"Component for a file to be included as is in the build output\"))\n  (defclass doc-file (static-file) ())\n  (defclass html-file (doc-file)\n    ((type :initform \"html\")))\n\n  (defclass parent-component (component)\n    ((children\n      :initform nil\n      :initarg :components\n      :reader module-components ; backward-compatibility\n      :accessor component-children)\n     (children-by-name\n      :reader module-components-by-name ; backward-compatibility\n      :accessor component-children-by-name)\n     (default-component-class\n      :initform nil\n      :initarg :default-component-class\n      :accessor module-default-component-class))\n  (:documentation \"A PARENT-COMPONENT is a component that may have children.\")))\n\n(with-upgradability ()\n  ;; (Private) Function that given a PARENT component,\n  ;; the list of children of which has been initialized,\n  ;; compute the hash-table in slot children-by-name that allows to retrieve its children by name.\n  ;; If ONLY-IF-NEEDED-P is defined, skip any (re)computation if the slot is already populated.\n  (defun compute-children-by-name (parent &key only-if-needed-p)\n    (unless (and only-if-needed-p (slot-boundp parent 'children-by-name))\n      (let ((hash (make-hash-table :test 'equal)))\n        (setf (component-children-by-name parent) hash)\n        (loop :for c :in (component-children parent)\n              :for name = (component-name c)\n              :for previous = (gethash name hash)\n              :do (when previous (error 'duplicate-names :name name))\n                  (setf (gethash name hash) c))\n        hash))))\n\n(with-upgradability ()\n  (defclass module (child-component parent-component)\n    (#+clisp (components)) ;; backward compatibility during upgrade only\n    (:documentation \"A module is a intermediate component with both a parent and children,\ntypically but not necessarily representing the files in a subdirectory of the build source.\")))\n\n\n;;;; component pathnames\n(with-upgradability ()\n  (defgeneric component-parent-pathname (component)\n    (:documentation \"The pathname of the COMPONENT's parent, if any, or NIL\"))\n  (defmethod component-parent-pathname (component)\n    (component-pathname (component-parent component)))\n\n  ;; The default method for component-pathname tries to extract a cached precomputed\n  ;; absolute-pathname from the relevant slot, and if not, computes it by merging the\n  ;; component-relative-pathname (which should be component-specified-pathname, it can be absolute)\n  ;; with the directory of the component-parent-pathname.\n  (defmethod component-pathname ((component component))\n    (if (slot-boundp component 'absolute-pathname)\n        (slot-value component 'absolute-pathname)\n        (let ((pathname\n                (merge-pathnames*\n                 (component-relative-pathname component)\n                 (pathname-directory-pathname (component-parent-pathname component)))))\n          (unless (or (null pathname) (absolute-pathname-p pathname))\n            (error (compatfmt \"~@<Invalid relative pathname ~S for component ~S~@:>\")\n                   pathname (component-find-path component)))\n          (setf (slot-value component 'absolute-pathname) pathname)\n          pathname)))\n\n  ;; Default method for component-relative-pathname:\n  ;; combine the contents of slot relative-pathname (from specified initarg :pathname)\n  ;; with the appropriate source-file-type, which defaults to the file-type of the component.\n  (defmethod component-relative-pathname ((component component))\n    ;; SOURCE-FILE-TYPE below is strictly for backward-compatibility with ASDF1.\n    ;; We ought to be able to extract this from the component alone with FILE-TYPE.\n    ;; TODO: track who uses it in Quicklisp, and have them not use it anymore;\n    ;; maybe issue a WARNING (then eventually CERROR) if the two methods diverge?\n    (parse-unix-namestring\n     (or (and (slot-boundp component 'relative-pathname)\n              (slot-value component 'relative-pathname))\n         (component-name component))\n     :want-relative t\n     :type (source-file-type component (component-system component))\n     :defaults (component-parent-pathname component)))\n\n  (defmethod source-file-type ((component parent-component) (system parent-component))\n    :directory)\n\n  (defmethod source-file-type ((component file-component) (system parent-component))\n    (file-type component)))\n\n\n;;;; Encodings\n(with-upgradability ()\n  (defmethod component-encoding ((c component))\n    (or (loop :for x = c :then (component-parent x)\n              :while x :thereis (%component-encoding x))\n        (detect-encoding (component-pathname c))))\n\n  (defmethod component-external-format ((c component))\n    (encoding-external-format (component-encoding c))))\n\n\n;;;; around-compile-hook\n(with-upgradability ()\n  (defgeneric around-compile-hook (component)\n    (:documentation \"An optional hook function that will be called with one argument, a thunk.\nThe hook function must call the thunk, that will compile code from the component, and may or may not\nalso evaluate the compiled results. The hook function may establish dynamic variable bindings around\nthis compilation, or check its results, etc.\"))\n  (defmethod around-compile-hook ((c component))\n    (cond\n      ((slot-boundp c 'around-compile)\n       (slot-value c 'around-compile))\n      ((component-parent c)\n       (around-compile-hook (component-parent c))))))\n\n\n;;;; version-satisfies\n(with-upgradability ()\n  ;; short-circuit testing of null version specifications.\n  ;; this is an all-pass, without warning\n  (defmethod version-satisfies :around ((c t) (version null))\n    t)\n  (defmethod version-satisfies ((c component) version)\n    (unless (and version (slot-boundp c 'version) (component-version c))\n      (when version\n        (warn \"Requested version ~S but ~S has no version\" version c))\n      (return-from version-satisfies nil))\n    (version-satisfies (component-version c) version))\n\n  (defmethod version-satisfies ((cver string) version)\n    (version<= version cver)))\n\n\n;;; all sub-components (of a given type)\n(with-upgradability ()\n  (defun sub-components (component &key (type t))\n    \"Compute the transitive sub-components of given COMPONENT that are of given TYPE\"\n    (while-collecting (c)\n      (labels ((recurse (x)\n                 (when (if-let (it (component-if-feature x)) (featurep it) t)\n                   (when (typep x type)\n                     (c x))\n                   (when (typep x 'parent-component)\n                     (map () #'recurse (component-children x))))))\n        (recurse component)))))\n\n;;;; -------------------------------------------------------------------------\n;;;; Systems\n\n(uiop/package:define-package :asdf/system\n  (:recycle :asdf :asdf/system)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/component)\n  (:export\n   #:system #:proto-system\n   #:system-source-file #:system-source-directory #:system-relative-pathname\n   #:reset-system\n   #:system-description #:system-long-description\n   #:system-author #:system-maintainer #:system-licence #:system-license\n   #:system-defsystem-depends-on #:system-depends-on #:system-weakly-depends-on\n   #:component-build-pathname #:build-pathname\n   #:component-entry-point #:entry-point\n   #:homepage #:system-homepage\n   #:bug-tracker #:system-bug-tracker\n   #:mailto #:system-mailto\n   #:long-name #:system-long-name\n   #:source-control #:system-source-control\n   #:find-system #:builtin-system-p)) ;; forward-reference, defined in find-system\n(in-package :asdf/system)\n\n(with-upgradability ()\n  ;; The method is actually defined in asdf/find-system,\n  ;; but we declare the function here to avoid a forward reference.\n  (defgeneric find-system (system &optional error-p)\n    (:documentation \"Given a system designator, find the actual corresponding system object.\nIf no system is found, then signal an error if ERROR-P is true (the default), or else return NIL.\nA system designator is usually a string (conventionally all lowercase) or a symbol, designating\nthe same system as its downcased name; it can also be a system object (designating itself).\"))\n  (defgeneric system-source-file (system)\n    (:documentation \"Return the source file in which system is defined.\"))\n  ;; This is bad design, but was the easiest kluge I found to let the user specify that\n  ;; some special actions create outputs at locations controled by the user that are not affected\n  ;; by the usual output-translations.\n  ;; TODO: Fix operate to stop passing flags to operation (which in the current design shouldn't\n  ;; have any flags, since the stamp cache, etc., can't distinguish them), and instead insert\n  ;; *there* the ability of specifying special output paths, not in the system definition.\n  (defgeneric component-build-pathname (component)\n    (:documentation \"The COMPONENT-BUILD-PATHNAME, when defined and not null, specifies the\noutput pathname for the action using the COMPONENT-BUILD-OPERATION.\n\nNB: This interface is subject to change. Please contact ASDF maintainers if you use it.\"))\n\n  ;; TODO: Should this have been made a SYSTEM-ENTRY-POINT instead?\n  (defgeneric component-entry-point (component)\n    (:documentation \"The COMPONENT-ENTRY-POINT, when defined, specifies what function to call\n(with no argument) when running an image dumped from the COMPONENT.\n\nNB: This interface is subject to change. Please contact ASDF maintainers if you use it.\"))\n  (defmethod component-entry-point ((c component))\n    nil))\n\n\n;;;; The system class\n\n(with-upgradability ()\n  (defclass proto-system () ; slots to keep when resetting a system\n    ;; To preserve identity for all objects, we'd need keep the components slots\n    ;; but also to modify parse-component-form to reset the recycled objects.\n    ((name) (source-file) #|(children) (children-by-names)|#)\n    (:documentation \"PROTO-SYSTEM defines the elements of identity that are preserved when\na SYSTEM is redefined and its class is modified.\"))\n\n  (defclass system (module proto-system)\n    ;; Backward-compatibility: inherit from module. ASDF4: only inherit from parent-component.\n    (;; {,long-}description is now inherited from component, but we add the legacy accessors\n     (description :accessor system-description)\n     (long-description :accessor system-long-description)\n     (author :accessor system-author :initarg :author :initform nil)\n     (maintainer :accessor system-maintainer :initarg :maintainer :initform nil)\n     (licence :accessor system-licence :initarg :licence\n              :accessor system-license :initarg :license :initform nil)\n     (homepage :accessor system-homepage :initarg :homepage :initform nil)\n     (bug-tracker :accessor system-bug-tracker :initarg :bug-tracker :initform nil)\n     (mailto :accessor system-mailto :initarg :mailto :initform nil)\n     (long-name :accessor system-long-name :initarg :long-name :initform nil)\n     ;; Conventions for this slot aren't clear yet as of ASDF 2.27, but whenever they are, they will be enforced.\n     ;; I'm introducing the slot before the conventions are set for maximum compatibility.\n     (source-control :accessor system-source-control :initarg :source-control :initform nil)\n     (builtin-system-p :accessor builtin-system-p :initform nil :initarg :builtin-system-p)\n     (build-pathname\n      :initform nil :initarg :build-pathname :accessor component-build-pathname)\n     (entry-point\n      :initform nil :initarg :entry-point :accessor component-entry-point)\n     (source-file :initform nil :initarg :source-file :accessor system-source-file)\n     (defsystem-depends-on :reader system-defsystem-depends-on :initarg :defsystem-depends-on\n                           :initform nil)\n     ;; these two are specially set in parse-component-form, so have no :INITARGs.\n     (depends-on :reader system-depends-on :initform nil)\n     (weakly-depends-on :reader system-weakly-depends-on :initform nil))\n    (:documentation \"SYSTEM is the base class for top-level components that users may request\nASDF to build.\"))\n\n\n  (defun reset-system (system &rest keys &key &allow-other-keys)\n    \"Erase any data from a SYSTEM except its basic identity, then reinitialize it\nbased on supplied KEYS.\"\n    (change-class (change-class system 'proto-system) 'system)\n    (apply 'reinitialize-instance system keys)))\n\n\n;;;; Pathnames\n\n(with-upgradability ()\n  ;; Resolve a system designator to a system before extracting its system-source-file\n  (defmethod system-source-file ((system-name string))\n    (system-source-file (find-system system-name)))\n  (defmethod system-source-file ((system-name symbol))\n    (when system-name\n      (system-source-file (find-system system-name))))\n\n  (defun system-source-directory (system-designator)\n    \"Return a pathname object corresponding to the directory\nin which the system specification (.asd file) is located.\"\n    (pathname-directory-pathname (system-source-file system-designator)))\n\n  (defun* (system-relative-pathname) (system name &key type)\n    \"Given a SYSTEM, and a (Unix-style relative path) NAME of a file (or directory) of given TYPE,\nreturn the absolute pathname of a corresponding file under that system's source code pathname.\"\n    (subpathname (system-source-directory system) name :type type))\n\n  (defmethod component-pathname ((system system))\n    \"Given a SYSTEM, and a (Unix-style relative path) NAME of a file (or directory) of given TYPE,\nreturn the absolute pathname of a corresponding file under that system's source code pathname.\"\n    (let ((pathname (or (call-next-method) (system-source-directory system))))\n      (unless (and (slot-boundp system 'relative-pathname) ;; backward-compatibility with ASDF1-age\n                   (slot-value system 'relative-pathname)) ;; systems that directly access this slot.\n        (setf (slot-value system 'relative-pathname) pathname))\n      pathname))\n\n  ;; The default method of component-relative-pathname for a system:\n  ;; if a pathname was specified in the .asd file, it must be relative to the .asd file\n  ;; (actually, to its truename* if *resolve-symlinks* it true, the default).\n  ;; The method will return an *absolute* pathname, once again showing that the historical name\n  ;; component-relative-pathname is misleading and should have been component-specified-pathname.\n  (defmethod component-relative-pathname ((system system))\n    (parse-unix-namestring\n     (and (slot-boundp system 'relative-pathname)\n          (slot-value system 'relative-pathname))\n     :want-relative t\n     :type :directory\n     :ensure-absolute t\n     :defaults (system-source-directory system)))\n\n  ;; A system has no parent; if some method wants to make a path \"relative to its parent\",\n  ;; it will instead be relative to the system itself.\n  (defmethod component-parent-pathname ((system system))\n    (system-source-directory system))\n\n  ;; Most components don't have a specified component-build-pathname, and therefore\n  ;; no magic redirection of their output that disregards the output-translations.\n  (defmethod component-build-pathname ((c component))\n    nil))\n\n;;;; -------------------------------------------------------------------------\n;;;; Finding systems\n\n(uiop/package:define-package :asdf/find-system\n  (:recycle :asdf/find-system :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n    :asdf/cache :asdf/component :asdf/system)\n  (:export\n   #:remove-entry-from-registry #:coerce-entry-to-directory\n   #:coerce-name #:primary-system-name #:coerce-filename\n   #:find-system #:locate-system #:load-asd\n   #:system-registered-p #:registered-system #:register-system\n   #:registered-systems* #:registered-systems\n   #:clear-system #:map-systems\n   #:missing-component #:missing-requires #:missing-parent\n   #:formatted-system-definition-error #:format-control #:format-arguments #:sysdef-error\n   #:load-system-definition-error #:error-name #:error-pathname #:error-condition\n   #:*system-definition-search-functions* #:search-for-system-definition\n   #:*central-registry* #:probe-asd #:sysdef-central-registry-search\n   #:find-system-if-being-defined\n   #:contrib-sysdef-search #:sysdef-find-asdf ;; backward compatibility symbols, functions removed\n   #:sysdef-preloaded-system-search #:register-preloaded-system #:*preloaded-systems*\n   #:mark-component-preloaded ;; forward reference to asdf/operate\n   #:sysdef-immutable-system-search #:register-immutable-system #:*immutable-systems*\n   #:*defined-systems* #:clear-defined-systems\n   ;; defined in source-registry, but specially mentioned here:\n   #:initialize-source-registry #:sysdef-source-registry-search))\n(in-package :asdf/find-system)\n\n(with-upgradability ()\n  (declaim (ftype (function (&optional t) t) initialize-source-registry)) ; forward reference\n\n  (define-condition missing-component (system-definition-error)\n    ((requires :initform \"(unnamed)\" :reader missing-requires :initarg :requires)\n     (parent :initform nil :reader missing-parent :initarg :parent)))\n\n  (define-condition formatted-system-definition-error (system-definition-error)\n    ((format-control :initarg :format-control :reader format-control)\n     (format-arguments :initarg :format-arguments :reader format-arguments))\n    (:report (lambda (c s)\n               (apply 'format s (format-control c) (format-arguments c)))))\n\n  (define-condition load-system-definition-error (system-definition-error)\n    ((name :initarg :name :reader error-name)\n     (pathname :initarg :pathname :reader error-pathname)\n     (condition :initarg :condition :reader error-condition))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while trying to load definition for system ~A from pathname ~A: ~3i~_~A~@:>\")\n                       (error-name c) (error-pathname c) (error-condition c)))))\n\n  (defun sysdef-error (format &rest arguments)\n    (error 'formatted-system-definition-error :format-control\n           format :format-arguments arguments))\n\n\n  ;;; Canonicalizing system names\n\n  (defun coerce-name (name)\n    \"Given a designator for a component NAME, return the name as a string.\nThe designator can be a COMPONENT (designing its name; note that a SYSTEM is a component),\na SYMBOL (designing its name, downcased), or a STRING (designing itself).\"\n    (typecase name\n      (component (component-name name))\n      (symbol (string-downcase name))\n      (string name)\n      (t (sysdef-error (compatfmt \"~@<Invalid component designator: ~3i~_~A~@:>\") name))))\n\n  (defun primary-system-name (name)\n    \"Given a system designator NAME, return the name of the corresponding primary system,\nafter which the .asd file is named. That's the first component when dividing the name\nas a string by / slashes.\"\n    (first (split-string (coerce-name name) :separator \"/\")))\n\n  (defun coerce-filename (name)\n    \"Coerce a system designator NAME into a string suitable as a filename component.\nThe (current) transformation is to replace characters /:\\\\ each by --,\nthe former being forbidden in a filename component.\nNB: The onus is unhappily on the user to avoid clashes.\"\n    (frob-substrings (coerce-name name) '(\"/\" \":\" \"\\\\\") \"--\"))\n\n\n  ;;; Registry of Defined Systems\n\n  (defvar *defined-systems* (make-hash-table :test 'equal)\n    \"This is a hash table whose keys are strings -- the\nnames of systems -- and whose values are pairs, the first\nelement of which is a universal-time indicating when the\nsystem definition was last updated, and the second element\nof which is a system object.\n  A system is referred to as \\\"registered\\\" if it is present\nin this table.\")\n\n  (defun system-registered-p (name)\n    \"Return a generalized boolean that is true if a system of given NAME was registered already.\nNAME is a system designator, to be normalized by COERCE-NAME.\nThe value returned if true is a pair of a timestamp and a system object.\"\n    (gethash (coerce-name name) *defined-systems*))\n\n  (defun registered-system (name)\n    \"Return a system of given NAME that was registered already,\nif such a system exists.  NAME is a system designator, to be\nnormalized by COERCE-NAME. The value returned is a system object,\nor NIL if not found.\"\n    (cdr (system-registered-p name)))\n\n  (defun registered-systems* ()\n    \"Return a list containing every registered system (as a system object).\"\n    (loop :for registered :being :the :hash-values :of *defined-systems*\n          :collect (cdr registered)))\n\n  (defun registered-systems ()\n    \"Return a list of the names of every registered system.\"\n    (mapcar 'coerce-name (registered-systems*)))\n\n  (defun register-system (system)\n    \"Given a SYSTEM object, register it.\"\n    (check-type system system)\n    (let ((name (component-name system)))\n      (check-type name string)\n      (asdf-message (compatfmt \"~&~@<; ~@;Registering ~3i~_~A~@:>~%\") system)\n      (unless (eq system (registered-system name))\n        (setf (gethash name *defined-systems*)\n              (cons (ignore-errors (get-file-stamp (system-source-file system)))\n                    system)))))\n\n  (defun map-systems (fn)\n    \"Apply FN to each defined system.\n\nFN should be a function of one argument. It will be\ncalled with an object of type asdf:system.\"\n    (loop :for registered :being :the :hash-values :of *defined-systems*\n          :do (funcall fn (cdr registered))))\n\n\n  ;;; Preloaded systems: in the image even if you can't find source files backing them.\n\n  (defvar *preloaded-systems* (make-hash-table :test 'equal)\n    \"Registration table for preloaded systems.\")\n\n  (declaim (ftype (function (t) t) mark-component-preloaded)) ; defined in asdf/operate\n\n  (defun make-preloaded-system (name keys)\n    \"Make a preloaded system of given NAME with build information from KEYS\"\n    (let ((system (apply 'make-instance (getf keys :class 'system)\n                         :name name :source-file (getf keys :source-file)\n                         (remove-plist-keys '(:class :name :source-file) keys))))\n      (mark-component-preloaded system)\n      system))\n\n  (defun sysdef-preloaded-system-search (requested)\n    \"If REQUESTED names a system registered as preloaded, return a new system\nwith its registration information.\"\n    (let ((name (coerce-name requested)))\n      (multiple-value-bind (keys foundp) (gethash name *preloaded-systems*)\n        (when foundp\n          (make-preloaded-system name keys)))))\n\n  (defun ensure-preloaded-system-registered (name)\n    \"If there isn't a registered _defined_ system of given NAME,\nand a there is a registered _preloaded_ system of given NAME,\nthen define and register said preloaded system.\"\n    (if-let (system (and (not (registered-system name)) (sysdef-preloaded-system-search name)))\n      (register-system system)))\n\n  (defun register-preloaded-system (system-name &rest keys &key (version t) &allow-other-keys)\n    \"Register a system as being preloaded. If the system has not been loaded from the filesystem\nyet, or if its build information is later cleared with CLEAR-SYSTEM, a dummy system will be\nregistered without backing filesystem information, based on KEYS (e.g. to provide a VERSION).\nIf VERSION is the default T, and a system was already loaded, then its version will be preserved.\"\n    (let ((name (coerce-name system-name)))\n      (when (eql version t)\n        (if-let (system (registered-system name))\n          (setf (getf keys :version) (component-version system))))\n      (setf (gethash name *preloaded-systems*) keys)\n      (ensure-preloaded-system-registered system-name)))\n\n\n  ;;; Immutable systems: in the image and can't be reloaded from source.\n\n  (defvar *immutable-systems* nil\n    \"A hash-set (equal hash-table mapping keys to T) of systems that are immutable,\ni.e. already loaded in memory and not to be refreshed from the filesystem.\nThey will be treated specially by find-system, and passed as :force-not argument to make-plan.\n\nFor instance, to can deliver an image with many systems precompiled, that *will not* check the\nfilesystem for them every time a user loads an extension, what more risk a problematic upgrade\n or catastrophic downgrade, before you dump an image, you may use:\n   (map () 'asdf:register-immutable-system (asdf:already-loaded-systems))\n\nNote that direct access to this variable from outside ASDF is not supported.\nPlease call REGISTER-IMMUTABLE-SYSTEM to add new immutable systems, and\ncontact maintainers if you need a stable API to do more than that.\")\n\n  (defun sysdef-immutable-system-search (requested)\n    (let ((name (coerce-name requested)))\n      (when (and *immutable-systems* (gethash name *immutable-systems*))\n        (or (registered-system requested)\n            (error 'formatted-system-definition-error\n                   :format-control \"Requested system ~A registered as an immutable-system, ~\nbut not even registered as defined\"\n                   :format-arguments (list name))))))\n\n  (defun register-immutable-system (system-name &rest keys)\n    \"Register SYSTEM-NAME as preloaded and immutable.\nIt will automatically be considered as passed to FORCE-NOT in a plan.\"\n    (let ((system-name (coerce-name system-name)))\n      (apply 'register-preloaded-system system-name keys)\n      (unless *immutable-systems*\n        (setf *immutable-systems* (list-to-hash-set nil)))\n      (setf (gethash system-name *immutable-systems*) t)))\n\n\n  ;;; Making systems undefined.\n\n  (defun clear-system (system)\n    \"Clear the entry for a SYSTEM in the database of systems previously defined.\nHowever if the system was registered as PRELOADED (which it is if it is IMMUTABLE),\nthen a new system with the same name will be defined and registered in its place\nfrom which build details will have been cleared.\nNote that this does NOT in any way cause any of the code of the system to be unloaded.\nReturns T if system was or is now undefined, NIL if a new preloaded system was redefined.\"\n    ;; There is no \"unload\" operation in Common Lisp, and\n    ;; a general such operation cannot be portably written,\n    ;; considering how much CL relies on side-effects to global data structures.\n    (let ((name (coerce-name system)))\n      (remhash name *defined-systems*)\n      (unset-asdf-cache-entry `(find-system ,name))\n      (not (ensure-preloaded-system-registered name))))\n\n  (defun clear-defined-systems ()\n    \"Clear all currently registered defined systems.\nPreloaded systems (including immutable ones) will be reset, other systems will be de-registered.\"\n    (loop :for name :being :the :hash-keys :of *defined-systems*\n          :unless (member name '(\"asdf\" \"uiop\") :test 'equal) :do (clear-system name)))\n\n\n  ;;; Searching for system definitions\n\n  ;; For the sake of keeping things reasonably neat, we adopt a convention that\n  ;; only symbols are to be pushed to this list (rather than e.g. function objects),\n  ;; which makes upgrade easier. Also, the name of these symbols shall start with SYSDEF-\n  (defvar *system-definition-search-functions* '()\n    \"A list that controls the ways that ASDF looks for system definitions.\nIt contains symbols to be funcalled in order, with a requested system name as argument,\nuntil one returns a non-NIL result (if any), which must then be a fully initialized system object\nwith that name.\")\n\n  ;; Initialize and/or upgrade the *system-definition-search-functions*\n  ;; so it doesn't contain obsolete symbols, and does contain the current ones.\n  (defun cleanup-system-definition-search-functions ()\n    (setf *system-definition-search-functions*\n          (append\n           ;; Remove known-incompatible sysdef functions from old versions of asdf.\n           ;; Order matters, so we can't just use set-difference.\n           (let ((obsolete\n                  '(contrib-sysdef-search sysdef-find-asdf sysdef-preloaded-system-search)))\n             (remove-if #'(lambda (x) (member x obsolete)) *system-definition-search-functions*))\n           ;; Tuck our defaults at the end of the list if they were absent.\n           ;; This is imperfect, in case they were removed on purpose,\n           ;; but then it will be the responsibility of whoever removes these symmbols\n           ;; to upgrade asdf before he does such a thing rather than after.\n           (remove-if #'(lambda (x) (member x *system-definition-search-functions*))\n                      '(sysdef-central-registry-search\n                        sysdef-source-registry-search)))))\n  (cleanup-system-definition-search-functions)\n\n  ;; This (private) function does the search for a system definition using *s-d-s-f*;\n  ;; it is to be called by locate-system.\n  (defun search-for-system-definition (system)\n    ;; Search for valid definitions of the system available in the current session.\n    ;; Previous definitions as registered in *defined-systems* MUST NOT be considered;\n    ;; they will be reconciled by locate-system then find-system.\n    ;; There are two special treatments: first, specially search for objects being defined\n    ;; in the current session, to avoid definition races between several files;\n    ;; second, specially search for immutable systems, so they cannot be redefined.\n    ;; Finally, use the search functions specified in *system-definition-search-functions*.\n    (let ((name (coerce-name system)))\n      (flet ((try (f) (if-let ((x (funcall f name))) (return-from search-for-system-definition x))))\n        (try 'find-system-if-being-defined)\n        (try 'sysdef-immutable-system-search)\n        (map () #'try *system-definition-search-functions*))))\n\n\n  ;;; The legacy way of finding a system: the *central-registry*\n\n  ;; This variable contains a list of directories to be lazily searched for the requested asd\n  ;; by sysdef-central-registry-search.\n  (defvar *central-registry* nil\n    \"A list of 'system directory designators' ASDF uses to find systems.\n\nA 'system directory designator' is a pathname or an expression\nwhich evaluates to a pathname. For example:\n\n    (setf asdf:*central-registry*\n          (list '*default-pathname-defaults*\n                #p\\\"/home/me/cl/systems/\\\"\n                #p\\\"/usr/share/common-lisp/systems/\\\"))\n\nThis variable is for backward compatibility.\nGoing forward, we recommend new users should be using the source-registry.\")\n\n  ;; Function to look for an asd file of given NAME under a directory provided by DEFAULTS.\n  ;; Return the truename of that file if it is found and TRUENAME is true.\n  ;; Return NIL if the file is not found.\n  ;; On Windows, follow shortcuts to .asd files.\n  (defun probe-asd (name defaults &key truename)\n    (block nil\n      (when (directory-pathname-p defaults)\n        (if-let (file (probe-file*\n                       (ensure-absolute-pathname\n                        (parse-unix-namestring name :type \"asd\")\n                        #'(lambda () (ensure-absolute-pathname defaults 'get-pathname-defaults nil))\n                        nil)\n                       :truename truename))\n          (return file))\n        #-(or clisp genera) ; clisp doesn't need it, plain genera doesn't have read-sequence(!)\n        (os-cond\n         ((os-windows-p)\n          (when (physical-pathname-p defaults)\n            (let ((shortcut\n                    (make-pathname\n                     :defaults defaults :case :local\n                     :name (strcat name \".asd\")\n                     :type \"lnk\")))\n              (when (probe-file* shortcut)\n                (ensure-pathname (parse-windows-shortcut shortcut) :namestring :native)))))))))\n\n  ;; Function to push onto *s-d-s-f* to use the *central-registry*\n  (defun sysdef-central-registry-search (system)\n    (let ((name (primary-system-name system))\n          (to-remove nil)\n          (to-replace nil))\n      (block nil\n        (unwind-protect\n             (dolist (dir *central-registry*)\n               (let ((defaults (eval dir))\n                     directorized)\n                 (when defaults\n                   (cond ((directory-pathname-p defaults)\n                          (let* ((file (probe-asd name defaults :truename *resolve-symlinks*)))\n                            (when file\n                              (return file))))\n                         (t\n                          (restart-case\n                              (let* ((*print-circle* nil)\n                                     (message\n                                       (format nil\n                                               (compatfmt \"~@<While searching for system ~S: ~3i~_~S evaluated to ~S which is not an absolute directory.~@:>\")\n                                               system dir defaults)))\n                                (error message))\n                            (remove-entry-from-registry ()\n                              :report \"Remove entry from *central-registry* and continue\"\n                              (push dir to-remove))\n                            (coerce-entry-to-directory ()\n                              :test (lambda (c) (declare (ignore c))\n                                      (and (not (directory-pathname-p defaults))\n                                           (directory-pathname-p\n                                            (setf directorized\n                                                  (ensure-directory-pathname defaults)))))\n                              :report (lambda (s)\n                                        (format s (compatfmt \"~@<Coerce entry to ~a, replace ~a and continue.~@:>\")\n                                                directorized dir))\n                              (push (cons dir directorized) to-replace))))))))\n          ;; cleanup\n          (dolist (dir to-remove)\n            (setf *central-registry* (remove dir *central-registry*)))\n          (dolist (pair to-replace)\n            (let* ((current (car pair))\n                   (new (cdr pair))\n                   (position (position current *central-registry*)))\n              (setf *central-registry*\n                    (append (subseq *central-registry* 0 position)\n                            (list new)\n                            (subseq *central-registry* (1+ position))))))))))\n\n\n  ;;; Methods for find-system\n\n  ;; Reject NIL as a system designator.\n  (defmethod find-system ((name null) &optional (error-p t))\n    (when error-p\n      (sysdef-error (compatfmt \"~@<NIL is not a valid system name~@:>\"))))\n\n  ;; Default method for find-system: resolve the argument using COERCE-NAME.\n  (defmethod find-system (name &optional (error-p t))\n    (find-system (coerce-name name) error-p))\n\n  (defun find-system-if-being-defined (name)\n    ;; This function finds systems being defined *in the current ASDF session*, as embodied by\n    ;; its session cache, even before they are fully defined and registered in *defined-systems*.\n    ;; The purpose of this function is to prevent races between two files that might otherwise\n    ;; try overwrite each other's system objects, resulting in infinite loops and stack overflow.\n    ;; This function explicitly MUST NOT find definitions merely registered in previous sessions.\n    ;; NB: this function depends on a corresponding side-effect in parse-defsystem;\n    ;; the precise protocol between the two functions may change in the future (or not).\n    (first (gethash `(find-system ,(coerce-name name)) *asdf-cache*)))\n\n  (defun load-asd (pathname\n                   &key name (external-format (encoding-external-format (detect-encoding pathname)))\n                   &aux (readtable *readtable*) (print-pprint-dispatch *print-pprint-dispatch*))\n    \"Load system definitions from PATHNAME.\nNAME if supplied is the name of a system expected to be defined in that file.\n\nDo NOT try to load a .asd file directly with CL:LOAD. Always use ASDF:LOAD-ASD.\"\n    (with-asdf-cache ()\n      (with-standard-io-syntax\n        (let ((*package* (find-package :asdf-user))\n              ;; Note that our backward-compatible *readtable* is\n              ;; a global readtable that gets globally side-effected. Ouch.\n              ;; Same for the *print-pprint-dispatch* table.\n              ;; We should do something about that for ASDF3 if possible, or else ASDF4.\n              (*readtable* readtable)\n              (*print-pprint-dispatch* print-pprint-dispatch)\n              (*print-readably* nil)\n              (*default-pathname-defaults*\n                ;; resolve logical-pathnames so they won't wreak havoc in parsing namestrings.\n                (pathname-directory-pathname (physicalize-pathname pathname))))\n          (handler-bind\n              (((and error (not missing-component))\n                 #'(lambda (condition)\n                     (error 'load-system-definition-error\n                            :name name :pathname pathname :condition condition))))\n            (asdf-message (compatfmt \"~&~@<; ~@;Loading system definition~@[ for ~A~] from ~A~@:>~%\")\n                          name pathname)\n            (load* pathname :external-format external-format))))))\n\n  (defvar *old-asdf-systems* (make-hash-table :test 'equal))\n\n  ;; (Private) function to check that a system that was found isn't an asdf downgrade.\n  ;; Returns T if everything went right, NIL if the system was an ASDF of the same or older version,\n  ;; that shall not be loaded. Also issue a warning if it was a strictly older version of ASDF.\n  (defun check-not-old-asdf-system (name pathname)\n    (or (not (equal name \"asdf\"))\n        (null pathname)\n        (let* ((version-pathname (subpathname pathname \"version.lisp-expr\"))\n               (version (and (probe-file* version-pathname :truename nil)\n                             (read-file-form version-pathname)))\n               (old-version (asdf-version)))\n          (cond\n            ((version< old-version version) t) ;; newer version: good!\n            ((equal old-version version) nil) ;; same version: don't load, but don't warn\n            (t ;; old version: bad\n             (ensure-gethash\n              (list (namestring pathname) version) *old-asdf-systems*\n              #'(lambda ()\n                 (let ((old-pathname (system-source-file (registered-system \"asdf\"))))\n                   (warn \"~@<~\n        You are using ASDF version ~A ~:[(probably from (require \\\"asdf\\\") ~\n        or loaded by quicklisp)~;from ~:*~S~] and have an older version of ASDF ~\n        ~:[(and older than 2.27 at that)~;~:*~A~] registered at ~S. ~\n        Having an ASDF installed and registered is the normal way of configuring ASDF to upgrade itself, ~\n        and having an old version registered is a configuration error. ~\n        ASDF will ignore this configured system rather than downgrade itself. ~\n        In the future, you may want to either: ~\n        (a) upgrade this configured ASDF to a newer version, ~\n        (b) install a newer ASDF and register it in front of the former in your configuration, or ~\n        (c) uninstall or unregister this and any other old version of ASDF from your configuration. ~\n        Note that the older ASDF might be registered implicitly through configuration inherited ~\n        from your system installation, in which case you might have to specify ~\n        :ignore-inherited-configuration in your in your ~~/.config/common-lisp/source-registry.conf ~\n        or other source-registry configuration file, environment variable or lisp parameter. ~\n        Indeed, a likely offender is an obsolete version of the cl-asdf debian or ubuntu package, ~\n        that you might want to upgrade (if a recent enough version is available) ~\n        or else remove altogether (since most implementations ship with a recent asdf); ~\n        if you lack the system administration rights to upgrade or remove this package, ~\n        then you might indeed want to either install and register a more recent version, ~\n        or use :ignore-inherited-configuration to avoid registering the old one. ~\n        Please consult ASDF documentation and/or experts.~@:>~%\"\n                         old-version old-pathname version pathname))))\n             nil))))) ;; only issue the warning the first time, but always return nil\n\n  (defun locate-system (name)\n    \"Given a system NAME designator, try to locate where to load the system from.\nReturns five values: FOUNDP FOUND-SYSTEM PATHNAME PREVIOUS PREVIOUS-TIME\nFOUNDP is true when a system was found,\neither a new unregistered one or a previously registered one.\nFOUND-SYSTEM when not null is a SYSTEM object that may be REGISTER-SYSTEM'ed.\nPATHNAME when not null is a path from which to load the system,\neither associated with FOUND-SYSTEM, or with the PREVIOUS system.\nPREVIOUS when not null is a previously loaded SYSTEM object of same name.\nPREVIOUS-TIME when not null is the time at which the PREVIOUS system was loaded.\"\n    (with-asdf-cache () ;; NB: We don't cache the results. We once used to, but it wasn't useful,\n      ;; and keeping a negative cache was a bug (see lp#1335323), which required\n      ;; explicit invalidation in clear-system and find-system (when unsucccessful).\n      (let* ((name (coerce-name name))\n             (in-memory (system-registered-p name)) ; load from disk if absent or newer on disk\n             (previous (cdr in-memory))\n             (previous (and (typep previous 'system) previous))\n             (previous-time (car in-memory))\n             (found (search-for-system-definition name))\n             (found-system (and (typep found 'system) found))\n             (pathname (ensure-pathname\n                        (or (and (typep found '(or pathname string)) (pathname found))\n                            (system-source-file found-system)\n                            (system-source-file previous))\n                        :want-absolute t :resolve-symlinks *resolve-symlinks*))\n             (foundp (and (or found-system pathname previous) t)))\n        (check-type found (or null pathname system))\n        (unless (check-not-old-asdf-system name pathname)\n          (check-type previous system) ;; asdf is preloaded, so there should be a previous one.\n          (setf found-system nil pathname nil))\n        (values foundp found-system pathname previous previous-time))))\n\n  ;; Main method for find-system: first, make sure the computation is memoized in a session cache.\n  ;; unless the system is immutable, use locate-system to find the primary system;\n  ;; reconcile the finding (if any) with any previous definition (in a previous session,\n  ;; preloaded, with a previous configuration, or before filesystem changes), and\n  ;; load a found .asd if appropriate. Finally, update registration table and return results.\n  (defmethod find-system ((name string) &optional (error-p t))\n    (with-asdf-cache (:key `(find-system ,name))\n      (let ((primary-name (primary-system-name name)))\n        (unless (equal name primary-name)\n          (find-system primary-name nil)))\n      (or (and *immutable-systems* (gethash name *immutable-systems*) (registered-system name))\n          (multiple-value-bind (foundp found-system pathname previous previous-time)\n              (locate-system name)\n            (assert (eq foundp (and (or found-system pathname previous) t)))\n            (let ((previous-pathname (system-source-file previous))\n                  (system (or previous found-system)))\n              (when (and found-system (not previous))\n                (register-system found-system))\n              (when (and system pathname)\n                (setf (system-source-file system) pathname))\n              (when (and pathname\n                         (let ((stamp (get-file-stamp pathname)))\n                           (and stamp\n                                (not (and previous\n                                          (or (pathname-equal pathname previous-pathname)\n                                              (and pathname previous-pathname\n                                                   (pathname-equal\n                                                    (physicalize-pathname pathname)\n                                                    (physicalize-pathname previous-pathname))))\n                                          (stamp<= stamp previous-time))))))\n                ;; Only load when it's a pathname that is different or has newer content.\n                (load-asd pathname :name name)))\n            ;; Try again after having loaded from disk if needed\n            (let ((in-memory (system-registered-p name)))\n              (cond\n                (in-memory\n                 (when pathname\n                   (setf (car in-memory) (get-file-stamp pathname)))\n                 (cdr in-memory))\n                (error-p\n                 (error 'missing-component :requires name))\n                (t\n                 (return-from find-system nil)))))))))\n;;;; -------------------------------------------------------------------------\n;;;; Finding components\n\n(uiop/package:define-package :asdf/find-component\n  (:recycle :asdf/find-component :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/cache\n   :asdf/component :asdf/system :asdf/find-system)\n  (:export\n   #:find-component\n   #:resolve-dependency-name #:resolve-dependency-spec\n   #:resolve-dependency-combination\n   ;; Conditions\n   #:missing-component #:missing-component-of-version #:retry\n   #:missing-dependency #:missing-dependency-of-version\n   #:missing-requires #:missing-parent\n   #:missing-required-by #:missing-version))\n(in-package :asdf/find-component)\n\n;;;; Missing component conditions\n\n(with-upgradability ()\n  (define-condition missing-component-of-version (missing-component)\n    ((version :initform nil :reader missing-version :initarg :version)))\n\n  (define-condition missing-dependency (missing-component)\n    ((required-by :initarg :required-by :reader missing-required-by)))\n\n  (defmethod print-object ((c missing-dependency) s)\n    (format s (compatfmt \"~@<~A, required by ~A~@:>\")\n            (call-next-method c nil) (missing-required-by c)))\n\n  (define-condition missing-dependency-of-version (missing-dependency\n                                                   missing-component-of-version)\n    ())\n\n  (defmethod print-object ((c missing-component) s)\n    (format s (compatfmt \"~@<Component ~S not found~@[ in ~A~]~@:>\")\n            (missing-requires c)\n            (when (missing-parent c)\n              (coerce-name (missing-parent c)))))\n\n  (defmethod print-object ((c missing-component-of-version) s)\n    (format s (compatfmt \"~@<Component ~S does not match version ~A~@[ in ~A~]~@:>\")\n            (missing-requires c)\n            (missing-version c)\n            (when (missing-parent c)\n              (coerce-name (missing-parent c))))))\n\n\n;;;; Finding components\n\n(with-upgradability ()\n  (defgeneric find-component (base path &key registered)\n    (:documentation \"Find a component by resolving the PATH starting from BASE parent.\nIf REGISTERED is true, only search currently registered systems.\"))\n  (defgeneric resolve-dependency-combination (component combinator arguments)\n    (:documentation \"Return a component satisfying the dependency specification (COMBINATOR . ARGUMENTS)\nin the context of COMPONENT\"))\n\n  ;; Methods for find-component\n\n  ;; If the base component is a string, resolve it as a system, then if not nil follow the path.\n  (defmethod find-component ((base string) path &key registered)\n    (if-let ((s (if registered\n                    (registered-system base)\n                    (find-system base nil))))\n      (find-component s path :registered registered)))\n\n  ;; If the base component is a symbol, coerce it to a name if not nil, and resolve that.\n  ;; If nil, use the path as base if not nil, or else return nil.\n  (defmethod find-component ((base symbol) path &key registered)\n    (cond\n      (base (find-component (coerce-name base) path :registered registered))\n      (path (find-component path nil :registered registered))\n      (t    nil)))\n\n  ;; If the base component is a cons cell, resolve its car, and add its cdr to the path.\n  (defmethod find-component ((base cons) path &key registered)\n    (find-component (car base) (cons (cdr base) path) :registered registered))\n\n  ;; If the base component is a parent-component and the path a string, find the named child.\n  (defmethod find-component ((parent parent-component) (name string) &key registered)\n    (declare (ignorable registered))\n    (compute-children-by-name parent :only-if-needed-p t)\n    (values (gethash name (component-children-by-name parent))))\n\n  ;; If the path is a symbol, coerce it to a name if non-nil, or else just return the base.\n  (defmethod find-component (base (name symbol) &key registered)\n    (if name\n        (find-component base (coerce-name name) :registered registered)\n        base))\n\n  ;; If the path is a cons, first resolve its car as path, then its cdr.\n  (defmethod find-component ((c component) (name cons) &key registered)\n    (find-component (find-component c (car name) :registered registered)\n                    (cdr name) :registered registered))\n\n  ;; If the path is a component, return it, disregarding the base.\n  (defmethod find-component ((base t) (actual component) &key registered)\n    (declare (ignorable registered))\n    actual)\n\n  ;; Resolve dependency NAME in the context of a COMPONENT, with given optional VERSION constraint.\n  ;; This (private) function is used below by RESOLVE-DEPENDENCY-SPEC and by the :VERSION spec.\n  (defun resolve-dependency-name (component name &optional version)\n    (loop\n      (restart-case\n          (return\n            (let ((comp (find-component (component-parent component) name)))\n              (unless comp\n                (error 'missing-dependency\n                       :required-by component\n                       :requires name))\n              (when version\n                (unless (version-satisfies comp version)\n                  (error 'missing-dependency-of-version\n                         :required-by component\n                         :version version\n                         :requires name)))\n              comp))\n        (retry ()\n          :report (lambda (s)\n                    (format s (compatfmt \"~@<Retry loading ~3i~_~A.~@:>\") name))\n          :test\n          (lambda (c)\n            (or (null c)\n                (and (typep c 'missing-dependency)\n                     (eq (missing-required-by c) component)\n                     (equal (missing-requires c) name))))\n          (unless (component-parent component)\n            (let ((name (coerce-name name)))\n              (unset-asdf-cache-entry `(find-system ,name))))))))\n\n  ;; Resolve dependency specification DEP-SPEC in the context of COMPONENT.\n  ;; This is notably used by MAP-DIRECT-DEPENDENCIES to process the results of COMPONENT-DEPENDS-ON\n  ;; and by PARSE-DEFSYSTEM to process DEFSYSTEM-DEPENDS-ON.\n  (defun resolve-dependency-spec (component dep-spec)\n    (let ((component (find-component () component)))\n      (if (atom dep-spec)\n          (resolve-dependency-name component dep-spec)\n          (resolve-dependency-combination component (car dep-spec) (cdr dep-spec)))))\n\n  ;; Methods for RESOLVE-DEPENDENCY-COMBINATION to parse lists as dependency specifications.\n  (defmethod resolve-dependency-combination (component combinator arguments)\n    (parameter-error (compatfmt \"~@<In ~S, bad dependency ~S for ~S~@:>\")\n                     'resolve-dependency-combination (cons combinator arguments) component))\n\n  (defmethod resolve-dependency-combination (component (combinator (eql :feature)) arguments)\n    (when (featurep (first arguments))\n      (resolve-dependency-spec component (second arguments))))\n\n  (defmethod resolve-dependency-combination (component (combinator (eql :version)) arguments)\n    (resolve-dependency-name component (first arguments) (second arguments)))) ;; See lp#527788\n\n;;;; -------------------------------------------------------------------------\n;;;; Operations\n\n(uiop/package:define-package :asdf/operation\n  (:recycle :asdf/operation :asdf/action :asdf) ;; asdf/action for FEATURE pre 2.31.5.\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/find-system)\n  (:export\n   #:operation\n   #:*operations* #:make-operation #:find-operation\n   #:feature)) ;; TODO: stop exporting the deprecated FEATURE feature.\n(in-package :asdf/operation)\n\n;;; Operation Classes\n(when-upgrading (:version \"2.27\" :when (find-class 'operation nil))\n  ;; override any obsolete shared-initialize method when upgrading from ASDF2.\n  (defmethod shared-initialize :after ((o operation) (slot-names t) &key)\n    (values)))\n\n(with-upgradability ()\n  (defclass operation ()\n    ()\n    (:documentation \"The base class for all ASDF operations.\n\nASDF does NOT and never did distinguish between multiple operations of the same class.\nTherefore, all slots of all operations MUST have :allocation :class and no initargs. No exceptions.\n\"))\n\n  (defvar *in-make-operation* nil)\n\n  (defun check-operation-constructor ()\n    \"Enforce that OPERATION instances must be created with MAKE-OPERATION.\"\n    (unless *in-make-operation*\n      (sysdef-error \"OPERATION instances must only be created through MAKE-OPERATION.\")))\n\n  (defmethod print-object ((o operation) stream)\n    (print-unreadable-object (o stream :type t :identity nil)))\n\n  ;;; Override previous methods (from 3.1.7 and earlier) and add proper error checking.\n  (defmethod initialize-instance :after ((o operation) &rest initargs &key &allow-other-keys)\n    (unless (null initargs)\n      (parameter-error \"~S does not accept initargs\" 'operation))))\n\n\n;;; make-operation, find-operation\n\n(with-upgradability ()\n  ;; A table to memoize instances of a given operation. There shall be only one.\n  (defparameter* *operations* (make-hash-table :test 'equal))\n\n  ;; A memoizing way of creating instances of operation.\n  (defun make-operation (operation-class)\n    \"This function creates and memoizes an instance of OPERATION-CLASS.\nAll operation instances MUST be created through this function.\n\nUse of INITARGS is not supported at this time.\"\n    (let ((class (coerce-class operation-class\n                               :package :asdf/interface :super 'operation :error 'sysdef-error))\n          (*in-make-operation* t))\n      (ensure-gethash class *operations* `(make-instance ,class))))\n\n  ;; This function is mostly for backward and forward compatibility:\n  ;; operations used to preserve the operation-original-initargs of the context,\n  ;; and may in the future preserve some operation-canonical-initargs.\n  ;; Still, the treatment of NIL as a disabling context is useful in some cases.\n  (defgeneric find-operation (context spec)\n    (:documentation \"Find an operation by resolving the SPEC in the CONTEXT\"))\n  (defmethod find-operation ((context t) (spec operation))\n    spec)\n  (defmethod find-operation ((context t) (spec symbol))\n    (when spec ;; NIL designates itself, i.e. absence of operation\n      (make-operation spec))) ;; TODO: preserve the (operation-canonical-initargs context)\n  (defmethod find-operation ((context t) (spec string))\n    (make-operation spec))) ;; TODO: preserve the (operation-canonical-initargs context)\n\n;;;; -------------------------------------------------------------------------\n;;;; Actions\n\n(uiop/package:define-package :asdf/action\n  (:nicknames :asdf-action)\n  (:recycle :asdf/action :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/system #:asdf/cache :asdf/find-system :asdf/find-component :asdf/operation)\n  (:import-from :asdf/operation #:check-operation-constructor)\n  #-clisp (:unintern #:required-components #:traverse-action #:traverse-sub-actions)\n  (:export\n   #:action #:define-convenience-action-methods\n   #:action-description\n   #:downward-operation #:upward-operation #:sideway-operation #:selfward-operation #:non-propagating-operation\n   #:component-depends-on\n   #:input-files #:output-files #:output-file #:operation-done-p\n   #:action-status #:action-stamp #:action-done-p\n   #:action-operation #:action-component #:make-action\n   #:component-operation-time #:mark-operation-done #:compute-action-stamp\n   #:perform #:perform-with-restarts #:retry #:accept\n   #:action-path #:find-action #:stamp #:done-p\n   #:operation-definition-warning #:operation-definition-error ;; condition\n   ))\n(in-package :asdf/action)\n\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute) ;; LispWorks issues spurious warning\n\n  (deftype action ()\n    \"A pair of operation and component uniquely identifies a node in the dependency graph\nof steps to be performed while building a system.\"\n    '(cons operation component))\n\n  (deftype operation-designator ()\n    \"An operation designates itself. NIL designates a context-dependent current operation,\nand a class-name or class designates the canonical instance of the designated class.\"\n    '(or operation null symbol class)))\n\n;;; these are pseudo accessors -- let us abstract away the CONS cell representation of plan\n;;; actions.\n(with-upgradability ()\n  (defun make-action (operation component)\n    (cons operation component))\n  (defun action-operation (action)\n    (car action))\n  (defun action-component (action)\n    (cdr action)))\n\n;;;; Reified representation for storage or debugging. Note: an action is identified by its class.\n(with-upgradability ()\n  (defun action-path (action)\n    \"A readable data structure that identifies the action.\"\n    (let ((o (action-operation action))\n          (c (action-component action)))\n      (cons (type-of o) (component-find-path c))))\n  (defun find-action (path)\n    \"Reconstitute an action from its action-path\"\n    (destructuring-bind (o . c) path (make-action (make-operation o) (find-component () c)))))\n\n;;;; Convenience methods\n(with-upgradability ()\n  ;; A macro that defines convenience methods for a generic function (gf) that\n  ;; dispatches on operation and component.  The convenience methods allow users\n  ;; to call the gf with operation and/or component designators, that the\n  ;; methods will resolve into actual operation and component objects, so that\n  ;; the users can interact using readable designators, but developers only have\n  ;; to write methods that handle operation and component objects.\n  ;; FUNCTION is the generic function name\n  ;; FORMALS is its list of arguments, which must include OPERATION and COMPONENT.\n  ;; IF-NO-OPERATION is a form (defaults to NIL) describing what to do if no operation is found.\n  ;; IF-NO-COMPONENT is a form (defaults to NIL) describing what to do if no component is found.\n  (defmacro define-convenience-action-methods\n      (function formals &key if-no-operation if-no-component)\n    (let* ((rest (gensym \"REST\"))\n           (found (gensym \"FOUND\"))\n           (keyp (equal (last formals) '(&key)))\n           (formals-no-key (if keyp (butlast formals) formals))\n           (len (length formals-no-key))\n           (operation 'operation)\n           (component 'component)\n           (opix (position operation formals))\n           (coix (position component formals))\n           (prefix (subseq formals 0 opix))\n           (suffix (subseq formals (1+ coix) len))\n           (more-args (when keyp `(&rest ,rest &key &allow-other-keys))))\n      (assert (and (integerp opix) (integerp coix) (= coix (1+ opix))))\n      (flet ((next-method (o c)\n               (if keyp\n                   `(apply ',function ,@prefix ,o ,c ,@suffix ,rest)\n                   `(,function ,@prefix ,o ,c ,@suffix))))\n        `(progn\n           (defmethod ,function (,@prefix (,operation string) ,component ,@suffix ,@more-args)\n             (declare (notinline ,function))\n             (let ((,component (find-component () ,component))) ;; do it first, for defsystem-depends-on\n               ,(next-method `(safe-read-from-string ,operation :package :asdf/interface) component)))\n           (defmethod ,function (,@prefix (,operation symbol) ,component ,@suffix ,@more-args)\n             (declare (notinline ,function))\n             (if ,operation\n                 ,(next-method\n                   `(make-operation ,operation)\n                   `(or (find-component () ,component) ,if-no-component))\n                 ,if-no-operation))\n           (defmethod ,function (,@prefix (,operation operation) ,component ,@suffix ,@more-args)\n             (declare (notinline ,function))\n             (if (typep ,component 'component)\n                 (error \"No defined method for ~S on ~/asdf-action:format-action/\"\n                        ',function (make-action ,operation ,component))\n                 (if-let (,found (find-component () ,component))\n                    ,(next-method operation found)\n                    ,if-no-component))))))))\n\n\n;;;; self-description\n(with-upgradability ()\n  (defgeneric action-description (operation component)\n    (:documentation \"returns a phrase that describes performing this operation\non this component, e.g. \\\"loading /a/b/c\\\".\nYou can put together sentences using this phrase.\"))\n  (defmethod action-description (operation component)\n    (format nil (compatfmt \"~@<~A on ~A~@:>\")\n            operation component))\n\n  (defun format-action (stream action &optional colon-p at-sign-p)\n    \"FORMAT helper to display an action's action-description.\nUse it in FORMAT control strings as ~/asdf-action:format-action/\"\n    (assert (null colon-p)) (assert (null at-sign-p))\n    (destructuring-bind (operation . component) action\n      (princ (action-description operation component) stream))))\n\n\n;;;; Dependencies\n(with-upgradability ()\n  (defgeneric component-depends-on (operation component) ;; ASDF4: rename to component-dependencies\n    (:documentation\n     \"Returns a list of dependencies needed by the component to perform\n    the operation.  A dependency has one of the following forms:\n\n      (<operation> <component>*), where <operation> is an operation designator\n        with respect to FIND-OPERATION in the context of the OPERATION argument,\n        and each <component> is a component designator with respect to\n        FIND-COMPONENT in the context of the COMPONENT argument,\n        and means that the component depends on\n        <operation> having been performed on each <component>;\n\n        [Note: an <operation> is an operation designator -- it can be either an\n        operation name or an operation object.  Similarly, a <component> may be\n        a component name or a component object.  Also note that, the degenerate\n        case of (<operation>) is a no-op.]\n\n    Methods specialized on subclasses of existing component types\n    should usually append the results of CALL-NEXT-METHOD to the list.\"))\n  (define-convenience-action-methods component-depends-on (operation component))\n\n  (defmethod component-depends-on :around ((o operation) (c component))\n    (do-asdf-cache `(component-depends-on ,o ,c)\n      (call-next-method))))\n\n\n;;;; upward-operation, downward-operation, sideway-operation, selfward-operation\n;; These together handle actions that propagate along the component hierarchy or operation universe.\n(with-upgradability ()\n  (defclass downward-operation (operation)\n    ((downward-operation\n      :initform nil :reader downward-operation\n      :type operation-designator :allocation :class))\n    (:documentation \"A DOWNWARD-OPERATION's dependencies propagate down the component hierarchy.\nI.e., if O is a DOWNWARD-OPERATION and its DOWNWARD-OPERATION slot designates operation D, then\nthe action (O . M) of O on module M will depends on each of (D . C) for each child C of module M.\nThe default value for slot DOWNWARD-OPERATION is NIL, which designates the operation O itself.\nE.g. in order for a MODULE to be loaded with LOAD-OP (resp. compiled with COMPILE-OP), all the\nchildren of the MODULE must have been loaded with LOAD-OP (resp. compiled with COMPILE-OP.\"))\n  (defun downward-operation-depends-on (o c)\n    `((,(or (downward-operation o) o) ,@(component-children c))))\n  (defmethod component-depends-on ((o downward-operation) (c parent-component))\n    `(,@(downward-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass upward-operation (operation)\n    ((upward-operation\n      :initform nil :reader upward-operation\n      :type operation-designator :allocation :class))\n    (:documentation \"An UPWARD-OPERATION has dependencies that propagate up the component hierarchy.\nI.e., if O is an instance of UPWARD-OPERATION, and its UPWARD-OPERATION slot designates operation U,\nthen the action (O . C) of O on a component C that has the parent P will depends on (U . P).\nThe default value for slot UPWARD-OPERATION is NIL, which designates the operation O itself.\nE.g. in order for a COMPONENT to be prepared for loading or compiling with PREPARE-OP, its PARENT\nmust first be prepared for loading or compiling with PREPARE-OP.\"))\n  ;; For backward-compatibility reasons, a system inherits from module and is a child-component\n  ;; so we must guard against this case. ASDF4: remove that.\n  (defun upward-operation-depends-on (o c)\n    (if-let (p (component-parent c)) `((,(or (upward-operation o) o) ,p))))\n  (defmethod component-depends-on ((o upward-operation) (c child-component))\n    `(,@(upward-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass sideway-operation (operation)\n    ((sideway-operation\n      :initform nil :reader sideway-operation\n      :type operation-designator :allocation :class))\n    (:documentation \"A SIDEWAY-OPERATION has dependencies that propagate \\\"sideway\\\" to siblings\nthat a component depends on. I.e. if O is a SIDEWAY-OPERATION, and its SIDEWAY-OPERATION slot\ndesignates operation S (where NIL designates O itself), then the action (O . C) of O on component C\ndepends on each of (S . D) where D is a declared dependency of C.\nE.g. in order for a COMPONENT to be prepared for loading or compiling with PREPARE-OP,\neach of its declared dependencies must first be loaded as by LOAD-OP.\"))\n  (defun sideway-operation-depends-on (o c)\n    `((,(or (sideway-operation o) o) ,@(component-sideway-dependencies c))))\n  (defmethod component-depends-on ((o sideway-operation) (c component))\n    `(,@(sideway-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass selfward-operation (operation)\n    ((selfward-operation\n      ;; NB: no :initform -- if an operation depends on others, it must explicitly specify which\n      :type (or operation-designator list) :reader selfward-operation :allocation :class))\n    (:documentation \"A SELFWARD-OPERATION depends on another operation on the same component.\nI.e., if O is a SELFWARD-OPERATION, and its SELFWARD-OPERATION designates a list of operations L,\nthen the action (O . C) of O on component C depends on each (S . C) for S in L.\nE.g. before a component may be loaded by LOAD-OP, it must have been compiled by COMPILE-OP.\nA operation-designator designates a singleton list of the designated operation;\na list of operation-designators designates the list of designated operations;\nNIL is not a valid operation designator in that context.  Note that any dependency\nordering between the operations in a list of SELFWARD-OPERATION should be specified separately\nin the respective operation's COMPONENT-DEPENDS-ON methods so that they be scheduled properly.\"))\n  (defun selfward-operation-depends-on (o c)\n    (loop :for op :in (ensure-list (selfward-operation o)) :collect `(,op ,c)))\n  (defmethod component-depends-on ((o selfward-operation) (c component))\n    `(,@(selfward-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass non-propagating-operation (operation)\n    ()\n    (:documentation \"A NON-PROPAGATING-OPERATION is an operation that propagates\nno dependencies whatsoever.  It is supplied in order that the programmer be able\nto specify that s/he is intentionally specifying an operation which invokes no\ndependencies.\")))\n\n\n;;;---------------------------------------------------------------------------\n;;; Help programmers catch obsolete OPERATION subclasses\n;;;---------------------------------------------------------------------------\n(with-upgradability ()\n  (define-condition operation-definition-warning (simple-warning)\n    ()\n    (:documentation \"Warning condition related to definition of obsolete OPERATION objects.\"))\n\n  (define-condition operation-definition-error (simple-error)\n    ()\n    (:documentation \"Error condition related to definition of incorrect OPERATION objects.\"))\n\n  (defmethod initialize-instance :before ((o operation) &key)\n    (check-operation-constructor)\n    (unless (typep o '(or downward-operation upward-operation sideway-operation\n                          selfward-operation non-propagating-operation))\n      (warn 'operation-definition-warning\n            :format-control\n            \"No dependency propagating scheme specified for operation class ~S.\nThe class needs to be updated for ASDF 3.1 and specify appropriate propagation mixins.\"\n            :format-arguments (list (type-of o)))))\n\n  (defmethod initialize-instance :before ((o non-propagating-operation) &key)\n    (when (typep o '(or downward-operation upward-operation sideway-operation selfward-operation))\n      (error 'operation-definition-error\n             :format-control\n             \"Inconsistent class: ~S\n  NON-PROPAGATING-OPERATION is incompatible with propagating operation classes as superclasses.\"\n             :format-arguments\n             (list (type-of o)))))\n\n  (defun backward-compatible-depends-on (o c)\n    \"DEPRECATED: all subclasses of OPERATION used in ASDF should inherit from one of\n DOWNWARD-OPERATION UPWARD-OPERATION SIDEWAY-OPERATION SELFWARD-OPERATION NON-PROPAGATING-OPERATION.\n The function BACKWARD-COMPATIBLE-DEPENDS-ON temporarily provides ASDF2 behaviour for those that\n don't. In the future this functionality will be removed, and the default will be no propagation.\"\n    (uiop/version::notify-deprecated-function\n     (version-deprecation *asdf-version* :style-warning \"3.2\")\n     'backward-compatible-depends-on)\n    `(,@(sideway-operation-depends-on o c)\n      ,@(when (typep c 'parent-component) (downward-operation-depends-on o c))))\n\n  (defmethod component-depends-on ((o operation) (c component))\n    `(;; Normal behavior, to allow user-specified in-order-to dependencies\n      ,@(cdr (assoc (type-of o) (component-in-order-to c)))\n        ;; For backward-compatibility with ASDF2, any operation that doesn't specify propagation\n        ;; or non-propagation through an appropriate mixin will be downward and sideway.\n        ,@(unless (typep o '(or downward-operation upward-operation sideway-operation\n                             selfward-operation non-propagating-operation))\n            (backward-compatible-depends-on o c))))\n\n  (defmethod downward-operation ((o operation)) nil)\n  (defmethod sideway-operation ((o operation)) nil))\n\n\n;;;---------------------------------------------------------------------------\n;;; End of OPERATION class checking\n;;;---------------------------------------------------------------------------\n\n\n;;;; Inputs, Outputs, and invisible dependencies\n(with-upgradability ()\n  (defgeneric output-files (operation component)\n    (:documentation \"Methods for this function return two values: a list of output files\ncorresponding to this action, and a boolean indicating if they have already been subjected\nto relevant output translations and should not be further translated.\n\nMethods on PERFORM *must* call this function to determine where their outputs are to be located.\nThey may rely on the order of the files to discriminate between outputs.\n\"))\n  (defgeneric input-files (operation component)\n    (:documentation \"A list of input files corresponding to this action.\n\nMethods on PERFORM *must* call this function to determine where their inputs are located.\nThey may rely on the order of the files to discriminate between inputs.\n\"))\n  (defgeneric operation-done-p (operation component)\n    (:documentation \"Returns a boolean which is NIL if the action must be performed (again).\"))\n  (define-convenience-action-methods output-files (operation component))\n  (define-convenience-action-methods input-files (operation component))\n  (define-convenience-action-methods operation-done-p (operation component))\n\n  (defmethod operation-done-p ((o operation) (c component))\n    t)\n\n  ;; Translate output files, unless asked not to. Memoize the result.\n  (defmethod output-files :around ((operation t) (component t))\n    (do-asdf-cache `(output-files ,operation ,component)\n      (values\n       (multiple-value-bind (pathnames fixedp) (call-next-method)\n         ;; 1- Make sure we have absolute pathnames\n         (let* ((directory (pathname-directory-pathname\n                            (component-pathname (find-component () component))))\n                (absolute-pathnames\n                  (loop\n                    :for pathname :in pathnames\n                    :collect (ensure-absolute-pathname pathname directory))))\n           ;; 2- Translate those pathnames as required\n           (if fixedp\n               absolute-pathnames\n               (mapcar *output-translation-function* absolute-pathnames))))\n       t)))\n  (defmethod output-files ((o operation) (c component))\n    nil)\n  (defun output-file (operation component)\n    \"The unique output file of performing OPERATION on COMPONENT\"\n    (let ((files (output-files operation component)))\n      (assert (length=n-p files 1))\n      (first files)))\n\n  ;; Memoize input files.\n  (defmethod input-files :around (operation component)\n    (do-asdf-cache `(input-files ,operation ,component)\n      (call-next-method)))\n\n  ;; By default an action has no input-files.\n  (defmethod input-files ((o operation) (c component))\n    nil)\n\n  ;; An action with a selfward-operation by default gets its input-files from the output-files of\n  ;; the actions using selfward-operations it depends on (and the same component),\n  ;; or if there are none, on the component-pathname of the component if it's a file\n  ;; -- and then on the results of the next-method.\n  (defmethod input-files ((o selfward-operation) (c component))\n    `(,@(or (loop :for dep-o :in (ensure-list (selfward-operation o))\n                  :append (or (output-files dep-o c) (input-files dep-o c)))\n            (if-let ((pathname (component-pathname c)))\n              (and (file-pathname-p pathname) (list pathname))))\n      ,@(call-next-method))))\n\n\n;;;; Done performing\n(with-upgradability ()\n  ;; ASDF4: hide it behind plan-action-stamp\n  (defgeneric component-operation-time (operation component)\n    (:documentation \"Return the timestamp for when an action was last performed\"))\n  (defgeneric (setf component-operation-time) (time operation component)\n    (:documentation \"Update the timestamp for when an action was last performed\"))\n  (define-convenience-action-methods component-operation-time (operation component))\n\n  ;; ASDF4: hide it behind (setf plan-action-stamp)\n  (defgeneric mark-operation-done (operation component)\n    (:documentation \"Mark a action as having been just done.\n\nUpdates the action's COMPONENT-OPERATION-TIME to match the COMPUTE-ACTION-STAMP\nusing the JUST-DONE flag.\"))\n  (defgeneric compute-action-stamp (plan operation component &key just-done)\n    (:documentation \"Has this action been successfully done already,\nand at what known timestamp has it been done at or will it be done at?\n* PLAN is a plan object modelling future effects of actions,\n  or NIL to denote what actually happened.\n* OPERATION and COMPONENT denote the action.\nTakes keyword JUST-DONE:\n* JUST-DONE is a boolean that is true if the action was just successfully performed,\n  at which point we want compute the actual stamp and warn if files are missing;\n  otherwise we are making plans, anticipating the effects of the action.\nReturns two values:\n* a STAMP saying when it was done or will be done,\n  or T if the action involves files that need to be recomputed.\n* a boolean DONE-P that indicates whether the action has actually been done,\n  and both its output-files and its in-image side-effects are up to date.\"))\n\n  (defclass action-status ()\n    ((stamp\n      :initarg :stamp :reader action-stamp\n      :documentation \"STAMP associated with the ACTION if it has been completed already\nin some previous image, or T if it needs to be done.\")\n     (done-p\n      :initarg :done-p :reader action-done-p\n      :documentation \"a boolean, true iff the action was already done (before any planned action).\"))\n    (:documentation \"Status of an action\"))\n\n  (defmethod print-object ((status action-status) stream)\n    (print-unreadable-object (status stream :type t)\n      (with-slots (stamp done-p) status\n        (format stream \"~@{~S~^ ~}\" :stamp stamp :done-p done-p))))\n\n  (defmethod component-operation-time ((o operation) (c component))\n    (gethash o (component-operation-times c)))\n\n  (defmethod (setf component-operation-time) (stamp (o operation) (c component))\n    (setf (gethash o (component-operation-times c)) stamp))\n\n  (defmethod mark-operation-done ((o operation) (c component))\n    (setf (component-operation-time o c) (compute-action-stamp nil o c :just-done t))))\n\n\n;;;; Perform\n(with-upgradability ()\n  (defgeneric perform (operation component)\n    (:documentation \"PERFORM an action, consuming its input-files and building its output-files\"))\n  (define-convenience-action-methods perform (operation component))\n\n  (defmethod perform :before ((o operation) (c component))\n    (ensure-all-directories-exist (output-files o c)))\n  (defmethod perform :after ((o operation) (c component))\n    (mark-operation-done o c))\n  (defmethod perform ((o operation) (c parent-component))\n    nil)\n  (defmethod perform ((o operation) (c source-file))\n    ;; For backward compatibility, don't error on operations that don't specify propagation.\n    (when (typep o '(or downward-operation upward-operation sideway-operation\n                     selfward-operation non-propagating-operation))\n      (sysdef-error\n       (compatfmt \"~@<Required method ~S not implemented for ~/asdf-action:format-action/~@:>\")\n       'perform (make-action o c))))\n\n  ;; The restarts of the perform-with-restarts variant matter in an interactive context.\n  ;; The retry strategies of p-w-r itself, and/or the background workers of a multiprocess build\n  ;; may call perform directly rather than call p-w-r.\n  (defgeneric perform-with-restarts (operation component)\n    (:documentation \"PERFORM an action in a context where suitable restarts are in place.\"))\n  (defmethod perform-with-restarts (operation component)\n    (perform operation component))\n  (defmethod perform-with-restarts :around (operation component)\n    (loop\n      (restart-case\n          (return (call-next-method))\n        (retry ()\n          :report\n          (lambda (s)\n            (format s (compatfmt \"~@<Retry ~A.~@:>\")\n                    (action-description operation component))))\n        (accept ()\n          :report\n          (lambda (s)\n            (format s (compatfmt \"~@<Continue, treating ~A as having been successful.~@:>\")\n                    (action-description operation component)))\n          (mark-operation-done operation component)\n          (return))))))\n;;;; -------------------------------------------------------------------------\n;;;; Actions to build Common Lisp software\n\n(uiop/package:define-package :asdf/lisp-action\n  (:recycle :asdf/lisp-action :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/cache\n   :asdf/component :asdf/system :asdf/find-component :asdf/find-system\n   :asdf/operation :asdf/action)\n  (:export\n   #:try-recompiling\n   #:cl-source-file #:cl-source-file.cl #:cl-source-file.lsp\n   #:basic-load-op #:basic-compile-op\n   #:load-op #:prepare-op #:compile-op #:test-op #:load-source-op #:prepare-source-op\n   #:call-with-around-compile-hook\n   #:perform-lisp-compilation #:perform-lisp-load-fasl #:perform-lisp-load-source\n   #:lisp-compilation-output-files))\n(in-package :asdf/lisp-action)\n\n\n;;;; Component classes\n(with-upgradability ()\n  (defclass cl-source-file (source-file)\n    ((type :initform \"lisp\"))\n    (:documentation \"Component class for a Common Lisp source file (using type \\\"lisp\\\")\"))\n  (defclass cl-source-file.cl (cl-source-file)\n    ((type :initform \"cl\"))\n    (:documentation \"Component class for a Common Lisp source file using type \\\"cl\\\"\"))\n  (defclass cl-source-file.lsp (cl-source-file)\n    ((type :initform \"lsp\"))\n    (:documentation \"Component class for a Common Lisp source file using type \\\"lsp\\\"\")))\n\n\n;;;; Operation classes\n(with-upgradability ()\n  (defclass basic-load-op (operation) ()\n    (:documentation \"Base class for operations that apply the load-time effects of a file\"))\n  (defclass basic-compile-op (operation) ()\n    (:documentation \"Base class for operations that apply the compile-time effects of a file\")))\n\n\n;;; Our default operations: loading into the current lisp image\n(with-upgradability ()\n  (defclass prepare-op (upward-operation sideway-operation)\n    ((sideway-operation :initform 'load-op :allocation :class))\n    (:documentation \"Load the dependencies for the COMPILE-OP or LOAD-OP of a given COMPONENT.\"))\n  (defclass load-op (basic-load-op downward-operation selfward-operation)\n    ;; NB: even though compile-op depends on prepare-op it is not needed-in-image-p,\n    ;; so we need to directly depend on prepare-op for its side-effects in the current image.\n    ((selfward-operation :initform '(prepare-op compile-op) :allocation :class))\n    (:documentation \"Operation for loading the compiled FASL for a Lisp file\"))\n  (defclass compile-op (basic-compile-op downward-operation selfward-operation)\n    ((selfward-operation :initform 'prepare-op :allocation :class))\n    (:documentation \"Operation for compiling a Lisp file to a FASL\"))\n\n\n  (defclass prepare-source-op (upward-operation sideway-operation)\n    ((sideway-operation :initform 'load-source-op :allocation :class))\n    (:documentation \"Operation for loading the dependencies of a Lisp file as source.\"))\n  (defclass load-source-op (basic-load-op downward-operation selfward-operation)\n    ((selfward-operation :initform 'prepare-source-op :allocation :class))\n    (:documentation \"Operation for loading a Lisp file as source.\"))\n\n  (defclass test-op (selfward-operation)\n    ((selfward-operation :initform 'load-op :allocation :class))\n    (:documentation \"Operation for running the tests for system.\nIf the tests fail, an error will be signaled.\")))\n\n\n;;;; Methods for prepare-op, compile-op and load-op\n\n;;; prepare-op\n(with-upgradability ()\n  (defmethod action-description ((o prepare-op) (c component))\n    (format nil (compatfmt \"~@<loading dependencies of ~3i~_~A~@:>\") c))\n  (defmethod perform ((o prepare-op) (c component))\n    nil)\n  (defmethod input-files ((o prepare-op) (s system))\n    (if-let (it (system-source-file s)) (list it))))\n\n;;; compile-op\n(with-upgradability ()\n  (defmethod action-description ((o compile-op) (c component))\n    (format nil (compatfmt \"~@<compiling ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o compile-op) (c parent-component))\n    (format nil (compatfmt \"~@<completing compilation for ~3i~_~A~@:>\") c))\n  (defgeneric call-with-around-compile-hook (component thunk)\n    (:documentation \"A method to be called around the PERFORM'ing of actions that apply the\ncompile-time side-effects of file (i.e., COMPILE-OP or LOAD-SOURCE-OP). This method can be used\nto setup readtables and other variables that control reading, macroexpanding, and compiling, etc.\nNote that it will NOT be called around the performing of LOAD-OP.\"))\n  (defmethod call-with-around-compile-hook ((c component) function)\n    (call-around-hook (around-compile-hook c) function))\n  (defun perform-lisp-compilation (o c)\n    \"Perform the compilation of the Lisp file associated to the specified action (O . C).\"\n    (let (;; Before 2.26.53, that was unfortunately component-pathname. Now,\n          ;; we consult input-files, the first of which should be the one to compile-file\n          (input-file (first (input-files o c)))\n          ;; On some implementations, there are more than one output-file,\n          ;; but the first one should always be the primary fasl that gets loaded.\n          (outputs (output-files o c)))\n      (multiple-value-bind (output warnings-p failure-p)\n          (destructuring-bind\n              (output-file\n               &optional\n                 #+(or clasp ecl mkcl) object-file\n                 #+clisp lib-file\n                 warnings-file &rest rest) outputs\n            ;; Allow for extra outputs that are not of type warnings-file\n            ;; The way we do it is kludgy. In ASDF4, output-files shall not be positional.\n            (declare (ignore rest))\n            (when warnings-file\n              (unless (equal (pathname-type warnings-file) (warnings-file-type))\n                (setf warnings-file nil)))\n            (call-with-around-compile-hook\n             c #'(lambda (&rest flags)\n                   (apply 'compile-file* input-file\n                          :output-file output-file\n                          :external-format (component-external-format c)\n                          :warnings-file warnings-file\n                          (append\n                           #+clisp (list :lib-file lib-file)\n                           #+(or clasp ecl mkcl) (list :object-file object-file)\n                           flags)))))\n        (check-lisp-compile-results output warnings-p failure-p\n                                    \"~/asdf-action::format-action/\" (list (cons o c))))))\n  (defun report-file-p (f)\n    \"Is F a build report file containing, e.g., warnings to check?\"\n    (equalp (pathname-type f) \"build-report\"))\n  (defun perform-lisp-warnings-check (o c)\n    \"Check the warnings associated with the dependencies of an action.\"\n    (let* ((expected-warnings-files (remove-if-not #'warnings-file-p (input-files o c)))\n           (actual-warnings-files (loop :for w :in expected-warnings-files\n                                        :when (get-file-stamp w)\n                                          :collect w\n                                        :else :do (warn \"Missing warnings file ~S while ~A\"\n                                                        w (action-description o c)))))\n      (check-deferred-warnings actual-warnings-files)\n      (let* ((output (output-files o c))\n             (report (find-if #'report-file-p output)))\n        (when report\n          (with-open-file (s report :direction :output :if-exists :supersede)\n            (format s \":success~%\"))))))\n  (defmethod perform ((o compile-op) (c cl-source-file))\n    (perform-lisp-compilation o c))\n  (defun lisp-compilation-output-files (o c)\n    \"Compute the output-files for compiling the Lisp file for the specified action (O . C),\nan OPERATION and a COMPONENT.\"\n    (let* ((i (first (input-files o c)))\n           (f (compile-file-pathname\n               i #+clasp :output-type #+ecl :type #+(or clasp ecl) :fasl\n               #+mkcl :fasl-p #+mkcl t)))\n      `(,f ;; the fasl is the primary output, in first position\n        #+clasp\n        ,@(unless nil ;; was (use-ecl-byte-compiler-p)\n            `(,(compile-file-pathname i :output-type :object)))\n        #+clisp\n        ,@`(,(make-pathname :type \"lib\" :defaults f))\n        #+ecl\n        ,@(unless (use-ecl-byte-compiler-p)\n            `(,(compile-file-pathname i :type :object)))\n        #+mkcl\n        ,(compile-file-pathname i :fasl-p nil) ;; object file\n        ,@(when (and *warnings-file-type* (not (builtin-system-p (component-system c))))\n            `(,(make-pathname :type *warnings-file-type* :defaults f))))))\n  (defmethod output-files ((o compile-op) (c cl-source-file))\n    (lisp-compilation-output-files o c))\n  (defmethod perform ((o compile-op) (c static-file))\n    nil)\n\n  ;; Performing compile-op on a system will check the deferred warnings for the system\n  (defmethod perform ((o compile-op) (c system))\n    (when (and *warnings-file-type* (not (builtin-system-p c)))\n      (perform-lisp-warnings-check o c)))\n  (defmethod input-files ((o compile-op) (c system))\n    (when (and *warnings-file-type* (not (builtin-system-p c)))\n      ;; The most correct way to do it would be to use:\n      ;; (traverse-sub-actions o c :other-systems nil :keep-operation 'compile-op :keep-component 'cl-source-file)\n      ;; but it's expensive and we don't care too much about file order or ASDF extensions.\n      (loop :for sub :in (sub-components c :type 'cl-source-file)\n            :nconc (remove-if-not 'warnings-file-p (output-files o sub)))))\n  (defmethod output-files ((o compile-op) (c system))\n    (when (and *warnings-file-type* (not (builtin-system-p c)))\n      (if-let ((pathname (component-pathname c)))\n        (list (subpathname pathname (coerce-filename c) :type \"build-report\"))))))\n\n;;; load-op\n(with-upgradability ()\n  (defmethod action-description ((o load-op) (c cl-source-file))\n    (format nil (compatfmt \"~@<loading FASL for ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o load-op) (c parent-component))\n    (format nil (compatfmt \"~@<completing load for ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o load-op) (c component))\n    (format nil (compatfmt \"~@<loading ~3i~_~A~@:>\") c))\n  (defmethod perform-with-restarts ((o load-op) (c cl-source-file))\n    (loop\n      (restart-case\n          (return (call-next-method))\n        (try-recompiling ()\n          :report (lambda (s)\n                    (format s \"Recompile ~a and try loading it again\"\n                            (component-name c)))\n          (perform (find-operation o 'compile-op) c)))))\n  (defun perform-lisp-load-fasl (o c)\n    \"Perform the loading of a FASL associated to specified action (O . C),\nan OPERATION and a COMPONENT.\"\n    (if-let (fasl (first (input-files o c)))\n      (load* fasl)))\n  (defmethod perform ((o load-op) (c cl-source-file))\n    (perform-lisp-load-fasl o c))\n  (defmethod perform ((o load-op) (c static-file))\n    nil))\n\n\n;;;; prepare-source-op, load-source-op\n\n;;; prepare-source-op\n(with-upgradability ()\n  (defmethod action-description ((o prepare-source-op) (c component))\n    (format nil (compatfmt \"~@<loading source for dependencies of ~3i~_~A~@:>\") c))\n  (defmethod input-files ((o prepare-source-op) (s system))\n    (if-let (it (system-source-file s)) (list it)))\n  (defmethod perform ((o prepare-source-op) (c component))\n    nil))\n\n;;; load-source-op\n(with-upgradability ()\n  (defmethod action-description ((o load-source-op) (c component))\n    (format nil (compatfmt \"~@<Loading source of ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o load-source-op) (c parent-component))\n    (format nil (compatfmt \"~@<Loaded source of ~3i~_~A~@:>\") c))\n  (defun perform-lisp-load-source (o c)\n    \"Perform the loading of a Lisp file as associated to specified action (O . C)\"\n    (call-with-around-compile-hook\n     c #'(lambda ()\n           (load* (first (input-files o c))\n                  :external-format (component-external-format c)))))\n\n  (defmethod perform ((o load-source-op) (c cl-source-file))\n    (perform-lisp-load-source o c))\n  (defmethod perform ((o load-source-op) (c static-file))\n    nil))\n\n\n;;;; test-op\n(with-upgradability ()\n  (defmethod perform ((o test-op) (c component))\n    nil)\n  (defmethod operation-done-p ((o test-op) (c system))\n    \"Testing a system is _never_ done.\"\n    nil))\n;;;; -------------------------------------------------------------------------\n;;;; Plan\n\n(uiop/package:define-package :asdf/plan\n  ;; asdf/action below is needed for required-components, traverse-action and traverse-sub-actions\n  ;; that used to live there before 3.2.0.\n  (:recycle :asdf/plan :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/operation :asdf/system\n   :asdf/cache :asdf/find-system :asdf/find-component\n   :asdf/operation :asdf/action :asdf/lisp-action)\n  (:export\n   #:component-operation-time\n   #:plan #:plan-traversal #:sequential-plan #:*default-plan-class*\n   #:planned-action-status #:plan-action-status #:action-already-done-p\n   #:circular-dependency #:circular-dependency-actions\n   #:needed-in-image-p\n   #:action-index #:action-planned-p #:action-valid-p\n   #:plan-record-dependency\n   #:normalize-forced-systems #:action-forced-p #:action-forced-not-p\n   #:map-direct-dependencies #:reduce-direct-dependencies #:direct-dependencies\n   #:compute-action-stamp #:traverse-action\n   #:circular-dependency #:circular-dependency-actions\n   #:call-while-visiting-action #:while-visiting-action\n   #:make-plan #:plan-actions #:perform-plan #:plan-operates-on-p\n   #:planned-p #:index #:forced #:forced-not #:total-action-count\n   #:planned-action-count #:planned-output-action-count #:visited-actions\n   #:visiting-action-set #:visiting-action-list #:plan-actions-r\n   #:required-components #:filtered-sequential-plan\n   #:plan-system\n   #:plan-action-filter #:plan-component-type #:plan-keep-operation #:plan-keep-component\n   #:traverse-actions #:traverse-sub-actions))\n(in-package :asdf/plan)\n\n;;;; Generic plan traversal class\n(with-upgradability ()\n  (defclass plan () ()\n    (:documentation \"Base class for a plan based on which ASDF can build a system\"))\n  (defclass plan-traversal (plan)\n    (;; The system for which the plan is computed\n     (system :initform nil :initarg :system :accessor plan-system)\n     ;; Table of systems specified via :force arguments\n     (forced :initform nil :initarg :force :accessor plan-forced)\n     ;; Table of systems specified via :force-not argument (and/or immutable)\n     (forced-not :initform nil :initarg :force-not :accessor plan-forced-not)\n     ;; Counts of total actions in plan\n     (total-action-count :initform 0 :accessor plan-total-action-count)\n     ;; Count of actions that need to be performed\n     (planned-action-count :initform 0 :accessor plan-planned-action-count)\n     ;; Count of actions that need to be performed that have a non-empty list of output-files.\n     (planned-output-action-count :initform 0 :accessor plan-planned-output-action-count)\n     ;; Table that to actions already visited while walking the dependencies associates status\n     (visited-actions :initform (make-hash-table :test 'equal) :accessor plan-visited-actions)\n     ;; Actions that depend on those being currently walked through, to detect circularities\n     (visiting-action-set ;; as a set\n      :initform (make-hash-table :test 'equal) :accessor plan-visiting-action-set)\n     (visiting-action-list :initform () :accessor plan-visiting-action-list)) ;; as a list\n    (:documentation \"Base class for plans that simply traverse dependencies\")))\n\n\n;;;; Planned action status\n(with-upgradability ()\n  (defgeneric plan-action-status (plan operation component)\n    (:documentation \"Returns the ACTION-STATUS associated to\nthe action of OPERATION on COMPONENT in the PLAN\"))\n\n  (defgeneric (setf plan-action-status) (new-status plan operation component)\n    (:documentation \"Sets the ACTION-STATUS associated to\nthe action of OPERATION on COMPONENT in the PLAN\"))\n\n  (defclass planned-action-status (action-status)\n    ((planned-p\n      :initarg :planned-p :reader action-planned-p\n      :documentation \"a boolean, true iff the action was included in the plan.\")\n     (index\n      :initarg :index :reader action-index\n      :documentation \"an integer, counting all traversed actions in traversal order.\"))\n    (:documentation \"Status of an action in a plan\"))\n\n  (defmethod print-object ((status planned-action-status) stream)\n    (print-unreadable-object (status stream :type t :identity nil)\n      (with-slots (stamp done-p planned-p index) status\n        (format stream \"~@{~S~^ ~}\" :stamp stamp :done-p done-p :planned-p planned-p :index index))))\n\n  (defmethod action-planned-p ((action-status t))\n    t) ; default method for non planned-action-status objects\n\n  (defun action-already-done-p (plan operation component)\n    \"According to this plan, is this action already done and up to date?\"\n    (action-done-p (plan-action-status plan operation component)))\n\n  (defmethod plan-action-status ((plan null) (o operation) (c component))\n    (multiple-value-bind (stamp done-p) (component-operation-time o c)\n      (make-instance 'action-status :stamp stamp :done-p done-p)))\n\n  (defmethod (setf plan-action-status) (new-status (plan null) (o operation) (c component))\n    (let ((times (component-operation-times c)))\n      (if (action-done-p new-status)\n          (remhash o times)\n          (setf (gethash o times) (action-stamp new-status))))\n    new-status))\n\n\n;;;; forcing\n(with-upgradability ()\n  (defgeneric action-forced-p (plan operation component)\n    (:documentation \"Is this action forced to happen in this plan?\"))\n  (defgeneric action-forced-not-p (plan operation component)\n    (:documentation \"Is this action forced to not happen in this plan?\nTakes precedence over action-forced-p.\"))\n\n  (defun normalize-forced-systems (force system)\n    \"Given a SYSTEM on which operate is called and the specified FORCE argument,\nextract a hash-set of systems that are forced, or a predicate on system names,\nor NIL if none are forced, or :ALL if all are.\"\n    (etypecase force\n      ((or (member nil :all) hash-table function) force)\n      (cons (list-to-hash-set (mapcar #'coerce-name force)))\n      ((eql t) (when system (list-to-hash-set (list (coerce-name system)))))))\n\n  (defun normalize-forced-not-systems (force-not system)\n    \"Given a SYSTEM on which operate is called, the specified FORCE-NOT argument,\nand the set of IMMUTABLE systems, extract a hash-set of systems that are effectively forced-not,\nor predicate on system names, or NIL if none are forced, or :ALL if all are.\"\n    (let ((requested\n            (etypecase force-not\n              ((or (member nil :all) hash-table function) force-not)\n              (cons (list-to-hash-set (mapcar #'coerce-name force-not)))\n              ((eql t) (if system (let ((name (coerce-name system)))\n                                    #'(lambda (x) (not (equal x name))))\n                           :all)))))\n      (if (and *immutable-systems* requested)\n          #'(lambda (x) (or (call-function requested x)\n                            (call-function *immutable-systems* x)))\n          (or *immutable-systems* requested))))\n\n  ;; TODO: shouldn't we be looking up the primary system name, rather than the system name?\n  (defun action-override-p (plan operation component override-accessor)\n    \"Given a plan, an action, and a function that given the plan accesses a set of overrides\n(i.e. force or force-not), see if the override applies to the current action.\"\n    (declare (ignore operation))\n    (call-function (funcall override-accessor plan)\n                   (coerce-name (component-system (find-component () component)))))\n\n  (defmethod action-forced-p (plan operation component)\n    (and\n     ;; Did the user ask us to re-perform the action?\n     (action-override-p plan operation component 'plan-forced)\n     ;; You really can't force a builtin system and :all doesn't apply to it,\n     ;; except if it's the specifically the system currently being built.\n     (not (let ((system (component-system component)))\n            (and (builtin-system-p system)\n                 (not (eq system (plan-system plan))))))))\n\n  (defmethod action-forced-not-p (plan operation component)\n    ;; Did the user ask us to not re-perform the action?\n    ;; NB: force-not takes precedence over force, as it should\n    (action-override-p plan operation component 'plan-forced-not))\n\n  (defmethod action-forced-p ((plan null) (operation operation) (component component))\n    nil)\n\n  (defmethod action-forced-not-p ((plan null) (operation operation) (component component))\n    nil))\n\n\n;;;; action-valid-p\n(with-upgradability ()\n  (defgeneric action-valid-p (plan operation component)\n    (:documentation \"Is this action valid to include amongst dependencies?\"))\n  ;; :if-feature will invalidate actions on components for which the features don't apply.\n  (defmethod action-valid-p ((plan t) (o operation) (c component))\n    (if-let (it (component-if-feature c)) (featurep it) t))\n  ;; If either the operation or component was resolved to nil, the action is invalid.\n  (defmethod action-valid-p ((plan t) (o null) (c t)) nil)\n  (defmethod action-valid-p ((plan t) (o t) (c null)) nil)\n  ;; If the plan is null, i.e., we're looking at reality,\n  ;; then any action with actual operation and component objects is valid.\n  (defmethod action-valid-p ((plan null) (o operation) (c component)) t))\n\n;;;; Is the action needed in this image?\n(with-upgradability ()\n  (defgeneric needed-in-image-p (operation component)\n    (:documentation \"Is the action of OPERATION on COMPONENT needed in the current image\nto be meaningful, or could it just as well have been done in another Lisp image?\"))\n\n  (defmethod needed-in-image-p ((o operation) (c component))\n    ;; We presume that actions that modify the filesystem don't need be run\n    ;; in the current image if they have already been done in another,\n    ;; and can be run in another process (e.g. a fork),\n    ;; whereas those that don't are meant to side-effect the current image and can't.\n    (not (output-files o c))))\n\n\n;;;; Visiting dependencies of an action and computing action stamps\n(with-upgradability ()\n  (defun* (map-direct-dependencies) (plan operation component fun)\n    \"Call FUN on all the valid dependencies of the given action in the given plan\"\n    (loop* :for (dep-o-spec . dep-c-specs) :in (component-depends-on operation component)\n           :for dep-o = (find-operation operation dep-o-spec)\n           :when dep-o\n           :do (loop :for dep-c-spec :in dep-c-specs\n                     :for dep-c = (and dep-c-spec (resolve-dependency-spec component dep-c-spec))\n                     :when (and dep-c (action-valid-p plan dep-o dep-c))\n                       :do (funcall fun dep-o dep-c))))\n\n  (defun* (reduce-direct-dependencies) (plan operation component combinator seed)\n    \"Reduce the direct dependencies to a value computed by iteratively calling COMBINATOR\nfor each dependency action on the dependency's operation and component and an accumulator\ninitialized with SEED.\"\n    (map-direct-dependencies\n     plan operation component\n     #'(lambda (dep-o dep-c)\n         (setf seed (funcall combinator dep-o dep-c seed))))\n    seed)\n\n  (defun* (direct-dependencies) (plan operation component)\n    \"Compute a list of the direct dependencies of the action within the plan\"\n    (reverse (reduce-direct-dependencies plan operation component #'acons nil)))\n\n  ;; In a distant future, get-file-stamp, component-operation-time and latest-stamp\n  ;; shall also be parametrized by the plan, or by a second model object,\n  ;; so they need not refer to the state of the filesystem,\n  ;; and the stamps could be cryptographic checksums rather than timestamps.\n  ;; Such a change remarkably would only affect COMPUTE-ACTION-STAMP.\n\n  (defmethod compute-action-stamp (plan (o operation) (c component) &key just-done)\n    ;; Given an action, figure out at what time in the past it has been done,\n    ;; or if it has just been done, return the time that it has.\n    ;; Returns two values:\n    ;; 1- the TIMESTAMP of the action if it has already been done and is up to date,\n    ;;   or T is either hasn't been done or is out of date.\n    ;; 2- the DONE-IN-IMAGE-P boolean flag that is T if the action has already been done\n    ;;   in the current image, or NIL if it hasn't.\n    ;; Note that if e.g. LOAD-OP only depends on up-to-date files, but\n    ;; hasn't been done in the current image yet, then it can have a non-T timestamp,\n    ;; yet a NIL done-in-image-p flag: we can predict what timestamp it will have once loaded,\n    ;; i.e. that of the input-files.\n    (nest\n     (block ())\n     (let ((dep-stamp ; collect timestamp from dependencies (or T if forced or out-of-date)\n             (reduce-direct-dependencies\n              plan o c\n              #'(lambda (o c stamp)\n                  (if-let (it (plan-action-status plan o c))\n                    (latest-stamp stamp (action-stamp it))\n                    t))\n              nil)))\n       ;; out-of-date dependency: don't bother expensively querying the filesystem\n       (when (and (eq dep-stamp t) (not just-done)) (return (values t nil))))\n     ;; collect timestamps from inputs, and exit early if any is missing\n     (let* ((in-files (input-files o c))\n            (in-stamps (mapcar #'get-file-stamp in-files))\n            (missing-in (loop :for f :in in-files :for s :in in-stamps :unless s :collect f))\n            (latest-in (stamps-latest (cons dep-stamp in-stamps))))\n       (when (and missing-in (not just-done)) (return (values t nil))))\n     ;; collect timestamps from outputs, and exit early if any is missing\n     (let* ((out-files (remove-if 'null (output-files o c)))\n            (out-stamps (mapcar (if just-done 'register-file-stamp 'get-file-stamp) out-files))\n            (missing-out (loop :for f :in out-files :for s :in out-stamps :unless s :collect f))\n            (earliest-out (stamps-earliest out-stamps)))\n       (when (and missing-out (not just-done)) (return (values t nil))))\n     (let* (;; There are three kinds of actions:\n            (out-op (and out-files t)) ; those that create files on the filesystem\n            ;;(image-op (and in-files (null out-files))) ; those that load stuff into the image\n            ;;(null-op (and (null out-files) (null in-files))) ; placeholders that do nothing\n            ;; When was the thing last actually done? (Now, or ask.)\n            (op-time (or just-done (component-operation-time o c)))\n            ;; Time stamps from the files at hand, and whether any is missing\n            (all-present (not (or missing-in missing-out)))\n            ;; Has any input changed since we last generated the files?\n            (up-to-date-p (stamp<= latest-in earliest-out))\n            ;; If everything is up to date, the latest of inputs and outputs is our stamp\n            (done-stamp (stamps-latest (cons latest-in out-stamps))))\n       ;; Warn if some files are missing:\n       ;; either our model is wrong or some other process is messing with our files.\n       (when (and just-done (not all-present))\n         (warn \"~A completed without ~:[~*~;~*its input file~:p~2:*~{ ~S~}~*~]~\n                ~:[~; or ~]~:[~*~;~*its output file~:p~2:*~{ ~S~}~*~]\"\n               (action-description o c)\n               missing-in (length missing-in) (and missing-in missing-out)\n               missing-out (length missing-out))))\n     ;; Note that we use stamp<= instead of stamp< to play nice with generated files.\n     ;; Any race condition is intrinsic to the limited timestamp resolution.\n     (if (or just-done ;; The done-stamp is valid: if we're just done, or\n             ;; if all filesystem effects are up-to-date and there's no invalidating reason.\n             (and all-present up-to-date-p (operation-done-p o c) (not (action-forced-p plan o c))))\n         (values done-stamp ;; return the hard-earned timestamp\n                 (or just-done\n                     out-op ;; a file-creating op is done when all files are up to date\n                     ;; a image-effecting a placeholder op is done when it was actually run,\n                     (and op-time (eql op-time done-stamp)))) ;; with the matching stamp\n         ;; done-stamp invalid: return a timestamp in an indefinite future, action not done yet\n         (values t nil)))))\n\n\n;;;; Generic support for plan-traversal\n(with-upgradability ()\n  (defmethod initialize-instance :after ((plan plan-traversal)\n                                         &key force force-not system\n                                         &allow-other-keys)\n    (with-slots (forced forced-not) plan\n      (setf forced (normalize-forced-systems force system))\n      (setf forced-not (normalize-forced-not-systems force-not system))))\n\n  (defgeneric plan-actions (plan)\n    (:documentation \"Extract from a plan a list of actions to perform in sequence\"))\n  (defmethod plan-actions ((plan list))\n    plan)\n\n  (defmethod (setf plan-action-status) (new-status (p plan-traversal) (o operation) (c component))\n    (setf (gethash (cons o c) (plan-visited-actions p)) new-status))\n\n  (defmethod plan-action-status ((p plan-traversal) (o operation) (c component))\n    (or (and (action-forced-not-p p o c) (plan-action-status nil o c))\n        (values (gethash (cons o c) (plan-visited-actions p)))))\n\n  (defmethod action-valid-p ((p plan-traversal) (o operation) (s system))\n    (and (not (action-forced-not-p p o s)) (call-next-method)))\n\n  (defgeneric plan-record-dependency (plan operation component)\n    (:documentation \"Record an action as a dependency in the current plan\")))\n\n\n;;;; Detection of circular dependencies\n(with-upgradability ()\n  (define-condition circular-dependency (system-definition-error)\n    ((actions :initarg :actions :reader circular-dependency-actions))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Circular dependency: ~3i~_~S~@:>\")\n                       (circular-dependency-actions c)))))\n\n  (defgeneric call-while-visiting-action (plan operation component function)\n    (:documentation \"Detect circular dependencies\"))\n\n  (defmethod call-while-visiting-action ((plan plan-traversal) operation component fun)\n    (with-accessors ((action-set plan-visiting-action-set)\n                     (action-list plan-visiting-action-list)) plan\n      (let ((action (make-action operation component)))\n        (when (gethash action action-set)\n          (error 'circular-dependency :actions\n                 (member action (reverse action-list) :test 'equal)))\n        (setf (gethash action action-set) t)\n        (push action action-list)\n        (unwind-protect\n             (funcall fun)\n          (pop action-list)\n          (setf (gethash action action-set) nil)))))\n\n  ;; Syntactic sugar for call-while-visiting-action\n  (defmacro while-visiting-action ((p o c) &body body)\n    `(call-while-visiting-action ,p ,o ,c #'(lambda () ,@body))))\n\n\n;;;; Actual traversal: traverse-action\n(with-upgradability ()\n  (defgeneric traverse-action (plan operation component needed-in-image-p))\n\n  ;; TRAVERSE-ACTION, in the context of a given PLAN object that accumulates dependency data,\n  ;; visits the action defined by its OPERATION and COMPONENT arguments,\n  ;; and all its transitive dependencies (unless already visited),\n  ;; in the context of the action being (or not) NEEDED-IN-IMAGE-P,\n  ;; i.e. needs to be done in the current image vs merely have been done in a previous image.\n  ;; For actions that are up-to-date, it returns a STAMP identifying the state of the action\n  ;; (that's timestamp, but it could be a cryptographic digest in some ASDF extension),\n  ;; or T if the action needs to be done again.\n  ;;\n  ;; Note that for an XCVB-like plan with one-image-per-file-outputting-action,\n  ;; the below method would be insufficient, since it assumes a single image\n  ;; to traverse each node at most twice; non-niip actions would be traversed only once,\n  ;; but niip nodes could be traversed once per image, i.e. once plus once per non-niip action.\n\n  (defmethod traverse-action (plan operation component needed-in-image-p)\n    (block nil\n      ;; ACTION-VALID-P among other things, handles forcing logic, including FORCE-NOT,\n      ;; and IF-FEATURE filtering.\n      (unless (action-valid-p plan operation component) (return nil))\n      ;; the following hook is needed by POIU, which tracks a full dependency graph,\n      ;; instead of just a dependency order as in vanilla ASDF\n      (plan-record-dependency plan operation component)\n      ;; needed in image distinguishes b/w things that must happen in the\n      ;; current image and those things that simply need to have been done in a previous one.\n      (let* ((aniip (needed-in-image-p operation component)) ; action-specific needed-in-image\n             ;; effective niip: meaningful for the action and required by the plan as traversed\n             (eniip (and aniip needed-in-image-p))\n             ;; status: have we traversed that action previously, and if so what was its status?\n             (status (plan-action-status plan operation component)))\n        (when (and status (or (action-done-p status) (action-planned-p status) (not eniip)))\n          (return (action-stamp status))) ; Already visited with sufficient need-in-image level!\n        (labels ((visit-action (niip) ; We may visit the action twice, once with niip NIL, then T\n                   (map-direct-dependencies ; recursively traverse dependencies\n                    plan operation component #'(lambda (o c) (traverse-action plan o c niip)))\n                   (multiple-value-bind (stamp done-p) ; AFTER dependencies have been traversed,\n                       (compute-action-stamp plan operation component) ; compute action stamp\n                     (let ((add-to-plan-p (or (eql stamp t) (and niip (not done-p)))))\n                       (cond ; it needs be done if it's out of date or needed in image but absent\n                         ((and add-to-plan-p (not niip)) ; if we need to do it,\n                          (visit-action t)) ; then we need to do it *in the (current) image*!\n                         (t\n                          (setf (plan-action-status plan operation component) ; update status:\n                                (make-instance\n                                 'planned-action-status\n                                 :stamp stamp ; computed stamp\n                                 :done-p (and done-p (not add-to-plan-p)) ; done *and* up-to-date?\n                                 :planned-p add-to-plan-p ; included in list of things to be done?\n                                 :index (if status ; index of action amongst all nodes in traversal\n                                            (action-index status) ;; if already visited, keep index\n                                            (incf (plan-total-action-count plan))))) ; else new index\n                          (when (and done-p (not add-to-plan-p))\n                            (setf (component-operation-time operation component) stamp))\n                          (when add-to-plan-p ; if it needs to be added to the plan,\n                            (incf (plan-planned-action-count plan)) ; count it\n                            (unless aniip ; if it's output-producing,\n                              (incf (plan-planned-output-action-count plan)))) ; count it\n                          stamp)))))) ; return the stamp\n          (while-visiting-action (plan operation component) ; maintain context, handle circularity.\n            (visit-action eniip))))))) ; visit the action\n\n\n;;;; Sequential plans (the default)\n(with-upgradability ()\n  (defclass sequential-plan (plan-traversal)\n    ((actions-r :initform nil :accessor plan-actions-r))\n    (:documentation \"Simplest, default plan class, accumulating a sequence of actions\"))\n\n  (defmethod plan-actions ((plan sequential-plan))\n    (reverse (plan-actions-r plan)))\n\n  ;; No need to record a dependency to build a full graph, just accumulate nodes in order.\n  (defmethod plan-record-dependency ((plan sequential-plan) (o operation) (c component))\n    (values))\n\n  (defmethod (setf plan-action-status) :after\n      (new-status (p sequential-plan) (o operation) (c component))\n    (when (action-planned-p new-status)\n      (push (make-action o c) (plan-actions-r p)))))\n\n\n;;;; High-level interface: traverse, perform-plan, plan-operates-on-p\n(with-upgradability ()\n  (defgeneric make-plan (plan-class operation component &key &allow-other-keys)\n    (:documentation \"Generate and return a plan for performing OPERATION on COMPONENT.\"))\n  (define-convenience-action-methods make-plan (plan-class operation component &key))\n\n  (defgeneric perform-plan (plan &key)\n    (:documentation \"Actually perform a plan and build the requested actions\"))\n  (defgeneric plan-operates-on-p (plan component)\n    (:documentation \"Does this PLAN include any operation on given COMPONENT?\"))\n\n  (defvar *default-plan-class* 'sequential-plan\n    \"The default plan class to use when building with ASDF\")\n\n  (defmethod make-plan (plan-class (o operation) (c component) &rest keys &key &allow-other-keys)\n    (let ((plan (apply 'make-instance (or plan-class *default-plan-class*)\n                       :system (component-system c) keys)))\n      (traverse-action plan o c t)\n      plan))\n\n  (defmethod perform-plan :around ((plan t) &key)\n    #+xcl (declare (ignorable plan))\n    (let ((*package* *package*)\n          (*readtable* *readtable*))\n      (with-compilation-unit () ;; backward-compatibility.\n        (call-next-method))))   ;; Going forward, see deferred-warning support in lisp-build.\n\n  (defmethod perform-plan ((plan t) &rest keys &key &allow-other-keys)\n    (apply 'perform-plan (plan-actions plan) keys))\n\n  (defmethod perform-plan ((steps list) &key force &allow-other-keys)\n    (loop* :for action :in steps\n           :as o = (action-operation action)\n           :as c = (action-component action)\n           :when (or force (not (nth-value 1 (compute-action-stamp nil o c))))\n           :do (perform-with-restarts o c)))\n\n  (defmethod plan-operates-on-p ((plan plan-traversal) (component-path list))\n    (plan-operates-on-p (plan-actions plan) component-path))\n\n  (defmethod plan-operates-on-p ((plan list) (component-path list))\n    (find component-path (mapcar 'action-component plan)\n          :test 'equal :key 'component-find-path)))\n\n\n;;;; Incidental traversals\n\n;;; Making a FILTERED-SEQUENTIAL-PLAN can be used to, e.g., all of the source\n;;; files required by a bundling operation.\n(with-upgradability ()\n  (defclass filtered-sequential-plan (sequential-plan)\n    ((action-filter :initform t :initarg :action-filter :reader plan-action-filter)\n     (component-type :initform t :initarg :component-type :reader plan-component-type)\n     (keep-operation :initform t :initarg :keep-operation :reader plan-keep-operation)\n     (keep-component :initform t :initarg :keep-component :reader plan-keep-component))\n    (:documentation \"A variant of SEQUENTIAL-PLAN that only records a subset of actions.\"))\n\n  (defmethod initialize-instance :after ((plan filtered-sequential-plan)\n                                         &key force force-not\n                                         other-systems)\n    (declare (ignore force force-not))\n    ;; Ignore force and force-not, rely on other-systems:\n    ;; force traversal of what we're interested in, i.e. current system or also others;\n    ;; force-not traversal of what we're not interested in, i.e. other systems unless other-systems.\n    (with-slots (forced forced-not action-filter system) plan\n      (setf forced (normalize-forced-systems (if other-systems :all t) system))\n      (setf forced-not (normalize-forced-not-systems (if other-systems nil t) system))\n      (setf action-filter (ensure-function action-filter))))\n\n  (defmethod action-valid-p ((plan filtered-sequential-plan) o c)\n    (and (funcall (plan-action-filter plan) o c)\n         (typep c (plan-component-type plan))\n         (call-next-method)))\n\n  (defun* (traverse-actions) (actions &rest keys &key plan-class &allow-other-keys)\n    \"Given a list of actions, build a plan with these actions as roots.\"\n    (let ((plan (apply 'make-instance (or plan-class 'filtered-sequential-plan) keys)))\n      (loop* :for action :in actions\n             :as o = (action-operation action)\n             :as c = (action-component action)\n             :do (traverse-action plan o c t))\n      plan))\n\n  (defgeneric traverse-sub-actions (operation component &key &allow-other-keys))\n  (define-convenience-action-methods traverse-sub-actions (operation component &key))\n  (defmethod traverse-sub-actions ((operation operation) (component component)\n                                   &rest keys &key &allow-other-keys)\n    (apply 'traverse-actions (direct-dependencies t operation component)\n           :system (component-system component) keys))\n\n  (defmethod plan-actions ((plan filtered-sequential-plan))\n    (with-slots (keep-operation keep-component) plan\n      (loop* :for action :in (call-next-method)\n             :as o = (action-operation action)\n             :as c = (action-component action)\n             :when (and (typep o keep-operation) (typep c keep-component))\n             :collect (make-action o c))))\n\n  (defun* (required-components) (system &rest keys &key (goal-operation 'load-op) &allow-other-keys)\n    \"Given a SYSTEM and a GOAL-OPERATION (default LOAD-OP), traverse the dependencies and\nreturn a list of the components involved in building the desired action.\"\n    (remove-duplicates\n     (mapcar 'action-component\n             (plan-actions\n              (apply 'traverse-sub-actions goal-operation system\n                     (remove-plist-key :goal-operation keys))))\n     :from-end t)))\n\n;;;; -------------------------------------------------------------------------\n;;;; Invoking Operations\n\n(uiop/package:define-package :asdf/operate\n  (:recycle :asdf/operate :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/cache\n   :asdf/component :asdf/system :asdf/operation :asdf/action\n   :asdf/find-system :asdf/find-component :asdf/lisp-action :asdf/plan)\n  (:export\n   #:operate #:oos\n   #:build-op #:make\n   #:load-system #:load-systems #:load-systems*\n   #:compile-system #:test-system #:require-system\n   #:module-provide-asdf\n   #:component-loaded-p #:already-loaded-systems))\n(in-package :asdf/operate)\n\n(with-upgradability ()\n  (defgeneric operate (operation component &key &allow-other-keys)\n    (:documentation\n     \"Operate does mainly four things for the user:\n\n1. Resolves the OPERATION designator into an operation object.\n   OPERATION is typically a symbol denoting an operation class, instantiated with MAKE-OPERATION.\n2. Resolves the COMPONENT designator into a component object.\n   COMPONENT is typically a string or symbol naming a system, loaded from disk using FIND-SYSTEM.\n3. It then calls MAKE-PLAN with the operation and system as arguments.\n4. Finally calls PERFORM-PLAN on the resulting plan to actually build the system.\n\nThe entire computation is wrapped in WITH-COMPILATION-UNIT and error handling code.\nIf a VERSION argument is supplied, then operate also ensures that the system found satisfies it\nusing the VERSION-SATISFIES method.\nIf a PLAN-CLASS argument is supplied, that class is used for the plan.\n\nThe :FORCE or :FORCE-NOT argument to OPERATE can be:\n  T to force the inside of the specified system to be rebuilt (resp. not),\n    without recursively forcing the other systems we depend on.\n  :ALL to force all systems including other systems we depend on to be rebuilt (resp. not).\n  (SYSTEM1 SYSTEM2 ... SYSTEMN) to force systems named in a given list\n:FORCE-NOT has precedence over :FORCE; builtin systems cannot be forced.\n\nFor backward compatibility, all keyword arguments are passed to MAKE-OPERATION\nwhen instantiating a new operation, that will in turn be inherited by new operations.\nBut do NOT depend on it, for this is deprecated behavior.\"))\n\n  (define-convenience-action-methods operate (operation component &key)\n    :if-no-component (error 'missing-component :requires component))\n\n  (defvar *in-operate* nil\n    \"Are we in operate?\")\n\n  ;; This method ensures that an ASDF upgrade is attempted as the very first thing,\n  ;; with suitable state preservation in case in case it actually happens,\n  ;; and that a few suitable dynamic bindings are established.\n  (defmethod operate :around (operation component &rest keys\n                              &key verbose\n                                (on-warnings *compile-file-warnings-behaviour*)\n                                (on-failure *compile-file-failure-behaviour*) &allow-other-keys)\n    (nest\n     (with-asdf-cache ())\n     (let ((in-operate *in-operate*)\n           (*in-operate* t)\n           (operation-remaker ;; how to remake the operation after ASDF was upgraded (if it was)\n            (etypecase operation\n              (operation (let ((name (type-of operation)))\n                           #'(lambda () (make-operation name))))\n              ((or symbol string) (constantly operation))))\n           (component-path (typecase component ;; to remake the component after ASDF upgrade\n                             (component (component-find-path component))\n                             (t component)))))\n     ;; Before we operate on any system, make sure ASDF is up-to-date,\n     ;; for if an upgrade is ever attempted at any later time, there may be BIG trouble.\n     (progn\n       (unless in-operate\n         (when (upgrade-asdf)\n           ;; If we were upgraded, restart OPERATE the hardest of ways, for\n           ;; its function may have been redefined.\n           (return-from operate\n             (apply 'operate (funcall operation-remaker) component-path keys)))))\n      ;; Setup proper bindings around any operate call.\n     (let* ((*verbose-out* (and verbose *standard-output*))\n            (*compile-file-warnings-behaviour* on-warnings)\n            (*compile-file-failure-behaviour* on-failure))\n       (call-next-method))))\n\n  (defmethod operate :before ((operation operation) (component component)\n                              &key version &allow-other-keys)\n    (unless (version-satisfies component version)\n      (error 'missing-component-of-version :requires component :version version)))\n\n  (defmethod operate ((operation operation) (component component)\n                      &rest keys &key plan-class &allow-other-keys)\n    (let ((plan (apply 'make-plan plan-class operation component keys)))\n      (apply 'perform-plan plan keys)\n      (values operation plan)))\n\n  (defun oos (operation component &rest args &key &allow-other-keys)\n    (apply 'operate operation component args))\n\n  (setf (documentation 'oos 'function)\n        (format nil \"Short for _operate on system_ and an alias for the OPERATE function.~%~%~a\"\n                (documentation 'operate 'function))))\n\n\n;;;; Common operations\n(when-upgrading ()\n  (defmethod component-depends-on ((o prepare-op) (s system))\n    (call-next-method)))\n(with-upgradability ()\n  (defclass build-op (non-propagating-operation) ()\n    (:documentation \"Since ASDF3, BUILD-OP is the recommended 'master' operation,\nto operate by default on a system or component, via the function BUILD.\nIts meaning is configurable via the :BUILD-OPERATION option of a component.\nwhich typically specifies the name of a specific operation to which to delegate the build,\nas a symbol or as a string later read as a symbol (after loading the defsystem-depends-on);\nif NIL is specified (the default), BUILD-OP falls back to LOAD-OP,\nthat will load the system in the current image.\"))\n  (defmethod component-depends-on ((o build-op) (c component))\n    `((,(or (component-build-operation c) 'load-op) ,c)\n      ,@(call-next-method)))\n\n  (defun make (system &rest keys)\n    \"The recommended way to interact with ASDF3.1 is via (ASDF:MAKE :FOO).\nIt will build system FOO using the operation BUILD-OP,\nthe meaning of which is configurable by the system, and\ndefaults to LOAD-OP, to load it in current image.\"\n    (apply 'operate 'build-op system keys)\n    t)\n\n  (defun load-system (system &rest keys &key force force-not verbose version &allow-other-keys)\n    \"Shorthand for `(operate 'asdf:load-op system)`. See OPERATE for details.\"\n    (declare (ignore force force-not verbose version))\n    (apply 'operate 'load-op system keys)\n    t)\n\n  (defun load-systems* (systems &rest keys)\n    \"Loading multiple systems at once.\"\n    (dolist (s systems) (apply 'load-system s keys)))\n\n  (defun load-systems (&rest systems)\n    \"Loading multiple systems at once.\"\n    (load-systems* systems))\n\n  (defun compile-system (system &rest args &key force force-not verbose version &allow-other-keys)\n    \"Shorthand for `(asdf:operate 'asdf:compile-op system)`. See OPERATE for details.\"\n    (declare (ignore force force-not verbose version))\n    (apply 'operate 'compile-op system args)\n    t)\n\n  (defun test-system (system &rest args &key force force-not verbose version &allow-other-keys)\n    \"Shorthand for `(asdf:operate 'asdf:test-op system)`. See OPERATE for details.\"\n    (declare (ignore force force-not verbose version))\n    (apply 'operate 'test-op system args)\n    t))\n\n;;;;; Define the function REQUIRE-SYSTEM, that, similarly to REQUIRE,\n;; only tries to load its specified target if it's not loaded yet.\n(with-upgradability ()\n  (defun component-loaded-p (component)\n    \"Has the given COMPONENT been successfully loaded in the current image (yet)?\nNote that this returns true even if the component is not up to date.\"\n    (if-let ((component (find-component component () :registered t)))\n      (action-already-done-p nil (make-operation 'load-op) component)))\n\n  (defun already-loaded-systems ()\n    \"return a list of the names of the systems that have been successfully loaded so far\"\n    (mapcar 'coerce-name (remove-if-not 'component-loaded-p (registered-systems*))))\n\n  (defun require-system (system &rest keys &key &allow-other-keys)\n    \"Ensure the specified SYSTEM is loaded, passing the KEYS to OPERATE, but do not update the\nsystem or its dependencies if they have already been loaded.\"\n    (unless (component-loaded-p system)\n      (apply 'load-system system :force-not (already-loaded-systems) keys))))\n\n\n;;;; Define the class REQUIRE-SYSTEM, to be hooked into CL:REQUIRE when possible,\n;; i.e. for ABCL, CLISP, ClozureCL, CMUCL, ECL, MKCL and SBCL\n;; Note that despite the two being homonyms, the _function_ require-system\n;; and the _class_ require-system are quite distinct entities, fulfilling independent purposes.\n(with-upgradability ()\n  (defvar *modules-being-required* nil)\n\n  (defclass require-system (system)\n    ((module :initarg :module :initform nil :accessor required-module))\n    (:documentation \"A SYSTEM subclass whose processing is handled by\nthe implementation's REQUIRE rather than by internal ASDF mechanisms.\"))\n\n  (defmethod perform ((o compile-op) (c require-system))\n    nil)\n\n  (defmethod perform ((o load-op) (s require-system))\n    (let* ((module (or (required-module s) (coerce-name s)))\n           (*modules-being-required* (cons module *modules-being-required*)))\n      (assert (null (component-children s)))\n      (require module)))\n\n  (defmethod resolve-dependency-combination (component (combinator (eql :require)) arguments)\n    (unless (and (length=n-p arguments 1)\n                 (typep (car arguments) '(or string (and symbol (not null)))))\n      (parameter-error (compatfmt \"~@<In ~S, bad dependency ~S for ~S. ~S takes one argument, a string or non-null symbol~@:>\")\n                       'resolve-dependency-combination\n                       (cons combinator arguments) component combinator))\n    ;; :require must be prepared for some implementations providing modules using ASDF,\n    ;; as SBCL used to do, and others may might do. Thus, the system provided in the end\n    ;; would be a downcased name as per module-provide-asdf above. For the same reason,\n    ;; we cannot assume that the system in the end will be of type require-system,\n    ;; but must check whether we can use find-system and short-circuit cl:require.\n    ;; Otherwise, calling cl:require could result in nasty reentrant calls between\n    ;; cl:require and asdf:operate that could potentially blow up the stack,\n    ;; all the while defeating the consistency of the dependency graph.\n    (let* ((module (car arguments)) ;; NB: we already checked that it was not null\n           ;; CMUCL, MKCL, SBCL like their module names to be all upcase.\n           (module-name (string module))\n           (system-name (string-downcase module))\n           (system (find-system system-name nil)))\n      (or system (let ((system (make-instance 'require-system :name system-name :module module-name)))\n                   (register-system system)\n                   system))))\n\n  (defun module-provide-asdf (name)\n    ;; We must use string-downcase, because modules are traditionally specified as symbols,\n    ;; that implementations traditionally normalize as uppercase, for which we seek a system\n    ;; with a name that is traditionally in lowercase. Case is lost along the way. That's fine.\n    ;; We could make complex, non-portable rules to try to preserve case, and just documenting\n    ;; them would be a hell that it would be a disservice to inflict on users.\n    (let ((module-name (string name))\n          (system-name (string-downcase name)))\n      (unless (member module-name *modules-being-required* :test 'equal)\n        (let ((*modules-being-required* (cons module-name *modules-being-required*))\n              #+sbcl (sb-impl::*requiring* (remove module-name sb-impl::*requiring* :test 'equal)))\n          (handler-bind\n              ((style-warning #'muffle-warning)\n               (missing-component (constantly nil))\n               (fatal-condition\n                #'(lambda (e)\n                    (format *error-output* (compatfmt \"~@<ASDF could not load ~(~A~) because ~A.~@:>~%\")\n                            name e))))\n            (let ((*verbose-out* (make-broadcast-stream)))\n              (let ((system (find-system system-name nil)))\n                (when system\n                  (require-system system-name :verbose nil)\n                  t)))))))))\n\n\n;;;; Some upgrade magic\n(with-upgradability ()\n  (defun restart-upgraded-asdf ()\n    ;; If we're in the middle of something, restart it.\n    (let ((systems-being-defined\n           (when *asdf-cache*\n             (prog1\n                 (loop :for k :being :the hash-keys :of *asdf-cache*\n                   :when (eq (first k) 'find-system) :collect (second k))\n               (clrhash *asdf-cache*)))))\n      ;; Regardless, clear defined systems, since they might be invalid\n      ;; after an incompatible ASDF upgrade.\n      (clear-defined-systems)\n      ;; The configuration also may have to be upgraded.\n      (upgrade-configuration)\n      ;; If we were in the middle of an operation, be sure to restore the system being defined.\n      (dolist (s systems-being-defined) (find-system s nil))))\n  (register-hook-function '*post-upgrade-cleanup-hook* 'restart-upgraded-asdf)\n\n  ;; The following function's symbol is from asdf/find-system.\n  ;; It is defined here to resolve what would otherwise be forward package references.\n  (defun mark-component-preloaded (component)\n    \"Mark a component as preloaded.\"\n    (let ((component (find-component component nil :registered t)))\n      ;; Recurse to children, so asdf/plan will hopefully be happy.\n      (map () 'mark-component-preloaded (component-children component))\n      ;; Mark the timestamps of the common lisp-action operations as 0.\n      (let ((times (component-operation-times component)))\n        (dolist (o '(load-op compile-op prepare-op))\n          (setf (gethash (make-operation o) times) 0))))))\n\n;;;; -------------------------------------------------------------------------\n;;;; Defsystem\n\n(uiop/package:define-package :asdf/parse-defsystem\n  (:recycle :asdf/parse-defsystem :asdf/defsystem :asdf)\n  (:nicknames :asdf/defsystem) ;; previous name, to be compatible with, in case anyone cares\n  (:use :uiop/common-lisp :asdf/driver :asdf/upgrade\n   :asdf/cache :asdf/component :asdf/system\n   :asdf/find-system :asdf/find-component :asdf/action :asdf/lisp-action :asdf/operate)\n  (:import-from :asdf/system #:depends-on #:weakly-depends-on)\n  (:export\n   #:defsystem #:register-system-definition\n   #:class-for-type #:*default-component-class*\n   #:determine-system-directory #:parse-component-form\n   #:non-toplevel-system #:non-system-system #:bad-system-name\n   #:sysdef-error-component #:check-component-input))\n(in-package :asdf/parse-defsystem)\n\n;;; Pathname\n(with-upgradability ()\n  (defun determine-system-directory (pathname)\n    ;; The defsystem macro calls this function to determine the pathname of a system as follows:\n    ;; 1. If the pathname argument is an pathname object (NOT a namestring),\n    ;;    that is already an absolute pathname, return it.\n    ;; 2. Otherwise, the directory containing the LOAD-PATHNAME\n    ;;    is considered (as deduced from e.g. *LOAD-PATHNAME*), and\n    ;;    if it is indeed available and an absolute pathname, then\n    ;;    the PATHNAME argument is normalized to a relative pathname\n    ;;    as per PARSE-UNIX-NAMESTRING (with ENSURE-DIRECTORY T)\n    ;;    and merged into that DIRECTORY as per SUBPATHNAME.\n    ;;    Note: avoid *COMPILE-FILE-PATHNAME* because the .asd is loaded as source,\n    ;;    but may be from within the EVAL-WHEN of a file compilation.\n    ;; If no absolute pathname was found, we return NIL.\n    (check-type pathname (or null string pathname))\n    (pathname-directory-pathname\n     (resolve-symlinks*\n      (ensure-absolute-pathname\n       (parse-unix-namestring pathname :type :directory)\n       #'(lambda () (ensure-absolute-pathname\n                     (load-pathname) 'get-pathname-defaults nil))\n       nil)))))\n\n\n;;; Component class\n(with-upgradability ()\n  ;; What :file gets interpreted as, unless overridden by a :default-component-class\n  (defvar *default-component-class* 'cl-source-file)\n\n  (defun class-for-type (parent type)\n      (or (coerce-class type :package :asdf/interface :super 'component :error nil)\n          (and (eq type :file)\n               (coerce-class\n                (or (loop :for p = parent :then (component-parent p) :while p\n                      :thereis (module-default-component-class p))\n                    *default-component-class*)\n                :package :asdf/interface :super 'component :error nil))\n          (sysdef-error \"don't recognize component type ~S\" type))))\n\n\n;;; Check inputs\n(with-upgradability ()\n  (define-condition non-system-system (system-definition-error)\n    ((name :initarg :name :reader non-system-system-name)\n     (class-name :initarg :class-name :reader non-system-system-class-name))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while defining system ~S: class ~S isn't a subclass of ~S~@:>\")\n                       (non-system-system-name c) (non-system-system-class-name c) 'system))))\n\n  (define-condition non-toplevel-system (system-definition-error)\n    ((parent :initarg :parent :reader non-toplevel-system-parent)\n     (name :initarg :name :reader non-toplevel-system-name))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while defining system: component ~S claims to have a system ~S as a child~@:>\")\n                       (non-toplevel-system-parent c) (non-toplevel-system-name c)))))\n\n  (define-condition bad-system-name (warning)\n    ((name :initarg :name :reader component-name)\n     (source-file :initarg :source-file :reader system-source-file))\n    (:report (lambda (c s)\n               (let* ((file (system-source-file c))\n                      (name (component-name c))\n                      (asd (pathname-name file)))\n                 (format s (compatfmt \"~@<System definition file ~S contains definition for system ~S. ~\nPlease only define ~S and secondary systems with a name starting with ~S (e.g. ~S) in that file.~@:>\")\n                       file name asd (strcat asd \"/\") (strcat asd \"/test\"))))))\n\n  (defun sysdef-error-component (msg type name value)\n    (sysdef-error (strcat msg (compatfmt \"~&~@<The value specified for ~(~A~) ~A is ~S~@:>\"))\n                  type name value))\n\n  (defun check-component-input (type name weakly-depends-on\n                                depends-on components)\n    \"A partial test of the values of a component.\"\n    (unless (listp depends-on)\n      (sysdef-error-component \":depends-on must be a list.\"\n                              type name depends-on))\n    (unless (listp weakly-depends-on)\n      (sysdef-error-component \":weakly-depends-on must be a list.\"\n                              type name weakly-depends-on))\n    (unless (listp components)\n      (sysdef-error-component \":components must be NIL or a list of components.\"\n                              type name components)))\n\n  ;; Given a form used as :version specification, in the context of a system definition\n  ;; in a file at PATHNAME, for given COMPONENT with given PARENT, normalize the form\n  ;; to an acceptable ASDF-format version.\n  (defun* (normalize-version) (form &key pathname component parent)\n    (labels ((invalid (&optional (continuation \"using NIL instead\"))\n               (warn (compatfmt \"~@<Invalid :version specifier ~S~@[ for component ~S~]~@[ in ~S~]~@[ from file ~S~]~@[, ~A~]~@:>\")\n                     form component parent pathname continuation))\n             (invalid-parse (control &rest args)\n               (unless (if-let (target (find-component parent component)) (builtin-system-p target))\n                 (apply 'warn control args)\n                 (invalid))))\n      (if-let (v (typecase form\n                   ((or string null) form)\n                   (real\n                    (invalid \"Substituting a string\")\n                    (format nil \"~D\" form)) ;; 1.0 becomes \"1.0\"\n                   (cons\n                    (case (first form)\n                      ((:read-file-form)\n                       (destructuring-bind (subpath &key (at 0)) (rest form)\n                         (safe-read-file-form (subpathname pathname subpath)\n                                              :at at :package :asdf-user)))\n                      ((:read-file-line)\n                       (destructuring-bind (subpath &key (at 0)) (rest form)\n                         (safe-read-file-line (subpathname pathname subpath)\n                                              :at at)))\n                      (otherwise\n                       (invalid))))\n                   (t\n                    (invalid))))\n        (if-let (pv (parse-version v #'invalid-parse))\n          (unparse-version pv)\n          (invalid))))))\n\n\n;;; \"inline methods\"\n(with-upgradability ()\n  (defparameter* +asdf-methods+\n    '(perform-with-restarts perform explain output-files operation-done-p))\n\n  (defun %remove-component-inline-methods (component)\n    (dolist (name +asdf-methods+)\n      (map ()\n           ;; this is inefficient as most of the stored\n           ;; methods will not be for this particular gf\n           ;; But this is hardly performance-critical\n           #'(lambda (m)\n               (remove-method (symbol-function name) m))\n           (component-inline-methods component)))\n    (component-inline-methods component) nil)\n\n  (defun %define-component-inline-methods (ret rest)\n    (loop* :for (key value) :on rest :by #'cddr\n           :for name = (and (keywordp key) (find key +asdf-methods+ :test 'string=))\n           :when name :do\n           (destructuring-bind (op &rest body) value\n             (loop :for arg = (pop body)\n                   :while (atom arg)\n                   :collect arg :into qualifiers\n                   :finally\n                      (destructuring-bind (o c) arg\n                        (pushnew\n                         (eval `(defmethod ,name ,@qualifiers ((,o ,op) (,c (eql ,ret))) ,@body))\n                         (component-inline-methods ret)))))))\n\n  (defun %refresh-component-inline-methods (component rest)\n    ;; clear methods, then add the new ones\n    (%remove-component-inline-methods component)\n    (%define-component-inline-methods component rest)))\n\n\n;;; Main parsing function\n(with-upgradability ()\n  (defun parse-dependency-def (dd)\n    (if (listp dd)\n        (case (first dd)\n          (:feature\n           (unless (= (length dd) 3)\n             (sysdef-error \"Ill-formed feature dependency: ~s\" dd))\n           (let ((embedded (parse-dependency-def (third dd))))\n             `(:feature ,(second dd) ,embedded)))\n          (feature\n           (sysdef-error \"`feature' has been removed from the dependency spec language of ASDF. Use :feature instead in ~s.\" dd))\n          (:require\n           (unless (= (length dd) 2)\n             (sysdef-error \"Ill-formed require dependency: ~s\" dd))\n           dd)\n          (:version\n           (unless (= (length dd) 3)\n             (sysdef-error \"Ill-formed version dependency: ~s\" dd))\n           `(:version ,(coerce-name (second dd)) ,(third dd)))\n          (otherwise (sysdef-error \"Ill-formed dependency: ~s\" dd)))\n      (coerce-name dd)))\n\n  (defun parse-dependency-defs (dd-list)\n    \"Parse the dependency defs in DD-LIST into canonical form by translating all\nsystem names contained using COERCE-NAME. Return the result.\"\n    (mapcar 'parse-dependency-def dd-list))\n\n  (defun* (parse-component-form) (parent options &key previous-serial-component)\n    (destructuring-bind\n        (type name &rest rest &key\n                                (builtin-system-p () bspp)\n                                ;; the following list of keywords is reproduced below in the\n                                ;; remove-plist-keys form.  important to keep them in sync\n                                components pathname perform explain output-files operation-done-p\n                                weakly-depends-on depends-on serial\n                                do-first if-component-dep-fails version\n                                ;; list ends\n         &allow-other-keys) options\n      (declare (ignore perform explain output-files operation-done-p builtin-system-p))\n      (check-component-input type name weakly-depends-on depends-on components)\n      (when (and parent\n                 (find-component parent name)\n                 (not ;; ignore the same object when rereading the defsystem\n                  (typep (find-component parent name)\n                         (class-for-type parent type))))\n        (error 'duplicate-names :name name))\n      (when do-first (error \"DO-FIRST is not supported anymore as of ASDF 3\"))\n      (let* ((name (coerce-name name))\n             (args `(:name ,name\n                     :pathname ,pathname\n                     ,@(when parent `(:parent ,parent))\n                     ,@(remove-plist-keys\n                        '(:components :pathname :if-component-dep-fails :version\n                          :perform :explain :output-files :operation-done-p\n                          :weakly-depends-on :depends-on :serial)\n                        rest)))\n             (component (find-component parent name))\n             (class (class-for-type parent type)))\n        (when (and parent (subtypep class 'system))\n          (error 'non-toplevel-system :parent parent :name name))\n        (if component ; preserve identity\n            (apply 'reinitialize-instance component args)\n            (setf component (apply 'make-instance class args)))\n        (component-pathname component) ; eagerly compute the absolute pathname\n        (when (typep component 'system)\n          ;; cache information for introspection\n          (setf (slot-value component 'depends-on)\n                (parse-dependency-defs depends-on)\n                (slot-value component 'weakly-depends-on)\n                ;; these must be a list of systems, cannot be features or versioned systems\n                (mapcar 'coerce-name weakly-depends-on)))\n        (let ((sysfile (system-source-file (component-system component)))) ;; requires the previous\n          (when (and (typep component 'system) (not bspp))\n            (setf (builtin-system-p component) (lisp-implementation-pathname-p sysfile)))\n          (setf version (normalize-version version :component name :parent parent :pathname sysfile)))\n        ;; Don't use the accessor: kluge to avoid upgrade issue on CCL 1.8.\n        ;; A better fix is required.\n        (setf (slot-value component 'version) version)\n        (when (typep component 'parent-component)\n          (setf (component-children component)\n                (loop\n                  :with previous-component = nil\n                  :for c-form :in components\n                  :for c = (parse-component-form component c-form\n                                                 :previous-serial-component previous-component)\n                  :for name = (component-name c)\n                  :collect c\n                  :when serial :do (setf previous-component name)))\n          (compute-children-by-name component))\n        (when previous-serial-component\n          (push previous-serial-component depends-on))\n        (when weakly-depends-on\n          ;; ASDF4: deprecate this feature and remove it.\n          (appendf depends-on\n                   (remove-if (complement #'(lambda (x) (find-system x nil))) weakly-depends-on)))\n        ;; Used by POIU. ASDF4: rename to component-depends-on?\n        (setf (component-sideway-dependencies component) depends-on)\n        (%refresh-component-inline-methods component rest)\n        (when if-component-dep-fails\n          (error \"The system definition for ~S uses deprecated ~\n            ASDF option :IF-COMPONENT-DEP-FAILS. ~\n            Starting with ASDF 3, please use :IF-FEATURE instead\"\n           (coerce-name (component-system component))))\n        component)))\n\n  (defun register-system-definition\n      (name &rest options &key pathname (class 'system) (source-file () sfp)\n                            defsystem-depends-on &allow-other-keys)\n    ;; The system must be registered before we parse the body,\n    ;; otherwise we recur when trying to find an existing system\n    ;; of the same name to reuse options (e.g. pathname) from.\n    ;; To avoid infinite recursion in cases where you defsystem a system\n    ;; that is registered to a different location to find-system,\n    ;; we also need to remember it in the asdf-cache.\n    (nest\n     (with-asdf-cache ())\n     (let* ((name (coerce-name name))\n            (source-file (if sfp source-file (resolve-symlinks* (load-pathname))))))\n     (flet ((fix-case (x) (if (logical-pathname-p source-file) (string-downcase x) x))))\n     (let* ((asd-name (and source-file\n                           (equal \"asd\" (fix-case (pathname-type source-file)))\n                           (fix-case (pathname-name source-file))))\n            (primary-name (primary-system-name name)))\n       (when (and asd-name (not (equal asd-name primary-name)))\n         (warn (make-condition 'bad-system-name :source-file source-file :name name))))\n     (let* (;; NB: handle defsystem-depends-on BEFORE to create the system object,\n            ;; so that in case it fails, there is no incomplete object polluting the build.\n            (checked-defsystem-depends-on\n             (let* ((dep-forms (parse-dependency-defs defsystem-depends-on))\n                    (deps (loop :for spec :in dep-forms\n                            :when (resolve-dependency-spec nil spec)\n                            :collect :it)))\n               (load-systems* deps)\n               dep-forms))\n            (registered (system-registered-p name))\n            (registered! (if registered\n                             (rplaca registered (get-file-stamp source-file))\n                             (register-system\n                              (make-instance 'system :name name :source-file source-file))))\n            (system (reset-system (cdr registered!)\n                                  :name name :source-file source-file))\n            (component-options\n             (append\n              (remove-plist-keys '(:defsystem-depends-on :class) options)\n              ;; cache defsystem-depends-on in canonical form\n              (when checked-defsystem-depends-on\n                `(:defsystem-depends-on ,checked-defsystem-depends-on))))\n            (directory (determine-system-directory pathname)))\n       ;; This works hand in hand with asdf/find-system:find-system-if-being-defined:\n       (set-asdf-cache-entry `(find-system ,name) (list system)))\n     ;; We change-class AFTER we loaded the defsystem-depends-on\n     ;; since the class might be defined as part of those.\n     (let ((class (class-for-type nil class)))\n       (unless (subtypep class 'system)\n         (error 'non-system-system :name name :class-name (class-name class)))\n       (unless (eq (type-of system) class)\n         (change-class system class)))\n     (parse-component-form nil (list* :module name :pathname directory component-options))))\n\n  (defmacro defsystem (name &body options)\n    `(apply 'register-system-definition ',name ',options)))\n;;;; -------------------------------------------------------------------------\n;;;; ASDF-Bundle\n\n(uiop/package:define-package :asdf/bundle\n  (:recycle :asdf/bundle :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/system :asdf/find-system :asdf/find-component :asdf/operation\n   :asdf/action :asdf/lisp-action :asdf/plan :asdf/operate :asdf/defsystem)\n  (:export\n   #:bundle-op #:bundle-type #:program-system\n   #:bundle-system #:bundle-pathname-type #:direct-dependency-files\n   #:monolithic-op #:monolithic-bundle-op #:operation-monolithic-p\n   #:basic-compile-bundle-op #:prepare-bundle-op\n   #:compile-bundle-op #:load-bundle-op #:monolithic-compile-bundle-op #:monolithic-load-bundle-op\n   #:lib-op #:monolithic-lib-op\n   #:dll-op #:monolithic-dll-op\n   #:deliver-asd-op #:monolithic-deliver-asd-op\n   #:program-op #:image-op #:compiled-file #:precompiled-system #:prebuilt-system\n   #:user-system-p #:user-system #:trivial-system-p\n   #:prologue-code #:epilogue-code #:static-library))\n(in-package :asdf/bundle)\n\n(with-upgradability ()\n  (defclass bundle-op (operation)\n    ;; NB: use of instance-allocated slots for operations is DEPRECATED\n    ;; and only supported in a temporary fashion for backward compatibility.\n    ;; Supported replacement: Define slots on program-system instead.\n    ((bundle-type :initform :no-output-file :reader bundle-type :allocation :class))\n    (:documentation \"base class for operations that bundle outputs from multiple components\"))\n\n  (defclass monolithic-op (operation) ()\n    (:documentation \"A MONOLITHIC operation operates on a system *and all of its\ndependencies*.  So, for example, a monolithic concatenate operation will\nconcatenate together a system's components and all of its dependencies, but a\nsimple concatenate operation will concatenate only the components of the system\nitself.\"))\n\n  (defclass monolithic-bundle-op (bundle-op monolithic-op)\n    ;; Old style way of specifying prologue and epilogue on ECL: in the monolithic operation.\n    ;; DEPRECATED. Supported replacement: Define slots on program-system instead.\n    ((prologue-code :initform nil :accessor prologue-code)\n     (epilogue-code :initform nil :accessor epilogue-code))\n    (:documentation \"operations that are both monolithic-op and bundle-op\"))\n\n  (defclass program-system (system)\n    ;; New style (ASDF3.1) way of specifying prologue and epilogue on ECL: in the system\n    ((prologue-code :initform nil :initarg :prologue-code :reader prologue-code)\n     (epilogue-code :initform nil :initarg :epilogue-code :reader epilogue-code)\n     (no-uiop :initform nil :initarg :no-uiop :reader no-uiop)\n     (prefix-lisp-object-files :initarg :prefix-lisp-object-files\n                               :initform nil :accessor prefix-lisp-object-files)\n     (postfix-lisp-object-files :initarg :postfix-lisp-object-files\n                                :initform nil :accessor postfix-lisp-object-files)\n     (extra-object-files :initarg :extra-object-files\n                         :initform nil :accessor extra-object-files)\n     (extra-build-args :initarg :extra-build-args\n                       :initform nil :accessor extra-build-args)))\n\n  (defmethod prologue-code ((x system)) nil)\n  (defmethod epilogue-code ((x system)) nil)\n  (defmethod no-uiop ((x system)) nil)\n  (defmethod prefix-lisp-object-files ((x system)) nil)\n  (defmethod postfix-lisp-object-files ((x system)) nil)\n  (defmethod extra-object-files ((x system)) nil)\n  (defmethod extra-build-args ((x system)) nil)\n\n  (defclass link-op (bundle-op) ()\n    (:documentation \"Abstract operation for linking files together\"))\n\n  (defclass gather-operation (bundle-op)\n    ((gather-operation :initform nil :allocation :class :reader gather-operation)\n     (gather-type :initform :no-output-file :allocation :class :reader gather-type))\n    (:documentation \"Abstract operation for gathering many input files from a system\"))\n\n  (defun operation-monolithic-p (op)\n    (typep op 'monolithic-op))\n\n  ;; Dependencies of a gather-op are the actions of the dependent operation\n  ;; for all the (sorted) required components for loading the system.\n  ;; Monolithic operations typically use lib-op as the dependent operation,\n  ;; and all system-level dependencies as required components.\n  ;; Non-monolithic operations typically use compile-op as the dependent operation,\n  ;; and all transitive sub-components as required components (excluding other systems).\n  (defmethod component-depends-on ((o gather-operation) (s system))\n    (let* ((mono (operation-monolithic-p o))\n           (go (make-operation (or (gather-operation o) 'compile-op)))\n           (bundle-p (typep go 'bundle-op))\n           ;; In a non-mono operation, don't recurse to other systems.\n           ;; In a mono operation gathering bundles, don't recurse inside systems.\n           (component-type (if mono (if bundle-p 'system t) '(not system)))\n           ;; In the end, only keep system bundles or non-system bundles, depending.\n           (keep-component (if bundle-p 'system '(not system)))\n           (deps\n            ;; Required-components only looks at the dependencies of an action, excluding the action\n            ;; itself, so it may be safely used by an action recursing on its dependencies (which\n            ;; may or may not be an overdesigned API, since in practice we never use it that way).\n            ;; Therefore, if we use :goal-operation 'load-op :keep-operation 'load-op, which looks\n            ;; cleaner, we will miss the load-op on the requested system itself, which doesn't\n            ;; matter for a regular system, but matters, a lot, for a package-inferred-system.\n            ;; Using load-op as the goal operation and basic-compile-op as the keep-operation works\n            ;; for our needs of gathering all the files we want to include in a bundle.\n            ;; Note that we use basic-compile-op rather than compile-op so it will still work on\n            ;; systems that would somehow load dependencies with load-bundle-op.\n            (required-components\n             s :other-systems mono :component-type component-type :keep-component keep-component\n             :goal-operation 'load-op :keep-operation 'basic-compile-op)))\n      `((,go ,@deps) ,@(call-next-method))))\n\n  ;; Create a single fasl for the entire library\n  (defclass basic-compile-bundle-op (bundle-op basic-compile-op)\n    ((gather-type :initform #-(or clasp ecl mkcl) :fasl #+(or clasp ecl mkcl) :object\n                  :allocation :class)\n     (bundle-type :initform :fasb :allocation :class))\n    (:documentation \"Base class for compiling into a bundle\"))\n\n  ;; Analog to prepare-op, for load-bundle-op and compile-bundle-op\n  (defclass prepare-bundle-op (sideway-operation)\n    ((sideway-operation\n      :initform #+(or clasp ecl mkcl) 'load-bundle-op #-(or clasp ecl mkcl) 'load-op\n      :allocation :class))\n    (:documentation \"Operation class for loading the bundles of a system's dependencies\"))\n\n  (defclass lib-op (link-op gather-operation non-propagating-operation)\n    ((gather-type :initform :object :allocation :class)\n     (bundle-type :initform :lib :allocation :class))\n    (:documentation \"Compile the system and produce a linkable static library (.a/.lib)\nfor all the linkable object files associated with the system. Compare with DLL-OP.\n\nOn most implementations, these object files only include extensions to the runtime\nwritten in C or another language with a compiler producing linkable object files.\nOn CLASP, ECL, MKCL, these object files _also_ include the contents of Lisp files\nthemselves. In any case, this operation will produce what you need to further build\na static runtime for your system, or a dynamic library to load in an existing runtime.\"))\n\n  ;; What works: on ECL, CLASP(?), MKCL, we link the many .o files from the system into the .so;\n  ;; on other implementations, we combine (usually concatenate) the .fasl files into one.\n  (defclass compile-bundle-op (basic-compile-bundle-op selfward-operation gather-operation\n                                                       #+(or clasp ecl mkcl) link-op)\n    ((selfward-operation :initform '(prepare-bundle-op) :allocation :class))\n    (:documentation \"This operator is an alternative to COMPILE-OP. Build a system\nand all of its dependencies, but build only a single (\\\"monolithic\\\") FASL, instead\nof one per source file, which may be more resource efficient.  That monolithic\nFASL should be loaded with LOAD-BUNDLE-OP, rather than LOAD-OP.\"))\n\n  (defclass load-bundle-op (basic-load-op selfward-operation)\n    ((selfward-operation :initform '(prepare-bundle-op compile-bundle-op) :allocation :class))\n    (:documentation \"This operator is an alternative to LOAD-OP. Build a system\nand all of its dependencies, using COMPILE-BUNDLE-OP. The difference with\nrespect to LOAD-OP is that it builds only a single FASL, which may be\nfaster and more resource efficient.\"))\n\n  ;; NB: since the monolithic-op's can't be sideway-operation's,\n  ;; if we wanted lib-op, dll-op, deliver-asd-op to be sideway-operation's,\n  ;; we'd have to have the monolithic-op not inherit from the main op,\n  ;; but instead inherit from a basic-FOO-op as with basic-compile-bundle-op above.\n\n  (defclass dll-op (link-op gather-operation non-propagating-operation)\n    ((gather-type :initform :object :allocation :class)\n     (bundle-type :initform :dll :allocation :class))\n    (:documentation \"Compile the system and produce a dynamic loadable library (.so/.dll)\nfor all the linkable object files associated with the system. Compare with LIB-OP.\"))\n\n  (defclass deliver-asd-op (basic-compile-op selfward-operation)\n    ((selfward-operation\n      ;; TODO: implement link-op on all implementations, and make that\n      ;; '(compile-bundle-op lib-op #-(or clasp ecl mkcl) dll-op)\n      :initform '(compile-bundle-op #+(or clasp ecl mkcl) lib-op)\n      :allocation :class))\n    (:documentation \"produce an asd file for delivering the system as a single fasl\"))\n\n\n  (defclass monolithic-deliver-asd-op (deliver-asd-op monolithic-bundle-op)\n    ((selfward-operation\n      ;; TODO: implement link-op on all implementations, and make that\n      ;; '(monolithic-compile-bundle-op monolithic-lib-op #-(or clasp ecl mkcl) monolithic-dll-op)\n      :initform '(monolithic-compile-bundle-op #+(or clasp ecl mkcl) monolithic-lib-op)\n      :allocation :class))\n    (:documentation \"produce fasl and asd files for combined system and dependencies.\"))\n\n  (defclass monolithic-compile-bundle-op\n      (basic-compile-bundle-op monolithic-bundle-op\n       #+(or clasp ecl mkcl) link-op gather-operation non-propagating-operation)\n    ()\n    (:documentation \"Create a single fasl for the system and its dependencies.\"))\n\n  (defclass monolithic-load-bundle-op (load-bundle-op monolithic-bundle-op)\n    ((selfward-operation :initform 'monolithic-compile-bundle-op :allocation :class))\n    (:documentation \"Load a single fasl for the system and its dependencies.\"))\n\n  (defclass monolithic-lib-op (lib-op monolithic-bundle-op non-propagating-operation)\n    ((gather-type :initform :object :allocation :class))\n    (:documentation \"Compile the system and produce a linkable static library (.a/.lib)\nfor all the linkable object files associated with the system or its dependencies. See LIB-OP.\"))\n\n  (defclass monolithic-dll-op (dll-op monolithic-bundle-op non-propagating-operation)\n    ((gather-type :initform :object :allocation :class))\n    (:documentation \"Compile the system and produce a dynamic loadable library (.so/.dll)\nfor all the linkable object files associated with the system or its dependencies. See LIB-OP\"))\n\n  (defclass image-op (monolithic-bundle-op selfward-operation\n                      #+(or clasp ecl mkcl) link-op #+(or clasp ecl mkcl) gather-operation)\n    ((bundle-type :initform :image :allocation :class)\n     (gather-operation :initform 'lib-op :allocation :class)\n     #+(or clasp ecl mkcl) (gather-type :initform :static-library :allocation :class)\n     (selfward-operation :initform '(#-(or clasp ecl mkcl) load-op) :allocation :class))\n    (:documentation \"create an image file from the system and its dependencies\"))\n\n  (defclass program-op (image-op)\n    ((bundle-type :initform :program :allocation :class))\n    (:documentation \"create an executable file from the system and its dependencies\"))\n\n  ;; From the ASDF-internal bundle-type identifier, get a filesystem-usable pathname type.\n  (defun bundle-pathname-type (bundle-type)\n    (etypecase bundle-type\n      ((or null string) ;; pass through nil or string literal\n       bundle-type)\n      ((eql :no-output-file) ;; marker for a bundle-type that has NO output file\n       (error \"No output file, therefore no pathname type\"))\n      ((eql :fasl) ;; the type of a fasl\n       (compile-file-type)) ; on image-based platforms, used as input and output\n      ((eql :fasb) ;; the type of a fasl\n       #-(or clasp ecl mkcl) (compile-file-type) ; on image-based platforms, used as input and output\n       #+(or clasp ecl mkcl) \"fasb\") ; on C-linking platforms, only used as output for system bundles\n      ((member :image)\n       #+allegro \"dxl\"\n       #+(and clisp os-windows) \"exe\"\n       #-(or allegro (and clisp os-windows)) \"image\")\n      ;; NB: on CLASP and ECL these implementations, we better agree with\n      ;; (compile-file-type :type bundle-type))\n      ((eql :object) ;; the type of a linkable object file\n       (os-cond ((os-unix-p) \"o\")\n                ((os-windows-p) (if (featurep '(:or :mingw32 :mingw64)) \"o\" \"obj\"))))\n      ((member :lib :static-library) ;; the type of a linkable library\n       (os-cond ((os-unix-p) \"a\")\n                ((os-windows-p) (if (featurep '(:or :mingw32 :mingw64)) \"a\" \"lib\"))))\n      ((member :dll :shared-library) ;; the type of a shared library\n       (os-cond ((os-macosx-p) \"dylib\") ((os-unix-p) \"so\") ((os-windows-p) \"dll\")))\n      ((eql :program) ;; the type of an executable program\n       (os-cond ((os-unix-p) nil) ((os-windows-p) \"exe\")))))\n\n  ;; Compute the output-files for a given bundle action\n  (defun bundle-output-files (o c)\n    (let ((bundle-type (bundle-type o)))\n      (unless (or (eq bundle-type :no-output-file) ;; NIL already means something regarding type.\n                  (and (null (input-files o c)) (not (member bundle-type '(:image :program)))))\n        (let ((name (or (component-build-pathname c)\n                        (let ((suffix\n                               (unless (typep o 'program-op)\n                                 ;; \".\" is no good separator for Logical Pathnames, so we use \"--\"\n                                 (if (operation-monolithic-p o)\n                                     \"--all-systems\"\n                                     ;; These use a different type .fasb or .a instead of .fasl\n                                     #-(or clasp ecl mkcl) \"--system\"))))\n                          (format nil \"~A~@[~A~]\" (component-name c) suffix))))\n              (type (bundle-pathname-type bundle-type)))\n          (values (list (subpathname (component-pathname c) name :type type))\n                  (eq (class-of o) (coerce-class (component-build-operation c)\n                                                 :package :asdf/interface\n                                                 :super 'operation\n                                                 :error nil)))))))\n\n  (defmethod output-files ((o bundle-op) (c system))\n    (bundle-output-files o c))\n\n  #-(or clasp ecl mkcl)\n  (progn\n    (defmethod perform ((o image-op) (c system))\n      (dump-image (output-file o c) :executable (typep o 'program-op)))\n    (defmethod perform :before ((o program-op) (c system))\n      (setf *image-entry-point* (ensure-function (component-entry-point c)))))\n\n  (defclass compiled-file (file-component)\n    ((type :initform #-(or clasp ecl mkcl) (compile-file-type) #+(or clasp ecl mkcl) \"fasb\"))\n    (:documentation \"Class for a file that is already compiled,\ne.g. as part of the implementation, of an outer build system that calls into ASDF,\nor of opaque libraries shipped along the source code.\"))\n\n  (defclass precompiled-system (system)\n    ((build-pathname :initarg :fasb :initarg :fasl))\n    (:documentation \"Class For a system that is delivered as a precompiled fasl\"))\n\n  (defclass prebuilt-system (system)\n    ((build-pathname :initarg :static-library :initarg :lib\n                     :accessor prebuilt-system-static-library))\n    (:documentation \"Class for a system delivered with a linkable static library (.a/.lib)\")))\n\n\n;;;\n;;; BUNDLE-OP\n;;;\n;;; This operation takes all components from one or more systems and\n;;; creates a single output file, which may be\n;;; a FASL, a statically linked library, a shared library, etc.\n;;; The different targets are defined by specialization.\n;;;\n(when-upgrading (:version \"3.2.0\")\n  ;; Cancel any previously defined method\n  (defmethod initialize-instance :after ((instance bundle-op) &rest initargs &key &allow-other-keys)\n    (declare (ignore initargs))))\n\n(with-upgradability ()\n  (defgeneric trivial-system-p (component))\n\n  (defun user-system-p (s)\n    (and (typep s 'system)\n         (not (builtin-system-p s))\n         (not (trivial-system-p s)))))\n\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute)\n  (deftype user-system () '(and system (satisfies user-system-p))))\n\n;;;\n;;; First we handle monolithic bundles.\n;;; These are standalone systems which contain everything,\n;;; including other ASDF systems required by the current one.\n;;; A PROGRAM is always monolithic.\n;;;\n;;; MONOLITHIC SHARED LIBRARIES, PROGRAMS, FASL\n;;;\n(with-upgradability ()\n  (defun direct-dependency-files (o c &key (test 'identity) (key 'output-files) &allow-other-keys)\n    ;; This function selects output files from direct dependencies;\n    ;; your component-depends-on method must gather the correct dependencies in the correct order.\n    (while-collecting (collect)\n      (map-direct-dependencies\n       t o c #'(lambda (sub-o sub-c)\n                 (loop :for f :in (funcall key sub-o sub-c)\n                       :when (funcall test f) :do (collect f))))))\n\n  (defun pathname-type-equal-function (type)\n    #'(lambda (p) (equalp (pathname-type p) type)))\n\n  (defmethod input-files ((o gather-operation) (c system))\n    (unless (eq (bundle-type o) :no-output-file)\n      (direct-dependency-files\n       o c :key 'output-files\n           :test (pathname-type-equal-function (bundle-pathname-type (gather-type o))))))\n\n  ;; Find the operation that produces a given bundle-type\n  (defun select-bundle-operation (type &optional monolithic)\n    (ecase type\n      ((:dll :shared-library)\n       (if monolithic 'monolithic-dll-op 'dll-op))\n      ((:lib :static-library)\n       (if monolithic 'monolithic-lib-op 'lib-op))\n      ((:fasb)\n       (if monolithic 'monolithic-compile-bundle-op 'compile-bundle-op))\n      ((:image)\n       'image-op)\n      ((:program)\n       'program-op))))\n\n;;;\n;;; LOAD-BUNDLE-OP\n;;;\n;;; This is like ASDF's LOAD-OP, but using bundle fasl files.\n;;;\n(with-upgradability ()\n  (defmethod component-depends-on ((o load-bundle-op) (c system))\n    `((,o ,@(component-sideway-dependencies c))\n      (,(if (user-system-p c) 'compile-bundle-op 'load-op) ,c)\n      ,@(call-next-method)))\n\n  (defmethod input-files ((o load-bundle-op) (c system))\n    (when (user-system-p c)\n      (output-files (find-operation o 'compile-bundle-op) c)))\n\n  (defmethod perform ((o load-bundle-op) (c system))\n    (when (input-files o c)\n      (perform-lisp-load-fasl o c)))\n\n  (defmethod mark-operation-done :after ((o load-bundle-op) (c system))\n    (mark-operation-done (find-operation o 'load-op) c)))\n\n;;;\n;;; PRECOMPILED FILES\n;;;\n;;; This component can be used to distribute ASDF systems in precompiled form.\n;;; Only useful when the dependencies have also been precompiled.\n;;;\n(with-upgradability ()\n  (defmethod trivial-system-p ((s system))\n    (every #'(lambda (c) (typep c 'compiled-file)) (component-children s)))\n\n  (defmethod input-files ((o operation) (c compiled-file))\n    (list (component-pathname c)))\n  (defmethod perform ((o load-op) (c compiled-file))\n    (perform-lisp-load-fasl o c))\n  (defmethod perform ((o load-source-op) (c compiled-file))\n    (perform (find-operation o 'load-op) c))\n  (defmethod perform ((o operation) (c compiled-file))\n    nil))\n\n;;;\n;;; Pre-built systems\n;;;\n(with-upgradability ()\n  (defmethod trivial-system-p ((s prebuilt-system))\n    t)\n\n  (defmethod perform ((o link-op) (c prebuilt-system))\n    nil)\n\n  (defmethod perform ((o basic-compile-bundle-op) (c prebuilt-system))\n    nil)\n\n  (defmethod perform ((o lib-op) (c prebuilt-system))\n    nil)\n\n  (defmethod perform ((o dll-op) (c prebuilt-system))\n    nil)\n\n  (defmethod component-depends-on ((o gather-operation) (c prebuilt-system))\n    nil)\n\n  (defmethod output-files ((o lib-op) (c prebuilt-system))\n    (values (list (prebuilt-system-static-library c)) t)))\n\n\n;;;\n;;; PREBUILT SYSTEM CREATOR\n;;;\n(with-upgradability ()\n  (defmethod output-files ((o deliver-asd-op) (s system))\n    (list (make-pathname :name (component-name s) :type \"asd\"\n                         :defaults (component-pathname s))))\n\n  (defmethod perform ((o deliver-asd-op) (s system))\n    (let* ((inputs (input-files o s))\n           (fasl (first inputs))\n           (library (second inputs))\n           (asd (first (output-files o s)))\n           (name (if (and fasl asd) (pathname-name asd) (return-from perform)))\n           (version (component-version s))\n           (dependencies\n             (if (operation-monolithic-p o)\n                 ;; We want only dependencies, and we use basic-load-op rather than load-op so that\n                 ;; this will keep working on systems that load dependencies with load-bundle-op\n                 (remove-if-not 'builtin-system-p\n                                (required-components s :component-type 'system\n                                                       :keep-operation 'basic-load-op))\n                 (while-collecting (x) ;; resolve the sideway-dependencies of s\n                   (map-direct-dependencies\n                    t 'load-op s\n                    #'(lambda (o c)\n                        (when (and (typep o 'load-op) (typep c 'system))\n                          (x c)))))))\n           (depends-on (mapcar 'coerce-name dependencies)))\n      (when (pathname-equal asd (system-source-file s))\n        (cerror \"overwrite the asd file\"\n                \"~/asdf-action:format-action/ is going to overwrite the system definition file ~S ~\nwhich is probably not what you want; you probably need to tweak your output translations.\"\n                (cons o s) asd))\n      (with-open-file (s asd :direction :output :if-exists :supersede\n                             :if-does-not-exist :create)\n        (format s \";;; Prebuilt~:[~; monolithic~] ASDF definition for system ~A~%\"\n                (operation-monolithic-p o) name)\n        (format s \";;; Built for ~A ~A on a ~A/~A ~A~%\"\n                (lisp-implementation-type)\n                (lisp-implementation-version)\n                (software-type)\n                (machine-type)\n                (software-version))\n        (let ((*package* (find-package :asdf-user)))\n          (pprint `(defsystem ,name\n                     :class prebuilt-system\n                     :version ,version\n                     :depends-on ,depends-on\n                     :components ((:compiled-file ,(pathname-name fasl)))\n                     ,@(when library `(:lib ,(file-namestring library))))\n                  s)\n          (terpri s)))))\n\n  #-(or clasp ecl mkcl)\n  (defmethod perform ((o basic-compile-bundle-op) (c system))\n    (let* ((input-files (input-files o c))\n           (fasl-files (remove (compile-file-type) input-files :key #'pathname-type :test-not #'equalp))\n           (non-fasl-files (remove (compile-file-type) input-files :key #'pathname-type :test #'equalp))\n           (output-files (output-files o c))\n           (output-file (first output-files)))\n      (assert (eq (not input-files) (not output-files)))\n      (when input-files\n        (when non-fasl-files\n          (error \"On ~A, asdf/bundle can only bundle FASL files, but these were also produced: ~S\"\n                 (implementation-type) non-fasl-files))\n        (when (or (prologue-code c) (epilogue-code c))\n          (error \"prologue-code and epilogue-code are not supported on ~A\"\n                 (implementation-type)))\n        (with-staging-pathname (output-file)\n          (combine-fasls fasl-files output-file)))))\n\n  (defmethod input-files ((o load-op) (s precompiled-system))\n    (bundle-output-files (find-operation o 'compile-bundle-op) s))\n\n  (defmethod perform ((o load-op) (s precompiled-system))\n    (perform-lisp-load-fasl o s))\n\n  (defmethod component-depends-on ((o load-bundle-op) (s precompiled-system))\n    #+xcl (declare (ignorable o))\n    `((load-op ,s) ,@(call-next-method))))\n\n#| ;; Example use:\n(asdf:defsystem :precompiled-asdf-utils :class asdf::precompiled-system :fasl (asdf:apply-output-translations (asdf:system-relative-pathname :asdf-utils \"asdf-utils.system.fasl\")))\n(asdf:load-system :precompiled-asdf-utils)\n|#\n\n#+(or clasp ecl mkcl)\n(with-upgradability ()\n  (defun system-module-pathname (module)\n    (let ((name (coerce-name module)))\n      (some\n       'file-exists-p\n       (list\n        #+clasp (compile-file-pathname (make-pathname :name name :defaults \"sys:\") :output-type :object)\n        #+ecl (compile-file-pathname (make-pathname :name name :defaults \"sys:\") :type :lib)\n        #+ecl (compile-file-pathname (make-pathname :name (strcat \"lib\" name) :defaults \"sys:\") :type :lib)\n        #+ecl (compile-file-pathname (make-pathname :name name :defaults \"sys:\") :type :object)\n        #+mkcl (make-pathname :name name :type (bundle-pathname-type :lib) :defaults #p\"sys:\")\n        #+mkcl (make-pathname :name name :type (bundle-pathname-type :lib) :defaults #p\"sys:contrib;\")))))\n\n  (defun make-prebuilt-system (name &optional (pathname (system-module-pathname name)))\n    \"Creates a prebuilt-system if PATHNAME isn't NIL.\"\n    (when pathname\n      (make-instance 'prebuilt-system\n                     :name (coerce-name name)\n                     :static-library (resolve-symlinks* pathname))))\n\n  (defun linkable-system (x)\n    (or (if-let (s (find-system x))\n          (and (system-source-file x) s))\n        (if-let (p (system-module-pathname (coerce-name x)))\n          (make-prebuilt-system x p))))\n\n  (defmethod component-depends-on :around ((o image-op) (c system))\n    (let* ((next (call-next-method))\n           (deps (make-hash-table :test 'equal))\n           (linkable (loop* :for (do . dcs) :in next :collect\n                       (cons do\n                             (loop :for dc :in dcs\n                               :for dep = (and dc (resolve-dependency-spec c dc))\n                               :when dep\n                               :do (setf (gethash (coerce-name (component-system dep)) deps) t)\n                               :collect (or (and (typep dep 'system) (linkable-system dep)) dep))))))\n        `((lib-op\n           ,@(unless (no-uiop c)\n               (list (linkable-system \"cmp\")\n                     (unless (or (gethash \"uiop\" deps) (gethash \"asdf\" deps))\n                       (or (linkable-system \"uiop\")\n                           (linkable-system \"asdf\")\n                           \"asdf\")))))\n          ,@linkable)))\n\n  (defmethod perform ((o link-op) (c system))\n    (let* ((object-files (input-files o c))\n           (output (output-files o c))\n           (bundle (first output))\n           (programp (typep o 'program-op))\n           (kind (bundle-type o)))\n      (when output\n        (apply 'create-image\n               bundle (append\n                       (when programp (prefix-lisp-object-files c))\n                       object-files\n                       (when programp (postfix-lisp-object-files c)))\n               :kind kind\n               :prologue-code (when programp (prologue-code c))\n               :epilogue-code (when programp (epilogue-code c))\n               :build-args (when programp (extra-build-args c))\n               :extra-object-files (when programp (extra-object-files c))\n               :no-uiop (no-uiop c)\n               (when programp `(:entry-point ,(component-entry-point c))))))))\n;;;; -------------------------------------------------------------------------\n;;;; Concatenate-source\n\n(uiop/package:define-package :asdf/concatenate-source\n  (:recycle :asdf/concatenate-source :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/operation\n   :asdf/system :asdf/find-system\n   :asdf/action :asdf/lisp-action :asdf/plan :asdf/bundle)\n  (:export\n   #:concatenate-source-op\n   #:load-concatenated-source-op\n   #:compile-concatenated-source-op\n   #:load-compiled-concatenated-source-op\n   #:monolithic-concatenate-source-op\n   #:monolithic-load-concatenated-source-op\n   #:monolithic-compile-concatenated-source-op\n   #:monolithic-load-compiled-concatenated-source-op))\n(in-package :asdf/concatenate-source)\n\n;;;\n;;; Concatenate sources\n;;;\n(with-upgradability ()\n  ;; Base classes for both regular and monolithic concatenate-source operations\n  (defclass basic-concatenate-source-op (bundle-op)\n    ((bundle-type :initform \"lisp\" :allocation :class)))\n  (defclass basic-load-concatenated-source-op (basic-load-op selfward-operation) ())\n  (defclass basic-compile-concatenated-source-op (basic-compile-op selfward-operation) ())\n  (defclass basic-load-compiled-concatenated-source-op (basic-load-op selfward-operation) ())\n\n  ;; Regular concatenate-source operations\n  (defclass concatenate-source-op (basic-concatenate-source-op non-propagating-operation) ()\n    (:documentation \"Operation to concatenate all sources in a system into a single file\"))\n  (defclass load-concatenated-source-op (basic-load-concatenated-source-op)\n    ((selfward-operation :initform '(prepare-op concatenate-source-op) :allocation :class))\n    (:documentation \"Operation to load the result of concatenate-source-op as source\"))\n  (defclass compile-concatenated-source-op (basic-compile-concatenated-source-op)\n    ((selfward-operation :initform '(prepare-op concatenate-source-op) :allocation :class))\n    (:documentation \"Operation to compile the result of concatenate-source-op\"))\n  (defclass load-compiled-concatenated-source-op (basic-load-compiled-concatenated-source-op)\n    ((selfward-operation :initform '(prepare-op compile-concatenated-source-op) :allocation :class))\n    (:documentation \"Operation to load the result of compile-concatenated-source-op\"))\n\n  (defclass monolithic-concatenate-source-op\n      (basic-concatenate-source-op monolithic-bundle-op non-propagating-operation) ()\n    (:documentation \"Operation to concatenate all sources in a system and its dependencies\ninto a single file\"))\n  (defclass monolithic-load-concatenated-source-op (basic-load-concatenated-source-op)\n    ((selfward-operation :initform 'monolithic-concatenate-source-op :allocation :class))\n    (:documentation \"Operation to load the result of monolithic-concatenate-source-op as source\"))\n  (defclass monolithic-compile-concatenated-source-op (basic-compile-concatenated-source-op)\n    ((selfward-operation :initform 'monolithic-concatenate-source-op :allocation :class))\n    (:documentation \"Operation to compile the result of monolithic-concatenate-source-op\"))\n  (defclass monolithic-load-compiled-concatenated-source-op\n      (basic-load-compiled-concatenated-source-op)\n    ((selfward-operation :initform 'monolithic-compile-concatenated-source-op :allocation :class))\n    (:documentation \"Operation to load the result of monolithic-compile-concatenated-source-op\"))\n\n  (defmethod input-files ((operation basic-concatenate-source-op) (s system))\n    (loop :with encoding = (or (component-encoding s) *default-encoding*)\n          :with other-encodings = '()\n          :with around-compile = (around-compile-hook s)\n          :with other-around-compile = '()\n          :for c :in (required-components  ;; see note about similar call to required-components\n                      s :goal-operation 'load-op ;;  in bundle.lisp\n                        :keep-operation 'basic-compile-op\n                        :other-systems (operation-monolithic-p operation))\n          :append\n          (when (typep c 'cl-source-file)\n            (let ((e (component-encoding c)))\n              (unless (equal e encoding)\n                (let ((a (assoc e other-encodings)))\n                  (if a (push (component-find-path c) (cdr a))\n                      (push (list a (component-find-path c)) other-encodings)))))\n            (unless (equal around-compile (around-compile-hook c))\n              (push (component-find-path c) other-around-compile))\n            (input-files (make-operation 'compile-op) c)) :into inputs\n          :finally\n             (when other-encodings\n               (warn \"~S uses encoding ~A but has sources that use these encodings:~{ ~A~}\"\n                     operation encoding\n                     (mapcar #'(lambda (x) (cons (car x) (list (reverse (cdr x)))))\n                             other-encodings)))\n             (when other-around-compile\n               (warn \"~S uses around-compile hook ~A but has sources that use these hooks: ~A\"\n                     operation around-compile other-around-compile))\n             (return inputs)))\n  (defmethod output-files ((o basic-compile-concatenated-source-op) (s system))\n    (lisp-compilation-output-files o s))\n\n  (defmethod perform ((o basic-concatenate-source-op) (s system))\n    (let* ((ins (input-files o s))\n           (out (output-file o s))\n           (tmp (tmpize-pathname out)))\n      (concatenate-files ins tmp)\n      (rename-file-overwriting-target tmp out)))\n  (defmethod perform ((o basic-load-concatenated-source-op) (s system))\n    (perform-lisp-load-source o s))\n  (defmethod perform ((o basic-compile-concatenated-source-op) (s system))\n    (perform-lisp-compilation o s))\n  (defmethod perform ((o basic-load-compiled-concatenated-source-op) (s system))\n    (perform-lisp-load-fasl o s)))\n\n;;;; ---------------------------------------------------------------------------\n;;;; asdf-output-translations\n\n(uiop/package:define-package :asdf/output-translations\n  (:recycle :asdf/output-translations :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade)\n  (:export\n   #:*output-translations* #:*output-translations-parameter*\n   #:invalid-output-translation\n   #:output-translations #:output-translations-initialized-p\n   #:initialize-output-translations #:clear-output-translations\n   #:disable-output-translations #:ensure-output-translations\n   #:apply-output-translations\n   #:validate-output-translations-directive #:validate-output-translations-form\n   #:validate-output-translations-file #:validate-output-translations-directory\n   #:parse-output-translations-string #:wrapping-output-translations\n   #:user-output-translations-pathname #:system-output-translations-pathname\n   #:user-output-translations-directory-pathname #:system-output-translations-directory-pathname\n   #:environment-output-translations #:process-output-translations\n   #:compute-output-translations\n   #+abcl #:translate-jar-pathname\n   ))\n(in-package :asdf/output-translations)\n\n;; (setf output-translations) between 2.27 and 3.0.3 was using a defsetf macro\n;; for the sake of obsolete versions of GCL 2.6. Make sure it doesn't come to haunt us.\n(when-upgrading (:version \"3.1.2\") (fmakunbound '(setf output-translations)))\n\n(with-upgradability ()\n  (define-condition invalid-output-translation (invalid-configuration warning)\n    ((format :initform (compatfmt \"~@<Invalid asdf output-translation ~S~@[ in ~S~]~@{ ~@?~}~@:>\"))))\n\n  (defvar *output-translations* ()\n    \"Either NIL (for uninitialized), or a list of one element,\nsaid element itself being a sorted list of mappings.\nEach mapping is a pair of a source pathname and destination pathname,\nand the order is by decreasing length of namestring of the source pathname.\")\n\n  (defun output-translations ()\n    \"Return the configured output-translations, if any\"\n    (car *output-translations*))\n\n  ;; Set the output-translations, by sorting the provided new-value.\n  (defun set-output-translations (new-value)\n    (setf *output-translations*\n          (list\n           (stable-sort (copy-list new-value) #'>\n                        :key #'(lambda (x)\n                                 (etypecase (car x)\n                                   ((eql t) -1)\n                                   (pathname\n                                    (let ((directory\n                                           (normalize-pathname-directory-component\n                                            (pathname-directory (car x)))))\n                                      (if (listp directory) (length directory) 0))))))))\n    new-value)\n  (defun (setf output-translations) (new-value) (set-output-translations new-value))\n\n  (defun output-translations-initialized-p ()\n    \"Have the output-translations been initialized yet?\"\n    (and *output-translations* t))\n\n  (defun clear-output-translations ()\n    \"Undoes any initialization of the output translations.\"\n    (setf *output-translations* '())\n    (values))\n  (register-clear-configuration-hook 'clear-output-translations)\n\n\n  ;;; Validation of the configuration directives...\n\n  (defun validate-output-translations-directive (directive)\n    (or (member directive '(:enable-user-cache :disable-cache nil))\n        (and (consp directive)\n             (or (and (length=n-p directive 2)\n                      (or (and (eq (first directive) :include)\n                               (typep (second directive) '(or string pathname null)))\n                          (and (location-designator-p (first directive))\n                               (or (location-designator-p (second directive))\n                                   (location-function-p (second directive))))))\n                 (and (length=n-p directive 1)\n                      (location-designator-p (first directive)))))))\n\n  (defun validate-output-translations-form (form &key location)\n    (validate-configuration-form\n     form\n     :output-translations\n     'validate-output-translations-directive\n     :location location :invalid-form-reporter 'invalid-output-translation))\n\n  (defun validate-output-translations-file (file)\n    (validate-configuration-file\n     file 'validate-output-translations-form :description \"output translations\"))\n\n  (defun validate-output-translations-directory (directory)\n    (validate-configuration-directory\n     directory :output-translations 'validate-output-translations-directive\n               :invalid-form-reporter 'invalid-output-translation))\n\n\n  ;;; Parse the ASDF_OUTPUT_TRANSLATIONS environment variable and/or some file contents\n  (defun parse-output-translations-string (string &key location)\n    (cond\n      ((or (null string) (equal string \"\"))\n       '(:output-translations :inherit-configuration))\n      ((not (stringp string))\n       (error (compatfmt \"~@<Environment string isn't: ~3i~_~S~@:>\") string))\n      ((eql (char string 0) #\\\")\n       (parse-output-translations-string (read-from-string string) :location location))\n      ((eql (char string 0) #\\()\n       (validate-output-translations-form (read-from-string string) :location location))\n      (t\n       (loop\n         :with inherit = nil\n         :with directives = ()\n         :with start = 0\n         :with end = (length string)\n         :with source = nil\n         :with separator = (inter-directory-separator)\n         :for i = (or (position separator string :start start) end) :do\n           (let ((s (subseq string start i)))\n             (cond\n               (source\n                (push (list source (if (equal \"\" s) nil s)) directives)\n                (setf source nil))\n               ((equal \"\" s)\n                (when inherit\n                  (error (compatfmt \"~@<Only one inherited configuration allowed: ~3i~_~S~@:>\")\n                         string))\n                (setf inherit t)\n                (push :inherit-configuration directives))\n               (t\n                (setf source s)))\n             (setf start (1+ i))\n             (when (> start end)\n               (when source\n                 (error (compatfmt \"~@<Uneven number of components in source to destination mapping: ~3i~_~S~@:>\")\n                        string))\n               (unless inherit\n                 (push :ignore-inherited-configuration directives))\n               (return `(:output-translations ,@(nreverse directives)))))))))\n\n\n  ;; The default sources of configuration for output-translations\n  (defparameter* *default-output-translations*\n    '(environment-output-translations\n      user-output-translations-pathname\n      user-output-translations-directory-pathname\n      system-output-translations-pathname\n      system-output-translations-directory-pathname))\n\n  ;; Compulsory implementation-dependent wrapping for the translations:\n  ;; handle implementation-provided systems.\n  (defun wrapping-output-translations ()\n    `(:output-translations\n    ;; Some implementations have precompiled ASDF systems,\n    ;; so we must disable translations for implementation paths.\n      #+(or clasp #|clozure|# ecl mkcl sbcl)\n      ,@(let ((h (resolve-symlinks* (lisp-implementation-directory))))\n          (when h `(((,h ,*wild-path*) ()))))\n      #+mkcl (,(translate-logical-pathname \"CONTRIB:\") ())\n      ;; All-import, here is where we want user stuff to be:\n      :inherit-configuration\n      ;; These are for convenience, and can be overridden by the user:\n      #+abcl (#p\"/___jar___file___root___/**/*.*\" (:user-cache #p\"**/*.*\"))\n      #+abcl (#p\"jar:file:/**/*.jar!/**/*.*\" (:function translate-jar-pathname))\n      ;; We enable the user cache by default, and here is the place we do:\n      :enable-user-cache))\n\n  ;; Relative pathnames of output-translations configuration to XDG configuration directory\n  (defparameter *output-translations-file* (parse-unix-namestring \"common-lisp/asdf-output-translations.conf\"))\n  (defparameter *output-translations-directory* (parse-unix-namestring \"common-lisp/asdf-output-translations.conf.d/\"))\n\n  ;; Locating various configuration pathnames, depending on input or output intent.\n  (defun user-output-translations-pathname (&key (direction :input))\n    (xdg-config-pathname *output-translations-file* direction))\n  (defun system-output-translations-pathname (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *output-translations-file*)\n                         :direction direction))\n  (defun user-output-translations-directory-pathname (&key (direction :input))\n    (xdg-config-pathname *output-translations-directory* direction))\n  (defun system-output-translations-directory-pathname (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *output-translations-directory*)\n                         :direction direction))\n  (defun environment-output-translations ()\n    (getenv \"ASDF_OUTPUT_TRANSLATIONS\"))\n\n\n  ;;; Processing the configuration.\n\n  (defgeneric process-output-translations (spec &key inherit collect))\n\n  (defun inherit-output-translations (inherit &key collect)\n    (when inherit\n      (process-output-translations (first inherit) :collect collect :inherit (rest inherit))))\n\n  (defun* (process-output-translations-directive) (directive &key inherit collect)\n    (if (atom directive)\n        (ecase directive\n          ((:enable-user-cache)\n           (process-output-translations-directive '(t :user-cache) :collect collect))\n          ((:disable-cache)\n           (process-output-translations-directive '(t t) :collect collect))\n          ((:inherit-configuration)\n           (inherit-output-translations inherit :collect collect))\n          ((:ignore-inherited-configuration :ignore-invalid-entries nil)\n           nil))\n        (let ((src (first directive))\n              (dst (second directive)))\n          (if (eq src :include)\n              (when dst\n                (process-output-translations (pathname dst) :inherit nil :collect collect))\n              (when src\n                (let ((trusrc (or (eql src t)\n                                  (let ((loc (resolve-location src :ensure-directory t :wilden t)))\n                                    (if (absolute-pathname-p loc) (resolve-symlinks* loc) loc)))))\n                  (cond\n                    ((location-function-p dst)\n                     (funcall collect\n                              (list trusrc (ensure-function (second dst)))))\n                    ((typep dst 'boolean)\n                     (funcall collect (list trusrc t)))\n                    (t\n                     (let* ((trudst (resolve-location dst :ensure-directory t :wilden t)))\n                       (funcall collect (list trudst t))\n                       (funcall collect (list trusrc trudst)))))))))))\n\n  (defmethod process-output-translations ((x symbol) &key\n                                                       (inherit *default-output-translations*)\n                                                       collect)\n    (process-output-translations (funcall x) :inherit inherit :collect collect))\n  (defmethod process-output-translations ((pathname pathname) &key inherit collect)\n    (cond\n      ((directory-pathname-p pathname)\n       (process-output-translations (validate-output-translations-directory pathname)\n                                    :inherit inherit :collect collect))\n      ((probe-file* pathname :truename *resolve-symlinks*)\n       (process-output-translations (validate-output-translations-file pathname)\n                                    :inherit inherit :collect collect))\n      (t\n       (inherit-output-translations inherit :collect collect))))\n  (defmethod process-output-translations ((string string) &key inherit collect)\n    (process-output-translations (parse-output-translations-string string)\n                                 :inherit inherit :collect collect))\n  (defmethod process-output-translations ((x null) &key inherit collect)\n    (inherit-output-translations inherit :collect collect))\n  (defmethod process-output-translations ((form cons) &key inherit collect)\n    (dolist (directive (cdr (validate-output-translations-form form)))\n      (process-output-translations-directive directive :inherit inherit :collect collect)))\n\n\n  ;;; Top-level entry-points to configure output-translations\n\n  (defun compute-output-translations (&optional parameter)\n    \"read the configuration, return it\"\n    (remove-duplicates\n     (while-collecting (c)\n       (inherit-output-translations\n        `(wrapping-output-translations ,parameter ,@*default-output-translations*) :collect #'c))\n     :test 'equal :from-end t))\n\n  ;; Saving the user-provided parameter to output-translations, if any,\n  ;; so we can recompute the translations after code upgrade.\n  (defvar *output-translations-parameter* nil)\n\n  ;; Main entry-point for users.\n  (defun initialize-output-translations (&optional (parameter *output-translations-parameter*))\n    \"read the configuration, initialize the internal configuration variable,\nreturn the configuration\"\n    (setf *output-translations-parameter* parameter\n          (output-translations) (compute-output-translations parameter)))\n\n  (defun disable-output-translations ()\n    \"Initialize output translations in a way that maps every file to itself,\neffectively disabling the output translation facility.\"\n    (initialize-output-translations\n     '(:output-translations :disable-cache :ignore-inherited-configuration)))\n\n  ;; checks an initial variable to see whether the state is initialized\n  ;; or cleared. In the former case, return current configuration; in\n  ;; the latter, initialize.  ASDF will call this function at the start\n  ;; of (asdf:find-system).\n  (defun ensure-output-translations ()\n    (if (output-translations-initialized-p)\n        (output-translations)\n        (initialize-output-translations)))\n\n\n  ;; Top-level entry-point to _use_ output-translations\n  (defun* (apply-output-translations) (path)\n    (etypecase path\n      (logical-pathname\n       path)\n      ((or pathname string)\n       (ensure-output-translations)\n       (loop* :with p = (resolve-symlinks* path)\n              :for (source destination) :in (car *output-translations*)\n              :for root = (when (or (eq source t)\n                                    (and (pathnamep source)\n                                         (not (absolute-pathname-p source))))\n                            (pathname-root p))\n              :for absolute-source = (cond\n                                       ((eq source t) (wilden root))\n                                       (root (merge-pathnames* source root))\n                                       (t source))\n              :when (or (eq source t) (pathname-match-p p absolute-source))\n              :return (translate-pathname* p absolute-source destination root source)\n              :finally (return p)))))\n\n\n  ;; Hook into uiop's output-translation mechanism\n  #-cormanlisp\n  (setf *output-translation-function* 'apply-output-translations)\n\n\n  ;;; Implementation-dependent hacks\n  #+abcl ;; ABCL: make it possible to use systems provided in the ABCL jar.\n  (defun translate-jar-pathname (source wildcard)\n    (declare (ignore wildcard))\n    (flet ((normalize-device (pathname)\n             (if (find :windows *features*)\n                 pathname\n                 (make-pathname :defaults pathname :device :unspecific))))\n      (let* ((jar\n               (pathname (first (pathname-device source))))\n             (target-root-directory-namestring\n               (format nil \"/___jar___file___root___/~@[~A/~]\"\n                       (and (find :windows *features*)\n                            (pathname-device jar))))\n             (relative-source\n               (relativize-pathname-directory source))\n             (relative-jar\n               (relativize-pathname-directory (ensure-directory-pathname jar)))\n             (target-root-directory\n               (normalize-device\n                (pathname-directory-pathname\n                 (parse-namestring target-root-directory-namestring))))\n             (target-root\n               (merge-pathnames* relative-jar target-root-directory))\n             (target\n               (merge-pathnames* relative-source target-root)))\n        (normalize-device (apply-output-translations target))))))\n\n;;;; -----------------------------------------------------------------\n;;;; Source Registry Configuration, by Francois-Rene Rideau\n;;;; See the Manual and https://bugs.launchpad.net/asdf/+bug/485918\n\n(uiop/package:define-package :asdf/source-registry\n  (:recycle :asdf/source-registry :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/find-system)\n  (:export\n   #:*source-registry-parameter* #:*default-source-registries*\n   #:invalid-source-registry\n   #:source-registry-initialized-p\n   #:initialize-source-registry #:clear-source-registry #:*source-registry*\n   #:ensure-source-registry #:*source-registry-parameter*\n   #:*default-source-registry-exclusions* #:*source-registry-exclusions*\n   #:*wild-asd* #:directory-asd-files #:register-asd-directory\n   #:*recurse-beyond-asds* #:collect-asds-in-directory #:collect-sub*directories-asd-files\n   #:validate-source-registry-directive #:validate-source-registry-form\n   #:validate-source-registry-file #:validate-source-registry-directory\n   #:parse-source-registry-string #:wrapping-source-registry\n   #:default-user-source-registry #:default-system-source-registry\n   #:user-source-registry #:system-source-registry\n   #:user-source-registry-directory #:system-source-registry-directory\n   #:environment-source-registry #:process-source-registry #:inherit-source-registry\n   #:compute-source-registry #:flatten-source-registry\n   #:sysdef-source-registry-search))\n(in-package :asdf/source-registry)\n\n(with-upgradability ()\n  (define-condition invalid-source-registry (invalid-configuration warning)\n    ((format :initform (compatfmt \"~@<Invalid source registry ~S~@[ in ~S~]~@{ ~@?~}~@:>\"))))\n\n  ;; Default list of directories under which the source-registry tree search won't recurse\n  (defvar *default-source-registry-exclusions*\n    '(;;-- Using ack 1.2 exclusions\n      \".bzr\" \".cdv\"\n      ;; \"~.dep\" \"~.dot\" \"~.nib\" \"~.plst\" ; we don't support ack wildcards\n      \".git\" \".hg\" \".pc\" \".svn\" \"CVS\" \"RCS\" \"SCCS\" \"_darcs\"\n      \"_sgbak\" \"autom4te.cache\" \"cover_db\" \"_build\"\n      ;;-- debian often builds stuff under the debian directory... BAD.\n      \"debian\"))\n\n  ;; Actual list of directories under which the source-registry tree search won't recurse\n  (defvar *source-registry-exclusions* *default-source-registry-exclusions*)\n\n  ;; The state of the source-registry after search in configured locations\n  (defvar *source-registry* nil\n    \"Either NIL (for uninitialized), or an equal hash-table, mapping\nsystem names to pathnames of .asd files\")\n\n  ;; Saving the user-provided parameter to the source-registry, if any,\n  ;; so we can recompute the source-registry after code upgrade.\n  (defvar *source-registry-parameter* nil)\n\n  (defun source-registry-initialized-p ()\n    (typep *source-registry* 'hash-table))\n\n  (defun clear-source-registry ()\n    \"Undoes any initialization of the source registry.\"\n    (setf *source-registry* nil)\n    (values))\n  (register-clear-configuration-hook 'clear-source-registry)\n\n  (defparameter *wild-asd*\n    (make-pathname :directory nil :name *wild* :type \"asd\" :version :newest))\n\n  (defun directory-asd-files (directory)\n    (directory-files directory *wild-asd*))\n\n  (defun collect-asds-in-directory (directory collect)\n    (let ((asds (directory-asd-files directory)))\n      (map () collect asds)\n      asds))\n\n  (defvar *recurse-beyond-asds* t\n    \"Should :tree entries of the source-registry recurse in subdirectories\nafter having found a .asd file? True by default.\")\n\n  ;; When walking down a filesystem tree, if in a directory there is a .cl-source-registry.cache,\n  ;; read its contents instead of further recursively querying the filesystem.\n  (defun process-source-registry-cache (directory collect)\n    (let ((cache (ignore-errors\n                  (safe-read-file-form (subpathname directory \".cl-source-registry.cache\")))))\n      (when (and (listp cache) (eq :source-registry-cache (first cache)))\n        (loop :for s :in (rest cache) :do (funcall collect (subpathname directory s)))\n        t)))\n\n  (defun collect-sub*directories-asd-files\n      (directory &key (exclude *default-source-registry-exclusions*) collect\n                   (recurse-beyond-asds *recurse-beyond-asds*) ignore-cache)\n    (let ((visited (make-hash-table :test 'equalp)))\n      (flet ((collectp (dir)\n               (unless (and (not ignore-cache) (process-source-registry-cache directory collect))\n                 (let ((asds (collect-asds-in-directory dir collect)))\n                   (or recurse-beyond-asds (not asds)))))\n             (recursep (x)                    ; x will be a directory pathname\n               (and\n                (not (member (car (last (pathname-directory x))) exclude :test #'equal))\n                (flet ((pathname-key (x)\n                         (namestring (truename* x))))\n                  (let ((visitedp (gethash (pathname-key x) visited)))\n                    (if visitedp nil\n                        (setf (gethash (pathname-key x) visited) t)))))))\n      (collect-sub*directories directory #'collectp #'recursep (constantly nil)))))\n\n\n  ;;; Validate the configuration forms\n\n  (defun validate-source-registry-directive (directive)\n    (or (member directive '(:default-registry))\n        (and (consp directive)\n             (let ((rest (rest directive)))\n               (case (first directive)\n                 ((:include :directory :tree)\n                  (and (length=n-p rest 1)\n                       (location-designator-p (first rest))))\n                 ((:exclude :also-exclude)\n                  (every #'stringp rest))\n                 ((:default-registry)\n                  (null rest)))))))\n\n  (defun validate-source-registry-form (form &key location)\n    (validate-configuration-form\n     form :source-registry 'validate-source-registry-directive\n          :location location :invalid-form-reporter 'invalid-source-registry))\n\n  (defun validate-source-registry-file (file)\n    (validate-configuration-file\n     file 'validate-source-registry-form :description \"a source registry\"))\n\n  (defun validate-source-registry-directory (directory)\n    (validate-configuration-directory\n     directory :source-registry 'validate-source-registry-directive\n               :invalid-form-reporter 'invalid-source-registry))\n\n\n  ;;; Parse the configuration string\n\n  (defun parse-source-registry-string (string &key location)\n    (cond\n      ((or (null string) (equal string \"\"))\n       '(:source-registry :inherit-configuration))\n      ((not (stringp string))\n       (error (compatfmt \"~@<Environment string isn't: ~3i~_~S~@:>\") string))\n      ((find (char string 0) \"\\\"(\")\n       (validate-source-registry-form (read-from-string string) :location location))\n      (t\n       (loop\n         :with inherit = nil\n         :with directives = ()\n         :with start = 0\n         :with end = (length string)\n         :with separator = (inter-directory-separator)\n         :for pos = (position separator string :start start) :do\n           (let ((s (subseq string start (or pos end))))\n             (flet ((check (dir)\n                      (unless (absolute-pathname-p dir)\n                        (error (compatfmt \"~@<source-registry string must specify absolute pathnames: ~3i~_~S~@:>\") string))\n                      dir))\n               (cond\n                 ((equal \"\" s) ; empty element: inherit\n                  (when inherit\n                    (error (compatfmt \"~@<Only one inherited configuration allowed: ~3i~_~S~@:>\")\n                           string))\n                  (setf inherit t)\n                  (push ':inherit-configuration directives))\n                 ((string-suffix-p s \"//\") ;; TODO: allow for doubling of separator even outside Unix?\n                  (push `(:tree ,(check (subseq s 0 (- (length s) 2)))) directives))\n                 (t\n                  (push `(:directory ,(check s)) directives))))\n             (cond\n               (pos\n                (setf start (1+ pos)))\n               (t\n                (unless inherit\n                  (push '(:ignore-inherited-configuration) directives))\n                (return `(:source-registry ,@(nreverse directives))))))))))\n\n  (defun register-asd-directory (directory &key recurse exclude collect)\n    (if (not recurse)\n        (collect-asds-in-directory directory collect)\n        (collect-sub*directories-asd-files\n         directory :exclude exclude :collect collect)))\n\n  (defparameter* *default-source-registries*\n    '(environment-source-registry\n      user-source-registry\n      user-source-registry-directory\n      default-user-source-registry\n      system-source-registry\n      system-source-registry-directory\n      default-system-source-registry)\n    \"List of default source registries\" \"3.1.0.102\")\n\n  (defparameter *source-registry-file* (parse-unix-namestring \"common-lisp/source-registry.conf\"))\n  (defparameter *source-registry-directory* (parse-unix-namestring \"common-lisp/source-registry.conf.d/\"))\n\n  (defun wrapping-source-registry ()\n    `(:source-registry\n      #+(or clasp ecl sbcl) (:tree ,(resolve-symlinks* (lisp-implementation-directory)))\n      :inherit-configuration\n      #+mkcl (:tree ,(translate-logical-pathname \"SYS:\"))\n      #+cmucl (:tree #p\"modules:\")\n      #+scl (:tree #p\"file://modules/\")))\n  (defun default-user-source-registry ()\n    `(:source-registry\n      (:tree (:home \"common-lisp/\"))\n      #+sbcl (:directory (:home \".sbcl/systems/\"))\n      (:directory ,(xdg-data-home \"common-lisp/systems/\"))\n      (:tree ,(xdg-data-home \"common-lisp/source/\"))\n      :inherit-configuration))\n  (defun default-system-source-registry ()\n    `(:source-registry\n      ,@(loop :for dir :in (xdg-data-dirs \"common-lisp/\")\n              :collect `(:directory (,dir \"systems/\"))\n              :collect `(:tree (,dir \"source/\")))\n      :inherit-configuration))\n  (defun user-source-registry (&key (direction :input))\n    (xdg-config-pathname *source-registry-file* direction))\n  (defun system-source-registry (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *source-registry-file*)\n                         :direction direction))\n  (defun user-source-registry-directory (&key (direction :input))\n    (xdg-config-pathname *source-registry-directory* direction))\n  (defun system-source-registry-directory (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *source-registry-directory*)\n                         :direction direction))\n  (defun environment-source-registry ()\n    (getenv \"CL_SOURCE_REGISTRY\"))\n\n\n  ;;; Process the source-registry configuration\n\n  (defgeneric process-source-registry (spec &key inherit register))\n\n  (defun* (inherit-source-registry) (inherit &key register)\n    (when inherit\n      (process-source-registry (first inherit) :register register :inherit (rest inherit))))\n\n  (defun* (process-source-registry-directive) (directive &key inherit register)\n    (destructuring-bind (kw &rest rest) (if (consp directive) directive (list directive))\n      (ecase kw\n        ((:include)\n         (destructuring-bind (pathname) rest\n           (process-source-registry (resolve-location pathname) :inherit nil :register register)))\n        ((:directory)\n         (destructuring-bind (pathname) rest\n           (when pathname\n             (funcall register (resolve-location pathname :ensure-directory t)))))\n        ((:tree)\n         (destructuring-bind (pathname) rest\n           (when pathname\n             (funcall register (resolve-location pathname :ensure-directory t)\n                      :recurse t :exclude *source-registry-exclusions*))))\n        ((:exclude)\n         (setf *source-registry-exclusions* rest))\n        ((:also-exclude)\n         (appendf *source-registry-exclusions* rest))\n        ((:default-registry)\n         (inherit-source-registry\n          '(default-user-source-registry default-system-source-registry) :register register))\n        ((:inherit-configuration)\n         (inherit-source-registry inherit :register register))\n        ((:ignore-inherited-configuration)\n         nil)))\n    nil)\n\n  (defmethod process-source-registry ((x symbol) &key inherit register)\n    (process-source-registry (funcall x) :inherit inherit :register register))\n  (defmethod process-source-registry ((pathname pathname) &key inherit register)\n    (cond\n      ((directory-pathname-p pathname)\n       (let ((*here-directory* (resolve-symlinks* pathname)))\n         (process-source-registry (validate-source-registry-directory pathname)\n                                  :inherit inherit :register register)))\n      ((probe-file* pathname :truename *resolve-symlinks*)\n       (let ((*here-directory* (pathname-directory-pathname pathname)))\n         (process-source-registry (validate-source-registry-file pathname)\n                                  :inherit inherit :register register)))\n      (t\n       (inherit-source-registry inherit :register register))))\n  (defmethod process-source-registry ((string string) &key inherit register)\n    (process-source-registry (parse-source-registry-string string)\n                             :inherit inherit :register register))\n  (defmethod process-source-registry ((x null) &key inherit register)\n    (inherit-source-registry inherit :register register))\n  (defmethod process-source-registry ((form cons) &key inherit register)\n    (let ((*source-registry-exclusions* *default-source-registry-exclusions*))\n      (dolist (directive (cdr (validate-source-registry-form form)))\n        (process-source-registry-directive directive :inherit inherit :register register))))\n\n\n  ;; Flatten the user-provided configuration into an ordered list of directories and trees\n  (defun flatten-source-registry (&optional (parameter *source-registry-parameter*))\n    (remove-duplicates\n     (while-collecting (collect)\n       (with-pathname-defaults () ;; be location-independent\n         (inherit-source-registry\n          `(wrapping-source-registry\n            ,parameter\n            ,@*default-source-registries*)\n          :register #'(lambda (directory &key recurse exclude)\n                        (collect (list directory :recurse recurse :exclude exclude))))))\n     :test 'equal :from-end t))\n\n  ;; MAYBE: move this utility function to uiop/pathname and export it?\n  (defun pathname-directory-depth (p)\n    (length (normalize-pathname-directory-component (pathname-directory p))))\n\n  (defun preferred-source-path-p (x y)\n    \"Return T iff X is to be preferred over Y as a source path\"\n    (let ((lx (pathname-directory-depth x))\n          (ly (pathname-directory-depth y)))\n      (or (< lx ly)\n          (and (= lx ly)\n               (string< (namestring x)\n                        (namestring y))))))\n\n  ;; Will read the configuration and initialize all internal variables.\n  (defun compute-source-registry (&optional (parameter *source-registry-parameter*)\n                                    (registry *source-registry*))\n    (dolist (entry (flatten-source-registry parameter))\n      (destructuring-bind (directory &key recurse exclude) entry\n        (let* ((h (make-hash-table :test 'equal))) ; table to detect duplicates\n          (register-asd-directory\n           directory :recurse recurse :exclude exclude :collect\n           #'(lambda (asd)\n               (let* ((name (pathname-name asd))\n                      (name (if (typep asd 'logical-pathname)\n                                ;; logical pathnames are upper-case,\n                                ;; at least in the CLHS and on SBCL,\n                                ;; yet (coerce-name :foo) is lower-case.\n                                ;; won't work well with (load-system \"Foo\")\n                                ;; instead of (load-system 'foo)\n                                (string-downcase name)\n                                name)))\n                 (unless (gethash name registry) ; already shadowed by something else\n                   (if-let (old (gethash name h))\n                     ;; If the name appears multiple times,\n                     ;; prefer the one with the shallowest directory,\n                     ;; or if they have same depth, compare unix-namestring with string<\n                     (multiple-value-bind (better worse)\n                         (if (preferred-source-path-p asd old)\n                             (progn (setf (gethash name h) asd) (values asd old))\n                             (values old asd))\n                       (when *verbose-out*\n                         (warn (compatfmt \"~@<In source-registry entry ~A~@[/~*~] ~\n                                              found several entries for ~A - picking ~S over ~S~:>\")\n                               directory recurse name better worse)))\n                     (setf (gethash name h) asd))))))\n          (maphash #'(lambda (k v) (setf (gethash k registry) v)) h))))\n    (values))\n\n  (defun initialize-source-registry (&optional (parameter *source-registry-parameter*))\n    ;; Record the parameter used to configure the registry\n    (setf *source-registry-parameter* parameter)\n    ;; Clear the previous registry database:\n    (setf *source-registry* (make-hash-table :test 'equal))\n    ;; Do it!\n    (compute-source-registry parameter))\n\n  ;; Checks an initial variable to see whether the state is initialized\n  ;; or cleared. In the former case, return current configuration; in\n  ;; the latter, initialize.  ASDF will call this function at the start\n  ;; of (asdf:find-system) to make sure the source registry is initialized.\n  ;; However, it will do so *without* a parameter, at which point it\n  ;; will be too late to provide a parameter to this function, though\n  ;; you may override the configuration explicitly by calling\n  ;; initialize-source-registry directly with your parameter.\n  (defun ensure-source-registry (&optional parameter)\n    (unless (source-registry-initialized-p)\n      (initialize-source-registry parameter))\n    (values))\n\n  (defun sysdef-source-registry-search (system)\n    (ensure-source-registry)\n    (values (gethash (primary-system-name system) *source-registry*))))\n\n\n;;;; -------------------------------------------------------------------------\n;;;; Package systems in the style of quick-build or faslpath\n\n(uiop:define-package :asdf/package-inferred-system\n  (:recycle :asdf/package-inferred-system :asdf/package-system :asdf)\n  (:use :uiop/common-lisp :uiop\n        :asdf/defsystem ;; Using the old name of :asdf/parse-defsystem for compatibility\n        :asdf/upgrade :asdf/component :asdf/system :asdf/find-system :asdf/lisp-action)\n  (:export\n   #:package-inferred-system #:sysdef-package-inferred-system-search\n   #:package-system ;; backward compatibility only. To be removed.\n   #:register-system-packages\n   #:*defpackage-forms* #:*package-inferred-systems* #:package-inferred-system-missing-package-error))\n(in-package :asdf/package-inferred-system)\n\n(with-upgradability ()\n  ;; The names of the recognized defpackage forms.\n  (defparameter *defpackage-forms* '(defpackage define-package))\n\n  (defun initial-package-inferred-systems-table ()\n    ;; Mark all existing packages are preloaded.\n    (let ((h (make-hash-table :test 'equal)))\n      (dolist (p (list-all-packages))\n        (dolist (n (package-names p))\n          (setf (gethash n h) t)))\n      h))\n\n  ;; Mapping from package names to systems that provide them.\n  (defvar *package-inferred-systems* (initial-package-inferred-systems-table))\n\n  (defclass package-inferred-system (system)\n    ()\n    (:documentation \"Class for primary systems for which secondary systems are automatically\nin the one-file, one-file, one-system style: system names are mapped to files under the primary\nsystem's system-source-directory, dependencies are inferred from the first defpackage form in\nevery such file\"))\n\n  ;; DEPRECATED. For backward compatibility only. To be removed in an upcoming release:\n  (defclass package-system (package-inferred-system) ())\n\n  ;; Is a given form recognizable as a defpackage form?\n  (defun defpackage-form-p (form)\n    (and (consp form)\n         (member (car form) *defpackage-forms*)))\n\n  ;; Find the first defpackage form in a stream, if any\n  (defun stream-defpackage-form (stream)\n    (loop :for form = (read stream nil nil) :while form\n          :when (defpackage-form-p form) :return form))\n\n  (defun file-defpackage-form (file)\n    \"Return the first DEFPACKAGE form in FILE.\"\n    (with-input-file (f file)\n      (stream-defpackage-form f)))\n\n  (define-condition package-inferred-system-missing-package-error (system-definition-error)\n    ((system :initarg :system :reader error-system)\n     (pathname :initarg :pathname :reader error-pathname))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<No package form found while ~\n                                     trying to define package-inferred-system ~A from file ~A~>\")\n                       (error-system c) (error-pathname c)))))\n\n  (defun package-dependencies (defpackage-form)\n    \"Return a list of packages depended on by the package\ndefined in DEFPACKAGE-FORM.  A package is depended upon if\nthe DEFPACKAGE-FORM uses it or imports a symbol from it.\"\n    (assert (defpackage-form-p defpackage-form))\n    (remove-duplicates\n     (while-collecting (dep)\n       (loop* :for (option . arguments) :in (cddr defpackage-form) :do\n              (ecase option\n                ((:use :mix :reexport :use-reexport :mix-reexport)\n                 (dolist (p arguments) (dep (string p))))\n                ((:import-from :shadowing-import-from)\n                 (dep (string (first arguments))))\n                ((:nicknames :documentation :shadow :export :intern :unintern :recycle)))))\n     :from-end t :test 'equal))\n\n  (defun package-designator-name (package)\n    \"Normalize a package designator to a string\"\n    (etypecase package\n      (package (package-name package))\n      (string package)\n      (symbol (string package))))\n\n  (defun register-system-packages (system packages)\n    \"Register SYSTEM as providing PACKAGES.\"\n    (let ((name (or (eq system t) (coerce-name system))))\n      (dolist (p (ensure-list packages))\n        (setf (gethash (package-designator-name p) *package-inferred-systems*) name))))\n\n  (defun package-name-system (package-name)\n    \"Return the name of the SYSTEM providing PACKAGE-NAME, if such exists,\notherwise return a default system name computed from PACKAGE-NAME.\"\n    (check-type package-name string)\n    (or (gethash package-name *package-inferred-systems*)\n        (string-downcase package-name)))\n\n  ;; Given a file in package-inferred-system style, find its dependencies\n  (defun package-inferred-system-file-dependencies (file &optional system)\n    (if-let (defpackage-form (file-defpackage-form file))\n      (remove t (mapcar 'package-name-system (package-dependencies defpackage-form)))\n      (error 'package-inferred-system-missing-package-error :system system :pathname file)))\n\n  ;; Given package-inferred-system object, check whether its specification matches\n  ;; the provided parameters\n  (defun same-package-inferred-system-p (system name directory subpath around-compile dependencies)\n    (and (eq (type-of system) 'package-inferred-system)\n         (equal (component-name system) name)\n         (pathname-equal directory (component-pathname system))\n         (equal dependencies (component-sideway-dependencies system))\n         (equal around-compile (around-compile-hook system))\n         (let ((children (component-children system)))\n           (and (length=n-p children 1)\n                (let ((child (first children)))\n                  (and (eq (type-of child) 'cl-source-file)\n                       (equal (component-name child) \"lisp\")\n                       (and (slot-boundp child 'relative-pathname)\n                            (equal (slot-value child 'relative-pathname) subpath))))))))\n\n  ;; sysdef search function to push into *system-definition-search-functions*\n  (defun sysdef-package-inferred-system-search (system)\n    (let ((primary (primary-system-name system)))\n      (unless (equal primary system)\n        (let ((top (find-system primary nil)))\n          (when (typep top 'package-inferred-system)\n            (if-let (dir (component-pathname top))\n              (let* ((sub (subseq system (1+ (length primary))))\n                     (f (probe-file* (subpathname dir sub :type \"lisp\")\n                                     :truename *resolve-symlinks*)))\n                (when (file-pathname-p f)\n                  (let ((dependencies (package-inferred-system-file-dependencies f system))\n                        (previous (registered-system system))\n                        (around-compile (around-compile-hook top)))\n                    (if (same-package-inferred-system-p previous system dir sub around-compile dependencies)\n                        previous\n                        (eval `(defsystem ,system\n                                 :class package-inferred-system\n                                 :source-file nil\n                                 :pathname ,dir\n                                 :depends-on ,dependencies\n                                 :around-compile ,around-compile\n                                 :components ((cl-source-file \"lisp\" :pathname ,sub)))))))))))))))\n\n(with-upgradability ()\n  (pushnew 'sysdef-package-inferred-system-search *system-definition-search-functions*)\n  (setf *system-definition-search-functions*\n        (remove (find-symbol* :sysdef-package-system-search :asdf/package-system nil)\n                *system-definition-search-functions*)))\n;;;; -------------------------------------------------------------------------\n;;; Backward-compatible interfaces\n\n(uiop/package:define-package :asdf/backward-interface\n  (:recycle :asdf/backward-interface :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/system :asdf/find-system :asdf/operation :asdf/action\n   :asdf/lisp-action :asdf/plan :asdf/operate :asdf/output-translations)\n  (:export\n   #:*asdf-verbose*\n   #:operation-error #:compile-error #:compile-failed #:compile-warned\n   #:error-component #:error-operation #:traverse\n   #:component-load-dependencies\n   #:enable-asdf-binary-locations-compatibility\n   #:operation-on-failure #:operation-on-warnings #:on-failure #:on-warnings\n   #:component-property\n   #:run-shell-command\n   #:system-definition-pathname\n   #:explain))\n(in-package :asdf/backward-interface)\n\n;; NB: the warning status of these functions may have to be distinguished later,\n;; as some get removed faster than the others in client code.\n(with-asdf-deprecation (:style-warning \"3.2\")\n\n  ;; These conditions from ASDF 1 and 2 are used by many packages in Quicklisp;\n  ;; but ASDF3 replaced them with somewhat different variants of uiop:compile-condition\n  ;; that do not involve ASDF actions.\n  ;; TODO: find the offenders and stop them.\n  (progn\n    (define-condition operation-error (error) ;; Bad, backward-compatible name\n      ;; Used by SBCL, cffi-tests, clsql-mysql, clsql-uffi, qt, elephant, uffi-tests, sb-grovel\n      ((component :reader error-component :initarg :component)\n       (operation :reader error-operation :initarg :operation))\n      (:report (lambda (c s)\n                 (format s (compatfmt \"~@<~A while invoking ~A on ~A~@:>\")\n                         (type-of c) (error-operation c) (error-component c)))))\n    (define-condition compile-error (operation-error) ())\n    (define-condition compile-failed (compile-error) ())\n    (define-condition compile-warned (compile-error) ()))\n\n  ;; In Quicklisp 2015-05, still used by lisp-executable, staple, repl-utilities, cffi\n  (defun component-load-dependencies (component) ;; from ASDF 2.000 to 2.26\n    \"DEPRECATED. Please use COMPONENT-SIDEWAY-DEPENDENCIES instead; or better,\ndefine your operations with proper use of SIDEWAY-OPERATION, SELFWARD-OPERATION,\nor define methods on PREPARE-OP, etc.\"\n    ;; Old deprecated name for the same thing. Please update your software.\n    (component-sideway-dependencies component))\n\n  ;; These old interfaces from ASDF1 have never been very meaningful\n  ;; but are still used in obscure places.\n  ;; In Quicklisp 2015-05, still used by cl-protobufs and clx.\n  (defgeneric operation-on-warnings (operation)\n    (:documentation \"DEPRECATED. Please use UIOP:*COMPILE-FILE-WARNINGS-BEHAVIOUR* instead.\"))\n  (defgeneric operation-on-failure (operation)\n    (:documentation \"DEPRECATED. Please use UIOP:*COMPILE-FILE-FAILURE-BEHAVIOUR* instead.\"))\n  (defgeneric (setf operation-on-warnings) (x operation)\n    (:documentation \"DEPRECATED. Please SETF UIOP:*COMPILE-FILE-WARNINGS-BEHAVIOUR* instead.\"))\n  (defgeneric (setf operation-on-failure) (x operation)\n    (:documentation \"DEPRECATED. Please SETF UIOP:*COMPILE-FILE-FAILURE-BEHAVIOUR* instead.\"))\n  (progn\n    (defmethod operation-on-warnings ((o operation))\n      *compile-file-warnings-behaviour*)\n    (defmethod operation-on-failure ((o operation))\n      *compile-file-failure-behaviour*)\n    (defmethod (setf operation-on-warnings) (x (o operation))\n      (setf *compile-file-warnings-behaviour* x))\n    (defmethod (setf operation-on-failure) (x (o operation))\n      (setf *compile-file-failure-behaviour* x)))\n\n  ;; Quicklisp 2015-05: Still used by SLIME's swank-asdf (!), common-lisp-stat,\n  ;; js-parser, osicat, babel, staple, weblocks, cl-png, plain-odbc, autoproject,\n  ;; cl-blapack, com.informatimago, cells-gtk3, asdf-dependency-grovel,\n  ;; cl-glfw, cffi, jwacs, montezuma\n  (defun system-definition-pathname (x)\n    ;; As of 2.014.8, we mean to make this function obsolete,\n    ;; but that won't happen until all clients have been updated.\n    \"DEPRECATED. This function used to expose ASDF internals with subtle\ndifferences with respect to user expectations, that have been refactored\naway since. We recommend you use ASDF:SYSTEM-SOURCE-FILE instead for a\nmostly compatible replacement that we're supporting, or even\nASDF:SYSTEM-SOURCE-DIRECTORY or ASDF:SYSTEM-RELATIVE-PATHNAME\nif that's whay you mean.\" ;;)\n    (system-source-file x))\n\n  ;; TRAVERSE is the function used to compute a plan in ASDF 1 and 2.\n  ;; It was never officially exposed but some people still used it.\n  (defgeneric traverse (operation component &key &allow-other-keys)\n    (:documentation\n     \"DEPRECATED. Use MAKE-PLAN and PLAN-ACTIONS, or REQUIRED-COMPONENTS,\nor some other supported interface instead.\n\nGenerate and return a plan for performing OPERATION on COMPONENT.\n\nThe plan returned is a list of dotted-pairs. Each pair is the CONS\nof ASDF operation object and a COMPONENT object. The pairs will be\nprocessed in order by OPERATE.\"))\n  (progn\n    (define-convenience-action-methods traverse (operation component &key)))\n  (defmethod traverse ((o operation) (c component) &rest keys &key plan-class &allow-other-keys)\n    (plan-actions (apply 'make-plan plan-class o c keys)))\n\n\n  ;; ASDF-Binary-Locations compatibility\n  ;; This remains supported for legacy user, but not recommended for new users.\n  ;; We suspect there are no more legacy users in 2016.\n  (defun enable-asdf-binary-locations-compatibility\n      (&key\n         (centralize-lisp-binaries nil)\n         (default-toplevel-directory\n             ;; Use \".cache/common-lisp/\" instead ???\n             (subpathname (user-homedir-pathname) \".fasls/\"))\n         (include-per-user-information nil)\n         (map-all-source-files (or #+(or clasp clisp ecl mkcl) t nil))\n         (source-to-target-mappings nil)\n         (file-types `(,(compile-file-type)\n                        \"build-report\"\n                        #+clasp (compile-file-type :output-type :object)\n                        #+ecl (compile-file-type :type :object)\n                        #+mkcl (compile-file-type :fasl-p nil)\n                        #+clisp \"lib\" #+sbcl \"cfasl\"\n                        #+sbcl \"sbcl-warnings\" #+clozure \"ccl-warnings\")))\n    \"DEPRECATED. Use asdf-output-translations instead.\"\n    #+(or clasp clisp ecl mkcl)\n    (when (null map-all-source-files)\n      (error \"asdf:enable-asdf-binary-locations-compatibility doesn't support :map-all-source-files nil on CLISP, ECL and MKCL\"))\n    (let* ((patterns (if map-all-source-files (list *wild-file*)\n                         (loop :for type :in file-types\n                           :collect (make-pathname :type type :defaults *wild-file*))))\n           (destination-directory\n            (if centralize-lisp-binaries\n                `(,default-toplevel-directory\n                     ,@(when include-per-user-information\n                             (cdr (pathname-directory (user-homedir-pathname))))\n                     :implementation ,*wild-inferiors*)\n                `(:root ,*wild-inferiors* :implementation))))\n      (initialize-output-translations\n       `(:output-translations\n         ,@source-to-target-mappings\n         #+abcl (#p\"jar:file:/**/*.jar!/**/*.*\" (:function translate-jar-pathname))\n         #+abcl (#p\"/___jar___file___root___/**/*.*\" (,@destination-directory))\n         ,@(loop :for pattern :in patterns\n             :collect `((:root ,*wild-inferiors* ,pattern)\n                        (,@destination-directory ,pattern)))\n         (t t)\n         :ignore-inherited-configuration))))\n  (progn\n    (defmethod operate :before (operation-class system &rest args &key &allow-other-keys)\n      (declare (ignore operation-class system args))\n      (when (find-symbol* '#:output-files-for-system-and-operation :asdf nil)\n        (error \"ASDF 2 is not compatible with ASDF-BINARY-LOCATIONS, which you are using.\nASDF 2 now achieves the same purpose with its builtin ASDF-OUTPUT-TRANSLATIONS,\nwhich should be easier to configure. Please stop using ASDF-BINARY-LOCATIONS,\nand instead use ASDF-OUTPUT-TRANSLATIONS. See the ASDF manual for details.\nIn case you insist on preserving your previous A-B-L configuration, but\ndo not know how to achieve the same effect with A-O-T, you may use function\nASDF:ENABLE-ASDF-BINARY-LOCATIONS-COMPATIBILITY as documented in the manual;\ncall that function where you would otherwise have loaded and configured A-B-L.\"))))\n\n\n  ;; run-shell-command from ASDF 2, lightly fixed from ASDF 1, copied from MK-DEFSYSTEM. Die!\n  (defun run-shell-command (control-string &rest args)\n    \"PLEASE DO NOT USE. This function is not just DEPRECATED, but also dysfunctional.\nPlease use UIOP:RUN-PROGRAM instead.\"\n    #-(and ecl os-windows)\n    (let ((command (apply 'format nil control-string args)))\n      (asdf-message \"; $ ~A~%\" command)\n      (let ((exit-code\n             (ignore-errors\n               (nth-value 2 (run-program command :force-shell t :ignore-error-status t\n                                         :output *verbose-out*)))))\n        (typecase exit-code\n          ((integer 0 255) exit-code)\n          (t 255))))\n    #+(and ecl os-windows)\n    (not-implemented-error \"run-shell-command\" \"for ECL on Windows.\"))\n\n  ;; HOW do we get rid of variables??? With a symbol-macro that issues a warning?\n  ;; In Quicklisp 2015-05, cl-protobufs still uses it, but that should be fixed in next version.\n  (progn\n    (defvar *asdf-verbose* nil)) ;; backward-compatibility with ASDF2 only. Unused.\n\n  ;; Do NOT use in new code. NOT SUPPORTED.\n  ;; NB: When this goes away, remove the slot PROPERTY in COMPONENT.\n  ;; In Quicklisp 2014-05, it's still used by yaclml, amazon-ecs, blackthorn-engine, cl-tidy.\n  ;; See TODO for further cleanups required before to get rid of it.\n  (defgeneric component-property (component property))\n  (defgeneric (setf component-property) (new-value component property))\n\n  (defmethod component-property ((c component) property)\n    (cdr (assoc property (slot-value c 'properties) :test #'equal)))\n\n  (defmethod (setf component-property) (new-value (c component) property)\n    (let ((a (assoc property (slot-value c 'properties) :test #'equal)))\n      (if a\n          (setf (cdr a) new-value)\n          (setf (slot-value c 'properties)\n                (acons property new-value (slot-value c 'properties)))))\n    new-value)\n\n\n  ;; This method survives from ASDF 1, but really it is superseded by action-description.\n  (defgeneric explain (operation component)\n    (:documentation \"Display a message describing an action.\n\nDEPRECATED. Use ASDF:ACTION-DESCRIPTION and/or ASDF::FORMAT-ACTION instead.\"))\n  (progn\n    (define-convenience-action-methods explain (operation component)))\n  (defmethod explain ((o operation) (c component))\n    (asdf-message (compatfmt \"~&~@<; ~@;~A~:>~%\") (action-description o c))))\n;;;; -------------------------------------------------------------------------\n;;; Internal hacks for backward-compatibility\n\n(uiop/package:define-package :asdf/backward-internals\n  (:recycle :asdf/backward-internals :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/find-system)\n  (:export #:load-sysdef))\n(in-package :asdf/backward-internals)\n\n(with-asdf-deprecation (:style-warning \"3.2\")\n  (defun load-sysdef (name pathname)\n    (declare (ignore name pathname))\n    ;; Needed for backward compatibility with swank-asdf from SLIME 2015-12-01 or older.\n    (error \"Use asdf:load-asd instead of asdf::load-sysdef\")))\n;;;; ---------------------------------------------------------------------------\n;;;; Handle ASDF package upgrade, including implementation-dependent magic.\n\n(uiop/package:define-package :asdf/interface\n  (:nicknames :asdf :asdf-utilities)\n  (:recycle :asdf/interface :asdf)\n  (:unintern\n   #:loaded-systems ; makes for annoying SLIME completion\n   #:output-files-for-system-and-operation) ; ASDF-BINARY-LOCATION function we use to detect ABL\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/cache\n   :asdf/component :asdf/system :asdf/find-system :asdf/find-component\n   :asdf/operation :asdf/action :asdf/lisp-action\n   :asdf/output-translations :asdf/source-registry\n   :asdf/plan :asdf/operate :asdf/parse-defsystem :asdf/bundle :asdf/concatenate-source\n   :asdf/backward-internals :asdf/backward-interface :asdf/package-inferred-system)\n  ;; Note: (1) we are NOT automatically reexporting everything from previous packages.\n  ;; (2) we only reexport UIOP functionality when backward-compatibility requires it.\n  (:export\n   #:defsystem #:find-system #:load-asd #:locate-system #:coerce-name #:primary-system-name\n   #:oos #:operate #:make-plan #:perform-plan #:sequential-plan\n   #:system-definition-pathname\n   #:search-for-system-definition #:find-component #:component-find-path\n   #:compile-system #:load-system #:load-systems #:load-systems*\n   #:require-system #:test-system #:clear-system\n   #:operation #:make-operation #:find-operation\n   #:upward-operation #:downward-operation #:sideway-operation #:selfward-operation\n                      #:non-propagating-operation\n   #:build-op #:make\n   #:load-op #:prepare-op #:compile-op\n   #:prepare-source-op #:load-source-op #:test-op\n   #:feature #:version #:version-satisfies #:upgrade-asdf\n   #:implementation-identifier #:implementation-type #:hostname\n   #:input-files #:output-files #:output-file #:perform #:perform-with-restarts\n   #:operation-done-p #:explain #:action-description #:component-sideway-dependencies\n   #:needed-in-image-p\n   #:component-load-dependencies #:run-shell-command ; deprecated, do not use\n   #:bundle-op #:monolithic-bundle-op #:precompiled-system #:compiled-file #:bundle-system\n   #:program-system\n   #:basic-compile-bundle-op #:prepare-bundle-op\n   #:compile-bundle-op #:load-bundle-op #:monolithic-compile-bundle-op #:monolithic-load-bundle-op\n   #:lib-op #:dll-op #:deliver-asd-op #:program-op #:image-op\n   #:monolithic-lib-op #:monolithic-dll-op #:monolithic-deliver-asd-op\n   #:concatenate-source-op\n   #:load-concatenated-source-op\n   #:compile-concatenated-source-op\n   #:load-compiled-concatenated-source-op\n   #:monolithic-concatenate-source-op\n   #:monolithic-load-concatenated-source-op\n   #:monolithic-compile-concatenated-source-op\n   #:monolithic-load-compiled-concatenated-source-op\n   #:operation-monolithic-p\n   #:required-components\n   #:component-loaded-p\n\n   #:component #:parent-component #:child-component #:system #:module\n   #:file-component #:source-file #:c-source-file #:java-source-file\n   #:cl-source-file #:cl-source-file.cl #:cl-source-file.lsp\n   #:static-file #:doc-file #:html-file\n   #:file-type #:source-file-type\n\n   #:register-preloaded-system #:sysdef-preloaded-system-search\n   #:register-immutable-system #:sysdef-immutable-system-search\n\n   #:package-inferred-system #:register-system-packages\n   #:package-system ;; backward-compatibility during migration, to be removed in a further release.\n\n   #:component-children          ; component accessors\n   #:component-children-by-name\n   #:component-pathname\n   #:component-relative-pathname\n   #:component-name\n   #:component-version\n   #:component-parent\n   #:component-system\n   #:component-encoding\n   #:component-external-format\n\n   #:component-depends-on ; backward-compatible name rather than action-depends-on\n   #:module-components ; backward-compatibility\n   #:operation-on-warnings #:operation-on-failure ; backward-compatibility\n   #:component-property ; backward-compatibility\n   #:traverse ; backward-compatibility\n\n   #:system-description\n   #:system-long-description\n   #:system-author\n   #:system-maintainer\n   #:system-license\n   #:system-licence\n   #:system-source-file\n   #:system-source-directory\n   #:system-relative-pathname\n   #:system-homepage\n   #:system-mailto\n   #:system-bug-tracker\n   #:system-long-name\n   #:system-source-control\n   #:map-systems\n   #:system-defsystem-depends-on\n   #:system-depends-on\n   #:system-weakly-depends-on\n\n   #:*system-definition-search-functions*   ; variables\n   #:*central-registry*\n   #:*compile-file-warnings-behaviour*\n   #:*compile-file-failure-behaviour*\n   #:*resolve-symlinks*\n   #:*asdf-verbose* ;; unused. For backward-compatibility only.\n   #:*verbose-out*\n\n   #:asdf-version\n\n   #:compile-condition #:compile-file-error #:compile-warned-error #:compile-failed-error\n   #:compile-warned-warning #:compile-failed-warning\n   #:operation-error #:compile-failed #:compile-warned #:compile-error ;; backward compatibility\n   #:error-name\n   #:error-pathname\n   #:load-system-definition-error\n   #:error-component #:error-operation\n   #:system-definition-error\n   #:missing-component\n   #:missing-component-of-version\n   #:missing-dependency\n   #:missing-dependency-of-version\n   #:circular-dependency        ; errors\n   #:duplicate-names #:non-toplevel-system #:non-system-system #:bad-system-name\n   #:package-inferred-system-missing-package-error\n   #:operation-definition-warning #:operation-definition-error\n\n   #:try-recompiling ; restarts\n   #:retry\n   #:accept\n   #:coerce-entry-to-directory\n   #:remove-entry-from-registry\n   #:clear-configuration-and-retry\n\n\n   #:*encoding-detection-hook*\n   #:*encoding-external-format-hook*\n   #:*default-encoding*\n   #:*utf-8-external-format*\n\n   #:clear-configuration\n   #:*output-translations-parameter*\n   #:initialize-output-translations\n   #:disable-output-translations\n   #:clear-output-translations\n   #:ensure-output-translations\n   #:apply-output-translations\n   #:compile-file*\n   #:compile-file-pathname*\n   #:*warnings-file-type* #:enable-deferred-warnings-check #:disable-deferred-warnings-check\n   #:enable-asdf-binary-locations-compatibility\n   #:*default-source-registries*\n   #:*source-registry-parameter*\n   #:initialize-source-registry\n   #:compute-source-registry\n   #:clear-source-registry\n   #:ensure-source-registry\n   #:process-source-registry\n   #:system-registered-p #:registered-systems #:already-loaded-systems\n   #:resolve-location\n   #:asdf-message\n   #:*user-cache*\n   #:user-output-translations-pathname\n   #:system-output-translations-pathname\n   #:user-output-translations-directory-pathname\n   #:system-output-translations-directory-pathname\n   #:user-source-registry\n   #:system-source-registry\n   #:user-source-registry-directory\n   #:system-source-registry-directory\n   ))\n\n;;;; ---------------------------------------------------------------------------\n;;;; ASDF-USER, where the action happens.\n\n(uiop/package:define-package :asdf/user\n  (:nicknames :asdf-user)\n  ;; NB: releases before 3.1.2 this :use'd only uiop/package instead of uiop below.\n  ;; They also :use'd uiop/common-lisp, that reexports common-lisp and is not included in uiop.\n  ;; ASDF3 releases from 2.27 to 2.31 called uiop asdf-driver and asdf/foo uiop/foo.\n  ;; ASDF1 and ASDF2 releases (2.26 and earlier) create a temporary package\n  ;; that only :use's :cl and :asdf\n  (:use :uiop/common-lisp :uiop :asdf/interface))\n;;;; -----------------------------------------------------------------------\n;;;; ASDF Footer: last words and cleanup\n\n(uiop/package:define-package :asdf/footer\n  (:recycle :asdf/footer :asdf)\n  (:use :uiop/common-lisp :uiop\n        :asdf/upgrade :asdf/find-system :asdf/operate :asdf/bundle)\n  ;; Happily, all those implementations all have the same module-provider hook interface.\n  #+(or abcl clasp cmucl clozure ecl mkcl sbcl)\n  (:import-from #+abcl :sys #+(or clasp cmucl ecl) :ext #+clozure :ccl #+mkcl :mk-ext #+sbcl sb-ext\n\t\t#:*module-provider-functions*\n\t\t#+ecl #:*load-hooks*)\n  #+(or clasp mkcl) (:import-from :si #:*load-hooks*))\n\n(in-package :asdf/footer)\n\n;;;; Register ASDF itself and all its subsystems as preloaded.\n(with-upgradability ()\n  (dolist (s '(\"asdf\" \"uiop\" \"asdf-package-system\"))\n    ;; Don't bother with these system names, no one relies on them anymore:\n    ;; \"asdf-utils\" \"asdf-bundle\" \"asdf-driver\" \"asdf-defsystem\"\n    (register-preloaded-system s :version *asdf-version*)))\n\n\n;;;; Hook ASDF into the implementation's REQUIRE and other entry points.\n#+(or abcl clasp clisp clozure cmucl ecl mkcl sbcl)\n(with-upgradability ()\n  ;; Hook into CL:REQUIRE.\n  #-clisp (pushnew 'module-provide-asdf *module-provider-functions*)\n  #+clisp (if-let (x (find-symbol* '#:*module-provider-functions* :custom nil))\n            (eval `(pushnew 'module-provide-asdf ,x)))\n\n  #+(or clasp ecl mkcl)\n  (progn\n    (pushnew '(\"fasb\" . si::load-binary) *load-hooks* :test 'equal :key 'car)\n\n    #+os-windows\n    (unless (assoc \"asd\" *load-hooks* :test 'equal)\n      (appendf *load-hooks* '((\"asd\" . si::load-source))))\n\n    ;; Wrap module provider functions in an idempotent, upgrade friendly way\n    (defvar *wrapped-module-provider* (make-hash-table))\n    (setf (gethash 'module-provide-asdf *wrapped-module-provider*) 'module-provide-asdf)\n    (defun wrap-module-provider (provider name)\n      (let ((results (multiple-value-list (funcall provider name))))\n\t(when (first results) (register-preloaded-system (coerce-name name)))\n\t(values-list results)))\n    (defun wrap-module-provider-function (provider)\n      (ensure-gethash provider *wrapped-module-provider*\n\t\t      (constantly\n\t\t       #'(lambda (module-name)\n\t\t\t   (wrap-module-provider provider module-name)))))\n    (setf *module-provider-functions*\n\t  (mapcar #'wrap-module-provider-function *module-provider-functions*))))\n\n#+cmucl ;; Hook into the CMUCL herald.\n(with-upgradability ()\n  (defun herald-asdf (stream)\n    (format stream \"    ASDF ~A\" (asdf-version)))\n  (setf (getf ext:*herald-items* :asdf) '(herald-asdf)))\n\n\n;;;; Done!\n(with-upgradability ()\n  #+allegro ;; restore *w-o-n-r-c* setting as saved in uiop/common-lisp\n  (when (boundp 'excl:*warn-on-nested-reader-conditionals*)\n    (setf excl:*warn-on-nested-reader-conditionals* uiop/common-lisp::*acl-warn-save*))\n\n  ;; Advertise the features we provide.\n  (dolist (f '(:asdf :asdf2 :asdf3 :asdf3.1 :asdf3.2 :asdf-package-system)) (pushnew f *features*))\n\n  ;; Provide both lowercase and uppercase, to satisfy more people, especially LispWorks users.\n  (provide \"asdf\") (provide \"ASDF\")\n\n  ;; Finally, call a function that will cleanup in case this is an upgrade of an older ASDF.\n  (cleanup-upgraded-asdf))\n\n(when *load-verbose*\n  (asdf-message \";; ASDF, version ~a~%\" (asdf-version)))\n"
  },
  {
    "path": "quicklisp/client-info.sexp",
    "content": "(:version \"2021-02-13\"\n :client-info-format \"1\"\n\n :subscription-url\n \"http://beta.quicklisp.org/client/quicklisp.sexp\"\n :canonical-client-info-url\n \"http://beta.quicklisp.org/client/2021-02-13/client-info.sexp\"\n\n :client-tar\n (:url \"http://beta.quicklisp.org/client/2021-02-13/quicklisp.tar\"\n  :size 266240\n  :md5 \"542cf39ce18d25b245976a5ea26e4b9b\"\n  :sha256\n  \"a8a3c8c91b51dd185175abad4d7c3999ebb4e2520be5a6cee2127035ac6c87be\")\n\n :setup\n (:url \"http://beta.quicklisp.org/client/2021-02-11/setup.lisp\"\n  :size 5057\n  :md5 \"76f4570ecee8066924f915cb882dba64\"\n  :sha256\n  \"549fe3e7e0f2669daede98437c99cd60e02c0b8536d3d135c9aa9d346ed951b6\")\n\n :asdf\n (:url \"http://beta.quicklisp.org/asdf/3.2.1/asdf.lisp\"\n  :size 643253\n  :md5 \"1765635a8b5c545b586cb33c2dcdec0d\"\n  :sha256\n  \"51912f3f7c2c62c204f515d97346d56011528399bbf0a76a123318343ebd8bf0\"))\n"
  },
  {
    "path": "quicklisp/dists/quicklisp/distinfo.txt",
    "content": "name: quicklisp\nversion: 2026-01-01\nsystem-index-url: http://beta.quicklisp.org/dist/quicklisp/2026-01-01/systems.txt\nrelease-index-url: http://beta.quicklisp.org/dist/quicklisp/2026-01-01/releases.txt\narchive-base-url: http://beta.quicklisp.org/\ncanonical-distinfo-url: http://beta.quicklisp.org/dist/quicklisp/2026-01-01/distinfo.txt\ndistinfo-subscription-url: http://beta.quicklisp.org/dist/quicklisp.txt\n"
  },
  {
    "path": "quicklisp/dists/quicklisp/enabled.txt",
    "content": ""
  },
  {
    "path": "quicklisp/dists/quicklisp/preference.txt",
    "content": "3828362824"
  },
  {
    "path": "quicklisp/log4slime-setup.el",
    "content": ";; load Log4slime support\n\n(add-to-list 'load-path \"~/quicklisp/dists/quicklisp/software/log4cl-20200925-git/elisp/\")\n(require 'log4slime)\n"
  },
  {
    "path": "quicklisp/quicklisp/bundle-template.lisp",
    "content": "(cl:in-package #:cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (require \"asdf\")\n  (unless (find-package '#:asdf)\n    (error \"ASDF could not be required\")))\n\n(let ((indicator '#:ql-bundle-v1)\n      (searcher-name '#:ql-bundle-searcher)\n      (base (make-pathname :name nil :type nil\n                           :defaults #. (or *compile-file-truename*\n                                            *load-truename*))))\n  (labels ((file-lines (file)\n             (with-open-file (stream file)\n               (loop for line = (read-line stream nil)\n                     while line\n                     collect line)))\n           (relative (pathname)\n             (merge-pathnames pathname base))\n           (pathname-timestamp (pathname)\n             #+clisp\n             (nth-value 2 (ext:probe-pathname pathname))\n             #-clisp\n             (file-write-date pathname))\n           (system-table (table pathnames)\n             (dolist (pathname pathnames table)\n               (setf (gethash (pathname-name pathname) table)\n                     (relative pathname))))\n\n           (initialize-bundled-systems-table (table data-source)\n             (system-table table\n                           (mapcar (lambda (line)\n                                     (merge-pathnames line data-source))\n                                   (file-lines data-source))))\n\n           (local-projects-system-pathnames (data-source)\n             (let ((files (directory (merge-pathnames \"**/*.asd\"\n                                                      data-source))))\n               (stable-sort (sort files #'string< :key #'namestring)\n                            #'<\n                            :key (lambda (file)\n                                   (length (namestring file))))))\n           (initialize-local-projects-table (table data-source)\n             (system-table table (local-projects-system-pathnames data-source)))\n\n           (make-table (&key data-source init-function)\n             (let ((table (make-hash-table :test 'equalp)))\n               (setf (gethash \"/data-source\" table)\n                     data-source\n                     (gethash \"/timestamp\" table)\n                     (pathname-timestamp data-source)\n                     (gethash \"/init\" table)\n                     init-function)\n               table))\n\n           (tcall (table key &rest args)\n             (let ((fun (gethash key table)))\n               (unless (and fun (functionp fun))\n                 (error \"Unknown function key ~S\" key))\n               (apply fun args)))\n           (created-timestamp (table)\n             (gethash \"/timestamp\" table))\n           (data-source-timestamp (table)\n             (pathname-timestamp (data-source table)))\n           (data-source (table)\n             (gethash \"/data-source\" table))\n\n           (stalep (table)\n             ;; FIXME: Handle newly missing data sources?\n             (< (created-timestamp table)\n                (data-source-timestamp table)))\n           (meta-key-p (key)\n             (and (stringp key)\n                  (< 0 (length key))\n                  (char= (char key 0) #\\/)))\n           (clear (table)\n             ;; Don't clear \"/foo\" keys\n             (maphash (lambda (key value)\n                        (declare (ignore value))\n                        (unless (meta-key-p key)\n                          (remhash key table)))\n                      table))\n           (initialize (table)\n             (tcall table \"/init\" table (data-source table))\n             (setf (gethash \"/timestamp\" table)\n                   (pathname-timestamp (data-source table)))\n             table)\n           (update (table)\n             (clear table)\n             (initialize table))\n           (lookup (system-name table)\n             (when (stalep table)\n               (update table))\n             (values (gethash system-name table)))\n\n           (search-function (system-name)\n             (let ((tables (get searcher-name indicator)))\n               (dolist (table tables)\n                 (let* ((result (lookup system-name table))\n                        (probed (and result (probe-file result))))\n                   (when probed\n                     (return probed))))))\n\n           (make-bundled-systems-table ()\n             (initialize\n              (make-table :data-source (relative \"system-index.txt\")\n                          :init-function #'initialize-bundled-systems-table)))\n           (make-bundled-local-projects-systems-table ()\n             (let ((data-source (relative \"bundled-local-projects/system-index.txt\")))\n               (when (probe-file data-source)\n                 (initialize\n                  (make-table :data-source data-source\n                              :init-function #'initialize-bundled-systems-table)))))\n           (make-local-projects-table ()\n             (initialize\n              (make-table :data-source (relative \"local-projects/\")\n                          :init-function #'initialize-local-projects-table)))\n\n           (=matching-data-sources (tables)\n             (let ((data-sources (mapcar #'data-source tables)))\n               (lambda (table)\n                 (member (data-source table) data-sources\n                         :test #'equalp))))\n\n           (check-for-existing-searcher (searchers)\n             (block done\n               (dolist (searcher searchers)\n                 (when (symbolp searcher)\n                   (let ((plist (symbol-plist searcher)))\n                     (loop for key in plist by #'cddr\n                           when\n                           (and (symbolp key) (string= key indicator))\n                           do\n                           (setf indicator key)\n                           (setf searcher-name searcher)\n                           (return-from done t)))))))\n\n           (clear-asdf (table)\n             (maphash (lambda (system-name pathname)\n                        (declare (ignore pathname))\n                        (asdf:clear-system system-name))\n                      table)))\n\n    (let ((existing (check-for-existing-searcher\n                     asdf:*system-definition-search-functions*)))\n      (let* ((local (make-local-projects-table))\n             (bundled-local-projects\n              (make-bundled-local-projects-systems-table))\n             (bundled (make-bundled-systems-table))\n             (new-tables (remove nil (list local\n                                           bundled-local-projects\n                                           bundled)))\n             (existing-tables (get searcher-name indicator))\n             (filter (=matching-data-sources new-tables)))\n        (setf (get searcher-name indicator)\n              (append new-tables (delete-if filter existing-tables)))\n        (map nil #'clear-asdf new-tables))\n      (unless existing\n        (setf (symbol-function searcher-name) #'search-function)\n        (push searcher-name asdf:*system-definition-search-functions*)))\n    t))\n"
  },
  {
    "path": "quicklisp/quicklisp/bundle.lisp",
    "content": ";;;; bundle.lisp\n\n(in-package #:ql-bundle)\n\n;;; Bundling is taking a set of Quicklisp-provided systems and\n;;; creating a directory structure and metadata in which those systems\n;;; can be loaded without involving Quicklisp.\n;;;\n;;; This works for systems provided directly Quicklisp, or systems in\n;;; the Quicklisp local-projects directories (if\n;;; :include-local-projects is specified).\n\n(defgeneric find-system (system bundle))\n(defgeneric add-system (system bundle))\n(defgeneric ensure-system (system bundle))\n\n(defgeneric find-release (relase bundle))\n(defgeneric add-release (release bundle))\n(defgeneric ensure-release (release bundle))\n\n(defgeneric write-loader-script (bundle stream))\n(defgeneric write-system-index (bundle stream))\n\n(defgeneric unpack-release (release target))\n(defgeneric unpack-releases (bundle target))\n\n(defgeneric write-bundle (bundle target))\n\n(defvar *ignored-systems*\n  (list \"asdf\")\n  \"Systems that might appear in depends-on lists in Quicklisp, but\n  which can't be bundled.\")\n\n(defvar *bundle-progress-output*\n  (make-synonym-stream '*trace-output*)\n  \"Informative output related to creating the bundle is sent to this\n  stream.\")\n\n;;; Implementation\n\n;;; Conditions\n\n(define-condition bundle-error (error) ())\n\n(define-condition object-not-found (bundle-error)\n  ((name\n    :initarg :name\n    :reader object-not-found-name)\n   (type\n    :initarg :type\n    :reader object-not-found-type))\n  (:report\n   (lambda (condition stream)\n     (format stream \"~A ~S not found\"\n             (object-not-found-type condition)\n             (object-not-found-name condition))))\n  (:default-initargs\n   :type \"Object\"))\n\n(define-condition system-not-found (object-not-found)\n  ((name\n    :reader system-not-found-system))\n  (:default-initargs\n   :type \"System\"))\n\n(define-condition release-not-found (object-not-found)\n  ()\n  (:default-initargs\n   :type \"Release\"))\n\n(define-condition bundle-directory-exists (bundle-error)\n  ((directory\n    :initarg :directory\n    :reader bundle-directory-exists-directory))\n  (:report\n   (lambda (condition stream)\n     (format stream \"Bundle directory ~A already exists\"\n             (bundle-directory-exists-directory condition)))))\n\n\n(defun iso8601-time-stamp (&optional (time (get-universal-time)))\n  (multiple-value-bind (second minute hour day month year)\n      (decode-universal-time time 0)\n     (format nil \"~4,'0D-~2,'0D-~2,'0DT~\n                  ~2,'0D:~2,'0D:~2,'0DZ\"\n             year month day\n             hour minute second)))\n\n\n(defclass bundle ()\n  ((requested-systems\n    :initarg :requested-systems\n    :reader requested-systems\n    :documentation \"Names of the systems requested directly for\n    bundling.\")\n   (creation-time\n    :initarg :creation-time\n    :reader creation-time)\n   (release-table\n    :initarg :release-table\n    :reader release-table)\n   (system-table\n    :initarg :system-table\n    :reader system-table))\n  (:default-initargs\n   :requested-systems nil\n   :creation-time (iso8601-time-stamp)\n   :release-table (make-hash-table :test 'equalp)\n   :system-table (make-hash-table :test 'equalp)))\n\n(defmethod print-object ((bundle bundle) stream)\n  (print-unreadable-object (bundle stream :type t)\n    (format stream \"~D release~:P, ~D system~:P\"\n            (hash-table-count (release-table bundle))\n            (hash-table-count (system-table bundle)))))\n\n(defmethod provided-releases ((bundle bundle))\n  (let ((releases '()))\n    (maphash (lambda (name release)\n               (declare (ignore name))\n               (push release releases))\n             (release-table bundle))\n    (sort releases 'string< :key 'name)))\n\n(defmethod provided-systems ((bundle bundle))\n  (sort (mapcan #'provided-systems (provided-releases bundle))\n        'string<\n        :key 'name))\n\n(defmethod find-system (name (bundle bundle))\n  (values (gethash name (system-table bundle))))\n\n(defmethod add-system (name (bundle bundle))\n  (let ((system (ql-dist:find-system name)))\n    (unless system\n      (error 'system-not-found\n             :name name))\n    (ensure-release (name (release system)) bundle)\n    system))\n\n(defmethod ensure-system (name (bundle bundle))\n  (or (find-system name bundle)\n      (add-system name bundle)))\n\n(defmethod find-release (name (bundle bundle))\n  (values (gethash name (release-table bundle))))\n\n(defmethod add-release (name (bundle bundle))\n  (let ((release (ql-dist:find-release name)))\n    (unless release\n      (error 'release-not-found\n             :name name))\n    (setf (gethash (name release) (release-table bundle)) release)\n    (let ((system-table (system-table bundle)))\n      (dolist (system (provided-systems release))\n        (setf (gethash (name system) system-table) system)))\n    release))\n\n(defmethod ensure-release (name (bundle bundle))\n  (or (find-release name bundle)\n      (add-release name bundle)))\n\n\n(defun add-systems-recursively (names bundle)\n  (with-consistent-dists\n    (labels ((add-one (name)\n               (unless (member name *ignored-systems* :test 'equalp)\n                 (let ((system\n                        (restart-case\n                            (ensure-system name bundle)\n                          (omit ()\n                            :report \"Ignore this system and omit it from the bundle.\"))))\n                   (when system\n                     (dolist (required-system-name (required-systems system))\n                       (add-one required-system-name)))))))\n      (map nil #'add-one names)))\n  bundle)\n\n\n(defmethod unpack-release (release target)\n  (let ((*default-pathname-defaults* (truename\n                                      (ensure-directories-exist target)))\n        (archive (ensure-local-archive-file release))\n        (temp-tar (ensure-directories-exist\n                   (ql-setup:qmerge \"tmp/bundle.tar\"))))\n    (ql-gunzipper:gunzip archive temp-tar)\n    (ql-minitar:unpack-tarball temp-tar :directory \"software/\")\n    (delete-file temp-tar)\n    release))\n\n(defmethod unpack-releases ((bundle bundle) target)\n  (dolist (release (provided-releases bundle))\n    (unpack-release release target))\n  bundle)\n\n(defmethod write-system-index ((bundle bundle) stream)\n  (dolist (release (provided-releases bundle))\n    ;; Working with strings, here, intentionally not with pathnames\n    (let ((prefix (concatenate 'string \"software/\" (prefix release))))\n      (dolist (system-file (system-files release))\n        (format stream \"~A/~A~%\" prefix system-file)))))\n\n(defmethod write-loader-script ((bundle bundle) stream)\n  (let ((template-lines\n         (load-time-value\n          (with-open-file (stream #. (merge-pathnames \"bundle-template\"\n                                                      (or *compile-file-truename*\n                                                          *load-truename*)))\n            (loop for line = (read-line stream nil)\n                  while line collect line)))))\n    (dolist (line template-lines)\n      (write-line line stream))))\n\n(defun coerce-to-directory (pathname)\n  ;; Cribbed from quicklisp-bootstrap/quicklisp.lisp\n  (let ((name (file-namestring pathname)))\n    (if (or (null name)\n            (equal name \"\"))\n        pathname\n        (make-pathname :defaults pathname\n                       :name nil\n                       :type nil\n                       :directory (append (pathname-directory pathname)\n                                          (list name))))))\n\n(defun bundle-metadata-plist (bundle)\n  (list :creation-time (creation-time bundle)\n        :requested-systems (requested-systems bundle)\n        :lisp-info (list :machine-instance (machine-instance)\n                         :machine-type (machine-type)\n                         :machine-version (machine-version)\n                         :lisp-implementation-type (lisp-implementation-type)\n                         :lisp-implementation-version (lisp-implementation-version))\n        :quicklisp-info (list :home (namestring ql:*quicklisp-home*)\n                              :local-project-directories\n                              (mapcar 'namestring ql:*local-project-directories*)\n                              :dists\n                              (loop for dist in (enabled-dists)\n                                    collect (list :name (name dist)\n                                                  :dist-url\n                                                  (canonical-distinfo-url dist)\n                                                  :version (version dist))))))\n\n(defmethod write-bundle ((bundle bundle) target)\n  (unpack-releases bundle target)\n  (let ((index-file (merge-pathnames \"system-index.txt\" target))\n        (loader-file (merge-pathnames \"bundle.lisp\" target))\n        (local-projects (merge-pathnames \"local-projects/\" target))\n        (metadata-file (merge-pathnames \"bundle-info.sexp\" target)))\n    (ensure-directories-exist local-projects)\n    (with-open-file (stream index-file :direction :output\n                            :if-exists :supersede)\n      (write-system-index bundle stream))\n    (with-open-file (stream loader-file :direction :output\n                            :if-exists :supersede)\n      (write-loader-script bundle stream))\n    (with-open-file (stream metadata-file :direction :output\n                            :if-exists :supersede)\n      (with-standard-io-syntax\n        (let ((*print-pretty* t))\n          (prin1 (bundle-metadata-plist bundle) stream)\n          (terpri stream))))\n    (probe-file loader-file)))\n\n\n(defun copy-file (from-file to-file)\n  (with-open-file (from-stream from-file :element-type '(unsigned-byte 8)\n                               :if-does-not-exist nil)\n    (when from-stream\n      (let ((buffer (make-array 10000 :element-type '(unsigned-byte 8))))\n        (with-open-file (to-stream to-file\n                                   :direction :output\n                                   :if-exists :supersede\n                                   :element-type '(unsigned-byte 8))\n          (loop\n            (let ((end-index (read-sequence buffer from-stream)))\n              (when (zerop end-index)\n                (return to-file))\n              (write-sequence buffer to-stream :end end-index))))))))\n\n(defun copy-directory-tree (from-directory to-directory)\n  ;; Use the truename here to ensure that relative pathnames match up\n  ;; properly. For example, on SBCL, \"~/foo/bar/\" entries are not\n  ;; relative to \"/home/baz/foo/bar/\" entries.\n  (setf from-directory (truename from-directory))\n  (map-directory-tree\n   from-directory\n   (lambda (from-pathname)\n     (when (probe-file from-pathname)\n       (let* ((relative (enough-namestring from-pathname from-directory))\n              (relative-directory (pathname-directory relative))\n              (to-pathname (merge-pathnames relative to-directory)))\n         (unless (or (null relative-directory)\n                     (eql (first relative-directory)\n                          :relative))\n           (error \"Expected relative pathname to copy from ~A ~\n                   - bad symlink? - ~S\"\n                  from-pathname\n                  relative))\n         (ensure-directories-exist to-pathname)\n         (copy-file from-pathname to-pathname))))))\n\n(defun copy-local-projects-directories (local-projects-directories\n                                        to-directory)\n  \"Copy the local-projects directories to TO-DIRECTORY. Each one gets\n  a distinct subdirectory.\"\n  (loop for prefix from 0\n        for prefix-directory = (make-pathname :directory\n                                              (list :relative\n                                                    (format nil \"~4,'0X\" prefix)))\n        for from-directory in local-projects-directories\n        for real-to-directory = (merge-pathnames prefix-directory to-directory)\n        do\n        (format *bundle-progress-output*\n                \"~&; Copying ~A to bundle...\" from-directory )\n        (force-output *bundle-progress-output*)\n        (ensure-directories-exist real-to-directory)\n        (copy-directory-tree from-directory real-to-directory)\n        (format *bundle-progress-output* \"done.~%\")\n        (force-output *bundle-progress-output*)))\n\n\n(defun ql:bundle-systems (system-names\n                          &key include-local-projects to (overwrite t))\n  \"In the directory TO, construct a self-contained bundle of libraries\nbased on SYSTEM-NAMES. For each system named, and its recursive\nrequired systems, unpack its release archive in TO/software/, and\nwrite a system index, compatible with the output of\nQL:WRITE-ASDF-MANIFEST-FILE, to TO/system-index.txt. Write a loader\nscript to TO/bundle.lisp that, when loaded via CL:LOAD, configures\nASDF to load systems from the bundle before any other system.\n\nSYSTEM-NAMES must name systems provided directly by Quicklisp.\n\nIf INCLUDE-LOCAL-PROJECTS is true, each directory in\nQL:*LOCAL-PROJECT-DIRECTORIES* is copied into the bundle and loaded\nbefore any of the other bundled systems.\"\n  (unless to\n    (error \"TO argument must be provided\"))\n  (let* ((bundle (make-instance 'bundle\n                                :requested-systems system-names))\n         (to (coerce-to-directory to))\n         (software (merge-pathnames \"software/\" to)))\n    (when (and (probe-directory to)\n               (not overwrite))\n      (cerror \"Overwrite it\"\n              'bundle-directory-exists\n              :directory to))\n    (when (probe-directory software)\n      (delete-directory-tree software))\n    (add-systems-recursively system-names bundle)\n    (let ((bundled-local-projects (merge-pathnames \"bundled-local-projects/\"\n                                                   to)))\n      (when include-local-projects\n        (when (probe-directory bundled-local-projects)\n          (delete-directory-tree bundled-local-projects))\n        (copy-local-projects-directories ql:*local-project-directories*\n                                         bundled-local-projects)\n        (ensure-directories-exist bundled-local-projects)\n        (ql::make-system-index bundled-local-projects)))\n    (values (write-bundle bundle to)\n            bundle)))\n"
  },
  {
    "path": "quicklisp/quicklisp/cdb.lisp",
    "content": ";;;; cdb.lisp\n\n(in-package #:ql-cdb)\n\n(defconstant +initial-hash-value+ 5381)\n\n(defun cdb-hash (octets)\n  \"http://cr.yp.to/cdb/cdb.txt\"\n  (declare (type (simple-array (unsigned-byte 8) (*)) octets)\n           (optimize speed))\n  (let ((h +initial-hash-value+))\n    (declare (type (unsigned-byte 32) h))\n    (dotimes (i (length octets) h)\n      (let ((c (aref octets i)))\n        (setf h (logand #xFFFFFFFF (+ h (ash h 5))))\n        (setf h (logxor h c))))))\n\n(defun make-growable-vector (&key\n                             (size 10) (element-type t))\n  (make-array size :fill-pointer 0 :adjustable t :element-type element-type))\n\n(defun make-octet-vector (size)\n  (make-array size :element-type '(unsigned-byte 8)))\n\n(defun encode-string (string)\n  \"Do a bare-bones ASCII encoding of STRING.\"\n  (map-into (make-octet-vector (length string))\n            'char-code\n            string))\n\n(defun decode-octets (octets)\n  \"Do a bare-bones ASCII decoding of OCTETS.\"\n  (map-into (make-string (length octets))\n            'code-char\n            octets))\n\n(defun read-cdb-u32 (stream)\n  (logand #xFFFFFFFF\n          (logior (ash (read-byte stream) 0)\n                  (ash (read-byte stream) 8)\n                  (ash (read-byte stream) 16)\n                  (ash (read-byte stream) 24))))\n\n(defun lookup-record-at (position key stream)\n  (file-position stream position)\n  (let ((key-size (read-cdb-u32 stream))\n        (value-size (read-cdb-u32 stream)))\n    (when (= key-size (length key))\n      (let ((test-key (make-octet-vector key-size)))\n        (when (/= key-size (read-sequence test-key stream))\n          (error \"Could not read record key of size ~D from cdb stream\"\n                 key-size))\n        (unless (mismatch test-key key :test #'=)\n          (let ((value (make-octet-vector value-size)))\n            (if (= value-size (read-sequence value stream))\n                value\n                (error \"Could not read record value of size ~D from cdb stream\"\n                       value-size))))))))\n\n(defun table-slot-lookup (key hash table-position\n                          initial-slot slot-count stream)\n  (let ((slot initial-slot))\n    (loop\n      (file-position stream (+ table-position (* slot 8)))\n      (let ((test-hash (read-cdb-u32 stream))\n            (record-position (read-cdb-u32 stream)))\n        (when (zerop record-position)\n          (return))\n        (when (= hash test-hash)\n          (let ((value (lookup-record-at record-position key stream)))\n            (when value\n              (return value)))))\n      (setf slot (mod (1+ slot) slot-count)))))\n\n(defun stream-lookup (key stream)\n  (let* ((hash (cdb-hash key))\n         (pointer-index (logand #xFF hash)))\n    (file-position stream (* pointer-index 8))\n    (let ((table-position (read-cdb-u32 stream))\n          (slot-count (read-cdb-u32 stream)))\n      (when (plusp slot-count)\n        (let ((initial-slot (mod (ash hash -8) slot-count)))\n          (table-slot-lookup key hash\n                             table-position initial-slot slot-count stream))))))\n\n(defun %lookup (key cdb)\n  \"Return the value for KEY in CDB, or NIL if no matching key is\nfound. CDB should be a pathname or an open octet stream. The key\nshould be a vector of octets. The returned value will be a vector of\noctets.\"\n  (if (streamp cdb)\n      (stream-lookup key cdb)\n      (with-open-file (stream cdb :element-type '(unsigned-byte 8))\n        (stream-lookup key stream))))\n\n(defun lookup (key cdb)\n  \"Return the value for KEY in CDB, or NIL if no matching key is\nfound. CDB should be a pathname or an open octet stream. The key\nshould be an ASCII-encodable string. The returned value will be a\nstring.\"\n  (let ((value (%lookup (encode-string key) cdb)))\n    (when value\n      (decode-octets value))))\n\n(defun stream-map-cdb (function stream)\n  (labels ((map-one-slot (i)\n             (file-position stream (* i 8))\n             (let ((table-position (read-cdb-u32 stream))\n                   (slot-count (read-cdb-u32 stream)))\n               (when (plusp slot-count)\n                 (map-one-table table-position slot-count))))\n           (map-one-table (position count)\n             (dotimes (i count)\n               (file-position stream (+ position (* i 8)))\n               (let ((hash (read-cdb-u32 stream))\n                     (position (read-cdb-u32 stream)))\n                 (declare (ignore hash))\n                 (when (plusp position)\n                   (map-record position)))))\n           (map-record (position)\n             (file-position stream position)\n             (let* ((key-size (read-cdb-u32 stream))\n                    (value-size (read-cdb-u32 stream))\n                    (key (make-octet-vector key-size))\n                    (value (make-octet-vector value-size)))\n               (read-sequence key stream)\n               (read-sequence value stream)\n               (funcall function key value))))\n    (dotimes (i 256)\n      (map-one-slot i))))\n\n(defun %map-cdb (function cdb)\n  \"Call FUNCTION once with each key and value in CDB.\"\n  (if (streamp cdb)\n      (stream-map-cdb function cdb)\n      (with-open-file (stream cdb :element-type '(unsigned-byte 8))\n        (stream-map-cdb function stream))))\n\n(defun map-cdb (function cdb)\n  (%map-cdb (lambda (key value)\n              (funcall function\n                       (decode-octets key)\n                       (decode-octets value)))\n            cdb))\n\n\n;;; Writing CDB files\n\n(defun write-cdb-u32 (u32 stream)\n  \"Write an (unsigned-byte 32) value to STREAM in little-endian order.\"\n  (write-byte (ldb (byte 8 0) u32) stream)\n  (write-byte (ldb (byte 8 8) u32) stream)\n  (write-byte (ldb (byte 8 16) u32) stream)\n  (write-byte (ldb (byte 8 24) u32) stream))\n\n(defclass record-pointer ()\n  ((hash-value\n    :initarg :hash-value\n    :accessor hash-value\n    :documentation \"The hash value of the record key.\")\n   (record-position\n    :initarg :record-position\n    :accessor record-position\n    :documentation \"The file position at which the record is stored.\"))\n  (:default-initargs\n   :hash-value 0\n   :record-position 0)\n  (:documentation \"Every key/value record written to a CDB has a\n  corresponding record pointer, which tracks the key's hash value and\n  the record's position in the data file. When all records have been\n  written to the file, these record pointers are organized into hash\n  tables at the end of the cdb file.\"))\n\n(defmethod print-object ((record-pointer record-pointer) stream)\n  (print-unreadable-object (record-pointer stream :type t)\n    (format stream \"~8,'0X@~:D\"\n            (hash-value record-pointer)\n            (record-position record-pointer))))\n\n(defvar *empty-record-pointer* (make-instance 'record-pointer))\n\n\n(defclass hash-table-bucket ()\n  ((table-position\n    :initarg :table-position\n    :accessor table-position\n    :documentation \"The file position at which this table\n    is (eventually) slotted.\")\n   (entries\n    :initarg :entries\n    :accessor entries\n    :documentation \"A vector of record-pointers.\"))\n  (:default-initargs\n   :table-position 0\n   :entries (make-growable-vector))\n  (:documentation \"During construction of the CDB, record pointers are\n  accumulated into one of 256 hash table buckets, depending on the low\n  8 bits of the hash value of the key. At the end of record writing,\n  these buckets are used to write out hash table vectors at the end of\n  the file, and write pointers to the hash table vectors at the start\n  of the file.\"))\n\n(defgeneric entry-count (object)\n  (:method ((object hash-table-bucket))\n    (length (entries object))))\n\n(defgeneric slot-count (object)\n  (:method ((object hash-table-bucket))\n    (* (entry-count object) 2)))\n\n(defun bucket-hash-vector (bucket)\n  \"Create a hash vector for a bucket. A hash vector has 2x the entries\nof the bucket, and is initialized to an empty record pointer. The high\n24 bits of the hash value of a record pointer, mod the size of the\nvector, is used as a starting slot, and the vector is walked (wrapping\nat the end) to find the first free slot for positioning each record\npointer entry.\"\n  (let* ((size (slot-count bucket))\n         (vector (make-array size :initial-element nil)))\n    (flet ((slot (record)\n             (let ((index (mod (ash (hash-value record) -8) size)))\n               (loop\n                 (unless (aref vector index)\n                   (return (setf (aref vector index) record)))\n                 (setf index (mod (1+ index) size))))))\n      (map nil #'slot (entries bucket)))\n    (nsubstitute *empty-record-pointer* nil vector)))\n\n(defmethod print-object ((bucket hash-table-bucket) stream)\n  (print-unreadable-object (bucket stream :type t)\n    (format stream \"~D entr~:@P\" (entry-count bucket))))\n\n\n(defclass cdb-writer ()\n  ((buckets\n    :initarg :buckets\n    :accessor buckets)\n   (end-of-records-position\n    :initarg :end-of-records-position\n    :accessor end-of-records-position)\n   (output\n    :initarg :output\n    :accessor output))\n  (:default-initargs\n   :end-of-records-position 2048\n   :buckets (map-into (make-array 256)\n                      (lambda () (make-instance 'hash-table-bucket)))))\n\n\n(defun add-record (key value cdb-writer)\n  \"Add KEY and VALUE to a cdb file. KEY and VALUE should both\nbe (unsigned-byte 8) vectors.\"\n  (let* ((output (output cdb-writer))\n         (hash-value (cdb-hash key))\n         (bucket-index (logand #xFF hash-value))\n         (bucket (aref (buckets cdb-writer) bucket-index))\n         (record-position (file-position output))\n         (record-pointer (make-instance 'record-pointer\n                                        :record-position record-position\n                                        :hash-value hash-value)))\n    (vector-push-extend record-pointer (entries bucket))\n    (write-cdb-u32 (length key) output)\n    (write-cdb-u32 (length value) output)\n    (write-sequence key output)\n    (write-sequence value output)\n    (force-output output)\n    (incf (end-of-records-position cdb-writer)\n          (+ 8 (length key) (length value)))))\n\n(defun write-bucket-hash-table (bucket stream)\n  \"Write BUCKET's hash table vector to STREAM.\"\n  (map nil\n       (lambda (pointer)\n         (write-cdb-u32 (hash-value pointer) stream)\n         (write-cdb-u32 (record-position pointer) stream))\n       (bucket-hash-vector bucket)))\n\n(defun write-hash-tables (cdb-writer)\n  \"Write the traililng hash tables to the end of the cdb\nfile. Initializes the position of the buckets in the process.\"\n  (let ((stream (output cdb-writer)))\n    (map nil\n         (lambda (bucket)\n           (setf (table-position bucket) (file-position stream))\n           (write-bucket-hash-table bucket stream))\n         (buckets cdb-writer))))\n\n(defun write-pointers (cdb-writer)\n  \"Write the leading hash table pointers to the beginning of the cdb\nfile. Must be called after WRITE-HASH-TABLES, or the positions won't\nbe available.\"\n  (let ((stream (output cdb-writer)))\n    (file-position stream :start)\n    (map nil\n         (lambda (bucket)\n           (let ((position (table-position bucket))\n                 (count (slot-count bucket)))\n             (when (zerop position)\n               (error \"Table positions not initialized correctly\"))\n             (write-cdb-u32 position stream)\n             (write-cdb-u32 count stream)))\n         (buckets cdb-writer))))\n\n(defun finish-cdb-writer (cdb-writer)\n  \"Write the trailing hash tables and leading table pointers to the\ncdb file.\"\n  (write-hash-tables cdb-writer)\n  (write-pointers cdb-writer)\n  (force-output (output cdb-writer)))\n\n\n(defvar *pointer-padding* (make-array 2048 :element-type '( unsigned-byte 8)))\n\n(defun call-with-output-to-cdb (cdb-pathname temp-pathname fun)\n  \"Call FUN with one argument, a CDB-WRITER instance to which records\ncan be added with ADD-RECORD.\"\n  (with-open-file (stream temp-pathname\n                          :direction :output\n                          :element-type '(unsigned-byte 8)\n                          :if-exists :supersede)\n    (let ((cdb (make-instance 'cdb-writer :output stream)))\n      (write-sequence *pointer-padding* stream)\n      (funcall fun cdb)\n      (finish-cdb-writer cdb)))\n  (values (rename-file temp-pathname cdb-pathname)))\n\n(defmacro with-output-to-cdb ((cdb file temp-file) &body body)\n  \"Evaluate BODY with CDB bound to a CDB-WRITER object. The CDB in\nprogress is written to TEMP-FILE, and then when the CDB is\nsuccessfully written, TEMP-FILE is renamed to FILE. For atomic\noperation, FILE and TEMP-FILE must be on the same filesystem.\"\n  `(call-with-output-to-cdb ,file ,temp-file\n                            (lambda (,cdb)\n                              ,@body)))\n\n\n;;; Index file (systems.txt, releases.txt) conversion\n\n(defun convert-index-file (index-file\n                           &key (cdb-file (make-pathname :type \"cdb\"\n                                                         :defaults index-file))\n                             (index 0))\n  (with-open-file (stream index-file)\n    (let ((header (read-line stream)))\n      (unless (and (plusp (length header))\n                   (char= (char header 0) #\\#))\n        (error \"Bad header line in ~A -- ~S\"\n               index-file header)))\n    (with-output-to-cdb (cdb cdb-file (make-pathname :type \"cdb-tmp\"\n                                                     :defaults cdb-file))\n      (loop for line = (read-line stream nil)\n            for words = (and line (ql-util:split-spaces line))\n            while line do\n            (add-record (encode-string (elt words index))\n                        (encode-string line)\n                        cdb)))))\n"
  },
  {
    "path": "quicklisp/quicklisp/client-info.lisp",
    "content": ";;;; client-info.lisp\n\n(in-package #:quicklisp-client)\n\n(defparameter *client-base-url* \"http://beta.quicklisp.org/\")\n\n(defgeneric info-equal (info1 info2)\n  (:documentation \"Return TRUE if INFO1 and INFO2 are 'equal' in some\n  important sense.\"))\n\n;;; Information for checking the validity of files fetched for\n;;; installing/updating the client code.\n\n(defclass client-file-info ()\n  ((plist-key\n    :initarg :plist-key\n    :reader plist-key)\n   (file-url\n    :initarg :url\n    :reader file-url)\n   (name\n    :reader name\n    :initarg :name)\n   (size\n    :initarg :size\n    :reader size)\n   (md5\n    :reader md5\n    :initarg :md5)\n   (sha256\n    :reader sha256\n    :initarg :sha256)\n   (plist\n    :reader plist\n    :initarg :plist)))\n\n(defmethod print-object ((info client-file-info) stream)\n  (print-unreadable-object (info stream :type t)\n    (format stream \"~S ~D ~S\"\n            (name info)\n            (size info)\n            (md5 info))))\n\n(defmethod info-equal ((info1 client-file-info) (info2 client-file-info))\n  (and (eql (size info1) (size info2))\n       (equal (name info1) (name info2))\n       (equal (md5 info1) (md5 info2))))\n\n(defclass asdf-file-info (client-file-info)\n  ()\n  (:default-initargs\n   :plist-key :asdf\n   :name \"asdf.lisp\"))\n\n(defclass setup-file-info (client-file-info)\n  ()\n  (:default-initargs\n   :plist-key :setup\n   :name \"setup.lisp\"))\n\n(defclass client-tar-file-info (client-file-info)\n  ()\n  (:default-initargs\n   :plist-key :client-tar\n   :name \"quicklisp.tar\"))\n\n(define-condition invalid-client-file (error)\n  ((file\n    :initarg :file\n    :reader invalid-client-file-file)))\n\n(define-condition badly-sized-client-file (invalid-client-file)\n  ((expected-size\n    :initarg :expected-size\n    :reader badly-sized-client-file-expected-size)\n   (actual-size\n    :initarg :actual-size\n    :reader badly-sized-client-file-actual-size))\n  (:report (lambda (condition stream)\n             (format stream \"Unexpected file size for ~A ~\n                             - expected ~A but got ~A\"\n                     (invalid-client-file-file condition)\n                     (badly-sized-client-file-expected-size condition)\n                     (badly-sized-client-file-actual-size condition)))))\n\n(defun check-client-file-size (file expected-size)\n  (let ((actual-size (file-size file)))\n    (unless (eql expected-size actual-size)\n      (error 'badly-sized-client-file\n             :file file\n             :expected-size expected-size\n             :actual-size actual-size))))\n\n;;; TODO: check cryptographic digests too.\n\n(defgeneric check-client-file (file client-file-info)\n  (:documentation\n   \"Signal an INVALID-CLIENT-FILE error if FILE does not match the\n   metadata in CLIENT-FILE-INFO.\")\n  (:method (file client-file-info)\n    (check-client-file-size file (size client-file-info))\n    client-file-info))\n\n;;; Structuring and loading information about the Quicklisp client\n;;; code\n\n(defclass client-info ()\n  ((setup-info\n    :reader setup-info\n    :initarg :setup-info)\n   (asdf-info\n    :reader asdf-info\n    :initarg :asdf-info)\n   (client-tar-info\n    :reader client-tar-info\n    :initarg :client-tar-info)\n   (canonical-client-info-url\n    :reader canonical-client-info-url\n    :initarg :canonical-client-info-url)\n   (version\n    :reader version\n    :initarg :version)\n   (subscription-url\n    :reader subscription-url\n    :initarg :subscription-url)\n   (plist\n    :reader plist\n    :initarg :plist)\n   (source-file\n    :reader source-file\n    :initarg :source-file)))\n\n(defmethod print-object ((client-info client-info) stream)\n  (print-unreadable-object (client-info stream :type t)\n    (prin1 (version client-info) stream)))\n\n(defmethod available-versions-url ((info client-info))\n  (make-versions-url (subscription-url info)))\n\n(defgeneric extract-client-file-info (file-info-class plist)\n  (:method (file-info-class plist)\n    (let* ((instance (make-instance file-info-class))\n           (key (plist-key instance))\n           (file-info-plist (getf plist key)))\n      (unless file-info-plist\n        (error \"Missing client-info data for ~S\" key))\n      (destructuring-bind (&key url size md5 sha256 &allow-other-keys)\n          file-info-plist\n        (unless (and url size md5 sha256)\n          (error \"Missing client-info data for ~S\" key))\n        (reinitialize-instance instance\n                               :plist file-info-plist\n                               :url url\n                               :size size\n                               :md5 md5\n                               :sha256 sha256)))))\n\n(defun format-client-url (path &rest format-arguments)\n  (if format-arguments\n      (format nil \"~A~{~}\" *client-base-url* path format-arguments)\n      (format nil \"~A~A\" *client-base-url* path)))\n\n(defun client-info-url-from-version (version)\n  (format-client-url \"client/~A/client-info.sexp\" version))\n\n(define-condition invalid-client-info (error)\n  ((plist\n    :initarg plist\n    :reader invalid-client-info-plist)))\n\n(defun load-client-info (file)\n  (let ((plist (safely-read-file file)))\n    (destructuring-bind (&key subscription-url\n                              version\n                              canonical-client-info-url\n                              &allow-other-keys)\n        plist\n      (make-instance 'client-info\n                     :setup-info (extract-client-file-info 'setup-file-info\n                                                           plist)\n                     :asdf-info (extract-client-file-info 'asdf-file-info\n                                                          plist)\n                     :client-tar-info\n                     (extract-client-file-info 'client-tar-file-info\n                                               plist)\n                     :canonical-client-info-url canonical-client-info-url\n                     :version version\n                     :subscription-url subscription-url\n                     :plist plist\n                     :source-file (probe-file file)))))\n\n(defun mock-client-info ()\n  (flet ((mock-client-file-info (class)\n           (make-instance class\n                          :size 0\n                          :url \"\"\n                          :md5 \"\"\n                          :sha256 \"\"\n                          :plist nil)))\n    (make-instance 'client-info\n                   :version ql-info:*version*\n                   :subscription-url\n                   (format-client-url \"client/quicklisp.sexp\")\n                   :setup-info (mock-client-file-info 'setup-file-info)\n                   :asdf-info (mock-client-file-info 'asdf-file-info)\n                   :client-tar-info (mock-client-file-info\n                                     'client-tar-file-info))))\n\n(defun fetch-client-info (url)\n  (let ((info-file (qmerge \"tmp/client-info.sexp\")))\n    (delete-file-if-exists info-file)\n    (fetch url info-file :quietly t)\n    (handler-case\n        (load-client-info info-file)\n      ;; FIXME: So many other things could go wrong here; I think it\n      ;; would be nice to catch and report them clearly as bogus URLs\n      (invalid-client-info ()\n        (error \"Invalid client info URL -- ~A\" url)))))\n\n(defun local-client-info ()\n  (let ((info-file (qmerge \"client-info.sexp\")))\n    (if (probe-file info-file)\n        (load-client-info info-file)\n        (progn\n          (warn \"Missing client-info.sexp, using mock info\")\n          (mock-client-info)))))\n\n(defun newest-client-info (&optional (info (local-client-info)))\n  (let ((latest (subscription-url info)))\n    (when latest\n      (fetch-client-info latest))))\n\n(defun client-version-lessp (client-info-1 client-info-2)\n  (string-lessp (version client-info-1)\n                (version client-info-2)))\n\n(defun client-version ()\n  \"Return the version for the current local client installation. May\nor may not be suitable for passing as the :VERSION argument to\nINSTALL-CLIENT, depending on if it's a standard Quicklisp-provided\nclient.\"\n  (version (local-client-info)))\n\n(defun client-url ()\n  \"Return an URL suitable for passing as the :URL argument to\nINSTALL-CLIENT for the current local client installation.\"\n  (canonical-client-info-url (local-client-info)))\n\n(defun available-client-versions ()\n  (let ((url (available-versions-url (local-client-info)))\n        (temp-file (qmerge \"tmp/client-versions.sexp\")))\n    (when url\n      (handler-case\n          (progn\n            (maybe-fetch-gzipped url temp-file)\n            (prog1\n                (with-open-file (stream temp-file)\n                  (safely-read stream))\n              (delete-file-if-exists temp-file)))\n        (unexpected-http-status (condition)\n          (unless (url-not-suitable-error-p condition)\n            (error condition)))))))\n"
  },
  {
    "path": "quicklisp/quicklisp/client-update.lisp",
    "content": ";;;; client-update.lisp\n\n(in-package #:quicklisp-client)\n\n(defun fetch-client-file-info (client-file-info output-file)\n  (maybe-fetch-gzipped (file-url client-file-info) output-file)\n  (check-client-file output-file client-file-info)\n  (probe-file output-file))\n\n(defun retirement-directory (base)\n  (let ((suffix 0))\n    (loop\n      (incf suffix)\n      (let* ((try (format nil \"~A-~D\" base suffix))\n             (dir (qmerge (make-pathname :directory\n                                         (list :relative \"retired\" try)))))\n        (unless (probe-directory dir)\n          (return dir))))))\n\n(defun retire (directory base)\n  (let ((retirement-home (qmerge \"retired/\"))\n        (from (truename directory)))\n    (ensure-directories-exist retirement-home)\n    (let* ((*default-pathname-defaults* retirement-home)\n           (to (retirement-directory base)))\n      (rename-directory from to)\n      to)))\n\n(defun client-update-scratch-directory (client-info)\n  (qmerge (make-pathname :directory\n                         (list :relative\n                               \"tmp\"\n                               \"client-update\"\n                               (version client-info)))))\n\n(defun %install-client (new-info local-info)\n  (let* ((work-directory (client-update-scratch-directory new-info))\n         (current-quicklisp-directory (qmerge \"quicklisp/\"))\n         (new-quicklisp-directory\n          (merge-pathnames \"quicklisp/\" work-directory))\n         (local-temp-tar (merge-pathnames \"quicklisp.tar\" work-directory))\n         (local-setup (merge-pathnames \"setup.lisp\" work-directory))\n         (local-asdf (merge-pathnames \"asdf.lisp\" work-directory))\n         (new-client-tar-p (not (info-equal (client-tar-info new-info)\n                                            (client-tar-info local-info))))\n         (new-setup-p (not (info-equal (setup-info new-info)\n                                       (setup-info local-info))))\n         (new-asdf-p (not (info-equal (asdf-info new-info)\n                                      (asdf-info local-info)))))\n    (ensure-directories-exist work-directory)\n    ;; Fetch and unpack quicklisp.tar if needed\n    (when new-client-tar-p\n      (fetch-client-file-info (client-tar-info new-info) local-temp-tar)\n      (unpack-tarball local-temp-tar :directory work-directory))\n    ;; Fetch setup.lisp if needed\n    (when new-setup-p\n      (fetch-client-file-info (setup-info new-info) local-setup))\n    ;; Fetch asdf.lisp if needed\n    (when new-asdf-p\n      (fetch-client-file-info (asdf-info new-info) local-asdf))\n    ;; Everything fetched, so move the old stuff away and move the new\n    ;; stuff in\n    (when new-client-tar-p\n      (retire (qmerge \"quicklisp/\")\n              (format nil \"quicklisp-~A\"\n                      (version local-info)))\n      (rename-directory new-quicklisp-directory current-quicklisp-directory))\n    (when new-setup-p\n      (replace-file local-setup (qmerge \"setup.lisp\")))\n    (when new-asdf-p\n      (replace-file local-asdf (qmerge \"asdf.lisp\")))\n    ;; But unconditionally move the new client-info into place\n    (replace-file (source-file new-info) (qmerge \"client-info.sexp\"))\n    new-info))\n\n(defun update-client (&key (prompt t))\n  (let* ((local-info (local-client-info))\n         (newest-info (newest-client-info local-info)))\n    (cond ((null newest-info)\n           (format t \"No client update available.~%\"))\n          ((client-version-lessp local-info newest-info)\n           (format t \"Updating client from version ~A to version ~A.~%\"\n                   (version local-info)\n                   (version newest-info))\n           (when (or (not prompt)\n                     (press-enter-to-continue))\n             (%install-client newest-info local-info)\n             (format t \"~&New Quicklisp client installed. ~\n                          It will take effect on restart.~%\")))\n          (t\n           (format t \"The most up-to-date client, version ~A, ~\n                      is already installed.~%\"\n                   (version local-info)))))\n  t)\n\n\n(defun install-client (&key url version)\n  (unless (or url version)\n    (error \"One of ~S or ~S is required\" :url :version))\n  (when (and url version)\n    (error \"Only one of ~S or ~S is allowed\" :url :version))\n  (when version\n    (setf url (client-info-url-from-version version)))\n  (let ((local-info (local-client-info))\n        (new-info (fetch-client-info url)))\n    (%install-client new-info local-info)))\n"
  },
  {
    "path": "quicklisp/quicklisp/client.lisp",
    "content": ";;;; client.lisp\n\n(in-package #:quicklisp-client)\n\n(defvar *quickload-verbose* nil\n  \"When NIL, show terse output when quickloading a system. Otherwise,\n  show normal compile and load output.\")\n\n(defvar *quickload-prompt* nil\n  \"When NIL, quickload systems without prompting for enter to\n  continue, otherwise proceed directly without user intervention.\")\n\n(defvar *quickload-explain* t)\n\n(define-condition system-not-quickloadable (error)\n  ((system\n    :initarg :system\n    :reader not-quickloadable-system)))\n\n(defun maybe-silence (silent stream)\n  (or (and silent (make-broadcast-stream)) stream))\n\n(defgeneric quickload (systems &key verbose silent prompt explain &allow-other-keys)\n  (:documentation\n   \"Load SYSTEMS the quicklisp way. SYSTEMS is a designator for a list\n   of things to be loaded.\")\n  (:method (systems &key\n            (prompt *quickload-prompt*)\n            (silent nil)\n            (verbose *quickload-verbose*) &allow-other-keys)\n    (let ((*standard-output* (maybe-silence silent *standard-output*))\n          (*trace-output*    (maybe-silence silent *trace-output*)))\n      (unless (listp systems)\n        (setf systems (list systems)))\n      (dolist (thing systems systems)\n        (flet ((ql ()\n                 (autoload-system-and-dependencies thing :prompt prompt)))\n          (tagbody :start\n             (restart-case (if verbose\n                               (ql)\n                               (call-with-quiet-compilation #'ql))\n               (register-local-projects ()\n                 :report \"Register local projects and try again.\"\n                 (register-local-projects)\n                 (go :start)))))))))\n\n(defgeneric quickfind (system &key prompt)\n  (:method (system &key prompt)\n    (let ((result))\n     (call-with-autoloading-system-and-dependencies\n      system\n      (lambda ()\n        (setf result (asdf:find-system system)))\n      :prompt prompt)\n      result)))\n\n(defmethod quickload :around (systems &key verbose prompt explain\n                                      &allow-other-keys)\n  (declare (ignorable systems verbose prompt explain))\n  (with-consistent-dists\n    (call-next-method)))\n\n(defun system-list ()\n  (provided-systems t))\n\n(defun update-dist (dist &key (prompt t))\n  (when (stringp dist)\n    (setf dist (find-dist dist)))\n  (let ((new (available-update dist)))\n    (cond (new\n           (show-update-report dist new)\n           (when (or (not prompt) (press-enter-to-continue))\n             (update-in-place dist new)))\n          ((not (subscribedp dist))\n           (format t \"~&You are not subscribed to ~S.\"\n                   (name dist)))\n          (t\n           (format t \"~&You already have the latest version of ~S: ~A.~%\"\n                   (name dist)\n                   (version dist))))))\n\n(defun update-all-dists (&key (prompt t))\n  (let ((dists (remove-if-not 'subscribedp (all-dists))))\n    (format t \"~&~D dist~:P to check.~%\" (length dists))\n    (dolist (old dists)\n      (with-simple-restart (skip \"Skip update of dist ~S\" (name old))\n        (update-dist old :prompt prompt)))))\n\n(defun available-dist-versions (name)\n  (available-versions (find-dist-or-lose name)))\n\n(defun help ()\n  \"For help with Quicklisp, see http://www.quicklisp.org/beta/\")\n\n(defun uninstall (system-name)\n  (let ((system (find-system system-name)))\n    (cond (system\n           (ql-dist:uninstall system))\n          (t\n           (warn \"Unknown system ~S\" system-name)\n           nil))))\n\n(defun uninstall-dist (name)\n  (let ((dist (find-dist name)))\n    (when dist\n      (ql-dist:uninstall dist))))\n\n(defun write-asdf-manifest-file (output-file &key (if-exists :rename-and-delete)\n                                               exclude-local-projects)\n  \"Write a list of system file pathnames to OUTPUT-FILE, one per line,\nin order of descending QL-DIST:PREFERENCE.\"\n  (when (or (eql output-file nil)\n            (eql output-file t))\n    (setf output-file (qmerge \"manifest.txt\")))\n  (with-open-file (stream output-file\n                          :direction :output\n                          :if-exists if-exists)\n    (unless exclude-local-projects\n      (register-local-projects)\n      (dolist (system-file (list-local-projects))\n        (let* ((enough (enough-namestring system-file output-file))\n               (native (native-namestring enough)))\n          (write-line native stream))))\n    (with-consistent-dists\n      (let ((systems (provided-systems t))\n            (already-seen (make-hash-table :test 'equal)))\n        (dolist (system (sort systems #'>\n                              :key #'preference))\n          ;; FIXME: find-asdf-system-file does another find-system\n          ;; behind the scenes. Bogus. Should be a better way to go\n          ;; from system object to system file.\n          (let* ((system-file (find-asdf-system-file (name system)))\n                 (enough (and system-file (enough-namestring system-file\n                                                             output-file)))\n                 (native (and enough (native-namestring enough))))\n            (when (and native (not (gethash native already-seen)))\n              (setf (gethash native already-seen) native)\n              (format stream \"~A~%\" native)))))))\n  (probe-file output-file))\n\n(defun where-is-system (name)\n  \"Return the pathname to the source directory of ASDF system with the\ngiven NAME, or NIL if no system by that name can be found known.\"\n  (let ((system (asdf:find-system name nil)))\n    (when system\n      (asdf:system-source-directory system))))\n"
  },
  {
    "path": "quicklisp/quicklisp/config.lisp",
    "content": ";;;; config.lisp\n\n(in-package #:ql-config)\n\n(defun config-value-file-pathname (path)\n  (let ((bad-position (position #\\Space path)))\n    (when bad-position\n      (error \"Space not allowed at position ~D in ~S\"\n             bad-position\n             path)))\n  (let* ((space-path (substitute #\\Space #\\/ path))\n         (split (split-spaces space-path))\n         (directory-parts (butlast split))\n         (name (first (last split)))\n         (base (qmerge \"config/\")))\n     (merge-pathnames\n      (make-pathname :name name\n                     :type \"txt\"\n                     :directory (list* :relative directory-parts))\n      base)))\n\n(defun config-value (path)\n  (let ((file (config-value-file-pathname path)))\n    (with-open-file (stream file :if-does-not-exist nil)\n      (when stream\n        (values (read-line stream nil))))))\n\n(defun (setf config-value) (new-value path)\n  (let ((file (config-value-file-pathname path)))\n    (typecase new-value\n      (null\n       (delete-file-if-exists file))\n      (string\n       (ensure-directories-exist file)\n       (with-open-file (stream file :direction :output\n                               :if-does-not-exist :create\n                               :if-exists :rename-and-delete)\n         (write-line new-value stream)))\n      (t\n       (error \"Bad config value ~S; must be a string or NIL\" new-value)))))\n"
  },
  {
    "path": "quicklisp/quicklisp/deflate.lisp",
    "content": ";;;; Deflate --- RFC 1951 Deflate Decompression\n;;;;\n;;;; Copyright (C) 2000-2009 PMSF IT Consulting Pierre R. Mai.\n;;;;\n;;;; Permission is hereby granted, free of charge, to any person obtaining\n;;;; a copy of this software and associated documentation files (the\n;;;; \"Software\"), to deal in the Software without restriction, including\n;;;; without limitation the rights to use, copy, modify, merge, publish,\n;;;; distribute, sublicense, and/or sell copies of the Software, and to\n;;;; permit persons to whom the Software is furnished to do so, subject to\n;;;; the following conditions:\n;;;;\n;;;; The above copyright notice and this permission notice shall be\n;;;; included in all copies or substantial portions of the Software.\n;;;;\n;;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n;;;; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR\n;;;; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n;;;; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n;;;; OTHER DEALINGS IN THE SOFTWARE.\n;;;;\n;;;; Except as contained in this notice, the name of the author shall\n;;;; not be used in advertising or otherwise to promote the sale, use or\n;;;; other dealings in this Software without prior written authorization\n;;;; from the author.\n;;;; \n;;;; $Id: 377d3a33e9db5a3b54c850619183ee555a41b894 $\n\n(cl:in-package #:ql-gunzipper)\n\n;;;; %File Description:\n;;;; \n;;;; This file contains routines implementing the RFC 1951 Deflate\n;;;; Compression and/or Decompression method, as used by e.g. gzip and\n;;;; other compression and archiving tools and protocols.  It also\n;;;; implements handling routines for zlib-style (RFC 1950) and\n;;;; gzip-style (RFC 1952) wrappers around raw Deflate streams.\n;;;; \n;;;; The main entry points are the functions inflate-stream, and its\n;;;; cousins inflate-zlib-stream and inflate-gzip-stream, which take\n;;;; an input-stream and an output-stream as their arguments, and\n;;;; inflate the RFC 1951, RFC 1950 or RFC 1952-style deflate formats\n;;;; from the input-stream to the output-stream.\n;;;;\n\n;;;\n;;; Conditions\n;;;\n\n(define-condition decompression-error (simple-error)\n  ())\n\n(define-condition deflate-decompression-error (decompression-error)\n  ()\n  (:report \n   (lambda (c s)\n     (with-standard-io-syntax\n       (let ((*print-readably* nil))\n         (format s \n                 \"Error detected during deflate decompression: ~?\"\n                 (simple-condition-format-control c)\n                 (simple-condition-format-arguments c)))))))\n\n(define-condition zlib-decompression-error (decompression-error)\n  ()\n  (:report \n   (lambda (c s)\n     (with-standard-io-syntax\n       (let ((*print-readably* nil))\n         (format s \n                 \"Error detected during zlib decompression: ~?\"\n                 (simple-condition-format-control c)\n                 (simple-condition-format-arguments c)))))))\n\n(define-condition gzip-decompression-error (decompression-error)\n  ()\n  (:report \n   (lambda (c s)\n     (with-standard-io-syntax\n       (let ((*print-readably* nil))\n         (format s \n                 \"Error detected during zlib decompression: ~?\"\n                 (simple-condition-format-control c)\n                 (simple-condition-format-arguments c)))))))\n\n;;;\n;;; Adler-32 Checksums\n;;;\n\n(defconstant +adler-32-start-value+ 1\n  \"Start value for Adler-32 checksums as per RFC 1950.\")\n\n(defconstant +adler-32-base+ 65521\n  \"Base value for Adler-32 checksums as per RFC 1950.\")\n\n(declaim (ftype \n          (function ((unsigned-byte 32) (simple-array (unsigned-byte 8) (*)) fixnum)\n                    (unsigned-byte 32))\n          update-adler32-checksum))\n(defun update-adler32-checksum (crc buffer end)\n  (declare (type (unsigned-byte 32) crc)\n           (type (simple-array (unsigned-byte 8) (*)) buffer)\n           (type fixnum end)\n           (optimize (speed 3) (debug 0) (space 0) (safety 0))\n           #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note))\n  (let ((s1 (ldb (byte 16 0) crc))\n        (s2 (ldb (byte 16 16) crc)))\n    (declare (type (unsigned-byte 32) s1 s2))\n    (dotimes (i end)\n      (declare (type fixnum i))\n      (setq s1 (mod (+ s1 (aref buffer i)) +adler-32-base+)\n            s2 (mod (+ s2 s1) +adler-32-base+)))\n    (dpb s2 (byte 16 16) s1)))\n\n;;;\n;;; CRC-32 Checksums\n;;;\n\n(defconstant +crc-32-start-value+ 0\n  \"Start value for CRC-32 checksums as per RFC 1952.\")\n\n(defconstant +crc-32-polynomial+ #xedb88320\n  \"CRC-32 Polynomial as per RFC 1952.\")\n\n(declaim (ftype #-lispworks (function () (simple-array (unsigned-byte 32) (256)))\n                #+lispworks (function () (sys:simple-int32-vector 256))\n                generate-crc32-table))\n(defun generate-crc32-table ()\n  (let ((result #-lispworks (make-array 256 :element-type '(unsigned-byte 32))\n                #+lispworks (sys:make-simple-int32-vector 256)))\n    (dotimes (i #-lispworks (length result) #+lispworks 256 result)\n      (let ((cur i))\n        (dotimes (k 8)\n          (setq cur (if (= 1 (logand cur 1))\n                        (logxor (ash cur -1) +crc-32-polynomial+)\n                        (ash cur -1))))\n        #-lispworks (setf (aref result i) cur)\n        #+lispworks (setf (sys:int32-aref result i)\n                          (sys:integer-to-int32\n                           (dpb (ldb (byte 32 0) cur) (byte 32 0) \n                                (if (logbitp 31 cur) -1 0))))))))\n\n(declaim (ftype \n          (function ((unsigned-byte 32) (simple-array (unsigned-byte 8) (*)) fixnum)\n                    (unsigned-byte 32))\n          update-crc32-checksum))\n#-lispworks\n(defun update-crc32-checksum (crc buffer end)\n  (declare (type (unsigned-byte 32) crc)\n           (type (simple-array (unsigned-byte 8) (*)) buffer)\n           (type fixnum end)\n           (optimize (speed 3) (debug 0) (space 0) (safety 0))\n           #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note))\n  (let ((table (load-time-value (generate-crc32-table)))\n        (cur (logxor crc #xffffffff)))\n    (declare (type (simple-array (unsigned-byte 32) (256)) table)\n             (type (unsigned-byte 32) cur))\n    (dotimes (i end)\n      (declare (type fixnum i))\n      (let ((index (logand #xff (logxor cur (aref buffer i)))))\n        (declare (type (unsigned-byte 8) index))\n        (setq cur (logxor (aref table index) (ash cur -8)))))\n    (logxor cur #xffffffff)))\n\n#+lispworks\n(defun update-crc32-checksum (crc buffer end)\n  (declare (type (unsigned-byte 32) crc)\n           (type (simple-array (unsigned-byte 8) (*)) buffer)\n           (type fixnum end)\n           (optimize (speed 3) (debug 0) (space 0) (safety 0) (float 0)))\n  (let ((table (load-time-value (generate-crc32-table)))\n        (cur (sys:int32-lognot (sys:integer-to-int32 \n                                (dpb (ldb (byte 32 0) crc) (byte 32 0) \n                                     (if (logbitp 31 crc) -1 0))))))\n    (declare (type (sys:simple-int32-vector 256) table)\n             (type sys:int32 cur))\n    (dotimes (i end)\n      (declare (type fixnum i))\n      (let ((index (sys:int32-to-integer\n                    (sys:int32-logand #xff (sys:int32-logxor cur (aref buffer i))))))\n        (declare (type fixnum index))\n        (setq cur (sys:int32-logxor (sys:int32-aref table index)\n                                    (sys:int32-logand #x00ffffff \n                                                      (sys:int32>> cur 8))))))\n    (ldb (byte 32 0) (sys:int32-to-integer (sys:int32-lognot cur)))))\n\n;;;\n;;; Helper Data Structures: Sliding Window Stream\n;;;\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defconstant +sliding-window-size+ 32768\n    \"Size of sliding window for RFC 1951 Deflate compression scheme.\"))\n\n(defstruct sliding-window-stream\n  (stream nil :type stream :read-only t)\n  (buffer (make-array +sliding-window-size+ :element-type '(unsigned-byte 8)) \n   :type (simple-array (unsigned-byte 8) (#.+sliding-window-size+)) :read-only t)\n  (buffer-end 0 :type fixnum)\n  (checksum nil :type symbol :read-only t)\n  (checksum-value 0 :type (unsigned-byte 32)))\n\n(declaim (inline sliding-window-stream-write-byte))\n(defun sliding-window-stream-write-byte (stream byte)\n  (declare (type sliding-window-stream stream) (type (unsigned-byte 8) byte)\n           #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note))\n  \"Write a single byte to the sliding-window-stream.\"\n  (let ((end (sliding-window-stream-buffer-end stream)))\n    (declare (type fixnum end))\n    (unless (< end +sliding-window-size+)\n      (write-sequence (sliding-window-stream-buffer stream)\n                      (sliding-window-stream-stream stream))\n      (case (sliding-window-stream-checksum stream)\n        (:adler-32 (setf (sliding-window-stream-checksum-value stream)\n                         (update-adler32-checksum\n                          (sliding-window-stream-checksum-value stream)\n                          (sliding-window-stream-buffer stream)\n                          +sliding-window-size+)))\n        (:crc-32 (setf (sliding-window-stream-checksum-value stream)\n                       (update-crc32-checksum\n                        (sliding-window-stream-checksum-value stream)\n                        (sliding-window-stream-buffer stream)\n                        +sliding-window-size+))))\n      (setq end 0))\n    (setf (aref (sliding-window-stream-buffer stream) end) byte\n          (sliding-window-stream-buffer-end stream) (1+ end))))\n\n(defun sliding-window-stream-flush (stream)\n  (declare (type sliding-window-stream stream))\n  \"Flush any remaining buffered bytes from the stream.\"\n  (let ((end (sliding-window-stream-buffer-end stream)))\n    (declare (type fixnum end))\n    (unless (zerop end)\n      (case (sliding-window-stream-checksum stream)\n        (:adler-32 (setf (sliding-window-stream-checksum-value stream)\n                         (update-adler32-checksum\n                          (sliding-window-stream-checksum-value stream)\n                          (sliding-window-stream-buffer stream)\n                          end)))\n        (:crc-32 (setf (sliding-window-stream-checksum-value stream)\n                       (update-crc32-checksum\n                        (sliding-window-stream-checksum-value stream)\n                        (sliding-window-stream-buffer stream)\n                        end))))\n      (write-sequence (sliding-window-stream-buffer stream)\n                      (sliding-window-stream-stream stream)\n                      :end end))))\n\n(defun sliding-window-stream-copy-bytes (stream distance length)\n  (declare (type sliding-window-stream stream) (type fixnum distance length))\n  \"Copy a number of bytes from the current sliding window.\"\n  (let* ((end (sliding-window-stream-buffer-end stream))\n         (start (mod (- end distance) +sliding-window-size+))\n         (buffer (sliding-window-stream-buffer stream)))\n    (declare (type fixnum end start)\n             (type (simple-array (unsigned-byte 8) (#.+sliding-window-size+)) buffer))\n    (dotimes (i length)\n      (sliding-window-stream-write-byte\n       stream\n       (aref buffer (mod (+ start i) +sliding-window-size+))))))\n\n;;;\n;;; Helper Data Structures: Bit-wise Input Stream\n;;;\n\n(defstruct bit-stream\n  (stream nil :type stream :read-only t)\n  (next-byte 0 :type fixnum)\n  (bits 0 :type (unsigned-byte 29))\n  (bit-count 0 :type (unsigned-byte 8)))\n\n(declaim (inline bit-stream-get-byte))\n(defun bit-stream-get-byte (stream)\n  (declare (type bit-stream stream))\n  \"Read another byte from the underlying stream.\"\n  (the (unsigned-byte 8) (read-byte (bit-stream-stream stream))))\n\n(declaim (inline bit-stream-read-bits))\n(defun bit-stream-read-bits (stream bits)\n  (declare (type bit-stream stream)\n           ;; [quicklisp-added]\n           ;; FIXME: This might be fixed soon in ECL.\n           ;; http://article.gmane.org/gmane.lisp.ecl.general/7659\n           #-ecl\n           (type (unsigned-byte 8) bits))\n  \"Read single or multiple bits from the given bit-stream.\"\n  (loop while (< (bit-stream-bit-count stream) bits)\n        do\n     ;; Fill bits\n     (setf (bit-stream-bits stream)\n           (logior (bit-stream-bits stream)\n                   (the (unsigned-byte 29)\n                     (ash (bit-stream-get-byte stream)\n                          (bit-stream-bit-count stream))))\n           (bit-stream-bit-count stream) (+ (bit-stream-bit-count stream) 8)))\n  ;; Return properly masked bits\n  (if (= (bit-stream-bit-count stream) bits)\n      (prog1 (bit-stream-bits stream)\n        (setf (bit-stream-bits stream) 0\n              (bit-stream-bit-count stream) 0))\n      (prog1 (ldb (byte bits 0) (bit-stream-bits stream))\n        (setf (bit-stream-bits stream) (ash (bit-stream-bits stream) (- bits))\n              (bit-stream-bit-count stream) (- (bit-stream-bit-count stream) bits)))))\n\n(declaim (inline bit-stream-copy-block))\n(defun bit-stream-copy-block (stream out-stream)\n  (declare (type bit-stream stream) (type sliding-window-stream out-stream)\n           (optimize (speed 3) (safety 0) (space 0) (debug 0)))\n  \"Copy a given block of bytes directly from the underlying stream.\"\n  ;; Skip any remaining unprocessed bits\n  (setf (bit-stream-bits stream) 0\n        (bit-stream-bit-count stream) 0)\n  ;; Get LEN/NLEN and copy bytes\n  (let* ((len (logior (bit-stream-get-byte stream)\n                      (ash (bit-stream-get-byte stream) 8)))\n         (nlen (ldb (byte 16 0) \n                    (lognot (logior (bit-stream-get-byte stream)\n                                    (ash (bit-stream-get-byte stream) 8))))))\n    (unless (= len nlen)\n      (error 'deflate-decompression-error\n             :format-control \n             \"Block length mismatch for stored block: LEN(~D) vs. NLEN(~D)!\"\n             :format-arguments (list len nlen)))\n    (dotimes (i len)\n      (sliding-window-stream-write-byte out-stream (bit-stream-get-byte stream)))))\n\n;;;\n;;; Huffman Coding\n;;;\n\n;;; A decode-tree struct contains all information necessary to decode\n;;; the given canonical huffman code.  Note that length-count contains\n;;; the number of codes with a given length for each length, whereas\n;;; the code-symbols array contains the symbols corresponding to the\n;;; codes in canoical order of the codes.\n;;;\n;;; Decoding then uses this information and the principles underlying\n;;; canonical huffman codes to determine whether the currently\n;;; collected word falls between the first code and the last code for\n;;; the current length, and if so, uses the offset to determine the\n;;; code's symbol.  Otherwise more bits are needed.\n\n(defstruct decode-tree\n  (length-count (make-array 16 :element-type 'fixnum :initial-element 0)\n   :type (simple-array fixnum (*)) :read-only t)\n  (code-symbols (make-array 16 :element-type 'fixnum :initial-element 0)\n   :type (simple-array fixnum (*))))\n\n(defun make-huffman-decode-tree (code-lengths)\n  \"Construct a huffman decode-tree for the canonical huffman code with\nthe code lengths of each symbol given in the input array.\"\n  (let* ((max-length (reduce #'max code-lengths :initial-value 0))\n         (next-code (make-array (1+ max-length) :element-type 'fixnum\n                                :initial-element 0))\n         (code-symbols (make-array (length code-lengths) :element-type 'fixnum\n                                   :initial-element 0))\n         (length-count (make-array (1+ max-length) :element-type 'fixnum\n                                   :initial-element 0)))\n    ;; Count length occurences and calculate offsets of smallest codes\n    (loop for index from 1 to max-length\n          for code = 0 then (+ code (aref length-count (1- index)))\n          do\n       (setf (aref next-code index) code)\n          initially\n       ;; Count length occurences\n       (loop for length across code-lengths\n             do\n          (incf (aref length-count length))\n             finally\n          (setf (aref length-count 0) 0)))\n    ;; Construct code symbols mapping\n    (loop for length across code-lengths\n          for index upfrom 0\n          unless (zerop length)\n            do\n         (setf (aref code-symbols (aref next-code length)) index)\n         (incf (aref next-code length)))\n    ;; Return result\n    (make-decode-tree :length-count length-count :code-symbols code-symbols)))\n\n(declaim (inline read-huffman-code))\n(defun read-huffman-code (bit-stream decode-tree)\n  (declare (type bit-stream bit-stream) (type decode-tree decode-tree)\n           (optimize (speed 3) (safety 0) (space 0) (debug 0)))\n  \"Read the next huffman code word from the given bit-stream and\nreturn its decoded symbol, for the huffman code given by decode-tree.\"\n  (loop with length-count of-type (simple-array fixnum (*)) \n          = (decode-tree-length-count decode-tree)\n        with code-symbols of-type (simple-array fixnum (*))\n          = (decode-tree-code-symbols decode-tree)\n        for code of-type fixnum = (bit-stream-read-bits bit-stream 1)\n          then (+ (* code 2) (bit-stream-read-bits bit-stream 1))\n        for index of-type fixnum = 0 then (+ index count)\n        for first of-type fixnum = 0 then (* (+ first count) 2)\n        for length of-type fixnum upfrom 1 below (length length-count)\n        for count = (aref length-count length)\n        thereis (when (< code (the fixnum (+ first count)))\n                  (aref code-symbols (+ index (- code first))))\n        finally\n     (error 'deflate-decompression-error\n            :format-control \n            \"Corrupted Data detected during decompression: ~\n             Incorrect huffman code (~X) in huffman decode!\"\n            :format-arguments (list code))))\n\n;;;\n;;; Standard Huffman Tables\n;;;\n\n(defparameter *std-lit-decode-tree* \n  (make-huffman-decode-tree \n   (concatenate 'vector\n                (make-sequence 'vector 144 :initial-element 8)\n                (make-sequence 'vector 112 :initial-element 9)\n                (make-sequence 'vector 24 :initial-element 7)\n                (make-sequence 'vector 8 :initial-element 8))))\n                \n(defparameter *std-dist-decode-tree* \n  (make-huffman-decode-tree\n   (make-sequence 'vector 32 :initial-element 5)))\n\n;;;\n;;; Dynamic Huffman Table Handling\n;;;\n\n(defparameter *code-length-entry-order*\n  #(16 17 18 0 8 7 9 6 10 5 11 4 12 3 13 2 14 1 15)\n  \"Order of Code Length Tree Code Lengths.\")\n\n(defun decode-code-length-entries (bit-stream count decode-tree)\n  \"Decode the given number of code length entries from the bit-stream \nusing the given decode-tree, and return a corresponding array of code \nlengths for further processing.\"\n  (do ((result (make-array count :element-type 'fixnum :initial-element 0))\n       (index 0))\n      ((>= index count) result)\n    (let ((code (read-huffman-code bit-stream decode-tree)))\n      (ecase code\n        ((0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)\n           (setf (aref result index) code)\n           (incf index))\n        (16\n           (let ((length (+ 3 (bit-stream-read-bits bit-stream 2))))\n             (dotimes (i length)\n               (setf (aref result (+ index i)) (aref result (1- index))))\n             (incf index length)))\n        (17\n           (let ((length (+ 3 (bit-stream-read-bits bit-stream 3))))\n             (dotimes (i length)\n               (setf (aref result (+ index i)) 0))\n             (incf index length)))\n        (18\n           (let ((length (+ 11 (bit-stream-read-bits bit-stream 7))))\n             (dotimes (i length)\n               (setf (aref result (+ index i)) 0))\n             (incf index length)))))))\n\n(defun decode-huffman-tables (bit-stream)\n  \"Decode the stored huffman tables from the given bit-stream, returning\nthe corresponding decode-trees for literals/length and distance codes.\"\n  (let* ((hlit (bit-stream-read-bits bit-stream 5))\n         (hdist (bit-stream-read-bits bit-stream 5))\n         (hclen (bit-stream-read-bits bit-stream 4)))\n    ;; Construct Code Length Decode Tree\n    (let ((cl-decode-tree\n           (loop with code-lengths = (make-array 19 :element-type '(unsigned-byte 8) \n                                          :initial-element 0)\n                 for index from 0 below (+ hclen 4)\n                 for code-length = (bit-stream-read-bits bit-stream 3)\n                 for code-index = (aref *code-length-entry-order* index)\n                 do\n              (setf (aref code-lengths code-index) code-length)\n                 finally\n              (return (make-huffman-decode-tree code-lengths)))))\n      ;; Decode Code Length Table and generate separate huffman trees\n      (let ((entries (decode-code-length-entries bit-stream \n                                                 (+ hlit 257 hdist 1)\n                                                 cl-decode-tree)))\n        (values \n          (make-huffman-decode-tree (subseq entries 0 (+ hlit 257)))\n          (make-huffman-decode-tree (subseq entries (+ hlit 257))))))))\n\n;;;\n;;; Compressed Block Handling\n;;;\n\n(declaim (inline decode-length-entry))\n(defun decode-length-entry (symbol bit-stream)\n  \"Decode the given length symbol into a proper length specification.\"\n  (cond\n    ((<= symbol 264) (- symbol 254))\n    ((<= symbol 268) (+ 11 (* (- symbol 265) 2) (bit-stream-read-bits bit-stream 1)))\n    ((<= symbol 272) (+ 19 (* (- symbol 269) 4) (bit-stream-read-bits bit-stream 2)))\n    ((<= symbol 276) (+ 35 (* (- symbol 273) 8) (bit-stream-read-bits bit-stream 3)))\n    ((<= symbol 280) (+ 67 (* (- symbol 277) 16) (bit-stream-read-bits bit-stream 4)))\n    ((<= symbol 284)\n     (+ 131 (* (- symbol 281) 32) (bit-stream-read-bits bit-stream 5)))\n    ((= symbol 285) 258)\n    (t \n     (error 'deflate-decompression-error \n            :format-control \"Strange Length Code in bitstream: ~D\" \n            :format-arguments (list symbol)))))\n\n(declaim (inline decode-distance-entry))\n(defun decode-distance-entry (symbol bit-stream)\n  \"Decode the given distance symbol into a proper distance specification.\"\n  (cond\n    ((<= symbol 3) (1+ symbol))\n    (t\n     (multiple-value-bind (order offset) (truncate symbol 2)\n       (let* ((extra-bits (1- order))\n              (factor (ash 1 extra-bits)))\n         (+ (1+ (ash 1 order))\n            (* offset factor)\n            (bit-stream-read-bits bit-stream extra-bits)))))))\n\n(defun decode-huffman-block (bit-stream window-stream \n                             lit-decode-tree dist-decode-tree)\n  \"Decode the huffman code block using the huffman codes given by \nlit-decode-tree and dist-decode-tree.\"\n  (do ((symbol (read-huffman-code bit-stream lit-decode-tree)\n               (read-huffman-code bit-stream lit-decode-tree)))\n      ((= symbol 256))\n    (cond\n      ((<= symbol 255)\n       (sliding-window-stream-write-byte window-stream symbol))\n      (t\n       (let ((length (decode-length-entry symbol bit-stream))\n             (distance (decode-distance-entry \n                        (read-huffman-code bit-stream dist-decode-tree) bit-stream)))\n         (sliding-window-stream-copy-bytes window-stream distance length))))))\n\n;;;\n;;; Block Handling Code\n;;;\n\n(defun decode-block (bit-stream window-stream)\n  \"Decompress a block read from bit-stream into window-stream.\"\n  (let* ((finalp (not (zerop (bit-stream-read-bits bit-stream 1))))\n         (type (bit-stream-read-bits bit-stream 2)))\n    (ecase type\n      (#b00 (bit-stream-copy-block bit-stream window-stream))\n      (#b01 \n         (decode-huffman-block bit-stream window-stream \n                               *std-lit-decode-tree* \n                               *std-dist-decode-tree*))\n      (#b10 \n         (multiple-value-bind (lit-decode-tree dist-decode-tree)\n             (decode-huffman-tables bit-stream)\n           (decode-huffman-block bit-stream window-stream \n                                 lit-decode-tree dist-decode-tree)))\n      (#b11 \n         (error 'deflate-decompression-error \n                :format-control \"Encountered Reserved Block Type ~D!\"\n                :format-arguments (list type))))\n    (not finalp)))\n\n;;;\n;;; ZLIB - RFC 1950 handling\n;;;\n\n(defun parse-zlib-header (input-stream)\n  \"Parse a ZLIB-style header as per RFC 1950 from the input-stream and\nreturn the compression-method, compression-level dictionary-id and flags\nfields of the header as return values.  Checks the header for corruption\nand signals a zlib-decompression-error in case of corruption.\"\n  (let ((compression-method (read-byte input-stream))\n        (flags (read-byte input-stream)))\n    (unless (zerop (mod (+ (* compression-method 256) flags) 31))\n      (error 'zlib-decompression-error\n             :format-control \"Corrupted Header ~2,'0X,~2,'0X!\"\n             :format-arguments (list compression-method flags)))\n    (let ((dict (unless (zerop (ldb (byte 1 5) flags))\n                  (parse-zlib-checksum input-stream))))\n      (values (ldb (byte 4 0) compression-method)\n              (ldb (byte 4 4) compression-method)\n              dict\n              (ldb (byte 2 6) flags)))))\n\n(defun parse-zlib-checksum (input-stream)\n  (+ (* (read-byte input-stream) 256 256 256)\n     (* (read-byte input-stream) 256 256)\n     (* (read-byte input-stream) 256)\n     (read-byte input-stream)))\n\n(defun parse-zlib-footer (input-stream)\n  \"Parse the ZLIB-style footer as per RFC 1950 from the input-stream and\nreturn the Adler-32 checksum contained in the footer as its return value.\"\n  (parse-zlib-checksum input-stream))\n\n;;;\n;;; GZIP - RFC 1952 handling\n;;;\n\n(defconstant +gzip-header-id1+ 31\n  \"GZIP Header Magic Value ID1 as per RFC 1952.\")\n\n(defconstant +gzip-header-id2+ 139\n  \"GZIP Header Magic Value ID2 as per RFC 1952.\")\n\n(defun parse-gzip-header (input-stream)\n  \"Parse a GZIP-style header as per RFC 1952 from the input-stream and\nreturn the compression-method, text-flag, modification time, XFLAGS,\nOS, FEXTRA flags, filename, comment and CRC16 fields of the header as\nreturn values (or nil if any given field is not present).  Checks the\nheader for magic values and correct flags settings and signals a\ngzip-decompression-error in case of incorrect or unsupported magic\nvalues or flags.\"\n  (let ((id1 (read-byte input-stream))\n        (id2 (read-byte input-stream))\n        (compression-method (read-byte input-stream))\n        (flags (read-byte input-stream)))\n    (unless (and (= id1 +gzip-header-id1+) (= id2 +gzip-header-id2+))\n      (error 'gzip-decompression-error\n             :format-control\n             \"Header missing magic values ~2,'0X,~2,'0X (got ~2,'0X,~2,'0X instead)!\"\n             :format-arguments (list +gzip-header-id1+ +gzip-header-id2+ id1 id2)))\n    (unless (= compression-method 8)\n      (error 'gzip-decompression-error\n             :format-control \"Unknown compression-method in Header ~2,'0X!\"\n             :format-arguments (list compression-method)))\n    (unless (zerop (ldb (byte 3 5) flags))\n      (error 'gzip-decompression-error\n             :format-control \"Unknown flags in Header ~2,'0X!\"\n             :format-arguments (list flags)))\n    (values compression-method\n            ;; FTEXT\n            (= 1 (ldb (byte 1 0) flags))\n            ;; MTIME\n            (parse-gzip-mtime input-stream)\n            ;; XFLAGS\n            (read-byte input-stream)\n            ;; OS\n            (read-byte input-stream)\n            ;; FEXTRA\n            (unless (zerop (ldb (byte 1 2) flags))\n              (parse-gzip-extra input-stream))\n            ;; FNAME\n            (unless (zerop (ldb (byte 1 3) flags))\n              (parse-gzip-string input-stream))\n            ;; FCOMMENT\n            (unless (zerop (ldb (byte 1 4) flags))\n              (parse-gzip-string input-stream))\n            ;; CRC16\n            (unless (zerop (ldb (byte 1 1) flags))\n              (+ (read-byte input-stream)\n                 (* (read-byte input-stream 256)))))))\n\n(defun parse-gzip-mtime (input-stream)\n  (let ((time (+ (read-byte input-stream)\n                 (* (read-byte input-stream) 256)\n                 (* (read-byte input-stream) 256 256)\n                 (* (read-byte input-stream) 256 256 256))))\n    (if (zerop time)\n        nil\n        (+ time 2208988800))))\n\n(defun parse-gzip-extra (input-stream)\n  (let* ((length (+ (read-byte input-stream) (* (read-byte input-stream) 256)))\n         (result (make-array length :element-type '(unsigned-byte 8))))\n    (read-sequence result input-stream)\n    result))\n\n(defun parse-gzip-string (input-stream)\n  (with-output-to-string (string)\n    (loop for value = (read-byte input-stream)\n          until (zerop value)\n          do (write-char (code-char value) string))))\n          \n(defun parse-gzip-checksum (input-stream)\n  (+ (read-byte input-stream)\n     (* (read-byte input-stream) 256)\n     (* (read-byte input-stream) 256 256)\n     (* (read-byte input-stream) 256 256 256)))\n\n(defun parse-gzip-footer (input-stream)\n  \"Parse the GZIP-style footer as per RFC 1952 from the input-stream and\nreturn the CRC-32 checksum and ISIZE fields contained in the footer as\nits return values.\"\n  (values (parse-gzip-checksum input-stream)\n          ;; ISIZE\n          (+ (read-byte input-stream)\n             (* (read-byte input-stream) 256)\n             (* (read-byte input-stream) 256 256)\n             (* (read-byte input-stream) 256 256 256))))\n\n;;;\n;;; Main Entry Points\n;;;\n\n(defun inflate-stream (input-stream output-stream &key checksum)\n  \"Inflate the RFC 1951 data from the given input stream into the\ngiven output stream, which are required to have an element-type\nof (unsigned-byte 8).  If checksum is given, it indicates the\nchecksumming algorithm to employ in calculating a checksum of\nthe expanded content, which is then returned from this function.\nValid values are :adler-32 for Adler-32 checksum (see RFC 1950),\nor :crc-32 for CRC-32 as per ISO 3309 (see RFC 1952, ZIP).\"\n  (loop with window-stream = (make-sliding-window-stream :stream output-stream\n                                  :checksum checksum\n                                  :checksum-value\n                                  (ecase checksum\n                                    ((nil) 0)\n                                    (:crc-32 +crc-32-start-value+)\n                                    (:adler-32 +adler-32-start-value+)))\n        with bit-stream = (make-bit-stream :stream input-stream)\n        while (decode-block bit-stream window-stream)\n        finally (sliding-window-stream-flush window-stream)\n                (when checksum \n                  (return (sliding-window-stream-checksum-value window-stream)))))\n\n(defun inflate-zlib-stream (input-stream output-stream &key check-checksum)\n  \"Inflate the RFC 1950 zlib data from the given input stream into\nthe given output stream, which are required to have an element-type\nof (unsigned-byte 8).  This returns the Adler-32 checksum of the\nfile as its first return value, with the compression level as its\nsecond return value.  Note that it is the responsibility of the \ncaller to check whether the expanded data matches the Adler-32\nchecksum, unless the check-checksum keyword argument is set to\ntrue, in which case the checksum is checked internally and a\nzlib-decompression-error is signalled if they don't match.\"\n  (multiple-value-bind (cm cinfo dictid flevel) (parse-zlib-header input-stream)\n    (unless (= cm 8)\n      (error 'zlib-decompression-error\n             :format-control \"Unknown compression method ~D!\"\n             :format-arguments (list cm)))\n    (unless (<= cinfo 7)\n      (error 'zlib-decompression-error\n             :format-control \"Unsupported sliding window size 2^~D = ~D!\"\n             :format-arguments (list (+ 8 cinfo) (expt 2 (+ 8 cinfo)))))\n    (unless (null dictid)\n      (error 'zlib-decompression-error\n             :format-control \"Unknown preset dictionary id ~8,'0X!\"\n             :format-arguments (list dictid)))\n    (let ((checksum-new (inflate-stream input-stream output-stream \n                                        :checksum (when check-checksum :adler-32)))\n          (checksum-old (parse-zlib-footer input-stream)))\n      (when (and check-checksum (not (= checksum-old checksum-new)))\n        (error 'zlib-decompression-error\n               :format-control\n               \"Checksum mismatch for decompressed stream: ~8,'0X != ~8,'0X!\"\n               :format-arguments (list checksum-old checksum-new)))\n      (values checksum-old flevel))))\n\n(defun inflate-gzip-stream (input-stream output-stream &key check-checksum)\n  \"Inflate the RFC 1952 gzip data from the given input stream into\nthe given output stream, which are required to have an element-type\nof (unsigned-byte 8).  This returns the CRC-32 checksum of the\nfile as its first return value, with any filename, modification time,\nand comment fields as further return values or nil if not present.\nNote that it is the responsibility of the caller to check whether the\nexpanded data matches the CRC-32 checksum, unless the check-checksum\nkeyword argument is set to true, in which case the checksum is checked\ninternally and a gzip-decompression-error is signalled if they don't\nmatch.\"\n  (multiple-value-bind (cm ftext mtime xfl os fextra fname fcomment)\n      (parse-gzip-header input-stream)\n    (declare (ignore ftext xfl os fextra))\n    (unless (= cm 8)\n      (error 'gzip-decompression-error\n             :format-control \"Unknown compression method ~D!\"\n             :format-arguments (list cm)))\n    (let ((checksum-new (inflate-stream input-stream output-stream \n                                        :checksum (when check-checksum :crc-32)))\n          (checksum-old (parse-gzip-footer input-stream)))\n      ;; Handle Checksums\n      (when (and check-checksum (not (= checksum-old checksum-new)))\n        (error 'gzip-decompression-error\n               :format-control\n               \"Checksum mismatch for decompressed stream: ~8,'0X != ~8,'0X!\"\n               :format-arguments (list checksum-old checksum-new)))\n      (values checksum-old fname mtime fcomment))))\n\n\n(defun gunzip (input-file output-file)\n  (with-open-file (input input-file\n                         :element-type '(unsigned-byte 8))\n    (with-open-file (output output-file\n                            :direction :output\n                            :if-exists :supersede\n                            :element-type '(unsigned-byte 8))\n      (inflate-gzip-stream input output)))\n  (probe-file output-file))\n"
  },
  {
    "path": "quicklisp/quicklisp/dist-update.lisp",
    "content": ";;;; dist-update.lisp\n\n(in-package #:ql-dist)\n\n(defgeneric available-update (dist)\n  (:documentation \"If an update is available for DIST, return the\n  update as an uninstalled dist object. Otherwise, return NIL.\"))\n\n(defgeneric update-release-differences (old-dist new-dist)\n  (:documentation \"Compare OLD-DIST to NEW-DIST and return three lists\n  as multiple values: new releases \\(present in NEW-DIST but not\n  OLD-DIST), changed releases \\(present in both dists but different in\n  some way), and removed releases \\(present in OLD-DIST but not\n  NEW-DIST). The list of changed releases is a list of two-element\n  lists, with each two-element list having first the old release\n  object and then the new release object.\"))\n\n(defgeneric show-update-report (old-dist new-dist)\n  (:documentation \"Display a description of the update from OLD-DIST\n  to NEW-DIST.\"))\n\n(defgeneric update-in-place (old-dist new-dist)\n  (:documentation \"Update OLD-DIST to NEW-DIST in place.\"))\n\n(defmethod available-update ((dist dist))\n  (let ((url (distinfo-subscription-url dist))\n        (target (qmerge \"tmp/distinfo-update/distinfo.txt\"))\n        (update-directory (qmerge \"tmp/distinfo-update/\")))\n    (when (probe-directory update-directory)\n      (delete-directory-tree (qmerge \"tmp/distinfo-update/\")))\n    (when url\n      (ensure-directories-exist target)\n      (fetch url target :quietly t)\n      (let ((new (make-dist-from-file target)))\n        (setf (base-directory new)\n              (make-pathname :name nil\n                             :type nil\n                             :version nil\n                             :defaults target))\n        (when (and (string= (name dist) (name new))\n                   (string/= (version dist) (version new)))\n          new)))))\n\n(defmethod update-release-differences ((old-dist dist)\n                                       (new-dist dist))\n  (let ((old-releases (provided-releases old-dist))\n        (new-releases (provided-releases new-dist))\n        (new '())\n        (updated '())\n        (removed '())\n        (old-by-name (make-hash-table :test 'equalp)))\n    (dolist (release old-releases)\n      (setf (gethash (name release) old-by-name)\n            release))\n    (dolist (new-release new-releases)\n      (let* ((name (name new-release))\n             (old-release (gethash name old-by-name)))\n        (remhash name old-by-name)\n        (cond ((not old-release)\n               (push new-release new))\n              ((not (equal (archive-content-sha1 new-release)\n                           (archive-content-sha1 old-release)))\n               (push (list old-release new-release) updated)))))\n    (maphash (lambda (name old-release)\n               (declare (ignore name))\n               (push old-release removed))\n             old-by-name)\n    (values (nreverse  new)\n            (nreverse  updated)\n            (sort removed #'string< :key #'prefix))))\n\n(defmethod show-update-report ((old-dist dist) (new-dist dist))\n  (multiple-value-bind (new updated removed)\n      (update-release-differences old-dist new-dist)\n    (format t \"Changes from ~A ~A to ~A ~A:~%\"\n            (name old-dist)\n            (version old-dist)\n            (name new-dist)\n            (version new-dist))\n    (when new\n      (format t \"~&  New projects:~%\")\n      (format t \"~{    ~A~%~}\" (mapcar #'prefix new)))\n    (when updated\n      (format t \"~%  Updated projects:~%\")\n      (loop for (old-release new-release) in updated\n            do (format t \"    ~A -> ~A~%\"\n                       (prefix old-release)\n                       (prefix new-release))))\n    (when removed\n      (format t \"~%  Removed projects:~%\")\n      (format t \"~{    ~A~%~}\" (mapcar #'prefix removed)))))\n\n(defun clear-dist-systems (dist)\n  (dolist (system (provided-systems dist))\n    (asdf:clear-system (name system))))\n\n(defmethod update-in-place :before ((old-dist dist) (new-dist dist))\n  ;; Make sure ASDF will reload any systems at their new locations\n  (clear-dist-systems old-dist))\n\n(defmethod update-in-place :after ((old-dist dist) (new-dist dist))\n  (clean new-dist))\n\n(defmethod update-in-place ((old-dist dist) (new-dist dist))\n  (flet ((remove-installed (type)\n           (let ((wild (merge-pathnames (make-pathname :directory\n                                                       (list :relative\n                                                             \"installed\"\n                                                             type)\n                                                       :name :wild\n                                                       :type \"txt\")\n                                        (base-directory old-dist))))\n             (dolist (file (directory wild))\n               (delete-file file)))))\n    (let ((reinstall-releases (installed-releases old-dist)))\n      (remove-installed \"systems\")\n      (remove-installed \"releases\")\n      (delete-file-if-exists (relative-to old-dist \"releases.txt\"))\n      (delete-file-if-exists (relative-to old-dist \"systems.txt\"))\n      (delete-file-if-exists (relative-to old-dist \"releases.cdb\"))\n      (delete-file-if-exists (relative-to old-dist \"systems.cdb\"))\n      (replace-file (local-distinfo-file new-dist)\n                    (local-distinfo-file old-dist))\n      (setf new-dist (find-dist (name new-dist)))\n      (dolist (old-release reinstall-releases)\n        (let* ((name (name old-release))\n               (new-release (find-release-in-dist name new-dist)))\n          (if new-release\n              (ensure-installed new-release)\n              (warn \"~S is not available in ~A\" name new-dist)))))))\n\n(defun install-dist (url &key (prompt t) replace)\n  (block nil\n    (setf url (url url))\n    (let ((temp-file (qmerge \"tmp/install-dist-distinfo.txt\")))\n      (ensure-directories-exist temp-file)\n      (delete-file-if-exists temp-file)\n      (fetch url temp-file)\n      (let* ((new-dist (make-dist-from-file temp-file))\n             (old-dist (find-dist (name new-dist))))\n        (when old-dist\n          (if replace\n              (uninstall old-dist)\n              (restart-case\n                  (error \"A dist named ~S is already installed.\"\n                         (name new-dist))\n                (replace ()\n                  :report \"Replace installed dist with new dist\"\n                  (uninstall old-dist)))))\n        (format t \"Installing dist ~S version ~S.~%\"\n                (name new-dist)\n                (version new-dist))\n        (when (or (not prompt)\n                  (press-enter-to-continue))\n          (ensure-directories-exist (base-directory new-dist))\n          (copy-file temp-file (relative-to new-dist \"distinfo.txt\"))\n          (ensure-release-index-file new-dist)\n          (ensure-system-index-file new-dist)\n          (enable new-dist)\n          (setf (preference new-dist) (get-universal-time))\n          (when old-dist\n            (clear-dist-systems old-dist))\n          (clear-dist-systems new-dist)\n          new-dist)))))\n"
  },
  {
    "path": "quicklisp/quicklisp/dist.lisp",
    "content": ";;;; dist.lisp\n\n(in-package #:ql-dist)\n\n\n;;; Generic functions\n\n(defgeneric dist (object)\n  (:documentation\n   \"Return the dist of OBJECT.\"))\n\n(defgeneric available-versions (object)\n  (:documentation\n   \"Return a list of version information for OBJECT.\"))\n\n(defgeneric system-index-url (object)\n  (:documentation\n   \"Return the URL for the system index of OBJECT.\"))\n\n(defgeneric release-index-url (object)\n  (:documentation\n   \"Return the URL for the release index of OBJECT.\"))\n\n(defgeneric available-versions-url (object)\n  (:documentation\n   \"Return the URL for the available versions data file of OBJECT.\"))\n\n(defgeneric release (object)\n  (:documentation\n   \"Return the release of OBJECT.\"))\n\n(defgeneric system (object)\n  (:documentation\n   \"Return the system of OBJECT.\"))\n\n(defgeneric name (object)\n  (:documentation\n   \"Return the name of OBJECT.\"))\n\n(defgeneric find-system (name)\n  (:documentation\n   \"Return a system with the given NAME, or NIL if no system is\n   found. If multiple systems have the same name, the one with the\n   highest preference is returned.\"))\n\n(defgeneric find-release (name)\n  (:documentation\n   \"Return a release with the given NAME, or NIL if no system is\n   found. If multiple releases have the same name, the one with the\n   highest preference is returned.\"))\n\n(defgeneric find-systems-named (name)\n  (:documentation\n   \"Return a list of all systems in all enabled dists with the given\n   NAME, sorted by preference.\"))\n\n(defgeneric find-releases-named (name)\n  (:documentation\n   \"Return a list of all releases in all enabled dists with the given\n   NAME, sorted by preference.\"))\n\n\n(defgeneric base-directory (object)\n  (:documentation\n   \"Return the base directory pathname of OBJECT.\")\n  (:method ((object pathname))\n    (merge-pathnames object)))\n\n(defgeneric relative-to (object pathname)\n  (:documentation\n   \"Merge PATHNAME with the base-directory of OBJECT.\")\n  (:method (object pathname)\n    (merge-pathnames pathname (base-directory object))))\n\n\n(defgeneric enabledp (object)\n  (:documentation\n   \"Return true if OBJECT is enabled.\"))\n\n(defgeneric enable (object)\n  (:documentation\n   \"Enable OBJECT.\"))\n\n(defgeneric disable (object)\n  (:documentation\n   \"Disable OBJECT.\"))\n\n(defgeneric installedp (object)\n  (:documentation\n   \"Return true if OBJECT is installed.\"))\n\n(defgeneric install (object)\n  (:documentation\n   \"Install OBJECT.\"))\n\n(defgeneric ensure-installed (object)\n  (:documentation\n   \"Ensure that OBJECT is installed.\")\n  (:method (object)\n    (unless (installedp object)\n      (install object))\n    object))\n\n(defgeneric uninstall (object)\n  (:documentation\n   \"Uninstall OBJECT.\"))\n\n(defgeneric metadata-name (object)\n  (:documentation\n   \"The metadata-name of an object is used to form the pathname for a\n   few different object metadata files.\"))\n\n(defgeneric install-metadata-file (object)\n  (:documentation\n   \"The pathname to a file describing the installation status of\n   OBJECT.\"))\n\n(defgeneric subscription-inhibition-file (object)\n  (:documentation \"The file whose presence indicates the inhibited\n  subscription status of OBJECT.\")\n  (:method (object)\n    (relative-to object \"subscription-inhibited.txt\")))\n\n(defgeneric inhibit-subscription (object)\n  (:documentation \"Inhibit subscription for OBJECT.\")\n  (:method (object)\n    (ensure-file-exists (subscription-inhibition-file object))))\n\n(defgeneric uninhibit-subscription (object)\n  (:documentation \"Remove inhibition of subscription for OBJECT.\")\n  (:method (object)\n    (delete-file-if-exists (subscription-inhibition-file object))))\n\n(defgeneric subscription-inhibited-p (object)\n  (:documentation \"Return T if subscription to OBJECT is inhibited.\")\n  (:method (object)\n    (not (not (probe-file (subscription-inhibition-file object))))))\n\n(define-condition subscription-unavailable (error)\n  ((object\n    :initarg :object\n    :reader subscription-unavailable-object)))\n\n(defgeneric subscribedp (object)\n  (:documentation \"Return true if OBJECT is subscribed to updates.\"))\n\n(defgeneric subscribe (object)\n  (:documentation \"Subscribe to updates of OBJECT, if possible. If no\n  updates are available, a condition of type SUBSCRIPTION-UNAVAILABLE\n  is raised.\")\n  (:method (object)\n    (uninhibit-subscription object)\n    (unless (subscribedp object)\n      (error 'subscription-unavailable\n             :object object))\n    t))\n\n(defgeneric unsubscribe (object)\n  (:documentation \"Unsubscribe from updates to OBJECT.\")\n  (:method (object)\n    (inhibit-subscription object)))\n\n\n(defgeneric preference-parent (object)\n  (:documentation\n   \"Return a value suitable for checking if OBJECT has no specific\n   preference set.\")\n  (:method (object)\n    (declare (ignore object))\n    nil))\n\n(defgeneric preference-file (object)\n  (:documentation\n   \"Return the file from which preference information is loaded for\n   OBJECT.\")\n  (:method (object)\n    (relative-to object \"preference.txt\")))\n\n(defgeneric preference (object)\n  (:documentation\n   \"Returns a value used when comparing multiple systems or releases\n   with the same name. Objects with higher preference are returned by\n   FIND-SYSTEM and FIND-RELEASE.\")\n  (:method ((object null))\n    0)\n  (:method (object)\n    (with-open-file (stream (preference-file object)\n                            :if-does-not-exist nil)\n      (if stream\n          (values (parse-integer (read-line stream)))\n          (preference (preference-parent object))))))\n\n(defgeneric (setf preference) (preference object)\n  (:documentation\n   \"Set the preference for OBJECT. Objects with higher preference are\n   returned by FIND-SYSTEM and FIND-RELEASE.\")\n  (:method (preference object)\n    (check-type preference integer)\n    (let ((preference-file (preference-file object)))\n      (ensure-directories-exist preference-file)\n      (with-open-file (stream (preference-file object)\n                              :direction :output\n                              :if-exists :supersede)\n        (format stream \"~D\" preference)))\n    preference))\n\n(defgeneric forget-preference (object)\n  (:documentation\n   \"Remove specific preference information for OBJECT.\")\n  (:method (object)\n    (delete-file-if-exists (preference-file object))))\n\n(defgeneric short-description (object)\n  (:documentation \"Return a short string describing OBJECT.\"))\n\n\n(defgeneric provided-releases (object)\n  (:documentation \"Return a list of releases provided by OBJECT.\"))\n\n(defgeneric provided-systems (object)\n  (:documentation \"Return a list of systems provided by OBJECT.\"))\n\n(defgeneric installed-releases (dist)\n  (:documentation\n   \"Return a list of all releases installed for DIST.\")\n  (:method (dist)\n    (remove-if-not #'installedp (provided-releases dist))))\n\n(defgeneric installed-systems (dist)\n  (:documentation\n   \"Return a list of all systems installed for DIST.\")\n  (:method (dist)\n    (remove-if-not #'installedp (provided-systems dist))))\n\n(defgeneric new-version-available-p (dist)\n  (:documentation\n   \"Return true if a new version of DIST is available.\"))\n\n(defgeneric find-system-in-dist (system-name dist)\n  (:documentation\n   \"Return a system with the given NAME in DIST, or NIL if no system\n   is found.\"))\n\n(defgeneric find-release-in-dist (release-name dist)\n  (:documentation\n   \"Return a release with the given NAME in DIST, or NIL if no release\n   is found.\"))\n\n\n(defgeneric ensure-system-index-file (dist)\n  (:documentation\n   \"Return the pathname for the system index file of DIST, fetching it\n   from a remote source first if necessary.\"))\n\n(defgeneric ensure-system-cdb-file (dist)\n  (:documentation\n   \"Return the pathname for the system cdb file of DIST, creating it\n   if necessary.\"))\n\n(defgeneric ensure-release-index-file (dist)\n  (:documentation\n   \"Return the pathname for the release index file of DIST, fetching\n   it from a remote source first if necessary.\"))\n\n(defgeneric ensure-release-cdb-file (dist)\n  (:documentation\n   \"Return the pathname for the release cdb file of DIST, creating it\n   if necessary.\"))\n\n\n(defgeneric initialize-release-index (dist)\n  (:documentation\n   \"Initialize the release index of DIST.\"))\n\n(defgeneric initialize-system-index (dist)\n  (:documentation\n   \"Initialize the system index of DIST.\"))\n\n\n(defgeneric local-archive-file (release)\n  (:documentation\n   \"Return the pathname to where the archive file of RELEASE should be\n   stored.\"))\n\n(defgeneric ensure-local-archive-file (release)\n  (:documentation\n   \"If the archive file for RELEASE is not available locally, fetch it\n   and return the pathname to it.\"))\n\n(defgeneric check-local-archive-file (release)\n  (:documentation\n   \"Check the local archive file of RELEASE for validity, including\n   size and signature checks. Signals errors in the case of invalid files.\"))\n\n\n(defgeneric archive-url (release)\n  (:documentation\n   \"Return the full URL for fetching the archive file of RELEASE.\"))\n\n(defgeneric installed-asdf-system-file (object)\n  (:documentation\n   \"Return the path to the installed ASDF system file for OBJECT, or\n   NIL if there is no installed system file.\"))\n\n\n\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defmacro destructure-line (lambda-list line &body body)\n    `(destructuring-bind ,lambda-list\n         (split-spaces ,line)\n       ,@body))\n\n  (defun call-for-each-line (fun file)\n    (with-open-file (stream file)\n      (loop for line = (read-line stream nil)\n            while line do (funcall fun line))))\n\n  (defmacro for-each-line ((line file) &body body)\n    `(call-for-each-line (lambda (,line) ,@body) ,file)))\n\n(defun make-line-instance (line class &rest initargs)\n  \"Create an instance from words in an index file line. The last initarg collects all the trailing arguments, if any.\"\n  (let* ((words (split-spaces line))\n         (args (mapcan #'list\n                       (butlast initargs)\n                       words))\n         (trailing (subseq words (1- (length initargs)))))\n    (apply #'make-instance class (first (last initargs)) trailing args)))\n\n(defun ignorable-line (line)\n  (labels ((blank-char-p (char)\n             (member char '(#\\Space #\\Tab)))\n           (blankp (line)\n             (every #'blank-char-p line))\n           (ignorable (line)\n             (or (zerop (length line))\n                 (blankp line)\n                 (eql (char line 0) #\\#))))\n    (ignorable line)))\n\n(defvar *initarg-case-converter*\n  (cond ((string= :string \"string\")\n         #'string-downcase)\n        ((string= :string \"STRING\")\n         #'string-upcase)))\n\n(defun config-file-initargs (file)\n  (flet ((initarg-keyword (string)\n           ;; A concession to mlisp\n           (intern (funcall *initarg-case-converter* string)\n                   'keyword)))\n    (let ((initargs '()))\n      (for-each-line (line file)\n        (unless (ignorable-line line)\n          (destructure-line (initarg value)\n              line\n            (let ((keyword (initarg-keyword (string-right-trim \":\" initarg))))\n              (push value initargs)\n              (push keyword initargs)))))\n      initargs)))\n\n;;;\n;;; A few generic things\n;;;\n\n(defmethod dist ((name symbol))\n  (dist (string name)))\n\n(defmethod dist ((name string))\n  (find-dist (string-downcase name)))\n\n(defmethod release ((name symbol))\n  (release (string name)))\n\n(defmethod release ((name string))\n  (find-release (string-downcase name)))\n\n(defmethod system ((name symbol))\n  (system (string name)))\n\n(defmethod system ((name string))\n  (find-system (string-downcase name)))\n\n;;;\n;;; Dists\n;;;\n;;; A dist is a set of releases.\n;;;\n\n(defclass dist ()\n  ((base-directory\n    :initarg :base-directory\n    :accessor base-directory)\n   (name\n    :initarg :name\n    :accessor name)\n   (version\n    :initarg :version\n    :accessor version)\n   (system-index-url\n    :initarg :system-index-url\n    :accessor system-index-url)\n   (release-index-url\n    :initarg :release-index-url\n    :accessor release-index-url)\n   (available-versions-url\n    :initarg :available-versions-url\n    :accessor available-versions-url)\n   (archive-base-url\n    :initarg :archive-base-url\n    :accessor archive-base-url)\n   (canonical-distinfo-url\n    :initarg :canonical-distinfo-url\n    :accessor canonical-distinfo-url)\n   (distinfo-subscription-url\n    :initarg :distinfo-subscription-url\n    :accessor distinfo-subscription-url)\n   (system-index\n    :initarg :system-index\n    :accessor system-index)\n   (release-index\n    :initarg :release-index\n    :accessor release-index)\n   (provided-systems\n    :initarg :provided-systems\n    :accessor provided-systems)\n   (provided-releases\n    :initarg :provided-releases\n    :accessor provided-releases)\n   (local-distinfo-file\n    :initarg :local-distinfo-file\n    :accessor local-distinfo-file))\n  (:default-initargs\n   :name \"unnamed\"\n   :version \"unknown\"\n   :distinfo-subscription-url nil))\n\n(defmethod short-description ((dist dist))\n  (format nil \"~A ~A\" (name dist) (version dist)))\n\n(defmethod print-object ((dist dist) stream)\n  (print-unreadable-object (dist stream :type t)\n    (write-string (short-description dist) stream)))\n\n(defun cdb-lookup (dist key cdb)\n  (ql-cdb:lookup key\n                 (relative-to dist cdb)))\n\n(defmethod slot-unbound (class (dist dist) (slot (eql 'available-versions-url)))\n  (declare (ignore class))\n  (setf (available-versions-url dist)\n        (make-versions-url (distinfo-subscription-url dist))))\n\n\n(defmethod ensure-system-index-file ((dist dist))\n  (let ((pathname (relative-to dist \"systems.txt\")))\n    (or (probe-file pathname)\n        (nth-value 1 (fetch (system-index-url dist) pathname)))))\n\n(defmethod ensure-system-cdb-file ((dist dist))\n  (let* ((system-file (ensure-system-index-file dist))\n         (cdb-file (make-pathname :type \"cdb\" :defaults system-file)))\n    (or (probe-file cdb-file)\n        (ql-cdb:convert-index-file system-file\n                                   :cdb-file cdb-file\n                                   :index 2))))\n\n(defmethod ensure-release-index-file ((dist dist))\n  (let ((pathname (relative-to dist \"releases.txt\")))\n    (or (probe-file pathname)\n        (nth-value 1 (fetch (release-index-url dist) pathname)))))\n\n(defmethod ensure-release-cdb-file ((dist dist))\n  (let* ((release-file (ensure-release-index-file dist))\n         (cdb-file (make-pathname :type \"cdb\" :defaults release-file)))\n    (or (probe-file cdb-file)\n        (ql-cdb:convert-index-file release-file\n                                   :cdb-file cdb-file\n                                   :index 0))))\n\n(defmethod slot-unbound (class (dist dist) (slot (eql 'provided-systems)))\n  (declare (ignore class))\n  (initialize-system-index dist)\n  (setf (slot-value dist 'provided-systems)\n        (loop for system being each hash-value of (system-index dist)\n              collect system)))\n\n(defmethod slot-unbound (class (dist dist) (slot (eql 'provided-releases)))\n  (declare (ignore class))\n  (initialize-release-index dist)\n  (setf (slot-value dist 'provided-releases)\n        (loop for system being each hash-value of (release-index dist)\n              collect system)))\n\n\n(defun dist-name-pathname (name)\n  \"Return the pathname that would be used for an installed dist with\nthe given NAME.\"\n  (qmerge (make-pathname :directory (list :relative \"dists\" name))))\n\n(defmethod slot-unbound (class (dist dist) (slot (eql 'base-directory)))\n  (declare (ignore class))\n  (setf (base-directory dist) (dist-name-pathname (name dist))))\n\n(defun make-dist-from-file (file &key (class 'dist))\n  \"Load dist info from FILE and use it to create a dist instance.\"\n  (let ((initargs (config-file-initargs file)))\n    (apply #'make-instance class\n           :local-distinfo-file file\n           :allow-other-keys t\n           initargs)))\n\n(defmethod install-metadata-file ((dist dist))\n  (relative-to dist \"distinfo.txt\"))\n\n(defun find-dist (name)\n  (find name (all-dists)\n        :key #'name\n        :test #'string=))\n\n(defmethod enabledp ((dist dist))\n  (not (not (probe-file (relative-to dist \"enabled.txt\")))))\n\n(defmethod enable ((dist dist))\n  (ensure-file-exists (relative-to dist \"enabled.txt\"))\n  t)\n\n(defmethod disable ((dist dist))\n  (delete-file-if-exists (relative-to dist \"enabled.txt\"))\n  t)\n\n(defmethod installedp ((dist dist))\n  (let ((installed (find-dist (name dist))))\n    (equalp (version installed) (version dist))))\n\n(defmethod uninstall ((dist dist))\n  (when (installedp dist)\n    (dolist (system (provided-systems dist))\n      (asdf:clear-system (name system)))\n    (ql-impl-util:delete-directory-tree (base-directory dist))\n    t))\n\n\n(defun make-release-from-line (line dist)\n  (let ((release\n         (make-line-instance line 'release\n                             :project-name\n                             :archive-url\n                             :archive-size\n                             :archive-md5\n                             :archive-content-sha1\n                             :prefix\n                             :system-files)))\n    (setf (dist release) dist)\n    (setf (archive-size release)\n          (parse-integer (archive-size release)))\n    release))\n\n(defmethod find-release-in-dist (release-name (dist dist))\n  (let* ((index (release-index dist))\n         (release (gethash release-name index)))\n    (or release\n        (let ((line (cdb-lookup dist release-name\n                                (ensure-release-cdb-file dist))))\n          (when line\n            (setf (gethash release-name index)\n                  (make-release-from-line line dist)))))))\n\n\n(defparameter *dist-enumeration-functions*\n  '(standard-dist-enumeration-function)\n  \"ALL-DISTS calls each function in this list with no arguments, and\n  appends the results into a list of dist objects, removing\n  duplicates. Functions might be called just once for a batch of\n  related operations; see WITH-CONSISTENT-DISTS.\")\n\n(defun standard-dist-enumeration-function ()\n  \"The default function used for producing a list of dist objects.\"\n  (loop for file in (directory (qmerge \"dists/*/distinfo.txt\"))\n        collect (make-dist-from-file file)))\n\n(defun all-dists ()\n  \"Return a list of all known dists.\"\n  (remove-duplicates\n   (apply 'append (mapcar 'funcall *dist-enumeration-functions*))))\n\n(defun enabled-dists ()\n  \"Return a list of all known dists for which ENABLEDP returns true.\"\n  (remove-if-not #'enabledp (all-dists)))\n\n\n(defmethod install-metadata-file (object)\n  (relative-to (dist object)\n               (make-pathname :directory\n                              (list :relative \"installed\"\n                                    (metadata-name object))\n                              :name (name object)\n                              :type \"txt\")))\n\n\n(defclass preference-mixin () ()\n  (:documentation\n   \"Instances of this class have a special location for their\n   preference files.\"))\n\n(defgeneric filesystem-name (object)\n  (:method (object)\n    ;; This is to work around system names like \"foo/bar\".\n    (let* ((name (name object))\n           (slash (position #\\/ name)))\n      (if slash\n          (subseq name 0 slash)\n          name))))\n\n(defmethod preference-file ((object preference-mixin))\n  (relative-to\n   (dist object)\n   (make-pathname :directory (list :relative\n                                   \"preferences\"\n                                   (metadata-name object))\n                              :name (filesystem-name object)\n                              :type \"txt\")))\n\n(defmethod distinfo-subscription-url :around ((dist dist))\n  (unless (subscription-inhibited-p dist)\n    (call-next-method)))\n\n(defmethod subscribedp ((dist dist))\n  (distinfo-subscription-url dist))\n\n;;;\n;;; Releases\n;;;\n\n(defclass release (preference-mixin)\n  ((project-name\n    :initarg :project-name\n    :accessor name\n    :accessor project-name)\n   (dist\n    :initarg :dist\n    :accessor dist\n    :reader preference-parent)\n   (provided-systems\n    :initarg :provided-systems\n    :accessor provided-systems)\n   (archive-url\n    :initarg :archive-url\n    :accessor archive-url)\n   (archive-size\n    :initarg :archive-size\n    :accessor archive-size)\n   (archive-md5\n    :initarg :archive-md5\n    :accessor archive-md5)\n   (archive-content-sha1\n    :initarg :archive-content-sha1\n    :accessor archive-content-sha1)\n   (prefix\n    :initarg :prefix\n    :accessor prefix\n    :reader short-description)\n   (system-files\n    :initarg :system-files\n    :accessor system-files)\n   (metadata-name\n    :initarg :metadata-name\n    :accessor metadata-name))\n  (:default-initargs\n   :metadata-name \"releases\")\n  (:documentation\n   \"Instances of this class represent a snapshot of a project at some\n   point in time, which might be from version control, or from an\n   official release, or from some other source.\"))\n\n(defmethod print-object ((release release) stream)\n  (print-unreadable-object (release stream :type t)\n    (format stream \"~A / ~A\"\n            (short-description release)\n            (short-description (dist release)))))\n\n(define-condition invalid-local-archive (error)\n  ((release\n    :initarg :release\n    :reader invalid-local-archive-release)\n   (file\n    :initarg :file\n    :reader invalid-local-archive-file))\n  (:report\n   (lambda (condition stream)\n     (format stream \"The archive file ~S for release ~S is invalid\"\n             (file-namestring (invalid-local-archive-file condition))\n             (name (invalid-local-archive-release condition))))))\n\n(define-condition missing-local-archive (invalid-local-archive)\n  ()\n  (:report\n   (lambda (condition stream)\n     (format stream \"The archive file ~S for release ~S is missing\"\n             (file-namestring (invalid-local-archive-file condition))\n             (name (invalid-local-archive-release condition))))))\n\n(define-condition badly-sized-local-archive (invalid-local-archive)\n  ((expected-size\n    :initarg :expected-size\n    :reader badly-sized-local-archive-expected-size)\n   (actual-size\n    :initarg :actual-size\n    :reader badly-sized-local-archive-actual-size))\n  (:report\n   (lambda (condition stream)\n     (format stream \"The archive file ~S for ~S is the wrong size: ~\n                     expected ~:D, got ~:D\"\n             (file-namestring (invalid-local-archive-file condition))\n             (name (invalid-local-archive-release condition))\n             (badly-sized-local-archive-expected-size condition)\n             (badly-sized-local-archive-actual-size condition)))))\n\n(defmethod check-local-archive-file ((release release))\n  (let ((file (local-archive-file release)))\n    (unless (probe-file file)\n      (error 'missing-local-archive\n             :file file\n             :release release))\n    (let ((actual-size (file-size file))\n          (expected-size (archive-size release)))\n      (unless (= actual-size expected-size)\n        (error 'badly-sized-local-archive\n               :file file\n               :release release\n               :actual-size actual-size\n               :expected-size expected-size)))))\n\n(defmethod local-archive-file ((release release))\n  (relative-to (dist release)\n               (make-pathname :directory '(:relative \"archives\")\n                              :defaults (file-namestring\n                                         (path (url (archive-url release)))))))\n\n(defmethod ensure-local-archive-file ((release release))\n  (let ((pathname (local-archive-file release)))\n    (tagbody\n     :retry\n       (or (probe-file pathname)\n           (progn\n             (ensure-directories-exist pathname)\n             (fetch (archive-url release) pathname)))\n       (restart-case\n           (check-local-archive-file release)\n         (delete-and-retry (&optional v)\n           :report \"Delete the archive file and fetch it again\"\n           (declare (ignore v))\n           (delete-file pathname)\n           (go :retry))))\n    pathname))\n\n\n(defmethod base-directory ((release release))\n  (relative-to\n   (dist release)\n   (make-pathname :directory (list :relative \"software\" (prefix release)))))\n\n(defmethod installedp ((release release))\n  (and (probe-file (install-metadata-file release))\n       (every #'installedp (provided-systems release))))\n\n(defmethod install ((release release))\n  (let ((archive (ensure-local-archive-file release))\n        (output (relative-to (dist release)\n                             (make-pathname :directory\n                                            (list :relative \"software\"))))\n        (tracking (install-metadata-file release)))\n    (with-temporary-file (tar \"release-install.tar\")\n      (ensure-directories-exist tar)\n      (ensure-directories-exist output)\n      (ensure-directories-exist tracking)\n      (gunzip archive tar)\n      (unpack-tarball tar :directory output))\n    (ensure-directories-exist tracking)\n    (with-open-file (stream tracking\n                            :direction :output\n                            :if-exists :supersede)\n      (write-line (qenough (base-directory release)) stream))\n    (let ((provided (provided-systems release))\n          (dist (dist release)))\n      (dolist (file (system-files release))\n        (let ((system (find-system-in-dist (pathname-name file) dist)))\n          (unless (member system provided)\n            (error \"FIND-SYSTEM-IN-DIST returned ~A but I expected one of ~A\"\n                   system provided))\n          (let ((system-tracking (install-metadata-file system))\n                (system-file (merge-pathnames file\n                                              (base-directory release))))\n            (ensure-directories-exist system-tracking)\n            (unless (probe-file system-file)\n              (error \"Release claims to have ~A, but I can't find it\"\n                     system-file))\n            (with-open-file (stream system-tracking\n                                    :direction :output\n                                    :if-exists :supersede)\n              (write-line (qenough system-file)\n                          stream))))))\n    release))\n\n(defmethod uninstall ((release release))\n  (when (installedp release)\n    (dolist (system (installed-systems release))\n      (asdf:clear-system (name system))\n      (delete-file (install-metadata-file system)))\n    (delete-file (install-metadata-file release))\n    (delete-file (local-archive-file release))\n    (ql-impl-util:delete-directory-tree (base-directory release))\n    t))\n\n\n(defun call-for-each-index-entry (file fun)\n  (labels ((blank-char-p (char)\n             (member char '(#\\Space #\\Tab)))\n           (blankp (line)\n             (every #'blank-char-p line))\n           (ignorable (line)\n             (or (zerop (length line))\n                 (blankp line)\n                 (eql (char line 0) #\\#))))\n    (with-open-file (stream file)\n      (loop for line = (read-line stream nil)\n            while line do\n            (unless (ignorable line)\n              (funcall fun line))))))\n\n(defmethod slot-unbound (class (dist dist) (slot (eql 'release-index)))\n  (declare (ignore class))\n  (setf (slot-value dist 'release-index)\n        (make-hash-table :test 'equal)))\n\n\n;;;\n;;; Systems\n;;;\n;;; A \"system\" in the defsystem sense.\n;;;\n\n(defclass system (preference-mixin)\n  ((name\n    :initarg :name\n    :accessor name\n    :reader short-description)\n   (system-file-name\n    :initarg :system-file-name\n    :accessor system-file-name)\n   (release\n    :initarg :release\n    :accessor release\n    :reader preference-parent)\n   (dist\n    :initarg :dist\n    :accessor dist)\n   (required-systems\n    :initarg :required-systems\n    :accessor required-systems)\n   (metadata-name\n    :initarg :metadata-name\n    :accessor metadata-name))\n  (:default-initargs\n   :metadata-name \"systems\"))\n\n(defmethod print-object ((system system) stream)\n  (print-unreadable-object (system stream :type t)\n    (format stream \"~A / ~A / ~A\"\n            (short-description system)\n            (short-description (release system))\n            (short-description (dist system)))))\n\n(defmethod provided-systems ((system system))\n  (list system))\n\n(defmethod initialize-release-index ((dist dist))\n  (let ((releases (ensure-release-index-file dist))\n        (index (release-index dist)))\n    (call-for-each-index-entry\n     releases\n     (lambda (line)\n       (let ((instance (make-line-instance line 'release\n                                           :project-name\n                                           :archive-url\n                                           :archive-size\n                                           :archive-md5\n                                           :archive-content-sha1\n                                           :prefix\n                                           :system-files)))\n         ;; Don't clobber anything previously loaded via CDB\n         (unless (gethash (project-name instance) index)\n           (setf (dist instance) dist)\n           (setf (archive-size instance)\n                 (parse-integer (archive-size instance)))\n           (setf (gethash (project-name instance) index) instance)))))\n    (setf (release-index dist) index)))\n\n(defmethod initialize-system-index ((dist dist))\n  (initialize-release-index dist)\n  (let ((systems (ensure-system-index-file dist))\n        (index (system-index dist)))\n    (call-for-each-index-entry\n     systems\n     (lambda (line)\n       (let ((instance (make-line-instance line 'system\n                                           :release\n                                           :system-file-name\n                                           :name\n                                           :required-systems)))\n         ;; Don't clobber anything previously loaded via CDB\n         (unless (gethash (name instance) index)\n           (let ((release (find-release-in-dist (release instance) dist)))\n             (setf (release instance) release)\n             (if (slot-boundp release 'provided-systems)\n                 (pushnew instance (provided-systems release))\n                 (setf (provided-systems release) (list instance))))\n           (setf (dist instance) dist)\n           (setf (gethash (name instance) index) instance)))))\n    (setf (system-index dist) index)))\n\n(defmethod slot-unbound (class (release release) (slot (eql 'provided-systems)))\n  (declare (ignore class))\n  ;; FIXME: This isn't right, since the system index has systems that\n  ;; don't match the defining system file name.\n  (setf (slot-value release 'provided-systems)\n        (mapcar (lambda (system-file)\n                  (find-system-in-dist (pathname-name system-file)\n                                       (dist release)))\n                (system-files release))))\n\n(defmethod slot-unbound (class (dist dist) (slot (eql 'system-index)))\n  (declare (ignore class))\n  (setf (slot-value dist 'system-index)\n        (make-hash-table :test 'equal)))\n\n(defun make-system-from-line (line dist)\n  (let ((system (make-line-instance line 'system\n                                    :release\n                                    :system-file-name\n                                    :name\n                                    :required-systems)))\n    (setf (dist system) dist)\n    (setf (release system)\n          (find-release-in-dist (release system) dist))\n    system))\n\n(defmethod find-system-in-dist (system-name (dist dist))\n  (let* ((index (system-index dist))\n         (system (gethash system-name index)))\n    (or system\n        (let ((line (cdb-lookup dist system-name\n                                (ensure-system-cdb-file dist))))\n          (when line\n            (setf (gethash system-name index)\n                  (make-system-from-line line dist)))))))\n\n(defmethod preference ((system system))\n  (if (probe-file (preference-file system))\n      (call-next-method)\n      (preference (release system))))\n\n(defun thing-name-designator (designator)\n  \"Convert DESIGNATOR to a string naming a thing. Strings are used\n  as-is, symbols are converted to their downcased symbol-name.\"\n  (typecase designator\n    (string designator)\n    (symbol (string-downcase designator))\n    (t\n     (error \"~S is not a valid designator for a system or release\"\n            designator))))\n\n(defun find-thing-named (find-fun name)\n  (setf name (thing-name-designator name))\n  (let ((result '()))\n    (dolist (dist (enabled-dists) (sort result #'> :key #'preference))\n      (let ((thing (funcall find-fun name dist)))\n        (when thing\n          (push thing result))))))\n\n(defmethod find-systems-named (name)\n  (find-thing-named #'find-system-in-dist name))\n\n(defmethod find-releases-named (name)\n  (find-thing-named #'find-release-in-dist name))\n\n(defmethod find-system (name)\n  (first (find-systems-named name)))\n\n(defmethod find-release (name)\n  (first (find-releases-named name)))\n\n(defmethod install ((system system))\n  (ensure-installed (release system)))\n\n\n(defmethod install-metadata-file ((system system))\n  (relative-to (dist system)\n               (make-pathname :name (system-file-name system)\n                              :type \"txt\"\n                              :directory '(:relative \"installed\" \"systems\"))))\n\n(defmethod installed-asdf-system-file ((system system))\n  (let ((metadata-file (install-metadata-file system)))\n    (when (probe-file metadata-file)\n      (with-open-file (stream metadata-file)\n        (let* ((relative (read-line stream))\n               (full (qmerge relative)))\n          (when (probe-file full)\n            full))))))\n\n(defmethod installedp ((system system))\n  (installed-asdf-system-file system))\n\n(defmethod uninstall ((system system))\n  (uninstall (release system)))\n\n(defun find-asdf-system-file (name)\n  \"Return the ASDF system file in which the system named NAME is defined.\"\n  (let ((system (find-system name)))\n    (when system\n      (installed-asdf-system-file system))))\n\n(defun system-definition-searcher (name)\n  \"Like FIND-ASDF-SYSTEM-FILE, but this function can be used in\nASDF:*SYSTEM-DEFINITION-SEARCH-FUNCTIONS*; it will only return system\nfile names if they match NAME.\"\n  (let ((system-file (find-asdf-system-file name)))\n    (when (and system-file\n               (string= (pathname-name system-file) name))\n      system-file)))\n\n(defun call-with-consistent-dists (fun)\n  \"Take a snapshot of the available dists and return the same list\nconsistently each time ALL-DISTS is called in the dynamic scope of\nFUN.\"\n  (let* ((all-dists (all-dists))\n         (*dist-enumeration-functions* (list (constantly all-dists))))\n    (funcall fun)))\n\n(defmacro with-consistent-dists (&body body)\n  \"See CALL-WITH-CONSISTENT-DISTS.\"\n  `(call-with-consistent-dists (lambda () ,@body)))\n\n\n(defgeneric dependency-tree (system)\n  (:method ((symbol symbol))\n    (dependency-tree (string-downcase symbol)))\n  (:method ((string string))\n    (let ((system (find-system string)))\n      (when system\n        (dependency-tree system))))\n  (:method ((system system))\n    (with-consistent-dists\n      (list* system\n             (remove nil\n                     (mapcar 'dependency-tree (required-systems system)))))))\n\n(defmethod provided-systems ((object (eql t)))\n  (let ((systems (loop for dist in (enabled-dists)\n                       appending (provided-systems dist))))\n    (sort systems #'string< :key #'name)))\n\n(defmethod provided-releases ((object (eql t)))\n  (let ((releases (loop for dist in (enabled-dists)\n                        appending (provided-releases dist))))\n    (sort releases #'string< :key #'name)))\n\n\n(defgeneric system-apropos-list (term)\n  (:method ((term symbol))\n    (system-apropos-list (symbol-name term)))\n  (:method ((term string))\n    (setf term (string-downcase term))\n    (let ((result '()))\n      (dolist (system (provided-systems t) (nreverse result))\n        (when (or (search term (name system))\n                  (search term (name (release system))))\n          (push system result))))))\n\n(defgeneric system-apropos (term)\n  (:method (term)\n    (map nil (lambda (system)\n               (format t \"~A~%\" system))\n         (system-apropos-list term))\n    (values)))\n\n\n;;;\n;;; Clean up things\n;;;\n\n(defgeneric clean (object)\n  (:documentation \"Remove any unneeded files or directories related to\n  OBJECT.\"))\n\n(defmethod clean ((dist dist))\n  (let* ((releases (provided-releases dist))\n         (known-archives (mapcar 'local-archive-file releases))\n         (known-directories (mapcar 'base-directory releases))\n         (present-archives (mapcar 'truename\n                                   (directory-entries\n                                    (relative-to dist \"archives/\"))))\n         (present-directories (mapcar 'truename\n                                      (directory-entries\n                                       (relative-to dist \"software/\"))))\n         (garbage-archives\n          (set-difference present-archives known-archives\n                          :test 'equalp))\n         (garbage-directories\n          ;; Use the namestring here on the theory that pathnames with\n          ;; equalp namestrings are sufficiently the same. On\n          ;; LispWorks, for example, identical namestrings can still\n          ;; differ in :name, :type, and more.\n          (set-difference present-directories known-directories\n                          :test 'equalp\n                          :key 'namestring)))\n    (map nil 'delete-file garbage-archives)\n    (map nil 'delete-directory-tree garbage-directories)))\n\n\n;;;\n;;; Available versions\n;;;\n\n(defmethod available-versions ((dist dist))\n  (let ((temp (qmerge \"tmp/dist-versions.txt\"))\n        (versions '())\n        (url (available-versions-url dist)))\n    (when url\n      (ensure-directories-exist temp)\n      (delete-file-if-exists temp)\n      (handler-case\n          (fetch url temp)\n        (unexpected-http-status ()\n          (return-from available-versions nil)))\n      (with-open-file (stream temp)\n        (loop for line = (read-line stream nil)\n              while line do\n              (destructuring-bind (version url)\n                  (split-spaces line)\n                (setf versions (acons version url versions)))))\n      versions)))\n\n\n;;;\n;;; User interface bits to re-export from QL\n;;;\n\n(define-condition unknown-dist (error)\n  ((name\n    :initarg :name\n    :reader unknown-dist-name))\n  (:report (lambda (condition stream)\n             (format stream \"No dist known by that name -- ~S\"\n                     (unknown-dist-name condition)))))\n\n(defun find-dist-or-lose (name)\n  (let ((dist (find-dist name)))\n    (or dist\n        (error 'unknown-dist :name name))))\n\n(defun dist-url (name)\n  (canonical-distinfo-url  (find-dist-or-lose name)))\n\n(defun dist-version (name)\n  (version (find-dist-or-lose name)))\n"
  },
  {
    "path": "quicklisp/quicklisp/fetch-gzipped.lisp",
    "content": ";;;; fetch-gzipped.lisp\n\n(in-package #:quicklisp-client)\n\n(defun gzipped-url (url)\n  (check-type url string)\n  (concatenate 'string url \".gz\"))\n\n(defun fetch-gzipped-version (url file &key quietly)\n  (let ((gzipped (gzipped-url url))\n        (gzipped-temp (merge-pathnames \"gzipped.tmp\" file)))\n    (fetch gzipped gzipped-temp :quietly quietly)\n    (gunzip gzipped-temp file)\n    (delete-file-if-exists gzipped-temp)\n    (probe-file file)))\n\n(defun url-not-suitable-error-p (condition)\n  (<= 400 (unexpected-http-status-code condition) 499))\n\n(defun maybe-fetch-gzipped (url file &key quietly)\n  (handler-case\n      (fetch-gzipped-version url file :quietly quietly)\n    (unexpected-http-status (condition)\n      (cond ((url-not-suitable-error-p condition)\n             (fetch url file :quietly quietly)\n             (probe-file file))\n            (t\n             (error condition))))))\n\n"
  },
  {
    "path": "quicklisp/quicklisp/http.lisp",
    "content": ";;;\n;;; A simple HTTP client\n;;;\n\n(in-package #:ql-http)\n\n;;; Octet data\n\n(deftype octet ()\n  '(unsigned-byte 8))\n\n(defun make-octet-vector (size)\n  (make-array size :element-type 'octet\n              :initial-element 0))\n\n(defun octet-vector (&rest octets)\n  (make-array (length octets) :element-type 'octet\n              :initial-contents octets))\n\n;;; ASCII characters as integers\n\n(defun acode (char)\n  (cond ((eql char :cr)\n         13)\n        ((eql char :lf)\n         10)\n        (t\n         (let ((code (char-code char)))\n           (if (<= 0 code 127)\n               code\n               (error \"Character ~S is not in the ASCII character set\"\n                      char))))))\n\n(defvar *whitespace*\n  (list (acode #\\Space) (acode #\\Tab) (acode :cr) (acode :lf)))\n\n(defun whitep (code)\n  (member code *whitespace*))\n\n(defun ascii-vector (string)\n  (let ((vector (make-octet-vector (length string))))\n    (loop for char across string\n          for code = (char-code char)\n          for i from 0\n          if (< 127 code) do\n          (error \"Invalid character for ASCII -- ~A\" char)\n          else\n          do (setf (aref vector i) code))\n    vector))\n\n(defun ascii-subseq (vector start end)\n  \"Return a subseq of octet-specialized VECTOR as a string.\"\n  (let ((string (make-string (- end start))))\n    (loop for i from 0\n          for j from start below end\n          do (setf (char string i) (code-char (aref vector j))))\n    string))\n\n(defun ascii-downcase (code)\n  (if (<= 65 code 90)\n      (+ code 32)\n      code))\n\n(defun ascii-equal (a b)\n  (eql (ascii-downcase a) (ascii-downcase b)))\n\n(defmacro acase (value &body cases)\n  (flet ((convert-case-keys (keys)\n           (mapcar (lambda (key)\n                     (etypecase key\n                       (integer key)\n                       (character (char-code key))\n                       (symbol\n                        (ecase key\n                          (:cr 13)\n                          (:lf 10)\n                          ((t) t)))))\n                   (if (consp keys) keys (list keys)))))\n    `(case ,value\n       ,@(mapcar (lambda (case)\n                   (destructuring-bind (keys &rest body)\n                       case\n                     `(,(if (eql keys t)\n                            t\n                            (convert-case-keys keys))\n                        ,@body)))\n                 cases))))\n\n;;; Pattern matching (for finding headers)\n\n(defclass matcher ()\n  ((pattern\n    :initarg :pattern\n    :reader pattern)\n   (pos\n    :initform 0\n    :accessor match-pos)\n   (matchedp\n    :initform nil\n    :accessor matchedp)))\n\n(defun reset-match (matcher)\n  (setf (match-pos matcher) 0\n        (matchedp matcher) nil))\n\n(define-condition match-failure (error) ())\n\n(defun match (matcher input &key (start 0) end error)\n  (let ((i start)\n        (end (or end (length input)))\n        (match-end (length (pattern matcher))))\n    (with-slots (pattern pos)\n        matcher\n      (loop\n       (cond ((= pos match-end)\n              (let ((match-start (- i pos)))\n                (setf pos 0)\n                (setf (matchedp matcher) t)\n                (return (values match-start (+ match-start match-end)))))\n             ((= i end)\n              (return nil))\n             ((= (aref pattern pos)\n                 (aref input i))\n              (incf i)\n              (incf pos))\n             (t\n              (if error\n                  (error 'match-failure)\n                  (if (zerop pos)\n                      (incf i)\n                      (setf pos 0)))))))))\n\n(defun ascii-matcher (string)\n  (make-instance 'matcher\n                 :pattern (ascii-vector string)))\n\n(defun octet-matcher (&rest octets)\n  (make-instance 'matcher\n                 :pattern (apply 'octet-vector octets)))\n\n(defun acode-matcher (&rest codes)\n  (make-instance 'matcher\n                 :pattern (make-array (length codes)\n                                      :element-type 'octet\n                                      :initial-contents\n                                      (mapcar 'acode codes))))\n\n\n;;; \"Connection Buffers\" are a kind of callback-driven,\n;;; pattern-matching chunky stream. Callbacks can be called for a\n;;; certain number of octets or until one or more patterns are seen in\n;;; the input. cbufs automatically refill themselves from a\n;;; connection as needed.\n\n(defvar *cbuf-buffer-size* 8192)\n\n(define-condition end-of-data (error) ())\n\n(defclass cbuf ()\n  ((data\n    :initarg :data\n    :accessor data)\n   (connection\n    :initarg :connection\n    :accessor connection)\n   (start\n    :initarg :start\n    :accessor start)\n   (end\n    :initarg :end\n    :accessor end)\n   (eofp\n    :initarg :eofp\n    :accessor eofp))\n  (:default-initargs\n   :data (make-octet-vector *cbuf-buffer-size*)\n   :connection nil\n   :start 0\n   :end 0\n   :eofp nil)\n  (:documentation \"A CBUF is a connection buffer that keeps track of\n  incoming data from a connection. Several functions make it easy to\n  treat a CBUF as a kind of chunky, callback-driven stream.\"))\n\n(define-condition cbuf-progress ()\n  ((size\n    :initarg :size\n    :accessor cbuf-progress-size\n    :initform 0)))\n\n(defun call-processor (fun cbuf start end)\n  (signal 'cbuf-progress :size (- end start))\n  (funcall fun (data cbuf) start end))\n\n(defun make-cbuf (connection)\n  (make-instance 'cbuf :connection connection))\n\n(defun make-stream-writer (stream)\n  \"Create a callback for writing data to STREAM.\"\n  (lambda (data start end)\n    (write-sequence data stream :start start :end end)))\n\n(defgeneric size (cbuf)\n  (:method ((cbuf cbuf))\n    (- (end cbuf) (start cbuf))))\n\n(defgeneric emptyp (cbuf)\n  (:method ((cbuf cbuf))\n    (zerop (size cbuf))))\n\n(defgeneric refill (cbuf)\n  (:method ((cbuf cbuf))\n    (when (eofp cbuf)\n      (error 'end-of-data))\n    (setf (start cbuf) 0)\n    (setf (end cbuf)\n          (read-octets (data cbuf)\n                       (connection cbuf)))\n    (cond ((emptyp cbuf)\n           (setf (eofp cbuf) t)\n           (error 'end-of-data))\n          (t (size cbuf)))))\n\n(defun process-all (fun cbuf)\n  (unless (emptyp cbuf)\n    (call-processor fun cbuf (start cbuf) (end cbuf))))\n\n(defun multi-cmatch (matchers cbuf)\n  (let (start end)\n    (dolist (matcher matchers (values start end))\n      (multiple-value-bind (s e)\n          (match matcher (data cbuf)\n                 :start (start cbuf)\n                 :end (end cbuf))\n        (when (and s (or (null start) (< s start)))\n          (setf start s\n                end e))))))\n\n(defun cmatch (matcher cbuf)\n  (if (consp matcher)\n      (multi-cmatch matcher cbuf)\n      (match matcher (data cbuf) :start (start cbuf) :end (end cbuf))))\n\n(defun call-until-end (fun cbuf)\n  (handler-case\n      (loop\n       (process-all fun cbuf)\n       (refill cbuf))\n    (end-of-data ()\n      (return-from call-until-end))))\n\n(defun show-cbuf (context cbuf)\n  (format t \"cbuf: ~A ~D - ~D~%\" context (start cbuf) (end cbuf)))\n\n(defun call-for-n-octets (n fun cbuf)\n  (let ((remaining n))\n    (loop\n     (when (<= remaining (size cbuf))\n       (let ((end (+ (start cbuf) remaining)))\n         (call-processor fun cbuf (start cbuf) end)\n         (setf (start cbuf) end)\n         (return)))\n     (process-all fun cbuf)\n     (decf remaining (size cbuf))\n     (refill cbuf))))\n\n(defun call-until-matching (matcher fun cbuf)\n  (loop\n   (multiple-value-bind (start end)\n       (cmatch matcher cbuf)\n     (when start\n       (call-processor fun cbuf (start cbuf) end)\n       (setf (start cbuf) end)\n       (return)))\n   (process-all fun cbuf)\n   (refill cbuf)))\n\n(defun ignore-data (data start end)\n  (declare (ignore data start end)))\n\n(defun skip-until-matching (matcher cbuf)\n  (call-until-matching matcher 'ignore-data cbuf))\n\n\n;;; Creating HTTP requests as octet buffers\n\n(defclass octet-sink ()\n  ((storage\n    :initarg :storage\n    :accessor storage))\n  (:default-initargs\n   :storage (make-array 1024 :element-type 'octet\n                        :fill-pointer 0\n                        :adjustable t))\n  (:documentation \"A simple stream-like target for collecting\n  octets.\"))\n\n(defun add-octet (octet sink)\n  (vector-push-extend octet (storage sink)))\n\n(defun add-octets (octets sink &key (start 0) end)\n  (setf end (or end (length octets)))\n  (loop for i from start below end\n        do (add-octet (aref octets i) sink)))\n\n(defun add-string (string sink)\n  (loop for char across string\n        for code = (char-code char)\n        do (add-octet code sink)))\n\n(defun add-strings (sink &rest strings)\n  (mapc (lambda (string) (add-string string sink)) strings))\n\n(defun add-newline (sink)\n  (add-octet 13 sink)\n  (add-octet 10 sink))\n\n(defun sink-buffer (sink)\n  (subseq (storage sink) 0))\n\n(defvar *proxy-url* (config-value \"proxy-url\"))\n\n(defun full-proxy-path (host port path)\n  (format nil \"~:[http~;https~]://~A~:[:~D~;~*~]~A\"\n          (eql port 443)\n          host\n          (or (null port)\n              (eql port 80)\n              (eql port 443))\n          port\n          path))\n\n(defun user-agent-string ()\n  \"Return a string suitable for using as the User-Agent value in HTTP\nrequests. Includes Quicklisp version and CL implementation and version\ninformation.\"\n  (labels ((requires-encoding (char)\n             (not (or (alphanumericp char)\n                      (member char '(#\\. #\\- #\\_)))))\n           (encode (string)\n             (substitute-if #\\_ #'requires-encoding string))\n           (version-string (string)\n             (if (string-equal string nil)\n                 \"unknown\"\n                 (let* ((length (length string))\n                        (start (or (position-if #'digit-char-p string)\n                                   0))\n                        (space (or (position #\\Space string :start start)\n                                   length))\n                        (limit (min space length (+ start 24))))\n                   (encode (subseq string start limit))))))\n    ;; FIXME: Be more configurable, and take/set the version from\n    ;; somewhere else.\n    (format nil \"quicklisp-client/~A ~A/~A\"\n            ql-info:*version*\n            (encode (lisp-implementation-type))\n            (version-string (lisp-implementation-version)))))\n\n(defun make-request-buffer (host port path &key (method \"GET\"))\n  \"Return an octet vector suitable for sending as an HTTP 1.1 request.\"\n  (setf method (string method))\n  (when *proxy-url*\n    (setf path (full-proxy-path host port path)))\n  (let ((sink (make-instance 'octet-sink)))\n    (flet ((add-line (&rest strings)\n             (apply #'add-strings sink strings)\n             (add-newline sink)))\n      (add-line method \" \" path \" HTTP/1.1\")\n      (add-line \"Host: \" host (if (integerp port)\n                                  (format nil \":~D\" port)\n                                  \"\"))\n      (add-line \"Connection: close\")\n      (add-line \"User-Agent: \" (user-agent-string))\n      (add-newline sink)\n      (sink-buffer sink))))\n\n(defun sink-until-matching (matcher cbuf)\n  (let ((sink (make-instance 'octet-sink)))\n    (call-until-matching\n     matcher\n     (lambda (buffer start end)\n       (add-octets buffer sink :start start :end end))\n     cbuf)\n    (sink-buffer sink)))\n\n\n;;; HTTP headers\n\n(defclass header ()\n  ((data\n    :initarg :data\n    :accessor data)\n   (status\n    :initarg :status\n    :accessor status)\n   (name-starts\n    :initarg :name-starts\n    :accessor name-starts)\n   (name-ends\n    :initarg :name-ends\n    :accessor name-ends)\n   (value-starts\n    :initarg :value-starts\n    :accessor value-starts)\n   (value-ends\n    :initarg :value-ends\n    :accessor value-ends)))\n\n(defmethod print-object ((header header) stream)\n  (print-unreadable-object (header stream :type t)\n    (prin1 (status header) stream)))\n\n(defun matches-at (pattern target pos)\n  (= (mismatch pattern target :start2 pos) (length pattern)))\n\n(defun header-value-indexes (field-name header)\n  (loop with data = (data header)\n        with pattern = (ascii-vector (string-downcase field-name))\n        for start across (name-starts header)\n        for i from 0\n        when (matches-at pattern data start)\n        return (values (aref (value-starts header) i)\n                       (aref (value-ends header) i))))\n\n(defun ascii-header-value (field-name header)\n  (multiple-value-bind (start end)\n      (header-value-indexes field-name header)\n    (when start\n      (ascii-subseq (data header) start end))))\n\n(defun all-field-names (header)\n  (map 'list\n       (lambda (start end)\n         (ascii-subseq (data header) start end))\n       (name-starts header)\n       (name-ends header)))\n\n(defun headers-alist (header)\n  (mapcar (lambda (name)\n            (cons name (ascii-header-value name header)))\n          (all-field-names header)))\n\n(defmethod describe-object :after ((header header) stream)\n  (format stream \"~&Decoded headers:~%  ~S~%\" (headers-alist header)))\n\n(defun content-length (header)\n  (let ((field-value (ascii-header-value \"content-length\" header)))\n    (when field-value\n      (let ((value (ignore-errors (parse-integer field-value))))\n        (or value\n            (error \"Content-Length header field value is not a number -- ~A\"\n                   field-value))))))\n\n(defun chunkedp (header)\n  (string= (ascii-header-value \"transfer-encoding\" header) \"chunked\"))\n\n(defun location (header)\n  (ascii-header-value \"location\" header))\n\n(defun status-code (vector)\n  (let* ((space (position (acode #\\Space) vector))\n         (c1 (- (aref vector (incf space)) 48))\n         (c2 (- (aref vector (incf space)) 48))\n         (c3 (- (aref vector (incf space)) 48)))\n    (+ (* c1 100)\n       (* c2  10)\n       (* c3   1))))\n\n(defun force-downcase-field-names (header)\n  (loop with data = (data header)\n        for start across (name-starts header)\n        for end across (name-ends header)\n        do (loop for i from start below end\n                 for code = (aref data i)\n                 do (setf (aref data i) (ascii-downcase code)))))\n\n(defun skip-white-forward (pos vector)\n  (position-if-not 'whitep vector :start pos))\n\n(defun skip-white-backward (pos vector)\n  (let ((nonwhite (position-if-not 'whitep vector :end pos :from-end t)))\n    (if nonwhite\n        (1+ nonwhite)\n        pos)))\n\n(defun contract-field-value-indexes (header)\n  \"Header field values exclude leading and trailing whitespace; adjust\nthe indexes in the header accordingly.\"\n  (loop with starts = (value-starts header)\n        with ends = (value-ends header)\n        with data = (data header)\n        for i from 0\n        for start across starts\n        for end across ends\n        do\n        (setf (aref starts i) (skip-white-forward start data))\n        (setf (aref ends i) (skip-white-backward end data))))\n\n(defun next-line-pos (vector)\n  (let ((pos 0))\n    (labels ((finish (&optional (i pos))\n               (return-from next-line-pos i))\n             (after-cr (code)\n               (acase code\n                 (:lf (finish pos))\n                 (t (finish (1- pos)))))\n             (pending (code)\n               (acase code\n                 (:cr #'after-cr)\n                 (:lf (finish pos))\n                 (t #'pending))))\n      (let ((state #'pending))\n        (loop\n         (setf state (funcall state (aref vector pos)))\n         (incf pos))))))\n\n(defun make-hvector ()\n  (make-array 16 :fill-pointer 0 :adjustable t))\n\n(defun process-header (vector)\n  \"Create a HEADER instance from the octet data in VECTOR.\"\n  (let* ((name-starts (make-hvector))\n         (name-ends (make-hvector))\n         (value-starts (make-hvector))\n         (value-ends (make-hvector))\n         (header (make-instance 'header\n                                :data vector\n                                :status 999\n                                :name-starts name-starts\n                                :name-ends name-ends\n                                :value-starts value-starts\n                                :value-ends value-ends))\n         (mark nil)\n         (pos (next-line-pos vector)))\n    (unless pos\n      (error \"Unable to process HTTP header\"))\n    (setf (status header) (status-code vector))\n    (labels ((save (value vector)\n               (vector-push-extend value vector))\n             (mark ()\n               (setf mark pos))\n             (clear-mark ()\n               (setf mark nil))\n             (finish ()\n               (if mark\n                   (save mark value-ends)\n                   (save pos value-ends))\n              (force-downcase-field-names header)\n              (contract-field-value-indexes header)\n              (return-from process-header header))\n             (in-new-line (code)\n               (acase code\n                 ((#\\Tab #\\Space) (setf mark nil) #'in-value)\n                 (t\n                  (when mark\n                    (save mark value-ends))\n                  (clear-mark)\n                  (save pos name-starts)\n                  (in-name code))))\n             (after-cr (code)\n               (acase code\n                 (:lf #'in-new-line)\n                 (t (in-new-line code))))\n             (in-name (code)\n               (acase code\n                 (#\\:\n                  (save pos name-ends)\n                  (save (1+ pos) value-starts)\n                  #'in-value)\n                 ((:cr :lf)\n                  (finish))\n                 ((#\\Tab #\\Space)\n                  (error \"Unexpected whitespace in header field name\"))\n                 (t\n                  (unless (<= 0 code 127)\n                    (error \"Unexpected non-ASCII header field name\"))\n                  #'in-name)))\n             (in-value (code)\n               (acase code\n                 (:lf (mark) #'in-new-line)\n                 (:cr (mark) #'after-cr)\n                 (t #'in-value))))\n      (let ((state #'in-new-line))\n        (loop\n         (incf pos)\n         (when (<= (length vector) pos)\n           (error \"No header found in response\"))\n         (setf state (funcall state (aref vector pos))))))))\n\n\n;;; HTTP URL parsing\n\n(defclass url ()\n  ((scheme\n    :initarg :scheme\n    :accessor scheme\n    :initform nil)\n   (hostname\n    :initarg :hostname\n    :accessor hostname\n    :initform nil)\n   (port\n    :initarg :port\n    :accessor port\n    :initform nil)\n   (path\n    :initarg :path\n    :accessor path\n    :initform \"/\")))\n\n(defun parse-urlstring (urlstring)\n  (setf urlstring (string-trim \" \" urlstring))\n  (let* ((pos (position #\\: urlstring))\n         (scheme (or (and pos (subseq  urlstring 0 pos)) \"http\"))\n         (pos (mismatch urlstring \"://\" :test 'char-equal :start1 pos))\n         (mark pos)\n         (url (make-instance 'url)))\n    (setf (scheme url) scheme)\n    (labels ((save ()\n               (subseq urlstring mark pos))\n             (mark ()\n               (setf mark pos))\n             (finish ()\n               (return-from parse-urlstring url))\n             (hostname-char-p (char)\n               (position char \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.\"\n                         :test 'char-equal))\n             (at-start (char)\n               (case char\n                 (#\\/\n                  (setf (port url) nil)\n                  (mark)\n                  #'in-path)\n                 (t\n                  #'in-host)))\n             (in-host (char)\n               (case char\n                 ((#\\/ :end)\n                  (setf (hostname url) (save))\n                  (mark)\n                  #'in-path)\n                 (#\\:\n                  (setf (hostname url) (save))\n                  (mark)\n                  #'in-port)\n                 (t\n                  (unless (hostname-char-p char)\n                    (error \"~S is not a valid URL\" urlstring))\n                  #'in-host)))\n             (in-port (char)\n               (case char\n                 ((#\\/ :end)\n                  (setf (port url)\n                        (parse-integer urlstring\n                                       :start (1+ mark)\n                                       :end pos))\n                  (mark)\n                  #'in-path)\n                 (t\n                  (unless (digit-char-p char)\n                    (error \"Bad port in URL ~S\" urlstring))\n                  #'in-port)))\n             (in-path (char)\n               (case char\n                 ((#\\# :end)\n                  (setf (path url) (save))\n                  (finish)))\n               #'in-path))\n      (let ((state #'at-start))\n        (loop\n         (when (<= (length urlstring) pos)\n           (funcall state :end)\n           (finish))\n         (setf state (funcall state (aref urlstring pos)))\n         (incf pos))))))\n\n(defun url (thing)\n  (if (stringp thing)\n      (parse-urlstring thing)\n      thing))\n\n(defgeneric request-buffer (method url)\n  (:method (method url)\n    (setf url (url url))\n    (make-request-buffer (hostname url) (or (port url) 80) (path url)\n                         :method method)))\n\n(defun urlstring (url)\n  (format nil \"~@[~A://~]~@[~A~]~@[:~D~]~A\"\n          (and (hostname url) (scheme url))\n          (hostname url)\n          (port url)\n          (path url)))\n\n(defmethod print-object ((url url) stream)\n  (print-unreadable-object (url stream :type t)\n    (prin1 (urlstring url) stream)))\n\n(defun merge-urls (url1 url2)\n  (setf url1 (url url1))\n  (setf url2 (url url2))\n  (make-instance 'url\n                 :scheme (or (scheme url1)\n                             (scheme url2))\n                 :hostname (or (hostname url1)\n                               (hostname url2))\n                 :port (or (port url1)\n                           (port url2))\n                 :path (or (path url1)\n                           (path url2))))\n\n\n;;; Requesting an URL and saving it to a file\n\n(defparameter *maximum-redirects* 10)\n(defvar *default-url-defaults* (url \"http://src.quicklisp.org/\"))\n\n(defun read-http-header (cbuf)\n  (let ((header-data (sink-until-matching (list (acode-matcher :lf :lf)\n                                                (acode-matcher :cr :cr)\n                                                (acode-matcher :cr :lf :cr :lf))\n                                 cbuf)))\n    (process-header header-data)))\n\n(defun read-chunk-header (cbuf)\n  (let* ((header-data (sink-until-matching (acode-matcher :cr :lf) cbuf))\n         (end (or (position (acode :cr) header-data)\n                  (position (acode #\\;) header-data))))\n    (values (parse-integer (ascii-subseq header-data 0 end) :radix 16))))\n\n(defun save-chunk-response (stream cbuf)\n  \"For a chunked response, read all chunks and write them to STREAM.\"\n  (let ((fun (make-stream-writer stream))\n        (matcher (acode-matcher :cr :lf)))\n    (loop\n     (let ((chunk-size (read-chunk-header cbuf)))\n       (when (zerop chunk-size)\n         (return))\n       (call-for-n-octets chunk-size fun cbuf)\n       (skip-until-matching matcher cbuf)))))\n\n(defun save-response (file header cbuf &key (if-exists :rename-and-delete))\n  (with-open-file (stream file\n                          :direction :output\n                          :if-exists if-exists\n                          :element-type 'octet)\n    (let ((content-length (content-length header)))\n      (cond ((chunkedp header)\n             (save-chunk-response stream cbuf))\n            (content-length\n             (call-for-n-octets content-length\n                                (make-stream-writer stream)\n                                cbuf))\n            (t\n             (call-until-end (make-stream-writer stream) cbuf))))))\n\n(defun call-with-progress-bar (size fun)\n  (let ((progress-bar (make-progress-bar size)))\n    (start-display progress-bar)\n    (flet ((update (condition)\n             (update-progress progress-bar\n                              (cbuf-progress-size condition))))\n      (handler-bind ((cbuf-progress #'update))\n        (funcall fun)))\n    (finish-display progress-bar)))\n\n(define-condition fetch-error (error) ())\n\n(define-condition unexpected-http-status (fetch-error)\n  ((status-code\n    :initarg :status-code\n    :reader unexpected-http-status-code)\n   (url\n    :initarg :url\n    :reader unexpected-http-status-url))\n  (:report\n   (lambda (condition stream)\n     (format stream \"Unexpected HTTP status for ~A: ~A\"\n             (unexpected-http-status-url condition)\n             (unexpected-http-status-code condition)))))\n\n(define-condition too-many-redirects (fetch-error)\n  ((url\n    :initarg :url\n    :reader too-many-redirects-url)\n   (redirect-count\n    :initarg :redirect-count\n    :reader too-many-redirects-count))\n  (:report\n   (lambda (condition stream)\n     (format stream \"Too many redirects (~:D) for ~A\"\n             (too-many-redirects-count condition)\n             (too-many-redirects-url condition)))))\n\n(defvar *fetch-scheme-functions*\n  '((\"http\" . http-fetch))\n  \"assoc list to decide which scheme-function are called by FETCH function.\")\n\n(defun fetch (url file &rest rest)\n  \"Request URL and write the body of the response to FILE.\"\n  (let* ((url (merge-urls url *default-url-defaults*))\n         (call (cdr (assoc (scheme url) *fetch-scheme-functions* :test 'equal))))\n    (if call\n        (apply call (urlstring url) file rest)\n        (error \"Unknown scheme ~S\" url))))\n\n(defun http-fetch (url file &key (follow-redirects t) quietly\n              (if-exists :rename-and-delete)\n              (maximum-redirects *maximum-redirects*))\n  \"default scheme-function for http protocol.\"\n  (setf url (merge-urls url *default-url-defaults*))\n  (setf file (merge-pathnames file))\n  (let ((redirect-count 0)\n        (original-url url)\n        (connect-url (or (url *proxy-url*) url))\n        (stream (if quietly\n                    (make-broadcast-stream)\n                    *trace-output*)))\n    (loop\n     (when (<= maximum-redirects redirect-count)\n       (error 'too-many-redirects\n              :url original-url\n              :redirect-count redirect-count))\n     (with-connection (connection (hostname connect-url) (or (port connect-url) 80))\n       (let ((cbuf (make-instance 'cbuf :connection connection))\n             (request (request-buffer \"GET\" url)))\n         (write-octets request connection)\n         (let ((header (read-http-header cbuf)))\n           (loop while (= (status header) 100)\n                 do (setf header (read-http-header cbuf)))\n           (cond ((= (status header) 200)\n                  (let ((size (content-length header)))\n                    (format stream \"~&; Fetching ~A~%\" url)\n                    (if (and (numberp size)\n                             (plusp size))\n                        (format stream \"; ~$KB~%\" (/ size 1024))\n                        (format stream \"; Unknown size~%\"))\n                    (if quietly\n                        (save-response file header cbuf\n                                       :if-exists if-exists)\n                        (call-with-progress-bar\n                         (content-length header)\n                         (lambda ()\n                           (save-response file header cbuf\n                                          :if-exists if-exists))))))\n                 ((not (<= 300 (status header) 399))\n                  (error 'unexpected-http-status\n                         :url url\n                         :status-code (status header))))\n           (if (and follow-redirects (<= 300 (status header) 399))\n               (let ((new-urlstring (ascii-header-value \"location\" header)))\n                 (when (not new-urlstring)\n                   (error \"Redirect code ~D received, but no Location: header\"\n                          (status header)))\n                 (incf redirect-count)\n                 (setf url (merge-urls new-urlstring\n                                       url))\n                 (format stream \"~&; Redirecting to ~A~%\" url))\n               (return (values header (and file (probe-file file)))))))))))\n"
  },
  {
    "path": "quicklisp/quicklisp/impl-util.lisp",
    "content": ";;;; impl-util.lisp\n\n(in-package #:ql-impl-util)\n\n(definterface call-with-quiet-compilation (fun)\n  (:documentation\n   \"Call FUN with warnings, style-warnings, and other verbose messages\n   suppressed.\")\n  (:implementation t\n    (let ((*load-verbose* nil)\n          (*compile-verbose* nil)\n          (*load-print* nil)\n          (*compile-print* nil))\n      (handler-bind ((warning #'muffle-warning))\n        (funcall fun)))))\n\n(defimplementation (call-with-quiet-compilation :for sbcl :qualifier :around)\n    (fun)\n  (declare (ignore fun))\n  (handler-bind ((ql-sbcl:compiler-note #'muffle-warning))\n    (call-next-method)))\n\n(defimplementation (call-with-quiet-compilation :for cmucl :qualifier :around)\n    (fun)\n  (declare (ignore fun))\n  (let ((ql-cmucl:*gc-verbose* nil))\n    (call-next-method)))\n\n(definterface rename-directory (from to)\n  (:implementation t\n    (rename-file from to)\n    (truename to))\n  (:implementation cmucl\n    (rename-file from (string-right-trim \"/\" (namestring to)))\n    (truename to))\n  (:implementation clisp\n    (ql-clisp:rename-directory from to)\n    (truename to)))\n\n(definterface probe-directory (pathname)\n  (:documentation \"Return the truename of PATHNAME, if it exists and\n  is a directory, or NIL otherwise.\")\n  (:implementation t\n    (let ((directory (probe-file pathname)))\n      (when directory\n        ;; probe-file is specified to return the truename of the path,\n        ;; but Allegro does not return the truename; truenamize it.\n        (truename directory))))\n  (:implementation clisp\n    (let ((directory (ql-clisp:probe-pathname pathname)))\n      (when (and directory (ql-clisp:probe-directory directory))\n        directory))))\n\n(definterface init-file-name ()\n  (:documentation \"Return the init file name for the current implementation.\")\n  (:implementation allegro\n    \".clinit.cl\")\n  (:implementation abcl\n    \".abclrc\")\n  (:implementation ccl\n    #+windows\n    \"ccl-init.lisp\"\n    #-windows\n    \".ccl-init.lisp\")\n  (:implementation clasp\n    \".clasprc\")\n  (:implementation clisp\n    \".clisprc.lisp\")\n  (:implementation ecl\n    \".eclrc\")\n  (:implementation mezzano\n    \"init.lisp\")\n  (:implementation mkcl\n    \".mkclrc\")\n  (:implementation lispworks\n    \".lispworks\")\n  (:implementation sbcl\n    \".sbclrc\")\n  (:implementation cmucl\n    \".cmucl-init.lisp\")\n  (:implementation scl\n    \".scl-init.lisp\")\n  )\n\n(defun init-file-name-for (&optional implementation-designator)\n  (let* ((class-name (find-symbol (string-upcase implementation-designator)\n                                  'ql-impl))\n         (class (find-class class-name nil)))\n    (when class\n      (let ((*implementation* (make-instance class)))\n        (init-file-name)))))\n\n(defun quicklisp-init-file-form ()\n  \"Return a form suitable for describing the location of the quicklisp\n  init file. If the file is available relative to the home directory,\n  returns a form that merges with the home directory instead of\n  specifying an absolute file.\"\n  (let* ((init-file (ql-setup:qmerge \"setup.lisp\"))\n         (enough (enough-namestring init-file (user-homedir-pathname))))\n    (cond ((equal (pathname enough) (pathname init-file))\n           ;; The init-file is somewhere outside of the home directory\n           (pathname enough))\n          (t\n           `(merge-pathnames ,enough (user-homedir-pathname))))))\n\n(defun write-init-forms (stream &key (indentation 0))\n  (format stream \"~%~v@T;;; The following lines added by ql:add-to-init-file:~%\"\n          indentation)\n  (format stream \"~v@T#-quicklisp~%\" indentation)\n  (let ((*print-case* :downcase))\n    (format stream \"~v@T(let ((quicklisp-init ~S))~%\"\n            indentation\n            (quicklisp-init-file-form)))\n  (format stream \"~v@T  (when (probe-file quicklisp-init)~%\" indentation)\n  (format stream \"~v@T    (load quicklisp-init)))~%~%\" indentation))\n\n(defun suitable-lisp-init-file (implementation)\n  \"Return the name of IMPLEMENTATION's init file. If IMPLEMENTAION is\na string or pathname, return its merged pathname instead.\"\n  (typecase implementation\n    ((or string pathname)\n     (merge-pathnames implementation))\n    ((or null (eql t))\n     (init-file-name))\n    (t\n     (init-file-name-for implementation))))\n\n(defun add-to-init-file (&optional implementation-or-file)\n  \"Add forms to the Lisp implementation's init file that will load\nquicklisp at CL startup.\"\n  (let ((init-file (suitable-lisp-init-file implementation-or-file)))\n    (unless init-file\n      (error \"Don't know how to add to init file for your implementation.\"))\n    (setf init-file (merge-pathnames init-file (user-homedir-pathname)))\n    (format *query-io* \"~&I will append the following lines to ~S:~%\"\n            init-file)\n    (write-init-forms *query-io* :indentation 2)\n    (when (ql-util:press-enter-to-continue)\n      (with-open-file (stream init-file\n                              :direction :output\n                              :if-does-not-exist :create\n                              :if-exists :append)\n        (write-init-forms stream)))\n    init-file))\n\n\n\n;;;\n;;; Native namestrings.\n;;;\n\n(definterface native-namestring (pathname)\n  (:documentation \"In Clozure CL, #\\\\.s in pathname-names are escaped\n  in namestrings with #\\\\> on Windows and #\\\\\\\\ elsewhere. This can\n  cause a problem when using CL:NAMESTRING to store pathname data that\n  might be used by other implementations. NATIVE-NAMESTRING is\n  intended to provide a namestring that can be parsed as a same-enough\n  object on multiple implementations.\")\n  (:implementation t\n    (namestring pathname))\n  (:implementation ccl\n    (ql-ccl:native-translated-namestring pathname))\n  (:implementation sbcl\n    (ql-sbcl:native-namestring pathname)))\n\n\n;;;\n;;; Directory write date\n;;;\n\n(definterface directory-write-date (pathname)\n  (:documentation \"Return the write-date of the directory designated\n  by PATHNAME as a universal time, like file-write-date.\")\n  (:implementation t\n    (file-write-date pathname))\n  (:implementation clisp\n    (nth-value 2 (ql-clisp:probe-pathname pathname))))\n\n\n;;;\n;;; Deleting a directory tree\n;;;\n\n(defvar *wild-entry*\n  (make-pathname :name :wild :type :wild :version :wild))\n\n(defvar *wild-relative*\n  (make-pathname :directory '(:relative :wild)))\n\n(definterface directoryp (entry)\n  (:documentation \"Return true if ENTRY refers to a directory.\")\n  (:implementation t\n    (not (or (pathname-name entry)\n             (pathname-type entry))))\n  (:implementation allegro\n    (ql-allegro:file-directory-p entry :follow-symbolic-links nil))\n  (:implementation lispworks\n    (ql-lispworks:file-directory-p entry)))\n\n(definterface directory-entries (directory)\n  (:documentation \"Return all directory entries of DIRECTORY as a\n  list, or NIL if there are no directory entries. Excludes the \\\".\\\"\n  and \\\"..\\\" entries.\")\n  (:implementation allegro\n    (directory directory\n               #+allegro :directories-are-files\n               #+allegro nil\n               #+allegro :follow-symbolic-links\n               #+allegro nil))\n  (:implementation abcl\n    (directory (merge-pathnames *wild-entry* directory)\n               #+abcl :resolve-symlinks #+abcl nil))\n  (:implementation ccl\n    (directory (merge-pathnames *wild-entry* directory)\n               #+ccl :directories #+ccl t\n               #+ccl :follow-links #+ccl nil))\n  (:implementation clasp\n    (nconc\n     (directory (merge-pathnames *wild-entry* directory)\n                #+clasp :resolve-symlinks #+clasp nil)\n     (directory (merge-pathnames *wild-relative* directory)\n                #+clasp :resolve-symlinks #+clasp nil)))\n  (:implementation clisp\n    ;; :full gives pathnames as well as truenames, BUT: it returns a\n    ;; singleton pathname, not a list, on dead symlinks.\n    (remove nil\n            (mapcar (lambda (entry) (and (listp entry) (first entry)))\n                    (nconc (directory (merge-pathnames *wild-entry* directory)\n                                      #+clisp :full #+clisp t\n                                      #+clisp :if-does-not-exist #+clisp :keep)\n                           (directory (merge-pathnames *wild-relative* directory)\n                                      #+clisp :full #+clisp t\n                                      #+clisp :if-does-not-exist #+clisp :keep)))))\n  (:implementation cmucl\n    (directory (merge-pathnames *wild-entry* directory)\n               #+cmucl :truenamep #+cmucl nil))\n  (:implementation scl\n    (directory (merge-pathnames *wild-entry* directory)\n               #+scl :truenamep #+scl nil))\n  (:implementation lispworks\n    (directory (merge-pathnames *wild-entry* directory)\n               #+lispworks :directories #+lispworks t\n               #+lispworks :link-transparency #+lispworks nil))\n  (:implementation ecl\n    (nconc\n     (directory (merge-pathnames *wild-entry* directory)\n                #+ecl :resolve-symlinks #+ecl nil)\n     (directory (merge-pathnames *wild-relative* directory)\n                #+ecl :resolve-symlinks #+ecl nil)))\n  (:implementation mezzano\n    (directory (merge-pathnames *wild-entry* directory)))\n  (:implementation mkcl\n    (setf directory (truename directory))\n    (nconc\n     (directory (merge-pathnames *wild-entry* directory))\n     (directory (merge-pathnames *wild-relative* directory))))\n  (:implementation sbcl\n    (directory (merge-pathnames *wild-entry* directory)\n               #+sbcl :resolve-symlinks #+sbcl nil)))\n\n(defimplementation (directory-entries :qualifier :around) (directory)\n  ;; Don't return any entries when called with a non-directory\n  ;; argument\n  (if (directoryp directory)\n      (call-next-method)\n      (warn \"directory-entries - not a directory -- ~S\" directory)))\n\n(definterface delete-directory (entry)\n  (:documentation \"Delete the directory ENTRY. Might signal an error\n  if it is not an empty directory.\")\n  (:implementation t\n    (delete-file entry))\n  (:implementation allegro\n    (ql-allegro:delete-directory entry))\n  (:implementation ccl\n    (ql-ccl:delete-directory entry))\n  (:implementation clasp\n    (ql-clasp:rmdir entry))\n  (:implementation clisp\n    (ql-clisp:delete-directory entry))\n  (:implementation cmucl\n    (ql-cmucl:unix-rmdir (namestring entry)))\n  (:implementation scl\n    (ql-scl:unix-rmdir (ql-scl:unix-namestring entry)))\n  (:implementation ecl\n    (ql-ecl:rmdir entry))\n  (:implementation mkcl\n    (ql-mkcl:rmdir entry))\n  (:implementation lispworks\n    (ql-lispworks:delete-directory entry))\n  (:implementation sbcl\n    (ql-sbcl:rmdir entry)))\n\n(defimplementation (delete-directory :qualifier :around) (directory)\n  ;; Don't delete non-directories with delete-directory\n  (if (directoryp directory)\n      (call-next-method)\n      (error \"delete-directory - not a directory -- ~A\" directory)))\n\n(definterface delete-directory-tree (pathname)\n  (:documentation \"Delete the directory tree rooted at PATHNAME.\")\n  (:implementation t\n    (let ((directories-to-process (list (truename pathname)))\n          (directories-to-delete '()))\n      (loop\n        (unless directories-to-process\n          (return))\n        (let* ((current (pop directories-to-process))\n               (entries (directory-entries current)))\n          (push current directories-to-delete)\n          (dolist (entry entries)\n            (if (directoryp entry)\n                (push entry directories-to-process)\n                (delete-file entry)))))\n      (map nil 'delete-directory directories-to-delete)))\n  (:implementation allegro\n    (ql-allegro:delete-directory-and-files pathname))\n  (:implementation ccl\n    (ql-ccl:delete-directory pathname)))\n\n(defimplementation (delete-directory-tree :qualifier :around) (pathname)\n  (if (directoryp pathname)\n      (call-next-method)\n      (progn\n        (warn \"delete-directory-tree - not a directory, ~\n               deleting anyway -- ~s\" pathname)\n        (delete-file pathname))))\n\n(defun map-directory-tree (directory fun)\n  \"Call FUN for every file in directory and all its subdirectories,\nrecursively. Uses the truename of directory as a starting point. Does\nnot follow symlinks, but, on some implementations, DOES include\npotentially dead symlinks.\"\n  (let ((directories-to-process (list (truename directory))))\n    (loop\n      (unless directories-to-process\n        (return))\n      (let* ((current (pop directories-to-process))\n             (entries (directory-entries current)))\n        (dolist (entry entries)\n          (if (directoryp entry)\n              (push entry directories-to-process)\n              (funcall fun entry)))))))\n\n"
  },
  {
    "path": "quicklisp/quicklisp/impl.lisp",
    "content": "(in-package #:ql-impl)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defun error-unimplemented (&rest args)\n    (declare (ignore args))\n    (error \"Not implemented\")))\n\n(defvar *interfaces* (make-hash-table)\n  \"A table of defined interfaces and their documentation.\")\n\n(defun show-interfaces ()\n  \"Display information about what interfaces are defined.\"\n  (maphash (lambda (interface info)\n             (destructuring-bind (arguments docstring)\n                 info\n               (let ((*package* (find-package :keyword)))\n                 (format t \"(~S ~:[()~;~:*~A~]~@[~% ~S~])~%\"\n                         interface arguments docstring))))\n           *interfaces*))\n\n(defmacro neuter-package (name)\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (let ((definition (fdefinition 'error-unimplemented)))\n       (do-external-symbols (symbol ,(string name))\n         (unless (fboundp symbol)\n           (setf (fdefinition symbol) definition))))))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defun feature-expression-passes-p (expression)\n    (cond ((keywordp expression)\n           (member expression *features*))\n          ((consp expression)\n           (case (first expression)\n             (or\n              (some 'feature-expression-passes-p (rest expression)))\n             (and\n              (every 'feature-expression-passes-p (rest expression)))))\n          (t (error \"Unrecognized feature expression -- ~S\" expression)))))\n\n\n(defmacro define-implementation-package (feature package-name &rest options)\n  (let* ((output-options '((:use)\n                           (:export #:lisp)))\n         (prep (cdr (assoc :prep options)))\n         (class-option (cdr (assoc :class options)))\n         (class (first class-option))\n         (superclasses (rest class-option))\n         (import-options '())\n         (effectivep (feature-expression-passes-p feature)))\n    (dolist (option options)\n      (ecase (first option)\n        ((:prep :class))\n        ((:import-from\n          :import)\n         (push option import-options))\n        ((:export\n          :shadow\n          :intern\n          :documentation)\n         (push option output-options))\n        ((:reexport-from)\n         (push (cons :export (cddr option)) output-options)\n         (push (cons :import-from (cdr option)) import-options))))\n    `(progn\n       ,@(when effectivep\n               `((eval-when (:compile-toplevel :load-toplevel :execute)\n                   ,@prep)))\n       (defclass ,class ,superclasses ())\n       (defpackage ,package-name ,@output-options\n                   ,@(when effectivep\n                           import-options))\n       ,@(when effectivep\n               `((setf *implementation* (make-instance ',class))))\n       ,@(unless effectivep\n                 `((neuter-package ,package-name))))))\n\n(defmacro definterface (name lambda-list &body options)\n  (let* ((doc-option (find :documentation options :key #'first))\n         (doc (second doc-option)))\n    (setf (gethash name *interfaces*) (list lambda-list doc)))\n  (let* ((forbidden (intersection lambda-list lambda-list-keywords))\n         (gf-options (remove :implementation options :key #'first))\n         (implementations (set-difference options gf-options))\n         (implementation-arg (copy-symbol '%implementation)))\n    (when forbidden\n      (error \"~S not allowed in definterface lambda list\" forbidden))\n    (flet ((method-option (class body)\n             `(:method ((,implementation-arg ,class) ,@lambda-list)\n                ,@body)))\n      (let ((generic-name (intern (format nil \"%~A\" name))))\n        `(progn\n           (defgeneric ,generic-name (lisp ,@lambda-list)\n             ,@gf-options\n             ,@(mapcan (lambda (implementation)\n                         (destructuring-bind (class &rest body)\n                             (rest implementation)\n                           (mapcar (lambda (class)\n                                     (method-option class body))\n                                   (if (consp class)\n                                       class\n                                       (list class)))))\n                       implementations))\n           (defun ,name ,lambda-list\n             (,generic-name *implementation* ,@lambda-list)))))))\n\n(defmacro defimplementation (name-and-options\n                             lambda-list &body body)\n  (destructuring-bind (name &key (for t) qualifier)\n      (if (consp name-and-options)\n          name-and-options\n          (list name-and-options))\n    (unless for\n      (error \"You must specify an implementation name.\"))\n    (let ((generic-name (find-symbol (format nil \"%~A\" name)))\n          (implementation-arg (copy-symbol '%implementation)))\n      (unless generic-name\n        (error \"~S does not name an implementation function\" name))\n      `(defmethod ,generic-name\n           ,@(when qualifier (list qualifier))\n         ,(list* `(,implementation-arg ,for) lambda-list) ,@body))))\n\n\n;;; Bootstrap implementations\n\n(defvar *implementation* nil)\n(defclass lisp () ())\n\n\n;;; Allegro Common Lisp\n\n(define-implementation-package :allegro #:ql-allegro\n  (:documentation\n   \"Allegro Common Lisp - http://www.franz.com/products/allegrocl/\")\n  (:class allegro)\n  (:reexport-from #:socket\n                  #:make-socket)\n  (:reexport-from #:excl\n                  #:file-directory-p\n                  #:delete-directory\n                  #:delete-directory-and-files\n                  #:read-vector))\n\n\n;;; Armed Bear Common Lisp\n\n(define-implementation-package :abcl #:ql-abcl\n  (:documentation\n   \"Armed Bear Common Lisp - http://common-lisp.net/project/armedbear/\")\n  (:class abcl)\n  (:reexport-from #:ext\n                  #:make-socket\n                  #:get-socket-stream))\n\n;;; Clozure CL\n\n(define-implementation-package :ccl #:ql-ccl\n  (:documentation\n   \"Clozure Common Lisp - http://www.clozure.com/clozurecl.html\")\n  (:class ccl)\n  (:reexport-from #:ccl\n                  #:delete-directory\n                  #:make-socket\n                  #:native-translated-namestring))\n\n;;; CLASP\n\n(define-implementation-package :clasp #:ql-clasp\n  (:documentation \"CLASP - http://github.com/drmeister/clasp\")\n  (:class clasp)\n  (:prep\n   (require 'sockets))\n  (:intern #:host-network-address)\n  (:reexport-from #:si\n                  #:rmdir\n                  #:file-kind)\n  (:reexport-from #:sb-bsd-sockets\n                  #:get-host-by-name\n                  #:host-ent-address\n                  #:inet-socket\n                  #:socket-connect\n                  #:socket-make-stream))\n\n\n;;; GNU CLISP\n\n(define-implementation-package :clisp #:ql-clisp\n  (:documentation \"GNU CLISP - http://clisp.cons.org/\")\n  (:class clisp)\n  (:reexport-from #:socket\n                  #:socket-connect)\n  (:reexport-from #:ext\n                  #:delete-directory\n                  #:rename-directory\n                  #:probe-directory\n                  #:probe-pathname\n                  #:read-byte-sequence))\n\n\n;;; CMUCL\n\n(define-implementation-package :cmu #:ql-cmucl\n  (:documentation \"CMU Common Lisp - http://www.cons.org/cmucl/\")\n  (:class cmucl)\n  (:reexport-from #:system\n                  #:make-fd-stream)\n  (:reexport-from #:unix\n                  #:unix-rmdir)\n  (:reexport-from #:extensions\n                  #:connect-to-inet-socket\n                  #:*gc-verbose*))\n\n(defvar ql-cmucl:*gc-verbose*)\n\n\n;;; Scieneer CL\n\n(define-implementation-package :scl #:ql-scl\n  (:documentation \"Scieneer Common Lisp - http://www.scieneer.com/scl/\")\n  (:class scl)\n  (:reexport-from #:system\n                  #:make-fd-stream)\n  (:reexport-from #:unix\n                  #:unix-rmdir)\n  (:reexport-from #:extensions\n                  #:connect-to-inet-socket\n                  #:unix-namestring))\n\n\n;;; LispWorks\n\n(define-implementation-package :lispworks #:ql-lispworks\n  (:documentation \"LispWorks - http://www.lispworks.com/\")\n  (:class lispworks)\n  (:prep\n   (require \"comm\"))\n  (:reexport-from #:lw\n                  #:file-directory-p\n                  #:delete-directory)\n  (:reexport-from #:comm\n                  #:open-tcp-stream\n                  #:get-host-entry))\n\n\n;;; ECL\n\n(define-implementation-package :ecl #:ql-ecl\n  (:documentation \"ECL - http://ecls.sourceforge.net/\")\n  (:class ecl)\n  (:prep\n   (require 'sockets))\n  (:intern #:host-network-address)\n  (:reexport-from #:si\n                  #:rmdir\n                  #:file-kind)\n  (:reexport-from #:sb-bsd-sockets\n                  #:get-host-by-name\n                  #:host-ent-address\n                  #:inet-socket\n                  #:socket-connect\n                  #:socket-make-stream))\n\n;;; Mezzano\n\n(define-implementation-package :mezzano #:ql-mezzano\n  (:documentation \"Mezzano Lisp Operating System - https://github.com/froggey/Mezzano\")\n  (:class mezzano)\n  (:reexport-from #:mezzano.network.tcp\n                  #:tcp-stream-connect))\n\n;;; MKCL\n\n(define-implementation-package :mkcl #:ql-mkcl\n  (:documentation \"ManKai Common Lisp - http://common-lisp.net/project/mkcl/\")\n  (:class mkcl)\n  (:prep\n   (require 'sockets))\n  (:intern #:host-network-address)\n  (:reexport-from #:si\n                  #:rmdir\n                  #:file-kind)\n  (:reexport-from #:sb-bsd-sockets\n                  #:get-host-by-name\n                  #:host-ent-address\n                  #:inet-socket\n                  #:socket-connect\n                  #:socket-make-stream))\n\n\n;;; SBCL\n\n(define-implementation-package :sbcl #:ql-sbcl\n  (:class sbcl)\n  (:documentation\n   \"Steel Bank Common Lisp - http://www.sbcl.org/\")\n  (:prep\n   (require 'sb-posix)\n   (require 'sb-bsd-sockets))\n  (:intern #:host-network-address)\n  (:reexport-from #:sb-posix\n                  #:rmdir)\n  (:reexport-from #:sb-ext\n                  #:compiler-note\n                  #:native-namestring)\n  (:reexport-from #:sb-bsd-sockets\n                  #:get-host-by-name\n                  #:inet-socket\n                  #:host-ent-address\n                  #:socket-connect\n                  #:socket-make-stream))\n"
  },
  {
    "path": "quicklisp/quicklisp/local-projects.lisp",
    "content": ";;;; local-projects.lisp\n\n;;;\n;;; Local project support.\n;;;\n;;; Local projects can be placed in <quicklisp>/local-projects/. New\n;;; entries in that directory are automatically scanned for system\n;;; files for use with QL:QUICKLOAD.\n;;;\n;;; This works by keeping a cache of system file pathnames in\n;;; <quicklisp>/local-projects/system-index.txt. Whenever the\n;;; timestamp on the local projects directory is newer than the\n;;; timestamp on the system index file, the entire tree is re-scanned\n;;; and cached.\n;;;\n;;; This will pick up system files that are created as a result of\n;;; creating new project directory in <quicklisp>/local-projects/,\n;;; e.g. unpacking a tarball or zip file, checking out a project from\n;;; version control, etc. It will NOT pick up a system file that is\n;;; added sometime later in a subdirectory; for that, the\n;;; REGISTER-LOCAL-PROJECTS function is needed to rebuild the system\n;;; file index.\n;;;\n;;; In the event there are multiple systems of the same name in the\n;;; directory tree, the one with the shortest pathname namestring is\n;;; used. This is intended to ignore stuff like _darcs pristine\n;;; directories.\n;;;\n;;; Work in progress!\n;;;\n\n(in-package #:quicklisp-client)\n\n(defparameter *local-project-directories*\n  (list (qmerge \"local-projects/\"))\n  \"The default local projects directory.\")\n\n(defun system-index-file (pathname)\n  \"Return the system index file for the directory PATHNAME.\"\n  (merge-pathnames \"system-index.txt\" pathname))\n\n(defun matching-directory-files (directory fun)\n  (let ((result '()))\n    (map-directory-tree directory\n                        (lambda (file)\n                          (when (funcall fun file)\n                            (push file result))))\n    result))\n\n(defun local-project-system-files (pathname)\n  \"Return a list of system files under PATHNAME.\"\n  (let* ((files (matching-directory-files pathname\n                                          (lambda (file)\n                                            (equalp (pathname-type file)\n                                                    \"asd\")))))\n    (setf files (sort files\n                      #'string<\n                      :key #'namestring))\n    (stable-sort files\n                 #'<\n                 :key (lambda (file)\n                        (length (namestring file))))))\n\n(defun make-system-index (pathname)\n  \"Create a system index file for all system files under\nPATHNAME. Current format is one native namestring per line.\"\n  (setf pathname (truename pathname))\n  (with-open-file (stream (system-index-file pathname)\n                          :direction :output\n                          :if-exists :rename-and-delete)\n    (dolist (system-file (local-project-system-files pathname))\n      (let ((system-path (enough-namestring system-file pathname)))\n        (write-line (native-namestring system-path) stream)))\n    (probe-file stream)))\n\n(defun find-valid-system-index (pathname)\n  \"Find a valid system index file for PATHNAME; one that both exists\nand has a newer timestamp than PATHNAME.\"\n  (let* ((file (system-index-file pathname))\n         (probed (probe-file file)))\n    (when (and probed\n               (<= (directory-write-date pathname)\n                   (file-write-date probed)))\n      probed)))\n\n(defun ensure-system-index (pathname)\n  \"Find or create a system index file for PATHNAME.\"\n  (or (find-valid-system-index pathname)\n      (make-system-index pathname)))\n\n(defun find-system-in-index (system index-file)\n  \"If any system pathname in INDEX-FILE has a pathname-name matching\nSYSTEM, return its full pathname.\"\n  (with-open-file (stream index-file)\n    (loop for namestring = (read-line stream nil)\n          while namestring\n          when (string= system (pathname-name namestring))\n          return (or (probe-file (merge-pathnames namestring index-file))\n                     ;; If the indexed .asd file doesn't exist anymore\n                     ;; then regenerate the index and restart the search.\n                     (find-system-in-index system (make-system-index (directory-namestring index-file)))))))\n\n(defun local-projects-searcher (system-name)\n  \"This function is added to ASDF:*SYSTEM-DEFINITION-SEARCH-FUNCTIONS*\nto use the local project directory and cache to find systems.\"\n  (dolist (directory *local-project-directories*)\n    (when (probe-directory directory)\n      (let ((system-index (ensure-system-index directory)))\n        (when system-index\n          (let ((system (find-system-in-index system-name system-index)))\n            (when system\n              (return system))))))))\n\n(defun list-local-projects ()\n  \"Return a list of pathnames to local project system files.\"\n  (let ((result (make-array 16 :fill-pointer 0 :adjustable t))\n        (seen (make-hash-table :test 'equal)))\n    (dolist (directory *local-project-directories*\n             (coerce result 'list))\n      (let ((index (ensure-system-index directory)))\n        (when index\n          (with-open-file (stream index)\n            (loop for line = (read-line stream nil)\n                  while line do\n                  (let ((pathname (merge-pathnames line index)))\n                    (unless (gethash (pathname-name pathname) seen)\n                      (setf (gethash (pathname-name pathname) seen) t)\n                      (vector-push-extend (merge-pathnames line index)\n                                          result))))))))))\n\n(defun register-local-projects ()\n  \"Force a scan of the local projects directory to create the system\nfile index.\"\n  (map nil 'make-system-index *local-project-directories*))\n\n(defun list-local-systems ()\n  \"Return a list of local project system names.\"\n  (mapcar #'pathname-name (list-local-projects)))\n"
  },
  {
    "path": "quicklisp/quicklisp/minitar.lisp",
    "content": "(in-package #:ql-minitar)\n\n(defconstant +block-size+ 512)\n(defconstant +space-code+ 32)\n(defconstant +newline-code+ 10)\n(defconstant +equals-code+ 61)\n\n(defun make-block-buffer ()\n  (make-array +block-size+ :element-type '(unsigned-byte 8) :initial-element 0))\n\n(defun skip-n-blocks (n stream)\n  (let ((block (make-block-buffer)))\n    (dotimes (i n)\n      (read-sequence block stream))))\n\n(defun read-octet-vector (length stream)\n  (let ((block (make-block-buffer))\n        (vector (make-array length :element-type '(unsigned-byte 8)))\n        (offset 0)\n        (block-count (ceiling length +block-size+)))\n    (dotimes (i block-count)\n        (read-sequence block stream)\n        (replace vector block :start1 offset)\n        (incf offset +block-size+))\n    vector))\n\n\n(defun decode-pax-header-record (vector offset)\n  \"Decode VECTOR as pax extended header data. Returns the keyword and\nvalue it specifies as multiple values.\"\n  ;; Vector format is: \"%d %s=%s\\n\", <length>, <keyword>, <value>\n  ;; See http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html\n  (let* ((length-start offset)\n         (length-end (position +space-code+ vector :start length-start))\n         (length-string (ascii-subseq vector length-start length-end))\n         (length (parse-integer length-string))\n         (keyword-start (1+ length-end))\n         (keyword-end (position +equals-code+ vector :start keyword-start))\n         (keyword (ascii-subseq vector keyword-start keyword-end))\n         (value-start (1+ keyword-end))\n         (value-end (1- (+ offset length)))\n         (value (ascii-subseq vector value-start value-end)))\n    (values keyword value (+ offset length))))\n\n(defun decode-pax-header (vector)\n  \"Decode VECTOR as a pax header and return it as an alist.\"\n  (let ((header nil)\n        (offset 0)\n        (length (length vector)))\n    (loop\n      (when (<= length offset)\n        (return header))\n      (multiple-value-bind (keyword value new-offset)\n          (decode-pax-header-record vector offset)\n        (setf header (acons keyword value header))\n        (setf offset new-offset)))))\n\n(defun pax-header-path (vector)\n  \"Decode VECTOR as a pax header and return its 'path' value, if\n  any.\"\n  (let ((header-alist (decode-pax-header vector)))\n    (cdr (assoc \"path\" header-alist :test 'equal))))\n\n(defun ascii-subseq (vector start end)\n  (let ((string (make-string (- end start))))\n    (loop for i from 0\n          for j from start below end\n          do (setf (char string i) (code-char (aref vector j))))\n    string))\n\n(defun block-asciiz-string (block start length)\n  (let* ((end (+ start length))\n         (eos (or (position 0 block :start start :end end)\n                            end)))\n    (ascii-subseq block start eos)))\n\n(defun prefix (header)\n  (when (plusp (aref header 345))\n    (block-asciiz-string header 345 155)))\n\n(defun name (header)\n  (block-asciiz-string header 0 100))\n\n(defun payload-size (header)\n  (values (parse-integer (block-asciiz-string header 124 12) :radix 8)))\n\n(defun nth-block (n file)\n  (with-open-file (stream file :element-type '(unsigned-byte 8))\n    (let ((block (make-block-buffer)))\n      (skip-n-blocks (1- n) stream)\n      (read-sequence block stream)\n      block)))\n\n(defun payload-type (code)\n  (case code\n    (0 :file)\n    (48 :file)\n    (50 :symlink)\n    (76 :long-name)\n    (53 :directory)\n    (103 :global-header)\n    (120 :pax-extended-header)\n    (t :unsupported)))\n\n(defun full-path (header)\n  (let ((prefix (prefix header))\n        (name (name header)))\n    (if prefix\n        (format nil \"~A/~A\" prefix name)\n        name)))\n\n(defun save-file (file size stream)\n  (multiple-value-bind (full-blocks partial)\n      (truncate size +block-size+)\n    (ensure-directories-exist file)\n    (with-open-file (outstream file\n                     :direction :output\n                     :if-exists :supersede\n                     :element-type '(unsigned-byte 8))\n      (let ((block (make-block-buffer)))\n        (dotimes (i full-blocks)\n          (read-sequence block stream)\n          (write-sequence block outstream))\n        (when (plusp partial)\n          (read-sequence block stream)\n          (write-sequence block outstream :end partial))))))\n\n(defun gnu-long-name (size stream)\n  ;; GNU long names are simply the filename (null terminated) packed into the\n  ;; payload.\n  (let ((payload (read-octet-vector size stream)))\n    (ascii-subseq payload 0 (1- size))))\n\n(defun unpack-tarball (tarfile &key (directory *default-pathname-defaults*))\n  (let ((block (make-block-buffer))\n        (extended-path nil))\n    (with-open-file (stream tarfile :element-type '(unsigned-byte 8))\n      (loop\n       (let ((size (read-sequence block stream)))\n         (when (zerop size)\n           (return))\n         (unless (= size +block-size+)\n           (error \"Bad size on tarfile\"))\n         (when (every #'zerop block)\n           (return))\n         (let* ((payload-code (aref block 156))\n                (payload-type (payload-type payload-code))\n                (tar-path (or (shiftf extended-path nil)\n                              (full-path block)))\n                (full-path (merge-pathnames tar-path directory))\n                (payload-size (payload-size block))\n                (block-count (ceiling (payload-size block) +block-size+)))\n         (case payload-type\n           (:file\n            (save-file full-path payload-size stream))\n           (:directory\n            (ensure-directories-exist full-path))\n           ((:symlink :global-header)\n            ;; These block types aren't required for Quicklisp archives\n            (skip-n-blocks block-count stream))\n           (:long-name\n            (setf extended-path (gnu-long-name payload-size stream)))\n           (:pax-extended-header\n            (let* ((pax-header-data (read-octet-vector payload-size stream))\n                   (path (pax-header-path pax-header-data)))\n              (when path\n                (setf extended-path path))))\n           (t\n            (warn \"Unknown tar block payload code -- ~D\" payload-code)\n            (skip-n-blocks block-count stream)))))))))\n\n(defun contents (tarfile)\n  (let ((block (make-block-buffer))\n        (result '()))\n    (with-open-file (stream tarfile :element-type '(unsigned-byte 8))\n      (loop\n        (let ((size (read-sequence block stream)))\n          (when (zerop size)\n            (return (nreverse result)))\n          (unless (= size +block-size+)\n            (error \"Bad size on tarfile\"))\n          (when (every #'zerop block)\n            (return (nreverse result)))\n          (let* ((payload-type (payload-type (aref block 156)))\n                 (tar-path (full-path block))\n                 (payload-size (payload-size block)))\n            (skip-n-blocks (ceiling payload-size +block-size+) stream)\n            (case payload-type\n              (:file\n               (push tar-path result))\n              (:directory\n               (push tar-path result)))))))))\n"
  },
  {
    "path": "quicklisp/quicklisp/misc.lisp",
    "content": ";;;; misc.lisp\n\n(in-package #:quicklisp-client)\n\n;;;\n;;; This stuff will probably end up somewhere else.\n;;;\n\n(defun use-only-quicklisp-systems ()\n  (asdf:initialize-source-registry\n   '(:source-registry :ignore-inherited-configuration))\n  (asdf:map-systems 'asdf:clear-system)\n  t)\n\n(defun who-depends-on (system-name)\n  \"Return a list of names of systems that depend on SYSTEM-NAME.\"\n  (setf system-name (string-downcase system-name))\n  (loop for system in (provided-systems t)\n        when (member system-name (required-systems system) :test 'string=)\n        collect (name system)))\n"
  },
  {
    "path": "quicklisp/quicklisp/network.lisp",
    "content": ";;;\n;;; Low-level networking implementations\n;;;\n\n(in-package #:ql-network)\n\n(definterface host-address (host)\n  (:implementation t\n    host)\n  (:implementation sbcl\n    (ql-sbcl:host-ent-address (ql-sbcl:get-host-by-name host))))\n\n(definterface open-connection (host port)\n  (:documentation \"Open and return a network connection to HOST on the\n  given PORT.\")\n  (:implementation t\n    (declare (ignore host port))\n    (error \"Sorry, quicklisp in implementation ~S is not supported yet.\"\n           (lisp-implementation-type)))\n  (:implementation allegro\n    (ql-allegro:make-socket :remote-host host\n                            :remote-port port))\n  (:implementation abcl\n    (let ((socket (ql-abcl:make-socket host port)))\n      (ql-abcl:get-socket-stream socket :element-type '(unsigned-byte 8))))\n  (:implementation ccl\n    (ql-ccl:make-socket :remote-host host\n                        :remote-port port))\n  (:implementation clasp\n    (let* ((endpoint (ql-clasp:host-ent-address\n                      (ql-clasp:get-host-by-name host)))\n           (socket (make-instance 'ql-clasp:inet-socket\n                                  :protocol :tcp\n                                  :type :stream)))\n      (ql-clasp:socket-connect socket endpoint port)\n      (ql-clasp:socket-make-stream socket\n                                 :element-type '(unsigned-byte 8)\n                                 :input t\n                                 :output t\n                                 :buffering :full)))\n  (:implementation clisp\n    (ql-clisp:socket-connect port host :element-type '(unsigned-byte 8)))\n  (:implementation cmucl\n    (let ((fd (ql-cmucl:connect-to-inet-socket host port)))\n      (ql-cmucl:make-fd-stream fd\n                               :element-type '(unsigned-byte 8)\n                               :binary-stream-p t\n                               :input t\n                               :output t)))\n  (:implementation scl\n    (let ((fd (ql-scl:connect-to-inet-socket host port)))\n      (ql-scl:make-fd-stream fd\n\t\t\t     :element-type '(unsigned-byte 8)\n\t\t\t     :input t\n\t\t\t     :output t)))\n  (:implementation ecl\n    (let* ((endpoint (ql-ecl:host-ent-address\n                      (ql-ecl:get-host-by-name host)))\n           (socket (make-instance 'ql-ecl:inet-socket\n                                  :protocol :tcp\n                                  :type :stream)))\n      (ql-ecl:socket-connect socket endpoint port)\n      (ql-ecl:socket-make-stream socket\n                                 :element-type '(unsigned-byte 8)\n                                 :input t\n                                 :output t\n                                 :buffering :full)))\n  (:implementation mezzano\n    (ql-mezzano:tcp-stream-connect host port\n                                   :element-type '(unsigned-byte 8)))\n  (:implementation mkcl\n    (let* ((endpoint (ql-mkcl:host-ent-address\n                      (ql-mkcl:get-host-by-name host)))\n           (socket (make-instance 'ql-mkcl:inet-socket\n                                  :protocol :tcp\n                                  :type :stream)))\n      (ql-mkcl:socket-connect socket endpoint port)\n      (ql-mkcl:socket-make-stream socket\n                                   :element-type '(unsigned-byte 8)\n                                   :input t\n                                   :output t\n                                   :buffering :full)))\n  (:implementation lispworks\n    (ql-lispworks:open-tcp-stream host port\n                                  :direction :io\n                                  :errorp t\n                                  :read-timeout nil\n                                  :element-type '(unsigned-byte 8)\n                                  :timeout 5))\n  (:implementation sbcl\n    (let* ((endpoint (ql-sbcl:host-ent-address\n                      (ql-sbcl:get-host-by-name host)))\n           (socket (make-instance 'ql-sbcl:inet-socket\n                                  :protocol :tcp\n                                  :type :stream)))\n      (ql-sbcl:socket-connect socket endpoint port)\n      (ql-sbcl:socket-make-stream socket\n                                  :element-type '(unsigned-byte 8)\n                                  :input t\n                                  :output t\n                                  :buffering :full))))\n\n(definterface read-octets (buffer connection)\n  (:documentation \"Read from CONNECTION into BUFFER. Returns the\n  number of octets read.\")\n  (:implementation t\n    (read-sequence buffer connection))\n  (:implementation allegro\n    (ql-allegro:read-vector buffer connection))\n  (:implementation clisp\n    (ql-clisp:read-byte-sequence buffer connection\n                                  :no-hang nil\n                                  :interactive t)))\n\n(definterface write-octets (buffer connection)\n  (:documentation \"Write the contents of BUFFER to CONNECTION.\")\n  (:implementation t\n    (write-sequence buffer connection)\n    (finish-output connection)))\n\n(definterface close-connection (connection)\n  (:implementation t\n    (ignore-errors (close connection))))\n\n(definterface call-with-connection (host port fun)\n  (:documentation \"Establish a network connection to HOST on PORT and\n  call FUN with that connection as the only argument. Unconditionally\n  closes the connection afterwareds via CLOSE-CONNECTION in an\n  unwind-protect. See also WITH-CONNECTION.\")\n  (:implementation t\n    (let (connection)\n      (unwind-protect\n           (progn\n             (setf connection (open-connection host port))\n             (funcall fun connection))\n        (when connection\n          (close-connection connection))))))\n\n(defmacro with-connection ((connection host port) &body body)\n  `(call-with-connection ,host ,port (lambda (,connection) ,@body)))\n"
  },
  {
    "path": "quicklisp/quicklisp/package.lisp",
    "content": ";;;; package.lisp\n\n(defpackage #:ql-util\n  (:documentation\n   \"Utility functions used in various places.\")\n  (:use #:cl)\n  (:export #:write-line-to-file\n           #:without-prompting\n           #:press-enter-to-continue\n           #:replace-file\n           #:copy-file\n           #:delete-file-if-exists\n           #:ensure-file-exists\n           #:split-spaces\n           #:first-line\n           #:file-size\n           #:safely-read\n           #:safely-read-file\n           #:make-versions-url\n           #:with-temporary-file))\n\n(defpackage #:ql-setup\n  (:documentation\n   \"Functions and variables initialized early in the Quicklisp client\n   configuration.\")\n  (:use #:cl)\n  (:export #:qmerge\n           #:qenough\n           #:*quicklisp-home*))\n\n(defpackage #:ql-config\n  (:documentation\n   \"Getting and setting persistent configuration values.\")\n  (:use #:cl #:ql-util #:ql-setup)\n  (:export #:config-value))\n\n(defpackage #:ql-impl\n  (:documentation\n   \"Configuration of implementation-specific packages and interfaces.\")\n  (:use #:cl)\n  (:export #:*implementation*)\n  (:export #:definterface\n           #:defimplementation\n           #:show-interfaces)\n  (:export #:lisp\n           #:abcl\n           #:allegro\n           #:ccl\n           #:clasp\n           #:clisp\n           #:cmucl\n           #:cormanlisp\n           #:ecl\n           #:gcl\n           #:lispworks\n           #:mezzano\n           #:mkcl\n           #:scl\n           #:sbcl))\n\n(defpackage #:ql-impl-util\n  (:documentation\n   \"Utility functions that require implementation-specific\n   functionality.\")\n  (:use #:cl #:ql-impl)\n  (:export #:call-with-quiet-compilation\n           #:add-to-init-file\n           #:rename-directory\n           #:delete-directory\n           #:probe-directory\n           #:directory-entries\n           #:delete-directory-tree\n           #:map-directory-tree\n           #:native-namestring\n           #:directory-write-date))\n\n(defpackage #:ql-network\n  (:documentation\n   \"Simple, low-level network access.\")\n  (:use #:cl #:ql-impl)\n  (:export #:open-connection\n           #:write-octets\n           #:read-octets\n           #:close-connection\n           #:with-connection))\n\n(defpackage #:ql-progress\n  (:documentation\n   \"Displaying a progress bar.\")\n  (:use #:cl)\n  (:export #:make-progress-bar\n           #:start-display\n           #:update-progress\n           #:finish-display))\n\n(defpackage #:ql-http\n  (:documentation\n   \"A simple HTTP client.\")\n  (:use #:cl #:ql-network #:ql-progress #:ql-config)\n  (:export #:*proxy-url*\n           #:fetch\n           #:http-fetch\n           #:*fetch-scheme-functions*\n           #:scheme\n           #:hostname\n           #:port\n           #:path\n           #:url\n           #:*maximum-redirects*\n           #:*default-url-defaults*)\n  (:export #:fetch-error\n           #:unexpected-http-status\n           #:unexpected-http-status-code\n           #:unexpected-http-status-url\n           #:too-many-redirects\n           #:too-many-redirects-url\n           #:too-many-redirects-count))\n\n(defpackage #:ql-minitar\n  (:documentation\n   \"A simple implementation of unpacking the 'tar' file format.\")\n  (:use #:cl)\n  (:export #:unpack-tarball))\n\n(defpackage #:ql-gunzipper\n  (:documentation\n   \"An implementation of gunzip.\")\n  (:use #:cl)\n  (:export #:gunzip))\n\n(defpackage #:ql-cdb\n  (:documentation\n   \"Read and write CDB files; code adapted from ZCDB.\")\n  (:use #:cl)\n  (:export #:lookup\n           #:map-cdb\n           #:convert-index-file))\n\n(defpackage #:ql-dist\n  (:documentation\n   \"Generic functions, variables, and classes for interacting with the\n   dist system. Documented, exported symbols are intended for public\n   use.\")\n  (:use #:cl\n        #:ql-util\n        #:ql-http\n        #:ql-setup\n        #:ql-gunzipper\n        #:ql-minitar)\n  (:intern #:dist-version\n           #:dist-url)\n  (:import-from #:ql-impl-util\n                #:delete-directory-tree\n                #:directory-entries\n                #:probe-directory)\n  ;; Install/enable protocol\n  (:export #:installedp\n           #:install\n           #:uninstall\n           #:ensure-installed\n           #:enabledp\n           #:enable\n           #:disable)\n  ;; Preference protocol\n  (:export #:preference\n           #:preference-file\n           #:preference-parent\n           #:forget-preference)\n  ;; Generic\n  (:export #:all-dists\n           #:canonical-distinfo-url\n           #:enabled-dists\n           #:find-dist\n           #:find-dist-or-lose\n           #:find-system\n           #:find-release\n           #:dist\n           #:system\n           #:release\n           #:base-directory\n           #:relative-to\n           #:metadata-name\n           #:install-metadata-file\n           #:short-description\n           #:provided-releases\n           #:provided-systems\n           #:installed-releases\n           #:installed-systems\n           #:name)\n  ;; Dists\n  (:export #:dist\n           #:dist-merge\n           #:find-system-in-dist\n           #:find-release-in-dist\n           #:system-index-url\n           #:release-index-url\n           #:available-versions-url\n           #:available-versions\n           #:version\n           #:subscription-url\n           #:new-version-available-p\n           #:dist-difference\n           #:fetch-dist\n           #:initialize-release-index\n           #:initialize-system-index\n           #:with-consistent-dists)\n  ;; Dist updates\n  (:export #:available-update\n           #:update-release-differences\n           #:show-update-report\n           #:update-in-place\n           #:install-dist\n           #:subscription-inhibition-file\n           #:inhibit-subscription\n           #:uninhibit-subscription\n           #:subscription-inhibited-p\n           #:subscription-unavailable\n           #:subscribedp\n           #:subscribe\n           #:unsubscribe)\n  ;; Releases\n  (:export #:release\n           #:project-name\n           #:system-files\n           #:archive-url\n           #:archive-size\n           #:ensure-archive-file\n           #:archive-content-sha1\n           #:archive-md5\n           #:prefix\n           #:local-archive-file\n           #:ensure-local-archive-file\n           #:check-local-archive-file\n           #:invalid-local-archive\n           #:invalid-local-archive-file\n           #:invalid-local-archive-release\n           #:missing-local-archive\n           #:badly-sized-local-archive\n           #:delete-and-retry)\n  ;; Systems\n  (:export #:dist\n           #:release\n           #:preference\n           #:system-file-name\n           #:required-systems)\n  ;; Misc\n  (:export #:standard-dist-enumeration-function\n           #:*dist-enumeration-functions*\n           #:find-asdf-system-file\n           #:system-definition-searcher\n           #:system-apropos\n           #:system-apropos-list\n           #:dependency-tree\n           #:clean\n           #:unknown-dist))\n\n(defpackage #:ql-dist-user\n  (:documentation\n   \"A package that uses QL-DIST; useful for playing around in without\n   clobbering any QL-DIST internals.\")\n  (:use #:cl\n        #:ql-dist))\n\n(defpackage #:ql-bundle\n  (:documentation\n   \"A package for supporting the QL:BUNDLE-SYSTEMS function.\")\n  (:use #:cl #:ql-dist #:ql-impl-util)\n  (:shadow #:find-system\n           #:find-release)\n  (:export #:bundle\n           #:requested-systems\n           #:ensure-system\n           #:ensure-release\n           #:write-bundle\n           #:add-systems-recursively\n           #:object-not-found\n           #:system-not-found\n           #:system-not-found-system\n           #:release-not-found\n           #:bundle-directory-exists\n           #:bundle-directory-exists-directory))\n\n(defpackage #:quicklisp-client\n  (:documentation\n   \"The Quicklisp client package, intended for end-user Quicklisp\n   commands and configuration parameters.\")\n  (:nicknames #:quicklisp #:ql)\n  (:use #:cl\n        #:ql-util\n        #:ql-impl-util\n        #:ql-dist\n        #:ql-http\n        #:ql-setup\n        #:ql-config\n        #:ql-minitar\n        #:ql-gunzipper)\n  (:shadow #:uninstall)\n  (:shadowing-import-from #:ql-dist\n                          #:dist-version\n                          #:dist-url)\n  (:export #:dist-version\n           #:dist-url)\n  (:export #:quickload\n           #:quickfind\n           #:*quickload-prompt*\n           #:*quickload-verbose*\n           #:*quickload-explain*\n           #:system-not-found\n           #:system-not-found-name\n           #:uninstall\n           #:uninstall-dist\n           #:qmerge\n           #:*quicklisp-home*\n           #:*initial-dist-url*\n           #:*proxy-url*\n           #:config-value\n           #:setup\n           #:provided-systems\n           #:system-apropos\n           #:system-apropos-list\n           #:system-list\n           #:client-version\n           #:client-url\n           #:available-client-versions\n           #:install-client\n           #:update-client\n           #:update-dist\n           #:update-all-dists\n           #:available-dist-versions\n           #:add-to-init-file\n           #:use-only-quicklisp-systems\n           #:write-asdf-manifest-file\n           #:where-is-system\n           #:help\n           #:register-local-projects\n           #:local-projects-searcher\n           #:*local-project-directories*\n           #:list-local-projects\n           #:list-local-systems\n           #:who-depends-on\n           #:bundle-systems))\n\n(in-package #:quicklisp-client)\n"
  },
  {
    "path": "quicklisp/quicklisp/progress.lisp",
    "content": ";;;\n;;; A text progress bar\n;;;\n\n(in-package #:ql-progress)\n\n(defclass progress-bar ()\n  ((start-time\n    :initarg :start-time\n    :accessor start-time)\n   (end-time\n    :initarg :end-time\n    :accessor end-time)\n   (progress-character\n    :initarg :progress-character\n    :accessor progress-character)\n   (character-count\n    :initarg :character-count\n    :accessor character-count\n    :documentation \"How many characters wide is the progress bar?\")\n   (characters-so-far\n    :initarg :characters-so-far\n    :accessor characters-so-far)\n   (update-interval\n    :initarg :update-interval\n    :accessor update-interval\n    :documentation \"Update the progress bar display after this many\n    internal-time units.\")\n   (last-update-time\n    :initarg :last-update-time\n    :accessor last-update-time\n    :documentation \"The display was last updated at this time.\")\n   (total\n    :initarg :total\n    :accessor total\n    :documentation \"The total number of units tracked by this progress bar.\")\n   (progress\n    :initarg :progress\n    :accessor progress\n    :documentation \"How far in the progress are we?\")\n   (pending\n    :initarg :pending\n    :accessor pending\n    :documentation \"How many raw units should be tracked in the next\n    display update?\"))\n  (:default-initargs\n   :progress-character #\\=\n   :character-count 50\n   :characters-so-far 0\n   :update-interval (floor internal-time-units-per-second 4)\n   :last-update-time 0\n   :total 0\n   :progress 0\n   :pending 0))\n\n(defgeneric start-display (progress-bar))\n(defgeneric update-progress (progress-bar unit-count))\n(defgeneric update-display (progress-bar))\n(defgeneric finish-display (progress-bar))\n(defgeneric elapsed-time (progress-bar))\n(defgeneric units-per-second (progress-bar))\n\n(defmethod start-display (progress-bar)\n  (setf (last-update-time progress-bar) (get-internal-real-time))\n  (setf (start-time progress-bar) (get-internal-real-time))\n  (fresh-line)\n  (finish-output))\n\n(defmethod update-display (progress-bar)\n  (incf (progress progress-bar) (pending progress-bar))\n  (setf (pending progress-bar) 0)\n  (setf (last-update-time progress-bar) (get-internal-real-time))\n  (let* ((showable (floor (character-count progress-bar)\n                          (/ (total progress-bar) (progress progress-bar))))\n         (needed (- showable (characters-so-far progress-bar))))\n    (setf (characters-so-far progress-bar) showable)\n    (dotimes (i needed)\n      (write-char (progress-character progress-bar)))\n    (finish-output)))\n\n(defmethod update-progress (progress-bar unit-count)\n  (incf (pending progress-bar) unit-count)\n  (let ((now (get-internal-real-time)))\n    (when (< (update-interval progress-bar)\n             (- now (last-update-time progress-bar)))\n      (update-display progress-bar))))\n\n(defmethod finish-display (progress-bar)\n  (update-display progress-bar)\n  (setf (end-time progress-bar) (get-internal-real-time))\n  (terpri)\n  (format t \"~:D bytes in ~$ seconds (~$KB/sec)~%\"\n          (total progress-bar)\n          (elapsed-time progress-bar)\n          (/  (units-per-second progress-bar) 1024))\n  (finish-output))\n\n(defmethod elapsed-time (progress-bar)\n  (/ (- (end-time progress-bar) (start-time progress-bar))\n     internal-time-units-per-second))\n\n(defmethod units-per-second (progress-bar)\n  (if (plusp (elapsed-time progress-bar))\n      (/ (total progress-bar) (elapsed-time progress-bar))\n      0))\n\n(defun kb/sec (progress-bar)\n  (/ (units-per-second progress-bar) 1024))\n\n\n\n(defparameter *uncertain-progress-chars* \"?\")\n\n(defclass uncertain-size-progress-bar (progress-bar)\n  ((progress-char-index\n    :initarg :progress-char-index\n    :accessor progress-char-index)\n   (units-per-char\n    :initarg :units-per-char\n    :accessor units-per-char))\n  (:default-initargs\n   :total 0\n   :progress-char-index 0\n   :units-per-char (floor (expt 1024 2) 50)))\n\n(defmethod update-progress :after ((progress-bar uncertain-size-progress-bar)\n                            unit-count)\n  (incf (total progress-bar) unit-count))\n\n(defmethod progress-character ((progress-bar uncertain-size-progress-bar))\n  (let ((index (progress-char-index progress-bar)))\n    (prog1\n        (char *uncertain-progress-chars* index)\n      (setf (progress-char-index progress-bar)\n            (mod (1+ index) (length *uncertain-progress-chars*))))))\n\n(defmethod update-display ((progress-bar uncertain-size-progress-bar))\n  (setf (last-update-time progress-bar) (get-internal-real-time))\n  (multiple-value-bind (chars pend)\n      (floor (pending progress-bar) (units-per-char progress-bar))\n    (setf (pending progress-bar) pend)\n    (dotimes (i chars)\n      (write-char (progress-character progress-bar))\n      (incf (characters-so-far progress-bar))\n      (when (<= (character-count progress-bar)\n                (characters-so-far progress-bar))\n        (terpri)\n        (setf (characters-so-far progress-bar) 0)\n        (finish-output)))\n    (finish-output)))\n\n(defun make-progress-bar (total)\n  (if (or (not total) (zerop total))\n      (make-instance 'uncertain-size-progress-bar)\n      (make-instance 'progress-bar :total total)))\n\n"
  },
  {
    "path": "quicklisp/quicklisp/quicklisp.asd",
    "content": ";;;; quicklisp.asd\n\n(defpackage #:ql-info\n  (:export #:*version*))\n\n(defvar ql-info:*version*\n  (with-open-file (stream (merge-pathnames \"version.txt\" *load-truename*))\n    (read-line stream)))\n\n(asdf:defsystem #:quicklisp\n  :description \"The Quicklisp client application.\"\n  :author \"Zach Beane <zach@quicklisp.org>\"\n  :license \"BSD-style\"\n  :serial t\n  :version #.(remove-if-not #'digit-char-p ql-info:*version*)\n  :components ((:file \"package\")\n               (:file \"utils\")\n               (:file \"config\")\n               (:file \"impl\")\n               (:file \"impl-util\")\n               (:file \"network\")\n               (:file \"progress\")\n               (:file \"http\")\n               (:file \"deflate\")\n               (:file \"minitar\")\n               (:file \"cdb\")\n               (:file \"dist\")\n               (:file \"setup\")\n               (:file \"client\")\n               (:file \"fetch-gzipped\")\n               (:file \"client-info\")\n               (:file \"client-update\")\n               (:file \"dist-update\")\n               (:file \"misc\")\n               (:file \"local-projects\")\n               (:file \"bundle\")))\n"
  },
  {
    "path": "quicklisp/quicklisp/setup.lisp",
    "content": "(in-package #:quicklisp)\n\n(defun show-wrapped-list (words &key (indent 4) (margin 60))\n (let ((*print-right-margin* margin)\n       (*print-pretty* t)\n       (*print-escape* nil)\n       (prefix (make-string indent :initial-element #\\Space)))\n   (pprint-logical-block (nil words :per-line-prefix prefix)\n     (pprint-fill *standard-output* (sort (copy-seq words) #'string<) nil))\n   (fresh-line)\n   (finish-output)))\n\n(defun recursively-install (name)\n  (labels ((recurse (name)\n             (let ((system (find-system name)))\n               (unless system\n                 (error \"Unknown system ~S\" name))\n               (ensure-installed system)\n               (mapcar #'recurse (required-systems system))\n               name)))\n    (with-consistent-dists\n      (recurse name))))\n\n(defclass load-strategy ()\n  ((name\n    :initarg :name\n    :accessor name)\n   (asdf-systems\n    :initarg :asdf-systems\n    :accessor asdf-systems)\n   (quicklisp-systems\n    :initarg :quicklisp-systems\n    :accessor quicklisp-systems)))\n\n(defmethod print-object ((strategy load-strategy) stream)\n  (print-unreadable-object (strategy stream :type t)\n    (format stream \"~S (~D asdf, ~D quicklisp)\"\n            (name strategy)\n            (length (asdf-systems strategy))\n            (length (quicklisp-systems strategy)))))\n\n(defgeneric quicklisp-releases (strategy)\n  (:method (strategy)\n    (remove-duplicates (mapcar 'release (quicklisp-systems strategy)))))\n\n(defgeneric quicklisp-release-table (strategy)\n  (:method ((strategy load-strategy))\n    (let ((table (make-hash-table)))\n      (dolist (system (quicklisp-systems strategy))\n        (push system (gethash (release system) table nil)))\n      table)))\n\n(define-condition system-not-found (error)\n  ((name\n    :initarg :name\n    :reader system-not-found-name))\n  (:report (lambda (condition stream)\n             (format stream \"System ~S not found\"\n                     (system-not-found-name condition))))\n  (:documentation \"This condition is signaled by QUICKLOAD when a\n  system given to load is not available via ASDF or a Quicklisp\n  dist.\"))\n\n(defun compute-load-strategy (name)\n  (setf name (string-downcase name))\n  (let ((asdf-systems '())\n        (quicklisp-systems '()))\n    (labels ((recurse (name)\n               (let ((asdf-system (asdf:find-system name nil))\n                     (quicklisp-system (find-system name)))\n                 (cond (asdf-system\n                        (push asdf-system asdf-systems))\n                       (quicklisp-system\n                        (push quicklisp-system quicklisp-systems)\n                        (dolist (subname (required-systems quicklisp-system))\n                          (recurse subname)))\n                       (t\n                        (cerror \"Try again\"\n                                'system-not-found\n                                :name name)\n                        (recurse name))))))\n      (with-consistent-dists\n        (recurse name)))\n    (make-instance 'load-strategy\n                   :name name\n                   :asdf-systems (remove-duplicates asdf-systems)\n                   :quicklisp-systems (remove-duplicates quicklisp-systems))))\n\n(defun show-load-strategy (strategy)\n  (format t \"To load ~S:~%\" (name strategy))\n  (let ((asdf-systems (asdf-systems strategy))\n        (releases (quicklisp-releases strategy)))\n    (when asdf-systems\n      (format t \"  Load ~D ASDF system~:P:~%\" (length asdf-systems))\n      (show-wrapped-list (mapcar 'asdf:component-name asdf-systems)))\n    (when releases\n      (format t \"  Install ~D Quicklisp release~:P:~%\" (length releases))\n      (show-wrapped-list (mapcar 'name releases)))))\n\n(defvar *macroexpand-progress-in-progress* nil)\n\n(defun macroexpand-progress-fun (old-hook &key (char #\\.)\n                                 (chars-per-line 50)\n                                 (forms-per-char 250))\n  (let ((output-so-far 0)\n        (seen-so-far 0))\n    (labels ((finish-line ()\n               (when (plusp output-so-far)\n                 (dotimes (i (- chars-per-line output-so-far))\n                   (write-char char))\n                 (terpri)\n                 (setf output-so-far 0)))\n             (show-string (string)\n               (let* ((length (length string))\n                      (new-output (+ length output-so-far)))\n                 (cond ((< chars-per-line new-output)\n                        (finish-line)\n                        (write-string string)\n                        (setf output-so-far length))\n                       (t\n                        (write-string string)\n                        (setf output-so-far new-output))))\n               (finish-output))\n             (show-package (name)\n               ;; Only show package markers when compiling. Showing\n               ;; them when loading shows a bunch of ASDF system\n               ;; package noise.\n               (when *compile-file-pathname*\n                 (finish-line)\n                 (show-string (format nil \"[package ~(~A~)]\" name)))))\n      (lambda (fun form env)\n        (when (and (consp form)\n                   (eq (first form) 'cl:defpackage)\n                   (ignore-errors (string (second form))))\n          (show-package (second form)))\n        (incf seen-so-far)\n        (when (<= forms-per-char seen-so-far)\n          (setf seen-so-far 0)\n          (write-char char)\n          (finish-output)\n          (incf output-so-far)\n          (when (<= chars-per-line output-so-far)\n            (setf output-so-far 0)\n            (terpri)\n            (finish-output)))\n        (funcall old-hook fun form env)))))\n\n(defun call-with-macroexpand-progress (fun)\n  (let ((*macroexpand-hook* (if *macroexpand-progress-in-progress*\n                                *macroexpand-hook*\n                                (macroexpand-progress-fun *macroexpand-hook*)))\n        (*macroexpand-progress-in-progress* t))\n    (funcall fun)\n    (terpri)))\n\n(defun apply-load-strategy (strategy)\n  (map nil 'ensure-installed (quicklisp-releases strategy))\n  (call-with-macroexpand-progress\n   (lambda ()\n     (format t \"~&; Loading ~S~%\" (name strategy))\n     (asdf:load-system (name strategy) :verbose nil))))\n\n(defun call-with-autoloading-system-and-dependencies (name fn &key prompt)\n  (with-simple-restart (abort \"Give up on ~S\" name)\n    (let ((tried-so-far (make-hash-table :test 'equalp)))\n      (tagbody\n       retry\n         (handler-case\n             (funcall fn)\n           (asdf:missing-dependency-of-version (c)\n             ;; Nothing Quicklisp can do to recover from this, so just\n             ;; resignal\n             (error c))\n           (asdf:missing-dependency (c)\n             (let ((parent (asdf::missing-required-by c))\n                   (missing (asdf::missing-requires c)))\n               (typecase parent\n                 ((or null asdf:system)\n                  ;; NIL parent comes from :defsystem-depends-on failures\n                  (if (gethash missing tried-so-far)\n                      (error \"Dependency looping -- already tried to load ~\n                                 ~A\" missing)\n                      (setf (gethash missing tried-so-far) missing))\n                  (autoload-system-and-dependencies missing\n                                                    :prompt prompt)\n                  (go retry))\n                 (t\n                  ;; Error isn't from a system dependency, so there's\n                  ;; nothing to autoload\n                  (error c))))))))\n    name))\n\n(defun autoload-system-and-dependencies (name &key prompt)\n  \"Try to load the system named by NAME, automatically loading any\nQuicklisp-provided systems first, and catching ASDF missing\ndependencies too if possible.\"\n  (setf name (string-downcase name))\n  (call-with-autoloading-system-and-dependencies\n   name\n   (lambda ()\n    (let ((strategy (compute-load-strategy name)))\n      (show-load-strategy strategy)\n      (when (or (not prompt)\n                (press-enter-to-continue))\n        (apply-load-strategy strategy))))))\n\n(defvar *initial-dist-url*\n  \"http://beta.quicklisp.org/dist/quicklisp.txt\")\n\n(defun dists-initialized-p ()\n  (not (not (ignore-errors (truename (qmerge \"dists/\"))))))\n\n(defun quickstart-parameter (name &optional default)\n  (let* ((package (find-package '#:quicklisp-quickstart))\n         (symbol (and package (find-symbol (string '#:*quickstart-parameters*)\n                                           package)))\n         (plist (and symbol (symbol-value symbol)))\n         (parameter (and plist (getf plist name))))\n    (or parameter default)))\n\n(defun maybe-initial-setup ()\n  \"Run the steps needed when Quicklisp setup is run for the first time\nafter the quickstart installation.\"\n  (let ((quickstart-proxy-url (quickstart-parameter :proxy-url))\n        (quickstart-initial-dist-url (quickstart-parameter :initial-dist-url)))\n    (when (and quickstart-proxy-url (not *proxy-url*))\n      (setf *proxy-url* quickstart-proxy-url)\n      (setf (config-value \"proxy-url\") quickstart-proxy-url))\n    (unless (dists-initialized-p)\n      (let ((target (qmerge \"dists/quicklisp/distinfo.txt\"))\n            (url (or quickstart-initial-dist-url\n                     *initial-dist-url*)))\n        (ensure-directories-exist target)\n        (install-dist url :prompt nil)))))\n\n(defun setup ()\n  (unless (member 'system-definition-searcher\n                  asdf:*system-definition-search-functions*)\n    (setf asdf:*system-definition-search-functions*\n          (append asdf:*system-definition-search-functions*\n                  (list 'local-projects-searcher\n                        'system-definition-searcher))))\n  (let ((files (nconc (directory (qmerge \"local-init/*.lisp\"))\n                      (directory (qmerge \"local-init/*.cl\")))))\n    (with-simple-restart (abort \"Stop loading local setup files\")\n      (dolist (file (sort files #'string< :key #'pathname-name))\n        (with-simple-restart (skip \"Skip local setup file ~S\" file)\n          ;; Don't try to load Emacs lock files, other hidden files\n          (unless (char= (char (pathname-name file) 0)\n                         #\\.)\n            (load file))))))\n  (maybe-initial-setup)\n  (ensure-directories-exist (qmerge \"local-projects/\"))\n  (pushnew :quicklisp *features*)\n  t)\n"
  },
  {
    "path": "quicklisp/quicklisp/utils.lisp",
    "content": ";;;; utils.lisp\n\n(in-package #:ql-util)\n\n(defun write-line-to-file (string file)\n  (with-open-file (stream file\n                          :direction :output\n                          :if-exists :supersede)\n    (write-line string stream)))\n\n(defvar *do-not-prompt* nil\n  \"When *DO-NOT-PROMPT* is true, PRESS-ENTER-TO-CONTINUE returns true\n  without user interaction.\")\n\n(defmacro without-prompting (&body body)\n  \"Evaluate BODY in an environment where PRESS-ENTER-TO-CONTINUE\n always returns true without prompting for the user to press enter.\"\n  `(let ((*do-not-prompt* t))\n     ,@body))\n\n(defun press-enter-to-continue ()\n  (when *do-not-prompt*\n    (return-from press-enter-to-continue t))\n  (format *query-io* \"~&Press Enter to continue.~%\")\n  (let ((result (read-line *query-io*)))\n    (zerop (length result))))\n\n(defun replace-file (from to)\n  \"Like RENAME-FILE, but deletes TO if it exists, first.\"\n  (when (probe-file to)\n    (delete-file to))\n  (rename-file from to))\n\n(defun copy-file (from to &key (if-exists :rename-and-delete))\n  \"Copy the file FROM to TO.\"\n  (let* ((buffer-size 8192)\n         (buffer (make-array buffer-size :element-type '(unsigned-byte 8))))\n    (with-open-file (from-stream from :element-type '(unsigned-byte 8))\n      (with-open-file (to-stream to :element-type '(unsigned-byte 8)\n                                 :direction :output\n                                 :if-exists if-exists)\n        (let ((length (file-length from-stream)))\n          (multiple-value-bind (full leftover)\n              (floor length buffer-size)\n            (dotimes (i full)\n              (read-sequence buffer from-stream)\n              (write-sequence buffer to-stream))\n            (read-sequence buffer from-stream)\n            (write-sequence buffer to-stream :end leftover)))))\n    (probe-file to)))\n\n(defun ensure-file-exists (pathname)\n  (open pathname :direction :probe :if-does-not-exist :create))\n\n(defun delete-file-if-exists (pathname)\n  (when (probe-file pathname)\n    (delete-file pathname)))\n\n(defun split-spaces (line)\n  (let ((words '())\n        (mark 0)\n        (pos 0))\n    (labels ((finish ()\n               (setf pos (length line))\n               (save)\n               (return-from split-spaces (nreverse words)))\n             (save ()\n               (when (< mark pos)\n                 (push (subseq line mark pos) words)))\n             (mark ()\n               (setf mark pos))\n             (in-word (char)\n               (case char\n                 (#\\Space\n                    (save)\n                    #'in-space)\n                 (t\n                    #'in-word)))\n             (in-space (char)\n               (case char\n                 (#\\Space\n                    #'in-space)\n                 (t\n                    (mark)\n                    #'in-word))))\n      (let ((state #'in-word))\n        (dotimes (i (length line) (finish))\n          (setf pos i)\n          (setf state (funcall state (char line i))))))))\n\n(defun first-line (file)\n  (with-open-file (stream file)\n    (values (read-line stream))))\n\n(defun (setf first-line) (line file)\n  (with-open-file (stream file :direction :output\n                          :if-exists :rename-and-delete)\n    (write-line line stream)))\n\n(defun file-size (file)\n  (with-open-file (stream file :element-type '(unsigned-byte 8))\n    (file-length stream)))\n\n(defun safely-read (stream)\n  \"Read one form from STREAM with *READ-EVAL* bound to NIL.\"\n  (let ((*read-eval* nil))\n    (read stream)))\n\n(defun safely-read-file (file)\n  \"Read the first form from FILE with SAFELY-READ.\"\n  (with-open-file (stream file)\n    (safely-read stream)))\n\n(defun make-versions-url (url)\n  \"Given an URL that looks like http://foo/bar.ext, return\nhttp://foo/bar-versions.txt.\"\n  (let ((suffix-pos (position #\\. url :from-end t)))\n    (unless suffix-pos\n      (error \"Can't make a versions URL from ~A\" url))\n    (let ((extension (subseq url suffix-pos)))\n      (concatenate 'string\n                   (subseq url 0 suffix-pos)\n                   \"-versions\"\n                   extension))))\n\n(defun call-with-temporary-file (fun template-pathname)\n  (assert (null (pathname-directory template-pathname)))\n  (let* ((relative-file (merge-pathnames template-pathname\n                                        #p\"tmp/\"))\n         (absolute-file (ql-setup:qmerge relative-file))\n         (randomized-file (make-pathname :name (format nil \"~A-~36,5,'0R\"\n                                                       (pathname-name template-pathname)\n                                                       (random #xFFFFFF))\n                                         :defaults absolute-file)))\n    (unwind-protect\n        (funcall fun randomized-file)\n      (delete-file-if-exists randomized-file))))\n\n;;; TODO: Use this where (qmerge \"tmp/...\") is used, when possible\n(defmacro with-temporary-file ((var template) &body body)\n  \"Evaluate BODY with VAR bound to a temporary pathname created by\nadding random data to the pathname-name of TEMPLATE, which should be a\npathname without a directory component. After evaluation, the\ntemporary pathname is deleted if it exists.\"\n  `(call-with-temporary-file (lambda (,var) ,@body) ,template))\n"
  },
  {
    "path": "quicklisp/quicklisp/version.txt",
    "content": "2021-02-13\n"
  },
  {
    "path": "quicklisp/setup.lisp",
    "content": "(defpackage #:ql-setup\n  (:use #:cl)\n  (:export #:*quicklisp-home*\n           #:qmerge\n           #:qenough))\n\n(in-package #:ql-setup)\n\n(unless *load-truename*\n  (error \"This file must be LOADed to set up quicklisp.\"))\n\n(defvar *quicklisp-home*\n  (make-pathname :name nil :type nil\n                 :defaults *load-truename*))\n\n(defun qmerge (pathname)\n  \"Return PATHNAME merged with the base Quicklisp directory.\"\n  (merge-pathnames pathname *quicklisp-home*))\n\n(defun qenough (pathname)\n  (enough-namestring pathname *quicklisp-home*))\n\n;;; ASDF is a hard requirement of quicklisp. Make sure it's either\n;;; already loaded or load it from quicklisp's bundled version.\n\n(defvar *required-asdf-version* \"3.0\")\n\n;;; Put ASDF's fasls in a separate directory\n\n(defun implementation-signature ()\n  \"Return a string suitable for discriminating different\nimplementations, or similar implementations with possibly-incompatible\nFASLs.\"\n  ;; XXX Will this have problems with stuff like threads vs\n  ;; non-threads fasls?\n  (let ((*print-pretty* nil))\n    (format nil \"lisp-implementation-type: ~A~%~\n                 lisp-implementation-version: ~A~%~\n                 machine-type: ~A~%~\n                 machine-version: ~A~%\"\n            (lisp-implementation-type)\n            (lisp-implementation-version)\n            (machine-type)\n            (machine-version))))\n\n(defun dumb-string-hash (string)\n  \"Produce a six-character hash of STRING.\"\n  (let ((hash #xD13CCD13))\n    (loop for char across string\n          for value = (char-code char)\n          do\n          (setf hash (logand #xFFFFFFFF\n                             (logxor (ash hash 5)\n                                     (ash hash -27)\n                                     value))))\n    (subseq (format nil \"~(~36,6,'0R~)\" (mod hash 88888901))\n            0 6)))\n\n(defun asdf-fasl-pathname ()\n  \"Return a pathname suitable for storing the ASDF FASL, separated\nfrom ASDF FASLs from incompatible implementations. Also, save a file\nin the directory with the implementation signature, if it doesn't\nalready exist.\"\n  (let* ((implementation-signature (implementation-signature))\n         (original-fasl (compile-file-pathname (qmerge \"asdf.lisp\")))\n         (fasl\n          (qmerge (make-pathname\n                   :defaults original-fasl\n                   :directory\n                   (list :relative\n                         \"cache\"\n                         \"asdf-fasls\"\n                         (dumb-string-hash implementation-signature)))))\n         (signature-file (merge-pathnames \"signature.txt\" fasl)))\n    (ensure-directories-exist fasl)\n    (unless (probe-file signature-file)\n      (with-open-file (stream signature-file :direction :output)\n        (write-string implementation-signature stream)))\n    fasl))\n\n(defun ensure-asdf-loaded ()\n  \"Try several methods to make sure that a sufficiently-new ASDF is\nloaded: first try (require \\\"asdf\\\"), then loading the ASDF FASL, then\ncompiling asdf.lisp to a FASL and then loading it.\"\n  (let ((source (qmerge \"asdf.lisp\")))\n    (labels ((asdf-symbol (name)\n               (let ((asdf-package (find-package '#:asdf)))\n                 (when asdf-package\n                   (find-symbol (string name) asdf-package))))\n             (version-satisfies (version)\n               (let ((vs-fun (asdf-symbol '#:version-satisfies))\n                     (vfun (asdf-symbol '#:asdf-version)))\n                 (when (and vs-fun vfun\n                            (fboundp vs-fun)\n                            (fboundp vfun))\n                   (funcall vs-fun (funcall vfun) version)))))\n      (block nil\n        (macrolet ((try (&body asdf-loading-forms)\n                     `(progn\n                        (handler-bind ((warning #'muffle-warning))\n                          (ignore-errors\n                            ,@asdf-loading-forms))\n                        (when (version-satisfies *required-asdf-version*)\n                          (return t)))))\n          (try)\n          (try (require \"asdf\"))\n          (let ((fasl (asdf-fasl-pathname)))\n            (try (load fasl :verbose nil))\n            (try (load (compile-file source :verbose nil :output-file fasl))))\n          (error \"Could not load ASDF ~S or newer\" *required-asdf-version*))))))\n\n(ensure-asdf-loaded)\n\n;;;\n;;; Quicklisp sometimes must upgrade ASDF. Ugrading ASDF will blow\n;;; away existing ASDF methods, so e.g. FASL recompilation :around\n;;; methods would be lost. This config file will make it possible to\n;;; ensure ASDF can be configured before loading Quicklisp itself via\n;;; ASDF. Thanks to Nikodemus Siivola for pointing out this issue.\n;;;\n\n(let ((asdf-init (probe-file (qmerge \"asdf-config/init.lisp\"))))\n  (when asdf-init\n    (with-simple-restart (skip \"Skip loading ~S\" asdf-init)\n      (load asdf-init :verbose nil :print nil))))\n\n(push (qmerge \"quicklisp/\") asdf:*central-registry*)\n\n(let ((*compile-print* nil)\n      (*compile-verbose* nil)\n      (*load-verbose* nil)\n      (*load-print* nil))\n  (asdf:oos 'asdf:load-op \"quicklisp\" :verbose nil))\n\n(quicklisp:setup)\n"
  },
  {
    "path": "scripts/asdf.lisp",
    "content": ";;; -*- mode: Lisp; Base: 10 ; Syntax: ANSI-Common-Lisp ; Package: CL-USER ; buffer-read-only: t; -*-\n;;; This is ASDF 3.3.7: Another System Definition Facility.\n;;;\n;;; Feedback, bug reports, and patches are all welcome:\n;;; please mail to <asdf-devel@common-lisp.net>.\n;;; Note first that the canonical source for ASDF is presently\n;;; <URL:http://common-lisp.net/project/asdf/>.\n;;;\n;;; If you obtained this copy from anywhere else, and you experience\n;;; trouble using it, or find bugs, you may want to check at the\n;;; location above for a more recent version (and for documentation\n;;; and test files, if your copy came without them) before reporting\n;;; bugs.  There are usually two \"supported\" revisions - the git master\n;;; branch is the latest development version, whereas the git release\n;;; branch may be slightly older but is considered `stable'\n\n;;; -- LICENSE START\n;;; (This is the MIT / X Consortium license as taken from\n;;;  http://www.opensource.org/licenses/mit-license.html on or about\n;;;  Monday; July 13, 2009)\n;;;\n;;; Copyright (c) 2001-2019 Daniel Barlow and contributors\n;;;\n;;; Permission is hereby granted, free of charge, to any person obtaining\n;;; a copy of this software and associated documentation files (the\n;;; \"Software\"), to deal in the Software without restriction, including\n;;; without limitation the rights to use, copy, modify, merge, publish,\n;;; distribute, sublicense, and/or sell copies of the Software, and to\n;;; permit persons to whom the Software is furnished to do so, subject to\n;;; the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n;;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n;;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n;;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n;;;\n;;; -- LICENSE END\n\n;;; The problem with writing a defsystem replacement is bootstrapping:\n;;; we can't use defsystem to compile it.  Hence, all in one file.\n\n#+genera\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (multiple-value-bind (system-major system-minor)\n      (sct:get-system-version)\n    (multiple-value-bind (is-major is-minor)\n\t(sct:get-system-version \"Intel-Support\")\n      (unless (or (> system-major 452)\n\t\t  (and is-major\n\t\t       (or (> is-major 3)\n\t\t\t   (and (= is-major 3) (> is-minor 86)))))\n\t(error \"ASDF requires either System 453 or later or Intel Support 3.87 or later\")))))\n;;;; ---------------------------------------------------------------------------\n;;;; ASDF package upgrade, including implementation-dependent magic.\n;;\n;; See https://bugs.launchpad.net/asdf/+bug/485687\n;;\n\n;; CAUTION: The definition of the UIOP/PACKAGE package MUST NOT CHANGE,\n;; NOT NOW, NOT EVER, NOT UNDER ANY CIRCUMSTANCE. NEVER.\n;; ... and the same goes for UIOP/PACKAGE-LOCAL-NICKNAMES.\n;;\n;; The entire point of UIOP/PACKAGE is to address the fact that the CL standard\n;; *leaves it unspecified what happens when a package is redefined incompatibly*.\n;; For instance, SBCL 1.4.2 will signal a full WARNING when this happens,\n;; throwing a wrench in upgrading code with ASDF itself, while continuing to\n;; export old symbols it now shouldn't as it also exports new ones,\n;; causing problems with code that relies on the new/current exports.\n;; CLISP and CCL also exports both sets of symbols, though without any WARNING.\n;; ABCL 1.6.1 will plainly ignore the new definition.\n;; Other implementations may do whatever they want and change their behavior at any time.\n;; ***Using DEFPACKAGE twice with different definitions is nasal-demon territory.***\n;;\n;; Thus we define UIOP/PACKAGE:DEFINE-PACKAGE with which packages can be defined\n;; in an upgrade-friendly way: the new definition is authoritative, and\n;; the package will define and export exactly those symbols in the new definition,\n;; no more and no fewer, whereas it is well-defined what happens to previous symbols.\n;; However, for obvious bootstrap reasons, we cannot use DEFINE-PACKAGE\n;; to define UIOP/PACKAGE itself, only DEFPACKAGE.\n;; Therefore, unlike the other packages in ASDF, UIOP/PACKAGE is immutable,\n;; now and forever. It is frozen for the aeons to come, like the CL package itself,\n;; to the same exact state it was defined at its inception, in ASDF 2.27 in 2013.\n;; The same goes for UIOP/PACKAGE-LOCAL-NICKNAMES, that we use internally.\n;;\n;; If you ever must define new symbols in this file, you can and must\n;; export them from a different package, possibly defined in the same file,\n;; say a package UIOP/PACKAGE* defined at the end of this file with DEFINE-PACKAGE,\n;; that might use :import-from to import the symbols from UIOP/PACKAGE,\n;; if you must somehow define them in UIOP/PACKAGE.\n\n(defpackage :uiop/package ;;; THOU SHALT NOT modify this definition, EVER. See explanations above.\n  (:use :common-lisp)\n  (:export\n   #:find-package* #:find-symbol* #:symbol-call\n   #:intern* #:export* #:import* #:shadowing-import* #:shadow* #:make-symbol* #:unintern*\n   #:symbol-shadowing-p #:home-package-p\n   #:symbol-package-name #:standard-common-lisp-symbol-p\n   #:reify-package #:unreify-package #:reify-symbol #:unreify-symbol\n   #:nuke-symbol-in-package #:nuke-symbol #:rehome-symbol\n   #:ensure-package-unused #:delete-package*\n   #:package-names #:packages-from-names #:fresh-package-name #:rename-package-away\n   #:package-definition-form #:parse-define-package-form\n   #:ensure-package #:define-package\n   ))\n\n(in-package :uiop/package)\n\n;;; package local nicknames feature.\n;;; This can't be deferred until common-lisp.lisp, where most such features are set.\n;;; ABCL and CCL already define this feature appropriately.\n;;; Seems to be unconditionally present for SBCL, ACL, and CLASP\n;;; Don't know about ECL, or others\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  ;; ABCL pushes :package-local-nicknames without UIOP interfering,\n  ;; and Lispworks will do so\n  #+(or sbcl clasp)\n  (pushnew :package-local-nicknames *features*)\n  #+allegro\n  (let ((fname (find-symbol (symbol-name '#:add-package-local-nickname) '#:excl)))\n    (when (and fname (fboundp fname))\n      (pushnew :package-local-nicknames *features*))))\n\n;;; THOU SHALT NOT modify this definition, EVER, *EXCEPT* to add a new implementation.\n;; If you somehow need to modify the API in any way,\n;; you will need to create another, differently named, and just as immutable package.\n#+package-local-nicknames\n(defpackage :uiop/package-local-nicknames\n  (:use :cl)\n  (:import-from\n   #+allegro #:excl\n   #+sbcl #:sb-ext\n   #+(or clasp abcl ecl) #:ext\n   #+ccl #:ccl\n   #+lispworks #:hcl\n   #-(or allegro sbcl clasp abcl ccl lispworks ecl)\n   (error \"Don't know from which package this lisp supplies the local-package-nicknames API.\")\n   #:remove-package-local-nickname #:package-local-nicknames #:add-package-local-nickname)\n  (:export\n   #:add-package-local-nickname #:remove-package-local-nickname #:package-local-nicknames))\n\n;;;; General purpose package utilities\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (deftype package-designator () '(and (or package character string symbol) (satisfies find-package)))\n  (define-condition no-such-package-error (type-error)\n    ()\n    (:default-initargs :expected-type 'package-designator)\n    (:report (lambda (c s)\n              (format s \"No package named ~a\" (string (type-error-datum c))))))\n\n  (defmethod package-designator ((c no-such-package-error))\n    (type-error-datum c))\n\n  (defun find-package* (package-designator &optional (errorp t))\n    \"Like CL:FIND-PACKAGE, but by default raises a UIOP:NO-SUCH-PACKAGE-ERROR if the\n  package is not found.\"\n    (let ((package (find-package package-designator)))\n      (cond\n        (package package)\n        (errorp (error 'no-such-package-error :datum package-designator))\n        (t nil))))\n\n  (defun find-symbol* (name package-designator &optional (error t))\n    \"Find a symbol in a package of given string'ified NAME;\nunlike CL:FIND-SYMBOL, work well with 'modern' case sensitive syntax\nby letting you supply a symbol or keyword for the name;\nalso works well when the package is not present.\nIf optional ERROR argument is NIL, return NIL instead of an error\nwhen the symbol is not found.\"\n    (block nil\n      (let ((package (find-package* package-designator error)))\n        (when package ;; package error handled by find-package* already\n          (multiple-value-bind (symbol status) (find-symbol (string name) package)\n            (cond\n              (status (return (values symbol status)))\n              (error (error \"There is no symbol ~S in package ~S\" name (package-name package))))))\n        (values nil nil))))\n  (defun symbol-call (package name &rest args)\n    \"Call a function associated with symbol of given name in given package,\nwith given ARGS. Useful when the call is read before the package is loaded,\nor when loading the package is optional.\"\n    (apply (find-symbol* name package) args))\n  (defun intern* (name package-designator &optional (error t))\n    (intern (string name) (find-package* package-designator error)))\n  (defun export* (name package-designator)\n    (let* ((package (find-package* package-designator))\n           (symbol (intern* name package)))\n      (export (or symbol (list symbol)) package)))\n  (defun import* (symbol package-designator)\n    (import (or symbol (list symbol)) (find-package* package-designator)))\n  (defun shadowing-import* (symbol package-designator)\n    (shadowing-import (or symbol (list symbol)) (find-package* package-designator)))\n  (defun shadow* (name package-designator)\n    (shadow (list (string name)) (find-package* package-designator)))\n  (defun make-symbol* (name)\n    (etypecase name\n      (string (make-symbol name))\n      (symbol (copy-symbol name))))\n  (defun unintern* (name package-designator &optional (error t))\n    (block nil\n      (let ((package (find-package* package-designator error)))\n        (when package\n          (multiple-value-bind (symbol status) (find-symbol* name package error)\n            (cond\n              (status (unintern symbol package)\n                      (return (values symbol status)))\n              (error (error \"symbol ~A not present in package ~A\"\n                            (string symbol) (package-name package))))))\n        (values nil nil))))\n  (defun symbol-shadowing-p (symbol package)\n    (and (member symbol (package-shadowing-symbols package)) t))\n  (defun home-package-p (symbol package)\n    (and package (let ((sp (symbol-package symbol)))\n                   (and sp (let ((pp (find-package* package)))\n                             (and pp (eq sp pp))))))))\n\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun symbol-package-name (symbol)\n    (let ((package (symbol-package symbol)))\n      (and package (package-name package))))\n  (defun standard-common-lisp-symbol-p (symbol)\n    (multiple-value-bind (sym status) (find-symbol* symbol :common-lisp nil)\n      (and (eq sym symbol) (eq status :external))))\n  (defun reify-package (package &optional package-context)\n    (if (eq package package-context) t\n        (etypecase package\n          (null nil)\n          ((eql (find-package :cl)) :cl)\n          (package (package-name package)))))\n  (defun unreify-package (package &optional package-context)\n    (etypecase package\n      (null nil)\n      ((eql t) package-context)\n      ((or symbol string) (find-package package))))\n  (defun reify-symbol (symbol &optional package-context)\n    (etypecase symbol\n      ((or keyword (satisfies standard-common-lisp-symbol-p)) symbol)\n      (symbol (vector (symbol-name symbol)\n                      (reify-package (symbol-package symbol) package-context)))))\n  (defun unreify-symbol (symbol &optional package-context)\n    (etypecase symbol\n      (symbol symbol)\n      ((simple-vector 2)\n       (let* ((symbol-name (svref symbol 0))\n              (package-foo (svref symbol 1))\n              (package (unreify-package package-foo package-context)))\n         (if package (intern* symbol-name package)\n             (make-symbol* symbol-name)))))))\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defvar *all-package-happiness* '())\n  (defvar *all-package-fishiness* (list t))\n  (defun record-fishy (info)\n    ;;(format t \"~&FISHY: ~S~%\" info)\n    (push info *all-package-fishiness*))\n  (defmacro when-package-fishiness (&body body)\n    `(when *all-package-fishiness* ,@body))\n  (defmacro note-package-fishiness (&rest info)\n    `(when-package-fishiness (record-fishy (list ,@info)))))\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  #+(or clisp clozure)\n  (defun get-setf-function-symbol (symbol)\n    #+clisp (let ((sym (get symbol 'system::setf-function)))\n              (if sym (values sym :setf-function)\n                  (let ((sym (get symbol 'system::setf-expander)))\n                    (if sym (values sym :setf-expander)\n                        (values nil nil)))))\n    #+clozure (gethash symbol ccl::%setf-function-names%))\n  #+(or clisp clozure)\n  (defun set-setf-function-symbol (new-setf-symbol symbol &optional kind)\n    #+clisp (assert (member kind '(:setf-function :setf-expander)))\n    #+clozure (assert (eq kind t))\n    #+clisp\n    (cond\n      ((null new-setf-symbol)\n       (remprop symbol 'system::setf-function)\n       (remprop symbol 'system::setf-expander))\n      ((eq kind :setf-function)\n       (setf (get symbol 'system::setf-function) new-setf-symbol))\n      ((eq kind :setf-expander)\n       (setf (get symbol 'system::setf-expander) new-setf-symbol))\n      (t (error \"invalid kind of setf-function ~S for ~S to be set to ~S\"\n                kind symbol new-setf-symbol)))\n    #+clozure\n    (progn\n      (gethash symbol ccl::%setf-function-names%) new-setf-symbol\n      (gethash new-setf-symbol ccl::%setf-function-name-inverses%) symbol))\n  #+(or clisp clozure)\n  (defun create-setf-function-symbol (symbol)\n    #+clisp (system::setf-symbol symbol)\n    #+clozure (ccl::construct-setf-function-name symbol))\n  (defun set-dummy-symbol (symbol reason other-symbol)\n    (setf (get symbol 'dummy-symbol) (cons reason other-symbol)))\n  (defun make-dummy-symbol (symbol)\n    (let ((dummy (copy-symbol symbol)))\n      (set-dummy-symbol dummy 'replacing symbol)\n      (set-dummy-symbol symbol 'replaced-by dummy)\n      dummy))\n  (defun dummy-symbol (symbol)\n    (get symbol 'dummy-symbol))\n  (defun get-dummy-symbol (symbol)\n    (let ((existing (dummy-symbol symbol)))\n      (if existing (values (cdr existing) (car existing))\n          (make-dummy-symbol symbol))))\n  (defun nuke-symbol-in-package (symbol package-designator)\n    (let ((package (find-package* package-designator))\n          (name (symbol-name symbol)))\n      (multiple-value-bind (sym stat) (find-symbol name package)\n        (when (and (member stat '(:internal :external)) (eq symbol sym))\n          (if (symbol-shadowing-p symbol package)\n              (shadowing-import* (get-dummy-symbol symbol) package)\n              (unintern* symbol package))))))\n  (defun nuke-symbol (symbol &optional (packages (list-all-packages)))\n    #+(or clisp clozure)\n    (multiple-value-bind (setf-symbol kind)\n        (get-setf-function-symbol symbol)\n      (when kind (nuke-symbol setf-symbol)))\n    (loop :for p :in packages :do (nuke-symbol-in-package symbol p)))\n  (defun rehome-symbol (symbol package-designator)\n    \"Changes the home package of a symbol, also leaving it present in its old home if any\"\n    (let* ((name (symbol-name symbol))\n           (package (find-package* package-designator))\n           (old-package (symbol-package symbol))\n           (old-status (and old-package (nth-value 1 (find-symbol name old-package))))\n           (shadowing (and old-package (symbol-shadowing-p symbol old-package) (make-symbol name))))\n      (multiple-value-bind (overwritten-symbol overwritten-symbol-status) (find-symbol name package)\n        (unless (eq package old-package)\n          (let ((overwritten-symbol-shadowing-p\n                  (and overwritten-symbol-status\n                       (symbol-shadowing-p overwritten-symbol package))))\n            (note-package-fishiness\n             :rehome-symbol name\n             (when old-package (package-name old-package)) old-status (and shadowing t)\n             (package-name package) overwritten-symbol-status overwritten-symbol-shadowing-p)\n            (when old-package\n              (if shadowing\n                  (shadowing-import* shadowing old-package))\n              (unintern* symbol old-package))\n            (cond\n              (overwritten-symbol-shadowing-p\n               (shadowing-import* symbol package))\n              (t\n               (when overwritten-symbol-status\n                 (unintern* overwritten-symbol package))\n               (import* symbol package)))\n            (if shadowing\n                (shadowing-import* symbol old-package)\n                (import* symbol old-package))\n            #+(or clisp clozure)\n            (multiple-value-bind (setf-symbol kind)\n                (get-setf-function-symbol symbol)\n              (when kind\n                (let* ((setf-function (fdefinition setf-symbol))\n                       (new-setf-symbol (create-setf-function-symbol symbol)))\n                  (note-package-fishiness\n                   :setf-function\n                   name (package-name package)\n                   (symbol-name setf-symbol) (symbol-package-name setf-symbol)\n                   (symbol-name new-setf-symbol) (symbol-package-name new-setf-symbol))\n                  (when (symbol-package setf-symbol)\n                    (unintern* setf-symbol (symbol-package setf-symbol)))\n                  (setf (fdefinition new-setf-symbol) setf-function)\n                  (set-setf-function-symbol new-setf-symbol symbol kind))))\n            #+(or clisp clozure)\n            (multiple-value-bind (overwritten-setf foundp)\n                (get-setf-function-symbol overwritten-symbol)\n              (when foundp\n                (unintern overwritten-setf)))\n            (when (eq old-status :external)\n              (export* symbol old-package))\n            (when (eq overwritten-symbol-status :external)\n              (export* symbol package))))\n        (values overwritten-symbol overwritten-symbol-status))))\n  (defun ensure-package-unused (package)\n    (loop :for p :in (package-used-by-list package) :do\n      (unuse-package package p)))\n  (defun delete-package* (package &key nuke)\n    (let ((p (find-package package)))\n      (when p\n        (when nuke (do-symbols (s p) (when (home-package-p s p) (nuke-symbol s))))\n        (ensure-package-unused p)\n        (delete-package package))))\n  (defun package-names (package)\n    (cons (package-name package) (package-nicknames package)))\n  (defun packages-from-names (names)\n    (remove-duplicates (remove nil (mapcar #'find-package names)) :from-end t))\n  (defun fresh-package-name (&key (prefix :%TO-BE-DELETED)\n                               separator\n                               (index (random most-positive-fixnum)))\n    (loop :for i :from index\n          :for n = (format nil \"~A~@[~A~D~]\" prefix (and (plusp i) (or separator \"\")) i)\n          :thereis (and (not (find-package n)) n)))\n  (defun rename-package-away (p &rest keys &key prefix &allow-other-keys)\n    (let ((new-name\n            (apply 'fresh-package-name\n                   :prefix (or prefix (format nil \"__~A__\" (package-name p))) keys)))\n      (record-fishy (list :rename-away (package-names p) new-name))\n      (rename-package p new-name))))\n\n\n;;; Communicable representation of symbol and package information\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun package-definition-form (package-designator\n                                  &key (nicknamesp t) (usep t)\n                                    (shadowp t) (shadowing-import-p t)\n                                    (exportp t) (importp t) internp (error t))\n    (let* ((package (or (find-package* package-designator error)\n                        (return-from package-definition-form nil)))\n           (name (package-name package))\n           (nicknames (package-nicknames package))\n           (use (mapcar #'package-name (package-use-list package)))\n           (shadow ())\n           (shadowing-import (make-hash-table :test 'equal))\n           (import (make-hash-table :test 'equal))\n           (export ())\n           (intern ()))\n      (when package\n        (loop :for sym :being :the :symbols :in package\n              :for status = (nth-value 1 (find-symbol* sym package)) :do\n                (ecase status\n                  ((nil :inherited))\n                  ((:internal :external)\n                   (let* ((name (symbol-name sym))\n                          (external (eq status :external))\n                          (home (symbol-package sym))\n                          (home-name (package-name home))\n                          (imported (not (eq home package)))\n                          (shadowing (symbol-shadowing-p sym package)))\n                     (cond\n                       ((and shadowing imported)\n                        (push name (gethash home-name shadowing-import)))\n                       (shadowing\n                        (push name shadow))\n                       (imported\n                        (push name (gethash home-name import))))\n                     (cond\n                       (external\n                        (push name export))\n                       (imported)\n                       (t (push name intern)))))))\n        (labels ((sort-names (names)\n                   (sort (copy-list names) #'string<))\n                 (table-keys (table)\n                   (loop :for k :being :the :hash-keys :of table :collect k))\n                 (when-relevant (key value)\n                   (when value (list (cons key value))))\n                 (import-options (key table)\n                   (loop :for i :in (sort-names (table-keys table))\n                         :collect `(,key ,i ,@(sort-names (gethash i table))))))\n          `(defpackage ,name\n             ,@(when-relevant :nicknames (and nicknamesp (sort-names nicknames)))\n             (:use ,@(and usep (sort-names use)))\n             ,@(when-relevant :shadow (and shadowp (sort-names shadow)))\n             ,@(import-options :shadowing-import-from (and shadowing-import-p shadowing-import))\n             ,@(import-options :import-from (and importp import))\n             ,@(when-relevant :export (and exportp (sort-names export)))\n             ,@(when-relevant :intern (and internp (sort-names intern)))))))))\n\n\n;;; ensure-package, define-package\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  ;; We already have UIOP:SIMPLE-STYLE-WARNING, but it comes from a later\n  ;; package.\n  (define-condition define-package-style-warning\n      #+sbcl (sb-int:simple-style-warning) #-sbcl (simple-condition style-warning)\n      ())\n  (defun ensure-shadowing-import (name to-package from-package shadowed imported)\n    (check-type name string)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (let ((import-me (find-symbol* name from-package)))\n      (multiple-value-bind (existing status) (find-symbol name to-package)\n        (cond\n          ((gethash name shadowed)\n           (unless (eq import-me existing)\n             (error \"Conflicting shadowings for ~A\" name)))\n          (t\n           (setf (gethash name shadowed) t)\n           (setf (gethash name imported) t)\n           (unless (or (null status)\n                       (and (member status '(:internal :external))\n                            (eq existing import-me)\n                            (symbol-shadowing-p existing to-package)))\n             (note-package-fishiness\n              :shadowing-import name\n              (package-name from-package)\n              (or (home-package-p import-me from-package) (symbol-package-name import-me))\n              (package-name to-package) status\n              (and status (or (home-package-p existing to-package) (symbol-package-name existing)))))\n           (shadowing-import* import-me to-package))))))\n  (defun ensure-imported (import-me into-package &optional from-package)\n    (check-type import-me symbol)\n    (check-type into-package package)\n    (check-type from-package (or null package))\n    (let ((name (symbol-name import-me)))\n      (multiple-value-bind (existing status) (find-symbol name into-package)\n        (cond\n          ((not status)\n           (import* import-me into-package))\n          ((eq import-me existing))\n          (t\n           (let ((shadowing-p (symbol-shadowing-p existing into-package)))\n             (note-package-fishiness\n              :ensure-imported name\n              (and from-package (package-name from-package))\n              (or (home-package-p import-me from-package) (symbol-package-name import-me))\n              (package-name into-package)\n              status\n              (and status (or (home-package-p existing into-package) (symbol-package-name existing)))\n              shadowing-p)\n             (cond\n               ((or shadowing-p (eq status :inherited))\n                (shadowing-import* import-me into-package))\n               (t\n                (unintern* existing into-package)\n                (import* import-me into-package))))))))\n    (values))\n  (defun ensure-import (name to-package from-package shadowed imported)\n    (check-type name string)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (multiple-value-bind (import-me import-status) (find-symbol name from-package)\n      (when (null import-status)\n        (note-package-fishiness\n         :import-uninterned name (package-name from-package) (package-name to-package))\n        (setf import-me (intern* name from-package)))\n      (multiple-value-bind (existing status) (find-symbol name to-package)\n        (cond\n          ((and imported (gethash name imported))\n           (unless (and status (eq import-me existing))\n             (error \"Can't import ~S from both ~S and ~S\"\n                    name (package-name (symbol-package existing)) (package-name from-package))))\n          ((gethash name shadowed)\n           (error \"Can't both shadow ~S and import it from ~S\" name (package-name from-package)))\n          (t\n           (setf (gethash name imported) t))))\n      (ensure-imported import-me to-package from-package)))\n  (defun ensure-inherited (name symbol to-package from-package mixp shadowed imported inherited)\n    (check-type name string)\n    (check-type symbol symbol)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type mixp (member nil t)) ; no cl:boolean on Genera\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (check-type inherited hash-table)\n    (multiple-value-bind (existing status) (find-symbol name to-package)\n      (let* ((sp (symbol-package symbol))\n             (in (gethash name inherited))\n             (xp (and status (symbol-package existing))))\n        (when (null sp)\n          (note-package-fishiness\n           :import-uninterned name\n           (package-name from-package) (package-name to-package) mixp)\n          (import* symbol from-package)\n          (setf sp (package-name from-package)))\n        (cond\n          ((gethash name shadowed))\n          (in\n           (unless (equal sp (first in))\n             (if mixp\n                 (ensure-shadowing-import name to-package (second in) shadowed imported)\n                 (error \"Can't inherit ~S from ~S, it is inherited from ~S\"\n                        name (package-name sp) (package-name (first in))))))\n          ((gethash name imported)\n           (unless (eq symbol existing)\n             (error \"Can't inherit ~S from ~S, it is imported from ~S\"\n                    name (package-name sp) (package-name xp))))\n          (t\n           (setf (gethash name inherited) (list sp from-package))\n           (when (and status (not (eq sp xp)))\n             (let ((shadowing (symbol-shadowing-p existing to-package)))\n               (note-package-fishiness\n                :inherited name\n                (package-name from-package)\n                (or (home-package-p symbol from-package) (symbol-package-name symbol))\n                (package-name to-package)\n                (or (home-package-p existing to-package) (symbol-package-name existing)))\n               (if shadowing (ensure-shadowing-import name to-package from-package shadowed imported)\n                   (unintern* existing to-package)))))))))\n  (defun ensure-mix (name symbol to-package from-package shadowed imported inherited)\n    (check-type name string)\n    (check-type symbol symbol)\n    (check-type to-package package)\n    (check-type from-package package)\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (check-type inherited hash-table)\n    (unless (gethash name shadowed)\n      (multiple-value-bind (existing status) (find-symbol name to-package)\n        (let* ((sp (symbol-package symbol))\n               (im (gethash name imported))\n               (in (gethash name inherited)))\n          (cond\n            ((or (null status)\n                 (and status (eq symbol existing))\n                 (and in (eq sp (first in))))\n             (ensure-inherited name symbol to-package from-package t shadowed imported inherited))\n            (in\n             (remhash name inherited)\n             (ensure-shadowing-import name to-package (second in) shadowed imported))\n            (im\n             (error \"Symbol ~S import from ~S~:[~; actually ~:[uninterned~;~:*from ~S~]~] conflicts with existing symbol in ~S~:[~; actually ~:[uninterned~;from ~:*~S~]~]\"\n                    name (package-name from-package)\n                    (home-package-p symbol from-package) (symbol-package-name symbol)\n                    (package-name to-package)\n                    (home-package-p existing to-package) (symbol-package-name existing)))\n            (t\n             (ensure-inherited name symbol to-package from-package t shadowed imported inherited)))))))\n\n  (defun recycle-symbol (name recycle exported)\n    ;; Takes a symbol NAME (a string), a list of package designators for RECYCLE\n    ;; packages, and a hash-table of names (strings) of symbols scheduled to be\n    ;; EXPORTED from the package being defined. It returns two values, the\n    ;; symbol found (if any, or else NIL), and a boolean flag indicating whether\n    ;; a symbol was found. The caller (DEFINE-PACKAGE) will then do the\n    ;; re-homing of the symbol, etc.\n    (check-type name string)\n    (check-type recycle list)\n    (check-type exported hash-table)\n    (when (gethash name exported) ;; don't bother recycling private symbols\n      (let (recycled foundp)\n        (dolist (r recycle (values recycled foundp))\n          (multiple-value-bind (symbol status) (find-symbol name r)\n            (when (and status (home-package-p symbol r))\n              (cond\n                (foundp\n                 ;; (nuke-symbol symbol)) -- even simple variable names like O or C will do that.\n                 (note-package-fishiness :recycled-duplicate name (package-name foundp) (package-name r)))\n                (t\n                 (setf recycled symbol foundp r)))))))))\n  (defun symbol-recycled-p (sym recycle)\n    (check-type sym symbol)\n    (check-type recycle list)\n    (and (member (symbol-package sym) recycle) t))\n  (defun ensure-symbol (name package intern recycle shadowed imported inherited exported)\n    (check-type name string)\n    (check-type package package)\n    (check-type intern (member nil t)) ; no cl:boolean on Genera\n    (check-type shadowed hash-table)\n    (check-type imported hash-table)\n    (check-type inherited hash-table)\n    (unless (or (gethash name shadowed)\n                (gethash name imported)\n                (gethash name inherited))\n      (multiple-value-bind (existing status)\n          (find-symbol name package)\n        (multiple-value-bind (recycled previous) (recycle-symbol name recycle exported)\n          (cond\n            ((and status (eq existing recycled) (eq previous package)))\n            (previous\n             (rehome-symbol recycled package))\n            ((and status (eq package (symbol-package existing))))\n            (t\n             (when status\n               (note-package-fishiness\n                :ensure-symbol name\n                (reify-package (symbol-package existing) package)\n                status intern)\n               (unintern existing))\n             (when intern\n               (intern* name package))))))))\n  (declaim (ftype (function (t t t &optional t) t) ensure-exported))\n  (defun ensure-exported-to-user (name symbol to-package &optional recycle)\n    (check-type name string)\n    (check-type symbol symbol)\n    (check-type to-package package)\n    (check-type recycle list)\n    (assert (equal name (symbol-name symbol)))\n    (multiple-value-bind (existing status) (find-symbol name to-package)\n      (unless (and status (eq symbol existing))\n        (let ((accessible\n                (or (null status)\n                    (let ((shadowing (symbol-shadowing-p existing to-package))\n                          (recycled (symbol-recycled-p existing recycle)))\n                      (unless (and shadowing (not recycled))\n                        (note-package-fishiness\n                         :ensure-export name (symbol-package-name symbol)\n                         (package-name to-package)\n                         (or (home-package-p existing to-package) (symbol-package-name existing))\n                         status shadowing)\n                        (if (or (eq status :inherited) shadowing)\n                            (shadowing-import* symbol to-package)\n                            (unintern existing to-package))\n                        t)))))\n          (when (and accessible (eq status :external))\n            (ensure-exported name symbol to-package recycle))))))\n  (defun ensure-exported (name symbol from-package &optional recycle)\n    (dolist (to-package (package-used-by-list from-package))\n      (ensure-exported-to-user name symbol to-package recycle))\n    (unless (eq from-package (symbol-package symbol))\n      (ensure-imported symbol from-package))\n    (export* name from-package))\n  (defun ensure-export (name from-package &optional recycle)\n    (multiple-value-bind (symbol status) (find-symbol* name from-package)\n      (unless (eq status :external)\n        (ensure-exported name symbol from-package recycle))))\n\n  #+package-local-nicknames\n  (defun install-package-local-nicknames (destination-package new-nicknames)\n    ;; First, remove all package-local nicknames. (We'll reinstall any desired ones later.)\n    (dolist (pair-to-remove (uiop/package-local-nicknames:package-local-nicknames destination-package))\n      (uiop/package-local-nicknames:remove-package-local-nickname\n       (string (car pair-to-remove)) destination-package))\n    ;; Then, install all desired nicknames.\n    (loop :for (nickname package) :in new-nicknames\n          :do (uiop/package-local-nicknames:add-package-local-nickname\n               (string nickname)\n               (find-package package)\n               destination-package)))\n\n  (defun ensure-package (name &key\n                                nicknames documentation use\n                                shadow shadowing-import-from\n                                import-from export intern\n                                recycle mix reexport\n                                unintern local-nicknames)\n    #+genera (declare (ignore documentation))\n    (let* ((package-name (string name))\n           (nicknames (mapcar #'string nicknames))\n           (names (cons package-name nicknames))\n           (previous (packages-from-names names))\n           (discarded (cdr previous))\n           (to-delete ())\n           (package (or (first previous) (make-package package-name :nicknames nicknames)))\n           (recycle (packages-from-names recycle))\n           (use (mapcar 'find-package* use))\n           (mix (mapcar 'find-package* mix))\n           (reexport (mapcar 'find-package* reexport))\n           (shadow (mapcar 'string shadow))\n           (export (mapcar 'string export))\n           (intern (mapcar 'string intern))\n           (unintern (mapcar 'string unintern))\n           (local-nicknames (mapcar #'(lambda (pair) (mapcar 'string pair)) local-nicknames))\n           (shadowed (make-hash-table :test 'equal)) ; string to bool\n           (imported (make-hash-table :test 'equal)) ; string to bool\n           (exported (make-hash-table :test 'equal)) ; string to bool\n           ;; string to list home package and use package:\n           (inherited (make-hash-table :test 'equal)))\n      #-package-local-nicknames\n      (declare (ignore local-nicknames)) ; if not supported\n      (when-package-fishiness (record-fishy package-name))\n      ;; if supported, put package documentation\n      #-genera\n      (when documentation (setf (documentation package t) documentation))\n      ;; remove unwanted packages from use list\n      (loop :for p :in (set-difference (package-use-list package) (append mix use))\n            :do (note-package-fishiness :over-use name (package-names p))\n                (unuse-package p package))\n      ;; mark unwanted packages for deletion\n      (loop :for p :in discarded\n            :for n = (remove-if #'(lambda (x) (member x names :test 'equal))\n                                (package-names p))\n            :do (note-package-fishiness :nickname name (package-names p))\n                (cond (n (rename-package p (first n) (rest n)))\n                      (t (rename-package-away p)\n                         (push p to-delete))))\n      ;; give package its desired name\n      (rename-package package package-name nicknames)\n      ;; Handle local nicknames\n      #+package-local-nicknames\n      (install-package-local-nicknames package local-nicknames)\n      (dolist (name unintern)\n        (multiple-value-bind (existing status) (find-symbol name package)\n          (when status\n            (unless (eq status :inherited)\n              (note-package-fishiness\n               :unintern (package-name package) name (symbol-package-name existing) status)\n              (unintern* name package nil)))))\n      ;; handle exports\n      (dolist (name export)\n        (setf (gethash name exported) t))\n      ;; handle reexportss\n      (dolist (p reexport)\n        (do-external-symbols (sym p)\n          (setf (gethash (string sym) exported) t)))\n      ;; unexport symbols not listed in (re)export\n      (do-external-symbols (sym package)\n        (let ((name (symbol-name sym)))\n          (unless (gethash name exported)\n            (note-package-fishiness\n             :over-export (package-name package) name\n             (or (home-package-p sym package) (symbol-package-name sym)))\n            (unexport sym package))))\n      ;; handle explicitly listed shadowed ssymbols\n      (dolist (name shadow)\n        (setf (gethash name shadowed) t)\n        (multiple-value-bind (existing status) (find-symbol name package)\n          (multiple-value-bind (recycled previous) (recycle-symbol name recycle exported)\n            (let ((shadowing (and status (symbol-shadowing-p existing package))))\n              (cond\n                ((eq previous package))\n                (previous\n                 (rehome-symbol recycled package))\n                ((or (member status '(nil :inherited))\n                     (home-package-p existing package)))\n                (t\n                 (let ((dummy (make-symbol name)))\n                   (note-package-fishiness\n                    :shadow-imported (package-name package) name\n                    (symbol-package-name existing) status shadowing)\n                   (shadowing-import* dummy package)\n                   (import* dummy package)))))))\n        (shadow* name package))\n      ;; handle shadowing imports\n      (loop :for (p . syms) :in shadowing-import-from\n            :for pp = (find-package* p) :do\n              (dolist (sym syms) (ensure-shadowing-import (string sym) package pp shadowed imported)))\n      ;; handle mixed packages\n      (loop :for p :in mix\n            :for pp = (find-package* p) :do\n              (do-external-symbols (sym pp) (ensure-mix (symbol-name sym) sym package pp shadowed imported inherited)))\n      ;; handle import-from packages\n      (loop :for (p . syms) :in import-from\n            ;; FOR NOW suppress errors in the case where the :import-from\n            ;; symbol list is empty (used only to establish a dependency by\n            ;; package-inferred-system users).\n            :for pp = (find-package* p syms) :do\n              (when (null pp)\n                ;; TODO: ASDF 3.4 Change to a full warning.\n                (warn 'define-package-style-warning\n                      :format-control \"When defining package ~a, attempting to import-from non-existent package ~a. This is deprecated behavior and will be removed from UIOP in the future.\"\n                      :format-arguments (list name p)))\n              (dolist (sym syms) (ensure-import (symbol-name sym) package pp shadowed imported)))\n      ;; handle use-list and mix\n      (dolist (p (append use mix))\n        (do-external-symbols (sym p) (ensure-inherited (string sym) sym package p nil shadowed imported inherited))\n        (use-package p package))\n      (loop :for name :being :the :hash-keys :of exported :do\n        (ensure-symbol name package t recycle shadowed imported inherited exported)\n        (ensure-export name package recycle))\n      ;; intern dessired symbols\n      (dolist (name intern)\n        (ensure-symbol name package t recycle shadowed imported inherited exported))\n      (do-symbols (sym package)\n        (ensure-symbol (symbol-name sym) package nil recycle shadowed imported inherited exported))\n      ;; delete now-deceased packages\n      (map () 'delete-package* to-delete)\n      package)))\n\n\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun parse-define-package-form (package clauses)\n    (loop\n      :with use-p = nil :with recycle-p = nil\n      :with documentation = nil\n      :for (kw . args) :in clauses\n      :when (eq kw :nicknames) :append args :into nicknames :else\n      :when (eq kw :documentation)\n        :do (cond\n              (documentation (error \"define-package: can't define documentation twice\"))\n              ((or (atom args) (cdr args)) (error \"define-package: bad documentation\"))\n              (t (setf documentation (car args)))) :else\n      :when (eq kw :use) :append args :into use :and :do (setf use-p t) :else\n      :when (eq kw :shadow) :append args :into shadow :else\n      :when (eq kw :shadowing-import-from) :collect args :into shadowing-import-from :else\n      :when (eq kw :import-from) :collect args :into import-from :else\n      :when (eq kw :export) :append args :into export :else\n      :when (eq kw :intern) :append args :into intern :else\n      :when (eq kw :recycle) :append args :into recycle :and :do (setf recycle-p t) :else\n      :when (eq kw :mix) :append args :into mix :else\n      :when (eq kw :reexport) :append args :into reexport :else\n      :when (eq kw :use-reexport) :append args :into use :and :append args :into reexport\n        :and :do (setf use-p t) :else\n      :when (eq kw :mix-reexport) :append args :into mix :and :append args :into reexport\n        :and :do (setf use-p t) :else\n      :when (eq kw :unintern) :append args :into unintern :else\n      :when (eq kw :local-nicknames)\n        :if (symbol-call '#:uiop '#:featurep :package-local-nicknames)\n          :append args :into local-nicknames\n        :else\n          :do (error \":LOCAL-NICKAMES option is not supported on this lisp implementation.\")\n        :end\n      :else\n        :do (error \"unrecognized define-package keyword ~S\" kw)\n      :finally (return `(',package\n                         :nicknames ',nicknames :documentation ',documentation\n                         :use ',(if use-p use '(:common-lisp))\n                         :shadow ',shadow :shadowing-import-from ',shadowing-import-from\n                         :import-from ',import-from :export ',export :intern ',intern\n                         :recycle ',(if recycle-p recycle (cons package nicknames))\n                         :mix ',mix :reexport ',reexport :unintern ',unintern\n                         ,@(when local-nicknames\n                             `(:local-nicknames ',local-nicknames)))))))\n\n(defmacro define-package (package &rest clauses)\n  \"DEFINE-PACKAGE takes a PACKAGE and a number of CLAUSES, of the form\n\\(KEYWORD . ARGS\\).\nDEFINE-PACKAGE supports the following keywords:\nSHADOW, SHADOWING-IMPORT-FROM, IMPORT-FROM, EXPORT, INTERN, NICKNAMES,\nDOCUMENTATION -- as per CL:DEFPACKAGE.\nUSE -- as per CL:DEFPACKAGE, but if neither USE, USE-REEXPORT, MIX,\nnor MIX-REEXPORT is supplied, then it is equivalent to specifying\n(:USE :COMMON-LISP). This is unlike CL:DEFPACKAGE for which the\nbehavior of a form without USE is implementation-dependent.\nRECYCLE -- Recycle the package's exported symbols from the specified packages,\nin order.  For every symbol scheduled to be exported by the DEFINE-PACKAGE,\neither through an :EXPORT option or a :REEXPORT option, if the symbol exists in\none of the :RECYCLE packages, the first such symbol is re-homed to the package\nbeing defined.\nFor the sake of idempotence, it is important that the package being defined\nshould appear in first position if it already exists, and even if it doesn't,\nahead of any package that is not going to be deleted afterwards and never\ncreated again. In short, except for special cases, always make it the first\npackage on the list if the list is not empty.\nMIX -- Takes a list of package designators.  MIX behaves like\n\\(:USE PKG1 PKG2 ... PKGn\\) but additionally uses :SHADOWING-IMPORT-FROM to\nresolve conflicts in favor of the first found symbol.  It may still yield\nan error if there is a conflict with an explicitly :IMPORT-FROM symbol.\nREEXPORT -- Takes a list of package designators.  For each package, p, in the list,\nexport symbols with the same name as those exported from p.  Note that in the case\nof shadowing, etc. the symbols with the same name may not be the same symbols.\nUNINTERN -- Remove symbols here from PACKAGE. Note that this is primarily useful\nwhen *redefining* a previously-existing package in the current image (e.g., when\nupgrading ASDF).  Most programmers will have no use for this option.\nLOCAL-NICKNAMES -- If the host implementation supports package local nicknames\n\\(check for the :PACKAGE-LOCAL-NICKNAMES feature\\), then this should be a list of\nnickname and package name pairs.  Using this option will cause an error if the\nhost CL implementation does not support it.\nUSE-REEXPORT, MIX-REEXPORT -- Use or mix the specified packages as per the USE or\nMIX directives, and reexport their contents as per the REEXPORT directive.\"\n  (let ((ensure-form\n         `(prog1\n              (funcall 'ensure-package ,@(parse-define-package-form package clauses))\n            #+sbcl (setf (sb-impl::package-source-location (find-package ',package))\n                         (sb-c:source-location)))))\n    `(progn\n       #+(or clasp ecl gcl mkcl) (defpackage ,package (:use))\n       (eval-when (:compile-toplevel :load-toplevel :execute)\n         ,ensure-form))))\n\n;; This package, unlike UIOP/PACKAGE, is allowed to evolve and acquire new symbols or drop old ones.\n(define-package :uiop/package*\n  (:use-reexport :uiop/package\n                 #+package-local-nicknames :uiop/package-local-nicknames)\n  (:import-from :uiop/package\n                #:define-package-style-warning\n                #:no-such-package-error\n                #:package-designator)\n  (:export #:define-package-style-warning\n           #:no-such-package-error\n           #:package-designator))\n;;;; -------------------------------------------------------------------------\n;;;; Handle compatibility with multiple implementations.\n;;; This file is for papering over the deficiencies and peculiarities\n;;; of various Common Lisp implementations.\n;;; For implementation-specific access to the system, see os.lisp instead.\n;;; A few functions are defined here, but actually exported from utility;\n;;; from this package only common-lisp symbols are exported.\n\n(uiop/package:define-package :uiop/common-lisp\n  (:nicknames :uiop/cl)\n  (:use :uiop/package)\n  (:use-reexport #-genera :common-lisp #+genera :future-common-lisp)\n  #+allegro (:intern #:*acl-warn-save*)\n  #+cormanlisp (:shadow #:user-homedir-pathname)\n  #+cormanlisp\n  (:export\n   #:logical-pathname #:translate-logical-pathname\n   #:make-broadcast-stream #:file-namestring)\n  #+genera (:shadowing-import-from :scl #:boolean)\n  #+genera (:export #:boolean #:ensure-directories-exist #:read-sequence #:write-sequence)\n  #+(or mcl cmucl) (:shadow #:user-homedir-pathname))\n(in-package :uiop/common-lisp)\n\n#-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mcl mezzano mkcl sbcl scl xcl)\n(error \"ASDF is not supported on your implementation. Please help us port it.\")\n\n;; (declaim (optimize (speed 1) (debug 3) (safety 3))) ; DON'T: trust implementation defaults.\n\n\n;;;; Early meta-level tweaks\n\n#+(or allegro clasp clisp clozure cmucl ecl lispworks mezzano mkcl sbcl abcl)\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (when (and #+allegro (member :ics *features*)\n             #+(or clasp clisp cmucl ecl lispworks mkcl) (member :unicode *features*)\n             #+clozure (member :openmcl-unicode-strings *features*)\n             #+sbcl (member :sb-unicode *features*)\n             #+abcl t)\n    ;; Check for unicode at runtime, so that a hypothetical FASL compiled with unicode\n    ;; but loaded in a non-unicode setting (e.g. on Allegro) won't tell a lie.\n    (pushnew :asdf-unicode *features*)))\n\n#+allegro\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  ;; We need to disable autoloading BEFORE any mention of package ASDF.\n  ;; In particular, there must NOT be a mention of package ASDF in the defpackage of this file\n  ;; or any previous file.\n  (setf excl::*autoload-package-name-alist*\n        (remove \"asdf\" excl::*autoload-package-name-alist*\n                :test 'equalp :key 'car))\n  (defparameter *acl-warn-save*\n    (when (boundp 'excl:*warn-on-nested-reader-conditionals*)\n      excl:*warn-on-nested-reader-conditionals*))\n  (when (boundp 'excl:*warn-on-nested-reader-conditionals*)\n    (setf excl:*warn-on-nested-reader-conditionals* nil))\n  (setf *print-readably* nil))\n\n#+clasp\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (setf *load-verbose* nil)\n  (defun use-ecl-byte-compiler-p () nil))\n\n#+clozure (in-package :ccl)\n#+(and clozure windows-target) ;; See http://trac.clozure.com/ccl/ticket/1117\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (unless (fboundp 'external-process-wait)\n    (in-development-mode\n     (defun external-process-wait (proc)\n       (when (and (external-process-pid proc) (eq (external-process-%status proc) :running))\n         (with-interrupts-enabled\n             (wait-on-semaphore (external-process-completed proc))))\n       (values (external-process-%exit-code proc)\n               (external-process-%status proc))))))\n#+clozure (in-package :uiop/common-lisp) ;; back in this package.\n\n#+cmucl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (setf ext:*gc-verbose* nil)\n  (defun user-homedir-pathname ()\n    (first (ext:search-list (cl:user-homedir-pathname)))))\n\n#+cormanlisp\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (deftype logical-pathname () nil)\n  (defun make-broadcast-stream () *error-output*)\n  (defun translate-logical-pathname (x) x)\n  (defun user-homedir-pathname (&optional host)\n    (declare (ignore host))\n    (parse-namestring (format nil \"~A\\\\\" (cl:user-homedir-pathname))))\n  (defun file-namestring (p)\n    (setf p (pathname p))\n    (format nil \"~@[~A~]~@[.~A~]\" (pathname-name p) (pathname-type p))))\n\n#+ecl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (setf *load-verbose* nil)\n  (defun use-ecl-byte-compiler-p () (and (member :ecl-bytecmp *features*) t))\n  (unless (use-ecl-byte-compiler-p) (require :cmp)))\n\n#+gcl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (unless (member :ansi-cl *features*)\n    (error \"ASDF only supports GCL in ANSI mode. Aborting.~%\"))\n  (setf compiler::*compiler-default-type* (pathname \"\")\n        compiler::*lsp-ext* \"\")\n  #.(let ((code ;; Only support very recent GCL 2.7.0 from November 2013 or later.\n            (cond\n              #+gcl\n              ((or (< system::*gcl-major-version* 2)\n                   (and (= system::*gcl-major-version* 2)\n                        (< system::*gcl-minor-version* 7)))\n               '(error \"GCL 2.7 or later required to use ASDF\")))))\n      (eval code)\n      code))\n\n#+genera\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (unless (fboundp 'lambda)\n    (defmacro lambda (&whole form &rest bvl-decls-and-body)\n      (declare (ignore bvl-decls-and-body)(zwei::indentation 1 1))\n      `#',(cons 'lisp::lambda (cdr form))))\n  (unless (fboundp 'ensure-directories-exist)\n    (defun ensure-directories-exist (path)\n      (fs:create-directories-recursively (pathname path))))\n  (unless (fboundp 'read-sequence)\n    (defun read-sequence (sequence stream &key (start 0) end)\n      (scl:send stream :string-in nil sequence start end)))\n  (unless (fboundp 'write-sequence)\n    (defun write-sequence (sequence stream &key (start 0) end)\n      (scl:send stream :string-out sequence start end)\n      sequence)))\n\n#+lispworks\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  ;; lispworks 3 and earlier cannot be checked for so we always assume\n  ;; at least version 4\n  (unless (member :lispworks4 *features*)\n    (pushnew :lispworks5+ *features*)\n    (unless (member :lispworks5 *features*)\n      (pushnew :lispworks6+ *features*)\n      (unless (member :lispworks6 *features*)\n        (pushnew :lispworks7+ *features*)))))\n\n\n#.(or #+mcl ;; the #$ doesn't work on other lisps, even protected by #+mcl, so we use this trick\n      (read-from-string\n       \"(eval-when (:load-toplevel :compile-toplevel :execute)\n          (ccl:define-entry-point (_getenv \\\"getenv\\\") ((name :string)) :string)\n          (ccl:define-entry-point (_system \\\"system\\\") ((name :string)) :int)\n          ;; Note: ASDF may expect user-homedir-pathname to provide\n          ;; the pathname of the current user's home directory, whereas\n          ;; MCL by default provides the directory from which MCL was started.\n          ;; See http://code.google.com/p/mcl/wiki/Portability\n          (defun user-homedir-pathname ()\n            (ccl::findfolder #$kuserdomain #$kCurrentUserFolderType))\n          (defun probe-posix (posix-namestring)\n            \\\"If a file exists for the posix namestring, return the pathname\\\"\n            (ccl::with-cstrs ((cpath posix-namestring))\n              (ccl::rlet ((is-dir :boolean)\n                          (fsref :fsref))\n                (when (eq #$noerr (#_fspathmakeref cpath fsref is-dir))\n                  (ccl::%path-from-fsref fsref is-dir))))))\"))\n\n#+mkcl\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (require :cmp)\n  (setq clos::*redefine-class-in-place* t)) ;; Make sure we have strict ANSI class redefinition semantics\n\n\n;;;; compatfmt: avoid fancy format directives when unsupported\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun frob-substrings (string substrings &optional frob)\n    \"for each substring in SUBSTRINGS, find occurrences of it within STRING\nthat don't use parts of matched occurrences of previous strings, and\nFROB them, that is to say, remove them if FROB is NIL,\nreplace by FROB if FROB is a STRING, or if FROB is a FUNCTION,\ncall FROB with the match and a function that emits a string in the output.\nReturn a string made of the parts not omitted or emitted by FROB.\"\n    (declare (optimize (speed 0) (safety #-gcl 3 #+gcl 0) (debug 3)))\n    (let ((length (length string)) (stream nil))\n      (labels ((emit-string (x &optional (start 0) (end (length x)))\n                 (when (< start end)\n                   (unless stream (setf stream (make-string-output-stream)))\n                   (write-string x stream :start start :end end)))\n               (emit-substring (start end)\n                 (when (and (zerop start) (= end length))\n                   (return-from frob-substrings string))\n                 (emit-string string start end))\n               (recurse (substrings start end)\n                 (cond\n                   ((>= start end))\n                   ((null substrings) (emit-substring start end))\n                   (t (let* ((sub-spec (first substrings))\n                             (sub (if (consp sub-spec) (car sub-spec) sub-spec))\n                             (fun (if (consp sub-spec) (cdr sub-spec) frob))\n                             (found (search sub string :start2 start :end2 end))\n                             (more (rest substrings)))\n                        (cond\n                          (found\n                           (recurse more start found)\n                           (etypecase fun\n                             (null)\n                             (string (emit-string fun))\n                             (function (funcall fun sub #'emit-string)))\n                           (recurse substrings (+ found (length sub)) end))\n                          (t\n                           (recurse more start end))))))))\n        (recurse substrings 0 length))\n      (if stream (get-output-stream-string stream) \"\")))\n\n  (defmacro compatfmt (format)\n    #+(or gcl genera)\n    (frob-substrings format `(\"~3i~_\" #+genera ,@'(\"~@<\" \"~@;\" \"~@:>\" \"~:>\")))\n    #-(or gcl genera) format))\n;;;; -------------------------------------------------------------------------\n;;;; General Purpose Utilities for ASDF\n\n(uiop/package:define-package :uiop/utility\n  (:use :uiop/common-lisp :uiop/package)\n  ;; import and reexport a few things defined in :uiop/common-lisp\n  (:import-from :uiop/common-lisp #:compatfmt #:frob-substrings\n   #+(or clasp ecl) #:use-ecl-byte-compiler-p #+mcl #:probe-posix)\n  (:export #:compatfmt #:frob-substrings #:compatfmt\n   #+(or clasp ecl) #:use-ecl-byte-compiler-p #+mcl #:probe-posix)\n  (:export\n   ;; magic helper to define debugging functions:\n   #:uiop-debug #:load-uiop-debug-utility #:*uiop-debug-utility*\n   #:with-upgradability ;; (un)defining functions in an upgrade-friendly way\n   #:nest #:if-let ;; basic flow control\n   #:parse-body ;; macro definition helper\n   #:while-collecting #:appendf #:length=n-p #:ensure-list ;; lists\n   #:remove-plist-keys #:remove-plist-key ;; plists\n   #:emptyp ;; sequences\n   #:+non-base-chars-exist-p+ ;; characters\n   #:+max-character-type-index+ #:character-type-index #:+character-types+\n   #:base-string-p #:strings-common-element-type #:reduce/strcat #:strcat ;; strings\n   #:first-char #:last-char #:split-string #:stripln #:+cr+ #:+lf+ #:+crlf+\n   #:string-prefix-p #:string-enclosed-p #:string-suffix-p\n   #:standard-case-symbol-name #:find-standard-case-symbol ;; symbols\n   #:coerce-class ;; CLOS\n   #:timestamp< #:timestamps< #:timestamp*< #:timestamp<= ;; timestamps\n   #:earlier-timestamp #:timestamps-earliest #:earliest-timestamp\n   #:later-timestamp #:timestamps-latest #:latest-timestamp #:latest-timestamp-f\n   #:list-to-hash-set #:ensure-gethash ;; hash-table\n   #:ensure-function #:access-at #:access-at-count ;; functions\n   #:call-function #:call-functions #:register-hook-function\n   #:lexicographic< #:lexicographic<= ;; version\n   #:simple-style-warning #:style-warn ;; simple style warnings\n   #:match-condition-p #:match-any-condition-p ;; conditions\n   #:call-with-muffled-conditions #:with-muffled-conditions\n   #:not-implemented-error #:parameter-error\n   #:symbol-test-to-feature-expression\n   #:boolean-to-feature-expression))\n(in-package :uiop/utility)\n\n;;;; Defining functions in a way compatible with hot-upgrade:\n;; - The WTIH-UPGRADABILITY infrastructure below ensures that functions are declared NOTINLINE,\n;;   so that new definitions are always seen by all callers, even those up the stack.\n;; - WITH-UPGRADABILITY also uses EVAL-WHEN so that definitions used by ASDF are in a limbo state\n;;   (especially for gf's) in between the COMPILE-OP and LOAD-OP operations on the defining file.\n;; - THOU SHALT NOT redefine a function with a backward-incompatible semantics without renaming it,\n;;   at least if that function is used by ASDF while performing the plan to load ASDF.\n;; - THOU SHALT change the name of a function whenever thou makest an incompatible change.\n;; - For instance, when the meanings of NIL and T for timestamps was inverted,\n;;   functions in the STAMP<, STAMP<=, etc. family had to be renamed to TIMESTAMP<, TIMESTAMP<=, etc.,\n;;   because the change other caused a huge incompatibility during upgrade.\n;; - Whenever a function goes from a DEFUN to a DEFGENERIC, or the DEFGENERIC signature changes, etc.,\n;;   even in a backward-compatible way, you MUST precede the definition by FMAKUNBOUND.\n;; - Since FMAKUNBOUND will remove all the methods on the generic function, make sure that\n;;   all the methods required for ASDF to successfully continue compiling itself\n;;   shall be defined in the same file as the one with the FMAKUNBOUND, *after* the DEFGENERIC.\n;; - When a function goes from DEFGENERIC to DEFUN, you may omit to use FMAKUNBOUND.\n;; - For safety, you shall put the FMAKUNBOUND just before the DEFUN or DEFGENERIC,\n;;   in the same WITH-UPGRADABILITY form (and its implicit EVAL-WHEN).\n;; - Any time you change a signature, please keep a comment specifying the first release after the change;\n;;   put that comment on the same line as FMAKUNBOUND, it you use FMAKUNBOUND.\n(eval-when (:load-toplevel :compile-toplevel :execute)\n  (defun ensure-function-notinline (definition &aux (name (second definition)))\n    (assert (member (first definition) '(defun defgeneric)))\n    `(progn\n       ,(when (and #+(or clasp ecl) (symbolp name)) ; NB: fails for (SETF functions) on ECL\n          `(declaim (notinline ,name)))\n       ,definition))\n  (defmacro with-upgradability ((&optional) &body body)\n    \"Evaluate BODY at compile- load- and run- times, with DEFUN and DEFGENERIC modified\nto also declare the functions NOTINLINE and to accept a wrapping the function name\nspecification into a list with keyword argument SUPERSEDE (which defaults to T if the name\nis not wrapped, and NIL if it is wrapped). If SUPERSEDE is true, call UNDEFINE-FUNCTION\nto supersede any previous definition.\"\n    `(eval-when (:compile-toplevel :load-toplevel :execute)\n       ,@(loop :for form :in body :collect\n               (if (consp form)\n                   (case (first form)\n                     ((defun defgeneric) (ensure-function-notinline form))\n                     (otherwise form))\n                   form)))))\n\n;;; Magic debugging help. See contrib/debug.lisp\n(with-upgradability ()\n  (defvar *uiop-debug-utility*\n    '(symbol-call :uiop :subpathname (symbol-call :uiop :uiop-directory) \"contrib/debug.lisp\")\n    \"form that evaluates to the pathname to your favorite debugging utilities\")\n\n  (defmacro uiop-debug (&rest keys)\n    \"Load the UIOP debug utility at compile-time as well as runtime\"\n    `(eval-when (:compile-toplevel :load-toplevel :execute)\n       (load-uiop-debug-utility ,@keys)))\n\n  (defun load-uiop-debug-utility (&key package utility-file)\n    \"Load the UIOP debug utility in given PACKAGE (default *PACKAGE*).\nBeware: The utility is located by EVAL'uating the UTILITY-FILE form (default *UIOP-DEBUG-UTILITY*).\"\n    (let* ((*package* (if package (find-package package) *package*))\n           (keyword (read-from-string\n                     (format nil \":DBG-~:@(~A~)\" (package-name *package*)))))\n      (unless (member keyword *features*)\n        (let* ((utility-file (or utility-file *uiop-debug-utility*))\n               (file (ignore-errors (probe-file (eval utility-file)))))\n          (if file (load file)\n              (error \"Failed to locate debug utility file: ~S\" utility-file)))))))\n\n;;; Flow control\n(with-upgradability ()\n  (defmacro nest (&rest things)\n    \"Macro to keep code nesting and indentation under control.\" ;; Thanks to mbaringer\n    (reduce #'(lambda (outer inner) `(,@outer ,inner))\n            things :from-end t))\n\n  (defmacro if-let (bindings &body (then-form &optional else-form)) ;; from alexandria\n    ;; bindings can be (var form) or ((var1 form1) ...)\n    (let* ((binding-list (if (and (consp bindings) (symbolp (car bindings)))\n                             (list bindings)\n                             bindings))\n           (variables (mapcar #'car binding-list)))\n      `(let ,binding-list\n         (if (and ,@variables)\n             ,then-form\n             ,else-form)))))\n\n;;; Macro definition helper\n(with-upgradability ()\n  (defun parse-body (body &key documentation whole) ;; from alexandria\n    \"Parses BODY into (values remaining-forms declarations doc-string).\nDocumentation strings are recognized only if DOCUMENTATION is true.\nSyntax errors in body are signalled and WHOLE is used in the signal\narguments when given.\"\n    (let ((doc nil)\n          (decls nil)\n          (current nil))\n      (tagbody\n       :declarations\n         (setf current (car body))\n         (when (and documentation (stringp current) (cdr body))\n           (if doc\n               (error \"Too many documentation strings in ~S.\" (or whole body))\n               (setf doc (pop body)))\n           (go :declarations))\n         (when (and (listp current) (eql (first current) 'declare))\n           (push (pop body) decls)\n           (go :declarations)))\n      (values body (nreverse decls) doc))))\n\n\n;;; List manipulation\n(with-upgradability ()\n  (defmacro while-collecting ((&rest collectors) &body body)\n    \"COLLECTORS should be a list of names for collections.  A collector\ndefines a function that, when applied to an argument inside BODY, will\nadd its argument to the corresponding collection.  Returns multiple values,\na list for each collection, in order.\n   E.g.,\n\\(while-collecting \\(foo bar\\)\n           \\(dolist \\(x '\\(\\(a 1\\) \\(b 2\\) \\(c 3\\)\\)\\)\n             \\(foo \\(first x\\)\\)\n             \\(bar \\(second x\\)\\)\\)\\)\nReturns two values: \\(A B C\\) and \\(1 2 3\\).\"\n    (let ((vars (mapcar #'(lambda (x) (gensym (symbol-name x))) collectors))\n          (initial-values (mapcar (constantly nil) collectors)))\n      `(let ,(mapcar #'list vars initial-values)\n         (flet ,(mapcar #'(lambda (c v) `(,c (x) (push x ,v) (values))) collectors vars)\n           ,@body\n           (values ,@(mapcar #'(lambda (v) `(reverse ,v)) vars))))))\n\n  (define-modify-macro appendf (&rest args)\n    append \"Append onto list\") ;; only to be used on short lists.\n\n  (defun length=n-p (x n) ;is it that (= (length x) n) ?\n    (check-type n (integer 0 *))\n    (loop\n      :for l = x :then (cdr l)\n      :for i :downfrom n :do\n        (cond\n          ((zerop i) (return (null l)))\n          ((not (consp l)) (return nil)))))\n\n  (defun ensure-list (x)\n    (if (listp x) x (list x))))\n\n\n;;; Remove a key from a plist, i.e. for keyword argument cleanup\n(with-upgradability ()\n  (defun remove-plist-key (key plist)\n    \"Remove a single key from a plist\"\n    (loop :for (k v) :on plist :by #'cddr\n          :unless (eq k key)\n            :append (list k v)))\n\n  (defun remove-plist-keys (keys plist)\n    \"Remove a list of keys from a plist\"\n    (loop :for (k v) :on plist :by #'cddr\n          :unless (member k keys)\n            :append (list k v))))\n\n\n;;; Sequences\n(with-upgradability ()\n  (defun emptyp (x)\n    \"Predicate that is true for an empty sequence\"\n    (or (null x) (and (vectorp x) (zerop (length x))))))\n\n\n;;; Characters\n(with-upgradability ()\n  ;; base-char != character on ECL, LW, SBCL, Genera.\n  ;; NB: We assume a total order on character types.\n  ;; If that's not true... this code will need to be updated.\n  (defparameter +character-types+ ;; assuming a simple hierarchy\n    #.(coerce (loop :for (type next) :on\n                    '(;; In SCL, all characters seem to be 16-bit base-char\n                      ;; Yet somehow character fails to be a subtype of base-char\n                      #-scl base-char\n                      ;; LW6 has BASE-CHAR < SIMPLE-CHAR < CHARACTER\n                      ;; LW7 has BASE-CHAR < BMP-CHAR < SIMPLE-CHAR = CHARACTER\n                      #+lispworks7+ lw:bmp-char\n                      #+lispworks lw:simple-char\n                      character)\n                    :unless (and next (subtypep next type))\n                      :collect type) 'vector))\n  (defparameter +max-character-type-index+ (1- (length +character-types+)))\n  (defconstant +non-base-chars-exist-p+ (plusp +max-character-type-index+))\n  (when +non-base-chars-exist-p+ (pushnew :non-base-chars-exist-p *features*)))\n\n(with-upgradability ()\n  (defun character-type-index (x)\n    (declare (ignorable x))\n    #.(case +max-character-type-index+\n        (0 0)\n        (1 '(etypecase x\n             (character (if (typep x 'base-char) 0 1))\n             (symbol (if (subtypep x 'base-char) 0 1))))\n        (otherwise\n         '(or (position-if (etypecase x\n                             (character #'(lambda (type) (typep x type)))\n                             (symbol #'(lambda (type) (subtypep x type))))\n               +character-types+)\n           (error \"Not a character or character type: ~S\" x))))))\n\n\n;;; Strings\n(with-upgradability ()\n  (defun base-string-p (string)\n    \"Does the STRING only contain BASE-CHARs?\"\n    (declare (ignorable string))\n    (and #+non-base-chars-exist-p (eq 'base-char (array-element-type string))))\n\n  (defun strings-common-element-type (strings)\n    \"What least subtype of CHARACTER can contain all the elements of all the STRINGS?\"\n    (declare (ignorable strings))\n    #.(if +non-base-chars-exist-p+\n          `(aref +character-types+\n            (loop :with index = 0 :for s :in strings :do\n              (flet ((consider (i)\n                       (cond ((= i ,+max-character-type-index+) (return i))\n                             ,@(when (> +max-character-type-index+ 1) `(((> i index) (setf index i)))))))\n                (cond\n                  ((emptyp s)) ;; NIL or empty string\n                  ((characterp s) (consider (character-type-index s)))\n                  ((stringp s) (let ((string-type-index\n                                       (character-type-index (array-element-type s))))\n                                 (unless (>= index string-type-index)\n                                   (loop :for c :across s :for i = (character-type-index c)\n                                         :do (consider i)\n                                         ,@(when (> +max-character-type-index+ 1)\n                                             `((when (= i string-type-index) (return))))))))\n                  (t (error \"Invalid string designator ~S for ~S\" s 'strings-common-element-type))))\n                  :finally (return index)))\n          ''character))\n\n  (defun reduce/strcat (strings &key key start end)\n    \"Reduce a list as if by STRCAT, accepting KEY START and END keywords like REDUCE.\nNIL is interpreted as an empty string. A character is interpreted as a string of length one.\"\n    (when (or start end) (setf strings (subseq strings start end)))\n    (when key (setf strings (mapcar key strings)))\n    (loop :with output = (make-string (loop :for s :in strings\n                                            :sum (if (characterp s) 1 (length s)))\n                                      :element-type (strings-common-element-type strings))\n          :with pos = 0\n          :for input :in strings\n          :do (etypecase input\n                (null)\n                (character (setf (char output pos) input) (incf pos))\n                (string (replace output input :start1 pos) (incf pos (length input))))\n          :finally (return output)))\n\n  (defun strcat (&rest strings)\n    \"Concatenate strings.\nNIL is interpreted as an empty string, a character as a string of length one.\"\n    (reduce/strcat strings))\n\n  (defun first-char (s)\n    \"Return the first character of a non-empty string S, or NIL\"\n    (and (stringp s) (plusp (length s)) (char s 0)))\n\n  (defun last-char (s)\n    \"Return the last character of a non-empty string S, or NIL\"\n    (and (stringp s) (plusp (length s)) (char s (1- (length s)))))\n\n  (defun split-string (string &key max (separator '(#\\Space #\\Tab)))\n    \"Split STRING into a list of components separated by\nany of the characters in the sequence SEPARATOR.\nIf MAX is specified, then no more than max(1,MAX) components will be returned,\nstarting the separation from the end, e.g. when called with arguments\n \\\"a.b.c.d.e\\\" :max 3 :separator \\\".\\\" it will return (\\\"a.b.c\\\" \\\"d\\\" \\\"e\\\").\"\n    (block ()\n      (let ((list nil) (words 0) (end (length string)))\n        (when (zerop end) (return nil))\n        (flet ((separatorp (char) (find char separator))\n               (done () (return (cons (subseq string 0 end) list))))\n          (loop\n            :for start = (if (and max (>= words (1- max)))\n                             (done)\n                             (position-if #'separatorp string :end end :from-end t))\n            :do (when (null start) (done))\n                (push (subseq string (1+ start) end) list)\n                (incf words)\n                (setf end start))))))\n\n  (defun string-prefix-p (prefix string)\n    \"Does STRING begin with PREFIX?\"\n    (let* ((x (string prefix))\n           (y (string string))\n           (lx (length x))\n           (ly (length y)))\n      (and (<= lx ly) (string= x y :end2 lx))))\n\n  (defun string-suffix-p (string suffix)\n    \"Does STRING end with SUFFIX?\"\n    (let* ((x (string string))\n           (y (string suffix))\n           (lx (length x))\n           (ly (length y)))\n      (and (<= ly lx) (string= x y :start1 (- lx ly)))))\n\n  (defun string-enclosed-p (prefix string suffix)\n    \"Does STRING begin with PREFIX and end with SUFFIX?\"\n    (and (string-prefix-p prefix string)\n         (string-suffix-p string suffix)))\n\n  (defvar +cr+ (coerce #(#\\Return) 'string))\n  (defvar +lf+ (coerce #(#\\Linefeed) 'string))\n  (defvar +crlf+ (coerce #(#\\Return #\\Linefeed) 'string))\n\n  (defun stripln (x)\n    \"Strip a string X from any ending CR, LF or CRLF.\nReturn two values, the stripped string and the ending that was stripped,\nor the original value and NIL if no stripping took place.\nSince our STRCAT accepts NIL as empty string designator,\nthe two results passed to STRCAT always reconstitute the original string\"\n    (check-type x string)\n    (block nil\n      (flet ((c (end) (when (string-suffix-p x end)\n                        (return (values (subseq x 0 (- (length x) (length end))) end)))))\n        (when x (c +crlf+) (c +lf+) (c +cr+) (values x nil)))))\n\n  (defun standard-case-symbol-name (name-designator)\n    \"Given a NAME-DESIGNATOR for a symbol, if it is a symbol, convert it to a string using STRING;\nif it is a string, use STRING-UPCASE on an ANSI CL platform, or STRING on a so-called \\\"modern\\\"\nplatform such as Allegro with modern syntax.\"\n    (check-type name-designator (or string symbol))\n    (cond\n      ((or (symbolp name-designator) #+allegro (eq excl:*current-case-mode* :case-sensitive-lower))\n       (string name-designator))\n      ;; Should we be doing something on CLISP?\n      (t (string-upcase name-designator))))\n\n  (defun find-standard-case-symbol (name-designator package-designator &optional (error t))\n    \"Find a symbol designated by NAME-DESIGNATOR in a package designated by PACKAGE-DESIGNATOR,\nwhere STANDARD-CASE-SYMBOL-NAME is used to transform them if these designators are strings.\nIf optional ERROR argument is NIL, return NIL instead of an error when the symbol is not found.\"\n    (find-symbol* (standard-case-symbol-name name-designator)\n                  (etypecase package-designator\n                    ((or package symbol) package-designator)\n                    (string (standard-case-symbol-name package-designator)))\n                  error)))\n\n;;; timestamps: a REAL or a boolean where T=-infinity, NIL=+infinity\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute)\n  (deftype timestamp () '(or real boolean)))\n(with-upgradability ()\n  (defun timestamp< (x y)\n    (etypecase x\n      ((eql t) (not (eql y t)))\n      (real (etypecase y\n              ((eql t) nil)\n              (real (< x y))\n              (null t)))\n      (null nil)))\n  (defun timestamps< (list) (loop :for y :in list :for x = nil :then y :always (timestamp< x y)))\n  (defun timestamp*< (&rest list) (timestamps< list))\n  (defun timestamp<= (x y) (not (timestamp< y x)))\n  (defun earlier-timestamp (x y) (if (timestamp< x y) x y))\n  (defun timestamps-earliest (list) (reduce 'earlier-timestamp list :initial-value nil))\n  (defun earliest-timestamp (&rest list) (timestamps-earliest list))\n  (defun later-timestamp (x y) (if (timestamp< x y) y x))\n  (defun timestamps-latest (list) (reduce 'later-timestamp list :initial-value t))\n  (defun latest-timestamp (&rest list) (timestamps-latest list))\n  (define-modify-macro latest-timestamp-f (&rest timestamps) latest-timestamp))\n\n\n;;; Function designators\n(with-upgradability ()\n  (defun ensure-function (fun &key (package :cl))\n    \"Coerce the object FUN into a function.\n\nIf FUN is a FUNCTION, return it.\nIf the FUN is a non-sequence literal constant, return constantly that,\ni.e. for a boolean keyword character number or pathname.\nOtherwise if FUN is a non-literally constant symbol, return its FDEFINITION.\nIf FUN is a CONS, return the function that applies its CAR\nto the appended list of the rest of its CDR and the arguments,\nunless the CAR is LAMBDA, in which case the expression is evaluated.\nIf FUN is a string, READ a form from it in the specified PACKAGE (default: CL)\nand EVAL that in a (FUNCTION ...) context.\"\n    (etypecase fun\n      (function fun)\n      ((or boolean keyword character number pathname) (constantly fun))\n      (hash-table #'(lambda (x) (gethash x fun)))\n      (symbol (fdefinition fun))\n      (cons (if (eq 'lambda (car fun))\n                (eval fun)\n                #'(lambda (&rest args) (apply (car fun) (append (cdr fun) args)))))\n      (string (eval `(function ,(with-standard-io-syntax\n                                  (let ((*package* (find-package package)))\n                                    (read-from-string fun))))))))\n\n  (defun access-at (object at)\n    \"Given an OBJECT and an AT specifier, list of successive accessors,\ncall each accessor on the result of the previous calls.\nAn accessor may be an integer, meaning a call to ELT,\na keyword, meaning a call to GETF,\nNIL, meaning identity,\na function or other symbol, meaning itself,\nor a list of a function designator and arguments, interpreted as per ENSURE-FUNCTION.\nAs a degenerate case, the AT specifier may be an atom of a single such accessor\ninstead of a list.\"\n    (flet ((access (object accessor)\n             (etypecase accessor\n               (function (funcall accessor object))\n               (integer (elt object accessor))\n               (keyword (getf object accessor))\n               (null object)\n               (symbol (funcall accessor object))\n               (cons (funcall (ensure-function accessor) object)))))\n      (if (listp at)\n          (dolist (accessor at object)\n            (setf object (access object accessor)))\n          (access object at))))\n\n  (defun access-at-count (at)\n    \"From an AT specification, extract a COUNT of maximum number\nof sub-objects to read as per ACCESS-AT\"\n    (cond\n      ((integerp at)\n       (1+ at))\n      ((and (consp at) (integerp (first at)))\n       (1+ (first at)))))\n\n  (defun call-function (function-spec &rest arguments)\n    \"Call the function designated by FUNCTION-SPEC as per ENSURE-FUNCTION,\nwith the given ARGUMENTS\"\n    (apply (ensure-function function-spec) arguments))\n\n  (defun call-functions (function-specs)\n    \"For each function in the list FUNCTION-SPECS, in order, call the function as per CALL-FUNCTION\"\n    (map () 'call-function function-specs))\n\n  (defun register-hook-function (variable hook &optional call-now-p)\n    \"Push the HOOK function (a designator as per ENSURE-FUNCTION) onto the hook VARIABLE.\nWhen CALL-NOW-P is true, also call the function immediately.\"\n    (pushnew hook (symbol-value variable) :test 'equal)\n    (when call-now-p (call-function hook))))\n\n\n;;; CLOS\n(with-upgradability ()\n  (defun coerce-class (class &key (package :cl) (super t) (error 'error))\n    \"Coerce CLASS to a class that is subclass of SUPER if specified,\nor invoke ERROR handler as per CALL-FUNCTION.\n\nA keyword designates the name a symbol, which when found in either PACKAGE, designates a class.\n-- for backward compatibility, *PACKAGE* is also accepted for now, but this may go in the future.\nA string is read as a symbol while in PACKAGE, the symbol designates a class.\n\nA class object designates itself.\nNIL designates itself (no class).\nA symbol otherwise designates a class by name.\"\n    (let* ((normalized\n            (typecase class\n              (keyword (or (find-symbol* class package nil)\n                           (find-symbol* class *package* nil)))\n              (string (symbol-call :uiop :safe-read-from-string class :package package))\n              (t class)))\n           (found\n            (etypecase normalized\n              ((or standard-class built-in-class) normalized)\n              ((or null keyword) nil)\n              (symbol (find-class normalized nil nil))))\n           (super-class\n            (etypecase super\n              ((or standard-class built-in-class) super)\n              ((or null keyword) nil)\n              (symbol (find-class super nil nil)))))\n      #+allegro (when found (mop:finalize-inheritance found))\n      (or (and found\n               (or (eq super t) (#-cormanlisp subtypep #+cormanlisp cl::subclassp found super-class))\n               found)\n          (call-function error \"Can't coerce ~S to a ~:[class~;subclass of ~:*~S~]\" class super)))))\n\n\n;;; Hash-tables\n(with-upgradability ()\n  (defun ensure-gethash (key table default)\n    \"Lookup the TABLE for a KEY as by GETHASH, but if not present,\ncall the (possibly constant) function designated by DEFAULT as per CALL-FUNCTION,\nset the corresponding entry to the result in the table.\nReturn two values: the entry after its optional computation, and whether it was found\"\n    (multiple-value-bind (value foundp) (gethash key table)\n      (values\n       (if foundp\n           value\n           (setf (gethash key table) (call-function default)))\n       foundp)))\n\n  (defun list-to-hash-set (list &aux (h (make-hash-table :test 'equal)))\n    \"Convert a LIST into hash-table that has the same elements when viewed as a set,\nup to the given equality TEST\"\n    (dolist (x list h) (setf (gethash x h) t))))\n\n\n;;; Lexicographic comparison of lists of numbers\n(with-upgradability ()\n  (defun lexicographic< (element< x y)\n    \"Lexicographically compare two lists of using the function element< to compare elements.\nelement< is a strict total order; the resulting order on X and Y will also be strict.\"\n    (cond ((null y) nil)\n          ((null x) t)\n          ((funcall element< (car x) (car y)) t)\n          ((funcall element< (car y) (car x)) nil)\n          (t (lexicographic< element< (cdr x) (cdr y)))))\n\n  (defun lexicographic<= (element< x y)\n    \"Lexicographically compare two lists of using the function element< to compare elements.\nelement< is a strict total order; the resulting order on X and Y will be a non-strict total order.\"\n    (not (lexicographic< element< y x))))\n\n\n;;; Simple style warnings\n(with-upgradability ()\n  (define-condition simple-style-warning\n      #+sbcl (sb-int:simple-style-warning) #-sbcl (simple-condition style-warning)\n    ())\n\n  (defun style-warn (datum &rest arguments)\n    (etypecase datum\n      (string (warn (make-condition 'simple-style-warning :format-control datum :format-arguments arguments)))\n      (symbol (assert (subtypep datum 'style-warning)) (apply 'warn datum arguments))\n      (style-warning (apply 'warn datum arguments)))))\n\n\n;;; Condition control\n\n(with-upgradability ()\n  (defparameter +simple-condition-format-control-slot+\n    #+abcl 'system::format-control\n    #+allegro 'excl::format-control\n    #+(or clasp ecl mkcl) 'si::format-control\n    #+clisp 'system::$format-control\n    #+clozure 'ccl::format-control\n    #+(or cmucl scl) 'conditions::format-control\n    #+(or gcl lispworks) 'conditions::format-string\n    #+sbcl 'sb-kernel:format-control\n    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl lispworks mkcl sbcl scl) nil\n    \"Name of the slot for FORMAT-CONTROL in simple-condition\")\n\n  (defun match-condition-p (x condition)\n    \"Compare received CONDITION to some pattern X:\na symbol naming a condition class,\na simple vector of length 2, arguments to find-symbol* with result as above,\nor a string describing the format-control of a simple-condition.\"\n    (etypecase x\n      (symbol (typep condition x))\n      ((simple-vector 2)\n       (ignore-errors (typep condition (find-symbol* (svref x 0) (svref x 1) nil))))\n      (function (funcall x condition))\n      (string (and (typep condition 'simple-condition)\n                   ;; On SBCL, it's always set and the check triggers a warning\n                   #+(or allegro clozure cmucl lispworks scl)\n                   (slot-boundp condition +simple-condition-format-control-slot+)\n                   (ignore-errors (equal (simple-condition-format-control condition) x))))))\n\n  (defun match-any-condition-p (condition conditions)\n    \"match CONDITION against any of the patterns of CONDITIONS supplied\"\n    (loop :for x :in conditions :thereis (match-condition-p x condition)))\n\n  (defun call-with-muffled-conditions (thunk conditions)\n    \"calls the THUNK in a context where the CONDITIONS are muffled\"\n    (handler-bind ((t #'(lambda (c) (when (match-any-condition-p c conditions)\n                                      (muffle-warning c)))))\n      (funcall thunk)))\n\n  (defmacro with-muffled-conditions ((conditions) &body body)\n    \"Shorthand syntax for CALL-WITH-MUFFLED-CONDITIONS\"\n    `(call-with-muffled-conditions #'(lambda () ,@body) ,conditions)))\n\n;;; Conditions\n\n(with-upgradability ()\n  (define-condition not-implemented-error (error)\n    ((functionality :initarg :functionality)\n     (format-control :initarg :format-control)\n     (format-arguments :initarg :format-arguments))\n    (:report (lambda (condition stream)\n               (format stream \"Not (currently) implemented on ~A: ~S~@[ ~?~]\"\n                       (nth-value 1 (symbol-call :uiop :implementation-type))\n                       (slot-value condition 'functionality)\n                       (slot-value condition 'format-control)\n                       (slot-value condition 'format-arguments)))))\n\n  (defun not-implemented-error (functionality &optional format-control &rest format-arguments)\n    \"Signal an error because some FUNCTIONALITY is not implemented in the current version\nof the software on the current platform; it may or may not be implemented in different combinations\nof version of the software and of the underlying platform. Optionally, report a formatted error\nmessage.\"\n    (error 'not-implemented-error\n           :functionality functionality\n           :format-control format-control\n           :format-arguments format-arguments))\n\n  (define-condition parameter-error (error)\n    ((functionality :initarg :functionality)\n     (format-control :initarg :format-control)\n     (format-arguments :initarg :format-arguments))\n    (:report (lambda (condition stream)\n               (apply 'format stream\n                       (slot-value condition 'format-control)\n                       (slot-value condition 'functionality)\n                       (slot-value condition 'format-arguments)))))\n\n  ;; Note that functionality MUST be passed as the second argument to parameter-error, just after\n  ;; the format-control. If you want it to not appear in first position in actual message, use\n  ;; ~* and ~:* to adjust parameter order.\n  (defun parameter-error (format-control functionality &rest format-arguments)\n    \"Signal an error because some FUNCTIONALITY or its specific implementation on a given underlying\nplatform does not accept a given parameter or combination of parameters. Report a formatted error\nmessage, that takes the functionality as its first argument (that can be skipped with ~*).\"\n    (error 'parameter-error\n           :functionality functionality\n           :format-control format-control\n           :format-arguments format-arguments)))\n\n(with-upgradability ()\n  (defun boolean-to-feature-expression (value)\n    \"Converts a boolean VALUE to a form suitable for testing with #+.\"\n    (if value\n        '(:and)\n        '(:or)))\n\n  (defun symbol-test-to-feature-expression (name package)\n    \"Check if a symbol with a given NAME exists in PACKAGE and returns a\nform suitable for testing with #+.\"\n    (boolean-to-feature-expression\n     (find-symbol* name package nil))))\n(uiop/package:define-package :uiop/version\n  (:recycle :uiop/version :uiop/utility :asdf)\n  (:use :uiop/common-lisp :uiop/package :uiop/utility)\n  (:export\n   #:*uiop-version*\n   #:parse-version #:unparse-version #:version< #:version<= #:version= ;; version support, moved from uiop/utility\n   #:next-version\n   #:deprecated-function-condition #:deprecated-function-name ;; deprecation control\n   #:deprecated-function-style-warning #:deprecated-function-warning\n   #:deprecated-function-error #:deprecated-function-should-be-deleted\n   #:version-deprecation #:with-deprecation))\n(in-package :uiop/version)\n\n(with-upgradability ()\n  (defparameter *uiop-version* \"3.3.7\")\n\n  (defun unparse-version (version-list)\n    \"From a parsed version (a list of natural numbers), compute the version string\"\n    (format nil \"~{~D~^.~}\" version-list))\n\n  (defun parse-version (version-string &optional on-error)\n    \"Parse a VERSION-STRING as a series of natural numbers separated by dots.\nReturn a (non-null) list of integers if the string is valid;\notherwise return NIL.\n\nWhen invalid, ON-ERROR is called as per CALL-FUNCTION before to return NIL,\nwith format arguments explaining why the version is invalid.\nON-ERROR is also called if the version is not canonical\nin that it doesn't print back to itself, but the list is returned anyway.\"\n    (block nil\n      (unless (stringp version-string)\n        (call-function on-error \"~S: ~S is not a string\" 'parse-version version-string)\n        (return))\n      (unless (loop :for prev = nil :then c :for c :across version-string\n                    :always (or (digit-char-p c)\n                                (and (eql c #\\.) prev (not (eql prev #\\.))))\n                    :finally (return (and c (digit-char-p c))))\n        (call-function on-error \"~S: ~S doesn't follow asdf version numbering convention\"\n                       'parse-version version-string)\n        (return))\n      (let* ((version-list\n               (mapcar #'parse-integer (split-string version-string :separator \".\")))\n             (normalized-version (unparse-version version-list)))\n        (unless (equal version-string normalized-version)\n          (call-function on-error \"~S: ~S contains leading zeros\" 'parse-version version-string))\n        version-list)))\n\n  (defun next-version (version)\n    \"When VERSION is not nil, it is a string, then parse it as a version, compute the next version\nand return it as a string.\"\n    (when version\n      (let ((version-list (parse-version version)))\n        (incf (car (last version-list)))\n        (unparse-version version-list))))\n\n  (defun version< (version1 version2)\n    \"Given two version strings, return T if the second is strictly newer\"\n    (let ((v1 (parse-version version1 nil))\n          (v2 (parse-version version2 nil)))\n      (lexicographic< '< v1 v2)))\n\n  (defun version<= (version1 version2)\n    \"Given two version strings, return T if the second is newer or the same\"\n    (not (version< version2 version1))))\n\n  (defun version= (version1 version2)\n    \"Given two version strings, return T if the first is newer or the same and\nthe second is also newer or the same.\"\n    (and (version<= version1 version2)\n         (version<= version2 version1)))\n\n\n(with-upgradability ()\n  (define-condition deprecated-function-condition (condition)\n    ((name :initarg :name :reader deprecated-function-name)))\n  (define-condition deprecated-function-style-warning (deprecated-function-condition style-warning) ())\n  (define-condition deprecated-function-warning (deprecated-function-condition warning) ())\n  (define-condition deprecated-function-error (deprecated-function-condition error) ())\n  (define-condition deprecated-function-should-be-deleted (deprecated-function-condition error) ())\n\n  (defun deprecated-function-condition-kind (type)\n    (ecase type\n      ((deprecated-function-style-warning) :style-warning)\n      ((deprecated-function-warning) :warning)\n      ((deprecated-function-error) :error)\n      ((deprecated-function-should-be-deleted) :delete)))\n\n  (defmethod print-object ((c deprecated-function-condition) stream)\n    (let ((name (deprecated-function-name c)))\n      (cond\n        (*print-readably*\n         (let ((fmt \"#.(make-condition '~S :name ~S)\")\n               (args (list (type-of c) name)))\n           (if *read-eval*\n               (apply 'format stream fmt args)\n               (error \"Can't print ~?\" fmt args))))\n        (*print-escape*\n         (print-unreadable-object (c stream :type t) (format stream \":name ~S\" name)))\n        (t\n         (let ((*package* (find-package :cl))\n               (type (type-of c)))\n           (format stream\n                   (if (eq type 'deprecated-function-should-be-deleted)\n                       \"~A: Still defining deprecated function~:P ~{~S~^ ~} that promised to delete\"\n                       \"~A: Using deprecated function ~S -- please update your code to use a newer API.~\n~@[~%The docstring for this function says:~%~A~%~]\")\n                   type name (when (symbolp name) (documentation name 'function))))))))\n\n  (defun notify-deprecated-function (status name)\n    (ecase status\n      ((nil) nil)\n      ((:style-warning) (style-warn 'deprecated-function-style-warning :name name))\n      ((:warning) (warn 'deprecated-function-warning :name name))\n      ((:error) (cerror \"USE FUNCTION ANYWAY\" 'deprecated-function-error :name name))))\n\n  (defun version-deprecation (version &key (style-warning nil)\n                                        (warning (next-version style-warning))\n                                        (error (next-version warning))\n                                        (delete (next-version error)))\n    \"Given a VERSION string, and the starting versions for notifying the programmer of\nvarious levels of deprecation, return the current level of deprecation as per WITH-DEPRECATION\nthat is the highest level that has a declared version older than the specified version.\nEach start version for a level of deprecation can be specified by a keyword argument, or\nif left unspecified, will be the NEXT-VERSION of the immediate lower level of deprecation.\"\n    (cond\n      ((and delete (version<= delete version)) :delete)\n      ((and error (version<= error version)) :error)\n      ((and warning (version<= warning version)) :warning)\n      ((and style-warning (version<= style-warning version)) :style-warning)))\n\n  (defmacro with-deprecation ((level) &body definitions)\n    \"Given a deprecation LEVEL (a form to be EVAL'ed at macro-expansion time), instrument the\nDEFUN and DEFMETHOD forms in DEFINITIONS to notify the programmer of the deprecation of the function\nwhen it is compiled or called.\n\nIncreasing levels (as result from evaluating LEVEL) are: NIL (not deprecated yet),\n:STYLE-WARNING (a style warning is issued when used), :WARNING (a full warning is issued when used),\n:ERROR (a continuable error instead), and :DELETE (it's an error if the code is still there while\nat that level).\n\nForms other than DEFUN and DEFMETHOD are not instrumented, and you can protect a DEFUN or DEFMETHOD\nfrom instrumentation by enclosing it in a PROGN.\"\n    (let ((level (eval level)))\n      (check-type level (member nil :style-warning :warning :error :delete))\n      (when (eq level :delete)\n        (error 'deprecated-function-should-be-deleted :name\n               (mapcar 'second\n                       (remove-if-not #'(lambda (x) (member x '(defun defmethod)))\n                                      definitions :key 'first))))\n      (labels ((instrument (name head body whole)\n                 (if level\n                     (let ((notifiedp\n                            (intern (format nil \"*~A-~A-~A-~A*\"\n                                            :deprecated-function level name :notified-p))))\n                       (multiple-value-bind (remaining-forms declarations doc-string)\n                           (parse-body body :documentation t :whole whole)\n                         `(progn\n                            (defparameter ,notifiedp nil)\n                            ;; tell some implementations to use the compiler-macro\n                            (declaim (inline ,name))\n                            (define-compiler-macro ,name (&whole form &rest args)\n                              (declare (ignore args))\n                              (notify-deprecated-function ,level ',name)\n                              form)\n                            (,@head ,@(when doc-string (list doc-string)) ,@declarations\n                                    (unless ,notifiedp\n                                      (setf ,notifiedp t)\n                                      (notify-deprecated-function ,level ',name))\n                                    ,@remaining-forms))))\n                     `(progn\n                        (eval-when (:compile-toplevel :load-toplevel :execute)\n                          (setf (compiler-macro-function ',name) nil))\n                        (declaim (notinline ,name))\n                        (,@head ,@body)))))\n        `(progn\n           ,@(loop :for form :in definitions :collect\n               (cond\n                 ((and (consp form) (eq (car form) 'defun))\n                  (instrument (second form) (subseq form 0 3) (subseq form 3) form))\n                 ((and (consp form) (eq (car form) 'defmethod))\n                  (let ((body-start (if (listp (third form)) 3 4)))\n                    (instrument (second form)\n                                (subseq form 0 body-start)\n                                (subseq form body-start)\n                                form)))\n                 (t\n                  form))))))))\n;;;; ---------------------------------------------------------------------------\n;;;; Access to the Operating System\n\n(uiop/package:define-package :uiop/os\n  (:use :uiop/common-lisp :uiop/package :uiop/utility)\n  (:export\n   #:featurep #:os-unix-p #:os-macosx-p #:os-windows-p #:os-genera-p #:detect-os ;; features\n   #:os-cond\n   #:getenv #:getenvp ;; environment variables\n   #:implementation-identifier ;; implementation identifier\n   #:implementation-type #:*implementation-type*\n   #:operating-system #:architecture #:lisp-version-string\n   #:hostname #:getcwd #:chdir\n   ;; Windows shortcut support\n   #:read-null-terminated-string #:read-little-endian\n   #:parse-file-location-info #:parse-windows-shortcut))\n(in-package :uiop/os)\n\n;;; Features\n(with-upgradability ()\n  (defun featurep (x &optional (*features* *features*))\n    \"Checks whether a feature expression X is true with respect to the *FEATURES* set,\nas per the CLHS standard for #+ and #-. Beware that just like the CLHS,\nwe assume symbols from the KEYWORD package are used, but that unless you're using #+/#-\nyour reader will not have magically used the KEYWORD package, so you need specify\nkeywords explicitly.\"\n    (cond\n      ((atom x) (and (member x *features*) t))\n      ((eq :not (car x)) (assert (null (cddr x))) (not (featurep (cadr x))))\n      ((eq :or (car x)) (some #'featurep (cdr x)))\n      ((eq :and (car x)) (every #'featurep (cdr x)))\n      (t (parameter-error \"~S: malformed feature specification ~S\" 'featurep x))))\n\n  ;; Starting with UIOP 3.1.5, these are runtime tests.\n  ;; You may bind *features* with a copy of what your target system offers to test its properties.\n  (defun os-macosx-p ()\n    \"Is the underlying operating system MacOS X?\"\n    ;; OS-MACOSX is not mutually exclusive with OS-UNIX,\n    ;; in fact the former implies the latter.\n    (featurep '(:or :darwin (:and :allegro :macosx) (:and :clisp :macos))))\n\n  (defun os-unix-p ()\n    \"Is the underlying operating system some Unix variant?\"\n    (or (featurep '(:or :unix :cygwin :haiku)) (os-macosx-p)))\n\n  (defun os-windows-p ()\n    \"Is the underlying operating system Microsoft Windows?\"\n    (and (not (os-unix-p)) (featurep '(:or :win32 :windows :mswindows :mingw32 :mingw64))))\n\n  (defun os-genera-p ()\n    \"Is the underlying operating system Genera (running on a Symbolics Lisp Machine)?\"\n    (featurep :genera))\n\n  (defun os-oldmac-p ()\n    \"Is the underlying operating system an (emulated?) MacOS 9 or earlier?\"\n    (featurep :mcl))\n\n  (defun os-haiku-p ()\n    \"Is the underlying operating system Haiku?\"\n    (featurep :haiku))\n\n  (defun os-mezzano-p ()\n    \"Is the underlying operating system Mezzano?\"\n    (featurep :mezzano))\n\n  (defun detect-os ()\n    \"Detects the current operating system. Only needs be run at compile-time,\nexcept on ABCL where it might change between FASL compilation and runtime.\"\n    (loop :with o\n          :for (feature . detect) :in '((:os-unix . os-unix-p) (:os-macosx . os-macosx-p)\n                                        (:os-windows . os-windows-p)\n                                        (:os-genera . os-genera-p) (:os-oldmac . os-oldmac-p)\n                                        (:os-haiku . os-haiku-p)\n                                        (:os-mezzano . os-mezzano-p))\n          :when (and (or (not o) (eq feature :os-macosx) (eq feature :os-haiku)) (funcall detect))\n            :do (setf o feature) (pushnew feature *features*)\n          :else :do (setf *features* (remove feature *features*))\n          :finally\n             (return (or o (error \"Congratulations for trying ASDF on an operating system~%~\nthat is neither Unix, nor Windows, nor Genera, nor even old MacOS.~%Now you port it.\")))))\n\n  (defmacro os-cond (&rest clauses)\n    #+abcl `(cond ,@clauses)\n    #-abcl (loop :for (test . body) :in clauses :when (eval test) :return `(progn ,@body)))\n\n  (detect-os))\n\n;;;; Environment variables: getting them, and parsing them.\n(with-upgradability ()\n  (defun getenv (x)\n    \"Query the environment, as in C getenv.\nBeware: may return empty string if a variable is present but empty;\nuse getenvp to return NIL in such a case.\"\n    (declare (ignorable x))\n    #+(or abcl clasp clisp ecl xcl) (ext:getenv x)\n    #+allegro (sys:getenv x)\n    #+clozure (ccl:getenv x)\n    #+cmucl (unix:unix-getenv x)\n    #+scl (cdr (assoc x ext:*environment-list* :test #'string=))\n    #+cormanlisp\n    (let* ((buffer (ct:malloc 1))\n           (cname (ct:lisp-string-to-c-string x))\n           (needed-size (win:getenvironmentvariable cname buffer 0))\n           (buffer1 (ct:malloc (1+ needed-size))))\n      (prog1 (if (zerop (win:getenvironmentvariable cname buffer1 needed-size))\n                 nil\n                 (ct:c-string-to-lisp-string buffer1))\n        (ct:free buffer)\n        (ct:free buffer1)))\n    #+gcl (system:getenv x)\n    #+(or genera mezzano) nil\n    #+lispworks (lispworks:environment-variable x)\n    #+mcl (ccl:with-cstrs ((name x))\n            (let ((value (_getenv name)))\n              (unless (ccl:%null-ptr-p value)\n                (ccl:%get-cstring value))))\n    #+mkcl (#.(or (find-symbol* 'getenv :si nil) (find-symbol* 'getenv :mk-ext nil)) x)\n    #+sbcl (sb-ext:posix-getenv x)\n    #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mcl mezzano mkcl sbcl scl xcl)\n    (not-implemented-error 'getenv))\n\n  (defsetf getenv (x) (val)\n    \"Set an environment variable.\"\n    (declare (ignorable x val))         ; for the not-implemented cases.\n    (if (constantp val)\n        (if val\n         #+allegro `(setf (sys:getenv ,x) ,val)\n         #+clasp `(ext:setenv ,x ,val)\n         #+clisp `(system::setenv ,x ,val)\n         #+clozure `(ccl:setenv ,x ,val)\n         #+cmucl `(unix:unix-setenv ,x ,val 1)\n         #+ecl `(ext:setenv ,x ,val)\n         #+lispworks `(setf (lispworks:environment-variable ,x) ,val)\n         #+mkcl `(mkcl:setenv ,x ,val)\n         #+sbcl `(progn (require :sb-posix) (symbol-call :sb-posix :setenv ,x ,val 1))\n         #-(or allegro clasp clisp clozure cmucl ecl lispworks mkcl sbcl)\n         '(not-implemented-error '(setf getenv))\n         ;; VAL is NIL, unset the variable\n         #+allegro `(symbol-call :excl.osi :unsetenv ,x)\n         ;; #+clasp `(ext:setenv ,x ,val) ; UNSETENV is not supported.\n         #+clisp `(system::setenv ,x ,val) ; need fix -- no idea if this works.\n         #+clozure `(ccl:unsetenv ,x)\n         #+cmucl `(unix:unix-unsetenv ,x)\n         #+ecl `(ext:setenv ,x ,val) ; Looked at source, don't see UNSETENV\n         #+lispworks `(setf (lispworks:environment-variable ,x) ,val) ; according to their docs, this should unset the variable\n         #+mkcl `(mkcl:setenv ,x ,val) ; like other ECL-family implementations, don't see UNSETENV\n         #+sbcl `(progn (require :sb-posix) (symbol-call :sb-posix :unsetenv ,x))\n         #-(or allegro clisp clozure cmucl ecl lispworks mkcl sbcl)\n         '(not-implemented-error 'unsetenv))\n        `(if ,val\n             #+allegro (setf (sys:getenv ,x) ,val)\n             #+clasp (ext:setenv ,x ,val)\n             #+clisp (system::setenv ,x ,val)\n             #+clozure (ccl:setenv ,x ,val)\n             #+cmucl (unix:unix-setenv ,x ,val 1)\n             #+ecl (ext:setenv ,x ,val)\n             #+lispworks (setf (lispworks:environment-variable ,x) ,val)\n             #+mkcl (mkcl:setenv ,x ,val)\n             #+sbcl (progn (require :sb-posix) (symbol-call :sb-posix :setenv ,x ,val 1))\n             #-(or allegro clasp clisp clozure cmucl ecl lispworks mkcl sbcl)\n             '(not-implemented-error '(setf getenv))\n             ;; VAL is NIL, unset the variable\n             #+allegro (symbol-call :excl.osi :unsetenv ,x)\n             ;; #+clasp (ext:setenv ,x ,val) ; UNSETENV not supported\n             #+clisp (system::setenv ,x ,val) ; need fix -- no idea if this works.\n             #+clozure (ccl:unsetenv ,x)\n             #+cmucl (unix:unix-unsetenv ,x)\n             #+ecl (ext:setenv ,x ,val) ; Looked at source, don't see UNSETENV\n             #+lispworks (setf (lispworks:environment-variable ,x) ,val) ; according to their docs, this should unset the variable\n             #+mkcl (mkcl:setenv ,x ,val) ; like other ECL-family implementations, don't see UNSETENV\n             #+sbcl (progn (require :sb-posix) (symbol-call :sb-posix :unsetenv ,x))\n             #-(or allegro clisp clozure cmucl ecl lispworks mkcl sbcl)\n             '(not-implemented-error 'unsetenv))))\n\n  (defun getenvp (x)\n    \"Predicate that is true if the named variable is present in the libc environment,\nthen returning the non-empty string value of the variable\"\n    (let ((g (getenv x))) (and (not (emptyp g)) g))))\n\n\n;;;; implementation-identifier\n;;\n;; produce a string to identify current implementation.\n;; Initially stolen from SLIME's SWANK, completely rewritten since.\n;; We're back to runtime checking, for the sake of e.g. ABCL.\n\n(with-upgradability ()\n  (defun first-feature (feature-sets)\n    \"A helper for various feature detection functions\"\n    (dolist (x feature-sets)\n      (multiple-value-bind (short long feature-expr)\n          (if (consp x)\n              (values (first x) (second x) (cons :or (rest x)))\n              (values x x x))\n        (when (featurep feature-expr)\n          (return (values short long))))))\n\n  (defun implementation-type ()\n    \"The type of Lisp implementation used, as a short UIOP-standardized keyword\"\n    (first-feature\n     '(:abcl (:acl :allegro) (:ccl :clozure) :clisp (:corman :cormanlisp)\n       (:cmu :cmucl :cmu) :clasp :ecl :gcl\n       (:lwpe :lispworks-personal-edition) (:lw :lispworks)\n       :mcl :mezzano :mkcl :sbcl :scl (:smbx :symbolics) :xcl)))\n\n  (defvar *implementation-type* (implementation-type)\n    \"The type of Lisp implementation used, as a short UIOP-standardized keyword\")\n\n  (defun operating-system ()\n    \"The operating system of the current host\"\n    (first-feature\n     '(:cygwin\n       (:win :windows :mswindows :win32 :mingw32) ;; try cygwin first!\n       (:linux :linux :linux-target) ;; for GCL at least, must appear before :bsd\n       (:macosx :macosx :darwin :darwin-target :apple) ; also before :bsd\n       (:solaris :solaris :sunos)\n       (:bsd :bsd :freebsd :netbsd :openbsd :dragonfly)\n       :unix\n       :genera\n       :mezzano)))\n\n  (defun architecture ()\n    \"The CPU architecture of the current host\"\n    (first-feature\n     '((:x64 :x86-64 :x86_64 :x8664-target :amd64 (:and :word-size=64 :pc386))\n       (:x86 :x86 :i386 :i486 :i586 :i686 :pentium3 :pentium4 :pc386 :iapx386 :x8632-target)\n       (:ppc64 :ppc64 :ppc64-target) (:ppc32 :ppc32 :ppc32-target :ppc :powerpc)\n       :hppa64 :hppa :sparc64 (:sparc32 :sparc32 :sparc)\n       :mipsel :mipseb :mips :alpha\n       (:arm64 :arm64 :aarch64 :armv8l :armv8b :aarch64_be :|aarch64|)\n       (:arm :arm :arm-target) :vlm :imach\n       ;; Java comes last: if someone uses C via CFFI or otherwise JNA or JNI,\n       ;; we may have to segregate the code still by architecture.\n       (:java :java :java-1.4 :java-1.5 :java-1.6 :java-1.7))))\n\n  #+clozure\n  (defun ccl-fasl-version ()\n    ;; the fasl version is target-dependent from CCL 1.8 on.\n    (or (let ((s 'ccl::target-fasl-version))\n          (and (fboundp s) (funcall s)))\n        (and (boundp 'ccl::fasl-version)\n             (symbol-value 'ccl::fasl-version))\n        (error \"Can't determine fasl version.\")))\n\n  (defun lisp-version-string ()\n    \"return a string that identifies the current Lisp implementation version\"\n    (let ((s (lisp-implementation-version)))\n      (car ; as opposed to OR, this idiom prevents some unreachable code warning\n       (list\n        #+allegro\n        (format nil \"~A~@[~A~]~@[~A~]~@[~A~]\"\n                excl::*common-lisp-version-number*\n                ;; M means \"modern\", as opposed to ANSI-compatible mode (which I consider default)\n                (and (eq excl:*current-case-mode* :case-sensitive-lower) \"M\")\n                ;; Note if not using International ACL\n                ;; see http://www.franz.com/support/documentation/8.1/doc/operators/excl/ics-target-case.htm\n                (excl:ics-target-case (:-ics \"8\"))\n                (and (member :smp *features*) \"SBT\"))\n        #+armedbear (format nil \"~a-fasl~a\" s system::*fasl-version*)\n        #+clisp\n        (subseq s 0 (position #\\space s)) ; strip build information (date, etc.)\n        #+clozure\n        (format nil \"~d.~d-f~d\" ; shorten for windows\n                ccl::*openmcl-major-version*\n                ccl::*openmcl-minor-version*\n                (logand (ccl-fasl-version) #xFF))\n        #+cmucl (substitute #\\- #\\/ s)\n        #+scl (format nil \"~A~A\" s\n                      ;; ANSI upper case vs lower case.\n                      (ecase ext:*case-mode* (:upper \"\") (:lower \"l\")))\n        #+ecl (format nil \"~A~@[-~A~]\" s\n                      (let ((vcs-id (ext:lisp-implementation-vcs-id)))\n                        (unless (equal vcs-id \"UNKNOWN\")\n                          (subseq vcs-id 0 (min (length vcs-id) 8)))))\n        #+gcl (subseq s (1+ (position #\\space s)))\n        #+genera\n        (multiple-value-bind (major minor) (sct:get-system-version \"System\")\n          (format nil \"~D.~D\" major minor))\n        #+mcl (subseq s 8) ; strip the leading \"Version \"\n        #+mezzano (format nil \"~A-~D\"\n                          (subseq s 0 (position #\\space s)) ; strip commit hash\n                          sys.int::*llf-version*)\n        ;; seems like there should be a shorter way to do this, like ACALL.\n        #+mkcl (or\n                (let ((fname (find-symbol* '#:git-describe-this-mkcl :mkcl nil)))\n                  (when (and fname (fboundp fname))\n                    (funcall fname)))\n                s)\n        s))))\n\n  (defun implementation-identifier ()\n    \"Return a string that identifies the ABI of the current implementation,\nsuitable for use as a directory name to segregate Lisp FASLs, C dynamic libraries, etc.\"\n    (substitute-if\n     #\\_ #'(lambda (x) (find x \" /:;&^\\\\|?<>(){}[]$#`'\\\"\"))\n     (format nil \"~(~a~@{~@[-~a~]~}~)\"\n             (or (implementation-type) (lisp-implementation-type))\n             (lisp-version-string)\n             (or (operating-system) (software-type))\n             (or (architecture) (machine-type))\n             #+sbcl (if (featurep :sb-thread) \"S\" \"\")))))\n\n\n;;;; Other system information\n\n(with-upgradability ()\n  (defun hostname ()\n    \"return the hostname of the current host\"\n    #+(or abcl clasp clozure cmucl ecl genera lispworks mcl mezzano mkcl sbcl scl xcl) (machine-instance)\n    #+cormanlisp \"localhost\" ;; is there a better way? Does it matter?\n    #+allegro (symbol-call :excl.osi :gethostname)\n    #+clisp (first (split-string (machine-instance) :separator \" \"))\n    #+gcl (system:gethostname)))\n\n\n;;; Current directory\n(with-upgradability ()\n\n  #+cmucl\n  (defun parse-unix-namestring* (unix-namestring)\n    \"variant of LISP::PARSE-UNIX-NAMESTRING that returns a pathname object\"\n    (multiple-value-bind (host device directory name type version)\n        (lisp::parse-unix-namestring unix-namestring 0 (length unix-namestring))\n      (make-pathname :host (or host lisp::*unix-host*) :device device\n                     :directory directory :name name :type type :version version)))\n\n  (defun getcwd ()\n    \"Get the current working directory as per POSIX getcwd(3), as a pathname object\"\n    (or #+(or abcl genera mezzano xcl) (truename *default-pathname-defaults*) ;; d-p-d is canonical!\n        #+allegro (excl::current-directory)\n        #+clisp (ext:default-directory)\n        #+clozure (ccl:current-directory)\n        #+(or cmucl scl) (#+cmucl parse-unix-namestring* #+scl lisp::parse-unix-namestring\n                        (strcat (nth-value 1 (unix:unix-current-directory)) \"/\"))\n        #+cormanlisp (pathname (pl::get-current-directory)) ;; Q: what type does it return?\n        #+(or clasp ecl) (ext:getcwd)\n        #+gcl (let ((*default-pathname-defaults* #p\"\")) (truename #p\"\"))\n        #+lispworks (hcl:get-working-directory)\n        #+mkcl (mk-ext:getcwd)\n        #+sbcl (sb-ext:parse-native-namestring (sb-unix:posix-getcwd/))\n        #+xcl (extensions:current-directory)\n        (not-implemented-error 'getcwd)))\n\n  (defun chdir (x)\n    \"Change current directory, as per POSIX chdir(2), to a given pathname object\"\n    (if-let (x (pathname x))\n      #+(or abcl genera mezzano xcl) (setf *default-pathname-defaults* (truename x)) ;; d-p-d is canonical!\n      #+allegro (excl:chdir x)\n      #+clisp (ext:cd x)\n      #+clozure (setf (ccl:current-directory) x)\n      #+(or cmucl scl) (unix:unix-chdir (ext:unix-namestring x))\n      #+cormanlisp (unless (zerop (win32::_chdir (namestring x)))\n                     (error \"Could not set current directory to ~A\" x))\n      #+ecl (ext:chdir x)\n      #+clasp (ext:chdir x t)\n      #+gcl (system:chdir x)\n      #+lispworks (hcl:change-directory x)\n      #+mkcl (mk-ext:chdir x)\n      #+sbcl (progn (require :sb-posix) (symbol-call :sb-posix :chdir (sb-ext:native-namestring x)))\n      #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mkcl sbcl scl xcl)\n      (not-implemented-error 'chdir))))\n\n\n;;;; -----------------------------------------------------------------\n;;;; Windows shortcut support.  Based on:\n;;;;\n;;;; Jesse Hager: The Windows Shortcut File Format.\n;;;; http://www.wotsit.org/list.asp?fc=13\n\n#-(or clisp genera) ; CLISP doesn't need it, and READ-SEQUENCE annoys old Genera that doesn't need it\n(with-upgradability ()\n  (defparameter *link-initial-dword* 76)\n  (defparameter *link-guid* #(1 20 2 0 0 0 0 0 192 0 0 0 0 0 0 70))\n\n  (defun read-null-terminated-string (s)\n    \"Read a null-terminated string from an octet stream S\"\n    ;; note: doesn't play well with UNICODE\n    (with-output-to-string (out)\n      (loop :for code = (read-byte s)\n            :until (zerop code)\n            :do (write-char (code-char code) out))))\n\n  (defun read-little-endian (s &optional (bytes 4))\n    \"Read a number in little-endian format from an byte (octet) stream S,\nthe number having BYTES octets (defaulting to 4).\"\n    (loop :for i :from 0 :below bytes\n          :sum (ash (read-byte s) (* 8 i))))\n\n  (defun parse-file-location-info (s)\n    \"helper to parse-windows-shortcut\"\n    (let ((start (file-position s))\n          (total-length (read-little-endian s))\n          (end-of-header (read-little-endian s))\n          (fli-flags (read-little-endian s))\n          (local-volume-offset (read-little-endian s))\n          (local-offset (read-little-endian s))\n          (network-volume-offset (read-little-endian s))\n          (remaining-offset (read-little-endian s)))\n      (declare (ignore total-length end-of-header local-volume-offset))\n      (unless (zerop fli-flags)\n        (cond\n          ((logbitp 0 fli-flags)\n           (file-position s (+ start local-offset)))\n          ((logbitp 1 fli-flags)\n           (file-position s (+ start\n                               network-volume-offset\n                               #x14))))\n        (strcat (read-null-terminated-string s)\n                (progn\n                  (file-position s (+ start remaining-offset))\n                  (read-null-terminated-string s))))))\n\n  (defun parse-windows-shortcut (pathname)\n    \"From a .lnk windows shortcut, extract the pathname linked to\"\n    ;; NB: doesn't do much checking & doesn't look like it will work well with UNICODE.\n    (with-open-file (s pathname :element-type '(unsigned-byte 8))\n      (handler-case\n          (when (and (= (read-little-endian s) *link-initial-dword*)\n                     (let ((header (make-array (length *link-guid*))))\n                       (read-sequence header s)\n                       (equalp header *link-guid*)))\n            (let ((flags (read-little-endian s)))\n              (file-position s 76)        ;skip rest of header\n              (when (logbitp 0 flags)\n                ;; skip shell item id list\n                (let ((length (read-little-endian s 2)))\n                  (file-position s (+ length (file-position s)))))\n              (cond\n                ((logbitp 1 flags)\n                 (parse-file-location-info s))\n                (t\n                 (when (logbitp 2 flags)\n                   ;; skip description string\n                   (let ((length (read-little-endian s 2)))\n                     (file-position s (+ length (file-position s)))))\n                 (when (logbitp 3 flags)\n                   ;; finally, our pathname\n                   (let* ((length (read-little-endian s 2))\n                          (buffer (make-array length)))\n                     (read-sequence buffer s)\n                     (map 'string #'code-char buffer)))))))\n        (end-of-file (c)\n          (declare (ignore c))\n          nil)))))\n;;;; -------------------------------------------------------------------------\n;;;; Portability layer around Common Lisp pathnames\n;; This layer allows for portable manipulation of pathname objects themselves,\n;; which all is necessary prior to any access the filesystem or environment.\n\n(uiop/package:define-package :uiop/pathname\n  (:nicknames :asdf/pathname) ;; deprecated. Used by ceramic\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os)\n  (:export\n   ;; Making and merging pathnames, portably\n   #:normalize-pathname-directory-component #:denormalize-pathname-directory-component\n   #:merge-pathname-directory-components #:*unspecific-pathname-type* #:make-pathname*\n   #:make-pathname-component-logical #:make-pathname-logical\n   #:merge-pathnames*\n   #:nil-pathname #:*nil-pathname* #:with-pathname-defaults\n   ;; Predicates\n   #:pathname-equal #:logical-pathname-p #:physical-pathname-p #:physicalize-pathname\n   #:absolute-pathname-p #:relative-pathname-p #:hidden-pathname-p #:file-pathname-p\n   ;; Directories\n   #:pathname-directory-pathname #:pathname-parent-directory-pathname\n   #:directory-pathname-p #:ensure-directory-pathname\n   ;; Parsing filenames\n   #:split-name-type #:parse-unix-namestring #:unix-namestring\n   #:split-unix-namestring-directory-components\n   ;; Absolute and relative pathnames\n   #:subpathname #:subpathname*\n   #:ensure-absolute-pathname\n   #:pathname-root #:pathname-host-pathname\n   #:subpathp #:enough-pathname #:with-enough-pathname #:call-with-enough-pathname\n   ;; Checking constraints\n   #:ensure-pathname ;; implemented in filesystem.lisp to accommodate for existence constraints\n   ;; Wildcard pathnames\n   #:*wild* #:*wild-file* #:*wild-file-for-directory* #:*wild-directory*\n   #:*wild-inferiors* #:*wild-path* #:wilden\n   ;; Translate a pathname\n   #:relativize-directory-component #:relativize-pathname-directory\n   #:directory-separator-for-host #:directorize-pathname-host-device\n   #:translate-pathname*\n   #:*output-translation-function*))\n(in-package :uiop/pathname)\n\n;;; Normalizing pathnames across implementations\n\n(with-upgradability ()\n  (defun normalize-pathname-directory-component (directory)\n    \"Convert the DIRECTORY component from a format usable by the underlying\nimplementation's MAKE-PATHNAME and other primitives to a CLHS-standard format\nthat is a list and not a string.\"\n    (cond\n      #-(or cmucl sbcl scl) ;; these implementations already normalize directory components.\n      ((stringp directory) `(:absolute ,directory))\n      ((or (null directory)\n           (and (consp directory) (member (first directory) '(:absolute :relative))))\n       directory)\n      #+gcl\n      ((consp directory)\n       (cons :relative directory))\n      (t\n       (parameter-error (compatfmt \"~@<~S: Unrecognized pathname directory component ~S~@:>\")\n                        'normalize-pathname-directory-component directory))))\n\n  (defun denormalize-pathname-directory-component (directory-component)\n    \"Convert the DIRECTORY-COMPONENT from a CLHS-standard format to a format usable\nby the underlying implementation's MAKE-PATHNAME and other primitives\"\n    directory-component)\n\n  (defun merge-pathname-directory-components (specified defaults)\n    \"Helper for MERGE-PATHNAMES* that handles directory components\"\n    (let ((directory (normalize-pathname-directory-component specified)))\n      (ecase (first directory)\n        ((nil) defaults)\n        (:absolute specified)\n        (:relative\n         (let ((defdir (normalize-pathname-directory-component defaults))\n               (reldir (cdr directory)))\n           (cond\n             ((null defdir)\n              directory)\n             ((not (eq :back (first reldir)))\n              (append defdir reldir))\n             (t\n              (loop :with defabs = (first defdir)\n                    :with defrev = (reverse (rest defdir))\n                    :while (and (eq :back (car reldir))\n                                (or (and (eq :absolute defabs) (null defrev))\n                                    (stringp (car defrev))))\n                    :do (pop reldir) (pop defrev)\n                    :finally (return (cons defabs (append (reverse defrev) reldir)))))))))))\n\n  ;; Giving :unspecific as :type argument to make-pathname is not portable.\n  ;; See CLHS make-pathname and 19.2.2.2.3.\n  ;; This will be :unspecific if supported, or NIL if not.\n  (defparameter *unspecific-pathname-type*\n    #+(or abcl allegro clozure cmucl lispworks sbcl scl) :unspecific\n    #+(or genera clasp clisp ecl mkcl gcl xcl #|These haven't been tested:|# cormanlisp mcl mezzano) nil\n    \"Unspecific type component to use with the underlying implementation's MAKE-PATHNAME\")\n\n  (defun make-pathname* (&rest keys &key directory host device name type version defaults\n                                      #+scl &allow-other-keys)\n    \"Takes arguments like CL:MAKE-PATHNAME in the CLHS, and\n   tries hard to make a pathname that will actually behave as documented,\n   despite the peculiarities of each implementation. DEPRECATED: just use MAKE-PATHNAME.\"\n    (declare (ignore host device directory name type version defaults))\n    (apply 'make-pathname keys))\n\n  (defun make-pathname-component-logical (x)\n    \"Make a pathname component suitable for use in a logical-pathname\"\n    (typecase x\n      ((eql :unspecific) nil)\n      #+clisp (string (string-upcase x))\n      #+clisp (cons (mapcar 'make-pathname-component-logical x))\n      (t x)))\n\n  (defun make-pathname-logical (pathname host)\n    \"Take a PATHNAME's directory, name, type and version components,\nand make a new pathname with corresponding components and specified logical HOST\"\n    (make-pathname\n     :host host\n     :directory (make-pathname-component-logical (pathname-directory pathname))\n     :name (make-pathname-component-logical (pathname-name pathname))\n     :type (make-pathname-component-logical (pathname-type pathname))\n     :version (make-pathname-component-logical (pathname-version pathname))))\n\n  (defun merge-pathnames* (specified &optional (defaults *default-pathname-defaults*))\n    \"MERGE-PATHNAMES* is like MERGE-PATHNAMES except that\nif the SPECIFIED pathname does not have an absolute directory,\nthen the HOST and DEVICE both come from the DEFAULTS, whereas\nif the SPECIFIED pathname does have an absolute directory,\nthen the HOST and DEVICE both come from the SPECIFIED pathname.\nThis is what users want on a modern Unix or Windows operating system,\nunlike the MERGE-PATHNAMES behavior.\nAlso, if either argument is NIL, then the other argument is returned unmodified;\nthis is unlike MERGE-PATHNAMES which always merges with a pathname,\nby default *DEFAULT-PATHNAME-DEFAULTS*, which cannot be NIL.\"\n    (when (null specified) (return-from merge-pathnames* defaults))\n    (when (null defaults) (return-from merge-pathnames* specified))\n    #+scl\n    (ext:resolve-pathname specified defaults)\n    #-scl\n    (let* ((specified (pathname specified))\n           (defaults (pathname defaults))\n           (directory (normalize-pathname-directory-component (pathname-directory specified)))\n           (name (or (pathname-name specified) (pathname-name defaults)))\n           (type (or (pathname-type specified) (pathname-type defaults)))\n           (version (or (pathname-version specified) (pathname-version defaults))))\n      (labels ((unspecific-handler (p)\n                 (if (typep p 'logical-pathname) #'make-pathname-component-logical #'identity)))\n        (multiple-value-bind (host device directory unspecific-handler)\n            (ecase (first directory)\n              ((:absolute)\n               (values (pathname-host specified)\n                       (pathname-device specified)\n                       directory\n                       (unspecific-handler specified)))\n              ((nil :relative)\n               (values (pathname-host defaults)\n                       (pathname-device defaults)\n                       (merge-pathname-directory-components directory (pathname-directory defaults))\n                       (unspecific-handler defaults))))\n          (make-pathname :host host :device device :directory directory\n                         :name (funcall unspecific-handler name)\n                         :type (funcall unspecific-handler type)\n                         :version (funcall unspecific-handler version))))))\n\n  (defun logical-pathname-p (x)\n    \"is X a logical-pathname?\"\n    (typep x 'logical-pathname))\n\n  (defun physical-pathname-p (x)\n    \"is X a pathname that is not a logical-pathname?\"\n    (and (pathnamep x) (not (logical-pathname-p x))))\n\n  (defun physicalize-pathname (x)\n    \"if X is a logical pathname, use translate-logical-pathname on it.\"\n    ;; Ought to be the same as translate-logical-pathname, except the latter borks on CLISP\n    (let ((p (when x (pathname x))))\n      (if (logical-pathname-p p) (translate-logical-pathname p) p)))\n\n  (defun nil-pathname (&optional (defaults *default-pathname-defaults*))\n    \"A pathname that is as neutral as possible for use as defaults\nwhen merging, making or parsing pathnames\"\n    ;; 19.2.2.2.1 says a NIL host can mean a default host;\n    ;; see also \"valid physical pathname host\" in the CLHS glossary, that suggests\n    ;; strings and lists of strings or :unspecific\n    ;; But CMUCL decides to die on NIL.\n    ;; MCL has issues with make-pathname, nil and defaulting\n    (declare (ignorable defaults))\n    #.`(make-pathname :directory nil :name nil :type nil :version nil\n                      :device (or #+(and mkcl os-unix) :unspecific)\n                      :host (or #+cmucl lisp::*unix-host* #+(and mkcl os-unix) \"localhost\")\n                      #+scl ,@'(:scheme nil :scheme-specific-part nil\n                                :username nil :password nil :parameters nil :query nil :fragment nil)\n                      ;; the default shouldn't matter, but we really want something physical\n                      #-mcl ,@'(:defaults defaults)))\n\n  (defvar *nil-pathname* (nil-pathname (physicalize-pathname (user-homedir-pathname)))\n    \"A pathname that is as neutral as possible for use as defaults\nwhen merging, making or parsing pathnames\")\n\n  (defmacro with-pathname-defaults ((&optional defaults) &body body)\n    \"Execute BODY in a context where the *DEFAULT-PATHNAME-DEFAULTS* is as specified,\nwhere leaving the defaults NIL or unspecified means a (NIL-PATHNAME), except\non ABCL, Genera and XCL, where it remains unchanged for it doubles as current-directory.\"\n    `(let ((*default-pathname-defaults*\n             ,(or defaults\n                  #-(or abcl genera xcl) '*nil-pathname*\n                  #+(or abcl genera xcl) '*default-pathname-defaults*)))\n       ,@body)))\n\n\n;;; Some pathname predicates\n(with-upgradability ()\n  (defun pathname-equal (p1 p2)\n    \"Are the two pathnames P1 and P2 reasonably equal in the paths they denote?\"\n    (when (stringp p1) (setf p1 (pathname p1)))\n    (when (stringp p2) (setf p2 (pathname p2)))\n    (flet ((normalize-component (x)\n             (unless (member x '(nil :unspecific :newest (:relative)) :test 'equal)\n               x)))\n      (macrolet ((=? (&rest accessors)\n                   (flet ((frob (x)\n                            (reduce 'list (cons 'normalize-component accessors)\n                                    :initial-value x :from-end t)))\n                     `(equal ,(frob 'p1) ,(frob 'p2)))))\n        (or (and (null p1) (null p2))\n            (and (pathnamep p1) (pathnamep p2)\n                 (and (=? pathname-host)\n                      #-(and mkcl os-unix) (=? pathname-device)\n                      (=? normalize-pathname-directory-component pathname-directory)\n                      (=? pathname-name)\n                      (=? pathname-type)\n                      #-mkcl (=? pathname-version)))))))\n\n  (defun absolute-pathname-p (pathspec)\n    \"If PATHSPEC is a pathname or namestring object that parses as a pathname\npossessing an :ABSOLUTE directory component, return the (parsed) pathname.\nOtherwise return NIL\"\n    (and pathspec\n         (typep pathspec '(or null pathname string))\n         (let ((pathname (pathname pathspec)))\n           (and (eq :absolute (car (normalize-pathname-directory-component\n                                    (pathname-directory pathname))))\n                pathname))))\n\n  (defun relative-pathname-p (pathspec)\n    \"If PATHSPEC is a pathname or namestring object that parses as a pathname\npossessing a :RELATIVE or NIL directory component, return the (parsed) pathname.\nOtherwise return NIL\"\n    (and pathspec\n         (typep pathspec '(or null pathname string))\n         (let* ((pathname (pathname pathspec))\n                (directory (normalize-pathname-directory-component\n                            (pathname-directory pathname))))\n           (when (or (null directory) (eq :relative (car directory)))\n             pathname))))\n\n  (defun hidden-pathname-p (pathname)\n    \"Return a boolean that is true if the pathname is hidden as per Unix style,\ni.e. its name starts with a dot.\"\n    (and pathname (equal (first-char (pathname-name pathname)) #\\.)))\n\n  (defun file-pathname-p (pathname)\n    \"Does PATHNAME represent a file, i.e. has a non-null NAME component?\n\nAccepts NIL, a string (converted through PARSE-NAMESTRING) or a PATHNAME.\n\nNote that this does _not_ check to see that PATHNAME points to an\nactually-existing file.\n\nReturns the (parsed) PATHNAME when true\"\n    (when pathname\n      (let ((pathname (pathname pathname)))\n        (unless (and (member (pathname-name pathname) '(nil :unspecific \"\") :test 'equal)\n                     (member (pathname-type pathname) '(nil :unspecific \"\") :test 'equal))\n          pathname)))))\n\n\n;;; Directory pathnames\n(with-upgradability ()\n  (defun pathname-directory-pathname (pathname)\n    \"Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME,\nand NIL NAME, TYPE and VERSION components\"\n    (when pathname\n      (make-pathname :name nil :type nil :version nil :defaults pathname)))\n\n  (defun pathname-parent-directory-pathname (pathname)\n    \"Returns a new pathname that corresponds to the parent of the current pathname's directory,\ni.e. removing one level of depth in the DIRECTORY component. e.g. if pathname is\nUnix pathname /foo/bar/baz/file.type then return /foo/bar/\"\n    (when pathname\n      (make-pathname :name nil :type nil :version nil\n                     :directory (merge-pathname-directory-components\n                                 '(:relative :back) (pathname-directory pathname))\n                     :defaults pathname)))\n\n  (defun directory-pathname-p (pathname)\n    \"Does PATHNAME represent a directory?\n\nA directory-pathname is a pathname _without_ a filename. The three\nways that the filename components can be missing are for it to be NIL,\n:UNSPECIFIC or the empty string.\n\nNote that this does _not_ check to see that PATHNAME points to an\nactually-existing directory.\"\n    (when pathname\n      ;; I tried using Allegro's excl:file-directory-p, but this cannot be done,\n      ;; because it rejects apparently legal pathnames as\n      ;; ill-formed. [2014/02/10:rpg]\n      (let ((pathname (pathname pathname)))\n        (flet ((check-one (x)\n                 (member x '(nil :unspecific) :test 'equal)))\n          (and (not (wild-pathname-p pathname))\n               (check-one (pathname-name pathname))\n               (check-one (pathname-type pathname))\n               t)))))\n\n  (defun ensure-directory-pathname (pathspec &optional (on-error 'error))\n    \"Converts the non-wild pathname designator PATHSPEC to directory form.\"\n    (cond\n      ((stringp pathspec)\n       (ensure-directory-pathname (pathname pathspec)))\n      ((not (pathnamep pathspec))\n       (call-function on-error (compatfmt \"~@<Invalid pathname designator ~S~@:>\") pathspec))\n      ((wild-pathname-p pathspec)\n       (call-function on-error (compatfmt \"~@<Can't reliably convert wild pathname ~3i~_~S~@:>\") pathspec))\n      ((directory-pathname-p pathspec)\n       pathspec)\n      (t\n       (handler-case\n           (make-pathname :directory (append (or (normalize-pathname-directory-component\n                                                  (pathname-directory pathspec))\n                                                 (list :relative))\n                                             (list #-genera (file-namestring pathspec)\n                                                   ;; On Genera's native filesystem (LMFS),\n                                                   ;; directories have a type and version\n                                                   ;; which must be ignored when converting\n                                                   ;; to a directory pathname\n                                                   #+genera (if (typep pathspec 'fs:lmfs-pathname)\n                                                                (pathname-name pathspec)\n                                                                (file-namestring pathspec))))\n                          :name nil :type nil :version nil :defaults pathspec)\n         (error (c) (call-function on-error (compatfmt \"~@<error while trying to create a directory pathname for ~S: ~A~@:>\") pathspec c)))))))\n\n\n;;; Parsing filenames\n(with-upgradability ()\n  (declaim (ftype function ensure-pathname)) ; forward reference\n\n  (defun split-unix-namestring-directory-components\n      (unix-namestring &key ensure-directory dot-dot)\n    \"Splits the path string UNIX-NAMESTRING, returning four values:\nA flag that is either :absolute or :relative, indicating\n   how the rest of the values are to be interpreted.\nA directory path --- a list of strings and keywords, suitable for\n   use with MAKE-PATHNAME when prepended with the flag value.\n   Directory components with an empty name or the name . are removed.\n   Any directory named .. is read as DOT-DOT, or :BACK if it's NIL (not :UP).\nA last-component, either a file-namestring including type extension,\n   or NIL in the case of a directory pathname.\nA flag that is true iff the unix-style-pathname was just\n   a file-namestring without / path specification.\nENSURE-DIRECTORY forces the namestring to be interpreted as a directory pathname:\nthe third return value will be NIL, and final component of the namestring\nwill be treated as part of the directory path.\n\nAn empty string is thus read as meaning a pathname object with all fields nil.\n\nNote that colon characters #\\: will NOT be interpreted as host specification.\nAbsolute pathnames are only appropriate on Unix-style systems.\n\nThe intention of this function is to support structured component names,\ne.g., \\(:file \\\"foo/bar\\\"\\), which will be unpacked to relative pathnames.\"\n    (check-type unix-namestring string)\n    (check-type dot-dot (member nil :back :up))\n    (if (and (not (find #\\/ unix-namestring)) (not ensure-directory)\n             (plusp (length unix-namestring)))\n        (values :relative () unix-namestring t)\n        (let* ((components (split-string unix-namestring :separator \"/\"))\n               (last-comp (car (last components))))\n          (multiple-value-bind (relative components)\n              (if (equal (first components) \"\")\n                  (if (equal (first-char unix-namestring) #\\/)\n                      (values :absolute (cdr components))\n                      (values :relative nil))\n                  (values :relative components))\n            (setf components (remove-if #'(lambda (x) (member x '(\"\" \".\") :test #'equal))\n                                        components))\n            (setf components (substitute (or dot-dot :back) \"..\" components :test #'equal))\n            (cond\n              ((equal last-comp \"\")\n               (values relative components nil nil)) ; \"\" already removed from components\n              (ensure-directory\n               (values relative components nil nil))\n              (t\n               (values relative (butlast components) last-comp nil)))))))\n\n  (defun split-name-type (filename)\n    \"Split a filename into two values NAME and TYPE that are returned.\nWe assume filename has no directory component.\nThe last . if any separates name and type from from type,\nexcept that if there is only one . and it is in first position,\nthe whole filename is the NAME with an empty type.\nNAME is always a string.\nFor an empty type, *UNSPECIFIC-PATHNAME-TYPE* is returned.\"\n    (check-type filename string)\n    (assert (plusp (length filename)))\n    (destructuring-bind (name &optional (type *unspecific-pathname-type*))\n        (split-string filename :max 2 :separator \".\")\n      (if (equal name \"\")\n          (values filename *unspecific-pathname-type*)\n          (values name type))))\n\n  (defun parse-unix-namestring (name &rest keys &key type defaults dot-dot ensure-directory\n                                &allow-other-keys)\n    \"Coerce NAME into a PATHNAME using standard Unix syntax.\n\nUnix syntax is used whether or not the underlying system is Unix;\non such non-Unix systems it is reliably usable only for relative pathnames.\nThis function is especially useful to manipulate relative pathnames portably,\nwhere it is crucial to possess a portable pathname syntax independent of the underlying OS.\nThis is what PARSE-UNIX-NAMESTRING provides, and why we use it in ASDF.\n\nWhen given a PATHNAME object, just return it untouched.\nWhen given NIL, just return NIL.\nWhen given a non-null SYMBOL, first downcase its name and treat it as a string.\nWhen given a STRING, portably decompose it into a pathname as below.\n\n#\\\\/ separates directory components.\n\nThe last #\\\\/-separated substring is interpreted as follows:\n1- If TYPE is :DIRECTORY or ENSURE-DIRECTORY is true,\n the string is made the last directory component, and NAME and TYPE are NIL.\n if the string is empty, it's the empty pathname with all slots NIL.\n2- If TYPE is NIL, the substring is a file-namestring, and its NAME and TYPE\n are separated by SPLIT-NAME-TYPE.\n3- If TYPE is a string, it is the given TYPE, and the whole string is the NAME.\n\nDirectory components with an empty name or the name \\\".\\\" are removed.\nAny directory named \\\"..\\\" is read as DOT-DOT,\nwhich must be one of :BACK or :UP and defaults to :BACK.\n\nHOST, DEVICE and VERSION components are taken from DEFAULTS,\nwhich itself defaults to *NIL-PATHNAME*, also used if DEFAULTS is NIL.\nNo host or device can be specified in the string itself,\nwhich makes it unsuitable for absolute pathnames outside Unix.\n\nFor relative pathnames, these components (and hence the defaults) won't matter\nif you use MERGE-PATHNAMES* but will matter if you use MERGE-PATHNAMES,\nwhich is an important reason to always use MERGE-PATHNAMES*.\n\nArbitrary keys are accepted, and the parse result is passed to ENSURE-PATHNAME\nwith those keys, removing TYPE DEFAULTS and DOT-DOT.\nWhen you're manipulating pathnames that are supposed to make sense portably\neven though the OS may not be Unixish, we recommend you use :WANT-RELATIVE T\nto throw an error if the pathname is absolute\"\n    (block nil\n      (check-type type (or null string (eql :directory)))\n      (when ensure-directory\n        (setf type :directory))\n      (etypecase name\n        ((or null pathname) (return name))\n        (symbol\n         (setf name (string-downcase name)))\n        (string))\n      (multiple-value-bind (relative path filename file-only)\n          (split-unix-namestring-directory-components\n           name :dot-dot dot-dot :ensure-directory (eq type :directory))\n        (multiple-value-bind (name type)\n            (cond\n              ((or (eq type :directory) (null filename))\n               (values nil nil))\n              (type\n               (values filename type))\n              (t\n               (split-name-type filename)))\n            (let* ((directory\n                    (unless file-only (cons relative path)))\n                   (pathname\n                    #-abcl\n                    (make-pathname\n                     :directory directory\n                     :name name :type type\n                     :defaults (or #-mcl defaults *nil-pathname*))\n                    #+abcl\n                    (if (and defaults\n                             (ext:pathname-jar-p defaults)\n                             (null directory))\n                        ;; When DEFAULTS is a jar, it will have the directory we want\n                        (make-pathname :name name :type type\n                                       :defaults (or defaults *nil-pathname*))\n                        (make-pathname :name name :type type\n                                       :defaults (or defaults *nil-pathname*)\n                                       :directory directory))))\n              (apply 'ensure-pathname\n                     pathname\n                     (remove-plist-keys '(:type :dot-dot :defaults) keys)))))))\n\n  (defun unix-namestring (pathname)\n    \"Given a non-wild PATHNAME, return a Unix-style namestring for it.\nIf the PATHNAME is NIL or a STRING, return it unchanged.\n\nThis only considers the DIRECTORY, NAME and TYPE components of the pathname.\nThis is a portable solution for representing relative pathnames,\nBut unless you are running on a Unix system, it is not a general solution\nto representing native pathnames.\n\nAn error is signaled if the argument is not NULL, a STRING or a PATHNAME,\nor if it is a PATHNAME but some of its components are not recognized.\"\n    (etypecase pathname\n      ((or null string) pathname)\n      (pathname\n       (with-output-to-string (s)\n         (flet ((err () (parameter-error \"~S: invalid unix-namestring ~S\"\n                                         'unix-namestring pathname)))\n           (let* ((dir (normalize-pathname-directory-component (pathname-directory pathname)))\n                  (name (pathname-name pathname))\n                  (name (and (not (eq name :unspecific)) name))\n                  (type (pathname-type pathname))\n                  (type (and (not (eq type :unspecific)) type)))\n             (cond\n               ((member dir '(nil :unspecific)))\n               ((eq dir '(:relative)) (princ \"./\" s))\n               ((consp dir)\n                (destructuring-bind (relabs &rest dirs) dir\n                  (or (member relabs '(:relative :absolute)) (err))\n                  (when (eq relabs :absolute) (princ #\\/ s))\n                  (loop :for x :in dirs :do\n                    (cond\n                      ((member x '(:back :up)) (princ \"../\" s))\n                      ((equal x \"\") (err))\n                      ;;((member x '(\".\" \"..\") :test 'equal) (err))\n                      ((stringp x) (format s \"~A/\" x))\n                      (t (err))))))\n               (t (err)))\n             (cond\n               (name\n                (unless (and (stringp name) (or (null type) (stringp type))) (err))\n                (format s \"~A~@[.~A~]\" name type))\n               (t\n                (or (null type) (err)))))))))))\n\n;;; Absolute and relative pathnames\n(with-upgradability ()\n  (defun subpathname (pathname subpath &key type)\n    \"This function takes a PATHNAME and a SUBPATH and a TYPE.\nIf SUBPATH is already a PATHNAME object (not namestring),\nand is an absolute pathname at that, it is returned unchanged;\notherwise, SUBPATH is turned into a relative pathname with given TYPE\nas per PARSE-UNIX-NAMESTRING with :WANT-RELATIVE T :TYPE TYPE,\nthen it is merged with the PATHNAME-DIRECTORY-PATHNAME of PATHNAME.\"\n    (or (and (pathnamep subpath) (absolute-pathname-p subpath))\n        (merge-pathnames* (parse-unix-namestring subpath :type type :want-relative t)\n                          (pathname-directory-pathname pathname))))\n\n  (defun subpathname* (pathname subpath &key type)\n    \"returns NIL if the base pathname is NIL, otherwise like SUBPATHNAME.\"\n    (and pathname\n         (subpathname (ensure-directory-pathname pathname) subpath :type type)))\n\n  (defun pathname-root (pathname)\n    \"return the root directory for the host and device of given PATHNAME\"\n    (make-pathname :directory '(:absolute)\n                   :name nil :type nil :version nil\n                   :defaults pathname ;; host device, and on scl, *some*\n                   ;; scheme-specific parts: port username password, not others:\n                   . #.(or #+scl '(:parameters nil :query nil :fragment nil))))\n\n  (defun pathname-host-pathname (pathname)\n    \"return a pathname with the same host as given PATHNAME, and all other fields NIL\"\n    (make-pathname :directory nil\n                   :name nil :type nil :version nil :device nil\n                   :defaults pathname ;; host device, and on scl, *some*\n                   ;; scheme-specific parts: port username password, not others:\n                   . #.(or #+scl '(:parameters nil :query nil :fragment nil))))\n\n  (defun ensure-absolute-pathname (path &optional defaults (on-error 'error))\n    \"Given a pathname designator PATH, return an absolute pathname as specified by PATH\nconsidering the DEFAULTS, or, if not possible, use CALL-FUNCTION on the specified ON-ERROR behavior,\nwith a format control-string and other arguments as arguments\"\n    (cond\n      ((absolute-pathname-p path))\n      ((stringp path) (ensure-absolute-pathname (pathname path) defaults on-error))\n      ((not (pathnamep path)) (call-function on-error \"not a valid pathname designator ~S\" path))\n      ((let ((default-pathname (if (pathnamep defaults) defaults (call-function defaults))))\n         (or (if (absolute-pathname-p default-pathname)\n                 (absolute-pathname-p (merge-pathnames* path default-pathname))\n                 (call-function on-error \"Default pathname ~S is not an absolute pathname\"\n                                default-pathname))\n             (call-function on-error \"Failed to merge ~S with ~S into an absolute pathname\"\n                            path default-pathname))))\n      (t (call-function on-error\n                        \"Cannot ensure ~S is evaluated as an absolute pathname with defaults ~S\"\n                        path defaults))))\n\n  (defun subpathp (maybe-subpath base-pathname)\n    \"if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that\nwhen used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH.\"\n    (and (pathnamep maybe-subpath) (pathnamep base-pathname)\n         (absolute-pathname-p maybe-subpath) (absolute-pathname-p base-pathname)\n         (directory-pathname-p base-pathname) (not (wild-pathname-p base-pathname))\n         (pathname-equal (pathname-root maybe-subpath) (pathname-root base-pathname))\n         (with-pathname-defaults (*nil-pathname*)\n           (let ((enough (enough-namestring maybe-subpath base-pathname)))\n             (and (relative-pathname-p enough) (pathname enough))))))\n\n  (defun enough-pathname (maybe-subpath base-pathname)\n    \"if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that\nwhen used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH.\"\n    (let ((sub (when maybe-subpath (pathname maybe-subpath)))\n          (base (when base-pathname (ensure-absolute-pathname (pathname base-pathname)))))\n      (or (and base (subpathp sub base)) sub)))\n\n  (defun call-with-enough-pathname (maybe-subpath defaults-pathname thunk)\n    \"In a context where *DEFAULT-PATHNAME-DEFAULTS* is bound to DEFAULTS-PATHNAME (if not null,\nor else to its current value), call THUNK with ENOUGH-PATHNAME for MAYBE-SUBPATH\ngiven DEFAULTS-PATHNAME as a base pathname.\"\n    (let ((enough (enough-pathname maybe-subpath defaults-pathname))\n          (*default-pathname-defaults* (or defaults-pathname *default-pathname-defaults*)))\n      (funcall thunk enough)))\n\n  (defmacro with-enough-pathname ((pathname-var &key (pathname pathname-var)\n                                                  (defaults *default-pathname-defaults*))\n                                  &body body)\n    \"Shorthand syntax for CALL-WITH-ENOUGH-PATHNAME\"\n    `(call-with-enough-pathname ,pathname ,defaults #'(lambda (,pathname-var) ,@body))))\n\n\n;;; Wildcard pathnames\n(with-upgradability ()\n  (defparameter *wild* (or #+cormanlisp \"*\" :wild)\n    \"Wild component for use with MAKE-PATHNAME\")\n  (defparameter *wild-directory-component* (or :wild)\n    \"Wild directory component for use with MAKE-PATHNAME\")\n  (defparameter *wild-inferiors-component* (or :wild-inferiors)\n    \"Wild-inferiors directory component for use with MAKE-PATHNAME\")\n  (defparameter *wild-file*\n    (make-pathname :directory nil :name *wild* :type *wild*\n                   :version (or #-(or allegro abcl xcl) *wild*))\n    \"A pathname object with wildcards for matching any file with TRANSLATE-PATHNAME\")\n  (defparameter *wild-file-for-directory*\n    (make-pathname :directory nil :name *wild* :type (or #-(or clisp gcl) *wild*)\n                   :version (or #-(or allegro abcl clisp gcl xcl) *wild*))\n    \"A pathname object with wildcards for matching any file with DIRECTORY\")\n  (defparameter *wild-directory*\n    (make-pathname :directory `(:relative ,*wild-directory-component*)\n                   :name nil :type nil :version nil)\n    \"A pathname object with wildcards for matching any subdirectory\")\n  (defparameter *wild-inferiors*\n    (make-pathname :directory `(:relative ,*wild-inferiors-component*)\n                   :name nil :type nil :version nil)\n    \"A pathname object with wildcards for matching any recursive subdirectory\")\n  (defparameter *wild-path*\n    (merge-pathnames* *wild-file* *wild-inferiors*)\n    \"A pathname object with wildcards for matching any file in any recursive subdirectory\")\n\n  (defun wilden (path)\n    \"From a pathname, return a wildcard pathname matching any file in any subdirectory of given pathname's directory\"\n    (merge-pathnames* *wild-path* path)))\n\n\n;;; Translate a pathname\n(with-upgradability ()\n  (defun relativize-directory-component (directory-component)\n    \"Given the DIRECTORY-COMPONENT of a pathname, return an otherwise similar relative directory component\"\n    (let ((directory (normalize-pathname-directory-component directory-component)))\n      (cond\n        ((stringp directory)\n         (list :relative directory))\n        ((eq (car directory) :absolute)\n         (cons :relative (cdr directory)))\n        (t\n         directory))))\n\n  (defun relativize-pathname-directory (pathspec)\n    \"Given a PATHNAME, return a relative pathname with otherwise the same components\"\n    (let ((p (pathname pathspec)))\n      (make-pathname\n       :directory (relativize-directory-component (pathname-directory p))\n       :defaults p)))\n\n  (defun directory-separator-for-host (&optional (pathname *default-pathname-defaults*))\n    \"Given a PATHNAME, return the character used to delimit directory names on this host and device.\"\n    (let ((foo (make-pathname :directory '(:absolute \"FOO\") :defaults pathname)))\n      (last-char (namestring foo))))\n\n  #-scl\n  (defun directorize-pathname-host-device (pathname)\n    \"Given a PATHNAME, return a pathname that has representations of its HOST and DEVICE components\nadded to its DIRECTORY component. This is useful for output translations.\"\n    (os-cond\n     ((os-unix-p)\n      (when (physical-pathname-p pathname)\n        (return-from directorize-pathname-host-device pathname))))\n    (let* ((root (pathname-root pathname))\n           (wild-root (wilden root))\n           (absolute-pathname (merge-pathnames* pathname root))\n           (separator (directory-separator-for-host root))\n           (root-namestring (namestring root))\n           (root-string\n             (substitute-if #\\/\n                            #'(lambda (x) (or (eql x #\\:)\n                                              (eql x separator)))\n                            root-namestring)))\n      (multiple-value-bind (relative path filename)\n          (split-unix-namestring-directory-components root-string :ensure-directory t)\n        (declare (ignore relative filename))\n        (let ((new-base (make-pathname :defaults root :directory `(:absolute ,@path))))\n          (translate-pathname absolute-pathname wild-root (wilden new-base))))))\n\n  #+scl\n  (defun directorize-pathname-host-device (pathname)\n    (let ((scheme (ext:pathname-scheme pathname))\n          (host (pathname-host pathname))\n          (port (ext:pathname-port pathname))\n          (directory (pathname-directory pathname)))\n      (flet ((specificp (x) (and x (not (eq x :unspecific)))))\n        (if (or (specificp port)\n                (and (specificp host) (plusp (length host)))\n                (specificp scheme))\n            (let ((prefix \"\"))\n              (when (specificp port)\n                (setf prefix (format nil \":~D\" port)))\n              (when (and (specificp host) (plusp (length host)))\n                (setf prefix (strcat host prefix)))\n              (setf prefix (strcat \":\" prefix))\n              (when (specificp scheme)\n                (setf prefix (strcat scheme prefix)))\n              (assert (and directory (eq (first directory) :absolute)))\n              (make-pathname :directory `(:absolute ,prefix ,@(rest directory))\n                             :defaults pathname)))\n        pathname)))\n\n  (defun translate-pathname* (path absolute-source destination &optional root source)\n    \"A wrapper around TRANSLATE-PATHNAME to be used by the ASDF output-translations facility.\nPATH is the pathname to be translated.\nABSOLUTE-SOURCE is an absolute pathname to use as source for translate-pathname,\nDESTINATION is either a function, to be called with PATH and ABSOLUTE-SOURCE,\nor a relative pathname, to be merged with ROOT and used as destination for translate-pathname\nor an absolute pathname, to be used as destination for translate-pathname.\nIn that last case, if ROOT is non-NIL, PATH is first transformated by DIRECTORIZE-PATHNAME-HOST-DEVICE.\"\n    (declare (ignore source))\n    (cond\n      ((functionp destination)\n       (funcall destination path absolute-source))\n      ((eq destination t)\n       path)\n      ((not (pathnamep destination))\n       (parameter-error \"~S: Invalid destination\" 'translate-pathname*))\n      ((not (absolute-pathname-p destination))\n       (translate-pathname path absolute-source (merge-pathnames* destination root)))\n      (root\n       (translate-pathname (directorize-pathname-host-device path) absolute-source destination))\n      (t\n       (translate-pathname path absolute-source destination))))\n\n  (defvar *output-translation-function* 'identity\n    \"Hook for output translations.\n\nThis function needs to be idempotent, so that actions can work\nwhether their inputs were translated or not,\nwhich they will be if we are composing operations. e.g. if some\ncreate-lisp-op creates a lisp file from some higher-level input,\nyou need to still be able to use compile-op on that lisp file.\"))\n;;;; -------------------------------------------------------------------------\n;;;; Portability layer around Common Lisp filesystem access\n\n(uiop/package:define-package :uiop/filesystem\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os :uiop/pathname)\n  (:export\n   ;; Native namestrings\n   #:native-namestring #:parse-native-namestring\n   ;; Probing the filesystem\n   #:truename* #:safe-file-write-date #:probe-file* #:directory-exists-p #:file-exists-p\n   #:directory* #:filter-logical-directory-results #:directory-files #:subdirectories\n   #:collect-sub*directories\n   ;; Resolving symlinks somewhat\n   #:truenamize #:resolve-symlinks #:*resolve-symlinks* #:resolve-symlinks*\n   ;; merging with cwd\n   #:get-pathname-defaults #:call-with-current-directory #:with-current-directory\n   ;; Environment pathnames\n   #:inter-directory-separator #:split-native-pathnames-string\n   #:getenv-pathname #:getenv-pathnames\n   #:getenv-absolute-directory #:getenv-absolute-directories\n   #:lisp-implementation-directory #:lisp-implementation-pathname-p\n   ;; Simple filesystem operations\n   #:ensure-all-directories-exist\n   #:rename-file-overwriting-target\n   #:delete-file-if-exists #:delete-empty-directory #:delete-directory-tree))\n(in-package :uiop/filesystem)\n\n;;; Native namestrings, as seen by the operating system calls rather than Lisp\n(with-upgradability ()\n  (defun native-namestring (x)\n    \"From a non-wildcard CL pathname, a return namestring suitable for passing to the operating system\"\n    (when x\n      (let ((p (pathname x)))\n        #+clozure (with-pathname-defaults () (ccl:native-translated-namestring p)) ; see ccl bug 978\n        #+(or cmucl scl) (ext:unix-namestring p nil)\n        #+sbcl (sb-ext:native-namestring p)\n        #-(or clozure cmucl sbcl scl)\n        (os-cond\n         ((os-unix-p) (unix-namestring p))\n         (t (namestring p))))))\n\n  (defun parse-native-namestring (string &rest constraints &key ensure-directory &allow-other-keys)\n    \"From a native namestring suitable for use by the operating system, return\na CL pathname satisfying all the specified constraints as per ENSURE-PATHNAME\"\n    (check-type string (or string null))\n    (let* ((pathname\n             (when string\n               (with-pathname-defaults ()\n                 #+clozure (ccl:native-to-pathname string)\n                 #+cmucl (uiop/os::parse-unix-namestring* string)\n                 #+sbcl (sb-ext:parse-native-namestring string)\n                 #+scl (lisp::parse-unix-namestring string)\n                 #-(or clozure cmucl sbcl scl)\n                 (os-cond\n                  ((os-unix-p) (parse-unix-namestring string :ensure-directory ensure-directory))\n                  (t (parse-namestring string))))))\n           (pathname\n             (if ensure-directory\n                 (and pathname (ensure-directory-pathname pathname))\n                 pathname)))\n      (apply 'ensure-pathname pathname constraints))))\n\n\n;;; Probing the filesystem\n(with-upgradability ()\n  (defun truename* (p)\n    \"Nicer variant of TRUENAME that plays well with NIL, avoids logical pathname contexts, and tries both files and directories\"\n    (when p\n      (when (stringp p) (setf p (with-pathname-defaults () (parse-namestring p))))\n      (values\n       (or (ignore-errors (truename p))\n           ;; this is here because trying to find the truename of a directory pathname WITHOUT supplying\n           ;; a trailing directory separator, causes an error on some lisps.\n           #+(or clisp gcl) (if-let (d (ensure-directory-pathname p nil)) (ignore-errors (truename d)))\n           ;; On Genera, truename of a directory pathname will probably fail as Genera\n           ;; will merge in a filename/type/version from *default-pathname-defaults* and\n           ;; will try to get the truename of a file that probably doesn't exist.\n           #+genera (when (directory-pathname-p p)\n                      (let ((d (scl:send p :directory-pathname-as-file)))\n                        (ensure-directory-pathname (ignore-errors (truename d)) nil)))))))\n\n  (defun safe-file-write-date (pathname)\n    \"Safe variant of FILE-WRITE-DATE that may return NIL rather than raise an error.\"\n    ;; If FILE-WRITE-DATE returns NIL, it's possible that\n    ;; the user or some other agent has deleted an input file.\n    ;; Also, generated files will not exist at the time planning is done\n    ;; and calls compute-action-stamp which calls safe-file-write-date.\n    ;; So it is very possible that we can't get a valid file-write-date,\n    ;; and we can survive and we will continue the planning\n    ;; as if the file were very old.\n    ;; (or should we treat the case in a different, special way?)\n    (and pathname\n         (handler-case (file-write-date (physicalize-pathname pathname))\n           (file-error () nil))))\n\n  (defun probe-file* (p &key truename)\n    \"when given a pathname P (designated by a string as per PARSE-NAMESTRING),\nprobes the filesystem for a file or directory with given pathname.\nIf it exists, return its truename if TRUENAME is true,\nor the original (parsed) pathname if it is false (the default).\"\n    (values\n     (ignore-errors\n      (setf p (funcall 'ensure-pathname p\n                       :namestring :lisp\n                       :ensure-physical t\n                       :ensure-absolute t :defaults 'get-pathname-defaults\n                       :want-non-wild t\n                       :on-error nil))\n      (when p\n        #+allegro\n        (probe-file p :follow-symlinks truename)\n        #+gcl\n        (if truename\n            (truename* p)\n            (let ((kind (car (si::stat p))))\n              (when (eq kind :link)\n                (setf kind (ignore-errors (car (si::stat (truename* p))))))\n              (ecase kind\n                ((nil) nil)\n                ((:file :link)\n                 (cond\n                   ((file-pathname-p p) p)\n                   ((directory-pathname-p p)\n                    (subpathname p (car (last (pathname-directory p)))))))\n                (:directory (ensure-directory-pathname p)))))\n        #+clisp\n        #.(let* ((fs (or #-os-windows (find-symbol* '#:file-stat :posix nil)))\n                 (pp (find-symbol* '#:probe-pathname :ext nil)))\n            `(if truename\n                 ,(if pp\n                      `(values (,pp p))\n                      '(or (truename* p)\n                        (truename* (ignore-errors (ensure-directory-pathname p)))))\n                 ,(cond\n                    (fs `(and (,fs p) p))\n                    (pp `(nth-value 1 (,pp p)))\n                    (t '(or (and (truename* p) p)\n                         (if-let (d (ensure-directory-pathname p))\n                          (and (truename* d) d)))))))\n        #-(or allegro clisp gcl)\n        (if truename\n            (probe-file p)\n            (and\n             #+(or cmucl scl) (unix:unix-stat (ext:unix-namestring p))\n             #+(and lispworks os-unix) (system:get-file-stat p)\n             #+sbcl (sb-unix:unix-stat (sb-ext:native-namestring p))\n             #-(or cmucl (and lispworks os-unix) sbcl scl) (file-write-date p)\n             p))))))\n\n  (defun directory-exists-p (x)\n    \"Is X the name of a directory that exists on the filesystem?\"\n    #+allegro\n    (excl:probe-directory x)\n    #+clisp\n    (handler-case (ext:probe-directory x)\n           (sys::simple-file-error ()\n             nil))\n    #-(or allegro clisp)\n    (let ((p (probe-file* x :truename t)))\n      (and (directory-pathname-p p) p)))\n\n  (defun file-exists-p (x)\n    \"Is X the name of a file that exists on the filesystem?\"\n    (let ((p (probe-file* x :truename t)))\n      (and (file-pathname-p p) p)))\n\n  (defun directory* (pathname-spec &rest keys &key &allow-other-keys)\n    \"Return a list of the entries in a directory by calling DIRECTORY.\nTry to override the defaults to not resolving symlinks, if implementation allows.\"\n    (apply 'directory pathname-spec\n           (append keys '#.(or #+allegro '(:directories-are-files nil :follow-symbolic-links nil)\n                               #+(or clozure digitool) '(:follow-links nil)\n                               #+clisp '(:circle t :if-does-not-exist :ignore)\n                               #+(or cmucl scl) '(:follow-links nil :truenamep nil)\n                               #+lispworks '(:link-transparency nil)\n                               #+sbcl (when (find-symbol* :resolve-symlinks '#:sb-impl nil)\n                                        '(:resolve-symlinks nil))))))\n\n  (defun filter-logical-directory-results (directory entries merger)\n    \"If DIRECTORY isn't a logical pathname, return ENTRIES. If it is,\ngiven ENTRIES in the DIRECTORY, remove the entries which are physical yet\nwhen transformed by MERGER have a different TRUENAME.\nAlso remove duplicates as may appear with some translation rules.\nThis function is used as a helper to DIRECTORY-FILES to avoid invalid entries\nwhen using logical-pathnames.\"\n    (if (logical-pathname-p directory)\n        (remove-duplicates ;; on CLISP, querying ~/ will return duplicates\n         ;; Try hard to not resolve logical-pathname into physical pathnames;\n         ;; otherwise logical-pathname users/lovers will be disappointed.\n         ;; If directory* could use some implementation-dependent magic,\n         ;; we will have logical pathnames already; otherwise,\n         ;; we only keep pathnames for which specifying the name and\n         ;; translating the LPN commute.\n         (loop :for f :in entries\n               :for p = (or (and (logical-pathname-p f) f)\n                            (let* ((u (ignore-errors (call-function merger f))))\n                              ;; The first u avoids a cumbersome (truename u) error.\n                              ;; At this point f should already be a truename,\n                              ;; but isn't quite in CLISP, for it doesn't have :version :newest\n                              (and u (equal (truename* u) (truename* f)) u)))\n           :when p :collect p)\n         :test 'pathname-equal)\n        entries))\n\n  (defun directory-files (directory &optional (pattern *wild-file-for-directory*))\n    \"Return a list of the files in a directory according to the PATTERN.\nSubdirectories should NOT be returned.\n  PATTERN defaults to a pattern carefully chosen based on the implementation;\noverride the default at your own risk.\n  DIRECTORY-FILES tries NOT to resolve symlinks if the implementation permits this,\nbut the behavior in presence of symlinks is not portable. Use IOlib to handle such situations.\"\n    (let ((dir (ensure-directory-pathname directory)))\n      (when (logical-pathname-p dir)\n        ;; Because of the filtering we do below,\n        ;; logical pathnames have restrictions on wild patterns.\n        ;; Not that the results are very portable when you use these patterns on physical pathnames.\n        (when (wild-pathname-p dir)\n          (parameter-error \"~S: Invalid wild pattern in logical directory ~S\"\n                           'directory-files directory))\n        (unless (member (pathname-directory pattern) '(() (:relative)) :test 'equal)\n          (parameter-error \"~S: Invalid file pattern ~S for logical directory ~S\" 'directory-files pattern directory))\n        (setf pattern (make-pathname-logical pattern (pathname-host dir))))\n      (let* ((pat (merge-pathnames* pattern dir))\n             (entries (ignore-errors (directory* pat))))\n        (remove-if 'directory-pathname-p\n                   (filter-logical-directory-results\n                    directory entries\n                    #'(lambda (f)\n                        (make-pathname :defaults dir\n                                       :name (make-pathname-component-logical (pathname-name f))\n                                       :type (make-pathname-component-logical (pathname-type f))\n                                       :version (make-pathname-component-logical (pathname-version f)))))))))\n\n  (defun subdirectories (directory)\n    \"Given a DIRECTORY pathname designator, return a list of the subdirectories under it.\nThe behavior in presence of symlinks is not portable. Use IOlib to handle such situations.\"\n    (let* ((directory (ensure-directory-pathname directory))\n           #-(or abcl cormanlisp genera xcl)\n           (wild (merge-pathnames*\n                  #-(or abcl allegro cmucl lispworks sbcl scl xcl)\n                  *wild-directory*\n                  #+(or abcl allegro cmucl lispworks sbcl scl xcl) \"*.*\"\n                  directory))\n           (dirs\n             #-(or abcl cormanlisp genera xcl)\n             (ignore-errors\n              (directory* wild . #.(or #+clozure '(:directories t :files nil)\n                                       #+mcl '(:directories t))))\n             #+(or abcl xcl) (system:list-directory directory)\n             #+cormanlisp (cl::directory-subdirs directory)\n             #+genera (handler-case (fs:directory-list directory) (fs:directory-not-found () nil)))\n           #+(or abcl allegro cmucl genera lispworks sbcl scl xcl)\n           (dirs (loop :for x :in dirs\n                       :for d = #+(or abcl xcl) (extensions:probe-directory x)\n                       #+allegro (excl:probe-directory x)\n                       #+(or cmucl sbcl scl) (directory-pathname-p x)\n                       #+genera (getf (cdr x) :directory)\n                       #+lispworks (lw:file-directory-p x)\n                       :when d :collect #+(or abcl allegro xcl) (ensure-directory-pathname d)\n                         #+genera (ensure-directory-pathname (first x))\n                       #+(or cmucl lispworks sbcl scl) x)))\n      (filter-logical-directory-results\n       directory dirs\n       (let ((prefix (or (normalize-pathname-directory-component (pathname-directory directory))\n                         '(:absolute)))) ; because allegro returns NIL for #p\"FOO:\"\n         #'(lambda (d)\n             (let ((dir (normalize-pathname-directory-component (pathname-directory d))))\n               (and (consp dir) (consp (cdr dir))\n                    (make-pathname\n                     :defaults directory :name nil :type nil :version nil\n                     :directory (append prefix (make-pathname-component-logical (last dir)))))))))))\n\n  (defun collect-sub*directories (directory collectp recursep collector)\n    \"Given a DIRECTORY, when COLLECTP returns true when CALL-FUNCTION'ed with the directory,\ncall-function the COLLECTOR function designator on the directory,\nand recurse each of its subdirectories on which the RECURSEP returns true when CALL-FUNCTION'ed with them.\nThis function will thus let you traverse a filesystem hierarchy,\nsuperseding the functionality of CL-FAD:WALK-DIRECTORY.\nThe behavior in presence of symlinks is not portable. Use IOlib to handle such situations.\"\n    (when (call-function collectp directory)\n      (call-function collector directory)\n      (dolist (subdir (subdirectories directory))\n        (when (call-function recursep subdir)\n          (collect-sub*directories subdir collectp recursep collector))))))\n\n;;; Resolving symlinks somewhat\n(with-upgradability ()\n  (defun truenamize (pathname)\n    \"Resolve as much of a pathname as possible\"\n    (block nil\n      (when (typep pathname '(or null logical-pathname)) (return pathname))\n      (let ((p pathname))\n        (unless (absolute-pathname-p p)\n          (setf p (or (absolute-pathname-p (ensure-absolute-pathname p 'get-pathname-defaults nil))\n                      (return p))))\n        (when (logical-pathname-p p) (return p))\n        (let ((found (probe-file* p :truename t)))\n          (when found (return found)))\n        (let* ((directory (normalize-pathname-directory-component (pathname-directory p)))\n               (up-components (reverse (rest directory)))\n               (down-components ()))\n          (assert (eq :absolute (first directory)))\n          (loop :while up-components :do\n            (if-let (parent\n                     (ignore-errors\n                      (probe-file* (make-pathname :directory `(:absolute ,@(reverse up-components))\n                                                  :name nil :type nil :version nil :defaults p))))\n              (if-let (simplified\n                       (ignore-errors\n                        (merge-pathnames*\n                         (make-pathname :directory `(:relative ,@down-components)\n                                        :defaults p)\n                         (ensure-directory-pathname parent))))\n                (return simplified)))\n            (push (pop up-components) down-components)\n            :finally (return p))))))\n\n  (defun resolve-symlinks (path)\n    \"Do a best effort at resolving symlinks in PATH, returning a partially or totally resolved PATH.\"\n    #-allegro (truenamize path)\n    #+allegro\n    (if (physical-pathname-p path)\n        (or (ignore-errors (excl:pathname-resolve-symbolic-links path)) path)\n        path))\n\n  (defvar *resolve-symlinks* t\n    \"Determine whether or not ASDF resolves symlinks when defining systems.\nDefaults to T.\")\n\n  (defun resolve-symlinks* (path)\n    \"RESOLVE-SYMLINKS in PATH iff *RESOLVE-SYMLINKS* is T (the default).\"\n    (if *resolve-symlinks*\n        (and path (resolve-symlinks path))\n        path)))\n\n\n;;; Check pathname constraints\n(with-upgradability ()\n  (defun ensure-pathname\n      (pathname &key\n                  on-error\n                  defaults type dot-dot namestring\n                  empty-is-nil\n                  want-pathname\n                  want-logical want-physical ensure-physical\n                  want-relative want-absolute ensure-absolute ensure-subpath\n                  want-non-wild want-wild wilden\n                  want-file want-directory ensure-directory\n                  want-existing ensure-directories-exist\n                  truename resolve-symlinks truenamize\n       &aux (p pathname)) ;; mutable working copy, preserve original\n    \"Coerces its argument into a PATHNAME,\noptionally doing some transformations and checking specified constraints.\n\nIf the argument is NIL, then NIL is returned unless the WANT-PATHNAME constraint is specified.\n\nIf the argument is a STRING, it is first converted to a pathname via\nPARSE-UNIX-NAMESTRING, PARSE-NAMESTRING or PARSE-NATIVE-NAMESTRING respectively\ndepending on the NAMESTRING argument being :UNIX, :LISP or :NATIVE respectively,\nor else by using CALL-FUNCTION on the NAMESTRING argument;\nif :UNIX is specified (or NIL, the default, which specifies the same thing),\nthen PARSE-UNIX-NAMESTRING it is called with the keywords\nDEFAULTS TYPE DOT-DOT ENSURE-DIRECTORY WANT-RELATIVE, and\nthe result is optionally merged into the DEFAULTS if ENSURE-ABSOLUTE is true.\n\nThe pathname passed or resulting from parsing the string\nis then subjected to all the checks and transformations below are run.\n\nEach non-nil constraint argument can be one of the symbols T, ERROR, CERROR or IGNORE.\nThe boolean T is an alias for ERROR.\nERROR means that an error will be raised if the constraint is not satisfied.\nCERROR means that an continuable error will be raised if the constraint is not satisfied.\nIGNORE means just return NIL instead of the pathname.\n\nThe ON-ERROR argument, if not NIL, is a function designator (as per CALL-FUNCTION)\nthat will be called with the the following arguments:\na generic format string for ensure pathname, the pathname,\nthe keyword argument corresponding to the failed check or transformation,\na format string for the reason ENSURE-PATHNAME failed,\nand a list with arguments to that format string.\nIf ON-ERROR is NIL, ERROR is used instead, which does the right thing.\nYou could also pass (CERROR \\\"CONTINUE DESPITE FAILED CHECK\\\").\n\nThe transformations and constraint checks are done in this order,\nwhich is also the order in the lambda-list:\n\nEMPTY-IS-NIL returns NIL if the argument is an empty string.\nWANT-PATHNAME checks that pathname (after parsing if needed) is not null.\nOtherwise, if the pathname is NIL, ensure-pathname returns NIL.\nWANT-LOGICAL checks that pathname is a LOGICAL-PATHNAME\nWANT-PHYSICAL checks that pathname is not a LOGICAL-PATHNAME\nENSURE-PHYSICAL ensures that pathname is physical via TRANSLATE-LOGICAL-PATHNAME\nWANT-RELATIVE checks that pathname has a relative directory component\nWANT-ABSOLUTE checks that pathname does have an absolute directory component\nENSURE-ABSOLUTE merges with the DEFAULTS, then checks again\nthat the result absolute is an absolute pathname indeed.\nENSURE-SUBPATH checks that the pathname is a subpath of the DEFAULTS.\nWANT-FILE checks that pathname has a non-nil FILE component\nWANT-DIRECTORY checks that pathname has nil FILE and TYPE components\nENSURE-DIRECTORY uses ENSURE-DIRECTORY-PATHNAME to interpret\nany file and type components as being actually a last directory component.\nWANT-NON-WILD checks that pathname is not a wild pathname\nWANT-WILD checks that pathname is a wild pathname\nWILDEN merges the pathname with **/*.*.* if it is not wild\nWANT-EXISTING checks that a file (or directory) exists with that pathname.\nENSURE-DIRECTORIES-EXIST creates any parent directory with ENSURE-DIRECTORIES-EXIST.\nTRUENAME replaces the pathname by its truename, or errors if not possible.\nRESOLVE-SYMLINKS replaces the pathname by a variant with symlinks resolved by RESOLVE-SYMLINKS.\nTRUENAMIZE uses TRUENAMIZE to resolve as many symlinks as possible.\"\n    (block nil\n      (flet ((report-error (keyword description &rest arguments)\n               (call-function (or on-error 'error)\n                              \"Invalid pathname ~S: ~*~?\"\n                              pathname keyword description arguments)))\n        (macrolet ((err (constraint &rest arguments)\n                     `(report-error ',(intern* constraint :keyword) ,@arguments))\n                   (check (constraint condition &rest arguments)\n                     `(when ,constraint\n                        (unless ,condition (err ,constraint ,@arguments))))\n                   (transform (transform condition expr)\n                     `(when ,transform\n                        (,@(if condition `(when ,condition) '(progn))\n                         (setf p ,expr)))))\n          (etypecase p\n            ((or null pathname))\n            (string\n             (when (and (emptyp p) empty-is-nil)\n               (return-from ensure-pathname nil))\n             (setf p (case namestring\n                       ((:unix nil)\n                        (parse-unix-namestring\n                         p :defaults defaults :type type :dot-dot dot-dot\n                           :ensure-directory ensure-directory :want-relative want-relative))\n                       ((:native)\n                        (parse-native-namestring p))\n                       ((:lisp)\n                        (parse-namestring p))\n                       (t\n                        (call-function namestring p))))))\n          (etypecase p\n            (pathname)\n            (null\n             (check want-pathname (pathnamep p) \"Expected a pathname, not NIL\")\n             (return nil)))\n          (check want-logical (logical-pathname-p p) \"Expected a logical pathname\")\n          (check want-physical (physical-pathname-p p) \"Expected a physical pathname\")\n          (transform ensure-physical () (physicalize-pathname p))\n          (check ensure-physical (physical-pathname-p p) \"Could not translate to a physical pathname\")\n          (check want-relative (relative-pathname-p p) \"Expected a relative pathname\")\n          (check want-absolute (absolute-pathname-p p) \"Expected an absolute pathname\")\n          (transform ensure-absolute (not (absolute-pathname-p p))\n                     (ensure-absolute-pathname p defaults (list #'report-error :ensure-absolute \"~@?\")))\n          (check ensure-absolute (absolute-pathname-p p)\n                 \"Could not make into an absolute pathname even after merging with ~S\" defaults)\n          (check ensure-subpath (absolute-pathname-p defaults)\n                 \"cannot be checked to be a subpath of non-absolute pathname ~S\" defaults)\n          (check ensure-subpath (subpathp p defaults) \"is not a sub pathname of ~S\" defaults)\n          (check want-file (file-pathname-p p) \"Expected a file pathname\")\n          (check want-directory (directory-pathname-p p) \"Expected a directory pathname\")\n          (transform ensure-directory (not (directory-pathname-p p)) (ensure-directory-pathname p))\n          (check want-non-wild (not (wild-pathname-p p)) \"Expected a non-wildcard pathname\")\n          (check want-wild (wild-pathname-p p) \"Expected a wildcard pathname\")\n          (transform wilden (not (wild-pathname-p p)) (wilden p))\n          (when want-existing\n            (let ((existing (probe-file* p :truename truename)))\n              (if existing\n                  (when truename\n                    (return existing))\n                  (err want-existing \"Expected an existing pathname\"))))\n          (when ensure-directories-exist (ensure-directories-exist p))\n          (when truename\n            (let ((truename (truename* p)))\n              (if truename\n                  (return truename)\n                  (err truename \"Can't get a truename for pathname\"))))\n          (transform resolve-symlinks () (resolve-symlinks p))\n          (transform truenamize () (truenamize p))\n          p)))))\n\n\n;;; Pathname defaults\n(with-upgradability ()\n  (defun get-pathname-defaults (&optional (defaults *default-pathname-defaults*))\n    \"Find the actual DEFAULTS to use for pathnames, including\nresolving them with respect to GETCWD if the DEFAULTS were relative\"\n    (or (absolute-pathname-p defaults)\n        (merge-pathnames* defaults (getcwd))))\n\n  (defun call-with-current-directory (dir thunk)\n    \"call the THUNK in a context where the current directory was changed to DIR, if not NIL.\nNote that this operation is usually NOT thread-safe.\"\n    (if dir\n        (let* ((dir (resolve-symlinks*\n                     (get-pathname-defaults\n                      (ensure-directory-pathname\n                       dir))))\n               (cwd (getcwd))\n               (*default-pathname-defaults* dir))\n          (chdir dir)\n          (unwind-protect\n               (funcall thunk)\n            (chdir cwd)))\n        (funcall thunk)))\n\n  (defmacro with-current-directory ((&optional dir) &body body)\n    \"Call BODY while the POSIX current working directory is set to DIR\"\n    `(call-with-current-directory ,dir #'(lambda () ,@body))))\n\n\n;;; Environment pathnames\n(with-upgradability ()\n  (defun inter-directory-separator ()\n    \"What character does the current OS conventionally uses to separate directories?\"\n    (os-cond ((os-unix-p) #\\:) (t #\\;)))\n\n  (defun split-native-pathnames-string (string &rest constraints &key &allow-other-keys)\n    \"Given a string of pathnames specified in native OS syntax, separate them in a list,\ncheck constraints and normalize each one as per ENSURE-PATHNAME,\nwhere an empty string denotes NIL.\"\n    (loop :for namestring :in (split-string string :separator (string (inter-directory-separator)))\n          :collect (unless (emptyp namestring) (apply 'parse-native-namestring namestring constraints))))\n\n  (defun getenv-pathname (x &rest constraints &key ensure-directory want-directory on-error &allow-other-keys)\n    \"Extract a pathname from a user-configured environment variable, as per native OS,\ncheck constraints and normalize as per ENSURE-PATHNAME.\"\n    ;; For backward compatibility with ASDF 2, want-directory implies ensure-directory\n    (apply 'parse-native-namestring (getenvp x)\n           :ensure-directory (or ensure-directory want-directory)\n           :on-error (or on-error\n                         `(error \"In (~S ~S), invalid pathname ~*~S: ~*~?\" getenv-pathname ,x))\n           constraints))\n  (defun getenv-pathnames (x &rest constraints &key on-error &allow-other-keys)\n    \"Extract a list of pathname from a user-configured environment variable, as per native OS,\ncheck constraints and normalize each one as per ENSURE-PATHNAME.\n       Any empty entries in the environment variable X will be returned as NILs.\"\n    (unless (getf constraints :empty-is-nil t)\n      (parameter-error \"Cannot have EMPTY-IS-NIL false for ~S\" 'getenv-pathnames))\n    (apply 'split-native-pathnames-string (getenvp x)\n           :on-error (or on-error\n                         `(error \"In (~S ~S), invalid pathname ~*~S: ~*~?\" getenv-pathnames ,x))\n           :empty-is-nil t\n           constraints))\n  (defun getenv-absolute-directory (x)\n    \"Extract an absolute directory pathname from a user-configured environment variable,\nas per native OS\"\n    (getenv-pathname x :want-absolute t :ensure-directory t))\n  (defun getenv-absolute-directories (x)\n    \"Extract a list of absolute directories from a user-configured environment variable,\nas per native OS.  Any empty entries in the environment variable X will be returned as\nNILs.\"\n    (getenv-pathnames x :want-absolute t :ensure-directory t))\n\n  (defun lisp-implementation-directory (&key truename)\n    \"Where are the system files of the current installation of the CL implementation?\"\n    (declare (ignorable truename))\n    (let ((dir\n            #+abcl extensions:*lisp-home*\n            #+(or allegro clasp ecl mkcl) #p\"SYS:\"\n            #+clisp custom:*lib-directory*\n            #+clozure #p\"ccl:\"\n            #+cmucl (ignore-errors (pathname-parent-directory-pathname (truename #p\"modules:\")))\n            #+gcl system::*system-directory*\n            #+lispworks lispworks:*lispworks-directory*\n            #+sbcl (if-let (it (find-symbol* :sbcl-homedir-pathname :sb-int nil))\n                     (funcall it)\n                     (getenv-pathname \"SBCL_HOME\" :ensure-directory t))\n            #+scl (ignore-errors (pathname-parent-directory-pathname (truename #p\"file://modules/\")))\n            #+xcl ext:*xcl-home*))\n      (if (and dir truename)\n          (truename* dir)\n          dir)))\n\n  (defun lisp-implementation-pathname-p (pathname)\n    \"Is the PATHNAME under the current installation of the CL implementation?\"\n    ;; Other builtin systems are those under the implementation directory\n    (and (when pathname\n           (if-let (impdir (lisp-implementation-directory))\n             (or (subpathp pathname impdir)\n                 (when *resolve-symlinks*\n                   (if-let (truename (truename* pathname))\n                     (if-let (trueimpdir (truename* impdir))\n                       (subpathp truename trueimpdir)))))))\n         t)))\n\n\n;;; Simple filesystem operations\n(with-upgradability ()\n  (defun ensure-all-directories-exist (pathnames)\n    \"Ensure that for every pathname in PATHNAMES, we ensure its directories exist\"\n    (dolist (pathname pathnames)\n      (when pathname\n        (ensure-directories-exist (physicalize-pathname pathname)))))\n\n  (defun delete-file-if-exists (x)\n    \"Delete a file X if it already exists\"\n    (when x (handler-case (delete-file x) (file-error () nil))))\n\n  (defun rename-file-overwriting-target (source target)\n    \"Rename a file, overwriting any previous file with the TARGET name,\nin an atomic way if the implementation allows.\"\n    (let ((source (ensure-pathname source :namestring :lisp :ensure-physical t :want-file t))\n          (target (ensure-pathname target :namestring :lisp :ensure-physical t :want-file t)))\n      #+clisp ;; in recent enough versions of CLISP, :if-exists :overwrite would make it atomic\n      (progn (funcall 'require \"syscalls\")\n             (symbol-call :posix :copy-file source target :method :rename))\n      #+(and sbcl os-windows) (delete-file-if-exists target) ;; not atomic\n      #-clisp\n      (rename-file source target\n                   #+(or clasp clozure ecl) :if-exists\n                   #+clozure :rename-and-delete #+(or clasp ecl) t)))\n\n  (defun delete-empty-directory (directory-pathname)\n    \"Delete an empty directory\"\n    #+(or abcl digitool gcl) (delete-file directory-pathname)\n    #+allegro (excl:delete-directory directory-pathname)\n    #+clisp (ext:delete-directory directory-pathname)\n    #+clozure (ccl::delete-empty-directory directory-pathname)\n    #+(or cmucl scl) (multiple-value-bind (ok errno)\n                       (unix:unix-rmdir (native-namestring directory-pathname))\n                     (unless ok\n                       #+cmucl (error \"Error number ~A when trying to delete directory ~A\"\n                                    errno directory-pathname)\n                       #+scl (error \"~@<Error deleting ~S: ~A~@:>\"\n                                    directory-pathname (unix:get-unix-error-msg errno))))\n    #+cormanlisp (win32:delete-directory directory-pathname)\n    #+(or clasp ecl) (si:rmdir directory-pathname)\n    #+genera (fs:delete-directory directory-pathname)\n    #+lispworks (lw:delete-directory directory-pathname)\n    #+mkcl (mkcl:rmdir directory-pathname)\n    #+sbcl #.(if-let (dd (find-symbol* :delete-directory :sb-ext nil))\n               `(,dd directory-pathname) ;; requires SBCL 1.0.44 or later\n               `(progn (require :sb-posix) (symbol-call :sb-posix :rmdir directory-pathname)))\n    #+xcl (symbol-call :uiop :run-program `(\"rmdir\" ,(native-namestring directory-pathname)))\n    #-(or abcl allegro clasp clisp clozure cmucl cormanlisp digitool ecl gcl genera lispworks mkcl sbcl scl xcl)\n    (not-implemented-error 'delete-empty-directory \"(on your platform)\")) ; genera\n\n  (defun delete-directory-tree (directory-pathname &key (validate nil validatep) (if-does-not-exist :error))\n    \"Delete a directory including all its recursive contents, aka rm -rf.\n\nTo reduce the risk of infortunate mistakes, DIRECTORY-PATHNAME must be\na physical non-wildcard directory pathname (not namestring).\n\nIf the directory does not exist, the IF-DOES-NOT-EXIST argument specifies what happens:\nif it is :ERROR (the default), an error is signaled, whereas if it is :IGNORE, nothing is done.\n\nFurthermore, before any deletion is attempted, the DIRECTORY-PATHNAME must pass\nthe validation function designated (as per ENSURE-FUNCTION) by the VALIDATE keyword argument\nwhich in practice is thus compulsory, and validates by returning a non-NIL result.\nIf you're suicidal or extremely confident, just use :VALIDATE T.\"\n    (check-type if-does-not-exist (member :error :ignore))\n    (setf directory-pathname (ensure-pathname directory-pathname\n                                              :want-pathname t :want-non-wild t\n                                              :want-physical t :want-directory t))\n    (cond\n      ((not validatep)\n       (parameter-error \"~S was asked to delete ~S but was not provided a validation predicate\"\n              'delete-directory-tree directory-pathname))\n      ((not (call-function validate directory-pathname))\n       (parameter-error \"~S was asked to delete ~S but it is not valid ~@[according to ~S~]\"\n              'delete-directory-tree directory-pathname validate))\n      ((not (directory-exists-p directory-pathname))\n       (ecase if-does-not-exist\n         (:error\n          (error \"~S was asked to delete ~S but the directory does not exist\"\n              'delete-directory-tree directory-pathname))\n         (:ignore nil)))\n      #-(or allegro cmucl clozure genera sbcl scl)\n      ((os-unix-p) ;; On Unix, don't recursively walk the directory and delete everything in Lisp,\n       ;; except on implementations where we can prevent DIRECTORY from following symlinks;\n       ;; instead spawn a standard external program to do the dirty work.\n       (symbol-call :uiop :run-program `(\"rm\" \"-rf\" ,(native-namestring directory-pathname))))\n      (t\n       ;; On supported implementation, call supported system functions\n       #+allegro (symbol-call :excl.osi :delete-directory-and-files\n                              directory-pathname :if-does-not-exist if-does-not-exist)\n       #+clozure (ccl:delete-directory directory-pathname)\n       #+genera (fs:delete-directory directory-pathname :confirm nil)\n       #+sbcl #.(if-let (dd (find-symbol* :delete-directory :sb-ext nil))\n                  `(,dd directory-pathname :recursive t) ;; requires SBCL 1.0.44 or later\n                  '(error \"~S requires SBCL 1.0.44 or later\" 'delete-directory-tree))\n       ;; Outside Unix or on CMUCL and SCL that can avoid following symlinks,\n       ;; do things the hard way.\n       #-(or allegro clozure genera sbcl)\n       (let ((sub*directories\n               (while-collecting (c)\n                 (collect-sub*directories directory-pathname t t #'c))))\n             (dolist (d (nreverse sub*directories))\n               (map () 'delete-file (directory-files d))\n               (delete-empty-directory d)))))))\n;;;; ---------------------------------------------------------------------------\n;;;; Utilities related to streams\n\n(uiop/package:define-package :uiop/stream\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os :uiop/pathname :uiop/filesystem)\n  (:export\n   #:*default-stream-element-type*\n   #:*stdin* #:setup-stdin #:*stdout* #:setup-stdout #:*stderr* #:setup-stderr\n   #:detect-encoding #:*encoding-detection-hook* #:always-default-encoding\n   #:encoding-external-format #:*encoding-external-format-hook* #:default-encoding-external-format\n   #:*default-encoding* #:*utf-8-external-format*\n   #:with-safe-io-syntax #:call-with-safe-io-syntax #:safe-read-from-string\n   #:with-output #:output-string #:with-input #:input-string\n   #:with-input-file #:call-with-input-file #:with-output-file #:call-with-output-file\n   #:null-device-pathname #:call-with-null-input #:with-null-input\n   #:call-with-null-output #:with-null-output\n   #:finish-outputs #:format! #:safe-format!\n   #:copy-stream-to-stream #:concatenate-files #:copy-file\n   #:slurp-stream-string #:slurp-stream-lines #:slurp-stream-line\n   #:slurp-stream-forms #:slurp-stream-form\n   #:read-file-string #:read-file-line #:read-file-lines #:safe-read-file-line\n   #:read-file-forms #:read-file-form #:safe-read-file-form\n   #:eval-input #:eval-thunk #:standard-eval-thunk\n   #:println #:writeln\n   #:file-stream-p #:file-or-synonym-stream-p\n   ;; Temporary files\n   #:*temporary-directory* #:temporary-directory #:default-temporary-directory\n   #:setup-temporary-directory\n   #:call-with-temporary-file #:with-temporary-file\n   #:add-pathname-suffix #:tmpize-pathname\n   #:call-with-staging-pathname #:with-staging-pathname))\n(in-package :uiop/stream)\n\n(with-upgradability ()\n  (defvar *default-stream-element-type*\n    (or #+(or abcl cmucl cormanlisp scl xcl) 'character\n        #+lispworks 'lw:simple-char\n        :default)\n    \"default element-type for open (depends on the current CL implementation)\")\n\n  (defvar *stdin* *standard-input*\n    \"the original standard input stream at startup\")\n\n  (defun setup-stdin ()\n    (setf *stdin*\n          #.(or #+clozure 'ccl::*stdin*\n                #+(or cmucl scl) 'system:*stdin*\n                #+(or clasp ecl) 'ext::+process-standard-input+\n                #+sbcl 'sb-sys:*stdin*\n                '*standard-input*)))\n\n  (defvar *stdout* *standard-output*\n    \"the original standard output stream at startup\")\n\n  (defun setup-stdout ()\n    (setf *stdout*\n          #.(or #+clozure 'ccl::*stdout*\n                #+(or cmucl scl) 'system:*stdout*\n                #+(or clasp ecl) 'ext::+process-standard-output+\n                #+sbcl 'sb-sys:*stdout*\n                '*standard-output*)))\n\n  (defvar *stderr* *error-output*\n    \"the original error output stream at startup\")\n\n  (defun setup-stderr ()\n    (setf *stderr*\n          #.(or #+allegro 'excl::*stderr*\n                #+clozure 'ccl::*stderr*\n                #+(or cmucl scl) 'system:*stderr*\n                #+(or clasp ecl) 'ext::+process-error-output+\n                #+sbcl 'sb-sys:*stderr*\n                '*error-output*)))\n\n  ;; Run them now. In image.lisp, we'll register them to be run at image restart.\n  (setup-stdin) (setup-stdout) (setup-stderr))\n\n\n;;; Encodings (mostly hooks only; full support requires asdf-encodings)\n(with-upgradability ()\n  (defparameter *default-encoding*\n    ;; preserve explicit user changes to something other than the legacy default :default\n    (or (if-let (previous (and (boundp '*default-encoding*) (symbol-value '*default-encoding*)))\n          (unless (eq previous :default) previous))\n        :utf-8)\n    \"Default encoding for source files.\nThe default value :utf-8 is the portable thing.\nThe legacy behavior was :default.\nIf you (asdf:load-system :asdf-encodings) then\nyou will have autodetection via *encoding-detection-hook* below,\nreading emacs-style -*- coding: utf-8 -*- specifications,\nand falling back to utf-8 or latin1 if nothing is specified.\")\n\n  (defparameter *utf-8-external-format*\n    (if (featurep :asdf-unicode)\n        (or #+clisp charset:utf-8 :utf-8)\n        :default)\n    \"Default :external-format argument to pass to CL:OPEN and also\nCL:LOAD or CL:COMPILE-FILE to best process a UTF-8 encoded file.\nOn modern implementations, this will decode UTF-8 code points as CL characters.\nOn legacy implementations, it may fall back on some 8-bit encoding,\nwith non-ASCII code points being read as several CL characters;\nhopefully, if done consistently, that won't affect program behavior too much.\")\n\n  (defun always-default-encoding (pathname)\n    \"Trivial function to use as *encoding-detection-hook*,\nalways 'detects' the *default-encoding*\"\n    (declare (ignore pathname))\n    *default-encoding*)\n\n  (defvar *encoding-detection-hook* #'always-default-encoding\n    \"Hook for an extension to define a function to automatically detect a file's encoding\")\n\n  (defun detect-encoding (pathname)\n    \"Detects the encoding of a specified file, going through user-configurable hooks\"\n    (if (and pathname (not (directory-pathname-p pathname)) (probe-file* pathname))\n        (funcall *encoding-detection-hook* pathname)\n        *default-encoding*))\n\n  (defun default-encoding-external-format (encoding)\n    \"Default, ignorant, function to transform a character ENCODING as a\nportable keyword to an implementation-dependent EXTERNAL-FORMAT specification.\nLoad system ASDF-ENCODINGS to hook in a better one.\"\n    (case encoding\n      (:default :default) ;; for backward-compatibility only. Explicit usage discouraged.\n      (:utf-8 *utf-8-external-format*)\n      (otherwise\n       (cerror \"Continue using :external-format :default\" (compatfmt \"~@<Your ASDF component is using encoding ~S but it isn't recognized. Your system should :defsystem-depends-on (:asdf-encodings).~:>\") encoding)\n       :default)))\n\n  (defvar *encoding-external-format-hook*\n    #'default-encoding-external-format\n    \"Hook for an extension (e.g. ASDF-ENCODINGS) to define a better mapping\nfrom non-default encodings to and implementation-defined external-format's\")\n\n  (defun encoding-external-format (encoding)\n    \"Transform a portable ENCODING keyword to an implementation-dependent EXTERNAL-FORMAT,\ngoing through all the proper hooks.\"\n    (funcall *encoding-external-format-hook* (or encoding *default-encoding*))))\n\n\n;;; Safe syntax\n(with-upgradability ()\n  (defvar *standard-readtable* (with-standard-io-syntax *readtable*)\n    \"The standard readtable, implementing the syntax specified by the CLHS.\nIt must never be modified, though only good implementations will even enforce that.\")\n\n  (defmacro with-safe-io-syntax ((&key (package :cl)) &body body)\n    \"Establish safe CL reader options around the evaluation of BODY\"\n    `(call-with-safe-io-syntax #'(lambda () (let ((*package* (find-package ,package))) ,@body))))\n\n  (defun call-with-safe-io-syntax (thunk &key (package :cl))\n    (with-standard-io-syntax\n      (let ((*package* (find-package package))\n            (*read-default-float-format* 'double-float)\n            (*print-readably* nil)\n            (*read-eval* nil))\n        (funcall thunk))))\n\n  (defun safe-read-from-string (string &key (package :cl) (eof-error-p t) eof-value (start 0) end preserve-whitespace)\n    \"Read from STRING using a safe syntax, as per WITH-SAFE-IO-SYNTAX\"\n    (with-safe-io-syntax (:package package)\n      (read-from-string string eof-error-p eof-value :start start :end end :preserve-whitespace preserve-whitespace))))\n\n;;; Output helpers\n (with-upgradability ()\n  (defun call-with-output-file (pathname thunk\n                                &key\n                                  (element-type *default-stream-element-type*)\n                                  (external-format *utf-8-external-format*)\n                                  (if-exists :error)\n                                  (if-does-not-exist :create))\n    \"Open FILE for input with given recognizes options, call THUNK with the resulting stream.\nOther keys are accepted but discarded.\"\n    (with-open-file (s pathname :direction :output\n                                :element-type element-type\n                                :external-format external-format\n                                :if-exists if-exists\n                                :if-does-not-exist if-does-not-exist)\n      (funcall thunk s)))\n\n  (defmacro with-output-file ((var pathname &rest keys\n                               &key element-type external-format if-exists if-does-not-exist)\n                              &body body)\n    (declare (ignore element-type external-format if-exists if-does-not-exist))\n    `(call-with-output-file ,pathname #'(lambda (,var) ,@body) ,@keys))\n\n  (defun call-with-output (output function &key (element-type 'character))\n    \"Calls FUNCTION with an actual stream argument,\nbehaving like FORMAT with respect to how stream designators are interpreted:\nIf OUTPUT is a STREAM, use it as the stream.\nIf OUTPUT is NIL, use a STRING-OUTPUT-STREAM of given ELEMENT-TYPE as the stream, and\nreturn the resulting string.\nIf OUTPUT is T, use *STANDARD-OUTPUT* as the stream.\nIf OUTPUT is a STRING with a fill-pointer, use it as a STRING-OUTPUT-STREAM of given ELEMENT-TYPE.\nIf OUTPUT is a PATHNAME, open the file and write to it, passing ELEMENT-TYPE to WITH-OUTPUT-FILE\n-- this latter as an extension since ASDF 3.1.\n\\(Proper ELEMENT-TYPE treatment since ASDF 3.3.4 only.\\)\nOtherwise, signal an error.\"\n    (etypecase output\n      (null\n       (with-output-to-string (stream nil :element-type element-type) (funcall function stream)))\n      ((eql t)\n       (funcall function *standard-output*))\n      (stream\n       (funcall function output))\n      (string\n       (assert (fill-pointer output))\n       (with-output-to-string (stream output :element-type element-type) (funcall function stream)))\n      (pathname\n       (call-with-output-file output function :element-type element-type)))))\n\n(with-upgradability ()\n  (locally (declare #+sbcl (sb-ext:muffle-conditions style-warning))\n    (handler-bind (#+sbcl (style-warning #'muffle-warning))\n      (defmacro with-output ((output-var &optional (value output-var) &key element-type) &body body)\n        \"Bind OUTPUT-VAR to an output stream obtained from VALUE (default: previous binding\nof OUTPUT-VAR) treated as a stream designator per CALL-WITH-OUTPUT. Evaluate BODY in\nthe scope of this binding.\"\n        `(call-with-output ,value #'(lambda (,output-var) ,@body)\n                           ,@(when element-type `(:element-type ,element-type)))))))\n\n(defun output-string (string &optional output)\n  \"If the desired OUTPUT is not NIL, print the string to the output; otherwise return the string\"\n  (if output\n      (with-output (output) (princ string output))\n      string))\n\n\n;;; Input helpers\n(with-upgradability ()\n  (defun call-with-input-file (pathname thunk\n                               &key\n                                 (element-type *default-stream-element-type*)\n                                 (external-format *utf-8-external-format*)\n                                 (if-does-not-exist :error))\n    \"Open FILE for input with given recognizes options, call THUNK with the resulting stream.\nOther keys are accepted but discarded.\"\n    (with-open-file (s pathname :direction :input\n                                :element-type element-type\n                                :external-format external-format\n                                :if-does-not-exist if-does-not-exist)\n      (funcall thunk s)))\n\n  (defmacro with-input-file ((var pathname &rest keys\n                              &key element-type external-format if-does-not-exist)\n                             &body body)\n    (declare (ignore element-type external-format if-does-not-exist))\n    `(call-with-input-file ,pathname #'(lambda (,var) ,@body) ,@keys))\n\n  (defun call-with-input (input function &key keys)\n    \"Calls FUNCTION with an actual stream argument, interpreting\nstream designators like READ, but also coercing strings to STRING-INPUT-STREAM,\nand PATHNAME to FILE-STREAM.\nIf INPUT is a STREAM, use it as the stream.\nIf INPUT is NIL, use a *STANDARD-INPUT* as the stream.\nIf INPUT is T, use *TERMINAL-IO* as the stream.\nIf INPUT is a STRING, use it as a string-input-stream.\nIf INPUT is a PATHNAME, open it, passing KEYS to WITH-INPUT-FILE\n-- the latter is an extension since ASDF 3.1.\nOtherwise, signal an error.\"\n    (etypecase input\n      (null (funcall function *standard-input*))\n      ((eql t) (funcall function *terminal-io*))\n      (stream (funcall function input))\n      (string (with-input-from-string (stream input) (funcall function stream)))\n      (pathname (apply 'call-with-input-file input function keys))))\n\n  (defmacro with-input ((input-var &optional (value input-var)) &body body)\n    \"Bind INPUT-VAR to an input stream, coercing VALUE (default: previous binding of INPUT-VAR)\nas per CALL-WITH-INPUT, and evaluate BODY within the scope of this binding.\"\n    `(call-with-input ,value #'(lambda (,input-var) ,@body)))\n\n  (defun input-string (&optional input)\n    \"If the desired INPUT is a string, return that string; otherwise slurp the INPUT into a string\nand return that\"\n    (if (stringp input)\n        input\n        (with-input (input) (funcall 'slurp-stream-string input)))))\n\n;;; Null device\n(with-upgradability ()\n  (defun null-device-pathname ()\n    \"Pathname to a bit bucket device that discards any information written to it\nand always returns EOF when read from\"\n    (os-cond\n      ((os-unix-p) #p\"/dev/null\")\n      ((os-windows-p) #p\"NUL\") ;; Q: how many Lisps accept the #p\"NUL:\" syntax?\n      (t (error \"No /dev/null on your OS\"))))\n  (defun call-with-null-input (fun &key element-type external-format if-does-not-exist)\n    \"Call FUN with an input stream that always returns end of file.\nThe keyword arguments are allowed for backward compatibility, but are ignored.\"\n    (declare (ignore element-type external-format if-does-not-exist))\n    (with-open-stream (input (make-concatenated-stream))\n      (funcall fun input)))\n  (defmacro with-null-input ((var &rest keys\n                              &key element-type external-format if-does-not-exist)\n                             &body body)\n    (declare (ignore element-type external-format if-does-not-exist))\n    \"Evaluate BODY in a context when VAR is bound to an input stream that always returns end of file.\nThe keyword arguments are allowed for backward compatibility, but are ignored.\"\n    `(call-with-null-input #'(lambda (,var) ,@body) ,@keys))\n  (defun call-with-null-output (fun\n                                &key (element-type *default-stream-element-type*)\n                                  (external-format *utf-8-external-format*)\n                                  (if-exists :overwrite)\n                                  (if-does-not-exist :error))\n    (declare (ignore element-type external-format if-exists if-does-not-exist))\n    \"Call FUN with an output stream that discards all output.\nThe keyword arguments are allowed for backward compatibility, but are ignored.\"\n    (with-open-stream (output (make-broadcast-stream))\n      (funcall fun output)))\n  (defmacro with-null-output ((var &rest keys\n                              &key element-type external-format if-does-not-exist if-exists)\n                              &body body)\n    \"Evaluate BODY in a context when VAR is bound to an output stream that discards all output.\nThe keyword arguments are allowed for backward compatibility, but are ignored.\"\n    (declare (ignore element-type external-format if-exists if-does-not-exist))\n    `(call-with-null-output #'(lambda (,var) ,@body) ,@keys)))\n\n;;; Ensure output buffers are flushed\n(with-upgradability ()\n  (defun finish-outputs (&rest streams)\n    \"Finish output on the main output streams as well as any specified one.\nUseful for portably flushing I/O before user input or program exit.\"\n    ;; CCL notably buffers its stream output by default.\n    (dolist (s (append streams\n                       (list *stdout* *stderr* *error-output* *standard-output* *trace-output*\n                             *debug-io* *terminal-io* *query-io*)))\n      (ignore-errors (finish-output s)))\n    (values))\n\n  (defun format! (stream format &rest args)\n    \"Just like format, but call finish-outputs before and after the output.\"\n    (finish-outputs stream)\n    (apply 'format stream format args)\n    (finish-outputs stream))\n\n  (defun safe-format! (stream format &rest args)\n    \"Variant of FORMAT that is safe against both\ndangerous syntax configuration and errors while printing.\"\n    (with-safe-io-syntax ()\n      (ignore-errors (apply 'format! stream format args))\n      (finish-outputs stream)))) ; just in case format failed\n\n\n;;; Simple Whole-Stream processing\n(with-upgradability ()\n  (defun copy-stream-to-stream (input output &key element-type buffer-size linewise prefix)\n    \"Copy the contents of the INPUT stream into the OUTPUT stream.\nIf LINEWISE is true, then read and copy the stream line by line, with an optional PREFIX.\nOtherwise, using WRITE-SEQUENCE using a buffer of size BUFFER-SIZE.\"\n    (with-open-stream (input input)\n      (if linewise\n          (loop :for (line eof) = (multiple-value-list (read-line input nil nil))\n                :while line :do\n                  (when prefix (princ prefix output))\n                  (princ line output)\n                  (unless eof (terpri output))\n                  (finish-output output)\n                  (when eof (return)))\n          (loop\n            :with buffer-size = (or buffer-size 8192)\n            :with buffer = (make-array (list buffer-size) :element-type (or element-type 'character))\n            :for end = (read-sequence buffer input)\n            :until (zerop end)\n            :do (write-sequence buffer output :end end)\n                (when (< end buffer-size) (return))))))\n\n  (defun concatenate-files (inputs output)\n    \"create a new OUTPUT file the contents of which a the concatenate of the INPUTS files.\"\n    (with-open-file (o output :element-type '(unsigned-byte 8)\n                              :direction :output :if-exists :rename-and-delete)\n      (dolist (input inputs)\n        (with-open-file (i input :element-type '(unsigned-byte 8)\n                                 :direction :input :if-does-not-exist :error)\n          (copy-stream-to-stream i o :element-type '(unsigned-byte 8))))))\n\n  (defun copy-file (input output)\n    \"Copy contents of the INPUT file to the OUTPUT file\"\n    ;; Not available on LW personal edition or LW 6.0 on Mac: (lispworks:copy-file i f)\n    #+allegro\n    (excl.osi:copy-file input output)\n    #+ecl\n    (ext:copy-file input output)\n    #-(or allegro ecl)\n    (concatenate-files (list input) output))\n\n  (defun slurp-stream-string (input &key (element-type 'character) stripped)\n    \"Read the contents of the INPUT stream as a string\"\n    (let ((string\n            (with-open-stream (input input)\n              (with-output-to-string (output nil :element-type element-type)\n                (copy-stream-to-stream input output :element-type element-type)))))\n      (if stripped (stripln string) string)))\n\n  (defun slurp-stream-lines (input &key count)\n    \"Read the contents of the INPUT stream as a list of lines, return those lines.\n\nNote: relies on the Lisp's READ-LINE, but additionally removes any remaining CR\nfrom the line-ending if the file or stream had CR+LF but Lisp only removed LF.\n\nRead no more than COUNT lines.\"\n    (check-type count (or null integer))\n    (with-open-stream (input input)\n      (loop :for n :from 0\n            :for l = (and (or (not count) (< n count))\n                          (read-line input nil nil))\n            ;; stripln: to remove CR when the OS sends CRLF and Lisp only remove LF\n            :while l :collect (stripln l))))\n\n  (defun slurp-stream-line (input &key (at 0))\n    \"Read the contents of the INPUT stream as a list of lines,\nthen return the ACCESS-AT of that list of lines using the AT specifier.\nPATH defaults to 0, i.e. return the first line.\nPATH is typically an integer, or a list of an integer and a function.\nIf PATH is NIL, it will return all the lines in the file.\n\nThe stream will not be read beyond the Nth lines,\nwhere N is the index specified by path\nif path is either an integer or a list that starts with an integer.\"\n    (access-at (slurp-stream-lines input :count (access-at-count at)) at))\n\n  (defun slurp-stream-forms (input &key count)\n    \"Read the contents of the INPUT stream as a list of forms,\nand return those forms.\n\nIf COUNT is null, read to the end of the stream;\nif COUNT is an integer, stop after COUNT forms were read.\n\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (check-type count (or null integer))\n    (loop :with eof = '#:eof\n          :for n :from 0\n          :for form = (if (and count (>= n count))\n                          eof\n                          (read-preserving-whitespace input nil eof))\n          :until (eq form eof) :collect form))\n\n  (defun slurp-stream-form (input &key (at 0))\n    \"Read the contents of the INPUT stream as a list of forms,\nthen return the ACCESS-AT of these forms following the AT.\nAT defaults to 0, i.e. return the first form.\nAT is typically a list of integers.\nIf AT is NIL, it will return all the forms in the file.\n\nThe stream will not be read beyond the Nth form,\nwhere N is the index specified by path,\nif path is either an integer or a list that starts with an integer.\n\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (access-at (slurp-stream-forms input :count (access-at-count at)) at))\n\n  (defun read-file-string (file &rest keys)\n    \"Open FILE with option KEYS, read its contents as a string\"\n    (apply 'call-with-input-file file 'slurp-stream-string keys))\n\n  (defun read-file-lines (file &rest keys)\n    \"Open FILE with option KEYS, read its contents as a list of lines\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file 'slurp-stream-lines keys))\n\n  (defun read-file-line (file &rest keys &key (at 0) &allow-other-keys)\n    \"Open input FILE with option KEYS (except AT),\nand read its contents as per SLURP-STREAM-LINE with given AT specifier.\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file\n           #'(lambda (input) (slurp-stream-line input :at at))\n           (remove-plist-key :at keys)))\n\n  (defun read-file-forms (file &rest keys &key count &allow-other-keys)\n    \"Open input FILE with option KEYS (except COUNT),\nand read its contents as per SLURP-STREAM-FORMS with given COUNT.\nIf COUNT is null, read to the end of the stream;\nif COUNT is an integer, stop after COUNT forms were read.\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file\n           #'(lambda (input) (slurp-stream-forms input :count count))\n           (remove-plist-key :count keys)))\n\n  (defun read-file-form (file &rest keys &key (at 0) &allow-other-keys)\n    \"Open input FILE with option KEYS (except AT),\nand read its contents as per SLURP-STREAM-FORM with given AT specifier.\nBEWARE: be sure to use WITH-SAFE-IO-SYNTAX, or some variant thereof\"\n    (apply 'call-with-input-file file\n           #'(lambda (input) (slurp-stream-form input :at at))\n           (remove-plist-key :at keys)))\n\n  (defun safe-read-file-line (pathname &rest keys &key (package :cl) &allow-other-keys)\n    \"Reads the specified line from the top of a file using a safe standardized syntax.\nExtracts the line using READ-FILE-LINE,\nwithin an WITH-SAFE-IO-SYNTAX using the specified PACKAGE.\"\n    (with-safe-io-syntax (:package package)\n      (apply 'read-file-line pathname (remove-plist-key :package keys))))\n\n  (defun safe-read-file-form (pathname &rest keys &key (package :cl) &allow-other-keys)\n    \"Reads the specified form from the top of a file using a safe standardized syntax.\nExtracts the form using READ-FILE-FORM,\nwithin an WITH-SAFE-IO-SYNTAX using the specified PACKAGE.\"\n    (with-safe-io-syntax (:package package)\n      (apply 'read-file-form pathname (remove-plist-key :package keys))))\n\n  (defun eval-input (input)\n    \"Portably read and evaluate forms from INPUT, return the last values.\"\n    (with-input (input)\n      (loop :with results :with eof ='#:eof\n            :for form = (read input nil eof)\n            :until (eq form eof)\n            :do (setf results (multiple-value-list (eval form)))\n            :finally (return (values-list results)))))\n\n  (defun eval-thunk (thunk)\n    \"Evaluate a THUNK of code:\nIf a function, FUNCALL it without arguments.\nIf a constant literal and not a sequence, return it.\nIf a cons or a symbol, EVAL it.\nIf a string, repeatedly read and evaluate from it, returning the last values.\"\n    (etypecase thunk\n      ((or boolean keyword number character pathname) thunk)\n      ((or cons symbol) (eval thunk))\n      (function (funcall thunk))\n      (string (eval-input thunk))))\n\n  (defun standard-eval-thunk (thunk &key (package :cl))\n    \"Like EVAL-THUNK, but in a more standardized evaluation context.\"\n    ;; Note: it's \"standard-\" not \"safe-\", because evaluation is never safe.\n    (when thunk\n      (with-safe-io-syntax (:package package)\n        (let ((*read-eval* t))\n          (eval-thunk thunk))))))\n\n(with-upgradability ()\n  (defun println (x &optional (stream *standard-output*))\n    \"Variant of PRINC that also calls TERPRI afterwards\"\n    (princ x stream) (terpri stream) (finish-output stream) (values))\n\n  (defun writeln (x &rest keys &key (stream *standard-output*) &allow-other-keys)\n    \"Variant of WRITE that also calls TERPRI afterwards\"\n    (apply 'write x keys) (terpri stream) (finish-output stream) (values)))\n\n\n;;; Using temporary files\n(with-upgradability ()\n  (defun default-temporary-directory ()\n    \"Return a default directory to use for temporary files\"\n    (os-cond\n      ((os-unix-p)\n       (or (getenv-pathname \"TMPDIR\" :ensure-directory t)\n           (parse-native-namestring \"/tmp/\")))\n      ((os-windows-p)\n       (getenv-pathname \"TEMP\" :ensure-directory t))\n      (t (subpathname (user-homedir-pathname) \"tmp/\"))))\n\n  (defvar *temporary-directory* nil \"User-configurable location for temporary files\")\n\n  (defun temporary-directory ()\n    \"Return a directory to use for temporary files\"\n    (or *temporary-directory* (default-temporary-directory)))\n\n  (defun setup-temporary-directory ()\n    \"Configure a default temporary directory to use.\"\n    (setf *temporary-directory* (default-temporary-directory))\n    #+gcl (setf system::*tmp-dir* *temporary-directory*))\n\n  (defun call-with-temporary-file\n      (thunk &key\n               (want-stream-p t) (want-pathname-p t) (direction :io) keep after\n               directory (type \"tmp\" typep) prefix (suffix (when typep \"-tmp\"))\n               (element-type *default-stream-element-type*)\n               (external-format *utf-8-external-format*))\n    \"Call a THUNK with stream and/or pathname arguments identifying a temporary file.\n\nThe temporary file's pathname will be based on concatenating\nPREFIX (or \\\"tmp\\\" if it's NIL), a random alphanumeric string,\nand optional SUFFIX (defaults to \\\"-tmp\\\" if a type was provided)\nand TYPE (defaults to \\\"tmp\\\", using a dot as separator if not NIL),\nwithin DIRECTORY (defaulting to the TEMPORARY-DIRECTORY) if the PREFIX isn't absolute.\n\nThe file will be open with specified DIRECTION (defaults to :IO),\nELEMENT-TYPE (defaults to *DEFAULT-STREAM-ELEMENT-TYPE*) and\nEXTERNAL-FORMAT (defaults to *UTF-8-EXTERNAL-FORMAT*).\nIf WANT-STREAM-P is true (the defaults to T), then THUNK will then be CALL-FUNCTION'ed\nwith the stream and the pathname (if WANT-PATHNAME-P is true, defaults to T),\nand stream will be closed after the THUNK exits (either normally or abnormally).\nIf WANT-STREAM-P is false, then WANT-PATHAME-P must be true, and then\nTHUNK is only CALL-FUNCTION'ed after the stream is closed, with the pathname as argument.\nUpon exit of THUNK, the AFTER thunk if defined is CALL-FUNCTION'ed with the pathname as argument.\nIf AFTER is defined, its results are returned, otherwise, the results of THUNK are returned.\nFinally, the file will be deleted, unless the KEEP argument when CALL-FUNCTION'ed returns true.\"\n    #+xcl (declare (ignorable typep))\n    (check-type direction (member :output :io))\n    (assert (or want-stream-p want-pathname-p))\n    (loop\n      :with prefix-pn = (ensure-absolute-pathname\n                         (or prefix \"tmp\")\n                         (or (ensure-pathname\n                              directory\n                              :namestring :native\n                              :ensure-directory t\n                              :ensure-physical t)\n                             #'temporary-directory))\n      :with prefix-nns = (native-namestring prefix-pn)\n      :with results = (progn (ensure-directories-exist prefix-pn)\n                             ())\n      :for counter :from (random (expt 36 #-gcl 8 #+gcl 5))\n      :for pathname = (parse-native-namestring\n                       (format nil \"~A~36R~@[~A~]~@[.~A~]\"\n                               prefix-nns counter suffix (unless (eq type :unspecific) type)))\n      :for okp = nil :do\n        ;; TODO: on Unix, do something about umask\n        ;; TODO: on Unix, audit the code so we make sure it uses O_CREAT|O_EXCL\n        ;; TODO: on Unix, use CFFI and mkstemp --\n        ;; except UIOP is precisely meant to not depend on CFFI or on anything! Grrrr.\n        ;; Can we at least design some hook?\n        (unwind-protect\n             (progn\n               (ensure-directories-exist pathname)\n               (with-open-file (stream pathname\n                                       :direction direction\n                                       :element-type element-type\n                                       :external-format external-format\n                                       :if-exists nil :if-does-not-exist :create)\n                 (when stream\n                   (setf okp pathname)\n                   (when want-stream-p\n                     ;; Note: can't return directly from within with-open-file\n                     ;; or the non-local return causes the file creation to be undone.\n                     (setf results (multiple-value-list\n                                    (if want-pathname-p\n                                        (call-function thunk stream pathname)\n                                        (call-function thunk stream)))))))\n               ;; if we don't want a stream, then we must call the thunk *after*\n               ;; the stream is closed, but only if it was successfully opened.\n               (when okp\n                 (when (and want-pathname-p (not want-stream-p))\n                   (setf results (multiple-value-list (call-function thunk okp))))\n                 ;; if the stream was successfully opened, then return a value,\n                 ;; either one computed already, or one from AFTER, if that exists.\n                 (if after\n                     (return (call-function after pathname))\n                     (return (values-list results)))))\n          (when (and okp (not (call-function keep)))\n            (ignore-errors (delete-file-if-exists okp))))))\n\n  (defmacro with-temporary-file ((&key (stream (gensym \"STREAM\") streamp)\n                                    (pathname (gensym \"PATHNAME\") pathnamep)\n                                    directory prefix suffix type\n                                    keep direction element-type external-format)\n                                 &body body)\n    \"Evaluate BODY where the symbols specified by keyword arguments\nSTREAM and PATHNAME (if respectively specified) are bound corresponding\nto a newly created temporary file ready for I/O, as per CALL-WITH-TEMPORARY-FILE.\nAt least one of STREAM or PATHNAME must be specified.\nIf the STREAM is not specified, it will be closed before the BODY is evaluated.\nIf STREAM is specified, then the :CLOSE-STREAM label if it appears in the BODY,\nseparates forms run before and after the stream is closed.\nThe values of the last form of the BODY (not counting the separating :CLOSE-STREAM) are returned.\nUpon success, the KEEP form is evaluated and the file is is deleted unless it evaluates to TRUE.\"\n    (check-type stream symbol)\n    (check-type pathname symbol)\n    (assert (or streamp pathnamep))\n    (let* ((afterp (position :close-stream body))\n           (before (if afterp (subseq body 0 afterp) body))\n           (after (when afterp (subseq body (1+ afterp))))\n           (beforef (gensym \"BEFORE\"))\n           (afterf (gensym \"AFTER\")))\n      (when (eql afterp 0)\n        (style-warn \":CLOSE-STREAM should not be the first form of BODY in WITH-TEMPORARY-FILE. Instead, do not provide a STREAM.\"))\n      `(flet (,@(when before\n                  `((,beforef (,@(when streamp `(,stream)) ,@(when pathnamep `(,pathname)))\n                       ,@(when after `((declare (ignorable ,pathname))))\n                       ,@before)))\n              ,@(when after\n                  (assert pathnamep)\n                  `((,afterf (,pathname) (declare (ignorable ,pathname)) ,@after))))\n         #-gcl (declare (dynamic-extent ,@(when before `(#',beforef)) ,@(when after `(#',afterf))))\n         (call-with-temporary-file\n          ,(when before `#',beforef)\n          :want-stream-p ,streamp\n          :want-pathname-p ,pathnamep\n          ,@(when direction `(:direction ,direction))\n          ,@(when directory `(:directory ,directory))\n          ,@(when prefix `(:prefix ,prefix))\n          ,@(when suffix `(:suffix ,suffix))\n          ,@(when type `(:type ,type))\n          ,@(when keep `(:keep ,keep))\n          ,@(when after `(:after #',afterf))\n          ,@(when element-type `(:element-type ,element-type))\n          ,@(when external-format `(:external-format ,external-format))))))\n\n  (defun get-temporary-file (&key directory prefix suffix type (keep t))\n    (with-temporary-file (:pathname pn :keep keep\n                          :directory directory :prefix prefix :suffix suffix :type type)\n      pn))\n\n  ;; Temporary pathnames in simple cases where no contention is assumed\n  (defun add-pathname-suffix (pathname suffix &rest keys)\n    \"Add a SUFFIX to the name of a PATHNAME, return a new pathname.\nFurther KEYS can be passed to MAKE-PATHNAME.\"\n    (apply 'make-pathname :name (strcat (pathname-name pathname) suffix)\n                          :defaults pathname keys))\n\n  (defun tmpize-pathname (x)\n    \"Return a new pathname modified from X by adding a trivial random suffix.\nA new empty file with said temporary pathname is created, to ensure there is no\nclash with any concurrent process attempting the same thing.\"\n    (let* ((px (ensure-pathname x :ensure-physical t))\n           (prefix (if-let (n (pathname-name px)) (strcat n \"-tmp\") \"tmp\"))\n           (directory (pathname-directory-pathname px)))\n      ;; Genera uses versioned pathnames -- If we leave the empty file in place,\n      ;; the system will create a new version of the file when the caller opens\n      ;; it for output. That empty file will remain after the operation is completed.\n      ;; As Genera is a single core processor, the possibility of a name conflict is\n      ;; minimal if not nil. (And, in the event of a collision, the two processes\n      ;; would be writing to different versions of the file.)\n      (get-temporary-file :directory directory :prefix prefix :type (pathname-type px)\n                          #+genera :keep #+genera nil)))\n\n  (defun call-with-staging-pathname (pathname fun)\n    \"Calls FUN with a staging pathname, and atomically\nrenames the staging pathname to the PATHNAME in the end.\nNB: this protects only against failure of the program, not against concurrent attempts.\nFor the latter case, we ought pick a random suffix and atomically open it.\"\n    (let* ((pathname (pathname pathname))\n           (staging (tmpize-pathname pathname)))\n      (unwind-protect\n           (multiple-value-prog1\n               (funcall fun staging)\n             (rename-file-overwriting-target staging pathname))\n        (delete-file-if-exists staging))))\n\n  (defmacro with-staging-pathname ((pathname-var &optional (pathname-value pathname-var)) &body body)\n    \"Trivial syntax wrapper for CALL-WITH-STAGING-PATHNAME\"\n    `(call-with-staging-pathname ,pathname-value #'(lambda (,pathname-var) ,@body))))\n\n(with-upgradability ()\n  (defun file-stream-p (stream)\n    (typep stream 'file-stream))\n  (defun file-or-synonym-stream-p (stream)\n    (or (file-stream-p stream)\n        (and (typep stream 'synonym-stream)\n             (file-or-synonym-stream-p\n              (symbol-value (synonym-stream-symbol stream)))))))\n;;;; -------------------------------------------------------------------------\n;;;; Starting, Stopping, Dumping a Lisp image\n\n(uiop/package:define-package :uiop/image\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/pathname :uiop/stream :uiop/os)\n  (:export\n   #:*image-dumped-p* #:raw-command-line-arguments #:*command-line-arguments*\n   #:command-line-arguments #:raw-command-line-arguments #:setup-command-line-arguments #:argv0\n   #:*lisp-interaction*\n   #:fatal-condition #:fatal-condition-p\n   #:handle-fatal-condition\n   #:call-with-fatal-condition-handler #:with-fatal-condition-handler\n   #:*image-restore-hook* #:*image-prelude* #:*image-entry-point*\n   #:*image-postlude* #:*image-dump-hook*\n   #:quit #:die #:raw-print-backtrace #:print-backtrace #:print-condition-backtrace\n   #:shell-boolean-exit\n   #:register-image-restore-hook #:register-image-dump-hook\n   #:call-image-restore-hook #:call-image-dump-hook\n   #:restore-image #:dump-image #:create-image\n))\n(in-package :uiop/image)\n\n(with-upgradability ()\n  (defvar *lisp-interaction* t\n    \"Is this an interactive Lisp environment, or is it batch processing?\")\n\n  (defvar *command-line-arguments* nil\n    \"Command-line arguments\")\n\n  (defvar *image-dumped-p* nil ; may matter as to how to get to command-line-arguments\n    \"Is this a dumped image? As a standalone executable?\")\n\n  (defvar *image-restore-hook* nil\n    \"Functions to call (in reverse order) when the image is restored\")\n\n  (defvar *image-restored-p* nil\n    \"Has the image been restored? A boolean, or :in-progress while restoring, :in-regress while dumping\")\n\n  (defvar *image-prelude* nil\n    \"a form to evaluate, or string containing forms to read and evaluate\nwhen the image is restarted, but before the entry point is called.\")\n\n  (defvar *image-entry-point* nil\n    \"a function with which to restart the dumped image when execution is restored from it.\")\n\n  (defvar *image-postlude* nil\n    \"a form to evaluate, or string containing forms to read and evaluate\nbefore the image dump hooks are called and before the image is dumped.\")\n\n  (defvar *image-dump-hook* nil\n    \"Functions to call (in order) before an image is dumped\"))\n\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute)\n  (deftype fatal-condition ()\n    `(and serious-condition #+clozure (not ccl:process-reset))))\n\n;;; Exiting properly or im-\n(with-upgradability ()\n  (defun quit (&optional (code 0) (finish-output t))\n    \"Quits from the Lisp world, with the given exit status if provided.\nThis is designed to abstract away the implementation specific quit forms.\"\n    (when finish-output ;; essential, for ClozureCL, and for standard compliance.\n      (finish-outputs))\n    #+(or abcl xcl) (ext:quit :status code)\n    #+allegro (excl:exit code :quiet t)\n    #+(or clasp ecl) (si:quit code)\n    #+clisp (ext:quit code)\n    #+clozure (ccl:quit code)\n    #+cormanlisp (win32:exitprocess code)\n    #+(or cmucl scl) (unix:unix-exit code)\n    #+gcl (system:quit code)\n    #+genera (error \"~S: You probably don't want to Halt Genera. (code: ~S)\" 'quit code)\n    #+lispworks (lispworks:quit :status code :confirm nil :return nil :ignore-errors-p t)\n    #+mcl (progn code (ccl:quit)) ;; or should we use FFI to call libc's exit(3) ?\n    #+mkcl (mk-ext:quit :exit-code code)\n    #+sbcl #.(let ((exit (find-symbol* :exit :sb-ext nil))\n                   (quit (find-symbol* :quit :sb-ext nil)))\n               (cond\n                 (exit `(,exit :code code :abort (not finish-output)))\n                 (quit `(,quit :unix-status code :recklessly-p (not finish-output)))))\n    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl genera lispworks mcl mkcl sbcl scl xcl)\n    (not-implemented-error 'quit \"(called with exit code ~S)\" code))\n\n  (defun die (code format &rest arguments)\n    \"Die in error with some error message\"\n    (with-safe-io-syntax ()\n      (ignore-errors\n       (format! *stderr* \"~&~?~&\" format arguments)))\n    (quit code))\n\n  (defun raw-print-backtrace (&key (stream *debug-io*) count condition)\n    \"Print a backtrace, directly accessing the implementation\"\n    (declare (ignorable stream count condition))\n    #+abcl\n    (loop :for i :from 0\n          :for frame :in (sys:backtrace (or count most-positive-fixnum)) :do\n            (safe-format! stream \"~&~D: ~A~%\" i frame))\n    #+allegro\n    (let ((*terminal-io* stream)\n          (*standard-output* stream)\n          (tpl:*zoom-print-circle* *print-circle*)\n          (tpl:*zoom-print-level* *print-level*)\n          (tpl:*zoom-print-length* *print-length*))\n      (tpl:do-command \"zoom\"\n        :from-read-eval-print-loop nil\n        :count (or count t)\n        :all t))\n    #+clasp\n    (clasp-debug:print-backtrace :stream stream :count count)\n    #+(or ecl mkcl)\n    (let* ((top (si:ihs-top))\n           (repeats (if count (min top count) top))\n           (backtrace (loop :for ihs :from 0 :below top\n                            :collect (list (si::ihs-fun ihs)\n                                           (si::ihs-env ihs)))))\n      (loop :for i :from 0 :below repeats\n            :for frame :in (nreverse backtrace) :do\n              (safe-format! stream \"~&~D: ~S~%\" i frame)))\n    #+clisp\n    (system::print-backtrace :out stream :limit count)\n    #+(or clozure mcl)\n    (let ((*debug-io* stream))\n      #+clozure (ccl:print-call-history :count count :start-frame-number 1)\n      #+mcl (ccl:print-call-history :detailed-p nil)\n      (finish-output stream))\n    #+(or cmucl scl)\n    (let ((debug:*debug-print-level* *print-level*)\n          (debug:*debug-print-length* *print-length*))\n      (debug:backtrace (or count most-positive-fixnum) stream))\n    #+gcl\n    (let ((*debug-io* stream))\n      (ignore-errors\n       (with-safe-io-syntax ()\n         (if condition\n             (conditions::condition-backtrace condition)\n             (system::simple-backtrace)))))\n    #+lispworks\n    (let ((dbg::*debugger-stack*\n            (dbg::grab-stack nil :how-many (or count most-positive-fixnum)))\n          (*debug-io* stream)\n          (dbg:*debug-print-level* *print-level*)\n          (dbg:*debug-print-length* *print-length*))\n      (dbg:bug-backtrace nil))\n    #+mezzano\n    (let ((*standard-output* stream))\n      (sys.int::backtrace count))\n    #+sbcl\n    (sb-debug:print-backtrace :stream stream :count (or count most-positive-fixnum))\n    #+xcl\n    (loop :for i :from 0 :below (or count most-positive-fixnum)\n          :for frame :in (extensions:backtrace-as-list) :do\n            (safe-format! stream \"~&~D: ~S~%\" i frame)))\n\n  (defun print-backtrace (&rest keys &key stream count condition)\n    \"Print a backtrace\"\n    (declare (ignore stream count condition))\n    (with-safe-io-syntax (:package :cl)\n      (let ((*print-readably* nil)\n            (*print-circle* t)\n            (*print-miser-width* 75)\n            (*print-length* nil)\n            (*print-level* nil)\n            (*print-pretty* t))\n        (ignore-errors (apply 'raw-print-backtrace keys)))))\n\n  (defun print-condition-backtrace (condition &key (stream *stderr*) count)\n    \"Print a condition after a backtrace triggered by that condition\"\n    ;; We print the condition *after* the backtrace,\n    ;; for the sake of who sees the backtrace at a terminal.\n    ;; It is up to the caller to print the condition *before*, with some context.\n    (print-backtrace :stream stream :count count :condition condition)\n    (when condition\n      (safe-format! stream \"~&Above backtrace due to this condition:~%~A~&\"\n                    condition)))\n\n  (defun fatal-condition-p (condition)\n    \"Is the CONDITION fatal?\"\n    (typep condition 'fatal-condition))\n\n  (defun handle-fatal-condition (condition)\n    \"Handle a fatal CONDITION:\ndepending on whether *LISP-INTERACTION* is set, enter debugger or die\"\n    (cond\n      (*lisp-interaction*\n       (invoke-debugger condition))\n      (t\n       (safe-format! *stderr* \"~&Fatal condition:~%~A~%\" condition)\n       (print-condition-backtrace condition :stream *stderr*)\n       (die 99 \"~A\" condition))))\n\n  (defun call-with-fatal-condition-handler (thunk)\n    \"Call THUNK in a context where fatal conditions are appropriately handled\"\n    (handler-bind ((fatal-condition #'handle-fatal-condition))\n      (funcall thunk)))\n\n  (defmacro with-fatal-condition-handler ((&optional) &body body)\n    \"Execute BODY in a context where fatal conditions are appropriately handled\"\n    `(call-with-fatal-condition-handler #'(lambda () ,@body)))\n\n  (defun shell-boolean-exit (x)\n    \"Quit with a return code that is 0 iff argument X is true\"\n    (quit (if x 0 1))))\n\n\n;;; Using image hooks\n(with-upgradability ()\n  (defun register-image-restore-hook (hook &optional (call-now-p t))\n    \"Regiter a hook function to be run when restoring a dumped image\"\n    (register-hook-function '*image-restore-hook* hook call-now-p))\n\n  (defun register-image-dump-hook (hook &optional (call-now-p nil))\n    \"Register a the hook function to be run before to dump an image\"\n    (register-hook-function '*image-dump-hook* hook call-now-p))\n\n  (defun call-image-restore-hook ()\n    \"Call the hook functions registered to be run when restoring a dumped image\"\n    (call-functions (reverse *image-restore-hook*)))\n\n  (defun call-image-dump-hook ()\n    \"Call the hook functions registered to be run before to dump an image\"\n    (call-functions *image-dump-hook*)))\n\n\n;;; Proper command-line arguments\n(with-upgradability ()\n  (defun raw-command-line-arguments ()\n    \"Find what the actual command line for this process was.\"\n    #+abcl ext:*command-line-argument-list* ; Use 1.0.0 or later!\n    #+allegro (sys:command-line-arguments) ; default: :application t\n    #+(or clasp ecl) (loop :for i :from 0 :below (si:argc) :collect (si:argv i))\n    #+clisp (coerce (ext:argv) 'list)\n    #+clozure ccl:*command-line-argument-list*\n    #+(or cmucl scl) extensions:*command-line-strings*\n    #+gcl si:*command-args*\n    #+(or genera mcl mezzano) nil\n    #+lispworks sys:*line-arguments-list*\n    #+mkcl (loop :for i :from 0 :below (mkcl:argc) :collect (mkcl:argv i))\n    #+sbcl sb-ext:*posix-argv*\n    #+xcl system:*argv*\n    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl genera lispworks mcl mezzano mkcl sbcl scl xcl)\n    (not-implemented-error 'raw-command-line-arguments))\n\n  (defun command-line-arguments (&optional (arguments (raw-command-line-arguments)))\n    \"Extract user arguments from command-line invocation of current process.\nAssume the calling conventions of a generated script that uses --\nif we are not called from a directly executable image.\"\n    (block nil\n      #+abcl (return arguments)\n      ;; SBCL and Allegro already separate user arguments from implementation arguments.\n      #-(or sbcl allegro)\n      (unless (eq *image-dumped-p* :executable)\n        ;; LispWorks command-line processing isn't transparent to the user\n        ;; unless you create a standalone executable; in that case,\n        ;; we rely on cl-launch or some other script to set the arguments for us.\n        #+lispworks (return *command-line-arguments*)\n        ;; On other implementations, on non-standalone executables,\n        ;; we trust cl-launch or whichever script starts the program\n        ;; to use -- as a delimiter between implementation arguments and user arguments.\n        #-lispworks (setf arguments (member \"--\" arguments :test 'string-equal)))\n      (rest arguments)))\n\n  (defun argv0 ()\n    \"On supported implementations (most that matter), or when invoked by a proper wrapper script,\nreturn a string that for the name with which the program was invoked, i.e. argv[0] in C.\nOtherwise, return NIL.\"\n    (cond\n      ((eq *image-dumped-p* :executable) ; yes, this ARGV0 is our argv0 !\n       ;; NB: not currently available on ABCL, Corman, Genera, MCL\n       (or #+(or allegro clisp clozure cmucl gcl lispworks sbcl scl xcl)\n           (first (raw-command-line-arguments))\n           #+(or clasp ecl) (si:argv 0) #+mkcl (mkcl:argv 0)))\n      (t ;; argv[0] is the name of the interpreter.\n       ;; The wrapper script can export __CL_ARGV0. cl-launch does as of 4.0.1.8.\n       (getenvp \"__CL_ARGV0\"))))\n\n  (defun setup-command-line-arguments ()\n    (setf *command-line-arguments* (command-line-arguments)))\n\n  (defun restore-image (&key\n                          (lisp-interaction *lisp-interaction*)\n                          (restore-hook *image-restore-hook*)\n                          (prelude *image-prelude*)\n                          (entry-point *image-entry-point*)\n                          (if-already-restored '(cerror \"RUN RESTORE-IMAGE ANYWAY\")))\n    \"From a freshly restarted Lisp image, restore the saved Lisp environment\nby setting appropriate variables, running various hooks, and calling any specified entry point.\n\nIf the image has already been restored or is already being restored, as per *IMAGE-RESTORED-P*,\ncall the IF-ALREADY-RESTORED error handler (by default, a continuable error), and do return\nimmediately to the surrounding restore process if allowed to continue.\n\nThen, comes the restore process itself:\nFirst, call each function in the RESTORE-HOOK,\nin the order they were registered with REGISTER-IMAGE-RESTORE-HOOK.\nSecond, evaluate the prelude, which is often Lisp text that is read,\nas per EVAL-INPUT.\nThird, call the ENTRY-POINT function, if any is specified, with no argument.\n\nThe restore process happens in a WITH-FATAL-CONDITION-HANDLER, so that if LISP-INTERACTION is NIL,\nany unhandled error leads to a backtrace and an exit with an error status.\nIf LISP-INTERACTION is NIL, the process also exits when no error occurs:\nif neither restart nor entry function is provided, the program will exit with status 0 (success);\nif a function was provided, the program will exit after the function returns (if it returns),\nwith status 0 if and only if the primary return value of result is generalized boolean true,\nand with status 1 if this value is NIL.\n\nIf LISP-INTERACTION is true, unhandled errors will take you to the debugger, and the result\nof the function will be returned rather than interpreted as a boolean designating an exit code.\"\n    (when *image-restored-p*\n      (if if-already-restored\n          (call-function if-already-restored \"Image already ~:[being ~;~]restored\"\n                         (eq *image-restored-p* t))\n          (return-from restore-image)))\n    (with-fatal-condition-handler ()\n      (setf *lisp-interaction* lisp-interaction)\n      (setf *image-restore-hook* restore-hook)\n      (setf *image-prelude* prelude)\n      (setf *image-restored-p* :in-progress)\n      (call-image-restore-hook)\n      (standard-eval-thunk prelude)\n      (setf *image-restored-p* t)\n      (let ((results (multiple-value-list\n                      (if entry-point\n                          (call-function entry-point)\n                          t))))\n        (if lisp-interaction\n            (values-list results)\n            (shell-boolean-exit (first results)))))))\n\n\n;;; Dumping an image\n\n(with-upgradability ()\n  (defun dump-image (filename &key output-name executable\n                                (postlude *image-postlude*)\n                                (dump-hook *image-dump-hook*)\n                                #+clozure prepend-symbols #+clozure (purify t)\n                                #+sbcl compression\n                                #+(and sbcl os-windows) application-type)\n    \"Dump an image of the current Lisp environment at pathname FILENAME, with various options.\n\nFirst, finalize the image, by evaluating the POSTLUDE as per EVAL-INPUT, then calling each of\n the functions in DUMP-HOOK, in reverse order of registration by REGISTER-IMAGE-DUMP-HOOK.\n\nIf EXECUTABLE is true, create an standalone executable program that calls RESTORE-IMAGE on startup.\n\nPass various implementation-defined options, such as PREPEND-SYMBOLS and PURITY on CCL,\nor COMPRESSION on SBCL, and APPLICATION-TYPE on SBCL/Windows.\"\n    ;; Note: at least SBCL saves only global values of variables in the heap image,\n    ;; so make sure things you want to dump are NOT just local bindings shadowing the global values.\n    (declare (ignorable filename output-name executable))\n    (setf *image-dumped-p* (if executable :executable t))\n    (setf *image-restored-p* :in-regress)\n    (setf *image-postlude* postlude)\n    (standard-eval-thunk *image-postlude*)\n    (setf *image-dump-hook* dump-hook)\n    (call-image-dump-hook)\n    (setf *image-restored-p* nil)\n    #-(or clisp clozure (and cmucl executable) lispworks sbcl scl)\n    (when executable\n      (not-implemented-error 'dump-image \"dumping an executable\"))\n    #+allegro ;; revised with help from Franz\n    (progn\n      #+(and allegro-version>= (version>= 11))\n      (sys:resize-areas\n       :old :no-change :old-code :no-change\n       :global-gc t\n       :tenure t)\n      #+(and allegro-version>= (version= 10 1))\n      (sys:resize-areas :old 10000000 :global-gc t :pack-heap t :sift-old-areas t :tenure t)\n      #+(and allegro-version>= (not (version>= 10 1)))\n      (sys:resize-areas :global-gc t :pack-heap t :sift-old-areas t :tenure t)\n      (excl:dumplisp :name filename :suppress-allegro-cl-banner t))\n    #+clisp\n    (apply #'ext:saveinitmem filename\n           :quiet t\n           :start-package *package*\n           :keep-global-handlers nil\n           ;; Faré explains the odd executable value (slightly paraphrased):\n           ;; 0 is very different from t in clisp and there for a good reason:\n           ;; 0 turns the executable into one that has its own command-line handling, so hackers can't\n           ;; use the underlying -i or -x to turn your would-be restricted binary into an unrestricted evaluator.\n           :executable (if executable 0 t) ;--- requires clisp 2.48 or later, still catches --clisp-x\n           (when executable\n             (list\n              ;; :parse-options nil ;--- requires a non-standard patch to clisp.\n              :norc t :script nil :init-function #'restore-image)))\n    #+clozure\n    (flet ((dump (prepend-kernel)\n             (ccl:save-application filename :prepend-kernel prepend-kernel :purify purify\n                                            :toplevel-function (when executable #'restore-image))))\n      ;;(setf ccl::*application* (make-instance 'ccl::lisp-development-system))\n      (if prepend-symbols\n          (with-temporary-file (:prefix \"ccl-symbols-\" :direction :output :pathname path)\n            (require 'elf)\n            (funcall (fdefinition 'ccl::write-elf-symbols-to-file) path)\n            (dump path))\n          (dump t)))\n    #+(or cmucl scl)\n    (progn\n      (ext:gc :full t)\n      (setf ext:*batch-mode* nil)\n      (setf ext::*gc-run-time* 0)\n      (apply 'ext:save-lisp filename\n             :allow-other-keys t ;; hush SCL and old versions of CMUCL\n             #+(and cmucl executable) :executable #+(and cmucl executable) t\n             (when executable '(:init-function restore-image :process-command-line nil\n                                :quiet t :load-init-file nil :site-init nil))))\n    #+gcl\n    (progn\n      (si::set-hole-size 500) (si::gbc nil) (si::sgc-on t)\n      (si::save-system filename))\n    #+lispworks\n    (if executable\n        (lispworks:deliver 'restore-image filename 0 :interface nil)\n        (hcl:save-image filename :environment nil))\n    #+sbcl\n    (progn\n      ;;(sb-pcl::precompile-random-code-segments) ;--- it is ugly slow at compile-time (!) when the initial core is a big CLOS program. If you want it, do it yourself\n      (setf sb-ext::*gc-run-time* 0)\n      (apply 'sb-ext:save-lisp-and-die filename\n             :executable t ;--- always include the runtime that goes with the core\n             (append\n              (when compression (list :compression compression))\n              ;;--- only save runtime-options for standalone executables\n              (when executable (list :toplevel #'restore-image :save-runtime-options t))\n              #+(and sbcl os-windows) ;; passing :application-type :gui will disable the console window.\n              ;; the default is :console - only works with SBCL 1.1.15 or later.\n              (when application-type (list :application-type application-type)))))\n    #-(or allegro clisp clozure cmucl gcl lispworks sbcl scl)\n    (not-implemented-error 'dump-image))\n\n  (defun create-image (destination lisp-object-files\n                       &key kind output-name prologue-code epilogue-code extra-object-files\n                         (prelude () preludep) (postlude () postludep)\n                         (entry-point () entry-point-p) build-args no-uiop)\n    (declare (ignorable destination lisp-object-files extra-object-files kind output-name\n                        prologue-code epilogue-code prelude preludep postlude postludep\n                        entry-point entry-point-p build-args no-uiop))\n    \"On ECL, create an executable at pathname DESTINATION from the specified OBJECT-FILES and options\"\n    ;; Is it meaningful to run these in the current environment?\n    ;; only if we also track the object files that constitute the \"current\" image,\n    ;; and otherwise simulate dump-image, including quitting at the end.\n    #-(or clasp ecl mkcl) (not-implemented-error 'create-image)\n    #+(or clasp ecl mkcl)\n    (let ((epilogue-code\n           (if no-uiop\n               epilogue-code\n               (let ((forms\n                      (append\n                       (when epilogue-code `(,epilogue-code))\n                       (when postludep `((setf *image-postlude* ',postlude)))\n                       (when preludep `((setf *image-prelude* ',prelude)))\n                       (when entry-point-p `((setf *image-entry-point* ',entry-point)))\n                       (case kind\n                         ((:image)\n                          (setf kind :program) ;; to ECL, it's just another program.\n                          `((setf *image-dumped-p* t)\n                            (si::top-level #+(or clasp ecl) t) (quit)))\n                         ((:program)\n                          `((setf *image-dumped-p* :executable)\n                            (shell-boolean-exit\n                             (restore-image))))))))\n                 (when forms `(progn ,@forms))))))\n      (check-type kind (member :dll :shared-library :lib :static-library\n                               :fasl :fasb :program))\n      (apply #+clasp 'cmp:builder #+clasp kind\n             #+(or ecl mkcl)\n             (ecase kind\n               ((:dll :shared-library)\n                #+ecl 'c::build-shared-library #+mkcl 'compiler:build-shared-library)\n               ((:lib :static-library)\n                #+ecl 'c::build-static-library #+mkcl 'compiler:build-static-library)\n               ((:fasl #+ecl :fasb)\n                #+ecl 'c::build-fasl #+mkcl 'compiler:build-fasl)\n               #+mkcl ((:fasb) 'compiler:build-bundle)\n               ((:program)\n                #+ecl 'c::build-program #+mkcl 'compiler:build-program))\n             (pathname destination)\n             #+(or clasp ecl) :lisp-files #+mkcl :lisp-object-files\n             (append lisp-object-files #+(or clasp ecl) extra-object-files)\n             #+ecl :init-name\n             #+ecl (getf build-args :init-name)\n             (append\n              (when prologue-code `(:prologue-code ,prologue-code))\n              (when epilogue-code `(:epilogue-code ,epilogue-code))\n              #+mkcl (when extra-object-files `(:object-files ,extra-object-files))\n              build-args)))))\n\n\n;;; Some universal image restore hooks\n(with-upgradability ()\n  (map () 'register-image-restore-hook\n       '(setup-stdin setup-stdout setup-stderr\n         setup-command-line-arguments setup-temporary-directory\n         #+abcl detect-os)))\n;;;; -------------------------------------------------------------------------\n;;;; Support to build (compile and load) Lisp files\n\n(uiop/package:define-package :uiop/lisp-build\n  (:nicknames :asdf/lisp-build) ;; OBSOLETE, used by slime/contrib/swank-asdf.lisp\n  (:use :uiop/common-lisp :uiop/package :uiop/utility\n   :uiop/os :uiop/pathname :uiop/filesystem :uiop/stream :uiop/image)\n  (:export\n   ;; Variables\n   #:*compile-file-warnings-behaviour* #:*compile-file-failure-behaviour*\n   #:*output-translation-function*\n   ;; the following dropped because unnecessary.\n   ;; #:*optimization-settings* #:*previous-optimization-settings*\n   #:*base-build-directory*\n   #:compile-condition #:compile-file-error #:compile-warned-error #:compile-failed-error\n   #:compile-warned-warning #:compile-failed-warning\n   #:check-lisp-compile-results #:check-lisp-compile-warnings\n   #:*uninteresting-conditions* #:*usual-uninteresting-conditions*\n   #:*uninteresting-compiler-conditions* #:*uninteresting-loader-conditions*\n   ;; Types\n   #+sbcl #:sb-grovel-unknown-constant-condition\n   ;; Functions & Macros\n   ;; the following three removed from UIOP because they have bugs, it's\n   ;; easier to remove tham than to fix them, and they could never have been\n   ;; used successfully in the wild. [2023/12/11:rpg]\n   ;; #:get-optimization-settings #:proclaim-optimization-settings #:with-optimization-settings\n   #:call-with-muffled-compiler-conditions #:with-muffled-compiler-conditions\n   #:call-with-muffled-loader-conditions #:with-muffled-loader-conditions\n   #:reify-simple-sexp #:unreify-simple-sexp\n   #:reify-deferred-warnings #:unreify-deferred-warnings\n   #:reset-deferred-warnings #:save-deferred-warnings #:check-deferred-warnings\n   #:with-saved-deferred-warnings #:warnings-file-p #:warnings-file-type #:*warnings-file-type*\n   #:enable-deferred-warnings-check #:disable-deferred-warnings-check\n   #:current-lisp-file-pathname #:load-pathname\n   #:lispize-pathname #:compile-file-type #:call-around-hook\n   #:compile-file* #:compile-file-pathname* #:*compile-check*\n   #:load* #:load-from-string #:combine-fasls)\n  (:intern #:defaults #:failure-p #:warnings-p #:s #:y #:body))\n(in-package :uiop/lisp-build)\n\n(with-upgradability ()\n  (defvar *compile-file-warnings-behaviour*\n    (or #+clisp :ignore :warn)\n    \"How should ASDF react if it encounters a warning when compiling a file?\nValid values are :error, :warn, and :ignore.\")\n\n  (defvar *compile-file-failure-behaviour*\n    (or #+(or mkcl sbcl) :error #+clisp :ignore :warn)\n    \"How should ASDF react if it encounters a failure (per the ANSI spec of COMPILE-FILE)\nwhen compiling a file, which includes any non-style-warning warning.\nValid values are :error, :warn, and :ignore.\nNote that ASDF ALWAYS raises an error if it fails to create an output file when compiling.\")\n\n  (defvar *base-build-directory* nil\n    \"When set to a non-null value, it should be an absolute directory pathname,\nwhich will serve as the *DEFAULT-PATHNAME-DEFAULTS* around a COMPILE-FILE,\nwhat more while the input-file is shortened if possible to ENOUGH-PATHNAME relative to it.\nThis can help you produce more deterministic output for FASLs.\"))\n\n;;; Optimization settings\n#+ignore\n(with-upgradability ()\n  (defvar *optimization-settings* nil\n    \"Optimization settings to be used by PROCLAIM-OPTIMIZATION-SETTINGS\")\n  (defvar *previous-optimization-settings* nil\n    \"Optimization settings saved by PROCLAIM-OPTIMIZATION-SETTINGS\")\n  (defparameter +optimization-variables+\n    ;; TODO: allegro genera corman mcl\n    (or #+(or abcl xcl) '(system::*speed* system::*space* system::*safety* system::*debug*)\n        #+clisp '() ;; system::*optimize* is a constant hash-table! (with non-constant contents)\n        #+clozure '(ccl::*nx-speed* ccl::*nx-space* ccl::*nx-safety*\n                    ccl::*nx-debug* ccl::*nx-cspeed*)\n        #+(or cmucl scl) '(c::*default-cookie*)\n        #+clasp nil\n        #+ecl (unless (use-ecl-byte-compiler-p) '(c::*speed* c::*space* c::*safety* c::*debug*))\n        #+gcl '(compiler::*speed* compiler::*space* compiler::*compiler-new-safety* compiler::*debug*)\n        #+lispworks '(compiler::*optimization-level*)\n        #+mkcl '(si::*speed* si::*space* si::*safety* si::*debug*)\n        #+sbcl '(sb-c::*policy*)))\n  (defun get-optimization-settings ()\n    \"Get current compiler optimization settings, ready to PROCLAIM again\"\n    #-(or abcl allegro clasp clisp clozure cmucl ecl lispworks mkcl sbcl scl xcl)\n    (warn \"~S does not support ~S. Please help me fix that.\"\n          'get-optimization-settings (implementation-type))\n    #+clasp (cleavir-env:optimize (cleavir-env:optimize-info CLASP-CLEAVIR:*CLASP-ENV*))\n    #+(or abcl allegro clisp clozure cmucl ecl lispworks mkcl sbcl scl xcl)\n    (let ((settings '(speed space safety debug compilation-speed #+(or cmucl scl) c::brevity)))\n      #.`(loop #+(or allegro clozure)\n               ,@'(:with info = #+allegro (sys:declaration-information 'optimize)\n                   #+clozure (ccl:declaration-information 'optimize nil))\n               :for x :in settings\n               ,@(or #+(or abcl clasp ecl gcl mkcl xcl) '(:for v :in +optimization-variables+))\n               :for y = (or #+(or allegro clozure) (second (assoc x info)) ; normalize order\n                            #+clisp (gethash x system::*optimize* 1)\n                            #+(or abcl ecl mkcl xcl) (symbol-value v)\n                            #+(or cmucl scl) (slot-value c::*default-cookie*\n                                                       (case x (compilation-speed 'c::cspeed)\n                                                             (otherwise x)))\n                            #+lispworks (slot-value compiler::*optimization-level* x)\n                            #+sbcl (sb-c::policy-quality sb-c::*policy* x))\n               :when y :collect (list x y))))\n  (defun proclaim-optimization-settings ()\n    \"Proclaim the optimization settings in *OPTIMIZATION-SETTINGS*\"\n    (proclaim `(optimize ,@*optimization-settings*))\n    (let ((settings (get-optimization-settings)))\n      (unless (equal *previous-optimization-settings* settings)\n        (setf *previous-optimization-settings* settings))))\n  (defmacro with-optimization-settings ((&optional (settings *optimization-settings*)) &body body)\n    #+(or allegro clasp clisp)\n    (let ((previous-settings (gensym \"PREVIOUS-SETTINGS\"))\n          (reset-settings (gensym \"RESET-SETTINGS\")))\n      `(let* ((,previous-settings (get-optimization-settings))\n              (,reset-settings #+clasp (reverse ,previous-settings) #-clasp ,previous-settings))\n         ,@(when settings `((proclaim `(optimize ,@,settings))))\n         (unwind-protect (progn ,@body)\n           (proclaim `(optimize ,@,reset-settings)))))\n    #-(or allegro clasp clisp)\n    `(let ,(loop :for v :in +optimization-variables+ :collect `(,v ,v))\n       ,@(when settings `((proclaim '(optimize ,@settings))))\n       ,@body)))\n\n\n;;; Condition control\n(with-upgradability ()\n  #+sbcl\n  (progn\n    (defun sb-grovel-unknown-constant-condition-p (c)\n      \"Detect SB-GROVEL unknown-constant conditions on older versions of SBCL\"\n      (ignore-errors\n       (and (typep c 'sb-int:simple-style-warning)\n            (string-enclosed-p\n             \"Couldn't grovel for \"\n             (simple-condition-format-control c)\n             \" (unknown to the C compiler).\"))))\n    (deftype sb-grovel-unknown-constant-condition ()\n      '(and style-warning (satisfies sb-grovel-unknown-constant-condition-p))))\n\n  (defvar *usual-uninteresting-conditions*\n    (append\n     ;;#+clozure '(ccl:compiler-warning)\n     #+cmucl '(\"Deleting unreachable code.\")\n     #+lispworks '(\"~S being redefined in ~A (previously in ~A).\"\n                   \"~S defined more than once in ~A.\") ;; lispworks gets confused by eval-when.\n     #+sbcl\n     '(sb-c::simple-compiler-note\n       \"&OPTIONAL and &KEY found in the same lambda list: ~S\"\n       sb-kernel:undefined-alien-style-warning\n       sb-grovel-unknown-constant-condition ; defined above.\n       sb-ext:implicit-generic-function-warning ;; Controversial.\n       sb-int:package-at-variance\n       sb-kernel:uninteresting-redefinition\n       ;; BEWARE: the below four are controversial to include here.\n       sb-kernel:redefinition-with-defun\n       sb-kernel:redefinition-with-defgeneric\n       sb-kernel:redefinition-with-defmethod\n       sb-kernel::redefinition-with-defmacro) ; not exported by old SBCLs\n     #+sbcl\n     (let ((condition (find-symbol* '#:lexical-environment-too-complex :sb-kernel nil)))\n       (when condition\n         (list condition)))\n     '(\"No generic function ~S present when encountering macroexpansion of defmethod. Assuming it will be an instance of standard-generic-function.\")) ;; from closer2mop\n    \"A suggested value to which to set or bind *uninteresting-conditions*.\")\n\n  (defvar *uninteresting-conditions* '()\n    \"Conditions that may be skipped while compiling or loading Lisp code.\")\n  (defvar *uninteresting-compiler-conditions* '()\n    \"Additional conditions that may be skipped while compiling Lisp code.\")\n  (defvar *uninteresting-loader-conditions*\n    (append\n     '(\"Overwriting already existing readtable ~S.\" ;; from named-readtables\n       #(#:finalizers-off-warning :asdf-finalizers)) ;; from asdf-finalizers\n     #+clisp '(clos::simple-gf-replacing-method-warning))\n    \"Additional conditions that may be skipped while loading Lisp code.\"))\n\n;;;; ----- Filtering conditions while building -----\n(with-upgradability ()\n  (defun call-with-muffled-compiler-conditions (thunk)\n    \"Call given THUNK in a context where uninteresting conditions and compiler conditions are muffled\"\n    (call-with-muffled-conditions\n     thunk (append *uninteresting-conditions* *uninteresting-compiler-conditions*)))\n  (defmacro with-muffled-compiler-conditions ((&optional) &body body)\n    \"Trivial syntax for CALL-WITH-MUFFLED-COMPILER-CONDITIONS\"\n    `(call-with-muffled-compiler-conditions #'(lambda () ,@body)))\n  (defun call-with-muffled-loader-conditions (thunk)\n    \"Call given THUNK in a context where uninteresting conditions and loader conditions are muffled\"\n    (call-with-muffled-conditions\n     thunk (append *uninteresting-conditions* *uninteresting-loader-conditions*)))\n  (defmacro with-muffled-loader-conditions ((&optional) &body body)\n    \"Trivial syntax for CALL-WITH-MUFFLED-LOADER-CONDITIONS\"\n    `(call-with-muffled-loader-conditions #'(lambda () ,@body))))\n\n\n;;;; Handle warnings and failures\n(with-upgradability ()\n  (define-condition compile-condition (condition)\n    ((context-format\n      :initform nil :reader compile-condition-context-format :initarg :context-format)\n     (context-arguments\n      :initform nil :reader compile-condition-context-arguments :initarg :context-arguments)\n     (description\n      :initform nil :reader compile-condition-description :initarg :description))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<~A~@[ while ~?~]~@:>\")\n                       (or (compile-condition-description c) (type-of c))\n                       (compile-condition-context-format c)\n                       (compile-condition-context-arguments c)))))\n  (define-condition compile-file-error (compile-condition error) ())\n  (define-condition compile-warned-warning (compile-condition warning) ())\n  (define-condition compile-warned-error (compile-condition error) ())\n  (define-condition compile-failed-warning (compile-condition warning) ())\n  (define-condition compile-failed-error (compile-condition error) ())\n\n  (defun check-lisp-compile-warnings (warnings-p failure-p\n                                                  &optional context-format context-arguments)\n    \"Given the warnings or failures as resulted from COMPILE-FILE or checking deferred warnings,\nraise an error or warning as appropriate\"\n    (when failure-p\n      (case *compile-file-failure-behaviour*\n        (:warn (warn 'compile-failed-warning\n                     :description \"Lisp compilation failed\"\n                     :context-format context-format\n                     :context-arguments context-arguments))\n        (:error (error 'compile-failed-error\n                       :description \"Lisp compilation failed\"\n                       :context-format context-format\n                       :context-arguments context-arguments))\n        (:ignore nil)))\n    (when warnings-p\n      (case *compile-file-warnings-behaviour*\n        (:warn (warn 'compile-warned-warning\n                     :description \"Lisp compilation had style-warnings\"\n                     :context-format context-format\n                     :context-arguments context-arguments))\n        (:error (error 'compile-warned-error\n                       :description \"Lisp compilation had style-warnings\"\n                       :context-format context-format\n                       :context-arguments context-arguments))\n        (:ignore nil))))\n\n  (defun check-lisp-compile-results (output warnings-p failure-p\n                                             &optional context-format context-arguments)\n    \"Given the results of COMPILE-FILE, raise an error or warning as appropriate\"\n    (unless output\n      (error 'compile-file-error :context-format context-format :context-arguments context-arguments))\n    (check-lisp-compile-warnings warnings-p failure-p context-format context-arguments)))\n\n\n;;;; Deferred-warnings treatment, originally implemented by Douglas Katzman.\n;;;\n;;; To support an implementation, three functions must be implemented:\n;;; reify-deferred-warnings unreify-deferred-warnings reset-deferred-warnings\n;;; See their respective docstrings.\n(with-upgradability ()\n  (defun reify-simple-sexp (sexp)\n    \"Given a simple SEXP, return a representation of it as a portable SEXP.\nSimple means made of symbols, numbers, characters, simple-strings, pathnames, cons cells.\"\n    (etypecase sexp\n      (symbol (reify-symbol sexp))\n      ((or number character simple-string pathname) sexp)\n      (cons (cons (reify-simple-sexp (car sexp)) (reify-simple-sexp (cdr sexp))))\n      (simple-vector (vector (mapcar 'reify-simple-sexp (coerce sexp 'list))))))\n\n  (defun unreify-simple-sexp (sexp)\n    \"Given the portable output of REIFY-SIMPLE-SEXP, return the simple SEXP it represents\"\n    (etypecase sexp\n      ((or symbol number character simple-string pathname) sexp)\n      (cons (cons (unreify-simple-sexp (car sexp)) (unreify-simple-sexp (cdr sexp))))\n      ((simple-vector 2) (unreify-symbol sexp))\n      ((simple-vector 1) (coerce (mapcar 'unreify-simple-sexp (aref sexp 0)) 'vector))))\n\n  #+clozure\n  (progn\n    (defun reify-source-note (source-note)\n      (when source-note\n        (with-accessors ((source ccl::source-note-source) (filename ccl:source-note-filename)\n                         (start-pos ccl:source-note-start-pos) (end-pos ccl:source-note-end-pos)) source-note\n          (declare (ignorable source))\n          (list :filename filename :start-pos start-pos :end-pos end-pos\n                #|:source (reify-source-note source)|#))))\n    (defun unreify-source-note (source-note)\n      (when source-note\n        (destructuring-bind (&key filename start-pos end-pos source) source-note\n          (ccl::make-source-note :filename filename :start-pos start-pos :end-pos end-pos\n                                 :source (unreify-source-note source)))))\n    (defun unsymbolify-function-name (name)\n      (if-let (setfed (gethash name ccl::%setf-function-name-inverses%))\n        `(setf ,setfed)\n        name))\n    (defun symbolify-function-name (name)\n      (if (and (consp name) (eq (first name) 'setf))\n          (let ((setfed (second name)))\n            (gethash setfed ccl::%setf-function-names%))\n          name))\n    (defun reify-function-name (function-name)\n      (let ((name (or (first function-name) ;; defun: extract the name\n                      (let ((sec (second function-name)))\n                        (or (and (atom sec) sec) ; scoped method: drop scope\n                            (first sec)))))) ; method: keep gf name, drop method specializers\n        (list name)))\n    (defun unreify-function-name (function-name)\n      function-name)\n    (defun nullify-non-literals (sexp)\n      (typecase sexp\n        ((or number character simple-string symbol pathname) sexp)\n        (cons (cons (nullify-non-literals (car sexp))\n                    (nullify-non-literals (cdr sexp))))\n        (t nil)))\n    (defun reify-deferred-warning (deferred-warning)\n      (with-accessors ((warning-type ccl::compiler-warning-warning-type)\n                       (args ccl::compiler-warning-args)\n                       (source-note ccl:compiler-warning-source-note)\n                       (function-name ccl:compiler-warning-function-name)) deferred-warning\n        (list :warning-type warning-type :function-name (reify-function-name function-name)\n              :source-note (reify-source-note source-note)\n              :args (destructuring-bind (fun &rest more)\n                        args\n                      (cons (unsymbolify-function-name fun)\n                            (nullify-non-literals more))))))\n    (defun unreify-deferred-warning (reified-deferred-warning)\n      (destructuring-bind (&key warning-type function-name source-note args)\n          reified-deferred-warning\n        (make-condition (or (cdr (ccl::assq warning-type ccl::*compiler-whining-conditions*))\n                            'ccl::compiler-warning)\n                        :function-name (unreify-function-name function-name)\n                        :source-note (unreify-source-note source-note)\n                        :warning-type warning-type\n                        :args (destructuring-bind (fun . more) args\n                                (cons (symbolify-function-name fun) more))))))\n  #+(or cmucl scl)\n  (defun reify-undefined-warning (warning)\n    ;; Extracting undefined-warnings from the compilation-unit\n    ;; To be passed through the above reify/unreify link, it must be a \"simple-sexp\"\n    (list*\n     (c::undefined-warning-kind warning)\n     (c::undefined-warning-name warning)\n     (c::undefined-warning-count warning)\n     (mapcar\n      #'(lambda (frob)\n          ;; the lexenv slot can be ignored for reporting purposes\n          `(:enclosing-source ,(c::compiler-error-context-enclosing-source frob)\n            :source ,(c::compiler-error-context-source frob)\n            :original-source ,(c::compiler-error-context-original-source frob)\n            :context ,(c::compiler-error-context-context frob)\n            :file-name ,(c::compiler-error-context-file-name frob) ; a pathname\n            :file-position ,(c::compiler-error-context-file-position frob) ; an integer\n            :original-source-path ,(c::compiler-error-context-original-source-path frob)))\n      (c::undefined-warning-warnings warning))))\n\n  #+sbcl\n  (defun reify-undefined-warning (warning)\n    ;; Extracting undefined-warnings from the compilation-unit\n    ;; To be passed through the above reify/unreify link, it must be a \"simple-sexp\"\n    (list*\n     (sb-c::undefined-warning-kind warning)\n     (sb-c::undefined-warning-name warning)\n     (sb-c::undefined-warning-count warning)\n     ;; the COMPILER-ERROR-CONTEXT struct has changed in SBCL, which means how we\n     ;; handle deferred warnings must change... TODO: when enough time has\n     ;; gone by, just assume all versions of SBCL are adequately\n     ;; up-to-date, and cut this material.[2018/05/30:rpg]\n     (mapcar\n      #'(lambda (frob)\n          ;; the lexenv slot can be ignored for reporting purposes\n          `(\n            #- #.(uiop/utility:symbol-test-to-feature-expression '#:compiler-error-context-%source '#:sb-c)\n            ,@`(:enclosing-source\n                ,(sb-c::compiler-error-context-enclosing-source frob)\n                :source\n                ,(sb-c::compiler-error-context-source frob)\n                :original-source\n                ,(sb-c::compiler-error-context-original-source frob))\n            #+ #.(uiop/utility:symbol-test-to-feature-expression '#:compiler-error-context-%source '#:sb-c)\n            ,@ `(:%enclosing-source\n                 ,(sb-c::compiler-error-context-enclosing-source frob)\n                 :%source\n                 ,(sb-c::compiler-error-context-source frob)\n                 :original-form\n                 ,(sb-c::compiler-error-context-original-form frob))\n            :context ,(sb-c::compiler-error-context-context frob)\n            :file-name ,(sb-c::compiler-error-context-file-name frob) ; a pathname\n            :file-position ,(sb-c::compiler-error-context-file-position frob) ; an integer\n            :original-source-path ,(sb-c::compiler-error-context-original-source-path frob)))\n      (sb-c::undefined-warning-warnings warning))))\n\n  (defun reify-deferred-warnings ()\n    \"return a portable S-expression, portably readable and writeable in any Common Lisp implementation\nusing READ within a WITH-SAFE-IO-SYNTAX, that represents the warnings currently deferred by\nWITH-COMPILATION-UNIT. One of three functions required for deferred-warnings support in ASDF.\"\n    #+allegro\n    (list :functions-defined\n          #+(and allegro-version>= (version>= 11))\n          (let (functions-defined)\n            (maphash #'(lambda (k v)\n                         (declare (ignore v))\n                         (push k functions-defined))\n                     excl::.functions-defined.)\n            functions-defined)\n          #+(and allegro-version>= (not (version>= 11)))\n          excl::.functions-defined.\n          :functions-called excl::.functions-called.)\n    #+clozure\n    (mapcar 'reify-deferred-warning\n            (if-let (dw ccl::*outstanding-deferred-warnings*)\n              (let ((mdw (ccl::ensure-merged-deferred-warnings dw)))\n                (ccl::deferred-warnings.warnings mdw))))\n    #+(or cmucl scl)\n    (when lisp::*in-compilation-unit*\n      ;; Try to send nothing through the pipe if nothing needs to be accumulated\n      `(,@(when c::*undefined-warnings*\n            `((c::*undefined-warnings*\n               ,@(mapcar #'reify-undefined-warning c::*undefined-warnings*))))\n        ,@(loop :for what :in '(c::*compiler-error-count*\n                                c::*compiler-warning-count*\n                                c::*compiler-note-count*)\n                :for value = (symbol-value what)\n                :when (plusp value)\n                  :collect `(,what . ,value))))\n    #+sbcl\n    (when sb-c::*in-compilation-unit*\n      ;; Try to send nothing through the pipe if nothing needs to be accumulated\n      `(,@(when sb-c::*undefined-warnings*\n            `((sb-c::*undefined-warnings*\n               ,@(mapcar #'reify-undefined-warning sb-c::*undefined-warnings*))))\n        ,@(loop :for what :in '(sb-c::*aborted-compilation-unit-count*\n                                sb-c::*compiler-error-count*\n                                sb-c::*compiler-warning-count*\n                                sb-c::*compiler-style-warning-count*\n                                sb-c::*compiler-note-count*)\n                :for value = (symbol-value what)\n                :when (plusp value)\n                  :collect `(,what . ,value)))))\n\n  (defun unreify-deferred-warnings (reified-deferred-warnings)\n    \"given a S-expression created by REIFY-DEFERRED-WARNINGS, reinstantiate the corresponding\ndeferred warnings as to be handled at the end of the current WITH-COMPILATION-UNIT.\nHandle any warning that has been resolved already,\nsuch as an undefined function that has been defined since.\nOne of three functions required for deferred-warnings support in ASDF.\"\n    (declare (ignorable reified-deferred-warnings))\n    #+allegro\n    (destructuring-bind (&key functions-defined functions-called)\n        reified-deferred-warnings\n      (setf #+(and allegro-version>= (not (version>= 11)))\n            excl::.functions-defined.\n            #+(and allegro-version>= (not (version>= 11)))\n            (append functions-defined excl::.functions-defined.)\n            excl::.functions-called.\n            (append functions-called excl::.functions-called.))\n      #+(and allegro-version>= (version>= 11))\n      ;; in ACL >= 11, instead of adding defined functions to a list,\n      ;; we insert them into a no-values hash-table.\n      (mapc #'(lambda (fn)\n                (excl:puthash-key fn excl::.functions-defined.))\n            functions-defined))\n    #+clozure\n    (let ((dw (or ccl::*outstanding-deferred-warnings*\n                  (setf ccl::*outstanding-deferred-warnings* (ccl::%defer-warnings t)))))\n      (appendf (ccl::deferred-warnings.warnings dw)\n               (mapcar 'unreify-deferred-warning reified-deferred-warnings)))\n    #+(or cmucl scl)\n    (dolist (item reified-deferred-warnings)\n      ;; Each item is (symbol . adjustment) where the adjustment depends on the symbol.\n      ;; For *undefined-warnings*, the adjustment is a list of initargs.\n      ;; For everything else, it's an integer.\n      (destructuring-bind (symbol . adjustment) item\n        (case symbol\n          ((c::*undefined-warnings*)\n           (setf c::*undefined-warnings*\n                 (nconc (mapcan\n                         #'(lambda (stuff)\n                             (destructuring-bind (kind name count . rest) stuff\n                               (unless (case kind (:function (fboundp name)))\n                                 (list\n                                  (c::make-undefined-warning\n                                   :name name\n                                   :kind kind\n                                   :count count\n                                   :warnings\n                                   (mapcar #'(lambda (x)\n                                               (apply #'c::make-compiler-error-context x))\n                                           rest))))))\n                         adjustment)\n                        c::*undefined-warnings*)))\n          (otherwise\n           (set symbol (+ (symbol-value symbol) adjustment))))))\n    #+sbcl\n    (dolist (item reified-deferred-warnings)\n      ;; Each item is (symbol . adjustment) where the adjustment depends on the symbol.\n      ;; For *undefined-warnings*, the adjustment is a list of initargs.\n      ;; For everything else, it's an integer.\n      (destructuring-bind (symbol . adjustment) item\n        (case symbol\n          ((sb-c::*undefined-warnings*)\n           (setf sb-c::*undefined-warnings*\n                 (nconc (mapcan\n                         #'(lambda (stuff)\n                             (destructuring-bind (kind name count . rest) stuff\n                               (unless (case kind (:function (fboundp name)))\n                                 (list\n                                  (sb-c::make-undefined-warning\n                                   :name name\n                                   :kind kind\n                                   :count count\n                                   :warnings\n                                   (mapcar #'(lambda (x)\n                                               (apply #'sb-c::make-compiler-error-context x))\n                                           rest))))))\n                         adjustment)\n                        sb-c::*undefined-warnings*)))\n          (otherwise\n           (set symbol (+ (symbol-value symbol) adjustment)))))))\n\n  (defun reset-deferred-warnings ()\n    \"Reset the set of deferred warnings to be handled at the end of the current WITH-COMPILATION-UNIT.\nOne of three functions required for deferred-warnings support in ASDF.\"\n    #+allegro\n    (setf excl::.functions-defined.\n          #+(and allegro-version>= (not (version>= 11)))\n          nil\n          #+(and allegro-version>= (version>= 11))\n          (make-hash-table :test #'equal :values nil)\n          excl::.functions-called. nil)\n    #+clozure\n    (if-let (dw ccl::*outstanding-deferred-warnings*)\n      (let ((mdw (ccl::ensure-merged-deferred-warnings dw)))\n        (setf (ccl::deferred-warnings.warnings mdw) nil)))\n    #+(or cmucl scl)\n    (when lisp::*in-compilation-unit*\n      (setf c::*undefined-warnings* nil\n            c::*compiler-error-count* 0\n            c::*compiler-warning-count* 0\n            c::*compiler-note-count* 0))\n    #+sbcl\n    (when sb-c::*in-compilation-unit*\n      (setf sb-c::*undefined-warnings* nil\n            sb-c::*aborted-compilation-unit-count* 0\n            sb-c::*compiler-error-count* 0\n            sb-c::*compiler-warning-count* 0\n            sb-c::*compiler-style-warning-count* 0\n            sb-c::*compiler-note-count* 0)))\n\n  (defun save-deferred-warnings (warnings-file)\n    \"Save forward reference conditions so they may be issued at a latter time,\npossibly in a different process.\"\n    (with-open-file (s warnings-file :direction :output :if-exists :supersede\n                       :element-type *default-stream-element-type*\n                       :external-format *utf-8-external-format*)\n      (with-safe-io-syntax ()\n        (let ((*read-eval* t))\n          (write (reify-deferred-warnings) :stream s :pretty t :readably t))\n        (terpri s))))\n\n  (defun warnings-file-type (&optional implementation-type)\n    \"The pathname type for warnings files on given IMPLEMENTATION-TYPE,\nwhere NIL designates the current one\"\n    (case (or implementation-type *implementation-type*)\n      ((:acl :allegro) \"allegro-warnings\")\n      ;;((:clisp) \"clisp-warnings\")\n      ((:cmu :cmucl) \"cmucl-warnings\")\n      ((:sbcl) \"sbcl-warnings\")\n      ((:clozure :ccl) \"ccl-warnings\")\n      ((:scl) \"scl-warnings\")))\n\n  (defvar *warnings-file-type* nil\n    \"Pathname type for warnings files, or NIL if disabled\")\n\n  (defun enable-deferred-warnings-check ()\n    \"Enable the saving of deferred warnings\"\n    (setf *warnings-file-type* (warnings-file-type)))\n\n  (defun disable-deferred-warnings-check ()\n    \"Disable the saving of deferred warnings\"\n    (setf *warnings-file-type* nil))\n\n  (defun warnings-file-p (file &optional implementation-type)\n    \"Is FILE a saved warnings file for the given IMPLEMENTATION-TYPE?\nIf that given type is NIL, use the currently configured *WARNINGS-FILE-TYPE* instead.\"\n    (if-let (type (if implementation-type\n                      (warnings-file-type implementation-type)\n                      *warnings-file-type*))\n      (equal (pathname-type file) type)))\n\n  (defun check-deferred-warnings (files &optional context-format context-arguments)\n    \"Given a list of FILES containing deferred warnings saved by CALL-WITH-SAVED-DEFERRED-WARNINGS,\nre-intern and raise any warnings that are still meaningful.\"\n    (let ((file-errors nil)\n          (failure-p nil)\n          (warnings-p nil))\n      (handler-bind\n          ((warning #'(lambda (c)\n                        (setf warnings-p t)\n                        (unless (typep c 'style-warning)\n                          (setf failure-p t)))))\n        (with-compilation-unit (:override t)\n          (reset-deferred-warnings)\n          (dolist (file files)\n            (unreify-deferred-warnings\n             (handler-case\n                 (with-safe-io-syntax ()\n                   (let ((*read-eval* t))\n                     (read-file-form file)))\n               (error (c)\n                 ;;(delete-file-if-exists file) ;; deleting forces rebuild but prevents debugging\n                 (push c file-errors)\n                 nil))))))\n      (dolist (error file-errors) (error error))\n      (check-lisp-compile-warnings\n       (or failure-p warnings-p) failure-p context-format context-arguments)))\n\n  #|\n  Mini-guide to adding support for deferred warnings on an implementation.\n\n  First, look at what such a warning looks like:\n\n  (describe\n  (handler-case\n  (and (eval '(lambda () (some-undefined-function))) nil)\n  (t (c) c)))\n\n  Then you can grep for the condition type in your compiler sources\n  and see how to catch those that have been deferred,\n  and/or read, clear and restore the deferred list.\n\n  Also look at\n  (macroexpand-1 '(with-compilation-unit () foo))\n  |#\n\n  (defun call-with-saved-deferred-warnings (thunk warnings-file &key source-namestring)\n    \"If WARNINGS-FILE is not nil, record the deferred-warnings around a call to THUNK\nand save those warnings to the given file for latter use,\npossibly in a different process. Otherwise just call THUNK.\"\n    (declare (ignorable source-namestring))\n    (if warnings-file\n        (with-compilation-unit (:override t #+sbcl :source-namestring #+sbcl source-namestring)\n          (unwind-protect\n               (let (#+sbcl (sb-c::*undefined-warnings* nil))\n                 (multiple-value-prog1\n                     (funcall thunk)\n                   (save-deferred-warnings warnings-file)))\n            (reset-deferred-warnings)))\n        (funcall thunk)))\n\n  (defmacro with-saved-deferred-warnings ((warnings-file &key source-namestring) &body body)\n    \"Trivial syntax for CALL-WITH-SAVED-DEFERRED-WARNINGS\"\n    `(call-with-saved-deferred-warnings\n      #'(lambda () ,@body) ,warnings-file :source-namestring ,source-namestring)))\n\n\n;;; from ASDF\n(with-upgradability ()\n  (defun current-lisp-file-pathname ()\n    \"Portably return the PATHNAME of the current Lisp source file being compiled or loaded\"\n    (or *compile-file-pathname* *load-pathname*))\n\n  (defun load-pathname ()\n    \"Portably return the LOAD-PATHNAME of the current source file or fasl.\n    May return a relative pathname.\"\n    *load-pathname*) ;; magic no longer needed for GCL.\n\n  (defun lispize-pathname (input-file)\n    \"From a INPUT-FILE pathname, return a corresponding .lisp source pathname\"\n    (make-pathname :type \"lisp\" :defaults input-file))\n\n  (defun compile-file-type (&rest keys)\n    \"pathname TYPE for lisp FASt Loading files\"\n    (declare (ignorable keys))\n    #-(or clasp ecl mkcl) (load-time-value (pathname-type (compile-file-pathname \"foo.lisp\")))\n    #+(or clasp ecl mkcl) (pathname-type (apply 'compile-file-pathname \"foo\" keys)))\n\n  (defun call-around-hook (hook function)\n    \"Call a HOOK around the execution of FUNCTION\"\n    (call-function (or hook 'funcall) function))\n\n  (defun compile-file-pathname* (input-file &rest keys &key output-file &allow-other-keys)\n    \"Variant of COMPILE-FILE-PATHNAME that works well with COMPILE-FILE*\"\n    (let* ((keys\n             (remove-plist-keys `(#+(or (and allegro (not (version>= 8 2)))) :external-format\n                                    ,@(unless output-file '(:output-file))) keys)))\n      (if (absolute-pathname-p output-file)\n          ;; what cfp should be doing, w/ mp* instead of mp\n          (let* ((type (pathname-type (apply 'compile-file-type keys)))\n                 (defaults (make-pathname\n                            :type type :defaults (merge-pathnames* input-file))))\n            (merge-pathnames* output-file defaults))\n          (funcall *output-translation-function*\n                   (apply 'compile-file-pathname input-file keys)))))\n\n  (defvar *compile-check* nil\n    \"A hook for user-defined compile-time invariants\")\n\n  (defun compile-file* (input-file &rest keys\n                                      &key (compile-check *compile-check*) output-file warnings-file\n                                      #+clisp lib-file #+(or clasp ecl mkcl) object-file #+sbcl emit-cfasl\n                                      &allow-other-keys)\n    \"This function provides a portable wrapper around COMPILE-FILE.\nIt ensures that the OUTPUT-FILE value is only returned and\nthe file only actually created if the compilation was successful,\neven though your implementation may not do that. It also checks an optional\nuser-provided consistency function COMPILE-CHECK to determine success;\nit will call this function if not NIL at the end of the compilation\nwith the arguments sent to COMPILE-FILE*, except with :OUTPUT-FILE TMP-FILE\nwhere TMP-FILE is the name of a temporary output-file.\nIt also checks two flags (with legacy british spelling from ASDF1),\n*COMPILE-FILE-FAILURE-BEHAVIOUR* and *COMPILE-FILE-WARNINGS-BEHAVIOUR*\nwith appropriate implementation-dependent defaults,\nand if a failure (respectively warnings) are reported by COMPILE-FILE,\nit will consider that an error unless the respective behaviour flag\nis one of :SUCCESS :WARN :IGNORE.\nIf WARNINGS-FILE is defined, deferred warnings are saved to that file.\nOn ECL or MKCL, it creates both the linkable object and loadable fasl files.\nOn implementations that erroneously do not recognize standard keyword arguments,\nit will filter them appropriately.\"\n    #+(or clasp ecl)\n    (when (and object-file (equal (compile-file-type) (pathname object-file)))\n      (format t \"Whoa, some funky ASDF upgrade switched ~S calling convention for ~S and ~S~%\"\n              'compile-file* output-file object-file)\n      (rotatef output-file object-file))\n    (let* ((keywords (remove-plist-keys\n                      `(:output-file :compile-check :warnings-file\n                                     #+clisp :lib-file #+(or clasp ecl mkcl) :object-file) keys))\n           (output-file\n             (or output-file\n                 (apply 'compile-file-pathname* input-file :output-file output-file keywords)))\n           (physical-output-file (physicalize-pathname output-file))\n           #+(or clasp ecl)\n           (object-file\n             (unless (use-ecl-byte-compiler-p)\n               (or object-file\n                   #+ecl (compile-file-pathname output-file :type :object)\n                   #+clasp (compile-file-pathname output-file :output-type :object))))\n           #+mkcl\n           (object-file\n             (or object-file\n                 (compile-file-pathname output-file :fasl-p nil)))\n           (tmp-file (tmpize-pathname physical-output-file))\n           #+clasp\n           (tmp-object-file (compile-file-pathname tmp-file :output-type :object))\n           #+sbcl\n           (cfasl-file (etypecase emit-cfasl\n                         (null nil)\n                         ((eql t) (make-pathname :type \"cfasl\" :defaults physical-output-file))\n                         (string (parse-namestring emit-cfasl))\n                         (pathname emit-cfasl)))\n           #+sbcl\n           (tmp-cfasl (when cfasl-file (make-pathname :type \"cfasl\" :defaults tmp-file)))\n           #+clisp\n           (tmp-lib (make-pathname :type \"lib\" :defaults tmp-file)))\n      (multiple-value-bind (output-truename warnings-p failure-p)\n          (with-enough-pathname (input-file :defaults *base-build-directory*)\n            (with-saved-deferred-warnings (warnings-file :source-namestring (namestring input-file))\n              (with-muffled-compiler-conditions ()\n                (or #-(or clasp ecl mkcl)\n                    (let (#+genera (si:*common-lisp-syntax-is-ansi-common-lisp* t))\n                      (apply 'compile-file input-file :output-file tmp-file\n                             #+sbcl (if emit-cfasl (list* :emit-cfasl tmp-cfasl keywords) keywords)\n                             #-sbcl keywords))\n                    #+ecl (apply 'compile-file input-file :output-file\n                                (if object-file\n                                    (list* object-file :system-p t keywords)\n                                    (list* tmp-file keywords)))\n                    #+clasp (apply 'compile-file input-file :output-file\n                                  (if object-file\n                                      (list* tmp-object-file :output-type :object #|:system-p t|# keywords)\n                                      (list* tmp-file keywords)))\n                    #+mkcl (apply 'compile-file input-file\n                                  :output-file object-file :fasl-p nil keywords)))))\n        (cond\n          ((and output-truename\n                (flet ((check-flag (flag behaviour)\n                         (or (not flag) (member behaviour '(:success :warn :ignore)))))\n                  (and (check-flag failure-p *compile-file-failure-behaviour*)\n                       (check-flag warnings-p *compile-file-warnings-behaviour*)))\n                (progn\n                  #+(or clasp ecl mkcl)\n                  (when (and #+(or clasp ecl) object-file)\n                    (setf output-truename\n                          (compiler::build-fasl tmp-file\n                           #+(or clasp ecl) :lisp-files #+mkcl :lisp-object-files (list #+clasp tmp-object-file #-clasp object-file))))\n                  (or (not compile-check)\n                      (apply compile-check input-file\n                             :output-file output-truename\n                             keywords))))\n           (delete-file-if-exists physical-output-file)\n           (when output-truename\n             ;; see CLISP bug 677\n             #+clisp\n             (progn\n               (setf tmp-lib (make-pathname :type \"lib\" :defaults output-truename))\n               (unless lib-file (setf lib-file (make-pathname :type \"lib\" :defaults physical-output-file)))\n               (rename-file-overwriting-target tmp-lib lib-file))\n             #+sbcl (when cfasl-file (rename-file-overwriting-target tmp-cfasl cfasl-file))\n             #+clasp\n             (progn\n               ;;; the following 4 rename-file-overwriting-target better be atomic, but we can't implement this right now\n               #+:target-os-darwin\n               (let ((temp-dwarf (pathname (strcat (namestring output-truename) \".dwarf\")))\n                     (target-dwarf (pathname (strcat (namestring physical-output-file) \".dwarf\"))))\n                 (when (probe-file temp-dwarf)\n                   (rename-file-overwriting-target temp-dwarf target-dwarf)))\n               ;;; need to rename the bc or ll file as well or test-bundle.script fails\n               ;;; They might not exist with parallel compilation\n               (let ((bitcode-src (compile-file-pathname tmp-file :output-type :bitcode))\n                     (bitcode-target (compile-file-pathname physical-output-file :output-type :bitcode)))\n                 (when (probe-file bitcode-src)\n                   (rename-file-overwriting-target bitcode-src bitcode-target)))\n               (rename-file-overwriting-target tmp-object-file object-file))\n             (rename-file-overwriting-target output-truename physical-output-file)\n             (setf output-truename (truename physical-output-file)))\n           #+clasp (delete-file-if-exists tmp-file)\n           #+clisp (progn (delete-file-if-exists tmp-file) ;; this one works around clisp BUG 677\n                          (delete-file-if-exists tmp-lib))) ;; this one is \"normal\" defensive cleanup\n          (t ;; error or failed check\n           (delete-file-if-exists output-truename)\n           #+clisp (delete-file-if-exists tmp-lib)\n           #+sbcl (delete-file-if-exists tmp-cfasl)\n           (setf output-truename nil)))\n        (values output-truename warnings-p failure-p))))\n\n  (defun load* (x &rest keys &key &allow-other-keys)\n    \"Portable wrapper around LOAD that properly handles loading from a stream.\"\n    (with-muffled-loader-conditions ()\n      (let (#+genera (si:*common-lisp-syntax-is-ansi-common-lisp* t))\n        (etypecase x\n          ((or pathname string #-(or allegro clozure genera) stream #+clozure file-stream)\n           (apply 'load x keys))\n          ;; Genera can't load from a string-input-stream\n          ;; ClozureCL 1.6 can only load from file input stream\n          ;; Allegro 5, I don't remember but it must have been broken when I tested.\n          #+(or allegro clozure genera)\n          (stream ;; make do this way\n           (let ((*package* *package*)\n                 (*readtable* *readtable*)\n                 (*load-pathname* nil)\n                 (*load-truename* nil))\n             (eval-input x)))))))\n\n  (defun load-from-string (string)\n    \"Portably read and evaluate forms from a STRING.\"\n    (with-input-from-string (s string) (load* s))))\n\n;;; Links FASLs together\n(with-upgradability ()\n  (defun combine-fasls (inputs output)\n    \"Combine a list of FASLs INPUTS into a single FASL OUTPUT\"\n    #-(or abcl allegro clisp clozure cmucl lispworks sbcl scl xcl)\n    (not-implemented-error 'combine-fasls \"~%inputs: ~S~%output: ~S\" inputs output)\n    #+abcl (funcall 'sys::concatenate-fasls inputs output) ; requires ABCL 1.2.0\n    #+(or allegro clisp cmucl sbcl scl xcl) (concatenate-files inputs output)\n    #+clozure (ccl:fasl-concatenate output inputs :if-exists :supersede)\n    #+lispworks\n    (let (fasls)\n      (unwind-protect\n           (progn\n             (loop :for i :in inputs\n                   :for n :from 1\n                   :for f = (add-pathname-suffix\n                             output (format nil \"-FASL~D\" n))\n                   :do (copy-file i f)\n                       (push f fasls))\n             (ignore-errors (lispworks:delete-system :fasls-to-concatenate))\n             (eval `(scm:defsystem :fasls-to-concatenate\n                      (:default-pathname ,(pathname-directory-pathname output))\n                      :members\n                      ,(loop :for f :in (reverse fasls)\n                             :collect `(,(namestring f) :load-only t))))\n             (scm:concatenate-system output :fasls-to-concatenate :force t))\n        (loop :for f :in fasls :do (ignore-errors (delete-file f)))\n        (ignore-errors (lispworks:delete-system :fasls-to-concatenate))))))\n;;;; -------------------------------------------------------------------------\n;;;; launch-program - semi-portably spawn asynchronous subprocesses\n\n(uiop/package:define-package :uiop/launch-program\n  (:use :uiop/common-lisp :uiop/package :uiop/utility\n   :uiop/pathname :uiop/os :uiop/filesystem :uiop/stream\n   :uiop/version)\n  (:export\n   ;;; Escaping the command invocation madness\n   #:easy-sh-character-p #:escape-sh-token #:escape-sh-command\n   #:escape-windows-token #:escape-windows-command\n   #:escape-shell-token #:escape-shell-command\n   #:escape-token #:escape-command\n\n   ;;; launch-program\n   #:launch-program\n   #:close-streams #:process-alive-p #:terminate-process #:wait-process\n   #:process-info\n   #:process-info-error-output #:process-info-input #:process-info-output #:process-info-pid))\n(in-package :uiop/launch-program)\n\n;;;; ----- Escaping strings for the shell -----\n(with-upgradability ()\n  (defun requires-escaping-p (token &key good-chars bad-chars)\n    \"Does this token require escaping, given the specification of\neither good chars that don't need escaping or bad chars that do need escaping,\nas either a recognizing function or a sequence of characters.\"\n    (some\n     (cond\n       ((and good-chars bad-chars)\n        (parameter-error \"~S: only one of good-chars and bad-chars can be provided\"\n                         'requires-escaping-p))\n       ((typep good-chars 'function)\n        (complement good-chars))\n       ((typep bad-chars 'function)\n        bad-chars)\n       ((and good-chars (typep good-chars 'sequence))\n        #'(lambda (c) (not (find c good-chars))))\n       ((and bad-chars (typep bad-chars 'sequence))\n        #'(lambda (c) (find c bad-chars)))\n       (t (parameter-error \"~S: no good-char criterion\" 'requires-escaping-p)))\n     token))\n\n  (defun escape-token (token &key stream quote good-chars bad-chars escaper)\n    \"Call the ESCAPER function on TOKEN string if it needs escaping as per\nREQUIRES-ESCAPING-P using GOOD-CHARS and BAD-CHARS, otherwise output TOKEN,\nusing STREAM as output (or returning result as a string if NIL)\"\n    (if (requires-escaping-p token :good-chars good-chars :bad-chars bad-chars)\n        (with-output (stream)\n          (apply escaper token stream (when quote `(:quote ,quote))))\n        (output-string token stream)))\n\n  (defun escape-windows-token-within-double-quotes (x &optional s)\n    \"Escape a string token X within double-quotes\nfor use within a MS Windows command-line, outputing to S.\"\n    (labels ((issue (c) (princ c s))\n             (issue-backslash (n) (loop :repeat n :do (issue #\\\\))))\n      (loop\n        :initially (issue #\\\") :finally (issue #\\\")\n        :with l = (length x) :with i = 0\n        :for i+1 = (1+ i) :while (< i l) :do\n          (case (char x i)\n            ((#\\\") (issue-backslash 1) (issue #\\\") (setf i i+1))\n            ((#\\\\)\n             (let* ((j (and (< i+1 l) (position-if-not\n                                       #'(lambda (c) (eql c #\\\\)) x :start i+1)))\n                    (n (- (or j l) i)))\n               (cond\n                 ((null j)\n                  (issue-backslash (* 2 n)) (setf i l))\n                 ((and (< j l) (eql (char x j) #\\\"))\n                  (issue-backslash (1+ (* 2 n))) (issue #\\\") (setf i (1+ j)))\n                 (t\n                  (issue-backslash n) (setf i j)))))\n            (otherwise\n             (issue (char x i)) (setf i i+1))))))\n\n  (defun easy-windows-character-p (x)\n    \"Is X an \\\"easy\\\" character that does not require quoting by the shell?\"\n    (or (alphanumericp x) (find x \"+-_.,@:/=\")))\n\n  (defun escape-windows-token (token &optional s)\n    \"Escape a string TOKEN within double-quotes if needed\nfor use within a MS Windows command-line, outputing to S.\"\n    (escape-token token :stream s :good-chars #'easy-windows-character-p :quote nil\n                        :escaper 'escape-windows-token-within-double-quotes))\n\n  (defun escape-sh-token-within-double-quotes (x s &key (quote t))\n    \"Escape a string TOKEN within double-quotes\nfor use within a POSIX Bourne shell, outputing to S;\nomit the outer double-quotes if key argument :QUOTE is NIL\"\n    (when quote (princ #\\\" s))\n    (loop :for c :across x :do\n      (when (find c \"$`\\\\\\\"\") (princ #\\\\ s))\n      (princ c s))\n    (when quote (princ #\\\" s)))\n\n  (defun easy-sh-character-p (x)\n    \"Is X an \\\"easy\\\" character that does not require quoting by the shell?\"\n    (or (alphanumericp x) (find x \"+-_.,%@:/=\")))\n\n  (defun escape-sh-token (token &optional s)\n    \"Escape a string TOKEN within double-quotes if needed\nfor use within a POSIX Bourne shell, outputing to S.\"\n    (escape-token token :stream s :quote #\\\" :good-chars #'easy-sh-character-p\n                        :escaper 'escape-sh-token-within-double-quotes))\n\n  (defun escape-shell-token (token &optional s)\n    \"Escape a token for the current operating system shell\"\n    (os-cond\n      ((os-unix-p) (escape-sh-token token s))\n      ((os-windows-p) (escape-windows-token token s))))\n\n  (defun escape-command (command &optional s\n                                  (escaper 'escape-shell-token))\n    \"Given a COMMAND as a list of tokens, return a string of the\nspaced, escaped tokens, using ESCAPER to escape.\"\n    (etypecase command\n      (string (output-string command s))\n      (list (with-output (s)\n              (loop :for first = t :then nil :for token :in command :do\n                (unless first (princ #\\space s))\n                (funcall escaper token s))))))\n\n  (defun escape-windows-command (command &optional s)\n    \"Escape a list of command-line arguments into a string suitable for parsing\nby CommandLineToArgv in MS Windows\"\n    ;; http://msdn.microsoft.com/en-us/library/bb776391(v=vs.85).aspx\n    ;; http://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.85).aspx\n    (escape-command command s 'escape-windows-token))\n\n  (defun escape-sh-command (command &optional s)\n    \"Escape a list of command-line arguments into a string suitable for parsing\nby /bin/sh in POSIX\"\n    (escape-command command s 'escape-sh-token))\n\n  (defun escape-shell-command (command &optional stream)\n    \"Escape a command for the current operating system's shell\"\n    (escape-command command stream 'escape-shell-token)))\n\n\n(with-upgradability ()\n  ;;; Internal helpers for run-program\n  (defun %normalize-io-specifier (specifier &optional role)\n    \"Normalizes a portable I/O specifier for LAUNCH-PROGRAM into an implementation-dependent\nargument to pass to the internal RUN-PROGRAM\"\n    (declare (ignorable role))\n    (typecase specifier\n      (null (or #+(or allegro lispworks) (null-device-pathname)))\n      (string (parse-native-namestring specifier))\n      (pathname specifier)\n      (stream specifier)\n      ((eql :stream) :stream)\n      ((eql :interactive)\n       #+(or allegro lispworks) nil\n       #+clisp :terminal\n       #+(or abcl clasp clozure cmucl ecl mkcl sbcl scl) t\n       #-(or abcl clasp clozure cmucl ecl mkcl sbcl scl allegro lispworks clisp)\n       (not-implemented-error :interactive-output\n                              \"On this lisp implementation, cannot interpret ~a value of ~a\"\n                              specifier role))\n      ((eql :output)\n       (cond ((eq role :error-output)\n              #+(or abcl allegro clasp clozure cmucl ecl lispworks mkcl sbcl scl)\n              :output\n              #-(or abcl allegro clasp clozure cmucl ecl lispworks mkcl sbcl scl)\n              (not-implemented-error :error-output-redirect\n                                     \"Can't send ~a to ~a on this lisp implementation.\"\n                                     role specifier))\n             (t (parameter-error \"~S IO specifier invalid for ~S\" specifier role))))\n      ((eql t)\n       #+ (or lispworks abcl)\n       (not-implemented-error :interactive-output\n                              \"On this lisp implementation, cannot interpret ~a value of ~a\"\n                              specifier role)\n       #- (or lispworks abcl)\n       (cond ((eq role :error-output) *error-output*)\n             ((eq role :output) #+lispworks *terminal-io* #-lispworks *standard-output*)\n             ((eq role :input) *standard-input*)))\n      (otherwise\n       (parameter-error \"Incorrect I/O specifier ~S for ~S\"\n                        specifier role))))\n\n  (defun %interactivep (input output error-output)\n    (member :interactive (list input output error-output)))\n\n  (defun %signal-to-exit-code (signum)\n    (+ 128 signum))\n\n  (defun %code-to-status (exit-code signal-code)\n    (cond ((null exit-code) :running)\n          ((null signal-code) (values :exited exit-code))\n          (t (values :signaled signal-code))))\n\n  #+mkcl\n  (defun %mkcl-signal-to-number (signal)\n    (require :mk-unix)\n    (symbol-value (find-symbol signal :mk-unix)))\n\n  (defclass process-info ()\n    (;; The process field is highly platform-, implementation-, and\n     ;; even version-dependent.\n     ;; Prior to LispWorks 7, the only information that\n     ;; `sys:run-shell-command` with `:wait nil` was certain to return\n     ;; is a PID (e.g. when all streams are nil), hence we stored it\n     ;; and used `sys:pid-exit-status` to obtain an exit status\n     ;; later. That is still what we do.\n     ;; From LispWorks 7 on, if `sys:run-shell-command` does not\n     ;; return a proper stream, we are instead given a dummy stream.\n     ;; We can thus always store a stream and use\n     ;; `sys:pipe-exit-status` to obtain an exit status later.\n     ;; The advantage of dealing with streams instead of PID is the\n     ;; availability of functions like `sys:pipe-kill-process`.\n     (process :initform nil)\n     (input-stream :initform nil)\n     (output-stream :initform nil)\n     (bidir-stream :initform nil)\n     (error-output-stream :initform nil)\n     ;; For backward-compatibility, to maintain the property (zerop\n     ;; exit-code) <-> success, an exit in response to a signal is\n     ;; encoded as 128+signum.\n     (exit-code :initform nil)\n     ;; If the platform allows it, distinguish exiting with a code\n     ;; >128 from exiting in response to a signal by setting this code\n     (signal-code :initform nil))\n    (:documentation \"This class should be treated as opaque by programmers, except for the\nexported PROCESS-INFO-* functions.  It should never be directly instantiated by\nMAKE-INSTANCE. Primarily, it is being made available to enable type-checking.\"))\n\n;;;---------------------------------------------------------------------------\n;;; The following two helper functions take care of handling the IF-EXISTS and\n;;; IF-DOES-NOT-EXIST arguments for RUN-PROGRAM. In particular, they process the\n;;; :ERROR, :APPEND, and :SUPERSEDE arguments *here*, allowing the master\n;;; function to treat input and output files unconditionally for reading and\n;;; writing.\n;;;---------------------------------------------------------------------------\n\n  (defun %handle-if-exists (file if-exists)\n    (when (or (stringp file) (pathnamep file))\n      (ecase if-exists\n        ((:append :supersede :error)\n         (with-open-file (dummy file :direction :output :if-exists if-exists)\n           (declare (ignorable dummy)))))))\n\n  (defun %handle-if-does-not-exist (file if-does-not-exist)\n    (when (or (stringp file) (pathnamep file))\n      (ecase if-does-not-exist\n        ((:create :error)\n         (with-open-file (dummy file :direction :probe\n                                :if-does-not-exist if-does-not-exist)\n           (declare (ignorable dummy)))))))\n\n  (defun process-info-error-output (process-info)\n    (slot-value process-info 'error-output-stream))\n  (defun process-info-input (process-info)\n    (or (slot-value process-info 'bidir-stream)\n        (slot-value process-info 'input-stream)))\n  (defun process-info-output (process-info)\n    (or (slot-value process-info 'bidir-stream)\n        (slot-value process-info 'output-stream)))\n\n  (defun process-info-pid (process-info)\n    (let ((process (slot-value process-info 'process)))\n      (declare (ignorable process))\n      #+abcl (symbol-call :sys :process-pid process)\n      #+allegro process\n      #+clasp (if (find-symbol* '#:external-process-pid :ext nil)\n                  (symbol-call :ext '#:external-process-pid process)\n                  (not-implemented-error 'process-info-pid))\n      #+clozure (ccl:external-process-id process)\n      #+ecl (ext:external-process-pid process)\n      #+(or cmucl scl) (ext:process-pid process)\n      #+lispworks7+ (sys:pipe-pid process)\n      #+(and lispworks (not lispworks7+)) process\n      #+mkcl (mkcl:process-id process)\n      #+sbcl (sb-ext:process-pid process)\n      #-(or abcl allegro clasp clozure cmucl ecl mkcl lispworks sbcl scl)\n      (not-implemented-error 'process-info-pid)))\n\n  (defun %process-status (process-info)\n    (if-let (exit-code (slot-value process-info 'exit-code))\n      (return-from %process-status\n        (if-let (signal-code (slot-value process-info 'signal-code))\n          (values :signaled signal-code)\n          (values :exited exit-code))))\n    #-(or allegro clasp clozure cmucl ecl lispworks mkcl sbcl scl)\n    (not-implemented-error '%process-status)\n    (if-let (process (slot-value process-info 'process))\n      (multiple-value-bind (status code)\n          (progn\n            #+allegro (multiple-value-bind (exit-code pid signal-code)\n                          (sys:reap-os-subprocess :pid process :wait nil)\n                        (assert pid)\n                        (%code-to-status exit-code signal-code))\n            #+clasp (if (find-symbol* '#:external-process-status :ext nil)\n                        (symbol-call :ext '#:external-process-status process)\n                        (not-implemented-error '%process-status))\n            #+clozure (ccl:external-process-status process)\n            #+(or cmucl scl) (let ((status (ext:process-status process)))\n                               (if (member status '(:exited :signaled))\n                                   ;; Calling ext:process-exit-code on\n                                   ;; processes that are still alive\n                                   ;; yields an undefined result\n                                   (values status (ext:process-exit-code process))\n                                   status))\n            #+ecl (ext:external-process-status process)\n            #+lispworks\n            ;; a signal is only returned on LispWorks 7+\n            (multiple-value-bind (exit-code signal-code)\n                (symbol-call :sys\n                             #+lispworks7+ :pipe-exit-status\n                             #-lispworks7+ :pid-exit-status\n                             process :wait nil)\n              (%code-to-status exit-code signal-code))\n            #+mkcl (let ((status (mk-ext:process-status process)))\n                     (if (eq status :exited)\n                         ;; Only call mk-ext:process-exit-code when\n                         ;; necessary since it leads to another waitpid()\n                         (let ((code (mk-ext:process-exit-code process)))\n                           (if (stringp code)\n                               (values :signaled (%mkcl-signal-to-number code))\n                               (values :exited code)))\n                         status))\n            #+sbcl (let ((status (sb-ext:process-status process)))\n                     (if (eq status :running)\n                         :running\n                         ;; sb-ext:process-exit-code can also be\n                         ;; called for stopped processes to determine\n                         ;; the signal that stopped them\n                         (values status (sb-ext:process-exit-code process)))))\n        (case status\n          (:exited (setf (slot-value process-info 'exit-code) code))\n          (:signaled (let ((%code (%signal-to-exit-code code)))\n                       (setf (slot-value process-info 'exit-code) %code\n                             (slot-value process-info 'signal-code) code))))\n        (if code\n            (values status code)\n            status))))\n\n  (defun process-alive-p (process-info)\n    \"Check if a process has yet to exit.\"\n    (unless (slot-value process-info 'exit-code)\n      #+abcl (sys:process-alive-p (slot-value process-info 'process))\n      #+(or cmucl scl) (ext:process-alive-p (slot-value process-info 'process))\n      #+sbcl (sb-ext:process-alive-p (slot-value process-info 'process))\n      #-(or abcl cmucl sbcl scl) (find (%process-status process-info)\n                                       '(:running :stopped :continued :resumed))))\n\n  (defun wait-process (process-info)\n    \"Wait for the process to terminate, if it is still running.\nOtherwise, return immediately. An exit code (a number) will be\nreturned, with 0 indicating success, and anything else indicating\nfailure. If the process exits after receiving a signal, the exit code\nwill be the sum of 128 and the (positive) numeric signal code. A second\nvalue may be returned in this case: the numeric signal code itself.\nAny asynchronously spawned process requires this function to be run\nbefore it is garbage-collected in order to free up resources that\nmight otherwise be irrevocably lost.\"\n    (if-let (exit-code (slot-value process-info 'exit-code))\n      (if-let (signal-code (slot-value process-info 'signal-code))\n        (values exit-code signal-code)\n        exit-code)\n      (let ((process (slot-value process-info 'process)))\n        #-(or abcl allegro clasp clozure cmucl ecl lispworks mkcl sbcl scl)\n        (not-implemented-error 'wait-process)\n        (when process\n          ;; 1- wait\n          #+clozure (ccl::external-process-wait process)\n          #+(or cmucl scl) (ext:process-wait process)\n          #+sbcl (sb-ext:process-wait process)\n          ;; 2- extract result\n          (multiple-value-bind (exit-code signal-code)\n              (progn\n                #+abcl (sys:process-wait process)\n                #+allegro (multiple-value-bind (exit-code pid signal)\n                              (sys:reap-os-subprocess :pid process :wait t)\n                            (assert pid)\n                            (values exit-code signal))\n                #+clasp (if (find-symbol* '#:external-process-wait :ext nil)\n                            (multiple-value-bind (status code)\n                                (symbol-call :ext '#:external-process-wait process t)\n                              (if (eq status :signaled)\n                                  (values nil code)\n                                  code))\n                            (not-implemented-error 'wait-process))\n                #+clozure (multiple-value-bind (status code)\n                              (ccl:external-process-status process)\n                            (if (eq status :signaled)\n                                (values nil code)\n                                code))\n                #+(or cmucl scl) (let ((status (ext:process-status process))\n                                       (code (ext:process-exit-code process)))\n                                   (if (eq status :signaled)\n                                       (values nil code)\n                                       code))\n                #+ecl (multiple-value-bind (status code)\n                          (ext:external-process-wait process t)\n                        (if (eq status :signaled)\n                            (values nil code)\n                            code))\n                #+lispworks (symbol-call :sys\n                                         #+lispworks7+ :pipe-exit-status\n                                         #-lispworks7+ :pid-exit-status\n                                         process :wait t)\n                #+mkcl (let ((code (mkcl:join-process process)))\n                         (if (stringp code)\n                             (values nil (%mkcl-signal-to-number code))\n                             code))\n                #+sbcl (let ((status (sb-ext:process-status process))\n                             (code (sb-ext:process-exit-code process)))\n                         (if (eq status :signaled)\n                             (values nil code)\n                             code)))\n            (if signal-code\n                (let ((%exit-code (%signal-to-exit-code signal-code)))\n                  (setf (slot-value process-info 'exit-code) %exit-code\n                        (slot-value process-info 'signal-code) signal-code)\n                  (values %exit-code signal-code))\n                (progn (setf (slot-value process-info 'exit-code) exit-code)\n                       exit-code)))))))\n\n  ;; WARNING: For signals other than SIGTERM and SIGKILL this may not\n  ;; do what you expect it to. Sending SIGSTOP to a process spawned\n  ;; via LAUNCH-PROGRAM, e.g., will stop the shell /bin/sh that is used\n  ;; to run the command (via `sh -c command`) but not the actual\n  ;; command.\n  #+os-unix\n  (defun %posix-send-signal (process-info signal)\n    #+allegro (excl.osi:kill (slot-value process-info 'process) signal)\n    #+clozure (ccl:signal-external-process (slot-value process-info 'process)\n                                           signal :error-if-exited nil)\n    #+(or cmucl scl) (ext:process-kill (slot-value process-info 'process) signal)\n    #+sbcl (sb-ext:process-kill (slot-value process-info 'process) signal)\n    #-(or allegro clozure cmucl sbcl scl)\n    (if-let (pid (process-info-pid process-info))\n      (symbol-call :uiop :run-program\n                   (format nil \"kill -~a ~a\" signal pid) :ignore-error-status t)))\n\n  ;;; this function never gets called on Windows, but the compiler cannot tell\n  ;;; that. [2016/09/25:rpg]\n  #+os-windows\n  (defun %posix-send-signal (process-info signal)\n    (declare (ignore process-info signal))\n    (values))\n\n  (defun terminate-process (process-info &key urgent)\n    \"Cause the process to exit. To that end, the process may or may\nnot be sent a signal, which it will find harder (or even impossible)\nto ignore if URGENT is T. On some platforms, it may also be subject to\nrace conditions.\"\n    (declare (ignorable urgent))\n    #+abcl (sys:process-kill (slot-value process-info 'process))\n    ;; On ECL, this will only work on versions later than 2016-09-06,\n    ;; but we still want to compile on earlier versions, so we use symbol-call\n    #+(or clasp ecl) (symbol-call :ext :terminate-process (slot-value process-info 'process) urgent)\n    #+lispworks7+ (sys:pipe-kill-process (slot-value process-info 'process))\n    #+mkcl (mk-ext:terminate-process (slot-value process-info 'process)\n                                     :force urgent)\n    #-(or abcl clasp ecl lispworks7+ mkcl)\n    (os-cond\n     ((os-unix-p) (%posix-send-signal process-info (if urgent 9 15)))\n     ((os-windows-p) (if-let (pid (process-info-pid process-info))\n                       (symbol-call :uiop :run-program\n                                    (format nil \"taskkill ~:[~;/f ~]/pid ~a\" urgent pid)\n                                    :ignore-error-status t)))\n     (t (not-implemented-error 'terminate-process))))\n\n  (defun close-streams (process-info)\n    \"Close any stream that the process might own. Needs to be run\nwhenever streams were requested by passing :stream to :input, :output,\nor :error-output.\"\n    (dolist (stream\n              (cons (slot-value process-info 'error-output-stream)\n                    (if-let (bidir-stream (slot-value process-info 'bidir-stream))\n                      (list bidir-stream)\n                      (list (slot-value process-info 'input-stream)\n                            (slot-value process-info 'output-stream)))))\n      (when stream (close stream))))\n\n  (defun launch-program (command &rest keys\n                         &key\n                           input (if-input-does-not-exist :error)\n                           output (if-output-exists :supersede)\n                           error-output (if-error-output-exists :supersede)\n                           (element-type #-clozure *default-stream-element-type*\n                                         #+clozure 'character)\n                           (external-format *utf-8-external-format*)\n                           directory\n                           #+allegro separate-streams\n                           &allow-other-keys)\n    \"Launch program specified by COMMAND,\neither a list of strings specifying a program and list of arguments,\nor a string specifying a shell command (/bin/sh on Unix, CMD.EXE on\nWindows) _asynchronously_.\n\nIf OUTPUT is a pathname, a string designating a pathname, or NIL (the\ndefault) designating the null device, the file at that path is used as\noutput.\nIf it's :INTERACTIVE, output is inherited from the current process;\nbeware that this may be different from your *STANDARD-OUTPUT*, and\nunder SLIME will be on your *inferior-lisp* buffer.  If it's T, output\ngoes to your current *STANDARD-OUTPUT* stream.  If it's :STREAM, a new\nstream will be made available that can be accessed via\nPROCESS-INFO-OUTPUT and read from. Otherwise, OUTPUT should be a value\nthat the underlying lisp implementation knows how to handle.\n\nIF-OUTPUT-EXISTS, which is only meaningful if OUTPUT is a string or a\npathname, can take the values :ERROR, :APPEND, and :SUPERSEDE (the\ndefault). The meaning of these values and their effect on the case\nwhere OUTPUT does not exist, is analogous to the IF-EXISTS parameter\nto OPEN with :DIRECTION :OUTPUT.\n\nERROR-OUTPUT is similar to OUTPUT. T designates the *ERROR-OUTPUT*,\n:OUTPUT means redirecting the error output to the output stream,\nand :STREAM causes a stream to be made available via\nPROCESS-INFO-ERROR-OUTPUT.\n\nIF-ERROR-OUTPUT-EXISTS is similar to IF-OUTPUT-EXIST, except that it\naffects ERROR-OUTPUT rather than OUTPUT.\n\nINPUT is similar to OUTPUT, except that T designates the\n*STANDARD-INPUT* and a stream requested through the :STREAM keyword\nwould be available through PROCESS-INFO-INPUT.\n\nIF-INPUT-DOES-NOT-EXIST, which is only meaningful if INPUT is a string\nor a pathname, can take the values :CREATE and :ERROR (the\ndefault). The meaning of these values is analogous to the\nIF-DOES-NOT-EXIST parameter to OPEN with :DIRECTION :INPUT.\n\nELEMENT-TYPE and EXTERNAL-FORMAT are passed on to your Lisp\nimplementation, when applicable, for creation of the output stream.\n\nLAUNCH-PROGRAM returns a PROCESS-INFO object.\n\nLAUNCH-PROGRAM currently does not smooth over all the differences between\nimplementations. Of particular note is when streams are provided for OUTPUT or\nERROR-OUTPUT. Some implementations don't support this at all, some support only\ncertain subclasses of streams, and some support any arbitrary\nstream. Additionally, the implementations that support streams may have\ndiffering behavior on how those streams are filled with data. If data is not\nperiodically read from the child process and sent to the stream, the child\ncould block because its output buffers are full.\"\n    #-(or abcl allegro clasp clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl)\n    (progn command keys input output error-output directory element-type external-format\n           if-input-does-not-exist if-output-exists if-error-output-exists ;; ignore\n           (not-implemented-error 'launch-program))\n    #+allegro\n    (when (some #'(lambda (stream)\n                    (and (streamp stream)\n                         (not (file-stream-p stream))))\n                (list input output error-output))\n      (parameter-error \"~S: Streams passed as I/O parameters need to be file streams on this lisp\"\n                       'launch-program))\n    #+(or abcl clisp lispworks)\n    (when (some #'streamp (list input output error-output))\n      (parameter-error \"~S: I/O parameters cannot be foreign streams on this lisp\"\n                       'launch-program))\n    #+clisp\n    (unless (eq error-output :interactive)\n      (parameter-error \"~S: The only admissible value for ~S is ~S on this lisp\"\n                       'launch-program :error-output :interactive))\n    #+(or clasp ecl)\n    (when (and #+ecl (version< (lisp-implementation-version) \"20.4.24\")\n               (some #'(lambda (stream)\n                         (and (streamp stream)\n                              (not (file-or-synonym-stream-p stream))))\n                     (list input output error-output)))\n      (parameter-error \"~S: Streams passed as I/O parameters need to be (synonymous with) file streams on this lisp\"\n                       'launch-program))\n    #+(or abcl allegro clasp clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl)\n    (nest\n     (progn ;; see comments for these functions\n       (%handle-if-does-not-exist input if-input-does-not-exist)\n       (%handle-if-exists output if-output-exists)\n       (%handle-if-exists error-output if-error-output-exists))\n     #+(or clasp ecl) (let ((*standard-input* *stdin*)\n                 (*standard-output* *stdout*)\n                 (*error-output* *stderr*)))\n     (let ((process-info (make-instance 'process-info))\n           (input (%normalize-io-specifier input :input))\n           (output (%normalize-io-specifier output :output))\n           (error-output (%normalize-io-specifier error-output :error-output))\n           #+(and allegro os-windows) (interactive (%interactivep input output error-output))\n           (command\n            (etypecase command\n              #+os-unix (string `(\"/bin/sh\" \"-c\" ,command))\n              #+os-unix (list command)\n              #+os-windows\n              (string\n               ;; NB: On other Windows implementations, this is utterly bogus\n               ;; except in the most trivial cases where no quoting is needed.\n               ;; Use at your own risk.\n               #-(or allegro clasp clisp clozure ecl)\n               (nest\n                #+(or clasp ecl sbcl) (unless (find-symbol* :escape-arguments #+(or clasp ecl) :ext #+sbcl :sb-impl nil))\n                (parameter-error \"~S doesn't support string commands on Windows on this Lisp\"\n                                 'launch-program command))\n               ;; NB: We add cmd /c here. Behavior without going through cmd is not well specified\n               ;; when the command contains spaces or special characters:\n               ;; IIUC, the system will use space as a separator,\n               ;; but the C++ argv-decoding libraries won't, and\n               ;; you're supposed to use an extra argument to CreateProcess to bridge the gap,\n               ;; yet neither allegro nor clisp provide access to that argument.\n               #+(or allegro clisp) (strcat \"cmd /c \" command)\n               ;; On ClozureCL for Windows, we assume you are using\n               ;; r15398 or later in 1.9 or later,\n               ;; so that bug 858 is fixed http://trac.clozure.com/ccl/ticket/858\n               ;; On ECL, commit 2040629 https://gitlab.com/embeddable-common-lisp/ecl/issues/304\n               ;; On SBCL, we assume the patch from fcae0fd (to be part of SBCL 1.3.13)\n               #+(or clasp clozure ecl sbcl) (cons \"cmd\" (strcat \"/c \" command)))\n              #+os-windows\n              (list\n               #+allegro (escape-windows-command command)\n               #-allegro command)))))\n     #+(or abcl (and allegro os-unix) clasp clozure cmucl ecl mkcl sbcl)\n     (let ((program (car command))\n           #-allegro (arguments (cdr command))))\n     #+(and (or clasp ecl sbcl) os-windows)\n     (multiple-value-bind (arguments escape-arguments)\n         (if (listp arguments)\n             (values arguments t)\n             (values (list arguments) nil)))\n     #-(or allegro mkcl sbcl) (with-current-directory (directory))\n     (multiple-value-bind\n       #+(or abcl clozure cmucl sbcl scl) (process)\n       #+allegro (in-or-io out-or-err err-or-pid pid-or-nil)\n       #+(or clasp ecl) (stream code process)\n       #+lispworks (io-or-pid err-or-nil #-lispworks7+ pid-or-nil)\n       #+mkcl (stream process code)\n       #.`(apply\n           #+abcl 'sys:run-program\n           #+allegro ,@'('excl:run-shell-command\n                         #+os-unix (coerce (cons program command) 'vector)\n                         #+os-windows command)\n           #+clasp (if (find-symbol* '#:run-program :ext nil)\n                       (find-symbol* '#:run-program :ext nil)\n                       (not-implemented-error 'launch-program))\n           #+clozure 'ccl:run-program\n           #+(or cmucl ecl scl) 'ext:run-program\n           \n           #+lispworks ,@'('system:run-shell-command `(\"/usr/bin/env\" ,@command)) ; full path needed\n           #+mkcl 'mk-ext:run-program\n           #+sbcl 'sb-ext:run-program\n           #+(or abcl clasp clozure cmucl ecl mkcl sbcl) ,@'(program arguments)\n           #+(and (or clasp ecl sbcl) os-windows) ,@'(:escape-arguments escape-arguments)\n           :input input :if-input-does-not-exist :error\n           :output output :if-output-exists :append\n           ,(or #+(or allegro lispworks) :error-output :error) error-output\n           ,(or #+(or allegro lispworks) :if-error-output-exists :if-error-exists) :append\n           :wait nil :element-type element-type :external-format external-format\n           :allow-other-keys t\n           #+allegro ,@`(:directory directory\n                         #+os-windows ,@'(:show-window (if interactive nil :hide)))\n           #+lispworks ,@'(:save-exit-status t)\n           #+mkcl ,@'(:directory (native-namestring directory))\n           #-sbcl keys ;; on SBCL, don't pass :directory nil but remove it from the keys\n           #+sbcl ,@'(:search t (if directory keys (remove-plist-key :directory keys)))))\n     (labels ((prop (key value) (setf (slot-value process-info key) value)))\n       #+allegro\n       (cond\n         (separate-streams\n          (prop 'process pid-or-nil)\n          (when (eq input :stream) (prop 'input-stream in-or-io))\n          (when (eq output :stream) (prop 'output-stream out-or-err))\n          (when (eq error-output :stream) (prop 'error-output-stream err-or-pid)))\n         (t\n          (prop 'process err-or-pid)\n          (ecase (+ (if (eq input :stream) 1 0) (if (eq output :stream) 2 0))\n            (0)\n            (1 (prop 'input-stream in-or-io))\n            (2 (prop 'output-stream in-or-io))\n            (3 (prop 'bidir-stream in-or-io)))\n          (when (eq error-output :stream)\n            (prop 'error-output-stream out-or-err))))\n       #+(or abcl clozure cmucl sbcl scl)\n       (progn\n         (prop 'process process)\n         (when (eq input :stream)\n           (nest\n            (prop 'input-stream)\n            #+abcl (symbol-call :sys :process-input)\n            #+clozure (ccl:external-process-input-stream)\n            #+(or cmucl scl) (ext:process-input)\n            #+sbcl (sb-ext:process-input)\n            process))\n         (when (eq output :stream)\n           (nest\n            (prop 'output-stream)\n            #+abcl (symbol-call :sys :process-output)\n            #+clozure (ccl:external-process-output-stream)\n            #+(or cmucl scl) (ext:process-output)\n            #+sbcl (sb-ext:process-output)\n            process))\n         (when (eq error-output :stream)\n           (nest\n            (prop 'error-output-stream)\n            #+abcl (symbol-call :sys :process-error)\n            #+clozure (ccl:external-process-error-stream)\n            #+(or cmucl scl) (ext:process-error)\n            #+sbcl (sb-ext:process-error)\n            process)))\n       #+(or clasp ecl mkcl)\n       (let ((mode (+ (if (eq input :stream) 1 0) (if (eq output :stream) 2 0))))\n         code ;; ignore\n         (unless (zerop mode)\n           (prop (case mode (1 'input-stream) (2 'output-stream) (3 'bidir-stream)) stream))\n         (when (eq error-output :stream)\n           (prop 'error-output-stream\n                 (if (and #+clasp nil #-clasp t (version< (lisp-implementation-version) \"16.0.0\"))\n                     (symbol-call :ext :external-process-error process)\n                     (symbol-call :ext :external-process-error-stream process))))\n         (prop 'process process))\n       #+lispworks\n       ;; See also the comments on the process-info class\n       (let ((mode (+ (if (eq input :stream) 1 0) (if (eq output :stream) 2 0))))\n         (cond\n           ((or (plusp mode) (eq error-output :stream))\n            (prop 'process #+lispworks7+ io-or-pid #-lispworks7+ pid-or-nil)\n            (when (plusp mode)\n              (prop (ecase mode (1 'input-stream) (2 'output-stream) (3 'bidir-stream))\n                    io-or-pid))\n            (when (eq error-output :stream)\n              (prop 'error-output-stream err-or-nil)))\n           ;; Prior to Lispworks 7, this returned (pid); now it\n           ;; returns (io err pid) of which we keep io.\n           (t (prop 'process io-or-pid)))))\n     process-info)))\n\n;;;; -------------------------------------------------------------------------\n;;;; run-program initially from xcvb-driver.\n\n(uiop/package:define-package :uiop/run-program\n  (:nicknames :asdf/run-program) ; OBSOLETE. Used by cl-sane, printv.\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/version\n   :uiop/pathname :uiop/os :uiop/filesystem :uiop/stream :uiop/launch-program)\n  (:export\n   #:run-program\n   #:slurp-input-stream #:vomit-output-stream\n   #:subprocess-error\n   #:subprocess-error-code #:subprocess-error-command #:subprocess-error-process)\n  (:import-from :uiop/launch-program\n   #:%handle-if-does-not-exist #:%handle-if-exists #:%interactivep\n   #:input-stream #:output-stream #:error-output-stream))\n(in-package :uiop/run-program)\n\n;;;; Slurping a stream, typically the output of another program\n(with-upgradability ()\n  (defun call-stream-processor (fun processor stream)\n    \"Given FUN (typically SLURP-INPUT-STREAM or VOMIT-OUTPUT-STREAM,\na PROCESSOR specification which is either an atom or a list specifying\na processor an keyword arguments, call the specified processor with\nthe given STREAM as input\"\n    (if (consp processor)\n        (apply fun (first processor) stream (rest processor))\n        (funcall fun processor stream)))\n\n  (defgeneric slurp-input-stream (processor input-stream &key)\n    (:documentation\n     \"SLURP-INPUT-STREAM is a generic function with two positional arguments\nPROCESSOR and INPUT-STREAM and additional keyword arguments, that consumes (slurps)\nthe contents of the INPUT-STREAM and processes them according to a method\nspecified by PROCESSOR.\n\nBuilt-in methods include the following:\n* if PROCESSOR is a function, it is called with the INPUT-STREAM as its argument\n* if PROCESSOR is a list, its first element should be a function.  It will be applied to a cons of the\n  INPUT-STREAM and the rest of the list.  That is (x . y) will be treated as\n    \\(APPLY x <stream> y\\)\n* if PROCESSOR is an output-stream, the contents of INPUT-STREAM is copied to the output-stream,\n  per copy-stream-to-stream, with appropriate keyword arguments.\n* if PROCESSOR is the symbol CL:STRING or the keyword :STRING, then the contents of INPUT-STREAM\n  are returned as a string, as per SLURP-STREAM-STRING.\n* if PROCESSOR is the keyword :LINES then the INPUT-STREAM will be handled by SLURP-STREAM-LINES.\n* if PROCESSOR is the keyword :LINE then the INPUT-STREAM will be handled by SLURP-STREAM-LINE.\n* if PROCESSOR is the keyword :FORMS then the INPUT-STREAM will be handled by SLURP-STREAM-FORMS.\n* if PROCESSOR is the keyword :FORM then the INPUT-STREAM will be handled by SLURP-STREAM-FORM.\n* if PROCESSOR is T, it is treated the same as *standard-output*. If it is NIL, NIL is returned.\n\nProgrammers are encouraged to define their own methods for this generic function.\"))\n\n  #-genera\n  (defmethod slurp-input-stream ((function function) input-stream &key)\n    (funcall function input-stream))\n\n  (defmethod slurp-input-stream ((list cons) input-stream &key)\n    (apply (first list) input-stream (rest list)))\n\n  #-genera\n  (defmethod slurp-input-stream ((output-stream stream) input-stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (copy-stream-to-stream\n     input-stream output-stream\n     :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n\n  (defmethod slurp-input-stream ((x (eql 'string)) stream &key stripped)\n    (slurp-stream-string stream :stripped stripped))\n\n  (defmethod slurp-input-stream ((x (eql :string)) stream &key stripped)\n    (slurp-stream-string stream :stripped stripped))\n\n  (defmethod slurp-input-stream ((x (eql :lines)) stream &key count)\n    (slurp-stream-lines stream :count count))\n\n  (defmethod slurp-input-stream ((x (eql :line)) stream &key (at 0))\n    (slurp-stream-line stream :at at))\n\n  (defmethod slurp-input-stream ((x (eql :forms)) stream &key count)\n    (slurp-stream-forms stream :count count))\n\n  (defmethod slurp-input-stream ((x (eql :form)) stream &key (at 0))\n    (slurp-stream-form stream :at at))\n\n  (defmethod slurp-input-stream ((x (eql t)) stream &rest keys &key &allow-other-keys)\n    (apply 'slurp-input-stream *standard-output* stream keys))\n\n  (defmethod slurp-input-stream ((x null) (stream t) &key)\n    nil)\n\n  (defmethod slurp-input-stream ((pathname pathname) input\n                                 &key\n                                   (element-type *default-stream-element-type*)\n                                   (external-format *utf-8-external-format*)\n                                   (if-exists :rename-and-delete)\n                                   (if-does-not-exist :create)\n                                   buffer-size\n                                   linewise)\n    (with-output-file (output pathname\n                              :element-type element-type\n                              :external-format external-format\n                              :if-exists if-exists\n                              :if-does-not-exist if-does-not-exist)\n      (copy-stream-to-stream\n       input output\n       :element-type element-type :buffer-size buffer-size :linewise linewise)))\n\n  (defmethod slurp-input-stream (x stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (declare (ignorable stream linewise prefix element-type buffer-size))\n    (cond\n      #+genera\n      ((functionp x) (funcall x stream))\n      #+genera\n      ((output-stream-p x)\n       (copy-stream-to-stream\n        stream x\n        :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n      (t\n       (parameter-error \"Invalid ~S destination ~S\" 'slurp-input-stream x)))))\n\n;;;; Vomiting a stream, typically into the input of another program.\n(with-upgradability ()\n  (defgeneric vomit-output-stream (processor output-stream &key)\n    (:documentation\n     \"VOMIT-OUTPUT-STREAM is a generic function with two positional arguments\nPROCESSOR and OUTPUT-STREAM and additional keyword arguments, that produces (vomits)\nsome content onto the OUTPUT-STREAM, according to a method specified by PROCESSOR.\n\nBuilt-in methods include the following:\n* if PROCESSOR is a function, it is called with the OUTPUT-STREAM as its argument\n* if PROCESSOR is a list, its first element should be a function.\n  It will be applied to a cons of the OUTPUT-STREAM and the rest of the list.\n  That is (x . y) will be treated as \\(APPLY x <stream> y\\)\n* if PROCESSOR is an input-stream, its contents will be copied the OUTPUT-STREAM,\n  per copy-stream-to-stream, with appropriate keyword arguments.\n* if PROCESSOR is a string, its contents will be printed to the OUTPUT-STREAM.\n* if PROCESSOR is T, it is treated the same as *standard-input*. If it is NIL, nothing is done.\n\nProgrammers are encouraged to define their own methods for this generic function.\"))\n\n  #-genera\n  (defmethod vomit-output-stream ((function function) output-stream &key)\n    (funcall function output-stream))\n\n  (defmethod vomit-output-stream ((list cons) output-stream &key)\n    (apply (first list) output-stream (rest list)))\n\n  #-genera\n  (defmethod vomit-output-stream ((input-stream stream) output-stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (copy-stream-to-stream\n     input-stream output-stream\n     :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n\n  (defmethod vomit-output-stream ((x string) stream &key fresh-line terpri)\n    (princ x stream)\n    (when fresh-line (fresh-line stream))\n    (when terpri (terpri stream))\n    (values))\n\n  (defmethod vomit-output-stream ((x (eql t)) stream &rest keys &key &allow-other-keys)\n    (apply 'vomit-output-stream *standard-input* stream keys))\n\n  (defmethod vomit-output-stream ((x null) (stream t) &key)\n    (values))\n\n  (defmethod vomit-output-stream ((pathname pathname) input\n                                 &key\n                                   (element-type *default-stream-element-type*)\n                                   (external-format *utf-8-external-format*)\n                                   (if-exists :rename-and-delete)\n                                   (if-does-not-exist :create)\n                                   buffer-size\n                                   linewise)\n    (with-output-file (output pathname\n                              :element-type element-type\n                              :external-format external-format\n                              :if-exists if-exists\n                              :if-does-not-exist if-does-not-exist)\n      (copy-stream-to-stream\n       input output\n       :element-type element-type :buffer-size buffer-size :linewise linewise)))\n\n  (defmethod vomit-output-stream (x stream\n                                 &key linewise prefix (element-type 'character) buffer-size)\n    (declare (ignorable stream linewise prefix element-type buffer-size))\n    (cond\n      #+genera\n      ((functionp x) (funcall x stream))\n      #+genera\n      ((input-stream-p x)\n       (copy-stream-to-stream\n        x stream\n        :linewise linewise :prefix prefix :element-type element-type :buffer-size buffer-size))\n      (t\n       (parameter-error \"Invalid ~S source ~S\" 'vomit-output-stream x)))))\n\n\n;;;; Run-program: synchronously run a program in a subprocess, handling input, output and error-output.\n(with-upgradability ()\n  (define-condition subprocess-error (error)\n    ((code :initform nil :initarg :code :reader subprocess-error-code)\n     (command :initform nil :initarg :command :reader subprocess-error-command)\n     (process :initform nil :initarg :process :reader subprocess-error-process))\n    (:report (lambda (condition stream)\n               (format stream \"Subprocess ~@[~S~% ~]~@[with command ~S~% ~]exited with error~@[ code ~D~]\"\n                       (subprocess-error-process condition)\n                       (subprocess-error-command condition)\n                       (subprocess-error-code condition)))))\n\n  (defun %check-result (exit-code &key command process ignore-error-status)\n    (unless ignore-error-status\n      (unless (eql exit-code 0)\n        (cerror \"IGNORE-ERROR-STATUS\"\n                'subprocess-error :command command :code exit-code :process process)))\n    exit-code)\n\n  (defun %active-io-specifier-p (specifier)\n    \"Determines whether a run-program I/O specifier requires Lisp-side processing\nvia SLURP-INPUT-STREAM or VOMIT-OUTPUT-STREAM (return T),\nor whether it's already taken care of by the implementation's underlying run-program.\"\n    (not (typep specifier '(or null string pathname (member :interactive :output)\n                            #+(or cmucl (and sbcl os-unix) scl) (or stream (eql t))\n                            #+lispworks file-stream))))\n\n  (defun %run-program (command &rest keys &key &allow-other-keys)\n    \"DEPRECATED. Use LAUNCH-PROGRAM instead.\"\n    (apply 'launch-program command keys))\n\n  (defun %call-with-program-io (gf tval stream-easy-p fun direction spec activep returner\n                                &key\n                                  (element-type #-clozure *default-stream-element-type* #+clozure 'character)\n                                  (external-format *utf-8-external-format*) &allow-other-keys)\n    ;; handle redirection for run-program and system\n    ;; SPEC is the specification for the subprocess's input or output or error-output\n    ;; TVAL is the value used if the spec is T\n    ;; GF is the generic function to call to handle arbitrary values of SPEC\n    ;; STREAM-EASY-P is T if we're going to use a RUN-PROGRAM that copies streams in the background\n    ;; (it's only meaningful on CMUCL, SBCL, SCL that actually do it)\n    ;; DIRECTION is :INPUT, :OUTPUT or :ERROR-OUTPUT for the direction of this io argument\n    ;; FUN is a function of the new reduced spec and an activity function to call with a stream\n    ;; when the subprocess is active and communicating through that stream.\n    ;; ACTIVEP is a boolean true if we will get to run code while the process is running\n    ;; ELEMENT-TYPE and EXTERNAL-FORMAT control what kind of temporary file we may open.\n    ;; RETURNER is a function called with the value of the activity.\n    ;; --- TODO (fare@tunes.org): handle if-output-exists and such when doing it the hard way.\n    (declare (ignorable stream-easy-p))\n    (let* ((actual-spec (if (eq spec t) tval spec))\n           (activity-spec (if (eq actual-spec :output)\n                              (ecase direction\n                                ((:input :output)\n                                 (parameter-error \"~S does not allow ~S as a ~S spec\"\n                                                  'run-program :output direction))\n                                ((:error-output)\n                                 nil))\n                              actual-spec)))\n      (labels ((activity (stream)\n                 (call-function returner (call-stream-processor gf activity-spec stream)))\n               (easy-case ()\n                 (funcall fun actual-spec nil))\n               (hard-case ()\n                 (if activep\n                     (funcall fun :stream #'activity)\n                     (with-temporary-file (:pathname tmp)\n                       (ecase direction\n                         (:input\n                          (with-output-file (s tmp :if-exists :overwrite\n                                               :external-format external-format\n                                               :element-type element-type)\n                            (activity s))\n                          (funcall fun tmp nil))\n                         ((:output :error-output)\n                          (multiple-value-prog1 (funcall fun tmp nil)\n                            (with-input-file (s tmp\n                                               :external-format external-format\n                                               :element-type element-type)\n                              (activity s)))))))))\n        (typecase activity-spec\n          ((or null string pathname (eql :interactive))\n           (easy-case))\n          #+(or cmucl (and sbcl os-unix) scl) ;; streams are only easy on implementations that try very hard\n          (stream\n           (if stream-easy-p (easy-case) (hard-case)))\n          (t\n           (hard-case))))))\n\n  (defmacro place-setter (place)\n    (when place\n      (let ((value (gensym)))\n        `#'(lambda (,value) (setf ,place ,value)))))\n\n  (defmacro with-program-input (((reduced-input-var\n                                  &optional (input-activity-var (gensym) iavp))\n                                 input-form &key setf stream-easy-p active keys) &body body)\n    `(apply '%call-with-program-io 'vomit-output-stream *standard-input* ,stream-easy-p\n            #'(lambda (,reduced-input-var ,input-activity-var)\n                ,@(unless iavp `((declare (ignore ,input-activity-var))))\n                ,@body)\n            :input ,input-form ,active (place-setter ,setf) ,keys))\n\n  (defmacro with-program-output (((reduced-output-var\n                                  &optional (output-activity-var (gensym) oavp))\n                                  output-form &key setf stream-easy-p active keys) &body body)\n    `(apply '%call-with-program-io 'slurp-input-stream *standard-output* ,stream-easy-p\n            #'(lambda (,reduced-output-var ,output-activity-var)\n                ,@(unless oavp `((declare (ignore ,output-activity-var))))\n                ,@body)\n            :output ,output-form ,active (place-setter ,setf) ,keys))\n\n  (defmacro with-program-error-output (((reduced-error-output-var\n                                         &optional (error-output-activity-var (gensym) eoavp))\n                                        error-output-form &key setf stream-easy-p active keys)\n                                       &body body)\n    `(apply '%call-with-program-io 'slurp-input-stream *error-output* ,stream-easy-p\n            #'(lambda (,reduced-error-output-var ,error-output-activity-var)\n                ,@(unless eoavp `((declare (ignore ,error-output-activity-var))))\n                ,@body)\n            :error-output ,error-output-form ,active (place-setter ,setf) ,keys))\n\n  (defun %use-launch-program (command &rest keys\n                           &key input output error-output ignore-error-status &allow-other-keys)\n    ;; helper for RUN-PROGRAM when using LAUNCH-PROGRAM\n    #+(or cormanlisp gcl (and lispworks os-windows) mcl xcl)\n    (progn\n      command keys input output error-output ignore-error-status ;; ignore\n      (not-implemented-error '%use-launch-program))\n    (when (member :stream (list input output error-output))\n      (parameter-error \"~S: ~S is not allowed as synchronous I/O redirection argument\"\n                       'run-program :stream))\n    (let* ((active-input-p (%active-io-specifier-p input))\n           (active-output-p (%active-io-specifier-p output))\n           (active-error-output-p (%active-io-specifier-p error-output))\n           (activity\n             (cond\n               (active-output-p :output)\n               (active-input-p :input)\n               (active-error-output-p :error-output)\n               (t nil)))\n           output-result error-output-result exit-code process-info)\n      (with-program-output ((reduced-output output-activity)\n                            output :keys keys :setf output-result\n                            :stream-easy-p t :active (eq activity :output))\n        (with-program-error-output ((reduced-error-output error-output-activity)\n                                    error-output :keys keys :setf error-output-result\n                                    :stream-easy-p t :active (eq activity :error-output))\n          (with-program-input ((reduced-input input-activity)\n                               input :keys keys\n                               :stream-easy-p t :active (eq activity :input))\n            (setf process-info\n                  (apply 'launch-program command\n                         :input reduced-input :output reduced-output\n                         :error-output (if (eq error-output :output) :output reduced-error-output)\n                         keys))\n            (labels ((get-stream (stream-name &optional fallbackp)\n                       (or (slot-value process-info stream-name)\n                           (when fallbackp\n                             (slot-value process-info 'bidir-stream))))\n                     (run-activity (activity stream-name &optional fallbackp)\n                       (if-let (stream (get-stream stream-name fallbackp))\n                         (funcall activity stream)\n                         (error 'subprocess-error\n                                :code `(:missing ,stream-name)\n                                :command command :process process-info))))\n              (unwind-protect\n                   (ecase activity\n                     ((nil))\n                     (:input (run-activity input-activity 'input-stream t))\n                     (:output (run-activity output-activity 'output-stream t))\n                     (:error-output (run-activity error-output-activity 'error-output-stream)))\n                (close-streams process-info)\n                (setf exit-code (wait-process process-info)))))))\n      (%check-result exit-code\n                     :command command :process process-info\n                     :ignore-error-status ignore-error-status)\n      (values output-result error-output-result exit-code)))\n\n  (defun %normalize-system-command (command) ;; helper for %USE-SYSTEM\n    (etypecase command\n      (string command)\n      (list (escape-shell-command\n             (os-cond\n              ((os-unix-p) (cons \"exec\" command))\n              (t command))))))\n\n  (defun %redirected-system-command (command in out err directory) ;; helper for %USE-SYSTEM\n    (flet ((redirect (spec operator)\n             (let ((pathname\n                     (typecase spec\n                       (null (null-device-pathname))\n                       (string (parse-native-namestring spec))\n                       (pathname spec)\n                       ((eql :output)\n                        (unless (equal operator \" 2>>\")\n                          (parameter-error \"~S: only the ~S argument can be ~S\"\n                                           'run-program :error-output :output))\n                        (return-from redirect '(\" 2>&1\"))))))\n               (when pathname\n                 (list operator \" \"\n                       (escape-shell-token (native-namestring pathname)))))))\n      (let* ((redirections (append (redirect in \" <\") (redirect out \" >>\") (redirect err \" 2>>\")))\n             (normalized (%normalize-system-command command))\n             (directory (or directory #+(or abcl xcl) (getcwd)))\n             (chdir (when directory\n                      (let ((dir-arg (escape-shell-token (native-namestring directory))))\n                        (os-cond\n                         ((os-unix-p) `(\"cd \" ,dir-arg \" ; \"))\n                         ((os-windows-p) `(\"cd /d \" ,dir-arg \" & \")))))))\n        (reduce/strcat\n         (os-cond\n          ((os-unix-p) `(,@(when redirections `(\"exec \" ,@redirections \" ; \")) ,@chdir ,normalized))\n          ((os-windows-p) `(,@redirections \" (\" ,@chdir ,normalized \")\")))))))\n\n  (defun %system (command &rest keys &key directory\n                                       input (if-input-does-not-exist :error)\n                                       output (if-output-exists :supersede)\n                                       error-output (if-error-output-exists :supersede)\n                                       &allow-other-keys)\n    \"A portable abstraction of a low-level call to libc's system().\"\n    (declare (ignorable keys directory input if-input-does-not-exist output\n                        if-output-exists error-output if-error-output-exists))\n    (when (member :stream (list input output error-output))\n      (parameter-error \"~S: ~S is not allowed as synchronous I/O redirection argument\"\n                       'run-program :stream))\n    #+(or abcl allegro clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl)\n    (let (#+(or abcl ecl mkcl)\n            (version (parse-version\n                      #-abcl\n                      (lisp-implementation-version)\n                      #+abcl\n                      (second (split-string (implementation-identifier) :separator '(#\\-))))))\n      (nest\n       #+abcl (unless (lexicographic< '< version '(1 4 0)))\n       #+ecl (unless (lexicographic<= '< version '(16 0 0)))\n       #+mkcl (unless (lexicographic<= '< version '(1 1 9)))\n       (return-from %system\n         (wait-process\n          (apply 'launch-program (%normalize-system-command command) keys)))))\n    #+(or abcl clasp clisp cormanlisp ecl gcl genera (and lispworks os-windows) mkcl xcl)\n    (let ((%command (%redirected-system-command command input output error-output directory)))\n      ;; see comments for these functions\n      (%handle-if-does-not-exist input if-input-does-not-exist)\n      (%handle-if-exists output if-output-exists)\n      (%handle-if-exists error-output if-error-output-exists)\n      #+abcl (ext:run-shell-command %command)\n      #+(or clasp ecl) (let ((*standard-input* *stdin*)\n                             (*standard-output* *stdout*)\n                             (*error-output* *stderr*))\n                         (ext:system %command))\n      #+clisp\n      (let ((raw-exit-code\n             (or\n              #.`(#+os-windows ,@'(ext:run-shell-command %command)\n                  #+os-unix ,@'(ext:run-program \"/bin/sh\" :arguments `(\"-c\" ,%command))\n                  :wait t :input :terminal :output :terminal)\n              0)))\n        (if (minusp raw-exit-code)\n            (- 128 raw-exit-code)\n            raw-exit-code))\n      #+cormanlisp (win32:system %command)\n      #+gcl (system:system %command)\n      #+genera (not-implemented-error '%system)\n      #+(and lispworks os-windows)\n      (system:call-system %command :current-directory directory :wait t)\n      #+mcl (ccl::with-cstrs ((%%command %command)) (_system %%command))\n      #+mkcl (mkcl:system %command)\n      #+xcl (system:%run-shell-command %command)))\n\n  (defun %use-system (command &rest keys\n                      &key input output error-output ignore-error-status &allow-other-keys)\n    ;; helper for RUN-PROGRAM when using %system\n    (let (output-result error-output-result exit-code)\n      (with-program-output ((reduced-output)\n                            output :keys keys :setf output-result)\n        (with-program-error-output ((reduced-error-output)\n                                    error-output :keys keys :setf error-output-result)\n          (with-program-input ((reduced-input) input :keys keys)\n            (setf exit-code (apply '%system command\n                                   :input reduced-input :output reduced-output\n                                   :error-output reduced-error-output keys)))))\n      (%check-result exit-code\n                     :command command\n                     :ignore-error-status ignore-error-status)\n      (values output-result error-output-result exit-code)))\n\n  (defun run-program (command &rest keys\n                       &key ignore-error-status (force-shell nil force-shell-suppliedp)\n                         input (if-input-does-not-exist :error)\n                         output (if-output-exists :supersede)\n                         error-output (if-error-output-exists :supersede)\n                         (element-type #-clozure *default-stream-element-type* #+clozure 'character)\n                         (external-format *utf-8-external-format*)\n                       &allow-other-keys)\n    \"Run program specified by COMMAND,\neither a list of strings specifying a program and list of arguments,\nor a string specifying a shell command (/bin/sh on Unix, CMD.EXE on Windows);\n_synchronously_ process its output as specified and return the processing results\nwhen the program and its output processing are complete.\n\nAlways call a shell (rather than directly execute the command when possible)\nif FORCE-SHELL is specified.  Similarly, never call a shell if FORCE-SHELL is\nspecified to be NIL.\n\nSignal a continuable SUBPROCESS-ERROR if the process wasn't successful (exit-code 0),\nunless IGNORE-ERROR-STATUS is specified.\n\nIf OUTPUT is a pathname, a string designating a pathname, or NIL (the default)\ndesignating the null device, the file at that path is used as output.\nIf it's :INTERACTIVE, output is inherited from the current process;\nbeware that this may be different from your *STANDARD-OUTPUT*,\nand under SLIME will be on your *inferior-lisp* buffer.\nIf it's T, output goes to your current *STANDARD-OUTPUT* stream.\nOtherwise, OUTPUT should be a value that is a suitable first argument to\nSLURP-INPUT-STREAM (qv.), or a list of such a value and keyword arguments.\nIn this case, RUN-PROGRAM will create a temporary stream for the program output;\nthe program output, in that stream, will be processed by a call to SLURP-INPUT-STREAM,\nusing OUTPUT as the first argument (or the first element of OUTPUT, and the rest as keywords).\nThe primary value resulting from that call (or NIL if no call was needed)\nwill be the first value returned by RUN-PROGRAM.\nE.g., using :OUTPUT :STRING will have it return the entire output stream as a string.\nAnd using :OUTPUT '(:STRING :STRIPPED T) will have it return the same string\nstripped of any ending newline.\n\nIF-OUTPUT-EXISTS, which is only meaningful if OUTPUT is a string or a\npathname, can take the values :ERROR, :APPEND, and :SUPERSEDE (the\ndefault). The meaning of these values and their effect on the case\nwhere OUTPUT does not exist, is analogous to the IF-EXISTS parameter\nto OPEN with :DIRECTION :OUTPUT.\n\nERROR-OUTPUT is similar to OUTPUT, except that the resulting value is returned\nas the second value of RUN-PROGRAM. T designates the *ERROR-OUTPUT*.\nAlso :OUTPUT means redirecting the error output to the output stream,\nin which case NIL is returned.\n\nIF-ERROR-OUTPUT-EXISTS is similar to IF-OUTPUT-EXIST, except that it\naffects ERROR-OUTPUT rather than OUTPUT.\n\nINPUT is similar to OUTPUT, except that VOMIT-OUTPUT-STREAM is used,\nno value is returned, and T designates the *STANDARD-INPUT*.\n\nIF-INPUT-DOES-NOT-EXIST, which is only meaningful if INPUT is a string\nor a pathname, can take the values :CREATE and :ERROR (the\ndefault). The meaning of these values is analogous to the\nIF-DOES-NOT-EXIST parameter to OPEN with :DIRECTION :INPUT.\n\nELEMENT-TYPE and EXTERNAL-FORMAT are passed on\nto your Lisp implementation, when applicable, for creation of the output stream.\n\nOne and only one of the stream slurping or vomiting may or may not happen\nin parallel in parallel with the subprocess,\ndepending on options and implementation,\nand with priority being given to output processing.\nOther streams are completely produced or consumed\nbefore or after the subprocess is spawned, using temporary files.\n\nRUN-PROGRAM returns 3 values:\n0- the result of the OUTPUT slurping if any, or NIL\n1- the result of the ERROR-OUTPUT slurping if any, or NIL\n2- either 0 if the subprocess exited with success status,\nor an indication of failure via the EXIT-CODE of the process\"\n    (declare (ignorable input output error-output if-input-does-not-exist if-output-exists\n                        if-error-output-exists element-type external-format ignore-error-status))\n    #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl lispworks mcl mkcl sbcl scl xcl)\n    (not-implemented-error 'run-program)\n    (apply (if (or force-shell\n                   ;; Per doc string, set FORCE-SHELL to T if we get command as a string.\n                   ;; But don't override user's specified preference. [2015/06/29:rpg]\n                   (and (stringp command)\n                        (or (not force-shell-suppliedp)\n                            #-(or allegro clisp clozure sbcl) (os-cond ((os-windows-p) t))))\n                   #+(or clasp clisp cormanlisp gcl (and lispworks os-windows) mcl xcl) t\n                   ;; A race condition in ECL <= 16.0.0 prevents using ext:run-program\n                   #+ecl #.(if-let (ver (parse-version (lisp-implementation-version)))\n                                   (lexicographic<= '< ver '(16 0 0)))\n                   #+(and lispworks os-unix) (%interactivep input output error-output))\n               '%use-system '%use-launch-program)\n           command keys)))\n\n;;;; ---------------------------------------------------------------------------\n;;;; Generic support for configuration files\n\n(uiop/package:define-package :uiop/configuration\n  (:recycle :uiop/configuration :asdf/configuration) ;; necessary to upgrade from 2.27.\n  (:use :uiop/package :uiop/common-lisp :uiop/utility\n   :uiop/os :uiop/pathname :uiop/filesystem :uiop/stream :uiop/image :uiop/lisp-build)\n  (:export\n   #:user-configuration-directories #:system-configuration-directories ;; implemented in backward-driver\n   #:in-first-directory #:in-user-configuration-directory #:in-system-configuration-directory ;; idem\n   #:get-folder-path\n   #:xdg-data-home #:xdg-config-home #:xdg-data-dirs #:xdg-config-dirs\n   #:xdg-cache-home #:xdg-runtime-dir #:system-config-pathnames\n   #:filter-pathname-set #:xdg-data-pathnames #:xdg-config-pathnames\n   #:find-preferred-file #:xdg-data-pathname #:xdg-config-pathname\n   #:validate-configuration-form #:validate-configuration-file #:validate-configuration-directory\n   #:configuration-inheritance-directive-p\n   #:report-invalid-form #:invalid-configuration #:*ignored-configuration-form* #:*user-cache*\n   #:*clear-configuration-hook* #:clear-configuration #:register-clear-configuration-hook\n   #:resolve-location #:location-designator-p #:location-function-p #:*here-directory*\n   #:resolve-relative-location #:resolve-absolute-location #:upgrade-configuration\n   #:uiop-directory))\n(in-package :uiop/configuration)\n\n(with-upgradability ()\n  (define-condition invalid-configuration ()\n    ((form :reader condition-form :initarg :form)\n     (location :reader condition-location :initarg :location)\n     (format :reader condition-format :initarg :format)\n     (arguments :reader condition-arguments :initarg :arguments :initform nil))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<~? (will be skipped)~@:>\")\n                       (condition-format c)\n                       (list* (condition-form c) (condition-location c)\n                              (condition-arguments c))))))\n\n  (defun configuration-inheritance-directive-p (x)\n    \"Is X a configuration inheritance directive?\"\n    (let ((kw '(:inherit-configuration :ignore-inherited-configuration)))\n      (or (member x kw)\n          (and (length=n-p x 1) (member (car x) kw)))))\n\n  (defun report-invalid-form (reporter &rest args)\n    \"Report an invalid form according to REPORTER and various ARGS\"\n    (etypecase reporter\n      (null\n       (apply 'error 'invalid-configuration args))\n      (function\n       (apply reporter args))\n      ((or symbol string)\n       (apply 'error reporter args))\n      (cons\n       (apply 'apply (append reporter args)))))\n\n  (defvar *ignored-configuration-form* nil\n    \"Have configuration forms been ignored while parsing the configuration?\")\n\n  (defun validate-configuration-form (form tag directive-validator\n                                            &key location invalid-form-reporter)\n    \"Validate a configuration FORM. By default it will raise an error if the\nFORM is not valid.  Otherwise it will return the validated form.\n     Arguments control the behavior:\n     The configuration FORM should be of the form (TAG . <rest>)\n     Each element of <rest> will be checked by first seeing if it's a configuration inheritance\ndirective (see CONFIGURATION-INHERITANCE-DIRECTIVE-P) then invoking DIRECTIVE-VALIDATOR\non it.\n     In the event of an invalid form, INVALID-FORM-REPORTER will be used to control\nreporting (see REPORT-INVALID-FORM) with LOCATION providing information about where\nthe configuration form appeared.\"\n    (unless (and (consp form) (eq (car form) tag))\n      (setf *ignored-configuration-form* t)\n      (report-invalid-form invalid-form-reporter :form form :location location)\n      (return-from validate-configuration-form nil))\n    (loop :with inherit = 0 :with ignore-invalid-p = nil :with x = (list tag)\n          :for directive :in (cdr form)\n          :when (cond\n                  ((configuration-inheritance-directive-p directive)\n                   (incf inherit) t)\n                  ((eq directive :ignore-invalid-entries)\n                   (setf ignore-invalid-p t) t)\n                  ((funcall directive-validator directive)\n                   t)\n                  (ignore-invalid-p\n                   nil)\n                  (t\n                   (setf *ignored-configuration-form* t)\n                   (report-invalid-form invalid-form-reporter :form directive :location location)\n                   nil))\n            :do (push directive x)\n          :finally\n             (unless (= inherit 1)\n               (report-invalid-form invalid-form-reporter\n                                    :form form :location location\n                                    ;; we throw away the form and location arguments, hence the ~2*\n                                    ;; this is necessary because of the report in INVALID-CONFIGURATION\n                                    :format (compatfmt \"~@<Invalid source registry ~S~@[ in ~S~]. ~\n                                                        One and only one of ~S or ~S is required.~@:>\")\n                                    :arguments '(:inherit-configuration :ignore-inherited-configuration)))\n             (return (nreverse x))))\n\n  (defun validate-configuration-file (file validator &key description)\n    \"Validate a configuration FILE.  The configuration file should have only one s-expression\nin it, which will be checked with the VALIDATOR FORM.  DESCRIPTION argument used for error\nreporting.\"\n    (let ((forms (read-file-forms file)))\n      (unless (length=n-p forms 1)\n        (error (compatfmt \"~@<One and only one form allowed for ~A. Got: ~3i~_~S~@:>~%\")\n               description forms))\n      (funcall validator (car forms) :location file)))\n\n  (defun validate-configuration-directory (directory tag validator &key invalid-form-reporter)\n    \"Map the VALIDATOR across the .conf files in DIRECTORY, the TAG will\nbe applied to the results to yield a configuration form.  Current\nvalues of TAG include :source-registry and :output-translations.\"\n    (let ((files (sort (ignore-errors ;; SORT w/o COPY-LIST is OK: DIRECTORY returns a fresh list\n                        (remove-if\n                         'hidden-pathname-p\n                         (directory* (make-pathname :name *wild* :type \"conf\" :defaults directory))))\n                       #'string< :key #'namestring)))\n      `(,tag\n        ,@(loop :for file :in files :append\n                                    (loop :with ignore-invalid-p = nil\n                                          :for form :in (read-file-forms file)\n                                          :when (eq form :ignore-invalid-entries)\n                                            :do (setf ignore-invalid-p t)\n                                          :else\n                                            :when (funcall validator form)\n                                              :collect form\n                                          :else\n                                            :when ignore-invalid-p\n                                              :do (setf *ignored-configuration-form* t)\n                                          :else\n                                            :do (report-invalid-form invalid-form-reporter :form form :location file)))\n        :inherit-configuration)))\n\n  (defun resolve-relative-location (x &key ensure-directory wilden)\n    \"Given a designator X for an relative location, resolve it to a pathname.\"\n    (ensure-pathname\n     (etypecase x\n       (null nil)\n       (pathname x)\n       (string (parse-unix-namestring\n                x :ensure-directory ensure-directory))\n       (cons\n        (if (null (cdr x))\n            (resolve-relative-location\n             (car x) :ensure-directory ensure-directory :wilden wilden)\n            (let* ((car (resolve-relative-location\n                         (car x) :ensure-directory t :wilden nil)))\n              (merge-pathnames*\n               (resolve-relative-location\n                (cdr x) :ensure-directory ensure-directory :wilden wilden)\n               car))))\n       ((eql :*/) *wild-directory*)\n       ((eql :**/) *wild-inferiors*)\n       ((eql :*.*.*) *wild-file*)\n       ((eql :implementation)\n        (parse-unix-namestring\n         (implementation-identifier) :ensure-directory t))\n       ((eql :implementation-type)\n        (parse-unix-namestring\n         (string-downcase (implementation-type)) :ensure-directory t))\n       ((eql :hostname)\n        (parse-unix-namestring (hostname) :ensure-directory t)))\n     :wilden (and wilden (not (pathnamep x)) (not (member x '(:*/ :**/ :*.*.*))))\n     :want-relative t))\n\n  (defvar *here-directory* nil\n    \"This special variable is bound to the currect directory during calls to\nPROCESS-SOURCE-REGISTRY in order that we be able to interpret the :here\ndirective.\")\n\n  (defvar *user-cache* nil\n    \"A specification as per RESOLVE-LOCATION of where the user keeps his FASL cache\")\n\n  (defun resolve-absolute-location (x &key ensure-directory wilden)\n    \"Given a designator X for an absolute location, resolve it to a pathname\"\n    (ensure-pathname\n     (etypecase x\n       (null nil)\n       (pathname x)\n       (string\n        (let ((p #-mcl (parse-namestring x)\n                 #+mcl (probe-posix x)))\n          #+mcl (unless p (error \"POSIX pathname ~S does not exist\" x))\n          (if ensure-directory (ensure-directory-pathname p) p)))\n       (cons\n        (return-from resolve-absolute-location\n          (if (null (cdr x))\n              (resolve-absolute-location\n               (car x) :ensure-directory ensure-directory :wilden wilden)\n              (merge-pathnames*\n               (resolve-relative-location\n                (cdr x) :ensure-directory ensure-directory :wilden wilden)\n               (resolve-absolute-location\n                (car x) :ensure-directory t :wilden nil)))))\n       ((eql :root)\n        ;; special magic! we return a relative pathname,\n        ;; but what it means to the output-translations is\n        ;; \"relative to the root of the source pathname's host and device\".\n        (return-from resolve-absolute-location\n          (let ((p (make-pathname :directory '(:relative))))\n            (if wilden (wilden p) p))))\n       ((eql :home) (user-homedir-pathname))\n       ((eql :here) (resolve-absolute-location\n                     (or *here-directory* (pathname-directory-pathname (truename (load-pathname))))\n                     :ensure-directory t :wilden nil))\n       ((eql :user-cache) (resolve-absolute-location\n                           *user-cache* :ensure-directory t :wilden nil)))\n     :wilden (and wilden (not (pathnamep x)))\n     :resolve-symlinks *resolve-symlinks*\n     :want-absolute t))\n\n  ;; Try to override declaration in previous versions of ASDF.\n  (declaim (ftype (function (t &key (:directory boolean) (:wilden boolean)\n                               (:ensure-directory boolean)) t) resolve-location))\n\n  (defun resolve-location (x &key ensure-directory wilden directory)\n    \"Resolve location designator X into a PATHNAME\"\n    ;; :directory backward compatibility, until 2014-01-16: accept directory as well as ensure-directory\n    (loop :with dirp = (or directory ensure-directory)\n          :with (first . rest) = (if (atom x) (list x) x)\n          :with path = (or (resolve-absolute-location\n                            first :ensure-directory (and (or dirp rest) t)\n                            :wilden (and wilden (null rest)))\n                           (return nil))\n          :for (element . morep) :on rest\n          :for dir = (and (or morep dirp) t)\n          :for wild = (and wilden (not morep))\n          :for sub = (merge-pathnames*\n                      (resolve-relative-location\n                       element :ensure-directory dir :wilden wild)\n                      path)\n          :do (setf path (if (absolute-pathname-p sub) (resolve-symlinks* sub) sub))\n          :finally (return path)))\n\n  (defun location-designator-p (x)\n    \"Is X a designator for a location?\"\n    ;; NIL means \"skip this entry\", or as an output translation, same as translation input.\n    ;; T means \"any input\" for a translation, or as output, same as translation input.\n    (flet ((absolute-component-p (c)\n             (typep c '(or string pathname\n                        (member :root :home :here :user-cache))))\n           (relative-component-p (c)\n             (typep c '(or string pathname\n                        (member :*/ :**/ :*.*.* :implementation :implementation-type)))))\n      (or (typep x 'boolean)\n          (absolute-component-p x)\n          (and (consp x) (absolute-component-p (first x)) (every #'relative-component-p (rest x))))))\n\n  (defun location-function-p (x)\n    \"Is X the specification of a location function?\"\n    ;; Location functions are allowed in output translations, and notably used by ABCL for JAR file support.\n    (and (length=n-p x 2) (eq (car x) :function)))\n\n  (defvar *clear-configuration-hook* '())\n\n  (defun register-clear-configuration-hook (hook-function &optional call-now-p)\n    \"Register a function to be called when clearing configuration\"\n    (register-hook-function '*clear-configuration-hook* hook-function call-now-p))\n\n  (defun clear-configuration ()\n    \"Call the functions in *CLEAR-CONFIGURATION-HOOK*\"\n    (call-functions *clear-configuration-hook*))\n\n  (register-image-dump-hook 'clear-configuration)\n\n  (defun upgrade-configuration ()\n    \"If a previous version of ASDF failed to read some configuration, try again now.\"\n    (when *ignored-configuration-form*\n      (clear-configuration)\n      (setf *ignored-configuration-form* nil)))\n\n\n  (defun get-folder-path (folder)\n    \"Semi-portable implementation of a subset of LispWorks' sys:get-folder-path,\nthis function tries to locate the Windows FOLDER for one of\n:LOCAL-APPDATA, :APPDATA or :COMMON-APPDATA.\n     Returns NIL when the folder is not defined (e.g., not on Windows).\"\n    (or #+(and lispworks os-windows) (sys:get-folder-path folder)\n        ;; read-windows-registry HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\AppData\n        (ecase folder\n          (:local-appdata (or (getenv-absolute-directory \"LOCALAPPDATA\")\n                              (subpathname* (get-folder-path :appdata) \"Local\")))\n          (:appdata (getenv-absolute-directory \"APPDATA\"))\n          (:common-appdata (or (getenv-absolute-directory \"ALLUSERSAPPDATA\")\n                               (subpathname* (getenv-absolute-directory \"ALLUSERSPROFILE\") \"Application Data/\"))))))\n\n\n  ;; Support for the XDG Base Directory Specification\n  (defun xdg-data-home (&rest more)\n    \"Returns an absolute pathname for the directory containing user-specific data files.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (resolve-absolute-location\n     `(,(or (getenv-absolute-directory \"XDG_DATA_HOME\")\n            (os-cond\n             ((os-windows-p) (get-folder-path :local-appdata))\n             (t (subpathname (user-homedir-pathname) \".local/share/\"))))\n       ,more)))\n\n  (defun xdg-config-home (&rest more)\n    \"Returns a pathname for the directory containing user-specific configuration files.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (resolve-absolute-location\n     `(,(or (getenv-absolute-directory \"XDG_CONFIG_HOME\")\n            (os-cond\n             ((os-windows-p) (xdg-data-home \"config/\"))\n             (t (subpathname (user-homedir-pathname) \".config/\"))))\n       ,more)))\n\n  (defun xdg-data-dirs (&rest more)\n    \"The preference-ordered set of additional paths to search for data files.\nReturns a list of absolute directory pathnames.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (mapcar #'(lambda (d) (resolve-location `(,d ,more)))\n            (or (remove nil (getenv-absolute-directories \"XDG_DATA_DIRS\"))\n                (os-cond\n                 ((os-windows-p) (mapcar 'get-folder-path '(:appdata :common-appdata)))\n                 ;; macOS' separate read-only system volume means that the contents\n                 ;; of /usr/share are frozen by Apple. Unlike when running natively\n                 ;; on macOS, Genera must access the filesystem through NFS. Attempting\n                 ;; to export either the root (/) or /usr/share simply doesn't work.\n                 ;; (Genera will go into an infinite loop trying to access those mounts.)\n                 ;; So, when running Genera on macOS, only search /usr/local/share.\n                 ((os-genera-p)\n                  #+Genera (sys:system-case\n                            (darwin-vlm (mapcar 'parse-unix-namestring '(\"/usr/local/share/\")))\n                            (otherwise (mapcar 'parse-unix-namestring '(\"/usr/local/share/\" \"/usr/share/\")))))\n                 (t (mapcar 'parse-unix-namestring '(\"/usr/local/share/\" \"/usr/share/\")))))))\n\n  (defun xdg-config-dirs (&rest more)\n    \"The preference-ordered set of additional base paths to search for configuration files.\nReturns a list of absolute directory pathnames.\nMORE may contain specifications for a subpath relative to these directories:\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (mapcar #'(lambda (d) (resolve-location `(,d ,more)))\n            (or (remove nil (getenv-absolute-directories \"XDG_CONFIG_DIRS\"))\n                (os-cond\n                 ((os-windows-p) (xdg-data-dirs \"config/\"))\n                 (t (mapcar 'parse-unix-namestring '(\"/etc/xdg/\")))))))\n\n  (defun xdg-cache-home (&rest more)\n    \"The base directory relative to which user specific non-essential data files should be stored.\nReturns an absolute directory pathname.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (resolve-absolute-location\n     `(,(or (getenv-absolute-directory \"XDG_CACHE_HOME\")\n            (os-cond\n             ((os-windows-p) (xdg-data-home \"cache/\"))\n             (t (subpathname* (user-homedir-pathname) \".cache/\"))))\n       ,more)))\n\n  (defun xdg-runtime-dir (&rest more)\n    \"Pathname for user-specific non-essential runtime files and other file objects,\nsuch as sockets, named pipes, etc.\nReturns an absolute directory pathname.\nMORE may contain specifications for a subpath relative to this directory: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    ;; The XDG spec says that if not provided by the login system, the application should\n    ;; issue a warning and provide a replacement. UIOP is not equipped to do that and returns NIL.\n    (resolve-absolute-location `(,(getenv-absolute-directory \"XDG_RUNTIME_DIR\") ,more)))\n\n  ;;; NOTE: modified the docstring because \"system user configuration\n  ;;; directories\" seems self-contradictory. I'm not sure my wording is right.\n  (defun system-config-pathnames (&rest more)\n    \"Return a list of directories where are stored the system's default user configuration information.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (declare (ignorable more))\n    (os-cond\n     ((os-unix-p) (list (resolve-absolute-location `(,(parse-unix-namestring \"/etc/\") ,more))))))\n\n  (defun filter-pathname-set (dirs)\n    \"Parse strings as unix namestrings and remove duplicates and non absolute-pathnames in a list.\"\n    (remove-duplicates (remove-if-not #'absolute-pathname-p dirs) :from-end t :test 'equal))\n\n  (defun xdg-data-pathnames (&rest more)\n    \"Return a list of absolute pathnames for application data directories.  With APP,\nreturns directory for data for that application, without APP, returns the set of directories\nfor storing all application configurations.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (filter-pathname-set\n     `(,(xdg-data-home more)\n       ,@(xdg-data-dirs more))))\n\n  (defun xdg-config-pathnames (&rest more)\n    \"Return a list of pathnames for application configuration.\nMORE may contain specifications for a subpath relative to these directories: a\nsubpathname specification and keyword arguments as per RESOLVE-LOCATION \\(see\nalso \\\"Configuration DSL\\\"\\) in the ASDF manual.\"\n    (filter-pathname-set\n     `(,(xdg-config-home more)\n       ,@(xdg-config-dirs more))))\n\n  (defun find-preferred-file (files &key (direction :input))\n    \"Find first file in the list of FILES that exists (for direction :input or :probe)\nor just the first one (for direction :output or :io).\n    Note that when we say \\\"file\\\" here, the files in question may be directories.\"\n    (find-if (ecase direction ((:probe :input) 'probe-file*) ((:output :io) 'identity)) files))\n\n  (defun xdg-data-pathname (&optional more (direction :input))\n    (find-preferred-file (xdg-data-pathnames more) :direction direction))\n\n  (defun xdg-config-pathname (&optional more (direction :input))\n    (find-preferred-file (xdg-config-pathnames more) :direction direction))\n\n  (defun compute-user-cache ()\n    \"Compute (and return) the location of the default user-cache for translate-output\nobjects. Side-effects for cached file location computation.\"\n    (setf *user-cache* (xdg-cache-home \"common-lisp\" :implementation)))\n  (register-image-restore-hook 'compute-user-cache)\n\n  (defun uiop-directory ()\n    \"Try to locate the UIOP source directory at runtime\"\n    (labels ((pf (x) (ignore-errors (probe-file* x)))\n             (sub (x y) (pf (subpathname x y)))\n             (ssd (x) (ignore-errors (symbol-call :asdf :system-source-directory x))))\n      ;; NB: conspicuously *not* including searches based on #.(current-lisp-pathname)\n      (or\n       ;; Look under uiop if available as source override, under asdf if avaiable as source\n       (ssd \"uiop\")\n       (sub (ssd \"asdf\") \"uiop/\")\n       ;; Look in recommended path for user-visible source installation\n       (sub (user-homedir-pathname) \"common-lisp/asdf/uiop/\")\n       ;; Look in XDG paths under known package names for user-invisible source installation\n       (xdg-data-pathname \"common-lisp/source/asdf/uiop/\")\n       (xdg-data-pathname \"common-lisp/source/cl-asdf/uiop/\") ; traditional Debian location\n       ;; The last one below is useful for Fare, primary (sole?) known user\n       (sub (user-homedir-pathname) \"cl/asdf/uiop/\")\n       (cerror \"Configure source registry to include UIOP source directory and retry.\"\n               \"Unable to find UIOP directory\")\n       (uiop-directory)))))\n;;; -------------------------------------------------------------------------\n;;; Hacks for backward-compatibility with older versions of UIOP\n\n(uiop/package:define-package :uiop/backward-driver\n  (:recycle :uiop/backward-driver :asdf/backward-driver :uiop)\n  (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/version\n   :uiop/pathname :uiop/stream :uiop/os :uiop/image\n   :uiop/run-program :uiop/lisp-build :uiop/configuration)\n  (:export\n   #:coerce-pathname\n   #:user-configuration-directories #:system-configuration-directories\n   #:in-first-directory #:in-user-configuration-directory #:in-system-configuration-directory\n   #:version-compatible-p))\n(in-package :uiop/backward-driver)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n(with-deprecation ((version-deprecation *uiop-version* :style-warning \"3.2\" :warning \"3.4\"))\n  ;; Backward compatibility with ASDF 2.000 to 2.26\n\n  ;; For backward-compatibility only, for people using internals\n  ;; Reported users in quicklisp 2015-11: hu.dwim.asdf (removed in next release)\n  ;; Will be removed after 2015-12.\n  (defun coerce-pathname (name &key type defaults)\n    \"DEPRECATED. Please use UIOP:PARSE-UNIX-NAMESTRING instead.\"\n    (parse-unix-namestring name :type type :defaults defaults))\n\n  ;; Backward compatibility for ASDF 2.27 to 3.1.4\n  (defun user-configuration-directories ()\n    \"Return the current user's list of user configuration directories\nfor configuring common-lisp.\nDEPRECATED. Use UIOP:XDG-CONFIG-PATHNAMES instead.\"\n    (xdg-config-pathnames \"common-lisp\"))\n  (defun system-configuration-directories ()\n    \"Return the list of system configuration directories for common-lisp.\nDEPRECATED. Use UIOP:SYSTEM-CONFIG-PATHNAMES (with argument \\\"common-lisp\\\"),\ninstead.\"\n    (system-config-pathnames \"common-lisp\"))\n  (defun in-first-directory (dirs x &key (direction :input))\n    \"Finds the first appropriate file named X in the list of DIRS for I/O\nin DIRECTION \\(which may be :INPUT, :OUTPUT, :IO, or :PROBE).\nIf direction is :INPUT or :PROBE, will return the first extant file named\nX in one of the DIRS.\nIf direction is :OUTPUT or :IO, will simply return the file named X in the\nfirst element of DIRS that exists. DEPRECATED.\"\n    (find-preferred-file\n     (mapcar #'(lambda (dir) (subpathname (ensure-directory-pathname dir) x)) dirs)\n     :direction direction))\n  (defun in-user-configuration-directory (x &key (direction :input))\n    \"Return the file named X in the user configuration directory for common-lisp.\nDEPRECATED.\"\n    (xdg-config-pathname `(\"common-lisp\" ,x) direction))\n  (defun in-system-configuration-directory (x &key (direction :input))\n    \"Return the pathname for the file named X under the system configuration directory\nfor common-lisp. DEPRECATED.\"\n    (find-preferred-file (system-config-pathnames \"common-lisp\" x) :direction direction))\n\n\n  ;; Backward compatibility with ASDF 1 to ASDF 2.32\n\n  (defun version-compatible-p (provided-version required-version)\n    \"Is the provided version a compatible substitution for the required-version?\nIf major versions differ, it's not compatible.\nIf they are equal, then any later version is compatible,\nwith later being determined by a lexicographical comparison of minor numbers.\nDEPRECATED.\"\n    (let ((x (parse-version provided-version nil))\n          (y (parse-version required-version nil)))\n      (and x y (= (car x) (car y)) (lexicographic<= '< (cdr y) (cdr x)))))))\n\n;;;; ---------------------------------------------------------------------------\n;;;; Re-export all the functionality in UIOP\n\n(uiop/package:define-package :uiop/driver\n  (:nicknames :uiop ;; Official name we recommend should be used for all references to uiop symbols.\n              :asdf/driver) ;; DO NOT USE, a deprecated name, not supported anymore.\n  ;; We should remove the name :asdf/driver at some point,\n  ;; but not until it has been eradicated from Quicklisp for a year or two.\n  ;; The last known user was cffi (PR merged in May 2020).\n  (:use :uiop/common-lisp)\n  ;; NB: We are not reexporting uiop/common-lisp\n  ;; which include all of CL with compatibility modifications on select platforms,\n  ;; because that would cause potential conflicts for packages that\n  ;; might want to :use (:cl :uiop) or :use (:closer-common-lisp :uiop), etc.\n  (:use-reexport\n   :uiop/package* :uiop/utility :uiop/version\n   :uiop/os :uiop/pathname :uiop/filesystem :uiop/stream :uiop/image\n   :uiop/launch-program :uiop/run-program\n   :uiop/lisp-build :uiop/configuration :uiop/backward-driver))\n\n;; Provide both lowercase and uppercase, to satisfy more implementations.\n(provide \"uiop\") (provide \"UIOP\")\n;;;; -------------------------------------------------------------------------\n;;;; Handle upgrade as forward- and backward-compatibly as possible\n;; See https://bugs.launchpad.net/asdf/+bug/485687\n\n(uiop/package:define-package :asdf/upgrade\n  (:recycle :asdf/upgrade :asdf)\n  (:use :uiop/common-lisp :uiop)\n  (:export\n   #:asdf-version #:*previous-asdf-versions* #:*asdf-version*\n   #:asdf-message #:*verbose-out*\n   #:upgrading-p #:when-upgrading #:upgrade-asdf #:defparameter*\n   #:*post-upgrade-cleanup-hook* #:cleanup-upgraded-asdf\n   ;; There will be no symbol left behind!\n   #:with-asdf-deprecation\n   #:intern*\n   #:asdf-install-warning)\n  (:import-from :uiop/package #:intern* #:find-symbol*))\n(in-package :asdf/upgrade)\n\n;;; Special magic to detect if this is an upgrade\n\n(with-upgradability ()\n  (defun asdf-version ()\n    \"Exported interface to the version of ASDF currently installed. A string.\nYou can compare this string with e.g.: (ASDF:VERSION-SATISFIES (ASDF:ASDF-VERSION) \\\"3.4.5.67\\\").\"\n    (when (find-package :asdf)\n      (or (symbol-value (find-symbol (string :*asdf-version*) :asdf))\n          (let* ((revsym (find-symbol (string :*asdf-revision*) :asdf))\n                 (rev (and revsym (boundp revsym) (symbol-value revsym))))\n            (etypecase rev\n              (string rev)\n              (cons (format nil \"~{~D~^.~}\" rev))\n              (null \"1.0\"))))))\n  ;; This (private) variable contains a list of versions of previously loaded variants of ASDF,\n  ;; from which ASDF was upgraded.\n  ;; Important: define *p-a-v* /before/ *a-v* so that they initialize correctly.\n  (defvar *previous-asdf-versions*\n    (let ((previous (asdf-version)))\n      (when previous\n        ;; Punt on upgrade from ASDF1 or ASDF2, by renaming (or deleting) the package.\n        (when (version< previous \"2.27\") ;; 2.27 is the first to have the :asdf3 feature.\n          (let ((away (format nil \"~A-~A\" :asdf previous)))\n            (rename-package :asdf away)\n            (when *load-verbose*\n              (format t \"~&; Renamed old ~A package away to ~A~%\" :asdf away))))\n        (list previous))))\n  ;; This public variable will be bound shortly to the currently loaded version of ASDF.\n  (defvar *asdf-version* nil)\n  ;; We need to clear systems from versions older than the one in this (private) parameter.\n  ;; The latest incompatible defclass is 2.32.13 renaming a slot in component,\n  ;; or 3.2.0.2 for CCL (incompatibly changing some superclasses).\n  ;; the latest incompatible gf change is in 3.1.7.20 (see redefined-functions below).\n  (defparameter *oldest-forward-compatible-asdf-version* \"3.2.0.2\")\n  ;; Semi-private variable: a designator for a stream on which to output ASDF progress messages\n  (defvar *verbose-out* nil)\n  ;; Private function by which ASDF outputs progress messages and warning messages:\n  (defun asdf-message (format-string &rest format-args)\n    (when *verbose-out* (apply 'format *verbose-out* format-string format-args)))\n  ;; Private hook for functions to run after ASDF has upgraded itself from an older variant:\n  (defvar *post-upgrade-cleanup-hook* ())\n  ;; Private variable for post upgrade cleanup to communicate if an upgrade has\n  ;; actually occured.\n  (defvar *asdf-upgraded-p*)\n  ;; Private function to detect whether the current upgrade counts as an incompatible\n  ;; data schema upgrade implying the need to drop data.\n  (defun upgrading-p (&optional (oldest-compatible-version *oldest-forward-compatible-asdf-version*))\n    (and *previous-asdf-versions*\n         (version< (first *previous-asdf-versions*) oldest-compatible-version)))\n  ;; Private variant of defparameter that works in presence of incompatible upgrades:\n  ;; behaves like defvar in a compatible upgrade (e.g. reloading system after simple code change),\n  ;; but behaves like defparameter if in presence of an incompatible upgrade.\n  (defmacro defparameter* (var value &optional docstring (version *oldest-forward-compatible-asdf-version*))\n    (let* ((name (string-trim \"*\" var))\n           (valfun (intern (format nil \"%~A-~A-~A\" :compute name :value))))\n      `(progn\n         (defun ,valfun () ,value)\n         (defvar ,var (,valfun) ,@(ensure-list docstring))\n         (when (upgrading-p ,version)\n           (setf ,var (,valfun))))))\n  ;; Private macro to declare sections of code that are only compiled and run when upgrading.\n  ;; The use of eval portably ensures that the code will not have adverse compile-time side-effects,\n  ;; whereas the use of handler-bind portably ensures that it will not issue warnings when it runs.\n  (defmacro when-upgrading ((&key (version *oldest-forward-compatible-asdf-version*)\n                               (upgrading-p `(upgrading-p ,version)) when) &body body)\n    \"A wrapper macro for code that should only be run when upgrading a\npreviously-loaded version of ASDF.\"\n    `(with-upgradability ()\n       (when (and ,upgrading-p ,@(when when `(,when)))\n         (handler-bind ((style-warning #'muffle-warning))\n           (eval '(progn ,@body))))))\n  ;; Only now can we safely update the version.\n  (let* (;; For bug reporting sanity, please always bump this version when you modify this file.\n         ;; Please also modify asdf.asd to reflect this change. make bump-version v=3.4.5.67.8\n         ;; can help you do these changes in synch (look at the source for documentation).\n         ;; Relying on its automation, the version is now redundantly present on top of asdf.lisp.\n         ;; \"3.4\" would be the general branch for major version 3, minor version 4.\n         ;; \"3.4.5\" would be an official release in the 3.4 branch.\n         ;; \"3.4.5.67\" would be a development version in the official branch, on top of 3.4.5.\n         ;; \"3.4.5.0.8\" would be your eighth local modification of official release 3.4.5\n         ;; \"3.4.5.67.8\" would be your eighth local modification of development version 3.4.5.67\n         (asdf-version \"3.3.7\")\n         (existing-version (asdf-version)))\n    (setf *asdf-version* asdf-version)\n    (when (and existing-version (not (equal asdf-version existing-version)))\n      (push existing-version *previous-asdf-versions*)\n      (when (or *verbose-out* *load-verbose*)\n        (format (or *verbose-out* *trace-output*)\n                (compatfmt \"~&~@<; ~@;Upgrading ASDF ~@[from version ~A ~]to version ~A~@:>~%\")\n                existing-version asdf-version)))))\n\n;;; Upon upgrade, specially frob some functions and classes that are being incompatibly redefined\n(when-upgrading ()\n  (let* ((previous-version (first *previous-asdf-versions*))\n         (redefined-functions ;; List of functions that changed incompatibly since 2.27:\n          ;; gf signature changed, defun that became a generic function (but not way around),\n          ;; method removed that will mess up with new ones\n          ;; (especially :around :before :after, more specific or call-next-method'ed method)\n          ;; and/or semantics otherwise modified. Oops.\n          ;; NB: it's too late to do anything about functions in UIOP!\n          ;; If you introduce some critical incompatibility there, you MUST change the function name.\n          ;; Note that we don't need do anything about functions that changed incompatibly\n          ;; from ASDF 2.26 or earlier: we wholly punt on the entire ASDF package in such an upgrade.\n          ;; Also, the strong constraints apply most importantly for functions called from\n          ;; the continuation of compiling or loading some of the code in ASDF or UIOP.\n          ;; See discussion at https://gitlab.common-lisp.net/asdf/asdf/merge_requests/36\n          ;; and at https://gitlab.common-lisp.net/asdf/asdf/-/merge_requests/141\n          `(,@(when (version< previous-version \"2.31\") '(#:normalize-version)) ;; pathname became &key\n            ,@(when (version< previous-version \"3.1.2\") '(#:component-depends-on #:input-files)) ;; crucial methods *removed* before 3.1.2\n            ,@(when (version< previous-version \"3.1.7.20\") '(#:find-component)))) ;; added &key registered\n         (redefined-classes\n          ;; with the old ASDF during upgrade, and many implementations bork\n          (when (or #+(or clozure mkcl) t)\n            '((#:compile-concatenated-source-op (#:operation) ())\n              (#:compile-bundle-op (#:operation) ())\n              (#:concatenate-source-op (#:operation) ())\n              (#:dll-op (#:operation) ())\n              (#:lib-op (#:operation) ())\n              (#:monolithic-compile-bundle-op (#:operation) ())\n              (#:monolithic-concatenate-source-op (#:operation) ())))))\n    (loop :for name :in redefined-functions\n      :for sym = (find-symbol* name :asdf nil)\n      :do (when sym (fmakunbound sym)))\n    (labels ((asym (x) (multiple-value-bind (s p)\n                           (if (consp x) (values (car x) (cadr x)) (values x :asdf))\n                         (find-symbol* s p nil)))\n             (asyms (l) (mapcar #'asym l)))\n      (loop :for (name superclasses slots) :in redefined-classes\n            :for sym = (find-symbol* name :asdf nil)\n            :when (and sym (find-class sym))\n              :do #+ccl (eval `(defclass ,sym ,(asyms superclasses) ,(asyms slots)))\n                  #-ccl (setf (find-class sym) nil))))) ;; mkcl\n\n;;; Self-upgrade functions\n(with-upgradability ()\n  ;; This private function is called at the end of asdf/footer and ensures that,\n  ;; *if* this loading of ASDF was an upgrade, then all registered cleanup functions will be called.\n  (defun cleanup-upgraded-asdf (&optional (old-version (first *previous-asdf-versions*)))\n    (let ((new-version (asdf-version)))\n      (unless (equal old-version new-version)\n        (push new-version *previous-asdf-versions*)\n        (when (boundp '*asdf-upgraded-p*)\n          (setf *asdf-upgraded-p* t))\n        (when old-version\n          (if (version<= new-version old-version)\n              (error (compatfmt \"~&~@<; ~@;Downgraded ASDF from version ~A to version ~A~@:>~%\")\n                     old-version new-version)\n              (asdf-message (compatfmt \"~&~@<; ~@;Upgraded ASDF from version ~A to version ~A~@:>~%\")\n                            old-version new-version))\n          ;; In case the previous version was too old to be forward-compatible, clear systems.\n          ;; TODO: if needed, we may have to define a separate hook to run\n          ;; in case of forward-compatible upgrade.\n          ;; Or to move the tests forward-compatibility test inside each hook function?\n          (unless (version<= *oldest-forward-compatible-asdf-version* old-version)\n            (call-functions (reverse *post-upgrade-cleanup-hook*)))\n          t))))\n\n  (define-condition asdf-install-warning (simple-condition warning)\n    ((format-control\n      :initarg :format-control)\n     (format-arguments\n      :initarg :format-arguments\n      :initform nil))\n    (:documentation \"Warning class for issues related to upgrading or loading ASDF.\")\n    (:report (lambda (c s)\n               (format s \"WARNING: ~?\"\n                       (slot-value c 'format-control)\n                       (slot-value c 'format-arguments)))))\n\n\n  (defun upgrade-asdf ()\n    \"Try to upgrade of ASDF. If a different version was used, return T.\n   We need do that before we operate on anything that may possibly depend on ASDF.\"\n    (let ((*load-print* nil)\n          (*compile-print* nil)\n          (*asdf-upgraded-p* nil))\n      (handler-bind (((or style-warning) #'muffle-warning))\n        (symbol-call :asdf :load-system :asdf :verbose nil))\n      *asdf-upgraded-p*))\n\n  (defmacro with-asdf-deprecation ((&rest keys &key &allow-other-keys) &body body)\n    `(with-upgradability ()\n       (with-deprecation ((version-deprecation *asdf-version* ,@keys))\n         ,@body))))\n;;;; -------------------------------------------------------------------------\n;;;; Session\n\n(uiop/package:define-package :asdf/session\n  (:recycle :asdf/session :asdf/cache :asdf/component\n            :asdf/action :asdf/find-system :asdf/plan :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade)\n  (:export\n   #:get-file-stamp #:compute-file-stamp #:register-file-stamp\n   #:asdf-cache #:set-asdf-cache-entry #:unset-asdf-cache-entry #:consult-asdf-cache\n   #:do-asdf-cache #:normalize-namestring\n   #:call-with-asdf-session #:with-asdf-session\n   #:*asdf-session* #:*asdf-session-class* #:session #:toplevel-asdf-session\n   #:session-cache #:forcing #:asdf-upgraded-p\n   #:visited-actions #:visiting-action-set #:visiting-action-list\n   #:total-action-count #:planned-action-count #:planned-output-action-count\n   #:clear-configuration-and-retry #:retry\n   #:operate-level\n   ;; conditions\n   #:system-definition-error ;; top level, moved here because this is the earliest place for it.\n   #:formatted-system-definition-error #:format-control #:format-arguments #:sysdef-error))\n(in-package :asdf/session)\n\n\n(with-upgradability ()\n  ;; The session variable.\n  ;; NIL when outside a session.\n  (defvar *asdf-session* nil)\n  (defparameter* *asdf-session-class* 'session\n    \"The default class for sessions\")\n\n  (defclass session ()\n    (;; The ASDF session cache is used to memoize some computations.\n     ;; It is instrumental in achieving:\n     ;; * Consistency in the view of the world relied on by ASDF within a given session.\n     ;;   Inconsistencies in file stamps, system definitions, etc., could cause infinite loops\n     ;;   (a.k.a. stack overflows) and other erratic behavior.\n     ;; * Speed and reliability of ASDF, with fewer side-effects from access to the filesystem, and\n     ;;   no expensive recomputations of transitive dependencies for input-files or output-files.\n     ;; * Testability of ASDF with the ability to fake timestamps without actually touching files.\n     (ancestor\n      :initform nil :initarg :ancestor :reader session-ancestor\n      :documentation \"Top level session that this is part of\")\n     (session-cache\n      :initform (make-hash-table :test 'equal) :initarg :session-cache :reader session-cache\n      :documentation \"Memoize expensive computations\")\n     (operate-level\n      :initform 0 :initarg :operate-level :accessor session-operate-level\n      :documentation \"Number of nested calls to operate we're under (for toplevel session only)\")\n     ;; shouldn't the below be superseded by the session-wide caching of action-status\n     ;; for (load-op \"asdf\") ?\n     (asdf-upgraded-p\n      :initform nil :initarg :asdf-upgraded-p :accessor asdf-upgraded-p\n      :documentation \"Was ASDF already upgraded in this session - only valid for toplevel-asdf-session.\")\n     (forcing\n      :initform nil :initarg :forcing :accessor forcing\n      :documentation \"Forcing parameters for the session\")\n     ;; Table that to actions already visited while walking the dependencies associates status\n     (visited-actions :initform (make-hash-table :test 'equal) :accessor visited-actions)\n     ;; Actions that depend on those being currently walked through, to detect circularities\n     (visiting-action-set ;; as a set\n      :initform (make-hash-table :test 'equal) :accessor visiting-action-set)\n     (visiting-action-list :initform () :accessor visiting-action-list) ;; as a list\n     ;; Counts of total actions in plan\n     (total-action-count :initform 0 :accessor total-action-count)\n     ;; Count of actions that need to be performed\n     (planned-action-count :initform 0 :accessor planned-action-count)\n     ;; Count of actions that need to be performed that have a non-empty list of output-files.\n     (planned-output-action-count :initform 0 :accessor planned-output-action-count))\n    (:documentation \"An ASDF session with a cache to memoize some computations\"))\n\n  (defun toplevel-asdf-session ()\n    (when *asdf-session* (or (session-ancestor *asdf-session*) *asdf-session*)))\n\n  (defun operate-level ()\n    (session-operate-level (toplevel-asdf-session)))\n\n  (defun (setf operate-level) (new-level)\n    (setf (session-operate-level (toplevel-asdf-session)) new-level))\n\n  (defun asdf-cache ()\n    (session-cache *asdf-session*))\n\n  ;; Set a session cache entry for KEY to a list of values VALUE-LIST, when inside a session.\n  ;; Return those values.\n  (defun set-asdf-cache-entry (key value-list)\n    (values-list (if *asdf-session*\n                     (setf (gethash key (asdf-cache)) value-list)\n                     value-list)))\n\n  ;; Unset the session cache entry for KEY, when inside a session.\n  (defun unset-asdf-cache-entry (key)\n    (when *asdf-session*\n      (remhash key (session-cache *asdf-session*))))\n\n  ;; Consult the session cache entry for KEY if present and in a session;\n  ;; if not present, compute it by calling the THUNK,\n  ;; and set the session cache entry accordingly, if in a session.\n  ;; Return the values from the cache and/or the thunk computation.\n  (defun consult-asdf-cache (key &optional thunk)\n    (if *asdf-session*\n        (multiple-value-bind (results foundp) (gethash key (session-cache *asdf-session*))\n          (if foundp\n              (values-list results)\n              (set-asdf-cache-entry key (multiple-value-list (call-function thunk)))))\n        (call-function thunk)))\n\n  ;; Syntactic sugar for consult-asdf-cache\n  (defmacro do-asdf-cache (key &body body)\n    `(consult-asdf-cache ,key #'(lambda () ,@body)))\n\n  ;; Compute inside a ASDF session with a cache.\n  ;; First, make sure an ASDF session is underway, by binding the session cache variable\n  ;; to a new hash-table if it's currently null (or even if it isn't, if OVERRIDE is true).\n  ;; Second, if a new session was started, establish restarts for retrying the overall computation.\n  ;; Finally, consult the cache if a KEY was specified with the THUNK as a fallback when the cache\n  ;; entry isn't found, or just call the THUNK if no KEY was specified.\n  (defun call-with-asdf-session (thunk &key override key override-cache override-forcing)\n    (let ((fun (if key #'(lambda () (consult-asdf-cache key thunk)) thunk)))\n      (if (and (not override) *asdf-session*)\n          (funcall fun)\n          (loop\n            (restart-case\n                (let ((*asdf-session*\n                       (apply 'make-instance *asdf-session-class*\n                              (when *asdf-session*\n                                `(:ancestor ,(toplevel-asdf-session)\n                                  ,@(unless override-forcing\n                                      `(:forcing ,(forcing *asdf-session*)))\n                                  ,@(unless override-cache\n                                      `(:session-cache ,(session-cache *asdf-session*))))))))\n                  (return (funcall fun)))\n              (retry ()\n                :report (lambda (s)\n                          (format s (compatfmt \"~@<Retry ASDF operation.~@:>\"))))\n              (clear-configuration-and-retry ()\n                :report (lambda (s)\n                          (format s (compatfmt \"~@<Retry ASDF operation after resetting the configuration.~@:>\")))\n                (unless (null *asdf-session*)\n                  (clrhash (session-cache *asdf-session*)))\n                (clear-configuration)))))))\n\n  ;; Syntactic sugar for call-with-asdf-session\n  (defmacro with-asdf-session ((&key key override override-cache override-forcing) &body body)\n    `(call-with-asdf-session\n      #'(lambda () ,@body)\n      :override ,override :key ,key\n      :override-cache ,override-cache :override-forcing ,override-forcing))\n\n\n  ;;; Define specific accessor for file (date) stamp.\n\n  ;; Normalize a namestring for use as a key in the session cache.\n  (defun normalize-namestring (pathname)\n    (let ((resolved (resolve-symlinks*\n                     (ensure-absolute-pathname\n                      (physicalize-pathname pathname)\n                      'get-pathname-defaults))))\n      (with-pathname-defaults () (namestring resolved))))\n\n  ;; Compute the file stamp for a normalized namestring\n  (defun compute-file-stamp (normalized-namestring)\n    (with-pathname-defaults ()\n      (or (safe-file-write-date normalized-namestring) t)))\n\n  ;; Override the time STAMP associated to a given FILE in the session cache.\n  ;; If no STAMP is specified, recompute a new one from the filesystem.\n  (defun register-file-stamp (file &optional (stamp nil stampp))\n    (let* ((namestring (normalize-namestring file))\n           (stamp (if stampp stamp (compute-file-stamp namestring))))\n      (set-asdf-cache-entry `(get-file-stamp ,namestring) (list stamp))))\n\n  ;; Get or compute a memoized stamp for given FILE from the session cache.\n  (defun get-file-stamp (file)\n    (when file\n      (let ((namestring (normalize-namestring file)))\n        (do-asdf-cache `(get-file-stamp ,namestring) (compute-file-stamp namestring)))))\n\n\n  ;;; Conditions\n\n  (define-condition system-definition-error (error) ()\n    ;; [this use of :report should be redundant, but unfortunately it's not.\n    ;; cmucl's lisp::output-instance prefers the kernel:slot-class-print-function\n    ;; over print-object; this is always conditions::%print-condition for\n    ;; condition objects, which in turn does inheritance of :report options at\n    ;; run-time.  fortunately, inheritance means we only need this kludge here in\n    ;; order to fix all conditions that build on it.  -- rgr, 28-Jul-02.]\n    #+cmucl (:report print-object))\n\n  (define-condition formatted-system-definition-error (system-definition-error)\n    ((format-control :initarg :format-control :reader format-control)\n     (format-arguments :initarg :format-arguments :reader format-arguments))\n    (:report (lambda (c s)\n               (apply 'format s (format-control c) (format-arguments c)))))\n\n  (defun sysdef-error (format &rest arguments)\n    (error 'formatted-system-definition-error :format-control\n           format :format-arguments arguments)))\n;;;; -------------------------------------------------------------------------\n;;;; Components\n\n(uiop/package:define-package :asdf/component\n  (:recycle :asdf/component :asdf/find-component :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session)\n  (:export\n   #:component #:component-find-path\n   #:find-component ;; methods defined in find-component\n   #:component-name #:component-pathname #:component-relative-pathname\n   #:component-parent #:component-system #:component-parent-pathname\n   #:child-component #:parent-component #:module\n   #:file-component\n   #:source-file #:c-source-file #:java-source-file\n   #:static-file #:doc-file #:html-file\n   #:file-type\n   #:source-file-type #:source-file-explicit-type ;; backward-compatibility\n   #:component-in-order-to #:component-sideway-dependencies\n   #:component-if-feature #:around-compile-hook\n   #:component-description #:component-long-description\n   #:component-version #:version-satisfies\n   #:component-inline-methods ;; backward-compatibility only. DO NOT USE!\n   #:component-operation-times ;; For internal use only.\n   ;; portable ASDF encoding and implementation-specific external-format\n   #:component-external-format #:component-encoding\n   #:component-children-by-name #:component-children #:compute-children-by-name\n   #:component-build-operation\n   #:module-default-component-class\n   #:module-components ;; backward-compatibility. DO NOT USE.\n   #:sub-components\n\n   ;; conditions\n   #:duplicate-names\n\n   ;; Internals we'd like to share with the ASDF package, especially for upgrade purposes\n   #:name #:version #:description #:long-description #:author #:maintainer #:licence\n   #:components-by-name #:components #:children #:children-by-name\n   #:default-component-class #:source-file\n   #:defsystem-depends-on ; This symbol retained for backward compatibility.\n   #:sideway-dependencies #:if-feature #:in-order-to #:inline-methods\n   #:relative-pathname #:absolute-pathname #:operation-times #:around-compile\n   #:%encoding #:properties #:component-properties #:parent))\n(in-package :asdf/component)\n\n(with-upgradability ()\n  (defgeneric component-name (component)\n    (:documentation \"Name of the COMPONENT, unique relative to its parent\"))\n  (defgeneric component-system (component)\n    (:documentation \"Top-level system containing the COMPONENT\"))\n  (defgeneric component-pathname (component)\n    (:documentation \"Pathname of the COMPONENT if any, or NIL.\"))\n  (defgeneric component-relative-pathname (component)\n    ;; in ASDF4, rename that to component-specified-pathname ?\n    (:documentation \"Specified pathname of the COMPONENT,\nintended to be merged with the pathname of that component's parent if any, using merged-pathnames*.\nDespite the function's name, the return value can be an absolute pathname, in which case the merge\nwill leave it unmodified.\"))\n  (defgeneric component-external-format (component)\n    (:documentation \"The external-format of the COMPONENT.\nBy default, deduced from the COMPONENT-ENCODING.\"))\n  (defgeneric component-encoding (component)\n    (:documentation \"The encoding of the COMPONENT. By default, only :utf-8 is supported.\nUse asdf-encodings to support more encodings.\"))\n  (defgeneric version-satisfies (component version)\n    (:documentation \"Check whether a COMPONENT satisfies the constraint of being at least as recent\nas the specified VERSION, which must be a string of dot-separated natural numbers, or NIL.\"))\n  (defgeneric component-version (component)\n    (:documentation \"Return the version of a COMPONENT, which must be a string of dot-separated\nnatural numbers, or NIL.\"))\n  (defgeneric (setf component-version) (new-version component)\n    (:documentation \"Updates the version of a COMPONENT, which must be a string of dot-separated\nnatural numbers, or NIL.\"))\n  (defgeneric component-parent (component)\n    (:documentation \"The parent of a child COMPONENT,\nor NIL for top-level components (a.k.a. systems)\"))\n  ;; NIL is a designator for the absence of a component, in which case the parent is also absent.\n  (defmethod component-parent ((component null)) nil)\n\n  ;; Deprecated: Backward compatible way of computing the FILE-TYPE of a component.\n  (with-asdf-deprecation (:style-warning \"3.4\")\n   (defgeneric source-file-type (component system)\n     (:documentation \"DEPRECATED. Use the FILE-TYPE of a COMPONENT instead.\")))\n\n  (define-condition duplicate-names (system-definition-error)\n    ((name :initarg :name :reader duplicate-names-name))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while defining system: multiple components are given same name ~S~@:>\")\n                       (duplicate-names-name c))))))\n\n\n(with-upgradability ()\n  (defclass component ()\n    ((name :accessor component-name :initarg :name :type string :documentation\n           \"Component name: designator for a string composed of portable pathname characters\")\n     ;; We might want to constrain version with\n     ;; :type (and string (satisfies parse-version))\n     ;; but we cannot until we fix all systems that don't use it correctly!\n     (version :accessor component-version :initarg :version :initform nil)\n     (description :accessor component-description :initarg :description :initform nil)\n     (long-description :accessor component-long-description :initarg :long-description :initform nil)\n     (sideway-dependencies :accessor component-sideway-dependencies :initform nil)\n     (if-feature :accessor component-if-feature :initform nil :initarg :if-feature)\n     ;; In the ASDF object model, dependencies exist between *actions*,\n     ;; where an action is a pair of an operation and a component.\n     ;; Dependencies are represented as alists of operations\n     ;; to a list where each entry is a pair of an operation and a list of component specifiers.\n     ;; Up until ASDF 2.26.9, there used to be two kinds of dependencies:\n     ;; in-order-to and do-first, each stored in its own slot. Now there is only in-order-to.\n     ;; in-order-to used to represent things that modify the filesystem (such as compiling a fasl)\n     ;; and do-first things that modify the current image (such as loading a fasl).\n     ;; These are now unified because we now correctly propagate timestamps between dependencies.\n     ;; Happily, no one seems to have used do-first too much (especially since until ASDF 2.017,\n     ;; anything you specified was overridden by ASDF itself anyway), but the name in-order-to remains.\n     ;; The names are bad, but they have been the official API since Dan Barlow's ASDF 1.52!\n     ;; LispWorks's defsystem has caused-by and requires for in-order-to and do-first respectively.\n     ;; Maybe rename the slots in ASDF? But that's not very backward-compatible.\n     ;; See our ASDF 2 paper for more complete explanations.\n     (in-order-to :initform nil :initarg :in-order-to\n                  :accessor component-in-order-to)\n     ;; Methods defined using the \"inline\" style inside a defsystem form:\n     ;; we store them here so we can delete them when the system is re-evaluated.\n     (inline-methods :accessor component-inline-methods :initform nil)\n     ;; ASDF4: rename it from relative-pathname to specified-pathname. It need not be relative.\n     ;; There is no initform and no direct accessor for this specified pathname,\n     ;; so we only access the information through appropriate methods, after it has been processed.\n     ;; Unhappily, some braindead systems directly access the slot. Make them stop before ASDF4.\n     (relative-pathname :initarg :pathname)\n     ;; The absolute-pathname is computed based on relative-pathname and parent pathname.\n     ;; The slot is but a cache used by component-pathname.\n     (absolute-pathname)\n     (operation-times :initform (make-hash-table)\n                      :accessor component-operation-times)\n     (around-compile :initarg :around-compile)\n     ;; Properties are for backward-compatibility with ASDF2 only. DO NOT USE!\n     (properties :accessor component-properties :initarg :properties\n                 :initform nil)\n     (%encoding :accessor %component-encoding :initform nil :initarg :encoding)\n     ;; For backward-compatibility, this slot is part of component rather than of child-component. ASDF4: stop it.\n     (parent :initarg :parent :initform nil :reader component-parent)\n     (build-operation\n      :initarg :build-operation :initform nil :reader component-build-operation)\n     ;; Cache for ADDITIONAL-INPUT-FILES function.\n     (additional-input-files :accessor %additional-input-files :initform nil))\n    (:documentation \"Base class for all components of a build\"))\n\n  (defgeneric find-component (base path &key registered)\n    (:documentation \"Find a component by resolving the PATH starting from BASE parent.\nIf REGISTERED is true, only search currently registered systems.\"))\n\n  (defun component-find-path (component)\n    \"Return a path from a root system to the COMPONENT.\nThe return value is a list of component NAMES; a list of strings.\"\n    (check-type component (or null component))\n    (reverse\n     (loop :for c = component :then (component-parent c)\n           :while c :collect (component-name c))))\n\n  (defmethod print-object ((c component) stream)\n    (print-unreadable-object (c stream :type t :identity nil)\n      (format stream \"~{~S~^ ~}\" (component-find-path c))))\n\n  (defmethod component-system ((component component))\n    (if-let (system (component-parent component))\n      (component-system system)\n      component)))\n\n\n;;;; Component hierarchy within a system\n;; The tree typically but not necessarily follows the filesystem hierarchy.\n(with-upgradability ()\n  (defclass child-component (component) ()\n    (:documentation \"A CHILD-COMPONENT is a COMPONENT that may be part of\na PARENT-COMPONENT.\"))\n\n  (defclass file-component (child-component)\n    ((type :accessor file-type :initarg :type)) ; no default\n    (:documentation \"a COMPONENT that represents a file\"))\n  (defclass source-file (file-component)\n    ((type :accessor source-file-explicit-type ;; backward-compatibility\n           :initform nil))) ;; NB: many systems have come to rely on this default.\n  (defclass c-source-file (source-file)\n    ((type :initform \"c\")))\n  (defclass java-source-file (source-file)\n    ((type :initform \"java\")))\n  (defclass static-file (source-file)\n    ((type :initform nil))\n    (:documentation \"Component for a file to be included as is in the build output\"))\n  (defclass doc-file (static-file) ())\n  (defclass html-file (doc-file)\n    ((type :initform \"html\")))\n\n  (defclass parent-component (component)\n    ((children\n      :initform nil\n      :initarg :components\n      :reader module-components ; backward-compatibility\n      :accessor component-children)\n     (children-by-name\n      :reader module-components-by-name ; backward-compatibility\n      :accessor component-children-by-name)\n     (default-component-class\n      :initform nil\n      :initarg :default-component-class\n      :accessor module-default-component-class))\n  (:documentation \"A PARENT-COMPONENT is a component that may have children.\")))\n\n(with-upgradability ()\n  ;; (Private) Function that given a PARENT component,\n  ;; the list of children of which has been initialized,\n  ;; compute the hash-table in slot children-by-name that allows to retrieve its children by name.\n  ;; If ONLY-IF-NEEDED-P is defined, skip any (re)computation if the slot is already populated.\n  (defun compute-children-by-name (parent &key only-if-needed-p)\n    (unless (and only-if-needed-p (slot-boundp parent 'children-by-name))\n      (let ((hash (make-hash-table :test 'equal)))\n        (setf (component-children-by-name parent) hash)\n        (loop :for c :in (component-children parent)\n              :for name = (component-name c)\n              :for previous = (gethash name hash)\n              :do (when previous (error 'duplicate-names :name name))\n                  (setf (gethash name hash) c))\n        hash))))\n\n(with-upgradability ()\n  (defclass module (child-component parent-component)\n    (#+clisp (components)) ;; backward compatibility during upgrade only\n    (:documentation \"A module is a intermediate component with both a parent and children,\ntypically but not necessarily representing the files in a subdirectory of the build source.\")))\n\n\n;;;; component pathnames\n(with-upgradability ()\n  (defgeneric component-parent-pathname (component)\n    (:documentation \"The pathname of the COMPONENT's parent, if any, or NIL\"))\n  (defmethod component-parent-pathname (component)\n    (component-pathname (component-parent component)))\n\n  ;; The default method for component-pathname tries to extract a cached precomputed\n  ;; absolute-pathname from the relevant slot, and if not, computes it by merging the\n  ;; component-relative-pathname (which should be component-specified-pathname, it can be absolute)\n  ;; with the directory of the component-parent-pathname.\n  (defmethod component-pathname ((component component))\n    (if (slot-boundp component 'absolute-pathname)\n        (slot-value component 'absolute-pathname)\n        (let ((pathname\n                (merge-pathnames*\n                 (component-relative-pathname component)\n                 (pathname-directory-pathname (component-parent-pathname component)))))\n          (unless (or (null pathname) (absolute-pathname-p pathname))\n            (error (compatfmt \"~@<Invalid relative pathname ~S for component ~S~@:>\")\n                   pathname (component-find-path component)))\n          (setf (slot-value component 'absolute-pathname) pathname)\n          pathname)))\n\n  ;; Default method for component-relative-pathname:\n  ;; combine the contents of slot relative-pathname (from specified initarg :pathname)\n  ;; with the appropriate source-file-type, which defaults to the file-type of the component.\n  (defmethod component-relative-pathname ((component component))\n    ;; SOURCE-FILE-TYPE below is strictly for backward-compatibility with ASDF1.\n    ;; We ought to be able to extract this from the component alone with FILE-TYPE.\n    ;; TODO: track who uses it in Quicklisp, and have them not use it anymore;\n    ;; maybe issue a WARNING (then eventually CERROR) if the two methods diverge?\n    (let (#+abcl\n          (parent\n            (component-parent-pathname component)))\n      (parse-unix-namestring\n       (or (and (slot-boundp component 'relative-pathname)\n                (slot-value component 'relative-pathname))\n           (component-name component))\n       :want-relative\n       #-abcl t\n       ;; JAR-PATHNAMES always have absolute directories\n       #+abcl (not (ext:pathname-jar-p parent))\n       :type (source-file-type component (component-system component))\n       :defaults (component-parent-pathname component))))\n\n  (defmethod source-file-type ((component parent-component) (system parent-component))\n    :directory)\n\n  (defmethod source-file-type ((component file-component) (system parent-component))\n    (file-type component)))\n\n\n;;;; Encodings\n(with-upgradability ()\n  (defmethod component-encoding ((c component))\n    (or (loop :for x = c :then (component-parent x)\n              :while x :thereis (%component-encoding x))\n        (detect-encoding (component-pathname c))))\n\n  (defmethod component-external-format ((c component))\n    (encoding-external-format (component-encoding c))))\n\n\n;;;; around-compile-hook\n(with-upgradability ()\n  (defgeneric around-compile-hook (component)\n    (:documentation \"An optional hook function that will be called with one argument, a thunk.\nThe hook function must call the thunk, that will compile code from the component, and may or may not\nalso evaluate the compiled results. The hook function may establish dynamic variable bindings around\nthis compilation, or check its results, etc.\"))\n  (defmethod around-compile-hook ((c component))\n    (cond\n      ((slot-boundp c 'around-compile)\n       (slot-value c 'around-compile))\n      ((component-parent c)\n       (around-compile-hook (component-parent c))))))\n\n\n;;;; version-satisfies\n(with-upgradability ()\n  ;; short-circuit testing of null version specifications.\n  ;; this is an all-pass, without warning\n  (defmethod version-satisfies :around ((c t) (version null))\n    t)\n  (defmethod version-satisfies ((c component) version)\n    (unless (and version (slot-boundp c 'version) (component-version c))\n      (when version\n        (warn \"Requested version ~S but ~S has no version\" version c))\n      (return-from version-satisfies nil))\n    (version-satisfies (component-version c) version))\n\n  (defmethod version-satisfies ((cver string) version)\n    (version<= version cver)))\n\n\n;;; all sub-components (of a given type)\n(with-upgradability ()\n  (defun sub-components (component &key (type t))\n    \"Compute the transitive sub-components of given COMPONENT that are of given TYPE\"\n    (while-collecting (c)\n      (labels ((recurse (x)\n                 (when (if-let (it (component-if-feature x)) (featurep it) t)\n                   (when (typep x type)\n                     (c x))\n                   (when (typep x 'parent-component)\n                     (map () #'recurse (component-children x))))))\n        (recurse component)))))\n\n;;;; -------------------------------------------------------------------------\n;;;; Operations\n\n(uiop/package:define-package :asdf/operation\n  (:recycle :asdf/operation :asdf/action :asdf) ;; asdf/action for FEATURE pre 2.31.5.\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session)\n  (:export\n   #:operation\n   #:*operations* #:make-operation #:find-operation\n   #:feature)) ;; TODO: stop exporting the deprecated FEATURE feature.\n(in-package :asdf/operation)\n\n;;; Operation Classes\n(when-upgrading (:version \"2.27\" :when (find-class 'operation nil))\n  ;; override any obsolete shared-initialize method when upgrading from ASDF2.\n  (defmethod shared-initialize :after ((o operation) (slot-names t) &key)\n    (values)))\n\n(with-upgradability ()\n  (defclass operation ()\n    ()\n    (:documentation \"The base class for all ASDF operations.\n\nASDF does NOT and never did distinguish between multiple operations of the same class.\nTherefore, all slots of all operations MUST have :allocation :class and no initargs. No exceptions.\n\"))\n\n  (defvar *in-make-operation* nil)\n\n  (defun check-operation-constructor ()\n    \"Enforce that OPERATION instances must be created with MAKE-OPERATION.\"\n    (unless *in-make-operation*\n      (sysdef-error \"OPERATION instances must only be created through MAKE-OPERATION.\")))\n\n  (defmethod print-object ((o operation) stream)\n    (print-unreadable-object (o stream :type t :identity nil)))\n\n  ;;; Override previous methods (from 3.1.7 and earlier) and add proper error checking.\n  #-genera ;; Genera adds its own system initargs, e.g. clos-internals:storage-area 8\n  (defmethod initialize-instance :after ((o operation) &rest initargs &key &allow-other-keys)\n    (unless (null initargs)\n      (parameter-error \"~S does not accept initargs\" 'operation))))\n\n\n;;; make-operation, find-operation\n\n(with-upgradability ()\n  ;; A table to memoize instances of a given operation. There shall be only one.\n  (defparameter* *operations* (make-hash-table :test 'equal))\n\n  ;; A memoizing way of creating instances of operation.\n  (defun make-operation (operation-class)\n    \"This function creates and memoizes an instance of OPERATION-CLASS.\nAll operation instances MUST be created through this function.\n\nUse of INITARGS is not supported at this time.\"\n    (let ((class (coerce-class operation-class\n                               :package :asdf/interface :super 'operation :error 'sysdef-error))\n          (*in-make-operation* t))\n      (ensure-gethash class *operations* `(make-instance ,class))))\n\n  ;; This function is mostly for backward and forward compatibility:\n  ;; operations used to preserve the operation-original-initargs of the context,\n  ;; and may in the future preserve some operation-canonical-initargs.\n  ;; Still, the treatment of NIL as a disabling context is useful in some cases.\n  (defgeneric find-operation (context spec)\n    (:documentation \"Find an operation by resolving the SPEC in the CONTEXT\"))\n  (defmethod find-operation ((context t) (spec operation))\n    spec)\n  (defmethod find-operation ((context t) (spec symbol))\n    (when spec ;; NIL designates itself, i.e. absence of operation\n      (make-operation spec))) ;; TODO: preserve the (operation-canonical-initargs context)\n  (defmethod find-operation ((context t) (spec string))\n    (make-operation spec))) ;; TODO: preserve the (operation-canonical-initargs context)\n\n;;;; -------------------------------------------------------------------------\n;;;; Systems\n\n(uiop/package:define-package :asdf/system\n  (:recycle :asdf :asdf/system :asdf/find-system)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session :asdf/component)\n  (:export\n   #:system #:proto-system #:undefined-system #:reset-system-class\n   #:system-source-file #:system-source-directory #:system-relative-pathname\n   #:system-description #:system-long-description\n   #:system-author #:system-maintainer #:system-licence #:system-license\n   #:system-version\n   #:definition-dependency-list #:definition-dependency-set #:system-defsystem-depends-on\n   #:system-depends-on #:system-weakly-depends-on\n   #:component-build-pathname #:build-pathname\n   #:component-entry-point #:entry-point\n   #:homepage #:system-homepage\n   #:bug-tracker #:system-bug-tracker\n   #:mailto #:system-mailto\n   #:long-name #:system-long-name\n   #:source-control #:system-source-control\n   #:coerce-name #:primary-system-name #:primary-system-p #:coerce-filename\n   #:find-system #:builtin-system-p)) ;; forward-reference, defined in find-system\n(in-package :asdf/system)\n\n(with-upgradability ()\n  ;; The method is actually defined in asdf/find-system,\n  ;; but we declare the function here to avoid a forward reference.\n  (defgeneric find-system (system &optional error-p)\n    (:documentation \"Given a system designator, find the actual corresponding system object.\nIf no system is found, then signal an error if ERROR-P is true (the default), or else return NIL.\nA system designator is usually a string (conventionally all lowercase) or a symbol, designating\nthe same system as its downcased name; it can also be a system object (designating itself).\"))\n\n  (defgeneric system-source-file (system)\n    (:documentation \"Return the source file in which system is defined.\"))\n\n  ;; This is bad design, but was the easiest kluge I found to let the user specify that\n  ;; some special actions create outputs at locations controled by the user that are not affected\n  ;; by the usual output-translations.\n  ;; TODO: Fix operate to stop passing flags to operation (which in the current design shouldn't\n  ;; have any flags, since the stamp cache, etc., can't distinguish them), and instead insert\n  ;; *there* the ability of specifying special output paths, not in the system definition.\n  (defgeneric component-build-pathname (component)\n    (:documentation \"The COMPONENT-BUILD-PATHNAME, when defined and not null, specifies the\noutput pathname for the action using the COMPONENT-BUILD-OPERATION.\n\nNB: This interface is subject to change. Please contact ASDF maintainers if you use it.\"))\n\n  ;; TODO: Should this have been made a SYSTEM-ENTRY-POINT instead?\n  (defgeneric component-entry-point (component)\n    (:documentation \"The COMPONENT-ENTRY-POINT, when defined, specifies what function to call\n(with no argument) when running an image dumped from the COMPONENT.\n\nNB: This interface is subject to change. Please contact ASDF maintainers if you use it.\"))\n\n  (defmethod component-entry-point ((c component))\n    nil))\n\n\n;;;; The system class\n\n(with-upgradability ()\n  (defclass proto-system () ; slots to keep when resetting a system\n    ;; To preserve identity for all objects, we'd need keep the components slots\n    ;; but also to modify parse-component-form to reset the recycled objects.\n    ((name)\n     (source-file)\n     ;; These two slots contains the *inferred* dependencies of define-op,\n     ;; from loading the .asd file, as list and as set.\n     (definition-dependency-list\n         :initform nil :accessor definition-dependency-list)\n     (definition-dependency-set\n         :initform (list-to-hash-set nil) :accessor definition-dependency-set))\n    (:documentation \"PROTO-SYSTEM defines the elements of identity that are preserved when\na SYSTEM is redefined and its class is modified.\"))\n\n  (defclass system (module proto-system)\n    ;; Backward-compatibility: inherit from module. ASDF4: only inherit from parent-component.\n    (;; {,long-}description is now inherited from component, but we add the legacy accessors\n     (description :writer (setf system-description))\n     (long-description :writer (setf system-long-description))\n     (author :writer (setf system-author) :initarg :author :initform nil)\n     (maintainer :writer (setf system-maintainer) :initarg :maintainer :initform nil)\n     (licence :writer (setf system-licence) :initarg :licence\n              :writer (setf system-license) :initarg :license\n              :initform nil)\n     (homepage :writer (setf system-homepage) :initarg :homepage :initform nil)\n     (bug-tracker :writer (setf system-bug-tracker) :initarg :bug-tracker :initform nil)\n     (mailto :writer (setf system-mailto) :initarg :mailto :initform nil)\n     (long-name :writer (setf system-long-name) :initarg :long-name :initform nil)\n     ;; Conventions for this slot aren't clear yet as of ASDF 2.27, but whenever they are, they will be enforced.\n     ;; I'm introducing the slot before the conventions are set for maximum compatibility.\n     (source-control :writer (setf system-source-control) :initarg :source-control :initform nil)\n\n     (builtin-system-p :accessor builtin-system-p :initform nil :initarg :builtin-system-p)\n     (build-pathname\n      :initform nil :initarg :build-pathname :accessor component-build-pathname)\n     (entry-point\n      :initform nil :initarg :entry-point :accessor component-entry-point)\n     (source-file :initform nil :initarg :source-file :accessor system-source-file)\n     ;; This slot contains the *declared* defsystem-depends-on dependencies\n     (defsystem-depends-on :reader system-defsystem-depends-on :initarg :defsystem-depends-on\n                           :initform nil)\n     ;; these two are specially set in parse-component-form, so have no :INITARGs.\n     (depends-on :reader system-depends-on :initform nil)\n     (weakly-depends-on :reader system-weakly-depends-on :initform nil))\n    (:documentation \"SYSTEM is the base class for top-level components that users may request\nASDF to build.\"))\n\n  (defclass undefined-system (system) ()\n    (:documentation \"System that was not defined yet.\"))\n\n  (defun reset-system-class (system new-class &rest keys &key &allow-other-keys)\n    \"Erase any data from a SYSTEM except its basic identity, then reinitialize it\nbased on supplied KEYS.\"\n    (change-class (change-class system 'proto-system) new-class)\n    (apply 'reinitialize-instance system keys)))\n\n\n;;; Canonicalizing system names\n\n(with-upgradability ()\n  (defun coerce-name (name)\n    \"Given a designator for a component NAME, return the name as a string.\nThe designator can be a COMPONENT (designing its name; note that a SYSTEM is a component),\na SYMBOL (designing its name, downcased), or a STRING (designing itself).\"\n    (typecase name\n      (component (component-name name))\n      (symbol (string-downcase name))\n      (string name)\n      (t (sysdef-error (compatfmt \"~@<Invalid component designator: ~3i~_~A~@:>\") name))))\n\n  (defun primary-system-name (system-designator)\n    \"Given a system designator NAME, return the name of the corresponding\nprimary system, after which the .asd file in which it is defined is named.\nIf given a string or symbol (to downcase), do it syntactically\n by stripping anything from the first slash on.\nIf given a component, do it semantically by extracting\nthe system-primary-system-name of its system from its source-file if any,\nfalling back to the syntactic criterion if none.\"\n    (etypecase system-designator\n      (string (if-let (p (position #\\/ system-designator))\n                (subseq system-designator 0 p) system-designator))\n      (symbol (primary-system-name (coerce-name system-designator)))\n      (component (let* ((system (component-system system-designator))\n                        (source-file (physicalize-pathname (system-source-file system))))\n                   (if source-file\n                       (and (equal (pathname-type source-file) \"asd\")\n                            (pathname-name source-file))\n                       (primary-system-name (component-name system)))))))\n\n  (defun primary-system-p (system)\n    \"Given a system designator SYSTEM, return T if it designates a primary system, or else NIL.\nIf given a string, do it syntactically and return true if the name does not contain a slash.\nIf given a symbol, downcase to a string then fallback to previous case (NB: for NIL return T).\nIf given a component, do it semantically and return T if it's a SYSTEM and its primary-system-name\nis the same as its component-name.\"\n    (etypecase system\n      (string (not (find #\\/ system)))\n      (symbol (primary-system-p (coerce-name system)))\n      (component (and (typep system 'system)\n                      (equal (component-name system) (primary-system-name system))))))\n\n  (defun coerce-filename (name)\n    \"Coerce a system designator NAME into a string suitable as a filename component.\nThe (current) transformation is to replace characters /:\\\\ each by --,\nthe former being forbidden in a filename component.\nNB: The onus is unhappily on the user to avoid clashes.\"\n    (frob-substrings (coerce-name name) '(\"/\" \":\" \"\\\\\") \"--\")))\n\n\n;;; System virtual slot readers, recursing to the primary system if needed.\n(with-upgradability ()\n  (defvar *system-virtual-slots* '(long-name description long-description\n                                   author maintainer mailto\n                                   homepage source-control\n                                   licence version bug-tracker)\n    \"The list of system virtual slot names.\")\n  (defun system-virtual-slot-value (system slot-name)\n    \"Return SYSTEM's virtual SLOT-NAME value.\nIf SYSTEM's SLOT-NAME value is NIL and SYSTEM is a secondary system, look in\nthe primary one.\"\n    (or (slot-value system slot-name)\n        (unless (primary-system-p system)\n          (slot-value (find-system (primary-system-name system))\n                      slot-name))))\n  (defmacro define-system-virtual-slot-reader (slot-name)\n    (let ((name (intern (strcat (string :system-) (string slot-name)))))\n      `(progn\n         (fmakunbound ',name) ;; These were gf from defgeneric before 3.3.2.11\n         (declaim (notinline ,name))\n         (defun ,name (system) (system-virtual-slot-value system ',slot-name)))))\n  (defmacro define-system-virtual-slot-readers ()\n    `(progn ,@(mapcar (lambda (slot-name)\n                        `(define-system-virtual-slot-reader ,slot-name))\n                *system-virtual-slots*)))\n  (define-system-virtual-slot-readers)\n  (defun system-license (system)\n    (system-virtual-slot-value system 'licence)))\n\n\n;;;; Pathnames\n\n(with-upgradability ()\n  ;; Resolve a system designator to a system before extracting its system-source-file\n  (defmethod system-source-file ((system-name string))\n    (system-source-file (find-system system-name)))\n  (defmethod system-source-file ((system-name symbol))\n    (when system-name\n      (system-source-file (find-system system-name))))\n\n  (defun system-source-directory (system-designator)\n    \"Return a pathname object corresponding to the directory\nin which the system specification (.asd file) is located.\"\n    (pathname-directory-pathname (system-source-file system-designator)))\n\n  (defun system-relative-pathname (system name &key type)\n    \"Given a SYSTEM, and a (Unix-style relative path) NAME of a file (or directory) of given TYPE,\nreturn the absolute pathname of a corresponding file under that system's source code pathname.\"\n    (subpathname (system-source-directory system) name :type type))\n\n  (defmethod component-pathname ((system system))\n    \"Given a SYSTEM, and a (Unix-style relative path) NAME of a file (or directory) of given TYPE,\nreturn the absolute pathname of a corresponding file under that system's source code pathname.\"\n    (let ((pathname (or (call-next-method) (system-source-directory system))))\n      (unless (and (slot-boundp system 'relative-pathname) ;; backward-compatibility with ASDF1-age\n                   (slot-value system 'relative-pathname)) ;; systems that directly access this slot.\n        (setf (slot-value system 'relative-pathname) pathname))\n      pathname))\n\n  ;; The default method of component-relative-pathname for a system:\n  ;; if a pathname was specified in the .asd file, it must be relative to the .asd file\n  ;; (actually, to its truename* if *resolve-symlinks* it true, the default).\n  ;; The method will return an *absolute* pathname, once again showing that the historical name\n  ;; component-relative-pathname is misleading and should have been component-specified-pathname.\n  (defmethod component-relative-pathname ((system system))\n    (parse-unix-namestring\n     (and (slot-boundp system 'relative-pathname)\n          (slot-value system 'relative-pathname))\n     :want-relative t\n     :type :directory\n     :ensure-absolute t\n     :defaults (system-source-directory system)))\n\n  ;; A system has no parent; if some method wants to make a path \"relative to its parent\",\n  ;; it will instead be relative to the system itself.\n  (defmethod component-parent-pathname ((system system))\n    (system-source-directory system))\n\n  ;; Most components don't have a specified component-build-pathname, and therefore\n  ;; no magic redirection of their output that disregards the output-translations.\n  (defmethod component-build-pathname ((c component))\n    nil))\n\n;;;; -------------------------------------------------------------------------\n;;;; Finding systems\n\n(uiop/package:define-package :asdf/system-registry\n  (:recycle :asdf/system-registry :asdf/find-system :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n    :asdf/session :asdf/component :asdf/system)\n  (:export\n   #:remove-entry-from-registry #:coerce-entry-to-directory\n   #:registered-system #:register-system\n   #:registered-systems* #:registered-systems\n   #:clear-system #:map-systems\n   #:*system-definition-search-functions* #:search-for-system-definition\n   #:*central-registry* #:probe-asd #:sysdef-central-registry-search\n   #:contrib-sysdef-search #:sysdef-find-asdf ;; backward compatibility symbols, functions removed\n   #:sysdef-preloaded-system-search #:register-preloaded-system #:*preloaded-systems*\n   #:find-system-if-being-defined #:mark-component-preloaded ;; forward references to asdf/find-system\n   #:sysdef-immutable-system-search #:register-immutable-system #:*immutable-systems*\n   #:*registered-systems* #:clear-registered-systems\n   ;; defined in source-registry, but specially mentioned here:\n   #:sysdef-source-registry-search))\n(in-package :asdf/system-registry)\n\n(with-upgradability ()\n  ;;; Registry of Defined Systems\n\n  (defvar *registered-systems* (make-hash-table :test 'equal)\n    \"This is a hash table whose keys are strings -- the names of systems --\nand whose values are systems.\nA system is referred to as \\\"registered\\\" if it is present in this table.\")\n\n  (defun registered-system (name)\n    \"Return a system of given NAME that was registered already,\nif such a system exists.  NAME is a system designator, to be\nnormalized by COERCE-NAME. The value returned is a system object,\nor NIL if not found.\"\n    (gethash (coerce-name name) *registered-systems*))\n\n  (defun registered-systems* ()\n    \"Return a list containing every registered system (as a system object).\"\n    (loop :for registered :being :the :hash-values :of *registered-systems*\n          :collect registered))\n\n  (defun registered-systems ()\n    \"Return a list of the names of every registered system.\"\n    (mapcar 'coerce-name (registered-systems*)))\n\n  (defun register-system (system)\n    \"Given a SYSTEM object, register it.\"\n    (check-type system system)\n    (let ((name (component-name system)))\n      (check-type name string)\n      (asdf-message (compatfmt \"~&~@<; ~@;Registering system ~3i~_~A~@:>~%\") name)\n      (setf (gethash name *registered-systems*) system)))\n\n  (defun map-systems (fn)\n    \"Apply FN to each defined system.\n\nFN should be a function of one argument. It will be\ncalled with an object of type asdf:system.\"\n    (loop :for registered :being :the :hash-values :of *registered-systems*\n          :do (funcall fn registered)))\n\n\n  ;;; Preloaded systems: in the image even if you can't find source files backing them.\n\n  (defvar *preloaded-systems* (make-hash-table :test 'equal)\n    \"Registration table for preloaded systems.\")\n\n  (declaim (ftype (function (t) t) mark-component-preloaded)) ; defined in asdf/find-system\n\n  (defun make-preloaded-system (name keys)\n    \"Make a preloaded system of given NAME with build information from KEYS\"\n    (let ((system (apply 'make-instance (getf keys :class 'system)\n                         :name name :source-file (getf keys :source-file)\n                         (remove-plist-keys '(:class :name :source-file) keys))))\n      (mark-component-preloaded system)\n      system))\n\n  (defun sysdef-preloaded-system-search (requested)\n    \"If REQUESTED names a system registered as preloaded, return a new system\nwith its registration information.\"\n    (let ((name (coerce-name requested)))\n      (multiple-value-bind (keys foundp) (gethash name *preloaded-systems*)\n        (when foundp\n          (make-preloaded-system name keys)))))\n\n  (defun ensure-preloaded-system-registered (name)\n    \"If there isn't a registered _defined_ system of given NAME,\nand a there is a registered _preloaded_ system of given NAME,\nthen define and register said preloaded system.\"\n    (if-let (system (and (not (registered-system name)) (sysdef-preloaded-system-search name)))\n      (register-system system)))\n\n  (defun register-preloaded-system (system-name &rest keys &key (version t) &allow-other-keys)\n    \"Register a system as being preloaded. If the system has not been loaded from the filesystem\nyet, or if its build information is later cleared with CLEAR-SYSTEM, a dummy system will be\nregistered without backing filesystem information, based on KEYS (e.g. to provide a VERSION).\nIf VERSION is the default T, and a system was already loaded, then its version will be preserved.\"\n    (let ((name (coerce-name system-name)))\n      (when (eql version t)\n        (if-let (system (registered-system name))\n          (setf (getf keys :version) (component-version system))))\n      (setf (gethash name *preloaded-systems*) keys)\n      (ensure-preloaded-system-registered system-name)))\n\n\n  ;;; Immutable systems: in the image and can't be reloaded from source.\n\n  (defvar *immutable-systems* nil\n    \"A hash-set (equal hash-table mapping keys to T) of systems that are immutable,\ni.e. already loaded in memory and not to be refreshed from the filesystem.\nThey will be treated specially by find-system, and passed as :force-not argument to make-plan.\n\nFor instance, to can deliver an image with many systems precompiled, that *will not* check the\nfilesystem for them every time a user loads an extension, what more risk a problematic upgrade\n or catastrophic downgrade, before you dump an image, you may use:\n   (map () 'asdf:register-immutable-system (asdf:already-loaded-systems))\n\nNote that direct access to this variable from outside ASDF is not supported.\nPlease call REGISTER-IMMUTABLE-SYSTEM to add new immutable systems, and\ncontact maintainers if you need a stable API to do more than that.\")\n\n  (defun sysdef-immutable-system-search (requested)\n    (let ((name (coerce-name requested)))\n      (when (and *immutable-systems* (gethash name *immutable-systems*))\n        (or (registered-system requested)\n            (error 'formatted-system-definition-error\n                   :format-control \"Requested system ~A registered as an immutable-system, ~\nbut not even registered as defined\"\n                   :format-arguments (list name))))))\n\n  (defun register-immutable-system (system-name &rest keys)\n    \"Register SYSTEM-NAME as preloaded and immutable.\nIt will automatically be considered as passed to FORCE-NOT in a plan.\"\n    (let ((system-name (coerce-name system-name)))\n      (apply 'register-preloaded-system system-name keys)\n      (unless *immutable-systems*\n        (setf *immutable-systems* (list-to-hash-set nil)))\n      (setf (gethash system-name *immutable-systems*) t)))\n\n\n  ;;; Making systems undefined.\n\n  (defun clear-system (system)\n    \"Clear the entry for a SYSTEM in the database of systems previously defined.\nHowever if the system was registered as PRELOADED (which it is if it is IMMUTABLE),\nthen a new system with the same name will be defined and registered in its place\nfrom which build details will have been cleared.\nNote that this does NOT in any way cause any of the code of the system to be unloaded.\nReturns T if system was or is now undefined, NIL if a new preloaded system was redefined.\"\n    ;; There is no \"unload\" operation in Common Lisp, and\n    ;; a general such operation cannot be portably written,\n    ;; considering how much CL relies on side-effects to global data structures.\n    (let ((name (coerce-name system)))\n      (remhash name *registered-systems*)\n      (unset-asdf-cache-entry `(find-system ,name))\n      (not (ensure-preloaded-system-registered name))))\n\n  (defun clear-registered-systems ()\n    \"Clear all currently registered defined systems.\nPreloaded systems (including immutable ones) will be reset, other systems will be de-registered.\"\n    (map () 'clear-system (registered-systems)))\n\n\n  ;;; Searching for system definitions\n\n  ;; For the sake of keeping things reasonably neat, we adopt a convention that\n  ;; only symbols are to be pushed to this list (rather than e.g. function objects),\n  ;; which makes upgrade easier. Also, the name of these symbols shall start with SYSDEF-\n  (defvar *system-definition-search-functions* '()\n    \"A list that controls the ways that ASDF looks for system definitions.\nIt contains symbols to be funcalled in order, with a requested system name as argument,\nuntil one returns a non-NIL result (if any), which must then be a fully initialized system object\nwith that name.\")\n\n  ;; Initialize and/or upgrade the *system-definition-search-functions*\n  ;; so it doesn't contain obsolete symbols, and does contain the current ones.\n  (defun cleanup-system-definition-search-functions ()\n    (setf *system-definition-search-functions*\n          (append\n           ;; Remove known-incompatible sysdef functions from old versions of asdf.\n           ;; Order matters, so we can't just use set-difference.\n           (let ((obsolete\n                  '(contrib-sysdef-search sysdef-find-asdf sysdef-preloaded-system-search)))\n             (remove-if #'(lambda (x) (member x obsolete)) *system-definition-search-functions*))\n           ;; Tuck our defaults at the end of the list if they were absent.\n           ;; This is imperfect, in case they were removed on purpose,\n           ;; but then it will be the responsibility of whoever removes these symmbols\n           ;; to upgrade asdf before he does such a thing rather than after.\n           (remove-if #'(lambda (x) (member x *system-definition-search-functions*))\n                      '(sysdef-central-registry-search\n                        sysdef-source-registry-search)))))\n  (cleanup-system-definition-search-functions)\n\n  ;; This (private) function does the search for a system definition using *s-d-s-f*;\n  ;; it is to be called by locate-system.\n  (defun search-for-system-definition (system)\n    ;; Search for valid definitions of the system available in the current session.\n    ;; Previous definitions as registered in *registered-systems* MUST NOT be considered;\n    ;; they will be reconciled by locate-system then find-system.\n    ;; There are two special treatments: first, specially search for objects being defined\n    ;; in the current session, to avoid definition races between several files;\n    ;; second, specially search for immutable systems, so they cannot be redefined.\n    ;; Finally, use the search functions specified in *system-definition-search-functions*.\n    (let ((name (coerce-name system)))\n      (flet ((try (f) (if-let ((x (funcall f name))) (return-from search-for-system-definition x))))\n        (try 'find-system-if-being-defined)\n        (try 'sysdef-immutable-system-search)\n        (map () #'try *system-definition-search-functions*))))\n\n\n  ;;; The legacy way of finding a system: the *central-registry*\n\n  ;; This variable contains a list of directories to be lazily searched for the requested asd\n  ;; by sysdef-central-registry-search.\n  (defvar *central-registry* nil\n    \"A list of 'system directory designators' ASDF uses to find systems.\n\nA 'system directory designator' is a pathname or an expression\nwhich evaluates to a pathname. For example:\n\n    (setf asdf:*central-registry*\n          (list '*default-pathname-defaults*\n                #p\\\"/home/me/cl/systems/\\\"\n                #p\\\"/usr/share/common-lisp/systems/\\\"))\n\nThis variable is for backward compatibility.\nGoing forward, we recommend new users should be using the source-registry.\")\n\n  ;; Function to look for an asd file of given NAME under a directory provided by DEFAULTS.\n  ;; Return the truename of that file if it is found and TRUENAME is true.\n  ;; Return NIL if the file is not found.\n  ;; On Windows, follow shortcuts to .asd files.\n  (defun probe-asd (name defaults &key truename)\n    (block nil\n      (when (directory-pathname-p defaults)\n        (if-let (file (probe-file*\n                       (ensure-absolute-pathname\n                        (parse-unix-namestring name :type \"asd\")\n                        #'(lambda () (ensure-absolute-pathname defaults 'get-pathname-defaults nil))\n                        nil)\n                       :truename truename))\n          (return file))\n        #-(or clisp genera) ; clisp doesn't need it, plain genera doesn't have read-sequence(!)\n        (os-cond\n         ((os-windows-p)\n          (when (physical-pathname-p defaults)\n            (let ((shortcut\n                    (make-pathname\n                     :defaults defaults :case :local\n                     :name (strcat name \".asd\")\n                     :type \"lnk\")))\n              (when (probe-file* shortcut)\n                (ensure-pathname (parse-windows-shortcut shortcut) :namestring :native)))))))))\n\n  ;; Function to push onto *s-d-s-f* to use the *central-registry*\n  (defun sysdef-central-registry-search (system)\n    (let ((name (primary-system-name system))\n          (to-remove nil)\n          (to-replace nil))\n      (block nil\n        (unwind-protect\n             (dolist (dir *central-registry*)\n               (let ((defaults (eval dir))\n                     directorized)\n                 (when defaults\n                   (cond ((directory-pathname-p defaults)\n                          (let* ((file (probe-asd name defaults :truename *resolve-symlinks*)))\n                            (when file\n                              (return file))))\n                         (t\n                          (restart-case\n                              (let* ((*print-circle* nil)\n                                     (message\n                                       (format nil\n                                               (compatfmt \"~@<While searching for system ~S: ~3i~_~S evaluated to ~S which is not an absolute directory.~@:>\")\n                                               system dir defaults)))\n                                (error message))\n                            (remove-entry-from-registry ()\n                              :report \"Remove entry from *central-registry* and continue\"\n                              (push dir to-remove))\n                            (coerce-entry-to-directory ()\n                              :test (lambda (c) (declare (ignore c))\n                                      (and (not (directory-pathname-p defaults))\n                                           (directory-pathname-p\n                                            (setf directorized\n                                                  (ensure-directory-pathname defaults)))))\n                              :report (lambda (s)\n                                        (format s (compatfmt \"~@<Coerce entry to ~a, replace ~a and continue.~@:>\")\n                                                directorized dir))\n                              (push (cons dir directorized) to-replace))))))))\n          ;; cleanup\n          (dolist (dir to-remove)\n            (setf *central-registry* (remove dir *central-registry*)))\n          (dolist (pair to-replace)\n            (let* ((current (car pair))\n                   (new (cdr pair))\n                   (position (position current *central-registry*)))\n              (setf *central-registry*\n                    (append (subseq *central-registry* 0 position)\n                            (list new)\n                            (subseq *central-registry* (1+ position)))))))))))\n\n;;;; -------------------------------------------------------------------------\n;;;; Actions\n\n(uiop/package:define-package :asdf/action\n  (:nicknames :asdf-action)\n  (:recycle :asdf/action :asdf/plan :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session :asdf/component :asdf/operation)\n  (:import-from :asdf/operation #:check-operation-constructor)\n  (:import-from :asdf/component #:%additional-input-files)\n  (:export\n   #:action #:define-convenience-action-methods\n   #:action-description #:format-action\n   #:downward-operation #:upward-operation #:sideway-operation #:selfward-operation\n   #:non-propagating-operation\n   #:component-depends-on\n   #:input-files #:output-files #:output-file #:operation-done-p\n   #:action-operation #:action-component #:make-action\n   #:component-operation-time #:mark-operation-done #:compute-action-stamp\n   #:perform #:perform-with-restarts #:retry #:accept\n   #:action-path #:find-action\n   #:operation-definition-warning #:operation-definition-error ;; condition\n   #:action-valid-p\n   #:circular-dependency #:circular-dependency-actions\n   #:call-while-visiting-action #:while-visiting-action\n   #:additional-input-files))\n(in-package :asdf/action)\n\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute) ;; LispWorks issues spurious warning\n\n  (deftype action ()\n    \"A pair of operation and component uniquely identifies a node in the dependency graph\nof steps to be performed while building a system.\"\n    '(cons operation component))\n\n  (deftype operation-designator ()\n    \"An operation designates itself. NIL designates a context-dependent current operation,\nand a class-name or class designates the canonical instance of the designated class.\"\n    '(or operation null symbol class)))\n\n;;; these are pseudo accessors -- let us abstract away the CONS cell representation of plan\n;;; actions.\n(with-upgradability ()\n  (defun make-action (operation component)\n    (cons operation component))\n  (defun action-operation (action)\n    (car action))\n  (defun action-component (action)\n    (cdr action)))\n\n;;;; Reified representation for storage or debugging. Note: an action is identified by its class.\n(with-upgradability ()\n  (defun action-path (action)\n    \"A readable data structure that identifies the action.\"\n    (when action\n      (let ((o (action-operation action))\n            (c (action-component action)))\n        (cons (type-of o) (component-find-path c)))))\n  (defun find-action (path)\n    \"Reconstitute an action from its action-path\"\n    (destructuring-bind (o . c) path (make-action (make-operation o) (find-component () c)))))\n\n;;;; Convenience methods\n(with-upgradability ()\n  ;; A macro that defines convenience methods for a generic function (gf) that\n  ;; dispatches on operation and component.  The convenience methods allow users\n  ;; to call the gf with operation and/or component designators, that the\n  ;; methods will resolve into actual operation and component objects, so that\n  ;; the users can interact using readable designators, but developers only have\n  ;; to write methods that handle operation and component objects.\n  ;; FUNCTION is the generic function name\n  ;; FORMALS is its list of arguments, which must include OPERATION and COMPONENT.\n  ;; IF-NO-OPERATION is a form (defaults to NIL) describing what to do if no operation is found.\n  ;; IF-NO-COMPONENT is a form (defaults to NIL) describing what to do if no component is found.\n  (defmacro define-convenience-action-methods\n      (function formals &key if-no-operation if-no-component)\n    (let* ((rest (gensym \"REST\"))\n           (found (gensym \"FOUND\"))\n           (keyp (equal (last formals) '(&key)))\n           (formals-no-key (if keyp (butlast formals) formals))\n           (len (length formals-no-key))\n           (operation 'operation)\n           (component 'component)\n           (opix (position operation formals))\n           (coix (position component formals))\n           (prefix (subseq formals 0 opix))\n           (suffix (subseq formals (1+ coix) len))\n           (more-args (when keyp `(&rest ,rest &key &allow-other-keys))))\n      (assert (and (integerp opix) (integerp coix) (= coix (1+ opix))))\n      (flet ((next-method (o c)\n               (if keyp\n                   `(apply ',function ,@prefix ,o ,c ,@suffix ,rest)\n                   `(,function ,@prefix ,o ,c ,@suffix))))\n        `(progn\n           (defmethod ,function (,@prefix (,operation string) ,component ,@suffix ,@more-args)\n             (declare (notinline ,function))\n             (let ((,component (find-component () ,component))) ;; do it first, for defsystem-depends-on\n               ,(next-method `(safe-read-from-string ,operation :package :asdf/interface) component)))\n           (defmethod ,function (,@prefix (,operation symbol) ,component ,@suffix ,@more-args)\n             (declare (notinline ,function))\n             (if ,operation\n                 ,(next-method\n                   `(make-operation ,operation)\n                   `(or (find-component () ,component) ,if-no-component))\n                 ,if-no-operation))\n           (defmethod ,function (,@prefix (,operation operation) ,component ,@suffix ,@more-args)\n             (declare (notinline ,function))\n             (if (typep ,component 'component)\n                 (error \"No defined method for ~S on ~/asdf-action:format-action/\"\n                        ',function (make-action ,operation ,component))\n                 (if-let (,found (find-component () ,component))\n                    ,(next-method operation found)\n                    ,if-no-component))))))))\n\n\n;;;; Self-description\n(with-upgradability ()\n  (defgeneric action-description (operation component)\n    (:documentation \"returns a phrase that describes performing this operation\non this component, e.g. \\\"loading /a/b/c\\\".\nYou can put together sentences using this phrase.\"))\n  (defmethod action-description (operation component)\n    (format nil (compatfmt \"~@<~A on ~A~@:>\")\n            operation component))\n\n  (defun format-action (stream action &optional colon-p at-sign-p)\n    \"FORMAT helper to display an action's action-description.\nUse it in FORMAT control strings as ~/asdf-action:format-action/\"\n    (assert (null colon-p)) (assert (null at-sign-p))\n    (destructuring-bind (operation . component) action\n      (princ (action-description operation component) stream))))\n\n\n;;;; Detection of circular dependencies\n(with-upgradability ()\n  (defun action-valid-p (operation component)\n    \"Is this action valid to include amongst dependencies?\"\n    ;; If either the operation or component was resolved to nil, the action is invalid.\n    ;; :if-feature will invalidate actions on components for which the features don't apply.\n    (and operation component\n         (if-let (it (component-if-feature component)) (featurep it) t)))\n\n  (define-condition circular-dependency (system-definition-error)\n    ((actions :initarg :actions :reader circular-dependency-actions))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Circular dependency of ~s on: ~3i~_~S~@:>\")\n                       (first (circular-dependency-actions c))\n                       (circular-dependency-actions c)))))\n\n  (defun call-while-visiting-action (operation component fun)\n    \"Detect circular dependencies\"\n    (with-asdf-session ()\n      (with-accessors ((action-set visiting-action-set)\n                       (action-list visiting-action-list)) *asdf-session*\n        (let ((action (cons operation component)))\n          (when (gethash action action-set)\n            (error 'circular-dependency :actions\n                   (member action (reverse action-list) :test 'equal)))\n          (setf (gethash action action-set) t)\n          (push action action-list)\n          (unwind-protect\n               (funcall fun)\n            (pop action-list)\n            (setf (gethash action action-set) nil))))))\n\n  ;; Syntactic sugar for call-while-visiting-action\n  (defmacro while-visiting-action ((o c) &body body)\n    `(call-while-visiting-action ,o ,c #'(lambda () ,@body))))\n\n\n;;;; Dependencies\n(with-upgradability ()\n  (defgeneric component-depends-on (operation component) ;; ASDF4: rename to component-dependencies\n    (:documentation\n     \"Returns a list of dependencies needed by the component to perform\n    the operation.  A dependency has one of the following forms:\n\n      (<operation> <component>*), where <operation> is an operation designator\n        with respect to FIND-OPERATION in the context of the OPERATION argument,\n        and each <component> is a component designator with respect to\n        FIND-COMPONENT in the context of the COMPONENT argument,\n        and means that the component depends on\n        <operation> having been performed on each <component>;\n\n        [Note: an <operation> is an operation designator -- it can be either an\n        operation name or an operation object.  Similarly, a <component> may be\n        a component name or a component object.  Also note that, the degenerate\n        case of (<operation>) is a no-op.]\n\n    Methods specialized on subclasses of existing component types\n    should usually append the results of CALL-NEXT-METHOD to the list.\"))\n  (define-convenience-action-methods component-depends-on (operation component))\n\n  (defmethod component-depends-on :around ((o operation) (c component))\n    (do-asdf-cache `(component-depends-on ,o ,c)\n      (call-next-method))))\n\n\n;;;; upward-operation, downward-operation, sideway-operation, selfward-operation\n;; These together handle actions that propagate along the component hierarchy or operation universe.\n(with-upgradability ()\n  (defclass downward-operation (operation)\n    ((downward-operation\n      :initform nil :reader downward-operation\n      :type operation-designator :allocation :class))\n    (:documentation \"A DOWNWARD-OPERATION's dependencies propagate down the component hierarchy.\nI.e., if O is a DOWNWARD-OPERATION and its DOWNWARD-OPERATION slot designates operation D, then\nthe action (O . M) of O on module M will depends on each of (D . C) for each child C of module M.\nThe default value for slot DOWNWARD-OPERATION is NIL, which designates the operation O itself.\nE.g. in order for a MODULE to be loaded with LOAD-OP (resp. compiled with COMPILE-OP), all the\nchildren of the MODULE must have been loaded with LOAD-OP (resp. compiled with COMPILE-OP.\"))\n  (defun downward-operation-depends-on (o c)\n    `((,(or (downward-operation o) o) ,@(component-children c))))\n  (defmethod component-depends-on ((o downward-operation) (c parent-component))\n    `(,@(downward-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass upward-operation (operation)\n    ((upward-operation\n      :initform nil :reader upward-operation\n      :type operation-designator :allocation :class))\n    (:documentation \"An UPWARD-OPERATION has dependencies that propagate up the component hierarchy.\nI.e., if O is an instance of UPWARD-OPERATION, and its UPWARD-OPERATION slot designates operation U,\nthen the action (O . C) of O on a component C that has the parent P will depends on (U . P).\nThe default value for slot UPWARD-OPERATION is NIL, which designates the operation O itself.\nE.g. in order for a COMPONENT to be prepared for loading or compiling with PREPARE-OP, its PARENT\nmust first be prepared for loading or compiling with PREPARE-OP.\"))\n  ;; For backward-compatibility reasons, a system inherits from module and is a child-component\n  ;; so we must guard against this case. ASDF4: remove that.\n  (defun upward-operation-depends-on (o c)\n    (if-let (p (component-parent c)) `((,(or (upward-operation o) o) ,p))))\n  (defmethod component-depends-on ((o upward-operation) (c child-component))\n    `(,@(upward-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass sideway-operation (operation)\n    ((sideway-operation\n      :initform nil :reader sideway-operation\n      :type operation-designator :allocation :class))\n    (:documentation \"A SIDEWAY-OPERATION has dependencies that propagate \\\"sideway\\\" to siblings\nthat a component depends on. I.e. if O is a SIDEWAY-OPERATION, and its SIDEWAY-OPERATION slot\ndesignates operation S (where NIL designates O itself), then the action (O . C) of O on component C\ndepends on each of (S . D) where D is a declared dependency of C.\nE.g. in order for a COMPONENT to be prepared for loading or compiling with PREPARE-OP,\neach of its declared dependencies must first be loaded as by LOAD-OP.\"))\n  (defun sideway-operation-depends-on (o c)\n    `((,(or (sideway-operation o) o) ,@(component-sideway-dependencies c))))\n  (defmethod component-depends-on ((o sideway-operation) (c component))\n    `(,@(sideway-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass selfward-operation (operation)\n    ((selfward-operation\n      ;; NB: no :initform -- if an operation depends on others, it must explicitly specify which\n      :type (or operation-designator list) :reader selfward-operation :allocation :class))\n    (:documentation \"A SELFWARD-OPERATION depends on another operation on the same component.\nI.e., if O is a SELFWARD-OPERATION, and its SELFWARD-OPERATION designates a list of operations L,\nthen the action (O . C) of O on component C depends on each (S . C) for S in L.\nE.g. before a component may be loaded by LOAD-OP, it must have been compiled by COMPILE-OP.\nA operation-designator designates a singleton list of the designated operation;\na list of operation-designators designates the list of designated operations;\nNIL is not a valid operation designator in that context.  Note that any dependency\nordering between the operations in a list of SELFWARD-OPERATION should be specified separately\nin the respective operation's COMPONENT-DEPENDS-ON methods so that they be scheduled properly.\"))\n  (defun selfward-operation-depends-on (o c)\n    (loop :for op :in (ensure-list (selfward-operation o)) :collect `(,op ,c)))\n  (defmethod component-depends-on ((o selfward-operation) (c component))\n    `(,@(selfward-operation-depends-on o c) ,@(call-next-method)))\n\n  (defclass non-propagating-operation (operation)\n    ()\n    (:documentation \"A NON-PROPAGATING-OPERATION is an operation that propagates\nno dependencies whatsoever.  It is supplied in order that the programmer be able\nto specify that s/he is intentionally specifying an operation which invokes no\ndependencies.\")))\n\n\n;;;---------------------------------------------------------------------------\n;;; Help programmers catch obsolete OPERATION subclasses\n;;;---------------------------------------------------------------------------\n(with-upgradability ()\n  (define-condition operation-definition-warning (simple-warning)\n    ()\n    (:documentation \"Warning condition related to definition of obsolete OPERATION objects.\"))\n\n  (define-condition operation-definition-error (simple-error)\n    ()\n    (:documentation \"Error condition related to definition of incorrect OPERATION objects.\"))\n\n  (defmethod initialize-instance :before ((o operation) &key)\n    (check-operation-constructor)\n    (unless (typep o '(or downward-operation upward-operation sideway-operation\n                          selfward-operation non-propagating-operation))\n      (warn 'operation-definition-warning\n            :format-control\n            \"No dependency propagating scheme specified for operation class ~S.\nThe class needs to be updated for ASDF 3.1 and specify appropriate propagation mixins.\"\n            :format-arguments (list (type-of o)))))\n\n  (defmethod initialize-instance :before ((o non-propagating-operation) &key)\n    (when (typep o '(or downward-operation upward-operation sideway-operation selfward-operation))\n      (error 'operation-definition-error\n             :format-control\n             \"Inconsistent class: ~S\n  NON-PROPAGATING-OPERATION is incompatible with propagating operation classes as superclasses.\"\n             :format-arguments\n             (list (type-of o)))))\n\n  (defun backward-compatible-depends-on (o c)\n    \"DEPRECATED: all subclasses of OPERATION used in ASDF should inherit from one of\n DOWNWARD-OPERATION UPWARD-OPERATION SIDEWAY-OPERATION SELFWARD-OPERATION NON-PROPAGATING-OPERATION.\n The function BACKWARD-COMPATIBLE-DEPENDS-ON temporarily provides ASDF2 behaviour for those that\n don't. In the future this functionality will be removed, and the default will be no propagation.\"\n    (uiop/version::notify-deprecated-function\n     (version-deprecation *asdf-version* :style-warning \"3.2\")\n     `(backward-compatible-depends-on :for-operation ,o))\n    `(,@(sideway-operation-depends-on o c)\n      ,@(when (typep c 'parent-component) (downward-operation-depends-on o c))))\n\n  (defmethod component-depends-on ((o operation) (c component))\n    `(;; Normal behavior, to allow user-specified in-order-to dependencies\n      ,@(cdr (assoc (type-of o) (component-in-order-to c)))\n        ;; For backward-compatibility with ASDF2, any operation that doesn't specify propagation\n        ;; or non-propagation through an appropriate mixin will be downward and sideway.\n        ,@(unless (typep o '(or downward-operation upward-operation sideway-operation\n                             selfward-operation non-propagating-operation))\n            (backward-compatible-depends-on o c))))\n\n  (defmethod downward-operation ((o operation)) nil)\n  (defmethod sideway-operation ((o operation)) nil))\n\n\n;;;---------------------------------------------------------------------------\n;;; End of OPERATION class checking\n;;;---------------------------------------------------------------------------\n\n\n;;;; Inputs, Outputs, and invisible dependencies\n(with-upgradability ()\n  (defgeneric output-files (operation component)\n    (:documentation \"Methods for this function return two values: a list of output files\ncorresponding to this action, and a boolean indicating if they have already been subjected\nto relevant output translations and should not be further translated.\n\nMethods on PERFORM *must* call this function to determine where their outputs are to be located.\nThey may rely on the order of the files to discriminate between outputs.\n\"))\n  (defgeneric input-files (operation component)\n    (:documentation \"A list of input files corresponding to this action.\n\nMethods on PERFORM *must* call this function to determine where their inputs are located.\nThey may rely on the order of the files to discriminate between inputs.\n\"))\n  (defgeneric operation-done-p (operation component)\n    (:documentation \"Returns a boolean which is NIL if the action must be performed (again).\"))\n  (define-convenience-action-methods output-files (operation component))\n  (define-convenience-action-methods input-files (operation component))\n  (define-convenience-action-methods operation-done-p (operation component))\n\n  (defmethod operation-done-p ((o operation) (c component))\n    t)\n\n  ;; Translate output files, unless asked not to. Memoize the result.\n  (defmethod output-files :around ((operation t) (component t))\n    (do-asdf-cache `(output-files ,operation ,component)\n      (values\n       (multiple-value-bind (pathnames fixedp) (call-next-method)\n         ;; 1- Make sure we have absolute pathnames\n         (let* ((directory (pathname-directory-pathname\n                            (component-pathname (find-component () component))))\n                (absolute-pathnames\n                  (loop\n                    :for pathname :in pathnames\n                    :collect (ensure-absolute-pathname pathname directory))))\n           ;; 2- Translate those pathnames as required\n           (if fixedp\n               absolute-pathnames\n               (mapcar *output-translation-function* absolute-pathnames))))\n       t)))\n  (defmethod output-files ((o operation) (c component))\n    nil)\n  (defun output-file (operation component)\n    \"The unique output file of performing OPERATION on COMPONENT\"\n    (let ((files (output-files operation component)))\n      (assert (length=n-p files 1))\n      (first files)))\n\n  (defgeneric additional-input-files (operation component)\n    (:documentation \"Additional input files for the operation on this\n    component.  These are files that are inferred, rather than\n    explicitly specified, and these are typically NOT files that\n    undergo operations directly.  Instead, they are files that it is\n    important for ASDF to know about in order to compute operation times,etc.\"))\n  (define-convenience-action-methods additional-input-files (operation component))\n  (defmethod additional-input-files ((op operation) (comp component))\n      (cdr (assoc op (%additional-input-files comp))))\n\n  ;; Memoize input files.\n  (defmethod input-files :around (operation component)\n    (do-asdf-cache `(input-files ,operation ,component)\n      ;; get the additional input files, if any\n      (append (call-next-method)\n              ;; must come after the first, for other code that\n              ;; assumes the first will be the \"key\" file\n              (additional-input-files operation component))))\n\n  ;; By default an action has no input-files.\n  (defmethod input-files ((o operation) (c component))\n    nil)\n\n  ;; An action with a selfward-operation by default gets its input-files from the output-files of\n  ;; the actions using selfward-operations it depends on (and the same component),\n  ;; or if there are none, on the component-pathname of the component if it's a file\n  ;; -- and then on the results of the next-method.\n  (defmethod input-files ((o selfward-operation) (c component))\n    `(,@(or (loop :for dep-o :in (ensure-list (selfward-operation o))\n                  :append (or (output-files dep-o c) (input-files dep-o c)))\n            (if-let ((pathname (component-pathname c)))\n              (and (file-pathname-p pathname) (list pathname))))\n      ,@(call-next-method))))\n\n\n;;;; Done performing\n(with-upgradability ()\n  ;; ASDF4: hide it behind plan-action-stamp\n  (defgeneric component-operation-time (operation component)\n    (:documentation \"Return the timestamp for when an action was last performed\"))\n  (defgeneric (setf component-operation-time) (time operation component)\n    (:documentation \"Update the timestamp for when an action was last performed\"))\n  (define-convenience-action-methods component-operation-time (operation component))\n\n  ;; ASDF4: hide it behind (setf plan-action-stamp)\n  (defgeneric mark-operation-done (operation component)\n    (:documentation \"Mark a action as having been just done.\n\nUpdates the action's COMPONENT-OPERATION-TIME to match the COMPUTE-ACTION-STAMP\nusing the JUST-DONE flag.\"))\n  (defgeneric compute-action-stamp (plan- operation component &key just-done)\n    ;; NB: using plan- rather than plan above allows clisp to upgrade from 2.26(!)\n    (:documentation \"Has this action been successfully done already,\nand at what known timestamp has it been done at or will it be done at?\n* PLAN is a plan object modelling future effects of actions,\n  or NIL to denote what actually happened.\n* OPERATION and COMPONENT denote the action.\nTakes keyword JUST-DONE:\n* JUST-DONE is a boolean that is true if the action was just successfully performed,\n  at which point we want compute the actual stamp and warn if files are missing;\n  otherwise we are making plans, anticipating the effects of the action.\nReturns two values:\n* a STAMP saying when it was done or will be done,\n  or T if the action involves files that need to be recomputed.\n* a boolean DONE-P that indicates whether the action has actually been done,\n  and both its output-files and its in-image side-effects are up to date.\"))\n\n  (defmethod component-operation-time ((o operation) (c component))\n    (gethash o (component-operation-times c)))\n\n  (defmethod (setf component-operation-time) (stamp (o operation) (c component))\n    (assert stamp () \"invalid null stamp for ~A\" (action-description o c))\n    (setf (gethash o (component-operation-times c)) stamp))\n\n  (defmethod mark-operation-done ((o operation) (c component))\n    (let ((stamp (compute-action-stamp nil o c :just-done t)))\n      (assert stamp () \"Failed to compute a stamp for completed action ~A\" (action-description o c))1\n      (setf (component-operation-time o c) stamp))))\n\n\n;;;; Perform\n(with-upgradability ()\n  (defgeneric perform (operation component)\n    (:documentation \"PERFORM an action, consuming its input-files and building its output-files\"))\n  (define-convenience-action-methods perform (operation component))\n\n  (defmethod perform :around ((o operation) (c component))\n    (while-visiting-action (o c) (call-next-method)))\n  (defmethod perform :before ((o operation) (c component))\n    (ensure-all-directories-exist (output-files o c)))\n  (defmethod perform :after ((o operation) (c component))\n    (mark-operation-done o c))\n  (defmethod perform ((o operation) (c parent-component))\n    nil)\n  (defmethod perform ((o operation) (c source-file))\n    ;; For backward compatibility, don't error on operations that don't specify propagation.\n    (when (typep o '(or downward-operation upward-operation sideway-operation\n                     selfward-operation non-propagating-operation))\n      (sysdef-error\n       (compatfmt \"~@<Required method ~S not implemented for ~/asdf-action:format-action/~@:>\")\n       'perform (make-action o c))))\n\n  ;; The restarts of the perform-with-restarts variant matter in an interactive context.\n  ;; The retry strategies of p-w-r itself, and/or the background workers of a multiprocess build\n  ;; may call perform directly rather than call p-w-r.\n  (defgeneric perform-with-restarts (operation component)\n    (:documentation \"PERFORM an action in a context where suitable restarts are in place.\"))\n  (defmethod perform-with-restarts (operation component)\n    (perform operation component))\n  (defmethod perform-with-restarts :around (operation component)\n    (loop\n      (restart-case\n          (return (call-next-method))\n        (retry ()\n          :report\n          (lambda (s)\n            (format s (compatfmt \"~@<Retry ~A.~@:>\")\n                    (action-description operation component))))\n        (accept ()\n          :report\n          (lambda (s)\n            (format s (compatfmt \"~@<Continue, treating ~A as having been successful.~@:>\")\n                    (action-description operation component)))\n          (mark-operation-done operation component)\n          (return))))))\n;;;; -------------------------------------------------------------------------\n;;;; Actions to build Common Lisp software\n\n(uiop/package:define-package :asdf/lisp-action\n  (:recycle :asdf/lisp-action :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n   :asdf/component :asdf/system :asdf/operation :asdf/action)\n  (:export\n   #:try-recompiling\n   #:cl-source-file #:cl-source-file.cl #:cl-source-file.lsp\n   #:basic-load-op #:basic-compile-op\n   #:load-op #:prepare-op #:compile-op #:test-op #:load-source-op #:prepare-source-op\n   #:call-with-around-compile-hook\n   #:perform-lisp-compilation #:perform-lisp-load-fasl #:perform-lisp-load-source\n   #:lisp-compilation-output-files))\n(in-package :asdf/lisp-action)\n\n\n;;;; Component classes\n(with-upgradability ()\n  (defclass cl-source-file (source-file)\n    ((type :initform \"lisp\"))\n    (:documentation \"Component class for a Common Lisp source file (using type \\\"lisp\\\")\"))\n  (defclass cl-source-file.cl (cl-source-file)\n    ((type :initform \"cl\"))\n    (:documentation \"Component class for a Common Lisp source file using type \\\"cl\\\"\"))\n  (defclass cl-source-file.lsp (cl-source-file)\n    ((type :initform \"lsp\"))\n    (:documentation \"Component class for a Common Lisp source file using type \\\"lsp\\\"\")))\n\n\n;;;; Operation classes\n(with-upgradability ()\n  (defclass basic-load-op (operation) ()\n    (:documentation \"Base class for operations that apply the load-time effects of a file\"))\n  (defclass basic-compile-op (operation) ()\n    (:documentation \"Base class for operations that apply the compile-time effects of a file\")))\n\n\n;;; Our default operations: loading into the current lisp image\n(with-upgradability ()\n  (defclass prepare-op (upward-operation sideway-operation)\n    ((sideway-operation :initform 'load-op :allocation :class))\n    (:documentation \"Load the dependencies for the COMPILE-OP or LOAD-OP of a given COMPONENT.\"))\n  (defclass load-op (basic-load-op downward-operation selfward-operation)\n    ;; NB: even though compile-op depends on prepare-op it is not needed-in-image-p,\n    ;; so we need to directly depend on prepare-op for its side-effects in the current image.\n    ((selfward-operation :initform '(prepare-op compile-op) :allocation :class))\n    (:documentation \"Operation for loading the compiled FASL for a Lisp file\"))\n  (defclass compile-op (basic-compile-op downward-operation selfward-operation)\n    ((selfward-operation :initform 'prepare-op :allocation :class))\n    (:documentation \"Operation for compiling a Lisp file to a FASL\"))\n\n\n  (defclass prepare-source-op (upward-operation sideway-operation)\n    ((sideway-operation :initform 'load-source-op :allocation :class))\n    (:documentation \"Operation for loading the dependencies of a Lisp file as source.\"))\n  (defclass load-source-op (basic-load-op downward-operation selfward-operation)\n    ((selfward-operation :initform 'prepare-source-op :allocation :class))\n    (:documentation \"Operation for loading a Lisp file as source.\"))\n\n  (defclass test-op (selfward-operation)\n    ((selfward-operation :initform 'load-op :allocation :class))\n    (:documentation \"Operation for running the tests for system.\nIf the tests fail, an error will be signaled.\")))\n\n\n;;;; Methods for prepare-op, compile-op and load-op\n\n;;; prepare-op\n(with-upgradability ()\n  (defmethod action-description ((o prepare-op) (c component))\n    (format nil (compatfmt \"~@<loading dependencies of ~3i~_~A~@:>\") c))\n  (defmethod perform ((o prepare-op) (c component))\n    nil)\n  (defmethod input-files ((o prepare-op) (s system))\n    (if-let (it (system-source-file s)) (list it))))\n\n;;; compile-op\n(with-upgradability ()\n  (defmethod action-description ((o compile-op) (c component))\n    (format nil (compatfmt \"~@<compiling ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o compile-op) (c parent-component))\n    (format nil (compatfmt \"~@<completing compilation for ~3i~_~A~@:>\") c))\n  (defgeneric call-with-around-compile-hook (component thunk)\n    (:documentation \"A method to be called around the PERFORM'ing of actions that apply the\ncompile-time side-effects of file (i.e., COMPILE-OP or LOAD-SOURCE-OP). This method can be used\nto setup readtables and other variables that control reading, macroexpanding, and compiling, etc.\nNote that it will NOT be called around the performing of LOAD-OP.\"))\n  (defmethod call-with-around-compile-hook ((c component) function)\n    (call-around-hook (around-compile-hook c) function))\n  (defun perform-lisp-compilation (o c)\n    \"Perform the compilation of the Lisp file associated to the specified action (O . C).\"\n    (let (;; Before 2.26.53, that was unfortunately component-pathname. Now,\n          ;; we consult input-files, the first of which should be the one to compile-file\n          (input-file (first (input-files o c)))\n          ;; On some implementations, there are more than one output-file,\n          ;; but the first one should always be the primary fasl that gets loaded.\n          (outputs (output-files o c)))\n      (multiple-value-bind (output warnings-p failure-p)\n          (destructuring-bind\n              (output-file\n               &optional\n                 #+(or clasp ecl mkcl) object-file\n                 #+clisp lib-file\n                 warnings-file &rest rest) outputs\n            ;; Allow for extra outputs that are not of type warnings-file\n            ;; The way we do it is kludgy. In ASDF4, output-files shall not be positional.\n            (declare (ignore rest))\n            (when warnings-file\n              (unless (equal (pathname-type warnings-file) (warnings-file-type))\n                (setf warnings-file nil)))\n            (let ((*package* (find-package* '#:common-lisp-user)))\n             (call-with-around-compile-hook\n              c #'(lambda (&rest flags)\n                    (apply 'compile-file* input-file\n                           :output-file output-file\n                           :external-format (component-external-format c)\n                           :warnings-file warnings-file\n                           (append\n                            #+clisp (list :lib-file lib-file)\n                            #+(or clasp ecl mkcl) (list :object-file object-file)\n                            flags))))))\n        (check-lisp-compile-results output warnings-p failure-p\n                                    \"~/asdf-action::format-action/\" (list (cons o c))))))\n  (defun report-file-p (f)\n    \"Is F a build report file containing, e.g., warnings to check?\"\n    (equalp (pathname-type f) \"build-report\"))\n  (defun perform-lisp-warnings-check (o c)\n    \"Check the warnings associated with the dependencies of an action.\"\n    (let* ((expected-warnings-files (remove-if-not #'warnings-file-p (input-files o c)))\n           (actual-warnings-files (loop :for w :in expected-warnings-files\n                                        :when (get-file-stamp w)\n                                          :collect w\n                                        :else :do (warn \"Missing warnings file ~S while ~A\"\n                                                        w (action-description o c)))))\n      (check-deferred-warnings actual-warnings-files)\n      (let* ((output (output-files o c))\n             (report (find-if #'report-file-p output)))\n        (when report\n          (with-open-file (s report :direction :output :if-exists :supersede)\n            (format s \":success~%\"))))))\n  (defmethod perform ((o compile-op) (c cl-source-file))\n    (perform-lisp-compilation o c))\n  (defun lisp-compilation-output-files (o c)\n    \"Compute the output-files for compiling the Lisp file for the specified action (O . C),\nan OPERATION and a COMPONENT.\"\n    (let* ((i (first (input-files o c)))\n           (f (compile-file-pathname\n               i #+clasp :output-type #+ecl :type #+(or clasp ecl) :fasl\n               #+mkcl :fasl-p #+mkcl t)))\n      `(,f ;; the fasl is the primary output, in first position\n        #+clasp\n        ,@(unless nil ;; was (use-ecl-byte-compiler-p)\n            `(,(compile-file-pathname i :output-type :object)))\n        #+clisp\n        ,@`(,(make-pathname :type \"lib\" :defaults f))\n        #+ecl\n        ,@(unless (use-ecl-byte-compiler-p)\n            `(,(compile-file-pathname i :type :object)))\n        #+mkcl\n        ,(compile-file-pathname i :fasl-p nil) ;; object file\n        ,@(when (and *warnings-file-type* (not (builtin-system-p (component-system c))))\n            `(,(make-pathname :type *warnings-file-type* :defaults f))))))\n  (defmethod output-files ((o compile-op) (c cl-source-file))\n    (lisp-compilation-output-files o c))\n  (defmethod perform ((o compile-op) (c static-file))\n    nil)\n\n  ;; Performing compile-op on a system will check the deferred warnings for the system\n  (defmethod perform ((o compile-op) (c system))\n    (when (and *warnings-file-type* (not (builtin-system-p c)))\n      (perform-lisp-warnings-check o c)))\n  (defmethod input-files ((o compile-op) (c system))\n    (when (and *warnings-file-type* (not (builtin-system-p c)))\n      ;; The most correct way to do it would be to use:\n      ;; (collect-dependencies o c :other-systems nil :keep-operation 'compile-op :keep-component 'cl-source-file)\n      ;; but it's expensive and we don't care too much about file order or ASDF extensions.\n      (loop :for sub :in (sub-components c :type 'cl-source-file)\n            :nconc (remove-if-not 'warnings-file-p (output-files o sub)))))\n  (defmethod output-files ((o compile-op) (c system))\n    (when (and *warnings-file-type* (not (builtin-system-p c)))\n      (if-let ((pathname (component-pathname c)))\n        (list (subpathname pathname (coerce-filename c) :type \"build-report\"))))))\n\n;;; load-op\n(with-upgradability ()\n  (defmethod action-description ((o load-op) (c cl-source-file))\n    (format nil (compatfmt \"~@<loading FASL for ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o load-op) (c parent-component))\n    (format nil (compatfmt \"~@<completing load for ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o load-op) (c component))\n    (format nil (compatfmt \"~@<loading ~3i~_~A~@:>\") c))\n  (defmethod perform-with-restarts ((o load-op) (c cl-source-file))\n    (loop\n      (restart-case\n          (return (call-next-method))\n        (try-recompiling ()\n          :report (lambda (s)\n                    (format s \"Recompile ~a and try loading it again\"\n                            (component-name c)))\n          (perform (find-operation o 'compile-op) c)))))\n  (defun perform-lisp-load-fasl (o c)\n    \"Perform the loading of a FASL associated to specified action (O . C),\nan OPERATION and a COMPONENT.\"\n    (if-let (fasl (first (input-files o c)))\n            (let ((*package* (find-package '#:common-lisp-user)))\n              (load* fasl))))\n  (defmethod perform ((o load-op) (c cl-source-file))\n    (perform-lisp-load-fasl o c))\n  (defmethod perform ((o load-op) (c static-file))\n    nil))\n\n\n;;;; prepare-source-op, load-source-op\n\n;;; prepare-source-op\n(with-upgradability ()\n  (defmethod action-description ((o prepare-source-op) (c component))\n    (format nil (compatfmt \"~@<loading source for dependencies of ~3i~_~A~@:>\") c))\n  (defmethod input-files ((o prepare-source-op) (s system))\n    (if-let (it (system-source-file s)) (list it)))\n  (defmethod perform ((o prepare-source-op) (c component))\n    nil))\n\n;;; load-source-op\n(with-upgradability ()\n  (defmethod action-description ((o load-source-op) (c component))\n    (format nil (compatfmt \"~@<Loading source of ~3i~_~A~@:>\") c))\n  (defmethod action-description ((o load-source-op) (c parent-component))\n    (format nil (compatfmt \"~@<Loaded source of ~3i~_~A~@:>\") c))\n  (defun perform-lisp-load-source (o c)\n    \"Perform the loading of a Lisp file as associated to specified action (O . C)\"\n    (call-with-around-compile-hook\n     c #'(lambda ()\n           (load* (first (input-files o c))\n                  :external-format (component-external-format c)))))\n\n  (defmethod perform ((o load-source-op) (c cl-source-file))\n    (perform-lisp-load-source o c))\n  (defmethod perform ((o load-source-op) (c static-file))\n    nil))\n\n\n;;;; test-op\n(with-upgradability ()\n  (defmethod perform ((o test-op) (c component))\n    nil)\n  (defmethod operation-done-p ((o test-op) (c system))\n    \"Testing a system is _never_ done.\"\n    nil))\n;;;; -------------------------------------------------------------------------\n;;;; Finding components\n\n(uiop/package:define-package :asdf/find-component\n  (:recycle :asdf/find-component :asdf/find-system :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n   :asdf/component :asdf/system :asdf/system-registry)\n  (:export\n   #:find-component\n   #:resolve-dependency-name #:resolve-dependency-spec\n   #:resolve-dependency-combination\n   ;; Conditions\n   #:missing-component #:missing-requires #:missing-parent #:missing-component-of-version #:retry\n   #:missing-dependency #:missing-dependency-of-version\n   #:missing-requires #:missing-parent\n   #:missing-required-by #:missing-version))\n(in-package :asdf/find-component)\n\n;;;; Missing component conditions\n\n(with-upgradability ()\n  (define-condition missing-component (system-definition-error)\n    ((requires :initform \"(unnamed)\" :reader missing-requires :initarg :requires)\n     (parent :initform nil :reader missing-parent :initarg :parent)))\n\n  (define-condition missing-component-of-version (missing-component)\n    ((version :initform nil :reader missing-version :initarg :version)))\n\n  (define-condition missing-dependency (missing-component)\n    ((required-by :initarg :required-by :reader missing-required-by)))\n\n  (defmethod print-object ((c missing-dependency) s)\n    (format s (compatfmt \"~@<~A, required by ~A~@:>\")\n            (call-next-method c nil) (missing-required-by c)))\n\n  (define-condition missing-dependency-of-version (missing-dependency\n                                                   missing-component-of-version)\n    ())\n\n  (defmethod print-object ((c missing-component) s)\n    (format s (compatfmt \"~@<Component ~S not found~@[ in ~A~]~@:>\")\n            (missing-requires c)\n            (when (missing-parent c)\n              (coerce-name (missing-parent c)))))\n\n  (defmethod print-object ((c missing-component-of-version) s)\n    (format s (compatfmt \"~@<Component ~S does not match version ~A~@[ in ~A~]~@:>\")\n            (missing-requires c)\n            (missing-version c)\n            (when (missing-parent c)\n              (coerce-name (missing-parent c))))))\n\n\n;;;; Finding components\n\n(with-upgradability ()\n  (defgeneric resolve-dependency-combination (component combinator arguments)\n    (:documentation \"Return a component satisfying the dependency specification (COMBINATOR . ARGUMENTS)\nin the context of COMPONENT\"))\n\n  ;; Methods for find-component\n\n  ;; If the base component is a string, resolve it as a system, then if not nil follow the path.\n  (defmethod find-component ((base string) path &key registered)\n    (if-let ((s (if registered\n                    (registered-system base)\n                    (find-system base nil))))\n      (find-component s path :registered registered)))\n\n  ;; If the base component is a symbol, coerce it to a name if not nil, and resolve that.\n  ;; If nil, use the path as base if not nil, or else return nil.\n  (defmethod find-component ((base symbol) path &key registered)\n    (cond\n      (base (find-component (coerce-name base) path :registered registered))\n      (path (find-component path nil :registered registered))\n      (t    nil)))\n\n  ;; If the base component is a cons cell, resolve its car, and add its cdr to the path.\n  (defmethod find-component ((base cons) path &key registered)\n    (find-component (car base) (cons (cdr base) path) :registered registered))\n\n  ;; If the base component is a parent-component and the path a string, find the named child.\n  (defmethod find-component ((parent parent-component) (name string) &key registered)\n    (declare (ignorable registered))\n    (compute-children-by-name parent :only-if-needed-p t)\n    (values (gethash name (component-children-by-name parent))))\n\n  ;; If the path is a symbol, coerce it to a name if non-nil, or else just return the base.\n  (defmethod find-component (base (name symbol) &key registered)\n    (if name\n        (find-component base (coerce-name name) :registered registered)\n        base))\n\n  ;; If the path is a cons, first resolve its car as path, then its cdr.\n  (defmethod find-component ((c component) (name cons) &key registered)\n    (find-component (find-component c (car name) :registered registered)\n                    (cdr name) :registered registered))\n\n  ;; If the path is a component, return it, disregarding the base.\n  (defmethod find-component ((base t) (actual component) &key registered)\n    (declare (ignorable registered))\n    actual)\n\n  ;; Resolve dependency NAME in the context of a COMPONENT, with given optional VERSION constraint.\n  ;; This (private) function is used below by RESOLVE-DEPENDENCY-SPEC and by the :VERSION spec.\n  (defun resolve-dependency-name (component name &optional version)\n    (loop\n      (restart-case\n          (return\n            (let ((comp (find-component (component-parent component) name)))\n              (unless comp\n                (error 'missing-dependency\n                       :required-by component\n                       :requires name))\n              (when version\n                (unless (version-satisfies comp version)\n                  (error 'missing-dependency-of-version\n                         :required-by component\n                         :version version\n                         :requires name)))\n              comp))\n        (retry ()\n          :report (lambda (s)\n                    (format s (compatfmt \"~@<Retry loading ~3i~_~A.~@:>\") name))\n          :test\n          (lambda (c)\n            (or (null c)\n                (and (typep c 'missing-dependency)\n                     (eq (missing-required-by c) component)\n                     (equal (missing-requires c) name))))\n          (unless (component-parent component)\n            (let ((name (coerce-name name)))\n              (unset-asdf-cache-entry `(find-system ,name))))))))\n\n  ;; Resolve dependency specification DEP-SPEC in the context of COMPONENT.\n  ;; This is notably used by MAP-DIRECT-DEPENDENCIES to process the results of COMPONENT-DEPENDS-ON\n  ;; and by PARSE-DEFSYSTEM to process DEFSYSTEM-DEPENDS-ON.\n  (defun resolve-dependency-spec (component dep-spec)\n    (let ((component (find-component () component)))\n      (if (atom dep-spec)\n          (resolve-dependency-name component dep-spec)\n          (resolve-dependency-combination component (car dep-spec) (cdr dep-spec)))))\n\n  ;; Methods for RESOLVE-DEPENDENCY-COMBINATION to parse lists as dependency specifications.\n  (defmethod resolve-dependency-combination (component combinator arguments)\n    (parameter-error (compatfmt \"~@<In ~S, bad dependency ~S for ~S~@:>\")\n                     'resolve-dependency-combination (cons combinator arguments) component))\n\n  (defmethod resolve-dependency-combination (component (combinator (eql :feature)) arguments)\n    (when (featurep (first arguments))\n      (resolve-dependency-spec component (second arguments))))\n\n  (defmethod resolve-dependency-combination (component (combinator (eql :version)) arguments)\n    (resolve-dependency-name component (first arguments) (second arguments)))) ;; See lp#527788\n\n;;;; -------------------------------------------------------------------------\n;;;; Forcing\n\n(uiop/package:define-package :asdf/forcing\n  (:recycle :asdf/forcing :asdf/plan :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n        :asdf/component :asdf/operation :asdf/system :asdf/system-registry)\n  (:export\n   #:forcing #:make-forcing #:forced #:forced-not #:performable-p\n   #:normalize-forced-systems #:normalize-forced-not-systems\n   #:action-forced-p #:action-forced-not-p))\n(in-package :asdf/forcing)\n\n;;;; Forcing\n(with-upgradability ()\n  (defclass forcing ()\n    (;; Can plans using this forcing be PERFORMed? A plan that has different force and force-not\n     ;; settings than the session can only be used for read-only queries that do not cause the\n     ;; status of any action to be raised.\n     (performable-p :initform nil :initarg :performable-p :reader performable-p)\n     ;; Parameters\n     (parameters :initform nil :initarg :parameters :reader parameters)\n     ;; Table of systems specified via :force arguments\n     (forced :initarg :forced :reader forced)\n     ;; Table of systems specified via :force-not argument (and/or immutable)\n     (forced-not :initarg :forced-not :reader forced-not)))\n\n  (defgeneric action-forced-p (forcing operation component)\n    (:documentation \"Is this action forced to happen in this plan?\"))\n  (defgeneric action-forced-not-p (forcing operation component)\n    (:documentation \"Is this action forced to not happen in this plan?\nTakes precedence over action-forced-p.\"))\n\n  (defun normalize-forced-systems (force system)\n    \"Given a SYSTEM on which operate is called and the specified FORCE argument,\nextract a hash-set of systems that are forced, or a predicate on system names,\nor NIL if none are forced, or :ALL if all are.\"\n    (etypecase force\n      ((or (member nil :all) hash-table function) force)\n      (cons (list-to-hash-set (mapcar #'coerce-name force)))\n      ((eql t) (when system (list-to-hash-set (list (coerce-name system)))))))\n\n  (defun normalize-forced-not-systems (force-not system)\n    \"Given a SYSTEM on which operate is called, the specified FORCE-NOT argument,\nand the set of IMMUTABLE systems, extract a hash-set of systems that are effectively forced-not,\nor predicate on system names, or NIL if none are forced, or :ALL if all are.\"\n    (let ((requested\n            (etypecase force-not\n              ((or (member nil :all) hash-table function) force-not)\n              (cons (list-to-hash-set (mapcar #'coerce-name force-not)))\n              ((eql t) (if system (let ((name (coerce-name system)))\n                                    #'(lambda (x) (not (equal x name))))\n                           :all)))))\n      (if (and *immutable-systems* requested)\n          #'(lambda (x) (or (call-function requested x)\n                            (call-function *immutable-systems* x)))\n          (or *immutable-systems* requested))))\n\n  ;; TODO: shouldn't we be looking up the primary system name, rather than the system name?\n  (defun action-override-p (forcing operation component override-accessor)\n    \"Given a plan, an action, and a function that given the plan accesses a set of overrides,\ni.e. force or force-not, see if the override applies to the current action.\"\n    (declare (ignore operation))\n    (call-function (funcall override-accessor forcing)\n                   (coerce-name (component-system (find-component () component)))))\n\n  (defmethod action-forced-p (forcing operation component)\n    (and\n     ;; Did the user ask us to re-perform the action?\n     (action-override-p forcing operation component 'forced)\n     ;; You really can't force a builtin system and :all doesn't apply to it.\n     (not (builtin-system-p (component-system component)))))\n\n  (defmethod action-forced-not-p (forcing operation component)\n    ;; Did the user ask us to not re-perform the action?\n    ;; NB: force-not takes precedence over force, as it should\n    (action-override-p forcing operation component 'forced-not))\n\n  ;; Null forcing means no forcing either way\n  (defmethod action-forced-p ((forcing null) (operation operation) (component component))\n    nil)\n  (defmethod action-forced-not-p ((forcing null) (operation operation) (component component))\n    nil)\n\n  (defun or-function (fun1 fun2)\n    (cond\n      ((or (null fun2) (eq fun1 :all)) fun1)\n      ((or (null fun1) (eq fun2 :all)) fun2)\n      (t #'(lambda (x) (or (call-function fun1 x) (call-function fun2 x))))))\n\n  (defun make-forcing (&key performable-p system\n                         (force nil force-p) (force-not nil force-not-p) &allow-other-keys)\n    (let* ((session-forcing (when *asdf-session* (forcing *asdf-session*)))\n           (system (and system (coerce-name system)))\n           (forced (normalize-forced-systems force system))\n           (forced-not (normalize-forced-not-systems force-not system))\n           (parameters `(,@(when force `(:force ,force))\n                         ,@(when force-not `(:force-not ,force-not))\n                         ,@(when (or (eq force t) (eq force-not t)) `(:system ,system))\n                         ,@(when performable-p `(:performable-p t))))\n           forcing)\n      (cond\n        ((not session-forcing)\n         (setf forcing (make-instance 'forcing\n                                      :performable-p performable-p :parameters parameters\n                                      :forced forced :forced-not forced-not))\n         (when (and performable-p *asdf-session*)\n           (setf (forcing *asdf-session*) forcing)))\n        (performable-p\n         (when (and (not (equal parameters (parameters session-forcing)))\n                    (or force-p force-not-p))\n           (parameter-error \"~*~S and ~S arguments not allowed in a nested call to ~3:*~S ~\nunless identically to toplevel\"\n                            (find-symbol* :operate :asdf) :force :force-not))\n         (setf forcing session-forcing))\n        (t\n         (setf forcing (make-instance 'forcing\n                           ;; Combine force and force-not with values from the toplevel-plan\n                           :parameters `(,@parameters :on-top-of ,(parameters session-forcing))\n                           :forced (or-function (forced session-forcing) forced)\n                           :forced-not (or-function (forced-not session-forcing) forced-not)))))\n      forcing))\n\n  (defmethod print-object ((forcing forcing) stream)\n    (print-unreadable-object (forcing stream :type t)\n      (format stream \"~{~S~^ ~}\" (parameters forcing))))\n\n  ;; During upgrade, the *asdf-session* may legitimately be NIL, so we must handle that case.\n  (defmethod forcing ((x null))\n    (if-let (session (toplevel-asdf-session))\n      (forcing session)\n      (make-forcing :performable-p t)))\n\n  ;; When performing a plan that is a list of actions, use the toplevel asdf sesssion forcing.\n  (defmethod forcing ((x cons)) (forcing (toplevel-asdf-session))))\n;;;; -------------------------------------------------------------------------\n;;;; Plan\n\n(uiop/package:define-package :asdf/plan\n  ;; asdf/action below is needed for required-components, traverse-action and traverse-sub-actions\n  ;; that used to live there before 3.2.0.\n  (:recycle :asdf/plan :asdf/action :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n        :asdf/component :asdf/operation :asdf/action :asdf/lisp-action\n        :asdf/system :asdf/system-registry :asdf/find-component :asdf/forcing)\n  (:export\n   #:plan #:plan-traversal #:sequential-plan #:*plan-class*\n   #:action-status #:status-stamp #:status-index #:status-done-p #:status-keep-p #:status-need-p\n   #:action-already-done-p\n   #:+status-good+ #:+status-todo+ #:+status-void+\n   #:system-out-of-date #:action-up-to-date-p\n   #:circular-dependency #:circular-dependency-actions\n   #:needed-in-image-p\n   #:map-direct-dependencies #:reduce-direct-dependencies #:direct-dependencies\n   #:compute-action-stamp #:traverse-action #:record-dependency\n   #:make-plan #:plan-actions #:plan-actions-r #:perform-plan #:mark-as-done\n   #:required-components #:filtered-sequential-plan\n   #:plan-component-type #:plan-keep-operation #:plan-keep-component))\n(in-package :asdf/plan)\n\n;;;; Generic plan traversal class\n(with-upgradability ()\n  (defclass plan () ()\n    (:documentation \"Base class for a plan based on which ASDF can build a system\"))\n  (defclass plan-traversal (plan)\n    (;; The forcing parameters for this plan. Also indicates whether the plan is performable,\n     ;; in which case the forcing is the same as for the entire session.\n     (forcing :initform (forcing (toplevel-asdf-session)) :initarg :forcing :reader forcing))\n    (:documentation \"Base class for plans that simply traverse dependencies\"))\n  ;; Sequential plans (the default)\n  (defclass sequential-plan (plan-traversal)\n    ((actions-r :initform nil :accessor plan-actions-r))\n    (:documentation \"Simplest, default plan class, accumulating a sequence of actions\"))\n\n  (defgeneric plan-actions (plan)\n    (:documentation \"Extract from a plan a list of actions to perform in sequence\"))\n  (defmethod plan-actions ((plan list))\n    plan)\n  (defmethod plan-actions ((plan sequential-plan))\n    (reverse (plan-actions-r plan)))\n\n  (defgeneric record-dependency (plan operation component)\n    (:documentation \"Record that, within PLAN, performing OPERATION on COMPONENT depends on all\nof the (OPERATION . COMPONENT) actions in the current ASDF session's VISITING-ACTION-LIST.\n\nYou can get a single action which dominates the set of dependencies corresponding to this call with\n(first (visiting-action-list *asdf-session*))\nsince VISITING-ACTION-LIST is a stack whose top action depends directly on its second action,\nand whose second action depends directly on its third action, and so forth.\"))\n\n  ;; No need to record a dependency to build a full graph, just accumulate nodes in order.\n  (defmethod record-dependency ((plan sequential-plan) (o operation) (c component))\n    (values)))\n\n(when-upgrading (:version \"3.3.0\")\n  (defmethod initialize-instance :after ((plan plan-traversal) &key &allow-other-keys)))\n\n\n;;;; Planned action status\n(with-upgradability ()\n  (defclass action-status ()\n    ((bits\n      :type fixnum :initarg :bits :reader status-bits\n      :documentation \"bitmap describing the status of the action.\")\n     (stamp\n      :type (or integer boolean) :initarg :stamp :reader status-stamp\n      :documentation \"STAMP associated with the ACTION if it has been completed already in some\nprevious session or image, T if it was done and builtin the image, or NIL if it needs to be done.\")\n     (level\n      :type fixnum :initarg :level :initform 0 :reader status-level\n      :documentation \"the highest (operate-level) at which the action was needed\")\n     (index\n      :type (or integer null) :initarg :index :initform nil :reader status-index\n      :documentation \"INDEX associated with the ACTION in the current session,\nor NIL if no the status is considered outside of a specific plan.\"))\n    (:documentation \"Status of an action in a plan\"))\n\n  ;; STAMP   KEEP-P DONE-P NEED-P     symbol bitmap  previously   currently\n  ;; not-nil   T      T      T     =>  GOOD     7    up-to-date   done (e.g. file previously loaded)\n  ;; not-nil   T      T     NIL    =>  HERE     6    up-to-date   unplanned yet done\n  ;; not-nil   T     NIL     T     =>  REDO     5    up-to-date   planned (e.g. file to load)\n  ;; not-nil   T     NIL    NIL    =>  SKIP     4    up-to-date   unplanned (e.g. file compiled)\n  ;; not-nil  NIL     T      T     =>  DONE     3    out-of-date  done\n  ;; not-nil  NIL     T     NIL    =>  WHAT     2    out-of-date  unplanned yet done(?)\n  ;;  NIL     NIL    NIL     T     =>  TODO     1    out-of-date  planned\n  ;;  NIL     NIL    NIL    NIL    =>  VOID     0    out-of-date  unplanned\n  ;;\n  ;; Note that a VOID status cannot happen as part of a transitive dependency of a wanted node\n  ;; while traversing a node with TRAVERSE-ACTION; it can only happen while checking whether an\n  ;; action is up-to-date with ACTION-UP-TO-DATE-P.\n  ;;\n  ;; When calling TRAVERSE-ACTION, the +need-bit+ is set,\n  ;; unless the action is up-to-date and not needed-in-image (HERE, SKIP).\n  ;; When PERFORMing an action, the +done-bit+ is set.\n  ;; When the +need-bit+ is set but not the +done-bit+, the level slot indicates which level of\n  ;; OPERATE it was last marked needed for; if it happens to be needed at a higher-level, then\n  ;; its urgency (and that of its transitive dependencies) must be escalated so that it will be\n  ;; done before the end of this level of operate.\n  ;;\n  ;; Also, when no ACTION-STATUS is associated to an action yet, NIL serves as a bottom value.\n  ;;\n  (defparameter +keep-bit+ 4)\n  (defparameter +done-bit+ 2)\n  (defparameter +need-bit+ 1)\n  (defparameter +good-bits+ 7)\n  (defparameter +todo-bits+ 1)\n  (defparameter +void-bits+ 0)\n\n  (defparameter +status-good+\n    (make-instance 'action-status :bits +good-bits+ :stamp t))\n  (defparameter +status-todo+\n    (make-instance 'action-status :bits +todo-bits+ :stamp nil))\n  (defparameter +status-void+\n    (make-instance 'action-status :bits +void-bits+ :stamp nil)))\n\n(with-upgradability ()\n  (defun make-action-status (&key bits stamp (level 0) index)\n    (check-type bits (integer 0 7))\n    (check-type stamp (or integer boolean))\n    (check-type level (integer 0 #.most-positive-fixnum))\n    (check-type index (or integer null))\n    (assert (eq (null stamp) (zerop (logand bits #.(logior +keep-bit+ +done-bit+)))) ()\n            \"Bad action-status :bits ~S :stamp ~S\" bits stamp)\n    (block nil\n      (when (and (null index) (zerop level))\n        (case bits\n          (#.+void-bits+ (return +status-void+))\n          (#.+todo-bits+ (return +status-todo+))\n          (#.+good-bits+ (when (eq stamp t) (return +status-good+)))))\n      (make-instance 'action-status :bits bits :stamp stamp :level level :index index)))\n\n  (defun status-keep-p (status)\n    (plusp (logand (status-bits status) #.+keep-bit+)))\n  (defun status-done-p (status)\n    (plusp (logand (status-bits status) #.+done-bit+)))\n  (defun status-need-p (status)\n    (plusp (logand (status-bits status) #.+need-bit+)))\n\n  (defun merge-action-status (status1 status2) ;; status-and\n    \"Return the earliest status later than both status1 and status2\"\n    (make-action-status\n     :bits (logand (status-bits status1) (status-bits status2))\n     :stamp (latest-timestamp (status-stamp status1) (status-stamp status2))\n     :level (min (status-level status1) (status-level status2))\n     :index (or (status-index status1) (status-index status2))))\n\n  (defun mark-status-needed (status &optional (level (operate-level))) ;; limited status-or\n    \"Return the same status but with the need bit set, for the given level\"\n    (if (and (status-need-p status)\n             (>= (status-level status) level))\n        status\n        (make-action-status\n         :bits (logior (status-bits status) +need-bit+)\n         :level (max level (status-level status))\n         :stamp (status-stamp status)\n         :index (status-index status))))\n\n  (defmethod print-object ((status action-status) stream)\n    (print-unreadable-object (status stream :type t)\n      (with-slots (bits stamp level index) status\n        (format stream \"~{~S~^ ~}\" `(:bits ,bits :stamp ,stamp :level ,level :index ,index)))))\n\n  (defgeneric action-status (plan operation component)\n    (:documentation \"Returns the ACTION-STATUS associated to the action of OPERATION on COMPONENT\nin the PLAN, or NIL if the action wasn't visited yet as part of the PLAN.\"))\n\n  (defgeneric (setf action-status) (new-status plan operation component)\n    (:documentation \"Sets the ACTION-STATUS associated to\nthe action of OPERATION on COMPONENT in the PLAN\"))\n\n  (defmethod action-status ((plan null) (o operation) (c component))\n    (multiple-value-bind (stamp done-p) (component-operation-time o c)\n      (if done-p\n          (make-action-status :bits #.+keep-bit+ :stamp stamp)\n          +status-void+)))\n\n  (defmethod (setf action-status) (new-status (plan null) (o operation) (c component))\n    (let ((times (component-operation-times c)))\n      (if (status-done-p new-status)\n          (setf (gethash o times) (status-stamp new-status))\n          (remhash o times)))\n    new-status)\n\n  ;; Handle FORCED-NOT: it makes an action return its current timestamp as status\n  (defmethod action-status ((p plan) (o operation) (c component))\n    ;; TODO: should we instead test something like:\n    ;; (action-forced-not-p plan operation (primary-system component))\n    (or (gethash (make-action o c) (visited-actions *asdf-session*))\n        (when (action-forced-not-p (forcing p) o c)\n          (let ((status (action-status nil o c)))\n            (setf (gethash (make-action o c) (visited-actions *asdf-session*))\n                  (make-action-status\n                   :bits +good-bits+\n                   :stamp (or (and status (status-stamp status)) t)\n                   :index (incf (total-action-count *asdf-session*))))))))\n\n  (defmethod (setf action-status) (new-status (p plan) (o operation) (c component))\n    (setf (gethash (make-action o c) (visited-actions *asdf-session*)) new-status))\n\n  (defmethod (setf action-status) :after\n      (new-status (p sequential-plan) (o operation) (c component))\n    (unless (status-done-p new-status)\n      (push (make-action o c) (plan-actions-r p)))))\n\n\n;;;; Is the action needed in this image?\n(with-upgradability ()\n  (defgeneric needed-in-image-p (operation component)\n    (:documentation \"Is the action of OPERATION on COMPONENT needed in the current image\nto be meaningful, or could it just as well have been done in another Lisp image?\"))\n\n  (defmethod needed-in-image-p ((o operation) (c component))\n    ;; We presume that actions that modify the filesystem don't need be run\n    ;; in the current image if they have already been done in another,\n    ;; and can be run in another process (e.g. a fork),\n    ;; whereas those that don't are meant to side-effect the current image and can't.\n    (not (output-files o c))))\n\n\n;;;; Visiting dependencies of an action and computing action stamps\n(with-upgradability ()\n  (defun map-direct-dependencies (operation component fun)\n    \"Call FUN on all the valid dependencies of the given action in the given plan\"\n    (loop :for (dep-o-spec . dep-c-specs) :in (component-depends-on operation component)\n          :for dep-o = (find-operation operation dep-o-spec)\n          :when dep-o\n            :do (loop :for dep-c-spec :in dep-c-specs\n                      :for dep-c = (and dep-c-spec (resolve-dependency-spec component dep-c-spec))\n                      :when (action-valid-p dep-o dep-c)\n                        :do (funcall fun dep-o dep-c))))\n\n  (defun reduce-direct-dependencies (operation component combinator seed)\n    \"Reduce the direct dependencies to a value computed by iteratively calling COMBINATOR\nfor each dependency action on the dependency's operation and component and an accumulator\ninitialized with SEED.\"\n    (map-direct-dependencies\n     operation component\n     #'(lambda (dep-o dep-c) (setf seed (funcall combinator dep-o dep-c seed))))\n    seed)\n\n  (defun direct-dependencies (operation component)\n    \"Compute a list of the direct dependencies of the action within the plan\"\n    (reverse (reduce-direct-dependencies operation component #'acons nil)))\n\n  ;; In a distant future, get-file-stamp, component-operation-time and latest-stamp\n  ;; shall also be parametrized by the plan, or by a second model object,\n  ;; so they need not refer to the state of the filesystem,\n  ;; and the stamps could be cryptographic checksums rather than timestamps.\n  ;; Such a change remarkably would only affect COMPUTE-ACTION-STAMP.\n  (define-condition dependency-not-done (warning)\n    ((op\n      :initarg :op)\n     (component\n      :initarg :component)\n     (dep-op\n      :initarg :dep-op)\n     (dep-component\n      :initarg :dep-component)\n     (plan\n      :initarg :plan\n      :initform nil))\n    (:report (lambda (condition stream)\n               (with-slots (op component dep-op dep-component plan) condition\n                 (format stream \"Computing just-done stamp ~@[in plan ~S~] for action ~S, but dependency ~S wasn't done yet!\"\n                         plan\n                         (action-path (make-action op component))\n                         (action-path (make-action dep-op dep-component)))))))\n\n  (defmethod compute-action-stamp (plan (o operation) (c component) &key just-done)\n    ;; Given an action, figure out at what time in the past it has been done,\n    ;; or if it has just been done, return the time that it has.\n    ;; Returns two values:\n    ;; 1- the TIMESTAMP of the action if it has already been done and is up to date,\n    ;;   or NIL is either hasn't been done or is out of date.\n    ;;   (An ASDF extension could use a cryptographic digest instead.)\n    ;; 2- the DONE-IN-IMAGE-P boolean flag that is T if the action has already been done\n    ;;   in the current image, or NIL if it hasn't.\n    ;; Note that if e.g. LOAD-OP only depends on up-to-date files, but\n    ;; hasn't been done in the current image yet, then it can have a non-NIL timestamp,\n    ;; yet a NIL done-in-image-p flag: we can predict what timestamp it will have once loaded,\n    ;; i.e. that of the input-files.\n    ;; If just-done is NIL, these values return are the notional fields of\n    ;; a KEEP, REDO or TODO status (VOID is possible, but probably an error).\n    ;; If just-done is T, they are the notional fields of DONE status\n    ;; (or, if something went wrong, TODO).\n    (nest\n     (block ())\n     (let* ((dep-status ; collect timestamp from dependencies (or T if forced or out-of-date)\n             (reduce-direct-dependencies\n              o c\n              #'(lambda (do dc status)\n                  ;; out-of-date dependency: don't bother looking further\n                  (let ((action-status (action-status plan do dc)))\n                    (cond\n                      ((and action-status (or (status-keep-p action-status)\n                                              (and just-done (status-stamp action-status))))\n                       (merge-action-status action-status status))\n                      (just-done\n                       ;; It's OK to lose some ASDF action stamps during self-upgrade\n                       (unless (equal \"asdf\" (primary-system-name dc))\n                         (warn 'dependency-not-done\n                               :plan plan\n                               :op o :component c\n                               :dep-op do :dep-component dc))\n                       status)\n                      (t\n                       (return (values nil nil))))))\n              +status-good+))\n            (dep-stamp (status-stamp dep-status))))\n     (let* (;; collect timestamps from inputs, and exit early if any is missing\n            (in-files (input-files o c))\n            (in-stamps (mapcar #'get-file-stamp in-files))\n            (missing-in (loop :for f :in in-files :for s :in in-stamps :unless s :collect f))\n            (latest-in (timestamps-latest (cons dep-stamp in-stamps))))\n       (when (and missing-in (not just-done)) (return (values nil nil))))\n     (let* (;; collect timestamps from outputs, and exit early if any is missing\n            (out-files (remove-if 'null (output-files o c)))\n            (out-stamps (mapcar (if just-done 'register-file-stamp 'get-file-stamp) out-files))\n            (missing-out (loop :for f :in out-files :for s :in out-stamps :unless s :collect f))\n            (earliest-out (timestamps-earliest out-stamps)))\n       (when (and missing-out (not just-done)) (return (values nil nil))))\n     (let (;; Time stamps from the files at hand, and whether any is missing\n           (all-present (not (or missing-in missing-out)))\n           ;; Has any input changed since we last generated the files?\n           ;; Note that we use timestamp<= instead of timestamp< to play nice with generated files.\n           ;; Any race condition is intrinsic to the limited timestamp resolution.\n           (up-to-date-p (timestamp<= latest-in earliest-out))\n           ;; If everything is up to date, the latest of inputs and outputs is our stamp\n           (done-stamp (timestamps-latest (cons latest-in out-stamps))))\n       ;; Warn if some files are missing:\n       ;; either our model is wrong or some other process is messing with our files.\n       (when (and just-done (not all-present))\n         ;; Shouldn't that be an error instead?\n         (warn \"~A completed without ~:[~*~;~*its input file~:p~2:*~{ ~S~}~*~]~\n                ~:[~; or ~]~:[~*~;~*its output file~:p~2:*~{ ~S~}~*~]\"\n               (action-description o c)\n               missing-in (length missing-in) (and missing-in missing-out)\n               missing-out (length missing-out))))\n     (let (;; There are three kinds of actions:\n           (out-op (and out-files t)) ; those that create files on the filesystem\n           ;;(image-op (and in-files (null out-files))) ; those that load stuff into the image\n           ;;(null-op (and (null out-files) (null in-files))) ; placeholders that do nothing\n           ))\n     (if (or just-done ;; The done-stamp is valid: if we're just done, or\n             (and all-present ;; if all filesystem effects are up-to-date\n                  up-to-date-p\n                  (operation-done-p o c) ;; and there's no invalidating reason.\n                  (not (action-forced-p (forcing (or plan *asdf-session*)) o c))))\n         (values done-stamp ;; return the hard-earned timestamp\n                 (or just-done\n                     out-op ;; A file-creating op is done when all files are up to date.\n                     ;; An image-effecting operation is done when\n                     (and (status-done-p dep-status) ;; all the dependencies were done, and\n                          (multiple-value-bind (perform-stamp perform-done-p)\n                              (component-operation-time o c)\n                            (and perform-done-p ;; the op was actually run,\n                                 (equal perform-stamp done-stamp)))))) ;; with a matching stamp.\n         ;; done-stamp invalid: return a timestamp in an indefinite future, action not done yet\n         (values nil nil)))))\n\n\n;;;; The four different actual traversals:\n;; * TRAVERSE-ACTION o c T: Ensure all dependencies are either up-to-date in-image, or planned\n;; * TRAVERSE-ACTION o c NIL: Ensure all dependencies are up-to-date or planned, in-image or not\n;; * ACTION-UP-TO-DATE-P: Check whether some (defsystem-depends-on ?) dependencies are up to date\n;; * COLLECT-ACTION-DEPENDENCIES: Get the dependencies (filtered), don't change any status\n(with-upgradability ()\n\n  ;; Compute the action status for a newly visited action.\n  (defun compute-action-status (plan operation component need-p)\n    (multiple-value-bind (stamp done-p)\n        (compute-action-stamp plan operation component)\n      (assert (or stamp (not done-p)))\n      (make-action-status\n       :bits (logior (if stamp #.+keep-bit+ 0)\n                     (if done-p #.+done-bit+ 0)\n                     (if need-p #.+need-bit+ 0))\n       :stamp stamp\n       :level (operate-level)\n       :index (incf (total-action-count *asdf-session*)))))\n\n  ;; TRAVERSE-ACTION, in the context of a given PLAN object that accumulates dependency data,\n  ;; visits the action defined by its OPERATION and COMPONENT arguments,\n  ;; and all its transitive dependencies (unless already visited),\n  ;; in the context of the action being (or not) NEEDED-IN-IMAGE-P,\n  ;; i.e. needs to be done in the current image vs merely have been done in a previous image.\n  ;;\n  ;; TRAVERSE-ACTION updates the VISITED-ACTIONS entries for the action and for all its\n  ;; transitive dependencies (that haven't been sufficiently visited so far).\n  ;; It does not return any usable value.\n  ;;\n  ;; Note that for an XCVB-like plan with one-image-per-file-outputting-action,\n  ;; the below method would be insufficient, since it assumes a single image\n  ;; to traverse each node at most twice; non-niip actions would be traversed only once,\n  ;; but niip nodes could be traversed once per image, i.e. once plus once per non-niip action.\n\n  (defun traverse-action (plan operation component needed-in-image-p)\n    (block nil\n      (unless (action-valid-p operation component) (return))\n      ;; Record the dependency. This hook is needed by POIU, which tracks a full dependency graph,\n      ;; instead of just a dependency order as in vanilla ASDF.\n      ;; TODO: It is also needed to detect OPERATE-in-PERFORM.\n      (record-dependency plan operation component)\n      (while-visiting-action (operation component) ; maintain context, handle circularity.\n        ;; needed-in-image distinguishes b/w things that must happen in the\n        ;; current image and those things that simply need to have been done in a previous one.\n        (let* ((aniip (needed-in-image-p operation component)) ; action-specific needed-in-image\n               ;; effective niip: meaningful for the action and required by the plan as traversed\n               (eniip (and aniip needed-in-image-p))\n               ;; status: have we traversed that action previously, and if so what was its status?\n               (status (action-status plan operation component))\n               (level (operate-level)))\n          (when (and status\n                     (or (status-done-p status) ;; all done\n                         (and (status-need-p status) (<= level (status-level status))) ;; already visited\n                         (and (status-keep-p status) (not eniip)))) ;; up-to-date and not eniip\n            (return)) ; Already visited with sufficient need-in-image level!\n          (labels ((visit-action (niip) ; We may visit the action twice, once with niip NIL, then T\n                     (map-direct-dependencies ; recursively traverse dependencies\n                      operation component #'(lambda (o c) (traverse-action plan o c niip)))\n                     ;; AFTER dependencies have been traversed, compute action stamp\n                     (let* ((status (if status\n                                        (mark-status-needed status level)\n                                        (compute-action-status plan operation component t)))\n                            (out-of-date-p (not (status-keep-p status)))\n                            (to-perform-p (or out-of-date-p (and niip (not (status-done-p status))))))\n                       (cond ; it needs be done if it's out of date or needed in image but absent\n                         ((and out-of-date-p (not niip)) ; if we need to do it,\n                          (visit-action t)) ; then we need to do it *in the (current) image*!\n                         (t\n                          (setf (action-status plan operation component) status)\n                          (when (status-done-p status)\n                            (setf (component-operation-time operation component)\n                                  (status-stamp status)))\n                          (when to-perform-p ; if it needs to be added to the plan, count it\n                            (incf (planned-action-count *asdf-session*))\n                            (unless aniip ; if it's output-producing, count it\n                              (incf (planned-output-action-count *asdf-session*)))))))))\n            (visit-action eniip)))))) ; visit the action\n\n  ;; NB: This is not an error, not a warning, but a normal expected condition,\n  ;; to be to signaled by FIND-SYSTEM when it detects an out-of-date system,\n  ;; *before* it tries to replace it with a new definition.\n  (define-condition system-out-of-date (condition)\n    ((name :initarg :name :reader component-name))\n    (:documentation \"condition signaled when a system is detected as being out of date\")\n    (:report (lambda (c s)\n               (format s \"system ~A is out of date\" (component-name c)))))\n\n  (defun action-up-to-date-p (plan operation component)\n    \"Check whether an action was up-to-date at the beginning of the session.\nUpdate the VISITED-ACTIONS table with the known status, but don't add anything to the PLAN.\"\n    (block nil\n      (unless (action-valid-p operation component) (return t))\n      (while-visiting-action (operation component) ; maintain context, handle circularity.\n        ;; Do NOT record the dependency: it might be out of date.\n        (let ((status (or (action-status plan operation component)\n                          (setf (action-status plan operation component)\n                                (let ((dependencies-up-to-date-p\n                                       (handler-case\n                                           (block nil\n                                             (map-direct-dependencies\n                                              operation component\n                                              #'(lambda (o c)\n                                                  (unless (action-up-to-date-p plan o c)\n                                                    (return nil))))\n                                             t)\n                                         (system-out-of-date () nil))))\n                                  (if dependencies-up-to-date-p\n                                      (compute-action-status plan operation component nil)\n                                      +status-void+))))))\n          (and (status-keep-p status) (status-stamp status)))))))\n\n\n;;;; Incidental traversals\n\n;;; Making a FILTERED-SEQUENTIAL-PLAN can be used to, e.g., all of the source\n;;; files required by a bundling operation.\n(with-upgradability ()\n  (defclass filtered-sequential-plan (sequential-plan)\n    ((component-type :initform t :initarg :component-type :reader plan-component-type)\n     (keep-operation :initform t :initarg :keep-operation :reader plan-keep-operation)\n     (keep-component :initform t :initarg :keep-component :reader plan-keep-component))\n    (:documentation \"A variant of SEQUENTIAL-PLAN that only records a subset of actions.\"))\n\n  (defmethod initialize-instance :after ((plan filtered-sequential-plan)\n                                         &key system other-systems)\n    ;; Ignore force and force-not, rely on other-systems:\n    ;; force traversal of what we're interested in, i.e. current system or also others;\n    ;; force-not traversal of what we're not interested in, i.e. other systems unless other-systems.\n    (setf (slot-value plan 'forcing)\n          (make-forcing :system system :force :all :force-not (if other-systems nil t))))\n\n  (defmethod plan-actions ((plan filtered-sequential-plan))\n    (with-slots (keep-operation keep-component) plan\n      (loop :for action :in (call-next-method)\n        :as o = (action-operation action)\n        :as c = (action-component action)\n        :when (and (typep o keep-operation) (typep c keep-component))\n        :collect (make-action o c))))\n\n  (defun collect-action-dependencies (plan operation component)\n    (when (action-valid-p operation component)\n      (while-visiting-action (operation component) ; maintain context, handle circularity.\n        (let ((action (make-action operation component)))\n          (unless (nth-value 1 (gethash action (visited-actions *asdf-session*)))\n            (setf (gethash action (visited-actions *asdf-session*)) nil)\n            (when (and (typep component (plan-component-type plan))\n                       (not (action-forced-not-p (forcing plan) operation component)))\n              (map-direct-dependencies operation component\n                                       #'(lambda (o c) (collect-action-dependencies plan o c)))\n              (push action (plan-actions-r plan))))))))\n\n  (defgeneric collect-dependencies (operation component &key &allow-other-keys)\n    (:documentation \"Given an action, build a plan for all of its dependencies.\"))\n  (define-convenience-action-methods collect-dependencies (operation component &key))\n  (defmethod collect-dependencies ((operation operation) (component component)\n                                   &rest keys &key &allow-other-keys)\n    (let ((plan (apply 'make-instance 'filtered-sequential-plan\n                       :system (component-system component) keys)))\n      (loop :for action :in (direct-dependencies operation component)\n        :do (collect-action-dependencies plan (action-operation action) (action-component action)))\n      (plan-actions plan)))\n\n  (defun required-components (system &rest keys &key (goal-operation 'load-op) &allow-other-keys)\n    \"Given a SYSTEM and a GOAL-OPERATION (default LOAD-OP), traverse the dependencies and\nreturn a list of the components involved in building the desired action.\"\n    (with-asdf-session (:override t)\n      (remove-duplicates\n       (mapcar 'action-component\n               (apply 'collect-dependencies goal-operation system\n                      (remove-plist-key :goal-operation keys)))\n       :from-end t))))\n\n\n;;;; High-level interface: make-plan, perform-plan\n(with-upgradability ()\n  (defgeneric make-plan (plan-class operation component &key &allow-other-keys)\n    (:documentation \"Generate and return a plan for performing OPERATION on COMPONENT.\"))\n  (define-convenience-action-methods make-plan (plan-class operation component &key))\n\n  (defgeneric mark-as-done (plan-class operation component)\n    (:documentation \"Mark an action as done in a plan, after performing it.\"))\n  (define-convenience-action-methods mark-as-done (plan-class operation component))\n\n  (defgeneric perform-plan (plan &key)\n    (:documentation \"Actually perform a plan and build the requested actions\"))\n\n  (defparameter* *plan-class* 'sequential-plan\n    \"The default plan class to use when building with ASDF\")\n\n  (defmethod make-plan (plan-class (o operation) (c component) &rest keys &key &allow-other-keys)\n    (with-asdf-session ()\n      (let ((plan (apply 'make-instance (or plan-class *plan-class*) keys)))\n        (traverse-action plan o c t)\n        plan)))\n\n  (defmethod perform-plan :around ((plan t) &key)\n    (assert (performable-p (forcing plan)) () \"plan not performable\")\n    (let ((*package* *package*)\n          (*readtable* *readtable*))\n      (with-compilation-unit () ;; backward-compatibility.\n        (call-next-method))))   ;; Going forward, see deferred-warning support in lisp-build.\n\n  (defun action-already-done-p (plan operation component)\n    (if-let (status (action-status plan operation component))\n      (status-done-p status)))\n\n  (defmethod perform-plan ((plan t) &key)\n    (loop :for action :in (plan-actions plan)\n      :as o = (action-operation action)\n      :as c = (action-component action) :do\n      (unless (action-already-done-p plan o c)\n        (perform-with-restarts o c)\n        (mark-as-done plan o c))))\n\n  (defmethod mark-as-done ((plan plan) (o operation) (c component))\n    (let ((plan-status (action-status plan o c))\n          (perform-status (action-status nil o c)))\n      (assert (and (status-stamp perform-status) (status-keep-p perform-status)) ()\n              \"Just performed ~A but failed to mark it done\" (action-description o c))\n      (setf (action-status plan o c)\n            (make-action-status\n             :bits (logior (status-bits plan-status) +done-bit+)\n             :stamp (status-stamp perform-status)\n             :level (status-level plan-status)\n             :index (status-index plan-status))))))\n;;;; -------------------------------------------------------------------------\n;;;; Invoking Operations\n\n(uiop/package:define-package :asdf/operate\n  (:recycle :asdf/operate :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n        :asdf/component :asdf/system :asdf/system-registry :asdf/find-component\n        :asdf/operation :asdf/action :asdf/lisp-action :asdf/forcing :asdf/plan)\n  (:export\n   #:operate #:oos #:build-op #:make\n   #:load-system #:load-systems #:load-systems*\n   #:compile-system #:test-system #:require-system #:module-provide-asdf\n   #:component-loaded-p #:already-loaded-systems\n   #:recursive-operate))\n(in-package :asdf/operate)\n\n(with-upgradability ()\n  (defgeneric operate (operation component &key)\n    (:documentation\n     \"Operate does mainly four things for the user:\n\n1. Resolves the OPERATION designator into an operation object.\n   OPERATION is typically a symbol denoting an operation class, instantiated with MAKE-OPERATION.\n2. Resolves the COMPONENT designator into a component object.\n   COMPONENT is typically a string or symbol naming a system, loaded from disk using FIND-SYSTEM.\n3. It then calls MAKE-PLAN with the operation and system as arguments.\n4. Finally calls PERFORM-PLAN on the resulting plan to actually build the system.\n\nThe entire computation is wrapped in WITH-COMPILATION-UNIT and error handling code.\nIf a VERSION argument is supplied, then operate also ensures that the system found satisfies it\nusing the VERSION-SATISFIES method.\nIf a PLAN-CLASS argument is supplied, that class is used for the plan.\nIf a PLAN-OPTIONS argument is supplied, the options are passed to the plan.\n\nThe :FORCE or :FORCE-NOT argument to OPERATE can be:\n  T to force the inside of the specified system to be rebuilt (resp. not),\n    without recursively forcing the other systems we depend on.\n  :ALL to force all systems including other systems we depend on to be rebuilt (resp. not).\n  (SYSTEM1 SYSTEM2 ... SYSTEMN) to force systems named in a given list\n:FORCE-NOT has precedence over :FORCE; builtin systems cannot be forced.\n\nFor backward compatibility, all keyword arguments are passed to MAKE-OPERATION\nwhen instantiating a new operation, that will in turn be inherited by new operations.\nBut do NOT depend on it, for this is deprecated behavior.\"))\n\n  (define-convenience-action-methods operate (operation component &key)\n    :if-no-component (error 'missing-component :requires component))\n\n  ;; This method ensures that an ASDF upgrade is attempted as the very first thing,\n  ;; with suitable state preservation in case in case it actually happens,\n  ;; and that a few suitable dynamic bindings are established.\n  (defmethod operate :around (operation component &rest keys\n                              &key verbose\n                                (on-warnings *compile-file-warnings-behaviour*)\n                                (on-failure *compile-file-failure-behaviour*))\n    (nest\n     (with-asdf-session ())\n     (let* ((operation-remaker ;; how to remake the operation after ASDF was upgraded (if it was)\n             (etypecase operation\n               (operation (let ((name (type-of operation)))\n                            #'(lambda () (make-operation name))))\n               ((or symbol string) (constantly operation))))\n            (component-path (typecase component ;; to remake the component after ASDF upgrade\n                              (component (component-find-path component))\n                              (t component)))\n            (system-name (labels ((first-name (x)\n                                    (etypecase x\n                                      ((or string symbol) x) ; NB: includes the NIL case.\n                                      (cons (or (first-name (car x)) (first-name (cdr x)))))))\n                           (coerce-name (first-name component-path)))))\n       (apply 'make-forcing :performable-p t :system system-name keys)\n       ;; Before we operate on any system, make sure ASDF is up-to-date,\n       ;; for if an upgrade is ever attempted at any later time, there may be BIG trouble.\n       (unless (asdf-upgraded-p (toplevel-asdf-session))\n         (setf (asdf-upgraded-p (toplevel-asdf-session)) t)\n         (when (upgrade-asdf)\n           ;; If we were upgraded, restart OPERATE the hardest of ways, for\n           ;; its function may have been redefined.\n           (return-from operate\n             (with-asdf-session (:override t :override-cache t)\n               (apply 'operate (funcall operation-remaker) component-path keys))))))\n      ;; Setup proper bindings around any operate call.\n     (let* ((*verbose-out* (and verbose *standard-output*))\n            (*compile-file-warnings-behaviour* on-warnings)\n            (*compile-file-failure-behaviour* on-failure)))\n     (unwind-protect\n          (progn\n            (incf (operate-level))\n            (call-next-method))\n       (decf (operate-level)))))\n\n  (defmethod operate :before ((operation operation) (component component)\n                              &key version)\n    (unless (version-satisfies component version)\n      (error 'missing-component-of-version :requires component :version version))\n    (record-dependency nil operation component))\n\n  (defmethod operate ((operation operation) (component component)\n                      &key plan-class plan-options)\n    (let ((plan (apply 'make-plan plan-class operation component\n                       :forcing (forcing *asdf-session*) plan-options)))\n      (perform-plan plan)\n      (values operation plan)))\n\n  (defun oos (operation component &rest args &key &allow-other-keys)\n    (apply 'operate operation component args))\n\n  (setf (documentation 'oos 'function)\n        (format nil \"Short for _operate on system_ and an alias for the OPERATE function.~%~%~a\"\n                (documentation 'operate 'function)))\n\n  (define-condition recursive-operate (warning)\n    ((operation :initarg :operation :reader condition-operation)\n     (component :initarg :component :reader condition-component)\n     (action :initarg :action :reader condition-action))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Deprecated recursive use of (~S '~S '~S) while visiting ~S ~\n- please use proper dependencies instead~@:>\")\n                       'operate\n                       (type-of (condition-operation c))\n                       (component-find-path (condition-component c))\n                       (action-path (condition-action c)))))))\n\n;;;; Common operations\n(when-upgrading ()\n  (defmethod component-depends-on ((o prepare-op) (s system))\n    (call-next-method)))\n(with-upgradability ()\n  (defclass build-op (non-propagating-operation) ()\n    (:documentation \"Since ASDF3, BUILD-OP is the recommended 'master' operation,\nto operate by default on a system or component, via the function BUILD.\nIts meaning is configurable via the :BUILD-OPERATION option of a component.\nwhich typically specifies the name of a specific operation to which to delegate the build,\nas a symbol or as a string later read as a symbol (after loading the defsystem-depends-on);\nif NIL is specified (the default), BUILD-OP falls back to LOAD-OP,\nthat will load the system in the current image.\"))\n  (defmethod component-depends-on ((o build-op) (c component))\n    `((,(or (component-build-operation c) 'load-op) ,c)\n      ,@(call-next-method)))\n\n  (defun make (system &rest keys)\n    \"The recommended way to interact with ASDF3.1 is via (ASDF:MAKE :FOO).\nIt will build system FOO using the operation BUILD-OP,\nthe meaning of which is configurable by the system, and\ndefaults to LOAD-OP, to load it in current image.\"\n    (apply 'operate 'build-op system keys)\n    t)\n\n  (defun load-system (system &rest keys &key force force-not verbose version &allow-other-keys)\n    \"Shorthand for `(operate 'asdf:load-op system)`. See OPERATE for details.\"\n    (declare (ignore force force-not verbose version))\n    (apply 'operate 'load-op system keys)\n    t)\n\n  (defun load-systems* (systems &rest keys)\n    \"Loading multiple systems at once.\"\n    (dolist (s systems) (apply 'load-system s keys)))\n\n  (defun load-systems (&rest systems)\n    \"Loading multiple systems at once.\"\n    (load-systems* systems))\n\n  (defun compile-system (system &rest args &key force force-not verbose version &allow-other-keys)\n    \"Shorthand for `(asdf:operate 'asdf:compile-op system)`. See OPERATE for details.\"\n    (declare (ignore force force-not verbose version))\n    (apply 'operate 'compile-op system args)\n    t)\n\n  (defun test-system (system &rest args &key force force-not verbose version &allow-other-keys)\n    \"Shorthand for `(asdf:operate 'asdf:test-op system)`. See OPERATE for details.\"\n    (declare (ignore force force-not verbose version))\n    (apply 'operate 'test-op system args)\n    t))\n\n;;;;; Define the function REQUIRE-SYSTEM, that, similarly to REQUIRE,\n;; only tries to load its specified target if it's not loaded yet.\n(with-upgradability ()\n  (defun component-loaded-p (component)\n    \"Has the given COMPONENT been successfully loaded in the current image (yet)?\nNote that this returns true even if the component is not up to date.\"\n    (if-let ((component (find-component component () :registered t)))\n      (nth-value 1 (component-operation-time (make-operation 'load-op) component))))\n\n  (defun already-loaded-systems ()\n    \"return a list of the names of the systems that have been successfully loaded so far\"\n    (mapcar 'coerce-name (remove-if-not 'component-loaded-p (registered-systems*)))))\n\n\n;;;; Define the class REQUIRE-SYSTEM, to be hooked into CL:REQUIRE when possible,\n;; i.e. for ABCL, CLISP, ClozureCL, CMUCL, ECL, MKCL and SBCL\n;; Note that despite the two being homonyms, the _function_ require-system\n;; and the _class_ require-system are quite distinct entities, fulfilling independent purposes.\n(with-upgradability ()\n  (defvar *modules-being-required* nil)\n\n  (defclass require-system (system)\n    ((module :initarg :module :initform nil :accessor required-module))\n    (:documentation \"A SYSTEM subclass whose processing is handled by\nthe implementation's REQUIRE rather than by internal ASDF mechanisms.\"))\n\n  (defmethod perform ((o compile-op) (c require-system))\n    nil)\n\n  (defmethod perform ((o load-op) (s require-system))\n    (let* ((module (or (required-module s) (coerce-name s)))\n           (*modules-being-required* (cons module *modules-being-required*)))\n      (assert (null (component-children s)))\n      (require module)))\n\n  (defmethod resolve-dependency-combination (component (combinator (eql :require)) arguments)\n    (unless (and (length=n-p arguments 1)\n                 (typep (car arguments) '(or string (and symbol (not null)))))\n      (parameter-error (compatfmt \"~@<In ~S, bad dependency ~S for ~S. ~S takes one argument, a string or non-null symbol~@:>\")\n                       'resolve-dependency-combination\n                       (cons combinator arguments) component combinator))\n    ;; :require must be prepared for some implementations providing modules using ASDF,\n    ;; as SBCL used to do, and others may might do. Thus, the system provided in the end\n    ;; would be a downcased name as per module-provide-asdf above. For the same reason,\n    ;; we cannot assume that the system in the end will be of type require-system,\n    ;; but must check whether we can use find-system and short-circuit cl:require.\n    ;; Otherwise, calling cl:require could result in nasty reentrant calls between\n    ;; cl:require and asdf:operate that could potentially blow up the stack,\n    ;; all the while defeating the consistency of the dependency graph.\n    (let* ((module (car arguments)) ;; NB: we already checked that it was not null\n           ;; CMUCL, MKCL, SBCL like their module names to be all upcase.\n           (module-name (string module))\n           (system-name (string-downcase module))\n           (system (find-system system-name nil)))\n      (or system (let ((system (make-instance 'require-system :name system-name :module module-name)))\n                   (register-system system)\n                   system))))\n\n  (defun module-provide-asdf (name)\n    ;; We must use string-downcase, because modules are traditionally specified as symbols,\n    ;; that implementations traditionally normalize as uppercase, for which we seek a system\n    ;; with a name that is traditionally in lowercase. Case is lost along the way. That's fine.\n    ;; We could make complex, non-portable rules to try to preserve case, and just documenting\n    ;; them would be a hell that it would be a disservice to inflict on users.\n    (let ((module-name (string name))\n          (system-name (string-downcase name)))\n      (unless (member module-name *modules-being-required* :test 'equal)\n        (let ((*modules-being-required* (cons module-name *modules-being-required*))\n              #+sbcl (sb-impl::*requiring* (remove module-name sb-impl::*requiring* :test 'equal)))\n          (handler-bind\n              (((or style-warning recursive-operate) #'muffle-warning)\n               (missing-component (constantly nil))\n               (fatal-condition\n                #'(lambda (e)\n                    (format *error-output* (compatfmt \"~@<ASDF could not load ~(~A~) because ~A.~@:>~%\")\n                            name e))))\n            (let ((*verbose-out* (make-broadcast-stream)))\n              (let ((system (find-system system-name nil)))\n                (when system\n                  ;; Do not use require-system after all, use load-system:\n                  ;; on the one hand, REQUIRE already uses *MODULES* not to load something twice,\n                  ;; on the other hand, REQUIRE-SYSTEM uses FORCE-NOT which may conflict with\n                  ;; the toplevel session forcing settings.\n                  (load-system system :verbose nil)\n                  t)))))))))\n\n\n;;;; Some upgrade magic\n(with-upgradability ()\n  (defun restart-upgraded-asdf ()\n    ;; If we're in the middle of something, restart it.\n    (let ((systems-being-defined\n           (when *asdf-session*\n             (prog1\n                 (loop :for k :being :the hash-keys :of (asdf-cache)\n                   :when (eq (first k) 'find-system) :collect (second k))\n               (clrhash (asdf-cache))))))\n      ;; Regardless, clear defined systems, since they might be invalid\n      ;; after an incompatible ASDF upgrade.\n      (clear-registered-systems)\n      ;; The configuration also may have to be upgraded.\n      (upgrade-configuration)\n      ;; If we were in the middle of an operation, be sure to restore the system being defined.\n      (dolist (s systems-being-defined) (find-system s nil))))\n  (register-hook-function '*post-upgrade-cleanup-hook* 'restart-upgraded-asdf))\n;;;; -------------------------------------------------------------------------\n;;;; Finding systems\n\n(uiop/package:define-package :asdf/find-system\n  (:recycle :asdf/find-system :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n        :asdf/session :asdf/component :asdf/system :asdf/operation :asdf/action :asdf/lisp-action\n        :asdf/find-component :asdf/system-registry :asdf/plan :asdf/operate)\n  (:import-from #:asdf/component #:%additional-input-files)\n  (:export\n   #:find-system #:locate-system #:load-asd #:define-op\n   #:load-system-definition-error #:error-name #:error-pathname #:error-condition))\n(in-package :asdf/find-system)\n\n(with-upgradability ()\n  (define-condition load-system-definition-error (system-definition-error)\n    ((name :initarg :name :reader error-name)\n     (pathname :initarg :pathname :reader error-pathname)\n     (condition :initarg :condition :reader error-condition))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while trying to load definition for system ~A from pathname ~A: ~3i~_~A~@:>\")\n                       (error-name c) (error-pathname c) (error-condition c)))))\n\n\n  ;;; Methods for find-system\n\n  ;; Reject NIL as a system designator.\n  (defmethod find-system ((name null) &optional (error-p t))\n    (when error-p\n      (sysdef-error (compatfmt \"~@<NIL is not a valid system name~@:>\"))))\n\n  ;; Default method for find-system: resolve the argument using COERCE-NAME.\n  (defmethod find-system (name &optional (error-p t))\n    (find-system (coerce-name name) error-p))\n\n  (defun find-system-if-being-defined (name)\n    ;; This function finds systems being defined *in the current ASDF session*, as embodied by\n    ;; its session cache, even before they are fully defined and registered in *registered-systems*.\n    ;; The purpose of this function is to prevent races between two files that might otherwise\n    ;; try overwrite each other's system objects, resulting in infinite loops and stack overflow.\n    ;; This function explicitly MUST NOT find definitions merely registered in previous sessions.\n    ;; NB: this function depends on a corresponding side-effect in parse-defsystem;\n    ;; the precise protocol between the two functions may change in the future (or not).\n    (first (gethash `(find-system ,(coerce-name name)) (asdf-cache))))\n\n  (defclass define-op (non-propagating-operation) ()\n    (:documentation \"An operation to record dependencies on loading a .asd file.\"))\n\n  (defmethod record-dependency ((plan null) (operation t) (component t))\n    (unless (or (typep operation 'define-op)\n                (and (typep operation 'load-op)\n                     (typep component 'system)\n                     (equal \"asdf\" (coerce-name component))))\n      (if-let ((action (first (visiting-action-list *asdf-session*))))\n        (let ((parent-operation (action-operation action))\n              (parent-component (action-component action)))\n          (cond\n            ((and (typep parent-operation 'define-op)\n                  (typep parent-component 'system))\n             (let ((action (cons operation component)))\n               (unless (gethash action (definition-dependency-set parent-component))\n                 (push (cons operation component) (definition-dependency-list parent-component))\n                 (setf (gethash action (definition-dependency-set parent-component)) t))))\n            (t\n             (warn 'recursive-operate\n                   :operation operation :component component :action action)))))))\n\n  (defmethod component-depends-on ((o define-op) (s system))\n    `(;;NB: 1- ,@(system-defsystem-depends-on s)) ; Should be already included in the below.\n      ;; 2- We don't call-next-method to avoid other methods\n      ,@(loop :for (o . c) :in (definition-dependency-list s) :collect (list o c))))\n\n  (defmethod component-depends-on ((o operation) (s system))\n    `(,@(when (and (not (typep o 'define-op))\n                   (or (system-source-file s) (definition-dependency-list s)))\n              `((define-op ,(primary-system-name s))))\n      ,@(call-next-method)))\n\n  (defmethod perform ((o operation) (c undefined-system))\n    (sysdef-error \"Trying to use undefined or incompletely defined system ~A\" (coerce-name c)))\n\n  ;; TODO: could this file be refactored so that locate-system is merely\n  ;; the cache-priming call to input-files here?\n  (defmethod input-files ((o define-op) (s system))\n    (if-let ((asd (system-source-file s))) (list asd)))\n\n  (defmethod perform ((o define-op) (s system))\n    (nest\n     (if-let ((pathname (first (input-files o s)))))\n     (let ((readtable *readtable*) ;; save outer syntax tables. TODO: proper syntax-control\n           (print-pprint-dispatch *print-pprint-dispatch*)))\n     (with-standard-io-syntax)\n     (let ((*print-readably* nil)\n           ;; Note that our backward-compatible *readtable* is\n           ;; a global readtable that gets globally side-effected. Ouch.\n           ;; Same for the *print-pprint-dispatch* table.\n           ;; We should do something about that for ASDF3 if possible, or else ASDF4.\n           (*readtable* readtable) ;; restore inside syntax table\n           (*print-pprint-dispatch* print-pprint-dispatch)\n           (*package* (find-package :asdf-user))\n           (*default-pathname-defaults*\n            ;; resolve logical-pathnames so they won't wreak havoc in parsing namestrings.\n            (pathname-directory-pathname (physicalize-pathname pathname)))))\n     (handler-bind\n         (((and error (not missing-component))\n           #'(lambda (condition)\n               (error 'load-system-definition-error\n                      :name (coerce-name s) :pathname pathname :condition condition))))\n       (asdf-message (compatfmt \"~&~@<; ~@;Loading system definition~@[ for ~A~] from ~A~@:>~%\")\n                     (coerce-name s) pathname)\n       ;; dependencies will depend on what's loaded via definition-dependency-list\n       (unset-asdf-cache-entry `(component-depends-on ,o ,s))\n       (unset-asdf-cache-entry `(input-files ,o ,s)))\n     (load* pathname :external-format (encoding-external-format (detect-encoding pathname)))))\n\n  (defun load-asd (pathname &key name)\n    \"Load system definitions from PATHNAME.\nNAME if supplied is the name of a system expected to be defined in that file.\n\nDo NOT try to load a .asd file directly with CL:LOAD. Always use ASDF:LOAD-ASD.\"\n    (with-asdf-session ()\n      ;; TODO: use OPERATE, so we consult the cache and only load once per session.\n      (flet ((do-it (o c) (operate o c)))\n        (let ((primary-name (primary-system-name (or name (pathname-name pathname))))\n              (operation (make-operation 'define-op)))\n          (if-let (system (registered-system primary-name))\n            (progn\n              ;; We already determine this to be obsolete ---\n              ;; or should we move some tests from find-system to check for up-to-date-ness here?\n              (setf (component-operation-time operation system) t\n                    (definition-dependency-list system) nil\n                    (definition-dependency-set system) (list-to-hash-set nil))\n              (do-it operation system))\n            (let ((system (make-instance 'undefined-system\n                                         :name primary-name :source-file pathname)))\n              (register-system system)\n              (unwind-protect (do-it operation system)\n                (when (typep system 'undefined-system)\n                  (clear-system system)))))))))\n\n  (defvar *old-asdf-systems* (make-hash-table :test 'equal))\n\n  ;; (Private) function to check that a system that was found isn't an asdf downgrade.\n  ;; Returns T if everything went right, NIL if the system was an ASDF at an older version,\n  ;; or UIOP of the same or older version, that shall not be loaded.\n  ;; Also issue a warning if it was a strictly older version of ASDF.\n  (defun check-not-old-asdf-system (name pathname)\n    (or (not (member name '(\"asdf\" \"uiop\") :test 'equal))\n        (null pathname)\n        (let* ((asdfp (equal name \"asdf\")) ;; otherwise, it's uiop\n               (version-pathname\n                (subpathname pathname \"version\" :type (if asdfp \"lisp-expr\" \"lisp\")))\n               (version (and (probe-file* version-pathname :truename nil)\n                             (read-file-form version-pathname :at (if asdfp '(0) '(2 2 2)))))\n               (old-version (asdf-version)))\n          (cond\n            ;; Same version is OK for ASDF, to allow loading from modified source.\n            ;; However, do *not* load UIOP of the exact same version:\n            ;; it was already loaded it as part of ASDF and would only be double-loading.\n            ;; Be quiet about it, though, since it's a normal situation.\n            ((equal old-version version) asdfp)\n            ((version< old-version version) t) ;; newer version: Good!\n            (t ;; old version: bad\n             (ensure-gethash\n              (list (namestring pathname) version) *old-asdf-systems*\n              #'(lambda ()\n                  (let ((old-pathname (system-source-file (registered-system \"asdf\"))))\n                    (if asdfp\n                        (warn \"~@<~\n        You are using ASDF version ~A ~:[(probably from (require \\\"asdf\\\") ~\n        or loaded by quicklisp)~;from ~:*~S~] and have an older version of ASDF ~\n        ~:[(and older than 2.27 at that)~;~:*~A~] registered at ~S. ~\n        Having an ASDF installed and registered is the normal way of configuring ASDF to upgrade itself, ~\n        and having an old version registered is a configuration error. ~\n        ASDF will ignore this configured system rather than downgrade itself. ~\n        In the future, you may want to either: ~\n        (a) upgrade this configured ASDF to a newer version, ~\n        (b) install a newer ASDF and register it in front of the former in your configuration, or ~\n        (c) uninstall or unregister this and any other old version of ASDF from your configuration. ~\n        Note that the older ASDF might be registered implicitly through configuration inherited ~\n        from your system installation, in which case you might have to specify ~\n        :ignore-inherited-configuration in your in your ~~/.config/common-lisp/source-registry.conf ~\n        or other source-registry configuration file, environment variable or lisp parameter. ~\n        Indeed, a likely offender is an obsolete version of the cl-asdf debian or ubuntu package, ~\n        that you might want to upgrade (if a recent enough version is available) ~\n        or else remove altogether (since most implementations ship with a recent asdf); ~\n        if you lack the system administration rights to upgrade or remove this package, ~\n        then you might indeed want to either install and register a more recent version, ~\n        or use :ignore-inherited-configuration to avoid registering the old one. ~\n        Please consult ASDF documentation and/or experts.~@:>~%\"\n                              old-version old-pathname version pathname)\n                        ;; NB: for UIOP, don't warn, just ignore.\n                        (warn \"ASDF ~A (from ~A), UIOP ~A (from ~A)\"\n                              old-version old-pathname version pathname)\n                        ))))\n             nil))))) ;; only issue the warning the first time, but always return nil\n\n  (defun locate-system (name)\n    \"Given a system NAME designator, try to locate where to load the system from.\nReturns six values: FOUNDP FOUND-SYSTEM PATHNAME PREVIOUS PREVIOUS-TIME PREVIOUS-PRIMARY\nFOUNDP is true when a system was found,\neither a new unregistered one or a previously registered one.\nFOUND-SYSTEM when not null is a SYSTEM object that may be REGISTER-SYSTEM'ed.\nPATHNAME when not null is a path from which to load the system,\neither associated with FOUND-SYSTEM, or with the PREVIOUS system.\nPREVIOUS when not null is a previously loaded SYSTEM object of same name.\nPREVIOUS-TIME when not null is the time at which the PREVIOUS system was loaded.\nPREVIOUS-PRIMARY when not null is the primary system for the PREVIOUS system.\"\n    (with-asdf-session () ;; NB: We don't cache the results. We once used to, but it wasn't useful,\n      ;; and keeping a negative cache was a bug (see lp#1335323), which required\n      ;; explicit invalidation in clear-system and find-system (when unsucccessful).\n      (let* ((name (coerce-name name))\n             (previous (registered-system name)) ; load from disk if absent or newer on disk\n             (previous-primary-name (and previous (primary-system-name previous)))\n             (previous-primary-system (and previous-primary-name\n                                           (registered-system previous-primary-name)))\n             (previous-time (and previous-primary-system\n                                 (component-operation-time 'define-op previous-primary-system)))\n             (found (search-for-system-definition name))\n             (found-system (and (typep found 'system) found))\n             (pathname (ensure-pathname\n                        (or (and (typep found '(or pathname string)) (pathname found))\n                            (system-source-file found-system)\n                            (system-source-file previous))\n                        :want-absolute t :resolve-symlinks *resolve-symlinks*))\n             (foundp (and (or found-system pathname previous) t)))\n        (check-type found (or null pathname system))\n        (unless (check-not-old-asdf-system name pathname)\n          (check-type previous system) ;; asdf is preloaded, so there should be a previous one.\n          (setf found-system nil pathname nil))\n        (values foundp found-system pathname previous previous-time previous-primary-system))))\n\n  ;; TODO: make a prepare-define-op node for this\n  ;; so we can properly cache the answer rather than recompute it.\n  (defun definition-dependencies-up-to-date-p (system)\n    (check-type system system)\n    (or (not (primary-system-p system))\n        (handler-case\n            (loop :with plan = (make-instance *plan-class*)\n              :for action :in (definition-dependency-list system)\n              :always (action-up-to-date-p\n                       plan (action-operation action) (action-component action))\n              :finally\n              (let ((o (make-operation 'define-op)))\n                (multiple-value-bind (stamp done-p)\n                    (compute-action-stamp plan o system)\n                  (return (and (timestamp<= stamp (component-operation-time o system))\n                               done-p)))))\n          (system-out-of-date () nil))))\n\n  ;; Main method for find-system: first, make sure the computation is memoized in a session cache.\n  ;; Unless the system is immutable, use locate-system to find the primary system;\n  ;; reconcile the finding (if any) with any previous definition (in a previous session,\n  ;; preloaded, with a previous configuration, or before filesystem changes), and\n  ;; load a found .asd if appropriate. Finally, update registration table and return results.\n  (defmethod find-system ((name string) &optional (error-p t))\n    (nest\n     (with-asdf-session (:key `(find-system ,name)))\n     (let ((name-primary-p (primary-system-p name)))\n       (unless name-primary-p (find-system (primary-system-name name) nil)))\n     (or (and *immutable-systems* (gethash name *immutable-systems*) (registered-system name)))\n     (multiple-value-bind (foundp found-system pathname previous previous-time previous-primary)\n         (locate-system name)\n       (assert (eq foundp (and (or found-system pathname previous) t))))\n     (let ((previous-pathname (system-source-file previous))\n           (system (or previous found-system)))\n       (when (and found-system (not previous))\n         (register-system found-system))\n       (when (and system pathname)\n         (setf (system-source-file system) pathname))\n       (if-let ((stamp (get-file-stamp pathname)))\n         (let ((up-to-date-p\n                (and previous previous-primary\n                     (or (pathname-equal pathname previous-pathname)\n                         (and pathname previous-pathname\n                              (pathname-equal\n                               (physicalize-pathname pathname)\n                               (physicalize-pathname previous-pathname))))\n                     (timestamp<= stamp previous-time)\n                     ;; Check that all previous definition-dependencies are up-to-date,\n                     ;; traversing them without triggering the adding of nodes to the plan.\n                     ;; TODO: actually have a prepare-define-op, extract its timestamp,\n                     ;; and check that it is less than the stamp of the previous define-op ?\n                     (definition-dependencies-up-to-date-p previous-primary))))\n           (unless up-to-date-p\n             (restart-case\n                 (signal 'system-out-of-date :name name)\n               (continue () :report \"continue\"))\n             (load-asd pathname :name name)))))\n     ;; Try again after having loaded from disk if needed\n     (or (registered-system name)\n         (when error-p (error 'missing-component :requires name)))))\n\n  ;; Resolved forward reference for asdf/system-registry.\n  (defun mark-component-preloaded (component)\n    \"Mark a component as preloaded.\"\n    (let ((component (find-component component nil :registered t)))\n      ;; Recurse to children, so asdf/plan will hopefully be happy.\n      (map () 'mark-component-preloaded (component-children component))\n      ;; Mark the timestamps of the common lisp-action operations as 0.\n      (let ((cot (component-operation-times component)))\n        (dolist (o `(,@(when (primary-system-p component) '(define-op))\n                       prepare-op compile-op load-op))\n          (setf (gethash (make-operation o) cot) 0))))))\n;;;; -------------------------------------------------------------------------\n;;;; Defsystem\n\n(uiop/package:define-package :asdf/parse-defsystem\n  (:recycle :asdf/parse-defsystem :asdf/defsystem :asdf)\n  (:nicknames :asdf/defsystem) ;; previous name, to be compatible with, in case anyone cares\n  (:use :uiop/common-lisp :asdf/driver :asdf/upgrade\n   :asdf/session :asdf/component :asdf/system :asdf/system-registry\n   :asdf/find-component :asdf/action :asdf/lisp-action :asdf/operate)\n  (:import-from :asdf/system #:depends-on #:weakly-depends-on)\n  ;; these needed for record-additional-system-input-file\n  (:import-from :asdf/operation #:make-operation)\n  (:import-from :asdf/component #:%additional-input-files)\n  (:import-from :asdf/find-system #:define-op)\n  (:export\n   #:defsystem #:register-system-definition\n   #:*default-component-class*\n   #:determine-system-directory #:parse-component-form\n   #:non-toplevel-system #:non-system-system #:bad-system-name\n   #:*known-systems-with-bad-secondary-system-names*\n   #:known-system-with-bad-secondary-system-names-p\n   #:sysdef-error-component #:check-component-input\n   #:explain\n   ;; for extending the component types\n   #:compute-component-children\n   #:class-for-type))\n(in-package :asdf/parse-defsystem)\n\n;;; Pathname\n(with-upgradability ()\n  (defun determine-system-directory (pathname)\n    ;; The defsystem macro calls this function to determine the pathname of a system as follows:\n    ;; 1. If the pathname argument is an pathname object (NOT a namestring),\n    ;;    that is already an absolute pathname, return it.\n    ;; 2. Otherwise, the directory containing the LOAD-PATHNAME\n    ;;    is considered (as deduced from e.g. *LOAD-PATHNAME*), and\n    ;;    if it is indeed available and an absolute pathname, then\n    ;;    the PATHNAME argument is normalized to a relative pathname\n    ;;    as per PARSE-UNIX-NAMESTRING (with ENSURE-DIRECTORY T)\n    ;;    and merged into that DIRECTORY as per SUBPATHNAME.\n    ;;    Note: avoid *COMPILE-FILE-PATHNAME* because the .asd is loaded as source,\n    ;;    but may be from within the EVAL-WHEN of a file compilation.\n    ;; If no absolute pathname was found, we return NIL.\n    (check-type pathname (or null string pathname))\n    (pathname-directory-pathname\n     (resolve-symlinks*\n      (ensure-absolute-pathname\n       (parse-unix-namestring pathname :type :directory)\n       #'(lambda () (ensure-absolute-pathname\n                     (load-pathname) 'get-pathname-defaults nil))\n       nil)))))\n\n\n(when-upgrading (:version \"3.3.4.17\")\n  ;; This turned into a generic function in 3.3.4.17\n  (fmakunbound 'class-for-type))\n\n;;; Component class\n(with-upgradability ()\n  ;; What :file gets interpreted as, unless overridden by a :default-component-class\n  (defvar *default-component-class* 'cl-source-file)\n\n  (defgeneric class-for-type (parent type-designator)\n    (:documentation\n     \"Return a CLASS object to be used to instantiate components specified by TYPE-DESIGNATOR in the context of PARENT.\"))\n\n  (defmethod class-for-type ((parent null) type)\n    \"If the PARENT is NIL, then TYPE must designate a subclass of SYSTEM.\"\n    (or (coerce-class type :package :asdf/interface :super 'system :error nil)\n        (sysdef-error \"don't recognize component type ~S in the context of no parent\" type)))\n\n  (defmethod class-for-type ((parent parent-component) type)\n    (or (coerce-class type :package :asdf/interface :super 'component :error nil)\n        (and (eq type :file)\n             (coerce-class\n              (or (loop :for p = parent :then (component-parent p) :while p\n                          :thereis (module-default-component-class p))\n                  *default-component-class*)\n              :package :asdf/interface :super 'component :error nil))\n        (sysdef-error \"don't recognize component type ~S\" type))))\n\n\n;;; Check inputs\n(with-upgradability ()\n  (define-condition non-system-system (system-definition-error)\n    ((name :initarg :name :reader non-system-system-name)\n     (class-name :initarg :class-name :reader non-system-system-class-name))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while defining system ~S: class ~S isn't a subclass of ~S~@:>\")\n                       (non-system-system-name c) (non-system-system-class-name c) 'system))))\n\n  (define-condition non-toplevel-system (system-definition-error)\n    ((parent :initarg :parent :reader non-toplevel-system-parent)\n     (name :initarg :name :reader non-toplevel-system-name))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Error while defining system: component ~S claims to have a system ~S as a child~@:>\")\n                       (non-toplevel-system-parent c) (non-toplevel-system-name c)))))\n\n  (define-condition bad-system-name (warning)\n    ((name :initarg :name :reader component-name)\n     (source-file :initarg :source-file :reader system-source-file))\n    (:report (lambda (c s)\n               (let* ((file (system-source-file c))\n                      (name (component-name c))\n                      (asd (pathname-name file)))\n                 (format s (compatfmt \"~@<System definition file ~S contains definition for system ~S. ~\nPlease only define ~S and secondary systems with a name starting with ~S (e.g. ~S) in that file.~@:>\")\n                       file name asd (strcat asd \"/\") (strcat asd \"/test\"))))))\n\n  (defun sysdef-error-component (msg type name value)\n    (sysdef-error (strcat msg (compatfmt \"~&~@<The value specified for ~(~A~) ~A is ~S~@:>\"))\n                  type name value))\n\n  (defun check-component-input (type name weakly-depends-on\n                                depends-on components)\n    \"A partial test of the values of a component.\"\n    (unless (listp depends-on)\n      (sysdef-error-component \":depends-on must be a list.\"\n                              type name depends-on))\n    (unless (listp weakly-depends-on)\n      (sysdef-error-component \":weakly-depends-on must be a list.\"\n                              type name weakly-depends-on))\n    (unless (listp components)\n      (sysdef-error-component \":components must be NIL or a list of components.\"\n                              type name components)))\n\n\n  (defun record-additional-system-input-file (pathname component parent)\n    (let* ((record-on (if parent\n                          (loop :with retval\n                                :for par = parent :then (component-parent par)\n                                :while par\n                                :do (setf retval par)\n                                :finally (return retval))\n                          component))\n           (comp (if (typep record-on 'component)\n                     record-on\n                     ;; at this point there will be no parent for RECORD-ON\n                     (find-component record-on nil)))\n           (op (make-operation 'define-op))\n           (cell (or (assoc op (%additional-input-files comp))\n                       (let ((new-cell (list op)))\n                         (push new-cell (%additional-input-files comp))\n                         new-cell))))\n      (pushnew pathname (cdr cell) :test 'pathname-equal)\n      (values)))\n\n  ;; Given a form used as :version specification, in the context of a system definition\n  ;; in a file at PATHNAME, for given COMPONENT with given PARENT, normalize the form\n  ;; to an acceptable ASDF-format version.\n  (fmakunbound 'normalize-version) ;; signature changed between 2.27 and 2.31\n  (defun normalize-version (form &key pathname component parent)\n    (labels ((invalid (&optional (continuation \"using NIL instead\"))\n               (warn (compatfmt \"~@<Invalid :version specifier ~S~@[ for component ~S~]~@[ in ~S~]~@[ from file ~S~]~@[, ~A~]~@:>\")\n                     form component parent pathname continuation))\n             (invalid-parse (control &rest args)\n               (unless (if-let (target (find-component parent component)) (builtin-system-p target))\n                 (apply 'warn control args)\n                 (invalid))))\n      (if-let (v (typecase form\n                   ((or string null) form)\n                   (real\n                    (invalid \"Substituting a string\")\n                    (format nil \"~D\" form)) ;; 1.0 becomes \"1.0\"\n                   (cons\n                    (case (first form)\n                      ((:read-file-form)\n                       (destructuring-bind (subpath &key (at 0)) (rest form)\n                         (let ((path (subpathname pathname subpath)))\n                           (record-additional-system-input-file path component parent)\n                           (safe-read-file-form path\n                                                :at at :package :asdf-user))))\n                      ((:read-file-line)\n                       (destructuring-bind (subpath &key (at 0)) (rest form)\n                         (let ((path (subpathname pathname subpath)))\n                           (record-additional-system-input-file path component parent)\n                           (safe-read-file-line (subpathname pathname subpath)\n                                                :at at))))\n                      (otherwise\n                       (invalid))))\n                   (t\n                    (invalid))))\n        (if-let (pv (parse-version v #'invalid-parse))\n          (unparse-version pv)\n          (invalid))))))\n\n\n;;; \"inline methods\"\n(with-upgradability ()\n  (defparameter* +asdf-methods+\n      '(perform-with-restarts perform explain output-files operation-done-p))\n\n  (defun %remove-component-inline-methods (component)\n    (dolist (name +asdf-methods+)\n      (map ()\n           ;; this is inefficient as most of the stored\n           ;; methods will not be for this particular gf\n           ;; But this is hardly performance-critical\n           #'(lambda (m)\n               (remove-method (symbol-function name) m))\n           (component-inline-methods component)))\n    (component-inline-methods component) nil)\n\n  (defparameter *standard-method-combination-qualifiers*\n    '(:around :before :after))\n\n;;; Find inline method definitions of the form\n;;;\n;;;   :perform (test-op :before (operation component) ...)\n;;;\n;;; in REST (which is the plist of all DEFSYSTEM initargs) and define the specified methods.\n  (defun %define-component-inline-methods (ret rest)\n    ;; find key-value pairs that look like inline method definitions in REST. For each identified\n    ;; definition, parse it and, if it is well-formed, define the method.\n    (loop :for (key value) :on rest :by #'cddr\n          :for name = (and (keywordp key) (find key +asdf-methods+ :test 'string=))\n          :when name :do\n            ;; parse VALUE as an inline method definition of the form\n            ;;\n            ;;   (OPERATION-NAME [QUALIFIER] (OPERATION-PARAMETER COMPONENT-PARAMETER) &rest BODY)\n            (destructuring-bind (operation-name &rest rest) value\n              (let ((qualifiers '()))\n                ;; ensure that OPERATION-NAME is a symbol.\n                (unless (and (symbolp operation-name) (not (null operation-name)))\n                  (sysdef-error \"Ill-formed inline method: ~S. The first element is not a symbol ~\n                              designating an operation but ~S.\"\n                                value operation-name))\n                ;; ensure that REST starts with either a cons (potential lambda list, further checked\n                ;; below) or a qualifier accepted by the standard method combination. Everything else\n                ;; is ill-formed. In case of a valid qualifier, pop it from REST so REST now definitely\n                ;; has to start with the lambda list.\n                (cond\n                  ((consp (car rest)))\n                  ((not (member (car rest)\n                                *standard-method-combination-qualifiers*))\n                   (sysdef-error \"Ill-formed inline method: ~S. Only a single of the standard ~\n                               qualifiers ~{~S~^ ~} is allowed, not ~S.\"\n                                 value *standard-method-combination-qualifiers* (car rest)))\n                  (t\n                   (setf qualifiers (list (pop rest)))))\n                ;; REST must start with a two-element lambda list.\n                (unless (and (listp (car rest))\n                             (length=n-p (car rest) 2)\n                             (null (cddar rest)))\n                  (sysdef-error \"Ill-formed inline method: ~S. The operation name ~S is not followed by ~\n                              a lambda-list of the form (OPERATION COMPONENT) and a method body.\"\n                                value operation-name))\n                ;; define the method.\n                (destructuring-bind ((o c) &rest body) rest\n                  (pushnew\n                   (eval `(defmethod ,name ,@qualifiers ((,o ,operation-name) (,c (eql ,ret))) ,@body))\n                   (component-inline-methods ret)))))))\n\n  (defun %refresh-component-inline-methods (component rest)\n    ;; clear methods, then add the new ones\n    (%remove-component-inline-methods component)\n    (%define-component-inline-methods component rest)))\n\n\n;;; Main parsing function\n(with-upgradability ()\n  (defun parse-dependency-def (dd)\n    (if (listp dd)\n        (case (first dd)\n          (:feature\n           (unless (= (length dd) 3)\n             (sysdef-error \"Ill-formed feature dependency: ~s\" dd))\n           (let ((embedded (parse-dependency-def (third dd))))\n             `(:feature ,(second dd) ,embedded)))\n          (feature\n           (sysdef-error \"`feature' has been removed from the dependency spec language of ASDF. Use :feature instead in ~s.\" dd))\n          (:require\n           (unless (= (length dd) 2)\n             (sysdef-error \"Ill-formed require dependency: ~s\" dd))\n           dd)\n          (:version\n           (unless (= (length dd) 3)\n             (sysdef-error \"Ill-formed version dependency: ~s\" dd))\n           `(:version ,(coerce-name (second dd)) ,(third dd)))\n          (otherwise (sysdef-error \"Ill-formed dependency: ~s\" dd)))\n      (coerce-name dd)))\n\n  (defun parse-dependency-defs (dd-list)\n    \"Parse the dependency defs in DD-LIST into canonical form by translating all\nsystem names contained using COERCE-NAME. Return the result.\"\n    (mapcar 'parse-dependency-def dd-list))\n\n  (defgeneric compute-component-children (component components serial-p)\n    (:documentation\n     \"Return a list of children for COMPONENT.\n\nCOMPONENTS is a list of the explicitly defined children descriptions.\n\nSERIAL-P is non-NIL if each child in COMPONENTS should depend on the previous\nchildren.\"))\n\n  (defun stable-union (s1 s2 &key (test #'eql) (key 'identity))\n   (append s1\n     (remove-if #'(lambda (e2) (member (funcall key e2) (funcall key s1) :test test)) s2)))\n\n  (defun parse-component-form (parent options &key previous-serial-components)\n    (destructuring-bind\n        (type name &rest rest &key\n                                (builtin-system-p () bspp)\n                                ;; the following list of keywords is reproduced below in the\n                                ;; remove-plist-keys form.  important to keep them in sync\n                                components pathname perform explain output-files operation-done-p\n                                weakly-depends-on depends-on serial\n                                do-first if-component-dep-fails version\n                                ;; list ends\n         &allow-other-keys) options\n      (declare (ignore perform explain output-files operation-done-p builtin-system-p))\n      (check-component-input type name weakly-depends-on depends-on components)\n      (when (and parent\n                 (find-component parent name)\n                 (not ;; ignore the same object when rereading the defsystem\n                  (typep (find-component parent name)\n                         (class-for-type parent type))))\n        (error 'duplicate-names :name name))\n      (when do-first (error \"DO-FIRST is not supported anymore as of ASDF 3\"))\n      (let* ((name (coerce-name name))\n             (args `(:name ,name\n                     :pathname ,pathname\n                     ,@(when parent `(:parent ,parent))\n                     ,@(remove-plist-keys\n                        '(:components :pathname :if-component-dep-fails :version\n                          :perform :explain :output-files :operation-done-p\n                          :weakly-depends-on :depends-on :serial)\n                        rest)))\n             (component (find-component parent name))\n             (class (class-for-type parent type)))\n        (when (and parent (subtypep class 'system))\n          (error 'non-toplevel-system :parent parent :name name))\n        (if component ; preserve identity\n            (apply 'reinitialize-instance component args)\n            (setf component (apply 'make-instance class args)))\n        (component-pathname component) ; eagerly compute the absolute pathname\n        (when (typep component 'system)\n          ;; cache information for introspection\n          (setf (slot-value component 'depends-on)\n                (parse-dependency-defs depends-on)\n                (slot-value component 'weakly-depends-on)\n                ;; these must be a list of systems, cannot be features or versioned systems\n                (mapcar 'coerce-name weakly-depends-on)))\n        (let ((sysfile (system-source-file (component-system component)))) ;; requires the previous\n          (when (and (typep component 'system) (not bspp))\n            (setf (builtin-system-p component) (lisp-implementation-pathname-p sysfile)))\n          (setf version (normalize-version version :component name :parent parent :pathname sysfile)))\n        ;; Don't use the accessor: kluge to avoid upgrade issue on CCL 1.8.\n        ;; A better fix is required.\n        (setf (slot-value component 'version) version)\n        (when (typep component 'parent-component)\n          (setf (component-children component) (compute-component-children component components serial))\n          (compute-children-by-name component))\n        (when previous-serial-components\n          (setf depends-on (stable-union depends-on previous-serial-components :test #'equal)))\n        (when weakly-depends-on\n          ;; ASDF4: deprecate this feature and remove it.\n          (appendf depends-on\n                   (remove-if (complement #'(lambda (x) (find-system x nil))) weakly-depends-on)))\n        ;; Used by POIU. ASDF4: rename to component-depends-on?\n        (setf (component-sideway-dependencies component) depends-on)\n        (%refresh-component-inline-methods component rest)\n        (when if-component-dep-fails\n          (error \"The system definition for ~S uses deprecated ~\n            ASDF option :IF-COMPONENT-DEP-FAILS. ~\n            Starting with ASDF 3, please use :IF-FEATURE instead\"\n                 (coerce-name (component-system component))))\n        component)))\n\n  (defmethod compute-component-children ((component parent-component) components serial-p)\n    (loop\n      :with previous-components = nil ; list of strings\n      :for c-form :in components\n      :for c = (parse-component-form component c-form\n                                     :previous-serial-components previous-components)\n      :for name :of-type string = (component-name c)\n      :when serial-p\n        ;; if this is an if-feature component, we need to make a serial link\n        ;; from previous components to following components -- otherwise should\n        ;; the IF-FEATURE component drop out, the chain of serial dependencies will be\n        ;; broken.\n        :unless (component-if-feature c)\n          :do (setf previous-components nil)\n        :end\n        :and\n          :do (push name previous-components)\n      :end\n      :collect c))\n\n  ;; the following are all systems that Stas Boukarev maintains and refuses to fix,\n  ;; hoping instead to make my life miserable. Instead, I just make ASDF ignore them.\n  (defparameter* *known-systems-with-bad-secondary-system-names*\n    (list-to-hash-set '(\"cl-ppcre\" \"cl-interpol\")))\n  (defun known-system-with-bad-secondary-system-names-p (asd-name)\n    ;; Does .asd file with name ASD-NAME contain known exceptions\n    ;; that should be screened out of checking for BAD-SYSTEM-NAME?\n    (gethash asd-name *known-systems-with-bad-secondary-system-names*))\n\n  (defun register-system-definition\n      (name &rest options &key pathname (class 'system) (source-file () sfp)\n                            defsystem-depends-on &allow-other-keys)\n    ;; The system must be registered before we parse the body,\n    ;; otherwise we recur when trying to find an existing system\n    ;; of the same name to reuse options (e.g. pathname) from.\n    ;; To avoid infinite recursion in cases where you defsystem a system\n    ;; that is registered to a different location to find-system,\n    ;; we also need to remember it in the asdf-cache.\n    (nest\n     (with-asdf-session ())\n     (let* ((name (coerce-name name))\n            (source-file (if sfp source-file (resolve-symlinks* (load-pathname))))))\n     (flet ((fix-case (x) (if (logical-pathname-p source-file) (string-downcase x) x))))\n     (let* ((asd-name (and source-file\n                           (equal \"asd\" (fix-case (pathname-type source-file)))\n                           (fix-case (pathname-name source-file))))\n            ;; note that PRIMARY-NAME is a *syntactically* primary name\n            (primary-name (primary-system-name name)))\n       (when (and asd-name\n                  (not (equal asd-name primary-name))\n                  (not (known-system-with-bad-secondary-system-names-p asd-name)))\n         (warn (make-condition 'bad-system-name :source-file source-file :name name))))\n     (let* (;; NB: handle defsystem-depends-on BEFORE to create the system object,\n            ;; so that in case it fails, there is no incomplete object polluting the build.\n            (checked-defsystem-depends-on\n             (let* ((dep-forms (parse-dependency-defs defsystem-depends-on))\n                    (deps (loop :for spec :in dep-forms\n                            :when (resolve-dependency-spec nil spec)\n                            :collect :it)))\n               (load-systems* deps)\n               dep-forms))\n            (system (or (find-system-if-being-defined name)\n                        (if-let (registered (registered-system name))\n                          (reset-system-class registered 'undefined-system\n                                              :name name :source-file source-file)\n                          (register-system (make-instance 'undefined-system\n                                                          :name name :source-file source-file)))))\n            (component-options\n             (append\n              (remove-plist-keys '(:defsystem-depends-on :class) options)\n              ;; cache defsystem-depends-on in canonical form\n              (when checked-defsystem-depends-on\n                `(:defsystem-depends-on ,checked-defsystem-depends-on))))\n            (directory (determine-system-directory pathname)))\n       ;; This works hand in hand with asdf/find-system:find-system-if-being-defined:\n       (set-asdf-cache-entry `(find-system ,name) (list system)))\n     ;; We change-class AFTER we loaded the defsystem-depends-on\n     ;; since the class might be defined as part of those.\n     (let ((class (class-for-type nil class)))\n       (unless (subtypep class 'system)\n         (error 'non-system-system :name name :class-name (class-name class)))\n       (unless (eq (type-of system) class)\n         (reset-system-class system class)))\n     (parse-component-form nil (list* :system name :pathname directory component-options))))\n\n  (defmacro defsystem (name &body options)\n    `(apply 'register-system-definition ',name ',options)))\n;;;; -------------------------------------------------------------------------\n;;;; ASDF-Bundle\n\n(uiop/package:define-package :asdf/bundle\n  (:recycle :asdf/bundle :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/system :asdf/operation\n   :asdf/find-component ;; used by ECL\n   :asdf/action :asdf/lisp-action :asdf/plan :asdf/operate :asdf/parse-defsystem)\n  (:export\n   #:bundle-op #:bundle-type #:program-system\n   #:bundle-system #:bundle-pathname-type #:direct-dependency-files\n   #:monolithic-op #:monolithic-bundle-op #:operation-monolithic-p\n   #:basic-compile-bundle-op #:prepare-bundle-op\n   #:compile-bundle-op #:load-bundle-op #:monolithic-compile-bundle-op #:monolithic-load-bundle-op\n   #:lib-op #:monolithic-lib-op\n   #:dll-op #:monolithic-dll-op\n   #:deliver-asd-op #:monolithic-deliver-asd-op\n   #:program-op #:image-op #:compiled-file #:precompiled-system #:prebuilt-system\n   #:user-system-p #:user-system #:trivial-system-p\n   #:prologue-code #:epilogue-code #:static-library))\n(in-package :asdf/bundle)\n\n(with-upgradability ()\n  (defclass bundle-op (operation) ()\n    (:documentation \"base class for operations that bundle outputs from multiple components\"))\n  (defgeneric bundle-type (bundle-op))\n\n  (defclass monolithic-op (operation) ()\n    (:documentation \"A MONOLITHIC operation operates on a system *and all of its\ndependencies*.  So, for example, a monolithic concatenate operation will\nconcatenate together a system's components and all of its dependencies, but a\nsimple concatenate operation will concatenate only the components of the system\nitself.\"))\n\n  (defclass monolithic-bundle-op (bundle-op monolithic-op)\n    ;; Old style way of specifying prologue and epilogue on ECL: in the monolithic operation.\n    ;; DEPRECATED. Supported replacement: Define slots on program-system instead.\n    ((prologue-code :initform nil :accessor prologue-code)\n     (epilogue-code :initform nil :accessor epilogue-code))\n    (:documentation \"operations that are both monolithic-op and bundle-op\"))\n\n  (defclass program-system (system)\n    ;; New style (ASDF3.1) way of specifying prologue and epilogue on ECL: in the system\n    ((prologue-code :initform nil :initarg :prologue-code :reader prologue-code)\n     (epilogue-code :initform nil :initarg :epilogue-code :reader epilogue-code)\n     (no-uiop :initform nil :initarg :no-uiop :reader no-uiop)\n     (prefix-lisp-object-files :initarg :prefix-lisp-object-files\n                               :initform nil :accessor prefix-lisp-object-files)\n     (postfix-lisp-object-files :initarg :postfix-lisp-object-files\n                                :initform nil :accessor postfix-lisp-object-files)\n     (extra-object-files :initarg :extra-object-files\n                         :initform nil :accessor extra-object-files)\n     (extra-build-args :initarg :extra-build-args\n                       :initform nil :accessor extra-build-args)))\n\n  (defmethod prologue-code ((x system)) nil)\n  (defmethod epilogue-code ((x system)) nil)\n  (defmethod no-uiop ((x system)) nil)\n  (defmethod prefix-lisp-object-files ((x system)) nil)\n  (defmethod postfix-lisp-object-files ((x system)) nil)\n  (defmethod extra-object-files ((x system)) nil)\n  (defmethod extra-build-args ((x system)) nil)\n\n  (defclass link-op (bundle-op) ()\n    (:documentation \"Abstract operation for linking files together\"))\n\n  (defclass gather-operation (bundle-op) ()\n    (:documentation \"Abstract operation for gathering many input files from a system\"))\n  (defgeneric gather-operation (gather-operation))\n  (defmethod gather-operation ((o gather-operation)) nil)\n  (defgeneric gather-type (gather-operation))\n\n  (defun operation-monolithic-p (op)\n    (typep op 'monolithic-op))\n\n  ;; Dependencies of a gather-op are the actions of the dependent operation\n  ;; for all the (sorted) required components for loading the system.\n  ;; Monolithic operations typically use lib-op as the dependent operation,\n  ;; and all system-level dependencies as required components.\n  ;; Non-monolithic operations typically use compile-op as the dependent operation,\n  ;; and all transitive sub-components as required components (excluding other systems).\n  (defmethod component-depends-on ((o gather-operation) (s system))\n    (let* ((mono (operation-monolithic-p o))\n           (go (make-operation (or (gather-operation o) 'compile-op)))\n           (bundle-p (typep go 'bundle-op))\n           ;; In a non-mono operation, don't recurse to other systems.\n           ;; In a mono operation gathering bundles, don't recurse inside systems.\n           (component-type (if mono (if bundle-p 'system t) '(not system)))\n           ;; In the end, only keep system bundles or non-system bundles, depending.\n           (keep-component (if bundle-p 'system '(not system)))\n           (deps\n            ;; Required-components only looks at the dependencies of an action, excluding the action\n            ;; itself, so it may be safely used by an action recursing on its dependencies (which\n            ;; may or may not be an overdesigned API, since in practice we never use it that way).\n            ;; Therefore, if we use :goal-operation 'load-op :keep-operation 'load-op, which looks\n            ;; cleaner, we will miss the load-op on the requested system itself, which doesn't\n            ;; matter for a regular system, but matters, a lot, for a package-inferred-system.\n            ;; Using load-op as the goal operation and basic-compile-op as the keep-operation works\n            ;; for our needs of gathering all the files we want to include in a bundle.\n            ;; Note that we use basic-compile-op rather than compile-op so it will still work on\n            ;; systems that would somehow load dependencies with load-bundle-op.\n            (required-components\n             s :other-systems mono :component-type component-type :keep-component keep-component\n             :goal-operation 'load-op :keep-operation 'basic-compile-op)))\n      `((,go ,@deps) ,@(call-next-method))))\n\n  ;; Create a single fasl for the entire library\n  (defclass basic-compile-bundle-op (bundle-op basic-compile-op) ()\n    (:documentation \"Base class for compiling into a bundle\"))\n  (defmethod bundle-type ((o basic-compile-bundle-op)) :fasb)\n  (defmethod gather-type ((o basic-compile-bundle-op))\n    #-(or clasp ecl mkcl) :fasl\n    #+(or clasp ecl mkcl) :object)\n\n  ;; Analog to prepare-op, for load-bundle-op and compile-bundle-op\n  (defclass prepare-bundle-op (sideway-operation)\n    ((sideway-operation\n      :initform #+(or clasp ecl mkcl) 'load-bundle-op #-(or clasp ecl mkcl) 'load-op\n      :allocation :class))\n    (:documentation \"Operation class for loading the bundles of a system's dependencies\"))\n\n  (defclass lib-op (link-op gather-operation non-propagating-operation) ()\n    (:documentation \"Compile the system and produce a linkable static library (.a/.lib)\nfor all the linkable object files associated with the system. Compare with DLL-OP.\n\nOn most implementations, these object files only include extensions to the runtime\nwritten in C or another language with a compiler producing linkable object files.\nOn CLASP, ECL, MKCL, these object files _also_ include the contents of Lisp files\nthemselves. In any case, this operation will produce what you need to further build\na static runtime for your system, or a dynamic library to load in an existing runtime.\"))\n  (defmethod bundle-type ((o lib-op)) :lib)\n  (defmethod gather-type ((o lib-op)) :object)\n\n  ;; What works: on ECL, CLASP(?), MKCL, we link the many .o files from the system into the .so;\n  ;; on other implementations, we combine (usually concatenate) the .fasl files into one.\n  (defclass compile-bundle-op (basic-compile-bundle-op selfward-operation gather-operation\n                                                       #+(or clasp ecl mkcl) link-op)\n    ((selfward-operation :initform '(prepare-bundle-op) :allocation :class))\n    (:documentation \"This operator is an alternative to COMPILE-OP. Build a system\nand all of its dependencies, but build only a single (\\\"monolithic\\\") FASL, instead\nof one per source file, which may be more resource efficient.  That monolithic\nFASL should be loaded with LOAD-BUNDLE-OP, rather than LOAD-OP.\"))\n\n  (defclass load-bundle-op (basic-load-op selfward-operation)\n    ((selfward-operation :initform '(prepare-bundle-op compile-bundle-op) :allocation :class))\n    (:documentation \"This operator is an alternative to LOAD-OP. Build a system\nand all of its dependencies, using COMPILE-BUNDLE-OP. The difference with\nrespect to LOAD-OP is that it builds only a single FASL, which may be\nfaster and more resource efficient.\"))\n\n  ;; NB: since the monolithic-op's can't be sideway-operation's,\n  ;; if we wanted lib-op, dll-op, deliver-asd-op to be sideway-operation's,\n  ;; we'd have to have the monolithic-op not inherit from the main op,\n  ;; but instead inherit from a basic-FOO-op as with basic-compile-bundle-op above.\n\n  (defclass dll-op (link-op gather-operation non-propagating-operation) ()\n    (:documentation \"Compile the system and produce a dynamic loadable library (.so/.dll)\nfor all the linkable object files associated with the system. Compare with LIB-OP.\"))\n  (defmethod bundle-type ((o dll-op)) :dll)\n  (defmethod gather-type ((o dll-op)) :object)\n\n  (defclass deliver-asd-op (basic-compile-op selfward-operation)\n    ((selfward-operation\n      ;; TODO: implement link-op on all implementations, and make that\n      ;; '(compile-bundle-op lib-op #-(or clasp ecl mkcl) dll-op)\n      :initform '(compile-bundle-op #+(or clasp ecl mkcl) lib-op)\n      :allocation :class))\n    (:documentation \"produce an asd file for delivering the system as a single fasl\"))\n\n\n  (defclass monolithic-deliver-asd-op (deliver-asd-op monolithic-bundle-op)\n    ((selfward-operation\n      ;; TODO: implement link-op on all implementations, and make that\n      ;; '(monolithic-compile-bundle-op monolithic-lib-op #-(or clasp ecl mkcl) monolithic-dll-op)\n      :initform '(monolithic-compile-bundle-op #+(or clasp ecl mkcl) monolithic-lib-op)\n      :allocation :class))\n    (:documentation \"produce fasl and asd files for combined system and dependencies.\"))\n\n  (defclass monolithic-compile-bundle-op\n      (basic-compile-bundle-op monolithic-bundle-op\n       #+(or clasp ecl mkcl) link-op gather-operation non-propagating-operation)\n    ()\n    (:documentation \"Create a single fasl for the system and its dependencies.\"))\n\n  (defclass monolithic-load-bundle-op (load-bundle-op monolithic-bundle-op)\n    ((selfward-operation :initform 'monolithic-compile-bundle-op :allocation :class))\n    (:documentation \"Load a single fasl for the system and its dependencies.\"))\n\n  (defclass monolithic-lib-op (lib-op monolithic-bundle-op non-propagating-operation) ()\n    (:documentation \"Compile the system and produce a linkable static library (.a/.lib)\nfor all the linkable object files associated with the system or its dependencies. See LIB-OP.\"))\n\n  (defclass monolithic-dll-op (dll-op monolithic-bundle-op non-propagating-operation) ()\n    (:documentation \"Compile the system and produce a dynamic loadable library (.so/.dll)\nfor all the linkable object files associated with the system or its dependencies. See LIB-OP\"))\n\n  (defclass image-op (monolithic-bundle-op selfward-operation\n                      #+(or clasp ecl mkcl) link-op #+(or clasp ecl mkcl) gather-operation)\n    ((selfward-operation :initform '(#-(or clasp ecl mkcl) load-op) :allocation :class))\n    (:documentation \"create an image file from the system and its dependencies\"))\n  (defmethod bundle-type ((o image-op)) :image)\n  #+(or clasp ecl mkcl) (defmethod gather-operation ((o image-op)) 'lib-op)\n  #+(or clasp ecl mkcl) (defmethod gather-type ((o image-op)) :static-library)\n\n  (defclass program-op (image-op) ()\n    (:documentation \"create an executable file from the system and its dependencies\"))\n  (defmethod bundle-type ((o program-op)) :program)\n\n  ;; From the ASDF-internal bundle-type identifier, get a filesystem-usable pathname type.\n  (defun bundle-pathname-type (bundle-type)\n    (etypecase bundle-type\n      ((or null string) ;; pass through nil or string literal\n       bundle-type)\n      ((eql :no-output-file) ;; marker for a bundle-type that has NO output file\n       (error \"No output file, therefore no pathname type\"))\n      ((eql :fasl) ;; the type of a fasl\n       (compile-file-type)) ; on image-based platforms, used as input and output\n      ((eql :fasb) ;; the type of a fasl\n       #-(or clasp ecl mkcl) (compile-file-type) ; on image-based platforms, used as input and output\n       #+(or ecl mkcl) \"fasb\"\n       #+clasp \"fasp\") ; on C-linking platforms, only used as output for system bundles\n      ((member :image)\n       #+allegro \"dxl\"\n       #+(and clisp os-windows) \"exe\"\n       #-(or allegro (and clisp os-windows)) \"image\")\n      ;; NB: on CLASP and ECL these implementations, we better agree with\n      ;; (compile-file-type :type bundle-type))\n      ((eql :object) ;; the type of a linkable object file\n       (os-cond ((os-unix-p)\n                 #+clasp \"fasp\" ;(core:build-extension cmp:*default-object-type*)\n                 #-clasp \"o\")\n                ((os-windows-p) (if (featurep '(:or :mingw32 :mingw64)) \"o\" \"obj\"))))\n      ((member :lib :static-library) ;; the type of a linkable library\n       (os-cond ((os-unix-p) \"a\")\n                ((os-windows-p) (if (featurep '(:or :mingw32 :mingw64)) \"a\" \"lib\"))))\n      ((member :dll :shared-library) ;; the type of a shared library\n       (os-cond ((os-macosx-p) \"dylib\") ((os-unix-p) \"so\") ((os-windows-p) \"dll\")))\n      ((eql :program) ;; the type of an executable program\n       (os-cond ((os-unix-p) nil) ((os-windows-p) \"exe\")))))\n\n  ;; Compute the output-files for a given bundle action\n  (defun bundle-output-files (o c)\n    (let ((bundle-type (bundle-type o)))\n      (unless (or (eq bundle-type :no-output-file) ;; NIL already means something regarding type.\n                  (and (null (input-files o c)) (not (member bundle-type '(:image :program)))))\n        (let ((name (or (component-build-pathname c)\n                        (let ((suffix\n                               (unless (typep o 'program-op)\n                                 ;; \".\" is no good separator for Logical Pathnames, so we use \"--\"\n                                 (if (operation-monolithic-p o)\n                                     \"--all-systems\"\n                                     ;; These use a different type .fasb or .a instead of .fasl\n                                     #-(or clasp ecl mkcl) \"--system\"))))\n                          (format nil \"~A~@[~A~]\" (coerce-filename (component-name c)) suffix))))\n              (type (bundle-pathname-type bundle-type)))\n          (values (list (subpathname (component-pathname c) name :type type))\n                  (eq (class-of o) (coerce-class (component-build-operation c)\n                                                 :package :asdf/interface\n                                                 :super 'operation\n                                                 :error nil)))))))\n\n  (defmethod output-files ((o bundle-op) (c system))\n    (bundle-output-files o c))\n\n  #-(or clasp ecl mkcl)\n  (progn\n    (defmethod perform ((o image-op) (c system))\n      (dump-image (output-file o c) :executable (typep o 'program-op)))\n    (defmethod perform :before ((o program-op) (c system))\n      (setf *image-entry-point* (ensure-function (component-entry-point c)))))\n\n  (defclass compiled-file (file-component)\n    ((type :initform #-(or clasp ecl mkcl) (compile-file-type) #+(or clasp ecl mkcl) \"fasb\"))\n    (:documentation \"Class for a file that is already compiled,\ne.g. as part of the implementation, of an outer build system that calls into ASDF,\nor of opaque libraries shipped along the source code.\"))\n\n  (defclass precompiled-system (system)\n    ((build-pathname :initarg :fasb :initarg :fasl))\n    (:documentation \"Class For a system that is delivered as a precompiled fasl\"))\n\n  (defclass prebuilt-system (system)\n    ((build-pathname :initarg :static-library :initarg :lib\n                     :accessor prebuilt-system-static-library))\n    (:documentation \"Class for a system delivered with a linkable static library (.a/.lib)\")))\n\n\n;;;\n;;; BUNDLE-OP\n;;;\n;;; This operation takes all components from one or more systems and\n;;; creates a single output file, which may be\n;;; a FASL, a statically linked library, a shared library, etc.\n;;; The different targets are defined by specialization.\n;;;\n(when-upgrading (:version \"3.2.0\")\n  ;; Cancel any previously defined method\n  (defmethod initialize-instance :after ((instance bundle-op) &rest initargs &key &allow-other-keys)\n    (declare (ignore initargs))))\n\n(with-upgradability ()\n  (defgeneric trivial-system-p (component))\n\n  (defun user-system-p (s)\n    (and (typep s 'system)\n         (not (builtin-system-p s))\n         (not (trivial-system-p s)))))\n\n(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute)\n  (deftype user-system () '(and system (satisfies user-system-p))))\n\n;;;\n;;; First we handle monolithic bundles.\n;;; These are standalone systems which contain everything,\n;;; including other ASDF systems required by the current one.\n;;; A PROGRAM is always monolithic.\n;;;\n;;; MONOLITHIC SHARED LIBRARIES, PROGRAMS, FASL\n;;;\n(with-upgradability ()\n  (defun direct-dependency-files (o c &key (test 'identity) (key 'output-files) &allow-other-keys)\n    ;; This function selects output files from direct dependencies;\n    ;; your component-depends-on method must gather the correct dependencies in the correct order.\n    (while-collecting (collect)\n      (map-direct-dependencies\n       o c #'(lambda (sub-o sub-c)\n               (loop :for f :in (funcall key sub-o sub-c)\n                 :when (funcall test f) :do (collect f))))))\n\n  (defun pathname-type-equal-function (type)\n    #'(lambda (p) (equalp (pathname-type p) type)))\n\n  (defmethod input-files ((o gather-operation) (c system))\n    (unless (eq (bundle-type o) :no-output-file)\n      (direct-dependency-files\n       o c :key 'output-files\n           :test (pathname-type-equal-function (bundle-pathname-type (gather-type o))))))\n\n  ;; Find the operation that produces a given bundle-type\n  (defun select-bundle-operation (type &optional monolithic)\n    (ecase type\n      ((:dll :shared-library)\n       (if monolithic 'monolithic-dll-op 'dll-op))\n      ((:lib :static-library)\n       (if monolithic 'monolithic-lib-op 'lib-op))\n      ((:fasb)\n       (if monolithic 'monolithic-compile-bundle-op 'compile-bundle-op))\n      ((:image)\n       'image-op)\n      ((:program)\n       'program-op))))\n\n;;;\n;;; LOAD-BUNDLE-OP\n;;;\n;;; This is like ASDF's LOAD-OP, but using bundle fasl files.\n;;;\n(with-upgradability ()\n  (defmethod component-depends-on ((o load-bundle-op) (c system))\n    `((,o ,@(component-sideway-dependencies c))\n      (,(if (user-system-p c) 'compile-bundle-op 'load-op) ,c)\n      ,@(call-next-method)))\n\n  (defmethod input-files ((o load-bundle-op) (c system))\n    (when (user-system-p c)\n      (output-files (find-operation o 'compile-bundle-op) c)))\n\n  (defmethod perform ((o load-bundle-op) (c system))\n    (when (input-files o c)\n      (perform-lisp-load-fasl o c)))\n\n  (defmethod mark-operation-done :after ((o load-bundle-op) (c system))\n    (mark-operation-done (find-operation o 'load-op) c)))\n\n;;;\n;;; PRECOMPILED FILES\n;;;\n;;; This component can be used to distribute ASDF systems in precompiled form.\n;;; Only useful when the dependencies have also been precompiled.\n;;;\n(with-upgradability ()\n  (defmethod trivial-system-p ((s system))\n    (every #'(lambda (c) (typep c 'compiled-file)) (component-children s)))\n\n  (defmethod input-files ((o operation) (c compiled-file))\n    (list (component-pathname c)))\n  (defmethod perform ((o load-op) (c compiled-file))\n    (perform-lisp-load-fasl o c))\n  (defmethod perform ((o load-source-op) (c compiled-file))\n    (perform (find-operation o 'load-op) c))\n  (defmethod perform ((o operation) (c compiled-file))\n    nil))\n\n;;;\n;;; Pre-built systems\n;;;\n(with-upgradability ()\n  (defmethod trivial-system-p ((s prebuilt-system))\n    t)\n\n  (defmethod perform ((o link-op) (c prebuilt-system))\n    nil)\n\n  (defmethod perform ((o basic-compile-bundle-op) (c prebuilt-system))\n    nil)\n\n  (defmethod perform ((o lib-op) (c prebuilt-system))\n    nil)\n\n  (defmethod perform ((o dll-op) (c prebuilt-system))\n    nil)\n\n  (defmethod component-depends-on ((o gather-operation) (c prebuilt-system))\n    nil)\n\n  (defmethod output-files ((o lib-op) (c prebuilt-system))\n    (values (list (prebuilt-system-static-library c)) t)))\n\n\n;;;\n;;; PREBUILT SYSTEM CREATOR\n;;;\n(with-upgradability ()\n  (defmethod output-files ((o deliver-asd-op) (s system))\n    (list (make-pathname :name (coerce-filename (component-name s)) :type \"asd\"\n                         :defaults (component-pathname s))))\n\n  ;; because of name collisions between the output files of different\n  ;; subclasses of DELIVER-ASD-OP, we cannot trust the file system to\n  ;; tell us if the output file is up-to-date, so just treat the\n  ;; operation as never being done.\n  (defmethod operation-done-p ((o deliver-asd-op) (s system))\n    (declare (ignorable o s))\n    nil)\n\n  (defun space-for-crlf (s)\n    (substitute-if #\\space #'(lambda (x) (find x +crlf+)) s))\n\n  (defmethod perform ((o deliver-asd-op) (s system))\n    \"Write an ASDF system definition for loading S as a delivered system.\"\n    (let* ((inputs (input-files o s))\n           (fasl (first inputs))\n           (library (second inputs))\n           (asd (output-file o s))\n           (name (if (and fasl asd) (pathname-name asd) (return-from perform)))\n           (version (component-version s))\n           (dependencies\n             (if (operation-monolithic-p o)\n                 ;; We want only dependencies, and we use basic-load-op rather than load-op so that\n                 ;; this will keep working on systems that load dependencies with load-bundle-op\n                 (remove-if-not 'builtin-system-p\n                                (required-components s :component-type 'system\n                                                       :keep-operation 'basic-load-op))\n                 (while-collecting (x) ;; resolve the sideway-dependencies of s\n                   (map-direct-dependencies\n                    'prepare-op s\n                    #'(lambda (o c)\n                        (when (and (typep o 'load-op) (typep c 'system))\n                          (x c)))))))\n           (depends-on (mapcar 'coerce-name dependencies)))\n      (when (pathname-equal asd (system-source-file s))\n        (cerror \"overwrite the asd file\"\n                \"~/asdf-action:format-action/ is going to overwrite the system definition file ~S ~\nwhich is probably not what you want; you probably need to tweak your output translations.\"\n                (cons o s) asd))\n      (with-open-file (s asd :direction :output :if-exists :supersede\n                             :if-does-not-exist :create)\n        (format s \";;; Prebuilt~:[~; monolithic~] ASDF definition for system ~A~%\"\n                (operation-monolithic-p o) name)\n        ;; this can cause bugs in cases where one of the functions returns a multi-line\n        ;; string\n        (let ((description-string (format nil \";;; Built for ~A ~A on a ~A/~A ~A\"\n                    (lisp-implementation-type)\n                    (lisp-implementation-version)\n                    (software-type)\n                    (machine-type)\n                    (software-version))))\n          ;; ensure the whole thing is on one line\n          (println (space-for-crlf description-string) s))\n        (let ((*package* (find-package :asdf-user)))\n          (pprint `(defsystem ,name\n                     :class prebuilt-system\n                     :version ,version\n                     :depends-on ,depends-on\n                     :components ((:compiled-file ,(pathname-name fasl)))\n                     ,@(when library `(:lib ,(file-namestring library))))\n                  s)\n          (terpri s)))))\n\n  #-(or clasp ecl mkcl)\n  (defmethod perform ((o basic-compile-bundle-op) (c system))\n    (let* ((input-files (input-files o c))\n           (fasl-files (remove (compile-file-type) input-files :key #'pathname-type :test-not #'equalp))\n           (non-fasl-files (remove (compile-file-type) input-files :key #'pathname-type :test #'equalp))\n           (output-files (output-files o c)) ; can't use OUTPUT-FILE fn because possibility it's NIL\n           (output-file (first output-files)))\n      (assert (eq (not input-files) (not output-files)))\n      (when input-files\n        (when non-fasl-files\n          (error \"On ~A, asdf/bundle can only bundle FASL files, but these were also produced: ~S\"\n                 (implementation-type) non-fasl-files))\n        (when (or (prologue-code c) (epilogue-code c))\n          (error \"prologue-code and epilogue-code are not supported on ~A\"\n                 (implementation-type)))\n        (with-staging-pathname (output-file)\n          (combine-fasls fasl-files output-file)))))\n\n  (defmethod input-files ((o load-op) (s precompiled-system))\n    (bundle-output-files (find-operation o 'compile-bundle-op) s))\n\n  (defmethod perform ((o load-op) (s precompiled-system))\n    (perform-lisp-load-fasl o s))\n\n  (defmethod component-depends-on ((o load-bundle-op) (s precompiled-system))\n    `((load-op ,s) ,@(call-next-method))))\n\n#| ;; Example use:\n(asdf:defsystem :precompiled-asdf-utils :class asdf::precompiled-system :fasl (asdf:apply-output-translations (asdf:system-relative-pathname :asdf-utils \"asdf-utils.system.fasl\")))\n(asdf:load-system :precompiled-asdf-utils)\n|#\n\n#+(or clasp ecl mkcl)\n(with-upgradability ()\n  (defun system-module-pathname (module)\n    (let ((name (coerce-name module)))\n      (some\n       'file-exists-p\n       (list\n        #+clasp (compile-file-pathname (make-pathname :name name :defaults \"sys:\") :output-type :object)\n        #+ecl (compile-file-pathname (make-pathname :name name :defaults \"sys:\") :type :lib)\n        #+ecl (compile-file-pathname (make-pathname :name (strcat \"lib\" name) :defaults \"sys:\") :type :lib)\n        #+ecl (compile-file-pathname (make-pathname :name name :defaults \"sys:\") :type :object)\n        #+mkcl (make-pathname :name name :type (bundle-pathname-type :lib) :defaults #p\"sys:\")\n        #+mkcl (make-pathname :name name :type (bundle-pathname-type :lib) :defaults #p\"sys:contrib;\")))))\n\n  (defun make-prebuilt-system (name &optional (pathname (system-module-pathname name)))\n    \"Creates a prebuilt-system if PATHNAME isn't NIL.\"\n    (when pathname\n      (make-instance 'prebuilt-system\n                     :name (coerce-name name)\n                     :static-library (resolve-symlinks* pathname))))\n\n  (defun linkable-system (x)\n    (or ;; If the system is available as source, use it.\n        (if-let (s (find-system x))\n          (and (output-files 'lib-op s) s))\n        ;; If an ASDF upgrade is available from source, but not a UIOP upgrade to that,\n        ;; then use the asdf/driver system instead of\n        ;; the UIOP that was disabled by check-not-old-asdf-system.\n        (if-let (s (and (equal (coerce-name x) \"uiop\")\n                        (output-files 'lib-op \"asdf\")\n                        (find-system \"asdf/driver\")))\n          (and (output-files 'lib-op s) s))\n        ;; If there was no source upgrade, look for modules provided by the implementation.\n        (if-let (p (system-module-pathname (coerce-name x)))\n          (make-prebuilt-system x p))))\n\n  (defmethod component-depends-on :around ((o image-op) (c system))\n    (let* ((next (call-next-method))\n           (deps (make-hash-table :test 'equal))\n           (linkable (loop :for (do . dcs) :in next :collect\n                       (cons do\n                             (loop :for dc :in dcs\n                               :for dep = (and dc (resolve-dependency-spec c dc))\n                               :when dep\n                               :do (setf (gethash (coerce-name (component-system dep)) deps) t)\n                               :collect (or (and (typep dep 'system) (linkable-system dep)) dep))))))\n        `((lib-op\n           ,@(unless (no-uiop c)\n               (list (linkable-system \"cmp\")\n                     (unless (or (and (gethash \"uiop\" deps) (linkable-system \"uiop\"))\n                                 (and (gethash \"asdf\" deps) (linkable-system \"asdf\")))\n                       (or (linkable-system \"uiop\")\n                           (linkable-system \"asdf\")\n                           \"asdf\")))))\n          ,@linkable)))\n\n  (defmethod perform ((o link-op) (c system))\n    (let* ((object-files (input-files o c))\n           (output (output-files o c))\n           (bundle (first output))\n           (programp (typep o 'program-op))\n           (kind (bundle-type o)))\n      (when output\n        (apply 'create-image\n               bundle (append\n                       (when programp (prefix-lisp-object-files c))\n                       object-files\n                       (when programp (postfix-lisp-object-files c)))\n               :kind kind\n               :prologue-code (when programp (prologue-code c))\n               :epilogue-code (when programp (epilogue-code c))\n               :build-args (when programp (extra-build-args c))\n               :extra-object-files (when programp (extra-object-files c))\n               :no-uiop (no-uiop c)\n               (when programp `(:entry-point ,(component-entry-point c))))))))\n;;;; -------------------------------------------------------------------------\n;;;; Concatenate-source\n\n(uiop/package:define-package :asdf/concatenate-source\n  (:recycle :asdf/concatenate-source :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade\n   :asdf/component :asdf/operation\n   :asdf/system\n   :asdf/action :asdf/lisp-action :asdf/plan :asdf/bundle)\n  (:export\n   #:concatenate-source-op\n   #:load-concatenated-source-op\n   #:compile-concatenated-source-op\n   #:load-compiled-concatenated-source-op\n   #:monolithic-concatenate-source-op\n   #:monolithic-load-concatenated-source-op\n   #:monolithic-compile-concatenated-source-op\n   #:monolithic-load-compiled-concatenated-source-op))\n(in-package :asdf/concatenate-source)\n\n;;;\n;;; Concatenate sources\n;;;\n(with-upgradability ()\n  ;; Base classes for both regular and monolithic concatenate-source operations\n  (defclass basic-concatenate-source-op (bundle-op) ())\n  (defmethod bundle-type ((o basic-concatenate-source-op)) \"lisp\")\n  (defclass basic-load-concatenated-source-op (basic-load-op selfward-operation) ())\n  (defclass basic-compile-concatenated-source-op (basic-compile-op selfward-operation) ())\n  (defclass basic-load-compiled-concatenated-source-op (basic-load-op selfward-operation) ())\n\n  ;; Regular concatenate-source operations\n  (defclass concatenate-source-op (basic-concatenate-source-op non-propagating-operation) ()\n    (:documentation \"Operation to concatenate all sources in a system into a single file\"))\n  (defclass load-concatenated-source-op (basic-load-concatenated-source-op)\n    ((selfward-operation :initform '(prepare-op concatenate-source-op) :allocation :class))\n    (:documentation \"Operation to load the result of concatenate-source-op as source\"))\n  (defclass compile-concatenated-source-op (basic-compile-concatenated-source-op)\n    ((selfward-operation :initform '(prepare-op concatenate-source-op) :allocation :class))\n    (:documentation \"Operation to compile the result of concatenate-source-op\"))\n  (defclass load-compiled-concatenated-source-op (basic-load-compiled-concatenated-source-op)\n    ((selfward-operation :initform '(prepare-op compile-concatenated-source-op) :allocation :class))\n    (:documentation \"Operation to load the result of compile-concatenated-source-op\"))\n\n  (defclass monolithic-concatenate-source-op\n      (basic-concatenate-source-op monolithic-bundle-op non-propagating-operation) ()\n    (:documentation \"Operation to concatenate all sources in a system and its dependencies\ninto a single file\"))\n  (defclass monolithic-load-concatenated-source-op (basic-load-concatenated-source-op)\n    ((selfward-operation :initform 'monolithic-concatenate-source-op :allocation :class))\n    (:documentation \"Operation to load the result of monolithic-concatenate-source-op as source\"))\n  (defclass monolithic-compile-concatenated-source-op (basic-compile-concatenated-source-op)\n    ((selfward-operation :initform 'monolithic-concatenate-source-op :allocation :class))\n    (:documentation \"Operation to compile the result of monolithic-concatenate-source-op\"))\n  (defclass monolithic-load-compiled-concatenated-source-op\n      (basic-load-compiled-concatenated-source-op)\n    ((selfward-operation :initform 'monolithic-compile-concatenated-source-op :allocation :class))\n    (:documentation \"Operation to load the result of monolithic-compile-concatenated-source-op\"))\n\n  (defmethod input-files ((operation basic-concatenate-source-op) (s system))\n    (loop :with encoding = (or (component-encoding s) *default-encoding*)\n          :with other-encodings = '()\n          :with around-compile = (around-compile-hook s)\n          :with other-around-compile = '()\n          :for c :in (required-components  ;; see note about similar call to required-components\n                      s :goal-operation 'load-op ;;  in bundle.lisp\n                        :keep-operation 'basic-compile-op\n                        :other-systems (operation-monolithic-p operation))\n          :append\n          (when (typep c 'cl-source-file)\n            (let ((e (component-encoding c)))\n              (unless (or (equal e encoding)\n                          (and (equal e :ASCII) (equal encoding :UTF-8)))\n                (let ((a (assoc e other-encodings)))\n                  (if a (push (component-find-path c) (cdr a))\n                      (push (list e (component-find-path c)) other-encodings)))))\n            (unless (equal around-compile (around-compile-hook c))\n              (push (component-find-path c) other-around-compile))\n            (input-files (make-operation 'compile-op) c)) :into inputs\n          :finally\n             (when other-encodings\n               (warn \"~S uses encoding ~A but has sources that use these encodings:~{ ~A~}\"\n                     operation encoding\n                     (mapcar #'(lambda (x) (cons (car x) (list (reverse (cdr x)))))\n                             other-encodings)))\n             (when other-around-compile\n               (warn \"~S uses around-compile hook ~A but has sources that use these hooks: ~A\"\n                     operation around-compile other-around-compile))\n             (return inputs)))\n  (defmethod output-files ((o basic-compile-concatenated-source-op) (s system))\n    (lisp-compilation-output-files o s))\n\n  (defmethod perform ((o basic-concatenate-source-op) (s system))\n    (let* ((ins (input-files o s))\n           (out (output-file o s))\n           (tmp (tmpize-pathname out)))\n      (concatenate-files ins tmp)\n      (rename-file-overwriting-target tmp out)))\n  (defmethod perform ((o basic-load-concatenated-source-op) (s system))\n    (perform-lisp-load-source o s))\n  (defmethod perform ((o basic-compile-concatenated-source-op) (s system))\n    (perform-lisp-compilation o s))\n  (defmethod perform ((o basic-load-compiled-concatenated-source-op) (s system))\n    (perform-lisp-load-fasl o s)))\n\n;;;; -------------------------------------------------------------------------\n;;;; Package systems in the style of quick-build or faslpath\n\n(uiop:define-package :asdf/package-inferred-system\n  (:recycle :asdf/package-inferred-system :asdf/package-system :asdf)\n  (:use :uiop/common-lisp :uiop\n        :asdf/upgrade :asdf/session\n        :asdf/component :asdf/system :asdf/system-registry :asdf/lisp-action\n        :asdf/parse-defsystem)\n  (:export\n   #:package-inferred-system #:sysdef-package-inferred-system-search\n   #:package-system ;; backward compatibility only. To be removed.\n   #:register-system-packages\n   #:*defpackage-forms* #:*package-inferred-systems*\n   #:package-inferred-system-missing-package-error\n   #:package-inferred-system-unknown-defpackage-option-error))\n(in-package :asdf/package-inferred-system)\n\n(with-upgradability ()\n  ;; The names of the recognized defpackage forms.\n  (defparameter *defpackage-forms* '(defpackage define-package))\n\n  (defun initial-package-inferred-systems-table ()\n    ;; Mark all existing packages are preloaded.\n    (let ((h (make-hash-table :test 'equal)))\n      (dolist (p (list-all-packages))\n        (dolist (n (package-names p))\n          (setf (gethash n h) t)))\n      h))\n\n  ;; Mapping from package names to systems that provide them.\n  (defvar *package-inferred-systems* (initial-package-inferred-systems-table))\n\n  (defclass package-inferred-system (system)\n    ()\n    (:documentation \"Class for primary systems for which secondary systems are automatically\nin the one-file, one-file, one-system style: system names are mapped to files under the primary\nsystem's system-source-directory, dependencies are inferred from the first defpackage form in\nevery such file\"))\n\n  ;; DEPRECATED. For backward compatibility only. To be removed in an upcoming release:\n  (defclass package-system (package-inferred-system) ())\n\n  ;; Is a given form recognizable as a defpackage form?\n  (defun defpackage-form-p (form)\n    (and (consp form)\n         (member (car form) *defpackage-forms*)))\n\n  ;; Find the first defpackage form in a stream, if any\n  (defun stream-defpackage-form (stream)\n    (loop :for form = (read stream nil nil) :while form\n          :when (defpackage-form-p form) :return form))\n\n  (defun file-defpackage-form (file)\n    \"Return the first DEFPACKAGE form in FILE.\"\n    (with-input-file (f file)\n      (stream-defpackage-form f)))\n\n  (define-condition package-inferred-system-missing-package-error (system-definition-error)\n    ((system :initarg :system :reader error-system)\n     (pathname :initarg :pathname :reader error-pathname))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<No package form found while ~\n                                     trying to define package-inferred-system ~A from file ~A~>\")\n                       (error-system c) (error-pathname c)))))\n\n  (define-condition package-inferred-system-unknown-defpackage-option-error (system-definition-error)\n    ((system :initarg :system :reader error-system)\n     (pathname :initarg :pathname :reader error-pathname)\n     (option :initarg :clause-head :reader error-option)\n     (arguments :initarg :clause-rest :reader error-arguments))\n    (:report (lambda (c s)\n               (format s (compatfmt \"~@<Don't know how to infer package dependencies ~\n                                     for non-standard option ~S ~\n                                     while trying to define package-inferred-system ~A ~\n                                     from file ~A~>\")\n                       (cons (error-option c)\n                             (error-arguments c))\n                       (error-system c)\n                       (error-pathname c)))))\n\n  (defun package-dependencies (defpackage-form &optional system pathname)\n    \"Return a list of packages depended on by the package\ndefined in DEFPACKAGE-FORM.  A package is depended upon if\nthe DEFPACKAGE-FORM uses it or imports a symbol from it.\n\nSYSTEM should be the name of the system being defined, and\nPATHNAME should be the file which contains the DEFPACKAGE-FORM.\nThese will be used to report errors when encountering an unknown defpackage argument.\"\n    (assert (defpackage-form-p defpackage-form))\n    (remove-duplicates\n     (while-collecting (dep)\n       (loop :for (option . arguments) :in (cddr defpackage-form) :do\n         (case option\n           ((:use :mix :reexport :use-reexport :mix-reexport)\n            (dolist (p arguments) (dep (string p))))\n           ((:import-from :shadowing-import-from)\n            (dep (string (first arguments))))\n           #+package-local-nicknames\n           ((:local-nicknames)\n            (loop :for (nil actual-package-name) :in arguments :do\n              (dep (string actual-package-name))))\n           ((:nicknames :documentation :shadow :export :intern :unintern :recycle))\n\n           ;;; SBCL extensions to defpackage relating to package locks.\n           ;; See https://www.sbcl.org/manual/#Implementation-Packages .\n           #+(or sbcl ecl) ;; MKCL too?\n           ((:lock)\n            ;; A :LOCK clause introduces no dependencies.\n            nil)\n           #+sbcl\n           ((:implement)\n            ;; A :IMPLEMENT clause introduces dependencies on the listed packages,\n            ;; as it's not meaningful to :IMPLEMENT a package which hasn't yet been defined.\n            (dolist (p arguments) (dep (string p))))\n\n           #+lispworks\n           ((:add-use-defaults) nil)\n\n           #+allegro\n           ((:implementation-packages :alternate-name :flat) nil)\n\n           ;; When encountering an unknown OPTION, signal a continuable error.\n           ;; We cannot in general know whether the unknown clause should introduce any dependencies,\n           ;; so we cannot do anything other than signal an error here,\n           ;; but users may know that certain extensions do not introduce dependencies,\n           ;; and may wish to manually continue building.\n           (otherwise (cerror \"Treat the unknown option as introducing no package dependencies\"\n                              'package-inferred-system-unknown-defpackage-option-error\n                              :system system\n                              :pathname pathname\n                              :option option\n                              :arguments arguments)))))\n     :from-end t :test 'equal))\n\n  (defun package-designator-name (package)\n    \"Normalize a package designator to a string\"\n    (etypecase package\n      (package (package-name package))\n      (string package)\n      (symbol (string package))))\n\n  (defun register-system-packages (system packages)\n    \"Register SYSTEM as providing PACKAGES.\"\n    (let ((name (or (eq system t) (coerce-name system))))\n      (dolist (p (ensure-list packages))\n        (setf (gethash (package-designator-name p) *package-inferred-systems*) name))))\n\n  (defun package-name-system (package-name)\n    \"Return the name of the SYSTEM providing PACKAGE-NAME, if such exists,\notherwise return a default system name computed from PACKAGE-NAME.\"\n    (check-type package-name string)\n    (or (gethash package-name *package-inferred-systems*)\n        (string-downcase package-name)))\n\n  ;; Given a file in package-inferred-system style, find its dependencies\n  (defun package-inferred-system-file-dependencies (file &optional system)\n    (if-let (defpackage-form (file-defpackage-form file))\n      (remove t (mapcar 'package-name-system (package-dependencies defpackage-form)))\n      (error 'package-inferred-system-missing-package-error :system system :pathname file)))\n\n  ;; Given package-inferred-system object, check whether its specification matches\n  ;; the provided parameters\n  (defun same-package-inferred-system-p (system name directory subpath around-compile dependencies)\n    (and (eq (type-of system) 'package-inferred-system)\n         (equal (component-name system) name)\n         (pathname-equal directory (component-pathname system))\n         (equal dependencies (component-sideway-dependencies system))\n         (equal around-compile (around-compile-hook system))\n         (let ((children (component-children system)))\n           (and (length=n-p children 1)\n                (let ((child (first children)))\n                  (and (eq (type-of child) 'cl-source-file)\n                       (equal (component-name child) \"lisp\")\n                       (and (slot-boundp child 'relative-pathname)\n                            (equal (slot-value child 'relative-pathname) subpath))))))))\n\n  ;; sysdef search function to push into *system-definition-search-functions*\n  (defun sysdef-package-inferred-system-search (system-name)\n  \"Takes SYSTEM-NAME and returns an initialized SYSTEM object, or NIL.  Made to be added to\n*SYSTEM-DEFINITION-SEARCH-FUNCTIONS*.\"\n    (let ((primary (primary-system-name system-name)))\n      ;; this function ONLY does something if the primary system name is NOT the same as\n      ;; SYSTEM-NAME.  It is used to find the systems with names that are relative to\n      ;; the primary system's name, and that are not explicitly specified in the system\n      ;; definition\n      (unless (equal primary system-name)\n        (let ((top (find-system primary nil)))\n          (when (typep top 'package-inferred-system)\n            (if-let (dir (component-pathname top))\n              (let* ((sub (subseq system-name (1+ (length primary))))\n                     (component-type (class-for-type top :file))\n                     (file-type (file-type (make-instance component-type)))\n                     (f (probe-file* (subpathname dir sub :type file-type)\n                                     :truename *resolve-symlinks*)))\n                (when (file-pathname-p f)\n                  (let ((dependencies (package-inferred-system-file-dependencies f system-name))\n                        (previous (registered-system system-name))\n                        (around-compile (around-compile-hook top)))\n                    (if (same-package-inferred-system-p previous system-name dir sub around-compile dependencies)\n                        previous\n                        (eval `(defsystem ,system-name\n                                 :class package-inferred-system\n                                 :default-component-class ,component-type\n                                 :source-file ,(system-source-file top)\n                                 :pathname ,dir\n                                 :depends-on ,dependencies\n                                 :around-compile ,around-compile\n                                 :components ((,component-type file-type :pathname ,sub)))))))))))))))\n\n(with-upgradability ()\n  (pushnew 'sysdef-package-inferred-system-search *system-definition-search-functions*)\n  (setf *system-definition-search-functions*\n        (remove (find-symbol* :sysdef-package-system-search :asdf/package-system nil)\n                *system-definition-search-functions*)))\n;;;; ---------------------------------------------------------------------------\n;;;; asdf-output-translations\n\n(uiop/package:define-package :asdf/output-translations\n  (:recycle :asdf/output-translations :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade)\n  (:export\n   #:*output-translations* #:*output-translations-parameter*\n   #:invalid-output-translation\n   #:output-translations #:output-translations-initialized-p\n   #:initialize-output-translations #:clear-output-translations\n   #:disable-output-translations #:ensure-output-translations\n   #:apply-output-translations\n   #:validate-output-translations-directive #:validate-output-translations-form\n   #:validate-output-translations-file #:validate-output-translations-directory\n   #:parse-output-translations-string #:wrapping-output-translations\n   #:user-output-translations-pathname #:system-output-translations-pathname\n   #:user-output-translations-directory-pathname #:system-output-translations-directory-pathname\n   #:environment-output-translations #:process-output-translations\n   #:compute-output-translations\n   #+abcl #:translate-jar-pathname\n   ))\n(in-package :asdf/output-translations)\n\n;; (setf output-translations) between 2.27 and 3.0.3 was using a defsetf macro\n;; for the sake of obsolete versions of GCL 2.6. Make sure it doesn't come to haunt us.\n(when-upgrading (:version \"3.1.2\") (fmakunbound '(setf output-translations)))\n\n(with-upgradability ()\n  (define-condition invalid-output-translation (invalid-configuration warning)\n    ((format :initform (compatfmt \"~@<Invalid asdf output-translation ~S~@[ in ~S~]~@{ ~@?~}~@:>\"))))\n\n  (defvar *output-translations* ()\n    \"Either NIL (for uninitialized), or a list of one element,\nsaid element itself being a sorted list of mappings.\nEach mapping is a pair of a source pathname and destination pathname,\nand the order is by decreasing length of namestring of the source pathname.\")\n\n  (defun output-translations ()\n    \"Return the configured output-translations, if any\"\n    (car *output-translations*))\n\n  ;; Set the output-translations, by sorting the provided new-value.\n  (defun set-output-translations (new-value)\n    (setf *output-translations*\n          (list\n           (stable-sort (copy-list new-value) #'>\n                        :key #'(lambda (x)\n                                 (etypecase (car x)\n                                   ((eql t) -1)\n                                   (pathname\n                                    (let ((directory\n                                           (normalize-pathname-directory-component\n                                            (pathname-directory (car x)))))\n                                      (if (listp directory) (length directory) 0))))))))\n    new-value)\n  (defun (setf output-translations) (new-value) (set-output-translations new-value))\n\n  (defun output-translations-initialized-p ()\n    \"Have the output-translations been initialized yet?\"\n    (and *output-translations* t))\n\n  (defun clear-output-translations ()\n    \"Undoes any initialization of the output translations.\"\n    (setf *output-translations* '())\n    (values))\n  (register-clear-configuration-hook 'clear-output-translations)\n\n\n  ;;; Validation of the configuration directives...\n\n  (defun validate-output-translations-directive (directive)\n    (or (member directive '(:enable-user-cache :disable-cache nil))\n        (and (consp directive)\n             (or (and (length=n-p directive 2)\n                      (or (and (eq (first directive) :include)\n                               (typep (second directive) '(or string pathname null)))\n                          (and (location-designator-p (first directive))\n                               (or (location-designator-p (second directive))\n                                   (location-function-p (second directive))))))\n                 (and (length=n-p directive 1)\n                      (location-designator-p (first directive)))))))\n\n  (defun validate-output-translations-form (form &key location)\n    (validate-configuration-form\n     form\n     :output-translations\n     'validate-output-translations-directive\n     :location location :invalid-form-reporter 'invalid-output-translation))\n\n  (defun validate-output-translations-file (file)\n    (validate-configuration-file\n     file 'validate-output-translations-form :description \"output translations\"))\n\n  (defun validate-output-translations-directory (directory)\n    (validate-configuration-directory\n     directory :output-translations 'validate-output-translations-directive\n               :invalid-form-reporter 'invalid-output-translation))\n\n\n  ;;; Parse the ASDF_OUTPUT_TRANSLATIONS environment variable and/or some file contents\n  (defun parse-output-translations-string (string &key location)\n    (cond\n      ((or (null string) (equal string \"\"))\n       '(:output-translations :inherit-configuration))\n      ((not (stringp string))\n       (error (compatfmt \"~@<Environment string isn't: ~3i~_~S~@:>\") string))\n      ((eql (char string 0) #\\\")\n       (parse-output-translations-string (read-from-string string) :location location))\n      ((eql (char string 0) #\\()\n       (validate-output-translations-form (read-from-string string) :location location))\n      (t\n       (loop\n         :with inherit = nil\n         :with directives = ()\n         :with start = 0\n         :with end = (length string)\n         :with source = nil\n         :with separator = (inter-directory-separator)\n         :for i = (or (position separator string :start start) end) :do\n           (let ((s (subseq string start i)))\n             (cond\n               (source\n                (push (list source (if (equal \"\" s) nil s)) directives)\n                (setf source nil))\n               ((equal \"\" s)\n                (when inherit\n                  (error (compatfmt \"~@<Only one inherited configuration allowed: ~3i~_~S~@:>\")\n                         string))\n                (setf inherit t)\n                (push :inherit-configuration directives))\n               (t\n                (setf source s)))\n             (setf start (1+ i))\n             (when (> start end)\n               (when source\n                 (error (compatfmt \"~@<Uneven number of components in source to destination mapping: ~3i~_~S~@:>\")\n                        string))\n               (unless inherit\n                 (push :ignore-inherited-configuration directives))\n               (return `(:output-translations ,@(nreverse directives)))))))))\n\n\n  ;; The default sources of configuration for output-translations\n  (defparameter* *default-output-translations*\n    '(environment-output-translations\n      user-output-translations-pathname\n      user-output-translations-directory-pathname\n      system-output-translations-pathname\n      system-output-translations-directory-pathname))\n\n  ;; Compulsory implementation-dependent wrapping for the translations:\n  ;; handle implementation-provided systems.\n  (defun wrapping-output-translations ()\n    `(:output-translations\n    ;; Some implementations have precompiled ASDF systems,\n    ;; so we must disable translations for implementation paths.\n      #+(or clasp #|clozure|# ecl mkcl sbcl)\n      ,@(let ((h (resolve-symlinks* (lisp-implementation-directory))))\n          (when h `(((,h ,*wild-path*) ()))))\n      #+mkcl (,(translate-logical-pathname \"CONTRIB:\") ())\n      ;; All-import, here is where we want user stuff to be:\n      :inherit-configuration\n      ;; These are for convenience, and can be overridden by the user:\n      #+abcl (#p\"/___jar___file___root___/**/*.*\" (:user-cache #p\"**/*.*\"))\n      #+abcl (#p\"jar:file:/**/*.jar!/**/*.*\" (:function translate-jar-pathname))\n      ;; We enable the user cache by default, and here is the place we do:\n      :enable-user-cache))\n\n  ;; Relative pathnames of output-translations configuration to XDG configuration directory\n  (defparameter *output-translations-file* (parse-unix-namestring \"common-lisp/asdf-output-translations.conf\"))\n  (defparameter *output-translations-directory* (parse-unix-namestring \"common-lisp/asdf-output-translations.conf.d/\"))\n\n  ;; Locating various configuration pathnames, depending on input or output intent.\n  (defun user-output-translations-pathname (&key (direction :input))\n    (xdg-config-pathname *output-translations-file* direction))\n  (defun system-output-translations-pathname (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *output-translations-file*)\n                         :direction direction))\n  (defun user-output-translations-directory-pathname (&key (direction :input))\n    (xdg-config-pathname *output-translations-directory* direction))\n  (defun system-output-translations-directory-pathname (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *output-translations-directory*)\n                         :direction direction))\n  (defun environment-output-translations ()\n    (getenv \"ASDF_OUTPUT_TRANSLATIONS\"))\n\n\n  ;;; Processing the configuration.\n\n  (defgeneric process-output-translations (spec &key inherit collect))\n\n  (defun inherit-output-translations (inherit &key collect)\n    (when inherit\n      (process-output-translations (first inherit) :collect collect :inherit (rest inherit))))\n\n  (defun process-output-translations-directive (directive &key inherit collect)\n    (if (atom directive)\n        (ecase directive\n          ((:enable-user-cache)\n           (process-output-translations-directive '(t :user-cache) :collect collect))\n          ((:disable-cache)\n           (process-output-translations-directive '(t t) :collect collect))\n          ((:inherit-configuration)\n           (inherit-output-translations inherit :collect collect))\n          ((:ignore-inherited-configuration :ignore-invalid-entries nil)\n           nil))\n        (let ((src (first directive))\n              (dst (second directive)))\n          (if (eq src :include)\n              (when dst\n                (process-output-translations (pathname dst) :inherit nil :collect collect))\n              (when src\n                (let ((trusrc (or (eql src t)\n                                  (let ((loc (resolve-location src :ensure-directory t :wilden t)))\n                                    (if (absolute-pathname-p loc) (resolve-symlinks* loc) loc)))))\n                  (cond\n                    ((location-function-p dst)\n                     (funcall collect\n                              (list trusrc (ensure-function (second dst)))))\n                    ((typep dst 'boolean)\n                     (funcall collect (list trusrc t)))\n                    (t\n                     (let* ((trudst (resolve-location dst :ensure-directory t :wilden t)))\n                       (funcall collect (list trudst t))\n                       (funcall collect (list trusrc trudst)))))))))))\n\n  (defmethod process-output-translations ((x symbol) &key\n                                                       (inherit *default-output-translations*)\n                                                       collect)\n    (process-output-translations (funcall x) :inherit inherit :collect collect))\n  (defmethod process-output-translations ((pathname pathname) &key inherit collect)\n    (cond\n      ((directory-pathname-p pathname)\n       (process-output-translations (validate-output-translations-directory pathname)\n                                    :inherit inherit :collect collect))\n      ((probe-file* pathname :truename *resolve-symlinks*)\n       (process-output-translations (validate-output-translations-file pathname)\n                                    :inherit inherit :collect collect))\n      (t\n       (inherit-output-translations inherit :collect collect))))\n  (defmethod process-output-translations ((string string) &key inherit collect)\n    (process-output-translations (parse-output-translations-string string)\n                                 :inherit inherit :collect collect))\n  (defmethod process-output-translations ((x null) &key inherit collect)\n    (inherit-output-translations inherit :collect collect))\n  (defmethod process-output-translations ((form cons) &key inherit collect)\n    (dolist (directive (cdr (validate-output-translations-form form)))\n      (process-output-translations-directive directive :inherit inherit :collect collect)))\n\n\n  ;;; Top-level entry-points to configure output-translations\n\n  (defun compute-output-translations (&optional parameter)\n    \"read the configuration, return it\"\n    (remove-duplicates\n     (while-collecting (c)\n       (inherit-output-translations\n        `(wrapping-output-translations ,parameter ,@*default-output-translations*) :collect #'c))\n     :test 'equal :from-end t))\n\n  ;; Saving the user-provided parameter to output-translations, if any,\n  ;; so we can recompute the translations after code upgrade.\n  (defvar *output-translations-parameter* nil)\n\n  ;; Main entry-point for users.\n  (defun initialize-output-translations (&optional (parameter *output-translations-parameter*))\n    \"read the configuration, initialize the internal configuration variable,\nreturn the configuration\"\n    (setf *output-translations-parameter* parameter\n          (output-translations) (compute-output-translations parameter)))\n\n  (defun disable-output-translations ()\n    \"Initialize output translations in a way that maps every file to itself,\neffectively disabling the output translation facility.\"\n    (initialize-output-translations\n     '(:output-translations :disable-cache :ignore-inherited-configuration)))\n\n  ;; checks an initial variable to see whether the state is initialized\n  ;; or cleared. In the former case, return current configuration; in\n  ;; the latter, initialize.  ASDF will call this function at the start\n  ;; of (asdf:find-system).\n  (defun ensure-output-translations ()\n    (if (output-translations-initialized-p)\n        (output-translations)\n        (initialize-output-translations)))\n\n\n  ;; Top-level entry-point to _use_ output-translations\n  (defun apply-output-translations (path)\n    (etypecase path\n      (logical-pathname\n       path)\n      ((or pathname string)\n       (ensure-output-translations)\n       (loop :with p = (resolve-symlinks* path)\n             :for (source destination) :in (car *output-translations*)\n             :for root = (when (or (eq source t)\n                                   (and (pathnamep source)\n                                        (not (absolute-pathname-p source))))\n                           (pathname-root p))\n             :for absolute-source = (cond\n                                      ((eq source t) (wilden root))\n                                      (root (merge-pathnames* source root))\n                                      (t source))\n             :when (or (eq source t) (pathname-match-p p absolute-source))\n               :return (translate-pathname* p absolute-source destination root source)\n             :finally (return p)))))\n\n\n  ;; Hook into uiop's output-translation mechanism\n  #-cormanlisp\n  (setf *output-translation-function* 'apply-output-translations)\n\n\n  ;;; Implementation-dependent hacks\n  #+abcl ;; ABCL: make it possible to use systems provided in the ABCL jar.\n  (defun translate-jar-pathname (source wildcard)\n    (declare (ignore wildcard))\n    (flet ((normalize-device (pathname)\n             (if (find :windows *features*)\n                 pathname\n                 (make-pathname :defaults pathname :device :unspecific))))\n      (let* ((jar\n               (pathname (first (pathname-device source))))\n             (target-root-directory-namestring\n               (format nil \"/___jar___file___root___/~@[~A/~]\"\n                       (and (find :windows *features*)\n                            (pathname-device jar))))\n             (relative-source\n               (relativize-pathname-directory source))\n             (relative-jar\n               (relativize-pathname-directory (ensure-directory-pathname jar)))\n             (target-root-directory\n               (normalize-device\n                (pathname-directory-pathname\n                 (parse-namestring target-root-directory-namestring))))\n             (target-root\n               (merge-pathnames* relative-jar target-root-directory))\n             (target\n               (merge-pathnames* relative-source target-root)))\n        (normalize-device (apply-output-translations target))))))\n\n;;;; -----------------------------------------------------------------\n;;;; Source Registry Configuration, by Francois-Rene Rideau\n;;;; See the Manual and https://bugs.launchpad.net/asdf/+bug/485918\n\n(uiop/package:define-package :asdf/source-registry\n  ;; NB: asdf/find-system allows upgrade from <=3.2.1 that have initialize-source-registry there\n  (:recycle :asdf/source-registry :asdf/find-system :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/system :asdf/system-registry)\n  (:export\n   #:*source-registry-parameter* #:*default-source-registries*\n   #:invalid-source-registry\n   #:source-registry-initialized-p\n   #:initialize-source-registry #:clear-source-registry #:*source-registry*\n   #:ensure-source-registry #:*source-registry-parameter*\n   #:*default-source-registry-exclusions* #:*source-registry-exclusions*\n   #:*wild-asd* #:directory-asd-files #:register-asd-directory\n   #:*recurse-beyond-asds* #:collect-asds-in-directory #:collect-sub*directories-asd-files\n   #:validate-source-registry-directive #:validate-source-registry-form\n   #:validate-source-registry-file #:validate-source-registry-directory\n   #:parse-source-registry-string #:wrapping-source-registry\n   #:default-user-source-registry #:default-system-source-registry\n   #:user-source-registry #:system-source-registry\n   #:user-source-registry-directory #:system-source-registry-directory\n   #:environment-source-registry #:process-source-registry #:inherit-source-registry\n   #:compute-source-registry #:flatten-source-registry\n   #:sysdef-source-registry-search))\n(in-package :asdf/source-registry)\n\n(with-upgradability ()\n  (define-condition invalid-source-registry (invalid-configuration warning)\n    ((format :initform (compatfmt \"~@<Invalid source registry ~S~@[ in ~S~]~@{ ~@?~}~@:>\"))))\n\n  ;; Default list of directories under which the source-registry tree search won't recurse\n  (defvar *default-source-registry-exclusions*\n    '(;;-- Using ack 1.2 exclusions\n      \".bzr\" \".cdv\"\n      ;; \"~.dep\" \"~.dot\" \"~.nib\" \"~.plst\" ; we don't support ack wildcards\n      \".git\" \".hg\" \".pc\" \".svn\" \"CVS\" \"RCS\" \"SCCS\" \"_darcs\"\n      \"_sgbak\" \"autom4te.cache\" \"cover_db\" \"_build\"\n      ;;-- debian often builds stuff under the debian directory... BAD.\n      \"debian\"))\n\n  ;; Actual list of directories under which the source-registry tree search won't recurse\n  (defvar *source-registry-exclusions* *default-source-registry-exclusions*)\n\n  ;; The state of the source-registry after search in configured locations\n  (defvar *source-registry* nil\n    \"Either NIL (for uninitialized), or an equal hash-table, mapping\nsystem names to pathnames of .asd files\")\n\n  ;; Saving the user-provided parameter to the source-registry, if any,\n  ;; so we can recompute the source-registry after code upgrade.\n  (defvar *source-registry-parameter* nil)\n\n  (defun source-registry-initialized-p ()\n    (typep *source-registry* 'hash-table))\n\n  (defun clear-source-registry ()\n    \"Undoes any initialization of the source registry.\"\n    (setf *source-registry* nil)\n    (values))\n  (register-clear-configuration-hook 'clear-source-registry)\n\n  (defparameter *wild-asd*\n    (make-pathname :directory nil :name *wild* :type \"asd\" :version :newest))\n\n  (defun directory-asd-files (directory)\n    (directory-files directory *wild-asd*))\n\n  (defun collect-asds-in-directory (directory collect)\n    (let ((asds (directory-asd-files directory)))\n      (map () collect asds)\n      asds))\n\n  (defvar *recurse-beyond-asds* t\n    \"Should :tree entries of the source-registry recurse in subdirectories\nafter having found a .asd file? True by default.\")\n\n  ;; When walking down a filesystem tree, if in a directory there is a .cl-source-registry.cache,\n  ;; read its contents instead of further recursively querying the filesystem.\n  (defun process-source-registry-cache (directory collect)\n    (let ((cache (ignore-errors\n                  (safe-read-file-form (subpathname directory \".cl-source-registry.cache\")))))\n      (when (and (listp cache) (eq :source-registry-cache (first cache)))\n        (loop :for s :in (rest cache) :do (funcall collect (subpathname directory s)))\n        t)))\n\n  (defun collect-sub*directories-asd-files\n      (directory &key (exclude *default-source-registry-exclusions*) collect\n                   (recurse-beyond-asds *recurse-beyond-asds*) ignore-cache)\n    (let ((visited (make-hash-table :test 'equalp)))\n      (flet ((collectp (dir)\n               (unless (and (not ignore-cache) (process-source-registry-cache dir collect))\n                 (let ((asds (collect-asds-in-directory dir collect)))\n                   (or recurse-beyond-asds (not asds)))))\n             (recursep (x)                    ; x will be a directory pathname\n               (and\n                (not (member (car (last (pathname-directory x))) exclude :test #'equal))\n                (flet ((pathname-key (x)\n                         (namestring (truename* x))))\n                  (let ((visitedp (gethash (pathname-key x) visited)))\n                    (if visitedp nil\n                        (setf (gethash (pathname-key x) visited) t)))))))\n      (collect-sub*directories directory #'collectp #'recursep (constantly nil)))))\n\n\n  ;;; Validate the configuration forms\n\n  (defun validate-source-registry-directive (directive)\n    (or (member directive '(:default-registry))\n        (and (consp directive)\n             (let ((rest (rest directive)))\n               (case (first directive)\n                 ((:include :directory :tree)\n                  (and (length=n-p rest 1)\n                       (location-designator-p (first rest))))\n                 ((:exclude :also-exclude)\n                  (every #'stringp rest))\n                 ((:default-registry)\n                  (null rest)))))))\n\n  (defun validate-source-registry-form (form &key location)\n    (validate-configuration-form\n     form :source-registry 'validate-source-registry-directive\n          :location location :invalid-form-reporter 'invalid-source-registry))\n\n  (defun validate-source-registry-file (file)\n    (validate-configuration-file\n     file 'validate-source-registry-form :description \"a source registry\"))\n\n  (defun validate-source-registry-directory (directory)\n    (validate-configuration-directory\n     directory :source-registry 'validate-source-registry-directive\n               :invalid-form-reporter 'invalid-source-registry))\n\n\n  ;;; Parse the configuration string\n\n  (defun parse-source-registry-string (string &key location)\n    (cond\n      ((or (null string) (equal string \"\"))\n       '(:source-registry :inherit-configuration))\n      ((not (stringp string))\n       (error (compatfmt \"~@<Environment string isn't: ~3i~_~S~@:>\") string))\n      ((find (char string 0) \"\\\"(\")\n       (validate-source-registry-form (read-from-string string) :location location))\n      (t\n       (loop\n         :with inherit = nil\n         :with directives = ()\n         :with start = 0\n         :with end = (length string)\n         :with separator = (inter-directory-separator)\n         :for pos = (position separator string :start start) :do\n           (let ((s (subseq string start (or pos end))))\n             (flet ((check (dir)\n                      (unless (absolute-pathname-p dir)\n                        (error (compatfmt \"~@<source-registry string must specify absolute pathnames: ~3i~_~S~@:>\") string))\n                      dir))\n               (cond\n                 ((equal \"\" s) ; empty element: inherit\n                  (when inherit\n                    (error (compatfmt \"~@<Only one inherited configuration allowed: ~3i~_~S~@:>\")\n                           string))\n                  (setf inherit t)\n                  (push ':inherit-configuration directives))\n                 ((string-suffix-p s \"//\") ;; TODO: allow for doubling of separator even outside Unix?\n                  (push `(:tree ,(check (subseq s 0 (- (length s) 2)))) directives))\n                 (t\n                  (push `(:directory ,(check s)) directives))))\n             (cond\n               (pos\n                (setf start (1+ pos)))\n               (t\n                (unless inherit\n                  (push '(:ignore-inherited-configuration) directives))\n                (return `(:source-registry ,@(nreverse directives))))))))))\n\n  (defun register-asd-directory (directory &key recurse exclude collect)\n    (if (not recurse)\n        (collect-asds-in-directory directory collect)\n        (collect-sub*directories-asd-files\n         directory :exclude exclude :collect collect)))\n\n  (defparameter* *default-source-registries*\n    '(environment-source-registry\n      user-source-registry\n      user-source-registry-directory\n      default-user-source-registry\n      system-source-registry\n      system-source-registry-directory\n      default-system-source-registry)\n    \"List of default source registries\" \"3.1.0.102\")\n\n  (defparameter *source-registry-file* (parse-unix-namestring \"common-lisp/source-registry.conf\"))\n  (defparameter *source-registry-directory* (parse-unix-namestring \"common-lisp/source-registry.conf.d/\"))\n\n  (defun wrapping-source-registry ()\n    `(:source-registry\n      #+(or clasp ecl sbcl) (:tree ,(resolve-symlinks* (lisp-implementation-directory)))\n      :inherit-configuration\n      #+mkcl (:tree ,(translate-logical-pathname \"SYS:\"))\n      #+cmucl (:tree #p\"modules:\")\n      #+scl (:tree #p\"file://modules/\")))\n  (defun default-user-source-registry ()\n    `(:source-registry\n      (:tree (:home \"common-lisp/\"))\n      #+sbcl (:directory (:home \".sbcl/systems/\"))\n      (:directory ,(xdg-data-home \"common-lisp/systems/\"))\n      (:tree ,(xdg-data-home \"common-lisp/source/\"))\n      :inherit-configuration))\n  (defun default-system-source-registry ()\n    `(:source-registry\n      ,@(loop :for dir :in (xdg-data-dirs \"common-lisp/\")\n              :collect `(:directory (,dir \"systems/\"))\n              :collect `(:tree (,dir \"source/\")))\n      :inherit-configuration))\n  (defun user-source-registry (&key (direction :input))\n    (xdg-config-pathname *source-registry-file* direction))\n  (defun system-source-registry (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *source-registry-file*)\n                         :direction direction))\n  (defun user-source-registry-directory (&key (direction :input))\n    (xdg-config-pathname *source-registry-directory* direction))\n  (defun system-source-registry-directory (&key (direction :input))\n    (find-preferred-file (system-config-pathnames *source-registry-directory*)\n                         :direction direction))\n  (defun environment-source-registry ()\n    (getenv \"CL_SOURCE_REGISTRY\"))\n\n\n  ;;; Process the source-registry configuration\n\n  (defgeneric process-source-registry (spec &key inherit register))\n\n  (defun inherit-source-registry (inherit &key register)\n    (when inherit\n      (process-source-registry (first inherit) :register register :inherit (rest inherit))))\n\n  (defun process-source-registry-directive (directive &key inherit register)\n    (destructuring-bind (kw &rest rest) (if (consp directive) directive (list directive))\n      (ecase kw\n        ((:include)\n         (destructuring-bind (pathname) rest\n           (process-source-registry (resolve-location pathname) :inherit nil :register register)))\n        ((:directory)\n         (destructuring-bind (pathname) rest\n           (when pathname\n             (funcall register (resolve-location pathname :ensure-directory t)))))\n        ((:tree)\n         (destructuring-bind (pathname) rest\n           (when pathname\n             (funcall register (resolve-location pathname :ensure-directory t)\n                      :recurse t :exclude *source-registry-exclusions*))))\n        ((:exclude)\n         (setf *source-registry-exclusions* rest))\n        ((:also-exclude)\n         (appendf *source-registry-exclusions* rest))\n        ((:default-registry)\n         (inherit-source-registry\n          '(default-user-source-registry default-system-source-registry) :register register))\n        ((:inherit-configuration)\n         (inherit-source-registry inherit :register register))\n        ((:ignore-inherited-configuration)\n         nil)))\n    nil)\n\n  (defmethod process-source-registry ((x symbol) &key inherit register)\n    (process-source-registry (funcall x) :inherit inherit :register register))\n  (defmethod process-source-registry ((pathname pathname) &key inherit register)\n    (cond\n      ((directory-pathname-p pathname)\n       (let ((*here-directory* (resolve-symlinks* pathname)))\n         (process-source-registry (validate-source-registry-directory pathname)\n                                  :inherit inherit :register register)))\n      ((probe-file* pathname :truename *resolve-symlinks*)\n       (let ((*here-directory* (pathname-directory-pathname pathname)))\n         (process-source-registry (validate-source-registry-file pathname)\n                                  :inherit inherit :register register)))\n      (t\n       (inherit-source-registry inherit :register register))))\n  (defmethod process-source-registry ((string string) &key inherit register)\n    (process-source-registry (parse-source-registry-string string)\n                             :inherit inherit :register register))\n  (defmethod process-source-registry ((x null) &key inherit register)\n    (inherit-source-registry inherit :register register))\n  (defmethod process-source-registry ((form cons) &key inherit register)\n    (let ((*source-registry-exclusions* *default-source-registry-exclusions*))\n      (dolist (directive (cdr (validate-source-registry-form form)))\n        (process-source-registry-directive directive :inherit inherit :register register))))\n\n\n  ;; Flatten the user-provided configuration into an ordered list of directories and trees\n  (defun flatten-source-registry (&optional (parameter *source-registry-parameter*))\n    (remove-duplicates\n     (while-collecting (collect)\n       (with-pathname-defaults () ;; be location-independent\n         (inherit-source-registry\n          `(wrapping-source-registry\n            ,parameter\n            ,@*default-source-registries*)\n          :register #'(lambda (directory &key recurse exclude)\n                        (collect (list directory :recurse recurse :exclude exclude))))))\n     :test 'equal :from-end t))\n\n  ;; MAYBE: move this utility function to uiop/pathname and export it?\n  (defun pathname-directory-depth (p)\n    (length (normalize-pathname-directory-component (pathname-directory p))))\n\n  (defun preferred-source-path-p (x y)\n    \"Return T iff X is to be preferred over Y as a source path\"\n    (let ((lx (pathname-directory-depth x))\n          (ly (pathname-directory-depth y)))\n      (or (< lx ly)\n          (and (= lx ly)\n               (string< (namestring x)\n                        (namestring y))))))\n\n  ;; Will read the configuration and initialize all internal variables.\n  (defun compute-source-registry (&optional (parameter *source-registry-parameter*)\n                                    (registry *source-registry*))\n    (dolist (entry (flatten-source-registry parameter))\n      (destructuring-bind (directory &key recurse exclude) entry\n        (let* ((h (make-hash-table :test 'equal))) ; table to detect duplicates\n          (register-asd-directory\n           directory :recurse recurse :exclude exclude :collect\n           #'(lambda (asd)\n               (let* ((name (pathname-name asd))\n                      (name (if (typep asd 'logical-pathname)\n                                ;; logical pathnames are upper-case,\n                                ;; at least in the CLHS and on SBCL,\n                                ;; yet (coerce-name :foo) is lower-case.\n                                ;; won't work well with (load-system \"Foo\")\n                                ;; instead of (load-system 'foo)\n                                (string-downcase name)\n                                name)))\n                 (unless (gethash name registry) ; already shadowed by something else\n                   (if-let (old (gethash name h))\n                     ;; If the name appears multiple times,\n                     ;; prefer the one with the shallowest directory,\n                     ;; or if they have same depth, compare unix-namestring with string<\n                     (multiple-value-bind (better worse)\n                         (if (preferred-source-path-p asd old)\n                             (progn (setf (gethash name h) asd) (values asd old))\n                             (values old asd))\n                       (when *verbose-out*\n                         (warn (compatfmt \"~@<In source-registry entry ~A~@[/~*~] ~\n                                              found several entries for ~A - picking ~S over ~S~:>\")\n                               directory recurse name better worse)))\n                     (setf (gethash name h) asd))))))\n          (maphash #'(lambda (k v) (setf (gethash k registry) v)) h))))\n    (values))\n\n  (defun initialize-source-registry (&optional (parameter *source-registry-parameter*))\n    ;; Record the parameter used to configure the registry\n    (setf *source-registry-parameter* parameter)\n    ;; Clear the previous registry database:\n    (setf *source-registry* (make-hash-table :test 'equal))\n    ;; Do it!\n    (compute-source-registry parameter))\n\n  ;; Checks an initial variable to see whether the state is initialized\n  ;; or cleared. In the former case, return current configuration; in\n  ;; the latter, initialize.  ASDF will call this function at the start\n  ;; of (asdf:find-system) to make sure the source registry is initialized.\n  ;; However, it will do so *without* a parameter, at which point it\n  ;; will be too late to provide a parameter to this function, though\n  ;; you may override the configuration explicitly by calling\n  ;; initialize-source-registry directly with your parameter.\n  (defun ensure-source-registry (&optional parameter)\n    (unless (source-registry-initialized-p)\n      (initialize-source-registry parameter))\n    (values))\n\n  (defun sysdef-source-registry-search (system)\n    (ensure-source-registry)\n    (values (gethash (primary-system-name system) *source-registry*))))\n\n\n;;;; -------------------------------------------------------------------------\n;;; Internal hacks for backward-compatibility\n\n(uiop/package:define-package :asdf/backward-internals\n  (:recycle :asdf/backward-internals :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/find-system)\n  (:export #:load-sysdef))\n(in-package :asdf/backward-internals)\n\n(with-asdf-deprecation (:style-warning \"3.2\" :warning \"3.4\")\n  (defun load-sysdef (name pathname)\n    (declare (ignore name pathname))\n    ;; Needed for backward compatibility with swank-asdf from SLIME 2015-12-01 or older.\n    (error \"Use asdf:load-asd instead of asdf::load-sysdef\")))\n;;;; -------------------------------------------------------------------------\n;;; Backward-compatible interfaces\n\n(uiop/package:define-package :asdf/backward-interface\n  (:recycle :asdf/backward-interface :asdf)\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n   :asdf/component :asdf/system :asdf/system-registry :asdf/operation :asdf/action\n   :asdf/lisp-action :asdf/plan :asdf/operate\n   :asdf/find-system :asdf/parse-defsystem :asdf/output-translations :asdf/bundle)\n  (:export\n   #:*asdf-verbose*\n   #:operation-error #:compile-error #:compile-failed #:compile-warned\n   #:error-component #:error-operation #:traverse\n   #:component-load-dependencies\n   #:enable-asdf-binary-locations-compatibility\n   #:operation-on-failure #:operation-on-warnings #:on-failure #:on-warnings\n   #:component-property\n   #:run-shell-command\n   #:system-definition-pathname #:system-registered-p #:require-system\n   #:explain\n   #+ecl #:make-build))\n(in-package :asdf/backward-interface)\n\n;; NB: the warning status of these functions may have to be distinguished later,\n;; as some get removed faster than the others in client code.\n(with-asdf-deprecation (:style-warning \"3.2\" :warning \"3.4\")\n\n  ;; These conditions from ASDF 1 and 2 are used by many packages in Quicklisp;\n  ;; but ASDF3 replaced them with somewhat different variants of uiop:compile-condition\n  ;; that do not involve ASDF actions.\n  ;; TODO: find the offenders and stop them.\n  (progn\n    (define-condition operation-error (error) ;; Bad, backward-compatible name\n      ;; Used by SBCL, cffi-tests, clsql-mysql, clsql-uffi, qt, elephant, uffi-tests, sb-grovel\n      ((component :reader error-component :initarg :component)\n       (operation :reader error-operation :initarg :operation))\n      (:report (lambda (c s)\n                 (format s (compatfmt \"~@<~A while invoking ~A on ~A~@:>\")\n                         (type-of c) (error-operation c) (error-component c)))))\n    (define-condition compile-error (operation-error) ())\n    (define-condition compile-failed (compile-error) ())\n    (define-condition compile-warned (compile-error) ()))\n\n  ;; In Quicklisp 2015-05, still used by lisp-executable, staple, repl-utilities, cffi\n  (defun component-load-dependencies (component) ;; from ASDF 2.000 to 2.26\n    \"DEPRECATED. Please use COMPONENT-SIDEWAY-DEPENDENCIES instead; or better,\ndefine your operations with proper use of SIDEWAY-OPERATION, SELFWARD-OPERATION,\nor define methods on PREPARE-OP, etc.\"\n    ;; Old deprecated name for the same thing. Please update your software.\n    (component-sideway-dependencies component))\n\n  ;; These old interfaces from ASDF1 have never been very meaningful\n  ;; but are still used in obscure places.\n  ;; In Quicklisp 2015-05, still used by cl-protobufs and clx.\n  (defgeneric operation-on-warnings (operation)\n    (:documentation \"DEPRECATED. Please use UIOP:*COMPILE-FILE-WARNINGS-BEHAVIOUR* instead.\"))\n  (defgeneric operation-on-failure (operation)\n    (:documentation \"DEPRECATED. Please use UIOP:*COMPILE-FILE-FAILURE-BEHAVIOUR* instead.\"))\n  (defgeneric (setf operation-on-warnings) (x operation)\n    (:documentation \"DEPRECATED. Please SETF UIOP:*COMPILE-FILE-WARNINGS-BEHAVIOUR* instead.\"))\n  (defgeneric (setf operation-on-failure) (x operation)\n    (:documentation \"DEPRECATED. Please SETF UIOP:*COMPILE-FILE-FAILURE-BEHAVIOUR* instead.\"))\n  (progn\n    (defmethod operation-on-warnings ((o operation))\n      *compile-file-warnings-behaviour*)\n    (defmethod operation-on-failure ((o operation))\n      *compile-file-failure-behaviour*)\n    (defmethod (setf operation-on-warnings) (x (o operation))\n      (setf *compile-file-warnings-behaviour* x))\n    (defmethod (setf operation-on-failure) (x (o operation))\n      (setf *compile-file-failure-behaviour* x)))\n\n  ;; Quicklisp 2015-05: Still used by SLIME's swank-asdf (!), common-lisp-stat,\n  ;; js-parser, osicat, babel, staple, weblocks, cl-png, plain-odbc, autoproject,\n  ;; cl-blapack, com.informatimago, cells-gtk3, asdf-dependency-grovel,\n  ;; cl-glfw, cffi, jwacs, montezuma\n  (defun system-definition-pathname (x)\n    ;; As of 2.014.8, we mean to make this function obsolete,\n    ;; but that won't happen until all clients have been updated.\n    \"DEPRECATED. This function used to expose ASDF internals with subtle\ndifferences with respect to user expectations, that have been refactored\naway since. We recommend you use ASDF:SYSTEM-SOURCE-FILE instead for a\nmostly compatible replacement that we're supporting, or even\nASDF:SYSTEM-SOURCE-DIRECTORY or ASDF:SYSTEM-RELATIVE-PATHNAME\nif that's whay you mean.\" ;;)\n    (system-source-file x))\n\n  ;; TRAVERSE is the function used to compute a plan in ASDF 1 and 2.\n  ;; It was never officially exposed but some people still used it.\n  (defgeneric traverse (operation component &key &allow-other-keys)\n    (:documentation\n     \"DEPRECATED. Use MAKE-PLAN and PLAN-ACTIONS, or REQUIRED-COMPONENTS,\nor some other supported interface instead.\n\nGenerate and return a plan for performing OPERATION on COMPONENT.\n\nThe plan returned is a list of dotted-pairs. Each pair is the CONS\nof ASDF operation object and a COMPONENT object. The pairs will be\nprocessed in order by OPERATE.\"))\n  (progn\n    (define-convenience-action-methods traverse (operation component &key)))\n  (defmethod traverse ((o operation) (c component) &rest keys &key plan-class &allow-other-keys)\n    (plan-actions (apply 'make-plan plan-class o c keys)))\n\n\n  ;; ASDF-Binary-Locations compatibility\n  ;; This remains supported for legacy user, but not recommended for new users.\n  ;; We suspect there are no more legacy users in 2016.\n  (defun enable-asdf-binary-locations-compatibility\n      (&key\n         (centralize-lisp-binaries nil)\n         (default-toplevel-directory\n             ;; Use \".cache/common-lisp/\" instead ???\n             (subpathname (user-homedir-pathname) \".fasls/\"))\n         (include-per-user-information nil)\n         (map-all-source-files (or #+(or clasp clisp ecl mkcl) t nil))\n         (source-to-target-mappings nil)\n         (file-types `(,(compile-file-type)\n                        \"build-report\"\n                        #+clasp (compile-file-type :output-type :object)\n                        #+ecl (compile-file-type :type :object)\n                        #+mkcl (compile-file-type :fasl-p nil)\n                        #+clisp \"lib\" #+sbcl \"cfasl\"\n                        #+sbcl \"sbcl-warnings\" #+clozure \"ccl-warnings\")))\n    \"DEPRECATED. Use asdf-output-translations instead.\"\n    #+(or clasp clisp ecl mkcl)\n    (when (null map-all-source-files)\n      (error \"asdf:enable-asdf-binary-locations-compatibility doesn't support :map-all-source-files nil on CLISP, ECL and MKCL\"))\n    (let* ((patterns (if map-all-source-files (list *wild-file*)\n                         (loop :for type :in file-types\n                           :collect (make-pathname :type type :defaults *wild-file*))))\n           (destination-directory\n            (if centralize-lisp-binaries\n                `(,default-toplevel-directory\n                     ,@(when include-per-user-information\n                             (cdr (pathname-directory (user-homedir-pathname))))\n                     :implementation ,*wild-inferiors*)\n                `(:root ,*wild-inferiors* :implementation))))\n      (initialize-output-translations\n       `(:output-translations\n         ,@source-to-target-mappings\n         #+abcl (#p\"jar:file:/**/*.jar!/**/*.*\" (:function translate-jar-pathname))\n         #+abcl (#p\"/___jar___file___root___/**/*.*\" (,@destination-directory))\n         ,@(loop :for pattern :in patterns\n             :collect `((:root ,*wild-inferiors* ,pattern)\n                        (,@destination-directory ,pattern)))\n         (t t)\n         :ignore-inherited-configuration))))\n  (progn\n    (defmethod operate :before (operation-class system &rest args &key &allow-other-keys)\n      (declare (ignore operation-class system args))\n      (when (find-symbol* '#:output-files-for-system-and-operation :asdf nil)\n        (error \"ASDF 2 is not compatible with ASDF-BINARY-LOCATIONS, which you are using.\nASDF 2 now achieves the same purpose with its builtin ASDF-OUTPUT-TRANSLATIONS,\nwhich should be easier to configure. Please stop using ASDF-BINARY-LOCATIONS,\nand instead use ASDF-OUTPUT-TRANSLATIONS. See the ASDF manual for details.\nIn case you insist on preserving your previous A-B-L configuration, but\ndo not know how to achieve the same effect with A-O-T, you may use function\nASDF:ENABLE-ASDF-BINARY-LOCATIONS-COMPATIBILITY as documented in the manual;\ncall that function where you would otherwise have loaded and configured A-B-L.\"))))\n\n\n  ;; run-shell-command from ASDF 2, lightly fixed from ASDF 1, copied from MK-DEFSYSTEM. Die!\n  (defun run-shell-command (control-string &rest args)\n    \"PLEASE DO NOT USE. This function is not just DEPRECATED, but also dysfunctional.\nPlease use UIOP:RUN-PROGRAM instead.\"\n    #-(and ecl os-windows)\n    (let ((command (apply 'format nil control-string args)))\n      (asdf-message \"; $ ~A~%\" command)\n      (let ((exit-code\n             (ignore-errors\n               (nth-value 2 (run-program command :force-shell t :ignore-error-status t\n                                         :output *verbose-out*)))))\n        (typecase exit-code\n          ((integer 0 255) exit-code)\n          (t 255))))\n    #+(and ecl os-windows)\n    (not-implemented-error \"run-shell-command\" \"for ECL on Windows.\"))\n\n  ;; HOW do we get rid of variables??? With a symbol-macro that issues a warning?\n  ;; In Quicklisp 2015-05, cl-protobufs still uses it, but that should be fixed in next version.\n  (progn\n    (defvar *asdf-verbose* nil)) ;; backward-compatibility with ASDF2 only. Unused.\n\n  ;; Do NOT use in new code. NOT SUPPORTED.\n  ;; NB: When this goes away, remove the slot PROPERTY in COMPONENT.\n  ;; In Quicklisp 2014-05, it's still used by yaclml, amazon-ecs, blackthorn-engine, cl-tidy.\n  ;; See TODO for further cleanups required before to get rid of it.\n  (defgeneric component-property (component property))\n  (defgeneric (setf component-property) (new-value component property))\n\n  (defmethod component-property ((c component) property)\n    (cdr (assoc property (slot-value c 'properties) :test #'equal)))\n\n  (defmethod (setf component-property) (new-value (c component) property)\n    (let ((a (assoc property (slot-value c 'properties) :test #'equal)))\n      (if a\n          (setf (cdr a) new-value)\n          (setf (slot-value c 'properties)\n                (acons property new-value (slot-value c 'properties)))))\n    new-value)\n\n\n  ;; This method survives from ASDF 1, but really it is superseded by action-description.\n  (defgeneric explain (operation component)\n    (:documentation \"Display a message describing an action.\n\nDEPRECATED. Use ASDF:ACTION-DESCRIPTION and/or ASDF::FORMAT-ACTION instead.\"))\n  (progn\n    (define-convenience-action-methods explain (operation component)))\n  (defmethod explain ((o operation) (c component))\n    (asdf-message (compatfmt \"~&~@<; ~@;~A~:>~%\") (action-description o c))))\n\n(with-asdf-deprecation (:style-warning \"3.3\")\n  (defun system-registered-p (name)\n    \"DEPRECATED. Return a generalized boolean that is true if a system of given NAME was registered already.\nNAME is a system designator, to be normalized by COERCE-NAME.\nThe value returned if true is a pair of a timestamp and a system object.\"\n    (if-let (system (registered-system name))\n      (cons (if-let (primary-system (registered-system (primary-system-name name)))\n              (component-operation-time 'define-op primary-system))\n            system)))\n\n  (defun require-system (system &rest keys &key &allow-other-keys)\n    \"Ensure the specified SYSTEM is loaded, passing the KEYS to OPERATE, but do not update the\nsystem or its dependencies if it has already been loaded.\"\n    (declare (ignore keys))\n    (unless (component-loaded-p system)\n      (load-system system))))\n\n;;; This function is for backward compatibility with ECL only.\n#+ecl\n(with-asdf-deprecation (:style-warning \"3.2\" :warning \"9999\")\n  (defun make-build (system &rest args\n                     &key (monolithic nil) (type :fasl) (move-here nil move-here-p)\n                       prologue-code epilogue-code no-uiop\n                       prefix-lisp-object-files postfix-lisp-object-files extra-object-files\n                       &allow-other-keys)\n    (let* ((operation (asdf/bundle::select-bundle-operation type monolithic))\n           (move-here-path (if (and move-here\n                                    (typep move-here '(or pathname string)))\n                               (ensure-pathname move-here :namestring :lisp :ensure-directory t)\n                               (system-relative-pathname system \"asdf-output/\")))\n           (extra-build-args (remove-plist-keys\n                              '(:monolithic :type :move-here\n                                :prologue-code :epilogue-code :no-uiop\n                                :prefix-lisp-object-files :postfix-lisp-object-files\n                                :extra-object-files)\n                              args))\n           (build-system (if (subtypep operation 'image-op)\n                             (eval `(defsystem \"asdf.make-build\"\n                                      :class program-system\n                                      :source-file nil\n                                      :pathname ,(system-source-directory system)\n                                      :build-operation ,operation\n                                      :build-pathname ,(subpathname move-here-path\n                                                                    (file-namestring (first (output-files operation system))))\n                                      :depends-on (,(coerce-name system))\n                                      :prologue-code ,prologue-code\n                                      :epilogue-code ,epilogue-code\n                                      :no-uiop ,no-uiop\n                                      :prefix-lisp-object-files ,prefix-lisp-object-files\n                                      :postfix-lisp-object-files ,postfix-lisp-object-files\n                                      :extra-object-files ,extra-object-files\n                                      :extra-build-args ,extra-build-args))\n                             system))\n           (files (output-files operation build-system)))\n      (operate operation build-system)\n      (if (or move-here\n              (and (null move-here-p) (member operation '(program-op image-op))))\n          (loop :with dest-path = (resolve-symlinks* (ensure-directories-exist move-here-path))\n            :for f :in files\n            :for new-f = (make-pathname :name (pathname-name f)\n                                        :type (pathname-type f)\n                                        :defaults dest-path)\n            :do (rename-file-overwriting-target f new-f)\n            :collect new-f)\n          files))))\n;;;; ---------------------------------------------------------------------------\n;;;; Handle ASDF package upgrade, including implementation-dependent magic.\n\n(uiop/package:define-package :asdf/interface\n  (:nicknames :asdf :asdf-utilities)\n  (:recycle :asdf/interface :asdf)\n  (:unintern\n   #:loaded-systems ; makes for annoying SLIME completion\n   #:output-files-for-system-and-operation) ; ASDF-BINARY-LOCATION function we use to detect ABL\n  (:use :uiop/common-lisp :uiop :asdf/upgrade :asdf/session\n   :asdf/component :asdf/system :asdf/system-registry :asdf/find-component\n   :asdf/operation :asdf/action :asdf/lisp-action\n   :asdf/output-translations :asdf/source-registry\n   :asdf/forcing :asdf/plan :asdf/operate :asdf/find-system :asdf/parse-defsystem\n   :asdf/bundle :asdf/concatenate-source\n   :asdf/backward-internals :asdf/backward-interface :asdf/package-inferred-system)\n  ;; Note: (1) we are NOT automatically reexporting everything from previous packages.\n  ;; (2) we only reexport UIOP functionality when backward-compatibility requires it.\n  (:export\n   #:defsystem #:find-system #:load-asd #:locate-system #:coerce-name\n   #:primary-system-name #:primary-system-p\n   #:oos #:operate #:make-plan #:perform-plan #:sequential-plan\n   #:system-definition-pathname\n   #:search-for-system-definition #:find-component #:component-find-path\n   #:compile-system #:load-system #:load-systems #:load-systems*\n   #:require-system #:test-system #:clear-system\n   #:operation #:make-operation #:find-operation\n   #:upward-operation #:downward-operation #:sideway-operation #:selfward-operation\n                      #:non-propagating-operation\n   #:build-op #:make\n   #:load-op #:prepare-op #:compile-op\n   #:prepare-source-op #:load-source-op #:test-op #:define-op\n   #:feature #:version #:version-satisfies #:upgrade-asdf\n   #:implementation-identifier #:implementation-type #:hostname\n   #:component-depends-on ; backward-compatible name rather than action-depends-on\n   #:input-files #:additional-input-files\n   #:output-files #:output-file #:perform #:perform-with-restarts\n   #:operation-done-p #:explain #:action-description #:component-sideway-dependencies\n   #:needed-in-image-p\n   #:bundle-op #:monolithic-bundle-op #:precompiled-system #:compiled-file #:bundle-system\n   #:program-system\n   #:basic-compile-bundle-op #:prepare-bundle-op\n   #:compile-bundle-op #:load-bundle-op #:monolithic-compile-bundle-op #:monolithic-load-bundle-op\n   #:lib-op #:dll-op #:deliver-asd-op #:program-op #:image-op\n   #:monolithic-lib-op #:monolithic-dll-op #:monolithic-deliver-asd-op\n   #:concatenate-source-op\n   #:load-concatenated-source-op\n   #:compile-concatenated-source-op\n   #:load-compiled-concatenated-source-op\n   #:monolithic-concatenate-source-op\n   #:monolithic-load-concatenated-source-op\n   #:monolithic-compile-concatenated-source-op\n   #:monolithic-load-compiled-concatenated-source-op\n   #:operation-monolithic-p\n   #:required-components\n   #:component-loaded-p\n   #:component #:parent-component #:child-component #:system #:module\n   #:file-component #:source-file #:c-source-file #:java-source-file\n   #:cl-source-file #:cl-source-file.cl #:cl-source-file.lsp\n   #:static-file #:doc-file #:html-file\n   #:file-type #:source-file-type\n   #:register-preloaded-system #:sysdef-preloaded-system-search\n   #:register-immutable-system #:sysdef-immutable-system-search\n   #:package-inferred-system #:register-system-packages\n   #:component-children\n   #:component-children-by-name\n   #:component-pathname\n   #:component-relative-pathname\n   #:component-name\n   #:component-version\n   #:component-parent\n   #:component-system\n   #:component-encoding\n   #:component-external-format\n   #:system-description\n   #:system-long-description\n   #:system-author\n   #:system-maintainer\n   #:system-license\n   #:system-licence\n   #:system-version\n   #:system-source-file\n   #:system-source-directory\n   #:system-relative-pathname\n   #:system-homepage\n   #:system-mailto\n   #:system-bug-tracker\n   #:system-long-name\n   #:system-source-control\n   #:map-systems\n   #:system-defsystem-depends-on\n   #:system-depends-on\n   #:system-weakly-depends-on\n   #:*system-definition-search-functions*   ; variables\n   #:*central-registry*\n   #:*compile-file-warnings-behaviour*\n   #:*compile-file-failure-behaviour*\n   #:*resolve-symlinks*\n   #:*verbose-out*\n   #:asdf-version\n   #:compile-condition #:compile-file-error #:compile-warned-error #:compile-failed-error\n   #:compile-warned-warning #:compile-failed-warning\n   #:error-name\n   #:error-pathname\n   #:load-system-definition-error\n   #:error-component #:error-operation\n   #:system-definition-error\n   #:missing-component\n   #:missing-component-of-version\n   #:missing-dependency\n   #:missing-dependency-of-version\n   #:circular-dependency        ; errors\n   #:duplicate-names #:non-toplevel-system #:non-system-system #:bad-system-name #:system-out-of-date\n   #:package-inferred-system-missing-package-error\n   #:operation-definition-warning #:operation-definition-error\n   #:try-recompiling ; restarts\n   #:retry\n   #:accept\n   #:coerce-entry-to-directory\n   #:remove-entry-from-registry\n   #:clear-configuration-and-retry\n   #:*encoding-detection-hook*\n   #:*encoding-external-format-hook*\n   #:*default-encoding*\n   #:*utf-8-external-format*\n   #:clear-configuration\n   #:*output-translations-parameter*\n   #:initialize-output-translations\n   #:disable-output-translations\n   #:clear-output-translations\n   #:ensure-output-translations\n   #:apply-output-translations\n   #:compile-file*\n   #:compile-file-pathname*\n   #:*warnings-file-type* #:enable-deferred-warnings-check #:disable-deferred-warnings-check\n   #:enable-asdf-binary-locations-compatibility\n   #:*default-source-registries*\n   #:*source-registry-parameter*\n   #:initialize-source-registry\n   #:compute-source-registry\n   #:clear-source-registry\n   #:ensure-source-registry\n   #:process-source-registry\n   #:registered-system #:registered-systems #:already-loaded-systems\n   #:resolve-location\n   #:asdf-message\n   #:*user-cache*\n   #:user-output-translations-pathname\n   #:system-output-translations-pathname\n   #:user-output-translations-directory-pathname\n   #:system-output-translations-directory-pathname\n   #:user-source-registry\n   #:system-source-registry\n   #:user-source-registry-directory\n   #:system-source-registry-directory\n\n   ;; The symbols below are all DEPRECATED, do not use. To be removed in a further release.\n   #:*asdf-verbose* #:run-shell-command\n   #:component-load-dependencies #:system-registered-p #:package-system\n   #+ecl #:make-build\n   #:operation-on-warnings #:operation-on-failure #:operation-error\n   #:compile-failed #:compile-warned #:compile-error\n   #:module-components #:component-property #:traverse))\n;;;; ---------------------------------------------------------------------------\n;;;; ASDF-USER, where the action happens.\n\n(uiop/package:define-package :asdf/user\n  (:nicknames :asdf-user)\n  ;; NB: releases before 3.1.2 this :use'd only uiop/package instead of uiop below.\n  ;; They also :use'd uiop/common-lisp, that reexports common-lisp and is not included in uiop.\n  ;; ASDF3 releases from 2.27 to 2.31 called uiop asdf-driver and asdf/foo uiop/foo.\n  ;; ASDF1 and ASDF2 releases (2.26 and earlier) create a temporary package\n  ;; that only :use's :cl and :asdf\n  (:use :uiop/common-lisp :uiop :asdf/interface))\n;;;; -----------------------------------------------------------------------\n;;;; ASDF Footer: last words and cleanup\n\n(uiop/package:define-package :asdf/footer\n  (:recycle :asdf/footer :asdf)\n  (:use :uiop/common-lisp :uiop\n        :asdf/system ;; used by ECL\n        :asdf/upgrade :asdf/system-registry :asdf/operate :asdf/bundle)\n  ;; Happily, all those implementations all have the same module-provider hook interface.\n  #+(or abcl clasp cmucl clozure ecl mezzano mkcl sbcl)\n  (:import-from #+abcl :sys #+(or clasp cmucl ecl) :ext #+clozure :ccl #+mkcl :mk-ext #+sbcl sb-ext #+mezzano :sys.int\n                #:*module-provider-functions*\n                #+ecl #:*load-hooks*)\n  #+(or clasp mkcl) (:import-from :si #:*load-hooks*))\n\n(in-package :asdf/footer)\n\n;;;; Register ASDF itself and all its subsystems as preloaded.\n(with-upgradability ()\n  (dolist (s '(\"asdf\" \"asdf-package-system\"))\n    ;; Don't bother with these system names, no one relies on them anymore:\n    ;; \"asdf-utils\" \"asdf-bundle\" \"asdf-driver\" \"asdf-defsystem\"\n    (register-preloaded-system s :version *asdf-version*))\n  (register-preloaded-system \"uiop\" :version *uiop-version*))\n\n;;;; Ensure that the version slot on the registered preloaded systems are\n;;;; correct, by CLEARing the system. However, we do not CLEAR-SYSTEM\n;;;; unconditionally. This is because it's possible the user has upgraded the\n;;;; systems using ASDF itself, meaning that the registered systems have real\n;;;; data from the file system that we want to preserve instead of blasting\n;;;; away and replacing with a blank preloaded system.\n(with-upgradability ()\n  (unless (equal (system-version (registered-system \"asdf\")) (asdf-version))\n    (clear-system \"asdf\"))\n  ;; 3.1.2 is the last version where asdf-package-system was a separate system.\n  (when (version< \"3.1.2\" (system-version (registered-system \"asdf-package-system\")))\n    (clear-system \"asdf-package-system\"))\n  (unless (equal (system-version (registered-system \"uiop\")) *uiop-version*)\n    (clear-system \"uiop\")))\n\n;;;; Hook ASDF into the implementation's REQUIRE and other entry points.\n#+(or abcl clasp clisp clozure cmucl ecl mezzano mkcl sbcl)\n(with-upgradability ()\n  ;; Hook into CL:REQUIRE.\n  #-clisp (pushnew 'module-provide-asdf *module-provider-functions*)\n  #+clisp (if-let (x (find-symbol* '#:*module-provider-functions* :custom nil))\n            (eval `(pushnew 'module-provide-asdf ,x)))\n\n  #+(or clasp ecl mkcl)\n  (progn\n    (pushnew '(\"fasb\" . si::load-binary) *load-hooks* :test 'equal :key 'car)\n\n    #+os-windows\n    (unless (assoc \"asd\" *load-hooks* :test 'equal)\n      (appendf *load-hooks* '((\"asd\" . si::load-source))))\n\n    ;; Wrap module provider functions in an idempotent, upgrade friendly way\n    (defvar *wrapped-module-provider* (make-hash-table))\n    (setf (gethash 'module-provide-asdf *wrapped-module-provider*) 'module-provide-asdf)\n    (defun wrap-module-provider (provider name)\n      (let ((results (multiple-value-list (funcall provider name))))\n        (when (first results) (register-preloaded-system (coerce-name name)))\n        (values-list results)))\n    (defun wrap-module-provider-function (provider)\n      (ensure-gethash provider *wrapped-module-provider*\n                      (constantly\n                       #'(lambda (module-name)\n                           (wrap-module-provider provider module-name)))))\n    (setf *module-provider-functions*\n          (mapcar #'wrap-module-provider-function *module-provider-functions*))))\n\n#+cmucl ;; Hook into the CMUCL herald.\n(with-upgradability ()\n  (defun herald-asdf (stream)\n    (format stream \"    ASDF ~A\" (asdf-version)))\n  (setf (getf ext:*herald-items* :asdf) '(herald-asdf)))\n\n\n;;;; Done!\n(with-upgradability ()\n  #+allegro ;; restore *w-o-n-r-c* setting as saved in uiop/common-lisp\n  (when (boundp 'excl:*warn-on-nested-reader-conditionals*)\n    (setf excl:*warn-on-nested-reader-conditionals* uiop/common-lisp::*acl-warn-save*))\n\n  #+(and allegro allegro-v10.1) ;; check for patch needed for upgradeability\n  (unless (assoc \"ma040\" (cdr (assoc :lisp sys:*patches*)) :test 'equal)\n    (warn 'asdf-install-warning\n          :format-control \"On Allegro Common Lisp 10.1, patch pma040 is ~\nneeded for correct ASDF upgrading. Please update your Allegro image ~\nusing (SYS:UPDATE-ALLEGRO).\"))\n\n  ;; Advertise the features we provide.\n  (dolist (f '(:asdf :asdf2 :asdf3 :asdf3.1 :asdf3.2 :asdf3.3)) (pushnew f *features*))\n\n  ;; Provide both lowercase and uppercase, to satisfy more people, especially LispWorks users.\n  (provide \"asdf\") (provide \"ASDF\")\n\n  ;; Finally, call a function that will cleanup in case this is an upgrade of an older ASDF.\n  (cleanup-upgraded-asdf))\n\n(when *load-verbose*\n  (asdf-message \";; ASDF, version ~a~%\" (asdf-version)))\n"
  },
  {
    "path": "scripts/build-cli.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(load \"scripts/prepare-image\")\n(load \"scripts/init\")\n\n(ql:quickload :screenshotbot.sdk)\n\n(defvar *output* (make-pathname\n                  :type (or #+(or windows win32) \"exe\")\n                  :name \"screenshotbot-cli\"))\n\n#+sbcl\n(sb-ext:save-lisp-and-die *output*\n                          :purify t\n                          :toplevel 'screenshotbot/sdk/main:main\n                          :executable t)\n\n#+ccl\n(ccl:save-application *output*\n                      :toplevel-function 'screenshotbot/sdk/main:main)\n\n#+lispworks\n(deliver 'screenshotbot/sdk/main:main *output* 5\n          :keep-function-name t\n          :keep-pretty-printer t\n          :keep-lisp-reader t\n          :keep-symbols `(system:pipe-exit-status)\n          :packages-to-keep-symbol-names :all\n          :multiprocessing t)\n"
  },
  {
    "path": "scripts/build-image.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package \"CL-USER\")\n\n(load \"scripts/prepare-image.lisp\")\n\n#+lispworks\n(load-all-patches)\n\n(defvar *image-load-hook-contents* (uiop:read-file-string \"scripts/init.lisp\"))\n(defvar *hook-loaded-p* nil)\n\n(defun image-load-hook ()\n  ;; On MacOS, the TMPDIR variable can change between sessions.\n  (uiop:setup-temporary-directory)\n\n  ;; If we used this image to deliver another image, we don't\n  ;; want to load the same hook twice\n  (unless *hook-loaded-p*\n    (load (make-string-input-stream *image-load-hook-contents*))\n    (setf *hook-loaded-p* t)))\n\n(compile 'image-load-hook)\n\n#+sbcl\n(pushnew 'image-load-hook sb-ext:*init-hooks*)\n\n#+lispworks\n(defun lw-image-load-hook ()\n  (handler-bind ((error\n                   (lambda (e)\n                     (declare (ignore e))\n                     (dbg:output-backtrace :verbose)\n                     (format t \"Could not load init~%\")\n                     (uiop:quit 1))))\n    (image-load-hook)))\n\n#+lispworks\n(compile 'lw-image-load-hook)\n\n#+lispworks\n(lw:define-action \"When starting image\" \"Call image load hook\"\n  #'lw-image-load-hook)\n\n(format t \"Got command line arguments: ~S\" (uiop:raw-command-line-arguments))\n\n(assert (not (find-package :bordeaux-threads)))\n\n#+lispworks\n(flet ((build ()\n         (let ((output \"build/lw-console-8-1-0\"))\n           (delete-file output)\n           (save-image output\n                       :console t\n                       :multiprocessing t\n                       :environment nil))))\n  #-darwin\n  (build)\n  #+darwin\n  (cond\n    ((hcl:building-universal-intermediate-p)\n     (build))\n    (t\n     (hcl:save-universal-from-script \"scripts/build-image.lisp\"))))\n\n\n#+sbcl\n(sb-ext:save-lisp-and-die\n (namestring\n  (make-pathname\n   #+win32 :type #+win32 \"exe\"\n   :defaults #P\"build/sbcl-console\"))\n :executable t)\n\n#+ccl\n(defun ccl-toplevel-function ()\n  (image-load-hook)\n  (let ((file (cadr ccl:*command-line-argument-list*)))\n    (if file\n     (load file :verbose t)\n     (loop\n           (print (eval (read)))))))\n\n\n#+ccl\n(ccl:save-application \"build/ccl-console\"\n                      :toplevel-function 'ccl-toplevel-function)\n"
  },
  {
    "path": "scripts/common.mk",
    "content": "\nbuild/affected-files.txt:\n"
  },
  {
    "path": "scripts/docker-compose-with-replay.yml",
    "content": "version: \"3.9\"\nservices:\n  screenshotbot:\n    build:\n      context: ${SB_OSS_CONTEXT}\n      dockerfile: ${SB_OSS_DOCKERFILE}\n\n    depends_on:\n      - selenium-hub\n      - selenium-firefox\n      - selenium-chrome\n\n    ports:\n      - \"4091:4091\"\n    volumes:\n      - screenshotbot-oss:/data\n      - .:/app\n      - screenshotbot-build:/app/build\n    networks:\n      default:\n      selenium:\n        ipv4_address: 172.29.1.14\n\n  selenium-hub:\n    image: selenium/hub:4.1.2-20220217\n    ports:\n      - \"4442-4444:4442-4444\"\n    environment:\n      - SE_NODE_SESSION_TIMEOUT=60\n    depends_on:\n      - squid\n    networks:\n      selenium:\n\n  selenium-chrome:\n    image: selenium/node-chrome:4.1.2-20220217\n    shm_size: 1g\n    depends_on:\n      - selenium-hub\n    environment:\n      - SE_NODE_SESSION_TIMEOUT=60\n      - SE_EVENT_BUS_HOST=selenium-hub\n      - SE_EVENT_BUS_PUBLISH_PORT=4442\n      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443\n      - SE_NODE_MAX_SESSIONS=2\n    extra_hosts:\n      - host.docker.internal:host-gateway\n    networks:\n      selenium:\n\n  selenium-firefox:\n    image: selenium/node-firefox:4.1.2-20220217\n    shm_size: 1g\n    depends_on:\n      - selenium-hub\n    environment:\n      - SE_NODE_SESSION_TIMEOUT=60\n      - SE_EVENT_BUS_HOST=selenium-hub\n      - SE_EVENT_BUS_PUBLISH_PORT=4442\n      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443\n      - SE_NODE_MAX_SESSIONS=2\n    networks:\n      selenium:\n\n  squid:\n    build:\n      context: .\n      dockerfile: docker/squid/Dockerfile\n    ports:\n      - 3128:3128\n    volumes:\n      - squid-cache:/var/spool/squid\n    networks:\n      default:\n      selenium:\n\nvolumes:\n  screenshotbot-oss:\n  squid-cache:\n  screenshotbot-build:\n\nnetworks:\n  default:\n  backend:\n  selenium:\n    driver: bridge\n    internal: true\n    ipam:\n      config:\n        - subnet: 172.29.1.0/16\n"
  },
  {
    "path": "scripts/init.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;; This file is loaded after the image is restarted\n\n(defpackage #:tdrhq-init\n  (:use #:cl\n        #:cl-user))\n\n(in-package #:tdrhq-init)\n\n(ql:quickload :quick-patch :silent t)\n\n(defun register-tdrhq (name commit)\n  (quick-patch:register (format nil \"https://github.com/tdrhq/~a\" name)\n                        commit))\n\n(compile 'register-tdrhq)\n\n#+nil\n(register-tdrhq \"stripe\" \"673d4b9600eb7c2dd21b4701a1b18e348dca7267\")\n\n;; Only on Windows, we're unable to checkout nyaml, because its test\n;; code has file names that don't load on Windows. This patched\n;; version has all the test code deleted.\n#+mswindows\n(register-tdrhq \"nyaml\" \"28d43dae676ad013affeab3c2b0f0d9307490d53\")\n\n(register-tdrhq \"cmd\"  \"29f1267d141b5117dc742bce74340711c99076f3\")\n(register-tdrhq \"opticl\" \"a33e3411d28ebff4b29a59a3619884c0f54ff586\")\n\n(when (uiop:os-windows-p)\n  ;; Disable mmap since it causes issues with long file names.\n  (register-tdrhq \"pngload\" \"63382b67d637479cbfbc3876281070151b641594\"))\n\n(quick-patch:register \"https://github.com/gschjetne/cljwt\"\n                            \"bd3e567097cd9d48eb811be601590afa167e6667\")\n\n(quick-patch:register \"https://github.com/tokenrove/imago\"\n                      \"29f2b42b248785acae3d05d5dd97a4e9ad0d8ecb\")\n\n(register-tdrhq \"plump\" \"aeea283021da94e9d30025f79c914b37fc522b75\")\n\n;; html2text is not part of quicklisp\n(register-tdrhq \"html2text\"\n                \"b5620fdd435df5254a713f3c10bd756632df3dce\")\n\n;; slynk-named-readtables is loaded from ~/.emacs.d. But this prevents\n;; us from loading it in a remote image. It's not part of quicklisp,\n;; so let's add it.\n(quick-patch:register \"https://github.com/joaotavora/sly-named-readtables\"\n                      \"a5a42674ccffa97ccd5e4e9742beaf3ea719931f\")\n\n(assert (not (find-package :bordeaux-threads)))\n\n;; See https://github.com/thephoeron/cl-isaac/pull/13\n(quick-patch:register \"https://github.com/svetlyak40wt/cl-isaac\"\n                      \"77a51b88d7d0e78f7517d744fff4a3135727b3b6\")\n\n;; See https://github.com/xach/zpb-ttf/pull/25. Might be safe to\n;; remove if this has been merged by the next QL release.\n(register-tdrhq \"zpb-ttf\" \"6e0eaec06c123f53b07d93200a8288d820487e0c\")\n\n(register-tdrhq \"esrap\" \"96f4d59905a7ffdcf9073572c748883b558db0d2\")\n\n;; TODO: automatically generate hashes\n#+(or screenshotbot-oss eaase-oss)\n(progn\n  (quick-patch:register \"https://github.com/moderninterpreters/markup\" \"master\")\n  (quick-patch:register \"https://github.com/moderninterpreters/cl-sentry-client\" \"master\")\n  (register-tdrhq \"easy-macros\" \"main\")\n  (register-tdrhq \"fiveam-matchers\" \"master\")\n  (register-tdrhq \"cl-unix-sockets\" \"master\")\n  (register-tdrhq \"bknr.cluster\" \"main\"))\n\n;; https://github.com/slburson/fset/issues/112\n(quick-patch:register \"https://github.com/slburson/fset\"\n                      \"b4a4d37f9175a1dca243ef592cd46316f0942834\")\n;; https://github.com/slburson/fset/issues/112\n(quick-patch:register \"https://github.com/slburson/misc-extensions\"\n                      \"d3d24809daf8667dafa2ac39c5b203c88afb9781\")\n\n;; Sharplisper forks\n(quick-patch:register \"https://github.com/sharplispers/lparallel\"\n                      \"95b162b152e43bae9a19bf7085db2aeb108a74d5\")\n\n(let ((output-dir (format nil \"~abuild/quick-patch/\"\n                          ;; *cwd* is from prepare-image.lisp. Can we clean this up better?\n                          (namestring cl-user::*cwd*))))\n  (format t \"Checking out quick-patch repos in: ~a~%\" output-dir)\n  (quick-patch:checkout-all output-dir))\n\n"
  },
  {
    "path": "scripts/install-sbcl.sh",
    "content": "#!/bin/bash\n\nset -e\nset -x\n\napt-get update \napt-get install -y wget sbcl build-essential \n# apt-get install -y valgrind\n# apt-get build-dep -y sbcl\nwget  https://github.com/sbcl/sbcl/archive/refs/tags/sbcl-2.5.4.tar.gz \ntar xvzf sbcl-2.5.4.tar.gz\ncd sbcl-sbcl-2.5.4/ && sh make.sh && INSTALL_ROOT=/usr/local sh install.sh\n"
  },
  {
    "path": "scripts/jenkins.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;; these quickloads are required because we need to load them before\n;; we set dspec:*redefinition-action* to :error\n\n(ql:quickload :test-runner :silent t)\n\n(format t \"Test runner loaded~%\")\n\n(test-runner:init)\n\n#+lispworks\n(mp:initialize-multiprocessing :main nil #'test-runner:main)\n\n#-lispworks\n(test-runner:main)\n"
  },
  {
    "path": "scripts/lispworks-versions.mk",
    "content": "# Links to all the Lispworks versions being used. By having a Makefile\n# dependency on this file, we can essentially add a dependency on the\n# Lispworks version.\n\nLW_VERSION=8-1-0\n\nLW=build/lw-console-$(LW_VERSION)$(ARCH)\nLW_CORE=lispworks-unknown-location\n\nLW_LIB_DIR=/opt/software/lispworks\nPRIVATE_PATCH_DIR=$(LW_LIB_DIR)/lib/$(LW_VERSION)-0/private-patches/\nPRIVATE_PATCHES=$(call FIND,$(PRIVATE_PATCH_DIR),*.lisp)\n\nifeq ($(UNAME),Linux)\n\tLW_CORE=$(LW_PREFIX)/lispworks-$(LW_VERSION)-*-linux\nendif\n\nifeq ($(UNAME),Darwin)\n\tLW_CORE=/Applications/LispWorks\\ 8.1\\ \\(64-bit\\)/LispWorks\\ \\(64-bit\\).app/Contents/MacOS/lispworks-$(LW_VERSION)-macos64-universal\n\tLW_LIB_DIR=/Applications/LispWorks\\ 8.1\\ \\(64-bit\\)/Library\nendif\n\nifeq ($(OS),Windows_NT)\n\tLW_CORE=\"C:\\Program Files\\LispWorks\\lispworks-$(LW_VERSION)-x64-windows.exe\"\nendif\n\n\n"
  },
  {
    "path": "scripts/prepare-image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :cl-user)\n\n(when (boundp '*prepare-image-called-p*)\n  (error \"Prepare image was already called in this image, calling it again will lead to a bad state.\"))\n\n#+lispworks\n(require \"java-interface\" )\n\n\n;; For SBCL, if you don't have SBCL_HOME set, then we won't be able to require this later.\n#+sbcl\n(require \"sb-introspect\")\n\n#+lispworks\n(progn\n  (lw:set-default-character-element-type 'character))\n\n(when (probe-file \".jipr\")\n  (push :jipr *features*))\n\n(when (probe-file \"scripts/asdf.lisp\")\n  (format t \"Compiling asdf..~%\")\n  (let ((output (compile-file \"scripts/asdf.lisp\" :verbose nil :print nil)))\n    (load output))\n  (provide \"asdf\"))\n\n(require \"asdf\")\n\n#+sbcl\n(require \"sb-sprof\")\n\n#+nil\n(push (pathname (format nil \"~a/local-projects/poiu/\" (namestring (uiop:getcwd))))\n      asdf:*central-registry*)\n\n(defvar *asdf-root-guesser* nil)\n\n(defparameter *cwd* (merge-pathnames\n               *default-pathname-defaults*\n               (uiop:getcwd)))\n\n(defun update-output-translations (root)\n  \"This function is called dynamically from deliver-utils/common.lisp!\"\n  (asdf:initialize-output-translations\n   `(:output-translations\n     :inherit-configuration\n     (,(namestring root)\n      ,(format nil \"~abuild/asdf-cache/~a~a/\" root\n               (uiop:implementation-identifier)\n               #+android-delivery\n               \"-android\"\n               #-android-delivery\n               \"\")))))\n(compile 'update-output-translations)\n(update-output-translations *cwd*)\n\n#+sbcl\n(progn\n  (require :sb-rotate-byte)\n  (require :sb-cltl2)\n  (asdf:register-preloaded-system :sb-rotate-byte)\n  (asdf:register-preloaded-system :sb-cltl2))\n\n\n#+lispworks\n(defun use-utf-8-for-all-lisp-files (pathname ext-format-spec first-byte max-extent)\n  (cond\n    ((equal \"lisp\" (pathname-type pathname))\n     :utf-8)\n    (t ext-format-spec)))\n\n#+lispworks\n(compile 'use-utf-8-for-all-lisp-files)\n\n#+lispworks\n(push 'use-utf-8-for-all-lisp-files system:*file-encoding-detection-algorithm*)\n\n(defun %read-version (file)\n  (let ((key \"version: \"))\n   (loop for line in (uiop:read-file-lines file)\n         if (string= key line :end2 (length key))\n           return (subseq line (length key)))))\n\n\n(defun init-quicklisp ()\n  (let ((version (%read-version \"quicklisp/dists/quicklisp/distinfo.txt\")))\n    (let ((quicklisp-loc (ensure-directories-exist\n                          (merge-pathnames\n                           (format nil \"build/quicklisp/~a/\" version)\n                           *cwd*)))\n          (src (merge-pathnames\n                \"quicklisp/\"\n                *cwd*)))\n      (flet ((safe-copy-file (path &optional (dest path))\n               (let ((src (merge-pathnames\n                           path\n                           \"quicklisp/\"))\n                     (dest (merge-pathnames\n                            dest\n                            quicklisp-loc)))\n                 (format t \"Copying: ~a to ~a~%\" src dest)\n\n                 (when (equal src dest)\n                   (error \"Trying to overwrite the same file\"))\n                 (when (or\n                        (not (uiop:file-exists-p dest))\n                        (< (file-write-date dest)\n                           (file-write-date src)))\n                   (uiop:copy-file\n                    src\n                    (ensure-directories-exist\n                     dest))))))\n        (loop for name in\n                       (append (directory\n                                (merge-pathnames\n                                 \"quicklisp/quicklisp/*.lisp\"\n                                 *cwd*))\n                               (directory\n                                (merge-pathnames\n                                 \"quicklisp/quicklisp/*.asd\"\n                                 *cwd*)))\n              do (safe-copy-file name\n                                 (format nil \"quicklisp/~a.~a\"\n                                         (pathname-name name)\n                                         (pathname-type name))))\n        (loop for name in (directory\n                           (merge-pathnames\n                            \"quicklisp/*.lisp\"\n                            *cwd*))\n              do (safe-copy-file name\n                                 (format nil \"~a.lisp\"\n                                         (pathname-name name))))\n        (safe-copy-file \"setup.lisp\")\n        (safe-copy-file \"quicklisp/version.txt\")\n        (safe-copy-file \"dists/quicklisp/distinfo.txt\")\n        (safe-copy-file \"dists/quicklisp/enabled.txt\")\n        (safe-copy-file \"dists/quicklisp/preference.txt\"))\n      (load (merge-pathnames\n             \"setup.lisp\"\n             quicklisp-loc)))))\n\n(init-quicklisp)\n\n#+nil\n(ql:update-all-dists :prompt nil)\n\n(pushnew :screenshotbot-oss *features*)\n\n\n\n(defun update-project-directories (cwd)\n  (flet ((push-src-dir (name)\n           (let ((dir (pathname (format nil \"~a~a/\" cwd name))))\n             (when (probe-file dir)\n               (push dir ql:*local-project-directories*)))))\n    #-screenshotbot-oss\n    (push-src-dir \"local-projects\")\n    (push-src-dir \"src\")\n    (push-src-dir \"third-party\")\n    (push-src-dir \"lisp\")))\n(compile 'update-project-directories)\n(defun update-root (cwd)\n  (update-output-translations cwd)\n  (update-project-directories cwd))\n(compile 'update-root)\n\n(update-project-directories *cwd*)\n\n(defvar *initial-path* *cwd*\n  \"The CWD before the image was saved\")\n\n(defun fix-absolute-path (path)\n  (let* ((initial-path *initial-path*)\n         (cwd (uiop:getcwd))\n         (path (pathname path))\n         (initial-parts (pathname-directory initial-path))\n         (parts (pathname-directory cwd)))\n    (cond\n      ((and\n        (> (length (pathname-directory path))\n           (length initial-parts))\n        (equalp\n         (subseq (pathname-directory path) 0 (length initial-parts))\n         initial-parts))\n       (make-pathname\n        :directory\n        (append\n         parts\n         (subseq (pathname-directory path) (length initial-parts)))\n        :defaults path))\n      (t\n       path))))\n(compile 'fix-absolute-path)\n\n(defmacro fix-paths-in (place)\n  `(setf ,place\n         (mapcar #'fix-absolute-path ,place)))\n\n(defmacro fix-path (place)\n  `(when ,place\n     (setf ,place (fix-absolute-path ,place))))\n\n(defmethod fix-asdf-components ((system asdf::parent-component))\n  (call-next-method)\n  (mapc #'fix-asdf-components (asdf:component-children system)))\n\n(defmethod fix-asdf-components ((system asdf:system))\n  (fix-path (slot-value system 'asdf::source-file))\n  (call-next-method))\n\n(defmethod fix-asdf-components ((c asdf::component))\n  (setf (slot-value c 'asdf/component::additional-input-files)\n        (loop for (key . values) in (slot-value c 'asdf/component::additional-input-files)\n              collect (cons key (mapcar #'fix-absolute-path values))))\n  (when (slot-boundp c 'asdf::absolute-pathname)\n    (fix-path (slot-value c 'asdf::absolute-pathname)))\n  (when (slot-boundp c 'asdf::relative-pathname)\n    (fix-path (slot-value c 'asdf::relative-pathname))))\n\n\n(defun fix-absolute-registry-paths ()\n  (when\n      #-lispworks t\n      #+lispworks\n      (or\n         (not (boundp 'lw:*delivery-level*))\n         (= 0 lw:*delivery-level*))\n    (setf asdf::*base-build-directory* (uiop:getcwd))\n    (fix-paths-in asdf:*central-registry*)\n    (fix-paths-in ql:*local-project-directories*)\n    (update-output-translations (uiop:getcwd))\n    (setf ql-setup:*quicklisp-home*\n          (fix-absolute-path ql-setup:*quicklisp-home*))\n    (loop for system being the hash-values of asdf::*registered-systems*\n          do (fix-asdf-components system))\n\n    (quicklisp:setup)\n    (ql:register-local-projects)))\n\n(compile 'fix-absolute-registry-paths)\n\n#+lispworks\n(lw:define-action \"When starting image\" \"Fix absolute registry paths\"\n  #'fix-absolute-registry-paths)\n\n\n(defun maybe-asdf-prepare ()\n  (when *asdf-root-guesser*\n    (update-root (funcall *asdf-root-guesser*))))\n\n(compile 'maybe-asdf-prepare)\n\n#+lispworks\n(lw:define-action \"When starting image\" \"Re-prepare asdf\"\n  #'maybe-asdf-prepare)\n\n(defun unprepare-asdf (root-guesser)\n  \"This function is called dynamically from deliver-utils/common.lisp!\"\n  (setf *asdf-root-guesser* root-guesser))\n\n(defun maybe-configure-proxy ()\n  (let ((proxy (uiop:getenv \"HTTP_PROXY\")))\n    (when (and proxy (> (length proxy) 0))\n      (setf ql:*proxy-url* proxy))))\n\n(maybe-configure-proxy)\n\n\n;; for SLY\n(ql:quickload \"flexi-streams\")\n\n#+sbcl ;; not sure why I need this, I didn't debug in detail\n(ql:quickload \"prove-asdf\")\n\n(ql:quickload :documentation-utils)\n\n;;(log:info \"*local-project-directories: ~S\" ql:*local-project-directories*)\n\n#+lispworks\n(require \"java-interface\")\n\n(ql:quickload :cl-ppcre) ;; used by sdk.deliver\n\n;; make sure we have build asd\n#+nil\n(push (pathname (format nil \"~a/build-utils/\" *cwd*))\n      asdf:*central-registry*)\n(ql:register-local-projects)\n\n(setf *prepare-image-called-p* t)\n\n#+(and :lispworks :mswindows)\n(comm:set-ssl-library-path\n (list\n  \"C:/Program Files/OpenSSL-Win64/libcrypto-3-x64.dll\"\n  \"C:/Program Files/OpenSSL-Win64/libssl-3-x64.dll\"))\n\n(defun assert-patch (name)\n  #+ (and :lispworks (not :screenshotbot-oss))\n  (assert\n   (search name\n           (with-output-to-string (s)\n             (dbg:output-backtrace :bug-form s)))))\n\n;; These patches also ensure that it will cause the image to be rebuild\n(assert-patch \"check-ssl-error\")\n(assert-patch \"ipv6-contraction\")\n\n"
  },
  {
    "path": "scripts/update-phabricator.lisp",
    "content": "(require :asdf)\n\n(ql:quickload :util/phabricator)\n\n(defpackage :my-test\n  (:use :cl\n   :util/phabricator/conduit))\n(in-package :my-test)\n\n(let ((phid (uiop:getenv \"PHID\")))\n  (let ((phab (make-phab-instance-from-arcrc \"https://phabricator.tdrhq.com\")))\n    (when phid\n      (let* ((args `((\"buildTargetPHID\" . ,(uiop:getenv \"PHID\"))\n                     (\"type\" . ,(if (member \"pass\" (uiop:raw-command-line-arguments)\n                                            :test 'string=)\n                                    \"pass\"\n                                    \"fail\")))))\n        (call-conduit\n         phab\n         \"harbormaster.sendmessage\"\n         args)))))\n"
  },
  {
    "path": "src/auth/acceptor.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/acceptor\n  (:use #:cl\n        #:auth))\n(in-package :auth/acceptor)\n\n(defclass auth-acceptor-mixin ()\n  ())\n"
  },
  {
    "path": "src/auth/auth.asd",
    "content": "(asdf:defsystem \"auth\"\n    :serial t\n  :depends-on (\"cl-pass\"\n               \"bknr.datastore\"\n               \"util.misc\"\n               \"core.installation\"\n               \"hunchentoot-extensions\"\n               \"util.store\"\n               \"log4cl\"\n               \"util/cron\"\n               \"secure-random\"\n               \"gravatar\"\n               \"cl-intbytes\"\n               \"encrypt\"\n               \"cl-fad\"\n               \"cl-store\"\n               \"hunchentoot\")\n  :components ((:file \"package\")\n               (:file \"auth\")\n               (:file \"viewer-context\")\n               (:file \"request\")\n               (:file \"view\")\n               (:file \"company\")\n               (:file \"acceptor\")\n               (:file \"avatar\")))\n\n(defsystem \"auth/tests\"\n  :serial t\n  :depends-on (:auth\n               :util.testing\n               :util.store\n               :core.api\n               :fiveam-matchers\n               :util/fiveam)\n  :components ((:file \"test-auth\")\n               (:file \"test-view\")\n               (:file \"test-avatar\")))\n"
  },
  {
    "path": "src/auth/auth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package #:auth)\n\n(defgeneric auth-get-user-class (acceptor)\n  (:documentation \"Gets the class used to store the user data\"))\n\n(defindex +sessions-index+ 'fset-set-index\n  :slot-name 'session-token)\n\n(defindex +expiry-index+ 'fset-set-index\n  :slot-name 'expiry-ts)\n\n(with-class-validation\n (defclass user-session-value (store-object)\n   ((session-key-and-prop-key\n     :accessor session-key-and-prop-key\n     :documentation \"This is an older mechanism used to find the cookies. Deprecated. It's only here for OSS support.\")\n    (prop-key :initarg :prop-key\n              :accessor prop-key\n              :documentation \"The property key. e.g. :USER, or :COMPANY.\")\n    (session-token :initarg :session-token\n                   :accessor  session-token\n                   :index +sessions-index+\n                   :index-reader values-for-session-token\n                   :documentation \"Just the session token, as a string.\")\n    (session-domain :initarg :session-domain\n                    :accessor session-domain\n                    :documentation \"The domain associated with this session\")\n    (value\n     :initarg :value\n     :relaxed-object-reference t\n     :accessor value)\n    (last-update-ts\n     :initarg :last-update-ts\n     :initform (get-universal-time) #| migration |#\n     :accessor last-update-ts)\n    (expiry-ts\n     :initarg :expiry-ts\n     :initform nil\n     :accessor expiry-ts\n     :index +expiry-index+))\n   (:metaclass persistent-class)\n   (:default-initargs :last-update-ts (get-universal-time))))\n\n(defmethod initialize-instance :after ((uv user-session-value)\n                                       &key session-key #| deprecated |#\n                                         prop-key\n                                         value\n                                         session-token\n                                         session-domain)\n  (declare (ignore value session-token session-domain))\n  (setf (session-key-and-prop-key uv)\n        (cons session-key prop-key)))\n\n(defun find-user-session-value (token domain prop-key)\n  (let ((usvs (values-for-session-token token)))\n    (fset:do-set (usv usvs)\n      (when (and\n             (eql prop-key (prop-key usv))\n             (equal domain (session-domain usv)))\n        (return-from find-user-session-value usv)))))\n\n(defvar *hash-cache* (make-hash-table :test #'equal))\n\n(defun find-user-session-value-by-hash (session-key-hash prop-key)\n  \"This is used for analytics purposes since we don't want to store the\n session key itself in plain text in many places.\"\n  (let* ((session-key-hash (ironclad:hex-string-to-byte-array session-key-hash))\n         (cache-key (cons session-key-hash prop-key)))\n    (util/misc:or-setf\n     (gethash cache-key *hash-cache*)\n     (loop for user-session-value in (bknr.datastore:store-objects-with-class 'user-session-value)\n           for session-key = (car (session-key-and-prop-key user-session-value))\n           if (and\n               (equalp session-key-hash\n                       (ironclad:digest-sequence\n                        :sha256\n                        (flexi-streams:string-to-octets\n                         (car session-key))))\n               (equal prop-key\n                      (cdr session-key)))\n             do\n                (return (value user-session-value))))))\n\n\n(defclass user-session-transient ()\n  ((session-key\n    :accessor %session-token\n    :initarg :token)\n   (domain\n    :initarg :domain\n    :reader session-domain\n    :initform (host-without-port))))\n\n(defun copy-session (user-session)\n  (make-instance 'user-session-transient\n                 :token (%session-token user-session)\n                 :domain (session-domain user-session)))\n\n(defmethod %session-token :before ((self user-session-transient))\n  (unless (session-created-p self)\n    (setf (%session-token self) (generate-session-token))\n    (set-session self)))\n\n(defmethod ensure-session-created (session)\n  (%session-token session)\n  session)\n\n(defmethod session-created-p ((self user-session-transient))\n  \"Have we actually generated a session? (i.e. have set set the session\ncookie?) The session might only generated the first time we set a\nsession-value, and until there there might be no session available.\"\n  (slot-boundp self 'session-key))\n\n(defmethod session-key ((session user-session-transient))\n  (cons (%session-token session)\n        (session-domain session)))\n\n\n(defvar *secure-cookie-p* t)\n\n(defun cookie-name ()\n  (format nil \"s~a\"\n          (cond\n            ((boundp '*installation*)\n             (length\n              (str:split \".\"\n                         (installation-domain *installation*))))\n            (t ;; for tests\n             2))))\n\n(defun host-without-port ()\n  (car (str:split \":\" (host))))\n\n(defun set-session-cookie (token &optional domain)\n  (let ((domain (or domain (host-without-port))))\n    (set-cookie (cookie-name)\n                :value token :domain domain :expires (+ (get-universal-time) (* (- (* 30 24) 8)  3600))\n                :http-only t\n                :same-site \"Lax\" ;; Strict breaks OAuth\n                :path \"/\" :secure (and\n                                   *secure-cookie-p*\n                                   (string=\n                                    \"https\"\n                                    (hunchentoot:header-in* :x-forwarded-proto))))))\n\n(defun drop-session (&optional domain)\n  (set-session-cookie \"\" domain))\n\n(defun %current-session ()\n  (let ((token (cookie-in (cookie-name))))\n    (and\n     token\n     (not (equal \"\" token))\n     (make-instance 'user-session-transient\n                    :token token))))\n\n\n(defvar *lock* (bt:make-lock \"auth-lock\"))\n\n(defun set-session (session &optional domain)\n  (set-session-cookie (car (session-key session)) domain))\n\n(defun generate-session-token ()\n  (push-counter-event :session-generated)\n  ;; TODO: We don't need lock since OpenSSL RAND_bytes is thread-safe:\n  ;; https://mta.openssl.org/pipermail/openssl-users/2020-November/013146.html\n  (bt:with-lock-held (*lock*)\n    (base64:usb8-array-to-base64-string\n     (concatenate\n      '(vector (unsigned-byte 8))\n      ;; What if there's a bug in the random number generator, which\n      ;; is out of our control? Having a timestamp reduces the chances\n      ;; of attackers abusing collisions.\n      (cl-intbytes:int32->octets (mod (get-universal-time) (ash 1 31)))\n      (secure-random:bytes 26 secure-random:*generator*))\n     :uri t)))\n(defvar *current-session*)\n\n(defparameter *iterations* 2000)\n\n(defun session= (session1 session2)\n  (and\n   (string= (%session-token session1)\n            (%session-token session2))\n   (string= (session-domain session1)\n            (session-domain session2))))\n\n(defun %with-sessions (body)\n  (cond\n    ((boundp '*current-session*)\n     (funcall body))\n    (t\n     (let ((*current-session* (%current-session)))\n       (unless *current-session*\n         (setf *current-session* (make-instance 'user-session-transient))\n         (assert *current-session*))\n       (funcall body)))))\n\n(defmacro with-sessions (() &body body)\n  \"Inside of this macro CURRENT-SESSION will always return a non-nil\nvalue.\"\n  `(%with-sessions (lambda () ,@body)))\n\n(defun current-session ()\n  *current-session*)\n\n(defun session-value (key &key (session (current-session)))\n  (when (auth:session-created-p session)\n   (let ((x (find-user-session-value (%session-token session)\n                                     (session-domain session)\n                                     key)))\n     (and x\n          (value x)))))\n\n(defun fix-corrupt-session-token-generator ()\n  ;; Temporary fix for T1280\n  (handler-case\n      (generate-session-token)\n    (error (e)\n      (warn \"Session token generation test failed with: ~a\" e)\n      (init-session-token-generator))))\n\n(def-cron fix-corrupt-session-token-generator (:step-min 5)\n  (fix-corrupt-session-token-generator))\n\n\n(defvar *session-value-lock* (bt:make-lock \"session-value\"))\n\n(defun (setf session-value) (value key &key (session (current-session))\n                                         expires-in)\n  (let ((expiry-ts (when expires-in (+ (get-universal-time) expires-in))))\n    (bt:with-lock-held (*session-value-lock*)\n      (let ((x (find-user-session-value (%session-token session)\n                                        (session-domain session)\n                                        key)))\n        (cond\n          (x\n           (bknr.datastore:with-transaction ()\n             (setf (last-update-ts x) (get-universal-time))\n             (when expiry-ts\n               (setf (expiry-ts x) expiry-ts))\n             (setf (value x) value)))\n          (t\n           (make-instance 'user-session-value\n                          :session-token (%session-token session)\n                          :session-domain (session-domain session)\n                          :session-key (session-key session) #| deprecated |#\n                          :expiry-ts expiry-ts\n                          :value value\n                          :prop-key key)))\n        value))))\n\n(defgeneric password-hash (user)\n  (:documentation \"password hash for the user\"))\n\n(defmethod csrf-token ()\n  (util/misc:or-setf\n   (session-value :csrf-token)\n   (generate-session-token)\n   :thread-safe t))\n\n\n(defmethod check-password (user password)\n  (and user\n   (cl-pass:check-password password (auth:password-hash user))))\n\n(defmethod (setf user-password) (password user)\n  (setf (auth:password-hash user)\n        (cl-pass:hash password :iterations *iterations*)))\n\n(defclass login-controller ()\n  ((login-page\n    :initarg :login-page)))\n\n(defmacro safe-setf (place val)\n  (alexandria:with-gensyms (xval)\n    `(let ((,xval ,val))\n       (unless (equal (ignore-errors ,place) ,xval)\n         (with-transaction ()\n           (setf ,place ,xval))))))\n\n(def-store-migration (\"auth: Set individual slots from keys\" :version 5)\n  (dolist (usv (class-instances 'user-session-value))\n    (when (session-key-and-prop-key usv)\n     (destructuring-bind ((token . domain) . prop-key) (session-key-and-prop-key usv)\n       (safe-setf (session-token usv) token)\n       (safe-setf (session-domain usv) domain)\n       (safe-setf (prop-key usv) prop-key)))))\n\n(defun clean-session-values (&optional (ts (get-universal-time)))\n  (let ((smallest (index-least +expiry-index+)))\n    (when (and\n           smallest\n           (< (expiry-ts smallest) ts))\n      (delete-object smallest)\n      (clean-session-values ts))))\n\n(def-cron clean-session-values (:minute 5 :step-hour 1)\n  (clean-session-values))\n\n(defindex +session-reset-index+\n  'fset-set-index\n  :slot-name 'old-token)\n\n\n(defclass session-reset (store-object)\n  ((old-token :initarg :old-token\n              :index +session-reset-index+\n              :index-reader session-reset-by-old-token\n              :index-values all-session-resets)\n   (%domain :initarg :domain\n            :reader session-reset-domain)\n   (new-token :initarg :new-token\n              :reader session-reset-new-token)\n   (ts :initarg :ts\n       :reader session-reset-ts))\n  (:metaclass persistent-class)\n  (:default-initargs :ts (get-universal-time)))\n\n(defun auth:reset-session ()\n  (let ((new-token (generate-session-token)))\n    (when (session-created-p *current-session*)\n      (make-instance 'session-reset\n                     :old-token (%session-token *current-session*)\n                     :domain (host-without-port)\n                     :new-token new-token))\n    (setf (%session-token *current-session*)\n          new-token))\n\n  (set-session *current-session*))\n\n\n(defun auth:is-same-session-disregarding-resets-p (from-session to-session)\n  \"TODO: currently it only tests if the two sessions are the same.\"\n  (or\n   (auth:session= from-session to-session)\n   (let ((old-sessions (session-reset-by-old-token (%session-token from-session))))\n     (let ((ts (get-universal-time)))\n      (fset:do-set (possibility old-sessions)\n        (when (and\n               (equal (session-reset-new-token possibility)\n                      (%session-token to-session))\n               (equal (session-reset-domain possibility)\n                      (session-domain to-session))\n               (> (session-reset-ts possibility)\n                  (- ts 7200)))\n          (return t)))))))\n"
  },
  {
    "path": "src/auth/avatar.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/avatar\n  (:use #:cl)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:auth\n                #:oauth-user-avatar)\n  (:import-from #:encrypt/hmac\n                #:verify-hmac)\n  (:import-from #:bknr.datastore\n                #:blob-pathname\n                #:persistent-class\n                #:blob)\n  (:import-from #:util/store/store\n                #:with-class-validation\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index\n                #:fset-set-index))\n(in-package :auth/avatar)\n\n(defindex +user-index-2+\n  'fset-unique-index\n  :slot-name '%user)\n\n(defvar *overriden-avatar-lock* (bt:make-lock)\n  \"When fetching or updating the avatars, this lock must be held. TODO:\ndoes it make sense to parallelize this in the future?\")\n\n(with-class-validation\n  (defclass overriden-avatar (blob)\n    ((content-type :initarg :content-type\n                   :accessor content-type)\n     (%user :initarg :user\n            :index +user-index-2+\n            :index-reader overriden-avatar-for-user))\n    (:metaclass persistent-class)\n    (:documentation \"If this object is created, the the avatar is overriden for the given\nuser. Currently only being used by Microsoft Entra, which has specific requirements\nfor downloading the profile picture.\")))\n\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/account/avatar\") (id signature)\n  (assert\n   (verify-hmac (format nil \"avatar.~a\" id)\n                (ironclad:hex-string-to-byte-array\n                 signature)))\n  (let ((user (bknr.datastore:store-object-with-id (parse-integer id))))\n    (assert user)\n    (setf (hunchentoot:header-out \"Cache-Control\") \"max-age=3600000\")  \n    (handle-avatar user)))\n\n(defun handle-avatar (user)\n  (bt:with-lock-held (*overriden-avatar-lock*)\n    (let ((overriden-avatar (overriden-avatar-for-user user)))\n      (cond\n        (overriden-avatar\n         (setf (hunchentoot:header-out \"X-override-avatar\") \"1\")           \n         (hunchentoot:handle-static-file\n          (blob-pathname overriden-avatar)\n          (content-type overriden-avatar)))\n        (t\n         (hunchentoot:redirect\n          (actual-public-url user)))))))\n\n(defmethod actual-public-url (user)\n  (let ((known-avatar\n          (?. oauth-user-avatar (car (auth:oauth-users user)))))\n    (cond\n      ((or\n        (str:emptyp known-avatar)\n        ;; T1828: temporary hack to use Gravatar for Entra\n        (equal \"https://graph.microsoft.com/v1.0/me/photo/$value\" known-avatar))\n       (format nil \"~a\" (gravatar:image-url\n                               (auth:user-email user))))\n      (t\n       known-avatar))))\n\n\n\n"
  },
  {
    "path": "src/auth/company.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/company\n  (:use #:cl\n        #:auth))\n(in-package :auth/company)\n\n(defun (setf current-company) (company)\n  (can-view! company)\n  (setf (auth:session-value :company) company\n        (auth:request-account hunchentoot:*request*) company))\n\n(defun current-company ()\n  (and\n   (boundp 'hunchentoot:*request*)\n   (auth:request-account hunchentoot:*request*)))\n"
  },
  {
    "path": "src/auth/login/auth.login.asd",
    "content": "(defsystem :auth.login\n  :serial t\n  :depends-on (:auth\n               :auth.model\n               :util.statsig\n               :hunchentoot-extensions\n               :nibble\n               :core.installation\n               :parenscript\n               :util\n               :util/throttler\n               :util/events\n               :util/recaptcha\n               :util/request\n               :util.store\n               :clavier\n               :oidc)\n  :components ((:file \"impersonation\")\n               (:file \"roles-auth-provider\")\n               (:file \"cached-avatar\")\n               (:file \"common\")\n               (:file \"github\")\n               (:file \"oidc\")\n               (:file \"login\")\n               (:file \"sso\")\n               (:file \"signup\")\n               (:file \"verify-email\")\n               (:file \"saml\")\n               (:file \"forgot-password\")))\n\n(defsystem :auth.login/tests\n  :serial t\n  :depends-on (:auth.login\n               :util.testing\n               :fiveam-matchers\n               :util/fiveam)\n  :components ((:file \"test-roles-auth-provider\")\n               (:file \"test-verify-email\")\n               (:file \"test-cached-avatar\")\n               (:file \"test-sso\")))\n"
  },
  {
    "path": "src/auth/login/cached-avatar.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/cached-avatar\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:blob-pathname\n                #:persistent-class\n                #:blob)\n  (:import-from #:auth/avatar\n                #:content-type\n                #:overriden-avatar\n                #:overriden-avatar-for-user\n                #:*overriden-avatar-lock*)\n  (:import-from #:util/threading\n                #:make-thread)\n  (:import-from #:util/request\n                #:http-success-response?\n                #:http-request)\n  (:import-from #:oidc/oidc\n                #:access-token-str\n                #:after-authentication)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:core/installation/installation\n                #:*installation*))\n(in-package :auth/login/cached-avatar)\n\n(defclass cached-avatar-provider ()\n  ()\n  (:documentation \"A mixin for an auth provider, where the avatar is cached at login time.\"))\n\n(defvar *cv* (bt:make-condition-variable))\n\n(defvar *token-logs* nil\n  \"For debugging T1832\")\n\n(defmethod after-authentication :after ((self cached-avatar-provider)\n                                        &key email\n                                          token\n                                          avatar\n                                        &allow-other-keys)\n  (let ((user (auth:find-user *installation* :email email)))\n    (make-thread\n     (lambda ()\n       (download-avatar user :token token :avatar avatar)))))\n\n\n(auto-restart:with-auto-restart ()\n  (defun download-avatar (user &key token avatar)\n    (bt:with-lock-held (*overriden-avatar-lock*)\n      (atomics:atomic-push (list user token avatar)\n                           *token-logs*)\n      (multiple-value-bind (res code headers)\n          (http-request\n           avatar\n           :additional-headers `((\"Authorization\" . ,(format nil \"Bearer ~a\"\n                                                             (access-token-str token))))\n           :force-binary t)\n        (unless (eql 404 code)\n          (unless (http-success-response? code)\n            (error  \"Got response ~a when downloading avatar ~a\" code avatar))\n          (write-avatar user :res res :headers headers))))))\n\n(defun write-avatar (user &key res headers)\n  (let ((obj (or\n              (overriden-avatar-for-user user)\n              (make-instance 'overriden-avatar :user user))))\n    (with-open-file (stream (blob-pathname obj)\n                            :direction :output\n                            :if-exists :supersede\n                            :element-type '(unsigned-byte 8))\n      (write-sequence res stream)\n      (setf (content-type obj)\n            (assoc-value headers :content-type)))))\n"
  },
  {
    "path": "src/auth/login/common.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/common\n  (:use :cl)\n  (:nicknames :auth/login/common)\n  (:import-from #:auth\n                #:can-view!)\n  (:import-from #:core/installation/auth\n                #:call-with-ensure-user-prepared\n                #:company-for-request)\n  (:import-from #:core/installation/auth-provider\n                #:auth-provider\n                #:auth-provider-signin-form\n                #:auth-provider-signup-form\n                #:call-with-company-login\n                #:company-sso-auth-provider)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:nibble\n                #:nibble\n                #:nibble-id)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler)\n  (:import-from #:auth/login/roles-auth-provider\n                #:roles-auth-provider)\n  (:import-from #:util.cdn\n                #:make-cdn)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:import-from #:oidc/oauth\n                #:make-oauth-url)\n  (:export\n   #:abstract-oauth-provider\n   #:after-create-user\n   #:make-oauth-url\n   #:make-redirect-nibble\n   #:oauth-callback\n   #:oauth-logo-svg\n   #:oauth-name\n   #:oauth-signin-link\n   #:oauth-signup-link\n   #:signin-get\n   #:signup-get\n   #:with-oauth-state-and-redirect\n   #:server-with-login\n   #:auth-template-impl\n   #:standard-auth-provider\n   #:with-login\n   #:or-divider\n   #:allowed-domains))\n(in-package :screenshotbot/login/common)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass abstract-oauth-provider (auth-provider\n                                   roles-auth-provider)\n  ((oauth-name :initarg :oauth-name\n               :accessor oauth-name)))\n\n(defmethod auth-provider-signin-form ((auth-provider abstract-oauth-provider) redirect)\n    <div class= \"form-group mt-1 text-center mb-0\">\n      <a class= \"btn btn-outline-secondary\" style= \"width:100%\"  href= (oauth-signin-link auth-provider redirect) >\n        ,(oauth-logo-svg auth-provider)\n        <span class= \"ms-1\">Sign in with ,(oauth-name auth-provider) </span>\n      </a>\n    </div>)\n\n(defgeneric after-create-user (installation user)\n  (:method (installation user)\n    (declare (ignore installation user))\n    (values)))\n\n(markup:deftag or-divider ()\n  <div class= \"or-wrapper row\" >\n    <div class= \"col-5 strikethrough\" >\n      <hr />\n    </div>\n    <div class= \"col-2 align-middle\">\n      <div class= \"text\" >\n        <span class= \"align-middle\" >\n          <em>or</em>\n        </span>\n      </div>\n    </div>\n    <div class= \"col-5 strikethrough\" >\n      <hr />\n    </div>\n  </div>)\n\n(markup:deftag auth-common-header (children)\n  <div class=\"text-center\">\n    <a href= \"/\"><img src= (make-cdn \"/assets/images/logo-dark.svg\") class= \"auth-small-logo mb-3\" /></a>\n    <p class=\"text-muted mt-3 font-weight-bold\">,@(progn children) </p>\n  </div>\n)\n\n(defmethod auth-provider-signup-form ((auth-provider abstract-oauth-provider)\n                                      invite\n                                      plan\n                                      redirect)\n    <div class= \"form-group mt-3 text-center mb-3\">\n      <a class= \"btn btn-outline-secondary\" style= \"width:100%\"  href= (oauth-signup-link auth-provider redirect) >\n        ,(oauth-logo-svg auth-provider)\n        <span class= \"ms-1\">Sign up with ,(oauth-name auth-provider) </span>\n      </a>\n    </div>)\n\n(defun server-with-login (fn &key needs-login signup alert company\n                           ;; The invite object that triggered this\n                           ;; flow.\n                           invite\n                           ;; Redirect a GET request back to the\n                           ;; original URL instead of a nibble.\n                           (allow-url-redirect nil)\n                           ;; Sometimes, for instance for the invite\n                           ;; flow, we want to get a callback before\n                           ;; the user is prepared, so we might want\n                           ;; to set this to NIL in those cases.\n                           (ensure-prepared t))\n  (let ((fn (cond\n              (ensure-prepared\n               (lambda ()\n                 (call-with-ensure-user-prepared\n                  *installation*\n                  (auth:current-user)\n                  fn)))\n              (t fn))))\n   (cond\n     ((and\n       needs-login\n       company\n       (company-sso-auth-provider company))\n      (call-with-company-login (company-sso-auth-provider company)\n                               company\n                               fn))\n     ((and needs-login (not (auth:current-user)))\n      (apply\n       (if signup #'signup-get #'signin-get)\n       :alert alert\n       :redirect\n       (cond\n         ((and\n           allow-url-redirect\n           (eql :get (hunchentoot:request-method*)))\n          (hunchentoot:request-uri*))\n         (t\n          (nibble (:name :after-login)\n            (funcall fn))))\n       (when signup\n         (list :invite invite))))\n     (t\n      (funcall fn)))))\n\n\n(defun %with-oauth-state-and-redirect (state body)\n  (let* ((nibble-id (and state (parse-integer state)))\n         (nibble (and nibble-id (nibble:get-nibble nibble-id))))\n    (funcall body)\n    (cond\n      ((null nibble)\n       (hex:safe-redirect \"/\"))\n      (t\n       (hex:safe-redirect nibble)))))\n\n(hex:def-named-url oauth-callback \"/account/oauth-callback\")\n\n(define-condition illegal-oauth-redirect (error)\n  ())\n\n(defmethod allow-oauth-redirect-p (installation redirect)\n  nil)\n\n(defmethod handle-oauth-callback ((self auth:auth-acceptor-mixin) code state)\n  (declare (ignore code))\n  (cond\n    ((str:emptyp state)\n     (warn \"State not present in oauth-callback\")\n     \"Invalid OpenID Connect response, missing state field.\")\n    (t\n     (destructuring-bind (state &optional redirect) (str:split \",\" state)\n       (cond\n         ((and redirect (allow-oauth-redirect-p *installation* redirect))\n          (hex:safe-redirect\n           (quri:render-uri\n            (quri:merge-uris\n             \"/account/oauth-callback\"\n             redirect))\n           :code code\n           :state state))\n         (redirect\n          (error 'illegal-oauth-redirect))\n         (t\n          (nibble:render-nibble self state)))))))\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/account/oauth-callback\") (code state)\n  \"Handles OAuth callback with authorization code and state parameter.\n   \n   The state parameter contains a nibble ID and optional redirect URL separated by comma.\n   If a valid redirect is provided and allowed by the installation, redirects there with\n   code and state parameters. Otherwise, renders the nibble identified by the state.\"\n  (handle-oauth-callback self code state))\n\n(defmacro with-oauth-state-and-redirect ((state) &body body)\n  `(flet ((body () ,@body))\n     (%with-oauth-state-and-redirect ,state #'body)))\n\n(defun make-redirect-nibble (redirect)\n  (if (stringp redirect)\n      (nibble () (hex:safe-redirect redirect))\n      redirect))\n\n\n(defclass ip-throttler (throttler)\n  ()\n  (:default-initargs :tokens 10))\n\n(defmethod throttle! ((self ip-throttler) &key key &allow-other-keys)\n  (call-next-method\n   self :key (hunchentoot:real-remote-addr)))\n\n(defgeneric auth-template-impl (installation children &key body-class simple))\n\n(markup:deftag auth-template (children &key body-class (simple t) full-width)\n  (auth-template-impl *installation*\n                      children :body-class body-class\n                      :full-width full-width\n                               :simple simple))\n\n(defclass standard-auth-provider (auth-provider\n                                  roles-auth-provider)\n  ((verify-email-p :initarg :verify-email-p\n                   :initform nil\n                   :reader verify-email-p)\n   (recaptcha-token :initarg :recaptcha-token\n                    :initform nil\n                    :reader recaptcha-token)\n   (allowed-domains :initarg :allowed-domains\n                    :initform :all\n                    :reader allowed-domains)))\n\n(defmacro with-login ((&key (needs-login t) (signup nil)\n                         (company nil)\n                         (invite nil)\n                         (ensure-prepared t)\n                         (allow-url-redirect nil)\n                         (alert nil)) &body body)\n  `(server-with-login (lambda () ,@body)\n                      :needs-login ,needs-login\n                      :invite ,invite\n                      :signup ,signup\n                      :company ,company\n                      :ensure-prepared ,ensure-prepared\n                      :allow-url-redirect ,allow-url-redirect\n                      :alert ,alert))\n"
  },
  {
    "path": "src/auth/login/forgot-password.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/forgot-password\n  (:use :cl)\n  (:nicknames :auth/login/forgot-password)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:markup/markup\n                #:deftag)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/login/common\n                #:auth-template\n                #:ip-throttler)\n  (:import-from #:screenshotbot/mailer\n                #:mailer*\n                #:send-mail)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:util/throttler\n                #:throttle!)\n  (:import-from #:core/installation/installation\n                #:*installation*))\n(in-package :screenshotbot/login/forgot-password)\n\n(defvar *throttler* (make-instance 'ip-throttler))\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass change-password-request ()\n  ((used-up-p :initform nil\n              :initarg :used-up-p\n              :accessor used-up-p)))\n\n(deftag forgot-password-confirmation (&key email)\n  <auth-template simple=t >\n    <div class= \"container\">\n      <div class= \"card\">\n        <div class= \"card-body\">\n          <p>We've sent an email to ,(progn email). Please click on the link in that email.</p>\n          <p>The link expires in 24 hours.</p>\n        </div>\n    </div>\n    </div>\n  </auth-template>)\n\n(defun finish-password-reset (&key user req password confirm-password)\n  (let ((errors))\n    (flet ((check (name test message)\n             (unless test\n               (push (cons name message) errors))))\n      (check :password (not (str:emptyp password))\n             \"Please enter a new password\")\n      (check :password (>= (length password) 8)\n             \"Password needs to be at least 8 letters long\")\n      (check :confirm-password (equal password confirm-password)\n             \"Passwords don't match\")\n      (check nil (not (used-up-p req))\n             \"The password reset code has expired or has already been\")\n      (log:info \"Password errors: ~s\" errors)\n      (cond\n        (errors\n         (with-form-errors (:errors errors\n                            :was-validated t)\n           (reset-password-after-confirmation :user user\n                                              :req req)))\n        (t\n         (with-transaction ()\n          (setf (auth:user-password user)\n                password))\n         (setf (used-up-p req) t)\n         <auth-template simple=t >\n           <div class= \"container\">\n             <p>Your password has changed. <a href= \"/login\">Go back to Login</a>.</p>\n           </div>\n         </auth-template>)))))\n\n(deftag reset-password-after-confirmation (&key user req)\n  (cond\n    ((used-up-p req)\n     <auth-template simple=t >\n       <p>That code has expired or has already been used</p>\n     </auth-template>)\n    (t\n     (let ((finish-reset (nibble (password confirm-password)\n                           (finish-password-reset :user user\n                                                  :req req\n                                                  :password password\n                                                  :confirm-password confirm-password))))\n       <auth-template simple=t >\n         <div class= \"container\">\n           <form method= \"POST\" action=finish-reset >\n             <div class= \"card\">\n               <div class= \"card-body\">\n                 <div class= \"form-group mb-3\">\n                   <label for= \"password\">Password</label>\n                   <input type= \"password\" id= \"password\" name= \"password\"\n                          class= \"form-control\" />\n                 </div>\n                 <div class= \"form-group mb-3\">\n                   <label for= \"password\">Confirm Password</label>\n                   <input type= \"password\" id= \"confirm-password\" name= \"confirm-password\"\n                          class= \"form-control\" />\n                 </div>\n               </div>\n               <div class= \"card-footer\">\n                 <input type= \"submit\" class= \"btn btn-primary form-control\" value= \"Update Password\" />\n               </div>\n             </div>\n           </form>\n         </div>\n       </auth-template>))))\n\n(defun password-recovery-mail (&key confirm)\n  <html>\n    <body>\n      <a href= (nibble:nibble-full-url confirm) >Click here to reset to your password.</a>\n      <p>If you didn't request this change of password, please contact us by replying to this email.</p>\n    </body>\n  </html>)\n\n(defun forgot-password-page ()\n  (let ((submit (nibble (email)\n                  (throttle! *throttler*)\n                  (let ((user (auth:find-user *installation* :email email)))\n                    (cond\n                      (user\n                       (let* ((request (make-instance 'change-password-request))\n                              (confirm (nibble ()\n                                         (reset-password-after-confirmation :user user\n                                                                            :req request))))\n                         (restart-case\n                             (send-mail (mailer*)\n                              :to email\n                              :subject \"Password recovery\"\n                              :html-message (password-recovery-mail :confirm confirm))\n                           (redirect-to-change-nibble-link ()\n                             (hunchentoot:redirect (nibble:nibble-full-url confirm)))))\n                       (forgot-password-confirmation :email email))\n                      (t\n                       (with-form-errors (:errors `((:email . \"No user with that email\"))\n                                          :was-validated t\n                                             :email email)\n                         (forgot-password-page))))))))\n    <auth-template simple=t >\n      <div class= \"mt-5 mb-5\">\n          <div class= \"row justify-content-center\">\n            <div class= \"col-lg-12\">\n              <form action=submit method= \"POST\">\n              <div class= \"card\">\n                <div class= \"card-header\">\n                  <h3>Recover your password</h3>\n                </div>\n                <div class= \"card-body\">\n                  <div class= \"form-group mb-3\">\n                    <label for= \"email\" class= \"form-label\" >Email</label>\n                    <input class= \"form-control\" type= \"email\" id= \"email\"\n                           name= \"email\"\n                           placeholder= \"xyz@example.com\" />\n                  </div>\n                </div>\n\n                <div class= \"card-footer\">\n                  <input type=\"submit\" class= \"btn btn-lg btn-primary\" value= \"Recover Password\" />\n                </div>\n                </div>\n              </form>\n              </div>\n            </div>\n\n        </div>\n    </auth-template>))\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/forgot-password\") ()\n  (forgot-password-page ))\n"
  },
  {
    "path": "src/auth/login/github.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/github\n  (:use :cl)\n  (:nicknames :screenshotbot/model/github\n              :auth/login/github)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:auth\n                #:oauth-user-avatar\n                #:oauth-user-email\n                #:oauth-user-full-name\n                #:oauth-user-user)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:util/store/permissive-persistent-class\n                #:permissive-persistent-class)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/migrations\n                #:ensure-symbol-in-package)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:export\n   #:%find-github-user-by-id\n   #:access-token-string\n   #:github-access-token\n   #:github-login\n   #:known-emails\n   #:oauth-access-token\n   #:oauth-user-avatar\n   #:oauth-user-email\n   #:oauth-user-full-name\n   #:oauth-user-user))\n(in-package :screenshotbot/login/github)\n\n(ensure-symbol-in-package\n #:github-user\n :old #:screenshotbot/model/user\n :new #:screenshotbot/model/github)\n\n(export 'github-user)\n\n(defindex +gh-user-id-index+\n  'fset-unique-index\n  :slot-name 'gh-user-id)\n\n(with-class-validation\n  (defclass github-user (store-object)\n    ((gh-user-id :type integer\n                 :initarg :gh-user-id\n                 :index +gh-user-id-index+\n                 :initform nil\n                 :index-reader %find-github-user-by-id)\n     (email :type (or null string)\n            :initarg :email\n            :initform nil\n            :accessor oauth-user-email)\n     (full-name :type (or null string)\n                :initarg :full-name\n                :initform nil\n                :reader %oauth-user-full-name\n                :writer (setf oauth-user-full-name))\n     (avatar :type (or null string)\n             :initarg :avatar\n             :initform nil\n             :accessor oauth-user-avatar)\n     (login :initform nil\n            :initarg :login\n            :accessor github-login)\n     (known-emails :initform nil\n                   :accessor known-emails)\n     (user :initarg :user\n           :initform nil\n           :accessor oauth-user-user))\n    (:metaclass permissive-persistent-class)))\n\n(defmethod oauth-user-full-name ((self github-user))\n  (cond\n    ((str:emptyp (%oauth-user-full-name self))\n     (github-login self))\n    (t\n     (%oauth-user-full-name self))))\n\n(defmethod github-user (user)\n  \"Get the github-user associated with a given user. I think it's only\nused by pro/admin code.\"\n  (loop for gu in (bknr.datastore:class-instances 'github-user)\n        if (eql user (ignore-errors (oauth-user-user gu)))\n          return gu))\n"
  },
  {
    "path": "src/auth/login/impersonation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/impersonation\n  (:use :cl)\n  (:nicknames :auth/login/impersonation)\n  (:import-from #:auth\n                #:current-user)\n  (:import-from #:util/cookies\n                #:cookies\n                #:get-cookie\n                #:set-cookie)\n  (:export\n   #:impersonate\n   #:impersonatedp\n   #:impersonation\n   #:logout\n   #:make-impersonation)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/impersonation)\n\n(defclass impersonation ()\n  ((cookies :initarg :cookies\n            :reader cookies)))\n\n(defmethod impersonate ((self impersonation) user)\n  (let ((admin-user (current-user)))\n    (setf (current-user) user)\n    (set-cookie (cookies self)\n                \"imp\" \"1\")\n    (setf (auth:session-value :admin-user)  admin-user)))\n\n(defmethod impersonatedp ((self impersonation))\n  (equal \"1\" (get-cookie (cookies self) \"imp\")))\n\n(defmethod admin-user ((self impersonation))\n  (auth:session-value :admin-user))\n\n(defmethod logout ((self impersonation))\n  (setf (auth:session-value :admin-user)  nil)\n  (set-cookie (cookies self) \"imp\" \"\"))\n\n(defun make-impersonation ()\n  \"In the past, we temporarily used a home-grown injector\nmechanism. That's why this impersonation class is so un-idiomatically\ndesigned.\"\n  (make-instance 'impersonation\n                 :cookies (make-instance 'cookies)))\n"
  },
  {
    "path": "src/auth/login/login.lisp",
    "content": ";;;; -*- coding: utf-8 -*-\n;;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/login\n  (:use :cl)\n  (:nicknames :auth/login/login)\n  (:import-from #:alexandria\n                #:if-let)\n  (:import-from #:auth\n                #:current-user\n                #:logged-in-p)\n  (:import-from #:core/installation/auth-provider\n                #:on-user-sign-in\n                #:auth-provider-signin-form\n                #:auth-providers\n                #:default-oidc-provider)\n  (:import-from #:core/installation/installation\n                #:site-alert\n                #:*installation*)\n  (:import-from #:markup/markup\n                #:deftag\n                #:unescaped)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:oidc/oidc\n                #:end-session-endpoint)\n  (:import-from #:screenshotbot/impersonation\n                #:impersonation\n                #:make-impersonation)\n  (:import-from #:screenshotbot/login/common\n                #:auth-common-header\n                #:or-divider\n                #:auth-template\n                #:ip-throttler\n                #:oauth-signin-link\n                #:signin-get\n                #:signup-get\n                #:standard-auth-provider)\n  (:import-from #:util/form-errors\n                #:with-error-builder\n                #:with-form-errors)\n  (:import-from #:util/throttler\n                #:throttle!)\n  (:import-from #:util.cdn\n                #:make-cdn)\n  (:import-from #:util/events\n                #:push-event)\n  (:export\n   #:auth-header-logo))\n(in-package :screenshotbot/login/login)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defparameter *throttler* (make-instance 'ip-throttler\n                                         :tokens 120))\n\n(defvar *signin-step1-throttler* (make-instance 'ip-throttler\n                                                :tokens 120))\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/login\") ()\n  (hex:safe-redirect \"/signin\"))\n\n(defun fix-input-email ()\n  (ps:ps\n    (funcall\n     (let ((ctr 0))\n       (lambda ()\n         (let ((el ((ps:@ document get-element-by-id) \"disabled-email\")))\n           (unless (or\n                    (equal (ps:@ el value)\n                           (ps:@ el dataset actual-value))\n                    (> ctr 1000))\n             (incf ctr)\n             (setf (ps:@ el value)\n                   (ps:@ el dataset actual-value)))))))))\n\n(defmethod sign-in-after-email ((auth-provider standard-auth-provider)\n                                &key\n                                email\n                                redirect)\n  (let ((result (nibble (password)\n                  (signin-post auth-provider\n                               :email email\n                               :password password\n                               :redirect redirect))))\n    <auth-template body-class=\"signin-v2\" simple=t >\n      <div class= \"account-pages mt-5 mb-5\" >\n          <div class= \"card border-0 account-card\">\n            <div class= \"card-header\">\n              <auth-common-header>\n                Log in to your account\n              </auth-common-header>\n\n              <div class= \"card-body p-4\">\n                <div class= \"alert alert-danger d-none\" ></div>\n                <div class=\"form-group mb-3\">\n\n                  <input\n                    class=\"form-control disabled-email\" disabled= \"\" value=email autocompletion= \"off\" readonly= \"\" id= \"disabled-email\"\n                    data-actual-value=email\n                    onchange= (fix-input-email)\n                    />\n\n                </div>\n\n                <form action=result method= \"POST\" >\n\n\n                  <div class=\"form-group mb-3 mt-3\">\n                    <div class=\"input-group input-group-merge\">\n                      <input name= \"password\" type=\"password\" id=\"password\" class=\"form-control\" placeholder=\"Password\" />\n                    </div>\n                  </div>\n\n                  <input type= \"submit\" class= \"btn btn-primary mb-2\" value= \"Log In\" />\n                  <div class= \"d-flex justify-content-between\">\n                    <div class= \"mb-3\" >\n                      <a href= \"/signin\">Re-enter email</a>\n                    </div>\n                    <div class= \"mb-3\" >\n                      <a href=\"/forgot-password\" class=\"links\"><small>Forgot your password?</small></a>\n                    </div>\n\n                  </div>\n\n                  <div class= \"float-end\">\n\n                  </div>\n\n                </form>\n\n              </div>\n            </div>\n          </div>\n      </div>\n    </auth-template>))\n\n(defmethod sign-in-step1-post ((auth-provider standard-auth-provider) &key email redirect)\n  (throttle! *signin-step1-throttler*)\n  (with-error-builder (:check check :errors errors\n                       :form-builder (signin-get)\n                       :success (hex:safe-redirect\n                                 (nibble ()\n                                   (sign-in-after-email auth-provider\n                                                        :email email\n                                                        :redirect redirect))))\n    (check :email (not (str:emptyp email))\n           \"Email must be provided\")\n    (check :email (< (length email) 250)\n           \"Email is too long\")\n    (check :email (auth:find-user *installation* :email email)\n           (format nil \"Could not find a user with email: ~a\" email))\n    (push-event :signin-attempt :email email)))\n\n(defmethod auth-provider-signin-form ((auth-provider standard-auth-provider) redirect)\n\n  (let ((result (nibble (email)\n                  (sign-in-step1-post\n                   auth-provider\n                   :email email\n                   :redirect redirect))))\n  <form action=result method= \"POST\" >\n    <div class= \"alert alert-danger d-none\" ></div>\n    <div class=\"form-group mb-3\">\n      <input name= \"email\"  class=\"form-control\" type=\"email\" id=\"emailaddress\" required=\"\" placeholder=\"Email\" autocomplete= \"username\" />\n    </div>\n\n    <div class=\"form-group mb-3\" >\n      <button class=\"btn btn-primary\" style= \"width: 100%\" type=\"submit\"> Next </button>\n    </div>\n  </form>))\n\n(defmethod default-login-redirect (request)\n  \"/runs\")\n\n(deftag signin-get (&key (redirect (default-login-redirect hunchentoot:*request*)) (alert nil))\n  (assert redirect)\n  (if-let ((provider (default-oidc-provider *installation*)))\n    (hex:safe-redirect (oauth-signin-link provider redirect))\n    (let ((signup (nibble ()\n                    (signup-get :redirect redirect\n                                :alert alert))))\n      <auth-template body-class= \"signin-v2\" simple=t  >\n\n        <div class=\"account-pages mt-5 mb-5\">\n          ,(site-alert *installation*)\n          ,(progn alert)\n          <div class=\"card border-0 account-card\">\n\n            <div class= \"card-header\">\n              <auth-common-header >\n                Log in to your account\n              </auth-common-header>\n            </div>\n\n            <div class=\"card-body p-4\">\n              ,@ (loop for auth-provider-cons on (auth-providers *installation*)\n                       for count from 0\n                       for auth-provider = (car auth-provider-cons)\n                       collect\n                       (let ((content (auth-provider-signin-form auth-provider redirect)))\n                         (if (and (= count 0) (cdr auth-provider-cons))\n                             <markup:merge-tag>\n                               ,(progn content)\n                               <or-divider />\n                               <div style=\"margin-top:2em\" />\n                             </markup:merge-tag>\n                             content)))\n            </div> <!-- end card-body -->\n          </div>\n          <!-- end card -->\n\n          <div class=\"row mt-3\">\n            <div class=\"col-12 text-center\">\n              <p class=\"signup-message\">Don't have an account? <a href=signup class=\"ms-1\"><b>Sign Up</b></a></p>\n\n            </div> <!-- end col -->\n          </div>\n          <!-- end row -->\n        </div>\n        <!-- end page -->\n      </auth-template>)))\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/signin\") (after-logout redirect)\n  (declare (ignore after-logout))\n  (let ((redirect (or redirect (default-login-redirect hunchentoot:*request*))))\n    (when (logged-in-p)\n      (hex:safe-redirect redirect))\n    (signin-get :redirect redirect)))\n\n(defun signin-post (auth-provider &key email password redirect)\n  (throttle! *throttler*)\n  (let (errors\n        (user (auth:find-user *installation* :email email)))\n    (flet ((check (name test message)\n             (unless test\n               (push (cons name message) errors))))\n      (check nil (not (str:emptyp email))\n             \"Please enter an email\")\n      (check :password (not (str:emptyp password))\n             \"Please enter a password\")\n      (check :password (< (length password) 150)\n             \"Password too long\")\n      (check nil user\n             (format nil \"No account associated with ~a\" email))\n      (when user\n        (check :password (auth:password-hash user)\n               \"This account appears to use an OAuth (either Google or GitHub)\")\n\n        (when (auth:password-hash user)\n          (check :password (auth:check-password user password)\n                 \"Password does not match the given account\"))))\n    (cond\n      (errors\n       (with-form-errors (:errors errors\n                            :was-validated t)\n        (sign-in-after-email auth-provider\n                             :email email\n                             :redirect redirect)))\n      (t\n       (assert (auth:check-password user password))\n       (on-user-sign-in auth-provider user)\n       (setf (current-user) user)\n       (hex:safe-redirect redirect)))))\n\n\n(defun hard-signout ()\n  (setf (current-user) nil)\n  (hex:safe-redirect \"/\"))\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/account/oauth-signout-callback\") ()\n  (hard-signout))\n\n(defun signout-from-oidc (auth-provider)\n  (let ((endpoint (end-session-endpoint auth-provider)))\n    (cond\n      (endpoint\n       (hunchentoot:redirect\n        (quri:render-uri\n         (quri:copy-uri\n          (quri:uri endpoint)\n          :query `((\"post_logout_redirect_uri\" . ,(hex:make-full-url hunchentoot:*request* \"/account/oauth-signout-callback\")))))))\n      (t\n       (hard-signout)))))\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/signout\") ()\n  (let ((impersonation (make-impersonation)))\n    (screenshotbot/impersonation:logout impersonation)\n    (if-let ((default-auth-provider (default-oidc-provider *installation*)))\n      (signout-from-oidc default-auth-provider)\n      (hard-signout))))\n\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/account/is-logged-in\") ()\n  (setf (hunchentoot:content-type*) \"application/json\")\n  (json:encode-json-to-string\n   `((:is-logged-in . ,(if (auth:current-user) t :false)))))\n"
  },
  {
    "path": "src/auth/login/oidc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;; CAREFUL: BKNR.DATASTORE CLASSES IN HERE!\n(defpackage :screenshotbot/login/oidc\n  (:use :cl)\n  (:nicknames :auth/login/oidc)\n  (:import-from #:auth\n                #:current-user\n                #:oauth-user-avatar\n                #:oauth-user-email\n                #:oauth-user-full-name\n                #:oauth-user-user\n                #:user-email)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:oidc/oidc\n                #:after-authentication\n                #:logout-link\n                #:make-oidc-auth-link\n                #:oidc)\n  (:import-from #:screenshotbot/login/common\n                #:abstract-oauth-provider\n                #:after-create-user\n                #:oauth-callback\n                #:oauth-logo-svg\n                #:oauth-signin-link\n                #:oauth-signup-link)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:import-from #:core/installation/auth-provider\n                #:on-user-sign-in)\n  (:export\n   #:access-token-class\n   #:access-token-str\n   #:authorization-endpoint\n   #:client-id\n   #:client-secret\n   #:discover\n   #:end-session-endpoint\n   #:find-existing-oidc-user\n   #:find-oidc-user-by-id\n   #:identifier\n   #:issuer\n   #:oauth-user-avatar\n   #:oauth-user-email\n   #:oauth-user-full-name\n   #:oauth-user-user\n   #:oidc-callback\n   #:oidc-provider\n   #:oidc-provider-identifier\n   #:oidc-user\n   #:prepare-oidc-user\n   #:scope\n   #:token-endpoint\n   #:update-oidc-user\n   #:userinfo-endpoint))\n(in-package :screenshotbot/login/oidc)\n\n(defclass oidc-provider (abstract-oauth-provider\n                         oidc)\n  ((identifier :initarg :identifier\n               #+screenshotbot-oss #+screenshotbot-oss\n               :initform 'default-oidc\n               :accessor oidc-provider-identifier)\n   (expiration-seconds :initarg :expiration-seconds\n                       :initform nil\n                       :reader expiration-seconds))\n  (:default-initargs\n   :oauth-name \"Generic OIDC\"))\n\n(defindex +user-id-index+\n  'fset-set-index\n  :slot-name 'user-id)\n\n(with-class-validation\n  (defclass oidc-user (store-object)\n    ((email\n      :initarg :old-email-slot\n      :documentation \"Old email slot. After a few restarts, it should all be unbound and should be safe to delete.\")\n     (%email :initarg :email\n             :accessor oauth-user-email)\n     (full-name :initarg :full-name\n                :accessor oauth-user-full-name)\n     (avatar :initarg :avatar\n             :accessor oauth-user-avatar)\n     (user-id :initarg :user-id\n              :index +user-id-index+\n              :index-reader find-oidc-users-by-user-id)\n     (user :initarg :old-user-slot\n           :documentation \"Old user slot. After a few restarts, it should all be unbound and should be safe to delete\")\n     (%user :initarg :user\n            :initform nil\n            :accessor oauth-user-user)\n     (identifier :initarg :identifier\n                 :accessor oidc-provider-identifier))\n    (:metaclass persistent-class)))\n\n(defmacro define-slot-renames (&rest slots)\n  `(progn\n     (defmethod bknr.datastore:convert-slot-value-while-restoring ((self oidc-user)\n                                                                   slot-name\n                                                                   value)\n       (assert (symbolp slot-name))\n       (cond\n         ,@ (loop for (slot nil nil) in slots\n                  collect\n                  `((string= ,(str:substring 1 nil (string slot)) (string slot-name))\n                    (log:info \"Renaming slot ~a for ~a\" ,(str:substring 1 nil (string slot)) self)\n                    (call-next-method self ',slot value)))\n            (t\n             (call-next-method))))\n\n     ,@(loop for (nil old-slot acc) in slots\n              collect\n              `(defmethod ,acc :around ((self oidc-user))\n                 (cond\n                   ((slot-boundp self ',old-slot)\n                    (slot-value self ',old-slot))\n                   (t\n                    (call-next-method)))))\n\n     ,@(loop for (nil old-slot acc) in slots\n             collect\n             `(defmethod (setf ,acc) :after (val (self oidc-user))\n               (slot-makunbound self ',old-slot)\n                val))))\n\n(define-slot-renames\n    (%email email oauth-user-email)\n  (%user user oauth-user-user))\n\n;; (token-endpoint (make-instance 'oidc-provider :issuer \"https://accounts.google.com\"))\n\n(defmethod oauth-signin-link ((auth oidc-provider) redirect)\n  (make-oidc-auth-link auth redirect))\n\n(defmethod oauth-signup-link ((auth oidc-provider) redirect)\n  (make-oidc-auth-link auth redirect))\n\n(defmethod after-authentication ((auth oidc-provider) &key\n                                                       user-id\n                                                       email\n                                                       full-name\n                                                        avatar\n                                                        token)\n  (log:debug \"Got user info ~S\" user-id)\n  (let ((user (prepare-oidc-user\n               auth\n               :user-id user-id\n               :email email\n               :full-name full-name\n               :avatar avatar)))\n    (on-user-sign-in auth user)\n    (setf (current-user :expires-in (expiration-seconds auth)) user)))\n\n(defun update-oidc-user (oauth-user &key\n                                      (email (error \"required\"))\n                                      (user-id (error \"required\"))\n                                      (full-name (error \"required\"))\n                                      (avatar (error \"required\")))\n  \"Update the OIDC user, and return the corresponding USER (i.e. the\nuser as used in Screenshotbot)\"\n  (declare (ignore user-id))\n  (setf (oauth-user-email oauth-user) email)\n  (setf (oauth-user-full-name oauth-user) full-name)\n  (setf (oauth-user-avatar oauth-user) avatar)\n  (multiple-value-bind (user first-time-p)\n    (or\n     (oauth-user-user oauth-user)\n     (auth:find-or-create-user *installation* :email email))\n\n    ;; ensure two way mapping.\n    (pushnew oauth-user (auth:oauth-users user))\n    (setf (oauth-user-user oauth-user) user)\n\n    (maybe-set-user-primary-email\n     user\n     email)\n\n    (when first-time-p\n      (after-create-user *installation* user))\n    user))\n\n(defun maybe-set-user-primary-email (user email)\n  ;; what happens if there's another user with the current email?\n  ;; For instance, if the Oauth session changed their email address?\n  ;; In that case, we ignore and don't make the email change.\n  (let ((old-user-for-email (auth:find-user\n                             *installation*\n                             :email email)))\n    (cond\n      ((and\n        old-user-for-email\n        (not (eql old-user-for-email user)))\n       ;; uh oh, there's already a user with that email, we're not\n       ;; going to update their primary email.\n       (values))\n      (t\n       (setf (user-email user) email)))))\n\n\n\n(defgeneric prepare-oidc-user (auth &key user-id email full-name avatar)\n  (:documentation \"Once we have all the information about the user\n  that just logged in, convert this into a user in Screenshotbot. You\n  may have to look up existing users to figure out which user this is\n  mapped to.\"))\n\n(defmethod find-existing-oidc-user ((auth oidc-provider) user-id)\n  (fset:do-set (x (find-oidc-users-by-user-id user-id))\n    (when (eql (oidc-provider-identifier auth)\n               (oidc-provider-identifier x))\n      (return x))))\n\n(defmethod prepare-oidc-user ((auth oidc-provider)\n                              &rest all\n                              &key user-id email full-name avatar)\n  (declare (ignore email full-name avatar))\n    (let ((oidc-user (or\n                      (find-existing-oidc-user auth (oidc-provider-identifier auth))\n                      (make-instance 'oidc-user\n                                     :user-id user-id\n                                     :identifier (oidc-provider-identifier auth)))))\n      (apply 'update-oidc-user\n           oidc-user all)))\n\n(defmethod oauth-logo-svg ((auth oidc-provider))\n  (declare (ignore auth))\n  nil)\n\n\n(defmethod logout-link ((self oidc-provider))\n  \"See https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html.\n\nIn particular this means that /cognito/logout-confirmation must be in your 'Allowed sign-out URLs'\"\n  nil)\n"
  },
  {
    "path": "src/auth/login/roles-auth-provider.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/roles-auth-provider\n  (:use #:cl)\n  (:import-from #:core/installation/auth-provider\n                #:on-user-sign-in)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:export\n   #:roles-auth-provider))\n(in-package :auth/login/roles-auth-provider)\n\n(defclass roles-auth-provider ()\n  ((company-provider :initarg :company-provider\n                     :initform nil\n                     :reader company-provider\n                     :documentation \"A function, when evaluated, produces the company for which we should add a default logged-in user.\"))\n  (:documentation \"Adds a logged in user to a default role for a company\"))\n\n\n(defmethod on-user-sign-in :after ((self roles-auth-provider)\n                                   user)\n  (let ((company (cond\n                   ((company-provider self)\n                    (funcall (company-provider self)))\n                   (t\n                    (get-company-for-auth-provider\n                     *installation*\n                     self)))))\n    (when company\n      (roles:ensure-has-role company user 'roles:standard-member))))\n\n(defmethod get-company-for-auth-provider (installation (self roles-auth-provider))\n  \"By creating a method that is specialized on installation, we can make\nsure the OSS version behaves a certain way. Technically a little ugly,\nbut it's a good API.\"\n  nil)\n"
  },
  {
    "path": "src/auth/login/saml.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/saml\n  (:use #:cl)\n  (:import-from #:screenshotbot/login/login\n                #:auth-provider)\n  (:import-from #:screenshotbot/login/common\n                #:oauth-logo-svg\n                #:oauth-signin-link\n                #:abstract-oauth-provider))\n(in-package :auth/login/saml)\n\n(defclass saml-provider (abstract-oauth-provider)\n  ((metadata :initarg :metadata)))\n\n(defmethod oauth-signin-link ((self saml-provider) redirect)\n  \"/foo\")\n\n(defmethod oauth-logo-svg ((self saml-provider))\n  nil)\n"
  },
  {
    "path": "src/auth/login/signup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/signup\n  (:use :cl)\n  (:nicknames :auth/login/signup)\n  (:import-from #:auth\n                #:current-user\n                #:logged-in-p\n                #:user-email\n                #:user-first-name)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object\n                #:with-transaction)\n  (:import-from #:core/installation/auth-provider\n                #:on-user-sign-in\n                #:auth-provider-signup-form\n                #:auth-providers)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:markup/markup\n                #:deftag)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/login/common\n                #:allowed-domains\n                #:verify-email-p\n                #:auth-common-header\n                #:or-divider\n                #:after-create-user\n                #:auth-template\n                #:signin-get\n                #:signup-get\n                #:standard-auth-provider)\n  (:import-from #:core/installation/mailer\n                #:mailer*\n                #:send-mail)\n  (:import-from #:screenshotbot/model/invite\n                #:invite\n                #:invite-code\n                #:invite-email\n                #:invite-with-code\n                #:invites-with-email)\n  (:import-from #:auth/model/email-confirmation\n                #:email-confirmation-code\n                #:finish-confirmation\n                #:secret-code)\n  (:import-from #:util/form-errors\n                #:with-error-builder\n                #:with-form-errors)\n  (:import-from #:util/store/object-id\n                #:find-by-oid\n                #:oid)\n  (:import-from #:util/throttler\n                #:keyed-throttler\n                #:throttle!)\n  (:import-from #:screenshotbot/login/login\n                #:default-login-redirect)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:auth/model/invite\n                #:set-user-has-seen-invite)\n  (:import-from #:util.cdn\n                #:make-cdn)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/recaptcha\n                #:recaptcha\n                #:recaptcha-add-head-script\n                #:recaptcha-annotate-form\n                #:recaptcha-verify-token)\n  (:export\n   #:signup-get\n   #:signup-post\n   #:verify-email))\n(in-package :screenshotbot/login/signup)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *disabled-emails* (fset:empty-set))\n\n(defparameter *signup-throttler* (make-instance 'keyed-throttler\n                                                :tokens 20)\n  \"Throttles signups by IP address\")\n\n(defmethod verify-recaptcha ((auth-provider standard-auth-provider)\n                             token &key email)\n  (let ((score (recaptcha-verify-token (recaptcha *installation*)\n                                       token)))\n    (push-event :signup-risk-score\n                :email email\n                :score score)\n    (cond\n      ((< score 0.5)\n       (hex:safe-redirect\n        (nibble ()\n          \"Uh oh. You don't look human. If you think this is an error, ping support@screenshotbot.io\")))\n      (t\n       (values)))))\n\n(defmethod signup-after-email/get ((auth-provider standard-auth-provider)\n                                    &key\n                                      invite\n                                      email\n                                      plan\n                                      redirect)\n  (let ((post (nibble (password full-name accept-terms-p)\n                (signup-post\n                 auth-provider\n                 :email email\n                 :password password\n                 :full-name full-name\n                 :invite invite\n                 :accept-terms-p accept-terms-p\n                 :plan plan\n                 :redirect redirect))))\n    <sales-pitch-template>\n      <form method= \"POST\" action=post >\n                <div class=\"card border-0 account-card\">\n\n                  <div class= \"card-header\">\n                    <auth-common-header>\n                      Create your account\n                    </auth-common-header>\n                  </div>\n\n                  <input type= \"hidden\" name= \"email\" value=email />\n                  <div class=\"form-group mb-3\">\n                    <label for= \"full-name\" class= \"form-label text-muted\">Full name</label>\n                    <input name= \"full-name\" class=\"form-control\" type=\"text\" id=\"fullname\" placeholder=\"Full Name\" required= \"required\" />\n                  </div>\n\n\n                  <div class=\"form-group mb-3\">\n                    <label for= \"password\" class= \"form-label text-muted\">Password</label>\n                    <div class=\"input-group input-group-merge\">\n                      <input name= \"password\"  type=\"password\" id=\"password\" class=\"form-control\" placeholder=\"Password\" />\n                    </div>\n                  </div>\n\n                  <div class=\"form-check mb-3\">\n                    <input name= \"accept-terms-p\" type=\"checkbox\" class=\"form-check-input\" id=\"checkbox-signup\" />\n                    <label class=\"form-check-label\" for=\"checkbox-signup\">I accept the <a href=\"/terms\" class=\"\">Terms and Conditions</a></label>\n                  </div>\n\n\n                  <div class=\"form-group mb-3 text-center\">\n                    <button class=\"btn btn-primary\" type=\"submit\" id= \"sign-up-submit\" > Complete Sign Up </button>\n                  </div>\n\n                </div>\n              </form>\n    </sales-pitch-template>))\n\n(markup:deftag legal ()\n  <div class= \"row mt-3\">\n    <div class= \"col-12 text-center\">\n      <a href= \"/privacy\">Privacy Policy</a> ,(progn \"|\") <a href= \"/terms\">Terms and Conditions</a>\n    </div>\n  </div>)\n\n(markup:deftag sales-pitch-template (children)\n  <auth-template body-class= \"signin-v2\" simple=t full-width=t >\n      <div class=\"account-pages mb-5\">\n        <div class=\"container\">\n\n          <div class= \"row g-4 justify-content-center\">\n            <div class= \"col-md-10 col-lg-5\">\n              ,@children\n            </div>\n\n          </div>\n        </div>\n      </div>\n\n    </auth-template>)\n\n(defmethod signup-after-email/post ((auth-provider standard-auth-provider)\n                                    &rest args\n                                      &key\n                                        email\n                                        plan\n                                        invite\n                                        redirect\n                                    &allow-other-keys)\n  (with-error-builder (:check check :errors errors\n                       :form-builder (signup-get :plan plan\n                                                 :invite invite\n                                                 :redirect redirect)\n                       :success (hex:safe-redirect\n                                 (nibble ()\n                                   (apply #'signup-after-email/get auth-provider args))))\n    (push-event :signup-attempt :email email\n                                :ip-address (hunchentoot:real-remote-addr))\n\n    (check :email (not (fset:contains? *disabled-emails* email))\n           \"This email is blocked from creating an account. Please reach out to us at support@screenshotbot.io\")\n\n    (check-email\n     auth-provider\n     #'check\n     :email\n     email)))\n\n(defmethod auth-provider-signup-form ((auth-provider standard-auth-provider) invite\n                                      plan\n                                      redirect)\n  (let* ((invite-email (?. invite-email invite))\n         (post (nibble (email password full-name accept-terms-p plan g-recaptcha-response)\n                 (verify-recaptcha auth-provider g-recaptcha-response :email email)\n                 (signup-after-email/post\n                  auth-provider\n                  :email email\n                  :invite invite\n                  :plan plan\n                  :redirect redirect))))\n    (recaptcha-annotate-form\n     (recaptcha *installation*)\n     <form action=post method= \"POST\" id= \"standard-signup-form\" >\n      <input type= \"hidden\" name= \"invite-code\" value= (?. invite-code invite) />\n      <input type= \"hidden\" name= \"plan\" value=plan />\n\n      <div class=\"form-group mb-3\">\n        <label for= \"email\" class= \"form-label text-muted\">Work email</label>\n        <input name= \"email\" class=\"form-control\" type=\"email\" id=\"email\" required= \"required\"\n               placeholder= \"Work email\"\n               value=invite-email\n               />\n      </div>\n\n      <div class=\"form-group mb-3 text-center\">\n        <!-- this button will be transformed by recaptcha! -->\n        <button class=\"btn btn-primary\" id= \"sign-up-submit\"\n                type= \"submit\"\n                > Sign Up </button>\n      </div>\n\n    </form>)))\n\n(deftag or-signup-with ()\n    <div class= \"or-signup-with\" >\n        or Sign Up with\n    </div>)\n\n(markup:deftag sales-pitch ()\n  <div class= \"sales-pitch\" >\n    <ul>\n      <li><i class=\"material-icons\">smartphone</i>\n        <strong>Automate</strong>\n        <p>Screenshot tests at scale from 10s to 10s of thousands</p>\n      </li>\n      <li>\n        <i class=\"material-icons\">merge</i>\n        <strong>Integrations</strong>\n        <p>See UI changes directly in pull requests</p>\n      </li>\n      <li>\n        <i class=\"material-icons\">error</i>\n        <strong>Alerts</strong>\n        <p>Spot UI regressions before your app ships</p>\n      </li>\n\n      <li>\n        <i class=\"material-icons\">people</i>\n        <strong>Collaborate</strong>\n        <p>Get notified on Slack, and share screenshots</p>\n      </li>\n      <li class= \"\" >\n        <i class=\"material-icons\">history</i>\n        <strong>Rewind</strong>\n        <p>Bisect UI regressions with just a few clicks</p>\n      </li>\n\n      <li>\n        <i class=\"material-icons\">code</i>\n        <strong>Flexible</strong>\n        <p>Use your existing iOS, Android or Web screenshot testing libraries</p>\n      </li>\n</ul>\n\n    <div class= \"preview-wrapper\">\n      <img src= (make-cdn \"/assets/images/screenshotbot-preview-screenshot-3.webp\") class= \"shadow-lg\" />\n    </div>\n  </div>)\n\n(deftag signup-get (&key plan (redirect (default-login-redirect hunchentoot:*request*))\n                    invite\n                    alert)\n  (let ((login (nibble ()\n                 (signin-get :redirect redirect\n                             :alert alert))))\n    (recaptcha-add-head-script\n     (recaptcha *installation*)\n     <sales-pitch-template>\n      <div class=\"card border-0 account-card\">\n\n        <div class= \"card-header\">\n          <auth-common-header>\n            Let's get you signed up!\n          </auth-common-header>\n        </div>\n\n        <div class=\"card-body p-4\">\n          ,(progn alert)\n\n          ,@ (let ((len (length (auth-providers *installation*))))\n               (loop for auth-provider in (auth-providers *installation*)\n                     for idx from 0\n                     collect\n                     (auth-provider-signup-form auth-provider invite\n                                                plan\n                                                redirect)\n                     if (and (eql idx 0) (> len 1))\n                       collect  <or-divider />))\n        </div>\n\n      </div>\n      <div class=\"row mt-3\">\n        <div class=\"col-12 text-center\">\n          <p class=\"\">Already have account? <a href=login class=\"ml-1\"><b>Log In</b></a></p>\n        </div>\n      </div>\n\n      <legal />\n      \n    </sales-pitch-template>)))\n\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/signup\") (plan)\n  (when (logged-in-p)\n    (hex:safe-redirect (default-login-redirect hunchentoot:*request*)))\n\n  (statsig:push-event :signup-page)\n\n  (signup-get :plan plan))\n\n(defun valid-email-address-p (string)\n  \"This comes from clavier::valid-email-address-p, but fixed for\nbugs. (See corresponding tests.)\"\n  (not (null\n\t(ppcre:scan \"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,12}$\" string))))\n\n(defun validate-name (check-fn full-name)\n  (flet ((check (&rest args)\n           (apply check-fn args)))\n    (check :full-name\n           (util:token-safe-for-email-p full-name)\n           \"That name looks invalid\")\n    (check :full-name\n           (< (length full-name) 150)\n           \"Name is too long\")\n    (check :full-name\n           (not (str:emptyp (str:trim full-name)))\n           \"We would really like you to introduce yourself!\")))\n\n(defun check-email (auth-provider check field email)\n  (flet ((check (&rest args)\n           (apply check field args)))\n    (check (< (length email) 150)\n           \"Password is too long\")\n    (check (valid-email-address-p email)\n           \"That doesn't look like a valid email address\")\n    (check (not (auth:find-user *installation* :email (string-downcase email)))\n           (format nil \"That email address is already in use: ~a\" email))\n    (when (valid-email-address-p email)\n      (check\n       (allowed-domain-p auth-provider email)\n       \"You may not sign up with this email domain.\"))))\n\n(defun allowed-domain-p (auth-provider email)\n  (cond\n    ((eql :all (allowed-domains auth-provider))\n     t)\n    (t\n     (let ((domain (car (last (str:split \"@\" email)))))\n       (str:s-member (allowed-domains auth-provider) domain)))))\n\n(defun create-user-and-complete-signup (auth-provider\n                                        &key email password full-name plan redirect invite)\n  \"Create the user account and complete the signup process.\nThis is called after all validation (including email verification if required).\"\n  (let ((user (auth:make-user\n               *installation*\n               :full-name full-name\n               :confirmed-p (verify-email-p auth-provider)\n               :email email)))\n    (with-transaction ()\n      (setf (auth:user-password user)\n            password))\n    (on-user-sign-in auth-provider user)\n    (setf (current-user) user)\n\n    (process-existing-invites user email :current-invite invite)\n    (unless (verify-email-p auth-provider)\n      (prepare-and-send-email-confirmation user))\n    (after-create-user *installation* user))\n\n  (cond\n    ((string= (string-upcase plan) (string :professional))\n     (hex:safe-redirect \"/upgrade\"))\n    (t\n     (hex:safe-redirect redirect))))\n\n(defun signup-post (auth-provider\n                    &key email password full-name accept-terms-p plan redirect\n                      invite)\n  (throttle! *signup-throttler* :key (hunchentoot:real-remote-addr))\n  (let ((errors))\n    (flet ((check (name test message)\n             (unless test\n               (push-event :signup-failure\n                           :test (string test)\n                           :email email\n                           :full-name full-name\n                           :message message)\n\n               (push (cons name message) errors))))\n      (check :password (and password (>= (length password) 8))\n             \"Please use at least 8 letters for the password\")\n\n      (check-email\n       auth-provider\n       #'check\n       :email\n       email)\n\n      (validate-name #'check full-name)\n\n      (check :password\n             (< (length password) 150)\n             \"Password is too long\")\n      (check :accept-terms-p\n             accept-terms-p\n             \"Please accept the terms and conditions to continue\")\n      (cond\n        (errors\n         (hex:safe-redirect\n          (nibble ()\n            (with-form-errors (:password password\n                            :full-name full-name\n                            :errors errors\n                            :accept-terms-p accept-terms-p\n                            :was-validated t)\n             (signup-after-email/get\n              auth-provider\n              :email email\n              :plan plan\n              :invite invite\n              :redirect redirect)))))\n        (t\n         ;; Validation passed - now either verify email or create user directly\n         (flet ((%create-user ()\n                  (create-user-and-complete-signup\n                   auth-provider\n                   :email email\n                   :password password\n                   :full-name full-name\n                   :plan plan\n                   :redirect redirect\n                   :invite invite)))\n           (cond\n             ((verify-email-p auth-provider)\n              ;; Email verification required - verify first, then create account\n              (verify-email email\n                            :callback (lambda () (%create-user))))\n             (t\n              ;; No email verification - create account immediately\n              (%create-user)))))))))\n\n(defun prepare-and-send-email-confirmation (user)\n  \"Send an email confirmation to the USER\"\n  (let ((confirmation (make-instance 'email-confirmation-code\n                                     :user user)))\n    (send-signup-confirmation (user-email user)\n                              (user-first-name user)\n                              confirmation)))\n\n(defun process-existing-invites (user email &key current-invite)\n  \"When the USER is created, the user might have existing invites. Do\nany processing related to those invites here.\n\nCURRENT-INVITE is the current invite being used while signing up. This\nmight not be an existing invite because when you sign up you might use\na different email.\"\n  (when current-invite\n    (set-user-has-seen-invite user current-invite))\n  (let ((invites (remove-if #'null\n                            (remove-duplicates\n                             (list*\n                              current-invite\n                              (invites-with-email email))))))\n    (with-transaction ()\n      (setf (auth:unaccepted-invites user)\n            invites))))\n\n(defun confirmation-success ()\n  <auth-template simple=t >\n    <section>\n      <div class= \"container full-height\">\n        <p>Thank you, your email has been confirmed.</p>\n        <p><a href= (default-login-redirect hunchentoot:*request*) >Click here to go back to the dashboard.</a></p>\n      </div>\n    </section>\n  </auth-template>)\n\n(hex:def-clos-dispatch ((self auth:auth-acceptor-mixin) \"/confirm-email\") (code id)\n  (handler-case\n      (let ((confirmation (find-by-oid id)))\n        (unless (string= code (secret-code confirmation))\n          (error \"secret code doesn't match\"))\n\n        (cond\n          ;; todo: handle expired links\n          (t\n           (finish-confirmation confirmation)\n           (confirmation-success))))))\n\n\n(defun render-signup-confirmation (first-name\n                                   confirmation\n                                   &key (confirmation-link\n                                         (hex:make-full-url hunchentoot:*request*\n                                                             \"/confirm-email\"\n                                                             :id (oid confirmation)\n                                                             :code (secret-code confirmation))))\n   <html>\n     <body>\n       <p>Hi ,(progn first-name),</p>\n\n       <p>Thank you for setting up your Screenshotbot account.</p>\n\n       <p>\n         Please <a href=confirmation-link >click here</a>\n         to confirm your email address.\n       </p>\n\n       <p>\n         If you need help or support please reach out to\n         <a href= \"mailto:support@screenshotbot.io\">support@screenshotbot.io</a>\n       </p>\n\n       <p>--Screenshotbot</p>\n\n     </body>\n  </html>)\n\n(defun send-signup-confirmation (email first-name confirmation)\n  (send-mail (mailer*)\n             :to email\n             :subject \"Welcome to Screenshotbot\"\n             :html-message (render-signup-confirmation first-name confirmation)))\n"
  },
  {
    "path": "src/auth/login/sso.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/sso\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:nibble\n                #:nibble))\n(in-package :auth/login/sso)\n\n(define-condition cant-view-company-condition (condition)\n  ((company :initarg :company\n            :reader needs-sso-condition-company\n            :reader cant-view-company-condition-company)))\n\n(define-condition needs-sso-condition (cant-view-company-condition)\n  ())\n\n(defgeneric maybe-redirect-for-sso (company final-redirect)\n  (:documentation \"Maybe redirect if SSO is enabled for the company, might not return if redirected. The redirect will finally redirecto to final-redirect\"))\n\n(def-easy-macro with-handle-needs-sso (&fn impl)\n  (let ((sso-company))\n    (handler-bind ((needs-sso-condition\n                     (lambda (condition)\n                       (setf sso-company (needs-sso-condition-company condition))))\n                   (auth:no-access-error\n                     (lambda (e)\n                       (declare (ignore e))\n                       (when sso-company\n                         (maybe-redirect-for-sso sso-company\n                                                 (nibble:allow-user-change\n                                                  (nibble ()\n                                                    (funcall impl))))))))\n      (funcall impl))))\n\n\n\n"
  },
  {
    "path": "src/auth/login/test-cached-avatar.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/test-cached-avatar\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:auth/login/cached-avatar\n                #:download-avatar\n                #:write-avatar)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:blob-pathname)\n  (:import-from #:auth/avatar\n                #:content-type\n                #:overriden-avatar)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:oidc/oidc\n                #:oauth-access-token))\n(in-package :auth/login/test-cached-avatar)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (with-test-store ()\n      (&body))))\n\n(test write-new-avatar\n  (with-fixture state ()\n    (finishes\n      (write-avatar 'user1\n                    :res #(65 66 67)\n                    :headers `((:content-type . \"image/jpg\"))))\n    (let ((oa (first (class-instances 'overriden-avatar))))\n      (is\n       (equal\n        \"ABC\"\n        (uiop:read-file-string\n         (blob-pathname oa))))\n      (is\n       (equal\n        \"image/jpg\"\n        (content-type oa))))\n    (finishes\n      (write-avatar 'user1\n                    :res #(66 67 68)\n                    :headers `((:content-type . \"image/png\"))))\n    (assert-that (class-instances 'overriden-avatar)\n                 (has-length 1))\n    (let ((oa (first (class-instances 'overriden-avatar))))\n      (is\n       (equal\n        \"BCD\"\n        (uiop:read-file-string\n         (blob-pathname oa))))\n      (is\n       (equal\n        \"image/png\"\n        (content-type oa))))))\n\n(test download-avatar-happy-path\n  (with-fixture state ()\n    (cl-mock:if-called 'http-request\n                       (lambda (url &key additional-headers ensure-success force-binary)\n                         (values #(65 66 67) 200 `((:content-type . \"image/png\")))))\n    (download-avatar 'user-1\n                     :token (make-instance 'oauth-access-token\n                                           :access-token \"foo\")\n                     :avatar \"https://example.com/image.png\")))\n\n(Test 403-crashes-and-logs\n  (With-fixture state ()\n    (cl-mock:if-called 'http-request\n                       (lambda (url &key additional-headers ensure-success force-binary)\n                         (values #(65 66 67) 403 `((:content-type . \"image/png\")))))\n    (signals simple-error\n     (download-avatar 'user-1\n                      :token (make-instance 'oauth-access-token\n                                            :access-token \"foo\")\n                      :avatar \"https://example.com/image.png\"))))\n\n(Test 404-does-not-crash\n  (With-fixture state ()\n    (cl-mock:if-called 'http-request\n                       (lambda (url &key additional-headers ensure-success force-binary)\n                         (values #(65 66 67) 404 `((:content-type . \"image/png\")))))\n    (finishes\n      (download-avatar 'user-1\n                       :token (make-instance 'oauth-access-token\n                                             :access-token \"foo\")\n                       :avatar \"https://example.com/image.png\"))\n    (Assert-that (bknr.datastore:class-instances 'overriden-avatar)\n                 (has-length 0))))\n"
  },
  {
    "path": "src/auth/login/test-roles-auth-provider.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/test-roles-auth-provider\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:auth/login/roles-auth-provider\n                #:roles-auth-provider)\n  (:import-from #:core/installation/auth-provider\n                #:on-user-sign-in)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:auth/model/roles\n                #:user-roles)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that))\n(in-package :auth/login/test-roles-auth-provider)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((*installation* 'fake-installation))\n     (let ((user 'test-user))\n       (&body)))))\n\n(test simple-on-user-sign-in\n  (with-fixture state ()\n    (let ((auth-provider (make-instance 'roles-auth-provider)))\n      (finishes\n        (on-user-sign-in auth-provider user))\n      (assert-that\n       (bknr.datastore:class-instances 'user-roles)\n       (has-length 0)))))\n\n(test simple-on-user-sign-in-with-a-company-provider-that-returns-nil\n  (with-fixture state ()\n    (let ((auth-provider (make-instance 'roles-auth-provider\n                                        :company-provider (lambda () nil))))\n      (finishes\n        (on-user-sign-in auth-provider user))\n      (assert-that\n       (bknr.datastore:class-instances 'user-roles)\n       (has-length 0)))))\n\n(test simple-on-user-sign-in-with-a-company-provider-that-returns-something\n  (with-fixture state ()\n    (let ((auth-provider (make-instance 'roles-auth-provider\n                                        :company-provider (lambda () 'something))))\n      (finishes\n        (on-user-sign-in auth-provider user))\n      (assert-that\n       (bknr.datastore:class-instances 'user-roles)\n       (has-length 1))\n      (is-true\n       (roles:has-role-p 'something user 'roles:standard-member)))))\n"
  },
  {
    "path": "src/auth/login/test-sso.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/test-sso\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:auth/login/sso\n                #:maybe-redirect-for-sso\n                #:needs-sso-condition\n                #:with-handle-needs-sso)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :auth/login/test-sso)\n\n(util/fiveam:def-suite)\n\n(test happy-path\n  (is (equal \"foobar\"\n             (with-handle-needs-sso ()\n               \"foobar\"))))\n\n(test simple-no-access-error-with-sso-issue\n  (signals auth:no-access-error\n    (with-handle-needs-sso ()\n      (error 'auth:no-access-error))))\n\n(defmethod maybe-redirect-for-sso ((company (eql 'foobar)) final-redirect)\n  (hex:safe-redirect \"/\"))\n\n(test sso-signal\n  (with-fake-request ()\n    (auth:with-sessions ()\n     (is\n      (equal nil ;; from the redirect\n             (catch 'hunchentoot::handler-done\n               (with-handle-needs-sso ()\n                 (signal 'needs-sso-condition :company 'foobar)\n                 (error 'auth:no-access-error))))))))\n\n"
  },
  {
    "path": "src/auth/login/test-verify-email.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/test-verify-email\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:auth/login/verify-email\n                #:*throttler*\n                #:enter-code-screen/post)\n  (:import-from #:core/installation/installation\n                #:abstract-installation\n                #:*installation*)\n  (:import-from #:screenshotbot/login/common\n                #:auth-template-impl)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:util/throttler\n                #:ip-throttler)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :auth/login/test-verify-email)\n\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (abstract-installation)\n  ())\n\n(defmethod auth-template-impl ((self my-installation) body &key &allow-other-keys)\n  body)\n\n(def-fixture state ()\n  (with-fake-request ()\n    (auth:with-sessions ()\n     (let ((*throttler* (make-instance 'ip-throttler :tokens 300)))\n       (let ((*installation* (make-instance 'my-installation)))\n         (let ((state (make-instance 'auth/login/verify-email::state\n                                     :code 123456\n                                     :email \"foo@example.com\"\n                                     :callback (lambda ()))))\n           (&body)))))))\n\n(defun %post-errors (state code)\n  (nth-value\n   1\n   (enter-code-screen/post state :entered-code code)))\n\n(test simple-post-error\n  (with-fixture state ()\n    (assert-that (%post-errors state \"22\")\n                 (has-length 1)\n                 (contains\n                  (is-equal-to\n                   '(:ENTERED-CODE . \"The code should be a six digit number that we sent to foo@example.com\"))))))\n\n(test too-many-attempts\n  (with-fixture state ()\n    (loop for i from 0 below 20\n          do \n          (%post-errors state \"22\"))\n    (assert-that (%post-errors state \"123456\")\n                 (has-length 1)\n                 (contains\n                  (is-equal-to\n                   '(:ENTERED-CODE . \"Too many attempts, please try signing up again\"))))))\n"
  },
  {
    "path": "src/auth/login/verify-email.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/login/verify-email\n  (:use #:cl)\n  (:import-from #:screenshotbot/mailer\n                #:mailer*\n                #:send-mail)\n  (:import-from #:screenshotbot/login/common\n                #:auth-common-header\n                #:auth-template)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/login/signup\n                #:verify-email\n                #:sales-pitch-template)\n  (:import-from #:util/form-errors\n                #:with-error-builder)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:ip-throttler))\n(in-package :auth/login/verify-email)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *lock* (bt:make-lock))\n\n(defvar *code-expiry* (* 20 60))\n\n(defvar *throttler* (make-instance 'ip-throttler\n                                   :tokens 300))\n\n(defclass state ()\n  ((code :initarg :code\n         :reader %code)\n   (callback :initarg :callback\n             :reader %callback)\n   (ts :initform (get-universal-time)\n       :reader %ts)\n   (attempts :initform 0\n             :accessor %attempts)\n   (email :initarg :email\n          :reader %email)))\n\n(defun verify-email (email &key callback)\n  \"Triggers an email verification flow, and once the email has been\nverified, the callback is called during the POST http method (so you\ncan mutate state).\"\n  (assert (eql :post (hunchentoot:request-method*)))\n  (throttle! *throttler*)\n  (let ((state (make-instance 'state\n                              :email email\n                              :callback callback\n                              :code (+ 100000 (secure-random:number 900000)))))\n    (send-code-email email (%code state))\n    (hex:safe-redirect\n     (nibble ()\n       (enter-code-screen state)))))\n\n(defun enter-code-screen (state)\n  (let ((post (nibble (entered-code)\n                (enter-code-screen/post\n                 state\n                 :entered-code entered-code))))\n    <sales-pitch-template  >\n      <form method= \"POST\" action= post >\n        <div class= \"card border-0 account-card\">\n          <div class= \"card-header\">\n            <auth-common-header>\n              Enter the code we sent to ,(%email state)\n            </auth-common-header>\n          </div>\n\n          <div class= \"card-body\">\n            <div class= \"form-group mb-3\">\n              <label for= \"code\" class= \"form-label text-muted\">Code</label>\n              <input name= \"entered-code\" class= \"form-control number-to-text\" type= \"number\" id= \"code\" placeholder= \"000000\" required= \"required\" />\n            </div>\n\n            <div class=\"form-group mb-3 text-center\">\n              <button class=\"btn btn-primary\" type=\"submit\" id= \"sign-up-submit\" > Verify Email </button>\n            </div>\n          </div>\n        </div>\n      </form>\n\n      <form method=\"POST\" action=(nibble (:method :post) (verify-email (%email state) :callback (%callback state))) >\n        <div class= \"form-group mb-3 text-center\">\n          <button class= \"btn btn-link text-muted\" type= \"submit\" >\n            Request a new code\n          </button>\n        </div>\n      </form>\n\n\n    </sales-pitch-template>))\n\n(defun enter-code-screen/post (state &key entered-code)\n  (throttle! *throttler*)\n  (bt:with-lock-held (*lock*)\n    (incf (%attempts state)))\n  (with-error-builder (:check check :errors errors\n                       :form-builder (enter-code-screen state)\n                       :success (funcall (%callback state)))\n    (cond\n      ((bt:with-lock-held (*lock*)\n         (> (%attempts state) 20))\n       (warn \"Too many attempts at guessing code for ~a\"\n             (%email state))\n       (check :entered-code\n              nil\n              \"Too many attempts, please try signing up again\"))\n      ((not (equal 6 (length entered-code)))\n       (check :entered-code\n              nil\n              (format nil\n                      \"The code should be a six digit number that we sent to ~a\"\n                      (%email state))))\n      (t\n       (let ((entered-code (parse-integer entered-code :junk-allowed t)))\n         (check :entered-code\n                (equal entered-code (%code state))\n                \"The code does not match what we sent over email\")\n         (check :entered-code (> (+ (%ts state) *code-expiry*) (get-universal-time))\n                \"The code we sent you has expired. Please request a new code.\"))))))\n\n\n(defun send-code-email (email code)\n  (send-mail\n     (mailer*)\n     :to email\n     :subject (format nil \"~a is your code for Screenshotbot\" code)\n     :html-message\n     <html>\n       <body>\n         <p>Please enter this code to create your account on Screenshotbot.\n         </p>\n\n         <h1>,(progn code)</h1>\n\n         <p>This code expires in 20 minutes.</p>\n\n         <p>Screenshotbot is an Enterprise-grade developer tool. To keep our customers secure and enable collaboration\n           we must verify each user's email address.</p>\n\n         <p>If you were not trying to creating an account on Screenshotbot, please ignore this email and do not share this code with any person or website. If you have any questions, please contact us at <a href= \"mailto:support@screenshotbot.io\">support@screenshotbot.io</a>.</p>\n       </body>\n     </html>))\n"
  },
  {
    "path": "src/auth/model/auth.model.asd",
    "content": "(defsystem :auth.model\n  :serial t\n  :depends-on (:auth\n               :util\n               :util.store)\n  :components ((:file \"user\")\n               (:file \"roles\")\n               (:file \"company-sso\")\n               (:file \"invite\")\n               (:file \"email-confirmation\")))\n\n(defsystem :auth.model/tests\n  :serial t\n  :depends-on (:auth.model\n               :fiveam-matchers\n               :util/fiveam)\n  :components ((:file \"test-email-confirmation\")\n               (:file \"test-invite\")\n               (:file \"test-roles\")))\n"
  },
  {
    "path": "src/auth/model/company-sso.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/company-sso\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object))\n(in-package :auth/model/company-sso)\n\n(defclass company-sso (store-object)\n  ((%company :initarg :company\n             :reader company-sso-company)\n   (%domain :initarg :domain\n            :reader company-sso-domain))\n  (:metaclass persistent-class))\n\n(defclass company-oidc-sso (company-sso)\n  ((issuer :initarg :issuer\n           :accessor company-sso-issuer)\n   (client-id :initarg :client-id\n              :accessor company-sso-client-id)\n   (client-secret :initarg :client-secret\n                  :accessor compqany-sso-client-secret))\n  (:metaclass persistent-class))\n"
  },
  {
    "path": "src/auth/model/email-confirmation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/email-confirmation\n  (:use #:cl)\n  (:import-from #:util/store/object-id\n                #:object-with-oid)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:export\n   #:secret-code\n   #:confirmation-code-email\n   #:confirmation-confirmed-p\n   #:confirmation-user\n   #:user-email-confirmed-p\n   #:finish-confirmation))\n(in-package :auth/model/email-confirmation)\n\n(defindex +user-index+\n  'fset-set-index\n  :slot-name '%user)\n\n(with-class-validation\n  (defclass email-confirmation-code (object-with-oid)\n    ((code :initform (util:make-secret-code)\n           :type string\n           :reader secret-code)\n     (email :type (or null string)\n            :initform nil\n            :initarg :email\n            :reader confirmation-code-email)\n     (confirmed-p\n      :type boolean\n      :initform nil\n      :accessor confirmation-confirmed-p)\n     (%user\n      :initarg :user\n      :index +user-index+\n      :index-reader %find-by-user\n      :accessor confirmation-user))\n    (:metaclass persistent-class)))\n\n(defmethod finish-confirmation ((self email-confirmation-code))\n  (setf (confirmation-confirmed-p self) t))\n\n(defmethod user-email-confirmed-p (user)\n  (fset:do-set (cc (%find-by-user user))\n    (when (confirmation-confirmed-p cc)\n      (return t))))\n"
  },
  {
    "path": "src/auth/model/invite.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/invite\n  (:use :cl)\n  (:nicknames :screenshotbot/model/invite)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util\n                #:make-secret-code)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:export\n   #:all-invites\n   #:email-count\n   #:invite\n   #:invite-code\n   #:invite-company\n   #:invite-email\n   #:invite-used-p\n   #:invite-with-code\n   #:inviter\n   #:invites-with-email\n   #:all-unused-invites\n   #:set-user-has-seen-invite))\n(in-package :screenshotbot/model/invite)\n\n(defindex +email-index+\n  'fset-set-index\n  :slot-name 'email)\n\n(with-class-validation\n  (defclass invite (store-object)\n    ((code :initform (make-secret-code)\n           :accessor invite-code)\n     (inviter :initarg :inviter\n              :accessor inviter)\n     (email :initarg :email\n            :initform nil\n            :index +email-index+\n            :index-reader %invites-with-email\n            :reader invite-email)\n     (used-p :initform nil\n             :accessor invite-used-p)\n     (email-count\n      :initform 0\n      :accessor email-count)\n     (company :initarg :company\n              :accessor invite-company))\n    (:metaclass persistent-class)))\n\n(defindex +proof-user-index+\n  'fset-set-index\n  :slot-name '%user)\n\n(with-class-validation\n  (defclass invite-code-proven (store-object)\n    ((%user :initarg :user\n            :index +proof-user-index+\n            :reader invite-user)\n     (%invite :initarg :invite\n              :reader invite))\n    (:metaclass persistent-class)\n    (:documentation \"This user has verified that they have access to the invitation\ncode (probably by clicking the sign-up link from an invitation.\")))\n\n(defmethod print-object ((invite invite) out)\n  (with-slots (email) invite\n   (format out \"#<INVITE ~a>\" email)))\n\n(defmethod initialize-instance :after ((invite invite) &key company &allow-other-keys)\n  (assert company))\n\n(defun all-invites (&key company)\n  (loop for invite in (bknr.datastore:class-instances 'invite)\n        if (or\n            (not company)\n            (eql company (invite-company invite)))\n          collect invite))\n\n(defun all-unused-invites (&key company)\n  (remove-if #'invite-used-p (all-invites :company company)))\n\n(defun invites-with-email (email &key company)\n  (remove-if #'invite-used-p\n   (let ((invites (fset:convert 'list (%invites-with-email email))))\n     (cond\n       (company\n        (loop for invite in invites\n              if (eql (invite-company invite) company)\n                collect invite))\n       (t\n        invites)))))\n\n(defun invite-with-code (code &key company)\n  (loop for invite in (all-invites :company company)\n        if (string= code (invite-code invite))\n          return invite))\n\n\n(defmethod set-user-has-seen-invite (user (invite invite))\n  (make-instance 'invite-code-proven\n                 :user user\n                 :invite invite))\n"
  },
  {
    "path": "src/auth/model/roles.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/roles\n  (:use #:cl)\n  (:nicknames #:roles)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index\n                #:fset-set-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:index-get)\n  (:import-from #:alexandria\n                #:when-let)\n  (:export\n   #:set-role\n   #:user-role\n   #:has-role-p\n   #:read-only\n   #:reviewer\n   #:disabled-user\n   #:standard-member\n   #:integrations-developer\n   #:owner\n   #:guest\n   #:external-member\n   #:site-admin\n   #:admin\n   #:users-for-company\n   #:companies-for-user\n   #:hidden-user\n   #:hidden-admin\n   #:role-friendly-name\n   #:company-owner\n   #:ensure-has-role\n   #:role-user\n   #:role-company))\n(in-package :auth/model/roles)\n\n;;;; See https://phabricator.tdrhq.com/w/user_roles/\n\n(defclass role ()\n  ())\n\n(defclass read-only (role)\n  ())\n\n(defclass reviewer (role)\n  ())\n\n(defclass disabled-user (role)\n  ())\n\n(defclass standard-member (read-only reviewer)\n  ())\n\n(defclass integrations-developer (standard-member)\n  ()\n  (:documentation \"Can access settings related to creating integrations\"))\n\n(defclass admin (standard-member)\n  ())\n\n(defclass owner (admin)\n  ())\n\n(defclass hidden-user (role)\n  ()\n  (:documentation \"Will not show up on the members page\"))\n\n(defclass hidden-admin (admin hidden-user)\n  ()\n  (:documentation \"Typically manually set for enterprise installs for site-admins\"))\n\n(defclass guest (read-only)\n  ())\n\n(defclass external-member (guest reviewer)\n  ())\n\n(defclass site-admin (owner)\n  ())\n\n(defmethod role-friendly-name ((role role))\n  (cond\n    ((eql (type-of role) 'standard-member)\n     \"Member\")\n    (t\n     (str:sentence-case (format nil \"~a\" (type-of role))))))\n\n(defindex +user-role-index+\n  'fset-unique-index\n  :slots '(%user %company))\n\n(defindex +company-index+\n  'fset-set-index\n  :slot-name '%company)\n\n(defindex +user-index+\n  'fset-set-index\n  :slot-name '%user)\n\n(defclass user-roles (store-object)\n  ((%user :initarg :user\n          :index +user-index+\n          :index-reader user-roles-for-user\n          :accessor role-user)\n   (%company :initarg :company\n             :index +company-index+\n             :index-reader user-roles-for-company\n             :accessor role-company)\n   (role :initarg :role\n         :accessor role-type\n         :documentation \"A symbol corresponding to the type of role.\"))\n  (:class-indices (user-role-index\n                   :index +user-role-index+\n                   :slots (%user %company)))\n  (:metaclass persistent-class))\n\n(defvar *lock* (bt:make-lock))\n\n(defmethod (setf user-role) ((value symbol) company user)\n  (cond\n    ((null user) ;; This might happen in scripts\n     (warn \"Not setting a role for a NIL user for company ~a\" company))\n    (t\n     (bt:with-lock-held (*lock*)\n       (let ((role (index-get +user-role-index+ (list user company))))\n         (cond\n           ((and role (not value))\n            ;; delete the role\n            (bknr.datastore:delete-object role))\n           (role\n            (setf (role-type role) value))\n           (value\n            (make-instance 'user-roles\n                           :user user\n                           :company company\n                           :role value))))))))\n\n(defmethod user-role (company user)\n  (when-let ((role (index-get +user-role-index+\n                              (list user company))))\n    (make-instance\n     (role-type role))))\n\n(defmethod users-for-company (company)\n  (fset:convert\n   'list\n   (fset:image #'role-user (user-roles-for-company company))))\n\n(defmethod find-users-for-role (company role)\n  (let ((user-roles (user-roles-for-company company)))\n    )\n  )\n\n(defmethod company-owner (company)\n  (fset:do-set (user-role (user-roles-for-company company))\n    (when (eql 'owner (role-type user-role))\n      (return (role-user user-role)))))\n\n(defmethod companies-for-user (user)\n  (fset:convert\n   'list\n   (fset:image #'role-company (user-roles-for-user user))))\n\n(defmethod has-role-p (company user type)\n  \"Check if a user has a role of type TYPE. It is allowed to use T as a\ntype to check if the user is part of the company at all.\"\n  (assert (find-class type))\n  (when-let ((role (user-role company user)))\n    (typep role type)))\n\n(defmethod ensure-has-role (company user type)\n  \"Ensures that the user has at least this role. If the role is not\nsatisfied, then it is set to this role.\"\n  ;; user-role may be overriden, but we want to sync it if that's the\n  ;; case.\n  (let ((role (user-role company user))\n        (saved-role (bknr.indices:index-get +user-role-index+\n                                            (list company user))))\n    (cond\n      ((not (typep role type))\n       (setf (user-role company user) type))\n      ((not saved-role)\n       (setf (user-role company user)\n             (type-of role))))))\n"
  },
  {
    "path": "src/auth/model/test-email-confirmation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/test-email-confirmation\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:auth/model/email-confirmation\n                #:finish-confirmation\n                #:user-email-confirmed-p\n                #:email-confirmation-code))\n(in-package :auth/model/test-email-confirmation)\n\n(util/fiveam:def-suite)\n\n(defclass fake-user (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((user (make-instance 'fake-user)))\n      (&body))))\n\n(test simple-user-confirmed\n  (with-fixture state ()\n    (is-false (user-email-confirmed-p user))\n    (let ((cc (make-instance 'email-confirmation-code\n                   :email \"foo@example.com\"\n                   :user user)))\n      (is-false (user-email-confirmed-p user))\n      (finish-confirmation cc)\n      (is-true (user-email-confirmed-p user)))))\n"
  },
  {
    "path": "src/auth/model/test-invite.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/test-invite\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:auth/model/invite\n                #:set-user-has-seen-invite\n                #:invite-used-p\n                #:all-unused-invites\n                #:invite-code\n                #:invite-with-code\n                #:invites-with-email\n                #:all-invites\n                #:invite)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:fiveam-matchers/misc\n                #:is-null))\n(in-package :auth/model/test-invite)\n\n(util/fiveam:def-suite)\n\n(defclass my-company (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((company (make-instance 'my-company))\n          (company-2 (make-instance 'my-company)))\n      (let ((invite (make-instance 'invite :company company\n                                           :email \"foo@example.com\"))\n          (invite2 (make-instance 'invite :company company-2\n                                          :email \"bar@example.com\")))\n       (&body)))))\n\n(test all-invites ()\n  (with-fixture state ()\n    (assert-that (all-invites :company company)\n                 (contains invite))\n    (assert-that (all-invites)\n                 (contains invite invite2))))\n\n(test invites-with-email ()\n  (with-fixture state ()\n    (assert-that (invites-with-email \"foo@example.com\")\n                 (contains invite))\n    (assert-that (invites-with-email \"foo@example.com\" :company :foo)\n                 (is-null))))\n\n(test invite-with-code ()\n  (with-fixture state ()\n    (assert-that (invite-with-code (invite-code invite))\n                 (is-equal-to invite))))\n\n(test all-unused-invites ()\n  (with-fixture state ()\n    (assert-that (all-unused-invites :company company)\n                 (contains invite))\n    (setf (invite-used-p invite) t)\n    (assert-that (all-unused-invites :company company)\n                 (contains))))\n\n(test happy-path-user-has-seen-invite\n  (with-fixture state ()\n    (finishes\n     (set-user-has-seen-invite :foo invite))))\n"
  },
  {
    "path": "src/auth/model/test-roles.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/test-roles\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:auth/model/roles\n                #:user-roles\n                #:admin\n                #:ensure-has-role\n                #:companies-for-user\n                #:users-for-company\n                #:read-only\n                #:reviewer\n                #:has-role-p\n                #:standard-member\n                #:guest\n                #:user-role)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:cl-mock\n                #:answer))\n(in-package :auth/model/test-roles)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test simple-role-lookup\n  (with-fixture state ()\n    (setf (user-role :foo :bar) 'guest)\n    (assert-that (user-role :foo :bar)\n                 (has-typep 'guest))\n    (setf (user-role :foo :bar) 'standard-member)\n    (assert-that (user-role :foo :bar)\n                 (has-typep 'standard-member))))\n\n(test has-role-p\n  (with-fixture state ()\n    (setf (user-role :foo :bar) 'guest)\n    (is-true (has-role-p :foo :bar 'guest))\n    (is-false (has-role-p :foo :bar 'reviewer))\n    (is-true (has-role-p :foo :bar t))\n    (is-false (has-role-p :foo :car t))\n    (setf (user-role :foo :bar) 'standard-member)\n    (is-true (has-role-p :foo :bar 'read-only))))\n\n(test deleting-a-role\n  (with-fixture state ()\n    (setf (user-role :foo :bar) 'guest)\n    (is-true (has-role-p :foo :bar 'guest))\n    (setf (user-role :foo :bar) nil)\n    (is-false (has-role-p :foo :bar 'guest))\n    ;; Delete a second time!\n    (setf (user-role :foo :bar) nil)\n    (is-false (has-role-p :foo :bar 'guest))))\n\n(test lookup-by-company-and-index\n  (with-fixture state ()\n    (setf (user-role :foo :bar) 'guest)\n    (assert-that (users-for-company :foo)\n                 (contains :bar))\n    (assert-that (companies-for-user :bar)\n                 (contains :foo))))\n\n\n(test ensure-a-role\n  (with-fixture state ()\n    (ensure-has-role :foo :bar 'standard-member)\n    (is-true (has-role-p :foo :bar 'standard-member))\n    (is-false (has-role-p :foo :bar 'admin))\n    (ensure-has-role :foo :bar 'admin)\n    (is-true (has-role-p :foo :bar 'standard-member))\n    (is-true (has-role-p :foo :bar 'admin))\n    (ensure-has-role :foo :bar 'standard-member)\n    (is-true (has-role-p :foo :bar 'standard-member))\n    (is-true (has-role-p :foo :bar 'admin))))\n\n(test if-user-role-is-overriden-we-still-write-the-new-role\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n      (answer (user-role 'foo 'bar) (make-instance 'roles:admin))\n      (ensure-has-role 'foo 'bar 'roles:standard-member)\n      (is (roles:has-role-p 'foo 'bar 'roles:admin)))\n\n    ;; But after we remove the override, we should still have the\n    ;; ensured-role saved.\n    (is (roles:has-role-p 'foo 'bar 'roles:admin))))\n\n(test we-cant-set-a-role-for-a-nil-user\n  (with-fixture state ()\n    (ensure-has-role 'foo nil 'roles:standard-member)\n    (assert-that (bknr.datastore:class-instances 'user-roles)\n                 (contains))))\n"
  },
  {
    "path": "src/auth/model/user.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/model/user\n  (:use #:cl\n        #:auth))\n(in-package :auth/model/user)\n\n(defgeneric find-or-create-user (installation &key email)\n  (:documentation \"Finds a user by email, or creates a new user if no such user\nexists. Returns two values: the user and a boolean indicating if it\nwas a new user.\"))\n\n(defgeneric make-user (installation &key email full-name)\n  (:documentation \"Create a user for the installation\"))\n\n(defgeneric user-full-name (user))\n\n(defmethod user-first-name (user)\n  (car (str:split \" \" (user-full-name user))))\n"
  },
  {
    "path": "src/auth/package.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:auth\n  (:use #:cl)\n  (:import-from :bknr.datastore\n                :delete-object\n                :with-transaction\n                :class-instances\n   :store-object\n                :hash-index\n                :unique-index\n   :persistent-class)\n  (:import-from :hunchentoot\n                :set-cookie\n                :host\n                :cookie-in)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:util/store/fset-index\n                #:index-least\n                #:fset-set-index)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:util/events\n                #:push-counter-event)\n  (:import-from #:bknr.indices\n                #:indexed-class)\n  (:export #:user-session\n           #:session-value\n           #:session-created-p\n           #:ensure-session-created\n           #:session-key\n           #:drop-session\n           #:*current-session*\n           #:password-hash\n           #:session=\n           #:login-page\n           #:wrong-password-page\n           #:%session-token ;; avoid using\n           #:has-password-p\n           #:set-session-cookie\n           #:current-session\n           #:define-login-handlers\n           #:check-password\n           #:with-sessions\n           #:user-password\n           #:signup-page\n           #:user-class\n           #:signup-errors\n           #:send-signup-email\n           #:current-user-id\n           #:%make-session\n           #:handle-signup\n           #:user-id\n           #:current-user\n           #:authenticate-request\n           #:authenticated-request\n           #:find-user-session-value-by-hash\n           #:request-user\n           #:request-account\n           #:csrf-token\n           #:current-user\n           #:logged-in-p\n           #:can-view\n           #:can-view!\n           #:can-edit\n           #:can-edit!\n           #:can-public-view\n           #:no-access-error\n           #:current-company\n           #:auth-acceptor-mixin\n           #:oauth-user-avatar\n           #:oauth-user-full-name\n           #:oauth-user-email\n           #:oauth-user-user\n           #:user-email\n           #:installation-user-with-email\n           #:find-or-create-user\n           #:find-user\n           #:oauth-users\n           #:make-user\n           #:viewer-context\n           #:unaccepted-invites\n           #:can-view-with-normal-viewer-context\n           #:can-edit-with-normal-viewer-context\n           #:user-full-name\n           #:user-first-name\n           #:is-same-session-disregarding-resets-p\n           #:reset-session))\n(in-package :auth)\n\n(util/store/migrations:ensure-symbol-in-package\n :can-viewer-view\n :old :auth/view\n :new :auth\n :export t)\n\n(util/store/migrations:ensure-symbol-in-package\n :can-viewer-edit\n :old :auth/view\n :new :auth\n :export t)\n"
  },
  {
    "path": "src/auth/request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/request\n  (:use #:cl\n        #:auth)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:core/installation/auth\n                #:company-for-request)\n  (:local-nicknames (#:viewer-context #:auth/viewer-context)))\n(in-package :auth/request)\n\n(defclass abstract-authenticated-request ()\n  ((user :initarg :user\n         :initform nil\n         :accessor request-user)\n   (%viewer-context :initarg :viewer-context\n                    :initform (make-instance 'viewer-context:anonymous-viewer-context)\n                    :accessor auth:viewer-context)\n   (account :initarg :account\n            :initform nil\n            :accessor request-account\n            :documentation \"In screenshotbot this is called a `company`. But this is any account\n object that the user is accessing.\")))\n\n(defclass authenticated-request (abstract-authenticated-request\n                                 hunchentoot:request)\n  ())\n\n(defmethod authenticate-request (request))\n\n(defmethod hunchentoot:acceptor-dispatch-request :around\n    (acceptor\n     (request authenticated-request))\n  (auth:with-sessions ()\n    (authenticate-request request)\n    (call-next-method)))\n\n(defmethod auth:authenticate-request ((request authenticated-request))\n  (unless (auth:request-user request) ;; Might happen in tests\n    (alexandria:when-let ((user (auth:session-value :user)))\n      (let ((vc (make-default-viewer-context\n                 request user)))\n        (setf (auth:request-user request) user)\n        (setf (auth:viewer-context request)\n              vc)\n        (unless (auth:request-account request)\n          (alexandria:when-let ((company (company-for-request *installation* request)))\n            (cond\n              ((auth:can-viewer-view vc company)\n               (setf (auth:request-account request) company))\n              (t\n               (warn \"Could not set company for user: ~a, ~a\"\n                     company\n                     user)))))))))\n\n\n(defun current-user ()\n  (and\n   (boundp 'hunchentoot:*request*)\n   (auth:request-user hunchentoot:*request*)))\n\n(defmethod make-default-viewer-context (request user)\n  (make-instance 'viewer-context:normal-viewer-context\n                 :user user))\n\n(defun (setf current-user) (user &key expires-in\n                                   viewer-context)\n  (maybe-reset-session user)\n  (setf (auth:session-value :user :expires-in expires-in) user)\n  (setf (auth:request-user hunchentoot:*request*) user)\n  (setf (auth:viewer-context hunchentoot:*request*)\n        (or\n         viewer-context\n         (make-default-viewer-context\n          hunchentoot:*request*\n          user)))\n  user)\n\n(defun maybe-reset-session (user)\n  ;; If the user has not changed, don't reset-sessions. This avoids\n  ;; race conditions when a user has two separate OAuth flows\n  ;; happening in parallel.\n  \n  (unless (equal user\n                 (auth:session-value :user))\n    (auth:reset-session)))\n\n(defun logged-in-p ()\n  (auth:current-user))\n"
  },
  {
    "path": "src/auth/test-auth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/test-auth\n  (:use :cl\n   :fiveam)\n  (:import-from :util/testing\n                :with-fake-request)\n  (:import-from :auth\n                #:%session-token\n                #:user-session-transient\n                #:copy-session\n                #:+session-reset-index+\n                #:session-reset\n                #:cookie-name\n                #:clean-session-values\n                #:expiry-ts\n                #:session-token\n                #:session-domain\n                #:session-key\n                #:prop-key\n                #:user-session-value\n                #:generate-session-token\n                #:csrf-token\n   #+windows\n   :read-windows-seed)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:fiveam-matchers\n                #:is-string\n                #:has-length\n                #:assert-that)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:abstract-installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:has-typep\n                #:is-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains\n                #:has-item)\n  (:import-from #:fiveam-matchers/strings\n                #:is-string)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:export))\n(in-package :auth/test-auth)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'abstract-installation)))\n    (cl-mock:with-mocks ()\n      (with-test-store ()\n        (with-fake-request ()\n          (&body))))))\n\n(test auth-simple-test\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (is-true (auth:current-session))\n      (is (equal (auth:current-session)\n                 (auth:current-session))))))\n\n(defun last-user-session-value ()\n  (car (last (class-instances 'user-session-value))))\n\n(test simple-key-val\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (setf (auth:session-value :name)  33)\n      (is (equal 33 (auth:session-value :name)))\n      (setf (auth:session-value :name) 44)\n      (is (equal 44 (auth:session-value :name))))))\n\n(test set-key-val-with-expiry\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (setf (auth:session-value :name :expires-in 1000)  33)\n      (is (equal 33 (auth:session-value :name)))\n      (is (<\n           (get-universal-time)\n           (expiry-ts (last-user-session-value))\n           (+ 1100 (get-universal-time)) ))\n      (setf (auth:session-value :name :expires-in 500) 44)\n      (is (equal 44 (auth:session-value :name)))\n      (is (<\n           (get-universal-time)\n           (expiry-ts (last-user-session-value))\n           (+ 600 (get-universal-time)) )))))\n\n(test new-fields-are-set\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (setf (auth:session-value :name) 33)\n      (is (eql :name (prop-key (last-user-session-value))))\n      (is (equal \"localhost\" (session-domain (last-user-session-value))))\n      (assert-that (session-token (last-user-session-value))\n                   (is-string)\n                   (has-length 40)))))\n\n(test cleans-old-values\n  (With-fixture state ()\n    (auth:with-sessions ()\n      (let ((ts (get-universal-time)))\n        (setf (auth:session-value :name :expires-in 3000) \"foobar\")\n        (is (equal \"foobar\" (auth:session-value :name)))\n        (clean-session-values (+ ts 6000))\n        (is (equal nil (auth:session-value :name)))))))\n\n(test cleans-only-old-values\n  (With-fixture state ()\n    (auth:with-sessions ()\n      (let ((ts (get-universal-time)))\n        (setf (auth:session-value :name :expires-in 3000) \"foobar\")\n        (setf (auth:session-value :bar :expires-in 9000) \"foobar1\")\n        (setf (auth:session-value :car) \"foobar2\")\n        (is (equal \"foobar\" (auth:session-value :name)))\n        (clean-session-values (+ ts 6000))\n        (is (equal nil (auth:session-value :name)))\n        (is (equal \"foobar1\" (auth:session-value :bar)))\n        (is (equal \"foobar2\" (auth:session-value :car)))))))\n\n#+windows\n(test read-windows-seed\n  (is-true (read-windows-seed)))\n\n(test csrf-token\n  (with-fixture state ()\n    (auth:with-sessions ()\n     (cl-mock:answer (generate-session-token) \"foobar\"\n       \"bad\")\n      (is (equal \"foobar\" (auth:csrf-token)))\n      (is (equal \"foobar\" (auth:csrf-token))))))\n\n\n(test cookie-name\n  (let ((*installation* (make-instance 'abstract-installation)))\n    (is (equal \"s2\" (cookie-name))))\n  (let ((*installation* (make-instance 'abstract-installation\n                                       :domain \"foo.example.com\")))\n    (is (equal \"s3\" (cookie-name)))))\n\n(test generate-session-happey-path\n  (finishes\n    (generate-session-token)))\n\n(test session-token-doesnt-get-called-if-theres-no-token\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (slot-makunbound\n       (auth:current-session)\n       'auth::session-key)\n\n      (cl-mock:answer (auth::%session-token session)\n        (error \"session-token should not be read\"))\n\n      (is (eql nil (auth:session-value :hello-world))))))\n\n(test simple-path-of-set-and-get-session-value\n  (with-fixture state ()\n    (flet ((cookies-out ()\n             (loop for (key . val) in (hunchentoot:cookies-out*)\n                   collect (list key val))))\n\n     (auth:with-sessions ()\n       (assert-that (hunchentoot:cookies-out*)\n                    (contains))\n\n       (setf (auth:session-value :foo) 22)\n\n       (assert-that (cookies-out)\n                    (contains\n                     (contains \"s2\" (has-typep t))))\n\n       (is (eql 22 (auth:session-value :foo)))))))\n\n(test reset-session\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (setf (auth:session-value :foo) \"22\")\n      (is (equal \"22\" (auth:session-value :foo)))\n      (auth:reset-session)\n      (is (eql nil (auth:session-value :foo))))))\n\n(test reset-session-logs-reset\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (setf (auth:session-value :foo) \"22\")\n      (auth:reset-session)\n      (assert-that (auth::all-session-resets)\n                   (has-length 1)))))\n\n(test if-session-token-wasnt-generated-we-dont-log-index\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (slot-makunbound (auth:current-session)\n                       'auth::session-key)\n      (auth:reset-session)\n      (assert-that (auth::all-session-resets)\n                   (has-length 0)))))\n\n(test is-same-session-disregarding-resets-p\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (let ((old-session (copy-session (auth:current-session))))\n        (is-true (auth:is-same-session-disregarding-resets-p old-session (auth:current-session)))\n        (auth:reset-session)\n        (is-true (auth:is-same-session-disregarding-resets-p old-session (auth:current-session)))\n        (is-false (auth:is-same-session-disregarding-resets-p\n                   old-session\n                   (make-instance 'user-session-transient\n                                  :token \"foobar\"\n                                  :domain (session-domain (auth:current-session)))))\n        (is-false (auth:is-same-session-disregarding-resets-p\n                   old-session\n                   (make-instance 'user-session-transient\n                                  :token (%session-token (auth:current-session))\n                                  :domain \"another.example.com\")))))))\n\n(test two-resets-is-will-not-be-the-same-session\n  \"This is just documenting current behavior. It might make sense to\nincrease this in the future.\"\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (let ((old-session (copy-session (auth:current-session))))\n        (is-true (auth:is-same-session-disregarding-resets-p old-session (auth:current-session)))\n        (auth:reset-session)\n        (is-true (auth:is-same-session-disregarding-resets-p old-session (auth:current-session)))\n        (auth:reset-session)\n        (assert-that\n         (auth:is-same-session-disregarding-resets-p old-session (auth:current-session))\n         (described-as \"We currently expect two resets to break the session chain\"\n           (is-equal-to nil)))))))\n\n(test we-dont-look-at-a-reset-that-was-done-long-ago\n  (with-fixture state ()\n    (auth:with-sessions ()\n      (let ((old-session (copy-session (auth:current-session))))\n        (auth:reset-session)\n        (is-true (auth:is-same-session-disregarding-resets-p old-session (auth:current-session)))\n        (setf (slot-value (car (bknr.datastore:class-instances 'session-reset))\n                          'auth::ts)\n              10)\n        (is-false (auth:is-same-session-disregarding-resets-p old-session (auth:current-session)))))))\n"
  },
  {
    "path": "src/auth/test-avatar.lisp",
    "content": "(defpackage :auth/test-avatar\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:blob-pathname\n                #:persistent-class\n                #:store-object)\n  (:import-from #:auth/avatar\n                #:overriden-avatar\n                #:handle-avatar)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:hunchentoot\n                #:*reply*)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/login/oidc\n                #:oidc-user)\n  (:import-from #:cl-mock\n                #:with-mocks))\n(in-package :auth/test-avatar)\n\n(util/fiveam:def-suite)\n\n(defclass fake-user (store-object)\n  ((oauth-users :accessor auth:oauth-users\n                :initform nil)\n   (email :accessor auth:user-email\n          :initform nil))\n  (:metaclass persistent-class))\n\n(def-fixture state ()\n  (with-mocks ()\n   (with-test-store ()\n     (with-fake-request ()\n       (let ((user (make-instance 'fake-user)))\n         (&body))))))\n\n(def-easy-macro assert-redirects (url &fn fn)\n  (catch 'hunchentoot::handler-done\n    (fn))\n  (is (equal (hunchentoot:header-out :location *reply*)\n             url)))\n\n(test gravatar ()\n  (with-fixture state ()\n    (assert-redirects (\"https://secure.gravatar.com/avatar/852438d026c018c4307b916406f98c62\")\n      (handle-avatar user))))\n\n(test gravatar-for-entra ()\n  (with-fixture state ()\n    (setf (auth:oauth-users user)\n          (list (make-instance 'oidc-user\n                               :avatar \"https://graph.microsoft.com/v1.0/me/photo/$value\")))\n    (assert-redirects (\"https://secure.gravatar.com/avatar/852438d026c018c4307b916406f98c62\")\n      (handle-avatar user))))\n\n(test with-avatar-from-oidc\n  (with-fixture state ()\n    (setf (auth:oauth-users user)\n          (list (make-instance 'oidc-user\n                               :avatar \"https://example.com/foo.png\")))\n    (assert-redirects (\"https://example.com/foo.png\")\n      (handle-avatar user))))\n\n(test overriden-avatar\n  (with-fixture state ()\n    (let ((override (make-instance 'overriden-avatar\n                                   :user user\n                                   :content-type \"image/png\")))\n      (with-open-file (stream (blob-pathname override) :direction :output)\n        (write-string \"foobar\" stream))\n\n      (cl-mock:if-called 'hunchentoot:handle-static-file\n                         (lambda (filename content-type)\n                           (is (equal (blob-pathname override) filename))\n                           (is (equal content-type \"image/png\"))\n                           'dummy))\n      (is (eql 'dummy (handle-avatar user))))))\n"
  },
  {
    "path": "src/auth/test-view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/test-view\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:auth\n                #:authenticated-request)\n  (:import-from #:auth/viewer-context\n                #:api-viewer-context\n                #:anonymous-viewer-context\n                #:normal-viewer-context)\n  (:import-from #:auth/request\n                #:abstract-authenticated-request)\n  (:import-from #:util/events\n                #:push-event\n                #:*events*)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:core/api/model/api-key\n                #:api-key)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string))\n(in-package :auth/test-view)\n\n(util/fiveam:def-suite)\n\n(defclass my-user (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(defmethod auth:can-view ((obj (eql :one)) (user my-user))\n  t)\n\n(defmethod auth:can-view (obj (user my-user))\n  nil)\n\n(defmethod auth:can-edit ((obj (eql :one)) (user my-user))\n  t)\n\n(def-fixture state ()\n  (with-test-store ()\n   (cl-mock:with-mocks ()\n     (let ((events))\n       (&body)))))\n\n(test happy-path-can-edit-view\n  (with-fixture state ()\n    (let ((user (make-instance 'my-user)))\n     (let ((hunchentoot:*request*\n             (make-instance 'abstract-authenticated-request\n                            :viewer-context\n                            (make-instance 'normal-viewer-context :user user))))\n       (is-true (auth:can-view :one user))\n       (finishes (auth:can-view! :one))\n       (finishes (auth:can-edit! :one))))))\n\n(test happy-path-cannot-edit-view\n  (with-fixture state ()\n    (let ((user (make-instance 'my-user)))\n      (let ((hunchentoot:*request*\n              (make-instance 'abstract-authenticated-request\n                             :viewer-context\n                             (make-instance 'normal-viewer-context :user user))))\n        (is-false (auth:can-view :two user))\n        (signals auth:no-access-error\n          (auth:can-view! :two))\n        (signals auth:no-access-error\n          (auth:can-edit! :two))))))\n\n(test no-access-error-with-api-viewer-context\n  (with-fixture state ()\n    (let ((user (make-instance 'my-user)))\n      (let ((hunchentoot:*request*\n              (make-instance 'abstract-authenticated-request\n                             :viewer-context\n                             (make-instance 'api-viewer-context\n                                            :api-key\n                                            (make-instance 'api-key\n                                                           :api-key \"bleh\"\n                                                           :user user)\n                                            :user user))))\n        (is-false (auth:can-view :two user))\n        (signals auth:no-access-error\n          (auth:can-view! :two))\n        (signals auth:no-access-error\n          (auth:can-edit! :two))\n\n        (handler-case\n            (auth:can-view! :two)\n          (auth:no-access-error (e)\n            (assert-that (format nil \"~a\" e)\n                         (contains-string \"API-KEY\"))))))))\n\n(defmethod auth:can-viewer-view (vc (obj (eql :one)))\n  t)\n\n(test can-view-prioritizes-objects-over-vc\n  ;; If the order is reversed, currently this will fail with an error\n  (is-true\n   (auth:can-viewer-view\n    (make-instance 'normal-viewer-context :user :fake-user)\n    :one)))\n\n\n(test cannot-view-null-object\n  (is-false (auth:can-viewer-view\n             (make-instance 'normal-viewer-context :user :fake-user)\n             nil)))\n\n(test event-gets-notified-once\n  (with-fixture state ()\n    (if-called 'push-event\n               (lambda (name &rest args)\n                 (push name events)))\n    (is-false (auth:can-viewer-view\n               (make-instance 'normal-viewer-context :user :fake-user)\n               nil))\n    (assert-that events\n                 (has-length 1))))\n\n(defmethod auth:can-viewer-view (vc (obj (eql :run-1)))\n  (auth:can-viewer-view vc :channel-1))\n\n(defmethod auth:can-viewer-view (vc (obj (eql :channel-1)))\n  nil)\n\n(test nested-invocation-only-gets-notified-once\n  (with-fixture state ()\n    (if-called 'push-event\n               (lambda (name &rest args)\n                 (push name events)))\n    (is-false (auth:can-viewer-view\n               :vc\n               :run-1))\n    (assert-that events\n                 (has-length 1))))\n"
  },
  {
    "path": "src/auth/view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/view\n  (:use #:cl\n        #:auth)\n  (:import-from #:auth/viewer-context\n                #:viewer-context-api-key\n                #:api-viewer-context\n                #:anonymous-viewer-context\n                #:logged-in-viewer-context\n                #:viewer-context-user\n                #:normal-viewer-context)\n  (:import-from #:util/events\n                #:push-event))\n(in-package :auth/view)\n\n(define-condition no-access-error (error)\n  ((user :initarg :user\n         :accessor error-user)\n   (api-key :initarg :api-key\n            :initform nil\n            :accessor %api-key)\n   (obj :initarg :obj\n        :accessor error-obj)))\n\n(defmethod print-object ((e no-access-error) out)\n  (cond\n    ((%api-key e)\n     (format out \"The API key ~a does not have access to ~S\"\n             (%api-key e)\n             (error-obj e)))\n    (t\n     (format out \"~S can't access ~S\" (error-user e) (error-obj e)))))\n\n(defgeneric can-view (obj user)\n  (:documentation \"Can the USER view object OBJ?\"))\n\n(defgeneric can-public-view (obj)\n  (:documentation \"Can the public (non-logged-in user?) view the object OBJ?\")\n  (:method (obj)\n    nil))\n\n(defgeneric can-viewer-view (vc obj)\n  (:argument-precedence-order obj vc))\n\n(defmethod can-viewer-view ((vc normal-viewer-context) obj)\n  (or\n   (can-public-view obj)\n   (call-next-method)))\n\n(defmethod can-viewer-view (vc (obj null))\n  (warn \"can-viewer-view called on NIL object\")\n  nil)\n\n(defmethod can-viewer-view ((vc anonymous-viewer-context) obj)\n  (can-public-view obj))\n\n(defmethod can-viewer-view ((vc logged-in-viewer-context) obj)\n  (let ((user (viewer-context-user vc)))\n    (and user\n         (can-view obj user))))\n\n(defmethod no-access-error (vc obj)\n  (error 'no-access-error\n         :user (ignore-errors ;; may not have a user\n                (viewer-context-user vc))\n         :obj obj))\n\n(defmethod no-access-error ((vc api-viewer-context) obj)\n  (error 'no-access-error\n         :user (ignore-errors ;; may not have a user\n                (viewer-context-user vc))         \n         :api-key (viewer-context-api-key vc)\n         :obj obj))\n\n(defun can-view! (&rest objects)\n  (let ((vc (auth:viewer-context hunchentoot:*request*)))\n    (dolist (obj objects)\n      (unless (can-viewer-view vc obj)\n        (restart-case\n            (no-access-error vc obj)\n          (give-access-anyway ()\n            nil))))))\n\n(defmethod can-viewer-edit ((vc logged-in-viewer-context) obj)\n  (let ((user (viewer-context-user vc)))\n    (can-edit obj user)))\n\n(defmethod can-view-with-normal-viewer-context (user obj)\n  \"This is a helper method just to make it easier to transition from\nCAN-VIEW to CAN-VIEWER-VIEW.\"\n  (warn \"can-view-with-normal-viewer-context called with ~a, ~a\" user obj)\n  (can-viewer-view\n   (make-instance 'normal-viewer-context\n                  :user user)\n   obj))\n\n(defmethod can-edit-with-normal-viewer-context (user obj)\n  \"This is a helper method just to make it easier to transition from\nCAN-EDIT to CAN-VIEWER-EDIT.\"\n  (warn \"can-edit-with-normal-viewer-context called with ~a, ~a\" user obj)\n  (can-viewer-edit\n   (make-instance 'normal-viewer-context\n                  :user user)\n   obj))\n\n(defmethod can-viewer-edit (vc obj)\n  nil)\n\n(defmethod can-viewer-edit :around (vc obj)\n  (and\n   (auth:can-viewer-view vc obj)\n   (call-next-method)))\n\n(defvar *nesting-count* 0)\n\n(defmethod can-viewer-view :around (vc obj)\n  ;; All of this logic is just for the simple push-event. I wonder if\n  ;; it would make sense to get rid of this push-event altogether at\n  ;; some point.\n  (let ((res\n          (let ((*nesting-count* (1+ *nesting-count*)))\n            (call-next-method))))\n    (unless res\n      ;; We are track failures so that a spike can indicate bugs in\n      ;; the authorization logic, or a potential attacker.\n      (when (eql *nesting-count* 0)\n        (push-event :can-viewer-view-failed\n                    :object (format nil \"~a\" obj)\n                    :vc (format nil \"~a\" vc))))\n    res))\n\n(defgeneric can-edit (obj user)\n  (:method (obj user)\n    nil)\n  (:documentation \"Can the USER edit object OBJ?\"))\n\n(defmethod can-edit! (&rest objects)\n  (let ((vc (auth:viewer-context hunchentoot:*request*)))\n   (dolist (obj objects)\n     (unless (can-viewer-edit vc obj)\n       (error 'no-access-error :user (ignore-errors\n                                      (viewer-context-user vc))\n                               :obj obj)))))\n\n(defmethod can-edit :around (obj user)\n  \"In order to edit something, we also need to be able to view it.\"\n  (and\n   user\n   (can-view obj user)\n   (call-next-method)))\n"
  },
  {
    "path": "src/auth/viewer-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auth/viewer-context\n  (:use #:cl)\n  (:export\n   #:viewer-context-user\n   #:viewer-context\n   #:api-viewer-context\n   #:normal-viewer-context\n   #:anonymous-viewer-context\n   #:email-viewer-context\n   #:viewer-context-api-key))\n(in-package :auth/viewer-context)\n\n(defclass abstract-viewer-context ()\n  ()\n  (:documentation \"The same viewer might view the object through different contexts. For\nexample a viewer viewer via API or browser should have different\npermissions. Or a super-admin might not want to browse with\nsuper-admin priviledges by default.\"))\n\n(defclass logged-in-viewer-context (abstract-viewer-context)\n  ((user :initarg :user\n         :reader viewer-context-user)))\n\n(defclass api-viewer-context (logged-in-viewer-context)\n  ((api-key :initarg :api-key\n            :reader viewer-context-api-key)))\n\n(defclass normal-viewer-context (logged-in-viewer-context)\n  ())\n\n(defclass viewer-context (normal-viewer-context)\n  ())\n\n(defclass anonymous-viewer-context (abstract-viewer-context)\n  ())\n\n(defclass site-admin-viewer-context (normal-viewer-context)\n  ())\n\n(defclass email-viewer-context (logged-in-viewer-context)\n  ((user :initarg :user\n         :reader viewer-context-user))\n  (:documentation \"Information being sent over an email. Technically not a 'logged-in'\nviewer context, but I suppose the user is logged in to their email\naddress so it's logged in in that sense.\"))\n"
  },
  {
    "path": "src/auto-restart/.circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    docker:\n      - image: cimg/base:2021.04\n    steps:\n      - checkout\n      - run:\n          name: Install SBCL\n          command: sudo apt-get update && sudo apt-get install -y sbcl\n      - run:\n          name: Install quicklisp\n          command: |\n            curl -O https://beta.quicklisp.org/quicklisp.lisp\n            sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)'\n      - run:\n          name: Run tests\n          command: sbcl --script run-circleci.lisp\n"
  },
  {
    "path": "src/auto-restart/LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "src/auto-restart/README.md",
    "content": "# AUTO-RESTART\n\n[![tdrhq](https://circleci.com/gh/tdrhq/auto-restart.svg?style=shield)](https://app.circleci.com/pipelines/github/tdrhq/auto-restart?branch=main)\n\nAuto-restart makes it easy to create restarts for the most common\nsituation of just retrying a function. To summarize, this is how\nyou'll use it:\n\n```lisp\n(with-auto-restart ()\n  (defun foo(arg1 &key other-options)\n    (... do-stuff ...)))\n```\n\nIf something fails inside foo, you'll be presented with a restart to\nRETRY-FOO. Once you've pushed this to production you might that the\nfunction FOO is flaky, possibly because it hits the network or some\nother unreliable resource. In that case, you can automate calling the\nrestart like so:\n\n```lisp\n(with-auto-restart (:retries 2 :sleep 1)\n  (defun foo(arg1 &key other-options)\n    (... do-stuff ...)))\n```\n\nThis is really it, but you might have some questions as to why the API\nis as it is. Let's build up the reasoning for this.\n\n## Motivation\n\nSo say you write a complex, slow running function FOO:\n\n```lisp\n(defun foo (...args ...)\n  (... do-stuff ...))\n```\n\nIt's part of a slow job, say a crawler, so if the function FOO fails,\nyou don't want to restart the job from scratch. Instead, you probably\njust want to restart the FOO function. You might consider doing\nsomething like this:\n\n```lisp\n(defun foo (...args...)\n  (labels ((actual-foo ()\n             (restart-case\n                (...do-stuff...)\n               (retry-foo ()\n                (actual-foo)))))\n     (actual-foo)\n```\n\nNow, when an error happens you'll get a restart to RETRY-FOO. But\nthere's a catch! If you make changes to `(...do stuff...)`, those\nchanges won't show up even if you RETRY-FOO.\n\nSo we'll do something like this instead:\n\n```lisp\n(defun foo (...args...)\n  (restart-case\n     (...do-stuff...)\n    (retry-foo ()\n      (foo ...args...))))\n```\n\nThis version works great. Applying `...args...` on the last line needs\nto be done carefully. You need to account for `&optional`, `&key`\netc. Also, any changes to the args needs to be correctly kept in sync\non the last line. So it's error prone.\n\n`with-auto-restart` just does this for you automatically.\n\n## Retries\n\nIn the process of developing, you'll end up testing that the restart\nworks correctly. (Restarts need to be verified too! It's just code\nafter all! A restart might fail, if for example some global state has\nchanged beforethe  condition was signaled).\n\nBut you might find that the `FOO` function keeps failing for\nlegitimate reasons. Perhaps it's hitting the network, or some other\nresource that's not always available. Using `:retries` makes it\ntrivial to update the existing function to automatically call the\nretry for you multiple times.\n\n## Future work\n\nOne thing I would like to do, but I don't know if this is possible, is\nto be able to enter the debugger, but if no action was chosen with a\nset amount of time, then automatically pick a restart.\n\nThe lambda-list parsing is also very primitive. If you have some\ncomplex lambda-list keywords, we may or may not get it right. Do look\nat the macroexpansion to make sure it's doing the right thing. We\nshould handle `&optional`, `&key` and `&rest` at a minimum.\n\n\n## Authors\n\nBuilt by Arnold Noronha <arnold@tdrhq.com>\n\n## License\n\nLicensed under the Mozilla Public License, v2.\n"
  },
  {
    "path": "src/auto-restart/auto-restart.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :auto-restart\n  :author \"Arnold Noronha <arnold@jipr.io>\"\n  :license  \"Apache License, Version 2.0\"\n  :version \"0.0.1\"\n  :description \"automatically generate restart-cases for the most common use cases, and also use the restart for automatic retries \"\n  :serial t\n  :depends-on (:iterate)\n  :components ((:file \"auto-restart\"))\n  :in-order-to ((test-op (test-op :auto-restart/tests))))\n\n(defsystem :auto-restart/tests\n  :serial t\n  :depends-on (:auto-restart\n               :fiveam)\n  :components ((:file \"test-auto-restart\"))\n  :perform (test-op (o c)\n                    (unless\n                        (symbol-call '#:fiveam '#:run!\n                                      :test-auto-restart)\n                      (error \"Some tests were failed!\"))))\n"
  },
  {
    "path": "src/auto-restart/auto-restart.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :auto-restart\n  (:use #:cl\n        #:iterate)\n  (:export\n   #:with-auto-restart\n   #:*global-enable-auto-retries-p*\n   #:exponential-backoff))\n(in-package :auto-restart)\n\n\n(define-condition restart-already-defined (error)\n  ((restart-name :initarg :restart-name))\n  (:documentation \"When calling with-auto-restart, we expect the\n  restart to be defined inside the body, not before it.\"))\n\n(defvar *frame* nil)\n\n(defvar *global-enable-auto-retries-p* t\n  \"Globally enable or disable automatic retries. Useful for unit tests.\")\n\n(defun exponential-backoff (base)\n  `(lambda (attempt)\n     (expt ,base attempt)))\n\n(defstruct frame\n  attempt\n  error-handler\n  restart-handler)\n\n(defun %is-error-of-type (e error-classes)\n  (loop for class in error-classes\n        if (typep e class)\n          return t\n        finally\n           (return nil)))\n\n(defun %add-noise (time &key noise)\n  (*\n   time\n   (+ 1  (- (random (* 2 noise)) noise))))\n\n(defmacro with-auto-restart ((&key (retries 0)\n                                (sleep (exponential-backoff 2))\n                                (sleep-noise 0.1)\n                                (attempt (gensym \"attempt\"))\n                                (restart-name)\n                                (auto-restartable-errors ''(error)))\n                             &body body)\n  \"Enable auto-restarts for a DEFUN.\n\nATTEMPT will be a variable name that will be bound to the current\nattempt. Attempts will be 1-indexed (1, 2, 3 ... ).\n\nAUTO-RESTARTABLE-ERRORS is evaluated to get a list of error classes\nfor which we're allowed to auto-restart. It defaults to `'(ERROR)`.\n\nIf RETRIES is provided, then we automatically retry, for a total of\nRETRIES times (including the first invocation). So RETRIES is\nmisleading, it's not a count of retries but a total count of attempts.\n\"\n  (assert (= 1 (length body)))\n\n  (let* ((body (car body))\n         (sleep (cond\n                  ((numberp sleep)\n                   `(lambda (attempt)\n                      (declare (ignore attempt))\n                      ,sleep))\n                  (t\n                   sleep)))\n         (sleep-var (gensym \"sleep\"))\n         (retries-var (gensym \"retries\"))\n         (args-pos (position-if #'listp body))\n         (before-args (subseq body 0 (1+ args-pos)))\n         (fn-name (cadr before-args))\n         (fn-args (elt body args-pos ))\n         (var-names (loop for var in fn-args\n                          if (listp var)\n                            collect (car var)\n                          else\n                            collect var))\n         (decls-onwards (subseq body (1+ args-pos)))\n         (restart-name (or\n                        restart-name\n                        (intern (format nil \"RETRY-~a\" fn-name) *package*))))\n\n    ;; quick validation\n    (loop for arg in var-names\n          do\n             (assert (symbolp arg)))\n    (multiple-value-bind (body decls doc)\n        (uiop:parse-body decls-onwards :documentation t)\n      `(let ((,sleep-var ,sleep)\n             (,retries-var ,retries))\n         (declare (ignorable ,retries-var))\n         (,@before-args\n          ,@(when doc (list doc))\n          ,@decls\n          (flet ((top-level-body ()\n                   (flet ((body (,attempt)\n                            (declare (ignorable ,attempt))\n                            ,@body))\n                     (let ((attempt (frame-attempt *frame*)))\n                       (setf (frame-error-handler *frame*)\n                             (lambda (e)\n                               (cond\n                                 ((and *global-enable-auto-retries-p* (< attempt ,retries)\n                                       (%is-error-of-type e ',(eval auto-restartable-errors)))\n                                  (let ((sleep-time (%add-noise (funcall ,sleep-var attempt)\n                                                                :noise ,sleep-noise)))\n                                    (unless (= 0 sleep-time)\n                                      (sleep sleep-time)))\n                                  (invoke-restart ',restart-name)))))\n                       (setf (frame-restart-handler *frame*)\n                             (lambda ()\n                               (apply #',fn-name ,@ (fix-args-for-funcall var-names))))\n                       ;; If we make a call to another auto-restart\n                       ;; it should not see the same frame\n                       (let ((*frame* nil))\n                         (body attempt))))))\n            (cond\n              ((null *frame*)\n               (let* ((frame (make-frame\n                              :attempt 1\n                              :error-handler nil\n                              :restart-handler (lambda ()\n                                                 ;; The first time we call we just call the function\n                                                 (top-level-body))))\n                      (*frame* frame))\n                 (block success\n                   (loop\n                     (restart-case\n                         (handler-bind ((error (lambda (e)\n                                                 (funcall (frame-error-handler frame) e))))\n                           (return-from success (funcall (frame-restart-handler frame))))\n                       (,restart-name ()\n                         ;; Retry in our loop\n                         (values)))))))\n              (t\n               (incf (frame-attempt *frame*))\n               (top-level-body)))))))))\n\n(defun fix-args-for-funcall (var-names)\n  (let ((state :default))\n    (iter (for var in (append var-names\n                              ;; fake ending point\n                              '(&rest nil) ))\n      (cond\n        ((eql :end state)\n         #| do nothing |#)\n        ((eql '&optional var)\n         (setf state :optional))\n        ((eql '&key var)\n         (setf state :key))\n        ((eql '&rest var)\n         (setf state :rest))\n        ((eql #\\& (elt (string var) 0))\n         (error \"Unsupported lambda-list specifier: ~a\" var))\n        ((not (symbolp var))\n         ;; TODO(arnold): this isn't hard to handle if you do need it.\n         (error \"Unsupported variable name: ~a, probably because of a\n         keyword arg?\" var))\n        (t\n         (case state\n           (:key\n            (appending `(,(intern (string var) \"KEYWORD\")\n                         ,var)))\n           (:rest\n            ;; this one is interesting, we can stop looking at anything\n            ;; that comes after this.\n            (collect var)\n            (setf state :end))\n           (otherwise\n            (collect var))))))))\n\n(defun call-with-auto-restart (restart-name fn)\n  (when (find-restart restart-name)\n    (error 'restart-already-defined :restart-name restart-name))\n  (funcall fn :attempt 0))\n"
  },
  {
    "path": "src/auto-restart/run-circleci.lisp",
    "content": "(load \"~/quicklisp/setup.lisp\")\n\n(push #P \"./\" asdf:*central-registry*)\n\n(ql:quickload :auto-restart/tests)\n\n(unless (fiveam:run-all-tests)\n  (uiop:quit 1))\n\n(uiop:quit 0)\n"
  },
  {
    "path": "src/auto-restart/test-auto-restart.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :test-auto-restart\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:auto-restart\n                #:%add-noise\n                #:exponential-backoff\n                #:*global-enable-auto-retries-p*\n                #:restart-already-defined\n                #:with-auto-restart))\n(in-package :test-auto-restart)\n\n\n(fiveam:def-suite* :test-auto-restart)\n\n(test simple-function\n  (with-auto-restart ()\n    (defun foo ()\n      2))\n  (is (equal 2 (foo))))\n\n(test function-has-restart-defined\n  (with-auto-restart ()\n    (defun foo ()\n      (find-restart 'retry-foo)))\n\n  (is-true (foo)))\n\n(test calling-the-restart-works\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo ()\n       (cond\n         ((not (>= (length res) 3))\n          (push t res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '(t t t)\n              (foo))))\n\n\n(test function-with-args\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a b c)\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b c) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((1 2 3) (1 2 3) (1 2 3))\n              (foo 1 2 3))))\n\n\n(test method-with-args\n  (let ((res nil))\n   (with-auto-restart ()\n     (defmethod dummy-method ((a symbol) b)\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b) res)\n          (invoke-restart 'retry-dummy-method))\n         (t\n          res)))))\n  (is (equal '((:foo 1) (:foo 1) (:foo 1))\n              (dummy-method :foo 1))))\n\n(test optional-args\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a &optional (b :bar))\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((:foo :bar) (:foo :bar) (:foo :bar))\n              (foo :foo))))\n\n(test keyword-arg\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a &key (b :bar))\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((:foo :bar) (:foo :bar) (:foo :bar))\n              (foo :foo))))\n\n(test keyword-arg-with-value\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a &key (b :bar))\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((:foo 3) (:foo 3) (:foo 3))\n              (foo :foo :b 3))))\n\n\n(test rest-args\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a &rest b)\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((:foo (1 2)) (:foo (1 2)) (:foo (1 2)))\n              (foo :foo 1 2))))\n\n(test rest-args-and-keywords\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a &rest b &key bar)\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b bar) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((:foo (:bar 2) 2) (:foo (:bar 2) 2) (:foo (:bar 2) 2))\n              (foo :foo :bar 2))))\n\n(test rest-args-and-keywords-with-default-keyword\n  (let ((res nil))\n   (with-auto-restart ()\n     (defun foo (a &rest b &key (bar 10))\n       (cond\n         ((not (>= (length res) 3))\n          (push (list a b bar) res)\n          (invoke-restart 'retry-foo))\n         (t\n          res)))))\n  (is (equal '((:foo nil 10) (:foo nil 10) (:foo nil 10))\n              (foo :foo))))\n\n(define-condition test-error (error)\n  ())\n\n(test actually-do-automatic-retries\n  (let ((res nil))\n    (with-auto-restart (:retries 3 :sleep 0.1)\n      (defun foo ()\n        \"dfdf\"\n        (declare (inline))\n        (cond\n          ((not (>= (length res) 10))\n           (push t res)\n           (error 'test-error))\n          (t\n           res))))\n\n    (signals test-error\n      (foo))\n    (is (equal '(t t t)\n                res))))\n\n(test but-we-dont-retry-if-the-global-flag-is-off\n  (let ((res nil)\n        (*global-enable-auto-retries-p* nil))\n    (with-auto-restart (:retries 3)\n      (defun foo ()\n        \"dfdf\"\n        (declare (inline))\n        (cond\n          ((not (>= (length res) 10))\n           (push t res)\n           (error 'test-error))\n          (t\n           res))))\n\n    (signals test-error\n      (foo))\n    (is (equal '(t)\n                res))))\n\n(test actually-do-automatic-retries-with-fake-sleep-happy-path\n  (let ((res nil))\n    (with-auto-restart (:retries 3 :sleep 0)\n      (defun foo ()\n        \"dfdf\"\n        (declare (inline))\n        (cond\n          ((not (>= (length res) 10))\n           (push t res)\n           (error 'test-error))\n          (t\n           res))))\n\n    (signals test-error\n      (foo))\n    (is (equal '(t t t)\n                res))))\n\n(test actually-do-automatic-retries-with-fake-sleep-fn-happy-path\n  (let ((res nil))\n    (with-auto-restart (:retries 3 :sleep (lambda (attempt) 0))\n      (defun foo ()\n        \"dfdf\"\n        (declare (inline))\n        (cond\n          ((not (>= (length res) 10))\n           (push t res)\n           (error 'test-error))\n          (t\n           res))))\n\n    (signals test-error\n      (foo))\n    (is (equal '(t t t)\n                res))))\n\n\n(test doc-and-declarations\n  (with-auto-restart ()\n    (defun foo (x)\n      \"hello world\"\n      (declare (optimize debug)\n               ;; this next declare expr ensures this is\n               ;; part of a function\n               (inline))\n      t))\n  (is (equal \"hello world\" (documentation #'foo 'function))))\n\n(test maybe-invoke-restart\n  (let ((count 0)\n        (seen nil))\n    (with-auto-restart (:attempt attempt)\n      (defun foo ()\n        (push attempt seen)\n        (incf count)\n        (assert (< count 10))\n        (when (< attempt 3)\n          (invoke-restart 'retry-foo))))\n    (foo)\n    (is (eql 3 count))\n    (is (equal (list 3 2 1) seen))))\n\n(define-condition my-error (error)\n  ())\n\n(with-auto-restart (:retries 1000 :sleep 0)\n  (defun crashes-after-many-attempts ()\n    (error 'my-error)))\n\n(test tail-call-optimized\n  (signals my-error\n    (crashes-after-many-attempts)))\n\n\n(with-auto-restart (:attempt attempt)\n  (defun nested-inner ()\n    (unless (= attempt 1)\n      (error \"inner function didn't reset attempts, got ~a\" attempt))))\n\n(with-auto-restart (:attempt attempt)\n  (defun nested-outer ()\n    (assert (= attempt 1))\n    (nested-inner)))\n\n(test nested-calls-maintains-attempts\n  (finishes\n    (nested-outer)))\n\n(with-auto-restart () ;; this auto-restart is important for the test\n                      ;; we're trying to test.\n (defun nested-inner-with (counter)\n   (unless (= counter 3)\n     (error \"inner function still failing, got ~a\" counter))))\n\n(with-auto-restart (:retries 3 :attempt attempt :sleep 0)\n  (defun nested-outer-with-inner-failures ()\n    (nested-inner-with attempt)\n    attempt))\n\n(test nested-calls-maintains-attempts\n  (is (eql\n       3\n       (nested-outer-with-inner-failures))))\n\n(defvar *nested-inner-2-counter*)\n\n(with-auto-restart (:retries 5 :sleep 0) ;; this auto-restart is important for the test\n                      ;; we're trying to test.\n  (defun nested-inner-with-2 (counter)\n    (incf *nested-inner-2-counter*)\n    (unless (= counter 3)\n      (error \"inner function still failing, got ~a\" counter))))\n\n(with-auto-restart (:retries 3 :attempt attempt :sleep 0)\n  (defun nested-outer-with-inner-failures-2 ()\n    (nested-inner-with-2 attempt)\n    attempt))\n\n(test nested-calls-maintains-attempts-2\n  (let ((*nested-inner-2-counter* 0))\n    (is (eql\n         3\n         (nested-outer-with-inner-failures-2)))\n    (is (eql\n         11 *nested-inner-2-counter*))))\n\n(define-condition dummy-error (error)\n  ())\n\n(with-auto-restart (:retries 3 :sleep 0 :auto-restartable-errors '(dummy-error))\n  (defun call-with-crash (fn)\n    (funcall fn)))\n\n(test specify-auto-restartable-errors\n  (let ((count 0))\n    (signals dummy-error\n     (call-with-crash\n      (lambda ()\n        (incf count)\n        (error 'dummy-error))))\n    (is (eql 3 count))))\n\n(test ignore-errors-that-are-not-specified-as-auto-restartable\n  (let ((count 0))\n    (signals error\n      (call-with-crash\n       (lambda ()\n         (incf count)\n         (error 'error))))\n    (is (eql 1 count))))\n\n(auto-restart:with-auto-restart ()\n  (defun %%%can-use-declarations%%% ()\n    (declare (optimize (debug 3)))\n    :foo))\n\n(test can-use-declarations\n  (is (eql :foo (%%%can-use-declarations%%%))))\n\n(test exponential-backoff-happy-path\n  (let ((fn (eval (exponential-backoff 0.01))))\n    (finishes (funcall fn 1))))\n\n(test add-noise\n  (dotimes (i 10)\n    (let ((result (%add-noise 1000 :noise 0.1)))\n      (is (<= result 1100))\n      (is (>= result 900)))))\n"
  },
  {
    "path": "src/build-utils/all.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :build-utils\n    (:use-reexport #:build-utils/js-package\n                   #:build-utils/wild-module\n                   #:build-utils/css-package\n                   #:build-utils/remote-file\n                   #:build-utils/jar-file))\n"
  },
  {
    "path": "src/build-utils/build-utils.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :build-utils/core\n  :serial t\n  :depends-on (:tmpdir\n               :str\n               :cl-store\n               :alexandria)\n  :components ((:file \"wild-module\")\n               (:file \"common\")\n               (:file \"remote-file\")\n               (:file \"jar-file\")))\n\n(defsystem :build-utils/js-package\n  :serial t\n  :depends-on (:build-utils/core\n               :parenscript)\n  :components ((:file \"js-package\")\n               (:file \"css-package\")))\n\n(defsystem :build-utils\n  :serial t\n  :depends-on (:build-utils/core\n               :build-utils/js-package)\n  :components ((:file \"all\")))\n\n(defsystem :build-utils/deliver-script\n  :serial t\n  :depends-on (:tmpdir\n               :alexandria\n               :trivial-features\n               :str)\n  :components ((:file \"deliver-script\")))\n"
  },
  {
    "path": "src/build-utils/closure-compiler/COPYING",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "src/build-utils/closure-compiler/README.md",
    "content": "# [Google Closure Compiler](https://developers.google.com/closure/compiler/)\n\n[![Build Status](https://travis-ci.org/google/closure-compiler.svg?branch=master)](https://travis-ci.org/google/closure-compiler)\n[![Open Source Helpers](https://www.codetriage.com/google/closure-compiler/badges/users.svg)](https://www.codetriage.com/google/closure-compiler)\n\nThe [Closure Compiler](https://developers.google.com/closure/compiler/) is a tool for making JavaScript download and run faster. It is a true compiler for JavaScript. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls.\n\n## Getting Started\n * [Download the latest version](https://dl.google.com/closure-compiler/compiler-latest.zip) ([Release details here](https://github.com/google/closure-compiler/wiki/Releases))\n * [Download a specific version](https://github.com/google/closure-compiler/wiki/Binary-Downloads). Also available via:\n   - [Maven](https://github.com/google/closure-compiler/wiki/Maven)\n   - [NPM](https://www.npmjs.com/package/google-closure-compiler) - includes java, native and javascript versions.\n * See the [Google Developers Site](https://developers.google.com/closure/compiler/docs/gettingstarted_app) for documentation including instructions for running the compiler from the command line.\n\n## Options for Getting Help\n1. Post in the [Closure Compiler Discuss Group](https://groups.google.com/forum/#!forum/closure-compiler-discuss).\n2. Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/google-closure-compiler).\n3. Consult the [FAQ](https://github.com/google/closure-compiler/wiki/FAQ).\n\n## Building it Yourself\n\nNote: The Closure Compiler requires [Java 8 or higher](https://www.java.com/).\n\n### Using [Maven](https://maven.apache.org/)\n\n1. Download [Maven](https://maven.apache.org/download.cgi).\n\n2. Add sonatype snapshots repository to `~/.m2/settings.xml`:\n   ```xml\n   <profile>\n     <id>allow-snapshots</id>\n        <activation><activeByDefault>true</activeByDefault></activation>\n     <repositories>\n       <repository>\n         <id>snapshots-repo</id>\n         <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n         <releases><enabled>false</enabled></releases>\n         <snapshots><enabled>true</enabled></snapshots>\n       </repository>\n     </repositories>\n   </profile>\n   ```\n\n3. On the command line, at the root of this project, run `mvn -DskipTests` (omit the `-DskipTests` if you want to run all the\nunit tests too).\n\n    This will produce a jar file called `target/closure-compiler-1.0-SNAPSHOT.jar`. You can run this jar\n    as per the [Running section](#running) of this Readme. If you want to depend on the compiler via\n    Maven in another Java project, use the `com.google.javascript/closure-compiler-unshaded` artifact.\n\n    Running `mvn -DskipTests -pl externs/pom.xml,pom-main.xml,pom-main-shaded.xml`\n    will skip building the GWT version of the compiler. This can speed up the build process significantly.\n\n### Using [Eclipse](https://www.eclipse.org/)\n\n1. Download and open [Eclipse IDE](https://www.eclipse.org/). Disable `Project > Build automatically` during this process.\n2. On the command line, at the root of this project, run `mvn eclipse:eclipse -DdownloadSources=true` to download JARs and build Eclipse project configuration.\n3. Run `mvn clean` and `mvn -DskipTests` to ensure AutoValues are generated and updated.\n4. In Eclipse, navigate to `File > Import > Maven > Existing Maven Projects` and browse to closure-compiler.\n5. Import both closure-compiler and the nested externs project.\n6. Disregard the warnings about maven-antrun-plugin and build errors.\n7. Configure the project to use the [Google Eclipse style guide](https://github.com/google/styleguide/blob/gh-pages/eclipse-java-google-style.xml)\n8. Edit `.classpath` in closure-compiler-parent. Delete the `<classpathentry ... kind=\"src\" path=\"src\" ... />` line, then add:\n   ```xml\n   <classpathentry excluding=\"com/google/debugging/sourcemap/super/**|com/google/javascript/jscomp/debugger/gwt/DebuggerGwtMain.java|com/google/javascript/jscomp/gwt/|com/google/javascript/jscomp/resources/super-gwt/**\" kind=\"src\" path=\"src\"/>\n   <classpathentry kind=\"src\" path=\"target/generated-sources/annotations\"/>\n   ```\n9. Ensure the Eclipse project settings specify 1.8 compliance level in \"Java Compiler\".\n10. Build project in Eclipse (right click on the project `closure-compiler-parent` and select `Build Project`).\n11. See *Using Maven* above to build the JAR.\n\n## Running\n\nOn the command line, at the root of this project, type\n\n```\njava -jar target/closure-compiler-1.0-SNAPSHOT.jar\n```\n\nThis starts the compiler in interactive mode. Type\n\n```javascript\nvar x = 17 + 25;\n```\n\nthen hit \"Enter\", then hit \"Ctrl-Z\" (on Windows) or \"Ctrl-D\" (on Mac or Linux)\nand \"Enter\" again. The Compiler will respond:\n\n```javascript\nvar x=42;\n```\n\nThe Closure Compiler has many options for reading input from a file, writing\noutput to a file, checking your code, and running optimizations. To learn more,\ntype\n\n```\njava -jar compiler.jar --help\n```\n\nMore detailed information about running the Closure Compiler is available in the\n[documentation](https://developers.google.com/closure/compiler/docs/gettingstarted_app).\n\n\n### Run using Eclipse\n\n1. Open the class `src/com/google/javascript/jscomp/CommandLineRunner.java` or create your own extended version of the class.\n2. Run the class in Eclipse.\n3. See the instructions above on how to use the interactive mode - but beware of the [bug](https://stackoverflow.com/questions/4711098/passing-end-of-transmission-ctrl-d-character-in-eclipse-cdt-console) regarding passing \"End of Transmission\" in the Eclipse console.\n\n\n## Compiling Multiple Scripts\n\nIf you have multiple scripts, you should compile them all together with one\ncompile command.\n\n```bash\njava -jar compiler.jar --js_output_file=out.js in1.js in2.js in3.js ...\n```\n\nYou can also use minimatch-style globs.\n\n```bash\n# Recursively include all js files in subdirs\njava -jar compiler.jar --js_output_file=out.js 'src/**.js'\n\n# Recursively include all js files in subdirs, excluding test files.\n# Use single-quotes, so that bash doesn't try to expand the '!'\njava -jar compiler.jar --js_output_file=out.js 'src/**.js' '!**_test.js'\n```\n\nThe Closure Compiler will concatenate the files in the order they're passed at\nthe command line.\n\nIf you're using globs or many files, you may start to run into\nproblems with managing dependencies between scripts. In this case, you should\nuse the [Closure Library](https://developers.google.com/closure/library/). It\ncontains functions for enforcing dependencies between scripts, and Closure Compiler\nwill re-order the inputs automatically.\n\n## How to Contribute\n### Reporting a bug\n1. First make sure that it is really a bug and not simply the way that Closure Compiler works (especially true for ADVANCED_OPTIMIZATIONS).\n * Check the [official documentation](https://developers.google.com/closure/compiler/)\n * Consult the [FAQ](https://github.com/google/closure-compiler/wiki/FAQ)\n * Search on [Stack Overflow](https://stackoverflow.com/questions/tagged/google-closure-compiler) and in the [Closure Compiler Discuss Group](https://groups.google.com/forum/#!forum/closure-compiler-discuss)\n * Look through the list of [compiler assumptions](https://github.com/google/closure-compiler/wiki/Compiler-Assumptions).\n2. If you still think you have found a bug, make sure someone hasn't already reported it. See the list of [known issues](https://github.com/google/closure-compiler/issues).\n3. If it hasn't been reported yet, post a new issue. Make sure to add enough detail so that the bug can be recreated. The smaller the reproduction code, the better.\n\n### Suggesting a Feature\n1. Consult the [FAQ](https://github.com/google/closure-compiler/wiki/FAQ) to make sure that the behaviour you would like isn't specifically excluded (such as string inlining).\n2. Make sure someone hasn't requested the same thing. See the list of [known issues](https://github.com/google/closure-compiler/issues).\n3. Read up on [what type of feature requests are accepted](https://github.com/google/closure-compiler/wiki/FAQ#how-do-i-submit-a-feature-request-for-a-new-type-of-optimization).\n4. Submit your request as an issue.\n\n### Submitting patches\n1. All contributors must sign a contributor license agreement (CLA).\n   A CLA basically says that you own the rights to any code you contribute,\n   and that you give us permission to use that code in Closure Compiler.\n   You maintain the copyright on that code.\n   If you own all the rights to your code, you can fill out an\n   [individual CLA](https://code.google.com/legal/individual-cla-v1.0.html).\n   If your employer has any rights to your code, then they also need to fill out\n   a [corporate CLA](https://code.google.com/legal/corporate-cla-v1.0.html).\n   If you don't know if your employer has any rights to your code, you should\n   ask before signing anything.\n   By default, anyone with an @google.com email address already has a CLA\n   signed for them.\n2. To make sure your changes are of the type that will be accepted, ask about your patch on the [Closure Compiler Discuss Group](https://groups.google.com/forum/#!forum/closure-compiler-discuss)\n3. Fork the repository.\n4. Make your changes. Check out our\n   [coding conventions](https://github.com/google/closure-compiler/wiki/Contributors#coding-conventions)\n   for details on making sure your code is in correct style.\n5. Submit a pull request for your changes. A project developer will review your work and then merge your request into the project.\n\n## Closure Compiler License\n\nCopyright 2009 The Closure Compiler Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n## Dependency Licenses\n\n### Rhino\n\n<table>\n  <tr>\n    <td>Code Path</td>\n    <td>\n      <code>src/com/google/javascript/rhino</code>, <code>test/com/google/javascript/rhino</code>\n    </td>\n  </tr>\n\n  <tr>\n    <td>URL</td>\n    <td>https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>1.5R3, with heavy modifications</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Netscape Public License and MPL / GPL dual license</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>A partial copy of Mozilla Rhino. Mozilla Rhino is an\nimplementation of JavaScript for the JVM.  The JavaScript\nparse tree data structures were extracted and modified\nsignificantly for use by Google's JavaScript compiler.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>The packages have been renamespaced. All code not\nrelevant to the parse tree has been removed. A JsDoc parser and static typing\nsystem have been added.</td>\n  </tr>\n</table>\n\n### Args4j\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>http://args4j.kohsuke.org/</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>2.33</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>MIT</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>args4j is a small Java class library that makes it easy to parse command line\noptions/arguments in your CUI application.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### Guava Libraries\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/google/guava</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>20.0</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Apache License 2.0</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Google's core Java libraries.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### JSR 305\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/findbugsproject/findbugs</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>3.0.1</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>BSD License</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Annotations for software defect detection.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### JUnit\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>http://junit.org/junit4/</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>4.12</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Common Public License 1.0</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>A framework for writing and running automated tests in Java.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### Protocol Buffers\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/google/protobuf</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>3.0.2</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>New BSD License</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Supporting libraries for protocol buffers,\nan encoding of structured data.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### RE2/J\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/google/re2j</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>1.3</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>New BSD License</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Linear time regular expression matching in Java.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### Truth\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/google/truth</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>0.32</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Apache License 2.0</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Assertion/Proposition framework for Java unit tests</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### Ant\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://ant.apache.org/bindownload.cgi</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>1.9.7</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Apache License 2.0</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Ant is a Java based build tool. In theory it is kind of like \"make\"\nwithout make's wrinkles and with the full portability of pure java code.</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### GSON\n\n<table>\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/google/gson</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>2.7</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Apache license 2.0</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>A Java library to convert JSON to Java objects and vice-versa</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>None</td>\n  </tr>\n</table>\n\n### Node.js Closure Compiler Externs\n\n<table>\n  <tr>\n    <td>Code Path</td>\n    <td><code>contrib/nodejs</code></td>\n  </tr>\n\n  <tr>\n    <td>URL</td>\n    <td>https://github.com/dcodeIO/node.js-closure-compiler-externs</td>\n  </tr>\n\n  <tr>\n    <td>Version</td>\n    <td>e891b4fbcf5f466cc4307b0fa842a7d8163a073a</td>\n  </tr>\n\n  <tr>\n    <td>License</td>\n    <td>Apache 2.0 license</td>\n  </tr>\n\n  <tr>\n    <td>Description</td>\n    <td>Type contracts for NodeJS APIs</td>\n  </tr>\n\n  <tr>\n    <td>Local Modifications</td>\n    <td>Substantial changes to make them compatible with NpmCommandLineRunner.</td>\n  </tr>\n</table>\n"
  },
  {
    "path": "src/build-utils/closure-compiler.lisp",
    "content": "(\n :version \"20220301\"\n :url \"https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v20220301/closure-compiler-v20220301.jar\"\n)\n"
  },
  {
    "path": "src/build-utils/common.lisp",
    "content": "(defpackage :build-utils/common\n  (:use #:cl)\n  (:export #:find-transitive-system-deps\n           #:safe-run-program))\n(in-package :build-utils/common)\n\n(defun all-transitive-deps (system)\n  \"Find all transitive deps in topological sort order. Uses Kahn's\n  algorithm.\"\n  (let ((L nil)\n        (seen (make-hash-table :test 'equal)))\n    (labels ((push-seen (x)\n               #+nil\n               (assert (stringp x))\n               (setf (gethash x seen) t))\n             (seenp (x)\n               #+nil\n               (assert (stringp x))\n               (gethash x seen))\n             (visit (n)\n               (unless (seenp n)\n                 (push-seen n)\n                 (dolist (m (asdf:system-depends-on (asdf:find-system n)))\n                   (visit m))\n                 (push-seen n)\n                 (push n L))))\n      (visit (asdf:component-name system)))\n    (nreverse L)))\n\n(defun find-transitive-system-deps (system type)\n  (let ((all-deps (all-transitive-deps system)))\n    (loop for x in (mapcar 'asdf:find-system all-deps)\n          if (typep x type)\n            collect x)))\n\n(defun safe-run-program (x)\n  #+nil(log:info \"Running: ~s\" x)\n  (uiop:run-program\n   (loop for el in x\n         if (pathnamep el)\n           collect (namestring el)\n         else\n           collect el)\n   :standard-output t\n   :error-output t))\n"
  },
  {
    "path": "src/build-utils/css-package.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :build-utils/css-package\n  (:use :cl\n        :asdf\n   :alexandria)\n  (:import-from :build-utils/js-package\n                :web-asset)\n  (:export :css-library\n   :css-system\n           :css-file\n   :scss-file))\n(in-package :build-utils/css-package)\n\n(defun sanitize-name (x)\n  (substitute #\\~ #\\/ x))\n\n(defclass copy-sources-op (asdf:operation)\n  ()\n  (:documentation \"Copy all the source files to a given output directory\"))\n\n(defclass css-library (asdf:system)\n  ((import-path :initarg :import-path\n                :initform #P \"\"\n                         :accessor import-path)))\n\n(defclass css-system (css-library web-asset)\n  ())\n\n(defclass base-css-file (asdf:static-file)\n  ())\n\n(defclass scss-file (base-css-file)\n  ()\n  (:default-initargs :type \"scss\"))\n\n(defclass css-file (base-css-file)\n  ()\n  (:default-initargs :type \"css\"))\n\n(defmethod asdf:input-files ((o copy-sources-op) (c css-library))\n  (mapcar\n   'asdf:component-pathname\n    (asdf:required-components c\n                              :keep-component 'base-css-file)))\n\n(defmethod asdf:output-files ((o copy-sources-op) (c css-library))\n  (let ((name (sanitize-name (component-name c))))\n    (list\n     (make-pathname\n      :directory (list :relative)\n      :name (format nil \"~a--sources-copy\" name)\n      :type \"store\"))))\n\n(defmethod asdf:operation-done-p ((o copy-sources-op) (c css-library))\n  \"Check if the operation is done by comparing timestamps of output vs both input files and the system definition\"\n  (let ((output-files (asdf:output-files o c))\n        (input-files (asdf:input-files o c))\n        (system-file (asdf:system-source-file c)))\n    (and output-files\n         (every #'uiop:file-exists-p output-files)\n         (let ((output-time (reduce #'max output-files :key #'file-write-date)))\n           (and (every (lambda (input)\n                         (< (file-write-date input) output-time))\n                       input-files)\n                ;; Also check that system definition hasn't changed\n                (< (file-write-date system-file) output-time))))))\n\n(defun rel-path (child root)\n  (assert child)\n  (assert root)\n  (cond\n    ((eql child root)\n     (make-pathname :directory '(:relative)))\n    (t\n     (let ((parent (asdf:component-parent child)))\n       (let ((final\n               (merge-pathnames\n                (etypecase child\n                  (module\n                   (asdf:component-relative-pathname child))\n                  (t\n                   (asdf:component-relative-pathname child)))\n                (rel-path parent root))))\n         final)))))\n\n\n(defclass css-file-content ()\n  ((name :initarg :name\n         :reader css-file-content-name)\n   (content :initarg :content\n            :accessor css-file-content-content)))\n\n(defmethod asdf:perform ((o copy-sources-op) (c css-library))\n  #+nil(log:info \"performing: ~a, ~a\" o c)\n  (destructuring-bind (output-file)\n      (asdf:output-files o c)\n    (ensure-directories-exist output-file)\n    (let ((results nil))\n      (loop for component in (asdf:required-components c :keep-component 'base-css-file)\n            for rel-path = (rel-path component c)\n            if (not (typep component 'asdf:module))\n              do\n                 (let ((input (asdf:component-pathname component)))\n                   (push\n                    (make-instance 'css-file-content\n                                   :name (merge-pathnames\n                                          rel-path\n                                          (import-path c))\n                                   :content (uiop:read-file-string input))\n                    results)))\n      (uiop:with-staging-pathname (output-file output-file)\n        (with-open-file (stream output-file :direction :output :if-exists :overwrite\n                                :element-type '(unsigned-byte 8))\n          (cl-store:store results stream))))))\n\n(defmethod asdf:input-files ((o compile-op) (c css-system))\n  (call-next-method))\n\n\n\n(defmethod output-files ((o compile-op) (c css-system))\n  (let ((name (string-downcase (sanitize-name (component-name c)))))\n    (list\n     (pathname\n      (format nil \"~a.css\" name))\n     (pathname\n      (format nil \"~a.css.map\" name)))))\n\n(defun copy-component-children (c output-dir root-dir)\n  (loop for x in (component-children c)\n        do\n           (typecase x\n             (asdf:module\n              ;;(format t \"copying module: ~a (~S)~%\" x (component-children x))\n              (copy-component-children\n               x\n               output-dir\n               root-dir))\n             (t\n              (let* ((pathname (component-pathname x))\n                     (rel-dir (subseq (pathname-directory pathname)\n                                      (length (pathname-directory root-dir))))\n                     (output-file (make-pathname\n                                   :name (pathname-name (component-pathname x))\n                                   :type (asdf:file-type x)\n                                   :directory (append (pathname-directory output-dir) rel-dir)\n                                   :defaults output-dir)))\n                (ensure-directories-exist output-file)\n                (uiop:copy-file (component-pathname x)\n                                output-file))))))\n\n\n(defun validate-dir-p (output-dir x)\n  (let ((prefix (pathname-directory output-dir)))\n    (equal\n     prefix\n     (subseq (pathname-directory x)\n             0 (length prefix)))))\n\n(defun sass ()\n  (flet ((rel (name)\n           (namestring\n            (asdf:system-relative-pathname :build-utils name))))\n   (cond\n     ((uiop:os-macosx-p)\n      (rel \"darwin/dart-sass/sass\"))\n     ((uiop:os-unix-p)\n      (or\n       #+arm64\n       (rel \"linux-arm64/dart-sass/sass\")\n       (rel \"dart-sass/sass\")))\n     ((uiop:os-windows-p)\n      ;; choco install sass\n      \"sass\")\n     (t\n      (error \"unsupported implementation could not find `sass`\")))))\n\n\n\n(defmethod asdf:component-depends-on ((o copy-sources-op) (c css-library))\n  (loop for x in (asdf:system-depends-on c)\n        collect `(copy-sources-op ,x)))\n\n(defmethod asdf:component-depends-on ((o compile-op) (c css-system))\n  `((copy-sources-op ,(asdf:component-name c))))\n\n(defun copy-css-to-dir (dep output-dir)\n  ;;(format t \"Copying CSS: ~a, ~a~%\" dep output-dir)\n  (let ((data (cl-store:restore (asdf:output-file 'copy-sources-op dep))))\n    (loop for css-file-content in data\n          do\n             (let ((file (ensure-directories-exist\n                          (path:catfile output-dir (css-file-content-name css-file-content)))))\n               (when (uiop:file-exists-p file)\n                 (error \"This CSS file already exists, original: ~a, new ~a\"\n                        (uiop:read-file-string file)\n                        (css-file-content-content css-file-content)))\n              (with-open-file (stream file\n                                      :direction :output\n                                      :external-format :utf-8)\n                (write-string (css-file-content-content css-file-content)\n                              stream))))))\n\n(defmethod asdf:perform ((o compile-op) (c css-system))\n  (destructuring-bind\n      (output-file source-map)\n      (output-files o c)\n    (let ((tmp-output (make-pathname :type \"css-tmp\" :defaults output-file)))\n      (tmpdir:with-tmpdir (imports)\n        (loop for dep in (required-components c\n                                              :other-systems t\n                                              :keep-component 'css-library)\n              do\n                 (copy-css-to-dir\n                  dep imports))\n        (multiple-value-bind (out err ret)\n            (uiop:run-program\n             `(,(sass)\n               \"--style=compressed\"\n               \"-I\" ,(namestring imports)\n               ,(let ((child (car (input-files 'copy-sources-op c))))\n                  (namestring\n                   child))\n               ,(namestring tmp-output))\n             :element-type 'character\n             :external-format :latin-1\n             :output 'string\n             :error-output 'string\n             :ignore-error-status t)\n          (unless (= ret 0)\n            (error \"Could not compile css assets: ~%~%stdout:~a~%~%stderr:~%~a~%\" out err))))\n      (uiop:rename-file-overwriting-target tmp-output output-file)\n      (uiop:rename-file-overwriting-target (format nil \"~a.map\" (namestring tmp-output))\n                                           source-map))))\n\n(defun get-css-component (dir)\n  (cond\n    ((path:-d dir)\n     (let ((name (car (last (pathname-directory dir)))))\n       `(:module ,name :components (,@ (remove-if 'null\n                                                   (loop for x in (fad:list-directory dir)\n                                                         collect (get-css-component x)))))))\n    ((equal \"scss\" (pathname-type dir))\n     `(,(let ((*package* :cl-user)) (format nil \"~s\" 'scss-file)) ,(pathname-name dir)))\n    ((equal \"css\" (pathname-type dir))\n     `(,(let ((*package* :cl-user)) (format nil \"~s\" 'css-file))\n       ,(pathname-name dir)))))\n"
  },
  {
    "path": "src/build-utils/dart-sass/sass",
    "content": "#!/bin/sh\n\n# This script drives the standalone dart-sass package, which bundles together a\n# Dart executable and a snapshot of dart-sass.\n\nfollow_links() {\n  file=\"$1\"\n  while [ -h \"$file\" ]; do\n    # On Mac OS, readlink -f doesn't work.\n    file=\"$(readlink \"$file\")\"\n  done\n  echo \"$file\"\n}\n\n# Unlike $0, $BASH_SOURCE points to the absolute path of this file.\npath=`dirname \"$(follow_links \"$0\")\"`\nexec \"$path/src/dart\" \"$path/src/sass.snapshot\" \"$@\"\n"
  },
  {
    "path": "src/build-utils/dart-sass/src/LICENSE",
    "content": "Dart Sass license:\n\nCopyright (c) 2016, Google Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\nDart SDK license:\n\nCopyright 2012, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\n_fe_analyzer_shared, checked_yaml and package_config license:\n\nCopyright 2019, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nanalyzer, args, csslib and logging license:\n\nCopyright 2013, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\narchive license:\n\n   Copyright 2013 Brendan Duncan\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n   \n   \nSome code has been derived from the following projects:\n\nzlib/inflate:\n  JavaScript Zlib Library, https://github.com/imaya/zlib.js\n  The MIT License\n  Copyright (c) 2012 imaya\n\nzlib/deflate:\n  Java JZLib Library, http://www.jcraft.com/jzlib/\n  Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are met:\n  \n    1. Redistributions of source code must retain the above copyright notice,\n       this list of conditions and the following disclaimer.\n  \n    2. Redistributions in binary form must reproduce the above copyright \n       notice, this list of conditions and the following disclaimer in \n       the documentation and/or other materials provided with the distribution.\n  \n    3. The names of the authors may not be used to endorse or promote products\n       derived from this software without specific prior written permission.\n  \n  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,\n  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\n  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,\n  INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,\n  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,\n  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\n  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nbzip2:\n  This program, \"bzip2\", the associated library \"libbzip2\", and all\n  documentation, are copyright (C) 1996-2010 Julian R Seward.  All\n  rights reserved.\n  \n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions\n  are met:\n  \n  1. Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer.\n  \n  2. The origin of this software must not be misrepresented; you must \n     not claim that you wrote the original software.  If you use this \n     software in a product, an acknowledgment in the product \n     documentation would be appreciated but is not required.\n  \n  3. Altered source versions must be plainly marked as such, and must\n     not be misrepresented as being the original software.\n  \n  4. The name of the author may not be used to endorse or promote \n     products derived from this software without specific prior written \n     permission.\n  \n  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS\n  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE\n  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\n  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n  \n  Julian Seward, jseward@bzip.org\n  bzip2/libbzip2 version 1.0.6 of 6 September 2010\n\n\n\n--------------------------------------------------------------------------------\n\nasync, cli_util, collection, convert, crypto, mime, package_resolver,\nshelf_static, source_map_stack_trace, stream_channel, typed_data and vm_service\nlicense:\n\nCopyright 2015, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nboolean_selector, meta, shelf_packages_handler, test_descriptor and\nweb_socket_channel license:\n\nCopyright 2016, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncharcode, dart_style, glob, http, http_multi_server, http_parser, matcher, path,\npool, pub_semver, shelf, shelf_web_socket, source_maps, source_span,\nstack_trace, string_scanner, test, watcher and yaml license:\n\nCopyright 2014, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncli_pkg license:\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\n--------------------------------------------------------------------------------\n\ncli_repl license:\n\nCopyright (c) 2018, Jennifer Thakar.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the project nor the names of its contributors may be\n      used to endorse or promote products derived from this software without\n      specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncoverage and quiver license:\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n--------------------------------------------------------------------------------\n\ngrinder and webkit_inspection_protocol license:\n\nCopyright 2013, Google Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n    * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nhtml license:\n\nCopyright (c) 2006-2012 The Authors\n\nContributors:\nJames Graham - jg307@cam.ac.uk\nAnne van Kesteren - annevankesteren@gmail.com\nLachlan Hunt - lachlan.hunt@lachy.id.au\nMatt McDonald - kanashii@kanashii.ca\nSam Ruby - rubys@intertwingly.net\nIan Hickson (Google) - ian@hixie.ch\nThomas Broyer - t.broyer@ltgt.net\nJacques Distler - distler@golem.ph.utexas.edu\nHenri Sivonen - hsivonen@iki.fi\nAdam Barth - abarth@webkit.org\nEric Seidel - eric@webkit.org\nThe Mozilla Foundation (contributions from Henri Sivonen since 2008)\nDavid Flanagan (Mozilla) - dflanagan@mozilla.com\nGoogle Inc. (contributed the Dart port) - misc@dartlang.org\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\nio, json_annotation, pedantic, stream_transform, term_glyph and test_process\nlicense:\n\nCopyright 2017, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\njs license:\n\nCopyright 2012, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nmustache license:\n\nCopyright (c) 2013, Greg Lowe\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\nRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\nRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n--------------------------------------------------------------------------------\n\nnode_interop license:\n\nCopyright (c) 2017, Anatoly Pulyaevskiy.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nnode_io license:\n\nCopyright (c) 2018, Anatoly Pulyaevskiy.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nnode_preamble license:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Michael Bullington\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n===\n\nCopyright 2012, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npetitparser license:\n\nThe MIT License\n\nCopyright (c) 2006-2020 Lukas Renggli.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\npubspec_parse, test_api and test_core license:\n\nCopyright 2018, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ntuple license:\n\nCopyright (c) 2014, the tuple project authors.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n--------------------------------------------------------------------------------\n\nxml license:\n\nThe MIT License\n\nCopyright (c) 2006-2019 Lukas Renggli.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "src/build-utils/darwin/dart-sass/sass",
    "content": "#!/bin/sh\n\n# This script drives the standalone dart-sass package, which bundles together a\n# Dart executable and a snapshot of dart-sass.\n\nfollow_links() {\n  file=\"$1\"\n  while [ -h \"$file\" ]; do\n    # On Mac OS, readlink -f doesn't work.\n    file=\"$(readlink \"$file\")\"\n  done\n  echo \"$file\"\n}\n\n# Unlike $0, $BASH_SOURCE points to the absolute path of this file.\npath=`dirname \"$(follow_links \"$0\")\"`\nexec \"$path/src/dart\" \"$path/src/sass.snapshot\" \"$@\"\n"
  },
  {
    "path": "src/build-utils/darwin/dart-sass/src/LICENSE",
    "content": "Dart Sass license:\n\nCopyright (c) 2016, Google Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\nDart SDK license:\n\nCopyright 2012, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\n_fe_analyzer_shared license:\n\nCopyright 2019, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nanalyzer license:\n\nCopyright 2013, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\narchive license:\n\nThe MIT License\n\nCopyright (c) 2013-2021 Brendan Duncan.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n--------------------------------------------------------------------------------\n\nargs, csslib and logging license:\n\nCopyright 2013, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nasync, cli_util, collection, mime, source_map_stack_trace, stream_channel and\ntyped_data license:\n\nCopyright 2015, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nboolean_selector, meta and shelf_packages_handler license:\n\nCopyright 2016, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncharcode license:\n\nCopyright 2014, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nchecked_yaml license:\n\nCopyright 2019, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncli_pkg license:\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\n--------------------------------------------------------------------------------\n\ncli_repl license:\n\nCopyright (c) 2018, Jennifer Thakar.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the project nor the names of its contributors may be\n      used to endorse or promote products derived from this software without\n      specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nconvert, crypto, shelf_static and vm_service license:\n\nCopyright 2015, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncoverage, dart_style, dartdoc, glob, http, http_parser, matcher, path, pool,\npub_semver, source_span, string_scanner, test and watcher license:\n\nCopyright 2014, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nfile license:\n\nCopyright 2017, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n--------------------------------------------------------------------------------\n\nfreezed_annotation license:\n\nMIT License\n\nCopyright (c) 2020 Remi Rousselet\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n--------------------------------------------------------------------------------\n\nfrontend_server_client license:\n\nCopyright 2020, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ngrinder and webkit_inspection_protocol license:\n\nCopyright 2013, Google Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n    * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nhtml license:\n\nCopyright (c) 2006-2012 The Authors\n\nContributors:\nJames Graham - jg307@cam.ac.uk\nAnne van Kesteren - annevankesteren@gmail.com\nLachlan Hunt - lachlan.hunt@lachy.id.au\nMatt McDonald - kanashii@kanashii.ca\nSam Ruby - rubys@intertwingly.net\nIan Hickson (Google) - ian@hixie.ch\nThomas Broyer - t.broyer@ltgt.net\nJacques Distler - distler@golem.ph.utexas.edu\nHenri Sivonen - hsivonen@iki.fi\nAdam Barth - abarth@webkit.org\nEric Seidel - eric@webkit.org\nThe Mozilla Foundation (contributions from Henri Sivonen since 2008)\nDavid Flanagan (Mozilla) - dflanagan@mozilla.com\nGoogle LLC (contributed the Dart port) - misc@dartlang.org\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\nhttp_multi_server, oauth2, shelf, shelf_web_socket, source_maps and stack_trace\nlicense:\n\nCopyright 2014, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nio, stream_transform and term_glyph license:\n\nCopyright 2017, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\njs license:\n\nCopyright 2012, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\njson_annotation license:\n\nCopyright 2017, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nlints license:\n\nCopyright 2021, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nmarkdown license:\n\nCopyright 2012, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nnode_interop license:\n\nCopyright (c) 2017, Anatoly Pulyaevskiy.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nnode_preamble license:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Michael Bullington\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n===\n\nCopyright 2012, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npackage_config license:\n\nCopyright 2019, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npetitparser and xml license:\n\nThe MIT License\n\nCopyright (c) 2006-2022 Lukas Renggli.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\npub_api_client license:\n\nMIT License\n\nCopyright (c) 2020 Leo Farias\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\npubspec license:\n\nCopyright (c) 2015, Anders Holmgren.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npubspec_parse, test_api and test_core license:\n\nCopyright 2018, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nquiver license:\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n--------------------------------------------------------------------------------\n\ntest_descriptor and web_socket_channel license:\n\nCopyright 2016, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ntest_process license:\n\nCopyright 2017, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ntuple license:\n\nCopyright (c) 2014, the tuple project authors.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n--------------------------------------------------------------------------------\n\nuri license:\n\nCopyright 2013, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nyaml license:\n\nCopyright (c) 2014, the Dart project authors.\nCopyright (c) 2006, Kirill Simonov.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "src/build-utils/deliver-script.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :build-utils/deliver-script\n  (:use #:cl\n        #:asdf)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:deliver-so-script\n   #:makeself-component\n   #:deliver-script\n   #:default-deliver))\n(in-package :build-utils/deliver-script)\n\n(defclass deliver-script (source-file)\n  ((type :initform \"lisp\")))\n\n(defmethod output-type ((self deliver-script))\n  #-mswindows nil #+mswindows \"exe\")\n\n(defmethod output-files ((o compile-op) (s deliver-script))\n  (let ((output (make-pathname :name (cl-ppcre:regex-replace-all\n                                      \"^deliver-\" (component-name s) \"\")\n                               :type (output-type s)\n                               :defaults (component-pathname s))))\n    (list\n     output\n     #+darwin\n     (make-pathname :type \"lwheap\"\n                    :defaults output))))\n\n#+lispworks\n(defmethod asdf:operation-done-p :around ((o compile-op) (s deliver-script))\n  (when (call-next-method)\n    (let* ((output-file (car (output-files o s)))\n           (core-file (merge-pathnames\n                       (car system:*line-arguments-list*)\n                       (uiop:getcwd))))\n      (>= (file-write-date output-file)\n          (file-write-date core-file)))))\n\n(defclass deliver-so-script (deliver-script)\n  ((type :initform \"lisp\")))\n\n(defmethod output-type ((self deliver-so-script))\n  #+mswindows\n  \"dll\"\n  #-mswindows\n  \"so\")\n\n(defmethod perform ((o load-op) (s deliver-script))\n  t)\n\n(defmethod perform ((o load-op) (s deliver-so-script))\n  t)\n\n(defclass makeself-component (source-file)\n  ((label :initarg :label)\n   (type :initform \"sh\")\n   (archive :initarg :archive)\n   (include-openssl-p :initform nil\n                    :initarg :include-openssl-p\n                    :reader include-openssl-p)\n   (startup-component :initarg :startup-component)))\n\n\n(defmethod output-files ((o compile-op) (m makeself-component))\n  (let ((name (component-name m)))\n    (list\n     (make-pathname :name name\n                    :type nil\n                    :defaults (component-pathname m)))))\n\n(defmethod perform ((o load-op) (m makeself-component))\n  t)\n\n(defun safe-delete-file (x)\n  (when (uiop:file-exists-p x)\n    (delete-file x)))\n\n(defun copy-openssl (dir)\n  \"Copies the system libssl and libcrypto files to the dir\"\n  (let ((src-dir\n          #+ (and linux x86-64)\n          \"/usr/lib/x86_64-linux-gnu/lib~a.so\"\n          #+ (and linux arm64)\n          \"/usr/lib/aarch64-linux-gnu/lib~a.so\"\n          #+ (and darwin arm64)\n          \"/opt/homebrew/lib/lib~a.dylib\"\n          #+ (and darwin x86-64)\n          \"/opt/openssl-x86-64/lib/lib~a.dylib\"))\n    (loop for name in (list \"crypto\" \"ssl\")\n          for src-file = (format nil src-dir name)\n          do\n             (uiop:copy-file src-file\n                             (make-pathname\n                              :name (pathname-name src-file)\n                              :type (pathname-type src-file)\n                              :defaults dir)))))\n\n(defun do-perform (tmpdir o m)\n  (with-slots (archive label startup-component license) m\n    (loop for component in (cons m archive)\n          do\n             (let ((component (find-component\n                               (component-parent m)\n                               component)))\n               (let* ((froms (if (or (typep component 'static-file)\n                                    (eq m component))\n                                (input-files 'compile-op component)\n                                (output-files 'compile-op component))))\n                 (dolist (from froms)\n                   (let ((to (make-pathname :defaults  from\n                                            :directory (pathname-directory tmpdir))))\n                     (uiop:copy-file from to)\n                     (uiop:run-program (list \"chmod\" \"a+x\" (namestring to))))))))\n    (when (include-openssl-p m)\n      (copy-openssl tmpdir))\n\n    (uiop:run-program (list #-darwin \"makeself\"\n                            #+darwin \"/opt/homebrew/bin/makeself\"\n                            \"--nox11\"\n                            (namestring tmpdir)\n                            (namestring (output-file o m))\n                            label\n                            (let ((pathname (output-file\n                                              'compile-op\n                                              m)))\n                             (format nil \"./~a.~a\"\n                                     (pathname-name pathname)\n                                     \"sh\")))\n                      :output t\n                      :error-output t)))\n\n(defmethod perform ((o compile-op) (m makeself-component))\n  (eval\n   `(let ((o ,o) (m ,m))\n        (,(find-symbol \"WITH-TMPDIR\" \"TMPDIR\") (tmpdir)\n         (do-perform tmpdir o m)))))\n\n(defun lw ()\n  (let ((version (or\n                  #+lispworks8.1\n                  \"8-1-0\"\n                  #+lispworks8\n                  \"8-0-0\"\n                  (error \"invalid version\"))))\n    (format nil\n            (or\n             #+(and :x86-64 darwin)\n             \"build/lw-console-~ax86_64\"\n             #+(or :linux (and :arm64 :darwin)) ;; TODO: we can do this on all platforms\n             (namestring\n              (asdf:system-relative-pathname\n               :build-utils\n               \"../../build/lw-console-~a\"))\n             #+mswindows\n             \".\\\\build\\\\lw-console-~a\"\n             (error \"unsupported platform image\"))\n            version)))\n\n(defmethod component-pathname ((m makeself-component))\n  (call-next-method))\n\n(defmethod perform ((o compile-op) (s deliver-script))\n  (uiop:run-program (append (cond\n                            #+lispworks\n                            (t (list (lw) \"-build\"))\n                            #+sbcl\n                            (t (list\n                                (namestring\n                                 (make-pathname\n                                  ;; win32 is just what SBCL calls it,\n                                  ;; it seems to be true on 64 bit\n                                  ;; too.\n                                  #+win32  :type #+win32 \"exe\"\n                                  :defaults\n                                  #P\"build/sbcl-console\"))\n\n                                \"--script\"))\n                            (t (error \"Unimplemented for CL implementation\")))\n\n                            (list\n                             (namestring\n                              (asdf:component-pathname s))\n                             (namestring\n                              (car (output-files o s)))))\n                    :directory (asdf:system-relative-pathname :build-utils \"../../\")\n                    :output t\n                    :error-output t))\n\n#+lispworks\n(defun safe-debugger-hook (condition old-debugger-hook)\n  (declare (ignore old-debugger-hook))\n  (let ((path \n         (dbg:log-bug-form (format nil \"an error occured : ~a\" condition)\n                           :message-stream nil)))\n    ;; Tell the user what happen, and try to make sure that if they report the error they\n    ;; include the log file. \n    (format t \"Unhandled Error log written to~%~a~%Please attach this file when reporting the error.~2%~a\"\n            path condition)\n    (abort)))\n\n\n\n(defun default-deliver (fn-name output-file deliver-level &rest args)\n  #-lispworks\n  (declare (ignore args deliver-level))\n\n  #+ccl\n  (error \"unimplemented\")\n\n  #+sbcl\n  (sb-ext:save-lisp-and-die\n   output-file\n   :toplevel fn-name\n   :executable t\n   :save-runtime-options t)\n\n  #+lispworks\n  (apply #'lw:deliver\n         (lambda ()\n           ;; Why don't we set the debugger-hook before deliver? It\n           ;; works, but if the deliver script itself crashes, then we\n           ;; won't be able to debug it easily.\n           (setf *debugger-hook* #'safe-debugger-hook)\n           (funcall fn-name))\n         output-file\n         deliver-level\n         (append\n          args\n          (list\n           :keep-function-name t\n           #+mswindows :console #+mswindows :init\n           #+mswindows :startup-bitmap-file #+mswindows nil\n           :keep-pretty-printer t\n           :keep-clos-object-printing t\n           :keep-lisp-reader t\n\n           ;;;; This is problematic: It makes it super hard to control\n           ;;;; output in error situations. (For instance, the\n           ;;;; --self-test will become noisy)\n           ;; :error-handler :btrace\n\n           :keep-symbols `(system:pipe-exit-status\n                           dspec:find-dspec-locations)\n           :packages-to-keep-symbol-names :all\n           :multiprocessing t)))\n\n  (uiop:quit))\n"
  },
  {
    "path": "src/build-utils/jar-file.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :build-utils/jar-file\n  (:use #:cl\n        #:alexandria\n        #:build-utils/common\n        #:tmpdir\n        #:asdf)\n  (:import-from #:build-utils/remote-file\n                #:remote-file)\n  (:export #:java-library\n           #:jar-file\n           #:java-class-path\n           #:java-file\n           #:jar-resource\n           #:remote-jar-file\n           #:jar-bundle-op\n           #:collect-runtime-jars\n           #:safe-run-program))\n(in-package :build-utils/jar-file)\n\n(defclass provides-jar ()\n  ()\n  (:documentation \"A component that can provide a jar file\"))\n\n(defclass java-library (asdf:system provides-jar)\n  ((compile-depends-on :initarg :compile-depends-on\n                       :reader compile-depends-on)))\n\n(defmethod jar ((lib java-library))\n  (car (output-files 'compile-op lib)))\n\n(defclass jar-file (asdf:static-file provides-jar)\n  ()\n  (:default-initargs :type \"jar\"))\n\n(defclass remote-jar-file (remote-file provides-jar)\n  ()\n  (:default-initargs :remote-file-type \"jar\"))\n\n(defmethod jar ((s remote-jar-file))\n  (car (output-files 'compile-op s)))\n\n(defclass jar-resource (asdf:static-file)\n  ()\n  (:default-initargs :type nil))\n\n(defclass java-file (asdf:source-file)\n  ()\n  (:default-initargs :type \"java\"))\n\n(defmethod jar ((x jar-file))\n  (component-pathname x))\n\n(defun java-class-path (system)\n  (append\n   (typecase system\n     (java-library\n      (cons\n       (car (output-files 'compile-op system))\n       (loop for x in (component-children system)\n             if (typep x 'provides-jar)\n               collect (jar x))))\n     (remote-jar-file\n      (output-files 'compile-op system)))\n   (compile-class-path system)))\n\n(defun compile-class-path (system)\n  (apply 'append\n   (mapcar 'java-class-path\n            (mapcar (lambda (x) (find-system x))\n                    (system-depends-on system)))))\n\n(defmethod output-files ((o compile-op) (s java-library))\n  (let ((name (string-downcase (component-name s))))\n    (mapcar 'pathname\n     (list (format nil \"~a.jar\" name)\n           (format nil \"~a-classes/\" name)))))\n\n(defun all-java-files (component)\n  (loop for x in (component-children component)\n        if (typep x 'parent-component)\n          append (all-java-files x)\n        if (typep x 'java-file)\n          collect (component-pathname x)))\n\n(defun all-resource-files (component)\n  (loop for x in (component-children component)\n        appending\n        (etypecase x\n          (remote-jar-component\n           (output-file 'compile-op x))\n          (parent-component\n           (all-resource-files x))\n          (java-resource\n           (component-pathname x)))))\n\n(defun join (sep list)\n  (let ((firstp t))\n   (let ((output (make-string-output-stream)))\n     (dolist (x list)\n       (cond\n         (firstp\n          (format output \"~a\" x)\n          (setf firstp nil))\n         (t\n          (format output \"~a~a\" sep x))))\n     (get-output-stream-string output))))\n\n(defun java-prefix ()\n  (cond\n   ((uiop:os-macosx-p)\n    \"/opt/homebrew/opt/java/bin/\")\n   (t \"\")))\n\n(defun jar-binary ()\n  (str:concat (java-prefix) \"jar\"))\n\n(defun javac-binary ()\n  (str:concat (java-prefix) \"javac\"))\n \n\n(defmethod asdf:perform ((o compile-op) (s java-library))\n  #+nil(log:info \"compiling: ~s\" s)\n  (destructuring-bind (jar dir) (output-files o s)\n    (when (uiop:directory-exists-p dir)\n      (uiop:delete-directory-tree dir :validate (lambda (x)\n                                                  (or\n                                                   (equal nil (pathname-name x))\n                                                   (equal \"class\" (pathname-type x))))))\n    (ensure-directories-exist dir)\n    (cond\n      ((all-java-files s)\n       (loop for x in (compile-class-path s)\n             do\n                (assert (uiop:file-exists-p x)))\n       (let ((cmd `(,(javac-binary)\n                    \"-d\" ,(namestring dir)\n                    \"-cp\" ,(join \n                            (cond \n                             ((uiop:os-windows-p)\n                              \";\")\n                             (t\n                              \":\"))\n                            (mapcar 'namestring (compile-class-path s)))\n                    ,@(mapcar 'namestring (all-java-files s)))))\n         (safe-run-program cmd))))\n\n    (copy-resources s dir)\n    (when (path:-e jar)\n     (delete-file jar))\n    (safe-run-program (list (jar-binary)\n                            \"cf\" (namestring jar)\n                            \"-C\" (namestring dir)\n                            \".\"))))\n\n\n(defmethod component-depends-on ((o compile-op) (s java-library))\n  `((load-op ,@(system-depends-on s))\n    ,@ (call-next-method)))\n\n(defmethod copy-resources ((s asdf:component) directory)\n  (loop for x in (component-children s) do\n    (etypecase x\n      (parent-component\n       (copy-resources x  (uiop:merge-pathnames*\n                           (make-pathname :directory (list :relative (component-name s)))\n                           directory)))\n      (jar-resource\n       (let ((dest (uiop:merge-pathnames*\n                    (component-relative-pathname x)\n                    directory)))\n         (ensure-directories-exist dest)\n         (copy-file (component-pathname x)\n                    dest)))\n      (t\n       (values)))))\n\n(defclass jar-bundle-op (selfward-operation)\n  ((asdf:selfward-operation :initform '(compile-op)\n                            :allocation :class))\n  (:documentation \"Creates a bundled jar with all the runtime dependencies\"))\n\n(defmethod output-files ((o jar-bundle-op) (lib java-library))\n  (list\n   (format nil \"~a.bundle.jar\" (component-name lib))))\n\n(defmethod runtime-depends-on ((lib java-library))\n  (let ((compile-deps (mapcar #'asdf:coerce-name (compile-depends-on lib)))\n        (all-deps (mapcar #'asdf:coerce-name (system-depends-on lib))))\n    (set-difference all-deps compile-deps :test 'string-equal)))\n\n(defmethod runtime-depends-on (lib)\n  (system-depends-on lib))\n\n(defmethod collect-runtime-jars ((lib provides-jar))\n  (list\n   (jar lib)))\n\n(defmethod collect-runtime-jars ((lib java-library))\n  (append\n   (loop for dep in (runtime-depends-on lib)\n      appending\n        (collect-runtime-jars (find-system dep)))\n   (loop for dep in (component-children lib)\n      appending\n\t(collect-runtime-jars dep))\n   (call-next-method)))\n\n(defmethod collect-runtime-jars (lib)\n  nil)\n\n(defmethod collect-runtime-jars :around (lib)\n  (remove-duplicates (call-next-method) :test 'equal))\n\n\n(defun merge-jars (output input)\n  (tmpdir:with-tmpdir (x)\n    (safe-run-program\n     (list \"unzip\"\n           (namestring input)\n           \"-d\" x))\n    (safe-run-program\n     (list \"ls\" x))\n    (safe-run-program\n     (list \"jar\"\n           \"-uf\" output\n           \"-C\" x\n           \".\"))))\n\n(defmethod perform ((o jar-bundle-op) (lib java-library))\n  #+nil(log:info \"bundling: ~s\" lib)\n  (let ((output-file (output-file o lib))\n        (jars (collect-runtime-jars lib)))\n    (uiop:with-staging-pathname (output-file)\n      (uiop:copy-file (car jars) output-file)\n      (loop for x in (cdr jars) do\n        (merge-jars output-file x)))))\n\n(defmethod asdf:perform ((o compile-op) (s java-file))\n  ;; do nothing! All compilation is on the system level\n  nil)\n\n(defmethod asdf:perform ((o load-op) (s java-file))\n  nil)\n\n;; (all-java-files (find-system :java.main))\n;; (compile-system :java.main)\n;; (compile-class-path (find-system :java.main))\n"
  },
  {
    "path": "src/build-utils/js-package.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :build-utils/js-package\n  (:use #:cl\n        #:asdf\n        #:alexandria)\n  (:import-from :build-utils/common\n                #:find-transitive-system-deps)\n  (:export #:web-asset\n           #:js-system\n           #:js-library\n           #:js-file\n           #:js-file\n           #:ps-file))\n(in-package :build-utils/js-package)\n\n(defclass web-asset ()\n  ())\n\n(defclass js-library (asdf:system)\n  ())\n\n(defclass js-system (js-library web-asset)\n  ())\n\n(defclass js-file (asdf:static-file)\n  ()\n  (:default-initargs :type \"js\"))\n\n(defclass ps-file (asdf:static-file)\n  ()\n  (:default-initargs :type \"lisp\"))\n\n(defmethod js-lib-input-files ((o compile-op) (m module))\n  (loop for x in (component-children m) appending\n        (js-lib-input-files o x)))\n\n(defmethod js-lib-input-files ((o compile-op) (j js-file))\n  (asdf:input-files o j))\n\n(defmethod js-lib-input-files ((o compile-op) (p ps-file))\n  (asdf:output-files o p))\n\n(defmethod js-lib-input-files ((o compile-op) (j js-library))\n  (call-next-method))\n\n(defmethod js-input-files ((o compile-op) (j js-system))\n  (let ((libraries (find-transitive-system-deps j 'js-library)))\n    (loop for lib in libraries\n          appending (js-lib-input-files o lib))))\n\n(defmethod asdf:output-files ((o compile-op) (j js-system))\n  (list\n   ;; okay, where does this go?\n   (format nil \"~a.js\" (string-downcase (component-name j)))\n   (format nil \"~a.js.map\" (string-downcase (component-name j)))))\n\n(defmethod asdf:output-files ((o compile-op) (p ps-file))\n  (list\n   (format nil \"~a.js\" (string-downcase (component-name p)))))\n\n(defmethod asdf:perform ((o compile-op) (p ps-file))\n  (uiop:with-staging-pathname (output-file (asdf:output-file o p))\n   (with-open-file (out output-file :direction :output\n                                    :if-exists :supersede)\n     (write-string\n      (ps:ps-compile-file (car (asdf:input-files o p)))\n      out))))\n\n(defun guess-java ()\n  (loop for path in (list\n                     \"/opt/homebrew/opt/openjdk/bin/java\"\n                     \"/usr/bin/java\")\n        if (probe-file path)\n          return path))\n\n(defmethod asdf:perform ((o compile-op) (j js-system))\n  (restart-case\n      (let ((input-files (remove-duplicates (js-input-files o j) :test 'equal :from-end t)))\n        ;;(format *error-output* \"Compling: ~S\" input-files)\n        (loop for x in input-files\n              do\n                 (unless (probe-file x)\n                   (error \"The file ~A does not exist\" x)))\n        (multiple-value-bind  (out err ret)\n            (uiop:run-program\n             (append\n              (list\n               #-darwin \"java\"\n               #+darwin (guess-java)\n               \"-jar\"\n               (namestring\n                (asdf:system-relative-pathname\n                 :build-utils \"closure-compiler/closure-compiler-v20240317.jar\"))\n               \"--js_output_file\"\n               (namestring (car (output-files o j)))\n               \"--create_source_map\"\n               (namestring (cadr (output-files o j))))\n              (loop for filename in input-files\n                    appending (list \"--js\" (namestring filename))))\n             :output 'string\n             :error-output 'string\n             :ignore-error-status t)\n          (unless (= ret 0)\n            (error \"Could not compile js assets: ~%~%stdout:~a~%~%stderr:~%~a~%\" out err)))\n        (with-open-file (s (car (output-files o j)))\n         (format t \"File size is: ~A\" (file-length s))))\n    (retry-perform ()\n      (perform o j))))\n\n\n;; (get-js-component \"/home/arnold/builds/web/src/common/bootstrap-5.0.0-beta2/js/src/\")\n;;\n;; At time of writing, we haven't used this to actually generate any\n;; asd files, so it might be buggy.\n(defun get-js-component (dir)\n  (cond\n    ((path:-d dir)\n     (let ((name (car (last (pathname-directory dir)))))\n       `(:module ,name :components (,@ (remove-if 'null\n                                                   (loop for x in (fad:list-directory dir)\n                                                         collect (get-js-component x)))))))\n    ((equal \"js\" (pathname-type dir))\n     `(,(let ((*package* :cl-user)) (format nil \"~s\" 'js-file)) ,(pathname-name dir)))))\n"
  },
  {
    "path": "src/build-utils/linux-arm64/dart-sass/sass",
    "content": "#!/bin/sh\n\n# This script drives the standalone dart-sass package, which bundles together a\n# Dart executable and a snapshot of dart-sass.\n\nfollow_links() {\n  # Use `readlink -f` if it exists, but fall back to manually following symlnks\n  # for systems (like older Mac OS) where it doesn't.\n  file=\"$1\"\n  if readlink -f \"$file\" 2>&-; then return; fi\n\n  while [ -h \"$file\" ]; do\n    file=\"$(readlink \"$file\")\"\n  done\n  echo \"$file\"\n}\n\n# Unlike $0, $BASH_SOURCE points to the absolute path of this file.\npath=`dirname \"$(follow_links \"$0\")\"`\nexec \"$path/src/dart\" \"$path/src/sass.snapshot\" \"$@\"\n"
  },
  {
    "path": "src/build-utils/linux-arm64/dart-sass/src/LICENSE",
    "content": "Dart Sass license:\n\nCopyright (c) 2016, Google Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\nDart SDK license:\n\nCopyright 2012, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\n_fe_analyzer_shared license:\n\nCopyright 2019, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nanalyzer license:\n\nCopyright 2013, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\narchive license:\n\nThe MIT License\n\nCopyright (c) 2013-2021 Brendan Duncan.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n--------------------------------------------------------------------------------\n\nargs, csslib and logging license:\n\nCopyright 2013, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nasync, cli_util, collection, mime, source_map_stack_trace, stream_channel and\ntyped_data license:\n\nCopyright 2015, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nboolean_selector, meta and shelf_packages_handler license:\n\nCopyright 2016, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncharcode license:\n\nCopyright 2014, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nchecked_yaml license:\n\nCopyright 2019, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncli_pkg license:\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\n--------------------------------------------------------------------------------\n\ncli_repl license:\n\nCopyright (c) 2018, Jennifer Thakar.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the project nor the names of its contributors may be\n      used to endorse or promote products derived from this software without\n      specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nconvert, crypto, shelf_static and vm_service license:\n\nCopyright 2015, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ncoverage, dart_style, dartdoc, glob, http, http_parser, matcher, path, pool,\npub_semver, source_span, string_scanner, test and watcher license:\n\nCopyright 2014, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nfile license:\n\nCopyright 2017, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n--------------------------------------------------------------------------------\n\nfrontend_server_client license:\n\nCopyright 2020, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ngrinder and webkit_inspection_protocol license:\n\nCopyright 2013, Google Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n    * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nhtml license:\n\nCopyright (c) 2006-2012 The Authors\n\nContributors:\nJames Graham - jg307@cam.ac.uk\nAnne van Kesteren - annevankesteren@gmail.com\nLachlan Hunt - lachlan.hunt@lachy.id.au\nMatt McDonald - kanashii@kanashii.ca\nSam Ruby - rubys@intertwingly.net\nIan Hickson (Google) - ian@hixie.ch\nThomas Broyer - t.broyer@ltgt.net\nJacques Distler - distler@golem.ph.utexas.edu\nHenri Sivonen - hsivonen@iki.fi\nAdam Barth - abarth@webkit.org\nEric Seidel - eric@webkit.org\nThe Mozilla Foundation (contributions from Henri Sivonen since 2008)\nDavid Flanagan (Mozilla) - dflanagan@mozilla.com\nGoogle LLC (contributed the Dart port) - misc@dartlang.org\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\nhttp_multi_server, oauth2, shelf, shelf_web_socket, source_maps and stack_trace\nlicense:\n\nCopyright 2014, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nio, stream_transform and term_glyph license:\n\nCopyright 2017, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\njs license:\n\nCopyright 2012, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\njson_annotation license:\n\nCopyright 2017, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nlints license:\n\nCopyright 2021, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nmarkdown license:\n\nCopyright 2012, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nnode_interop license:\n\nCopyright (c) 2017, Anatoly Pulyaevskiy.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nnode_preamble license:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Michael Bullington\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n===\n\nCopyright 2012, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npackage_config license:\n\nCopyright 2019, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npetitparser and xml license:\n\nThe MIT License\n\nCopyright (c) 2006-2022 Lukas Renggli.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\npointycastle license:\n\n\nCopyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n\n--------------------------------------------------------------------------------\n\npub_api_client license:\n\nMIT License\n\nCopyright (c) 2020 Leo Farias\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\n--------------------------------------------------------------------------------\n\npubspec license:\n\nCopyright (c) 2015, Anders Holmgren.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\npubspec_parse, test_api and test_core license:\n\nCopyright 2018, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nquiver and retry license:\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n--------------------------------------------------------------------------------\n\ntest_descriptor and web_socket_channel license:\n\nCopyright 2016, the Dart project authors. \n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ntest_process license:\n\nCopyright 2017, the Dart project authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google LLC nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\ntuple license:\n\nCopyright (c) 2014, the tuple project authors.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n--------------------------------------------------------------------------------\n\nuri license:\n\nCopyright 2013, the Dart project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n--------------------------------------------------------------------------------\n\nyaml license:\n\nCopyright (c) 2014, the Dart project authors.\nCopyright (c) 2006, Kirill Simonov.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "src/build-utils/remote-file.lisp",
    "content": "(defpackage :build-utils/remote-file\n  (:use #:cl\n        #:asdf)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:remote-file))\n(in-package :build-utils/remote-file)\n\n(defclass remote-file (asdf:system)\n  ((url :initarg :url\n        :reader url)\n   (version :initarg :version\n            :reader version)\n   (remote-file-type :initarg :remote-file-type\n\t\t     :initform nil\n\t\t     :reader remote-file-type)))\n\n(defmethod perform ((o compile-op) (s remote-file))\n  (unless (version s)\n    (error \"Provide a version for remote-jar-file for caching purposes\"))\n  (let ((output (output-file o s)))\n    (unless (uiop:file-exists-p output)\n      (uiop:with-staging-pathname (output)\n        (format t \"Downloading asset: ~a~%\" (url s))\n       (uiop:run-program\n        (list \"curl\" \"-L\" (url s)\n              \"-o\"\n              (namestring output)))))))\n\n(defmethod output-files ((o compile-op) (s remote-file))\n  (list\n   (format nil \"~a-~a.~a\" (component-name s)\n           (version s)\n           (remote-file-type s))))\n"
  },
  {
    "path": "src/build-utils/wild-module.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :build-utils/wild-module\n  (:use :cl\n        :asdf\n        :alexandria)\n  (:export :*-module))\n(in-package :build-utils/wild-module)\n\n(defclass *-module (module)\n  ((component-class :accessor wild-module-component-class\n                    :initform 'static-file :initarg :component-class)\n   (component-options :accessor wild-module-component-options\n                      :initform nil :initarg :component-options)))\n\n(defmethod (setf component-children) (new-value (module *-module))\n  (when new-value\n    (asdf::sysdef-error \"Cannot explicitly set wild-module ~A's children components. Please ~\nuse a wild pathname instead.\" module)))\n\n(defmethod reinitialize-instance :after ((self *-module) &key)\n  (let ((pathname (component-pathname self)))\n    (unless (and pathname (wild-pathname-p pathname))\n      (asdf::sysdef-error \"Wild-module ~A specified with non-wild pathname ~A.\"\n                    self pathname))\n    (setf (slot-value self 'components)\n          (let* ((files (uiop:directory* pathname))\n                 (class (wild-module-component-class self))\n                 (options (wild-module-component-options self)))\n            (mapcar (lambda (file)\n                      (apply #'make-instance class\n                             :name (namestring file)\n                             :pathname file\n                             :parent self\n                             options))\n                    files)))\n    (asdf::compute-children-by-name self)\n    (values)))\n\n(defmethod input-files ((o compile-op) (c *-module)) ())\n(defmethod input-files ((o load-op) (c *-module)) ())\n"
  },
  {
    "path": "src/cl-mongo-id/LICENSE",
    "content": "Copyright (c) 2012 Lyon Bros. Enterprises, LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "src/cl-mongo-id/README.markdown",
    "content": "cl-mongo-id\n===========\n\nThis is a simple library for creating/handling MongoDB's ObjectIDs. It works by\ngrabbing key pieces of info (unix timestamp, hostname, process pid, global \"inc\"\nvalue) and creating a new ObjectID as a 12-byte array #(79 151 129 ...)\n\nIt aims to be as cross-platform/implementation as possible. If you find you're\nunable to run it on your specific platform, please\n[let me know](https://github.com/orthecreedence/cl-mongo-id/issues) and I'll do\nwhatever I can to fix it!\n\nUsage\n-----\nInstall\n\n    (ql:quickload :cl-mongo-id)\n\nCreate byte array from string id:\n\n    (mongoid:oid \"4f9638d834322b9531000005\")  ->\n        #(79 150 56 216 52 50 43 149 49 0 0 5)\n    \nCreate new id using [MongoDB ObjectID specification](http://www.mongodb.org/display/DOCS/Object+IDs):\n\n    (mongoid:oid)  ->\n        #(79 150 62 92 61 196 44 20 228 0 0 0)\n\nGrab a string representation of an ObjectID:\n\n    (mongoid:oid-str (mongoid:oid))  ->\n\t    \"4F97001C3DC42C14E4000050\"\n\nUsage with cl-mongo\n-------------------\nThe cl-mongo-id library can be used with [cl-mongo](https://github.com/fons/cl-mongo).\nThere is just one caveat: You must import the `make-bson-oid` function from the\ncl-mongo package before you can use the two together.\n\n    (import 'cl-mongo::make-bson-oid)\n    \n    (with-mongo-connection (:host \"127.0.0.1\" :db \"test\")\n      ;; querying a document of a known id\n      (db.find \"mycoll\" (kv \"_id\" (make-bson-oid :oid (mongoid:oid \"4f9638d834322b9531000005\"))))\n    \n      ;; creating a new document with an oid\n\t  (let ((doc (make-document :oid (make-bson-oid :oid (mongoid:oid)))))\n\t    (db.save \"mycoll\" doc)))\n\nThread safe\n-----------\ncl-mongo-id is built to be thread-safe (specifically the inc value). This means\nyou can create ID's via `(oid)` from several different threads without problems,\nand your inc value will be unique between all of them.\n\nHelper functions\n----------------\ncl-mongo-id has some helper functions that may be useful to you for either \ndebugging or grabbing values from your IDs. \n\nNote that all of the following helper functions take a keyword argument `:bytes`,\nwhich if set to T will return the raw bytes of that portion of the ID and not\nconvert them to an integer.\n\n### get-timestamp\nGet the unix timestamp from a Mongo Object ID:\n\n    (mongoid:get-timestamp (oid \"4f96f9fa3dc42c14e400004e\"))  ->\n        1335294458\n\n### get-pid\nGet the PID the ID was created under:\n\n    (mongoid:get-pid (oid \"4f96f9fa3dc42c14e400004e\"))  ->\n        5348\n\n### get-hostname\nGet the int value corresponding to the first three bytes of the MD5 of the hostname the ID was created on:\n\n    (mongoid:get-hostname (oid \"4f96f9fa3dc42c14e400004e\"))  ->\n        4047916\n    (mongoid:get-hostname (oid \"4f96f9fa3dc42c14e400004e\") :bytes t)  ->\n        #(61 196 44)\n\n### get-inc\nGet the ID's \"inc\" value:\n\n    (mongoid:get-inc (oid \"4f96f9fa3dc42c14e400004e\"))  ->\n        78\n\n"
  },
  {
    "path": "src/cl-mongo-id/cl-mongo-id.asd",
    "content": "(asdf:defsystem :cl-mongo-id\n  :author \"Andrew Lyon <orthecreedence@gmail.com>\"\n  :description \"A library for the creation/parsing of MongoDB Object IDs\"\n  :license \"MIT\"\n  :version \"0.1.0\"\n  :depends-on (:bordeaux-threads :md5 :local-time\n                                 :cl-intbytes)\n  :components ((:file \"mongo-id\")))\n\n(defsystem :cl-mongo-id/tests\n  :depends-on (:cl-mongo-id\n               :tmpdir\n               :fiveam)\n  :components ((:file \"test-mongo-id\")))\n"
  },
  {
    "path": "src/cl-mongo-id/mongo-id.lisp",
    "content": ";;; This is a library for generating MongoDB ObjectIDs per the client\n;;; specification:\n;;;\n;;;   https://github.com/mongodb/specifications/blob/master/source/objectid.rst\n;;;\n;;; Full documentation on github:\n;;;\n;;;   https://github.com/orthecreedence/cl-mongo-id\n;;;\n;;; Please enjoy irresponsibly =].\n;;;\n;;; Andrew Lyon <orthecreedence@gmail.com>\n\n(defpackage :cl-mongo-id\n  (:use :cl)\n  (:import-from #:cl-intbytes\n                #:int64->octets)\n  (:export :oid\n           :oid-str\n           :get-timestamp\n           :get-hostname\n           :get-pid\n           :get-inc\n           :reset-state)\n  (:nicknames :mongoid))\n(in-package :cl-mongo-id)\n\n(defvar *id-inc*)\n(defvar *id-inc-lock* (bt:make-lock))\n\n;; In previous version of the ObjectId spec this was referenced as the\n;; machine and pid. In v0.2 of the spec if was made to be a random\n;; 5-byte sequence randomly chosen per instance of the process.\n(defvar *random-value*)\n\n(defun get-current-pid (&key if-not-exists-return)\n  \"Get the current process' PID. This function does it's best to be cross-\n   implementation. If it isn't able to grab the PID from the system, it defaults\n   to returning whatever value is passed into the :if-not-exists-return key.\"\n  #+clisp\n  (system::process-id)\n  #+(and lispworks unix)\n  (system::getpid)\n  #+(and sbcl unix)\n  (sb-unix:unix-getpid)\n  #+(and cmu unix)\n  (unix:unix-getpid)\n  #+openmcl\n  (ccl::getpid)\n  #+ecl\n  (ext:getpid)\n  #-(or clisp (and lispworks unix) (and sbcl unix) (and cmu unix) (and openmcl unix) openmcl ecl)\n  if-not-exists-return)\n\n(defun reset-state ()\n  \"Resets (or sets) the random state associated with the current\nprocess. This is useful when cl-mongo-id is loaded into an image that\nis deployed to multiple machines. In that case you should call\nRESET-STATE at the start of the process.\n\nOn SBCL, CCL and Lispworks, this is done automatically when a saved\nimage is restored.\n\nThe spec is okay with using non-cryptographic random numbers. But in\nthat case we're expected to seed the RNG with something that depends\non timestamp, pid, and hostname. The in-build RNG in CL doesn't give\nus a way to explicitly seed an RNG, so instead we generate the random\nnumbers and XOR it with the hash. (The XOR operation will keep the\nfinal output random, and will also make it unlikely that two servers\nwill end up generating the random numbers.)\"\n  (let ((state (make-random-state t))\n        (hash (sxhash (format nil \"~a.~a.~a\"\n                              (local-time:now)\n                              (get-current-pid)\n                              (uiop:hostname)))))\n    (flet ((safe-random (number)\n             (mod\n              (logxor (random number)\n                      hash)\n              number)))\n      (setf *id-inc* (safe-random (ash 1 24)))\n      (let* ((value (safe-random (ash 1 40)))\n             (octets (int64->octets value)))\n        (let ()\n          (setf *random-value*\n                (subseq octets 0 5))))))\n  ;; For easy debugging, we return the state (since we need to\n  ;; visually check if the outputs look random enough. Unit tests can\n  ;; be tricky to check).\n  (values\n   (cl-intbytes:int32->octets *id-inc*)\n   *random-value*))\n\n(reset-state)\n\n#+sbcl\n(pushnew 'reset-state sb-ext:*init-hooks*)\n\n#+lispworks\n(lw:define-action \"When starting image\" \"Reset cl-mongo-id state\"\n  #'reset-state)\n\n#+ccl\n(pushnew 'reset-state ccl:*restore-lisp-functions*)\n\n(defun oid (&optional id)\n  \"Generate a mongo id, in byte vector format.\"\n  (cond\n    ((stringp id) (convert-hex-vector id))\n    ((vectorp id) id)\n    ((null id)    (create-new-id))))\n\n(defun oid-str (oid)\n  \"Given a vector ID, convert it to a string.\"\n  (let ((hex-string (make-string 24)))\n    (loop for byte across oid\n          for i from 0 by 2 do\n      (let ((byte-hex (format nil \"~2,'0X\" byte)))\n        (setf (aref hex-string i) (aref byte-hex 0)\n              (aref hex-string (1+ i)) (aref byte-hex 1))))\n    hex-string))\n\n(defun get-timestamp (oid &key bytes)\n  \"Grab the timestamp out of a vector oid. Passing :bytes t will return an array\n  of bytes corresponding to the timestamp part of the ID instead of parsing it\n  as an integer.\"\n  (let ((timestamp (subseq oid 0 4)))\n    (if bytes\n        timestamp\n        (convert-vector-int timestamp))))\n\n(defun get-hostname (oid &key bytes)\n  (error \"get-hostname is no longer supported by the ObjectId spec\"))\n\n(defun get-pid (oid &key bytes)\n  (error \"get-pid is no longer supported by the ObjectId spec\"))\n\n(defun get-inc (oid &key bytes)\n  \"Grab the inc value out of a vector oid. Passing :bytes t will return an array\n  of bytes corresponding to the inc value of the ID instead of parsing it as an\n  integer.\"\n  (let ((inc (subseq oid 9)))\n    (if bytes\n        inc\n        (convert-vector-int inc))))\n\n(defun convert-hex-vector (hex-string)\n  \"Takes a hex string, IE 4f2b8096 and converts it into a byte array:\n      4f2b8096 -> #(79 43 128 150)\n  Hex string *must* have even number of bytes.\"\n  (assert (evenp (length hex-string)))\n  (let* ((octet-count (floor (length hex-string) 2))\n         (vector (make-array octet-count :element-type '(unsigned-byte 8))))\n    (loop for i below octet-count\n          for j from 0 by 2\n          for k from 2 by 2 do\n      (setf (aref vector i)\n            (parse-integer hex-string :start j :end k :radix 16)))\n    vector))\n\n(defun convert-vector-int (vector)\n  \"Convert a byte array to an integer:\n      #(79 150 243 81) -> 1335292753\"\n  (reduce (lambda (x y)\n            (+ (ash x 8) y))\n          vector))\n\n(defun create-new-id ()\n  \"Create a brand-spankin-new ObjectId using the current timestamp/inc values,\n  along with hostname and process pid.\"\n  (let ((timestamp (logand #xFFFFFFFF (get-current-timestamp)))\n        (inc (get-inc-val)))\n    (let ((timestamp-bytes (convert-hex-vector (format nil \"~8,'0X\" timestamp)))\n          (inc-bytes (convert-hex-vector (format nil \"~6,'0X\" inc))))\n      (concatenate 'vector timestamp-bytes *random-value* inc-bytes))))\n\n(defun get-current-timestamp ()\n  \"Get current unix timestamp.\"\n  (local-time:timestamp-to-unix (local-time:now)))\n\n(defun get-inc-val ()\n  \"Thread-safe method to get current ObjectId inc value. Takes an optional\n  timestamp value to calculate inc for.\"\n  (bt:with-lock-held (*id-inc-lock*)\n    (setf *id-inc* (logand #xFFFFFF (1+ *id-inc*)))\n    *id-inc*))\n"
  },
  {
    "path": "src/cl-mongo-id/test-mongo-id.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :cl-mongo-id/test-mongo-id\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mongo-id\n                #:*random-value*\n                #:*id-inc*\n                #:reset-state))\n(in-package :cl-mongo-id/test-mongo-id)\n\n(def-suite* :cl-mongo-id)\n\n(def-fixture state ()\n  (let ((*id-inc* nil)\n        (*random-value* nil))\n    (&body)))\n\n(test reset-state-happy-path\n  (with-fixture state ()\n    (finishes\n      (reset-state))\n    (is-true *id-inc*)\n    (is (< *id-inc* (ash 1 24)))\n    (is-true *random-value*)\n    (is (= (length *random-value*) 5))))\n\n(test happy-path-generate-oid\n  (with-fixture state ()\n    (reset-state)\n    (is (eql 12 (length (mongoid:oid))))))\n"
  },
  {
    "path": "src/common/bootstrap/.babelrc.js",
    "content": "module.exports = {\n  presets: [\n    [\n      '@babel/preset-env',\n      {\n        loose: true,\n        bugfixes: true,\n        modules: false\n      }\n    ]\n  ]\n};\n"
  },
  {
    "path": "src/common/bootstrap/.browserslistrc",
    "content": "# https://github.com/browserslist/browserslist#readme\n\n>= 0.5%\nlast 2 major versions\nnot dead\nChrome >= 60\nFirefox >= 60\nFirefox ESR\niOS >= 12\nSafari >= 12\nnot Explorer <= 11\n"
  },
  {
    "path": "src/common/bootstrap/.bundlewatch.config.json",
    "content": "{\n  \"files\": [\n    {\n      \"path\": \"./dist/css/bootstrap-grid.css\",\n      \"maxSize\": \"7.5 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap-grid.min.css\",\n      \"maxSize\": \"6.55 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap-reboot.css\",\n      \"maxSize\": \"2.75 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap-reboot.min.css\",\n      \"maxSize\": \"2.5 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap-utilities.css\",\n      \"maxSize\": \"9.25 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap-utilities.min.css\",\n      \"maxSize\": \"8.5 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap.css\",\n      \"maxSize\": \"28.75 kB\"\n    },\n    {\n      \"path\": \"./dist/css/bootstrap.min.css\",\n      \"maxSize\": \"26.75 kB\"\n    },\n    {\n      \"path\": \"./dist/js/bootstrap.bundle.js\",\n      \"maxSize\": \"43.25 kB\"\n    },\n    {\n      \"path\": \"./dist/js/bootstrap.bundle.min.js\",\n      \"maxSize\": \"22.75 kB\"\n    },\n    {\n      \"path\": \"./dist/js/bootstrap.esm.js\",\n      \"maxSize\": \"28.0 kB\"\n    },\n    {\n      \"path\": \"./dist/js/bootstrap.esm.min.js\",\n      \"maxSize\": \"18.5 kB\"\n    },\n    {\n      \"path\": \"./dist/js/bootstrap.js\",\n      \"maxSize\": \"28.75 kB\"\n    },\n    {\n      \"path\": \"./dist/js/bootstrap.min.js\",\n      \"maxSize\": \"16.25 kB\"\n    }\n  ],\n  \"ci\": {\n    \"trackBranches\": [\n      \"main\",\n      \"v4-dev\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/.cspell.json",
    "content": "{\n  \"version\": \"0.2\",\n  \"words\": [\n    \"affordance\",\n    \"allowfullscreen\",\n    \"Analyser\",\n    \"autohide\",\n    \"autohiding\",\n    \"autoplay\",\n    \"autoplays\",\n    \"blazingly\",\n    \"Blockquotes\",\n    \"Bootstrappers\",\n    \"borderless\",\n    \"Brotli\",\n    \"browserslist\",\n    \"browserslistrc\",\n    \"btncheck\",\n    \"btnradio\",\n    \"callout\",\n    \"callouts\",\n    \"camelCase\",\n    \"clearfix\",\n    \"Codesniffer\",\n    \"combinator\",\n    \"Contentful\",\n    \"Cpath\",\n    \"Crossfade\",\n    \"crossfading\",\n    \"cssgrid\",\n    \"Csvg\",\n    \"Datalists\",\n    \"Deque\",\n    \"discoverability\",\n    \"docsearch\",\n    \"docsref\",\n    \"dropend\",\n    \"dropleft\",\n    \"dropright\",\n    \"dropstart\",\n    \"dropup\",\n    \"errorf\",\n    \"favicon\",\n    \"favicons\",\n    \"fieldsets\",\n    \"flexbox\",\n    \"fullscreen\",\n    \"getbootstrap\",\n    \"Grayscale\",\n    \"Hoverable\",\n    \"hreflang\",\n    \"hstack\",\n    \"importmap\",\n    \"jsdelivr\",\n    \"Jumpstart\",\n    \"keyframes\",\n    \"libera\",\n    \"libman\",\n    \"Libsass\",\n    \"lightboxes\",\n    \"Lowercased\",\n    \"markdownify\",\n    \"mediaqueries\",\n    \"minifiers\",\n    \"misfunction\",\n    \"mkdir\",\n    \"monospace\",\n    \"mouseleave\",\n    \"navbars\",\n    \"navs\",\n    \"Neue\",\n    \"noindex\",\n    \"Noto\",\n    \"offcanvas\",\n    \"offcanvases\",\n    \"Packagist\",\n    \"popperjs\",\n    \"prebuild\",\n    \"prefersreducedmotion\",\n    \"prepended\",\n    \"printf\",\n    \"rects\",\n    \"relref\",\n    \"rgba\",\n    \"roboto\",\n    \"RTLCSS\",\n    \"ruleset\",\n    \"screenreaders\",\n    \"scrollbars\",\n    \"scrollspy\",\n    \"Segoe\",\n    \"semibold\",\n    \"socio\",\n    \"srcset\",\n    \"stackblitz\",\n    \"stickied\",\n    \"Stylelint\",\n    \"subnav\",\n    \"tabbable\",\n    \"textareas\",\n    \"toggleable\",\n    \"topbar\",\n    \"touchend\",\n    \"twbs\",\n    \"unitless\",\n    \"unstylable\",\n    \"unstyled\",\n    \"Uppercased\",\n    \"urlize\",\n    \"vbtn\",\n    \"viewports\",\n    \"Vite\",\n    \"vstack\",\n    \"walkthroughs\",\n    \"WCAG\",\n    \"zindex\"\n  ],\n  \"language\": \"en-US\",\n  \"files\": [\n    \"**/*.md\"\n  ],\n  \"ignorePaths\": [\n    \".cspell.json\",\n    \"dist/\",\n    \"*.min.*\",\n    \"**/*rtl*\",\n    \"**/tests/**\"\n  ],\n  \"useGitignore\": true\n}\n"
  },
  {
    "path": "src/common/bootstrap/.editorconfig",
    "content": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "src/common/bootstrap/.eslintignore",
    "content": "**/*.min.js\n**/dist/\n**/vendor/\n/_site/\n/js/coverage/\n/js/tests/integration/\n/site/static/sw.js\n/site/layouts/\n"
  },
  {
    "path": "src/common/bootstrap/.eslintrc.json",
    "content": "{\n  \"root\": true,\n  \"extends\": [\n    \"plugin:import/errors\",\n    \"plugin:import/warnings\",\n    \"plugin:unicorn/recommended\",\n    \"xo\",\n    \"xo/browser\"\n  ],\n  \"rules\": {\n    \"arrow-body-style\": \"off\",\n    \"capitalized-comments\": \"off\",\n    \"comma-dangle\": [\n      \"error\",\n      \"never\"\n    ],\n    \"indent\": [\n      \"error\",\n      2,\n      {\n        \"MemberExpression\": \"off\",\n        \"SwitchCase\": 1\n      }\n    ],\n    \"max-params\": [\n      \"warn\",\n      5\n    ],\n    \"multiline-ternary\": [\n      \"error\",\n      \"always-multiline\"\n    ],\n    \"new-cap\": [\n      \"error\",\n      {\n        \"properties\": false\n      }\n    ],\n    \"no-console\": \"error\",\n    \"no-negated-condition\": \"off\",\n    \"object-curly-spacing\": [\n      \"error\",\n      \"always\"\n    ],\n    \"operator-linebreak\": [\n      \"error\",\n      \"after\"\n    ],\n    \"semi\": [\n      \"error\",\n      \"never\"\n    ],\n    \"unicorn/explicit-length-check\": \"off\",\n    \"unicorn/no-array-callback-reference\": \"off\",\n    \"unicorn/no-array-method-this-argument\": \"off\",\n    \"unicorn/no-null\": \"off\",\n    \"unicorn/no-unused-properties\": \"error\",\n    \"unicorn/prefer-array-flat\": \"off\",\n    \"unicorn/prefer-dom-node-dataset\": \"off\",\n    \"unicorn/prefer-module\": \"off\",\n    \"unicorn/prefer-query-selector\": \"off\",\n    \"unicorn/prefer-spread\": \"off\",\n    \"unicorn/prevent-abbreviations\": \"off\"\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/.gitattributes",
    "content": "# Enforce Unix newlines\n* text=auto eol=lf\n\n# Don't diff or textually merge source maps\n*.map binary\n\nbootstrap.css linguist-vendored=false\nbootstrap.js linguist-vendored=false\n"
  },
  {
    "path": "src/common/bootstrap/.github/CODEOWNERS",
    "content": "*.js    @twbs/js-review\n*.css   @twbs/css-review\n*.scss  @twbs/css-review\n"
  },
  {
    "path": "src/common/bootstrap/.github/CONTRIBUTING.md",
    "content": "# Contributing to Bootstrap\n\nLooking to contribute something to Bootstrap? **Here's how you can help.**\n\nPlease take a moment to review this document in order to make the contribution\nprocess easy and effective for everyone involved.\n\nFollowing these guidelines helps to communicate that you respect the time of\nthe developers managing and developing this open source project. In return,\nthey should reciprocate that respect in addressing your issue or assessing\npatches and features.\n\n\n## Using the issue tracker\n\nThe [issue tracker](https://github.com/twbs/bootstrap/issues) is\nthe preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests)\nand [submitting pull requests](#pull-requests), but please respect the following\nrestrictions:\n\n* Please **do not** use the issue tracker for personal support requests. Stack Overflow ([`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5) tag), [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions) or [IRC](/README.md#community) are better places to get help.\n\n* Please **do not** derail or troll issues. Keep the discussion on topic and\n  respect the opinions of others.\n\n* Please **do not** post comments consisting solely of \"+1\" or \":thumbsup:\".\n  Use [GitHub's \"reactions\" feature](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)\n  instead. We reserve the right to delete comments which violate this rule.\n\n* Please **do not** open issues regarding the official themes offered on <https://themes.getbootstrap.com/>.\n  Instead, please email any questions or feedback regarding those themes to `themes AT getbootstrap DOT com`.\n\n\n## Issues and labels\n\nOur bug tracker utilizes several labels to help organize and identify issues. Here's what they represent and how we use them:\n\n- `browser bug` - Issues that are reported to us, but actually are the result of a browser-specific bug. These are diagnosed with reduced test cases and result in an issue opened on that browser's own bug tracker.\n- `confirmed` - Issues that have been confirmed with a reduced test case and identify a bug in Bootstrap.\n- `css` - Issues stemming from our compiled CSS or source Sass files.\n- `docs` - Issues for improving or updating our documentation.\n- `examples` - Issues involving the example templates included in our docs.\n- `feature` - Issues asking for a new feature to be added, or an existing one to be extended or modified. New features require a minor version bump (e.g., `v3.0.0` to `v3.1.0`).\n- `build` - Issues with our build system, which is used to run all our tests, concatenate and compile source files, and more.\n- `help wanted` - Issues we need or would love help from the community to resolve.\n- `js` - Issues stemming from our compiled or source JavaScript files.\n- `meta` - Issues with the project itself or our GitHub repository.\n\nFor a complete look at our labels, see the [project labels page](https://github.com/twbs/bootstrap/labels).\n\n\n## Bug reports\n\nA bug is a _demonstrable problem_ that is caused by the code in the repository.\nGood bug reports are extremely helpful, so thanks!\n\nGuidelines for bug reports:\n\n0. **[Validate your HTML](https://html5.validator.nu/)** to ensure your\n   problem isn't caused by a simple error in your own code.\n\n1. **Use the GitHub issue search** &mdash; check if the issue has already been\n   reported.\n\n2. **Check if the issue has been fixed** &mdash; try to reproduce it using the\n   latest `main` (or `v4-dev` branch if the issue is about v4) in the repository.\n\n3. **Isolate the problem** &mdash; ideally create a [reduced test\n   case](https://css-tricks.com/reduced-test-cases/) and a live example.\n   These [v4 CodePen](https://codepen.io/team/bootstrap/pen/yLabNQL) and [v5 CodePen](https://codepen.io/team/bootstrap/pen/qBamdLj) are helpful templates.\n\n\nA good bug report shouldn't leave others needing to chase you up for more\ninformation. Please try to be as detailed as possible in your report. What is\nyour environment? What steps will reproduce the issue? What browser(s) and OS\nexperience the problem? Do other browsers show the bug differently? What\nwould you expect to be the outcome? All these details will help people to fix\nany potential bugs.\n\nExample:\n\n> Short and descriptive example bug report title\n>\n> A summary of the issue and the browser/OS environment in which it occurs. If\n> suitable, include the steps required to reproduce the bug.\n>\n> 1. This is the first step\n> 2. This is the second step\n> 3. Further steps, etc.\n>\n> `<url>` - a link to the reduced test case\n>\n> Any other information you want to share that is relevant to the issue being\n> reported. This might include the lines of code that you have identified as\n> causing the bug, and potential solutions (and your opinions on their\n> merits).\n\n### Reporting upstream browser bugs\n\nSometimes bugs reported to us are actually caused by bugs in the browser(s) themselves, not bugs in Bootstrap per se.\n\n| Vendor(s)     | Browser(s)                   | Rendering engine | Bug reporting website(s)                               | Notes                                                    |\n| ------------- | ---------------------------- | ---------------- | ------------------------------------------------------ | -------------------------------------------------------- |\n| Mozilla       | Firefox                      | Gecko            | https://bugzilla.mozilla.org/enter_bug.cgi             | \"Core\" is normally the right product option to choose.   |\n| Apple         | Safari                       | WebKit           | https://bugs.webkit.org/enter_bug.cgi?product=WebKit   | In Apple's bug reporter, choose \"Safari\" as the product. |\n| Google, Opera | Chrome, Chromium, Opera v15+ | Blink            | https://bugs.chromium.org/p/chromium/issues/list       | Click the \"New issue\" button.                            |\n| Microsoft     | Edge                         | Blink            | https://developer.microsoft.com/en-us/microsoft-edge/  | Go to \"Help > Send Feedback\" from the browser            |\n\n\n## Feature requests\n\nFeature requests are welcome. But take a moment to find out whether your idea\nfits with the scope and aims of the project. It's up to *you* to make a strong\ncase to convince the project's developers of the merits of this feature. Please\nprovide as much detail and context as possible.\n\n\n## Pull requests\n\nGood pull requests—patches, improvements, new features—are a fantastic\nhelp. They should remain focused in scope and avoid containing unrelated\ncommits.\n\n**Please ask first** before embarking on any **significant** pull request (e.g.\nimplementing features, refactoring code, porting to a different language),\notherwise you risk spending a lot of time working on something that the\nproject's developers might not want to merge into the project. For trivial\nthings, or things that don't require a lot of your time, you can go ahead and\nmake a PR.\n\nPlease adhere to the [coding guidelines](#code-guidelines) used throughout the\nproject (indentation, accurate comments, etc.) and any other requirements\n(such as test coverage).\n\n**Do not edit `bootstrap.css` or `bootstrap.js`, and do not commit\nany dist files (`dist/` or `js/dist`).** Those files are automatically generated by our build tools. You should\nedit the source files in [`/bootstrap/scss/`](https://github.com/twbs/bootstrap/tree/main/scss)\nand/or [`/bootstrap/js/src/`](https://github.com/twbs/bootstrap/tree/main/js/src) instead.\n\nSimilarly, when contributing to Bootstrap's documentation, you should edit the\ndocumentation source files in\n[the `/bootstrap/site/content/docs/` directory of the `main` branch](https://github.com/twbs/bootstrap/tree/main/site/content/docs).\n**Do not edit the `gh-pages` branch.** That branch is generated from the\ndocumentation source files and is managed separately by the Bootstrap Core Team.\n\nAdhering to the following process is the best way to get your work\nincluded in the project:\n\n1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork,\n   and configure the remotes:\n\n   ```bash\n   # Clone your fork of the repo into the current directory\n   git clone https://github.com/<your-username>/bootstrap.git\n   # Navigate to the newly cloned directory\n   cd bootstrap\n   # Assign the original repo to a remote called \"upstream\"\n   git remote add upstream https://github.com/twbs/bootstrap.git\n   ```\n\n2. If you cloned a while ago, get the latest changes from upstream:\n\n   ```bash\n   git checkout main\n   git pull upstream main\n   ```\n\n3. Create a new topic branch (off the main project development branch) to\n   contain your feature, change, or fix:\n\n   ```bash\n   git checkout -b <topic-branch-name>\n   ```\n\n4. Commit your changes in logical chunks. Please adhere to these [git commit\n   message guidelines](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)\n   or your code is unlikely be merged into the main project. Use Git's\n   [interactive rebase](https://help.github.com/articles/about-git-rebase/)\n   feature to tidy up your commits before making them public.\n\n5. Locally merge (or rebase) the upstream development branch into your topic branch:\n\n   ```bash\n   git pull [--rebase] upstream main\n   ```\n\n6. Push your topic branch up to your fork:\n\n   ```bash\n   git push origin <topic-branch-name>\n   ```\n\n7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/)\n    with a clear title and description against the `main` branch.\n\n**IMPORTANT**: By submitting a patch, you agree to allow the project owners to\nlicense your work under the terms of the [MIT License](../LICENSE) (if it\nincludes code changes) and under the terms of the\n[Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/)\n(if it includes documentation changes).\n\n\n## Code guidelines\n\n### HTML\n\n[Adhere to the Code Guide.](https://codeguide.co/#html)\n\n- Use tags and elements appropriate for an HTML5 doctype (e.g., self-closing tags).\n- Use CDNs and HTTPS for third-party JS when possible. We don't use protocol-relative URLs in this case because they break when viewing the page locally via `file://`.\n- Use [WAI-ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes in documentation examples to promote accessibility.\n\n### CSS\n\n[Adhere to the Code Guide.](https://codeguide.co/#css)\n\n- When feasible, default color palettes should comply with [WCAG color contrast guidelines](https://www.w3.org/TR/WCAG20/#visual-audio-contrast).\n- Except in rare cases, don't remove default `:focus` styles (via e.g. `outline: none;`) without providing alternative styles. See [this A11Y Project post](https://www.a11yproject.com/posts/2013-01-25-never-remove-css-outlines/) for more details.\n\n### JS\n\n- No semicolons (in client-side JS)\n- 2 spaces (no tabs)\n- strict mode\n- \"Attractive\"\n\n### Checking coding style\n\nRun `npm run test` before committing to ensure your changes follow our coding standards.\n\n\n## License\n\nBy contributing your code, you agree to license your contribution under the [MIT License](../LICENSE).\nBy contributing to the documentation, you agree to license your contribution under the [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/).\n\nPrior to v3.1.0, Bootstrap's code was released under the Apache License v2.0.\n"
  },
  {
    "path": "src/common/bootstrap/.github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Report a bug\ndescription: Tell us about a bug or issue you may have identified in Bootstrap.\ntitle: \"Provide a general summary of the issue\"\nlabels: [bug]\nassignees: \"-\"\nbody:\n  - type: checkboxes\n    attributes:\n      label: Prerequisites\n      description: Take a couple minutes to help our maintainers work faster.\n      options:\n        - label: I have [searched](https://github.com/twbs/bootstrap/issues?utf8=%E2%9C%93&q=is%3Aissue) for duplicate or closed issues\n          required: true\n        - label: I have [validated](https://html5.validator.nu/) any HTML to avoid common problems\n          required: true\n        - label: I have read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md)\n          required: true\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: Describe the issue\n      description: Provide a summary of the issue and what you expected to happen, including specific steps to reproduce.\n    validations:\n      required: true\n  - type: textarea\n    id: reduced-test-case\n    attributes:\n      label: Reduced test cases\n      description: Include links [reduced test case](https://css-tricks.com/reduced-test-cases/) links or suggested fixes using CodePen ([v4 template](https://codepen.io/team/bootstrap/pen/yLabNQL) or [v5 template](https://codepen.io/team/bootstrap/pen/qBamdLj)).\n    validations:\n      required: true\n  - type: dropdown\n    id: os\n    attributes:\n      label: What operating system(s) are you seeing the problem on?\n      multiple: true\n      options:\n        - Windows\n        - macOS\n        - Android\n        - iOS\n        - Linux\n    validations:\n      required: true\n  - type: dropdown\n    id: browser\n    attributes:\n      label: What browser(s) are you seeing the problem on?\n      multiple: true\n      options:\n        - Chrome\n        - Safari\n        - Firefox\n        - Microsoft Edge\n        - Opera\n  - type: input\n    id: version\n    attributes:\n      label: What version of Bootstrap are you using?\n      placeholder: \"e.g., v5.1.0 or v4.5.2\"\n    validations:\n      required: true\n"
  },
  {
    "path": "src/common/bootstrap/.github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: Ask the community\n    url: https://github.com/twbs/bootstrap/discussions/new\n    about: Ask and discuss questions with other Bootstrap community members.\n"
  },
  {
    "path": "src/common/bootstrap/.github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest new or updated features to include in Bootstrap.\ntitle: \"Suggest a new feature\"\nlabels: [feature]\nassignees: []\nbody:\n  - type: checkboxes\n    attributes:\n      label: Prerequisites\n      description: Take a couple minutes to help our maintainers work faster.\n      options:\n        - label: I have [searched](https://github.com/twbs/bootstrap/issues?utf8=%E2%9C%93&q=is%3Aissue) for duplicate or closed feature requests\n          required: true\n        - label: I have read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md)\n          required: true\n  - type: textarea\n    id: proposal\n    attributes:\n      label: Proposal\n      description: Provide detailed information for what we should add, including relevant links to prior art, screenshots, or live demos whenever possible.\n    validations:\n      required: true\n  - type: textarea\n    id: motivation\n    attributes:\n      label: Motivation and context\n      description: Tell us why this change is needed or helpful, and what problems it may help solve.\n    validations:\n      required: true\n"
  },
  {
    "path": "src/common/bootstrap/.github/PULL_REQUEST_TEMPLATE.md",
    "content": "### Description\n\n<!-- Describe your changes in detail -->\n\n### Motivation & Context\n\n<!-- Why is this change required? What problem does it solve? -->\n\n### Type of changes\n\n<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Refactoring (non-breaking change)\n- [ ] Breaking change (fix or feature that would change existing functionality)\n\n### Checklist\n\n<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->\n<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->\n\n- [ ] I have read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md)\n- [ ] My code follows the code style of the project _(using `npm run lint`)_\n- [ ] My change introduces changes to the documentation\n- [ ] I have updated the documentation accordingly\n- [ ] I have added tests to cover my changes\n- [ ] All new and existing tests passed\n\n#### Live previews\n\n<!-- Please add direct links where your modifications can be seen in the documentation -->\n\n* https://deploy-preview-{your pr number}--twbs-bootstrap.netlify.app/\n\n### Related issues\n\n<!-- Please link any related issues here. -->\n"
  },
  {
    "path": "src/common/bootstrap/.github/SUPPORT.md",
    "content": "### Bug reports\n\nSee the [contributing guidelines](CONTRIBUTING.md) for sharing bug reports.\n\n### How-to\n\nFor general troubleshooting or help getting started:\n\n- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions).\n- Chat with fellow Bootstrappers in IRC. On the `irc.libera.chat` server, in the `#bootstrap` channel.\n- Ask and explore Stack Overflow with the [`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5) tag.\n"
  },
  {
    "path": "src/common/bootstrap/.github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: \"/\"\n    schedule:\n      interval: weekly\n      day: tuesday\n      time: \"12:00\"\n      timezone: Europe/Athens\n    open-pull-requests-limit: 10\n    reviewers:\n      - XhmikosR\n    labels:\n      - dependencies\n      - v5\n    versioning-strategy: increase\n    rebase-strategy: disabled\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: weekly\n      day: tuesday\n      time: \"12:00\"\n      timezone: Europe/Athens\n"
  },
  {
    "path": "src/common/bootstrap/.github/release-drafter.yml",
    "content": "name-template: 'v$NEXT_MAJOR_VERSION'\ntag-template: 'v$NEXT_MAJOR_VERSION'\nprerelease: true\nexclude-labels:\n  - 'skip-changelog'\ncategories:\n  - title: '❗ Breaking Changes'\n    labels:\n      - 'breaking-change'\n  - title: '🚀 Highlights'\n    labels:\n      - 'release-highlight'\n  - title: '🚀 Features'\n    labels:\n      - 'new-feature'\n      - 'feature'\n      - 'enhancement'\n  - title: '🐛 Bug fixes'\n    labels:\n      - 'fix'\n      - 'bugfix'\n      - 'bug'\n  - title: '⚡ Performance improvements'\n    labels:\n      - 'performance'\n  - title: '🎨 CSS'\n    labels:\n      - 'css'\n  - title: '☕️ JavaScript'\n    labels:\n      - 'js'\n  - title: '📖 Docs'\n    labels:\n      - 'docs'\n  - title: '🛠 Examples'\n    labels:\n      - 'examples'\n  - title: '🌎 Accessibility'\n    labels:\n      - 'accessibility'\n  - title: '🔧 Utility API'\n    labels:\n      - 'utility API'\n      - 'utilities'\n  - title: '🏭 Tests'\n    labels:\n      - 'tests'\n  - title: '🧰 Misc'\n    labels:\n      - 'build'\n      - 'meta'\n      - 'chore'\n      - 'CI'\n  - title: '📦 Dependencies'\n    labels:\n      - 'dependencies'\nchange-template: '- #$NUMBER: $TITLE'\ntemplate: |\n  ## Changes\n  $CHANGES\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/browserstack.yml",
    "content": "name: BrowserStack\n\non:\n  push:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  browserstack:\n    runs-on: ubuntu-latest\n    if: github.repository == 'twbs/bootstrap' && (!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]'))\n    timeout-minutes: 30\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: \"${{ env.NODE }}\"\n          cache: npm\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Run dist\n        run: npm run dist\n\n      - name: Run BrowserStack tests\n        run: npm run js-test-cloud\n        env:\n          BROWSER_STACK_ACCESS_KEY: \"${{ secrets.BROWSER_STACK_ACCESS_KEY }}\"\n          BROWSER_STACK_USERNAME: \"${{ secrets.BROWSER_STACK_USERNAME }}\"\n          GITHUB_SHA: \"${{ github.sha }}\"\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/bundlewatch.yml",
    "content": "name: Bundlewatch\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  bundlewatch:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: \"${{ env.NODE }}\"\n          cache: npm\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Run dist\n        run: npm run dist\n\n      - name: Run bundlewatch\n        run: npm run bundlewatch\n        env:\n          BUNDLEWATCH_GITHUB_TOKEN: \"${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}\"\n          CI_BRANCH_BASE: main\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/calibreapp-image-actions.yml",
    "content": "name: Compress Images\n\non:\n  pull_request:\n    paths:\n      - '**.jpg'\n      - '**.jpeg'\n      - '**.png'\n      - '**.webp'\n\njobs:\n  build:\n    # Only run on Pull Requests within the same repository, and not from forks.\n    if: github.event.pull_request.head.repo.full_name == github.repository\n    name: calibreapp/image-actions\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v3\n\n      - name: Compress Images\n        uses: calibreapp/image-actions@1.1.0\n        with:\n          githubToken: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches:\n      - main\n      - v4-dev\n      - \"!dependabot/**\"\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches:\n      - main\n      - v4-dev\n      - \"!dependabot/**\"\n  schedule:\n    - cron: \"0 2 * * 5\"\n  workflow_dispatch:\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v2\n        with:\n          languages: \"javascript\"\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/cspell.yml",
    "content": "name: cspell\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  cspell:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Run cspell\n        uses: streetsidesoftware/cspell-action@v2\n        with:\n          config: \".cspell.json\"\n          files: \"**/*.md\"\n          inline: error\n          incremental_files_only: false\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/css.yml",
    "content": "name: CSS\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  css:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: \"${{ env.NODE }}\"\n          cache: npm\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Build CSS\n        run: npm run css\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/docs.yml",
    "content": "name: Docs\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  docs:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: \"${{ env.NODE }}\"\n          cache: npm\n\n      - run: java -version\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Build docs\n        run: npm run docs-build\n\n      - name: Validate HTML\n        run: npm run docs-vnu\n\n      - name: Run linkinator\n        uses: JustinBeckwith/linkinator-action@v1\n        with:\n          paths: _site\n          recurse: true\n          verbosity: error\n          skip: \"^(?!http://localhost)\"\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/issue-close-require.yml",
    "content": "name: Close Issue Awaiting Reply\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  issue-close-require:\n    runs-on: ubuntu-latest\n    if: github.repository == 'twbs/bootstrap'\n    steps:\n      - name: awaiting reply\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"close-issues\"\n          labels: \"awaiting-reply\"\n          inactive-day: 14\n          body: |\n            As the issue was labeled with `awaiting-reply`, but there has been no response in 14 days, this issue will be closed. If you have any questions, you can comment/reply.\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/issue-labeled.yml",
    "content": "name: Issue Labeled\n\non:\n  issues:\n    types: [labeled]\n\njobs:\n  issue-labeled:\n    if: github.repository == 'twbs/bootstrap'\n    runs-on: ubuntu-latest\n    steps:\n      - name: awaiting reply\n        if: github.event.label.name == 'needs-example'\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"create-comment\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          body: |\n            Hello @${{ github.event.issue.user.login }}. Bug reports must include a **live demo** of the issue. Per our [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md), please create a reduced test case on [CodePen](https://codepen.io/) or [StackBlitz](https://stackblitz.com/) and report back with your link, Bootstrap version, and specific browser and Operating System details.\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/js.yml",
    "content": "name: JS Tests\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  run:\n    name: JS Tests\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ env.NODE }}\n          cache: npm\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Run dist\n        run: npm run js\n\n      - name: Run JS tests\n        run: npm run js-test\n\n      - name: Run Coveralls\n        uses: coverallsapp/github-action@1.1.3\n        with:\n          github-token: \"${{ secrets.GITHUB_TOKEN }}\"\n          path-to-lcov: \"./js/coverage/lcov.info\"\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: \"${{ env.NODE }}\"\n          cache: npm\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Lint\n        run: npm run lint\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/node-sass.yml",
    "content": "name: CSS (node-sass)\n\non:\n  push:\n    branches-ignore:\n      - \"dependabot/**\"\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 16\n\njobs:\n  css:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: \"${{ env.NODE }}\"\n\n      - name: Build CSS with node-sass\n        run: |\n          npx --package node-sass@latest node-sass --version\n          npx --package node-sass@latest node-sass --output-style expanded --source-map true --source-map-contents true --precision 6 scss/ -o dist-sass/css/\n          ls -Al dist-sass/css\n"
  },
  {
    "path": "src/common/bootstrap/.github/workflows/release-notes.yml",
    "content": "name: Release notes\n\non:\n  push:\n    branches:\n      - main\n  workflow_dispatch:\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    if: github.repository == 'twbs/bootstrap'\n    steps:\n      - uses: release-drafter/release-drafter@v5\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": "src/common/bootstrap/.gitignore",
    "content": "# Ignore docs files\n/_site/\n# Hugo files\n/resources/\n/.hugo_build.lock\n\n# Numerous always-ignore extensions\n*.diff\n*.err\n*.log\n*.orig\n*.rej\n*.swo\n*.swp\n*.vi\n*.zip\n*~\n\n# OS or Editor folders\n._*\n.cache\n.DS_Store\n.idea\n.project\n.settings\n.tmproj\n*.esproj\n*.sublime-project\n*.sublime-workspace\nnbproject\nThumbs.db\n/.vscode/\n# Local Netlify folder\n.netlify\n\n# Komodo\n.komodotools\n*.komodoproject\n\n# Folders to ignore\n/js/coverage/\n/node_modules/\n"
  },
  {
    "path": "src/common/bootstrap/.stylelintignore",
    "content": "**/*.min.css\n**/dist/\n**/vendor/\n/_site/\n/js/coverage/\n"
  },
  {
    "path": "src/common/bootstrap/.stylelintrc",
    "content": "{\n  \"extends\": [\n    \"stylelint-config-twbs-bootstrap\"\n  ],\n  \"rules\": {\n    \"declaration-property-value-disallowed-list\": {\n      \"border\": \"none\",\n      \"outline\": \"none\"\n    },\n    \"function-disallowed-list\": [\n      \"calc\",\n      \"lighten\",\n      \"darken\"\n    ],\n    \"property-disallowed-list\": [\n      \"border-radius\",\n      \"border-top-left-radius\",\n      \"border-top-right-radius\",\n      \"border-bottom-right-radius\",\n      \"border-bottom-left-radius\",\n      \"transition\"\n    ],\n    \"scss/dollar-variable-default\": [\n      true,\n      {\n        \"ignore\": \"local\"\n      }\n    ],\n    \"scss/selector-no-union-class-name\": true\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nmdo@getbootstrap.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "src/common/bootstrap/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2011-2022 Twitter, Inc.\nCopyright (c) 2011-2022 The Bootstrap Authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "src/common/bootstrap/README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://getbootstrap.com/\">\n    <img src=\"https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo-shadow.png\" alt=\"Bootstrap logo\" width=\"200\" height=\"165\">\n  </a>\n</p>\n\n<h3 align=\"center\">Bootstrap</h3>\n\n<p align=\"center\">\n  Sleek, intuitive, and powerful front-end framework for faster and easier web development.\n  <br>\n  <a href=\"https://getbootstrap.com/docs/5.2/\"><strong>Explore Bootstrap docs »</strong></a>\n  <br>\n  <br>\n  <a href=\"https://github.com/twbs/bootstrap/issues/new?assignees=-&labels=bug&template=bug_report.yml\">Report bug</a>\n  ·\n  <a href=\"https://github.com/twbs/bootstrap/issues/new?assignees=&labels=feature&template=feature_request.yml\">Request feature</a>\n  ·\n  <a href=\"https://themes.getbootstrap.com/\">Themes</a>\n  ·\n  <a href=\"https://blog.getbootstrap.com/\">Blog</a>\n</p>\n\n\n## Bootstrap 5\n\nOur default branch is for development of our Bootstrap 5 release. Head to the [`v4-dev` branch](https://github.com/twbs/bootstrap/tree/v4-dev) to view the readme, documentation, and source code for Bootstrap 4.\n\n\n## Table of contents\n\n- [Quick start](#quick-start)\n- [Status](#status)\n- [What's included](#whats-included)\n- [Bugs and feature requests](#bugs-and-feature-requests)\n- [Documentation](#documentation)\n- [Contributing](#contributing)\n- [Community](#community)\n- [Versioning](#versioning)\n- [Creators](#creators)\n- [Thanks](#thanks)\n- [Copyright and license](#copyright-and-license)\n\n\n## Quick start\n\nSeveral quick start options are available:\n\n- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.2.3.zip)\n- Clone the repo: `git clone https://github.com/twbs/bootstrap.git`\n- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.2.3`\n- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.2.3`\n- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.2.3`\n- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass`\n\nRead the [Getting started page](https://getbootstrap.com/docs/5.2/getting-started/introduction/) for information on the framework contents, templates, examples, and more.\n\n\n## Status\n\n[![Build Status](https://img.shields.io/github/workflow/status/twbs/bootstrap/JS%20Tests/main?label=JS%20Tests&logo=github)](https://github.com/twbs/bootstrap/actions?query=workflow%3AJS+Tests+branch%3Amain)\n[![npm version](https://img.shields.io/npm/v/bootstrap)](https://www.npmjs.com/package/bootstrap)\n[![Gem version](https://img.shields.io/gem/v/bootstrap)](https://rubygems.org/gems/bootstrap)\n[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue)](https://atmospherejs.com/twbs/bootstrap)\n[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap)](https://packagist.org/packages/twbs/bootstrap)\n[![NuGet](https://img.shields.io/nuget/vpre/bootstrap)](https://www.nuget.org/packages/bootstrap/absoluteLatest)\n[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main)](https://coveralls.io/github/twbs/bootstrap?branch=main)\n[![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=gzip&label=CSS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)\n[![CSS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=brotli&label=CSS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)\n[![JS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=gzip&label=JS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js)\n[![JS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=brotli&label=JS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js)\n[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)\n[![Backers on Open Collective](https://img.shields.io/opencollective/backers/bootstrap)](#backers)\n[![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/bootstrap)](#sponsors)\n\n\n## What's included\n\nWithin the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations.\n\n<details>\n  <summary>Download contents</summary>\n\n  ```text\n  bootstrap/\n  ├── css/\n  │   ├── bootstrap-grid.css\n  │   ├── bootstrap-grid.css.map\n  │   ├── bootstrap-grid.min.css\n  │   ├── bootstrap-grid.min.css.map\n  │   ├── bootstrap-grid.rtl.css\n  │   ├── bootstrap-grid.rtl.css.map\n  │   ├── bootstrap-grid.rtl.min.css\n  │   ├── bootstrap-grid.rtl.min.css.map\n  │   ├── bootstrap-reboot.css\n  │   ├── bootstrap-reboot.css.map\n  │   ├── bootstrap-reboot.min.css\n  │   ├── bootstrap-reboot.min.css.map\n  │   ├── bootstrap-reboot.rtl.css\n  │   ├── bootstrap-reboot.rtl.css.map\n  │   ├── bootstrap-reboot.rtl.min.css\n  │   ├── bootstrap-reboot.rtl.min.css.map\n  │   ├── bootstrap-utilities.css\n  │   ├── bootstrap-utilities.css.map\n  │   ├── bootstrap-utilities.min.css\n  │   ├── bootstrap-utilities.min.css.map\n  │   ├── bootstrap-utilities.rtl.css\n  │   ├── bootstrap-utilities.rtl.css.map\n  │   ├── bootstrap-utilities.rtl.min.css\n  │   ├── bootstrap-utilities.rtl.min.css.map\n  │   ├── bootstrap.css\n  │   ├── bootstrap.css.map\n  │   ├── bootstrap.min.css\n  │   ├── bootstrap.min.css.map\n  │   ├── bootstrap.rtl.css\n  │   ├── bootstrap.rtl.css.map\n  │   ├── bootstrap.rtl.min.css\n  │   └── bootstrap.rtl.min.css.map\n  └── js/\n      ├── bootstrap.bundle.js\n      ├── bootstrap.bundle.js.map\n      ├── bootstrap.bundle.min.js\n      ├── bootstrap.bundle.min.js.map\n      ├── bootstrap.esm.js\n      ├── bootstrap.esm.js.map\n      ├── bootstrap.esm.min.js\n      ├── bootstrap.esm.min.js.map\n      ├── bootstrap.js\n      ├── bootstrap.js.map\n      ├── bootstrap.min.js\n      └── bootstrap.min.js.map\n  ```\n</details>\n\nWe provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/).\n\n\n## Bugs and feature requests\n\nHave a bug or a feature request? Please first read the [issue guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/twbs/bootstrap/issues/new/choose).\n\n\n## Documentation\n\nBootstrap's documentation, included in this repo in the root directory, is built with [Hugo](https://gohugo.io/) and publicly hosted on GitHub Pages at <https://getbootstrap.com/>. The docs may also be run locally.\n\nDocumentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/). Working on our search? Be sure to set `debug: true` in `site/assets/js/search.js`.\n\n### Running documentation locally\n\n1. Run `npm install` to install the Node.js dependencies, including Hugo (the site builder).\n2. Run `npm run test` (or a specific npm script) to rebuild distributed CSS and JavaScript files, as well as our docs assets.\n3. From the root `/bootstrap` directory, run `npm run docs-serve` in the command line.\n4. Open `http://localhost:9001/` in your browser, and voilà.\n\nLearn more about using Hugo by reading its [documentation](https://gohugo.io/documentation/).\n\n### Documentation for previous releases\n\nYou can find all our previous releases docs on <https://getbootstrap.com/docs/versions/>.\n\n[Previous releases](https://github.com/twbs/bootstrap/releases) and their documentation are also available for download.\n\n\n## Contributing\n\nPlease read through our [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.\n\nMoreover, if your pull request contains JavaScript patches or features, you must include [relevant unit tests](https://github.com/twbs/bootstrap/tree/main/js/tests). All HTML and CSS should conform to the [Code Guide](https://github.com/mdo/code-guide), maintained by [Mark Otto](https://github.com/mdo).\n\nEditor preferences are available in the [editor config](https://github.com/twbs/bootstrap/blob/main/.editorconfig) for easy use in common text editors. Read more and download plugins at <https://editorconfig.org/>.\n\n\n## Community\n\nGet updates on Bootstrap's development and chat with the project maintainers and community members.\n\n- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap).\n- Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/).\n- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions).\n- Chat with fellow Bootstrappers in IRC. On the `irc.libera.chat` server, in the `#bootstrap` channel.\n- Implementation help may be found at Stack Overflow (tagged [`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5)).\n- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability.\n\n\n## Versioning\n\nFor transparency into our release cycle and in striving to maintain backward compatibility, Bootstrap is maintained under [the Semantic Versioning guidelines](https://semver.org/). Sometimes we screw up, but we adhere to those rules whenever possible.\n\nSee [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap. Release announcement posts on [the official Bootstrap blog](https://blog.getbootstrap.com/) contain summaries of the most noteworthy changes made in each release.\n\n\n## Creators\n\n**Mark Otto**\n\n- <https://twitter.com/mdo>\n- <https://github.com/mdo>\n\n**Jacob Thornton**\n\n- <https://twitter.com/fat>\n- <https://github.com/fat>\n\n\n## Thanks\n\n<a href=\"https://www.browserstack.com/\">\n  <img src=\"https://live.browserstack.com/images/opensource/browserstack-logo.svg\" alt=\"BrowserStack\" width=\"192\" height=\"42\">\n</a>\n\nThanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers!\n\n<a href=\"https://www.netlify.com/\">\n  <img src=\"https://www.netlify.com/v3/img/components/full-logo-light.svg\" alt=\"Netlify\" width=\"147\" height=\"40\">\n</a>\n\nThanks to [Netlify](https://www.netlify.com/) for providing us with Deploy Previews!\n\n\n## Sponsors\n\nSupport this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/bootstrap#sponsor)]\n\n[![OC sponsor 0](https://opencollective.com/bootstrap/sponsor/0/avatar.svg)](https://opencollective.com/bootstrap/sponsor/0/website)\n[![OC sponsor 1](https://opencollective.com/bootstrap/sponsor/1/avatar.svg)](https://opencollective.com/bootstrap/sponsor/1/website)\n[![OC sponsor 2](https://opencollective.com/bootstrap/sponsor/2/avatar.svg)](https://opencollective.com/bootstrap/sponsor/2/website)\n[![OC sponsor 3](https://opencollective.com/bootstrap/sponsor/3/avatar.svg)](https://opencollective.com/bootstrap/sponsor/3/website)\n[![OC sponsor 4](https://opencollective.com/bootstrap/sponsor/4/avatar.svg)](https://opencollective.com/bootstrap/sponsor/4/website)\n[![OC sponsor 5](https://opencollective.com/bootstrap/sponsor/5/avatar.svg)](https://opencollective.com/bootstrap/sponsor/5/website)\n[![OC sponsor 6](https://opencollective.com/bootstrap/sponsor/6/avatar.svg)](https://opencollective.com/bootstrap/sponsor/6/website)\n[![OC sponsor 7](https://opencollective.com/bootstrap/sponsor/7/avatar.svg)](https://opencollective.com/bootstrap/sponsor/7/website)\n[![OC sponsor 8](https://opencollective.com/bootstrap/sponsor/8/avatar.svg)](https://opencollective.com/bootstrap/sponsor/8/website)\n[![OC sponsor 9](https://opencollective.com/bootstrap/sponsor/9/avatar.svg)](https://opencollective.com/bootstrap/sponsor/9/website)\n\n\n## Backers\n\nThank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/bootstrap#backer)]\n\n[![Backers](https://opencollective.com/bootstrap/backers.svg?width=890)](https://opencollective.com/bootstrap#backers)\n\n\n## Copyright and license\n\nCode and documentation copyright 2011–2022 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).\n"
  },
  {
    "path": "src/common/bootstrap/SECURITY.md",
    "content": "# Reporting Security Issues\n\nThe Bootstrap team and community take security issues in Bootstrap seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.\n\nTo report a security issue, email [security@getbootstrap.com](mailto:security@getbootstrap.com) and include the word \"SECURITY\" in the subject line.\n\nWe'll endeavor to respond quickly, and will keep you updated throughout the process.\n"
  },
  {
    "path": "src/common/bootstrap/build/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": false,\n    \"node\": true\n  },\n  \"parserOptions\": {\n    \"sourceType\": \"script\"\n  },\n  \"extends\": \"../.eslintrc.json\",\n  \"rules\": {\n    \"no-console\": \"off\",\n    \"strict\": \"error\",\n    \"unicorn/prefer-top-level-await\": \"off\"\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/build/banner.js",
    "content": "'use strict'\n\nconst pkg = require('../package.json')\nconst year = new Date().getFullYear()\n\nfunction getBanner(pluginFilename) {\n  return `/*!\n  * Bootstrap${pluginFilename ? ` ${pluginFilename}` : ''} v${pkg.version} (${pkg.homepage})\n  * Copyright 2011-${year} ${pkg.author}\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */`\n}\n\nmodule.exports = getBanner\n"
  },
  {
    "path": "src/common/bootstrap/build/build-plugins.js",
    "content": "#!/usr/bin/env node\n\n/*!\n * Script to build our plugins to use them separately.\n * Copyright 2020-2022 The Bootstrap Authors\n * Copyright 2020-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n'use strict'\n\nconst path = require('node:path')\nconst rollup = require('rollup')\nconst globby = require('globby')\nconst { babel } = require('@rollup/plugin-babel')\nconst banner = require('./banner.js')\n\nconst sourcePath = path.resolve(__dirname, '../js/src/').replace(/\\\\/g, '/')\nconst jsFiles = globby.sync(sourcePath + '/**/*.js')\n\n// Array which holds the resolved plugins\nconst resolvedPlugins = []\n\n// Trims the \"js\" extension and uppercases => first letter, hyphens, backslashes & slashes\nconst filenameToEntity = filename => filename.replace('.js', '')\n  .replace(/(?:^|-|\\/|\\\\)[a-z]/g, str => str.slice(-1).toUpperCase())\n\nfor (const file of jsFiles) {\n  resolvedPlugins.push({\n    src: file.replace('.js', ''),\n    dist: file.replace('src', 'dist'),\n    fileName: path.basename(file),\n    className: filenameToEntity(path.basename(file))\n    // safeClassName: filenameToEntity(path.relative(sourcePath, file))\n  })\n}\n\nconst build = async plugin => {\n  const globals = {}\n\n  const bundle = await rollup.rollup({\n    input: plugin.src,\n    plugins: [\n      babel({\n        // Only transpile our source code\n        exclude: 'node_modules/**',\n        // Include the helpers in each file, at most one copy of each\n        babelHelpers: 'bundled'\n      })\n    ],\n    external(source) {\n      // Pattern to identify local files\n      const pattern = /^(\\.{1,2})\\//\n\n      // It's not a local file, e.g a Node.js package\n      if (!pattern.test(source)) {\n        globals[source] = source\n        return true\n      }\n\n      const usedPlugin = resolvedPlugins.find(plugin => {\n        return plugin.src.includes(source.replace(pattern, ''))\n      })\n\n      if (!usedPlugin) {\n        throw new Error(`Source ${source} is not mapped!`)\n      }\n\n      // We can change `Index` with `UtilIndex` etc if we use\n      // `safeClassName` instead of `className` everywhere\n      globals[path.normalize(usedPlugin.src)] = usedPlugin.className\n      return true\n    }\n  })\n\n  await bundle.write({\n    banner: banner(plugin.fileName),\n    format: 'umd',\n    name: plugin.className,\n    sourcemap: true,\n    globals,\n    generatedCode: 'es2015',\n    file: plugin.dist\n  })\n\n  console.log(`Built ${plugin.className}`)\n}\n\n(async () => {\n  try {\n    const basename = path.basename(__filename)\n    const timeLabel = `[${basename}] finished`\n\n    console.log('Building individual plugins...')\n    console.time(timeLabel)\n\n    await Promise.all(Object.values(resolvedPlugins).map(plugin => build(plugin)))\n\n    console.timeEnd(timeLabel)\n  } catch (error) {\n    console.error(error)\n    process.exit(1)\n  }\n})()\n"
  },
  {
    "path": "src/common/bootstrap/build/change-version.js",
    "content": "#!/usr/bin/env node\n\n/*!\n * Script to update version number references in the project.\n * Copyright 2017-2022 The Bootstrap Authors\n * Copyright 2017-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n'use strict'\n\nconst fs = require('node:fs').promises\nconst path = require('node:path')\nconst globby = require('globby')\n\nconst VERBOSE = process.argv.includes('--verbose')\nconst DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run')\n\n// These are the filetypes we only care about replacing the version\nconst GLOB = [\n  '**/*.{css,html,js,json,md,scss,txt,yml}'\n]\nconst GLOBBY_OPTIONS = {\n  cwd: path.join(__dirname, '..'),\n  gitignore: true\n}\n\n// Blame TC39... https://github.com/benjamingr/RegExp.escape/issues/37\nfunction regExpQuote(string) {\n  return string.replace(/[$()*+-.?[\\\\\\]^{|}]/g, '\\\\$&')\n}\n\nfunction regExpQuoteReplacement(string) {\n  return string.replace(/\\$/g, '$$')\n}\n\nasync function replaceRecursively(file, oldVersion, newVersion) {\n  const originalString = await fs.readFile(file, 'utf8')\n  const newString = originalString.replace(\n    new RegExp(regExpQuote(oldVersion), 'g'), regExpQuoteReplacement(newVersion)\n  )\n\n  // No need to move any further if the strings are identical\n  if (originalString === newString) {\n    return\n  }\n\n  if (VERBOSE) {\n    console.log(`FILE: ${file}`)\n  }\n\n  if (DRY_RUN) {\n    return\n  }\n\n  await fs.writeFile(file, newString, 'utf8')\n}\n\nasync function main(args) {\n  let [oldVersion, newVersion] = args\n\n  if (!oldVersion || !newVersion) {\n    console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]')\n    console.error('Got arguments:', args)\n    process.exit(1)\n  }\n\n  // Strip any leading `v` from arguments because otherwise we will end up with duplicate `v`s\n  [oldVersion, newVersion] = [oldVersion, newVersion].map(arg => arg.startsWith('v') ? arg.slice(1) : arg)\n\n  try {\n    const files = await globby(GLOB, GLOBBY_OPTIONS)\n\n    await Promise.all(files.map(file => replaceRecursively(file, oldVersion, newVersion)))\n  } catch (error) {\n    console.error(error)\n    process.exit(1)\n  }\n}\n\nmain(process.argv.slice(2))\n"
  },
  {
    "path": "src/common/bootstrap/build/generate-sri.js",
    "content": "#!/usr/bin/env node\n\n/*!\n * Script to generate SRI hashes for use in our docs.\n * Remember to use the same vendor files as the CDN ones,\n * otherwise the hashes won't match!\n *\n * Copyright 2017-2022 The Bootstrap Authors\n * Copyright 2017-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n'use strict'\n\nconst crypto = require('node:crypto')\nconst fs = require('node:fs')\nconst path = require('node:path')\nconst sh = require('shelljs')\n\nsh.config.fatal = true\n\nconst configFile = path.join(__dirname, '../config.yml')\n\n// Array of objects which holds the files to generate SRI hashes for.\n// `file` is the path from the root folder\n// `configPropertyName` is the config.yml variable's name of the file\nconst files = [\n  {\n    file: 'dist/css/bootstrap.min.css',\n    configPropertyName: 'css_hash'\n  },\n  {\n    file: 'dist/css/bootstrap.rtl.min.css',\n    configPropertyName: 'css_rtl_hash'\n  },\n  {\n    file: 'dist/js/bootstrap.min.js',\n    configPropertyName: 'js_hash'\n  },\n  {\n    file: 'dist/js/bootstrap.bundle.min.js',\n    configPropertyName: 'js_bundle_hash'\n  },\n  {\n    file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',\n    configPropertyName: 'popper_hash'\n  }\n]\n\nfor (const file of files) {\n  fs.readFile(file.file, 'utf8', (error, data) => {\n    if (error) {\n      throw error\n    }\n\n    const algo = 'sha384'\n    const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64')\n    const integrity = `${algo}-${hash}`\n\n    console.log(`${file.configPropertyName}: ${integrity}`)\n\n    sh.sed('-i', new RegExp(`^(\\\\s+${file.configPropertyName}:\\\\s+[\"'])\\\\S*([\"'])`), `$1${integrity}$2`, configFile)\n  })\n}\n"
  },
  {
    "path": "src/common/bootstrap/build/postcss.config.js",
    "content": "'use strict'\n\nconst mapConfig = {\n  inline: false,\n  annotation: true,\n  sourcesContent: true\n}\n\nmodule.exports = context => {\n  return {\n    map: context.file.dirname.includes('examples') ? false : mapConfig,\n    plugins: {\n      autoprefixer: {\n        cascade: false\n      },\n      rtlcss: context.env === 'RTL'\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/build/rollup.config.js",
    "content": "'use strict'\n\nconst path = require('node:path')\nconst { babel } = require('@rollup/plugin-babel')\nconst { nodeResolve } = require('@rollup/plugin-node-resolve')\nconst replace = require('@rollup/plugin-replace')\nconst banner = require('./banner.js')\n\nconst BUNDLE = process.env.BUNDLE === 'true'\nconst ESM = process.env.ESM === 'true'\n\nlet fileDestination = `bootstrap${ESM ? '.esm' : ''}`\nconst external = ['@popperjs/core']\nconst plugins = [\n  babel({\n    // Only transpile our source code\n    exclude: 'node_modules/**',\n    // Include the helpers in the bundle, at most one copy of each\n    babelHelpers: 'bundled'\n  })\n]\nconst globals = {\n  '@popperjs/core': 'Popper'\n}\n\nif (BUNDLE) {\n  fileDestination += '.bundle'\n  // Remove last entry in external array to bundle Popper\n  external.pop()\n  delete globals['@popperjs/core']\n  plugins.push(\n    replace({\n      'process.env.NODE_ENV': '\"production\"',\n      preventAssignment: true\n    }),\n    nodeResolve()\n  )\n}\n\nconst rollupConfig = {\n  input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`),\n  output: {\n    banner,\n    file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`),\n    format: ESM ? 'esm' : 'umd',\n    globals,\n    generatedCode: 'es2015'\n  },\n  external,\n  plugins\n}\n\nif (!ESM) {\n  rollupConfig.output.name = 'bootstrap'\n}\n\nmodule.exports = rollupConfig\n"
  },
  {
    "path": "src/common/bootstrap/build/vnu-jar.js",
    "content": "#!/usr/bin/env node\n\n/*!\n * Script to run vnu-jar if Java is available.\n * Copyright 2017-2022 The Bootstrap Authors\n * Copyright 2017-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n'use strict'\n\nconst { execFile, spawn } = require('node:child_process')\nconst vnu = require('vnu-jar')\n\nexecFile('java', ['-version'], (error, stdout, stderr) => {\n  if (error) {\n    console.error('Skipping vnu-jar test; Java is missing.')\n    return\n  }\n\n  const is32bitJava = !/64-Bit/.test(stderr)\n\n  // vnu-jar accepts multiple ignores joined with a `|`.\n  // Also note that the ignores are string regular expressions.\n  const ignores = [\n    // \"autocomplete\" is included in <button> and checkboxes and radio <input>s due to\n    // Firefox's non-standard autocomplete behavior - see https://bugzilla.mozilla.org/show_bug.cgi?id=654072\n    'Attribute “autocomplete” is only allowed when the input type is.*',\n    'Attribute “autocomplete” not allowed on element “button” at this point.',\n    // Per https://www.w3.org/TR/html-aria/#docconformance having \"aria-disabled\" on a link is\n    // NOT RECOMMENDED, but it's still valid - we explain in the docs that it's not ideal,\n    // and offer more robust alternatives, but also need to show a less-than-ideal example\n    'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.'\n  ].join('|')\n\n  const args = [\n    '-jar',\n    `\"${vnu}\"`,\n    '--asciiquotes',\n    '--skip-non-html',\n    '--Werror',\n    `--filterpattern \"${ignores}\"`,\n    '_site/',\n    'js/tests/'\n  ]\n\n  // For the 32-bit Java we need to pass `-Xss512k`\n  if (is32bitJava) {\n    args.splice(0, 0, '-Xss512k')\n  }\n\n  return spawn('java', args, {\n    shell: true,\n    stdio: 'inherit'\n  })\n    .on('exit', process.exit)\n})\n"
  },
  {
    "path": "src/common/bootstrap/build/zip-examples.js",
    "content": "#!/usr/bin/env node\n\n/*!\n * Script to create the built examples zip archive;\n * requires the `zip` command to be present!\n * Copyright 2020-2022 The Bootstrap Authors\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n'use strict'\n\nconst path = require('node:path')\nconst sh = require('shelljs')\n\nconst pkg = require('../package.json')\n\nconst versionShort = pkg.config.version_short\nconst distFolder = `bootstrap-${pkg.version}-examples`\nconst rootDocsDir = '_site'\nconst docsDir = `${rootDocsDir}/docs/${versionShort}/`\n\n// these are the files we need in the examples\nconst cssFiles = [\n  'bootstrap.min.css',\n  'bootstrap.min.css.map',\n  'bootstrap.rtl.min.css',\n  'bootstrap.rtl.min.css.map'\n]\nconst jsFiles = [\n  'bootstrap.bundle.min.js',\n  'bootstrap.bundle.min.js.map'\n]\nconst imgFiles = [\n  'bootstrap-logo.svg',\n  'bootstrap-logo-white.svg'\n]\n\nsh.config.fatal = true\n\nif (!sh.test('-d', rootDocsDir)) {\n  throw new Error(`The \"${rootDocsDir}\" folder does not exist, did you forget building the docs?`)\n}\n\n// switch to the root dir\nsh.cd(path.join(__dirname, '..'))\n\n// remove any previously created folder/zip with the same name\nsh.rm('-rf', [distFolder, `${distFolder}.zip`])\n\n// create any folders so that `cp` works\nsh.mkdir('-p', [\n  distFolder,\n  `${distFolder}/assets/brand/`,\n  `${distFolder}/assets/dist/css/`,\n  `${distFolder}/assets/dist/js/`\n])\n\nsh.cp('-Rf', `${docsDir}/examples/*`, distFolder)\n\nfor (const file of cssFiles) {\n  sh.cp('-f', `${docsDir}/dist/css/${file}`, `${distFolder}/assets/dist/css/`)\n}\n\nfor (const file of jsFiles) {\n  sh.cp('-f', `${docsDir}/dist/js/${file}`, `${distFolder}/assets/dist/js/`)\n}\n\nfor (const file of imgFiles) {\n  sh.cp('-f', `${docsDir}/assets/brand/${file}`, `${distFolder}/assets/brand/`)\n}\n\nsh.rm(`${distFolder}/index.html`)\n\n// get all examples' HTML files\nfor (const file of sh.find(`${distFolder}/**/*.html`)) {\n  const fileContents = sh.cat(file)\n    .toString()\n    .replace(new RegExp(`\"/docs/${versionShort}/`, 'g'), '\"../')\n    .replace(/\"..\\/dist\\//g, '\"../assets/dist/')\n    .replace(/(<link href=\"\\.\\.\\/.*) integrity=\".*>/g, '$1>')\n    .replace(/(<script src=\"\\.\\.\\/.*) integrity=\".*>/g, '$1></script>')\n    .replace(/( +)<!-- favicons(.|\\n)+<style>/i, '    <style>')\n  new sh.ShellString(fileContents).to(file)\n}\n\n// create the zip file\nsh.exec(`zip -r9 \"${distFolder}.zip\" \"${distFolder}\"`)\n\n// remove the folder we created\nsh.rm('-rf', distFolder)\n"
  },
  {
    "path": "src/common/bootstrap/composer.json",
    "content": "{\n  \"name\": \"twbs/bootstrap\",\n  \"description\": \"The most popular front-end framework for developing responsive, mobile first projects on the web.\",\n  \"keywords\": [\n    \"css\",\n    \"js\",\n    \"sass\",\n    \"mobile-first\",\n    \"responsive\",\n    \"front-end\",\n    \"framework\",\n    \"web\"\n  ],\n  \"homepage\": \"https://getbootstrap.com/\",\n  \"authors\": [\n    {\n      \"name\": \"Mark Otto\",\n      \"email\": \"markdotto@gmail.com\"\n    },\n    {\n      \"name\": \"Jacob Thornton\",\n      \"email\": \"jacobthornton@gmail.com\"\n    }\n  ],\n  \"support\": {\n    \"issues\": \"https://github.com/twbs/bootstrap/issues\"\n  },\n  \"license\": \"MIT\",\n  \"replace\": {\n    \"twitter/bootstrap\": \"self.version\"\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/config.yml",
    "content": "languageCode:           \"en\"\ntitle:                  \"Bootstrap\"\nbaseURL:                \"https://getbootstrap.com\"\n\nsecurity:\n  enableInlineShortcodes: true\n  funcs:\n    getenv:\n      - ^HUGO_\n      - NETLIFY\n\nmarkup:\n  goldmark:\n    renderer:\n      unsafe:           true\n  highlight:\n    noClasses:          false\n  tableOfContents:\n    startLevel:         2\n    endLevel:           6\n\nbuildDrafts:            true\nbuildFuture:            true\n\nenableRobotsTXT:        true\nmetaDataFormat:         \"yaml\"\ndisableKinds:           [\"404\", \"taxonomy\", \"term\", \"RSS\"]\n\npublishDir:             \"_site\"\n\nmodule:\n  mounts:\n    - source:           dist\n      target:           static/docs/5.2/dist\n    - source:           site/assets\n      target:           assets\n    - source:           site/content\n      target:           content\n    - source:           site/data\n      target:           data\n    - source:           site/layouts\n      target:           layouts\n    - source:           site/static\n      target:           static\n    - source:           site/static/docs/5.2/assets/img/favicons/apple-touch-icon.png\n      target:           static/apple-touch-icon.png\n    - source:           site/static/docs/5.2/assets/img/favicons/favicon.ico\n      target:           static/favicon.ico\n\nparams:\n  subtitle:             \"The most popular HTML, CSS, and JS library in the world.\"\n  description:          \"Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins.\"\n  authors:              \"Mark Otto, Jacob Thornton, and Bootstrap contributors\"\n\n  current_version:      \"5.2.3\"\n  current_ruby_version: \"5.2.3\"\n  docs_version:         \"5.2\"\n  rfs_version:          \"v9.0.6\"\n  github_org:           \"https://github.com/twbs\"\n  repo:                 \"https://github.com/twbs/bootstrap\"\n  twitter:              \"getbootstrap\"\n  opencollective:       \"https://opencollective.com/bootstrap\"\n  blog:                 \"https://blog.getbootstrap.com/\"\n  themes:               \"https://themes.getbootstrap.com/\"\n  icons:                \"https://icons.getbootstrap.com/\"\n  swag:                 \"https://cottonbureau.com/people/bootstrap\"\n\n  download:\n    source:             \"https://github.com/twbs/bootstrap/archive/v5.2.3.zip\"\n    dist:               \"https://github.com/twbs/bootstrap/releases/download/v5.2.3/bootstrap-5.2.3-dist.zip\"\n    dist_examples:      \"https://github.com/twbs/bootstrap/releases/download/v5.2.3/bootstrap-5.2.3-examples.zip\"\n\n  cdn:\n    # See https://www.srihash.org for info on how to generate the hashes\n    css:              \"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css\"\n    css_hash:         \"sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65\"\n    css_rtl:          \"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.rtl.min.css\"\n    css_rtl_hash:     \"sha384-DOXMLfHhQkvFFp+rWTZwVlPVqdIhpDVYT9csOnHSgWQWPX0v5MCGtjCJbY6ERspU\"\n    js:               \"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js\"\n    js_hash:          \"sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V\"\n    js_bundle:        \"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js\"\n    js_bundle_hash:   \"sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4\"\n    popper:           \"https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js\"\n    popper_hash:      \"sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3\"\n\n  anchors:\n    min: 2\n    max: 5\n"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap-grid.css",
    "content": "/*!\n * Bootstrap Grid v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n.container,\n.container-fluid,\n.container-xxl,\n.container-xl,\n.container-lg,\n.container-md,\n.container-sm {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  width: 100%;\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  margin-right: auto;\n  margin-left: auto;\n}\n\n@media (min-width: 576px) {\n  .container-sm, .container {\n    max-width: 540px;\n  }\n}\n@media (min-width: 768px) {\n  .container-md, .container-sm, .container {\n    max-width: 720px;\n  }\n}\n@media (min-width: 992px) {\n  .container-lg, .container-md, .container-sm, .container {\n    max-width: 960px;\n  }\n}\n@media (min-width: 1200px) {\n  .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1140px;\n  }\n}\n@media (min-width: 1400px) {\n  .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1320px;\n  }\n}\n.row {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  display: flex;\n  flex-wrap: wrap;\n  margin-top: calc(-1 * var(--bs-gutter-y));\n  margin-right: calc(-0.5 * var(--bs-gutter-x));\n  margin-left: calc(-0.5 * var(--bs-gutter-x));\n}\n.row > * {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 100%;\n  max-width: 100%;\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  margin-top: var(--bs-gutter-y);\n}\n\n.col {\n  flex: 1 0 0%;\n}\n\n.row-cols-auto > * {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.row-cols-1 > * {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.row-cols-2 > * {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.row-cols-3 > * {\n  flex: 0 0 auto;\n  width: 33.3333333333%;\n}\n\n.row-cols-4 > * {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.row-cols-5 > * {\n  flex: 0 0 auto;\n  width: 20%;\n}\n\n.row-cols-6 > * {\n  flex: 0 0 auto;\n  width: 16.6666666667%;\n}\n\n.col-auto {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.col-1 {\n  flex: 0 0 auto;\n  width: 8.33333333%;\n}\n\n.col-2 {\n  flex: 0 0 auto;\n  width: 16.66666667%;\n}\n\n.col-3 {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.col-4 {\n  flex: 0 0 auto;\n  width: 33.33333333%;\n}\n\n.col-5 {\n  flex: 0 0 auto;\n  width: 41.66666667%;\n}\n\n.col-6 {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.col-7 {\n  flex: 0 0 auto;\n  width: 58.33333333%;\n}\n\n.col-8 {\n  flex: 0 0 auto;\n  width: 66.66666667%;\n}\n\n.col-9 {\n  flex: 0 0 auto;\n  width: 75%;\n}\n\n.col-10 {\n  flex: 0 0 auto;\n  width: 83.33333333%;\n}\n\n.col-11 {\n  flex: 0 0 auto;\n  width: 91.66666667%;\n}\n\n.col-12 {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.offset-1 {\n  margin-left: 8.33333333%;\n}\n\n.offset-2 {\n  margin-left: 16.66666667%;\n}\n\n.offset-3 {\n  margin-left: 25%;\n}\n\n.offset-4 {\n  margin-left: 33.33333333%;\n}\n\n.offset-5 {\n  margin-left: 41.66666667%;\n}\n\n.offset-6 {\n  margin-left: 50%;\n}\n\n.offset-7 {\n  margin-left: 58.33333333%;\n}\n\n.offset-8 {\n  margin-left: 66.66666667%;\n}\n\n.offset-9 {\n  margin-left: 75%;\n}\n\n.offset-10 {\n  margin-left: 83.33333333%;\n}\n\n.offset-11 {\n  margin-left: 91.66666667%;\n}\n\n.g-0,\n.gx-0 {\n  --bs-gutter-x: 0;\n}\n\n.g-0,\n.gy-0 {\n  --bs-gutter-y: 0;\n}\n\n.g-1,\n.gx-1 {\n  --bs-gutter-x: 0.25rem;\n}\n\n.g-1,\n.gy-1 {\n  --bs-gutter-y: 0.25rem;\n}\n\n.g-2,\n.gx-2 {\n  --bs-gutter-x: 0.5rem;\n}\n\n.g-2,\n.gy-2 {\n  --bs-gutter-y: 0.5rem;\n}\n\n.g-3,\n.gx-3 {\n  --bs-gutter-x: 1rem;\n}\n\n.g-3,\n.gy-3 {\n  --bs-gutter-y: 1rem;\n}\n\n.g-4,\n.gx-4 {\n  --bs-gutter-x: 1.5rem;\n}\n\n.g-4,\n.gy-4 {\n  --bs-gutter-y: 1.5rem;\n}\n\n.g-5,\n.gx-5 {\n  --bs-gutter-x: 3rem;\n}\n\n.g-5,\n.gy-5 {\n  --bs-gutter-y: 3rem;\n}\n\n@media (min-width: 576px) {\n  .col-sm {\n    flex: 1 0 0%;\n  }\n  .row-cols-sm-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-sm-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-sm-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-sm-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-sm-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-sm-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-sm-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-sm-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-sm-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-sm-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-sm-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-sm-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-sm-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-sm-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-sm-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-sm-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-sm-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-sm-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-sm-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-sm-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-sm-0 {\n    margin-left: 0;\n  }\n  .offset-sm-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-sm-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-sm-3 {\n    margin-left: 25%;\n  }\n  .offset-sm-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-sm-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-sm-6 {\n    margin-left: 50%;\n  }\n  .offset-sm-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-sm-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-sm-9 {\n    margin-left: 75%;\n  }\n  .offset-sm-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-sm-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-sm-0,\n.gx-sm-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-sm-0,\n.gy-sm-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-sm-1,\n.gx-sm-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-sm-1,\n.gy-sm-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-sm-2,\n.gx-sm-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-sm-2,\n.gy-sm-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-sm-3,\n.gx-sm-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-sm-3,\n.gy-sm-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-sm-4,\n.gx-sm-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-sm-4,\n.gy-sm-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-sm-5,\n.gx-sm-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-sm-5,\n.gy-sm-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 768px) {\n  .col-md {\n    flex: 1 0 0%;\n  }\n  .row-cols-md-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-md-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-md-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-md-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-md-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-md-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-md-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-md-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-md-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-md-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-md-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-md-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-md-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-md-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-md-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-md-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-md-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-md-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-md-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-md-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-md-0 {\n    margin-left: 0;\n  }\n  .offset-md-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-md-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-md-3 {\n    margin-left: 25%;\n  }\n  .offset-md-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-md-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-md-6 {\n    margin-left: 50%;\n  }\n  .offset-md-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-md-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-md-9 {\n    margin-left: 75%;\n  }\n  .offset-md-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-md-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-md-0,\n.gx-md-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-md-0,\n.gy-md-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-md-1,\n.gx-md-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-md-1,\n.gy-md-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-md-2,\n.gx-md-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-md-2,\n.gy-md-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-md-3,\n.gx-md-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-md-3,\n.gy-md-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-md-4,\n.gx-md-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-md-4,\n.gy-md-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-md-5,\n.gx-md-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-md-5,\n.gy-md-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 992px) {\n  .col-lg {\n    flex: 1 0 0%;\n  }\n  .row-cols-lg-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-lg-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-lg-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-lg-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-lg-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-lg-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-lg-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-lg-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-lg-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-lg-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-lg-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-lg-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-lg-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-lg-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-lg-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-lg-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-lg-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-lg-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-lg-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-lg-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-lg-0 {\n    margin-left: 0;\n  }\n  .offset-lg-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-lg-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-lg-3 {\n    margin-left: 25%;\n  }\n  .offset-lg-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-lg-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-lg-6 {\n    margin-left: 50%;\n  }\n  .offset-lg-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-lg-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-lg-9 {\n    margin-left: 75%;\n  }\n  .offset-lg-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-lg-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-lg-0,\n.gx-lg-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-lg-0,\n.gy-lg-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-lg-1,\n.gx-lg-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-lg-1,\n.gy-lg-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-lg-2,\n.gx-lg-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-lg-2,\n.gy-lg-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-lg-3,\n.gx-lg-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-lg-3,\n.gy-lg-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-lg-4,\n.gx-lg-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-lg-4,\n.gy-lg-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-lg-5,\n.gx-lg-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-lg-5,\n.gy-lg-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1200px) {\n  .col-xl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xl-0 {\n    margin-left: 0;\n  }\n  .offset-xl-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-xl-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-xl-3 {\n    margin-left: 25%;\n  }\n  .offset-xl-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-xl-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-xl-6 {\n    margin-left: 50%;\n  }\n  .offset-xl-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-xl-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-xl-9 {\n    margin-left: 75%;\n  }\n  .offset-xl-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-xl-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-xl-0,\n.gx-xl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xl-0,\n.gy-xl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xl-1,\n.gx-xl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xl-1,\n.gy-xl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xl-2,\n.gx-xl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xl-2,\n.gy-xl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xl-3,\n.gx-xl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xl-3,\n.gy-xl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xl-4,\n.gx-xl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xl-4,\n.gy-xl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xl-5,\n.gx-xl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xl-5,\n.gy-xl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1400px) {\n  .col-xxl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xxl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xxl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xxl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xxl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xxl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xxl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xxl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xxl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xxl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xxl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xxl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xxl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xxl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xxl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xxl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xxl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xxl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xxl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xxl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xxl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xxl-0 {\n    margin-left: 0;\n  }\n  .offset-xxl-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-xxl-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-xxl-3 {\n    margin-left: 25%;\n  }\n  .offset-xxl-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-xxl-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-xxl-6 {\n    margin-left: 50%;\n  }\n  .offset-xxl-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-xxl-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-xxl-9 {\n    margin-left: 75%;\n  }\n  .offset-xxl-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-xxl-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-xxl-0,\n.gx-xxl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xxl-0,\n.gy-xxl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xxl-1,\n.gx-xxl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xxl-1,\n.gy-xxl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xxl-2,\n.gx-xxl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xxl-2,\n.gy-xxl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xxl-3,\n.gx-xxl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xxl-3,\n.gy-xxl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xxl-4,\n.gx-xxl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xxl-4,\n.gy-xxl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xxl-5,\n.gx-xxl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xxl-5,\n.gy-xxl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n.d-inline {\n  display: inline !important;\n}\n\n.d-inline-block {\n  display: inline-block !important;\n}\n\n.d-block {\n  display: block !important;\n}\n\n.d-grid {\n  display: grid !important;\n}\n\n.d-table {\n  display: table !important;\n}\n\n.d-table-row {\n  display: table-row !important;\n}\n\n.d-table-cell {\n  display: table-cell !important;\n}\n\n.d-flex {\n  display: flex !important;\n}\n\n.d-inline-flex {\n  display: inline-flex !important;\n}\n\n.d-none {\n  display: none !important;\n}\n\n.flex-fill {\n  flex: 1 1 auto !important;\n}\n\n.flex-row {\n  flex-direction: row !important;\n}\n\n.flex-column {\n  flex-direction: column !important;\n}\n\n.flex-row-reverse {\n  flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n  flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n  flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n  flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n  flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n  flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n  justify-content: flex-start !important;\n}\n\n.justify-content-end {\n  justify-content: flex-end !important;\n}\n\n.justify-content-center {\n  justify-content: center !important;\n}\n\n.justify-content-between {\n  justify-content: space-between !important;\n}\n\n.justify-content-around {\n  justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n  justify-content: space-evenly !important;\n}\n\n.align-items-start {\n  align-items: flex-start !important;\n}\n\n.align-items-end {\n  align-items: flex-end !important;\n}\n\n.align-items-center {\n  align-items: center !important;\n}\n\n.align-items-baseline {\n  align-items: baseline !important;\n}\n\n.align-items-stretch {\n  align-items: stretch !important;\n}\n\n.align-content-start {\n  align-content: flex-start !important;\n}\n\n.align-content-end {\n  align-content: flex-end !important;\n}\n\n.align-content-center {\n  align-content: center !important;\n}\n\n.align-content-between {\n  align-content: space-between !important;\n}\n\n.align-content-around {\n  align-content: space-around !important;\n}\n\n.align-content-stretch {\n  align-content: stretch !important;\n}\n\n.align-self-auto {\n  align-self: auto !important;\n}\n\n.align-self-start {\n  align-self: flex-start !important;\n}\n\n.align-self-end {\n  align-self: flex-end !important;\n}\n\n.align-self-center {\n  align-self: center !important;\n}\n\n.align-self-baseline {\n  align-self: baseline !important;\n}\n\n.align-self-stretch {\n  align-self: stretch !important;\n}\n\n.order-first {\n  order: -1 !important;\n}\n\n.order-0 {\n  order: 0 !important;\n}\n\n.order-1 {\n  order: 1 !important;\n}\n\n.order-2 {\n  order: 2 !important;\n}\n\n.order-3 {\n  order: 3 !important;\n}\n\n.order-4 {\n  order: 4 !important;\n}\n\n.order-5 {\n  order: 5 !important;\n}\n\n.order-last {\n  order: 6 !important;\n}\n\n.m-0 {\n  margin: 0 !important;\n}\n\n.m-1 {\n  margin: 0.25rem !important;\n}\n\n.m-2 {\n  margin: 0.5rem !important;\n}\n\n.m-3 {\n  margin: 1rem !important;\n}\n\n.m-4 {\n  margin: 1.5rem !important;\n}\n\n.m-5 {\n  margin: 3rem !important;\n}\n\n.m-auto {\n  margin: auto !important;\n}\n\n.mx-0 {\n  margin-right: 0 !important;\n  margin-left: 0 !important;\n}\n\n.mx-1 {\n  margin-right: 0.25rem !important;\n  margin-left: 0.25rem !important;\n}\n\n.mx-2 {\n  margin-right: 0.5rem !important;\n  margin-left: 0.5rem !important;\n}\n\n.mx-3 {\n  margin-right: 1rem !important;\n  margin-left: 1rem !important;\n}\n\n.mx-4 {\n  margin-right: 1.5rem !important;\n  margin-left: 1.5rem !important;\n}\n\n.mx-5 {\n  margin-right: 3rem !important;\n  margin-left: 3rem !important;\n}\n\n.mx-auto {\n  margin-right: auto !important;\n  margin-left: auto !important;\n}\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important;\n}\n\n.my-4 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important;\n}\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important;\n}\n\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.mt-1 {\n  margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n  margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n  margin-top: 1rem !important;\n}\n\n.mt-4 {\n  margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n  margin-top: 3rem !important;\n}\n\n.mt-auto {\n  margin-top: auto !important;\n}\n\n.me-0 {\n  margin-right: 0 !important;\n}\n\n.me-1 {\n  margin-right: 0.25rem !important;\n}\n\n.me-2 {\n  margin-right: 0.5rem !important;\n}\n\n.me-3 {\n  margin-right: 1rem !important;\n}\n\n.me-4 {\n  margin-right: 1.5rem !important;\n}\n\n.me-5 {\n  margin-right: 3rem !important;\n}\n\n.me-auto {\n  margin-right: auto !important;\n}\n\n.mb-0 {\n  margin-bottom: 0 !important;\n}\n\n.mb-1 {\n  margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n  margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n  margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n  margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n  margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n  margin-bottom: auto !important;\n}\n\n.ms-0 {\n  margin-left: 0 !important;\n}\n\n.ms-1 {\n  margin-left: 0.25rem !important;\n}\n\n.ms-2 {\n  margin-left: 0.5rem !important;\n}\n\n.ms-3 {\n  margin-left: 1rem !important;\n}\n\n.ms-4 {\n  margin-left: 1.5rem !important;\n}\n\n.ms-5 {\n  margin-left: 3rem !important;\n}\n\n.ms-auto {\n  margin-left: auto !important;\n}\n\n.p-0 {\n  padding: 0 !important;\n}\n\n.p-1 {\n  padding: 0.25rem !important;\n}\n\n.p-2 {\n  padding: 0.5rem !important;\n}\n\n.p-3 {\n  padding: 1rem !important;\n}\n\n.p-4 {\n  padding: 1.5rem !important;\n}\n\n.p-5 {\n  padding: 3rem !important;\n}\n\n.px-0 {\n  padding-right: 0 !important;\n  padding-left: 0 !important;\n}\n\n.px-1 {\n  padding-right: 0.25rem !important;\n  padding-left: 0.25rem !important;\n}\n\n.px-2 {\n  padding-right: 0.5rem !important;\n  padding-left: 0.5rem !important;\n}\n\n.px-3 {\n  padding-right: 1rem !important;\n  padding-left: 1rem !important;\n}\n\n.px-4 {\n  padding-right: 1.5rem !important;\n  padding-left: 1.5rem !important;\n}\n\n.px-5 {\n  padding-right: 3rem !important;\n  padding-left: 3rem !important;\n}\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n\n.py-4 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n  padding-top: 0 !important;\n}\n\n.pt-1 {\n  padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n  padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n  padding-top: 1rem !important;\n}\n\n.pt-4 {\n  padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n  padding-top: 3rem !important;\n}\n\n.pe-0 {\n  padding-right: 0 !important;\n}\n\n.pe-1 {\n  padding-right: 0.25rem !important;\n}\n\n.pe-2 {\n  padding-right: 0.5rem !important;\n}\n\n.pe-3 {\n  padding-right: 1rem !important;\n}\n\n.pe-4 {\n  padding-right: 1.5rem !important;\n}\n\n.pe-5 {\n  padding-right: 3rem !important;\n}\n\n.pb-0 {\n  padding-bottom: 0 !important;\n}\n\n.pb-1 {\n  padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n  padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n  padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n  padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n  padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n  padding-left: 0 !important;\n}\n\n.ps-1 {\n  padding-left: 0.25rem !important;\n}\n\n.ps-2 {\n  padding-left: 0.5rem !important;\n}\n\n.ps-3 {\n  padding-left: 1rem !important;\n}\n\n.ps-4 {\n  padding-left: 1.5rem !important;\n}\n\n.ps-5 {\n  padding-left: 3rem !important;\n}\n\n@media (min-width: 576px) {\n  .d-sm-inline {\n    display: inline !important;\n  }\n  .d-sm-inline-block {\n    display: inline-block !important;\n  }\n  .d-sm-block {\n    display: block !important;\n  }\n  .d-sm-grid {\n    display: grid !important;\n  }\n  .d-sm-table {\n    display: table !important;\n  }\n  .d-sm-table-row {\n    display: table-row !important;\n  }\n  .d-sm-table-cell {\n    display: table-cell !important;\n  }\n  .d-sm-flex {\n    display: flex !important;\n  }\n  .d-sm-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-sm-none {\n    display: none !important;\n  }\n  .flex-sm-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-sm-row {\n    flex-direction: row !important;\n  }\n  .flex-sm-column {\n    flex-direction: column !important;\n  }\n  .flex-sm-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-sm-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-sm-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-sm-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-sm-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-sm-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-sm-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-sm-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-sm-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-sm-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-sm-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-sm-center {\n    justify-content: center !important;\n  }\n  .justify-content-sm-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-sm-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-sm-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-sm-start {\n    align-items: flex-start !important;\n  }\n  .align-items-sm-end {\n    align-items: flex-end !important;\n  }\n  .align-items-sm-center {\n    align-items: center !important;\n  }\n  .align-items-sm-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-sm-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-sm-start {\n    align-content: flex-start !important;\n  }\n  .align-content-sm-end {\n    align-content: flex-end !important;\n  }\n  .align-content-sm-center {\n    align-content: center !important;\n  }\n  .align-content-sm-between {\n    align-content: space-between !important;\n  }\n  .align-content-sm-around {\n    align-content: space-around !important;\n  }\n  .align-content-sm-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-sm-auto {\n    align-self: auto !important;\n  }\n  .align-self-sm-start {\n    align-self: flex-start !important;\n  }\n  .align-self-sm-end {\n    align-self: flex-end !important;\n  }\n  .align-self-sm-center {\n    align-self: center !important;\n  }\n  .align-self-sm-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-sm-stretch {\n    align-self: stretch !important;\n  }\n  .order-sm-first {\n    order: -1 !important;\n  }\n  .order-sm-0 {\n    order: 0 !important;\n  }\n  .order-sm-1 {\n    order: 1 !important;\n  }\n  .order-sm-2 {\n    order: 2 !important;\n  }\n  .order-sm-3 {\n    order: 3 !important;\n  }\n  .order-sm-4 {\n    order: 4 !important;\n  }\n  .order-sm-5 {\n    order: 5 !important;\n  }\n  .order-sm-last {\n    order: 6 !important;\n  }\n  .m-sm-0 {\n    margin: 0 !important;\n  }\n  .m-sm-1 {\n    margin: 0.25rem !important;\n  }\n  .m-sm-2 {\n    margin: 0.5rem !important;\n  }\n  .m-sm-3 {\n    margin: 1rem !important;\n  }\n  .m-sm-4 {\n    margin: 1.5rem !important;\n  }\n  .m-sm-5 {\n    margin: 3rem !important;\n  }\n  .m-sm-auto {\n    margin: auto !important;\n  }\n  .mx-sm-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-sm-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-sm-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-sm-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-sm-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-sm-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-sm-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-sm-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-sm-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-sm-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-sm-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-sm-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-sm-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-sm-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-sm-0 {\n    margin-top: 0 !important;\n  }\n  .mt-sm-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-sm-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-sm-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-sm-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-sm-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-sm-auto {\n    margin-top: auto !important;\n  }\n  .me-sm-0 {\n    margin-right: 0 !important;\n  }\n  .me-sm-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-sm-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-sm-3 {\n    margin-right: 1rem !important;\n  }\n  .me-sm-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-sm-5 {\n    margin-right: 3rem !important;\n  }\n  .me-sm-auto {\n    margin-right: auto !important;\n  }\n  .mb-sm-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-sm-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-sm-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-sm-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-sm-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-sm-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-sm-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-sm-0 {\n    margin-left: 0 !important;\n  }\n  .ms-sm-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-sm-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-sm-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-sm-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-sm-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-sm-auto {\n    margin-left: auto !important;\n  }\n  .p-sm-0 {\n    padding: 0 !important;\n  }\n  .p-sm-1 {\n    padding: 0.25rem !important;\n  }\n  .p-sm-2 {\n    padding: 0.5rem !important;\n  }\n  .p-sm-3 {\n    padding: 1rem !important;\n  }\n  .p-sm-4 {\n    padding: 1.5rem !important;\n  }\n  .p-sm-5 {\n    padding: 3rem !important;\n  }\n  .px-sm-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-sm-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-sm-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-sm-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-sm-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-sm-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-sm-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-sm-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-sm-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-sm-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-sm-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-sm-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-sm-0 {\n    padding-top: 0 !important;\n  }\n  .pt-sm-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-sm-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-sm-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-sm-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-sm-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-sm-0 {\n    padding-right: 0 !important;\n  }\n  .pe-sm-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-sm-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-sm-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-sm-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-sm-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-sm-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-sm-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-sm-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-sm-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-sm-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-sm-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-sm-0 {\n    padding-left: 0 !important;\n  }\n  .ps-sm-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-sm-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-sm-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-sm-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-sm-5 {\n    padding-left: 3rem !important;\n  }\n}\n@media (min-width: 768px) {\n  .d-md-inline {\n    display: inline !important;\n  }\n  .d-md-inline-block {\n    display: inline-block !important;\n  }\n  .d-md-block {\n    display: block !important;\n  }\n  .d-md-grid {\n    display: grid !important;\n  }\n  .d-md-table {\n    display: table !important;\n  }\n  .d-md-table-row {\n    display: table-row !important;\n  }\n  .d-md-table-cell {\n    display: table-cell !important;\n  }\n  .d-md-flex {\n    display: flex !important;\n  }\n  .d-md-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-md-none {\n    display: none !important;\n  }\n  .flex-md-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-md-row {\n    flex-direction: row !important;\n  }\n  .flex-md-column {\n    flex-direction: column !important;\n  }\n  .flex-md-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-md-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-md-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-md-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-md-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-md-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-md-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-md-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-md-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-md-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-md-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-md-center {\n    justify-content: center !important;\n  }\n  .justify-content-md-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-md-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-md-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-md-start {\n    align-items: flex-start !important;\n  }\n  .align-items-md-end {\n    align-items: flex-end !important;\n  }\n  .align-items-md-center {\n    align-items: center !important;\n  }\n  .align-items-md-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-md-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-md-start {\n    align-content: flex-start !important;\n  }\n  .align-content-md-end {\n    align-content: flex-end !important;\n  }\n  .align-content-md-center {\n    align-content: center !important;\n  }\n  .align-content-md-between {\n    align-content: space-between !important;\n  }\n  .align-content-md-around {\n    align-content: space-around !important;\n  }\n  .align-content-md-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-md-auto {\n    align-self: auto !important;\n  }\n  .align-self-md-start {\n    align-self: flex-start !important;\n  }\n  .align-self-md-end {\n    align-self: flex-end !important;\n  }\n  .align-self-md-center {\n    align-self: center !important;\n  }\n  .align-self-md-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-md-stretch {\n    align-self: stretch !important;\n  }\n  .order-md-first {\n    order: -1 !important;\n  }\n  .order-md-0 {\n    order: 0 !important;\n  }\n  .order-md-1 {\n    order: 1 !important;\n  }\n  .order-md-2 {\n    order: 2 !important;\n  }\n  .order-md-3 {\n    order: 3 !important;\n  }\n  .order-md-4 {\n    order: 4 !important;\n  }\n  .order-md-5 {\n    order: 5 !important;\n  }\n  .order-md-last {\n    order: 6 !important;\n  }\n  .m-md-0 {\n    margin: 0 !important;\n  }\n  .m-md-1 {\n    margin: 0.25rem !important;\n  }\n  .m-md-2 {\n    margin: 0.5rem !important;\n  }\n  .m-md-3 {\n    margin: 1rem !important;\n  }\n  .m-md-4 {\n    margin: 1.5rem !important;\n  }\n  .m-md-5 {\n    margin: 3rem !important;\n  }\n  .m-md-auto {\n    margin: auto !important;\n  }\n  .mx-md-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-md-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-md-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-md-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-md-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-md-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-md-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-md-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-md-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-md-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-md-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-md-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-md-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-md-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-md-0 {\n    margin-top: 0 !important;\n  }\n  .mt-md-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-md-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-md-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-md-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-md-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-md-auto {\n    margin-top: auto !important;\n  }\n  .me-md-0 {\n    margin-right: 0 !important;\n  }\n  .me-md-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-md-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-md-3 {\n    margin-right: 1rem !important;\n  }\n  .me-md-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-md-5 {\n    margin-right: 3rem !important;\n  }\n  .me-md-auto {\n    margin-right: auto !important;\n  }\n  .mb-md-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-md-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-md-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-md-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-md-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-md-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-md-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-md-0 {\n    margin-left: 0 !important;\n  }\n  .ms-md-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-md-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-md-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-md-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-md-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-md-auto {\n    margin-left: auto !important;\n  }\n  .p-md-0 {\n    padding: 0 !important;\n  }\n  .p-md-1 {\n    padding: 0.25rem !important;\n  }\n  .p-md-2 {\n    padding: 0.5rem !important;\n  }\n  .p-md-3 {\n    padding: 1rem !important;\n  }\n  .p-md-4 {\n    padding: 1.5rem !important;\n  }\n  .p-md-5 {\n    padding: 3rem !important;\n  }\n  .px-md-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-md-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-md-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-md-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-md-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-md-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-md-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-md-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-md-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-md-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-md-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-md-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-md-0 {\n    padding-top: 0 !important;\n  }\n  .pt-md-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-md-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-md-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-md-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-md-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-md-0 {\n    padding-right: 0 !important;\n  }\n  .pe-md-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-md-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-md-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-md-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-md-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-md-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-md-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-md-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-md-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-md-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-md-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-md-0 {\n    padding-left: 0 !important;\n  }\n  .ps-md-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-md-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-md-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-md-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-md-5 {\n    padding-left: 3rem !important;\n  }\n}\n@media (min-width: 992px) {\n  .d-lg-inline {\n    display: inline !important;\n  }\n  .d-lg-inline-block {\n    display: inline-block !important;\n  }\n  .d-lg-block {\n    display: block !important;\n  }\n  .d-lg-grid {\n    display: grid !important;\n  }\n  .d-lg-table {\n    display: table !important;\n  }\n  .d-lg-table-row {\n    display: table-row !important;\n  }\n  .d-lg-table-cell {\n    display: table-cell !important;\n  }\n  .d-lg-flex {\n    display: flex !important;\n  }\n  .d-lg-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-lg-none {\n    display: none !important;\n  }\n  .flex-lg-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-lg-row {\n    flex-direction: row !important;\n  }\n  .flex-lg-column {\n    flex-direction: column !important;\n  }\n  .flex-lg-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-lg-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-lg-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-lg-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-lg-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-lg-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-lg-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-lg-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-lg-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-lg-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-lg-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-lg-center {\n    justify-content: center !important;\n  }\n  .justify-content-lg-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-lg-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-lg-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-lg-start {\n    align-items: flex-start !important;\n  }\n  .align-items-lg-end {\n    align-items: flex-end !important;\n  }\n  .align-items-lg-center {\n    align-items: center !important;\n  }\n  .align-items-lg-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-lg-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-lg-start {\n    align-content: flex-start !important;\n  }\n  .align-content-lg-end {\n    align-content: flex-end !important;\n  }\n  .align-content-lg-center {\n    align-content: center !important;\n  }\n  .align-content-lg-between {\n    align-content: space-between !important;\n  }\n  .align-content-lg-around {\n    align-content: space-around !important;\n  }\n  .align-content-lg-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-lg-auto {\n    align-self: auto !important;\n  }\n  .align-self-lg-start {\n    align-self: flex-start !important;\n  }\n  .align-self-lg-end {\n    align-self: flex-end !important;\n  }\n  .align-self-lg-center {\n    align-self: center !important;\n  }\n  .align-self-lg-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-lg-stretch {\n    align-self: stretch !important;\n  }\n  .order-lg-first {\n    order: -1 !important;\n  }\n  .order-lg-0 {\n    order: 0 !important;\n  }\n  .order-lg-1 {\n    order: 1 !important;\n  }\n  .order-lg-2 {\n    order: 2 !important;\n  }\n  .order-lg-3 {\n    order: 3 !important;\n  }\n  .order-lg-4 {\n    order: 4 !important;\n  }\n  .order-lg-5 {\n    order: 5 !important;\n  }\n  .order-lg-last {\n    order: 6 !important;\n  }\n  .m-lg-0 {\n    margin: 0 !important;\n  }\n  .m-lg-1 {\n    margin: 0.25rem !important;\n  }\n  .m-lg-2 {\n    margin: 0.5rem !important;\n  }\n  .m-lg-3 {\n    margin: 1rem !important;\n  }\n  .m-lg-4 {\n    margin: 1.5rem !important;\n  }\n  .m-lg-5 {\n    margin: 3rem !important;\n  }\n  .m-lg-auto {\n    margin: auto !important;\n  }\n  .mx-lg-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-lg-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-lg-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-lg-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-lg-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-lg-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-lg-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-lg-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-lg-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-lg-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-lg-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-lg-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-lg-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-lg-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-lg-0 {\n    margin-top: 0 !important;\n  }\n  .mt-lg-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-lg-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-lg-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-lg-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-lg-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-lg-auto {\n    margin-top: auto !important;\n  }\n  .me-lg-0 {\n    margin-right: 0 !important;\n  }\n  .me-lg-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-lg-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-lg-3 {\n    margin-right: 1rem !important;\n  }\n  .me-lg-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-lg-5 {\n    margin-right: 3rem !important;\n  }\n  .me-lg-auto {\n    margin-right: auto !important;\n  }\n  .mb-lg-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-lg-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-lg-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-lg-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-lg-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-lg-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-lg-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-lg-0 {\n    margin-left: 0 !important;\n  }\n  .ms-lg-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-lg-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-lg-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-lg-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-lg-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-lg-auto {\n    margin-left: auto !important;\n  }\n  .p-lg-0 {\n    padding: 0 !important;\n  }\n  .p-lg-1 {\n    padding: 0.25rem !important;\n  }\n  .p-lg-2 {\n    padding: 0.5rem !important;\n  }\n  .p-lg-3 {\n    padding: 1rem !important;\n  }\n  .p-lg-4 {\n    padding: 1.5rem !important;\n  }\n  .p-lg-5 {\n    padding: 3rem !important;\n  }\n  .px-lg-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-lg-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-lg-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-lg-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-lg-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-lg-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-lg-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-lg-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-lg-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-lg-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-lg-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-lg-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-lg-0 {\n    padding-top: 0 !important;\n  }\n  .pt-lg-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-lg-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-lg-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-lg-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-lg-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-lg-0 {\n    padding-right: 0 !important;\n  }\n  .pe-lg-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-lg-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-lg-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-lg-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-lg-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-lg-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-lg-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-lg-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-lg-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-lg-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-lg-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-lg-0 {\n    padding-left: 0 !important;\n  }\n  .ps-lg-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-lg-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-lg-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-lg-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-lg-5 {\n    padding-left: 3rem !important;\n  }\n}\n@media (min-width: 1200px) {\n  .d-xl-inline {\n    display: inline !important;\n  }\n  .d-xl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xl-block {\n    display: block !important;\n  }\n  .d-xl-grid {\n    display: grid !important;\n  }\n  .d-xl-table {\n    display: table !important;\n  }\n  .d-xl-table-row {\n    display: table-row !important;\n  }\n  .d-xl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xl-flex {\n    display: flex !important;\n  }\n  .d-xl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xl-none {\n    display: none !important;\n  }\n  .flex-xl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xl-row {\n    flex-direction: row !important;\n  }\n  .flex-xl-column {\n    flex-direction: column !important;\n  }\n  .flex-xl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xl-center {\n    align-items: center !important;\n  }\n  .align-items-xl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xl-center {\n    align-content: center !important;\n  }\n  .align-content-xl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xl-center {\n    align-self: center !important;\n  }\n  .align-self-xl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xl-first {\n    order: -1 !important;\n  }\n  .order-xl-0 {\n    order: 0 !important;\n  }\n  .order-xl-1 {\n    order: 1 !important;\n  }\n  .order-xl-2 {\n    order: 2 !important;\n  }\n  .order-xl-3 {\n    order: 3 !important;\n  }\n  .order-xl-4 {\n    order: 4 !important;\n  }\n  .order-xl-5 {\n    order: 5 !important;\n  }\n  .order-xl-last {\n    order: 6 !important;\n  }\n  .m-xl-0 {\n    margin: 0 !important;\n  }\n  .m-xl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xl-3 {\n    margin: 1rem !important;\n  }\n  .m-xl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xl-5 {\n    margin: 3rem !important;\n  }\n  .m-xl-auto {\n    margin: auto !important;\n  }\n  .mx-xl-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-xl-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-xl-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-xl-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-xl-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-xl-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-xl-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-xl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xl-auto {\n    margin-top: auto !important;\n  }\n  .me-xl-0 {\n    margin-right: 0 !important;\n  }\n  .me-xl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-xl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-xl-3 {\n    margin-right: 1rem !important;\n  }\n  .me-xl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-xl-5 {\n    margin-right: 3rem !important;\n  }\n  .me-xl-auto {\n    margin-right: auto !important;\n  }\n  .mb-xl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xl-0 {\n    margin-left: 0 !important;\n  }\n  .ms-xl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-xl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-xl-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-xl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-xl-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-xl-auto {\n    margin-left: auto !important;\n  }\n  .p-xl-0 {\n    padding: 0 !important;\n  }\n  .p-xl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xl-3 {\n    padding: 1rem !important;\n  }\n  .p-xl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xl-5 {\n    padding: 3rem !important;\n  }\n  .px-xl-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-xl-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-xl-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-xl-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-xl-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-xl-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-xl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xl-0 {\n    padding-right: 0 !important;\n  }\n  .pe-xl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-xl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-xl-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-xl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-xl-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-xl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xl-0 {\n    padding-left: 0 !important;\n  }\n  .ps-xl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-xl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-xl-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-xl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-xl-5 {\n    padding-left: 3rem !important;\n  }\n}\n@media (min-width: 1400px) {\n  .d-xxl-inline {\n    display: inline !important;\n  }\n  .d-xxl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xxl-block {\n    display: block !important;\n  }\n  .d-xxl-grid {\n    display: grid !important;\n  }\n  .d-xxl-table {\n    display: table !important;\n  }\n  .d-xxl-table-row {\n    display: table-row !important;\n  }\n  .d-xxl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xxl-flex {\n    display: flex !important;\n  }\n  .d-xxl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xxl-none {\n    display: none !important;\n  }\n  .flex-xxl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xxl-row {\n    flex-direction: row !important;\n  }\n  .flex-xxl-column {\n    flex-direction: column !important;\n  }\n  .flex-xxl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xxl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xxl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xxl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xxl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xxl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xxl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xxl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xxl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xxl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xxl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xxl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xxl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xxl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xxl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xxl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xxl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xxl-center {\n    align-items: center !important;\n  }\n  .align-items-xxl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xxl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xxl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xxl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xxl-center {\n    align-content: center !important;\n  }\n  .align-content-xxl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xxl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xxl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xxl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xxl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xxl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xxl-center {\n    align-self: center !important;\n  }\n  .align-self-xxl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xxl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xxl-first {\n    order: -1 !important;\n  }\n  .order-xxl-0 {\n    order: 0 !important;\n  }\n  .order-xxl-1 {\n    order: 1 !important;\n  }\n  .order-xxl-2 {\n    order: 2 !important;\n  }\n  .order-xxl-3 {\n    order: 3 !important;\n  }\n  .order-xxl-4 {\n    order: 4 !important;\n  }\n  .order-xxl-5 {\n    order: 5 !important;\n  }\n  .order-xxl-last {\n    order: 6 !important;\n  }\n  .m-xxl-0 {\n    margin: 0 !important;\n  }\n  .m-xxl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xxl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xxl-3 {\n    margin: 1rem !important;\n  }\n  .m-xxl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xxl-5 {\n    margin: 3rem !important;\n  }\n  .m-xxl-auto {\n    margin: auto !important;\n  }\n  .mx-xxl-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-xxl-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-xxl-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-xxl-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-xxl-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-xxl-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-xxl-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-xxl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xxl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xxl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xxl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xxl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xxl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xxl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xxl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xxl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xxl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xxl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xxl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xxl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xxl-auto {\n    margin-top: auto !important;\n  }\n  .me-xxl-0 {\n    margin-right: 0 !important;\n  }\n  .me-xxl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-xxl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-xxl-3 {\n    margin-right: 1rem !important;\n  }\n  .me-xxl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-xxl-5 {\n    margin-right: 3rem !important;\n  }\n  .me-xxl-auto {\n    margin-right: auto !important;\n  }\n  .mb-xxl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xxl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xxl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xxl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xxl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xxl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xxl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xxl-0 {\n    margin-left: 0 !important;\n  }\n  .ms-xxl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-xxl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-xxl-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-xxl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-xxl-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-xxl-auto {\n    margin-left: auto !important;\n  }\n  .p-xxl-0 {\n    padding: 0 !important;\n  }\n  .p-xxl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xxl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xxl-3 {\n    padding: 1rem !important;\n  }\n  .p-xxl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xxl-5 {\n    padding: 3rem !important;\n  }\n  .px-xxl-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-xxl-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-xxl-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-xxl-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-xxl-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-xxl-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-xxl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xxl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xxl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xxl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xxl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xxl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xxl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xxl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xxl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xxl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xxl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xxl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xxl-0 {\n    padding-right: 0 !important;\n  }\n  .pe-xxl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-xxl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-xxl-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-xxl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-xxl-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-xxl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xxl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xxl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xxl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xxl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xxl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xxl-0 {\n    padding-left: 0 !important;\n  }\n  .ps-xxl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-xxl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-xxl-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-xxl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-xxl-5 {\n    padding-left: 3rem !important;\n  }\n}\n@media print {\n  .d-print-inline {\n    display: inline !important;\n  }\n  .d-print-inline-block {\n    display: inline-block !important;\n  }\n  .d-print-block {\n    display: block !important;\n  }\n  .d-print-grid {\n    display: grid !important;\n  }\n  .d-print-table {\n    display: table !important;\n  }\n  .d-print-table-row {\n    display: table-row !important;\n  }\n  .d-print-table-cell {\n    display: table-cell !important;\n  }\n  .d-print-flex {\n    display: flex !important;\n  }\n  .d-print-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-print-none {\n    display: none !important;\n  }\n}\n\n/*# sourceMappingURL=bootstrap-grid.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap-grid.rtl.css",
    "content": "/*!\n * Bootstrap Grid v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n.container,\n.container-fluid,\n.container-xxl,\n.container-xl,\n.container-lg,\n.container-md,\n.container-sm {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  width: 100%;\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  margin-left: auto;\n  margin-right: auto;\n}\n\n@media (min-width: 576px) {\n  .container-sm, .container {\n    max-width: 540px;\n  }\n}\n@media (min-width: 768px) {\n  .container-md, .container-sm, .container {\n    max-width: 720px;\n  }\n}\n@media (min-width: 992px) {\n  .container-lg, .container-md, .container-sm, .container {\n    max-width: 960px;\n  }\n}\n@media (min-width: 1200px) {\n  .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1140px;\n  }\n}\n@media (min-width: 1400px) {\n  .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1320px;\n  }\n}\n.row {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  display: flex;\n  flex-wrap: wrap;\n  margin-top: calc(-1 * var(--bs-gutter-y));\n  margin-left: calc(-0.5 * var(--bs-gutter-x));\n  margin-right: calc(-0.5 * var(--bs-gutter-x));\n}\n.row > * {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 100%;\n  max-width: 100%;\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  margin-top: var(--bs-gutter-y);\n}\n\n.col {\n  flex: 1 0 0%;\n}\n\n.row-cols-auto > * {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.row-cols-1 > * {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.row-cols-2 > * {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.row-cols-3 > * {\n  flex: 0 0 auto;\n  width: 33.3333333333%;\n}\n\n.row-cols-4 > * {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.row-cols-5 > * {\n  flex: 0 0 auto;\n  width: 20%;\n}\n\n.row-cols-6 > * {\n  flex: 0 0 auto;\n  width: 16.6666666667%;\n}\n\n.col-auto {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.col-1 {\n  flex: 0 0 auto;\n  width: 8.33333333%;\n}\n\n.col-2 {\n  flex: 0 0 auto;\n  width: 16.66666667%;\n}\n\n.col-3 {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.col-4 {\n  flex: 0 0 auto;\n  width: 33.33333333%;\n}\n\n.col-5 {\n  flex: 0 0 auto;\n  width: 41.66666667%;\n}\n\n.col-6 {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.col-7 {\n  flex: 0 0 auto;\n  width: 58.33333333%;\n}\n\n.col-8 {\n  flex: 0 0 auto;\n  width: 66.66666667%;\n}\n\n.col-9 {\n  flex: 0 0 auto;\n  width: 75%;\n}\n\n.col-10 {\n  flex: 0 0 auto;\n  width: 83.33333333%;\n}\n\n.col-11 {\n  flex: 0 0 auto;\n  width: 91.66666667%;\n}\n\n.col-12 {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.offset-1 {\n  margin-right: 8.33333333%;\n}\n\n.offset-2 {\n  margin-right: 16.66666667%;\n}\n\n.offset-3 {\n  margin-right: 25%;\n}\n\n.offset-4 {\n  margin-right: 33.33333333%;\n}\n\n.offset-5 {\n  margin-right: 41.66666667%;\n}\n\n.offset-6 {\n  margin-right: 50%;\n}\n\n.offset-7 {\n  margin-right: 58.33333333%;\n}\n\n.offset-8 {\n  margin-right: 66.66666667%;\n}\n\n.offset-9 {\n  margin-right: 75%;\n}\n\n.offset-10 {\n  margin-right: 83.33333333%;\n}\n\n.offset-11 {\n  margin-right: 91.66666667%;\n}\n\n.g-0,\n.gx-0 {\n  --bs-gutter-x: 0;\n}\n\n.g-0,\n.gy-0 {\n  --bs-gutter-y: 0;\n}\n\n.g-1,\n.gx-1 {\n  --bs-gutter-x: 0.25rem;\n}\n\n.g-1,\n.gy-1 {\n  --bs-gutter-y: 0.25rem;\n}\n\n.g-2,\n.gx-2 {\n  --bs-gutter-x: 0.5rem;\n}\n\n.g-2,\n.gy-2 {\n  --bs-gutter-y: 0.5rem;\n}\n\n.g-3,\n.gx-3 {\n  --bs-gutter-x: 1rem;\n}\n\n.g-3,\n.gy-3 {\n  --bs-gutter-y: 1rem;\n}\n\n.g-4,\n.gx-4 {\n  --bs-gutter-x: 1.5rem;\n}\n\n.g-4,\n.gy-4 {\n  --bs-gutter-y: 1.5rem;\n}\n\n.g-5,\n.gx-5 {\n  --bs-gutter-x: 3rem;\n}\n\n.g-5,\n.gy-5 {\n  --bs-gutter-y: 3rem;\n}\n\n@media (min-width: 576px) {\n  .col-sm {\n    flex: 1 0 0%;\n  }\n  .row-cols-sm-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-sm-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-sm-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-sm-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-sm-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-sm-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-sm-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-sm-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-sm-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-sm-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-sm-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-sm-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-sm-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-sm-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-sm-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-sm-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-sm-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-sm-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-sm-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-sm-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-sm-0 {\n    margin-right: 0;\n  }\n  .offset-sm-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-sm-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-sm-3 {\n    margin-right: 25%;\n  }\n  .offset-sm-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-sm-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-sm-6 {\n    margin-right: 50%;\n  }\n  .offset-sm-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-sm-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-sm-9 {\n    margin-right: 75%;\n  }\n  .offset-sm-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-sm-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-sm-0,\n.gx-sm-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-sm-0,\n.gy-sm-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-sm-1,\n.gx-sm-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-sm-1,\n.gy-sm-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-sm-2,\n.gx-sm-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-sm-2,\n.gy-sm-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-sm-3,\n.gx-sm-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-sm-3,\n.gy-sm-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-sm-4,\n.gx-sm-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-sm-4,\n.gy-sm-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-sm-5,\n.gx-sm-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-sm-5,\n.gy-sm-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 768px) {\n  .col-md {\n    flex: 1 0 0%;\n  }\n  .row-cols-md-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-md-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-md-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-md-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-md-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-md-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-md-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-md-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-md-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-md-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-md-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-md-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-md-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-md-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-md-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-md-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-md-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-md-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-md-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-md-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-md-0 {\n    margin-right: 0;\n  }\n  .offset-md-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-md-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-md-3 {\n    margin-right: 25%;\n  }\n  .offset-md-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-md-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-md-6 {\n    margin-right: 50%;\n  }\n  .offset-md-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-md-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-md-9 {\n    margin-right: 75%;\n  }\n  .offset-md-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-md-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-md-0,\n.gx-md-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-md-0,\n.gy-md-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-md-1,\n.gx-md-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-md-1,\n.gy-md-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-md-2,\n.gx-md-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-md-2,\n.gy-md-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-md-3,\n.gx-md-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-md-3,\n.gy-md-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-md-4,\n.gx-md-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-md-4,\n.gy-md-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-md-5,\n.gx-md-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-md-5,\n.gy-md-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 992px) {\n  .col-lg {\n    flex: 1 0 0%;\n  }\n  .row-cols-lg-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-lg-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-lg-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-lg-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-lg-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-lg-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-lg-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-lg-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-lg-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-lg-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-lg-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-lg-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-lg-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-lg-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-lg-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-lg-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-lg-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-lg-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-lg-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-lg-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-lg-0 {\n    margin-right: 0;\n  }\n  .offset-lg-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-lg-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-lg-3 {\n    margin-right: 25%;\n  }\n  .offset-lg-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-lg-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-lg-6 {\n    margin-right: 50%;\n  }\n  .offset-lg-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-lg-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-lg-9 {\n    margin-right: 75%;\n  }\n  .offset-lg-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-lg-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-lg-0,\n.gx-lg-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-lg-0,\n.gy-lg-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-lg-1,\n.gx-lg-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-lg-1,\n.gy-lg-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-lg-2,\n.gx-lg-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-lg-2,\n.gy-lg-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-lg-3,\n.gx-lg-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-lg-3,\n.gy-lg-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-lg-4,\n.gx-lg-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-lg-4,\n.gy-lg-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-lg-5,\n.gx-lg-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-lg-5,\n.gy-lg-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1200px) {\n  .col-xl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xl-0 {\n    margin-right: 0;\n  }\n  .offset-xl-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-xl-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-xl-3 {\n    margin-right: 25%;\n  }\n  .offset-xl-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-xl-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-xl-6 {\n    margin-right: 50%;\n  }\n  .offset-xl-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-xl-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-xl-9 {\n    margin-right: 75%;\n  }\n  .offset-xl-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-xl-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-xl-0,\n.gx-xl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xl-0,\n.gy-xl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xl-1,\n.gx-xl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xl-1,\n.gy-xl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xl-2,\n.gx-xl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xl-2,\n.gy-xl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xl-3,\n.gx-xl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xl-3,\n.gy-xl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xl-4,\n.gx-xl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xl-4,\n.gy-xl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xl-5,\n.gx-xl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xl-5,\n.gy-xl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1400px) {\n  .col-xxl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xxl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xxl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xxl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xxl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xxl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xxl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xxl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xxl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xxl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xxl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xxl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xxl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xxl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xxl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xxl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xxl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xxl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xxl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xxl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xxl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xxl-0 {\n    margin-right: 0;\n  }\n  .offset-xxl-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-xxl-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-xxl-3 {\n    margin-right: 25%;\n  }\n  .offset-xxl-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-xxl-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-xxl-6 {\n    margin-right: 50%;\n  }\n  .offset-xxl-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-xxl-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-xxl-9 {\n    margin-right: 75%;\n  }\n  .offset-xxl-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-xxl-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-xxl-0,\n.gx-xxl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xxl-0,\n.gy-xxl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xxl-1,\n.gx-xxl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xxl-1,\n.gy-xxl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xxl-2,\n.gx-xxl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xxl-2,\n.gy-xxl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xxl-3,\n.gx-xxl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xxl-3,\n.gy-xxl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xxl-4,\n.gx-xxl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xxl-4,\n.gy-xxl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xxl-5,\n.gx-xxl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xxl-5,\n.gy-xxl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n.d-inline {\n  display: inline !important;\n}\n\n.d-inline-block {\n  display: inline-block !important;\n}\n\n.d-block {\n  display: block !important;\n}\n\n.d-grid {\n  display: grid !important;\n}\n\n.d-table {\n  display: table !important;\n}\n\n.d-table-row {\n  display: table-row !important;\n}\n\n.d-table-cell {\n  display: table-cell !important;\n}\n\n.d-flex {\n  display: flex !important;\n}\n\n.d-inline-flex {\n  display: inline-flex !important;\n}\n\n.d-none {\n  display: none !important;\n}\n\n.flex-fill {\n  flex: 1 1 auto !important;\n}\n\n.flex-row {\n  flex-direction: row !important;\n}\n\n.flex-column {\n  flex-direction: column !important;\n}\n\n.flex-row-reverse {\n  flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n  flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n  flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n  flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n  flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n  flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n  justify-content: flex-start !important;\n}\n\n.justify-content-end {\n  justify-content: flex-end !important;\n}\n\n.justify-content-center {\n  justify-content: center !important;\n}\n\n.justify-content-between {\n  justify-content: space-between !important;\n}\n\n.justify-content-around {\n  justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n  justify-content: space-evenly !important;\n}\n\n.align-items-start {\n  align-items: flex-start !important;\n}\n\n.align-items-end {\n  align-items: flex-end !important;\n}\n\n.align-items-center {\n  align-items: center !important;\n}\n\n.align-items-baseline {\n  align-items: baseline !important;\n}\n\n.align-items-stretch {\n  align-items: stretch !important;\n}\n\n.align-content-start {\n  align-content: flex-start !important;\n}\n\n.align-content-end {\n  align-content: flex-end !important;\n}\n\n.align-content-center {\n  align-content: center !important;\n}\n\n.align-content-between {\n  align-content: space-between !important;\n}\n\n.align-content-around {\n  align-content: space-around !important;\n}\n\n.align-content-stretch {\n  align-content: stretch !important;\n}\n\n.align-self-auto {\n  align-self: auto !important;\n}\n\n.align-self-start {\n  align-self: flex-start !important;\n}\n\n.align-self-end {\n  align-self: flex-end !important;\n}\n\n.align-self-center {\n  align-self: center !important;\n}\n\n.align-self-baseline {\n  align-self: baseline !important;\n}\n\n.align-self-stretch {\n  align-self: stretch !important;\n}\n\n.order-first {\n  order: -1 !important;\n}\n\n.order-0 {\n  order: 0 !important;\n}\n\n.order-1 {\n  order: 1 !important;\n}\n\n.order-2 {\n  order: 2 !important;\n}\n\n.order-3 {\n  order: 3 !important;\n}\n\n.order-4 {\n  order: 4 !important;\n}\n\n.order-5 {\n  order: 5 !important;\n}\n\n.order-last {\n  order: 6 !important;\n}\n\n.m-0 {\n  margin: 0 !important;\n}\n\n.m-1 {\n  margin: 0.25rem !important;\n}\n\n.m-2 {\n  margin: 0.5rem !important;\n}\n\n.m-3 {\n  margin: 1rem !important;\n}\n\n.m-4 {\n  margin: 1.5rem !important;\n}\n\n.m-5 {\n  margin: 3rem !important;\n}\n\n.m-auto {\n  margin: auto !important;\n}\n\n.mx-0 {\n  margin-left: 0 !important;\n  margin-right: 0 !important;\n}\n\n.mx-1 {\n  margin-left: 0.25rem !important;\n  margin-right: 0.25rem !important;\n}\n\n.mx-2 {\n  margin-left: 0.5rem !important;\n  margin-right: 0.5rem !important;\n}\n\n.mx-3 {\n  margin-left: 1rem !important;\n  margin-right: 1rem !important;\n}\n\n.mx-4 {\n  margin-left: 1.5rem !important;\n  margin-right: 1.5rem !important;\n}\n\n.mx-5 {\n  margin-left: 3rem !important;\n  margin-right: 3rem !important;\n}\n\n.mx-auto {\n  margin-left: auto !important;\n  margin-right: auto !important;\n}\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important;\n}\n\n.my-4 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important;\n}\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important;\n}\n\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.mt-1 {\n  margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n  margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n  margin-top: 1rem !important;\n}\n\n.mt-4 {\n  margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n  margin-top: 3rem !important;\n}\n\n.mt-auto {\n  margin-top: auto !important;\n}\n\n.me-0 {\n  margin-left: 0 !important;\n}\n\n.me-1 {\n  margin-left: 0.25rem !important;\n}\n\n.me-2 {\n  margin-left: 0.5rem !important;\n}\n\n.me-3 {\n  margin-left: 1rem !important;\n}\n\n.me-4 {\n  margin-left: 1.5rem !important;\n}\n\n.me-5 {\n  margin-left: 3rem !important;\n}\n\n.me-auto {\n  margin-left: auto !important;\n}\n\n.mb-0 {\n  margin-bottom: 0 !important;\n}\n\n.mb-1 {\n  margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n  margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n  margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n  margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n  margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n  margin-bottom: auto !important;\n}\n\n.ms-0 {\n  margin-right: 0 !important;\n}\n\n.ms-1 {\n  margin-right: 0.25rem !important;\n}\n\n.ms-2 {\n  margin-right: 0.5rem !important;\n}\n\n.ms-3 {\n  margin-right: 1rem !important;\n}\n\n.ms-4 {\n  margin-right: 1.5rem !important;\n}\n\n.ms-5 {\n  margin-right: 3rem !important;\n}\n\n.ms-auto {\n  margin-right: auto !important;\n}\n\n.p-0 {\n  padding: 0 !important;\n}\n\n.p-1 {\n  padding: 0.25rem !important;\n}\n\n.p-2 {\n  padding: 0.5rem !important;\n}\n\n.p-3 {\n  padding: 1rem !important;\n}\n\n.p-4 {\n  padding: 1.5rem !important;\n}\n\n.p-5 {\n  padding: 3rem !important;\n}\n\n.px-0 {\n  padding-left: 0 !important;\n  padding-right: 0 !important;\n}\n\n.px-1 {\n  padding-left: 0.25rem !important;\n  padding-right: 0.25rem !important;\n}\n\n.px-2 {\n  padding-left: 0.5rem !important;\n  padding-right: 0.5rem !important;\n}\n\n.px-3 {\n  padding-left: 1rem !important;\n  padding-right: 1rem !important;\n}\n\n.px-4 {\n  padding-left: 1.5rem !important;\n  padding-right: 1.5rem !important;\n}\n\n.px-5 {\n  padding-left: 3rem !important;\n  padding-right: 3rem !important;\n}\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n\n.py-4 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n  padding-top: 0 !important;\n}\n\n.pt-1 {\n  padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n  padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n  padding-top: 1rem !important;\n}\n\n.pt-4 {\n  padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n  padding-top: 3rem !important;\n}\n\n.pe-0 {\n  padding-left: 0 !important;\n}\n\n.pe-1 {\n  padding-left: 0.25rem !important;\n}\n\n.pe-2 {\n  padding-left: 0.5rem !important;\n}\n\n.pe-3 {\n  padding-left: 1rem !important;\n}\n\n.pe-4 {\n  padding-left: 1.5rem !important;\n}\n\n.pe-5 {\n  padding-left: 3rem !important;\n}\n\n.pb-0 {\n  padding-bottom: 0 !important;\n}\n\n.pb-1 {\n  padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n  padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n  padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n  padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n  padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n  padding-right: 0 !important;\n}\n\n.ps-1 {\n  padding-right: 0.25rem !important;\n}\n\n.ps-2 {\n  padding-right: 0.5rem !important;\n}\n\n.ps-3 {\n  padding-right: 1rem !important;\n}\n\n.ps-4 {\n  padding-right: 1.5rem !important;\n}\n\n.ps-5 {\n  padding-right: 3rem !important;\n}\n\n@media (min-width: 576px) {\n  .d-sm-inline {\n    display: inline !important;\n  }\n  .d-sm-inline-block {\n    display: inline-block !important;\n  }\n  .d-sm-block {\n    display: block !important;\n  }\n  .d-sm-grid {\n    display: grid !important;\n  }\n  .d-sm-table {\n    display: table !important;\n  }\n  .d-sm-table-row {\n    display: table-row !important;\n  }\n  .d-sm-table-cell {\n    display: table-cell !important;\n  }\n  .d-sm-flex {\n    display: flex !important;\n  }\n  .d-sm-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-sm-none {\n    display: none !important;\n  }\n  .flex-sm-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-sm-row {\n    flex-direction: row !important;\n  }\n  .flex-sm-column {\n    flex-direction: column !important;\n  }\n  .flex-sm-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-sm-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-sm-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-sm-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-sm-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-sm-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-sm-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-sm-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-sm-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-sm-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-sm-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-sm-center {\n    justify-content: center !important;\n  }\n  .justify-content-sm-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-sm-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-sm-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-sm-start {\n    align-items: flex-start !important;\n  }\n  .align-items-sm-end {\n    align-items: flex-end !important;\n  }\n  .align-items-sm-center {\n    align-items: center !important;\n  }\n  .align-items-sm-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-sm-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-sm-start {\n    align-content: flex-start !important;\n  }\n  .align-content-sm-end {\n    align-content: flex-end !important;\n  }\n  .align-content-sm-center {\n    align-content: center !important;\n  }\n  .align-content-sm-between {\n    align-content: space-between !important;\n  }\n  .align-content-sm-around {\n    align-content: space-around !important;\n  }\n  .align-content-sm-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-sm-auto {\n    align-self: auto !important;\n  }\n  .align-self-sm-start {\n    align-self: flex-start !important;\n  }\n  .align-self-sm-end {\n    align-self: flex-end !important;\n  }\n  .align-self-sm-center {\n    align-self: center !important;\n  }\n  .align-self-sm-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-sm-stretch {\n    align-self: stretch !important;\n  }\n  .order-sm-first {\n    order: -1 !important;\n  }\n  .order-sm-0 {\n    order: 0 !important;\n  }\n  .order-sm-1 {\n    order: 1 !important;\n  }\n  .order-sm-2 {\n    order: 2 !important;\n  }\n  .order-sm-3 {\n    order: 3 !important;\n  }\n  .order-sm-4 {\n    order: 4 !important;\n  }\n  .order-sm-5 {\n    order: 5 !important;\n  }\n  .order-sm-last {\n    order: 6 !important;\n  }\n  .m-sm-0 {\n    margin: 0 !important;\n  }\n  .m-sm-1 {\n    margin: 0.25rem !important;\n  }\n  .m-sm-2 {\n    margin: 0.5rem !important;\n  }\n  .m-sm-3 {\n    margin: 1rem !important;\n  }\n  .m-sm-4 {\n    margin: 1.5rem !important;\n  }\n  .m-sm-5 {\n    margin: 3rem !important;\n  }\n  .m-sm-auto {\n    margin: auto !important;\n  }\n  .mx-sm-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-sm-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-sm-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-sm-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-sm-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-sm-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-sm-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-sm-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-sm-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-sm-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-sm-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-sm-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-sm-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-sm-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-sm-0 {\n    margin-top: 0 !important;\n  }\n  .mt-sm-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-sm-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-sm-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-sm-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-sm-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-sm-auto {\n    margin-top: auto !important;\n  }\n  .me-sm-0 {\n    margin-left: 0 !important;\n  }\n  .me-sm-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-sm-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-sm-3 {\n    margin-left: 1rem !important;\n  }\n  .me-sm-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-sm-5 {\n    margin-left: 3rem !important;\n  }\n  .me-sm-auto {\n    margin-left: auto !important;\n  }\n  .mb-sm-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-sm-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-sm-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-sm-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-sm-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-sm-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-sm-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-sm-0 {\n    margin-right: 0 !important;\n  }\n  .ms-sm-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-sm-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-sm-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-sm-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-sm-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-sm-auto {\n    margin-right: auto !important;\n  }\n  .p-sm-0 {\n    padding: 0 !important;\n  }\n  .p-sm-1 {\n    padding: 0.25rem !important;\n  }\n  .p-sm-2 {\n    padding: 0.5rem !important;\n  }\n  .p-sm-3 {\n    padding: 1rem !important;\n  }\n  .p-sm-4 {\n    padding: 1.5rem !important;\n  }\n  .p-sm-5 {\n    padding: 3rem !important;\n  }\n  .px-sm-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-sm-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-sm-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-sm-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-sm-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-sm-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-sm-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-sm-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-sm-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-sm-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-sm-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-sm-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-sm-0 {\n    padding-top: 0 !important;\n  }\n  .pt-sm-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-sm-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-sm-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-sm-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-sm-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-sm-0 {\n    padding-left: 0 !important;\n  }\n  .pe-sm-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-sm-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-sm-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-sm-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-sm-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-sm-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-sm-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-sm-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-sm-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-sm-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-sm-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-sm-0 {\n    padding-right: 0 !important;\n  }\n  .ps-sm-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-sm-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-sm-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-sm-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-sm-5 {\n    padding-right: 3rem !important;\n  }\n}\n@media (min-width: 768px) {\n  .d-md-inline {\n    display: inline !important;\n  }\n  .d-md-inline-block {\n    display: inline-block !important;\n  }\n  .d-md-block {\n    display: block !important;\n  }\n  .d-md-grid {\n    display: grid !important;\n  }\n  .d-md-table {\n    display: table !important;\n  }\n  .d-md-table-row {\n    display: table-row !important;\n  }\n  .d-md-table-cell {\n    display: table-cell !important;\n  }\n  .d-md-flex {\n    display: flex !important;\n  }\n  .d-md-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-md-none {\n    display: none !important;\n  }\n  .flex-md-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-md-row {\n    flex-direction: row !important;\n  }\n  .flex-md-column {\n    flex-direction: column !important;\n  }\n  .flex-md-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-md-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-md-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-md-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-md-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-md-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-md-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-md-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-md-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-md-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-md-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-md-center {\n    justify-content: center !important;\n  }\n  .justify-content-md-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-md-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-md-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-md-start {\n    align-items: flex-start !important;\n  }\n  .align-items-md-end {\n    align-items: flex-end !important;\n  }\n  .align-items-md-center {\n    align-items: center !important;\n  }\n  .align-items-md-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-md-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-md-start {\n    align-content: flex-start !important;\n  }\n  .align-content-md-end {\n    align-content: flex-end !important;\n  }\n  .align-content-md-center {\n    align-content: center !important;\n  }\n  .align-content-md-between {\n    align-content: space-between !important;\n  }\n  .align-content-md-around {\n    align-content: space-around !important;\n  }\n  .align-content-md-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-md-auto {\n    align-self: auto !important;\n  }\n  .align-self-md-start {\n    align-self: flex-start !important;\n  }\n  .align-self-md-end {\n    align-self: flex-end !important;\n  }\n  .align-self-md-center {\n    align-self: center !important;\n  }\n  .align-self-md-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-md-stretch {\n    align-self: stretch !important;\n  }\n  .order-md-first {\n    order: -1 !important;\n  }\n  .order-md-0 {\n    order: 0 !important;\n  }\n  .order-md-1 {\n    order: 1 !important;\n  }\n  .order-md-2 {\n    order: 2 !important;\n  }\n  .order-md-3 {\n    order: 3 !important;\n  }\n  .order-md-4 {\n    order: 4 !important;\n  }\n  .order-md-5 {\n    order: 5 !important;\n  }\n  .order-md-last {\n    order: 6 !important;\n  }\n  .m-md-0 {\n    margin: 0 !important;\n  }\n  .m-md-1 {\n    margin: 0.25rem !important;\n  }\n  .m-md-2 {\n    margin: 0.5rem !important;\n  }\n  .m-md-3 {\n    margin: 1rem !important;\n  }\n  .m-md-4 {\n    margin: 1.5rem !important;\n  }\n  .m-md-5 {\n    margin: 3rem !important;\n  }\n  .m-md-auto {\n    margin: auto !important;\n  }\n  .mx-md-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-md-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-md-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-md-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-md-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-md-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-md-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-md-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-md-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-md-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-md-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-md-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-md-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-md-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-md-0 {\n    margin-top: 0 !important;\n  }\n  .mt-md-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-md-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-md-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-md-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-md-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-md-auto {\n    margin-top: auto !important;\n  }\n  .me-md-0 {\n    margin-left: 0 !important;\n  }\n  .me-md-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-md-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-md-3 {\n    margin-left: 1rem !important;\n  }\n  .me-md-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-md-5 {\n    margin-left: 3rem !important;\n  }\n  .me-md-auto {\n    margin-left: auto !important;\n  }\n  .mb-md-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-md-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-md-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-md-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-md-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-md-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-md-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-md-0 {\n    margin-right: 0 !important;\n  }\n  .ms-md-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-md-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-md-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-md-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-md-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-md-auto {\n    margin-right: auto !important;\n  }\n  .p-md-0 {\n    padding: 0 !important;\n  }\n  .p-md-1 {\n    padding: 0.25rem !important;\n  }\n  .p-md-2 {\n    padding: 0.5rem !important;\n  }\n  .p-md-3 {\n    padding: 1rem !important;\n  }\n  .p-md-4 {\n    padding: 1.5rem !important;\n  }\n  .p-md-5 {\n    padding: 3rem !important;\n  }\n  .px-md-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-md-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-md-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-md-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-md-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-md-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-md-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-md-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-md-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-md-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-md-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-md-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-md-0 {\n    padding-top: 0 !important;\n  }\n  .pt-md-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-md-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-md-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-md-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-md-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-md-0 {\n    padding-left: 0 !important;\n  }\n  .pe-md-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-md-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-md-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-md-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-md-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-md-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-md-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-md-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-md-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-md-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-md-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-md-0 {\n    padding-right: 0 !important;\n  }\n  .ps-md-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-md-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-md-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-md-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-md-5 {\n    padding-right: 3rem !important;\n  }\n}\n@media (min-width: 992px) {\n  .d-lg-inline {\n    display: inline !important;\n  }\n  .d-lg-inline-block {\n    display: inline-block !important;\n  }\n  .d-lg-block {\n    display: block !important;\n  }\n  .d-lg-grid {\n    display: grid !important;\n  }\n  .d-lg-table {\n    display: table !important;\n  }\n  .d-lg-table-row {\n    display: table-row !important;\n  }\n  .d-lg-table-cell {\n    display: table-cell !important;\n  }\n  .d-lg-flex {\n    display: flex !important;\n  }\n  .d-lg-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-lg-none {\n    display: none !important;\n  }\n  .flex-lg-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-lg-row {\n    flex-direction: row !important;\n  }\n  .flex-lg-column {\n    flex-direction: column !important;\n  }\n  .flex-lg-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-lg-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-lg-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-lg-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-lg-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-lg-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-lg-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-lg-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-lg-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-lg-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-lg-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-lg-center {\n    justify-content: center !important;\n  }\n  .justify-content-lg-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-lg-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-lg-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-lg-start {\n    align-items: flex-start !important;\n  }\n  .align-items-lg-end {\n    align-items: flex-end !important;\n  }\n  .align-items-lg-center {\n    align-items: center !important;\n  }\n  .align-items-lg-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-lg-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-lg-start {\n    align-content: flex-start !important;\n  }\n  .align-content-lg-end {\n    align-content: flex-end !important;\n  }\n  .align-content-lg-center {\n    align-content: center !important;\n  }\n  .align-content-lg-between {\n    align-content: space-between !important;\n  }\n  .align-content-lg-around {\n    align-content: space-around !important;\n  }\n  .align-content-lg-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-lg-auto {\n    align-self: auto !important;\n  }\n  .align-self-lg-start {\n    align-self: flex-start !important;\n  }\n  .align-self-lg-end {\n    align-self: flex-end !important;\n  }\n  .align-self-lg-center {\n    align-self: center !important;\n  }\n  .align-self-lg-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-lg-stretch {\n    align-self: stretch !important;\n  }\n  .order-lg-first {\n    order: -1 !important;\n  }\n  .order-lg-0 {\n    order: 0 !important;\n  }\n  .order-lg-1 {\n    order: 1 !important;\n  }\n  .order-lg-2 {\n    order: 2 !important;\n  }\n  .order-lg-3 {\n    order: 3 !important;\n  }\n  .order-lg-4 {\n    order: 4 !important;\n  }\n  .order-lg-5 {\n    order: 5 !important;\n  }\n  .order-lg-last {\n    order: 6 !important;\n  }\n  .m-lg-0 {\n    margin: 0 !important;\n  }\n  .m-lg-1 {\n    margin: 0.25rem !important;\n  }\n  .m-lg-2 {\n    margin: 0.5rem !important;\n  }\n  .m-lg-3 {\n    margin: 1rem !important;\n  }\n  .m-lg-4 {\n    margin: 1.5rem !important;\n  }\n  .m-lg-5 {\n    margin: 3rem !important;\n  }\n  .m-lg-auto {\n    margin: auto !important;\n  }\n  .mx-lg-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-lg-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-lg-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-lg-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-lg-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-lg-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-lg-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-lg-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-lg-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-lg-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-lg-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-lg-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-lg-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-lg-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-lg-0 {\n    margin-top: 0 !important;\n  }\n  .mt-lg-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-lg-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-lg-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-lg-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-lg-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-lg-auto {\n    margin-top: auto !important;\n  }\n  .me-lg-0 {\n    margin-left: 0 !important;\n  }\n  .me-lg-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-lg-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-lg-3 {\n    margin-left: 1rem !important;\n  }\n  .me-lg-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-lg-5 {\n    margin-left: 3rem !important;\n  }\n  .me-lg-auto {\n    margin-left: auto !important;\n  }\n  .mb-lg-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-lg-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-lg-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-lg-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-lg-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-lg-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-lg-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-lg-0 {\n    margin-right: 0 !important;\n  }\n  .ms-lg-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-lg-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-lg-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-lg-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-lg-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-lg-auto {\n    margin-right: auto !important;\n  }\n  .p-lg-0 {\n    padding: 0 !important;\n  }\n  .p-lg-1 {\n    padding: 0.25rem !important;\n  }\n  .p-lg-2 {\n    padding: 0.5rem !important;\n  }\n  .p-lg-3 {\n    padding: 1rem !important;\n  }\n  .p-lg-4 {\n    padding: 1.5rem !important;\n  }\n  .p-lg-5 {\n    padding: 3rem !important;\n  }\n  .px-lg-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-lg-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-lg-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-lg-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-lg-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-lg-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-lg-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-lg-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-lg-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-lg-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-lg-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-lg-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-lg-0 {\n    padding-top: 0 !important;\n  }\n  .pt-lg-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-lg-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-lg-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-lg-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-lg-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-lg-0 {\n    padding-left: 0 !important;\n  }\n  .pe-lg-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-lg-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-lg-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-lg-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-lg-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-lg-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-lg-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-lg-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-lg-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-lg-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-lg-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-lg-0 {\n    padding-right: 0 !important;\n  }\n  .ps-lg-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-lg-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-lg-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-lg-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-lg-5 {\n    padding-right: 3rem !important;\n  }\n}\n@media (min-width: 1200px) {\n  .d-xl-inline {\n    display: inline !important;\n  }\n  .d-xl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xl-block {\n    display: block !important;\n  }\n  .d-xl-grid {\n    display: grid !important;\n  }\n  .d-xl-table {\n    display: table !important;\n  }\n  .d-xl-table-row {\n    display: table-row !important;\n  }\n  .d-xl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xl-flex {\n    display: flex !important;\n  }\n  .d-xl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xl-none {\n    display: none !important;\n  }\n  .flex-xl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xl-row {\n    flex-direction: row !important;\n  }\n  .flex-xl-column {\n    flex-direction: column !important;\n  }\n  .flex-xl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xl-center {\n    align-items: center !important;\n  }\n  .align-items-xl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xl-center {\n    align-content: center !important;\n  }\n  .align-content-xl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xl-center {\n    align-self: center !important;\n  }\n  .align-self-xl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xl-first {\n    order: -1 !important;\n  }\n  .order-xl-0 {\n    order: 0 !important;\n  }\n  .order-xl-1 {\n    order: 1 !important;\n  }\n  .order-xl-2 {\n    order: 2 !important;\n  }\n  .order-xl-3 {\n    order: 3 !important;\n  }\n  .order-xl-4 {\n    order: 4 !important;\n  }\n  .order-xl-5 {\n    order: 5 !important;\n  }\n  .order-xl-last {\n    order: 6 !important;\n  }\n  .m-xl-0 {\n    margin: 0 !important;\n  }\n  .m-xl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xl-3 {\n    margin: 1rem !important;\n  }\n  .m-xl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xl-5 {\n    margin: 3rem !important;\n  }\n  .m-xl-auto {\n    margin: auto !important;\n  }\n  .mx-xl-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-xl-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-xl-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-xl-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-xl-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-xl-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-xl-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-xl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xl-auto {\n    margin-top: auto !important;\n  }\n  .me-xl-0 {\n    margin-left: 0 !important;\n  }\n  .me-xl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-xl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-xl-3 {\n    margin-left: 1rem !important;\n  }\n  .me-xl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-xl-5 {\n    margin-left: 3rem !important;\n  }\n  .me-xl-auto {\n    margin-left: auto !important;\n  }\n  .mb-xl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xl-0 {\n    margin-right: 0 !important;\n  }\n  .ms-xl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-xl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-xl-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-xl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-xl-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-xl-auto {\n    margin-right: auto !important;\n  }\n  .p-xl-0 {\n    padding: 0 !important;\n  }\n  .p-xl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xl-3 {\n    padding: 1rem !important;\n  }\n  .p-xl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xl-5 {\n    padding: 3rem !important;\n  }\n  .px-xl-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-xl-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-xl-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-xl-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-xl-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-xl-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-xl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xl-0 {\n    padding-left: 0 !important;\n  }\n  .pe-xl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-xl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-xl-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-xl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-xl-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-xl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xl-0 {\n    padding-right: 0 !important;\n  }\n  .ps-xl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-xl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-xl-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-xl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-xl-5 {\n    padding-right: 3rem !important;\n  }\n}\n@media (min-width: 1400px) {\n  .d-xxl-inline {\n    display: inline !important;\n  }\n  .d-xxl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xxl-block {\n    display: block !important;\n  }\n  .d-xxl-grid {\n    display: grid !important;\n  }\n  .d-xxl-table {\n    display: table !important;\n  }\n  .d-xxl-table-row {\n    display: table-row !important;\n  }\n  .d-xxl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xxl-flex {\n    display: flex !important;\n  }\n  .d-xxl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xxl-none {\n    display: none !important;\n  }\n  .flex-xxl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xxl-row {\n    flex-direction: row !important;\n  }\n  .flex-xxl-column {\n    flex-direction: column !important;\n  }\n  .flex-xxl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xxl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xxl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xxl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xxl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xxl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xxl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xxl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xxl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xxl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xxl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xxl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xxl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xxl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xxl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xxl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xxl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xxl-center {\n    align-items: center !important;\n  }\n  .align-items-xxl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xxl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xxl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xxl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xxl-center {\n    align-content: center !important;\n  }\n  .align-content-xxl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xxl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xxl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xxl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xxl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xxl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xxl-center {\n    align-self: center !important;\n  }\n  .align-self-xxl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xxl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xxl-first {\n    order: -1 !important;\n  }\n  .order-xxl-0 {\n    order: 0 !important;\n  }\n  .order-xxl-1 {\n    order: 1 !important;\n  }\n  .order-xxl-2 {\n    order: 2 !important;\n  }\n  .order-xxl-3 {\n    order: 3 !important;\n  }\n  .order-xxl-4 {\n    order: 4 !important;\n  }\n  .order-xxl-5 {\n    order: 5 !important;\n  }\n  .order-xxl-last {\n    order: 6 !important;\n  }\n  .m-xxl-0 {\n    margin: 0 !important;\n  }\n  .m-xxl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xxl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xxl-3 {\n    margin: 1rem !important;\n  }\n  .m-xxl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xxl-5 {\n    margin: 3rem !important;\n  }\n  .m-xxl-auto {\n    margin: auto !important;\n  }\n  .mx-xxl-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-xxl-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-xxl-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-xxl-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-xxl-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-xxl-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-xxl-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-xxl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xxl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xxl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xxl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xxl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xxl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xxl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xxl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xxl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xxl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xxl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xxl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xxl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xxl-auto {\n    margin-top: auto !important;\n  }\n  .me-xxl-0 {\n    margin-left: 0 !important;\n  }\n  .me-xxl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-xxl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-xxl-3 {\n    margin-left: 1rem !important;\n  }\n  .me-xxl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-xxl-5 {\n    margin-left: 3rem !important;\n  }\n  .me-xxl-auto {\n    margin-left: auto !important;\n  }\n  .mb-xxl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xxl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xxl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xxl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xxl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xxl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xxl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xxl-0 {\n    margin-right: 0 !important;\n  }\n  .ms-xxl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-xxl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-xxl-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-xxl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-xxl-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-xxl-auto {\n    margin-right: auto !important;\n  }\n  .p-xxl-0 {\n    padding: 0 !important;\n  }\n  .p-xxl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xxl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xxl-3 {\n    padding: 1rem !important;\n  }\n  .p-xxl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xxl-5 {\n    padding: 3rem !important;\n  }\n  .px-xxl-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-xxl-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-xxl-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-xxl-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-xxl-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-xxl-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-xxl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xxl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xxl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xxl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xxl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xxl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xxl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xxl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xxl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xxl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xxl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xxl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xxl-0 {\n    padding-left: 0 !important;\n  }\n  .pe-xxl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-xxl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-xxl-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-xxl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-xxl-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-xxl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xxl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xxl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xxl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xxl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xxl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xxl-0 {\n    padding-right: 0 !important;\n  }\n  .ps-xxl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-xxl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-xxl-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-xxl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-xxl-5 {\n    padding-right: 3rem !important;\n  }\n}\n@media print {\n  .d-print-inline {\n    display: inline !important;\n  }\n  .d-print-inline-block {\n    display: inline-block !important;\n  }\n  .d-print-block {\n    display: block !important;\n  }\n  .d-print-grid {\n    display: grid !important;\n  }\n  .d-print-table {\n    display: table !important;\n  }\n  .d-print-table-row {\n    display: table-row !important;\n  }\n  .d-print-table-cell {\n    display: table-cell !important;\n  }\n  .d-print-flex {\n    display: flex !important;\n  }\n  .d-print-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-print-none {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap-grid.rtl.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap-reboot.css",
    "content": "/*!\n * Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  :root {\n    scroll-behavior: smooth;\n  }\n}\n\nbody {\n  margin: 0;\n  font-family: var(--bs-body-font-family);\n  font-size: var(--bs-body-font-size);\n  font-weight: var(--bs-body-font-weight);\n  line-height: var(--bs-body-line-height);\n  color: var(--bs-body-color);\n  text-align: var(--bs-body-text-align);\n  background-color: var(--bs-body-bg);\n  -webkit-text-size-adjust: 100%;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nhr {\n  margin: 1rem 0;\n  color: inherit;\n  border: 0;\n  border-top: 1px solid;\n  opacity: 0.25;\n}\n\nh6, h5, h4, h3, h2, h1 {\n  margin-top: 0;\n  margin-bottom: 0.5rem;\n  font-weight: 500;\n  line-height: 1.2;\n}\n\nh1 {\n  font-size: calc(1.375rem + 1.5vw);\n}\n@media (min-width: 1200px) {\n  h1 {\n    font-size: 2.5rem;\n  }\n}\n\nh2 {\n  font-size: calc(1.325rem + 0.9vw);\n}\n@media (min-width: 1200px) {\n  h2 {\n    font-size: 2rem;\n  }\n}\n\nh3 {\n  font-size: calc(1.3rem + 0.6vw);\n}\n@media (min-width: 1200px) {\n  h3 {\n    font-size: 1.75rem;\n  }\n}\n\nh4 {\n  font-size: calc(1.275rem + 0.3vw);\n}\n@media (min-width: 1200px) {\n  h4 {\n    font-size: 1.5rem;\n  }\n}\n\nh5 {\n  font-size: 1.25rem;\n}\n\nh6 {\n  font-size: 1rem;\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nabbr[title] {\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n  cursor: help;\n  -webkit-text-decoration-skip-ink: none;\n  text-decoration-skip-ink: none;\n}\n\naddress {\n  margin-bottom: 1rem;\n  font-style: normal;\n  line-height: inherit;\n}\n\nol,\nul {\n  padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n  margin-bottom: 0;\n}\n\ndt {\n  font-weight: 700;\n}\n\ndd {\n  margin-bottom: 0.5rem;\n  margin-left: 0;\n}\n\nblockquote {\n  margin: 0 0 1rem;\n}\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\nsmall {\n  font-size: 0.875em;\n}\n\nmark {\n  padding: 0.1875em;\n  background-color: var(--bs-highlight-bg);\n}\n\nsub,\nsup {\n  position: relative;\n  font-size: 0.75em;\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\na {\n  color: var(--bs-link-color);\n  text-decoration: underline;\n}\na:hover {\n  color: var(--bs-link-hover-color);\n}\n\na:not([href]):not([class]), a:not([href]):not([class]):hover {\n  color: inherit;\n  text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: var(--bs-font-monospace);\n  font-size: 1em;\n}\n\npre {\n  display: block;\n  margin-top: 0;\n  margin-bottom: 1rem;\n  overflow: auto;\n  font-size: 0.875em;\n}\npre code {\n  font-size: inherit;\n  color: inherit;\n  word-break: normal;\n}\n\ncode {\n  font-size: 0.875em;\n  color: var(--bs-code-color);\n  word-wrap: break-word;\n}\na > code {\n  color: inherit;\n}\n\nkbd {\n  padding: 0.1875rem 0.375rem;\n  font-size: 0.875em;\n  color: var(--bs-body-bg);\n  background-color: var(--bs-body-color);\n  border-radius: 0.25rem;\n}\nkbd kbd {\n  padding: 0;\n  font-size: 1em;\n}\n\nfigure {\n  margin: 0 0 1rem;\n}\n\nimg,\nsvg {\n  vertical-align: middle;\n}\n\ntable {\n  caption-side: bottom;\n  border-collapse: collapse;\n}\n\ncaption {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: #6c757d;\n  text-align: left;\n}\n\nth {\n  text-align: inherit;\n  text-align: -webkit-match-parent;\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n}\n\nlabel {\n  display: inline-block;\n}\n\nbutton {\n  border-radius: 0;\n}\n\nbutton:focus:not(:focus-visible) {\n  outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n  margin: 0;\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n[role=button] {\n  cursor: pointer;\n}\n\nselect {\n  word-wrap: normal;\n}\nselect:disabled {\n  opacity: 1;\n}\n\n[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {\n  display: none !important;\n}\n\nbutton,\n[type=button],\n[type=reset],\n[type=submit] {\n  -webkit-appearance: button;\n}\nbutton:not(:disabled),\n[type=button]:not(:disabled),\n[type=reset]:not(:disabled),\n[type=submit]:not(:disabled) {\n  cursor: pointer;\n}\n\n::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\ntextarea {\n  resize: vertical;\n}\n\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\n\nlegend {\n  float: left;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 0.5rem;\n  font-size: calc(1.275rem + 0.3vw);\n  line-height: inherit;\n}\n@media (min-width: 1200px) {\n  legend {\n    font-size: 1.5rem;\n  }\n}\nlegend + * {\n  clear: left;\n}\n\n::-webkit-datetime-edit-fields-wrapper,\n::-webkit-datetime-edit-text,\n::-webkit-datetime-edit-minute,\n::-webkit-datetime-edit-hour-field,\n::-webkit-datetime-edit-day-field,\n::-webkit-datetime-edit-month-field,\n::-webkit-datetime-edit-year-field {\n  padding: 0;\n}\n\n::-webkit-inner-spin-button {\n  height: auto;\n}\n\n[type=search] {\n  outline-offset: -2px;\n  -webkit-appearance: textfield;\n}\n\n/* rtl:raw:\n[type=\"tel\"],\n[type=\"url\"],\n[type=\"email\"],\n[type=\"number\"] {\n  direction: ltr;\n}\n*/\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n::-webkit-color-swatch-wrapper {\n  padding: 0;\n}\n\n::-webkit-file-upload-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\n::file-selector-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\noutput {\n  display: inline-block;\n}\n\niframe {\n  border: 0;\n}\n\nsummary {\n  display: list-item;\n  cursor: pointer;\n}\n\nprogress {\n  vertical-align: baseline;\n}\n\n[hidden] {\n  display: none !important;\n}\n\n/*# sourceMappingURL=bootstrap-reboot.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap-reboot.rtl.css",
    "content": "/*!\n * Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  :root {\n    scroll-behavior: smooth;\n  }\n}\n\nbody {\n  margin: 0;\n  font-family: var(--bs-body-font-family);\n  font-size: var(--bs-body-font-size);\n  font-weight: var(--bs-body-font-weight);\n  line-height: var(--bs-body-line-height);\n  color: var(--bs-body-color);\n  text-align: var(--bs-body-text-align);\n  background-color: var(--bs-body-bg);\n  -webkit-text-size-adjust: 100%;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nhr {\n  margin: 1rem 0;\n  color: inherit;\n  border: 0;\n  border-top: 1px solid;\n  opacity: 0.25;\n}\n\nh6, h5, h4, h3, h2, h1 {\n  margin-top: 0;\n  margin-bottom: 0.5rem;\n  font-weight: 500;\n  line-height: 1.2;\n}\n\nh1 {\n  font-size: calc(1.375rem + 1.5vw);\n}\n@media (min-width: 1200px) {\n  h1 {\n    font-size: 2.5rem;\n  }\n}\n\nh2 {\n  font-size: calc(1.325rem + 0.9vw);\n}\n@media (min-width: 1200px) {\n  h2 {\n    font-size: 2rem;\n  }\n}\n\nh3 {\n  font-size: calc(1.3rem + 0.6vw);\n}\n@media (min-width: 1200px) {\n  h3 {\n    font-size: 1.75rem;\n  }\n}\n\nh4 {\n  font-size: calc(1.275rem + 0.3vw);\n}\n@media (min-width: 1200px) {\n  h4 {\n    font-size: 1.5rem;\n  }\n}\n\nh5 {\n  font-size: 1.25rem;\n}\n\nh6 {\n  font-size: 1rem;\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nabbr[title] {\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n  cursor: help;\n  -webkit-text-decoration-skip-ink: none;\n  text-decoration-skip-ink: none;\n}\n\naddress {\n  margin-bottom: 1rem;\n  font-style: normal;\n  line-height: inherit;\n}\n\nol,\nul {\n  padding-right: 2rem;\n}\n\nol,\nul,\ndl {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n  margin-bottom: 0;\n}\n\ndt {\n  font-weight: 700;\n}\n\ndd {\n  margin-bottom: 0.5rem;\n  margin-right: 0;\n}\n\nblockquote {\n  margin: 0 0 1rem;\n}\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\nsmall {\n  font-size: 0.875em;\n}\n\nmark {\n  padding: 0.1875em;\n  background-color: var(--bs-highlight-bg);\n}\n\nsub,\nsup {\n  position: relative;\n  font-size: 0.75em;\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\na {\n  color: var(--bs-link-color);\n  text-decoration: underline;\n}\na:hover {\n  color: var(--bs-link-hover-color);\n}\n\na:not([href]):not([class]), a:not([href]):not([class]):hover {\n  color: inherit;\n  text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: var(--bs-font-monospace);\n  font-size: 1em;\n}\n\npre {\n  display: block;\n  margin-top: 0;\n  margin-bottom: 1rem;\n  overflow: auto;\n  font-size: 0.875em;\n}\npre code {\n  font-size: inherit;\n  color: inherit;\n  word-break: normal;\n}\n\ncode {\n  font-size: 0.875em;\n  color: var(--bs-code-color);\n  word-wrap: break-word;\n}\na > code {\n  color: inherit;\n}\n\nkbd {\n  padding: 0.1875rem 0.375rem;\n  font-size: 0.875em;\n  color: var(--bs-body-bg);\n  background-color: var(--bs-body-color);\n  border-radius: 0.25rem;\n}\nkbd kbd {\n  padding: 0;\n  font-size: 1em;\n}\n\nfigure {\n  margin: 0 0 1rem;\n}\n\nimg,\nsvg {\n  vertical-align: middle;\n}\n\ntable {\n  caption-side: bottom;\n  border-collapse: collapse;\n}\n\ncaption {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: #6c757d;\n  text-align: right;\n}\n\nth {\n  text-align: inherit;\n  text-align: -webkit-match-parent;\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n}\n\nlabel {\n  display: inline-block;\n}\n\nbutton {\n  border-radius: 0;\n}\n\nbutton:focus:not(:focus-visible) {\n  outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n  margin: 0;\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n[role=button] {\n  cursor: pointer;\n}\n\nselect {\n  word-wrap: normal;\n}\nselect:disabled {\n  opacity: 1;\n}\n\n[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {\n  display: none !important;\n}\n\nbutton,\n[type=button],\n[type=reset],\n[type=submit] {\n  -webkit-appearance: button;\n}\nbutton:not(:disabled),\n[type=button]:not(:disabled),\n[type=reset]:not(:disabled),\n[type=submit]:not(:disabled) {\n  cursor: pointer;\n}\n\n::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\ntextarea {\n  resize: vertical;\n}\n\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\n\nlegend {\n  float: right;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 0.5rem;\n  font-size: calc(1.275rem + 0.3vw);\n  line-height: inherit;\n}\n@media (min-width: 1200px) {\n  legend {\n    font-size: 1.5rem;\n  }\n}\nlegend + * {\n  clear: right;\n}\n\n::-webkit-datetime-edit-fields-wrapper,\n::-webkit-datetime-edit-text,\n::-webkit-datetime-edit-minute,\n::-webkit-datetime-edit-hour-field,\n::-webkit-datetime-edit-day-field,\n::-webkit-datetime-edit-month-field,\n::-webkit-datetime-edit-year-field {\n  padding: 0;\n}\n\n::-webkit-inner-spin-button {\n  height: auto;\n}\n\n[type=search] {\n  outline-offset: -2px;\n  -webkit-appearance: textfield;\n}\n\n[type=\"tel\"],\n[type=\"url\"],\n[type=\"email\"],\n[type=\"number\"] {\n  direction: ltr;\n}\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n::-webkit-color-swatch-wrapper {\n  padding: 0;\n}\n\n::-webkit-file-upload-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\n::file-selector-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\noutput {\n  display: inline-block;\n}\n\niframe {\n  border: 0;\n}\n\nsummary {\n  display: list-item;\n  cursor: pointer;\n}\n\nprogress {\n  vertical-align: baseline;\n}\n\n[hidden] {\n  display: none !important;\n}\n/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap-utilities.css",
    "content": "/*!\n * Bootstrap Utilities v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n.clearfix::after {\n  display: block;\n  clear: both;\n  content: \"\";\n}\n\n.text-bg-primary {\n  color: #fff !important;\n  background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-secondary {\n  color: #fff !important;\n  background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-success {\n  color: #fff !important;\n  background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-info {\n  color: #000 !important;\n  background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-warning {\n  color: #000 !important;\n  background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-danger {\n  color: #fff !important;\n  background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-light {\n  color: #000 !important;\n  background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-dark {\n  color: #fff !important;\n  background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important;\n}\n\n.link-primary {\n  color: #0d6efd !important;\n}\n.link-primary:hover, .link-primary:focus {\n  color: #0a58ca !important;\n}\n\n.link-secondary {\n  color: #6c757d !important;\n}\n.link-secondary:hover, .link-secondary:focus {\n  color: #565e64 !important;\n}\n\n.link-success {\n  color: #198754 !important;\n}\n.link-success:hover, .link-success:focus {\n  color: #146c43 !important;\n}\n\n.link-info {\n  color: #0dcaf0 !important;\n}\n.link-info:hover, .link-info:focus {\n  color: #3dd5f3 !important;\n}\n\n.link-warning {\n  color: #ffc107 !important;\n}\n.link-warning:hover, .link-warning:focus {\n  color: #ffcd39 !important;\n}\n\n.link-danger {\n  color: #dc3545 !important;\n}\n.link-danger:hover, .link-danger:focus {\n  color: #b02a37 !important;\n}\n\n.link-light {\n  color: #f8f9fa !important;\n}\n.link-light:hover, .link-light:focus {\n  color: #f9fafb !important;\n}\n\n.link-dark {\n  color: #212529 !important;\n}\n.link-dark:hover, .link-dark:focus {\n  color: #1a1e21 !important;\n}\n\n.ratio {\n  position: relative;\n  width: 100%;\n}\n.ratio::before {\n  display: block;\n  padding-top: var(--bs-aspect-ratio);\n  content: \"\";\n}\n.ratio > * {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.ratio-1x1 {\n  --bs-aspect-ratio: 100%;\n}\n\n.ratio-4x3 {\n  --bs-aspect-ratio: 75%;\n}\n\n.ratio-16x9 {\n  --bs-aspect-ratio: 56.25%;\n}\n\n.ratio-21x9 {\n  --bs-aspect-ratio: 42.8571428571%;\n}\n\n.fixed-top {\n  position: fixed;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n\n.fixed-bottom {\n  position: fixed;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1030;\n}\n\n.sticky-top {\n  position: -webkit-sticky;\n  position: sticky;\n  top: 0;\n  z-index: 1020;\n}\n\n.sticky-bottom {\n  position: -webkit-sticky;\n  position: sticky;\n  bottom: 0;\n  z-index: 1020;\n}\n\n@media (min-width: 576px) {\n  .sticky-sm-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-sm-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 768px) {\n  .sticky-md-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-md-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 992px) {\n  .sticky-lg-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-lg-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1200px) {\n  .sticky-xl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1400px) {\n  .sticky-xxl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xxl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n.hstack {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  align-self: stretch;\n}\n\n.vstack {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  align-self: stretch;\n}\n\n.visually-hidden,\n.visually-hidden-focusable:not(:focus):not(:focus-within) {\n  position: absolute !important;\n  width: 1px !important;\n  height: 1px !important;\n  padding: 0 !important;\n  margin: -1px !important;\n  overflow: hidden !important;\n  clip: rect(0, 0, 0, 0) !important;\n  white-space: nowrap !important;\n  border: 0 !important;\n}\n\n.stretched-link::after {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1;\n  content: \"\";\n}\n\n.text-truncate {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.vr {\n  display: inline-block;\n  align-self: stretch;\n  width: 1px;\n  min-height: 1em;\n  background-color: currentcolor;\n  opacity: 0.25;\n}\n\n.align-baseline {\n  vertical-align: baseline !important;\n}\n\n.align-top {\n  vertical-align: top !important;\n}\n\n.align-middle {\n  vertical-align: middle !important;\n}\n\n.align-bottom {\n  vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n  vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n  vertical-align: text-top !important;\n}\n\n.float-start {\n  float: left !important;\n}\n\n.float-end {\n  float: right !important;\n}\n\n.float-none {\n  float: none !important;\n}\n\n.opacity-0 {\n  opacity: 0 !important;\n}\n\n.opacity-25 {\n  opacity: 0.25 !important;\n}\n\n.opacity-50 {\n  opacity: 0.5 !important;\n}\n\n.opacity-75 {\n  opacity: 0.75 !important;\n}\n\n.opacity-100 {\n  opacity: 1 !important;\n}\n\n.overflow-auto {\n  overflow: auto !important;\n}\n\n.overflow-hidden {\n  overflow: hidden !important;\n}\n\n.overflow-visible {\n  overflow: visible !important;\n}\n\n.overflow-scroll {\n  overflow: scroll !important;\n}\n\n.d-inline {\n  display: inline !important;\n}\n\n.d-inline-block {\n  display: inline-block !important;\n}\n\n.d-block {\n  display: block !important;\n}\n\n.d-grid {\n  display: grid !important;\n}\n\n.d-table {\n  display: table !important;\n}\n\n.d-table-row {\n  display: table-row !important;\n}\n\n.d-table-cell {\n  display: table-cell !important;\n}\n\n.d-flex {\n  display: flex !important;\n}\n\n.d-inline-flex {\n  display: inline-flex !important;\n}\n\n.d-none {\n  display: none !important;\n}\n\n.shadow {\n  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-sm {\n  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow-lg {\n  box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n  box-shadow: none !important;\n}\n\n.position-static {\n  position: static !important;\n}\n\n.position-relative {\n  position: relative !important;\n}\n\n.position-absolute {\n  position: absolute !important;\n}\n\n.position-fixed {\n  position: fixed !important;\n}\n\n.position-sticky {\n  position: -webkit-sticky !important;\n  position: sticky !important;\n}\n\n.top-0 {\n  top: 0 !important;\n}\n\n.top-50 {\n  top: 50% !important;\n}\n\n.top-100 {\n  top: 100% !important;\n}\n\n.bottom-0 {\n  bottom: 0 !important;\n}\n\n.bottom-50 {\n  bottom: 50% !important;\n}\n\n.bottom-100 {\n  bottom: 100% !important;\n}\n\n.start-0 {\n  left: 0 !important;\n}\n\n.start-50 {\n  left: 50% !important;\n}\n\n.start-100 {\n  left: 100% !important;\n}\n\n.end-0 {\n  right: 0 !important;\n}\n\n.end-50 {\n  right: 50% !important;\n}\n\n.end-100 {\n  right: 100% !important;\n}\n\n.translate-middle {\n  transform: translate(-50%, -50%) !important;\n}\n\n.translate-middle-x {\n  transform: translateX(-50%) !important;\n}\n\n.translate-middle-y {\n  transform: translateY(-50%) !important;\n}\n\n.border {\n  border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-0 {\n  border: 0 !important;\n}\n\n.border-top {\n  border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-top-0 {\n  border-top: 0 !important;\n}\n\n.border-end {\n  border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-end-0 {\n  border-right: 0 !important;\n}\n\n.border-bottom {\n  border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-bottom-0 {\n  border-bottom: 0 !important;\n}\n\n.border-start {\n  border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-start-0 {\n  border-left: 0 !important;\n}\n\n.border-primary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-secondary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-success {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-info {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-warning {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-danger {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-light {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-dark {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-white {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-1 {\n  --bs-border-width: 1px;\n}\n\n.border-2 {\n  --bs-border-width: 2px;\n}\n\n.border-3 {\n  --bs-border-width: 3px;\n}\n\n.border-4 {\n  --bs-border-width: 4px;\n}\n\n.border-5 {\n  --bs-border-width: 5px;\n}\n\n.border-opacity-10 {\n  --bs-border-opacity: 0.1;\n}\n\n.border-opacity-25 {\n  --bs-border-opacity: 0.25;\n}\n\n.border-opacity-50 {\n  --bs-border-opacity: 0.5;\n}\n\n.border-opacity-75 {\n  --bs-border-opacity: 0.75;\n}\n\n.border-opacity-100 {\n  --bs-border-opacity: 1;\n}\n\n.w-25 {\n  width: 25% !important;\n}\n\n.w-50 {\n  width: 50% !important;\n}\n\n.w-75 {\n  width: 75% !important;\n}\n\n.w-100 {\n  width: 100% !important;\n}\n\n.w-auto {\n  width: auto !important;\n}\n\n.mw-100 {\n  max-width: 100% !important;\n}\n\n.vw-100 {\n  width: 100vw !important;\n}\n\n.min-vw-100 {\n  min-width: 100vw !important;\n}\n\n.h-25 {\n  height: 25% !important;\n}\n\n.h-50 {\n  height: 50% !important;\n}\n\n.h-75 {\n  height: 75% !important;\n}\n\n.h-100 {\n  height: 100% !important;\n}\n\n.h-auto {\n  height: auto !important;\n}\n\n.mh-100 {\n  max-height: 100% !important;\n}\n\n.vh-100 {\n  height: 100vh !important;\n}\n\n.min-vh-100 {\n  min-height: 100vh !important;\n}\n\n.flex-fill {\n  flex: 1 1 auto !important;\n}\n\n.flex-row {\n  flex-direction: row !important;\n}\n\n.flex-column {\n  flex-direction: column !important;\n}\n\n.flex-row-reverse {\n  flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n  flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n  flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n  flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n  flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n  flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n  justify-content: flex-start !important;\n}\n\n.justify-content-end {\n  justify-content: flex-end !important;\n}\n\n.justify-content-center {\n  justify-content: center !important;\n}\n\n.justify-content-between {\n  justify-content: space-between !important;\n}\n\n.justify-content-around {\n  justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n  justify-content: space-evenly !important;\n}\n\n.align-items-start {\n  align-items: flex-start !important;\n}\n\n.align-items-end {\n  align-items: flex-end !important;\n}\n\n.align-items-center {\n  align-items: center !important;\n}\n\n.align-items-baseline {\n  align-items: baseline !important;\n}\n\n.align-items-stretch {\n  align-items: stretch !important;\n}\n\n.align-content-start {\n  align-content: flex-start !important;\n}\n\n.align-content-end {\n  align-content: flex-end !important;\n}\n\n.align-content-center {\n  align-content: center !important;\n}\n\n.align-content-between {\n  align-content: space-between !important;\n}\n\n.align-content-around {\n  align-content: space-around !important;\n}\n\n.align-content-stretch {\n  align-content: stretch !important;\n}\n\n.align-self-auto {\n  align-self: auto !important;\n}\n\n.align-self-start {\n  align-self: flex-start !important;\n}\n\n.align-self-end {\n  align-self: flex-end !important;\n}\n\n.align-self-center {\n  align-self: center !important;\n}\n\n.align-self-baseline {\n  align-self: baseline !important;\n}\n\n.align-self-stretch {\n  align-self: stretch !important;\n}\n\n.order-first {\n  order: -1 !important;\n}\n\n.order-0 {\n  order: 0 !important;\n}\n\n.order-1 {\n  order: 1 !important;\n}\n\n.order-2 {\n  order: 2 !important;\n}\n\n.order-3 {\n  order: 3 !important;\n}\n\n.order-4 {\n  order: 4 !important;\n}\n\n.order-5 {\n  order: 5 !important;\n}\n\n.order-last {\n  order: 6 !important;\n}\n\n.m-0 {\n  margin: 0 !important;\n}\n\n.m-1 {\n  margin: 0.25rem !important;\n}\n\n.m-2 {\n  margin: 0.5rem !important;\n}\n\n.m-3 {\n  margin: 1rem !important;\n}\n\n.m-4 {\n  margin: 1.5rem !important;\n}\n\n.m-5 {\n  margin: 3rem !important;\n}\n\n.m-auto {\n  margin: auto !important;\n}\n\n.mx-0 {\n  margin-right: 0 !important;\n  margin-left: 0 !important;\n}\n\n.mx-1 {\n  margin-right: 0.25rem !important;\n  margin-left: 0.25rem !important;\n}\n\n.mx-2 {\n  margin-right: 0.5rem !important;\n  margin-left: 0.5rem !important;\n}\n\n.mx-3 {\n  margin-right: 1rem !important;\n  margin-left: 1rem !important;\n}\n\n.mx-4 {\n  margin-right: 1.5rem !important;\n  margin-left: 1.5rem !important;\n}\n\n.mx-5 {\n  margin-right: 3rem !important;\n  margin-left: 3rem !important;\n}\n\n.mx-auto {\n  margin-right: auto !important;\n  margin-left: auto !important;\n}\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important;\n}\n\n.my-4 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important;\n}\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important;\n}\n\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.mt-1 {\n  margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n  margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n  margin-top: 1rem !important;\n}\n\n.mt-4 {\n  margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n  margin-top: 3rem !important;\n}\n\n.mt-auto {\n  margin-top: auto !important;\n}\n\n.me-0 {\n  margin-right: 0 !important;\n}\n\n.me-1 {\n  margin-right: 0.25rem !important;\n}\n\n.me-2 {\n  margin-right: 0.5rem !important;\n}\n\n.me-3 {\n  margin-right: 1rem !important;\n}\n\n.me-4 {\n  margin-right: 1.5rem !important;\n}\n\n.me-5 {\n  margin-right: 3rem !important;\n}\n\n.me-auto {\n  margin-right: auto !important;\n}\n\n.mb-0 {\n  margin-bottom: 0 !important;\n}\n\n.mb-1 {\n  margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n  margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n  margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n  margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n  margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n  margin-bottom: auto !important;\n}\n\n.ms-0 {\n  margin-left: 0 !important;\n}\n\n.ms-1 {\n  margin-left: 0.25rem !important;\n}\n\n.ms-2 {\n  margin-left: 0.5rem !important;\n}\n\n.ms-3 {\n  margin-left: 1rem !important;\n}\n\n.ms-4 {\n  margin-left: 1.5rem !important;\n}\n\n.ms-5 {\n  margin-left: 3rem !important;\n}\n\n.ms-auto {\n  margin-left: auto !important;\n}\n\n.p-0 {\n  padding: 0 !important;\n}\n\n.p-1 {\n  padding: 0.25rem !important;\n}\n\n.p-2 {\n  padding: 0.5rem !important;\n}\n\n.p-3 {\n  padding: 1rem !important;\n}\n\n.p-4 {\n  padding: 1.5rem !important;\n}\n\n.p-5 {\n  padding: 3rem !important;\n}\n\n.px-0 {\n  padding-right: 0 !important;\n  padding-left: 0 !important;\n}\n\n.px-1 {\n  padding-right: 0.25rem !important;\n  padding-left: 0.25rem !important;\n}\n\n.px-2 {\n  padding-right: 0.5rem !important;\n  padding-left: 0.5rem !important;\n}\n\n.px-3 {\n  padding-right: 1rem !important;\n  padding-left: 1rem !important;\n}\n\n.px-4 {\n  padding-right: 1.5rem !important;\n  padding-left: 1.5rem !important;\n}\n\n.px-5 {\n  padding-right: 3rem !important;\n  padding-left: 3rem !important;\n}\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n\n.py-4 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n  padding-top: 0 !important;\n}\n\n.pt-1 {\n  padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n  padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n  padding-top: 1rem !important;\n}\n\n.pt-4 {\n  padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n  padding-top: 3rem !important;\n}\n\n.pe-0 {\n  padding-right: 0 !important;\n}\n\n.pe-1 {\n  padding-right: 0.25rem !important;\n}\n\n.pe-2 {\n  padding-right: 0.5rem !important;\n}\n\n.pe-3 {\n  padding-right: 1rem !important;\n}\n\n.pe-4 {\n  padding-right: 1.5rem !important;\n}\n\n.pe-5 {\n  padding-right: 3rem !important;\n}\n\n.pb-0 {\n  padding-bottom: 0 !important;\n}\n\n.pb-1 {\n  padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n  padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n  padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n  padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n  padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n  padding-left: 0 !important;\n}\n\n.ps-1 {\n  padding-left: 0.25rem !important;\n}\n\n.ps-2 {\n  padding-left: 0.5rem !important;\n}\n\n.ps-3 {\n  padding-left: 1rem !important;\n}\n\n.ps-4 {\n  padding-left: 1.5rem !important;\n}\n\n.ps-5 {\n  padding-left: 3rem !important;\n}\n\n.gap-0 {\n  gap: 0 !important;\n}\n\n.gap-1 {\n  gap: 0.25rem !important;\n}\n\n.gap-2 {\n  gap: 0.5rem !important;\n}\n\n.gap-3 {\n  gap: 1rem !important;\n}\n\n.gap-4 {\n  gap: 1.5rem !important;\n}\n\n.gap-5 {\n  gap: 3rem !important;\n}\n\n.font-monospace {\n  font-family: var(--bs-font-monospace) !important;\n}\n\n.fs-1 {\n  font-size: calc(1.375rem + 1.5vw) !important;\n}\n\n.fs-2 {\n  font-size: calc(1.325rem + 0.9vw) !important;\n}\n\n.fs-3 {\n  font-size: calc(1.3rem + 0.6vw) !important;\n}\n\n.fs-4 {\n  font-size: calc(1.275rem + 0.3vw) !important;\n}\n\n.fs-5 {\n  font-size: 1.25rem !important;\n}\n\n.fs-6 {\n  font-size: 1rem !important;\n}\n\n.fst-italic {\n  font-style: italic !important;\n}\n\n.fst-normal {\n  font-style: normal !important;\n}\n\n.fw-light {\n  font-weight: 300 !important;\n}\n\n.fw-lighter {\n  font-weight: lighter !important;\n}\n\n.fw-normal {\n  font-weight: 400 !important;\n}\n\n.fw-bold {\n  font-weight: 700 !important;\n}\n\n.fw-semibold {\n  font-weight: 600 !important;\n}\n\n.fw-bolder {\n  font-weight: bolder !important;\n}\n\n.lh-1 {\n  line-height: 1 !important;\n}\n\n.lh-sm {\n  line-height: 1.25 !important;\n}\n\n.lh-base {\n  line-height: 1.5 !important;\n}\n\n.lh-lg {\n  line-height: 2 !important;\n}\n\n.text-start {\n  text-align: left !important;\n}\n\n.text-end {\n  text-align: right !important;\n}\n\n.text-center {\n  text-align: center !important;\n}\n\n.text-decoration-none {\n  text-decoration: none !important;\n}\n\n.text-decoration-underline {\n  text-decoration: underline !important;\n}\n\n.text-decoration-line-through {\n  text-decoration: line-through !important;\n}\n\n.text-lowercase {\n  text-transform: lowercase !important;\n}\n\n.text-uppercase {\n  text-transform: uppercase !important;\n}\n\n.text-capitalize {\n  text-transform: capitalize !important;\n}\n\n.text-wrap {\n  white-space: normal !important;\n}\n\n.text-nowrap {\n  white-space: nowrap !important;\n}\n\n/* rtl:begin:remove */\n.text-break {\n  word-wrap: break-word !important;\n  word-break: break-word !important;\n}\n\n/* rtl:end:remove */\n.text-primary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-secondary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-success {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-info {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-warning {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-danger {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-light {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-dark {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-black {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-white {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-body {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-muted {\n  --bs-text-opacity: 1;\n  color: #6c757d !important;\n}\n\n.text-black-50 {\n  --bs-text-opacity: 1;\n  color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n  --bs-text-opacity: 1;\n  color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-reset {\n  --bs-text-opacity: 1;\n  color: inherit !important;\n}\n\n.text-opacity-25 {\n  --bs-text-opacity: 0.25;\n}\n\n.text-opacity-50 {\n  --bs-text-opacity: 0.5;\n}\n\n.text-opacity-75 {\n  --bs-text-opacity: 0.75;\n}\n\n.text-opacity-100 {\n  --bs-text-opacity: 1;\n}\n\n.bg-primary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-secondary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-success {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-info {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-warning {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-danger {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-light {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-dark {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-black {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-white {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-body {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-transparent {\n  --bs-bg-opacity: 1;\n  background-color: transparent !important;\n}\n\n.bg-opacity-10 {\n  --bs-bg-opacity: 0.1;\n}\n\n.bg-opacity-25 {\n  --bs-bg-opacity: 0.25;\n}\n\n.bg-opacity-50 {\n  --bs-bg-opacity: 0.5;\n}\n\n.bg-opacity-75 {\n  --bs-bg-opacity: 0.75;\n}\n\n.bg-opacity-100 {\n  --bs-bg-opacity: 1;\n}\n\n.bg-gradient {\n  background-image: var(--bs-gradient) !important;\n}\n\n.user-select-all {\n  -webkit-user-select: all !important;\n  -moz-user-select: all !important;\n  user-select: all !important;\n}\n\n.user-select-auto {\n  -webkit-user-select: auto !important;\n  -moz-user-select: auto !important;\n  user-select: auto !important;\n}\n\n.user-select-none {\n  -webkit-user-select: none !important;\n  -moz-user-select: none !important;\n  user-select: none !important;\n}\n\n.pe-none {\n  pointer-events: none !important;\n}\n\n.pe-auto {\n  pointer-events: auto !important;\n}\n\n.rounded {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-0 {\n  border-radius: 0 !important;\n}\n\n.rounded-1 {\n  border-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-2 {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-3 {\n  border-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-4 {\n  border-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-5 {\n  border-radius: var(--bs-border-radius-2xl) !important;\n}\n\n.rounded-circle {\n  border-radius: 50% !important;\n}\n\n.rounded-pill {\n  border-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-top {\n  border-top-left-radius: var(--bs-border-radius) !important;\n  border-top-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-end {\n  border-top-right-radius: var(--bs-border-radius) !important;\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-bottom {\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-start {\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n  border-top-left-radius: var(--bs-border-radius) !important;\n}\n\n.visible {\n  visibility: visible !important;\n}\n\n.invisible {\n  visibility: hidden !important;\n}\n\n@media (min-width: 576px) {\n  .float-sm-start {\n    float: left !important;\n  }\n  .float-sm-end {\n    float: right !important;\n  }\n  .float-sm-none {\n    float: none !important;\n  }\n  .d-sm-inline {\n    display: inline !important;\n  }\n  .d-sm-inline-block {\n    display: inline-block !important;\n  }\n  .d-sm-block {\n    display: block !important;\n  }\n  .d-sm-grid {\n    display: grid !important;\n  }\n  .d-sm-table {\n    display: table !important;\n  }\n  .d-sm-table-row {\n    display: table-row !important;\n  }\n  .d-sm-table-cell {\n    display: table-cell !important;\n  }\n  .d-sm-flex {\n    display: flex !important;\n  }\n  .d-sm-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-sm-none {\n    display: none !important;\n  }\n  .flex-sm-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-sm-row {\n    flex-direction: row !important;\n  }\n  .flex-sm-column {\n    flex-direction: column !important;\n  }\n  .flex-sm-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-sm-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-sm-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-sm-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-sm-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-sm-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-sm-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-sm-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-sm-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-sm-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-sm-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-sm-center {\n    justify-content: center !important;\n  }\n  .justify-content-sm-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-sm-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-sm-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-sm-start {\n    align-items: flex-start !important;\n  }\n  .align-items-sm-end {\n    align-items: flex-end !important;\n  }\n  .align-items-sm-center {\n    align-items: center !important;\n  }\n  .align-items-sm-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-sm-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-sm-start {\n    align-content: flex-start !important;\n  }\n  .align-content-sm-end {\n    align-content: flex-end !important;\n  }\n  .align-content-sm-center {\n    align-content: center !important;\n  }\n  .align-content-sm-between {\n    align-content: space-between !important;\n  }\n  .align-content-sm-around {\n    align-content: space-around !important;\n  }\n  .align-content-sm-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-sm-auto {\n    align-self: auto !important;\n  }\n  .align-self-sm-start {\n    align-self: flex-start !important;\n  }\n  .align-self-sm-end {\n    align-self: flex-end !important;\n  }\n  .align-self-sm-center {\n    align-self: center !important;\n  }\n  .align-self-sm-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-sm-stretch {\n    align-self: stretch !important;\n  }\n  .order-sm-first {\n    order: -1 !important;\n  }\n  .order-sm-0 {\n    order: 0 !important;\n  }\n  .order-sm-1 {\n    order: 1 !important;\n  }\n  .order-sm-2 {\n    order: 2 !important;\n  }\n  .order-sm-3 {\n    order: 3 !important;\n  }\n  .order-sm-4 {\n    order: 4 !important;\n  }\n  .order-sm-5 {\n    order: 5 !important;\n  }\n  .order-sm-last {\n    order: 6 !important;\n  }\n  .m-sm-0 {\n    margin: 0 !important;\n  }\n  .m-sm-1 {\n    margin: 0.25rem !important;\n  }\n  .m-sm-2 {\n    margin: 0.5rem !important;\n  }\n  .m-sm-3 {\n    margin: 1rem !important;\n  }\n  .m-sm-4 {\n    margin: 1.5rem !important;\n  }\n  .m-sm-5 {\n    margin: 3rem !important;\n  }\n  .m-sm-auto {\n    margin: auto !important;\n  }\n  .mx-sm-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-sm-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-sm-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-sm-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-sm-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-sm-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-sm-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-sm-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-sm-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-sm-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-sm-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-sm-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-sm-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-sm-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-sm-0 {\n    margin-top: 0 !important;\n  }\n  .mt-sm-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-sm-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-sm-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-sm-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-sm-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-sm-auto {\n    margin-top: auto !important;\n  }\n  .me-sm-0 {\n    margin-right: 0 !important;\n  }\n  .me-sm-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-sm-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-sm-3 {\n    margin-right: 1rem !important;\n  }\n  .me-sm-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-sm-5 {\n    margin-right: 3rem !important;\n  }\n  .me-sm-auto {\n    margin-right: auto !important;\n  }\n  .mb-sm-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-sm-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-sm-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-sm-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-sm-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-sm-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-sm-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-sm-0 {\n    margin-left: 0 !important;\n  }\n  .ms-sm-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-sm-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-sm-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-sm-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-sm-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-sm-auto {\n    margin-left: auto !important;\n  }\n  .p-sm-0 {\n    padding: 0 !important;\n  }\n  .p-sm-1 {\n    padding: 0.25rem !important;\n  }\n  .p-sm-2 {\n    padding: 0.5rem !important;\n  }\n  .p-sm-3 {\n    padding: 1rem !important;\n  }\n  .p-sm-4 {\n    padding: 1.5rem !important;\n  }\n  .p-sm-5 {\n    padding: 3rem !important;\n  }\n  .px-sm-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-sm-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-sm-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-sm-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-sm-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-sm-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-sm-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-sm-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-sm-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-sm-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-sm-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-sm-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-sm-0 {\n    padding-top: 0 !important;\n  }\n  .pt-sm-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-sm-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-sm-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-sm-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-sm-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-sm-0 {\n    padding-right: 0 !important;\n  }\n  .pe-sm-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-sm-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-sm-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-sm-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-sm-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-sm-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-sm-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-sm-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-sm-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-sm-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-sm-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-sm-0 {\n    padding-left: 0 !important;\n  }\n  .ps-sm-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-sm-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-sm-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-sm-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-sm-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-sm-0 {\n    gap: 0 !important;\n  }\n  .gap-sm-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-sm-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-sm-3 {\n    gap: 1rem !important;\n  }\n  .gap-sm-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-sm-5 {\n    gap: 3rem !important;\n  }\n  .text-sm-start {\n    text-align: left !important;\n  }\n  .text-sm-end {\n    text-align: right !important;\n  }\n  .text-sm-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 768px) {\n  .float-md-start {\n    float: left !important;\n  }\n  .float-md-end {\n    float: right !important;\n  }\n  .float-md-none {\n    float: none !important;\n  }\n  .d-md-inline {\n    display: inline !important;\n  }\n  .d-md-inline-block {\n    display: inline-block !important;\n  }\n  .d-md-block {\n    display: block !important;\n  }\n  .d-md-grid {\n    display: grid !important;\n  }\n  .d-md-table {\n    display: table !important;\n  }\n  .d-md-table-row {\n    display: table-row !important;\n  }\n  .d-md-table-cell {\n    display: table-cell !important;\n  }\n  .d-md-flex {\n    display: flex !important;\n  }\n  .d-md-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-md-none {\n    display: none !important;\n  }\n  .flex-md-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-md-row {\n    flex-direction: row !important;\n  }\n  .flex-md-column {\n    flex-direction: column !important;\n  }\n  .flex-md-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-md-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-md-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-md-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-md-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-md-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-md-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-md-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-md-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-md-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-md-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-md-center {\n    justify-content: center !important;\n  }\n  .justify-content-md-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-md-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-md-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-md-start {\n    align-items: flex-start !important;\n  }\n  .align-items-md-end {\n    align-items: flex-end !important;\n  }\n  .align-items-md-center {\n    align-items: center !important;\n  }\n  .align-items-md-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-md-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-md-start {\n    align-content: flex-start !important;\n  }\n  .align-content-md-end {\n    align-content: flex-end !important;\n  }\n  .align-content-md-center {\n    align-content: center !important;\n  }\n  .align-content-md-between {\n    align-content: space-between !important;\n  }\n  .align-content-md-around {\n    align-content: space-around !important;\n  }\n  .align-content-md-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-md-auto {\n    align-self: auto !important;\n  }\n  .align-self-md-start {\n    align-self: flex-start !important;\n  }\n  .align-self-md-end {\n    align-self: flex-end !important;\n  }\n  .align-self-md-center {\n    align-self: center !important;\n  }\n  .align-self-md-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-md-stretch {\n    align-self: stretch !important;\n  }\n  .order-md-first {\n    order: -1 !important;\n  }\n  .order-md-0 {\n    order: 0 !important;\n  }\n  .order-md-1 {\n    order: 1 !important;\n  }\n  .order-md-2 {\n    order: 2 !important;\n  }\n  .order-md-3 {\n    order: 3 !important;\n  }\n  .order-md-4 {\n    order: 4 !important;\n  }\n  .order-md-5 {\n    order: 5 !important;\n  }\n  .order-md-last {\n    order: 6 !important;\n  }\n  .m-md-0 {\n    margin: 0 !important;\n  }\n  .m-md-1 {\n    margin: 0.25rem !important;\n  }\n  .m-md-2 {\n    margin: 0.5rem !important;\n  }\n  .m-md-3 {\n    margin: 1rem !important;\n  }\n  .m-md-4 {\n    margin: 1.5rem !important;\n  }\n  .m-md-5 {\n    margin: 3rem !important;\n  }\n  .m-md-auto {\n    margin: auto !important;\n  }\n  .mx-md-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-md-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-md-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-md-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-md-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-md-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-md-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-md-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-md-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-md-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-md-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-md-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-md-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-md-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-md-0 {\n    margin-top: 0 !important;\n  }\n  .mt-md-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-md-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-md-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-md-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-md-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-md-auto {\n    margin-top: auto !important;\n  }\n  .me-md-0 {\n    margin-right: 0 !important;\n  }\n  .me-md-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-md-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-md-3 {\n    margin-right: 1rem !important;\n  }\n  .me-md-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-md-5 {\n    margin-right: 3rem !important;\n  }\n  .me-md-auto {\n    margin-right: auto !important;\n  }\n  .mb-md-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-md-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-md-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-md-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-md-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-md-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-md-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-md-0 {\n    margin-left: 0 !important;\n  }\n  .ms-md-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-md-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-md-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-md-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-md-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-md-auto {\n    margin-left: auto !important;\n  }\n  .p-md-0 {\n    padding: 0 !important;\n  }\n  .p-md-1 {\n    padding: 0.25rem !important;\n  }\n  .p-md-2 {\n    padding: 0.5rem !important;\n  }\n  .p-md-3 {\n    padding: 1rem !important;\n  }\n  .p-md-4 {\n    padding: 1.5rem !important;\n  }\n  .p-md-5 {\n    padding: 3rem !important;\n  }\n  .px-md-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-md-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-md-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-md-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-md-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-md-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-md-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-md-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-md-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-md-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-md-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-md-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-md-0 {\n    padding-top: 0 !important;\n  }\n  .pt-md-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-md-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-md-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-md-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-md-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-md-0 {\n    padding-right: 0 !important;\n  }\n  .pe-md-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-md-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-md-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-md-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-md-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-md-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-md-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-md-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-md-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-md-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-md-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-md-0 {\n    padding-left: 0 !important;\n  }\n  .ps-md-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-md-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-md-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-md-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-md-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-md-0 {\n    gap: 0 !important;\n  }\n  .gap-md-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-md-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-md-3 {\n    gap: 1rem !important;\n  }\n  .gap-md-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-md-5 {\n    gap: 3rem !important;\n  }\n  .text-md-start {\n    text-align: left !important;\n  }\n  .text-md-end {\n    text-align: right !important;\n  }\n  .text-md-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 992px) {\n  .float-lg-start {\n    float: left !important;\n  }\n  .float-lg-end {\n    float: right !important;\n  }\n  .float-lg-none {\n    float: none !important;\n  }\n  .d-lg-inline {\n    display: inline !important;\n  }\n  .d-lg-inline-block {\n    display: inline-block !important;\n  }\n  .d-lg-block {\n    display: block !important;\n  }\n  .d-lg-grid {\n    display: grid !important;\n  }\n  .d-lg-table {\n    display: table !important;\n  }\n  .d-lg-table-row {\n    display: table-row !important;\n  }\n  .d-lg-table-cell {\n    display: table-cell !important;\n  }\n  .d-lg-flex {\n    display: flex !important;\n  }\n  .d-lg-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-lg-none {\n    display: none !important;\n  }\n  .flex-lg-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-lg-row {\n    flex-direction: row !important;\n  }\n  .flex-lg-column {\n    flex-direction: column !important;\n  }\n  .flex-lg-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-lg-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-lg-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-lg-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-lg-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-lg-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-lg-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-lg-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-lg-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-lg-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-lg-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-lg-center {\n    justify-content: center !important;\n  }\n  .justify-content-lg-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-lg-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-lg-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-lg-start {\n    align-items: flex-start !important;\n  }\n  .align-items-lg-end {\n    align-items: flex-end !important;\n  }\n  .align-items-lg-center {\n    align-items: center !important;\n  }\n  .align-items-lg-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-lg-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-lg-start {\n    align-content: flex-start !important;\n  }\n  .align-content-lg-end {\n    align-content: flex-end !important;\n  }\n  .align-content-lg-center {\n    align-content: center !important;\n  }\n  .align-content-lg-between {\n    align-content: space-between !important;\n  }\n  .align-content-lg-around {\n    align-content: space-around !important;\n  }\n  .align-content-lg-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-lg-auto {\n    align-self: auto !important;\n  }\n  .align-self-lg-start {\n    align-self: flex-start !important;\n  }\n  .align-self-lg-end {\n    align-self: flex-end !important;\n  }\n  .align-self-lg-center {\n    align-self: center !important;\n  }\n  .align-self-lg-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-lg-stretch {\n    align-self: stretch !important;\n  }\n  .order-lg-first {\n    order: -1 !important;\n  }\n  .order-lg-0 {\n    order: 0 !important;\n  }\n  .order-lg-1 {\n    order: 1 !important;\n  }\n  .order-lg-2 {\n    order: 2 !important;\n  }\n  .order-lg-3 {\n    order: 3 !important;\n  }\n  .order-lg-4 {\n    order: 4 !important;\n  }\n  .order-lg-5 {\n    order: 5 !important;\n  }\n  .order-lg-last {\n    order: 6 !important;\n  }\n  .m-lg-0 {\n    margin: 0 !important;\n  }\n  .m-lg-1 {\n    margin: 0.25rem !important;\n  }\n  .m-lg-2 {\n    margin: 0.5rem !important;\n  }\n  .m-lg-3 {\n    margin: 1rem !important;\n  }\n  .m-lg-4 {\n    margin: 1.5rem !important;\n  }\n  .m-lg-5 {\n    margin: 3rem !important;\n  }\n  .m-lg-auto {\n    margin: auto !important;\n  }\n  .mx-lg-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-lg-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-lg-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-lg-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-lg-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-lg-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-lg-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-lg-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-lg-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-lg-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-lg-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-lg-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-lg-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-lg-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-lg-0 {\n    margin-top: 0 !important;\n  }\n  .mt-lg-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-lg-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-lg-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-lg-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-lg-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-lg-auto {\n    margin-top: auto !important;\n  }\n  .me-lg-0 {\n    margin-right: 0 !important;\n  }\n  .me-lg-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-lg-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-lg-3 {\n    margin-right: 1rem !important;\n  }\n  .me-lg-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-lg-5 {\n    margin-right: 3rem !important;\n  }\n  .me-lg-auto {\n    margin-right: auto !important;\n  }\n  .mb-lg-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-lg-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-lg-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-lg-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-lg-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-lg-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-lg-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-lg-0 {\n    margin-left: 0 !important;\n  }\n  .ms-lg-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-lg-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-lg-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-lg-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-lg-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-lg-auto {\n    margin-left: auto !important;\n  }\n  .p-lg-0 {\n    padding: 0 !important;\n  }\n  .p-lg-1 {\n    padding: 0.25rem !important;\n  }\n  .p-lg-2 {\n    padding: 0.5rem !important;\n  }\n  .p-lg-3 {\n    padding: 1rem !important;\n  }\n  .p-lg-4 {\n    padding: 1.5rem !important;\n  }\n  .p-lg-5 {\n    padding: 3rem !important;\n  }\n  .px-lg-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-lg-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-lg-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-lg-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-lg-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-lg-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-lg-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-lg-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-lg-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-lg-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-lg-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-lg-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-lg-0 {\n    padding-top: 0 !important;\n  }\n  .pt-lg-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-lg-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-lg-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-lg-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-lg-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-lg-0 {\n    padding-right: 0 !important;\n  }\n  .pe-lg-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-lg-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-lg-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-lg-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-lg-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-lg-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-lg-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-lg-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-lg-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-lg-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-lg-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-lg-0 {\n    padding-left: 0 !important;\n  }\n  .ps-lg-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-lg-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-lg-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-lg-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-lg-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-lg-0 {\n    gap: 0 !important;\n  }\n  .gap-lg-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-lg-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-lg-3 {\n    gap: 1rem !important;\n  }\n  .gap-lg-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-lg-5 {\n    gap: 3rem !important;\n  }\n  .text-lg-start {\n    text-align: left !important;\n  }\n  .text-lg-end {\n    text-align: right !important;\n  }\n  .text-lg-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .float-xl-start {\n    float: left !important;\n  }\n  .float-xl-end {\n    float: right !important;\n  }\n  .float-xl-none {\n    float: none !important;\n  }\n  .d-xl-inline {\n    display: inline !important;\n  }\n  .d-xl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xl-block {\n    display: block !important;\n  }\n  .d-xl-grid {\n    display: grid !important;\n  }\n  .d-xl-table {\n    display: table !important;\n  }\n  .d-xl-table-row {\n    display: table-row !important;\n  }\n  .d-xl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xl-flex {\n    display: flex !important;\n  }\n  .d-xl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xl-none {\n    display: none !important;\n  }\n  .flex-xl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xl-row {\n    flex-direction: row !important;\n  }\n  .flex-xl-column {\n    flex-direction: column !important;\n  }\n  .flex-xl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xl-center {\n    align-items: center !important;\n  }\n  .align-items-xl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xl-center {\n    align-content: center !important;\n  }\n  .align-content-xl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xl-center {\n    align-self: center !important;\n  }\n  .align-self-xl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xl-first {\n    order: -1 !important;\n  }\n  .order-xl-0 {\n    order: 0 !important;\n  }\n  .order-xl-1 {\n    order: 1 !important;\n  }\n  .order-xl-2 {\n    order: 2 !important;\n  }\n  .order-xl-3 {\n    order: 3 !important;\n  }\n  .order-xl-4 {\n    order: 4 !important;\n  }\n  .order-xl-5 {\n    order: 5 !important;\n  }\n  .order-xl-last {\n    order: 6 !important;\n  }\n  .m-xl-0 {\n    margin: 0 !important;\n  }\n  .m-xl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xl-3 {\n    margin: 1rem !important;\n  }\n  .m-xl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xl-5 {\n    margin: 3rem !important;\n  }\n  .m-xl-auto {\n    margin: auto !important;\n  }\n  .mx-xl-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-xl-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-xl-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-xl-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-xl-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-xl-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-xl-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-xl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xl-auto {\n    margin-top: auto !important;\n  }\n  .me-xl-0 {\n    margin-right: 0 !important;\n  }\n  .me-xl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-xl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-xl-3 {\n    margin-right: 1rem !important;\n  }\n  .me-xl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-xl-5 {\n    margin-right: 3rem !important;\n  }\n  .me-xl-auto {\n    margin-right: auto !important;\n  }\n  .mb-xl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xl-0 {\n    margin-left: 0 !important;\n  }\n  .ms-xl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-xl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-xl-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-xl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-xl-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-xl-auto {\n    margin-left: auto !important;\n  }\n  .p-xl-0 {\n    padding: 0 !important;\n  }\n  .p-xl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xl-3 {\n    padding: 1rem !important;\n  }\n  .p-xl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xl-5 {\n    padding: 3rem !important;\n  }\n  .px-xl-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-xl-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-xl-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-xl-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-xl-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-xl-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-xl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xl-0 {\n    padding-right: 0 !important;\n  }\n  .pe-xl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-xl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-xl-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-xl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-xl-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-xl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xl-0 {\n    padding-left: 0 !important;\n  }\n  .ps-xl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-xl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-xl-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-xl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-xl-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-xl-0 {\n    gap: 0 !important;\n  }\n  .gap-xl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xl-5 {\n    gap: 3rem !important;\n  }\n  .text-xl-start {\n    text-align: left !important;\n  }\n  .text-xl-end {\n    text-align: right !important;\n  }\n  .text-xl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1400px) {\n  .float-xxl-start {\n    float: left !important;\n  }\n  .float-xxl-end {\n    float: right !important;\n  }\n  .float-xxl-none {\n    float: none !important;\n  }\n  .d-xxl-inline {\n    display: inline !important;\n  }\n  .d-xxl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xxl-block {\n    display: block !important;\n  }\n  .d-xxl-grid {\n    display: grid !important;\n  }\n  .d-xxl-table {\n    display: table !important;\n  }\n  .d-xxl-table-row {\n    display: table-row !important;\n  }\n  .d-xxl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xxl-flex {\n    display: flex !important;\n  }\n  .d-xxl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xxl-none {\n    display: none !important;\n  }\n  .flex-xxl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xxl-row {\n    flex-direction: row !important;\n  }\n  .flex-xxl-column {\n    flex-direction: column !important;\n  }\n  .flex-xxl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xxl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xxl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xxl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xxl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xxl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xxl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xxl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xxl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xxl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xxl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xxl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xxl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xxl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xxl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xxl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xxl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xxl-center {\n    align-items: center !important;\n  }\n  .align-items-xxl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xxl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xxl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xxl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xxl-center {\n    align-content: center !important;\n  }\n  .align-content-xxl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xxl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xxl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xxl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xxl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xxl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xxl-center {\n    align-self: center !important;\n  }\n  .align-self-xxl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xxl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xxl-first {\n    order: -1 !important;\n  }\n  .order-xxl-0 {\n    order: 0 !important;\n  }\n  .order-xxl-1 {\n    order: 1 !important;\n  }\n  .order-xxl-2 {\n    order: 2 !important;\n  }\n  .order-xxl-3 {\n    order: 3 !important;\n  }\n  .order-xxl-4 {\n    order: 4 !important;\n  }\n  .order-xxl-5 {\n    order: 5 !important;\n  }\n  .order-xxl-last {\n    order: 6 !important;\n  }\n  .m-xxl-0 {\n    margin: 0 !important;\n  }\n  .m-xxl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xxl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xxl-3 {\n    margin: 1rem !important;\n  }\n  .m-xxl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xxl-5 {\n    margin: 3rem !important;\n  }\n  .m-xxl-auto {\n    margin: auto !important;\n  }\n  .mx-xxl-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-xxl-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-xxl-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-xxl-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-xxl-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-xxl-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-xxl-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-xxl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xxl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xxl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xxl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xxl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xxl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xxl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xxl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xxl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xxl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xxl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xxl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xxl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xxl-auto {\n    margin-top: auto !important;\n  }\n  .me-xxl-0 {\n    margin-right: 0 !important;\n  }\n  .me-xxl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-xxl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-xxl-3 {\n    margin-right: 1rem !important;\n  }\n  .me-xxl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-xxl-5 {\n    margin-right: 3rem !important;\n  }\n  .me-xxl-auto {\n    margin-right: auto !important;\n  }\n  .mb-xxl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xxl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xxl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xxl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xxl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xxl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xxl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xxl-0 {\n    margin-left: 0 !important;\n  }\n  .ms-xxl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-xxl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-xxl-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-xxl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-xxl-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-xxl-auto {\n    margin-left: auto !important;\n  }\n  .p-xxl-0 {\n    padding: 0 !important;\n  }\n  .p-xxl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xxl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xxl-3 {\n    padding: 1rem !important;\n  }\n  .p-xxl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xxl-5 {\n    padding: 3rem !important;\n  }\n  .px-xxl-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-xxl-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-xxl-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-xxl-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-xxl-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-xxl-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-xxl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xxl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xxl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xxl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xxl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xxl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xxl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xxl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xxl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xxl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xxl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xxl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xxl-0 {\n    padding-right: 0 !important;\n  }\n  .pe-xxl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-xxl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-xxl-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-xxl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-xxl-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-xxl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xxl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xxl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xxl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xxl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xxl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xxl-0 {\n    padding-left: 0 !important;\n  }\n  .ps-xxl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-xxl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-xxl-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-xxl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-xxl-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-xxl-0 {\n    gap: 0 !important;\n  }\n  .gap-xxl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xxl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xxl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xxl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xxl-5 {\n    gap: 3rem !important;\n  }\n  .text-xxl-start {\n    text-align: left !important;\n  }\n  .text-xxl-end {\n    text-align: right !important;\n  }\n  .text-xxl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .fs-1 {\n    font-size: 2.5rem !important;\n  }\n  .fs-2 {\n    font-size: 2rem !important;\n  }\n  .fs-3 {\n    font-size: 1.75rem !important;\n  }\n  .fs-4 {\n    font-size: 1.5rem !important;\n  }\n}\n@media print {\n  .d-print-inline {\n    display: inline !important;\n  }\n  .d-print-inline-block {\n    display: inline-block !important;\n  }\n  .d-print-block {\n    display: block !important;\n  }\n  .d-print-grid {\n    display: grid !important;\n  }\n  .d-print-table {\n    display: table !important;\n  }\n  .d-print-table-row {\n    display: table-row !important;\n  }\n  .d-print-table-cell {\n    display: table-cell !important;\n  }\n  .d-print-flex {\n    display: flex !important;\n  }\n  .d-print-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-print-none {\n    display: none !important;\n  }\n}\n\n/*# sourceMappingURL=bootstrap-utilities.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap-utilities.rtl.css",
    "content": "/*!\n * Bootstrap Utilities v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n.clearfix::after {\n  display: block;\n  clear: both;\n  content: \"\";\n}\n\n.text-bg-primary {\n  color: #fff !important;\n  background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-secondary {\n  color: #fff !important;\n  background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-success {\n  color: #fff !important;\n  background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-info {\n  color: #000 !important;\n  background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-warning {\n  color: #000 !important;\n  background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-danger {\n  color: #fff !important;\n  background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-light {\n  color: #000 !important;\n  background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-dark {\n  color: #fff !important;\n  background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important;\n}\n\n.link-primary {\n  color: #0d6efd !important;\n}\n.link-primary:hover, .link-primary:focus {\n  color: #0a58ca !important;\n}\n\n.link-secondary {\n  color: #6c757d !important;\n}\n.link-secondary:hover, .link-secondary:focus {\n  color: #565e64 !important;\n}\n\n.link-success {\n  color: #198754 !important;\n}\n.link-success:hover, .link-success:focus {\n  color: #146c43 !important;\n}\n\n.link-info {\n  color: #0dcaf0 !important;\n}\n.link-info:hover, .link-info:focus {\n  color: #3dd5f3 !important;\n}\n\n.link-warning {\n  color: #ffc107 !important;\n}\n.link-warning:hover, .link-warning:focus {\n  color: #ffcd39 !important;\n}\n\n.link-danger {\n  color: #dc3545 !important;\n}\n.link-danger:hover, .link-danger:focus {\n  color: #b02a37 !important;\n}\n\n.link-light {\n  color: #f8f9fa !important;\n}\n.link-light:hover, .link-light:focus {\n  color: #f9fafb !important;\n}\n\n.link-dark {\n  color: #212529 !important;\n}\n.link-dark:hover, .link-dark:focus {\n  color: #1a1e21 !important;\n}\n\n.ratio {\n  position: relative;\n  width: 100%;\n}\n.ratio::before {\n  display: block;\n  padding-top: var(--bs-aspect-ratio);\n  content: \"\";\n}\n.ratio > * {\n  position: absolute;\n  top: 0;\n  right: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.ratio-1x1 {\n  --bs-aspect-ratio: 100%;\n}\n\n.ratio-4x3 {\n  --bs-aspect-ratio: 75%;\n}\n\n.ratio-16x9 {\n  --bs-aspect-ratio: 56.25%;\n}\n\n.ratio-21x9 {\n  --bs-aspect-ratio: 42.8571428571%;\n}\n\n.fixed-top {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  z-index: 1030;\n}\n\n.fixed-bottom {\n  position: fixed;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  z-index: 1030;\n}\n\n.sticky-top {\n  position: -webkit-sticky;\n  position: sticky;\n  top: 0;\n  z-index: 1020;\n}\n\n.sticky-bottom {\n  position: -webkit-sticky;\n  position: sticky;\n  bottom: 0;\n  z-index: 1020;\n}\n\n@media (min-width: 576px) {\n  .sticky-sm-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-sm-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 768px) {\n  .sticky-md-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-md-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 992px) {\n  .sticky-lg-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-lg-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1200px) {\n  .sticky-xl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1400px) {\n  .sticky-xxl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xxl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n.hstack {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  align-self: stretch;\n}\n\n.vstack {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  align-self: stretch;\n}\n\n.visually-hidden,\n.visually-hidden-focusable:not(:focus):not(:focus-within) {\n  position: absolute !important;\n  width: 1px !important;\n  height: 1px !important;\n  padding: 0 !important;\n  margin: -1px !important;\n  overflow: hidden !important;\n  clip: rect(0, 0, 0, 0) !important;\n  white-space: nowrap !important;\n  border: 0 !important;\n}\n\n.stretched-link::after {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  z-index: 1;\n  content: \"\";\n}\n\n.text-truncate {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.vr {\n  display: inline-block;\n  align-self: stretch;\n  width: 1px;\n  min-height: 1em;\n  background-color: currentcolor;\n  opacity: 0.25;\n}\n\n.align-baseline {\n  vertical-align: baseline !important;\n}\n\n.align-top {\n  vertical-align: top !important;\n}\n\n.align-middle {\n  vertical-align: middle !important;\n}\n\n.align-bottom {\n  vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n  vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n  vertical-align: text-top !important;\n}\n\n.float-start {\n  float: right !important;\n}\n\n.float-end {\n  float: left !important;\n}\n\n.float-none {\n  float: none !important;\n}\n\n.opacity-0 {\n  opacity: 0 !important;\n}\n\n.opacity-25 {\n  opacity: 0.25 !important;\n}\n\n.opacity-50 {\n  opacity: 0.5 !important;\n}\n\n.opacity-75 {\n  opacity: 0.75 !important;\n}\n\n.opacity-100 {\n  opacity: 1 !important;\n}\n\n.overflow-auto {\n  overflow: auto !important;\n}\n\n.overflow-hidden {\n  overflow: hidden !important;\n}\n\n.overflow-visible {\n  overflow: visible !important;\n}\n\n.overflow-scroll {\n  overflow: scroll !important;\n}\n\n.d-inline {\n  display: inline !important;\n}\n\n.d-inline-block {\n  display: inline-block !important;\n}\n\n.d-block {\n  display: block !important;\n}\n\n.d-grid {\n  display: grid !important;\n}\n\n.d-table {\n  display: table !important;\n}\n\n.d-table-row {\n  display: table-row !important;\n}\n\n.d-table-cell {\n  display: table-cell !important;\n}\n\n.d-flex {\n  display: flex !important;\n}\n\n.d-inline-flex {\n  display: inline-flex !important;\n}\n\n.d-none {\n  display: none !important;\n}\n\n.shadow {\n  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-sm {\n  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow-lg {\n  box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n  box-shadow: none !important;\n}\n\n.position-static {\n  position: static !important;\n}\n\n.position-relative {\n  position: relative !important;\n}\n\n.position-absolute {\n  position: absolute !important;\n}\n\n.position-fixed {\n  position: fixed !important;\n}\n\n.position-sticky {\n  position: -webkit-sticky !important;\n  position: sticky !important;\n}\n\n.top-0 {\n  top: 0 !important;\n}\n\n.top-50 {\n  top: 50% !important;\n}\n\n.top-100 {\n  top: 100% !important;\n}\n\n.bottom-0 {\n  bottom: 0 !important;\n}\n\n.bottom-50 {\n  bottom: 50% !important;\n}\n\n.bottom-100 {\n  bottom: 100% !important;\n}\n\n.start-0 {\n  right: 0 !important;\n}\n\n.start-50 {\n  right: 50% !important;\n}\n\n.start-100 {\n  right: 100% !important;\n}\n\n.end-0 {\n  left: 0 !important;\n}\n\n.end-50 {\n  left: 50% !important;\n}\n\n.end-100 {\n  left: 100% !important;\n}\n\n.translate-middle {\n  transform: translate(50%, -50%) !important;\n}\n\n.translate-middle-x {\n  transform: translateX(50%) !important;\n}\n\n.translate-middle-y {\n  transform: translateY(-50%) !important;\n}\n\n.border {\n  border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-0 {\n  border: 0 !important;\n}\n\n.border-top {\n  border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-top-0 {\n  border-top: 0 !important;\n}\n\n.border-end {\n  border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-end-0 {\n  border-left: 0 !important;\n}\n\n.border-bottom {\n  border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-bottom-0 {\n  border-bottom: 0 !important;\n}\n\n.border-start {\n  border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-start-0 {\n  border-right: 0 !important;\n}\n\n.border-primary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-secondary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-success {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-info {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-warning {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-danger {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-light {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-dark {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-white {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-1 {\n  --bs-border-width: 1px;\n}\n\n.border-2 {\n  --bs-border-width: 2px;\n}\n\n.border-3 {\n  --bs-border-width: 3px;\n}\n\n.border-4 {\n  --bs-border-width: 4px;\n}\n\n.border-5 {\n  --bs-border-width: 5px;\n}\n\n.border-opacity-10 {\n  --bs-border-opacity: 0.1;\n}\n\n.border-opacity-25 {\n  --bs-border-opacity: 0.25;\n}\n\n.border-opacity-50 {\n  --bs-border-opacity: 0.5;\n}\n\n.border-opacity-75 {\n  --bs-border-opacity: 0.75;\n}\n\n.border-opacity-100 {\n  --bs-border-opacity: 1;\n}\n\n.w-25 {\n  width: 25% !important;\n}\n\n.w-50 {\n  width: 50% !important;\n}\n\n.w-75 {\n  width: 75% !important;\n}\n\n.w-100 {\n  width: 100% !important;\n}\n\n.w-auto {\n  width: auto !important;\n}\n\n.mw-100 {\n  max-width: 100% !important;\n}\n\n.vw-100 {\n  width: 100vw !important;\n}\n\n.min-vw-100 {\n  min-width: 100vw !important;\n}\n\n.h-25 {\n  height: 25% !important;\n}\n\n.h-50 {\n  height: 50% !important;\n}\n\n.h-75 {\n  height: 75% !important;\n}\n\n.h-100 {\n  height: 100% !important;\n}\n\n.h-auto {\n  height: auto !important;\n}\n\n.mh-100 {\n  max-height: 100% !important;\n}\n\n.vh-100 {\n  height: 100vh !important;\n}\n\n.min-vh-100 {\n  min-height: 100vh !important;\n}\n\n.flex-fill {\n  flex: 1 1 auto !important;\n}\n\n.flex-row {\n  flex-direction: row !important;\n}\n\n.flex-column {\n  flex-direction: column !important;\n}\n\n.flex-row-reverse {\n  flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n  flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n  flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n  flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n  flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n  flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n  justify-content: flex-start !important;\n}\n\n.justify-content-end {\n  justify-content: flex-end !important;\n}\n\n.justify-content-center {\n  justify-content: center !important;\n}\n\n.justify-content-between {\n  justify-content: space-between !important;\n}\n\n.justify-content-around {\n  justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n  justify-content: space-evenly !important;\n}\n\n.align-items-start {\n  align-items: flex-start !important;\n}\n\n.align-items-end {\n  align-items: flex-end !important;\n}\n\n.align-items-center {\n  align-items: center !important;\n}\n\n.align-items-baseline {\n  align-items: baseline !important;\n}\n\n.align-items-stretch {\n  align-items: stretch !important;\n}\n\n.align-content-start {\n  align-content: flex-start !important;\n}\n\n.align-content-end {\n  align-content: flex-end !important;\n}\n\n.align-content-center {\n  align-content: center !important;\n}\n\n.align-content-between {\n  align-content: space-between !important;\n}\n\n.align-content-around {\n  align-content: space-around !important;\n}\n\n.align-content-stretch {\n  align-content: stretch !important;\n}\n\n.align-self-auto {\n  align-self: auto !important;\n}\n\n.align-self-start {\n  align-self: flex-start !important;\n}\n\n.align-self-end {\n  align-self: flex-end !important;\n}\n\n.align-self-center {\n  align-self: center !important;\n}\n\n.align-self-baseline {\n  align-self: baseline !important;\n}\n\n.align-self-stretch {\n  align-self: stretch !important;\n}\n\n.order-first {\n  order: -1 !important;\n}\n\n.order-0 {\n  order: 0 !important;\n}\n\n.order-1 {\n  order: 1 !important;\n}\n\n.order-2 {\n  order: 2 !important;\n}\n\n.order-3 {\n  order: 3 !important;\n}\n\n.order-4 {\n  order: 4 !important;\n}\n\n.order-5 {\n  order: 5 !important;\n}\n\n.order-last {\n  order: 6 !important;\n}\n\n.m-0 {\n  margin: 0 !important;\n}\n\n.m-1 {\n  margin: 0.25rem !important;\n}\n\n.m-2 {\n  margin: 0.5rem !important;\n}\n\n.m-3 {\n  margin: 1rem !important;\n}\n\n.m-4 {\n  margin: 1.5rem !important;\n}\n\n.m-5 {\n  margin: 3rem !important;\n}\n\n.m-auto {\n  margin: auto !important;\n}\n\n.mx-0 {\n  margin-left: 0 !important;\n  margin-right: 0 !important;\n}\n\n.mx-1 {\n  margin-left: 0.25rem !important;\n  margin-right: 0.25rem !important;\n}\n\n.mx-2 {\n  margin-left: 0.5rem !important;\n  margin-right: 0.5rem !important;\n}\n\n.mx-3 {\n  margin-left: 1rem !important;\n  margin-right: 1rem !important;\n}\n\n.mx-4 {\n  margin-left: 1.5rem !important;\n  margin-right: 1.5rem !important;\n}\n\n.mx-5 {\n  margin-left: 3rem !important;\n  margin-right: 3rem !important;\n}\n\n.mx-auto {\n  margin-left: auto !important;\n  margin-right: auto !important;\n}\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important;\n}\n\n.my-4 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important;\n}\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important;\n}\n\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.mt-1 {\n  margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n  margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n  margin-top: 1rem !important;\n}\n\n.mt-4 {\n  margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n  margin-top: 3rem !important;\n}\n\n.mt-auto {\n  margin-top: auto !important;\n}\n\n.me-0 {\n  margin-left: 0 !important;\n}\n\n.me-1 {\n  margin-left: 0.25rem !important;\n}\n\n.me-2 {\n  margin-left: 0.5rem !important;\n}\n\n.me-3 {\n  margin-left: 1rem !important;\n}\n\n.me-4 {\n  margin-left: 1.5rem !important;\n}\n\n.me-5 {\n  margin-left: 3rem !important;\n}\n\n.me-auto {\n  margin-left: auto !important;\n}\n\n.mb-0 {\n  margin-bottom: 0 !important;\n}\n\n.mb-1 {\n  margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n  margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n  margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n  margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n  margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n  margin-bottom: auto !important;\n}\n\n.ms-0 {\n  margin-right: 0 !important;\n}\n\n.ms-1 {\n  margin-right: 0.25rem !important;\n}\n\n.ms-2 {\n  margin-right: 0.5rem !important;\n}\n\n.ms-3 {\n  margin-right: 1rem !important;\n}\n\n.ms-4 {\n  margin-right: 1.5rem !important;\n}\n\n.ms-5 {\n  margin-right: 3rem !important;\n}\n\n.ms-auto {\n  margin-right: auto !important;\n}\n\n.p-0 {\n  padding: 0 !important;\n}\n\n.p-1 {\n  padding: 0.25rem !important;\n}\n\n.p-2 {\n  padding: 0.5rem !important;\n}\n\n.p-3 {\n  padding: 1rem !important;\n}\n\n.p-4 {\n  padding: 1.5rem !important;\n}\n\n.p-5 {\n  padding: 3rem !important;\n}\n\n.px-0 {\n  padding-left: 0 !important;\n  padding-right: 0 !important;\n}\n\n.px-1 {\n  padding-left: 0.25rem !important;\n  padding-right: 0.25rem !important;\n}\n\n.px-2 {\n  padding-left: 0.5rem !important;\n  padding-right: 0.5rem !important;\n}\n\n.px-3 {\n  padding-left: 1rem !important;\n  padding-right: 1rem !important;\n}\n\n.px-4 {\n  padding-left: 1.5rem !important;\n  padding-right: 1.5rem !important;\n}\n\n.px-5 {\n  padding-left: 3rem !important;\n  padding-right: 3rem !important;\n}\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n\n.py-4 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n  padding-top: 0 !important;\n}\n\n.pt-1 {\n  padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n  padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n  padding-top: 1rem !important;\n}\n\n.pt-4 {\n  padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n  padding-top: 3rem !important;\n}\n\n.pe-0 {\n  padding-left: 0 !important;\n}\n\n.pe-1 {\n  padding-left: 0.25rem !important;\n}\n\n.pe-2 {\n  padding-left: 0.5rem !important;\n}\n\n.pe-3 {\n  padding-left: 1rem !important;\n}\n\n.pe-4 {\n  padding-left: 1.5rem !important;\n}\n\n.pe-5 {\n  padding-left: 3rem !important;\n}\n\n.pb-0 {\n  padding-bottom: 0 !important;\n}\n\n.pb-1 {\n  padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n  padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n  padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n  padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n  padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n  padding-right: 0 !important;\n}\n\n.ps-1 {\n  padding-right: 0.25rem !important;\n}\n\n.ps-2 {\n  padding-right: 0.5rem !important;\n}\n\n.ps-3 {\n  padding-right: 1rem !important;\n}\n\n.ps-4 {\n  padding-right: 1.5rem !important;\n}\n\n.ps-5 {\n  padding-right: 3rem !important;\n}\n\n.gap-0 {\n  gap: 0 !important;\n}\n\n.gap-1 {\n  gap: 0.25rem !important;\n}\n\n.gap-2 {\n  gap: 0.5rem !important;\n}\n\n.gap-3 {\n  gap: 1rem !important;\n}\n\n.gap-4 {\n  gap: 1.5rem !important;\n}\n\n.gap-5 {\n  gap: 3rem !important;\n}\n\n.font-monospace {\n  font-family: var(--bs-font-monospace) !important;\n}\n\n.fs-1 {\n  font-size: calc(1.375rem + 1.5vw) !important;\n}\n\n.fs-2 {\n  font-size: calc(1.325rem + 0.9vw) !important;\n}\n\n.fs-3 {\n  font-size: calc(1.3rem + 0.6vw) !important;\n}\n\n.fs-4 {\n  font-size: calc(1.275rem + 0.3vw) !important;\n}\n\n.fs-5 {\n  font-size: 1.25rem !important;\n}\n\n.fs-6 {\n  font-size: 1rem !important;\n}\n\n.fst-italic {\n  font-style: italic !important;\n}\n\n.fst-normal {\n  font-style: normal !important;\n}\n\n.fw-light {\n  font-weight: 300 !important;\n}\n\n.fw-lighter {\n  font-weight: lighter !important;\n}\n\n.fw-normal {\n  font-weight: 400 !important;\n}\n\n.fw-bold {\n  font-weight: 700 !important;\n}\n\n.fw-semibold {\n  font-weight: 600 !important;\n}\n\n.fw-bolder {\n  font-weight: bolder !important;\n}\n\n.lh-1 {\n  line-height: 1 !important;\n}\n\n.lh-sm {\n  line-height: 1.25 !important;\n}\n\n.lh-base {\n  line-height: 1.5 !important;\n}\n\n.lh-lg {\n  line-height: 2 !important;\n}\n\n.text-start {\n  text-align: right !important;\n}\n\n.text-end {\n  text-align: left !important;\n}\n\n.text-center {\n  text-align: center !important;\n}\n\n.text-decoration-none {\n  text-decoration: none !important;\n}\n\n.text-decoration-underline {\n  text-decoration: underline !important;\n}\n\n.text-decoration-line-through {\n  text-decoration: line-through !important;\n}\n\n.text-lowercase {\n  text-transform: lowercase !important;\n}\n\n.text-uppercase {\n  text-transform: uppercase !important;\n}\n\n.text-capitalize {\n  text-transform: capitalize !important;\n}\n\n.text-wrap {\n  white-space: normal !important;\n}\n\n.text-nowrap {\n  white-space: nowrap !important;\n}\n.text-primary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-secondary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-success {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-info {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-warning {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-danger {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-light {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-dark {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-black {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-white {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-body {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-muted {\n  --bs-text-opacity: 1;\n  color: #6c757d !important;\n}\n\n.text-black-50 {\n  --bs-text-opacity: 1;\n  color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n  --bs-text-opacity: 1;\n  color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-reset {\n  --bs-text-opacity: 1;\n  color: inherit !important;\n}\n\n.text-opacity-25 {\n  --bs-text-opacity: 0.25;\n}\n\n.text-opacity-50 {\n  --bs-text-opacity: 0.5;\n}\n\n.text-opacity-75 {\n  --bs-text-opacity: 0.75;\n}\n\n.text-opacity-100 {\n  --bs-text-opacity: 1;\n}\n\n.bg-primary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-secondary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-success {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-info {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-warning {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-danger {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-light {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-dark {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-black {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-white {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-body {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-transparent {\n  --bs-bg-opacity: 1;\n  background-color: transparent !important;\n}\n\n.bg-opacity-10 {\n  --bs-bg-opacity: 0.1;\n}\n\n.bg-opacity-25 {\n  --bs-bg-opacity: 0.25;\n}\n\n.bg-opacity-50 {\n  --bs-bg-opacity: 0.5;\n}\n\n.bg-opacity-75 {\n  --bs-bg-opacity: 0.75;\n}\n\n.bg-opacity-100 {\n  --bs-bg-opacity: 1;\n}\n\n.bg-gradient {\n  background-image: var(--bs-gradient) !important;\n}\n\n.user-select-all {\n  -webkit-user-select: all !important;\n  -moz-user-select: all !important;\n  user-select: all !important;\n}\n\n.user-select-auto {\n  -webkit-user-select: auto !important;\n  -moz-user-select: auto !important;\n  user-select: auto !important;\n}\n\n.user-select-none {\n  -webkit-user-select: none !important;\n  -moz-user-select: none !important;\n  user-select: none !important;\n}\n\n.pe-none {\n  pointer-events: none !important;\n}\n\n.pe-auto {\n  pointer-events: auto !important;\n}\n\n.rounded {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-0 {\n  border-radius: 0 !important;\n}\n\n.rounded-1 {\n  border-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-2 {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-3 {\n  border-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-4 {\n  border-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-5 {\n  border-radius: var(--bs-border-radius-2xl) !important;\n}\n\n.rounded-circle {\n  border-radius: 50% !important;\n}\n\n.rounded-pill {\n  border-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-top {\n  border-top-right-radius: var(--bs-border-radius) !important;\n  border-top-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-end {\n  border-top-left-radius: var(--bs-border-radius) !important;\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-bottom {\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-start {\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n  border-top-right-radius: var(--bs-border-radius) !important;\n}\n\n.visible {\n  visibility: visible !important;\n}\n\n.invisible {\n  visibility: hidden !important;\n}\n\n@media (min-width: 576px) {\n  .float-sm-start {\n    float: right !important;\n  }\n  .float-sm-end {\n    float: left !important;\n  }\n  .float-sm-none {\n    float: none !important;\n  }\n  .d-sm-inline {\n    display: inline !important;\n  }\n  .d-sm-inline-block {\n    display: inline-block !important;\n  }\n  .d-sm-block {\n    display: block !important;\n  }\n  .d-sm-grid {\n    display: grid !important;\n  }\n  .d-sm-table {\n    display: table !important;\n  }\n  .d-sm-table-row {\n    display: table-row !important;\n  }\n  .d-sm-table-cell {\n    display: table-cell !important;\n  }\n  .d-sm-flex {\n    display: flex !important;\n  }\n  .d-sm-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-sm-none {\n    display: none !important;\n  }\n  .flex-sm-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-sm-row {\n    flex-direction: row !important;\n  }\n  .flex-sm-column {\n    flex-direction: column !important;\n  }\n  .flex-sm-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-sm-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-sm-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-sm-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-sm-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-sm-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-sm-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-sm-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-sm-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-sm-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-sm-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-sm-center {\n    justify-content: center !important;\n  }\n  .justify-content-sm-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-sm-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-sm-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-sm-start {\n    align-items: flex-start !important;\n  }\n  .align-items-sm-end {\n    align-items: flex-end !important;\n  }\n  .align-items-sm-center {\n    align-items: center !important;\n  }\n  .align-items-sm-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-sm-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-sm-start {\n    align-content: flex-start !important;\n  }\n  .align-content-sm-end {\n    align-content: flex-end !important;\n  }\n  .align-content-sm-center {\n    align-content: center !important;\n  }\n  .align-content-sm-between {\n    align-content: space-between !important;\n  }\n  .align-content-sm-around {\n    align-content: space-around !important;\n  }\n  .align-content-sm-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-sm-auto {\n    align-self: auto !important;\n  }\n  .align-self-sm-start {\n    align-self: flex-start !important;\n  }\n  .align-self-sm-end {\n    align-self: flex-end !important;\n  }\n  .align-self-sm-center {\n    align-self: center !important;\n  }\n  .align-self-sm-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-sm-stretch {\n    align-self: stretch !important;\n  }\n  .order-sm-first {\n    order: -1 !important;\n  }\n  .order-sm-0 {\n    order: 0 !important;\n  }\n  .order-sm-1 {\n    order: 1 !important;\n  }\n  .order-sm-2 {\n    order: 2 !important;\n  }\n  .order-sm-3 {\n    order: 3 !important;\n  }\n  .order-sm-4 {\n    order: 4 !important;\n  }\n  .order-sm-5 {\n    order: 5 !important;\n  }\n  .order-sm-last {\n    order: 6 !important;\n  }\n  .m-sm-0 {\n    margin: 0 !important;\n  }\n  .m-sm-1 {\n    margin: 0.25rem !important;\n  }\n  .m-sm-2 {\n    margin: 0.5rem !important;\n  }\n  .m-sm-3 {\n    margin: 1rem !important;\n  }\n  .m-sm-4 {\n    margin: 1.5rem !important;\n  }\n  .m-sm-5 {\n    margin: 3rem !important;\n  }\n  .m-sm-auto {\n    margin: auto !important;\n  }\n  .mx-sm-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-sm-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-sm-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-sm-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-sm-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-sm-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-sm-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-sm-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-sm-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-sm-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-sm-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-sm-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-sm-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-sm-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-sm-0 {\n    margin-top: 0 !important;\n  }\n  .mt-sm-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-sm-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-sm-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-sm-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-sm-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-sm-auto {\n    margin-top: auto !important;\n  }\n  .me-sm-0 {\n    margin-left: 0 !important;\n  }\n  .me-sm-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-sm-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-sm-3 {\n    margin-left: 1rem !important;\n  }\n  .me-sm-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-sm-5 {\n    margin-left: 3rem !important;\n  }\n  .me-sm-auto {\n    margin-left: auto !important;\n  }\n  .mb-sm-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-sm-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-sm-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-sm-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-sm-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-sm-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-sm-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-sm-0 {\n    margin-right: 0 !important;\n  }\n  .ms-sm-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-sm-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-sm-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-sm-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-sm-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-sm-auto {\n    margin-right: auto !important;\n  }\n  .p-sm-0 {\n    padding: 0 !important;\n  }\n  .p-sm-1 {\n    padding: 0.25rem !important;\n  }\n  .p-sm-2 {\n    padding: 0.5rem !important;\n  }\n  .p-sm-3 {\n    padding: 1rem !important;\n  }\n  .p-sm-4 {\n    padding: 1.5rem !important;\n  }\n  .p-sm-5 {\n    padding: 3rem !important;\n  }\n  .px-sm-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-sm-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-sm-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-sm-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-sm-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-sm-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-sm-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-sm-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-sm-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-sm-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-sm-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-sm-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-sm-0 {\n    padding-top: 0 !important;\n  }\n  .pt-sm-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-sm-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-sm-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-sm-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-sm-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-sm-0 {\n    padding-left: 0 !important;\n  }\n  .pe-sm-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-sm-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-sm-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-sm-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-sm-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-sm-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-sm-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-sm-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-sm-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-sm-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-sm-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-sm-0 {\n    padding-right: 0 !important;\n  }\n  .ps-sm-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-sm-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-sm-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-sm-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-sm-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-sm-0 {\n    gap: 0 !important;\n  }\n  .gap-sm-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-sm-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-sm-3 {\n    gap: 1rem !important;\n  }\n  .gap-sm-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-sm-5 {\n    gap: 3rem !important;\n  }\n  .text-sm-start {\n    text-align: right !important;\n  }\n  .text-sm-end {\n    text-align: left !important;\n  }\n  .text-sm-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 768px) {\n  .float-md-start {\n    float: right !important;\n  }\n  .float-md-end {\n    float: left !important;\n  }\n  .float-md-none {\n    float: none !important;\n  }\n  .d-md-inline {\n    display: inline !important;\n  }\n  .d-md-inline-block {\n    display: inline-block !important;\n  }\n  .d-md-block {\n    display: block !important;\n  }\n  .d-md-grid {\n    display: grid !important;\n  }\n  .d-md-table {\n    display: table !important;\n  }\n  .d-md-table-row {\n    display: table-row !important;\n  }\n  .d-md-table-cell {\n    display: table-cell !important;\n  }\n  .d-md-flex {\n    display: flex !important;\n  }\n  .d-md-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-md-none {\n    display: none !important;\n  }\n  .flex-md-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-md-row {\n    flex-direction: row !important;\n  }\n  .flex-md-column {\n    flex-direction: column !important;\n  }\n  .flex-md-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-md-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-md-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-md-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-md-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-md-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-md-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-md-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-md-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-md-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-md-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-md-center {\n    justify-content: center !important;\n  }\n  .justify-content-md-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-md-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-md-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-md-start {\n    align-items: flex-start !important;\n  }\n  .align-items-md-end {\n    align-items: flex-end !important;\n  }\n  .align-items-md-center {\n    align-items: center !important;\n  }\n  .align-items-md-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-md-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-md-start {\n    align-content: flex-start !important;\n  }\n  .align-content-md-end {\n    align-content: flex-end !important;\n  }\n  .align-content-md-center {\n    align-content: center !important;\n  }\n  .align-content-md-between {\n    align-content: space-between !important;\n  }\n  .align-content-md-around {\n    align-content: space-around !important;\n  }\n  .align-content-md-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-md-auto {\n    align-self: auto !important;\n  }\n  .align-self-md-start {\n    align-self: flex-start !important;\n  }\n  .align-self-md-end {\n    align-self: flex-end !important;\n  }\n  .align-self-md-center {\n    align-self: center !important;\n  }\n  .align-self-md-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-md-stretch {\n    align-self: stretch !important;\n  }\n  .order-md-first {\n    order: -1 !important;\n  }\n  .order-md-0 {\n    order: 0 !important;\n  }\n  .order-md-1 {\n    order: 1 !important;\n  }\n  .order-md-2 {\n    order: 2 !important;\n  }\n  .order-md-3 {\n    order: 3 !important;\n  }\n  .order-md-4 {\n    order: 4 !important;\n  }\n  .order-md-5 {\n    order: 5 !important;\n  }\n  .order-md-last {\n    order: 6 !important;\n  }\n  .m-md-0 {\n    margin: 0 !important;\n  }\n  .m-md-1 {\n    margin: 0.25rem !important;\n  }\n  .m-md-2 {\n    margin: 0.5rem !important;\n  }\n  .m-md-3 {\n    margin: 1rem !important;\n  }\n  .m-md-4 {\n    margin: 1.5rem !important;\n  }\n  .m-md-5 {\n    margin: 3rem !important;\n  }\n  .m-md-auto {\n    margin: auto !important;\n  }\n  .mx-md-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-md-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-md-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-md-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-md-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-md-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-md-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-md-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-md-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-md-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-md-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-md-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-md-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-md-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-md-0 {\n    margin-top: 0 !important;\n  }\n  .mt-md-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-md-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-md-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-md-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-md-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-md-auto {\n    margin-top: auto !important;\n  }\n  .me-md-0 {\n    margin-left: 0 !important;\n  }\n  .me-md-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-md-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-md-3 {\n    margin-left: 1rem !important;\n  }\n  .me-md-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-md-5 {\n    margin-left: 3rem !important;\n  }\n  .me-md-auto {\n    margin-left: auto !important;\n  }\n  .mb-md-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-md-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-md-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-md-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-md-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-md-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-md-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-md-0 {\n    margin-right: 0 !important;\n  }\n  .ms-md-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-md-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-md-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-md-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-md-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-md-auto {\n    margin-right: auto !important;\n  }\n  .p-md-0 {\n    padding: 0 !important;\n  }\n  .p-md-1 {\n    padding: 0.25rem !important;\n  }\n  .p-md-2 {\n    padding: 0.5rem !important;\n  }\n  .p-md-3 {\n    padding: 1rem !important;\n  }\n  .p-md-4 {\n    padding: 1.5rem !important;\n  }\n  .p-md-5 {\n    padding: 3rem !important;\n  }\n  .px-md-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-md-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-md-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-md-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-md-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-md-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-md-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-md-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-md-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-md-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-md-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-md-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-md-0 {\n    padding-top: 0 !important;\n  }\n  .pt-md-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-md-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-md-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-md-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-md-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-md-0 {\n    padding-left: 0 !important;\n  }\n  .pe-md-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-md-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-md-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-md-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-md-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-md-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-md-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-md-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-md-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-md-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-md-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-md-0 {\n    padding-right: 0 !important;\n  }\n  .ps-md-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-md-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-md-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-md-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-md-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-md-0 {\n    gap: 0 !important;\n  }\n  .gap-md-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-md-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-md-3 {\n    gap: 1rem !important;\n  }\n  .gap-md-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-md-5 {\n    gap: 3rem !important;\n  }\n  .text-md-start {\n    text-align: right !important;\n  }\n  .text-md-end {\n    text-align: left !important;\n  }\n  .text-md-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 992px) {\n  .float-lg-start {\n    float: right !important;\n  }\n  .float-lg-end {\n    float: left !important;\n  }\n  .float-lg-none {\n    float: none !important;\n  }\n  .d-lg-inline {\n    display: inline !important;\n  }\n  .d-lg-inline-block {\n    display: inline-block !important;\n  }\n  .d-lg-block {\n    display: block !important;\n  }\n  .d-lg-grid {\n    display: grid !important;\n  }\n  .d-lg-table {\n    display: table !important;\n  }\n  .d-lg-table-row {\n    display: table-row !important;\n  }\n  .d-lg-table-cell {\n    display: table-cell !important;\n  }\n  .d-lg-flex {\n    display: flex !important;\n  }\n  .d-lg-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-lg-none {\n    display: none !important;\n  }\n  .flex-lg-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-lg-row {\n    flex-direction: row !important;\n  }\n  .flex-lg-column {\n    flex-direction: column !important;\n  }\n  .flex-lg-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-lg-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-lg-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-lg-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-lg-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-lg-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-lg-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-lg-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-lg-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-lg-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-lg-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-lg-center {\n    justify-content: center !important;\n  }\n  .justify-content-lg-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-lg-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-lg-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-lg-start {\n    align-items: flex-start !important;\n  }\n  .align-items-lg-end {\n    align-items: flex-end !important;\n  }\n  .align-items-lg-center {\n    align-items: center !important;\n  }\n  .align-items-lg-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-lg-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-lg-start {\n    align-content: flex-start !important;\n  }\n  .align-content-lg-end {\n    align-content: flex-end !important;\n  }\n  .align-content-lg-center {\n    align-content: center !important;\n  }\n  .align-content-lg-between {\n    align-content: space-between !important;\n  }\n  .align-content-lg-around {\n    align-content: space-around !important;\n  }\n  .align-content-lg-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-lg-auto {\n    align-self: auto !important;\n  }\n  .align-self-lg-start {\n    align-self: flex-start !important;\n  }\n  .align-self-lg-end {\n    align-self: flex-end !important;\n  }\n  .align-self-lg-center {\n    align-self: center !important;\n  }\n  .align-self-lg-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-lg-stretch {\n    align-self: stretch !important;\n  }\n  .order-lg-first {\n    order: -1 !important;\n  }\n  .order-lg-0 {\n    order: 0 !important;\n  }\n  .order-lg-1 {\n    order: 1 !important;\n  }\n  .order-lg-2 {\n    order: 2 !important;\n  }\n  .order-lg-3 {\n    order: 3 !important;\n  }\n  .order-lg-4 {\n    order: 4 !important;\n  }\n  .order-lg-5 {\n    order: 5 !important;\n  }\n  .order-lg-last {\n    order: 6 !important;\n  }\n  .m-lg-0 {\n    margin: 0 !important;\n  }\n  .m-lg-1 {\n    margin: 0.25rem !important;\n  }\n  .m-lg-2 {\n    margin: 0.5rem !important;\n  }\n  .m-lg-3 {\n    margin: 1rem !important;\n  }\n  .m-lg-4 {\n    margin: 1.5rem !important;\n  }\n  .m-lg-5 {\n    margin: 3rem !important;\n  }\n  .m-lg-auto {\n    margin: auto !important;\n  }\n  .mx-lg-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-lg-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-lg-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-lg-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-lg-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-lg-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-lg-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-lg-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-lg-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-lg-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-lg-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-lg-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-lg-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-lg-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-lg-0 {\n    margin-top: 0 !important;\n  }\n  .mt-lg-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-lg-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-lg-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-lg-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-lg-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-lg-auto {\n    margin-top: auto !important;\n  }\n  .me-lg-0 {\n    margin-left: 0 !important;\n  }\n  .me-lg-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-lg-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-lg-3 {\n    margin-left: 1rem !important;\n  }\n  .me-lg-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-lg-5 {\n    margin-left: 3rem !important;\n  }\n  .me-lg-auto {\n    margin-left: auto !important;\n  }\n  .mb-lg-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-lg-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-lg-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-lg-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-lg-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-lg-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-lg-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-lg-0 {\n    margin-right: 0 !important;\n  }\n  .ms-lg-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-lg-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-lg-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-lg-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-lg-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-lg-auto {\n    margin-right: auto !important;\n  }\n  .p-lg-0 {\n    padding: 0 !important;\n  }\n  .p-lg-1 {\n    padding: 0.25rem !important;\n  }\n  .p-lg-2 {\n    padding: 0.5rem !important;\n  }\n  .p-lg-3 {\n    padding: 1rem !important;\n  }\n  .p-lg-4 {\n    padding: 1.5rem !important;\n  }\n  .p-lg-5 {\n    padding: 3rem !important;\n  }\n  .px-lg-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-lg-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-lg-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-lg-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-lg-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-lg-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-lg-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-lg-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-lg-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-lg-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-lg-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-lg-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-lg-0 {\n    padding-top: 0 !important;\n  }\n  .pt-lg-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-lg-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-lg-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-lg-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-lg-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-lg-0 {\n    padding-left: 0 !important;\n  }\n  .pe-lg-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-lg-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-lg-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-lg-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-lg-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-lg-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-lg-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-lg-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-lg-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-lg-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-lg-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-lg-0 {\n    padding-right: 0 !important;\n  }\n  .ps-lg-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-lg-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-lg-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-lg-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-lg-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-lg-0 {\n    gap: 0 !important;\n  }\n  .gap-lg-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-lg-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-lg-3 {\n    gap: 1rem !important;\n  }\n  .gap-lg-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-lg-5 {\n    gap: 3rem !important;\n  }\n  .text-lg-start {\n    text-align: right !important;\n  }\n  .text-lg-end {\n    text-align: left !important;\n  }\n  .text-lg-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .float-xl-start {\n    float: right !important;\n  }\n  .float-xl-end {\n    float: left !important;\n  }\n  .float-xl-none {\n    float: none !important;\n  }\n  .d-xl-inline {\n    display: inline !important;\n  }\n  .d-xl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xl-block {\n    display: block !important;\n  }\n  .d-xl-grid {\n    display: grid !important;\n  }\n  .d-xl-table {\n    display: table !important;\n  }\n  .d-xl-table-row {\n    display: table-row !important;\n  }\n  .d-xl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xl-flex {\n    display: flex !important;\n  }\n  .d-xl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xl-none {\n    display: none !important;\n  }\n  .flex-xl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xl-row {\n    flex-direction: row !important;\n  }\n  .flex-xl-column {\n    flex-direction: column !important;\n  }\n  .flex-xl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xl-center {\n    align-items: center !important;\n  }\n  .align-items-xl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xl-center {\n    align-content: center !important;\n  }\n  .align-content-xl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xl-center {\n    align-self: center !important;\n  }\n  .align-self-xl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xl-first {\n    order: -1 !important;\n  }\n  .order-xl-0 {\n    order: 0 !important;\n  }\n  .order-xl-1 {\n    order: 1 !important;\n  }\n  .order-xl-2 {\n    order: 2 !important;\n  }\n  .order-xl-3 {\n    order: 3 !important;\n  }\n  .order-xl-4 {\n    order: 4 !important;\n  }\n  .order-xl-5 {\n    order: 5 !important;\n  }\n  .order-xl-last {\n    order: 6 !important;\n  }\n  .m-xl-0 {\n    margin: 0 !important;\n  }\n  .m-xl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xl-3 {\n    margin: 1rem !important;\n  }\n  .m-xl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xl-5 {\n    margin: 3rem !important;\n  }\n  .m-xl-auto {\n    margin: auto !important;\n  }\n  .mx-xl-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-xl-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-xl-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-xl-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-xl-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-xl-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-xl-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-xl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xl-auto {\n    margin-top: auto !important;\n  }\n  .me-xl-0 {\n    margin-left: 0 !important;\n  }\n  .me-xl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-xl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-xl-3 {\n    margin-left: 1rem !important;\n  }\n  .me-xl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-xl-5 {\n    margin-left: 3rem !important;\n  }\n  .me-xl-auto {\n    margin-left: auto !important;\n  }\n  .mb-xl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xl-0 {\n    margin-right: 0 !important;\n  }\n  .ms-xl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-xl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-xl-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-xl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-xl-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-xl-auto {\n    margin-right: auto !important;\n  }\n  .p-xl-0 {\n    padding: 0 !important;\n  }\n  .p-xl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xl-3 {\n    padding: 1rem !important;\n  }\n  .p-xl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xl-5 {\n    padding: 3rem !important;\n  }\n  .px-xl-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-xl-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-xl-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-xl-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-xl-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-xl-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-xl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xl-0 {\n    padding-left: 0 !important;\n  }\n  .pe-xl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-xl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-xl-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-xl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-xl-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-xl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xl-0 {\n    padding-right: 0 !important;\n  }\n  .ps-xl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-xl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-xl-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-xl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-xl-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-xl-0 {\n    gap: 0 !important;\n  }\n  .gap-xl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xl-5 {\n    gap: 3rem !important;\n  }\n  .text-xl-start {\n    text-align: right !important;\n  }\n  .text-xl-end {\n    text-align: left !important;\n  }\n  .text-xl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1400px) {\n  .float-xxl-start {\n    float: right !important;\n  }\n  .float-xxl-end {\n    float: left !important;\n  }\n  .float-xxl-none {\n    float: none !important;\n  }\n  .d-xxl-inline {\n    display: inline !important;\n  }\n  .d-xxl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xxl-block {\n    display: block !important;\n  }\n  .d-xxl-grid {\n    display: grid !important;\n  }\n  .d-xxl-table {\n    display: table !important;\n  }\n  .d-xxl-table-row {\n    display: table-row !important;\n  }\n  .d-xxl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xxl-flex {\n    display: flex !important;\n  }\n  .d-xxl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xxl-none {\n    display: none !important;\n  }\n  .flex-xxl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xxl-row {\n    flex-direction: row !important;\n  }\n  .flex-xxl-column {\n    flex-direction: column !important;\n  }\n  .flex-xxl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xxl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xxl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xxl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xxl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xxl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xxl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xxl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xxl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xxl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xxl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xxl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xxl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xxl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xxl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xxl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xxl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xxl-center {\n    align-items: center !important;\n  }\n  .align-items-xxl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xxl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xxl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xxl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xxl-center {\n    align-content: center !important;\n  }\n  .align-content-xxl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xxl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xxl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xxl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xxl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xxl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xxl-center {\n    align-self: center !important;\n  }\n  .align-self-xxl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xxl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xxl-first {\n    order: -1 !important;\n  }\n  .order-xxl-0 {\n    order: 0 !important;\n  }\n  .order-xxl-1 {\n    order: 1 !important;\n  }\n  .order-xxl-2 {\n    order: 2 !important;\n  }\n  .order-xxl-3 {\n    order: 3 !important;\n  }\n  .order-xxl-4 {\n    order: 4 !important;\n  }\n  .order-xxl-5 {\n    order: 5 !important;\n  }\n  .order-xxl-last {\n    order: 6 !important;\n  }\n  .m-xxl-0 {\n    margin: 0 !important;\n  }\n  .m-xxl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xxl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xxl-3 {\n    margin: 1rem !important;\n  }\n  .m-xxl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xxl-5 {\n    margin: 3rem !important;\n  }\n  .m-xxl-auto {\n    margin: auto !important;\n  }\n  .mx-xxl-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-xxl-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-xxl-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-xxl-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-xxl-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-xxl-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-xxl-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-xxl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xxl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xxl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xxl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xxl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xxl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xxl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xxl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xxl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xxl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xxl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xxl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xxl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xxl-auto {\n    margin-top: auto !important;\n  }\n  .me-xxl-0 {\n    margin-left: 0 !important;\n  }\n  .me-xxl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-xxl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-xxl-3 {\n    margin-left: 1rem !important;\n  }\n  .me-xxl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-xxl-5 {\n    margin-left: 3rem !important;\n  }\n  .me-xxl-auto {\n    margin-left: auto !important;\n  }\n  .mb-xxl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xxl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xxl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xxl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xxl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xxl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xxl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xxl-0 {\n    margin-right: 0 !important;\n  }\n  .ms-xxl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-xxl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-xxl-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-xxl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-xxl-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-xxl-auto {\n    margin-right: auto !important;\n  }\n  .p-xxl-0 {\n    padding: 0 !important;\n  }\n  .p-xxl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xxl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xxl-3 {\n    padding: 1rem !important;\n  }\n  .p-xxl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xxl-5 {\n    padding: 3rem !important;\n  }\n  .px-xxl-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-xxl-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-xxl-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-xxl-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-xxl-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-xxl-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-xxl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xxl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xxl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xxl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xxl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xxl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xxl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xxl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xxl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xxl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xxl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xxl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xxl-0 {\n    padding-left: 0 !important;\n  }\n  .pe-xxl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-xxl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-xxl-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-xxl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-xxl-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-xxl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xxl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xxl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xxl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xxl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xxl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xxl-0 {\n    padding-right: 0 !important;\n  }\n  .ps-xxl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-xxl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-xxl-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-xxl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-xxl-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-xxl-0 {\n    gap: 0 !important;\n  }\n  .gap-xxl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xxl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xxl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xxl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xxl-5 {\n    gap: 3rem !important;\n  }\n  .text-xxl-start {\n    text-align: right !important;\n  }\n  .text-xxl-end {\n    text-align: left !important;\n  }\n  .text-xxl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .fs-1 {\n    font-size: 2.5rem !important;\n  }\n  .fs-2 {\n    font-size: 2rem !important;\n  }\n  .fs-3 {\n    font-size: 1.75rem !important;\n  }\n  .fs-4 {\n    font-size: 1.5rem !important;\n  }\n}\n@media print {\n  .d-print-inline {\n    display: inline !important;\n  }\n  .d-print-inline-block {\n    display: inline-block !important;\n  }\n  .d-print-block {\n    display: block !important;\n  }\n  .d-print-grid {\n    display: grid !important;\n  }\n  .d-print-table {\n    display: table !important;\n  }\n  .d-print-table-row {\n    display: table-row !important;\n  }\n  .d-print-table-cell {\n    display: table-cell !important;\n  }\n  .d-print-flex {\n    display: flex !important;\n  }\n  .d-print-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-print-none {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap-utilities.rtl.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap.css",
    "content": "@charset \"UTF-8\";\n/*!\n * Bootstrap  v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  :root {\n    scroll-behavior: smooth;\n  }\n}\n\nbody {\n  margin: 0;\n  font-family: var(--bs-body-font-family);\n  font-size: var(--bs-body-font-size);\n  font-weight: var(--bs-body-font-weight);\n  line-height: var(--bs-body-line-height);\n  color: var(--bs-body-color);\n  text-align: var(--bs-body-text-align);\n  background-color: var(--bs-body-bg);\n  -webkit-text-size-adjust: 100%;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nhr {\n  margin: 1rem 0;\n  color: inherit;\n  border: 0;\n  border-top: 1px solid;\n  opacity: 0.25;\n}\n\nh6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {\n  margin-top: 0;\n  margin-bottom: 0.5rem;\n  font-weight: 500;\n  line-height: 1.2;\n}\n\nh1, .h1 {\n  font-size: calc(1.375rem + 1.5vw);\n}\n@media (min-width: 1200px) {\n  h1, .h1 {\n    font-size: 2.5rem;\n  }\n}\n\nh2, .h2 {\n  font-size: calc(1.325rem + 0.9vw);\n}\n@media (min-width: 1200px) {\n  h2, .h2 {\n    font-size: 2rem;\n  }\n}\n\nh3, .h3 {\n  font-size: calc(1.3rem + 0.6vw);\n}\n@media (min-width: 1200px) {\n  h3, .h3 {\n    font-size: 1.75rem;\n  }\n}\n\nh4, .h4 {\n  font-size: calc(1.275rem + 0.3vw);\n}\n@media (min-width: 1200px) {\n  h4, .h4 {\n    font-size: 1.5rem;\n  }\n}\n\nh5, .h5 {\n  font-size: 1.25rem;\n}\n\nh6, .h6 {\n  font-size: 1rem;\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nabbr[title] {\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n  cursor: help;\n  -webkit-text-decoration-skip-ink: none;\n  text-decoration-skip-ink: none;\n}\n\naddress {\n  margin-bottom: 1rem;\n  font-style: normal;\n  line-height: inherit;\n}\n\nol,\nul {\n  padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n  margin-bottom: 0;\n}\n\ndt {\n  font-weight: 700;\n}\n\ndd {\n  margin-bottom: 0.5rem;\n  margin-left: 0;\n}\n\nblockquote {\n  margin: 0 0 1rem;\n}\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\nsmall, .small {\n  font-size: 0.875em;\n}\n\nmark, .mark {\n  padding: 0.1875em;\n  background-color: var(--bs-highlight-bg);\n}\n\nsub,\nsup {\n  position: relative;\n  font-size: 0.75em;\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\na {\n  color: var(--bs-link-color);\n  text-decoration: underline;\n}\na:hover {\n  color: var(--bs-link-hover-color);\n}\n\na:not([href]):not([class]), a:not([href]):not([class]):hover {\n  color: inherit;\n  text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: var(--bs-font-monospace);\n  font-size: 1em;\n}\n\npre {\n  display: block;\n  margin-top: 0;\n  margin-bottom: 1rem;\n  overflow: auto;\n  font-size: 0.875em;\n}\npre code {\n  font-size: inherit;\n  color: inherit;\n  word-break: normal;\n}\n\ncode {\n  font-size: 0.875em;\n  color: var(--bs-code-color);\n  word-wrap: break-word;\n}\na > code {\n  color: inherit;\n}\n\nkbd {\n  padding: 0.1875rem 0.375rem;\n  font-size: 0.875em;\n  color: var(--bs-body-bg);\n  background-color: var(--bs-body-color);\n  border-radius: 0.25rem;\n}\nkbd kbd {\n  padding: 0;\n  font-size: 1em;\n}\n\nfigure {\n  margin: 0 0 1rem;\n}\n\nimg,\nsvg {\n  vertical-align: middle;\n}\n\ntable {\n  caption-side: bottom;\n  border-collapse: collapse;\n}\n\ncaption {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: #6c757d;\n  text-align: left;\n}\n\nth {\n  text-align: inherit;\n  text-align: -webkit-match-parent;\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n}\n\nlabel {\n  display: inline-block;\n}\n\nbutton {\n  border-radius: 0;\n}\n\nbutton:focus:not(:focus-visible) {\n  outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n  margin: 0;\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n[role=button] {\n  cursor: pointer;\n}\n\nselect {\n  word-wrap: normal;\n}\nselect:disabled {\n  opacity: 1;\n}\n\n[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {\n  display: none !important;\n}\n\nbutton,\n[type=button],\n[type=reset],\n[type=submit] {\n  -webkit-appearance: button;\n}\nbutton:not(:disabled),\n[type=button]:not(:disabled),\n[type=reset]:not(:disabled),\n[type=submit]:not(:disabled) {\n  cursor: pointer;\n}\n\n::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\ntextarea {\n  resize: vertical;\n}\n\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\n\nlegend {\n  float: left;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 0.5rem;\n  font-size: calc(1.275rem + 0.3vw);\n  line-height: inherit;\n}\n@media (min-width: 1200px) {\n  legend {\n    font-size: 1.5rem;\n  }\n}\nlegend + * {\n  clear: left;\n}\n\n::-webkit-datetime-edit-fields-wrapper,\n::-webkit-datetime-edit-text,\n::-webkit-datetime-edit-minute,\n::-webkit-datetime-edit-hour-field,\n::-webkit-datetime-edit-day-field,\n::-webkit-datetime-edit-month-field,\n::-webkit-datetime-edit-year-field {\n  padding: 0;\n}\n\n::-webkit-inner-spin-button {\n  height: auto;\n}\n\n[type=search] {\n  outline-offset: -2px;\n  -webkit-appearance: textfield;\n}\n\n/* rtl:raw:\n[type=\"tel\"],\n[type=\"url\"],\n[type=\"email\"],\n[type=\"number\"] {\n  direction: ltr;\n}\n*/\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n::-webkit-color-swatch-wrapper {\n  padding: 0;\n}\n\n::-webkit-file-upload-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\n::file-selector-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\noutput {\n  display: inline-block;\n}\n\niframe {\n  border: 0;\n}\n\nsummary {\n  display: list-item;\n  cursor: pointer;\n}\n\nprogress {\n  vertical-align: baseline;\n}\n\n[hidden] {\n  display: none !important;\n}\n\n.lead {\n  font-size: 1.25rem;\n  font-weight: 300;\n}\n\n.display-1 {\n  font-size: calc(1.625rem + 4.5vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-1 {\n    font-size: 5rem;\n  }\n}\n\n.display-2 {\n  font-size: calc(1.575rem + 3.9vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-2 {\n    font-size: 4.5rem;\n  }\n}\n\n.display-3 {\n  font-size: calc(1.525rem + 3.3vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-3 {\n    font-size: 4rem;\n  }\n}\n\n.display-4 {\n  font-size: calc(1.475rem + 2.7vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-4 {\n    font-size: 3.5rem;\n  }\n}\n\n.display-5 {\n  font-size: calc(1.425rem + 2.1vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-5 {\n    font-size: 3rem;\n  }\n}\n\n.display-6 {\n  font-size: calc(1.375rem + 1.5vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-6 {\n    font-size: 2.5rem;\n  }\n}\n\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n\n.list-inline {\n  padding-left: 0;\n  list-style: none;\n}\n\n.list-inline-item {\n  display: inline-block;\n}\n.list-inline-item:not(:last-child) {\n  margin-right: 0.5rem;\n}\n\n.initialism {\n  font-size: 0.875em;\n  text-transform: uppercase;\n}\n\n.blockquote {\n  margin-bottom: 1rem;\n  font-size: 1.25rem;\n}\n.blockquote > :last-child {\n  margin-bottom: 0;\n}\n\n.blockquote-footer {\n  margin-top: -1rem;\n  margin-bottom: 1rem;\n  font-size: 0.875em;\n  color: #6c757d;\n}\n.blockquote-footer::before {\n  content: \"— \";\n}\n\n.img-fluid {\n  max-width: 100%;\n  height: auto;\n}\n\n.img-thumbnail {\n  padding: 0.25rem;\n  background-color: #fff;\n  border: 1px solid var(--bs-border-color);\n  border-radius: 0.375rem;\n  max-width: 100%;\n  height: auto;\n}\n\n.figure {\n  display: inline-block;\n}\n\n.figure-img {\n  margin-bottom: 0.5rem;\n  line-height: 1;\n}\n\n.figure-caption {\n  font-size: 0.875em;\n  color: #6c757d;\n}\n\n.container,\n.container-fluid,\n.container-xxl,\n.container-xl,\n.container-lg,\n.container-md,\n.container-sm {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  width: 100%;\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  margin-right: auto;\n  margin-left: auto;\n}\n\n@media (min-width: 576px) {\n  .container-sm, .container {\n    max-width: 540px;\n  }\n}\n@media (min-width: 768px) {\n  .container-md, .container-sm, .container {\n    max-width: 720px;\n  }\n}\n@media (min-width: 992px) {\n  .container-lg, .container-md, .container-sm, .container {\n    max-width: 960px;\n  }\n}\n@media (min-width: 1200px) {\n  .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1140px;\n  }\n}\n@media (min-width: 1400px) {\n  .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1320px;\n  }\n}\n.row {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  display: flex;\n  flex-wrap: wrap;\n  margin-top: calc(-1 * var(--bs-gutter-y));\n  margin-right: calc(-0.5 * var(--bs-gutter-x));\n  margin-left: calc(-0.5 * var(--bs-gutter-x));\n}\n.row > * {\n  flex-shrink: 0;\n  width: 100%;\n  max-width: 100%;\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  margin-top: var(--bs-gutter-y);\n}\n\n.col {\n  flex: 1 0 0%;\n}\n\n.row-cols-auto > * {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.row-cols-1 > * {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.row-cols-2 > * {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.row-cols-3 > * {\n  flex: 0 0 auto;\n  width: 33.3333333333%;\n}\n\n.row-cols-4 > * {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.row-cols-5 > * {\n  flex: 0 0 auto;\n  width: 20%;\n}\n\n.row-cols-6 > * {\n  flex: 0 0 auto;\n  width: 16.6666666667%;\n}\n\n.col-auto {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.col-1 {\n  flex: 0 0 auto;\n  width: 8.33333333%;\n}\n\n.col-2 {\n  flex: 0 0 auto;\n  width: 16.66666667%;\n}\n\n.col-3 {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.col-4 {\n  flex: 0 0 auto;\n  width: 33.33333333%;\n}\n\n.col-5 {\n  flex: 0 0 auto;\n  width: 41.66666667%;\n}\n\n.col-6 {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.col-7 {\n  flex: 0 0 auto;\n  width: 58.33333333%;\n}\n\n.col-8 {\n  flex: 0 0 auto;\n  width: 66.66666667%;\n}\n\n.col-9 {\n  flex: 0 0 auto;\n  width: 75%;\n}\n\n.col-10 {\n  flex: 0 0 auto;\n  width: 83.33333333%;\n}\n\n.col-11 {\n  flex: 0 0 auto;\n  width: 91.66666667%;\n}\n\n.col-12 {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.offset-1 {\n  margin-left: 8.33333333%;\n}\n\n.offset-2 {\n  margin-left: 16.66666667%;\n}\n\n.offset-3 {\n  margin-left: 25%;\n}\n\n.offset-4 {\n  margin-left: 33.33333333%;\n}\n\n.offset-5 {\n  margin-left: 41.66666667%;\n}\n\n.offset-6 {\n  margin-left: 50%;\n}\n\n.offset-7 {\n  margin-left: 58.33333333%;\n}\n\n.offset-8 {\n  margin-left: 66.66666667%;\n}\n\n.offset-9 {\n  margin-left: 75%;\n}\n\n.offset-10 {\n  margin-left: 83.33333333%;\n}\n\n.offset-11 {\n  margin-left: 91.66666667%;\n}\n\n.g-0,\n.gx-0 {\n  --bs-gutter-x: 0;\n}\n\n.g-0,\n.gy-0 {\n  --bs-gutter-y: 0;\n}\n\n.g-1,\n.gx-1 {\n  --bs-gutter-x: 0.25rem;\n}\n\n.g-1,\n.gy-1 {\n  --bs-gutter-y: 0.25rem;\n}\n\n.g-2,\n.gx-2 {\n  --bs-gutter-x: 0.5rem;\n}\n\n.g-2,\n.gy-2 {\n  --bs-gutter-y: 0.5rem;\n}\n\n.g-3,\n.gx-3 {\n  --bs-gutter-x: 1rem;\n}\n\n.g-3,\n.gy-3 {\n  --bs-gutter-y: 1rem;\n}\n\n.g-4,\n.gx-4 {\n  --bs-gutter-x: 1.5rem;\n}\n\n.g-4,\n.gy-4 {\n  --bs-gutter-y: 1.5rem;\n}\n\n.g-5,\n.gx-5 {\n  --bs-gutter-x: 3rem;\n}\n\n.g-5,\n.gy-5 {\n  --bs-gutter-y: 3rem;\n}\n\n@media (min-width: 576px) {\n  .col-sm {\n    flex: 1 0 0%;\n  }\n  .row-cols-sm-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-sm-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-sm-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-sm-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-sm-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-sm-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-sm-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-sm-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-sm-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-sm-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-sm-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-sm-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-sm-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-sm-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-sm-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-sm-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-sm-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-sm-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-sm-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-sm-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-sm-0 {\n    margin-left: 0;\n  }\n  .offset-sm-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-sm-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-sm-3 {\n    margin-left: 25%;\n  }\n  .offset-sm-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-sm-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-sm-6 {\n    margin-left: 50%;\n  }\n  .offset-sm-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-sm-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-sm-9 {\n    margin-left: 75%;\n  }\n  .offset-sm-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-sm-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-sm-0,\n.gx-sm-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-sm-0,\n.gy-sm-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-sm-1,\n.gx-sm-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-sm-1,\n.gy-sm-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-sm-2,\n.gx-sm-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-sm-2,\n.gy-sm-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-sm-3,\n.gx-sm-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-sm-3,\n.gy-sm-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-sm-4,\n.gx-sm-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-sm-4,\n.gy-sm-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-sm-5,\n.gx-sm-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-sm-5,\n.gy-sm-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 768px) {\n  .col-md {\n    flex: 1 0 0%;\n  }\n  .row-cols-md-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-md-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-md-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-md-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-md-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-md-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-md-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-md-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-md-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-md-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-md-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-md-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-md-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-md-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-md-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-md-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-md-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-md-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-md-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-md-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-md-0 {\n    margin-left: 0;\n  }\n  .offset-md-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-md-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-md-3 {\n    margin-left: 25%;\n  }\n  .offset-md-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-md-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-md-6 {\n    margin-left: 50%;\n  }\n  .offset-md-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-md-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-md-9 {\n    margin-left: 75%;\n  }\n  .offset-md-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-md-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-md-0,\n.gx-md-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-md-0,\n.gy-md-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-md-1,\n.gx-md-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-md-1,\n.gy-md-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-md-2,\n.gx-md-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-md-2,\n.gy-md-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-md-3,\n.gx-md-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-md-3,\n.gy-md-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-md-4,\n.gx-md-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-md-4,\n.gy-md-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-md-5,\n.gx-md-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-md-5,\n.gy-md-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 992px) {\n  .col-lg {\n    flex: 1 0 0%;\n  }\n  .row-cols-lg-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-lg-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-lg-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-lg-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-lg-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-lg-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-lg-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-lg-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-lg-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-lg-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-lg-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-lg-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-lg-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-lg-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-lg-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-lg-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-lg-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-lg-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-lg-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-lg-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-lg-0 {\n    margin-left: 0;\n  }\n  .offset-lg-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-lg-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-lg-3 {\n    margin-left: 25%;\n  }\n  .offset-lg-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-lg-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-lg-6 {\n    margin-left: 50%;\n  }\n  .offset-lg-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-lg-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-lg-9 {\n    margin-left: 75%;\n  }\n  .offset-lg-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-lg-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-lg-0,\n.gx-lg-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-lg-0,\n.gy-lg-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-lg-1,\n.gx-lg-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-lg-1,\n.gy-lg-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-lg-2,\n.gx-lg-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-lg-2,\n.gy-lg-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-lg-3,\n.gx-lg-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-lg-3,\n.gy-lg-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-lg-4,\n.gx-lg-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-lg-4,\n.gy-lg-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-lg-5,\n.gx-lg-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-lg-5,\n.gy-lg-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1200px) {\n  .col-xl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xl-0 {\n    margin-left: 0;\n  }\n  .offset-xl-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-xl-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-xl-3 {\n    margin-left: 25%;\n  }\n  .offset-xl-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-xl-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-xl-6 {\n    margin-left: 50%;\n  }\n  .offset-xl-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-xl-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-xl-9 {\n    margin-left: 75%;\n  }\n  .offset-xl-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-xl-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-xl-0,\n.gx-xl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xl-0,\n.gy-xl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xl-1,\n.gx-xl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xl-1,\n.gy-xl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xl-2,\n.gx-xl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xl-2,\n.gy-xl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xl-3,\n.gx-xl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xl-3,\n.gy-xl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xl-4,\n.gx-xl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xl-4,\n.gy-xl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xl-5,\n.gx-xl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xl-5,\n.gy-xl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1400px) {\n  .col-xxl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xxl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xxl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xxl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xxl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xxl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xxl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xxl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xxl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xxl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xxl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xxl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xxl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xxl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xxl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xxl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xxl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xxl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xxl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xxl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xxl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xxl-0 {\n    margin-left: 0;\n  }\n  .offset-xxl-1 {\n    margin-left: 8.33333333%;\n  }\n  .offset-xxl-2 {\n    margin-left: 16.66666667%;\n  }\n  .offset-xxl-3 {\n    margin-left: 25%;\n  }\n  .offset-xxl-4 {\n    margin-left: 33.33333333%;\n  }\n  .offset-xxl-5 {\n    margin-left: 41.66666667%;\n  }\n  .offset-xxl-6 {\n    margin-left: 50%;\n  }\n  .offset-xxl-7 {\n    margin-left: 58.33333333%;\n  }\n  .offset-xxl-8 {\n    margin-left: 66.66666667%;\n  }\n  .offset-xxl-9 {\n    margin-left: 75%;\n  }\n  .offset-xxl-10 {\n    margin-left: 83.33333333%;\n  }\n  .offset-xxl-11 {\n    margin-left: 91.66666667%;\n  }\n  .g-xxl-0,\n.gx-xxl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xxl-0,\n.gy-xxl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xxl-1,\n.gx-xxl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xxl-1,\n.gy-xxl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xxl-2,\n.gx-xxl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xxl-2,\n.gy-xxl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xxl-3,\n.gx-xxl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xxl-3,\n.gy-xxl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xxl-4,\n.gx-xxl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xxl-4,\n.gy-xxl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xxl-5,\n.gx-xxl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xxl-5,\n.gy-xxl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n.table {\n  --bs-table-color: var(--bs-body-color);\n  --bs-table-bg: transparent;\n  --bs-table-border-color: var(--bs-border-color);\n  --bs-table-accent-bg: transparent;\n  --bs-table-striped-color: var(--bs-body-color);\n  --bs-table-striped-bg: rgba(0, 0, 0, 0.05);\n  --bs-table-active-color: var(--bs-body-color);\n  --bs-table-active-bg: rgba(0, 0, 0, 0.1);\n  --bs-table-hover-color: var(--bs-body-color);\n  --bs-table-hover-bg: rgba(0, 0, 0, 0.075);\n  width: 100%;\n  margin-bottom: 1rem;\n  color: var(--bs-table-color);\n  vertical-align: top;\n  border-color: var(--bs-table-border-color);\n}\n.table > :not(caption) > * > * {\n  padding: 0.5rem 0.5rem;\n  background-color: var(--bs-table-bg);\n  border-bottom-width: 1px;\n  box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);\n}\n.table > tbody {\n  vertical-align: inherit;\n}\n.table > thead {\n  vertical-align: bottom;\n}\n\n.table-group-divider {\n  border-top: 2px solid currentcolor;\n}\n\n.caption-top {\n  caption-side: top;\n}\n\n.table-sm > :not(caption) > * > * {\n  padding: 0.25rem 0.25rem;\n}\n\n.table-bordered > :not(caption) > * {\n  border-width: 1px 0;\n}\n.table-bordered > :not(caption) > * > * {\n  border-width: 0 1px;\n}\n\n.table-borderless > :not(caption) > * > * {\n  border-bottom-width: 0;\n}\n.table-borderless > :not(:first-child) {\n  border-top-width: 0;\n}\n\n.table-striped > tbody > tr:nth-of-type(odd) > * {\n  --bs-table-accent-bg: var(--bs-table-striped-bg);\n  color: var(--bs-table-striped-color);\n}\n\n.table-striped-columns > :not(caption) > tr > :nth-child(even) {\n  --bs-table-accent-bg: var(--bs-table-striped-bg);\n  color: var(--bs-table-striped-color);\n}\n\n.table-active {\n  --bs-table-accent-bg: var(--bs-table-active-bg);\n  color: var(--bs-table-active-color);\n}\n\n.table-hover > tbody > tr:hover > * {\n  --bs-table-accent-bg: var(--bs-table-hover-bg);\n  color: var(--bs-table-hover-color);\n}\n\n.table-primary {\n  --bs-table-color: #000;\n  --bs-table-bg: #cfe2ff;\n  --bs-table-border-color: #bacbe6;\n  --bs-table-striped-bg: #c5d7f2;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #bacbe6;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #bfd1ec;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-secondary {\n  --bs-table-color: #000;\n  --bs-table-bg: #e2e3e5;\n  --bs-table-border-color: #cbccce;\n  --bs-table-striped-bg: #d7d8da;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #cbccce;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #d1d2d4;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-success {\n  --bs-table-color: #000;\n  --bs-table-bg: #d1e7dd;\n  --bs-table-border-color: #bcd0c7;\n  --bs-table-striped-bg: #c7dbd2;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #bcd0c7;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #c1d6cc;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-info {\n  --bs-table-color: #000;\n  --bs-table-bg: #cff4fc;\n  --bs-table-border-color: #badce3;\n  --bs-table-striped-bg: #c5e8ef;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #badce3;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #bfe2e9;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-warning {\n  --bs-table-color: #000;\n  --bs-table-bg: #fff3cd;\n  --bs-table-border-color: #e6dbb9;\n  --bs-table-striped-bg: #f2e7c3;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #e6dbb9;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #ece1be;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-danger {\n  --bs-table-color: #000;\n  --bs-table-bg: #f8d7da;\n  --bs-table-border-color: #dfc2c4;\n  --bs-table-striped-bg: #eccccf;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #dfc2c4;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #e5c7ca;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-light {\n  --bs-table-color: #000;\n  --bs-table-bg: #f8f9fa;\n  --bs-table-border-color: #dfe0e1;\n  --bs-table-striped-bg: #ecedee;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #dfe0e1;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #e5e6e7;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-dark {\n  --bs-table-color: #fff;\n  --bs-table-bg: #212529;\n  --bs-table-border-color: #373b3e;\n  --bs-table-striped-bg: #2c3034;\n  --bs-table-striped-color: #fff;\n  --bs-table-active-bg: #373b3e;\n  --bs-table-active-color: #fff;\n  --bs-table-hover-bg: #323539;\n  --bs-table-hover-color: #fff;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-responsive {\n  overflow-x: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n@media (max-width: 575.98px) {\n  .table-responsive-sm {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 767.98px) {\n  .table-responsive-md {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 991.98px) {\n  .table-responsive-lg {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 1199.98px) {\n  .table-responsive-xl {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 1399.98px) {\n  .table-responsive-xxl {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n.form-label {\n  margin-bottom: 0.5rem;\n}\n\n.col-form-label {\n  padding-top: calc(0.375rem + 1px);\n  padding-bottom: calc(0.375rem + 1px);\n  margin-bottom: 0;\n  font-size: inherit;\n  line-height: 1.5;\n}\n\n.col-form-label-lg {\n  padding-top: calc(0.5rem + 1px);\n  padding-bottom: calc(0.5rem + 1px);\n  font-size: 1.25rem;\n}\n\n.col-form-label-sm {\n  padding-top: calc(0.25rem + 1px);\n  padding-bottom: calc(0.25rem + 1px);\n  font-size: 0.875rem;\n}\n\n.form-text {\n  margin-top: 0.25rem;\n  font-size: 0.875em;\n  color: #6c757d;\n}\n\n.form-control {\n  display: block;\n  width: 100%;\n  padding: 0.375rem 0.75rem;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5;\n  color: #212529;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 1px solid #ced4da;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  border-radius: 0.375rem;\n  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-control {\n    transition: none;\n  }\n}\n.form-control[type=file] {\n  overflow: hidden;\n}\n.form-control[type=file]:not(:disabled):not([readonly]) {\n  cursor: pointer;\n}\n.form-control:focus {\n  color: #212529;\n  background-color: #fff;\n  border-color: #86b7fe;\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-control::-webkit-date-and-time-value {\n  height: 1.5em;\n}\n.form-control::-moz-placeholder {\n  color: #6c757d;\n  opacity: 1;\n}\n.form-control::placeholder {\n  color: #6c757d;\n  opacity: 1;\n}\n.form-control:disabled {\n  background-color: #e9ecef;\n  opacity: 1;\n}\n.form-control::-webkit-file-upload-button {\n  padding: 0.375rem 0.75rem;\n  margin: -0.375rem -0.75rem;\n  -webkit-margin-end: 0.75rem;\n  margin-inline-end: 0.75rem;\n  color: #212529;\n  background-color: #e9ecef;\n  pointer-events: none;\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n  border-inline-end-width: 1px;\n  border-radius: 0;\n  -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n.form-control::file-selector-button {\n  padding: 0.375rem 0.75rem;\n  margin: -0.375rem -0.75rem;\n  -webkit-margin-end: 0.75rem;\n  margin-inline-end: 0.75rem;\n  color: #212529;\n  background-color: #e9ecef;\n  pointer-events: none;\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n  border-inline-end-width: 1px;\n  border-radius: 0;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-control::-webkit-file-upload-button {\n    -webkit-transition: none;\n    transition: none;\n  }\n  .form-control::file-selector-button {\n    transition: none;\n  }\n}\n.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button {\n  background-color: #dde0e3;\n}\n.form-control:hover:not(:disabled):not([readonly])::file-selector-button {\n  background-color: #dde0e3;\n}\n\n.form-control-plaintext {\n  display: block;\n  width: 100%;\n  padding: 0.375rem 0;\n  margin-bottom: 0;\n  line-height: 1.5;\n  color: #212529;\n  background-color: transparent;\n  border: solid transparent;\n  border-width: 1px 0;\n}\n.form-control-plaintext:focus {\n  outline: 0;\n}\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n  padding-right: 0;\n  padding-left: 0;\n}\n\n.form-control-sm {\n  min-height: calc(1.5em + 0.5rem + 2px);\n  padding: 0.25rem 0.5rem;\n  font-size: 0.875rem;\n  border-radius: 0.25rem;\n}\n.form-control-sm::-webkit-file-upload-button {\n  padding: 0.25rem 0.5rem;\n  margin: -0.25rem -0.5rem;\n  -webkit-margin-end: 0.5rem;\n  margin-inline-end: 0.5rem;\n}\n.form-control-sm::file-selector-button {\n  padding: 0.25rem 0.5rem;\n  margin: -0.25rem -0.5rem;\n  -webkit-margin-end: 0.5rem;\n  margin-inline-end: 0.5rem;\n}\n\n.form-control-lg {\n  min-height: calc(1.5em + 1rem + 2px);\n  padding: 0.5rem 1rem;\n  font-size: 1.25rem;\n  border-radius: 0.5rem;\n}\n.form-control-lg::-webkit-file-upload-button {\n  padding: 0.5rem 1rem;\n  margin: -0.5rem -1rem;\n  -webkit-margin-end: 1rem;\n  margin-inline-end: 1rem;\n}\n.form-control-lg::file-selector-button {\n  padding: 0.5rem 1rem;\n  margin: -0.5rem -1rem;\n  -webkit-margin-end: 1rem;\n  margin-inline-end: 1rem;\n}\n\ntextarea.form-control {\n  min-height: calc(1.5em + 0.75rem + 2px);\n}\ntextarea.form-control-sm {\n  min-height: calc(1.5em + 0.5rem + 2px);\n}\ntextarea.form-control-lg {\n  min-height: calc(1.5em + 1rem + 2px);\n}\n\n.form-control-color {\n  width: 3rem;\n  height: calc(1.5em + 0.75rem + 2px);\n  padding: 0.375rem;\n}\n.form-control-color:not(:disabled):not([readonly]) {\n  cursor: pointer;\n}\n.form-control-color::-moz-color-swatch {\n  border: 0 !important;\n  border-radius: 0.375rem;\n}\n.form-control-color::-webkit-color-swatch {\n  border-radius: 0.375rem;\n}\n.form-control-color.form-control-sm {\n  height: calc(1.5em + 0.5rem + 2px);\n}\n.form-control-color.form-control-lg {\n  height: calc(1.5em + 1rem + 2px);\n}\n\n.form-select {\n  display: block;\n  width: 100%;\n  padding: 0.375rem 2.25rem 0.375rem 0.75rem;\n  -moz-padding-start: calc(0.75rem - 3px);\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5;\n  color: #212529;\n  background-color: #fff;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\");\n  background-repeat: no-repeat;\n  background-position: right 0.75rem center;\n  background-size: 16px 12px;\n  border: 1px solid #ced4da;\n  border-radius: 0.375rem;\n  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-select {\n    transition: none;\n  }\n}\n.form-select:focus {\n  border-color: #86b7fe;\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-select[multiple], .form-select[size]:not([size=\"1\"]) {\n  padding-right: 0.75rem;\n  background-image: none;\n}\n.form-select:disabled {\n  background-color: #e9ecef;\n}\n.form-select:-moz-focusring {\n  color: transparent;\n  text-shadow: 0 0 0 #212529;\n}\n\n.form-select-sm {\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  padding-left: 0.5rem;\n  font-size: 0.875rem;\n  border-radius: 0.25rem;\n}\n\n.form-select-lg {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  padding-left: 1rem;\n  font-size: 1.25rem;\n  border-radius: 0.5rem;\n}\n\n.form-check {\n  display: block;\n  min-height: 1.5rem;\n  padding-left: 1.5em;\n  margin-bottom: 0.125rem;\n}\n.form-check .form-check-input {\n  float: left;\n  margin-left: -1.5em;\n}\n\n.form-check-reverse {\n  padding-right: 1.5em;\n  padding-left: 0;\n  text-align: right;\n}\n.form-check-reverse .form-check-input {\n  float: right;\n  margin-right: -1.5em;\n  margin-left: 0;\n}\n\n.form-check-input {\n  width: 1em;\n  height: 1em;\n  margin-top: 0.25em;\n  vertical-align: top;\n  background-color: #fff;\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: contain;\n  border: 1px solid rgba(0, 0, 0, 0.25);\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  -webkit-print-color-adjust: exact;\n  color-adjust: exact;\n  print-color-adjust: exact;\n}\n.form-check-input[type=checkbox] {\n  border-radius: 0.25em;\n}\n.form-check-input[type=radio] {\n  border-radius: 50%;\n}\n.form-check-input:active {\n  filter: brightness(90%);\n}\n.form-check-input:focus {\n  border-color: #86b7fe;\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-check-input:checked {\n  background-color: #0d6efd;\n  border-color: #0d6efd;\n}\n.form-check-input:checked[type=checkbox] {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e\");\n}\n.form-check-input:checked[type=radio] {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e\");\n}\n.form-check-input[type=checkbox]:indeterminate {\n  background-color: #0d6efd;\n  border-color: #0d6efd;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e\");\n}\n.form-check-input:disabled {\n  pointer-events: none;\n  filter: none;\n  opacity: 0.5;\n}\n.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {\n  cursor: default;\n  opacity: 0.5;\n}\n\n.form-switch {\n  padding-left: 2.5em;\n}\n.form-switch .form-check-input {\n  width: 2em;\n  margin-left: -2.5em;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e\");\n  background-position: left center;\n  border-radius: 2em;\n  transition: background-position 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-switch .form-check-input {\n    transition: none;\n  }\n}\n.form-switch .form-check-input:focus {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e\");\n}\n.form-switch .form-check-input:checked {\n  background-position: right center;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n.form-switch.form-check-reverse {\n  padding-right: 2.5em;\n  padding-left: 0;\n}\n.form-switch.form-check-reverse .form-check-input {\n  margin-right: -2.5em;\n  margin-left: 0;\n}\n\n.form-check-inline {\n  display: inline-block;\n  margin-right: 1rem;\n}\n\n.btn-check {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.btn-check[disabled] + .btn, .btn-check:disabled + .btn {\n  pointer-events: none;\n  filter: none;\n  opacity: 0.65;\n}\n\n.form-range {\n  width: 100%;\n  height: 1.5rem;\n  padding: 0;\n  background-color: transparent;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n}\n.form-range:focus {\n  outline: 0;\n}\n.form-range:focus::-webkit-slider-thumb {\n  box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-range:focus::-moz-range-thumb {\n  box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-range::-moz-focus-outer {\n  border: 0;\n}\n.form-range::-webkit-slider-thumb {\n  width: 1rem;\n  height: 1rem;\n  margin-top: -0.25rem;\n  background-color: #0d6efd;\n  border: 0;\n  border-radius: 1rem;\n  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  -webkit-appearance: none;\n  appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-range::-webkit-slider-thumb {\n    -webkit-transition: none;\n    transition: none;\n  }\n}\n.form-range::-webkit-slider-thumb:active {\n  background-color: #b6d4fe;\n}\n.form-range::-webkit-slider-runnable-track {\n  width: 100%;\n  height: 0.5rem;\n  color: transparent;\n  cursor: pointer;\n  background-color: #dee2e6;\n  border-color: transparent;\n  border-radius: 1rem;\n}\n.form-range::-moz-range-thumb {\n  width: 1rem;\n  height: 1rem;\n  background-color: #0d6efd;\n  border: 0;\n  border-radius: 1rem;\n  -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  -moz-appearance: none;\n  appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-range::-moz-range-thumb {\n    -moz-transition: none;\n    transition: none;\n  }\n}\n.form-range::-moz-range-thumb:active {\n  background-color: #b6d4fe;\n}\n.form-range::-moz-range-track {\n  width: 100%;\n  height: 0.5rem;\n  color: transparent;\n  cursor: pointer;\n  background-color: #dee2e6;\n  border-color: transparent;\n  border-radius: 1rem;\n}\n.form-range:disabled {\n  pointer-events: none;\n}\n.form-range:disabled::-webkit-slider-thumb {\n  background-color: #adb5bd;\n}\n.form-range:disabled::-moz-range-thumb {\n  background-color: #adb5bd;\n}\n\n.form-floating {\n  position: relative;\n}\n.form-floating > .form-control,\n.form-floating > .form-control-plaintext,\n.form-floating > .form-select {\n  height: calc(3.5rem + 2px);\n  line-height: 1.25;\n}\n.form-floating > label {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  padding: 1rem 0.75rem;\n  overflow: hidden;\n  text-align: start;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  pointer-events: none;\n  border: 1px solid transparent;\n  transform-origin: 0 0;\n  transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-floating > label {\n    transition: none;\n  }\n}\n.form-floating > .form-control,\n.form-floating > .form-control-plaintext {\n  padding: 1rem 0.75rem;\n}\n.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder {\n  color: transparent;\n}\n.form-floating > .form-control::placeholder,\n.form-floating > .form-control-plaintext::placeholder {\n  color: transparent;\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown),\n.form-floating > .form-control-plaintext:focus,\n.form-floating > .form-control-plaintext:not(:placeholder-shown) {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:-webkit-autofill,\n.form-floating > .form-control-plaintext:-webkit-autofill {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-select {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label {\n  opacity: 0.65;\n  transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n.form-floating > .form-control:focus ~ label,\n.form-floating > .form-control:not(:placeholder-shown) ~ label,\n.form-floating > .form-control-plaintext ~ label,\n.form-floating > .form-select ~ label {\n  opacity: 0.65;\n  transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n.form-floating > .form-control:-webkit-autofill ~ label {\n  opacity: 0.65;\n  transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n.form-floating > .form-control-plaintext ~ label {\n  border-width: 1px 0;\n}\n\n.input-group {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: stretch;\n  width: 100%;\n}\n.input-group > .form-control,\n.input-group > .form-select,\n.input-group > .form-floating {\n  position: relative;\n  flex: 1 1 auto;\n  width: 1%;\n  min-width: 0;\n}\n.input-group > .form-control:focus,\n.input-group > .form-select:focus,\n.input-group > .form-floating:focus-within {\n  z-index: 5;\n}\n.input-group .btn {\n  position: relative;\n  z-index: 2;\n}\n.input-group .btn:focus {\n  z-index: 5;\n}\n\n.input-group-text {\n  display: flex;\n  align-items: center;\n  padding: 0.375rem 0.75rem;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5;\n  color: #212529;\n  text-align: center;\n  white-space: nowrap;\n  background-color: #e9ecef;\n  border: 1px solid #ced4da;\n  border-radius: 0.375rem;\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .form-select,\n.input-group-lg > .input-group-text,\n.input-group-lg > .btn {\n  padding: 0.5rem 1rem;\n  font-size: 1.25rem;\n  border-radius: 0.5rem;\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .form-select,\n.input-group-sm > .input-group-text,\n.input-group-sm > .btn {\n  padding: 0.25rem 0.5rem;\n  font-size: 0.875rem;\n  border-radius: 0.25rem;\n}\n\n.input-group-lg > .form-select,\n.input-group-sm > .form-select {\n  padding-right: 3rem;\n}\n\n.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3),\n.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control,\n.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4),\n.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-control,\n.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-select {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {\n  margin-left: -1px;\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group > .form-floating:not(:first-child) > .form-control,\n.input-group > .form-floating:not(:first-child) > .form-select {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n\n.valid-feedback {\n  display: none;\n  width: 100%;\n  margin-top: 0.25rem;\n  font-size: 0.875em;\n  color: #198754;\n}\n\n.valid-tooltip {\n  position: absolute;\n  top: 100%;\n  z-index: 5;\n  display: none;\n  max-width: 100%;\n  padding: 0.25rem 0.5rem;\n  margin-top: 0.1rem;\n  font-size: 0.875rem;\n  color: #fff;\n  background-color: rgba(25, 135, 84, 0.9);\n  border-radius: 0.375rem;\n}\n\n.was-validated :valid ~ .valid-feedback,\n.was-validated :valid ~ .valid-tooltip,\n.is-valid ~ .valid-feedback,\n.is-valid ~ .valid-tooltip {\n  display: block;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n  border-color: #198754;\n  padding-right: calc(1.5em + 0.75rem);\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n  background-repeat: no-repeat;\n  background-position: right calc(0.375em + 0.1875rem) center;\n  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n  border-color: #198754;\n  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n  padding-right: calc(1.5em + 0.75rem);\n  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:valid, .form-select.is-valid {\n  border-color: #198754;\n}\n.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size=\"1\"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size=\"1\"] {\n  padding-right: 4.125rem;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\"), url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n  background-position: right 0.75rem center, center right 2.25rem;\n  background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-select:valid:focus, .form-select.is-valid:focus {\n  border-color: #198754;\n  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n\n.was-validated .form-control-color:valid, .form-control-color.is-valid {\n  width: calc(3rem + calc(1.5em + 0.75rem));\n}\n\n.was-validated .form-check-input:valid, .form-check-input.is-valid {\n  border-color: #198754;\n}\n.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked {\n  background-color: #198754;\n}\n.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus {\n  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n  color: #198754;\n}\n\n.form-check-inline .form-check-input ~ .valid-feedback {\n  margin-left: 0.5em;\n}\n\n.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid,\n.was-validated .input-group > .form-select:not(:focus):valid,\n.input-group > .form-select:not(:focus).is-valid,\n.was-validated .input-group > .form-floating:not(:focus-within):valid,\n.input-group > .form-floating:not(:focus-within).is-valid {\n  z-index: 3;\n}\n\n.invalid-feedback {\n  display: none;\n  width: 100%;\n  margin-top: 0.25rem;\n  font-size: 0.875em;\n  color: #dc3545;\n}\n\n.invalid-tooltip {\n  position: absolute;\n  top: 100%;\n  z-index: 5;\n  display: none;\n  max-width: 100%;\n  padding: 0.25rem 0.5rem;\n  margin-top: 0.1rem;\n  font-size: 0.875rem;\n  color: #fff;\n  background-color: rgba(220, 53, 69, 0.9);\n  border-radius: 0.375rem;\n}\n\n.was-validated :invalid ~ .invalid-feedback,\n.was-validated :invalid ~ .invalid-tooltip,\n.is-invalid ~ .invalid-feedback,\n.is-invalid ~ .invalid-tooltip {\n  display: block;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n  border-color: #dc3545;\n  padding-right: calc(1.5em + 0.75rem);\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n  background-repeat: no-repeat;\n  background-position: right calc(0.375em + 0.1875rem) center;\n  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n  border-color: #dc3545;\n  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n  padding-right: calc(1.5em + 0.75rem);\n  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:invalid, .form-select.is-invalid {\n  border-color: #dc3545;\n}\n.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size=\"1\"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size=\"1\"] {\n  padding-right: 4.125rem;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\"), url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n  background-position: right 0.75rem center, center right 2.25rem;\n  background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {\n  border-color: #dc3545;\n  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control-color:invalid, .form-control-color.is-invalid {\n  width: calc(3rem + calc(1.5em + 0.75rem));\n}\n\n.was-validated .form-check-input:invalid, .form-check-input.is-invalid {\n  border-color: #dc3545;\n}\n.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked {\n  background-color: #dc3545;\n}\n.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus {\n  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n  color: #dc3545;\n}\n\n.form-check-inline .form-check-input ~ .invalid-feedback {\n  margin-left: 0.5em;\n}\n\n.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid,\n.was-validated .input-group > .form-select:not(:focus):invalid,\n.input-group > .form-select:not(:focus).is-invalid,\n.was-validated .input-group > .form-floating:not(:focus-within):invalid,\n.input-group > .form-floating:not(:focus-within).is-invalid {\n  z-index: 4;\n}\n\n.btn {\n  --bs-btn-padding-x: 0.75rem;\n  --bs-btn-padding-y: 0.375rem;\n  --bs-btn-font-family: ;\n  --bs-btn-font-size: 1rem;\n  --bs-btn-font-weight: 400;\n  --bs-btn-line-height: 1.5;\n  --bs-btn-color: #212529;\n  --bs-btn-bg: transparent;\n  --bs-btn-border-width: 1px;\n  --bs-btn-border-color: transparent;\n  --bs-btn-border-radius: 0.375rem;\n  --bs-btn-hover-border-color: transparent;\n  --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n  --bs-btn-disabled-opacity: 0.65;\n  --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);\n  display: inline-block;\n  padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);\n  font-family: var(--bs-btn-font-family);\n  font-size: var(--bs-btn-font-size);\n  font-weight: var(--bs-btn-font-weight);\n  line-height: var(--bs-btn-line-height);\n  color: var(--bs-btn-color);\n  text-align: center;\n  text-decoration: none;\n  vertical-align: middle;\n  cursor: pointer;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);\n  border-radius: var(--bs-btn-border-radius);\n  background-color: var(--bs-btn-bg);\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .btn {\n    transition: none;\n  }\n}\n.btn:hover {\n  color: var(--bs-btn-hover-color);\n  background-color: var(--bs-btn-hover-bg);\n  border-color: var(--bs-btn-hover-border-color);\n}\n.btn-check + .btn:hover {\n  color: var(--bs-btn-color);\n  background-color: var(--bs-btn-bg);\n  border-color: var(--bs-btn-border-color);\n}\n.btn:focus-visible {\n  color: var(--bs-btn-hover-color);\n  background-color: var(--bs-btn-hover-bg);\n  border-color: var(--bs-btn-hover-border-color);\n  outline: 0;\n  box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn-check:focus-visible + .btn {\n  border-color: var(--bs-btn-hover-border-color);\n  outline: 0;\n  box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show {\n  color: var(--bs-btn-active-color);\n  background-color: var(--bs-btn-active-bg);\n  border-color: var(--bs-btn-active-border-color);\n}\n.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {\n  box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn:disabled, .btn.disabled, fieldset:disabled .btn {\n  color: var(--bs-btn-disabled-color);\n  pointer-events: none;\n  background-color: var(--bs-btn-disabled-bg);\n  border-color: var(--bs-btn-disabled-border-color);\n  opacity: var(--bs-btn-disabled-opacity);\n}\n\n.btn-primary {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #0d6efd;\n  --bs-btn-border-color: #0d6efd;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #0b5ed7;\n  --bs-btn-hover-border-color: #0a58ca;\n  --bs-btn-focus-shadow-rgb: 49, 132, 253;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #0a58ca;\n  --bs-btn-active-border-color: #0a53be;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #0d6efd;\n  --bs-btn-disabled-border-color: #0d6efd;\n}\n\n.btn-secondary {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #6c757d;\n  --bs-btn-border-color: #6c757d;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #5c636a;\n  --bs-btn-hover-border-color: #565e64;\n  --bs-btn-focus-shadow-rgb: 130, 138, 145;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #565e64;\n  --bs-btn-active-border-color: #51585e;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #6c757d;\n  --bs-btn-disabled-border-color: #6c757d;\n}\n\n.btn-success {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #198754;\n  --bs-btn-border-color: #198754;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #157347;\n  --bs-btn-hover-border-color: #146c43;\n  --bs-btn-focus-shadow-rgb: 60, 153, 110;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #146c43;\n  --bs-btn-active-border-color: #13653f;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #198754;\n  --bs-btn-disabled-border-color: #198754;\n}\n\n.btn-info {\n  --bs-btn-color: #000;\n  --bs-btn-bg: #0dcaf0;\n  --bs-btn-border-color: #0dcaf0;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #31d2f2;\n  --bs-btn-hover-border-color: #25cff2;\n  --bs-btn-focus-shadow-rgb: 11, 172, 204;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #3dd5f3;\n  --bs-btn-active-border-color: #25cff2;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #000;\n  --bs-btn-disabled-bg: #0dcaf0;\n  --bs-btn-disabled-border-color: #0dcaf0;\n}\n\n.btn-warning {\n  --bs-btn-color: #000;\n  --bs-btn-bg: #ffc107;\n  --bs-btn-border-color: #ffc107;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #ffca2c;\n  --bs-btn-hover-border-color: #ffc720;\n  --bs-btn-focus-shadow-rgb: 217, 164, 6;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #ffcd39;\n  --bs-btn-active-border-color: #ffc720;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #000;\n  --bs-btn-disabled-bg: #ffc107;\n  --bs-btn-disabled-border-color: #ffc107;\n}\n\n.btn-danger {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #dc3545;\n  --bs-btn-border-color: #dc3545;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #bb2d3b;\n  --bs-btn-hover-border-color: #b02a37;\n  --bs-btn-focus-shadow-rgb: 225, 83, 97;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #b02a37;\n  --bs-btn-active-border-color: #a52834;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #dc3545;\n  --bs-btn-disabled-border-color: #dc3545;\n}\n\n.btn-light {\n  --bs-btn-color: #000;\n  --bs-btn-bg: #f8f9fa;\n  --bs-btn-border-color: #f8f9fa;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #d3d4d5;\n  --bs-btn-hover-border-color: #c6c7c8;\n  --bs-btn-focus-shadow-rgb: 211, 212, 213;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #c6c7c8;\n  --bs-btn-active-border-color: #babbbc;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #000;\n  --bs-btn-disabled-bg: #f8f9fa;\n  --bs-btn-disabled-border-color: #f8f9fa;\n}\n\n.btn-dark {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #212529;\n  --bs-btn-border-color: #212529;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #424649;\n  --bs-btn-hover-border-color: #373b3e;\n  --bs-btn-focus-shadow-rgb: 66, 70, 73;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #4d5154;\n  --bs-btn-active-border-color: #373b3e;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #212529;\n  --bs-btn-disabled-border-color: #212529;\n}\n\n.btn-outline-primary {\n  --bs-btn-color: #0d6efd;\n  --bs-btn-border-color: #0d6efd;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #0d6efd;\n  --bs-btn-hover-border-color: #0d6efd;\n  --bs-btn-focus-shadow-rgb: 13, 110, 253;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #0d6efd;\n  --bs-btn-active-border-color: #0d6efd;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #0d6efd;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #0d6efd;\n  --bs-gradient: none;\n}\n\n.btn-outline-secondary {\n  --bs-btn-color: #6c757d;\n  --bs-btn-border-color: #6c757d;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #6c757d;\n  --bs-btn-hover-border-color: #6c757d;\n  --bs-btn-focus-shadow-rgb: 108, 117, 125;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #6c757d;\n  --bs-btn-active-border-color: #6c757d;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #6c757d;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #6c757d;\n  --bs-gradient: none;\n}\n\n.btn-outline-success {\n  --bs-btn-color: #198754;\n  --bs-btn-border-color: #198754;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #198754;\n  --bs-btn-hover-border-color: #198754;\n  --bs-btn-focus-shadow-rgb: 25, 135, 84;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #198754;\n  --bs-btn-active-border-color: #198754;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #198754;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #198754;\n  --bs-gradient: none;\n}\n\n.btn-outline-info {\n  --bs-btn-color: #0dcaf0;\n  --bs-btn-border-color: #0dcaf0;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #0dcaf0;\n  --bs-btn-hover-border-color: #0dcaf0;\n  --bs-btn-focus-shadow-rgb: 13, 202, 240;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #0dcaf0;\n  --bs-btn-active-border-color: #0dcaf0;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #0dcaf0;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #0dcaf0;\n  --bs-gradient: none;\n}\n\n.btn-outline-warning {\n  --bs-btn-color: #ffc107;\n  --bs-btn-border-color: #ffc107;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #ffc107;\n  --bs-btn-hover-border-color: #ffc107;\n  --bs-btn-focus-shadow-rgb: 255, 193, 7;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #ffc107;\n  --bs-btn-active-border-color: #ffc107;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #ffc107;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #ffc107;\n  --bs-gradient: none;\n}\n\n.btn-outline-danger {\n  --bs-btn-color: #dc3545;\n  --bs-btn-border-color: #dc3545;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #dc3545;\n  --bs-btn-hover-border-color: #dc3545;\n  --bs-btn-focus-shadow-rgb: 220, 53, 69;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #dc3545;\n  --bs-btn-active-border-color: #dc3545;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #dc3545;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #dc3545;\n  --bs-gradient: none;\n}\n\n.btn-outline-light {\n  --bs-btn-color: #f8f9fa;\n  --bs-btn-border-color: #f8f9fa;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #f8f9fa;\n  --bs-btn-hover-border-color: #f8f9fa;\n  --bs-btn-focus-shadow-rgb: 248, 249, 250;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #f8f9fa;\n  --bs-btn-active-border-color: #f8f9fa;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #f8f9fa;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #f8f9fa;\n  --bs-gradient: none;\n}\n\n.btn-outline-dark {\n  --bs-btn-color: #212529;\n  --bs-btn-border-color: #212529;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #212529;\n  --bs-btn-hover-border-color: #212529;\n  --bs-btn-focus-shadow-rgb: 33, 37, 41;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #212529;\n  --bs-btn-active-border-color: #212529;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #212529;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #212529;\n  --bs-gradient: none;\n}\n\n.btn-link {\n  --bs-btn-font-weight: 400;\n  --bs-btn-color: var(--bs-link-color);\n  --bs-btn-bg: transparent;\n  --bs-btn-border-color: transparent;\n  --bs-btn-hover-color: var(--bs-link-hover-color);\n  --bs-btn-hover-border-color: transparent;\n  --bs-btn-active-color: var(--bs-link-hover-color);\n  --bs-btn-active-border-color: transparent;\n  --bs-btn-disabled-color: #6c757d;\n  --bs-btn-disabled-border-color: transparent;\n  --bs-btn-box-shadow: none;\n  --bs-btn-focus-shadow-rgb: 49, 132, 253;\n  text-decoration: underline;\n}\n.btn-link:focus-visible {\n  color: var(--bs-btn-color);\n}\n.btn-link:hover {\n  color: var(--bs-btn-hover-color);\n}\n\n.btn-lg, .btn-group-lg > .btn {\n  --bs-btn-padding-y: 0.5rem;\n  --bs-btn-padding-x: 1rem;\n  --bs-btn-font-size: 1.25rem;\n  --bs-btn-border-radius: 0.5rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n  --bs-btn-padding-y: 0.25rem;\n  --bs-btn-padding-x: 0.5rem;\n  --bs-btn-font-size: 0.875rem;\n  --bs-btn-border-radius: 0.25rem;\n}\n\n.fade {\n  transition: opacity 0.15s linear;\n}\n@media (prefers-reduced-motion: reduce) {\n  .fade {\n    transition: none;\n  }\n}\n.fade:not(.show) {\n  opacity: 0;\n}\n\n.collapse:not(.show) {\n  display: none;\n}\n\n.collapsing {\n  height: 0;\n  overflow: hidden;\n  transition: height 0.35s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .collapsing {\n    transition: none;\n  }\n}\n.collapsing.collapse-horizontal {\n  width: 0;\n  height: auto;\n  transition: width 0.35s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .collapsing.collapse-horizontal {\n    transition: none;\n  }\n}\n\n.dropup,\n.dropend,\n.dropdown,\n.dropstart,\n.dropup-center,\n.dropdown-center {\n  position: relative;\n}\n\n.dropdown-toggle {\n  white-space: nowrap;\n}\n.dropdown-toggle::after {\n  display: inline-block;\n  margin-left: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0.3em solid;\n  border-right: 0.3em solid transparent;\n  border-bottom: 0;\n  border-left: 0.3em solid transparent;\n}\n.dropdown-toggle:empty::after {\n  margin-left: 0;\n}\n\n.dropdown-menu {\n  --bs-dropdown-zindex: 1000;\n  --bs-dropdown-min-width: 10rem;\n  --bs-dropdown-padding-x: 0;\n  --bs-dropdown-padding-y: 0.5rem;\n  --bs-dropdown-spacer: 0.125rem;\n  --bs-dropdown-font-size: 1rem;\n  --bs-dropdown-color: #212529;\n  --bs-dropdown-bg: #fff;\n  --bs-dropdown-border-color: var(--bs-border-color-translucent);\n  --bs-dropdown-border-radius: 0.375rem;\n  --bs-dropdown-border-width: 1px;\n  --bs-dropdown-inner-border-radius: calc(0.375rem - 1px);\n  --bs-dropdown-divider-bg: var(--bs-border-color-translucent);\n  --bs-dropdown-divider-margin-y: 0.5rem;\n  --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  --bs-dropdown-link-color: #212529;\n  --bs-dropdown-link-hover-color: #1e2125;\n  --bs-dropdown-link-hover-bg: #e9ecef;\n  --bs-dropdown-link-active-color: #fff;\n  --bs-dropdown-link-active-bg: #0d6efd;\n  --bs-dropdown-link-disabled-color: #adb5bd;\n  --bs-dropdown-item-padding-x: 1rem;\n  --bs-dropdown-item-padding-y: 0.25rem;\n  --bs-dropdown-header-color: #6c757d;\n  --bs-dropdown-header-padding-x: 1rem;\n  --bs-dropdown-header-padding-y: 0.5rem;\n  position: absolute;\n  z-index: var(--bs-dropdown-zindex);\n  display: none;\n  min-width: var(--bs-dropdown-min-width);\n  padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);\n  margin: 0;\n  font-size: var(--bs-dropdown-font-size);\n  color: var(--bs-dropdown-color);\n  text-align: left;\n  list-style: none;\n  background-color: var(--bs-dropdown-bg);\n  background-clip: padding-box;\n  border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);\n  border-radius: var(--bs-dropdown-border-radius);\n}\n.dropdown-menu[data-bs-popper] {\n  top: 100%;\n  left: 0;\n  margin-top: var(--bs-dropdown-spacer);\n}\n\n.dropdown-menu-start {\n  --bs-position: start;\n}\n.dropdown-menu-start[data-bs-popper] {\n  right: auto;\n  left: 0;\n}\n\n.dropdown-menu-end {\n  --bs-position: end;\n}\n.dropdown-menu-end[data-bs-popper] {\n  right: 0;\n  left: auto;\n}\n\n@media (min-width: 576px) {\n  .dropdown-menu-sm-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-sm-start[data-bs-popper] {\n    right: auto;\n    left: 0;\n  }\n  .dropdown-menu-sm-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-sm-end[data-bs-popper] {\n    right: 0;\n    left: auto;\n  }\n}\n@media (min-width: 768px) {\n  .dropdown-menu-md-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-md-start[data-bs-popper] {\n    right: auto;\n    left: 0;\n  }\n  .dropdown-menu-md-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-md-end[data-bs-popper] {\n    right: 0;\n    left: auto;\n  }\n}\n@media (min-width: 992px) {\n  .dropdown-menu-lg-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-lg-start[data-bs-popper] {\n    right: auto;\n    left: 0;\n  }\n  .dropdown-menu-lg-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-lg-end[data-bs-popper] {\n    right: 0;\n    left: auto;\n  }\n}\n@media (min-width: 1200px) {\n  .dropdown-menu-xl-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-xl-start[data-bs-popper] {\n    right: auto;\n    left: 0;\n  }\n  .dropdown-menu-xl-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-xl-end[data-bs-popper] {\n    right: 0;\n    left: auto;\n  }\n}\n@media (min-width: 1400px) {\n  .dropdown-menu-xxl-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-xxl-start[data-bs-popper] {\n    right: auto;\n    left: 0;\n  }\n  .dropdown-menu-xxl-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-xxl-end[data-bs-popper] {\n    right: 0;\n    left: auto;\n  }\n}\n.dropup .dropdown-menu[data-bs-popper] {\n  top: auto;\n  bottom: 100%;\n  margin-top: 0;\n  margin-bottom: var(--bs-dropdown-spacer);\n}\n.dropup .dropdown-toggle::after {\n  display: inline-block;\n  margin-left: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0;\n  border-right: 0.3em solid transparent;\n  border-bottom: 0.3em solid;\n  border-left: 0.3em solid transparent;\n}\n.dropup .dropdown-toggle:empty::after {\n  margin-left: 0;\n}\n\n.dropend .dropdown-menu[data-bs-popper] {\n  top: 0;\n  right: auto;\n  left: 100%;\n  margin-top: 0;\n  margin-left: var(--bs-dropdown-spacer);\n}\n.dropend .dropdown-toggle::after {\n  display: inline-block;\n  margin-left: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0.3em solid transparent;\n  border-right: 0;\n  border-bottom: 0.3em solid transparent;\n  border-left: 0.3em solid;\n}\n.dropend .dropdown-toggle:empty::after {\n  margin-left: 0;\n}\n.dropend .dropdown-toggle::after {\n  vertical-align: 0;\n}\n\n.dropstart .dropdown-menu[data-bs-popper] {\n  top: 0;\n  right: 100%;\n  left: auto;\n  margin-top: 0;\n  margin-right: var(--bs-dropdown-spacer);\n}\n.dropstart .dropdown-toggle::after {\n  display: inline-block;\n  margin-left: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n}\n.dropstart .dropdown-toggle::after {\n  display: none;\n}\n.dropstart .dropdown-toggle::before {\n  display: inline-block;\n  margin-right: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0.3em solid transparent;\n  border-right: 0.3em solid;\n  border-bottom: 0.3em solid transparent;\n}\n.dropstart .dropdown-toggle:empty::after {\n  margin-left: 0;\n}\n.dropstart .dropdown-toggle::before {\n  vertical-align: 0;\n}\n\n.dropdown-divider {\n  height: 0;\n  margin: var(--bs-dropdown-divider-margin-y) 0;\n  overflow: hidden;\n  border-top: 1px solid var(--bs-dropdown-divider-bg);\n  opacity: 1;\n}\n\n.dropdown-item {\n  display: block;\n  width: 100%;\n  padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);\n  clear: both;\n  font-weight: 400;\n  color: var(--bs-dropdown-link-color);\n  text-align: inherit;\n  text-decoration: none;\n  white-space: nowrap;\n  background-color: transparent;\n  border: 0;\n}\n.dropdown-item:hover, .dropdown-item:focus {\n  color: var(--bs-dropdown-link-hover-color);\n  background-color: var(--bs-dropdown-link-hover-bg);\n}\n.dropdown-item.active, .dropdown-item:active {\n  color: var(--bs-dropdown-link-active-color);\n  text-decoration: none;\n  background-color: var(--bs-dropdown-link-active-bg);\n}\n.dropdown-item.disabled, .dropdown-item:disabled {\n  color: var(--bs-dropdown-link-disabled-color);\n  pointer-events: none;\n  background-color: transparent;\n}\n\n.dropdown-menu.show {\n  display: block;\n}\n\n.dropdown-header {\n  display: block;\n  padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);\n  margin-bottom: 0;\n  font-size: 0.875rem;\n  color: var(--bs-dropdown-header-color);\n  white-space: nowrap;\n}\n\n.dropdown-item-text {\n  display: block;\n  padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);\n  color: var(--bs-dropdown-link-color);\n}\n\n.dropdown-menu-dark {\n  --bs-dropdown-color: #dee2e6;\n  --bs-dropdown-bg: #343a40;\n  --bs-dropdown-border-color: var(--bs-border-color-translucent);\n  --bs-dropdown-box-shadow: ;\n  --bs-dropdown-link-color: #dee2e6;\n  --bs-dropdown-link-hover-color: #fff;\n  --bs-dropdown-divider-bg: var(--bs-border-color-translucent);\n  --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);\n  --bs-dropdown-link-active-color: #fff;\n  --bs-dropdown-link-active-bg: #0d6efd;\n  --bs-dropdown-link-disabled-color: #adb5bd;\n  --bs-dropdown-header-color: #adb5bd;\n}\n\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-flex;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  flex: 1 1 auto;\n}\n.btn-group > .btn-check:checked + .btn,\n.btn-group > .btn-check:focus + .btn,\n.btn-group > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn-check:checked + .btn,\n.btn-group-vertical > .btn-check:focus + .btn,\n.btn-group-vertical > .btn:hover,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n  z-index: 1;\n}\n\n.btn-toolbar {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-start;\n}\n.btn-toolbar .input-group {\n  width: auto;\n}\n\n.btn-group {\n  border-radius: 0.375rem;\n}\n.btn-group > :not(.btn-check:first-child) + .btn,\n.btn-group > .btn-group:not(:first-child) {\n  margin-left: -1px;\n}\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn.dropdown-toggle-split:first-child,\n.btn-group > .btn-group:not(:last-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:nth-child(n+3),\n.btn-group > :not(.btn-check) + .btn,\n.btn-group > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n  padding-right: 0.5625rem;\n  padding-left: 0.5625rem;\n}\n.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after {\n  margin-left: 0;\n}\n.dropstart .dropdown-toggle-split::before {\n  margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n  padding-right: 0.375rem;\n  padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n  padding-right: 0.75rem;\n  padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n  flex-direction: column;\n  align-items: flex-start;\n  justify-content: center;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n  width: 100%;\n}\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n  margin-top: -1px;\n}\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn ~ .btn,\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n\n.nav {\n  --bs-nav-link-padding-x: 1rem;\n  --bs-nav-link-padding-y: 0.5rem;\n  --bs-nav-link-font-weight: ;\n  --bs-nav-link-color: var(--bs-link-color);\n  --bs-nav-link-hover-color: var(--bs-link-hover-color);\n  --bs-nav-link-disabled-color: #6c757d;\n  display: flex;\n  flex-wrap: wrap;\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n\n.nav-link {\n  display: block;\n  padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);\n  font-size: var(--bs-nav-link-font-size);\n  font-weight: var(--bs-nav-link-font-weight);\n  color: var(--bs-nav-link-color);\n  text-decoration: none;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .nav-link {\n    transition: none;\n  }\n}\n.nav-link:hover, .nav-link:focus {\n  color: var(--bs-nav-link-hover-color);\n}\n.nav-link.disabled {\n  color: var(--bs-nav-link-disabled-color);\n  pointer-events: none;\n  cursor: default;\n}\n\n.nav-tabs {\n  --bs-nav-tabs-border-width: 1px;\n  --bs-nav-tabs-border-color: #dee2e6;\n  --bs-nav-tabs-border-radius: 0.375rem;\n  --bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #dee2e6;\n  --bs-nav-tabs-link-active-color: #495057;\n  --bs-nav-tabs-link-active-bg: #fff;\n  --bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff;\n  border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color);\n}\n.nav-tabs .nav-link {\n  margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width));\n  background: none;\n  border: var(--bs-nav-tabs-border-width) solid transparent;\n  border-top-left-radius: var(--bs-nav-tabs-border-radius);\n  border-top-right-radius: var(--bs-nav-tabs-border-radius);\n}\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n  isolation: isolate;\n  border-color: var(--bs-nav-tabs-link-hover-border-color);\n}\n.nav-tabs .nav-link.disabled, .nav-tabs .nav-link:disabled {\n  color: var(--bs-nav-link-disabled-color);\n  background-color: transparent;\n  border-color: transparent;\n}\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n  color: var(--bs-nav-tabs-link-active-color);\n  background-color: var(--bs-nav-tabs-link-active-bg);\n  border-color: var(--bs-nav-tabs-link-active-border-color);\n}\n.nav-tabs .dropdown-menu {\n  margin-top: calc(-1 * var(--bs-nav-tabs-border-width));\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n\n.nav-pills {\n  --bs-nav-pills-border-radius: 0.375rem;\n  --bs-nav-pills-link-active-color: #fff;\n  --bs-nav-pills-link-active-bg: #0d6efd;\n}\n.nav-pills .nav-link {\n  background: none;\n  border: 0;\n  border-radius: var(--bs-nav-pills-border-radius);\n}\n.nav-pills .nav-link:disabled {\n  color: var(--bs-nav-link-disabled-color);\n  background-color: transparent;\n  border-color: transparent;\n}\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n  color: var(--bs-nav-pills-link-active-color);\n  background-color: var(--bs-nav-pills-link-active-bg);\n}\n\n.nav-fill > .nav-link,\n.nav-fill .nav-item {\n  flex: 1 1 auto;\n  text-align: center;\n}\n\n.nav-justified > .nav-link,\n.nav-justified .nav-item {\n  flex-basis: 0;\n  flex-grow: 1;\n  text-align: center;\n}\n\n.nav-fill .nav-item .nav-link,\n.nav-justified .nav-item .nav-link {\n  width: 100%;\n}\n\n.tab-content > .tab-pane {\n  display: none;\n}\n.tab-content > .active {\n  display: block;\n}\n\n.navbar {\n  --bs-navbar-padding-x: 0;\n  --bs-navbar-padding-y: 0.5rem;\n  --bs-navbar-color: rgba(0, 0, 0, 0.55);\n  --bs-navbar-hover-color: rgba(0, 0, 0, 0.7);\n  --bs-navbar-disabled-color: rgba(0, 0, 0, 0.3);\n  --bs-navbar-active-color: rgba(0, 0, 0, 0.9);\n  --bs-navbar-brand-padding-y: 0.3125rem;\n  --bs-navbar-brand-margin-end: 1rem;\n  --bs-navbar-brand-font-size: 1.25rem;\n  --bs-navbar-brand-color: rgba(0, 0, 0, 0.9);\n  --bs-navbar-brand-hover-color: rgba(0, 0, 0, 0.9);\n  --bs-navbar-nav-link-padding-x: 0.5rem;\n  --bs-navbar-toggler-padding-y: 0.25rem;\n  --bs-navbar-toggler-padding-x: 0.75rem;\n  --bs-navbar-toggler-font-size: 1.25rem;\n  --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n  --bs-navbar-toggler-border-color: rgba(0, 0, 0, 0.1);\n  --bs-navbar-toggler-border-radius: 0.375rem;\n  --bs-navbar-toggler-focus-width: 0.25rem;\n  --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);\n}\n.navbar > .container,\n.navbar > .container-fluid,\n.navbar > .container-sm,\n.navbar > .container-md,\n.navbar > .container-lg,\n.navbar > .container-xl,\n.navbar > .container-xxl {\n  display: flex;\n  flex-wrap: inherit;\n  align-items: center;\n  justify-content: space-between;\n}\n.navbar-brand {\n  padding-top: var(--bs-navbar-brand-padding-y);\n  padding-bottom: var(--bs-navbar-brand-padding-y);\n  margin-right: var(--bs-navbar-brand-margin-end);\n  font-size: var(--bs-navbar-brand-font-size);\n  color: var(--bs-navbar-brand-color);\n  text-decoration: none;\n  white-space: nowrap;\n}\n.navbar-brand:hover, .navbar-brand:focus {\n  color: var(--bs-navbar-brand-hover-color);\n}\n\n.navbar-nav {\n  --bs-nav-link-padding-x: 0;\n  --bs-nav-link-padding-y: 0.5rem;\n  --bs-nav-link-font-weight: ;\n  --bs-nav-link-color: var(--bs-navbar-color);\n  --bs-nav-link-hover-color: var(--bs-navbar-hover-color);\n  --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);\n  display: flex;\n  flex-direction: column;\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.navbar-nav .show > .nav-link,\n.navbar-nav .nav-link.active {\n  color: var(--bs-navbar-active-color);\n}\n.navbar-nav .dropdown-menu {\n  position: static;\n}\n\n.navbar-text {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: var(--bs-navbar-color);\n}\n.navbar-text a,\n.navbar-text a:hover,\n.navbar-text a:focus {\n  color: var(--bs-navbar-active-color);\n}\n\n.navbar-collapse {\n  flex-basis: 100%;\n  flex-grow: 1;\n  align-items: center;\n}\n\n.navbar-toggler {\n  padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);\n  font-size: var(--bs-navbar-toggler-font-size);\n  line-height: 1;\n  color: var(--bs-navbar-color);\n  background-color: transparent;\n  border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);\n  border-radius: var(--bs-navbar-toggler-border-radius);\n  transition: var(--bs-navbar-toggler-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .navbar-toggler {\n    transition: none;\n  }\n}\n.navbar-toggler:hover {\n  text-decoration: none;\n}\n.navbar-toggler:focus {\n  text-decoration: none;\n  outline: 0;\n  box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width);\n}\n\n.navbar-toggler-icon {\n  display: inline-block;\n  width: 1.5em;\n  height: 1.5em;\n  vertical-align: middle;\n  background-image: var(--bs-navbar-toggler-icon-bg);\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: 100%;\n}\n\n.navbar-nav-scroll {\n  max-height: var(--bs-scroll-height, 75vh);\n  overflow-y: auto;\n}\n\n@media (min-width: 576px) {\n  .navbar-expand-sm {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-sm .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-sm .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-sm .navbar-nav .nav-link {\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-sm .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-sm .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-sm .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-sm .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-sm .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-sm .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-expand-md {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-md .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-md .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-md .navbar-nav .nav-link {\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-md .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-md .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-md .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-md .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-md .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-md .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 992px) {\n  .navbar-expand-lg {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-lg .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-lg .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-lg .navbar-nav .nav-link {\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-lg .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-lg .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-lg .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-lg .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-lg .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-lg .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 1200px) {\n  .navbar-expand-xl {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-xl .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-xl .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-xl .navbar-nav .nav-link {\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-xl .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-xl .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-xl .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-xl .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-xl .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-xl .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 1400px) {\n  .navbar-expand-xxl {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-xxl .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-xxl .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-xxl .navbar-nav .nav-link {\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-xxl .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-xxl .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-xxl .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-xxl .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-xxl .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-xxl .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n.navbar-expand {\n  flex-wrap: nowrap;\n  justify-content: flex-start;\n}\n.navbar-expand .navbar-nav {\n  flex-direction: row;\n}\n.navbar-expand .navbar-nav .dropdown-menu {\n  position: absolute;\n}\n.navbar-expand .navbar-nav .nav-link {\n  padding-right: var(--bs-navbar-nav-link-padding-x);\n  padding-left: var(--bs-navbar-nav-link-padding-x);\n}\n.navbar-expand .navbar-nav-scroll {\n  overflow: visible;\n}\n.navbar-expand .navbar-collapse {\n  display: flex !important;\n  flex-basis: auto;\n}\n.navbar-expand .navbar-toggler {\n  display: none;\n}\n.navbar-expand .offcanvas {\n  position: static;\n  z-index: auto;\n  flex-grow: 1;\n  width: auto !important;\n  height: auto !important;\n  visibility: visible !important;\n  background-color: transparent !important;\n  border: 0 !important;\n  transform: none !important;\n  transition: none;\n}\n.navbar-expand .offcanvas .offcanvas-header {\n  display: none;\n}\n.navbar-expand .offcanvas .offcanvas-body {\n  display: flex;\n  flex-grow: 0;\n  padding: 0;\n  overflow-y: visible;\n}\n\n.navbar-dark {\n  --bs-navbar-color: rgba(255, 255, 255, 0.55);\n  --bs-navbar-hover-color: rgba(255, 255, 255, 0.75);\n  --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25);\n  --bs-navbar-active-color: #fff;\n  --bs-navbar-brand-color: #fff;\n  --bs-navbar-brand-hover-color: #fff;\n  --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1);\n  --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.card {\n  --bs-card-spacer-y: 1rem;\n  --bs-card-spacer-x: 1rem;\n  --bs-card-title-spacer-y: 0.5rem;\n  --bs-card-border-width: 1px;\n  --bs-card-border-color: var(--bs-border-color-translucent);\n  --bs-card-border-radius: 0.375rem;\n  --bs-card-box-shadow: ;\n  --bs-card-inner-border-radius: calc(0.375rem - 1px);\n  --bs-card-cap-padding-y: 0.5rem;\n  --bs-card-cap-padding-x: 1rem;\n  --bs-card-cap-bg: rgba(0, 0, 0, 0.03);\n  --bs-card-cap-color: ;\n  --bs-card-height: ;\n  --bs-card-color: ;\n  --bs-card-bg: #fff;\n  --bs-card-img-overlay-padding: 1rem;\n  --bs-card-group-margin: 0.75rem;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n  height: var(--bs-card-height);\n  word-wrap: break-word;\n  background-color: var(--bs-card-bg);\n  background-clip: border-box;\n  border: var(--bs-card-border-width) solid var(--bs-card-border-color);\n  border-radius: var(--bs-card-border-radius);\n}\n.card > hr {\n  margin-right: 0;\n  margin-left: 0;\n}\n.card > .list-group {\n  border-top: inherit;\n  border-bottom: inherit;\n}\n.card > .list-group:first-child {\n  border-top-width: 0;\n  border-top-left-radius: var(--bs-card-inner-border-radius);\n  border-top-right-radius: var(--bs-card-inner-border-radius);\n}\n.card > .list-group:last-child {\n  border-bottom-width: 0;\n  border-bottom-right-radius: var(--bs-card-inner-border-radius);\n  border-bottom-left-radius: var(--bs-card-inner-border-radius);\n}\n.card > .card-header + .list-group,\n.card > .list-group + .card-footer {\n  border-top: 0;\n}\n\n.card-body {\n  flex: 1 1 auto;\n  padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);\n  color: var(--bs-card-color);\n}\n\n.card-title {\n  margin-bottom: var(--bs-card-title-spacer-y);\n}\n\n.card-subtitle {\n  margin-top: calc(-0.5 * var(--bs-card-title-spacer-y));\n  margin-bottom: 0;\n}\n\n.card-text:last-child {\n  margin-bottom: 0;\n}\n\n.card-link + .card-link {\n  margin-left: var(--bs-card-spacer-x);\n}\n\n.card-header {\n  padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);\n  margin-bottom: 0;\n  color: var(--bs-card-cap-color);\n  background-color: var(--bs-card-cap-bg);\n  border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n}\n.card-header:first-child {\n  border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0;\n}\n\n.card-footer {\n  padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);\n  color: var(--bs-card-cap-color);\n  background-color: var(--bs-card-cap-bg);\n  border-top: var(--bs-card-border-width) solid var(--bs-card-border-color);\n}\n.card-footer:last-child {\n  border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);\n}\n\n.card-header-tabs {\n  margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));\n  margin-bottom: calc(-1 * var(--bs-card-cap-padding-y));\n  margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));\n  border-bottom: 0;\n}\n.card-header-tabs .nav-link.active {\n  background-color: var(--bs-card-bg);\n  border-bottom-color: var(--bs-card-bg);\n}\n\n.card-header-pills {\n  margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));\n  margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));\n}\n\n.card-img-overlay {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  padding: var(--bs-card-img-overlay-padding);\n  border-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n  width: 100%;\n}\n\n.card-img,\n.card-img-top {\n  border-top-left-radius: var(--bs-card-inner-border-radius);\n  border-top-right-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-img,\n.card-img-bottom {\n  border-bottom-right-radius: var(--bs-card-inner-border-radius);\n  border-bottom-left-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-group > .card {\n  margin-bottom: var(--bs-card-group-margin);\n}\n@media (min-width: 576px) {\n  .card-group {\n    display: flex;\n    flex-flow: row wrap;\n  }\n  .card-group > .card {\n    flex: 1 0 0%;\n    margin-bottom: 0;\n  }\n  .card-group > .card + .card {\n    margin-left: 0;\n    border-left: 0;\n  }\n  .card-group > .card:not(:last-child) {\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .card-group > .card:not(:last-child) .card-img-top,\n.card-group > .card:not(:last-child) .card-header {\n    border-top-right-radius: 0;\n  }\n  .card-group > .card:not(:last-child) .card-img-bottom,\n.card-group > .card:not(:last-child) .card-footer {\n    border-bottom-right-radius: 0;\n  }\n  .card-group > .card:not(:first-child) {\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n  .card-group > .card:not(:first-child) .card-img-top,\n.card-group > .card:not(:first-child) .card-header {\n    border-top-left-radius: 0;\n  }\n  .card-group > .card:not(:first-child) .card-img-bottom,\n.card-group > .card:not(:first-child) .card-footer {\n    border-bottom-left-radius: 0;\n  }\n}\n\n.accordion {\n  --bs-accordion-color: #212529;\n  --bs-accordion-bg: #fff;\n  --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;\n  --bs-accordion-border-color: var(--bs-border-color);\n  --bs-accordion-border-width: 1px;\n  --bs-accordion-border-radius: 0.375rem;\n  --bs-accordion-inner-border-radius: calc(0.375rem - 1px);\n  --bs-accordion-btn-padding-x: 1.25rem;\n  --bs-accordion-btn-padding-y: 1rem;\n  --bs-accordion-btn-color: #212529;\n  --bs-accordion-btn-bg: var(--bs-accordion-bg);\n  --bs-accordion-btn-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n  --bs-accordion-btn-icon-width: 1.25rem;\n  --bs-accordion-btn-icon-transform: rotate(-180deg);\n  --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;\n  --bs-accordion-btn-active-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n  --bs-accordion-btn-focus-border-color: #86b7fe;\n  --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n  --bs-accordion-body-padding-x: 1.25rem;\n  --bs-accordion-body-padding-y: 1rem;\n  --bs-accordion-active-color: #0c63e4;\n  --bs-accordion-active-bg: #e7f1ff;\n}\n\n.accordion-button {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);\n  font-size: 1rem;\n  color: var(--bs-accordion-btn-color);\n  text-align: left;\n  background-color: var(--bs-accordion-btn-bg);\n  border: 0;\n  border-radius: 0;\n  overflow-anchor: none;\n  transition: var(--bs-accordion-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .accordion-button {\n    transition: none;\n  }\n}\n.accordion-button:not(.collapsed) {\n  color: var(--bs-accordion-active-color);\n  background-color: var(--bs-accordion-active-bg);\n  box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color);\n}\n.accordion-button:not(.collapsed)::after {\n  background-image: var(--bs-accordion-btn-active-icon);\n  transform: var(--bs-accordion-btn-icon-transform);\n}\n.accordion-button::after {\n  flex-shrink: 0;\n  width: var(--bs-accordion-btn-icon-width);\n  height: var(--bs-accordion-btn-icon-width);\n  margin-left: auto;\n  content: \"\";\n  background-image: var(--bs-accordion-btn-icon);\n  background-repeat: no-repeat;\n  background-size: var(--bs-accordion-btn-icon-width);\n  transition: var(--bs-accordion-btn-icon-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .accordion-button::after {\n    transition: none;\n  }\n}\n.accordion-button:hover {\n  z-index: 2;\n}\n.accordion-button:focus {\n  z-index: 3;\n  border-color: var(--bs-accordion-btn-focus-border-color);\n  outline: 0;\n  box-shadow: var(--bs-accordion-btn-focus-box-shadow);\n}\n\n.accordion-header {\n  margin-bottom: 0;\n}\n\n.accordion-item {\n  color: var(--bs-accordion-color);\n  background-color: var(--bs-accordion-bg);\n  border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color);\n}\n.accordion-item:first-of-type {\n  border-top-left-radius: var(--bs-accordion-border-radius);\n  border-top-right-radius: var(--bs-accordion-border-radius);\n}\n.accordion-item:first-of-type .accordion-button {\n  border-top-left-radius: var(--bs-accordion-inner-border-radius);\n  border-top-right-radius: var(--bs-accordion-inner-border-radius);\n}\n.accordion-item:not(:first-of-type) {\n  border-top: 0;\n}\n.accordion-item:last-of-type {\n  border-bottom-right-radius: var(--bs-accordion-border-radius);\n  border-bottom-left-radius: var(--bs-accordion-border-radius);\n}\n.accordion-item:last-of-type .accordion-button.collapsed {\n  border-bottom-right-radius: var(--bs-accordion-inner-border-radius);\n  border-bottom-left-radius: var(--bs-accordion-inner-border-radius);\n}\n.accordion-item:last-of-type .accordion-collapse {\n  border-bottom-right-radius: var(--bs-accordion-border-radius);\n  border-bottom-left-radius: var(--bs-accordion-border-radius);\n}\n\n.accordion-body {\n  padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);\n}\n\n.accordion-flush .accordion-collapse {\n  border-width: 0;\n}\n.accordion-flush .accordion-item {\n  border-right: 0;\n  border-left: 0;\n  border-radius: 0;\n}\n.accordion-flush .accordion-item:first-child {\n  border-top: 0;\n}\n.accordion-flush .accordion-item:last-child {\n  border-bottom: 0;\n}\n.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {\n  border-radius: 0;\n}\n\n.breadcrumb {\n  --bs-breadcrumb-padding-x: 0;\n  --bs-breadcrumb-padding-y: 0;\n  --bs-breadcrumb-margin-bottom: 1rem;\n  --bs-breadcrumb-bg: ;\n  --bs-breadcrumb-border-radius: ;\n  --bs-breadcrumb-divider-color: #6c757d;\n  --bs-breadcrumb-item-padding-x: 0.5rem;\n  --bs-breadcrumb-item-active-color: #6c757d;\n  display: flex;\n  flex-wrap: wrap;\n  padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);\n  margin-bottom: var(--bs-breadcrumb-margin-bottom);\n  font-size: var(--bs-breadcrumb-font-size);\n  list-style: none;\n  background-color: var(--bs-breadcrumb-bg);\n  border-radius: var(--bs-breadcrumb-border-radius);\n}\n\n.breadcrumb-item + .breadcrumb-item {\n  padding-left: var(--bs-breadcrumb-item-padding-x);\n}\n.breadcrumb-item + .breadcrumb-item::before {\n  float: left;\n  padding-right: var(--bs-breadcrumb-item-padding-x);\n  color: var(--bs-breadcrumb-divider-color);\n  content: var(--bs-breadcrumb-divider, \"/\") /* rtl: var(--bs-breadcrumb-divider, \"/\") */;\n}\n.breadcrumb-item.active {\n  color: var(--bs-breadcrumb-item-active-color);\n}\n\n.pagination {\n  --bs-pagination-padding-x: 0.75rem;\n  --bs-pagination-padding-y: 0.375rem;\n  --bs-pagination-font-size: 1rem;\n  --bs-pagination-color: var(--bs-link-color);\n  --bs-pagination-bg: #fff;\n  --bs-pagination-border-width: 1px;\n  --bs-pagination-border-color: #dee2e6;\n  --bs-pagination-border-radius: 0.375rem;\n  --bs-pagination-hover-color: var(--bs-link-hover-color);\n  --bs-pagination-hover-bg: #e9ecef;\n  --bs-pagination-hover-border-color: #dee2e6;\n  --bs-pagination-focus-color: var(--bs-link-hover-color);\n  --bs-pagination-focus-bg: #e9ecef;\n  --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n  --bs-pagination-active-color: #fff;\n  --bs-pagination-active-bg: #0d6efd;\n  --bs-pagination-active-border-color: #0d6efd;\n  --bs-pagination-disabled-color: #6c757d;\n  --bs-pagination-disabled-bg: #fff;\n  --bs-pagination-disabled-border-color: #dee2e6;\n  display: flex;\n  padding-left: 0;\n  list-style: none;\n}\n\n.page-link {\n  position: relative;\n  display: block;\n  padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);\n  font-size: var(--bs-pagination-font-size);\n  color: var(--bs-pagination-color);\n  text-decoration: none;\n  background-color: var(--bs-pagination-bg);\n  border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .page-link {\n    transition: none;\n  }\n}\n.page-link:hover {\n  z-index: 2;\n  color: var(--bs-pagination-hover-color);\n  background-color: var(--bs-pagination-hover-bg);\n  border-color: var(--bs-pagination-hover-border-color);\n}\n.page-link:focus {\n  z-index: 3;\n  color: var(--bs-pagination-focus-color);\n  background-color: var(--bs-pagination-focus-bg);\n  outline: 0;\n  box-shadow: var(--bs-pagination-focus-box-shadow);\n}\n.page-link.active, .active > .page-link {\n  z-index: 3;\n  color: var(--bs-pagination-active-color);\n  background-color: var(--bs-pagination-active-bg);\n  border-color: var(--bs-pagination-active-border-color);\n}\n.page-link.disabled, .disabled > .page-link {\n  color: var(--bs-pagination-disabled-color);\n  pointer-events: none;\n  background-color: var(--bs-pagination-disabled-bg);\n  border-color: var(--bs-pagination-disabled-border-color);\n}\n\n.page-item:not(:first-child) .page-link {\n  margin-left: -1px;\n}\n.page-item:first-child .page-link {\n  border-top-left-radius: var(--bs-pagination-border-radius);\n  border-bottom-left-radius: var(--bs-pagination-border-radius);\n}\n.page-item:last-child .page-link {\n  border-top-right-radius: var(--bs-pagination-border-radius);\n  border-bottom-right-radius: var(--bs-pagination-border-radius);\n}\n\n.pagination-lg {\n  --bs-pagination-padding-x: 1.5rem;\n  --bs-pagination-padding-y: 0.75rem;\n  --bs-pagination-font-size: 1.25rem;\n  --bs-pagination-border-radius: 0.5rem;\n}\n\n.pagination-sm {\n  --bs-pagination-padding-x: 0.5rem;\n  --bs-pagination-padding-y: 0.25rem;\n  --bs-pagination-font-size: 0.875rem;\n  --bs-pagination-border-radius: 0.25rem;\n}\n\n.badge {\n  --bs-badge-padding-x: 0.65em;\n  --bs-badge-padding-y: 0.35em;\n  --bs-badge-font-size: 0.75em;\n  --bs-badge-font-weight: 700;\n  --bs-badge-color: #fff;\n  --bs-badge-border-radius: 0.375rem;\n  display: inline-block;\n  padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);\n  font-size: var(--bs-badge-font-size);\n  font-weight: var(--bs-badge-font-weight);\n  line-height: 1;\n  color: var(--bs-badge-color);\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: var(--bs-badge-border-radius);\n}\n.badge:empty {\n  display: none;\n}\n\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n\n.alert {\n  --bs-alert-bg: transparent;\n  --bs-alert-padding-x: 1rem;\n  --bs-alert-padding-y: 1rem;\n  --bs-alert-margin-bottom: 1rem;\n  --bs-alert-color: inherit;\n  --bs-alert-border-color: transparent;\n  --bs-alert-border: 1px solid var(--bs-alert-border-color);\n  --bs-alert-border-radius: 0.375rem;\n  position: relative;\n  padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x);\n  margin-bottom: var(--bs-alert-margin-bottom);\n  color: var(--bs-alert-color);\n  background-color: var(--bs-alert-bg);\n  border: var(--bs-alert-border);\n  border-radius: var(--bs-alert-border-radius);\n}\n\n.alert-heading {\n  color: inherit;\n}\n\n.alert-link {\n  font-weight: 700;\n}\n\n.alert-dismissible {\n  padding-right: 3rem;\n}\n.alert-dismissible .btn-close {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  padding: 1.25rem 1rem;\n}\n\n.alert-primary {\n  --bs-alert-color: #084298;\n  --bs-alert-bg: #cfe2ff;\n  --bs-alert-border-color: #b6d4fe;\n}\n.alert-primary .alert-link {\n  color: #06357a;\n}\n\n.alert-secondary {\n  --bs-alert-color: #41464b;\n  --bs-alert-bg: #e2e3e5;\n  --bs-alert-border-color: #d3d6d8;\n}\n.alert-secondary .alert-link {\n  color: #34383c;\n}\n\n.alert-success {\n  --bs-alert-color: #0f5132;\n  --bs-alert-bg: #d1e7dd;\n  --bs-alert-border-color: #badbcc;\n}\n.alert-success .alert-link {\n  color: #0c4128;\n}\n\n.alert-info {\n  --bs-alert-color: #055160;\n  --bs-alert-bg: #cff4fc;\n  --bs-alert-border-color: #b6effb;\n}\n.alert-info .alert-link {\n  color: #04414d;\n}\n\n.alert-warning {\n  --bs-alert-color: #664d03;\n  --bs-alert-bg: #fff3cd;\n  --bs-alert-border-color: #ffecb5;\n}\n.alert-warning .alert-link {\n  color: #523e02;\n}\n\n.alert-danger {\n  --bs-alert-color: #842029;\n  --bs-alert-bg: #f8d7da;\n  --bs-alert-border-color: #f5c2c7;\n}\n.alert-danger .alert-link {\n  color: #6a1a21;\n}\n\n.alert-light {\n  --bs-alert-color: #636464;\n  --bs-alert-bg: #fefefe;\n  --bs-alert-border-color: #fdfdfe;\n}\n.alert-light .alert-link {\n  color: #4f5050;\n}\n\n.alert-dark {\n  --bs-alert-color: #141619;\n  --bs-alert-bg: #d3d3d4;\n  --bs-alert-border-color: #bcbebf;\n}\n.alert-dark .alert-link {\n  color: #101214;\n}\n\n@keyframes progress-bar-stripes {\n  0% {\n    background-position-x: 1rem;\n  }\n}\n.progress {\n  --bs-progress-height: 1rem;\n  --bs-progress-font-size: 0.75rem;\n  --bs-progress-bg: #e9ecef;\n  --bs-progress-border-radius: 0.375rem;\n  --bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);\n  --bs-progress-bar-color: #fff;\n  --bs-progress-bar-bg: #0d6efd;\n  --bs-progress-bar-transition: width 0.6s ease;\n  display: flex;\n  height: var(--bs-progress-height);\n  overflow: hidden;\n  font-size: var(--bs-progress-font-size);\n  background-color: var(--bs-progress-bg);\n  border-radius: var(--bs-progress-border-radius);\n}\n\n.progress-bar {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  overflow: hidden;\n  color: var(--bs-progress-bar-color);\n  text-align: center;\n  white-space: nowrap;\n  background-color: var(--bs-progress-bar-bg);\n  transition: var(--bs-progress-bar-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .progress-bar {\n    transition: none;\n  }\n}\n\n.progress-bar-striped {\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-size: var(--bs-progress-height) var(--bs-progress-height);\n}\n\n.progress-bar-animated {\n  animation: 1s linear infinite progress-bar-stripes;\n}\n@media (prefers-reduced-motion: reduce) {\n  .progress-bar-animated {\n    animation: none;\n  }\n}\n\n.list-group {\n  --bs-list-group-color: #212529;\n  --bs-list-group-bg: #fff;\n  --bs-list-group-border-color: rgba(0, 0, 0, 0.125);\n  --bs-list-group-border-width: 1px;\n  --bs-list-group-border-radius: 0.375rem;\n  --bs-list-group-item-padding-x: 1rem;\n  --bs-list-group-item-padding-y: 0.5rem;\n  --bs-list-group-action-color: #495057;\n  --bs-list-group-action-hover-color: #495057;\n  --bs-list-group-action-hover-bg: #f8f9fa;\n  --bs-list-group-action-active-color: #212529;\n  --bs-list-group-action-active-bg: #e9ecef;\n  --bs-list-group-disabled-color: #6c757d;\n  --bs-list-group-disabled-bg: #fff;\n  --bs-list-group-active-color: #fff;\n  --bs-list-group-active-bg: #0d6efd;\n  --bs-list-group-active-border-color: #0d6efd;\n  display: flex;\n  flex-direction: column;\n  padding-left: 0;\n  margin-bottom: 0;\n  border-radius: var(--bs-list-group-border-radius);\n}\n\n.list-group-numbered {\n  list-style-type: none;\n  counter-reset: section;\n}\n.list-group-numbered > .list-group-item::before {\n  content: counters(section, \".\") \". \";\n  counter-increment: section;\n}\n\n.list-group-item-action {\n  width: 100%;\n  color: var(--bs-list-group-action-color);\n  text-align: inherit;\n}\n.list-group-item-action:hover, .list-group-item-action:focus {\n  z-index: 1;\n  color: var(--bs-list-group-action-hover-color);\n  text-decoration: none;\n  background-color: var(--bs-list-group-action-hover-bg);\n}\n.list-group-item-action:active {\n  color: var(--bs-list-group-action-active-color);\n  background-color: var(--bs-list-group-action-active-bg);\n}\n\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);\n  color: var(--bs-list-group-color);\n  text-decoration: none;\n  background-color: var(--bs-list-group-bg);\n  border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);\n}\n.list-group-item:first-child {\n  border-top-left-radius: inherit;\n  border-top-right-radius: inherit;\n}\n.list-group-item:last-child {\n  border-bottom-right-radius: inherit;\n  border-bottom-left-radius: inherit;\n}\n.list-group-item.disabled, .list-group-item:disabled {\n  color: var(--bs-list-group-disabled-color);\n  pointer-events: none;\n  background-color: var(--bs-list-group-disabled-bg);\n}\n.list-group-item.active {\n  z-index: 2;\n  color: var(--bs-list-group-active-color);\n  background-color: var(--bs-list-group-active-bg);\n  border-color: var(--bs-list-group-active-border-color);\n}\n.list-group-item + .list-group-item {\n  border-top-width: 0;\n}\n.list-group-item + .list-group-item.active {\n  margin-top: calc(-1 * var(--bs-list-group-border-width));\n  border-top-width: var(--bs-list-group-border-width);\n}\n\n.list-group-horizontal {\n  flex-direction: row;\n}\n.list-group-horizontal > .list-group-item:first-child:not(:last-child) {\n  border-bottom-left-radius: var(--bs-list-group-border-radius);\n  border-top-right-radius: 0;\n}\n.list-group-horizontal > .list-group-item:last-child:not(:first-child) {\n  border-top-right-radius: var(--bs-list-group-border-radius);\n  border-bottom-left-radius: 0;\n}\n.list-group-horizontal > .list-group-item.active {\n  margin-top: 0;\n}\n.list-group-horizontal > .list-group-item + .list-group-item {\n  border-top-width: var(--bs-list-group-border-width);\n  border-left-width: 0;\n}\n.list-group-horizontal > .list-group-item + .list-group-item.active {\n  margin-left: calc(-1 * var(--bs-list-group-border-width));\n  border-left-width: var(--bs-list-group-border-width);\n}\n\n@media (min-width: 576px) {\n  .list-group-horizontal-sm {\n    flex-direction: row;\n  }\n  .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) {\n    border-bottom-left-radius: var(--bs-list-group-border-radius);\n    border-top-right-radius: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) {\n    border-top-right-radius: var(--bs-list-group-border-radius);\n    border-bottom-left-radius: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-left-width: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item + .list-group-item.active {\n    margin-left: calc(-1 * var(--bs-list-group-border-width));\n    border-left-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 768px) {\n  .list-group-horizontal-md {\n    flex-direction: row;\n  }\n  .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) {\n    border-bottom-left-radius: var(--bs-list-group-border-radius);\n    border-top-right-radius: 0;\n  }\n  .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) {\n    border-top-right-radius: var(--bs-list-group-border-radius);\n    border-bottom-left-radius: 0;\n  }\n  .list-group-horizontal-md > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-md > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-left-width: 0;\n  }\n  .list-group-horizontal-md > .list-group-item + .list-group-item.active {\n    margin-left: calc(-1 * var(--bs-list-group-border-width));\n    border-left-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 992px) {\n  .list-group-horizontal-lg {\n    flex-direction: row;\n  }\n  .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) {\n    border-bottom-left-radius: var(--bs-list-group-border-radius);\n    border-top-right-radius: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) {\n    border-top-right-radius: var(--bs-list-group-border-radius);\n    border-bottom-left-radius: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-left-width: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item + .list-group-item.active {\n    margin-left: calc(-1 * var(--bs-list-group-border-width));\n    border-left-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 1200px) {\n  .list-group-horizontal-xl {\n    flex-direction: row;\n  }\n  .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) {\n    border-bottom-left-radius: var(--bs-list-group-border-radius);\n    border-top-right-radius: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) {\n    border-top-right-radius: var(--bs-list-group-border-radius);\n    border-bottom-left-radius: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-left-width: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item + .list-group-item.active {\n    margin-left: calc(-1 * var(--bs-list-group-border-width));\n    border-left-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 1400px) {\n  .list-group-horizontal-xxl {\n    flex-direction: row;\n  }\n  .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) {\n    border-bottom-left-radius: var(--bs-list-group-border-radius);\n    border-top-right-radius: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) {\n    border-top-right-radius: var(--bs-list-group-border-radius);\n    border-bottom-left-radius: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-left-width: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item + .list-group-item.active {\n    margin-left: calc(-1 * var(--bs-list-group-border-width));\n    border-left-width: var(--bs-list-group-border-width);\n  }\n}\n.list-group-flush {\n  border-radius: 0;\n}\n.list-group-flush > .list-group-item {\n  border-width: 0 0 var(--bs-list-group-border-width);\n}\n.list-group-flush > .list-group-item:last-child {\n  border-bottom-width: 0;\n}\n\n.list-group-item-primary {\n  color: #084298;\n  background-color: #cfe2ff;\n}\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n  color: #084298;\n  background-color: #bacbe6;\n}\n.list-group-item-primary.list-group-item-action.active {\n  color: #fff;\n  background-color: #084298;\n  border-color: #084298;\n}\n\n.list-group-item-secondary {\n  color: #41464b;\n  background-color: #e2e3e5;\n}\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n  color: #41464b;\n  background-color: #cbccce;\n}\n.list-group-item-secondary.list-group-item-action.active {\n  color: #fff;\n  background-color: #41464b;\n  border-color: #41464b;\n}\n\n.list-group-item-success {\n  color: #0f5132;\n  background-color: #d1e7dd;\n}\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n  color: #0f5132;\n  background-color: #bcd0c7;\n}\n.list-group-item-success.list-group-item-action.active {\n  color: #fff;\n  background-color: #0f5132;\n  border-color: #0f5132;\n}\n\n.list-group-item-info {\n  color: #055160;\n  background-color: #cff4fc;\n}\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n  color: #055160;\n  background-color: #badce3;\n}\n.list-group-item-info.list-group-item-action.active {\n  color: #fff;\n  background-color: #055160;\n  border-color: #055160;\n}\n\n.list-group-item-warning {\n  color: #664d03;\n  background-color: #fff3cd;\n}\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n  color: #664d03;\n  background-color: #e6dbb9;\n}\n.list-group-item-warning.list-group-item-action.active {\n  color: #fff;\n  background-color: #664d03;\n  border-color: #664d03;\n}\n\n.list-group-item-danger {\n  color: #842029;\n  background-color: #f8d7da;\n}\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n  color: #842029;\n  background-color: #dfc2c4;\n}\n.list-group-item-danger.list-group-item-action.active {\n  color: #fff;\n  background-color: #842029;\n  border-color: #842029;\n}\n\n.list-group-item-light {\n  color: #636464;\n  background-color: #fefefe;\n}\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n  color: #636464;\n  background-color: #e5e5e5;\n}\n.list-group-item-light.list-group-item-action.active {\n  color: #fff;\n  background-color: #636464;\n  border-color: #636464;\n}\n\n.list-group-item-dark {\n  color: #141619;\n  background-color: #d3d3d4;\n}\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n  color: #141619;\n  background-color: #bebebf;\n}\n.list-group-item-dark.list-group-item-action.active {\n  color: #fff;\n  background-color: #141619;\n  border-color: #141619;\n}\n\n.btn-close {\n  box-sizing: content-box;\n  width: 1em;\n  height: 1em;\n  padding: 0.25em 0.25em;\n  color: #000;\n  background: transparent url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e\") center/1em auto no-repeat;\n  border: 0;\n  border-radius: 0.375rem;\n  opacity: 0.5;\n}\n.btn-close:hover {\n  color: #000;\n  text-decoration: none;\n  opacity: 0.75;\n}\n.btn-close:focus {\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n  opacity: 1;\n}\n.btn-close:disabled, .btn-close.disabled {\n  pointer-events: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  opacity: 0.25;\n}\n\n.btn-close-white {\n  filter: invert(1) grayscale(100%) brightness(200%);\n}\n\n.toast {\n  --bs-toast-zindex: 1090;\n  --bs-toast-padding-x: 0.75rem;\n  --bs-toast-padding-y: 0.5rem;\n  --bs-toast-spacing: 1.5rem;\n  --bs-toast-max-width: 350px;\n  --bs-toast-font-size: 0.875rem;\n  --bs-toast-color: ;\n  --bs-toast-bg: rgba(255, 255, 255, 0.85);\n  --bs-toast-border-width: 1px;\n  --bs-toast-border-color: var(--bs-border-color-translucent);\n  --bs-toast-border-radius: 0.375rem;\n  --bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  --bs-toast-header-color: #6c757d;\n  --bs-toast-header-bg: rgba(255, 255, 255, 0.85);\n  --bs-toast-header-border-color: rgba(0, 0, 0, 0.05);\n  width: var(--bs-toast-max-width);\n  max-width: 100%;\n  font-size: var(--bs-toast-font-size);\n  color: var(--bs-toast-color);\n  pointer-events: auto;\n  background-color: var(--bs-toast-bg);\n  background-clip: padding-box;\n  border: var(--bs-toast-border-width) solid var(--bs-toast-border-color);\n  box-shadow: var(--bs-toast-box-shadow);\n  border-radius: var(--bs-toast-border-radius);\n}\n.toast.showing {\n  opacity: 0;\n}\n.toast:not(.show) {\n  display: none;\n}\n\n.toast-container {\n  --bs-toast-zindex: 1090;\n  position: absolute;\n  z-index: var(--bs-toast-zindex);\n  width: -webkit-max-content;\n  width: -moz-max-content;\n  width: max-content;\n  max-width: 100%;\n  pointer-events: none;\n}\n.toast-container > :not(:last-child) {\n  margin-bottom: var(--bs-toast-spacing);\n}\n\n.toast-header {\n  display: flex;\n  align-items: center;\n  padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);\n  color: var(--bs-toast-header-color);\n  background-color: var(--bs-toast-header-bg);\n  background-clip: padding-box;\n  border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);\n  border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));\n  border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));\n}\n.toast-header .btn-close {\n  margin-right: calc(-0.5 * var(--bs-toast-padding-x));\n  margin-left: var(--bs-toast-padding-x);\n}\n\n.toast-body {\n  padding: var(--bs-toast-padding-x);\n  word-wrap: break-word;\n}\n\n.modal {\n  --bs-modal-zindex: 1055;\n  --bs-modal-width: 500px;\n  --bs-modal-padding: 1rem;\n  --bs-modal-margin: 0.5rem;\n  --bs-modal-color: ;\n  --bs-modal-bg: #fff;\n  --bs-modal-border-color: var(--bs-border-color-translucent);\n  --bs-modal-border-width: 1px;\n  --bs-modal-border-radius: 0.5rem;\n  --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n  --bs-modal-inner-border-radius: calc(0.5rem - 1px);\n  --bs-modal-header-padding-x: 1rem;\n  --bs-modal-header-padding-y: 1rem;\n  --bs-modal-header-padding: 1rem 1rem;\n  --bs-modal-header-border-color: var(--bs-border-color);\n  --bs-modal-header-border-width: 1px;\n  --bs-modal-title-line-height: 1.5;\n  --bs-modal-footer-gap: 0.5rem;\n  --bs-modal-footer-bg: ;\n  --bs-modal-footer-border-color: var(--bs-border-color);\n  --bs-modal-footer-border-width: 1px;\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: var(--bs-modal-zindex);\n  display: none;\n  width: 100%;\n  height: 100%;\n  overflow-x: hidden;\n  overflow-y: auto;\n  outline: 0;\n}\n\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: var(--bs-modal-margin);\n  pointer-events: none;\n}\n.modal.fade .modal-dialog {\n  transition: transform 0.3s ease-out;\n  transform: translate(0, -50px);\n}\n@media (prefers-reduced-motion: reduce) {\n  .modal.fade .modal-dialog {\n    transition: none;\n  }\n}\n.modal.show .modal-dialog {\n  transform: none;\n}\n.modal.modal-static .modal-dialog {\n  transform: scale(1.02);\n}\n\n.modal-dialog-scrollable {\n  height: calc(100% - var(--bs-modal-margin) * 2);\n}\n.modal-dialog-scrollable .modal-content {\n  max-height: 100%;\n  overflow: hidden;\n}\n.modal-dialog-scrollable .modal-body {\n  overflow-y: auto;\n}\n\n.modal-dialog-centered {\n  display: flex;\n  align-items: center;\n  min-height: calc(100% - var(--bs-modal-margin) * 2);\n}\n\n.modal-content {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  color: var(--bs-modal-color);\n  pointer-events: auto;\n  background-color: var(--bs-modal-bg);\n  background-clip: padding-box;\n  border: var(--bs-modal-border-width) solid var(--bs-modal-border-color);\n  border-radius: var(--bs-modal-border-radius);\n  outline: 0;\n}\n\n.modal-backdrop {\n  --bs-backdrop-zindex: 1050;\n  --bs-backdrop-bg: #000;\n  --bs-backdrop-opacity: 0.5;\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: var(--bs-backdrop-zindex);\n  width: 100vw;\n  height: 100vh;\n  background-color: var(--bs-backdrop-bg);\n}\n.modal-backdrop.fade {\n  opacity: 0;\n}\n.modal-backdrop.show {\n  opacity: var(--bs-backdrop-opacity);\n}\n\n.modal-header {\n  display: flex;\n  flex-shrink: 0;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--bs-modal-header-padding);\n  border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);\n  border-top-left-radius: var(--bs-modal-inner-border-radius);\n  border-top-right-radius: var(--bs-modal-inner-border-radius);\n}\n.modal-header .btn-close {\n  padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5);\n  margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto;\n}\n\n.modal-title {\n  margin-bottom: 0;\n  line-height: var(--bs-modal-title-line-height);\n}\n\n.modal-body {\n  position: relative;\n  flex: 1 1 auto;\n  padding: var(--bs-modal-padding);\n}\n\n.modal-footer {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: flex-end;\n  padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5);\n  background-color: var(--bs-modal-footer-bg);\n  border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);\n  border-bottom-right-radius: var(--bs-modal-inner-border-radius);\n  border-bottom-left-radius: var(--bs-modal-inner-border-radius);\n}\n.modal-footer > * {\n  margin: calc(var(--bs-modal-footer-gap) * 0.5);\n}\n\n@media (min-width: 576px) {\n  .modal {\n    --bs-modal-margin: 1.75rem;\n    --bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  }\n  .modal-dialog {\n    max-width: var(--bs-modal-width);\n    margin-right: auto;\n    margin-left: auto;\n  }\n  .modal-sm {\n    --bs-modal-width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg,\n.modal-xl {\n    --bs-modal-width: 800px;\n  }\n}\n@media (min-width: 1200px) {\n  .modal-xl {\n    --bs-modal-width: 1140px;\n  }\n}\n.modal-fullscreen {\n  width: 100vw;\n  max-width: none;\n  height: 100%;\n  margin: 0;\n}\n.modal-fullscreen .modal-content {\n  height: 100%;\n  border: 0;\n  border-radius: 0;\n}\n.modal-fullscreen .modal-header,\n.modal-fullscreen .modal-footer {\n  border-radius: 0;\n}\n.modal-fullscreen .modal-body {\n  overflow-y: auto;\n}\n\n@media (max-width: 575.98px) {\n  .modal-fullscreen-sm-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-sm-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-sm-down .modal-header,\n.modal-fullscreen-sm-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-sm-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 767.98px) {\n  .modal-fullscreen-md-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-md-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-md-down .modal-header,\n.modal-fullscreen-md-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-md-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 991.98px) {\n  .modal-fullscreen-lg-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-lg-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-lg-down .modal-header,\n.modal-fullscreen-lg-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-lg-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 1199.98px) {\n  .modal-fullscreen-xl-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-xl-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-xl-down .modal-header,\n.modal-fullscreen-xl-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-xl-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 1399.98px) {\n  .modal-fullscreen-xxl-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-xxl-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-xxl-down .modal-header,\n.modal-fullscreen-xxl-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-xxl-down .modal-body {\n    overflow-y: auto;\n  }\n}\n.tooltip {\n  --bs-tooltip-zindex: 1080;\n  --bs-tooltip-max-width: 200px;\n  --bs-tooltip-padding-x: 0.5rem;\n  --bs-tooltip-padding-y: 0.25rem;\n  --bs-tooltip-margin: ;\n  --bs-tooltip-font-size: 0.875rem;\n  --bs-tooltip-color: #fff;\n  --bs-tooltip-bg: #000;\n  --bs-tooltip-border-radius: 0.375rem;\n  --bs-tooltip-opacity: 0.9;\n  --bs-tooltip-arrow-width: 0.8rem;\n  --bs-tooltip-arrow-height: 0.4rem;\n  z-index: var(--bs-tooltip-zindex);\n  display: block;\n  padding: var(--bs-tooltip-arrow-height);\n  margin: var(--bs-tooltip-margin);\n  font-family: var(--bs-font-sans-serif);\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1.5;\n  text-align: left;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  white-space: normal;\n  word-spacing: normal;\n  line-break: auto;\n  font-size: var(--bs-tooltip-font-size);\n  word-wrap: break-word;\n  opacity: 0;\n}\n.tooltip.show {\n  opacity: var(--bs-tooltip-opacity);\n}\n.tooltip .tooltip-arrow {\n  display: block;\n  width: var(--bs-tooltip-arrow-width);\n  height: var(--bs-tooltip-arrow-height);\n}\n.tooltip .tooltip-arrow::before {\n  position: absolute;\n  content: \"\";\n  border-color: transparent;\n  border-style: solid;\n}\n\n.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow {\n  bottom: 0;\n}\n.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before {\n  top: -1px;\n  border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;\n  border-top-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow {\n  left: 0;\n  width: var(--bs-tooltip-arrow-height);\n  height: var(--bs-tooltip-arrow-width);\n}\n.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before {\n  right: -1px;\n  border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;\n  border-right-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:end:ignore */\n.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow {\n  top: 0;\n}\n.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before {\n  bottom: -1px;\n  border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);\n  border-bottom-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow {\n  right: 0;\n  width: var(--bs-tooltip-arrow-height);\n  height: var(--bs-tooltip-arrow-width);\n}\n.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before {\n  left: -1px;\n  border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);\n  border-left-color: var(--bs-tooltip-bg);\n}\n\n/* rtl:end:ignore */\n.tooltip-inner {\n  max-width: var(--bs-tooltip-max-width);\n  padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);\n  color: var(--bs-tooltip-color);\n  text-align: center;\n  background-color: var(--bs-tooltip-bg);\n  border-radius: var(--bs-tooltip-border-radius);\n}\n\n.popover {\n  --bs-popover-zindex: 1070;\n  --bs-popover-max-width: 276px;\n  --bs-popover-font-size: 0.875rem;\n  --bs-popover-bg: #fff;\n  --bs-popover-border-width: 1px;\n  --bs-popover-border-color: var(--bs-border-color-translucent);\n  --bs-popover-border-radius: 0.5rem;\n  --bs-popover-inner-border-radius: calc(0.5rem - 1px);\n  --bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  --bs-popover-header-padding-x: 1rem;\n  --bs-popover-header-padding-y: 0.5rem;\n  --bs-popover-header-font-size: 1rem;\n  --bs-popover-header-color: ;\n  --bs-popover-header-bg: #f0f0f0;\n  --bs-popover-body-padding-x: 1rem;\n  --bs-popover-body-padding-y: 1rem;\n  --bs-popover-body-color: #212529;\n  --bs-popover-arrow-width: 1rem;\n  --bs-popover-arrow-height: 0.5rem;\n  --bs-popover-arrow-border: var(--bs-popover-border-color);\n  z-index: var(--bs-popover-zindex);\n  display: block;\n  max-width: var(--bs-popover-max-width);\n  font-family: var(--bs-font-sans-serif);\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1.5;\n  text-align: left;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  white-space: normal;\n  word-spacing: normal;\n  line-break: auto;\n  font-size: var(--bs-popover-font-size);\n  word-wrap: break-word;\n  background-color: var(--bs-popover-bg);\n  background-clip: padding-box;\n  border: var(--bs-popover-border-width) solid var(--bs-popover-border-color);\n  border-radius: var(--bs-popover-border-radius);\n}\n.popover .popover-arrow {\n  display: block;\n  width: var(--bs-popover-arrow-width);\n  height: var(--bs-popover-arrow-height);\n}\n.popover .popover-arrow::before, .popover .popover-arrow::after {\n  position: absolute;\n  display: block;\n  content: \"\";\n  border-color: transparent;\n  border-style: solid;\n  border-width: 0;\n}\n\n.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow {\n  bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n}\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {\n  border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;\n}\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before {\n  bottom: 0;\n  border-top-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {\n  bottom: var(--bs-popover-border-width);\n  border-top-color: var(--bs-popover-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow {\n  left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n  width: var(--bs-popover-arrow-height);\n  height: var(--bs-popover-arrow-width);\n}\n.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {\n  border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;\n}\n.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before {\n  left: 0;\n  border-right-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {\n  left: var(--bs-popover-border-width);\n  border-right-color: var(--bs-popover-bg);\n}\n\n/* rtl:end:ignore */\n.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow {\n  top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n}\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {\n  border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);\n}\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before {\n  top: 0;\n  border-bottom-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {\n  top: var(--bs-popover-border-width);\n  border-bottom-color: var(--bs-popover-bg);\n}\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before {\n  position: absolute;\n  top: 0;\n  left: 50%;\n  display: block;\n  width: var(--bs-popover-arrow-width);\n  margin-left: calc(-0.5 * var(--bs-popover-arrow-width));\n  content: \"\";\n  border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg);\n}\n\n/* rtl:begin:ignore */\n.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow {\n  right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n  width: var(--bs-popover-arrow-height);\n  height: var(--bs-popover-arrow-width);\n}\n.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {\n  border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);\n}\n.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before {\n  right: 0;\n  border-left-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {\n  right: var(--bs-popover-border-width);\n  border-left-color: var(--bs-popover-bg);\n}\n\n/* rtl:end:ignore */\n.popover-header {\n  padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);\n  margin-bottom: 0;\n  font-size: var(--bs-popover-header-font-size);\n  color: var(--bs-popover-header-color);\n  background-color: var(--bs-popover-header-bg);\n  border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color);\n  border-top-left-radius: var(--bs-popover-inner-border-radius);\n  border-top-right-radius: var(--bs-popover-inner-border-radius);\n}\n.popover-header:empty {\n  display: none;\n}\n\n.popover-body {\n  padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);\n  color: var(--bs-popover-body-color);\n}\n\n.carousel {\n  position: relative;\n}\n\n.carousel.pointer-event {\n  touch-action: pan-y;\n}\n\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner::after {\n  display: block;\n  clear: both;\n  content: \"\";\n}\n\n.carousel-item {\n  position: relative;\n  display: none;\n  float: left;\n  width: 100%;\n  margin-right: -100%;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  transition: transform 0.6s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-item {\n    transition: none;\n  }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n  display: block;\n}\n\n.carousel-item-next:not(.carousel-item-start),\n.active.carousel-item-end {\n  transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-end),\n.active.carousel-item-start {\n  transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n  opacity: 0;\n  transition-property: opacity;\n  transform: none;\n}\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-start,\n.carousel-fade .carousel-item-prev.carousel-item-end {\n  z-index: 1;\n  opacity: 1;\n}\n.carousel-fade .active.carousel-item-start,\n.carousel-fade .active.carousel-item-end {\n  z-index: 0;\n  opacity: 0;\n  transition: opacity 0s 0.6s;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-fade .active.carousel-item-start,\n.carousel-fade .active.carousel-item-end {\n    transition: none;\n  }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  z-index: 1;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 15%;\n  padding: 0;\n  color: #fff;\n  text-align: center;\n  background: none;\n  border: 0;\n  opacity: 0.5;\n  transition: opacity 0.15s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-control-prev,\n.carousel-control-next {\n    transition: none;\n  }\n}\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n  color: #fff;\n  text-decoration: none;\n  outline: 0;\n  opacity: 0.9;\n}\n\n.carousel-control-prev {\n  left: 0;\n}\n\n.carousel-control-next {\n  right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n  display: inline-block;\n  width: 2rem;\n  height: 2rem;\n  background-repeat: no-repeat;\n  background-position: 50%;\n  background-size: 100% 100%;\n}\n\n/* rtl:options: {\n  \"autoRename\": true,\n  \"stringMap\":[ {\n    \"name\"    : \"prev-next\",\n    \"search\"  : \"prev\",\n    \"replace\" : \"next\"\n  } ]\n} */\n.carousel-control-prev-icon {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 2;\n  display: flex;\n  justify-content: center;\n  padding: 0;\n  margin-right: 15%;\n  margin-bottom: 1rem;\n  margin-left: 15%;\n  list-style: none;\n}\n.carousel-indicators [data-bs-target] {\n  box-sizing: content-box;\n  flex: 0 1 auto;\n  width: 30px;\n  height: 3px;\n  padding: 0;\n  margin-right: 3px;\n  margin-left: 3px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 0;\n  border-top: 10px solid transparent;\n  border-bottom: 10px solid transparent;\n  opacity: 0.5;\n  transition: opacity 0.6s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-indicators [data-bs-target] {\n    transition: none;\n  }\n}\n.carousel-indicators .active {\n  opacity: 1;\n}\n\n.carousel-caption {\n  position: absolute;\n  right: 15%;\n  bottom: 1.25rem;\n  left: 15%;\n  padding-top: 1.25rem;\n  padding-bottom: 1.25rem;\n  color: #fff;\n  text-align: center;\n}\n\n.carousel-dark .carousel-control-prev-icon,\n.carousel-dark .carousel-control-next-icon {\n  filter: invert(1) grayscale(100);\n}\n.carousel-dark .carousel-indicators [data-bs-target] {\n  background-color: #000;\n}\n.carousel-dark .carousel-caption {\n  color: #000;\n}\n\n.spinner-grow,\n.spinner-border {\n  display: inline-block;\n  width: var(--bs-spinner-width);\n  height: var(--bs-spinner-height);\n  vertical-align: var(--bs-spinner-vertical-align);\n  border-radius: 50%;\n  animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);\n}\n\n@keyframes spinner-border {\n  to {\n    transform: rotate(360deg) /* rtl:ignore */;\n  }\n}\n.spinner-border {\n  --bs-spinner-width: 2rem;\n  --bs-spinner-height: 2rem;\n  --bs-spinner-vertical-align: -0.125em;\n  --bs-spinner-border-width: 0.25em;\n  --bs-spinner-animation-speed: 0.75s;\n  --bs-spinner-animation-name: spinner-border;\n  border: var(--bs-spinner-border-width) solid currentcolor;\n  border-right-color: transparent;\n}\n\n.spinner-border-sm {\n  --bs-spinner-width: 1rem;\n  --bs-spinner-height: 1rem;\n  --bs-spinner-border-width: 0.2em;\n}\n\n@keyframes spinner-grow {\n  0% {\n    transform: scale(0);\n  }\n  50% {\n    opacity: 1;\n    transform: none;\n  }\n}\n.spinner-grow {\n  --bs-spinner-width: 2rem;\n  --bs-spinner-height: 2rem;\n  --bs-spinner-vertical-align: -0.125em;\n  --bs-spinner-animation-speed: 0.75s;\n  --bs-spinner-animation-name: spinner-grow;\n  background-color: currentcolor;\n  opacity: 0;\n}\n\n.spinner-grow-sm {\n  --bs-spinner-width: 1rem;\n  --bs-spinner-height: 1rem;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .spinner-border,\n.spinner-grow {\n    --bs-spinner-animation-speed: 1.5s;\n  }\n}\n.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm {\n  --bs-offcanvas-zindex: 1045;\n  --bs-offcanvas-width: 400px;\n  --bs-offcanvas-height: 30vh;\n  --bs-offcanvas-padding-x: 1rem;\n  --bs-offcanvas-padding-y: 1rem;\n  --bs-offcanvas-color: ;\n  --bs-offcanvas-bg: #fff;\n  --bs-offcanvas-border-width: 1px;\n  --bs-offcanvas-border-color: var(--bs-border-color-translucent);\n  --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n}\n\n@media (max-width: 575.98px) {\n  .offcanvas-sm {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-sm {\n    transition: none;\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-start {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-end {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-top {\n    top: 0;\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-bottom {\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 576px) {\n  .offcanvas-sm {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-sm .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-sm .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 767.98px) {\n  .offcanvas-md {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-md {\n    transition: none;\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-start {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-end {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-top {\n    top: 0;\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-bottom {\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 768px) {\n  .offcanvas-md {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-md .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-md .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 991.98px) {\n  .offcanvas-lg {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-lg {\n    transition: none;\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-start {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-end {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-top {\n    top: 0;\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-bottom {\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 992px) {\n  .offcanvas-lg {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-lg .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-lg .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 1199.98px) {\n  .offcanvas-xl {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-xl {\n    transition: none;\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-start {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-end {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-top {\n    top: 0;\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-bottom {\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 1200px) {\n  .offcanvas-xl {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-xl .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-xl .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-xxl {\n    transition: none;\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-start {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-end {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-top {\n    top: 0;\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-bottom {\n    right: 0;\n    left: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 1400px) {\n  .offcanvas-xxl {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-xxl .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-xxl .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n.offcanvas {\n  position: fixed;\n  bottom: 0;\n  z-index: var(--bs-offcanvas-zindex);\n  display: flex;\n  flex-direction: column;\n  max-width: 100%;\n  color: var(--bs-offcanvas-color);\n  visibility: hidden;\n  background-color: var(--bs-offcanvas-bg);\n  background-clip: padding-box;\n  outline: 0;\n  transition: transform 0.3s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .offcanvas {\n    transition: none;\n  }\n}\n.offcanvas.offcanvas-start {\n  top: 0;\n  left: 0;\n  width: var(--bs-offcanvas-width);\n  border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateX(-100%);\n}\n.offcanvas.offcanvas-end {\n  top: 0;\n  right: 0;\n  width: var(--bs-offcanvas-width);\n  border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateX(100%);\n}\n.offcanvas.offcanvas-top {\n  top: 0;\n  right: 0;\n  left: 0;\n  height: var(--bs-offcanvas-height);\n  max-height: 100%;\n  border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateY(-100%);\n}\n.offcanvas.offcanvas-bottom {\n  right: 0;\n  left: 0;\n  height: var(--bs-offcanvas-height);\n  max-height: 100%;\n  border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateY(100%);\n}\n.offcanvas.showing, .offcanvas.show:not(.hiding) {\n  transform: none;\n}\n.offcanvas.showing, .offcanvas.hiding, .offcanvas.show {\n  visibility: visible;\n}\n\n.offcanvas-backdrop {\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: 1040;\n  width: 100vw;\n  height: 100vh;\n  background-color: #000;\n}\n.offcanvas-backdrop.fade {\n  opacity: 0;\n}\n.offcanvas-backdrop.show {\n  opacity: 0.5;\n}\n\n.offcanvas-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);\n}\n.offcanvas-header .btn-close {\n  padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);\n  margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));\n  margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));\n  margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));\n}\n\n.offcanvas-title {\n  margin-bottom: 0;\n  line-height: 1.5;\n}\n\n.offcanvas-body {\n  flex-grow: 1;\n  padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);\n  overflow-y: auto;\n}\n\n.placeholder {\n  display: inline-block;\n  min-height: 1em;\n  vertical-align: middle;\n  cursor: wait;\n  background-color: currentcolor;\n  opacity: 0.5;\n}\n.placeholder.btn::before {\n  display: inline-block;\n  content: \"\";\n}\n\n.placeholder-xs {\n  min-height: 0.6em;\n}\n\n.placeholder-sm {\n  min-height: 0.8em;\n}\n\n.placeholder-lg {\n  min-height: 1.2em;\n}\n\n.placeholder-glow .placeholder {\n  animation: placeholder-glow 2s ease-in-out infinite;\n}\n\n@keyframes placeholder-glow {\n  50% {\n    opacity: 0.2;\n  }\n}\n.placeholder-wave {\n  -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);\n  mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);\n  -webkit-mask-size: 200% 100%;\n  mask-size: 200% 100%;\n  animation: placeholder-wave 2s linear infinite;\n}\n\n@keyframes placeholder-wave {\n  100% {\n    -webkit-mask-position: -200% 0%;\n    mask-position: -200% 0%;\n  }\n}\n.clearfix::after {\n  display: block;\n  clear: both;\n  content: \"\";\n}\n\n.text-bg-primary {\n  color: #fff !important;\n  background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-secondary {\n  color: #fff !important;\n  background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-success {\n  color: #fff !important;\n  background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-info {\n  color: #000 !important;\n  background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-warning {\n  color: #000 !important;\n  background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-danger {\n  color: #fff !important;\n  background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-light {\n  color: #000 !important;\n  background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-dark {\n  color: #fff !important;\n  background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important;\n}\n\n.link-primary {\n  color: #0d6efd !important;\n}\n.link-primary:hover, .link-primary:focus {\n  color: #0a58ca !important;\n}\n\n.link-secondary {\n  color: #6c757d !important;\n}\n.link-secondary:hover, .link-secondary:focus {\n  color: #565e64 !important;\n}\n\n.link-success {\n  color: #198754 !important;\n}\n.link-success:hover, .link-success:focus {\n  color: #146c43 !important;\n}\n\n.link-info {\n  color: #0dcaf0 !important;\n}\n.link-info:hover, .link-info:focus {\n  color: #3dd5f3 !important;\n}\n\n.link-warning {\n  color: #ffc107 !important;\n}\n.link-warning:hover, .link-warning:focus {\n  color: #ffcd39 !important;\n}\n\n.link-danger {\n  color: #dc3545 !important;\n}\n.link-danger:hover, .link-danger:focus {\n  color: #b02a37 !important;\n}\n\n.link-light {\n  color: #f8f9fa !important;\n}\n.link-light:hover, .link-light:focus {\n  color: #f9fafb !important;\n}\n\n.link-dark {\n  color: #212529 !important;\n}\n.link-dark:hover, .link-dark:focus {\n  color: #1a1e21 !important;\n}\n\n.ratio {\n  position: relative;\n  width: 100%;\n}\n.ratio::before {\n  display: block;\n  padding-top: var(--bs-aspect-ratio);\n  content: \"\";\n}\n.ratio > * {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.ratio-1x1 {\n  --bs-aspect-ratio: 100%;\n}\n\n.ratio-4x3 {\n  --bs-aspect-ratio: 75%;\n}\n\n.ratio-16x9 {\n  --bs-aspect-ratio: 56.25%;\n}\n\n.ratio-21x9 {\n  --bs-aspect-ratio: 42.8571428571%;\n}\n\n.fixed-top {\n  position: fixed;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n\n.fixed-bottom {\n  position: fixed;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1030;\n}\n\n.sticky-top {\n  position: -webkit-sticky;\n  position: sticky;\n  top: 0;\n  z-index: 1020;\n}\n\n.sticky-bottom {\n  position: -webkit-sticky;\n  position: sticky;\n  bottom: 0;\n  z-index: 1020;\n}\n\n@media (min-width: 576px) {\n  .sticky-sm-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-sm-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 768px) {\n  .sticky-md-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-md-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 992px) {\n  .sticky-lg-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-lg-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1200px) {\n  .sticky-xl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1400px) {\n  .sticky-xxl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xxl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n.hstack {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  align-self: stretch;\n}\n\n.vstack {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  align-self: stretch;\n}\n\n.visually-hidden,\n.visually-hidden-focusable:not(:focus):not(:focus-within) {\n  position: absolute !important;\n  width: 1px !important;\n  height: 1px !important;\n  padding: 0 !important;\n  margin: -1px !important;\n  overflow: hidden !important;\n  clip: rect(0, 0, 0, 0) !important;\n  white-space: nowrap !important;\n  border: 0 !important;\n}\n\n.stretched-link::after {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1;\n  content: \"\";\n}\n\n.text-truncate {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.vr {\n  display: inline-block;\n  align-self: stretch;\n  width: 1px;\n  min-height: 1em;\n  background-color: currentcolor;\n  opacity: 0.25;\n}\n\n.align-baseline {\n  vertical-align: baseline !important;\n}\n\n.align-top {\n  vertical-align: top !important;\n}\n\n.align-middle {\n  vertical-align: middle !important;\n}\n\n.align-bottom {\n  vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n  vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n  vertical-align: text-top !important;\n}\n\n.float-start {\n  float: left !important;\n}\n\n.float-end {\n  float: right !important;\n}\n\n.float-none {\n  float: none !important;\n}\n\n.opacity-0 {\n  opacity: 0 !important;\n}\n\n.opacity-25 {\n  opacity: 0.25 !important;\n}\n\n.opacity-50 {\n  opacity: 0.5 !important;\n}\n\n.opacity-75 {\n  opacity: 0.75 !important;\n}\n\n.opacity-100 {\n  opacity: 1 !important;\n}\n\n.overflow-auto {\n  overflow: auto !important;\n}\n\n.overflow-hidden {\n  overflow: hidden !important;\n}\n\n.overflow-visible {\n  overflow: visible !important;\n}\n\n.overflow-scroll {\n  overflow: scroll !important;\n}\n\n.d-inline {\n  display: inline !important;\n}\n\n.d-inline-block {\n  display: inline-block !important;\n}\n\n.d-block {\n  display: block !important;\n}\n\n.d-grid {\n  display: grid !important;\n}\n\n.d-table {\n  display: table !important;\n}\n\n.d-table-row {\n  display: table-row !important;\n}\n\n.d-table-cell {\n  display: table-cell !important;\n}\n\n.d-flex {\n  display: flex !important;\n}\n\n.d-inline-flex {\n  display: inline-flex !important;\n}\n\n.d-none {\n  display: none !important;\n}\n\n.shadow {\n  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-sm {\n  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow-lg {\n  box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n  box-shadow: none !important;\n}\n\n.position-static {\n  position: static !important;\n}\n\n.position-relative {\n  position: relative !important;\n}\n\n.position-absolute {\n  position: absolute !important;\n}\n\n.position-fixed {\n  position: fixed !important;\n}\n\n.position-sticky {\n  position: -webkit-sticky !important;\n  position: sticky !important;\n}\n\n.top-0 {\n  top: 0 !important;\n}\n\n.top-50 {\n  top: 50% !important;\n}\n\n.top-100 {\n  top: 100% !important;\n}\n\n.bottom-0 {\n  bottom: 0 !important;\n}\n\n.bottom-50 {\n  bottom: 50% !important;\n}\n\n.bottom-100 {\n  bottom: 100% !important;\n}\n\n.start-0 {\n  left: 0 !important;\n}\n\n.start-50 {\n  left: 50% !important;\n}\n\n.start-100 {\n  left: 100% !important;\n}\n\n.end-0 {\n  right: 0 !important;\n}\n\n.end-50 {\n  right: 50% !important;\n}\n\n.end-100 {\n  right: 100% !important;\n}\n\n.translate-middle {\n  transform: translate(-50%, -50%) !important;\n}\n\n.translate-middle-x {\n  transform: translateX(-50%) !important;\n}\n\n.translate-middle-y {\n  transform: translateY(-50%) !important;\n}\n\n.border {\n  border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-0 {\n  border: 0 !important;\n}\n\n.border-top {\n  border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-top-0 {\n  border-top: 0 !important;\n}\n\n.border-end {\n  border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-end-0 {\n  border-right: 0 !important;\n}\n\n.border-bottom {\n  border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-bottom-0 {\n  border-bottom: 0 !important;\n}\n\n.border-start {\n  border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-start-0 {\n  border-left: 0 !important;\n}\n\n.border-primary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-secondary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-success {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-info {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-warning {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-danger {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-light {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-dark {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-white {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-1 {\n  --bs-border-width: 1px;\n}\n\n.border-2 {\n  --bs-border-width: 2px;\n}\n\n.border-3 {\n  --bs-border-width: 3px;\n}\n\n.border-4 {\n  --bs-border-width: 4px;\n}\n\n.border-5 {\n  --bs-border-width: 5px;\n}\n\n.border-opacity-10 {\n  --bs-border-opacity: 0.1;\n}\n\n.border-opacity-25 {\n  --bs-border-opacity: 0.25;\n}\n\n.border-opacity-50 {\n  --bs-border-opacity: 0.5;\n}\n\n.border-opacity-75 {\n  --bs-border-opacity: 0.75;\n}\n\n.border-opacity-100 {\n  --bs-border-opacity: 1;\n}\n\n.w-25 {\n  width: 25% !important;\n}\n\n.w-50 {\n  width: 50% !important;\n}\n\n.w-75 {\n  width: 75% !important;\n}\n\n.w-100 {\n  width: 100% !important;\n}\n\n.w-auto {\n  width: auto !important;\n}\n\n.mw-100 {\n  max-width: 100% !important;\n}\n\n.vw-100 {\n  width: 100vw !important;\n}\n\n.min-vw-100 {\n  min-width: 100vw !important;\n}\n\n.h-25 {\n  height: 25% !important;\n}\n\n.h-50 {\n  height: 50% !important;\n}\n\n.h-75 {\n  height: 75% !important;\n}\n\n.h-100 {\n  height: 100% !important;\n}\n\n.h-auto {\n  height: auto !important;\n}\n\n.mh-100 {\n  max-height: 100% !important;\n}\n\n.vh-100 {\n  height: 100vh !important;\n}\n\n.min-vh-100 {\n  min-height: 100vh !important;\n}\n\n.flex-fill {\n  flex: 1 1 auto !important;\n}\n\n.flex-row {\n  flex-direction: row !important;\n}\n\n.flex-column {\n  flex-direction: column !important;\n}\n\n.flex-row-reverse {\n  flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n  flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n  flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n  flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n  flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n  flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n  justify-content: flex-start !important;\n}\n\n.justify-content-end {\n  justify-content: flex-end !important;\n}\n\n.justify-content-center {\n  justify-content: center !important;\n}\n\n.justify-content-between {\n  justify-content: space-between !important;\n}\n\n.justify-content-around {\n  justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n  justify-content: space-evenly !important;\n}\n\n.align-items-start {\n  align-items: flex-start !important;\n}\n\n.align-items-end {\n  align-items: flex-end !important;\n}\n\n.align-items-center {\n  align-items: center !important;\n}\n\n.align-items-baseline {\n  align-items: baseline !important;\n}\n\n.align-items-stretch {\n  align-items: stretch !important;\n}\n\n.align-content-start {\n  align-content: flex-start !important;\n}\n\n.align-content-end {\n  align-content: flex-end !important;\n}\n\n.align-content-center {\n  align-content: center !important;\n}\n\n.align-content-between {\n  align-content: space-between !important;\n}\n\n.align-content-around {\n  align-content: space-around !important;\n}\n\n.align-content-stretch {\n  align-content: stretch !important;\n}\n\n.align-self-auto {\n  align-self: auto !important;\n}\n\n.align-self-start {\n  align-self: flex-start !important;\n}\n\n.align-self-end {\n  align-self: flex-end !important;\n}\n\n.align-self-center {\n  align-self: center !important;\n}\n\n.align-self-baseline {\n  align-self: baseline !important;\n}\n\n.align-self-stretch {\n  align-self: stretch !important;\n}\n\n.order-first {\n  order: -1 !important;\n}\n\n.order-0 {\n  order: 0 !important;\n}\n\n.order-1 {\n  order: 1 !important;\n}\n\n.order-2 {\n  order: 2 !important;\n}\n\n.order-3 {\n  order: 3 !important;\n}\n\n.order-4 {\n  order: 4 !important;\n}\n\n.order-5 {\n  order: 5 !important;\n}\n\n.order-last {\n  order: 6 !important;\n}\n\n.m-0 {\n  margin: 0 !important;\n}\n\n.m-1 {\n  margin: 0.25rem !important;\n}\n\n.m-2 {\n  margin: 0.5rem !important;\n}\n\n.m-3 {\n  margin: 1rem !important;\n}\n\n.m-4 {\n  margin: 1.5rem !important;\n}\n\n.m-5 {\n  margin: 3rem !important;\n}\n\n.m-auto {\n  margin: auto !important;\n}\n\n.mx-0 {\n  margin-right: 0 !important;\n  margin-left: 0 !important;\n}\n\n.mx-1 {\n  margin-right: 0.25rem !important;\n  margin-left: 0.25rem !important;\n}\n\n.mx-2 {\n  margin-right: 0.5rem !important;\n  margin-left: 0.5rem !important;\n}\n\n.mx-3 {\n  margin-right: 1rem !important;\n  margin-left: 1rem !important;\n}\n\n.mx-4 {\n  margin-right: 1.5rem !important;\n  margin-left: 1.5rem !important;\n}\n\n.mx-5 {\n  margin-right: 3rem !important;\n  margin-left: 3rem !important;\n}\n\n.mx-auto {\n  margin-right: auto !important;\n  margin-left: auto !important;\n}\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important;\n}\n\n.my-4 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important;\n}\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important;\n}\n\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.mt-1 {\n  margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n  margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n  margin-top: 1rem !important;\n}\n\n.mt-4 {\n  margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n  margin-top: 3rem !important;\n}\n\n.mt-auto {\n  margin-top: auto !important;\n}\n\n.me-0 {\n  margin-right: 0 !important;\n}\n\n.me-1 {\n  margin-right: 0.25rem !important;\n}\n\n.me-2 {\n  margin-right: 0.5rem !important;\n}\n\n.me-3 {\n  margin-right: 1rem !important;\n}\n\n.me-4 {\n  margin-right: 1.5rem !important;\n}\n\n.me-5 {\n  margin-right: 3rem !important;\n}\n\n.me-auto {\n  margin-right: auto !important;\n}\n\n.mb-0 {\n  margin-bottom: 0 !important;\n}\n\n.mb-1 {\n  margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n  margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n  margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n  margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n  margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n  margin-bottom: auto !important;\n}\n\n.ms-0 {\n  margin-left: 0 !important;\n}\n\n.ms-1 {\n  margin-left: 0.25rem !important;\n}\n\n.ms-2 {\n  margin-left: 0.5rem !important;\n}\n\n.ms-3 {\n  margin-left: 1rem !important;\n}\n\n.ms-4 {\n  margin-left: 1.5rem !important;\n}\n\n.ms-5 {\n  margin-left: 3rem !important;\n}\n\n.ms-auto {\n  margin-left: auto !important;\n}\n\n.p-0 {\n  padding: 0 !important;\n}\n\n.p-1 {\n  padding: 0.25rem !important;\n}\n\n.p-2 {\n  padding: 0.5rem !important;\n}\n\n.p-3 {\n  padding: 1rem !important;\n}\n\n.p-4 {\n  padding: 1.5rem !important;\n}\n\n.p-5 {\n  padding: 3rem !important;\n}\n\n.px-0 {\n  padding-right: 0 !important;\n  padding-left: 0 !important;\n}\n\n.px-1 {\n  padding-right: 0.25rem !important;\n  padding-left: 0.25rem !important;\n}\n\n.px-2 {\n  padding-right: 0.5rem !important;\n  padding-left: 0.5rem !important;\n}\n\n.px-3 {\n  padding-right: 1rem !important;\n  padding-left: 1rem !important;\n}\n\n.px-4 {\n  padding-right: 1.5rem !important;\n  padding-left: 1.5rem !important;\n}\n\n.px-5 {\n  padding-right: 3rem !important;\n  padding-left: 3rem !important;\n}\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n\n.py-4 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n  padding-top: 0 !important;\n}\n\n.pt-1 {\n  padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n  padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n  padding-top: 1rem !important;\n}\n\n.pt-4 {\n  padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n  padding-top: 3rem !important;\n}\n\n.pe-0 {\n  padding-right: 0 !important;\n}\n\n.pe-1 {\n  padding-right: 0.25rem !important;\n}\n\n.pe-2 {\n  padding-right: 0.5rem !important;\n}\n\n.pe-3 {\n  padding-right: 1rem !important;\n}\n\n.pe-4 {\n  padding-right: 1.5rem !important;\n}\n\n.pe-5 {\n  padding-right: 3rem !important;\n}\n\n.pb-0 {\n  padding-bottom: 0 !important;\n}\n\n.pb-1 {\n  padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n  padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n  padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n  padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n  padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n  padding-left: 0 !important;\n}\n\n.ps-1 {\n  padding-left: 0.25rem !important;\n}\n\n.ps-2 {\n  padding-left: 0.5rem !important;\n}\n\n.ps-3 {\n  padding-left: 1rem !important;\n}\n\n.ps-4 {\n  padding-left: 1.5rem !important;\n}\n\n.ps-5 {\n  padding-left: 3rem !important;\n}\n\n.gap-0 {\n  gap: 0 !important;\n}\n\n.gap-1 {\n  gap: 0.25rem !important;\n}\n\n.gap-2 {\n  gap: 0.5rem !important;\n}\n\n.gap-3 {\n  gap: 1rem !important;\n}\n\n.gap-4 {\n  gap: 1.5rem !important;\n}\n\n.gap-5 {\n  gap: 3rem !important;\n}\n\n.font-monospace {\n  font-family: var(--bs-font-monospace) !important;\n}\n\n.fs-1 {\n  font-size: calc(1.375rem + 1.5vw) !important;\n}\n\n.fs-2 {\n  font-size: calc(1.325rem + 0.9vw) !important;\n}\n\n.fs-3 {\n  font-size: calc(1.3rem + 0.6vw) !important;\n}\n\n.fs-4 {\n  font-size: calc(1.275rem + 0.3vw) !important;\n}\n\n.fs-5 {\n  font-size: 1.25rem !important;\n}\n\n.fs-6 {\n  font-size: 1rem !important;\n}\n\n.fst-italic {\n  font-style: italic !important;\n}\n\n.fst-normal {\n  font-style: normal !important;\n}\n\n.fw-light {\n  font-weight: 300 !important;\n}\n\n.fw-lighter {\n  font-weight: lighter !important;\n}\n\n.fw-normal {\n  font-weight: 400 !important;\n}\n\n.fw-bold {\n  font-weight: 700 !important;\n}\n\n.fw-semibold {\n  font-weight: 600 !important;\n}\n\n.fw-bolder {\n  font-weight: bolder !important;\n}\n\n.lh-1 {\n  line-height: 1 !important;\n}\n\n.lh-sm {\n  line-height: 1.25 !important;\n}\n\n.lh-base {\n  line-height: 1.5 !important;\n}\n\n.lh-lg {\n  line-height: 2 !important;\n}\n\n.text-start {\n  text-align: left !important;\n}\n\n.text-end {\n  text-align: right !important;\n}\n\n.text-center {\n  text-align: center !important;\n}\n\n.text-decoration-none {\n  text-decoration: none !important;\n}\n\n.text-decoration-underline {\n  text-decoration: underline !important;\n}\n\n.text-decoration-line-through {\n  text-decoration: line-through !important;\n}\n\n.text-lowercase {\n  text-transform: lowercase !important;\n}\n\n.text-uppercase {\n  text-transform: uppercase !important;\n}\n\n.text-capitalize {\n  text-transform: capitalize !important;\n}\n\n.text-wrap {\n  white-space: normal !important;\n}\n\n.text-nowrap {\n  white-space: nowrap !important;\n}\n\n/* rtl:begin:remove */\n.text-break {\n  word-wrap: break-word !important;\n  word-break: break-word !important;\n}\n\n/* rtl:end:remove */\n.text-primary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-secondary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-success {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-info {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-warning {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-danger {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-light {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-dark {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-black {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-white {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-body {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-muted {\n  --bs-text-opacity: 1;\n  color: #6c757d !important;\n}\n\n.text-black-50 {\n  --bs-text-opacity: 1;\n  color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n  --bs-text-opacity: 1;\n  color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-reset {\n  --bs-text-opacity: 1;\n  color: inherit !important;\n}\n\n.text-opacity-25 {\n  --bs-text-opacity: 0.25;\n}\n\n.text-opacity-50 {\n  --bs-text-opacity: 0.5;\n}\n\n.text-opacity-75 {\n  --bs-text-opacity: 0.75;\n}\n\n.text-opacity-100 {\n  --bs-text-opacity: 1;\n}\n\n.bg-primary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-secondary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-success {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-info {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-warning {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-danger {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-light {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-dark {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-black {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-white {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-body {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-transparent {\n  --bs-bg-opacity: 1;\n  background-color: transparent !important;\n}\n\n.bg-opacity-10 {\n  --bs-bg-opacity: 0.1;\n}\n\n.bg-opacity-25 {\n  --bs-bg-opacity: 0.25;\n}\n\n.bg-opacity-50 {\n  --bs-bg-opacity: 0.5;\n}\n\n.bg-opacity-75 {\n  --bs-bg-opacity: 0.75;\n}\n\n.bg-opacity-100 {\n  --bs-bg-opacity: 1;\n}\n\n.bg-gradient {\n  background-image: var(--bs-gradient) !important;\n}\n\n.user-select-all {\n  -webkit-user-select: all !important;\n  -moz-user-select: all !important;\n  user-select: all !important;\n}\n\n.user-select-auto {\n  -webkit-user-select: auto !important;\n  -moz-user-select: auto !important;\n  user-select: auto !important;\n}\n\n.user-select-none {\n  -webkit-user-select: none !important;\n  -moz-user-select: none !important;\n  user-select: none !important;\n}\n\n.pe-none {\n  pointer-events: none !important;\n}\n\n.pe-auto {\n  pointer-events: auto !important;\n}\n\n.rounded {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-0 {\n  border-radius: 0 !important;\n}\n\n.rounded-1 {\n  border-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-2 {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-3 {\n  border-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-4 {\n  border-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-5 {\n  border-radius: var(--bs-border-radius-2xl) !important;\n}\n\n.rounded-circle {\n  border-radius: 50% !important;\n}\n\n.rounded-pill {\n  border-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-top {\n  border-top-left-radius: var(--bs-border-radius) !important;\n  border-top-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-end {\n  border-top-right-radius: var(--bs-border-radius) !important;\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-bottom {\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-start {\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n  border-top-left-radius: var(--bs-border-radius) !important;\n}\n\n.visible {\n  visibility: visible !important;\n}\n\n.invisible {\n  visibility: hidden !important;\n}\n\n@media (min-width: 576px) {\n  .float-sm-start {\n    float: left !important;\n  }\n  .float-sm-end {\n    float: right !important;\n  }\n  .float-sm-none {\n    float: none !important;\n  }\n  .d-sm-inline {\n    display: inline !important;\n  }\n  .d-sm-inline-block {\n    display: inline-block !important;\n  }\n  .d-sm-block {\n    display: block !important;\n  }\n  .d-sm-grid {\n    display: grid !important;\n  }\n  .d-sm-table {\n    display: table !important;\n  }\n  .d-sm-table-row {\n    display: table-row !important;\n  }\n  .d-sm-table-cell {\n    display: table-cell !important;\n  }\n  .d-sm-flex {\n    display: flex !important;\n  }\n  .d-sm-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-sm-none {\n    display: none !important;\n  }\n  .flex-sm-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-sm-row {\n    flex-direction: row !important;\n  }\n  .flex-sm-column {\n    flex-direction: column !important;\n  }\n  .flex-sm-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-sm-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-sm-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-sm-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-sm-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-sm-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-sm-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-sm-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-sm-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-sm-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-sm-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-sm-center {\n    justify-content: center !important;\n  }\n  .justify-content-sm-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-sm-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-sm-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-sm-start {\n    align-items: flex-start !important;\n  }\n  .align-items-sm-end {\n    align-items: flex-end !important;\n  }\n  .align-items-sm-center {\n    align-items: center !important;\n  }\n  .align-items-sm-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-sm-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-sm-start {\n    align-content: flex-start !important;\n  }\n  .align-content-sm-end {\n    align-content: flex-end !important;\n  }\n  .align-content-sm-center {\n    align-content: center !important;\n  }\n  .align-content-sm-between {\n    align-content: space-between !important;\n  }\n  .align-content-sm-around {\n    align-content: space-around !important;\n  }\n  .align-content-sm-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-sm-auto {\n    align-self: auto !important;\n  }\n  .align-self-sm-start {\n    align-self: flex-start !important;\n  }\n  .align-self-sm-end {\n    align-self: flex-end !important;\n  }\n  .align-self-sm-center {\n    align-self: center !important;\n  }\n  .align-self-sm-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-sm-stretch {\n    align-self: stretch !important;\n  }\n  .order-sm-first {\n    order: -1 !important;\n  }\n  .order-sm-0 {\n    order: 0 !important;\n  }\n  .order-sm-1 {\n    order: 1 !important;\n  }\n  .order-sm-2 {\n    order: 2 !important;\n  }\n  .order-sm-3 {\n    order: 3 !important;\n  }\n  .order-sm-4 {\n    order: 4 !important;\n  }\n  .order-sm-5 {\n    order: 5 !important;\n  }\n  .order-sm-last {\n    order: 6 !important;\n  }\n  .m-sm-0 {\n    margin: 0 !important;\n  }\n  .m-sm-1 {\n    margin: 0.25rem !important;\n  }\n  .m-sm-2 {\n    margin: 0.5rem !important;\n  }\n  .m-sm-3 {\n    margin: 1rem !important;\n  }\n  .m-sm-4 {\n    margin: 1.5rem !important;\n  }\n  .m-sm-5 {\n    margin: 3rem !important;\n  }\n  .m-sm-auto {\n    margin: auto !important;\n  }\n  .mx-sm-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-sm-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-sm-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-sm-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-sm-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-sm-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-sm-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-sm-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-sm-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-sm-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-sm-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-sm-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-sm-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-sm-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-sm-0 {\n    margin-top: 0 !important;\n  }\n  .mt-sm-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-sm-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-sm-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-sm-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-sm-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-sm-auto {\n    margin-top: auto !important;\n  }\n  .me-sm-0 {\n    margin-right: 0 !important;\n  }\n  .me-sm-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-sm-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-sm-3 {\n    margin-right: 1rem !important;\n  }\n  .me-sm-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-sm-5 {\n    margin-right: 3rem !important;\n  }\n  .me-sm-auto {\n    margin-right: auto !important;\n  }\n  .mb-sm-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-sm-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-sm-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-sm-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-sm-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-sm-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-sm-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-sm-0 {\n    margin-left: 0 !important;\n  }\n  .ms-sm-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-sm-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-sm-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-sm-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-sm-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-sm-auto {\n    margin-left: auto !important;\n  }\n  .p-sm-0 {\n    padding: 0 !important;\n  }\n  .p-sm-1 {\n    padding: 0.25rem !important;\n  }\n  .p-sm-2 {\n    padding: 0.5rem !important;\n  }\n  .p-sm-3 {\n    padding: 1rem !important;\n  }\n  .p-sm-4 {\n    padding: 1.5rem !important;\n  }\n  .p-sm-5 {\n    padding: 3rem !important;\n  }\n  .px-sm-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-sm-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-sm-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-sm-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-sm-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-sm-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-sm-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-sm-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-sm-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-sm-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-sm-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-sm-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-sm-0 {\n    padding-top: 0 !important;\n  }\n  .pt-sm-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-sm-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-sm-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-sm-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-sm-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-sm-0 {\n    padding-right: 0 !important;\n  }\n  .pe-sm-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-sm-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-sm-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-sm-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-sm-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-sm-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-sm-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-sm-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-sm-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-sm-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-sm-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-sm-0 {\n    padding-left: 0 !important;\n  }\n  .ps-sm-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-sm-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-sm-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-sm-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-sm-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-sm-0 {\n    gap: 0 !important;\n  }\n  .gap-sm-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-sm-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-sm-3 {\n    gap: 1rem !important;\n  }\n  .gap-sm-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-sm-5 {\n    gap: 3rem !important;\n  }\n  .text-sm-start {\n    text-align: left !important;\n  }\n  .text-sm-end {\n    text-align: right !important;\n  }\n  .text-sm-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 768px) {\n  .float-md-start {\n    float: left !important;\n  }\n  .float-md-end {\n    float: right !important;\n  }\n  .float-md-none {\n    float: none !important;\n  }\n  .d-md-inline {\n    display: inline !important;\n  }\n  .d-md-inline-block {\n    display: inline-block !important;\n  }\n  .d-md-block {\n    display: block !important;\n  }\n  .d-md-grid {\n    display: grid !important;\n  }\n  .d-md-table {\n    display: table !important;\n  }\n  .d-md-table-row {\n    display: table-row !important;\n  }\n  .d-md-table-cell {\n    display: table-cell !important;\n  }\n  .d-md-flex {\n    display: flex !important;\n  }\n  .d-md-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-md-none {\n    display: none !important;\n  }\n  .flex-md-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-md-row {\n    flex-direction: row !important;\n  }\n  .flex-md-column {\n    flex-direction: column !important;\n  }\n  .flex-md-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-md-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-md-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-md-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-md-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-md-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-md-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-md-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-md-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-md-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-md-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-md-center {\n    justify-content: center !important;\n  }\n  .justify-content-md-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-md-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-md-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-md-start {\n    align-items: flex-start !important;\n  }\n  .align-items-md-end {\n    align-items: flex-end !important;\n  }\n  .align-items-md-center {\n    align-items: center !important;\n  }\n  .align-items-md-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-md-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-md-start {\n    align-content: flex-start !important;\n  }\n  .align-content-md-end {\n    align-content: flex-end !important;\n  }\n  .align-content-md-center {\n    align-content: center !important;\n  }\n  .align-content-md-between {\n    align-content: space-between !important;\n  }\n  .align-content-md-around {\n    align-content: space-around !important;\n  }\n  .align-content-md-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-md-auto {\n    align-self: auto !important;\n  }\n  .align-self-md-start {\n    align-self: flex-start !important;\n  }\n  .align-self-md-end {\n    align-self: flex-end !important;\n  }\n  .align-self-md-center {\n    align-self: center !important;\n  }\n  .align-self-md-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-md-stretch {\n    align-self: stretch !important;\n  }\n  .order-md-first {\n    order: -1 !important;\n  }\n  .order-md-0 {\n    order: 0 !important;\n  }\n  .order-md-1 {\n    order: 1 !important;\n  }\n  .order-md-2 {\n    order: 2 !important;\n  }\n  .order-md-3 {\n    order: 3 !important;\n  }\n  .order-md-4 {\n    order: 4 !important;\n  }\n  .order-md-5 {\n    order: 5 !important;\n  }\n  .order-md-last {\n    order: 6 !important;\n  }\n  .m-md-0 {\n    margin: 0 !important;\n  }\n  .m-md-1 {\n    margin: 0.25rem !important;\n  }\n  .m-md-2 {\n    margin: 0.5rem !important;\n  }\n  .m-md-3 {\n    margin: 1rem !important;\n  }\n  .m-md-4 {\n    margin: 1.5rem !important;\n  }\n  .m-md-5 {\n    margin: 3rem !important;\n  }\n  .m-md-auto {\n    margin: auto !important;\n  }\n  .mx-md-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-md-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-md-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-md-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-md-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-md-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-md-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-md-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-md-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-md-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-md-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-md-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-md-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-md-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-md-0 {\n    margin-top: 0 !important;\n  }\n  .mt-md-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-md-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-md-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-md-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-md-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-md-auto {\n    margin-top: auto !important;\n  }\n  .me-md-0 {\n    margin-right: 0 !important;\n  }\n  .me-md-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-md-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-md-3 {\n    margin-right: 1rem !important;\n  }\n  .me-md-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-md-5 {\n    margin-right: 3rem !important;\n  }\n  .me-md-auto {\n    margin-right: auto !important;\n  }\n  .mb-md-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-md-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-md-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-md-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-md-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-md-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-md-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-md-0 {\n    margin-left: 0 !important;\n  }\n  .ms-md-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-md-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-md-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-md-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-md-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-md-auto {\n    margin-left: auto !important;\n  }\n  .p-md-0 {\n    padding: 0 !important;\n  }\n  .p-md-1 {\n    padding: 0.25rem !important;\n  }\n  .p-md-2 {\n    padding: 0.5rem !important;\n  }\n  .p-md-3 {\n    padding: 1rem !important;\n  }\n  .p-md-4 {\n    padding: 1.5rem !important;\n  }\n  .p-md-5 {\n    padding: 3rem !important;\n  }\n  .px-md-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-md-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-md-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-md-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-md-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-md-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-md-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-md-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-md-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-md-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-md-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-md-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-md-0 {\n    padding-top: 0 !important;\n  }\n  .pt-md-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-md-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-md-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-md-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-md-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-md-0 {\n    padding-right: 0 !important;\n  }\n  .pe-md-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-md-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-md-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-md-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-md-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-md-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-md-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-md-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-md-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-md-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-md-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-md-0 {\n    padding-left: 0 !important;\n  }\n  .ps-md-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-md-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-md-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-md-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-md-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-md-0 {\n    gap: 0 !important;\n  }\n  .gap-md-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-md-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-md-3 {\n    gap: 1rem !important;\n  }\n  .gap-md-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-md-5 {\n    gap: 3rem !important;\n  }\n  .text-md-start {\n    text-align: left !important;\n  }\n  .text-md-end {\n    text-align: right !important;\n  }\n  .text-md-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 992px) {\n  .float-lg-start {\n    float: left !important;\n  }\n  .float-lg-end {\n    float: right !important;\n  }\n  .float-lg-none {\n    float: none !important;\n  }\n  .d-lg-inline {\n    display: inline !important;\n  }\n  .d-lg-inline-block {\n    display: inline-block !important;\n  }\n  .d-lg-block {\n    display: block !important;\n  }\n  .d-lg-grid {\n    display: grid !important;\n  }\n  .d-lg-table {\n    display: table !important;\n  }\n  .d-lg-table-row {\n    display: table-row !important;\n  }\n  .d-lg-table-cell {\n    display: table-cell !important;\n  }\n  .d-lg-flex {\n    display: flex !important;\n  }\n  .d-lg-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-lg-none {\n    display: none !important;\n  }\n  .flex-lg-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-lg-row {\n    flex-direction: row !important;\n  }\n  .flex-lg-column {\n    flex-direction: column !important;\n  }\n  .flex-lg-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-lg-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-lg-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-lg-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-lg-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-lg-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-lg-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-lg-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-lg-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-lg-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-lg-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-lg-center {\n    justify-content: center !important;\n  }\n  .justify-content-lg-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-lg-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-lg-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-lg-start {\n    align-items: flex-start !important;\n  }\n  .align-items-lg-end {\n    align-items: flex-end !important;\n  }\n  .align-items-lg-center {\n    align-items: center !important;\n  }\n  .align-items-lg-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-lg-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-lg-start {\n    align-content: flex-start !important;\n  }\n  .align-content-lg-end {\n    align-content: flex-end !important;\n  }\n  .align-content-lg-center {\n    align-content: center !important;\n  }\n  .align-content-lg-between {\n    align-content: space-between !important;\n  }\n  .align-content-lg-around {\n    align-content: space-around !important;\n  }\n  .align-content-lg-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-lg-auto {\n    align-self: auto !important;\n  }\n  .align-self-lg-start {\n    align-self: flex-start !important;\n  }\n  .align-self-lg-end {\n    align-self: flex-end !important;\n  }\n  .align-self-lg-center {\n    align-self: center !important;\n  }\n  .align-self-lg-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-lg-stretch {\n    align-self: stretch !important;\n  }\n  .order-lg-first {\n    order: -1 !important;\n  }\n  .order-lg-0 {\n    order: 0 !important;\n  }\n  .order-lg-1 {\n    order: 1 !important;\n  }\n  .order-lg-2 {\n    order: 2 !important;\n  }\n  .order-lg-3 {\n    order: 3 !important;\n  }\n  .order-lg-4 {\n    order: 4 !important;\n  }\n  .order-lg-5 {\n    order: 5 !important;\n  }\n  .order-lg-last {\n    order: 6 !important;\n  }\n  .m-lg-0 {\n    margin: 0 !important;\n  }\n  .m-lg-1 {\n    margin: 0.25rem !important;\n  }\n  .m-lg-2 {\n    margin: 0.5rem !important;\n  }\n  .m-lg-3 {\n    margin: 1rem !important;\n  }\n  .m-lg-4 {\n    margin: 1.5rem !important;\n  }\n  .m-lg-5 {\n    margin: 3rem !important;\n  }\n  .m-lg-auto {\n    margin: auto !important;\n  }\n  .mx-lg-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-lg-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-lg-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-lg-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-lg-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-lg-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-lg-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-lg-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-lg-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-lg-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-lg-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-lg-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-lg-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-lg-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-lg-0 {\n    margin-top: 0 !important;\n  }\n  .mt-lg-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-lg-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-lg-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-lg-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-lg-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-lg-auto {\n    margin-top: auto !important;\n  }\n  .me-lg-0 {\n    margin-right: 0 !important;\n  }\n  .me-lg-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-lg-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-lg-3 {\n    margin-right: 1rem !important;\n  }\n  .me-lg-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-lg-5 {\n    margin-right: 3rem !important;\n  }\n  .me-lg-auto {\n    margin-right: auto !important;\n  }\n  .mb-lg-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-lg-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-lg-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-lg-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-lg-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-lg-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-lg-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-lg-0 {\n    margin-left: 0 !important;\n  }\n  .ms-lg-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-lg-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-lg-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-lg-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-lg-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-lg-auto {\n    margin-left: auto !important;\n  }\n  .p-lg-0 {\n    padding: 0 !important;\n  }\n  .p-lg-1 {\n    padding: 0.25rem !important;\n  }\n  .p-lg-2 {\n    padding: 0.5rem !important;\n  }\n  .p-lg-3 {\n    padding: 1rem !important;\n  }\n  .p-lg-4 {\n    padding: 1.5rem !important;\n  }\n  .p-lg-5 {\n    padding: 3rem !important;\n  }\n  .px-lg-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-lg-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-lg-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-lg-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-lg-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-lg-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-lg-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-lg-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-lg-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-lg-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-lg-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-lg-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-lg-0 {\n    padding-top: 0 !important;\n  }\n  .pt-lg-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-lg-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-lg-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-lg-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-lg-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-lg-0 {\n    padding-right: 0 !important;\n  }\n  .pe-lg-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-lg-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-lg-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-lg-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-lg-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-lg-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-lg-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-lg-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-lg-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-lg-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-lg-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-lg-0 {\n    padding-left: 0 !important;\n  }\n  .ps-lg-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-lg-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-lg-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-lg-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-lg-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-lg-0 {\n    gap: 0 !important;\n  }\n  .gap-lg-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-lg-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-lg-3 {\n    gap: 1rem !important;\n  }\n  .gap-lg-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-lg-5 {\n    gap: 3rem !important;\n  }\n  .text-lg-start {\n    text-align: left !important;\n  }\n  .text-lg-end {\n    text-align: right !important;\n  }\n  .text-lg-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .float-xl-start {\n    float: left !important;\n  }\n  .float-xl-end {\n    float: right !important;\n  }\n  .float-xl-none {\n    float: none !important;\n  }\n  .d-xl-inline {\n    display: inline !important;\n  }\n  .d-xl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xl-block {\n    display: block !important;\n  }\n  .d-xl-grid {\n    display: grid !important;\n  }\n  .d-xl-table {\n    display: table !important;\n  }\n  .d-xl-table-row {\n    display: table-row !important;\n  }\n  .d-xl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xl-flex {\n    display: flex !important;\n  }\n  .d-xl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xl-none {\n    display: none !important;\n  }\n  .flex-xl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xl-row {\n    flex-direction: row !important;\n  }\n  .flex-xl-column {\n    flex-direction: column !important;\n  }\n  .flex-xl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xl-center {\n    align-items: center !important;\n  }\n  .align-items-xl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xl-center {\n    align-content: center !important;\n  }\n  .align-content-xl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xl-center {\n    align-self: center !important;\n  }\n  .align-self-xl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xl-first {\n    order: -1 !important;\n  }\n  .order-xl-0 {\n    order: 0 !important;\n  }\n  .order-xl-1 {\n    order: 1 !important;\n  }\n  .order-xl-2 {\n    order: 2 !important;\n  }\n  .order-xl-3 {\n    order: 3 !important;\n  }\n  .order-xl-4 {\n    order: 4 !important;\n  }\n  .order-xl-5 {\n    order: 5 !important;\n  }\n  .order-xl-last {\n    order: 6 !important;\n  }\n  .m-xl-0 {\n    margin: 0 !important;\n  }\n  .m-xl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xl-3 {\n    margin: 1rem !important;\n  }\n  .m-xl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xl-5 {\n    margin: 3rem !important;\n  }\n  .m-xl-auto {\n    margin: auto !important;\n  }\n  .mx-xl-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-xl-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-xl-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-xl-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-xl-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-xl-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-xl-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-xl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xl-auto {\n    margin-top: auto !important;\n  }\n  .me-xl-0 {\n    margin-right: 0 !important;\n  }\n  .me-xl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-xl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-xl-3 {\n    margin-right: 1rem !important;\n  }\n  .me-xl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-xl-5 {\n    margin-right: 3rem !important;\n  }\n  .me-xl-auto {\n    margin-right: auto !important;\n  }\n  .mb-xl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xl-0 {\n    margin-left: 0 !important;\n  }\n  .ms-xl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-xl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-xl-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-xl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-xl-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-xl-auto {\n    margin-left: auto !important;\n  }\n  .p-xl-0 {\n    padding: 0 !important;\n  }\n  .p-xl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xl-3 {\n    padding: 1rem !important;\n  }\n  .p-xl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xl-5 {\n    padding: 3rem !important;\n  }\n  .px-xl-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-xl-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-xl-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-xl-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-xl-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-xl-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-xl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xl-0 {\n    padding-right: 0 !important;\n  }\n  .pe-xl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-xl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-xl-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-xl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-xl-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-xl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xl-0 {\n    padding-left: 0 !important;\n  }\n  .ps-xl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-xl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-xl-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-xl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-xl-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-xl-0 {\n    gap: 0 !important;\n  }\n  .gap-xl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xl-5 {\n    gap: 3rem !important;\n  }\n  .text-xl-start {\n    text-align: left !important;\n  }\n  .text-xl-end {\n    text-align: right !important;\n  }\n  .text-xl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1400px) {\n  .float-xxl-start {\n    float: left !important;\n  }\n  .float-xxl-end {\n    float: right !important;\n  }\n  .float-xxl-none {\n    float: none !important;\n  }\n  .d-xxl-inline {\n    display: inline !important;\n  }\n  .d-xxl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xxl-block {\n    display: block !important;\n  }\n  .d-xxl-grid {\n    display: grid !important;\n  }\n  .d-xxl-table {\n    display: table !important;\n  }\n  .d-xxl-table-row {\n    display: table-row !important;\n  }\n  .d-xxl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xxl-flex {\n    display: flex !important;\n  }\n  .d-xxl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xxl-none {\n    display: none !important;\n  }\n  .flex-xxl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xxl-row {\n    flex-direction: row !important;\n  }\n  .flex-xxl-column {\n    flex-direction: column !important;\n  }\n  .flex-xxl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xxl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xxl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xxl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xxl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xxl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xxl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xxl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xxl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xxl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xxl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xxl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xxl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xxl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xxl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xxl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xxl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xxl-center {\n    align-items: center !important;\n  }\n  .align-items-xxl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xxl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xxl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xxl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xxl-center {\n    align-content: center !important;\n  }\n  .align-content-xxl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xxl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xxl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xxl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xxl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xxl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xxl-center {\n    align-self: center !important;\n  }\n  .align-self-xxl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xxl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xxl-first {\n    order: -1 !important;\n  }\n  .order-xxl-0 {\n    order: 0 !important;\n  }\n  .order-xxl-1 {\n    order: 1 !important;\n  }\n  .order-xxl-2 {\n    order: 2 !important;\n  }\n  .order-xxl-3 {\n    order: 3 !important;\n  }\n  .order-xxl-4 {\n    order: 4 !important;\n  }\n  .order-xxl-5 {\n    order: 5 !important;\n  }\n  .order-xxl-last {\n    order: 6 !important;\n  }\n  .m-xxl-0 {\n    margin: 0 !important;\n  }\n  .m-xxl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xxl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xxl-3 {\n    margin: 1rem !important;\n  }\n  .m-xxl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xxl-5 {\n    margin: 3rem !important;\n  }\n  .m-xxl-auto {\n    margin: auto !important;\n  }\n  .mx-xxl-0 {\n    margin-right: 0 !important;\n    margin-left: 0 !important;\n  }\n  .mx-xxl-1 {\n    margin-right: 0.25rem !important;\n    margin-left: 0.25rem !important;\n  }\n  .mx-xxl-2 {\n    margin-right: 0.5rem !important;\n    margin-left: 0.5rem !important;\n  }\n  .mx-xxl-3 {\n    margin-right: 1rem !important;\n    margin-left: 1rem !important;\n  }\n  .mx-xxl-4 {\n    margin-right: 1.5rem !important;\n    margin-left: 1.5rem !important;\n  }\n  .mx-xxl-5 {\n    margin-right: 3rem !important;\n    margin-left: 3rem !important;\n  }\n  .mx-xxl-auto {\n    margin-right: auto !important;\n    margin-left: auto !important;\n  }\n  .my-xxl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xxl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xxl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xxl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xxl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xxl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xxl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xxl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xxl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xxl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xxl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xxl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xxl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xxl-auto {\n    margin-top: auto !important;\n  }\n  .me-xxl-0 {\n    margin-right: 0 !important;\n  }\n  .me-xxl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .me-xxl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .me-xxl-3 {\n    margin-right: 1rem !important;\n  }\n  .me-xxl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .me-xxl-5 {\n    margin-right: 3rem !important;\n  }\n  .me-xxl-auto {\n    margin-right: auto !important;\n  }\n  .mb-xxl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xxl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xxl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xxl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xxl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xxl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xxl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xxl-0 {\n    margin-left: 0 !important;\n  }\n  .ms-xxl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .ms-xxl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .ms-xxl-3 {\n    margin-left: 1rem !important;\n  }\n  .ms-xxl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .ms-xxl-5 {\n    margin-left: 3rem !important;\n  }\n  .ms-xxl-auto {\n    margin-left: auto !important;\n  }\n  .p-xxl-0 {\n    padding: 0 !important;\n  }\n  .p-xxl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xxl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xxl-3 {\n    padding: 1rem !important;\n  }\n  .p-xxl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xxl-5 {\n    padding: 3rem !important;\n  }\n  .px-xxl-0 {\n    padding-right: 0 !important;\n    padding-left: 0 !important;\n  }\n  .px-xxl-1 {\n    padding-right: 0.25rem !important;\n    padding-left: 0.25rem !important;\n  }\n  .px-xxl-2 {\n    padding-right: 0.5rem !important;\n    padding-left: 0.5rem !important;\n  }\n  .px-xxl-3 {\n    padding-right: 1rem !important;\n    padding-left: 1rem !important;\n  }\n  .px-xxl-4 {\n    padding-right: 1.5rem !important;\n    padding-left: 1.5rem !important;\n  }\n  .px-xxl-5 {\n    padding-right: 3rem !important;\n    padding-left: 3rem !important;\n  }\n  .py-xxl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xxl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xxl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xxl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xxl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xxl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xxl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xxl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xxl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xxl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xxl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xxl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xxl-0 {\n    padding-right: 0 !important;\n  }\n  .pe-xxl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .pe-xxl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .pe-xxl-3 {\n    padding-right: 1rem !important;\n  }\n  .pe-xxl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .pe-xxl-5 {\n    padding-right: 3rem !important;\n  }\n  .pb-xxl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xxl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xxl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xxl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xxl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xxl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xxl-0 {\n    padding-left: 0 !important;\n  }\n  .ps-xxl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .ps-xxl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .ps-xxl-3 {\n    padding-left: 1rem !important;\n  }\n  .ps-xxl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .ps-xxl-5 {\n    padding-left: 3rem !important;\n  }\n  .gap-xxl-0 {\n    gap: 0 !important;\n  }\n  .gap-xxl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xxl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xxl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xxl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xxl-5 {\n    gap: 3rem !important;\n  }\n  .text-xxl-start {\n    text-align: left !important;\n  }\n  .text-xxl-end {\n    text-align: right !important;\n  }\n  .text-xxl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .fs-1 {\n    font-size: 2.5rem !important;\n  }\n  .fs-2 {\n    font-size: 2rem !important;\n  }\n  .fs-3 {\n    font-size: 1.75rem !important;\n  }\n  .fs-4 {\n    font-size: 1.5rem !important;\n  }\n}\n@media print {\n  .d-print-inline {\n    display: inline !important;\n  }\n  .d-print-inline-block {\n    display: inline-block !important;\n  }\n  .d-print-block {\n    display: block !important;\n  }\n  .d-print-grid {\n    display: grid !important;\n  }\n  .d-print-table {\n    display: table !important;\n  }\n  .d-print-table-row {\n    display: table-row !important;\n  }\n  .d-print-table-cell {\n    display: table-cell !important;\n  }\n  .d-print-flex {\n    display: flex !important;\n  }\n  .d-print-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-print-none {\n    display: none !important;\n  }\n}\n\n/*# sourceMappingURL=bootstrap.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/css/bootstrap.rtl.css",
    "content": "@charset \"UTF-8\";\n/*!\n * Bootstrap  v5.2.3 (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n:root {\n  --bs-blue: #0d6efd;\n  --bs-indigo: #6610f2;\n  --bs-purple: #6f42c1;\n  --bs-pink: #d63384;\n  --bs-red: #dc3545;\n  --bs-orange: #fd7e14;\n  --bs-yellow: #ffc107;\n  --bs-green: #198754;\n  --bs-teal: #20c997;\n  --bs-cyan: #0dcaf0;\n  --bs-black: #000;\n  --bs-white: #fff;\n  --bs-gray: #6c757d;\n  --bs-gray-dark: #343a40;\n  --bs-gray-100: #f8f9fa;\n  --bs-gray-200: #e9ecef;\n  --bs-gray-300: #dee2e6;\n  --bs-gray-400: #ced4da;\n  --bs-gray-500: #adb5bd;\n  --bs-gray-600: #6c757d;\n  --bs-gray-700: #495057;\n  --bs-gray-800: #343a40;\n  --bs-gray-900: #212529;\n  --bs-primary: #0d6efd;\n  --bs-secondary: #6c757d;\n  --bs-success: #198754;\n  --bs-info: #0dcaf0;\n  --bs-warning: #ffc107;\n  --bs-danger: #dc3545;\n  --bs-light: #f8f9fa;\n  --bs-dark: #212529;\n  --bs-primary-rgb: 13, 110, 253;\n  --bs-secondary-rgb: 108, 117, 125;\n  --bs-success-rgb: 25, 135, 84;\n  --bs-info-rgb: 13, 202, 240;\n  --bs-warning-rgb: 255, 193, 7;\n  --bs-danger-rgb: 220, 53, 69;\n  --bs-light-rgb: 248, 249, 250;\n  --bs-dark-rgb: 33, 37, 41;\n  --bs-white-rgb: 255, 255, 255;\n  --bs-black-rgb: 0, 0, 0;\n  --bs-body-color-rgb: 33, 37, 41;\n  --bs-body-bg-rgb: 255, 255, 255;\n  --bs-font-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));\n  --bs-body-font-family: var(--bs-font-sans-serif);\n  --bs-body-font-size: 1rem;\n  --bs-body-font-weight: 400;\n  --bs-body-line-height: 1.5;\n  --bs-body-color: #212529;\n  --bs-body-bg: #fff;\n  --bs-border-width: 1px;\n  --bs-border-style: solid;\n  --bs-border-color: #dee2e6;\n  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);\n  --bs-border-radius: 0.375rem;\n  --bs-border-radius-sm: 0.25rem;\n  --bs-border-radius-lg: 0.5rem;\n  --bs-border-radius-xl: 1rem;\n  --bs-border-radius-2xl: 2rem;\n  --bs-border-radius-pill: 50rem;\n  --bs-link-color: #0d6efd;\n  --bs-link-hover-color: #0a58ca;\n  --bs-code-color: #d63384;\n  --bs-highlight-bg: #fff3cd;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  :root {\n    scroll-behavior: smooth;\n  }\n}\n\nbody {\n  margin: 0;\n  font-family: var(--bs-body-font-family);\n  font-size: var(--bs-body-font-size);\n  font-weight: var(--bs-body-font-weight);\n  line-height: var(--bs-body-line-height);\n  color: var(--bs-body-color);\n  text-align: var(--bs-body-text-align);\n  background-color: var(--bs-body-bg);\n  -webkit-text-size-adjust: 100%;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nhr {\n  margin: 1rem 0;\n  color: inherit;\n  border: 0;\n  border-top: 1px solid;\n  opacity: 0.25;\n}\n\nh6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {\n  margin-top: 0;\n  margin-bottom: 0.5rem;\n  font-weight: 500;\n  line-height: 1.2;\n}\n\nh1, .h1 {\n  font-size: calc(1.375rem + 1.5vw);\n}\n@media (min-width: 1200px) {\n  h1, .h1 {\n    font-size: 2.5rem;\n  }\n}\n\nh2, .h2 {\n  font-size: calc(1.325rem + 0.9vw);\n}\n@media (min-width: 1200px) {\n  h2, .h2 {\n    font-size: 2rem;\n  }\n}\n\nh3, .h3 {\n  font-size: calc(1.3rem + 0.6vw);\n}\n@media (min-width: 1200px) {\n  h3, .h3 {\n    font-size: 1.75rem;\n  }\n}\n\nh4, .h4 {\n  font-size: calc(1.275rem + 0.3vw);\n}\n@media (min-width: 1200px) {\n  h4, .h4 {\n    font-size: 1.5rem;\n  }\n}\n\nh5, .h5 {\n  font-size: 1.25rem;\n}\n\nh6, .h6 {\n  font-size: 1rem;\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nabbr[title] {\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n  cursor: help;\n  -webkit-text-decoration-skip-ink: none;\n  text-decoration-skip-ink: none;\n}\n\naddress {\n  margin-bottom: 1rem;\n  font-style: normal;\n  line-height: inherit;\n}\n\nol,\nul {\n  padding-right: 2rem;\n}\n\nol,\nul,\ndl {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n  margin-bottom: 0;\n}\n\ndt {\n  font-weight: 700;\n}\n\ndd {\n  margin-bottom: 0.5rem;\n  margin-right: 0;\n}\n\nblockquote {\n  margin: 0 0 1rem;\n}\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\nsmall, .small {\n  font-size: 0.875em;\n}\n\nmark, .mark {\n  padding: 0.1875em;\n  background-color: var(--bs-highlight-bg);\n}\n\nsub,\nsup {\n  position: relative;\n  font-size: 0.75em;\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\na {\n  color: var(--bs-link-color);\n  text-decoration: underline;\n}\na:hover {\n  color: var(--bs-link-hover-color);\n}\n\na:not([href]):not([class]), a:not([href]):not([class]):hover {\n  color: inherit;\n  text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: var(--bs-font-monospace);\n  font-size: 1em;\n}\n\npre {\n  display: block;\n  margin-top: 0;\n  margin-bottom: 1rem;\n  overflow: auto;\n  font-size: 0.875em;\n}\npre code {\n  font-size: inherit;\n  color: inherit;\n  word-break: normal;\n}\n\ncode {\n  font-size: 0.875em;\n  color: var(--bs-code-color);\n  word-wrap: break-word;\n}\na > code {\n  color: inherit;\n}\n\nkbd {\n  padding: 0.1875rem 0.375rem;\n  font-size: 0.875em;\n  color: var(--bs-body-bg);\n  background-color: var(--bs-body-color);\n  border-radius: 0.25rem;\n}\nkbd kbd {\n  padding: 0;\n  font-size: 1em;\n}\n\nfigure {\n  margin: 0 0 1rem;\n}\n\nimg,\nsvg {\n  vertical-align: middle;\n}\n\ntable {\n  caption-side: bottom;\n  border-collapse: collapse;\n}\n\ncaption {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: #6c757d;\n  text-align: right;\n}\n\nth {\n  text-align: inherit;\n  text-align: -webkit-match-parent;\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n}\n\nlabel {\n  display: inline-block;\n}\n\nbutton {\n  border-radius: 0;\n}\n\nbutton:focus:not(:focus-visible) {\n  outline: 0;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n  margin: 0;\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n[role=button] {\n  cursor: pointer;\n}\n\nselect {\n  word-wrap: normal;\n}\nselect:disabled {\n  opacity: 1;\n}\n\n[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {\n  display: none !important;\n}\n\nbutton,\n[type=button],\n[type=reset],\n[type=submit] {\n  -webkit-appearance: button;\n}\nbutton:not(:disabled),\n[type=button]:not(:disabled),\n[type=reset]:not(:disabled),\n[type=submit]:not(:disabled) {\n  cursor: pointer;\n}\n\n::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\ntextarea {\n  resize: vertical;\n}\n\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\n\nlegend {\n  float: right;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 0.5rem;\n  font-size: calc(1.275rem + 0.3vw);\n  line-height: inherit;\n}\n@media (min-width: 1200px) {\n  legend {\n    font-size: 1.5rem;\n  }\n}\nlegend + * {\n  clear: right;\n}\n\n::-webkit-datetime-edit-fields-wrapper,\n::-webkit-datetime-edit-text,\n::-webkit-datetime-edit-minute,\n::-webkit-datetime-edit-hour-field,\n::-webkit-datetime-edit-day-field,\n::-webkit-datetime-edit-month-field,\n::-webkit-datetime-edit-year-field {\n  padding: 0;\n}\n\n::-webkit-inner-spin-button {\n  height: auto;\n}\n\n[type=search] {\n  outline-offset: -2px;\n  -webkit-appearance: textfield;\n}\n\n[type=\"tel\"],\n[type=\"url\"],\n[type=\"email\"],\n[type=\"number\"] {\n  direction: ltr;\n}\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n::-webkit-color-swatch-wrapper {\n  padding: 0;\n}\n\n::-webkit-file-upload-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\n::file-selector-button {\n  font: inherit;\n  -webkit-appearance: button;\n}\n\noutput {\n  display: inline-block;\n}\n\niframe {\n  border: 0;\n}\n\nsummary {\n  display: list-item;\n  cursor: pointer;\n}\n\nprogress {\n  vertical-align: baseline;\n}\n\n[hidden] {\n  display: none !important;\n}\n\n.lead {\n  font-size: 1.25rem;\n  font-weight: 300;\n}\n\n.display-1 {\n  font-size: calc(1.625rem + 4.5vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-1 {\n    font-size: 5rem;\n  }\n}\n\n.display-2 {\n  font-size: calc(1.575rem + 3.9vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-2 {\n    font-size: 4.5rem;\n  }\n}\n\n.display-3 {\n  font-size: calc(1.525rem + 3.3vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-3 {\n    font-size: 4rem;\n  }\n}\n\n.display-4 {\n  font-size: calc(1.475rem + 2.7vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-4 {\n    font-size: 3.5rem;\n  }\n}\n\n.display-5 {\n  font-size: calc(1.425rem + 2.1vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-5 {\n    font-size: 3rem;\n  }\n}\n\n.display-6 {\n  font-size: calc(1.375rem + 1.5vw);\n  font-weight: 300;\n  line-height: 1.2;\n}\n@media (min-width: 1200px) {\n  .display-6 {\n    font-size: 2.5rem;\n  }\n}\n\n.list-unstyled {\n  padding-right: 0;\n  list-style: none;\n}\n\n.list-inline {\n  padding-right: 0;\n  list-style: none;\n}\n\n.list-inline-item {\n  display: inline-block;\n}\n.list-inline-item:not(:last-child) {\n  margin-left: 0.5rem;\n}\n\n.initialism {\n  font-size: 0.875em;\n  text-transform: uppercase;\n}\n\n.blockquote {\n  margin-bottom: 1rem;\n  font-size: 1.25rem;\n}\n.blockquote > :last-child {\n  margin-bottom: 0;\n}\n\n.blockquote-footer {\n  margin-top: -1rem;\n  margin-bottom: 1rem;\n  font-size: 0.875em;\n  color: #6c757d;\n}\n.blockquote-footer::before {\n  content: \"— \";\n}\n\n.img-fluid {\n  max-width: 100%;\n  height: auto;\n}\n\n.img-thumbnail {\n  padding: 0.25rem;\n  background-color: #fff;\n  border: 1px solid var(--bs-border-color);\n  border-radius: 0.375rem;\n  max-width: 100%;\n  height: auto;\n}\n\n.figure {\n  display: inline-block;\n}\n\n.figure-img {\n  margin-bottom: 0.5rem;\n  line-height: 1;\n}\n\n.figure-caption {\n  font-size: 0.875em;\n  color: #6c757d;\n}\n\n.container,\n.container-fluid,\n.container-xxl,\n.container-xl,\n.container-lg,\n.container-md,\n.container-sm {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  width: 100%;\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  margin-left: auto;\n  margin-right: auto;\n}\n\n@media (min-width: 576px) {\n  .container-sm, .container {\n    max-width: 540px;\n  }\n}\n@media (min-width: 768px) {\n  .container-md, .container-sm, .container {\n    max-width: 720px;\n  }\n}\n@media (min-width: 992px) {\n  .container-lg, .container-md, .container-sm, .container {\n    max-width: 960px;\n  }\n}\n@media (min-width: 1200px) {\n  .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1140px;\n  }\n}\n@media (min-width: 1400px) {\n  .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {\n    max-width: 1320px;\n  }\n}\n.row {\n  --bs-gutter-x: 1.5rem;\n  --bs-gutter-y: 0;\n  display: flex;\n  flex-wrap: wrap;\n  margin-top: calc(-1 * var(--bs-gutter-y));\n  margin-left: calc(-0.5 * var(--bs-gutter-x));\n  margin-right: calc(-0.5 * var(--bs-gutter-x));\n}\n.row > * {\n  flex-shrink: 0;\n  width: 100%;\n  max-width: 100%;\n  padding-left: calc(var(--bs-gutter-x) * 0.5);\n  padding-right: calc(var(--bs-gutter-x) * 0.5);\n  margin-top: var(--bs-gutter-y);\n}\n\n.col {\n  flex: 1 0 0%;\n}\n\n.row-cols-auto > * {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.row-cols-1 > * {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.row-cols-2 > * {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.row-cols-3 > * {\n  flex: 0 0 auto;\n  width: 33.3333333333%;\n}\n\n.row-cols-4 > * {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.row-cols-5 > * {\n  flex: 0 0 auto;\n  width: 20%;\n}\n\n.row-cols-6 > * {\n  flex: 0 0 auto;\n  width: 16.6666666667%;\n}\n\n.col-auto {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n.col-1 {\n  flex: 0 0 auto;\n  width: 8.33333333%;\n}\n\n.col-2 {\n  flex: 0 0 auto;\n  width: 16.66666667%;\n}\n\n.col-3 {\n  flex: 0 0 auto;\n  width: 25%;\n}\n\n.col-4 {\n  flex: 0 0 auto;\n  width: 33.33333333%;\n}\n\n.col-5 {\n  flex: 0 0 auto;\n  width: 41.66666667%;\n}\n\n.col-6 {\n  flex: 0 0 auto;\n  width: 50%;\n}\n\n.col-7 {\n  flex: 0 0 auto;\n  width: 58.33333333%;\n}\n\n.col-8 {\n  flex: 0 0 auto;\n  width: 66.66666667%;\n}\n\n.col-9 {\n  flex: 0 0 auto;\n  width: 75%;\n}\n\n.col-10 {\n  flex: 0 0 auto;\n  width: 83.33333333%;\n}\n\n.col-11 {\n  flex: 0 0 auto;\n  width: 91.66666667%;\n}\n\n.col-12 {\n  flex: 0 0 auto;\n  width: 100%;\n}\n\n.offset-1 {\n  margin-right: 8.33333333%;\n}\n\n.offset-2 {\n  margin-right: 16.66666667%;\n}\n\n.offset-3 {\n  margin-right: 25%;\n}\n\n.offset-4 {\n  margin-right: 33.33333333%;\n}\n\n.offset-5 {\n  margin-right: 41.66666667%;\n}\n\n.offset-6 {\n  margin-right: 50%;\n}\n\n.offset-7 {\n  margin-right: 58.33333333%;\n}\n\n.offset-8 {\n  margin-right: 66.66666667%;\n}\n\n.offset-9 {\n  margin-right: 75%;\n}\n\n.offset-10 {\n  margin-right: 83.33333333%;\n}\n\n.offset-11 {\n  margin-right: 91.66666667%;\n}\n\n.g-0,\n.gx-0 {\n  --bs-gutter-x: 0;\n}\n\n.g-0,\n.gy-0 {\n  --bs-gutter-y: 0;\n}\n\n.g-1,\n.gx-1 {\n  --bs-gutter-x: 0.25rem;\n}\n\n.g-1,\n.gy-1 {\n  --bs-gutter-y: 0.25rem;\n}\n\n.g-2,\n.gx-2 {\n  --bs-gutter-x: 0.5rem;\n}\n\n.g-2,\n.gy-2 {\n  --bs-gutter-y: 0.5rem;\n}\n\n.g-3,\n.gx-3 {\n  --bs-gutter-x: 1rem;\n}\n\n.g-3,\n.gy-3 {\n  --bs-gutter-y: 1rem;\n}\n\n.g-4,\n.gx-4 {\n  --bs-gutter-x: 1.5rem;\n}\n\n.g-4,\n.gy-4 {\n  --bs-gutter-y: 1.5rem;\n}\n\n.g-5,\n.gx-5 {\n  --bs-gutter-x: 3rem;\n}\n\n.g-5,\n.gy-5 {\n  --bs-gutter-y: 3rem;\n}\n\n@media (min-width: 576px) {\n  .col-sm {\n    flex: 1 0 0%;\n  }\n  .row-cols-sm-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-sm-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-sm-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-sm-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-sm-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-sm-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-sm-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-sm-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-sm-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-sm-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-sm-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-sm-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-sm-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-sm-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-sm-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-sm-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-sm-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-sm-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-sm-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-sm-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-sm-0 {\n    margin-right: 0;\n  }\n  .offset-sm-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-sm-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-sm-3 {\n    margin-right: 25%;\n  }\n  .offset-sm-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-sm-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-sm-6 {\n    margin-right: 50%;\n  }\n  .offset-sm-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-sm-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-sm-9 {\n    margin-right: 75%;\n  }\n  .offset-sm-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-sm-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-sm-0,\n.gx-sm-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-sm-0,\n.gy-sm-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-sm-1,\n.gx-sm-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-sm-1,\n.gy-sm-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-sm-2,\n.gx-sm-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-sm-2,\n.gy-sm-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-sm-3,\n.gx-sm-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-sm-3,\n.gy-sm-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-sm-4,\n.gx-sm-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-sm-4,\n.gy-sm-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-sm-5,\n.gx-sm-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-sm-5,\n.gy-sm-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 768px) {\n  .col-md {\n    flex: 1 0 0%;\n  }\n  .row-cols-md-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-md-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-md-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-md-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-md-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-md-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-md-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-md-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-md-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-md-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-md-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-md-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-md-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-md-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-md-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-md-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-md-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-md-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-md-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-md-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-md-0 {\n    margin-right: 0;\n  }\n  .offset-md-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-md-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-md-3 {\n    margin-right: 25%;\n  }\n  .offset-md-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-md-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-md-6 {\n    margin-right: 50%;\n  }\n  .offset-md-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-md-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-md-9 {\n    margin-right: 75%;\n  }\n  .offset-md-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-md-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-md-0,\n.gx-md-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-md-0,\n.gy-md-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-md-1,\n.gx-md-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-md-1,\n.gy-md-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-md-2,\n.gx-md-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-md-2,\n.gy-md-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-md-3,\n.gx-md-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-md-3,\n.gy-md-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-md-4,\n.gx-md-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-md-4,\n.gy-md-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-md-5,\n.gx-md-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-md-5,\n.gy-md-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 992px) {\n  .col-lg {\n    flex: 1 0 0%;\n  }\n  .row-cols-lg-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-lg-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-lg-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-lg-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-lg-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-lg-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-lg-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-lg-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-lg-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-lg-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-lg-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-lg-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-lg-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-lg-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-lg-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-lg-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-lg-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-lg-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-lg-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-lg-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-lg-0 {\n    margin-right: 0;\n  }\n  .offset-lg-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-lg-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-lg-3 {\n    margin-right: 25%;\n  }\n  .offset-lg-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-lg-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-lg-6 {\n    margin-right: 50%;\n  }\n  .offset-lg-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-lg-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-lg-9 {\n    margin-right: 75%;\n  }\n  .offset-lg-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-lg-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-lg-0,\n.gx-lg-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-lg-0,\n.gy-lg-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-lg-1,\n.gx-lg-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-lg-1,\n.gy-lg-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-lg-2,\n.gx-lg-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-lg-2,\n.gy-lg-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-lg-3,\n.gx-lg-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-lg-3,\n.gy-lg-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-lg-4,\n.gx-lg-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-lg-4,\n.gy-lg-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-lg-5,\n.gx-lg-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-lg-5,\n.gy-lg-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1200px) {\n  .col-xl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xl-0 {\n    margin-right: 0;\n  }\n  .offset-xl-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-xl-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-xl-3 {\n    margin-right: 25%;\n  }\n  .offset-xl-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-xl-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-xl-6 {\n    margin-right: 50%;\n  }\n  .offset-xl-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-xl-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-xl-9 {\n    margin-right: 75%;\n  }\n  .offset-xl-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-xl-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-xl-0,\n.gx-xl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xl-0,\n.gy-xl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xl-1,\n.gx-xl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xl-1,\n.gy-xl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xl-2,\n.gx-xl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xl-2,\n.gy-xl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xl-3,\n.gx-xl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xl-3,\n.gy-xl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xl-4,\n.gx-xl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xl-4,\n.gy-xl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xl-5,\n.gx-xl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xl-5,\n.gy-xl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n@media (min-width: 1400px) {\n  .col-xxl {\n    flex: 1 0 0%;\n  }\n  .row-cols-xxl-auto > * {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .row-cols-xxl-1 > * {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .row-cols-xxl-2 > * {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .row-cols-xxl-3 > * {\n    flex: 0 0 auto;\n    width: 33.3333333333%;\n  }\n  .row-cols-xxl-4 > * {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .row-cols-xxl-5 > * {\n    flex: 0 0 auto;\n    width: 20%;\n  }\n  .row-cols-xxl-6 > * {\n    flex: 0 0 auto;\n    width: 16.6666666667%;\n  }\n  .col-xxl-auto {\n    flex: 0 0 auto;\n    width: auto;\n  }\n  .col-xxl-1 {\n    flex: 0 0 auto;\n    width: 8.33333333%;\n  }\n  .col-xxl-2 {\n    flex: 0 0 auto;\n    width: 16.66666667%;\n  }\n  .col-xxl-3 {\n    flex: 0 0 auto;\n    width: 25%;\n  }\n  .col-xxl-4 {\n    flex: 0 0 auto;\n    width: 33.33333333%;\n  }\n  .col-xxl-5 {\n    flex: 0 0 auto;\n    width: 41.66666667%;\n  }\n  .col-xxl-6 {\n    flex: 0 0 auto;\n    width: 50%;\n  }\n  .col-xxl-7 {\n    flex: 0 0 auto;\n    width: 58.33333333%;\n  }\n  .col-xxl-8 {\n    flex: 0 0 auto;\n    width: 66.66666667%;\n  }\n  .col-xxl-9 {\n    flex: 0 0 auto;\n    width: 75%;\n  }\n  .col-xxl-10 {\n    flex: 0 0 auto;\n    width: 83.33333333%;\n  }\n  .col-xxl-11 {\n    flex: 0 0 auto;\n    width: 91.66666667%;\n  }\n  .col-xxl-12 {\n    flex: 0 0 auto;\n    width: 100%;\n  }\n  .offset-xxl-0 {\n    margin-right: 0;\n  }\n  .offset-xxl-1 {\n    margin-right: 8.33333333%;\n  }\n  .offset-xxl-2 {\n    margin-right: 16.66666667%;\n  }\n  .offset-xxl-3 {\n    margin-right: 25%;\n  }\n  .offset-xxl-4 {\n    margin-right: 33.33333333%;\n  }\n  .offset-xxl-5 {\n    margin-right: 41.66666667%;\n  }\n  .offset-xxl-6 {\n    margin-right: 50%;\n  }\n  .offset-xxl-7 {\n    margin-right: 58.33333333%;\n  }\n  .offset-xxl-8 {\n    margin-right: 66.66666667%;\n  }\n  .offset-xxl-9 {\n    margin-right: 75%;\n  }\n  .offset-xxl-10 {\n    margin-right: 83.33333333%;\n  }\n  .offset-xxl-11 {\n    margin-right: 91.66666667%;\n  }\n  .g-xxl-0,\n.gx-xxl-0 {\n    --bs-gutter-x: 0;\n  }\n  .g-xxl-0,\n.gy-xxl-0 {\n    --bs-gutter-y: 0;\n  }\n  .g-xxl-1,\n.gx-xxl-1 {\n    --bs-gutter-x: 0.25rem;\n  }\n  .g-xxl-1,\n.gy-xxl-1 {\n    --bs-gutter-y: 0.25rem;\n  }\n  .g-xxl-2,\n.gx-xxl-2 {\n    --bs-gutter-x: 0.5rem;\n  }\n  .g-xxl-2,\n.gy-xxl-2 {\n    --bs-gutter-y: 0.5rem;\n  }\n  .g-xxl-3,\n.gx-xxl-3 {\n    --bs-gutter-x: 1rem;\n  }\n  .g-xxl-3,\n.gy-xxl-3 {\n    --bs-gutter-y: 1rem;\n  }\n  .g-xxl-4,\n.gx-xxl-4 {\n    --bs-gutter-x: 1.5rem;\n  }\n  .g-xxl-4,\n.gy-xxl-4 {\n    --bs-gutter-y: 1.5rem;\n  }\n  .g-xxl-5,\n.gx-xxl-5 {\n    --bs-gutter-x: 3rem;\n  }\n  .g-xxl-5,\n.gy-xxl-5 {\n    --bs-gutter-y: 3rem;\n  }\n}\n.table {\n  --bs-table-color: var(--bs-body-color);\n  --bs-table-bg: transparent;\n  --bs-table-border-color: var(--bs-border-color);\n  --bs-table-accent-bg: transparent;\n  --bs-table-striped-color: var(--bs-body-color);\n  --bs-table-striped-bg: rgba(0, 0, 0, 0.05);\n  --bs-table-active-color: var(--bs-body-color);\n  --bs-table-active-bg: rgba(0, 0, 0, 0.1);\n  --bs-table-hover-color: var(--bs-body-color);\n  --bs-table-hover-bg: rgba(0, 0, 0, 0.075);\n  width: 100%;\n  margin-bottom: 1rem;\n  color: var(--bs-table-color);\n  vertical-align: top;\n  border-color: var(--bs-table-border-color);\n}\n.table > :not(caption) > * > * {\n  padding: 0.5rem 0.5rem;\n  background-color: var(--bs-table-bg);\n  border-bottom-width: 1px;\n  box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);\n}\n.table > tbody {\n  vertical-align: inherit;\n}\n.table > thead {\n  vertical-align: bottom;\n}\n\n.table-group-divider {\n  border-top: 2px solid currentcolor;\n}\n\n.caption-top {\n  caption-side: top;\n}\n\n.table-sm > :not(caption) > * > * {\n  padding: 0.25rem 0.25rem;\n}\n\n.table-bordered > :not(caption) > * {\n  border-width: 1px 0;\n}\n.table-bordered > :not(caption) > * > * {\n  border-width: 0 1px;\n}\n\n.table-borderless > :not(caption) > * > * {\n  border-bottom-width: 0;\n}\n.table-borderless > :not(:first-child) {\n  border-top-width: 0;\n}\n\n.table-striped > tbody > tr:nth-of-type(odd) > * {\n  --bs-table-accent-bg: var(--bs-table-striped-bg);\n  color: var(--bs-table-striped-color);\n}\n\n.table-striped-columns > :not(caption) > tr > :nth-child(even) {\n  --bs-table-accent-bg: var(--bs-table-striped-bg);\n  color: var(--bs-table-striped-color);\n}\n\n.table-active {\n  --bs-table-accent-bg: var(--bs-table-active-bg);\n  color: var(--bs-table-active-color);\n}\n\n.table-hover > tbody > tr:hover > * {\n  --bs-table-accent-bg: var(--bs-table-hover-bg);\n  color: var(--bs-table-hover-color);\n}\n\n.table-primary {\n  --bs-table-color: #000;\n  --bs-table-bg: #cfe2ff;\n  --bs-table-border-color: #bacbe6;\n  --bs-table-striped-bg: #c5d7f2;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #bacbe6;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #bfd1ec;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-secondary {\n  --bs-table-color: #000;\n  --bs-table-bg: #e2e3e5;\n  --bs-table-border-color: #cbccce;\n  --bs-table-striped-bg: #d7d8da;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #cbccce;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #d1d2d4;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-success {\n  --bs-table-color: #000;\n  --bs-table-bg: #d1e7dd;\n  --bs-table-border-color: #bcd0c7;\n  --bs-table-striped-bg: #c7dbd2;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #bcd0c7;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #c1d6cc;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-info {\n  --bs-table-color: #000;\n  --bs-table-bg: #cff4fc;\n  --bs-table-border-color: #badce3;\n  --bs-table-striped-bg: #c5e8ef;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #badce3;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #bfe2e9;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-warning {\n  --bs-table-color: #000;\n  --bs-table-bg: #fff3cd;\n  --bs-table-border-color: #e6dbb9;\n  --bs-table-striped-bg: #f2e7c3;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #e6dbb9;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #ece1be;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-danger {\n  --bs-table-color: #000;\n  --bs-table-bg: #f8d7da;\n  --bs-table-border-color: #dfc2c4;\n  --bs-table-striped-bg: #eccccf;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #dfc2c4;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #e5c7ca;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-light {\n  --bs-table-color: #000;\n  --bs-table-bg: #f8f9fa;\n  --bs-table-border-color: #dfe0e1;\n  --bs-table-striped-bg: #ecedee;\n  --bs-table-striped-color: #000;\n  --bs-table-active-bg: #dfe0e1;\n  --bs-table-active-color: #000;\n  --bs-table-hover-bg: #e5e6e7;\n  --bs-table-hover-color: #000;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-dark {\n  --bs-table-color: #fff;\n  --bs-table-bg: #212529;\n  --bs-table-border-color: #373b3e;\n  --bs-table-striped-bg: #2c3034;\n  --bs-table-striped-color: #fff;\n  --bs-table-active-bg: #373b3e;\n  --bs-table-active-color: #fff;\n  --bs-table-hover-bg: #323539;\n  --bs-table-hover-color: #fff;\n  color: var(--bs-table-color);\n  border-color: var(--bs-table-border-color);\n}\n\n.table-responsive {\n  overflow-x: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n@media (max-width: 575.98px) {\n  .table-responsive-sm {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 767.98px) {\n  .table-responsive-md {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 991.98px) {\n  .table-responsive-lg {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 1199.98px) {\n  .table-responsive-xl {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n@media (max-width: 1399.98px) {\n  .table-responsive-xxl {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}\n.form-label {\n  margin-bottom: 0.5rem;\n}\n\n.col-form-label {\n  padding-top: calc(0.375rem + 1px);\n  padding-bottom: calc(0.375rem + 1px);\n  margin-bottom: 0;\n  font-size: inherit;\n  line-height: 1.5;\n}\n\n.col-form-label-lg {\n  padding-top: calc(0.5rem + 1px);\n  padding-bottom: calc(0.5rem + 1px);\n  font-size: 1.25rem;\n}\n\n.col-form-label-sm {\n  padding-top: calc(0.25rem + 1px);\n  padding-bottom: calc(0.25rem + 1px);\n  font-size: 0.875rem;\n}\n\n.form-text {\n  margin-top: 0.25rem;\n  font-size: 0.875em;\n  color: #6c757d;\n}\n\n.form-control {\n  display: block;\n  width: 100%;\n  padding: 0.375rem 0.75rem;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5;\n  color: #212529;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 1px solid #ced4da;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  border-radius: 0.375rem;\n  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-control {\n    transition: none;\n  }\n}\n.form-control[type=file] {\n  overflow: hidden;\n}\n.form-control[type=file]:not(:disabled):not([readonly]) {\n  cursor: pointer;\n}\n.form-control:focus {\n  color: #212529;\n  background-color: #fff;\n  border-color: #86b7fe;\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-control::-webkit-date-and-time-value {\n  height: 1.5em;\n}\n.form-control::-moz-placeholder {\n  color: #6c757d;\n  opacity: 1;\n}\n.form-control::placeholder {\n  color: #6c757d;\n  opacity: 1;\n}\n.form-control:disabled {\n  background-color: #e9ecef;\n  opacity: 1;\n}\n.form-control::-webkit-file-upload-button {\n  padding: 0.375rem 0.75rem;\n  margin: -0.375rem -0.75rem;\n  -webkit-margin-end: 0.75rem;\n  margin-inline-end: 0.75rem;\n  color: #212529;\n  background-color: #e9ecef;\n  pointer-events: none;\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n  border-inline-end-width: 1px;\n  border-radius: 0;\n  -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n.form-control::file-selector-button {\n  padding: 0.375rem 0.75rem;\n  margin: -0.375rem -0.75rem;\n  -webkit-margin-end: 0.75rem;\n  margin-inline-end: 0.75rem;\n  color: #212529;\n  background-color: #e9ecef;\n  pointer-events: none;\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n  border-inline-end-width: 1px;\n  border-radius: 0;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-control::-webkit-file-upload-button {\n    -webkit-transition: none;\n    transition: none;\n  }\n  .form-control::file-selector-button {\n    transition: none;\n  }\n}\n.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button {\n  background-color: #dde0e3;\n}\n.form-control:hover:not(:disabled):not([readonly])::file-selector-button {\n  background-color: #dde0e3;\n}\n\n.form-control-plaintext {\n  display: block;\n  width: 100%;\n  padding: 0.375rem 0;\n  margin-bottom: 0;\n  line-height: 1.5;\n  color: #212529;\n  background-color: transparent;\n  border: solid transparent;\n  border-width: 1px 0;\n}\n.form-control-plaintext:focus {\n  outline: 0;\n}\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n  padding-left: 0;\n  padding-right: 0;\n}\n\n.form-control-sm {\n  min-height: calc(1.5em + 0.5rem + 2px);\n  padding: 0.25rem 0.5rem;\n  font-size: 0.875rem;\n  border-radius: 0.25rem;\n}\n.form-control-sm::-webkit-file-upload-button {\n  padding: 0.25rem 0.5rem;\n  margin: -0.25rem -0.5rem;\n  -webkit-margin-end: 0.5rem;\n  margin-inline-end: 0.5rem;\n}\n.form-control-sm::file-selector-button {\n  padding: 0.25rem 0.5rem;\n  margin: -0.25rem -0.5rem;\n  -webkit-margin-end: 0.5rem;\n  margin-inline-end: 0.5rem;\n}\n\n.form-control-lg {\n  min-height: calc(1.5em + 1rem + 2px);\n  padding: 0.5rem 1rem;\n  font-size: 1.25rem;\n  border-radius: 0.5rem;\n}\n.form-control-lg::-webkit-file-upload-button {\n  padding: 0.5rem 1rem;\n  margin: -0.5rem -1rem;\n  -webkit-margin-end: 1rem;\n  margin-inline-end: 1rem;\n}\n.form-control-lg::file-selector-button {\n  padding: 0.5rem 1rem;\n  margin: -0.5rem -1rem;\n  -webkit-margin-end: 1rem;\n  margin-inline-end: 1rem;\n}\n\ntextarea.form-control {\n  min-height: calc(1.5em + 0.75rem + 2px);\n}\ntextarea.form-control-sm {\n  min-height: calc(1.5em + 0.5rem + 2px);\n}\ntextarea.form-control-lg {\n  min-height: calc(1.5em + 1rem + 2px);\n}\n\n.form-control-color {\n  width: 3rem;\n  height: calc(1.5em + 0.75rem + 2px);\n  padding: 0.375rem;\n}\n.form-control-color:not(:disabled):not([readonly]) {\n  cursor: pointer;\n}\n.form-control-color::-moz-color-swatch {\n  border: 0 !important;\n  border-radius: 0.375rem;\n}\n.form-control-color::-webkit-color-swatch {\n  border-radius: 0.375rem;\n}\n.form-control-color.form-control-sm {\n  height: calc(1.5em + 0.5rem + 2px);\n}\n.form-control-color.form-control-lg {\n  height: calc(1.5em + 1rem + 2px);\n}\n\n.form-select {\n  display: block;\n  width: 100%;\n  padding: 0.375rem 0.75rem 0.375rem 2.25rem;\n  -moz-padding-start: calc(0.75rem - 3px);\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5;\n  color: #212529;\n  background-color: #fff;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\");\n  background-repeat: no-repeat;\n  background-position: left 0.75rem center;\n  background-size: 16px 12px;\n  border: 1px solid #ced4da;\n  border-radius: 0.375rem;\n  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-select {\n    transition: none;\n  }\n}\n.form-select:focus {\n  border-color: #86b7fe;\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-select[multiple], .form-select[size]:not([size=\"1\"]) {\n  padding-left: 0.75rem;\n  background-image: none;\n}\n.form-select:disabled {\n  background-color: #e9ecef;\n}\n.form-select:-moz-focusring {\n  color: transparent;\n  text-shadow: 0 0 0 #212529;\n}\n\n.form-select-sm {\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  padding-right: 0.5rem;\n  font-size: 0.875rem;\n  border-radius: 0.25rem;\n}\n\n.form-select-lg {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  padding-right: 1rem;\n  font-size: 1.25rem;\n  border-radius: 0.5rem;\n}\n\n.form-check {\n  display: block;\n  min-height: 1.5rem;\n  padding-right: 1.5em;\n  margin-bottom: 0.125rem;\n}\n.form-check .form-check-input {\n  float: right;\n  margin-right: -1.5em;\n}\n\n.form-check-reverse {\n  padding-left: 1.5em;\n  padding-right: 0;\n  text-align: left;\n}\n.form-check-reverse .form-check-input {\n  float: left;\n  margin-left: -1.5em;\n  margin-right: 0;\n}\n\n.form-check-input {\n  width: 1em;\n  height: 1em;\n  margin-top: 0.25em;\n  vertical-align: top;\n  background-color: #fff;\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: contain;\n  border: 1px solid rgba(0, 0, 0, 0.25);\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  -webkit-print-color-adjust: exact;\n  color-adjust: exact;\n  print-color-adjust: exact;\n}\n.form-check-input[type=checkbox] {\n  border-radius: 0.25em;\n}\n.form-check-input[type=radio] {\n  border-radius: 50%;\n}\n.form-check-input:active {\n  filter: brightness(90%);\n}\n.form-check-input:focus {\n  border-color: #86b7fe;\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-check-input:checked {\n  background-color: #0d6efd;\n  border-color: #0d6efd;\n}\n.form-check-input:checked[type=checkbox] {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e\");\n}\n.form-check-input:checked[type=radio] {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e\");\n}\n.form-check-input[type=checkbox]:indeterminate {\n  background-color: #0d6efd;\n  border-color: #0d6efd;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e\");\n}\n.form-check-input:disabled {\n  pointer-events: none;\n  filter: none;\n  opacity: 0.5;\n}\n.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {\n  cursor: default;\n  opacity: 0.5;\n}\n\n.form-switch {\n  padding-right: 2.5em;\n}\n.form-switch .form-check-input {\n  width: 2em;\n  margin-right: -2.5em;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e\");\n  background-position: right center;\n  border-radius: 2em;\n  transition: background-position 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-switch .form-check-input {\n    transition: none;\n  }\n}\n.form-switch .form-check-input:focus {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e\");\n}\n.form-switch .form-check-input:checked {\n  background-position: left center;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n.form-switch.form-check-reverse {\n  padding-left: 2.5em;\n  padding-right: 0;\n}\n.form-switch.form-check-reverse .form-check-input {\n  margin-left: -2.5em;\n  margin-right: 0;\n}\n\n.form-check-inline {\n  display: inline-block;\n  margin-left: 1rem;\n}\n\n.btn-check {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.btn-check[disabled] + .btn, .btn-check:disabled + .btn {\n  pointer-events: none;\n  filter: none;\n  opacity: 0.65;\n}\n\n.form-range {\n  width: 100%;\n  height: 1.5rem;\n  padding: 0;\n  background-color: transparent;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n}\n.form-range:focus {\n  outline: 0;\n}\n.form-range:focus::-webkit-slider-thumb {\n  box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-range:focus::-moz-range-thumb {\n  box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.form-range::-moz-focus-outer {\n  border: 0;\n}\n.form-range::-webkit-slider-thumb {\n  width: 1rem;\n  height: 1rem;\n  margin-top: -0.25rem;\n  background-color: #0d6efd;\n  border: 0;\n  border-radius: 1rem;\n  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  -webkit-appearance: none;\n  appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-range::-webkit-slider-thumb {\n    -webkit-transition: none;\n    transition: none;\n  }\n}\n.form-range::-webkit-slider-thumb:active {\n  background-color: #b6d4fe;\n}\n.form-range::-webkit-slider-runnable-track {\n  width: 100%;\n  height: 0.5rem;\n  color: transparent;\n  cursor: pointer;\n  background-color: #dee2e6;\n  border-color: transparent;\n  border-radius: 1rem;\n}\n.form-range::-moz-range-thumb {\n  width: 1rem;\n  height: 1rem;\n  background-color: #0d6efd;\n  border: 0;\n  border-radius: 1rem;\n  -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n  -moz-appearance: none;\n  appearance: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-range::-moz-range-thumb {\n    -moz-transition: none;\n    transition: none;\n  }\n}\n.form-range::-moz-range-thumb:active {\n  background-color: #b6d4fe;\n}\n.form-range::-moz-range-track {\n  width: 100%;\n  height: 0.5rem;\n  color: transparent;\n  cursor: pointer;\n  background-color: #dee2e6;\n  border-color: transparent;\n  border-radius: 1rem;\n}\n.form-range:disabled {\n  pointer-events: none;\n}\n.form-range:disabled::-webkit-slider-thumb {\n  background-color: #adb5bd;\n}\n.form-range:disabled::-moz-range-thumb {\n  background-color: #adb5bd;\n}\n\n.form-floating {\n  position: relative;\n}\n.form-floating > .form-control,\n.form-floating > .form-control-plaintext,\n.form-floating > .form-select {\n  height: calc(3.5rem + 2px);\n  line-height: 1.25;\n}\n.form-floating > label {\n  position: absolute;\n  top: 0;\n  right: 0;\n  width: 100%;\n  height: 100%;\n  padding: 1rem 0.75rem;\n  overflow: hidden;\n  text-align: start;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  pointer-events: none;\n  border: 1px solid transparent;\n  transform-origin: 100% 0;\n  transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .form-floating > label {\n    transition: none;\n  }\n}\n.form-floating > .form-control,\n.form-floating > .form-control-plaintext {\n  padding: 1rem 0.75rem;\n}\n.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder {\n  color: transparent;\n}\n.form-floating > .form-control::placeholder,\n.form-floating > .form-control-plaintext::placeholder {\n  color: transparent;\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown),\n.form-floating > .form-control-plaintext:focus,\n.form-floating > .form-control-plaintext:not(:placeholder-shown) {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:-webkit-autofill,\n.form-floating > .form-control-plaintext:-webkit-autofill {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-select {\n  padding-top: 1.625rem;\n  padding-bottom: 0.625rem;\n}\n.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label {\n  opacity: 0.65;\n  transform: scale(0.85) translateY(-0.5rem) translateX(-0.15rem);\n}\n.form-floating > .form-control:focus ~ label,\n.form-floating > .form-control:not(:placeholder-shown) ~ label,\n.form-floating > .form-control-plaintext ~ label,\n.form-floating > .form-select ~ label {\n  opacity: 0.65;\n  transform: scale(0.85) translateY(-0.5rem) translateX(-0.15rem);\n}\n.form-floating > .form-control:-webkit-autofill ~ label {\n  opacity: 0.65;\n  transform: scale(0.85) translateY(-0.5rem) translateX(-0.15rem);\n}\n.form-floating > .form-control-plaintext ~ label {\n  border-width: 1px 0;\n}\n\n.input-group {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: stretch;\n  width: 100%;\n}\n.input-group > .form-control,\n.input-group > .form-select,\n.input-group > .form-floating {\n  position: relative;\n  flex: 1 1 auto;\n  width: 1%;\n  min-width: 0;\n}\n.input-group > .form-control:focus,\n.input-group > .form-select:focus,\n.input-group > .form-floating:focus-within {\n  z-index: 5;\n}\n.input-group .btn {\n  position: relative;\n  z-index: 2;\n}\n.input-group .btn:focus {\n  z-index: 5;\n}\n\n.input-group-text {\n  display: flex;\n  align-items: center;\n  padding: 0.375rem 0.75rem;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5;\n  color: #212529;\n  text-align: center;\n  white-space: nowrap;\n  background-color: #e9ecef;\n  border: 1px solid #ced4da;\n  border-radius: 0.375rem;\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .form-select,\n.input-group-lg > .input-group-text,\n.input-group-lg > .btn {\n  padding: 0.5rem 1rem;\n  font-size: 1.25rem;\n  border-radius: 0.5rem;\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .form-select,\n.input-group-sm > .input-group-text,\n.input-group-sm > .btn {\n  padding: 0.25rem 0.5rem;\n  font-size: 0.875rem;\n  border-radius: 0.25rem;\n}\n\n.input-group-lg > .form-select,\n.input-group-sm > .form-select {\n  padding-left: 3rem;\n}\n\n.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3),\n.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control,\n.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4),\n.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-control,\n.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-select {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {\n  margin-right: -1px;\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group > .form-floating:not(:first-child) > .form-control,\n.input-group > .form-floating:not(:first-child) > .form-select {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\n.valid-feedback {\n  display: none;\n  width: 100%;\n  margin-top: 0.25rem;\n  font-size: 0.875em;\n  color: #198754;\n}\n\n.valid-tooltip {\n  position: absolute;\n  top: 100%;\n  z-index: 5;\n  display: none;\n  max-width: 100%;\n  padding: 0.25rem 0.5rem;\n  margin-top: 0.1rem;\n  font-size: 0.875rem;\n  color: #fff;\n  background-color: rgba(25, 135, 84, 0.9);\n  border-radius: 0.375rem;\n}\n\n.was-validated :valid ~ .valid-feedback,\n.was-validated :valid ~ .valid-tooltip,\n.is-valid ~ .valid-feedback,\n.is-valid ~ .valid-tooltip {\n  display: block;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n  border-color: #198754;\n  padding-left: calc(1.5em + 0.75rem);\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n  background-repeat: no-repeat;\n  background-position: left calc(0.375em + 0.1875rem) center;\n  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n  border-color: #198754;\n  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n  padding-left: calc(1.5em + 0.75rem);\n  background-position: top calc(0.375em + 0.1875rem) left calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:valid, .form-select.is-valid {\n  border-color: #198754;\n}\n.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size=\"1\"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size=\"1\"] {\n  padding-left: 4.125rem;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\"), url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n  background-position: left 0.75rem center, center left 2.25rem;\n  background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-select:valid:focus, .form-select.is-valid:focus {\n  border-color: #198754;\n  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n\n.was-validated .form-control-color:valid, .form-control-color.is-valid {\n  width: calc(3rem + calc(1.5em + 0.75rem));\n}\n\n.was-validated .form-check-input:valid, .form-check-input.is-valid {\n  border-color: #198754;\n}\n.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked {\n  background-color: #198754;\n}\n.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus {\n  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n  color: #198754;\n}\n\n.form-check-inline .form-check-input ~ .valid-feedback {\n  margin-right: 0.5em;\n}\n\n.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid,\n.was-validated .input-group > .form-select:not(:focus):valid,\n.input-group > .form-select:not(:focus).is-valid,\n.was-validated .input-group > .form-floating:not(:focus-within):valid,\n.input-group > .form-floating:not(:focus-within).is-valid {\n  z-index: 3;\n}\n\n.invalid-feedback {\n  display: none;\n  width: 100%;\n  margin-top: 0.25rem;\n  font-size: 0.875em;\n  color: #dc3545;\n}\n\n.invalid-tooltip {\n  position: absolute;\n  top: 100%;\n  z-index: 5;\n  display: none;\n  max-width: 100%;\n  padding: 0.25rem 0.5rem;\n  margin-top: 0.1rem;\n  font-size: 0.875rem;\n  color: #fff;\n  background-color: rgba(220, 53, 69, 0.9);\n  border-radius: 0.375rem;\n}\n\n.was-validated :invalid ~ .invalid-feedback,\n.was-validated :invalid ~ .invalid-tooltip,\n.is-invalid ~ .invalid-feedback,\n.is-invalid ~ .invalid-tooltip {\n  display: block;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n  border-color: #dc3545;\n  padding-left: calc(1.5em + 0.75rem);\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n  background-repeat: no-repeat;\n  background-position: left calc(0.375em + 0.1875rem) center;\n  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n  border-color: #dc3545;\n  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n  padding-left: calc(1.5em + 0.75rem);\n  background-position: top calc(0.375em + 0.1875rem) left calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:invalid, .form-select.is-invalid {\n  border-color: #dc3545;\n}\n.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size=\"1\"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size=\"1\"] {\n  padding-left: 4.125rem;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e\"), url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n  background-position: left 0.75rem center, center left 2.25rem;\n  background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {\n  border-color: #dc3545;\n  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control-color:invalid, .form-control-color.is-invalid {\n  width: calc(3rem + calc(1.5em + 0.75rem));\n}\n\n.was-validated .form-check-input:invalid, .form-check-input.is-invalid {\n  border-color: #dc3545;\n}\n.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked {\n  background-color: #dc3545;\n}\n.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus {\n  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n  color: #dc3545;\n}\n\n.form-check-inline .form-check-input ~ .invalid-feedback {\n  margin-right: 0.5em;\n}\n\n.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid,\n.was-validated .input-group > .form-select:not(:focus):invalid,\n.input-group > .form-select:not(:focus).is-invalid,\n.was-validated .input-group > .form-floating:not(:focus-within):invalid,\n.input-group > .form-floating:not(:focus-within).is-invalid {\n  z-index: 4;\n}\n\n.btn {\n  --bs-btn-padding-x: 0.75rem;\n  --bs-btn-padding-y: 0.375rem;\n  --bs-btn-font-family: ;\n  --bs-btn-font-size: 1rem;\n  --bs-btn-font-weight: 400;\n  --bs-btn-line-height: 1.5;\n  --bs-btn-color: #212529;\n  --bs-btn-bg: transparent;\n  --bs-btn-border-width: 1px;\n  --bs-btn-border-color: transparent;\n  --bs-btn-border-radius: 0.375rem;\n  --bs-btn-hover-border-color: transparent;\n  --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n  --bs-btn-disabled-opacity: 0.65;\n  --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);\n  display: inline-block;\n  padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);\n  font-family: var(--bs-btn-font-family);\n  font-size: var(--bs-btn-font-size);\n  font-weight: var(--bs-btn-font-weight);\n  line-height: var(--bs-btn-line-height);\n  color: var(--bs-btn-color);\n  text-align: center;\n  text-decoration: none;\n  vertical-align: middle;\n  cursor: pointer;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);\n  border-radius: var(--bs-btn-border-radius);\n  background-color: var(--bs-btn-bg);\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .btn {\n    transition: none;\n  }\n}\n.btn:hover {\n  color: var(--bs-btn-hover-color);\n  background-color: var(--bs-btn-hover-bg);\n  border-color: var(--bs-btn-hover-border-color);\n}\n.btn-check + .btn:hover {\n  color: var(--bs-btn-color);\n  background-color: var(--bs-btn-bg);\n  border-color: var(--bs-btn-border-color);\n}\n.btn:focus-visible {\n  color: var(--bs-btn-hover-color);\n  background-color: var(--bs-btn-hover-bg);\n  border-color: var(--bs-btn-hover-border-color);\n  outline: 0;\n  box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn-check:focus-visible + .btn {\n  border-color: var(--bs-btn-hover-border-color);\n  outline: 0;\n  box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show {\n  color: var(--bs-btn-active-color);\n  background-color: var(--bs-btn-active-bg);\n  border-color: var(--bs-btn-active-border-color);\n}\n.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {\n  box-shadow: var(--bs-btn-focus-box-shadow);\n}\n.btn:disabled, .btn.disabled, fieldset:disabled .btn {\n  color: var(--bs-btn-disabled-color);\n  pointer-events: none;\n  background-color: var(--bs-btn-disabled-bg);\n  border-color: var(--bs-btn-disabled-border-color);\n  opacity: var(--bs-btn-disabled-opacity);\n}\n\n.btn-primary {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #0d6efd;\n  --bs-btn-border-color: #0d6efd;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #0b5ed7;\n  --bs-btn-hover-border-color: #0a58ca;\n  --bs-btn-focus-shadow-rgb: 49, 132, 253;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #0a58ca;\n  --bs-btn-active-border-color: #0a53be;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #0d6efd;\n  --bs-btn-disabled-border-color: #0d6efd;\n}\n\n.btn-secondary {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #6c757d;\n  --bs-btn-border-color: #6c757d;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #5c636a;\n  --bs-btn-hover-border-color: #565e64;\n  --bs-btn-focus-shadow-rgb: 130, 138, 145;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #565e64;\n  --bs-btn-active-border-color: #51585e;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #6c757d;\n  --bs-btn-disabled-border-color: #6c757d;\n}\n\n.btn-success {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #198754;\n  --bs-btn-border-color: #198754;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #157347;\n  --bs-btn-hover-border-color: #146c43;\n  --bs-btn-focus-shadow-rgb: 60, 153, 110;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #146c43;\n  --bs-btn-active-border-color: #13653f;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #198754;\n  --bs-btn-disabled-border-color: #198754;\n}\n\n.btn-info {\n  --bs-btn-color: #000;\n  --bs-btn-bg: #0dcaf0;\n  --bs-btn-border-color: #0dcaf0;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #31d2f2;\n  --bs-btn-hover-border-color: #25cff2;\n  --bs-btn-focus-shadow-rgb: 11, 172, 204;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #3dd5f3;\n  --bs-btn-active-border-color: #25cff2;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #000;\n  --bs-btn-disabled-bg: #0dcaf0;\n  --bs-btn-disabled-border-color: #0dcaf0;\n}\n\n.btn-warning {\n  --bs-btn-color: #000;\n  --bs-btn-bg: #ffc107;\n  --bs-btn-border-color: #ffc107;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #ffca2c;\n  --bs-btn-hover-border-color: #ffc720;\n  --bs-btn-focus-shadow-rgb: 217, 164, 6;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #ffcd39;\n  --bs-btn-active-border-color: #ffc720;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #000;\n  --bs-btn-disabled-bg: #ffc107;\n  --bs-btn-disabled-border-color: #ffc107;\n}\n\n.btn-danger {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #dc3545;\n  --bs-btn-border-color: #dc3545;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #bb2d3b;\n  --bs-btn-hover-border-color: #b02a37;\n  --bs-btn-focus-shadow-rgb: 225, 83, 97;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #b02a37;\n  --bs-btn-active-border-color: #a52834;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #dc3545;\n  --bs-btn-disabled-border-color: #dc3545;\n}\n\n.btn-light {\n  --bs-btn-color: #000;\n  --bs-btn-bg: #f8f9fa;\n  --bs-btn-border-color: #f8f9fa;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #d3d4d5;\n  --bs-btn-hover-border-color: #c6c7c8;\n  --bs-btn-focus-shadow-rgb: 211, 212, 213;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #c6c7c8;\n  --bs-btn-active-border-color: #babbbc;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #000;\n  --bs-btn-disabled-bg: #f8f9fa;\n  --bs-btn-disabled-border-color: #f8f9fa;\n}\n\n.btn-dark {\n  --bs-btn-color: #fff;\n  --bs-btn-bg: #212529;\n  --bs-btn-border-color: #212529;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #424649;\n  --bs-btn-hover-border-color: #373b3e;\n  --bs-btn-focus-shadow-rgb: 66, 70, 73;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #4d5154;\n  --bs-btn-active-border-color: #373b3e;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #fff;\n  --bs-btn-disabled-bg: #212529;\n  --bs-btn-disabled-border-color: #212529;\n}\n\n.btn-outline-primary {\n  --bs-btn-color: #0d6efd;\n  --bs-btn-border-color: #0d6efd;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #0d6efd;\n  --bs-btn-hover-border-color: #0d6efd;\n  --bs-btn-focus-shadow-rgb: 13, 110, 253;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #0d6efd;\n  --bs-btn-active-border-color: #0d6efd;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #0d6efd;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #0d6efd;\n  --bs-gradient: none;\n}\n\n.btn-outline-secondary {\n  --bs-btn-color: #6c757d;\n  --bs-btn-border-color: #6c757d;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #6c757d;\n  --bs-btn-hover-border-color: #6c757d;\n  --bs-btn-focus-shadow-rgb: 108, 117, 125;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #6c757d;\n  --bs-btn-active-border-color: #6c757d;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #6c757d;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #6c757d;\n  --bs-gradient: none;\n}\n\n.btn-outline-success {\n  --bs-btn-color: #198754;\n  --bs-btn-border-color: #198754;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #198754;\n  --bs-btn-hover-border-color: #198754;\n  --bs-btn-focus-shadow-rgb: 25, 135, 84;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #198754;\n  --bs-btn-active-border-color: #198754;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #198754;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #198754;\n  --bs-gradient: none;\n}\n\n.btn-outline-info {\n  --bs-btn-color: #0dcaf0;\n  --bs-btn-border-color: #0dcaf0;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #0dcaf0;\n  --bs-btn-hover-border-color: #0dcaf0;\n  --bs-btn-focus-shadow-rgb: 13, 202, 240;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #0dcaf0;\n  --bs-btn-active-border-color: #0dcaf0;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #0dcaf0;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #0dcaf0;\n  --bs-gradient: none;\n}\n\n.btn-outline-warning {\n  --bs-btn-color: #ffc107;\n  --bs-btn-border-color: #ffc107;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #ffc107;\n  --bs-btn-hover-border-color: #ffc107;\n  --bs-btn-focus-shadow-rgb: 255, 193, 7;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #ffc107;\n  --bs-btn-active-border-color: #ffc107;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #ffc107;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #ffc107;\n  --bs-gradient: none;\n}\n\n.btn-outline-danger {\n  --bs-btn-color: #dc3545;\n  --bs-btn-border-color: #dc3545;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #dc3545;\n  --bs-btn-hover-border-color: #dc3545;\n  --bs-btn-focus-shadow-rgb: 220, 53, 69;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #dc3545;\n  --bs-btn-active-border-color: #dc3545;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #dc3545;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #dc3545;\n  --bs-gradient: none;\n}\n\n.btn-outline-light {\n  --bs-btn-color: #f8f9fa;\n  --bs-btn-border-color: #f8f9fa;\n  --bs-btn-hover-color: #000;\n  --bs-btn-hover-bg: #f8f9fa;\n  --bs-btn-hover-border-color: #f8f9fa;\n  --bs-btn-focus-shadow-rgb: 248, 249, 250;\n  --bs-btn-active-color: #000;\n  --bs-btn-active-bg: #f8f9fa;\n  --bs-btn-active-border-color: #f8f9fa;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #f8f9fa;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #f8f9fa;\n  --bs-gradient: none;\n}\n\n.btn-outline-dark {\n  --bs-btn-color: #212529;\n  --bs-btn-border-color: #212529;\n  --bs-btn-hover-color: #fff;\n  --bs-btn-hover-bg: #212529;\n  --bs-btn-hover-border-color: #212529;\n  --bs-btn-focus-shadow-rgb: 33, 37, 41;\n  --bs-btn-active-color: #fff;\n  --bs-btn-active-bg: #212529;\n  --bs-btn-active-border-color: #212529;\n  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  --bs-btn-disabled-color: #212529;\n  --bs-btn-disabled-bg: transparent;\n  --bs-btn-disabled-border-color: #212529;\n  --bs-gradient: none;\n}\n\n.btn-link {\n  --bs-btn-font-weight: 400;\n  --bs-btn-color: var(--bs-link-color);\n  --bs-btn-bg: transparent;\n  --bs-btn-border-color: transparent;\n  --bs-btn-hover-color: var(--bs-link-hover-color);\n  --bs-btn-hover-border-color: transparent;\n  --bs-btn-active-color: var(--bs-link-hover-color);\n  --bs-btn-active-border-color: transparent;\n  --bs-btn-disabled-color: #6c757d;\n  --bs-btn-disabled-border-color: transparent;\n  --bs-btn-box-shadow: none;\n  --bs-btn-focus-shadow-rgb: 49, 132, 253;\n  text-decoration: underline;\n}\n.btn-link:focus-visible {\n  color: var(--bs-btn-color);\n}\n.btn-link:hover {\n  color: var(--bs-btn-hover-color);\n}\n\n.btn-lg, .btn-group-lg > .btn {\n  --bs-btn-padding-y: 0.5rem;\n  --bs-btn-padding-x: 1rem;\n  --bs-btn-font-size: 1.25rem;\n  --bs-btn-border-radius: 0.5rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n  --bs-btn-padding-y: 0.25rem;\n  --bs-btn-padding-x: 0.5rem;\n  --bs-btn-font-size: 0.875rem;\n  --bs-btn-border-radius: 0.25rem;\n}\n\n.fade {\n  transition: opacity 0.15s linear;\n}\n@media (prefers-reduced-motion: reduce) {\n  .fade {\n    transition: none;\n  }\n}\n.fade:not(.show) {\n  opacity: 0;\n}\n\n.collapse:not(.show) {\n  display: none;\n}\n\n.collapsing {\n  height: 0;\n  overflow: hidden;\n  transition: height 0.35s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .collapsing {\n    transition: none;\n  }\n}\n.collapsing.collapse-horizontal {\n  width: 0;\n  height: auto;\n  transition: width 0.35s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .collapsing.collapse-horizontal {\n    transition: none;\n  }\n}\n\n.dropup,\n.dropend,\n.dropdown,\n.dropstart,\n.dropup-center,\n.dropdown-center {\n  position: relative;\n}\n\n.dropdown-toggle {\n  white-space: nowrap;\n}\n.dropdown-toggle::after {\n  display: inline-block;\n  margin-right: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0.3em solid;\n  border-left: 0.3em solid transparent;\n  border-bottom: 0;\n  border-right: 0.3em solid transparent;\n}\n.dropdown-toggle:empty::after {\n  margin-right: 0;\n}\n\n.dropdown-menu {\n  --bs-dropdown-zindex: 1000;\n  --bs-dropdown-min-width: 10rem;\n  --bs-dropdown-padding-x: 0;\n  --bs-dropdown-padding-y: 0.5rem;\n  --bs-dropdown-spacer: 0.125rem;\n  --bs-dropdown-font-size: 1rem;\n  --bs-dropdown-color: #212529;\n  --bs-dropdown-bg: #fff;\n  --bs-dropdown-border-color: var(--bs-border-color-translucent);\n  --bs-dropdown-border-radius: 0.375rem;\n  --bs-dropdown-border-width: 1px;\n  --bs-dropdown-inner-border-radius: calc(0.375rem - 1px);\n  --bs-dropdown-divider-bg: var(--bs-border-color-translucent);\n  --bs-dropdown-divider-margin-y: 0.5rem;\n  --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  --bs-dropdown-link-color: #212529;\n  --bs-dropdown-link-hover-color: #1e2125;\n  --bs-dropdown-link-hover-bg: #e9ecef;\n  --bs-dropdown-link-active-color: #fff;\n  --bs-dropdown-link-active-bg: #0d6efd;\n  --bs-dropdown-link-disabled-color: #adb5bd;\n  --bs-dropdown-item-padding-x: 1rem;\n  --bs-dropdown-item-padding-y: 0.25rem;\n  --bs-dropdown-header-color: #6c757d;\n  --bs-dropdown-header-padding-x: 1rem;\n  --bs-dropdown-header-padding-y: 0.5rem;\n  position: absolute;\n  z-index: var(--bs-dropdown-zindex);\n  display: none;\n  min-width: var(--bs-dropdown-min-width);\n  padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);\n  margin: 0;\n  font-size: var(--bs-dropdown-font-size);\n  color: var(--bs-dropdown-color);\n  text-align: right;\n  list-style: none;\n  background-color: var(--bs-dropdown-bg);\n  background-clip: padding-box;\n  border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);\n  border-radius: var(--bs-dropdown-border-radius);\n}\n.dropdown-menu[data-bs-popper] {\n  top: 100%;\n  right: 0;\n  margin-top: var(--bs-dropdown-spacer);\n}\n\n.dropdown-menu-start {\n  --bs-position: start;\n}\n.dropdown-menu-start[data-bs-popper] {\n  left: auto;\n  right: 0;\n}\n\n.dropdown-menu-end {\n  --bs-position: end;\n}\n.dropdown-menu-end[data-bs-popper] {\n  left: 0;\n  right: auto;\n}\n\n@media (min-width: 576px) {\n  .dropdown-menu-sm-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-sm-start[data-bs-popper] {\n    left: auto;\n    right: 0;\n  }\n  .dropdown-menu-sm-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-sm-end[data-bs-popper] {\n    left: 0;\n    right: auto;\n  }\n}\n@media (min-width: 768px) {\n  .dropdown-menu-md-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-md-start[data-bs-popper] {\n    left: auto;\n    right: 0;\n  }\n  .dropdown-menu-md-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-md-end[data-bs-popper] {\n    left: 0;\n    right: auto;\n  }\n}\n@media (min-width: 992px) {\n  .dropdown-menu-lg-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-lg-start[data-bs-popper] {\n    left: auto;\n    right: 0;\n  }\n  .dropdown-menu-lg-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-lg-end[data-bs-popper] {\n    left: 0;\n    right: auto;\n  }\n}\n@media (min-width: 1200px) {\n  .dropdown-menu-xl-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-xl-start[data-bs-popper] {\n    left: auto;\n    right: 0;\n  }\n  .dropdown-menu-xl-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-xl-end[data-bs-popper] {\n    left: 0;\n    right: auto;\n  }\n}\n@media (min-width: 1400px) {\n  .dropdown-menu-xxl-start {\n    --bs-position: start;\n  }\n  .dropdown-menu-xxl-start[data-bs-popper] {\n    left: auto;\n    right: 0;\n  }\n  .dropdown-menu-xxl-end {\n    --bs-position: end;\n  }\n  .dropdown-menu-xxl-end[data-bs-popper] {\n    left: 0;\n    right: auto;\n  }\n}\n.dropup .dropdown-menu[data-bs-popper] {\n  top: auto;\n  bottom: 100%;\n  margin-top: 0;\n  margin-bottom: var(--bs-dropdown-spacer);\n}\n.dropup .dropdown-toggle::after {\n  display: inline-block;\n  margin-right: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0;\n  border-left: 0.3em solid transparent;\n  border-bottom: 0.3em solid;\n  border-right: 0.3em solid transparent;\n}\n.dropup .dropdown-toggle:empty::after {\n  margin-right: 0;\n}\n\n.dropend .dropdown-menu[data-bs-popper] {\n  top: 0;\n  left: auto;\n  right: 100%;\n  margin-top: 0;\n  margin-right: var(--bs-dropdown-spacer);\n}\n.dropend .dropdown-toggle::after {\n  display: inline-block;\n  margin-right: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0.3em solid transparent;\n  border-left: 0;\n  border-bottom: 0.3em solid transparent;\n  border-right: 0.3em solid;\n}\n.dropend .dropdown-toggle:empty::after {\n  margin-right: 0;\n}\n.dropend .dropdown-toggle::after {\n  vertical-align: 0;\n}\n\n.dropstart .dropdown-menu[data-bs-popper] {\n  top: 0;\n  left: 100%;\n  right: auto;\n  margin-top: 0;\n  margin-left: var(--bs-dropdown-spacer);\n}\n.dropstart .dropdown-toggle::after {\n  display: inline-block;\n  margin-right: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n}\n.dropstart .dropdown-toggle::after {\n  display: none;\n}\n.dropstart .dropdown-toggle::before {\n  display: inline-block;\n  margin-left: 0.255em;\n  vertical-align: 0.255em;\n  content: \"\";\n  border-top: 0.3em solid transparent;\n  border-left: 0.3em solid;\n  border-bottom: 0.3em solid transparent;\n}\n.dropstart .dropdown-toggle:empty::after {\n  margin-right: 0;\n}\n.dropstart .dropdown-toggle::before {\n  vertical-align: 0;\n}\n\n.dropdown-divider {\n  height: 0;\n  margin: var(--bs-dropdown-divider-margin-y) 0;\n  overflow: hidden;\n  border-top: 1px solid var(--bs-dropdown-divider-bg);\n  opacity: 1;\n}\n\n.dropdown-item {\n  display: block;\n  width: 100%;\n  padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);\n  clear: both;\n  font-weight: 400;\n  color: var(--bs-dropdown-link-color);\n  text-align: inherit;\n  text-decoration: none;\n  white-space: nowrap;\n  background-color: transparent;\n  border: 0;\n}\n.dropdown-item:hover, .dropdown-item:focus {\n  color: var(--bs-dropdown-link-hover-color);\n  background-color: var(--bs-dropdown-link-hover-bg);\n}\n.dropdown-item.active, .dropdown-item:active {\n  color: var(--bs-dropdown-link-active-color);\n  text-decoration: none;\n  background-color: var(--bs-dropdown-link-active-bg);\n}\n.dropdown-item.disabled, .dropdown-item:disabled {\n  color: var(--bs-dropdown-link-disabled-color);\n  pointer-events: none;\n  background-color: transparent;\n}\n\n.dropdown-menu.show {\n  display: block;\n}\n\n.dropdown-header {\n  display: block;\n  padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);\n  margin-bottom: 0;\n  font-size: 0.875rem;\n  color: var(--bs-dropdown-header-color);\n  white-space: nowrap;\n}\n\n.dropdown-item-text {\n  display: block;\n  padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);\n  color: var(--bs-dropdown-link-color);\n}\n\n.dropdown-menu-dark {\n  --bs-dropdown-color: #dee2e6;\n  --bs-dropdown-bg: #343a40;\n  --bs-dropdown-border-color: var(--bs-border-color-translucent);\n  --bs-dropdown-box-shadow: ;\n  --bs-dropdown-link-color: #dee2e6;\n  --bs-dropdown-link-hover-color: #fff;\n  --bs-dropdown-divider-bg: var(--bs-border-color-translucent);\n  --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);\n  --bs-dropdown-link-active-color: #fff;\n  --bs-dropdown-link-active-bg: #0d6efd;\n  --bs-dropdown-link-disabled-color: #adb5bd;\n  --bs-dropdown-header-color: #adb5bd;\n}\n\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-flex;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  flex: 1 1 auto;\n}\n.btn-group > .btn-check:checked + .btn,\n.btn-group > .btn-check:focus + .btn,\n.btn-group > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn-check:checked + .btn,\n.btn-group-vertical > .btn-check:focus + .btn,\n.btn-group-vertical > .btn:hover,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n  z-index: 1;\n}\n\n.btn-toolbar {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-start;\n}\n.btn-toolbar .input-group {\n  width: auto;\n}\n\n.btn-group {\n  border-radius: 0.375rem;\n}\n.btn-group > :not(.btn-check:first-child) + .btn,\n.btn-group > .btn-group:not(:first-child) {\n  margin-right: -1px;\n}\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn.dropdown-toggle-split:first-child,\n.btn-group > .btn-group:not(:last-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn:nth-child(n+3),\n.btn-group > :not(.btn-check) + .btn,\n.btn-group > .btn-group:not(:first-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\n.dropdown-toggle-split {\n  padding-left: 0.5625rem;\n  padding-right: 0.5625rem;\n}\n.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after {\n  margin-right: 0;\n}\n.dropstart .dropdown-toggle-split::before {\n  margin-left: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n  padding-left: 0.375rem;\n  padding-right: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n  padding-left: 0.75rem;\n  padding-right: 0.75rem;\n}\n\n.btn-group-vertical {\n  flex-direction: column;\n  align-items: flex-start;\n  justify-content: center;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n  width: 100%;\n}\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n  margin-top: -1px;\n}\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group-vertical > .btn ~ .btn,\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n  border-top-right-radius: 0;\n  border-top-left-radius: 0;\n}\n\n.nav {\n  --bs-nav-link-padding-x: 1rem;\n  --bs-nav-link-padding-y: 0.5rem;\n  --bs-nav-link-font-weight: ;\n  --bs-nav-link-color: var(--bs-link-color);\n  --bs-nav-link-hover-color: var(--bs-link-hover-color);\n  --bs-nav-link-disabled-color: #6c757d;\n  display: flex;\n  flex-wrap: wrap;\n  padding-right: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n\n.nav-link {\n  display: block;\n  padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);\n  font-size: var(--bs-nav-link-font-size);\n  font-weight: var(--bs-nav-link-font-weight);\n  color: var(--bs-nav-link-color);\n  text-decoration: none;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .nav-link {\n    transition: none;\n  }\n}\n.nav-link:hover, .nav-link:focus {\n  color: var(--bs-nav-link-hover-color);\n}\n.nav-link.disabled {\n  color: var(--bs-nav-link-disabled-color);\n  pointer-events: none;\n  cursor: default;\n}\n\n.nav-tabs {\n  --bs-nav-tabs-border-width: 1px;\n  --bs-nav-tabs-border-color: #dee2e6;\n  --bs-nav-tabs-border-radius: 0.375rem;\n  --bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #dee2e6;\n  --bs-nav-tabs-link-active-color: #495057;\n  --bs-nav-tabs-link-active-bg: #fff;\n  --bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff;\n  border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color);\n}\n.nav-tabs .nav-link {\n  margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width));\n  background: none;\n  border: var(--bs-nav-tabs-border-width) solid transparent;\n  border-top-right-radius: var(--bs-nav-tabs-border-radius);\n  border-top-left-radius: var(--bs-nav-tabs-border-radius);\n}\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n  isolation: isolate;\n  border-color: var(--bs-nav-tabs-link-hover-border-color);\n}\n.nav-tabs .nav-link.disabled, .nav-tabs .nav-link:disabled {\n  color: var(--bs-nav-link-disabled-color);\n  background-color: transparent;\n  border-color: transparent;\n}\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n  color: var(--bs-nav-tabs-link-active-color);\n  background-color: var(--bs-nav-tabs-link-active-bg);\n  border-color: var(--bs-nav-tabs-link-active-border-color);\n}\n.nav-tabs .dropdown-menu {\n  margin-top: calc(-1 * var(--bs-nav-tabs-border-width));\n  border-top-right-radius: 0;\n  border-top-left-radius: 0;\n}\n\n.nav-pills {\n  --bs-nav-pills-border-radius: 0.375rem;\n  --bs-nav-pills-link-active-color: #fff;\n  --bs-nav-pills-link-active-bg: #0d6efd;\n}\n.nav-pills .nav-link {\n  background: none;\n  border: 0;\n  border-radius: var(--bs-nav-pills-border-radius);\n}\n.nav-pills .nav-link:disabled {\n  color: var(--bs-nav-link-disabled-color);\n  background-color: transparent;\n  border-color: transparent;\n}\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n  color: var(--bs-nav-pills-link-active-color);\n  background-color: var(--bs-nav-pills-link-active-bg);\n}\n\n.nav-fill > .nav-link,\n.nav-fill .nav-item {\n  flex: 1 1 auto;\n  text-align: center;\n}\n\n.nav-justified > .nav-link,\n.nav-justified .nav-item {\n  flex-basis: 0;\n  flex-grow: 1;\n  text-align: center;\n}\n\n.nav-fill .nav-item .nav-link,\n.nav-justified .nav-item .nav-link {\n  width: 100%;\n}\n\n.tab-content > .tab-pane {\n  display: none;\n}\n.tab-content > .active {\n  display: block;\n}\n\n.navbar {\n  --bs-navbar-padding-x: 0;\n  --bs-navbar-padding-y: 0.5rem;\n  --bs-navbar-color: rgba(0, 0, 0, 0.55);\n  --bs-navbar-hover-color: rgba(0, 0, 0, 0.7);\n  --bs-navbar-disabled-color: rgba(0, 0, 0, 0.3);\n  --bs-navbar-active-color: rgba(0, 0, 0, 0.9);\n  --bs-navbar-brand-padding-y: 0.3125rem;\n  --bs-navbar-brand-margin-end: 1rem;\n  --bs-navbar-brand-font-size: 1.25rem;\n  --bs-navbar-brand-color: rgba(0, 0, 0, 0.9);\n  --bs-navbar-brand-hover-color: rgba(0, 0, 0, 0.9);\n  --bs-navbar-nav-link-padding-x: 0.5rem;\n  --bs-navbar-toggler-padding-y: 0.25rem;\n  --bs-navbar-toggler-padding-x: 0.75rem;\n  --bs-navbar-toggler-font-size: 1.25rem;\n  --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n  --bs-navbar-toggler-border-color: rgba(0, 0, 0, 0.1);\n  --bs-navbar-toggler-border-radius: 0.375rem;\n  --bs-navbar-toggler-focus-width: 0.25rem;\n  --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);\n}\n.navbar > .container,\n.navbar > .container-fluid,\n.navbar > .container-sm,\n.navbar > .container-md,\n.navbar > .container-lg,\n.navbar > .container-xl,\n.navbar > .container-xxl {\n  display: flex;\n  flex-wrap: inherit;\n  align-items: center;\n  justify-content: space-between;\n}\n.navbar-brand {\n  padding-top: var(--bs-navbar-brand-padding-y);\n  padding-bottom: var(--bs-navbar-brand-padding-y);\n  margin-left: var(--bs-navbar-brand-margin-end);\n  font-size: var(--bs-navbar-brand-font-size);\n  color: var(--bs-navbar-brand-color);\n  text-decoration: none;\n  white-space: nowrap;\n}\n.navbar-brand:hover, .navbar-brand:focus {\n  color: var(--bs-navbar-brand-hover-color);\n}\n\n.navbar-nav {\n  --bs-nav-link-padding-x: 0;\n  --bs-nav-link-padding-y: 0.5rem;\n  --bs-nav-link-font-weight: ;\n  --bs-nav-link-color: var(--bs-navbar-color);\n  --bs-nav-link-hover-color: var(--bs-navbar-hover-color);\n  --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);\n  display: flex;\n  flex-direction: column;\n  padding-right: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.navbar-nav .show > .nav-link,\n.navbar-nav .nav-link.active {\n  color: var(--bs-navbar-active-color);\n}\n.navbar-nav .dropdown-menu {\n  position: static;\n}\n\n.navbar-text {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: var(--bs-navbar-color);\n}\n.navbar-text a,\n.navbar-text a:hover,\n.navbar-text a:focus {\n  color: var(--bs-navbar-active-color);\n}\n\n.navbar-collapse {\n  flex-basis: 100%;\n  flex-grow: 1;\n  align-items: center;\n}\n\n.navbar-toggler {\n  padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);\n  font-size: var(--bs-navbar-toggler-font-size);\n  line-height: 1;\n  color: var(--bs-navbar-color);\n  background-color: transparent;\n  border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);\n  border-radius: var(--bs-navbar-toggler-border-radius);\n  transition: var(--bs-navbar-toggler-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .navbar-toggler {\n    transition: none;\n  }\n}\n.navbar-toggler:hover {\n  text-decoration: none;\n}\n.navbar-toggler:focus {\n  text-decoration: none;\n  outline: 0;\n  box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width);\n}\n\n.navbar-toggler-icon {\n  display: inline-block;\n  width: 1.5em;\n  height: 1.5em;\n  vertical-align: middle;\n  background-image: var(--bs-navbar-toggler-icon-bg);\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: 100%;\n}\n\n.navbar-nav-scroll {\n  max-height: var(--bs-scroll-height, 75vh);\n  overflow-y: auto;\n}\n\n@media (min-width: 576px) {\n  .navbar-expand-sm {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-sm .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-sm .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-sm .navbar-nav .nav-link {\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-sm .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-sm .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-sm .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-sm .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-sm .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-sm .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-expand-md {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-md .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-md .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-md .navbar-nav .nav-link {\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-md .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-md .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-md .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-md .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-md .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-md .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 992px) {\n  .navbar-expand-lg {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-lg .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-lg .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-lg .navbar-nav .nav-link {\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-lg .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-lg .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-lg .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-lg .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-lg .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-lg .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 1200px) {\n  .navbar-expand-xl {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-xl .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-xl .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-xl .navbar-nav .nav-link {\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-xl .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-xl .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-xl .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-xl .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-xl .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-xl .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n@media (min-width: 1400px) {\n  .navbar-expand-xxl {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n  }\n  .navbar-expand-xxl .navbar-nav {\n    flex-direction: row;\n  }\n  .navbar-expand-xxl .navbar-nav .dropdown-menu {\n    position: absolute;\n  }\n  .navbar-expand-xxl .navbar-nav .nav-link {\n    padding-left: var(--bs-navbar-nav-link-padding-x);\n    padding-right: var(--bs-navbar-nav-link-padding-x);\n  }\n  .navbar-expand-xxl .navbar-nav-scroll {\n    overflow: visible;\n  }\n  .navbar-expand-xxl .navbar-collapse {\n    display: flex !important;\n    flex-basis: auto;\n  }\n  .navbar-expand-xxl .navbar-toggler {\n    display: none;\n  }\n  .navbar-expand-xxl .offcanvas {\n    position: static;\n    z-index: auto;\n    flex-grow: 1;\n    width: auto !important;\n    height: auto !important;\n    visibility: visible !important;\n    background-color: transparent !important;\n    border: 0 !important;\n    transform: none !important;\n    transition: none;\n  }\n  .navbar-expand-xxl .offcanvas .offcanvas-header {\n    display: none;\n  }\n  .navbar-expand-xxl .offcanvas .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n  }\n}\n.navbar-expand {\n  flex-wrap: nowrap;\n  justify-content: flex-start;\n}\n.navbar-expand .navbar-nav {\n  flex-direction: row;\n}\n.navbar-expand .navbar-nav .dropdown-menu {\n  position: absolute;\n}\n.navbar-expand .navbar-nav .nav-link {\n  padding-left: var(--bs-navbar-nav-link-padding-x);\n  padding-right: var(--bs-navbar-nav-link-padding-x);\n}\n.navbar-expand .navbar-nav-scroll {\n  overflow: visible;\n}\n.navbar-expand .navbar-collapse {\n  display: flex !important;\n  flex-basis: auto;\n}\n.navbar-expand .navbar-toggler {\n  display: none;\n}\n.navbar-expand .offcanvas {\n  position: static;\n  z-index: auto;\n  flex-grow: 1;\n  width: auto !important;\n  height: auto !important;\n  visibility: visible !important;\n  background-color: transparent !important;\n  border: 0 !important;\n  transform: none !important;\n  transition: none;\n}\n.navbar-expand .offcanvas .offcanvas-header {\n  display: none;\n}\n.navbar-expand .offcanvas .offcanvas-body {\n  display: flex;\n  flex-grow: 0;\n  padding: 0;\n  overflow-y: visible;\n}\n\n.navbar-dark {\n  --bs-navbar-color: rgba(255, 255, 255, 0.55);\n  --bs-navbar-hover-color: rgba(255, 255, 255, 0.75);\n  --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25);\n  --bs-navbar-active-color: #fff;\n  --bs-navbar-brand-color: #fff;\n  --bs-navbar-brand-hover-color: #fff;\n  --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1);\n  --bs-navbar-toggler-icon-bg: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.card {\n  --bs-card-spacer-y: 1rem;\n  --bs-card-spacer-x: 1rem;\n  --bs-card-title-spacer-y: 0.5rem;\n  --bs-card-border-width: 1px;\n  --bs-card-border-color: var(--bs-border-color-translucent);\n  --bs-card-border-radius: 0.375rem;\n  --bs-card-box-shadow: ;\n  --bs-card-inner-border-radius: calc(0.375rem - 1px);\n  --bs-card-cap-padding-y: 0.5rem;\n  --bs-card-cap-padding-x: 1rem;\n  --bs-card-cap-bg: rgba(0, 0, 0, 0.03);\n  --bs-card-cap-color: ;\n  --bs-card-height: ;\n  --bs-card-color: ;\n  --bs-card-bg: #fff;\n  --bs-card-img-overlay-padding: 1rem;\n  --bs-card-group-margin: 0.75rem;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n  height: var(--bs-card-height);\n  word-wrap: break-word;\n  background-color: var(--bs-card-bg);\n  background-clip: border-box;\n  border: var(--bs-card-border-width) solid var(--bs-card-border-color);\n  border-radius: var(--bs-card-border-radius);\n}\n.card > hr {\n  margin-left: 0;\n  margin-right: 0;\n}\n.card > .list-group {\n  border-top: inherit;\n  border-bottom: inherit;\n}\n.card > .list-group:first-child {\n  border-top-width: 0;\n  border-top-right-radius: var(--bs-card-inner-border-radius);\n  border-top-left-radius: var(--bs-card-inner-border-radius);\n}\n.card > .list-group:last-child {\n  border-bottom-width: 0;\n  border-bottom-left-radius: var(--bs-card-inner-border-radius);\n  border-bottom-right-radius: var(--bs-card-inner-border-radius);\n}\n.card > .card-header + .list-group,\n.card > .list-group + .card-footer {\n  border-top: 0;\n}\n\n.card-body {\n  flex: 1 1 auto;\n  padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);\n  color: var(--bs-card-color);\n}\n\n.card-title {\n  margin-bottom: var(--bs-card-title-spacer-y);\n}\n\n.card-subtitle {\n  margin-top: calc(-0.5 * var(--bs-card-title-spacer-y));\n  margin-bottom: 0;\n}\n\n.card-text:last-child {\n  margin-bottom: 0;\n}\n\n.card-link + .card-link {\n  margin-right: var(--bs-card-spacer-x);\n}\n\n.card-header {\n  padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);\n  margin-bottom: 0;\n  color: var(--bs-card-cap-color);\n  background-color: var(--bs-card-cap-bg);\n  border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n}\n.card-header:first-child {\n  border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0;\n}\n\n.card-footer {\n  padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);\n  color: var(--bs-card-cap-color);\n  background-color: var(--bs-card-cap-bg);\n  border-top: var(--bs-card-border-width) solid var(--bs-card-border-color);\n}\n.card-footer:last-child {\n  border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);\n}\n\n.card-header-tabs {\n  margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));\n  margin-bottom: calc(-1 * var(--bs-card-cap-padding-y));\n  margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));\n  border-bottom: 0;\n}\n.card-header-tabs .nav-link.active {\n  background-color: var(--bs-card-bg);\n  border-bottom-color: var(--bs-card-bg);\n}\n\n.card-header-pills {\n  margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));\n  margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));\n}\n\n.card-img-overlay {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  padding: var(--bs-card-img-overlay-padding);\n  border-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n  width: 100%;\n}\n\n.card-img,\n.card-img-top {\n  border-top-right-radius: var(--bs-card-inner-border-radius);\n  border-top-left-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-img,\n.card-img-bottom {\n  border-bottom-left-radius: var(--bs-card-inner-border-radius);\n  border-bottom-right-radius: var(--bs-card-inner-border-radius);\n}\n\n.card-group > .card {\n  margin-bottom: var(--bs-card-group-margin);\n}\n@media (min-width: 576px) {\n  .card-group {\n    display: flex;\n    flex-flow: row wrap;\n  }\n  .card-group > .card {\n    flex: 1 0 0%;\n    margin-bottom: 0;\n  }\n  .card-group > .card + .card {\n    margin-right: 0;\n    border-right: 0;\n  }\n  .card-group > .card:not(:last-child) {\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n  .card-group > .card:not(:last-child) .card-img-top,\n.card-group > .card:not(:last-child) .card-header {\n    border-top-left-radius: 0;\n  }\n  .card-group > .card:not(:last-child) .card-img-bottom,\n.card-group > .card:not(:last-child) .card-footer {\n    border-bottom-left-radius: 0;\n  }\n  .card-group > .card:not(:first-child) {\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .card-group > .card:not(:first-child) .card-img-top,\n.card-group > .card:not(:first-child) .card-header {\n    border-top-right-radius: 0;\n  }\n  .card-group > .card:not(:first-child) .card-img-bottom,\n.card-group > .card:not(:first-child) .card-footer {\n    border-bottom-right-radius: 0;\n  }\n}\n\n.accordion {\n  --bs-accordion-color: #212529;\n  --bs-accordion-bg: #fff;\n  --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;\n  --bs-accordion-border-color: var(--bs-border-color);\n  --bs-accordion-border-width: 1px;\n  --bs-accordion-border-radius: 0.375rem;\n  --bs-accordion-inner-border-radius: calc(0.375rem - 1px);\n  --bs-accordion-btn-padding-x: 1.25rem;\n  --bs-accordion-btn-padding-y: 1rem;\n  --bs-accordion-btn-color: #212529;\n  --bs-accordion-btn-bg: var(--bs-accordion-bg);\n  --bs-accordion-btn-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n  --bs-accordion-btn-icon-width: 1.25rem;\n  --bs-accordion-btn-icon-transform: rotate(-180deg);\n  --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;\n  --bs-accordion-btn-active-icon: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n  --bs-accordion-btn-focus-border-color: #86b7fe;\n  --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n  --bs-accordion-body-padding-x: 1.25rem;\n  --bs-accordion-body-padding-y: 1rem;\n  --bs-accordion-active-color: #0c63e4;\n  --bs-accordion-active-bg: #e7f1ff;\n}\n\n.accordion-button {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);\n  font-size: 1rem;\n  color: var(--bs-accordion-btn-color);\n  text-align: right;\n  background-color: var(--bs-accordion-btn-bg);\n  border: 0;\n  border-radius: 0;\n  overflow-anchor: none;\n  transition: var(--bs-accordion-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .accordion-button {\n    transition: none;\n  }\n}\n.accordion-button:not(.collapsed) {\n  color: var(--bs-accordion-active-color);\n  background-color: var(--bs-accordion-active-bg);\n  box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color);\n}\n.accordion-button:not(.collapsed)::after {\n  background-image: var(--bs-accordion-btn-active-icon);\n  transform: var(--bs-accordion-btn-icon-transform);\n}\n.accordion-button::after {\n  flex-shrink: 0;\n  width: var(--bs-accordion-btn-icon-width);\n  height: var(--bs-accordion-btn-icon-width);\n  margin-right: auto;\n  content: \"\";\n  background-image: var(--bs-accordion-btn-icon);\n  background-repeat: no-repeat;\n  background-size: var(--bs-accordion-btn-icon-width);\n  transition: var(--bs-accordion-btn-icon-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .accordion-button::after {\n    transition: none;\n  }\n}\n.accordion-button:hover {\n  z-index: 2;\n}\n.accordion-button:focus {\n  z-index: 3;\n  border-color: var(--bs-accordion-btn-focus-border-color);\n  outline: 0;\n  box-shadow: var(--bs-accordion-btn-focus-box-shadow);\n}\n\n.accordion-header {\n  margin-bottom: 0;\n}\n\n.accordion-item {\n  color: var(--bs-accordion-color);\n  background-color: var(--bs-accordion-bg);\n  border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color);\n}\n.accordion-item:first-of-type {\n  border-top-right-radius: var(--bs-accordion-border-radius);\n  border-top-left-radius: var(--bs-accordion-border-radius);\n}\n.accordion-item:first-of-type .accordion-button {\n  border-top-right-radius: var(--bs-accordion-inner-border-radius);\n  border-top-left-radius: var(--bs-accordion-inner-border-radius);\n}\n.accordion-item:not(:first-of-type) {\n  border-top: 0;\n}\n.accordion-item:last-of-type {\n  border-bottom-left-radius: var(--bs-accordion-border-radius);\n  border-bottom-right-radius: var(--bs-accordion-border-radius);\n}\n.accordion-item:last-of-type .accordion-button.collapsed {\n  border-bottom-left-radius: var(--bs-accordion-inner-border-radius);\n  border-bottom-right-radius: var(--bs-accordion-inner-border-radius);\n}\n.accordion-item:last-of-type .accordion-collapse {\n  border-bottom-left-radius: var(--bs-accordion-border-radius);\n  border-bottom-right-radius: var(--bs-accordion-border-radius);\n}\n\n.accordion-body {\n  padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);\n}\n\n.accordion-flush .accordion-collapse {\n  border-width: 0;\n}\n.accordion-flush .accordion-item {\n  border-left: 0;\n  border-right: 0;\n  border-radius: 0;\n}\n.accordion-flush .accordion-item:first-child {\n  border-top: 0;\n}\n.accordion-flush .accordion-item:last-child {\n  border-bottom: 0;\n}\n.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {\n  border-radius: 0;\n}\n\n.breadcrumb {\n  --bs-breadcrumb-padding-x: 0;\n  --bs-breadcrumb-padding-y: 0;\n  --bs-breadcrumb-margin-bottom: 1rem;\n  --bs-breadcrumb-bg: ;\n  --bs-breadcrumb-border-radius: ;\n  --bs-breadcrumb-divider-color: #6c757d;\n  --bs-breadcrumb-item-padding-x: 0.5rem;\n  --bs-breadcrumb-item-active-color: #6c757d;\n  display: flex;\n  flex-wrap: wrap;\n  padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);\n  margin-bottom: var(--bs-breadcrumb-margin-bottom);\n  font-size: var(--bs-breadcrumb-font-size);\n  list-style: none;\n  background-color: var(--bs-breadcrumb-bg);\n  border-radius: var(--bs-breadcrumb-border-radius);\n}\n\n.breadcrumb-item + .breadcrumb-item {\n  padding-right: var(--bs-breadcrumb-item-padding-x);\n}\n.breadcrumb-item + .breadcrumb-item::before {\n  float: right;\n  padding-left: var(--bs-breadcrumb-item-padding-x);\n  color: var(--bs-breadcrumb-divider-color);\n  content:  var(--bs-breadcrumb-divider, \"/\") ;\n}\n.breadcrumb-item.active {\n  color: var(--bs-breadcrumb-item-active-color);\n}\n\n.pagination {\n  --bs-pagination-padding-x: 0.75rem;\n  --bs-pagination-padding-y: 0.375rem;\n  --bs-pagination-font-size: 1rem;\n  --bs-pagination-color: var(--bs-link-color);\n  --bs-pagination-bg: #fff;\n  --bs-pagination-border-width: 1px;\n  --bs-pagination-border-color: #dee2e6;\n  --bs-pagination-border-radius: 0.375rem;\n  --bs-pagination-hover-color: var(--bs-link-hover-color);\n  --bs-pagination-hover-bg: #e9ecef;\n  --bs-pagination-hover-border-color: #dee2e6;\n  --bs-pagination-focus-color: var(--bs-link-hover-color);\n  --bs-pagination-focus-bg: #e9ecef;\n  --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n  --bs-pagination-active-color: #fff;\n  --bs-pagination-active-bg: #0d6efd;\n  --bs-pagination-active-border-color: #0d6efd;\n  --bs-pagination-disabled-color: #6c757d;\n  --bs-pagination-disabled-bg: #fff;\n  --bs-pagination-disabled-border-color: #dee2e6;\n  display: flex;\n  padding-right: 0;\n  list-style: none;\n}\n\n.page-link {\n  position: relative;\n  display: block;\n  padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);\n  font-size: var(--bs-pagination-font-size);\n  color: var(--bs-pagination-color);\n  text-decoration: none;\n  background-color: var(--bs-pagination-bg);\n  border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .page-link {\n    transition: none;\n  }\n}\n.page-link:hover {\n  z-index: 2;\n  color: var(--bs-pagination-hover-color);\n  background-color: var(--bs-pagination-hover-bg);\n  border-color: var(--bs-pagination-hover-border-color);\n}\n.page-link:focus {\n  z-index: 3;\n  color: var(--bs-pagination-focus-color);\n  background-color: var(--bs-pagination-focus-bg);\n  outline: 0;\n  box-shadow: var(--bs-pagination-focus-box-shadow);\n}\n.page-link.active, .active > .page-link {\n  z-index: 3;\n  color: var(--bs-pagination-active-color);\n  background-color: var(--bs-pagination-active-bg);\n  border-color: var(--bs-pagination-active-border-color);\n}\n.page-link.disabled, .disabled > .page-link {\n  color: var(--bs-pagination-disabled-color);\n  pointer-events: none;\n  background-color: var(--bs-pagination-disabled-bg);\n  border-color: var(--bs-pagination-disabled-border-color);\n}\n\n.page-item:not(:first-child) .page-link {\n  margin-right: -1px;\n}\n.page-item:first-child .page-link {\n  border-top-right-radius: var(--bs-pagination-border-radius);\n  border-bottom-right-radius: var(--bs-pagination-border-radius);\n}\n.page-item:last-child .page-link {\n  border-top-left-radius: var(--bs-pagination-border-radius);\n  border-bottom-left-radius: var(--bs-pagination-border-radius);\n}\n\n.pagination-lg {\n  --bs-pagination-padding-x: 1.5rem;\n  --bs-pagination-padding-y: 0.75rem;\n  --bs-pagination-font-size: 1.25rem;\n  --bs-pagination-border-radius: 0.5rem;\n}\n\n.pagination-sm {\n  --bs-pagination-padding-x: 0.5rem;\n  --bs-pagination-padding-y: 0.25rem;\n  --bs-pagination-font-size: 0.875rem;\n  --bs-pagination-border-radius: 0.25rem;\n}\n\n.badge {\n  --bs-badge-padding-x: 0.65em;\n  --bs-badge-padding-y: 0.35em;\n  --bs-badge-font-size: 0.75em;\n  --bs-badge-font-weight: 700;\n  --bs-badge-color: #fff;\n  --bs-badge-border-radius: 0.375rem;\n  display: inline-block;\n  padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);\n  font-size: var(--bs-badge-font-size);\n  font-weight: var(--bs-badge-font-weight);\n  line-height: 1;\n  color: var(--bs-badge-color);\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: var(--bs-badge-border-radius);\n}\n.badge:empty {\n  display: none;\n}\n\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n\n.alert {\n  --bs-alert-bg: transparent;\n  --bs-alert-padding-x: 1rem;\n  --bs-alert-padding-y: 1rem;\n  --bs-alert-margin-bottom: 1rem;\n  --bs-alert-color: inherit;\n  --bs-alert-border-color: transparent;\n  --bs-alert-border: 1px solid var(--bs-alert-border-color);\n  --bs-alert-border-radius: 0.375rem;\n  position: relative;\n  padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x);\n  margin-bottom: var(--bs-alert-margin-bottom);\n  color: var(--bs-alert-color);\n  background-color: var(--bs-alert-bg);\n  border: var(--bs-alert-border);\n  border-radius: var(--bs-alert-border-radius);\n}\n\n.alert-heading {\n  color: inherit;\n}\n\n.alert-link {\n  font-weight: 700;\n}\n\n.alert-dismissible {\n  padding-left: 3rem;\n}\n.alert-dismissible .btn-close {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 2;\n  padding: 1.25rem 1rem;\n}\n\n.alert-primary {\n  --bs-alert-color: #084298;\n  --bs-alert-bg: #cfe2ff;\n  --bs-alert-border-color: #b6d4fe;\n}\n.alert-primary .alert-link {\n  color: #06357a;\n}\n\n.alert-secondary {\n  --bs-alert-color: #41464b;\n  --bs-alert-bg: #e2e3e5;\n  --bs-alert-border-color: #d3d6d8;\n}\n.alert-secondary .alert-link {\n  color: #34383c;\n}\n\n.alert-success {\n  --bs-alert-color: #0f5132;\n  --bs-alert-bg: #d1e7dd;\n  --bs-alert-border-color: #badbcc;\n}\n.alert-success .alert-link {\n  color: #0c4128;\n}\n\n.alert-info {\n  --bs-alert-color: #055160;\n  --bs-alert-bg: #cff4fc;\n  --bs-alert-border-color: #b6effb;\n}\n.alert-info .alert-link {\n  color: #04414d;\n}\n\n.alert-warning {\n  --bs-alert-color: #664d03;\n  --bs-alert-bg: #fff3cd;\n  --bs-alert-border-color: #ffecb5;\n}\n.alert-warning .alert-link {\n  color: #523e02;\n}\n\n.alert-danger {\n  --bs-alert-color: #842029;\n  --bs-alert-bg: #f8d7da;\n  --bs-alert-border-color: #f5c2c7;\n}\n.alert-danger .alert-link {\n  color: #6a1a21;\n}\n\n.alert-light {\n  --bs-alert-color: #636464;\n  --bs-alert-bg: #fefefe;\n  --bs-alert-border-color: #fdfdfe;\n}\n.alert-light .alert-link {\n  color: #4f5050;\n}\n\n.alert-dark {\n  --bs-alert-color: #141619;\n  --bs-alert-bg: #d3d3d4;\n  --bs-alert-border-color: #bcbebf;\n}\n.alert-dark .alert-link {\n  color: #101214;\n}\n\n@keyframes progress-bar-stripes {\n  0% {\n    background-position-x: 1rem;\n  }\n}\n.progress {\n  --bs-progress-height: 1rem;\n  --bs-progress-font-size: 0.75rem;\n  --bs-progress-bg: #e9ecef;\n  --bs-progress-border-radius: 0.375rem;\n  --bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);\n  --bs-progress-bar-color: #fff;\n  --bs-progress-bar-bg: #0d6efd;\n  --bs-progress-bar-transition: width 0.6s ease;\n  display: flex;\n  height: var(--bs-progress-height);\n  overflow: hidden;\n  font-size: var(--bs-progress-font-size);\n  background-color: var(--bs-progress-bg);\n  border-radius: var(--bs-progress-border-radius);\n}\n\n.progress-bar {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  overflow: hidden;\n  color: var(--bs-progress-bar-color);\n  text-align: center;\n  white-space: nowrap;\n  background-color: var(--bs-progress-bar-bg);\n  transition: var(--bs-progress-bar-transition);\n}\n@media (prefers-reduced-motion: reduce) {\n  .progress-bar {\n    transition: none;\n  }\n}\n\n.progress-bar-striped {\n  background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-size: var(--bs-progress-height) var(--bs-progress-height);\n}\n\n.progress-bar-animated {\n  animation: 1s linear infinite progress-bar-stripes;\n}\n@media (prefers-reduced-motion: reduce) {\n  .progress-bar-animated {\n    animation: none;\n  }\n}\n\n.list-group {\n  --bs-list-group-color: #212529;\n  --bs-list-group-bg: #fff;\n  --bs-list-group-border-color: rgba(0, 0, 0, 0.125);\n  --bs-list-group-border-width: 1px;\n  --bs-list-group-border-radius: 0.375rem;\n  --bs-list-group-item-padding-x: 1rem;\n  --bs-list-group-item-padding-y: 0.5rem;\n  --bs-list-group-action-color: #495057;\n  --bs-list-group-action-hover-color: #495057;\n  --bs-list-group-action-hover-bg: #f8f9fa;\n  --bs-list-group-action-active-color: #212529;\n  --bs-list-group-action-active-bg: #e9ecef;\n  --bs-list-group-disabled-color: #6c757d;\n  --bs-list-group-disabled-bg: #fff;\n  --bs-list-group-active-color: #fff;\n  --bs-list-group-active-bg: #0d6efd;\n  --bs-list-group-active-border-color: #0d6efd;\n  display: flex;\n  flex-direction: column;\n  padding-right: 0;\n  margin-bottom: 0;\n  border-radius: var(--bs-list-group-border-radius);\n}\n\n.list-group-numbered {\n  list-style-type: none;\n  counter-reset: section;\n}\n.list-group-numbered > .list-group-item::before {\n  content: counters(section, \".\") \". \";\n  counter-increment: section;\n}\n\n.list-group-item-action {\n  width: 100%;\n  color: var(--bs-list-group-action-color);\n  text-align: inherit;\n}\n.list-group-item-action:hover, .list-group-item-action:focus {\n  z-index: 1;\n  color: var(--bs-list-group-action-hover-color);\n  text-decoration: none;\n  background-color: var(--bs-list-group-action-hover-bg);\n}\n.list-group-item-action:active {\n  color: var(--bs-list-group-action-active-color);\n  background-color: var(--bs-list-group-action-active-bg);\n}\n\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);\n  color: var(--bs-list-group-color);\n  text-decoration: none;\n  background-color: var(--bs-list-group-bg);\n  border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);\n}\n.list-group-item:first-child {\n  border-top-right-radius: inherit;\n  border-top-left-radius: inherit;\n}\n.list-group-item:last-child {\n  border-bottom-left-radius: inherit;\n  border-bottom-right-radius: inherit;\n}\n.list-group-item.disabled, .list-group-item:disabled {\n  color: var(--bs-list-group-disabled-color);\n  pointer-events: none;\n  background-color: var(--bs-list-group-disabled-bg);\n}\n.list-group-item.active {\n  z-index: 2;\n  color: var(--bs-list-group-active-color);\n  background-color: var(--bs-list-group-active-bg);\n  border-color: var(--bs-list-group-active-border-color);\n}\n.list-group-item + .list-group-item {\n  border-top-width: 0;\n}\n.list-group-item + .list-group-item.active {\n  margin-top: calc(-1 * var(--bs-list-group-border-width));\n  border-top-width: var(--bs-list-group-border-width);\n}\n\n.list-group-horizontal {\n  flex-direction: row;\n}\n.list-group-horizontal > .list-group-item:first-child:not(:last-child) {\n  border-bottom-right-radius: var(--bs-list-group-border-radius);\n  border-top-left-radius: 0;\n}\n.list-group-horizontal > .list-group-item:last-child:not(:first-child) {\n  border-top-left-radius: var(--bs-list-group-border-radius);\n  border-bottom-right-radius: 0;\n}\n.list-group-horizontal > .list-group-item.active {\n  margin-top: 0;\n}\n.list-group-horizontal > .list-group-item + .list-group-item {\n  border-top-width: var(--bs-list-group-border-width);\n  border-right-width: 0;\n}\n.list-group-horizontal > .list-group-item + .list-group-item.active {\n  margin-right: calc(-1 * var(--bs-list-group-border-width));\n  border-right-width: var(--bs-list-group-border-width);\n}\n\n@media (min-width: 576px) {\n  .list-group-horizontal-sm {\n    flex-direction: row;\n  }\n  .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) {\n    border-bottom-right-radius: var(--bs-list-group-border-radius);\n    border-top-left-radius: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) {\n    border-top-left-radius: var(--bs-list-group-border-radius);\n    border-bottom-right-radius: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-right-width: 0;\n  }\n  .list-group-horizontal-sm > .list-group-item + .list-group-item.active {\n    margin-right: calc(-1 * var(--bs-list-group-border-width));\n    border-right-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 768px) {\n  .list-group-horizontal-md {\n    flex-direction: row;\n  }\n  .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) {\n    border-bottom-right-radius: var(--bs-list-group-border-radius);\n    border-top-left-radius: 0;\n  }\n  .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) {\n    border-top-left-radius: var(--bs-list-group-border-radius);\n    border-bottom-right-radius: 0;\n  }\n  .list-group-horizontal-md > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-md > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-right-width: 0;\n  }\n  .list-group-horizontal-md > .list-group-item + .list-group-item.active {\n    margin-right: calc(-1 * var(--bs-list-group-border-width));\n    border-right-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 992px) {\n  .list-group-horizontal-lg {\n    flex-direction: row;\n  }\n  .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) {\n    border-bottom-right-radius: var(--bs-list-group-border-radius);\n    border-top-left-radius: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) {\n    border-top-left-radius: var(--bs-list-group-border-radius);\n    border-bottom-right-radius: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-right-width: 0;\n  }\n  .list-group-horizontal-lg > .list-group-item + .list-group-item.active {\n    margin-right: calc(-1 * var(--bs-list-group-border-width));\n    border-right-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 1200px) {\n  .list-group-horizontal-xl {\n    flex-direction: row;\n  }\n  .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) {\n    border-bottom-right-radius: var(--bs-list-group-border-radius);\n    border-top-left-radius: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) {\n    border-top-left-radius: var(--bs-list-group-border-radius);\n    border-bottom-right-radius: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-right-width: 0;\n  }\n  .list-group-horizontal-xl > .list-group-item + .list-group-item.active {\n    margin-right: calc(-1 * var(--bs-list-group-border-width));\n    border-right-width: var(--bs-list-group-border-width);\n  }\n}\n@media (min-width: 1400px) {\n  .list-group-horizontal-xxl {\n    flex-direction: row;\n  }\n  .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) {\n    border-bottom-right-radius: var(--bs-list-group-border-radius);\n    border-top-left-radius: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) {\n    border-top-left-radius: var(--bs-list-group-border-radius);\n    border-bottom-right-radius: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item.active {\n    margin-top: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item + .list-group-item {\n    border-top-width: var(--bs-list-group-border-width);\n    border-right-width: 0;\n  }\n  .list-group-horizontal-xxl > .list-group-item + .list-group-item.active {\n    margin-right: calc(-1 * var(--bs-list-group-border-width));\n    border-right-width: var(--bs-list-group-border-width);\n  }\n}\n.list-group-flush {\n  border-radius: 0;\n}\n.list-group-flush > .list-group-item {\n  border-width: 0 0 var(--bs-list-group-border-width);\n}\n.list-group-flush > .list-group-item:last-child {\n  border-bottom-width: 0;\n}\n\n.list-group-item-primary {\n  color: #084298;\n  background-color: #cfe2ff;\n}\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n  color: #084298;\n  background-color: #bacbe6;\n}\n.list-group-item-primary.list-group-item-action.active {\n  color: #fff;\n  background-color: #084298;\n  border-color: #084298;\n}\n\n.list-group-item-secondary {\n  color: #41464b;\n  background-color: #e2e3e5;\n}\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n  color: #41464b;\n  background-color: #cbccce;\n}\n.list-group-item-secondary.list-group-item-action.active {\n  color: #fff;\n  background-color: #41464b;\n  border-color: #41464b;\n}\n\n.list-group-item-success {\n  color: #0f5132;\n  background-color: #d1e7dd;\n}\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n  color: #0f5132;\n  background-color: #bcd0c7;\n}\n.list-group-item-success.list-group-item-action.active {\n  color: #fff;\n  background-color: #0f5132;\n  border-color: #0f5132;\n}\n\n.list-group-item-info {\n  color: #055160;\n  background-color: #cff4fc;\n}\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n  color: #055160;\n  background-color: #badce3;\n}\n.list-group-item-info.list-group-item-action.active {\n  color: #fff;\n  background-color: #055160;\n  border-color: #055160;\n}\n\n.list-group-item-warning {\n  color: #664d03;\n  background-color: #fff3cd;\n}\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n  color: #664d03;\n  background-color: #e6dbb9;\n}\n.list-group-item-warning.list-group-item-action.active {\n  color: #fff;\n  background-color: #664d03;\n  border-color: #664d03;\n}\n\n.list-group-item-danger {\n  color: #842029;\n  background-color: #f8d7da;\n}\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n  color: #842029;\n  background-color: #dfc2c4;\n}\n.list-group-item-danger.list-group-item-action.active {\n  color: #fff;\n  background-color: #842029;\n  border-color: #842029;\n}\n\n.list-group-item-light {\n  color: #636464;\n  background-color: #fefefe;\n}\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n  color: #636464;\n  background-color: #e5e5e5;\n}\n.list-group-item-light.list-group-item-action.active {\n  color: #fff;\n  background-color: #636464;\n  border-color: #636464;\n}\n\n.list-group-item-dark {\n  color: #141619;\n  background-color: #d3d3d4;\n}\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n  color: #141619;\n  background-color: #bebebf;\n}\n.list-group-item-dark.list-group-item-action.active {\n  color: #fff;\n  background-color: #141619;\n  border-color: #141619;\n}\n\n.btn-close {\n  box-sizing: content-box;\n  width: 1em;\n  height: 1em;\n  padding: 0.25em 0.25em;\n  color: #000;\n  background: transparent url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e\") center/1em auto no-repeat;\n  border: 0;\n  border-radius: 0.375rem;\n  opacity: 0.5;\n}\n.btn-close:hover {\n  color: #000;\n  text-decoration: none;\n  opacity: 0.75;\n}\n.btn-close:focus {\n  outline: 0;\n  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n  opacity: 1;\n}\n.btn-close:disabled, .btn-close.disabled {\n  pointer-events: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  opacity: 0.25;\n}\n\n.btn-close-white {\n  filter: invert(1) grayscale(100%) brightness(200%);\n}\n\n.toast {\n  --bs-toast-zindex: 1090;\n  --bs-toast-padding-x: 0.75rem;\n  --bs-toast-padding-y: 0.5rem;\n  --bs-toast-spacing: 1.5rem;\n  --bs-toast-max-width: 350px;\n  --bs-toast-font-size: 0.875rem;\n  --bs-toast-color: ;\n  --bs-toast-bg: rgba(255, 255, 255, 0.85);\n  --bs-toast-border-width: 1px;\n  --bs-toast-border-color: var(--bs-border-color-translucent);\n  --bs-toast-border-radius: 0.375rem;\n  --bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  --bs-toast-header-color: #6c757d;\n  --bs-toast-header-bg: rgba(255, 255, 255, 0.85);\n  --bs-toast-header-border-color: rgba(0, 0, 0, 0.05);\n  width: var(--bs-toast-max-width);\n  max-width: 100%;\n  font-size: var(--bs-toast-font-size);\n  color: var(--bs-toast-color);\n  pointer-events: auto;\n  background-color: var(--bs-toast-bg);\n  background-clip: padding-box;\n  border: var(--bs-toast-border-width) solid var(--bs-toast-border-color);\n  box-shadow: var(--bs-toast-box-shadow);\n  border-radius: var(--bs-toast-border-radius);\n}\n.toast.showing {\n  opacity: 0;\n}\n.toast:not(.show) {\n  display: none;\n}\n\n.toast-container {\n  --bs-toast-zindex: 1090;\n  position: absolute;\n  z-index: var(--bs-toast-zindex);\n  width: -webkit-max-content;\n  width: -moz-max-content;\n  width: max-content;\n  max-width: 100%;\n  pointer-events: none;\n}\n.toast-container > :not(:last-child) {\n  margin-bottom: var(--bs-toast-spacing);\n}\n\n.toast-header {\n  display: flex;\n  align-items: center;\n  padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);\n  color: var(--bs-toast-header-color);\n  background-color: var(--bs-toast-header-bg);\n  background-clip: padding-box;\n  border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);\n  border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));\n  border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));\n}\n.toast-header .btn-close {\n  margin-left: calc(-0.5 * var(--bs-toast-padding-x));\n  margin-right: var(--bs-toast-padding-x);\n}\n\n.toast-body {\n  padding: var(--bs-toast-padding-x);\n  word-wrap: break-word;\n}\n\n.modal {\n  --bs-modal-zindex: 1055;\n  --bs-modal-width: 500px;\n  --bs-modal-padding: 1rem;\n  --bs-modal-margin: 0.5rem;\n  --bs-modal-color: ;\n  --bs-modal-bg: #fff;\n  --bs-modal-border-color: var(--bs-border-color-translucent);\n  --bs-modal-border-width: 1px;\n  --bs-modal-border-radius: 0.5rem;\n  --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n  --bs-modal-inner-border-radius: calc(0.5rem - 1px);\n  --bs-modal-header-padding-x: 1rem;\n  --bs-modal-header-padding-y: 1rem;\n  --bs-modal-header-padding: 1rem 1rem;\n  --bs-modal-header-border-color: var(--bs-border-color);\n  --bs-modal-header-border-width: 1px;\n  --bs-modal-title-line-height: 1.5;\n  --bs-modal-footer-gap: 0.5rem;\n  --bs-modal-footer-bg: ;\n  --bs-modal-footer-border-color: var(--bs-border-color);\n  --bs-modal-footer-border-width: 1px;\n  position: fixed;\n  top: 0;\n  right: 0;\n  z-index: var(--bs-modal-zindex);\n  display: none;\n  width: 100%;\n  height: 100%;\n  overflow-x: hidden;\n  overflow-y: auto;\n  outline: 0;\n}\n\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: var(--bs-modal-margin);\n  pointer-events: none;\n}\n.modal.fade .modal-dialog {\n  transition: transform 0.3s ease-out;\n  transform: translate(0, -50px);\n}\n@media (prefers-reduced-motion: reduce) {\n  .modal.fade .modal-dialog {\n    transition: none;\n  }\n}\n.modal.show .modal-dialog {\n  transform: none;\n}\n.modal.modal-static .modal-dialog {\n  transform: scale(1.02);\n}\n\n.modal-dialog-scrollable {\n  height: calc(100% - var(--bs-modal-margin) * 2);\n}\n.modal-dialog-scrollable .modal-content {\n  max-height: 100%;\n  overflow: hidden;\n}\n.modal-dialog-scrollable .modal-body {\n  overflow-y: auto;\n}\n\n.modal-dialog-centered {\n  display: flex;\n  align-items: center;\n  min-height: calc(100% - var(--bs-modal-margin) * 2);\n}\n\n.modal-content {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  color: var(--bs-modal-color);\n  pointer-events: auto;\n  background-color: var(--bs-modal-bg);\n  background-clip: padding-box;\n  border: var(--bs-modal-border-width) solid var(--bs-modal-border-color);\n  border-radius: var(--bs-modal-border-radius);\n  outline: 0;\n}\n\n.modal-backdrop {\n  --bs-backdrop-zindex: 1050;\n  --bs-backdrop-bg: #000;\n  --bs-backdrop-opacity: 0.5;\n  position: fixed;\n  top: 0;\n  right: 0;\n  z-index: var(--bs-backdrop-zindex);\n  width: 100vw;\n  height: 100vh;\n  background-color: var(--bs-backdrop-bg);\n}\n.modal-backdrop.fade {\n  opacity: 0;\n}\n.modal-backdrop.show {\n  opacity: var(--bs-backdrop-opacity);\n}\n\n.modal-header {\n  display: flex;\n  flex-shrink: 0;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--bs-modal-header-padding);\n  border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);\n  border-top-right-radius: var(--bs-modal-inner-border-radius);\n  border-top-left-radius: var(--bs-modal-inner-border-radius);\n}\n.modal-header .btn-close {\n  padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5);\n  margin: calc(-0.5 * var(--bs-modal-header-padding-y)) auto calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x));\n}\n\n.modal-title {\n  margin-bottom: 0;\n  line-height: var(--bs-modal-title-line-height);\n}\n\n.modal-body {\n  position: relative;\n  flex: 1 1 auto;\n  padding: var(--bs-modal-padding);\n}\n\n.modal-footer {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: flex-end;\n  padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5);\n  background-color: var(--bs-modal-footer-bg);\n  border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);\n  border-bottom-left-radius: var(--bs-modal-inner-border-radius);\n  border-bottom-right-radius: var(--bs-modal-inner-border-radius);\n}\n.modal-footer > * {\n  margin: calc(var(--bs-modal-footer-gap) * 0.5);\n}\n\n@media (min-width: 576px) {\n  .modal {\n    --bs-modal-margin: 1.75rem;\n    --bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  }\n  .modal-dialog {\n    max-width: var(--bs-modal-width);\n    margin-left: auto;\n    margin-right: auto;\n  }\n  .modal-sm {\n    --bs-modal-width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg,\n.modal-xl {\n    --bs-modal-width: 800px;\n  }\n}\n@media (min-width: 1200px) {\n  .modal-xl {\n    --bs-modal-width: 1140px;\n  }\n}\n.modal-fullscreen {\n  width: 100vw;\n  max-width: none;\n  height: 100%;\n  margin: 0;\n}\n.modal-fullscreen .modal-content {\n  height: 100%;\n  border: 0;\n  border-radius: 0;\n}\n.modal-fullscreen .modal-header,\n.modal-fullscreen .modal-footer {\n  border-radius: 0;\n}\n.modal-fullscreen .modal-body {\n  overflow-y: auto;\n}\n\n@media (max-width: 575.98px) {\n  .modal-fullscreen-sm-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-sm-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-sm-down .modal-header,\n.modal-fullscreen-sm-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-sm-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 767.98px) {\n  .modal-fullscreen-md-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-md-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-md-down .modal-header,\n.modal-fullscreen-md-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-md-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 991.98px) {\n  .modal-fullscreen-lg-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-lg-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-lg-down .modal-header,\n.modal-fullscreen-lg-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-lg-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 1199.98px) {\n  .modal-fullscreen-xl-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-xl-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-xl-down .modal-header,\n.modal-fullscreen-xl-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-xl-down .modal-body {\n    overflow-y: auto;\n  }\n}\n@media (max-width: 1399.98px) {\n  .modal-fullscreen-xxl-down {\n    width: 100vw;\n    max-width: none;\n    height: 100%;\n    margin: 0;\n  }\n  .modal-fullscreen-xxl-down .modal-content {\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n  }\n  .modal-fullscreen-xxl-down .modal-header,\n.modal-fullscreen-xxl-down .modal-footer {\n    border-radius: 0;\n  }\n  .modal-fullscreen-xxl-down .modal-body {\n    overflow-y: auto;\n  }\n}\n.tooltip {\n  --bs-tooltip-zindex: 1080;\n  --bs-tooltip-max-width: 200px;\n  --bs-tooltip-padding-x: 0.5rem;\n  --bs-tooltip-padding-y: 0.25rem;\n  --bs-tooltip-margin: ;\n  --bs-tooltip-font-size: 0.875rem;\n  --bs-tooltip-color: #fff;\n  --bs-tooltip-bg: #000;\n  --bs-tooltip-border-radius: 0.375rem;\n  --bs-tooltip-opacity: 0.9;\n  --bs-tooltip-arrow-width: 0.8rem;\n  --bs-tooltip-arrow-height: 0.4rem;\n  z-index: var(--bs-tooltip-zindex);\n  display: block;\n  padding: var(--bs-tooltip-arrow-height);\n  margin: var(--bs-tooltip-margin);\n  font-family: var(--bs-font-sans-serif);\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1.5;\n  text-align: right;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  white-space: normal;\n  word-spacing: normal;\n  line-break: auto;\n  font-size: var(--bs-tooltip-font-size);\n  word-wrap: break-word;\n  opacity: 0;\n}\n.tooltip.show {\n  opacity: var(--bs-tooltip-opacity);\n}\n.tooltip .tooltip-arrow {\n  display: block;\n  width: var(--bs-tooltip-arrow-width);\n  height: var(--bs-tooltip-arrow-height);\n}\n.tooltip .tooltip-arrow::before {\n  position: absolute;\n  content: \"\";\n  border-color: transparent;\n  border-style: solid;\n}\n\n.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow {\n  bottom: 0;\n}\n.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before {\n  top: -1px;\n  border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;\n  border-top-color: var(--bs-tooltip-bg);\n}\n.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow {\n  left: 0;\n  width: var(--bs-tooltip-arrow-height);\n  height: var(--bs-tooltip-arrow-width);\n}\n.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before {\n  right: -1px;\n  border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;\n  border-right-color: var(--bs-tooltip-bg);\n}\n.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow {\n  top: 0;\n}\n.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before {\n  bottom: -1px;\n  border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);\n  border-bottom-color: var(--bs-tooltip-bg);\n}\n.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow {\n  right: 0;\n  width: var(--bs-tooltip-arrow-height);\n  height: var(--bs-tooltip-arrow-width);\n}\n.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before {\n  left: -1px;\n  border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);\n  border-left-color: var(--bs-tooltip-bg);\n}\n.tooltip-inner {\n  max-width: var(--bs-tooltip-max-width);\n  padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);\n  color: var(--bs-tooltip-color);\n  text-align: center;\n  background-color: var(--bs-tooltip-bg);\n  border-radius: var(--bs-tooltip-border-radius);\n}\n\n.popover {\n  --bs-popover-zindex: 1070;\n  --bs-popover-max-width: 276px;\n  --bs-popover-font-size: 0.875rem;\n  --bs-popover-bg: #fff;\n  --bs-popover-border-width: 1px;\n  --bs-popover-border-color: var(--bs-border-color-translucent);\n  --bs-popover-border-radius: 0.5rem;\n  --bs-popover-inner-border-radius: calc(0.5rem - 1px);\n  --bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n  --bs-popover-header-padding-x: 1rem;\n  --bs-popover-header-padding-y: 0.5rem;\n  --bs-popover-header-font-size: 1rem;\n  --bs-popover-header-color: ;\n  --bs-popover-header-bg: #f0f0f0;\n  --bs-popover-body-padding-x: 1rem;\n  --bs-popover-body-padding-y: 1rem;\n  --bs-popover-body-color: #212529;\n  --bs-popover-arrow-width: 1rem;\n  --bs-popover-arrow-height: 0.5rem;\n  --bs-popover-arrow-border: var(--bs-popover-border-color);\n  z-index: var(--bs-popover-zindex);\n  display: block;\n  max-width: var(--bs-popover-max-width);\n  font-family: var(--bs-font-sans-serif);\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1.5;\n  text-align: right;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  white-space: normal;\n  word-spacing: normal;\n  line-break: auto;\n  font-size: var(--bs-popover-font-size);\n  word-wrap: break-word;\n  background-color: var(--bs-popover-bg);\n  background-clip: padding-box;\n  border: var(--bs-popover-border-width) solid var(--bs-popover-border-color);\n  border-radius: var(--bs-popover-border-radius);\n}\n.popover .popover-arrow {\n  display: block;\n  width: var(--bs-popover-arrow-width);\n  height: var(--bs-popover-arrow-height);\n}\n.popover .popover-arrow::before, .popover .popover-arrow::after {\n  position: absolute;\n  display: block;\n  content: \"\";\n  border-color: transparent;\n  border-style: solid;\n  border-width: 0;\n}\n\n.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow {\n  bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n}\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {\n  border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;\n}\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before {\n  bottom: 0;\n  border-top-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {\n  bottom: var(--bs-popover-border-width);\n  border-top-color: var(--bs-popover-bg);\n}\n.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow {\n  left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n  width: var(--bs-popover-arrow-height);\n  height: var(--bs-popover-arrow-width);\n}\n.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {\n  border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;\n}\n.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before {\n  left: 0;\n  border-right-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {\n  left: var(--bs-popover-border-width);\n  border-right-color: var(--bs-popover-bg);\n}\n.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow {\n  top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n}\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {\n  border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);\n}\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before {\n  top: 0;\n  border-bottom-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {\n  top: var(--bs-popover-border-width);\n  border-bottom-color: var(--bs-popover-bg);\n}\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before {\n  position: absolute;\n  top: 0;\n  right: 50%;\n  display: block;\n  width: var(--bs-popover-arrow-width);\n  margin-right: calc(-0.5 * var(--bs-popover-arrow-width));\n  content: \"\";\n  border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg);\n}\n.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow {\n  right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));\n  width: var(--bs-popover-arrow-height);\n  height: var(--bs-popover-arrow-width);\n}\n.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {\n  border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);\n}\n.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before {\n  right: 0;\n  border-left-color: var(--bs-popover-arrow-border);\n}\n.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {\n  right: var(--bs-popover-border-width);\n  border-left-color: var(--bs-popover-bg);\n}\n.popover-header {\n  padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);\n  margin-bottom: 0;\n  font-size: var(--bs-popover-header-font-size);\n  color: var(--bs-popover-header-color);\n  background-color: var(--bs-popover-header-bg);\n  border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color);\n  border-top-right-radius: var(--bs-popover-inner-border-radius);\n  border-top-left-radius: var(--bs-popover-inner-border-radius);\n}\n.popover-header:empty {\n  display: none;\n}\n\n.popover-body {\n  padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);\n  color: var(--bs-popover-body-color);\n}\n\n.carousel {\n  position: relative;\n}\n\n.carousel.pointer-event {\n  touch-action: pan-y;\n}\n\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner::after {\n  display: block;\n  clear: both;\n  content: \"\";\n}\n\n.carousel-item {\n  position: relative;\n  display: none;\n  float: right;\n  width: 100%;\n  margin-left: -100%;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  transition: transform 0.6s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-item {\n    transition: none;\n  }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n  display: block;\n}\n\n.carousel-item-next:not(.carousel-item-start),\n.active.carousel-item-end {\n  transform: translateX(-100%);\n}\n\n.carousel-item-prev:not(.carousel-item-end),\n.active.carousel-item-start {\n  transform: translateX(100%);\n}\n\n.carousel-fade .carousel-item {\n  opacity: 0;\n  transition-property: opacity;\n  transform: none;\n}\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-start,\n.carousel-fade .carousel-item-prev.carousel-item-end {\n  z-index: 1;\n  opacity: 1;\n}\n.carousel-fade .active.carousel-item-start,\n.carousel-fade .active.carousel-item-end {\n  z-index: 0;\n  opacity: 0;\n  transition: opacity 0s 0.6s;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-fade .active.carousel-item-start,\n.carousel-fade .active.carousel-item-end {\n    transition: none;\n  }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  z-index: 1;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 15%;\n  padding: 0;\n  color: #fff;\n  text-align: center;\n  background: none;\n  border: 0;\n  opacity: 0.5;\n  transition: opacity 0.15s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-control-prev,\n.carousel-control-next {\n    transition: none;\n  }\n}\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n  color: #fff;\n  text-decoration: none;\n  outline: 0;\n  opacity: 0.9;\n}\n\n.carousel-control-prev {\n  right: 0;\n}\n\n.carousel-control-next {\n  left: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n  display: inline-block;\n  width: 2rem;\n  height: 2rem;\n  background-repeat: no-repeat;\n  background-position: 50%;\n  background-size: 100% 100%;\n}\n.carousel-control-next-icon {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-prev-icon {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n  position: absolute;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  z-index: 2;\n  display: flex;\n  justify-content: center;\n  padding: 0;\n  margin-left: 15%;\n  margin-bottom: 1rem;\n  margin-right: 15%;\n  list-style: none;\n}\n.carousel-indicators [data-bs-target] {\n  box-sizing: content-box;\n  flex: 0 1 auto;\n  width: 30px;\n  height: 3px;\n  padding: 0;\n  margin-left: 3px;\n  margin-right: 3px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 0;\n  border-top: 10px solid transparent;\n  border-bottom: 10px solid transparent;\n  opacity: 0.5;\n  transition: opacity 0.6s ease;\n}\n@media (prefers-reduced-motion: reduce) {\n  .carousel-indicators [data-bs-target] {\n    transition: none;\n  }\n}\n.carousel-indicators .active {\n  opacity: 1;\n}\n\n.carousel-caption {\n  position: absolute;\n  left: 15%;\n  bottom: 1.25rem;\n  right: 15%;\n  padding-top: 1.25rem;\n  padding-bottom: 1.25rem;\n  color: #fff;\n  text-align: center;\n}\n\n.carousel-dark .carousel-control-next-icon,\n.carousel-dark .carousel-control-prev-icon {\n  filter: invert(1) grayscale(100);\n}\n.carousel-dark .carousel-indicators [data-bs-target] {\n  background-color: #000;\n}\n.carousel-dark .carousel-caption {\n  color: #000;\n}\n\n.spinner-grow,\n.spinner-border {\n  display: inline-block;\n  width: var(--bs-spinner-width);\n  height: var(--bs-spinner-height);\n  vertical-align: var(--bs-spinner-vertical-align);\n  border-radius: 50%;\n  animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);\n}\n\n@keyframes spinner-border {\n  to {\n    transform: rotate(360deg) ;\n  }\n}\n.spinner-border {\n  --bs-spinner-width: 2rem;\n  --bs-spinner-height: 2rem;\n  --bs-spinner-vertical-align: -0.125em;\n  --bs-spinner-border-width: 0.25em;\n  --bs-spinner-animation-speed: 0.75s;\n  --bs-spinner-animation-name: spinner-border;\n  border: var(--bs-spinner-border-width) solid currentcolor;\n  border-left-color: transparent;\n}\n\n.spinner-border-sm {\n  --bs-spinner-width: 1rem;\n  --bs-spinner-height: 1rem;\n  --bs-spinner-border-width: 0.2em;\n}\n\n@keyframes spinner-grow {\n  0% {\n    transform: scale(0);\n  }\n  50% {\n    opacity: 1;\n    transform: none;\n  }\n}\n.spinner-grow {\n  --bs-spinner-width: 2rem;\n  --bs-spinner-height: 2rem;\n  --bs-spinner-vertical-align: -0.125em;\n  --bs-spinner-animation-speed: 0.75s;\n  --bs-spinner-animation-name: spinner-grow;\n  background-color: currentcolor;\n  opacity: 0;\n}\n\n.spinner-grow-sm {\n  --bs-spinner-width: 1rem;\n  --bs-spinner-height: 1rem;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .spinner-border,\n.spinner-grow {\n    --bs-spinner-animation-speed: 1.5s;\n  }\n}\n.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm {\n  --bs-offcanvas-zindex: 1045;\n  --bs-offcanvas-width: 400px;\n  --bs-offcanvas-height: 30vh;\n  --bs-offcanvas-padding-x: 1rem;\n  --bs-offcanvas-padding-y: 1rem;\n  --bs-offcanvas-color: ;\n  --bs-offcanvas-bg: #fff;\n  --bs-offcanvas-border-width: 1px;\n  --bs-offcanvas-border-color: var(--bs-border-color-translucent);\n  --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);\n}\n\n@media (max-width: 575.98px) {\n  .offcanvas-sm {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-sm {\n    transition: none;\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-start {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-end {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-top {\n    top: 0;\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.offcanvas-bottom {\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 575.98px) {\n  .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 576px) {\n  .offcanvas-sm {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-sm .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-sm .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 767.98px) {\n  .offcanvas-md {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-md {\n    transition: none;\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-start {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-end {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-top {\n    top: 0;\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.offcanvas-bottom {\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 767.98px) {\n  .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 768px) {\n  .offcanvas-md {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-md .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-md .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 991.98px) {\n  .offcanvas-lg {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-lg {\n    transition: none;\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-start {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-end {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-top {\n    top: 0;\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.offcanvas-bottom {\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 991.98px) {\n  .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 992px) {\n  .offcanvas-lg {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-lg .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-lg .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 1199.98px) {\n  .offcanvas-xl {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-xl {\n    transition: none;\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-start {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-end {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-top {\n    top: 0;\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.offcanvas-bottom {\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 1199.98px) {\n  .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 1200px) {\n  .offcanvas-xl {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-xl .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-xl .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl {\n    position: fixed;\n    bottom: 0;\n    z-index: var(--bs-offcanvas-zindex);\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    color: var(--bs-offcanvas-color);\n    visibility: hidden;\n    background-color: var(--bs-offcanvas-bg);\n    background-clip: padding-box;\n    outline: 0;\n    transition: transform 0.3s ease-in-out;\n  }\n}\n@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) {\n  .offcanvas-xxl {\n    transition: none;\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-start {\n    top: 0;\n    right: 0;\n    width: var(--bs-offcanvas-width);\n    border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-end {\n    top: 0;\n    left: 0;\n    width: var(--bs-offcanvas-width);\n    border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateX(-100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-top {\n    top: 0;\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(-100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.offcanvas-bottom {\n    left: 0;\n    right: 0;\n    height: var(--bs-offcanvas-height);\n    max-height: 100%;\n    border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n    transform: translateY(100%);\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) {\n    transform: none;\n  }\n}\n@media (max-width: 1399.98px) {\n  .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show {\n    visibility: visible;\n  }\n}\n@media (min-width: 1400px) {\n  .offcanvas-xxl {\n    --bs-offcanvas-height: auto;\n    --bs-offcanvas-border-width: 0;\n    background-color: transparent !important;\n  }\n  .offcanvas-xxl .offcanvas-header {\n    display: none;\n  }\n  .offcanvas-xxl .offcanvas-body {\n    display: flex;\n    flex-grow: 0;\n    padding: 0;\n    overflow-y: visible;\n    background-color: transparent !important;\n  }\n}\n\n.offcanvas {\n  position: fixed;\n  bottom: 0;\n  z-index: var(--bs-offcanvas-zindex);\n  display: flex;\n  flex-direction: column;\n  max-width: 100%;\n  color: var(--bs-offcanvas-color);\n  visibility: hidden;\n  background-color: var(--bs-offcanvas-bg);\n  background-clip: padding-box;\n  outline: 0;\n  transition: transform 0.3s ease-in-out;\n}\n@media (prefers-reduced-motion: reduce) {\n  .offcanvas {\n    transition: none;\n  }\n}\n.offcanvas.offcanvas-start {\n  top: 0;\n  right: 0;\n  width: var(--bs-offcanvas-width);\n  border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateX(100%);\n}\n.offcanvas.offcanvas-end {\n  top: 0;\n  left: 0;\n  width: var(--bs-offcanvas-width);\n  border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateX(-100%);\n}\n.offcanvas.offcanvas-top {\n  top: 0;\n  left: 0;\n  right: 0;\n  height: var(--bs-offcanvas-height);\n  max-height: 100%;\n  border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateY(-100%);\n}\n.offcanvas.offcanvas-bottom {\n  left: 0;\n  right: 0;\n  height: var(--bs-offcanvas-height);\n  max-height: 100%;\n  border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);\n  transform: translateY(100%);\n}\n.offcanvas.showing, .offcanvas.show:not(.hiding) {\n  transform: none;\n}\n.offcanvas.showing, .offcanvas.hiding, .offcanvas.show {\n  visibility: visible;\n}\n\n.offcanvas-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  z-index: 1040;\n  width: 100vw;\n  height: 100vh;\n  background-color: #000;\n}\n.offcanvas-backdrop.fade {\n  opacity: 0;\n}\n.offcanvas-backdrop.show {\n  opacity: 0.5;\n}\n\n.offcanvas-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);\n}\n.offcanvas-header .btn-close {\n  padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);\n  margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));\n  margin-left: calc(-0.5 * var(--bs-offcanvas-padding-x));\n  margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));\n}\n\n.offcanvas-title {\n  margin-bottom: 0;\n  line-height: 1.5;\n}\n\n.offcanvas-body {\n  flex-grow: 1;\n  padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);\n  overflow-y: auto;\n}\n\n.placeholder {\n  display: inline-block;\n  min-height: 1em;\n  vertical-align: middle;\n  cursor: wait;\n  background-color: currentcolor;\n  opacity: 0.5;\n}\n.placeholder.btn::before {\n  display: inline-block;\n  content: \"\";\n}\n\n.placeholder-xs {\n  min-height: 0.6em;\n}\n\n.placeholder-sm {\n  min-height: 0.8em;\n}\n\n.placeholder-lg {\n  min-height: 1.2em;\n}\n\n.placeholder-glow .placeholder {\n  animation: placeholder-glow 2s ease-in-out infinite;\n}\n\n@keyframes placeholder-glow {\n  50% {\n    opacity: 0.2;\n  }\n}\n.placeholder-wave {\n  -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);\n  mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);\n  -webkit-mask-size: 200% 100%;\n  mask-size: 200% 100%;\n  animation: placeholder-wave 2s linear infinite;\n}\n\n@keyframes placeholder-wave {\n  100% {\n    -webkit-mask-position: -200% 0%;\n    mask-position: -200% 0%;\n  }\n}\n.clearfix::after {\n  display: block;\n  clear: both;\n  content: \"\";\n}\n\n.text-bg-primary {\n  color: #fff !important;\n  background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-secondary {\n  color: #fff !important;\n  background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-success {\n  color: #fff !important;\n  background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-info {\n  color: #000 !important;\n  background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-warning {\n  color: #000 !important;\n  background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-danger {\n  color: #fff !important;\n  background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-light {\n  color: #000 !important;\n  background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important;\n}\n\n.text-bg-dark {\n  color: #fff !important;\n  background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important;\n}\n\n.link-primary {\n  color: #0d6efd !important;\n}\n.link-primary:hover, .link-primary:focus {\n  color: #0a58ca !important;\n}\n\n.link-secondary {\n  color: #6c757d !important;\n}\n.link-secondary:hover, .link-secondary:focus {\n  color: #565e64 !important;\n}\n\n.link-success {\n  color: #198754 !important;\n}\n.link-success:hover, .link-success:focus {\n  color: #146c43 !important;\n}\n\n.link-info {\n  color: #0dcaf0 !important;\n}\n.link-info:hover, .link-info:focus {\n  color: #3dd5f3 !important;\n}\n\n.link-warning {\n  color: #ffc107 !important;\n}\n.link-warning:hover, .link-warning:focus {\n  color: #ffcd39 !important;\n}\n\n.link-danger {\n  color: #dc3545 !important;\n}\n.link-danger:hover, .link-danger:focus {\n  color: #b02a37 !important;\n}\n\n.link-light {\n  color: #f8f9fa !important;\n}\n.link-light:hover, .link-light:focus {\n  color: #f9fafb !important;\n}\n\n.link-dark {\n  color: #212529 !important;\n}\n.link-dark:hover, .link-dark:focus {\n  color: #1a1e21 !important;\n}\n\n.ratio {\n  position: relative;\n  width: 100%;\n}\n.ratio::before {\n  display: block;\n  padding-top: var(--bs-aspect-ratio);\n  content: \"\";\n}\n.ratio > * {\n  position: absolute;\n  top: 0;\n  right: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.ratio-1x1 {\n  --bs-aspect-ratio: 100%;\n}\n\n.ratio-4x3 {\n  --bs-aspect-ratio: 75%;\n}\n\n.ratio-16x9 {\n  --bs-aspect-ratio: 56.25%;\n}\n\n.ratio-21x9 {\n  --bs-aspect-ratio: 42.8571428571%;\n}\n\n.fixed-top {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  z-index: 1030;\n}\n\n.fixed-bottom {\n  position: fixed;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  z-index: 1030;\n}\n\n.sticky-top {\n  position: -webkit-sticky;\n  position: sticky;\n  top: 0;\n  z-index: 1020;\n}\n\n.sticky-bottom {\n  position: -webkit-sticky;\n  position: sticky;\n  bottom: 0;\n  z-index: 1020;\n}\n\n@media (min-width: 576px) {\n  .sticky-sm-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-sm-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 768px) {\n  .sticky-md-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-md-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 992px) {\n  .sticky-lg-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-lg-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1200px) {\n  .sticky-xl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n@media (min-width: 1400px) {\n  .sticky-xxl-top {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0;\n    z-index: 1020;\n  }\n  .sticky-xxl-bottom {\n    position: -webkit-sticky;\n    position: sticky;\n    bottom: 0;\n    z-index: 1020;\n  }\n}\n.hstack {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  align-self: stretch;\n}\n\n.vstack {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  align-self: stretch;\n}\n\n.visually-hidden,\n.visually-hidden-focusable:not(:focus):not(:focus-within) {\n  position: absolute !important;\n  width: 1px !important;\n  height: 1px !important;\n  padding: 0 !important;\n  margin: -1px !important;\n  overflow: hidden !important;\n  clip: rect(0, 0, 0, 0) !important;\n  white-space: nowrap !important;\n  border: 0 !important;\n}\n\n.stretched-link::after {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  z-index: 1;\n  content: \"\";\n}\n\n.text-truncate {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.vr {\n  display: inline-block;\n  align-self: stretch;\n  width: 1px;\n  min-height: 1em;\n  background-color: currentcolor;\n  opacity: 0.25;\n}\n\n.align-baseline {\n  vertical-align: baseline !important;\n}\n\n.align-top {\n  vertical-align: top !important;\n}\n\n.align-middle {\n  vertical-align: middle !important;\n}\n\n.align-bottom {\n  vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n  vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n  vertical-align: text-top !important;\n}\n\n.float-start {\n  float: right !important;\n}\n\n.float-end {\n  float: left !important;\n}\n\n.float-none {\n  float: none !important;\n}\n\n.opacity-0 {\n  opacity: 0 !important;\n}\n\n.opacity-25 {\n  opacity: 0.25 !important;\n}\n\n.opacity-50 {\n  opacity: 0.5 !important;\n}\n\n.opacity-75 {\n  opacity: 0.75 !important;\n}\n\n.opacity-100 {\n  opacity: 1 !important;\n}\n\n.overflow-auto {\n  overflow: auto !important;\n}\n\n.overflow-hidden {\n  overflow: hidden !important;\n}\n\n.overflow-visible {\n  overflow: visible !important;\n}\n\n.overflow-scroll {\n  overflow: scroll !important;\n}\n\n.d-inline {\n  display: inline !important;\n}\n\n.d-inline-block {\n  display: inline-block !important;\n}\n\n.d-block {\n  display: block !important;\n}\n\n.d-grid {\n  display: grid !important;\n}\n\n.d-table {\n  display: table !important;\n}\n\n.d-table-row {\n  display: table-row !important;\n}\n\n.d-table-cell {\n  display: table-cell !important;\n}\n\n.d-flex {\n  display: flex !important;\n}\n\n.d-inline-flex {\n  display: inline-flex !important;\n}\n\n.d-none {\n  display: none !important;\n}\n\n.shadow {\n  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-sm {\n  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow-lg {\n  box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n  box-shadow: none !important;\n}\n\n.position-static {\n  position: static !important;\n}\n\n.position-relative {\n  position: relative !important;\n}\n\n.position-absolute {\n  position: absolute !important;\n}\n\n.position-fixed {\n  position: fixed !important;\n}\n\n.position-sticky {\n  position: -webkit-sticky !important;\n  position: sticky !important;\n}\n\n.top-0 {\n  top: 0 !important;\n}\n\n.top-50 {\n  top: 50% !important;\n}\n\n.top-100 {\n  top: 100% !important;\n}\n\n.bottom-0 {\n  bottom: 0 !important;\n}\n\n.bottom-50 {\n  bottom: 50% !important;\n}\n\n.bottom-100 {\n  bottom: 100% !important;\n}\n\n.start-0 {\n  right: 0 !important;\n}\n\n.start-50 {\n  right: 50% !important;\n}\n\n.start-100 {\n  right: 100% !important;\n}\n\n.end-0 {\n  left: 0 !important;\n}\n\n.end-50 {\n  left: 50% !important;\n}\n\n.end-100 {\n  left: 100% !important;\n}\n\n.translate-middle {\n  transform: translate(50%, -50%) !important;\n}\n\n.translate-middle-x {\n  transform: translateX(50%) !important;\n}\n\n.translate-middle-y {\n  transform: translateY(-50%) !important;\n}\n\n.border {\n  border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-0 {\n  border: 0 !important;\n}\n\n.border-top {\n  border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-top-0 {\n  border-top: 0 !important;\n}\n\n.border-end {\n  border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-end-0 {\n  border-left: 0 !important;\n}\n\n.border-bottom {\n  border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-bottom-0 {\n  border-bottom: 0 !important;\n}\n\n.border-start {\n  border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;\n}\n\n.border-start-0 {\n  border-right: 0 !important;\n}\n\n.border-primary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-secondary {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-success {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-info {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-warning {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-danger {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-light {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-dark {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-white {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important;\n}\n\n.border-1 {\n  --bs-border-width: 1px;\n}\n\n.border-2 {\n  --bs-border-width: 2px;\n}\n\n.border-3 {\n  --bs-border-width: 3px;\n}\n\n.border-4 {\n  --bs-border-width: 4px;\n}\n\n.border-5 {\n  --bs-border-width: 5px;\n}\n\n.border-opacity-10 {\n  --bs-border-opacity: 0.1;\n}\n\n.border-opacity-25 {\n  --bs-border-opacity: 0.25;\n}\n\n.border-opacity-50 {\n  --bs-border-opacity: 0.5;\n}\n\n.border-opacity-75 {\n  --bs-border-opacity: 0.75;\n}\n\n.border-opacity-100 {\n  --bs-border-opacity: 1;\n}\n\n.w-25 {\n  width: 25% !important;\n}\n\n.w-50 {\n  width: 50% !important;\n}\n\n.w-75 {\n  width: 75% !important;\n}\n\n.w-100 {\n  width: 100% !important;\n}\n\n.w-auto {\n  width: auto !important;\n}\n\n.mw-100 {\n  max-width: 100% !important;\n}\n\n.vw-100 {\n  width: 100vw !important;\n}\n\n.min-vw-100 {\n  min-width: 100vw !important;\n}\n\n.h-25 {\n  height: 25% !important;\n}\n\n.h-50 {\n  height: 50% !important;\n}\n\n.h-75 {\n  height: 75% !important;\n}\n\n.h-100 {\n  height: 100% !important;\n}\n\n.h-auto {\n  height: auto !important;\n}\n\n.mh-100 {\n  max-height: 100% !important;\n}\n\n.vh-100 {\n  height: 100vh !important;\n}\n\n.min-vh-100 {\n  min-height: 100vh !important;\n}\n\n.flex-fill {\n  flex: 1 1 auto !important;\n}\n\n.flex-row {\n  flex-direction: row !important;\n}\n\n.flex-column {\n  flex-direction: column !important;\n}\n\n.flex-row-reverse {\n  flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n  flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n  flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n  flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n  flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n  flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n  justify-content: flex-start !important;\n}\n\n.justify-content-end {\n  justify-content: flex-end !important;\n}\n\n.justify-content-center {\n  justify-content: center !important;\n}\n\n.justify-content-between {\n  justify-content: space-between !important;\n}\n\n.justify-content-around {\n  justify-content: space-around !important;\n}\n\n.justify-content-evenly {\n  justify-content: space-evenly !important;\n}\n\n.align-items-start {\n  align-items: flex-start !important;\n}\n\n.align-items-end {\n  align-items: flex-end !important;\n}\n\n.align-items-center {\n  align-items: center !important;\n}\n\n.align-items-baseline {\n  align-items: baseline !important;\n}\n\n.align-items-stretch {\n  align-items: stretch !important;\n}\n\n.align-content-start {\n  align-content: flex-start !important;\n}\n\n.align-content-end {\n  align-content: flex-end !important;\n}\n\n.align-content-center {\n  align-content: center !important;\n}\n\n.align-content-between {\n  align-content: space-between !important;\n}\n\n.align-content-around {\n  align-content: space-around !important;\n}\n\n.align-content-stretch {\n  align-content: stretch !important;\n}\n\n.align-self-auto {\n  align-self: auto !important;\n}\n\n.align-self-start {\n  align-self: flex-start !important;\n}\n\n.align-self-end {\n  align-self: flex-end !important;\n}\n\n.align-self-center {\n  align-self: center !important;\n}\n\n.align-self-baseline {\n  align-self: baseline !important;\n}\n\n.align-self-stretch {\n  align-self: stretch !important;\n}\n\n.order-first {\n  order: -1 !important;\n}\n\n.order-0 {\n  order: 0 !important;\n}\n\n.order-1 {\n  order: 1 !important;\n}\n\n.order-2 {\n  order: 2 !important;\n}\n\n.order-3 {\n  order: 3 !important;\n}\n\n.order-4 {\n  order: 4 !important;\n}\n\n.order-5 {\n  order: 5 !important;\n}\n\n.order-last {\n  order: 6 !important;\n}\n\n.m-0 {\n  margin: 0 !important;\n}\n\n.m-1 {\n  margin: 0.25rem !important;\n}\n\n.m-2 {\n  margin: 0.5rem !important;\n}\n\n.m-3 {\n  margin: 1rem !important;\n}\n\n.m-4 {\n  margin: 1.5rem !important;\n}\n\n.m-5 {\n  margin: 3rem !important;\n}\n\n.m-auto {\n  margin: auto !important;\n}\n\n.mx-0 {\n  margin-left: 0 !important;\n  margin-right: 0 !important;\n}\n\n.mx-1 {\n  margin-left: 0.25rem !important;\n  margin-right: 0.25rem !important;\n}\n\n.mx-2 {\n  margin-left: 0.5rem !important;\n  margin-right: 0.5rem !important;\n}\n\n.mx-3 {\n  margin-left: 1rem !important;\n  margin-right: 1rem !important;\n}\n\n.mx-4 {\n  margin-left: 1.5rem !important;\n  margin-right: 1.5rem !important;\n}\n\n.mx-5 {\n  margin-left: 3rem !important;\n  margin-right: 3rem !important;\n}\n\n.mx-auto {\n  margin-left: auto !important;\n  margin-right: auto !important;\n}\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important;\n}\n\n.my-4 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important;\n}\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important;\n}\n\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.mt-1 {\n  margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n  margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n  margin-top: 1rem !important;\n}\n\n.mt-4 {\n  margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n  margin-top: 3rem !important;\n}\n\n.mt-auto {\n  margin-top: auto !important;\n}\n\n.me-0 {\n  margin-left: 0 !important;\n}\n\n.me-1 {\n  margin-left: 0.25rem !important;\n}\n\n.me-2 {\n  margin-left: 0.5rem !important;\n}\n\n.me-3 {\n  margin-left: 1rem !important;\n}\n\n.me-4 {\n  margin-left: 1.5rem !important;\n}\n\n.me-5 {\n  margin-left: 3rem !important;\n}\n\n.me-auto {\n  margin-left: auto !important;\n}\n\n.mb-0 {\n  margin-bottom: 0 !important;\n}\n\n.mb-1 {\n  margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n  margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n  margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n  margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n  margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n  margin-bottom: auto !important;\n}\n\n.ms-0 {\n  margin-right: 0 !important;\n}\n\n.ms-1 {\n  margin-right: 0.25rem !important;\n}\n\n.ms-2 {\n  margin-right: 0.5rem !important;\n}\n\n.ms-3 {\n  margin-right: 1rem !important;\n}\n\n.ms-4 {\n  margin-right: 1.5rem !important;\n}\n\n.ms-5 {\n  margin-right: 3rem !important;\n}\n\n.ms-auto {\n  margin-right: auto !important;\n}\n\n.p-0 {\n  padding: 0 !important;\n}\n\n.p-1 {\n  padding: 0.25rem !important;\n}\n\n.p-2 {\n  padding: 0.5rem !important;\n}\n\n.p-3 {\n  padding: 1rem !important;\n}\n\n.p-4 {\n  padding: 1.5rem !important;\n}\n\n.p-5 {\n  padding: 3rem !important;\n}\n\n.px-0 {\n  padding-left: 0 !important;\n  padding-right: 0 !important;\n}\n\n.px-1 {\n  padding-left: 0.25rem !important;\n  padding-right: 0.25rem !important;\n}\n\n.px-2 {\n  padding-left: 0.5rem !important;\n  padding-right: 0.5rem !important;\n}\n\n.px-3 {\n  padding-left: 1rem !important;\n  padding-right: 1rem !important;\n}\n\n.px-4 {\n  padding-left: 1.5rem !important;\n  padding-right: 1.5rem !important;\n}\n\n.px-5 {\n  padding-left: 3rem !important;\n  padding-right: 3rem !important;\n}\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n\n.py-4 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n  padding-top: 0 !important;\n}\n\n.pt-1 {\n  padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n  padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n  padding-top: 1rem !important;\n}\n\n.pt-4 {\n  padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n  padding-top: 3rem !important;\n}\n\n.pe-0 {\n  padding-left: 0 !important;\n}\n\n.pe-1 {\n  padding-left: 0.25rem !important;\n}\n\n.pe-2 {\n  padding-left: 0.5rem !important;\n}\n\n.pe-3 {\n  padding-left: 1rem !important;\n}\n\n.pe-4 {\n  padding-left: 1.5rem !important;\n}\n\n.pe-5 {\n  padding-left: 3rem !important;\n}\n\n.pb-0 {\n  padding-bottom: 0 !important;\n}\n\n.pb-1 {\n  padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n  padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n  padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n  padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n  padding-bottom: 3rem !important;\n}\n\n.ps-0 {\n  padding-right: 0 !important;\n}\n\n.ps-1 {\n  padding-right: 0.25rem !important;\n}\n\n.ps-2 {\n  padding-right: 0.5rem !important;\n}\n\n.ps-3 {\n  padding-right: 1rem !important;\n}\n\n.ps-4 {\n  padding-right: 1.5rem !important;\n}\n\n.ps-5 {\n  padding-right: 3rem !important;\n}\n\n.gap-0 {\n  gap: 0 !important;\n}\n\n.gap-1 {\n  gap: 0.25rem !important;\n}\n\n.gap-2 {\n  gap: 0.5rem !important;\n}\n\n.gap-3 {\n  gap: 1rem !important;\n}\n\n.gap-4 {\n  gap: 1.5rem !important;\n}\n\n.gap-5 {\n  gap: 3rem !important;\n}\n\n.font-monospace {\n  font-family: var(--bs-font-monospace) !important;\n}\n\n.fs-1 {\n  font-size: calc(1.375rem + 1.5vw) !important;\n}\n\n.fs-2 {\n  font-size: calc(1.325rem + 0.9vw) !important;\n}\n\n.fs-3 {\n  font-size: calc(1.3rem + 0.6vw) !important;\n}\n\n.fs-4 {\n  font-size: calc(1.275rem + 0.3vw) !important;\n}\n\n.fs-5 {\n  font-size: 1.25rem !important;\n}\n\n.fs-6 {\n  font-size: 1rem !important;\n}\n\n.fst-italic {\n  font-style: italic !important;\n}\n\n.fst-normal {\n  font-style: normal !important;\n}\n\n.fw-light {\n  font-weight: 300 !important;\n}\n\n.fw-lighter {\n  font-weight: lighter !important;\n}\n\n.fw-normal {\n  font-weight: 400 !important;\n}\n\n.fw-bold {\n  font-weight: 700 !important;\n}\n\n.fw-semibold {\n  font-weight: 600 !important;\n}\n\n.fw-bolder {\n  font-weight: bolder !important;\n}\n\n.lh-1 {\n  line-height: 1 !important;\n}\n\n.lh-sm {\n  line-height: 1.25 !important;\n}\n\n.lh-base {\n  line-height: 1.5 !important;\n}\n\n.lh-lg {\n  line-height: 2 !important;\n}\n\n.text-start {\n  text-align: right !important;\n}\n\n.text-end {\n  text-align: left !important;\n}\n\n.text-center {\n  text-align: center !important;\n}\n\n.text-decoration-none {\n  text-decoration: none !important;\n}\n\n.text-decoration-underline {\n  text-decoration: underline !important;\n}\n\n.text-decoration-line-through {\n  text-decoration: line-through !important;\n}\n\n.text-lowercase {\n  text-transform: lowercase !important;\n}\n\n.text-uppercase {\n  text-transform: uppercase !important;\n}\n\n.text-capitalize {\n  text-transform: capitalize !important;\n}\n\n.text-wrap {\n  white-space: normal !important;\n}\n\n.text-nowrap {\n  white-space: nowrap !important;\n}\n.text-primary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-secondary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-success {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-info {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-warning {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-danger {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-light {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-dark {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-black {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-white {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-body {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;\n}\n\n.text-muted {\n  --bs-text-opacity: 1;\n  color: #6c757d !important;\n}\n\n.text-black-50 {\n  --bs-text-opacity: 1;\n  color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n  --bs-text-opacity: 1;\n  color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-reset {\n  --bs-text-opacity: 1;\n  color: inherit !important;\n}\n\n.text-opacity-25 {\n  --bs-text-opacity: 0.25;\n}\n\n.text-opacity-50 {\n  --bs-text-opacity: 0.5;\n}\n\n.text-opacity-75 {\n  --bs-text-opacity: 0.75;\n}\n\n.text-opacity-100 {\n  --bs-text-opacity: 1;\n}\n\n.bg-primary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-secondary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-success {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-info {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-warning {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-danger {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-light {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-dark {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-black {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-white {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-body {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;\n}\n\n.bg-transparent {\n  --bs-bg-opacity: 1;\n  background-color: transparent !important;\n}\n\n.bg-opacity-10 {\n  --bs-bg-opacity: 0.1;\n}\n\n.bg-opacity-25 {\n  --bs-bg-opacity: 0.25;\n}\n\n.bg-opacity-50 {\n  --bs-bg-opacity: 0.5;\n}\n\n.bg-opacity-75 {\n  --bs-bg-opacity: 0.75;\n}\n\n.bg-opacity-100 {\n  --bs-bg-opacity: 1;\n}\n\n.bg-gradient {\n  background-image: var(--bs-gradient) !important;\n}\n\n.user-select-all {\n  -webkit-user-select: all !important;\n  -moz-user-select: all !important;\n  user-select: all !important;\n}\n\n.user-select-auto {\n  -webkit-user-select: auto !important;\n  -moz-user-select: auto !important;\n  user-select: auto !important;\n}\n\n.user-select-none {\n  -webkit-user-select: none !important;\n  -moz-user-select: none !important;\n  user-select: none !important;\n}\n\n.pe-none {\n  pointer-events: none !important;\n}\n\n.pe-auto {\n  pointer-events: auto !important;\n}\n\n.rounded {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-0 {\n  border-radius: 0 !important;\n}\n\n.rounded-1 {\n  border-radius: var(--bs-border-radius-sm) !important;\n}\n\n.rounded-2 {\n  border-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-3 {\n  border-radius: var(--bs-border-radius-lg) !important;\n}\n\n.rounded-4 {\n  border-radius: var(--bs-border-radius-xl) !important;\n}\n\n.rounded-5 {\n  border-radius: var(--bs-border-radius-2xl) !important;\n}\n\n.rounded-circle {\n  border-radius: 50% !important;\n}\n\n.rounded-pill {\n  border-radius: var(--bs-border-radius-pill) !important;\n}\n\n.rounded-top {\n  border-top-right-radius: var(--bs-border-radius) !important;\n  border-top-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-end {\n  border-top-left-radius: var(--bs-border-radius) !important;\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-bottom {\n  border-bottom-left-radius: var(--bs-border-radius) !important;\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n}\n\n.rounded-start {\n  border-bottom-right-radius: var(--bs-border-radius) !important;\n  border-top-right-radius: var(--bs-border-radius) !important;\n}\n\n.visible {\n  visibility: visible !important;\n}\n\n.invisible {\n  visibility: hidden !important;\n}\n\n@media (min-width: 576px) {\n  .float-sm-start {\n    float: right !important;\n  }\n  .float-sm-end {\n    float: left !important;\n  }\n  .float-sm-none {\n    float: none !important;\n  }\n  .d-sm-inline {\n    display: inline !important;\n  }\n  .d-sm-inline-block {\n    display: inline-block !important;\n  }\n  .d-sm-block {\n    display: block !important;\n  }\n  .d-sm-grid {\n    display: grid !important;\n  }\n  .d-sm-table {\n    display: table !important;\n  }\n  .d-sm-table-row {\n    display: table-row !important;\n  }\n  .d-sm-table-cell {\n    display: table-cell !important;\n  }\n  .d-sm-flex {\n    display: flex !important;\n  }\n  .d-sm-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-sm-none {\n    display: none !important;\n  }\n  .flex-sm-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-sm-row {\n    flex-direction: row !important;\n  }\n  .flex-sm-column {\n    flex-direction: column !important;\n  }\n  .flex-sm-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-sm-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-sm-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-sm-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-sm-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-sm-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-sm-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-sm-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-sm-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-sm-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-sm-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-sm-center {\n    justify-content: center !important;\n  }\n  .justify-content-sm-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-sm-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-sm-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-sm-start {\n    align-items: flex-start !important;\n  }\n  .align-items-sm-end {\n    align-items: flex-end !important;\n  }\n  .align-items-sm-center {\n    align-items: center !important;\n  }\n  .align-items-sm-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-sm-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-sm-start {\n    align-content: flex-start !important;\n  }\n  .align-content-sm-end {\n    align-content: flex-end !important;\n  }\n  .align-content-sm-center {\n    align-content: center !important;\n  }\n  .align-content-sm-between {\n    align-content: space-between !important;\n  }\n  .align-content-sm-around {\n    align-content: space-around !important;\n  }\n  .align-content-sm-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-sm-auto {\n    align-self: auto !important;\n  }\n  .align-self-sm-start {\n    align-self: flex-start !important;\n  }\n  .align-self-sm-end {\n    align-self: flex-end !important;\n  }\n  .align-self-sm-center {\n    align-self: center !important;\n  }\n  .align-self-sm-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-sm-stretch {\n    align-self: stretch !important;\n  }\n  .order-sm-first {\n    order: -1 !important;\n  }\n  .order-sm-0 {\n    order: 0 !important;\n  }\n  .order-sm-1 {\n    order: 1 !important;\n  }\n  .order-sm-2 {\n    order: 2 !important;\n  }\n  .order-sm-3 {\n    order: 3 !important;\n  }\n  .order-sm-4 {\n    order: 4 !important;\n  }\n  .order-sm-5 {\n    order: 5 !important;\n  }\n  .order-sm-last {\n    order: 6 !important;\n  }\n  .m-sm-0 {\n    margin: 0 !important;\n  }\n  .m-sm-1 {\n    margin: 0.25rem !important;\n  }\n  .m-sm-2 {\n    margin: 0.5rem !important;\n  }\n  .m-sm-3 {\n    margin: 1rem !important;\n  }\n  .m-sm-4 {\n    margin: 1.5rem !important;\n  }\n  .m-sm-5 {\n    margin: 3rem !important;\n  }\n  .m-sm-auto {\n    margin: auto !important;\n  }\n  .mx-sm-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-sm-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-sm-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-sm-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-sm-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-sm-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-sm-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-sm-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-sm-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-sm-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-sm-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-sm-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-sm-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-sm-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-sm-0 {\n    margin-top: 0 !important;\n  }\n  .mt-sm-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-sm-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-sm-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-sm-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-sm-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-sm-auto {\n    margin-top: auto !important;\n  }\n  .me-sm-0 {\n    margin-left: 0 !important;\n  }\n  .me-sm-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-sm-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-sm-3 {\n    margin-left: 1rem !important;\n  }\n  .me-sm-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-sm-5 {\n    margin-left: 3rem !important;\n  }\n  .me-sm-auto {\n    margin-left: auto !important;\n  }\n  .mb-sm-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-sm-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-sm-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-sm-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-sm-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-sm-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-sm-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-sm-0 {\n    margin-right: 0 !important;\n  }\n  .ms-sm-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-sm-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-sm-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-sm-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-sm-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-sm-auto {\n    margin-right: auto !important;\n  }\n  .p-sm-0 {\n    padding: 0 !important;\n  }\n  .p-sm-1 {\n    padding: 0.25rem !important;\n  }\n  .p-sm-2 {\n    padding: 0.5rem !important;\n  }\n  .p-sm-3 {\n    padding: 1rem !important;\n  }\n  .p-sm-4 {\n    padding: 1.5rem !important;\n  }\n  .p-sm-5 {\n    padding: 3rem !important;\n  }\n  .px-sm-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-sm-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-sm-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-sm-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-sm-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-sm-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-sm-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-sm-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-sm-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-sm-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-sm-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-sm-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-sm-0 {\n    padding-top: 0 !important;\n  }\n  .pt-sm-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-sm-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-sm-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-sm-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-sm-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-sm-0 {\n    padding-left: 0 !important;\n  }\n  .pe-sm-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-sm-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-sm-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-sm-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-sm-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-sm-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-sm-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-sm-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-sm-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-sm-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-sm-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-sm-0 {\n    padding-right: 0 !important;\n  }\n  .ps-sm-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-sm-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-sm-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-sm-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-sm-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-sm-0 {\n    gap: 0 !important;\n  }\n  .gap-sm-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-sm-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-sm-3 {\n    gap: 1rem !important;\n  }\n  .gap-sm-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-sm-5 {\n    gap: 3rem !important;\n  }\n  .text-sm-start {\n    text-align: right !important;\n  }\n  .text-sm-end {\n    text-align: left !important;\n  }\n  .text-sm-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 768px) {\n  .float-md-start {\n    float: right !important;\n  }\n  .float-md-end {\n    float: left !important;\n  }\n  .float-md-none {\n    float: none !important;\n  }\n  .d-md-inline {\n    display: inline !important;\n  }\n  .d-md-inline-block {\n    display: inline-block !important;\n  }\n  .d-md-block {\n    display: block !important;\n  }\n  .d-md-grid {\n    display: grid !important;\n  }\n  .d-md-table {\n    display: table !important;\n  }\n  .d-md-table-row {\n    display: table-row !important;\n  }\n  .d-md-table-cell {\n    display: table-cell !important;\n  }\n  .d-md-flex {\n    display: flex !important;\n  }\n  .d-md-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-md-none {\n    display: none !important;\n  }\n  .flex-md-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-md-row {\n    flex-direction: row !important;\n  }\n  .flex-md-column {\n    flex-direction: column !important;\n  }\n  .flex-md-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-md-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-md-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-md-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-md-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-md-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-md-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-md-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-md-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-md-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-md-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-md-center {\n    justify-content: center !important;\n  }\n  .justify-content-md-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-md-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-md-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-md-start {\n    align-items: flex-start !important;\n  }\n  .align-items-md-end {\n    align-items: flex-end !important;\n  }\n  .align-items-md-center {\n    align-items: center !important;\n  }\n  .align-items-md-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-md-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-md-start {\n    align-content: flex-start !important;\n  }\n  .align-content-md-end {\n    align-content: flex-end !important;\n  }\n  .align-content-md-center {\n    align-content: center !important;\n  }\n  .align-content-md-between {\n    align-content: space-between !important;\n  }\n  .align-content-md-around {\n    align-content: space-around !important;\n  }\n  .align-content-md-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-md-auto {\n    align-self: auto !important;\n  }\n  .align-self-md-start {\n    align-self: flex-start !important;\n  }\n  .align-self-md-end {\n    align-self: flex-end !important;\n  }\n  .align-self-md-center {\n    align-self: center !important;\n  }\n  .align-self-md-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-md-stretch {\n    align-self: stretch !important;\n  }\n  .order-md-first {\n    order: -1 !important;\n  }\n  .order-md-0 {\n    order: 0 !important;\n  }\n  .order-md-1 {\n    order: 1 !important;\n  }\n  .order-md-2 {\n    order: 2 !important;\n  }\n  .order-md-3 {\n    order: 3 !important;\n  }\n  .order-md-4 {\n    order: 4 !important;\n  }\n  .order-md-5 {\n    order: 5 !important;\n  }\n  .order-md-last {\n    order: 6 !important;\n  }\n  .m-md-0 {\n    margin: 0 !important;\n  }\n  .m-md-1 {\n    margin: 0.25rem !important;\n  }\n  .m-md-2 {\n    margin: 0.5rem !important;\n  }\n  .m-md-3 {\n    margin: 1rem !important;\n  }\n  .m-md-4 {\n    margin: 1.5rem !important;\n  }\n  .m-md-5 {\n    margin: 3rem !important;\n  }\n  .m-md-auto {\n    margin: auto !important;\n  }\n  .mx-md-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-md-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-md-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-md-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-md-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-md-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-md-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-md-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-md-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-md-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-md-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-md-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-md-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-md-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-md-0 {\n    margin-top: 0 !important;\n  }\n  .mt-md-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-md-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-md-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-md-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-md-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-md-auto {\n    margin-top: auto !important;\n  }\n  .me-md-0 {\n    margin-left: 0 !important;\n  }\n  .me-md-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-md-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-md-3 {\n    margin-left: 1rem !important;\n  }\n  .me-md-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-md-5 {\n    margin-left: 3rem !important;\n  }\n  .me-md-auto {\n    margin-left: auto !important;\n  }\n  .mb-md-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-md-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-md-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-md-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-md-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-md-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-md-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-md-0 {\n    margin-right: 0 !important;\n  }\n  .ms-md-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-md-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-md-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-md-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-md-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-md-auto {\n    margin-right: auto !important;\n  }\n  .p-md-0 {\n    padding: 0 !important;\n  }\n  .p-md-1 {\n    padding: 0.25rem !important;\n  }\n  .p-md-2 {\n    padding: 0.5rem !important;\n  }\n  .p-md-3 {\n    padding: 1rem !important;\n  }\n  .p-md-4 {\n    padding: 1.5rem !important;\n  }\n  .p-md-5 {\n    padding: 3rem !important;\n  }\n  .px-md-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-md-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-md-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-md-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-md-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-md-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-md-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-md-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-md-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-md-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-md-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-md-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-md-0 {\n    padding-top: 0 !important;\n  }\n  .pt-md-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-md-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-md-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-md-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-md-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-md-0 {\n    padding-left: 0 !important;\n  }\n  .pe-md-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-md-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-md-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-md-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-md-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-md-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-md-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-md-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-md-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-md-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-md-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-md-0 {\n    padding-right: 0 !important;\n  }\n  .ps-md-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-md-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-md-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-md-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-md-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-md-0 {\n    gap: 0 !important;\n  }\n  .gap-md-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-md-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-md-3 {\n    gap: 1rem !important;\n  }\n  .gap-md-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-md-5 {\n    gap: 3rem !important;\n  }\n  .text-md-start {\n    text-align: right !important;\n  }\n  .text-md-end {\n    text-align: left !important;\n  }\n  .text-md-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 992px) {\n  .float-lg-start {\n    float: right !important;\n  }\n  .float-lg-end {\n    float: left !important;\n  }\n  .float-lg-none {\n    float: none !important;\n  }\n  .d-lg-inline {\n    display: inline !important;\n  }\n  .d-lg-inline-block {\n    display: inline-block !important;\n  }\n  .d-lg-block {\n    display: block !important;\n  }\n  .d-lg-grid {\n    display: grid !important;\n  }\n  .d-lg-table {\n    display: table !important;\n  }\n  .d-lg-table-row {\n    display: table-row !important;\n  }\n  .d-lg-table-cell {\n    display: table-cell !important;\n  }\n  .d-lg-flex {\n    display: flex !important;\n  }\n  .d-lg-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-lg-none {\n    display: none !important;\n  }\n  .flex-lg-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-lg-row {\n    flex-direction: row !important;\n  }\n  .flex-lg-column {\n    flex-direction: column !important;\n  }\n  .flex-lg-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-lg-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-lg-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-lg-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-lg-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-lg-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-lg-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-lg-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-lg-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-lg-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-lg-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-lg-center {\n    justify-content: center !important;\n  }\n  .justify-content-lg-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-lg-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-lg-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-lg-start {\n    align-items: flex-start !important;\n  }\n  .align-items-lg-end {\n    align-items: flex-end !important;\n  }\n  .align-items-lg-center {\n    align-items: center !important;\n  }\n  .align-items-lg-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-lg-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-lg-start {\n    align-content: flex-start !important;\n  }\n  .align-content-lg-end {\n    align-content: flex-end !important;\n  }\n  .align-content-lg-center {\n    align-content: center !important;\n  }\n  .align-content-lg-between {\n    align-content: space-between !important;\n  }\n  .align-content-lg-around {\n    align-content: space-around !important;\n  }\n  .align-content-lg-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-lg-auto {\n    align-self: auto !important;\n  }\n  .align-self-lg-start {\n    align-self: flex-start !important;\n  }\n  .align-self-lg-end {\n    align-self: flex-end !important;\n  }\n  .align-self-lg-center {\n    align-self: center !important;\n  }\n  .align-self-lg-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-lg-stretch {\n    align-self: stretch !important;\n  }\n  .order-lg-first {\n    order: -1 !important;\n  }\n  .order-lg-0 {\n    order: 0 !important;\n  }\n  .order-lg-1 {\n    order: 1 !important;\n  }\n  .order-lg-2 {\n    order: 2 !important;\n  }\n  .order-lg-3 {\n    order: 3 !important;\n  }\n  .order-lg-4 {\n    order: 4 !important;\n  }\n  .order-lg-5 {\n    order: 5 !important;\n  }\n  .order-lg-last {\n    order: 6 !important;\n  }\n  .m-lg-0 {\n    margin: 0 !important;\n  }\n  .m-lg-1 {\n    margin: 0.25rem !important;\n  }\n  .m-lg-2 {\n    margin: 0.5rem !important;\n  }\n  .m-lg-3 {\n    margin: 1rem !important;\n  }\n  .m-lg-4 {\n    margin: 1.5rem !important;\n  }\n  .m-lg-5 {\n    margin: 3rem !important;\n  }\n  .m-lg-auto {\n    margin: auto !important;\n  }\n  .mx-lg-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-lg-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-lg-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-lg-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-lg-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-lg-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-lg-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-lg-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-lg-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-lg-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-lg-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-lg-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-lg-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-lg-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-lg-0 {\n    margin-top: 0 !important;\n  }\n  .mt-lg-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-lg-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-lg-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-lg-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-lg-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-lg-auto {\n    margin-top: auto !important;\n  }\n  .me-lg-0 {\n    margin-left: 0 !important;\n  }\n  .me-lg-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-lg-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-lg-3 {\n    margin-left: 1rem !important;\n  }\n  .me-lg-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-lg-5 {\n    margin-left: 3rem !important;\n  }\n  .me-lg-auto {\n    margin-left: auto !important;\n  }\n  .mb-lg-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-lg-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-lg-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-lg-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-lg-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-lg-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-lg-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-lg-0 {\n    margin-right: 0 !important;\n  }\n  .ms-lg-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-lg-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-lg-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-lg-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-lg-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-lg-auto {\n    margin-right: auto !important;\n  }\n  .p-lg-0 {\n    padding: 0 !important;\n  }\n  .p-lg-1 {\n    padding: 0.25rem !important;\n  }\n  .p-lg-2 {\n    padding: 0.5rem !important;\n  }\n  .p-lg-3 {\n    padding: 1rem !important;\n  }\n  .p-lg-4 {\n    padding: 1.5rem !important;\n  }\n  .p-lg-5 {\n    padding: 3rem !important;\n  }\n  .px-lg-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-lg-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-lg-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-lg-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-lg-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-lg-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-lg-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-lg-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-lg-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-lg-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-lg-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-lg-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-lg-0 {\n    padding-top: 0 !important;\n  }\n  .pt-lg-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-lg-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-lg-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-lg-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-lg-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-lg-0 {\n    padding-left: 0 !important;\n  }\n  .pe-lg-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-lg-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-lg-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-lg-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-lg-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-lg-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-lg-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-lg-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-lg-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-lg-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-lg-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-lg-0 {\n    padding-right: 0 !important;\n  }\n  .ps-lg-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-lg-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-lg-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-lg-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-lg-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-lg-0 {\n    gap: 0 !important;\n  }\n  .gap-lg-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-lg-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-lg-3 {\n    gap: 1rem !important;\n  }\n  .gap-lg-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-lg-5 {\n    gap: 3rem !important;\n  }\n  .text-lg-start {\n    text-align: right !important;\n  }\n  .text-lg-end {\n    text-align: left !important;\n  }\n  .text-lg-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .float-xl-start {\n    float: right !important;\n  }\n  .float-xl-end {\n    float: left !important;\n  }\n  .float-xl-none {\n    float: none !important;\n  }\n  .d-xl-inline {\n    display: inline !important;\n  }\n  .d-xl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xl-block {\n    display: block !important;\n  }\n  .d-xl-grid {\n    display: grid !important;\n  }\n  .d-xl-table {\n    display: table !important;\n  }\n  .d-xl-table-row {\n    display: table-row !important;\n  }\n  .d-xl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xl-flex {\n    display: flex !important;\n  }\n  .d-xl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xl-none {\n    display: none !important;\n  }\n  .flex-xl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xl-row {\n    flex-direction: row !important;\n  }\n  .flex-xl-column {\n    flex-direction: column !important;\n  }\n  .flex-xl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xl-center {\n    align-items: center !important;\n  }\n  .align-items-xl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xl-center {\n    align-content: center !important;\n  }\n  .align-content-xl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xl-center {\n    align-self: center !important;\n  }\n  .align-self-xl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xl-first {\n    order: -1 !important;\n  }\n  .order-xl-0 {\n    order: 0 !important;\n  }\n  .order-xl-1 {\n    order: 1 !important;\n  }\n  .order-xl-2 {\n    order: 2 !important;\n  }\n  .order-xl-3 {\n    order: 3 !important;\n  }\n  .order-xl-4 {\n    order: 4 !important;\n  }\n  .order-xl-5 {\n    order: 5 !important;\n  }\n  .order-xl-last {\n    order: 6 !important;\n  }\n  .m-xl-0 {\n    margin: 0 !important;\n  }\n  .m-xl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xl-3 {\n    margin: 1rem !important;\n  }\n  .m-xl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xl-5 {\n    margin: 3rem !important;\n  }\n  .m-xl-auto {\n    margin: auto !important;\n  }\n  .mx-xl-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-xl-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-xl-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-xl-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-xl-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-xl-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-xl-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-xl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xl-auto {\n    margin-top: auto !important;\n  }\n  .me-xl-0 {\n    margin-left: 0 !important;\n  }\n  .me-xl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-xl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-xl-3 {\n    margin-left: 1rem !important;\n  }\n  .me-xl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-xl-5 {\n    margin-left: 3rem !important;\n  }\n  .me-xl-auto {\n    margin-left: auto !important;\n  }\n  .mb-xl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xl-0 {\n    margin-right: 0 !important;\n  }\n  .ms-xl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-xl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-xl-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-xl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-xl-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-xl-auto {\n    margin-right: auto !important;\n  }\n  .p-xl-0 {\n    padding: 0 !important;\n  }\n  .p-xl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xl-3 {\n    padding: 1rem !important;\n  }\n  .p-xl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xl-5 {\n    padding: 3rem !important;\n  }\n  .px-xl-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-xl-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-xl-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-xl-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-xl-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-xl-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-xl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xl-0 {\n    padding-left: 0 !important;\n  }\n  .pe-xl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-xl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-xl-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-xl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-xl-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-xl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xl-0 {\n    padding-right: 0 !important;\n  }\n  .ps-xl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-xl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-xl-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-xl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-xl-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-xl-0 {\n    gap: 0 !important;\n  }\n  .gap-xl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xl-5 {\n    gap: 3rem !important;\n  }\n  .text-xl-start {\n    text-align: right !important;\n  }\n  .text-xl-end {\n    text-align: left !important;\n  }\n  .text-xl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1400px) {\n  .float-xxl-start {\n    float: right !important;\n  }\n  .float-xxl-end {\n    float: left !important;\n  }\n  .float-xxl-none {\n    float: none !important;\n  }\n  .d-xxl-inline {\n    display: inline !important;\n  }\n  .d-xxl-inline-block {\n    display: inline-block !important;\n  }\n  .d-xxl-block {\n    display: block !important;\n  }\n  .d-xxl-grid {\n    display: grid !important;\n  }\n  .d-xxl-table {\n    display: table !important;\n  }\n  .d-xxl-table-row {\n    display: table-row !important;\n  }\n  .d-xxl-table-cell {\n    display: table-cell !important;\n  }\n  .d-xxl-flex {\n    display: flex !important;\n  }\n  .d-xxl-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-xxl-none {\n    display: none !important;\n  }\n  .flex-xxl-fill {\n    flex: 1 1 auto !important;\n  }\n  .flex-xxl-row {\n    flex-direction: row !important;\n  }\n  .flex-xxl-column {\n    flex-direction: column !important;\n  }\n  .flex-xxl-row-reverse {\n    flex-direction: row-reverse !important;\n  }\n  .flex-xxl-column-reverse {\n    flex-direction: column-reverse !important;\n  }\n  .flex-xxl-grow-0 {\n    flex-grow: 0 !important;\n  }\n  .flex-xxl-grow-1 {\n    flex-grow: 1 !important;\n  }\n  .flex-xxl-shrink-0 {\n    flex-shrink: 0 !important;\n  }\n  .flex-xxl-shrink-1 {\n    flex-shrink: 1 !important;\n  }\n  .flex-xxl-wrap {\n    flex-wrap: wrap !important;\n  }\n  .flex-xxl-nowrap {\n    flex-wrap: nowrap !important;\n  }\n  .flex-xxl-wrap-reverse {\n    flex-wrap: wrap-reverse !important;\n  }\n  .justify-content-xxl-start {\n    justify-content: flex-start !important;\n  }\n  .justify-content-xxl-end {\n    justify-content: flex-end !important;\n  }\n  .justify-content-xxl-center {\n    justify-content: center !important;\n  }\n  .justify-content-xxl-between {\n    justify-content: space-between !important;\n  }\n  .justify-content-xxl-around {\n    justify-content: space-around !important;\n  }\n  .justify-content-xxl-evenly {\n    justify-content: space-evenly !important;\n  }\n  .align-items-xxl-start {\n    align-items: flex-start !important;\n  }\n  .align-items-xxl-end {\n    align-items: flex-end !important;\n  }\n  .align-items-xxl-center {\n    align-items: center !important;\n  }\n  .align-items-xxl-baseline {\n    align-items: baseline !important;\n  }\n  .align-items-xxl-stretch {\n    align-items: stretch !important;\n  }\n  .align-content-xxl-start {\n    align-content: flex-start !important;\n  }\n  .align-content-xxl-end {\n    align-content: flex-end !important;\n  }\n  .align-content-xxl-center {\n    align-content: center !important;\n  }\n  .align-content-xxl-between {\n    align-content: space-between !important;\n  }\n  .align-content-xxl-around {\n    align-content: space-around !important;\n  }\n  .align-content-xxl-stretch {\n    align-content: stretch !important;\n  }\n  .align-self-xxl-auto {\n    align-self: auto !important;\n  }\n  .align-self-xxl-start {\n    align-self: flex-start !important;\n  }\n  .align-self-xxl-end {\n    align-self: flex-end !important;\n  }\n  .align-self-xxl-center {\n    align-self: center !important;\n  }\n  .align-self-xxl-baseline {\n    align-self: baseline !important;\n  }\n  .align-self-xxl-stretch {\n    align-self: stretch !important;\n  }\n  .order-xxl-first {\n    order: -1 !important;\n  }\n  .order-xxl-0 {\n    order: 0 !important;\n  }\n  .order-xxl-1 {\n    order: 1 !important;\n  }\n  .order-xxl-2 {\n    order: 2 !important;\n  }\n  .order-xxl-3 {\n    order: 3 !important;\n  }\n  .order-xxl-4 {\n    order: 4 !important;\n  }\n  .order-xxl-5 {\n    order: 5 !important;\n  }\n  .order-xxl-last {\n    order: 6 !important;\n  }\n  .m-xxl-0 {\n    margin: 0 !important;\n  }\n  .m-xxl-1 {\n    margin: 0.25rem !important;\n  }\n  .m-xxl-2 {\n    margin: 0.5rem !important;\n  }\n  .m-xxl-3 {\n    margin: 1rem !important;\n  }\n  .m-xxl-4 {\n    margin: 1.5rem !important;\n  }\n  .m-xxl-5 {\n    margin: 3rem !important;\n  }\n  .m-xxl-auto {\n    margin: auto !important;\n  }\n  .mx-xxl-0 {\n    margin-left: 0 !important;\n    margin-right: 0 !important;\n  }\n  .mx-xxl-1 {\n    margin-left: 0.25rem !important;\n    margin-right: 0.25rem !important;\n  }\n  .mx-xxl-2 {\n    margin-left: 0.5rem !important;\n    margin-right: 0.5rem !important;\n  }\n  .mx-xxl-3 {\n    margin-left: 1rem !important;\n    margin-right: 1rem !important;\n  }\n  .mx-xxl-4 {\n    margin-left: 1.5rem !important;\n    margin-right: 1.5rem !important;\n  }\n  .mx-xxl-5 {\n    margin-left: 3rem !important;\n    margin-right: 3rem !important;\n  }\n  .mx-xxl-auto {\n    margin-left: auto !important;\n    margin-right: auto !important;\n  }\n  .my-xxl-0 {\n    margin-top: 0 !important;\n    margin-bottom: 0 !important;\n  }\n  .my-xxl-1 {\n    margin-top: 0.25rem !important;\n    margin-bottom: 0.25rem !important;\n  }\n  .my-xxl-2 {\n    margin-top: 0.5rem !important;\n    margin-bottom: 0.5rem !important;\n  }\n  .my-xxl-3 {\n    margin-top: 1rem !important;\n    margin-bottom: 1rem !important;\n  }\n  .my-xxl-4 {\n    margin-top: 1.5rem !important;\n    margin-bottom: 1.5rem !important;\n  }\n  .my-xxl-5 {\n    margin-top: 3rem !important;\n    margin-bottom: 3rem !important;\n  }\n  .my-xxl-auto {\n    margin-top: auto !important;\n    margin-bottom: auto !important;\n  }\n  .mt-xxl-0 {\n    margin-top: 0 !important;\n  }\n  .mt-xxl-1 {\n    margin-top: 0.25rem !important;\n  }\n  .mt-xxl-2 {\n    margin-top: 0.5rem !important;\n  }\n  .mt-xxl-3 {\n    margin-top: 1rem !important;\n  }\n  .mt-xxl-4 {\n    margin-top: 1.5rem !important;\n  }\n  .mt-xxl-5 {\n    margin-top: 3rem !important;\n  }\n  .mt-xxl-auto {\n    margin-top: auto !important;\n  }\n  .me-xxl-0 {\n    margin-left: 0 !important;\n  }\n  .me-xxl-1 {\n    margin-left: 0.25rem !important;\n  }\n  .me-xxl-2 {\n    margin-left: 0.5rem !important;\n  }\n  .me-xxl-3 {\n    margin-left: 1rem !important;\n  }\n  .me-xxl-4 {\n    margin-left: 1.5rem !important;\n  }\n  .me-xxl-5 {\n    margin-left: 3rem !important;\n  }\n  .me-xxl-auto {\n    margin-left: auto !important;\n  }\n  .mb-xxl-0 {\n    margin-bottom: 0 !important;\n  }\n  .mb-xxl-1 {\n    margin-bottom: 0.25rem !important;\n  }\n  .mb-xxl-2 {\n    margin-bottom: 0.5rem !important;\n  }\n  .mb-xxl-3 {\n    margin-bottom: 1rem !important;\n  }\n  .mb-xxl-4 {\n    margin-bottom: 1.5rem !important;\n  }\n  .mb-xxl-5 {\n    margin-bottom: 3rem !important;\n  }\n  .mb-xxl-auto {\n    margin-bottom: auto !important;\n  }\n  .ms-xxl-0 {\n    margin-right: 0 !important;\n  }\n  .ms-xxl-1 {\n    margin-right: 0.25rem !important;\n  }\n  .ms-xxl-2 {\n    margin-right: 0.5rem !important;\n  }\n  .ms-xxl-3 {\n    margin-right: 1rem !important;\n  }\n  .ms-xxl-4 {\n    margin-right: 1.5rem !important;\n  }\n  .ms-xxl-5 {\n    margin-right: 3rem !important;\n  }\n  .ms-xxl-auto {\n    margin-right: auto !important;\n  }\n  .p-xxl-0 {\n    padding: 0 !important;\n  }\n  .p-xxl-1 {\n    padding: 0.25rem !important;\n  }\n  .p-xxl-2 {\n    padding: 0.5rem !important;\n  }\n  .p-xxl-3 {\n    padding: 1rem !important;\n  }\n  .p-xxl-4 {\n    padding: 1.5rem !important;\n  }\n  .p-xxl-5 {\n    padding: 3rem !important;\n  }\n  .px-xxl-0 {\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  .px-xxl-1 {\n    padding-left: 0.25rem !important;\n    padding-right: 0.25rem !important;\n  }\n  .px-xxl-2 {\n    padding-left: 0.5rem !important;\n    padding-right: 0.5rem !important;\n  }\n  .px-xxl-3 {\n    padding-left: 1rem !important;\n    padding-right: 1rem !important;\n  }\n  .px-xxl-4 {\n    padding-left: 1.5rem !important;\n    padding-right: 1.5rem !important;\n  }\n  .px-xxl-5 {\n    padding-left: 3rem !important;\n    padding-right: 3rem !important;\n  }\n  .py-xxl-0 {\n    padding-top: 0 !important;\n    padding-bottom: 0 !important;\n  }\n  .py-xxl-1 {\n    padding-top: 0.25rem !important;\n    padding-bottom: 0.25rem !important;\n  }\n  .py-xxl-2 {\n    padding-top: 0.5rem !important;\n    padding-bottom: 0.5rem !important;\n  }\n  .py-xxl-3 {\n    padding-top: 1rem !important;\n    padding-bottom: 1rem !important;\n  }\n  .py-xxl-4 {\n    padding-top: 1.5rem !important;\n    padding-bottom: 1.5rem !important;\n  }\n  .py-xxl-5 {\n    padding-top: 3rem !important;\n    padding-bottom: 3rem !important;\n  }\n  .pt-xxl-0 {\n    padding-top: 0 !important;\n  }\n  .pt-xxl-1 {\n    padding-top: 0.25rem !important;\n  }\n  .pt-xxl-2 {\n    padding-top: 0.5rem !important;\n  }\n  .pt-xxl-3 {\n    padding-top: 1rem !important;\n  }\n  .pt-xxl-4 {\n    padding-top: 1.5rem !important;\n  }\n  .pt-xxl-5 {\n    padding-top: 3rem !important;\n  }\n  .pe-xxl-0 {\n    padding-left: 0 !important;\n  }\n  .pe-xxl-1 {\n    padding-left: 0.25rem !important;\n  }\n  .pe-xxl-2 {\n    padding-left: 0.5rem !important;\n  }\n  .pe-xxl-3 {\n    padding-left: 1rem !important;\n  }\n  .pe-xxl-4 {\n    padding-left: 1.5rem !important;\n  }\n  .pe-xxl-5 {\n    padding-left: 3rem !important;\n  }\n  .pb-xxl-0 {\n    padding-bottom: 0 !important;\n  }\n  .pb-xxl-1 {\n    padding-bottom: 0.25rem !important;\n  }\n  .pb-xxl-2 {\n    padding-bottom: 0.5rem !important;\n  }\n  .pb-xxl-3 {\n    padding-bottom: 1rem !important;\n  }\n  .pb-xxl-4 {\n    padding-bottom: 1.5rem !important;\n  }\n  .pb-xxl-5 {\n    padding-bottom: 3rem !important;\n  }\n  .ps-xxl-0 {\n    padding-right: 0 !important;\n  }\n  .ps-xxl-1 {\n    padding-right: 0.25rem !important;\n  }\n  .ps-xxl-2 {\n    padding-right: 0.5rem !important;\n  }\n  .ps-xxl-3 {\n    padding-right: 1rem !important;\n  }\n  .ps-xxl-4 {\n    padding-right: 1.5rem !important;\n  }\n  .ps-xxl-5 {\n    padding-right: 3rem !important;\n  }\n  .gap-xxl-0 {\n    gap: 0 !important;\n  }\n  .gap-xxl-1 {\n    gap: 0.25rem !important;\n  }\n  .gap-xxl-2 {\n    gap: 0.5rem !important;\n  }\n  .gap-xxl-3 {\n    gap: 1rem !important;\n  }\n  .gap-xxl-4 {\n    gap: 1.5rem !important;\n  }\n  .gap-xxl-5 {\n    gap: 3rem !important;\n  }\n  .text-xxl-start {\n    text-align: right !important;\n  }\n  .text-xxl-end {\n    text-align: left !important;\n  }\n  .text-xxl-center {\n    text-align: center !important;\n  }\n}\n@media (min-width: 1200px) {\n  .fs-1 {\n    font-size: 2.5rem !important;\n  }\n  .fs-2 {\n    font-size: 2rem !important;\n  }\n  .fs-3 {\n    font-size: 1.75rem !important;\n  }\n  .fs-4 {\n    font-size: 1.5rem !important;\n  }\n}\n@media print {\n  .d-print-inline {\n    display: inline !important;\n  }\n  .d-print-inline-block {\n    display: inline-block !important;\n  }\n  .d-print-block {\n    display: block !important;\n  }\n  .d-print-grid {\n    display: grid !important;\n  }\n  .d-print-table {\n    display: table !important;\n  }\n  .d-print-table-row {\n    display: table-row !important;\n  }\n  .d-print-table-cell {\n    display: table-cell !important;\n  }\n  .d-print-flex {\n    display: flex !important;\n  }\n  .d-print-inline-flex {\n    display: inline-flex !important;\n  }\n  .d-print-none {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap.rtl.css.map */"
  },
  {
    "path": "src/common/bootstrap/dist/js/bootstrap.bundle.js",
    "content": "/*!\n  * Bootstrap v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory());\n})(this, (function () { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/index.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const MAX_UID = 1000000;\n  const MILLISECONDS_MULTIPLIER = 1000;\n  const TRANSITION_END = 'transitionend'; // Shout-out Angus Croll (https://goo.gl/pxwQGp)\n\n  const toType = object => {\n    if (object === null || object === undefined) {\n      return `${object}`;\n    }\n\n    return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n  };\n  /**\n   * Public Util API\n   */\n\n\n  const getUID = prefix => {\n    do {\n      prefix += Math.floor(Math.random() * MAX_UID);\n    } while (document.getElementById(prefix));\n\n    return prefix;\n  };\n\n  const getSelector = element => {\n    let selector = element.getAttribute('data-bs-target');\n\n    if (!selector || selector === '#') {\n      let hrefAttribute = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,\n      // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n      // `document.querySelector` will rightfully complain it is invalid.\n      // See https://github.com/twbs/bootstrap/issues/32273\n\n      if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n        return null;\n      } // Just in case some CMS puts out a full URL with the anchor appended\n\n\n      if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n        hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n      }\n\n      selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n    }\n\n    return selector;\n  };\n\n  const getSelectorFromElement = element => {\n    const selector = getSelector(element);\n\n    if (selector) {\n      return document.querySelector(selector) ? selector : null;\n    }\n\n    return null;\n  };\n\n  const getElementFromSelector = element => {\n    const selector = getSelector(element);\n    return selector ? document.querySelector(selector) : null;\n  };\n\n  const getTransitionDurationFromElement = element => {\n    if (!element) {\n      return 0;\n    } // Get transition-duration of the element\n\n\n    let {\n      transitionDuration,\n      transitionDelay\n    } = window.getComputedStyle(element);\n    const floatTransitionDuration = Number.parseFloat(transitionDuration);\n    const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found\n\n    if (!floatTransitionDuration && !floatTransitionDelay) {\n      return 0;\n    } // If multiple durations are defined, take the first\n\n\n    transitionDuration = transitionDuration.split(',')[0];\n    transitionDelay = transitionDelay.split(',')[0];\n    return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n  };\n\n  const triggerTransitionEnd = element => {\n    element.dispatchEvent(new Event(TRANSITION_END));\n  };\n\n  const isElement$1 = object => {\n    if (!object || typeof object !== 'object') {\n      return false;\n    }\n\n    if (typeof object.jquery !== 'undefined') {\n      object = object[0];\n    }\n\n    return typeof object.nodeType !== 'undefined';\n  };\n\n  const getElement = object => {\n    // it's a jQuery object or a node element\n    if (isElement$1(object)) {\n      return object.jquery ? object[0] : object;\n    }\n\n    if (typeof object === 'string' && object.length > 0) {\n      return document.querySelector(object);\n    }\n\n    return null;\n  };\n\n  const isVisible = element => {\n    if (!isElement$1(element) || element.getClientRects().length === 0) {\n      return false;\n    }\n\n    const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; // Handle `details` element as its content may falsie appear visible when it is closed\n\n    const closedDetails = element.closest('details:not([open])');\n\n    if (!closedDetails) {\n      return elementIsVisible;\n    }\n\n    if (closedDetails !== element) {\n      const summary = element.closest('summary');\n\n      if (summary && summary.parentNode !== closedDetails) {\n        return false;\n      }\n\n      if (summary === null) {\n        return false;\n      }\n    }\n\n    return elementIsVisible;\n  };\n\n  const isDisabled = element => {\n    if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n      return true;\n    }\n\n    if (element.classList.contains('disabled')) {\n      return true;\n    }\n\n    if (typeof element.disabled !== 'undefined') {\n      return element.disabled;\n    }\n\n    return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n  };\n\n  const findShadowRoot = element => {\n    if (!document.documentElement.attachShadow) {\n      return null;\n    } // Can find the shadow root otherwise it'll return the document\n\n\n    if (typeof element.getRootNode === 'function') {\n      const root = element.getRootNode();\n      return root instanceof ShadowRoot ? root : null;\n    }\n\n    if (element instanceof ShadowRoot) {\n      return element;\n    } // when we don't find a shadow root\n\n\n    if (!element.parentNode) {\n      return null;\n    }\n\n    return findShadowRoot(element.parentNode);\n  };\n\n  const noop = () => {};\n  /**\n   * Trick to restart an element's animation\n   *\n   * @param {HTMLElement} element\n   * @return void\n   *\n   * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n   */\n\n\n  const reflow = element => {\n    element.offsetHeight; // eslint-disable-line no-unused-expressions\n  };\n\n  const getjQuery = () => {\n    if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n      return window.jQuery;\n    }\n\n    return null;\n  };\n\n  const DOMContentLoadedCallbacks = [];\n\n  const onDOMContentLoaded = callback => {\n    if (document.readyState === 'loading') {\n      // add listener on the first call when the document is in loading state\n      if (!DOMContentLoadedCallbacks.length) {\n        document.addEventListener('DOMContentLoaded', () => {\n          for (const callback of DOMContentLoadedCallbacks) {\n            callback();\n          }\n        });\n      }\n\n      DOMContentLoadedCallbacks.push(callback);\n    } else {\n      callback();\n    }\n  };\n\n  const isRTL = () => document.documentElement.dir === 'rtl';\n\n  const defineJQueryPlugin = plugin => {\n    onDOMContentLoaded(() => {\n      const $ = getjQuery();\n      /* istanbul ignore if */\n\n      if ($) {\n        const name = plugin.NAME;\n        const JQUERY_NO_CONFLICT = $.fn[name];\n        $.fn[name] = plugin.jQueryInterface;\n        $.fn[name].Constructor = plugin;\n\n        $.fn[name].noConflict = () => {\n          $.fn[name] = JQUERY_NO_CONFLICT;\n          return plugin.jQueryInterface;\n        };\n      }\n    });\n  };\n\n  const execute = callback => {\n    if (typeof callback === 'function') {\n      callback();\n    }\n  };\n\n  const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n    if (!waitForTransition) {\n      execute(callback);\n      return;\n    }\n\n    const durationPadding = 5;\n    const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n    let called = false;\n\n    const handler = ({\n      target\n    }) => {\n      if (target !== transitionElement) {\n        return;\n      }\n\n      called = true;\n      transitionElement.removeEventListener(TRANSITION_END, handler);\n      execute(callback);\n    };\n\n    transitionElement.addEventListener(TRANSITION_END, handler);\n    setTimeout(() => {\n      if (!called) {\n        triggerTransitionEnd(transitionElement);\n      }\n    }, emulatedDuration);\n  };\n  /**\n   * Return the previous/next element of a list.\n   *\n   * @param {array} list    The list of elements\n   * @param activeElement   The active element\n   * @param shouldGetNext   Choose to get next or previous element\n   * @param isCycleAllowed\n   * @return {Element|elem} The proper element\n   */\n\n\n  const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n    const listLength = list.length;\n    let index = list.indexOf(activeElement); // if the element does not exist in the list return an element\n    // depending on the direction and if cycle is allowed\n\n    if (index === -1) {\n      return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n    }\n\n    index += shouldGetNext ? 1 : -1;\n\n    if (isCycleAllowed) {\n      index = (index + listLength) % listLength;\n    }\n\n    return list[Math.max(0, Math.min(index, listLength - 1))];\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/event-handler.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\n  const stripNameRegex = /\\..*/;\n  const stripUidRegex = /::\\d+$/;\n  const eventRegistry = {}; // Events storage\n\n  let uidEvent = 1;\n  const customEvents = {\n    mouseenter: 'mouseover',\n    mouseleave: 'mouseout'\n  };\n  const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n  /**\n   * Private methods\n   */\n\n  function makeEventUid(element, uid) {\n    return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n  }\n\n  function getElementEvents(element) {\n    const uid = makeEventUid(element);\n    element.uidEvent = uid;\n    eventRegistry[uid] = eventRegistry[uid] || {};\n    return eventRegistry[uid];\n  }\n\n  function bootstrapHandler(element, fn) {\n    return function handler(event) {\n      hydrateObj(event, {\n        delegateTarget: element\n      });\n\n      if (handler.oneOff) {\n        EventHandler.off(element, event.type, fn);\n      }\n\n      return fn.apply(element, [event]);\n    };\n  }\n\n  function bootstrapDelegationHandler(element, selector, fn) {\n    return function handler(event) {\n      const domElements = element.querySelectorAll(selector);\n\n      for (let {\n        target\n      } = event; target && target !== this; target = target.parentNode) {\n        for (const domElement of domElements) {\n          if (domElement !== target) {\n            continue;\n          }\n\n          hydrateObj(event, {\n            delegateTarget: target\n          });\n\n          if (handler.oneOff) {\n            EventHandler.off(element, event.type, selector, fn);\n          }\n\n          return fn.apply(target, [event]);\n        }\n      }\n    };\n  }\n\n  function findHandler(events, callable, delegationSelector = null) {\n    return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n  }\n\n  function normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n    const isDelegated = typeof handler === 'string'; // todo: tooltip passes `false` instead of selector, so we need to check\n\n    const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n    let typeEvent = getTypeEvent(originalTypeEvent);\n\n    if (!nativeEvents.has(typeEvent)) {\n      typeEvent = originalTypeEvent;\n    }\n\n    return [isDelegated, callable, typeEvent];\n  }\n\n  function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n    if (typeof originalTypeEvent !== 'string' || !element) {\n      return;\n    }\n\n    let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n    // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n\n    if (originalTypeEvent in customEvents) {\n      const wrapFunction = fn => {\n        return function (event) {\n          if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n            return fn.call(this, event);\n          }\n        };\n      };\n\n      callable = wrapFunction(callable);\n    }\n\n    const events = getElementEvents(element);\n    const handlers = events[typeEvent] || (events[typeEvent] = {});\n    const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n\n    if (previousFunction) {\n      previousFunction.oneOff = previousFunction.oneOff && oneOff;\n      return;\n    }\n\n    const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n    const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n    fn.delegationSelector = isDelegated ? handler : null;\n    fn.callable = callable;\n    fn.oneOff = oneOff;\n    fn.uidEvent = uid;\n    handlers[uid] = fn;\n    element.addEventListener(typeEvent, fn, isDelegated);\n  }\n\n  function removeHandler(element, events, typeEvent, handler, delegationSelector) {\n    const fn = findHandler(events[typeEvent], handler, delegationSelector);\n\n    if (!fn) {\n      return;\n    }\n\n    element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n    delete events[typeEvent][fn.uidEvent];\n  }\n\n  function removeNamespacedHandlers(element, events, typeEvent, namespace) {\n    const storeElementEvent = events[typeEvent] || {};\n\n    for (const handlerKey of Object.keys(storeElementEvent)) {\n      if (handlerKey.includes(namespace)) {\n        const event = storeElementEvent[handlerKey];\n        removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n      }\n    }\n  }\n\n  function getTypeEvent(event) {\n    // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n    event = event.replace(stripNameRegex, '');\n    return customEvents[event] || event;\n  }\n\n  const EventHandler = {\n    on(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, false);\n    },\n\n    one(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, true);\n    },\n\n    off(element, originalTypeEvent, handler, delegationFunction) {\n      if (typeof originalTypeEvent !== 'string' || !element) {\n        return;\n      }\n\n      const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n      const inNamespace = typeEvent !== originalTypeEvent;\n      const events = getElementEvents(element);\n      const storeElementEvent = events[typeEvent] || {};\n      const isNamespace = originalTypeEvent.startsWith('.');\n\n      if (typeof callable !== 'undefined') {\n        // Simplest case: handler is passed, remove that listener ONLY.\n        if (!Object.keys(storeElementEvent).length) {\n          return;\n        }\n\n        removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n        return;\n      }\n\n      if (isNamespace) {\n        for (const elementEvent of Object.keys(events)) {\n          removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n        }\n      }\n\n      for (const keyHandlers of Object.keys(storeElementEvent)) {\n        const handlerKey = keyHandlers.replace(stripUidRegex, '');\n\n        if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n          const event = storeElementEvent[keyHandlers];\n          removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n        }\n      }\n    },\n\n    trigger(element, event, args) {\n      if (typeof event !== 'string' || !element) {\n        return null;\n      }\n\n      const $ = getjQuery();\n      const typeEvent = getTypeEvent(event);\n      const inNamespace = event !== typeEvent;\n      let jQueryEvent = null;\n      let bubbles = true;\n      let nativeDispatch = true;\n      let defaultPrevented = false;\n\n      if (inNamespace && $) {\n        jQueryEvent = $.Event(event, args);\n        $(element).trigger(jQueryEvent);\n        bubbles = !jQueryEvent.isPropagationStopped();\n        nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n        defaultPrevented = jQueryEvent.isDefaultPrevented();\n      }\n\n      let evt = new Event(event, {\n        bubbles,\n        cancelable: true\n      });\n      evt = hydrateObj(evt, args);\n\n      if (defaultPrevented) {\n        evt.preventDefault();\n      }\n\n      if (nativeDispatch) {\n        element.dispatchEvent(evt);\n      }\n\n      if (evt.defaultPrevented && jQueryEvent) {\n        jQueryEvent.preventDefault();\n      }\n\n      return evt;\n    }\n\n  };\n\n  function hydrateObj(obj, meta) {\n    for (const [key, value] of Object.entries(meta || {})) {\n      try {\n        obj[key] = value;\n      } catch (_unused) {\n        Object.defineProperty(obj, key, {\n          configurable: true,\n\n          get() {\n            return value;\n          }\n\n        });\n      }\n    }\n\n    return obj;\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/data.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  /**\n   * Constants\n   */\n  const elementMap = new Map();\n  const Data = {\n    set(element, key, instance) {\n      if (!elementMap.has(element)) {\n        elementMap.set(element, new Map());\n      }\n\n      const instanceMap = elementMap.get(element); // make it clear we only want one instance per element\n      // can be removed later when multiple key/instances are fine to be used\n\n      if (!instanceMap.has(key) && instanceMap.size !== 0) {\n        // eslint-disable-next-line no-console\n        console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n        return;\n      }\n\n      instanceMap.set(key, instance);\n    },\n\n    get(element, key) {\n      if (elementMap.has(element)) {\n        return elementMap.get(element).get(key) || null;\n      }\n\n      return null;\n    },\n\n    remove(element, key) {\n      if (!elementMap.has(element)) {\n        return;\n      }\n\n      const instanceMap = elementMap.get(element);\n      instanceMap.delete(key); // free up element references if there are no instances left for an element\n\n      if (instanceMap.size === 0) {\n        elementMap.delete(element);\n      }\n    }\n\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/manipulator.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  function normalizeData(value) {\n    if (value === 'true') {\n      return true;\n    }\n\n    if (value === 'false') {\n      return false;\n    }\n\n    if (value === Number(value).toString()) {\n      return Number(value);\n    }\n\n    if (value === '' || value === 'null') {\n      return null;\n    }\n\n    if (typeof value !== 'string') {\n      return value;\n    }\n\n    try {\n      return JSON.parse(decodeURIComponent(value));\n    } catch (_unused) {\n      return value;\n    }\n  }\n\n  function normalizeDataKey(key) {\n    return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n  }\n\n  const Manipulator = {\n    setDataAttribute(element, key, value) {\n      element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n    },\n\n    removeDataAttribute(element, key) {\n      element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n    },\n\n    getDataAttributes(element) {\n      if (!element) {\n        return {};\n      }\n\n      const attributes = {};\n      const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n\n      for (const key of bsKeys) {\n        let pureKey = key.replace(/^bs/, '');\n        pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n        attributes[pureKey] = normalizeData(element.dataset[key]);\n      }\n\n      return attributes;\n    },\n\n    getDataAttribute(element, key) {\n      return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n    }\n\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/config.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Class definition\n   */\n\n  class Config {\n    // Getters\n    static get Default() {\n      return {};\n    }\n\n    static get DefaultType() {\n      return {};\n    }\n\n    static get NAME() {\n      throw new Error('You have to implement the static method \"NAME\", for each component!');\n    }\n\n    _getConfig(config) {\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    }\n\n    _configAfterMerge(config) {\n      return config;\n    }\n\n    _mergeConfigObj(config, element) {\n      const jsonConfig = isElement$1(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n      return { ...this.constructor.Default,\n        ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n        ...(isElement$1(element) ? Manipulator.getDataAttributes(element) : {}),\n        ...(typeof config === 'object' ? config : {})\n      };\n    }\n\n    _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n      for (const property of Object.keys(configTypes)) {\n        const expectedTypes = configTypes[property];\n        const value = config[property];\n        const valueType = isElement$1(value) ? 'element' : toType(value);\n\n        if (!new RegExp(expectedTypes).test(valueType)) {\n          throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n        }\n      }\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): base-component.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const VERSION = '5.2.3';\n  /**\n   * Class definition\n   */\n\n  class BaseComponent extends Config {\n    constructor(element, config) {\n      super();\n      element = getElement(element);\n\n      if (!element) {\n        return;\n      }\n\n      this._element = element;\n      this._config = this._getConfig(config);\n      Data.set(this._element, this.constructor.DATA_KEY, this);\n    } // Public\n\n\n    dispose() {\n      Data.remove(this._element, this.constructor.DATA_KEY);\n      EventHandler.off(this._element, this.constructor.EVENT_KEY);\n\n      for (const propertyName of Object.getOwnPropertyNames(this)) {\n        this[propertyName] = null;\n      }\n    }\n\n    _queueCallback(callback, element, isAnimated = true) {\n      executeAfterTransition(callback, element, isAnimated);\n    }\n\n    _getConfig(config) {\n      config = this._mergeConfigObj(config, this._element);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    } // Static\n\n\n    static getInstance(element) {\n      return Data.get(getElement(element), this.DATA_KEY);\n    }\n\n    static getOrCreateInstance(element, config = {}) {\n      return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n    }\n\n    static get VERSION() {\n      return VERSION;\n    }\n\n    static get DATA_KEY() {\n      return `bs.${this.NAME}`;\n    }\n\n    static get EVENT_KEY() {\n      return `.${this.DATA_KEY}`;\n    }\n\n    static eventName(name) {\n      return `${name}${this.EVENT_KEY}`;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/component-functions.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  const enableDismissTrigger = (component, method = 'hide') => {\n    const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n    const name = component.NAME;\n    EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n      if (['A', 'AREA'].includes(this.tagName)) {\n        event.preventDefault();\n      }\n\n      if (isDisabled(this)) {\n        return;\n      }\n\n      const target = getElementFromSelector(this) || this.closest(`.${name}`);\n      const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n\n      instance[method]();\n    });\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): alert.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$f = 'alert';\n  const DATA_KEY$a = 'bs.alert';\n  const EVENT_KEY$b = `.${DATA_KEY$a}`;\n  const EVENT_CLOSE = `close${EVENT_KEY$b}`;\n  const EVENT_CLOSED = `closed${EVENT_KEY$b}`;\n  const CLASS_NAME_FADE$5 = 'fade';\n  const CLASS_NAME_SHOW$8 = 'show';\n  /**\n   * Class definition\n   */\n\n  class Alert extends BaseComponent {\n    // Getters\n    static get NAME() {\n      return NAME$f;\n    } // Public\n\n\n    close() {\n      const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n\n      if (closeEvent.defaultPrevented) {\n        return;\n      }\n\n      this._element.classList.remove(CLASS_NAME_SHOW$8);\n\n      const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n\n      this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n    } // Private\n\n\n    _destroyElement() {\n      this._element.remove();\n\n      EventHandler.trigger(this._element, EVENT_CLOSED);\n      this.dispose();\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Alert.getOrCreateInstance(this);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  enableDismissTrigger(Alert, 'close');\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Alert);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): button.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$e = 'button';\n  const DATA_KEY$9 = 'bs.button';\n  const EVENT_KEY$a = `.${DATA_KEY$9}`;\n  const DATA_API_KEY$6 = '.data-api';\n  const CLASS_NAME_ACTIVE$3 = 'active';\n  const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\n  const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n  /**\n   * Class definition\n   */\n\n  class Button extends BaseComponent {\n    // Getters\n    static get NAME() {\n      return NAME$e;\n    } // Public\n\n\n    toggle() {\n      // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n      this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Button.getOrCreateInstance(this);\n\n        if (config === 'toggle') {\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n    event.preventDefault();\n    const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n    const data = Button.getOrCreateInstance(button);\n    data.toggle();\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Button);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/selector-engine.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const SelectorEngine = {\n    find(selector, element = document.documentElement) {\n      return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n    },\n\n    findOne(selector, element = document.documentElement) {\n      return Element.prototype.querySelector.call(element, selector);\n    },\n\n    children(element, selector) {\n      return [].concat(...element.children).filter(child => child.matches(selector));\n    },\n\n    parents(element, selector) {\n      const parents = [];\n      let ancestor = element.parentNode.closest(selector);\n\n      while (ancestor) {\n        parents.push(ancestor);\n        ancestor = ancestor.parentNode.closest(selector);\n      }\n\n      return parents;\n    },\n\n    prev(element, selector) {\n      let previous = element.previousElementSibling;\n\n      while (previous) {\n        if (previous.matches(selector)) {\n          return [previous];\n        }\n\n        previous = previous.previousElementSibling;\n      }\n\n      return [];\n    },\n\n    // TODO: this is now unused; remove later along with prev()\n    next(element, selector) {\n      let next = element.nextElementSibling;\n\n      while (next) {\n        if (next.matches(selector)) {\n          return [next];\n        }\n\n        next = next.nextElementSibling;\n      }\n\n      return [];\n    },\n\n    focusableChildren(element) {\n      const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n      return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n    }\n\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/swipe.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$d = 'swipe';\n  const EVENT_KEY$9 = '.bs.swipe';\n  const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\n  const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\n  const EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\n  const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\n  const EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\n  const POINTER_TYPE_TOUCH = 'touch';\n  const POINTER_TYPE_PEN = 'pen';\n  const CLASS_NAME_POINTER_EVENT = 'pointer-event';\n  const SWIPE_THRESHOLD = 40;\n  const Default$c = {\n    endCallback: null,\n    leftCallback: null,\n    rightCallback: null\n  };\n  const DefaultType$c = {\n    endCallback: '(function|null)',\n    leftCallback: '(function|null)',\n    rightCallback: '(function|null)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Swipe extends Config {\n    constructor(element, config) {\n      super();\n      this._element = element;\n\n      if (!element || !Swipe.isSupported()) {\n        return;\n      }\n\n      this._config = this._getConfig(config);\n      this._deltaX = 0;\n      this._supportPointerEvents = Boolean(window.PointerEvent);\n\n      this._initEvents();\n    } // Getters\n\n\n    static get Default() {\n      return Default$c;\n    }\n\n    static get DefaultType() {\n      return DefaultType$c;\n    }\n\n    static get NAME() {\n      return NAME$d;\n    } // Public\n\n\n    dispose() {\n      EventHandler.off(this._element, EVENT_KEY$9);\n    } // Private\n\n\n    _start(event) {\n      if (!this._supportPointerEvents) {\n        this._deltaX = event.touches[0].clientX;\n        return;\n      }\n\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX;\n      }\n    }\n\n    _end(event) {\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX - this._deltaX;\n      }\n\n      this._handleSwipe();\n\n      execute(this._config.endCallback);\n    }\n\n    _move(event) {\n      this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n    }\n\n    _handleSwipe() {\n      const absDeltaX = Math.abs(this._deltaX);\n\n      if (absDeltaX <= SWIPE_THRESHOLD) {\n        return;\n      }\n\n      const direction = absDeltaX / this._deltaX;\n      this._deltaX = 0;\n\n      if (!direction) {\n        return;\n      }\n\n      execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n    }\n\n    _initEvents() {\n      if (this._supportPointerEvents) {\n        EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n        EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n\n        this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n      } else {\n        EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n        EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n        EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n      }\n    }\n\n    _eventIsPointerPenTouch(event) {\n      return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n    } // Static\n\n\n    static isSupported() {\n      return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): carousel.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$c = 'carousel';\n  const DATA_KEY$8 = 'bs.carousel';\n  const EVENT_KEY$8 = `.${DATA_KEY$8}`;\n  const DATA_API_KEY$5 = '.data-api';\n  const ARROW_LEFT_KEY$1 = 'ArrowLeft';\n  const ARROW_RIGHT_KEY$1 = 'ArrowRight';\n  const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\n  const ORDER_NEXT = 'next';\n  const ORDER_PREV = 'prev';\n  const DIRECTION_LEFT = 'left';\n  const DIRECTION_RIGHT = 'right';\n  const EVENT_SLIDE = `slide${EVENT_KEY$8}`;\n  const EVENT_SLID = `slid${EVENT_KEY$8}`;\n  const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\n  const EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\n  const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\n  const EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\n  const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\n  const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\n  const CLASS_NAME_CAROUSEL = 'carousel';\n  const CLASS_NAME_ACTIVE$2 = 'active';\n  const CLASS_NAME_SLIDE = 'slide';\n  const CLASS_NAME_END = 'carousel-item-end';\n  const CLASS_NAME_START = 'carousel-item-start';\n  const CLASS_NAME_NEXT = 'carousel-item-next';\n  const CLASS_NAME_PREV = 'carousel-item-prev';\n  const SELECTOR_ACTIVE = '.active';\n  const SELECTOR_ITEM = '.carousel-item';\n  const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\n  const SELECTOR_ITEM_IMG = '.carousel-item img';\n  const SELECTOR_INDICATORS = '.carousel-indicators';\n  const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\n  const SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\n  const KEY_TO_DIRECTION = {\n    [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n    [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n  };\n  const Default$b = {\n    interval: 5000,\n    keyboard: true,\n    pause: 'hover',\n    ride: false,\n    touch: true,\n    wrap: true\n  };\n  const DefaultType$b = {\n    interval: '(number|boolean)',\n    // TODO:v6 remove boolean support\n    keyboard: 'boolean',\n    pause: '(string|boolean)',\n    ride: '(boolean|string)',\n    touch: 'boolean',\n    wrap: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Carousel extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._interval = null;\n      this._activeElement = null;\n      this._isSliding = false;\n      this.touchTimeout = null;\n      this._swipeHelper = null;\n      this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n\n      this._addEventListeners();\n\n      if (this._config.ride === CLASS_NAME_CAROUSEL) {\n        this.cycle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default$b;\n    }\n\n    static get DefaultType() {\n      return DefaultType$b;\n    }\n\n    static get NAME() {\n      return NAME$c;\n    } // Public\n\n\n    next() {\n      this._slide(ORDER_NEXT);\n    }\n\n    nextWhenVisible() {\n      // FIXME TODO use `document.visibilityState`\n      // Don't call next when the page isn't visible\n      // or the carousel or its parent isn't visible\n      if (!document.hidden && isVisible(this._element)) {\n        this.next();\n      }\n    }\n\n    prev() {\n      this._slide(ORDER_PREV);\n    }\n\n    pause() {\n      if (this._isSliding) {\n        triggerTransitionEnd(this._element);\n      }\n\n      this._clearInterval();\n    }\n\n    cycle() {\n      this._clearInterval();\n\n      this._updateInterval();\n\n      this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n    }\n\n    _maybeEnableCycle() {\n      if (!this._config.ride) {\n        return;\n      }\n\n      if (this._isSliding) {\n        EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n        return;\n      }\n\n      this.cycle();\n    }\n\n    to(index) {\n      const items = this._getItems();\n\n      if (index > items.length - 1 || index < 0) {\n        return;\n      }\n\n      if (this._isSliding) {\n        EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n        return;\n      }\n\n      const activeIndex = this._getItemIndex(this._getActive());\n\n      if (activeIndex === index) {\n        return;\n      }\n\n      const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n\n      this._slide(order, items[index]);\n    }\n\n    dispose() {\n      if (this._swipeHelper) {\n        this._swipeHelper.dispose();\n      }\n\n      super.dispose();\n    } // Private\n\n\n    _configAfterMerge(config) {\n      config.defaultInterval = config.interval;\n      return config;\n    }\n\n    _addEventListeners() {\n      if (this._config.keyboard) {\n        EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n      }\n\n      if (this._config.pause === 'hover') {\n        EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n        EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n      }\n\n      if (this._config.touch && Swipe.isSupported()) {\n        this._addTouchEventListeners();\n      }\n    }\n\n    _addTouchEventListeners() {\n      for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n        EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n      }\n\n      const endCallBack = () => {\n        if (this._config.pause !== 'hover') {\n          return;\n        } // If it's a touch-enabled device, mouseenter/leave are fired as\n        // part of the mouse compatibility events on first tap - the carousel\n        // would stop cycling until user tapped out of it;\n        // here, we listen for touchend, explicitly pause the carousel\n        // (as if it's the second time we tap on it, mouseenter compat event\n        // is NOT fired) and after a timeout (to allow for mouse compatibility\n        // events to fire) we explicitly restart cycling\n\n\n        this.pause();\n\n        if (this.touchTimeout) {\n          clearTimeout(this.touchTimeout);\n        }\n\n        this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n      };\n\n      const swipeConfig = {\n        leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n        rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n        endCallback: endCallBack\n      };\n      this._swipeHelper = new Swipe(this._element, swipeConfig);\n    }\n\n    _keydown(event) {\n      if (/input|textarea/i.test(event.target.tagName)) {\n        return;\n      }\n\n      const direction = KEY_TO_DIRECTION[event.key];\n\n      if (direction) {\n        event.preventDefault();\n\n        this._slide(this._directionToOrder(direction));\n      }\n    }\n\n    _getItemIndex(element) {\n      return this._getItems().indexOf(element);\n    }\n\n    _setActiveIndicatorElement(index) {\n      if (!this._indicatorsElement) {\n        return;\n      }\n\n      const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n      activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n      activeIndicator.removeAttribute('aria-current');\n      const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n\n      if (newActiveIndicator) {\n        newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n        newActiveIndicator.setAttribute('aria-current', 'true');\n      }\n    }\n\n    _updateInterval() {\n      const element = this._activeElement || this._getActive();\n\n      if (!element) {\n        return;\n      }\n\n      const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n      this._config.interval = elementInterval || this._config.defaultInterval;\n    }\n\n    _slide(order, element = null) {\n      if (this._isSliding) {\n        return;\n      }\n\n      const activeElement = this._getActive();\n\n      const isNext = order === ORDER_NEXT;\n      const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n\n      if (nextElement === activeElement) {\n        return;\n      }\n\n      const nextElementIndex = this._getItemIndex(nextElement);\n\n      const triggerEvent = eventName => {\n        return EventHandler.trigger(this._element, eventName, {\n          relatedTarget: nextElement,\n          direction: this._orderToDirection(order),\n          from: this._getItemIndex(activeElement),\n          to: nextElementIndex\n        });\n      };\n\n      const slideEvent = triggerEvent(EVENT_SLIDE);\n\n      if (slideEvent.defaultPrevented) {\n        return;\n      }\n\n      if (!activeElement || !nextElement) {\n        // Some weirdness is happening, so we bail\n        // todo: change tests that use empty divs to avoid this check\n        return;\n      }\n\n      const isCycling = Boolean(this._interval);\n      this.pause();\n      this._isSliding = true;\n\n      this._setActiveIndicatorElement(nextElementIndex);\n\n      this._activeElement = nextElement;\n      const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n      const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n      nextElement.classList.add(orderClassName);\n      reflow(nextElement);\n      activeElement.classList.add(directionalClassName);\n      nextElement.classList.add(directionalClassName);\n\n      const completeCallBack = () => {\n        nextElement.classList.remove(directionalClassName, orderClassName);\n        nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n        activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n        this._isSliding = false;\n        triggerEvent(EVENT_SLID);\n      };\n\n      this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n\n      if (isCycling) {\n        this.cycle();\n      }\n    }\n\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_SLIDE);\n    }\n\n    _getActive() {\n      return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n    }\n\n    _getItems() {\n      return SelectorEngine.find(SELECTOR_ITEM, this._element);\n    }\n\n    _clearInterval() {\n      if (this._interval) {\n        clearInterval(this._interval);\n        this._interval = null;\n      }\n    }\n\n    _directionToOrder(direction) {\n      if (isRTL()) {\n        return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n      }\n\n      return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n    }\n\n    _orderToDirection(order) {\n      if (isRTL()) {\n        return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n      }\n\n      return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Carousel.getOrCreateInstance(this, config);\n\n        if (typeof config === 'number') {\n          data.to(config);\n          return;\n        }\n\n        if (typeof config === 'string') {\n          if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n    const target = getElementFromSelector(this);\n\n    if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n      return;\n    }\n\n    event.preventDefault();\n    const carousel = Carousel.getOrCreateInstance(target);\n    const slideIndex = this.getAttribute('data-bs-slide-to');\n\n    if (slideIndex) {\n      carousel.to(slideIndex);\n\n      carousel._maybeEnableCycle();\n\n      return;\n    }\n\n    if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n      carousel.next();\n\n      carousel._maybeEnableCycle();\n\n      return;\n    }\n\n    carousel.prev();\n\n    carousel._maybeEnableCycle();\n  });\n  EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n    const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n\n    for (const carousel of carousels) {\n      Carousel.getOrCreateInstance(carousel);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Carousel);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): collapse.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$b = 'collapse';\n  const DATA_KEY$7 = 'bs.collapse';\n  const EVENT_KEY$7 = `.${DATA_KEY$7}`;\n  const DATA_API_KEY$4 = '.data-api';\n  const EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\n  const EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\n  const EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\n  const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\n  const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\n  const CLASS_NAME_SHOW$7 = 'show';\n  const CLASS_NAME_COLLAPSE = 'collapse';\n  const CLASS_NAME_COLLAPSING = 'collapsing';\n  const CLASS_NAME_COLLAPSED = 'collapsed';\n  const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\n  const CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\n  const WIDTH = 'width';\n  const HEIGHT = 'height';\n  const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\n  const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\n  const Default$a = {\n    parent: null,\n    toggle: true\n  };\n  const DefaultType$a = {\n    parent: '(null|element)',\n    toggle: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Collapse extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._isTransitioning = false;\n      this._triggerArray = [];\n      const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n\n      for (const elem of toggleList) {\n        const selector = getSelectorFromElement(elem);\n        const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n\n        if (selector !== null && filterElement.length) {\n          this._triggerArray.push(elem);\n        }\n      }\n\n      this._initializeChildren();\n\n      if (!this._config.parent) {\n        this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n      }\n\n      if (this._config.toggle) {\n        this.toggle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default$a;\n    }\n\n    static get DefaultType() {\n      return DefaultType$a;\n    }\n\n    static get NAME() {\n      return NAME$b;\n    } // Public\n\n\n    toggle() {\n      if (this._isShown()) {\n        this.hide();\n      } else {\n        this.show();\n      }\n    }\n\n    show() {\n      if (this._isTransitioning || this._isShown()) {\n        return;\n      }\n\n      let activeChildren = []; // find active children\n\n      if (this._config.parent) {\n        activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n          toggle: false\n        }));\n      }\n\n      if (activeChildren.length && activeChildren[0]._isTransitioning) {\n        return;\n      }\n\n      const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n\n      for (const activeInstance of activeChildren) {\n        activeInstance.hide();\n      }\n\n      const dimension = this._getDimension();\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSE);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n      this._element.style[dimension] = 0;\n\n      this._addAriaAndCollapsedClass(this._triggerArray, true);\n\n      this._isTransitioning = true;\n\n      const complete = () => {\n        this._isTransitioning = false;\n\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n        this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n\n        this._element.style[dimension] = '';\n        EventHandler.trigger(this._element, EVENT_SHOWN$6);\n      };\n\n      const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n      const scrollSize = `scroll${capitalizedDimension}`;\n\n      this._queueCallback(complete, this._element, true);\n\n      this._element.style[dimension] = `${this._element[scrollSize]}px`;\n    }\n\n    hide() {\n      if (this._isTransitioning || !this._isShown()) {\n        return;\n      }\n\n      const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n\n      const dimension = this._getDimension();\n\n      this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n      reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n\n      for (const trigger of this._triggerArray) {\n        const element = getElementFromSelector(trigger);\n\n        if (element && !this._isShown(element)) {\n          this._addAriaAndCollapsedClass([trigger], false);\n        }\n      }\n\n      this._isTransitioning = true;\n\n      const complete = () => {\n        this._isTransitioning = false;\n\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n        this._element.classList.add(CLASS_NAME_COLLAPSE);\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n      };\n\n      this._element.style[dimension] = '';\n\n      this._queueCallback(complete, this._element, true);\n    }\n\n    _isShown(element = this._element) {\n      return element.classList.contains(CLASS_NAME_SHOW$7);\n    } // Private\n\n\n    _configAfterMerge(config) {\n      config.toggle = Boolean(config.toggle); // Coerce string values\n\n      config.parent = getElement(config.parent);\n      return config;\n    }\n\n    _getDimension() {\n      return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n    }\n\n    _initializeChildren() {\n      if (!this._config.parent) {\n        return;\n      }\n\n      const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n\n      for (const element of children) {\n        const selected = getElementFromSelector(element);\n\n        if (selected) {\n          this._addAriaAndCollapsedClass([element], this._isShown(selected));\n        }\n      }\n    }\n\n    _getFirstLevelChildren(selector) {\n      const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); // remove children if greater depth\n\n      return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n    }\n\n    _addAriaAndCollapsedClass(triggerArray, isOpen) {\n      if (!triggerArray.length) {\n        return;\n      }\n\n      for (const element of triggerArray) {\n        element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n        element.setAttribute('aria-expanded', isOpen);\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      const _config = {};\n\n      if (typeof config === 'string' && /show|hide/.test(config)) {\n        _config.toggle = false;\n      }\n\n      return this.each(function () {\n        const data = Collapse.getOrCreateInstance(this, _config);\n\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n    // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n    if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n      event.preventDefault();\n    }\n\n    const selector = getSelectorFromElement(this);\n    const selectorElements = SelectorEngine.find(selector);\n\n    for (const element of selectorElements) {\n      Collapse.getOrCreateInstance(element, {\n        toggle: false\n      }).toggle();\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Collapse);\n\n  var top = 'top';\n  var bottom = 'bottom';\n  var right = 'right';\n  var left = 'left';\n  var auto = 'auto';\n  var basePlacements = [top, bottom, right, left];\n  var start = 'start';\n  var end = 'end';\n  var clippingParents = 'clippingParents';\n  var viewport = 'viewport';\n  var popper = 'popper';\n  var reference = 'reference';\n  var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n    return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n  }, []);\n  var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n    return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n  }, []); // modifiers that need to read the DOM\n\n  var beforeRead = 'beforeRead';\n  var read = 'read';\n  var afterRead = 'afterRead'; // pure-logic modifiers\n\n  var beforeMain = 'beforeMain';\n  var main = 'main';\n  var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\n  var beforeWrite = 'beforeWrite';\n  var write = 'write';\n  var afterWrite = 'afterWrite';\n  var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];\n\n  function getNodeName(element) {\n    return element ? (element.nodeName || '').toLowerCase() : null;\n  }\n\n  function getWindow(node) {\n    if (node == null) {\n      return window;\n    }\n\n    if (node.toString() !== '[object Window]') {\n      var ownerDocument = node.ownerDocument;\n      return ownerDocument ? ownerDocument.defaultView || window : window;\n    }\n\n    return node;\n  }\n\n  function isElement(node) {\n    var OwnElement = getWindow(node).Element;\n    return node instanceof OwnElement || node instanceof Element;\n  }\n\n  function isHTMLElement(node) {\n    var OwnElement = getWindow(node).HTMLElement;\n    return node instanceof OwnElement || node instanceof HTMLElement;\n  }\n\n  function isShadowRoot(node) {\n    // IE 11 has no ShadowRoot\n    if (typeof ShadowRoot === 'undefined') {\n      return false;\n    }\n\n    var OwnElement = getWindow(node).ShadowRoot;\n    return node instanceof OwnElement || node instanceof ShadowRoot;\n  }\n\n  // and applies them to the HTMLElements such as popper and arrow\n\n  function applyStyles(_ref) {\n    var state = _ref.state;\n    Object.keys(state.elements).forEach(function (name) {\n      var style = state.styles[name] || {};\n      var attributes = state.attributes[name] || {};\n      var element = state.elements[name]; // arrow is optional + virtual elements\n\n      if (!isHTMLElement(element) || !getNodeName(element)) {\n        return;\n      } // Flow doesn't support to extend this property, but it's the most\n      // effective way to apply styles to an HTMLElement\n      // $FlowFixMe[cannot-write]\n\n\n      Object.assign(element.style, style);\n      Object.keys(attributes).forEach(function (name) {\n        var value = attributes[name];\n\n        if (value === false) {\n          element.removeAttribute(name);\n        } else {\n          element.setAttribute(name, value === true ? '' : value);\n        }\n      });\n    });\n  }\n\n  function effect$2(_ref2) {\n    var state = _ref2.state;\n    var initialStyles = {\n      popper: {\n        position: state.options.strategy,\n        left: '0',\n        top: '0',\n        margin: '0'\n      },\n      arrow: {\n        position: 'absolute'\n      },\n      reference: {}\n    };\n    Object.assign(state.elements.popper.style, initialStyles.popper);\n    state.styles = initialStyles;\n\n    if (state.elements.arrow) {\n      Object.assign(state.elements.arrow.style, initialStyles.arrow);\n    }\n\n    return function () {\n      Object.keys(state.elements).forEach(function (name) {\n        var element = state.elements[name];\n        var attributes = state.attributes[name] || {};\n        var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n        var style = styleProperties.reduce(function (style, property) {\n          style[property] = '';\n          return style;\n        }, {}); // arrow is optional + virtual elements\n\n        if (!isHTMLElement(element) || !getNodeName(element)) {\n          return;\n        }\n\n        Object.assign(element.style, style);\n        Object.keys(attributes).forEach(function (attribute) {\n          element.removeAttribute(attribute);\n        });\n      });\n    };\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const applyStyles$1 = {\n    name: 'applyStyles',\n    enabled: true,\n    phase: 'write',\n    fn: applyStyles,\n    effect: effect$2,\n    requires: ['computeStyles']\n  };\n\n  function getBasePlacement(placement) {\n    return placement.split('-')[0];\n  }\n\n  var max = Math.max;\n  var min = Math.min;\n  var round = Math.round;\n\n  function getUAString() {\n    var uaData = navigator.userAgentData;\n\n    if (uaData != null && uaData.brands) {\n      return uaData.brands.map(function (item) {\n        return item.brand + \"/\" + item.version;\n      }).join(' ');\n    }\n\n    return navigator.userAgent;\n  }\n\n  function isLayoutViewport() {\n    return !/^((?!chrome|android).)*safari/i.test(getUAString());\n  }\n\n  function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n    if (includeScale === void 0) {\n      includeScale = false;\n    }\n\n    if (isFixedStrategy === void 0) {\n      isFixedStrategy = false;\n    }\n\n    var clientRect = element.getBoundingClientRect();\n    var scaleX = 1;\n    var scaleY = 1;\n\n    if (includeScale && isHTMLElement(element)) {\n      scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n      scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n    }\n\n    var _ref = isElement(element) ? getWindow(element) : window,\n        visualViewport = _ref.visualViewport;\n\n    var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n    var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n    var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n    var width = clientRect.width / scaleX;\n    var height = clientRect.height / scaleY;\n    return {\n      width: width,\n      height: height,\n      top: y,\n      right: x + width,\n      bottom: y + height,\n      left: x,\n      x: x,\n      y: y\n    };\n  }\n\n  // means it doesn't take into account transforms.\n\n  function getLayoutRect(element) {\n    var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n    // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n    var width = element.offsetWidth;\n    var height = element.offsetHeight;\n\n    if (Math.abs(clientRect.width - width) <= 1) {\n      width = clientRect.width;\n    }\n\n    if (Math.abs(clientRect.height - height) <= 1) {\n      height = clientRect.height;\n    }\n\n    return {\n      x: element.offsetLeft,\n      y: element.offsetTop,\n      width: width,\n      height: height\n    };\n  }\n\n  function contains(parent, child) {\n    var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n    if (parent.contains(child)) {\n      return true;\n    } // then fallback to custom implementation with Shadow DOM support\n    else if (rootNode && isShadowRoot(rootNode)) {\n        var next = child;\n\n        do {\n          if (next && parent.isSameNode(next)) {\n            return true;\n          } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n          next = next.parentNode || next.host;\n        } while (next);\n      } // Give up, the result is false\n\n\n    return false;\n  }\n\n  function getComputedStyle$1(element) {\n    return getWindow(element).getComputedStyle(element);\n  }\n\n  function isTableElement(element) {\n    return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n  }\n\n  function getDocumentElement(element) {\n    // $FlowFixMe[incompatible-return]: assume body is always available\n    return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n    element.document) || window.document).documentElement;\n  }\n\n  function getParentNode(element) {\n    if (getNodeName(element) === 'html') {\n      return element;\n    }\n\n    return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n      // $FlowFixMe[incompatible-return]\n      // $FlowFixMe[prop-missing]\n      element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n      element.parentNode || ( // DOM Element detected\n      isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n      // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n      getDocumentElement(element) // fallback\n\n    );\n  }\n\n  function getTrueOffsetParent(element) {\n    if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n    getComputedStyle$1(element).position === 'fixed') {\n      return null;\n    }\n\n    return element.offsetParent;\n  } // `.offsetParent` reports `null` for fixed elements, while absolute elements\n  // return the containing block\n\n\n  function getContainingBlock(element) {\n    var isFirefox = /firefox/i.test(getUAString());\n    var isIE = /Trident/i.test(getUAString());\n\n    if (isIE && isHTMLElement(element)) {\n      // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n      var elementCss = getComputedStyle$1(element);\n\n      if (elementCss.position === 'fixed') {\n        return null;\n      }\n    }\n\n    var currentNode = getParentNode(element);\n\n    if (isShadowRoot(currentNode)) {\n      currentNode = currentNode.host;\n    }\n\n    while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n      var css = getComputedStyle$1(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n      // create a containing block.\n      // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n      if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n        return currentNode;\n      } else {\n        currentNode = currentNode.parentNode;\n      }\n    }\n\n    return null;\n  } // Gets the closest ancestor positioned element. Handles some edge cases,\n  // such as table ancestors and cross browser bugs.\n\n\n  function getOffsetParent(element) {\n    var window = getWindow(element);\n    var offsetParent = getTrueOffsetParent(element);\n\n    while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === 'static') {\n      offsetParent = getTrueOffsetParent(offsetParent);\n    }\n\n    if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle$1(offsetParent).position === 'static')) {\n      return window;\n    }\n\n    return offsetParent || getContainingBlock(element) || window;\n  }\n\n  function getMainAxisFromPlacement(placement) {\n    return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n  }\n\n  function within(min$1, value, max$1) {\n    return max(min$1, min(value, max$1));\n  }\n  function withinMaxClamp(min, value, max) {\n    var v = within(min, value, max);\n    return v > max ? max : v;\n  }\n\n  function getFreshSideObject() {\n    return {\n      top: 0,\n      right: 0,\n      bottom: 0,\n      left: 0\n    };\n  }\n\n  function mergePaddingObject(paddingObject) {\n    return Object.assign({}, getFreshSideObject(), paddingObject);\n  }\n\n  function expandToHashMap(value, keys) {\n    return keys.reduce(function (hashMap, key) {\n      hashMap[key] = value;\n      return hashMap;\n    }, {});\n  }\n\n  var toPaddingObject = function toPaddingObject(padding, state) {\n    padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n      placement: state.placement\n    })) : padding;\n    return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n  };\n\n  function arrow(_ref) {\n    var _state$modifiersData$;\n\n    var state = _ref.state,\n        name = _ref.name,\n        options = _ref.options;\n    var arrowElement = state.elements.arrow;\n    var popperOffsets = state.modifiersData.popperOffsets;\n    var basePlacement = getBasePlacement(state.placement);\n    var axis = getMainAxisFromPlacement(basePlacement);\n    var isVertical = [left, right].indexOf(basePlacement) >= 0;\n    var len = isVertical ? 'height' : 'width';\n\n    if (!arrowElement || !popperOffsets) {\n      return;\n    }\n\n    var paddingObject = toPaddingObject(options.padding, state);\n    var arrowRect = getLayoutRect(arrowElement);\n    var minProp = axis === 'y' ? top : left;\n    var maxProp = axis === 'y' ? bottom : right;\n    var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n    var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n    var arrowOffsetParent = getOffsetParent(arrowElement);\n    var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n    var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n    // outside of the popper bounds\n\n    var min = paddingObject[minProp];\n    var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n    var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n    var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n    var axisProp = axis;\n    state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n  }\n\n  function effect$1(_ref2) {\n    var state = _ref2.state,\n        options = _ref2.options;\n    var _options$element = options.element,\n        arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n    if (arrowElement == null) {\n      return;\n    } // CSS selector\n\n\n    if (typeof arrowElement === 'string') {\n      arrowElement = state.elements.popper.querySelector(arrowElement);\n\n      if (!arrowElement) {\n        return;\n      }\n    }\n\n    if (!contains(state.elements.popper, arrowElement)) {\n\n      return;\n    }\n\n    state.elements.arrow = arrowElement;\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const arrow$1 = {\n    name: 'arrow',\n    enabled: true,\n    phase: 'main',\n    fn: arrow,\n    effect: effect$1,\n    requires: ['popperOffsets'],\n    requiresIfExists: ['preventOverflow']\n  };\n\n  function getVariation(placement) {\n    return placement.split('-')[1];\n  }\n\n  var unsetSides = {\n    top: 'auto',\n    right: 'auto',\n    bottom: 'auto',\n    left: 'auto'\n  }; // Round the offsets to the nearest suitable subpixel based on the DPR.\n  // Zooming can change the DPR, but it seems to report a value that will\n  // cleanly divide the values into the appropriate subpixels.\n\n  function roundOffsetsByDPR(_ref) {\n    var x = _ref.x,\n        y = _ref.y;\n    var win = window;\n    var dpr = win.devicePixelRatio || 1;\n    return {\n      x: round(x * dpr) / dpr || 0,\n      y: round(y * dpr) / dpr || 0\n    };\n  }\n\n  function mapToStyles(_ref2) {\n    var _Object$assign2;\n\n    var popper = _ref2.popper,\n        popperRect = _ref2.popperRect,\n        placement = _ref2.placement,\n        variation = _ref2.variation,\n        offsets = _ref2.offsets,\n        position = _ref2.position,\n        gpuAcceleration = _ref2.gpuAcceleration,\n        adaptive = _ref2.adaptive,\n        roundOffsets = _ref2.roundOffsets,\n        isFixed = _ref2.isFixed;\n    var _offsets$x = offsets.x,\n        x = _offsets$x === void 0 ? 0 : _offsets$x,\n        _offsets$y = offsets.y,\n        y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n    var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n      x: x,\n      y: y\n    }) : {\n      x: x,\n      y: y\n    };\n\n    x = _ref3.x;\n    y = _ref3.y;\n    var hasX = offsets.hasOwnProperty('x');\n    var hasY = offsets.hasOwnProperty('y');\n    var sideX = left;\n    var sideY = top;\n    var win = window;\n\n    if (adaptive) {\n      var offsetParent = getOffsetParent(popper);\n      var heightProp = 'clientHeight';\n      var widthProp = 'clientWidth';\n\n      if (offsetParent === getWindow(popper)) {\n        offsetParent = getDocumentElement(popper);\n\n        if (getComputedStyle$1(offsetParent).position !== 'static' && position === 'absolute') {\n          heightProp = 'scrollHeight';\n          widthProp = 'scrollWidth';\n        }\n      } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n      offsetParent = offsetParent;\n\n      if (placement === top || (placement === left || placement === right) && variation === end) {\n        sideY = bottom;\n        var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n        offsetParent[heightProp];\n        y -= offsetY - popperRect.height;\n        y *= gpuAcceleration ? 1 : -1;\n      }\n\n      if (placement === left || (placement === top || placement === bottom) && variation === end) {\n        sideX = right;\n        var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n        offsetParent[widthProp];\n        x -= offsetX - popperRect.width;\n        x *= gpuAcceleration ? 1 : -1;\n      }\n    }\n\n    var commonStyles = Object.assign({\n      position: position\n    }, adaptive && unsetSides);\n\n    var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n      x: x,\n      y: y\n    }) : {\n      x: x,\n      y: y\n    };\n\n    x = _ref4.x;\n    y = _ref4.y;\n\n    if (gpuAcceleration) {\n      var _Object$assign;\n\n      return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n    }\n\n    return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n  }\n\n  function computeStyles(_ref5) {\n    var state = _ref5.state,\n        options = _ref5.options;\n    var _options$gpuAccelerat = options.gpuAcceleration,\n        gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n        _options$adaptive = options.adaptive,\n        adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n        _options$roundOffsets = options.roundOffsets,\n        roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n\n    var commonStyles = {\n      placement: getBasePlacement(state.placement),\n      variation: getVariation(state.placement),\n      popper: state.elements.popper,\n      popperRect: state.rects.popper,\n      gpuAcceleration: gpuAcceleration,\n      isFixed: state.options.strategy === 'fixed'\n    };\n\n    if (state.modifiersData.popperOffsets != null) {\n      state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n        offsets: state.modifiersData.popperOffsets,\n        position: state.options.strategy,\n        adaptive: adaptive,\n        roundOffsets: roundOffsets\n      })));\n    }\n\n    if (state.modifiersData.arrow != null) {\n      state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n        offsets: state.modifiersData.arrow,\n        position: 'absolute',\n        adaptive: false,\n        roundOffsets: roundOffsets\n      })));\n    }\n\n    state.attributes.popper = Object.assign({}, state.attributes.popper, {\n      'data-popper-placement': state.placement\n    });\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const computeStyles$1 = {\n    name: 'computeStyles',\n    enabled: true,\n    phase: 'beforeWrite',\n    fn: computeStyles,\n    data: {}\n  };\n\n  var passive = {\n    passive: true\n  };\n\n  function effect(_ref) {\n    var state = _ref.state,\n        instance = _ref.instance,\n        options = _ref.options;\n    var _options$scroll = options.scroll,\n        scroll = _options$scroll === void 0 ? true : _options$scroll,\n        _options$resize = options.resize,\n        resize = _options$resize === void 0 ? true : _options$resize;\n    var window = getWindow(state.elements.popper);\n    var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n    if (scroll) {\n      scrollParents.forEach(function (scrollParent) {\n        scrollParent.addEventListener('scroll', instance.update, passive);\n      });\n    }\n\n    if (resize) {\n      window.addEventListener('resize', instance.update, passive);\n    }\n\n    return function () {\n      if (scroll) {\n        scrollParents.forEach(function (scrollParent) {\n          scrollParent.removeEventListener('scroll', instance.update, passive);\n        });\n      }\n\n      if (resize) {\n        window.removeEventListener('resize', instance.update, passive);\n      }\n    };\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const eventListeners = {\n    name: 'eventListeners',\n    enabled: true,\n    phase: 'write',\n    fn: function fn() {},\n    effect: effect,\n    data: {}\n  };\n\n  var hash$1 = {\n    left: 'right',\n    right: 'left',\n    bottom: 'top',\n    top: 'bottom'\n  };\n  function getOppositePlacement(placement) {\n    return placement.replace(/left|right|bottom|top/g, function (matched) {\n      return hash$1[matched];\n    });\n  }\n\n  var hash = {\n    start: 'end',\n    end: 'start'\n  };\n  function getOppositeVariationPlacement(placement) {\n    return placement.replace(/start|end/g, function (matched) {\n      return hash[matched];\n    });\n  }\n\n  function getWindowScroll(node) {\n    var win = getWindow(node);\n    var scrollLeft = win.pageXOffset;\n    var scrollTop = win.pageYOffset;\n    return {\n      scrollLeft: scrollLeft,\n      scrollTop: scrollTop\n    };\n  }\n\n  function getWindowScrollBarX(element) {\n    // If <html> has a CSS width greater than the viewport, then this will be\n    // incorrect for RTL.\n    // Popper 1 is broken in this case and never had a bug report so let's assume\n    // it's not an issue. I don't think anyone ever specifies width on <html>\n    // anyway.\n    // Browsers where the left scrollbar doesn't cause an issue report `0` for\n    // this (e.g. Edge 2019, IE11, Safari)\n    return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n  }\n\n  function getViewportRect(element, strategy) {\n    var win = getWindow(element);\n    var html = getDocumentElement(element);\n    var visualViewport = win.visualViewport;\n    var width = html.clientWidth;\n    var height = html.clientHeight;\n    var x = 0;\n    var y = 0;\n\n    if (visualViewport) {\n      width = visualViewport.width;\n      height = visualViewport.height;\n      var layoutViewport = isLayoutViewport();\n\n      if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n        x = visualViewport.offsetLeft;\n        y = visualViewport.offsetTop;\n      }\n    }\n\n    return {\n      width: width,\n      height: height,\n      x: x + getWindowScrollBarX(element),\n      y: y\n    };\n  }\n\n  // of the `<html>` and `<body>` rect bounds if horizontally scrollable\n\n  function getDocumentRect(element) {\n    var _element$ownerDocumen;\n\n    var html = getDocumentElement(element);\n    var winScroll = getWindowScroll(element);\n    var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n    var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n    var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n    var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n    var y = -winScroll.scrollTop;\n\n    if (getComputedStyle$1(body || html).direction === 'rtl') {\n      x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n    }\n\n    return {\n      width: width,\n      height: height,\n      x: x,\n      y: y\n    };\n  }\n\n  function isScrollParent(element) {\n    // Firefox wants us to check `-x` and `-y` variations as well\n    var _getComputedStyle = getComputedStyle$1(element),\n        overflow = _getComputedStyle.overflow,\n        overflowX = _getComputedStyle.overflowX,\n        overflowY = _getComputedStyle.overflowY;\n\n    return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n  }\n\n  function getScrollParent(node) {\n    if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n      // $FlowFixMe[incompatible-return]: assume body is always available\n      return node.ownerDocument.body;\n    }\n\n    if (isHTMLElement(node) && isScrollParent(node)) {\n      return node;\n    }\n\n    return getScrollParent(getParentNode(node));\n  }\n\n  /*\n  given a DOM element, return the list of all scroll parents, up the list of ancesors\n  until we get to the top window object. This list is what we attach scroll listeners\n  to, because if any of these parent elements scroll, we'll need to re-calculate the\n  reference element's position.\n  */\n\n  function listScrollParents(element, list) {\n    var _element$ownerDocumen;\n\n    if (list === void 0) {\n      list = [];\n    }\n\n    var scrollParent = getScrollParent(element);\n    var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n    var win = getWindow(scrollParent);\n    var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n    var updatedList = list.concat(target);\n    return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n    updatedList.concat(listScrollParents(getParentNode(target)));\n  }\n\n  function rectToClientRect(rect) {\n    return Object.assign({}, rect, {\n      left: rect.x,\n      top: rect.y,\n      right: rect.x + rect.width,\n      bottom: rect.y + rect.height\n    });\n  }\n\n  function getInnerBoundingClientRect(element, strategy) {\n    var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n    rect.top = rect.top + element.clientTop;\n    rect.left = rect.left + element.clientLeft;\n    rect.bottom = rect.top + element.clientHeight;\n    rect.right = rect.left + element.clientWidth;\n    rect.width = element.clientWidth;\n    rect.height = element.clientHeight;\n    rect.x = rect.left;\n    rect.y = rect.top;\n    return rect;\n  }\n\n  function getClientRectFromMixedType(element, clippingParent, strategy) {\n    return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n  } // A \"clipping parent\" is an overflowable container with the characteristic of\n  // clipping (or hiding) overflowing elements with a position different from\n  // `initial`\n\n\n  function getClippingParents(element) {\n    var clippingParents = listScrollParents(getParentNode(element));\n    var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle$1(element).position) >= 0;\n    var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n    if (!isElement(clipperElement)) {\n      return [];\n    } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n    return clippingParents.filter(function (clippingParent) {\n      return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n    });\n  } // Gets the maximum area that the element is visible in due to any number of\n  // clipping parents\n\n\n  function getClippingRect(element, boundary, rootBoundary, strategy) {\n    var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n    var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n    var firstClippingParent = clippingParents[0];\n    var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n      var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n      accRect.top = max(rect.top, accRect.top);\n      accRect.right = min(rect.right, accRect.right);\n      accRect.bottom = min(rect.bottom, accRect.bottom);\n      accRect.left = max(rect.left, accRect.left);\n      return accRect;\n    }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n    clippingRect.width = clippingRect.right - clippingRect.left;\n    clippingRect.height = clippingRect.bottom - clippingRect.top;\n    clippingRect.x = clippingRect.left;\n    clippingRect.y = clippingRect.top;\n    return clippingRect;\n  }\n\n  function computeOffsets(_ref) {\n    var reference = _ref.reference,\n        element = _ref.element,\n        placement = _ref.placement;\n    var basePlacement = placement ? getBasePlacement(placement) : null;\n    var variation = placement ? getVariation(placement) : null;\n    var commonX = reference.x + reference.width / 2 - element.width / 2;\n    var commonY = reference.y + reference.height / 2 - element.height / 2;\n    var offsets;\n\n    switch (basePlacement) {\n      case top:\n        offsets = {\n          x: commonX,\n          y: reference.y - element.height\n        };\n        break;\n\n      case bottom:\n        offsets = {\n          x: commonX,\n          y: reference.y + reference.height\n        };\n        break;\n\n      case right:\n        offsets = {\n          x: reference.x + reference.width,\n          y: commonY\n        };\n        break;\n\n      case left:\n        offsets = {\n          x: reference.x - element.width,\n          y: commonY\n        };\n        break;\n\n      default:\n        offsets = {\n          x: reference.x,\n          y: reference.y\n        };\n    }\n\n    var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n    if (mainAxis != null) {\n      var len = mainAxis === 'y' ? 'height' : 'width';\n\n      switch (variation) {\n        case start:\n          offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n          break;\n\n        case end:\n          offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n          break;\n      }\n    }\n\n    return offsets;\n  }\n\n  function detectOverflow(state, options) {\n    if (options === void 0) {\n      options = {};\n    }\n\n    var _options = options,\n        _options$placement = _options.placement,\n        placement = _options$placement === void 0 ? state.placement : _options$placement,\n        _options$strategy = _options.strategy,\n        strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n        _options$boundary = _options.boundary,\n        boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n        _options$rootBoundary = _options.rootBoundary,\n        rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n        _options$elementConte = _options.elementContext,\n        elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n        _options$altBoundary = _options.altBoundary,\n        altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n        _options$padding = _options.padding,\n        padding = _options$padding === void 0 ? 0 : _options$padding;\n    var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n    var altContext = elementContext === popper ? reference : popper;\n    var popperRect = state.rects.popper;\n    var element = state.elements[altBoundary ? altContext : elementContext];\n    var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n    var referenceClientRect = getBoundingClientRect(state.elements.reference);\n    var popperOffsets = computeOffsets({\n      reference: referenceClientRect,\n      element: popperRect,\n      strategy: 'absolute',\n      placement: placement\n    });\n    var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n    var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n    // 0 or negative = within the clipping rect\n\n    var overflowOffsets = {\n      top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n      bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n      left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n      right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n    };\n    var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n    if (elementContext === popper && offsetData) {\n      var offset = offsetData[placement];\n      Object.keys(overflowOffsets).forEach(function (key) {\n        var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n        var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n        overflowOffsets[key] += offset[axis] * multiply;\n      });\n    }\n\n    return overflowOffsets;\n  }\n\n  function computeAutoPlacement(state, options) {\n    if (options === void 0) {\n      options = {};\n    }\n\n    var _options = options,\n        placement = _options.placement,\n        boundary = _options.boundary,\n        rootBoundary = _options.rootBoundary,\n        padding = _options.padding,\n        flipVariations = _options.flipVariations,\n        _options$allowedAutoP = _options.allowedAutoPlacements,\n        allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP;\n    var variation = getVariation(placement);\n    var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n      return getVariation(placement) === variation;\n    }) : basePlacements;\n    var allowedPlacements = placements$1.filter(function (placement) {\n      return allowedAutoPlacements.indexOf(placement) >= 0;\n    });\n\n    if (allowedPlacements.length === 0) {\n      allowedPlacements = placements$1;\n    } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n    var overflows = allowedPlacements.reduce(function (acc, placement) {\n      acc[placement] = detectOverflow(state, {\n        placement: placement,\n        boundary: boundary,\n        rootBoundary: rootBoundary,\n        padding: padding\n      })[getBasePlacement(placement)];\n      return acc;\n    }, {});\n    return Object.keys(overflows).sort(function (a, b) {\n      return overflows[a] - overflows[b];\n    });\n  }\n\n  function getExpandedFallbackPlacements(placement) {\n    if (getBasePlacement(placement) === auto) {\n      return [];\n    }\n\n    var oppositePlacement = getOppositePlacement(placement);\n    return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n  }\n\n  function flip(_ref) {\n    var state = _ref.state,\n        options = _ref.options,\n        name = _ref.name;\n\n    if (state.modifiersData[name]._skip) {\n      return;\n    }\n\n    var _options$mainAxis = options.mainAxis,\n        checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n        _options$altAxis = options.altAxis,\n        checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n        specifiedFallbackPlacements = options.fallbackPlacements,\n        padding = options.padding,\n        boundary = options.boundary,\n        rootBoundary = options.rootBoundary,\n        altBoundary = options.altBoundary,\n        _options$flipVariatio = options.flipVariations,\n        flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n        allowedAutoPlacements = options.allowedAutoPlacements;\n    var preferredPlacement = state.options.placement;\n    var basePlacement = getBasePlacement(preferredPlacement);\n    var isBasePlacement = basePlacement === preferredPlacement;\n    var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n    var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n      return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n        placement: placement,\n        boundary: boundary,\n        rootBoundary: rootBoundary,\n        padding: padding,\n        flipVariations: flipVariations,\n        allowedAutoPlacements: allowedAutoPlacements\n      }) : placement);\n    }, []);\n    var referenceRect = state.rects.reference;\n    var popperRect = state.rects.popper;\n    var checksMap = new Map();\n    var makeFallbackChecks = true;\n    var firstFittingPlacement = placements[0];\n\n    for (var i = 0; i < placements.length; i++) {\n      var placement = placements[i];\n\n      var _basePlacement = getBasePlacement(placement);\n\n      var isStartVariation = getVariation(placement) === start;\n      var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n      var len = isVertical ? 'width' : 'height';\n      var overflow = detectOverflow(state, {\n        placement: placement,\n        boundary: boundary,\n        rootBoundary: rootBoundary,\n        altBoundary: altBoundary,\n        padding: padding\n      });\n      var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n      if (referenceRect[len] > popperRect[len]) {\n        mainVariationSide = getOppositePlacement(mainVariationSide);\n      }\n\n      var altVariationSide = getOppositePlacement(mainVariationSide);\n      var checks = [];\n\n      if (checkMainAxis) {\n        checks.push(overflow[_basePlacement] <= 0);\n      }\n\n      if (checkAltAxis) {\n        checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n      }\n\n      if (checks.every(function (check) {\n        return check;\n      })) {\n        firstFittingPlacement = placement;\n        makeFallbackChecks = false;\n        break;\n      }\n\n      checksMap.set(placement, checks);\n    }\n\n    if (makeFallbackChecks) {\n      // `2` may be desired in some cases – research later\n      var numberOfChecks = flipVariations ? 3 : 1;\n\n      var _loop = function _loop(_i) {\n        var fittingPlacement = placements.find(function (placement) {\n          var checks = checksMap.get(placement);\n\n          if (checks) {\n            return checks.slice(0, _i).every(function (check) {\n              return check;\n            });\n          }\n        });\n\n        if (fittingPlacement) {\n          firstFittingPlacement = fittingPlacement;\n          return \"break\";\n        }\n      };\n\n      for (var _i = numberOfChecks; _i > 0; _i--) {\n        var _ret = _loop(_i);\n\n        if (_ret === \"break\") break;\n      }\n    }\n\n    if (state.placement !== firstFittingPlacement) {\n      state.modifiersData[name]._skip = true;\n      state.placement = firstFittingPlacement;\n      state.reset = true;\n    }\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const flip$1 = {\n    name: 'flip',\n    enabled: true,\n    phase: 'main',\n    fn: flip,\n    requiresIfExists: ['offset'],\n    data: {\n      _skip: false\n    }\n  };\n\n  function getSideOffsets(overflow, rect, preventedOffsets) {\n    if (preventedOffsets === void 0) {\n      preventedOffsets = {\n        x: 0,\n        y: 0\n      };\n    }\n\n    return {\n      top: overflow.top - rect.height - preventedOffsets.y,\n      right: overflow.right - rect.width + preventedOffsets.x,\n      bottom: overflow.bottom - rect.height + preventedOffsets.y,\n      left: overflow.left - rect.width - preventedOffsets.x\n    };\n  }\n\n  function isAnySideFullyClipped(overflow) {\n    return [top, right, bottom, left].some(function (side) {\n      return overflow[side] >= 0;\n    });\n  }\n\n  function hide(_ref) {\n    var state = _ref.state,\n        name = _ref.name;\n    var referenceRect = state.rects.reference;\n    var popperRect = state.rects.popper;\n    var preventedOffsets = state.modifiersData.preventOverflow;\n    var referenceOverflow = detectOverflow(state, {\n      elementContext: 'reference'\n    });\n    var popperAltOverflow = detectOverflow(state, {\n      altBoundary: true\n    });\n    var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n    var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n    var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n    var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n    state.modifiersData[name] = {\n      referenceClippingOffsets: referenceClippingOffsets,\n      popperEscapeOffsets: popperEscapeOffsets,\n      isReferenceHidden: isReferenceHidden,\n      hasPopperEscaped: hasPopperEscaped\n    };\n    state.attributes.popper = Object.assign({}, state.attributes.popper, {\n      'data-popper-reference-hidden': isReferenceHidden,\n      'data-popper-escaped': hasPopperEscaped\n    });\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const hide$1 = {\n    name: 'hide',\n    enabled: true,\n    phase: 'main',\n    requiresIfExists: ['preventOverflow'],\n    fn: hide\n  };\n\n  function distanceAndSkiddingToXY(placement, rects, offset) {\n    var basePlacement = getBasePlacement(placement);\n    var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n    var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n      placement: placement\n    })) : offset,\n        skidding = _ref[0],\n        distance = _ref[1];\n\n    skidding = skidding || 0;\n    distance = (distance || 0) * invertDistance;\n    return [left, right].indexOf(basePlacement) >= 0 ? {\n      x: distance,\n      y: skidding\n    } : {\n      x: skidding,\n      y: distance\n    };\n  }\n\n  function offset(_ref2) {\n    var state = _ref2.state,\n        options = _ref2.options,\n        name = _ref2.name;\n    var _options$offset = options.offset,\n        offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n    var data = placements.reduce(function (acc, placement) {\n      acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n      return acc;\n    }, {});\n    var _data$state$placement = data[state.placement],\n        x = _data$state$placement.x,\n        y = _data$state$placement.y;\n\n    if (state.modifiersData.popperOffsets != null) {\n      state.modifiersData.popperOffsets.x += x;\n      state.modifiersData.popperOffsets.y += y;\n    }\n\n    state.modifiersData[name] = data;\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const offset$1 = {\n    name: 'offset',\n    enabled: true,\n    phase: 'main',\n    requires: ['popperOffsets'],\n    fn: offset\n  };\n\n  function popperOffsets(_ref) {\n    var state = _ref.state,\n        name = _ref.name;\n    // Offsets are the actual position the popper needs to have to be\n    // properly positioned near its reference element\n    // This is the most basic placement, and will be adjusted by\n    // the modifiers in the next step\n    state.modifiersData[name] = computeOffsets({\n      reference: state.rects.reference,\n      element: state.rects.popper,\n      strategy: 'absolute',\n      placement: state.placement\n    });\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const popperOffsets$1 = {\n    name: 'popperOffsets',\n    enabled: true,\n    phase: 'read',\n    fn: popperOffsets,\n    data: {}\n  };\n\n  function getAltAxis(axis) {\n    return axis === 'x' ? 'y' : 'x';\n  }\n\n  function preventOverflow(_ref) {\n    var state = _ref.state,\n        options = _ref.options,\n        name = _ref.name;\n    var _options$mainAxis = options.mainAxis,\n        checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n        _options$altAxis = options.altAxis,\n        checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n        boundary = options.boundary,\n        rootBoundary = options.rootBoundary,\n        altBoundary = options.altBoundary,\n        padding = options.padding,\n        _options$tether = options.tether,\n        tether = _options$tether === void 0 ? true : _options$tether,\n        _options$tetherOffset = options.tetherOffset,\n        tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n    var overflow = detectOverflow(state, {\n      boundary: boundary,\n      rootBoundary: rootBoundary,\n      padding: padding,\n      altBoundary: altBoundary\n    });\n    var basePlacement = getBasePlacement(state.placement);\n    var variation = getVariation(state.placement);\n    var isBasePlacement = !variation;\n    var mainAxis = getMainAxisFromPlacement(basePlacement);\n    var altAxis = getAltAxis(mainAxis);\n    var popperOffsets = state.modifiersData.popperOffsets;\n    var referenceRect = state.rects.reference;\n    var popperRect = state.rects.popper;\n    var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n      placement: state.placement\n    })) : tetherOffset;\n    var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n      mainAxis: tetherOffsetValue,\n      altAxis: tetherOffsetValue\n    } : Object.assign({\n      mainAxis: 0,\n      altAxis: 0\n    }, tetherOffsetValue);\n    var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n    var data = {\n      x: 0,\n      y: 0\n    };\n\n    if (!popperOffsets) {\n      return;\n    }\n\n    if (checkMainAxis) {\n      var _offsetModifierState$;\n\n      var mainSide = mainAxis === 'y' ? top : left;\n      var altSide = mainAxis === 'y' ? bottom : right;\n      var len = mainAxis === 'y' ? 'height' : 'width';\n      var offset = popperOffsets[mainAxis];\n      var min$1 = offset + overflow[mainSide];\n      var max$1 = offset - overflow[altSide];\n      var additive = tether ? -popperRect[len] / 2 : 0;\n      var minLen = variation === start ? referenceRect[len] : popperRect[len];\n      var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n      // outside the reference bounds\n\n      var arrowElement = state.elements.arrow;\n      var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n        width: 0,\n        height: 0\n      };\n      var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n      var arrowPaddingMin = arrowPaddingObject[mainSide];\n      var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n      // to include its full size in the calculation. If the reference is small\n      // and near the edge of a boundary, the popper can overflow even if the\n      // reference is not overflowing as well (e.g. virtual elements with no\n      // width or height)\n\n      var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n      var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n      var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n      var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n      var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n      var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n      var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n      var tetherMax = offset + maxOffset - offsetModifierValue;\n      var preventedOffset = within(tether ? min(min$1, tetherMin) : min$1, offset, tether ? max(max$1, tetherMax) : max$1);\n      popperOffsets[mainAxis] = preventedOffset;\n      data[mainAxis] = preventedOffset - offset;\n    }\n\n    if (checkAltAxis) {\n      var _offsetModifierState$2;\n\n      var _mainSide = mainAxis === 'x' ? top : left;\n\n      var _altSide = mainAxis === 'x' ? bottom : right;\n\n      var _offset = popperOffsets[altAxis];\n\n      var _len = altAxis === 'y' ? 'height' : 'width';\n\n      var _min = _offset + overflow[_mainSide];\n\n      var _max = _offset - overflow[_altSide];\n\n      var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n      var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n      var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n      var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n      var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n      popperOffsets[altAxis] = _preventedOffset;\n      data[altAxis] = _preventedOffset - _offset;\n    }\n\n    state.modifiersData[name] = data;\n  } // eslint-disable-next-line import/no-unused-modules\n\n\n  const preventOverflow$1 = {\n    name: 'preventOverflow',\n    enabled: true,\n    phase: 'main',\n    fn: preventOverflow,\n    requiresIfExists: ['offset']\n  };\n\n  function getHTMLElementScroll(element) {\n    return {\n      scrollLeft: element.scrollLeft,\n      scrollTop: element.scrollTop\n    };\n  }\n\n  function getNodeScroll(node) {\n    if (node === getWindow(node) || !isHTMLElement(node)) {\n      return getWindowScroll(node);\n    } else {\n      return getHTMLElementScroll(node);\n    }\n  }\n\n  function isElementScaled(element) {\n    var rect = element.getBoundingClientRect();\n    var scaleX = round(rect.width) / element.offsetWidth || 1;\n    var scaleY = round(rect.height) / element.offsetHeight || 1;\n    return scaleX !== 1 || scaleY !== 1;\n  } // Returns the composite rect of an element relative to its offsetParent.\n  // Composite means it takes into account transforms as well as layout.\n\n\n  function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n    if (isFixed === void 0) {\n      isFixed = false;\n    }\n\n    var isOffsetParentAnElement = isHTMLElement(offsetParent);\n    var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n    var documentElement = getDocumentElement(offsetParent);\n    var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n    var scroll = {\n      scrollLeft: 0,\n      scrollTop: 0\n    };\n    var offsets = {\n      x: 0,\n      y: 0\n    };\n\n    if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n      if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n      isScrollParent(documentElement)) {\n        scroll = getNodeScroll(offsetParent);\n      }\n\n      if (isHTMLElement(offsetParent)) {\n        offsets = getBoundingClientRect(offsetParent, true);\n        offsets.x += offsetParent.clientLeft;\n        offsets.y += offsetParent.clientTop;\n      } else if (documentElement) {\n        offsets.x = getWindowScrollBarX(documentElement);\n      }\n    }\n\n    return {\n      x: rect.left + scroll.scrollLeft - offsets.x,\n      y: rect.top + scroll.scrollTop - offsets.y,\n      width: rect.width,\n      height: rect.height\n    };\n  }\n\n  function order(modifiers) {\n    var map = new Map();\n    var visited = new Set();\n    var result = [];\n    modifiers.forEach(function (modifier) {\n      map.set(modifier.name, modifier);\n    }); // On visiting object, check for its dependencies and visit them recursively\n\n    function sort(modifier) {\n      visited.add(modifier.name);\n      var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n      requires.forEach(function (dep) {\n        if (!visited.has(dep)) {\n          var depModifier = map.get(dep);\n\n          if (depModifier) {\n            sort(depModifier);\n          }\n        }\n      });\n      result.push(modifier);\n    }\n\n    modifiers.forEach(function (modifier) {\n      if (!visited.has(modifier.name)) {\n        // check for visited object\n        sort(modifier);\n      }\n    });\n    return result;\n  }\n\n  function orderModifiers(modifiers) {\n    // order based on dependencies\n    var orderedModifiers = order(modifiers); // order based on phase\n\n    return modifierPhases.reduce(function (acc, phase) {\n      return acc.concat(orderedModifiers.filter(function (modifier) {\n        return modifier.phase === phase;\n      }));\n    }, []);\n  }\n\n  function debounce(fn) {\n    var pending;\n    return function () {\n      if (!pending) {\n        pending = new Promise(function (resolve) {\n          Promise.resolve().then(function () {\n            pending = undefined;\n            resolve(fn());\n          });\n        });\n      }\n\n      return pending;\n    };\n  }\n\n  function mergeByName(modifiers) {\n    var merged = modifiers.reduce(function (merged, current) {\n      var existing = merged[current.name];\n      merged[current.name] = existing ? Object.assign({}, existing, current, {\n        options: Object.assign({}, existing.options, current.options),\n        data: Object.assign({}, existing.data, current.data)\n      }) : current;\n      return merged;\n    }, {}); // IE11 does not support Object.values\n\n    return Object.keys(merged).map(function (key) {\n      return merged[key];\n    });\n  }\n\n  var DEFAULT_OPTIONS = {\n    placement: 'bottom',\n    modifiers: [],\n    strategy: 'absolute'\n  };\n\n  function areValidElements() {\n    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n      args[_key] = arguments[_key];\n    }\n\n    return !args.some(function (element) {\n      return !(element && typeof element.getBoundingClientRect === 'function');\n    });\n  }\n\n  function popperGenerator(generatorOptions) {\n    if (generatorOptions === void 0) {\n      generatorOptions = {};\n    }\n\n    var _generatorOptions = generatorOptions,\n        _generatorOptions$def = _generatorOptions.defaultModifiers,\n        defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n        _generatorOptions$def2 = _generatorOptions.defaultOptions,\n        defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n    return function createPopper(reference, popper, options) {\n      if (options === void 0) {\n        options = defaultOptions;\n      }\n\n      var state = {\n        placement: 'bottom',\n        orderedModifiers: [],\n        options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n        modifiersData: {},\n        elements: {\n          reference: reference,\n          popper: popper\n        },\n        attributes: {},\n        styles: {}\n      };\n      var effectCleanupFns = [];\n      var isDestroyed = false;\n      var instance = {\n        state: state,\n        setOptions: function setOptions(setOptionsAction) {\n          var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n          cleanupModifierEffects();\n          state.options = Object.assign({}, defaultOptions, state.options, options);\n          state.scrollParents = {\n            reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n            popper: listScrollParents(popper)\n          }; // Orders the modifiers based on their dependencies and `phase`\n          // properties\n\n          var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n          state.orderedModifiers = orderedModifiers.filter(function (m) {\n            return m.enabled;\n          }); // Validate the provided modifiers so that the consumer will get warned\n\n          runModifierEffects();\n          return instance.update();\n        },\n        // Sync update – it will always be executed, even if not necessary. This\n        // is useful for low frequency updates where sync behavior simplifies the\n        // logic.\n        // For high frequency updates (e.g. `resize` and `scroll` events), always\n        // prefer the async Popper#update method\n        forceUpdate: function forceUpdate() {\n          if (isDestroyed) {\n            return;\n          }\n\n          var _state$elements = state.elements,\n              reference = _state$elements.reference,\n              popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n          // anymore\n\n          if (!areValidElements(reference, popper)) {\n\n            return;\n          } // Store the reference and popper rects to be read by modifiers\n\n\n          state.rects = {\n            reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n            popper: getLayoutRect(popper)\n          }; // Modifiers have the ability to reset the current update cycle. The\n          // most common use case for this is the `flip` modifier changing the\n          // placement, which then needs to re-run all the modifiers, because the\n          // logic was previously ran for the previous placement and is therefore\n          // stale/incorrect\n\n          state.reset = false;\n          state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n          // is filled with the initial data specified by the modifier. This means\n          // it doesn't persist and is fresh on each update.\n          // To ensure persistent data, use `${name}#persistent`\n\n          state.orderedModifiers.forEach(function (modifier) {\n            return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n          });\n\n          for (var index = 0; index < state.orderedModifiers.length; index++) {\n\n            if (state.reset === true) {\n              state.reset = false;\n              index = -1;\n              continue;\n            }\n\n            var _state$orderedModifie = state.orderedModifiers[index],\n                fn = _state$orderedModifie.fn,\n                _state$orderedModifie2 = _state$orderedModifie.options,\n                _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n                name = _state$orderedModifie.name;\n\n            if (typeof fn === 'function') {\n              state = fn({\n                state: state,\n                options: _options,\n                name: name,\n                instance: instance\n              }) || state;\n            }\n          }\n        },\n        // Async and optimistically optimized update – it will not be executed if\n        // not necessary (debounced to run at most once-per-tick)\n        update: debounce(function () {\n          return new Promise(function (resolve) {\n            instance.forceUpdate();\n            resolve(state);\n          });\n        }),\n        destroy: function destroy() {\n          cleanupModifierEffects();\n          isDestroyed = true;\n        }\n      };\n\n      if (!areValidElements(reference, popper)) {\n\n        return instance;\n      }\n\n      instance.setOptions(options).then(function (state) {\n        if (!isDestroyed && options.onFirstUpdate) {\n          options.onFirstUpdate(state);\n        }\n      }); // Modifiers have the ability to execute arbitrary code before the first\n      // update cycle runs. They will be executed in the same order as the update\n      // cycle. This is useful when a modifier adds some persistent data that\n      // other modifiers need to use, but the modifier is run after the dependent\n      // one.\n\n      function runModifierEffects() {\n        state.orderedModifiers.forEach(function (_ref3) {\n          var name = _ref3.name,\n              _ref3$options = _ref3.options,\n              options = _ref3$options === void 0 ? {} : _ref3$options,\n              effect = _ref3.effect;\n\n          if (typeof effect === 'function') {\n            var cleanupFn = effect({\n              state: state,\n              name: name,\n              instance: instance,\n              options: options\n            });\n\n            var noopFn = function noopFn() {};\n\n            effectCleanupFns.push(cleanupFn || noopFn);\n          }\n        });\n      }\n\n      function cleanupModifierEffects() {\n        effectCleanupFns.forEach(function (fn) {\n          return fn();\n        });\n        effectCleanupFns = [];\n      }\n\n      return instance;\n    };\n  }\n  var createPopper$2 = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\n  var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1];\n  var createPopper$1 = /*#__PURE__*/popperGenerator({\n    defaultModifiers: defaultModifiers$1\n  }); // eslint-disable-next-line import/no-unused-modules\n\n  var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1];\n  var createPopper = /*#__PURE__*/popperGenerator({\n    defaultModifiers: defaultModifiers\n  }); // eslint-disable-next-line import/no-unused-modules\n\n  const Popper = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({\n    __proto__: null,\n    popperGenerator,\n    detectOverflow,\n    createPopperBase: createPopper$2,\n    createPopper,\n    createPopperLite: createPopper$1,\n    top,\n    bottom,\n    right,\n    left,\n    auto,\n    basePlacements,\n    start,\n    end,\n    clippingParents,\n    viewport,\n    popper,\n    reference,\n    variationPlacements,\n    placements,\n    beforeRead,\n    read,\n    afterRead,\n    beforeMain,\n    main,\n    afterMain,\n    beforeWrite,\n    write,\n    afterWrite,\n    modifierPhases,\n    applyStyles: applyStyles$1,\n    arrow: arrow$1,\n    computeStyles: computeStyles$1,\n    eventListeners,\n    flip: flip$1,\n    hide: hide$1,\n    offset: offset$1,\n    popperOffsets: popperOffsets$1,\n    preventOverflow: preventOverflow$1\n  }, Symbol.toStringTag, { value: 'Module' }));\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dropdown.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$a = 'dropdown';\n  const DATA_KEY$6 = 'bs.dropdown';\n  const EVENT_KEY$6 = `.${DATA_KEY$6}`;\n  const DATA_API_KEY$3 = '.data-api';\n  const ESCAPE_KEY$2 = 'Escape';\n  const TAB_KEY$1 = 'Tab';\n  const ARROW_UP_KEY$1 = 'ArrowUp';\n  const ARROW_DOWN_KEY$1 = 'ArrowDown';\n  const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\n  const EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\n  const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\n  const EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\n  const EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\n  const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\n  const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\n  const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\n  const CLASS_NAME_SHOW$6 = 'show';\n  const CLASS_NAME_DROPUP = 'dropup';\n  const CLASS_NAME_DROPEND = 'dropend';\n  const CLASS_NAME_DROPSTART = 'dropstart';\n  const CLASS_NAME_DROPUP_CENTER = 'dropup-center';\n  const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\n  const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\n  const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\n  const SELECTOR_MENU = '.dropdown-menu';\n  const SELECTOR_NAVBAR = '.navbar';\n  const SELECTOR_NAVBAR_NAV = '.navbar-nav';\n  const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\n  const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\n  const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\n  const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\n  const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\n  const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\n  const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\n  const PLACEMENT_TOPCENTER = 'top';\n  const PLACEMENT_BOTTOMCENTER = 'bottom';\n  const Default$9 = {\n    autoClose: true,\n    boundary: 'clippingParents',\n    display: 'dynamic',\n    offset: [0, 2],\n    popperConfig: null,\n    reference: 'toggle'\n  };\n  const DefaultType$9 = {\n    autoClose: '(boolean|string)',\n    boundary: '(string|element)',\n    display: 'string',\n    offset: '(array|string|function)',\n    popperConfig: '(null|object|function)',\n    reference: '(string|element|object)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Dropdown extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._popper = null;\n      this._parent = this._element.parentNode; // dropdown wrapper\n      // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n      this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n      this._inNavbar = this._detectNavbar();\n    } // Getters\n\n\n    static get Default() {\n      return Default$9;\n    }\n\n    static get DefaultType() {\n      return DefaultType$9;\n    }\n\n    static get NAME() {\n      return NAME$a;\n    } // Public\n\n\n    toggle() {\n      return this._isShown() ? this.hide() : this.show();\n    }\n\n    show() {\n      if (isDisabled(this._element) || this._isShown()) {\n        return;\n      }\n\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._createPopper(); // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n\n      if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.on(element, 'mouseover', noop);\n        }\n      }\n\n      this._element.focus();\n\n      this._element.setAttribute('aria-expanded', true);\n\n      this._menu.classList.add(CLASS_NAME_SHOW$6);\n\n      this._element.classList.add(CLASS_NAME_SHOW$6);\n\n      EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n    }\n\n    hide() {\n      if (isDisabled(this._element) || !this._isShown()) {\n        return;\n      }\n\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n\n      this._completeHide(relatedTarget);\n    }\n\n    dispose() {\n      if (this._popper) {\n        this._popper.destroy();\n      }\n\n      super.dispose();\n    }\n\n    update() {\n      this._inNavbar = this._detectNavbar();\n\n      if (this._popper) {\n        this._popper.update();\n      }\n    } // Private\n\n\n    _completeHide(relatedTarget) {\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      } // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.off(element, 'mouseover', noop);\n        }\n      }\n\n      if (this._popper) {\n        this._popper.destroy();\n      }\n\n      this._menu.classList.remove(CLASS_NAME_SHOW$6);\n\n      this._element.classList.remove(CLASS_NAME_SHOW$6);\n\n      this._element.setAttribute('aria-expanded', 'false');\n\n      Manipulator.removeDataAttribute(this._menu, 'popper');\n      EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n    }\n\n    _getConfig(config) {\n      config = super._getConfig(config);\n\n      if (typeof config.reference === 'object' && !isElement$1(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n        // Popper virtual elements require a getBoundingClientRect method\n        throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n      }\n\n      return config;\n    }\n\n    _createPopper() {\n      if (typeof Popper === 'undefined') {\n        throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n      }\n\n      let referenceElement = this._element;\n\n      if (this._config.reference === 'parent') {\n        referenceElement = this._parent;\n      } else if (isElement$1(this._config.reference)) {\n        referenceElement = getElement(this._config.reference);\n      } else if (typeof this._config.reference === 'object') {\n        referenceElement = this._config.reference;\n      }\n\n      const popperConfig = this._getPopperConfig();\n\n      this._popper = createPopper(referenceElement, this._menu, popperConfig);\n    }\n\n    _isShown() {\n      return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n    }\n\n    _getPlacement() {\n      const parentDropdown = this._parent;\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n        return PLACEMENT_RIGHT;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n        return PLACEMENT_LEFT;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n        return PLACEMENT_TOPCENTER;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n        return PLACEMENT_BOTTOMCENTER;\n      } // We need to trim the value because custom properties can also include spaces\n\n\n      const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n        return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n      }\n\n      return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n    }\n\n    _detectNavbar() {\n      return this._element.closest(SELECTOR_NAVBAR) !== null;\n    }\n\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n\n      return offset;\n    }\n\n    _getPopperConfig() {\n      const defaultBsPopperConfig = {\n        placement: this._getPlacement(),\n        modifiers: [{\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }]\n      }; // Disable Popper if we have a static display or Dropdown is in Navbar\n\n      if (this._inNavbar || this._config.display === 'static') {\n        Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // todo:v6 remove\n\n        defaultBsPopperConfig.modifiers = [{\n          name: 'applyStyles',\n          enabled: false\n        }];\n      }\n\n      return { ...defaultBsPopperConfig,\n        ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n      };\n    }\n\n    _selectMenuItem({\n      key,\n      target\n    }) {\n      const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n\n      if (!items.length) {\n        return;\n      } // if target isn't included in items (e.g. when expanding the dropdown)\n      // allow cycling to get the last item in case key equals ARROW_UP_KEY\n\n\n      getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Dropdown.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n    static clearMenus(event) {\n      if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n        return;\n      }\n\n      const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n\n      for (const toggle of openToggles) {\n        const context = Dropdown.getInstance(toggle);\n\n        if (!context || context._config.autoClose === false) {\n          continue;\n        }\n\n        const composedPath = event.composedPath();\n        const isMenuTarget = composedPath.includes(context._menu);\n\n        if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n          continue;\n        } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n\n\n        if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n          continue;\n        }\n\n        const relatedTarget = {\n          relatedTarget: context._element\n        };\n\n        if (event.type === 'click') {\n          relatedTarget.clickEvent = event;\n        }\n\n        context._completeHide(relatedTarget);\n      }\n    }\n\n    static dataApiKeydownHandler(event) {\n      // If not an UP | DOWN | ESCAPE key => not a dropdown command\n      // If input/textarea && if key is other than ESCAPE => not a dropdown command\n      const isInput = /input|textarea/i.test(event.target.tagName);\n      const isEscapeEvent = event.key === ESCAPE_KEY$2;\n      const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n\n      if (!isUpOrDownEvent && !isEscapeEvent) {\n        return;\n      }\n\n      if (isInput && !isEscapeEvent) {\n        return;\n      }\n\n      event.preventDefault(); // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n      const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n      const instance = Dropdown.getOrCreateInstance(getToggleButton);\n\n      if (isUpOrDownEvent) {\n        event.stopPropagation();\n        instance.show();\n\n        instance._selectMenuItem(event);\n\n        return;\n      }\n\n      if (instance._isShown()) {\n        // else is escape and we check if it is shown\n        event.stopPropagation();\n        instance.hide();\n        getToggleButton.focus();\n      }\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\n  EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\n  EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\n  EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\n  EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n    event.preventDefault();\n    Dropdown.getOrCreateInstance(this).toggle();\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Dropdown);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/scrollBar.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\n  const SELECTOR_STICKY_CONTENT = '.sticky-top';\n  const PROPERTY_PADDING = 'padding-right';\n  const PROPERTY_MARGIN = 'margin-right';\n  /**\n   * Class definition\n   */\n\n  class ScrollBarHelper {\n    constructor() {\n      this._element = document.body;\n    } // Public\n\n\n    getWidth() {\n      // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n      const documentWidth = document.documentElement.clientWidth;\n      return Math.abs(window.innerWidth - documentWidth);\n    }\n\n    hide() {\n      const width = this.getWidth();\n\n      this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width\n\n\n      this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n\n\n      this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n\n      this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n    }\n\n    reset() {\n      this._resetElementAttributes(this._element, 'overflow');\n\n      this._resetElementAttributes(this._element, PROPERTY_PADDING);\n\n      this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n\n      this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n    }\n\n    isOverflowing() {\n      return this.getWidth() > 0;\n    } // Private\n\n\n    _disableOverFlow() {\n      this._saveInitialAttribute(this._element, 'overflow');\n\n      this._element.style.overflow = 'hidden';\n    }\n\n    _setElementAttributes(selector, styleProperty, callback) {\n      const scrollbarWidth = this.getWidth();\n\n      const manipulationCallBack = element => {\n        if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n          return;\n        }\n\n        this._saveInitialAttribute(element, styleProperty);\n\n        const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n        element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n      };\n\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n\n    _saveInitialAttribute(element, styleProperty) {\n      const actualValue = element.style.getPropertyValue(styleProperty);\n\n      if (actualValue) {\n        Manipulator.setDataAttribute(element, styleProperty, actualValue);\n      }\n    }\n\n    _resetElementAttributes(selector, styleProperty) {\n      const manipulationCallBack = element => {\n        const value = Manipulator.getDataAttribute(element, styleProperty); // We only want to remove the property if the value is `null`; the value can also be zero\n\n        if (value === null) {\n          element.style.removeProperty(styleProperty);\n          return;\n        }\n\n        Manipulator.removeDataAttribute(element, styleProperty);\n        element.style.setProperty(styleProperty, value);\n      };\n\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n\n    _applyManipulationCallback(selector, callBack) {\n      if (isElement$1(selector)) {\n        callBack(selector);\n        return;\n      }\n\n      for (const sel of SelectorEngine.find(selector, this._element)) {\n        callBack(sel);\n      }\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/backdrop.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$9 = 'backdrop';\n  const CLASS_NAME_FADE$4 = 'fade';\n  const CLASS_NAME_SHOW$5 = 'show';\n  const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\n  const Default$8 = {\n    className: 'modal-backdrop',\n    clickCallback: null,\n    isAnimated: false,\n    isVisible: true,\n    // if false, we use the backdrop helper without adding any element to the dom\n    rootElement: 'body' // give the choice to place backdrop under different elements\n\n  };\n  const DefaultType$8 = {\n    className: 'string',\n    clickCallback: '(function|null)',\n    isAnimated: 'boolean',\n    isVisible: 'boolean',\n    rootElement: '(element|string)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Backdrop extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isAppended = false;\n      this._element = null;\n    } // Getters\n\n\n    static get Default() {\n      return Default$8;\n    }\n\n    static get DefaultType() {\n      return DefaultType$8;\n    }\n\n    static get NAME() {\n      return NAME$9;\n    } // Public\n\n\n    show(callback) {\n      if (!this._config.isVisible) {\n        execute(callback);\n        return;\n      }\n\n      this._append();\n\n      const element = this._getElement();\n\n      if (this._config.isAnimated) {\n        reflow(element);\n      }\n\n      element.classList.add(CLASS_NAME_SHOW$5);\n\n      this._emulateAnimation(() => {\n        execute(callback);\n      });\n    }\n\n    hide(callback) {\n      if (!this._config.isVisible) {\n        execute(callback);\n        return;\n      }\n\n      this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n\n      this._emulateAnimation(() => {\n        this.dispose();\n        execute(callback);\n      });\n    }\n\n    dispose() {\n      if (!this._isAppended) {\n        return;\n      }\n\n      EventHandler.off(this._element, EVENT_MOUSEDOWN);\n\n      this._element.remove();\n\n      this._isAppended = false;\n    } // Private\n\n\n    _getElement() {\n      if (!this._element) {\n        const backdrop = document.createElement('div');\n        backdrop.className = this._config.className;\n\n        if (this._config.isAnimated) {\n          backdrop.classList.add(CLASS_NAME_FADE$4);\n        }\n\n        this._element = backdrop;\n      }\n\n      return this._element;\n    }\n\n    _configAfterMerge(config) {\n      // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n      config.rootElement = getElement(config.rootElement);\n      return config;\n    }\n\n    _append() {\n      if (this._isAppended) {\n        return;\n      }\n\n      const element = this._getElement();\n\n      this._config.rootElement.append(element);\n\n      EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n        execute(this._config.clickCallback);\n      });\n      this._isAppended = true;\n    }\n\n    _emulateAnimation(callback) {\n      executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/focustrap.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$8 = 'focustrap';\n  const DATA_KEY$5 = 'bs.focustrap';\n  const EVENT_KEY$5 = `.${DATA_KEY$5}`;\n  const EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\n  const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\n  const TAB_KEY = 'Tab';\n  const TAB_NAV_FORWARD = 'forward';\n  const TAB_NAV_BACKWARD = 'backward';\n  const Default$7 = {\n    autofocus: true,\n    trapElement: null // The element to trap focus inside of\n\n  };\n  const DefaultType$7 = {\n    autofocus: 'boolean',\n    trapElement: 'element'\n  };\n  /**\n   * Class definition\n   */\n\n  class FocusTrap extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isActive = false;\n      this._lastTabNavDirection = null;\n    } // Getters\n\n\n    static get Default() {\n      return Default$7;\n    }\n\n    static get DefaultType() {\n      return DefaultType$7;\n    }\n\n    static get NAME() {\n      return NAME$8;\n    } // Public\n\n\n    activate() {\n      if (this._isActive) {\n        return;\n      }\n\n      if (this._config.autofocus) {\n        this._config.trapElement.focus();\n      }\n\n      EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n\n      EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n      EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n      this._isActive = true;\n    }\n\n    deactivate() {\n      if (!this._isActive) {\n        return;\n      }\n\n      this._isActive = false;\n      EventHandler.off(document, EVENT_KEY$5);\n    } // Private\n\n\n    _handleFocusin(event) {\n      const {\n        trapElement\n      } = this._config;\n\n      if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n        return;\n      }\n\n      const elements = SelectorEngine.focusableChildren(trapElement);\n\n      if (elements.length === 0) {\n        trapElement.focus();\n      } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n        elements[elements.length - 1].focus();\n      } else {\n        elements[0].focus();\n      }\n    }\n\n    _handleKeydown(event) {\n      if (event.key !== TAB_KEY) {\n        return;\n      }\n\n      this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): modal.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$7 = 'modal';\n  const DATA_KEY$4 = 'bs.modal';\n  const EVENT_KEY$4 = `.${DATA_KEY$4}`;\n  const DATA_API_KEY$2 = '.data-api';\n  const ESCAPE_KEY$1 = 'Escape';\n  const EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\n  const EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\n  const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\n  const EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\n  const EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\n  const EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\n  const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\n  const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\n  const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\n  const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\n  const CLASS_NAME_OPEN = 'modal-open';\n  const CLASS_NAME_FADE$3 = 'fade';\n  const CLASS_NAME_SHOW$4 = 'show';\n  const CLASS_NAME_STATIC = 'modal-static';\n  const OPEN_SELECTOR$1 = '.modal.show';\n  const SELECTOR_DIALOG = '.modal-dialog';\n  const SELECTOR_MODAL_BODY = '.modal-body';\n  const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\n  const Default$6 = {\n    backdrop: true,\n    focus: true,\n    keyboard: true\n  };\n  const DefaultType$6 = {\n    backdrop: '(boolean|string)',\n    focus: 'boolean',\n    keyboard: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Modal extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n      this._isShown = false;\n      this._isTransitioning = false;\n      this._scrollBar = new ScrollBarHelper();\n\n      this._addEventListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default$6;\n    }\n\n    static get DefaultType() {\n      return DefaultType$6;\n    }\n\n    static get NAME() {\n      return NAME$7;\n    } // Public\n\n\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n\n    show(relatedTarget) {\n      if (this._isShown || this._isTransitioning) {\n        return;\n      }\n\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n        relatedTarget\n      });\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = true;\n      this._isTransitioning = true;\n\n      this._scrollBar.hide();\n\n      document.body.classList.add(CLASS_NAME_OPEN);\n\n      this._adjustDialog();\n\n      this._backdrop.show(() => this._showElement(relatedTarget));\n    }\n\n    hide() {\n      if (!this._isShown || this._isTransitioning) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = false;\n      this._isTransitioning = true;\n\n      this._focustrap.deactivate();\n\n      this._element.classList.remove(CLASS_NAME_SHOW$4);\n\n      this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n    }\n\n    dispose() {\n      for (const htmlElement of [window, this._dialog]) {\n        EventHandler.off(htmlElement, EVENT_KEY$4);\n      }\n\n      this._backdrop.dispose();\n\n      this._focustrap.deactivate();\n\n      super.dispose();\n    }\n\n    handleUpdate() {\n      this._adjustDialog();\n    } // Private\n\n\n    _initializeBackDrop() {\n      return new Backdrop({\n        isVisible: Boolean(this._config.backdrop),\n        // 'static' option will be translated to true, and booleans will keep their value,\n        isAnimated: this._isAnimated()\n      });\n    }\n\n    _initializeFocusTrap() {\n      return new FocusTrap({\n        trapElement: this._element\n      });\n    }\n\n    _showElement(relatedTarget) {\n      // try to append dynamic modal\n      if (!document.body.contains(this._element)) {\n        document.body.append(this._element);\n      }\n\n      this._element.style.display = 'block';\n\n      this._element.removeAttribute('aria-hidden');\n\n      this._element.setAttribute('aria-modal', true);\n\n      this._element.setAttribute('role', 'dialog');\n\n      this._element.scrollTop = 0;\n      const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n\n      if (modalBody) {\n        modalBody.scrollTop = 0;\n      }\n\n      reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_SHOW$4);\n\n      const transitionComplete = () => {\n        if (this._config.focus) {\n          this._focustrap.activate();\n        }\n\n        this._isTransitioning = false;\n        EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n          relatedTarget\n        });\n      };\n\n      this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n    }\n\n    _addEventListeners() {\n      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n        if (event.key !== ESCAPE_KEY$1) {\n          return;\n        }\n\n        if (this._config.keyboard) {\n          event.preventDefault();\n          this.hide();\n          return;\n        }\n\n        this._triggerBackdropTransition();\n      });\n      EventHandler.on(window, EVENT_RESIZE$1, () => {\n        if (this._isShown && !this._isTransitioning) {\n          this._adjustDialog();\n        }\n      });\n      EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n        // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n        EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n          if (this._element !== event.target || this._element !== event2.target) {\n            return;\n          }\n\n          if (this._config.backdrop === 'static') {\n            this._triggerBackdropTransition();\n\n            return;\n          }\n\n          if (this._config.backdrop) {\n            this.hide();\n          }\n        });\n      });\n    }\n\n    _hideModal() {\n      this._element.style.display = 'none';\n\n      this._element.setAttribute('aria-hidden', true);\n\n      this._element.removeAttribute('aria-modal');\n\n      this._element.removeAttribute('role');\n\n      this._isTransitioning = false;\n\n      this._backdrop.hide(() => {\n        document.body.classList.remove(CLASS_NAME_OPEN);\n\n        this._resetAdjustments();\n\n        this._scrollBar.reset();\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n      });\n    }\n\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_FADE$3);\n    }\n\n    _triggerBackdropTransition() {\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n      const initialOverflowY = this._element.style.overflowY; // return if the following background transition hasn't yet completed\n\n      if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n        return;\n      }\n\n      if (!isModalOverflowing) {\n        this._element.style.overflowY = 'hidden';\n      }\n\n      this._element.classList.add(CLASS_NAME_STATIC);\n\n      this._queueCallback(() => {\n        this._element.classList.remove(CLASS_NAME_STATIC);\n\n        this._queueCallback(() => {\n          this._element.style.overflowY = initialOverflowY;\n        }, this._dialog);\n      }, this._dialog);\n\n      this._element.focus();\n    }\n    /**\n     * The following methods are used to handle overflowing modals\n     */\n\n\n    _adjustDialog() {\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n\n      const scrollbarWidth = this._scrollBar.getWidth();\n\n      const isBodyOverflowing = scrollbarWidth > 0;\n\n      if (isBodyOverflowing && !isModalOverflowing) {\n        const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n\n      if (!isBodyOverflowing && isModalOverflowing) {\n        const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n    }\n\n    _resetAdjustments() {\n      this._element.style.paddingLeft = '';\n      this._element.style.paddingRight = '';\n    } // Static\n\n\n    static jQueryInterface(config, relatedTarget) {\n      return this.each(function () {\n        const data = Modal.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](relatedTarget);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n    const target = getElementFromSelector(this);\n\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n      if (showEvent.defaultPrevented) {\n        // only register focus restorer if modal will actually get shown\n        return;\n      }\n\n      EventHandler.one(target, EVENT_HIDDEN$4, () => {\n        if (isVisible(this)) {\n          this.focus();\n        }\n      });\n    }); // avoid conflict when clicking modal toggler while another one is open\n\n    const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n\n    if (alreadyOpen) {\n      Modal.getInstance(alreadyOpen).hide();\n    }\n\n    const data = Modal.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  enableDismissTrigger(Modal);\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Modal);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): offcanvas.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$6 = 'offcanvas';\n  const DATA_KEY$3 = 'bs.offcanvas';\n  const EVENT_KEY$3 = `.${DATA_KEY$3}`;\n  const DATA_API_KEY$1 = '.data-api';\n  const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\n  const ESCAPE_KEY = 'Escape';\n  const CLASS_NAME_SHOW$3 = 'show';\n  const CLASS_NAME_SHOWING$1 = 'showing';\n  const CLASS_NAME_HIDING = 'hiding';\n  const CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\n  const OPEN_SELECTOR = '.offcanvas.show';\n  const EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\n  const EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\n  const EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\n  const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\n  const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\n  const EVENT_RESIZE = `resize${EVENT_KEY$3}`;\n  const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\n  const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\n  const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\n  const Default$5 = {\n    backdrop: true,\n    keyboard: true,\n    scroll: false\n  };\n  const DefaultType$5 = {\n    backdrop: '(boolean|string)',\n    keyboard: 'boolean',\n    scroll: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Offcanvas extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._isShown = false;\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n\n      this._addEventListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default$5;\n    }\n\n    static get DefaultType() {\n      return DefaultType$5;\n    }\n\n    static get NAME() {\n      return NAME$6;\n    } // Public\n\n\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n\n    show(relatedTarget) {\n      if (this._isShown) {\n        return;\n      }\n\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n        relatedTarget\n      });\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = true;\n\n      this._backdrop.show();\n\n      if (!this._config.scroll) {\n        new ScrollBarHelper().hide();\n      }\n\n      this._element.setAttribute('aria-modal', true);\n\n      this._element.setAttribute('role', 'dialog');\n\n      this._element.classList.add(CLASS_NAME_SHOWING$1);\n\n      const completeCallBack = () => {\n        if (!this._config.scroll || this._config.backdrop) {\n          this._focustrap.activate();\n        }\n\n        this._element.classList.add(CLASS_NAME_SHOW$3);\n\n        this._element.classList.remove(CLASS_NAME_SHOWING$1);\n\n        EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n          relatedTarget\n        });\n      };\n\n      this._queueCallback(completeCallBack, this._element, true);\n    }\n\n    hide() {\n      if (!this._isShown) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._focustrap.deactivate();\n\n      this._element.blur();\n\n      this._isShown = false;\n\n      this._element.classList.add(CLASS_NAME_HIDING);\n\n      this._backdrop.hide();\n\n      const completeCallback = () => {\n        this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n\n        this._element.removeAttribute('aria-modal');\n\n        this._element.removeAttribute('role');\n\n        if (!this._config.scroll) {\n          new ScrollBarHelper().reset();\n        }\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n      };\n\n      this._queueCallback(completeCallback, this._element, true);\n    }\n\n    dispose() {\n      this._backdrop.dispose();\n\n      this._focustrap.deactivate();\n\n      super.dispose();\n    } // Private\n\n\n    _initializeBackDrop() {\n      const clickCallback = () => {\n        if (this._config.backdrop === 'static') {\n          EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n\n        this.hide();\n      }; // 'static' option will be translated to true, and booleans will keep their value\n\n\n      const isVisible = Boolean(this._config.backdrop);\n      return new Backdrop({\n        className: CLASS_NAME_BACKDROP,\n        isVisible,\n        isAnimated: true,\n        rootElement: this._element.parentNode,\n        clickCallback: isVisible ? clickCallback : null\n      });\n    }\n\n    _initializeFocusTrap() {\n      return new FocusTrap({\n        trapElement: this._element\n      });\n    }\n\n    _addEventListeners() {\n      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n        if (event.key !== ESCAPE_KEY) {\n          return;\n        }\n\n        if (!this._config.keyboard) {\n          EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n\n        this.hide();\n      });\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Offcanvas.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n    const target = getElementFromSelector(this);\n\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (isDisabled(this)) {\n      return;\n    }\n\n    EventHandler.one(target, EVENT_HIDDEN$3, () => {\n      // focus on trigger when it is closed\n      if (isVisible(this)) {\n        this.focus();\n      }\n    }); // avoid conflict when clicking a toggler of an offcanvas, while another is open\n\n    const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n\n    if (alreadyOpen && alreadyOpen !== target) {\n      Offcanvas.getInstance(alreadyOpen).hide();\n    }\n\n    const data = Offcanvas.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n    for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n      Offcanvas.getOrCreateInstance(selector).show();\n    }\n  });\n  EventHandler.on(window, EVENT_RESIZE, () => {\n    for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n      if (getComputedStyle(element).position !== 'fixed') {\n        Offcanvas.getOrCreateInstance(element).hide();\n      }\n    }\n  });\n  enableDismissTrigger(Offcanvas);\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Offcanvas);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/sanitizer.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n  const ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\n  /**\n   * A pattern that recognizes a commonly useful subset of URLs that are safe.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n   */\n\n  const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i;\n  /**\n   * A pattern that matches safe data URLs. Only matches image, video and audio types.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n   */\n\n  const DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i;\n\n  const allowedAttribute = (attribute, allowedAttributeList) => {\n    const attributeName = attribute.nodeName.toLowerCase();\n\n    if (allowedAttributeList.includes(attributeName)) {\n      if (uriAttributes.has(attributeName)) {\n        return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue));\n      }\n\n      return true;\n    } // Check if a regular expression validates the attribute.\n\n\n    return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n  };\n\n  const DefaultAllowlist = {\n    // Global attributes allowed on any supplied element below.\n    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n    a: ['target', 'href', 'title', 'rel'],\n    area: [],\n    b: [],\n    br: [],\n    col: [],\n    code: [],\n    div: [],\n    em: [],\n    hr: [],\n    h1: [],\n    h2: [],\n    h3: [],\n    h4: [],\n    h5: [],\n    h6: [],\n    i: [],\n    img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n    li: [],\n    ol: [],\n    p: [],\n    pre: [],\n    s: [],\n    small: [],\n    span: [],\n    sub: [],\n    sup: [],\n    strong: [],\n    u: [],\n    ul: []\n  };\n  function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n    if (!unsafeHtml.length) {\n      return unsafeHtml;\n    }\n\n    if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n      return sanitizeFunction(unsafeHtml);\n    }\n\n    const domParser = new window.DOMParser();\n    const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n    const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n\n    for (const element of elements) {\n      const elementName = element.nodeName.toLowerCase();\n\n      if (!Object.keys(allowList).includes(elementName)) {\n        element.remove();\n        continue;\n      }\n\n      const attributeList = [].concat(...element.attributes);\n      const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n\n      for (const attribute of attributeList) {\n        if (!allowedAttribute(attribute, allowedAttributes)) {\n          element.removeAttribute(attribute.nodeName);\n        }\n      }\n    }\n\n    return createdDocument.body.innerHTML;\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/template-factory.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$5 = 'TemplateFactory';\n  const Default$4 = {\n    allowList: DefaultAllowlist,\n    content: {},\n    // { selector : text ,  selector2 : text2 , }\n    extraClass: '',\n    html: false,\n    sanitize: true,\n    sanitizeFn: null,\n    template: '<div></div>'\n  };\n  const DefaultType$4 = {\n    allowList: 'object',\n    content: 'object',\n    extraClass: '(string|function)',\n    html: 'boolean',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    template: 'string'\n  };\n  const DefaultContentType = {\n    entry: '(string|element|function|null)',\n    selector: '(string|element)'\n  };\n  /**\n   * Class definition\n   */\n\n  class TemplateFactory extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n    } // Getters\n\n\n    static get Default() {\n      return Default$4;\n    }\n\n    static get DefaultType() {\n      return DefaultType$4;\n    }\n\n    static get NAME() {\n      return NAME$5;\n    } // Public\n\n\n    getContent() {\n      return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n    }\n\n    hasContent() {\n      return this.getContent().length > 0;\n    }\n\n    changeContent(content) {\n      this._checkContent(content);\n\n      this._config.content = { ...this._config.content,\n        ...content\n      };\n      return this;\n    }\n\n    toHtml() {\n      const templateWrapper = document.createElement('div');\n      templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n\n      for (const [selector, text] of Object.entries(this._config.content)) {\n        this._setContent(templateWrapper, text, selector);\n      }\n\n      const template = templateWrapper.children[0];\n\n      const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n\n      if (extraClass) {\n        template.classList.add(...extraClass.split(' '));\n      }\n\n      return template;\n    } // Private\n\n\n    _typeCheckConfig(config) {\n      super._typeCheckConfig(config);\n\n      this._checkContent(config.content);\n    }\n\n    _checkContent(arg) {\n      for (const [selector, content] of Object.entries(arg)) {\n        super._typeCheckConfig({\n          selector,\n          entry: content\n        }, DefaultContentType);\n      }\n    }\n\n    _setContent(template, content, selector) {\n      const templateElement = SelectorEngine.findOne(selector, template);\n\n      if (!templateElement) {\n        return;\n      }\n\n      content = this._resolvePossibleFunction(content);\n\n      if (!content) {\n        templateElement.remove();\n        return;\n      }\n\n      if (isElement$1(content)) {\n        this._putElementInTemplate(getElement(content), templateElement);\n\n        return;\n      }\n\n      if (this._config.html) {\n        templateElement.innerHTML = this._maybeSanitize(content);\n        return;\n      }\n\n      templateElement.textContent = content;\n    }\n\n    _maybeSanitize(arg) {\n      return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n    }\n\n    _resolvePossibleFunction(arg) {\n      return typeof arg === 'function' ? arg(this) : arg;\n    }\n\n    _putElementInTemplate(element, templateElement) {\n      if (this._config.html) {\n        templateElement.innerHTML = '';\n        templateElement.append(element);\n        return;\n      }\n\n      templateElement.textContent = element.textContent;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): tooltip.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$4 = 'tooltip';\n  const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\n  const CLASS_NAME_FADE$2 = 'fade';\n  const CLASS_NAME_MODAL = 'modal';\n  const CLASS_NAME_SHOW$2 = 'show';\n  const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\n  const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\n  const EVENT_MODAL_HIDE = 'hide.bs.modal';\n  const TRIGGER_HOVER = 'hover';\n  const TRIGGER_FOCUS = 'focus';\n  const TRIGGER_CLICK = 'click';\n  const TRIGGER_MANUAL = 'manual';\n  const EVENT_HIDE$2 = 'hide';\n  const EVENT_HIDDEN$2 = 'hidden';\n  const EVENT_SHOW$2 = 'show';\n  const EVENT_SHOWN$2 = 'shown';\n  const EVENT_INSERTED = 'inserted';\n  const EVENT_CLICK$1 = 'click';\n  const EVENT_FOCUSIN$1 = 'focusin';\n  const EVENT_FOCUSOUT$1 = 'focusout';\n  const EVENT_MOUSEENTER = 'mouseenter';\n  const EVENT_MOUSELEAVE = 'mouseleave';\n  const AttachmentMap = {\n    AUTO: 'auto',\n    TOP: 'top',\n    RIGHT: isRTL() ? 'left' : 'right',\n    BOTTOM: 'bottom',\n    LEFT: isRTL() ? 'right' : 'left'\n  };\n  const Default$3 = {\n    allowList: DefaultAllowlist,\n    animation: true,\n    boundary: 'clippingParents',\n    container: false,\n    customClass: '',\n    delay: 0,\n    fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n    html: false,\n    offset: [0, 0],\n    placement: 'top',\n    popperConfig: null,\n    sanitize: true,\n    sanitizeFn: null,\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\">' + '<div class=\"tooltip-arrow\"></div>' + '<div class=\"tooltip-inner\"></div>' + '</div>',\n    title: '',\n    trigger: 'hover focus'\n  };\n  const DefaultType$3 = {\n    allowList: 'object',\n    animation: 'boolean',\n    boundary: '(string|element)',\n    container: '(string|element|boolean)',\n    customClass: '(string|function)',\n    delay: '(number|object)',\n    fallbackPlacements: 'array',\n    html: 'boolean',\n    offset: '(array|string|function)',\n    placement: '(string|function)',\n    popperConfig: '(null|object|function)',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    selector: '(string|boolean)',\n    template: 'string',\n    title: '(string|element|function)',\n    trigger: 'string'\n  };\n  /**\n   * Class definition\n   */\n\n  class Tooltip extends BaseComponent {\n    constructor(element, config) {\n      if (typeof Popper === 'undefined') {\n        throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n      }\n\n      super(element, config); // Private\n\n      this._isEnabled = true;\n      this._timeout = 0;\n      this._isHovered = null;\n      this._activeTrigger = {};\n      this._popper = null;\n      this._templateFactory = null;\n      this._newContent = null; // Protected\n\n      this.tip = null;\n\n      this._setListeners();\n\n      if (!this._config.selector) {\n        this._fixTitle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default$3;\n    }\n\n    static get DefaultType() {\n      return DefaultType$3;\n    }\n\n    static get NAME() {\n      return NAME$4;\n    } // Public\n\n\n    enable() {\n      this._isEnabled = true;\n    }\n\n    disable() {\n      this._isEnabled = false;\n    }\n\n    toggleEnabled() {\n      this._isEnabled = !this._isEnabled;\n    }\n\n    toggle() {\n      if (!this._isEnabled) {\n        return;\n      }\n\n      this._activeTrigger.click = !this._activeTrigger.click;\n\n      if (this._isShown()) {\n        this._leave();\n\n        return;\n      }\n\n      this._enter();\n    }\n\n    dispose() {\n      clearTimeout(this._timeout);\n      EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n\n      if (this._element.getAttribute('data-bs-original-title')) {\n        this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n      }\n\n      this._disposePopper();\n\n      super.dispose();\n    }\n\n    show() {\n      if (this._element.style.display === 'none') {\n        throw new Error('Please use show on visible elements');\n      }\n\n      if (!(this._isWithContent() && this._isEnabled)) {\n        return;\n      }\n\n      const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n      const shadowRoot = findShadowRoot(this._element);\n\n      const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n\n      if (showEvent.defaultPrevented || !isInTheDom) {\n        return;\n      } // todo v6 remove this OR make it optional\n\n\n      this._disposePopper();\n\n      const tip = this._getTipElement();\n\n      this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n\n      const {\n        container\n      } = this._config;\n\n      if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n        container.append(tip);\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n      }\n\n      this._popper = this._createPopper(tip);\n      tip.classList.add(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.on(element, 'mouseover', noop);\n        }\n      }\n\n      const complete = () => {\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n\n        if (this._isHovered === false) {\n          this._leave();\n        }\n\n        this._isHovered = false;\n      };\n\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n\n    hide() {\n      if (!this._isShown()) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const tip = this._getTipElement();\n\n      tip.classList.remove(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.off(element, 'mouseover', noop);\n        }\n      }\n\n      this._activeTrigger[TRIGGER_CLICK] = false;\n      this._activeTrigger[TRIGGER_FOCUS] = false;\n      this._activeTrigger[TRIGGER_HOVER] = false;\n      this._isHovered = null; // it is a trick to support manual triggering\n\n      const complete = () => {\n        if (this._isWithActiveTrigger()) {\n          return;\n        }\n\n        if (!this._isHovered) {\n          this._disposePopper();\n        }\n\n        this._element.removeAttribute('aria-describedby');\n\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n      };\n\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n\n    update() {\n      if (this._popper) {\n        this._popper.update();\n      }\n    } // Protected\n\n\n    _isWithContent() {\n      return Boolean(this._getTitle());\n    }\n\n    _getTipElement() {\n      if (!this.tip) {\n        this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n      }\n\n      return this.tip;\n    }\n\n    _createTipElement(content) {\n      const tip = this._getTemplateFactory(content).toHtml(); // todo: remove this check on v6\n\n\n      if (!tip) {\n        return null;\n      }\n\n      tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); // todo: on v6 the following can be achieved with CSS only\n\n      tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n      const tipId = getUID(this.constructor.NAME).toString();\n      tip.setAttribute('id', tipId);\n\n      if (this._isAnimated()) {\n        tip.classList.add(CLASS_NAME_FADE$2);\n      }\n\n      return tip;\n    }\n\n    setContent(content) {\n      this._newContent = content;\n\n      if (this._isShown()) {\n        this._disposePopper();\n\n        this.show();\n      }\n    }\n\n    _getTemplateFactory(content) {\n      if (this._templateFactory) {\n        this._templateFactory.changeContent(content);\n      } else {\n        this._templateFactory = new TemplateFactory({ ...this._config,\n          // the `content` var has to be after `this._config`\n          // to override config.content in case of popover\n          content,\n          extraClass: this._resolvePossibleFunction(this._config.customClass)\n        });\n      }\n\n      return this._templateFactory;\n    }\n\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n      };\n    }\n\n    _getTitle() {\n      return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n    } // Private\n\n\n    _initializeOnDelegatedTarget(event) {\n      return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n    }\n\n    _isAnimated() {\n      return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n    }\n\n    _isShown() {\n      return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n    }\n\n    _createPopper(tip) {\n      const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement;\n      const attachment = AttachmentMap[placement.toUpperCase()];\n      return createPopper(this._element, tip, this._getPopperConfig(attachment));\n    }\n\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n\n      return offset;\n    }\n\n    _resolvePossibleFunction(arg) {\n      return typeof arg === 'function' ? arg.call(this._element) : arg;\n    }\n\n    _getPopperConfig(attachment) {\n      const defaultBsPopperConfig = {\n        placement: attachment,\n        modifiers: [{\n          name: 'flip',\n          options: {\n            fallbackPlacements: this._config.fallbackPlacements\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }, {\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'arrow',\n          options: {\n            element: `.${this.constructor.NAME}-arrow`\n          }\n        }, {\n          name: 'preSetPlacement',\n          enabled: true,\n          phase: 'beforeMain',\n          fn: data => {\n            // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n            // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n            this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n          }\n        }]\n      };\n      return { ...defaultBsPopperConfig,\n        ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n      };\n    }\n\n    _setListeners() {\n      const triggers = this._config.trigger.split(' ');\n\n      for (const trigger of triggers) {\n        if (trigger === 'click') {\n          EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context.toggle();\n          });\n        } else if (trigger !== TRIGGER_MANUAL) {\n          const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n          const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n          EventHandler.on(this._element, eventIn, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n\n            context._enter();\n          });\n          EventHandler.on(this._element, eventOut, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n\n            context._leave();\n          });\n        }\n      }\n\n      this._hideModalHandler = () => {\n        if (this._element) {\n          this.hide();\n        }\n      };\n\n      EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n    }\n\n    _fixTitle() {\n      const title = this._element.getAttribute('title');\n\n      if (!title) {\n        return;\n      }\n\n      if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n        this._element.setAttribute('aria-label', title);\n      }\n\n      this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n\n\n      this._element.removeAttribute('title');\n    }\n\n    _enter() {\n      if (this._isShown() || this._isHovered) {\n        this._isHovered = true;\n        return;\n      }\n\n      this._isHovered = true;\n\n      this._setTimeout(() => {\n        if (this._isHovered) {\n          this.show();\n        }\n      }, this._config.delay.show);\n    }\n\n    _leave() {\n      if (this._isWithActiveTrigger()) {\n        return;\n      }\n\n      this._isHovered = false;\n\n      this._setTimeout(() => {\n        if (!this._isHovered) {\n          this.hide();\n        }\n      }, this._config.delay.hide);\n    }\n\n    _setTimeout(handler, timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = setTimeout(handler, timeout);\n    }\n\n    _isWithActiveTrigger() {\n      return Object.values(this._activeTrigger).includes(true);\n    }\n\n    _getConfig(config) {\n      const dataAttributes = Manipulator.getDataAttributes(this._element);\n\n      for (const dataAttribute of Object.keys(dataAttributes)) {\n        if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n          delete dataAttributes[dataAttribute];\n        }\n      }\n\n      config = { ...dataAttributes,\n        ...(typeof config === 'object' && config ? config : {})\n      };\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    }\n\n    _configAfterMerge(config) {\n      config.container = config.container === false ? document.body : getElement(config.container);\n\n      if (typeof config.delay === 'number') {\n        config.delay = {\n          show: config.delay,\n          hide: config.delay\n        };\n      }\n\n      if (typeof config.title === 'number') {\n        config.title = config.title.toString();\n      }\n\n      if (typeof config.content === 'number') {\n        config.content = config.content.toString();\n      }\n\n      return config;\n    }\n\n    _getDelegateConfig() {\n      const config = {};\n\n      for (const key in this._config) {\n        if (this.constructor.Default[key] !== this._config[key]) {\n          config[key] = this._config[key];\n        }\n      }\n\n      config.selector = false;\n      config.trigger = 'manual'; // In the future can be replaced with:\n      // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n      // `Object.fromEntries(keysWithDifferentValues)`\n\n      return config;\n    }\n\n    _disposePopper() {\n      if (this._popper) {\n        this._popper.destroy();\n\n        this._popper = null;\n      }\n\n      if (this.tip) {\n        this.tip.remove();\n        this.tip = null;\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tooltip.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * jQuery\n   */\n\n\n  defineJQueryPlugin(Tooltip);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): popover.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$3 = 'popover';\n  const SELECTOR_TITLE = '.popover-header';\n  const SELECTOR_CONTENT = '.popover-body';\n  const Default$2 = { ...Tooltip.Default,\n    content: '',\n    offset: [0, 8],\n    placement: 'right',\n    template: '<div class=\"popover\" role=\"tooltip\">' + '<div class=\"popover-arrow\"></div>' + '<h3 class=\"popover-header\"></h3>' + '<div class=\"popover-body\"></div>' + '</div>',\n    trigger: 'click'\n  };\n  const DefaultType$2 = { ...Tooltip.DefaultType,\n    content: '(null|string|element|function)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Popover extends Tooltip {\n    // Getters\n    static get Default() {\n      return Default$2;\n    }\n\n    static get DefaultType() {\n      return DefaultType$2;\n    }\n\n    static get NAME() {\n      return NAME$3;\n    } // Overrides\n\n\n    _isWithContent() {\n      return this._getTitle() || this._getContent();\n    } // Private\n\n\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TITLE]: this._getTitle(),\n        [SELECTOR_CONTENT]: this._getContent()\n      };\n    }\n\n    _getContent() {\n      return this._resolvePossibleFunction(this._config.content);\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Popover.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * jQuery\n   */\n\n\n  defineJQueryPlugin(Popover);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): scrollspy.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$2 = 'scrollspy';\n  const DATA_KEY$2 = 'bs.scrollspy';\n  const EVENT_KEY$2 = `.${DATA_KEY$2}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\n  const EVENT_CLICK = `click${EVENT_KEY$2}`;\n  const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\n  const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\n  const CLASS_NAME_ACTIVE$1 = 'active';\n  const SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\n  const SELECTOR_TARGET_LINKS = '[href]';\n  const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\n  const SELECTOR_NAV_LINKS = '.nav-link';\n  const SELECTOR_NAV_ITEMS = '.nav-item';\n  const SELECTOR_LIST_ITEMS = '.list-group-item';\n  const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\n  const SELECTOR_DROPDOWN = '.dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\n  const Default$1 = {\n    offset: null,\n    // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: '0px 0px -25%',\n    smoothScroll: false,\n    target: null,\n    threshold: [0.1, 0.5, 1]\n  };\n  const DefaultType$1 = {\n    offset: '(number|null)',\n    // TODO v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: 'string',\n    smoothScroll: 'boolean',\n    target: 'element',\n    threshold: 'array'\n  };\n  /**\n   * Class definition\n   */\n\n  class ScrollSpy extends BaseComponent {\n    constructor(element, config) {\n      super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper\n\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n      this._activeTarget = null;\n      this._observer = null;\n      this._previousScrollData = {\n        visibleEntryTop: 0,\n        parentScrollTop: 0\n      };\n      this.refresh(); // initialize\n    } // Getters\n\n\n    static get Default() {\n      return Default$1;\n    }\n\n    static get DefaultType() {\n      return DefaultType$1;\n    }\n\n    static get NAME() {\n      return NAME$2;\n    } // Public\n\n\n    refresh() {\n      this._initializeTargetsAndObservables();\n\n      this._maybeEnableSmoothScroll();\n\n      if (this._observer) {\n        this._observer.disconnect();\n      } else {\n        this._observer = this._getNewObserver();\n      }\n\n      for (const section of this._observableSections.values()) {\n        this._observer.observe(section);\n      }\n    }\n\n    dispose() {\n      this._observer.disconnect();\n\n      super.dispose();\n    } // Private\n\n\n    _configAfterMerge(config) {\n      // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n      config.target = getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n\n      config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n\n      if (typeof config.threshold === 'string') {\n        config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n      }\n\n      return config;\n    }\n\n    _maybeEnableSmoothScroll() {\n      if (!this._config.smoothScroll) {\n        return;\n      } // unregister any previous listeners\n\n\n      EventHandler.off(this._config.target, EVENT_CLICK);\n      EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n        const observableSection = this._observableSections.get(event.target.hash);\n\n        if (observableSection) {\n          event.preventDefault();\n          const root = this._rootElement || window;\n          const height = observableSection.offsetTop - this._element.offsetTop;\n\n          if (root.scrollTo) {\n            root.scrollTo({\n              top: height,\n              behavior: 'smooth'\n            });\n            return;\n          } // Chrome 60 doesn't support `scrollTo`\n\n\n          root.scrollTop = height;\n        }\n      });\n    }\n\n    _getNewObserver() {\n      const options = {\n        root: this._rootElement,\n        threshold: this._config.threshold,\n        rootMargin: this._config.rootMargin\n      };\n      return new IntersectionObserver(entries => this._observerCallback(entries), options);\n    } // The logic of selection\n\n\n    _observerCallback(entries) {\n      const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n\n      const activate = entry => {\n        this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n\n        this._process(targetElement(entry));\n      };\n\n      const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n      const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n      this._previousScrollData.parentScrollTop = parentScrollTop;\n\n      for (const entry of entries) {\n        if (!entry.isIntersecting) {\n          this._activeTarget = null;\n\n          this._clearActiveClass(targetElement(entry));\n\n          continue;\n        }\n\n        const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop\n\n        if (userScrollsDown && entryIsLowerThanPrevious) {\n          activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n\n          if (!parentScrollTop) {\n            return;\n          }\n\n          continue;\n        } // if we are scrolling up, pick the smallest offsetTop\n\n\n        if (!userScrollsDown && !entryIsLowerThanPrevious) {\n          activate(entry);\n        }\n      }\n    }\n\n    _initializeTargetsAndObservables() {\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n\n      for (const anchor of targetLinks) {\n        // ensure that the anchor has an id and is not disabled\n        if (!anchor.hash || isDisabled(anchor)) {\n          continue;\n        }\n\n        const observableSection = SelectorEngine.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible\n\n        if (isVisible(observableSection)) {\n          this._targetLinks.set(anchor.hash, anchor);\n\n          this._observableSections.set(anchor.hash, observableSection);\n        }\n      }\n    }\n\n    _process(target) {\n      if (this._activeTarget === target) {\n        return;\n      }\n\n      this._clearActiveClass(this._config.target);\n\n      this._activeTarget = target;\n      target.classList.add(CLASS_NAME_ACTIVE$1);\n\n      this._activateParents(target);\n\n      EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n        relatedTarget: target\n      });\n    }\n\n    _activateParents(target) {\n      // Activate dropdown parents\n      if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n        SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n        return;\n      }\n\n      for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n        // Set triggered links parents as active\n        // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n        for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n          item.classList.add(CLASS_NAME_ACTIVE$1);\n        }\n      }\n    }\n\n    _clearActiveClass(parent) {\n      parent.classList.remove(CLASS_NAME_ACTIVE$1);\n      const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE$1}`, parent);\n\n      for (const node of activeNodes) {\n        node.classList.remove(CLASS_NAME_ACTIVE$1);\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = ScrollSpy.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(window, EVENT_LOAD_DATA_API$1, () => {\n    for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n      ScrollSpy.getOrCreateInstance(spy);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(ScrollSpy);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): tab.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$1 = 'tab';\n  const DATA_KEY$1 = 'bs.tab';\n  const EVENT_KEY$1 = `.${DATA_KEY$1}`;\n  const EVENT_HIDE$1 = `hide${EVENT_KEY$1}`;\n  const EVENT_HIDDEN$1 = `hidden${EVENT_KEY$1}`;\n  const EVENT_SHOW$1 = `show${EVENT_KEY$1}`;\n  const EVENT_SHOWN$1 = `shown${EVENT_KEY$1}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY$1}`;\n  const EVENT_KEYDOWN = `keydown${EVENT_KEY$1}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY$1}`;\n  const ARROW_LEFT_KEY = 'ArrowLeft';\n  const ARROW_RIGHT_KEY = 'ArrowRight';\n  const ARROW_UP_KEY = 'ArrowUp';\n  const ARROW_DOWN_KEY = 'ArrowDown';\n  const CLASS_NAME_ACTIVE = 'active';\n  const CLASS_NAME_FADE$1 = 'fade';\n  const CLASS_NAME_SHOW$1 = 'show';\n  const CLASS_DROPDOWN = 'dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\n  const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';\n  const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)';\n  const SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]';\n  const SELECTOR_OUTER = '.nav-item, .list-group-item';\n  const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]'; // todo:v6: could be only `tab`\n\n  const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`;\n  /**\n   * Class definition\n   */\n\n  class Tab extends BaseComponent {\n    constructor(element) {\n      super(element);\n      this._parent = this._element.closest(SELECTOR_TAB_PANEL);\n\n      if (!this._parent) {\n        return; // todo: should Throw exception on v6\n        // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n      } // Set up initial aria attributes\n\n\n      this._setInitialAttributes(this._parent, this._getChildren());\n\n      EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n    } // Getters\n\n\n    static get NAME() {\n      return NAME$1;\n    } // Public\n\n\n    show() {\n      // Shows this elem and deactivate the active sibling if exists\n      const innerElem = this._element;\n\n      if (this._elemIsActive(innerElem)) {\n        return;\n      } // Search for active tab on same parent to deactivate it\n\n\n      const active = this._getActiveElem();\n\n      const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE$1, {\n        relatedTarget: innerElem\n      }) : null;\n      const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW$1, {\n        relatedTarget: active\n      });\n\n      if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._deactivate(active, innerElem);\n\n      this._activate(innerElem, active);\n    } // Private\n\n\n    _activate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n\n      element.classList.add(CLASS_NAME_ACTIVE);\n\n      this._activate(getElementFromSelector(element)); // Search and activate/show the proper section\n\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.add(CLASS_NAME_SHOW$1);\n          return;\n        }\n\n        element.removeAttribute('tabindex');\n        element.setAttribute('aria-selected', true);\n\n        this._toggleDropDown(element, true);\n\n        EventHandler.trigger(element, EVENT_SHOWN$1, {\n          relatedTarget: relatedElem\n        });\n      };\n\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1));\n    }\n\n    _deactivate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n\n      element.classList.remove(CLASS_NAME_ACTIVE);\n      element.blur();\n\n      this._deactivate(getElementFromSelector(element)); // Search and deactivate the shown section too\n\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.remove(CLASS_NAME_SHOW$1);\n          return;\n        }\n\n        element.setAttribute('aria-selected', false);\n        element.setAttribute('tabindex', '-1');\n\n        this._toggleDropDown(element, false);\n\n        EventHandler.trigger(element, EVENT_HIDDEN$1, {\n          relatedTarget: relatedElem\n        });\n      };\n\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1));\n    }\n\n    _keydown(event) {\n      if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {\n        return;\n      }\n\n      event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n\n      event.preventDefault();\n      const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);\n      const nextActiveElement = getNextActiveElement(this._getChildren().filter(element => !isDisabled(element)), event.target, isNext, true);\n\n      if (nextActiveElement) {\n        nextActiveElement.focus({\n          preventScroll: true\n        });\n        Tab.getOrCreateInstance(nextActiveElement).show();\n      }\n    }\n\n    _getChildren() {\n      // collection of inner elements\n      return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent);\n    }\n\n    _getActiveElem() {\n      return this._getChildren().find(child => this._elemIsActive(child)) || null;\n    }\n\n    _setInitialAttributes(parent, children) {\n      this._setAttributeIfNotExists(parent, 'role', 'tablist');\n\n      for (const child of children) {\n        this._setInitialAttributesOnChild(child);\n      }\n    }\n\n    _setInitialAttributesOnChild(child) {\n      child = this._getInnerElement(child);\n\n      const isActive = this._elemIsActive(child);\n\n      const outerElem = this._getOuterElement(child);\n\n      child.setAttribute('aria-selected', isActive);\n\n      if (outerElem !== child) {\n        this._setAttributeIfNotExists(outerElem, 'role', 'presentation');\n      }\n\n      if (!isActive) {\n        child.setAttribute('tabindex', '-1');\n      }\n\n      this._setAttributeIfNotExists(child, 'role', 'tab'); // set attributes to the related panel too\n\n\n      this._setInitialAttributesOnTargetPanel(child);\n    }\n\n    _setInitialAttributesOnTargetPanel(child) {\n      const target = getElementFromSelector(child);\n\n      if (!target) {\n        return;\n      }\n\n      this._setAttributeIfNotExists(target, 'role', 'tabpanel');\n\n      if (child.id) {\n        this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`);\n      }\n    }\n\n    _toggleDropDown(element, open) {\n      const outerElem = this._getOuterElement(element);\n\n      if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n        return;\n      }\n\n      const toggle = (selector, className) => {\n        const element = SelectorEngine.findOne(selector, outerElem);\n\n        if (element) {\n          element.classList.toggle(className, open);\n        }\n      };\n\n      toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);\n      toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW$1);\n      outerElem.setAttribute('aria-expanded', open);\n    }\n\n    _setAttributeIfNotExists(element, attribute, value) {\n      if (!element.hasAttribute(attribute)) {\n        element.setAttribute(attribute, value);\n      }\n    }\n\n    _elemIsActive(elem) {\n      return elem.classList.contains(CLASS_NAME_ACTIVE);\n    } // Try to get the inner element (usually the .nav-link)\n\n\n    _getInnerElement(elem) {\n      return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem);\n    } // Try to get the outer element (usually the .nav-item)\n\n\n    _getOuterElement(elem) {\n      return elem.closest(SELECTOR_OUTER) || elem;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tab.getOrCreateInstance(this);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (isDisabled(this)) {\n      return;\n    }\n\n    Tab.getOrCreateInstance(this).show();\n  });\n  /**\n   * Initialize on focus\n   */\n\n  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n      Tab.getOrCreateInstance(element);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Tab);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): toast.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'toast';\n  const DATA_KEY = 'bs.toast';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;\n  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;\n  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\n  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_HIDE = 'hide'; // @deprecated - kept here only for backwards compatibility\n\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_SHOWING = 'showing';\n  const DefaultType = {\n    animation: 'boolean',\n    autohide: 'boolean',\n    delay: 'number'\n  };\n  const Default = {\n    animation: true,\n    autohide: true,\n    delay: 5000\n  };\n  /**\n   * Class definition\n   */\n\n  class Toast extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._timeout = null;\n      this._hasMouseInteraction = false;\n      this._hasKeyboardInteraction = false;\n\n      this._setListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    show() {\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._clearTimeout();\n\n      if (this._config.animation) {\n        this._element.classList.add(CLASS_NAME_FADE);\n      }\n\n      const complete = () => {\n        this._element.classList.remove(CLASS_NAME_SHOWING);\n\n        EventHandler.trigger(this._element, EVENT_SHOWN);\n\n        this._maybeScheduleHide();\n      };\n\n      this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated\n\n\n      reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING);\n\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n\n    hide() {\n      if (!this.isShown()) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const complete = () => {\n        this._element.classList.add(CLASS_NAME_HIDE); // @deprecated\n\n\n        this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW);\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN);\n      };\n\n      this._element.classList.add(CLASS_NAME_SHOWING);\n\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n\n    dispose() {\n      this._clearTimeout();\n\n      if (this.isShown()) {\n        this._element.classList.remove(CLASS_NAME_SHOW);\n      }\n\n      super.dispose();\n    }\n\n    isShown() {\n      return this._element.classList.contains(CLASS_NAME_SHOW);\n    } // Private\n\n\n    _maybeScheduleHide() {\n      if (!this._config.autohide) {\n        return;\n      }\n\n      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n        return;\n      }\n\n      this._timeout = setTimeout(() => {\n        this.hide();\n      }, this._config.delay);\n    }\n\n    _onInteraction(event, isInteracting) {\n      switch (event.type) {\n        case 'mouseover':\n        case 'mouseout':\n          {\n            this._hasMouseInteraction = isInteracting;\n            break;\n          }\n\n        case 'focusin':\n        case 'focusout':\n          {\n            this._hasKeyboardInteraction = isInteracting;\n            break;\n          }\n      }\n\n      if (isInteracting) {\n        this._clearTimeout();\n\n        return;\n      }\n\n      const nextElement = event.relatedTarget;\n\n      if (this._element === nextElement || this._element.contains(nextElement)) {\n        return;\n      }\n\n      this._maybeScheduleHide();\n    }\n\n    _setListeners() {\n      EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));\n      EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));\n      EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));\n      EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));\n    }\n\n    _clearTimeout() {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Toast.getOrCreateInstance(this, config);\n\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config](this);\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  enableDismissTrigger(Toast);\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Toast);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): index.umd.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const index_umd = {\n    Alert,\n    Button,\n    Carousel,\n    Collapse,\n    Dropdown,\n    Modal,\n    Offcanvas,\n    Popover,\n    ScrollSpy,\n    Tab,\n    Toast,\n    Tooltip\n  };\n\n  return index_umd;\n\n}));\n//# sourceMappingURL=bootstrap.bundle.js.map\n"
  },
  {
    "path": "src/common/bootstrap/dist/js/bootstrap.esm.js",
    "content": "/*!\n  * Bootstrap v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\nimport * as Popper from '@popperjs/core';\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\nconst MAX_UID = 1000000;\nconst MILLISECONDS_MULTIPLIER = 1000;\nconst TRANSITION_END = 'transitionend'; // Shout-out Angus Croll (https://goo.gl/pxwQGp)\n\nconst toType = object => {\n  if (object === null || object === undefined) {\n    return `${object}`;\n  }\n\n  return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n};\n/**\n * Public Util API\n */\n\n\nconst getUID = prefix => {\n  do {\n    prefix += Math.floor(Math.random() * MAX_UID);\n  } while (document.getElementById(prefix));\n\n  return prefix;\n};\n\nconst getSelector = element => {\n  let selector = element.getAttribute('data-bs-target');\n\n  if (!selector || selector === '#') {\n    let hrefAttribute = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,\n    // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n    // `document.querySelector` will rightfully complain it is invalid.\n    // See https://github.com/twbs/bootstrap/issues/32273\n\n    if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n      return null;\n    } // Just in case some CMS puts out a full URL with the anchor appended\n\n\n    if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n      hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n    }\n\n    selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n  }\n\n  return selector;\n};\n\nconst getSelectorFromElement = element => {\n  const selector = getSelector(element);\n\n  if (selector) {\n    return document.querySelector(selector) ? selector : null;\n  }\n\n  return null;\n};\n\nconst getElementFromSelector = element => {\n  const selector = getSelector(element);\n  return selector ? document.querySelector(selector) : null;\n};\n\nconst getTransitionDurationFromElement = element => {\n  if (!element) {\n    return 0;\n  } // Get transition-duration of the element\n\n\n  let {\n    transitionDuration,\n    transitionDelay\n  } = window.getComputedStyle(element);\n  const floatTransitionDuration = Number.parseFloat(transitionDuration);\n  const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found\n\n  if (!floatTransitionDuration && !floatTransitionDelay) {\n    return 0;\n  } // If multiple durations are defined, take the first\n\n\n  transitionDuration = transitionDuration.split(',')[0];\n  transitionDelay = transitionDelay.split(',')[0];\n  return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n};\n\nconst triggerTransitionEnd = element => {\n  element.dispatchEvent(new Event(TRANSITION_END));\n};\n\nconst isElement = object => {\n  if (!object || typeof object !== 'object') {\n    return false;\n  }\n\n  if (typeof object.jquery !== 'undefined') {\n    object = object[0];\n  }\n\n  return typeof object.nodeType !== 'undefined';\n};\n\nconst getElement = object => {\n  // it's a jQuery object or a node element\n  if (isElement(object)) {\n    return object.jquery ? object[0] : object;\n  }\n\n  if (typeof object === 'string' && object.length > 0) {\n    return document.querySelector(object);\n  }\n\n  return null;\n};\n\nconst isVisible = element => {\n  if (!isElement(element) || element.getClientRects().length === 0) {\n    return false;\n  }\n\n  const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; // Handle `details` element as its content may falsie appear visible when it is closed\n\n  const closedDetails = element.closest('details:not([open])');\n\n  if (!closedDetails) {\n    return elementIsVisible;\n  }\n\n  if (closedDetails !== element) {\n    const summary = element.closest('summary');\n\n    if (summary && summary.parentNode !== closedDetails) {\n      return false;\n    }\n\n    if (summary === null) {\n      return false;\n    }\n  }\n\n  return elementIsVisible;\n};\n\nconst isDisabled = element => {\n  if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n    return true;\n  }\n\n  if (element.classList.contains('disabled')) {\n    return true;\n  }\n\n  if (typeof element.disabled !== 'undefined') {\n    return element.disabled;\n  }\n\n  return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n};\n\nconst findShadowRoot = element => {\n  if (!document.documentElement.attachShadow) {\n    return null;\n  } // Can find the shadow root otherwise it'll return the document\n\n\n  if (typeof element.getRootNode === 'function') {\n    const root = element.getRootNode();\n    return root instanceof ShadowRoot ? root : null;\n  }\n\n  if (element instanceof ShadowRoot) {\n    return element;\n  } // when we don't find a shadow root\n\n\n  if (!element.parentNode) {\n    return null;\n  }\n\n  return findShadowRoot(element.parentNode);\n};\n\nconst noop = () => {};\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\n\n\nconst reflow = element => {\n  element.offsetHeight; // eslint-disable-line no-unused-expressions\n};\n\nconst getjQuery = () => {\n  if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n    return window.jQuery;\n  }\n\n  return null;\n};\n\nconst DOMContentLoadedCallbacks = [];\n\nconst onDOMContentLoaded = callback => {\n  if (document.readyState === 'loading') {\n    // add listener on the first call when the document is in loading state\n    if (!DOMContentLoadedCallbacks.length) {\n      document.addEventListener('DOMContentLoaded', () => {\n        for (const callback of DOMContentLoadedCallbacks) {\n          callback();\n        }\n      });\n    }\n\n    DOMContentLoadedCallbacks.push(callback);\n  } else {\n    callback();\n  }\n};\n\nconst isRTL = () => document.documentElement.dir === 'rtl';\n\nconst defineJQueryPlugin = plugin => {\n  onDOMContentLoaded(() => {\n    const $ = getjQuery();\n    /* istanbul ignore if */\n\n    if ($) {\n      const name = plugin.NAME;\n      const JQUERY_NO_CONFLICT = $.fn[name];\n      $.fn[name] = plugin.jQueryInterface;\n      $.fn[name].Constructor = plugin;\n\n      $.fn[name].noConflict = () => {\n        $.fn[name] = JQUERY_NO_CONFLICT;\n        return plugin.jQueryInterface;\n      };\n    }\n  });\n};\n\nconst execute = callback => {\n  if (typeof callback === 'function') {\n    callback();\n  }\n};\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n  if (!waitForTransition) {\n    execute(callback);\n    return;\n  }\n\n  const durationPadding = 5;\n  const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n  let called = false;\n\n  const handler = ({\n    target\n  }) => {\n    if (target !== transitionElement) {\n      return;\n    }\n\n    called = true;\n    transitionElement.removeEventListener(TRANSITION_END, handler);\n    execute(callback);\n  };\n\n  transitionElement.addEventListener(TRANSITION_END, handler);\n  setTimeout(() => {\n    if (!called) {\n      triggerTransitionEnd(transitionElement);\n    }\n  }, emulatedDuration);\n};\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list    The list of elements\n * @param activeElement   The active element\n * @param shouldGetNext   Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\n\n\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n  const listLength = list.length;\n  let index = list.indexOf(activeElement); // if the element does not exist in the list return an element\n  // depending on the direction and if cycle is allowed\n\n  if (index === -1) {\n    return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n  }\n\n  index += shouldGetNext ? 1 : -1;\n\n  if (isCycleAllowed) {\n    index = (index + listLength) % listLength;\n  }\n\n  return list[Math.max(0, Math.min(index, listLength - 1))];\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\nconst stripNameRegex = /\\..*/;\nconst stripUidRegex = /::\\d+$/;\nconst eventRegistry = {}; // Events storage\n\nlet uidEvent = 1;\nconst customEvents = {\n  mouseenter: 'mouseover',\n  mouseleave: 'mouseout'\n};\nconst nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n  return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n}\n\nfunction getElementEvents(element) {\n  const uid = makeEventUid(element);\n  element.uidEvent = uid;\n  eventRegistry[uid] = eventRegistry[uid] || {};\n  return eventRegistry[uid];\n}\n\nfunction bootstrapHandler(element, fn) {\n  return function handler(event) {\n    hydrateObj(event, {\n      delegateTarget: element\n    });\n\n    if (handler.oneOff) {\n      EventHandler.off(element, event.type, fn);\n    }\n\n    return fn.apply(element, [event]);\n  };\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n  return function handler(event) {\n    const domElements = element.querySelectorAll(selector);\n\n    for (let {\n      target\n    } = event; target && target !== this; target = target.parentNode) {\n      for (const domElement of domElements) {\n        if (domElement !== target) {\n          continue;\n        }\n\n        hydrateObj(event, {\n          delegateTarget: target\n        });\n\n        if (handler.oneOff) {\n          EventHandler.off(element, event.type, selector, fn);\n        }\n\n        return fn.apply(target, [event]);\n      }\n    }\n  };\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n  return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n  const isDelegated = typeof handler === 'string'; // todo: tooltip passes `false` instead of selector, so we need to check\n\n  const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n  let typeEvent = getTypeEvent(originalTypeEvent);\n\n  if (!nativeEvents.has(typeEvent)) {\n    typeEvent = originalTypeEvent;\n  }\n\n  return [isDelegated, callable, typeEvent];\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n  if (typeof originalTypeEvent !== 'string' || !element) {\n    return;\n  }\n\n  let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n  // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n\n  if (originalTypeEvent in customEvents) {\n    const wrapFunction = fn => {\n      return function (event) {\n        if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n          return fn.call(this, event);\n        }\n      };\n    };\n\n    callable = wrapFunction(callable);\n  }\n\n  const events = getElementEvents(element);\n  const handlers = events[typeEvent] || (events[typeEvent] = {});\n  const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n\n  if (previousFunction) {\n    previousFunction.oneOff = previousFunction.oneOff && oneOff;\n    return;\n  }\n\n  const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n  const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n  fn.delegationSelector = isDelegated ? handler : null;\n  fn.callable = callable;\n  fn.oneOff = oneOff;\n  fn.uidEvent = uid;\n  handlers[uid] = fn;\n  element.addEventListener(typeEvent, fn, isDelegated);\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n  const fn = findHandler(events[typeEvent], handler, delegationSelector);\n\n  if (!fn) {\n    return;\n  }\n\n  element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n  delete events[typeEvent][fn.uidEvent];\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n  const storeElementEvent = events[typeEvent] || {};\n\n  for (const handlerKey of Object.keys(storeElementEvent)) {\n    if (handlerKey.includes(namespace)) {\n      const event = storeElementEvent[handlerKey];\n      removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n    }\n  }\n}\n\nfunction getTypeEvent(event) {\n  // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n  event = event.replace(stripNameRegex, '');\n  return customEvents[event] || event;\n}\n\nconst EventHandler = {\n  on(element, event, handler, delegationFunction) {\n    addHandler(element, event, handler, delegationFunction, false);\n  },\n\n  one(element, event, handler, delegationFunction) {\n    addHandler(element, event, handler, delegationFunction, true);\n  },\n\n  off(element, originalTypeEvent, handler, delegationFunction) {\n    if (typeof originalTypeEvent !== 'string' || !element) {\n      return;\n    }\n\n    const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n    const inNamespace = typeEvent !== originalTypeEvent;\n    const events = getElementEvents(element);\n    const storeElementEvent = events[typeEvent] || {};\n    const isNamespace = originalTypeEvent.startsWith('.');\n\n    if (typeof callable !== 'undefined') {\n      // Simplest case: handler is passed, remove that listener ONLY.\n      if (!Object.keys(storeElementEvent).length) {\n        return;\n      }\n\n      removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n      return;\n    }\n\n    if (isNamespace) {\n      for (const elementEvent of Object.keys(events)) {\n        removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n      }\n    }\n\n    for (const keyHandlers of Object.keys(storeElementEvent)) {\n      const handlerKey = keyHandlers.replace(stripUidRegex, '');\n\n      if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n        const event = storeElementEvent[keyHandlers];\n        removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n      }\n    }\n  },\n\n  trigger(element, event, args) {\n    if (typeof event !== 'string' || !element) {\n      return null;\n    }\n\n    const $ = getjQuery();\n    const typeEvent = getTypeEvent(event);\n    const inNamespace = event !== typeEvent;\n    let jQueryEvent = null;\n    let bubbles = true;\n    let nativeDispatch = true;\n    let defaultPrevented = false;\n\n    if (inNamespace && $) {\n      jQueryEvent = $.Event(event, args);\n      $(element).trigger(jQueryEvent);\n      bubbles = !jQueryEvent.isPropagationStopped();\n      nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n      defaultPrevented = jQueryEvent.isDefaultPrevented();\n    }\n\n    let evt = new Event(event, {\n      bubbles,\n      cancelable: true\n    });\n    evt = hydrateObj(evt, args);\n\n    if (defaultPrevented) {\n      evt.preventDefault();\n    }\n\n    if (nativeDispatch) {\n      element.dispatchEvent(evt);\n    }\n\n    if (evt.defaultPrevented && jQueryEvent) {\n      jQueryEvent.preventDefault();\n    }\n\n    return evt;\n  }\n\n};\n\nfunction hydrateObj(obj, meta) {\n  for (const [key, value] of Object.entries(meta || {})) {\n    try {\n      obj[key] = value;\n    } catch (_unused) {\n      Object.defineProperty(obj, key, {\n        configurable: true,\n\n        get() {\n          return value;\n        }\n\n      });\n    }\n  }\n\n  return obj;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\nconst elementMap = new Map();\nconst Data = {\n  set(element, key, instance) {\n    if (!elementMap.has(element)) {\n      elementMap.set(element, new Map());\n    }\n\n    const instanceMap = elementMap.get(element); // make it clear we only want one instance per element\n    // can be removed later when multiple key/instances are fine to be used\n\n    if (!instanceMap.has(key) && instanceMap.size !== 0) {\n      // eslint-disable-next-line no-console\n      console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n      return;\n    }\n\n    instanceMap.set(key, instance);\n  },\n\n  get(element, key) {\n    if (elementMap.has(element)) {\n      return elementMap.get(element).get(key) || null;\n    }\n\n    return null;\n  },\n\n  remove(element, key) {\n    if (!elementMap.has(element)) {\n      return;\n    }\n\n    const instanceMap = elementMap.get(element);\n    instanceMap.delete(key); // free up element references if there are no instances left for an element\n\n    if (instanceMap.size === 0) {\n      elementMap.delete(element);\n    }\n  }\n\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\nfunction normalizeData(value) {\n  if (value === 'true') {\n    return true;\n  }\n\n  if (value === 'false') {\n    return false;\n  }\n\n  if (value === Number(value).toString()) {\n    return Number(value);\n  }\n\n  if (value === '' || value === 'null') {\n    return null;\n  }\n\n  if (typeof value !== 'string') {\n    return value;\n  }\n\n  try {\n    return JSON.parse(decodeURIComponent(value));\n  } catch (_unused) {\n    return value;\n  }\n}\n\nfunction normalizeDataKey(key) {\n  return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n}\n\nconst Manipulator = {\n  setDataAttribute(element, key, value) {\n    element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n  },\n\n  removeDataAttribute(element, key) {\n    element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n  },\n\n  getDataAttributes(element) {\n    if (!element) {\n      return {};\n    }\n\n    const attributes = {};\n    const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n\n    for (const key of bsKeys) {\n      let pureKey = key.replace(/^bs/, '');\n      pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n      attributes[pureKey] = normalizeData(element.dataset[key]);\n    }\n\n    return attributes;\n  },\n\n  getDataAttribute(element, key) {\n    return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n  }\n\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Class definition\n */\n\nclass Config {\n  // Getters\n  static get Default() {\n    return {};\n  }\n\n  static get DefaultType() {\n    return {};\n  }\n\n  static get NAME() {\n    throw new Error('You have to implement the static method \"NAME\", for each component!');\n  }\n\n  _getConfig(config) {\n    config = this._mergeConfigObj(config);\n    config = this._configAfterMerge(config);\n\n    this._typeCheckConfig(config);\n\n    return config;\n  }\n\n  _configAfterMerge(config) {\n    return config;\n  }\n\n  _mergeConfigObj(config, element) {\n    const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n    return { ...this.constructor.Default,\n      ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n      ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n      ...(typeof config === 'object' ? config : {})\n    };\n  }\n\n  _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n    for (const property of Object.keys(configTypes)) {\n      const expectedTypes = configTypes[property];\n      const value = config[property];\n      const valueType = isElement(value) ? 'element' : toType(value);\n\n      if (!new RegExp(expectedTypes).test(valueType)) {\n        throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n      }\n    }\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst VERSION = '5.2.3';\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n  constructor(element, config) {\n    super();\n    element = getElement(element);\n\n    if (!element) {\n      return;\n    }\n\n    this._element = element;\n    this._config = this._getConfig(config);\n    Data.set(this._element, this.constructor.DATA_KEY, this);\n  } // Public\n\n\n  dispose() {\n    Data.remove(this._element, this.constructor.DATA_KEY);\n    EventHandler.off(this._element, this.constructor.EVENT_KEY);\n\n    for (const propertyName of Object.getOwnPropertyNames(this)) {\n      this[propertyName] = null;\n    }\n  }\n\n  _queueCallback(callback, element, isAnimated = true) {\n    executeAfterTransition(callback, element, isAnimated);\n  }\n\n  _getConfig(config) {\n    config = this._mergeConfigObj(config, this._element);\n    config = this._configAfterMerge(config);\n\n    this._typeCheckConfig(config);\n\n    return config;\n  } // Static\n\n\n  static getInstance(element) {\n    return Data.get(getElement(element), this.DATA_KEY);\n  }\n\n  static getOrCreateInstance(element, config = {}) {\n    return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n  }\n\n  static get VERSION() {\n    return VERSION;\n  }\n\n  static get DATA_KEY() {\n    return `bs.${this.NAME}`;\n  }\n\n  static get EVENT_KEY() {\n    return `.${this.DATA_KEY}`;\n  }\n\n  static eventName(name) {\n    return `${name}${this.EVENT_KEY}`;\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n  const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n  const name = component.NAME;\n  EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (isDisabled(this)) {\n      return;\n    }\n\n    const target = getElementFromSelector(this) || this.closest(`.${name}`);\n    const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n\n    instance[method]();\n  });\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$f = 'alert';\nconst DATA_KEY$a = 'bs.alert';\nconst EVENT_KEY$b = `.${DATA_KEY$a}`;\nconst EVENT_CLOSE = `close${EVENT_KEY$b}`;\nconst EVENT_CLOSED = `closed${EVENT_KEY$b}`;\nconst CLASS_NAME_FADE$5 = 'fade';\nconst CLASS_NAME_SHOW$8 = 'show';\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n  // Getters\n  static get NAME() {\n    return NAME$f;\n  } // Public\n\n\n  close() {\n    const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n\n    if (closeEvent.defaultPrevented) {\n      return;\n    }\n\n    this._element.classList.remove(CLASS_NAME_SHOW$8);\n\n    const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n\n    this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n  } // Private\n\n\n  _destroyElement() {\n    this._element.remove();\n\n    EventHandler.trigger(this._element, EVENT_CLOSED);\n    this.dispose();\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Alert.getOrCreateInstance(this);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config](this);\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nenableDismissTrigger(Alert, 'close');\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$e = 'button';\nconst DATA_KEY$9 = 'bs.button';\nconst EVENT_KEY$a = `.${DATA_KEY$9}`;\nconst DATA_API_KEY$6 = '.data-api';\nconst CLASS_NAME_ACTIVE$3 = 'active';\nconst SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\nconst EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n  // Getters\n  static get NAME() {\n    return NAME$e;\n  } // Public\n\n\n  toggle() {\n    // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n    this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Button.getOrCreateInstance(this);\n\n      if (config === 'toggle') {\n        data[config]();\n      }\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n  event.preventDefault();\n  const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n  const data = Button.getOrCreateInstance(button);\n  data.toggle();\n});\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst SelectorEngine = {\n  find(selector, element = document.documentElement) {\n    return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n  },\n\n  findOne(selector, element = document.documentElement) {\n    return Element.prototype.querySelector.call(element, selector);\n  },\n\n  children(element, selector) {\n    return [].concat(...element.children).filter(child => child.matches(selector));\n  },\n\n  parents(element, selector) {\n    const parents = [];\n    let ancestor = element.parentNode.closest(selector);\n\n    while (ancestor) {\n      parents.push(ancestor);\n      ancestor = ancestor.parentNode.closest(selector);\n    }\n\n    return parents;\n  },\n\n  prev(element, selector) {\n    let previous = element.previousElementSibling;\n\n    while (previous) {\n      if (previous.matches(selector)) {\n        return [previous];\n      }\n\n      previous = previous.previousElementSibling;\n    }\n\n    return [];\n  },\n\n  // TODO: this is now unused; remove later along with prev()\n  next(element, selector) {\n    let next = element.nextElementSibling;\n\n    while (next) {\n      if (next.matches(selector)) {\n        return [next];\n      }\n\n      next = next.nextElementSibling;\n    }\n\n    return [];\n  },\n\n  focusableChildren(element) {\n    const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n    return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n  }\n\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$d = 'swipe';\nconst EVENT_KEY$9 = '.bs.swipe';\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\nconst POINTER_TYPE_TOUCH = 'touch';\nconst POINTER_TYPE_PEN = 'pen';\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event';\nconst SWIPE_THRESHOLD = 40;\nconst Default$c = {\n  endCallback: null,\n  leftCallback: null,\n  rightCallback: null\n};\nconst DefaultType$c = {\n  endCallback: '(function|null)',\n  leftCallback: '(function|null)',\n  rightCallback: '(function|null)'\n};\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n  constructor(element, config) {\n    super();\n    this._element = element;\n\n    if (!element || !Swipe.isSupported()) {\n      return;\n    }\n\n    this._config = this._getConfig(config);\n    this._deltaX = 0;\n    this._supportPointerEvents = Boolean(window.PointerEvent);\n\n    this._initEvents();\n  } // Getters\n\n\n  static get Default() {\n    return Default$c;\n  }\n\n  static get DefaultType() {\n    return DefaultType$c;\n  }\n\n  static get NAME() {\n    return NAME$d;\n  } // Public\n\n\n  dispose() {\n    EventHandler.off(this._element, EVENT_KEY$9);\n  } // Private\n\n\n  _start(event) {\n    if (!this._supportPointerEvents) {\n      this._deltaX = event.touches[0].clientX;\n      return;\n    }\n\n    if (this._eventIsPointerPenTouch(event)) {\n      this._deltaX = event.clientX;\n    }\n  }\n\n  _end(event) {\n    if (this._eventIsPointerPenTouch(event)) {\n      this._deltaX = event.clientX - this._deltaX;\n    }\n\n    this._handleSwipe();\n\n    execute(this._config.endCallback);\n  }\n\n  _move(event) {\n    this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n  }\n\n  _handleSwipe() {\n    const absDeltaX = Math.abs(this._deltaX);\n\n    if (absDeltaX <= SWIPE_THRESHOLD) {\n      return;\n    }\n\n    const direction = absDeltaX / this._deltaX;\n    this._deltaX = 0;\n\n    if (!direction) {\n      return;\n    }\n\n    execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n  }\n\n  _initEvents() {\n    if (this._supportPointerEvents) {\n      EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n      EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n\n      this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n    } else {\n      EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n      EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n      EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n    }\n  }\n\n  _eventIsPointerPenTouch(event) {\n    return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n  } // Static\n\n\n  static isSupported() {\n    return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$c = 'carousel';\nconst DATA_KEY$8 = 'bs.carousel';\nconst EVENT_KEY$8 = `.${DATA_KEY$8}`;\nconst DATA_API_KEY$5 = '.data-api';\nconst ARROW_LEFT_KEY$1 = 'ArrowLeft';\nconst ARROW_RIGHT_KEY$1 = 'ArrowRight';\nconst TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next';\nconst ORDER_PREV = 'prev';\nconst DIRECTION_LEFT = 'left';\nconst DIRECTION_RIGHT = 'right';\nconst EVENT_SLIDE = `slide${EVENT_KEY$8}`;\nconst EVENT_SLID = `slid${EVENT_KEY$8}`;\nconst EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\nconst EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\nconst EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\nconst EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst CLASS_NAME_CAROUSEL = 'carousel';\nconst CLASS_NAME_ACTIVE$2 = 'active';\nconst CLASS_NAME_SLIDE = 'slide';\nconst CLASS_NAME_END = 'carousel-item-end';\nconst CLASS_NAME_START = 'carousel-item-start';\nconst CLASS_NAME_NEXT = 'carousel-item-next';\nconst CLASS_NAME_PREV = 'carousel-item-prev';\nconst SELECTOR_ACTIVE = '.active';\nconst SELECTOR_ITEM = '.carousel-item';\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\nconst SELECTOR_ITEM_IMG = '.carousel-item img';\nconst SELECTOR_INDICATORS = '.carousel-indicators';\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\nconst KEY_TO_DIRECTION = {\n  [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n  [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n};\nconst Default$b = {\n  interval: 5000,\n  keyboard: true,\n  pause: 'hover',\n  ride: false,\n  touch: true,\n  wrap: true\n};\nconst DefaultType$b = {\n  interval: '(number|boolean)',\n  // TODO:v6 remove boolean support\n  keyboard: 'boolean',\n  pause: '(string|boolean)',\n  ride: '(boolean|string)',\n  touch: 'boolean',\n  wrap: 'boolean'\n};\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n  constructor(element, config) {\n    super(element, config);\n    this._interval = null;\n    this._activeElement = null;\n    this._isSliding = false;\n    this.touchTimeout = null;\n    this._swipeHelper = null;\n    this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n\n    this._addEventListeners();\n\n    if (this._config.ride === CLASS_NAME_CAROUSEL) {\n      this.cycle();\n    }\n  } // Getters\n\n\n  static get Default() {\n    return Default$b;\n  }\n\n  static get DefaultType() {\n    return DefaultType$b;\n  }\n\n  static get NAME() {\n    return NAME$c;\n  } // Public\n\n\n  next() {\n    this._slide(ORDER_NEXT);\n  }\n\n  nextWhenVisible() {\n    // FIXME TODO use `document.visibilityState`\n    // Don't call next when the page isn't visible\n    // or the carousel or its parent isn't visible\n    if (!document.hidden && isVisible(this._element)) {\n      this.next();\n    }\n  }\n\n  prev() {\n    this._slide(ORDER_PREV);\n  }\n\n  pause() {\n    if (this._isSliding) {\n      triggerTransitionEnd(this._element);\n    }\n\n    this._clearInterval();\n  }\n\n  cycle() {\n    this._clearInterval();\n\n    this._updateInterval();\n\n    this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n  }\n\n  _maybeEnableCycle() {\n    if (!this._config.ride) {\n      return;\n    }\n\n    if (this._isSliding) {\n      EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n      return;\n    }\n\n    this.cycle();\n  }\n\n  to(index) {\n    const items = this._getItems();\n\n    if (index > items.length - 1 || index < 0) {\n      return;\n    }\n\n    if (this._isSliding) {\n      EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n      return;\n    }\n\n    const activeIndex = this._getItemIndex(this._getActive());\n\n    if (activeIndex === index) {\n      return;\n    }\n\n    const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n\n    this._slide(order, items[index]);\n  }\n\n  dispose() {\n    if (this._swipeHelper) {\n      this._swipeHelper.dispose();\n    }\n\n    super.dispose();\n  } // Private\n\n\n  _configAfterMerge(config) {\n    config.defaultInterval = config.interval;\n    return config;\n  }\n\n  _addEventListeners() {\n    if (this._config.keyboard) {\n      EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n    }\n\n    if (this._config.pause === 'hover') {\n      EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n      EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n    }\n\n    if (this._config.touch && Swipe.isSupported()) {\n      this._addTouchEventListeners();\n    }\n  }\n\n  _addTouchEventListeners() {\n    for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n      EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n    }\n\n    const endCallBack = () => {\n      if (this._config.pause !== 'hover') {\n        return;\n      } // If it's a touch-enabled device, mouseenter/leave are fired as\n      // part of the mouse compatibility events on first tap - the carousel\n      // would stop cycling until user tapped out of it;\n      // here, we listen for touchend, explicitly pause the carousel\n      // (as if it's the second time we tap on it, mouseenter compat event\n      // is NOT fired) and after a timeout (to allow for mouse compatibility\n      // events to fire) we explicitly restart cycling\n\n\n      this.pause();\n\n      if (this.touchTimeout) {\n        clearTimeout(this.touchTimeout);\n      }\n\n      this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n    };\n\n    const swipeConfig = {\n      leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n      rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n      endCallback: endCallBack\n    };\n    this._swipeHelper = new Swipe(this._element, swipeConfig);\n  }\n\n  _keydown(event) {\n    if (/input|textarea/i.test(event.target.tagName)) {\n      return;\n    }\n\n    const direction = KEY_TO_DIRECTION[event.key];\n\n    if (direction) {\n      event.preventDefault();\n\n      this._slide(this._directionToOrder(direction));\n    }\n  }\n\n  _getItemIndex(element) {\n    return this._getItems().indexOf(element);\n  }\n\n  _setActiveIndicatorElement(index) {\n    if (!this._indicatorsElement) {\n      return;\n    }\n\n    const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n    activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n    activeIndicator.removeAttribute('aria-current');\n    const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n\n    if (newActiveIndicator) {\n      newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n      newActiveIndicator.setAttribute('aria-current', 'true');\n    }\n  }\n\n  _updateInterval() {\n    const element = this._activeElement || this._getActive();\n\n    if (!element) {\n      return;\n    }\n\n    const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n    this._config.interval = elementInterval || this._config.defaultInterval;\n  }\n\n  _slide(order, element = null) {\n    if (this._isSliding) {\n      return;\n    }\n\n    const activeElement = this._getActive();\n\n    const isNext = order === ORDER_NEXT;\n    const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n\n    if (nextElement === activeElement) {\n      return;\n    }\n\n    const nextElementIndex = this._getItemIndex(nextElement);\n\n    const triggerEvent = eventName => {\n      return EventHandler.trigger(this._element, eventName, {\n        relatedTarget: nextElement,\n        direction: this._orderToDirection(order),\n        from: this._getItemIndex(activeElement),\n        to: nextElementIndex\n      });\n    };\n\n    const slideEvent = triggerEvent(EVENT_SLIDE);\n\n    if (slideEvent.defaultPrevented) {\n      return;\n    }\n\n    if (!activeElement || !nextElement) {\n      // Some weirdness is happening, so we bail\n      // todo: change tests that use empty divs to avoid this check\n      return;\n    }\n\n    const isCycling = Boolean(this._interval);\n    this.pause();\n    this._isSliding = true;\n\n    this._setActiveIndicatorElement(nextElementIndex);\n\n    this._activeElement = nextElement;\n    const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n    const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n    nextElement.classList.add(orderClassName);\n    reflow(nextElement);\n    activeElement.classList.add(directionalClassName);\n    nextElement.classList.add(directionalClassName);\n\n    const completeCallBack = () => {\n      nextElement.classList.remove(directionalClassName, orderClassName);\n      nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n      activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n      this._isSliding = false;\n      triggerEvent(EVENT_SLID);\n    };\n\n    this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n\n    if (isCycling) {\n      this.cycle();\n    }\n  }\n\n  _isAnimated() {\n    return this._element.classList.contains(CLASS_NAME_SLIDE);\n  }\n\n  _getActive() {\n    return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n  }\n\n  _getItems() {\n    return SelectorEngine.find(SELECTOR_ITEM, this._element);\n  }\n\n  _clearInterval() {\n    if (this._interval) {\n      clearInterval(this._interval);\n      this._interval = null;\n    }\n  }\n\n  _directionToOrder(direction) {\n    if (isRTL()) {\n      return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n    }\n\n    return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n  }\n\n  _orderToDirection(order) {\n    if (isRTL()) {\n      return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n    }\n\n    return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Carousel.getOrCreateInstance(this, config);\n\n      if (typeof config === 'number') {\n        data.to(config);\n        return;\n      }\n\n      if (typeof config === 'string') {\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      }\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n  const target = getElementFromSelector(this);\n\n  if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n    return;\n  }\n\n  event.preventDefault();\n  const carousel = Carousel.getOrCreateInstance(target);\n  const slideIndex = this.getAttribute('data-bs-slide-to');\n\n  if (slideIndex) {\n    carousel.to(slideIndex);\n\n    carousel._maybeEnableCycle();\n\n    return;\n  }\n\n  if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n    carousel.next();\n\n    carousel._maybeEnableCycle();\n\n    return;\n  }\n\n  carousel.prev();\n\n  carousel._maybeEnableCycle();\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n  const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n\n  for (const carousel of carousels) {\n    Carousel.getOrCreateInstance(carousel);\n  }\n});\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$b = 'collapse';\nconst DATA_KEY$7 = 'bs.collapse';\nconst EVENT_KEY$7 = `.${DATA_KEY$7}`;\nconst DATA_API_KEY$4 = '.data-api';\nconst EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\nconst EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\nconst EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\nconst EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\nconst EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\nconst CLASS_NAME_SHOW$7 = 'show';\nconst CLASS_NAME_COLLAPSE = 'collapse';\nconst CLASS_NAME_COLLAPSING = 'collapsing';\nconst CLASS_NAME_COLLAPSED = 'collapsed';\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\nconst WIDTH = 'width';\nconst HEIGHT = 'height';\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\nconst SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\nconst Default$a = {\n  parent: null,\n  toggle: true\n};\nconst DefaultType$a = {\n  parent: '(null|element)',\n  toggle: 'boolean'\n};\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n  constructor(element, config) {\n    super(element, config);\n    this._isTransitioning = false;\n    this._triggerArray = [];\n    const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n\n    for (const elem of toggleList) {\n      const selector = getSelectorFromElement(elem);\n      const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n\n      if (selector !== null && filterElement.length) {\n        this._triggerArray.push(elem);\n      }\n    }\n\n    this._initializeChildren();\n\n    if (!this._config.parent) {\n      this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n    }\n\n    if (this._config.toggle) {\n      this.toggle();\n    }\n  } // Getters\n\n\n  static get Default() {\n    return Default$a;\n  }\n\n  static get DefaultType() {\n    return DefaultType$a;\n  }\n\n  static get NAME() {\n    return NAME$b;\n  } // Public\n\n\n  toggle() {\n    if (this._isShown()) {\n      this.hide();\n    } else {\n      this.show();\n    }\n  }\n\n  show() {\n    if (this._isTransitioning || this._isShown()) {\n      return;\n    }\n\n    let activeChildren = []; // find active children\n\n    if (this._config.parent) {\n      activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n        toggle: false\n      }));\n    }\n\n    if (activeChildren.length && activeChildren[0]._isTransitioning) {\n      return;\n    }\n\n    const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n\n    if (startEvent.defaultPrevented) {\n      return;\n    }\n\n    for (const activeInstance of activeChildren) {\n      activeInstance.hide();\n    }\n\n    const dimension = this._getDimension();\n\n    this._element.classList.remove(CLASS_NAME_COLLAPSE);\n\n    this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n    this._element.style[dimension] = 0;\n\n    this._addAriaAndCollapsedClass(this._triggerArray, true);\n\n    this._isTransitioning = true;\n\n    const complete = () => {\n      this._isTransitioning = false;\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n\n      this._element.style[dimension] = '';\n      EventHandler.trigger(this._element, EVENT_SHOWN$6);\n    };\n\n    const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n    const scrollSize = `scroll${capitalizedDimension}`;\n\n    this._queueCallback(complete, this._element, true);\n\n    this._element.style[dimension] = `${this._element[scrollSize]}px`;\n  }\n\n  hide() {\n    if (this._isTransitioning || !this._isShown()) {\n      return;\n    }\n\n    const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n\n    if (startEvent.defaultPrevented) {\n      return;\n    }\n\n    const dimension = this._getDimension();\n\n    this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n    reflow(this._element);\n\n    this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n    this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n\n    for (const trigger of this._triggerArray) {\n      const element = getElementFromSelector(trigger);\n\n      if (element && !this._isShown(element)) {\n        this._addAriaAndCollapsedClass([trigger], false);\n      }\n    }\n\n    this._isTransitioning = true;\n\n    const complete = () => {\n      this._isTransitioning = false;\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSE);\n\n      EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n    };\n\n    this._element.style[dimension] = '';\n\n    this._queueCallback(complete, this._element, true);\n  }\n\n  _isShown(element = this._element) {\n    return element.classList.contains(CLASS_NAME_SHOW$7);\n  } // Private\n\n\n  _configAfterMerge(config) {\n    config.toggle = Boolean(config.toggle); // Coerce string values\n\n    config.parent = getElement(config.parent);\n    return config;\n  }\n\n  _getDimension() {\n    return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n  }\n\n  _initializeChildren() {\n    if (!this._config.parent) {\n      return;\n    }\n\n    const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n\n    for (const element of children) {\n      const selected = getElementFromSelector(element);\n\n      if (selected) {\n        this._addAriaAndCollapsedClass([element], this._isShown(selected));\n      }\n    }\n  }\n\n  _getFirstLevelChildren(selector) {\n    const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); // remove children if greater depth\n\n    return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n  }\n\n  _addAriaAndCollapsedClass(triggerArray, isOpen) {\n    if (!triggerArray.length) {\n      return;\n    }\n\n    for (const element of triggerArray) {\n      element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n      element.setAttribute('aria-expanded', isOpen);\n    }\n  } // Static\n\n\n  static jQueryInterface(config) {\n    const _config = {};\n\n    if (typeof config === 'string' && /show|hide/.test(config)) {\n      _config.toggle = false;\n    }\n\n    return this.each(function () {\n      const data = Collapse.getOrCreateInstance(this, _config);\n\n      if (typeof config === 'string') {\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      }\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n  // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n  if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n    event.preventDefault();\n  }\n\n  const selector = getSelectorFromElement(this);\n  const selectorElements = SelectorEngine.find(selector);\n\n  for (const element of selectorElements) {\n    Collapse.getOrCreateInstance(element, {\n      toggle: false\n    }).toggle();\n  }\n});\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$a = 'dropdown';\nconst DATA_KEY$6 = 'bs.dropdown';\nconst EVENT_KEY$6 = `.${DATA_KEY$6}`;\nconst DATA_API_KEY$3 = '.data-api';\nconst ESCAPE_KEY$2 = 'Escape';\nconst TAB_KEY$1 = 'Tab';\nconst ARROW_UP_KEY$1 = 'ArrowUp';\nconst ARROW_DOWN_KEY$1 = 'ArrowDown';\nconst RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\nconst EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\nconst EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\nconst EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\nconst EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst CLASS_NAME_SHOW$6 = 'show';\nconst CLASS_NAME_DROPUP = 'dropup';\nconst CLASS_NAME_DROPEND = 'dropend';\nconst CLASS_NAME_DROPSTART = 'dropstart';\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center';\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\nconst SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\nconst SELECTOR_MENU = '.dropdown-menu';\nconst SELECTOR_NAVBAR = '.navbar';\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav';\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\nconst PLACEMENT_TOPCENTER = 'top';\nconst PLACEMENT_BOTTOMCENTER = 'bottom';\nconst Default$9 = {\n  autoClose: true,\n  boundary: 'clippingParents',\n  display: 'dynamic',\n  offset: [0, 2],\n  popperConfig: null,\n  reference: 'toggle'\n};\nconst DefaultType$9 = {\n  autoClose: '(boolean|string)',\n  boundary: '(string|element)',\n  display: 'string',\n  offset: '(array|string|function)',\n  popperConfig: '(null|object|function)',\n  reference: '(string|element|object)'\n};\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n  constructor(element, config) {\n    super(element, config);\n    this._popper = null;\n    this._parent = this._element.parentNode; // dropdown wrapper\n    // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n    this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n    this._inNavbar = this._detectNavbar();\n  } // Getters\n\n\n  static get Default() {\n    return Default$9;\n  }\n\n  static get DefaultType() {\n    return DefaultType$9;\n  }\n\n  static get NAME() {\n    return NAME$a;\n  } // Public\n\n\n  toggle() {\n    return this._isShown() ? this.hide() : this.show();\n  }\n\n  show() {\n    if (isDisabled(this._element) || this._isShown()) {\n      return;\n    }\n\n    const relatedTarget = {\n      relatedTarget: this._element\n    };\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n\n    if (showEvent.defaultPrevented) {\n      return;\n    }\n\n    this._createPopper(); // If this is a touch-enabled device we add extra\n    // empty mouseover listeners to the body's immediate children;\n    // only needed because of broken event delegation on iOS\n    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n\n    if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.on(element, 'mouseover', noop);\n      }\n    }\n\n    this._element.focus();\n\n    this._element.setAttribute('aria-expanded', true);\n\n    this._menu.classList.add(CLASS_NAME_SHOW$6);\n\n    this._element.classList.add(CLASS_NAME_SHOW$6);\n\n    EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n  }\n\n  hide() {\n    if (isDisabled(this._element) || !this._isShown()) {\n      return;\n    }\n\n    const relatedTarget = {\n      relatedTarget: this._element\n    };\n\n    this._completeHide(relatedTarget);\n  }\n\n  dispose() {\n    if (this._popper) {\n      this._popper.destroy();\n    }\n\n    super.dispose();\n  }\n\n  update() {\n    this._inNavbar = this._detectNavbar();\n\n    if (this._popper) {\n      this._popper.update();\n    }\n  } // Private\n\n\n  _completeHide(relatedTarget) {\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n\n    if (hideEvent.defaultPrevented) {\n      return;\n    } // If this is a touch-enabled device we remove the extra\n    // empty mouseover listeners we added for iOS support\n\n\n    if ('ontouchstart' in document.documentElement) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.off(element, 'mouseover', noop);\n      }\n    }\n\n    if (this._popper) {\n      this._popper.destroy();\n    }\n\n    this._menu.classList.remove(CLASS_NAME_SHOW$6);\n\n    this._element.classList.remove(CLASS_NAME_SHOW$6);\n\n    this._element.setAttribute('aria-expanded', 'false');\n\n    Manipulator.removeDataAttribute(this._menu, 'popper');\n    EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n  }\n\n  _getConfig(config) {\n    config = super._getConfig(config);\n\n    if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n      // Popper virtual elements require a getBoundingClientRect method\n      throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n    }\n\n    return config;\n  }\n\n  _createPopper() {\n    if (typeof Popper === 'undefined') {\n      throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n    }\n\n    let referenceElement = this._element;\n\n    if (this._config.reference === 'parent') {\n      referenceElement = this._parent;\n    } else if (isElement(this._config.reference)) {\n      referenceElement = getElement(this._config.reference);\n    } else if (typeof this._config.reference === 'object') {\n      referenceElement = this._config.reference;\n    }\n\n    const popperConfig = this._getPopperConfig();\n\n    this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig);\n  }\n\n  _isShown() {\n    return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n  }\n\n  _getPlacement() {\n    const parentDropdown = this._parent;\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n      return PLACEMENT_RIGHT;\n    }\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n      return PLACEMENT_LEFT;\n    }\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n      return PLACEMENT_TOPCENTER;\n    }\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n      return PLACEMENT_BOTTOMCENTER;\n    } // We need to trim the value because custom properties can also include spaces\n\n\n    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n      return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n    }\n\n    return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n  }\n\n  _detectNavbar() {\n    return this._element.closest(SELECTOR_NAVBAR) !== null;\n  }\n\n  _getOffset() {\n    const {\n      offset\n    } = this._config;\n\n    if (typeof offset === 'string') {\n      return offset.split(',').map(value => Number.parseInt(value, 10));\n    }\n\n    if (typeof offset === 'function') {\n      return popperData => offset(popperData, this._element);\n    }\n\n    return offset;\n  }\n\n  _getPopperConfig() {\n    const defaultBsPopperConfig = {\n      placement: this._getPlacement(),\n      modifiers: [{\n        name: 'preventOverflow',\n        options: {\n          boundary: this._config.boundary\n        }\n      }, {\n        name: 'offset',\n        options: {\n          offset: this._getOffset()\n        }\n      }]\n    }; // Disable Popper if we have a static display or Dropdown is in Navbar\n\n    if (this._inNavbar || this._config.display === 'static') {\n      Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // todo:v6 remove\n\n      defaultBsPopperConfig.modifiers = [{\n        name: 'applyStyles',\n        enabled: false\n      }];\n    }\n\n    return { ...defaultBsPopperConfig,\n      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n    };\n  }\n\n  _selectMenuItem({\n    key,\n    target\n  }) {\n    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n\n    if (!items.length) {\n      return;\n    } // if target isn't included in items (e.g. when expanding the dropdown)\n    // allow cycling to get the last item in case key equals ARROW_UP_KEY\n\n\n    getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Dropdown.getOrCreateInstance(this, config);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config]();\n    });\n  }\n\n  static clearMenus(event) {\n    if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n      return;\n    }\n\n    const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n\n    for (const toggle of openToggles) {\n      const context = Dropdown.getInstance(toggle);\n\n      if (!context || context._config.autoClose === false) {\n        continue;\n      }\n\n      const composedPath = event.composedPath();\n      const isMenuTarget = composedPath.includes(context._menu);\n\n      if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n        continue;\n      } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n\n\n      if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n        continue;\n      }\n\n      const relatedTarget = {\n        relatedTarget: context._element\n      };\n\n      if (event.type === 'click') {\n        relatedTarget.clickEvent = event;\n      }\n\n      context._completeHide(relatedTarget);\n    }\n  }\n\n  static dataApiKeydownHandler(event) {\n    // If not an UP | DOWN | ESCAPE key => not a dropdown command\n    // If input/textarea && if key is other than ESCAPE => not a dropdown command\n    const isInput = /input|textarea/i.test(event.target.tagName);\n    const isEscapeEvent = event.key === ESCAPE_KEY$2;\n    const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n\n    if (!isUpOrDownEvent && !isEscapeEvent) {\n      return;\n    }\n\n    if (isInput && !isEscapeEvent) {\n      return;\n    }\n\n    event.preventDefault(); // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n    const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n    const instance = Dropdown.getOrCreateInstance(getToggleButton);\n\n    if (isUpOrDownEvent) {\n      event.stopPropagation();\n      instance.show();\n\n      instance._selectMenuItem(event);\n\n      return;\n    }\n\n    if (instance._isShown()) {\n      // else is escape and we check if it is shown\n      event.stopPropagation();\n      instance.hide();\n      getToggleButton.focus();\n    }\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n  event.preventDefault();\n  Dropdown.getOrCreateInstance(this).toggle();\n});\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\nconst SELECTOR_STICKY_CONTENT = '.sticky-top';\nconst PROPERTY_PADDING = 'padding-right';\nconst PROPERTY_MARGIN = 'margin-right';\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n  constructor() {\n    this._element = document.body;\n  } // Public\n\n\n  getWidth() {\n    // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n    const documentWidth = document.documentElement.clientWidth;\n    return Math.abs(window.innerWidth - documentWidth);\n  }\n\n  hide() {\n    const width = this.getWidth();\n\n    this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width\n\n\n    this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n\n\n    this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n\n    this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n  }\n\n  reset() {\n    this._resetElementAttributes(this._element, 'overflow');\n\n    this._resetElementAttributes(this._element, PROPERTY_PADDING);\n\n    this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n\n    this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n  }\n\n  isOverflowing() {\n    return this.getWidth() > 0;\n  } // Private\n\n\n  _disableOverFlow() {\n    this._saveInitialAttribute(this._element, 'overflow');\n\n    this._element.style.overflow = 'hidden';\n  }\n\n  _setElementAttributes(selector, styleProperty, callback) {\n    const scrollbarWidth = this.getWidth();\n\n    const manipulationCallBack = element => {\n      if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n        return;\n      }\n\n      this._saveInitialAttribute(element, styleProperty);\n\n      const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n      element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n    };\n\n    this._applyManipulationCallback(selector, manipulationCallBack);\n  }\n\n  _saveInitialAttribute(element, styleProperty) {\n    const actualValue = element.style.getPropertyValue(styleProperty);\n\n    if (actualValue) {\n      Manipulator.setDataAttribute(element, styleProperty, actualValue);\n    }\n  }\n\n  _resetElementAttributes(selector, styleProperty) {\n    const manipulationCallBack = element => {\n      const value = Manipulator.getDataAttribute(element, styleProperty); // We only want to remove the property if the value is `null`; the value can also be zero\n\n      if (value === null) {\n        element.style.removeProperty(styleProperty);\n        return;\n      }\n\n      Manipulator.removeDataAttribute(element, styleProperty);\n      element.style.setProperty(styleProperty, value);\n    };\n\n    this._applyManipulationCallback(selector, manipulationCallBack);\n  }\n\n  _applyManipulationCallback(selector, callBack) {\n    if (isElement(selector)) {\n      callBack(selector);\n      return;\n    }\n\n    for (const sel of SelectorEngine.find(selector, this._element)) {\n      callBack(sel);\n    }\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$9 = 'backdrop';\nconst CLASS_NAME_FADE$4 = 'fade';\nconst CLASS_NAME_SHOW$5 = 'show';\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\nconst Default$8 = {\n  className: 'modal-backdrop',\n  clickCallback: null,\n  isAnimated: false,\n  isVisible: true,\n  // if false, we use the backdrop helper without adding any element to the dom\n  rootElement: 'body' // give the choice to place backdrop under different elements\n\n};\nconst DefaultType$8 = {\n  className: 'string',\n  clickCallback: '(function|null)',\n  isAnimated: 'boolean',\n  isVisible: 'boolean',\n  rootElement: '(element|string)'\n};\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n  constructor(config) {\n    super();\n    this._config = this._getConfig(config);\n    this._isAppended = false;\n    this._element = null;\n  } // Getters\n\n\n  static get Default() {\n    return Default$8;\n  }\n\n  static get DefaultType() {\n    return DefaultType$8;\n  }\n\n  static get NAME() {\n    return NAME$9;\n  } // Public\n\n\n  show(callback) {\n    if (!this._config.isVisible) {\n      execute(callback);\n      return;\n    }\n\n    this._append();\n\n    const element = this._getElement();\n\n    if (this._config.isAnimated) {\n      reflow(element);\n    }\n\n    element.classList.add(CLASS_NAME_SHOW$5);\n\n    this._emulateAnimation(() => {\n      execute(callback);\n    });\n  }\n\n  hide(callback) {\n    if (!this._config.isVisible) {\n      execute(callback);\n      return;\n    }\n\n    this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n\n    this._emulateAnimation(() => {\n      this.dispose();\n      execute(callback);\n    });\n  }\n\n  dispose() {\n    if (!this._isAppended) {\n      return;\n    }\n\n    EventHandler.off(this._element, EVENT_MOUSEDOWN);\n\n    this._element.remove();\n\n    this._isAppended = false;\n  } // Private\n\n\n  _getElement() {\n    if (!this._element) {\n      const backdrop = document.createElement('div');\n      backdrop.className = this._config.className;\n\n      if (this._config.isAnimated) {\n        backdrop.classList.add(CLASS_NAME_FADE$4);\n      }\n\n      this._element = backdrop;\n    }\n\n    return this._element;\n  }\n\n  _configAfterMerge(config) {\n    // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n    config.rootElement = getElement(config.rootElement);\n    return config;\n  }\n\n  _append() {\n    if (this._isAppended) {\n      return;\n    }\n\n    const element = this._getElement();\n\n    this._config.rootElement.append(element);\n\n    EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n      execute(this._config.clickCallback);\n    });\n    this._isAppended = true;\n  }\n\n  _emulateAnimation(callback) {\n    executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$8 = 'focustrap';\nconst DATA_KEY$5 = 'bs.focustrap';\nconst EVENT_KEY$5 = `.${DATA_KEY$5}`;\nconst EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\nconst TAB_KEY = 'Tab';\nconst TAB_NAV_FORWARD = 'forward';\nconst TAB_NAV_BACKWARD = 'backward';\nconst Default$7 = {\n  autofocus: true,\n  trapElement: null // The element to trap focus inside of\n\n};\nconst DefaultType$7 = {\n  autofocus: 'boolean',\n  trapElement: 'element'\n};\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n  constructor(config) {\n    super();\n    this._config = this._getConfig(config);\n    this._isActive = false;\n    this._lastTabNavDirection = null;\n  } // Getters\n\n\n  static get Default() {\n    return Default$7;\n  }\n\n  static get DefaultType() {\n    return DefaultType$7;\n  }\n\n  static get NAME() {\n    return NAME$8;\n  } // Public\n\n\n  activate() {\n    if (this._isActive) {\n      return;\n    }\n\n    if (this._config.autofocus) {\n      this._config.trapElement.focus();\n    }\n\n    EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n\n    EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n    EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n    this._isActive = true;\n  }\n\n  deactivate() {\n    if (!this._isActive) {\n      return;\n    }\n\n    this._isActive = false;\n    EventHandler.off(document, EVENT_KEY$5);\n  } // Private\n\n\n  _handleFocusin(event) {\n    const {\n      trapElement\n    } = this._config;\n\n    if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n      return;\n    }\n\n    const elements = SelectorEngine.focusableChildren(trapElement);\n\n    if (elements.length === 0) {\n      trapElement.focus();\n    } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n      elements[elements.length - 1].focus();\n    } else {\n      elements[0].focus();\n    }\n  }\n\n  _handleKeydown(event) {\n    if (event.key !== TAB_KEY) {\n      return;\n    }\n\n    this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$7 = 'modal';\nconst DATA_KEY$4 = 'bs.modal';\nconst EVENT_KEY$4 = `.${DATA_KEY$4}`;\nconst DATA_API_KEY$2 = '.data-api';\nconst ESCAPE_KEY$1 = 'Escape';\nconst EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\nconst EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\nconst EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\nconst EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\nconst EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\nconst EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\nconst EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\nconst EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\nconst CLASS_NAME_OPEN = 'modal-open';\nconst CLASS_NAME_FADE$3 = 'fade';\nconst CLASS_NAME_SHOW$4 = 'show';\nconst CLASS_NAME_STATIC = 'modal-static';\nconst OPEN_SELECTOR$1 = '.modal.show';\nconst SELECTOR_DIALOG = '.modal-dialog';\nconst SELECTOR_MODAL_BODY = '.modal-body';\nconst SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\nconst Default$6 = {\n  backdrop: true,\n  focus: true,\n  keyboard: true\n};\nconst DefaultType$6 = {\n  backdrop: '(boolean|string)',\n  focus: 'boolean',\n  keyboard: 'boolean'\n};\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n  constructor(element, config) {\n    super(element, config);\n    this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n    this._backdrop = this._initializeBackDrop();\n    this._focustrap = this._initializeFocusTrap();\n    this._isShown = false;\n    this._isTransitioning = false;\n    this._scrollBar = new ScrollBarHelper();\n\n    this._addEventListeners();\n  } // Getters\n\n\n  static get Default() {\n    return Default$6;\n  }\n\n  static get DefaultType() {\n    return DefaultType$6;\n  }\n\n  static get NAME() {\n    return NAME$7;\n  } // Public\n\n\n  toggle(relatedTarget) {\n    return this._isShown ? this.hide() : this.show(relatedTarget);\n  }\n\n  show(relatedTarget) {\n    if (this._isShown || this._isTransitioning) {\n      return;\n    }\n\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n      relatedTarget\n    });\n\n    if (showEvent.defaultPrevented) {\n      return;\n    }\n\n    this._isShown = true;\n    this._isTransitioning = true;\n\n    this._scrollBar.hide();\n\n    document.body.classList.add(CLASS_NAME_OPEN);\n\n    this._adjustDialog();\n\n    this._backdrop.show(() => this._showElement(relatedTarget));\n  }\n\n  hide() {\n    if (!this._isShown || this._isTransitioning) {\n      return;\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n\n    if (hideEvent.defaultPrevented) {\n      return;\n    }\n\n    this._isShown = false;\n    this._isTransitioning = true;\n\n    this._focustrap.deactivate();\n\n    this._element.classList.remove(CLASS_NAME_SHOW$4);\n\n    this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n  }\n\n  dispose() {\n    for (const htmlElement of [window, this._dialog]) {\n      EventHandler.off(htmlElement, EVENT_KEY$4);\n    }\n\n    this._backdrop.dispose();\n\n    this._focustrap.deactivate();\n\n    super.dispose();\n  }\n\n  handleUpdate() {\n    this._adjustDialog();\n  } // Private\n\n\n  _initializeBackDrop() {\n    return new Backdrop({\n      isVisible: Boolean(this._config.backdrop),\n      // 'static' option will be translated to true, and booleans will keep their value,\n      isAnimated: this._isAnimated()\n    });\n  }\n\n  _initializeFocusTrap() {\n    return new FocusTrap({\n      trapElement: this._element\n    });\n  }\n\n  _showElement(relatedTarget) {\n    // try to append dynamic modal\n    if (!document.body.contains(this._element)) {\n      document.body.append(this._element);\n    }\n\n    this._element.style.display = 'block';\n\n    this._element.removeAttribute('aria-hidden');\n\n    this._element.setAttribute('aria-modal', true);\n\n    this._element.setAttribute('role', 'dialog');\n\n    this._element.scrollTop = 0;\n    const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n\n    if (modalBody) {\n      modalBody.scrollTop = 0;\n    }\n\n    reflow(this._element);\n\n    this._element.classList.add(CLASS_NAME_SHOW$4);\n\n    const transitionComplete = () => {\n      if (this._config.focus) {\n        this._focustrap.activate();\n      }\n\n      this._isTransitioning = false;\n      EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n        relatedTarget\n      });\n    };\n\n    this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n  }\n\n  _addEventListeners() {\n    EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n      if (event.key !== ESCAPE_KEY$1) {\n        return;\n      }\n\n      if (this._config.keyboard) {\n        event.preventDefault();\n        this.hide();\n        return;\n      }\n\n      this._triggerBackdropTransition();\n    });\n    EventHandler.on(window, EVENT_RESIZE$1, () => {\n      if (this._isShown && !this._isTransitioning) {\n        this._adjustDialog();\n      }\n    });\n    EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n      // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n      EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n        if (this._element !== event.target || this._element !== event2.target) {\n          return;\n        }\n\n        if (this._config.backdrop === 'static') {\n          this._triggerBackdropTransition();\n\n          return;\n        }\n\n        if (this._config.backdrop) {\n          this.hide();\n        }\n      });\n    });\n  }\n\n  _hideModal() {\n    this._element.style.display = 'none';\n\n    this._element.setAttribute('aria-hidden', true);\n\n    this._element.removeAttribute('aria-modal');\n\n    this._element.removeAttribute('role');\n\n    this._isTransitioning = false;\n\n    this._backdrop.hide(() => {\n      document.body.classList.remove(CLASS_NAME_OPEN);\n\n      this._resetAdjustments();\n\n      this._scrollBar.reset();\n\n      EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n    });\n  }\n\n  _isAnimated() {\n    return this._element.classList.contains(CLASS_NAME_FADE$3);\n  }\n\n  _triggerBackdropTransition() {\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n\n    if (hideEvent.defaultPrevented) {\n      return;\n    }\n\n    const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n    const initialOverflowY = this._element.style.overflowY; // return if the following background transition hasn't yet completed\n\n    if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n      return;\n    }\n\n    if (!isModalOverflowing) {\n      this._element.style.overflowY = 'hidden';\n    }\n\n    this._element.classList.add(CLASS_NAME_STATIC);\n\n    this._queueCallback(() => {\n      this._element.classList.remove(CLASS_NAME_STATIC);\n\n      this._queueCallback(() => {\n        this._element.style.overflowY = initialOverflowY;\n      }, this._dialog);\n    }, this._dialog);\n\n    this._element.focus();\n  }\n  /**\n   * The following methods are used to handle overflowing modals\n   */\n\n\n  _adjustDialog() {\n    const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n\n    const scrollbarWidth = this._scrollBar.getWidth();\n\n    const isBodyOverflowing = scrollbarWidth > 0;\n\n    if (isBodyOverflowing && !isModalOverflowing) {\n      const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n      this._element.style[property] = `${scrollbarWidth}px`;\n    }\n\n    if (!isBodyOverflowing && isModalOverflowing) {\n      const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n      this._element.style[property] = `${scrollbarWidth}px`;\n    }\n  }\n\n  _resetAdjustments() {\n    this._element.style.paddingLeft = '';\n    this._element.style.paddingRight = '';\n  } // Static\n\n\n  static jQueryInterface(config, relatedTarget) {\n    return this.each(function () {\n      const data = Modal.getOrCreateInstance(this, config);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config](relatedTarget);\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n  const target = getElementFromSelector(this);\n\n  if (['A', 'AREA'].includes(this.tagName)) {\n    event.preventDefault();\n  }\n\n  EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n    if (showEvent.defaultPrevented) {\n      // only register focus restorer if modal will actually get shown\n      return;\n    }\n\n    EventHandler.one(target, EVENT_HIDDEN$4, () => {\n      if (isVisible(this)) {\n        this.focus();\n      }\n    });\n  }); // avoid conflict when clicking modal toggler while another one is open\n\n  const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n\n  if (alreadyOpen) {\n    Modal.getInstance(alreadyOpen).hide();\n  }\n\n  const data = Modal.getOrCreateInstance(target);\n  data.toggle(this);\n});\nenableDismissTrigger(Modal);\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$6 = 'offcanvas';\nconst DATA_KEY$3 = 'bs.offcanvas';\nconst EVENT_KEY$3 = `.${DATA_KEY$3}`;\nconst DATA_API_KEY$1 = '.data-api';\nconst EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst ESCAPE_KEY = 'Escape';\nconst CLASS_NAME_SHOW$3 = 'show';\nconst CLASS_NAME_SHOWING$1 = 'showing';\nconst CLASS_NAME_HIDING = 'hiding';\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\nconst OPEN_SELECTOR = '.offcanvas.show';\nconst EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\nconst EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\nconst EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\nconst EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\nconst EVENT_RESIZE = `resize${EVENT_KEY$3}`;\nconst EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\nconst SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\nconst Default$5 = {\n  backdrop: true,\n  keyboard: true,\n  scroll: false\n};\nconst DefaultType$5 = {\n  backdrop: '(boolean|string)',\n  keyboard: 'boolean',\n  scroll: 'boolean'\n};\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n  constructor(element, config) {\n    super(element, config);\n    this._isShown = false;\n    this._backdrop = this._initializeBackDrop();\n    this._focustrap = this._initializeFocusTrap();\n\n    this._addEventListeners();\n  } // Getters\n\n\n  static get Default() {\n    return Default$5;\n  }\n\n  static get DefaultType() {\n    return DefaultType$5;\n  }\n\n  static get NAME() {\n    return NAME$6;\n  } // Public\n\n\n  toggle(relatedTarget) {\n    return this._isShown ? this.hide() : this.show(relatedTarget);\n  }\n\n  show(relatedTarget) {\n    if (this._isShown) {\n      return;\n    }\n\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n      relatedTarget\n    });\n\n    if (showEvent.defaultPrevented) {\n      return;\n    }\n\n    this._isShown = true;\n\n    this._backdrop.show();\n\n    if (!this._config.scroll) {\n      new ScrollBarHelper().hide();\n    }\n\n    this._element.setAttribute('aria-modal', true);\n\n    this._element.setAttribute('role', 'dialog');\n\n    this._element.classList.add(CLASS_NAME_SHOWING$1);\n\n    const completeCallBack = () => {\n      if (!this._config.scroll || this._config.backdrop) {\n        this._focustrap.activate();\n      }\n\n      this._element.classList.add(CLASS_NAME_SHOW$3);\n\n      this._element.classList.remove(CLASS_NAME_SHOWING$1);\n\n      EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n        relatedTarget\n      });\n    };\n\n    this._queueCallback(completeCallBack, this._element, true);\n  }\n\n  hide() {\n    if (!this._isShown) {\n      return;\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n\n    if (hideEvent.defaultPrevented) {\n      return;\n    }\n\n    this._focustrap.deactivate();\n\n    this._element.blur();\n\n    this._isShown = false;\n\n    this._element.classList.add(CLASS_NAME_HIDING);\n\n    this._backdrop.hide();\n\n    const completeCallback = () => {\n      this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n\n      this._element.removeAttribute('aria-modal');\n\n      this._element.removeAttribute('role');\n\n      if (!this._config.scroll) {\n        new ScrollBarHelper().reset();\n      }\n\n      EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n    };\n\n    this._queueCallback(completeCallback, this._element, true);\n  }\n\n  dispose() {\n    this._backdrop.dispose();\n\n    this._focustrap.deactivate();\n\n    super.dispose();\n  } // Private\n\n\n  _initializeBackDrop() {\n    const clickCallback = () => {\n      if (this._config.backdrop === 'static') {\n        EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n        return;\n      }\n\n      this.hide();\n    }; // 'static' option will be translated to true, and booleans will keep their value\n\n\n    const isVisible = Boolean(this._config.backdrop);\n    return new Backdrop({\n      className: CLASS_NAME_BACKDROP,\n      isVisible,\n      isAnimated: true,\n      rootElement: this._element.parentNode,\n      clickCallback: isVisible ? clickCallback : null\n    });\n  }\n\n  _initializeFocusTrap() {\n    return new FocusTrap({\n      trapElement: this._element\n    });\n  }\n\n  _addEventListeners() {\n    EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n      if (event.key !== ESCAPE_KEY) {\n        return;\n      }\n\n      if (!this._config.keyboard) {\n        EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n        return;\n      }\n\n      this.hide();\n    });\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Offcanvas.getOrCreateInstance(this, config);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config](this);\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n  const target = getElementFromSelector(this);\n\n  if (['A', 'AREA'].includes(this.tagName)) {\n    event.preventDefault();\n  }\n\n  if (isDisabled(this)) {\n    return;\n  }\n\n  EventHandler.one(target, EVENT_HIDDEN$3, () => {\n    // focus on trigger when it is closed\n    if (isVisible(this)) {\n      this.focus();\n    }\n  }); // avoid conflict when clicking a toggler of an offcanvas, while another is open\n\n  const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n\n  if (alreadyOpen && alreadyOpen !== target) {\n    Offcanvas.getInstance(alreadyOpen).hide();\n  }\n\n  const data = Offcanvas.getOrCreateInstance(target);\n  data.toggle(this);\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n  for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n    Offcanvas.getOrCreateInstance(selector).show();\n  }\n});\nEventHandler.on(window, EVENT_RESIZE, () => {\n  for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n    if (getComputedStyle(element).position !== 'fixed') {\n      Offcanvas.getOrCreateInstance(element).hide();\n    }\n  }\n});\nenableDismissTrigger(Offcanvas);\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\nconst uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\n/**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\n\nconst SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i;\n/**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\n\nconst DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i;\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n  const attributeName = attribute.nodeName.toLowerCase();\n\n  if (allowedAttributeList.includes(attributeName)) {\n    if (uriAttributes.has(attributeName)) {\n      return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue));\n    }\n\n    return true;\n  } // Check if a regular expression validates the attribute.\n\n\n  return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n};\n\nconst DefaultAllowlist = {\n  // Global attributes allowed on any supplied element below.\n  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n  a: ['target', 'href', 'title', 'rel'],\n  area: [],\n  b: [],\n  br: [],\n  col: [],\n  code: [],\n  div: [],\n  em: [],\n  hr: [],\n  h1: [],\n  h2: [],\n  h3: [],\n  h4: [],\n  h5: [],\n  h6: [],\n  i: [],\n  img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n  li: [],\n  ol: [],\n  p: [],\n  pre: [],\n  s: [],\n  small: [],\n  span: [],\n  sub: [],\n  sup: [],\n  strong: [],\n  u: [],\n  ul: []\n};\nfunction sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n  if (!unsafeHtml.length) {\n    return unsafeHtml;\n  }\n\n  if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n    return sanitizeFunction(unsafeHtml);\n  }\n\n  const domParser = new window.DOMParser();\n  const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n  const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n\n  for (const element of elements) {\n    const elementName = element.nodeName.toLowerCase();\n\n    if (!Object.keys(allowList).includes(elementName)) {\n      element.remove();\n      continue;\n    }\n\n    const attributeList = [].concat(...element.attributes);\n    const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n\n    for (const attribute of attributeList) {\n      if (!allowedAttribute(attribute, allowedAttributes)) {\n        element.removeAttribute(attribute.nodeName);\n      }\n    }\n  }\n\n  return createdDocument.body.innerHTML;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$5 = 'TemplateFactory';\nconst Default$4 = {\n  allowList: DefaultAllowlist,\n  content: {},\n  // { selector : text ,  selector2 : text2 , }\n  extraClass: '',\n  html: false,\n  sanitize: true,\n  sanitizeFn: null,\n  template: '<div></div>'\n};\nconst DefaultType$4 = {\n  allowList: 'object',\n  content: 'object',\n  extraClass: '(string|function)',\n  html: 'boolean',\n  sanitize: 'boolean',\n  sanitizeFn: '(null|function)',\n  template: 'string'\n};\nconst DefaultContentType = {\n  entry: '(string|element|function|null)',\n  selector: '(string|element)'\n};\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n  constructor(config) {\n    super();\n    this._config = this._getConfig(config);\n  } // Getters\n\n\n  static get Default() {\n    return Default$4;\n  }\n\n  static get DefaultType() {\n    return DefaultType$4;\n  }\n\n  static get NAME() {\n    return NAME$5;\n  } // Public\n\n\n  getContent() {\n    return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n  }\n\n  hasContent() {\n    return this.getContent().length > 0;\n  }\n\n  changeContent(content) {\n    this._checkContent(content);\n\n    this._config.content = { ...this._config.content,\n      ...content\n    };\n    return this;\n  }\n\n  toHtml() {\n    const templateWrapper = document.createElement('div');\n    templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n\n    for (const [selector, text] of Object.entries(this._config.content)) {\n      this._setContent(templateWrapper, text, selector);\n    }\n\n    const template = templateWrapper.children[0];\n\n    const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n\n    if (extraClass) {\n      template.classList.add(...extraClass.split(' '));\n    }\n\n    return template;\n  } // Private\n\n\n  _typeCheckConfig(config) {\n    super._typeCheckConfig(config);\n\n    this._checkContent(config.content);\n  }\n\n  _checkContent(arg) {\n    for (const [selector, content] of Object.entries(arg)) {\n      super._typeCheckConfig({\n        selector,\n        entry: content\n      }, DefaultContentType);\n    }\n  }\n\n  _setContent(template, content, selector) {\n    const templateElement = SelectorEngine.findOne(selector, template);\n\n    if (!templateElement) {\n      return;\n    }\n\n    content = this._resolvePossibleFunction(content);\n\n    if (!content) {\n      templateElement.remove();\n      return;\n    }\n\n    if (isElement(content)) {\n      this._putElementInTemplate(getElement(content), templateElement);\n\n      return;\n    }\n\n    if (this._config.html) {\n      templateElement.innerHTML = this._maybeSanitize(content);\n      return;\n    }\n\n    templateElement.textContent = content;\n  }\n\n  _maybeSanitize(arg) {\n    return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n  }\n\n  _resolvePossibleFunction(arg) {\n    return typeof arg === 'function' ? arg(this) : arg;\n  }\n\n  _putElementInTemplate(element, templateElement) {\n    if (this._config.html) {\n      templateElement.innerHTML = '';\n      templateElement.append(element);\n      return;\n    }\n\n    templateElement.textContent = element.textContent;\n  }\n\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$4 = 'tooltip';\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\nconst CLASS_NAME_FADE$2 = 'fade';\nconst CLASS_NAME_MODAL = 'modal';\nconst CLASS_NAME_SHOW$2 = 'show';\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\nconst EVENT_MODAL_HIDE = 'hide.bs.modal';\nconst TRIGGER_HOVER = 'hover';\nconst TRIGGER_FOCUS = 'focus';\nconst TRIGGER_CLICK = 'click';\nconst TRIGGER_MANUAL = 'manual';\nconst EVENT_HIDE$2 = 'hide';\nconst EVENT_HIDDEN$2 = 'hidden';\nconst EVENT_SHOW$2 = 'show';\nconst EVENT_SHOWN$2 = 'shown';\nconst EVENT_INSERTED = 'inserted';\nconst EVENT_CLICK$1 = 'click';\nconst EVENT_FOCUSIN$1 = 'focusin';\nconst EVENT_FOCUSOUT$1 = 'focusout';\nconst EVENT_MOUSEENTER = 'mouseenter';\nconst EVENT_MOUSELEAVE = 'mouseleave';\nconst AttachmentMap = {\n  AUTO: 'auto',\n  TOP: 'top',\n  RIGHT: isRTL() ? 'left' : 'right',\n  BOTTOM: 'bottom',\n  LEFT: isRTL() ? 'right' : 'left'\n};\nconst Default$3 = {\n  allowList: DefaultAllowlist,\n  animation: true,\n  boundary: 'clippingParents',\n  container: false,\n  customClass: '',\n  delay: 0,\n  fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n  html: false,\n  offset: [0, 0],\n  placement: 'top',\n  popperConfig: null,\n  sanitize: true,\n  sanitizeFn: null,\n  selector: false,\n  template: '<div class=\"tooltip\" role=\"tooltip\">' + '<div class=\"tooltip-arrow\"></div>' + '<div class=\"tooltip-inner\"></div>' + '</div>',\n  title: '',\n  trigger: 'hover focus'\n};\nconst DefaultType$3 = {\n  allowList: 'object',\n  animation: 'boolean',\n  boundary: '(string|element)',\n  container: '(string|element|boolean)',\n  customClass: '(string|function)',\n  delay: '(number|object)',\n  fallbackPlacements: 'array',\n  html: 'boolean',\n  offset: '(array|string|function)',\n  placement: '(string|function)',\n  popperConfig: '(null|object|function)',\n  sanitize: 'boolean',\n  sanitizeFn: '(null|function)',\n  selector: '(string|boolean)',\n  template: 'string',\n  title: '(string|element|function)',\n  trigger: 'string'\n};\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n  constructor(element, config) {\n    if (typeof Popper === 'undefined') {\n      throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n    }\n\n    super(element, config); // Private\n\n    this._isEnabled = true;\n    this._timeout = 0;\n    this._isHovered = null;\n    this._activeTrigger = {};\n    this._popper = null;\n    this._templateFactory = null;\n    this._newContent = null; // Protected\n\n    this.tip = null;\n\n    this._setListeners();\n\n    if (!this._config.selector) {\n      this._fixTitle();\n    }\n  } // Getters\n\n\n  static get Default() {\n    return Default$3;\n  }\n\n  static get DefaultType() {\n    return DefaultType$3;\n  }\n\n  static get NAME() {\n    return NAME$4;\n  } // Public\n\n\n  enable() {\n    this._isEnabled = true;\n  }\n\n  disable() {\n    this._isEnabled = false;\n  }\n\n  toggleEnabled() {\n    this._isEnabled = !this._isEnabled;\n  }\n\n  toggle() {\n    if (!this._isEnabled) {\n      return;\n    }\n\n    this._activeTrigger.click = !this._activeTrigger.click;\n\n    if (this._isShown()) {\n      this._leave();\n\n      return;\n    }\n\n    this._enter();\n  }\n\n  dispose() {\n    clearTimeout(this._timeout);\n    EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n\n    if (this._element.getAttribute('data-bs-original-title')) {\n      this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n    }\n\n    this._disposePopper();\n\n    super.dispose();\n  }\n\n  show() {\n    if (this._element.style.display === 'none') {\n      throw new Error('Please use show on visible elements');\n    }\n\n    if (!(this._isWithContent() && this._isEnabled)) {\n      return;\n    }\n\n    const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n    const shadowRoot = findShadowRoot(this._element);\n\n    const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n\n    if (showEvent.defaultPrevented || !isInTheDom) {\n      return;\n    } // todo v6 remove this OR make it optional\n\n\n    this._disposePopper();\n\n    const tip = this._getTipElement();\n\n    this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n\n    const {\n      container\n    } = this._config;\n\n    if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n      container.append(tip);\n      EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n    }\n\n    this._popper = this._createPopper(tip);\n    tip.classList.add(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we add extra\n    // empty mouseover listeners to the body's immediate children;\n    // only needed because of broken event delegation on iOS\n    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n    if ('ontouchstart' in document.documentElement) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.on(element, 'mouseover', noop);\n      }\n    }\n\n    const complete = () => {\n      EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n\n      if (this._isHovered === false) {\n        this._leave();\n      }\n\n      this._isHovered = false;\n    };\n\n    this._queueCallback(complete, this.tip, this._isAnimated());\n  }\n\n  hide() {\n    if (!this._isShown()) {\n      return;\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n\n    if (hideEvent.defaultPrevented) {\n      return;\n    }\n\n    const tip = this._getTipElement();\n\n    tip.classList.remove(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we remove the extra\n    // empty mouseover listeners we added for iOS support\n\n    if ('ontouchstart' in document.documentElement) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.off(element, 'mouseover', noop);\n      }\n    }\n\n    this._activeTrigger[TRIGGER_CLICK] = false;\n    this._activeTrigger[TRIGGER_FOCUS] = false;\n    this._activeTrigger[TRIGGER_HOVER] = false;\n    this._isHovered = null; // it is a trick to support manual triggering\n\n    const complete = () => {\n      if (this._isWithActiveTrigger()) {\n        return;\n      }\n\n      if (!this._isHovered) {\n        this._disposePopper();\n      }\n\n      this._element.removeAttribute('aria-describedby');\n\n      EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n    };\n\n    this._queueCallback(complete, this.tip, this._isAnimated());\n  }\n\n  update() {\n    if (this._popper) {\n      this._popper.update();\n    }\n  } // Protected\n\n\n  _isWithContent() {\n    return Boolean(this._getTitle());\n  }\n\n  _getTipElement() {\n    if (!this.tip) {\n      this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n    }\n\n    return this.tip;\n  }\n\n  _createTipElement(content) {\n    const tip = this._getTemplateFactory(content).toHtml(); // todo: remove this check on v6\n\n\n    if (!tip) {\n      return null;\n    }\n\n    tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); // todo: on v6 the following can be achieved with CSS only\n\n    tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n    const tipId = getUID(this.constructor.NAME).toString();\n    tip.setAttribute('id', tipId);\n\n    if (this._isAnimated()) {\n      tip.classList.add(CLASS_NAME_FADE$2);\n    }\n\n    return tip;\n  }\n\n  setContent(content) {\n    this._newContent = content;\n\n    if (this._isShown()) {\n      this._disposePopper();\n\n      this.show();\n    }\n  }\n\n  _getTemplateFactory(content) {\n    if (this._templateFactory) {\n      this._templateFactory.changeContent(content);\n    } else {\n      this._templateFactory = new TemplateFactory({ ...this._config,\n        // the `content` var has to be after `this._config`\n        // to override config.content in case of popover\n        content,\n        extraClass: this._resolvePossibleFunction(this._config.customClass)\n      });\n    }\n\n    return this._templateFactory;\n  }\n\n  _getContentForTemplate() {\n    return {\n      [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n    };\n  }\n\n  _getTitle() {\n    return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n  } // Private\n\n\n  _initializeOnDelegatedTarget(event) {\n    return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n  }\n\n  _isAnimated() {\n    return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n  }\n\n  _isShown() {\n    return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n  }\n\n  _createPopper(tip) {\n    const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement;\n    const attachment = AttachmentMap[placement.toUpperCase()];\n    return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment));\n  }\n\n  _getOffset() {\n    const {\n      offset\n    } = this._config;\n\n    if (typeof offset === 'string') {\n      return offset.split(',').map(value => Number.parseInt(value, 10));\n    }\n\n    if (typeof offset === 'function') {\n      return popperData => offset(popperData, this._element);\n    }\n\n    return offset;\n  }\n\n  _resolvePossibleFunction(arg) {\n    return typeof arg === 'function' ? arg.call(this._element) : arg;\n  }\n\n  _getPopperConfig(attachment) {\n    const defaultBsPopperConfig = {\n      placement: attachment,\n      modifiers: [{\n        name: 'flip',\n        options: {\n          fallbackPlacements: this._config.fallbackPlacements\n        }\n      }, {\n        name: 'offset',\n        options: {\n          offset: this._getOffset()\n        }\n      }, {\n        name: 'preventOverflow',\n        options: {\n          boundary: this._config.boundary\n        }\n      }, {\n        name: 'arrow',\n        options: {\n          element: `.${this.constructor.NAME}-arrow`\n        }\n      }, {\n        name: 'preSetPlacement',\n        enabled: true,\n        phase: 'beforeMain',\n        fn: data => {\n          // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n          // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n          this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n        }\n      }]\n    };\n    return { ...defaultBsPopperConfig,\n      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n    };\n  }\n\n  _setListeners() {\n    const triggers = this._config.trigger.split(' ');\n\n    for (const trigger of triggers) {\n      if (trigger === 'click') {\n        EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n          const context = this._initializeOnDelegatedTarget(event);\n\n          context.toggle();\n        });\n      } else if (trigger !== TRIGGER_MANUAL) {\n        const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n        const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n        EventHandler.on(this._element, eventIn, this._config.selector, event => {\n          const context = this._initializeOnDelegatedTarget(event);\n\n          context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n\n          context._enter();\n        });\n        EventHandler.on(this._element, eventOut, this._config.selector, event => {\n          const context = this._initializeOnDelegatedTarget(event);\n\n          context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n\n          context._leave();\n        });\n      }\n    }\n\n    this._hideModalHandler = () => {\n      if (this._element) {\n        this.hide();\n      }\n    };\n\n    EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n  }\n\n  _fixTitle() {\n    const title = this._element.getAttribute('title');\n\n    if (!title) {\n      return;\n    }\n\n    if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n      this._element.setAttribute('aria-label', title);\n    }\n\n    this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n\n\n    this._element.removeAttribute('title');\n  }\n\n  _enter() {\n    if (this._isShown() || this._isHovered) {\n      this._isHovered = true;\n      return;\n    }\n\n    this._isHovered = true;\n\n    this._setTimeout(() => {\n      if (this._isHovered) {\n        this.show();\n      }\n    }, this._config.delay.show);\n  }\n\n  _leave() {\n    if (this._isWithActiveTrigger()) {\n      return;\n    }\n\n    this._isHovered = false;\n\n    this._setTimeout(() => {\n      if (!this._isHovered) {\n        this.hide();\n      }\n    }, this._config.delay.hide);\n  }\n\n  _setTimeout(handler, timeout) {\n    clearTimeout(this._timeout);\n    this._timeout = setTimeout(handler, timeout);\n  }\n\n  _isWithActiveTrigger() {\n    return Object.values(this._activeTrigger).includes(true);\n  }\n\n  _getConfig(config) {\n    const dataAttributes = Manipulator.getDataAttributes(this._element);\n\n    for (const dataAttribute of Object.keys(dataAttributes)) {\n      if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n        delete dataAttributes[dataAttribute];\n      }\n    }\n\n    config = { ...dataAttributes,\n      ...(typeof config === 'object' && config ? config : {})\n    };\n    config = this._mergeConfigObj(config);\n    config = this._configAfterMerge(config);\n\n    this._typeCheckConfig(config);\n\n    return config;\n  }\n\n  _configAfterMerge(config) {\n    config.container = config.container === false ? document.body : getElement(config.container);\n\n    if (typeof config.delay === 'number') {\n      config.delay = {\n        show: config.delay,\n        hide: config.delay\n      };\n    }\n\n    if (typeof config.title === 'number') {\n      config.title = config.title.toString();\n    }\n\n    if (typeof config.content === 'number') {\n      config.content = config.content.toString();\n    }\n\n    return config;\n  }\n\n  _getDelegateConfig() {\n    const config = {};\n\n    for (const key in this._config) {\n      if (this.constructor.Default[key] !== this._config[key]) {\n        config[key] = this._config[key];\n      }\n    }\n\n    config.selector = false;\n    config.trigger = 'manual'; // In the future can be replaced with:\n    // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n    // `Object.fromEntries(keysWithDifferentValues)`\n\n    return config;\n  }\n\n  _disposePopper() {\n    if (this._popper) {\n      this._popper.destroy();\n\n      this._popper = null;\n    }\n\n    if (this.tip) {\n      this.tip.remove();\n      this.tip = null;\n    }\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Tooltip.getOrCreateInstance(this, config);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config]();\n    });\n  }\n\n}\n/**\n * jQuery\n */\n\n\ndefineJQueryPlugin(Tooltip);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$3 = 'popover';\nconst SELECTOR_TITLE = '.popover-header';\nconst SELECTOR_CONTENT = '.popover-body';\nconst Default$2 = { ...Tooltip.Default,\n  content: '',\n  offset: [0, 8],\n  placement: 'right',\n  template: '<div class=\"popover\" role=\"tooltip\">' + '<div class=\"popover-arrow\"></div>' + '<h3 class=\"popover-header\"></h3>' + '<div class=\"popover-body\"></div>' + '</div>',\n  trigger: 'click'\n};\nconst DefaultType$2 = { ...Tooltip.DefaultType,\n  content: '(null|string|element|function)'\n};\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n  // Getters\n  static get Default() {\n    return Default$2;\n  }\n\n  static get DefaultType() {\n    return DefaultType$2;\n  }\n\n  static get NAME() {\n    return NAME$3;\n  } // Overrides\n\n\n  _isWithContent() {\n    return this._getTitle() || this._getContent();\n  } // Private\n\n\n  _getContentForTemplate() {\n    return {\n      [SELECTOR_TITLE]: this._getTitle(),\n      [SELECTOR_CONTENT]: this._getContent()\n    };\n  }\n\n  _getContent() {\n    return this._resolvePossibleFunction(this._config.content);\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Popover.getOrCreateInstance(this, config);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config]();\n    });\n  }\n\n}\n/**\n * jQuery\n */\n\n\ndefineJQueryPlugin(Popover);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$2 = 'scrollspy';\nconst DATA_KEY$2 = 'bs.scrollspy';\nconst EVENT_KEY$2 = `.${DATA_KEY$2}`;\nconst DATA_API_KEY = '.data-api';\nconst EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\nconst EVENT_CLICK = `click${EVENT_KEY$2}`;\nconst EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\nconst CLASS_NAME_ACTIVE$1 = 'active';\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\nconst SELECTOR_TARGET_LINKS = '[href]';\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\nconst SELECTOR_NAV_LINKS = '.nav-link';\nconst SELECTOR_NAV_ITEMS = '.nav-item';\nconst SELECTOR_LIST_ITEMS = '.list-group-item';\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\nconst SELECTOR_DROPDOWN = '.dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\nconst Default$1 = {\n  offset: null,\n  // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n  rootMargin: '0px 0px -25%',\n  smoothScroll: false,\n  target: null,\n  threshold: [0.1, 0.5, 1]\n};\nconst DefaultType$1 = {\n  offset: '(number|null)',\n  // TODO v6 @deprecated, keep it for backwards compatibility reasons\n  rootMargin: 'string',\n  smoothScroll: 'boolean',\n  target: 'element',\n  threshold: 'array'\n};\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n  constructor(element, config) {\n    super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper\n\n    this._targetLinks = new Map();\n    this._observableSections = new Map();\n    this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n    this._activeTarget = null;\n    this._observer = null;\n    this._previousScrollData = {\n      visibleEntryTop: 0,\n      parentScrollTop: 0\n    };\n    this.refresh(); // initialize\n  } // Getters\n\n\n  static get Default() {\n    return Default$1;\n  }\n\n  static get DefaultType() {\n    return DefaultType$1;\n  }\n\n  static get NAME() {\n    return NAME$2;\n  } // Public\n\n\n  refresh() {\n    this._initializeTargetsAndObservables();\n\n    this._maybeEnableSmoothScroll();\n\n    if (this._observer) {\n      this._observer.disconnect();\n    } else {\n      this._observer = this._getNewObserver();\n    }\n\n    for (const section of this._observableSections.values()) {\n      this._observer.observe(section);\n    }\n  }\n\n  dispose() {\n    this._observer.disconnect();\n\n    super.dispose();\n  } // Private\n\n\n  _configAfterMerge(config) {\n    // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n    config.target = getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n\n    config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n\n    if (typeof config.threshold === 'string') {\n      config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n    }\n\n    return config;\n  }\n\n  _maybeEnableSmoothScroll() {\n    if (!this._config.smoothScroll) {\n      return;\n    } // unregister any previous listeners\n\n\n    EventHandler.off(this._config.target, EVENT_CLICK);\n    EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n      const observableSection = this._observableSections.get(event.target.hash);\n\n      if (observableSection) {\n        event.preventDefault();\n        const root = this._rootElement || window;\n        const height = observableSection.offsetTop - this._element.offsetTop;\n\n        if (root.scrollTo) {\n          root.scrollTo({\n            top: height,\n            behavior: 'smooth'\n          });\n          return;\n        } // Chrome 60 doesn't support `scrollTo`\n\n\n        root.scrollTop = height;\n      }\n    });\n  }\n\n  _getNewObserver() {\n    const options = {\n      root: this._rootElement,\n      threshold: this._config.threshold,\n      rootMargin: this._config.rootMargin\n    };\n    return new IntersectionObserver(entries => this._observerCallback(entries), options);\n  } // The logic of selection\n\n\n  _observerCallback(entries) {\n    const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n\n    const activate = entry => {\n      this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n\n      this._process(targetElement(entry));\n    };\n\n    const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n    const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n    this._previousScrollData.parentScrollTop = parentScrollTop;\n\n    for (const entry of entries) {\n      if (!entry.isIntersecting) {\n        this._activeTarget = null;\n\n        this._clearActiveClass(targetElement(entry));\n\n        continue;\n      }\n\n      const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop\n\n      if (userScrollsDown && entryIsLowerThanPrevious) {\n        activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n\n        if (!parentScrollTop) {\n          return;\n        }\n\n        continue;\n      } // if we are scrolling up, pick the smallest offsetTop\n\n\n      if (!userScrollsDown && !entryIsLowerThanPrevious) {\n        activate(entry);\n      }\n    }\n  }\n\n  _initializeTargetsAndObservables() {\n    this._targetLinks = new Map();\n    this._observableSections = new Map();\n    const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n\n    for (const anchor of targetLinks) {\n      // ensure that the anchor has an id and is not disabled\n      if (!anchor.hash || isDisabled(anchor)) {\n        continue;\n      }\n\n      const observableSection = SelectorEngine.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible\n\n      if (isVisible(observableSection)) {\n        this._targetLinks.set(anchor.hash, anchor);\n\n        this._observableSections.set(anchor.hash, observableSection);\n      }\n    }\n  }\n\n  _process(target) {\n    if (this._activeTarget === target) {\n      return;\n    }\n\n    this._clearActiveClass(this._config.target);\n\n    this._activeTarget = target;\n    target.classList.add(CLASS_NAME_ACTIVE$1);\n\n    this._activateParents(target);\n\n    EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n      relatedTarget: target\n    });\n  }\n\n  _activateParents(target) {\n    // Activate dropdown parents\n    if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n      SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n      return;\n    }\n\n    for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n      // Set triggered links parents as active\n      // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n      for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n        item.classList.add(CLASS_NAME_ACTIVE$1);\n      }\n    }\n  }\n\n  _clearActiveClass(parent) {\n    parent.classList.remove(CLASS_NAME_ACTIVE$1);\n    const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE$1}`, parent);\n\n    for (const node of activeNodes) {\n      node.classList.remove(CLASS_NAME_ACTIVE$1);\n    }\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = ScrollSpy.getOrCreateInstance(this, config);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config]();\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(window, EVENT_LOAD_DATA_API$1, () => {\n  for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n    ScrollSpy.getOrCreateInstance(spy);\n  }\n});\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME$1 = 'tab';\nconst DATA_KEY$1 = 'bs.tab';\nconst EVENT_KEY$1 = `.${DATA_KEY$1}`;\nconst EVENT_HIDE$1 = `hide${EVENT_KEY$1}`;\nconst EVENT_HIDDEN$1 = `hidden${EVENT_KEY$1}`;\nconst EVENT_SHOW$1 = `show${EVENT_KEY$1}`;\nconst EVENT_SHOWN$1 = `shown${EVENT_KEY$1}`;\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY$1}`;\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY$1}`;\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY$1}`;\nconst ARROW_LEFT_KEY = 'ArrowLeft';\nconst ARROW_RIGHT_KEY = 'ArrowRight';\nconst ARROW_UP_KEY = 'ArrowUp';\nconst ARROW_DOWN_KEY = 'ArrowDown';\nconst CLASS_NAME_ACTIVE = 'active';\nconst CLASS_NAME_FADE$1 = 'fade';\nconst CLASS_NAME_SHOW$1 = 'show';\nconst CLASS_DROPDOWN = 'dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu';\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)';\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]';\nconst SELECTOR_OUTER = '.nav-item, .list-group-item';\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]'; // todo:v6: could be only `tab`\n\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`;\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n  constructor(element) {\n    super(element);\n    this._parent = this._element.closest(SELECTOR_TAB_PANEL);\n\n    if (!this._parent) {\n      return; // todo: should Throw exception on v6\n      // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n    } // Set up initial aria attributes\n\n\n    this._setInitialAttributes(this._parent, this._getChildren());\n\n    EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n  } // Getters\n\n\n  static get NAME() {\n    return NAME$1;\n  } // Public\n\n\n  show() {\n    // Shows this elem and deactivate the active sibling if exists\n    const innerElem = this._element;\n\n    if (this._elemIsActive(innerElem)) {\n      return;\n    } // Search for active tab on same parent to deactivate it\n\n\n    const active = this._getActiveElem();\n\n    const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE$1, {\n      relatedTarget: innerElem\n    }) : null;\n    const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW$1, {\n      relatedTarget: active\n    });\n\n    if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {\n      return;\n    }\n\n    this._deactivate(active, innerElem);\n\n    this._activate(innerElem, active);\n  } // Private\n\n\n  _activate(element, relatedElem) {\n    if (!element) {\n      return;\n    }\n\n    element.classList.add(CLASS_NAME_ACTIVE);\n\n    this._activate(getElementFromSelector(element)); // Search and activate/show the proper section\n\n\n    const complete = () => {\n      if (element.getAttribute('role') !== 'tab') {\n        element.classList.add(CLASS_NAME_SHOW$1);\n        return;\n      }\n\n      element.removeAttribute('tabindex');\n      element.setAttribute('aria-selected', true);\n\n      this._toggleDropDown(element, true);\n\n      EventHandler.trigger(element, EVENT_SHOWN$1, {\n        relatedTarget: relatedElem\n      });\n    };\n\n    this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1));\n  }\n\n  _deactivate(element, relatedElem) {\n    if (!element) {\n      return;\n    }\n\n    element.classList.remove(CLASS_NAME_ACTIVE);\n    element.blur();\n\n    this._deactivate(getElementFromSelector(element)); // Search and deactivate the shown section too\n\n\n    const complete = () => {\n      if (element.getAttribute('role') !== 'tab') {\n        element.classList.remove(CLASS_NAME_SHOW$1);\n        return;\n      }\n\n      element.setAttribute('aria-selected', false);\n      element.setAttribute('tabindex', '-1');\n\n      this._toggleDropDown(element, false);\n\n      EventHandler.trigger(element, EVENT_HIDDEN$1, {\n        relatedTarget: relatedElem\n      });\n    };\n\n    this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1));\n  }\n\n  _keydown(event) {\n    if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {\n      return;\n    }\n\n    event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n\n    event.preventDefault();\n    const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);\n    const nextActiveElement = getNextActiveElement(this._getChildren().filter(element => !isDisabled(element)), event.target, isNext, true);\n\n    if (nextActiveElement) {\n      nextActiveElement.focus({\n        preventScroll: true\n      });\n      Tab.getOrCreateInstance(nextActiveElement).show();\n    }\n  }\n\n  _getChildren() {\n    // collection of inner elements\n    return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent);\n  }\n\n  _getActiveElem() {\n    return this._getChildren().find(child => this._elemIsActive(child)) || null;\n  }\n\n  _setInitialAttributes(parent, children) {\n    this._setAttributeIfNotExists(parent, 'role', 'tablist');\n\n    for (const child of children) {\n      this._setInitialAttributesOnChild(child);\n    }\n  }\n\n  _setInitialAttributesOnChild(child) {\n    child = this._getInnerElement(child);\n\n    const isActive = this._elemIsActive(child);\n\n    const outerElem = this._getOuterElement(child);\n\n    child.setAttribute('aria-selected', isActive);\n\n    if (outerElem !== child) {\n      this._setAttributeIfNotExists(outerElem, 'role', 'presentation');\n    }\n\n    if (!isActive) {\n      child.setAttribute('tabindex', '-1');\n    }\n\n    this._setAttributeIfNotExists(child, 'role', 'tab'); // set attributes to the related panel too\n\n\n    this._setInitialAttributesOnTargetPanel(child);\n  }\n\n  _setInitialAttributesOnTargetPanel(child) {\n    const target = getElementFromSelector(child);\n\n    if (!target) {\n      return;\n    }\n\n    this._setAttributeIfNotExists(target, 'role', 'tabpanel');\n\n    if (child.id) {\n      this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`);\n    }\n  }\n\n  _toggleDropDown(element, open) {\n    const outerElem = this._getOuterElement(element);\n\n    if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n      return;\n    }\n\n    const toggle = (selector, className) => {\n      const element = SelectorEngine.findOne(selector, outerElem);\n\n      if (element) {\n        element.classList.toggle(className, open);\n      }\n    };\n\n    toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);\n    toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW$1);\n    outerElem.setAttribute('aria-expanded', open);\n  }\n\n  _setAttributeIfNotExists(element, attribute, value) {\n    if (!element.hasAttribute(attribute)) {\n      element.setAttribute(attribute, value);\n    }\n  }\n\n  _elemIsActive(elem) {\n    return elem.classList.contains(CLASS_NAME_ACTIVE);\n  } // Try to get the inner element (usually the .nav-link)\n\n\n  _getInnerElement(elem) {\n    return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem);\n  } // Try to get the outer element (usually the .nav-item)\n\n\n  _getOuterElement(elem) {\n    return elem.closest(SELECTOR_OUTER) || elem;\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Tab.getOrCreateInstance(this);\n\n      if (typeof config !== 'string') {\n        return;\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`);\n      }\n\n      data[config]();\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n  if (['A', 'AREA'].includes(this.tagName)) {\n    event.preventDefault();\n  }\n\n  if (isDisabled(this)) {\n    return;\n  }\n\n  Tab.getOrCreateInstance(this).show();\n});\n/**\n * Initialize on focus\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n  for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n    Tab.getOrCreateInstance(element);\n  }\n});\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n/**\n * Constants\n */\n\nconst NAME = 'toast';\nconst DATA_KEY = 'bs.toast';\nconst EVENT_KEY = `.${DATA_KEY}`;\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;\nconst EVENT_HIDE = `hide${EVENT_KEY}`;\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`;\nconst EVENT_SHOW = `show${EVENT_KEY}`;\nconst EVENT_SHOWN = `shown${EVENT_KEY}`;\nconst CLASS_NAME_FADE = 'fade';\nconst CLASS_NAME_HIDE = 'hide'; // @deprecated - kept here only for backwards compatibility\n\nconst CLASS_NAME_SHOW = 'show';\nconst CLASS_NAME_SHOWING = 'showing';\nconst DefaultType = {\n  animation: 'boolean',\n  autohide: 'boolean',\n  delay: 'number'\n};\nconst Default = {\n  animation: true,\n  autohide: true,\n  delay: 5000\n};\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n  constructor(element, config) {\n    super(element, config);\n    this._timeout = null;\n    this._hasMouseInteraction = false;\n    this._hasKeyboardInteraction = false;\n\n    this._setListeners();\n  } // Getters\n\n\n  static get Default() {\n    return Default;\n  }\n\n  static get DefaultType() {\n    return DefaultType;\n  }\n\n  static get NAME() {\n    return NAME;\n  } // Public\n\n\n  show() {\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);\n\n    if (showEvent.defaultPrevented) {\n      return;\n    }\n\n    this._clearTimeout();\n\n    if (this._config.animation) {\n      this._element.classList.add(CLASS_NAME_FADE);\n    }\n\n    const complete = () => {\n      this._element.classList.remove(CLASS_NAME_SHOWING);\n\n      EventHandler.trigger(this._element, EVENT_SHOWN);\n\n      this._maybeScheduleHide();\n    };\n\n    this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated\n\n\n    reflow(this._element);\n\n    this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING);\n\n    this._queueCallback(complete, this._element, this._config.animation);\n  }\n\n  hide() {\n    if (!this.isShown()) {\n      return;\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n\n    if (hideEvent.defaultPrevented) {\n      return;\n    }\n\n    const complete = () => {\n      this._element.classList.add(CLASS_NAME_HIDE); // @deprecated\n\n\n      this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW);\n\n      EventHandler.trigger(this._element, EVENT_HIDDEN);\n    };\n\n    this._element.classList.add(CLASS_NAME_SHOWING);\n\n    this._queueCallback(complete, this._element, this._config.animation);\n  }\n\n  dispose() {\n    this._clearTimeout();\n\n    if (this.isShown()) {\n      this._element.classList.remove(CLASS_NAME_SHOW);\n    }\n\n    super.dispose();\n  }\n\n  isShown() {\n    return this._element.classList.contains(CLASS_NAME_SHOW);\n  } // Private\n\n\n  _maybeScheduleHide() {\n    if (!this._config.autohide) {\n      return;\n    }\n\n    if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n      return;\n    }\n\n    this._timeout = setTimeout(() => {\n      this.hide();\n    }, this._config.delay);\n  }\n\n  _onInteraction(event, isInteracting) {\n    switch (event.type) {\n      case 'mouseover':\n      case 'mouseout':\n        {\n          this._hasMouseInteraction = isInteracting;\n          break;\n        }\n\n      case 'focusin':\n      case 'focusout':\n        {\n          this._hasKeyboardInteraction = isInteracting;\n          break;\n        }\n    }\n\n    if (isInteracting) {\n      this._clearTimeout();\n\n      return;\n    }\n\n    const nextElement = event.relatedTarget;\n\n    if (this._element === nextElement || this._element.contains(nextElement)) {\n      return;\n    }\n\n    this._maybeScheduleHide();\n  }\n\n  _setListeners() {\n    EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));\n    EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));\n    EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));\n    EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));\n  }\n\n  _clearTimeout() {\n    clearTimeout(this._timeout);\n    this._timeout = null;\n  } // Static\n\n\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Toast.getOrCreateInstance(this, config);\n\n      if (typeof config === 'string') {\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      }\n    });\n  }\n\n}\n/**\n * Data API implementation\n */\n\n\nenableDismissTrigger(Toast);\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast);\n\nexport { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip };\n//# sourceMappingURL=bootstrap.esm.js.map\n"
  },
  {
    "path": "src/common/bootstrap/dist/js/bootstrap.js",
    "content": "/*!\n  * Bootstrap v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core')) :\n  typeof define === 'function' && define.amd ? define(['@popperjs/core'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory(global.Popper));\n})(this, (function (Popper) { 'use strict';\n\n  function _interopNamespace(e) {\n    if (e && e.__esModule) return e;\n    const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });\n    if (e) {\n      for (const k in e) {\n        if (k !== 'default') {\n          const d = Object.getOwnPropertyDescriptor(e, k);\n          Object.defineProperty(n, k, d.get ? d : {\n            enumerable: true,\n            get: () => e[k]\n          });\n        }\n      }\n    }\n    n.default = e;\n    return Object.freeze(n);\n  }\n\n  const Popper__namespace = /*#__PURE__*/_interopNamespace(Popper);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/index.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const MAX_UID = 1000000;\n  const MILLISECONDS_MULTIPLIER = 1000;\n  const TRANSITION_END = 'transitionend'; // Shout-out Angus Croll (https://goo.gl/pxwQGp)\n\n  const toType = object => {\n    if (object === null || object === undefined) {\n      return `${object}`;\n    }\n\n    return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n  };\n  /**\n   * Public Util API\n   */\n\n\n  const getUID = prefix => {\n    do {\n      prefix += Math.floor(Math.random() * MAX_UID);\n    } while (document.getElementById(prefix));\n\n    return prefix;\n  };\n\n  const getSelector = element => {\n    let selector = element.getAttribute('data-bs-target');\n\n    if (!selector || selector === '#') {\n      let hrefAttribute = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,\n      // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n      // `document.querySelector` will rightfully complain it is invalid.\n      // See https://github.com/twbs/bootstrap/issues/32273\n\n      if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n        return null;\n      } // Just in case some CMS puts out a full URL with the anchor appended\n\n\n      if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n        hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n      }\n\n      selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n    }\n\n    return selector;\n  };\n\n  const getSelectorFromElement = element => {\n    const selector = getSelector(element);\n\n    if (selector) {\n      return document.querySelector(selector) ? selector : null;\n    }\n\n    return null;\n  };\n\n  const getElementFromSelector = element => {\n    const selector = getSelector(element);\n    return selector ? document.querySelector(selector) : null;\n  };\n\n  const getTransitionDurationFromElement = element => {\n    if (!element) {\n      return 0;\n    } // Get transition-duration of the element\n\n\n    let {\n      transitionDuration,\n      transitionDelay\n    } = window.getComputedStyle(element);\n    const floatTransitionDuration = Number.parseFloat(transitionDuration);\n    const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found\n\n    if (!floatTransitionDuration && !floatTransitionDelay) {\n      return 0;\n    } // If multiple durations are defined, take the first\n\n\n    transitionDuration = transitionDuration.split(',')[0];\n    transitionDelay = transitionDelay.split(',')[0];\n    return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n  };\n\n  const triggerTransitionEnd = element => {\n    element.dispatchEvent(new Event(TRANSITION_END));\n  };\n\n  const isElement = object => {\n    if (!object || typeof object !== 'object') {\n      return false;\n    }\n\n    if (typeof object.jquery !== 'undefined') {\n      object = object[0];\n    }\n\n    return typeof object.nodeType !== 'undefined';\n  };\n\n  const getElement = object => {\n    // it's a jQuery object or a node element\n    if (isElement(object)) {\n      return object.jquery ? object[0] : object;\n    }\n\n    if (typeof object === 'string' && object.length > 0) {\n      return document.querySelector(object);\n    }\n\n    return null;\n  };\n\n  const isVisible = element => {\n    if (!isElement(element) || element.getClientRects().length === 0) {\n      return false;\n    }\n\n    const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; // Handle `details` element as its content may falsie appear visible when it is closed\n\n    const closedDetails = element.closest('details:not([open])');\n\n    if (!closedDetails) {\n      return elementIsVisible;\n    }\n\n    if (closedDetails !== element) {\n      const summary = element.closest('summary');\n\n      if (summary && summary.parentNode !== closedDetails) {\n        return false;\n      }\n\n      if (summary === null) {\n        return false;\n      }\n    }\n\n    return elementIsVisible;\n  };\n\n  const isDisabled = element => {\n    if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n      return true;\n    }\n\n    if (element.classList.contains('disabled')) {\n      return true;\n    }\n\n    if (typeof element.disabled !== 'undefined') {\n      return element.disabled;\n    }\n\n    return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n  };\n\n  const findShadowRoot = element => {\n    if (!document.documentElement.attachShadow) {\n      return null;\n    } // Can find the shadow root otherwise it'll return the document\n\n\n    if (typeof element.getRootNode === 'function') {\n      const root = element.getRootNode();\n      return root instanceof ShadowRoot ? root : null;\n    }\n\n    if (element instanceof ShadowRoot) {\n      return element;\n    } // when we don't find a shadow root\n\n\n    if (!element.parentNode) {\n      return null;\n    }\n\n    return findShadowRoot(element.parentNode);\n  };\n\n  const noop = () => {};\n  /**\n   * Trick to restart an element's animation\n   *\n   * @param {HTMLElement} element\n   * @return void\n   *\n   * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n   */\n\n\n  const reflow = element => {\n    element.offsetHeight; // eslint-disable-line no-unused-expressions\n  };\n\n  const getjQuery = () => {\n    if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n      return window.jQuery;\n    }\n\n    return null;\n  };\n\n  const DOMContentLoadedCallbacks = [];\n\n  const onDOMContentLoaded = callback => {\n    if (document.readyState === 'loading') {\n      // add listener on the first call when the document is in loading state\n      if (!DOMContentLoadedCallbacks.length) {\n        document.addEventListener('DOMContentLoaded', () => {\n          for (const callback of DOMContentLoadedCallbacks) {\n            callback();\n          }\n        });\n      }\n\n      DOMContentLoadedCallbacks.push(callback);\n    } else {\n      callback();\n    }\n  };\n\n  const isRTL = () => document.documentElement.dir === 'rtl';\n\n  const defineJQueryPlugin = plugin => {\n    onDOMContentLoaded(() => {\n      const $ = getjQuery();\n      /* istanbul ignore if */\n\n      if ($) {\n        const name = plugin.NAME;\n        const JQUERY_NO_CONFLICT = $.fn[name];\n        $.fn[name] = plugin.jQueryInterface;\n        $.fn[name].Constructor = plugin;\n\n        $.fn[name].noConflict = () => {\n          $.fn[name] = JQUERY_NO_CONFLICT;\n          return plugin.jQueryInterface;\n        };\n      }\n    });\n  };\n\n  const execute = callback => {\n    if (typeof callback === 'function') {\n      callback();\n    }\n  };\n\n  const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n    if (!waitForTransition) {\n      execute(callback);\n      return;\n    }\n\n    const durationPadding = 5;\n    const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n    let called = false;\n\n    const handler = ({\n      target\n    }) => {\n      if (target !== transitionElement) {\n        return;\n      }\n\n      called = true;\n      transitionElement.removeEventListener(TRANSITION_END, handler);\n      execute(callback);\n    };\n\n    transitionElement.addEventListener(TRANSITION_END, handler);\n    setTimeout(() => {\n      if (!called) {\n        triggerTransitionEnd(transitionElement);\n      }\n    }, emulatedDuration);\n  };\n  /**\n   * Return the previous/next element of a list.\n   *\n   * @param {array} list    The list of elements\n   * @param activeElement   The active element\n   * @param shouldGetNext   Choose to get next or previous element\n   * @param isCycleAllowed\n   * @return {Element|elem} The proper element\n   */\n\n\n  const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n    const listLength = list.length;\n    let index = list.indexOf(activeElement); // if the element does not exist in the list return an element\n    // depending on the direction and if cycle is allowed\n\n    if (index === -1) {\n      return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n    }\n\n    index += shouldGetNext ? 1 : -1;\n\n    if (isCycleAllowed) {\n      index = (index + listLength) % listLength;\n    }\n\n    return list[Math.max(0, Math.min(index, listLength - 1))];\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/event-handler.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\n  const stripNameRegex = /\\..*/;\n  const stripUidRegex = /::\\d+$/;\n  const eventRegistry = {}; // Events storage\n\n  let uidEvent = 1;\n  const customEvents = {\n    mouseenter: 'mouseover',\n    mouseleave: 'mouseout'\n  };\n  const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n  /**\n   * Private methods\n   */\n\n  function makeEventUid(element, uid) {\n    return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n  }\n\n  function getElementEvents(element) {\n    const uid = makeEventUid(element);\n    element.uidEvent = uid;\n    eventRegistry[uid] = eventRegistry[uid] || {};\n    return eventRegistry[uid];\n  }\n\n  function bootstrapHandler(element, fn) {\n    return function handler(event) {\n      hydrateObj(event, {\n        delegateTarget: element\n      });\n\n      if (handler.oneOff) {\n        EventHandler.off(element, event.type, fn);\n      }\n\n      return fn.apply(element, [event]);\n    };\n  }\n\n  function bootstrapDelegationHandler(element, selector, fn) {\n    return function handler(event) {\n      const domElements = element.querySelectorAll(selector);\n\n      for (let {\n        target\n      } = event; target && target !== this; target = target.parentNode) {\n        for (const domElement of domElements) {\n          if (domElement !== target) {\n            continue;\n          }\n\n          hydrateObj(event, {\n            delegateTarget: target\n          });\n\n          if (handler.oneOff) {\n            EventHandler.off(element, event.type, selector, fn);\n          }\n\n          return fn.apply(target, [event]);\n        }\n      }\n    };\n  }\n\n  function findHandler(events, callable, delegationSelector = null) {\n    return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n  }\n\n  function normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n    const isDelegated = typeof handler === 'string'; // todo: tooltip passes `false` instead of selector, so we need to check\n\n    const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n    let typeEvent = getTypeEvent(originalTypeEvent);\n\n    if (!nativeEvents.has(typeEvent)) {\n      typeEvent = originalTypeEvent;\n    }\n\n    return [isDelegated, callable, typeEvent];\n  }\n\n  function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n    if (typeof originalTypeEvent !== 'string' || !element) {\n      return;\n    }\n\n    let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n    // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n\n    if (originalTypeEvent in customEvents) {\n      const wrapFunction = fn => {\n        return function (event) {\n          if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n            return fn.call(this, event);\n          }\n        };\n      };\n\n      callable = wrapFunction(callable);\n    }\n\n    const events = getElementEvents(element);\n    const handlers = events[typeEvent] || (events[typeEvent] = {});\n    const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n\n    if (previousFunction) {\n      previousFunction.oneOff = previousFunction.oneOff && oneOff;\n      return;\n    }\n\n    const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n    const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n    fn.delegationSelector = isDelegated ? handler : null;\n    fn.callable = callable;\n    fn.oneOff = oneOff;\n    fn.uidEvent = uid;\n    handlers[uid] = fn;\n    element.addEventListener(typeEvent, fn, isDelegated);\n  }\n\n  function removeHandler(element, events, typeEvent, handler, delegationSelector) {\n    const fn = findHandler(events[typeEvent], handler, delegationSelector);\n\n    if (!fn) {\n      return;\n    }\n\n    element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n    delete events[typeEvent][fn.uidEvent];\n  }\n\n  function removeNamespacedHandlers(element, events, typeEvent, namespace) {\n    const storeElementEvent = events[typeEvent] || {};\n\n    for (const handlerKey of Object.keys(storeElementEvent)) {\n      if (handlerKey.includes(namespace)) {\n        const event = storeElementEvent[handlerKey];\n        removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n      }\n    }\n  }\n\n  function getTypeEvent(event) {\n    // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n    event = event.replace(stripNameRegex, '');\n    return customEvents[event] || event;\n  }\n\n  const EventHandler = {\n    on(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, false);\n    },\n\n    one(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, true);\n    },\n\n    off(element, originalTypeEvent, handler, delegationFunction) {\n      if (typeof originalTypeEvent !== 'string' || !element) {\n        return;\n      }\n\n      const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n      const inNamespace = typeEvent !== originalTypeEvent;\n      const events = getElementEvents(element);\n      const storeElementEvent = events[typeEvent] || {};\n      const isNamespace = originalTypeEvent.startsWith('.');\n\n      if (typeof callable !== 'undefined') {\n        // Simplest case: handler is passed, remove that listener ONLY.\n        if (!Object.keys(storeElementEvent).length) {\n          return;\n        }\n\n        removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n        return;\n      }\n\n      if (isNamespace) {\n        for (const elementEvent of Object.keys(events)) {\n          removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n        }\n      }\n\n      for (const keyHandlers of Object.keys(storeElementEvent)) {\n        const handlerKey = keyHandlers.replace(stripUidRegex, '');\n\n        if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n          const event = storeElementEvent[keyHandlers];\n          removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n        }\n      }\n    },\n\n    trigger(element, event, args) {\n      if (typeof event !== 'string' || !element) {\n        return null;\n      }\n\n      const $ = getjQuery();\n      const typeEvent = getTypeEvent(event);\n      const inNamespace = event !== typeEvent;\n      let jQueryEvent = null;\n      let bubbles = true;\n      let nativeDispatch = true;\n      let defaultPrevented = false;\n\n      if (inNamespace && $) {\n        jQueryEvent = $.Event(event, args);\n        $(element).trigger(jQueryEvent);\n        bubbles = !jQueryEvent.isPropagationStopped();\n        nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n        defaultPrevented = jQueryEvent.isDefaultPrevented();\n      }\n\n      let evt = new Event(event, {\n        bubbles,\n        cancelable: true\n      });\n      evt = hydrateObj(evt, args);\n\n      if (defaultPrevented) {\n        evt.preventDefault();\n      }\n\n      if (nativeDispatch) {\n        element.dispatchEvent(evt);\n      }\n\n      if (evt.defaultPrevented && jQueryEvent) {\n        jQueryEvent.preventDefault();\n      }\n\n      return evt;\n    }\n\n  };\n\n  function hydrateObj(obj, meta) {\n    for (const [key, value] of Object.entries(meta || {})) {\n      try {\n        obj[key] = value;\n      } catch (_unused) {\n        Object.defineProperty(obj, key, {\n          configurable: true,\n\n          get() {\n            return value;\n          }\n\n        });\n      }\n    }\n\n    return obj;\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/data.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  /**\n   * Constants\n   */\n  const elementMap = new Map();\n  const Data = {\n    set(element, key, instance) {\n      if (!elementMap.has(element)) {\n        elementMap.set(element, new Map());\n      }\n\n      const instanceMap = elementMap.get(element); // make it clear we only want one instance per element\n      // can be removed later when multiple key/instances are fine to be used\n\n      if (!instanceMap.has(key) && instanceMap.size !== 0) {\n        // eslint-disable-next-line no-console\n        console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n        return;\n      }\n\n      instanceMap.set(key, instance);\n    },\n\n    get(element, key) {\n      if (elementMap.has(element)) {\n        return elementMap.get(element).get(key) || null;\n      }\n\n      return null;\n    },\n\n    remove(element, key) {\n      if (!elementMap.has(element)) {\n        return;\n      }\n\n      const instanceMap = elementMap.get(element);\n      instanceMap.delete(key); // free up element references if there are no instances left for an element\n\n      if (instanceMap.size === 0) {\n        elementMap.delete(element);\n      }\n    }\n\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/manipulator.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  function normalizeData(value) {\n    if (value === 'true') {\n      return true;\n    }\n\n    if (value === 'false') {\n      return false;\n    }\n\n    if (value === Number(value).toString()) {\n      return Number(value);\n    }\n\n    if (value === '' || value === 'null') {\n      return null;\n    }\n\n    if (typeof value !== 'string') {\n      return value;\n    }\n\n    try {\n      return JSON.parse(decodeURIComponent(value));\n    } catch (_unused) {\n      return value;\n    }\n  }\n\n  function normalizeDataKey(key) {\n    return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n  }\n\n  const Manipulator = {\n    setDataAttribute(element, key, value) {\n      element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n    },\n\n    removeDataAttribute(element, key) {\n      element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n    },\n\n    getDataAttributes(element) {\n      if (!element) {\n        return {};\n      }\n\n      const attributes = {};\n      const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n\n      for (const key of bsKeys) {\n        let pureKey = key.replace(/^bs/, '');\n        pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n        attributes[pureKey] = normalizeData(element.dataset[key]);\n      }\n\n      return attributes;\n    },\n\n    getDataAttribute(element, key) {\n      return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n    }\n\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/config.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Class definition\n   */\n\n  class Config {\n    // Getters\n    static get Default() {\n      return {};\n    }\n\n    static get DefaultType() {\n      return {};\n    }\n\n    static get NAME() {\n      throw new Error('You have to implement the static method \"NAME\", for each component!');\n    }\n\n    _getConfig(config) {\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    }\n\n    _configAfterMerge(config) {\n      return config;\n    }\n\n    _mergeConfigObj(config, element) {\n      const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n      return { ...this.constructor.Default,\n        ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n        ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n        ...(typeof config === 'object' ? config : {})\n      };\n    }\n\n    _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n      for (const property of Object.keys(configTypes)) {\n        const expectedTypes = configTypes[property];\n        const value = config[property];\n        const valueType = isElement(value) ? 'element' : toType(value);\n\n        if (!new RegExp(expectedTypes).test(valueType)) {\n          throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n        }\n      }\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): base-component.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const VERSION = '5.2.3';\n  /**\n   * Class definition\n   */\n\n  class BaseComponent extends Config {\n    constructor(element, config) {\n      super();\n      element = getElement(element);\n\n      if (!element) {\n        return;\n      }\n\n      this._element = element;\n      this._config = this._getConfig(config);\n      Data.set(this._element, this.constructor.DATA_KEY, this);\n    } // Public\n\n\n    dispose() {\n      Data.remove(this._element, this.constructor.DATA_KEY);\n      EventHandler.off(this._element, this.constructor.EVENT_KEY);\n\n      for (const propertyName of Object.getOwnPropertyNames(this)) {\n        this[propertyName] = null;\n      }\n    }\n\n    _queueCallback(callback, element, isAnimated = true) {\n      executeAfterTransition(callback, element, isAnimated);\n    }\n\n    _getConfig(config) {\n      config = this._mergeConfigObj(config, this._element);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    } // Static\n\n\n    static getInstance(element) {\n      return Data.get(getElement(element), this.DATA_KEY);\n    }\n\n    static getOrCreateInstance(element, config = {}) {\n      return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n    }\n\n    static get VERSION() {\n      return VERSION;\n    }\n\n    static get DATA_KEY() {\n      return `bs.${this.NAME}`;\n    }\n\n    static get EVENT_KEY() {\n      return `.${this.DATA_KEY}`;\n    }\n\n    static eventName(name) {\n      return `${name}${this.EVENT_KEY}`;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/component-functions.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  const enableDismissTrigger = (component, method = 'hide') => {\n    const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n    const name = component.NAME;\n    EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n      if (['A', 'AREA'].includes(this.tagName)) {\n        event.preventDefault();\n      }\n\n      if (isDisabled(this)) {\n        return;\n      }\n\n      const target = getElementFromSelector(this) || this.closest(`.${name}`);\n      const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n\n      instance[method]();\n    });\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): alert.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$f = 'alert';\n  const DATA_KEY$a = 'bs.alert';\n  const EVENT_KEY$b = `.${DATA_KEY$a}`;\n  const EVENT_CLOSE = `close${EVENT_KEY$b}`;\n  const EVENT_CLOSED = `closed${EVENT_KEY$b}`;\n  const CLASS_NAME_FADE$5 = 'fade';\n  const CLASS_NAME_SHOW$8 = 'show';\n  /**\n   * Class definition\n   */\n\n  class Alert extends BaseComponent {\n    // Getters\n    static get NAME() {\n      return NAME$f;\n    } // Public\n\n\n    close() {\n      const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n\n      if (closeEvent.defaultPrevented) {\n        return;\n      }\n\n      this._element.classList.remove(CLASS_NAME_SHOW$8);\n\n      const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n\n      this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n    } // Private\n\n\n    _destroyElement() {\n      this._element.remove();\n\n      EventHandler.trigger(this._element, EVENT_CLOSED);\n      this.dispose();\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Alert.getOrCreateInstance(this);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  enableDismissTrigger(Alert, 'close');\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Alert);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): button.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$e = 'button';\n  const DATA_KEY$9 = 'bs.button';\n  const EVENT_KEY$a = `.${DATA_KEY$9}`;\n  const DATA_API_KEY$6 = '.data-api';\n  const CLASS_NAME_ACTIVE$3 = 'active';\n  const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\n  const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n  /**\n   * Class definition\n   */\n\n  class Button extends BaseComponent {\n    // Getters\n    static get NAME() {\n      return NAME$e;\n    } // Public\n\n\n    toggle() {\n      // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n      this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Button.getOrCreateInstance(this);\n\n        if (config === 'toggle') {\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n    event.preventDefault();\n    const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n    const data = Button.getOrCreateInstance(button);\n    data.toggle();\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Button);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/selector-engine.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const SelectorEngine = {\n    find(selector, element = document.documentElement) {\n      return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n    },\n\n    findOne(selector, element = document.documentElement) {\n      return Element.prototype.querySelector.call(element, selector);\n    },\n\n    children(element, selector) {\n      return [].concat(...element.children).filter(child => child.matches(selector));\n    },\n\n    parents(element, selector) {\n      const parents = [];\n      let ancestor = element.parentNode.closest(selector);\n\n      while (ancestor) {\n        parents.push(ancestor);\n        ancestor = ancestor.parentNode.closest(selector);\n      }\n\n      return parents;\n    },\n\n    prev(element, selector) {\n      let previous = element.previousElementSibling;\n\n      while (previous) {\n        if (previous.matches(selector)) {\n          return [previous];\n        }\n\n        previous = previous.previousElementSibling;\n      }\n\n      return [];\n    },\n\n    // TODO: this is now unused; remove later along with prev()\n    next(element, selector) {\n      let next = element.nextElementSibling;\n\n      while (next) {\n        if (next.matches(selector)) {\n          return [next];\n        }\n\n        next = next.nextElementSibling;\n      }\n\n      return [];\n    },\n\n    focusableChildren(element) {\n      const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n      return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n    }\n\n  };\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/swipe.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$d = 'swipe';\n  const EVENT_KEY$9 = '.bs.swipe';\n  const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\n  const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\n  const EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\n  const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\n  const EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\n  const POINTER_TYPE_TOUCH = 'touch';\n  const POINTER_TYPE_PEN = 'pen';\n  const CLASS_NAME_POINTER_EVENT = 'pointer-event';\n  const SWIPE_THRESHOLD = 40;\n  const Default$c = {\n    endCallback: null,\n    leftCallback: null,\n    rightCallback: null\n  };\n  const DefaultType$c = {\n    endCallback: '(function|null)',\n    leftCallback: '(function|null)',\n    rightCallback: '(function|null)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Swipe extends Config {\n    constructor(element, config) {\n      super();\n      this._element = element;\n\n      if (!element || !Swipe.isSupported()) {\n        return;\n      }\n\n      this._config = this._getConfig(config);\n      this._deltaX = 0;\n      this._supportPointerEvents = Boolean(window.PointerEvent);\n\n      this._initEvents();\n    } // Getters\n\n\n    static get Default() {\n      return Default$c;\n    }\n\n    static get DefaultType() {\n      return DefaultType$c;\n    }\n\n    static get NAME() {\n      return NAME$d;\n    } // Public\n\n\n    dispose() {\n      EventHandler.off(this._element, EVENT_KEY$9);\n    } // Private\n\n\n    _start(event) {\n      if (!this._supportPointerEvents) {\n        this._deltaX = event.touches[0].clientX;\n        return;\n      }\n\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX;\n      }\n    }\n\n    _end(event) {\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX - this._deltaX;\n      }\n\n      this._handleSwipe();\n\n      execute(this._config.endCallback);\n    }\n\n    _move(event) {\n      this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n    }\n\n    _handleSwipe() {\n      const absDeltaX = Math.abs(this._deltaX);\n\n      if (absDeltaX <= SWIPE_THRESHOLD) {\n        return;\n      }\n\n      const direction = absDeltaX / this._deltaX;\n      this._deltaX = 0;\n\n      if (!direction) {\n        return;\n      }\n\n      execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n    }\n\n    _initEvents() {\n      if (this._supportPointerEvents) {\n        EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n        EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n\n        this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n      } else {\n        EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n        EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n        EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n      }\n    }\n\n    _eventIsPointerPenTouch(event) {\n      return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n    } // Static\n\n\n    static isSupported() {\n      return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): carousel.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$c = 'carousel';\n  const DATA_KEY$8 = 'bs.carousel';\n  const EVENT_KEY$8 = `.${DATA_KEY$8}`;\n  const DATA_API_KEY$5 = '.data-api';\n  const ARROW_LEFT_KEY$1 = 'ArrowLeft';\n  const ARROW_RIGHT_KEY$1 = 'ArrowRight';\n  const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\n  const ORDER_NEXT = 'next';\n  const ORDER_PREV = 'prev';\n  const DIRECTION_LEFT = 'left';\n  const DIRECTION_RIGHT = 'right';\n  const EVENT_SLIDE = `slide${EVENT_KEY$8}`;\n  const EVENT_SLID = `slid${EVENT_KEY$8}`;\n  const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\n  const EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\n  const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\n  const EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\n  const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\n  const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\n  const CLASS_NAME_CAROUSEL = 'carousel';\n  const CLASS_NAME_ACTIVE$2 = 'active';\n  const CLASS_NAME_SLIDE = 'slide';\n  const CLASS_NAME_END = 'carousel-item-end';\n  const CLASS_NAME_START = 'carousel-item-start';\n  const CLASS_NAME_NEXT = 'carousel-item-next';\n  const CLASS_NAME_PREV = 'carousel-item-prev';\n  const SELECTOR_ACTIVE = '.active';\n  const SELECTOR_ITEM = '.carousel-item';\n  const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\n  const SELECTOR_ITEM_IMG = '.carousel-item img';\n  const SELECTOR_INDICATORS = '.carousel-indicators';\n  const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\n  const SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\n  const KEY_TO_DIRECTION = {\n    [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n    [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n  };\n  const Default$b = {\n    interval: 5000,\n    keyboard: true,\n    pause: 'hover',\n    ride: false,\n    touch: true,\n    wrap: true\n  };\n  const DefaultType$b = {\n    interval: '(number|boolean)',\n    // TODO:v6 remove boolean support\n    keyboard: 'boolean',\n    pause: '(string|boolean)',\n    ride: '(boolean|string)',\n    touch: 'boolean',\n    wrap: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Carousel extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._interval = null;\n      this._activeElement = null;\n      this._isSliding = false;\n      this.touchTimeout = null;\n      this._swipeHelper = null;\n      this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n\n      this._addEventListeners();\n\n      if (this._config.ride === CLASS_NAME_CAROUSEL) {\n        this.cycle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default$b;\n    }\n\n    static get DefaultType() {\n      return DefaultType$b;\n    }\n\n    static get NAME() {\n      return NAME$c;\n    } // Public\n\n\n    next() {\n      this._slide(ORDER_NEXT);\n    }\n\n    nextWhenVisible() {\n      // FIXME TODO use `document.visibilityState`\n      // Don't call next when the page isn't visible\n      // or the carousel or its parent isn't visible\n      if (!document.hidden && isVisible(this._element)) {\n        this.next();\n      }\n    }\n\n    prev() {\n      this._slide(ORDER_PREV);\n    }\n\n    pause() {\n      if (this._isSliding) {\n        triggerTransitionEnd(this._element);\n      }\n\n      this._clearInterval();\n    }\n\n    cycle() {\n      this._clearInterval();\n\n      this._updateInterval();\n\n      this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n    }\n\n    _maybeEnableCycle() {\n      if (!this._config.ride) {\n        return;\n      }\n\n      if (this._isSliding) {\n        EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n        return;\n      }\n\n      this.cycle();\n    }\n\n    to(index) {\n      const items = this._getItems();\n\n      if (index > items.length - 1 || index < 0) {\n        return;\n      }\n\n      if (this._isSliding) {\n        EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n        return;\n      }\n\n      const activeIndex = this._getItemIndex(this._getActive());\n\n      if (activeIndex === index) {\n        return;\n      }\n\n      const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n\n      this._slide(order, items[index]);\n    }\n\n    dispose() {\n      if (this._swipeHelper) {\n        this._swipeHelper.dispose();\n      }\n\n      super.dispose();\n    } // Private\n\n\n    _configAfterMerge(config) {\n      config.defaultInterval = config.interval;\n      return config;\n    }\n\n    _addEventListeners() {\n      if (this._config.keyboard) {\n        EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n      }\n\n      if (this._config.pause === 'hover') {\n        EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n        EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n      }\n\n      if (this._config.touch && Swipe.isSupported()) {\n        this._addTouchEventListeners();\n      }\n    }\n\n    _addTouchEventListeners() {\n      for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n        EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n      }\n\n      const endCallBack = () => {\n        if (this._config.pause !== 'hover') {\n          return;\n        } // If it's a touch-enabled device, mouseenter/leave are fired as\n        // part of the mouse compatibility events on first tap - the carousel\n        // would stop cycling until user tapped out of it;\n        // here, we listen for touchend, explicitly pause the carousel\n        // (as if it's the second time we tap on it, mouseenter compat event\n        // is NOT fired) and after a timeout (to allow for mouse compatibility\n        // events to fire) we explicitly restart cycling\n\n\n        this.pause();\n\n        if (this.touchTimeout) {\n          clearTimeout(this.touchTimeout);\n        }\n\n        this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n      };\n\n      const swipeConfig = {\n        leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n        rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n        endCallback: endCallBack\n      };\n      this._swipeHelper = new Swipe(this._element, swipeConfig);\n    }\n\n    _keydown(event) {\n      if (/input|textarea/i.test(event.target.tagName)) {\n        return;\n      }\n\n      const direction = KEY_TO_DIRECTION[event.key];\n\n      if (direction) {\n        event.preventDefault();\n\n        this._slide(this._directionToOrder(direction));\n      }\n    }\n\n    _getItemIndex(element) {\n      return this._getItems().indexOf(element);\n    }\n\n    _setActiveIndicatorElement(index) {\n      if (!this._indicatorsElement) {\n        return;\n      }\n\n      const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n      activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n      activeIndicator.removeAttribute('aria-current');\n      const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n\n      if (newActiveIndicator) {\n        newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n        newActiveIndicator.setAttribute('aria-current', 'true');\n      }\n    }\n\n    _updateInterval() {\n      const element = this._activeElement || this._getActive();\n\n      if (!element) {\n        return;\n      }\n\n      const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n      this._config.interval = elementInterval || this._config.defaultInterval;\n    }\n\n    _slide(order, element = null) {\n      if (this._isSliding) {\n        return;\n      }\n\n      const activeElement = this._getActive();\n\n      const isNext = order === ORDER_NEXT;\n      const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n\n      if (nextElement === activeElement) {\n        return;\n      }\n\n      const nextElementIndex = this._getItemIndex(nextElement);\n\n      const triggerEvent = eventName => {\n        return EventHandler.trigger(this._element, eventName, {\n          relatedTarget: nextElement,\n          direction: this._orderToDirection(order),\n          from: this._getItemIndex(activeElement),\n          to: nextElementIndex\n        });\n      };\n\n      const slideEvent = triggerEvent(EVENT_SLIDE);\n\n      if (slideEvent.defaultPrevented) {\n        return;\n      }\n\n      if (!activeElement || !nextElement) {\n        // Some weirdness is happening, so we bail\n        // todo: change tests that use empty divs to avoid this check\n        return;\n      }\n\n      const isCycling = Boolean(this._interval);\n      this.pause();\n      this._isSliding = true;\n\n      this._setActiveIndicatorElement(nextElementIndex);\n\n      this._activeElement = nextElement;\n      const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n      const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n      nextElement.classList.add(orderClassName);\n      reflow(nextElement);\n      activeElement.classList.add(directionalClassName);\n      nextElement.classList.add(directionalClassName);\n\n      const completeCallBack = () => {\n        nextElement.classList.remove(directionalClassName, orderClassName);\n        nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n        activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n        this._isSliding = false;\n        triggerEvent(EVENT_SLID);\n      };\n\n      this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n\n      if (isCycling) {\n        this.cycle();\n      }\n    }\n\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_SLIDE);\n    }\n\n    _getActive() {\n      return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n    }\n\n    _getItems() {\n      return SelectorEngine.find(SELECTOR_ITEM, this._element);\n    }\n\n    _clearInterval() {\n      if (this._interval) {\n        clearInterval(this._interval);\n        this._interval = null;\n      }\n    }\n\n    _directionToOrder(direction) {\n      if (isRTL()) {\n        return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n      }\n\n      return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n    }\n\n    _orderToDirection(order) {\n      if (isRTL()) {\n        return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n      }\n\n      return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Carousel.getOrCreateInstance(this, config);\n\n        if (typeof config === 'number') {\n          data.to(config);\n          return;\n        }\n\n        if (typeof config === 'string') {\n          if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n    const target = getElementFromSelector(this);\n\n    if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n      return;\n    }\n\n    event.preventDefault();\n    const carousel = Carousel.getOrCreateInstance(target);\n    const slideIndex = this.getAttribute('data-bs-slide-to');\n\n    if (slideIndex) {\n      carousel.to(slideIndex);\n\n      carousel._maybeEnableCycle();\n\n      return;\n    }\n\n    if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n      carousel.next();\n\n      carousel._maybeEnableCycle();\n\n      return;\n    }\n\n    carousel.prev();\n\n    carousel._maybeEnableCycle();\n  });\n  EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n    const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n\n    for (const carousel of carousels) {\n      Carousel.getOrCreateInstance(carousel);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Carousel);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): collapse.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$b = 'collapse';\n  const DATA_KEY$7 = 'bs.collapse';\n  const EVENT_KEY$7 = `.${DATA_KEY$7}`;\n  const DATA_API_KEY$4 = '.data-api';\n  const EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\n  const EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\n  const EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\n  const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\n  const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\n  const CLASS_NAME_SHOW$7 = 'show';\n  const CLASS_NAME_COLLAPSE = 'collapse';\n  const CLASS_NAME_COLLAPSING = 'collapsing';\n  const CLASS_NAME_COLLAPSED = 'collapsed';\n  const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\n  const CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\n  const WIDTH = 'width';\n  const HEIGHT = 'height';\n  const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\n  const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\n  const Default$a = {\n    parent: null,\n    toggle: true\n  };\n  const DefaultType$a = {\n    parent: '(null|element)',\n    toggle: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Collapse extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._isTransitioning = false;\n      this._triggerArray = [];\n      const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n\n      for (const elem of toggleList) {\n        const selector = getSelectorFromElement(elem);\n        const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n\n        if (selector !== null && filterElement.length) {\n          this._triggerArray.push(elem);\n        }\n      }\n\n      this._initializeChildren();\n\n      if (!this._config.parent) {\n        this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n      }\n\n      if (this._config.toggle) {\n        this.toggle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default$a;\n    }\n\n    static get DefaultType() {\n      return DefaultType$a;\n    }\n\n    static get NAME() {\n      return NAME$b;\n    } // Public\n\n\n    toggle() {\n      if (this._isShown()) {\n        this.hide();\n      } else {\n        this.show();\n      }\n    }\n\n    show() {\n      if (this._isTransitioning || this._isShown()) {\n        return;\n      }\n\n      let activeChildren = []; // find active children\n\n      if (this._config.parent) {\n        activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n          toggle: false\n        }));\n      }\n\n      if (activeChildren.length && activeChildren[0]._isTransitioning) {\n        return;\n      }\n\n      const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n\n      for (const activeInstance of activeChildren) {\n        activeInstance.hide();\n      }\n\n      const dimension = this._getDimension();\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSE);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n      this._element.style[dimension] = 0;\n\n      this._addAriaAndCollapsedClass(this._triggerArray, true);\n\n      this._isTransitioning = true;\n\n      const complete = () => {\n        this._isTransitioning = false;\n\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n        this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n\n        this._element.style[dimension] = '';\n        EventHandler.trigger(this._element, EVENT_SHOWN$6);\n      };\n\n      const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n      const scrollSize = `scroll${capitalizedDimension}`;\n\n      this._queueCallback(complete, this._element, true);\n\n      this._element.style[dimension] = `${this._element[scrollSize]}px`;\n    }\n\n    hide() {\n      if (this._isTransitioning || !this._isShown()) {\n        return;\n      }\n\n      const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n\n      const dimension = this._getDimension();\n\n      this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n      reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n\n      for (const trigger of this._triggerArray) {\n        const element = getElementFromSelector(trigger);\n\n        if (element && !this._isShown(element)) {\n          this._addAriaAndCollapsedClass([trigger], false);\n        }\n      }\n\n      this._isTransitioning = true;\n\n      const complete = () => {\n        this._isTransitioning = false;\n\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n        this._element.classList.add(CLASS_NAME_COLLAPSE);\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n      };\n\n      this._element.style[dimension] = '';\n\n      this._queueCallback(complete, this._element, true);\n    }\n\n    _isShown(element = this._element) {\n      return element.classList.contains(CLASS_NAME_SHOW$7);\n    } // Private\n\n\n    _configAfterMerge(config) {\n      config.toggle = Boolean(config.toggle); // Coerce string values\n\n      config.parent = getElement(config.parent);\n      return config;\n    }\n\n    _getDimension() {\n      return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n    }\n\n    _initializeChildren() {\n      if (!this._config.parent) {\n        return;\n      }\n\n      const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n\n      for (const element of children) {\n        const selected = getElementFromSelector(element);\n\n        if (selected) {\n          this._addAriaAndCollapsedClass([element], this._isShown(selected));\n        }\n      }\n    }\n\n    _getFirstLevelChildren(selector) {\n      const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); // remove children if greater depth\n\n      return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n    }\n\n    _addAriaAndCollapsedClass(triggerArray, isOpen) {\n      if (!triggerArray.length) {\n        return;\n      }\n\n      for (const element of triggerArray) {\n        element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n        element.setAttribute('aria-expanded', isOpen);\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      const _config = {};\n\n      if (typeof config === 'string' && /show|hide/.test(config)) {\n        _config.toggle = false;\n      }\n\n      return this.each(function () {\n        const data = Collapse.getOrCreateInstance(this, _config);\n\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n    // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n    if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n      event.preventDefault();\n    }\n\n    const selector = getSelectorFromElement(this);\n    const selectorElements = SelectorEngine.find(selector);\n\n    for (const element of selectorElements) {\n      Collapse.getOrCreateInstance(element, {\n        toggle: false\n      }).toggle();\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Collapse);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dropdown.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$a = 'dropdown';\n  const DATA_KEY$6 = 'bs.dropdown';\n  const EVENT_KEY$6 = `.${DATA_KEY$6}`;\n  const DATA_API_KEY$3 = '.data-api';\n  const ESCAPE_KEY$2 = 'Escape';\n  const TAB_KEY$1 = 'Tab';\n  const ARROW_UP_KEY$1 = 'ArrowUp';\n  const ARROW_DOWN_KEY$1 = 'ArrowDown';\n  const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\n  const EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\n  const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\n  const EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\n  const EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\n  const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\n  const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\n  const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\n  const CLASS_NAME_SHOW$6 = 'show';\n  const CLASS_NAME_DROPUP = 'dropup';\n  const CLASS_NAME_DROPEND = 'dropend';\n  const CLASS_NAME_DROPSTART = 'dropstart';\n  const CLASS_NAME_DROPUP_CENTER = 'dropup-center';\n  const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\n  const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\n  const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\n  const SELECTOR_MENU = '.dropdown-menu';\n  const SELECTOR_NAVBAR = '.navbar';\n  const SELECTOR_NAVBAR_NAV = '.navbar-nav';\n  const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\n  const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\n  const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\n  const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\n  const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\n  const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\n  const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\n  const PLACEMENT_TOPCENTER = 'top';\n  const PLACEMENT_BOTTOMCENTER = 'bottom';\n  const Default$9 = {\n    autoClose: true,\n    boundary: 'clippingParents',\n    display: 'dynamic',\n    offset: [0, 2],\n    popperConfig: null,\n    reference: 'toggle'\n  };\n  const DefaultType$9 = {\n    autoClose: '(boolean|string)',\n    boundary: '(string|element)',\n    display: 'string',\n    offset: '(array|string|function)',\n    popperConfig: '(null|object|function)',\n    reference: '(string|element|object)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Dropdown extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._popper = null;\n      this._parent = this._element.parentNode; // dropdown wrapper\n      // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n      this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n      this._inNavbar = this._detectNavbar();\n    } // Getters\n\n\n    static get Default() {\n      return Default$9;\n    }\n\n    static get DefaultType() {\n      return DefaultType$9;\n    }\n\n    static get NAME() {\n      return NAME$a;\n    } // Public\n\n\n    toggle() {\n      return this._isShown() ? this.hide() : this.show();\n    }\n\n    show() {\n      if (isDisabled(this._element) || this._isShown()) {\n        return;\n      }\n\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._createPopper(); // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n\n      if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.on(element, 'mouseover', noop);\n        }\n      }\n\n      this._element.focus();\n\n      this._element.setAttribute('aria-expanded', true);\n\n      this._menu.classList.add(CLASS_NAME_SHOW$6);\n\n      this._element.classList.add(CLASS_NAME_SHOW$6);\n\n      EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n    }\n\n    hide() {\n      if (isDisabled(this._element) || !this._isShown()) {\n        return;\n      }\n\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n\n      this._completeHide(relatedTarget);\n    }\n\n    dispose() {\n      if (this._popper) {\n        this._popper.destroy();\n      }\n\n      super.dispose();\n    }\n\n    update() {\n      this._inNavbar = this._detectNavbar();\n\n      if (this._popper) {\n        this._popper.update();\n      }\n    } // Private\n\n\n    _completeHide(relatedTarget) {\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      } // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.off(element, 'mouseover', noop);\n        }\n      }\n\n      if (this._popper) {\n        this._popper.destroy();\n      }\n\n      this._menu.classList.remove(CLASS_NAME_SHOW$6);\n\n      this._element.classList.remove(CLASS_NAME_SHOW$6);\n\n      this._element.setAttribute('aria-expanded', 'false');\n\n      Manipulator.removeDataAttribute(this._menu, 'popper');\n      EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n    }\n\n    _getConfig(config) {\n      config = super._getConfig(config);\n\n      if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n        // Popper virtual elements require a getBoundingClientRect method\n        throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n      }\n\n      return config;\n    }\n\n    _createPopper() {\n      if (typeof Popper__namespace === 'undefined') {\n        throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n      }\n\n      let referenceElement = this._element;\n\n      if (this._config.reference === 'parent') {\n        referenceElement = this._parent;\n      } else if (isElement(this._config.reference)) {\n        referenceElement = getElement(this._config.reference);\n      } else if (typeof this._config.reference === 'object') {\n        referenceElement = this._config.reference;\n      }\n\n      const popperConfig = this._getPopperConfig();\n\n      this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);\n    }\n\n    _isShown() {\n      return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n    }\n\n    _getPlacement() {\n      const parentDropdown = this._parent;\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n        return PLACEMENT_RIGHT;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n        return PLACEMENT_LEFT;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n        return PLACEMENT_TOPCENTER;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n        return PLACEMENT_BOTTOMCENTER;\n      } // We need to trim the value because custom properties can also include spaces\n\n\n      const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n        return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n      }\n\n      return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n    }\n\n    _detectNavbar() {\n      return this._element.closest(SELECTOR_NAVBAR) !== null;\n    }\n\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n\n      return offset;\n    }\n\n    _getPopperConfig() {\n      const defaultBsPopperConfig = {\n        placement: this._getPlacement(),\n        modifiers: [{\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }]\n      }; // Disable Popper if we have a static display or Dropdown is in Navbar\n\n      if (this._inNavbar || this._config.display === 'static') {\n        Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // todo:v6 remove\n\n        defaultBsPopperConfig.modifiers = [{\n          name: 'applyStyles',\n          enabled: false\n        }];\n      }\n\n      return { ...defaultBsPopperConfig,\n        ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n      };\n    }\n\n    _selectMenuItem({\n      key,\n      target\n    }) {\n      const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n\n      if (!items.length) {\n        return;\n      } // if target isn't included in items (e.g. when expanding the dropdown)\n      // allow cycling to get the last item in case key equals ARROW_UP_KEY\n\n\n      getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Dropdown.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n    static clearMenus(event) {\n      if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n        return;\n      }\n\n      const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n\n      for (const toggle of openToggles) {\n        const context = Dropdown.getInstance(toggle);\n\n        if (!context || context._config.autoClose === false) {\n          continue;\n        }\n\n        const composedPath = event.composedPath();\n        const isMenuTarget = composedPath.includes(context._menu);\n\n        if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n          continue;\n        } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n\n\n        if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n          continue;\n        }\n\n        const relatedTarget = {\n          relatedTarget: context._element\n        };\n\n        if (event.type === 'click') {\n          relatedTarget.clickEvent = event;\n        }\n\n        context._completeHide(relatedTarget);\n      }\n    }\n\n    static dataApiKeydownHandler(event) {\n      // If not an UP | DOWN | ESCAPE key => not a dropdown command\n      // If input/textarea && if key is other than ESCAPE => not a dropdown command\n      const isInput = /input|textarea/i.test(event.target.tagName);\n      const isEscapeEvent = event.key === ESCAPE_KEY$2;\n      const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n\n      if (!isUpOrDownEvent && !isEscapeEvent) {\n        return;\n      }\n\n      if (isInput && !isEscapeEvent) {\n        return;\n      }\n\n      event.preventDefault(); // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n      const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n      const instance = Dropdown.getOrCreateInstance(getToggleButton);\n\n      if (isUpOrDownEvent) {\n        event.stopPropagation();\n        instance.show();\n\n        instance._selectMenuItem(event);\n\n        return;\n      }\n\n      if (instance._isShown()) {\n        // else is escape and we check if it is shown\n        event.stopPropagation();\n        instance.hide();\n        getToggleButton.focus();\n      }\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\n  EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\n  EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\n  EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\n  EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n    event.preventDefault();\n    Dropdown.getOrCreateInstance(this).toggle();\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Dropdown);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/scrollBar.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\n  const SELECTOR_STICKY_CONTENT = '.sticky-top';\n  const PROPERTY_PADDING = 'padding-right';\n  const PROPERTY_MARGIN = 'margin-right';\n  /**\n   * Class definition\n   */\n\n  class ScrollBarHelper {\n    constructor() {\n      this._element = document.body;\n    } // Public\n\n\n    getWidth() {\n      // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n      const documentWidth = document.documentElement.clientWidth;\n      return Math.abs(window.innerWidth - documentWidth);\n    }\n\n    hide() {\n      const width = this.getWidth();\n\n      this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width\n\n\n      this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n\n\n      this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n\n      this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n    }\n\n    reset() {\n      this._resetElementAttributes(this._element, 'overflow');\n\n      this._resetElementAttributes(this._element, PROPERTY_PADDING);\n\n      this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n\n      this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n    }\n\n    isOverflowing() {\n      return this.getWidth() > 0;\n    } // Private\n\n\n    _disableOverFlow() {\n      this._saveInitialAttribute(this._element, 'overflow');\n\n      this._element.style.overflow = 'hidden';\n    }\n\n    _setElementAttributes(selector, styleProperty, callback) {\n      const scrollbarWidth = this.getWidth();\n\n      const manipulationCallBack = element => {\n        if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n          return;\n        }\n\n        this._saveInitialAttribute(element, styleProperty);\n\n        const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n        element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n      };\n\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n\n    _saveInitialAttribute(element, styleProperty) {\n      const actualValue = element.style.getPropertyValue(styleProperty);\n\n      if (actualValue) {\n        Manipulator.setDataAttribute(element, styleProperty, actualValue);\n      }\n    }\n\n    _resetElementAttributes(selector, styleProperty) {\n      const manipulationCallBack = element => {\n        const value = Manipulator.getDataAttribute(element, styleProperty); // We only want to remove the property if the value is `null`; the value can also be zero\n\n        if (value === null) {\n          element.style.removeProperty(styleProperty);\n          return;\n        }\n\n        Manipulator.removeDataAttribute(element, styleProperty);\n        element.style.setProperty(styleProperty, value);\n      };\n\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n\n    _applyManipulationCallback(selector, callBack) {\n      if (isElement(selector)) {\n        callBack(selector);\n        return;\n      }\n\n      for (const sel of SelectorEngine.find(selector, this._element)) {\n        callBack(sel);\n      }\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/backdrop.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$9 = 'backdrop';\n  const CLASS_NAME_FADE$4 = 'fade';\n  const CLASS_NAME_SHOW$5 = 'show';\n  const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\n  const Default$8 = {\n    className: 'modal-backdrop',\n    clickCallback: null,\n    isAnimated: false,\n    isVisible: true,\n    // if false, we use the backdrop helper without adding any element to the dom\n    rootElement: 'body' // give the choice to place backdrop under different elements\n\n  };\n  const DefaultType$8 = {\n    className: 'string',\n    clickCallback: '(function|null)',\n    isAnimated: 'boolean',\n    isVisible: 'boolean',\n    rootElement: '(element|string)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Backdrop extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isAppended = false;\n      this._element = null;\n    } // Getters\n\n\n    static get Default() {\n      return Default$8;\n    }\n\n    static get DefaultType() {\n      return DefaultType$8;\n    }\n\n    static get NAME() {\n      return NAME$9;\n    } // Public\n\n\n    show(callback) {\n      if (!this._config.isVisible) {\n        execute(callback);\n        return;\n      }\n\n      this._append();\n\n      const element = this._getElement();\n\n      if (this._config.isAnimated) {\n        reflow(element);\n      }\n\n      element.classList.add(CLASS_NAME_SHOW$5);\n\n      this._emulateAnimation(() => {\n        execute(callback);\n      });\n    }\n\n    hide(callback) {\n      if (!this._config.isVisible) {\n        execute(callback);\n        return;\n      }\n\n      this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n\n      this._emulateAnimation(() => {\n        this.dispose();\n        execute(callback);\n      });\n    }\n\n    dispose() {\n      if (!this._isAppended) {\n        return;\n      }\n\n      EventHandler.off(this._element, EVENT_MOUSEDOWN);\n\n      this._element.remove();\n\n      this._isAppended = false;\n    } // Private\n\n\n    _getElement() {\n      if (!this._element) {\n        const backdrop = document.createElement('div');\n        backdrop.className = this._config.className;\n\n        if (this._config.isAnimated) {\n          backdrop.classList.add(CLASS_NAME_FADE$4);\n        }\n\n        this._element = backdrop;\n      }\n\n      return this._element;\n    }\n\n    _configAfterMerge(config) {\n      // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n      config.rootElement = getElement(config.rootElement);\n      return config;\n    }\n\n    _append() {\n      if (this._isAppended) {\n        return;\n      }\n\n      const element = this._getElement();\n\n      this._config.rootElement.append(element);\n\n      EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n        execute(this._config.clickCallback);\n      });\n      this._isAppended = true;\n    }\n\n    _emulateAnimation(callback) {\n      executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/focustrap.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$8 = 'focustrap';\n  const DATA_KEY$5 = 'bs.focustrap';\n  const EVENT_KEY$5 = `.${DATA_KEY$5}`;\n  const EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\n  const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\n  const TAB_KEY = 'Tab';\n  const TAB_NAV_FORWARD = 'forward';\n  const TAB_NAV_BACKWARD = 'backward';\n  const Default$7 = {\n    autofocus: true,\n    trapElement: null // The element to trap focus inside of\n\n  };\n  const DefaultType$7 = {\n    autofocus: 'boolean',\n    trapElement: 'element'\n  };\n  /**\n   * Class definition\n   */\n\n  class FocusTrap extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isActive = false;\n      this._lastTabNavDirection = null;\n    } // Getters\n\n\n    static get Default() {\n      return Default$7;\n    }\n\n    static get DefaultType() {\n      return DefaultType$7;\n    }\n\n    static get NAME() {\n      return NAME$8;\n    } // Public\n\n\n    activate() {\n      if (this._isActive) {\n        return;\n      }\n\n      if (this._config.autofocus) {\n        this._config.trapElement.focus();\n      }\n\n      EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n\n      EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n      EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n      this._isActive = true;\n    }\n\n    deactivate() {\n      if (!this._isActive) {\n        return;\n      }\n\n      this._isActive = false;\n      EventHandler.off(document, EVENT_KEY$5);\n    } // Private\n\n\n    _handleFocusin(event) {\n      const {\n        trapElement\n      } = this._config;\n\n      if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n        return;\n      }\n\n      const elements = SelectorEngine.focusableChildren(trapElement);\n\n      if (elements.length === 0) {\n        trapElement.focus();\n      } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n        elements[elements.length - 1].focus();\n      } else {\n        elements[0].focus();\n      }\n    }\n\n    _handleKeydown(event) {\n      if (event.key !== TAB_KEY) {\n        return;\n      }\n\n      this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): modal.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$7 = 'modal';\n  const DATA_KEY$4 = 'bs.modal';\n  const EVENT_KEY$4 = `.${DATA_KEY$4}`;\n  const DATA_API_KEY$2 = '.data-api';\n  const ESCAPE_KEY$1 = 'Escape';\n  const EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\n  const EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\n  const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\n  const EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\n  const EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\n  const EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\n  const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\n  const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\n  const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\n  const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\n  const CLASS_NAME_OPEN = 'modal-open';\n  const CLASS_NAME_FADE$3 = 'fade';\n  const CLASS_NAME_SHOW$4 = 'show';\n  const CLASS_NAME_STATIC = 'modal-static';\n  const OPEN_SELECTOR$1 = '.modal.show';\n  const SELECTOR_DIALOG = '.modal-dialog';\n  const SELECTOR_MODAL_BODY = '.modal-body';\n  const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\n  const Default$6 = {\n    backdrop: true,\n    focus: true,\n    keyboard: true\n  };\n  const DefaultType$6 = {\n    backdrop: '(boolean|string)',\n    focus: 'boolean',\n    keyboard: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Modal extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n      this._isShown = false;\n      this._isTransitioning = false;\n      this._scrollBar = new ScrollBarHelper();\n\n      this._addEventListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default$6;\n    }\n\n    static get DefaultType() {\n      return DefaultType$6;\n    }\n\n    static get NAME() {\n      return NAME$7;\n    } // Public\n\n\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n\n    show(relatedTarget) {\n      if (this._isShown || this._isTransitioning) {\n        return;\n      }\n\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n        relatedTarget\n      });\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = true;\n      this._isTransitioning = true;\n\n      this._scrollBar.hide();\n\n      document.body.classList.add(CLASS_NAME_OPEN);\n\n      this._adjustDialog();\n\n      this._backdrop.show(() => this._showElement(relatedTarget));\n    }\n\n    hide() {\n      if (!this._isShown || this._isTransitioning) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = false;\n      this._isTransitioning = true;\n\n      this._focustrap.deactivate();\n\n      this._element.classList.remove(CLASS_NAME_SHOW$4);\n\n      this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n    }\n\n    dispose() {\n      for (const htmlElement of [window, this._dialog]) {\n        EventHandler.off(htmlElement, EVENT_KEY$4);\n      }\n\n      this._backdrop.dispose();\n\n      this._focustrap.deactivate();\n\n      super.dispose();\n    }\n\n    handleUpdate() {\n      this._adjustDialog();\n    } // Private\n\n\n    _initializeBackDrop() {\n      return new Backdrop({\n        isVisible: Boolean(this._config.backdrop),\n        // 'static' option will be translated to true, and booleans will keep their value,\n        isAnimated: this._isAnimated()\n      });\n    }\n\n    _initializeFocusTrap() {\n      return new FocusTrap({\n        trapElement: this._element\n      });\n    }\n\n    _showElement(relatedTarget) {\n      // try to append dynamic modal\n      if (!document.body.contains(this._element)) {\n        document.body.append(this._element);\n      }\n\n      this._element.style.display = 'block';\n\n      this._element.removeAttribute('aria-hidden');\n\n      this._element.setAttribute('aria-modal', true);\n\n      this._element.setAttribute('role', 'dialog');\n\n      this._element.scrollTop = 0;\n      const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n\n      if (modalBody) {\n        modalBody.scrollTop = 0;\n      }\n\n      reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_SHOW$4);\n\n      const transitionComplete = () => {\n        if (this._config.focus) {\n          this._focustrap.activate();\n        }\n\n        this._isTransitioning = false;\n        EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n          relatedTarget\n        });\n      };\n\n      this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n    }\n\n    _addEventListeners() {\n      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n        if (event.key !== ESCAPE_KEY$1) {\n          return;\n        }\n\n        if (this._config.keyboard) {\n          event.preventDefault();\n          this.hide();\n          return;\n        }\n\n        this._triggerBackdropTransition();\n      });\n      EventHandler.on(window, EVENT_RESIZE$1, () => {\n        if (this._isShown && !this._isTransitioning) {\n          this._adjustDialog();\n        }\n      });\n      EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n        // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n        EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n          if (this._element !== event.target || this._element !== event2.target) {\n            return;\n          }\n\n          if (this._config.backdrop === 'static') {\n            this._triggerBackdropTransition();\n\n            return;\n          }\n\n          if (this._config.backdrop) {\n            this.hide();\n          }\n        });\n      });\n    }\n\n    _hideModal() {\n      this._element.style.display = 'none';\n\n      this._element.setAttribute('aria-hidden', true);\n\n      this._element.removeAttribute('aria-modal');\n\n      this._element.removeAttribute('role');\n\n      this._isTransitioning = false;\n\n      this._backdrop.hide(() => {\n        document.body.classList.remove(CLASS_NAME_OPEN);\n\n        this._resetAdjustments();\n\n        this._scrollBar.reset();\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n      });\n    }\n\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_FADE$3);\n    }\n\n    _triggerBackdropTransition() {\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n      const initialOverflowY = this._element.style.overflowY; // return if the following background transition hasn't yet completed\n\n      if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n        return;\n      }\n\n      if (!isModalOverflowing) {\n        this._element.style.overflowY = 'hidden';\n      }\n\n      this._element.classList.add(CLASS_NAME_STATIC);\n\n      this._queueCallback(() => {\n        this._element.classList.remove(CLASS_NAME_STATIC);\n\n        this._queueCallback(() => {\n          this._element.style.overflowY = initialOverflowY;\n        }, this._dialog);\n      }, this._dialog);\n\n      this._element.focus();\n    }\n    /**\n     * The following methods are used to handle overflowing modals\n     */\n\n\n    _adjustDialog() {\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n\n      const scrollbarWidth = this._scrollBar.getWidth();\n\n      const isBodyOverflowing = scrollbarWidth > 0;\n\n      if (isBodyOverflowing && !isModalOverflowing) {\n        const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n\n      if (!isBodyOverflowing && isModalOverflowing) {\n        const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n    }\n\n    _resetAdjustments() {\n      this._element.style.paddingLeft = '';\n      this._element.style.paddingRight = '';\n    } // Static\n\n\n    static jQueryInterface(config, relatedTarget) {\n      return this.each(function () {\n        const data = Modal.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](relatedTarget);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n    const target = getElementFromSelector(this);\n\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n      if (showEvent.defaultPrevented) {\n        // only register focus restorer if modal will actually get shown\n        return;\n      }\n\n      EventHandler.one(target, EVENT_HIDDEN$4, () => {\n        if (isVisible(this)) {\n          this.focus();\n        }\n      });\n    }); // avoid conflict when clicking modal toggler while another one is open\n\n    const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n\n    if (alreadyOpen) {\n      Modal.getInstance(alreadyOpen).hide();\n    }\n\n    const data = Modal.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  enableDismissTrigger(Modal);\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Modal);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): offcanvas.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$6 = 'offcanvas';\n  const DATA_KEY$3 = 'bs.offcanvas';\n  const EVENT_KEY$3 = `.${DATA_KEY$3}`;\n  const DATA_API_KEY$1 = '.data-api';\n  const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\n  const ESCAPE_KEY = 'Escape';\n  const CLASS_NAME_SHOW$3 = 'show';\n  const CLASS_NAME_SHOWING$1 = 'showing';\n  const CLASS_NAME_HIDING = 'hiding';\n  const CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\n  const OPEN_SELECTOR = '.offcanvas.show';\n  const EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\n  const EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\n  const EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\n  const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\n  const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\n  const EVENT_RESIZE = `resize${EVENT_KEY$3}`;\n  const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\n  const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\n  const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\n  const Default$5 = {\n    backdrop: true,\n    keyboard: true,\n    scroll: false\n  };\n  const DefaultType$5 = {\n    backdrop: '(boolean|string)',\n    keyboard: 'boolean',\n    scroll: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Offcanvas extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._isShown = false;\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n\n      this._addEventListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default$5;\n    }\n\n    static get DefaultType() {\n      return DefaultType$5;\n    }\n\n    static get NAME() {\n      return NAME$6;\n    } // Public\n\n\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n\n    show(relatedTarget) {\n      if (this._isShown) {\n        return;\n      }\n\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n        relatedTarget\n      });\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = true;\n\n      this._backdrop.show();\n\n      if (!this._config.scroll) {\n        new ScrollBarHelper().hide();\n      }\n\n      this._element.setAttribute('aria-modal', true);\n\n      this._element.setAttribute('role', 'dialog');\n\n      this._element.classList.add(CLASS_NAME_SHOWING$1);\n\n      const completeCallBack = () => {\n        if (!this._config.scroll || this._config.backdrop) {\n          this._focustrap.activate();\n        }\n\n        this._element.classList.add(CLASS_NAME_SHOW$3);\n\n        this._element.classList.remove(CLASS_NAME_SHOWING$1);\n\n        EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n          relatedTarget\n        });\n      };\n\n      this._queueCallback(completeCallBack, this._element, true);\n    }\n\n    hide() {\n      if (!this._isShown) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._focustrap.deactivate();\n\n      this._element.blur();\n\n      this._isShown = false;\n\n      this._element.classList.add(CLASS_NAME_HIDING);\n\n      this._backdrop.hide();\n\n      const completeCallback = () => {\n        this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n\n        this._element.removeAttribute('aria-modal');\n\n        this._element.removeAttribute('role');\n\n        if (!this._config.scroll) {\n          new ScrollBarHelper().reset();\n        }\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n      };\n\n      this._queueCallback(completeCallback, this._element, true);\n    }\n\n    dispose() {\n      this._backdrop.dispose();\n\n      this._focustrap.deactivate();\n\n      super.dispose();\n    } // Private\n\n\n    _initializeBackDrop() {\n      const clickCallback = () => {\n        if (this._config.backdrop === 'static') {\n          EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n\n        this.hide();\n      }; // 'static' option will be translated to true, and booleans will keep their value\n\n\n      const isVisible = Boolean(this._config.backdrop);\n      return new Backdrop({\n        className: CLASS_NAME_BACKDROP,\n        isVisible,\n        isAnimated: true,\n        rootElement: this._element.parentNode,\n        clickCallback: isVisible ? clickCallback : null\n      });\n    }\n\n    _initializeFocusTrap() {\n      return new FocusTrap({\n        trapElement: this._element\n      });\n    }\n\n    _addEventListeners() {\n      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n        if (event.key !== ESCAPE_KEY) {\n          return;\n        }\n\n        if (!this._config.keyboard) {\n          EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n\n        this.hide();\n      });\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Offcanvas.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n    const target = getElementFromSelector(this);\n\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (isDisabled(this)) {\n      return;\n    }\n\n    EventHandler.one(target, EVENT_HIDDEN$3, () => {\n      // focus on trigger when it is closed\n      if (isVisible(this)) {\n        this.focus();\n      }\n    }); // avoid conflict when clicking a toggler of an offcanvas, while another is open\n\n    const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n\n    if (alreadyOpen && alreadyOpen !== target) {\n      Offcanvas.getInstance(alreadyOpen).hide();\n    }\n\n    const data = Offcanvas.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n    for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n      Offcanvas.getOrCreateInstance(selector).show();\n    }\n  });\n  EventHandler.on(window, EVENT_RESIZE, () => {\n    for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n      if (getComputedStyle(element).position !== 'fixed') {\n        Offcanvas.getOrCreateInstance(element).hide();\n      }\n    }\n  });\n  enableDismissTrigger(Offcanvas);\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Offcanvas);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/sanitizer.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n  const ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\n  /**\n   * A pattern that recognizes a commonly useful subset of URLs that are safe.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n   */\n\n  const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i;\n  /**\n   * A pattern that matches safe data URLs. Only matches image, video and audio types.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n   */\n\n  const DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i;\n\n  const allowedAttribute = (attribute, allowedAttributeList) => {\n    const attributeName = attribute.nodeName.toLowerCase();\n\n    if (allowedAttributeList.includes(attributeName)) {\n      if (uriAttributes.has(attributeName)) {\n        return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue));\n      }\n\n      return true;\n    } // Check if a regular expression validates the attribute.\n\n\n    return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n  };\n\n  const DefaultAllowlist = {\n    // Global attributes allowed on any supplied element below.\n    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n    a: ['target', 'href', 'title', 'rel'],\n    area: [],\n    b: [],\n    br: [],\n    col: [],\n    code: [],\n    div: [],\n    em: [],\n    hr: [],\n    h1: [],\n    h2: [],\n    h3: [],\n    h4: [],\n    h5: [],\n    h6: [],\n    i: [],\n    img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n    li: [],\n    ol: [],\n    p: [],\n    pre: [],\n    s: [],\n    small: [],\n    span: [],\n    sub: [],\n    sup: [],\n    strong: [],\n    u: [],\n    ul: []\n  };\n  function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n    if (!unsafeHtml.length) {\n      return unsafeHtml;\n    }\n\n    if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n      return sanitizeFunction(unsafeHtml);\n    }\n\n    const domParser = new window.DOMParser();\n    const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n    const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n\n    for (const element of elements) {\n      const elementName = element.nodeName.toLowerCase();\n\n      if (!Object.keys(allowList).includes(elementName)) {\n        element.remove();\n        continue;\n      }\n\n      const attributeList = [].concat(...element.attributes);\n      const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n\n      for (const attribute of attributeList) {\n        if (!allowedAttribute(attribute, allowedAttributes)) {\n          element.removeAttribute(attribute.nodeName);\n        }\n      }\n    }\n\n    return createdDocument.body.innerHTML;\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/template-factory.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$5 = 'TemplateFactory';\n  const Default$4 = {\n    allowList: DefaultAllowlist,\n    content: {},\n    // { selector : text ,  selector2 : text2 , }\n    extraClass: '',\n    html: false,\n    sanitize: true,\n    sanitizeFn: null,\n    template: '<div></div>'\n  };\n  const DefaultType$4 = {\n    allowList: 'object',\n    content: 'object',\n    extraClass: '(string|function)',\n    html: 'boolean',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    template: 'string'\n  };\n  const DefaultContentType = {\n    entry: '(string|element|function|null)',\n    selector: '(string|element)'\n  };\n  /**\n   * Class definition\n   */\n\n  class TemplateFactory extends Config {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n    } // Getters\n\n\n    static get Default() {\n      return Default$4;\n    }\n\n    static get DefaultType() {\n      return DefaultType$4;\n    }\n\n    static get NAME() {\n      return NAME$5;\n    } // Public\n\n\n    getContent() {\n      return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n    }\n\n    hasContent() {\n      return this.getContent().length > 0;\n    }\n\n    changeContent(content) {\n      this._checkContent(content);\n\n      this._config.content = { ...this._config.content,\n        ...content\n      };\n      return this;\n    }\n\n    toHtml() {\n      const templateWrapper = document.createElement('div');\n      templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n\n      for (const [selector, text] of Object.entries(this._config.content)) {\n        this._setContent(templateWrapper, text, selector);\n      }\n\n      const template = templateWrapper.children[0];\n\n      const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n\n      if (extraClass) {\n        template.classList.add(...extraClass.split(' '));\n      }\n\n      return template;\n    } // Private\n\n\n    _typeCheckConfig(config) {\n      super._typeCheckConfig(config);\n\n      this._checkContent(config.content);\n    }\n\n    _checkContent(arg) {\n      for (const [selector, content] of Object.entries(arg)) {\n        super._typeCheckConfig({\n          selector,\n          entry: content\n        }, DefaultContentType);\n      }\n    }\n\n    _setContent(template, content, selector) {\n      const templateElement = SelectorEngine.findOne(selector, template);\n\n      if (!templateElement) {\n        return;\n      }\n\n      content = this._resolvePossibleFunction(content);\n\n      if (!content) {\n        templateElement.remove();\n        return;\n      }\n\n      if (isElement(content)) {\n        this._putElementInTemplate(getElement(content), templateElement);\n\n        return;\n      }\n\n      if (this._config.html) {\n        templateElement.innerHTML = this._maybeSanitize(content);\n        return;\n      }\n\n      templateElement.textContent = content;\n    }\n\n    _maybeSanitize(arg) {\n      return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n    }\n\n    _resolvePossibleFunction(arg) {\n      return typeof arg === 'function' ? arg(this) : arg;\n    }\n\n    _putElementInTemplate(element, templateElement) {\n      if (this._config.html) {\n        templateElement.innerHTML = '';\n        templateElement.append(element);\n        return;\n      }\n\n      templateElement.textContent = element.textContent;\n    }\n\n  }\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): tooltip.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$4 = 'tooltip';\n  const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\n  const CLASS_NAME_FADE$2 = 'fade';\n  const CLASS_NAME_MODAL = 'modal';\n  const CLASS_NAME_SHOW$2 = 'show';\n  const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\n  const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\n  const EVENT_MODAL_HIDE = 'hide.bs.modal';\n  const TRIGGER_HOVER = 'hover';\n  const TRIGGER_FOCUS = 'focus';\n  const TRIGGER_CLICK = 'click';\n  const TRIGGER_MANUAL = 'manual';\n  const EVENT_HIDE$2 = 'hide';\n  const EVENT_HIDDEN$2 = 'hidden';\n  const EVENT_SHOW$2 = 'show';\n  const EVENT_SHOWN$2 = 'shown';\n  const EVENT_INSERTED = 'inserted';\n  const EVENT_CLICK$1 = 'click';\n  const EVENT_FOCUSIN$1 = 'focusin';\n  const EVENT_FOCUSOUT$1 = 'focusout';\n  const EVENT_MOUSEENTER = 'mouseenter';\n  const EVENT_MOUSELEAVE = 'mouseleave';\n  const AttachmentMap = {\n    AUTO: 'auto',\n    TOP: 'top',\n    RIGHT: isRTL() ? 'left' : 'right',\n    BOTTOM: 'bottom',\n    LEFT: isRTL() ? 'right' : 'left'\n  };\n  const Default$3 = {\n    allowList: DefaultAllowlist,\n    animation: true,\n    boundary: 'clippingParents',\n    container: false,\n    customClass: '',\n    delay: 0,\n    fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n    html: false,\n    offset: [0, 0],\n    placement: 'top',\n    popperConfig: null,\n    sanitize: true,\n    sanitizeFn: null,\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\">' + '<div class=\"tooltip-arrow\"></div>' + '<div class=\"tooltip-inner\"></div>' + '</div>',\n    title: '',\n    trigger: 'hover focus'\n  };\n  const DefaultType$3 = {\n    allowList: 'object',\n    animation: 'boolean',\n    boundary: '(string|element)',\n    container: '(string|element|boolean)',\n    customClass: '(string|function)',\n    delay: '(number|object)',\n    fallbackPlacements: 'array',\n    html: 'boolean',\n    offset: '(array|string|function)',\n    placement: '(string|function)',\n    popperConfig: '(null|object|function)',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    selector: '(string|boolean)',\n    template: 'string',\n    title: '(string|element|function)',\n    trigger: 'string'\n  };\n  /**\n   * Class definition\n   */\n\n  class Tooltip extends BaseComponent {\n    constructor(element, config) {\n      if (typeof Popper__namespace === 'undefined') {\n        throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n      }\n\n      super(element, config); // Private\n\n      this._isEnabled = true;\n      this._timeout = 0;\n      this._isHovered = null;\n      this._activeTrigger = {};\n      this._popper = null;\n      this._templateFactory = null;\n      this._newContent = null; // Protected\n\n      this.tip = null;\n\n      this._setListeners();\n\n      if (!this._config.selector) {\n        this._fixTitle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default$3;\n    }\n\n    static get DefaultType() {\n      return DefaultType$3;\n    }\n\n    static get NAME() {\n      return NAME$4;\n    } // Public\n\n\n    enable() {\n      this._isEnabled = true;\n    }\n\n    disable() {\n      this._isEnabled = false;\n    }\n\n    toggleEnabled() {\n      this._isEnabled = !this._isEnabled;\n    }\n\n    toggle() {\n      if (!this._isEnabled) {\n        return;\n      }\n\n      this._activeTrigger.click = !this._activeTrigger.click;\n\n      if (this._isShown()) {\n        this._leave();\n\n        return;\n      }\n\n      this._enter();\n    }\n\n    dispose() {\n      clearTimeout(this._timeout);\n      EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n\n      if (this._element.getAttribute('data-bs-original-title')) {\n        this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n      }\n\n      this._disposePopper();\n\n      super.dispose();\n    }\n\n    show() {\n      if (this._element.style.display === 'none') {\n        throw new Error('Please use show on visible elements');\n      }\n\n      if (!(this._isWithContent() && this._isEnabled)) {\n        return;\n      }\n\n      const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n      const shadowRoot = findShadowRoot(this._element);\n\n      const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n\n      if (showEvent.defaultPrevented || !isInTheDom) {\n        return;\n      } // todo v6 remove this OR make it optional\n\n\n      this._disposePopper();\n\n      const tip = this._getTipElement();\n\n      this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n\n      const {\n        container\n      } = this._config;\n\n      if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n        container.append(tip);\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n      }\n\n      this._popper = this._createPopper(tip);\n      tip.classList.add(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.on(element, 'mouseover', noop);\n        }\n      }\n\n      const complete = () => {\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n\n        if (this._isHovered === false) {\n          this._leave();\n        }\n\n        this._isHovered = false;\n      };\n\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n\n    hide() {\n      if (!this._isShown()) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const tip = this._getTipElement();\n\n      tip.classList.remove(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler.off(element, 'mouseover', noop);\n        }\n      }\n\n      this._activeTrigger[TRIGGER_CLICK] = false;\n      this._activeTrigger[TRIGGER_FOCUS] = false;\n      this._activeTrigger[TRIGGER_HOVER] = false;\n      this._isHovered = null; // it is a trick to support manual triggering\n\n      const complete = () => {\n        if (this._isWithActiveTrigger()) {\n          return;\n        }\n\n        if (!this._isHovered) {\n          this._disposePopper();\n        }\n\n        this._element.removeAttribute('aria-describedby');\n\n        EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n      };\n\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n\n    update() {\n      if (this._popper) {\n        this._popper.update();\n      }\n    } // Protected\n\n\n    _isWithContent() {\n      return Boolean(this._getTitle());\n    }\n\n    _getTipElement() {\n      if (!this.tip) {\n        this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n      }\n\n      return this.tip;\n    }\n\n    _createTipElement(content) {\n      const tip = this._getTemplateFactory(content).toHtml(); // todo: remove this check on v6\n\n\n      if (!tip) {\n        return null;\n      }\n\n      tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); // todo: on v6 the following can be achieved with CSS only\n\n      tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n      const tipId = getUID(this.constructor.NAME).toString();\n      tip.setAttribute('id', tipId);\n\n      if (this._isAnimated()) {\n        tip.classList.add(CLASS_NAME_FADE$2);\n      }\n\n      return tip;\n    }\n\n    setContent(content) {\n      this._newContent = content;\n\n      if (this._isShown()) {\n        this._disposePopper();\n\n        this.show();\n      }\n    }\n\n    _getTemplateFactory(content) {\n      if (this._templateFactory) {\n        this._templateFactory.changeContent(content);\n      } else {\n        this._templateFactory = new TemplateFactory({ ...this._config,\n          // the `content` var has to be after `this._config`\n          // to override config.content in case of popover\n          content,\n          extraClass: this._resolvePossibleFunction(this._config.customClass)\n        });\n      }\n\n      return this._templateFactory;\n    }\n\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n      };\n    }\n\n    _getTitle() {\n      return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n    } // Private\n\n\n    _initializeOnDelegatedTarget(event) {\n      return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n    }\n\n    _isAnimated() {\n      return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n    }\n\n    _isShown() {\n      return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n    }\n\n    _createPopper(tip) {\n      const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement;\n      const attachment = AttachmentMap[placement.toUpperCase()];\n      return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment));\n    }\n\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n\n      return offset;\n    }\n\n    _resolvePossibleFunction(arg) {\n      return typeof arg === 'function' ? arg.call(this._element) : arg;\n    }\n\n    _getPopperConfig(attachment) {\n      const defaultBsPopperConfig = {\n        placement: attachment,\n        modifiers: [{\n          name: 'flip',\n          options: {\n            fallbackPlacements: this._config.fallbackPlacements\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }, {\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'arrow',\n          options: {\n            element: `.${this.constructor.NAME}-arrow`\n          }\n        }, {\n          name: 'preSetPlacement',\n          enabled: true,\n          phase: 'beforeMain',\n          fn: data => {\n            // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n            // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n            this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n          }\n        }]\n      };\n      return { ...defaultBsPopperConfig,\n        ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n      };\n    }\n\n    _setListeners() {\n      const triggers = this._config.trigger.split(' ');\n\n      for (const trigger of triggers) {\n        if (trigger === 'click') {\n          EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context.toggle();\n          });\n        } else if (trigger !== TRIGGER_MANUAL) {\n          const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n          const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n          EventHandler.on(this._element, eventIn, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n\n            context._enter();\n          });\n          EventHandler.on(this._element, eventOut, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n\n            context._leave();\n          });\n        }\n      }\n\n      this._hideModalHandler = () => {\n        if (this._element) {\n          this.hide();\n        }\n      };\n\n      EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n    }\n\n    _fixTitle() {\n      const title = this._element.getAttribute('title');\n\n      if (!title) {\n        return;\n      }\n\n      if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n        this._element.setAttribute('aria-label', title);\n      }\n\n      this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n\n\n      this._element.removeAttribute('title');\n    }\n\n    _enter() {\n      if (this._isShown() || this._isHovered) {\n        this._isHovered = true;\n        return;\n      }\n\n      this._isHovered = true;\n\n      this._setTimeout(() => {\n        if (this._isHovered) {\n          this.show();\n        }\n      }, this._config.delay.show);\n    }\n\n    _leave() {\n      if (this._isWithActiveTrigger()) {\n        return;\n      }\n\n      this._isHovered = false;\n\n      this._setTimeout(() => {\n        if (!this._isHovered) {\n          this.hide();\n        }\n      }, this._config.delay.hide);\n    }\n\n    _setTimeout(handler, timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = setTimeout(handler, timeout);\n    }\n\n    _isWithActiveTrigger() {\n      return Object.values(this._activeTrigger).includes(true);\n    }\n\n    _getConfig(config) {\n      const dataAttributes = Manipulator.getDataAttributes(this._element);\n\n      for (const dataAttribute of Object.keys(dataAttributes)) {\n        if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n          delete dataAttributes[dataAttribute];\n        }\n      }\n\n      config = { ...dataAttributes,\n        ...(typeof config === 'object' && config ? config : {})\n      };\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    }\n\n    _configAfterMerge(config) {\n      config.container = config.container === false ? document.body : getElement(config.container);\n\n      if (typeof config.delay === 'number') {\n        config.delay = {\n          show: config.delay,\n          hide: config.delay\n        };\n      }\n\n      if (typeof config.title === 'number') {\n        config.title = config.title.toString();\n      }\n\n      if (typeof config.content === 'number') {\n        config.content = config.content.toString();\n      }\n\n      return config;\n    }\n\n    _getDelegateConfig() {\n      const config = {};\n\n      for (const key in this._config) {\n        if (this.constructor.Default[key] !== this._config[key]) {\n          config[key] = this._config[key];\n        }\n      }\n\n      config.selector = false;\n      config.trigger = 'manual'; // In the future can be replaced with:\n      // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n      // `Object.fromEntries(keysWithDifferentValues)`\n\n      return config;\n    }\n\n    _disposePopper() {\n      if (this._popper) {\n        this._popper.destroy();\n\n        this._popper = null;\n      }\n\n      if (this.tip) {\n        this.tip.remove();\n        this.tip = null;\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tooltip.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * jQuery\n   */\n\n\n  defineJQueryPlugin(Tooltip);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): popover.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$3 = 'popover';\n  const SELECTOR_TITLE = '.popover-header';\n  const SELECTOR_CONTENT = '.popover-body';\n  const Default$2 = { ...Tooltip.Default,\n    content: '',\n    offset: [0, 8],\n    placement: 'right',\n    template: '<div class=\"popover\" role=\"tooltip\">' + '<div class=\"popover-arrow\"></div>' + '<h3 class=\"popover-header\"></h3>' + '<div class=\"popover-body\"></div>' + '</div>',\n    trigger: 'click'\n  };\n  const DefaultType$2 = { ...Tooltip.DefaultType,\n    content: '(null|string|element|function)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Popover extends Tooltip {\n    // Getters\n    static get Default() {\n      return Default$2;\n    }\n\n    static get DefaultType() {\n      return DefaultType$2;\n    }\n\n    static get NAME() {\n      return NAME$3;\n    } // Overrides\n\n\n    _isWithContent() {\n      return this._getTitle() || this._getContent();\n    } // Private\n\n\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TITLE]: this._getTitle(),\n        [SELECTOR_CONTENT]: this._getContent()\n      };\n    }\n\n    _getContent() {\n      return this._resolvePossibleFunction(this._config.content);\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Popover.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * jQuery\n   */\n\n\n  defineJQueryPlugin(Popover);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): scrollspy.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$2 = 'scrollspy';\n  const DATA_KEY$2 = 'bs.scrollspy';\n  const EVENT_KEY$2 = `.${DATA_KEY$2}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\n  const EVENT_CLICK = `click${EVENT_KEY$2}`;\n  const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\n  const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\n  const CLASS_NAME_ACTIVE$1 = 'active';\n  const SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\n  const SELECTOR_TARGET_LINKS = '[href]';\n  const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\n  const SELECTOR_NAV_LINKS = '.nav-link';\n  const SELECTOR_NAV_ITEMS = '.nav-item';\n  const SELECTOR_LIST_ITEMS = '.list-group-item';\n  const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\n  const SELECTOR_DROPDOWN = '.dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\n  const Default$1 = {\n    offset: null,\n    // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: '0px 0px -25%',\n    smoothScroll: false,\n    target: null,\n    threshold: [0.1, 0.5, 1]\n  };\n  const DefaultType$1 = {\n    offset: '(number|null)',\n    // TODO v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: 'string',\n    smoothScroll: 'boolean',\n    target: 'element',\n    threshold: 'array'\n  };\n  /**\n   * Class definition\n   */\n\n  class ScrollSpy extends BaseComponent {\n    constructor(element, config) {\n      super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper\n\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n      this._activeTarget = null;\n      this._observer = null;\n      this._previousScrollData = {\n        visibleEntryTop: 0,\n        parentScrollTop: 0\n      };\n      this.refresh(); // initialize\n    } // Getters\n\n\n    static get Default() {\n      return Default$1;\n    }\n\n    static get DefaultType() {\n      return DefaultType$1;\n    }\n\n    static get NAME() {\n      return NAME$2;\n    } // Public\n\n\n    refresh() {\n      this._initializeTargetsAndObservables();\n\n      this._maybeEnableSmoothScroll();\n\n      if (this._observer) {\n        this._observer.disconnect();\n      } else {\n        this._observer = this._getNewObserver();\n      }\n\n      for (const section of this._observableSections.values()) {\n        this._observer.observe(section);\n      }\n    }\n\n    dispose() {\n      this._observer.disconnect();\n\n      super.dispose();\n    } // Private\n\n\n    _configAfterMerge(config) {\n      // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n      config.target = getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n\n      config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n\n      if (typeof config.threshold === 'string') {\n        config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n      }\n\n      return config;\n    }\n\n    _maybeEnableSmoothScroll() {\n      if (!this._config.smoothScroll) {\n        return;\n      } // unregister any previous listeners\n\n\n      EventHandler.off(this._config.target, EVENT_CLICK);\n      EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n        const observableSection = this._observableSections.get(event.target.hash);\n\n        if (observableSection) {\n          event.preventDefault();\n          const root = this._rootElement || window;\n          const height = observableSection.offsetTop - this._element.offsetTop;\n\n          if (root.scrollTo) {\n            root.scrollTo({\n              top: height,\n              behavior: 'smooth'\n            });\n            return;\n          } // Chrome 60 doesn't support `scrollTo`\n\n\n          root.scrollTop = height;\n        }\n      });\n    }\n\n    _getNewObserver() {\n      const options = {\n        root: this._rootElement,\n        threshold: this._config.threshold,\n        rootMargin: this._config.rootMargin\n      };\n      return new IntersectionObserver(entries => this._observerCallback(entries), options);\n    } // The logic of selection\n\n\n    _observerCallback(entries) {\n      const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n\n      const activate = entry => {\n        this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n\n        this._process(targetElement(entry));\n      };\n\n      const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n      const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n      this._previousScrollData.parentScrollTop = parentScrollTop;\n\n      for (const entry of entries) {\n        if (!entry.isIntersecting) {\n          this._activeTarget = null;\n\n          this._clearActiveClass(targetElement(entry));\n\n          continue;\n        }\n\n        const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop\n\n        if (userScrollsDown && entryIsLowerThanPrevious) {\n          activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n\n          if (!parentScrollTop) {\n            return;\n          }\n\n          continue;\n        } // if we are scrolling up, pick the smallest offsetTop\n\n\n        if (!userScrollsDown && !entryIsLowerThanPrevious) {\n          activate(entry);\n        }\n      }\n    }\n\n    _initializeTargetsAndObservables() {\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n\n      for (const anchor of targetLinks) {\n        // ensure that the anchor has an id and is not disabled\n        if (!anchor.hash || isDisabled(anchor)) {\n          continue;\n        }\n\n        const observableSection = SelectorEngine.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible\n\n        if (isVisible(observableSection)) {\n          this._targetLinks.set(anchor.hash, anchor);\n\n          this._observableSections.set(anchor.hash, observableSection);\n        }\n      }\n    }\n\n    _process(target) {\n      if (this._activeTarget === target) {\n        return;\n      }\n\n      this._clearActiveClass(this._config.target);\n\n      this._activeTarget = target;\n      target.classList.add(CLASS_NAME_ACTIVE$1);\n\n      this._activateParents(target);\n\n      EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n        relatedTarget: target\n      });\n    }\n\n    _activateParents(target) {\n      // Activate dropdown parents\n      if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n        SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n        return;\n      }\n\n      for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n        // Set triggered links parents as active\n        // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n        for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n          item.classList.add(CLASS_NAME_ACTIVE$1);\n        }\n      }\n    }\n\n    _clearActiveClass(parent) {\n      parent.classList.remove(CLASS_NAME_ACTIVE$1);\n      const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE$1}`, parent);\n\n      for (const node of activeNodes) {\n        node.classList.remove(CLASS_NAME_ACTIVE$1);\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = ScrollSpy.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(window, EVENT_LOAD_DATA_API$1, () => {\n    for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n      ScrollSpy.getOrCreateInstance(spy);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(ScrollSpy);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): tab.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME$1 = 'tab';\n  const DATA_KEY$1 = 'bs.tab';\n  const EVENT_KEY$1 = `.${DATA_KEY$1}`;\n  const EVENT_HIDE$1 = `hide${EVENT_KEY$1}`;\n  const EVENT_HIDDEN$1 = `hidden${EVENT_KEY$1}`;\n  const EVENT_SHOW$1 = `show${EVENT_KEY$1}`;\n  const EVENT_SHOWN$1 = `shown${EVENT_KEY$1}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY$1}`;\n  const EVENT_KEYDOWN = `keydown${EVENT_KEY$1}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY$1}`;\n  const ARROW_LEFT_KEY = 'ArrowLeft';\n  const ARROW_RIGHT_KEY = 'ArrowRight';\n  const ARROW_UP_KEY = 'ArrowUp';\n  const ARROW_DOWN_KEY = 'ArrowDown';\n  const CLASS_NAME_ACTIVE = 'active';\n  const CLASS_NAME_FADE$1 = 'fade';\n  const CLASS_NAME_SHOW$1 = 'show';\n  const CLASS_DROPDOWN = 'dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\n  const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';\n  const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)';\n  const SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]';\n  const SELECTOR_OUTER = '.nav-item, .list-group-item';\n  const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]'; // todo:v6: could be only `tab`\n\n  const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`;\n  /**\n   * Class definition\n   */\n\n  class Tab extends BaseComponent {\n    constructor(element) {\n      super(element);\n      this._parent = this._element.closest(SELECTOR_TAB_PANEL);\n\n      if (!this._parent) {\n        return; // todo: should Throw exception on v6\n        // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n      } // Set up initial aria attributes\n\n\n      this._setInitialAttributes(this._parent, this._getChildren());\n\n      EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n    } // Getters\n\n\n    static get NAME() {\n      return NAME$1;\n    } // Public\n\n\n    show() {\n      // Shows this elem and deactivate the active sibling if exists\n      const innerElem = this._element;\n\n      if (this._elemIsActive(innerElem)) {\n        return;\n      } // Search for active tab on same parent to deactivate it\n\n\n      const active = this._getActiveElem();\n\n      const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE$1, {\n        relatedTarget: innerElem\n      }) : null;\n      const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW$1, {\n        relatedTarget: active\n      });\n\n      if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._deactivate(active, innerElem);\n\n      this._activate(innerElem, active);\n    } // Private\n\n\n    _activate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n\n      element.classList.add(CLASS_NAME_ACTIVE);\n\n      this._activate(getElementFromSelector(element)); // Search and activate/show the proper section\n\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.add(CLASS_NAME_SHOW$1);\n          return;\n        }\n\n        element.removeAttribute('tabindex');\n        element.setAttribute('aria-selected', true);\n\n        this._toggleDropDown(element, true);\n\n        EventHandler.trigger(element, EVENT_SHOWN$1, {\n          relatedTarget: relatedElem\n        });\n      };\n\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1));\n    }\n\n    _deactivate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n\n      element.classList.remove(CLASS_NAME_ACTIVE);\n      element.blur();\n\n      this._deactivate(getElementFromSelector(element)); // Search and deactivate the shown section too\n\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.remove(CLASS_NAME_SHOW$1);\n          return;\n        }\n\n        element.setAttribute('aria-selected', false);\n        element.setAttribute('tabindex', '-1');\n\n        this._toggleDropDown(element, false);\n\n        EventHandler.trigger(element, EVENT_HIDDEN$1, {\n          relatedTarget: relatedElem\n        });\n      };\n\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1));\n    }\n\n    _keydown(event) {\n      if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {\n        return;\n      }\n\n      event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n\n      event.preventDefault();\n      const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);\n      const nextActiveElement = getNextActiveElement(this._getChildren().filter(element => !isDisabled(element)), event.target, isNext, true);\n\n      if (nextActiveElement) {\n        nextActiveElement.focus({\n          preventScroll: true\n        });\n        Tab.getOrCreateInstance(nextActiveElement).show();\n      }\n    }\n\n    _getChildren() {\n      // collection of inner elements\n      return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent);\n    }\n\n    _getActiveElem() {\n      return this._getChildren().find(child => this._elemIsActive(child)) || null;\n    }\n\n    _setInitialAttributes(parent, children) {\n      this._setAttributeIfNotExists(parent, 'role', 'tablist');\n\n      for (const child of children) {\n        this._setInitialAttributesOnChild(child);\n      }\n    }\n\n    _setInitialAttributesOnChild(child) {\n      child = this._getInnerElement(child);\n\n      const isActive = this._elemIsActive(child);\n\n      const outerElem = this._getOuterElement(child);\n\n      child.setAttribute('aria-selected', isActive);\n\n      if (outerElem !== child) {\n        this._setAttributeIfNotExists(outerElem, 'role', 'presentation');\n      }\n\n      if (!isActive) {\n        child.setAttribute('tabindex', '-1');\n      }\n\n      this._setAttributeIfNotExists(child, 'role', 'tab'); // set attributes to the related panel too\n\n\n      this._setInitialAttributesOnTargetPanel(child);\n    }\n\n    _setInitialAttributesOnTargetPanel(child) {\n      const target = getElementFromSelector(child);\n\n      if (!target) {\n        return;\n      }\n\n      this._setAttributeIfNotExists(target, 'role', 'tabpanel');\n\n      if (child.id) {\n        this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`);\n      }\n    }\n\n    _toggleDropDown(element, open) {\n      const outerElem = this._getOuterElement(element);\n\n      if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n        return;\n      }\n\n      const toggle = (selector, className) => {\n        const element = SelectorEngine.findOne(selector, outerElem);\n\n        if (element) {\n          element.classList.toggle(className, open);\n        }\n      };\n\n      toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);\n      toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW$1);\n      outerElem.setAttribute('aria-expanded', open);\n    }\n\n    _setAttributeIfNotExists(element, attribute, value) {\n      if (!element.hasAttribute(attribute)) {\n        element.setAttribute(attribute, value);\n      }\n    }\n\n    _elemIsActive(elem) {\n      return elem.classList.contains(CLASS_NAME_ACTIVE);\n    } // Try to get the inner element (usually the .nav-link)\n\n\n    _getInnerElement(elem) {\n      return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem);\n    } // Try to get the outer element (usually the .nav-item)\n\n\n    _getOuterElement(elem) {\n      return elem.closest(SELECTOR_OUTER) || elem;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tab.getOrCreateInstance(this);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (isDisabled(this)) {\n      return;\n    }\n\n    Tab.getOrCreateInstance(this).show();\n  });\n  /**\n   * Initialize on focus\n   */\n\n  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n      Tab.getOrCreateInstance(element);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Tab);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): toast.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'toast';\n  const DATA_KEY = 'bs.toast';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;\n  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;\n  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\n  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_HIDE = 'hide'; // @deprecated - kept here only for backwards compatibility\n\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_SHOWING = 'showing';\n  const DefaultType = {\n    animation: 'boolean',\n    autohide: 'boolean',\n    delay: 'number'\n  };\n  const Default = {\n    animation: true,\n    autohide: true,\n    delay: 5000\n  };\n  /**\n   * Class definition\n   */\n\n  class Toast extends BaseComponent {\n    constructor(element, config) {\n      super(element, config);\n      this._timeout = null;\n      this._hasMouseInteraction = false;\n      this._hasKeyboardInteraction = false;\n\n      this._setListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    show() {\n      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._clearTimeout();\n\n      if (this._config.animation) {\n        this._element.classList.add(CLASS_NAME_FADE);\n      }\n\n      const complete = () => {\n        this._element.classList.remove(CLASS_NAME_SHOWING);\n\n        EventHandler.trigger(this._element, EVENT_SHOWN);\n\n        this._maybeScheduleHide();\n      };\n\n      this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated\n\n\n      reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING);\n\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n\n    hide() {\n      if (!this.isShown()) {\n        return;\n      }\n\n      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const complete = () => {\n        this._element.classList.add(CLASS_NAME_HIDE); // @deprecated\n\n\n        this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW);\n\n        EventHandler.trigger(this._element, EVENT_HIDDEN);\n      };\n\n      this._element.classList.add(CLASS_NAME_SHOWING);\n\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n\n    dispose() {\n      this._clearTimeout();\n\n      if (this.isShown()) {\n        this._element.classList.remove(CLASS_NAME_SHOW);\n      }\n\n      super.dispose();\n    }\n\n    isShown() {\n      return this._element.classList.contains(CLASS_NAME_SHOW);\n    } // Private\n\n\n    _maybeScheduleHide() {\n      if (!this._config.autohide) {\n        return;\n      }\n\n      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n        return;\n      }\n\n      this._timeout = setTimeout(() => {\n        this.hide();\n      }, this._config.delay);\n    }\n\n    _onInteraction(event, isInteracting) {\n      switch (event.type) {\n        case 'mouseover':\n        case 'mouseout':\n          {\n            this._hasMouseInteraction = isInteracting;\n            break;\n          }\n\n        case 'focusin':\n        case 'focusout':\n          {\n            this._hasKeyboardInteraction = isInteracting;\n            break;\n          }\n      }\n\n      if (isInteracting) {\n        this._clearTimeout();\n\n        return;\n      }\n\n      const nextElement = event.relatedTarget;\n\n      if (this._element === nextElement || this._element.contains(nextElement)) {\n        return;\n      }\n\n      this._maybeScheduleHide();\n    }\n\n    _setListeners() {\n      EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));\n      EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));\n      EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));\n      EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));\n    }\n\n    _clearTimeout() {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Toast.getOrCreateInstance(this, config);\n\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config](this);\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  enableDismissTrigger(Toast);\n  /**\n   * jQuery\n   */\n\n  defineJQueryPlugin(Toast);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): index.umd.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const index_umd = {\n    Alert,\n    Button,\n    Carousel,\n    Collapse,\n    Dropdown,\n    Modal,\n    Offcanvas,\n    Popover,\n    ScrollSpy,\n    Tab,\n    Toast,\n    Tooltip\n  };\n\n  return index_umd;\n\n}));\n//# sourceMappingURL=bootstrap.js.map\n"
  },
  {
    "path": "src/common/bootstrap/dist/js/bootstrap5-js.asd",
    "content": "(defpackage :bootstrap5-js-system\n  (:use :cl\n   :asdf))\n(in-package :bootstrap5-js-system)\n\n\n(defsystem :bootstrap5-js\n  :class \"build-utils:js-library\"\n  :depends-on (:jquery-js)\n  :defsystem-depends-on (:build-utils)\n  :components ((\"build-utils:js-file\" \"bootstrap.bundle\")))\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/alert.js",
    "content": "/*!\n  * Bootstrap alert.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./base-component'), require('./util/component-functions')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './base-component', './util/component-functions'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Alert = factory(global.Index, global.EventHandler, global.BaseComponent, global.ComponentFunctions));\n})(this, (function (index, EventHandler, BaseComponent, componentFunctions) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): alert.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'alert';\n  const DATA_KEY = 'bs.alert';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_CLOSE = `close${EVENT_KEY}`;\n  const EVENT_CLOSED = `closed${EVENT_KEY}`;\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  /**\n   * Class definition\n   */\n\n  class Alert extends BaseComponent__default.default {\n    // Getters\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    close() {\n      const closeEvent = EventHandler__default.default.trigger(this._element, EVENT_CLOSE);\n\n      if (closeEvent.defaultPrevented) {\n        return;\n      }\n\n      this._element.classList.remove(CLASS_NAME_SHOW);\n\n      const isAnimated = this._element.classList.contains(CLASS_NAME_FADE);\n\n      this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n    } // Private\n\n\n    _destroyElement() {\n      this._element.remove();\n\n      EventHandler__default.default.trigger(this._element, EVENT_CLOSED);\n      this.dispose();\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Alert.getOrCreateInstance(this);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  componentFunctions.enableDismissTrigger(Alert, 'close');\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Alert);\n\n  return Alert;\n\n}));\n//# sourceMappingURL=alert.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/base-component.js",
    "content": "/*!\n  * Bootstrap base-component.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./dom/data'), require('./util/index'), require('./dom/event-handler'), require('./util/config')) :\n  typeof define === 'function' && define.amd ? define(['./dom/data', './util/index', './dom/event-handler', './util/config'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BaseComponent = factory(global.Data, global.Index, global.EventHandler, global.Config));\n})(this, (function (Data, index, EventHandler, Config) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const Data__default = /*#__PURE__*/_interopDefaultLegacy(Data);\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const Config__default = /*#__PURE__*/_interopDefaultLegacy(Config);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): base-component.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const VERSION = '5.2.3';\n  /**\n   * Class definition\n   */\n\n  class BaseComponent extends Config__default.default {\n    constructor(element, config) {\n      super();\n      element = index.getElement(element);\n\n      if (!element) {\n        return;\n      }\n\n      this._element = element;\n      this._config = this._getConfig(config);\n      Data__default.default.set(this._element, this.constructor.DATA_KEY, this);\n    } // Public\n\n\n    dispose() {\n      Data__default.default.remove(this._element, this.constructor.DATA_KEY);\n      EventHandler__default.default.off(this._element, this.constructor.EVENT_KEY);\n\n      for (const propertyName of Object.getOwnPropertyNames(this)) {\n        this[propertyName] = null;\n      }\n    }\n\n    _queueCallback(callback, element, isAnimated = true) {\n      index.executeAfterTransition(callback, element, isAnimated);\n    }\n\n    _getConfig(config) {\n      config = this._mergeConfigObj(config, this._element);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    } // Static\n\n\n    static getInstance(element) {\n      return Data__default.default.get(index.getElement(element), this.DATA_KEY);\n    }\n\n    static getOrCreateInstance(element, config = {}) {\n      return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n    }\n\n    static get VERSION() {\n      return VERSION;\n    }\n\n    static get DATA_KEY() {\n      return `bs.${this.NAME}`;\n    }\n\n    static get EVENT_KEY() {\n      return `.${this.DATA_KEY}`;\n    }\n\n    static eventName(name) {\n      return `${name}${this.EVENT_KEY}`;\n    }\n\n  }\n\n  return BaseComponent;\n\n}));\n//# sourceMappingURL=base-component.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/button.js",
    "content": "/*!\n  * Bootstrap button.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./base-component')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './base-component'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Button = factory(global.Index, global.EventHandler, global.BaseComponent));\n})(this, (function (index, EventHandler, BaseComponent) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): button.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'button';\n  const DATA_KEY = 'bs.button';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const CLASS_NAME_ACTIVE = 'active';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]';\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  /**\n   * Class definition\n   */\n\n  class Button extends BaseComponent__default.default {\n    // Getters\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    toggle() {\n      // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n      this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE));\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Button.getOrCreateInstance(this);\n\n        if (config === 'toggle') {\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n    event.preventDefault();\n    const button = event.target.closest(SELECTOR_DATA_TOGGLE);\n    const data = Button.getOrCreateInstance(button);\n    data.toggle();\n  });\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Button);\n\n  return Button;\n\n}));\n//# sourceMappingURL=button.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/carousel.js",
    "content": "/*!\n  * Bootstrap carousel.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./dom/manipulator'), require('./dom/selector-engine'), require('./util/swipe'), require('./base-component')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/swipe', './base-component'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Carousel = factory(global.Index, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Swipe, global.BaseComponent));\n})(this, (function (index, EventHandler, Manipulator, SelectorEngine, Swipe, BaseComponent) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const Swipe__default = /*#__PURE__*/_interopDefaultLegacy(Swipe);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): carousel.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'carousel';\n  const DATA_KEY = 'bs.carousel';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const ARROW_LEFT_KEY = 'ArrowLeft';\n  const ARROW_RIGHT_KEY = 'ArrowRight';\n  const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\n  const ORDER_NEXT = 'next';\n  const ORDER_PREV = 'prev';\n  const DIRECTION_LEFT = 'left';\n  const DIRECTION_RIGHT = 'right';\n  const EVENT_SLIDE = `slide${EVENT_KEY}`;\n  const EVENT_SLID = `slid${EVENT_KEY}`;\n  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;\n  const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`;\n  const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`;\n  const EVENT_DRAG_START = `dragstart${EVENT_KEY}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_CAROUSEL = 'carousel';\n  const CLASS_NAME_ACTIVE = 'active';\n  const CLASS_NAME_SLIDE = 'slide';\n  const CLASS_NAME_END = 'carousel-item-end';\n  const CLASS_NAME_START = 'carousel-item-start';\n  const CLASS_NAME_NEXT = 'carousel-item-next';\n  const CLASS_NAME_PREV = 'carousel-item-prev';\n  const SELECTOR_ACTIVE = '.active';\n  const SELECTOR_ITEM = '.carousel-item';\n  const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\n  const SELECTOR_ITEM_IMG = '.carousel-item img';\n  const SELECTOR_INDICATORS = '.carousel-indicators';\n  const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\n  const SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\n  const KEY_TO_DIRECTION = {\n    [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n    [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n  };\n  const Default = {\n    interval: 5000,\n    keyboard: true,\n    pause: 'hover',\n    ride: false,\n    touch: true,\n    wrap: true\n  };\n  const DefaultType = {\n    interval: '(number|boolean)',\n    // TODO:v6 remove boolean support\n    keyboard: 'boolean',\n    pause: '(string|boolean)',\n    ride: '(boolean|string)',\n    touch: 'boolean',\n    wrap: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Carousel extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config);\n      this._interval = null;\n      this._activeElement = null;\n      this._isSliding = false;\n      this.touchTimeout = null;\n      this._swipeHelper = null;\n      this._indicatorsElement = SelectorEngine__default.default.findOne(SELECTOR_INDICATORS, this._element);\n\n      this._addEventListeners();\n\n      if (this._config.ride === CLASS_NAME_CAROUSEL) {\n        this.cycle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    next() {\n      this._slide(ORDER_NEXT);\n    }\n\n    nextWhenVisible() {\n      // FIXME TODO use `document.visibilityState`\n      // Don't call next when the page isn't visible\n      // or the carousel or its parent isn't visible\n      if (!document.hidden && index.isVisible(this._element)) {\n        this.next();\n      }\n    }\n\n    prev() {\n      this._slide(ORDER_PREV);\n    }\n\n    pause() {\n      if (this._isSliding) {\n        index.triggerTransitionEnd(this._element);\n      }\n\n      this._clearInterval();\n    }\n\n    cycle() {\n      this._clearInterval();\n\n      this._updateInterval();\n\n      this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n    }\n\n    _maybeEnableCycle() {\n      if (!this._config.ride) {\n        return;\n      }\n\n      if (this._isSliding) {\n        EventHandler__default.default.one(this._element, EVENT_SLID, () => this.cycle());\n        return;\n      }\n\n      this.cycle();\n    }\n\n    to(index) {\n      const items = this._getItems();\n\n      if (index > items.length - 1 || index < 0) {\n        return;\n      }\n\n      if (this._isSliding) {\n        EventHandler__default.default.one(this._element, EVENT_SLID, () => this.to(index));\n        return;\n      }\n\n      const activeIndex = this._getItemIndex(this._getActive());\n\n      if (activeIndex === index) {\n        return;\n      }\n\n      const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n\n      this._slide(order, items[index]);\n    }\n\n    dispose() {\n      if (this._swipeHelper) {\n        this._swipeHelper.dispose();\n      }\n\n      super.dispose();\n    } // Private\n\n\n    _configAfterMerge(config) {\n      config.defaultInterval = config.interval;\n      return config;\n    }\n\n    _addEventListeners() {\n      if (this._config.keyboard) {\n        EventHandler__default.default.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n      }\n\n      if (this._config.pause === 'hover') {\n        EventHandler__default.default.on(this._element, EVENT_MOUSEENTER, () => this.pause());\n        EventHandler__default.default.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle());\n      }\n\n      if (this._config.touch && Swipe__default.default.isSupported()) {\n        this._addTouchEventListeners();\n      }\n    }\n\n    _addTouchEventListeners() {\n      for (const img of SelectorEngine__default.default.find(SELECTOR_ITEM_IMG, this._element)) {\n        EventHandler__default.default.on(img, EVENT_DRAG_START, event => event.preventDefault());\n      }\n\n      const endCallBack = () => {\n        if (this._config.pause !== 'hover') {\n          return;\n        } // If it's a touch-enabled device, mouseenter/leave are fired as\n        // part of the mouse compatibility events on first tap - the carousel\n        // would stop cycling until user tapped out of it;\n        // here, we listen for touchend, explicitly pause the carousel\n        // (as if it's the second time we tap on it, mouseenter compat event\n        // is NOT fired) and after a timeout (to allow for mouse compatibility\n        // events to fire) we explicitly restart cycling\n\n\n        this.pause();\n\n        if (this.touchTimeout) {\n          clearTimeout(this.touchTimeout);\n        }\n\n        this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n      };\n\n      const swipeConfig = {\n        leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n        rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n        endCallback: endCallBack\n      };\n      this._swipeHelper = new Swipe__default.default(this._element, swipeConfig);\n    }\n\n    _keydown(event) {\n      if (/input|textarea/i.test(event.target.tagName)) {\n        return;\n      }\n\n      const direction = KEY_TO_DIRECTION[event.key];\n\n      if (direction) {\n        event.preventDefault();\n\n        this._slide(this._directionToOrder(direction));\n      }\n    }\n\n    _getItemIndex(element) {\n      return this._getItems().indexOf(element);\n    }\n\n    _setActiveIndicatorElement(index) {\n      if (!this._indicatorsElement) {\n        return;\n      }\n\n      const activeIndicator = SelectorEngine__default.default.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n      activeIndicator.classList.remove(CLASS_NAME_ACTIVE);\n      activeIndicator.removeAttribute('aria-current');\n      const newActiveIndicator = SelectorEngine__default.default.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n\n      if (newActiveIndicator) {\n        newActiveIndicator.classList.add(CLASS_NAME_ACTIVE);\n        newActiveIndicator.setAttribute('aria-current', 'true');\n      }\n    }\n\n    _updateInterval() {\n      const element = this._activeElement || this._getActive();\n\n      if (!element) {\n        return;\n      }\n\n      const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n      this._config.interval = elementInterval || this._config.defaultInterval;\n    }\n\n    _slide(order, element = null) {\n      if (this._isSliding) {\n        return;\n      }\n\n      const activeElement = this._getActive();\n\n      const isNext = order === ORDER_NEXT;\n      const nextElement = element || index.getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n\n      if (nextElement === activeElement) {\n        return;\n      }\n\n      const nextElementIndex = this._getItemIndex(nextElement);\n\n      const triggerEvent = eventName => {\n        return EventHandler__default.default.trigger(this._element, eventName, {\n          relatedTarget: nextElement,\n          direction: this._orderToDirection(order),\n          from: this._getItemIndex(activeElement),\n          to: nextElementIndex\n        });\n      };\n\n      const slideEvent = triggerEvent(EVENT_SLIDE);\n\n      if (slideEvent.defaultPrevented) {\n        return;\n      }\n\n      if (!activeElement || !nextElement) {\n        // Some weirdness is happening, so we bail\n        // todo: change tests that use empty divs to avoid this check\n        return;\n      }\n\n      const isCycling = Boolean(this._interval);\n      this.pause();\n      this._isSliding = true;\n\n      this._setActiveIndicatorElement(nextElementIndex);\n\n      this._activeElement = nextElement;\n      const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n      const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n      nextElement.classList.add(orderClassName);\n      index.reflow(nextElement);\n      activeElement.classList.add(directionalClassName);\n      nextElement.classList.add(directionalClassName);\n\n      const completeCallBack = () => {\n        nextElement.classList.remove(directionalClassName, orderClassName);\n        nextElement.classList.add(CLASS_NAME_ACTIVE);\n        activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName);\n        this._isSliding = false;\n        triggerEvent(EVENT_SLID);\n      };\n\n      this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n\n      if (isCycling) {\n        this.cycle();\n      }\n    }\n\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_SLIDE);\n    }\n\n    _getActive() {\n      return SelectorEngine__default.default.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n    }\n\n    _getItems() {\n      return SelectorEngine__default.default.find(SELECTOR_ITEM, this._element);\n    }\n\n    _clearInterval() {\n      if (this._interval) {\n        clearInterval(this._interval);\n        this._interval = null;\n      }\n    }\n\n    _directionToOrder(direction) {\n      if (index.isRTL()) {\n        return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n      }\n\n      return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n    }\n\n    _orderToDirection(order) {\n      if (index.isRTL()) {\n        return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n      }\n\n      return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Carousel.getOrCreateInstance(this, config);\n\n        if (typeof config === 'number') {\n          data.to(config);\n          return;\n        }\n\n        if (typeof config === 'string') {\n          if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n    const target = index.getElementFromSelector(this);\n\n    if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n      return;\n    }\n\n    event.preventDefault();\n    const carousel = Carousel.getOrCreateInstance(target);\n    const slideIndex = this.getAttribute('data-bs-slide-to');\n\n    if (slideIndex) {\n      carousel.to(slideIndex);\n\n      carousel._maybeEnableCycle();\n\n      return;\n    }\n\n    if (Manipulator__default.default.getDataAttribute(this, 'slide') === 'next') {\n      carousel.next();\n\n      carousel._maybeEnableCycle();\n\n      return;\n    }\n\n    carousel.prev();\n\n    carousel._maybeEnableCycle();\n  });\n  EventHandler__default.default.on(window, EVENT_LOAD_DATA_API, () => {\n    const carousels = SelectorEngine__default.default.find(SELECTOR_DATA_RIDE);\n\n    for (const carousel of carousels) {\n      Carousel.getOrCreateInstance(carousel);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Carousel);\n\n  return Carousel;\n\n}));\n//# sourceMappingURL=carousel.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/collapse.js",
    "content": "/*!\n  * Bootstrap collapse.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./dom/selector-engine'), require('./base-component')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './dom/selector-engine', './base-component'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Collapse = factory(global.Index, global.EventHandler, global.SelectorEngine, global.BaseComponent));\n})(this, (function (index, EventHandler, SelectorEngine, BaseComponent) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): collapse.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'collapse';\n  const DATA_KEY = 'bs.collapse';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_COLLAPSE = 'collapse';\n  const CLASS_NAME_COLLAPSING = 'collapsing';\n  const CLASS_NAME_COLLAPSED = 'collapsed';\n  const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\n  const CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\n  const WIDTH = 'width';\n  const HEIGHT = 'height';\n  const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]';\n  const Default = {\n    parent: null,\n    toggle: true\n  };\n  const DefaultType = {\n    parent: '(null|element)',\n    toggle: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Collapse extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config);\n      this._isTransitioning = false;\n      this._triggerArray = [];\n      const toggleList = SelectorEngine__default.default.find(SELECTOR_DATA_TOGGLE);\n\n      for (const elem of toggleList) {\n        const selector = index.getSelectorFromElement(elem);\n        const filterElement = SelectorEngine__default.default.find(selector).filter(foundElement => foundElement === this._element);\n\n        if (selector !== null && filterElement.length) {\n          this._triggerArray.push(elem);\n        }\n      }\n\n      this._initializeChildren();\n\n      if (!this._config.parent) {\n        this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n      }\n\n      if (this._config.toggle) {\n        this.toggle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    toggle() {\n      if (this._isShown()) {\n        this.hide();\n      } else {\n        this.show();\n      }\n    }\n\n    show() {\n      if (this._isTransitioning || this._isShown()) {\n        return;\n      }\n\n      let activeChildren = []; // find active children\n\n      if (this._config.parent) {\n        activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n          toggle: false\n        }));\n      }\n\n      if (activeChildren.length && activeChildren[0]._isTransitioning) {\n        return;\n      }\n\n      const startEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW);\n\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n\n      for (const activeInstance of activeChildren) {\n        activeInstance.hide();\n      }\n\n      const dimension = this._getDimension();\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSE);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n      this._element.style[dimension] = 0;\n\n      this._addAriaAndCollapsedClass(this._triggerArray, true);\n\n      this._isTransitioning = true;\n\n      const complete = () => {\n        this._isTransitioning = false;\n\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n        this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW);\n\n        this._element.style[dimension] = '';\n        EventHandler__default.default.trigger(this._element, EVENT_SHOWN);\n      };\n\n      const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n      const scrollSize = `scroll${capitalizedDimension}`;\n\n      this._queueCallback(complete, this._element, true);\n\n      this._element.style[dimension] = `${this._element[scrollSize]}px`;\n    }\n\n    hide() {\n      if (this._isTransitioning || !this._isShown()) {\n        return;\n      }\n\n      const startEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE);\n\n      if (startEvent.defaultPrevented) {\n        return;\n      }\n\n      const dimension = this._getDimension();\n\n      this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n      index.reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_COLLAPSING);\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW);\n\n      for (const trigger of this._triggerArray) {\n        const element = index.getElementFromSelector(trigger);\n\n        if (element && !this._isShown(element)) {\n          this._addAriaAndCollapsedClass([trigger], false);\n        }\n      }\n\n      this._isTransitioning = true;\n\n      const complete = () => {\n        this._isTransitioning = false;\n\n        this._element.classList.remove(CLASS_NAME_COLLAPSING);\n\n        this._element.classList.add(CLASS_NAME_COLLAPSE);\n\n        EventHandler__default.default.trigger(this._element, EVENT_HIDDEN);\n      };\n\n      this._element.style[dimension] = '';\n\n      this._queueCallback(complete, this._element, true);\n    }\n\n    _isShown(element = this._element) {\n      return element.classList.contains(CLASS_NAME_SHOW);\n    } // Private\n\n\n    _configAfterMerge(config) {\n      config.toggle = Boolean(config.toggle); // Coerce string values\n\n      config.parent = index.getElement(config.parent);\n      return config;\n    }\n\n    _getDimension() {\n      return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n    }\n\n    _initializeChildren() {\n      if (!this._config.parent) {\n        return;\n      }\n\n      const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE);\n\n      for (const element of children) {\n        const selected = index.getElementFromSelector(element);\n\n        if (selected) {\n          this._addAriaAndCollapsedClass([element], this._isShown(selected));\n        }\n      }\n    }\n\n    _getFirstLevelChildren(selector) {\n      const children = SelectorEngine__default.default.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); // remove children if greater depth\n\n      return SelectorEngine__default.default.find(selector, this._config.parent).filter(element => !children.includes(element));\n    }\n\n    _addAriaAndCollapsedClass(triggerArray, isOpen) {\n      if (!triggerArray.length) {\n        return;\n      }\n\n      for (const element of triggerArray) {\n        element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n        element.setAttribute('aria-expanded', isOpen);\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      const _config = {};\n\n      if (typeof config === 'string' && /show|hide/.test(config)) {\n        _config.toggle = false;\n      }\n\n      return this.each(function () {\n        const data = Collapse.getOrCreateInstance(this, _config);\n\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config]();\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n    if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n      event.preventDefault();\n    }\n\n    const selector = index.getSelectorFromElement(this);\n    const selectorElements = SelectorEngine__default.default.find(selector);\n\n    for (const element of selectorElements) {\n      Collapse.getOrCreateInstance(element, {\n        toggle: false\n      }).toggle();\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Collapse);\n\n  return Collapse;\n\n}));\n//# sourceMappingURL=collapse.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/dom/data.js",
    "content": "/*!\n  * Bootstrap data.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Data = factory());\n})(this, (function () { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/data.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  /**\n   * Constants\n   */\n  const elementMap = new Map();\n  const data = {\n    set(element, key, instance) {\n      if (!elementMap.has(element)) {\n        elementMap.set(element, new Map());\n      }\n\n      const instanceMap = elementMap.get(element); // make it clear we only want one instance per element\n      // can be removed later when multiple key/instances are fine to be used\n\n      if (!instanceMap.has(key) && instanceMap.size !== 0) {\n        // eslint-disable-next-line no-console\n        console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n        return;\n      }\n\n      instanceMap.set(key, instance);\n    },\n\n    get(element, key) {\n      if (elementMap.has(element)) {\n        return elementMap.get(element).get(key) || null;\n      }\n\n      return null;\n    },\n\n    remove(element, key) {\n      if (!elementMap.has(element)) {\n        return;\n      }\n\n      const instanceMap = elementMap.get(element);\n      instanceMap.delete(key); // free up element references if there are no instances left for an element\n\n      if (instanceMap.size === 0) {\n        elementMap.delete(element);\n      }\n    }\n\n  };\n\n  return data;\n\n}));\n//# sourceMappingURL=data.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/dom/event-handler.js",
    "content": "/*!\n  * Bootstrap event-handler.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index')) :\n  typeof define === 'function' && define.amd ? define(['../util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EventHandler = factory(global.Index));\n})(this, (function (index) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/event-handler.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\n  const stripNameRegex = /\\..*/;\n  const stripUidRegex = /::\\d+$/;\n  const eventRegistry = {}; // Events storage\n\n  let uidEvent = 1;\n  const customEvents = {\n    mouseenter: 'mouseover',\n    mouseleave: 'mouseout'\n  };\n  const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n  /**\n   * Private methods\n   */\n\n  function makeEventUid(element, uid) {\n    return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n  }\n\n  function getElementEvents(element) {\n    const uid = makeEventUid(element);\n    element.uidEvent = uid;\n    eventRegistry[uid] = eventRegistry[uid] || {};\n    return eventRegistry[uid];\n  }\n\n  function bootstrapHandler(element, fn) {\n    return function handler(event) {\n      hydrateObj(event, {\n        delegateTarget: element\n      });\n\n      if (handler.oneOff) {\n        EventHandler.off(element, event.type, fn);\n      }\n\n      return fn.apply(element, [event]);\n    };\n  }\n\n  function bootstrapDelegationHandler(element, selector, fn) {\n    return function handler(event) {\n      const domElements = element.querySelectorAll(selector);\n\n      for (let {\n        target\n      } = event; target && target !== this; target = target.parentNode) {\n        for (const domElement of domElements) {\n          if (domElement !== target) {\n            continue;\n          }\n\n          hydrateObj(event, {\n            delegateTarget: target\n          });\n\n          if (handler.oneOff) {\n            EventHandler.off(element, event.type, selector, fn);\n          }\n\n          return fn.apply(target, [event]);\n        }\n      }\n    };\n  }\n\n  function findHandler(events, callable, delegationSelector = null) {\n    return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n  }\n\n  function normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n    const isDelegated = typeof handler === 'string'; // todo: tooltip passes `false` instead of selector, so we need to check\n\n    const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n    let typeEvent = getTypeEvent(originalTypeEvent);\n\n    if (!nativeEvents.has(typeEvent)) {\n      typeEvent = originalTypeEvent;\n    }\n\n    return [isDelegated, callable, typeEvent];\n  }\n\n  function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n    if (typeof originalTypeEvent !== 'string' || !element) {\n      return;\n    }\n\n    let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n    // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n\n    if (originalTypeEvent in customEvents) {\n      const wrapFunction = fn => {\n        return function (event) {\n          if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n            return fn.call(this, event);\n          }\n        };\n      };\n\n      callable = wrapFunction(callable);\n    }\n\n    const events = getElementEvents(element);\n    const handlers = events[typeEvent] || (events[typeEvent] = {});\n    const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n\n    if (previousFunction) {\n      previousFunction.oneOff = previousFunction.oneOff && oneOff;\n      return;\n    }\n\n    const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n    const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n    fn.delegationSelector = isDelegated ? handler : null;\n    fn.callable = callable;\n    fn.oneOff = oneOff;\n    fn.uidEvent = uid;\n    handlers[uid] = fn;\n    element.addEventListener(typeEvent, fn, isDelegated);\n  }\n\n  function removeHandler(element, events, typeEvent, handler, delegationSelector) {\n    const fn = findHandler(events[typeEvent], handler, delegationSelector);\n\n    if (!fn) {\n      return;\n    }\n\n    element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n    delete events[typeEvent][fn.uidEvent];\n  }\n\n  function removeNamespacedHandlers(element, events, typeEvent, namespace) {\n    const storeElementEvent = events[typeEvent] || {};\n\n    for (const handlerKey of Object.keys(storeElementEvent)) {\n      if (handlerKey.includes(namespace)) {\n        const event = storeElementEvent[handlerKey];\n        removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n      }\n    }\n  }\n\n  function getTypeEvent(event) {\n    // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n    event = event.replace(stripNameRegex, '');\n    return customEvents[event] || event;\n  }\n\n  const EventHandler = {\n    on(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, false);\n    },\n\n    one(element, event, handler, delegationFunction) {\n      addHandler(element, event, handler, delegationFunction, true);\n    },\n\n    off(element, originalTypeEvent, handler, delegationFunction) {\n      if (typeof originalTypeEvent !== 'string' || !element) {\n        return;\n      }\n\n      const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n      const inNamespace = typeEvent !== originalTypeEvent;\n      const events = getElementEvents(element);\n      const storeElementEvent = events[typeEvent] || {};\n      const isNamespace = originalTypeEvent.startsWith('.');\n\n      if (typeof callable !== 'undefined') {\n        // Simplest case: handler is passed, remove that listener ONLY.\n        if (!Object.keys(storeElementEvent).length) {\n          return;\n        }\n\n        removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n        return;\n      }\n\n      if (isNamespace) {\n        for (const elementEvent of Object.keys(events)) {\n          removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n        }\n      }\n\n      for (const keyHandlers of Object.keys(storeElementEvent)) {\n        const handlerKey = keyHandlers.replace(stripUidRegex, '');\n\n        if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n          const event = storeElementEvent[keyHandlers];\n          removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n        }\n      }\n    },\n\n    trigger(element, event, args) {\n      if (typeof event !== 'string' || !element) {\n        return null;\n      }\n\n      const $ = index.getjQuery();\n      const typeEvent = getTypeEvent(event);\n      const inNamespace = event !== typeEvent;\n      let jQueryEvent = null;\n      let bubbles = true;\n      let nativeDispatch = true;\n      let defaultPrevented = false;\n\n      if (inNamespace && $) {\n        jQueryEvent = $.Event(event, args);\n        $(element).trigger(jQueryEvent);\n        bubbles = !jQueryEvent.isPropagationStopped();\n        nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n        defaultPrevented = jQueryEvent.isDefaultPrevented();\n      }\n\n      let evt = new Event(event, {\n        bubbles,\n        cancelable: true\n      });\n      evt = hydrateObj(evt, args);\n\n      if (defaultPrevented) {\n        evt.preventDefault();\n      }\n\n      if (nativeDispatch) {\n        element.dispatchEvent(evt);\n      }\n\n      if (evt.defaultPrevented && jQueryEvent) {\n        jQueryEvent.preventDefault();\n      }\n\n      return evt;\n    }\n\n  };\n\n  function hydrateObj(obj, meta) {\n    for (const [key, value] of Object.entries(meta || {})) {\n      try {\n        obj[key] = value;\n      } catch (_unused) {\n        Object.defineProperty(obj, key, {\n          configurable: true,\n\n          get() {\n            return value;\n          }\n\n        });\n      }\n    }\n\n    return obj;\n  }\n\n  return EventHandler;\n\n}));\n//# sourceMappingURL=event-handler.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/dom/manipulator.js",
    "content": "/*!\n  * Bootstrap manipulator.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Manipulator = factory());\n})(this, (function () { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/manipulator.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  function normalizeData(value) {\n    if (value === 'true') {\n      return true;\n    }\n\n    if (value === 'false') {\n      return false;\n    }\n\n    if (value === Number(value).toString()) {\n      return Number(value);\n    }\n\n    if (value === '' || value === 'null') {\n      return null;\n    }\n\n    if (typeof value !== 'string') {\n      return value;\n    }\n\n    try {\n      return JSON.parse(decodeURIComponent(value));\n    } catch (_unused) {\n      return value;\n    }\n  }\n\n  function normalizeDataKey(key) {\n    return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n  }\n\n  const Manipulator = {\n    setDataAttribute(element, key, value) {\n      element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n    },\n\n    removeDataAttribute(element, key) {\n      element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n    },\n\n    getDataAttributes(element) {\n      if (!element) {\n        return {};\n      }\n\n      const attributes = {};\n      const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n\n      for (const key of bsKeys) {\n        let pureKey = key.replace(/^bs/, '');\n        pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n        attributes[pureKey] = normalizeData(element.dataset[key]);\n      }\n\n      return attributes;\n    },\n\n    getDataAttribute(element, key) {\n      return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n    }\n\n  };\n\n  return Manipulator;\n\n}));\n//# sourceMappingURL=manipulator.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/dom/selector-engine.js",
    "content": "/*!\n  * Bootstrap selector-engine.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index')) :\n  typeof define === 'function' && define.amd ? define(['../util/index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SelectorEngine = factory(global.Index));\n})(this, (function (index) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dom/selector-engine.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const SelectorEngine = {\n    find(selector, element = document.documentElement) {\n      return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n    },\n\n    findOne(selector, element = document.documentElement) {\n      return Element.prototype.querySelector.call(element, selector);\n    },\n\n    children(element, selector) {\n      return [].concat(...element.children).filter(child => child.matches(selector));\n    },\n\n    parents(element, selector) {\n      const parents = [];\n      let ancestor = element.parentNode.closest(selector);\n\n      while (ancestor) {\n        parents.push(ancestor);\n        ancestor = ancestor.parentNode.closest(selector);\n      }\n\n      return parents;\n    },\n\n    prev(element, selector) {\n      let previous = element.previousElementSibling;\n\n      while (previous) {\n        if (previous.matches(selector)) {\n          return [previous];\n        }\n\n        previous = previous.previousElementSibling;\n      }\n\n      return [];\n    },\n\n    // TODO: this is now unused; remove later along with prev()\n    next(element, selector) {\n      let next = element.nextElementSibling;\n\n      while (next) {\n        if (next.matches(selector)) {\n          return [next];\n        }\n\n        next = next.nextElementSibling;\n      }\n\n      return [];\n    },\n\n    focusableChildren(element) {\n      const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n      return this.find(focusables, element).filter(el => !index.isDisabled(el) && index.isVisible(el));\n    }\n\n  };\n\n  return SelectorEngine;\n\n}));\n//# sourceMappingURL=selector-engine.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/dropdown.js",
    "content": "/*!\n  * Bootstrap dropdown.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./util/index'), require('./dom/event-handler'), require('./dom/manipulator'), require('./dom/selector-engine'), require('./base-component')) :\n  typeof define === 'function' && define.amd ? define(['@popperjs/core', './util/index', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './base-component'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global[\"@popperjs/core\"], global.Index, global.EventHandler, global.Manipulator, global.SelectorEngine, global.BaseComponent));\n})(this, (function (Popper, index, EventHandler, Manipulator, SelectorEngine, BaseComponent) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  function _interopNamespace(e) {\n    if (e && e.__esModule) return e;\n    const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });\n    if (e) {\n      for (const k in e) {\n        if (k !== 'default') {\n          const d = Object.getOwnPropertyDescriptor(e, k);\n          Object.defineProperty(n, k, d.get ? d : {\n            enumerable: true,\n            get: () => e[k]\n          });\n        }\n      }\n    }\n    n.default = e;\n    return Object.freeze(n);\n  }\n\n  const Popper__namespace = /*#__PURE__*/_interopNamespace(Popper);\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): dropdown.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'dropdown';\n  const DATA_KEY = 'bs.dropdown';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const ESCAPE_KEY = 'Escape';\n  const TAB_KEY = 'Tab';\n  const ARROW_UP_KEY = 'ArrowUp';\n  const ARROW_DOWN_KEY = 'ArrowDown';\n  const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_DROPUP = 'dropup';\n  const CLASS_NAME_DROPEND = 'dropend';\n  const CLASS_NAME_DROPSTART = 'dropstart';\n  const CLASS_NAME_DROPUP_CENTER = 'dropup-center';\n  const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\n  const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;\n  const SELECTOR_MENU = '.dropdown-menu';\n  const SELECTOR_NAVBAR = '.navbar';\n  const SELECTOR_NAVBAR_NAV = '.navbar-nav';\n  const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\n  const PLACEMENT_TOP = index.isRTL() ? 'top-end' : 'top-start';\n  const PLACEMENT_TOPEND = index.isRTL() ? 'top-start' : 'top-end';\n  const PLACEMENT_BOTTOM = index.isRTL() ? 'bottom-end' : 'bottom-start';\n  const PLACEMENT_BOTTOMEND = index.isRTL() ? 'bottom-start' : 'bottom-end';\n  const PLACEMENT_RIGHT = index.isRTL() ? 'left-start' : 'right-start';\n  const PLACEMENT_LEFT = index.isRTL() ? 'right-start' : 'left-start';\n  const PLACEMENT_TOPCENTER = 'top';\n  const PLACEMENT_BOTTOMCENTER = 'bottom';\n  const Default = {\n    autoClose: true,\n    boundary: 'clippingParents',\n    display: 'dynamic',\n    offset: [0, 2],\n    popperConfig: null,\n    reference: 'toggle'\n  };\n  const DefaultType = {\n    autoClose: '(boolean|string)',\n    boundary: '(string|element)',\n    display: 'string',\n    offset: '(array|string|function)',\n    popperConfig: '(null|object|function)',\n    reference: '(string|element|object)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Dropdown extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config);\n      this._popper = null;\n      this._parent = this._element.parentNode; // dropdown wrapper\n      // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n      this._menu = SelectorEngine__default.default.next(this._element, SELECTOR_MENU)[0] || SelectorEngine__default.default.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine__default.default.findOne(SELECTOR_MENU, this._parent);\n      this._inNavbar = this._detectNavbar();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    toggle() {\n      return this._isShown() ? this.hide() : this.show();\n    }\n\n    show() {\n      if (index.isDisabled(this._element) || this._isShown()) {\n        return;\n      }\n\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n      const showEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW, relatedTarget);\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._createPopper(); // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n\n      if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler__default.default.on(element, 'mouseover', index.noop);\n        }\n      }\n\n      this._element.focus();\n\n      this._element.setAttribute('aria-expanded', true);\n\n      this._menu.classList.add(CLASS_NAME_SHOW);\n\n      this._element.classList.add(CLASS_NAME_SHOW);\n\n      EventHandler__default.default.trigger(this._element, EVENT_SHOWN, relatedTarget);\n    }\n\n    hide() {\n      if (index.isDisabled(this._element) || !this._isShown()) {\n        return;\n      }\n\n      const relatedTarget = {\n        relatedTarget: this._element\n      };\n\n      this._completeHide(relatedTarget);\n    }\n\n    dispose() {\n      if (this._popper) {\n        this._popper.destroy();\n      }\n\n      super.dispose();\n    }\n\n    update() {\n      this._inNavbar = this._detectNavbar();\n\n      if (this._popper) {\n        this._popper.update();\n      }\n    } // Private\n\n\n    _completeHide(relatedTarget) {\n      const hideEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE, relatedTarget);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      } // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler__default.default.off(element, 'mouseover', index.noop);\n        }\n      }\n\n      if (this._popper) {\n        this._popper.destroy();\n      }\n\n      this._menu.classList.remove(CLASS_NAME_SHOW);\n\n      this._element.classList.remove(CLASS_NAME_SHOW);\n\n      this._element.setAttribute('aria-expanded', 'false');\n\n      Manipulator__default.default.removeDataAttribute(this._menu, 'popper');\n      EventHandler__default.default.trigger(this._element, EVENT_HIDDEN, relatedTarget);\n    }\n\n    _getConfig(config) {\n      config = super._getConfig(config);\n\n      if (typeof config.reference === 'object' && !index.isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n        // Popper virtual elements require a getBoundingClientRect method\n        throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n      }\n\n      return config;\n    }\n\n    _createPopper() {\n      if (typeof Popper__namespace === 'undefined') {\n        throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n      }\n\n      let referenceElement = this._element;\n\n      if (this._config.reference === 'parent') {\n        referenceElement = this._parent;\n      } else if (index.isElement(this._config.reference)) {\n        referenceElement = index.getElement(this._config.reference);\n      } else if (typeof this._config.reference === 'object') {\n        referenceElement = this._config.reference;\n      }\n\n      const popperConfig = this._getPopperConfig();\n\n      this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);\n    }\n\n    _isShown() {\n      return this._menu.classList.contains(CLASS_NAME_SHOW);\n    }\n\n    _getPlacement() {\n      const parentDropdown = this._parent;\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n        return PLACEMENT_RIGHT;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n        return PLACEMENT_LEFT;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n        return PLACEMENT_TOPCENTER;\n      }\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n        return PLACEMENT_BOTTOMCENTER;\n      } // We need to trim the value because custom properties can also include spaces\n\n\n      const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n\n      if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n        return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n      }\n\n      return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n    }\n\n    _detectNavbar() {\n      return this._element.closest(SELECTOR_NAVBAR) !== null;\n    }\n\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n\n      return offset;\n    }\n\n    _getPopperConfig() {\n      const defaultBsPopperConfig = {\n        placement: this._getPlacement(),\n        modifiers: [{\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }]\n      }; // Disable Popper if we have a static display or Dropdown is in Navbar\n\n      if (this._inNavbar || this._config.display === 'static') {\n        Manipulator__default.default.setDataAttribute(this._menu, 'popper', 'static'); // todo:v6 remove\n\n        defaultBsPopperConfig.modifiers = [{\n          name: 'applyStyles',\n          enabled: false\n        }];\n      }\n\n      return { ...defaultBsPopperConfig,\n        ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n      };\n    }\n\n    _selectMenuItem({\n      key,\n      target\n    }) {\n      const items = SelectorEngine__default.default.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index.isVisible(element));\n\n      if (!items.length) {\n        return;\n      } // if target isn't included in items (e.g. when expanding the dropdown)\n      // allow cycling to get the last item in case key equals ARROW_UP_KEY\n\n\n      index.getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Dropdown.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n    static clearMenus(event) {\n      if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY) {\n        return;\n      }\n\n      const openToggles = SelectorEngine__default.default.find(SELECTOR_DATA_TOGGLE_SHOWN);\n\n      for (const toggle of openToggles) {\n        const context = Dropdown.getInstance(toggle);\n\n        if (!context || context._config.autoClose === false) {\n          continue;\n        }\n\n        const composedPath = event.composedPath();\n        const isMenuTarget = composedPath.includes(context._menu);\n\n        if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n          continue;\n        } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n\n\n        if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n          continue;\n        }\n\n        const relatedTarget = {\n          relatedTarget: context._element\n        };\n\n        if (event.type === 'click') {\n          relatedTarget.clickEvent = event;\n        }\n\n        context._completeHide(relatedTarget);\n      }\n    }\n\n    static dataApiKeydownHandler(event) {\n      // If not an UP | DOWN | ESCAPE key => not a dropdown command\n      // If input/textarea && if key is other than ESCAPE => not a dropdown command\n      const isInput = /input|textarea/i.test(event.target.tagName);\n      const isEscapeEvent = event.key === ESCAPE_KEY;\n      const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);\n\n      if (!isUpOrDownEvent && !isEscapeEvent) {\n        return;\n      }\n\n      if (isInput && !isEscapeEvent) {\n        return;\n      }\n\n      event.preventDefault(); // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n\n      const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine__default.default.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine__default.default.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine__default.default.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);\n      const instance = Dropdown.getOrCreateInstance(getToggleButton);\n\n      if (isUpOrDownEvent) {\n        event.stopPropagation();\n        instance.show();\n\n        instance._selectMenuItem(event);\n\n        return;\n      }\n\n      if (instance._isShown()) {\n        // else is escape and we check if it is shown\n        event.stopPropagation();\n        instance.hide();\n        getToggleButton.focus();\n      }\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler);\n  EventHandler__default.default.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus);\n  EventHandler__default.default.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    event.preventDefault();\n    Dropdown.getOrCreateInstance(this).toggle();\n  });\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Dropdown);\n\n  return Dropdown;\n\n}));\n//# sourceMappingURL=dropdown.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/modal.js",
    "content": "/*!\n  * Bootstrap modal.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./dom/selector-engine'), require('./util/scrollbar'), require('./base-component'), require('./util/backdrop'), require('./util/focustrap'), require('./util/component-functions')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './dom/selector-engine', './util/scrollbar', './base-component', './util/backdrop', './util/focustrap', './util/component-functions'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Modal = factory(global.Index, global.EventHandler, global.SelectorEngine, global.Scrollbar, global.BaseComponent, global.Backdrop, global.Focustrap, global.ComponentFunctions));\n})(this, (function (index, EventHandler, SelectorEngine, ScrollBarHelper, BaseComponent, Backdrop, FocusTrap, componentFunctions) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const ScrollBarHelper__default = /*#__PURE__*/_interopDefaultLegacy(ScrollBarHelper);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n  const Backdrop__default = /*#__PURE__*/_interopDefaultLegacy(Backdrop);\n  const FocusTrap__default = /*#__PURE__*/_interopDefaultLegacy(FocusTrap);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): modal.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'modal';\n  const DATA_KEY = 'bs.modal';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const ESCAPE_KEY = 'Escape';\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_RESIZE = `resize${EVENT_KEY}`;\n  const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;\n  const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`;\n  const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_OPEN = 'modal-open';\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_STATIC = 'modal-static';\n  const OPEN_SELECTOR = '.modal.show';\n  const SELECTOR_DIALOG = '.modal-dialog';\n  const SELECTOR_MODAL_BODY = '.modal-body';\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]';\n  const Default = {\n    backdrop: true,\n    focus: true,\n    keyboard: true\n  };\n  const DefaultType = {\n    backdrop: '(boolean|string)',\n    focus: 'boolean',\n    keyboard: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Modal extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config);\n      this._dialog = SelectorEngine__default.default.findOne(SELECTOR_DIALOG, this._element);\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n      this._isShown = false;\n      this._isTransitioning = false;\n      this._scrollBar = new ScrollBarHelper__default.default();\n\n      this._addEventListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n\n    show(relatedTarget) {\n      if (this._isShown || this._isTransitioning) {\n        return;\n      }\n\n      const showEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW, {\n        relatedTarget\n      });\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = true;\n      this._isTransitioning = true;\n\n      this._scrollBar.hide();\n\n      document.body.classList.add(CLASS_NAME_OPEN);\n\n      this._adjustDialog();\n\n      this._backdrop.show(() => this._showElement(relatedTarget));\n    }\n\n    hide() {\n      if (!this._isShown || this._isTransitioning) {\n        return;\n      }\n\n      const hideEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = false;\n      this._isTransitioning = true;\n\n      this._focustrap.deactivate();\n\n      this._element.classList.remove(CLASS_NAME_SHOW);\n\n      this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n    }\n\n    dispose() {\n      for (const htmlElement of [window, this._dialog]) {\n        EventHandler__default.default.off(htmlElement, EVENT_KEY);\n      }\n\n      this._backdrop.dispose();\n\n      this._focustrap.deactivate();\n\n      super.dispose();\n    }\n\n    handleUpdate() {\n      this._adjustDialog();\n    } // Private\n\n\n    _initializeBackDrop() {\n      return new Backdrop__default.default({\n        isVisible: Boolean(this._config.backdrop),\n        // 'static' option will be translated to true, and booleans will keep their value,\n        isAnimated: this._isAnimated()\n      });\n    }\n\n    _initializeFocusTrap() {\n      return new FocusTrap__default.default({\n        trapElement: this._element\n      });\n    }\n\n    _showElement(relatedTarget) {\n      // try to append dynamic modal\n      if (!document.body.contains(this._element)) {\n        document.body.append(this._element);\n      }\n\n      this._element.style.display = 'block';\n\n      this._element.removeAttribute('aria-hidden');\n\n      this._element.setAttribute('aria-modal', true);\n\n      this._element.setAttribute('role', 'dialog');\n\n      this._element.scrollTop = 0;\n      const modalBody = SelectorEngine__default.default.findOne(SELECTOR_MODAL_BODY, this._dialog);\n\n      if (modalBody) {\n        modalBody.scrollTop = 0;\n      }\n\n      index.reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_SHOW);\n\n      const transitionComplete = () => {\n        if (this._config.focus) {\n          this._focustrap.activate();\n        }\n\n        this._isTransitioning = false;\n        EventHandler__default.default.trigger(this._element, EVENT_SHOWN, {\n          relatedTarget\n        });\n      };\n\n      this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n    }\n\n    _addEventListeners() {\n      EventHandler__default.default.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n        if (event.key !== ESCAPE_KEY) {\n          return;\n        }\n\n        if (this._config.keyboard) {\n          event.preventDefault();\n          this.hide();\n          return;\n        }\n\n        this._triggerBackdropTransition();\n      });\n      EventHandler__default.default.on(window, EVENT_RESIZE, () => {\n        if (this._isShown && !this._isTransitioning) {\n          this._adjustDialog();\n        }\n      });\n      EventHandler__default.default.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n        // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n        EventHandler__default.default.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n          if (this._element !== event.target || this._element !== event2.target) {\n            return;\n          }\n\n          if (this._config.backdrop === 'static') {\n            this._triggerBackdropTransition();\n\n            return;\n          }\n\n          if (this._config.backdrop) {\n            this.hide();\n          }\n        });\n      });\n    }\n\n    _hideModal() {\n      this._element.style.display = 'none';\n\n      this._element.setAttribute('aria-hidden', true);\n\n      this._element.removeAttribute('aria-modal');\n\n      this._element.removeAttribute('role');\n\n      this._isTransitioning = false;\n\n      this._backdrop.hide(() => {\n        document.body.classList.remove(CLASS_NAME_OPEN);\n\n        this._resetAdjustments();\n\n        this._scrollBar.reset();\n\n        EventHandler__default.default.trigger(this._element, EVENT_HIDDEN);\n      });\n    }\n\n    _isAnimated() {\n      return this._element.classList.contains(CLASS_NAME_FADE);\n    }\n\n    _triggerBackdropTransition() {\n      const hideEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE_PREVENTED);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n      const initialOverflowY = this._element.style.overflowY; // return if the following background transition hasn't yet completed\n\n      if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n        return;\n      }\n\n      if (!isModalOverflowing) {\n        this._element.style.overflowY = 'hidden';\n      }\n\n      this._element.classList.add(CLASS_NAME_STATIC);\n\n      this._queueCallback(() => {\n        this._element.classList.remove(CLASS_NAME_STATIC);\n\n        this._queueCallback(() => {\n          this._element.style.overflowY = initialOverflowY;\n        }, this._dialog);\n      }, this._dialog);\n\n      this._element.focus();\n    }\n    /**\n     * The following methods are used to handle overflowing modals\n     */\n\n\n    _adjustDialog() {\n      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n\n      const scrollbarWidth = this._scrollBar.getWidth();\n\n      const isBodyOverflowing = scrollbarWidth > 0;\n\n      if (isBodyOverflowing && !isModalOverflowing) {\n        const property = index.isRTL() ? 'paddingLeft' : 'paddingRight';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n\n      if (!isBodyOverflowing && isModalOverflowing) {\n        const property = index.isRTL() ? 'paddingRight' : 'paddingLeft';\n        this._element.style[property] = `${scrollbarWidth}px`;\n      }\n    }\n\n    _resetAdjustments() {\n      this._element.style.paddingLeft = '';\n      this._element.style.paddingRight = '';\n    } // Static\n\n\n    static jQueryInterface(config, relatedTarget) {\n      return this.each(function () {\n        const data = Modal.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](relatedTarget);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    const target = index.getElementFromSelector(this);\n\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    EventHandler__default.default.one(target, EVENT_SHOW, showEvent => {\n      if (showEvent.defaultPrevented) {\n        // only register focus restorer if modal will actually get shown\n        return;\n      }\n\n      EventHandler__default.default.one(target, EVENT_HIDDEN, () => {\n        if (index.isVisible(this)) {\n          this.focus();\n        }\n      });\n    }); // avoid conflict when clicking modal toggler while another one is open\n\n    const alreadyOpen = SelectorEngine__default.default.findOne(OPEN_SELECTOR);\n\n    if (alreadyOpen) {\n      Modal.getInstance(alreadyOpen).hide();\n    }\n\n    const data = Modal.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  componentFunctions.enableDismissTrigger(Modal);\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Modal);\n\n  return Modal;\n\n}));\n//# sourceMappingURL=modal.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/offcanvas.js",
    "content": "/*!\n  * Bootstrap offcanvas.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./util/scrollbar'), require('./dom/event-handler'), require('./base-component'), require('./dom/selector-engine'), require('./util/backdrop'), require('./util/focustrap'), require('./util/component-functions')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './util/scrollbar', './dom/event-handler', './base-component', './dom/selector-engine', './util/backdrop', './util/focustrap', './util/component-functions'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Offcanvas = factory(global.Index, global.Scrollbar, global.EventHandler, global.BaseComponent, global.SelectorEngine, global.Backdrop, global.Focustrap, global.ComponentFunctions));\n})(this, (function (index, ScrollBarHelper, EventHandler, BaseComponent, SelectorEngine, Backdrop, FocusTrap, componentFunctions) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const ScrollBarHelper__default = /*#__PURE__*/_interopDefaultLegacy(ScrollBarHelper);\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const Backdrop__default = /*#__PURE__*/_interopDefaultLegacy(Backdrop);\n  const FocusTrap__default = /*#__PURE__*/_interopDefaultLegacy(FocusTrap);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): offcanvas.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'offcanvas';\n  const DATA_KEY = 'bs.offcanvas';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;\n  const ESCAPE_KEY = 'Escape';\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_SHOWING = 'showing';\n  const CLASS_NAME_HIDING = 'hiding';\n  const CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\n  const OPEN_SELECTOR = '.offcanvas.show';\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_RESIZE = `resize${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;\n  const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]';\n  const Default = {\n    backdrop: true,\n    keyboard: true,\n    scroll: false\n  };\n  const DefaultType = {\n    backdrop: '(boolean|string)',\n    keyboard: 'boolean',\n    scroll: 'boolean'\n  };\n  /**\n   * Class definition\n   */\n\n  class Offcanvas extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config);\n      this._isShown = false;\n      this._backdrop = this._initializeBackDrop();\n      this._focustrap = this._initializeFocusTrap();\n\n      this._addEventListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    toggle(relatedTarget) {\n      return this._isShown ? this.hide() : this.show(relatedTarget);\n    }\n\n    show(relatedTarget) {\n      if (this._isShown) {\n        return;\n      }\n\n      const showEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW, {\n        relatedTarget\n      });\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._isShown = true;\n\n      this._backdrop.show();\n\n      if (!this._config.scroll) {\n        new ScrollBarHelper__default.default().hide();\n      }\n\n      this._element.setAttribute('aria-modal', true);\n\n      this._element.setAttribute('role', 'dialog');\n\n      this._element.classList.add(CLASS_NAME_SHOWING);\n\n      const completeCallBack = () => {\n        if (!this._config.scroll || this._config.backdrop) {\n          this._focustrap.activate();\n        }\n\n        this._element.classList.add(CLASS_NAME_SHOW);\n\n        this._element.classList.remove(CLASS_NAME_SHOWING);\n\n        EventHandler__default.default.trigger(this._element, EVENT_SHOWN, {\n          relatedTarget\n        });\n      };\n\n      this._queueCallback(completeCallBack, this._element, true);\n    }\n\n    hide() {\n      if (!this._isShown) {\n        return;\n      }\n\n      const hideEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._focustrap.deactivate();\n\n      this._element.blur();\n\n      this._isShown = false;\n\n      this._element.classList.add(CLASS_NAME_HIDING);\n\n      this._backdrop.hide();\n\n      const completeCallback = () => {\n        this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING);\n\n        this._element.removeAttribute('aria-modal');\n\n        this._element.removeAttribute('role');\n\n        if (!this._config.scroll) {\n          new ScrollBarHelper__default.default().reset();\n        }\n\n        EventHandler__default.default.trigger(this._element, EVENT_HIDDEN);\n      };\n\n      this._queueCallback(completeCallback, this._element, true);\n    }\n\n    dispose() {\n      this._backdrop.dispose();\n\n      this._focustrap.deactivate();\n\n      super.dispose();\n    } // Private\n\n\n    _initializeBackDrop() {\n      const clickCallback = () => {\n        if (this._config.backdrop === 'static') {\n          EventHandler__default.default.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n\n        this.hide();\n      }; // 'static' option will be translated to true, and booleans will keep their value\n\n\n      const isVisible = Boolean(this._config.backdrop);\n      return new Backdrop__default.default({\n        className: CLASS_NAME_BACKDROP,\n        isVisible,\n        isAnimated: true,\n        rootElement: this._element.parentNode,\n        clickCallback: isVisible ? clickCallback : null\n      });\n    }\n\n    _initializeFocusTrap() {\n      return new FocusTrap__default.default({\n        trapElement: this._element\n      });\n    }\n\n    _addEventListeners() {\n      EventHandler__default.default.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n        if (event.key !== ESCAPE_KEY) {\n          return;\n        }\n\n        if (!this._config.keyboard) {\n          EventHandler__default.default.trigger(this._element, EVENT_HIDE_PREVENTED);\n          return;\n        }\n\n        this.hide();\n      });\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Offcanvas.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config](this);\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    const target = index.getElementFromSelector(this);\n\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (index.isDisabled(this)) {\n      return;\n    }\n\n    EventHandler__default.default.one(target, EVENT_HIDDEN, () => {\n      // focus on trigger when it is closed\n      if (index.isVisible(this)) {\n        this.focus();\n      }\n    }); // avoid conflict when clicking a toggler of an offcanvas, while another is open\n\n    const alreadyOpen = SelectorEngine__default.default.findOne(OPEN_SELECTOR);\n\n    if (alreadyOpen && alreadyOpen !== target) {\n      Offcanvas.getInstance(alreadyOpen).hide();\n    }\n\n    const data = Offcanvas.getOrCreateInstance(target);\n    data.toggle(this);\n  });\n  EventHandler__default.default.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const selector of SelectorEngine__default.default.find(OPEN_SELECTOR)) {\n      Offcanvas.getOrCreateInstance(selector).show();\n    }\n  });\n  EventHandler__default.default.on(window, EVENT_RESIZE, () => {\n    for (const element of SelectorEngine__default.default.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n      if (getComputedStyle(element).position !== 'fixed') {\n        Offcanvas.getOrCreateInstance(element).hide();\n      }\n    }\n  });\n  componentFunctions.enableDismissTrigger(Offcanvas);\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Offcanvas);\n\n  return Offcanvas;\n\n}));\n//# sourceMappingURL=offcanvas.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/popover.js",
    "content": "/*!\n  * Bootstrap popover.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./tooltip')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './tooltip'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Index, global.Tooltip));\n})(this, (function (index, Tooltip) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const Tooltip__default = /*#__PURE__*/_interopDefaultLegacy(Tooltip);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): popover.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'popover';\n  const SELECTOR_TITLE = '.popover-header';\n  const SELECTOR_CONTENT = '.popover-body';\n  const Default = { ...Tooltip__default.default.Default,\n    content: '',\n    offset: [0, 8],\n    placement: 'right',\n    template: '<div class=\"popover\" role=\"tooltip\">' + '<div class=\"popover-arrow\"></div>' + '<h3 class=\"popover-header\"></h3>' + '<div class=\"popover-body\"></div>' + '</div>',\n    trigger: 'click'\n  };\n  const DefaultType = { ...Tooltip__default.default.DefaultType,\n    content: '(null|string|element|function)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Popover extends Tooltip__default.default {\n    // Getters\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Overrides\n\n\n    _isWithContent() {\n      return this._getTitle() || this._getContent();\n    } // Private\n\n\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TITLE]: this._getTitle(),\n        [SELECTOR_CONTENT]: this._getContent()\n      };\n    }\n\n    _getContent() {\n      return this._resolvePossibleFunction(this._config.content);\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Popover.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * jQuery\n   */\n\n\n  index.defineJQueryPlugin(Popover);\n\n  return Popover;\n\n}));\n//# sourceMappingURL=popover.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/scrollspy.js",
    "content": "/*!\n  * Bootstrap scrollspy.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./dom/selector-engine'), require('./base-component')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './dom/selector-engine', './base-component'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollspy = factory(global.Index, global.EventHandler, global.SelectorEngine, global.BaseComponent));\n})(this, (function (index, EventHandler, SelectorEngine, BaseComponent) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): scrollspy.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'scrollspy';\n  const DATA_KEY = 'bs.scrollspy';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const DATA_API_KEY = '.data-api';\n  const EVENT_ACTIVATE = `activate${EVENT_KEY}`;\n  const EVENT_CLICK = `click${EVENT_KEY}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;\n  const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\n  const CLASS_NAME_ACTIVE = 'active';\n  const SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\n  const SELECTOR_TARGET_LINKS = '[href]';\n  const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\n  const SELECTOR_NAV_LINKS = '.nav-link';\n  const SELECTOR_NAV_ITEMS = '.nav-item';\n  const SELECTOR_LIST_ITEMS = '.list-group-item';\n  const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\n  const SELECTOR_DROPDOWN = '.dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\n  const Default = {\n    offset: null,\n    // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: '0px 0px -25%',\n    smoothScroll: false,\n    target: null,\n    threshold: [0.1, 0.5, 1]\n  };\n  const DefaultType = {\n    offset: '(number|null)',\n    // TODO v6 @deprecated, keep it for backwards compatibility reasons\n    rootMargin: 'string',\n    smoothScroll: 'boolean',\n    target: 'element',\n    threshold: 'array'\n  };\n  /**\n   * Class definition\n   */\n\n  class ScrollSpy extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper\n\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n      this._activeTarget = null;\n      this._observer = null;\n      this._previousScrollData = {\n        visibleEntryTop: 0,\n        parentScrollTop: 0\n      };\n      this.refresh(); // initialize\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    refresh() {\n      this._initializeTargetsAndObservables();\n\n      this._maybeEnableSmoothScroll();\n\n      if (this._observer) {\n        this._observer.disconnect();\n      } else {\n        this._observer = this._getNewObserver();\n      }\n\n      for (const section of this._observableSections.values()) {\n        this._observer.observe(section);\n      }\n    }\n\n    dispose() {\n      this._observer.disconnect();\n\n      super.dispose();\n    } // Private\n\n\n    _configAfterMerge(config) {\n      // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n      config.target = index.getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n\n      config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n\n      if (typeof config.threshold === 'string') {\n        config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n      }\n\n      return config;\n    }\n\n    _maybeEnableSmoothScroll() {\n      if (!this._config.smoothScroll) {\n        return;\n      } // unregister any previous listeners\n\n\n      EventHandler__default.default.off(this._config.target, EVENT_CLICK);\n      EventHandler__default.default.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n        const observableSection = this._observableSections.get(event.target.hash);\n\n        if (observableSection) {\n          event.preventDefault();\n          const root = this._rootElement || window;\n          const height = observableSection.offsetTop - this._element.offsetTop;\n\n          if (root.scrollTo) {\n            root.scrollTo({\n              top: height,\n              behavior: 'smooth'\n            });\n            return;\n          } // Chrome 60 doesn't support `scrollTo`\n\n\n          root.scrollTop = height;\n        }\n      });\n    }\n\n    _getNewObserver() {\n      const options = {\n        root: this._rootElement,\n        threshold: this._config.threshold,\n        rootMargin: this._config.rootMargin\n      };\n      return new IntersectionObserver(entries => this._observerCallback(entries), options);\n    } // The logic of selection\n\n\n    _observerCallback(entries) {\n      const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n\n      const activate = entry => {\n        this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n\n        this._process(targetElement(entry));\n      };\n\n      const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n      const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n      this._previousScrollData.parentScrollTop = parentScrollTop;\n\n      for (const entry of entries) {\n        if (!entry.isIntersecting) {\n          this._activeTarget = null;\n\n          this._clearActiveClass(targetElement(entry));\n\n          continue;\n        }\n\n        const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop\n\n        if (userScrollsDown && entryIsLowerThanPrevious) {\n          activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n\n          if (!parentScrollTop) {\n            return;\n          }\n\n          continue;\n        } // if we are scrolling up, pick the smallest offsetTop\n\n\n        if (!userScrollsDown && !entryIsLowerThanPrevious) {\n          activate(entry);\n        }\n      }\n    }\n\n    _initializeTargetsAndObservables() {\n      this._targetLinks = new Map();\n      this._observableSections = new Map();\n      const targetLinks = SelectorEngine__default.default.find(SELECTOR_TARGET_LINKS, this._config.target);\n\n      for (const anchor of targetLinks) {\n        // ensure that the anchor has an id and is not disabled\n        if (!anchor.hash || index.isDisabled(anchor)) {\n          continue;\n        }\n\n        const observableSection = SelectorEngine__default.default.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible\n\n        if (index.isVisible(observableSection)) {\n          this._targetLinks.set(anchor.hash, anchor);\n\n          this._observableSections.set(anchor.hash, observableSection);\n        }\n      }\n    }\n\n    _process(target) {\n      if (this._activeTarget === target) {\n        return;\n      }\n\n      this._clearActiveClass(this._config.target);\n\n      this._activeTarget = target;\n      target.classList.add(CLASS_NAME_ACTIVE);\n\n      this._activateParents(target);\n\n      EventHandler__default.default.trigger(this._element, EVENT_ACTIVATE, {\n        relatedTarget: target\n      });\n    }\n\n    _activateParents(target) {\n      // Activate dropdown parents\n      if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n        SelectorEngine__default.default.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE);\n        return;\n      }\n\n      for (const listGroup of SelectorEngine__default.default.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n        // Set triggered links parents as active\n        // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n        for (const item of SelectorEngine__default.default.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n          item.classList.add(CLASS_NAME_ACTIVE);\n        }\n      }\n    }\n\n    _clearActiveClass(parent) {\n      parent.classList.remove(CLASS_NAME_ACTIVE);\n      const activeNodes = SelectorEngine__default.default.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent);\n\n      for (const node of activeNodes) {\n        node.classList.remove(CLASS_NAME_ACTIVE);\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = ScrollSpy.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const spy of SelectorEngine__default.default.find(SELECTOR_DATA_SPY)) {\n      ScrollSpy.getOrCreateInstance(spy);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(ScrollSpy);\n\n  return ScrollSpy;\n\n}));\n//# sourceMappingURL=scrollspy.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/tab.js",
    "content": "/*!\n  * Bootstrap tab.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./dom/selector-engine'), require('./base-component')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './dom/selector-engine', './base-component'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tab = factory(global.Index, global.EventHandler, global.SelectorEngine, global.BaseComponent));\n})(this, (function (index, EventHandler, SelectorEngine, BaseComponent) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): tab.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'tab';\n  const DATA_KEY = 'bs.tab';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}`;\n  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;\n  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`;\n  const ARROW_LEFT_KEY = 'ArrowLeft';\n  const ARROW_RIGHT_KEY = 'ArrowRight';\n  const ARROW_UP_KEY = 'ArrowUp';\n  const ARROW_DOWN_KEY = 'ArrowDown';\n  const CLASS_NAME_ACTIVE = 'active';\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_DROPDOWN = 'dropdown';\n  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';\n  const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';\n  const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)';\n  const SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]';\n  const SELECTOR_OUTER = '.nav-item, .list-group-item';\n  const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]'; // todo:v6: could be only `tab`\n\n  const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;\n  const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`;\n  /**\n   * Class definition\n   */\n\n  class Tab extends BaseComponent__default.default {\n    constructor(element) {\n      super(element);\n      this._parent = this._element.closest(SELECTOR_TAB_PANEL);\n\n      if (!this._parent) {\n        return; // todo: should Throw exception on v6\n        // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n      } // Set up initial aria attributes\n\n\n      this._setInitialAttributes(this._parent, this._getChildren());\n\n      EventHandler__default.default.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));\n    } // Getters\n\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    show() {\n      // Shows this elem and deactivate the active sibling if exists\n      const innerElem = this._element;\n\n      if (this._elemIsActive(innerElem)) {\n        return;\n      } // Search for active tab on same parent to deactivate it\n\n\n      const active = this._getActiveElem();\n\n      const hideEvent = active ? EventHandler__default.default.trigger(active, EVENT_HIDE, {\n        relatedTarget: innerElem\n      }) : null;\n      const showEvent = EventHandler__default.default.trigger(innerElem, EVENT_SHOW, {\n        relatedTarget: active\n      });\n\n      if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {\n        return;\n      }\n\n      this._deactivate(active, innerElem);\n\n      this._activate(innerElem, active);\n    } // Private\n\n\n    _activate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n\n      element.classList.add(CLASS_NAME_ACTIVE);\n\n      this._activate(index.getElementFromSelector(element)); // Search and activate/show the proper section\n\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.add(CLASS_NAME_SHOW);\n          return;\n        }\n\n        element.removeAttribute('tabindex');\n        element.setAttribute('aria-selected', true);\n\n        this._toggleDropDown(element, true);\n\n        EventHandler__default.default.trigger(element, EVENT_SHOWN, {\n          relatedTarget: relatedElem\n        });\n      };\n\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));\n    }\n\n    _deactivate(element, relatedElem) {\n      if (!element) {\n        return;\n      }\n\n      element.classList.remove(CLASS_NAME_ACTIVE);\n      element.blur();\n\n      this._deactivate(index.getElementFromSelector(element)); // Search and deactivate the shown section too\n\n\n      const complete = () => {\n        if (element.getAttribute('role') !== 'tab') {\n          element.classList.remove(CLASS_NAME_SHOW);\n          return;\n        }\n\n        element.setAttribute('aria-selected', false);\n        element.setAttribute('tabindex', '-1');\n\n        this._toggleDropDown(element, false);\n\n        EventHandler__default.default.trigger(element, EVENT_HIDDEN, {\n          relatedTarget: relatedElem\n        });\n      };\n\n      this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));\n    }\n\n    _keydown(event) {\n      if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {\n        return;\n      }\n\n      event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n\n      event.preventDefault();\n      const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);\n      const nextActiveElement = index.getNextActiveElement(this._getChildren().filter(element => !index.isDisabled(element)), event.target, isNext, true);\n\n      if (nextActiveElement) {\n        nextActiveElement.focus({\n          preventScroll: true\n        });\n        Tab.getOrCreateInstance(nextActiveElement).show();\n      }\n    }\n\n    _getChildren() {\n      // collection of inner elements\n      return SelectorEngine__default.default.find(SELECTOR_INNER_ELEM, this._parent);\n    }\n\n    _getActiveElem() {\n      return this._getChildren().find(child => this._elemIsActive(child)) || null;\n    }\n\n    _setInitialAttributes(parent, children) {\n      this._setAttributeIfNotExists(parent, 'role', 'tablist');\n\n      for (const child of children) {\n        this._setInitialAttributesOnChild(child);\n      }\n    }\n\n    _setInitialAttributesOnChild(child) {\n      child = this._getInnerElement(child);\n\n      const isActive = this._elemIsActive(child);\n\n      const outerElem = this._getOuterElement(child);\n\n      child.setAttribute('aria-selected', isActive);\n\n      if (outerElem !== child) {\n        this._setAttributeIfNotExists(outerElem, 'role', 'presentation');\n      }\n\n      if (!isActive) {\n        child.setAttribute('tabindex', '-1');\n      }\n\n      this._setAttributeIfNotExists(child, 'role', 'tab'); // set attributes to the related panel too\n\n\n      this._setInitialAttributesOnTargetPanel(child);\n    }\n\n    _setInitialAttributesOnTargetPanel(child) {\n      const target = index.getElementFromSelector(child);\n\n      if (!target) {\n        return;\n      }\n\n      this._setAttributeIfNotExists(target, 'role', 'tabpanel');\n\n      if (child.id) {\n        this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`);\n      }\n    }\n\n    _toggleDropDown(element, open) {\n      const outerElem = this._getOuterElement(element);\n\n      if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n        return;\n      }\n\n      const toggle = (selector, className) => {\n        const element = SelectorEngine__default.default.findOne(selector, outerElem);\n\n        if (element) {\n          element.classList.toggle(className, open);\n        }\n      };\n\n      toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);\n      toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW);\n      outerElem.setAttribute('aria-expanded', open);\n    }\n\n    _setAttributeIfNotExists(element, attribute, value) {\n      if (!element.hasAttribute(attribute)) {\n        element.setAttribute(attribute, value);\n      }\n    }\n\n    _elemIsActive(elem) {\n      return elem.classList.contains(CLASS_NAME_ACTIVE);\n    } // Try to get the inner element (usually the .nav-link)\n\n\n    _getInnerElement(elem) {\n      return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine__default.default.findOne(SELECTOR_INNER_ELEM, elem);\n    } // Try to get the outer element (usually the .nav-item)\n\n\n    _getOuterElement(elem) {\n      return elem.closest(SELECTOR_OUTER) || elem;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tab.getOrCreateInstance(this);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault();\n    }\n\n    if (index.isDisabled(this)) {\n      return;\n    }\n\n    Tab.getOrCreateInstance(this).show();\n  });\n  /**\n   * Initialize on focus\n   */\n\n  EventHandler__default.default.on(window, EVENT_LOAD_DATA_API, () => {\n    for (const element of SelectorEngine__default.default.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n      Tab.getOrCreateInstance(element);\n    }\n  });\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Tab);\n\n  return Tab;\n\n}));\n//# sourceMappingURL=tab.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/toast.js",
    "content": "/*!\n  * Bootstrap toast.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./base-component'), require('./util/component-functions')) :\n  typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './base-component', './util/component-functions'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Toast = factory(global.Index, global.EventHandler, global.BaseComponent, global.ComponentFunctions));\n})(this, (function (index, EventHandler, BaseComponent, componentFunctions) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): toast.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'toast';\n  const DATA_KEY = 'bs.toast';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;\n  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;\n  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\n  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;\n  const EVENT_HIDE = `hide${EVENT_KEY}`;\n  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;\n  const EVENT_SHOW = `show${EVENT_KEY}`;\n  const EVENT_SHOWN = `shown${EVENT_KEY}`;\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_HIDE = 'hide'; // @deprecated - kept here only for backwards compatibility\n\n  const CLASS_NAME_SHOW = 'show';\n  const CLASS_NAME_SHOWING = 'showing';\n  const DefaultType = {\n    animation: 'boolean',\n    autohide: 'boolean',\n    delay: 'number'\n  };\n  const Default = {\n    animation: true,\n    autohide: true,\n    delay: 5000\n  };\n  /**\n   * Class definition\n   */\n\n  class Toast extends BaseComponent__default.default {\n    constructor(element, config) {\n      super(element, config);\n      this._timeout = null;\n      this._hasMouseInteraction = false;\n      this._hasKeyboardInteraction = false;\n\n      this._setListeners();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    show() {\n      const showEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW);\n\n      if (showEvent.defaultPrevented) {\n        return;\n      }\n\n      this._clearTimeout();\n\n      if (this._config.animation) {\n        this._element.classList.add(CLASS_NAME_FADE);\n      }\n\n      const complete = () => {\n        this._element.classList.remove(CLASS_NAME_SHOWING);\n\n        EventHandler__default.default.trigger(this._element, EVENT_SHOWN);\n\n        this._maybeScheduleHide();\n      };\n\n      this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated\n\n\n      index.reflow(this._element);\n\n      this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING);\n\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n\n    hide() {\n      if (!this.isShown()) {\n        return;\n      }\n\n      const hideEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE);\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const complete = () => {\n        this._element.classList.add(CLASS_NAME_HIDE); // @deprecated\n\n\n        this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW);\n\n        EventHandler__default.default.trigger(this._element, EVENT_HIDDEN);\n      };\n\n      this._element.classList.add(CLASS_NAME_SHOWING);\n\n      this._queueCallback(complete, this._element, this._config.animation);\n    }\n\n    dispose() {\n      this._clearTimeout();\n\n      if (this.isShown()) {\n        this._element.classList.remove(CLASS_NAME_SHOW);\n      }\n\n      super.dispose();\n    }\n\n    isShown() {\n      return this._element.classList.contains(CLASS_NAME_SHOW);\n    } // Private\n\n\n    _maybeScheduleHide() {\n      if (!this._config.autohide) {\n        return;\n      }\n\n      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n        return;\n      }\n\n      this._timeout = setTimeout(() => {\n        this.hide();\n      }, this._config.delay);\n    }\n\n    _onInteraction(event, isInteracting) {\n      switch (event.type) {\n        case 'mouseover':\n        case 'mouseout':\n          {\n            this._hasMouseInteraction = isInteracting;\n            break;\n          }\n\n        case 'focusin':\n        case 'focusout':\n          {\n            this._hasKeyboardInteraction = isInteracting;\n            break;\n          }\n      }\n\n      if (isInteracting) {\n        this._clearTimeout();\n\n        return;\n      }\n\n      const nextElement = event.relatedTarget;\n\n      if (this._element === nextElement || this._element.contains(nextElement)) {\n        return;\n      }\n\n      this._maybeScheduleHide();\n    }\n\n    _setListeners() {\n      EventHandler__default.default.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));\n      EventHandler__default.default.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));\n      EventHandler__default.default.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));\n      EventHandler__default.default.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));\n    }\n\n    _clearTimeout() {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Toast.getOrCreateInstance(this, config);\n\n        if (typeof config === 'string') {\n          if (typeof data[config] === 'undefined') {\n            throw new TypeError(`No method named \"${config}\"`);\n          }\n\n          data[config](this);\n        }\n      });\n    }\n\n  }\n  /**\n   * Data API implementation\n   */\n\n\n  componentFunctions.enableDismissTrigger(Toast);\n  /**\n   * jQuery\n   */\n\n  index.defineJQueryPlugin(Toast);\n\n  return Toast;\n\n}));\n//# sourceMappingURL=toast.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/tooltip.js",
    "content": "/*!\n  * Bootstrap tooltip.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./util/index'), require('./util/sanitizer'), require('./dom/event-handler'), require('./dom/manipulator'), require('./base-component'), require('./util/template-factory')) :\n  typeof define === 'function' && define.amd ? define(['@popperjs/core', './util/index', './util/sanitizer', './dom/event-handler', './dom/manipulator', './base-component', './util/template-factory'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global[\"@popperjs/core\"], global.Index, global.Sanitizer, global.EventHandler, global.Manipulator, global.BaseComponent, global.TemplateFactory));\n})(this, (function (Popper, index, sanitizer, EventHandler, Manipulator, BaseComponent, TemplateFactory) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  function _interopNamespace(e) {\n    if (e && e.__esModule) return e;\n    const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });\n    if (e) {\n      for (const k in e) {\n        if (k !== 'default') {\n          const d = Object.getOwnPropertyDescriptor(e, k);\n          Object.defineProperty(n, k, d.get ? d : {\n            enumerable: true,\n            get: () => e[k]\n          });\n        }\n      }\n    }\n    n.default = e;\n    return Object.freeze(n);\n  }\n\n  const Popper__namespace = /*#__PURE__*/_interopNamespace(Popper);\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);\n  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);\n  const TemplateFactory__default = /*#__PURE__*/_interopDefaultLegacy(TemplateFactory);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): tooltip.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'tooltip';\n  const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_MODAL = 'modal';\n  const CLASS_NAME_SHOW = 'show';\n  const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\n  const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\n  const EVENT_MODAL_HIDE = 'hide.bs.modal';\n  const TRIGGER_HOVER = 'hover';\n  const TRIGGER_FOCUS = 'focus';\n  const TRIGGER_CLICK = 'click';\n  const TRIGGER_MANUAL = 'manual';\n  const EVENT_HIDE = 'hide';\n  const EVENT_HIDDEN = 'hidden';\n  const EVENT_SHOW = 'show';\n  const EVENT_SHOWN = 'shown';\n  const EVENT_INSERTED = 'inserted';\n  const EVENT_CLICK = 'click';\n  const EVENT_FOCUSIN = 'focusin';\n  const EVENT_FOCUSOUT = 'focusout';\n  const EVENT_MOUSEENTER = 'mouseenter';\n  const EVENT_MOUSELEAVE = 'mouseleave';\n  const AttachmentMap = {\n    AUTO: 'auto',\n    TOP: 'top',\n    RIGHT: index.isRTL() ? 'left' : 'right',\n    BOTTOM: 'bottom',\n    LEFT: index.isRTL() ? 'right' : 'left'\n  };\n  const Default = {\n    allowList: sanitizer.DefaultAllowlist,\n    animation: true,\n    boundary: 'clippingParents',\n    container: false,\n    customClass: '',\n    delay: 0,\n    fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n    html: false,\n    offset: [0, 0],\n    placement: 'top',\n    popperConfig: null,\n    sanitize: true,\n    sanitizeFn: null,\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\">' + '<div class=\"tooltip-arrow\"></div>' + '<div class=\"tooltip-inner\"></div>' + '</div>',\n    title: '',\n    trigger: 'hover focus'\n  };\n  const DefaultType = {\n    allowList: 'object',\n    animation: 'boolean',\n    boundary: '(string|element)',\n    container: '(string|element|boolean)',\n    customClass: '(string|function)',\n    delay: '(number|object)',\n    fallbackPlacements: 'array',\n    html: 'boolean',\n    offset: '(array|string|function)',\n    placement: '(string|function)',\n    popperConfig: '(null|object|function)',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    selector: '(string|boolean)',\n    template: 'string',\n    title: '(string|element|function)',\n    trigger: 'string'\n  };\n  /**\n   * Class definition\n   */\n\n  class Tooltip extends BaseComponent__default.default {\n    constructor(element, config) {\n      if (typeof Popper__namespace === 'undefined') {\n        throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n      }\n\n      super(element, config); // Private\n\n      this._isEnabled = true;\n      this._timeout = 0;\n      this._isHovered = null;\n      this._activeTrigger = {};\n      this._popper = null;\n      this._templateFactory = null;\n      this._newContent = null; // Protected\n\n      this.tip = null;\n\n      this._setListeners();\n\n      if (!this._config.selector) {\n        this._fixTitle();\n      }\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    enable() {\n      this._isEnabled = true;\n    }\n\n    disable() {\n      this._isEnabled = false;\n    }\n\n    toggleEnabled() {\n      this._isEnabled = !this._isEnabled;\n    }\n\n    toggle() {\n      if (!this._isEnabled) {\n        return;\n      }\n\n      this._activeTrigger.click = !this._activeTrigger.click;\n\n      if (this._isShown()) {\n        this._leave();\n\n        return;\n      }\n\n      this._enter();\n    }\n\n    dispose() {\n      clearTimeout(this._timeout);\n      EventHandler__default.default.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n\n      if (this._element.getAttribute('data-bs-original-title')) {\n        this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n      }\n\n      this._disposePopper();\n\n      super.dispose();\n    }\n\n    show() {\n      if (this._element.style.display === 'none') {\n        throw new Error('Please use show on visible elements');\n      }\n\n      if (!(this._isWithContent() && this._isEnabled)) {\n        return;\n      }\n\n      const showEvent = EventHandler__default.default.trigger(this._element, this.constructor.eventName(EVENT_SHOW));\n      const shadowRoot = index.findShadowRoot(this._element);\n\n      const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n\n      if (showEvent.defaultPrevented || !isInTheDom) {\n        return;\n      } // todo v6 remove this OR make it optional\n\n\n      this._disposePopper();\n\n      const tip = this._getTipElement();\n\n      this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n\n      const {\n        container\n      } = this._config;\n\n      if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n        container.append(tip);\n        EventHandler__default.default.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n      }\n\n      this._popper = this._createPopper(tip);\n      tip.classList.add(CLASS_NAME_SHOW); // If this is a touch-enabled device we add extra\n      // empty mouseover listeners to the body's immediate children;\n      // only needed because of broken event delegation on iOS\n      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler__default.default.on(element, 'mouseover', index.noop);\n        }\n      }\n\n      const complete = () => {\n        EventHandler__default.default.trigger(this._element, this.constructor.eventName(EVENT_SHOWN));\n\n        if (this._isHovered === false) {\n          this._leave();\n        }\n\n        this._isHovered = false;\n      };\n\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n\n    hide() {\n      if (!this._isShown()) {\n        return;\n      }\n\n      const hideEvent = EventHandler__default.default.trigger(this._element, this.constructor.eventName(EVENT_HIDE));\n\n      if (hideEvent.defaultPrevented) {\n        return;\n      }\n\n      const tip = this._getTipElement();\n\n      tip.classList.remove(CLASS_NAME_SHOW); // If this is a touch-enabled device we remove the extra\n      // empty mouseover listeners we added for iOS support\n\n      if ('ontouchstart' in document.documentElement) {\n        for (const element of [].concat(...document.body.children)) {\n          EventHandler__default.default.off(element, 'mouseover', index.noop);\n        }\n      }\n\n      this._activeTrigger[TRIGGER_CLICK] = false;\n      this._activeTrigger[TRIGGER_FOCUS] = false;\n      this._activeTrigger[TRIGGER_HOVER] = false;\n      this._isHovered = null; // it is a trick to support manual triggering\n\n      const complete = () => {\n        if (this._isWithActiveTrigger()) {\n          return;\n        }\n\n        if (!this._isHovered) {\n          this._disposePopper();\n        }\n\n        this._element.removeAttribute('aria-describedby');\n\n        EventHandler__default.default.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN));\n      };\n\n      this._queueCallback(complete, this.tip, this._isAnimated());\n    }\n\n    update() {\n      if (this._popper) {\n        this._popper.update();\n      }\n    } // Protected\n\n\n    _isWithContent() {\n      return Boolean(this._getTitle());\n    }\n\n    _getTipElement() {\n      if (!this.tip) {\n        this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n      }\n\n      return this.tip;\n    }\n\n    _createTipElement(content) {\n      const tip = this._getTemplateFactory(content).toHtml(); // todo: remove this check on v6\n\n\n      if (!tip) {\n        return null;\n      }\n\n      tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW); // todo: on v6 the following can be achieved with CSS only\n\n      tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n      const tipId = index.getUID(this.constructor.NAME).toString();\n      tip.setAttribute('id', tipId);\n\n      if (this._isAnimated()) {\n        tip.classList.add(CLASS_NAME_FADE);\n      }\n\n      return tip;\n    }\n\n    setContent(content) {\n      this._newContent = content;\n\n      if (this._isShown()) {\n        this._disposePopper();\n\n        this.show();\n      }\n    }\n\n    _getTemplateFactory(content) {\n      if (this._templateFactory) {\n        this._templateFactory.changeContent(content);\n      } else {\n        this._templateFactory = new TemplateFactory__default.default({ ...this._config,\n          // the `content` var has to be after `this._config`\n          // to override config.content in case of popover\n          content,\n          extraClass: this._resolvePossibleFunction(this._config.customClass)\n        });\n      }\n\n      return this._templateFactory;\n    }\n\n    _getContentForTemplate() {\n      return {\n        [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n      };\n    }\n\n    _getTitle() {\n      return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n    } // Private\n\n\n    _initializeOnDelegatedTarget(event) {\n      return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n    }\n\n    _isAnimated() {\n      return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE);\n    }\n\n    _isShown() {\n      return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW);\n    }\n\n    _createPopper(tip) {\n      const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement;\n      const attachment = AttachmentMap[placement.toUpperCase()];\n      return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment));\n    }\n\n    _getOffset() {\n      const {\n        offset\n      } = this._config;\n\n      if (typeof offset === 'string') {\n        return offset.split(',').map(value => Number.parseInt(value, 10));\n      }\n\n      if (typeof offset === 'function') {\n        return popperData => offset(popperData, this._element);\n      }\n\n      return offset;\n    }\n\n    _resolvePossibleFunction(arg) {\n      return typeof arg === 'function' ? arg.call(this._element) : arg;\n    }\n\n    _getPopperConfig(attachment) {\n      const defaultBsPopperConfig = {\n        placement: attachment,\n        modifiers: [{\n          name: 'flip',\n          options: {\n            fallbackPlacements: this._config.fallbackPlacements\n          }\n        }, {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        }, {\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        }, {\n          name: 'arrow',\n          options: {\n            element: `.${this.constructor.NAME}-arrow`\n          }\n        }, {\n          name: 'preSetPlacement',\n          enabled: true,\n          phase: 'beforeMain',\n          fn: data => {\n            // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n            // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n            this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n          }\n        }]\n      };\n      return { ...defaultBsPopperConfig,\n        ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n      };\n    }\n\n    _setListeners() {\n      const triggers = this._config.trigger.split(' ');\n\n      for (const trigger of triggers) {\n        if (trigger === 'click') {\n          EventHandler__default.default.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context.toggle();\n          });\n        } else if (trigger !== TRIGGER_MANUAL) {\n          const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN);\n          const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT);\n          EventHandler__default.default.on(this._element, eventIn, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n\n            context._enter();\n          });\n          EventHandler__default.default.on(this._element, eventOut, this._config.selector, event => {\n            const context = this._initializeOnDelegatedTarget(event);\n\n            context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n\n            context._leave();\n          });\n        }\n      }\n\n      this._hideModalHandler = () => {\n        if (this._element) {\n          this.hide();\n        }\n      };\n\n      EventHandler__default.default.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n    }\n\n    _fixTitle() {\n      const title = this._element.getAttribute('title');\n\n      if (!title) {\n        return;\n      }\n\n      if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n        this._element.setAttribute('aria-label', title);\n      }\n\n      this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n\n\n      this._element.removeAttribute('title');\n    }\n\n    _enter() {\n      if (this._isShown() || this._isHovered) {\n        this._isHovered = true;\n        return;\n      }\n\n      this._isHovered = true;\n\n      this._setTimeout(() => {\n        if (this._isHovered) {\n          this.show();\n        }\n      }, this._config.delay.show);\n    }\n\n    _leave() {\n      if (this._isWithActiveTrigger()) {\n        return;\n      }\n\n      this._isHovered = false;\n\n      this._setTimeout(() => {\n        if (!this._isHovered) {\n          this.hide();\n        }\n      }, this._config.delay.hide);\n    }\n\n    _setTimeout(handler, timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = setTimeout(handler, timeout);\n    }\n\n    _isWithActiveTrigger() {\n      return Object.values(this._activeTrigger).includes(true);\n    }\n\n    _getConfig(config) {\n      const dataAttributes = Manipulator__default.default.getDataAttributes(this._element);\n\n      for (const dataAttribute of Object.keys(dataAttributes)) {\n        if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n          delete dataAttributes[dataAttribute];\n        }\n      }\n\n      config = { ...dataAttributes,\n        ...(typeof config === 'object' && config ? config : {})\n      };\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    }\n\n    _configAfterMerge(config) {\n      config.container = config.container === false ? document.body : index.getElement(config.container);\n\n      if (typeof config.delay === 'number') {\n        config.delay = {\n          show: config.delay,\n          hide: config.delay\n        };\n      }\n\n      if (typeof config.title === 'number') {\n        config.title = config.title.toString();\n      }\n\n      if (typeof config.content === 'number') {\n        config.content = config.content.toString();\n      }\n\n      return config;\n    }\n\n    _getDelegateConfig() {\n      const config = {};\n\n      for (const key in this._config) {\n        if (this.constructor.Default[key] !== this._config[key]) {\n          config[key] = this._config[key];\n        }\n      }\n\n      config.selector = false;\n      config.trigger = 'manual'; // In the future can be replaced with:\n      // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n      // `Object.fromEntries(keysWithDifferentValues)`\n\n      return config;\n    }\n\n    _disposePopper() {\n      if (this._popper) {\n        this._popper.destroy();\n\n        this._popper = null;\n      }\n\n      if (this.tip) {\n        this.tip.remove();\n        this.tip = null;\n      }\n    } // Static\n\n\n    static jQueryInterface(config) {\n      return this.each(function () {\n        const data = Tooltip.getOrCreateInstance(this, config);\n\n        if (typeof config !== 'string') {\n          return;\n        }\n\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`);\n        }\n\n        data[config]();\n      });\n    }\n\n  }\n  /**\n   * jQuery\n   */\n\n\n  index.defineJQueryPlugin(Tooltip);\n\n  return Tooltip;\n\n}));\n//# sourceMappingURL=tooltip.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/backdrop.js",
    "content": "/*!\n  * Bootstrap backdrop.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler'), require('./index'), require('./config')) :\n  typeof define === 'function' && define.amd ? define(['../dom/event-handler', './index', './config'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Backdrop = factory(global.EventHandler, global.Index, global.Config));\n})(this, (function (EventHandler, index, Config) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const Config__default = /*#__PURE__*/_interopDefaultLegacy(Config);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/backdrop.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'backdrop';\n  const CLASS_NAME_FADE = 'fade';\n  const CLASS_NAME_SHOW = 'show';\n  const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`;\n  const Default = {\n    className: 'modal-backdrop',\n    clickCallback: null,\n    isAnimated: false,\n    isVisible: true,\n    // if false, we use the backdrop helper without adding any element to the dom\n    rootElement: 'body' // give the choice to place backdrop under different elements\n\n  };\n  const DefaultType = {\n    className: 'string',\n    clickCallback: '(function|null)',\n    isAnimated: 'boolean',\n    isVisible: 'boolean',\n    rootElement: '(element|string)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Backdrop extends Config__default.default {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isAppended = false;\n      this._element = null;\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    show(callback) {\n      if (!this._config.isVisible) {\n        index.execute(callback);\n        return;\n      }\n\n      this._append();\n\n      const element = this._getElement();\n\n      if (this._config.isAnimated) {\n        index.reflow(element);\n      }\n\n      element.classList.add(CLASS_NAME_SHOW);\n\n      this._emulateAnimation(() => {\n        index.execute(callback);\n      });\n    }\n\n    hide(callback) {\n      if (!this._config.isVisible) {\n        index.execute(callback);\n        return;\n      }\n\n      this._getElement().classList.remove(CLASS_NAME_SHOW);\n\n      this._emulateAnimation(() => {\n        this.dispose();\n        index.execute(callback);\n      });\n    }\n\n    dispose() {\n      if (!this._isAppended) {\n        return;\n      }\n\n      EventHandler__default.default.off(this._element, EVENT_MOUSEDOWN);\n\n      this._element.remove();\n\n      this._isAppended = false;\n    } // Private\n\n\n    _getElement() {\n      if (!this._element) {\n        const backdrop = document.createElement('div');\n        backdrop.className = this._config.className;\n\n        if (this._config.isAnimated) {\n          backdrop.classList.add(CLASS_NAME_FADE);\n        }\n\n        this._element = backdrop;\n      }\n\n      return this._element;\n    }\n\n    _configAfterMerge(config) {\n      // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n      config.rootElement = index.getElement(config.rootElement);\n      return config;\n    }\n\n    _append() {\n      if (this._isAppended) {\n        return;\n      }\n\n      const element = this._getElement();\n\n      this._config.rootElement.append(element);\n\n      EventHandler__default.default.on(element, EVENT_MOUSEDOWN, () => {\n        index.execute(this._config.clickCallback);\n      });\n      this._isAppended = true;\n    }\n\n    _emulateAnimation(callback) {\n      index.executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n    }\n\n  }\n\n  return Backdrop;\n\n}));\n//# sourceMappingURL=backdrop.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/component-functions.js",
    "content": "/*!\n  * Bootstrap component-functions.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('../dom/event-handler'), require('./index')) :\n  typeof define === 'function' && define.amd ? define(['exports', '../dom/event-handler', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ComponentFunctions = {}, global.EventHandler, global.Index));\n})(this, (function (exports, EventHandler, index) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/component-functions.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n\n  const enableDismissTrigger = (component, method = 'hide') => {\n    const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n    const name = component.NAME;\n    EventHandler__default.default.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n      if (['A', 'AREA'].includes(this.tagName)) {\n        event.preventDefault();\n      }\n\n      if (index.isDisabled(this)) {\n        return;\n      }\n\n      const target = index.getElementFromSelector(this) || this.closest(`.${name}`);\n      const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n\n      instance[method]();\n    });\n  };\n\n  exports.enableDismissTrigger = enableDismissTrigger;\n\n  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });\n\n}));\n//# sourceMappingURL=component-functions.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/config.js",
    "content": "/*!\n  * Bootstrap config.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./index'), require('../dom/manipulator')) :\n  typeof define === 'function' && define.amd ? define(['./index', '../dom/manipulator'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Config = factory(global.Index, global.Manipulator));\n})(this, (function (index, Manipulator) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/config.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Class definition\n   */\n\n  class Config {\n    // Getters\n    static get Default() {\n      return {};\n    }\n\n    static get DefaultType() {\n      return {};\n    }\n\n    static get NAME() {\n      throw new Error('You have to implement the static method \"NAME\", for each component!');\n    }\n\n    _getConfig(config) {\n      config = this._mergeConfigObj(config);\n      config = this._configAfterMerge(config);\n\n      this._typeCheckConfig(config);\n\n      return config;\n    }\n\n    _configAfterMerge(config) {\n      return config;\n    }\n\n    _mergeConfigObj(config, element) {\n      const jsonConfig = index.isElement(element) ? Manipulator__default.default.getDataAttribute(element, 'config') : {}; // try to parse\n\n      return { ...this.constructor.Default,\n        ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n        ...(index.isElement(element) ? Manipulator__default.default.getDataAttributes(element) : {}),\n        ...(typeof config === 'object' ? config : {})\n      };\n    }\n\n    _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n      for (const property of Object.keys(configTypes)) {\n        const expectedTypes = configTypes[property];\n        const value = config[property];\n        const valueType = index.isElement(value) ? 'element' : index.toType(value);\n\n        if (!new RegExp(expectedTypes).test(valueType)) {\n          throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n        }\n      }\n    }\n\n  }\n\n  return Config;\n\n}));\n//# sourceMappingURL=config.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/focustrap.js",
    "content": "/*!\n  * Bootstrap focustrap.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler'), require('../dom/selector-engine'), require('./config')) :\n  typeof define === 'function' && define.amd ? define(['../dom/event-handler', '../dom/selector-engine', './config'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Focustrap = factory(global.EventHandler, global.SelectorEngine, global.Config));\n})(this, (function (EventHandler, SelectorEngine, Config) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const Config__default = /*#__PURE__*/_interopDefaultLegacy(Config);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/focustrap.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'focustrap';\n  const DATA_KEY = 'bs.focustrap';\n  const EVENT_KEY = `.${DATA_KEY}`;\n  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;\n  const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`;\n  const TAB_KEY = 'Tab';\n  const TAB_NAV_FORWARD = 'forward';\n  const TAB_NAV_BACKWARD = 'backward';\n  const Default = {\n    autofocus: true,\n    trapElement: null // The element to trap focus inside of\n\n  };\n  const DefaultType = {\n    autofocus: 'boolean',\n    trapElement: 'element'\n  };\n  /**\n   * Class definition\n   */\n\n  class FocusTrap extends Config__default.default {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n      this._isActive = false;\n      this._lastTabNavDirection = null;\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    activate() {\n      if (this._isActive) {\n        return;\n      }\n\n      if (this._config.autofocus) {\n        this._config.trapElement.focus();\n      }\n\n      EventHandler__default.default.off(document, EVENT_KEY); // guard against infinite focus loop\n\n      EventHandler__default.default.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event));\n      EventHandler__default.default.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n      this._isActive = true;\n    }\n\n    deactivate() {\n      if (!this._isActive) {\n        return;\n      }\n\n      this._isActive = false;\n      EventHandler__default.default.off(document, EVENT_KEY);\n    } // Private\n\n\n    _handleFocusin(event) {\n      const {\n        trapElement\n      } = this._config;\n\n      if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n        return;\n      }\n\n      const elements = SelectorEngine__default.default.focusableChildren(trapElement);\n\n      if (elements.length === 0) {\n        trapElement.focus();\n      } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n        elements[elements.length - 1].focus();\n      } else {\n        elements[0].focus();\n      }\n    }\n\n    _handleKeydown(event) {\n      if (event.key !== TAB_KEY) {\n        return;\n      }\n\n      this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n    }\n\n  }\n\n  return FocusTrap;\n\n}));\n//# sourceMappingURL=focustrap.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/index.js",
    "content": "/*!\n  * Bootstrap index.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Index = {}));\n})(this, (function (exports) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/index.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const MAX_UID = 1000000;\n  const MILLISECONDS_MULTIPLIER = 1000;\n  const TRANSITION_END = 'transitionend'; // Shout-out Angus Croll (https://goo.gl/pxwQGp)\n\n  const toType = object => {\n    if (object === null || object === undefined) {\n      return `${object}`;\n    }\n\n    return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n  };\n  /**\n   * Public Util API\n   */\n\n\n  const getUID = prefix => {\n    do {\n      prefix += Math.floor(Math.random() * MAX_UID);\n    } while (document.getElementById(prefix));\n\n    return prefix;\n  };\n\n  const getSelector = element => {\n    let selector = element.getAttribute('data-bs-target');\n\n    if (!selector || selector === '#') {\n      let hrefAttribute = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,\n      // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n      // `document.querySelector` will rightfully complain it is invalid.\n      // See https://github.com/twbs/bootstrap/issues/32273\n\n      if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n        return null;\n      } // Just in case some CMS puts out a full URL with the anchor appended\n\n\n      if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n        hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n      }\n\n      selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n    }\n\n    return selector;\n  };\n\n  const getSelectorFromElement = element => {\n    const selector = getSelector(element);\n\n    if (selector) {\n      return document.querySelector(selector) ? selector : null;\n    }\n\n    return null;\n  };\n\n  const getElementFromSelector = element => {\n    const selector = getSelector(element);\n    return selector ? document.querySelector(selector) : null;\n  };\n\n  const getTransitionDurationFromElement = element => {\n    if (!element) {\n      return 0;\n    } // Get transition-duration of the element\n\n\n    let {\n      transitionDuration,\n      transitionDelay\n    } = window.getComputedStyle(element);\n    const floatTransitionDuration = Number.parseFloat(transitionDuration);\n    const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found\n\n    if (!floatTransitionDuration && !floatTransitionDelay) {\n      return 0;\n    } // If multiple durations are defined, take the first\n\n\n    transitionDuration = transitionDuration.split(',')[0];\n    transitionDelay = transitionDelay.split(',')[0];\n    return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n  };\n\n  const triggerTransitionEnd = element => {\n    element.dispatchEvent(new Event(TRANSITION_END));\n  };\n\n  const isElement = object => {\n    if (!object || typeof object !== 'object') {\n      return false;\n    }\n\n    if (typeof object.jquery !== 'undefined') {\n      object = object[0];\n    }\n\n    return typeof object.nodeType !== 'undefined';\n  };\n\n  const getElement = object => {\n    // it's a jQuery object or a node element\n    if (isElement(object)) {\n      return object.jquery ? object[0] : object;\n    }\n\n    if (typeof object === 'string' && object.length > 0) {\n      return document.querySelector(object);\n    }\n\n    return null;\n  };\n\n  const isVisible = element => {\n    if (!isElement(element) || element.getClientRects().length === 0) {\n      return false;\n    }\n\n    const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; // Handle `details` element as its content may falsie appear visible when it is closed\n\n    const closedDetails = element.closest('details:not([open])');\n\n    if (!closedDetails) {\n      return elementIsVisible;\n    }\n\n    if (closedDetails !== element) {\n      const summary = element.closest('summary');\n\n      if (summary && summary.parentNode !== closedDetails) {\n        return false;\n      }\n\n      if (summary === null) {\n        return false;\n      }\n    }\n\n    return elementIsVisible;\n  };\n\n  const isDisabled = element => {\n    if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n      return true;\n    }\n\n    if (element.classList.contains('disabled')) {\n      return true;\n    }\n\n    if (typeof element.disabled !== 'undefined') {\n      return element.disabled;\n    }\n\n    return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n  };\n\n  const findShadowRoot = element => {\n    if (!document.documentElement.attachShadow) {\n      return null;\n    } // Can find the shadow root otherwise it'll return the document\n\n\n    if (typeof element.getRootNode === 'function') {\n      const root = element.getRootNode();\n      return root instanceof ShadowRoot ? root : null;\n    }\n\n    if (element instanceof ShadowRoot) {\n      return element;\n    } // when we don't find a shadow root\n\n\n    if (!element.parentNode) {\n      return null;\n    }\n\n    return findShadowRoot(element.parentNode);\n  };\n\n  const noop = () => {};\n  /**\n   * Trick to restart an element's animation\n   *\n   * @param {HTMLElement} element\n   * @return void\n   *\n   * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n   */\n\n\n  const reflow = element => {\n    element.offsetHeight; // eslint-disable-line no-unused-expressions\n  };\n\n  const getjQuery = () => {\n    if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n      return window.jQuery;\n    }\n\n    return null;\n  };\n\n  const DOMContentLoadedCallbacks = [];\n\n  const onDOMContentLoaded = callback => {\n    if (document.readyState === 'loading') {\n      // add listener on the first call when the document is in loading state\n      if (!DOMContentLoadedCallbacks.length) {\n        document.addEventListener('DOMContentLoaded', () => {\n          for (const callback of DOMContentLoadedCallbacks) {\n            callback();\n          }\n        });\n      }\n\n      DOMContentLoadedCallbacks.push(callback);\n    } else {\n      callback();\n    }\n  };\n\n  const isRTL = () => document.documentElement.dir === 'rtl';\n\n  const defineJQueryPlugin = plugin => {\n    onDOMContentLoaded(() => {\n      const $ = getjQuery();\n      /* istanbul ignore if */\n\n      if ($) {\n        const name = plugin.NAME;\n        const JQUERY_NO_CONFLICT = $.fn[name];\n        $.fn[name] = plugin.jQueryInterface;\n        $.fn[name].Constructor = plugin;\n\n        $.fn[name].noConflict = () => {\n          $.fn[name] = JQUERY_NO_CONFLICT;\n          return plugin.jQueryInterface;\n        };\n      }\n    });\n  };\n\n  const execute = callback => {\n    if (typeof callback === 'function') {\n      callback();\n    }\n  };\n\n  const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n    if (!waitForTransition) {\n      execute(callback);\n      return;\n    }\n\n    const durationPadding = 5;\n    const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n    let called = false;\n\n    const handler = ({\n      target\n    }) => {\n      if (target !== transitionElement) {\n        return;\n      }\n\n      called = true;\n      transitionElement.removeEventListener(TRANSITION_END, handler);\n      execute(callback);\n    };\n\n    transitionElement.addEventListener(TRANSITION_END, handler);\n    setTimeout(() => {\n      if (!called) {\n        triggerTransitionEnd(transitionElement);\n      }\n    }, emulatedDuration);\n  };\n  /**\n   * Return the previous/next element of a list.\n   *\n   * @param {array} list    The list of elements\n   * @param activeElement   The active element\n   * @param shouldGetNext   Choose to get next or previous element\n   * @param isCycleAllowed\n   * @return {Element|elem} The proper element\n   */\n\n\n  const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n    const listLength = list.length;\n    let index = list.indexOf(activeElement); // if the element does not exist in the list return an element\n    // depending on the direction and if cycle is allowed\n\n    if (index === -1) {\n      return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n    }\n\n    index += shouldGetNext ? 1 : -1;\n\n    if (isCycleAllowed) {\n      index = (index + listLength) % listLength;\n    }\n\n    return list[Math.max(0, Math.min(index, listLength - 1))];\n  };\n\n  exports.defineJQueryPlugin = defineJQueryPlugin;\n  exports.execute = execute;\n  exports.executeAfterTransition = executeAfterTransition;\n  exports.findShadowRoot = findShadowRoot;\n  exports.getElement = getElement;\n  exports.getElementFromSelector = getElementFromSelector;\n  exports.getNextActiveElement = getNextActiveElement;\n  exports.getSelectorFromElement = getSelectorFromElement;\n  exports.getTransitionDurationFromElement = getTransitionDurationFromElement;\n  exports.getUID = getUID;\n  exports.getjQuery = getjQuery;\n  exports.isDisabled = isDisabled;\n  exports.isElement = isElement;\n  exports.isRTL = isRTL;\n  exports.isVisible = isVisible;\n  exports.noop = noop;\n  exports.onDOMContentLoaded = onDOMContentLoaded;\n  exports.reflow = reflow;\n  exports.toType = toType;\n  exports.triggerTransitionEnd = triggerTransitionEnd;\n\n  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });\n\n}));\n//# sourceMappingURL=index.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/sanitizer.js",
    "content": "/*!\n  * Bootstrap sanitizer.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Sanitizer = {}));\n})(this, (function (exports) { 'use strict';\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/sanitizer.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n  const ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\n  /**\n   * A pattern that recognizes a commonly useful subset of URLs that are safe.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n   */\n\n  const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i;\n  /**\n   * A pattern that matches safe data URLs. Only matches image, video and audio types.\n   *\n   * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n   */\n\n  const DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i;\n\n  const allowedAttribute = (attribute, allowedAttributeList) => {\n    const attributeName = attribute.nodeName.toLowerCase();\n\n    if (allowedAttributeList.includes(attributeName)) {\n      if (uriAttributes.has(attributeName)) {\n        return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue));\n      }\n\n      return true;\n    } // Check if a regular expression validates the attribute.\n\n\n    return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n  };\n\n  const DefaultAllowlist = {\n    // Global attributes allowed on any supplied element below.\n    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n    a: ['target', 'href', 'title', 'rel'],\n    area: [],\n    b: [],\n    br: [],\n    col: [],\n    code: [],\n    div: [],\n    em: [],\n    hr: [],\n    h1: [],\n    h2: [],\n    h3: [],\n    h4: [],\n    h5: [],\n    h6: [],\n    i: [],\n    img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n    li: [],\n    ol: [],\n    p: [],\n    pre: [],\n    s: [],\n    small: [],\n    span: [],\n    sub: [],\n    sup: [],\n    strong: [],\n    u: [],\n    ul: []\n  };\n  function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n    if (!unsafeHtml.length) {\n      return unsafeHtml;\n    }\n\n    if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n      return sanitizeFunction(unsafeHtml);\n    }\n\n    const domParser = new window.DOMParser();\n    const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n    const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n\n    for (const element of elements) {\n      const elementName = element.nodeName.toLowerCase();\n\n      if (!Object.keys(allowList).includes(elementName)) {\n        element.remove();\n        continue;\n      }\n\n      const attributeList = [].concat(...element.attributes);\n      const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n\n      for (const attribute of attributeList) {\n        if (!allowedAttribute(attribute, allowedAttributes)) {\n          element.removeAttribute(attribute.nodeName);\n        }\n      }\n    }\n\n    return createdDocument.body.innerHTML;\n  }\n\n  exports.DefaultAllowlist = DefaultAllowlist;\n  exports.sanitizeHtml = sanitizeHtml;\n\n  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });\n\n}));\n//# sourceMappingURL=sanitizer.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/scrollbar.js",
    "content": "/*!\n  * Bootstrap scrollbar.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/selector-engine'), require('../dom/manipulator'), require('./index')) :\n  typeof define === 'function' && define.amd ? define(['../dom/selector-engine', '../dom/manipulator', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollbar = factory(global.SelectorEngine, global.Manipulator, global.Index));\n})(this, (function (SelectorEngine, Manipulator, index) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/scrollBar.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\n  const SELECTOR_STICKY_CONTENT = '.sticky-top';\n  const PROPERTY_PADDING = 'padding-right';\n  const PROPERTY_MARGIN = 'margin-right';\n  /**\n   * Class definition\n   */\n\n  class ScrollBarHelper {\n    constructor() {\n      this._element = document.body;\n    } // Public\n\n\n    getWidth() {\n      // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n      const documentWidth = document.documentElement.clientWidth;\n      return Math.abs(window.innerWidth - documentWidth);\n    }\n\n    hide() {\n      const width = this.getWidth();\n\n      this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width\n\n\n      this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n\n\n      this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n\n      this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n    }\n\n    reset() {\n      this._resetElementAttributes(this._element, 'overflow');\n\n      this._resetElementAttributes(this._element, PROPERTY_PADDING);\n\n      this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n\n      this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n    }\n\n    isOverflowing() {\n      return this.getWidth() > 0;\n    } // Private\n\n\n    _disableOverFlow() {\n      this._saveInitialAttribute(this._element, 'overflow');\n\n      this._element.style.overflow = 'hidden';\n    }\n\n    _setElementAttributes(selector, styleProperty, callback) {\n      const scrollbarWidth = this.getWidth();\n\n      const manipulationCallBack = element => {\n        if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n          return;\n        }\n\n        this._saveInitialAttribute(element, styleProperty);\n\n        const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n        element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n      };\n\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n\n    _saveInitialAttribute(element, styleProperty) {\n      const actualValue = element.style.getPropertyValue(styleProperty);\n\n      if (actualValue) {\n        Manipulator__default.default.setDataAttribute(element, styleProperty, actualValue);\n      }\n    }\n\n    _resetElementAttributes(selector, styleProperty) {\n      const manipulationCallBack = element => {\n        const value = Manipulator__default.default.getDataAttribute(element, styleProperty); // We only want to remove the property if the value is `null`; the value can also be zero\n\n        if (value === null) {\n          element.style.removeProperty(styleProperty);\n          return;\n        }\n\n        Manipulator__default.default.removeDataAttribute(element, styleProperty);\n        element.style.setProperty(styleProperty, value);\n      };\n\n      this._applyManipulationCallback(selector, manipulationCallBack);\n    }\n\n    _applyManipulationCallback(selector, callBack) {\n      if (index.isElement(selector)) {\n        callBack(selector);\n        return;\n      }\n\n      for (const sel of SelectorEngine__default.default.find(selector, this._element)) {\n        callBack(sel);\n      }\n    }\n\n  }\n\n  return ScrollBarHelper;\n\n}));\n//# sourceMappingURL=scrollbar.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/swipe.js",
    "content": "/*!\n  * Bootstrap swipe.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./config'), require('../dom/event-handler'), require('./index')) :\n  typeof define === 'function' && define.amd ? define(['./config', '../dom/event-handler', './index'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Swipe = factory(global.Config, global.EventHandler, global.Index));\n})(this, (function (Config, EventHandler, index) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const Config__default = /*#__PURE__*/_interopDefaultLegacy(Config);\n  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/swipe.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'swipe';\n  const EVENT_KEY = '.bs.swipe';\n  const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`;\n  const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`;\n  const EVENT_TOUCHEND = `touchend${EVENT_KEY}`;\n  const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`;\n  const EVENT_POINTERUP = `pointerup${EVENT_KEY}`;\n  const POINTER_TYPE_TOUCH = 'touch';\n  const POINTER_TYPE_PEN = 'pen';\n  const CLASS_NAME_POINTER_EVENT = 'pointer-event';\n  const SWIPE_THRESHOLD = 40;\n  const Default = {\n    endCallback: null,\n    leftCallback: null,\n    rightCallback: null\n  };\n  const DefaultType = {\n    endCallback: '(function|null)',\n    leftCallback: '(function|null)',\n    rightCallback: '(function|null)'\n  };\n  /**\n   * Class definition\n   */\n\n  class Swipe extends Config__default.default {\n    constructor(element, config) {\n      super();\n      this._element = element;\n\n      if (!element || !Swipe.isSupported()) {\n        return;\n      }\n\n      this._config = this._getConfig(config);\n      this._deltaX = 0;\n      this._supportPointerEvents = Boolean(window.PointerEvent);\n\n      this._initEvents();\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    dispose() {\n      EventHandler__default.default.off(this._element, EVENT_KEY);\n    } // Private\n\n\n    _start(event) {\n      if (!this._supportPointerEvents) {\n        this._deltaX = event.touches[0].clientX;\n        return;\n      }\n\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX;\n      }\n    }\n\n    _end(event) {\n      if (this._eventIsPointerPenTouch(event)) {\n        this._deltaX = event.clientX - this._deltaX;\n      }\n\n      this._handleSwipe();\n\n      index.execute(this._config.endCallback);\n    }\n\n    _move(event) {\n      this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n    }\n\n    _handleSwipe() {\n      const absDeltaX = Math.abs(this._deltaX);\n\n      if (absDeltaX <= SWIPE_THRESHOLD) {\n        return;\n      }\n\n      const direction = absDeltaX / this._deltaX;\n      this._deltaX = 0;\n\n      if (!direction) {\n        return;\n      }\n\n      index.execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n    }\n\n    _initEvents() {\n      if (this._supportPointerEvents) {\n        EventHandler__default.default.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n        EventHandler__default.default.on(this._element, EVENT_POINTERUP, event => this._end(event));\n\n        this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n      } else {\n        EventHandler__default.default.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n        EventHandler__default.default.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n        EventHandler__default.default.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n      }\n    }\n\n    _eventIsPointerPenTouch(event) {\n      return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n    } // Static\n\n\n    static isSupported() {\n      return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n    }\n\n  }\n\n  return Swipe;\n\n}));\n//# sourceMappingURL=swipe.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/dist/util/template-factory.js",
    "content": "/*!\n  * Bootstrap template-factory.js v5.2.3 (https://getbootstrap.com/)\n  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n  */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./sanitizer'), require('./index'), require('../dom/selector-engine'), require('./config')) :\n  typeof define === 'function' && define.amd ? define(['./sanitizer', './index', '../dom/selector-engine', './config'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TemplateFactory = factory(global.Sanitizer, global.Index, global.SelectorEngine, global.Config));\n})(this, (function (sanitizer, index, SelectorEngine, Config) { 'use strict';\n\n  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };\n\n  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);\n  const Config__default = /*#__PURE__*/_interopDefaultLegacy(Config);\n\n  /**\n   * --------------------------------------------------------------------------\n   * Bootstrap (v5.2.3): util/template-factory.js\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   * --------------------------------------------------------------------------\n   */\n  /**\n   * Constants\n   */\n\n  const NAME = 'TemplateFactory';\n  const Default = {\n    allowList: sanitizer.DefaultAllowlist,\n    content: {},\n    // { selector : text ,  selector2 : text2 , }\n    extraClass: '',\n    html: false,\n    sanitize: true,\n    sanitizeFn: null,\n    template: '<div></div>'\n  };\n  const DefaultType = {\n    allowList: 'object',\n    content: 'object',\n    extraClass: '(string|function)',\n    html: 'boolean',\n    sanitize: 'boolean',\n    sanitizeFn: '(null|function)',\n    template: 'string'\n  };\n  const DefaultContentType = {\n    entry: '(string|element|function|null)',\n    selector: '(string|element)'\n  };\n  /**\n   * Class definition\n   */\n\n  class TemplateFactory extends Config__default.default {\n    constructor(config) {\n      super();\n      this._config = this._getConfig(config);\n    } // Getters\n\n\n    static get Default() {\n      return Default;\n    }\n\n    static get DefaultType() {\n      return DefaultType;\n    }\n\n    static get NAME() {\n      return NAME;\n    } // Public\n\n\n    getContent() {\n      return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n    }\n\n    hasContent() {\n      return this.getContent().length > 0;\n    }\n\n    changeContent(content) {\n      this._checkContent(content);\n\n      this._config.content = { ...this._config.content,\n        ...content\n      };\n      return this;\n    }\n\n    toHtml() {\n      const templateWrapper = document.createElement('div');\n      templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n\n      for (const [selector, text] of Object.entries(this._config.content)) {\n        this._setContent(templateWrapper, text, selector);\n      }\n\n      const template = templateWrapper.children[0];\n\n      const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n\n      if (extraClass) {\n        template.classList.add(...extraClass.split(' '));\n      }\n\n      return template;\n    } // Private\n\n\n    _typeCheckConfig(config) {\n      super._typeCheckConfig(config);\n\n      this._checkContent(config.content);\n    }\n\n    _checkContent(arg) {\n      for (const [selector, content] of Object.entries(arg)) {\n        super._typeCheckConfig({\n          selector,\n          entry: content\n        }, DefaultContentType);\n      }\n    }\n\n    _setContent(template, content, selector) {\n      const templateElement = SelectorEngine__default.default.findOne(selector, template);\n\n      if (!templateElement) {\n        return;\n      }\n\n      content = this._resolvePossibleFunction(content);\n\n      if (!content) {\n        templateElement.remove();\n        return;\n      }\n\n      if (index.isElement(content)) {\n        this._putElementInTemplate(index.getElement(content), templateElement);\n\n        return;\n      }\n\n      if (this._config.html) {\n        templateElement.innerHTML = this._maybeSanitize(content);\n        return;\n      }\n\n      templateElement.textContent = content;\n    }\n\n    _maybeSanitize(arg) {\n      return this._config.sanitize ? sanitizer.sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n    }\n\n    _resolvePossibleFunction(arg) {\n      return typeof arg === 'function' ? arg(this) : arg;\n    }\n\n    _putElementInTemplate(element, templateElement) {\n      if (this._config.html) {\n        templateElement.innerHTML = '';\n        templateElement.append(element);\n        return;\n      }\n\n      templateElement.textContent = element.textContent;\n    }\n\n  }\n\n  return TemplateFactory;\n\n}));\n//# sourceMappingURL=template-factory.js.map\n"
  },
  {
    "path": "src/common/bootstrap/js/index.esm.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): index.esm.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nexport { default as Alert } from './src/alert'\nexport { default as Button } from './src/button'\nexport { default as Carousel } from './src/carousel'\nexport { default as Collapse } from './src/collapse'\nexport { default as Dropdown } from './src/dropdown'\nexport { default as Modal } from './src/modal'\nexport { default as Offcanvas } from './src/offcanvas'\nexport { default as Popover } from './src/popover'\nexport { default as ScrollSpy } from './src/scrollspy'\nexport { default as Tab } from './src/tab'\nexport { default as Toast } from './src/toast'\nexport { default as Tooltip } from './src/tooltip'\n"
  },
  {
    "path": "src/common/bootstrap/js/index.umd.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert'\nimport Button from './src/button'\nimport Carousel from './src/carousel'\nimport Collapse from './src/collapse'\nimport Dropdown from './src/dropdown'\nimport Modal from './src/modal'\nimport Offcanvas from './src/offcanvas'\nimport Popover from './src/popover'\nimport ScrollSpy from './src/scrollspy'\nimport Tab from './src/tab'\nimport Toast from './src/toast'\nimport Tooltip from './src/tooltip'\n\nexport default {\n  Alert,\n  Button,\n  Carousel,\n  Collapse,\n  Dropdown,\n  Modal,\n  Offcanvas,\n  Popover,\n  ScrollSpy,\n  Tab,\n  Toast,\n  Tooltip\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/src/alert.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n  // Getters\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  close() {\n    const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n    if (closeEvent.defaultPrevented) {\n      return\n    }\n\n    this._element.classList.remove(CLASS_NAME_SHOW)\n\n    const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n    this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n  }\n\n  // Private\n  _destroyElement() {\n    this._element.remove()\n    EventHandler.trigger(this._element, EVENT_CLOSED)\n    this.dispose()\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Alert.getOrCreateInstance(this)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config](this)\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n"
  },
  {
    "path": "src/common/bootstrap/js/src/base-component.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data'\nimport { executeAfterTransition, getElement } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Config from './util/config'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.2.3'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n  constructor(element, config) {\n    super()\n\n    element = getElement(element)\n    if (!element) {\n      return\n    }\n\n    this._element = element\n    this._config = this._getConfig(config)\n\n    Data.set(this._element, this.constructor.DATA_KEY, this)\n  }\n\n  // Public\n  dispose() {\n    Data.remove(this._element, this.constructor.DATA_KEY)\n    EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n    for (const propertyName of Object.getOwnPropertyNames(this)) {\n      this[propertyName] = null\n    }\n  }\n\n  _queueCallback(callback, element, isAnimated = true) {\n    executeAfterTransition(callback, element, isAnimated)\n  }\n\n  _getConfig(config) {\n    config = this._mergeConfigObj(config, this._element)\n    config = this._configAfterMerge(config)\n    this._typeCheckConfig(config)\n    return config\n  }\n\n  // Static\n  static getInstance(element) {\n    return Data.get(getElement(element), this.DATA_KEY)\n  }\n\n  static getOrCreateInstance(element, config = {}) {\n    return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n  }\n\n  static get VERSION() {\n    return VERSION\n  }\n\n  static get DATA_KEY() {\n    return `bs.${this.NAME}`\n  }\n\n  static get EVENT_KEY() {\n    return `.${this.DATA_KEY}`\n  }\n\n  static eventName(name) {\n    return `${name}${this.EVENT_KEY}`\n  }\n}\n\nexport default BaseComponent\n"
  },
  {
    "path": "src/common/bootstrap/js/src/button.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n  // Getters\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  toggle() {\n    // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n    this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Button.getOrCreateInstance(this)\n\n      if (config === 'toggle') {\n        data[config]()\n      }\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n  event.preventDefault()\n\n  const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n  const data = Button.getOrCreateInstance(button)\n\n  data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n"
  },
  {
    "path": "src/common/bootstrap/js/src/carousel.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n  defineJQueryPlugin,\n  getElementFromSelector,\n  getNextActiveElement,\n  isRTL,\n  isVisible,\n  reflow,\n  triggerTransitionEnd\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport Swipe from './util/swipe'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n  [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n  [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n  interval: 5000,\n  keyboard: true,\n  pause: 'hover',\n  ride: false,\n  touch: true,\n  wrap: true\n}\n\nconst DefaultType = {\n  interval: '(number|boolean)', // TODO:v6 remove boolean support\n  keyboard: 'boolean',\n  pause: '(string|boolean)',\n  ride: '(boolean|string)',\n  touch: 'boolean',\n  wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    this._interval = null\n    this._activeElement = null\n    this._isSliding = false\n    this.touchTimeout = null\n    this._swipeHelper = null\n\n    this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n    this._addEventListeners()\n\n    if (this._config.ride === CLASS_NAME_CAROUSEL) {\n      this.cycle()\n    }\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  next() {\n    this._slide(ORDER_NEXT)\n  }\n\n  nextWhenVisible() {\n    // FIXME TODO use `document.visibilityState`\n    // Don't call next when the page isn't visible\n    // or the carousel or its parent isn't visible\n    if (!document.hidden && isVisible(this._element)) {\n      this.next()\n    }\n  }\n\n  prev() {\n    this._slide(ORDER_PREV)\n  }\n\n  pause() {\n    if (this._isSliding) {\n      triggerTransitionEnd(this._element)\n    }\n\n    this._clearInterval()\n  }\n\n  cycle() {\n    this._clearInterval()\n    this._updateInterval()\n\n    this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n  }\n\n  _maybeEnableCycle() {\n    if (!this._config.ride) {\n      return\n    }\n\n    if (this._isSliding) {\n      EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n      return\n    }\n\n    this.cycle()\n  }\n\n  to(index) {\n    const items = this._getItems()\n    if (index > items.length - 1 || index < 0) {\n      return\n    }\n\n    if (this._isSliding) {\n      EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n      return\n    }\n\n    const activeIndex = this._getItemIndex(this._getActive())\n    if (activeIndex === index) {\n      return\n    }\n\n    const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n    this._slide(order, items[index])\n  }\n\n  dispose() {\n    if (this._swipeHelper) {\n      this._swipeHelper.dispose()\n    }\n\n    super.dispose()\n  }\n\n  // Private\n  _configAfterMerge(config) {\n    config.defaultInterval = config.interval\n    return config\n  }\n\n  _addEventListeners() {\n    if (this._config.keyboard) {\n      EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n    }\n\n    if (this._config.pause === 'hover') {\n      EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n      EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n    }\n\n    if (this._config.touch && Swipe.isSupported()) {\n      this._addTouchEventListeners()\n    }\n  }\n\n  _addTouchEventListeners() {\n    for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n      EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n    }\n\n    const endCallBack = () => {\n      if (this._config.pause !== 'hover') {\n        return\n      }\n\n      // If it's a touch-enabled device, mouseenter/leave are fired as\n      // part of the mouse compatibility events on first tap - the carousel\n      // would stop cycling until user tapped out of it;\n      // here, we listen for touchend, explicitly pause the carousel\n      // (as if it's the second time we tap on it, mouseenter compat event\n      // is NOT fired) and after a timeout (to allow for mouse compatibility\n      // events to fire) we explicitly restart cycling\n\n      this.pause()\n      if (this.touchTimeout) {\n        clearTimeout(this.touchTimeout)\n      }\n\n      this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n    }\n\n    const swipeConfig = {\n      leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n      rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n      endCallback: endCallBack\n    }\n\n    this._swipeHelper = new Swipe(this._element, swipeConfig)\n  }\n\n  _keydown(event) {\n    if (/input|textarea/i.test(event.target.tagName)) {\n      return\n    }\n\n    const direction = KEY_TO_DIRECTION[event.key]\n    if (direction) {\n      event.preventDefault()\n      this._slide(this._directionToOrder(direction))\n    }\n  }\n\n  _getItemIndex(element) {\n    return this._getItems().indexOf(element)\n  }\n\n  _setActiveIndicatorElement(index) {\n    if (!this._indicatorsElement) {\n      return\n    }\n\n    const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n    activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n    activeIndicator.removeAttribute('aria-current')\n\n    const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n    if (newActiveIndicator) {\n      newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n      newActiveIndicator.setAttribute('aria-current', 'true')\n    }\n  }\n\n  _updateInterval() {\n    const element = this._activeElement || this._getActive()\n\n    if (!element) {\n      return\n    }\n\n    const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n    this._config.interval = elementInterval || this._config.defaultInterval\n  }\n\n  _slide(order, element = null) {\n    if (this._isSliding) {\n      return\n    }\n\n    const activeElement = this._getActive()\n    const isNext = order === ORDER_NEXT\n    const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n    if (nextElement === activeElement) {\n      return\n    }\n\n    const nextElementIndex = this._getItemIndex(nextElement)\n\n    const triggerEvent = eventName => {\n      return EventHandler.trigger(this._element, eventName, {\n        relatedTarget: nextElement,\n        direction: this._orderToDirection(order),\n        from: this._getItemIndex(activeElement),\n        to: nextElementIndex\n      })\n    }\n\n    const slideEvent = triggerEvent(EVENT_SLIDE)\n\n    if (slideEvent.defaultPrevented) {\n      return\n    }\n\n    if (!activeElement || !nextElement) {\n      // Some weirdness is happening, so we bail\n      // todo: change tests that use empty divs to avoid this check\n      return\n    }\n\n    const isCycling = Boolean(this._interval)\n    this.pause()\n\n    this._isSliding = true\n\n    this._setActiveIndicatorElement(nextElementIndex)\n    this._activeElement = nextElement\n\n    const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n    const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n    nextElement.classList.add(orderClassName)\n\n    reflow(nextElement)\n\n    activeElement.classList.add(directionalClassName)\n    nextElement.classList.add(directionalClassName)\n\n    const completeCallBack = () => {\n      nextElement.classList.remove(directionalClassName, orderClassName)\n      nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n      activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n      this._isSliding = false\n\n      triggerEvent(EVENT_SLID)\n    }\n\n    this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n    if (isCycling) {\n      this.cycle()\n    }\n  }\n\n  _isAnimated() {\n    return this._element.classList.contains(CLASS_NAME_SLIDE)\n  }\n\n  _getActive() {\n    return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n  }\n\n  _getItems() {\n    return SelectorEngine.find(SELECTOR_ITEM, this._element)\n  }\n\n  _clearInterval() {\n    if (this._interval) {\n      clearInterval(this._interval)\n      this._interval = null\n    }\n  }\n\n  _directionToOrder(direction) {\n    if (isRTL()) {\n      return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n    }\n\n    return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n  }\n\n  _orderToDirection(order) {\n    if (isRTL()) {\n      return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n    }\n\n    return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Carousel.getOrCreateInstance(this, config)\n\n      if (typeof config === 'number') {\n        data.to(config)\n        return\n      }\n\n      if (typeof config === 'string') {\n        if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n          throw new TypeError(`No method named \"${config}\"`)\n        }\n\n        data[config]()\n      }\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n  const target = getElementFromSelector(this)\n\n  if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n    return\n  }\n\n  event.preventDefault()\n\n  const carousel = Carousel.getOrCreateInstance(target)\n  const slideIndex = this.getAttribute('data-bs-slide-to')\n\n  if (slideIndex) {\n    carousel.to(slideIndex)\n    carousel._maybeEnableCycle()\n    return\n  }\n\n  if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n    carousel.next()\n    carousel._maybeEnableCycle()\n    return\n  }\n\n  carousel.prev()\n  carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n  const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n  for (const carousel of carousels) {\n    Carousel.getOrCreateInstance(carousel)\n  }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n"
  },
  {
    "path": "src/common/bootstrap/js/src/collapse.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n  defineJQueryPlugin,\n  getElement,\n  getElementFromSelector,\n  getSelectorFromElement,\n  reflow\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n  parent: null,\n  toggle: true\n}\n\nconst DefaultType = {\n  parent: '(null|element)',\n  toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    this._isTransitioning = false\n    this._triggerArray = []\n\n    const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n    for (const elem of toggleList) {\n      const selector = getSelectorFromElement(elem)\n      const filterElement = SelectorEngine.find(selector)\n        .filter(foundElement => foundElement === this._element)\n\n      if (selector !== null && filterElement.length) {\n        this._triggerArray.push(elem)\n      }\n    }\n\n    this._initializeChildren()\n\n    if (!this._config.parent) {\n      this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n    }\n\n    if (this._config.toggle) {\n      this.toggle()\n    }\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  toggle() {\n    if (this._isShown()) {\n      this.hide()\n    } else {\n      this.show()\n    }\n  }\n\n  show() {\n    if (this._isTransitioning || this._isShown()) {\n      return\n    }\n\n    let activeChildren = []\n\n    // find active children\n    if (this._config.parent) {\n      activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n        .filter(element => element !== this._element)\n        .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n    }\n\n    if (activeChildren.length && activeChildren[0]._isTransitioning) {\n      return\n    }\n\n    const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n    if (startEvent.defaultPrevented) {\n      return\n    }\n\n    for (const activeInstance of activeChildren) {\n      activeInstance.hide()\n    }\n\n    const dimension = this._getDimension()\n\n    this._element.classList.remove(CLASS_NAME_COLLAPSE)\n    this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n    this._element.style[dimension] = 0\n\n    this._addAriaAndCollapsedClass(this._triggerArray, true)\n    this._isTransitioning = true\n\n    const complete = () => {\n      this._isTransitioning = false\n\n      this._element.classList.remove(CLASS_NAME_COLLAPSING)\n      this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n      this._element.style[dimension] = ''\n\n      EventHandler.trigger(this._element, EVENT_SHOWN)\n    }\n\n    const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n    const scrollSize = `scroll${capitalizedDimension}`\n\n    this._queueCallback(complete, this._element, true)\n    this._element.style[dimension] = `${this._element[scrollSize]}px`\n  }\n\n  hide() {\n    if (this._isTransitioning || !this._isShown()) {\n      return\n    }\n\n    const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n    if (startEvent.defaultPrevented) {\n      return\n    }\n\n    const dimension = this._getDimension()\n\n    this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n    reflow(this._element)\n\n    this._element.classList.add(CLASS_NAME_COLLAPSING)\n    this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n    for (const trigger of this._triggerArray) {\n      const element = getElementFromSelector(trigger)\n\n      if (element && !this._isShown(element)) {\n        this._addAriaAndCollapsedClass([trigger], false)\n      }\n    }\n\n    this._isTransitioning = true\n\n    const complete = () => {\n      this._isTransitioning = false\n      this._element.classList.remove(CLASS_NAME_COLLAPSING)\n      this._element.classList.add(CLASS_NAME_COLLAPSE)\n      EventHandler.trigger(this._element, EVENT_HIDDEN)\n    }\n\n    this._element.style[dimension] = ''\n\n    this._queueCallback(complete, this._element, true)\n  }\n\n  _isShown(element = this._element) {\n    return element.classList.contains(CLASS_NAME_SHOW)\n  }\n\n  // Private\n  _configAfterMerge(config) {\n    config.toggle = Boolean(config.toggle) // Coerce string values\n    config.parent = getElement(config.parent)\n    return config\n  }\n\n  _getDimension() {\n    return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n  }\n\n  _initializeChildren() {\n    if (!this._config.parent) {\n      return\n    }\n\n    const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n    for (const element of children) {\n      const selected = getElementFromSelector(element)\n\n      if (selected) {\n        this._addAriaAndCollapsedClass([element], this._isShown(selected))\n      }\n    }\n  }\n\n  _getFirstLevelChildren(selector) {\n    const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n    // remove children if greater depth\n    return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n  }\n\n  _addAriaAndCollapsedClass(triggerArray, isOpen) {\n    if (!triggerArray.length) {\n      return\n    }\n\n    for (const element of triggerArray) {\n      element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n      element.setAttribute('aria-expanded', isOpen)\n    }\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    const _config = {}\n    if (typeof config === 'string' && /show|hide/.test(config)) {\n      _config.toggle = false\n    }\n\n    return this.each(function () {\n      const data = Collapse.getOrCreateInstance(this, _config)\n\n      if (typeof config === 'string') {\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`)\n        }\n\n        data[config]()\n      }\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n  // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n  if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n    event.preventDefault()\n  }\n\n  const selector = getSelectorFromElement(this)\n  const selectorElements = SelectorEngine.find(selector)\n\n  for (const element of selectorElements) {\n    Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n  }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n"
  },
  {
    "path": "src/common/bootstrap/js/src/dom/data.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n  set(element, key, instance) {\n    if (!elementMap.has(element)) {\n      elementMap.set(element, new Map())\n    }\n\n    const instanceMap = elementMap.get(element)\n\n    // make it clear we only want one instance per element\n    // can be removed later when multiple key/instances are fine to be used\n    if (!instanceMap.has(key) && instanceMap.size !== 0) {\n      // eslint-disable-next-line no-console\n      console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n      return\n    }\n\n    instanceMap.set(key, instance)\n  },\n\n  get(element, key) {\n    if (elementMap.has(element)) {\n      return elementMap.get(element).get(key) || null\n    }\n\n    return null\n  },\n\n  remove(element, key) {\n    if (!elementMap.has(element)) {\n      return\n    }\n\n    const instanceMap = elementMap.get(element)\n\n    instanceMap.delete(key)\n\n    // free up element references if there are no instances left for an element\n    if (instanceMap.size === 0) {\n      elementMap.delete(element)\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/src/dom/event-handler.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n  mouseenter: 'mouseover',\n  mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n  'click',\n  'dblclick',\n  'mouseup',\n  'mousedown',\n  'contextmenu',\n  'mousewheel',\n  'DOMMouseScroll',\n  'mouseover',\n  'mouseout',\n  'mousemove',\n  'selectstart',\n  'selectend',\n  'keydown',\n  'keypress',\n  'keyup',\n  'orientationchange',\n  'touchstart',\n  'touchmove',\n  'touchend',\n  'touchcancel',\n  'pointerdown',\n  'pointermove',\n  'pointerup',\n  'pointerleave',\n  'pointercancel',\n  'gesturestart',\n  'gesturechange',\n  'gestureend',\n  'focus',\n  'blur',\n  'change',\n  'reset',\n  'select',\n  'submit',\n  'focusin',\n  'focusout',\n  'load',\n  'unload',\n  'beforeunload',\n  'resize',\n  'move',\n  'DOMContentLoaded',\n  'readystatechange',\n  'error',\n  'abort',\n  'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n  return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n  const uid = makeEventUid(element)\n\n  element.uidEvent = uid\n  eventRegistry[uid] = eventRegistry[uid] || {}\n\n  return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n  return function handler(event) {\n    hydrateObj(event, { delegateTarget: element })\n\n    if (handler.oneOff) {\n      EventHandler.off(element, event.type, fn)\n    }\n\n    return fn.apply(element, [event])\n  }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n  return function handler(event) {\n    const domElements = element.querySelectorAll(selector)\n\n    for (let { target } = event; target && target !== this; target = target.parentNode) {\n      for (const domElement of domElements) {\n        if (domElement !== target) {\n          continue\n        }\n\n        hydrateObj(event, { delegateTarget: target })\n\n        if (handler.oneOff) {\n          EventHandler.off(element, event.type, selector, fn)\n        }\n\n        return fn.apply(target, [event])\n      }\n    }\n  }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n  return Object.values(events)\n    .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n  const isDelegated = typeof handler === 'string'\n  // todo: tooltip passes `false` instead of selector, so we need to check\n  const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n  let typeEvent = getTypeEvent(originalTypeEvent)\n\n  if (!nativeEvents.has(typeEvent)) {\n    typeEvent = originalTypeEvent\n  }\n\n  return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n  if (typeof originalTypeEvent !== 'string' || !element) {\n    return\n  }\n\n  let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n  // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n  // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n  if (originalTypeEvent in customEvents) {\n    const wrapFunction = fn => {\n      return function (event) {\n        if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n          return fn.call(this, event)\n        }\n      }\n    }\n\n    callable = wrapFunction(callable)\n  }\n\n  const events = getElementEvents(element)\n  const handlers = events[typeEvent] || (events[typeEvent] = {})\n  const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n  if (previousFunction) {\n    previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n    return\n  }\n\n  const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n  const fn = isDelegated ?\n    bootstrapDelegationHandler(element, handler, callable) :\n    bootstrapHandler(element, callable)\n\n  fn.delegationSelector = isDelegated ? handler : null\n  fn.callable = callable\n  fn.oneOff = oneOff\n  fn.uidEvent = uid\n  handlers[uid] = fn\n\n  element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n  const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n  if (!fn) {\n    return\n  }\n\n  element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n  delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n  const storeElementEvent = events[typeEvent] || {}\n\n  for (const handlerKey of Object.keys(storeElementEvent)) {\n    if (handlerKey.includes(namespace)) {\n      const event = storeElementEvent[handlerKey]\n      removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n    }\n  }\n}\n\nfunction getTypeEvent(event) {\n  // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n  event = event.replace(stripNameRegex, '')\n  return customEvents[event] || event\n}\n\nconst EventHandler = {\n  on(element, event, handler, delegationFunction) {\n    addHandler(element, event, handler, delegationFunction, false)\n  },\n\n  one(element, event, handler, delegationFunction) {\n    addHandler(element, event, handler, delegationFunction, true)\n  },\n\n  off(element, originalTypeEvent, handler, delegationFunction) {\n    if (typeof originalTypeEvent !== 'string' || !element) {\n      return\n    }\n\n    const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n    const inNamespace = typeEvent !== originalTypeEvent\n    const events = getElementEvents(element)\n    const storeElementEvent = events[typeEvent] || {}\n    const isNamespace = originalTypeEvent.startsWith('.')\n\n    if (typeof callable !== 'undefined') {\n      // Simplest case: handler is passed, remove that listener ONLY.\n      if (!Object.keys(storeElementEvent).length) {\n        return\n      }\n\n      removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n      return\n    }\n\n    if (isNamespace) {\n      for (const elementEvent of Object.keys(events)) {\n        removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n      }\n    }\n\n    for (const keyHandlers of Object.keys(storeElementEvent)) {\n      const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n      if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n        const event = storeElementEvent[keyHandlers]\n        removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n      }\n    }\n  },\n\n  trigger(element, event, args) {\n    if (typeof event !== 'string' || !element) {\n      return null\n    }\n\n    const $ = getjQuery()\n    const typeEvent = getTypeEvent(event)\n    const inNamespace = event !== typeEvent\n\n    let jQueryEvent = null\n    let bubbles = true\n    let nativeDispatch = true\n    let defaultPrevented = false\n\n    if (inNamespace && $) {\n      jQueryEvent = $.Event(event, args)\n\n      $(element).trigger(jQueryEvent)\n      bubbles = !jQueryEvent.isPropagationStopped()\n      nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n      defaultPrevented = jQueryEvent.isDefaultPrevented()\n    }\n\n    let evt = new Event(event, { bubbles, cancelable: true })\n    evt = hydrateObj(evt, args)\n\n    if (defaultPrevented) {\n      evt.preventDefault()\n    }\n\n    if (nativeDispatch) {\n      element.dispatchEvent(evt)\n    }\n\n    if (evt.defaultPrevented && jQueryEvent) {\n      jQueryEvent.preventDefault()\n    }\n\n    return evt\n  }\n}\n\nfunction hydrateObj(obj, meta) {\n  for (const [key, value] of Object.entries(meta || {})) {\n    try {\n      obj[key] = value\n    } catch {\n      Object.defineProperty(obj, key, {\n        configurable: true,\n        get() {\n          return value\n        }\n      })\n    }\n  }\n\n  return obj\n}\n\nexport default EventHandler\n"
  },
  {
    "path": "src/common/bootstrap/js/src/dom/manipulator.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n  if (value === 'true') {\n    return true\n  }\n\n  if (value === 'false') {\n    return false\n  }\n\n  if (value === Number(value).toString()) {\n    return Number(value)\n  }\n\n  if (value === '' || value === 'null') {\n    return null\n  }\n\n  if (typeof value !== 'string') {\n    return value\n  }\n\n  try {\n    return JSON.parse(decodeURIComponent(value))\n  } catch {\n    return value\n  }\n}\n\nfunction normalizeDataKey(key) {\n  return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n  setDataAttribute(element, key, value) {\n    element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n  },\n\n  removeDataAttribute(element, key) {\n    element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n  },\n\n  getDataAttributes(element) {\n    if (!element) {\n      return {}\n    }\n\n    const attributes = {}\n    const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n    for (const key of bsKeys) {\n      let pureKey = key.replace(/^bs/, '')\n      pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n      attributes[pureKey] = normalizeData(element.dataset[key])\n    }\n\n    return attributes\n  },\n\n  getDataAttribute(element, key) {\n    return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n  }\n}\n\nexport default Manipulator\n"
  },
  {
    "path": "src/common/bootstrap/js/src/dom/selector-engine.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible } from '../util/index'\n\n/**\n * Constants\n */\n\nconst SelectorEngine = {\n  find(selector, element = document.documentElement) {\n    return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n  },\n\n  findOne(selector, element = document.documentElement) {\n    return Element.prototype.querySelector.call(element, selector)\n  },\n\n  children(element, selector) {\n    return [].concat(...element.children).filter(child => child.matches(selector))\n  },\n\n  parents(element, selector) {\n    const parents = []\n    let ancestor = element.parentNode.closest(selector)\n\n    while (ancestor) {\n      parents.push(ancestor)\n      ancestor = ancestor.parentNode.closest(selector)\n    }\n\n    return parents\n  },\n\n  prev(element, selector) {\n    let previous = element.previousElementSibling\n\n    while (previous) {\n      if (previous.matches(selector)) {\n        return [previous]\n      }\n\n      previous = previous.previousElementSibling\n    }\n\n    return []\n  },\n  // TODO: this is now unused; remove later along with prev()\n  next(element, selector) {\n    let next = element.nextElementSibling\n\n    while (next) {\n      if (next.matches(selector)) {\n        return [next]\n      }\n\n      next = next.nextElementSibling\n    }\n\n    return []\n  },\n\n  focusableChildren(element) {\n    const focusables = [\n      'a',\n      'button',\n      'input',\n      'textarea',\n      'select',\n      'details',\n      '[tabindex]',\n      '[contenteditable=\"true\"]'\n    ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n    return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n  }\n}\n\nexport default SelectorEngine\n"
  },
  {
    "path": "src/common/bootstrap/js/src/dropdown.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport {\n  defineJQueryPlugin,\n  getElement,\n  getNextActiveElement,\n  isDisabled,\n  isElement,\n  isRTL,\n  isVisible,\n  noop\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n  autoClose: true,\n  boundary: 'clippingParents',\n  display: 'dynamic',\n  offset: [0, 2],\n  popperConfig: null,\n  reference: 'toggle'\n}\n\nconst DefaultType = {\n  autoClose: '(boolean|string)',\n  boundary: '(string|element)',\n  display: 'string',\n  offset: '(array|string|function)',\n  popperConfig: '(null|object|function)',\n  reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    this._popper = null\n    this._parent = this._element.parentNode // dropdown wrapper\n    // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n    this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n      SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n      SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n    this._inNavbar = this._detectNavbar()\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  toggle() {\n    return this._isShown() ? this.hide() : this.show()\n  }\n\n  show() {\n    if (isDisabled(this._element) || this._isShown()) {\n      return\n    }\n\n    const relatedTarget = {\n      relatedTarget: this._element\n    }\n\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n    if (showEvent.defaultPrevented) {\n      return\n    }\n\n    this._createPopper()\n\n    // If this is a touch-enabled device we add extra\n    // empty mouseover listeners to the body's immediate children;\n    // only needed because of broken event delegation on iOS\n    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n    if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.on(element, 'mouseover', noop)\n      }\n    }\n\n    this._element.focus()\n    this._element.setAttribute('aria-expanded', true)\n\n    this._menu.classList.add(CLASS_NAME_SHOW)\n    this._element.classList.add(CLASS_NAME_SHOW)\n    EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n  }\n\n  hide() {\n    if (isDisabled(this._element) || !this._isShown()) {\n      return\n    }\n\n    const relatedTarget = {\n      relatedTarget: this._element\n    }\n\n    this._completeHide(relatedTarget)\n  }\n\n  dispose() {\n    if (this._popper) {\n      this._popper.destroy()\n    }\n\n    super.dispose()\n  }\n\n  update() {\n    this._inNavbar = this._detectNavbar()\n    if (this._popper) {\n      this._popper.update()\n    }\n  }\n\n  // Private\n  _completeHide(relatedTarget) {\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n    if (hideEvent.defaultPrevented) {\n      return\n    }\n\n    // If this is a touch-enabled device we remove the extra\n    // empty mouseover listeners we added for iOS support\n    if ('ontouchstart' in document.documentElement) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.off(element, 'mouseover', noop)\n      }\n    }\n\n    if (this._popper) {\n      this._popper.destroy()\n    }\n\n    this._menu.classList.remove(CLASS_NAME_SHOW)\n    this._element.classList.remove(CLASS_NAME_SHOW)\n    this._element.setAttribute('aria-expanded', 'false')\n    Manipulator.removeDataAttribute(this._menu, 'popper')\n    EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n  }\n\n  _getConfig(config) {\n    config = super._getConfig(config)\n\n    if (typeof config.reference === 'object' && !isElement(config.reference) &&\n      typeof config.reference.getBoundingClientRect !== 'function'\n    ) {\n      // Popper virtual elements require a getBoundingClientRect method\n      throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n    }\n\n    return config\n  }\n\n  _createPopper() {\n    if (typeof Popper === 'undefined') {\n      throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n    }\n\n    let referenceElement = this._element\n\n    if (this._config.reference === 'parent') {\n      referenceElement = this._parent\n    } else if (isElement(this._config.reference)) {\n      referenceElement = getElement(this._config.reference)\n    } else if (typeof this._config.reference === 'object') {\n      referenceElement = this._config.reference\n    }\n\n    const popperConfig = this._getPopperConfig()\n    this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n  }\n\n  _isShown() {\n    return this._menu.classList.contains(CLASS_NAME_SHOW)\n  }\n\n  _getPlacement() {\n    const parentDropdown = this._parent\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n      return PLACEMENT_RIGHT\n    }\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n      return PLACEMENT_LEFT\n    }\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n      return PLACEMENT_TOPCENTER\n    }\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n      return PLACEMENT_BOTTOMCENTER\n    }\n\n    // We need to trim the value because custom properties can also include spaces\n    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n      return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n    }\n\n    return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n  }\n\n  _detectNavbar() {\n    return this._element.closest(SELECTOR_NAVBAR) !== null\n  }\n\n  _getOffset() {\n    const { offset } = this._config\n\n    if (typeof offset === 'string') {\n      return offset.split(',').map(value => Number.parseInt(value, 10))\n    }\n\n    if (typeof offset === 'function') {\n      return popperData => offset(popperData, this._element)\n    }\n\n    return offset\n  }\n\n  _getPopperConfig() {\n    const defaultBsPopperConfig = {\n      placement: this._getPlacement(),\n      modifiers: [{\n        name: 'preventOverflow',\n        options: {\n          boundary: this._config.boundary\n        }\n      },\n      {\n        name: 'offset',\n        options: {\n          offset: this._getOffset()\n        }\n      }]\n    }\n\n    // Disable Popper if we have a static display or Dropdown is in Navbar\n    if (this._inNavbar || this._config.display === 'static') {\n      Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove\n      defaultBsPopperConfig.modifiers = [{\n        name: 'applyStyles',\n        enabled: false\n      }]\n    }\n\n    return {\n      ...defaultBsPopperConfig,\n      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n    }\n  }\n\n  _selectMenuItem({ key, target }) {\n    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n    if (!items.length) {\n      return\n    }\n\n    // if target isn't included in items (e.g. when expanding the dropdown)\n    // allow cycling to get the last item in case key equals ARROW_UP_KEY\n    getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Dropdown.getOrCreateInstance(this, config)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config]()\n    })\n  }\n\n  static clearMenus(event) {\n    if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n      return\n    }\n\n    const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n    for (const toggle of openToggles) {\n      const context = Dropdown.getInstance(toggle)\n      if (!context || context._config.autoClose === false) {\n        continue\n      }\n\n      const composedPath = event.composedPath()\n      const isMenuTarget = composedPath.includes(context._menu)\n      if (\n        composedPath.includes(context._element) ||\n        (context._config.autoClose === 'inside' && !isMenuTarget) ||\n        (context._config.autoClose === 'outside' && isMenuTarget)\n      ) {\n        continue\n      }\n\n      // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n      if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n        continue\n      }\n\n      const relatedTarget = { relatedTarget: context._element }\n\n      if (event.type === 'click') {\n        relatedTarget.clickEvent = event\n      }\n\n      context._completeHide(relatedTarget)\n    }\n  }\n\n  static dataApiKeydownHandler(event) {\n    // If not an UP | DOWN | ESCAPE key => not a dropdown command\n    // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n    const isInput = /input|textarea/i.test(event.target.tagName)\n    const isEscapeEvent = event.key === ESCAPE_KEY\n    const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n    if (!isUpOrDownEvent && !isEscapeEvent) {\n      return\n    }\n\n    if (isInput && !isEscapeEvent) {\n      return\n    }\n\n    event.preventDefault()\n\n    // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n    const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n      this :\n      (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n        SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n        SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n    const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n    if (isUpOrDownEvent) {\n      event.stopPropagation()\n      instance.show()\n      instance._selectMenuItem(event)\n      return\n    }\n\n    if (instance._isShown()) { // else is escape and we check if it is shown\n      event.stopPropagation()\n      instance.hide()\n      getToggleButton.focus()\n    }\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n  event.preventDefault()\n  Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n"
  },
  {
    "path": "src/common/bootstrap/js/src/modal.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport ScrollBarHelper from './util/scrollbar'\nimport BaseComponent from './base-component'\nimport Backdrop from './util/backdrop'\nimport FocusTrap from './util/focustrap'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n  backdrop: true,\n  focus: true,\n  keyboard: true\n}\n\nconst DefaultType = {\n  backdrop: '(boolean|string)',\n  focus: 'boolean',\n  keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n    this._backdrop = this._initializeBackDrop()\n    this._focustrap = this._initializeFocusTrap()\n    this._isShown = false\n    this._isTransitioning = false\n    this._scrollBar = new ScrollBarHelper()\n\n    this._addEventListeners()\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  toggle(relatedTarget) {\n    return this._isShown ? this.hide() : this.show(relatedTarget)\n  }\n\n  show(relatedTarget) {\n    if (this._isShown || this._isTransitioning) {\n      return\n    }\n\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n      relatedTarget\n    })\n\n    if (showEvent.defaultPrevented) {\n      return\n    }\n\n    this._isShown = true\n    this._isTransitioning = true\n\n    this._scrollBar.hide()\n\n    document.body.classList.add(CLASS_NAME_OPEN)\n\n    this._adjustDialog()\n\n    this._backdrop.show(() => this._showElement(relatedTarget))\n  }\n\n  hide() {\n    if (!this._isShown || this._isTransitioning) {\n      return\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n    if (hideEvent.defaultPrevented) {\n      return\n    }\n\n    this._isShown = false\n    this._isTransitioning = true\n    this._focustrap.deactivate()\n\n    this._element.classList.remove(CLASS_NAME_SHOW)\n\n    this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n  }\n\n  dispose() {\n    for (const htmlElement of [window, this._dialog]) {\n      EventHandler.off(htmlElement, EVENT_KEY)\n    }\n\n    this._backdrop.dispose()\n    this._focustrap.deactivate()\n    super.dispose()\n  }\n\n  handleUpdate() {\n    this._adjustDialog()\n  }\n\n  // Private\n  _initializeBackDrop() {\n    return new Backdrop({\n      isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n      isAnimated: this._isAnimated()\n    })\n  }\n\n  _initializeFocusTrap() {\n    return new FocusTrap({\n      trapElement: this._element\n    })\n  }\n\n  _showElement(relatedTarget) {\n    // try to append dynamic modal\n    if (!document.body.contains(this._element)) {\n      document.body.append(this._element)\n    }\n\n    this._element.style.display = 'block'\n    this._element.removeAttribute('aria-hidden')\n    this._element.setAttribute('aria-modal', true)\n    this._element.setAttribute('role', 'dialog')\n    this._element.scrollTop = 0\n\n    const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n    if (modalBody) {\n      modalBody.scrollTop = 0\n    }\n\n    reflow(this._element)\n\n    this._element.classList.add(CLASS_NAME_SHOW)\n\n    const transitionComplete = () => {\n      if (this._config.focus) {\n        this._focustrap.activate()\n      }\n\n      this._isTransitioning = false\n      EventHandler.trigger(this._element, EVENT_SHOWN, {\n        relatedTarget\n      })\n    }\n\n    this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n  }\n\n  _addEventListeners() {\n    EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n      if (event.key !== ESCAPE_KEY) {\n        return\n      }\n\n      if (this._config.keyboard) {\n        event.preventDefault()\n        this.hide()\n        return\n      }\n\n      this._triggerBackdropTransition()\n    })\n\n    EventHandler.on(window, EVENT_RESIZE, () => {\n      if (this._isShown && !this._isTransitioning) {\n        this._adjustDialog()\n      }\n    })\n\n    EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n      // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n      EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n        if (this._element !== event.target || this._element !== event2.target) {\n          return\n        }\n\n        if (this._config.backdrop === 'static') {\n          this._triggerBackdropTransition()\n          return\n        }\n\n        if (this._config.backdrop) {\n          this.hide()\n        }\n      })\n    })\n  }\n\n  _hideModal() {\n    this._element.style.display = 'none'\n    this._element.setAttribute('aria-hidden', true)\n    this._element.removeAttribute('aria-modal')\n    this._element.removeAttribute('role')\n    this._isTransitioning = false\n\n    this._backdrop.hide(() => {\n      document.body.classList.remove(CLASS_NAME_OPEN)\n      this._resetAdjustments()\n      this._scrollBar.reset()\n      EventHandler.trigger(this._element, EVENT_HIDDEN)\n    })\n  }\n\n  _isAnimated() {\n    return this._element.classList.contains(CLASS_NAME_FADE)\n  }\n\n  _triggerBackdropTransition() {\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n    if (hideEvent.defaultPrevented) {\n      return\n    }\n\n    const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n    const initialOverflowY = this._element.style.overflowY\n    // return if the following background transition hasn't yet completed\n    if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n      return\n    }\n\n    if (!isModalOverflowing) {\n      this._element.style.overflowY = 'hidden'\n    }\n\n    this._element.classList.add(CLASS_NAME_STATIC)\n    this._queueCallback(() => {\n      this._element.classList.remove(CLASS_NAME_STATIC)\n      this._queueCallback(() => {\n        this._element.style.overflowY = initialOverflowY\n      }, this._dialog)\n    }, this._dialog)\n\n    this._element.focus()\n  }\n\n  /**\n   * The following methods are used to handle overflowing modals\n   */\n\n  _adjustDialog() {\n    const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n    const scrollbarWidth = this._scrollBar.getWidth()\n    const isBodyOverflowing = scrollbarWidth > 0\n\n    if (isBodyOverflowing && !isModalOverflowing) {\n      const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n      this._element.style[property] = `${scrollbarWidth}px`\n    }\n\n    if (!isBodyOverflowing && isModalOverflowing) {\n      const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n      this._element.style[property] = `${scrollbarWidth}px`\n    }\n  }\n\n  _resetAdjustments() {\n    this._element.style.paddingLeft = ''\n    this._element.style.paddingRight = ''\n  }\n\n  // Static\n  static jQueryInterface(config, relatedTarget) {\n    return this.each(function () {\n      const data = Modal.getOrCreateInstance(this, config)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config](relatedTarget)\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n  const target = getElementFromSelector(this)\n\n  if (['A', 'AREA'].includes(this.tagName)) {\n    event.preventDefault()\n  }\n\n  EventHandler.one(target, EVENT_SHOW, showEvent => {\n    if (showEvent.defaultPrevented) {\n      // only register focus restorer if modal will actually get shown\n      return\n    }\n\n    EventHandler.one(target, EVENT_HIDDEN, () => {\n      if (isVisible(this)) {\n        this.focus()\n      }\n    })\n  })\n\n  // avoid conflict when clicking modal toggler while another one is open\n  const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n  if (alreadyOpen) {\n    Modal.getInstance(alreadyOpen).hide()\n  }\n\n  const data = Modal.getOrCreateInstance(target)\n\n  data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n"
  },
  {
    "path": "src/common/bootstrap/js/src/offcanvas.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n  defineJQueryPlugin,\n  getElementFromSelector,\n  isDisabled,\n  isVisible\n} from './util/index'\nimport ScrollBarHelper from './util/scrollbar'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport SelectorEngine from './dom/selector-engine'\nimport Backdrop from './util/backdrop'\nimport FocusTrap from './util/focustrap'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n  backdrop: true,\n  keyboard: true,\n  scroll: false\n}\n\nconst DefaultType = {\n  backdrop: '(boolean|string)',\n  keyboard: 'boolean',\n  scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    this._isShown = false\n    this._backdrop = this._initializeBackDrop()\n    this._focustrap = this._initializeFocusTrap()\n    this._addEventListeners()\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  toggle(relatedTarget) {\n    return this._isShown ? this.hide() : this.show(relatedTarget)\n  }\n\n  show(relatedTarget) {\n    if (this._isShown) {\n      return\n    }\n\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n    if (showEvent.defaultPrevented) {\n      return\n    }\n\n    this._isShown = true\n    this._backdrop.show()\n\n    if (!this._config.scroll) {\n      new ScrollBarHelper().hide()\n    }\n\n    this._element.setAttribute('aria-modal', true)\n    this._element.setAttribute('role', 'dialog')\n    this._element.classList.add(CLASS_NAME_SHOWING)\n\n    const completeCallBack = () => {\n      if (!this._config.scroll || this._config.backdrop) {\n        this._focustrap.activate()\n      }\n\n      this._element.classList.add(CLASS_NAME_SHOW)\n      this._element.classList.remove(CLASS_NAME_SHOWING)\n      EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n    }\n\n    this._queueCallback(completeCallBack, this._element, true)\n  }\n\n  hide() {\n    if (!this._isShown) {\n      return\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n    if (hideEvent.defaultPrevented) {\n      return\n    }\n\n    this._focustrap.deactivate()\n    this._element.blur()\n    this._isShown = false\n    this._element.classList.add(CLASS_NAME_HIDING)\n    this._backdrop.hide()\n\n    const completeCallback = () => {\n      this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n      this._element.removeAttribute('aria-modal')\n      this._element.removeAttribute('role')\n\n      if (!this._config.scroll) {\n        new ScrollBarHelper().reset()\n      }\n\n      EventHandler.trigger(this._element, EVENT_HIDDEN)\n    }\n\n    this._queueCallback(completeCallback, this._element, true)\n  }\n\n  dispose() {\n    this._backdrop.dispose()\n    this._focustrap.deactivate()\n    super.dispose()\n  }\n\n  // Private\n  _initializeBackDrop() {\n    const clickCallback = () => {\n      if (this._config.backdrop === 'static') {\n        EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n        return\n      }\n\n      this.hide()\n    }\n\n    // 'static' option will be translated to true, and booleans will keep their value\n    const isVisible = Boolean(this._config.backdrop)\n\n    return new Backdrop({\n      className: CLASS_NAME_BACKDROP,\n      isVisible,\n      isAnimated: true,\n      rootElement: this._element.parentNode,\n      clickCallback: isVisible ? clickCallback : null\n    })\n  }\n\n  _initializeFocusTrap() {\n    return new FocusTrap({\n      trapElement: this._element\n    })\n  }\n\n  _addEventListeners() {\n    EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n      if (event.key !== ESCAPE_KEY) {\n        return\n      }\n\n      if (!this._config.keyboard) {\n        EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n        return\n      }\n\n      this.hide()\n    })\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Offcanvas.getOrCreateInstance(this, config)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config](this)\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n  const target = getElementFromSelector(this)\n\n  if (['A', 'AREA'].includes(this.tagName)) {\n    event.preventDefault()\n  }\n\n  if (isDisabled(this)) {\n    return\n  }\n\n  EventHandler.one(target, EVENT_HIDDEN, () => {\n    // focus on trigger when it is closed\n    if (isVisible(this)) {\n      this.focus()\n    }\n  })\n\n  // avoid conflict when clicking a toggler of an offcanvas, while another is open\n  const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n  if (alreadyOpen && alreadyOpen !== target) {\n    Offcanvas.getInstance(alreadyOpen).hide()\n  }\n\n  const data = Offcanvas.getOrCreateInstance(target)\n  data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n  for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n    Offcanvas.getOrCreateInstance(selector).show()\n  }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n  for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n    if (getComputedStyle(element).position !== 'fixed') {\n      Offcanvas.getOrCreateInstance(element).hide()\n    }\n  }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n"
  },
  {
    "path": "src/common/bootstrap/js/src/popover.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport Tooltip from './tooltip'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n  ...Tooltip.Default,\n  content: '',\n  offset: [0, 8],\n  placement: 'right',\n  template: '<div class=\"popover\" role=\"tooltip\">' +\n    '<div class=\"popover-arrow\"></div>' +\n    '<h3 class=\"popover-header\"></h3>' +\n    '<div class=\"popover-body\"></div>' +\n    '</div>',\n  trigger: 'click'\n}\n\nconst DefaultType = {\n  ...Tooltip.DefaultType,\n  content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Overrides\n  _isWithContent() {\n    return this._getTitle() || this._getContent()\n  }\n\n  // Private\n  _getContentForTemplate() {\n    return {\n      [SELECTOR_TITLE]: this._getTitle(),\n      [SELECTOR_CONTENT]: this._getContent()\n    }\n  }\n\n  _getContent() {\n    return this._resolvePossibleFunction(this._config.content)\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Popover.getOrCreateInstance(this, config)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config]()\n    })\n  }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n"
  },
  {
    "path": "src/common/bootstrap/js/src/scrollspy.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n  offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n  rootMargin: '0px 0px -25%',\n  smoothScroll: false,\n  target: null,\n  threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n  offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n  rootMargin: 'string',\n  smoothScroll: 'boolean',\n  target: 'element',\n  threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    // this._element is the observablesContainer and config.target the menu links wrapper\n    this._targetLinks = new Map()\n    this._observableSections = new Map()\n    this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n    this._activeTarget = null\n    this._observer = null\n    this._previousScrollData = {\n      visibleEntryTop: 0,\n      parentScrollTop: 0\n    }\n    this.refresh() // initialize\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  refresh() {\n    this._initializeTargetsAndObservables()\n    this._maybeEnableSmoothScroll()\n\n    if (this._observer) {\n      this._observer.disconnect()\n    } else {\n      this._observer = this._getNewObserver()\n    }\n\n    for (const section of this._observableSections.values()) {\n      this._observer.observe(section)\n    }\n  }\n\n  dispose() {\n    this._observer.disconnect()\n    super.dispose()\n  }\n\n  // Private\n  _configAfterMerge(config) {\n    // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n    config.target = getElement(config.target) || document.body\n\n    // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n    config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n    if (typeof config.threshold === 'string') {\n      config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n    }\n\n    return config\n  }\n\n  _maybeEnableSmoothScroll() {\n    if (!this._config.smoothScroll) {\n      return\n    }\n\n    // unregister any previous listeners\n    EventHandler.off(this._config.target, EVENT_CLICK)\n\n    EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n      const observableSection = this._observableSections.get(event.target.hash)\n      if (observableSection) {\n        event.preventDefault()\n        const root = this._rootElement || window\n        const height = observableSection.offsetTop - this._element.offsetTop\n        if (root.scrollTo) {\n          root.scrollTo({ top: height, behavior: 'smooth' })\n          return\n        }\n\n        // Chrome 60 doesn't support `scrollTo`\n        root.scrollTop = height\n      }\n    })\n  }\n\n  _getNewObserver() {\n    const options = {\n      root: this._rootElement,\n      threshold: this._config.threshold,\n      rootMargin: this._config.rootMargin\n    }\n\n    return new IntersectionObserver(entries => this._observerCallback(entries), options)\n  }\n\n  // The logic of selection\n  _observerCallback(entries) {\n    const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n    const activate = entry => {\n      this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n      this._process(targetElement(entry))\n    }\n\n    const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n    const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n    this._previousScrollData.parentScrollTop = parentScrollTop\n\n    for (const entry of entries) {\n      if (!entry.isIntersecting) {\n        this._activeTarget = null\n        this._clearActiveClass(targetElement(entry))\n\n        continue\n      }\n\n      const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n      // if we are scrolling down, pick the bigger offsetTop\n      if (userScrollsDown && entryIsLowerThanPrevious) {\n        activate(entry)\n        // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n        if (!parentScrollTop) {\n          return\n        }\n\n        continue\n      }\n\n      // if we are scrolling up, pick the smallest offsetTop\n      if (!userScrollsDown && !entryIsLowerThanPrevious) {\n        activate(entry)\n      }\n    }\n  }\n\n  _initializeTargetsAndObservables() {\n    this._targetLinks = new Map()\n    this._observableSections = new Map()\n\n    const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n    for (const anchor of targetLinks) {\n      // ensure that the anchor has an id and is not disabled\n      if (!anchor.hash || isDisabled(anchor)) {\n        continue\n      }\n\n      const observableSection = SelectorEngine.findOne(anchor.hash, this._element)\n\n      // ensure that the observableSection exists & is visible\n      if (isVisible(observableSection)) {\n        this._targetLinks.set(anchor.hash, anchor)\n        this._observableSections.set(anchor.hash, observableSection)\n      }\n    }\n  }\n\n  _process(target) {\n    if (this._activeTarget === target) {\n      return\n    }\n\n    this._clearActiveClass(this._config.target)\n    this._activeTarget = target\n    target.classList.add(CLASS_NAME_ACTIVE)\n    this._activateParents(target)\n\n    EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n  }\n\n  _activateParents(target) {\n    // Activate dropdown parents\n    if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n      SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n        .classList.add(CLASS_NAME_ACTIVE)\n      return\n    }\n\n    for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n      // Set triggered links parents as active\n      // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n      for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n        item.classList.add(CLASS_NAME_ACTIVE)\n      }\n    }\n  }\n\n  _clearActiveClass(parent) {\n    parent.classList.remove(CLASS_NAME_ACTIVE)\n\n    const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n    for (const node of activeNodes) {\n      node.classList.remove(CLASS_NAME_ACTIVE)\n    }\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = ScrollSpy.getOrCreateInstance(this, config)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config]()\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n  for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n    ScrollSpy.getOrCreateInstance(spy)\n  }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n"
  },
  {
    "path": "src/common/bootstrap/js/src/tab.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)'\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // todo:v6: could be only `tab`\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n  constructor(element) {\n    super(element)\n    this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n    if (!this._parent) {\n      return\n      // todo: should Throw exception on v6\n      // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n    }\n\n    // Set up initial aria attributes\n    this._setInitialAttributes(this._parent, this._getChildren())\n\n    EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n  }\n\n  // Getters\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  show() { // Shows this elem and deactivate the active sibling if exists\n    const innerElem = this._element\n    if (this._elemIsActive(innerElem)) {\n      return\n    }\n\n    // Search for active tab on same parent to deactivate it\n    const active = this._getActiveElem()\n\n    const hideEvent = active ?\n      EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n      null\n\n    const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n    if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n      return\n    }\n\n    this._deactivate(active, innerElem)\n    this._activate(innerElem, active)\n  }\n\n  // Private\n  _activate(element, relatedElem) {\n    if (!element) {\n      return\n    }\n\n    element.classList.add(CLASS_NAME_ACTIVE)\n\n    this._activate(getElementFromSelector(element)) // Search and activate/show the proper section\n\n    const complete = () => {\n      if (element.getAttribute('role') !== 'tab') {\n        element.classList.add(CLASS_NAME_SHOW)\n        return\n      }\n\n      element.removeAttribute('tabindex')\n      element.setAttribute('aria-selected', true)\n      this._toggleDropDown(element, true)\n      EventHandler.trigger(element, EVENT_SHOWN, {\n        relatedTarget: relatedElem\n      })\n    }\n\n    this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n  }\n\n  _deactivate(element, relatedElem) {\n    if (!element) {\n      return\n    }\n\n    element.classList.remove(CLASS_NAME_ACTIVE)\n    element.blur()\n\n    this._deactivate(getElementFromSelector(element)) // Search and deactivate the shown section too\n\n    const complete = () => {\n      if (element.getAttribute('role') !== 'tab') {\n        element.classList.remove(CLASS_NAME_SHOW)\n        return\n      }\n\n      element.setAttribute('aria-selected', false)\n      element.setAttribute('tabindex', '-1')\n      this._toggleDropDown(element, false)\n      EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n    }\n\n    this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n  }\n\n  _keydown(event) {\n    if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key))) {\n      return\n    }\n\n    event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n    event.preventDefault()\n    const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n    const nextActiveElement = getNextActiveElement(this._getChildren().filter(element => !isDisabled(element)), event.target, isNext, true)\n\n    if (nextActiveElement) {\n      nextActiveElement.focus({ preventScroll: true })\n      Tab.getOrCreateInstance(nextActiveElement).show()\n    }\n  }\n\n  _getChildren() { // collection of inner elements\n    return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n  }\n\n  _getActiveElem() {\n    return this._getChildren().find(child => this._elemIsActive(child)) || null\n  }\n\n  _setInitialAttributes(parent, children) {\n    this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n    for (const child of children) {\n      this._setInitialAttributesOnChild(child)\n    }\n  }\n\n  _setInitialAttributesOnChild(child) {\n    child = this._getInnerElement(child)\n    const isActive = this._elemIsActive(child)\n    const outerElem = this._getOuterElement(child)\n    child.setAttribute('aria-selected', isActive)\n\n    if (outerElem !== child) {\n      this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n    }\n\n    if (!isActive) {\n      child.setAttribute('tabindex', '-1')\n    }\n\n    this._setAttributeIfNotExists(child, 'role', 'tab')\n\n    // set attributes to the related panel too\n    this._setInitialAttributesOnTargetPanel(child)\n  }\n\n  _setInitialAttributesOnTargetPanel(child) {\n    const target = getElementFromSelector(child)\n\n    if (!target) {\n      return\n    }\n\n    this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n    if (child.id) {\n      this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`)\n    }\n  }\n\n  _toggleDropDown(element, open) {\n    const outerElem = this._getOuterElement(element)\n    if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n      return\n    }\n\n    const toggle = (selector, className) => {\n      const element = SelectorEngine.findOne(selector, outerElem)\n      if (element) {\n        element.classList.toggle(className, open)\n      }\n    }\n\n    toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n    toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n    outerElem.setAttribute('aria-expanded', open)\n  }\n\n  _setAttributeIfNotExists(element, attribute, value) {\n    if (!element.hasAttribute(attribute)) {\n      element.setAttribute(attribute, value)\n    }\n  }\n\n  _elemIsActive(elem) {\n    return elem.classList.contains(CLASS_NAME_ACTIVE)\n  }\n\n  // Try to get the inner element (usually the .nav-link)\n  _getInnerElement(elem) {\n    return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n  }\n\n  // Try to get the outer element (usually the .nav-item)\n  _getOuterElement(elem) {\n    return elem.closest(SELECTOR_OUTER) || elem\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Tab.getOrCreateInstance(this)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config]()\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n  if (['A', 'AREA'].includes(this.tagName)) {\n    event.preventDefault()\n  }\n\n  if (isDisabled(this)) {\n    return\n  }\n\n  Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n  for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n    Tab.getOrCreateInstance(element)\n  }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n"
  },
  {
    "path": "src/common/bootstrap/js/src/toast.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, reflow } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n  animation: 'boolean',\n  autohide: 'boolean',\n  delay: 'number'\n}\n\nconst Default = {\n  animation: true,\n  autohide: true,\n  delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n  constructor(element, config) {\n    super(element, config)\n\n    this._timeout = null\n    this._hasMouseInteraction = false\n    this._hasKeyboardInteraction = false\n    this._setListeners()\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  show() {\n    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n    if (showEvent.defaultPrevented) {\n      return\n    }\n\n    this._clearTimeout()\n\n    if (this._config.animation) {\n      this._element.classList.add(CLASS_NAME_FADE)\n    }\n\n    const complete = () => {\n      this._element.classList.remove(CLASS_NAME_SHOWING)\n      EventHandler.trigger(this._element, EVENT_SHOWN)\n\n      this._maybeScheduleHide()\n    }\n\n    this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n    reflow(this._element)\n    this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n    this._queueCallback(complete, this._element, this._config.animation)\n  }\n\n  hide() {\n    if (!this.isShown()) {\n      return\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n    if (hideEvent.defaultPrevented) {\n      return\n    }\n\n    const complete = () => {\n      this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n      this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n      EventHandler.trigger(this._element, EVENT_HIDDEN)\n    }\n\n    this._element.classList.add(CLASS_NAME_SHOWING)\n    this._queueCallback(complete, this._element, this._config.animation)\n  }\n\n  dispose() {\n    this._clearTimeout()\n\n    if (this.isShown()) {\n      this._element.classList.remove(CLASS_NAME_SHOW)\n    }\n\n    super.dispose()\n  }\n\n  isShown() {\n    return this._element.classList.contains(CLASS_NAME_SHOW)\n  }\n\n  // Private\n\n  _maybeScheduleHide() {\n    if (!this._config.autohide) {\n      return\n    }\n\n    if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n      return\n    }\n\n    this._timeout = setTimeout(() => {\n      this.hide()\n    }, this._config.delay)\n  }\n\n  _onInteraction(event, isInteracting) {\n    switch (event.type) {\n      case 'mouseover':\n      case 'mouseout': {\n        this._hasMouseInteraction = isInteracting\n        break\n      }\n\n      case 'focusin':\n      case 'focusout': {\n        this._hasKeyboardInteraction = isInteracting\n        break\n      }\n\n      default: {\n        break\n      }\n    }\n\n    if (isInteracting) {\n      this._clearTimeout()\n      return\n    }\n\n    const nextElement = event.relatedTarget\n    if (this._element === nextElement || this._element.contains(nextElement)) {\n      return\n    }\n\n    this._maybeScheduleHide()\n  }\n\n  _setListeners() {\n    EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n    EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n    EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n    EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n  }\n\n  _clearTimeout() {\n    clearTimeout(this._timeout)\n    this._timeout = null\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Toast.getOrCreateInstance(this, config)\n\n      if (typeof config === 'string') {\n        if (typeof data[config] === 'undefined') {\n          throw new TypeError(`No method named \"${config}\"`)\n        }\n\n        data[config](this)\n      }\n    })\n  }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n"
  },
  {
    "path": "src/common/bootstrap/js/src/tooltip.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'\nimport { DefaultAllowlist } from './util/sanitizer'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport BaseComponent from './base-component'\nimport TemplateFactory from './util/template-factory'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n  AUTO: 'auto',\n  TOP: 'top',\n  RIGHT: isRTL() ? 'left' : 'right',\n  BOTTOM: 'bottom',\n  LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n  allowList: DefaultAllowlist,\n  animation: true,\n  boundary: 'clippingParents',\n  container: false,\n  customClass: '',\n  delay: 0,\n  fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n  html: false,\n  offset: [0, 0],\n  placement: 'top',\n  popperConfig: null,\n  sanitize: true,\n  sanitizeFn: null,\n  selector: false,\n  template: '<div class=\"tooltip\" role=\"tooltip\">' +\n            '<div class=\"tooltip-arrow\"></div>' +\n            '<div class=\"tooltip-inner\"></div>' +\n            '</div>',\n  title: '',\n  trigger: 'hover focus'\n}\n\nconst DefaultType = {\n  allowList: 'object',\n  animation: 'boolean',\n  boundary: '(string|element)',\n  container: '(string|element|boolean)',\n  customClass: '(string|function)',\n  delay: '(number|object)',\n  fallbackPlacements: 'array',\n  html: 'boolean',\n  offset: '(array|string|function)',\n  placement: '(string|function)',\n  popperConfig: '(null|object|function)',\n  sanitize: 'boolean',\n  sanitizeFn: '(null|function)',\n  selector: '(string|boolean)',\n  template: 'string',\n  title: '(string|element|function)',\n  trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n  constructor(element, config) {\n    if (typeof Popper === 'undefined') {\n      throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n    }\n\n    super(element, config)\n\n    // Private\n    this._isEnabled = true\n    this._timeout = 0\n    this._isHovered = null\n    this._activeTrigger = {}\n    this._popper = null\n    this._templateFactory = null\n    this._newContent = null\n\n    // Protected\n    this.tip = null\n\n    this._setListeners()\n\n    if (!this._config.selector) {\n      this._fixTitle()\n    }\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  enable() {\n    this._isEnabled = true\n  }\n\n  disable() {\n    this._isEnabled = false\n  }\n\n  toggleEnabled() {\n    this._isEnabled = !this._isEnabled\n  }\n\n  toggle() {\n    if (!this._isEnabled) {\n      return\n    }\n\n    this._activeTrigger.click = !this._activeTrigger.click\n    if (this._isShown()) {\n      this._leave()\n      return\n    }\n\n    this._enter()\n  }\n\n  dispose() {\n    clearTimeout(this._timeout)\n\n    EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n    if (this._element.getAttribute('data-bs-original-title')) {\n      this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n    }\n\n    this._disposePopper()\n    super.dispose()\n  }\n\n  show() {\n    if (this._element.style.display === 'none') {\n      throw new Error('Please use show on visible elements')\n    }\n\n    if (!(this._isWithContent() && this._isEnabled)) {\n      return\n    }\n\n    const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n    const shadowRoot = findShadowRoot(this._element)\n    const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n    if (showEvent.defaultPrevented || !isInTheDom) {\n      return\n    }\n\n    // todo v6 remove this OR make it optional\n    this._disposePopper()\n\n    const tip = this._getTipElement()\n\n    this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n    const { container } = this._config\n\n    if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n      container.append(tip)\n      EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n    }\n\n    this._popper = this._createPopper(tip)\n\n    tip.classList.add(CLASS_NAME_SHOW)\n\n    // If this is a touch-enabled device we add extra\n    // empty mouseover listeners to the body's immediate children;\n    // only needed because of broken event delegation on iOS\n    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n    if ('ontouchstart' in document.documentElement) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.on(element, 'mouseover', noop)\n      }\n    }\n\n    const complete = () => {\n      EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n      if (this._isHovered === false) {\n        this._leave()\n      }\n\n      this._isHovered = false\n    }\n\n    this._queueCallback(complete, this.tip, this._isAnimated())\n  }\n\n  hide() {\n    if (!this._isShown()) {\n      return\n    }\n\n    const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n    if (hideEvent.defaultPrevented) {\n      return\n    }\n\n    const tip = this._getTipElement()\n    tip.classList.remove(CLASS_NAME_SHOW)\n\n    // If this is a touch-enabled device we remove the extra\n    // empty mouseover listeners we added for iOS support\n    if ('ontouchstart' in document.documentElement) {\n      for (const element of [].concat(...document.body.children)) {\n        EventHandler.off(element, 'mouseover', noop)\n      }\n    }\n\n    this._activeTrigger[TRIGGER_CLICK] = false\n    this._activeTrigger[TRIGGER_FOCUS] = false\n    this._activeTrigger[TRIGGER_HOVER] = false\n    this._isHovered = null // it is a trick to support manual triggering\n\n    const complete = () => {\n      if (this._isWithActiveTrigger()) {\n        return\n      }\n\n      if (!this._isHovered) {\n        this._disposePopper()\n      }\n\n      this._element.removeAttribute('aria-describedby')\n      EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n    }\n\n    this._queueCallback(complete, this.tip, this._isAnimated())\n  }\n\n  update() {\n    if (this._popper) {\n      this._popper.update()\n    }\n  }\n\n  // Protected\n  _isWithContent() {\n    return Boolean(this._getTitle())\n  }\n\n  _getTipElement() {\n    if (!this.tip) {\n      this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n    }\n\n    return this.tip\n  }\n\n  _createTipElement(content) {\n    const tip = this._getTemplateFactory(content).toHtml()\n\n    // todo: remove this check on v6\n    if (!tip) {\n      return null\n    }\n\n    tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n    // todo: on v6 the following can be achieved with CSS only\n    tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n    const tipId = getUID(this.constructor.NAME).toString()\n\n    tip.setAttribute('id', tipId)\n\n    if (this._isAnimated()) {\n      tip.classList.add(CLASS_NAME_FADE)\n    }\n\n    return tip\n  }\n\n  setContent(content) {\n    this._newContent = content\n    if (this._isShown()) {\n      this._disposePopper()\n      this.show()\n    }\n  }\n\n  _getTemplateFactory(content) {\n    if (this._templateFactory) {\n      this._templateFactory.changeContent(content)\n    } else {\n      this._templateFactory = new TemplateFactory({\n        ...this._config,\n        // the `content` var has to be after `this._config`\n        // to override config.content in case of popover\n        content,\n        extraClass: this._resolvePossibleFunction(this._config.customClass)\n      })\n    }\n\n    return this._templateFactory\n  }\n\n  _getContentForTemplate() {\n    return {\n      [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n    }\n  }\n\n  _getTitle() {\n    return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n  }\n\n  // Private\n  _initializeOnDelegatedTarget(event) {\n    return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n  }\n\n  _isAnimated() {\n    return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n  }\n\n  _isShown() {\n    return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n  }\n\n  _createPopper(tip) {\n    const placement = typeof this._config.placement === 'function' ?\n      this._config.placement.call(this, tip, this._element) :\n      this._config.placement\n    const attachment = AttachmentMap[placement.toUpperCase()]\n    return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n  }\n\n  _getOffset() {\n    const { offset } = this._config\n\n    if (typeof offset === 'string') {\n      return offset.split(',').map(value => Number.parseInt(value, 10))\n    }\n\n    if (typeof offset === 'function') {\n      return popperData => offset(popperData, this._element)\n    }\n\n    return offset\n  }\n\n  _resolvePossibleFunction(arg) {\n    return typeof arg === 'function' ? arg.call(this._element) : arg\n  }\n\n  _getPopperConfig(attachment) {\n    const defaultBsPopperConfig = {\n      placement: attachment,\n      modifiers: [\n        {\n          name: 'flip',\n          options: {\n            fallbackPlacements: this._config.fallbackPlacements\n          }\n        },\n        {\n          name: 'offset',\n          options: {\n            offset: this._getOffset()\n          }\n        },\n        {\n          name: 'preventOverflow',\n          options: {\n            boundary: this._config.boundary\n          }\n        },\n        {\n          name: 'arrow',\n          options: {\n            element: `.${this.constructor.NAME}-arrow`\n          }\n        },\n        {\n          name: 'preSetPlacement',\n          enabled: true,\n          phase: 'beforeMain',\n          fn: data => {\n            // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n            // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n            this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n          }\n        }\n      ]\n    }\n\n    return {\n      ...defaultBsPopperConfig,\n      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n    }\n  }\n\n  _setListeners() {\n    const triggers = this._config.trigger.split(' ')\n\n    for (const trigger of triggers) {\n      if (trigger === 'click') {\n        EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n          const context = this._initializeOnDelegatedTarget(event)\n          context.toggle()\n        })\n      } else if (trigger !== TRIGGER_MANUAL) {\n        const eventIn = trigger === TRIGGER_HOVER ?\n          this.constructor.eventName(EVENT_MOUSEENTER) :\n          this.constructor.eventName(EVENT_FOCUSIN)\n        const eventOut = trigger === TRIGGER_HOVER ?\n          this.constructor.eventName(EVENT_MOUSELEAVE) :\n          this.constructor.eventName(EVENT_FOCUSOUT)\n\n        EventHandler.on(this._element, eventIn, this._config.selector, event => {\n          const context = this._initializeOnDelegatedTarget(event)\n          context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n          context._enter()\n        })\n        EventHandler.on(this._element, eventOut, this._config.selector, event => {\n          const context = this._initializeOnDelegatedTarget(event)\n          context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n            context._element.contains(event.relatedTarget)\n\n          context._leave()\n        })\n      }\n    }\n\n    this._hideModalHandler = () => {\n      if (this._element) {\n        this.hide()\n      }\n    }\n\n    EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n  }\n\n  _fixTitle() {\n    const title = this._element.getAttribute('title')\n\n    if (!title) {\n      return\n    }\n\n    if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n      this._element.setAttribute('aria-label', title)\n    }\n\n    this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n    this._element.removeAttribute('title')\n  }\n\n  _enter() {\n    if (this._isShown() || this._isHovered) {\n      this._isHovered = true\n      return\n    }\n\n    this._isHovered = true\n\n    this._setTimeout(() => {\n      if (this._isHovered) {\n        this.show()\n      }\n    }, this._config.delay.show)\n  }\n\n  _leave() {\n    if (this._isWithActiveTrigger()) {\n      return\n    }\n\n    this._isHovered = false\n\n    this._setTimeout(() => {\n      if (!this._isHovered) {\n        this.hide()\n      }\n    }, this._config.delay.hide)\n  }\n\n  _setTimeout(handler, timeout) {\n    clearTimeout(this._timeout)\n    this._timeout = setTimeout(handler, timeout)\n  }\n\n  _isWithActiveTrigger() {\n    return Object.values(this._activeTrigger).includes(true)\n  }\n\n  _getConfig(config) {\n    const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n    for (const dataAttribute of Object.keys(dataAttributes)) {\n      if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n        delete dataAttributes[dataAttribute]\n      }\n    }\n\n    config = {\n      ...dataAttributes,\n      ...(typeof config === 'object' && config ? config : {})\n    }\n    config = this._mergeConfigObj(config)\n    config = this._configAfterMerge(config)\n    this._typeCheckConfig(config)\n    return config\n  }\n\n  _configAfterMerge(config) {\n    config.container = config.container === false ? document.body : getElement(config.container)\n\n    if (typeof config.delay === 'number') {\n      config.delay = {\n        show: config.delay,\n        hide: config.delay\n      }\n    }\n\n    if (typeof config.title === 'number') {\n      config.title = config.title.toString()\n    }\n\n    if (typeof config.content === 'number') {\n      config.content = config.content.toString()\n    }\n\n    return config\n  }\n\n  _getDelegateConfig() {\n    const config = {}\n\n    for (const key in this._config) {\n      if (this.constructor.Default[key] !== this._config[key]) {\n        config[key] = this._config[key]\n      }\n    }\n\n    config.selector = false\n    config.trigger = 'manual'\n\n    // In the future can be replaced with:\n    // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n    // `Object.fromEntries(keysWithDifferentValues)`\n    return config\n  }\n\n  _disposePopper() {\n    if (this._popper) {\n      this._popper.destroy()\n      this._popper = null\n    }\n\n    if (this.tip) {\n      this.tip.remove()\n      this.tip = null\n    }\n  }\n\n  // Static\n  static jQueryInterface(config) {\n    return this.each(function () {\n      const data = Tooltip.getOrCreateInstance(this, config)\n\n      if (typeof config !== 'string') {\n        return\n      }\n\n      if (typeof data[config] === 'undefined') {\n        throw new TypeError(`No method named \"${config}\"`)\n      }\n\n      data[config]()\n    })\n  }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/backdrop.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { execute, executeAfterTransition, getElement, reflow } from './index'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n  className: 'modal-backdrop',\n  clickCallback: null,\n  isAnimated: false,\n  isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n  rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n  className: 'string',\n  clickCallback: '(function|null)',\n  isAnimated: 'boolean',\n  isVisible: 'boolean',\n  rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n  constructor(config) {\n    super()\n    this._config = this._getConfig(config)\n    this._isAppended = false\n    this._element = null\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  show(callback) {\n    if (!this._config.isVisible) {\n      execute(callback)\n      return\n    }\n\n    this._append()\n\n    const element = this._getElement()\n    if (this._config.isAnimated) {\n      reflow(element)\n    }\n\n    element.classList.add(CLASS_NAME_SHOW)\n\n    this._emulateAnimation(() => {\n      execute(callback)\n    })\n  }\n\n  hide(callback) {\n    if (!this._config.isVisible) {\n      execute(callback)\n      return\n    }\n\n    this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n    this._emulateAnimation(() => {\n      this.dispose()\n      execute(callback)\n    })\n  }\n\n  dispose() {\n    if (!this._isAppended) {\n      return\n    }\n\n    EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n    this._element.remove()\n    this._isAppended = false\n  }\n\n  // Private\n  _getElement() {\n    if (!this._element) {\n      const backdrop = document.createElement('div')\n      backdrop.className = this._config.className\n      if (this._config.isAnimated) {\n        backdrop.classList.add(CLASS_NAME_FADE)\n      }\n\n      this._element = backdrop\n    }\n\n    return this._element\n  }\n\n  _configAfterMerge(config) {\n    // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n    config.rootElement = getElement(config.rootElement)\n    return config\n  }\n\n  _append() {\n    if (this._isAppended) {\n      return\n    }\n\n    const element = this._getElement()\n    this._config.rootElement.append(element)\n\n    EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n      execute(this._config.clickCallback)\n    })\n\n    this._isAppended = true\n  }\n\n  _emulateAnimation(callback) {\n    executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n  }\n}\n\nexport default Backdrop\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/component-functions.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { getElementFromSelector, isDisabled } from './index'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n  const clickEvent = `click.dismiss${component.EVENT_KEY}`\n  const name = component.NAME\n\n  EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n    if (['A', 'AREA'].includes(this.tagName)) {\n      event.preventDefault()\n    }\n\n    if (isDisabled(this)) {\n      return\n    }\n\n    const target = getElementFromSelector(this) || this.closest(`.${name}`)\n    const instance = component.getOrCreateInstance(target)\n\n    // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n    instance[method]()\n  })\n}\n\nexport {\n  enableDismissTrigger\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/config.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isElement, toType } from './index'\nimport Manipulator from '../dom/manipulator'\n\n/**\n * Class definition\n */\n\nclass Config {\n  // Getters\n  static get Default() {\n    return {}\n  }\n\n  static get DefaultType() {\n    return {}\n  }\n\n  static get NAME() {\n    throw new Error('You have to implement the static method \"NAME\", for each component!')\n  }\n\n  _getConfig(config) {\n    config = this._mergeConfigObj(config)\n    config = this._configAfterMerge(config)\n    this._typeCheckConfig(config)\n    return config\n  }\n\n  _configAfterMerge(config) {\n    return config\n  }\n\n  _mergeConfigObj(config, element) {\n    const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n    return {\n      ...this.constructor.Default,\n      ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n      ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n      ...(typeof config === 'object' ? config : {})\n    }\n  }\n\n  _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n    for (const property of Object.keys(configTypes)) {\n      const expectedTypes = configTypes[property]\n      const value = config[property]\n      const valueType = isElement(value) ? 'element' : toType(value)\n\n      if (!new RegExp(expectedTypes).test(valueType)) {\n        throw new TypeError(\n          `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n        )\n      }\n    }\n  }\n}\n\nexport default Config\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/focustrap.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n  autofocus: true,\n  trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n  autofocus: 'boolean',\n  trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n  constructor(config) {\n    super()\n    this._config = this._getConfig(config)\n    this._isActive = false\n    this._lastTabNavDirection = null\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  activate() {\n    if (this._isActive) {\n      return\n    }\n\n    if (this._config.autofocus) {\n      this._config.trapElement.focus()\n    }\n\n    EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n    EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n    EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n    this._isActive = true\n  }\n\n  deactivate() {\n    if (!this._isActive) {\n      return\n    }\n\n    this._isActive = false\n    EventHandler.off(document, EVENT_KEY)\n  }\n\n  // Private\n  _handleFocusin(event) {\n    const { trapElement } = this._config\n\n    if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n      return\n    }\n\n    const elements = SelectorEngine.focusableChildren(trapElement)\n\n    if (elements.length === 0) {\n      trapElement.focus()\n    } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n      elements[elements.length - 1].focus()\n    } else {\n      elements[0].focus()\n    }\n  }\n\n  _handleKeydown(event) {\n    if (event.key !== TAB_KEY) {\n      return\n    }\n\n    this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n  }\n}\n\nexport default FocusTrap\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/index.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n  if (object === null || object === undefined) {\n    return `${object}`\n  }\n\n  return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n  do {\n    prefix += Math.floor(Math.random() * MAX_UID)\n  } while (document.getElementById(prefix))\n\n  return prefix\n}\n\nconst getSelector = element => {\n  let selector = element.getAttribute('data-bs-target')\n\n  if (!selector || selector === '#') {\n    let hrefAttribute = element.getAttribute('href')\n\n    // The only valid content that could double as a selector are IDs or classes,\n    // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n    // `document.querySelector` will rightfully complain it is invalid.\n    // See https://github.com/twbs/bootstrap/issues/32273\n    if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n      return null\n    }\n\n    // Just in case some CMS puts out a full URL with the anchor appended\n    if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n      hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n    }\n\n    selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n  }\n\n  return selector\n}\n\nconst getSelectorFromElement = element => {\n  const selector = getSelector(element)\n\n  if (selector) {\n    return document.querySelector(selector) ? selector : null\n  }\n\n  return null\n}\n\nconst getElementFromSelector = element => {\n  const selector = getSelector(element)\n\n  return selector ? document.querySelector(selector) : null\n}\n\nconst getTransitionDurationFromElement = element => {\n  if (!element) {\n    return 0\n  }\n\n  // Get transition-duration of the element\n  let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n  const floatTransitionDuration = Number.parseFloat(transitionDuration)\n  const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n  // Return 0 if element or transition duration is not found\n  if (!floatTransitionDuration && !floatTransitionDelay) {\n    return 0\n  }\n\n  // If multiple durations are defined, take the first\n  transitionDuration = transitionDuration.split(',')[0]\n  transitionDelay = transitionDelay.split(',')[0]\n\n  return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n  element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n  if (!object || typeof object !== 'object') {\n    return false\n  }\n\n  if (typeof object.jquery !== 'undefined') {\n    object = object[0]\n  }\n\n  return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n  // it's a jQuery object or a node element\n  if (isElement(object)) {\n    return object.jquery ? object[0] : object\n  }\n\n  if (typeof object === 'string' && object.length > 0) {\n    return document.querySelector(object)\n  }\n\n  return null\n}\n\nconst isVisible = element => {\n  if (!isElement(element) || element.getClientRects().length === 0) {\n    return false\n  }\n\n  const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n  // Handle `details` element as its content may falsie appear visible when it is closed\n  const closedDetails = element.closest('details:not([open])')\n\n  if (!closedDetails) {\n    return elementIsVisible\n  }\n\n  if (closedDetails !== element) {\n    const summary = element.closest('summary')\n    if (summary && summary.parentNode !== closedDetails) {\n      return false\n    }\n\n    if (summary === null) {\n      return false\n    }\n  }\n\n  return elementIsVisible\n}\n\nconst isDisabled = element => {\n  if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n    return true\n  }\n\n  if (element.classList.contains('disabled')) {\n    return true\n  }\n\n  if (typeof element.disabled !== 'undefined') {\n    return element.disabled\n  }\n\n  return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n  if (!document.documentElement.attachShadow) {\n    return null\n  }\n\n  // Can find the shadow root otherwise it'll return the document\n  if (typeof element.getRootNode === 'function') {\n    const root = element.getRootNode()\n    return root instanceof ShadowRoot ? root : null\n  }\n\n  if (element instanceof ShadowRoot) {\n    return element\n  }\n\n  // when we don't find a shadow root\n  if (!element.parentNode) {\n    return null\n  }\n\n  return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n  element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n  if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n    return window.jQuery\n  }\n\n  return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n  if (document.readyState === 'loading') {\n    // add listener on the first call when the document is in loading state\n    if (!DOMContentLoadedCallbacks.length) {\n      document.addEventListener('DOMContentLoaded', () => {\n        for (const callback of DOMContentLoadedCallbacks) {\n          callback()\n        }\n      })\n    }\n\n    DOMContentLoadedCallbacks.push(callback)\n  } else {\n    callback()\n  }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n  onDOMContentLoaded(() => {\n    const $ = getjQuery()\n    /* istanbul ignore if */\n    if ($) {\n      const name = plugin.NAME\n      const JQUERY_NO_CONFLICT = $.fn[name]\n      $.fn[name] = plugin.jQueryInterface\n      $.fn[name].Constructor = plugin\n      $.fn[name].noConflict = () => {\n        $.fn[name] = JQUERY_NO_CONFLICT\n        return plugin.jQueryInterface\n      }\n    }\n  })\n}\n\nconst execute = callback => {\n  if (typeof callback === 'function') {\n    callback()\n  }\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n  if (!waitForTransition) {\n    execute(callback)\n    return\n  }\n\n  const durationPadding = 5\n  const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n  let called = false\n\n  const handler = ({ target }) => {\n    if (target !== transitionElement) {\n      return\n    }\n\n    called = true\n    transitionElement.removeEventListener(TRANSITION_END, handler)\n    execute(callback)\n  }\n\n  transitionElement.addEventListener(TRANSITION_END, handler)\n  setTimeout(() => {\n    if (!called) {\n      triggerTransitionEnd(transitionElement)\n    }\n  }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list    The list of elements\n * @param activeElement   The active element\n * @param shouldGetNext   Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n  const listLength = list.length\n  let index = list.indexOf(activeElement)\n\n  // if the element does not exist in the list return an element\n  // depending on the direction and if cycle is allowed\n  if (index === -1) {\n    return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n  }\n\n  index += shouldGetNext ? 1 : -1\n\n  if (isCycleAllowed) {\n    index = (index + listLength) % listLength\n  }\n\n  return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n  defineJQueryPlugin,\n  execute,\n  executeAfterTransition,\n  findShadowRoot,\n  getElement,\n  getElementFromSelector,\n  getjQuery,\n  getNextActiveElement,\n  getSelectorFromElement,\n  getTransitionDurationFromElement,\n  getUID,\n  isDisabled,\n  isElement,\n  isRTL,\n  isVisible,\n  noop,\n  onDOMContentLoaded,\n  reflow,\n  triggerTransitionEnd,\n  toType\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/sanitizer.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst uriAttributes = new Set([\n  'background',\n  'cite',\n  'href',\n  'itemtype',\n  'longdesc',\n  'poster',\n  'src',\n  'xlink:href'\n])\n\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\n/**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i\n\n/**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n  const attributeName = attribute.nodeName.toLowerCase()\n\n  if (allowedAttributeList.includes(attributeName)) {\n    if (uriAttributes.has(attributeName)) {\n      return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue))\n    }\n\n    return true\n  }\n\n  // Check if a regular expression validates the attribute.\n  return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n    .some(regex => regex.test(attributeName))\n}\n\nexport const DefaultAllowlist = {\n  // Global attributes allowed on any supplied element below.\n  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n  a: ['target', 'href', 'title', 'rel'],\n  area: [],\n  b: [],\n  br: [],\n  col: [],\n  code: [],\n  div: [],\n  em: [],\n  hr: [],\n  h1: [],\n  h2: [],\n  h3: [],\n  h4: [],\n  h5: [],\n  h6: [],\n  i: [],\n  img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n  li: [],\n  ol: [],\n  p: [],\n  pre: [],\n  s: [],\n  small: [],\n  span: [],\n  sub: [],\n  sup: [],\n  strong: [],\n  u: [],\n  ul: []\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n  if (!unsafeHtml.length) {\n    return unsafeHtml\n  }\n\n  if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n    return sanitizeFunction(unsafeHtml)\n  }\n\n  const domParser = new window.DOMParser()\n  const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n  const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n  for (const element of elements) {\n    const elementName = element.nodeName.toLowerCase()\n\n    if (!Object.keys(allowList).includes(elementName)) {\n      element.remove()\n\n      continue\n    }\n\n    const attributeList = [].concat(...element.attributes)\n    const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n    for (const attribute of attributeList) {\n      if (!allowedAttribute(attribute, allowedAttributes)) {\n        element.removeAttribute(attribute.nodeName)\n      }\n    }\n  }\n\n  return createdDocument.body.innerHTML\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/scrollbar.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine'\nimport Manipulator from '../dom/manipulator'\nimport { isElement } from './index'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n  constructor() {\n    this._element = document.body\n  }\n\n  // Public\n  getWidth() {\n    // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n    const documentWidth = document.documentElement.clientWidth\n    return Math.abs(window.innerWidth - documentWidth)\n  }\n\n  hide() {\n    const width = this.getWidth()\n    this._disableOverFlow()\n    // give padding to element to balance the hidden scrollbar width\n    this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n    // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n    this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n    this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n  }\n\n  reset() {\n    this._resetElementAttributes(this._element, 'overflow')\n    this._resetElementAttributes(this._element, PROPERTY_PADDING)\n    this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n    this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n  }\n\n  isOverflowing() {\n    return this.getWidth() > 0\n  }\n\n  // Private\n  _disableOverFlow() {\n    this._saveInitialAttribute(this._element, 'overflow')\n    this._element.style.overflow = 'hidden'\n  }\n\n  _setElementAttributes(selector, styleProperty, callback) {\n    const scrollbarWidth = this.getWidth()\n    const manipulationCallBack = element => {\n      if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n        return\n      }\n\n      this._saveInitialAttribute(element, styleProperty)\n      const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n      element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n    }\n\n    this._applyManipulationCallback(selector, manipulationCallBack)\n  }\n\n  _saveInitialAttribute(element, styleProperty) {\n    const actualValue = element.style.getPropertyValue(styleProperty)\n    if (actualValue) {\n      Manipulator.setDataAttribute(element, styleProperty, actualValue)\n    }\n  }\n\n  _resetElementAttributes(selector, styleProperty) {\n    const manipulationCallBack = element => {\n      const value = Manipulator.getDataAttribute(element, styleProperty)\n      // We only want to remove the property if the value is `null`; the value can also be zero\n      if (value === null) {\n        element.style.removeProperty(styleProperty)\n        return\n      }\n\n      Manipulator.removeDataAttribute(element, styleProperty)\n      element.style.setProperty(styleProperty, value)\n    }\n\n    this._applyManipulationCallback(selector, manipulationCallBack)\n  }\n\n  _applyManipulationCallback(selector, callBack) {\n    if (isElement(selector)) {\n      callBack(selector)\n      return\n    }\n\n    for (const sel of SelectorEngine.find(selector, this._element)) {\n      callBack(sel)\n    }\n  }\n}\n\nexport default ScrollBarHelper\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/swipe.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Config from './config'\nimport EventHandler from '../dom/event-handler'\nimport { execute } from './index'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n  endCallback: null,\n  leftCallback: null,\n  rightCallback: null\n}\n\nconst DefaultType = {\n  endCallback: '(function|null)',\n  leftCallback: '(function|null)',\n  rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n  constructor(element, config) {\n    super()\n    this._element = element\n\n    if (!element || !Swipe.isSupported()) {\n      return\n    }\n\n    this._config = this._getConfig(config)\n    this._deltaX = 0\n    this._supportPointerEvents = Boolean(window.PointerEvent)\n    this._initEvents()\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  dispose() {\n    EventHandler.off(this._element, EVENT_KEY)\n  }\n\n  // Private\n  _start(event) {\n    if (!this._supportPointerEvents) {\n      this._deltaX = event.touches[0].clientX\n\n      return\n    }\n\n    if (this._eventIsPointerPenTouch(event)) {\n      this._deltaX = event.clientX\n    }\n  }\n\n  _end(event) {\n    if (this._eventIsPointerPenTouch(event)) {\n      this._deltaX = event.clientX - this._deltaX\n    }\n\n    this._handleSwipe()\n    execute(this._config.endCallback)\n  }\n\n  _move(event) {\n    this._deltaX = event.touches && event.touches.length > 1 ?\n      0 :\n      event.touches[0].clientX - this._deltaX\n  }\n\n  _handleSwipe() {\n    const absDeltaX = Math.abs(this._deltaX)\n\n    if (absDeltaX <= SWIPE_THRESHOLD) {\n      return\n    }\n\n    const direction = absDeltaX / this._deltaX\n\n    this._deltaX = 0\n\n    if (!direction) {\n      return\n    }\n\n    execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n  }\n\n  _initEvents() {\n    if (this._supportPointerEvents) {\n      EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n      EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n      this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n    } else {\n      EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n      EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n      EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n    }\n  }\n\n  _eventIsPointerPenTouch(event) {\n    return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n  }\n\n  // Static\n  static isSupported() {\n    return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n  }\n}\n\nexport default Swipe\n"
  },
  {
    "path": "src/common/bootstrap/js/src/util/template-factory.js",
    "content": "/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.3): util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer'\nimport { getElement, isElement } from '../util/index'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n  allowList: DefaultAllowlist,\n  content: {}, // { selector : text ,  selector2 : text2 , }\n  extraClass: '',\n  html: false,\n  sanitize: true,\n  sanitizeFn: null,\n  template: '<div></div>'\n}\n\nconst DefaultType = {\n  allowList: 'object',\n  content: 'object',\n  extraClass: '(string|function)',\n  html: 'boolean',\n  sanitize: 'boolean',\n  sanitizeFn: '(null|function)',\n  template: 'string'\n}\n\nconst DefaultContentType = {\n  entry: '(string|element|function|null)',\n  selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n  constructor(config) {\n    super()\n    this._config = this._getConfig(config)\n  }\n\n  // Getters\n  static get Default() {\n    return Default\n  }\n\n  static get DefaultType() {\n    return DefaultType\n  }\n\n  static get NAME() {\n    return NAME\n  }\n\n  // Public\n  getContent() {\n    return Object.values(this._config.content)\n      .map(config => this._resolvePossibleFunction(config))\n      .filter(Boolean)\n  }\n\n  hasContent() {\n    return this.getContent().length > 0\n  }\n\n  changeContent(content) {\n    this._checkContent(content)\n    this._config.content = { ...this._config.content, ...content }\n    return this\n  }\n\n  toHtml() {\n    const templateWrapper = document.createElement('div')\n    templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n    for (const [selector, text] of Object.entries(this._config.content)) {\n      this._setContent(templateWrapper, text, selector)\n    }\n\n    const template = templateWrapper.children[0]\n    const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n    if (extraClass) {\n      template.classList.add(...extraClass.split(' '))\n    }\n\n    return template\n  }\n\n  // Private\n  _typeCheckConfig(config) {\n    super._typeCheckConfig(config)\n    this._checkContent(config.content)\n  }\n\n  _checkContent(arg) {\n    for (const [selector, content] of Object.entries(arg)) {\n      super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n    }\n  }\n\n  _setContent(template, content, selector) {\n    const templateElement = SelectorEngine.findOne(selector, template)\n\n    if (!templateElement) {\n      return\n    }\n\n    content = this._resolvePossibleFunction(content)\n\n    if (!content) {\n      templateElement.remove()\n      return\n    }\n\n    if (isElement(content)) {\n      this._putElementInTemplate(getElement(content), templateElement)\n      return\n    }\n\n    if (this._config.html) {\n      templateElement.innerHTML = this._maybeSanitize(content)\n      return\n    }\n\n    templateElement.textContent = content\n  }\n\n  _maybeSanitize(arg) {\n    return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n  }\n\n  _resolvePossibleFunction(arg) {\n    return typeof arg === 'function' ? arg(this) : arg\n  }\n\n  _putElementInTemplate(element, templateElement) {\n    if (this._config.html) {\n      templateElement.innerHTML = ''\n      templateElement.append(element)\n      return\n    }\n\n    templateElement.textContent = element.textContent\n  }\n}\n\nexport default TemplateFactory\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/README.md",
    "content": "## How does Bootstrap's test suite work?\n\nBootstrap uses [Jasmine](https://jasmine.github.io/). Each plugin has a file dedicated to its tests in `tests/unit/<plugin-name>.spec.js`.\n\n- `visual/` contains \"visual\" tests which are run interactively in real browsers and require manual verification by humans.\n\nTo run the unit test suite via [Karma](https://karma-runner.github.io/), run `npm run js-test`.\nTo run the unit test suite via [Karma](https://karma-runner.github.io/) and debug, run `npm run js-debug`.\n\n## How do I add a new unit test?\n\n1. Locate and open the file dedicated to the plugin which you need to add tests to (`tests/unit/<plugin-name>.spec.js`).\n2. Review the [Jasmine API Documentation](https://jasmine.github.io/pages/docs_home.html) and use the existing tests as references for how to structure your new tests.\n3. Write the necessary unit test(s) for the new or revised functionality.\n4. Run `npm run js-test` to see the results of your newly-added test(s).\n\n**Note:** Your new unit tests should fail before your changes are applied to the plugin, and should pass after your changes are applied to the plugin.\n\n## What should a unit test look like?\n\n- Each test should have a unique name clearly stating what unit is being tested.\n- Each test should be in the corresponding `describe`.\n- Each test should test only one unit per test, although one test can include several assertions. Create multiple tests for multiple units of functionality.\n- Each test should use [`expect`](https://jasmine.github.io/api/edge/matchers.html) to ensure something is expected.\n- Each test should follow the project's [JavaScript Code Guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md#js)\n\n## Code coverage\n\nCurrently we're aiming for at least 90% test coverage for our code. To ensure your changes meet or exceed this limit, run `npm run js-test-karma` and open the file in `js/coverage/lcov-report/index.html` to see the code coverage for each plugin. See more details when you select a plugin and ensure your change is fully covered by unit tests.\n\n### Example tests\n\n```js\n// Synchronous test\ndescribe('getInstance', () => {\n  it('should return null if there is no instance', () => {\n    // Make assertion\n    expect(Tab.getInstance(fixtureEl)).toBeNull()\n  })\n\n  it('should return this instance', () => {\n    fixtureEl.innerHTML = '<div></div>'\n\n    const divEl = fixtureEl.querySelector('div')\n    const tab = new Tab(divEl)\n\n    // Make assertion\n    expect(Tab.getInstance(divEl)).toEqual(tab)\n  })\n})\n\n// Asynchronous test\nit('should show a tooltip without the animation', () => {\n  return new Promise(resolve => {\n    fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n    const tooltipEl = fixtureEl.querySelector('a')\n    const tooltip = new Tooltip(tooltipEl, {\n      animation: false\n    })\n\n    tooltipEl.addEventListener('shown.bs.tooltip', () => {\n      const tip = document.querySelector('.tooltip')\n\n      expect(tip).not.toBeNull()\n      expect(tip.classList.contains('fade')).toEqual(false)\n      resolve()\n    })\n\n    tooltip.show()\n  })\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/browsers.js",
    "content": "/* eslint-env node */\n/* eslint-disable camelcase */\n\nconst browsers = {\n  safariMac: {\n    base: 'BrowserStack',\n    os: 'OS X',\n    os_version: 'Catalina',\n    browser: 'Safari',\n    browser_version: 'latest'\n  },\n  chromeMac: {\n    base: 'BrowserStack',\n    os: 'OS X',\n    os_version: 'Catalina',\n    browser: 'Chrome',\n    browser_version: 'latest'\n  },\n  firefoxMac: {\n    base: 'BrowserStack',\n    os: 'OS X',\n    os_version: 'Catalina',\n    browser: 'Firefox',\n    browser_version: 'latest'\n  },\n  chromeWin10: {\n    base: 'BrowserStack',\n    os: 'Windows',\n    os_version: '10',\n    browser: 'Chrome',\n    browser_version: '60'\n  },\n  firefoxWin10: {\n    base: 'BrowserStack',\n    os: 'Windows',\n    os_version: '10',\n    browser: 'Firefox',\n    browser_version: '60'\n  },\n  chromeWin10Latest: {\n    base: 'BrowserStack',\n    os: 'Windows',\n    os_version: '10',\n    browser: 'Chrome',\n    browser_version: 'latest'\n  },\n  firefoxWin10Latest: {\n    base: 'BrowserStack',\n    os: 'Windows',\n    os_version: '10',\n    browser: 'Firefox',\n    browser_version: 'latest'\n  },\n  iphone7: {\n    base: 'BrowserStack',\n    os: 'ios',\n    os_version: '12.0',\n    device: 'iPhone 7',\n    real_mobile: true\n  },\n  iphone12: {\n    base: 'BrowserStack',\n    os: 'ios',\n    os_version: '14.0',\n    device: 'iPhone 12',\n    real_mobile: true\n  },\n  pixel2: {\n    base: 'BrowserStack',\n    os: 'android',\n    os_version: '8.0',\n    device: 'Google Pixel 2',\n    real_mobile: true\n  }\n}\n\nmodule.exports = {\n  browsers\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/helpers/fixture.js",
    "content": "const fixtureId = 'fixture'\n\nexport const getFixture = () => {\n  let fixtureElement = document.getElementById(fixtureId)\n\n  if (!fixtureElement) {\n    fixtureElement = document.createElement('div')\n    fixtureElement.setAttribute('id', fixtureId)\n    fixtureElement.style.position = 'absolute'\n    fixtureElement.style.top = '-10000px'\n    fixtureElement.style.left = '-10000px'\n    fixtureElement.style.width = '10000px'\n    fixtureElement.style.height = '10000px'\n    document.body.append(fixtureElement)\n  }\n\n  return fixtureElement\n}\n\nexport const clearFixture = () => {\n  const fixtureElement = getFixture()\n\n  fixtureElement.innerHTML = ''\n}\n\nexport const createEvent = (eventName, parameters = {}) => {\n  return new Event(eventName, parameters)\n}\n\nexport const jQueryMock = {\n  elements: undefined,\n  fn: {},\n  each(fn) {\n    for (const element of this.elements) {\n      fn.call(element)\n    }\n  }\n}\n\nexport const clearBodyAndDocument = () => {\n  const attributes = ['data-bs-padding-right', 'style']\n\n  for (const attribute of attributes) {\n    document.documentElement.removeAttribute(attribute)\n    document.body.removeAttribute(attribute)\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/integration/bundle-modularity.js",
    "content": "import Tooltip from '../../dist/tooltip'\nimport '../../dist/carousel'\n\nwindow.addEventListener('load', () => {\n  [].concat(...document.querySelectorAll('[data-bs-toggle=\"tooltip\"]'))\n    .map(tooltipNode => new Tooltip(tooltipNode))\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/integration/bundle.js",
    "content": "import { Tooltip } from '../../../dist/js/bootstrap.esm.js'\n\nwindow.addEventListener('load', () => {\n  [].concat(...document.querySelectorAll('[data-bs-toggle=\"tooltip\"]'))\n    .map(tooltipNode => new Tooltip(tooltipNode))\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/integration/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n    <!-- Bootstrap CSS -->\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n\n    <title>Hello, world!</title>\n  </head>\n  <body>\n    <div class=\"container py-4\">\n      <h1>Hello, world!</h1>\n\n      <div class=\"mt-5\">\n        <button type=\"button\" class=\"btn btn-secondary mb-3\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"Tooltip on top\">\n          Tooltip on top\n        </button>\n\n        <div id=\"carouselExampleIndicators\" class=\"carousel slide mt-2\" data-bs-ride=\"carousel\">\n          <div class=\"carousel-indicators\">\n            <button type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide-to=\"0\" aria-label=\"Slide 1\"></button>\n            <button type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide-to=\"1\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 2\"></button>\n            <button type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n          </div>\n\n          <div class=\"carousel-inner\">\n            <div class=\"carousel-item\">\n              <img class=\"d-block w-100\" alt=\"First slide\" src=\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22800%22%20height%3D%22400%22%20preserveAspectRatio%3D%22none%22%20viewBox%3D%220%200%20800%20400%22%3E%3Cpath%20fill%3D%22%23777%22%20d%3D%22M0%200h800v400H0z%22%2F%3E%3Ctext%20x%3D%22285.922%22%20y%3D%22217.7%22%20fill%3D%22%23555%22%20font-family%3D%22Helvetica%2Cmonospace%22%20font-size%3D%2240pt%22%20font-weight%3D%22400%22%3EFirst%20slide%3C%2Ftext%3E%3C%2Fsvg%3E\">\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>First slide label</h5>\n                <p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>\n              </div>\n            </div>\n            <div class=\"carousel-item active\">\n              <img class=\"d-block w-100\" alt=\"Second slide\" src=\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22800%22%20height%3D%22400%22%20preserveAspectRatio%3D%22none%22%20viewBox%3D%220%200%20800%20400%22%3E%3Cpath%20fill%3D%22%23777%22%20d%3D%22M0%200h800v400H0z%22%2F%3E%3Ctext%20x%3D%22285.922%22%20y%3D%22217.7%22%20fill%3D%22%23555%22%20font-family%3D%22Helvetica%2Cmonospace%22%20font-size%3D%2240pt%22%20font-weight%3D%22400%22%3ESecond%20slide%3C%2Ftext%3E%3C%2Fsvg%3E\">\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>Second slide label</h5>\n                <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\n              </div>\n            </div>\n            <div class=\"carousel-item\">\n              <img class=\"d-block w-100\" alt=\"Third slide\" src=\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22800%22%20height%3D%22400%22%20preserveAspectRatio%3D%22none%22%20viewBox%3D%220%200%20800%20400%22%3E%3Cpath%20fill%3D%22%23777%22%20d%3D%22M0%200h800v400H0z%22%2F%3E%3Ctext%20x%3D%22285.922%22%20y%3D%22217.7%22%20fill%3D%22%23555%22%20font-family%3D%22Helvetica%2Cmonospace%22%20font-size%3D%2240pt%22%20font-weight%3D%22400%22%3EThird%20slide%3C%2Ftext%3E%3C%2Fsvg%3E\">\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>Third slide label</h5>\n                <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur.</p>\n              </div>\n            </div>\n          </div>\n\n          <a class=\"carousel-control-prev\" href=\"#carouselExampleIndicators\" role=\"button\" data-bs-slide=\"prev\">\n            <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n            <span class=\"visually-hidden\">Previous</span>\n          </a>\n          <a class=\"carousel-control-next\" href=\"#carouselExampleIndicators\" role=\"button\" data-bs-slide=\"next\">\n            <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n            <span class=\"visually-hidden\">Next</span>\n          </a>\n        </div>\n      </div>\n    </div>\n\n    <script src=\"../../coverage/bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/integration/rollup.bundle-modularity.js",
    "content": "/* eslint-env node */\n\nconst commonjs = require('@rollup/plugin-commonjs')\nconst configRollup = require('./rollup.bundle')\n\nconst config = {\n  ...configRollup,\n  input: 'js/tests/integration/bundle-modularity.js',\n  output: {\n    file: 'js/coverage/bundle-modularity.js',\n    format: 'iife'\n  }\n}\n\nconfig.plugins.unshift(commonjs())\n\nmodule.exports = config\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/integration/rollup.bundle.js",
    "content": "/* eslint-env node */\n\nconst { babel } = require('@rollup/plugin-babel')\nconst { nodeResolve } = require('@rollup/plugin-node-resolve')\nconst replace = require('@rollup/plugin-replace')\n\nmodule.exports = {\n  input: 'js/tests/integration/bundle.js',\n  output: {\n    file: 'js/coverage/bundle.js',\n    format: 'iife'\n  },\n  plugins: [\n    replace({\n      'process.env.NODE_ENV': '\"production\"',\n      preventAssignment: true\n    }),\n    nodeResolve(),\n    babel({\n      exclude: 'node_modules/**',\n      babelHelpers: 'bundled'\n    })\n  ]\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/karma.conf.js",
    "content": "/* eslint-env node */\n\n'use strict'\n\nconst path = require('node:path')\nconst ip = require('ip')\nconst { babel } = require('@rollup/plugin-babel')\nconst istanbul = require('rollup-plugin-istanbul')\nconst { nodeResolve } = require('@rollup/plugin-node-resolve')\nconst replace = require('@rollup/plugin-replace')\nconst { browsers } = require('./browsers')\n\nconst ENV = process.env\nconst BROWSERSTACK = Boolean(ENV.BROWSERSTACK)\nconst DEBUG = Boolean(ENV.DEBUG)\nconst JQUERY_TEST = Boolean(ENV.JQUERY)\n\nconst frameworks = [\n  'jasmine'\n]\n\nconst plugins = [\n  'karma-jasmine',\n  'karma-rollup-preprocessor'\n]\n\nconst reporters = ['dots']\n\nconst detectBrowsers = {\n  usePhantomJS: false,\n  postDetection(availableBrowser) {\n    // On CI just use Chrome\n    if (ENV.CI === true) {\n      return ['ChromeHeadless']\n    }\n\n    if (availableBrowser.includes('Chrome')) {\n      return DEBUG ? ['Chrome'] : ['ChromeHeadless']\n    }\n\n    if (availableBrowser.includes('Chromium')) {\n      return DEBUG ? ['Chromium'] : ['ChromiumHeadless']\n    }\n\n    if (availableBrowser.includes('Firefox')) {\n      return DEBUG ? ['Firefox'] : ['FirefoxHeadless']\n    }\n\n    throw new Error('Please install Chrome, Chromium or Firefox')\n  }\n}\n\nconst config = {\n  basePath: '../..',\n  port: 9876,\n  colors: true,\n  autoWatch: false,\n  singleRun: true,\n  concurrency: Number.POSITIVE_INFINITY,\n  client: {\n    clearContext: false\n  },\n  files: [\n    'node_modules/hammer-simulator/index.js',\n    {\n      pattern: 'js/tests/unit/**/!(jquery).spec.js',\n      watched: !BROWSERSTACK\n    }\n  ],\n  preprocessors: {\n    'js/tests/unit/**/*.spec.js': ['rollup']\n  },\n  rollupPreprocessor: {\n    plugins: [\n      replace({\n        'process.env.NODE_ENV': '\"dev\"',\n        preventAssignment: true\n      }),\n      istanbul({\n        exclude: [\n          'node_modules/**',\n          'js/tests/unit/**/*.spec.js',\n          'js/tests/helpers/**/*.js'\n        ]\n      }),\n      babel({\n        // Only transpile our source code\n        exclude: 'node_modules/**',\n        // Inline the required helpers in each file\n        babelHelpers: 'inline'\n      }),\n      nodeResolve()\n    ],\n    output: {\n      format: 'iife',\n      name: 'bootstrapTest',\n      sourcemap: 'inline',\n      generatedCode: 'es2015'\n    }\n  }\n}\n\nif (BROWSERSTACK) {\n  config.hostname = ip.address()\n  config.browserStack = {\n    username: ENV.BROWSER_STACK_USERNAME,\n    accessKey: ENV.BROWSER_STACK_ACCESS_KEY,\n    build: `bootstrap-${ENV.GITHUB_SHA ? ENV.GITHUB_SHA.slice(0, 7) + '-' : ''}${new Date().toISOString()}`,\n    project: 'Bootstrap',\n    retryLimit: 2\n  }\n  plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter')\n  config.customLaunchers = browsers\n  config.browsers = Object.keys(browsers)\n  reporters.push('BrowserStack', 'kjhtml')\n} else if (JQUERY_TEST) {\n  frameworks.push('detectBrowsers')\n  plugins.push(\n    'karma-chrome-launcher',\n    'karma-firefox-launcher',\n    'karma-detect-browsers'\n  )\n  config.detectBrowsers = detectBrowsers\n  config.files = [\n    'node_modules/jquery/dist/jquery.slim.min.js',\n    {\n      pattern: 'js/tests/unit/jquery.spec.js',\n      watched: false\n    }\n  ]\n} else {\n  frameworks.push('detectBrowsers')\n  plugins.push(\n    'karma-chrome-launcher',\n    'karma-firefox-launcher',\n    'karma-detect-browsers',\n    'karma-coverage-istanbul-reporter'\n  )\n  reporters.push('coverage-istanbul')\n  config.detectBrowsers = detectBrowsers\n  config.coverageIstanbulReporter = {\n    dir: path.resolve(__dirname, '../coverage/'),\n    reports: ['lcov', 'text-summary'],\n    thresholds: {\n      emitWarning: false,\n      global: {\n        statements: 90,\n        branches: 89,\n        functions: 90,\n        lines: 90\n      }\n    }\n  }\n\n  if (DEBUG) {\n    config.hostname = ip.address()\n    plugins.push('karma-jasmine-html-reporter')\n    reporters.push('kjhtml')\n    config.singleRun = false\n    config.autoWatch = true\n  }\n}\n\nconfig.frameworks = frameworks\nconfig.plugins = plugins\nconfig.reporters = reporters\n\nmodule.exports = karmaConfig => {\n  config.logLevel = karmaConfig.LOG_ERROR\n  karmaConfig.set(config)\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/.eslintrc.json",
    "content": "{\n  \"extends\": [\n    \"../../../.eslintrc.json\"\n  ],\n  \"env\": {\n    \"jasmine\": true\n  },\n  \"rules\": {\n    \"unicorn/consistent-function-scoping\": \"off\",\n    \"unicorn/no-useless-undefined\": \"off\",\n    \"unicorn/prefer-add-event-listener\": \"off\"\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/alert.spec.js",
    "content": "import Alert from '../../src/alert'\nimport { getTransitionDurationFromElement } from '../../src/util/index'\nimport { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Alert', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  it('should take care of element either passed as a CSS selector or DOM element', () => {\n    fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n    const alertEl = fixtureEl.querySelector('.alert')\n    const alertBySelector = new Alert('.alert')\n    const alertByElement = new Alert(alertEl)\n\n    expect(alertBySelector._element).toEqual(alertEl)\n    expect(alertByElement._element).toEqual(alertEl)\n  })\n\n  it('should return version', () => {\n    expect(Alert.VERSION).toEqual(jasmine.any(String))\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Alert.DATA_KEY).toEqual('bs.alert')\n    })\n  })\n\n  describe('data-api', () => {\n    it('should close an alert without instantiating it manually', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"alert\">',\n        '  <button type=\"button\" data-bs-dismiss=\"alert\">x</button>',\n        '</div>'\n      ].join('')\n\n      const button = document.querySelector('button')\n\n      button.click()\n      expect(document.querySelectorAll('.alert')).toHaveSize(0)\n    })\n\n    it('should close an alert without instantiating it manually with the parent selector', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"alert\">',\n        '  <button type=\"button\" data-bs-target=\".alert\" data-bs-dismiss=\"alert\">x</button>',\n        '</div>'\n      ].join('')\n\n      const button = document.querySelector('button')\n\n      button.click()\n      expect(document.querySelectorAll('.alert')).toHaveSize(0)\n    })\n  })\n\n  describe('close', () => {\n    it('should close an alert', () => {\n      return new Promise(resolve => {\n        const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)\n        fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n        const alertEl = document.querySelector('.alert')\n        const alert = new Alert(alertEl)\n\n        alertEl.addEventListener('closed.bs.alert', () => {\n          expect(document.querySelectorAll('.alert')).toHaveSize(0)\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        alert.close()\n      })\n    })\n\n    it('should close alert with fade class', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"alert fade\"></div>'\n\n        const alertEl = document.querySelector('.alert')\n        const alert = new Alert(alertEl)\n\n        alertEl.addEventListener('transitionend', () => {\n          expect().nothing()\n        })\n\n        alertEl.addEventListener('closed.bs.alert', () => {\n          expect(document.querySelectorAll('.alert')).toHaveSize(0)\n          resolve()\n        })\n\n        alert.close()\n      })\n    })\n\n    it('should not remove alert if close event is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n        const getAlert = () => document.querySelector('.alert')\n        const alertEl = getAlert()\n        const alert = new Alert(alertEl)\n\n        alertEl.addEventListener('close.bs.alert', event => {\n          event.preventDefault()\n          setTimeout(() => {\n            expect(getAlert()).not.toBeNull()\n            resolve()\n          }, 10)\n        })\n\n        alertEl.addEventListener('closed.bs.alert', () => {\n          reject(new Error('should not fire closed event'))\n        })\n\n        alert.close()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose an alert', () => {\n      fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n      const alertEl = document.querySelector('.alert')\n      const alert = new Alert(alertEl)\n\n      expect(Alert.getInstance(alertEl)).not.toBeNull()\n\n      alert.dispose()\n\n      expect(Alert.getInstance(alertEl)).toBeNull()\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should handle config passed and toggle existing alert', () => {\n      fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n      const alertEl = fixtureEl.querySelector('.alert')\n      const alert = new Alert(alertEl)\n\n      const spy = spyOn(alert, 'close')\n\n      jQueryMock.fn.alert = Alert.jQueryInterface\n      jQueryMock.elements = [alertEl]\n\n      jQueryMock.fn.alert.call(jQueryMock, 'close')\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should create new alert instance and call close', () => {\n      fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n      const alertEl = fixtureEl.querySelector('.alert')\n\n      jQueryMock.fn.alert = Alert.jQueryInterface\n      jQueryMock.elements = [alertEl]\n\n      expect(Alert.getInstance(alertEl)).toBeNull()\n      jQueryMock.fn.alert.call(jQueryMock, 'close')\n\n      expect(fixtureEl.querySelector('.alert')).toBeNull()\n    })\n\n    it('should just create an alert instance without calling close', () => {\n      fixtureEl.innerHTML = '<div class=\"alert\"></div>'\n\n      const alertEl = fixtureEl.querySelector('.alert')\n\n      jQueryMock.fn.alert = Alert.jQueryInterface\n      jQueryMock.elements = [alertEl]\n\n      jQueryMock.fn.alert.call(jQueryMock)\n\n      expect(Alert.getInstance(alertEl)).not.toBeNull()\n      expect(fixtureEl.querySelector('.alert')).not.toBeNull()\n    })\n\n    it('should throw an error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.alert = Alert.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.alert.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should throw an error on protected method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = '_getConfig'\n\n      jQueryMock.fn.alert = Alert.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.alert.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return alert instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const alert = new Alert(div)\n\n      expect(Alert.getInstance(div)).toEqual(alert)\n      expect(Alert.getInstance(div)).toBeInstanceOf(Alert)\n    })\n\n    it('should return null when there is no alert instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Alert.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return alert instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const alert = new Alert(div)\n\n      expect(Alert.getOrCreateInstance(div)).toEqual(alert)\n      expect(Alert.getInstance(div)).toEqual(Alert.getOrCreateInstance(div, {}))\n      expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)\n    })\n\n    it('should return new instance when there is no alert instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Alert.getInstance(div)).toBeNull()\n      expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/base-component.spec.js",
    "content": "import BaseComponent from '../../src/base-component'\nimport { clearFixture, getFixture } from '../helpers/fixture'\nimport EventHandler from '../../src/dom/event-handler'\nimport { noop } from '../../src/util'\n\nclass DummyClass extends BaseComponent {\n  constructor(element) {\n    super(element)\n\n    EventHandler.on(this._element, `click${DummyClass.EVENT_KEY}`, noop)\n  }\n\n  static get NAME() {\n    return 'dummy'\n  }\n}\n\ndescribe('Base Component', () => {\n  let fixtureEl\n  const name = 'dummy'\n  let element\n  let instance\n  const createInstance = () => {\n    fixtureEl.innerHTML = '<div id=\"foo\"></div>'\n    element = fixtureEl.querySelector('#foo')\n    instance = new DummyClass(element)\n  }\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('Static Methods', () => {\n    describe('VERSION', () => {\n      it('should return version', () => {\n        expect(DummyClass.VERSION).toEqual(jasmine.any(String))\n      })\n    })\n\n    describe('DATA_KEY', () => {\n      it('should return plugin data key', () => {\n        expect(DummyClass.DATA_KEY).toEqual(`bs.${name}`)\n      })\n    })\n\n    describe('NAME', () => {\n      it('should throw an Error if it is not initialized', () => {\n        expect(() => {\n          // eslint-disable-next-line no-unused-expressions\n          BaseComponent.NAME\n        }).toThrowError(Error)\n      })\n\n      it('should return plugin NAME', () => {\n        expect(DummyClass.NAME).toEqual(name)\n      })\n    })\n\n    describe('EVENT_KEY', () => {\n      it('should return plugin event key', () => {\n        expect(DummyClass.EVENT_KEY).toEqual(`.bs.${name}`)\n      })\n    })\n  })\n\n  describe('Public Methods', () => {\n    describe('constructor', () => {\n      it('should accept element, either passed as a CSS selector or DOM element', () => {\n        fixtureEl.innerHTML = [\n          '<div id=\"foo\"></div>',\n          '<div id=\"bar\"></div>'\n        ].join('')\n\n        const el = fixtureEl.querySelector('#foo')\n        const elInstance = new DummyClass(el)\n        const selectorInstance = new DummyClass('#bar')\n\n        expect(elInstance._element).toEqual(el)\n        expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar'))\n      })\n\n      it('should not initialize and add element record to Data (caching), if argument `element` is not an HTML element', () => {\n        fixtureEl.innerHTML = ''\n\n        const el = fixtureEl.querySelector('#foo')\n        const elInstance = new DummyClass(el)\n        const selectorInstance = new DummyClass('#bar')\n\n        expect(elInstance._element).not.toBeDefined()\n        expect(selectorInstance._element).not.toBeDefined()\n      })\n    })\n\n    describe('dispose', () => {\n      it('should dispose an component', () => {\n        createInstance()\n        expect(DummyClass.getInstance(element)).not.toBeNull()\n\n        instance.dispose()\n\n        expect(DummyClass.getInstance(element)).toBeNull()\n        expect(instance._element).toBeNull()\n      })\n\n      it('should de-register element event listeners', () => {\n        createInstance()\n        const spy = spyOn(EventHandler, 'off')\n\n        instance.dispose()\n\n        expect(spy).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY)\n      })\n    })\n\n    describe('getInstance', () => {\n      it('should return an instance', () => {\n        createInstance()\n\n        expect(DummyClass.getInstance(element)).toEqual(instance)\n        expect(DummyClass.getInstance(element)).toBeInstanceOf(DummyClass)\n      })\n\n      it('should accept element, either passed as a CSS selector, jQuery element, or DOM element', () => {\n        createInstance()\n\n        expect(DummyClass.getInstance('#foo')).toEqual(instance)\n        expect(DummyClass.getInstance(element)).toEqual(instance)\n\n        const fakejQueryObject = {\n          0: element,\n          jquery: 'foo'\n        }\n\n        expect(DummyClass.getInstance(fakejQueryObject)).toEqual(instance)\n      })\n\n      it('should return null when there is no instance', () => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const div = fixtureEl.querySelector('div')\n\n        expect(DummyClass.getInstance(div)).toBeNull()\n      })\n    })\n\n    describe('getOrCreateInstance', () => {\n      it('should return an instance', () => {\n        createInstance()\n\n        expect(DummyClass.getOrCreateInstance(element)).toEqual(instance)\n        expect(DummyClass.getInstance(element)).toEqual(DummyClass.getOrCreateInstance(element, {}))\n        expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)\n      })\n\n      it('should return new instance when there is no alert instance', () => {\n        fixtureEl.innerHTML = '<div id=\"foo\"></div>'\n        element = fixtureEl.querySelector('#foo')\n\n        expect(DummyClass.getInstance(element)).toBeNull()\n        expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/button.spec.js",
    "content": "import Button from '../../src/button'\nimport { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Button', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  it('should take care of element either passed as a CSS selector or DOM element', () => {\n    fixtureEl.innerHTML = '<button data-bs-toggle=\"button\">Placeholder</button>'\n    const buttonEl = fixtureEl.querySelector('[data-bs-toggle=\"button\"]')\n    const buttonBySelector = new Button('[data-bs-toggle=\"button\"]')\n    const buttonByElement = new Button(buttonEl)\n\n    expect(buttonBySelector._element).toEqual(buttonEl)\n    expect(buttonByElement._element).toEqual(buttonEl)\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Button.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Button.DATA_KEY).toEqual('bs.button')\n    })\n  })\n\n  describe('data-api', () => {\n    it('should toggle active class on click', () => {\n      fixtureEl.innerHTML = [\n        '<button class=\"btn\" data-bs-toggle=\"button\">btn</button>',\n        '<button class=\"btn testParent\" data-bs-toggle=\"button\"><div class=\"test\"></div></button>'\n      ].join('')\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelector('.test')\n      const btnTestParent = fixtureEl.querySelector('.testParent')\n\n      expect(btn).not.toHaveClass('active')\n\n      btn.click()\n\n      expect(btn).toHaveClass('active')\n\n      btn.click()\n\n      expect(btn).not.toHaveClass('active')\n\n      divTest.click()\n\n      expect(btnTestParent).toHaveClass('active')\n    })\n  })\n\n  describe('toggle', () => {\n    it('should toggle aria-pressed', () => {\n      fixtureEl.innerHTML = '<button class=\"btn\" data-bs-toggle=\"button\" aria-pressed=\"false\"></button>'\n\n      const btnEl = fixtureEl.querySelector('.btn')\n      const button = new Button(btnEl)\n\n      expect(btnEl.getAttribute('aria-pressed')).toEqual('false')\n      expect(btnEl).not.toHaveClass('active')\n\n      button.toggle()\n\n      expect(btnEl.getAttribute('aria-pressed')).toEqual('true')\n      expect(btnEl).toHaveClass('active')\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose a button', () => {\n      fixtureEl.innerHTML = '<button class=\"btn\" data-bs-toggle=\"button\"></button>'\n\n      const btnEl = fixtureEl.querySelector('.btn')\n      const button = new Button(btnEl)\n\n      expect(Button.getInstance(btnEl)).not.toBeNull()\n\n      button.dispose()\n\n      expect(Button.getInstance(btnEl)).toBeNull()\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should handle config passed and toggle existing button', () => {\n      fixtureEl.innerHTML = '<button class=\"btn\" data-bs-toggle=\"button\"></button>'\n\n      const btnEl = fixtureEl.querySelector('.btn')\n      const button = new Button(btnEl)\n\n      const spy = spyOn(button, 'toggle')\n\n      jQueryMock.fn.button = Button.jQueryInterface\n      jQueryMock.elements = [btnEl]\n\n      jQueryMock.fn.button.call(jQueryMock, 'toggle')\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should create new button instance and call toggle', () => {\n      fixtureEl.innerHTML = '<button class=\"btn\" data-bs-toggle=\"button\"></button>'\n\n      const btnEl = fixtureEl.querySelector('.btn')\n\n      jQueryMock.fn.button = Button.jQueryInterface\n      jQueryMock.elements = [btnEl]\n\n      jQueryMock.fn.button.call(jQueryMock, 'toggle')\n\n      expect(Button.getInstance(btnEl)).not.toBeNull()\n      expect(btnEl).toHaveClass('active')\n    })\n\n    it('should just create a button instance without calling toggle', () => {\n      fixtureEl.innerHTML = '<button class=\"btn\" data-bs-toggle=\"button\"></button>'\n\n      const btnEl = fixtureEl.querySelector('.btn')\n\n      jQueryMock.fn.button = Button.jQueryInterface\n      jQueryMock.elements = [btnEl]\n\n      jQueryMock.fn.button.call(jQueryMock)\n\n      expect(Button.getInstance(btnEl)).not.toBeNull()\n      expect(btnEl).not.toHaveClass('active')\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return button instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const button = new Button(div)\n\n      expect(Button.getInstance(div)).toEqual(button)\n      expect(Button.getInstance(div)).toBeInstanceOf(Button)\n    })\n\n    it('should return null when there is no button instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Button.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return button instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const button = new Button(div)\n\n      expect(Button.getOrCreateInstance(div)).toEqual(button)\n      expect(Button.getInstance(div)).toEqual(Button.getOrCreateInstance(div, {}))\n      expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)\n    })\n\n    it('should return new instance when there is no button instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Button.getInstance(div)).toBeNull()\n      expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/carousel.spec.js",
    "content": "import Carousel from '../../src/carousel'\nimport EventHandler from '../../src/dom/event-handler'\nimport { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\nimport { isRTL, noop } from '../../src/util/index'\nimport Swipe from '../../src/util/swipe'\n\ndescribe('Carousel', () => {\n  const { Simulator, PointerEvent } = window\n  const originWinPointerEvent = PointerEvent\n  const supportPointerEvent = Boolean(PointerEvent)\n\n  const cssStyleCarousel = '.carousel.pointer-event { touch-action: none; }'\n\n  const stylesCarousel = document.createElement('style')\n  stylesCarousel.type = 'text/css'\n  stylesCarousel.append(document.createTextNode(cssStyleCarousel))\n\n  const clearPointerEvents = () => {\n    window.PointerEvent = null\n  }\n\n  const restorePointerEvents = () => {\n    window.PointerEvent = originWinPointerEvent\n  }\n\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Carousel.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Carousel.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Carousel.DATA_KEY).toEqual('bs.carousel')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = '<div id=\"myCarousel\" class=\"carousel slide\"></div>'\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const carouselBySelector = new Carousel('#myCarousel')\n      const carouselByElement = new Carousel(carouselEl)\n\n      expect(carouselBySelector._element).toEqual(carouselEl)\n      expect(carouselByElement._element).toEqual(carouselEl)\n    })\n\n    it('should start cycling if `ride`===`carousel`', () => {\n      fixtureEl.innerHTML = '<div id=\"myCarousel\" class=\"carousel slide\" data-bs-ride=\"carousel\"></div>'\n\n      const carousel = new Carousel('#myCarousel')\n      expect(carousel._interval).not.toBeNull()\n    })\n\n    it('should not start cycling if `ride`!==`carousel`', () => {\n      fixtureEl.innerHTML = '<div id=\"myCarousel\" class=\"carousel slide\" data-bs-ride=\"true\"></div>'\n\n      const carousel = new Carousel('#myCarousel')\n      expect(carousel._interval).toBeNull()\n    })\n\n    it('should go to next item if right arrow key is pressed', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {\n          keyboard: true\n        })\n\n        const spy = spyOn(carousel, '_keydown').and.callThrough()\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'ArrowRight'\n\n        carouselEl.dispatchEvent(keydown)\n      })\n    })\n\n    it('should ignore keyboard events if data-bs-keyboard=false', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\" data-bs-keyboard=\"false\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const spy = spyOn(EventHandler, 'trigger').and.callThrough()\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      // eslint-disable-next-line no-new\n      new Carousel('#myCarousel')\n      expect(spy).not.toHaveBeenCalledWith(carouselEl, 'keydown.bs.carousel', jasmine.any(Function))\n    })\n\n    it('should ignore mouse events if data-bs-pause=false', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\" data-bs-pause=\"false\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const spy = spyOn(EventHandler, 'trigger').and.callThrough()\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      // eslint-disable-next-line no-new\n      new Carousel('#myCarousel')\n      expect(spy).not.toHaveBeenCalledWith(carouselEl, 'hover.bs.carousel', jasmine.any(Function))\n    })\n\n    it('should go to previous item if left arrow key is pressed', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item1\" class=\"carousel-item\">item 1</div>',\n          '    <div class=\"carousel-item active\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {\n          keyboard: true\n        })\n\n        const spy = spyOn(carousel, '_keydown').and.callThrough()\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'ArrowLeft'\n\n        carouselEl.dispatchEvent(keydown)\n      })\n    })\n\n    it('should not prevent keydown if key is not ARROW_LEFT or ARROW_RIGHT', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {\n          keyboard: true\n        })\n\n        const spy = spyOn(carousel, '_keydown').and.callThrough()\n\n        carouselEl.addEventListener('keydown', event => {\n          expect(spy).toHaveBeenCalled()\n          expect(event.defaultPrevented).toBeFalse()\n          resolve()\n        })\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'ArrowDown'\n\n        carouselEl.dispatchEvent(keydown)\n      })\n    })\n\n    it('should ignore keyboard events within <input>s and <textarea>s', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">',\n        '      <input type=\"text\">',\n        '      <textarea></textarea>',\n        '    </div>',\n        '    <div class=\"carousel-item\"></div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const input = fixtureEl.querySelector('input')\n      const textarea = fixtureEl.querySelector('textarea')\n      const carousel = new Carousel(carouselEl, {\n        keyboard: true\n      })\n\n      const spyKeydown = spyOn(carousel, '_keydown').and.callThrough()\n      const spySlide = spyOn(carousel, '_slide')\n\n      const keydown = createEvent('keydown', { bubbles: true, cancelable: true })\n      keydown.key = 'ArrowRight'\n      Object.defineProperty(keydown, 'target', {\n        value: input,\n        writable: true,\n        configurable: true\n      })\n\n      input.dispatchEvent(keydown)\n\n      expect(spyKeydown).toHaveBeenCalled()\n      expect(spySlide).not.toHaveBeenCalled()\n\n      spyKeydown.calls.reset()\n      spySlide.calls.reset()\n\n      Object.defineProperty(keydown, 'target', {\n        value: textarea\n      })\n      textarea.dispatchEvent(keydown)\n\n      expect(spyKeydown).toHaveBeenCalled()\n      expect(spySlide).not.toHaveBeenCalled()\n    })\n\n    it('should not slide if arrow key is pressed and carousel is sliding', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      carousel._isSliding = true\n\n      for (const key of ['ArrowLeft', 'ArrowRight']) {\n        const keydown = createEvent('keydown')\n        keydown.key = key\n\n        carouselEl.dispatchEvent(keydown)\n      }\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should wrap around from end to start when wrap option is true', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"one\" class=\"carousel-item active\"></div>',\n          '    <div id=\"two\" class=\"carousel-item\"></div>',\n          '    <div id=\"three\" class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, { wrap: true })\n        const getActiveId = () => carouselEl.querySelector('.carousel-item.active').getAttribute('id')\n\n        carouselEl.addEventListener('slid.bs.carousel', event => {\n          const activeId = getActiveId()\n\n          if (activeId === 'two') {\n            carousel.next()\n            return\n          }\n\n          if (activeId === 'three') {\n            carousel.next()\n            return\n          }\n\n          if (activeId === 'one') {\n            // carousel wrapped around and slid from 3rd to 1st slide\n            expect(activeId).toEqual('one')\n            expect(event.from + 1).toEqual(3)\n            resolve()\n          }\n        })\n\n        carousel.next()\n      })\n    })\n\n    it('should stay at the start when the prev method is called and wrap is false', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"one\" class=\"carousel-item active\"></div>',\n          '    <div id=\"two\" class=\"carousel-item\"></div>',\n          '    <div id=\"three\" class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const firstElement = fixtureEl.querySelector('#one')\n        const carousel = new Carousel(carouselEl, { wrap: false })\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          reject(new Error('carousel slid when it should not have slid'))\n        })\n\n        carousel.prev()\n\n        setTimeout(() => {\n          expect(firstElement).toHaveClass('active')\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not add touch event listeners if touch = false', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n\n      const spy = spyOn(Carousel.prototype, '_addTouchEventListeners')\n\n      const carousel = new Carousel(carouselEl, {\n        touch: false\n      })\n\n      expect(spy).not.toHaveBeenCalled()\n      expect(carousel._swipeHelper).toBeNull()\n    })\n\n    it('should not add touch event listeners if touch supported = false', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      spyOn(Swipe, 'isSupported').and.returnValue(false)\n\n      const carousel = new Carousel(carouselEl)\n      EventHandler.off(carouselEl, Carousel.EVENT_KEY)\n\n      const spy = spyOn(carousel, '_addTouchEventListeners')\n\n      carousel._addEventListeners()\n\n      expect(spy).not.toHaveBeenCalled()\n      expect(carousel._swipeHelper).toBeNull()\n    })\n\n    it('should add touch event listeners by default', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n\n      spyOn(Carousel.prototype, '_addTouchEventListeners')\n\n      // Headless browser does not support touch events, so need to fake it\n      // to test that touch events are add properly.\n      document.documentElement.ontouchstart = noop\n      const carousel = new Carousel(carouselEl)\n\n      expect(carousel._addTouchEventListeners).toHaveBeenCalled()\n    })\n\n    it('should allow swiperight and call _slide (prev) with pointer events', () => {\n      return new Promise(resolve => {\n        if (!supportPointerEvent) {\n          expect().nothing()\n          resolve()\n          return\n        }\n\n        document.documentElement.ontouchstart = noop\n        document.head.append(stylesCarousel)\n        Simulator.setType('pointer')\n\n        fixtureEl.innerHTML = [\n          '<div class=\"carousel\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item\" class=\"carousel-item\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '    <div class=\"carousel-item active\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const item = fixtureEl.querySelector('#item')\n        const carousel = new Carousel(carouselEl)\n\n        const spy = spyOn(carousel, '_slide').and.callThrough()\n\n        carouselEl.addEventListener('slid.bs.carousel', event => {\n          expect(item).toHaveClass('active')\n          expect(spy).toHaveBeenCalledWith('prev')\n          expect(event.direction).toEqual('right')\n          stylesCarousel.remove()\n          delete document.documentElement.ontouchstart\n          resolve()\n        })\n\n        Simulator.gestures.swipe(carouselEl, {\n          deltaX: 300,\n          deltaY: 0\n        })\n      })\n    })\n\n    it('should allow swipeleft and call next with pointer events', () => {\n      return new Promise(resolve => {\n        if (!supportPointerEvent) {\n          expect().nothing()\n          resolve()\n          return\n        }\n\n        document.documentElement.ontouchstart = noop\n        document.head.append(stylesCarousel)\n        Simulator.setType('pointer')\n\n        fixtureEl.innerHTML = [\n          '<div class=\"carousel\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item\" class=\"carousel-item active\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '    <div class=\"carousel-item\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const item = fixtureEl.querySelector('#item')\n        const carousel = new Carousel(carouselEl)\n\n        const spy = spyOn(carousel, '_slide').and.callThrough()\n\n        carouselEl.addEventListener('slid.bs.carousel', event => {\n          expect(item).not.toHaveClass('active')\n          expect(spy).toHaveBeenCalledWith('next')\n          expect(event.direction).toEqual('left')\n          stylesCarousel.remove()\n          delete document.documentElement.ontouchstart\n          resolve()\n        })\n\n        Simulator.gestures.swipe(carouselEl, {\n          pos: [300, 10],\n          deltaX: -300,\n          deltaY: 0\n        })\n      })\n    })\n\n    it('should allow swiperight and call _slide (prev) with touch events', () => {\n      return new Promise(resolve => {\n        Simulator.setType('touch')\n        clearPointerEvents()\n        document.documentElement.ontouchstart = noop\n\n        fixtureEl.innerHTML = [\n          '<div class=\"carousel\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item\" class=\"carousel-item\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '    <div class=\"carousel-item active\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const item = fixtureEl.querySelector('#item')\n        const carousel = new Carousel(carouselEl)\n\n        const spy = spyOn(carousel, '_slide').and.callThrough()\n\n        carouselEl.addEventListener('slid.bs.carousel', event => {\n          expect(item).toHaveClass('active')\n          expect(spy).toHaveBeenCalledWith('prev')\n          expect(event.direction).toEqual('right')\n          delete document.documentElement.ontouchstart\n          restorePointerEvents()\n          resolve()\n        })\n\n        Simulator.gestures.swipe(carouselEl, {\n          deltaX: 300,\n          deltaY: 0\n        })\n      })\n    })\n\n    it('should allow swipeleft and call _slide (next) with touch events', () => {\n      return new Promise(resolve => {\n        Simulator.setType('touch')\n        clearPointerEvents()\n        document.documentElement.ontouchstart = noop\n\n        fixtureEl.innerHTML = [\n          '<div class=\"carousel\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item\" class=\"carousel-item active\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '    <div class=\"carousel-item\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const item = fixtureEl.querySelector('#item')\n        const carousel = new Carousel(carouselEl)\n\n        const spy = spyOn(carousel, '_slide').and.callThrough()\n\n        carouselEl.addEventListener('slid.bs.carousel', event => {\n          expect(item).not.toHaveClass('active')\n          expect(spy).toHaveBeenCalledWith('next')\n          expect(event.direction).toEqual('left')\n          delete document.documentElement.ontouchstart\n          restorePointerEvents()\n          resolve()\n        })\n\n        Simulator.gestures.swipe(carouselEl, {\n          pos: [300, 10],\n          deltaX: -300,\n          deltaY: 0\n        })\n      })\n    })\n\n    it('should not slide when swiping and carousel is sliding', () => {\n      return new Promise(resolve => {\n        Simulator.setType('touch')\n        clearPointerEvents()\n        document.documentElement.ontouchstart = noop\n\n        fixtureEl.innerHTML = [\n          '<div class=\"carousel\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item\" class=\"carousel-item active\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '    <div class=\"carousel-item\">',\n          '      <img alt=\"\">',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const carousel = new Carousel(carouselEl)\n        carousel._isSliding = true\n\n        const spy = spyOn(EventHandler, 'trigger')\n\n        Simulator.gestures.swipe(carouselEl, {\n          deltaX: 300,\n          deltaY: 0\n        })\n\n        Simulator.gestures.swipe(carouselEl, {\n          pos: [300, 10],\n          deltaX: -300,\n          deltaY: 0\n        })\n\n        setTimeout(() => {\n          expect(spy).not.toHaveBeenCalled()\n          delete document.documentElement.ontouchstart\n          restorePointerEvents()\n          resolve()\n        }, 300)\n      })\n    })\n\n    it('should not allow pinch with touch events', () => {\n      return new Promise(resolve => {\n        Simulator.setType('touch')\n        clearPointerEvents()\n        document.documentElement.ontouchstart = noop\n\n        fixtureEl.innerHTML = '<div class=\"carousel\"></div>'\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const carousel = new Carousel(carouselEl)\n\n        Simulator.gestures.swipe(carouselEl, {\n          pos: [300, 10],\n          deltaX: -300,\n          deltaY: 0,\n          touches: 2\n        }, () => {\n          restorePointerEvents()\n          delete document.documentElement.ontouchstart\n          expect(carousel._swipeHelper._deltaX).toEqual(0)\n          resolve()\n        })\n      })\n    })\n\n    it('should call pause method on mouse over with pause equal to hover', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"carousel\"></div>'\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const carousel = new Carousel(carouselEl)\n\n        const spy = spyOn(carousel, 'pause')\n\n        const mouseOverEvent = createEvent('mouseover')\n        carouselEl.dispatchEvent(mouseOverEvent)\n\n        setTimeout(() => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should call `maybeEnableCycle` on mouse out with pause equal to hover', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"carousel\" data-bs-ride=\"true\"></div>'\n\n        const carouselEl = fixtureEl.querySelector('.carousel')\n        const carousel = new Carousel(carouselEl)\n\n        const spyEnable = spyOn(carousel, '_maybeEnableCycle').and.callThrough()\n        const spyCycle = spyOn(carousel, 'cycle')\n\n        const mouseOutEvent = createEvent('mouseout')\n        carouselEl.dispatchEvent(mouseOutEvent)\n\n        setTimeout(() => {\n          expect(spyEnable).toHaveBeenCalled()\n          expect(spyCycle).toHaveBeenCalled()\n          resolve()\n        }, 10)\n      })\n    })\n  })\n\n  describe('next', () => {\n    it('should not slide if the carousel is sliding', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      carousel._isSliding = true\n      carousel.next()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should not fire slid when slide is prevented', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const carouselEl = fixtureEl.querySelector('div')\n        const carousel = new Carousel(carouselEl, {})\n        let slidEvent = false\n\n        const doneTest = () => {\n          setTimeout(() => {\n            expect(slidEvent).toBeFalse()\n            resolve()\n          }, 20)\n        }\n\n        carouselEl.addEventListener('slide.bs.carousel', event => {\n          event.preventDefault()\n          doneTest()\n        })\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          slidEvent = true\n        })\n\n        carousel.next()\n      })\n    })\n\n    it('should fire slide event with: direction, relatedTarget, from and to', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {})\n\n        const onSlide = event => {\n          expect(event.direction).toEqual('left')\n          expect(event.relatedTarget).toHaveClass('carousel-item')\n          expect(event.from).toEqual(0)\n          expect(event.to).toEqual(1)\n\n          carouselEl.removeEventListener('slide.bs.carousel', onSlide)\n          carouselEl.addEventListener('slide.bs.carousel', onSlide2)\n\n          carousel.prev()\n        }\n\n        const onSlide2 = event => {\n          expect(event.direction).toEqual('right')\n          resolve()\n        }\n\n        carouselEl.addEventListener('slide.bs.carousel', onSlide)\n        carousel.next()\n      })\n    })\n\n    it('should fire slid event with: direction, relatedTarget, from and to', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {})\n\n        const onSlid = event => {\n          expect(event.direction).toEqual('left')\n          expect(event.relatedTarget).toHaveClass('carousel-item')\n          expect(event.from).toEqual(0)\n          expect(event.to).toEqual(1)\n\n          carouselEl.removeEventListener('slid.bs.carousel', onSlid)\n          carouselEl.addEventListener('slid.bs.carousel', onSlid2)\n\n          carousel.prev()\n        }\n\n        const onSlid2 = event => {\n          expect(event.direction).toEqual('right')\n          resolve()\n        }\n\n        carouselEl.addEventListener('slid.bs.carousel', onSlid)\n        carousel.next()\n      })\n    })\n\n    it('should update the active element to the next item before sliding', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div id=\"secondItem\" class=\"carousel-item\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const secondItemEl = fixtureEl.querySelector('#secondItem')\n      const carousel = new Carousel(carouselEl)\n\n      carousel.next()\n\n      expect(carousel._activeElement).toEqual(secondItemEl)\n    })\n\n    it('should continue cycling if it was already', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\">item 2</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const carousel = new Carousel(carouselEl)\n      const spy = spyOn(carousel, 'cycle')\n\n      carousel.next()\n      expect(spy).not.toHaveBeenCalled()\n\n      carousel.cycle()\n      carousel.next()\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it('should update indicators if present', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-indicators\">',\n          '    <button type=\"button\" id=\"firstIndicator\" data-bs-target=\"myCarousel\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>',\n          '    <button type=\"button\" id=\"secondIndicator\" data-bs-target=\"myCarousel\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>',\n          '    <button type=\"button\" data-bs-target=\"myCarousel\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>',\n          '  </div>',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item\" data-bs-interval=\"7\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const firstIndicator = fixtureEl.querySelector('#firstIndicator')\n        const secondIndicator = fixtureEl.querySelector('#secondIndicator')\n        const carousel = new Carousel(carouselEl)\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          expect(firstIndicator).not.toHaveClass('active')\n          expect(firstIndicator.hasAttribute('aria-current')).toBeFalse()\n          expect(secondIndicator).toHaveClass('active')\n          expect(secondIndicator.getAttribute('aria-current')).toEqual('true')\n          resolve()\n        })\n\n        carousel.next()\n      })\n    })\n\n    it('should call next()/prev() instance methods when clicking the respective direction buttons', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"carousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carousel\" data-bs-slide=\"prev\"></button>',\n        '  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carousel\" data-bs-slide=\"next\"></button>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#carousel')\n      const prevBtnEl = fixtureEl.querySelector('.carousel-control-prev')\n      const nextBtnEl = fixtureEl.querySelector('.carousel-control-next')\n\n      const carousel = new Carousel(carouselEl)\n      const nextSpy = spyOn(carousel, 'next')\n      const prevSpy = spyOn(carousel, 'prev')\n      const spyEnable = spyOn(carousel, '_maybeEnableCycle')\n\n      nextBtnEl.click()\n      prevBtnEl.click()\n\n      expect(nextSpy).toHaveBeenCalled()\n      expect(prevSpy).toHaveBeenCalled()\n      expect(spyEnable).toHaveBeenCalled()\n    })\n  })\n\n  describe('nextWhenVisible', () => {\n    it('should not call next when the page is not visible', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"display: none;\">',\n        '  <div class=\"carousel\"></div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('.carousel')\n      const carousel = new Carousel(carouselEl)\n\n      const spy = spyOn(carousel, 'next')\n\n      carousel.nextWhenVisible()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('prev', () => {\n    it('should not slide if the carousel is sliding', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      carousel._isSliding = true\n      carousel.prev()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('pause', () => {\n    it('should trigger transitionend if the carousel have carousel-item-next or carousel-item-prev class, cause is sliding', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item carousel-item-next\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '  <div class=\"carousel-control-prev\"></div>',\n          '  <div class=\"carousel-control-next\"></div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl)\n        const spy = spyOn(carousel, '_clearInterval')\n\n        carouselEl.addEventListener('transitionend', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        carousel._slide('next')\n        carousel.pause()\n      })\n    })\n  })\n\n  describe('cycle', () => {\n    it('should set an interval', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '  <div class=\"carousel-control-prev\"></div>',\n        '  <div class=\"carousel-control-next\"></div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const carousel = new Carousel(carouselEl)\n\n      const spy = spyOn(window, 'setInterval').and.callThrough()\n\n      carousel.cycle()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should clear interval if there is one', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '  <div class=\"carousel-control-prev\"></div>',\n        '  <div class=\"carousel-control-next\"></div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const carousel = new Carousel(carouselEl)\n\n      carousel._interval = setInterval(noop, 10)\n\n      const spySet = spyOn(window, 'setInterval').and.callThrough()\n      const spyClear = spyOn(window, 'clearInterval').and.callThrough()\n\n      carousel.cycle()\n\n      expect(spySet).toHaveBeenCalled()\n      expect(spyClear).toHaveBeenCalled()\n    })\n\n    it('should get interval from data attribute on the active item element', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\" data-bs-interval=\"7\">item 1</div>',\n        '    <div id=\"secondItem\" class=\"carousel-item\" data-bs-interval=\"9385\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const secondItemEl = fixtureEl.querySelector('#secondItem')\n      const carousel = new Carousel(carouselEl, {\n        interval: 1814\n      })\n\n      expect(carousel._config.interval).toEqual(1814)\n\n      carousel.cycle()\n\n      expect(carousel._config.interval).toEqual(7)\n\n      carousel._activeElement = secondItemEl\n      carousel.cycle()\n\n      expect(carousel._config.interval).toEqual(9385)\n    })\n  })\n\n  describe('to', () => {\n    it('should go directly to the provided index', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div id=\"item1\" class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item\">item 2</div>',\n          '    <div id=\"item3\" class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {})\n\n        expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))\n\n        carousel.to(2)\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))\n          resolve()\n        })\n      })\n    })\n\n    it('should return to a previous slide if the provided index is lower than the current', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item\">item 1</div>',\n          '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n          '    <div id=\"item3\" class=\"carousel-item active\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {})\n\n        expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))\n\n        carousel.to(1)\n\n        carouselEl.addEventListener('slid.bs.carousel', () => {\n          expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))\n          resolve()\n        })\n      })\n    })\n\n    it('should do nothing if a wrong index is provided', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\" data-bs-interval=\"7\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const carousel = new Carousel(carouselEl, {})\n\n      const spy = spyOn(carousel, '_slide')\n\n      carousel.to(25)\n\n      expect(spy).not.toHaveBeenCalled()\n\n      spy.calls.reset()\n\n      carousel.to(-5)\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should not continue if the provided is the same compare to the current one', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\" data-bs-interval=\"7\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const carousel = new Carousel(carouselEl, {})\n\n      const spy = spyOn(carousel, '_slide')\n\n      carousel.to(0)\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should wait before performing to if a slide is sliding', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div class=\"carousel-item\" data-bs-interval=\"7\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const carouselEl = fixtureEl.querySelector('#myCarousel')\n        const carousel = new Carousel(carouselEl, {})\n\n        const spyOne = spyOn(EventHandler, 'one').and.callThrough()\n        const spySlide = spyOn(carousel, '_slide')\n\n        carousel._isSliding = true\n        carousel.to(1)\n\n        expect(spySlide).not.toHaveBeenCalled()\n        expect(spyOne).toHaveBeenCalled()\n\n        const spyTo = spyOn(carousel, 'to')\n\n        EventHandler.trigger(carouselEl, 'slid.bs.carousel')\n\n        setTimeout(() => {\n          expect(spyTo).toHaveBeenCalledWith(1)\n          resolve()\n        })\n      })\n    })\n  })\n\n  describe('rtl function', () => {\n    it('\"_directionToOrder\" and \"_orderToDirection\" must return the right results', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n\n      expect(carousel._directionToOrder('left')).toEqual('next')\n      expect(carousel._directionToOrder('right')).toEqual('prev')\n\n      expect(carousel._orderToDirection('next')).toEqual('left')\n      expect(carousel._orderToDirection('prev')).toEqual('right')\n    })\n\n    it('\"_directionToOrder\" and \"_orderToDirection\" must return the right results when rtl=true', () => {\n      document.documentElement.dir = 'rtl'\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n      expect(isRTL()).toBeTrue()\n\n      expect(carousel._directionToOrder('left')).toEqual('prev')\n      expect(carousel._directionToOrder('right')).toEqual('next')\n\n      expect(carousel._orderToDirection('next')).toEqual('right')\n      expect(carousel._orderToDirection('prev')).toEqual('left')\n      document.documentElement.dir = 'ltl'\n    })\n\n    it('\"_slide\" has to call _directionToOrder and \"_orderToDirection\"', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n\n      const spy = spyOn(carousel, '_orderToDirection').and.callThrough()\n\n      carousel._slide(carousel._directionToOrder('left'))\n      expect(spy).toHaveBeenCalledWith('next')\n\n      carousel._slide(carousel._directionToOrder('right'))\n      expect(spy).toHaveBeenCalledWith('prev')\n    })\n\n    it('\"_slide\" has to call \"_directionToOrder\" and \"_orderToDirection\" when rtl=true', () => {\n      document.documentElement.dir = 'rtl'\n      fixtureEl.innerHTML = '<div></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const carousel = new Carousel(carouselEl, {})\n      const spy = spyOn(carousel, '_orderToDirection').and.callThrough()\n\n      carousel._slide(carousel._directionToOrder('left'))\n      expect(spy).toHaveBeenCalledWith('prev')\n\n      carousel._slide(carousel._directionToOrder('right'))\n      expect(spy).toHaveBeenCalledWith('next')\n\n      document.documentElement.dir = 'ltl'\n    })\n  })\n\n  describe('dispose', () => {\n    it('should destroy a carousel', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\" data-bs-interval=\"7\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const carouselEl = fixtureEl.querySelector('#myCarousel')\n      const addEventSpy = spyOn(carouselEl, 'addEventListener').and.callThrough()\n      const removeEventSpy = spyOn(EventHandler, 'off').and.callThrough()\n\n      // Headless browser does not support touch events, so need to fake it\n      // to test that touch events are add/removed properly.\n      document.documentElement.ontouchstart = noop\n\n      const carousel = new Carousel(carouselEl)\n      const swipeHelperSpy = spyOn(carousel._swipeHelper, 'dispose').and.callThrough()\n\n      const expectedArgs = [\n        ['keydown', jasmine.any(Function), jasmine.any(Boolean)],\n        ['mouseover', jasmine.any(Function), jasmine.any(Boolean)],\n        ['mouseout', jasmine.any(Function), jasmine.any(Boolean)],\n        ...(carousel._swipeHelper._supportPointerEvents ?\n          [\n            ['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],\n            ['pointerup', jasmine.any(Function), jasmine.any(Boolean)]\n          ] :\n          [\n            ['touchstart', jasmine.any(Function), jasmine.any(Boolean)],\n            ['touchmove', jasmine.any(Function), jasmine.any(Boolean)],\n            ['touchend', jasmine.any(Function), jasmine.any(Boolean)]\n          ])\n      ]\n\n      expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)\n\n      carousel.dispose()\n\n      expect(carousel._swipeHelper).toBeNull()\n      expect(removeEventSpy).toHaveBeenCalledWith(carouselEl, Carousel.EVENT_KEY)\n      expect(swipeHelperSpy).toHaveBeenCalled()\n\n      delete document.documentElement.ontouchstart\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return carousel instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const carousel = new Carousel(div)\n\n      expect(Carousel.getInstance(div)).toEqual(carousel)\n      expect(Carousel.getInstance(div)).toBeInstanceOf(Carousel)\n    })\n\n    it('should return null when there is no carousel instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Carousel.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return carousel instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const carousel = new Carousel(div)\n\n      expect(Carousel.getOrCreateInstance(div)).toEqual(carousel)\n      expect(Carousel.getInstance(div)).toEqual(Carousel.getOrCreateInstance(div, {}))\n      expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel)\n    })\n\n    it('should return new instance when there is no carousel instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Carousel.getInstance(div)).toBeNull()\n      expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel)\n    })\n\n    it('should return new instance when there is no carousel instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Carousel.getInstance(div)).toBeNull()\n      const carousel = Carousel.getOrCreateInstance(div, {\n        interval: 1\n      })\n      expect(carousel).toBeInstanceOf(Carousel)\n\n      expect(carousel._config.interval).toEqual(1)\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const carousel = new Carousel(div, {\n        interval: 1\n      })\n      expect(Carousel.getInstance(div)).toEqual(carousel)\n\n      const carousel2 = Carousel.getOrCreateInstance(div, {\n        interval: 2\n      })\n      expect(carousel).toBeInstanceOf(Carousel)\n      expect(carousel2).toEqual(carousel)\n\n      expect(carousel2._config.interval).toEqual(1)\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a carousel', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.carousel = Carousel.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.carousel.call(jQueryMock)\n\n      expect(Carousel.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create a carousel', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const carousel = new Carousel(div)\n\n      jQueryMock.fn.carousel = Carousel.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.carousel.call(jQueryMock)\n\n      expect(Carousel.getInstance(div)).toEqual(carousel)\n    })\n\n    it('should call to if the config is a number', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const carousel = new Carousel(div)\n      const slideTo = 2\n\n      const spy = spyOn(carousel, 'to')\n\n      jQueryMock.fn.carousel = Carousel.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.carousel.call(jQueryMock, slideTo)\n\n      expect(spy).toHaveBeenCalledWith(slideTo)\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.carousel = Carousel.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.carousel.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('data-api', () => {\n    it('should init carousels with data-bs-ride=\"carousel\" on load', () => {\n      fixtureEl.innerHTML = '<div data-bs-ride=\"carousel\"></div>'\n\n      const carouselEl = fixtureEl.querySelector('div')\n      const loadEvent = createEvent('load')\n\n      window.dispatchEvent(loadEvent)\n      const carousel = Carousel.getInstance(carouselEl)\n      expect(carousel._interval).not.toBeNull()\n    })\n\n    it('should create carousel and go to the next slide on click (with real button controls)', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '  <button class=\"carousel-control-prev\" data-bs-target=\"#myCarousel\" type=\"button\" data-bs-slide=\"prev\"></button>',\n          '  <button id=\"next\" class=\"carousel-control-next\" data-bs-target=\"#myCarousel\" type=\"button\" data-bs-slide=\"next\"></button>',\n          '</div>'\n        ].join('')\n\n        const next = fixtureEl.querySelector('#next')\n        const item2 = fixtureEl.querySelector('#item2')\n\n        next.click()\n\n        setTimeout(() => {\n          expect(item2).toHaveClass('active')\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should create carousel and go to the next slide on click (using links as controls)', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '  <a class=\"carousel-control-prev\" href=\"#myCarousel\" role=\"button\" data-bs-slide=\"prev\"></a>',\n          '  <a id=\"next\" class=\"carousel-control-next\" href=\"#myCarousel\" role=\"button\" data-bs-slide=\"next\"></a>',\n          '</div>'\n        ].join('')\n\n        const next = fixtureEl.querySelector('#next')\n        const item2 = fixtureEl.querySelector('#item2')\n\n        next.click()\n\n        setTimeout(() => {\n          expect(item2).toHaveClass('active')\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should create carousel and go to the next slide on click with data-bs-slide-to', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"myCarousel\" class=\"carousel slide\" data-bs-ride=\"true\">',\n          '  <div class=\"carousel-inner\">',\n          '    <div class=\"carousel-item active\">item 1</div>',\n          '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n          '    <div class=\"carousel-item\">item 3</div>',\n          '  </div>',\n          '  <div id=\"next\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"1\"></div>',\n          '</div>'\n        ].join('')\n\n        const next = fixtureEl.querySelector('#next')\n        const item2 = fixtureEl.querySelector('#item2')\n\n        next.click()\n\n        setTimeout(() => {\n          expect(item2).toHaveClass('active')\n          expect(Carousel.getInstance('#myCarousel')._interval).not.toBeNull()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should do nothing if no selector on click on arrows', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"carousel slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div class=\"carousel-item\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '  <button class=\"carousel-control-prev\" data-bs-target=\"#myCarousel\" type=\"button\" data-bs-slide=\"prev\"></button>',\n        '  <button id=\"next\" class=\"carousel-control-next\" type=\"button\" data-bs-slide=\"next\"></button>',\n        '</div>'\n      ].join('')\n\n      const next = fixtureEl.querySelector('#next')\n\n      next.click()\n\n      expect().nothing()\n    })\n\n    it('should do nothing if no carousel class on click on arrows', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"myCarousel\" class=\"slide\">',\n        '  <div class=\"carousel-inner\">',\n        '    <div class=\"carousel-item active\">item 1</div>',\n        '    <div id=\"item2\" class=\"carousel-item\">item 2</div>',\n        '    <div class=\"carousel-item\">item 3</div>',\n        '  </div>',\n        '  <button class=\"carousel-control-prev\" data-bs-target=\"#myCarousel\" type=\"button\" data-bs-slide=\"prev\"></button>',\n        '  <button id=\"next\" class=\"carousel-control-next\" data-bs-target=\"#myCarousel\" type=\"button\" data-bs-slide=\"next\"></button>',\n        '</div>'\n      ].join('')\n\n      const next = fixtureEl.querySelector('#next')\n\n      next.click()\n\n      expect().nothing()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/collapse.spec.js",
    "content": "import Collapse from '../../src/collapse'\nimport EventHandler from '../../src/dom/event-handler'\nimport { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Collapse', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Collapse.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Collapse.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Collapse.DATA_KEY).toEqual('bs.collapse')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = '<div class=\"my-collapse\"></div>'\n\n      const collapseEl = fixtureEl.querySelector('div.my-collapse')\n      const collapseBySelector = new Collapse('div.my-collapse')\n      const collapseByElement = new Collapse(collapseEl)\n\n      expect(collapseBySelector._element).toEqual(collapseEl)\n      expect(collapseByElement._element).toEqual(collapseEl)\n    })\n\n    it('should allow jquery object in parent config', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"my-collapse\">',\n        '  <div class=\"item\">',\n        '    <a data-bs-toggle=\"collapse\" href=\"#\">Toggle item</a>',\n        '    <div class=\"collapse\">Lorem ipsum</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const collapseEl = fixtureEl.querySelector('div.collapse')\n      const myCollapseEl = fixtureEl.querySelector('.my-collapse')\n      const fakejQueryObject = {\n        0: myCollapseEl,\n        jquery: 'foo'\n      }\n      const collapse = new Collapse(collapseEl, {\n        parent: fakejQueryObject\n      })\n\n      expect(collapse._config.parent).toEqual(myCollapseEl)\n    })\n\n    it('should allow non jquery object in parent config', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"my-collapse\">',\n        '  <div class=\"item\">',\n        '    <a data-bs-toggle=\"collapse\" href=\"#\">Toggle item</a>',\n        '    <div class=\"collapse\">Lorem ipsum</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const collapseEl = fixtureEl.querySelector('div.collapse')\n      const myCollapseEl = fixtureEl.querySelector('.my-collapse')\n      const collapse = new Collapse(collapseEl, {\n        parent: myCollapseEl\n      })\n\n      expect(collapse._config.parent).toEqual(myCollapseEl)\n    })\n\n    it('should allow string selector in parent config', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"my-collapse\">',\n        '  <div class=\"item\">',\n        '    <a data-bs-toggle=\"collapse\" href=\"#\">Toggle item</a>',\n        '    <div class=\"collapse\">Lorem ipsum</div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const collapseEl = fixtureEl.querySelector('div.collapse')\n      const myCollapseEl = fixtureEl.querySelector('.my-collapse')\n      const collapse = new Collapse(collapseEl, {\n        parent: 'div.my-collapse'\n      })\n\n      expect(collapse._config.parent).toEqual(myCollapseEl)\n    })\n  })\n\n  describe('toggle', () => {\n    it('should call show method if show class is not present', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const collapseEl = fixtureEl.querySelector('div')\n      const collapse = new Collapse(collapseEl)\n\n      const spy = spyOn(collapse, 'show')\n\n      collapse.toggle()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should call hide method if show class is present', () => {\n      fixtureEl.innerHTML = '<div class=\"show\"></div>'\n\n      const collapseEl = fixtureEl.querySelector('.show')\n      const collapse = new Collapse(collapseEl, {\n        toggle: false\n      })\n\n      const spy = spyOn(collapse, 'hide')\n\n      collapse.toggle()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should find collapse children if they have collapse class too not only data-bs-parent', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"my-collapse\">',\n          '  <div class=\"item\">',\n          '    <a data-bs-toggle=\"collapse\" href=\"#\">Toggle item 1</a>',\n          '    <div id=\"collapse1\" class=\"collapse show\">Lorem ipsum 1</div>',\n          '  </div>',\n          '  <div class=\"item\">',\n          '    <a id=\"triggerCollapse2\" data-bs-toggle=\"collapse\" href=\"#\">Toggle item 2</a>',\n          '    <div id=\"collapse2\" class=\"collapse\">Lorem ipsum 2</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const parent = fixtureEl.querySelector('.my-collapse')\n        const collapseEl1 = fixtureEl.querySelector('#collapse1')\n        const collapseEl2 = fixtureEl.querySelector('#collapse2')\n\n        const collapseList = [].concat(...fixtureEl.querySelectorAll('.collapse'))\n          .map(el => new Collapse(el, {\n            parent,\n            toggle: false\n          }))\n\n        collapseEl2.addEventListener('shown.bs.collapse', () => {\n          expect(collapseEl2).toHaveClass('show')\n          expect(collapseEl1).not.toHaveClass('show')\n          resolve()\n        })\n\n        collapseList[1].toggle()\n      })\n    })\n  })\n\n  describe('show', () => {\n    it('should do nothing if is transitioning', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      const collapseEl = fixtureEl.querySelector('div')\n      const collapse = new Collapse(collapseEl, {\n        toggle: false\n      })\n\n      collapse._isTransitioning = true\n      collapse.show()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should do nothing if already shown', () => {\n      fixtureEl.innerHTML = '<div class=\"show\"></div>'\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      const collapseEl = fixtureEl.querySelector('div')\n      const collapse = new Collapse(collapseEl, {\n        toggle: false\n      })\n\n      collapse.show()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should show a collapsed element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"collapse\" style=\"height: 0px;\"></div>'\n\n        const collapseEl = fixtureEl.querySelector('div')\n        const collapse = new Collapse(collapseEl, {\n          toggle: false\n        })\n\n        collapseEl.addEventListener('show.bs.collapse', () => {\n          expect(collapseEl.style.height).toEqual('0px')\n        })\n        collapseEl.addEventListener('shown.bs.collapse', () => {\n          expect(collapseEl).toHaveClass('show')\n          expect(collapseEl.style.height).toEqual('')\n          resolve()\n        })\n\n        collapse.show()\n      })\n    })\n\n    it('should show a collapsed element on width', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"collapse collapse-horizontal\" style=\"width: 0px;\"></div>'\n\n        const collapseEl = fixtureEl.querySelector('div')\n        const collapse = new Collapse(collapseEl, {\n          toggle: false\n        })\n\n        collapseEl.addEventListener('show.bs.collapse', () => {\n          expect(collapseEl.style.width).toEqual('0px')\n        })\n        collapseEl.addEventListener('shown.bs.collapse', () => {\n          expect(collapseEl).toHaveClass('show')\n          expect(collapseEl.style.width).toEqual('')\n          resolve()\n        })\n\n        collapse.show()\n      })\n    })\n\n    it('should collapse only the first collapse', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"card\" id=\"accordion1\">',\n          '  <div id=\"collapse1\" class=\"collapse\"></div>',\n          '</div>',\n          '<div class=\"card\" id=\"accordion2\">',\n          '  <div id=\"collapse2\" class=\"collapse show\"></div>',\n          '</div>'\n        ].join('')\n\n        const el1 = fixtureEl.querySelector('#collapse1')\n        const el2 = fixtureEl.querySelector('#collapse2')\n        const collapse = new Collapse(el1, {\n          toggle: false\n        })\n\n        el1.addEventListener('shown.bs.collapse', () => {\n          expect(el1).toHaveClass('show')\n          expect(el2).toHaveClass('show')\n          resolve()\n        })\n\n        collapse.show()\n      })\n    })\n\n    it('should be able to handle toggling of other children siblings', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"parentGroup\" class=\"accordion\">',\n          '  <div id=\"parentHeader\" class=\"accordion-header\">',\n          '    <button data-bs-target=\"#parentContent\" data-bs-toggle=\"collapse\" role=\"button\" class=\"accordion-toggle\">Parent</button>',\n          '  </div>',\n          '  <div id=\"parentContent\" class=\"accordion-collapse collapse\" aria-labelledby=\"parentHeader\" data-bs-parent=\"#parentGroup\">',\n          '    <div class=\"accordion-body\">',\n          '      <div id=\"childGroup\" class=\"accordion\">',\n          '        <div class=\"accordion-item\">',\n          '          <div id=\"childHeader1\" class=\"accordion-header\">',\n          '            <button data-bs-target=\"#childContent1\" data-bs-toggle=\"collapse\" role=\"button\" class=\"accordion-toggle\">Child 1</button>',\n          '          </div>',\n          '          <div id=\"childContent1\" class=\"accordion-collapse collapse\" aria-labelledby=\"childHeader1\" data-bs-parent=\"#childGroup\">',\n          '            <div>content</div>',\n          '          </div>',\n          '        </div>',\n          '        <div class=\"accordion-item\">',\n          '          <div id=\"childHeader2\" class=\"accordion-header\">',\n          '            <button data-bs-target=\"#childContent2\" data-bs-toggle=\"collapse\" role=\"button\" class=\"accordion-toggle\">Child 2</button>',\n          '          </div>',\n          '          <div id=\"childContent2\" class=\"accordion-collapse collapse\" aria-labelledby=\"childHeader2\" data-bs-parent=\"#childGroup\">',\n          '            <div>content</div>',\n          '          </div>',\n          '        </div>',\n          '      </div>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const el = selector => fixtureEl.querySelector(selector)\n\n        const parentBtn = el('[data-bs-target=\"#parentContent\"]')\n        const childBtn1 = el('[data-bs-target=\"#childContent1\"]')\n        const childBtn2 = el('[data-bs-target=\"#childContent2\"]')\n\n        const parentCollapseEl = el('#parentContent')\n        const childCollapseEl1 = el('#childContent1')\n        const childCollapseEl2 = el('#childContent2')\n\n        parentCollapseEl.addEventListener('shown.bs.collapse', () => {\n          expect(parentCollapseEl).toHaveClass('show')\n          childBtn1.click()\n        })\n        childCollapseEl1.addEventListener('shown.bs.collapse', () => {\n          expect(childCollapseEl1).toHaveClass('show')\n          childBtn2.click()\n        })\n        childCollapseEl2.addEventListener('shown.bs.collapse', () => {\n          expect(childCollapseEl2).toHaveClass('show')\n          expect(childCollapseEl1).not.toHaveClass('show')\n          resolve()\n        })\n\n        parentBtn.click()\n      })\n    })\n\n    it('should not change tab tabpanels descendants on accordion', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"accordion\" id=\"accordionExample\">',\n          '  <div class=\"accordion-item\">',\n          '    <h2 class=\"accordion-header\" id=\"headingOne\">',\n          '      <button class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">',\n          '        Accordion Item #1',\n          '      </button>',\n          '    </h2>',\n          '    <div id=\"collapseOne\" class=\"accordion-collapse collapse show\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionExample\">',\n          '      <div class=\"accordion-body\">',\n          '        <nav>',\n          '          <div class=\"nav nav-tabs\" id=\"nav-tab\" role=\"tablist\">',\n          '            <button class=\"nav-link active\" id=\"nav-home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-home\" type=\"button\" role=\"tab\" aria-controls=\"nav-home\" aria-selected=\"true\">Home</button>',\n          '            <button class=\"nav-link\" id=\"nav-profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-profile\" type=\"button\" role=\"tab\" aria-controls=\"nav-profile\" aria-selected=\"false\">Profile</button>',\n          '          </div>',\n          '        </nav>',\n          '        <div class=\"tab-content\" id=\"nav-tabContent\">',\n          '          <div class=\"tab-pane fade show active\" id=\"nav-home\" role=\"tabpanel\" aria-labelledby=\"nav-home-tab\">Home</div>',\n          '          <div class=\"tab-pane fade\" id=\"nav-profile\" role=\"tabpanel\" aria-labelledby=\"nav-profile-tab\">Profile</div>',\n          '        </div>',\n          '      </div>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const el = fixtureEl.querySelector('#collapseOne')\n        const activeTabPane = fixtureEl.querySelector('#nav-home')\n        const collapse = new Collapse(el)\n        let times = 1\n\n        el.addEventListener('hidden.bs.collapse', () => {\n          collapse.show()\n        })\n\n        el.addEventListener('shown.bs.collapse', () => {\n          expect(activeTabPane).toHaveClass('show')\n          times++\n          if (times === 2) {\n            resolve()\n          }\n\n          collapse.hide()\n        })\n\n        collapse.show()\n      })\n    })\n\n    it('should not fire shown when show is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"collapse\"></div>'\n\n        const collapseEl = fixtureEl.querySelector('div')\n        const collapse = new Collapse(collapseEl, {\n          toggle: false\n        })\n\n        const expectEnd = () => {\n          setTimeout(() => {\n            expect().nothing()\n            resolve()\n          }, 10)\n        }\n\n        collapseEl.addEventListener('show.bs.collapse', event => {\n          event.preventDefault()\n          expectEnd()\n        })\n\n        collapseEl.addEventListener('shown.bs.collapse', () => {\n          reject(new Error('should not fire shown event'))\n        })\n\n        collapse.show()\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should do nothing if is transitioning', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      const collapseEl = fixtureEl.querySelector('div')\n      const collapse = new Collapse(collapseEl, {\n        toggle: false\n      })\n\n      collapse._isTransitioning = true\n      collapse.hide()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should do nothing if already shown', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const spy = spyOn(EventHandler, 'trigger')\n\n      const collapseEl = fixtureEl.querySelector('div')\n      const collapse = new Collapse(collapseEl, {\n        toggle: false\n      })\n\n      collapse.hide()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should hide a collapsed element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"collapse show\"></div>'\n\n        const collapseEl = fixtureEl.querySelector('div')\n        const collapse = new Collapse(collapseEl, {\n          toggle: false\n        })\n\n        collapseEl.addEventListener('hidden.bs.collapse', () => {\n          expect(collapseEl).not.toHaveClass('show')\n          expect(collapseEl.style.height).toEqual('')\n          resolve()\n        })\n\n        collapse.hide()\n      })\n    })\n\n    it('should not fire hidden when hide is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"collapse show\"></div>'\n\n        const collapseEl = fixtureEl.querySelector('div')\n        const collapse = new Collapse(collapseEl, {\n          toggle: false\n        })\n\n        const expectEnd = () => {\n          setTimeout(() => {\n            expect().nothing()\n            resolve()\n          }, 10)\n        }\n\n        collapseEl.addEventListener('hide.bs.collapse', event => {\n          event.preventDefault()\n          expectEnd()\n        })\n\n        collapseEl.addEventListener('hidden.bs.collapse', () => {\n          reject(new Error('should not fire hidden event'))\n        })\n\n        collapse.hide()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should destroy a collapse', () => {\n      fixtureEl.innerHTML = '<div class=\"collapse show\"></div>'\n\n      const collapseEl = fixtureEl.querySelector('div')\n      const collapse = new Collapse(collapseEl, {\n        toggle: false\n      })\n\n      expect(Collapse.getInstance(collapseEl)).toEqual(collapse)\n\n      collapse.dispose()\n\n      expect(Collapse.getInstance(collapseEl)).toBeNull()\n    })\n  })\n\n  describe('data-api', () => {\n    it('should prevent url change if click on nested elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a role=\"button\" data-bs-toggle=\"collapse\" class=\"collapsed\" href=\"#collapse\">',\n          '  <span id=\"nested\"></span>',\n          '</a>',\n          '<div id=\"collapse\" class=\"collapse\"></div>'\n        ].join('')\n\n        const triggerEl = fixtureEl.querySelector('a')\n        const nestedTriggerEl = fixtureEl.querySelector('#nested')\n\n        const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n        triggerEl.addEventListener('click', event => {\n          expect(event.target.isEqualNode(nestedTriggerEl)).toBeTrue()\n          expect(event.delegateTarget.isEqualNode(triggerEl)).toBeTrue()\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        nestedTriggerEl.click()\n      })\n    })\n\n    it('should show multiple collapsed elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a role=\"button\" data-bs-toggle=\"collapse\" class=\"collapsed\" href=\".multi\"></a>',\n          '<div id=\"collapse1\" class=\"collapse multi\"></div>',\n          '<div id=\"collapse2\" class=\"collapse multi\"></div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('a')\n        const collapse1 = fixtureEl.querySelector('#collapse1')\n        const collapse2 = fixtureEl.querySelector('#collapse2')\n\n        collapse2.addEventListener('shown.bs.collapse', () => {\n          expect(trigger.getAttribute('aria-expanded')).toEqual('true')\n          expect(trigger).not.toHaveClass('collapsed')\n          expect(collapse1).toHaveClass('show')\n          expect(collapse1).toHaveClass('show')\n          resolve()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should hide multiple collapsed elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a role=\"button\" data-bs-toggle=\"collapse\" href=\".multi\"></a>',\n          '<div id=\"collapse1\" class=\"collapse multi show\"></div>',\n          '<div id=\"collapse2\" class=\"collapse multi show\"></div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('a')\n        const collapse1 = fixtureEl.querySelector('#collapse1')\n        const collapse2 = fixtureEl.querySelector('#collapse2')\n\n        collapse2.addEventListener('hidden.bs.collapse', () => {\n          expect(trigger.getAttribute('aria-expanded')).toEqual('false')\n          expect(trigger).toHaveClass('collapsed')\n          expect(collapse1).not.toHaveClass('show')\n          expect(collapse1).not.toHaveClass('show')\n          resolve()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should remove \"collapsed\" class from target when collapse is shown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a id=\"link1\" role=\"button\" data-bs-toggle=\"collapse\" class=\"collapsed\" href=\"#\" data-bs-target=\"#test1\"></a>',\n          '<a id=\"link2\" role=\"button\" data-bs-toggle=\"collapse\" class=\"collapsed\" href=\"#\" data-bs-target=\"#test1\"></a>',\n          '<div id=\"test1\"></div>'\n        ].join('')\n\n        const link1 = fixtureEl.querySelector('#link1')\n        const link2 = fixtureEl.querySelector('#link2')\n        const collapseTest1 = fixtureEl.querySelector('#test1')\n\n        collapseTest1.addEventListener('shown.bs.collapse', () => {\n          expect(link1.getAttribute('aria-expanded')).toEqual('true')\n          expect(link2.getAttribute('aria-expanded')).toEqual('true')\n          expect(link1).not.toHaveClass('collapsed')\n          expect(link2).not.toHaveClass('collapsed')\n          resolve()\n        })\n\n        link1.click()\n      })\n    })\n\n    it('should add \"collapsed\" class to target when collapse is hidden', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a id=\"link1\" role=\"button\" data-bs-toggle=\"collapse\" href=\"#\" data-bs-target=\"#test1\"></a>',\n          '<a id=\"link2\" role=\"button\" data-bs-toggle=\"collapse\" href=\"#\" data-bs-target=\"#test1\"></a>',\n          '<div id=\"test1\" class=\"show\"></div>'\n        ].join('')\n\n        const link1 = fixtureEl.querySelector('#link1')\n        const link2 = fixtureEl.querySelector('#link2')\n        const collapseTest1 = fixtureEl.querySelector('#test1')\n\n        collapseTest1.addEventListener('hidden.bs.collapse', () => {\n          expect(link1.getAttribute('aria-expanded')).toEqual('false')\n          expect(link2.getAttribute('aria-expanded')).toEqual('false')\n          expect(link1).toHaveClass('collapsed')\n          expect(link2).toHaveClass('collapsed')\n          resolve()\n        })\n\n        link1.click()\n      })\n    })\n\n    it('should allow accordion to use children other than card', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"accordion\">',\n          '  <div class=\"item\">',\n          '    <a id=\"linkTrigger\" data-bs-toggle=\"collapse\" href=\"#collapseOne\" aria-expanded=\"false\" aria-controls=\"collapseOne\"></a>',\n          '    <div id=\"collapseOne\" class=\"collapse\" role=\"tabpanel\" aria-labelledby=\"headingThree\" data-bs-parent=\"#accordion\"></div>',\n          '  </div>',\n          '  <div class=\"item\">',\n          '    <a id=\"linkTriggerTwo\" data-bs-toggle=\"collapse\" href=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\"></a>',\n          '    <div id=\"collapseTwo\" class=\"collapse show\" role=\"tabpanel\" aria-labelledby=\"headingTwo\" data-bs-parent=\"#accordion\"></div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('#linkTrigger')\n        const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')\n        const collapseOne = fixtureEl.querySelector('#collapseOne')\n        const collapseTwo = fixtureEl.querySelector('#collapseTwo')\n\n        collapseOne.addEventListener('shown.bs.collapse', () => {\n          expect(collapseOne).toHaveClass('show')\n          expect(collapseTwo).not.toHaveClass('show')\n\n          collapseTwo.addEventListener('shown.bs.collapse', () => {\n            expect(collapseOne).not.toHaveClass('show')\n            expect(collapseTwo).toHaveClass('show')\n            resolve()\n          })\n\n          triggerTwo.click()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should not prevent event for input', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<input type=\"checkbox\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapsediv1\">',\n          '<div id=\"collapsediv1\"></div>'\n        ].join('')\n\n        const target = fixtureEl.querySelector('input')\n        const collapseEl = fixtureEl.querySelector('#collapsediv1')\n\n        collapseEl.addEventListener('shown.bs.collapse', () => {\n          expect(collapseEl).toHaveClass('show')\n          expect(target.checked).toBeTrue()\n          resolve()\n        })\n\n        target.click()\n      })\n    })\n\n    it('should allow accordion to contain nested elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"accordion\">',\n          '  <div class=\"row\">',\n          '    <div class=\"col-lg-6\">',\n          '      <div class=\"item\">',\n          '        <a id=\"linkTrigger\" data-bs-toggle=\"collapse\" href=\"#collapseOne\" aria-expanded=\"false\" aria-controls=\"collapseOne\"></a>',\n          '        <div id=\"collapseOne\" class=\"collapse\" role=\"tabpanel\" aria-labelledby=\"headingThree\" data-bs-parent=\"#accordion\"></div>',\n          '      </div>',\n          '    </div>',\n          '    <div class=\"col-lg-6\">',\n          '      <div class=\"item\">',\n          '        <a id=\"linkTriggerTwo\" data-bs-toggle=\"collapse\" href=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\"></a>',\n          '        <div id=\"collapseTwo\" class=\"collapse show\" role=\"tabpanel\" aria-labelledby=\"headingTwo\" data-bs-parent=\"#accordion\"></div>',\n          '      </div>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerEl = fixtureEl.querySelector('#linkTrigger')\n        const triggerTwoEl = fixtureEl.querySelector('#linkTriggerTwo')\n        const collapseOneEl = fixtureEl.querySelector('#collapseOne')\n        const collapseTwoEl = fixtureEl.querySelector('#collapseTwo')\n\n        collapseOneEl.addEventListener('shown.bs.collapse', () => {\n          expect(collapseOneEl).toHaveClass('show')\n          expect(triggerEl).not.toHaveClass('collapsed')\n          expect(triggerEl.getAttribute('aria-expanded')).toEqual('true')\n\n          expect(collapseTwoEl).not.toHaveClass('show')\n          expect(triggerTwoEl).toHaveClass('collapsed')\n          expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('false')\n\n          collapseTwoEl.addEventListener('shown.bs.collapse', () => {\n            expect(collapseOneEl).not.toHaveClass('show')\n            expect(triggerEl).toHaveClass('collapsed')\n            expect(triggerEl.getAttribute('aria-expanded')).toEqual('false')\n\n            expect(collapseTwoEl).toHaveClass('show')\n            expect(triggerTwoEl).not.toHaveClass('collapsed')\n            expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('true')\n            resolve()\n          })\n\n          triggerTwoEl.click()\n        })\n\n        triggerEl.click()\n      })\n    })\n\n    it('should allow accordion to target multiple elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"accordion\">',\n          '  <a id=\"linkTriggerOne\" data-bs-toggle=\"collapse\" data-bs-target=\".collapseOne\" href=\"#\" aria-expanded=\"false\" aria-controls=\"collapseOne\"></a>',\n          '  <a id=\"linkTriggerTwo\" data-bs-toggle=\"collapse\" data-bs-target=\".collapseTwo\" href=\"#\" aria-expanded=\"false\" aria-controls=\"collapseTwo\"></a>',\n          '  <div id=\"collapseOneOne\" class=\"collapse collapseOne\" role=\"tabpanel\" data-bs-parent=\"#accordion\"></div>',\n          '  <div id=\"collapseOneTwo\" class=\"collapse collapseOne\" role=\"tabpanel\" data-bs-parent=\"#accordion\"></div>',\n          '  <div id=\"collapseTwoOne\" class=\"collapse collapseTwo\" role=\"tabpanel\" data-bs-parent=\"#accordion\"></div>',\n          '  <div id=\"collapseTwoTwo\" class=\"collapse collapseTwo\" role=\"tabpanel\" data-bs-parent=\"#accordion\"></div>',\n          '</div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('#linkTriggerOne')\n        const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')\n        const collapseOneOne = fixtureEl.querySelector('#collapseOneOne')\n        const collapseOneTwo = fixtureEl.querySelector('#collapseOneTwo')\n        const collapseTwoOne = fixtureEl.querySelector('#collapseTwoOne')\n        const collapseTwoTwo = fixtureEl.querySelector('#collapseTwoTwo')\n        const collapsedElements = {\n          one: false,\n          two: false\n        }\n\n        function firstTest() {\n          expect(collapseOneOne).toHaveClass('show')\n          expect(collapseOneTwo).toHaveClass('show')\n\n          expect(collapseTwoOne).not.toHaveClass('show')\n          expect(collapseTwoTwo).not.toHaveClass('show')\n\n          triggerTwo.click()\n        }\n\n        function secondTest() {\n          expect(collapseOneOne).not.toHaveClass('show')\n          expect(collapseOneTwo).not.toHaveClass('show')\n\n          expect(collapseTwoOne).toHaveClass('show')\n          expect(collapseTwoTwo).toHaveClass('show')\n          resolve()\n        }\n\n        collapseOneOne.addEventListener('shown.bs.collapse', () => {\n          if (collapsedElements.one) {\n            firstTest()\n          } else {\n            collapsedElements.one = true\n          }\n        })\n\n        collapseOneTwo.addEventListener('shown.bs.collapse', () => {\n          if (collapsedElements.one) {\n            firstTest()\n          } else {\n            collapsedElements.one = true\n          }\n        })\n\n        collapseTwoOne.addEventListener('shown.bs.collapse', () => {\n          if (collapsedElements.two) {\n            secondTest()\n          } else {\n            collapsedElements.two = true\n          }\n        })\n\n        collapseTwoTwo.addEventListener('shown.bs.collapse', () => {\n          if (collapsedElements.two) {\n            secondTest()\n          } else {\n            collapsedElements.two = true\n          }\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should collapse accordion children but not nested accordion children', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"accordion\">',\n          '  <div class=\"item\">',\n          '    <a id=\"linkTrigger\" data-bs-toggle=\"collapse\" href=\"#collapseOne\" aria-expanded=\"false\" aria-controls=\"collapseOne\"></a>',\n          '    <div id=\"collapseOne\" data-bs-parent=\"#accordion\" class=\"collapse\" role=\"tabpanel\" aria-labelledby=\"headingThree\">',\n          '      <div id=\"nestedAccordion\">',\n          '        <div class=\"item\">',\n          '          <a id=\"nestedLinkTrigger\" data-bs-toggle=\"collapse\" href=\"#nestedCollapseOne\" aria-expanded=\"false\" aria-controls=\"nestedCollapseOne\"></a>',\n          '          <div id=\"nestedCollapseOne\" data-bs-parent=\"#nestedAccordion\" class=\"collapse\" role=\"tabpanel\" aria-labelledby=\"headingThree\"></div>',\n          '        </div>',\n          '      </div>',\n          '    </div>',\n          '  </div>',\n          '  <div class=\"item\">',\n          '    <a id=\"linkTriggerTwo\" data-bs-toggle=\"collapse\" href=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\"></a>',\n          '    <div id=\"collapseTwo\" data-bs-parent=\"#accordion\" class=\"collapse show\" role=\"tabpanel\" aria-labelledby=\"headingTwo\"></div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('#linkTrigger')\n        const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')\n        const nestedTrigger = fixtureEl.querySelector('#nestedLinkTrigger')\n        const collapseOne = fixtureEl.querySelector('#collapseOne')\n        const collapseTwo = fixtureEl.querySelector('#collapseTwo')\n        const nestedCollapseOne = fixtureEl.querySelector('#nestedCollapseOne')\n\n        function handlerCollapseOne() {\n          expect(collapseOne).toHaveClass('show')\n          expect(collapseTwo).not.toHaveClass('show')\n          expect(nestedCollapseOne).not.toHaveClass('show')\n\n          nestedCollapseOne.addEventListener('shown.bs.collapse', handlerNestedCollapseOne)\n          nestedTrigger.click()\n          collapseOne.removeEventListener('shown.bs.collapse', handlerCollapseOne)\n        }\n\n        function handlerNestedCollapseOne() {\n          expect(collapseOne).toHaveClass('show')\n          expect(collapseTwo).not.toHaveClass('show')\n          expect(nestedCollapseOne).toHaveClass('show')\n\n          collapseTwo.addEventListener('shown.bs.collapse', () => {\n            expect(collapseOne).not.toHaveClass('show')\n            expect(collapseTwo).toHaveClass('show')\n            expect(nestedCollapseOne).toHaveClass('show')\n            resolve()\n          })\n\n          triggerTwo.click()\n          nestedCollapseOne.removeEventListener('shown.bs.collapse', handlerNestedCollapseOne)\n        }\n\n        collapseOne.addEventListener('shown.bs.collapse', handlerCollapseOne)\n        trigger.click()\n      })\n    })\n\n    it('should add \"collapsed\" class and set aria-expanded to triggers only when all the targeted collapse are hidden', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a id=\"trigger1\" role=\"button\" data-bs-toggle=\"collapse\" href=\"#test1\"></a>',\n          '<a id=\"trigger2\" role=\"button\" data-bs-toggle=\"collapse\" href=\"#test2\"></a>',\n          '<a id=\"trigger3\" role=\"button\" data-bs-toggle=\"collapse\" href=\".multi\"></a>',\n          '<div id=\"test1\" class=\"multi\"></div>',\n          '<div id=\"test2\" class=\"multi\"></div>'\n        ].join('')\n\n        const trigger1 = fixtureEl.querySelector('#trigger1')\n        const trigger2 = fixtureEl.querySelector('#trigger2')\n        const trigger3 = fixtureEl.querySelector('#trigger3')\n        const target1 = fixtureEl.querySelector('#test1')\n        const target2 = fixtureEl.querySelector('#test2')\n\n        const target2Shown = () => {\n          expect(trigger1).not.toHaveClass('collapsed')\n          expect(trigger1.getAttribute('aria-expanded')).toEqual('true')\n\n          expect(trigger2).not.toHaveClass('collapsed')\n          expect(trigger2.getAttribute('aria-expanded')).toEqual('true')\n\n          expect(trigger3).not.toHaveClass('collapsed')\n          expect(trigger3.getAttribute('aria-expanded')).toEqual('true')\n\n          target2.addEventListener('hidden.bs.collapse', () => {\n            expect(trigger1).not.toHaveClass('collapsed')\n            expect(trigger1.getAttribute('aria-expanded')).toEqual('true')\n\n            expect(trigger2).toHaveClass('collapsed')\n            expect(trigger2.getAttribute('aria-expanded')).toEqual('false')\n\n            expect(trigger3).not.toHaveClass('collapsed')\n            expect(trigger3.getAttribute('aria-expanded')).toEqual('true')\n\n            target1.addEventListener('hidden.bs.collapse', () => {\n              expect(trigger1).toHaveClass('collapsed')\n              expect(trigger1.getAttribute('aria-expanded')).toEqual('false')\n\n              expect(trigger2).toHaveClass('collapsed')\n              expect(trigger2.getAttribute('aria-expanded')).toEqual('false')\n\n              expect(trigger3).toHaveClass('collapsed')\n              expect(trigger3.getAttribute('aria-expanded')).toEqual('false')\n              resolve()\n            })\n\n            trigger1.click()\n          })\n\n          trigger2.click()\n        }\n\n        target2.addEventListener('shown.bs.collapse', target2Shown)\n        trigger3.click()\n      })\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a collapse', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.collapse = Collapse.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.collapse.call(jQueryMock)\n\n      expect(Collapse.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create a collapse', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const collapse = new Collapse(div)\n\n      jQueryMock.fn.collapse = Collapse.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.collapse.call(jQueryMock)\n\n      expect(Collapse.getInstance(div)).toEqual(collapse)\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.collapse = Collapse.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.collapse.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return collapse instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const collapse = new Collapse(div)\n\n      expect(Collapse.getInstance(div)).toEqual(collapse)\n      expect(Collapse.getInstance(div)).toBeInstanceOf(Collapse)\n    })\n\n    it('should return null when there is no collapse instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Collapse.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return collapse instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const collapse = new Collapse(div)\n\n      expect(Collapse.getOrCreateInstance(div)).toEqual(collapse)\n      expect(Collapse.getInstance(div)).toEqual(Collapse.getOrCreateInstance(div, {}))\n      expect(Collapse.getOrCreateInstance(div)).toBeInstanceOf(Collapse)\n    })\n\n    it('should return new instance when there is no collapse instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Collapse.getInstance(div)).toBeNull()\n      expect(Collapse.getOrCreateInstance(div)).toBeInstanceOf(Collapse)\n    })\n\n    it('should return new instance when there is no collapse instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Collapse.getInstance(div)).toBeNull()\n      const collapse = Collapse.getOrCreateInstance(div, {\n        toggle: false\n      })\n      expect(collapse).toBeInstanceOf(Collapse)\n\n      expect(collapse._config.toggle).toBeFalse()\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const collapse = new Collapse(div, {\n        toggle: false\n      })\n      expect(Collapse.getInstance(div)).toEqual(collapse)\n\n      const collapse2 = Collapse.getOrCreateInstance(div, {\n        toggle: true\n      })\n      expect(collapse).toBeInstanceOf(Collapse)\n      expect(collapse2).toEqual(collapse)\n\n      expect(collapse2._config.toggle).toBeFalse()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/dom/data.spec.js",
    "content": "import Data from '../../../src/dom/data'\nimport { getFixture, clearFixture } from '../../helpers/fixture'\n\ndescribe('Data', () => {\n  const TEST_KEY = 'bs.test'\n  const UNKNOWN_KEY = 'bs.unknown'\n  const TEST_DATA = {\n    test: 'bsData'\n  }\n\n  let fixtureEl\n  let div\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  beforeEach(() => {\n    fixtureEl.innerHTML = '<div></div>'\n    div = fixtureEl.querySelector('div')\n  })\n\n  afterEach(() => {\n    Data.remove(div, TEST_KEY)\n    clearFixture()\n  })\n\n  it('should return null for unknown elements', () => {\n    const data = { ...TEST_DATA }\n\n    Data.set(div, TEST_KEY, data)\n\n    expect(Data.get(null)).toBeNull()\n    expect(Data.get(undefined)).toBeNull()\n    expect(Data.get(document.createElement('div'), TEST_KEY)).toBeNull()\n  })\n\n  it('should return null for unknown keys', () => {\n    const data = { ...TEST_DATA }\n\n    Data.set(div, TEST_KEY, data)\n\n    expect(Data.get(div, null)).toBeNull()\n    expect(Data.get(div, undefined)).toBeNull()\n    expect(Data.get(div, UNKNOWN_KEY)).toBeNull()\n  })\n\n  it('should store data for an element with a given key and return it', () => {\n    const data = { ...TEST_DATA }\n\n    Data.set(div, TEST_KEY, data)\n\n    expect(Data.get(div, TEST_KEY)).toEqual(data)\n  })\n\n  it('should overwrite data if something is already stored', () => {\n    const data = { ...TEST_DATA }\n    const copy = { ...data }\n\n    Data.set(div, TEST_KEY, data)\n    Data.set(div, TEST_KEY, copy)\n\n    // Using `toBe` since spread creates a shallow copy\n    expect(Data.get(div, TEST_KEY)).not.toBe(data)\n    expect(Data.get(div, TEST_KEY)).toBe(copy)\n  })\n\n  it('should do nothing when an element has nothing stored', () => {\n    Data.remove(div, TEST_KEY)\n\n    expect().nothing()\n  })\n\n  it('should remove nothing for an unknown key', () => {\n    const data = { ...TEST_DATA }\n\n    Data.set(div, TEST_KEY, data)\n    Data.remove(div, UNKNOWN_KEY)\n\n    expect(Data.get(div, TEST_KEY)).toEqual(data)\n  })\n\n  it('should remove data for a given key', () => {\n    const data = { ...TEST_DATA }\n\n    Data.set(div, TEST_KEY, data)\n    Data.remove(div, TEST_KEY)\n\n    expect(Data.get(div, TEST_KEY)).toBeNull()\n  })\n\n  /* eslint-disable no-console */\n  it('should console.error a message if called with multiple keys', () => {\n    console.error = jasmine.createSpy('console.error')\n\n    const data = { ...TEST_DATA }\n    const copy = { ...data }\n\n    Data.set(div, TEST_KEY, data)\n    Data.set(div, UNKNOWN_KEY, copy)\n\n    expect(console.error).toHaveBeenCalled()\n    expect(Data.get(div, UNKNOWN_KEY)).toBeNull()\n  })\n  /* eslint-enable no-console */\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/dom/event-handler.spec.js",
    "content": "import EventHandler from '../../../src/dom/event-handler'\nimport { clearFixture, getFixture } from '../../helpers/fixture'\nimport { noop } from '../../../src/util'\n\ndescribe('EventHandler', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('on', () => {\n    it('should not add event listener if the event is not a string', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      EventHandler.on(div, null, noop)\n      EventHandler.on(null, 'click', noop)\n\n      expect().nothing()\n    })\n\n    it('should add event listener', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const div = fixtureEl.querySelector('div')\n\n        EventHandler.on(div, 'click', () => {\n          expect().nothing()\n          resolve()\n        })\n\n        div.click()\n      })\n    })\n\n    it('should add namespaced event listener', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const div = fixtureEl.querySelector('div')\n\n        EventHandler.on(div, 'bs.namespace', () => {\n          expect().nothing()\n          resolve()\n        })\n\n        EventHandler.trigger(div, 'bs.namespace')\n      })\n    })\n\n    it('should add native namespaced event listener', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const div = fixtureEl.querySelector('div')\n\n        EventHandler.on(div, 'click.namespace', () => {\n          expect().nothing()\n          resolve()\n        })\n\n        EventHandler.trigger(div, 'click')\n      })\n    })\n\n    it('should handle event delegation', () => {\n      return new Promise(resolve => {\n        EventHandler.on(document, 'click', '.test', () => {\n          expect().nothing()\n          resolve()\n        })\n\n        fixtureEl.innerHTML = '<div class=\"test\"></div>'\n\n        const div = fixtureEl.querySelector('div')\n\n        div.click()\n      })\n    })\n\n    it('should handle mouseenter/mouseleave like the native counterpart', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"outer\">',\n          '<div class=\"inner\">',\n          '<div class=\"nested\">',\n          '<div class=\"deep\"></div>',\n          '</div>',\n          '</div>',\n          '<div class=\"sibling\"></div>',\n          '</div>'\n        ].join('')\n\n        const outer = fixtureEl.querySelector('.outer')\n        const inner = fixtureEl.querySelector('.inner')\n        const nested = fixtureEl.querySelector('.nested')\n        const deep = fixtureEl.querySelector('.deep')\n        const sibling = fixtureEl.querySelector('.sibling')\n\n        const enterSpy = jasmine.createSpy('mouseenter')\n        const leaveSpy = jasmine.createSpy('mouseleave')\n        const delegateEnterSpy = jasmine.createSpy('mouseenter')\n        const delegateLeaveSpy = jasmine.createSpy('mouseleave')\n\n        EventHandler.on(inner, 'mouseenter', enterSpy)\n        EventHandler.on(inner, 'mouseleave', leaveSpy)\n        EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy)\n        EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)\n\n        EventHandler.on(sibling, 'mouseenter', () => {\n          expect(enterSpy.calls.count()).toEqual(2)\n          expect(leaveSpy.calls.count()).toEqual(2)\n          expect(delegateEnterSpy.calls.count()).toEqual(2)\n          expect(delegateLeaveSpy.calls.count()).toEqual(2)\n          resolve()\n        })\n\n        const moveMouse = (from, to) => {\n          from.dispatchEvent(new MouseEvent('mouseout', {\n            bubbles: true,\n            relatedTarget: to\n          }))\n\n          to.dispatchEvent(new MouseEvent('mouseover', {\n            bubbles: true,\n            relatedTarget: from\n          }))\n        }\n\n        // from outer to deep and back to outer (nested)\n        moveMouse(outer, inner)\n        moveMouse(inner, nested)\n        moveMouse(nested, deep)\n        moveMouse(deep, nested)\n        moveMouse(nested, inner)\n        moveMouse(inner, outer)\n\n        setTimeout(() => {\n          expect(enterSpy.calls.count()).toEqual(1)\n          expect(leaveSpy.calls.count()).toEqual(1)\n          expect(delegateEnterSpy.calls.count()).toEqual(1)\n          expect(delegateLeaveSpy.calls.count()).toEqual(1)\n\n          // from outer to inner to sibling (adjacent)\n          moveMouse(outer, inner)\n          moveMouse(inner, sibling)\n        }, 20)\n      })\n    })\n  })\n\n  describe('one', () => {\n    it('should call listener just once', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        let called = 0\n        const div = fixtureEl.querySelector('div')\n        const obj = {\n          oneListener() {\n            called++\n          }\n        }\n\n        EventHandler.one(div, 'bootstrap', obj.oneListener)\n\n        EventHandler.trigger(div, 'bootstrap')\n        EventHandler.trigger(div, 'bootstrap')\n\n        setTimeout(() => {\n          expect(called).toEqual(1)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should call delegated listener just once', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        let called = 0\n        const div = fixtureEl.querySelector('div')\n        const obj = {\n          oneListener() {\n            called++\n          }\n        }\n\n        EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener)\n\n        EventHandler.trigger(div, 'bootstrap')\n        EventHandler.trigger(div, 'bootstrap')\n\n        setTimeout(() => {\n          expect(called).toEqual(1)\n          resolve()\n        }, 20)\n      })\n    })\n  })\n\n  describe('off', () => {\n    it('should not remove a listener', () => {\n      fixtureEl.innerHTML = '<div></div>'\n      const div = fixtureEl.querySelector('div')\n\n      EventHandler.off(div, null, noop)\n      EventHandler.off(null, 'click', noop)\n      expect().nothing()\n    })\n\n    it('should remove a listener', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n        const div = fixtureEl.querySelector('div')\n\n        let called = 0\n        const handler = () => {\n          called++\n        }\n\n        EventHandler.on(div, 'foobar', handler)\n        EventHandler.trigger(div, 'foobar')\n\n        EventHandler.off(div, 'foobar', handler)\n        EventHandler.trigger(div, 'foobar')\n\n        setTimeout(() => {\n          expect(called).toEqual(1)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove all the events', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n        const div = fixtureEl.querySelector('div')\n\n        let called = 0\n\n        EventHandler.on(div, 'foobar', () => {\n          called++\n        })\n        EventHandler.on(div, 'foobar', () => {\n          called++\n        })\n        EventHandler.trigger(div, 'foobar')\n\n        EventHandler.off(div, 'foobar')\n        EventHandler.trigger(div, 'foobar')\n\n        setTimeout(() => {\n          expect(called).toEqual(2)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove all the namespaced listeners if namespace is passed', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n        const div = fixtureEl.querySelector('div')\n\n        let called = 0\n\n        EventHandler.on(div, 'foobar.namespace', () => {\n          called++\n        })\n        EventHandler.on(div, 'foofoo.namespace', () => {\n          called++\n        })\n        EventHandler.trigger(div, 'foobar.namespace')\n        EventHandler.trigger(div, 'foofoo.namespace')\n\n        EventHandler.off(div, '.namespace')\n        EventHandler.trigger(div, 'foobar.namespace')\n        EventHandler.trigger(div, 'foofoo.namespace')\n\n        setTimeout(() => {\n          expect(called).toEqual(2)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove the namespaced listeners', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n        const div = fixtureEl.querySelector('div')\n\n        let calledCallback1 = 0\n        let calledCallback2 = 0\n\n        EventHandler.on(div, 'foobar.namespace', () => {\n          calledCallback1++\n        })\n        EventHandler.on(div, 'foofoo.namespace', () => {\n          calledCallback2++\n        })\n\n        EventHandler.trigger(div, 'foobar.namespace')\n        EventHandler.off(div, 'foobar.namespace')\n        EventHandler.trigger(div, 'foobar.namespace')\n\n        EventHandler.trigger(div, 'foofoo.namespace')\n\n        setTimeout(() => {\n          expect(calledCallback1).toEqual(1)\n          expect(calledCallback2).toEqual(1)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove the all the namespaced listeners for native events', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n        const div = fixtureEl.querySelector('div')\n\n        let called = 0\n\n        EventHandler.on(div, 'click.namespace', () => {\n          called++\n        })\n        EventHandler.on(div, 'click.namespace2', () => {\n          called++\n        })\n\n        EventHandler.trigger(div, 'click')\n        EventHandler.off(div, 'click')\n        EventHandler.trigger(div, 'click')\n\n        setTimeout(() => {\n          expect(called).toEqual(2)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove the specified namespaced listeners for native events', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n        const div = fixtureEl.querySelector('div')\n\n        let called1 = 0\n        let called2 = 0\n\n        EventHandler.on(div, 'click.namespace', () => {\n          called1++\n        })\n        EventHandler.on(div, 'click.namespace2', () => {\n          called2++\n        })\n        EventHandler.trigger(div, 'click')\n\n        EventHandler.off(div, 'click.namespace')\n        EventHandler.trigger(div, 'click')\n\n        setTimeout(() => {\n          expect(called1).toEqual(1)\n          expect(called2).toEqual(2)\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove a listener registered by .one', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const div = fixtureEl.querySelector('div')\n        const handler = () => {\n          reject(new Error('called'))\n        }\n\n        EventHandler.one(div, 'foobar', handler)\n        EventHandler.off(div, 'foobar', handler)\n\n        EventHandler.trigger(div, 'foobar')\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should remove the correct delegated event listener', () => {\n      const element = document.createElement('div')\n      const subelement = document.createElement('span')\n      element.append(subelement)\n\n      const anchor = document.createElement('a')\n      element.append(anchor)\n\n      let i = 0\n      const handler = () => {\n        i++\n      }\n\n      EventHandler.on(element, 'click', 'a', handler)\n      EventHandler.on(element, 'click', 'span', handler)\n\n      fixtureEl.append(element)\n\n      EventHandler.trigger(anchor, 'click')\n      EventHandler.trigger(subelement, 'click')\n\n      // first listeners called\n      expect(i).toEqual(2)\n\n      EventHandler.off(element, 'click', 'span', handler)\n      EventHandler.trigger(subelement, 'click')\n\n      // removed listener not called\n      expect(i).toEqual(2)\n\n      EventHandler.trigger(anchor, 'click')\n\n      // not removed listener called\n      expect(i).toEqual(3)\n\n      EventHandler.on(element, 'click', 'span', handler)\n      EventHandler.trigger(anchor, 'click')\n      EventHandler.trigger(subelement, 'click')\n\n      // listener re-registered\n      expect(i).toEqual(5)\n\n      EventHandler.off(element, 'click', 'span')\n      EventHandler.trigger(subelement, 'click')\n\n      // listener removed again\n      expect(i).toEqual(5)\n    })\n  })\n\n  describe('general functionality', () => {\n    it('should hydrate properties, and make them configurable', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"div1\">',\n          '   <div id=\"div2\"></div>',\n          '   <div id=\"div3\"></div>',\n          '</div>'\n        ].join('')\n\n        const div1 = fixtureEl.querySelector('#div1')\n        const div2 = fixtureEl.querySelector('#div2')\n\n        EventHandler.on(div1, 'click', event => {\n          expect(event.currentTarget).toBe(div2)\n          expect(event.delegateTarget).toBe(div1)\n          expect(event.originalTarget).toBeNull()\n\n          Object.defineProperty(event, 'currentTarget', {\n            configurable: true,\n            get() {\n              return div1\n            }\n          })\n\n          expect(event.currentTarget).toBe(div1)\n          resolve()\n        })\n\n        expect(() => {\n          EventHandler.trigger(div1, 'click', { originalTarget: null, currentTarget: div2 })\n        }).not.toThrowError(TypeError)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/dom/manipulator.spec.js",
    "content": "import Manipulator from '../../../src/dom/manipulator'\nimport { clearFixture, getFixture } from '../../helpers/fixture'\n\ndescribe('Manipulator', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('setDataAttribute', () => {\n    it('should set data attribute prefixed with bs', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      Manipulator.setDataAttribute(div, 'key', 'value')\n      expect(div.getAttribute('data-bs-key')).toEqual('value')\n    })\n\n    it('should set data attribute in kebab case', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      Manipulator.setDataAttribute(div, 'testKey', 'value')\n      expect(div.getAttribute('data-bs-test-key')).toEqual('value')\n    })\n  })\n\n  describe('removeDataAttribute', () => {\n    it('should only remove bs-prefixed data attribute', () => {\n      fixtureEl.innerHTML = '<div data-bs-key=\"value\" data-key-bs=\"postfixed\" data-key=\"value\"></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      Manipulator.removeDataAttribute(div, 'key')\n      expect(div.getAttribute('data-bs-key')).toBeNull()\n      expect(div.getAttribute('data-key-bs')).toEqual('postfixed')\n      expect(div.getAttribute('data-key')).toEqual('value')\n    })\n\n    it('should remove data attribute in kebab case', () => {\n      fixtureEl.innerHTML = '<div data-bs-test-key=\"value\"></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      Manipulator.removeDataAttribute(div, 'testKey')\n      expect(div.getAttribute('data-bs-test-key')).toBeNull()\n    })\n  })\n\n  describe('getDataAttributes', () => {\n    it('should return an empty object for null', () => {\n      expect(Manipulator.getDataAttributes(null)).toEqual({})\n      expect().nothing()\n    })\n\n    it('should get only bs-prefixed data attributes without bs namespace', () => {\n      fixtureEl.innerHTML = '<div data-bs-toggle=\"tabs\" data-bs-target=\"#element\" data-another=\"value\" data-target-bs=\"#element\" data-in-bs-out=\"in-between\"></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Manipulator.getDataAttributes(div)).toEqual({\n        toggle: 'tabs',\n        target: '#element'\n      })\n    })\n\n    it('should omit `bs-config` data attribute', () => {\n      fixtureEl.innerHTML = '<div data-bs-toggle=\"tabs\" data-bs-target=\"#element\" data-bs-config=\\'{\"testBool\":false}\\'></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Manipulator.getDataAttributes(div)).toEqual({\n        toggle: 'tabs',\n        target: '#element'\n      })\n    })\n  })\n\n  describe('getDataAttribute', () => {\n    it('should only get bs-prefixed data attribute', () => {\n      fixtureEl.innerHTML = '<div data-bs-key=\"value\" data-test-bs=\"postFixed\" data-toggle=\"tab\"></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Manipulator.getDataAttribute(div, 'key')).toEqual('value')\n      expect(Manipulator.getDataAttribute(div, 'test')).toBeNull()\n      expect(Manipulator.getDataAttribute(div, 'toggle')).toBeNull()\n    })\n\n    it('should get data attribute in kebab case', () => {\n      fixtureEl.innerHTML = '<div data-bs-test-key=\"value\" ></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Manipulator.getDataAttribute(div, 'testKey')).toEqual('value')\n    })\n\n    it('should normalize data', () => {\n      fixtureEl.innerHTML = '<div data-bs-test=\"false\" ></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Manipulator.getDataAttribute(div, 'test')).toBeFalse()\n\n      div.setAttribute('data-bs-test', 'true')\n      expect(Manipulator.getDataAttribute(div, 'test')).toBeTrue()\n\n      div.setAttribute('data-bs-test', '1')\n      expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1)\n    })\n\n    it('should normalize json data', () => {\n      fixtureEl.innerHTML = '<div data-bs-test=\\'{\"delay\":{\"show\":100,\"hide\":10}}\\'></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Manipulator.getDataAttribute(div, 'test')).toEqual({ delay: { show: 100, hide: 10 } })\n\n      const objectData = { 'Super Hero': ['Iron Man', 'Super Man'], testNum: 90, url: 'http://localhost:8080/test?foo=bar' }\n      const dataStr = JSON.stringify(objectData)\n      div.setAttribute('data-bs-test', encodeURIComponent(dataStr))\n      expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)\n\n      div.setAttribute('data-bs-test', dataStr)\n      expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/dom/selector-engine.spec.js",
    "content": "import SelectorEngine from '../../../src/dom/selector-engine'\nimport { getFixture, clearFixture } from '../../helpers/fixture'\n\ndescribe('SelectorEngine', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('find', () => {\n    it('should find elements', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(SelectorEngine.find('div', fixtureEl)).toEqual([div])\n    })\n\n    it('should find elements globally', () => {\n      fixtureEl.innerHTML = '<div id=\"test\"></div>'\n\n      const div = fixtureEl.querySelector('#test')\n\n      expect(SelectorEngine.find('#test')).toEqual([div])\n    })\n\n    it('should handle :scope selectors', () => {\n      fixtureEl.innerHTML = [\n        '<ul>',\n        '  <li></li>',\n        '  <li>',\n        '    <a href=\"#\" class=\"active\">link</a>',\n        '  </li>',\n        '  <li></li>',\n        '</ul>'\n      ].join('')\n\n      const listEl = fixtureEl.querySelector('ul')\n      const aActive = fixtureEl.querySelector('.active')\n\n      expect(SelectorEngine.find(':scope > li > .active', listEl)).toEqual([aActive])\n    })\n  })\n\n  describe('findOne', () => {\n    it('should return one element', () => {\n      fixtureEl.innerHTML = '<div id=\"test\"></div>'\n\n      const div = fixtureEl.querySelector('#test')\n\n      expect(SelectorEngine.findOne('#test')).toEqual(div)\n    })\n  })\n\n  describe('children', () => {\n    it('should find children', () => {\n      fixtureEl.innerHTML = [\n        '<ul>',\n        '  <li></li>',\n        '  <li></li>',\n        '  <li></li>',\n        '</ul>'\n      ].join('')\n\n      const list = fixtureEl.querySelector('ul')\n      const liList = [].concat(...fixtureEl.querySelectorAll('li'))\n      const result = SelectorEngine.children(list, 'li')\n\n      expect(result).toEqual(liList)\n    })\n  })\n\n  describe('parents', () => {\n    it('should return parents', () => {\n      expect(SelectorEngine.parents(fixtureEl, 'body')).toHaveSize(1)\n    })\n  })\n\n  describe('prev', () => {\n    it('should return previous element', () => {\n      fixtureEl.innerHTML = '<div class=\"test\"></div><button class=\"btn\"></button>'\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelector('.test')\n\n      expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])\n    })\n\n    it('should return previous element with an extra element between', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"test\"></div>',\n        '<span></span>',\n        '<button class=\"btn\"></button>'\n      ].join('')\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelector('.test')\n\n      expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])\n    })\n\n    it('should return previous element with comments or text nodes between', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"test\"></div>',\n        '<div class=\"test\"></div>',\n        '<!-- Comment-->',\n        'Text',\n        '<button class=\"btn\"></button>'\n      ].join('')\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelectorAll('.test')[1]\n\n      expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])\n    })\n  })\n\n  describe('next', () => {\n    it('should return next element', () => {\n      fixtureEl.innerHTML = '<div class=\"test\"></div><button class=\"btn\"></button>'\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelector('.test')\n\n      expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])\n    })\n\n    it('should return next element with an extra element between', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"test\"></div>',\n        '<span></span>',\n        '<button class=\"btn\"></button>'\n      ].join('')\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelector('.test')\n\n      expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])\n    })\n\n    it('should return next element with comments or text nodes between', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"test\"></div>',\n        '<!-- Comment-->',\n        'Text',\n        '<button class=\"btn\"></button>',\n        '<button class=\"btn\"></button>'\n      ].join('')\n\n      const btn = fixtureEl.querySelector('.btn')\n      const divTest = fixtureEl.querySelector('.test')\n\n      expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])\n    })\n  })\n\n  describe('focusableChildren', () => {\n    it('should return only elements with specific tag names', () => {\n      fixtureEl.innerHTML = [\n        '<div>lorem</div>',\n        '<span>lorem</span>',\n        '<a>lorem</a>',\n        '<button>lorem</button>',\n        '<input>',\n        '<textarea></textarea>',\n        '<select></select>',\n        '<details>lorem</details>'\n      ].join('')\n\n      const expectedElements = [\n        fixtureEl.querySelector('a'),\n        fixtureEl.querySelector('button'),\n        fixtureEl.querySelector('input'),\n        fixtureEl.querySelector('textarea'),\n        fixtureEl.querySelector('select'),\n        fixtureEl.querySelector('details')\n      ]\n\n      expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)\n    })\n\n    it('should return any element with non negative tab index', () => {\n      fixtureEl.innerHTML = [\n        '<div tabindex>lorem</div>',\n        '<div tabindex=\"0\">lorem</div>',\n        '<div tabindex=\"10\">lorem</div>'\n      ].join('')\n\n      const expectedElements = [\n        fixtureEl.querySelector('[tabindex]'),\n        fixtureEl.querySelector('[tabindex=\"0\"]'),\n        fixtureEl.querySelector('[tabindex=\"10\"]')\n      ]\n\n      expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)\n    })\n\n    it('should return not return elements with negative tab index', () => {\n      fixtureEl.innerHTML = '<button tabindex=\"-1\">lorem</button>'\n\n      const expectedElements = []\n\n      expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)\n    })\n\n    it('should return contenteditable elements', () => {\n      fixtureEl.innerHTML = '<div contenteditable=\"true\">lorem</div>'\n\n      const expectedElements = [fixtureEl.querySelector('[contenteditable=\"true\"]')]\n\n      expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)\n    })\n\n    it('should not return disabled elements', () => {\n      fixtureEl.innerHTML = '<button disabled=\"true\">lorem</button>'\n\n      const expectedElements = []\n\n      expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)\n    })\n\n    it('should not return invisible elements', () => {\n      fixtureEl.innerHTML = '<button style=\"display:none;\">lorem</button>'\n\n      const expectedElements = []\n\n      expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)\n    })\n  })\n})\n\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/dropdown.spec.js",
    "content": "import Dropdown from '../../src/dropdown'\nimport EventHandler from '../../src/dom/event-handler'\nimport { noop } from '../../src/util/index'\nimport { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Dropdown', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Dropdown.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Dropdown.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DefaultType', () => {\n    it('should return plugin default type config', () => {\n      expect(Dropdown.DefaultType).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Dropdown.DATA_KEY).toEqual('bs.dropdown')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdownBySelector = new Dropdown('[data-bs-toggle=\"dropdown\"]')\n      const dropdownByElement = new Dropdown(btnDropdown)\n\n      expect(dropdownBySelector._element).toEqual(btnDropdown)\n      expect(dropdownByElement._element).toEqual(btnDropdown)\n    })\n\n    it('should work on invalid markup', () => {\n      return new Promise(resolve => {\n        // TODO: REMOVE in v6\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const dropdownElem = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(dropdownElem)\n\n        dropdownElem.addEventListener('shown.bs.dropdown', () => {\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should create offset modifier correctly when offset option is a function', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown, {\n          offset: getOffset,\n          popperConfig: {\n            onFirstUpdate(state) {\n              expect(getOffset).toHaveBeenCalledWith({\n                popper: state.rects.popper,\n                reference: state.rects.reference,\n                placement: state.placement\n              }, btnDropdown)\n              resolve()\n            }\n          }\n        })\n        const offset = dropdown._getOffset()\n\n        expect(typeof offset).toEqual('function')\n\n        dropdown.show()\n      })\n    })\n\n    it('should create offset modifier correctly when offset option is a string into data attribute', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-offset=\"10,20\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdown = new Dropdown(btnDropdown)\n\n      expect(dropdown._getOffset()).toEqual([10, 20])\n    })\n\n    it('should allow to pass config to Popper with `popperConfig`', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdown = new Dropdown(btnDropdown, {\n        popperConfig: {\n          placement: 'left'\n        }\n      })\n\n      const popperConfig = dropdown._getPopperConfig()\n\n      expect(popperConfig.placement).toEqual('left')\n    })\n\n    it('should allow to pass config to Popper with `popperConfig` as a function', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-placement=\"right\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })\n      const dropdown = new Dropdown(btnDropdown, {\n        popperConfig: getPopperConfig\n      })\n\n      const popperConfig = dropdown._getPopperConfig()\n\n      expect(getPopperConfig).toHaveBeenCalled()\n      expect(popperConfig.placement).toEqual('left')\n    })\n  })\n\n  describe('toggle', () => {\n    it('should toggle a dropdown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should destroy old popper references on toggle', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"first dropdown\">',\n          '  <button class=\"firstBtn btn\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>',\n          '<div class=\"second dropdown\">',\n          '  <button class=\"secondBtn btn\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown1 = fixtureEl.querySelector('.firstBtn')\n        const btnDropdown2 = fixtureEl.querySelector('.secondBtn')\n        const firstDropdownEl = fixtureEl.querySelector('.first')\n        const secondDropdownEl = fixtureEl.querySelector('.second')\n        const dropdown1 = new Dropdown(btnDropdown1)\n\n        firstDropdownEl.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown1).toHaveClass('show')\n          spyOn(dropdown1._popper, 'destroy')\n          btnDropdown2.click()\n        })\n\n        secondDropdownEl.addEventListener('shown.bs.dropdown', () => setTimeout(() => {\n          expect(dropdown1._popper.destroy).toHaveBeenCalled()\n          resolve()\n        }))\n\n        dropdown1.toggle()\n      })\n    })\n\n    it('should toggle a dropdown and add/remove event listener on mobile', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const defaultValueOnTouchStart = document.documentElement.ontouchstart\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        document.documentElement.ontouchstart = noop\n        const spy = spyOn(EventHandler, 'on')\n        const spyOff = spyOn(EventHandler, 'off')\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)\n\n          dropdown.toggle()\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(btnDropdown).not.toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')\n          expect(spyOff).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)\n\n          document.documentElement.ontouchstart = defaultValueOnTouchStart\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropdown at the right', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu dropdown-menu-end\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a centered dropdown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown-center\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropup', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropup\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropupEl = fixtureEl.querySelector('.dropup')\n        const dropdown = new Dropdown(btnDropdown)\n\n        dropupEl.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropup centered', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropup-center\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropupEl = fixtureEl.querySelector('.dropup-center')\n        const dropdown = new Dropdown(btnDropdown)\n\n        dropupEl.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropup at the right', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropup\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu dropdown-menu-end\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropupEl = fixtureEl.querySelector('.dropup')\n        const dropdown = new Dropdown(btnDropdown)\n\n        dropupEl.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropend', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropend\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropendEl = fixtureEl.querySelector('.dropend')\n        const dropdown = new Dropdown(btnDropdown)\n\n        dropendEl.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropstart', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropstart\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropstartEl = fixtureEl.querySelector('.dropstart')\n        const dropdown = new Dropdown(btnDropdown)\n\n        dropstartEl.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropdown with parent reference', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown, {\n          reference: 'parent'\n        })\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropdown with a dom node reference', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown, {\n          reference: fixtureEl\n        })\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropdown with a jquery object reference', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown, {\n          reference: { 0: fixtureEl, jquery: 'jQuery' }\n        })\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          resolve()\n        })\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should toggle a dropdown with a valid virtual element reference', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle visually-hidden\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const virtualElement = {\n          nodeType: 1,\n          getBoundingClientRect() {\n            return {\n              width: 0,\n              height: 0,\n              top: 0,\n              right: 0,\n              bottom: 0,\n              left: 0\n            }\n          }\n        }\n\n        expect(() => new Dropdown(btnDropdown, {\n          reference: {}\n        })).toThrowError(TypeError, 'DROPDOWN: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.')\n\n        expect(() => new Dropdown(btnDropdown, {\n          reference: {\n            getBoundingClientRect: 'not-a-function'\n          }\n        })).toThrowError(TypeError, 'DROPDOWN: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.')\n\n        // use onFirstUpdate as Poppers internal update is executed async\n        const dropdown = new Dropdown(btnDropdown, {\n          reference: virtualElement,\n          popperConfig: {\n            onFirstUpdate() {\n              expect(spy).toHaveBeenCalled()\n              expect(btnDropdown).toHaveClass('show')\n              expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n              resolve()\n            }\n          }\n        })\n\n        const spy = spyOn(virtualElement, 'getBoundingClientRect').and.callThrough()\n\n        dropdown.toggle()\n      })\n    })\n\n    it('should not toggle a dropdown if the element is disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button disabled class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.toggle()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        })\n      })\n    })\n\n    it('should not toggle a dropdown if the element contains .disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle disabled\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.toggle()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        })\n      })\n    })\n\n    it('should not toggle a dropdown if the menu is shown', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu show\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.toggle()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        })\n      })\n    })\n\n    it('should not toggle a dropdown if show event is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('show.bs.dropdown', event => {\n          event.preventDefault()\n        })\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.toggle()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        })\n      })\n    })\n  })\n\n  describe('show', () => {\n    it('should show a dropdown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should not show a dropdown if the element is disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button disabled class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.show()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not show a dropdown if the element contains .disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle disabled\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.show()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not show a dropdown if the menu is shown', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu show\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.show()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not show a dropdown if show event is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('show.bs.dropdown', event => {\n          event.preventDefault()\n        })\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          reject(new Error('should not throw shown.bs.dropdown event'))\n        })\n\n        dropdown.show()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should hide a dropdown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"true\">Dropdown</button>',\n          '  <div class=\"dropdown-menu show\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(dropdownMenu).not.toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')\n          resolve()\n        })\n\n        dropdown.hide()\n      })\n    })\n\n    it('should hide a dropdown and destroy popper', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          spyOn(dropdown._popper, 'destroy')\n          dropdown.hide()\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(dropdown._popper.destroy).toHaveBeenCalled()\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should not hide a dropdown if the element is disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button disabled class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu show\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          reject(new Error('should not throw hidden.bs.dropdown event'))\n        })\n\n        dropdown.hide()\n\n        setTimeout(() => {\n          expect(dropdownMenu).toHaveClass('show')\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not hide a dropdown if the element contains .disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle disabled\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu show\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          reject(new Error('should not throw hidden.bs.dropdown event'))\n        })\n\n        dropdown.hide()\n\n        setTimeout(() => {\n          expect(dropdownMenu).toHaveClass('show')\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not hide a dropdown if the menu is not shown', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          reject(new Error('should not throw hidden.bs.dropdown event'))\n        })\n\n        dropdown.hide()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should not hide a dropdown if hide event is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu show\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('hide.bs.dropdown', event => {\n          event.preventDefault()\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          reject(new Error('should not throw hidden.bs.dropdown event'))\n        })\n\n        dropdown.hide()\n\n        setTimeout(() => {\n          expect(dropdownMenu).toHaveClass('show')\n          resolve()\n        })\n      })\n    })\n\n    it('should remove event listener on touch-enabled device that was added in show method', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const defaultValueOnTouchStart = document.documentElement.ontouchstart\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(btnDropdown)\n\n        document.documentElement.ontouchstart = noop\n        const spy = spyOn(EventHandler, 'off')\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          dropdown.hide()\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(btnDropdown).not.toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')\n          expect(spy).toHaveBeenCalled()\n\n          document.documentElement.ontouchstart = defaultValueOnTouchStart\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose dropdown', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n      const dropdown = new Dropdown(btnDropdown)\n\n      expect(dropdown._popper).toBeNull()\n      expect(dropdown._menu).not.toBeNull()\n      expect(dropdown._element).not.toBeNull()\n      const spy = spyOn(EventHandler, 'off')\n\n      dropdown.dispose()\n\n      expect(dropdown._menu).toBeNull()\n      expect(dropdown._element).toBeNull()\n      expect(spy).toHaveBeenCalledWith(btnDropdown, Dropdown.EVENT_KEY)\n    })\n\n    it('should dispose dropdown with Popper', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdown = new Dropdown(btnDropdown)\n\n      dropdown.toggle()\n\n      expect(dropdown._popper).not.toBeNull()\n      expect(dropdown._menu).not.toBeNull()\n      expect(dropdown._element).not.toBeNull()\n\n      dropdown.dispose()\n\n      expect(dropdown._popper).toBeNull()\n      expect(dropdown._menu).toBeNull()\n      expect(dropdown._element).toBeNull()\n    })\n  })\n\n  describe('update', () => {\n    it('should call Popper and detect navbar on update', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdown = new Dropdown(btnDropdown)\n\n      dropdown.toggle()\n\n      expect(dropdown._popper).not.toBeNull()\n\n      const spyUpdate = spyOn(dropdown._popper, 'update')\n      const spyDetect = spyOn(dropdown, '_detectNavbar')\n\n      dropdown.update()\n\n      expect(spyUpdate).toHaveBeenCalled()\n      expect(spyDetect).toHaveBeenCalled()\n    })\n\n    it('should just detect navbar on update', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdown = new Dropdown(btnDropdown)\n\n      const spy = spyOn(dropdown, '_detectNavbar')\n\n      dropdown.update()\n\n      expect(dropdown._popper).toBeNull()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('data-api', () => {\n    it('should show and hide a dropdown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        let showEventTriggered = false\n        let hideEventTriggered = false\n\n        btnDropdown.addEventListener('show.bs.dropdown', () => {\n          showEventTriggered = true\n        })\n\n        btnDropdown.addEventListener('shown.bs.dropdown', event => setTimeout(() => {\n          expect(btnDropdown).toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n          expect(showEventTriggered).toBeTrue()\n          expect(event.relatedTarget).toEqual(btnDropdown)\n          document.body.click()\n        }))\n\n        btnDropdown.addEventListener('hide.bs.dropdown', () => {\n          hideEventTriggered = true\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', event => {\n          expect(btnDropdown).not.toHaveClass('show')\n          expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')\n          expect(hideEventTriggered).toBeTrue()\n          expect(event.relatedTarget).toEqual(btnDropdown)\n          resolve()\n        })\n\n        btnDropdown.click()\n      })\n    })\n\n    it('should not use \"static\" Popper in navbar', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar navbar-expand-md bg-light\">',\n          '  <div class=\"dropdown\">',\n          '    <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '    <div class=\"dropdown-menu\">',\n          '      <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '    </div>',\n          '  </div>',\n          '</nav>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(dropdown._popper).not.toBeNull()\n          expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should not collapse the dropdown when clicking a select option nested in the dropdown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <select>',\n          '      <option selected>Open this select menu</option>',\n          '      <option value=\"1\">One</option>',\n          '    </select>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        const hideSpy = spyOn(dropdown, '_completeHide')\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          const clickEvent = new MouseEvent('click', {\n            bubbles: true\n          })\n\n          dropdownMenu.querySelector('option').dispatchEvent(clickEvent)\n        })\n\n        dropdownMenu.addEventListener('click', event => {\n          expect(event.target.tagName).toMatch(/select|option/i)\n\n          Dropdown.clearMenus(event)\n\n          setTimeout(() => {\n            expect(hideSpy).not.toHaveBeenCalled()\n            resolve()\n          }, 10)\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should manage bs attribute `data-bs-popper`=\"static\" when dropdown is in navbar', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar navbar-expand-md bg-light\">',\n          '  <div class=\"dropdown\">',\n          '    <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>',\n          '    <div class=\"dropdown-menu\">',\n          '      <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '    </div>',\n          '  </div>',\n          '</nav>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')\n          dropdown.hide()\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should not use Popper if display set to static', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          // Popper adds this attribute when we use it\n          expect(dropdownMenu.getAttribute('data-popper-placement')).toBeNull()\n          resolve()\n        })\n\n        btnDropdown.click()\n      })\n    })\n\n    it('should manage bs attribute `data-bs-popper`=\"static\" when display set to static', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n        const dropdown = new Dropdown(btnDropdown)\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')\n          dropdown.hide()\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should remove \"show\" class if tabbing outside of menu', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        btnDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(btnDropdown).toHaveClass('show')\n\n          const keyup = createEvent('keyup')\n\n          keyup.key = 'Tab'\n          document.dispatchEvent(keyup)\n        })\n\n        btnDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect(btnDropdown).not.toHaveClass('show')\n          resolve()\n        })\n\n        btnDropdown.click()\n      })\n    })\n\n    it('should remove \"show\" class if body is clicked, with multiple dropdowns', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"nav\">',\n          '  <div class=\"dropdown\" id=\"testmenu\">',\n          '    <a class=\"dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#testmenu\">Test menu</a>',\n          '    <div class=\"dropdown-menu\">',\n          '      <a class=\"dropdown-item\" href=\"#sub1\">Submenu 1</a>',\n          '    </div>',\n          '  </div>',\n          '</div>',\n          '<div class=\"btn-group\">',\n          '  <button class=\"btn\">Actions</button>',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\"></button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Action 1</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle=\"dropdown\"]')\n\n        expect(triggerDropdownList).toHaveSize(2)\n\n        const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList\n\n        triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {\n          expect(triggerDropdownFirst).toHaveClass('show')\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)\n          document.body.click()\n        })\n\n        triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)\n          triggerDropdownLast.click()\n        })\n\n        triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {\n          expect(triggerDropdownLast).toHaveClass('show')\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)\n          document.body.click()\n        })\n\n        triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)\n          resolve()\n        })\n\n        triggerDropdownFirst.click()\n      })\n    })\n\n    it('should remove \"show\" class if body if tabbing outside of menu, with multiple dropdowns', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <a class=\"dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#testmenu\">Test menu</a>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#sub1\">Submenu 1</a>',\n          '  </div>',\n          '</div>',\n          '<div class=\"btn-group\">',\n          '  <button class=\"btn\">Actions</button>',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\"></button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Action 1</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle=\"dropdown\"]')\n\n        expect(triggerDropdownList).toHaveSize(2)\n\n        const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList\n\n        triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {\n          expect(triggerDropdownFirst).toHaveClass('show')\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)\n\n          const keyup = createEvent('keyup')\n          keyup.key = 'Tab'\n\n          document.dispatchEvent(keyup)\n        })\n\n        triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)\n          triggerDropdownLast.click()\n        })\n\n        triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {\n          expect(triggerDropdownLast).toHaveClass('show')\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)\n\n          const keyup = createEvent('keyup')\n          keyup.key = 'Tab'\n\n          document.dispatchEvent(keyup)\n        })\n\n        triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {\n          expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)\n          resolve()\n        })\n\n        triggerDropdownFirst.click()\n      })\n    })\n\n    it('should be able to identify clicked dropdown, even with multiple dropdowns in the same tag', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button id=\"dropdown1\" class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown toggle</button>',\n        '  <div id=\"menu1\" class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n        '  </div>',\n        '  <button id=\"dropdown2\" class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown toggle</button>',\n        '  <div id=\"menu2\" class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const dropdownToggle1 = fixtureEl.querySelector('#dropdown1')\n      const dropdownToggle2 = fixtureEl.querySelector('#dropdown2')\n      const dropdownMenu1 = fixtureEl.querySelector('#menu1')\n      const dropdownMenu2 = fixtureEl.querySelector('#menu2')\n      const spy = spyOn(Dropdown, 'getOrCreateInstance').and.callThrough()\n\n      dropdownToggle1.click()\n      expect(spy).toHaveBeenCalledWith(dropdownToggle1)\n\n      dropdownToggle2.click()\n      expect(spy).toHaveBeenCalledWith(dropdownToggle2)\n\n      dropdownMenu1.click()\n      expect(spy).toHaveBeenCalledWith(dropdownToggle1)\n\n      dropdownMenu2.click()\n      expect(spy).toHaveBeenCalledWith(dropdownToggle2)\n    })\n\n    it('should be able to show the proper menu, even with multiple dropdowns in the same tag', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button id=\"dropdown1\" class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown toggle</button>',\n        '  <div id=\"menu1\" class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n        '  </div>',\n        '  <button id=\"dropdown2\" class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown toggle</button>',\n        '  <div id=\"menu2\" class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const dropdownToggle1 = fixtureEl.querySelector('#dropdown1')\n      const dropdownToggle2 = fixtureEl.querySelector('#dropdown2')\n      const dropdownMenu1 = fixtureEl.querySelector('#menu1')\n      const dropdownMenu2 = fixtureEl.querySelector('#menu2')\n\n      dropdownToggle1.click()\n      expect(dropdownMenu1).toHaveClass('show')\n      expect(dropdownMenu2).not.toHaveClass('show')\n\n      dropdownToggle2.click()\n      expect(dropdownMenu1).not.toHaveClass('show')\n      expect(dropdownMenu2).toHaveClass('show')\n    })\n\n    it('should fire hide and hidden event without a clickEvent if event type is not click', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#sub1\">Submenu 1</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        triggerDropdown.addEventListener('hide.bs.dropdown', event => {\n          expect(event.clickEvent).toBeUndefined()\n        })\n\n        triggerDropdown.addEventListener('hidden.bs.dropdown', event => {\n          expect(event.clickEvent).toBeUndefined()\n          resolve()\n        })\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          const keydown = createEvent('keydown')\n\n          keydown.key = 'Escape'\n          triggerDropdown.dispatchEvent(keydown)\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should bubble up the events to the parent elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#subMenu\">Sub menu</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownParent = fixtureEl.querySelector('.dropdown')\n        const dropdown = new Dropdown(triggerDropdown)\n\n        const showFunction = jasmine.createSpy('showFunction')\n        dropdownParent.addEventListener('show.bs.dropdown', showFunction)\n\n        const shownFunction = jasmine.createSpy('shownFunction')\n        dropdownParent.addEventListener('shown.bs.dropdown', () => {\n          shownFunction()\n          dropdown.hide()\n        })\n\n        const hideFunction = jasmine.createSpy('hideFunction')\n        dropdownParent.addEventListener('hide.bs.dropdown', hideFunction)\n\n        dropdownParent.addEventListener('hidden.bs.dropdown', () => {\n          expect(showFunction).toHaveBeenCalled()\n          expect(shownFunction).toHaveBeenCalled()\n          expect(hideFunction).toHaveBeenCalled()\n          resolve()\n        })\n\n        dropdown.show()\n      })\n    })\n\n    it('should ignore keyboard events within <input>s and <textarea>s', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#sub1\">Submenu 1</a>',\n          '    <input type=\"text\">',\n          '    <textarea></textarea>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const input = fixtureEl.querySelector('input')\n        const textarea = fixtureEl.querySelector('textarea')\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          input.focus()\n          const keydown = createEvent('keydown')\n\n          keydown.key = 'ArrowUp'\n          input.dispatchEvent(keydown)\n\n          expect(document.activeElement).toEqual(input, 'input still focused')\n\n          textarea.focus()\n          textarea.dispatchEvent(keydown)\n\n          expect(document.activeElement).toEqual(textarea, 'textarea still focused')\n          resolve()\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should skip disabled element when using keyboard navigation', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item disabled\" href=\"#sub1\">Submenu 1</a>',\n          '    <button class=\"dropdown-item\" type=\"button\" disabled>Disabled button</button>',\n          '    <a id=\"item1\" class=\"dropdown-item\" href=\"#\">Another link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          const keydown = createEvent('keydown')\n          keydown.key = 'ArrowDown'\n\n          triggerDropdown.dispatchEvent(keydown)\n          triggerDropdown.dispatchEvent(keydown)\n\n          expect(document.activeElement).not.toHaveClass('disabled')\n          expect(document.activeElement.hasAttribute('disabled')).toBeFalse()\n          resolve()\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should skip hidden element when using keyboard navigation', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<style>',\n          '  .d-none {',\n          '    display: none;',\n          '  }',\n          '</style>',\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <button class=\"dropdown-item d-none\" type=\"button\">Hidden button by class</button>',\n          '    <a class=\"dropdown-item\" href=\"#sub1\" style=\"display: none\">Hidden link</a>',\n          '    <a class=\"dropdown-item\" href=\"#sub1\" style=\"visibility: hidden\">Hidden link</a>',\n          '    <a id=\"item1\" class=\"dropdown-item\" href=\"#\">Another link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          const keydown = createEvent('keydown')\n          keydown.key = 'ArrowDown'\n\n          triggerDropdown.dispatchEvent(keydown)\n\n          expect(document.activeElement).not.toHaveClass('d-none')\n          expect(document.activeElement.style.display).not.toEqual('none')\n          expect(document.activeElement.style.visibility).not.toEqual('hidden')\n\n          resolve()\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should focus next/previous element when using keyboard navigation', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a id=\"item1\" class=\"dropdown-item\" href=\"#\">A link</a>',\n          '    <a id=\"item2\" class=\"dropdown-item\" href=\"#\">Another link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const item1 = fixtureEl.querySelector('#item1')\n        const item2 = fixtureEl.querySelector('#item2')\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          const keydownArrowDown = createEvent('keydown')\n          keydownArrowDown.key = 'ArrowDown'\n\n          triggerDropdown.dispatchEvent(keydownArrowDown)\n          expect(document.activeElement).toEqual(item1, 'item1 is focused')\n\n          document.activeElement.dispatchEvent(keydownArrowDown)\n          expect(document.activeElement).toEqual(item2, 'item2 is focused')\n\n          const keydownArrowUp = createEvent('keydown')\n          keydownArrowUp.key = 'ArrowUp'\n\n          document.activeElement.dispatchEvent(keydownArrowUp)\n          expect(document.activeElement).toEqual(item1, 'item1 is focused')\n\n          resolve()\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should open the dropdown and focus on the last item when using ArrowUp for the first time', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a id=\"item1\" class=\"dropdown-item\" href=\"#\">A link</a>',\n          '    <a id=\"item2\" class=\"dropdown-item\" href=\"#\">Another link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const lastItem = fixtureEl.querySelector('#item2')\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          setTimeout(() => {\n            expect(document.activeElement).toEqual(lastItem, 'item2 is focused')\n            resolve()\n          })\n        })\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'ArrowUp'\n        triggerDropdown.dispatchEvent(keydown)\n      })\n    })\n\n    it('should open the dropdown and focus on the first item when using ArrowDown for the first time', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a id=\"item1\" class=\"dropdown-item\" href=\"#\">A link</a>',\n          '    <a id=\"item2\" class=\"dropdown-item\" href=\"#\">Another link</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const firstItem = fixtureEl.querySelector('#item1')\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          setTimeout(() => {\n            expect(document.activeElement).toEqual(firstItem, 'item1 is focused')\n            resolve()\n          })\n        })\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'ArrowDown'\n        triggerDropdown.dispatchEvent(keydown)\n      })\n    })\n\n    it('should not close the dropdown if the user clicks on a text field within dropdown-menu', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <input type=\"text\">',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const input = fixtureEl.querySelector('input')\n\n        input.addEventListener('click', () => {\n          expect(triggerDropdown).toHaveClass('show')\n          resolve()\n        })\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(triggerDropdown).toHaveClass('show')\n          input.dispatchEvent(createEvent('click'))\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should not close the dropdown if the user clicks on a textarea within dropdown-menu', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <textarea></textarea>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const textarea = fixtureEl.querySelector('textarea')\n\n        textarea.addEventListener('click', () => {\n          expect(triggerDropdown).toHaveClass('show')\n          resolve()\n        })\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          expect(triggerDropdown).toHaveClass('show')\n          textarea.dispatchEvent(createEvent('click'))\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should close the dropdown if the user clicks on a text field that is not contained within dropdown-menu', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '  </div>',\n          '</div>',\n          '<input type=\"text\">'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const input = fixtureEl.querySelector('input')\n\n        triggerDropdown.addEventListener('hidden.bs.dropdown', () => {\n          expect().nothing()\n          resolve()\n        })\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          input.dispatchEvent(createEvent('click', {\n            bubbles: true\n          }))\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should ignore keyboard events for <input>s and <textarea>s within dropdown-menu, except for escape key', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#sub1\">Submenu 1</a>',\n          '    <input type=\"text\">',\n          '    <textarea></textarea>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const input = fixtureEl.querySelector('input')\n        const textarea = fixtureEl.querySelector('textarea')\n\n        const test = (eventKey, elementToDispatch) => {\n          const event = createEvent('keydown')\n          event.key = eventKey\n          elementToDispatch.focus()\n          elementToDispatch.dispatchEvent(event)\n          expect(document.activeElement).toEqual(elementToDispatch, `${elementToDispatch.tagName} still focused`)\n        }\n\n        const keydownEscape = createEvent('keydown')\n        keydownEscape.key = 'Escape'\n\n        triggerDropdown.addEventListener('shown.bs.dropdown', () => {\n          // Key Space\n          test('Space', input)\n\n          test('Space', textarea)\n\n          // Key ArrowUp\n          test('ArrowUp', input)\n\n          test('ArrowUp', textarea)\n\n          // Key ArrowDown\n          test('ArrowDown', input)\n\n          test('ArrowDown', textarea)\n\n          // Key Escape\n          input.focus()\n          input.dispatchEvent(keydownEscape)\n\n          expect(triggerDropdown).not.toHaveClass('show')\n          resolve()\n        })\n\n        triggerDropdown.click()\n      })\n    })\n\n    it('should not open dropdown if escape key was pressed on the toggle', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"tabs\">',\n          '  <div class=\"dropdown\">',\n          '    <button disabled class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '    <div class=\"dropdown-menu\">',\n          '      <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n          '      <a class=\"dropdown-item\" href=\"#\">Something else here</a>',\n          '      <div class=\"divider\"></div>',\n          '      <a class=\"dropdown-item\" href=\"#\">Another link</a>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdown = new Dropdown(triggerDropdown)\n        const button = fixtureEl.querySelector('button[data-bs-toggle=\"dropdown\"]')\n\n        const spy = spyOn(dropdown, 'toggle')\n\n        // Key escape\n        button.focus()\n        // Key escape\n        const keydownEscape = createEvent('keydown')\n        keydownEscape.key = 'Escape'\n        button.dispatchEvent(keydownEscape)\n\n        setTimeout(() => {\n          expect(spy).not.toHaveBeenCalled()\n          expect(triggerDropdown).not.toHaveClass('show')\n          resolve()\n        }, 20)\n      })\n    })\n\n    it('should propagate escape key events if dropdown is closed', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"parent\">',\n          '  <div class=\"dropdown\">',\n          '    <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '    <div class=\"dropdown-menu\">',\n          '      <a class=\"dropdown-item\" href=\"#\">Some Item</a>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const parent = fixtureEl.querySelector('.parent')\n        const toggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        const parentKeyHandler = jasmine.createSpy('parentKeyHandler')\n\n        parent.addEventListener('keydown', parentKeyHandler)\n        parent.addEventListener('keyup', () => {\n          expect(parentKeyHandler).toHaveBeenCalled()\n          resolve()\n        })\n\n        const keydownEscape = createEvent('keydown', { bubbles: true })\n        keydownEscape.key = 'Escape'\n        const keyupEscape = createEvent('keyup', { bubbles: true })\n        keyupEscape.key = 'Escape'\n\n        toggle.focus()\n        toggle.dispatchEvent(keydownEscape)\n        toggle.dispatchEvent(keyupEscape)\n      })\n    })\n\n    it('should not propagate escape key events if dropdown is open', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"parent\">',\n          '  <div class=\"dropdown\">',\n          '    <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '    <div class=\"dropdown-menu\">',\n          '      <a class=\"dropdown-item\" href=\"#\">Some Item</a>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const parent = fixtureEl.querySelector('.parent')\n        const toggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        const parentKeyHandler = jasmine.createSpy('parentKeyHandler')\n\n        parent.addEventListener('keydown', parentKeyHandler)\n        parent.addEventListener('keyup', () => {\n          expect(parentKeyHandler).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        const keydownEscape = createEvent('keydown', { bubbles: true })\n        keydownEscape.key = 'Escape'\n        const keyupEscape = createEvent('keyup', { bubbles: true })\n        keyupEscape.key = 'Escape'\n\n        toggle.click()\n        toggle.dispatchEvent(keydownEscape)\n        toggle.dispatchEvent(keyupEscape)\n      })\n    })\n\n    it('should close dropdown using `escape` button, and return focus to its trigger', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Some Item</a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n\n        toggle.addEventListener('shown.bs.dropdown', () => {\n          const keydownEvent = createEvent('keydown', { bubbles: true })\n          keydownEvent.key = 'ArrowDown'\n          toggle.dispatchEvent(keydownEvent)\n          keydownEvent.key = 'Escape'\n          toggle.dispatchEvent(keydownEvent)\n        })\n\n        toggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {\n          expect(document.activeElement).toEqual(toggle)\n          resolve()\n        }))\n\n        toggle.click()\n      })\n    })\n\n    it('should close dropdown (only) by clicking inside the dropdown menu when it has data-attribute `data-bs-auto-close=\"inside\"`', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"inside\">Dropdown toggle</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n          ' </div>',\n          '</div>'\n        ].join('')\n\n        const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n\n        const expectDropdownToBeOpened = () => setTimeout(() => {\n          expect(dropdownToggle).toHaveClass('show')\n          dropdownMenu.click()\n        }, 150)\n\n        dropdownToggle.addEventListener('shown.bs.dropdown', () => {\n          document.documentElement.click()\n          expectDropdownToBeOpened()\n        })\n\n        dropdownToggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {\n          expect(dropdownToggle).not.toHaveClass('show')\n          resolve()\n        }))\n\n        dropdownToggle.click()\n      })\n    })\n\n    it('should close dropdown (only) by clicking outside the dropdown menu when it has data-attribute `data-bs-auto-close=\"outside\"`', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"outside\">Dropdown toggle</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n          ' </div>',\n          '</div>'\n        ].join('')\n\n        const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n\n        const expectDropdownToBeOpened = () => setTimeout(() => {\n          expect(dropdownToggle).toHaveClass('show')\n          document.documentElement.click()\n        }, 150)\n\n        dropdownToggle.addEventListener('shown.bs.dropdown', () => {\n          dropdownMenu.click()\n          expectDropdownToBeOpened()\n        })\n\n        dropdownToggle.addEventListener('hidden.bs.dropdown', () => {\n          expect(dropdownToggle).not.toHaveClass('show')\n          resolve()\n        })\n\n        dropdownToggle.click()\n      })\n    })\n\n    it('should not close dropdown by clicking inside or outside the dropdown menu when it has data-attribute `data-bs-auto-close=\"false\"`', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"dropdown\">',\n          '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"false\">Dropdown toggle</button>',\n          '  <div class=\"dropdown-menu\">',\n          '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n          ' </div>',\n          '</div>'\n        ].join('')\n\n        const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n\n        const expectDropdownToBeOpened = (shouldTriggerClick = true) => setTimeout(() => {\n          expect(dropdownToggle).toHaveClass('show')\n          if (shouldTriggerClick) {\n            document.documentElement.click()\n          } else {\n            resolve()\n          }\n\n          expectDropdownToBeOpened(false)\n        }, 150)\n\n        dropdownToggle.addEventListener('shown.bs.dropdown', () => {\n          dropdownMenu.click()\n          expectDropdownToBeOpened()\n        })\n\n        dropdownToggle.click()\n      })\n    })\n\n    it('should be able to identify clicked dropdown, no matter the markup order', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item\" href=\"#\">Dropdown item</a>',\n        '  </div>',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown toggle</button>',\n        '</div>'\n      ].join('')\n\n      const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')\n      const spy = spyOn(Dropdown, 'getOrCreateInstance').and.callThrough()\n\n      dropdownToggle.click()\n      expect(spy).toHaveBeenCalledWith(dropdownToggle)\n      dropdownMenu.click()\n      expect(spy).toHaveBeenCalledWith(dropdownToggle)\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a dropdown', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.dropdown = Dropdown.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.dropdown.call(jQueryMock)\n\n      expect(Dropdown.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create a dropdown', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const dropdown = new Dropdown(div)\n\n      jQueryMock.fn.dropdown = Dropdown.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.dropdown.call(jQueryMock)\n\n      expect(Dropdown.getInstance(div)).toEqual(dropdown)\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.dropdown = Dropdown.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.dropdown.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return dropdown instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const dropdown = new Dropdown(div)\n\n      expect(Dropdown.getInstance(div)).toEqual(dropdown)\n      expect(Dropdown.getInstance(div)).toBeInstanceOf(Dropdown)\n    })\n\n    it('should return null when there is no dropdown instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Dropdown.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return dropdown instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const dropdown = new Dropdown(div)\n\n      expect(Dropdown.getOrCreateInstance(div)).toEqual(dropdown)\n      expect(Dropdown.getInstance(div)).toEqual(Dropdown.getOrCreateInstance(div, {}))\n      expect(Dropdown.getOrCreateInstance(div)).toBeInstanceOf(Dropdown)\n    })\n\n    it('should return new instance when there is no dropdown instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Dropdown.getInstance(div)).toBeNull()\n      expect(Dropdown.getOrCreateInstance(div)).toBeInstanceOf(Dropdown)\n    })\n\n    it('should return new instance when there is no dropdown instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Dropdown.getInstance(div)).toBeNull()\n      const dropdown = Dropdown.getOrCreateInstance(div, {\n        display: 'dynamic'\n      })\n      expect(dropdown).toBeInstanceOf(Dropdown)\n\n      expect(dropdown._config.display).toEqual('dynamic')\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const dropdown = new Dropdown(div, {\n        display: 'dynamic'\n      })\n      expect(Dropdown.getInstance(div)).toEqual(dropdown)\n\n      const dropdown2 = Dropdown.getOrCreateInstance(div, {\n        display: 'static'\n      })\n      expect(dropdown).toBeInstanceOf(Dropdown)\n      expect(dropdown2).toEqual(dropdown)\n\n      expect(dropdown2._config.display).toEqual('dynamic')\n    })\n  })\n\n  it('should open dropdown when pressing keydown or keyup', () => {\n    return new Promise(resolve => {\n      fixtureEl.innerHTML = [\n        '<div class=\"dropdown\">',\n        '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n        '  <div class=\"dropdown-menu\">',\n        '    <a class=\"dropdown-item disabled\" href=\"#sub1\">Submenu 1</a>',\n        '    <button class=\"dropdown-item\" type=\"button\" disabled>Disabled button</button>',\n        '    <a id=\"item1\" class=\"dropdown-item\" href=\"#\">Another link</a>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const dropdown = fixtureEl.querySelector('.dropdown')\n\n      const keydown = createEvent('keydown')\n      keydown.key = 'ArrowDown'\n\n      const keyup = createEvent('keyup')\n      keyup.key = 'ArrowUp'\n\n      const handleArrowDown = () => {\n        expect(triggerDropdown).toHaveClass('show')\n        expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')\n        setTimeout(() => {\n          dropdown.hide()\n          keydown.key = 'ArrowUp'\n          triggerDropdown.dispatchEvent(keyup)\n        }, 20)\n      }\n\n      const handleArrowUp = () => {\n        expect(triggerDropdown).toHaveClass('show')\n        expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')\n        resolve()\n      }\n\n      dropdown.addEventListener('shown.bs.dropdown', event => {\n        if (event.target.key === 'ArrowDown') {\n          handleArrowDown()\n        } else {\n          handleArrowUp()\n        }\n      })\n\n      triggerDropdown.dispatchEvent(keydown)\n    })\n  })\n\n  it('should allow `data-bs-toggle=\"dropdown\"` click events to bubble up', () => {\n    fixtureEl.innerHTML = [\n      '<div class=\"dropdown\">',\n      '  <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\">Dropdown</button>',\n      '  <div class=\"dropdown-menu\">',\n      '    <a class=\"dropdown-item\" href=\"#\">Secondary link</a>',\n      '  </div>',\n      '</div>'\n    ].join('')\n\n    const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n    const clickListener = jasmine.createSpy('clickListener')\n    const delegatedClickListener = jasmine.createSpy('delegatedClickListener')\n\n    btnDropdown.addEventListener('click', clickListener)\n    document.addEventListener('click', delegatedClickListener)\n\n    btnDropdown.click()\n\n    expect(clickListener).toHaveBeenCalled()\n    expect(delegatedClickListener).toHaveBeenCalled()\n  })\n\n  it('should open the dropdown when clicking the child element inside `data-bs-toggle=\"dropdown\"`', () => {\n    return new Promise(resolve => {\n      fixtureEl.innerHTML = [\n        '<div class=\"container\">',\n        '  <div class=\"dropdown\">',\n        '    <button class=\"btn dropdown-toggle\" data-bs-toggle=\"dropdown\"><span id=\"childElement\">Dropdown</span></button>',\n        '    <div class=\"dropdown-menu\">',\n        '      <a class=\"dropdown-item\" href=\"#subMenu\">Sub menu</a>',\n        '    </div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle=\"dropdown\"]')\n      const childElement = fixtureEl.querySelector('#childElement')\n\n      btnDropdown.addEventListener('shown.bs.dropdown', () => setTimeout(() => {\n        expect(btnDropdown).toHaveClass('show')\n        expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')\n        resolve()\n      }))\n\n      childElement.click()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/jquery.spec.js",
    "content": "/* eslint-env jquery */\n\nimport Alert from '../../src/alert'\nimport Button from '../../src/button'\nimport Carousel from '../../src/carousel'\nimport Collapse from '../../src/collapse'\nimport Dropdown from '../../src/dropdown'\nimport Modal from '../../src/modal'\nimport Offcanvas from '../../src/offcanvas'\nimport Popover from '../../src/popover'\nimport ScrollSpy from '../../src/scrollspy'\nimport Tab from '../../src/tab'\nimport Toast from '../../src/toast'\nimport Tooltip from '../../src/tooltip'\nimport { clearFixture, getFixture } from '../helpers/fixture'\n\ndescribe('jQuery', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  it('should add all plugins in jQuery', () => {\n    expect(Alert.jQueryInterface).toEqual(jQuery.fn.alert)\n    expect(Button.jQueryInterface).toEqual(jQuery.fn.button)\n    expect(Carousel.jQueryInterface).toEqual(jQuery.fn.carousel)\n    expect(Collapse.jQueryInterface).toEqual(jQuery.fn.collapse)\n    expect(Dropdown.jQueryInterface).toEqual(jQuery.fn.dropdown)\n    expect(Modal.jQueryInterface).toEqual(jQuery.fn.modal)\n    expect(Offcanvas.jQueryInterface).toEqual(jQuery.fn.offcanvas)\n    expect(Popover.jQueryInterface).toEqual(jQuery.fn.popover)\n    expect(ScrollSpy.jQueryInterface).toEqual(jQuery.fn.scrollspy)\n    expect(Tab.jQueryInterface).toEqual(jQuery.fn.tab)\n    expect(Toast.jQueryInterface).toEqual(jQuery.fn.toast)\n    expect(Tooltip.jQueryInterface).toEqual(jQuery.fn.tooltip)\n  })\n\n  it('should use jQuery event system', () => {\n    return new Promise(resolve => {\n      fixtureEl.innerHTML = [\n        '<div class=\"alert\">',\n        '  <button type=\"button\" data-bs-dismiss=\"alert\">x</button>',\n        '</div>'\n      ].join('')\n\n      $(fixtureEl).find('.alert')\n        .one('closed.bs.alert', () => {\n          expect($(fixtureEl).find('.alert')).toHaveSize(0)\n          resolve()\n        })\n\n      $(fixtureEl).find('button').trigger('click')\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/modal.spec.js",
    "content": "import Modal from '../../src/modal'\nimport EventHandler from '../../src/dom/event-handler'\nimport ScrollBarHelper from '../../src/util/scrollbar'\nimport { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Modal', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n    clearBodyAndDocument()\n    document.body.classList.remove('modal-open')\n\n    for (const backdrop of document.querySelectorAll('.modal-backdrop')) {\n      backdrop.remove()\n    }\n  })\n\n  beforeEach(() => {\n    clearBodyAndDocument()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Modal.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Modal.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Modal.DATA_KEY).toEqual('bs.modal')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modalBySelector = new Modal('.modal')\n      const modalByElement = new Modal(modalEl)\n\n      expect(modalBySelector._element).toEqual(modalEl)\n      expect(modalByElement._element).toEqual(modalEl)\n    })\n  })\n\n  describe('toggle', () => {\n    it('should call ScrollBarHelper to handle scrollBar on body', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()\n        const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(spyHide).toHaveBeenCalled()\n          modal.toggle()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spyReset).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.toggle()\n      })\n    })\n  })\n\n  describe('show', () => {\n    it('should show a modal', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('show.bs.modal', event => {\n          expect(event).toBeDefined()\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modalEl.getAttribute('aria-modal')).toEqual('true')\n          expect(modalEl.getAttribute('role')).toEqual('dialog')\n          expect(modalEl.getAttribute('aria-hidden')).toBeNull()\n          expect(modalEl.style.display).toEqual('block')\n          expect(document.querySelector('.modal-backdrop')).not.toBeNull()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should show a modal without backdrop', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          backdrop: false\n        })\n\n        modalEl.addEventListener('show.bs.modal', event => {\n          expect(event).toBeDefined()\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modalEl.getAttribute('aria-modal')).toEqual('true')\n          expect(modalEl.getAttribute('role')).toEqual('dialog')\n          expect(modalEl.getAttribute('aria-hidden')).toBeNull()\n          expect(modalEl.style.display).toEqual('block')\n          expect(document.querySelector('.modal-backdrop')).toBeNull()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should show a modal and append the element', () => {\n      return new Promise(resolve => {\n        const modalEl = document.createElement('div')\n        const id = 'dynamicModal'\n\n        modalEl.setAttribute('id', id)\n        modalEl.classList.add('modal')\n        modalEl.innerHTML = '<div class=\"modal-dialog\"></div>'\n\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const dynamicModal = document.getElementById(id)\n          expect(dynamicModal).not.toBeNull()\n          dynamicModal.remove()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should do nothing if a modal is shown', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modal = new Modal(modalEl)\n\n      const spy = spyOn(EventHandler, 'trigger')\n      modal._isShown = true\n\n      modal.show()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should do nothing if a modal is transitioning', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modal = new Modal(modalEl)\n\n      const spy = spyOn(EventHandler, 'trigger')\n      modal._isTransitioning = true\n\n      modal.show()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should not fire shown event when show is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('show.bs.modal', event => {\n          event.preventDefault()\n\n          const expectedDone = () => {\n            expect().nothing()\n            resolve()\n          }\n\n          setTimeout(expectedDone, 10)\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          reject(new Error('shown event triggered'))\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should be shown after the first call to show() has been prevented while fading is enabled ', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal fade\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        let prevented = false\n        modalEl.addEventListener('show.bs.modal', event => {\n          if (!prevented) {\n            event.preventDefault()\n            prevented = true\n\n            setTimeout(() => {\n              modal.show()\n            })\n          }\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(prevented).toBeTrue()\n          expect(modal._isAnimated()).toBeTrue()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n    it('should set is transitioning if fade class is present', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal fade\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('show.bs.modal', () => {\n          setTimeout(() => {\n            expect(modal._isTransitioning).toBeTrue()\n          })\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modal._isTransitioning).toBeFalse()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should close modal when a click occurred on data-bs-dismiss=\"modal\" inside modal', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal fade\">',\n          '  <div class=\"modal-dialog\">',\n          '    <div class=\"modal-header\">',\n          '      <button type=\"button\" data-bs-dismiss=\"modal\"></button>',\n          '    </div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const btnClose = fixtureEl.querySelector('[data-bs-dismiss=\"modal\"]')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, 'hide').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          btnClose.click()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should close modal when a click occurred on a data-bs-dismiss=\"modal\" with \"bs-target\" outside of modal element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button type=\"button\" data-bs-dismiss=\"modal\" data-bs-target=\"#modal1\"></button>',\n          '<div id=\"modal1\" class=\"modal fade\">',\n          '  <div class=\"modal-dialog\"></div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const btnClose = fixtureEl.querySelector('[data-bs-dismiss=\"modal\"]')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, 'hide').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          btnClose.click()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should set .modal\\'s scroll top to 0', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal fade\">',\n          '  <div class=\"modal-dialog\"></div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modalEl.scrollTop).toEqual(0)\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should set modal body scroll top to 0 if modal body do not exists', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal fade\">',\n          '  <div class=\"modal-dialog\">',\n          '    <div class=\"modal-body\"></div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modalBody = modalEl.querySelector('.modal-body')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modalBody.scrollTop).toEqual(0)\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not trap focus if focus equal to false', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal fade\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          focus: false\n        })\n\n        const spy = spyOn(modal._focustrap, 'activate').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should add listener when escape touch is pressed', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, 'hide').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const keydownEscape = createEvent('keydown')\n          keydownEscape.key = 'Escape'\n\n          modalEl.dispatchEvent(keydownEscape)\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should do nothing when the pressed key is not escape', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, 'hide')\n\n        const expectDone = () => {\n          expect(spy).not.toHaveBeenCalled()\n\n          resolve()\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const keydownTab = createEvent('keydown')\n          keydownTab.key = 'Tab'\n\n          modalEl.dispatchEvent(keydownTab)\n          setTimeout(expectDone, 30)\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should adjust dialog on resize', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, '_adjustDialog').and.callThrough()\n\n        const expectDone = () => {\n          expect(spy).toHaveBeenCalled()\n\n          resolve()\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const resizeEvent = createEvent('resize')\n\n          window.dispatchEvent(resizeEvent)\n          setTimeout(expectDone, 10)\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not close modal when clicking on modal-content', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal\">',\n          '  <div class=\"modal-dialog\">',\n          '    <div class=\"modal-content\"></div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        const shownCallback = () => {\n          setTimeout(() => {\n            expect(modal._isShown).toEqual(true)\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          fixtureEl.querySelector('.modal-dialog').click()\n          fixtureEl.querySelector('.modal-content').click()\n          shownCallback()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          reject(new Error('Should not hide a modal'))\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not close modal when clicking outside of modal-content if backdrop = false', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          backdrop: false\n        })\n\n        const shownCallback = () => {\n          setTimeout(() => {\n            expect(modal._isShown).toBeTrue()\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modalEl.click()\n          shownCallback()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          reject(new Error('Should not hide a modal'))\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not close modal when clicking outside of modal-content if backdrop = static', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          backdrop: 'static'\n        })\n\n        const shownCallback = () => {\n          setTimeout(() => {\n            expect(modal._isShown).toBeTrue()\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modalEl.click()\n          shownCallback()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          reject(new Error('Should not hide a modal'))\n        })\n\n        modal.show()\n      })\n    })\n    it('should close modal when escape key is pressed with keyboard = true and backdrop is static', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          backdrop: 'static',\n          keyboard: true\n        })\n\n        const shownCallback = () => {\n          setTimeout(() => {\n            expect(modal._isShown).toBeFalse()\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const keydownEscape = createEvent('keydown')\n          keydownEscape.key = 'Escape'\n\n          modalEl.dispatchEvent(keydownEscape)\n          shownCallback()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not close modal when escape key is pressed with keyboard = false', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          keyboard: false\n        })\n\n        const shownCallback = () => {\n          setTimeout(() => {\n            expect(modal._isShown).toBeTrue()\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const keydownEscape = createEvent('keydown')\n          keydownEscape.key = 'Escape'\n\n          modalEl.dispatchEvent(keydownEscape)\n          shownCallback()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          reject(new Error('Should not hide a modal'))\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not overflow when clicking outside of modal-content if backdrop = static', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\" style=\"transition-duration: 20ms;\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          backdrop: 'static'\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modalEl.click()\n          setTimeout(() => {\n            expect(modalEl.clientHeight).toEqual(modalEl.scrollHeight)\n            resolve()\n          }, 20)\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not queue multiple callbacks when clicking outside of modal-content and backdrop = static', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\" style=\"transition-duration: 50ms;\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl, {\n          backdrop: 'static'\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const spy = spyOn(modal, '_queueCallback').and.callThrough()\n          const mouseDown = createEvent('mousedown')\n\n          modalEl.dispatchEvent(mouseDown)\n          modalEl.click()\n          modalEl.dispatchEvent(mouseDown)\n          modalEl.click()\n\n          setTimeout(() => {\n            expect(spy).toHaveBeenCalledTimes(1)\n            resolve()\n          }, 20)\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should trap focus', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal._focustrap, 'activate').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should hide a modal', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n        const backdropSpy = spyOn(modal._backdrop, 'hide').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modal.hide()\n        })\n\n        modalEl.addEventListener('hide.bs.modal', event => {\n          expect(event).toBeDefined()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(modalEl.getAttribute('aria-modal')).toBeNull()\n          expect(modalEl.getAttribute('role')).toBeNull()\n          expect(modalEl.getAttribute('aria-hidden')).toEqual('true')\n          expect(modalEl.style.display).toEqual('none')\n          expect(backdropSpy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should close modal when clicking outside of modal-content', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const dialogEl = modalEl.querySelector('.modal-dialog')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, 'hide')\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const mouseDown = createEvent('mousedown')\n\n          dialogEl.dispatchEvent(mouseDown)\n          modalEl.click()\n          expect(spy).not.toHaveBeenCalled()\n\n          modalEl.dispatchEvent(mouseDown)\n          modalEl.click()\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should not close modal when clicking on an element removed from modal content', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal\">',\n          ' <div class=\"modal-dialog\">',\n          '   <button class=\"btn\">BTN</button>',\n          ' </div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const buttonEl = modalEl.querySelector('.btn')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(modal, 'hide')\n        buttonEl.addEventListener('click', () => {\n          buttonEl.remove()\n        })\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modalEl.dispatchEvent(createEvent('mousedown'))\n          buttonEl.click()\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should do nothing is the modal is not shown', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modal = new Modal(modalEl)\n\n      modal.hide()\n\n      expect().nothing()\n    })\n\n    it('should do nothing is the modal is transitioning', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modal = new Modal(modalEl)\n\n      modal._isTransitioning = true\n      modal.hide()\n\n      expect().nothing()\n    })\n\n    it('should not hide a modal if hide is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modal.hide()\n        })\n\n        const hideCallback = () => {\n          setTimeout(() => {\n            expect(modal._isShown).toBeTrue()\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('hide.bs.modal', event => {\n          event.preventDefault()\n          hideCallback()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          reject(new Error('should not trigger hidden'))\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should release focus trap', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n        const spy = spyOn(modal._focustrap, 'deactivate').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          modal.hide()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose a modal', () => {\n      fixtureEl.innerHTML = '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modal = new Modal(modalEl)\n      const focustrap = modal._focustrap\n      const spyDeactivate = spyOn(focustrap, 'deactivate').and.callThrough()\n\n      expect(Modal.getInstance(modalEl)).toEqual(modal)\n\n      const spyOff = spyOn(EventHandler, 'off')\n\n      modal.dispose()\n\n      expect(Modal.getInstance(modalEl)).toBeNull()\n      expect(spyOff).toHaveBeenCalledTimes(3)\n      expect(spyDeactivate).toHaveBeenCalled()\n    })\n  })\n\n  describe('handleUpdate', () => {\n    it('should call adjust dialog', () => {\n      fixtureEl.innerHTML = '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const modalEl = fixtureEl.querySelector('.modal')\n      const modal = new Modal(modalEl)\n\n      const spy = spyOn(modal, '_adjustDialog')\n\n      modal.handleUpdate()\n\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('data-api', () => {\n    it('should toggle modal', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button type=\"button\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModal\"></button>',\n          '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const trigger = fixtureEl.querySelector('[data-bs-toggle=\"modal\"]')\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modalEl.getAttribute('aria-modal')).toEqual('true')\n          expect(modalEl.getAttribute('role')).toEqual('dialog')\n          expect(modalEl.getAttribute('aria-hidden')).toBeNull()\n          expect(modalEl.style.display).toEqual('block')\n          expect(document.querySelector('.modal-backdrop')).not.toBeNull()\n          setTimeout(() => trigger.click(), 10)\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(modalEl.getAttribute('aria-modal')).toBeNull()\n          expect(modalEl.getAttribute('role')).toBeNull()\n          expect(modalEl.getAttribute('aria-hidden')).toEqual('true')\n          expect(modalEl.style.display).toEqual('none')\n          expect(document.querySelector('.modal-backdrop')).toBeNull()\n          resolve()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should not recreate a new modal', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button type=\"button\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModal\"></button>',\n          '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const modal = new Modal(modalEl)\n        const trigger = fixtureEl.querySelector('[data-bs-toggle=\"modal\"]')\n\n        const spy = spyOn(modal, 'show').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should prevent default when the trigger is <a> or <area>', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a data-bs-toggle=\"modal\" href=\"#\" data-bs-target=\"#exampleModal\"></a>',\n          '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const trigger = fixtureEl.querySelector('[data-bs-toggle=\"modal\"]')\n\n        const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          expect(modalEl.getAttribute('aria-modal')).toEqual('true')\n          expect(modalEl.getAttribute('role')).toEqual('dialog')\n          expect(modalEl.getAttribute('aria-hidden')).toBeNull()\n          expect(modalEl.style.display).toEqual('block')\n          expect(document.querySelector('.modal-backdrop')).not.toBeNull()\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should focus the trigger on hide', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a data-bs-toggle=\"modal\" href=\"#\" data-bs-target=\"#exampleModal\"></a>',\n          '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const trigger = fixtureEl.querySelector('[data-bs-toggle=\"modal\"]')\n\n        const spy = spyOn(trigger, 'focus')\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const modal = Modal.getInstance(modalEl)\n\n          modal.hide()\n        })\n\n        const hideListener = () => {\n          setTimeout(() => {\n            expect(spy).toHaveBeenCalled()\n            resolve()\n          }, 20)\n        }\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          hideListener()\n        })\n\n        trigger.click()\n      })\n    })\n    it('should not prevent default when a click occurred on data-bs-dismiss=\"modal\" where tagName is DIFFERENT than <a> or <area>', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal\">',\n          '  <div class=\"modal-dialog\">',\n          '    <button type=\"button\" data-bs-dismiss=\"modal\"></button>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const btnClose = fixtureEl.querySelector('button[data-bs-dismiss=\"modal\"]')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          btnClose.click()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n\n    it('should prevent default when a click occurred on data-bs-dismiss=\"modal\" where tagName is <a> or <area>', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"modal\">',\n          '  <div class=\"modal-dialog\">',\n          '    <a type=\"button\" data-bs-dismiss=\"modal\"></a>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const btnClose = fixtureEl.querySelector('a[data-bs-dismiss=\"modal\"]')\n        const modal = new Modal(modalEl)\n\n        const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          btnClose.click()\n        })\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        modal.show()\n      })\n    })\n    it('should not focus the trigger if the modal is not visible', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a data-bs-toggle=\"modal\" href=\"#\" data-bs-target=\"#exampleModal\" style=\"display: none;\"></a>',\n          '<div id=\"exampleModal\" class=\"modal\" style=\"display: none;\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const trigger = fixtureEl.querySelector('[data-bs-toggle=\"modal\"]')\n\n        const spy = spyOn(trigger, 'focus')\n\n        modalEl.addEventListener('shown.bs.modal', () => {\n          const modal = Modal.getInstance(modalEl)\n\n          modal.hide()\n        })\n\n        const hideListener = () => {\n          setTimeout(() => {\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          }, 20)\n        }\n\n        modalEl.addEventListener('hidden.bs.modal', () => {\n          hideListener()\n        })\n\n        trigger.click()\n      })\n    })\n    it('should not focus the trigger if the modal is not shown', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a data-bs-toggle=\"modal\" href=\"#\" data-bs-target=\"#exampleModal\"></a>',\n          '<div id=\"exampleModal\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const modalEl = fixtureEl.querySelector('.modal')\n        const trigger = fixtureEl.querySelector('[data-bs-toggle=\"modal\"]')\n\n        const spy = spyOn(trigger, 'focus')\n\n        const showListener = () => {\n          setTimeout(() => {\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          }, 10)\n        }\n\n        modalEl.addEventListener('show.bs.modal', event => {\n          event.preventDefault()\n          showListener()\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should call hide first, if another modal is open', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button data-bs-toggle=\"modal\"  data-bs-target=\"#modal2\"></button>',\n          '<div id=\"modal1\" class=\"modal fade\"><div class=\"modal-dialog\"></div></div>',\n          '<div id=\"modal2\" class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n        ].join('')\n\n        const trigger2 = fixtureEl.querySelector('button')\n        const modalEl1 = document.querySelector('#modal1')\n        const modalEl2 = document.querySelector('#modal2')\n        const modal1 = new Modal(modalEl1)\n\n        modalEl1.addEventListener('shown.bs.modal', () => {\n          trigger2.click()\n        })\n        modalEl1.addEventListener('hidden.bs.modal', () => {\n          expect(Modal.getInstance(modalEl2)).not.toBeNull()\n          expect(modalEl2).toHaveClass('show')\n          resolve()\n        })\n        modal1.show()\n      })\n    })\n  })\n  describe('jQueryInterface', () => {\n    it('should create a modal', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.modal = Modal.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.modal.call(jQueryMock)\n\n      expect(Modal.getInstance(div)).not.toBeNull()\n    })\n\n    it('should create a modal with given config', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.modal = Modal.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.modal.call(jQueryMock, { keyboard: false })\n      const spy = spyOn(Modal.prototype, 'constructor')\n      expect(spy).not.toHaveBeenCalledWith(div, { keyboard: false })\n\n      const modal = Modal.getInstance(div)\n      expect(modal).not.toBeNull()\n      expect(modal._config.keyboard).toBeFalse()\n    })\n\n    it('should not re create a modal', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const modal = new Modal(div)\n\n      jQueryMock.fn.modal = Modal.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.modal.call(jQueryMock)\n\n      expect(Modal.getInstance(div)).toEqual(modal)\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.modal = Modal.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.modal.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should call show method', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const modal = new Modal(div)\n\n      jQueryMock.fn.modal = Modal.jQueryInterface\n      jQueryMock.elements = [div]\n\n      const spy = spyOn(modal, 'show')\n\n      jQueryMock.fn.modal.call(jQueryMock, 'show')\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should not call show method', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\" data-bs-show=\"false\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.modal = Modal.jQueryInterface\n      jQueryMock.elements = [div]\n\n      const spy = spyOn(Modal.prototype, 'show')\n\n      jQueryMock.fn.modal.call(jQueryMock)\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return modal instance', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const modal = new Modal(div)\n\n      expect(Modal.getInstance(div)).toEqual(modal)\n      expect(Modal.getInstance(div)).toBeInstanceOf(Modal)\n    })\n\n    it('should return null when there is no modal instance', () => {\n      fixtureEl.innerHTML = '<div class=\"modal\"><div class=\"modal-dialog\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Modal.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return modal instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const modal = new Modal(div)\n\n      expect(Modal.getOrCreateInstance(div)).toEqual(modal)\n      expect(Modal.getInstance(div)).toEqual(Modal.getOrCreateInstance(div, {}))\n      expect(Modal.getOrCreateInstance(div)).toBeInstanceOf(Modal)\n    })\n\n    it('should return new instance when there is no modal instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Modal.getInstance(div)).toBeNull()\n      expect(Modal.getOrCreateInstance(div)).toBeInstanceOf(Modal)\n    })\n\n    it('should return new instance when there is no modal instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Modal.getInstance(div)).toBeNull()\n      const modal = Modal.getOrCreateInstance(div, {\n        backdrop: true\n      })\n      expect(modal).toBeInstanceOf(Modal)\n\n      expect(modal._config.backdrop).toBeTrue()\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const modal = new Modal(div, {\n        backdrop: true\n      })\n      expect(Modal.getInstance(div)).toEqual(modal)\n\n      const modal2 = Modal.getOrCreateInstance(div, {\n        backdrop: false\n      })\n      expect(modal).toBeInstanceOf(Modal)\n      expect(modal2).toEqual(modal)\n\n      expect(modal2._config.backdrop).toBeTrue()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/offcanvas.spec.js",
    "content": "import Offcanvas from '../../src/offcanvas'\nimport EventHandler from '../../src/dom/event-handler'\nimport { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\nimport { isVisible } from '../../src/util/index'\nimport ScrollBarHelper from '../../src/util/scrollbar'\n\ndescribe('Offcanvas', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n    document.body.classList.remove('offcanvas-open')\n    clearBodyAndDocument()\n  })\n\n  beforeEach(() => {\n    clearBodyAndDocument()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Offcanvas.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Offcanvas.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Offcanvas.DATA_KEY).toEqual('bs.offcanvas')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should call hide when a element with data-bs-dismiss=\"offcanvas\" is clicked', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"offcanvas\">',\n        '  <a href=\"#\" data-bs-dismiss=\"offcanvas\">Close</a>',\n        '</div>'\n      ].join('')\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const closeEl = fixtureEl.querySelector('a')\n      const offCanvas = new Offcanvas(offCanvasEl)\n\n      const spy = spyOn(offCanvas, 'hide')\n\n      closeEl.click()\n\n      expect(offCanvas._config.keyboard).toBeTrue()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should hide if esc is pressed', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl)\n      const keyDownEsc = createEvent('keydown')\n      keyDownEsc.key = 'Escape'\n\n      const spy = spyOn(offCanvas, 'hide')\n\n      offCanvasEl.dispatchEvent(keyDownEsc)\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should hide if esc is pressed and backdrop is static', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })\n      const keyDownEsc = createEvent('keydown')\n      keyDownEsc.key = 'Escape'\n\n      const spy = spyOn(offCanvas, 'hide')\n\n      offCanvasEl.dispatchEvent(keyDownEsc)\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should not hide if esc is not pressed', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl)\n      const keydownTab = createEvent('keydown')\n      keydownTab.key = 'Tab'\n\n      const spy = spyOn(offCanvas, 'hide')\n\n      offCanvasEl.dispatchEvent(keydownTab)\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should not hide if esc is pressed but with keyboard = false', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl, { keyboard: false })\n        const keyDownEsc = createEvent('keydown')\n        keyDownEsc.key = 'Escape'\n\n        const spy = spyOn(offCanvas, 'hide')\n        const hidePreventedSpy = jasmine.createSpy('hidePrevented')\n        offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(offCanvas._config.keyboard).toBeFalse()\n          offCanvasEl.dispatchEvent(keyDownEsc)\n\n          expect(hidePreventedSpy).toHaveBeenCalled()\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n\n    it('should not hide if user clicks on static backdrop', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })\n\n        const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })\n        const spyClick = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()\n        const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()\n        const hidePreventedSpy = jasmine.createSpy('hidePrevented')\n        offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(spyClick).toEqual(jasmine.any(Function))\n\n          offCanvas._backdrop._getElement().dispatchEvent(clickEvent)\n          expect(hidePreventedSpy).toHaveBeenCalled()\n          expect(spyHide).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n\n    it('should call `hide` on resize, if element\\'s position is not fixed any more', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas-lg\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl)\n\n        const spy = spyOn(offCanvas, 'hide').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          const resizeEvent = createEvent('resize')\n          offCanvasEl.style.removeProperty('position')\n\n          window.dispatchEvent(resizeEvent)\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n  })\n\n  describe('config', () => {\n    it('should have default values', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl)\n\n      expect(offCanvas._config.backdrop).toBeTrue()\n      expect(offCanvas._backdrop._config.isVisible).toBeTrue()\n      expect(offCanvas._config.keyboard).toBeTrue()\n      expect(offCanvas._config.scroll).toBeFalse()\n    })\n\n    it('should read data attributes and override default config', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\" data-bs-scroll=\"true\" data-bs-backdrop=\"false\" data-bs-keyboard=\"false\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl)\n\n      expect(offCanvas._config.backdrop).toBeFalse()\n      expect(offCanvas._backdrop._config.isVisible).toBeFalse()\n      expect(offCanvas._config.keyboard).toBeFalse()\n      expect(offCanvas._config.scroll).toBeTrue()\n    })\n\n    it('given a config object must override data attributes', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\" data-bs-scroll=\"true\" data-bs-backdrop=\"false\" data-bs-keyboard=\"false\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl, {\n        backdrop: true,\n        keyboard: true,\n        scroll: false\n      })\n      expect(offCanvas._config.backdrop).toBeTrue()\n      expect(offCanvas._config.keyboard).toBeTrue()\n      expect(offCanvas._config.scroll).toBeFalse()\n    })\n  })\n\n  describe('options', () => {\n    it('if scroll is enabled, should allow body to scroll while offcanvas is open', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()\n        const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl, { scroll: true })\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(spyHide).not.toHaveBeenCalled()\n          offCanvas.hide()\n        })\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          expect(spyReset).not.toHaveBeenCalled()\n          resolve()\n        })\n        offCanvas.show()\n      })\n    })\n\n    it('if scroll is disabled, should call ScrollBarHelper to handle scrollBar on body', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()\n        const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl, { scroll: false })\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(spyHide).toHaveBeenCalled()\n          offCanvas.hide()\n        })\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          expect(spyReset).toHaveBeenCalled()\n          resolve()\n        })\n        offCanvas.show()\n      })\n    })\n\n    it('should hide a shown element if user click on backdrop', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl, { backdrop: true })\n\n        const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })\n        const spy = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(offCanvas._backdrop._config.clickCallback).toEqual(jasmine.any(Function))\n\n          offCanvas._backdrop._getElement().dispatchEvent(clickEvent)\n        })\n\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n\n    it('should not trap focus if scroll is allowed', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl, {\n          scroll: true,\n          backdrop: false\n        })\n\n        const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n\n    it('should trap focus if scroll is allowed OR backdrop is enabled', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl, {\n          scroll: true,\n          backdrop: true\n        })\n\n        const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n  })\n\n  describe('toggle', () => {\n    it('should call show method if show class is not present', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n      const offCanvas = new Offcanvas(offCanvasEl)\n\n      const spy = spyOn(offCanvas, 'show')\n\n      offCanvas.toggle()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should call hide method if show class is present', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl)\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(offCanvasEl).toHaveClass('show')\n          const spy = spyOn(offCanvas, 'hide')\n\n          offCanvas.toggle()\n\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n  })\n\n  describe('show', () => {\n    it('should add `showing` class during opening and `show` class on end', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl)\n\n        offCanvasEl.addEventListener('show.bs.offcanvas', () => {\n          expect(offCanvasEl).not.toHaveClass('show')\n        })\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(offCanvasEl).not.toHaveClass('showing')\n          expect(offCanvasEl).toHaveClass('show')\n          resolve()\n        })\n\n        offCanvas.show()\n        expect(offCanvasEl).toHaveClass('showing')\n      })\n    })\n\n    it('should do nothing if already shown', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas show\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('div')\n      const offCanvas = new Offcanvas(offCanvasEl)\n      offCanvas.show()\n\n      expect(offCanvasEl).toHaveClass('show')\n\n      const spyShow = spyOn(offCanvas._backdrop, 'show').and.callThrough()\n      const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()\n      offCanvas.show()\n\n      expect(spyTrigger).not.toHaveBeenCalled()\n      expect(spyShow).not.toHaveBeenCalled()\n    })\n\n    it('should show a hidden element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl)\n        const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(offCanvasEl).toHaveClass('show')\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n\n    it('should not fire shown when show is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl)\n        const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()\n\n        const expectEnd = () => {\n          setTimeout(() => {\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          }, 10)\n        }\n\n        offCanvasEl.addEventListener('show.bs.offcanvas', event => {\n          event.preventDefault()\n          expectEnd()\n        })\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          reject(new Error('should not fire shown event'))\n        })\n\n        offCanvas.show()\n      })\n    })\n\n    it('on window load, should make visible an offcanvas element, if its markup contains class \"show\"', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas show\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const spy = spyOn(Offcanvas.prototype, 'show').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          resolve()\n        })\n\n        window.dispatchEvent(createEvent('load'))\n\n        const instance = Offcanvas.getInstance(offCanvasEl)\n        expect(instance).not.toBeNull()\n        expect(spy).toHaveBeenCalled()\n      })\n    })\n\n    it('should trap focus', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl)\n\n        const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.show()\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should add `hiding` class during closing and remover `show` & `hiding` classes on end', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n        const offCanvasEl = fixtureEl.querySelector('.offcanvas')\n        const offCanvas = new Offcanvas(offCanvasEl)\n\n        offCanvasEl.addEventListener('hide.bs.offcanvas', () => {\n          expect(offCanvasEl).not.toHaveClass('showing')\n          expect(offCanvasEl).toHaveClass('show')\n        })\n\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          expect(offCanvasEl).not.toHaveClass('hiding')\n          expect(offCanvasEl).not.toHaveClass('show')\n          resolve()\n        })\n\n        offCanvas.show()\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          offCanvas.hide()\n          expect(offCanvasEl).not.toHaveClass('showing')\n          expect(offCanvasEl).toHaveClass('hiding')\n        })\n      })\n    })\n\n    it('should do nothing if already shown', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()\n\n      const offCanvasEl = fixtureEl.querySelector('div')\n      const offCanvas = new Offcanvas(offCanvasEl)\n      const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()\n\n      offCanvas.hide()\n      expect(spyHide).not.toHaveBeenCalled()\n      expect(spyTrigger).not.toHaveBeenCalled()\n    })\n\n    it('should hide a shown element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl)\n        const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()\n        offCanvas.show()\n\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          expect(offCanvasEl).not.toHaveClass('show')\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.hide()\n      })\n    })\n\n    it('should not fire hidden when hide is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl)\n        const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()\n\n        offCanvas.show()\n\n        const expectEnd = () => {\n          setTimeout(() => {\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          }, 10)\n        }\n\n        offCanvasEl.addEventListener('hide.bs.offcanvas', event => {\n          event.preventDefault()\n          expectEnd()\n        })\n\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          reject(new Error('should not fire hidden event'))\n        })\n\n        offCanvas.hide()\n      })\n    })\n\n    it('should release focus trap', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n        const offCanvasEl = fixtureEl.querySelector('div')\n        const offCanvas = new Offcanvas(offCanvasEl)\n        const spy = spyOn(offCanvas._focustrap, 'deactivate').and.callThrough()\n        offCanvas.show()\n\n        offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        offCanvas.hide()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose an offcanvas', () => {\n      fixtureEl.innerHTML = '<div class=\"offcanvas\"></div>'\n\n      const offCanvasEl = fixtureEl.querySelector('div')\n      const offCanvas = new Offcanvas(offCanvasEl)\n      const backdrop = offCanvas._backdrop\n      const spyDispose = spyOn(backdrop, 'dispose').and.callThrough()\n      const focustrap = offCanvas._focustrap\n      const spyDeactivate = spyOn(focustrap, 'deactivate').and.callThrough()\n\n      expect(Offcanvas.getInstance(offCanvasEl)).toEqual(offCanvas)\n\n      offCanvas.dispose()\n\n      expect(spyDispose).toHaveBeenCalled()\n      expect(offCanvas._backdrop).toBeNull()\n      expect(spyDeactivate).toHaveBeenCalled()\n      expect(offCanvas._focustrap).toBeNull()\n      expect(Offcanvas.getInstance(offCanvasEl)).toBeNull()\n    })\n  })\n\n  describe('data-api', () => {\n    it('should not prevent event for input', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<input type=\"checkbox\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasdiv1\">',\n          '<div id=\"offcanvasdiv1\" class=\"offcanvas\"></div>'\n        ].join('')\n\n        const target = fixtureEl.querySelector('input')\n        const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1')\n\n        offCanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          expect(offCanvasEl).toHaveClass('show')\n          expect(target.checked).toBeTrue()\n          resolve()\n        })\n\n        target.click()\n      })\n    })\n\n    it('should not call toggle on disabled elements', () => {\n      fixtureEl.innerHTML = [\n        '<a href=\"#\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasdiv1\" class=\"disabled\"></a>',\n        '<div id=\"offcanvasdiv1\" class=\"offcanvas\"></div>'\n      ].join('')\n\n      const target = fixtureEl.querySelector('a')\n\n      const spy = spyOn(Offcanvas.prototype, 'toggle')\n\n      target.click()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should call hide first, if another offcanvas is open', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"btn2\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvas2\"></button>',\n          '<div id=\"offcanvas1\" class=\"offcanvas\"></div>',\n          '<div id=\"offcanvas2\" class=\"offcanvas\"></div>'\n        ].join('')\n\n        const trigger2 = fixtureEl.querySelector('#btn2')\n        const offcanvasEl1 = document.querySelector('#offcanvas1')\n        const offcanvasEl2 = document.querySelector('#offcanvas2')\n        const offcanvas1 = new Offcanvas(offcanvasEl1)\n\n        offcanvasEl1.addEventListener('shown.bs.offcanvas', () => {\n          trigger2.click()\n        })\n        offcanvasEl1.addEventListener('hidden.bs.offcanvas', () => {\n          expect(Offcanvas.getInstance(offcanvasEl2)).not.toBeNull()\n          resolve()\n        })\n        offcanvas1.show()\n      })\n    })\n\n    it('should focus on trigger element after closing offcanvas', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"btn\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvas\"></button>',\n          '<div id=\"offcanvas\" class=\"offcanvas\"></div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('#btn')\n        const offcanvasEl = fixtureEl.querySelector('#offcanvas')\n        const offcanvas = new Offcanvas(offcanvasEl)\n        const spy = spyOn(trigger, 'focus')\n\n        offcanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          offcanvas.hide()\n        })\n        offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          setTimeout(() => {\n            expect(spy).toHaveBeenCalled()\n            resolve()\n          }, 5)\n        })\n\n        trigger.click()\n      })\n    })\n\n    it('should not focus on trigger element after closing offcanvas, if it is not visible', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"btn\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvas\"></button>',\n          '<div id=\"offcanvas\" class=\"offcanvas\"></div>'\n        ].join('')\n\n        const trigger = fixtureEl.querySelector('#btn')\n        const offcanvasEl = fixtureEl.querySelector('#offcanvas')\n        const offcanvas = new Offcanvas(offcanvasEl)\n        const spy = spyOn(trigger, 'focus')\n\n        offcanvasEl.addEventListener('shown.bs.offcanvas', () => {\n          trigger.style.display = 'none'\n          offcanvas.hide()\n        })\n        offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {\n          setTimeout(() => {\n            expect(isVisible(trigger)).toBeFalse()\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          }, 5)\n        })\n\n        trigger.click()\n      })\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create an offcanvas', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.offcanvas.call(jQueryMock)\n\n      expect(Offcanvas.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create an offcanvas', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const offCanvas = new Offcanvas(div)\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.offcanvas.call(jQueryMock)\n\n      expect(Offcanvas.getInstance(div)).toEqual(offCanvas)\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.offcanvas.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should throw error on protected method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = '_getConfig'\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.offcanvas.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should throw error if method \"constructor\" is being called', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'constructor'\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.offcanvas.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should call offcanvas method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      const spy = spyOn(Offcanvas.prototype, 'show')\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.offcanvas.call(jQueryMock, 'show')\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should create a offcanvas with given config', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.offcanvas.call(jQueryMock, { scroll: true })\n\n      const offcanvas = Offcanvas.getInstance(div)\n      expect(offcanvas).not.toBeNull()\n      expect(offcanvas._config.scroll).toBeTrue()\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return offcanvas instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const offCanvas = new Offcanvas(div)\n\n      expect(Offcanvas.getInstance(div)).toEqual(offCanvas)\n      expect(Offcanvas.getInstance(div)).toBeInstanceOf(Offcanvas)\n    })\n\n    it('should return null when there is no offcanvas instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Offcanvas.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return offcanvas instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const offcanvas = new Offcanvas(div)\n\n      expect(Offcanvas.getOrCreateInstance(div)).toEqual(offcanvas)\n      expect(Offcanvas.getInstance(div)).toEqual(Offcanvas.getOrCreateInstance(div, {}))\n      expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)\n    })\n\n    it('should return new instance when there is no Offcanvas instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Offcanvas.getInstance(div)).toBeNull()\n      expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)\n    })\n\n    it('should return new instance when there is no offcanvas instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Offcanvas.getInstance(div)).toBeNull()\n      const offcanvas = Offcanvas.getOrCreateInstance(div, {\n        scroll: true\n      })\n      expect(offcanvas).toBeInstanceOf(Offcanvas)\n\n      expect(offcanvas._config.scroll).toBeTrue()\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const offcanvas = new Offcanvas(div, {\n        scroll: true\n      })\n      expect(Offcanvas.getInstance(div)).toEqual(offcanvas)\n\n      const offcanvas2 = Offcanvas.getOrCreateInstance(div, {\n        scroll: false\n      })\n      expect(offcanvas).toBeInstanceOf(Offcanvas)\n      expect(offcanvas2).toEqual(offcanvas)\n\n      expect(offcanvas2._config.scroll).toBeTrue()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/popover.spec.js",
    "content": "import Popover from '../../src/popover'\nimport EventHandler from '../../src/dom/event-handler'\nimport { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Popover', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n\n    const popoverList = document.querySelectorAll('.popover')\n\n    for (const popoverEl of popoverList) {\n      popoverEl.remove()\n    }\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Popover.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Popover.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('NAME', () => {\n    it('should return plugin name', () => {\n      expect(Popover.NAME).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Popover.DATA_KEY).toEqual('bs.popover')\n    })\n  })\n\n  describe('EVENT_KEY', () => {\n    it('should return plugin event key', () => {\n      expect(Popover.EVENT_KEY).toEqual('.bs.popover')\n    })\n  })\n\n  describe('DefaultType', () => {\n    it('should return plugin default type', () => {\n      expect(Popover.DefaultType).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('show', () => {\n    it('should show a popover', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl)\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          expect(document.querySelector('.popover')).not.toBeNull()\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n\n    it('should set title and content from functions', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\">BS twitter</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl, {\n          title: () => 'Bootstrap',\n          content: () => 'loves writing tests （╯°□°）╯︵ ┻━┻'\n        })\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          const popoverDisplayed = document.querySelector('.popover')\n\n          expect(popoverDisplayed).not.toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Bootstrap')\n          expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('loves writing tests （╯°□°）╯︵ ┻━┻')\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n\n    it('should show a popover with just content without having header', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\">Nice link</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl, {\n          content: 'Some beautiful content :)'\n        })\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          const popoverDisplayed = document.querySelector('.popover')\n\n          expect(popoverDisplayed).not.toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-header')).toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Some beautiful content :)')\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n\n    it('should show a popover with just title without having body', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\">Nice link</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl, {\n          title: 'Title which does not require content'\n        })\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          const popoverDisplayed = document.querySelector('.popover')\n\n          expect(popoverDisplayed).not.toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title which does not require content')\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n\n    it('should show a popover with just title without having body using data-attribute to get config', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" data-bs-content=\"\" title=\"Title which does not require content\">Nice link</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl)\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          const popoverDisplayed = document.querySelector('.popover')\n\n          expect(popoverDisplayed).not.toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title which does not require content')\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n\n    it('should NOT show a popover without `title` and `content`', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" data-bs-content=\"\" title=\"\">Nice link</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n      const popover = new Popover(popoverEl, { animation: false })\n      const spy = spyOn(EventHandler, 'trigger').and.callThrough()\n\n      popover.show()\n\n      expect(spy).not.toHaveBeenCalledWith(popoverEl, Popover.eventName('show'))\n      expect(document.querySelector('.popover')).toBeNull()\n    })\n\n    it('\"setContent\" should keep the initial template', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\" data-bs-custom-class=\"custom-class\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n      const popover = new Popover(popoverEl)\n\n      popover.setContent({ '.tooltip-inner': 'foo' })\n      const tip = popover._getTipElement()\n\n      expect(tip).toHaveClass('popover')\n      expect(tip).toHaveClass('bs-popover-auto')\n      expect(tip.querySelector('.popover-arrow')).not.toBeNull()\n      expect(tip.querySelector('.popover-header')).not.toBeNull()\n      expect(tip.querySelector('.popover-body')).not.toBeNull()\n    })\n\n    it('should call setContent once', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\">BS twitter</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl, {\n          content: 'Popover content'\n        })\n        expect(popover._templateFactory).toBeNull()\n        let spy = null\n        let times = 1\n\n        popoverEl.addEventListener('hidden.bs.popover', () => {\n          popover.show()\n        })\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          spy = spy || spyOn(popover._templateFactory, 'constructor').and.callThrough()\n          const popoverDisplayed = document.querySelector('.popover')\n\n          expect(popoverDisplayed).not.toBeNull()\n          expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')\n          expect(spy).toHaveBeenCalledTimes(0)\n          if (times > 1) {\n            resolve()\n          }\n\n          times++\n          popover.hide()\n        })\n        popover.show()\n      })\n    })\n\n    it('should show a popover with provided custom class', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\" data-bs-custom-class=\"custom-class\">BS twitter</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl)\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          const tip = document.querySelector('.popover')\n          expect(tip).not.toBeNull()\n          expect(tip).toHaveClass('custom-class')\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should hide a popover', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n        const popoverEl = fixtureEl.querySelector('a')\n        const popover = new Popover(popoverEl)\n\n        popoverEl.addEventListener('shown.bs.popover', () => {\n          popover.hide()\n        })\n\n        popoverEl.addEventListener('hidden.bs.popover', () => {\n          expect(document.querySelector('.popover')).toBeNull()\n          resolve()\n        })\n\n        popover.show()\n      })\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a popover', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n\n      jQueryMock.fn.popover = Popover.jQueryInterface\n      jQueryMock.elements = [popoverEl]\n\n      jQueryMock.fn.popover.call(jQueryMock)\n\n      expect(Popover.getInstance(popoverEl)).not.toBeNull()\n    })\n\n    it('should create a popover with a config object', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n\n      jQueryMock.fn.popover = Popover.jQueryInterface\n      jQueryMock.elements = [popoverEl]\n\n      jQueryMock.fn.popover.call(jQueryMock, {\n        content: 'Popover content'\n      })\n\n      expect(Popover.getInstance(popoverEl)).not.toBeNull()\n    })\n\n    it('should not re create a popover', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n      const popover = new Popover(popoverEl)\n\n      jQueryMock.fn.popover = Popover.jQueryInterface\n      jQueryMock.elements = [popoverEl]\n\n      jQueryMock.fn.popover.call(jQueryMock)\n\n      expect(Popover.getInstance(popoverEl)).toEqual(popover)\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.popover = Popover.jQueryInterface\n      jQueryMock.elements = [popoverEl]\n\n      expect(() => {\n        jQueryMock.fn.popover.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should should call show method', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n      const popover = new Popover(popoverEl)\n\n      jQueryMock.fn.popover = Popover.jQueryInterface\n      jQueryMock.elements = [popoverEl]\n\n      const spy = spyOn(popover, 'show')\n\n      jQueryMock.fn.popover.call(jQueryMock, 'show')\n\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return popover instance', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n      const popover = new Popover(popoverEl)\n\n      expect(Popover.getInstance(popoverEl)).toEqual(popover)\n      expect(Popover.getInstance(popoverEl)).toBeInstanceOf(Popover)\n    })\n\n    it('should return null when there is no popover instance', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" title=\"Popover\" data-bs-content=\"https://twitter.com/getbootstrap\">BS twitter</a>'\n\n      const popoverEl = fixtureEl.querySelector('a')\n\n      expect(Popover.getInstance(popoverEl)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return popover instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const popover = new Popover(div)\n\n      expect(Popover.getOrCreateInstance(div)).toEqual(popover)\n      expect(Popover.getInstance(div)).toEqual(Popover.getOrCreateInstance(div, {}))\n      expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover)\n    })\n\n    it('should return new instance when there is no popover instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Popover.getInstance(div)).toBeNull()\n      expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover)\n    })\n\n    it('should return new instance when there is no popover instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Popover.getInstance(div)).toBeNull()\n      const popover = Popover.getOrCreateInstance(div, {\n        placement: 'top'\n      })\n      expect(popover).toBeInstanceOf(Popover)\n\n      expect(popover._config.placement).toEqual('top')\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const popover = new Popover(div, {\n        placement: 'top'\n      })\n      expect(Popover.getInstance(div)).toEqual(popover)\n\n      const popover2 = Popover.getOrCreateInstance(div, {\n        placement: 'bottom'\n      })\n      expect(popover).toBeInstanceOf(Popover)\n      expect(popover2).toEqual(popover)\n\n      expect(popover2._config.placement).toEqual('top')\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/scrollspy.spec.js",
    "content": "import ScrollSpy from '../../src/scrollspy'\n\n/** Test helpers */\nimport { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\nimport EventHandler from '../../src/dom/event-handler'\n\ndescribe('ScrollSpy', () => {\n  let fixtureEl\n\n  const getElementScrollSpy = element => element.scrollTo ?\n    spyOn(element, 'scrollTo').and.callThrough() :\n    spyOnProperty(element, 'scrollTop', 'set').and.callThrough()\n\n  const scrollTo = (el, height) => {\n    el.scrollTop = height\n  }\n\n  const onScrollStop = (callback, element, timeout = 30) => {\n    let handle = null\n    const onScroll = function () {\n      if (handle) {\n        window.clearTimeout(handle)\n      }\n\n      handle = setTimeout(() => {\n        element.removeEventListener('scroll', onScroll)\n        callback()\n      }, timeout + 1)\n    }\n\n    element.addEventListener('scroll', onScroll)\n  }\n\n  const getDummyFixture = () => {\n    return [\n      '<nav id=\"navBar\" class=\"navbar\">',\n      '  <ul class=\"nav\">',\n      '    <li class=\"nav-item\"><a id=\"li-jsm-1\" class=\"nav-link\" href=\"#div-jsm-1\">div 1</a></li>',\n      '  </ul>',\n      '</nav>',\n      '<div class=\"content\" data-bs-target=\"#navBar\" style=\"overflow-y: auto\">',\n      '  <div id=\"div-jsm-1\">div 1</div>',\n      '</div>'\n    ].join('')\n  }\n\n  const testElementIsActiveAfterScroll = ({ elementSelector, targetSelector, contentEl, scrollSpy, cb }) => {\n    const element = fixtureEl.querySelector(elementSelector)\n    const target = fixtureEl.querySelector(targetSelector)\n    // add top padding to fix Chrome on Android failures\n    const paddingTop = 0\n    const parentOffset = getComputedStyle(contentEl).getPropertyValue('position') === 'relative' ? 0 : contentEl.offsetTop\n    const scrollHeight = (target.offsetTop - parentOffset) + paddingTop\n\n    contentEl.addEventListener('activate.bs.scrollspy', event => {\n      if (scrollSpy._activeTarget !== element) {\n        return\n      }\n\n      expect(element).toHaveClass('active')\n      expect(scrollSpy._activeTarget).toEqual(element)\n      expect(event.relatedTarget).toEqual(element)\n      cb()\n    })\n\n    setTimeout(() => { // in case we scroll something before the test\n      scrollTo(contentEl, scrollHeight)\n    }, 100)\n  }\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(ScrollSpy.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(ScrollSpy.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(ScrollSpy.DATA_KEY).toEqual('bs.scrollspy')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const sSpyEl = fixtureEl.querySelector('.content')\n      const sSpyBySelector = new ScrollSpy('.content')\n      const sSpyByElement = new ScrollSpy(sSpyEl)\n\n      expect(sSpyBySelector._element).toEqual(sSpyEl)\n      expect(sSpyByElement._element).toEqual(sSpyEl)\n    })\n\n    it('should null, if element is not scrollable', () => {\n      fixtureEl.innerHTML = [\n        '<nav id=\"navigation\" class=\"navbar\">',\n        '  <ul class=\"navbar-nav\">' +\n        '     <li class=\"nav-item\"><a class=\"nav-link active\" id=\"one-link\" href=\"#\">One</a></li>' +\n        '  </ul>',\n        '</nav>',\n        '<div id=\"content\">',\n        '  <div id=\"1\" style=\"height: 300px;\">test</div>',\n        '</div>'\n      ].join('')\n\n      const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {\n        target: '#navigation'\n      })\n\n      expect(scrollSpy._observer.root).toBeNull()\n      expect(scrollSpy._rootElement).toBeNull()\n    })\n\n    it('should respect threshold option', () => {\n      fixtureEl.innerHTML = [\n        '<ul id=\"navigation\" class=\"navbar\">',\n        '   <a class=\"nav-link active\" id=\"one-link\" href=\"#\">One</a>' +\n        '</ul>',\n        '<div id=\"content\">',\n        '  <div id=\"one-link\">test</div>',\n        '</div>'\n      ].join('')\n\n      const scrollSpy = new ScrollSpy('#content', {\n        target: '#navigation',\n        threshold: [1]\n      })\n\n      expect(scrollSpy._observer.thresholds).toEqual([1])\n    })\n\n    it('should respect threshold option markup', () => {\n      fixtureEl.innerHTML = [\n        '<ul id=\"navigation\" class=\"navbar\">',\n        '   <a class=\"nav-link active\" id=\"one-link\" href=\"#\">One</a>' +\n        '</ul>',\n        '<div id=\"content\" data-bs-threshold=\"0,0.2,1\">',\n        '  <div id=\"one-link\">test</div>',\n        '</div>'\n      ].join('')\n\n      const scrollSpy = new ScrollSpy('#content', {\n        target: '#navigation'\n      })\n\n      // See https://stackoverflow.com/a/45592926\n      const expectToBeCloseToArray = (actual, expected) => {\n        expect(actual.length).toBe(expected.length)\n        for (const x of actual) {\n          const i = actual.indexOf(x)\n          expect(x).withContext(`[${i}]`).toBeCloseTo(expected[i])\n        }\n      }\n\n      expectToBeCloseToArray(scrollSpy._observer.thresholds, [0, 0.2, 1])\n    })\n\n    it('should not take count to not visible sections', () => {\n      fixtureEl.innerHTML = [\n        '<nav id=\"navigation\" class=\"navbar\">',\n        '  <ul class=\"navbar-nav\">',\n        '    <li class=\"nav-item\"><a class=\"nav-link active\" id=\"one-link\" href=\"#one\">One</a></li>',\n        '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"two-link\" href=\"#two\">Two</a></li>',\n        '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"three-link\" href=\"#three\">Three</a></li>',\n        '  </ul>',\n        '</nav>',\n        '<div id=\"content\" style=\"height: 200px; overflow-y: auto;\">',\n        '  <div id=\"one\" style=\"height: 300px;\">test</div>',\n        '  <div id=\"two\" hidden style=\"height: 300px;\">test</div>',\n        '  <div id=\"three\"  style=\"display: none;\">test</div>',\n        '</div>'\n      ].join('')\n\n      const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {\n        target: '#navigation'\n      })\n\n      expect(scrollSpy._observableSections.size).toBe(1)\n      expect(scrollSpy._targetLinks.size).toBe(1)\n    })\n\n    it('should not process element without target', () => {\n      fixtureEl.innerHTML = [\n        '<nav id=\"navigation\" class=\"navbar\">',\n        '  <ul class=\"navbar-nav\">',\n        '    <li class=\"nav-item\"><a class=\"nav-link active\" id=\"one-link\" href=\"#\">One</a></li>',\n        '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"two-link\" href=\"#two\">Two</a></li>',\n        '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"three-link\" href=\"#three\">Three</a></li>',\n        '  </ul>',\n        '</nav>',\n        '<div id=\"content\" style=\"height: 200px; overflow-y: auto;\">',\n        '  <div id=\"two\" style=\"height: 300px;\">test</div>',\n        '  <div id=\"three\" style=\"height: 10px;\">test2</div>',\n        '</div>'\n      ].join('')\n\n      const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {\n        target: '#navigation'\n      })\n\n      expect(scrollSpy._targetLinks).toHaveSize(2)\n    })\n\n    it('should only switch \"active\" class on current target', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"root\" class=\"active\" style=\"display: block\">',\n          '  <div class=\"topbar\">',\n          '    <div class=\"topbar-inner\">',\n          '      <div class=\"container\" id=\"ss-target\">',\n          '        <ul class=\"nav\">',\n          '          <li class=\"nav-item\"><a href=\"#masthead\">Overview</a></li>',\n          '          <li class=\"nav-item\"><a href=\"#detail\">Detail</a></li>',\n          '        </ul>',\n          '      </div>',\n          '    </div>',\n          '  </div>',\n          '  <div id=\"scrollspy-example\" style=\"height: 100px; overflow: auto;\">',\n          '     <div style=\"height: 200px;\" id=\"masthead\">Overview</div>',\n          '     <div style=\"height: 200px;\" id=\"detail\">Detail</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')\n        const rootEl = fixtureEl.querySelector('#root')\n        const scrollSpy = new ScrollSpy(scrollSpyEl, {\n          target: 'ss-target'\n        })\n\n        const spy = spyOn(scrollSpy, '_process').and.callThrough()\n\n        onScrollStop(() => {\n          expect(rootEl).toHaveClass('active')\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        }, scrollSpyEl)\n\n        scrollTo(scrollSpyEl, 350)\n      })\n    })\n\n    it('should not process data if `activeTarget` is same as given target', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar\">',\n          '  <ul class=\"nav\">',\n          '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"a-1\" href=\"#div-1\">div 1</a></li>',\n          '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"a-2\" href=\"#div-2\">div 2</a></li>',\n          '  </ul>',\n          '</nav>',\n          '<div class=\"content\" style=\"overflow: auto; height: 50px\">',\n          '  <div id=\"div-1\" style=\"height: 100px; padding: 0; margin: 0\">div 1</div>',\n          '  <div id=\"div-2\" style=\"height: 200px; padding: 0; margin: 0\">div 2</div>',\n          '</div>'\n        ].join('')\n\n        const contentEl = fixtureEl.querySelector('.content')\n        const scrollSpy = new ScrollSpy(contentEl, {\n          offset: 0,\n          target: '.navbar'\n        })\n\n        const triggerSpy = spyOn(EventHandler, 'trigger').and.callThrough()\n\n        scrollSpy._activeTarget = fixtureEl.querySelector('#a-1')\n        testElementIsActiveAfterScroll({\n          elementSelector: '#a-1',\n          targetSelector: '#div-1',\n          contentEl,\n          scrollSpy,\n          cb: reject\n        })\n\n        setTimeout(() => {\n          expect(triggerSpy).not.toHaveBeenCalled()\n          resolve()\n        }, 100)\n      })\n    })\n\n    it('should only switch \"active\" class on current target specified w element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"root\" class=\"active\" style=\"display: block\">',\n          '  <div class=\"topbar\">',\n          '    <div class=\"topbar-inner\">',\n          '      <div class=\"container\" id=\"ss-target\">',\n          '        <ul class=\"nav\">',\n          '          <li class=\"nav-item\"><a href=\"#masthead\">Overview</a></li>',\n          '          <li class=\"nav-item\"><a href=\"#detail\">Detail</a></li>',\n          '        </ul>',\n          '      </div>',\n          '    </div>',\n          '  </div>',\n          '  <div id=\"scrollspy-example\" style=\"height: 100px; overflow: auto;\">',\n          '    <div style=\"height: 200px;\" id=\"masthead\">Overview</div>',\n          '    <div style=\"height: 200px;\" id=\"detail\">Detail</div>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')\n        const rootEl = fixtureEl.querySelector('#root')\n        const scrollSpy = new ScrollSpy(scrollSpyEl, {\n          target: fixtureEl.querySelector('#ss-target')\n        })\n\n        const spy = spyOn(scrollSpy, '_process').and.callThrough()\n\n        onScrollStop(() => {\n          expect(rootEl).toHaveClass('active')\n          expect(scrollSpy._activeTarget).toEqual(fixtureEl.querySelector('[href=\"#detail\"]'))\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        }, scrollSpyEl)\n\n        scrollTo(scrollSpyEl, 350)\n      })\n    })\n\n    it('should add the active class to the correct element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar\">',\n          '  <ul class=\"nav\">',\n          '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"a-1\" href=\"#div-1\">div 1</a></li>',\n          '    <li class=\"nav-item\"><a class=\"nav-link\" id=\"a-2\" href=\"#div-2\">div 2</a></li>',\n          '  </ul>',\n          '</nav>',\n          '<div class=\"content\" style=\"overflow: auto; height: 50px\">',\n          '  <div id=\"div-1\" style=\"height: 100px; padding: 0; margin: 0\">div 1</div>',\n          '  <div id=\"div-2\" style=\"height: 200px; padding: 0; margin: 0\">div 2</div>',\n          '</div>'\n        ].join('')\n\n        const contentEl = fixtureEl.querySelector('.content')\n        const scrollSpy = new ScrollSpy(contentEl, {\n          offset: 0,\n          target: '.navbar'\n        })\n\n        testElementIsActiveAfterScroll({\n          elementSelector: '#a-1',\n          targetSelector: '#div-1',\n          contentEl,\n          scrollSpy,\n          cb() {\n            testElementIsActiveAfterScroll({\n              elementSelector: '#a-2',\n              targetSelector: '#div-2',\n              contentEl,\n              scrollSpy,\n              cb: resolve\n            })\n          }\n        })\n      })\n    })\n\n    it('should add to nav the active class to the correct element (nav markup)', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar\">',\n          '  <nav class=\"nav\">',\n          '    <a class=\"nav-link\" id=\"a-1\" href=\"#div-1\">div 1</a>',\n          '    <a class=\"nav-link\" id=\"a-2\" href=\"#div-2\">div 2</a>',\n          '  </nav>',\n          '</nav>',\n          '<div class=\"content\" style=\"overflow: auto; height: 50px\">',\n          '  <div id=\"div-1\" style=\"height: 100px; padding: 0; margin: 0\">div 1</div>',\n          '  <div id=\"div-2\" style=\"height: 200px; padding: 0; margin: 0\">div 2</div>',\n          '</div>'\n        ].join('')\n\n        const contentEl = fixtureEl.querySelector('.content')\n        const scrollSpy = new ScrollSpy(contentEl, {\n          offset: 0,\n          target: '.navbar'\n        })\n\n        testElementIsActiveAfterScroll({\n          elementSelector: '#a-1',\n          targetSelector: '#div-1',\n          contentEl,\n          scrollSpy,\n          cb() {\n            testElementIsActiveAfterScroll({\n              elementSelector: '#a-2',\n              targetSelector: '#div-2',\n              contentEl,\n              scrollSpy,\n              cb: resolve\n            })\n          }\n        })\n      })\n    })\n\n    it('should add to list-group, the active class to the correct element (list-group markup)', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar\">',\n          '  <div class=\"list-group\">',\n          '    <a class=\"list-group-item\" id=\"a-1\" href=\"#div-1\">div 1</a>',\n          '    <a class=\"list-group-item\" id=\"a-2\" href=\"#div-2\">div 2</a>',\n          '  </div>',\n          '</nav>',\n          '<div class=\"content\" style=\"overflow: auto; height: 50px\">',\n          '  <div id=\"div-1\" style=\"height: 100px; padding: 0; margin: 0\">div 1</div>',\n          '  <div id=\"div-2\" style=\"height: 200px; padding: 0; margin: 0\">div 2</div>',\n          '</div>'\n        ].join('')\n\n        const contentEl = fixtureEl.querySelector('.content')\n        const scrollSpy = new ScrollSpy(contentEl, {\n          offset: 0,\n          target: '.navbar'\n        })\n\n        testElementIsActiveAfterScroll({\n          elementSelector: '#a-1',\n          targetSelector: '#div-1',\n          contentEl,\n          scrollSpy,\n          cb() {\n            testElementIsActiveAfterScroll({\n              elementSelector: '#a-2',\n              targetSelector: '#div-2',\n              contentEl,\n              scrollSpy,\n              cb: resolve\n            })\n          }\n        })\n      })\n    })\n\n    it('should clear selection if above the first section', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"header\" style=\"height: 500px;\"></div>',\n          '<nav id=\"navigation\" class=\"navbar\">',\n          '  <ul class=\"navbar-nav\">',\n          '    <li class=\"nav-item\"><a id=\"one-link\"   class=\"nav-link active\" href=\"#one\">One</a></li>',\n          '    <li class=\"nav-item\"><a id=\"two-link\"   class=\"nav-link\" href=\"#two\">Two</a></li>',\n          '    <li class=\"nav-item\"><a id=\"three-link\" class=\"nav-link\" href=\"#three\">Three</a></li>',\n          '  </ul>',\n          '</nav>',\n          '<div id=\"content\" style=\"height: 200px; overflow-y: auto;\">',\n          '  <div id=\"spacer\" style=\"height: 200px;\"></div>',\n          '  <div id=\"one\" style=\"height: 100px;\">text</div>',\n          '  <div id=\"two\" style=\"height: 100px;\">text</div>',\n          '  <div id=\"three\" style=\"height: 100px;\">text</div>',\n          '  <div id=\"spacer\" style=\"height: 100px;\"></div>',\n          '</div>'\n        ].join('')\n\n        const contentEl = fixtureEl.querySelector('#content')\n        const scrollSpy = new ScrollSpy(contentEl, {\n          target: '#navigation',\n          offset: contentEl.offsetTop\n        })\n        const spy = spyOn(scrollSpy, '_process').and.callThrough()\n\n        onScrollStop(() => {\n          const active = () => fixtureEl.querySelector('.active')\n          expect(spy).toHaveBeenCalled()\n\n          expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)\n          expect(active().getAttribute('id')).toEqual('two-link')\n          onScrollStop(() => {\n            expect(active()).toBeNull()\n            resolve()\n          }, contentEl)\n          scrollTo(contentEl, 0)\n        }, contentEl)\n\n        scrollTo(contentEl, 200)\n      })\n    })\n\n    it('should not clear selection if above the first section and first section is at the top', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div id=\"header\" style=\"height: 500px;\"></div>',\n          '<nav id=\"navigation\" class=\"navbar\">',\n          '  <ul class=\"navbar-nav\">',\n          '    <li class=\"nav-item\"><a id=\"one-link\" class=\"nav-link active\" href=\"#one\">One</a></li>',\n          '    <li class=\"nav-item\"><a id=\"two-link\" class=\"nav-link\" href=\"#two\">Two</a></li>',\n          '    <li class=\"nav-item\"><a id=\"three-link\" class=\"nav-link\" href=\"#three\">Three</a></li>',\n          '  </ul>',\n          '</nav>',\n          '<div id=\"content\" style=\"height: 150px; overflow-y: auto;\">',\n          '  <div id=\"one\" style=\"height: 100px;\">test</div>',\n          '  <div id=\"two\" style=\"height: 100px;\">test</div>',\n          '  <div id=\"three\" style=\"height: 100px;\">test</div>',\n          '  <div id=\"spacer\" style=\"height: 100px;\">test</div>',\n          '</div>'\n        ].join('')\n\n        const negativeHeight = 0\n        const startOfSectionTwo = 101\n        const contentEl = fixtureEl.querySelector('#content')\n        // eslint-disable-next-line no-unused-vars\n        const scrollSpy = new ScrollSpy(contentEl, {\n          target: '#navigation',\n          rootMargin: '0px 0px -50%'\n        })\n\n        onScrollStop(() => {\n          const activeId = () => fixtureEl.querySelector('.active').getAttribute('id')\n\n          expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)\n          expect(activeId()).toEqual('two-link')\n          scrollTo(contentEl, negativeHeight)\n\n          onScrollStop(() => {\n            expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)\n            expect(activeId()).toEqual('one-link')\n            resolve()\n          }, contentEl)\n\n          scrollTo(contentEl, 0)\n        }, contentEl)\n\n        scrollTo(contentEl, startOfSectionTwo)\n      })\n    })\n\n    it('should correctly select navigation element on backward scrolling when each target section height is 100%', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"navbar\">',\n          '  <ul class=\"nav\">',\n          '    <li class=\"nav-item\"><a id=\"li-100-1\" class=\"nav-link\" href=\"#div-100-1\">div 1</a></li>',\n          '    <li class=\"nav-item\"><a id=\"li-100-2\" class=\"nav-link\" href=\"#div-100-2\">div 2</a></li>',\n          '    <li class=\"nav-item\"><a id=\"li-100-3\" class=\"nav-link\" href=\"#div-100-3\">div 3</a></li>',\n          '    <li class=\"nav-item\"><a id=\"li-100-4\" class=\"nav-link\" href=\"#div-100-4\">div 4</a></li>',\n          '    <li class=\"nav-item\"><a id=\"li-100-5\" class=\"nav-link\" href=\"#div-100-5\">div 5</a></li>',\n          '  </ul>',\n          '</nav>',\n          '<div class=\"content\" style=\"position: relative; overflow: auto; height: 100px\">',\n          '  <div id=\"div-100-1\" style=\"position: relative; height: 100%; padding: 0; margin: 0\">div 1</div>',\n          '  <div id=\"div-100-2\" style=\"position: relative; height: 100%; padding: 0; margin: 0\">div 2</div>',\n          '  <div id=\"div-100-3\" style=\"position: relative; height: 100%; padding: 0; margin: 0\">div 3</div>',\n          '  <div id=\"div-100-4\" style=\"position: relative; height: 100%; padding: 0; margin: 0\">div 4</div>',\n          '  <div id=\"div-100-5\" style=\"position: relative; height: 100%; padding: 0; margin: 0\">div 5</div>',\n          '</div>'\n        ].join('')\n\n        const contentEl = fixtureEl.querySelector('.content')\n        const scrollSpy = new ScrollSpy(contentEl, {\n          offset: 0,\n          target: '.navbar'\n        })\n\n        scrollTo(contentEl, 0)\n        testElementIsActiveAfterScroll({\n          elementSelector: '#li-100-5',\n          targetSelector: '#div-100-5',\n          contentEl,\n          scrollSpy,\n          cb() {\n            scrollTo(contentEl, 0)\n            testElementIsActiveAfterScroll({\n              elementSelector: '#li-100-2',\n              targetSelector: '#div-100-2',\n              contentEl,\n              scrollSpy,\n              cb() {\n                scrollTo(contentEl, 0)\n                testElementIsActiveAfterScroll({\n                  elementSelector: '#li-100-3',\n                  targetSelector: '#div-100-3',\n                  contentEl,\n                  scrollSpy,\n                  cb() {\n                    scrollTo(contentEl, 0)\n                    testElementIsActiveAfterScroll({\n                      elementSelector: '#li-100-2',\n                      targetSelector: '#div-100-2',\n                      contentEl,\n                      scrollSpy,\n                      cb() {\n                        scrollTo(contentEl, 0)\n                        testElementIsActiveAfterScroll({\n                          elementSelector: '#li-100-1',\n                          targetSelector: '#div-100-1',\n                          contentEl,\n                          scrollSpy,\n                          cb: resolve\n                        })\n                      }\n                    })\n                  }\n                })\n              }\n            })\n          }\n        })\n      })\n    })\n  })\n\n  describe('refresh', () => {\n    it('should disconnect existing observer', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const el = fixtureEl.querySelector('.content')\n      const scrollSpy = new ScrollSpy(el)\n\n      const spy = spyOn(scrollSpy._observer, 'disconnect')\n\n      scrollSpy.refresh()\n\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose a scrollspy', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const el = fixtureEl.querySelector('.content')\n      const scrollSpy = new ScrollSpy(el)\n\n      expect(ScrollSpy.getInstance(el)).not.toBeNull()\n\n      scrollSpy.dispose()\n\n      expect(ScrollSpy.getInstance(el)).toBeNull()\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a scrollspy', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.scrollspy.call(jQueryMock, { target: '#navBar' })\n\n      expect(ScrollSpy.getInstance(div)).not.toBeNull()\n    })\n\n    it('should create a scrollspy with given config', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.scrollspy.call(jQueryMock, { rootMargin: '100px' })\n      const spy = spyOn(ScrollSpy.prototype, 'constructor')\n      expect(spy).not.toHaveBeenCalledWith(div, { rootMargin: '100px' })\n\n      const scrollspy = ScrollSpy.getInstance(div)\n      expect(scrollspy).not.toBeNull()\n      expect(scrollspy._config.rootMargin).toEqual('100px')\n    })\n\n    it('should not re create a scrollspy', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const scrollSpy = new ScrollSpy(div)\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.scrollspy.call(jQueryMock)\n\n      expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)\n    })\n\n    it('should call a scrollspy method', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const scrollSpy = new ScrollSpy(div)\n\n      const spy = spyOn(scrollSpy, 'refresh')\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.scrollspy.call(jQueryMock, 'refresh')\n\n      expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.scrollspy.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should throw error on protected method', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const action = '_getConfig'\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.scrollspy.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n\n    it('should throw error if method \"constructor\" is being called', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const action = 'constructor'\n\n      jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.scrollspy.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return scrollspy instance', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const scrollSpy = new ScrollSpy(div, { target: fixtureEl.querySelector('#navBar') })\n\n      expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)\n      expect(ScrollSpy.getInstance(div)).toBeInstanceOf(ScrollSpy)\n    })\n\n    it('should return null if there is no instance', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      expect(ScrollSpy.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return scrollspy instance', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const scrollspy = new ScrollSpy(div)\n\n      expect(ScrollSpy.getOrCreateInstance(div)).toEqual(scrollspy)\n      expect(ScrollSpy.getInstance(div)).toEqual(ScrollSpy.getOrCreateInstance(div, {}))\n      expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy)\n    })\n\n    it('should return new instance when there is no scrollspy instance', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n\n      expect(ScrollSpy.getInstance(div)).toBeNull()\n      expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy)\n    })\n\n    it('should return new instance when there is no scrollspy instance with given configuration', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n\n      expect(ScrollSpy.getInstance(div)).toBeNull()\n      const scrollspy = ScrollSpy.getOrCreateInstance(div, {\n        offset: 1\n      })\n      expect(scrollspy).toBeInstanceOf(ScrollSpy)\n\n      expect(scrollspy._config.offset).toEqual(1)\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const scrollspy = new ScrollSpy(div, {\n        offset: 1\n      })\n      expect(ScrollSpy.getInstance(div)).toEqual(scrollspy)\n\n      const scrollspy2 = ScrollSpy.getOrCreateInstance(div, {\n        offset: 2\n      })\n      expect(scrollspy).toBeInstanceOf(ScrollSpy)\n      expect(scrollspy2).toEqual(scrollspy)\n\n      expect(scrollspy2._config.offset).toEqual(1)\n    })\n  })\n\n  describe('event handler', () => {\n    it('should create scrollspy on window load event', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"nav\"></div>' +\n        '<div id=\"wrapper\" data-bs-spy=\"scroll\" data-bs-target=\"#nav\" style=\"overflow-y: auto\"></div>'\n      ].join('')\n\n      const scrollSpyEl = fixtureEl.querySelector('#wrapper')\n\n      window.dispatchEvent(createEvent('load'))\n\n      expect(ScrollSpy.getInstance(scrollSpyEl)).not.toBeNull()\n    })\n  })\n\n  describe('SmoothScroll', () => {\n    it('should not enable smoothScroll', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n      const offSpy = spyOn(EventHandler, 'off').and.callThrough()\n      const onSpy = spyOn(EventHandler, 'on').and.callThrough()\n\n      const div = fixtureEl.querySelector('.content')\n      const target = fixtureEl.querySelector('#navBar')\n      // eslint-disable-next-line no-new\n      new ScrollSpy(div, {\n        offset: 1\n      })\n\n      expect(offSpy).not.toHaveBeenCalledWith(target, 'click.bs.scrollspy')\n      expect(onSpy).not.toHaveBeenCalledWith(target, 'click.bs.scrollspy')\n    })\n\n    it('should enable smoothScroll', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n      const offSpy = spyOn(EventHandler, 'off').and.callThrough()\n      const onSpy = spyOn(EventHandler, 'on').and.callThrough()\n\n      const div = fixtureEl.querySelector('.content')\n      const target = fixtureEl.querySelector('#navBar')\n      // eslint-disable-next-line no-new\n      new ScrollSpy(div, {\n        offset: 1,\n        smoothScroll: true\n      })\n\n      expect(offSpy).toHaveBeenCalledWith(target, 'click.bs.scrollspy')\n      expect(onSpy).toHaveBeenCalledWith(target, 'click.bs.scrollspy', '[href]', jasmine.any(Function))\n    })\n\n    it('should not smoothScroll to element if it not handles a scrollspy section', () => {\n      fixtureEl.innerHTML = [\n        '<nav id=\"navBar\" class=\"navbar\">',\n        '  <ul class=\"nav\">',\n        '    <a id=\"anchor-1\" href=\"#div-jsm-1\">div 1</a></li>',\n        '    <a id=\"anchor-2\" href=\"#foo\">div 2</a></li>',\n        '  </ul>',\n        '</nav>',\n        '<div class=\"content\" data-bs-target=\"#navBar\" style=\"overflow-y: auto\">',\n        '  <div id=\"div-jsm-1\">div 1</div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('.content')\n      // eslint-disable-next-line no-new\n      new ScrollSpy(div, {\n        offset: 1,\n        smoothScroll: true\n      })\n\n      const clickSpy = getElementScrollSpy(div)\n\n      fixtureEl.querySelector('#anchor-2').click()\n      expect(clickSpy).not.toHaveBeenCalled()\n    })\n\n    it('should call `scrollTop` if element doesn\\'t not support `scrollTo`', () => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const link = fixtureEl.querySelector('[href=\"#div-jsm-1\"]')\n      delete div.scrollTo\n      const clickSpy = getElementScrollSpy(div)\n      // eslint-disable-next-line no-new\n      new ScrollSpy(div, {\n        offset: 1,\n        smoothScroll: true\n      })\n\n      link.click()\n      expect(clickSpy).toHaveBeenCalled()\n    })\n\n    it('should smoothScroll to the proper observable element on anchor click', done => {\n      fixtureEl.innerHTML = getDummyFixture()\n\n      const div = fixtureEl.querySelector('.content')\n      const link = fixtureEl.querySelector('[href=\"#div-jsm-1\"]')\n      const observable = fixtureEl.querySelector('#div-jsm-1')\n      const clickSpy = getElementScrollSpy(div)\n      // eslint-disable-next-line no-new\n      new ScrollSpy(div, {\n        offset: 1,\n        smoothScroll: true\n      })\n\n      setTimeout(() => {\n        if (div.scrollTo) {\n          expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' })\n        } else {\n          expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop)\n        }\n\n        done()\n      }, 100)\n      link.click()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/tab.spec.js",
    "content": "import Tab from '../../src/tab'\nimport { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Tab', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Tab.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = [\n        '<ul class=\"nav\">',\n        '  <li><a href=\"#home\" role=\"tab\">Home</a></li>',\n        '</ul>',\n        '<ul>',\n        '  <li id=\"home\"></li>',\n        '</ul>'\n      ].join('')\n\n      const tabEl = fixtureEl.querySelector('[href=\"#home\"]')\n      const tabBySelector = new Tab('[href=\"#home\"]')\n      const tabByElement = new Tab(tabEl)\n\n      expect(tabBySelector._element).toEqual(tabEl)\n      expect(tabByElement._element).toEqual(tabEl)\n    })\n\n    it('Do not Throw exception if not parent', () => {\n      fixtureEl.innerHTML = [\n        fixtureEl.innerHTML = '<div class=\"\"><div class=\"nav-link\"></div></div>'\n      ].join('')\n      const navEl = fixtureEl.querySelector('.nav-link')\n\n      expect(() => {\n        new Tab(navEl) // eslint-disable-line no-new\n      }).not.toThrowError(TypeError)\n    })\n  })\n\n  describe('show', () => {\n    it('should activate element by tab id (using buttons, the preferred semantic way)', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav\" role=\"tablist\">',\n          '  <li><button type=\"button\" data-bs-target=\"#home\" role=\"tab\">Home</button></li>',\n          '  <li><button type=\"button\" id=\"triggerProfile\" data-bs-target=\"#profile\" role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<ul>',\n          '  <li id=\"home\" role=\"tabpanel\"></li>',\n          '  <li id=\"profile\" role=\"tabpanel\"></li>',\n          '</ul>'\n        ].join('')\n\n        const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')\n        const tab = new Tab(profileTriggerEl)\n\n        profileTriggerEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelector('#profile')).toHaveClass('active')\n          expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')\n          resolve()\n        })\n\n        tab.show()\n      })\n    })\n\n    it('should activate element by tab id (using links for tabs - not ideal, but still supported)', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav\" role=\"tablist\">',\n          '  <li><a href=\"#home\" role=\"tab\">Home</a></li>',\n          '  <li><a id=\"triggerProfile\" href=\"#profile\" role=\"tab\">Profile</a></li>',\n          '</ul>',\n          '<ul>',\n          '  <li id=\"home\" role=\"tabpanel\"></li>',\n          '  <li id=\"profile\" role=\"tabpanel\"></li>',\n          '</ul>'\n        ].join('')\n\n        const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')\n        const tab = new Tab(profileTriggerEl)\n\n        profileTriggerEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelector('#profile')).toHaveClass('active')\n          expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')\n          resolve()\n        })\n\n        tab.show()\n      })\n    })\n\n    it('should activate element by tab id in ordered list', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ol class=\"nav nav-pills\">',\n          '  <li><button type=\"button\" data-bs-target=\"#home\" role=\"tab\">Home</button></li>',\n          '  <li><button type=\"button\" id=\"triggerProfile\" href=\"#profile\" role=\"tab\">Profile</button></li>',\n          '</ol>',\n          '<ol>',\n          '  <li id=\"home\" role=\"tabpanel\"></li>',\n          '  <li id=\"profile\" role=\"tabpanel\"></li>',\n          '</ol>'\n        ].join('')\n\n        const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')\n        const tab = new Tab(profileTriggerEl)\n\n        profileTriggerEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelector('#profile')).toHaveClass('active')\n          resolve()\n        })\n\n        tab.show()\n      })\n    })\n\n    it('should activate element by tab id in nav list', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"nav\">',\n          '  <button type=\"button\" data-bs-target=\"#home\" role=\"tab\">Home</button>',\n          '  <button type=\"button\" id=\"triggerProfile\" data-bs-target=\"#profile\" role=\"tab\">Profile</button>',\n          '</nav>',\n          '<div>',\n          '  <div id=\"home\" role=\"tabpanel\"></div>',\n          '  <div id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')\n        const tab = new Tab(profileTriggerEl)\n\n        profileTriggerEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelector('#profile')).toHaveClass('active')\n          resolve()\n        })\n\n        tab.show()\n      })\n    })\n\n    it('should activate element by tab id in list group', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"list-group\" role=\"tablist\">',\n          '  <button type=\"button\" data-bs-target=\"#home\" role=\"tab\">Home</button>',\n          '  <button type=\"button\" id=\"triggerProfile\" data-bs-target=\"#profile\" role=\"tab\">Profile</button>',\n          '</div>',\n          '<div>',\n          '  <div id=\"home\" role=\"tabpanel\"></div>',\n          '  <div id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')\n        const tab = new Tab(profileTriggerEl)\n\n        profileTriggerEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelector('#profile')).toHaveClass('active')\n          resolve()\n        })\n\n        tab.show()\n      })\n    })\n\n    it('should not fire shown when show is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n        const navEl = fixtureEl.querySelector('.nav > div')\n        const tab = new Tab(navEl)\n        const expectDone = () => {\n          setTimeout(() => {\n            expect().nothing()\n            resolve()\n          }, 30)\n        }\n\n        navEl.addEventListener('show.bs.tab', ev => {\n          ev.preventDefault()\n          expectDone()\n        })\n\n        navEl.addEventListener('shown.bs.tab', () => {\n          reject(new Error('should not trigger shown event'))\n        })\n\n        tab.show()\n      })\n    })\n\n    it('should not fire shown when tab is already active', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" data-bs-target=\"#home\" class=\"nav-link active\" role=\"tab\" aria-selected=\"true\">Home</button></li>',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" data-bs-target=\"#profile\" class=\"nav-link\" role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\"></div>',\n          '  <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const triggerActive = fixtureEl.querySelector('button.active')\n        const tab = new Tab(triggerActive)\n\n        triggerActive.addEventListener('shown.bs.tab', () => {\n          reject(new Error('should not trigger shown event'))\n        })\n\n        tab.show()\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 30)\n      })\n    })\n\n    it('show and shown events should reference correct relatedTarget', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" data-bs-target=\"#home\" class=\"nav-link active\" role=\"tab\" aria-selected=\"true\">Home</button></li>',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" id=\"triggerProfile\" data-bs-target=\"#profile\" class=\"nav-link\" role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\"></div>',\n          '  <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')\n        const secondTab = new Tab(secondTabTrigger)\n\n        secondTabTrigger.addEventListener('show.bs.tab', ev => {\n          expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#home')\n        })\n\n        secondTabTrigger.addEventListener('shown.bs.tab', ev => {\n          expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#home')\n          expect(secondTabTrigger.getAttribute('aria-selected')).toEqual('true')\n          expect(fixtureEl.querySelector('button:not(.active)').getAttribute('aria-selected')).toEqual('false')\n          resolve()\n        })\n\n        secondTab.show()\n      })\n    })\n\n    it('should fire hide and hidden events', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav\" role=\"tablist\">',\n          '  <li><button type=\"button\" data-bs-target=\"#home\" role=\"tab\">Home</button></li>',\n          '  <li><button type=\"button\" data-bs-target=\"#profile\" role=\"tab\">Profile</button></li>',\n          '</ul>'\n        ].join('')\n\n        const triggerList = fixtureEl.querySelectorAll('button')\n        const firstTab = new Tab(triggerList[0])\n        const secondTab = new Tab(triggerList[1])\n\n        let hideCalled = false\n        triggerList[0].addEventListener('shown.bs.tab', () => {\n          secondTab.show()\n        })\n\n        triggerList[0].addEventListener('hide.bs.tab', ev => {\n          hideCalled = true\n          expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')\n        })\n\n        triggerList[0].addEventListener('hidden.bs.tab', ev => {\n          expect(hideCalled).toBeTrue()\n          expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')\n          resolve()\n        })\n\n        firstTab.show()\n      })\n    })\n\n    it('should not fire hidden when hide is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav\" role=\"tablist\">',\n          '  <li><button type=\"button\" data-bs-target=\"#home\" role=\"tab\">Home</button></li>',\n          '  <li><button type=\"button\" data-bs-target=\"#profile\" role=\"tab\">Profile</button></li>',\n          '</ul>'\n        ].join('')\n\n        const triggerList = fixtureEl.querySelectorAll('button')\n        const firstTab = new Tab(triggerList[0])\n        const secondTab = new Tab(triggerList[1])\n        const expectDone = () => {\n          setTimeout(() => {\n            expect().nothing()\n            resolve()\n          }, 30)\n        }\n\n        triggerList[0].addEventListener('shown.bs.tab', () => {\n          secondTab.show()\n        })\n\n        triggerList[0].addEventListener('hide.bs.tab', ev => {\n          ev.preventDefault()\n          expectDone()\n        })\n\n        triggerList[0].addEventListener('hidden.bs.tab', () => {\n          reject(new Error('should not trigger hidden'))\n        })\n\n        firstTab.show()\n      })\n    })\n\n    it('should handle removed tabs', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <a class=\"nav-link nav-tab\" href=\"#profile\" role=\"tab\" data-bs-toggle=\"tab\">',\n          '      <button class=\"btn-close\" aria-label=\"Close\"></button>',\n          '    </a>',\n          '  </li>',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <a id=\"secondNav\" class=\"nav-link nav-tab\" href=\"#buzz\" role=\"tab\" data-bs-toggle=\"tab\">',\n          '      <button class=\"btn-close\" aria-label=\"Close\"></button>',\n          '    </a>',\n          '  </li>',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <a class=\"nav-link nav-tab\" href=\"#references\" role=\"tab\" data-bs-toggle=\"tab\">',\n          '      <button id=\"btnClose\" class=\"btn-close\" aria-label=\"Close\"></button>',\n          '    </a>',\n          '  </li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div role=\"tabpanel\" class=\"tab-pane fade show active\" id=\"profile\">test 1</div>',\n          '  <div role=\"tabpanel\" class=\"tab-pane fade\" id=\"buzz\">test 2</div>',\n          '  <div role=\"tabpanel\" class=\"tab-pane fade\" id=\"references\">test 3</div>',\n          '</div>'\n        ].join('')\n\n        const secondNavEl = fixtureEl.querySelector('#secondNav')\n        const btnCloseEl = fixtureEl.querySelector('#btnClose')\n        const secondNavTab = new Tab(secondNavEl)\n\n        secondNavEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelectorAll('.nav-tab')).toHaveSize(2)\n          resolve()\n        })\n\n        btnCloseEl.addEventListener('click', () => {\n          const linkEl = btnCloseEl.parentNode\n          const liEl = linkEl.parentNode\n          const tabId = linkEl.getAttribute('href')\n          const tabIdEl = fixtureEl.querySelector(tabId)\n\n          liEl.remove()\n          tabIdEl.remove()\n          secondNavTab.show()\n        })\n\n        btnCloseEl.click()\n      })\n    })\n\n    it('should not focus on opened tab', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav\" role=\"tablist\">',\n          '  <li><button type=\"button\" id=\"home\" data-bs-target=\"#home\" role=\"tab\">Home</button></li>',\n          '  <li><button type=\"button\" id=\"triggerProfile\" data-bs-target=\"#profile\" role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<ul>',\n          '  <li id=\"home\" role=\"tabpanel\"></li>',\n          '  <li id=\"profile\" role=\"tabpanel\"></li>',\n          '</ul>'\n        ].join('')\n\n        const firstTab = fixtureEl.querySelector('#home')\n        firstTab.focus()\n\n        const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')\n        const tab = new Tab(profileTriggerEl)\n\n        profileTriggerEl.addEventListener('shown.bs.tab', () => {\n          expect(document.activeElement).toBe(firstTab)\n          expect(document.activeElement).not.toBe(profileTriggerEl)\n          resolve()\n        })\n\n        tab.show()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose a tab', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const el = fixtureEl.querySelector('.nav > div')\n      const tab = new Tab(fixtureEl.querySelector('.nav > div'))\n\n      expect(Tab.getInstance(el)).not.toBeNull()\n\n      tab.dispose()\n\n      expect(Tab.getInstance(el)).toBeNull()\n    })\n  })\n\n  describe('_activate', () => {\n    it('should not be called if element argument is null', () => {\n      fixtureEl.innerHTML = [\n        '<ul class=\"nav\" role=\"tablist\">',\n        '  <li class=\"nav-link\"></li>',\n        '</ul>'\n      ].join('')\n\n      const tabEl = fixtureEl.querySelector('.nav-link')\n      const tab = new Tab(tabEl)\n      const spy = jasmine.createSpy('spy')\n\n      const spyQueue = spyOn(tab, '_queueCallback')\n      tab._activate(null, spy)\n      expect(spyQueue).not.toHaveBeenCalled()\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('_setInitialAttributes', () => {\n    it('should put aria attributes', () => {\n      fixtureEl.innerHTML = [\n        '<ul class=\"nav\">',\n        '  <li class=\"nav-link\" id=\"foo\" data-bs-target=\"#panel\"></li>',\n        '  <li class=\"nav-link\" data-bs-target=\"#panel2\"></li>',\n        '</ul>',\n        '<div id=\"panel\"></div>',\n        '<div id=\"panel2\"></div>'\n      ].join('')\n\n      const tabEl = fixtureEl.querySelector('.nav-link')\n      const parent = fixtureEl.querySelector('.nav')\n      const children = fixtureEl.querySelectorAll('.nav-link')\n      const tabPanel = fixtureEl.querySelector('#panel')\n      const tabPanel2 = fixtureEl.querySelector('#panel2')\n\n      expect(parent.getAttribute('role')).toEqual(null)\n      expect(tabEl.getAttribute('role')).toEqual(null)\n      expect(tabPanel.getAttribute('role')).toEqual(null)\n      const tab = new Tab(tabEl)\n      tab._setInitialAttributes(parent, children)\n\n      expect(parent.getAttribute('role')).toEqual('tablist')\n      expect(tabEl.getAttribute('role')).toEqual('tab')\n\n      expect(tabPanel.getAttribute('role')).toEqual('tabpanel')\n      expect(tabPanel2.getAttribute('role')).toEqual('tabpanel')\n      expect(tabPanel.hasAttribute('tabindex')).toBeFalse()\n      expect(tabPanel.hasAttribute('tabindex2')).toBeFalse()\n\n      expect(tabPanel.getAttribute('aria-labelledby')).toEqual('#foo')\n      expect(tabPanel2.hasAttribute('aria-labelledby')).toBeFalse()\n    })\n  })\n\n  describe('_keydown', () => {\n    it('if event is not one of left/right/up/down arrow, ignore it', () => {\n      fixtureEl.innerHTML = [\n        '<ul class=\"nav\">',\n        '  <li class=\"nav-link\" data-bs-toggle=\"tab\"></li>',\n        '</ul>'\n      ].join('')\n\n      const tabEl = fixtureEl.querySelector('.nav-link')\n      const tab = new Tab(tabEl)\n\n      const keydown = createEvent('keydown')\n      keydown.key = 'Enter'\n      const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()\n      const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n      const spyKeydown = spyOn(tab, '_keydown')\n      const spyGet = spyOn(tab, '_getChildren')\n\n      tabEl.dispatchEvent(keydown)\n      expect(spyKeydown).toHaveBeenCalled()\n      expect(spyGet).not.toHaveBeenCalled()\n\n      expect(spyStop).not.toHaveBeenCalled()\n      expect(spyPrevent).not.toHaveBeenCalled()\n    })\n\n    it('if keydown event is right/down arrow, handle it', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"nav\">',\n        '  <span id=\"tab1\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab2\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab3\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '</div>'\n      ].join('')\n\n      const tabEl1 = fixtureEl.querySelector('#tab1')\n      const tabEl2 = fixtureEl.querySelector('#tab2')\n      const tabEl3 = fixtureEl.querySelector('#tab3')\n      const tab1 = new Tab(tabEl1)\n      const tab2 = new Tab(tabEl2)\n      const tab3 = new Tab(tabEl3)\n      const spyShow1 = spyOn(tab1, 'show').and.callThrough()\n      const spyShow2 = spyOn(tab2, 'show').and.callThrough()\n      const spyShow3 = spyOn(tab3, 'show').and.callThrough()\n      const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()\n      const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()\n      const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()\n\n      const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()\n      const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n      let keydown = createEvent('keydown')\n      keydown.key = 'ArrowRight'\n\n      tabEl1.dispatchEvent(keydown)\n      expect(spyShow2).toHaveBeenCalled()\n      expect(spyFocus2).toHaveBeenCalled()\n\n      keydown = createEvent('keydown')\n      keydown.key = 'ArrowDown'\n\n      tabEl2.dispatchEvent(keydown)\n      expect(spyShow3).toHaveBeenCalled()\n      expect(spyFocus3).toHaveBeenCalled()\n\n      tabEl3.dispatchEvent(keydown)\n      expect(spyShow1).toHaveBeenCalled()\n      expect(spyFocus1).toHaveBeenCalled()\n\n      expect(spyStop).toHaveBeenCalledTimes(3)\n      expect(spyPrevent).toHaveBeenCalledTimes(3)\n    })\n\n    it('if keydown event is left arrow, handle it', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"nav\">',\n        '  <span id=\"tab1\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab2\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '</div>'\n      ].join('')\n\n      const tabEl1 = fixtureEl.querySelector('#tab1')\n      const tabEl2 = fixtureEl.querySelector('#tab2')\n      const tab1 = new Tab(tabEl1)\n      const tab2 = new Tab(tabEl2)\n      const spyShow1 = spyOn(tab1, 'show').and.callThrough()\n      const spyShow2 = spyOn(tab2, 'show').and.callThrough()\n      const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()\n      const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()\n\n      const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()\n      const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n      let keydown = createEvent('keydown')\n      keydown.key = 'ArrowLeft'\n\n      tabEl2.dispatchEvent(keydown)\n      expect(spyShow1).toHaveBeenCalled()\n      expect(spyFocus1).toHaveBeenCalled()\n\n      keydown = createEvent('keydown')\n      keydown.key = 'ArrowUp'\n\n      tabEl1.dispatchEvent(keydown)\n      expect(spyShow2).toHaveBeenCalled()\n      expect(spyFocus2).toHaveBeenCalled()\n\n      expect(spyStop).toHaveBeenCalledTimes(2)\n      expect(spyPrevent).toHaveBeenCalledTimes(2)\n    })\n\n    it('if keydown event is right arrow and next element is disabled', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"nav\">',\n        '  <span id=\"tab1\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab2\" class=\"nav-link\" data-bs-toggle=\"tab\" disabled></span>',\n        '  <span id=\"tab3\" class=\"nav-link disabled\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab4\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '</div>'\n      ].join('')\n\n      const tabEl = fixtureEl.querySelector('#tab1')\n      const tabEl2 = fixtureEl.querySelector('#tab2')\n      const tabEl3 = fixtureEl.querySelector('#tab3')\n      const tabEl4 = fixtureEl.querySelector('#tab4')\n      const tab = new Tab(tabEl)\n      const tab2 = new Tab(tabEl2)\n      const tab3 = new Tab(tabEl3)\n      const tab4 = new Tab(tabEl4)\n      const spy1 = spyOn(tab, 'show').and.callThrough()\n      const spy2 = spyOn(tab2, 'show').and.callThrough()\n      const spy3 = spyOn(tab3, 'show').and.callThrough()\n      const spy4 = spyOn(tab4, 'show').and.callThrough()\n      const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough()\n      const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()\n      const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()\n      const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()\n\n      const keydown = createEvent('keydown')\n      keydown.key = 'ArrowRight'\n\n      tabEl.dispatchEvent(keydown)\n      expect(spy1).not.toHaveBeenCalled()\n      expect(spy2).not.toHaveBeenCalled()\n      expect(spy3).not.toHaveBeenCalled()\n      expect(spy4).toHaveBeenCalledTimes(1)\n      expect(spyFocus1).not.toHaveBeenCalled()\n      expect(spyFocus2).not.toHaveBeenCalled()\n      expect(spyFocus3).not.toHaveBeenCalled()\n      expect(spyFocus4).toHaveBeenCalledTimes(1)\n    })\n\n    it('if keydown event is left arrow and next element is disabled', () => {\n      fixtureEl.innerHTML = [\n        '<div class=\"nav\">',\n        '  <span id=\"tab1\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab2\" class=\"nav-link\" data-bs-toggle=\"tab\" disabled></span>',\n        '  <span id=\"tab3\" class=\"nav-link disabled\" data-bs-toggle=\"tab\"></span>',\n        '  <span id=\"tab4\" class=\"nav-link\" data-bs-toggle=\"tab\"></span>',\n        '</div>'\n      ].join('')\n\n      const tabEl = fixtureEl.querySelector('#tab1')\n      const tabEl2 = fixtureEl.querySelector('#tab2')\n      const tabEl3 = fixtureEl.querySelector('#tab3')\n      const tabEl4 = fixtureEl.querySelector('#tab4')\n      const tab = new Tab(tabEl)\n      const tab2 = new Tab(tabEl2)\n      const tab3 = new Tab(tabEl3)\n      const tab4 = new Tab(tabEl4)\n      const spy1 = spyOn(tab, 'show').and.callThrough()\n      const spy2 = spyOn(tab2, 'show').and.callThrough()\n      const spy3 = spyOn(tab3, 'show').and.callThrough()\n      const spy4 = spyOn(tab4, 'show').and.callThrough()\n      const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough()\n      const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()\n      const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()\n      const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()\n\n      const keydown = createEvent('keydown')\n      keydown.key = 'ArrowLeft'\n\n      tabEl4.dispatchEvent(keydown)\n      expect(spy4).not.toHaveBeenCalled()\n      expect(spy3).not.toHaveBeenCalled()\n      expect(spy2).not.toHaveBeenCalled()\n      expect(spy1).toHaveBeenCalledTimes(1)\n      expect(spyFocus4).not.toHaveBeenCalled()\n      expect(spyFocus3).not.toHaveBeenCalled()\n      expect(spyFocus2).not.toHaveBeenCalled()\n      expect(spyFocus1).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a tab', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const div = fixtureEl.querySelector('.nav > div')\n\n      jQueryMock.fn.tab = Tab.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.tab.call(jQueryMock)\n\n      expect(Tab.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create a tab', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const div = fixtureEl.querySelector('.nav > div')\n      const tab = new Tab(div)\n\n      jQueryMock.fn.tab = Tab.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.tab.call(jQueryMock)\n\n      expect(Tab.getInstance(div)).toEqual(tab)\n    })\n\n    it('should call a tab method', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const div = fixtureEl.querySelector('.nav > div')\n      const tab = new Tab(div)\n\n      const spy = spyOn(tab, 'show')\n\n      jQueryMock.fn.tab = Tab.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.tab.call(jQueryMock, 'show')\n\n      expect(Tab.getInstance(div)).toEqual(tab)\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const div = fixtureEl.querySelector('.nav > div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.tab = Tab.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.tab.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return null if there is no instance', () => {\n      expect(Tab.getInstance(fixtureEl)).toBeNull()\n    })\n\n    it('should return this instance', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const divEl = fixtureEl.querySelector('.nav > div')\n      const tab = new Tab(divEl)\n\n      expect(Tab.getInstance(divEl)).toEqual(tab)\n      expect(Tab.getInstance(divEl)).toBeInstanceOf(Tab)\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return tab instance', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const tab = new Tab(div)\n\n      expect(Tab.getOrCreateInstance(div)).toEqual(tab)\n      expect(Tab.getInstance(div)).toEqual(Tab.getOrCreateInstance(div, {}))\n      expect(Tab.getOrCreateInstance(div)).toBeInstanceOf(Tab)\n    })\n\n    it('should return new instance when there is no tab instance', () => {\n      fixtureEl.innerHTML = '<div class=\"nav\"><div class=\"nav-link\"></div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Tab.getInstance(div)).toBeNull()\n      expect(Tab.getOrCreateInstance(div)).toBeInstanceOf(Tab)\n    })\n  })\n\n  describe('data-api', () => {\n    it('should create dynamically a tab', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" data-bs-target=\"#home\" class=\"nav-link active\" role=\"tab\" aria-selected=\"true\">Home</button></li>',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" id=\"triggerProfile\" data-bs-toggle=\"tab\" data-bs-target=\"#profile\" class=\"nav-link\" role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\"></div>',\n          '  <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')\n\n        secondTabTrigger.addEventListener('shown.bs.tab', () => {\n          expect(secondTabTrigger).toHaveClass('active')\n          expect(fixtureEl.querySelector('#profile')).toHaveClass('active')\n          resolve()\n        })\n\n        secondTabTrigger.click()\n      })\n    })\n\n    it('selected tab should deactivate previous selected link in dropdown', () => {\n      fixtureEl.innerHTML = [\n        '<ul class=\"nav nav-tabs\">',\n        '  <li class=\"nav-item\"><a class=\"nav-link\" href=\"#home\" data-bs-toggle=\"tab\">Home</a></li>',\n        '  <li class=\"nav-item\"><a class=\"nav-link\" href=\"#profile\" data-bs-toggle=\"tab\">Profile</a></li>',\n        '  <li class=\"nav-item dropdown\">',\n        '    <a class=\"nav-link dropdown-toggle active\" data-bs-toggle=\"dropdown\" href=\"#\">Dropdown</a>',\n        '    <div class=\"dropdown-menu\">',\n        '      <a class=\"dropdown-item active\" href=\"#dropdown1\" id=\"dropdown1-tab\" data-bs-toggle=\"tab\">@fat</a>',\n        '      <a class=\"dropdown-item\" href=\"#dropdown2\" id=\"dropdown2-tab\" data-bs-toggle=\"tab\">@mdo</a>',\n        '    </div>',\n        '  </li>',\n        '</ul>'\n      ].join('')\n\n      const firstLiLinkEl = fixtureEl.querySelector('li:first-child a')\n\n      firstLiLinkEl.click()\n      expect(firstLiLinkEl).toHaveClass('active')\n      expect(fixtureEl.querySelector('li:last-child a')).not.toHaveClass('active')\n      expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child')).not.toHaveClass('active')\n    })\n\n    it('selecting a dropdown tab does not activate another', () => {\n      const nav1 = [\n        '<ul class=\"nav nav-tabs\" id=\"nav1\">',\n        '  <li class=\"nav-item active\"><a class=\"nav-link\" href=\"#home\" data-bs-toggle=\"tab\">Home</a></li>',\n        '  <li class=\"nav-item dropdown\">',\n        '    <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\">Dropdown</a>',\n        '    <div class=\"dropdown-menu\">',\n        '      <a class=\"dropdown-item\" href=\"#dropdown1\" id=\"dropdown1-tab\" data-bs-toggle=\"tab\">@fat</a>',\n        '    </div>',\n        '  </li>',\n        '</ul>'\n      ].join('')\n      const nav2 = [\n        '<ul class=\"nav nav-tabs\" id=\"nav2\">',\n        '  <li class=\"nav-item active\"><a class=\"nav-link\" href=\"#home\" data-bs-toggle=\"tab\">Home</a></li>',\n        '  <li class=\"nav-item dropdown\">',\n        '    <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\">Dropdown</a>',\n        '    <div class=\"dropdown-menu\">',\n        '      <a class=\"dropdown-item\" href=\"#dropdown1\" id=\"dropdown1-tab\" data-bs-toggle=\"tab\">@fat</a>',\n        '    </div>',\n        '  </li>',\n        '</ul>'\n      ].join('')\n\n      fixtureEl.innerHTML = nav1 + nav2\n\n      const firstDropItem = fixtureEl.querySelector('#nav1 .dropdown-item')\n\n      firstDropItem.click()\n      expect(firstDropItem).toHaveClass('active')\n      expect(fixtureEl.querySelector('#nav1 .dropdown-toggle')).toHaveClass('active')\n      expect(fixtureEl.querySelector('#nav2 .dropdown-toggle')).not.toHaveClass('active')\n      expect(fixtureEl.querySelector('#nav2 .dropdown-item')).not.toHaveClass('active')\n    })\n\n    it('should support li > .dropdown-item', () => {\n      fixtureEl.innerHTML = [\n        '<ul class=\"nav nav-tabs\">',\n        '  <li class=\"nav-item\"><a class=\"nav-link active\" href=\"#home\" data-bs-toggle=\"tab\">Home</a></li>',\n        '  <li class=\"nav-item\"><a class=\"nav-link\" href=\"#profile\" data-bs-toggle=\"tab\">Profile</a></li>',\n        '  <li class=\"nav-item dropdown\">',\n        '    <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\">Dropdown</a>',\n        '    <ul class=\"dropdown-menu\">',\n        '      <li><a class=\"dropdown-item\" href=\"#dropdown1\" id=\"dropdown1-tab\" data-bs-toggle=\"tab\">@fat</a></li>',\n        '      <li><a class=\"dropdown-item\" href=\"#dropdown2\" id=\"dropdown2-tab\" data-bs-toggle=\"tab\">@mdo</a></li>',\n        '    </ul>',\n        '  </li>',\n        '</ul>'\n      ].join('')\n\n      const dropItems = fixtureEl.querySelectorAll('.dropdown-item')\n\n      dropItems[1].click()\n      expect(dropItems[0]).not.toHaveClass('active')\n      expect(dropItems[1]).toHaveClass('active')\n      expect(fixtureEl.querySelector('.nav-link')).not.toHaveClass('active')\n    })\n\n    it('should handle nested tabs', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<nav class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <button type=\"button\" id=\"tab1\" data-bs-target=\"#x-tab1\" class=\"nav-link\" data-bs-toggle=\"tab\" role=\"tab\" aria-controls=\"x-tab1\">Tab 1</button>',\n          '  <button type=\"button\" data-bs-target=\"#x-tab2\" class=\"nav-link active\" data-bs-toggle=\"tab\" role=\"tab\" aria-controls=\"x-tab2\" aria-selected=\"true\">Tab 2</button>',\n          '  <button type=\"button\" data-bs-target=\"#x-tab3\" class=\"nav-link\" data-bs-toggle=\"tab\" role=\"tab\" aria-controls=\"x-tab3\">Tab 3</button>',\n          '</nav>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane\" id=\"x-tab1\" role=\"tabpanel\">',\n          '    <nav class=\"nav nav-tabs\" role=\"tablist\">',\n          '      <button type=\"button\" data-bs-target=\"#nested-tab1\" class=\"nav-link active\" data-bs-toggle=\"tab\" role=\"tab\" aria-controls=\"x-tab1\" aria-selected=\"true\">Nested Tab 1</button>',\n          '      <button type=\"button\" id=\"tabNested2\" data-bs-target=\"#nested-tab2\" class=\"nav-link\" data-bs-toggle=\"tab\" role=\"tab\" aria-controls=\"x-profile\">Nested Tab2</button>',\n          '    </nav>',\n          '    <div class=\"tab-content\">',\n          '      <div class=\"tab-pane active\" id=\"nested-tab1\" role=\"tabpanel\">Nested Tab1 Content</div>',\n          '      <div class=\"tab-pane\" id=\"nested-tab2\" role=\"tabpanel\">Nested Tab2 Content</div>',\n          '    </div>',\n          '  </div>',\n          '  <div class=\"tab-pane active\" id=\"x-tab2\" role=\"tabpanel\">Tab2 Content</div>',\n          '  <div class=\"tab-pane\" id=\"x-tab3\" role=\"tabpanel\">Tab3 Content</div>',\n          '</div>'\n        ].join('')\n\n        const tab1El = fixtureEl.querySelector('#tab1')\n        const tabNested2El = fixtureEl.querySelector('#tabNested2')\n        const xTab1El = fixtureEl.querySelector('#x-tab1')\n\n        tabNested2El.addEventListener('shown.bs.tab', () => {\n          expect(xTab1El).toHaveClass('active')\n          resolve()\n        })\n\n        tab1El.addEventListener('shown.bs.tab', () => {\n          expect(xTab1El).toHaveClass('active')\n          tabNested2El.click()\n        })\n\n        tab1El.click()\n      })\n    })\n\n    it('should not remove fade class if no active pane is present', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" id=\"tab-home\" data-bs-target=\"#home\" class=\"nav-link\" data-bs-toggle=\"tab\" role=\"tab\">Home</button></li>',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" id=\"tab-profile\" data-bs-target=\"#profile\" class=\"nav-link\" data-bs-toggle=\"tab\" role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane fade\" id=\"home\" role=\"tabpanel\"></div>',\n          '  <div class=\"tab-pane fade\" id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const triggerTabProfileEl = fixtureEl.querySelector('#tab-profile')\n        const triggerTabHomeEl = fixtureEl.querySelector('#tab-home')\n        const tabProfileEl = fixtureEl.querySelector('#profile')\n        const tabHomeEl = fixtureEl.querySelector('#home')\n\n        triggerTabHomeEl.addEventListener('shown.bs.tab', () => {\n          setTimeout(() => {\n            expect(tabProfileEl).toHaveClass('fade')\n            expect(tabProfileEl).not.toHaveClass('show')\n\n            expect(tabHomeEl).toHaveClass('fade')\n            expect(tabHomeEl).toHaveClass('show')\n\n            resolve()\n          }, 10)\n        })\n\n        triggerTabProfileEl.addEventListener('shown.bs.tab', () => {\n          setTimeout(() => {\n            expect(tabProfileEl).toHaveClass('fade')\n            expect(tabProfileEl).toHaveClass('show')\n            triggerTabHomeEl.click()\n          }, 10)\n        })\n\n        triggerTabProfileEl.click()\n      })\n    })\n\n    it('should add `show` class to tab panes if there is no `.fade` class', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <button type=\"button\" class=\"nav-link nav-tab\" data-bs-target=\"#home\" role=\"tab\" data-bs-toggle=\"tab\">Home</button>',\n          '  </li>',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <button type=\"button\" id=\"secondNav\" class=\"nav-link nav-tab\" data-bs-target=\"#profile\" role=\"tab\" data-bs-toggle=\"tab\">Profile</button>',\n          '  </li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div role=\"tabpanel\" class=\"tab-pane\" id=\"home\">test 1</div>',\n          '  <div role=\"tabpanel\" class=\"tab-pane\" id=\"profile\">test 2</div>',\n          '</div>'\n        ].join('')\n\n        const secondNavEl = fixtureEl.querySelector('#secondNav')\n\n        secondNavEl.addEventListener('shown.bs.tab', () => {\n          expect(fixtureEl.querySelectorAll('.tab-content .show')).toHaveSize(1)\n          resolve()\n        })\n\n        secondNavEl.click()\n      })\n    })\n\n    it('should add show class to tab panes if there is a `.fade` class', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <button type=\"button\" class=\"nav-link nav-tab\" data-bs-target=\"#home\" role=\"tab\" data-bs-toggle=\"tab\">Home</button>',\n          '  </li>',\n          '  <li class=\"nav-item\" role=\"presentation\">',\n          '    <button type=\"button\" id=\"secondNav\" class=\"nav-link nav-tab\" data-bs-target=\"#profile\" role=\"tab\" data-bs-toggle=\"tab\">Profile</button>',\n          '  </li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div role=\"tabpanel\" class=\"tab-pane fade\" id=\"home\">test 1</div>',\n          '  <div role=\"tabpanel\" class=\"tab-pane fade\" id=\"profile\">test 2</div>',\n          '</div>'\n        ].join('')\n\n        const secondNavEl = fixtureEl.querySelector('#secondNav')\n\n        secondNavEl.addEventListener('shown.bs.tab', () => {\n          setTimeout(() => {\n            expect(fixtureEl.querySelectorAll('.show')).toHaveSize(1)\n            resolve()\n          }, 10)\n        })\n\n        secondNavEl.click()\n      })\n    })\n\n    it('should prevent default when the trigger is <a> or <area>', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav\" role=\"tablist\">',\n          '  <li><a type=\"button\" href=\"#test\"  class=\"active\" role=\"tab\" data-bs-toggle=\"tab\">Home</a></li>',\n          '  <li><a type=\"button\" href=\"#test2\" role=\"tab\" data-bs-toggle=\"tab\">Home</a></li>',\n          '</ul>'\n        ].join('')\n\n        const tabEl = fixtureEl.querySelector('[href=\"#test2\"]')\n        const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n        tabEl.addEventListener('shown.bs.tab', () => {\n          expect(tabEl).toHaveClass('active')\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        tabEl.click()\n      })\n    })\n\n    it('should not fire shown when tab has disabled attribute', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" data-bs-target=\"#home\" class=\"nav-link active\" role=\"tab\" aria-selected=\"true\">Home</button></li>',\n          '  <li class=\"nav-item\" role=\"presentation\"><button type=\"button\" data-bs-target=\"#profile\" class=\"nav-link\" disabled role=\"tab\">Profile</button></li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\"></div>',\n          '  <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const triggerDisabled = fixtureEl.querySelector('button[disabled]')\n        triggerDisabled.addEventListener('shown.bs.tab', () => {\n          reject(new Error('should not trigger shown event'))\n        })\n\n        triggerDisabled.click()\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 30)\n      })\n    })\n\n    it('should not fire shown when tab has disabled class', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<ul class=\"nav nav-tabs\" role=\"tablist\">',\n          '  <li class=\"nav-item\" role=\"presentation\"><a href=\"#home\" class=\"nav-link active\" role=\"tab\" aria-selected=\"true\">Home</a></li>',\n          '  <li class=\"nav-item\" role=\"presentation\"><a href=\"#profile\" class=\"nav-link disabled\" role=\"tab\">Profile</a></li>',\n          '</ul>',\n          '<div class=\"tab-content\">',\n          '  <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\"></div>',\n          '  <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\"></div>',\n          '</div>'\n        ].join('')\n\n        const triggerDisabled = fixtureEl.querySelector('a.disabled')\n\n        triggerDisabled.addEventListener('shown.bs.tab', () => {\n          reject(new Error('should not trigger shown event'))\n        })\n\n        triggerDisabled.click()\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 30)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/toast.spec.js",
    "content": "import Toast from '../../src/toast'\nimport { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Toast', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Toast.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Toast.DATA_KEY).toEqual('bs.toast')\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = '<div class=\"toast\"></div>'\n\n      const toastEl = fixtureEl.querySelector('.toast')\n      const toastBySelector = new Toast('.toast')\n      const toastByElement = new Toast(toastEl)\n\n      expect(toastBySelector._element).toEqual(toastEl)\n      expect(toastByElement._element).toEqual(toastEl)\n    })\n\n    it('should allow to config in js', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('div')\n        const toast = new Toast(toastEl, {\n          delay: 1\n        })\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          expect(toastEl).toHaveClass('show')\n          resolve()\n        })\n\n        toast.show()\n      })\n    })\n\n    it('should close toast when close element with data-bs-dismiss attribute is set', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"1\" data-bs-autohide=\"false\" data-bs-animation=\"false\">',\n          '  <button type=\"button\" class=\"ms-2 mb-1 btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('div')\n        const toast = new Toast(toastEl)\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          expect(toastEl).toHaveClass('show')\n\n          const button = toastEl.querySelector('.btn-close')\n\n          button.click()\n        })\n\n        toastEl.addEventListener('hidden.bs.toast', () => {\n          expect(toastEl).not.toHaveClass('show')\n          resolve()\n        })\n\n        toast.show()\n      })\n    })\n  })\n\n  describe('Default', () => {\n    it('should expose default setting to allow to override them', () => {\n      const defaultDelay = 1000\n\n      Toast.Default.delay = defaultDelay\n\n      fixtureEl.innerHTML = [\n        '<div class=\"toast\" data-bs-autohide=\"false\" data-bs-animation=\"false\">',\n        '  <button type=\"button\" class=\"ms-2 mb-1 btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>',\n        '</div>'\n      ].join('')\n\n      const toastEl = fixtureEl.querySelector('div')\n      const toast = new Toast(toastEl)\n\n      expect(toast._config.delay).toEqual(defaultDelay)\n    })\n  })\n\n  describe('DefaultType', () => {\n    it('should expose default setting types for read', () => {\n      expect(Toast.DefaultType).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('show', () => {\n    it('should auto hide', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"1\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        toastEl.addEventListener('hidden.bs.toast', () => {\n          expect(toastEl).not.toHaveClass('show')\n          resolve()\n        })\n\n        toast.show()\n      })\n    })\n\n    it('should not add fade class', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"1\" data-bs-animation=\"false\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          expect(toastEl).not.toHaveClass('fade')\n          resolve()\n        })\n\n        toast.show()\n      })\n    })\n\n    it('should not trigger shown if show is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"1\" data-bs-animation=\"false\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        const assertDone = () => {\n          setTimeout(() => {\n            expect(toastEl).not.toHaveClass('show')\n            resolve()\n          }, 20)\n        }\n\n        toastEl.addEventListener('show.bs.toast', event => {\n          event.preventDefault()\n          assertDone()\n        })\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          reject(new Error('shown event should not be triggered if show is prevented'))\n        })\n\n        toast.show()\n      })\n    })\n\n    it('should clear timeout if toast is shown again before it is hidden', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        setTimeout(() => {\n          toast._config.autohide = false\n          toastEl.addEventListener('shown.bs.toast', () => {\n            expect(spy).toHaveBeenCalled()\n            expect(toast._timeout).toBeNull()\n            resolve()\n          })\n          toast.show()\n        }, toast._config.delay / 2)\n\n        const spy = spyOn(toast, '_clearTimeout').and.callThrough()\n\n        toast.show()\n      })\n    })\n\n    it('should clear timeout if toast is interacted with mouse', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n        const spy = spyOn(toast, '_clearTimeout').and.callThrough()\n\n        setTimeout(() => {\n          spy.calls.reset()\n\n          toastEl.addEventListener('mouseover', () => {\n            expect(toast._clearTimeout).toHaveBeenCalledTimes(1)\n            expect(toast._timeout).toBeNull()\n            resolve()\n          })\n\n          const mouseOverEvent = createEvent('mouseover')\n          toastEl.dispatchEvent(mouseOverEvent)\n        }, toast._config.delay / 2)\n\n        toast.show()\n      })\n    })\n\n    it('should clear timeout if toast is interacted with keyboard', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"outside-focusable\">outside focusable</button>',\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '    <button>with a button</button>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n        const spy = spyOn(toast, '_clearTimeout').and.callThrough()\n\n        setTimeout(() => {\n          spy.calls.reset()\n\n          toastEl.addEventListener('focusin', () => {\n            expect(toast._clearTimeout).toHaveBeenCalledTimes(1)\n            expect(toast._timeout).toBeNull()\n            resolve()\n          })\n\n          const insideFocusable = toastEl.querySelector('button')\n          insideFocusable.focus()\n        }, toast._config.delay / 2)\n\n        toast.show()\n      })\n    })\n\n    it('should still auto hide after being interacted with mouse and keyboard', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"outside-focusable\">outside focusable</button>',\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '    <button>with a button</button>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        setTimeout(() => {\n          toastEl.addEventListener('mouseover', () => {\n            const insideFocusable = toastEl.querySelector('button')\n            insideFocusable.focus()\n          })\n\n          toastEl.addEventListener('focusin', () => {\n            const mouseOutEvent = createEvent('mouseout')\n            toastEl.dispatchEvent(mouseOutEvent)\n          })\n\n          toastEl.addEventListener('mouseout', () => {\n            const outsideFocusable = document.getElementById('outside-focusable')\n            outsideFocusable.focus()\n          })\n\n          toastEl.addEventListener('focusout', () => {\n            expect(toast._timeout).not.toBeNull()\n            resolve()\n          })\n\n          const mouseOverEvent = createEvent('mouseover')\n          toastEl.dispatchEvent(mouseOverEvent)\n        }, toast._config.delay / 2)\n\n        toast.show()\n      })\n    })\n\n    it('should not auto hide if focus leaves but mouse pointer remains inside', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"outside-focusable\">outside focusable</button>',\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '    <button>with a button</button>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        setTimeout(() => {\n          toastEl.addEventListener('mouseover', () => {\n            const insideFocusable = toastEl.querySelector('button')\n            insideFocusable.focus()\n          })\n\n          toastEl.addEventListener('focusin', () => {\n            const outsideFocusable = document.getElementById('outside-focusable')\n            outsideFocusable.focus()\n          })\n\n          toastEl.addEventListener('focusout', () => {\n            expect(toast._timeout).toBeNull()\n            resolve()\n          })\n\n          const mouseOverEvent = createEvent('mouseover')\n          toastEl.dispatchEvent(mouseOverEvent)\n        }, toast._config.delay / 2)\n\n        toast.show()\n      })\n    })\n\n    it('should not auto hide if mouse pointer leaves but focus remains inside', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<button id=\"outside-focusable\">outside focusable</button>',\n          '<div class=\"toast\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '    <button>with a button</button>',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        setTimeout(() => {\n          toastEl.addEventListener('mouseover', () => {\n            const insideFocusable = toastEl.querySelector('button')\n            insideFocusable.focus()\n          })\n\n          toastEl.addEventListener('focusin', () => {\n            const mouseOutEvent = createEvent('mouseout')\n            toastEl.dispatchEvent(mouseOutEvent)\n          })\n\n          toastEl.addEventListener('mouseout', () => {\n            expect(toast._timeout).toBeNull()\n            resolve()\n          })\n\n          const mouseOverEvent = createEvent('mouseover')\n          toastEl.dispatchEvent(mouseOverEvent)\n        }, toast._config.delay / 2)\n\n        toast.show()\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should allow to hide toast manually', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"1\" data-bs-autohide=\"false\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          toast.hide()\n        })\n\n        toastEl.addEventListener('hidden.bs.toast', () => {\n          expect(toastEl).not.toHaveClass('show')\n          resolve()\n        })\n\n        toast.show()\n      })\n    })\n\n    it('should do nothing when we call hide on a non shown toast', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const toastEl = fixtureEl.querySelector('div')\n      const toast = new Toast(toastEl)\n\n      const spy = spyOn(toastEl.classList, 'contains')\n\n      toast.hide()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should not trigger hidden if hide is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"1\" data-bs-animation=\"false\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('.toast')\n        const toast = new Toast(toastEl)\n\n        const assertDone = () => {\n          setTimeout(() => {\n            expect(toastEl).toHaveClass('show')\n            resolve()\n          }, 20)\n        }\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          toast.hide()\n        })\n\n        toastEl.addEventListener('hide.bs.toast', event => {\n          event.preventDefault()\n          assertDone()\n        })\n\n        toastEl.addEventListener('hidden.bs.toast', () => {\n          reject(new Error('hidden event should not be triggered if hide is prevented'))\n        })\n\n        toast.show()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should allow to destroy toast', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const toastEl = fixtureEl.querySelector('div')\n\n      const toast = new Toast(toastEl)\n\n      expect(Toast.getInstance(toastEl)).not.toBeNull()\n\n      toast.dispose()\n\n      expect(Toast.getInstance(toastEl)).toBeNull()\n    })\n\n    it('should allow to destroy toast and hide it before that', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"toast\" data-bs-delay=\"0\" data-bs-autohide=\"false\">',\n          '  <div class=\"toast-body\">',\n          '    a simple toast',\n          '  </div>',\n          '</div>'\n        ].join('')\n\n        const toastEl = fixtureEl.querySelector('div')\n        const toast = new Toast(toastEl)\n        const expected = () => {\n          expect(toastEl).toHaveClass('show')\n          expect(Toast.getInstance(toastEl)).not.toBeNull()\n\n          toast.dispose()\n\n          expect(Toast.getInstance(toastEl)).toBeNull()\n          expect(toastEl).not.toHaveClass('show')\n\n          resolve()\n        }\n\n        toastEl.addEventListener('shown.bs.toast', () => {\n          setTimeout(expected, 1)\n        })\n\n        toast.show()\n      })\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a toast', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.toast = Toast.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.toast.call(jQueryMock)\n\n      expect(Toast.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create a toast', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const toast = new Toast(div)\n\n      jQueryMock.fn.toast = Toast.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.toast.call(jQueryMock)\n\n      expect(Toast.getInstance(div)).toEqual(toast)\n    })\n\n    it('should call a toast method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const toast = new Toast(div)\n\n      const spy = spyOn(toast, 'show')\n\n      jQueryMock.fn.toast = Toast.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.toast.call(jQueryMock, 'show')\n\n      expect(Toast.getInstance(div)).toEqual(toast)\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.toast = Toast.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.toast.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return a toast instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const toast = new Toast(div)\n\n      expect(Toast.getInstance(div)).toEqual(toast)\n      expect(Toast.getInstance(div)).toBeInstanceOf(Toast)\n    })\n\n    it('should return null when there is no toast instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Toast.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return toast instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const toast = new Toast(div)\n\n      expect(Toast.getOrCreateInstance(div)).toEqual(toast)\n      expect(Toast.getInstance(div)).toEqual(Toast.getOrCreateInstance(div, {}))\n      expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast)\n    })\n\n    it('should return new instance when there is no toast instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Toast.getInstance(div)).toBeNull()\n      expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast)\n    })\n\n    it('should return new instance when there is no toast instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Toast.getInstance(div)).toBeNull()\n      const toast = Toast.getOrCreateInstance(div, {\n        delay: 1\n      })\n      expect(toast).toBeInstanceOf(Toast)\n\n      expect(toast._config.delay).toEqual(1)\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const toast = new Toast(div, {\n        delay: 1\n      })\n      expect(Toast.getInstance(div)).toEqual(toast)\n\n      const toast2 = Toast.getOrCreateInstance(div, {\n        delay: 2\n      })\n      expect(toast).toBeInstanceOf(Toast)\n      expect(toast2).toEqual(toast)\n\n      expect(toast2._config.delay).toEqual(1)\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/tooltip.spec.js",
    "content": "import Tooltip from '../../src/tooltip'\nimport EventHandler from '../../src/dom/event-handler'\nimport { noop } from '../../src/util/index'\nimport { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'\n\ndescribe('Tooltip', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n\n    for (const tooltipEl of document.querySelectorAll('.tooltip')) {\n      tooltipEl.remove()\n    }\n  })\n\n  describe('VERSION', () => {\n    it('should return plugin version', () => {\n      expect(Tooltip.VERSION).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(Tooltip.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('NAME', () => {\n    it('should return plugin name', () => {\n      expect(Tooltip.NAME).toEqual(jasmine.any(String))\n    })\n  })\n\n  describe('DATA_KEY', () => {\n    it('should return plugin data key', () => {\n      expect(Tooltip.DATA_KEY).toEqual('bs.tooltip')\n    })\n  })\n\n  describe('EVENT_KEY', () => {\n    it('should return plugin event key', () => {\n      expect(Tooltip.EVENT_KEY).toEqual('.bs.tooltip')\n    })\n  })\n\n  describe('DefaultType', () => {\n    it('should return plugin default type', () => {\n      expect(Tooltip.DefaultType).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('constructor', () => {\n    it('should take care of element either passed as a CSS selector or DOM element', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" id=\"tooltipEl\" rel=\"tooltip\" title=\"Nice and short title\">'\n\n      const tooltipEl = fixtureEl.querySelector('#tooltipEl')\n      const tooltipBySelector = new Tooltip('#tooltipEl')\n      const tooltipByElement = new Tooltip(tooltipEl)\n\n      expect(tooltipBySelector._element).toEqual(tooltipEl)\n      expect(tooltipByElement._element).toEqual(tooltipEl)\n    })\n\n    it('should not take care of disallowed data attributes', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" data-bs-sanitize=\"false\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._config.sanitize).toBeTrue()\n    })\n\n    it('should convert title and content to string if numbers', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl, {\n        title: 1,\n        content: 7\n      })\n\n      expect(tooltip._config.title).toEqual('1')\n      expect(tooltip._config.content).toEqual('7')\n    })\n\n    it('should enable selector delegation', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const containerEl = fixtureEl.querySelector('div')\n        const tooltipContainer = new Tooltip(containerEl, {\n          selector: 'a[rel=\"tooltip\"]',\n          trigger: 'click'\n        })\n\n        containerEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipInContainerEl = containerEl.querySelector('a')\n\n        tooltipInContainerEl.addEventListener('shown.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).not.toBeNull()\n          tooltipContainer.dispose()\n          resolve()\n        })\n\n        tooltipInContainerEl.click()\n      })\n    })\n\n    it('should create offset modifier when offset is passed as a function', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Offset from function\">'\n\n        const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          offset: getOffset,\n          popperConfig: {\n            onFirstUpdate(state) {\n              expect(getOffset).toHaveBeenCalledWith({\n                popper: state.rects.popper,\n                reference: state.rects.reference,\n                placement: state.placement\n              }, tooltipEl)\n              resolve()\n            }\n          }\n        })\n\n        const offset = tooltip._getOffset()\n\n        expect(offset).toEqual(jasmine.any(Function))\n\n        tooltip.show()\n      })\n    })\n\n    it('should create offset modifier when offset option is passed in data attribute', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" data-bs-offset=\"10,20\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._getOffset()).toEqual([10, 20])\n    })\n\n    it('should allow to pass config to Popper with `popperConfig`', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl, {\n        popperConfig: {\n          placement: 'left'\n        }\n      })\n\n      const popperConfig = tooltip._getPopperConfig('top')\n\n      expect(popperConfig.placement).toEqual('left')\n    })\n\n    it('should allow to pass config to Popper with `popperConfig` as a function', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })\n      const tooltip = new Tooltip(tooltipEl, {\n        popperConfig: getPopperConfig\n      })\n\n      const popperConfig = tooltip._getPopperConfig('top')\n\n      expect(getPopperConfig).toHaveBeenCalled()\n      expect(popperConfig.placement).toEqual('left')\n    })\n\n    it('should use original title, if not \"data-bs-title\" is given', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._getTitle()).toEqual('Another tooltip')\n    })\n  })\n\n  describe('enable', () => {\n    it('should enable a tooltip', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltip.enable()\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).not.toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n  })\n\n  describe('disable', () => {\n    it('should disable tooltip', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltip.disable()\n\n        tooltipEl.addEventListener('show.bs.tooltip', () => {\n          reject(new Error('should not show a disabled tooltip'))\n        })\n\n        tooltip.show()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n  })\n\n  describe('toggleEnabled', () => {\n    it('should toggle enabled', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._isEnabled).toBeTrue()\n\n      tooltip.toggleEnabled()\n\n      expect(tooltip._isEnabled).toBeFalse()\n    })\n  })\n\n  describe('toggle', () => {\n    it('should do nothing if disabled', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltip.disable()\n\n        tooltipEl.addEventListener('show.bs.tooltip', () => {\n          reject(new Error('should not show a disabled tooltip'))\n        })\n\n        tooltip.toggle()\n\n        setTimeout(() => {\n          expect().nothing()\n          resolve()\n        }, 10)\n      })\n    })\n\n    it('should show a tooltip', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).not.toBeNull()\n          resolve()\n        })\n\n        tooltip.toggle()\n      })\n    })\n\n    it('should call toggle and show the tooltip when trigger is \"click\"', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          trigger: 'click'\n        })\n\n        const spy = spyOn(tooltip, 'toggle').and.callThrough()\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        tooltipEl.click()\n      })\n    })\n\n    it('should hide a tooltip', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          tooltip.toggle()\n        })\n\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).toBeNull()\n          resolve()\n        })\n\n        tooltip.toggle()\n      })\n    })\n\n    it('should call toggle and hide the tooltip when trigger is \"click\"', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          trigger: 'click'\n        })\n\n        const spy = spyOn(tooltip, 'toggle').and.callThrough()\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          tooltipEl.click()\n        })\n\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        tooltipEl.click()\n      })\n    })\n  })\n\n  describe('dispose', () => {\n    it('should destroy a tooltip', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough()\n      const removeEventSpy = spyOn(tooltipEl, 'removeEventListener').and.callThrough()\n\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(Tooltip.getInstance(tooltipEl)).toEqual(tooltip)\n\n      const expectedArgs = [\n        ['mouseover', jasmine.any(Function), jasmine.any(Boolean)],\n        ['mouseout', jasmine.any(Function), jasmine.any(Boolean)],\n        ['focusin', jasmine.any(Function), jasmine.any(Boolean)],\n        ['focusout', jasmine.any(Function), jasmine.any(Boolean)]\n      ]\n\n      expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)\n\n      tooltip.dispose()\n\n      expect(Tooltip.getInstance(tooltipEl)).toBeNull()\n      expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)\n    })\n\n    it('should destroy a tooltip after it is shown and hidden', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          tooltip.hide()\n        })\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          tooltip.dispose()\n          expect(tooltip.tip).toBeNull()\n          expect(Tooltip.getInstance(tooltipEl)).toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should destroy a tooltip and remove it from the dom', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).not.toBeNull()\n\n          tooltip.dispose()\n\n          expect(document.querySelector('.tooltip')).toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should destroy a tooltip and reset it\\'s initial title', () => {\n      fixtureEl.innerHTML = [\n        '<span id=\"tooltipWithTitle\" rel=\"tooltip\" title=\"tooltipTitle\"></span>',\n        '<span id=\"tooltipWithoutTitle\" rel=\"tooltip\" data-bs-title=\"tooltipTitle\"></span>'\n      ].join('')\n\n      const tooltipWithTitleEl = fixtureEl.querySelector('#tooltipWithTitle')\n      const tooltip = new Tooltip('#tooltipWithTitle')\n      expect(tooltipWithTitleEl.getAttribute('title')).toBeNull()\n      tooltip.dispose()\n      expect(tooltipWithTitleEl.getAttribute('title')).toBe('tooltipTitle')\n\n      const tooltipWithoutTitleEl = fixtureEl.querySelector('#tooltipWithoutTitle')\n      const tooltip2 = new Tooltip('#tooltipWithTitle')\n      expect(tooltipWithoutTitleEl.getAttribute('title')).toBeNull()\n      tooltip2.dispose()\n      expect(tooltipWithoutTitleEl.getAttribute('title')).toBeNull()\n    })\n  })\n\n  describe('show', () => {\n    it('should show a tooltip', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tooltipShown = document.querySelector('.tooltip')\n\n          expect(tooltipShown).not.toBeNull()\n          expect(tooltipEl.getAttribute('aria-describedby')).toEqual(tooltipShown.getAttribute('id'))\n          expect(tooltipShown.getAttribute('id')).toContain('tooltip')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip when hovering a child element', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">',\n          '  <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"50\" height=\"50\" viewBox=\"0 0 100 100\">',\n          '    <rect width=\"100%\" fill=\"#563d7c\"/>',\n          '    <circle cx=\"50\" cy=\"50\" r=\"30\" fill=\"#fff\"/>',\n          '  </svg>',\n          '</a>'\n        ].join('')\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        const spy = spyOn(tooltip, 'show')\n\n        tooltipEl.querySelector('rect').dispatchEvent(createEvent('mouseover', { bubbles: true }))\n\n        setTimeout(() => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        }, 0)\n      })\n    })\n\n    it('should show a tooltip on mobile', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n        document.documentElement.ontouchstart = noop\n\n        const spy = spyOn(EventHandler, 'on').and.callThrough()\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).not.toBeNull()\n          expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)\n          document.documentElement.ontouchstart = undefined\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip relative to placement option', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          placement: 'bottom'\n        })\n\n        tooltipEl.addEventListener('inserted.bs.tooltip', () => {\n          expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')\n          expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should not error when trying to show a tooltip that has been removed from the dom', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        const firstCallback = () => {\n          tooltipEl.removeEventListener('shown.bs.tooltip', firstCallback)\n          let tooltipShown = document.querySelector('.tooltip')\n\n          tooltipShown.remove()\n\n          tooltipEl.addEventListener('shown.bs.tooltip', () => {\n            tooltipShown = document.querySelector('.tooltip')\n\n            expect(tooltipShown).not.toBeNull()\n            resolve()\n          })\n\n          tooltip.show()\n        }\n\n        tooltipEl.addEventListener('shown.bs.tooltip', firstCallback)\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with a dom element container', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          container: fixtureEl\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with a jquery element container', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          container: {\n            0: fixtureEl,\n            jquery: 'jQuery'\n          }\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with a selector in container', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          container: '#fixture'\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with placement as a function', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const spy = jasmine.createSpy('placement').and.returnValue('top')\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          placement: spy\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).not.toBeNull()\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip without the animation', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          animation: false\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tip = document.querySelector('.tooltip')\n\n          expect(tip).not.toBeNull()\n          expect(tip).not.toHaveClass('fade')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should throw an error the element is not visible', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" style=\"display: none\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      try {\n        tooltip.show()\n      } catch (error) {\n        expect(error.message).toEqual('Please use show on visible elements')\n      }\n    })\n\n    it('should not show a tooltip if show.bs.tooltip is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        const expectedDone = () => {\n          setTimeout(() => {\n            expect(document.querySelector('.tooltip')).toBeNull()\n            resolve()\n          }, 10)\n        }\n\n        tooltipEl.addEventListener('show.bs.tooltip', ev => {\n          ev.preventDefault()\n          expectedDone()\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          reject(new Error('Tooltip should not be shown'))\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show tooltip if leave event hasn\\'t occurred before delay expires', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          delay: 150\n        })\n\n        const spy = spyOn(tooltip, 'show')\n\n        setTimeout(() => {\n          expect(spy).not.toHaveBeenCalled()\n        }, 100)\n\n        setTimeout(() => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        }, 200)\n\n        tooltipEl.dispatchEvent(createEvent('mouseover'))\n      })\n    })\n\n    it('should not show tooltip if leave event occurs before delay expires', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          delay: 150\n        })\n\n        const spy = spyOn(tooltip, 'show')\n\n        setTimeout(() => {\n          expect(spy).not.toHaveBeenCalled()\n          tooltipEl.dispatchEvent(createEvent('mouseover'))\n        }, 100)\n\n        setTimeout(() => {\n          expect(spy).toHaveBeenCalled()\n          expect(document.querySelectorAll('.tooltip')).toHaveSize(0)\n          resolve()\n        }, 200)\n\n        tooltipEl.dispatchEvent(createEvent('mouseover'))\n      })\n    })\n\n    it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\" data-bs-delay=\\'{\"show\":0,\"hide\":150}\\'>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        expect(tooltip._config.delay).toEqual({ show: 0, hide: 150 })\n\n        setTimeout(() => {\n          expect(tooltip._getTipElement()).toHaveClass('show')\n          tooltipEl.dispatchEvent(createEvent('mouseout'))\n\n          setTimeout(() => {\n            expect(tooltip._getTipElement()).toHaveClass('show')\n            tooltipEl.dispatchEvent(createEvent('mouseover'))\n          }, 100)\n\n          setTimeout(() => {\n            expect(tooltip._getTipElement()).toHaveClass('show')\n            expect(document.querySelectorAll('.tooltip')).toHaveSize(1)\n            resolve()\n          }, 200)\n        }, 10)\n\n        tooltipEl.dispatchEvent(createEvent('mouseover'))\n      })\n    })\n\n    it('should not hide tooltip if leave event occurs and interaction remains inside trigger', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">',\n          '<b>Trigger</b>',\n          'the tooltip',\n          '</a>'\n        ].join('')\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n        const triggerChild = tooltipEl.querySelector('b')\n\n        const spy = spyOn(tooltip, 'hide').and.callThrough()\n\n        tooltipEl.addEventListener('mouseover', () => {\n          const moveMouseToChildEvent = createEvent('mouseout')\n          Object.defineProperty(moveMouseToChildEvent, 'relatedTarget', {\n            value: triggerChild\n          })\n\n          tooltipEl.dispatchEvent(moveMouseToChildEvent)\n        })\n\n        tooltipEl.addEventListener('mouseout', () => {\n          expect(spy).not.toHaveBeenCalled()\n          resolve()\n        })\n\n        tooltipEl.dispatchEvent(createEvent('mouseover'))\n      })\n    })\n\n    it('should properly maintain tooltip state if leave event occurs and enter event occurs during hide transition', () => {\n      return new Promise(resolve => {\n        // Style this tooltip to give it plenty of room for popper to do what it wants\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\" data-bs-placement=\"top\" style=\"position:fixed;left:50%;top:50%;\">Trigger</a>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        spyOn(window, 'getComputedStyle').and.returnValue({\n          transitionDuration: '0.15s',\n          transitionDelay: '0s'\n        })\n\n        setTimeout(() => {\n          expect(tooltip._popper).not.toBeNull()\n          expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')\n          tooltipEl.dispatchEvent(createEvent('mouseout'))\n\n          setTimeout(() => {\n            expect(tooltip._getTipElement()).not.toHaveClass('show')\n            tooltipEl.dispatchEvent(createEvent('mouseover'))\n          }, 100)\n\n          setTimeout(() => {\n            expect(tooltip._popper).not.toBeNull()\n            expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')\n            resolve()\n          }, 200)\n        }, 10)\n\n        tooltipEl.dispatchEvent(createEvent('mouseover'))\n      })\n    })\n\n    it('should only trigger inserted event if a new tooltip element was created', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        spyOn(window, 'getComputedStyle').and.returnValue({\n          transitionDuration: '0.15s',\n          transitionDelay: '0s'\n        })\n\n        const insertedFunc = jasmine.createSpy()\n        tooltipEl.addEventListener('inserted.bs.tooltip', insertedFunc)\n\n        setTimeout(() => {\n          expect(insertedFunc).toHaveBeenCalledTimes(1)\n          tooltip.hide()\n\n          setTimeout(() => {\n            tooltip.show()\n          }, 100)\n\n          setTimeout(() => {\n            expect(insertedFunc).toHaveBeenCalledTimes(2)\n            resolve()\n          }, 200)\n        }, 0)\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with custom class provided in data attributes', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\" data-bs-custom-class=\"custom-class\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tip = document.querySelector('.tooltip')\n          expect(tip).not.toBeNull()\n          expect(tip).toHaveClass('custom-class')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with custom class provided as a string in config', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          customClass: 'custom-class custom-class-2'\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tip = document.querySelector('.tooltip')\n          expect(tip).not.toBeNull()\n          expect(tip).toHaveClass('custom-class')\n          expect(tip).toHaveClass('custom-class-2')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should show a tooltip with custom class provided as a function in config', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const spy = jasmine.createSpy('customClass').and.returnValue('custom-class')\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          customClass: spy\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tip = document.querySelector('.tooltip')\n          expect(tip).not.toBeNull()\n          expect(spy).toHaveBeenCalled()\n          expect(tip).toHaveClass('custom-class')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should remove `title` attribute if exists', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          expect(tooltipEl.getAttribute('title')).toBeNull()\n          resolve()\n        })\n        tooltip.show()\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should hide a tooltip', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).toBeNull()\n          expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should hide a tooltip on mobile', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n        const spy = spyOn(EventHandler, 'off')\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          document.documentElement.ontouchstart = noop\n          tooltip.hide()\n        })\n\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).toBeNull()\n          expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)\n          document.documentElement.ontouchstart = undefined\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should hide a tooltip without animation', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          animation: false\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          expect(document.querySelector('.tooltip')).toBeNull()\n          expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should not hide a tooltip if hide event is prevented', () => {\n      return new Promise((resolve, reject) => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const assertDone = () => {\n          setTimeout(() => {\n            expect(document.querySelector('.tooltip')).not.toBeNull()\n            resolve()\n          }, 20)\n        }\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl, {\n          animation: false\n        })\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())\n        tooltipEl.addEventListener('hide.bs.tooltip', event => {\n          event.preventDefault()\n          assertDone()\n        })\n        tooltipEl.addEventListener('hidden.bs.tooltip', () => {\n          reject(new Error('should not trigger hidden event'))\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should not throw error running hide if popper hasn\\'t been shown', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(div)\n\n      try {\n        tooltip.hide()\n        expect().nothing()\n      } catch {\n        throw new Error('should not throw error')\n      }\n    })\n  })\n\n  describe('update', () => {\n    it('should call popper update', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const spy = spyOn(tooltip._popper, 'update')\n\n          tooltip.update()\n\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should do nothing if the tooltip is not shown', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      tooltip.update()\n      expect().nothing()\n    })\n  })\n\n  describe('_isWithContent', () => {\n    it('should return true if there is content', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._isWithContent()).toBeTrue()\n    })\n\n    it('should return false if there is no content', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._isWithContent()).toBeFalse()\n    })\n  })\n\n  describe('_getTipElement', () => {\n    it('should create the tip element and return it', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      const spy = spyOn(document, 'createElement').and.callThrough()\n\n      expect(tooltip._getTipElement()).toBeDefined()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should return the created tip element', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      const spy = spyOn(document, 'createElement').and.callThrough()\n\n      expect(tooltip._getTipElement()).toBeDefined()\n      expect(spy).toHaveBeenCalled()\n\n      spy.calls.reset()\n\n      expect(tooltip._getTipElement()).toBeDefined()\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('setContent', () => {\n    it('should set tip content', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl, { animation: false })\n\n      const tip = tooltip._getTipElement()\n\n      tooltip.setContent(tip)\n\n      expect(tip).not.toHaveClass('show')\n      expect(tip).not.toHaveClass('fade')\n      expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip')\n    })\n\n    it('should re-show tip if it was already shown', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" data-bs-title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n      tooltip.show()\n      const tip = () => tooltip._getTipElement()\n\n      expect(tip()).toHaveClass('show')\n      tooltip.setContent({ '.tooltip-inner': 'foo' })\n\n      expect(tip()).toHaveClass('show')\n      expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')\n    })\n\n    it('should keep tip hidden, if it was already hidden before', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" data-bs-title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n      const tip = () => tooltip._getTipElement()\n\n      expect(tip()).not.toHaveClass('show')\n      tooltip.setContent({ '.tooltip-inner': 'foo' })\n\n      expect(tip()).not.toHaveClass('show')\n      tooltip.show()\n      expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')\n    })\n\n    it('\"setContent\" should keep the initial template', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      tooltip.setContent({ '.tooltip-inner': 'foo' })\n      const tip = tooltip._getTipElement()\n\n      expect(tip).toHaveClass('tooltip')\n      expect(tip).toHaveClass('bs-tooltip-auto')\n      expect(tip.querySelector('.tooltip-arrow')).not.toBeNull()\n      expect(tip.querySelector('.tooltip-inner')).not.toBeNull()\n    })\n  })\n\n  describe('setContent', () => {\n    it('should do nothing if the element is null', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      tooltip.setContent({ '.tooltip': null })\n      expect().nothing()\n    })\n\n    it('should do nothing if the content is a child of the element', () => {\n      fixtureEl.innerHTML = [\n        '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">',\n        '  <div id=\"childContent\"></div>',\n        '</a>'\n      ].join('')\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const childContent = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(tooltipEl, {\n        html: true\n      })\n\n      tooltip._getTipElement().append(childContent)\n      tooltip.setContent({ '.tooltip': childContent })\n\n      expect().nothing()\n    })\n\n    it('should add the content as a child of the element for jQuery elements', () => {\n      fixtureEl.innerHTML = [\n        '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">',\n        '  <div id=\"childContent\"></div>',\n        '</a>'\n      ].join('')\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const childContent = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(tooltipEl, {\n        html: true\n      })\n\n      tooltip.setContent({ '.tooltip': { 0: childContent, jquery: 'jQuery' } })\n      tooltip.show()\n\n      expect(childContent.parentNode).toEqual(tooltip._getTipElement())\n    })\n\n    it('should add the child text content in the element', () => {\n      fixtureEl.innerHTML = [\n        '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">',\n        '  <div id=\"childContent\">Tooltip</div>',\n        '</a>'\n      ].join('')\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const childContent = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(tooltipEl)\n\n      tooltip.setContent({ '.tooltip': childContent })\n\n      expect(childContent.textContent).toEqual(tooltip._getTipElement().textContent)\n    })\n\n    it('should add html without sanitize it', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl, {\n        sanitize: false,\n        html: true\n      })\n\n      tooltip.setContent({ '.tooltip': '<div id=\"childContent\">Tooltip</div>' })\n\n      expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')\n    })\n\n    it('should add html sanitized', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl, {\n        html: true\n      })\n\n      const content = [\n        '<div id=\"childContent\">',\n        '  <button type=\"button\">test btn</button>',\n        '</div>'\n      ].join('')\n\n      tooltip.setContent({ '.tooltip': content })\n      expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')\n      expect(tooltip._getTipElement().querySelector('button')).toBeNull()\n    })\n\n    it('should add text content', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      tooltip.setContent({ '.tooltip': 'test' })\n\n      expect(tooltip._getTipElement().textContent).toEqual('test')\n    })\n  })\n\n  describe('_getTitle', () => {\n    it('should return the title', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl)\n\n      expect(tooltip._getTitle()).toEqual('Another tooltip')\n    })\n\n    it('should call title function', () => {\n      fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\"></a>'\n\n      const tooltipEl = fixtureEl.querySelector('a')\n      const tooltip = new Tooltip(tooltipEl, {\n        title: () => 'test'\n      })\n\n      expect(tooltip._getTitle()).toEqual('test')\n    })\n  })\n\n  describe('getInstance', () => {\n    it('should return tooltip instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const alert = new Tooltip(div)\n\n      expect(Tooltip.getInstance(div)).toEqual(alert)\n      expect(Tooltip.getInstance(div)).toBeInstanceOf(Tooltip)\n    })\n\n    it('should return null when there is no tooltip instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Tooltip.getInstance(div)).toBeNull()\n    })\n  })\n\n  describe('aria-label', () => {\n    it('should add the aria-label attribute for referencing original title', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\"></a>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tooltipShown = document.querySelector('.tooltip')\n\n          expect(tooltipShown).not.toBeNull()\n          expect(tooltipEl.getAttribute('aria-label')).toEqual('Another tooltip')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should add the aria-label attribute when element text content is a whitespace string', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"A tooltip\"><span>    </span></a>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tooltipShown = document.querySelector('.tooltip')\n\n          expect(tooltipShown).not.toBeNull()\n          expect(tooltipEl.getAttribute('aria-label')).toEqual('A tooltip')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should not add the aria-label attribute if the attribute already exists', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" aria-label=\"Different label\" title=\"Another tooltip\"></a>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tooltipShown = document.querySelector('.tooltip')\n\n          expect(tooltipShown).not.toBeNull()\n          expect(tooltipEl.getAttribute('aria-label')).toEqual('Different label')\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n\n    it('should not add the aria-label attribute if the element has text content', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<a href=\"#\" rel=\"tooltip\" title=\"Another tooltip\">text content</a>'\n\n        const tooltipEl = fixtureEl.querySelector('a')\n        const tooltip = new Tooltip(tooltipEl)\n\n        tooltipEl.addEventListener('shown.bs.tooltip', () => {\n          const tooltipShown = document.querySelector('.tooltip')\n\n          expect(tooltipShown).not.toBeNull()\n          expect(tooltipEl.getAttribute('aria-label')).toBeNull()\n          resolve()\n        })\n\n        tooltip.show()\n      })\n    })\n  })\n\n  describe('getOrCreateInstance', () => {\n    it('should return tooltip instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(div)\n\n      expect(Tooltip.getOrCreateInstance(div)).toEqual(tooltip)\n      expect(Tooltip.getInstance(div)).toEqual(Tooltip.getOrCreateInstance(div, {}))\n      expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip)\n    })\n\n    it('should return new instance when there is no tooltip instance', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Tooltip.getInstance(div)).toBeNull()\n      expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip)\n    })\n\n    it('should return new instance when there is no tooltip instance with given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Tooltip.getInstance(div)).toBeNull()\n      const tooltip = Tooltip.getOrCreateInstance(div, {\n        title: () => 'test'\n      })\n      expect(tooltip).toBeInstanceOf(Tooltip)\n\n      expect(tooltip._getTitle()).toEqual('test')\n    })\n\n    it('should return the instance when exists without given configuration', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(div, {\n        title: () => 'nothing'\n      })\n      expect(Tooltip.getInstance(div)).toEqual(tooltip)\n\n      const tooltip2 = Tooltip.getOrCreateInstance(div, {\n        title: () => 'test'\n      })\n      expect(tooltip).toBeInstanceOf(Tooltip)\n      expect(tooltip2).toEqual(tooltip)\n\n      expect(tooltip2._getTitle()).toEqual('nothing')\n    })\n  })\n\n  describe('jQueryInterface', () => {\n    it('should create a tooltip', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      jQueryMock.fn.tooltip = Tooltip.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.tooltip.call(jQueryMock)\n\n      expect(Tooltip.getInstance(div)).not.toBeNull()\n    })\n\n    it('should not re create a tooltip', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(div)\n\n      jQueryMock.fn.tooltip = Tooltip.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.tooltip.call(jQueryMock)\n\n      expect(Tooltip.getInstance(div)).toEqual(tooltip)\n    })\n\n    it('should call a tooltip method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const tooltip = new Tooltip(div)\n\n      const spy = spyOn(tooltip, 'show')\n\n      jQueryMock.fn.tooltip = Tooltip.jQueryInterface\n      jQueryMock.elements = [div]\n\n      jQueryMock.fn.tooltip.call(jQueryMock, 'show')\n\n      expect(Tooltip.getInstance(div)).toEqual(tooltip)\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should throw error on undefined method', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const action = 'undefinedMethod'\n\n      jQueryMock.fn.tooltip = Tooltip.jQueryInterface\n      jQueryMock.elements = [div]\n\n      expect(() => {\n        jQueryMock.fn.tooltip.call(jQueryMock, action)\n      }).toThrowError(TypeError, `No method named \"${action}\"`)\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/backdrop.spec.js",
    "content": "import Backdrop from '../../../src/util/backdrop'\nimport { getTransitionDurationFromElement } from '../../../src/util/index'\nimport { clearFixture, getFixture } from '../../helpers/fixture'\n\nconst CLASS_BACKDROP = '.modal-backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\ndescribe('Backdrop', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n    const list = document.querySelectorAll(CLASS_BACKDROP)\n\n    for (const el of list) {\n      el.remove()\n    }\n  })\n\n  describe('show', () => {\n    it('should append the backdrop html once on show and include the \"show\" class if it is \"shown\"', () => {\n      return new Promise(resolve => {\n        const instance = new Backdrop({\n          isVisible: true,\n          isAnimated: false\n        })\n        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)\n\n        expect(getElements()).toHaveSize(0)\n\n        instance.show()\n        instance.show(() => {\n          expect(getElements()).toHaveSize(1)\n          for (const el of getElements()) {\n            expect(el).toHaveClass(CLASS_NAME_SHOW)\n          }\n\n          resolve()\n        })\n      })\n    })\n\n    it('should not append the backdrop html if it is not \"shown\"', () => {\n      return new Promise(resolve => {\n        const instance = new Backdrop({\n          isVisible: false,\n          isAnimated: true\n        })\n        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)\n\n        expect(getElements()).toHaveSize(0)\n        instance.show(() => {\n          expect(getElements()).toHaveSize(0)\n          resolve()\n        })\n      })\n    })\n\n    it('should append the backdrop html once and include the \"fade\" class if it is \"shown\" and \"animated\"', () => {\n      return new Promise(resolve => {\n        const instance = new Backdrop({\n          isVisible: true,\n          isAnimated: true\n        })\n        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)\n\n        expect(getElements()).toHaveSize(0)\n\n        instance.show(() => {\n          expect(getElements()).toHaveSize(1)\n          for (const el of getElements()) {\n            expect(el).toHaveClass(CLASS_NAME_FADE)\n          }\n\n          resolve()\n        })\n      })\n    })\n  })\n\n  describe('hide', () => {\n    it('should remove the backdrop html', () => {\n      return new Promise(resolve => {\n        const instance = new Backdrop({\n          isVisible: true,\n          isAnimated: true\n        })\n\n        const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP)\n\n        expect(getElements()).toHaveSize(0)\n        instance.show(() => {\n          expect(getElements()).toHaveSize(1)\n          instance.hide(() => {\n            expect(getElements()).toHaveSize(0)\n            resolve()\n          })\n        })\n      })\n    })\n\n    it('should remove the \"show\" class', () => {\n      return new Promise(resolve => {\n        const instance = new Backdrop({\n          isVisible: true,\n          isAnimated: true\n        })\n        const elem = instance._getElement()\n\n        instance.show()\n        instance.hide(() => {\n          expect(elem).not.toHaveClass(CLASS_NAME_SHOW)\n          resolve()\n        })\n      })\n    })\n\n    it('should not try to remove Node on remove method if it is not \"shown\"', () => {\n      return new Promise(resolve => {\n        const instance = new Backdrop({\n          isVisible: false,\n          isAnimated: true\n        })\n        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)\n        const spy = spyOn(instance, 'dispose').and.callThrough()\n\n        expect(getElements()).toHaveSize(0)\n        expect(instance._isAppended).toBeFalse()\n        instance.show(() => {\n          instance.hide(() => {\n            expect(getElements()).toHaveSize(0)\n            expect(spy).not.toHaveBeenCalled()\n            expect(instance._isAppended).toBeFalse()\n            resolve()\n          })\n        })\n      })\n    })\n\n    it('should not error if the backdrop no longer has a parent', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div id=\"wrapper\"></div>'\n\n        const wrapper = fixtureEl.querySelector('#wrapper')\n        const instance = new Backdrop({\n          isVisible: true,\n          isAnimated: true,\n          rootElement: wrapper\n        })\n\n        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)\n\n        instance.show(() => {\n          wrapper.remove()\n          instance.hide(() => {\n            expect(getElements()).toHaveSize(0)\n            resolve()\n          })\n        })\n      })\n    })\n  })\n\n  describe('click callback', () => {\n    it('should execute callback on click', () => {\n      return new Promise(resolve => {\n        const spy = jasmine.createSpy('spy')\n\n        const instance = new Backdrop({\n          isVisible: true,\n          isAnimated: false,\n          clickCallback: () => spy()\n        })\n        const endTest = () => {\n          setTimeout(() => {\n            expect(spy).toHaveBeenCalled()\n            resolve()\n          }, 10)\n        }\n\n        instance.show(() => {\n          const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })\n          document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent)\n          endTest()\n        })\n      })\n    })\n\n    describe('animation callbacks', () => {\n      it('should show and hide backdrop after counting transition duration if it is animated', () => {\n        return new Promise(resolve => {\n          const instance = new Backdrop({\n            isVisible: true,\n            isAnimated: true\n          })\n          const spy2 = jasmine.createSpy('spy2')\n\n          const execDone = () => {\n            setTimeout(() => {\n              expect(spy2).toHaveBeenCalledTimes(2)\n              resolve()\n            }, 10)\n          }\n\n          instance.show(spy2)\n          instance.hide(() => {\n            spy2()\n            execDone()\n          })\n          expect(spy2).not.toHaveBeenCalled()\n        })\n      })\n\n      it('should show and hide backdrop without a delay if it is not animated', () => {\n        return new Promise(resolve => {\n          const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)\n          const instance = new Backdrop({\n            isVisible: true,\n            isAnimated: false\n          })\n          const spy2 = jasmine.createSpy('spy2')\n\n          instance.show(spy2)\n          instance.hide(spy2)\n\n          setTimeout(() => {\n            expect(spy2).toHaveBeenCalled()\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          }, 10)\n        })\n      })\n\n      it('should not call delay callbacks if it is not \"shown\"', () => {\n        return new Promise(resolve => {\n          const instance = new Backdrop({\n            isVisible: false,\n            isAnimated: true\n          })\n          const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)\n\n          instance.show()\n          instance.hide(() => {\n            expect(spy).not.toHaveBeenCalled()\n            resolve()\n          })\n        })\n      })\n    })\n\n    describe('Config', () => {\n      describe('rootElement initialization', () => {\n        it('should be appended on \"document.body\" by default', () => {\n          return new Promise(resolve => {\n            const instance = new Backdrop({\n              isVisible: true\n            })\n            const getElement = () => document.querySelector(CLASS_BACKDROP)\n            instance.show(() => {\n              expect(getElement().parentElement).toEqual(document.body)\n              resolve()\n            })\n          })\n        })\n\n        it('should find the rootElement if passed as a string', () => {\n          return new Promise(resolve => {\n            const instance = new Backdrop({\n              isVisible: true,\n              rootElement: 'body'\n            })\n            const getElement = () => document.querySelector(CLASS_BACKDROP)\n            instance.show(() => {\n              expect(getElement().parentElement).toEqual(document.body)\n              resolve()\n            })\n          })\n        })\n\n        it('should be appended on any element given by the proper config', () => {\n          return new Promise(resolve => {\n            fixtureEl.innerHTML = '<div id=\"wrapper\"></div>'\n\n            const wrapper = fixtureEl.querySelector('#wrapper')\n            const instance = new Backdrop({\n              isVisible: true,\n              rootElement: wrapper\n            })\n            const getElement = () => document.querySelector(CLASS_BACKDROP)\n            instance.show(() => {\n              expect(getElement().parentElement).toEqual(wrapper)\n              resolve()\n            })\n          })\n        })\n      })\n\n      describe('ClassName', () => {\n        it('should allow configuring className', () => {\n          return new Promise(resolve => {\n            const instance = new Backdrop({\n              isVisible: true,\n              className: 'foo'\n            })\n            const getElement = () => document.querySelector('.foo')\n            instance.show(() => {\n              expect(getElement()).toEqual(instance._getElement())\n              instance.dispose()\n              resolve()\n            })\n          })\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/component-functions.spec.js",
    "content": "/* Test helpers */\n\nimport { clearFixture, createEvent, getFixture } from '../../helpers/fixture'\nimport { enableDismissTrigger } from '../../../src/util/component-functions'\nimport BaseComponent from '../../../src/base-component'\n\nclass DummyClass2 extends BaseComponent {\n  static get NAME() {\n    return 'test'\n  }\n\n  hide() {\n    return true\n  }\n\n  testMethod() {\n    return true\n  }\n}\n\ndescribe('Plugin functions', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('data-bs-dismiss functionality', () => {\n    it('should get Plugin and execute the given method, when a click occurred on data-bs-dismiss=\"PluginName\"', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"foo\" class=\"test\">',\n        '  <button type=\"button\" data-bs-dismiss=\"test\" data-bs-target=\"#foo\"></button>',\n        '</div>'\n      ].join('')\n\n      const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()\n      const spyTest = spyOn(DummyClass2.prototype, 'testMethod')\n      const componentWrapper = fixtureEl.querySelector('#foo')\n      const btnClose = fixtureEl.querySelector('[data-bs-dismiss=\"test\"]')\n      const event = createEvent('click')\n\n      enableDismissTrigger(DummyClass2, 'testMethod')\n      btnClose.dispatchEvent(event)\n\n      expect(spyGet).toHaveBeenCalledWith(componentWrapper)\n      expect(spyTest).toHaveBeenCalled()\n    })\n\n    it('if data-bs-dismiss=\"PluginName\" hasn\\'t got \"data-bs-target\", \"getOrCreateInstance\" has to be initialized by closest \"plugin.Name\" class', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"foo\" class=\"test\">',\n        '  <button type=\"button\" data-bs-dismiss=\"test\"></button>',\n        '</div>'\n      ].join('')\n\n      const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()\n      const spyHide = spyOn(DummyClass2.prototype, 'hide')\n      const componentWrapper = fixtureEl.querySelector('#foo')\n      const btnClose = fixtureEl.querySelector('[data-bs-dismiss=\"test\"]')\n      const event = createEvent('click')\n\n      enableDismissTrigger(DummyClass2)\n      btnClose.dispatchEvent(event)\n\n      expect(spyGet).toHaveBeenCalledWith(componentWrapper)\n      expect(spyHide).toHaveBeenCalled()\n    })\n\n    it('if data-bs-dismiss=\"PluginName\" is disabled, must not trigger function', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"foo\" class=\"test\">',\n        '  <button type=\"button\" disabled data-bs-dismiss=\"test\"></button>',\n        '</div>'\n      ].join('')\n\n      const spy = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()\n      const btnClose = fixtureEl.querySelector('[data-bs-dismiss=\"test\"]')\n      const event = createEvent('click')\n\n      enableDismissTrigger(DummyClass2)\n      btnClose.dispatchEvent(event)\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should prevent default when the trigger is <a> or <area>', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"foo\" class=\"test\">',\n        '  <a type=\"button\" data-bs-dismiss=\"test\"></a>',\n        '</div>'\n      ].join('')\n\n      const btnClose = fixtureEl.querySelector('[data-bs-dismiss=\"test\"]')\n      const event = createEvent('click')\n\n      enableDismissTrigger(DummyClass2)\n      const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()\n\n      btnClose.dispatchEvent(event)\n\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/config.spec.js",
    "content": "import Config from '../../../src/util/config'\nimport { clearFixture, getFixture } from '../../helpers/fixture'\n\nclass DummyConfigClass extends Config {\n  static get NAME() {\n    return 'dummy'\n  }\n}\n\ndescribe('Config', () => {\n  let fixtureEl\n  const name = 'dummy'\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('NAME', () => {\n    it('should return plugin NAME', () => {\n      expect(DummyConfigClass.NAME).toEqual(name)\n    })\n  })\n\n  describe('DefaultType', () => {\n    it('should return plugin default type', () => {\n      expect(DummyConfigClass.DefaultType).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin defaults', () => {\n      expect(DummyConfigClass.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('mergeConfigObj', () => {\n    it('should parse element\\'s data attributes and merge it with default config. Element\\'s data attributes must excel Defaults', () => {\n      fixtureEl.innerHTML = '<div id=\"test\" data-bs-test-bool=\"false\" data-bs-test-int=\"8\" data-bs-test-string1=\"bar\"></div>'\n\n      spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({\n        testBool: true,\n        testString: 'foo',\n        testString1: 'foo',\n        testInt: 7\n      })\n      const instance = new DummyConfigClass()\n      const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))\n\n      expect(configResult.testBool).toEqual(false)\n      expect(configResult.testString).toEqual('foo')\n      expect(configResult.testString1).toEqual('bar')\n      expect(configResult.testInt).toEqual(8)\n    })\n\n    it('should parse element\\'s data attributes and merge it with default config, plug these given during method call. The programmatically given should excel all', () => {\n      fixtureEl.innerHTML = '<div id=\"test\" data-bs-test-bool=\"false\" data-bs-test-int=\"8\" data-bs-test-string-1=\"bar\"></div>'\n\n      spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({\n        testBool: true,\n        testString: 'foo',\n        testString1: 'foo',\n        testInt: 7\n      })\n      const instance = new DummyConfigClass()\n      const configResult = instance._mergeConfigObj({\n        testString1: 'test',\n        testInt: 3\n      }, fixtureEl.querySelector('#test'))\n\n      expect(configResult.testBool).toEqual(false)\n      expect(configResult.testString).toEqual('foo')\n      expect(configResult.testString1).toEqual('test')\n      expect(configResult.testInt).toEqual(3)\n    })\n\n    it('should parse element\\'s data attribute `config` and any rest attributes. The programmatically given should excel all. Data attribute `config` should excel only Defaults', () => {\n      fixtureEl.innerHTML = '<div id=\"test\" data-bs-config=\\'{\"testBool\":false,\"testInt\":50,\"testInt2\":100}\\' data-bs-test-int=\"8\" data-bs-test-string-1=\"bar\"></div>'\n\n      spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({\n        testBool: true,\n        testString: 'foo',\n        testString1: 'foo',\n        testInt: 7,\n        testInt2: 600\n      })\n      const instance = new DummyConfigClass()\n      const configResult = instance._mergeConfigObj({\n        testString1: 'test'\n      }, fixtureEl.querySelector('#test'))\n\n      expect(configResult.testBool).toEqual(false)\n      expect(configResult.testString).toEqual('foo')\n      expect(configResult.testString1).toEqual('test')\n      expect(configResult.testInt).toEqual(8)\n      expect(configResult.testInt2).toEqual(100)\n    })\n\n    it('should omit element\\'s data attribute `config` if is not an object', () => {\n      fixtureEl.innerHTML = '<div id=\"test\" data-bs-config=\"foo\" data-bs-test-int=\"8\"></div>'\n\n      spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({\n        testInt: 7,\n        testInt2: 79\n      })\n      const instance = new DummyConfigClass()\n      const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))\n\n      expect(configResult.testInt).toEqual(8)\n      expect(configResult.testInt2).toEqual(79)\n    })\n  })\n\n  describe('typeCheckConfig', () => {\n    it('should check type of the config object', () => {\n      spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({\n        toggle: 'boolean',\n        parent: '(string|element)'\n      })\n      const config = {\n        toggle: true,\n        parent: 777\n      }\n\n      const obj = new DummyConfigClass()\n      expect(() => {\n        obj._typeCheckConfig(config)\n      }).toThrowError(TypeError, obj.constructor.NAME.toUpperCase() + ': Option \"parent\" provided type \"number\" but expected type \"(string|element)\".')\n    })\n\n    it('should return null stringified when null is passed', () => {\n      spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({\n        toggle: 'boolean',\n        parent: '(null|element)'\n      })\n\n      const obj = new DummyConfigClass()\n      const config = {\n        toggle: true,\n        parent: null\n      }\n\n      obj._typeCheckConfig(config)\n      expect().nothing()\n    })\n\n    it('should return undefined stringified when undefined is passed', () => {\n      spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({\n        toggle: 'boolean',\n        parent: '(undefined|element)'\n      })\n\n      const obj = new DummyConfigClass()\n      const config = {\n        toggle: true,\n        parent: undefined\n      }\n\n      obj._typeCheckConfig(config)\n      expect().nothing()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/focustrap.spec.js",
    "content": "import FocusTrap from '../../../src/util/focustrap'\nimport EventHandler from '../../../src/dom/event-handler'\nimport SelectorEngine from '../../../src/dom/selector-engine'\nimport { clearFixture, createEvent, getFixture } from '../../helpers/fixture'\n\ndescribe('FocusTrap', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('activate', () => {\n    it('should autofocus itself by default', () => {\n      fixtureEl.innerHTML = '<div id=\"focustrap\" tabindex=\"-1\"></div>'\n\n      const trapElement = fixtureEl.querySelector('div')\n\n      const spy = spyOn(trapElement, 'focus')\n\n      const focustrap = new FocusTrap({ trapElement })\n      focustrap.activate()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('if configured not to autofocus, should not autofocus itself', () => {\n      fixtureEl.innerHTML = '<div id=\"focustrap\" tabindex=\"-1\"></div>'\n\n      const trapElement = fixtureEl.querySelector('div')\n\n      const spy = spyOn(trapElement, 'focus')\n\n      const focustrap = new FocusTrap({ trapElement, autofocus: false })\n      focustrap.activate()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should force focus inside focus trap if it can', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a href=\"#\" id=\"outside\">outside</a>',\n          '<div id=\"focustrap\" tabindex=\"-1\">',\n          '  <a href=\"#\" id=\"inside\">inside</a>',\n          '</div>'\n        ].join('')\n\n        const trapElement = fixtureEl.querySelector('div')\n        const focustrap = new FocusTrap({ trapElement })\n        focustrap.activate()\n\n        const inside = document.getElementById('inside')\n\n        const focusInListener = () => {\n          expect(spy).toHaveBeenCalled()\n          document.removeEventListener('focusin', focusInListener)\n          resolve()\n        }\n\n        const spy = spyOn(inside, 'focus')\n        spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside])\n\n        document.addEventListener('focusin', focusInListener)\n\n        const focusInEvent = createEvent('focusin', { bubbles: true })\n        Object.defineProperty(focusInEvent, 'target', {\n          value: document.getElementById('outside')\n        })\n\n        document.dispatchEvent(focusInEvent)\n      })\n    })\n\n    it('should wrap focus around forward on tab', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a href=\"#\" id=\"outside\">outside</a>',\n          '<div id=\"focustrap\" tabindex=\"-1\">',\n          '  <a href=\"#\" id=\"first\">first</a>',\n          '  <a href=\"#\" id=\"inside\">inside</a>',\n          '  <a href=\"#\" id=\"last\">last</a>',\n          '</div>'\n        ].join('')\n\n        const trapElement = fixtureEl.querySelector('div')\n        const focustrap = new FocusTrap({ trapElement })\n        focustrap.activate()\n\n        const first = document.getElementById('first')\n        const inside = document.getElementById('inside')\n        const last = document.getElementById('last')\n        const outside = document.getElementById('outside')\n\n        spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])\n        const spy = spyOn(first, 'focus').and.callThrough()\n\n        const focusInListener = () => {\n          expect(spy).toHaveBeenCalled()\n          first.removeEventListener('focusin', focusInListener)\n          resolve()\n        }\n\n        first.addEventListener('focusin', focusInListener)\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'Tab'\n\n        document.dispatchEvent(keydown)\n        outside.focus()\n      })\n    })\n\n    it('should wrap focus around backwards on shift-tab', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a href=\"#\" id=\"outside\">outside</a>',\n          '<div id=\"focustrap\" tabindex=\"-1\">',\n          '  <a href=\"#\" id=\"first\">first</a>',\n          '  <a href=\"#\" id=\"inside\">inside</a>',\n          '  <a href=\"#\" id=\"last\">last</a>',\n          '</div>'\n        ].join('')\n\n        const trapElement = fixtureEl.querySelector('div')\n        const focustrap = new FocusTrap({ trapElement })\n        focustrap.activate()\n\n        const first = document.getElementById('first')\n        const inside = document.getElementById('inside')\n        const last = document.getElementById('last')\n        const outside = document.getElementById('outside')\n\n        spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])\n        const spy = spyOn(last, 'focus').and.callThrough()\n\n        const focusInListener = () => {\n          expect(spy).toHaveBeenCalled()\n          last.removeEventListener('focusin', focusInListener)\n          resolve()\n        }\n\n        last.addEventListener('focusin', focusInListener)\n\n        const keydown = createEvent('keydown')\n        keydown.key = 'Tab'\n        keydown.shiftKey = true\n\n        document.dispatchEvent(keydown)\n        outside.focus()\n      })\n    })\n\n    it('should force focus on itself if there is no focusable content', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<a href=\"#\" id=\"outside\">outside</a>',\n          '<div id=\"focustrap\" tabindex=\"-1\"></div>'\n        ].join('')\n\n        const trapElement = fixtureEl.querySelector('div')\n        const focustrap = new FocusTrap({ trapElement })\n        focustrap.activate()\n\n        const focusInListener = () => {\n          expect(spy).toHaveBeenCalled()\n          document.removeEventListener('focusin', focusInListener)\n          resolve()\n        }\n\n        const spy = spyOn(focustrap._config.trapElement, 'focus')\n\n        document.addEventListener('focusin', focusInListener)\n\n        const focusInEvent = createEvent('focusin', { bubbles: true })\n        Object.defineProperty(focusInEvent, 'target', {\n          value: document.getElementById('outside')\n        })\n\n        document.dispatchEvent(focusInEvent)\n      })\n    })\n  })\n\n  describe('deactivate', () => {\n    it('should flag itself as no longer active', () => {\n      const focustrap = new FocusTrap({ trapElement: fixtureEl })\n      focustrap.activate()\n      expect(focustrap._isActive).toBeTrue()\n\n      focustrap.deactivate()\n      expect(focustrap._isActive).toBeFalse()\n    })\n\n    it('should remove all event listeners', () => {\n      const focustrap = new FocusTrap({ trapElement: fixtureEl })\n      focustrap.activate()\n\n      const spy = spyOn(EventHandler, 'off')\n      focustrap.deactivate()\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('doesn\\'t try removing event listeners unless it needs to (in case it hasn\\'t been activated)', () => {\n      const focustrap = new FocusTrap({ trapElement: fixtureEl })\n\n      const spy = spyOn(EventHandler, 'off')\n      focustrap.deactivate()\n\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/index.spec.js",
    "content": "import * as Util from '../../../src/util/index'\nimport { clearFixture, getFixture } from '../../helpers/fixture'\nimport { noop } from '../../../src/util/index'\n\ndescribe('Util', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('getUID', () => {\n    it('should generate uid', () => {\n      const uid = Util.getUID('bs')\n      const uid2 = Util.getUID('bs')\n\n      expect(uid).not.toEqual(uid2)\n    })\n  })\n\n  describe('getSelectorFromElement', () => {\n    it('should get selector from data-bs-target', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"test\" data-bs-target=\".target\"></div>',\n        '<div class=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getSelectorFromElement(testEl)).toEqual('.target')\n    })\n\n    it('should get selector from href if no data-bs-target set', () => {\n      fixtureEl.innerHTML = [\n        '<a id=\"test\" href=\".target\"></a>',\n        '<div class=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getSelectorFromElement(testEl)).toEqual('.target')\n    })\n\n    it('should get selector from href if data-bs-target equal to #', () => {\n      fixtureEl.innerHTML = [\n        '<a id=\"test\" data-bs-target=\"#\" href=\".target\"></a>',\n        '<div class=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getSelectorFromElement(testEl)).toEqual('.target')\n    })\n\n    it('should return null if a selector from a href is a url without an anchor', () => {\n      fixtureEl.innerHTML = [\n        '<a id=\"test\" data-bs-target=\"#\" href=\"foo/bar.html\"></a>',\n        '<div class=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getSelectorFromElement(testEl)).toBeNull()\n    })\n\n    it('should return the anchor if a selector from a href is a url', () => {\n      fixtureEl.innerHTML = [\n        '<a id=\"test\" data-bs-target=\"#\" href=\"foo/bar.html#target\"></a>',\n        '<div id=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getSelectorFromElement(testEl)).toEqual('#target')\n    })\n\n    it('should return null if selector not found', () => {\n      fixtureEl.innerHTML = '<a id=\"test\" href=\".target\"></a>'\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getSelectorFromElement(testEl)).toBeNull()\n    })\n\n    it('should return null if no selector', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const testEl = fixtureEl.querySelector('div')\n\n      expect(Util.getSelectorFromElement(testEl)).toBeNull()\n    })\n  })\n\n  describe('getElementFromSelector', () => {\n    it('should get element from data-bs-target', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"test\" data-bs-target=\".target\"></div>',\n        '<div class=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))\n    })\n\n    it('should get element from href if no data-bs-target set', () => {\n      fixtureEl.innerHTML = [\n        '<a id=\"test\" href=\".target\"></a>',\n        '<div class=\"target\"></div>'\n      ].join('')\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))\n    })\n\n    it('should return null if element not found', () => {\n      fixtureEl.innerHTML = '<a id=\"test\" href=\".target\"></a>'\n\n      const testEl = fixtureEl.querySelector('#test')\n\n      expect(Util.getElementFromSelector(testEl)).toBeNull()\n    })\n\n    it('should return null if no selector', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const testEl = fixtureEl.querySelector('div')\n\n      expect(Util.getElementFromSelector(testEl)).toBeNull()\n    })\n  })\n\n  describe('getTransitionDurationFromElement', () => {\n    it('should get transition from element', () => {\n      fixtureEl.innerHTML = '<div style=\"transition: all 300ms ease-out;\"></div>'\n\n      expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(300)\n    })\n\n    it('should return 0 if the element is undefined or null', () => {\n      expect(Util.getTransitionDurationFromElement(null)).toEqual(0)\n      expect(Util.getTransitionDurationFromElement(undefined)).toEqual(0)\n    })\n\n    it('should return 0 if the element do not possess transition', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(0)\n    })\n  })\n\n  describe('triggerTransitionEnd', () => {\n    it('should trigger transitionend event', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = '<div></div>'\n\n        const el = fixtureEl.querySelector('div')\n        const spy = spyOn(el, 'dispatchEvent').and.callThrough()\n\n        el.addEventListener('transitionend', () => {\n          expect(spy).toHaveBeenCalled()\n          resolve()\n        })\n\n        Util.triggerTransitionEnd(el)\n      })\n    })\n  })\n\n  describe('isElement', () => {\n    it('should detect if the parameter is an element or not and return Boolean', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"foo\" class=\"test\"></div>',\n        '<div id=\"bar\" class=\"test\"></div>'\n      ].join('')\n\n      const el = fixtureEl.querySelector('#foo')\n\n      expect(Util.isElement(el)).toBeTrue()\n      expect(Util.isElement({})).toBeFalse()\n      expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toBeFalse()\n    })\n\n    it('should detect jQuery element', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const el = fixtureEl.querySelector('div')\n      const fakejQuery = {\n        0: el,\n        jquery: 'foo'\n      }\n\n      expect(Util.isElement(fakejQuery)).toBeTrue()\n    })\n  })\n\n  describe('getElement', () => {\n    it('should try to parse element', () => {\n      fixtureEl.innerHTML = [\n        '<div id=\"foo\" class=\"test\"></div>',\n        '<div id=\"bar\" class=\"test\"></div>'\n      ].join('')\n\n      const el = fixtureEl.querySelector('div')\n\n      expect(Util.getElement(el)).toEqual(el)\n      expect(Util.getElement('#foo')).toEqual(el)\n      expect(Util.getElement('#fail')).toBeNull()\n      expect(Util.getElement({})).toBeNull()\n      expect(Util.getElement([])).toBeNull()\n      expect(Util.getElement()).toBeNull()\n      expect(Util.getElement(null)).toBeNull()\n      expect(Util.getElement(fixtureEl.querySelectorAll('.test'))).toBeNull()\n\n      const fakejQueryObject = {\n        0: el,\n        jquery: 'foo'\n      }\n\n      expect(Util.getElement(fakejQueryObject)).toEqual(el)\n    })\n  })\n\n  describe('isVisible', () => {\n    it('should return false if the element is not defined', () => {\n      expect(Util.isVisible(null)).toBeFalse()\n      expect(Util.isVisible(undefined)).toBeFalse()\n    })\n\n    it('should return false if the element provided is not a dom element', () => {\n      expect(Util.isVisible({})).toBeFalse()\n    })\n\n    it('should return false if the element is not visible with display none', () => {\n      fixtureEl.innerHTML = '<div style=\"display: none;\"></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Util.isVisible(div)).toBeFalse()\n    })\n\n    it('should return false if the element is not visible with visibility hidden', () => {\n      fixtureEl.innerHTML = '<div style=\"visibility: hidden;\"></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      expect(Util.isVisible(div)).toBeFalse()\n    })\n\n    it('should return false if an ancestor element is display none', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"display: none;\">',\n        '  <div>',\n        '    <div>',\n        '      <div class=\"content\"></div>',\n        '    </div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('.content')\n\n      expect(Util.isVisible(div)).toBeFalse()\n    })\n\n    it('should return false if an ancestor element is visibility hidden', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"visibility: hidden;\">',\n        '  <div>',\n        '    <div>',\n        '      <div class=\"content\"></div>',\n        '    </div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('.content')\n\n      expect(Util.isVisible(div)).toBeFalse()\n    })\n\n    it('should return true if an ancestor element is visibility hidden, but reverted', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"visibility: hidden;\">',\n        '  <div style=\"visibility: visible;\">',\n        '    <div>',\n        '      <div class=\"content\"></div>',\n        '    </div>',\n        '  </div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('.content')\n\n      expect(Util.isVisible(div)).toBeTrue()\n    })\n\n    it('should return true if the element is visible', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <div id=\"element\"></div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#element')\n\n      expect(Util.isVisible(div)).toBeTrue()\n    })\n\n    it('should return false if the element is hidden, but not via display or visibility', () => {\n      fixtureEl.innerHTML = [\n        '<details>',\n        '  <div id=\"element\"></div>',\n        '</details>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#element')\n\n      expect(Util.isVisible(div)).toBeFalse()\n    })\n\n    it('should return true if its a closed details element', () => {\n      fixtureEl.innerHTML = '<details id=\"element\"></details>'\n\n      const div = fixtureEl.querySelector('#element')\n\n      expect(Util.isVisible(div)).toBeTrue()\n    })\n\n    it('should return true if the element is visible inside an open details element', () => {\n      fixtureEl.innerHTML = [\n        '<details open>',\n        '  <div id=\"element\"></div>',\n        '</details>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#element')\n\n      expect(Util.isVisible(div)).toBeTrue()\n    })\n\n    it('should return true if the element is a visible summary in a closed details element', () => {\n      fixtureEl.innerHTML = [\n        '<details>',\n        '  <summary id=\"element-1\">',\n        '    <span id=\"element-2\"></span>',\n        '  </summary>',\n        '</details>'\n      ].join('')\n\n      const element1 = fixtureEl.querySelector('#element-1')\n      const element2 = fixtureEl.querySelector('#element-2')\n\n      expect(Util.isVisible(element1)).toBeTrue()\n      expect(Util.isVisible(element2)).toBeTrue()\n    })\n  })\n\n  describe('isDisabled', () => {\n    it('should return true if the element is not defined', () => {\n      expect(Util.isDisabled(null)).toBeTrue()\n      expect(Util.isDisabled(undefined)).toBeTrue()\n      expect(Util.isDisabled()).toBeTrue()\n    })\n\n    it('should return true if the element provided is not a dom element', () => {\n      expect(Util.isDisabled({})).toBeTrue()\n      expect(Util.isDisabled('test')).toBeTrue()\n    })\n\n    it('should return true if the element has disabled attribute', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <div id=\"element\" disabled=\"disabled\"></div>',\n        '  <div id=\"element1\" disabled=\"true\"></div>',\n        '  <div id=\"element2\" disabled></div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#element')\n      const div1 = fixtureEl.querySelector('#element1')\n      const div2 = fixtureEl.querySelector('#element2')\n\n      expect(Util.isDisabled(div)).toBeTrue()\n      expect(Util.isDisabled(div1)).toBeTrue()\n      expect(Util.isDisabled(div2)).toBeTrue()\n    })\n\n    it('should return false if the element has disabled attribute with \"false\" value, or doesn\\'t have attribute', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <div id=\"element\" disabled=\"false\"></div>',\n        '  <div id=\"element1\" ></div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#element')\n      const div1 = fixtureEl.querySelector('#element1')\n\n      expect(Util.isDisabled(div)).toBeFalse()\n      expect(Util.isDisabled(div1)).toBeFalse()\n    })\n\n    it('should return false if the element is not disabled ', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <button id=\"button\"></button>',\n        '  <select id=\"select\"></select>',\n        '  <select id=\"input\"></select>',\n        '</div>'\n      ].join('')\n\n      const el = selector => fixtureEl.querySelector(selector)\n\n      expect(Util.isDisabled(el('#button'))).toBeFalse()\n      expect(Util.isDisabled(el('#select'))).toBeFalse()\n      expect(Util.isDisabled(el('#input'))).toBeFalse()\n    })\n\n    it('should return true if the element has disabled attribute', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <input id=\"input\" disabled=\"disabled\">',\n        '  <input id=\"input1\" disabled=\"disabled\">',\n        '  <button id=\"button\" disabled=\"true\"></button>',\n        '  <button id=\"button1\" disabled=\"disabled\"></button>',\n        '  <button id=\"button2\" disabled></button>',\n        '  <select id=\"select\" disabled></select>',\n        '  <select id=\"input\" disabled></select>',\n        '</div>'\n      ].join('')\n\n      const el = selector => fixtureEl.querySelector(selector)\n\n      expect(Util.isDisabled(el('#input'))).toBeTrue()\n      expect(Util.isDisabled(el('#input1'))).toBeTrue()\n      expect(Util.isDisabled(el('#button'))).toBeTrue()\n      expect(Util.isDisabled(el('#button1'))).toBeTrue()\n      expect(Util.isDisabled(el('#button2'))).toBeTrue()\n      expect(Util.isDisabled(el('#input'))).toBeTrue()\n    })\n\n    it('should return true if the element has class \"disabled\"', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <div id=\"element\" class=\"disabled\"></div>',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#element')\n\n      expect(Util.isDisabled(div)).toBeTrue()\n    })\n\n    it('should return true if the element has class \"disabled\" but disabled attribute is false', () => {\n      fixtureEl.innerHTML = [\n        '<div>',\n        '  <input id=\"input\" class=\"disabled\" disabled=\"false\">',\n        '</div>'\n      ].join('')\n\n      const div = fixtureEl.querySelector('#input')\n\n      expect(Util.isDisabled(div)).toBeTrue()\n    })\n  })\n\n  describe('findShadowRoot', () => {\n    it('should return null if shadow dom is not available', () => {\n      // Only for newer browsers\n      if (!document.documentElement.attachShadow) {\n        expect().nothing()\n        return\n      }\n\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n\n      spyOn(document.documentElement, 'attachShadow').and.returnValue(null)\n\n      expect(Util.findShadowRoot(div)).toBeNull()\n    })\n\n    it('should return null when we do not find a shadow root', () => {\n      // Only for newer browsers\n      if (!document.documentElement.attachShadow) {\n        expect().nothing()\n        return\n      }\n\n      spyOn(document, 'getRootNode').and.returnValue(undefined)\n\n      expect(Util.findShadowRoot(document)).toBeNull()\n    })\n\n    it('should return the shadow root when found', () => {\n      // Only for newer browsers\n      if (!document.documentElement.attachShadow) {\n        expect().nothing()\n        return\n      }\n\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const shadowRoot = div.attachShadow({\n        mode: 'open'\n      })\n\n      expect(Util.findShadowRoot(shadowRoot)).toEqual(shadowRoot)\n\n      shadowRoot.innerHTML = '<button>Shadow Button</button>'\n\n      expect(Util.findShadowRoot(shadowRoot.firstChild)).toEqual(shadowRoot)\n    })\n  })\n\n  describe('noop', () => {\n    it('should be a function', () => {\n      expect(Util.noop).toEqual(jasmine.any(Function))\n    })\n  })\n\n  describe('reflow', () => {\n    it('should return element offset height to force the reflow', () => {\n      fixtureEl.innerHTML = '<div></div>'\n\n      const div = fixtureEl.querySelector('div')\n      const spy = spyOnProperty(div, 'offsetHeight')\n      Util.reflow(div)\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('getjQuery', () => {\n    const fakejQuery = { trigger() {} }\n\n    beforeEach(() => {\n      Object.defineProperty(window, 'jQuery', {\n        value: fakejQuery,\n        writable: true\n      })\n    })\n\n    afterEach(() => {\n      window.jQuery = undefined\n    })\n\n    it('should return jQuery object when present', () => {\n      expect(Util.getjQuery()).toEqual(fakejQuery)\n    })\n\n    it('should not return jQuery object when present if data-bs-no-jquery', () => {\n      document.body.setAttribute('data-bs-no-jquery', '')\n\n      expect(window.jQuery).toEqual(fakejQuery)\n      expect(Util.getjQuery()).toBeNull()\n\n      document.body.removeAttribute('data-bs-no-jquery')\n    })\n\n    it('should not return jQuery if not present', () => {\n      window.jQuery = undefined\n      expect(Util.getjQuery()).toBeNull()\n    })\n  })\n\n  describe('onDOMContentLoaded', () => {\n    it('should execute callbacks when DOMContentLoaded is fired and should not add more than one listener', () => {\n      const spy = jasmine.createSpy()\n      const spy2 = jasmine.createSpy()\n\n      const spyAdd = spyOn(document, 'addEventListener').and.callThrough()\n      spyOnProperty(document, 'readyState').and.returnValue('loading')\n\n      Util.onDOMContentLoaded(spy)\n      Util.onDOMContentLoaded(spy2)\n\n      document.dispatchEvent(new Event('DOMContentLoaded', {\n        bubbles: true,\n        cancelable: true\n      }))\n\n      expect(spy).toHaveBeenCalled()\n      expect(spy2).toHaveBeenCalled()\n      expect(spyAdd).toHaveBeenCalledTimes(1)\n    })\n\n    it('should execute callback if readyState is not \"loading\"', () => {\n      const spy = jasmine.createSpy()\n      Util.onDOMContentLoaded(spy)\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('defineJQueryPlugin', () => {\n    const fakejQuery = { fn: {} }\n\n    beforeEach(() => {\n      Object.defineProperty(window, 'jQuery', {\n        value: fakejQuery,\n        writable: true\n      })\n    })\n\n    afterEach(() => {\n      window.jQuery = undefined\n    })\n\n    it('should define a plugin on the jQuery instance', () => {\n      const pluginMock = Util.noop\n      pluginMock.NAME = 'test'\n      pluginMock.jQueryInterface = Util.noop\n\n      Util.defineJQueryPlugin(pluginMock)\n      expect(fakejQuery.fn.test).toEqual(pluginMock.jQueryInterface)\n      expect(fakejQuery.fn.test.Constructor).toEqual(pluginMock)\n      expect(fakejQuery.fn.test.noConflict).toEqual(jasmine.any(Function))\n    })\n  })\n\n  describe('execute', () => {\n    it('should execute if arg is function', () => {\n      const spy = jasmine.createSpy('spy')\n      Util.execute(spy)\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('executeAfterTransition', () => {\n    it('should immediately execute a function when waitForTransition parameter is false', () => {\n      const el = document.createElement('div')\n      const callbackSpy = jasmine.createSpy('callback spy')\n      const eventListenerSpy = spyOn(el, 'addEventListener')\n\n      Util.executeAfterTransition(callbackSpy, el, false)\n\n      expect(callbackSpy).toHaveBeenCalled()\n      expect(eventListenerSpy).not.toHaveBeenCalled()\n    })\n\n    it('should execute a function when a transitionend event is dispatched', () => {\n      const el = document.createElement('div')\n      const callbackSpy = jasmine.createSpy('callback spy')\n\n      spyOn(window, 'getComputedStyle').and.returnValue({\n        transitionDuration: '0.05s',\n        transitionDelay: '0s'\n      })\n\n      Util.executeAfterTransition(callbackSpy, el)\n\n      el.dispatchEvent(new TransitionEvent('transitionend'))\n\n      expect(callbackSpy).toHaveBeenCalled()\n    })\n\n    it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', () => {\n      return new Promise(resolve => {\n        const el = document.createElement('div')\n        const callbackSpy = jasmine.createSpy('callback spy')\n\n        spyOn(window, 'getComputedStyle').and.returnValue({\n          transitionDuration: '0.05s',\n          transitionDelay: '0s'\n        })\n\n        Util.executeAfterTransition(callbackSpy, el)\n\n        setTimeout(() => {\n          expect(callbackSpy).toHaveBeenCalled()\n          resolve()\n        }, 70)\n      })\n    })\n\n    it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', () => {\n      return new Promise(resolve => {\n        const el = document.createElement('div')\n        const callbackSpy = jasmine.createSpy('callback spy')\n\n        spyOn(window, 'getComputedStyle').and.returnValue({\n          transitionDuration: '0.05s',\n          transitionDelay: '0s'\n        })\n\n        Util.executeAfterTransition(callbackSpy, el)\n\n        setTimeout(() => {\n          el.dispatchEvent(new TransitionEvent('transitionend'))\n        }, 50)\n\n        setTimeout(() => {\n          expect(callbackSpy).toHaveBeenCalledTimes(1)\n          resolve()\n        }, 70)\n      })\n    })\n\n    it('should not trigger a transitionend event if another transitionend event had already happened', () => {\n      return new Promise(resolve => {\n        const el = document.createElement('div')\n\n        spyOn(window, 'getComputedStyle').and.returnValue({\n          transitionDuration: '0.05s',\n          transitionDelay: '0s'\n        })\n\n        Util.executeAfterTransition(noop, el)\n\n        // simulate a event dispatched by the browser\n        el.dispatchEvent(new TransitionEvent('transitionend'))\n\n        const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough()\n\n        setTimeout(() => {\n          // setTimeout should not have triggered another transitionend event.\n          expect(dispatchSpy).not.toHaveBeenCalled()\n          resolve()\n        }, 70)\n      })\n    })\n\n    it('should ignore transitionend events from nested elements', () => {\n      return new Promise(resolve => {\n        fixtureEl.innerHTML = [\n          '<div class=\"outer\">',\n          '  <div class=\"nested\"></div>',\n          '</div>'\n        ].join('')\n\n        const outer = fixtureEl.querySelector('.outer')\n        const nested = fixtureEl.querySelector('.nested')\n        const callbackSpy = jasmine.createSpy('callback spy')\n\n        spyOn(window, 'getComputedStyle').and.returnValue({\n          transitionDuration: '0.05s',\n          transitionDelay: '0s'\n        })\n\n        Util.executeAfterTransition(callbackSpy, outer)\n\n        nested.dispatchEvent(new TransitionEvent('transitionend', {\n          bubbles: true\n        }))\n\n        setTimeout(() => {\n          expect(callbackSpy).not.toHaveBeenCalled()\n        }, 20)\n\n        setTimeout(() => {\n          expect(callbackSpy).toHaveBeenCalled()\n          resolve()\n        }, 70)\n      })\n    })\n  })\n\n  describe('getNextActiveElement', () => {\n    it('should return first element if active not exists or not given and shouldGetNext is either true, or false with cycling being disabled', () => {\n      const array = ['a', 'b', 'c', 'd']\n\n      expect(Util.getNextActiveElement(array, '', true, true)).toEqual('a')\n      expect(Util.getNextActiveElement(array, 'g', true, true)).toEqual('a')\n      expect(Util.getNextActiveElement(array, '', true, false)).toEqual('a')\n      expect(Util.getNextActiveElement(array, 'g', true, false)).toEqual('a')\n      expect(Util.getNextActiveElement(array, '', false, false)).toEqual('a')\n      expect(Util.getNextActiveElement(array, 'g', false, false)).toEqual('a')\n    })\n\n    it('should return last element if active not exists or not given and shouldGetNext is false but cycling is enabled', () => {\n      const array = ['a', 'b', 'c', 'd']\n\n      expect(Util.getNextActiveElement(array, '', false, true)).toEqual('d')\n      expect(Util.getNextActiveElement(array, 'g', false, true)).toEqual('d')\n    })\n\n    it('should return next element or same if is last', () => {\n      const array = ['a', 'b', 'c', 'd']\n\n      expect(Util.getNextActiveElement(array, 'a', true, true)).toEqual('b')\n      expect(Util.getNextActiveElement(array, 'b', true, true)).toEqual('c')\n      expect(Util.getNextActiveElement(array, 'd', true, false)).toEqual('d')\n    })\n\n    it('should return next element or first, if is last and \"isCycleAllowed = true\"', () => {\n      const array = ['a', 'b', 'c', 'd']\n\n      expect(Util.getNextActiveElement(array, 'c', true, true)).toEqual('d')\n      expect(Util.getNextActiveElement(array, 'd', true, true)).toEqual('a')\n    })\n\n    it('should return previous element or same if is first', () => {\n      const array = ['a', 'b', 'c', 'd']\n\n      expect(Util.getNextActiveElement(array, 'b', false, true)).toEqual('a')\n      expect(Util.getNextActiveElement(array, 'd', false, true)).toEqual('c')\n      expect(Util.getNextActiveElement(array, 'a', false, false)).toEqual('a')\n    })\n\n    it('should return next element or first, if is last and \"isCycleAllowed = true\"', () => {\n      const array = ['a', 'b', 'c', 'd']\n\n      expect(Util.getNextActiveElement(array, 'd', false, true)).toEqual('c')\n      expect(Util.getNextActiveElement(array, 'a', false, true)).toEqual('d')\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/sanitizer.spec.js",
    "content": "import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer'\n\ndescribe('Sanitizer', () => {\n  describe('sanitizeHtml', () => {\n    it('should return the same on empty string', () => {\n      const empty = ''\n\n      const result = sanitizeHtml(empty, DefaultAllowlist, null)\n\n      expect(result).toEqual(empty)\n    })\n\n    it('should sanitize template by removing tags with XSS', () => {\n      const template = [\n        '<div>',\n        '  <a href=\"javascript:alert(7)\">Click me</a>',\n        '  <span>Some content</span>',\n        '</div>'\n      ].join('')\n\n      const result = sanitizeHtml(template, DefaultAllowlist, null)\n\n      expect(result).not.toContain('href=\"javascript:alert(7)')\n    })\n\n    it('should sanitize template and work with multiple regex', () => {\n      const template = [\n        '<div>',\n        '  <a href=\"javascript:alert(7)\" aria-label=\"This is a link\" data-foo=\"bar\">Click me</a>',\n        '  <span>Some content</span>',\n        '</div>'\n      ].join('')\n\n      const myDefaultAllowList = DefaultAllowlist\n      // With the default allow list\n      let result = sanitizeHtml(template, myDefaultAllowList, null)\n\n      // `data-foo` won't be present\n      expect(result).not.toContain('data-foo=\"bar\"')\n\n      // Add the following regex too\n      myDefaultAllowList['*'].push(/^data-foo/)\n\n      result = sanitizeHtml(template, myDefaultAllowList, null)\n\n      expect(result).not.toContain('href=\"javascript:alert(7)') // This is in the default list\n      expect(result).toContain('aria-label=\"This is a link\"') // This is in the default list\n      expect(result).toContain('data-foo=\"bar\"') // We explicitly allow this\n    })\n\n    it('should allow aria attributes and safe attributes', () => {\n      const template = [\n        '<div aria-pressed=\"true\">',\n        '  <span class=\"test\">Some content</span>',\n        '</div>'\n      ].join('')\n\n      const result = sanitizeHtml(template, DefaultAllowlist, null)\n\n      expect(result).toContain('aria-pressed')\n      expect(result).toContain('class=\"test\"')\n    })\n\n    it('should remove tags not in allowlist', () => {\n      const template = [\n        '<div>',\n        '  <script>alert(7)</script>',\n        '</div>'\n      ].join('')\n\n      const result = sanitizeHtml(template, DefaultAllowlist, null)\n\n      expect(result).not.toContain('<script>')\n    })\n\n    it('should not use native api to sanitize if a custom function passed', () => {\n      const template = [\n        '<div>',\n        '  <span>Some content</span>',\n        '</div>'\n      ].join('')\n\n      function mySanitize(htmlUnsafe) {\n        return htmlUnsafe\n      }\n\n      const spy = spyOn(DOMParser.prototype, 'parseFromString')\n\n      const result = sanitizeHtml(template, DefaultAllowlist, mySanitize)\n\n      expect(result).toEqual(template)\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should allow multiple sanitation passes of the same template', () => {\n      const template = '<img src=\"test.jpg\">'\n\n      const firstResult = sanitizeHtml(template, DefaultAllowlist, null)\n      const secondResult = sanitizeHtml(template, DefaultAllowlist, null)\n\n      expect(firstResult).toContain('src')\n      expect(secondResult).toContain('src')\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/scrollbar.spec.js",
    "content": "import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture'\nimport Manipulator from '../../../src/dom/manipulator'\nimport ScrollBarHelper from '../../../src/util/scrollbar'\n\ndescribe('ScrollBar', () => {\n  let fixtureEl\n  const doc = document.documentElement\n  const parseIntDecimal = arg => Number.parseInt(arg, 10)\n  const getPaddingX = el => parseIntDecimal(window.getComputedStyle(el).paddingRight)\n  const getMarginX = el => parseIntDecimal(window.getComputedStyle(el).marginRight)\n  const getOverFlow = el => el.style.overflow\n  const getPaddingAttr = el => Manipulator.getDataAttribute(el, 'padding-right')\n  const getMarginAttr = el => Manipulator.getDataAttribute(el, 'margin-right')\n  const getOverFlowAttr = el => Manipulator.getDataAttribute(el, 'overflow')\n  const windowCalculations = () => {\n    return {\n      htmlClient: document.documentElement.clientWidth,\n      htmlOffset: document.documentElement.offsetWidth,\n      docClient: document.body.clientWidth,\n      htmlBound: document.documentElement.getBoundingClientRect().width,\n      bodyBound: document.body.getBoundingClientRect().width,\n      window: window.innerWidth,\n      width: Math.abs(window.innerWidth - document.documentElement.clientWidth)\n    }\n  }\n\n  // iOS, Android devices and macOS browsers hide scrollbar by default and show it only while scrolling.\n  // So the tests for scrollbar would fail\n  const isScrollBarHidden = () => {\n    const calc = windowCalculations()\n    return calc.htmlClient === calc.htmlOffset && calc.htmlClient === calc.window\n  }\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n    // custom fixture to avoid extreme style values\n    fixtureEl.removeAttribute('style')\n  })\n\n  afterAll(() => {\n    fixtureEl.remove()\n  })\n\n  afterEach(() => {\n    clearFixture()\n    clearBodyAndDocument()\n  })\n\n  beforeEach(() => {\n    clearBodyAndDocument()\n  })\n\n  describe('isBodyOverflowing', () => {\n    it('should return true if body is overflowing', () => {\n      document.documentElement.style.overflowY = 'scroll'\n      document.body.style.overflowY = 'scroll'\n      fixtureEl.innerHTML = '<div style=\"height: 110vh; width: 100%\"></div>'\n      const result = new ScrollBarHelper().isOverflowing()\n\n      if (isScrollBarHidden()) {\n        expect(result).toBeFalse()\n      } else {\n        expect(result).toBeTrue()\n      }\n    })\n\n    it('should return false if body is not overflowing', () => {\n      doc.style.overflowY = 'hidden'\n      document.body.style.overflowY = 'hidden'\n      fixtureEl.innerHTML = '<div style=\"height: 110vh; width: 100%\"></div>'\n      const scrollBar = new ScrollBarHelper()\n      const result = scrollBar.isOverflowing()\n\n      expect(result).toBeFalse()\n    })\n  })\n\n  describe('getWidth', () => {\n    it('should return an integer greater than zero, if body is overflowing', () => {\n      doc.style.overflowY = 'scroll'\n      document.body.style.overflowY = 'scroll'\n      fixtureEl.innerHTML = '<div style=\"height: 110vh; width: 100%\"></div>'\n      const result = new ScrollBarHelper().getWidth()\n\n      if (isScrollBarHidden()) {\n        expect(result).toEqual(0)\n      } else {\n        expect(result).toBeGreaterThan(1)\n      }\n    })\n\n    it('should return 0 if body is not overflowing', () => {\n      document.documentElement.style.overflowY = 'hidden'\n      document.body.style.overflowY = 'hidden'\n      fixtureEl.innerHTML = '<div style=\"height: 110vh; width: 100%\"></div>'\n\n      const result = new ScrollBarHelper().getWidth()\n\n      expect(result).toEqual(0)\n    })\n  })\n\n  describe('hide - reset', () => {\n    it('should adjust the inline padding of fixed elements which are full-width', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"height: 110vh; width: 100%\">',\n        '  <div class=\"fixed-top\" id=\"fixed1\" style=\"padding-right: 0px; width: 100vw\"></div>',\n        '  <div class=\"fixed-top\" id=\"fixed2\" style=\"padding-right: 5px; width: 100vw\"></div>',\n        '</div>'\n      ].join('')\n      doc.style.overflowY = 'scroll'\n\n      const fixedEl = fixtureEl.querySelector('#fixed1')\n      const fixedEl2 = fixtureEl.querySelector('#fixed2')\n      const originalPadding = getPaddingX(fixedEl)\n      const originalPadding2 = getPaddingX(fixedEl2)\n      const scrollBar = new ScrollBarHelper()\n      const expectedPadding = originalPadding + scrollBar.getWidth()\n      const expectedPadding2 = originalPadding2 + scrollBar.getWidth()\n\n      scrollBar.hide()\n\n      let currentPadding = getPaddingX(fixedEl)\n      let currentPadding2 = getPaddingX(fixedEl2)\n      expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`)\n      expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`)\n      expect(currentPadding).toEqual(expectedPadding)\n      expect(currentPadding2).toEqual(expectedPadding2)\n\n      scrollBar.reset()\n      currentPadding = getPaddingX(fixedEl)\n      currentPadding2 = getPaddingX(fixedEl2)\n      expect(getPaddingAttr(fixedEl)).toBeNull()\n      expect(getPaddingAttr(fixedEl2)).toBeNull()\n      expect(currentPadding).toEqual(originalPadding)\n      expect(currentPadding2).toEqual(originalPadding2)\n    })\n\n    it('should remove padding & margin if not existed before adjustment', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"height: 110vh; width: 100%\">',\n        '  <div class=\"fixed\" id=\"fixed\" style=\"width: 100vw;\"></div>',\n        '  <div class=\"sticky-top\" id=\"sticky\" style=\" width: 100vw;\"></div>',\n        '</div>'\n      ].join('')\n      doc.style.overflowY = 'scroll'\n\n      const fixedEl = fixtureEl.querySelector('#fixed')\n      const stickyEl = fixtureEl.querySelector('#sticky')\n      const scrollBar = new ScrollBarHelper()\n\n      scrollBar.hide()\n      scrollBar.reset()\n\n      expect(fixedEl.getAttribute('style').includes('padding-right')).toBeFalse()\n      expect(stickyEl.getAttribute('style').includes('margin-right')).toBeFalse()\n    })\n\n    it('should adjust the inline margin and padding of sticky elements', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"height: 110vh\">',\n        '  <div class=\"sticky-top\" style=\"margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px\"></div>',\n        '</div>'\n      ].join('')\n      doc.style.overflowY = 'scroll'\n\n      const stickyTopEl = fixtureEl.querySelector('.sticky-top')\n      const originalMargin = getMarginX(stickyTopEl)\n      const originalPadding = getPaddingX(stickyTopEl)\n      const scrollBar = new ScrollBarHelper()\n      const expectedMargin = originalMargin - scrollBar.getWidth()\n      const expectedPadding = originalPadding + scrollBar.getWidth()\n      scrollBar.hide()\n\n      expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`)\n      expect(getMarginX(stickyTopEl)).toEqual(expectedMargin)\n      expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`)\n      expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding)\n\n      scrollBar.reset()\n      expect(getMarginAttr(stickyTopEl)).toBeNull()\n      expect(getMarginX(stickyTopEl)).toEqual(originalMargin)\n      expect(getPaddingAttr(stickyTopEl)).toBeNull()\n      expect(getPaddingX(stickyTopEl)).toEqual(originalPadding)\n    })\n\n    it('should not adjust the inline margin and padding of sticky and fixed elements when element do not have full width', () => {\n      fixtureEl.innerHTML = '<div class=\"sticky-top\" style=\"margin-right: 0px; padding-right: 0px; width: 50vw\"></div>'\n\n      const stickyTopEl = fixtureEl.querySelector('.sticky-top')\n      const originalMargin = getMarginX(stickyTopEl)\n      const originalPadding = getPaddingX(stickyTopEl)\n\n      const scrollBar = new ScrollBarHelper()\n      scrollBar.hide()\n\n      const currentMargin = getMarginX(stickyTopEl)\n      const currentPadding = getPaddingX(stickyTopEl)\n\n      expect(currentMargin).toEqual(originalMargin)\n      expect(currentPadding).toEqual(originalPadding)\n\n      scrollBar.reset()\n    })\n\n    it('should not put data-attribute if element doesn\\'t have the proper style property, should just remove style property if element didn\\'t had one', () => {\n      fixtureEl.innerHTML = [\n        '<div style=\"height: 110vh; width: 100%\">',\n        '  <div class=\"sticky-top\" id=\"sticky\" style=\"width: 100vw\"></div>',\n        '</div>'\n      ].join('')\n\n      document.body.style.overflowY = 'scroll'\n      const scrollBar = new ScrollBarHelper()\n\n      const hasPaddingAttr = el => el.hasAttribute('data-bs-padding-right')\n      const hasMarginAttr = el => el.hasAttribute('data-bs-margin-right')\n      const stickyEl = fixtureEl.querySelector('#sticky')\n      const originalPadding = getPaddingX(stickyEl)\n      const originalMargin = getMarginX(stickyEl)\n      const scrollBarWidth = scrollBar.getWidth()\n\n      scrollBar.hide()\n\n      expect(getPaddingX(stickyEl)).toEqual(scrollBarWidth + originalPadding)\n      const expectedMargin = scrollBarWidth + originalMargin\n      expect(getMarginX(stickyEl)).toEqual(expectedMargin === 0 ? expectedMargin : -expectedMargin)\n      expect(hasMarginAttr(stickyEl)).toBeFalse() // We do not have to keep css margin\n      expect(hasPaddingAttr(stickyEl)).toBeFalse() // We do not have to keep css padding\n\n      scrollBar.reset()\n\n      expect(getPaddingX(stickyEl)).toEqual(originalPadding)\n      expect(getPaddingX(stickyEl)).toEqual(originalPadding)\n    })\n\n    describe('Body Handling', () => {\n      it('should ignore other inline styles when trying to restore body defaults ', () => {\n        document.body.style.color = 'red'\n\n        const scrollBar = new ScrollBarHelper()\n        const scrollBarWidth = scrollBar.getWidth()\n        scrollBar.hide()\n\n        expect(getPaddingX(document.body)).toEqual(scrollBarWidth)\n        expect(document.body.style.color).toEqual('red')\n\n        scrollBar.reset()\n      })\n\n      it('should hide scrollbar and reset it to its initial value', () => {\n        const styleSheetPadding = '7px'\n        fixtureEl.innerHTML = [\n          '<style>',\n          '  body {',\n          `    padding-right: ${styleSheetPadding}`,\n          '  }',\n          '</style>'\n        ].join('')\n\n        const el = document.body\n        const inlineStylePadding = '10px'\n        el.style.paddingRight = inlineStylePadding\n\n        const originalPadding = getPaddingX(el)\n        expect(originalPadding).toEqual(parseIntDecimal(inlineStylePadding)) // Respect only the inline style as it has prevails this of css\n        const originalOverFlow = 'auto'\n        el.style.overflow = originalOverFlow\n        const scrollBar = new ScrollBarHelper()\n        const scrollBarWidth = scrollBar.getWidth()\n\n        scrollBar.hide()\n\n        const currentPadding = getPaddingX(el)\n\n        expect(currentPadding).toEqual(scrollBarWidth + originalPadding)\n        expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(inlineStylePadding))\n        expect(getPaddingAttr(el)).toEqual(inlineStylePadding)\n        expect(getOverFlow(el)).toEqual('hidden')\n        expect(getOverFlowAttr(el)).toEqual(originalOverFlow)\n\n        scrollBar.reset()\n\n        const currentPadding1 = getPaddingX(el)\n        expect(currentPadding1).toEqual(originalPadding)\n        expect(getPaddingAttr(el)).toBeNull()\n        expect(getOverFlow(el)).toEqual(originalOverFlow)\n        expect(getOverFlowAttr(el)).toBeNull()\n      })\n\n      it('should hide scrollbar and reset it to its initial value - respecting css rules', () => {\n        const styleSheetPadding = '7px'\n        fixtureEl.innerHTML = [\n          '<style>',\n          '  body {',\n          `    padding-right: ${styleSheetPadding}`,\n          '  }',\n          '</style>'\n        ].join('')\n        const el = document.body\n        const originalPadding = getPaddingX(el)\n        const originalOverFlow = 'scroll'\n        el.style.overflow = originalOverFlow\n        const scrollBar = new ScrollBarHelper()\n        const scrollBarWidth = scrollBar.getWidth()\n\n        scrollBar.hide()\n\n        const currentPadding = getPaddingX(el)\n\n        expect(currentPadding).toEqual(scrollBarWidth + originalPadding)\n        expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(styleSheetPadding))\n        expect(getPaddingAttr(el)).toBeNull() // We do not have to keep css padding\n        expect(getOverFlow(el)).toEqual('hidden')\n        expect(getOverFlowAttr(el)).toEqual(originalOverFlow)\n\n        scrollBar.reset()\n\n        const currentPadding1 = getPaddingX(el)\n        expect(currentPadding1).toEqual(originalPadding)\n        expect(getPaddingAttr(el)).toBeNull()\n        expect(getOverFlow(el)).toEqual(originalOverFlow)\n        expect(getOverFlowAttr(el)).toBeNull()\n      })\n\n      it('should not adjust the inline body padding when it does not overflow', () => {\n        const originalPadding = getPaddingX(document.body)\n        const scrollBar = new ScrollBarHelper()\n\n        // Hide scrollbars to prevent the body overflowing\n        doc.style.overflowY = 'hidden'\n        doc.style.paddingRight = '0px'\n\n        scrollBar.hide()\n        const currentPadding = getPaddingX(document.body)\n\n        expect(currentPadding).toEqual(originalPadding)\n        scrollBar.reset()\n      })\n\n      it('should not adjust the inline body padding when it does not overflow, even on a scaled display', () => {\n        const originalPadding = getPaddingX(document.body)\n        const scrollBar = new ScrollBarHelper()\n        // Remove body margins as would be done by Bootstrap css\n        document.body.style.margin = '0'\n\n        // Hide scrollbars to prevent the body overflowing\n        doc.style.overflowY = 'hidden'\n\n        // Simulate a discrepancy between exact, i.e. floating point body width, and rounded body width\n        // as it can occur when zooming or scaling the display to something else than 100%\n        doc.style.paddingRight = '.48px'\n        scrollBar.hide()\n\n        const currentPadding = getPaddingX(document.body)\n\n        expect(currentPadding).toEqual(originalPadding)\n\n        scrollBar.reset()\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/swipe.spec.js",
    "content": "import { clearFixture, getFixture } from '../../helpers/fixture'\nimport EventHandler from '../../../src/dom/event-handler'\nimport Swipe from '../../../src/util/swipe'\nimport { noop } from '../../../src/util'\n\ndescribe('Swipe', () => {\n  const { Simulator, PointerEvent } = window\n  const originWinPointerEvent = PointerEvent\n  const supportPointerEvent = Boolean(PointerEvent)\n\n  let fixtureEl\n  let swipeEl\n  const clearPointerEvents = () => {\n    window.PointerEvent = null\n  }\n\n  const restorePointerEvents = () => {\n    window.PointerEvent = originWinPointerEvent\n  }\n\n  // The headless browser does not support touch events, so we need to fake it\n  // in order to test that touch events are added properly\n  const defineDocumentElementOntouchstart = () => {\n    document.documentElement.ontouchstart = noop\n  }\n\n  const deleteDocumentElementOntouchstart = () => {\n    delete document.documentElement.ontouchstart\n  }\n\n  const mockSwipeGesture = (element, options = {}, type = 'touch') => {\n    Simulator.setType(type)\n    const _options = { deltaX: 0, deltaY: 0, ...options }\n\n    Simulator.gestures.swipe(element, _options)\n  }\n\n  beforeEach(() => {\n    fixtureEl = getFixture()\n    const cssStyle = [\n      '<style>',\n      '  #fixture .pointer-event {',\n      '    touch-action: pan-y;',\n      '  }',\n      '  #fixture div {',\n      '    width: 300px;',\n      '    height: 300px;',\n      '  }',\n      '</style>'\n    ].join('')\n\n    fixtureEl.innerHTML = `<div id=\"swipeEl\"></div>${cssStyle}`\n    swipeEl = fixtureEl.querySelector('div')\n  })\n\n  afterEach(() => {\n    clearFixture()\n    deleteDocumentElementOntouchstart()\n  })\n\n  describe('constructor', () => {\n    it('should add touch event listeners by default', () => {\n      defineDocumentElementOntouchstart()\n\n      spyOn(Swipe.prototype, '_initEvents').and.callThrough()\n      const swipe = new Swipe(swipeEl)\n      expect(swipe._initEvents).toHaveBeenCalled()\n    })\n\n    it('should not add touch event listeners if touch is not supported', () => {\n      spyOn(Swipe, 'isSupported').and.returnValue(false)\n\n      spyOn(Swipe.prototype, '_initEvents').and.callThrough()\n      const swipe = new Swipe(swipeEl)\n\n      expect(swipe._initEvents).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('Config', () => {\n    it('Test leftCallback', () => {\n      return new Promise(resolve => {\n        const spyRight = jasmine.createSpy('spy')\n        clearPointerEvents()\n        defineDocumentElementOntouchstart()\n        // eslint-disable-next-line no-new\n        new Swipe(swipeEl, {\n          leftCallback() {\n            expect(spyRight).not.toHaveBeenCalled()\n            restorePointerEvents()\n            resolve()\n          },\n          rightCallback: spyRight\n        })\n\n        mockSwipeGesture(swipeEl, {\n          pos: [300, 10],\n          deltaX: -300\n        })\n      })\n    })\n\n    it('Test rightCallback', () => {\n      return new Promise(resolve => {\n        const spyLeft = jasmine.createSpy('spy')\n        clearPointerEvents()\n        defineDocumentElementOntouchstart()\n        // eslint-disable-next-line no-new\n        new Swipe(swipeEl, {\n          rightCallback() {\n            expect(spyLeft).not.toHaveBeenCalled()\n            restorePointerEvents()\n            resolve()\n          },\n          leftCallback: spyLeft\n        })\n\n        mockSwipeGesture(swipeEl, {\n          pos: [10, 10],\n          deltaX: 300\n        })\n      })\n    })\n\n    it('Test endCallback', () => {\n      return new Promise(resolve => {\n        clearPointerEvents()\n        defineDocumentElementOntouchstart()\n        let isFirstTime = true\n\n        const callback = () => {\n          if (isFirstTime) {\n            isFirstTime = false\n            return\n          }\n\n          expect().nothing()\n          restorePointerEvents()\n          resolve()\n        }\n\n        // eslint-disable-next-line no-new\n        new Swipe(swipeEl, {\n          endCallback: callback\n        })\n        mockSwipeGesture(swipeEl, {\n          pos: [10, 10],\n          deltaX: 300\n        })\n\n        mockSwipeGesture(swipeEl, {\n          pos: [300, 10],\n          deltaX: -300\n        })\n      })\n    })\n  })\n\n  describe('Functionality on PointerEvents', () => {\n    it('should not allow pinch with touch events', () => {\n      Simulator.setType('touch')\n      clearPointerEvents()\n      deleteDocumentElementOntouchstart()\n\n      const swipe = new Swipe(swipeEl)\n      const spy = spyOn(swipe, '_handleSwipe')\n\n      mockSwipeGesture(swipeEl, {\n        pos: [300, 10],\n        deltaX: -300,\n        deltaY: 0,\n        touches: 2\n      })\n\n      restorePointerEvents()\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should allow swipeRight and call \"rightCallback\" with pointer events', () => {\n      return new Promise(resolve => {\n        if (!supportPointerEvent) {\n          expect().nothing()\n          resolve()\n          return\n        }\n\n        const style = '#fixture .pointer-event { touch-action: none !important; }'\n        fixtureEl.innerHTML += style\n\n        defineDocumentElementOntouchstart()\n        // eslint-disable-next-line no-new\n        new Swipe(swipeEl, {\n          rightCallback() {\n            deleteDocumentElementOntouchstart()\n            expect().nothing()\n            resolve()\n          }\n        })\n\n        mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer')\n      })\n    })\n\n    it('should allow swipeLeft and call \"leftCallback\" with pointer events', () => {\n      return new Promise(resolve => {\n        if (!supportPointerEvent) {\n          expect().nothing()\n          resolve()\n          return\n        }\n\n        const style = '#fixture .pointer-event { touch-action: none !important; }'\n        fixtureEl.innerHTML += style\n\n        defineDocumentElementOntouchstart()\n        // eslint-disable-next-line no-new\n        new Swipe(swipeEl, {\n          leftCallback() {\n            expect().nothing()\n            deleteDocumentElementOntouchstart()\n            resolve()\n          }\n        })\n\n        mockSwipeGesture(swipeEl, {\n          pos: [300, 10],\n          deltaX: -300\n        }, 'pointer')\n      })\n    })\n  })\n\n  describe('Dispose', () => {\n    it('should call EventHandler.off', () => {\n      defineDocumentElementOntouchstart()\n      spyOn(EventHandler, 'off').and.callThrough()\n      const swipe = new Swipe(swipeEl)\n\n      swipe.dispose()\n      expect(EventHandler.off).toHaveBeenCalledWith(swipeEl, '.bs.swipe')\n    })\n\n    it('should destroy', () => {\n      const addEventSpy = spyOn(fixtureEl, 'addEventListener').and.callThrough()\n      const removeEventSpy = spyOn(EventHandler, 'off').and.callThrough()\n      defineDocumentElementOntouchstart()\n\n      const swipe = new Swipe(fixtureEl)\n\n      const expectedArgs =\n        swipe._supportPointerEvents ?\n          [\n            ['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],\n            ['pointerup', jasmine.any(Function), jasmine.any(Boolean)]\n          ] :\n          [\n            ['touchstart', jasmine.any(Function), jasmine.any(Boolean)],\n            ['touchmove', jasmine.any(Function), jasmine.any(Boolean)],\n            ['touchend', jasmine.any(Function), jasmine.any(Boolean)]\n          ]\n\n      expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)\n\n      swipe.dispose()\n\n      expect(removeEventSpy).toHaveBeenCalledWith(fixtureEl, '.bs.swipe')\n      deleteDocumentElementOntouchstart()\n    })\n  })\n\n  describe('\"isSupported\" static', () => {\n    it('should return \"true\" if \"touchstart\" exists in document element)', () => {\n      Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)\n      defineDocumentElementOntouchstart()\n\n      expect(Swipe.isSupported()).toBeTrue()\n    })\n\n    it('should return \"false\" if \"touchstart\" not exists in document element and \"navigator.maxTouchPoints\" are zero (0)', () => {\n      Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)\n      deleteDocumentElementOntouchstart()\n\n      if ('ontouchstart' in document.documentElement) {\n        expect().nothing()\n        return\n      }\n\n      expect(Swipe.isSupported()).toBeFalse()\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/unit/util/template-factory.spec.js",
    "content": "import { clearFixture, getFixture } from '../../helpers/fixture'\nimport TemplateFactory from '../../../src/util/template-factory'\n\ndescribe('TemplateFactory', () => {\n  let fixtureEl\n\n  beforeAll(() => {\n    fixtureEl = getFixture()\n  })\n\n  afterEach(() => {\n    clearFixture()\n  })\n\n  describe('NAME', () => {\n    it('should return plugin NAME', () => {\n      expect(TemplateFactory.NAME).toEqual('TemplateFactory')\n    })\n  })\n\n  describe('Default', () => {\n    it('should return plugin default config', () => {\n      expect(TemplateFactory.Default).toEqual(jasmine.any(Object))\n    })\n  })\n\n  describe('toHtml', () => {\n    describe('Sanitization', () => {\n      it('should use \"sanitizeHtml\" to sanitize template', () => {\n        const factory = new TemplateFactory({\n          sanitize: true,\n          template: '<div><a href=\"javascript:alert(7)\">Click me</a></div>'\n        })\n        const spy = spyOn(factory, '_maybeSanitize').and.callThrough()\n\n        expect(factory.toHtml().innerHTML).not.toContain('href=\"javascript:alert(7)')\n        expect(spy).toHaveBeenCalled()\n      })\n\n      it('should not sanitize template', () => {\n        const factory = new TemplateFactory({\n          sanitize: false,\n          template: '<div><a href=\"javascript:alert(7)\">Click me</a></div>'\n        })\n        const spy = spyOn(factory, '_maybeSanitize').and.callThrough()\n\n        expect(factory.toHtml().innerHTML).toContain('href=\"javascript:alert(7)')\n        expect(spy).toHaveBeenCalled()\n      })\n\n      it('should use \"sanitizeHtml\" to sanitize content', () => {\n        const factory = new TemplateFactory({\n          sanitize: true,\n          html: true,\n          template: '<div id=\"foo\"></div>',\n          content: { '#foo': '<a href=\"javascript:alert(7)\">Click me</a>' }\n        })\n        expect(factory.toHtml().innerHTML).not.toContain('href=\"javascript:alert(7)')\n      })\n\n      it('should not sanitize content', () => {\n        const factory = new TemplateFactory({\n          sanitize: false,\n          html: true,\n          template: '<div id=\"foo\"></div>',\n          content: { '#foo': '<a href=\"javascript:alert(7)\">Click me</a>' }\n        })\n        expect(factory.toHtml().innerHTML).toContain('href=\"javascript:alert(7)')\n      })\n\n      it('should sanitize content only if \"config.html\" is enabled', () => {\n        const factory = new TemplateFactory({\n          sanitize: true,\n          html: false,\n          template: '<div id=\"foo\"></div>',\n          content: { '#foo': '<a href=\"javascript:alert(7)\">Click me</a>' }\n        })\n        const spy = spyOn(factory, '_maybeSanitize').and.callThrough()\n\n        expect(spy).not.toHaveBeenCalled()\n      })\n    })\n\n    describe('Extra Class', () => {\n      it('should add extra class', () => {\n        const factory = new TemplateFactory({\n          extraClass: 'testClass'\n        })\n        expect(factory.toHtml()).toHaveClass('testClass')\n      })\n\n      it('should add extra classes', () => {\n        const factory = new TemplateFactory({\n          extraClass: 'testClass testClass2'\n        })\n        expect(factory.toHtml()).toHaveClass('testClass')\n        expect(factory.toHtml()).toHaveClass('testClass2')\n      })\n\n      it('should resolve class if function is given', () => {\n        const factory = new TemplateFactory({\n          extraClass(arg) {\n            expect(arg).toEqual(factory)\n            return 'testClass'\n          }\n        })\n\n        expect(factory.toHtml()).toHaveClass('testClass')\n      })\n    })\n  })\n\n  describe('Content', () => {\n    it('add simple text content', () => {\n      const template = [\n        '<div>',\n        '  <div class=\"foo\"></div>',\n        '  <div class=\"foo2\"></div>',\n        '</div>'\n      ].join('')\n\n      const factory = new TemplateFactory({\n        template,\n        content: {\n          '.foo': 'bar',\n          '.foo2': 'bar2'\n        }\n      })\n\n      const html = factory.toHtml()\n      expect(html.querySelector('.foo').textContent).toEqual('bar')\n      expect(html.querySelector('.foo2').textContent).toEqual('bar2')\n    })\n\n    it('should not fill template if selector not exists', () => {\n      const factory = new TemplateFactory({\n        sanitize: true,\n        html: true,\n        template: '<div id=\"foo\"></div>',\n        content: { '#bar': 'test' }\n      })\n\n      expect(factory.toHtml().outerHTML).toEqual('<div id=\"foo\"></div>')\n    })\n\n    it('should remove template selector, if content is null', () => {\n      const factory = new TemplateFactory({\n        sanitize: true,\n        html: true,\n        template: '<div><div id=\"foo\"></div></div>',\n        content: { '#foo': null }\n      })\n\n      expect(factory.toHtml().outerHTML).toEqual('<div></div>')\n    })\n\n    it('should resolve content if is function', () => {\n      const factory = new TemplateFactory({\n        sanitize: true,\n        html: true,\n        template: '<div><div id=\"foo\"></div></div>',\n        content: { '#foo': () => null }\n      })\n\n      expect(factory.toHtml().outerHTML).toEqual('<div></div>')\n    })\n\n    it('if content is element and \"config.html=false\", should put content\\'s textContent', () => {\n      fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'\n      const contentElement = fixtureEl.querySelector('div')\n\n      const factory = new TemplateFactory({\n        html: false,\n        template: '<div><div id=\"foo\"></div></div>',\n        content: { '#foo': contentElement }\n      })\n\n      const fooEl = factory.toHtml().querySelector('#foo')\n      expect(fooEl.innerHTML).not.toEqual(contentElement.innerHTML)\n      expect(fooEl.textContent).toEqual(contentElement.textContent)\n      expect(fooEl.textContent).toEqual('foobar')\n    })\n\n    it('if content is element and \"config.html=true\", should put content\\'s outerHtml as child', () => {\n      fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'\n      const contentElement = fixtureEl.querySelector('div')\n\n      const factory = new TemplateFactory({\n        html: true,\n        template: '<div><div id=\"foo\"></div></div>',\n        content: { '#foo': contentElement }\n      })\n\n      const fooEl = factory.toHtml().querySelector('#foo')\n      expect(fooEl.innerHTML).toEqual(contentElement.outerHTML)\n      expect(fooEl.textContent).toEqual(contentElement.textContent)\n    })\n  })\n\n  describe('getContent', () => {\n    it('should get content as array', () => {\n      const factory = new TemplateFactory({\n        content: {\n          '.foo': 'bar',\n          '.foo2': 'bar2'\n        }\n      })\n      expect(factory.getContent()).toEqual(['bar', 'bar2'])\n    })\n\n    it('should filter empties', () => {\n      const factory = new TemplateFactory({\n        content: {\n          '.foo': 'bar',\n          '.foo2': '',\n          '.foo3': null,\n          '.foo4': () => 2,\n          '.foo5': () => null\n        }\n      })\n      expect(factory.getContent()).toEqual(['bar', 2])\n    })\n  })\n\n  describe('hasContent', () => {\n    it('should return true, if it has', () => {\n      const factory = new TemplateFactory({\n        content: {\n          '.foo': 'bar',\n          '.foo2': 'bar2',\n          '.foo3': ''\n        }\n      })\n      expect(factory.hasContent()).toBeTrue()\n    })\n\n    it('should return false, if filtered content is empty', () => {\n      const factory = new TemplateFactory({\n        content: {\n          '.foo2': '',\n          '.foo3': null,\n          '.foo4': () => null\n        }\n      })\n      expect(factory.hasContent()).toBeFalse()\n    })\n  })\n\n  describe('changeContent', () => {\n    it('should change Content', () => {\n      const template = [\n        '<div>',\n        '  <div class=\"foo\"></div>',\n        '  <div class=\"foo2\"></div>',\n        '</div>'\n      ].join('')\n\n      const factory = new TemplateFactory({\n        template,\n        content: {\n          '.foo': 'bar',\n          '.foo2': 'bar2'\n        }\n      })\n\n      const html = selector => factory.toHtml().querySelector(selector).textContent\n      expect(html('.foo')).toEqual('bar')\n      expect(html('.foo2')).toEqual('bar2')\n      factory.changeContent({\n        '.foo': 'test',\n        '.foo2': 'test2'\n      })\n\n      expect(html('.foo')).toEqual('test')\n      expect(html('.foo2')).toEqual('test2')\n    })\n\n    it('should change only the given, content', () => {\n      const template = [\n        '<div>',\n        '  <div class=\"foo\"></div>',\n        '  <div class=\"foo2\"></div>',\n        '</div>'\n      ].join('')\n\n      const factory = new TemplateFactory({\n        template,\n        content: {\n          '.foo': 'bar',\n          '.foo2': 'bar2'\n        }\n      })\n\n      const html = selector => factory.toHtml().querySelector(selector).textContent\n      expect(html('.foo')).toEqual('bar')\n      expect(html('.foo2')).toEqual('bar2')\n      factory.changeContent({\n        '.foo': 'test',\n        '.wrong': 'wrong'\n      })\n\n      expect(html('.foo')).toEqual('test')\n      expect(html('.foo2')).toEqual('bar2')\n    })\n  })\n})\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/.eslintrc.json",
    "content": "{\n  \"plugins\": [\n    \"html\"\n  ],\n  \"extends\": \"../../../.eslintrc.json\",\n  \"parserOptions\": {\n    \"sourceType\": \"module\"\n  },\n  \"settings\": {\n    \"html/html-extensions\": [\n      \".html\"\n    ]\n  },\n  \"rules\": {\n    \"no-console\": \"off\",\n    \"no-new\": \"off\",\n    \"unicorn/no-array-for-each\": \"off\"\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/alert.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Alert</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Alert <small>Bootstrap Visual Test</small></h1>\n\n      <div class=\"alert alert-warning alert-dismissible fade show\" role=\"alert\">\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n        <strong>Holy guacamole!</strong> You should check in on some of those fields below.\n      </div>\n\n      <div class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n        <p>\n          <strong>Oh snap!</strong> <a href=\"#\" class=\"alert-link\">Change a few things up</a> and try submitting again.\n        </p>\n        <p>\n          <button type=\"button\" class=\"btn btn-danger\">Danger</button>\n          <button type=\"button\" class=\"btn btn-secondary\">Secondary</button>\n        </p>\n      </div>\n\n      <div class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n        <p>\n          <strong>Oh snap!</strong> <a href=\"#\" class=\"alert-link\">Change a few things up</a> and try submitting again. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum.\n        </p>\n        <p>\n          <button type=\"button\" class=\"btn btn-danger\">Take this action</button>\n          <button type=\"button\" class=\"btn btn-primary\">Or do this</button>\n        </p>\n      </div>\n\n      <div class=\"alert alert-warning alert-dismissible fade show\" role=\"alert\" style=\"transition-duration: 5s;\">\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n        This alert will take 5 seconds to fade out.\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/button.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Button</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Button <small>Bootstrap Visual Test</small></h1>\n\n      <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"button\" aria-pressed=\"false\" autocomplete=\"off\">\n        Single toggle\n      </button>\n\n      <p>For checkboxes and radio buttons, ensure that keyboard behavior is functioning correctly.</p>\n      <p>Navigate to the checkboxes with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>), and ensure that <kbd>SPACE</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>SPACE</kbd> toggles the checkbox again.</p>\n\n      <div class=\"btn-group\" data-bs-toggle=\"buttons\">\n        <label class=\"btn btn-primary active\">\n          <input type=\"checkbox\" checked autocomplete=\"off\"> Checkbox 1 (pre-checked)\n        </label>\n        <label class=\"btn btn-primary\">\n          <input type=\"checkbox\" autocomplete=\"off\"> Checkbox 2\n        </label>\n        <label class=\"btn btn-primary\">\n          <input type=\"checkbox\" autocomplete=\"off\"> Checkbox 3\n        </label>\n      </div>\n\n      <p>Navigate to the radio button group with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated \"forward\" to the group with <kbd>TAB</kbd> or \"backwards\" using <kbd>SHIFT + TAB</kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time.  Ensure that the selected radio button can be changed by using the <kbd>←</kbd> and <kbd>→</kbd> arrow keys. Click on one of the radio buttons with the mouse,  ensure that focus was correctly set on the actual radio button, and that <kbd>←</kbd> and <kbd>→</kbd> change the selected radio button again.</p>\n\n      <div class=\"btn-group\" data-bs-toggle=\"buttons\">\n        <label class=\"btn btn-primary active\">\n          <input type=\"radio\" name=\"options\" id=\"option1\" autocomplete=\"off\" checked> Radio 1 (preselected)\n        </label>\n        <label class=\"btn btn-primary\">\n          <input type=\"radio\" name=\"options\" id=\"option2\" autocomplete=\"off\"> Radio 2\n        </label>\n        <label class=\"btn btn-primary\">\n          <input type=\"radio\" name=\"options\" id=\"option3\" autocomplete=\"off\"> Radio 3\n        </label>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/carousel.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Carousel</title>\n    <style>\n      .carousel-item {\n        transition: transform 2s ease, opacity .5s ease;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Carousel <small>Bootstrap Visual Test</small></h1>\n\n      <p>The transition duration should be around 2s. Also, the carousel shouldn't slide when its window/tab is hidden. Check the console log.</p>\n\n      <div id=\"carousel-example-generic\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n        <div class=\"carousel-indicators\">\n          <button type=\"button\" data-bs-target=\"#carousel-example-generic\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n          <button type=\"button\" data-bs-target=\"#carousel-example-generic\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n          <button type=\"button\" data-bs-target=\"#carousel-example-generic\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n        </div>\n        <div class=\"carousel-inner\">\n          <div class=\"carousel-item active\">\n            <img src=\"https://i.imgur.com/iEZgY7Y.jpg\" alt=\"First slide\">\n          </div>\n          <div class=\"carousel-item\">\n            <img src=\"https://i.imgur.com/eNWn1Xs.jpg\" alt=\"Second slide\">\n          </div>\n          <div class=\"carousel-item\">\n            <img src=\"https://i.imgur.com/Nm7xoti.jpg\" alt=\"Third slide\">\n          </div>\n        </div>\n        <button class=\"carousel-control-prev\" data-bs-target=\"#carousel-example-generic\" type=\"button\" data-bs-slide=\"prev\">\n          <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n          <span class=\"visually-hidden\">Previous</span>\n        </button>\n        <button class=\"carousel-control-next\" data-bs-target=\"#carousel-example-generic\" type=\"button\" data-bs-slide=\"next\">\n          <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n          <span class=\"visually-hidden\">Next</span>\n        </button>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n    <script>\n      let t0\n      let t1\n      const carousel = document.getElementById('carousel-example-generic')\n\n      // Test to show that the carousel doesn't slide when the current tab isn't visible\n      // Test to show that transition-duration can be changed with css\n      carousel.addEventListener('slid.bs.carousel', event => {\n        t1 = performance.now()\n        console.log('transition-duration took ' + (t1 - t0) + 'ms, slid at ' + event.timeStamp)\n      })\n      carousel.addEventListener('slide.bs.carousel', () => {\n        t0 = performance.now()\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/collapse.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Collapse</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Collapse <small>Bootstrap Visual Test</small></h1>\n\n      <div id=\"accordion\" role=\"tablist\">\n        <div class=\"card\" role=\"presentation\">\n          <div class=\"card-header\" role=\"tab\" id=\"headingOne\">\n            <h5 class=\"mb-0\">\n              <a data-bs-toggle=\"collapse\" href=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">\n                Collapsible Group Item #1\n              </a>\n            </h5>\n          </div>\n\n          <div id=\"collapseOne\" class=\"collapse show\" data-bs-parent=\"#accordion\" role=\"tabpanel\" aria-labelledby=\"headingOne\">\n            <div class=\"card-body\">\n              Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n            </div>\n          </div>\n        </div>\n        <div class=\"card\" role=\"presentation\">\n          <div class=\"card-header\" role=\"tab\" id=\"headingTwo\">\n            <h5 class=\"mb-0\">\n              <a class=\"collapsed\" data-bs-toggle=\"collapse\" href=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\">\n                Collapsible Group Item #2\n              </a>\n            </h5>\n          </div>\n          <div id=\"collapseTwo\" class=\"collapse\" data-bs-parent=\"#accordion\" role=\"tabpanel\" aria-labelledby=\"headingTwo\">\n            <div class=\"card-body\">\n              Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n            </div>\n          </div>\n        </div>\n        <div class=\"card\" role=\"presentation\">\n          <div class=\"card-header\" role=\"tab\" id=\"headingThree\">\n            <h5 class=\"mb-0\">\n              <a class=\"collapsed\" data-bs-toggle=\"collapse\" href=\"#collapseThree\" aria-expanded=\"false\" aria-controls=\"collapseThree\">\n                Collapsible Group Item #3\n              </a>\n            </h5>\n          </div>\n          <div id=\"collapseThree\" class=\"collapse\" data-bs-parent=\"#accordion\" role=\"tabpanel\" aria-labelledby=\"headingThree\">\n            <div class=\"card-body\">\n              Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n            </div>\n          </div>\n        </div>\n        <div class=\"card\" role=\"presentation\">\n          <div class=\"card-header\" role=\"tab\" id=\"headingFour\">\n            <h5 class=\"mb-0\">\n              <a class=\"collapsed\" data-bs-toggle=\"collapse\" href=\"#collapseFour\" aria-expanded=\"false\" aria-controls=\"collapseFour\">\n                Collapsible Group Item with XSS in data-bs-parent\n              </a>\n            </h5>\n          </div>\n          <div id=\"collapseFour\" class=\"collapse\" data-bs-parent=\"<img src=1 onerror=alert(123)>\" role=\"tabpanel\" aria-labelledby=\"headingFour\">\n            <div class=\"card-body\">\n              Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/dropdown.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Dropdown</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Dropdown <small>Bootstrap Visual Test</small></h1>\n\n      <nav class=\"navbar navbar-expand-md bg-light\">\n        <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n        <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarResponsive\" aria-controls=\"navbarResponsive\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n          <span class=\"navbar-toggler-icon\"></span>\n        </button>\n\n        <div class=\"collapse navbar-collapse\" id=\"navbarResponsive\">\n          <ul class=\"navbar-nav\">\n            <li class=\"nav-item\">\n              <a class=\"nav-link active\" href=\"#\" aria-current=\"page\">Home</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item dropdown\">\n              <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n              <ul class=\"dropdown-menu\">\n                <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              </ul>\n            </li>\n          </ul>\n        </div>\n      </nav>\n\n      <ul class=\"nav nav-pills mt-3\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" href=\"#\">Active</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item dropdown\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n      </ul>\n\n      <div class=\"row\">\n        <div class=\"col-sm-12 mt-4\">\n          <div class=\"dropdown\">\n            <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>\n            <div class=\"dropdown-menu\">\n              <input id=\"textField\" type=\"text\">\n            </div>\n          </div>\n          <div class=\"btn-group dropup\">\n            <button type=\"button\" class=\"btn btn-secondary\">Dropup split</button>\n            <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              <span class=\"visually-hidden\">Dropup split</span>\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </div>\n        </div>\n\n        <div class=\"col-sm-12 mt-4\">\n          <div class=\"btn-group dropup\">\n            <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropup</button>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </div>\n\n          <div class=\"btn-group\">\n            <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              This dropdown's menu is end-aligned\n            </button>\n            <div class=\"dropdown-menu dropdown-menu-end\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here</button>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"col-sm-12 mt-4\">\n          <div class=\"btn-group dropup\">\n            <a href=\"#\" class=\"btn btn-secondary\">Dropup split align end</a>\n            <button type=\"button\" id=\"dropdown-page-subheader-button-3\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              <span class=\"visually-hidden\">Product actions</span>\n            </button>\n            <div class=\"dropdown-menu dropdown-menu-end\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here with a long text</button>\n            </div>\n          </div>\n          <div class=\"btn-group dropup\">\n            <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropup align end</button>\n            <div class=\"dropdown-menu dropdown-menu-end\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here with a long text</button>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"col-sm-12 mt-4\">\n          <div class=\"btn-group dropend\">\n            <a href=\"#\" class=\"btn btn-secondary\">Dropend split</a>\n            <button type=\"button\" id=\"dropdown-page-subheader-button-4\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              <span class=\"visually-hidden\">Product actions</span>\n            </button>\n            <div class=\"dropdown-menu\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here with a long text</button>\n            </div>\n          </div>\n          <div class=\"btn-group dropend\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropend\n            </button>\n            <div class=\"dropdown-menu\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here</button>\n            </div>\n          </div>\n          <!-- dropstart -->\n          <div class=\"btn-group dropstart\">\n            <a href=\"#\" class=\"btn btn-secondary\">Dropstart split</a>\n            <button type=\"button\" id=\"dropdown-page-subheader-button-5\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              <span class=\"visually-hidden\">Product actions</span>\n            </button>\n            <div class=\"dropdown-menu\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here with a long text</button>\n            </div>\n          </div>\n          <div class=\"btn-group dropstart\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropstart\n            </button>\n            <div class=\"dropdown-menu\">\n              <button class=\"dropdown-item\" type=\"button\">Action</button>\n              <button class=\"dropdown-item\" type=\"button\">Another action</button>\n              <button class=\"dropdown-item\" type=\"button\">Something else here</button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"row\">\n        <div class=\"col-sm-3 mt-4\">\n          <div class=\"btn-group dropdown\">\n            <button type=\"button\" class=\"btn btn-secondary\">Dropdown reference</button>\n            <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" data-bs-reference=\"parent\">\n              <span class=\"visually-hidden\">Dropdown split</span>\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </div>\n        </div>\n        <div class=\"col-sm-3 mt-4\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\" aria-expanded=\"false\">\n              Dropdown menu without Popper\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/modal.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Modal</title>\n    <style>\n      #tall {\n        height: 1500px;\n        width: 100px;\n      }\n    </style>\n  </head>\n  <body>\n    <nav class=\"navbar navbar-full navbar-dark bg-dark\">\n      <button class=\"navbar-toggler hidden-lg-up\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarResponsive\" aria-controls=\"navbarResponsive\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"></button>\n      <div class=\"collapse navbar-expand-md\" id=\"navbarResponsive\">\n        <a class=\"navbar-brand\" href=\"#\">This shouldn't jump!</a>\n        <ul class=\"navbar-nav\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" href=\"#\" aria-current=\"page\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n        </ul>\n      </div>\n    </nav>\n\n    <div class=\"container mt-3\">\n      <h1>Modal <small>Bootstrap Visual Test</small></h1>\n\n      <div class=\"modal fade\" id=\"myModal\" tabindex=\"-1\" aria-labelledby=\"myModalLabel\" aria-hidden=\"true\">\n        <div class=\"modal-dialog\">\n          <div class=\"modal-content\">\n            <div class=\"modal-header\">\n              <h1 class=\"modal-title fs-4\" id=\"myModalLabel\">Modal title</h1>\n              <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n            </div>\n            <div class=\"modal-body\">\n              <h4>Text in a modal</h4>\n              <p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>\n\n              <h4>Popover in a modal</h4>\n              <p>This <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"popover\" data-bs-placement=\"left\" title=\"Popover title\" data-bs-content=\"And here's some amazing content. It's very engaging. Right?\">button</button> should trigger a popover on click.</p>\n\n\n              <h4>Tooltips in a modal</h4>\n              <p><a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"Tooltip on top\">This link</a> and <a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" title=\"Tooltip on bottom\">that link</a> should have tooltips on hover.</p>\n\n              <div id=\"accordion\" role=\"tablist\">\n                <div class=\"card\" role=\"presentation\">\n                  <div class=\"card-header\" role=\"tab\" id=\"headingOne\">\n                    <h5 class=\"mb-0\">\n                      <a data-bs-toggle=\"collapse\" href=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">\n                        Collapsible Group Item #1\n                      </a>\n                    </h5>\n                  </div>\n\n                  <div id=\"collapseOne\" class=\"collapse show\" data-bs-parent=\"#accordion\" role=\"tabpanel\" aria-labelledby=\"headingOne\">\n                    <div class=\"card-body\">\n                      Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n                    </div>\n                  </div>\n                </div>\n                <div class=\"card\" role=\"presentation\">\n                  <div class=\"card-header\" role=\"tab\" id=\"headingTwo\">\n                    <h5 class=\"mb-0\">\n                      <a class=\"collapsed\" data-bs-toggle=\"collapse\" href=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\">\n                        Collapsible Group Item #2\n                      </a>\n                    </h5>\n                  </div>\n                  <div id=\"collapseTwo\" class=\"collapse\" data-bs-parent=\"#accordion\" role=\"tabpanel\" aria-labelledby=\"headingTwo\">\n                    <div class=\"card-body\">\n                      Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n                    </div>\n                  </div>\n                </div>\n                <div class=\"card\" role=\"presentation\">\n                  <div class=\"card-header\" role=\"tab\" id=\"headingThree\">\n                    <h5 class=\"mb-0\">\n                      <a class=\"collapsed\" data-bs-toggle=\"collapse\" href=\"#collapseThree\" aria-expanded=\"false\" aria-controls=\"collapseThree\">\n                        Collapsible Group Item #3\n                      </a>\n                    </h5>\n                  </div>\n                  <div id=\"collapseThree\" class=\"collapse\" data-bs-parent=\"#accordion\" role=\"tabpanel\" aria-labelledby=\"headingThree\">\n                    <div class=\"card-body\">\n                      Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.\n                    </div>\n                  </div>\n                </div>\n              </div>\n\n              <hr>\n\n              <h4>Overflowing text to show scroll behavior</h4>\n              <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>\n              <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p>\n              <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>\n              <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>\n              <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p>\n              <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>\n              <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>\n              <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p>\n              <p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>\n            </div>\n            <div class=\"modal-footer\">\n              <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n              <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"modal fade\" id=\"firefoxModal\" tabindex=\"-1\" aria-labelledby=\"firefoxModalLabel\" aria-hidden=\"true\">\n        <div class=\"modal-dialog\">\n          <div class=\"modal-content\">\n            <div class=\"modal-header\">\n              <h1 class=\"modal-title fs-4\" id=\"firefoxModalLabel\">Firefox Bug Test</h1>\n              <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n            </div>\n            <div class=\"modal-body\">\n              <ol>\n                <li>Ensure you're using Firefox.</li>\n                <li>Open a new tab and then switch back to this tab.</li>\n                <li>Click into this input: <input type=\"text\" id=\"ff-bug-input\"></li>\n                <li>Switch to the other tab and then back to this tab.</li>\n              </ol>\n              <p>Test result: <strong id=\"ff-bug-test-result\"></strong></p>\n            </div>\n            <div class=\"modal-footer\">\n              <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n              <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"modal fade\" id=\"slowModal\" tabindex=\"-1\" aria-labelledby=\"slowModalLabel\" aria-hidden=\"true\" style=\"transition-duration: 5s;\">\n        <div class=\"modal-dialog\" style=\"transition-duration: inherit;\">\n          <div class=\"modal-content\">\n            <div class=\"modal-header\">\n              <h1 class=\"modal-title fs-4\" id=\"slowModalLabel\">Lorem slowly</h1>\n              <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n            </div>\n            <div class=\"modal-body\">\n              <p>Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo. Nulla vitae elit libero, a pharetra augue.</p>\n            </div>\n            <div class=\"modal-footer\">\n              <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n              <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <button type=\"button\" class=\"btn btn-primary btn-lg\" data-bs-toggle=\"modal\" data-bs-target=\"#myModal\">\n        Launch demo modal\n      </button>\n\n      <button type=\"button\" class=\"btn btn-primary btn-lg\" id=\"tall-toggle\">\n        Toggle tall &lt;body&gt; content\n      </button>\n\n      <br><br>\n\n      <button type=\"button\" class=\"btn btn-secondary btn-lg\" data-bs-toggle=\"modal\" data-bs-target=\"#firefoxModal\">\n        Launch Firefox bug test modal\n      </button>\n      (<a href=\"https://github.com/twbs/bootstrap/issues/18365\">See Issue #18365</a>)\n\n      <br><br>\n\n      <button type=\"button\" class=\"btn btn-secondary btn-lg\" data-bs-toggle=\"modal\" data-bs-target=\"#slowModal\">\n        Launch modal with slow transition\n      </button>\n\n      <br><br>\n\n      <div class=\"text-bg-dark p-2\" id=\"tall\" style=\"display: none;\">\n        Tall body content to force the page to have a scrollbar.\n      </div>\n\n      <button type=\"button\" class=\"btn btn-secondary btn-lg\" data-bs-toggle=\"modal\" data-bs-target=\"&#x3C;div class=&#x22;modal fade the-bad&#x22; tabindex=&#x22;-1&#x22;&#x3E;&#x3C;div class=&#x22;modal-dialog&#x22;&#x3E;&#x3C;div class=&#x22;modal-content&#x22;&#x3E;&#x3C;div class=&#x22;modal-header&#x22;&#x3E;&#x3C;button type=&#x22;button&#x22; class=&#x22;btn-close&#x22; data-bs-dismiss=&#x22;modal&#x22; aria-label=&#x22;Close&#x22;&#x3E;&#x3C;span aria-hidden=&#x22;true&#x22;&#x3E;&#x26;times;&#x3C;/span&#x3E;&#x3C;/button&#x3E;&#x3C;h1 class=&#x22;modal-title fs-4&#x22;&#x3E;The Bad Modal&#x3C;/h1&#x3E;&#x3C;/div&#x3E;&#x3C;div class=&#x22;modal-body&#x22;&#x3E;This modal&#x27;s HTTML source code is declared inline, inside the data-bs-target attribute of it&#x27;s show-button&#x3C;/div&#x3E;&#x3C;/div&#x3E;&#x3C;/div&#x3E;&#x3C;/div&#x3E;\">\n        Modal with an XSS inside the data-bs-target\n      </button>\n\n      <br><br>\n\n      <button type=\"button\" class=\"btn btn-secondary btn-lg\" id=\"btnPreventModal\">\n        Launch prevented modal on hide (to see the result open your console)\n      </button>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n    <script>\n      /* global bootstrap: false */\n\n      const ffBugTestResult = document.getElementById('ff-bug-test-result')\n      const firefoxTestDone = false\n\n      function reportFirefoxTestResult(result) {\n        if (!firefoxTestDone) {\n          ffBugTestResult.classList.add(result ? 'text-success' : 'text-danger')\n          ffBugTestResult.textContent = result ? 'PASS' : 'FAIL'\n        }\n      }\n\n      document.querySelectorAll('[data-bs-toggle=\"popover\"]').forEach(popoverEl => new bootstrap.Popover(popoverEl))\n\n      document.querySelectorAll('[data-bs-toggle=\"tooltip\"]').forEach(tooltipEl => new bootstrap.Tooltip(tooltipEl))\n\n      const tall = document.getElementById('tall')\n      document.getElementById('tall-toggle').addEventListener('click', () => {\n        tall.style.display = tall.style.display === 'none' ? 'block' : 'none'\n      })\n\n      const ffBugInput = document.getElementById('ff-bug-input')\n      const firefoxModal = document.getElementById('firefoxModal')\n      function handlerClickFfBugInput() {\n        firefoxModal.addEventListener('focus', reportFirefoxTestResult.bind(false))\n        ffBugInput.addEventListener('focus', reportFirefoxTestResult.bind(true))\n        ffBugInput.removeEventListener('focus', handlerClickFfBugInput)\n      }\n\n      ffBugInput.addEventListener('focus', handlerClickFfBugInput)\n\n      const modalFf = new bootstrap.Modal(firefoxModal)\n\n      document.getElementById('btnPreventModal').addEventListener('click', () => {\n        const shownFirefoxModal = () => {\n          modalFf.hide()\n          firefoxModal.removeEventListener('shown.bs.modal', hideFirefoxModal)\n        }\n\n        const hideFirefoxModal = event => {\n          event.preventDefault()\n          firefoxModal.removeEventListener('hide.bs.modal', hideFirefoxModal)\n\n          if (modalFf._isTransitioning) {\n            console.error('Modal plugin should not set _isTransitioning when hide event is prevented')\n          } else {\n            console.log('Test passed')\n            modalFf.hide() // work as expected\n          }\n        }\n\n        firefoxModal.addEventListener('shown.bs.modal', shownFirefoxModal)\n        firefoxModal.addEventListener('hide.bs.modal', hideFirefoxModal)\n        modalFf.show()\n      })\n\n      // Test transition duration\n      let t0\n      let t1\n      const slowModal = document.getElementById('slowModal')\n\n      slowModal.addEventListener('shown.bs.modal', () => {\n        t1 = performance.now()\n        console.log('transition-duration took ' + (t1 - t0) + 'ms.')\n      })\n\n      slowModal.addEventListener('show.bs.modal', () => {\n        t0 = performance.now()\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/popover.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Popover</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Popover <small>Bootstrap Visual Test</small></h1>\n\n      <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"auto\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n        Popover on auto\n      </button>\n\n      <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"top\" data-bs-content=\"Default placement was on top but not enough place\">\n        Popover on top\n      </button>\n\n      <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"right\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n        Popover on end\n      </button>\n\n      <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"bottom\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n        Popover on bottom\n      </button>\n\n      <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"left\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n        Popover on start\n      </button>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n    <script>\n      /* global bootstrap: false */\n\n      document.querySelectorAll('[data-bs-toggle=\"popover\"]').forEach(popoverEl => new bootstrap.Popover(popoverEl))\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/scrollspy.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Scrollspy</title>\n    <style>\n      body { padding-top: 70px; }\n    </style>\n  </head>\n  <body data-bs-spy=\"scroll\" data-bs-target=\".navbar\" data-bs-offset=\"70\">\n    <nav class=\"navbar navbar-expand-md navbar-dark bg-dark fixed-top\">\n      <a class=\"navbar-brand\" href=\"#\">Scrollspy test</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"navbar-collapse collapse\" id=\"navbarSupportedContent\">\n        <ul class=\"navbar-nav me-auto\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#fat\">@fat</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#mdo\">@mdo</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#one\">One</a></li>\n              <li><a class=\"dropdown-item\" href=\"#two\">Two</a></li>\n              <li><a class=\"dropdown-item\" href=\"#three\">Three</a></li>\n            </ul>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#final\">Final</a>\n          </li>\n        </ul>\n      </div>\n    </nav>\n    <div class=\"container\">\n      <h2 id=\"fat\">@fat</h2>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <hr>\n      <h2 id=\"mdo\">@mdo</h2>\n      <p>Veniam marfa mustache skateboard, adipisicing fugiat velit pitchfork beard. Freegan beard aliqua cupidatat mcsweeney's vero. Cupidatat four loko nisi, ea helvetica nulla carles. Tattooed cosby sweater food truck, mcsweeney's quis non freegan vinyl. Lo-fi wes anderson +1 sartorial. Carles non aesthetic exercitation quis gentrify. Brooklyn adipisicing craft beer vice keytar deserunt.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <hr>\n      <h2 id=\"one\">one</h2>\n      <p>Occaecat commodo aliqua delectus. Fap craft beer deserunt skateboard ea. Lomo bicycle rights adipisicing banh mi, velit ea sunt next level locavore single-origin coffee in magna veniam. High life id vinyl, echo park consequat quis aliquip banh mi pitchfork. Vero VHS est adipisicing. Consectetur nisi DIY minim messenger bag. Cred ex in, sustainable delectus consectetur fanny pack iphone.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <hr>\n      <h2 id=\"two\">two</h2>\n      <p>In incididunt echo park, officia deserunt mcsweeney's proident master cleanse thundercats sapiente veniam. Excepteur VHS elit, proident shoreditch +1 biodiesel laborum craft beer. Single-origin coffee wayfarers irure four loko, cupidatat terry richardson master cleanse. Assumenda you probably haven't heard of them art party fanny pack, tattooed nulla cardigan tempor ad. Proident wolf nesciunt sartorial keffiyeh eu banh mi sustainable. Elit wolf voluptate, lo-fi ea portland before they sold out four loko. Locavore enim nostrud mlkshk brooklyn nesciunt.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <hr>\n      <h2 id=\"three\">three</h2>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Keytar twee blog, culpa messenger bag marfa whatever delectus food truck. Sapiente synth id assumenda. Locavore sed helvetica cliche irony, thundercats you probably haven't heard of them consequat hoodie gluten-free lo-fi fap aliquip. Labore elit placeat before they sold out, terry richardson proident brunch nesciunt quis cosby sweater pariatur keffiyeh ut helvetica artisan. Cardigan craft beer seitan readymade velit. VHS chambray laboris tempor veniam. Anim mollit minim commodo ullamco thundercats.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>\n      <hr>\n      <h2 id=\"final\">Final section</h2>\n      <p>Ad leggings keytar, brunch id art party dolor labore.</p>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/tab.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Tab</title>\n    <style>\n      h4 {\n        margin: 40px 0 10px;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Tab <small>Bootstrap Visual Test</small></h1>\n\n      <h4>Tabs without fade</h4>\n\n      <ul class=\"nav nav-tabs\" role=\"tablist\">\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#home\" role=\"tab\" aria-selected=\"true\">Home</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#profile\" role=\"tab\">Profile</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#fat\" role=\"tab\">@fat</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#mdo\" role=\"tab\">@mdo</button>\n        </li>\n      </ul>\n\n      <div class=\"tab-content\" role=\"tablist\">\n        <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\">\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n        </div>\n        <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\">\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n        </div>\n        <div class=\"tab-pane\" id=\"fat\" role=\"tabpanel\">\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n        </div>\n        <div class=\"tab-pane\" id=\"mdo\" role=\"tabpanel\">\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n        </div>\n      </div>\n\n      <h4>Tabs with fade</h4>\n\n      <ul class=\"nav nav-tabs\" role=\"tablist\">\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#home2\" role=\"tab\" aria-selected=\"true\">Home</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#profile2\" role=\"tab\">Profile</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#fat2\" role=\"tab\">@fat</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#mdo2\" role=\"tab\">@mdo</button>\n        </li>\n      </ul>\n\n      <div class=\"tab-content\" role=\"tablist\">\n        <div class=\"tab-pane fade show active\" id=\"home2\" role=\"tabpanel\">\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"profile2\" role=\"tabpanel\">\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"fat2\" role=\"tabpanel\">\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"mdo2\" role=\"tabpanel\">\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n        </div>\n      </div>\n\n      <h4>Tabs without fade (no initially active pane)</h4>\n\n      <ul class=\"nav nav-tabs\" role=\"tablist\">\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#home3\" role=\"tab\">Home</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#profile3\" role=\"tab\">Profile</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#fat3\" role=\"tab\">@fat</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#mdo3\" role=\"tab\">@mdo</button>\n        </li>\n      </ul>\n\n      <div class=\"tab-content\" role=\"tablist\">\n        <div class=\"tab-pane\" id=\"home3\" role=\"tabpanel\">\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n        </div>\n        <div class=\"tab-pane\" id=\"profile3\" role=\"tabpanel\">\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n        </div>\n        <div class=\"tab-pane\" id=\"fat3\" role=\"tabpanel\">\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n        </div>\n        <div class=\"tab-pane\" id=\"mdo3\" role=\"tabpanel\">\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n        </div>\n      </div>\n\n      <h4>Tabs with fade (no initially active pane)</h4>\n\n      <ul class=\"nav nav-tabs\" role=\"tablist\">\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#home4\" role=\"tab\">Home</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#profile4\" role=\"tab\">Profile</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#fat4\" role=\"tab\">@fat</button>\n        </li>\n        <li class=\"nav-item\" role=\"presentation\">\n          <button type=\"button\" class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#mdo4\" role=\"tab\">@mdo</button>\n        </li>\n      </ul>\n\n      <div class=\"tab-content\">\n        <div class=\"tab-pane fade\" id=\"home4\" role=\"tabpanel\">\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"profile4\" role=\"tabpanel\">\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n          <p>Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"fat4\" role=\"tabpanel\">\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"mdo4\" role=\"tabpanel\">\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n        </div>\n      </div>\n\n      <h4>Tabs with nav and using links (with fade)</h4>\n      <nav>\n          <div class=\"nav nav-pills\" id=\"nav-tab\" role=\"tablist\">\n          <a class=\"nav-link nav-item active\" role=\"tab\" data-bs-toggle=\"tab\" href=\"#home5\">Home</a>\n          <a class=\"nav-link nav-item\" role=\"tab\" data-bs-toggle=\"tab\" href=\"#profile5\">Profile</a>\n          <a class=\"nav-link nav-item\" role=\"tab\" data-bs-toggle=\"tab\" href=\"#fat5\">@fat</a>\n          <a class=\"nav-link nav-item\" role=\"tab\" data-bs-toggle=\"tab\" href=\"#mdo5\">@mdo</a>\n          <a class=\"nav-link nav-item disabled\" role=\"tab\" href=\"#\">Disabled</a>\n        </div>\n      </nav>\n\n      <div class=\"tab-content\">\n        <div class=\"tab-pane fade show active\" id=\"home5\" role=\"tabpanel\">\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"profile5\" role=\"tabpanel\">\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n          <p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"fat5\" role=\"tabpanel\">\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n          <p>Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"mdo5\" role=\"tabpanel\">\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n          <p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>\n        </div>\n      </div>\n\n      <h4>Tabs with list-group (with fade)</h4>\n      <div class=\"row\">\n        <div class=\"col-4\">\n          <div class=\"list-group\" id=\"list-tab\" role=\"tablist\">\n            <button type=\"button\" class=\"list-group-item list-group-item-action active\" id=\"list-home-list\" data-bs-toggle=\"tab\" data-bs-target=\"#list-home\" role=\"tab\" aria-controls=\"list-home\" aria-selected=\"true\">Home</button>\n            <button type=\"button\"  class=\"list-group-item list-group-item-action\" id=\"list-profile-list\" data-bs-toggle=\"tab\" data-bs-target=\"#list-profile\" role=\"tab\" aria-controls=\"list-profile\">Profile</button>\n            <button type=\"button\"  class=\"list-group-item list-group-item-action\" id=\"list-messages-list\" data-bs-toggle=\"tab\" data-bs-target=\"#list-messages\" role=\"tab\" aria-controls=\"list-messages\">Messages</button>\n            <button type=\"button\"  class=\"list-group-item list-group-item-action\" id=\"list-settings-list\" data-bs-toggle=\"tab\" data-bs-target=\"#list-settings\" role=\"tab\" aria-controls=\"list-settings\">Settings</button>\n          </div>\n        </div>\n        <div class=\"col-8\">\n          <div class=\"tab-content\" id=\"nav-tabContent\">\n            <div class=\"tab-pane fade show active\" id=\"list-home\" role=\"tabpanel\" aria-labelledby=\"list-home-list\">\n              <p>Velit aute mollit ipsum ad dolor consectetur nulla officia culpa adipisicing exercitation fugiat tempor. Voluptate deserunt sit sunt nisi aliqua fugiat proident ea ut. Mollit voluptate reprehenderit occaecat nisi ad non minim tempor sunt voluptate consectetur exercitation id ut nulla. Ea et fugiat aliquip nostrud sunt incididunt consectetur culpa aliquip eiusmod dolor. Anim ad Lorem aliqua in cupidatat nisi enim eu nostrud do aliquip veniam minim.</p>\n            </div>\n            <div class=\"tab-pane fade\" id=\"list-profile\" role=\"tabpanel\" aria-labelledby=\"list-profile-list\">\n              <p>Cupidatat quis ad sint excepteur laborum in esse qui. Et excepteur consectetur ex nisi eu do cillum ad laborum. Mollit et eu officia dolore sunt Lorem culpa qui commodo velit ex amet id ex. Officia anim incididunt laboris deserunt anim aute dolor incididunt veniam aute dolore do exercitation. Dolor nisi culpa ex ad irure in elit eu dolore. Ad laboris ipsum reprehenderit irure non commodo enim culpa commodo veniam incididunt veniam ad.</p>\n            </div>\n            <div class=\"tab-pane fade\" id=\"list-messages\" role=\"tabpanel\" aria-labelledby=\"list-messages-list\">\n              <p>Ut ut do pariatur aliquip aliqua aliquip exercitation do nostrud commodo reprehenderit aute ipsum voluptate. Irure Lorem et laboris nostrud amet cupidatat cupidatat anim do ut velit mollit consequat enim tempor. Consectetur est minim nostrud nostrud consectetur irure labore voluptate irure. Ipsum id Lorem sit sint voluptate est pariatur eu ad cupidatat et deserunt culpa sit eiusmod deserunt. Consectetur et fugiat anim do eiusmod aliquip nulla laborum elit adipisicing pariatur cillum.</p>\n            </div>\n            <div class=\"tab-pane fade\" id=\"list-settings\" role=\"tabpanel\" aria-labelledby=\"list-settings-list\">\n              <p>Irure enim occaecat labore sit qui aliquip reprehenderit amet velit. Deserunt ullamco ex elit nostrud ut dolore nisi officia magna sit occaecat laboris sunt dolor. Nisi eu minim cillum occaecat aute est cupidatat aliqua labore aute occaecat ea aliquip sunt amet. Aute mollit dolor ut exercitation irure commodo non amet consectetur quis amet culpa. Quis ullamco nisi amet qui aute irure eu. Magna labore dolor quis ex labore id nostrud deserunt dolor eiusmod eu pariatur culpa mollit in irure.</p>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/toast.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Toast</title>\n    <style>\n      .notifications {\n        position: absolute;\n        top: 10px;\n        right: 10px;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Toast <small>Bootstrap Visual Test</small></h1>\n\n      <div class=\"row mt-3\">\n        <div class=\"col-md-12\">\n          <button id=\"btnShowToast\" class=\"btn btn-primary\">Show toast</button>\n          <button id=\"btnHideToast\" class=\"btn btn-primary\">Hide toast</button>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"notifications\">\n      <div id=\"toastAutoHide\" class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\" data-bs-delay=\"2000\">\n        <div class=\"toast-header\">\n          <span class=\"d-block bg-primary rounded me-2\" style=\"width: 20px; height: 20px;\"></span>\n          <strong class=\"me-auto\">Bootstrap</strong>\n          <small>11 mins ago</small>\n        </div>\n        <div class=\"toast-body\">\n          Hello, world! This is a toast message with <strong>autohide</strong> in 2 seconds\n        </div>\n      </div>\n\n      <div class=\"toast\" data-bs-autohide=\"false\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n        <div class=\"toast-header\">\n          <span class=\"d-block bg-primary rounded me-2\" style=\"width: 20px; height: 20px;\"></span>\n          <strong class=\"me-auto\">Bootstrap</strong>\n          <small class=\"text-muted\">2 seconds ago</small>\n          <button type=\"button\" class=\"ms-2 mb-1 btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n        </div>\n        <div class=\"toast-body\">\n          Heads up, toasts will stack automatically\n        </div>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n    <script>\n      /* global bootstrap: false */\n\n      window.addEventListener('load', () => {\n        document.querySelectorAll('.toast').forEach(toastEl => new bootstrap.Toast(toastEl))\n\n        document.getElementById('btnShowToast').addEventListener('click', () => {\n          document.querySelectorAll('.toast').forEach(toastEl => bootstrap.Toast.getInstance(toastEl).show())\n        })\n\n        document.getElementById('btnHideToast').addEventListener('click', () => {\n          document.querySelectorAll('.toast').forEach(toastEl => bootstrap.Toast.getInstance(toastEl).hide())\n        })\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/js/tests/visual/tooltip.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"../../../dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <title>Tooltip</title>\n    <style>\n      #target {\n        border: 1px solid;\n        width: 100px;\n        height: 50px;\n        margin-left: 50px;\n        transform: rotate(270deg);\n        margin-top: 100px;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Tooltip <small>Bootstrap Visual Test</small></h1>\n\n      <p class=\"text-muted\">Tight pants next level keffiyeh <a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Default tooltip\">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Another tooltip\">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Another one here too\">whatever keytar</a>, scenester farm-to-table banksy Austin <a href=\"#\" data-bs-toggle=\"tooltip\" title=\"The last tip!\">twitter handle</a> freegan cred raw denim single-origin coffee viral.</p>\n\n      <hr>\n\n      <div class=\"row\">\n        <p>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"auto\" title=\"Tooltip on auto\">\n            Tooltip on auto\n          </button>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"Tooltip on top\">\n            Tooltip on top\n          </button>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\" title=\"Tooltip on right\">\n            Tooltip on end\n          </button>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" title=\"Tooltip on bottom\">\n            Tooltip on bottom\n          </button>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" title=\"Tooltip on left\">\n            Tooltip on start\n          </button>\n        </p>\n      </div>\n      <div class=\"row\">\n        <p>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" title=\"Tooltip with container (selector)\" data-bs-container=\"#customContainer\">\n            Tooltip with container (selector)\n          </button>\n          <button id=\"tooltipElement\" type=\"button\" class=\"btn btn-secondary\" data-bs-placement=\"left\" title=\"Tooltip with container (element)\">\n            Tooltip with container (element)\n          </button>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-html=\"true\" title=\"<em>Tooltip</em> <u>with</u> <b>HTML</b>\">\n            Tooltip with HTML\n          </button>\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" title=\"Tooltip with XSS\" data-bs-container=\"<img src=1 onerror=alert(123)>\">\n            Tooltip with XSS\n          </button>\n        </p>\n      </div>\n      <div class=\"row\">\n        <div class=\"col-sm-3\">\n          <div id=\"target\" title=\"Test tooltip on transformed element\"></div>\n        </div>\n        <div id=\"shadow\" class=\"pt-5\"></div>\n      </div>\n      <div id=\"customContainer\"></div>\n\n      <div class=\"row mt-4 border-top\">\n        <hr>\n        <div class=\"h4\">Test Selector triggered tooltips</div>\n        <div id=\"wrapperTriggeredBySelector\">\n          <div class=\"py-2 selectorButtonsBlock\">\n            <button type=\"button\" class=\"btn btn-secondary bs-dynamic-tooltip\" title=\"random title\">Using title</button>\n            <button type=\"button\" class=\"btn btn-secondary bs-dynamic-tooltip\" data-bs-title=\"random title\">Using bs-title</button>\n          </div>\n\n        </div>\n        <div class=\"mt-3\">\n          <button type=\"button\" class=\"btn btn-primary\" onclick=\"duplicateButtons()\">Duplicate above two buttons</button>\n        </div>\n      </div>\n    </div>\n\n    <script src=\"../../../dist/js/bootstrap.bundle.js\"></script>\n    <script>\n      /* global bootstrap: false */\n\n      if (typeof document.body.attachShadow === 'function') {\n        const shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' })\n        shadowRoot.innerHTML =\n          '<button id=\"firstShadowTooltip\" type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"Tooltip on top in a shadow dom\">' +\n          '  Tooltip on top in a shadow dom' +\n          '</button>' +\n          '<button id=\"secondShadowTooltip\" type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"Tooltip on top in a shadow dom with container option\">' +\n          '  Tooltip on top in a shadow dom' +\n          '</button>'\n\n        new bootstrap.Tooltip(shadowRoot.firstChild)\n        new bootstrap.Tooltip(shadowRoot.getElementById('secondShadowTooltip'), {\n          container: shadowRoot\n        })\n      }\n\n      new bootstrap.Tooltip('#tooltipElement', {\n        container: '#customContainer'\n      })\n\n      const targetTooltip = new bootstrap.Tooltip('#target', {\n        placement: 'top',\n        trigger: 'manual'\n      })\n      targetTooltip.show()\n\n      document.querySelectorAll('[data-bs-toggle=\"tooltip\"]').forEach(tooltipEl => new bootstrap.Tooltip(tooltipEl))\n    </script>\n\n    <script>\n      /* global bootstrap: false */\n\n      new bootstrap.Tooltip('#wrapperTriggeredBySelector', {\n        animation: false,\n        selector: '.bs-dynamic-tooltip'\n      })\n\n      /* eslint-disable no-unused-vars */\n      function duplicateButtons() {\n        const buttonsBlock = document.querySelector('.selectorButtonsBlock')// get first\n        const buttonsBlockClone = buttonsBlock.cloneNode(true)\n        buttonsBlockClone.innerHTML += new Date().toLocaleString()\n        document.querySelector('#wrapperTriggeredBySelector').append(buttonsBlockClone)\n      }\n      /* eslint-enable no-unused-vars */\n    </script>\n\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/nuget/MyGet.ps1",
    "content": "# set env vars usually set by MyGet (enable for local testing)\n#$env:SourcesPath = '..'\n#$env:NuGet = \"./nuget.exe\" # https://dist.nuget.org/win-x86-commandline/latest/nuget.exe\n\n$nuget = $env:NuGet\n\n# parse the version number out of package.json\n$bsversionParts = ((Get-Content $env:SourcesPath\\package.json) -join \"`n\" | ConvertFrom-Json).version.split('-', 2) # split the version on the '-'\n$bsversion = $bsversionParts[0]\n\nif ($bsversionParts.Length -gt 1) {\n  $bsversion += '-' + $bsversionParts[1].replace('.', '').replace('-', '_') # strip out invalid chars from the PreRelease part\n}\n\n# create packages\n& $nuget pack \"$env:SourcesPath\\nuget\\bootstrap.nuspec\" -Verbosity detailed -NonInteractive -NoPackageAnalysis -BasePath $env:SourcesPath -Version $bsversion\n& $nuget pack \"$env:SourcesPath\\nuget\\bootstrap.sass.nuspec\" -Verbosity detailed -NonInteractive -NoPackageAnalysis -BasePath $env:SourcesPath -Version $bsversion\n"
  },
  {
    "path": "src/common/bootstrap/nuget/bootstrap.nuspec",
    "content": "<?xml version=\"1.0\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd\">\n  <metadata>\n    <id>bootstrap</id>\n    <!-- pulled from package.json -->\n    <version>5</version>\n    <title>Bootstrap CSS</title>\n    <authors>The Bootstrap Authors, Twitter Inc.</authors>\n    <owners>bootstrap</owners>\n    <description>The most popular front-end framework for developing responsive, mobile first projects on the web.</description>\n    <releaseNotes>https://blog.getbootstrap.com/</releaseNotes>\n    <summary>Bootstrap framework in CSS. Includes JavaScript.</summary>\n    <language>en-us</language>\n    <projectUrl>https://getbootstrap.com/</projectUrl>\n    <icon>bootstrap.png</icon>\n    <license type=\"expression\">MIT</license>\n    <copyright>Copyright 2017-2022</copyright>\n    <requireLicenseAcceptance>false</requireLicenseAcceptance>\n    <tags>css mobile-first responsive front-end framework web</tags>\n    <contentFiles>\n      <files include=\"**/*\" buildAction=\"Content\" />\n    </contentFiles>\n  </metadata>\n  <files>\n    <file src=\"nuget\\bootstrap.png\" target=\"\" />\n\n    <file src=\"dist\\css\\*.*\" target=\"content\\Content\" />\n    <file src=\"dist\\js\\bootstrap*.js\" target=\"content\\Scripts\" />\n    <file src=\"dist\\js\\bootstrap*.js.map\" target=\"content\\Scripts\" />\n\n    <file src=\"dist\\css\\*.*\" target=\"contentFiles\\any\\any\\wwwroot\\css\" />\n    <file src=\"dist\\js\\bootstrap*.js\" target=\"contentFiles\\any\\any\\wwwroot\\js\" />\n    <file src=\"dist\\js\\bootstrap*.js.map\" target=\"contentFiles\\any\\any\\wwwroot\\js\" />\n  </files>\n</package>\n"
  },
  {
    "path": "src/common/bootstrap/nuget/bootstrap.sass.nuspec",
    "content": "<?xml version=\"1.0\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd\">\n  <metadata>\n    <id>bootstrap.sass</id>\n    <!-- pulled from package.json -->\n    <version>5</version>\n    <title>Bootstrap Sass</title>\n    <authors>The Bootstrap Authors, Twitter Inc.</authors>\n    <owners>bootstrap</owners>\n    <description>The most popular front-end framework for developing responsive, mobile first projects on the web.</description>\n    <releaseNotes>https://blog.getbootstrap.com/</releaseNotes>\n    <summary>Bootstrap framework in Sass. Includes JavaScript</summary>\n    <language>en-us</language>\n    <projectUrl>https://getbootstrap.com/</projectUrl>\n    <icon>bootstrap.png</icon>\n    <license type=\"expression\">MIT</license>\n    <copyright>Copyright 2017-2022</copyright>\n    <requireLicenseAcceptance>false</requireLicenseAcceptance>\n    <tags>css sass mobile-first responsive front-end framework web</tags>\n    <contentFiles>\n      <files include=\"**/*\" buildAction=\"Content\" />\n    </contentFiles>\n  </metadata>\n  <files>\n    <file src=\"nuget\\bootstrap.png\" target=\"\" />\n\n    <file src=\"scss\\**\\*.scss\" target=\"content\\Content\\bootstrap\" />\n    <file src=\"dist\\js\\bootstrap*.js\" target=\"content\\Scripts\" />\n    <file src=\"dist\\js\\bootstrap*.js.map\" target=\"content\\Scripts\" />\n\n    <file src=\"scss\\**\\*.scss\" target=\"contentFiles\\any\\any\\wwwroot\\scss\" />\n    <file src=\"dist\\js\\bootstrap*.js\" target=\"contentFiles\\any\\any\\wwwroot\\js\" />\n    <file src=\"dist\\js\\bootstrap*.js.map\" target=\"contentFiles\\any\\any\\wwwroot\\js\" />\n  </files>\n</package>\n"
  },
  {
    "path": "src/common/bootstrap/package.js",
    "content": "// package metadata file for Meteor.js\n\n/* eslint-env meteor */\n\nPackage.describe({\n  name: 'twbs:bootstrap', // https://atmospherejs.com/twbs/bootstrap\n  summary: 'The most popular front-end framework for developing responsive, mobile first projects on the web.',\n  version: '5.2.3',\n  git: 'https://github.com/twbs/bootstrap.git'\n})\n\nPackage.onUse(api => {\n  api.versionsFrom('METEOR@1.0')\n  api.addFiles([\n    'dist/css/bootstrap.css',\n    'dist/js/bootstrap.js'\n  ], 'client')\n})\n"
  },
  {
    "path": "src/common/bootstrap/package.json",
    "content": "{\n  \"name\": \"bootstrap\",\n  \"description\": \"The most popular front-end framework for developing responsive, mobile first projects on the web.\",\n  \"version\": \"5.2.3\",\n  \"config\": {\n    \"version_short\": \"5.2\"\n  },\n  \"keywords\": [\n    \"css\",\n    \"sass\",\n    \"mobile-first\",\n    \"responsive\",\n    \"front-end\",\n    \"framework\",\n    \"web\"\n  ],\n  \"homepage\": \"https://getbootstrap.com/\",\n  \"author\": \"The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\",\n  \"contributors\": [\n    \"Twitter, Inc.\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/twbs/bootstrap.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/twbs/bootstrap/issues\"\n  },\n  \"funding\": [\n    {\n      \"type\": \"github\",\n      \"url\": \"https://github.com/sponsors/twbs\"\n    },\n    {\n      \"type\": \"opencollective\",\n      \"url\": \"https://opencollective.com/bootstrap\"\n    }\n  ],\n  \"main\": \"dist/js/bootstrap.js\",\n  \"module\": \"dist/js/bootstrap.esm.js\",\n  \"sass\": \"scss/bootstrap.scss\",\n  \"style\": \"dist/css/bootstrap.css\",\n  \"scripts\": {\n    \"start\": \"npm-run-all --parallel watch docs-serve\",\n    \"bundlewatch\": \"bundlewatch --config .bundlewatch.config.json\",\n    \"css\": \"npm-run-all css-compile css-prefix css-rtl css-minify\",\n    \"css-compile\": \"sass --style expanded --source-map --embed-sources --no-error-css scss/:dist/css/\",\n    \"css-rtl\": \"cross-env NODE_ENV=RTL postcss --config build/postcss.config.js --dir \\\"dist/css\\\" --ext \\\".rtl.css\\\" \\\"dist/css/*.css\\\" \\\"!dist/css/*.min.css\\\" \\\"!dist/css/*.rtl.css\\\"\",\n    \"css-lint\": \"npm-run-all --aggregate-output --continue-on-error --parallel css-lint-*\",\n    \"css-lint-stylelint\": \"stylelint \\\"**/*.{css,scss}\\\" --cache --cache-location .cache/.stylelintcache --rd\",\n    \"css-lint-vars\": \"fusv scss/ site/assets/scss/\",\n    \"css-minify\": \"npm-run-all --aggregate-output --parallel css-minify-*\",\n    \"css-minify-main\": \"cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \\\".min\\\" \\\"dist/css/*.css\\\" \\\"!dist/css/*.min.css\\\" \\\"!dist/css/*rtl*.css\\\"\",\n    \"css-minify-rtl\": \"cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \\\".min\\\" \\\"dist/css/*rtl.css\\\" \\\"!dist/css/*.min.css\\\"\",\n    \"css-prefix\": \"npm-run-all --aggregate-output --parallel css-prefix-*\",\n    \"css-prefix-main\": \"postcss --config build/postcss.config.js --replace \\\"dist/css/*.css\\\" \\\"!dist/css/*.rtl*.css\\\" \\\"!dist/css/*.min.css\\\"\",\n    \"css-prefix-examples\": \"postcss --config build/postcss.config.js --replace \\\"site/content/**/*.css\\\"\",\n    \"css-prefix-examples-rtl\": \"cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.js --dir \\\"site/content/docs/$npm_package_config_version_short/examples/\\\" --ext \\\".rtl.css\\\" --base \\\"site/content/docs/$npm_package_config_version_short/examples/\\\" \\\"site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.css\\\" \\\"!site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\\\"\",\n    \"js\": \"npm-run-all js-compile js-minify\",\n    \"js-compile\": \"npm-run-all --aggregate-output --parallel js-compile-*\",\n    \"js-compile-standalone\": \"rollup --environment BUNDLE:false --config build/rollup.config.js --sourcemap\",\n    \"js-compile-standalone-esm\": \"rollup --environment ESM:true,BUNDLE:false --config build/rollup.config.js --sourcemap\",\n    \"js-compile-bundle\": \"rollup --environment BUNDLE:true --config build/rollup.config.js --sourcemap\",\n    \"js-compile-plugins\": \"node build/build-plugins.js\",\n    \"js-lint\": \"eslint --cache --cache-location .cache/.eslintcache --report-unused-disable-directives --ext .html,.js .\",\n    \"js-minify\": \"npm-run-all --aggregate-output --parallel js-minify-*\",\n    \"js-minify-standalone\": \"terser --compress passes=2 --mangle --comments \\\"/^!/\\\" --source-map \\\"content=dist/js/bootstrap.js.map,includeSources,url=bootstrap.min.js.map\\\" --output dist/js/bootstrap.min.js dist/js/bootstrap.js\",\n    \"js-minify-standalone-esm\": \"terser --compress passes=2 --mangle --comments \\\"/^!/\\\" --source-map \\\"content=dist/js/bootstrap.esm.js.map,includeSources,url=bootstrap.esm.min.js.map\\\" --output dist/js/bootstrap.esm.min.js dist/js/bootstrap.esm.js\",\n    \"js-minify-bundle\": \"terser --compress passes=2 --mangle --comments \\\"/^!/\\\" --source-map \\\"content=dist/js/bootstrap.bundle.js.map,includeSources,url=bootstrap.bundle.min.js.map\\\" --output dist/js/bootstrap.bundle.min.js dist/js/bootstrap.bundle.js\",\n    \"js-test\": \"npm-run-all --aggregate-output --parallel js-test-karma js-test-jquery js-test-integration-*\",\n    \"js-debug\": \"cross-env DEBUG=true npm run js-test-karma\",\n    \"js-test-karma\": \"karma start js/tests/karma.conf.js\",\n    \"js-test-integration-bundle\": \"rollup --config js/tests/integration/rollup.bundle.js\",\n    \"js-test-integration-modularity\": \"rollup --config js/tests/integration/rollup.bundle-modularity.js\",\n    \"js-test-cloud\": \"cross-env BROWSERSTACK=true npm run js-test-karma\",\n    \"js-test-jquery\": \"cross-env JQUERY=true npm run js-test-karma\",\n    \"lint\": \"npm-run-all --aggregate-output --continue-on-error --parallel js-lint css-lint lockfile-lint\",\n    \"docs\": \"npm-run-all docs-build docs-lint\",\n    \"docs-build\": \"hugo --cleanDestinationDir\",\n    \"docs-compile\": \"npm run docs-build\",\n    \"docs-vnu\": \"node build/vnu-jar.js\",\n    \"docs-lint\": \"npm run docs-vnu\",\n    \"docs-serve\": \"hugo server --port 9001 --disableFastRender\",\n    \"docs-serve-only\": \"npx sirv-cli _site --port 9001\",\n    \"lockfile-lint\": \"lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json\",\n    \"update-deps\": \"ncu -u -x globby,karma-browserstack-launcher,karma-rollup-preprocessor && echo Manually update site/assets/js/vendor\",\n    \"release\": \"npm-run-all dist release-sri docs-build release-zip*\",\n    \"release-sri\": \"node build/generate-sri.js\",\n    \"release-version\": \"node build/change-version.js\",\n    \"release-zip\": \"cross-env-shell \\\"rm -rf bootstrap-$npm_package_version-dist && cp -r dist/ bootstrap-$npm_package_version-dist && zip -r9 bootstrap-$npm_package_version-dist.zip bootstrap-$npm_package_version-dist && rm -rf bootstrap-$npm_package_version-dist\\\"\",\n    \"release-zip-examples\": \"node build/zip-examples.js\",\n    \"dist\": \"npm-run-all --aggregate-output --parallel css js\",\n    \"test\": \"npm-run-all lint dist js-test docs-build docs-lint\",\n    \"netlify\": \"cross-env-shell HUGO_BASEURL=$DEPLOY_PRIME_URL npm-run-all dist release-sri docs-build\",\n    \"watch\": \"npm-run-all --parallel watch-*\",\n    \"watch-css-main\": \"nodemon --watch scss/ --ext scss --exec \\\"npm-run-all css-lint css-compile css-prefix\\\"\",\n    \"watch-css-dist\": \"nodemon --watch dist/css/ --ext css --ignore \\\"dist/css/*.rtl.*\\\" --exec \\\"npm run css-rtl\\\"\",\n    \"watch-css-docs\": \"nodemon --watch site/assets/scss/ --ext scss --exec \\\"npm run css-lint\\\"\",\n    \"watch-js-main\": \"nodemon --watch js/src/ --ext js --exec \\\"npm-run-all js-lint js-compile\\\"\",\n    \"watch-js-docs\": \"nodemon --watch site/assets/js/ --ext js --exec \\\"npm run js-lint\\\"\"\n  },\n  \"peerDependencies\": {\n    \"@popperjs/core\": \"^2.11.6\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.19.3\",\n    \"@babel/core\": \"^7.19.3\",\n    \"@babel/preset-env\": \"^7.19.3\",\n    \"@popperjs/core\": \"^2.11.6\",\n    \"@rollup/plugin-babel\": \"^5.3.1\",\n    \"@rollup/plugin-commonjs\": \"^22.0.2\",\n    \"@rollup/plugin-node-resolve\": \"^14.1.0\",\n    \"@rollup/plugin-replace\": \"^4.0.0\",\n    \"autoprefixer\": \"^10.4.12\",\n    \"bundlewatch\": \"^0.3.3\",\n    \"clean-css-cli\": \"^5.6.1\",\n    \"cross-env\": \"^7.0.3\",\n    \"eslint\": \"^8.24.0\",\n    \"eslint-config-xo\": \"^0.42.0\",\n    \"eslint-plugin-html\": \"^7.1.0\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"eslint-plugin-markdown\": \"^3.0.0\",\n    \"eslint-plugin-unicorn\": \"^44.0.0\",\n    \"find-unused-sass-variables\": \"^4.0.4\",\n    \"globby\": \"^11.1.0\",\n    \"hammer-simulator\": \"0.0.1\",\n    \"hugo-bin\": \"^0.92.2\",\n    \"ip\": \"^2.0.0\",\n    \"jquery\": \"^3.6.1\",\n    \"karma\": \"^6.4.1\",\n    \"karma-browserstack-launcher\": \"1.4.0\",\n    \"karma-chrome-launcher\": \"^3.1.1\",\n    \"karma-coverage-istanbul-reporter\": \"^3.0.3\",\n    \"karma-detect-browsers\": \"^2.3.3\",\n    \"karma-firefox-launcher\": \"^2.1.2\",\n    \"karma-jasmine\": \"^5.1.0\",\n    \"karma-jasmine-html-reporter\": \"^2.0.0\",\n    \"karma-rollup-preprocessor\": \"7.0.7\",\n    \"lockfile-lint\": \"^4.9.5\",\n    \"nodemon\": \"^2.0.20\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"postcss\": \"^8.4.17\",\n    \"postcss-cli\": \"^10.0.0\",\n    \"rollup\": \"^2.79.1\",\n    \"rollup-plugin-istanbul\": \"^3.0.0\",\n    \"rtlcss\": \"^4.0.0\",\n    \"sass\": \"^1.55.0\",\n    \"shelljs\": \"^0.8.5\",\n    \"stylelint\": \"^14.13.0\",\n    \"stylelint-config-twbs-bootstrap\": \"^6.0.0\",\n    \"terser\": \"^5.15.0\",\n    \"vnu-jar\": \"22.9.29\"\n  },\n  \"files\": [\n    \"dist/{css,js}/*.{css,js,map}\",\n    \"js/{src,dist}/**/*.{js,map}\",\n    \"scss/**/*.scss\"\n  ],\n  \"hugo-bin\": {\n    \"buildTags\": \"extended\"\n  },\n  \"jspm\": {\n    \"registry\": \"npm\",\n    \"main\": \"js/bootstrap\",\n    \"directories\": {\n      \"lib\": \"dist\"\n    },\n    \"shim\": {\n      \"js/bootstrap\": {\n        \"deps\": [\n          \"@popperjs/core\"\n        ]\n      }\n    },\n    \"dependencies\": {},\n    \"peerDependencies\": {\n      \"@popperjs/core\": \"^2.11.6\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_accordion.scss",
    "content": "//\n// Base styles\n//\n\n.accordion {\n  // scss-docs-start accordion-css-vars\n  --#{$prefix}accordion-color: #{$accordion-color};\n  --#{$prefix}accordion-bg: #{$accordion-bg};\n  --#{$prefix}accordion-transition: #{$accordion-transition};\n  --#{$prefix}accordion-border-color: #{$accordion-border-color};\n  --#{$prefix}accordion-border-width: #{$accordion-border-width};\n  --#{$prefix}accordion-border-radius: #{$accordion-border-radius};\n  --#{$prefix}accordion-inner-border-radius: #{$accordion-inner-border-radius};\n  --#{$prefix}accordion-btn-padding-x: #{$accordion-button-padding-x};\n  --#{$prefix}accordion-btn-padding-y: #{$accordion-button-padding-y};\n  --#{$prefix}accordion-btn-color: #{$accordion-button-color};\n  --#{$prefix}accordion-btn-bg: #{$accordion-button-bg};\n  --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon)};\n  --#{$prefix}accordion-btn-icon-width: #{$accordion-icon-width};\n  --#{$prefix}accordion-btn-icon-transform: #{$accordion-icon-transform};\n  --#{$prefix}accordion-btn-icon-transition: #{$accordion-icon-transition};\n  --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon)};\n  --#{$prefix}accordion-btn-focus-border-color: #{$accordion-button-focus-border-color};\n  --#{$prefix}accordion-btn-focus-box-shadow: #{$accordion-button-focus-box-shadow};\n  --#{$prefix}accordion-body-padding-x: #{$accordion-body-padding-x};\n  --#{$prefix}accordion-body-padding-y: #{$accordion-body-padding-y};\n  --#{$prefix}accordion-active-color: #{$accordion-button-active-color};\n  --#{$prefix}accordion-active-bg: #{$accordion-button-active-bg};\n  // scss-docs-end accordion-css-vars\n}\n\n.accordion-button {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: var(--#{$prefix}accordion-btn-padding-y) var(--#{$prefix}accordion-btn-padding-x);\n  @include font-size($font-size-base);\n  color: var(--#{$prefix}accordion-btn-color);\n  text-align: left; // Reset button style\n  background-color: var(--#{$prefix}accordion-btn-bg);\n  border: 0;\n  @include border-radius(0);\n  overflow-anchor: none;\n  @include transition(var(--#{$prefix}accordion-transition));\n\n  &:not(.collapsed) {\n    color: var(--#{$prefix}accordion-active-color);\n    background-color: var(--#{$prefix}accordion-active-bg);\n    box-shadow: inset 0 calc(-1 * var(--#{$prefix}accordion-border-width)) 0 var(--#{$prefix}accordion-border-color); // stylelint-disable-line function-disallowed-list\n\n    &::after {\n      background-image: var(--#{$prefix}accordion-btn-active-icon);\n      transform: var(--#{$prefix}accordion-btn-icon-transform);\n    }\n  }\n\n  // Accordion icon\n  &::after {\n    flex-shrink: 0;\n    width: var(--#{$prefix}accordion-btn-icon-width);\n    height: var(--#{$prefix}accordion-btn-icon-width);\n    margin-left: auto;\n    content: \"\";\n    background-image: var(--#{$prefix}accordion-btn-icon);\n    background-repeat: no-repeat;\n    background-size: var(--#{$prefix}accordion-btn-icon-width);\n    @include transition(var(--#{$prefix}accordion-btn-icon-transition));\n  }\n\n  &:hover {\n    z-index: 2;\n  }\n\n  &:focus {\n    z-index: 3;\n    border-color: var(--#{$prefix}accordion-btn-focus-border-color);\n    outline: 0;\n    box-shadow: var(--#{$prefix}accordion-btn-focus-box-shadow);\n  }\n}\n\n.accordion-header {\n  margin-bottom: 0;\n}\n\n.accordion-item {\n  color: var(--#{$prefix}accordion-color);\n  background-color: var(--#{$prefix}accordion-bg);\n  border: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color);\n\n  &:first-of-type {\n    @include border-top-radius(var(--#{$prefix}accordion-border-radius));\n\n    .accordion-button {\n      @include border-top-radius(var(--#{$prefix}accordion-inner-border-radius));\n    }\n  }\n\n  &:not(:first-of-type) {\n    border-top: 0;\n  }\n\n  // Only set a border-radius on the last item if the accordion is collapsed\n  &:last-of-type {\n    @include border-bottom-radius(var(--#{$prefix}accordion-border-radius));\n\n    .accordion-button {\n      &.collapsed {\n        @include border-bottom-radius(var(--#{$prefix}accordion-inner-border-radius));\n      }\n    }\n\n    .accordion-collapse {\n      @include border-bottom-radius(var(--#{$prefix}accordion-border-radius));\n    }\n  }\n}\n\n.accordion-body {\n  padding: var(--#{$prefix}accordion-body-padding-y) var(--#{$prefix}accordion-body-padding-x);\n}\n\n\n// Flush accordion items\n//\n// Remove borders and border-radius to keep accordion items edge-to-edge.\n\n.accordion-flush {\n  .accordion-collapse {\n    border-width: 0;\n  }\n\n  .accordion-item {\n    border-right: 0;\n    border-left: 0;\n    @include border-radius(0);\n\n    &:first-child { border-top: 0; }\n    &:last-child { border-bottom: 0; }\n\n    .accordion-button {\n      &,\n      &.collapsed {\n        @include border-radius(0);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_alert.scss",
    "content": "//\n// Base styles\n//\n\n.alert {\n  // scss-docs-start alert-css-vars\n  --#{$prefix}alert-bg: transparent;\n  --#{$prefix}alert-padding-x: #{$alert-padding-x};\n  --#{$prefix}alert-padding-y: #{$alert-padding-y};\n  --#{$prefix}alert-margin-bottom: #{$alert-margin-bottom};\n  --#{$prefix}alert-color: inherit;\n  --#{$prefix}alert-border-color: transparent;\n  --#{$prefix}alert-border: #{$alert-border-width} solid var(--#{$prefix}alert-border-color);\n  --#{$prefix}alert-border-radius: #{$alert-border-radius};\n  // scss-docs-end alert-css-vars\n\n  position: relative;\n  padding: var(--#{$prefix}alert-padding-y) var(--#{$prefix}alert-padding-x);\n  margin-bottom: var(--#{$prefix}alert-margin-bottom);\n  color: var(--#{$prefix}alert-color);\n  background-color: var(--#{$prefix}alert-bg);\n  border: var(--#{$prefix}alert-border);\n  @include border-radius(var(--#{$prefix}alert-border-radius));\n}\n\n// Headings for larger alerts\n.alert-heading {\n  // Specified to prevent conflicts of changing $headings-color\n  color: inherit;\n}\n\n// Provide class for links that match alerts\n.alert-link {\n  font-weight: $alert-link-font-weight;\n}\n\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissible {\n  padding-right: $alert-dismissible-padding-r;\n\n  // Adjust close link position\n  .btn-close {\n    position: absolute;\n    top: 0;\n    right: 0;\n    z-index: $stretched-link-z-index + 1;\n    padding: $alert-padding-y * 1.25 $alert-padding-x;\n  }\n}\n\n\n// scss-docs-start alert-modifiers\n// Generate contextual modifier classes for colorizing the alert.\n\n@each $state, $value in $theme-colors {\n  $alert-background: shift-color($value, $alert-bg-scale);\n  $alert-border: shift-color($value, $alert-border-scale);\n  $alert-color: shift-color($value, $alert-color-scale);\n\n  @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) {\n    $alert-color: mix($value, color-contrast($alert-background), abs($alert-color-scale));\n  }\n  .alert-#{$state} {\n    @include alert-variant($alert-background, $alert-border, $alert-color);\n  }\n}\n// scss-docs-end alert-modifiers\n"
  },
  {
    "path": "src/common/bootstrap/scss/_badge.scss",
    "content": "// Base class\n//\n// Requires one of the contextual, color modifier classes for `color` and\n// `background-color`.\n\n.badge {\n  // scss-docs-start badge-css-vars\n  --#{$prefix}badge-padding-x: #{$badge-padding-x};\n  --#{$prefix}badge-padding-y: #{$badge-padding-y};\n  @include rfs($badge-font-size, --#{$prefix}badge-font-size);\n  --#{$prefix}badge-font-weight: #{$badge-font-weight};\n  --#{$prefix}badge-color: #{$badge-color};\n  --#{$prefix}badge-border-radius: #{$badge-border-radius};\n  // scss-docs-end badge-css-vars\n\n  display: inline-block;\n  padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x);\n  @include font-size(var(--#{$prefix}badge-font-size));\n  font-weight: var(--#{$prefix}badge-font-weight);\n  line-height: 1;\n  color: var(--#{$prefix}badge-color);\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  @include border-radius(var(--#{$prefix}badge-border-radius));\n  @include gradient-bg();\n\n  // Empty badges collapse automatically\n  &:empty {\n    display: none;\n  }\n}\n\n// Quick fix for badges in buttons\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_breadcrumb.scss",
    "content": ".breadcrumb {\n  // scss-docs-start breadcrumb-css-vars\n  --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x};\n  --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y};\n  --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom};\n  @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size);\n  --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg};\n  --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius};\n  --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color};\n  --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x};\n  --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color};\n  // scss-docs-end breadcrumb-css-vars\n\n  display: flex;\n  flex-wrap: wrap;\n  padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x);\n  margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom);\n  @include font-size(var(--#{$prefix}breadcrumb-font-size));\n  list-style: none;\n  background-color: var(--#{$prefix}breadcrumb-bg);\n  @include border-radius(var(--#{$prefix}breadcrumb-border-radius));\n}\n\n.breadcrumb-item {\n  // The separator between breadcrumbs (by default, a forward-slash: \"/\")\n  + .breadcrumb-item {\n    padding-left: var(--#{$prefix}breadcrumb-item-padding-x);\n\n    &::before {\n      float: left; // Suppress inline spacings and underlining of the separator\n      padding-right: var(--#{$prefix}breadcrumb-item-padding-x);\n      color: var(--#{$prefix}breadcrumb-divider-color);\n      content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{\"/* rtl:\"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{\"*/\"};\n    }\n  }\n\n  &.active {\n    color: var(--#{$prefix}breadcrumb-item-active-color);\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_button-group.scss",
    "content": "// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-flex;\n  vertical-align: middle; // match .btn alignment given font-size hack above\n\n  > .btn {\n    position: relative;\n    flex: 1 1 auto;\n  }\n\n  // Bring the hover, focused, and \"active\" buttons to the front to overlay\n  // the borders properly\n  > .btn-check:checked + .btn,\n  > .btn-check:focus + .btn,\n  > .btn:hover,\n  > .btn:focus,\n  > .btn:active,\n  > .btn.active {\n    z-index: 1;\n  }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-start;\n\n  .input-group {\n    width: auto;\n  }\n}\n\n.btn-group {\n  @include border-radius($btn-border-radius);\n\n  // Prevent double borders when buttons are next to each other\n  > :not(.btn-check:first-child) + .btn,\n  > .btn-group:not(:first-child) {\n    margin-left: -$btn-border-width;\n  }\n\n  // Reset rounded corners\n  > .btn:not(:last-child):not(.dropdown-toggle),\n  > .btn.dropdown-toggle-split:first-child,\n  > .btn-group:not(:last-child) > .btn {\n    @include border-end-radius(0);\n  }\n\n  // The left radius should be 0 if the button is:\n  // - the \"third or more\" child\n  // - the second child and the previous element isn't `.btn-check` (making it the first child visually)\n  // - part of a btn-group which isn't the first child\n  > .btn:nth-child(n + 3),\n  > :not(.btn-check) + .btn,\n  > .btn-group:not(:first-child) > .btn {\n    @include border-start-radius(0);\n  }\n}\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-sm > .btn { @extend .btn-sm; }\n.btn-group-lg > .btn { @extend .btn-lg; }\n\n\n//\n// Split button dropdowns\n//\n\n.dropdown-toggle-split {\n  padding-right: $btn-padding-x * .75;\n  padding-left: $btn-padding-x * .75;\n\n  &::after,\n  .dropup &::after,\n  .dropend &::after {\n    margin-left: 0;\n  }\n\n  .dropstart &::before {\n    margin-right: 0;\n  }\n}\n\n.btn-sm + .dropdown-toggle-split {\n  padding-right: $btn-padding-x-sm * .75;\n  padding-left: $btn-padding-x-sm * .75;\n}\n\n.btn-lg + .dropdown-toggle-split {\n  padding-right: $btn-padding-x-lg * .75;\n  padding-left: $btn-padding-x-lg * .75;\n}\n\n\n// The clickable button for toggling the menu\n// Set the same inset shadow as the :active state\n.btn-group.show .dropdown-toggle {\n  @include box-shadow($btn-active-box-shadow);\n\n  // Show no shadow for `.btn-link` since it has no other button styles.\n  &.btn-link {\n    @include box-shadow(none);\n  }\n}\n\n\n//\n// Vertical button groups\n//\n\n.btn-group-vertical {\n  flex-direction: column;\n  align-items: flex-start;\n  justify-content: center;\n\n  > .btn,\n  > .btn-group {\n    width: 100%;\n  }\n\n  > .btn:not(:first-child),\n  > .btn-group:not(:first-child) {\n    margin-top: -$btn-border-width;\n  }\n\n  // Reset rounded corners\n  > .btn:not(:last-child):not(.dropdown-toggle),\n  > .btn-group:not(:last-child) > .btn {\n    @include border-bottom-radius(0);\n  }\n\n  > .btn ~ .btn,\n  > .btn-group:not(:first-child) > .btn {\n    @include border-top-radius(0);\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_buttons.scss",
    "content": "//\n// Base styles\n//\n\n.btn {\n  // scss-docs-start btn-css-vars\n  --#{$prefix}btn-padding-x: #{$btn-padding-x};\n  --#{$prefix}btn-padding-y: #{$btn-padding-y};\n  --#{$prefix}btn-font-family: #{$btn-font-family};\n  @include rfs($btn-font-size, --#{$prefix}btn-font-size);\n  --#{$prefix}btn-font-weight: #{$btn-font-weight};\n  --#{$prefix}btn-line-height: #{$btn-line-height};\n  --#{$prefix}btn-color: #{$body-color};\n  --#{$prefix}btn-bg: transparent;\n  --#{$prefix}btn-border-width: #{$btn-border-width};\n  --#{$prefix}btn-border-color: transparent;\n  --#{$prefix}btn-border-radius: #{$btn-border-radius};\n  --#{$prefix}btn-hover-border-color: transparent;\n  --#{$prefix}btn-box-shadow: #{$btn-box-shadow};\n  --#{$prefix}btn-disabled-opacity: #{$btn-disabled-opacity};\n  --#{$prefix}btn-focus-box-shadow: 0 0 0 #{$btn-focus-width} rgba(var(--#{$prefix}btn-focus-shadow-rgb), .5);\n  // scss-docs-end btn-css-vars\n\n  display: inline-block;\n  padding: var(--#{$prefix}btn-padding-y) var(--#{$prefix}btn-padding-x);\n  font-family: var(--#{$prefix}btn-font-family);\n  @include font-size(var(--#{$prefix}btn-font-size));\n  font-weight: var(--#{$prefix}btn-font-weight);\n  line-height: var(--#{$prefix}btn-line-height);\n  color: var(--#{$prefix}btn-color);\n  text-align: center;\n  text-decoration: if($link-decoration == none, null, none);\n  white-space: $btn-white-space;\n  vertical-align: middle;\n  cursor: if($enable-button-pointers, pointer, null);\n  user-select: none;\n  border: var(--#{$prefix}btn-border-width) solid var(--#{$prefix}btn-border-color);\n  @include border-radius(var(--#{$prefix}btn-border-radius));\n  @include gradient-bg(var(--#{$prefix}btn-bg));\n  @include box-shadow(var(--#{$prefix}btn-box-shadow));\n  @include transition($btn-transition);\n\n  &:hover {\n    color: var(--#{$prefix}btn-hover-color);\n    text-decoration: if($link-hover-decoration == underline, none, null);\n    background-color: var(--#{$prefix}btn-hover-bg);\n    border-color: var(--#{$prefix}btn-hover-border-color);\n  }\n\n  .btn-check + &:hover {\n    // override for the checkbox/radio buttons\n    color: var(--#{$prefix}btn-color);\n    background-color: var(--#{$prefix}btn-bg);\n    border-color: var(--#{$prefix}btn-border-color);\n  }\n\n  &:focus-visible {\n    color: var(--#{$prefix}btn-hover-color);\n    @include gradient-bg(var(--#{$prefix}btn-hover-bg));\n    border-color: var(--#{$prefix}btn-hover-border-color);\n    outline: 0;\n    // Avoid using mixin so we can pass custom focus shadow properly\n    @if $enable-shadows {\n      box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow);\n    } @else {\n      box-shadow: var(--#{$prefix}btn-focus-box-shadow);\n    }\n  }\n\n  .btn-check:focus-visible + & {\n    border-color: var(--#{$prefix}btn-hover-border-color);\n    outline: 0;\n    // Avoid using mixin so we can pass custom focus shadow properly\n    @if $enable-shadows {\n      box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow);\n    } @else {\n      box-shadow: var(--#{$prefix}btn-focus-box-shadow);\n    }\n  }\n\n  .btn-check:checked + &,\n  :not(.btn-check) + &:active,\n  &:first-child:active,\n  &.active,\n  &.show {\n    color: var(--#{$prefix}btn-active-color);\n    background-color: var(--#{$prefix}btn-active-bg);\n    // Remove CSS gradients if they're enabled\n    background-image: if($enable-gradients, none, null);\n    border-color: var(--#{$prefix}btn-active-border-color);\n    @include box-shadow(var(--#{$prefix}btn-active-shadow));\n\n    &:focus-visible {\n      // Avoid using mixin so we can pass custom focus shadow properly\n      @if $enable-shadows {\n        box-shadow: var(--#{$prefix}btn-active-shadow), var(--#{$prefix}btn-focus-box-shadow);\n      } @else {\n        box-shadow: var(--#{$prefix}btn-focus-box-shadow);\n      }\n    }\n  }\n\n  &:disabled,\n  &.disabled,\n  fieldset:disabled & {\n    color: var(--#{$prefix}btn-disabled-color);\n    pointer-events: none;\n    background-color: var(--#{$prefix}btn-disabled-bg);\n    background-image: if($enable-gradients, none, null);\n    border-color: var(--#{$prefix}btn-disabled-border-color);\n    opacity: var(--#{$prefix}btn-disabled-opacity);\n    @include box-shadow(none);\n  }\n}\n\n\n//\n// Alternate buttons\n//\n\n// scss-docs-start btn-variant-loops\n@each $color, $value in $theme-colors {\n  .btn-#{$color} {\n    @if $color == \"light\" {\n      @include button-variant(\n        $value,\n        $value,\n        $hover-background: shade-color($value, $btn-hover-bg-shade-amount),\n        $hover-border: shade-color($value, $btn-hover-border-shade-amount),\n        $active-background: shade-color($value, $btn-active-bg-shade-amount),\n        $active-border: shade-color($value, $btn-active-border-shade-amount)\n      );\n    } @else if $color == \"dark\" {\n      @include button-variant(\n        $value,\n        $value,\n        $hover-background: tint-color($value, $btn-hover-bg-tint-amount),\n        $hover-border: tint-color($value, $btn-hover-border-tint-amount),\n        $active-background: tint-color($value, $btn-active-bg-tint-amount),\n        $active-border: tint-color($value, $btn-active-border-tint-amount)\n      );\n    } @else {\n      @include button-variant($value, $value);\n    }\n  }\n}\n\n@each $color, $value in $theme-colors {\n  .btn-outline-#{$color} {\n    @include button-outline-variant($value);\n  }\n}\n// scss-docs-end btn-variant-loops\n\n\n//\n// Link buttons\n//\n\n// Make a button look and behave like a link\n.btn-link {\n  --#{$prefix}btn-font-weight: #{$font-weight-normal};\n  --#{$prefix}btn-color: #{$btn-link-color};\n  --#{$prefix}btn-bg: transparent;\n  --#{$prefix}btn-border-color: transparent;\n  --#{$prefix}btn-hover-color: #{$btn-link-hover-color};\n  --#{$prefix}btn-hover-border-color: transparent;\n  --#{$prefix}btn-active-color: #{$btn-link-hover-color};\n  --#{$prefix}btn-active-border-color: transparent;\n  --#{$prefix}btn-disabled-color: #{$btn-link-disabled-color};\n  --#{$prefix}btn-disabled-border-color: transparent;\n  --#{$prefix}btn-box-shadow: none;\n  --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix(color-contrast($primary), $primary, 15%))};\n\n  text-decoration: $link-decoration;\n  @if $enable-gradients {\n    background-image: none;\n  }\n\n  &:hover,\n  &:focus-visible {\n    text-decoration: $link-hover-decoration;\n  }\n\n  &:focus-visible {\n    color: var(--#{$prefix}btn-color);\n  }\n\n  &:hover {\n    color: var(--#{$prefix}btn-hover-color);\n  }\n\n  // No need for an active state here\n}\n\n\n//\n// Button Sizes\n//\n\n.btn-lg {\n  @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg);\n}\n\n.btn-sm {\n  @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm);\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_card.scss",
    "content": "//\n// Base styles\n//\n\n.card {\n  // scss-docs-start card-css-vars\n  --#{$prefix}card-spacer-y: #{$card-spacer-y};\n  --#{$prefix}card-spacer-x: #{$card-spacer-x};\n  --#{$prefix}card-title-spacer-y: #{$card-title-spacer-y};\n  --#{$prefix}card-border-width: #{$card-border-width};\n  --#{$prefix}card-border-color: #{$card-border-color};\n  --#{$prefix}card-border-radius: #{$card-border-radius};\n  --#{$prefix}card-box-shadow: #{$card-box-shadow};\n  --#{$prefix}card-inner-border-radius: #{$card-inner-border-radius};\n  --#{$prefix}card-cap-padding-y: #{$card-cap-padding-y};\n  --#{$prefix}card-cap-padding-x: #{$card-cap-padding-x};\n  --#{$prefix}card-cap-bg: #{$card-cap-bg};\n  --#{$prefix}card-cap-color: #{$card-cap-color};\n  --#{$prefix}card-height: #{$card-height};\n  --#{$prefix}card-color: #{$card-color};\n  --#{$prefix}card-bg: #{$card-bg};\n  --#{$prefix}card-img-overlay-padding: #{$card-img-overlay-padding};\n  --#{$prefix}card-group-margin: #{$card-group-margin};\n  // scss-docs-end card-css-vars\n\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106\n  height: var(--#{$prefix}card-height);\n  word-wrap: break-word;\n  background-color: var(--#{$prefix}card-bg);\n  background-clip: border-box;\n  border: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color);\n  @include border-radius(var(--#{$prefix}card-border-radius));\n  @include box-shadow(var(--#{$prefix}card-box-shadow));\n\n  > hr {\n    margin-right: 0;\n    margin-left: 0;\n  }\n\n  > .list-group {\n    border-top: inherit;\n    border-bottom: inherit;\n\n    &:first-child {\n      border-top-width: 0;\n      @include border-top-radius(var(--#{$prefix}card-inner-border-radius));\n    }\n\n    &:last-child  {\n      border-bottom-width: 0;\n      @include border-bottom-radius(var(--#{$prefix}card-inner-border-radius));\n    }\n  }\n\n  // Due to specificity of the above selector (`.card > .list-group`), we must\n  // use a child selector here to prevent double borders.\n  > .card-header + .list-group,\n  > .list-group + .card-footer {\n    border-top: 0;\n  }\n}\n\n.card-body {\n  // Enable `flex-grow: 1` for decks and groups so that card blocks take up\n  // as much space as possible, ensuring footers are aligned to the bottom.\n  flex: 1 1 auto;\n  padding: var(--#{$prefix}card-spacer-y) var(--#{$prefix}card-spacer-x);\n  color: var(--#{$prefix}card-color);\n}\n\n.card-title {\n  margin-bottom: var(--#{$prefix}card-title-spacer-y);\n}\n\n.card-subtitle {\n  margin-top: calc(-.5 * var(--#{$prefix}card-title-spacer-y)); // stylelint-disable-line function-disallowed-list\n  margin-bottom: 0;\n}\n\n.card-text:last-child {\n  margin-bottom: 0;\n}\n\n.card-link {\n  &:hover {\n    text-decoration: if($link-hover-decoration == underline, none, null);\n  }\n\n  + .card-link {\n    margin-left: var(--#{$prefix}card-spacer-x);\n  }\n}\n\n//\n// Optional textual caps\n//\n\n.card-header {\n  padding: var(--#{$prefix}card-cap-padding-y) var(--#{$prefix}card-cap-padding-x);\n  margin-bottom: 0; // Removes the default margin-bottom of <hN>\n  color: var(--#{$prefix}card-cap-color);\n  background-color: var(--#{$prefix}card-cap-bg);\n  border-bottom: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color);\n\n  &:first-child {\n    @include border-radius(var(--#{$prefix}card-inner-border-radius) var(--#{$prefix}card-inner-border-radius) 0 0);\n  }\n}\n\n.card-footer {\n  padding: var(--#{$prefix}card-cap-padding-y) var(--#{$prefix}card-cap-padding-x);\n  color: var(--#{$prefix}card-cap-color);\n  background-color: var(--#{$prefix}card-cap-bg);\n  border-top: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color);\n\n  &:last-child {\n    @include border-radius(0 0 var(--#{$prefix}card-inner-border-radius) var(--#{$prefix}card-inner-border-radius));\n  }\n}\n\n\n//\n// Header navs\n//\n\n.card-header-tabs {\n  margin-right: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list\n  margin-bottom: calc(-1 * var(--#{$prefix}card-cap-padding-y)); // stylelint-disable-line function-disallowed-list\n  margin-left: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list\n  border-bottom: 0;\n\n  .nav-link.active {\n    background-color: var(--#{$prefix}card-bg);\n    border-bottom-color: var(--#{$prefix}card-bg);\n  }\n}\n\n.card-header-pills {\n  margin-right: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list\n  margin-left: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list\n}\n\n// Card image\n.card-img-overlay {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  padding: var(--#{$prefix}card-img-overlay-padding);\n  @include border-radius(var(--#{$prefix}card-inner-border-radius));\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n  width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch\n}\n\n.card-img,\n.card-img-top {\n  @include border-top-radius(var(--#{$prefix}card-inner-border-radius));\n}\n\n.card-img,\n.card-img-bottom {\n  @include border-bottom-radius(var(--#{$prefix}card-inner-border-radius));\n}\n\n\n//\n// Card groups\n//\n\n.card-group {\n  // The child selector allows nested `.card` within `.card-group`\n  // to display properly.\n  > .card {\n    margin-bottom: var(--#{$prefix}card-group-margin);\n  }\n\n  @include media-breakpoint-up(sm) {\n    display: flex;\n    flex-flow: row wrap;\n    // The child selector allows nested `.card` within `.card-group`\n    // to display properly.\n    > .card {\n      // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4\n      flex: 1 0 0%;\n      margin-bottom: 0;\n\n      + .card {\n        margin-left: 0;\n        border-left: 0;\n      }\n\n      // Handle rounded corners\n      @if $enable-rounded {\n        &:not(:last-child) {\n          @include border-end-radius(0);\n\n          .card-img-top,\n          .card-header {\n            // stylelint-disable-next-line property-disallowed-list\n            border-top-right-radius: 0;\n          }\n          .card-img-bottom,\n          .card-footer {\n            // stylelint-disable-next-line property-disallowed-list\n            border-bottom-right-radius: 0;\n          }\n        }\n\n        &:not(:first-child) {\n          @include border-start-radius(0);\n\n          .card-img-top,\n          .card-header {\n            // stylelint-disable-next-line property-disallowed-list\n            border-top-left-radius: 0;\n          }\n          .card-img-bottom,\n          .card-footer {\n            // stylelint-disable-next-line property-disallowed-list\n            border-bottom-left-radius: 0;\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_carousel.scss",
    "content": "// Notes on the classes:\n//\n// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)\n//    even when their scroll action started on a carousel, but for compatibility (with Firefox)\n//    we're preventing all actions instead\n// 2. The .carousel-item-start and .carousel-item-end is used to indicate where\n//    the active slide is heading.\n// 3. .active.carousel-item is the current slide.\n// 4. .active.carousel-item-start and .active.carousel-item-end is the current\n//    slide in its in-transition state. Only one of these occurs at a time.\n// 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end\n//    is the upcoming slide in transition.\n\n.carousel {\n  position: relative;\n}\n\n.carousel.pointer-event {\n  touch-action: pan-y;\n}\n\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n  @include clearfix();\n}\n\n.carousel-item {\n  position: relative;\n  display: none;\n  float: left;\n  width: 100%;\n  margin-right: -100%;\n  backface-visibility: hidden;\n  @include transition($carousel-transition);\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n  display: block;\n}\n\n.carousel-item-next:not(.carousel-item-start),\n.active.carousel-item-end {\n  transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-end),\n.active.carousel-item-start {\n  transform: translateX(-100%);\n}\n\n\n//\n// Alternate transitions\n//\n\n.carousel-fade {\n  .carousel-item {\n    opacity: 0;\n    transition-property: opacity;\n    transform: none;\n  }\n\n  .carousel-item.active,\n  .carousel-item-next.carousel-item-start,\n  .carousel-item-prev.carousel-item-end {\n    z-index: 1;\n    opacity: 1;\n  }\n\n  .active.carousel-item-start,\n  .active.carousel-item-end {\n    z-index: 0;\n    opacity: 0;\n    @include transition(opacity 0s $carousel-transition-duration);\n  }\n}\n\n\n//\n// Left/right controls for nav\n//\n\n.carousel-control-prev,\n.carousel-control-next {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  z-index: 1;\n  // Use flex for alignment (1-3)\n  display: flex; // 1. allow flex styles\n  align-items: center; // 2. vertically center contents\n  justify-content: center; // 3. horizontally center contents\n  width: $carousel-control-width;\n  padding: 0;\n  color: $carousel-control-color;\n  text-align: center;\n  background: none;\n  border: 0;\n  opacity: $carousel-control-opacity;\n  @include transition($carousel-control-transition);\n\n  // Hover/focus state\n  &:hover,\n  &:focus {\n    color: $carousel-control-color;\n    text-decoration: none;\n    outline: 0;\n    opacity: $carousel-control-hover-opacity;\n  }\n}\n.carousel-control-prev {\n  left: 0;\n  background-image: if($enable-gradients, linear-gradient(90deg, rgba($black, .25), rgba($black, .001)), null);\n}\n.carousel-control-next {\n  right: 0;\n  background-image: if($enable-gradients, linear-gradient(270deg, rgba($black, .25), rgba($black, .001)), null);\n}\n\n// Icons for within\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n  display: inline-block;\n  width: $carousel-control-icon-width;\n  height: $carousel-control-icon-width;\n  background-repeat: no-repeat;\n  background-position: 50%;\n  background-size: 100% 100%;\n}\n\n/* rtl:options: {\n  \"autoRename\": true,\n  \"stringMap\":[ {\n    \"name\"    : \"prev-next\",\n    \"search\"  : \"prev\",\n    \"replace\" : \"next\"\n  } ]\n} */\n.carousel-control-prev-icon {\n  background-image: escape-svg($carousel-control-prev-icon-bg);\n}\n.carousel-control-next-icon {\n  background-image: escape-svg($carousel-control-next-icon-bg);\n}\n\n// Optional indicator pips/controls\n//\n// Add a container (such as a list) with the following class and add an item (ideally a focusable control,\n// like a button) with data-bs-target for each slide your carousel holds.\n\n.carousel-indicators {\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 2;\n  display: flex;\n  justify-content: center;\n  padding: 0;\n  // Use the .carousel-control's width as margin so we don't overlay those\n  margin-right: $carousel-control-width;\n  margin-bottom: 1rem;\n  margin-left: $carousel-control-width;\n  list-style: none;\n\n  [data-bs-target] {\n    box-sizing: content-box;\n    flex: 0 1 auto;\n    width: $carousel-indicator-width;\n    height: $carousel-indicator-height;\n    padding: 0;\n    margin-right: $carousel-indicator-spacer;\n    margin-left: $carousel-indicator-spacer;\n    text-indent: -999px;\n    cursor: pointer;\n    background-color: $carousel-indicator-active-bg;\n    background-clip: padding-box;\n    border: 0;\n    // Use transparent borders to increase the hit area by 10px on top and bottom.\n    border-top: $carousel-indicator-hit-area-height solid transparent;\n    border-bottom: $carousel-indicator-hit-area-height solid transparent;\n    opacity: $carousel-indicator-opacity;\n    @include transition($carousel-indicator-transition);\n  }\n\n  .active {\n    opacity: $carousel-indicator-active-opacity;\n  }\n}\n\n\n// Optional captions\n//\n//\n\n.carousel-caption {\n  position: absolute;\n  right: (100% - $carousel-caption-width) * .5;\n  bottom: $carousel-caption-spacer;\n  left: (100% - $carousel-caption-width) * .5;\n  padding-top: $carousel-caption-padding-y;\n  padding-bottom: $carousel-caption-padding-y;\n  color: $carousel-caption-color;\n  text-align: center;\n}\n\n// Dark mode carousel\n\n.carousel-dark {\n  .carousel-control-prev-icon,\n  .carousel-control-next-icon {\n    filter: $carousel-dark-control-icon-filter;\n  }\n\n  .carousel-indicators [data-bs-target] {\n    background-color: $carousel-dark-indicator-active-bg;\n  }\n\n  .carousel-caption {\n    color: $carousel-dark-caption-color;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_close.scss",
    "content": "// Transparent background and border properties included for button version.\n// iOS requires the button element instead of an anchor tag.\n// If you want the anchor version, it requires `href=\"#\"`.\n// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n.btn-close {\n  box-sizing: content-box;\n  width: $btn-close-width;\n  height: $btn-close-height;\n  padding: $btn-close-padding-y $btn-close-padding-x;\n  color: $btn-close-color;\n  background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements\n  border: 0; // for button elements\n  @include border-radius();\n  opacity: $btn-close-opacity;\n\n  // Override <a>'s hover style\n  &:hover {\n    color: $btn-close-color;\n    text-decoration: none;\n    opacity: $btn-close-hover-opacity;\n  }\n\n  &:focus {\n    outline: 0;\n    box-shadow: $btn-close-focus-shadow;\n    opacity: $btn-close-focus-opacity;\n  }\n\n  &:disabled,\n  &.disabled {\n    pointer-events: none;\n    user-select: none;\n    opacity: $btn-close-disabled-opacity;\n  }\n}\n\n.btn-close-white {\n  filter: $btn-close-white-filter;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_containers.scss",
    "content": "// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n@if $enable-container-classes {\n  // Single container class with breakpoint max-widths\n  .container,\n  // 100% wide container at all breakpoints\n  .container-fluid {\n    @include make-container();\n  }\n\n  // Responsive containers that are 100% wide until a breakpoint\n  @each $breakpoint, $container-max-width in $container-max-widths {\n    .container-#{$breakpoint} {\n      @extend .container-fluid;\n    }\n\n    @include media-breakpoint-up($breakpoint, $grid-breakpoints) {\n      %responsive-container-#{$breakpoint} {\n        max-width: $container-max-width;\n      }\n\n      // Extend each breakpoint which is smaller or equal to the current breakpoint\n      $extend-breakpoint: true;\n\n      @each $name, $width in $grid-breakpoints {\n        @if ($extend-breakpoint) {\n          .container#{breakpoint-infix($name, $grid-breakpoints)} {\n            @extend %responsive-container-#{$breakpoint};\n          }\n\n          // Once the current breakpoint is reached, stop extending\n          @if ($breakpoint == $name) {\n            $extend-breakpoint: false;\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_dropdown.scss",
    "content": "// The dropdown wrapper (`<div>`)\n.dropup,\n.dropend,\n.dropdown,\n.dropstart,\n.dropup-center,\n.dropdown-center {\n  position: relative;\n}\n\n.dropdown-toggle {\n  white-space: nowrap;\n\n  // Generate the caret automatically\n  @include caret();\n}\n\n// The dropdown menu\n.dropdown-menu {\n  // scss-docs-start dropdown-css-vars\n  --#{$prefix}dropdown-zindex: #{$zindex-dropdown};\n  --#{$prefix}dropdown-min-width: #{$dropdown-min-width};\n  --#{$prefix}dropdown-padding-x: #{$dropdown-padding-x};\n  --#{$prefix}dropdown-padding-y: #{$dropdown-padding-y};\n  --#{$prefix}dropdown-spacer: #{$dropdown-spacer};\n  @include rfs($dropdown-font-size, --#{$prefix}dropdown-font-size);\n  --#{$prefix}dropdown-color: #{$dropdown-color};\n  --#{$prefix}dropdown-bg: #{$dropdown-bg};\n  --#{$prefix}dropdown-border-color: #{$dropdown-border-color};\n  --#{$prefix}dropdown-border-radius: #{$dropdown-border-radius};\n  --#{$prefix}dropdown-border-width: #{$dropdown-border-width};\n  --#{$prefix}dropdown-inner-border-radius: #{$dropdown-inner-border-radius};\n  --#{$prefix}dropdown-divider-bg: #{$dropdown-divider-bg};\n  --#{$prefix}dropdown-divider-margin-y: #{$dropdown-divider-margin-y};\n  --#{$prefix}dropdown-box-shadow: #{$dropdown-box-shadow};\n  --#{$prefix}dropdown-link-color: #{$dropdown-link-color};\n  --#{$prefix}dropdown-link-hover-color: #{$dropdown-link-hover-color};\n  --#{$prefix}dropdown-link-hover-bg: #{$dropdown-link-hover-bg};\n  --#{$prefix}dropdown-link-active-color: #{$dropdown-link-active-color};\n  --#{$prefix}dropdown-link-active-bg: #{$dropdown-link-active-bg};\n  --#{$prefix}dropdown-link-disabled-color: #{$dropdown-link-disabled-color};\n  --#{$prefix}dropdown-item-padding-x: #{$dropdown-item-padding-x};\n  --#{$prefix}dropdown-item-padding-y: #{$dropdown-item-padding-y};\n  --#{$prefix}dropdown-header-color: #{$dropdown-header-color};\n  --#{$prefix}dropdown-header-padding-x: #{$dropdown-header-padding-x};\n  --#{$prefix}dropdown-header-padding-y: #{$dropdown-header-padding-y};\n  // scss-docs-end dropdown-css-vars\n\n  position: absolute;\n  z-index: var(--#{$prefix}dropdown-zindex);\n  display: none; // none by default, but block on \"open\" of the menu\n  min-width: var(--#{$prefix}dropdown-min-width);\n  padding: var(--#{$prefix}dropdown-padding-y) var(--#{$prefix}dropdown-padding-x);\n  margin: 0; // Override default margin of ul\n  @include font-size(var(--#{$prefix}dropdown-font-size));\n  color: var(--#{$prefix}dropdown-color);\n  text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n  list-style: none;\n  background-color: var(--#{$prefix}dropdown-bg);\n  background-clip: padding-box;\n  border: var(--#{$prefix}dropdown-border-width) solid var(--#{$prefix}dropdown-border-color);\n  @include border-radius(var(--#{$prefix}dropdown-border-radius));\n  @include box-shadow(var(--#{$prefix}dropdown-box-shadow));\n\n  &[data-bs-popper] {\n    top: 100%;\n    left: 0;\n    margin-top: var(--#{$prefix}dropdown-spacer);\n  }\n\n  @if $dropdown-padding-y == 0 {\n    > .dropdown-item:first-child,\n    > li:first-child .dropdown-item {\n      @include border-top-radius(var(--#{$prefix}dropdown-inner-border-radius));\n    }\n    > .dropdown-item:last-child,\n    > li:last-child .dropdown-item {\n      @include border-bottom-radius(var(--#{$prefix}dropdown-inner-border-radius));\n    }\n\n  }\n}\n\n// scss-docs-start responsive-breakpoints\n// We deliberately hardcode the `bs-` prefix because we check\n// this custom property in JS to determine Popper's positioning\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n  @include media-breakpoint-up($breakpoint) {\n    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n    .dropdown-menu#{$infix}-start {\n      --bs-position: start;\n\n      &[data-bs-popper] {\n        right: auto;\n        left: 0;\n      }\n    }\n\n    .dropdown-menu#{$infix}-end {\n      --bs-position: end;\n\n      &[data-bs-popper] {\n        right: 0;\n        left: auto;\n      }\n    }\n  }\n}\n// scss-docs-end responsive-breakpoints\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n// Just add .dropup after the standard .dropdown class and you're set.\n.dropup {\n  .dropdown-menu[data-bs-popper] {\n    top: auto;\n    bottom: 100%;\n    margin-top: 0;\n    margin-bottom: var(--#{$prefix}dropdown-spacer);\n  }\n\n  .dropdown-toggle {\n    @include caret(up);\n  }\n}\n\n.dropend {\n  .dropdown-menu[data-bs-popper] {\n    top: 0;\n    right: auto;\n    left: 100%;\n    margin-top: 0;\n    margin-left: var(--#{$prefix}dropdown-spacer);\n  }\n\n  .dropdown-toggle {\n    @include caret(end);\n    &::after {\n      vertical-align: 0;\n    }\n  }\n}\n\n.dropstart {\n  .dropdown-menu[data-bs-popper] {\n    top: 0;\n    right: 100%;\n    left: auto;\n    margin-top: 0;\n    margin-right: var(--#{$prefix}dropdown-spacer);\n  }\n\n  .dropdown-toggle {\n    @include caret(start);\n    &::before {\n      vertical-align: 0;\n    }\n  }\n}\n\n\n// Dividers (basically an `<hr>`) within the dropdown\n.dropdown-divider {\n  height: 0;\n  margin: var(--#{$prefix}dropdown-divider-margin-y) 0;\n  overflow: hidden;\n  border-top: 1px solid var(--#{$prefix}dropdown-divider-bg);\n  opacity: 1; // Revisit in v6 to de-dupe styles that conflict with <hr> element\n}\n\n// Links, buttons, and more within the dropdown menu\n//\n// `<button>`-specific styles are denoted with `// For <button>s`\n.dropdown-item {\n  display: block;\n  width: 100%; // For `<button>`s\n  padding: var(--#{$prefix}dropdown-item-padding-y) var(--#{$prefix}dropdown-item-padding-x);\n  clear: both;\n  font-weight: $font-weight-normal;\n  color: var(--#{$prefix}dropdown-link-color);\n  text-align: inherit; // For `<button>`s\n  text-decoration: if($link-decoration == none, null, none);\n  white-space: nowrap; // prevent links from randomly breaking onto new lines\n  background-color: transparent; // For `<button>`s\n  border: 0; // For `<button>`s\n\n  &:hover,\n  &:focus {\n    color: var(--#{$prefix}dropdown-link-hover-color);\n    text-decoration: if($link-hover-decoration == underline, none, null);\n    @include gradient-bg(var(--#{$prefix}dropdown-link-hover-bg));\n  }\n\n  &.active,\n  &:active {\n    color: var(--#{$prefix}dropdown-link-active-color);\n    text-decoration: none;\n    @include gradient-bg(var(--#{$prefix}dropdown-link-active-bg));\n  }\n\n  &.disabled,\n  &:disabled {\n    color: var(--#{$prefix}dropdown-link-disabled-color);\n    pointer-events: none;\n    background-color: transparent;\n    // Remove CSS gradients if they're enabled\n    background-image: if($enable-gradients, none, null);\n  }\n}\n\n.dropdown-menu.show {\n  display: block;\n}\n\n// Dropdown section headers\n.dropdown-header {\n  display: block;\n  padding: var(--#{$prefix}dropdown-header-padding-y) var(--#{$prefix}dropdown-header-padding-x);\n  margin-bottom: 0; // for use with heading elements\n  @include font-size($font-size-sm);\n  color: var(--#{$prefix}dropdown-header-color);\n  white-space: nowrap; // as with > li > a\n}\n\n// Dropdown text\n.dropdown-item-text {\n  display: block;\n  padding: var(--#{$prefix}dropdown-item-padding-y) var(--#{$prefix}dropdown-item-padding-x);\n  color: var(--#{$prefix}dropdown-link-color);\n}\n\n// Dark dropdowns\n.dropdown-menu-dark {\n  // scss-docs-start dropdown-dark-css-vars\n  --#{$prefix}dropdown-color: #{$dropdown-dark-color};\n  --#{$prefix}dropdown-bg: #{$dropdown-dark-bg};\n  --#{$prefix}dropdown-border-color: #{$dropdown-dark-border-color};\n  --#{$prefix}dropdown-box-shadow: #{$dropdown-dark-box-shadow};\n  --#{$prefix}dropdown-link-color: #{$dropdown-dark-link-color};\n  --#{$prefix}dropdown-link-hover-color: #{$dropdown-dark-link-hover-color};\n  --#{$prefix}dropdown-divider-bg: #{$dropdown-dark-divider-bg};\n  --#{$prefix}dropdown-link-hover-bg: #{$dropdown-dark-link-hover-bg};\n  --#{$prefix}dropdown-link-active-color: #{$dropdown-dark-link-active-color};\n  --#{$prefix}dropdown-link-active-bg: #{$dropdown-dark-link-active-bg};\n  --#{$prefix}dropdown-link-disabled-color: #{$dropdown-dark-link-disabled-color};\n  --#{$prefix}dropdown-header-color: #{$dropdown-dark-header-color};\n  // scss-docs-end dropdown-dark-css-vars\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_forms.scss",
    "content": "@import \"forms/labels\";\n@import \"forms/form-text\";\n@import \"forms/form-control\";\n@import \"forms/form-select\";\n@import \"forms/form-check\";\n@import \"forms/form-range\";\n@import \"forms/floating-labels\";\n@import \"forms/input-group\";\n@import \"forms/validation\";\n"
  },
  {
    "path": "src/common/bootstrap/scss/_functions.scss",
    "content": "// Bootstrap functions\n//\n// Utility mixins and functions for evaluating source code across our variables, maps, and mixins.\n\n// Ascending\n// Used to evaluate Sass maps like our grid breakpoints.\n@mixin _assert-ascending($map, $map-name) {\n  $prev-key: null;\n  $prev-num: null;\n  @each $key, $num in $map {\n    @if $prev-num == null or unit($num) == \"%\" or unit($prev-num) == \"%\" {\n      // Do nothing\n    } @else if not comparable($prev-num, $num) {\n      @warn \"Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !\";\n    } @else if $prev-num >= $num {\n      @warn \"Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !\";\n    }\n    $prev-key: $key;\n    $prev-num: $num;\n  }\n}\n\n// Starts at zero\n// Used to ensure the min-width of the lowest breakpoint starts at 0.\n@mixin _assert-starts-at-zero($map, $map-name: \"$grid-breakpoints\") {\n  @if length($map) > 0 {\n    $values: map-values($map);\n    $first-value: nth($values, 1);\n    @if $first-value != 0 {\n      @warn \"First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}.\";\n    }\n  }\n}\n\n// Colors\n@function to-rgb($value) {\n  @return red($value), green($value), blue($value);\n}\n\n// stylelint-disable scss/dollar-variable-pattern\n@function rgba-css-var($identifier, $target) {\n  @if $identifier == \"body\" and $target == \"bg\" {\n    @return rgba(var(--#{$prefix}#{$identifier}-bg-rgb), var(--#{$prefix}#{$target}-opacity));\n  } @if $identifier == \"body\" and $target == \"text\" {\n    @return rgba(var(--#{$prefix}#{$identifier}-color-rgb), var(--#{$prefix}#{$target}-opacity));\n  } @else {\n    @return rgba(var(--#{$prefix}#{$identifier}-rgb), var(--#{$prefix}#{$target}-opacity));\n  }\n}\n\n@function map-loop($map, $func, $args...) {\n  $_map: ();\n\n  @each $key, $value in $map {\n    // allow to pass the $key and $value of the map as an function argument\n    $_args: ();\n    @each $arg in $args {\n      $_args: append($_args, if($arg == \"$key\", $key, if($arg == \"$value\", $value, $arg)));\n    }\n\n    $_map: map-merge($_map, ($key: call(get-function($func), $_args...)));\n  }\n\n  @return $_map;\n}\n// stylelint-enable scss/dollar-variable-pattern\n\n@function varify($list) {\n  $result: null;\n  @each $entry in $list {\n    $result: append($result, var(--#{$prefix}#{$entry}), space);\n  }\n  @return $result;\n}\n\n// Internal Bootstrap function to turn maps into its negative variant.\n// It prefixes the keys with `n` and makes the value negative.\n@function negativify-map($map) {\n  $result: ();\n  @each $key, $value in $map {\n    @if $key != 0 {\n      $result: map-merge($result, (\"n\" + $key: (-$value)));\n    }\n  }\n  @return $result;\n}\n\n// Get multiple keys from a sass map\n@function map-get-multiple($map, $values) {\n  $result: ();\n  @each $key, $value in $map {\n    @if (index($values, $key) != null) {\n      $result: map-merge($result, ($key: $value));\n    }\n  }\n  @return $result;\n}\n\n// Merge multiple maps\n@function map-merge-multiple($maps...) {\n  $merged-maps: ();\n\n  @each $map in $maps {\n    $merged-maps: map-merge($merged-maps, $map);\n  }\n  @return $merged-maps;\n}\n\n// Replace `$search` with `$replace` in `$string`\n// Used on our SVG icon backgrounds for custom forms.\n//\n// @author Kitty Giraudel\n// @param {String} $string - Initial string\n// @param {String} $search - Substring to replace\n// @param {String} $replace ('') - New value\n// @return {String} - Updated string\n@function str-replace($string, $search, $replace: \"\") {\n  $index: str-index($string, $search);\n\n  @if $index {\n    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n  }\n\n  @return $string;\n}\n\n// See https://codepen.io/kevinweber/pen/dXWoRw\n//\n// Requires the use of quotes around data URIs.\n\n@function escape-svg($string) {\n  @if str-index($string, \"data:image/svg+xml\") {\n    @each $char, $encoded in $escaped-characters {\n      // Do not escape the url brackets\n      @if str-index($string, \"url(\") == 1 {\n        $string: url(\"#{str-replace(str-slice($string, 6, -3), $char, $encoded)}\");\n      } @else {\n        $string: str-replace($string, $char, $encoded);\n      }\n    }\n  }\n\n  @return $string;\n}\n\n// Color contrast\n// See https://github.com/twbs/bootstrap/pull/30168\n\n// A list of pre-calculated numbers of pow(divide((divide($value, 255) + .055), 1.055), 2.4). (from 0 to 255)\n// stylelint-disable-next-line scss/dollar-variable-default, scss/dollar-variable-pattern\n$_luminance-list: .0008 .001 .0011 .0013 .0015 .0017 .002 .0022 .0025 .0027 .003 .0033 .0037 .004 .0044 .0048 .0052 .0056 .006 .0065 .007 .0075 .008 .0086 .0091 .0097 .0103 .011 .0116 .0123 .013 .0137 .0144 .0152 .016 .0168 .0176 .0185 .0194 .0203 .0212 .0222 .0232 .0242 .0252 .0262 .0273 .0284 .0296 .0307 .0319 .0331 .0343 .0356 .0369 .0382 .0395 .0409 .0423 .0437 .0452 .0467 .0482 .0497 .0513 .0529 .0545 .0561 .0578 .0595 .0612 .063 .0648 .0666 .0685 .0704 .0723 .0742 .0762 .0782 .0802 .0823 .0844 .0865 .0887 .0908 .0931 .0953 .0976 .0999 .1022 .1046 .107 .1095 .1119 .1144 .117 .1195 .1221 .1248 .1274 .1301 .1329 .1356 .1384 .1413 .1441 .147 .15 .1529 .1559 .159 .162 .1651 .1683 .1714 .1746 .1779 .1812 .1845 .1878 .1912 .1946 .1981 .2016 .2051 .2086 .2122 .2159 .2195 .2232 .227 .2307 .2346 .2384 .2423 .2462 .2502 .2542 .2582 .2623 .2664 .2705 .2747 .2789 .2831 .2874 .2918 .2961 .3005 .305 .3095 .314 .3185 .3231 .3278 .3325 .3372 .3419 .3467 .3515 .3564 .3613 .3663 .3712 .3763 .3813 .3864 .3916 .3968 .402 .4072 .4125 .4179 .4233 .4287 .4342 .4397 .4452 .4508 .4564 .4621 .4678 .4735 .4793 .4851 .491 .4969 .5029 .5089 .5149 .521 .5271 .5333 .5395 .5457 .552 .5583 .5647 .5711 .5776 .5841 .5906 .5972 .6038 .6105 .6172 .624 .6308 .6376 .6445 .6514 .6584 .6654 .6724 .6795 .6867 .6939 .7011 .7084 .7157 .7231 .7305 .7379 .7454 .7529 .7605 .7682 .7758 .7835 .7913 .7991 .807 .8148 .8228 .8308 .8388 .8469 .855 .8632 .8714 .8796 .8879 .8963 .9047 .9131 .9216 .9301 .9387 .9473 .956 .9647 .9734 .9823 .9911 1;\n\n@function color-contrast($background, $color-contrast-dark: $color-contrast-dark, $color-contrast-light: $color-contrast-light, $min-contrast-ratio: $min-contrast-ratio) {\n  $foregrounds: $color-contrast-light, $color-contrast-dark, $white, $black;\n  $max-ratio: 0;\n  $max-ratio-color: null;\n\n  @each $color in $foregrounds {\n    $contrast-ratio: contrast-ratio($background, $color);\n    @if $contrast-ratio > $min-contrast-ratio {\n      @return $color;\n    } @else if $contrast-ratio > $max-ratio {\n      $max-ratio: $contrast-ratio;\n      $max-ratio-color: $color;\n    }\n  }\n\n  @warn \"Found no color leading to #{$min-contrast-ratio}:1 contrast ratio against #{$background}...\";\n\n  @return $max-ratio-color;\n}\n\n@function contrast-ratio($background, $foreground: $color-contrast-light) {\n  $l1: luminance($background);\n  $l2: luminance(opaque($background, $foreground));\n\n  @return if($l1 > $l2, divide($l1 + .05, $l2 + .05), divide($l2 + .05, $l1 + .05));\n}\n\n// Return WCAG2.1 relative luminance\n// See https://www.w3.org/TR/WCAG/#dfn-relative-luminance\n// See https://www.w3.org/TR/WCAG/#dfn-contrast-ratio\n@function luminance($color) {\n  $rgb: (\n    \"r\": red($color),\n    \"g\": green($color),\n    \"b\": blue($color)\n  );\n\n  @each $name, $value in $rgb {\n    $value: if(divide($value, 255) < .03928, divide(divide($value, 255), 12.92), nth($_luminance-list, $value + 1));\n    $rgb: map-merge($rgb, ($name: $value));\n  }\n\n  @return (map-get($rgb, \"r\") * .2126) + (map-get($rgb, \"g\") * .7152) + (map-get($rgb, \"b\") * .0722);\n}\n\n// Return opaque color\n// opaque(#fff, rgba(0, 0, 0, .5)) => #808080\n@function opaque($background, $foreground) {\n  @return mix(rgba($foreground, 1), $background, opacity($foreground) * 100%);\n}\n\n// scss-docs-start color-functions\n// Tint a color: mix a color with white\n@function tint-color($color, $weight) {\n  @return mix(white, $color, $weight);\n}\n\n// Shade a color: mix a color with black\n@function shade-color($color, $weight) {\n  @return mix(black, $color, $weight);\n}\n\n// Shade the color if the weight is positive, else tint it\n@function shift-color($color, $weight) {\n  @return if($weight > 0, shade-color($color, $weight), tint-color($color, -$weight));\n}\n// scss-docs-end color-functions\n\n// Return valid calc\n@function add($value1, $value2, $return-calc: true) {\n  @if $value1 == null {\n    @return $value2;\n  }\n\n  @if $value2 == null {\n    @return $value1;\n  }\n\n  @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) {\n    @return $value1 + $value2;\n  }\n\n  @return if($return-calc == true, calc(#{$value1} + #{$value2}), $value1 + unquote(\" + \") + $value2);\n}\n\n@function subtract($value1, $value2, $return-calc: true) {\n  @if $value1 == null and $value2 == null {\n    @return null;\n  }\n\n  @if $value1 == null {\n    @return -$value2;\n  }\n\n  @if $value2 == null {\n    @return $value1;\n  }\n\n  @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) {\n    @return $value1 - $value2;\n  }\n\n  @if type-of($value2) != number {\n    $value2: unquote(\"(\") + $value2 + unquote(\")\");\n  }\n\n  @return if($return-calc == true, calc(#{$value1} - #{$value2}), $value1 + unquote(\" - \") + $value2);\n}\n\n@function divide($dividend, $divisor, $precision: 10) {\n  $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1);\n  $dividend: abs($dividend);\n  $divisor: abs($divisor);\n  @if $dividend == 0 {\n    @return 0;\n  }\n  @if $divisor == 0 {\n    @error \"Cannot divide by 0\";\n  }\n  $remainder: $dividend;\n  $result: 0;\n  $factor: 10;\n  @while ($remainder > 0 and $precision >= 0) {\n    $quotient: 0;\n    @while ($remainder >= $divisor) {\n      $remainder: $remainder - $divisor;\n      $quotient: $quotient + 1;\n    }\n    $result: $result * 10 + $quotient;\n    $factor: $factor * .1;\n    $remainder: $remainder * 10;\n    $precision: $precision - 1;\n    @if ($precision < 0 and $remainder >= $divisor * 5) {\n      $result: $result + 1;\n    }\n  }\n  $result: $result * $factor * $sign;\n  $dividend-unit: unit($dividend);\n  $divisor-unit: unit($divisor);\n  $unit-map: (\n    \"px\": 1px,\n    \"rem\": 1rem,\n    \"em\": 1em,\n    \"%\": 1%\n  );\n  @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) {\n    $result: $result * map-get($unit-map, $dividend-unit);\n  }\n  @return $result;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_grid.scss",
    "content": "// Row\n//\n// Rows contain your columns.\n\n@if $enable-grid-classes {\n  .row {\n    @include make-row();\n\n    > * {\n      @include make-col-ready();\n    }\n  }\n}\n\n@if $enable-cssgrid {\n  .grid {\n    display: grid;\n    grid-template-rows: repeat(var(--#{$prefix}rows, 1), 1fr);\n    grid-template-columns: repeat(var(--#{$prefix}columns, #{$grid-columns}), 1fr);\n    gap: var(--#{$prefix}gap, #{$grid-gutter-width});\n\n    @include make-cssgrid();\n  }\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n@if $enable-grid-classes {\n  @include make-grid-columns();\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_helpers.scss",
    "content": "@import \"helpers/clearfix\";\n@import \"helpers/color-bg\";\n@import \"helpers/colored-links\";\n@import \"helpers/ratio\";\n@import \"helpers/position\";\n@import \"helpers/stacks\";\n@import \"helpers/visually-hidden\";\n@import \"helpers/stretched-link\";\n@import \"helpers/text-truncation\";\n@import \"helpers/vr\";\n"
  },
  {
    "path": "src/common/bootstrap/scss/_images.scss",
    "content": "// Responsive images (ensure images don't scale beyond their parents)\n//\n// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s.\n// We previously tried the \"images are responsive by default\" approach in Bootstrap v2,\n// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)\n// which weren't expecting the images within themselves to be involuntarily resized.\n// See also https://github.com/twbs/bootstrap/issues/18178\n.img-fluid {\n  @include img-fluid();\n}\n\n\n// Image thumbnails\n.img-thumbnail {\n  padding: $thumbnail-padding;\n  background-color: $thumbnail-bg;\n  border: $thumbnail-border-width solid $thumbnail-border-color;\n  @include border-radius($thumbnail-border-radius);\n  @include box-shadow($thumbnail-box-shadow);\n\n  // Keep them at most 100% wide\n  @include img-fluid();\n}\n\n//\n// Figures\n//\n\n.figure {\n  // Ensures the caption's text aligns with the image.\n  display: inline-block;\n}\n\n.figure-img {\n  margin-bottom: $spacer * .5;\n  line-height: 1;\n}\n\n.figure-caption {\n  @include font-size($figure-caption-font-size);\n  color: $figure-caption-color;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_list-group.scss",
    "content": "// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n  // scss-docs-start list-group-css-vars\n  --#{$prefix}list-group-color: #{$list-group-color};\n  --#{$prefix}list-group-bg: #{$list-group-bg};\n  --#{$prefix}list-group-border-color: #{$list-group-border-color};\n  --#{$prefix}list-group-border-width: #{$list-group-border-width};\n  --#{$prefix}list-group-border-radius: #{$list-group-border-radius};\n  --#{$prefix}list-group-item-padding-x: #{$list-group-item-padding-x};\n  --#{$prefix}list-group-item-padding-y: #{$list-group-item-padding-y};\n  --#{$prefix}list-group-action-color: #{$list-group-action-color};\n  --#{$prefix}list-group-action-hover-color: #{$list-group-action-hover-color};\n  --#{$prefix}list-group-action-hover-bg: #{$list-group-hover-bg};\n  --#{$prefix}list-group-action-active-color: #{$list-group-action-active-color};\n  --#{$prefix}list-group-action-active-bg: #{$list-group-action-active-bg};\n  --#{$prefix}list-group-disabled-color: #{$list-group-disabled-color};\n  --#{$prefix}list-group-disabled-bg: #{$list-group-disabled-bg};\n  --#{$prefix}list-group-active-color: #{$list-group-active-color};\n  --#{$prefix}list-group-active-bg: #{$list-group-active-bg};\n  --#{$prefix}list-group-active-border-color: #{$list-group-active-border-color};\n  // scss-docs-end list-group-css-vars\n\n  display: flex;\n  flex-direction: column;\n\n  // No need to set list-style: none; since .list-group-item is block level\n  padding-left: 0; // reset padding because ul and ol\n  margin-bottom: 0;\n  @include border-radius(var(--#{$prefix}list-group-border-radius));\n}\n\n.list-group-numbered {\n  list-style-type: none;\n  counter-reset: section;\n\n  > .list-group-item::before {\n    // Increments only this instance of the section counter\n    content: counters(section, \".\") \". \";\n    counter-increment: section;\n  }\n}\n\n// Interactive list items\n//\n// Use anchor or button elements instead of `li`s or `div`s to create interactive\n// list items. Includes an extra `.active` modifier class for selected items.\n\n.list-group-item-action {\n  width: 100%; // For `<button>`s (anchors become 100% by default though)\n  color: var(--#{$prefix}list-group-action-color);\n  text-align: inherit; // For `<button>`s (anchors inherit)\n\n  // Hover state\n  &:hover,\n  &:focus {\n    z-index: 1; // Place hover/focus items above their siblings for proper border styling\n    color: var(--#{$prefix}list-group-action-hover-color);\n    text-decoration: none;\n    background-color: var(--#{$prefix}list-group-action-hover-bg);\n  }\n\n  &:active {\n    color: var(--#{$prefix}list-group-action-active-color);\n    background-color: var(--#{$prefix}list-group-action-active-bg);\n  }\n}\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: var(--#{$prefix}list-group-item-padding-y) var(--#{$prefix}list-group-item-padding-x);\n  color: var(--#{$prefix}list-group-color);\n  text-decoration: if($link-decoration == none, null, none);\n  background-color: var(--#{$prefix}list-group-bg);\n  border: var(--#{$prefix}list-group-border-width) solid var(--#{$prefix}list-group-border-color);\n\n  &:first-child {\n    @include border-top-radius(inherit);\n  }\n\n  &:last-child {\n    @include border-bottom-radius(inherit);\n  }\n\n  &.disabled,\n  &:disabled {\n    color: var(--#{$prefix}list-group-disabled-color);\n    pointer-events: none;\n    background-color: var(--#{$prefix}list-group-disabled-bg);\n  }\n\n  // Include both here for `<a>`s and `<button>`s\n  &.active {\n    z-index: 2; // Place active items above their siblings for proper border styling\n    color: var(--#{$prefix}list-group-active-color);\n    background-color: var(--#{$prefix}list-group-active-bg);\n    border-color: var(--#{$prefix}list-group-active-border-color);\n  }\n\n  // stylelint-disable-next-line scss/selector-no-redundant-nesting-selector\n  & + .list-group-item {\n    border-top-width: 0;\n\n    &.active {\n      margin-top: calc(-1 * var(--#{$prefix}list-group-border-width)); // stylelint-disable-line function-disallowed-list\n      border-top-width: var(--#{$prefix}list-group-border-width);\n    }\n  }\n}\n\n// Horizontal\n//\n// Change the layout of list group items from vertical (default) to horizontal.\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n  @include media-breakpoint-up($breakpoint) {\n    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n    .list-group-horizontal#{$infix} {\n      flex-direction: row;\n\n      > .list-group-item {\n        &:first-child:not(:last-child) {\n          @include border-bottom-start-radius(var(--#{$prefix}list-group-border-radius));\n          @include border-top-end-radius(0);\n        }\n\n        &:last-child:not(:first-child) {\n          @include border-top-end-radius(var(--#{$prefix}list-group-border-radius));\n          @include border-bottom-start-radius(0);\n        }\n\n        &.active {\n          margin-top: 0;\n        }\n\n        + .list-group-item {\n          border-top-width: var(--#{$prefix}list-group-border-width);\n          border-left-width: 0;\n\n          &.active {\n            margin-left: calc(-1 * var(--#{$prefix}list-group-border-width)); // stylelint-disable-line function-disallowed-list\n            border-left-width: var(--#{$prefix}list-group-border-width);\n          }\n        }\n      }\n    }\n  }\n}\n\n\n// Flush list items\n//\n// Remove borders and border-radius to keep list group items edge-to-edge. Most\n// useful within other components (e.g., cards).\n\n.list-group-flush {\n  @include border-radius(0);\n\n  > .list-group-item {\n    border-width: 0 0 var(--#{$prefix}list-group-border-width);\n\n    &:last-child {\n      border-bottom-width: 0;\n    }\n  }\n}\n\n\n// scss-docs-start list-group-modifiers\n// List group contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n@each $state, $value in $theme-colors {\n  $list-group-variant-bg: shift-color($value, $list-group-item-bg-scale);\n  $list-group-variant-color: shift-color($value, $list-group-item-color-scale);\n  @if (contrast-ratio($list-group-variant-bg, $list-group-variant-color) < $min-contrast-ratio) {\n    $list-group-variant-color: mix($value, color-contrast($list-group-variant-bg), abs($list-group-item-color-scale));\n  }\n\n  @include list-group-item-variant($state, $list-group-variant-bg, $list-group-variant-color);\n}\n// scss-docs-end list-group-modifiers\n"
  },
  {
    "path": "src/common/bootstrap/scss/_maps.scss",
    "content": "// Re-assigned maps\n//\n// Placed here so that others can override the default Sass maps and see automatic updates to utilities and more.\n\n// scss-docs-start theme-colors-rgb\n$theme-colors-rgb: map-loop($theme-colors, to-rgb, \"$value\") !default;\n// scss-docs-end theme-colors-rgb\n\n// Utilities maps\n//\n// Extends the default `$theme-colors` maps to help create our utilities.\n\n// Come v6, we'll de-dupe these variables. Until then, for backward compatibility, we keep them to reassign.\n// scss-docs-start utilities-colors\n$utilities-colors: $theme-colors-rgb !default;\n// scss-docs-end utilities-colors\n\n// scss-docs-start utilities-text-colors\n$utilities-text: map-merge(\n  $utilities-colors,\n  (\n    \"black\": to-rgb($black),\n    \"white\": to-rgb($white),\n    \"body\": to-rgb($body-color)\n  )\n) !default;\n$utilities-text-colors: map-loop($utilities-text, rgba-css-var, \"$key\", \"text\") !default;\n// scss-docs-end utilities-text-colors\n\n// scss-docs-start utilities-bg-colors\n$utilities-bg: map-merge(\n  $utilities-colors,\n  (\n    \"black\": to-rgb($black),\n    \"white\": to-rgb($white),\n    \"body\": to-rgb($body-bg)\n  )\n) !default;\n$utilities-bg-colors: map-loop($utilities-bg, rgba-css-var, \"$key\", \"bg\") !default;\n// scss-docs-end utilities-bg-colors\n\n// scss-docs-start utilities-border-colors\n$utilities-border: map-merge(\n  $utilities-colors,\n  (\n    \"white\": to-rgb($white)\n  )\n) !default;\n$utilities-border-colors: map-loop($utilities-border, rgba-css-var, \"$key\", \"border\") !default;\n// scss-docs-end utilities-border-colors\n\n$negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default;\n\n$gutters: $spacers !default;\n"
  },
  {
    "path": "src/common/bootstrap/scss/_mixins.scss",
    "content": "// Toggles\n//\n// Used in conjunction with global variables to enable certain theme features.\n\n// Vendor\n@import \"vendor/rfs\";\n\n// Deprecate\n@import \"mixins/deprecate\";\n\n// Helpers\n@import \"mixins/breakpoints\";\n@import \"mixins/color-scheme\";\n@import \"mixins/image\";\n@import \"mixins/resize\";\n@import \"mixins/visually-hidden\";\n@import \"mixins/reset-text\";\n@import \"mixins/text-truncate\";\n\n// Utilities\n@import \"mixins/utilities\";\n\n// Components\n@import \"mixins/alert\";\n@import \"mixins/backdrop\";\n@import \"mixins/buttons\";\n@import \"mixins/caret\";\n@import \"mixins/pagination\";\n@import \"mixins/lists\";\n@import \"mixins/list-group\";\n@import \"mixins/forms\";\n@import \"mixins/table-variants\";\n\n// Skins\n@import \"mixins/border-radius\";\n@import \"mixins/box-shadow\";\n@import \"mixins/gradients\";\n@import \"mixins/transition\";\n\n// Layout\n@import \"mixins/clearfix\";\n@import \"mixins/container\";\n@import \"mixins/grid\";\n"
  },
  {
    "path": "src/common/bootstrap/scss/_modal.scss",
    "content": "// stylelint-disable function-disallowed-list\n\n// .modal-open      - body class for killing the scroll\n// .modal           - container to scroll within\n// .modal-dialog    - positioning shell for the actual modal\n// .modal-content   - actual modal w/ bg and corners and stuff\n\n\n// Container that the modal scrolls within\n.modal {\n  // scss-docs-start modal-css-vars\n  --#{$prefix}modal-zindex: #{$zindex-modal};\n  --#{$prefix}modal-width: #{$modal-md};\n  --#{$prefix}modal-padding: #{$modal-inner-padding};\n  --#{$prefix}modal-margin: #{$modal-dialog-margin};\n  --#{$prefix}modal-color: #{$modal-content-color};\n  --#{$prefix}modal-bg: #{$modal-content-bg};\n  --#{$prefix}modal-border-color: #{$modal-content-border-color};\n  --#{$prefix}modal-border-width: #{$modal-content-border-width};\n  --#{$prefix}modal-border-radius: #{$modal-content-border-radius};\n  --#{$prefix}modal-box-shadow: #{$modal-content-box-shadow-xs};\n  --#{$prefix}modal-inner-border-radius: #{$modal-content-inner-border-radius};\n  --#{$prefix}modal-header-padding-x: #{$modal-header-padding-x};\n  --#{$prefix}modal-header-padding-y: #{$modal-header-padding-y};\n  --#{$prefix}modal-header-padding: #{$modal-header-padding}; // Todo in v6: Split this padding into x and y\n  --#{$prefix}modal-header-border-color: #{$modal-header-border-color};\n  --#{$prefix}modal-header-border-width: #{$modal-header-border-width};\n  --#{$prefix}modal-title-line-height: #{$modal-title-line-height};\n  --#{$prefix}modal-footer-gap: #{$modal-footer-margin-between};\n  --#{$prefix}modal-footer-bg: #{$modal-footer-bg};\n  --#{$prefix}modal-footer-border-color: #{$modal-footer-border-color};\n  --#{$prefix}modal-footer-border-width: #{$modal-footer-border-width};\n  // scss-docs-end modal-css-vars\n\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: var(--#{$prefix}modal-zindex);\n  display: none;\n  width: 100%;\n  height: 100%;\n  overflow-x: hidden;\n  overflow-y: auto;\n  // Prevent Chrome on Windows from adding a focus outline. For details, see\n  // https://github.com/twbs/bootstrap/pull/10951.\n  outline: 0;\n  // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a\n  // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342\n  // See also https://github.com/twbs/bootstrap/issues/17695\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: var(--#{$prefix}modal-margin);\n  // allow clicks to pass through for custom click handling to close modal\n  pointer-events: none;\n\n  // When fading in the modal, animate it to slide down\n  .modal.fade & {\n    @include transition($modal-transition);\n    transform: $modal-fade-transform;\n  }\n  .modal.show & {\n    transform: $modal-show-transform;\n  }\n\n  // When trying to close, animate focus to scale\n  .modal.modal-static & {\n    transform: $modal-scale-transform;\n  }\n}\n\n.modal-dialog-scrollable {\n  height: calc(100% - var(--#{$prefix}modal-margin) * 2);\n\n  .modal-content {\n    max-height: 100%;\n    overflow: hidden;\n  }\n\n  .modal-body {\n    overflow-y: auto;\n  }\n}\n\n.modal-dialog-centered {\n  display: flex;\n  align-items: center;\n  min-height: calc(100% - var(--#{$prefix}modal-margin) * 2);\n}\n\n// Actual modal\n.modal-content {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog`\n  // counteract the pointer-events: none; in the .modal-dialog\n  color: var(--#{$prefix}modal-color);\n  pointer-events: auto;\n  background-color: var(--#{$prefix}modal-bg);\n  background-clip: padding-box;\n  border: var(--#{$prefix}modal-border-width) solid var(--#{$prefix}modal-border-color);\n  @include border-radius(var(--#{$prefix}modal-border-radius));\n  @include box-shadow(var(--#{$prefix}modal-box-shadow));\n  // Remove focus outline from opened modal\n  outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n  // scss-docs-start modal-backdrop-css-vars\n  --#{$prefix}backdrop-zindex: #{$zindex-modal-backdrop};\n  --#{$prefix}backdrop-bg: #{$modal-backdrop-bg};\n  --#{$prefix}backdrop-opacity: #{$modal-backdrop-opacity};\n  // scss-docs-end modal-backdrop-css-vars\n\n  @include overlay-backdrop(var(--#{$prefix}backdrop-zindex), var(--#{$prefix}backdrop-bg), var(--#{$prefix}backdrop-opacity));\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n  display: flex;\n  flex-shrink: 0;\n  align-items: center;\n  justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends\n  padding: var(--#{$prefix}modal-header-padding);\n  border-bottom: var(--#{$prefix}modal-header-border-width) solid var(--#{$prefix}modal-header-border-color);\n  @include border-top-radius(var(--#{$prefix}modal-inner-border-radius));\n\n  .btn-close {\n    padding: calc(var(--#{$prefix}modal-header-padding-y) * .5) calc(var(--#{$prefix}modal-header-padding-x) * .5);\n    margin: calc(-.5 * var(--#{$prefix}modal-header-padding-y)) calc(-.5 * var(--#{$prefix}modal-header-padding-x)) calc(-.5 * var(--#{$prefix}modal-header-padding-y)) auto;\n  }\n}\n\n// Title text within header\n.modal-title {\n  margin-bottom: 0;\n  line-height: var(--#{$prefix}modal-title-line-height);\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n  position: relative;\n  // Enable `flex-grow: 1` so that the body take up as much space as possible\n  // when there should be a fixed height on `.modal-dialog`.\n  flex: 1 1 auto;\n  padding: var(--#{$prefix}modal-padding);\n}\n\n// Footer (for actions)\n.modal-footer {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  align-items: center; // vertically center\n  justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items\n  padding: calc(var(--#{$prefix}modal-padding) - var(--#{$prefix}modal-footer-gap) * .5);\n  background-color: var(--#{$prefix}modal-footer-bg);\n  border-top: var(--#{$prefix}modal-footer-border-width) solid var(--#{$prefix}modal-footer-border-color);\n  @include border-bottom-radius(var(--#{$prefix}modal-inner-border-radius));\n\n  // Place margin between footer elements\n  // This solution is far from ideal because of the universal selector usage,\n  // but is needed to fix https://github.com/twbs/bootstrap/issues/24800\n  > * {\n    margin: calc(var(--#{$prefix}modal-footer-gap) * .5); // Todo in v6: replace with gap on parent class\n  }\n}\n\n// Scale up the modal\n@include media-breakpoint-up(sm) {\n  .modal {\n    --#{$prefix}modal-margin: #{$modal-dialog-margin-y-sm-up};\n    --#{$prefix}modal-box-shadow: #{$modal-content-box-shadow-sm-up};\n  }\n\n  // Automatically set modal's width for larger viewports\n  .modal-dialog {\n    max-width: var(--#{$prefix}modal-width);\n    margin-right: auto;\n    margin-left: auto;\n  }\n\n  .modal-sm {\n    --#{$prefix}modal-width: #{$modal-sm};\n  }\n}\n\n@include media-breakpoint-up(lg) {\n  .modal-lg,\n  .modal-xl {\n    --#{$prefix}modal-width: #{$modal-lg};\n  }\n}\n\n@include media-breakpoint-up(xl) {\n  .modal-xl {\n    --#{$prefix}modal-width: #{$modal-xl};\n  }\n}\n\n// scss-docs-start modal-fullscreen-loop\n@each $breakpoint in map-keys($grid-breakpoints) {\n  $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n  $postfix: if($infix != \"\", $infix + \"-down\", \"\");\n\n  @include media-breakpoint-down($breakpoint) {\n    .modal-fullscreen#{$postfix} {\n      width: 100vw;\n      max-width: none;\n      height: 100%;\n      margin: 0;\n\n      .modal-content {\n        height: 100%;\n        border: 0;\n        @include border-radius(0);\n      }\n\n      .modal-header,\n      .modal-footer {\n        @include border-radius(0);\n      }\n\n      .modal-body {\n        overflow-y: auto;\n      }\n    }\n  }\n}\n// scss-docs-end modal-fullscreen-loop\n"
  },
  {
    "path": "src/common/bootstrap/scss/_nav.scss",
    "content": "// Base class\n//\n// Kickstart any navigation component with a set of style resets. Works with\n// `<nav>`s, `<ul>`s or `<ol>`s.\n\n.nav {\n  // scss-docs-start nav-css-vars\n  --#{$prefix}nav-link-padding-x: #{$nav-link-padding-x};\n  --#{$prefix}nav-link-padding-y: #{$nav-link-padding-y};\n  @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size);\n  --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight};\n  --#{$prefix}nav-link-color: #{$nav-link-color};\n  --#{$prefix}nav-link-hover-color: #{$nav-link-hover-color};\n  --#{$prefix}nav-link-disabled-color: #{$nav-link-disabled-color};\n  // scss-docs-end nav-css-vars\n\n  display: flex;\n  flex-wrap: wrap;\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n\n.nav-link {\n  display: block;\n  padding: var(--#{$prefix}nav-link-padding-y) var(--#{$prefix}nav-link-padding-x);\n  @include font-size(var(--#{$prefix}nav-link-font-size));\n  font-weight: var(--#{$prefix}nav-link-font-weight);\n  color: var(--#{$prefix}nav-link-color);\n  text-decoration: if($link-decoration == none, null, none);\n  @include transition($nav-link-transition);\n\n  &:hover,\n  &:focus {\n    color: var(--#{$prefix}nav-link-hover-color);\n    text-decoration: if($link-hover-decoration == underline, none, null);\n  }\n\n  // Disabled state lightens text\n  &.disabled {\n    color: var(--#{$prefix}nav-link-disabled-color);\n    pointer-events: none;\n    cursor: default;\n  }\n}\n\n//\n// Tabs\n//\n\n.nav-tabs {\n  // scss-docs-start nav-tabs-css-vars\n  --#{$prefix}nav-tabs-border-width: #{$nav-tabs-border-width};\n  --#{$prefix}nav-tabs-border-color: #{$nav-tabs-border-color};\n  --#{$prefix}nav-tabs-border-radius: #{$nav-tabs-border-radius};\n  --#{$prefix}nav-tabs-link-hover-border-color: #{$nav-tabs-link-hover-border-color};\n  --#{$prefix}nav-tabs-link-active-color: #{$nav-tabs-link-active-color};\n  --#{$prefix}nav-tabs-link-active-bg: #{$nav-tabs-link-active-bg};\n  --#{$prefix}nav-tabs-link-active-border-color: #{$nav-tabs-link-active-border-color};\n  // scss-docs-end nav-tabs-css-vars\n\n  border-bottom: var(--#{$prefix}nav-tabs-border-width) solid var(--#{$prefix}nav-tabs-border-color);\n\n  .nav-link {\n    margin-bottom: calc(-1 * var(--#{$prefix}nav-tabs-border-width)); // stylelint-disable-line function-disallowed-list\n    background: none;\n    border: var(--#{$prefix}nav-tabs-border-width) solid transparent;\n    @include border-top-radius(var(--#{$prefix}nav-tabs-border-radius));\n\n    &:hover,\n    &:focus {\n      // Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link\n      isolation: isolate;\n      border-color: var(--#{$prefix}nav-tabs-link-hover-border-color);\n    }\n\n    &.disabled,\n    &:disabled {\n      color: var(--#{$prefix}nav-link-disabled-color);\n      background-color: transparent;\n      border-color: transparent;\n    }\n  }\n\n  .nav-link.active,\n  .nav-item.show .nav-link {\n    color: var(--#{$prefix}nav-tabs-link-active-color);\n    background-color: var(--#{$prefix}nav-tabs-link-active-bg);\n    border-color: var(--#{$prefix}nav-tabs-link-active-border-color);\n  }\n\n  .dropdown-menu {\n    // Make dropdown border overlap tab border\n    margin-top: calc(-1 * var(--#{$prefix}nav-tabs-border-width)); // stylelint-disable-line function-disallowed-list\n    // Remove the top rounded corners here since there is a hard edge above the menu\n    @include border-top-radius(0);\n  }\n}\n\n\n//\n// Pills\n//\n\n.nav-pills {\n  // scss-docs-start nav-pills-css-vars\n  --#{$prefix}nav-pills-border-radius: #{$nav-pills-border-radius};\n  --#{$prefix}nav-pills-link-active-color: #{$nav-pills-link-active-color};\n  --#{$prefix}nav-pills-link-active-bg: #{$nav-pills-link-active-bg};\n  // scss-docs-end nav-pills-css-vars\n\n  .nav-link {\n    background: none;\n    border: 0;\n    @include border-radius(var(--#{$prefix}nav-pills-border-radius));\n\n    &:disabled {\n      color: var(--#{$prefix}nav-link-disabled-color);\n      background-color: transparent;\n      border-color: transparent;\n    }\n  }\n\n  .nav-link.active,\n  .show > .nav-link {\n    color: var(--#{$prefix}nav-pills-link-active-color);\n    @include gradient-bg(var(--#{$prefix}nav-pills-link-active-bg));\n  }\n}\n\n\n//\n// Justified variants\n//\n\n.nav-fill {\n  > .nav-link,\n  .nav-item {\n    flex: 1 1 auto;\n    text-align: center;\n  }\n}\n\n.nav-justified {\n  > .nav-link,\n  .nav-item {\n    flex-basis: 0;\n    flex-grow: 1;\n    text-align: center;\n  }\n}\n\n.nav-fill,\n.nav-justified {\n  .nav-item .nav-link {\n    width: 100%; // Make sure button will grow\n  }\n}\n\n\n// Tabbable tabs\n//\n// Hide tabbable panes to start, show them when `.active`\n\n.tab-content {\n  > .tab-pane {\n    display: none;\n  }\n  > .active {\n    display: block;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_navbar.scss",
    "content": "// Navbar\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n  // scss-docs-start navbar-css-vars\n  --#{$prefix}navbar-padding-x: #{if($navbar-padding-x == null, 0, $navbar-padding-x)};\n  --#{$prefix}navbar-padding-y: #{$navbar-padding-y};\n  --#{$prefix}navbar-color: #{$navbar-light-color};\n  --#{$prefix}navbar-hover-color: #{$navbar-light-hover-color};\n  --#{$prefix}navbar-disabled-color: #{$navbar-light-disabled-color};\n  --#{$prefix}navbar-active-color: #{$navbar-light-active-color};\n  --#{$prefix}navbar-brand-padding-y: #{$navbar-brand-padding-y};\n  --#{$prefix}navbar-brand-margin-end: #{$navbar-brand-margin-end};\n  --#{$prefix}navbar-brand-font-size: #{$navbar-brand-font-size};\n  --#{$prefix}navbar-brand-color: #{$navbar-light-brand-color};\n  --#{$prefix}navbar-brand-hover-color: #{$navbar-light-brand-hover-color};\n  --#{$prefix}navbar-nav-link-padding-x: #{$navbar-nav-link-padding-x};\n  --#{$prefix}navbar-toggler-padding-y: #{$navbar-toggler-padding-y};\n  --#{$prefix}navbar-toggler-padding-x: #{$navbar-toggler-padding-x};\n  --#{$prefix}navbar-toggler-font-size: #{$navbar-toggler-font-size};\n  --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-light-toggler-icon-bg)};\n  --#{$prefix}navbar-toggler-border-color: #{$navbar-light-toggler-border-color};\n  --#{$prefix}navbar-toggler-border-radius: #{$navbar-toggler-border-radius};\n  --#{$prefix}navbar-toggler-focus-width: #{$navbar-toggler-focus-width};\n  --#{$prefix}navbar-toggler-transition: #{$navbar-toggler-transition};\n  // scss-docs-end navbar-css-vars\n\n  position: relative;\n  display: flex;\n  flex-wrap: wrap; // allow us to do the line break for collapsing content\n  align-items: center;\n  justify-content: space-between; // space out brand from logo\n  padding: var(--#{$prefix}navbar-padding-y) var(--#{$prefix}navbar-padding-x);\n  @include gradient-bg();\n\n  // Because flex properties aren't inherited, we need to redeclare these first\n  // few properties so that content nested within behave properly.\n  // The `flex-wrap` property is inherited to simplify the expanded navbars\n  %container-flex-properties {\n    display: flex;\n    flex-wrap: inherit;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  > .container,\n  > .container-fluid {\n    @extend %container-flex-properties;\n  }\n\n  @each $breakpoint, $container-max-width in $container-max-widths {\n    > .container#{breakpoint-infix($breakpoint, $container-max-widths)} {\n      @extend %container-flex-properties;\n    }\n  }\n}\n\n\n// Navbar brand\n//\n// Used for brand, project, or site names.\n\n.navbar-brand {\n  padding-top: var(--#{$prefix}navbar-brand-padding-y);\n  padding-bottom: var(--#{$prefix}navbar-brand-padding-y);\n  margin-right: var(--#{$prefix}navbar-brand-margin-end);\n  @include font-size(var(--#{$prefix}navbar-brand-font-size));\n  color: var(--#{$prefix}navbar-brand-color);\n  text-decoration: if($link-decoration == none, null, none);\n  white-space: nowrap;\n\n  &:hover,\n  &:focus {\n    color: var(--#{$prefix}navbar-brand-hover-color);\n    text-decoration: if($link-hover-decoration == underline, none, null);\n  }\n}\n\n\n// Navbar nav\n//\n// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`).\n\n.navbar-nav {\n  // scss-docs-start navbar-nav-css-vars\n  --#{$prefix}nav-link-padding-x: 0;\n  --#{$prefix}nav-link-padding-y: #{$nav-link-padding-y};\n  @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size);\n  --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight};\n  --#{$prefix}nav-link-color: var(--#{$prefix}navbar-color);\n  --#{$prefix}nav-link-hover-color: var(--#{$prefix}navbar-hover-color);\n  --#{$prefix}nav-link-disabled-color: var(--#{$prefix}navbar-disabled-color);\n  // scss-docs-end navbar-nav-css-vars\n\n  display: flex;\n  flex-direction: column; // cannot use `inherit` to get the `.navbar`s value\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n\n  .show > .nav-link,\n  .nav-link.active {\n    color: var(--#{$prefix}navbar-active-color);\n  }\n\n  .dropdown-menu {\n    position: static;\n  }\n}\n\n\n// Navbar text\n//\n//\n\n.navbar-text {\n  padding-top: $nav-link-padding-y;\n  padding-bottom: $nav-link-padding-y;\n  color: var(--#{$prefix}navbar-color);\n\n  a,\n  a:hover,\n  a:focus  {\n    color: var(--#{$prefix}navbar-active-color);\n  }\n}\n\n\n// Responsive navbar\n//\n// Custom styles for responsive collapsing and toggling of navbar contents.\n// Powered by the collapse Bootstrap JavaScript plugin.\n\n// When collapsed, prevent the toggleable navbar contents from appearing in\n// the default flexbox row orientation. Requires the use of `flex-wrap: wrap`\n// on the `.navbar` parent.\n.navbar-collapse {\n  flex-basis: 100%;\n  flex-grow: 1;\n  // For always expanded or extra full navbars, ensure content aligns itself\n  // properly vertically. Can be easily overridden with flex utilities.\n  align-items: center;\n}\n\n// Button for toggling the navbar when in its collapsed state\n.navbar-toggler {\n  padding: var(--#{$prefix}navbar-toggler-padding-y) var(--#{$prefix}navbar-toggler-padding-x);\n  @include font-size(var(--#{$prefix}navbar-toggler-font-size));\n  line-height: 1;\n  color: var(--#{$prefix}navbar-color);\n  background-color: transparent; // remove default button style\n  border: var(--#{$prefix}border-width) solid var(--#{$prefix}navbar-toggler-border-color); // remove default button style\n  @include border-radius(var(--#{$prefix}navbar-toggler-border-radius));\n  @include transition(var(--#{$prefix}navbar-toggler-transition));\n\n  &:hover {\n    text-decoration: none;\n  }\n\n  &:focus {\n    text-decoration: none;\n    outline: 0;\n    box-shadow: 0 0 0 var(--#{$prefix}navbar-toggler-focus-width);\n  }\n}\n\n// Keep as a separate element so folks can easily override it with another icon\n// or image file as needed.\n.navbar-toggler-icon {\n  display: inline-block;\n  width: 1.5em;\n  height: 1.5em;\n  vertical-align: middle;\n  background-image: var(--#{$prefix}navbar-toggler-icon-bg);\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: 100%;\n}\n\n.navbar-nav-scroll {\n  max-height: var(--#{$prefix}scroll-height, 75vh);\n  overflow-y: auto;\n}\n\n// scss-docs-start navbar-expand-loop\n// Generate series of `.navbar-expand-*` responsive classes for configuring\n// where your navbar collapses.\n.navbar-expand {\n  @each $breakpoint in map-keys($grid-breakpoints) {\n    $next: breakpoint-next($breakpoint, $grid-breakpoints);\n    $infix: breakpoint-infix($next, $grid-breakpoints);\n\n    // stylelint-disable-next-line scss/selector-no-union-class-name\n    &#{$infix} {\n      @include media-breakpoint-up($next) {\n        flex-wrap: nowrap;\n        justify-content: flex-start;\n\n        .navbar-nav {\n          flex-direction: row;\n\n          .dropdown-menu {\n            position: absolute;\n          }\n\n          .nav-link {\n            padding-right: var(--#{$prefix}navbar-nav-link-padding-x);\n            padding-left: var(--#{$prefix}navbar-nav-link-padding-x);\n          }\n        }\n\n        .navbar-nav-scroll {\n          overflow: visible;\n        }\n\n        .navbar-collapse {\n          display: flex !important; // stylelint-disable-line declaration-no-important\n          flex-basis: auto;\n        }\n\n        .navbar-toggler {\n          display: none;\n        }\n\n        .offcanvas {\n          // stylelint-disable declaration-no-important\n          position: static;\n          z-index: auto;\n          flex-grow: 1;\n          width: auto !important;\n          height: auto !important;\n          visibility: visible !important;\n          background-color: transparent !important;\n          border: 0 !important;\n          transform: none !important;\n          @include box-shadow(none);\n          @include transition(none);\n          // stylelint-enable declaration-no-important\n\n          .offcanvas-header {\n            display: none;\n          }\n\n          .offcanvas-body {\n            display: flex;\n            flex-grow: 0;\n            padding: 0;\n            overflow-y: visible;\n          }\n        }\n      }\n    }\n  }\n}\n// scss-docs-end navbar-expand-loop\n\n// Navbar themes\n//\n// Styles for switching between navbars with light or dark background.\n\n.navbar-light {\n  @include deprecate(\"`.navbar-light`\", \"v5.2.0\", \"v6.0.0\", true);\n}\n\n.navbar-dark {\n  // scss-docs-start navbar-dark-css-vars\n  --#{$prefix}navbar-color: #{$navbar-dark-color};\n  --#{$prefix}navbar-hover-color: #{$navbar-dark-hover-color};\n  --#{$prefix}navbar-disabled-color: #{$navbar-dark-disabled-color};\n  --#{$prefix}navbar-active-color: #{$navbar-dark-active-color};\n  --#{$prefix}navbar-brand-color: #{$navbar-dark-brand-color};\n  --#{$prefix}navbar-brand-hover-color: #{$navbar-dark-brand-hover-color};\n  --#{$prefix}navbar-toggler-border-color: #{$navbar-dark-toggler-border-color};\n  --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)};\n  // scss-docs-end navbar-dark-css-vars\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_offcanvas.scss",
    "content": "// stylelint-disable function-disallowed-list\n\n%offcanvas-css-vars {\n  // scss-docs-start offcanvas-css-vars\n  --#{$prefix}offcanvas-zindex: #{$zindex-offcanvas};\n  --#{$prefix}offcanvas-width: #{$offcanvas-horizontal-width};\n  --#{$prefix}offcanvas-height: #{$offcanvas-vertical-height};\n  --#{$prefix}offcanvas-padding-x: #{$offcanvas-padding-x};\n  --#{$prefix}offcanvas-padding-y: #{$offcanvas-padding-y};\n  --#{$prefix}offcanvas-color: #{$offcanvas-color};\n  --#{$prefix}offcanvas-bg: #{$offcanvas-bg-color};\n  --#{$prefix}offcanvas-border-width: #{$offcanvas-border-width};\n  --#{$prefix}offcanvas-border-color: #{$offcanvas-border-color};\n  --#{$prefix}offcanvas-box-shadow: #{$offcanvas-box-shadow};\n  // scss-docs-end offcanvas-css-vars\n}\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n  $next: breakpoint-next($breakpoint, $grid-breakpoints);\n  $infix: breakpoint-infix($next, $grid-breakpoints);\n\n  .offcanvas#{$infix} {\n    @extend %offcanvas-css-vars;\n  }\n}\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n  $next: breakpoint-next($breakpoint, $grid-breakpoints);\n  $infix: breakpoint-infix($next, $grid-breakpoints);\n\n  .offcanvas#{$infix} {\n    @include media-breakpoint-down($next) {\n      position: fixed;\n      bottom: 0;\n      z-index: var(--#{$prefix}offcanvas-zindex);\n      display: flex;\n      flex-direction: column;\n      max-width: 100%;\n      color: var(--#{$prefix}offcanvas-color);\n      visibility: hidden;\n      background-color: var(--#{$prefix}offcanvas-bg);\n      background-clip: padding-box;\n      outline: 0;\n      @include box-shadow(var(--#{$prefix}offcanvas-box-shadow));\n      @include transition(transform $offcanvas-transition-duration ease-in-out);\n\n      &.offcanvas-start {\n        top: 0;\n        left: 0;\n        width: var(--#{$prefix}offcanvas-width);\n        border-right: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);\n        transform: translateX(-100%);\n      }\n\n      &.offcanvas-end {\n        top: 0;\n        right: 0;\n        width: var(--#{$prefix}offcanvas-width);\n        border-left: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);\n        transform: translateX(100%);\n      }\n\n      &.offcanvas-top {\n        top: 0;\n        right: 0;\n        left: 0;\n        height: var(--#{$prefix}offcanvas-height);\n        max-height: 100%;\n        border-bottom: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);\n        transform: translateY(-100%);\n      }\n\n      &.offcanvas-bottom {\n        right: 0;\n        left: 0;\n        height: var(--#{$prefix}offcanvas-height);\n        max-height: 100%;\n        border-top: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);\n        transform: translateY(100%);\n      }\n\n      &.showing,\n      &.show:not(.hiding) {\n        transform: none;\n      }\n\n      &.showing,\n      &.hiding,\n      &.show {\n        visibility: visible;\n      }\n    }\n\n    @if not ($infix == \"\") {\n      @include media-breakpoint-up($next) {\n        --#{$prefix}offcanvas-height: auto;\n        --#{$prefix}offcanvas-border-width: 0;\n        background-color: transparent !important; // stylelint-disable-line declaration-no-important\n\n        .offcanvas-header {\n          display: none;\n        }\n\n        .offcanvas-body {\n          display: flex;\n          flex-grow: 0;\n          padding: 0;\n          overflow-y: visible;\n          // Reset `background-color` in case `.bg-*` classes are used in offcanvas\n          background-color: transparent !important; // stylelint-disable-line declaration-no-important\n        }\n      }\n    }\n  }\n}\n\n.offcanvas-backdrop {\n  @include overlay-backdrop($zindex-offcanvas-backdrop, $offcanvas-backdrop-bg, $offcanvas-backdrop-opacity);\n}\n\n.offcanvas-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--#{$prefix}offcanvas-padding-y) var(--#{$prefix}offcanvas-padding-x);\n\n  .btn-close {\n    padding: calc(var(--#{$prefix}offcanvas-padding-y) * .5) calc(var(--#{$prefix}offcanvas-padding-x) * .5);\n    margin-top: calc(-.5 * var(--#{$prefix}offcanvas-padding-y));\n    margin-right: calc(-.5 * var(--#{$prefix}offcanvas-padding-x));\n    margin-bottom: calc(-.5 * var(--#{$prefix}offcanvas-padding-y));\n  }\n}\n\n.offcanvas-title {\n  margin-bottom: 0;\n  line-height: $offcanvas-title-line-height;\n}\n\n.offcanvas-body {\n  flex-grow: 1;\n  padding: var(--#{$prefix}offcanvas-padding-y) var(--#{$prefix}offcanvas-padding-x);\n  overflow-y: auto;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_pagination.scss",
    "content": ".pagination {\n  // scss-docs-start pagination-css-vars\n  --#{$prefix}pagination-padding-x: #{$pagination-padding-x};\n  --#{$prefix}pagination-padding-y: #{$pagination-padding-y};\n  @include rfs($pagination-font-size, --#{$prefix}pagination-font-size);\n  --#{$prefix}pagination-color: #{$pagination-color};\n  --#{$prefix}pagination-bg: #{$pagination-bg};\n  --#{$prefix}pagination-border-width: #{$pagination-border-width};\n  --#{$prefix}pagination-border-color: #{$pagination-border-color};\n  --#{$prefix}pagination-border-radius: #{$pagination-border-radius};\n  --#{$prefix}pagination-hover-color: #{$pagination-hover-color};\n  --#{$prefix}pagination-hover-bg: #{$pagination-hover-bg};\n  --#{$prefix}pagination-hover-border-color: #{$pagination-hover-border-color};\n  --#{$prefix}pagination-focus-color: #{$pagination-focus-color};\n  --#{$prefix}pagination-focus-bg: #{$pagination-focus-bg};\n  --#{$prefix}pagination-focus-box-shadow: #{$pagination-focus-box-shadow};\n  --#{$prefix}pagination-active-color: #{$pagination-active-color};\n  --#{$prefix}pagination-active-bg: #{$pagination-active-bg};\n  --#{$prefix}pagination-active-border-color: #{$pagination-active-border-color};\n  --#{$prefix}pagination-disabled-color: #{$pagination-disabled-color};\n  --#{$prefix}pagination-disabled-bg: #{$pagination-disabled-bg};\n  --#{$prefix}pagination-disabled-border-color: #{$pagination-disabled-border-color};\n  // scss-docs-end pagination-css-vars\n\n  display: flex;\n  @include list-unstyled();\n}\n\n.page-link {\n  position: relative;\n  display: block;\n  padding: var(--#{$prefix}pagination-padding-y) var(--#{$prefix}pagination-padding-x);\n  @include font-size(var(--#{$prefix}pagination-font-size));\n  color: var(--#{$prefix}pagination-color);\n  text-decoration: if($link-decoration == none, null, none);\n  background-color: var(--#{$prefix}pagination-bg);\n  border: var(--#{$prefix}pagination-border-width) solid var(--#{$prefix}pagination-border-color);\n  @include transition($pagination-transition);\n\n  &:hover {\n    z-index: 2;\n    color: var(--#{$prefix}pagination-hover-color);\n    text-decoration: if($link-hover-decoration == underline, none, null);\n    background-color: var(--#{$prefix}pagination-hover-bg);\n    border-color: var(--#{$prefix}pagination-hover-border-color);\n  }\n\n  &:focus {\n    z-index: 3;\n    color: var(--#{$prefix}pagination-focus-color);\n    background-color: var(--#{$prefix}pagination-focus-bg);\n    outline: $pagination-focus-outline;\n    box-shadow: var(--#{$prefix}pagination-focus-box-shadow);\n  }\n\n  &.active,\n  .active > & {\n    z-index: 3;\n    color: var(--#{$prefix}pagination-active-color);\n    @include gradient-bg(var(--#{$prefix}pagination-active-bg));\n    border-color: var(--#{$prefix}pagination-active-border-color);\n  }\n\n  &.disabled,\n  .disabled > & {\n    color: var(--#{$prefix}pagination-disabled-color);\n    pointer-events: none;\n    background-color: var(--#{$prefix}pagination-disabled-bg);\n    border-color: var(--#{$prefix}pagination-disabled-border-color);\n  }\n}\n\n.page-item {\n  &:not(:first-child) .page-link {\n    margin-left: $pagination-margin-start;\n  }\n\n  @if $pagination-margin-start == ($pagination-border-width * -1) {\n    &:first-child {\n      .page-link {\n        @include border-start-radius(var(--#{$prefix}pagination-border-radius));\n      }\n    }\n\n    &:last-child {\n      .page-link {\n        @include border-end-radius(var(--#{$prefix}pagination-border-radius));\n      }\n    }\n  } @else {\n    // Add border-radius to all pageLinks in case they have left margin\n    .page-link {\n      @include border-radius(var(--#{$prefix}pagination-border-radius));\n    }\n  }\n}\n\n\n//\n// Sizing\n//\n\n.pagination-lg {\n  @include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $pagination-border-radius-lg);\n}\n\n.pagination-sm {\n  @include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $pagination-border-radius-sm);\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_placeholders.scss",
    "content": ".placeholder {\n  display: inline-block;\n  min-height: 1em;\n  vertical-align: middle;\n  cursor: wait;\n  background-color: currentcolor;\n  opacity: $placeholder-opacity-max;\n\n  &.btn::before {\n    display: inline-block;\n    content: \"\";\n  }\n}\n\n// Sizing\n.placeholder-xs {\n  min-height: .6em;\n}\n\n.placeholder-sm {\n  min-height: .8em;\n}\n\n.placeholder-lg {\n  min-height: 1.2em;\n}\n\n// Animation\n.placeholder-glow {\n  .placeholder {\n    animation: placeholder-glow 2s ease-in-out infinite;\n  }\n}\n\n@keyframes placeholder-glow {\n  50% {\n    opacity: $placeholder-opacity-min;\n  }\n}\n\n.placeholder-wave {\n  mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%);\n  mask-size: 200% 100%;\n  animation: placeholder-wave 2s linear infinite;\n}\n\n@keyframes placeholder-wave {\n  100% {\n    mask-position: -200% 0%;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_popover.scss",
    "content": ".popover {\n  // scss-docs-start popover-css-vars\n  --#{$prefix}popover-zindex: #{$zindex-popover};\n  --#{$prefix}popover-max-width: #{$popover-max-width};\n  @include rfs($popover-font-size, --#{$prefix}popover-font-size);\n  --#{$prefix}popover-bg: #{$popover-bg};\n  --#{$prefix}popover-border-width: #{$popover-border-width};\n  --#{$prefix}popover-border-color: #{$popover-border-color};\n  --#{$prefix}popover-border-radius: #{$popover-border-radius};\n  --#{$prefix}popover-inner-border-radius: #{$popover-inner-border-radius};\n  --#{$prefix}popover-box-shadow: #{$popover-box-shadow};\n  --#{$prefix}popover-header-padding-x: #{$popover-header-padding-x};\n  --#{$prefix}popover-header-padding-y: #{$popover-header-padding-y};\n  @include rfs($popover-header-font-size, --#{$prefix}popover-header-font-size);\n  --#{$prefix}popover-header-color: #{$popover-header-color};\n  --#{$prefix}popover-header-bg: #{$popover-header-bg};\n  --#{$prefix}popover-body-padding-x: #{$popover-body-padding-x};\n  --#{$prefix}popover-body-padding-y: #{$popover-body-padding-y};\n  --#{$prefix}popover-body-color: #{$popover-body-color};\n  --#{$prefix}popover-arrow-width: #{$popover-arrow-width};\n  --#{$prefix}popover-arrow-height: #{$popover-arrow-height};\n  --#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color);\n  // scss-docs-end popover-css-vars\n\n  z-index: var(--#{$prefix}popover-zindex);\n  display: block;\n  max-width: var(--#{$prefix}popover-max-width);\n  // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n  // So reset our font and text properties to avoid inheriting weird values.\n  @include reset-text();\n  @include font-size(var(--#{$prefix}popover-font-size));\n  // Allow breaking very long words so they don't overflow the popover's bounds\n  word-wrap: break-word;\n  background-color: var(--#{$prefix}popover-bg);\n  background-clip: padding-box;\n  border: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color);\n  @include border-radius(var(--#{$prefix}popover-border-radius));\n  @include box-shadow(var(--#{$prefix}popover-box-shadow));\n\n  .popover-arrow {\n    display: block;\n    width: var(--#{$prefix}popover-arrow-width);\n    height: var(--#{$prefix}popover-arrow-height);\n\n    &::before,\n    &::after {\n      position: absolute;\n      display: block;\n      content: \"\";\n      border-color: transparent;\n      border-style: solid;\n      border-width: 0;\n    }\n  }\n}\n\n.bs-popover-top {\n  > .popover-arrow {\n    bottom: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list\n\n    &::before,\n    &::after {\n      border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list\n    }\n\n    &::before {\n      bottom: 0;\n      border-top-color: var(--#{$prefix}popover-arrow-border);\n    }\n\n    &::after {\n      bottom: var(--#{$prefix}popover-border-width);\n      border-top-color: var(--#{$prefix}popover-bg);\n    }\n  }\n}\n\n/* rtl:begin:ignore */\n.bs-popover-end {\n  > .popover-arrow {\n    left: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list\n    width: var(--#{$prefix}popover-arrow-height);\n    height: var(--#{$prefix}popover-arrow-width);\n\n    &::before,\n    &::after {\n      border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list\n    }\n\n    &::before {\n      left: 0;\n      border-right-color: var(--#{$prefix}popover-arrow-border);\n    }\n\n    &::after {\n      left: var(--#{$prefix}popover-border-width);\n      border-right-color: var(--#{$prefix}popover-bg);\n    }\n  }\n}\n\n/* rtl:end:ignore */\n\n.bs-popover-bottom {\n  > .popover-arrow {\n    top: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list\n\n    &::before,\n    &::after {\n      border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list\n    }\n\n    &::before {\n      top: 0;\n      border-bottom-color: var(--#{$prefix}popover-arrow-border);\n    }\n\n    &::after {\n      top: var(--#{$prefix}popover-border-width);\n      border-bottom-color: var(--#{$prefix}popover-bg);\n    }\n  }\n\n  // This will remove the popover-header's border just below the arrow\n  .popover-header::before {\n    position: absolute;\n    top: 0;\n    left: 50%;\n    display: block;\n    width: var(--#{$prefix}popover-arrow-width);\n    margin-left: calc(-.5 * var(--#{$prefix}popover-arrow-width)); // stylelint-disable-line function-disallowed-list\n    content: \"\";\n    border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg);\n  }\n}\n\n/* rtl:begin:ignore */\n.bs-popover-start {\n  > .popover-arrow {\n    right: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list\n    width: var(--#{$prefix}popover-arrow-height);\n    height: var(--#{$prefix}popover-arrow-width);\n\n    &::before,\n    &::after {\n      border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list\n    }\n\n    &::before {\n      right: 0;\n      border-left-color: var(--#{$prefix}popover-arrow-border);\n    }\n\n    &::after {\n      right: var(--#{$prefix}popover-border-width);\n      border-left-color: var(--#{$prefix}popover-bg);\n    }\n  }\n}\n\n/* rtl:end:ignore */\n\n.bs-popover-auto {\n  &[data-popper-placement^=\"top\"] {\n    @extend .bs-popover-top;\n  }\n  &[data-popper-placement^=\"right\"] {\n    @extend .bs-popover-end;\n  }\n  &[data-popper-placement^=\"bottom\"] {\n    @extend .bs-popover-bottom;\n  }\n  &[data-popper-placement^=\"left\"] {\n    @extend .bs-popover-start;\n  }\n}\n\n// Offset the popover to account for the popover arrow\n.popover-header {\n  padding: var(--#{$prefix}popover-header-padding-y) var(--#{$prefix}popover-header-padding-x);\n  margin-bottom: 0; // Reset the default from Reboot\n  @include font-size(var(--#{$prefix}popover-header-font-size));\n  color: var(--#{$prefix}popover-header-color);\n  background-color: var(--#{$prefix}popover-header-bg);\n  border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color);\n  @include border-top-radius(var(--#{$prefix}popover-inner-border-radius));\n\n  &:empty {\n    display: none;\n  }\n}\n\n.popover-body {\n  padding: var(--#{$prefix}popover-body-padding-y) var(--#{$prefix}popover-body-padding-x);\n  color: var(--#{$prefix}popover-body-color);\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_progress.scss",
    "content": "// Disable animation if transitions are disabled\n\n// scss-docs-start progress-keyframes\n@if $enable-transitions {\n  @keyframes progress-bar-stripes {\n    0% { background-position-x: $progress-height; }\n  }\n}\n// scss-docs-end progress-keyframes\n\n.progress {\n  // scss-docs-start progress-css-vars\n  --#{$prefix}progress-height: #{$progress-height};\n  @include rfs($progress-font-size, --#{$prefix}progress-font-size);\n  --#{$prefix}progress-bg: #{$progress-bg};\n  --#{$prefix}progress-border-radius: #{$progress-border-radius};\n  --#{$prefix}progress-box-shadow: #{$progress-box-shadow};\n  --#{$prefix}progress-bar-color: #{$progress-bar-color};\n  --#{$prefix}progress-bar-bg: #{$progress-bar-bg};\n  --#{$prefix}progress-bar-transition: #{$progress-bar-transition};\n  // scss-docs-end progress-css-vars\n\n  display: flex;\n  height: var(--#{$prefix}progress-height);\n  overflow: hidden; // force rounded corners by cropping it\n  @include font-size(var(--#{$prefix}progress-font-size));\n  background-color: var(--#{$prefix}progress-bg);\n  @include border-radius(var(--#{$prefix}progress-border-radius));\n  @include box-shadow(var(--#{$prefix}progress-box-shadow));\n}\n\n.progress-bar {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  overflow: hidden;\n  color: var(--#{$prefix}progress-bar-color);\n  text-align: center;\n  white-space: nowrap;\n  background-color: var(--#{$prefix}progress-bar-bg);\n  @include transition(var(--#{$prefix}progress-bar-transition));\n}\n\n.progress-bar-striped {\n  @include gradient-striped();\n  background-size: var(--#{$prefix}progress-height) var(--#{$prefix}progress-height);\n}\n\n@if $enable-transitions {\n  .progress-bar-animated {\n    animation: $progress-bar-animation-timing progress-bar-stripes;\n\n    @if $enable-reduced-motion {\n      @media (prefers-reduced-motion: reduce) {\n        animation: none;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_reboot.scss",
    "content": "// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n  @if $font-size-root != null {\n    @include font-size(var(--#{$prefix}root-font-size));\n  }\n\n  @if $enable-smooth-scroll {\n    @media (prefers-reduced-motion: no-preference) {\n      scroll-behavior: smooth;\n    }\n  }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n  margin: 0; // 1\n  font-family: var(--#{$prefix}body-font-family);\n  @include font-size(var(--#{$prefix}body-font-size));\n  font-weight: var(--#{$prefix}body-font-weight);\n  line-height: var(--#{$prefix}body-line-height);\n  color: var(--#{$prefix}body-color);\n  text-align: var(--#{$prefix}body-text-align);\n  background-color: var(--#{$prefix}body-bg); // 2\n  -webkit-text-size-adjust: 100%; // 3\n  -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n\nhr {\n  margin: $hr-margin-y 0;\n  color: $hr-color; // 1\n  border: 0;\n  border-top: $hr-border-width solid $hr-border-color;\n  opacity: $hr-opacity;\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n//    By default, `<h1>`-`<h6>` all receive top and bottom margins. We nuke the top\n//    margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n  margin-top: 0; // 1\n  margin-bottom: $headings-margin-bottom;\n  font-family: $headings-font-family;\n  font-style: $headings-font-style;\n  font-weight: $headings-font-weight;\n  line-height: $headings-line-height;\n  color: $headings-color;\n}\n\nh1 {\n  @extend %heading;\n  @include font-size($h1-font-size);\n}\n\nh2 {\n  @extend %heading;\n  @include font-size($h2-font-size);\n}\n\nh3 {\n  @extend %heading;\n  @include font-size($h3-font-size);\n}\n\nh4 {\n  @extend %heading;\n  @include font-size($h4-font-size);\n}\n\nh5 {\n  @extend %heading;\n  @include font-size($h5-font-size);\n}\n\nh6 {\n  @extend %heading;\n  @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `<p>`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n  margin-top: 0;\n  margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 2. Add explicit cursor to indicate changed behavior.\n// 3. Prevent the text-decoration to be skipped.\n\nabbr[title] {\n  text-decoration: underline dotted; // 1\n  cursor: help; // 2\n  text-decoration-skip-ink: none; // 3\n}\n\n\n// Address\n\naddress {\n  margin-bottom: 1rem;\n  font-style: normal;\n  line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n  padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n  margin-top: 0;\n  margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n  margin-bottom: 0;\n}\n\ndt {\n  font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n  margin-bottom: .5rem;\n  margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n  margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n  font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n  @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n  padding: $mark-padding;\n  background-color: var(--#{$prefix}highlight-bg);\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n  position: relative;\n  @include font-size($sub-sup-font-size);\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n  color: var(--#{$prefix}link-color);\n  text-decoration: $link-decoration;\n\n  &:hover {\n    color: var(--#{$prefix}link-hover-color);\n    text-decoration: $link-hover-decoration;\n  }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n  &,\n  &:hover {\n    color: inherit;\n    text-decoration: none;\n  }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: $font-family-code;\n  @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n  display: block;\n  margin-top: 0; // 1\n  margin-bottom: 1rem; // 2\n  overflow: auto; // 3\n  @include font-size($code-font-size);\n  color: $pre-color;\n\n  // Account for some code outputs that place code tags in pre tags\n  code {\n    @include font-size(inherit);\n    color: inherit;\n    word-break: normal;\n  }\n}\n\ncode {\n  @include font-size($code-font-size);\n  color: var(--#{$prefix}code-color);\n  word-wrap: break-word;\n\n  // Streamline the style when inside anchors to avoid broken underline and more\n  a > & {\n    color: inherit;\n  }\n}\n\nkbd {\n  padding: $kbd-padding-y $kbd-padding-x;\n  @include font-size($kbd-font-size);\n  color: $kbd-color;\n  background-color: $kbd-bg;\n  @include border-radius($border-radius-sm);\n\n  kbd {\n    padding: 0;\n    @include font-size(1em);\n    font-weight: $nested-kbd-font-weight;\n  }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n  margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n  vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n  caption-side: bottom;\n  border-collapse: collapse;\n}\n\ncaption {\n  padding-top: $table-cell-padding-y;\n  padding-bottom: $table-cell-padding-y;\n  color: $table-caption-color;\n  text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `<td>` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n  font-weight: $table-th-font-weight; // 1\n  text-align: inherit; // 2\n  text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n  border-color: inherit;\n  border-style: solid;\n  border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n  display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n  // stylelint-disable-next-line property-disallowed-list\n  border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n  outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n  margin: 0; // 1\n  font-family: inherit;\n  @include font-size(inherit);\n  line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n  text-transform: none;\n}\n// Set the cursor for non-`<button>` buttons\n//\n// Details at https://github.com/twbs/bootstrap/pull/30562\n[role=\"button\"] {\n  cursor: pointer;\n}\n\nselect {\n  // Remove the inheritance of word-wrap in Safari.\n  // See https://github.com/twbs/bootstrap/issues/24990\n  word-wrap: normal;\n\n  // Undo the opacity change from Chrome\n  &:disabled {\n    opacity: 1;\n  }\n}\n\n// Remove the dropdown arrow only from text type inputs built with datalists in Chrome.\n// See https://stackoverflow.com/a/54997118\n\n[list]:not([type=\"date\"]):not([type=\"datetime-local\"]):not([type=\"month\"]):not([type=\"week\"]):not([type=\"time\"])::-webkit-calendar-picker-indicator {\n  display: none !important;\n}\n\n// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n//    controls in Android 4.\n// 2. Correct the inability to style clickable types in iOS and Safari.\n// 3. Opinionated: add \"hand\" cursor to non-disabled button elements.\n\nbutton,\n[type=\"button\"], // 1\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button; // 2\n\n  @if $enable-button-pointers {\n    &:not(:disabled) {\n      cursor: pointer; // 3\n    }\n  }\n}\n\n// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.\n\n::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\n// 1. Textareas should really only resize vertically so they don't break their (horizontal) containers.\n\ntextarea {\n  resize: vertical; // 1\n}\n\n// 1. Browsers set a default `min-width: min-content;` on fieldsets,\n//    unlike e.g. `<div>`s, which have `min-width: 0;` by default.\n//    So we reset that to ensure fieldsets behave more like a standard block element.\n//    See https://github.com/twbs/bootstrap/issues/12359\n//    and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements\n// 2. Reset the default outline behavior of fieldsets so they don't affect page layout.\n\nfieldset {\n  min-width: 0; // 1\n  padding: 0; // 2\n  margin: 0; // 2\n  border: 0; // 2\n}\n\n// 1. By using `float: left`, the legend will behave like a block element.\n//    This way the border of a fieldset wraps around the legend if present.\n// 2. Fix wrapping bug.\n//    See https://github.com/twbs/bootstrap/issues/29712\n\nlegend {\n  float: left; // 1\n  width: 100%;\n  padding: 0;\n  margin-bottom: $legend-margin-bottom;\n  @include font-size($legend-font-size);\n  font-weight: $legend-font-weight;\n  line-height: inherit;\n\n  + * {\n    clear: left; // 2\n  }\n}\n\n// Fix height of inputs with a type of datetime-local, date, month, week, or time\n// See https://github.com/twbs/bootstrap/issues/18842\n\n::-webkit-datetime-edit-fields-wrapper,\n::-webkit-datetime-edit-text,\n::-webkit-datetime-edit-minute,\n::-webkit-datetime-edit-hour-field,\n::-webkit-datetime-edit-day-field,\n::-webkit-datetime-edit-month-field,\n::-webkit-datetime-edit-year-field {\n  padding: 0;\n}\n\n::-webkit-inner-spin-button {\n  height: auto;\n}\n\n// 1. Correct the outline style in Safari.\n// 2. This overrides the extra rounded corners on search inputs in iOS so that our\n//    `.form-control` class can properly style them. Note that this cannot simply\n//    be added to `.form-control` as it's not specific enough. For details, see\n//    https://github.com/twbs/bootstrap/issues/11586.\n\n[type=\"search\"] {\n  outline-offset: -2px; // 1\n  -webkit-appearance: textfield; // 2\n}\n\n// 1. A few input types should stay LTR\n// See https://rtlstyling.com/posts/rtl-styling#form-inputs\n// 2. RTL only output\n// See https://rtlcss.com/learn/usage-guide/control-directives/#raw\n\n/* rtl:raw:\n[type=\"tel\"],\n[type=\"url\"],\n[type=\"email\"],\n[type=\"number\"] {\n  direction: ltr;\n}\n*/\n\n// Remove the inner padding in Chrome and Safari on macOS.\n\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n// Remove padding around color pickers in webkit browsers\n\n::-webkit-color-swatch-wrapper {\n  padding: 0;\n}\n\n\n// 1. Inherit font family and line height for file input buttons\n// 2. Correct the inability to style clickable types in iOS and Safari.\n\n::file-selector-button {\n  font: inherit; // 1\n  -webkit-appearance: button; // 2\n}\n\n// Correct element displays\n\noutput {\n  display: inline-block;\n}\n\n// Remove border from iframe\n\niframe {\n  border: 0;\n}\n\n// Summary\n//\n// 1. Add the correct display in all browsers\n\nsummary {\n  display: list-item; // 1\n  cursor: pointer;\n}\n\n\n// Progress\n//\n// Add the correct vertical alignment in Chrome, Firefox, and Opera.\n\nprogress {\n  vertical-align: baseline;\n}\n\n\n// Hidden attribute\n//\n// Always hide an element with the `hidden` HTML attribute.\n\n[hidden] {\n  display: none !important;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_root.scss",
    "content": ":root {\n  // Note: Custom variable values only support SassScript inside `#{}`.\n\n  // Colors\n  //\n  // Generate palettes for full colors, grays, and theme colors.\n\n  @each $color, $value in $colors {\n    --#{$prefix}#{$color}: #{$value};\n  }\n\n  @each $color, $value in $grays {\n    --#{$prefix}gray-#{$color}: #{$value};\n  }\n\n  @each $color, $value in $theme-colors {\n    --#{$prefix}#{$color}: #{$value};\n  }\n\n  @each $color, $value in $theme-colors-rgb {\n    --#{$prefix}#{$color}-rgb: #{$value};\n  }\n\n  --#{$prefix}white-rgb: #{to-rgb($white)};\n  --#{$prefix}black-rgb: #{to-rgb($black)};\n  --#{$prefix}body-color-rgb: #{to-rgb($body-color)};\n  --#{$prefix}body-bg-rgb: #{to-rgb($body-bg)};\n\n  // Fonts\n\n  // Note: Use `inspect` for lists so that quoted items keep the quotes.\n  // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n  --#{$prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n  --#{$prefix}font-monospace: #{inspect($font-family-monospace)};\n  --#{$prefix}gradient: #{$gradient};\n\n  // Root and body\n  // scss-docs-start root-body-variables\n  @if $font-size-root != null {\n    --#{$prefix}root-font-size: #{$font-size-root};\n  }\n  --#{$prefix}body-font-family: #{$font-family-base};\n  @include rfs($font-size-base, --#{$prefix}body-font-size);\n  --#{$prefix}body-font-weight: #{$font-weight-base};\n  --#{$prefix}body-line-height: #{$line-height-base};\n  --#{$prefix}body-color: #{$body-color};\n  @if $body-text-align != null {\n    --#{$prefix}body-text-align: #{$body-text-align};\n  }\n  --#{$prefix}body-bg: #{$body-bg};\n  // scss-docs-end root-body-variables\n\n  // scss-docs-start root-border-var\n  --#{$prefix}border-width: #{$border-width};\n  --#{$prefix}border-style: #{$border-style};\n  --#{$prefix}border-color: #{$border-color};\n  --#{$prefix}border-color-translucent: #{$border-color-translucent};\n\n  --#{$prefix}border-radius: #{$border-radius};\n  --#{$prefix}border-radius-sm: #{$border-radius-sm};\n  --#{$prefix}border-radius-lg: #{$border-radius-lg};\n  --#{$prefix}border-radius-xl: #{$border-radius-xl};\n  --#{$prefix}border-radius-2xl: #{$border-radius-2xl};\n  --#{$prefix}border-radius-pill: #{$border-radius-pill};\n  // scss-docs-end root-border-var\n\n  --#{$prefix}link-color: #{$link-color};\n  --#{$prefix}link-hover-color: #{$link-hover-color};\n\n  --#{$prefix}code-color: #{$code-color};\n\n  --#{$prefix}highlight-bg: #{$mark-bg};\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_spinners.scss",
    "content": "//\n// Rotating border\n//\n\n.spinner-grow,\n.spinner-border {\n  display: inline-block;\n  width: var(--#{$prefix}spinner-width);\n  height: var(--#{$prefix}spinner-height);\n  vertical-align: var(--#{$prefix}spinner-vertical-align);\n  // stylelint-disable-next-line property-disallowed-list\n  border-radius: 50%;\n  animation: var(--#{$prefix}spinner-animation-speed) linear infinite var(--#{$prefix}spinner-animation-name);\n}\n\n// scss-docs-start spinner-border-keyframes\n@keyframes spinner-border {\n  to { transform: rotate(360deg) #{\"/* rtl:ignore */\"}; }\n}\n// scss-docs-end spinner-border-keyframes\n\n.spinner-border {\n  // scss-docs-start spinner-border-css-vars\n  --#{$prefix}spinner-width: #{$spinner-width};\n  --#{$prefix}spinner-height: #{$spinner-height};\n  --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align};\n  --#{$prefix}spinner-border-width: #{$spinner-border-width};\n  --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed};\n  --#{$prefix}spinner-animation-name: spinner-border;\n  // scss-docs-end spinner-border-css-vars\n\n  border: var(--#{$prefix}spinner-border-width) solid currentcolor;\n  border-right-color: transparent;\n}\n\n.spinner-border-sm {\n  // scss-docs-start spinner-border-sm-css-vars\n  --#{$prefix}spinner-width: #{$spinner-width-sm};\n  --#{$prefix}spinner-height: #{$spinner-height-sm};\n  --#{$prefix}spinner-border-width: #{$spinner-border-width-sm};\n  // scss-docs-end spinner-border-sm-css-vars\n}\n\n//\n// Growing circle\n//\n\n// scss-docs-start spinner-grow-keyframes\n@keyframes spinner-grow {\n  0% {\n    transform: scale(0);\n  }\n  50% {\n    opacity: 1;\n    transform: none;\n  }\n}\n// scss-docs-end spinner-grow-keyframes\n\n.spinner-grow {\n  // scss-docs-start spinner-grow-css-vars\n  --#{$prefix}spinner-width: #{$spinner-width};\n  --#{$prefix}spinner-height: #{$spinner-height};\n  --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align};\n  --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed};\n  --#{$prefix}spinner-animation-name: spinner-grow;\n  // scss-docs-end spinner-grow-css-vars\n\n  background-color: currentcolor;\n  opacity: 0;\n}\n\n.spinner-grow-sm {\n  --#{$prefix}spinner-width: #{$spinner-width-sm};\n  --#{$prefix}spinner-height: #{$spinner-height-sm};\n}\n\n@if $enable-reduced-motion {\n  @media (prefers-reduced-motion: reduce) {\n    .spinner-border,\n    .spinner-grow {\n      --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed * 2};\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_tables.scss",
    "content": "//\n// Basic Bootstrap table\n//\n\n.table {\n  --#{$prefix}table-color: #{$table-color};\n  --#{$prefix}table-bg: #{$table-bg};\n  --#{$prefix}table-border-color: #{$table-border-color};\n  --#{$prefix}table-accent-bg: #{$table-accent-bg};\n  --#{$prefix}table-striped-color: #{$table-striped-color};\n  --#{$prefix}table-striped-bg: #{$table-striped-bg};\n  --#{$prefix}table-active-color: #{$table-active-color};\n  --#{$prefix}table-active-bg: #{$table-active-bg};\n  --#{$prefix}table-hover-color: #{$table-hover-color};\n  --#{$prefix}table-hover-bg: #{$table-hover-bg};\n\n  width: 100%;\n  margin-bottom: $spacer;\n  color: var(--#{$prefix}table-color);\n  vertical-align: $table-cell-vertical-align;\n  border-color: var(--#{$prefix}table-border-color);\n\n  // Target th & td\n  // We need the child combinator to prevent styles leaking to nested tables which doesn't have a `.table` class.\n  // We use the universal selectors here to simplify the selector (else we would need 6 different selectors).\n  // Another advantage is that this generates less code and makes the selector less specific making it easier to override.\n  // stylelint-disable-next-line selector-max-universal\n  > :not(caption) > * > * {\n    padding: $table-cell-padding-y $table-cell-padding-x;\n    background-color: var(--#{$prefix}table-bg);\n    border-bottom-width: $table-border-width;\n    box-shadow: inset 0 0 0 9999px var(--#{$prefix}table-accent-bg);\n  }\n\n  > tbody {\n    vertical-align: inherit;\n  }\n\n  > thead {\n    vertical-align: bottom;\n  }\n}\n\n.table-group-divider {\n  border-top: ($table-border-width * 2) solid $table-group-separator-color;\n}\n\n//\n// Change placement of captions with a class\n//\n\n.caption-top {\n  caption-side: top;\n}\n\n\n//\n// Condensed table w/ half padding\n//\n\n.table-sm {\n  // stylelint-disable-next-line selector-max-universal\n  > :not(caption) > * > * {\n    padding: $table-cell-padding-y-sm $table-cell-padding-x-sm;\n  }\n}\n\n\n// Border versions\n//\n// Add or remove borders all around the table and between all the columns.\n//\n// When borders are added on all sides of the cells, the corners can render odd when\n// these borders do not have the same color or if they are semi-transparent.\n// Therefor we add top and border bottoms to the `tr`s and left and right borders\n// to the `td`s or `th`s\n\n.table-bordered {\n  > :not(caption) > * {\n    border-width: $table-border-width 0;\n\n    // stylelint-disable-next-line selector-max-universal\n    > * {\n      border-width: 0 $table-border-width;\n    }\n  }\n}\n\n.table-borderless {\n  // stylelint-disable-next-line selector-max-universal\n  > :not(caption) > * > * {\n    border-bottom-width: 0;\n  }\n\n  > :not(:first-child) {\n    border-top-width: 0;\n  }\n}\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n// For rows\n.table-striped {\n  > tbody > tr:nth-of-type(#{$table-striped-order}) > * {\n    --#{$prefix}table-accent-bg: var(--#{$prefix}table-striped-bg);\n    color: var(--#{$prefix}table-striped-color);\n  }\n}\n\n// For columns\n.table-striped-columns {\n  > :not(caption) > tr > :nth-child(#{$table-striped-columns-order}) {\n    --#{$prefix}table-accent-bg: var(--#{$prefix}table-striped-bg);\n    color: var(--#{$prefix}table-striped-color);\n  }\n}\n\n// Active table\n//\n// The `.table-active` class can be added to highlight rows or cells\n\n.table-active {\n  --#{$prefix}table-accent-bg: var(--#{$prefix}table-active-bg);\n  color: var(--#{$prefix}table-active-color);\n}\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n  > tbody > tr:hover > * {\n    --#{$prefix}table-accent-bg: var(--#{$prefix}table-hover-bg);\n    color: var(--#{$prefix}table-hover-color);\n  }\n}\n\n\n// Table variants\n//\n// Table variants set the table cell backgrounds, border colors\n// and the colors of the striped, hovered & active tables\n\n@each $color, $value in $table-variants {\n  @include table-variant($color, $value);\n}\n\n// Responsive tables\n//\n// Generate series of `.table-responsive-*` classes for configuring the screen\n// size of where your table will overflow.\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n  $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n  @include media-breakpoint-down($breakpoint) {\n    .table-responsive#{$infix} {\n      overflow-x: auto;\n      -webkit-overflow-scrolling: touch;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_toasts.scss",
    "content": ".toast {\n  // scss-docs-start toast-css-vars\n  --#{$prefix}toast-zindex: #{$zindex-toast};\n  --#{$prefix}toast-padding-x: #{$toast-padding-x};\n  --#{$prefix}toast-padding-y: #{$toast-padding-y};\n  --#{$prefix}toast-spacing: #{$toast-spacing};\n  --#{$prefix}toast-max-width: #{$toast-max-width};\n  @include rfs($toast-font-size, --#{$prefix}toast-font-size);\n  --#{$prefix}toast-color: #{$toast-color};\n  --#{$prefix}toast-bg: #{$toast-background-color};\n  --#{$prefix}toast-border-width: #{$toast-border-width};\n  --#{$prefix}toast-border-color: #{$toast-border-color};\n  --#{$prefix}toast-border-radius: #{$toast-border-radius};\n  --#{$prefix}toast-box-shadow: #{$toast-box-shadow};\n  --#{$prefix}toast-header-color: #{$toast-header-color};\n  --#{$prefix}toast-header-bg: #{$toast-header-background-color};\n  --#{$prefix}toast-header-border-color: #{$toast-header-border-color};\n  // scss-docs-end toast-css-vars\n\n  width: var(--#{$prefix}toast-max-width);\n  max-width: 100%;\n  @include font-size(var(--#{$prefix}toast-font-size));\n  color: var(--#{$prefix}toast-color);\n  pointer-events: auto;\n  background-color: var(--#{$prefix}toast-bg);\n  background-clip: padding-box;\n  border: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-border-color);\n  box-shadow: var(--#{$prefix}toast-box-shadow);\n  @include border-radius(var(--#{$prefix}toast-border-radius));\n\n  &.showing {\n    opacity: 0;\n  }\n\n  &:not(.show) {\n    display: none;\n  }\n}\n\n.toast-container {\n  --#{$prefix}toast-zindex: #{$zindex-toast};\n\n  position: absolute;\n  z-index: var(--#{$prefix}toast-zindex);\n  width: max-content;\n  max-width: 100%;\n  pointer-events: none;\n\n  > :not(:last-child) {\n    margin-bottom: var(--#{$prefix}toast-spacing);\n  }\n}\n\n.toast-header {\n  display: flex;\n  align-items: center;\n  padding: var(--#{$prefix}toast-padding-y) var(--#{$prefix}toast-padding-x);\n  color: var(--#{$prefix}toast-header-color);\n  background-color: var(--#{$prefix}toast-header-bg);\n  background-clip: padding-box;\n  border-bottom: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-header-border-color);\n  @include border-top-radius(calc(var(--#{$prefix}toast-border-radius) - var(--#{$prefix}toast-border-width)));\n\n  .btn-close {\n    margin-right: calc(-.5 * var(--#{$prefix}toast-padding-x)); // stylelint-disable-line function-disallowed-list\n    margin-left: var(--#{$prefix}toast-padding-x);\n  }\n}\n\n.toast-body {\n  padding: var(--#{$prefix}toast-padding-x);\n  word-wrap: break-word;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_tooltip.scss",
    "content": "// Base class\n.tooltip {\n  // scss-docs-start tooltip-css-vars\n  --#{$prefix}tooltip-zindex: #{$zindex-tooltip};\n  --#{$prefix}tooltip-max-width: #{$tooltip-max-width};\n  --#{$prefix}tooltip-padding-x: #{$tooltip-padding-x};\n  --#{$prefix}tooltip-padding-y: #{$tooltip-padding-y};\n  --#{$prefix}tooltip-margin: #{$tooltip-margin};\n  @include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size);\n  --#{$prefix}tooltip-color: #{$tooltip-color};\n  --#{$prefix}tooltip-bg: #{$tooltip-bg};\n  --#{$prefix}tooltip-border-radius: #{$tooltip-border-radius};\n  --#{$prefix}tooltip-opacity: #{$tooltip-opacity};\n  --#{$prefix}tooltip-arrow-width: #{$tooltip-arrow-width};\n  --#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height};\n  // scss-docs-end tooltip-css-vars\n\n  z-index: var(--#{$prefix}tooltip-zindex);\n  display: block;\n  padding: var(--#{$prefix}tooltip-arrow-height);\n  margin: var(--#{$prefix}tooltip-margin);\n  @include deprecate(\"`$tooltip-margin`\", \"v5\", \"v5.x\", true);\n  // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n  // So reset our font and text properties to avoid inheriting weird values.\n  @include reset-text();\n  @include font-size(var(--#{$prefix}tooltip-font-size));\n  // Allow breaking very long words so they don't overflow the tooltip's bounds\n  word-wrap: break-word;\n  opacity: 0;\n\n  &.show { opacity: var(--#{$prefix}tooltip-opacity); }\n\n  .tooltip-arrow {\n    display: block;\n    width: var(--#{$prefix}tooltip-arrow-width);\n    height: var(--#{$prefix}tooltip-arrow-height);\n\n    &::before {\n      position: absolute;\n      content: \"\";\n      border-color: transparent;\n      border-style: solid;\n    }\n  }\n}\n\n.bs-tooltip-top .tooltip-arrow {\n  bottom: 0;\n\n  &::before {\n    top: -1px;\n    border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list\n    border-top-color: var(--#{$prefix}tooltip-bg);\n  }\n}\n\n/* rtl:begin:ignore */\n.bs-tooltip-end .tooltip-arrow {\n  left: 0;\n  width: var(--#{$prefix}tooltip-arrow-height);\n  height: var(--#{$prefix}tooltip-arrow-width);\n\n  &::before {\n    right: -1px;\n    border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list\n    border-right-color: var(--#{$prefix}tooltip-bg);\n  }\n}\n\n/* rtl:end:ignore */\n\n.bs-tooltip-bottom .tooltip-arrow {\n  top: 0;\n\n  &::before {\n    bottom: -1px;\n    border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list\n    border-bottom-color: var(--#{$prefix}tooltip-bg);\n  }\n}\n\n/* rtl:begin:ignore */\n.bs-tooltip-start .tooltip-arrow {\n  right: 0;\n  width: var(--#{$prefix}tooltip-arrow-height);\n  height: var(--#{$prefix}tooltip-arrow-width);\n\n  &::before {\n    left: -1px;\n    border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list\n    border-left-color: var(--#{$prefix}tooltip-bg);\n  }\n}\n\n/* rtl:end:ignore */\n\n.bs-tooltip-auto {\n  &[data-popper-placement^=\"top\"] {\n    @extend .bs-tooltip-top;\n  }\n  &[data-popper-placement^=\"right\"] {\n    @extend .bs-tooltip-end;\n  }\n  &[data-popper-placement^=\"bottom\"] {\n    @extend .bs-tooltip-bottom;\n  }\n  &[data-popper-placement^=\"left\"] {\n    @extend .bs-tooltip-start;\n  }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n  max-width: var(--#{$prefix}tooltip-max-width);\n  padding: var(--#{$prefix}tooltip-padding-y) var(--#{$prefix}tooltip-padding-x);\n  color: var(--#{$prefix}tooltip-color);\n  text-align: center;\n  background-color: var(--#{$prefix}tooltip-bg);\n  @include border-radius(var(--#{$prefix}tooltip-border-radius));\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_transitions.scss",
    "content": ".fade {\n  @include transition($transition-fade);\n\n  &:not(.show) {\n    opacity: 0;\n  }\n}\n\n// scss-docs-start collapse-classes\n.collapse {\n  &:not(.show) {\n    display: none;\n  }\n}\n\n.collapsing {\n  height: 0;\n  overflow: hidden;\n  @include transition($transition-collapse);\n\n  &.collapse-horizontal {\n    width: 0;\n    height: auto;\n    @include transition($transition-collapse-width);\n  }\n}\n// scss-docs-end collapse-classes\n"
  },
  {
    "path": "src/common/bootstrap/scss/_type.scss",
    "content": "//\n// Headings\n//\n.h1 {\n  @extend h1;\n}\n\n.h2 {\n  @extend h2;\n}\n\n.h3 {\n  @extend h3;\n}\n\n.h4 {\n  @extend h4;\n}\n\n.h5 {\n  @extend h5;\n}\n\n.h6 {\n  @extend h6;\n}\n\n\n.lead {\n  @include font-size($lead-font-size);\n  font-weight: $lead-font-weight;\n}\n\n// Type display classes\n@each $display, $font-size in $display-font-sizes {\n  .display-#{$display} {\n    @include font-size($font-size);\n    font-family: $display-font-family;\n    font-style: $display-font-style;\n    font-weight: $display-font-weight;\n    line-height: $display-line-height;\n  }\n}\n\n//\n// Emphasis\n//\n.small {\n  @extend small;\n}\n\n.mark {\n  @extend mark;\n}\n\n//\n// Lists\n//\n\n.list-unstyled {\n  @include list-unstyled();\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n  @include list-unstyled();\n}\n.list-inline-item {\n  display: inline-block;\n\n  &:not(:last-child) {\n    margin-right: $list-inline-padding;\n  }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n  @include font-size($initialism-font-size);\n  text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n  margin-bottom: $blockquote-margin-y;\n  @include font-size($blockquote-font-size);\n\n  > :last-child {\n    margin-bottom: 0;\n  }\n}\n\n.blockquote-footer {\n  margin-top: -$blockquote-margin-y;\n  margin-bottom: $blockquote-margin-y;\n  @include font-size($blockquote-footer-font-size);\n  color: $blockquote-footer-color;\n\n  &::before {\n    content: \"\\2014\\00A0\"; // em dash, nbsp\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/_utilities.scss",
    "content": "// Utilities\n\n$utilities: () !default;\n// stylelint-disable-next-line scss/dollar-variable-default\n$utilities: map-merge(\n  (\n    // scss-docs-start utils-vertical-align\n    \"align\": (\n      property: vertical-align,\n      class: align,\n      values: baseline top middle bottom text-bottom text-top\n    ),\n    // scss-docs-end utils-vertical-align\n    // scss-docs-start utils-float\n    \"float\": (\n      responsive: true,\n      property: float,\n      values: (\n        start: left,\n        end: right,\n        none: none,\n      )\n    ),\n    // scss-docs-end utils-float\n    // Opacity utilities\n    // scss-docs-start utils-opacity\n    \"opacity\": (\n      property: opacity,\n      values: (\n        0: 0,\n        25: .25,\n        50: .5,\n        75: .75,\n        100: 1,\n      )\n    ),\n    // scss-docs-end utils-opacity\n    // scss-docs-start utils-overflow\n    \"overflow\": (\n      property: overflow,\n      values: auto hidden visible scroll,\n    ),\n    // scss-docs-end utils-overflow\n    // scss-docs-start utils-display\n    \"display\": (\n      responsive: true,\n      print: true,\n      property: display,\n      class: d,\n      values: inline inline-block block grid table table-row table-cell flex inline-flex none\n    ),\n    // scss-docs-end utils-display\n    // scss-docs-start utils-shadow\n    \"shadow\": (\n      property: box-shadow,\n      class: shadow,\n      values: (\n        null: $box-shadow,\n        sm: $box-shadow-sm,\n        lg: $box-shadow-lg,\n        none: none,\n      )\n    ),\n    // scss-docs-end utils-shadow\n    // scss-docs-start utils-position\n    \"position\": (\n      property: position,\n      values: static relative absolute fixed sticky\n    ),\n    \"top\": (\n      property: top,\n      values: $position-values\n    ),\n    \"bottom\": (\n      property: bottom,\n      values: $position-values\n    ),\n    \"start\": (\n      property: left,\n      class: start,\n      values: $position-values\n    ),\n    \"end\": (\n      property: right,\n      class: end,\n      values: $position-values\n    ),\n    \"translate-middle\": (\n      property: transform,\n      class: translate-middle,\n      values: (\n        null: translate(-50%, -50%),\n        x: translateX(-50%),\n        y: translateY(-50%),\n      )\n    ),\n    // scss-docs-end utils-position\n    // scss-docs-start utils-borders\n    \"border\": (\n      property: border,\n      values: (\n        null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),\n        0: 0,\n      )\n    ),\n    \"border-top\": (\n      property: border-top,\n      values: (\n        null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),\n        0: 0,\n      )\n    ),\n    \"border-end\": (\n      property: border-right,\n      class: border-end,\n      values: (\n        null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),\n        0: 0,\n      )\n    ),\n    \"border-bottom\": (\n      property: border-bottom,\n      values: (\n        null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),\n        0: 0,\n      )\n    ),\n    \"border-start\": (\n      property: border-left,\n      class: border-start,\n      values: (\n        null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),\n        0: 0,\n      )\n    ),\n    \"border-color\": (\n      property: border-color,\n      class: border,\n      local-vars: (\n        \"border-opacity\": 1\n      ),\n      values: $utilities-border-colors\n    ),\n    \"border-width\": (\n      css-var: true,\n      css-variable-name: border-width,\n      class: border,\n      values: $border-widths\n    ),\n    \"border-opacity\": (\n      css-var: true,\n      class: border-opacity,\n      values: (\n        10: .1,\n        25: .25,\n        50: .5,\n        75: .75,\n        100: 1\n      )\n    ),\n    // scss-docs-end utils-borders\n    // Sizing utilities\n    // scss-docs-start utils-sizing\n    \"width\": (\n      property: width,\n      class: w,\n      values: (\n        25: 25%,\n        50: 50%,\n        75: 75%,\n        100: 100%,\n        auto: auto\n      )\n    ),\n    \"max-width\": (\n      property: max-width,\n      class: mw,\n      values: (100: 100%)\n    ),\n    \"viewport-width\": (\n      property: width,\n      class: vw,\n      values: (100: 100vw)\n    ),\n    \"min-viewport-width\": (\n      property: min-width,\n      class: min-vw,\n      values: (100: 100vw)\n    ),\n    \"height\": (\n      property: height,\n      class: h,\n      values: (\n        25: 25%,\n        50: 50%,\n        75: 75%,\n        100: 100%,\n        auto: auto\n      )\n    ),\n    \"max-height\": (\n      property: max-height,\n      class: mh,\n      values: (100: 100%)\n    ),\n    \"viewport-height\": (\n      property: height,\n      class: vh,\n      values: (100: 100vh)\n    ),\n    \"min-viewport-height\": (\n      property: min-height,\n      class: min-vh,\n      values: (100: 100vh)\n    ),\n    // scss-docs-end utils-sizing\n    // Flex utilities\n    // scss-docs-start utils-flex\n    \"flex\": (\n      responsive: true,\n      property: flex,\n      values: (fill: 1 1 auto)\n    ),\n    \"flex-direction\": (\n      responsive: true,\n      property: flex-direction,\n      class: flex,\n      values: row column row-reverse column-reverse\n    ),\n    \"flex-grow\": (\n      responsive: true,\n      property: flex-grow,\n      class: flex,\n      values: (\n        grow-0: 0,\n        grow-1: 1,\n      )\n    ),\n    \"flex-shrink\": (\n      responsive: true,\n      property: flex-shrink,\n      class: flex,\n      values: (\n        shrink-0: 0,\n        shrink-1: 1,\n      )\n    ),\n    \"flex-wrap\": (\n      responsive: true,\n      property: flex-wrap,\n      class: flex,\n      values: wrap nowrap wrap-reverse\n    ),\n    \"justify-content\": (\n      responsive: true,\n      property: justify-content,\n      values: (\n        start: flex-start,\n        end: flex-end,\n        center: center,\n        between: space-between,\n        around: space-around,\n        evenly: space-evenly,\n      )\n    ),\n    \"align-items\": (\n      responsive: true,\n      property: align-items,\n      values: (\n        start: flex-start,\n        end: flex-end,\n        center: center,\n        baseline: baseline,\n        stretch: stretch,\n      )\n    ),\n    \"align-content\": (\n      responsive: true,\n      property: align-content,\n      values: (\n        start: flex-start,\n        end: flex-end,\n        center: center,\n        between: space-between,\n        around: space-around,\n        stretch: stretch,\n      )\n    ),\n    \"align-self\": (\n      responsive: true,\n      property: align-self,\n      values: (\n        auto: auto,\n        start: flex-start,\n        end: flex-end,\n        center: center,\n        baseline: baseline,\n        stretch: stretch,\n      )\n    ),\n    \"order\": (\n      responsive: true,\n      property: order,\n      values: (\n        first: -1,\n        0: 0,\n        1: 1,\n        2: 2,\n        3: 3,\n        4: 4,\n        5: 5,\n        last: 6,\n      ),\n    ),\n    // scss-docs-end utils-flex\n    // Margin utilities\n    // scss-docs-start utils-spacing\n    \"margin\": (\n      responsive: true,\n      property: margin,\n      class: m,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    \"margin-x\": (\n      responsive: true,\n      property: margin-right margin-left,\n      class: mx,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    \"margin-y\": (\n      responsive: true,\n      property: margin-top margin-bottom,\n      class: my,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    \"margin-top\": (\n      responsive: true,\n      property: margin-top,\n      class: mt,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    \"margin-end\": (\n      responsive: true,\n      property: margin-right,\n      class: me,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    \"margin-bottom\": (\n      responsive: true,\n      property: margin-bottom,\n      class: mb,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    \"margin-start\": (\n      responsive: true,\n      property: margin-left,\n      class: ms,\n      values: map-merge($spacers, (auto: auto))\n    ),\n    // Negative margin utilities\n    \"negative-margin\": (\n      responsive: true,\n      property: margin,\n      class: m,\n      values: $negative-spacers\n    ),\n    \"negative-margin-x\": (\n      responsive: true,\n      property: margin-right margin-left,\n      class: mx,\n      values: $negative-spacers\n    ),\n    \"negative-margin-y\": (\n      responsive: true,\n      property: margin-top margin-bottom,\n      class: my,\n      values: $negative-spacers\n    ),\n    \"negative-margin-top\": (\n      responsive: true,\n      property: margin-top,\n      class: mt,\n      values: $negative-spacers\n    ),\n    \"negative-margin-end\": (\n      responsive: true,\n      property: margin-right,\n      class: me,\n      values: $negative-spacers\n    ),\n    \"negative-margin-bottom\": (\n      responsive: true,\n      property: margin-bottom,\n      class: mb,\n      values: $negative-spacers\n    ),\n    \"negative-margin-start\": (\n      responsive: true,\n      property: margin-left,\n      class: ms,\n      values: $negative-spacers\n    ),\n    // Padding utilities\n    \"padding\": (\n      responsive: true,\n      property: padding,\n      class: p,\n      values: $spacers\n    ),\n    \"padding-x\": (\n      responsive: true,\n      property: padding-right padding-left,\n      class: px,\n      values: $spacers\n    ),\n    \"padding-y\": (\n      responsive: true,\n      property: padding-top padding-bottom,\n      class: py,\n      values: $spacers\n    ),\n    \"padding-top\": (\n      responsive: true,\n      property: padding-top,\n      class: pt,\n      values: $spacers\n    ),\n    \"padding-end\": (\n      responsive: true,\n      property: padding-right,\n      class: pe,\n      values: $spacers\n    ),\n    \"padding-bottom\": (\n      responsive: true,\n      property: padding-bottom,\n      class: pb,\n      values: $spacers\n    ),\n    \"padding-start\": (\n      responsive: true,\n      property: padding-left,\n      class: ps,\n      values: $spacers\n    ),\n    // Gap utility\n    \"gap\": (\n      responsive: true,\n      property: gap,\n      class: gap,\n      values: $spacers\n    ),\n    // scss-docs-end utils-spacing\n    // Text\n    // scss-docs-start utils-text\n    \"font-family\": (\n      property: font-family,\n      class: font,\n      values: (monospace: var(--#{$prefix}font-monospace))\n    ),\n    \"font-size\": (\n      rfs: true,\n      property: font-size,\n      class: fs,\n      values: $font-sizes\n    ),\n    \"font-style\": (\n      property: font-style,\n      class: fst,\n      values: italic normal\n    ),\n    \"font-weight\": (\n      property: font-weight,\n      class: fw,\n      values: (\n        light: $font-weight-light,\n        lighter: $font-weight-lighter,\n        normal: $font-weight-normal,\n        bold: $font-weight-bold,\n        semibold: $font-weight-semibold,\n        bolder: $font-weight-bolder\n      )\n    ),\n    \"line-height\": (\n      property: line-height,\n      class: lh,\n      values: (\n        1: 1,\n        sm: $line-height-sm,\n        base: $line-height-base,\n        lg: $line-height-lg,\n      )\n    ),\n    \"text-align\": (\n      responsive: true,\n      property: text-align,\n      class: text,\n      values: (\n        start: left,\n        end: right,\n        center: center,\n      )\n    ),\n    \"text-decoration\": (\n      property: text-decoration,\n      values: none underline line-through\n    ),\n    \"text-transform\": (\n      property: text-transform,\n      class: text,\n      values: lowercase uppercase capitalize\n    ),\n    \"white-space\": (\n      property: white-space,\n      class: text,\n      values: (\n        wrap: normal,\n        nowrap: nowrap,\n      )\n    ),\n    \"word-wrap\": (\n      property: word-wrap word-break,\n      class: text,\n      values: (break: break-word),\n      rtl: false\n    ),\n    // scss-docs-end utils-text\n    // scss-docs-start utils-color\n    \"color\": (\n      property: color,\n      class: text,\n      local-vars: (\n        \"text-opacity\": 1\n      ),\n      values: map-merge(\n        $utilities-text-colors,\n        (\n          \"muted\": $text-muted,\n          \"black-50\": rgba($black, .5), // deprecated\n          \"white-50\": rgba($white, .5), // deprecated\n          \"reset\": inherit,\n        )\n      )\n    ),\n    \"text-opacity\": (\n      css-var: true,\n      class: text-opacity,\n      values: (\n        25: .25,\n        50: .5,\n        75: .75,\n        100: 1\n      )\n    ),\n    // scss-docs-end utils-color\n    // scss-docs-start utils-bg-color\n    \"background-color\": (\n      property: background-color,\n      class: bg,\n      local-vars: (\n        \"bg-opacity\": 1\n      ),\n      values: map-merge(\n        $utilities-bg-colors,\n        (\n          \"transparent\": transparent\n        )\n      )\n    ),\n    \"bg-opacity\": (\n      css-var: true,\n      class: bg-opacity,\n      values: (\n        10: .1,\n        25: .25,\n        50: .5,\n        75: .75,\n        100: 1\n      )\n    ),\n    // scss-docs-end utils-bg-color\n    \"gradient\": (\n      property: background-image,\n      class: bg,\n      values: (gradient: var(--#{$prefix}gradient))\n    ),\n    // scss-docs-start utils-interaction\n    \"user-select\": (\n      property: user-select,\n      values: all auto none\n    ),\n    \"pointer-events\": (\n      property: pointer-events,\n      class: pe,\n      values: none auto,\n    ),\n    // scss-docs-end utils-interaction\n    // scss-docs-start utils-border-radius\n    \"rounded\": (\n      property: border-radius,\n      class: rounded,\n      values: (\n        null: var(--#{$prefix}border-radius),\n        0: 0,\n        1: var(--#{$prefix}border-radius-sm),\n        2: var(--#{$prefix}border-radius),\n        3: var(--#{$prefix}border-radius-lg),\n        4: var(--#{$prefix}border-radius-xl),\n        5: var(--#{$prefix}border-radius-2xl),\n        circle: 50%,\n        pill: var(--#{$prefix}border-radius-pill)\n      )\n    ),\n    \"rounded-top\": (\n      property: border-top-left-radius border-top-right-radius,\n      class: rounded-top,\n      values: (null: var(--#{$prefix}border-radius))\n    ),\n    \"rounded-end\": (\n      property: border-top-right-radius border-bottom-right-radius,\n      class: rounded-end,\n      values: (null: var(--#{$prefix}border-radius))\n    ),\n    \"rounded-bottom\": (\n      property: border-bottom-right-radius border-bottom-left-radius,\n      class: rounded-bottom,\n      values: (null: var(--#{$prefix}border-radius))\n    ),\n    \"rounded-start\": (\n      property: border-bottom-left-radius border-top-left-radius,\n      class: rounded-start,\n      values: (null: var(--#{$prefix}border-radius))\n    ),\n    // scss-docs-end utils-border-radius\n    // scss-docs-start utils-visibility\n    \"visibility\": (\n      property: visibility,\n      class: null,\n      values: (\n        visible: visible,\n        invisible: hidden,\n      )\n    )\n    // scss-docs-end utils-visibility\n  ),\n  $utilities\n);\n"
  },
  {
    "path": "src/common/bootstrap/scss/_variables.scss",
    "content": "// Variables\n//\n// Variables should follow the `$component-state-property-size` formula for\n// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.\n\n// Color system\n\n// scss-docs-start gray-color-variables\n$white:    #fff !default;\n$gray-100: #f8f9fa !default;\n$gray-200: #e9ecef !default;\n$gray-300: #dee2e6 !default;\n$gray-400: #ced4da !default;\n$gray-500: #adb5bd !default;\n$gray-600: #6c757d !default;\n$gray-700: #495057 !default;\n$gray-800: #343a40 !default;\n$gray-900: #212529 !default;\n$black:    #000 !default;\n// scss-docs-end gray-color-variables\n\n// fusv-disable\n// scss-docs-start gray-colors-map\n$grays: (\n  \"100\": $gray-100,\n  \"200\": $gray-200,\n  \"300\": $gray-300,\n  \"400\": $gray-400,\n  \"500\": $gray-500,\n  \"600\": $gray-600,\n  \"700\": $gray-700,\n  \"800\": $gray-800,\n  \"900\": $gray-900\n) !default;\n// scss-docs-end gray-colors-map\n// fusv-enable\n\n// scss-docs-start color-variables\n$blue:    #0d6efd !default;\n$indigo:  #6610f2 !default;\n$purple:  #6f42c1 !default;\n$pink:    #d63384 !default;\n$red:     #dc3545 !default;\n$orange:  #fd7e14 !default;\n$yellow:  #ffc107 !default;\n$green:   #198754 !default;\n$teal:    #20c997 !default;\n$cyan:    #0dcaf0 !default;\n// scss-docs-end color-variables\n\n// scss-docs-start colors-map\n$colors: (\n  \"blue\":       $blue,\n  \"indigo\":     $indigo,\n  \"purple\":     $purple,\n  \"pink\":       $pink,\n  \"red\":        $red,\n  \"orange\":     $orange,\n  \"yellow\":     $yellow,\n  \"green\":      $green,\n  \"teal\":       $teal,\n  \"cyan\":       $cyan,\n  \"black\":      $black,\n  \"white\":      $white,\n  \"gray\":       $gray-600,\n  \"gray-dark\":  $gray-800\n) !default;\n// scss-docs-end colors-map\n\n// The contrast ratio to reach against white, to determine if color changes from \"light\" to \"dark\". Acceptable values for WCAG 2.0 are 3, 4.5 and 7.\n// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast\n$min-contrast-ratio:   4.5 !default;\n\n// Customize the light and dark text colors for use in our color contrast function.\n$color-contrast-dark:      $black !default;\n$color-contrast-light:     $white !default;\n\n// fusv-disable\n$blue-100: tint-color($blue, 80%) !default;\n$blue-200: tint-color($blue, 60%) !default;\n$blue-300: tint-color($blue, 40%) !default;\n$blue-400: tint-color($blue, 20%) !default;\n$blue-500: $blue !default;\n$blue-600: shade-color($blue, 20%) !default;\n$blue-700: shade-color($blue, 40%) !default;\n$blue-800: shade-color($blue, 60%) !default;\n$blue-900: shade-color($blue, 80%) !default;\n\n$indigo-100: tint-color($indigo, 80%) !default;\n$indigo-200: tint-color($indigo, 60%) !default;\n$indigo-300: tint-color($indigo, 40%) !default;\n$indigo-400: tint-color($indigo, 20%) !default;\n$indigo-500: $indigo !default;\n$indigo-600: shade-color($indigo, 20%) !default;\n$indigo-700: shade-color($indigo, 40%) !default;\n$indigo-800: shade-color($indigo, 60%) !default;\n$indigo-900: shade-color($indigo, 80%) !default;\n\n$purple-100: tint-color($purple, 80%) !default;\n$purple-200: tint-color($purple, 60%) !default;\n$purple-300: tint-color($purple, 40%) !default;\n$purple-400: tint-color($purple, 20%) !default;\n$purple-500: $purple !default;\n$purple-600: shade-color($purple, 20%) !default;\n$purple-700: shade-color($purple, 40%) !default;\n$purple-800: shade-color($purple, 60%) !default;\n$purple-900: shade-color($purple, 80%) !default;\n\n$pink-100: tint-color($pink, 80%) !default;\n$pink-200: tint-color($pink, 60%) !default;\n$pink-300: tint-color($pink, 40%) !default;\n$pink-400: tint-color($pink, 20%) !default;\n$pink-500: $pink !default;\n$pink-600: shade-color($pink, 20%) !default;\n$pink-700: shade-color($pink, 40%) !default;\n$pink-800: shade-color($pink, 60%) !default;\n$pink-900: shade-color($pink, 80%) !default;\n\n$red-100: tint-color($red, 80%) !default;\n$red-200: tint-color($red, 60%) !default;\n$red-300: tint-color($red, 40%) !default;\n$red-400: tint-color($red, 20%) !default;\n$red-500: $red !default;\n$red-600: shade-color($red, 20%) !default;\n$red-700: shade-color($red, 40%) !default;\n$red-800: shade-color($red, 60%) !default;\n$red-900: shade-color($red, 80%) !default;\n\n$orange-100: tint-color($orange, 80%) !default;\n$orange-200: tint-color($orange, 60%) !default;\n$orange-300: tint-color($orange, 40%) !default;\n$orange-400: tint-color($orange, 20%) !default;\n$orange-500: $orange !default;\n$orange-600: shade-color($orange, 20%) !default;\n$orange-700: shade-color($orange, 40%) !default;\n$orange-800: shade-color($orange, 60%) !default;\n$orange-900: shade-color($orange, 80%) !default;\n\n$yellow-100: tint-color($yellow, 80%) !default;\n$yellow-200: tint-color($yellow, 60%) !default;\n$yellow-300: tint-color($yellow, 40%) !default;\n$yellow-400: tint-color($yellow, 20%) !default;\n$yellow-500: $yellow !default;\n$yellow-600: shade-color($yellow, 20%) !default;\n$yellow-700: shade-color($yellow, 40%) !default;\n$yellow-800: shade-color($yellow, 60%) !default;\n$yellow-900: shade-color($yellow, 80%) !default;\n\n$green-100: tint-color($green, 80%) !default;\n$green-200: tint-color($green, 60%) !default;\n$green-300: tint-color($green, 40%) !default;\n$green-400: tint-color($green, 20%) !default;\n$green-500: $green !default;\n$green-600: shade-color($green, 20%) !default;\n$green-700: shade-color($green, 40%) !default;\n$green-800: shade-color($green, 60%) !default;\n$green-900: shade-color($green, 80%) !default;\n\n$teal-100: tint-color($teal, 80%) !default;\n$teal-200: tint-color($teal, 60%) !default;\n$teal-300: tint-color($teal, 40%) !default;\n$teal-400: tint-color($teal, 20%) !default;\n$teal-500: $teal !default;\n$teal-600: shade-color($teal, 20%) !default;\n$teal-700: shade-color($teal, 40%) !default;\n$teal-800: shade-color($teal, 60%) !default;\n$teal-900: shade-color($teal, 80%) !default;\n\n$cyan-100: tint-color($cyan, 80%) !default;\n$cyan-200: tint-color($cyan, 60%) !default;\n$cyan-300: tint-color($cyan, 40%) !default;\n$cyan-400: tint-color($cyan, 20%) !default;\n$cyan-500: $cyan !default;\n$cyan-600: shade-color($cyan, 20%) !default;\n$cyan-700: shade-color($cyan, 40%) !default;\n$cyan-800: shade-color($cyan, 60%) !default;\n$cyan-900: shade-color($cyan, 80%) !default;\n\n$blues: (\n  \"blue-100\": $blue-100,\n  \"blue-200\": $blue-200,\n  \"blue-300\": $blue-300,\n  \"blue-400\": $blue-400,\n  \"blue-500\": $blue-500,\n  \"blue-600\": $blue-600,\n  \"blue-700\": $blue-700,\n  \"blue-800\": $blue-800,\n  \"blue-900\": $blue-900\n) !default;\n\n$indigos: (\n  \"indigo-100\": $indigo-100,\n  \"indigo-200\": $indigo-200,\n  \"indigo-300\": $indigo-300,\n  \"indigo-400\": $indigo-400,\n  \"indigo-500\": $indigo-500,\n  \"indigo-600\": $indigo-600,\n  \"indigo-700\": $indigo-700,\n  \"indigo-800\": $indigo-800,\n  \"indigo-900\": $indigo-900\n) !default;\n\n$purples: (\n  \"purple-100\": $purple-100,\n  \"purple-200\": $purple-200,\n  \"purple-300\": $purple-300,\n  \"purple-400\": $purple-400,\n  \"purple-500\": $purple-500,\n  \"purple-600\": $purple-600,\n  \"purple-700\": $purple-700,\n  \"purple-800\": $purple-800,\n  \"purple-900\": $purple-900\n) !default;\n\n$pinks: (\n  \"pink-100\": $pink-100,\n  \"pink-200\": $pink-200,\n  \"pink-300\": $pink-300,\n  \"pink-400\": $pink-400,\n  \"pink-500\": $pink-500,\n  \"pink-600\": $pink-600,\n  \"pink-700\": $pink-700,\n  \"pink-800\": $pink-800,\n  \"pink-900\": $pink-900\n) !default;\n\n$reds: (\n  \"red-100\": $red-100,\n  \"red-200\": $red-200,\n  \"red-300\": $red-300,\n  \"red-400\": $red-400,\n  \"red-500\": $red-500,\n  \"red-600\": $red-600,\n  \"red-700\": $red-700,\n  \"red-800\": $red-800,\n  \"red-900\": $red-900\n) !default;\n\n$oranges: (\n  \"orange-100\": $orange-100,\n  \"orange-200\": $orange-200,\n  \"orange-300\": $orange-300,\n  \"orange-400\": $orange-400,\n  \"orange-500\": $orange-500,\n  \"orange-600\": $orange-600,\n  \"orange-700\": $orange-700,\n  \"orange-800\": $orange-800,\n  \"orange-900\": $orange-900\n) !default;\n\n$yellows: (\n  \"yellow-100\": $yellow-100,\n  \"yellow-200\": $yellow-200,\n  \"yellow-300\": $yellow-300,\n  \"yellow-400\": $yellow-400,\n  \"yellow-500\": $yellow-500,\n  \"yellow-600\": $yellow-600,\n  \"yellow-700\": $yellow-700,\n  \"yellow-800\": $yellow-800,\n  \"yellow-900\": $yellow-900\n) !default;\n\n$greens: (\n  \"green-100\": $green-100,\n  \"green-200\": $green-200,\n  \"green-300\": $green-300,\n  \"green-400\": $green-400,\n  \"green-500\": $green-500,\n  \"green-600\": $green-600,\n  \"green-700\": $green-700,\n  \"green-800\": $green-800,\n  \"green-900\": $green-900\n) !default;\n\n$teals: (\n  \"teal-100\": $teal-100,\n  \"teal-200\": $teal-200,\n  \"teal-300\": $teal-300,\n  \"teal-400\": $teal-400,\n  \"teal-500\": $teal-500,\n  \"teal-600\": $teal-600,\n  \"teal-700\": $teal-700,\n  \"teal-800\": $teal-800,\n  \"teal-900\": $teal-900\n) !default;\n\n$cyans: (\n  \"cyan-100\": $cyan-100,\n  \"cyan-200\": $cyan-200,\n  \"cyan-300\": $cyan-300,\n  \"cyan-400\": $cyan-400,\n  \"cyan-500\": $cyan-500,\n  \"cyan-600\": $cyan-600,\n  \"cyan-700\": $cyan-700,\n  \"cyan-800\": $cyan-800,\n  \"cyan-900\": $cyan-900\n) !default;\n// fusv-enable\n\n// scss-docs-start theme-color-variables\n$primary:       $blue !default;\n$secondary:     $gray-600 !default;\n$success:       $green !default;\n$info:          $cyan !default;\n$warning:       $yellow !default;\n$danger:        $red !default;\n$light:         $gray-100 !default;\n$dark:          $gray-900 !default;\n// scss-docs-end theme-color-variables\n\n// scss-docs-start theme-colors-map\n$theme-colors: (\n  \"primary\":    $primary,\n  \"secondary\":  $secondary,\n  \"success\":    $success,\n  \"info\":       $info,\n  \"warning\":    $warning,\n  \"danger\":     $danger,\n  \"light\":      $light,\n  \"dark\":       $dark\n) !default;\n// scss-docs-end theme-colors-map\n\n// Characters which are escaped by the escape-svg function\n$escaped-characters: (\n  (\"<\", \"%3c\"),\n  (\">\", \"%3e\"),\n  (\"#\", \"%23\"),\n  (\"(\", \"%28\"),\n  (\")\", \"%29\"),\n) !default;\n\n// Options\n//\n// Quickly modify global styling by enabling or disabling optional features.\n\n$enable-caret:                true !default;\n$enable-rounded:              true !default;\n$enable-shadows:              false !default;\n$enable-gradients:            false !default;\n$enable-transitions:          true !default;\n$enable-reduced-motion:       true !default;\n$enable-smooth-scroll:        true !default;\n$enable-grid-classes:         true !default;\n$enable-container-classes:    true !default;\n$enable-cssgrid:              false !default;\n$enable-button-pointers:      true !default;\n$enable-rfs:                  true !default;\n$enable-validation-icons:     true !default;\n$enable-negative-margins:     false !default;\n$enable-deprecation-messages: true !default;\n$enable-important-utilities:  true !default;\n\n// Prefix for :root CSS variables\n\n$variable-prefix:             bs- !default; // Deprecated in v5.2.0 for the shorter `$prefix`\n$prefix:                      $variable-prefix !default;\n\n// Gradient\n//\n// The gradient which is added to components if `$enable-gradients` is `true`\n// This gradient is also added to elements with `.bg-gradient`\n// scss-docs-start variable-gradient\n$gradient: linear-gradient(180deg, rgba($white, .15), rgba($white, 0)) !default;\n// scss-docs-end variable-gradient\n\n// Spacing\n//\n// Control the default styling of most Bootstrap elements by modifying these\n// variables. Mostly focused on spacing.\n// You can add more entries to the $spacers map, should you need more variation.\n\n// scss-docs-start spacer-variables-maps\n$spacer: 1rem !default;\n$spacers: (\n  0: 0,\n  1: $spacer * .25,\n  2: $spacer * .5,\n  3: $spacer,\n  4: $spacer * 1.5,\n  5: $spacer * 3,\n) !default;\n// scss-docs-end spacer-variables-maps\n\n// Position\n//\n// Define the edge positioning anchors of the position utilities.\n\n// scss-docs-start position-map\n$position-values: (\n  0: 0,\n  50: 50%,\n  100: 100%\n) !default;\n// scss-docs-end position-map\n\n// Body\n//\n// Settings for the `<body>` element.\n\n$body-bg:                   $white !default;\n$body-color:                $gray-900 !default;\n$body-text-align:           null !default;\n\n// Links\n//\n// Style anchor elements.\n\n$link-color:                              $primary !default;\n$link-decoration:                         underline !default;\n$link-shade-percentage:                   20% !default;\n$link-hover-color:                        shift-color($link-color, $link-shade-percentage) !default;\n$link-hover-decoration:                   null !default;\n\n$stretched-link-pseudo-element:           after !default;\n$stretched-link-z-index:                  1 !default;\n\n// Paragraphs\n//\n// Style p element.\n\n$paragraph-margin-bottom:   1rem !default;\n\n\n// Grid breakpoints\n//\n// Define the minimum dimensions at which your layout will change,\n// adapting to different screen sizes, for use in media queries.\n\n// scss-docs-start grid-breakpoints\n$grid-breakpoints: (\n  xs: 0,\n  sm: 576px,\n  md: 768px,\n  lg: 992px,\n  xl: 1200px,\n  xxl: 1400px\n) !default;\n// scss-docs-end grid-breakpoints\n\n@include _assert-ascending($grid-breakpoints, \"$grid-breakpoints\");\n@include _assert-starts-at-zero($grid-breakpoints, \"$grid-breakpoints\");\n\n\n// Grid containers\n//\n// Define the maximum width of `.container` for different screen sizes.\n\n// scss-docs-start container-max-widths\n$container-max-widths: (\n  sm: 540px,\n  md: 720px,\n  lg: 960px,\n  xl: 1140px,\n  xxl: 1320px\n) !default;\n// scss-docs-end container-max-widths\n\n@include _assert-ascending($container-max-widths, \"$container-max-widths\");\n\n\n// Grid columns\n//\n// Set the number of columns and specify the width of the gutters.\n\n$grid-columns:                12 !default;\n$grid-gutter-width:           1.5rem !default;\n$grid-row-columns:            6 !default;\n\n// Container padding\n\n$container-padding-x: $grid-gutter-width !default;\n\n\n// Components\n//\n// Define common padding and border radius sizes and more.\n\n// scss-docs-start border-variables\n$border-width:                1px !default;\n$border-widths: (\n  1: 1px,\n  2: 2px,\n  3: 3px,\n  4: 4px,\n  5: 5px\n) !default;\n\n$border-style:                solid !default;\n$border-color:                $gray-300 !default;\n$border-color-translucent:    rgba($black, .175) !default;\n// scss-docs-end border-variables\n\n// scss-docs-start border-radius-variables\n$border-radius:               .375rem !default;\n$border-radius-sm:            .25rem !default;\n$border-radius-lg:            .5rem !default;\n$border-radius-xl:            1rem !default;\n$border-radius-2xl:           2rem !default;\n$border-radius-pill:          50rem !default;\n// scss-docs-end border-radius-variables\n\n// scss-docs-start box-shadow-variables\n$box-shadow:                  0 .5rem 1rem rgba($black, .15) !default;\n$box-shadow-sm:               0 .125rem .25rem rgba($black, .075) !default;\n$box-shadow-lg:               0 1rem 3rem rgba($black, .175) !default;\n$box-shadow-inset:            inset 0 1px 2px rgba($black, .075) !default;\n// scss-docs-end box-shadow-variables\n\n$component-active-color:      $white !default;\n$component-active-bg:         $primary !default;\n\n// scss-docs-start caret-variables\n$caret-width:                 .3em !default;\n$caret-vertical-align:        $caret-width * .85 !default;\n$caret-spacing:               $caret-width * .85 !default;\n// scss-docs-end caret-variables\n\n$transition-base:             all .2s ease-in-out !default;\n$transition-fade:             opacity .15s linear !default;\n// scss-docs-start collapse-transition\n$transition-collapse:         height .35s ease !default;\n$transition-collapse-width:   width .35s ease !default;\n// scss-docs-end collapse-transition\n\n// stylelint-disable function-disallowed-list\n// scss-docs-start aspect-ratios\n$aspect-ratios: (\n  \"1x1\": 100%,\n  \"4x3\": calc(3 / 4 * 100%),\n  \"16x9\": calc(9 / 16 * 100%),\n  \"21x9\": calc(9 / 21 * 100%)\n) !default;\n// scss-docs-end aspect-ratios\n// stylelint-enable function-disallowed-list\n\n// Typography\n//\n// Font, line-height, and color for body text, headings, and more.\n\n// scss-docs-start font-variables\n// stylelint-disable value-keyword-case\n$font-family-sans-serif:      system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\" !default;\n$font-family-monospace:       SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !default;\n// stylelint-enable value-keyword-case\n$font-family-base:            var(--#{$prefix}font-sans-serif) !default;\n$font-family-code:            var(--#{$prefix}font-monospace) !default;\n\n// $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins\n// $font-size-base affects the font size of the body text\n$font-size-root:              null !default;\n$font-size-base:              1rem !default; // Assumes the browser default, typically `16px`\n$font-size-sm:                $font-size-base * .875 !default;\n$font-size-lg:                $font-size-base * 1.25 !default;\n\n$font-weight-lighter:         lighter !default;\n$font-weight-light:           300 !default;\n$font-weight-normal:          400 !default;\n$font-weight-semibold:        600 !default;\n$font-weight-bold:            700 !default;\n$font-weight-bolder:          bolder !default;\n\n$font-weight-base:            $font-weight-normal !default;\n\n$line-height-base:            1.5 !default;\n$line-height-sm:              1.25 !default;\n$line-height-lg:              2 !default;\n\n$h1-font-size:                $font-size-base * 2.5 !default;\n$h2-font-size:                $font-size-base * 2 !default;\n$h3-font-size:                $font-size-base * 1.75 !default;\n$h4-font-size:                $font-size-base * 1.5 !default;\n$h5-font-size:                $font-size-base * 1.25 !default;\n$h6-font-size:                $font-size-base !default;\n// scss-docs-end font-variables\n\n// scss-docs-start font-sizes\n$font-sizes: (\n  1: $h1-font-size,\n  2: $h2-font-size,\n  3: $h3-font-size,\n  4: $h4-font-size,\n  5: $h5-font-size,\n  6: $h6-font-size\n) !default;\n// scss-docs-end font-sizes\n\n// scss-docs-start headings-variables\n$headings-margin-bottom:      $spacer * .5 !default;\n$headings-font-family:        null !default;\n$headings-font-style:         null !default;\n$headings-font-weight:        500 !default;\n$headings-line-height:        1.2 !default;\n$headings-color:              null !default;\n// scss-docs-end headings-variables\n\n// scss-docs-start display-headings\n$display-font-sizes: (\n  1: 5rem,\n  2: 4.5rem,\n  3: 4rem,\n  4: 3.5rem,\n  5: 3rem,\n  6: 2.5rem\n) !default;\n\n$display-font-family: null !default;\n$display-font-style:  null !default;\n$display-font-weight: 300 !default;\n$display-line-height: $headings-line-height !default;\n// scss-docs-end display-headings\n\n// scss-docs-start type-variables\n$lead-font-size:              $font-size-base * 1.25 !default;\n$lead-font-weight:            300 !default;\n\n$small-font-size:             .875em !default;\n\n$sub-sup-font-size:           .75em !default;\n\n$text-muted:                  $gray-600 !default;\n\n$initialism-font-size:        $small-font-size !default;\n\n$blockquote-margin-y:         $spacer !default;\n$blockquote-font-size:        $font-size-base * 1.25 !default;\n$blockquote-footer-color:     $gray-600 !default;\n$blockquote-footer-font-size: $small-font-size !default;\n\n$hr-margin-y:                 $spacer !default;\n$hr-color:                    inherit !default;\n\n// fusv-disable\n$hr-bg-color:                 null !default; // Deprecated in v5.2.0\n$hr-height:                   null !default; // Deprecated in v5.2.0\n// fusv-enable\n\n$hr-border-color:             null !default; // Allows for inherited colors\n$hr-border-width:             $border-width !default;\n$hr-opacity:                  .25 !default;\n\n$legend-margin-bottom:        .5rem !default;\n$legend-font-size:            1.5rem !default;\n$legend-font-weight:          null !default;\n\n$dt-font-weight:              $font-weight-bold !default;\n\n$list-inline-padding:         .5rem !default;\n\n$mark-padding:                .1875em !default;\n$mark-bg:                     $yellow-100 !default;\n// scss-docs-end type-variables\n\n\n// Tables\n//\n// Customizes the `.table` component with basic values, each used across all table variations.\n\n// scss-docs-start table-variables\n$table-cell-padding-y:        .5rem !default;\n$table-cell-padding-x:        .5rem !default;\n$table-cell-padding-y-sm:     .25rem !default;\n$table-cell-padding-x-sm:     .25rem !default;\n\n$table-cell-vertical-align:   top !default;\n\n$table-color:                 var(--#{$prefix}body-color) !default;\n$table-bg:                    transparent !default;\n$table-accent-bg:             transparent !default;\n\n$table-th-font-weight:        null !default;\n\n$table-striped-color:         $table-color !default;\n$table-striped-bg-factor:     .05 !default;\n$table-striped-bg:            rgba($black, $table-striped-bg-factor) !default;\n\n$table-active-color:          $table-color !default;\n$table-active-bg-factor:      .1 !default;\n$table-active-bg:             rgba($black, $table-active-bg-factor) !default;\n\n$table-hover-color:           $table-color !default;\n$table-hover-bg-factor:       .075 !default;\n$table-hover-bg:              rgba($black, $table-hover-bg-factor) !default;\n\n$table-border-factor:         .1 !default;\n$table-border-width:          $border-width !default;\n$table-border-color:          var(--#{$prefix}border-color) !default;\n\n$table-striped-order:         odd !default;\n$table-striped-columns-order: even !default;\n\n$table-group-separator-color: currentcolor !default;\n\n$table-caption-color:         $text-muted !default;\n\n$table-bg-scale:              -80% !default;\n// scss-docs-end table-variables\n\n// scss-docs-start table-loop\n$table-variants: (\n  \"primary\":    shift-color($primary, $table-bg-scale),\n  \"secondary\":  shift-color($secondary, $table-bg-scale),\n  \"success\":    shift-color($success, $table-bg-scale),\n  \"info\":       shift-color($info, $table-bg-scale),\n  \"warning\":    shift-color($warning, $table-bg-scale),\n  \"danger\":     shift-color($danger, $table-bg-scale),\n  \"light\":      $light,\n  \"dark\":       $dark,\n) !default;\n// scss-docs-end table-loop\n\n\n// Buttons + Forms\n//\n// Shared variables that are reassigned to `$input-` and `$btn-` specific variables.\n\n// scss-docs-start input-btn-variables\n$input-btn-padding-y:         .375rem !default;\n$input-btn-padding-x:         .75rem !default;\n$input-btn-font-family:       null !default;\n$input-btn-font-size:         $font-size-base !default;\n$input-btn-line-height:       $line-height-base !default;\n\n$input-btn-focus-width:         .25rem !default;\n$input-btn-focus-color-opacity: .25 !default;\n$input-btn-focus-color:         rgba($component-active-bg, $input-btn-focus-color-opacity) !default;\n$input-btn-focus-blur:          0 !default;\n$input-btn-focus-box-shadow:    0 0 $input-btn-focus-blur $input-btn-focus-width $input-btn-focus-color !default;\n\n$input-btn-padding-y-sm:      .25rem !default;\n$input-btn-padding-x-sm:      .5rem !default;\n$input-btn-font-size-sm:      $font-size-sm !default;\n\n$input-btn-padding-y-lg:      .5rem !default;\n$input-btn-padding-x-lg:      1rem !default;\n$input-btn-font-size-lg:      $font-size-lg !default;\n\n$input-btn-border-width:      $border-width !default;\n// scss-docs-end input-btn-variables\n\n\n// Buttons\n//\n// For each of Bootstrap's buttons, define text, background, and border color.\n\n// scss-docs-start btn-variables\n$btn-padding-y:               $input-btn-padding-y !default;\n$btn-padding-x:               $input-btn-padding-x !default;\n$btn-font-family:             $input-btn-font-family !default;\n$btn-font-size:               $input-btn-font-size !default;\n$btn-line-height:             $input-btn-line-height !default;\n$btn-white-space:             null !default; // Set to `nowrap` to prevent text wrapping\n\n$btn-padding-y-sm:            $input-btn-padding-y-sm !default;\n$btn-padding-x-sm:            $input-btn-padding-x-sm !default;\n$btn-font-size-sm:            $input-btn-font-size-sm !default;\n\n$btn-padding-y-lg:            $input-btn-padding-y-lg !default;\n$btn-padding-x-lg:            $input-btn-padding-x-lg !default;\n$btn-font-size-lg:            $input-btn-font-size-lg !default;\n\n$btn-border-width:            $input-btn-border-width !default;\n\n$btn-font-weight:             $font-weight-normal !default;\n$btn-box-shadow:              inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;\n$btn-focus-width:             $input-btn-focus-width !default;\n$btn-focus-box-shadow:        $input-btn-focus-box-shadow !default;\n$btn-disabled-opacity:        .65 !default;\n$btn-active-box-shadow:       inset 0 3px 5px rgba($black, .125) !default;\n\n$btn-link-color:              var(--#{$prefix}link-color) !default;\n$btn-link-hover-color:        var(--#{$prefix}link-hover-color) !default;\n$btn-link-disabled-color:     $gray-600 !default;\n\n// Allows for customizing button radius independently from global border radius\n$btn-border-radius:           $border-radius !default;\n$btn-border-radius-sm:        $border-radius-sm !default;\n$btn-border-radius-lg:        $border-radius-lg !default;\n\n$btn-transition:              color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;\n\n$btn-hover-bg-shade-amount:       15% !default;\n$btn-hover-bg-tint-amount:        15% !default;\n$btn-hover-border-shade-amount:   20% !default;\n$btn-hover-border-tint-amount:    10% !default;\n$btn-active-bg-shade-amount:      20% !default;\n$btn-active-bg-tint-amount:       20% !default;\n$btn-active-border-shade-amount:  25% !default;\n$btn-active-border-tint-amount:   10% !default;\n// scss-docs-end btn-variables\n\n\n// Forms\n\n// scss-docs-start form-text-variables\n$form-text-margin-top:                  .25rem !default;\n$form-text-font-size:                   $small-font-size !default;\n$form-text-font-style:                  null !default;\n$form-text-font-weight:                 null !default;\n$form-text-color:                       $text-muted !default;\n// scss-docs-end form-text-variables\n\n// scss-docs-start form-label-variables\n$form-label-margin-bottom:              .5rem !default;\n$form-label-font-size:                  null !default;\n$form-label-font-style:                 null !default;\n$form-label-font-weight:                null !default;\n$form-label-color:                      null !default;\n// scss-docs-end form-label-variables\n\n// scss-docs-start form-input-variables\n$input-padding-y:                       $input-btn-padding-y !default;\n$input-padding-x:                       $input-btn-padding-x !default;\n$input-font-family:                     $input-btn-font-family !default;\n$input-font-size:                       $input-btn-font-size !default;\n$input-font-weight:                     $font-weight-base !default;\n$input-line-height:                     $input-btn-line-height !default;\n\n$input-padding-y-sm:                    $input-btn-padding-y-sm !default;\n$input-padding-x-sm:                    $input-btn-padding-x-sm !default;\n$input-font-size-sm:                    $input-btn-font-size-sm !default;\n\n$input-padding-y-lg:                    $input-btn-padding-y-lg !default;\n$input-padding-x-lg:                    $input-btn-padding-x-lg !default;\n$input-font-size-lg:                    $input-btn-font-size-lg !default;\n\n$input-bg:                              $body-bg !default;\n$input-disabled-color:                  null !default;\n$input-disabled-bg:                     $gray-200 !default;\n$input-disabled-border-color:           null !default;\n\n$input-color:                           $body-color !default;\n$input-border-color:                    $gray-400 !default;\n$input-border-width:                    $input-btn-border-width !default;\n$input-box-shadow:                      $box-shadow-inset !default;\n\n$input-border-radius:                   $border-radius !default;\n$input-border-radius-sm:                $border-radius-sm !default;\n$input-border-radius-lg:                $border-radius-lg !default;\n\n$input-focus-bg:                        $input-bg !default;\n$input-focus-border-color:              tint-color($component-active-bg, 50%) !default;\n$input-focus-color:                     $input-color !default;\n$input-focus-width:                     $input-btn-focus-width !default;\n$input-focus-box-shadow:                $input-btn-focus-box-shadow !default;\n\n$input-placeholder-color:               $gray-600 !default;\n$input-plaintext-color:                 $body-color !default;\n\n$input-height-border:                   $input-border-width * 2 !default;\n\n$input-height-inner:                    add($input-line-height * 1em, $input-padding-y * 2) !default;\n$input-height-inner-half:               add($input-line-height * .5em, $input-padding-y) !default;\n$input-height-inner-quarter:            add($input-line-height * .25em, $input-padding-y * .5) !default;\n\n$input-height:                          add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default;\n$input-height-sm:                       add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default;\n$input-height-lg:                       add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default;\n\n$input-transition:                      border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;\n\n$form-color-width:                      3rem !default;\n// scss-docs-end form-input-variables\n\n// scss-docs-start form-check-variables\n$form-check-input-width:                  1em !default;\n$form-check-min-height:                   $font-size-base * $line-height-base !default;\n$form-check-padding-start:                $form-check-input-width + .5em !default;\n$form-check-margin-bottom:                .125rem !default;\n$form-check-label-color:                  null !default;\n$form-check-label-cursor:                 null !default;\n$form-check-transition:                   null !default;\n\n$form-check-input-active-filter:          brightness(90%) !default;\n\n$form-check-input-bg:                     $input-bg !default;\n$form-check-input-border:                 1px solid rgba($black, .25) !default;\n$form-check-input-border-radius:          .25em !default;\n$form-check-radio-border-radius:          50% !default;\n$form-check-input-focus-border:           $input-focus-border-color !default;\n$form-check-input-focus-box-shadow:       $input-btn-focus-box-shadow !default;\n\n$form-check-input-checked-color:          $component-active-color !default;\n$form-check-input-checked-bg-color:       $component-active-bg !default;\n$form-check-input-checked-border-color:   $form-check-input-checked-bg-color !default;\n$form-check-input-checked-bg-image:       url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill='none' stroke='#{$form-check-input-checked-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/></svg>\") !default;\n$form-check-radio-checked-bg-image:       url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='2' fill='#{$form-check-input-checked-color}'/></svg>\") !default;\n\n$form-check-input-indeterminate-color:          $component-active-color !default;\n$form-check-input-indeterminate-bg-color:       $component-active-bg !default;\n$form-check-input-indeterminate-border-color:   $form-check-input-indeterminate-bg-color !default;\n$form-check-input-indeterminate-bg-image:       url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill='none' stroke='#{$form-check-input-indeterminate-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/></svg>\") !default;\n\n$form-check-input-disabled-opacity:        .5 !default;\n$form-check-label-disabled-opacity:        $form-check-input-disabled-opacity !default;\n$form-check-btn-check-disabled-opacity:    $btn-disabled-opacity !default;\n\n$form-check-inline-margin-end:    1rem !default;\n// scss-docs-end form-check-variables\n\n// scss-docs-start form-switch-variables\n$form-switch-color:               rgba($black, .25) !default;\n$form-switch-width:               2em !default;\n$form-switch-padding-start:       $form-switch-width + .5em !default;\n$form-switch-bg-image:            url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color}'/></svg>\") !default;\n$form-switch-border-radius:       $form-switch-width !default;\n$form-switch-transition:          background-position .15s ease-in-out !default;\n\n$form-switch-focus-color:         $input-focus-border-color !default;\n$form-switch-focus-bg-image:      url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-focus-color}'/></svg>\") !default;\n\n$form-switch-checked-color:       $component-active-color !default;\n$form-switch-checked-bg-image:    url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-checked-color}'/></svg>\") !default;\n$form-switch-checked-bg-position: right center !default;\n// scss-docs-end form-switch-variables\n\n// scss-docs-start input-group-variables\n$input-group-addon-padding-y:           $input-padding-y !default;\n$input-group-addon-padding-x:           $input-padding-x !default;\n$input-group-addon-font-weight:         $input-font-weight !default;\n$input-group-addon-color:               $input-color !default;\n$input-group-addon-bg:                  $gray-200 !default;\n$input-group-addon-border-color:        $input-border-color !default;\n// scss-docs-end input-group-variables\n\n// scss-docs-start form-select-variables\n$form-select-padding-y:             $input-padding-y !default;\n$form-select-padding-x:             $input-padding-x !default;\n$form-select-font-family:           $input-font-family !default;\n$form-select-font-size:             $input-font-size !default;\n$form-select-indicator-padding:     $form-select-padding-x * 3 !default; // Extra padding for background-image\n$form-select-font-weight:           $input-font-weight !default;\n$form-select-line-height:           $input-line-height !default;\n$form-select-color:                 $input-color !default;\n$form-select-bg:                    $input-bg !default;\n$form-select-disabled-color:        null !default;\n$form-select-disabled-bg:           $gray-200 !default;\n$form-select-disabled-border-color: $input-disabled-border-color !default;\n$form-select-bg-position:           right $form-select-padding-x center !default;\n$form-select-bg-size:               16px 12px !default; // In pixels because image dimensions\n$form-select-indicator-color:       $gray-800 !default;\n$form-select-indicator:             url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>\") !default;\n\n$form-select-feedback-icon-padding-end: $form-select-padding-x * 2.5 + $form-select-indicator-padding !default;\n$form-select-feedback-icon-position:    center right $form-select-indicator-padding !default;\n$form-select-feedback-icon-size:        $input-height-inner-half $input-height-inner-half !default;\n\n$form-select-border-width:        $input-border-width !default;\n$form-select-border-color:        $input-border-color !default;\n$form-select-border-radius:       $input-border-radius !default;\n$form-select-box-shadow:          $box-shadow-inset !default;\n\n$form-select-focus-border-color:  $input-focus-border-color !default;\n$form-select-focus-width:         $input-focus-width !default;\n$form-select-focus-box-shadow:    0 0 0 $form-select-focus-width $input-btn-focus-color !default;\n\n$form-select-padding-y-sm:        $input-padding-y-sm !default;\n$form-select-padding-x-sm:        $input-padding-x-sm !default;\n$form-select-font-size-sm:        $input-font-size-sm !default;\n$form-select-border-radius-sm:    $input-border-radius-sm !default;\n\n$form-select-padding-y-lg:        $input-padding-y-lg !default;\n$form-select-padding-x-lg:        $input-padding-x-lg !default;\n$form-select-font-size-lg:        $input-font-size-lg !default;\n$form-select-border-radius-lg:    $input-border-radius-lg !default;\n\n$form-select-transition:          $input-transition !default;\n// scss-docs-end form-select-variables\n\n// scss-docs-start form-range-variables\n$form-range-track-width:          100% !default;\n$form-range-track-height:         .5rem !default;\n$form-range-track-cursor:         pointer !default;\n$form-range-track-bg:             $gray-300 !default;\n$form-range-track-border-radius:  1rem !default;\n$form-range-track-box-shadow:     $box-shadow-inset !default;\n\n$form-range-thumb-width:                   1rem !default;\n$form-range-thumb-height:                  $form-range-thumb-width !default;\n$form-range-thumb-bg:                      $component-active-bg !default;\n$form-range-thumb-border:                  0 !default;\n$form-range-thumb-border-radius:           1rem !default;\n$form-range-thumb-box-shadow:              0 .1rem .25rem rgba($black, .1) !default;\n$form-range-thumb-focus-box-shadow:        0 0 0 1px $body-bg, $input-focus-box-shadow !default;\n$form-range-thumb-focus-box-shadow-width:  $input-focus-width !default; // For focus box shadow issue in Edge\n$form-range-thumb-active-bg:               tint-color($component-active-bg, 70%) !default;\n$form-range-thumb-disabled-bg:             $gray-500 !default;\n$form-range-thumb-transition:              background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;\n// scss-docs-end form-range-variables\n\n// scss-docs-start form-file-variables\n$form-file-button-color:          $input-color !default;\n$form-file-button-bg:             $input-group-addon-bg !default;\n$form-file-button-hover-bg:       shade-color($form-file-button-bg, 5%) !default;\n// scss-docs-end form-file-variables\n\n// scss-docs-start form-floating-variables\n$form-floating-height:            add(3.5rem, $input-height-border) !default;\n$form-floating-line-height:       1.25 !default;\n$form-floating-padding-x:         $input-padding-x !default;\n$form-floating-padding-y:         1rem !default;\n$form-floating-input-padding-t:   1.625rem !default;\n$form-floating-input-padding-b:   .625rem !default;\n$form-floating-label-opacity:     .65 !default;\n$form-floating-label-transform:   scale(.85) translateY(-.5rem) translateX(.15rem) !default;\n$form-floating-transition:        opacity .1s ease-in-out, transform .1s ease-in-out !default;\n// scss-docs-end form-floating-variables\n\n// Form validation\n\n// scss-docs-start form-feedback-variables\n$form-feedback-margin-top:          $form-text-margin-top !default;\n$form-feedback-font-size:           $form-text-font-size !default;\n$form-feedback-font-style:          $form-text-font-style !default;\n$form-feedback-valid-color:         $success !default;\n$form-feedback-invalid-color:       $danger !default;\n\n$form-feedback-icon-valid-color:    $form-feedback-valid-color !default;\n$form-feedback-icon-valid:          url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#{$form-feedback-icon-valid-color}' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>\") !default;\n$form-feedback-icon-invalid-color:  $form-feedback-invalid-color !default;\n$form-feedback-icon-invalid:        url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='#{$form-feedback-icon-invalid-color}'><circle cx='6' cy='6' r='4.5'/><path stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/><circle cx='6' cy='8.2' r='.6' fill='#{$form-feedback-icon-invalid-color}' stroke='none'/></svg>\") !default;\n// scss-docs-end form-feedback-variables\n\n// scss-docs-start form-validation-states\n$form-validation-states: (\n  \"valid\": (\n    \"color\": $form-feedback-valid-color,\n    \"icon\": $form-feedback-icon-valid\n  ),\n  \"invalid\": (\n    \"color\": $form-feedback-invalid-color,\n    \"icon\": $form-feedback-icon-invalid\n  )\n) !default;\n// scss-docs-end form-validation-states\n\n// Z-index master list\n//\n// Warning: Avoid customizing these values. They're used for a bird's eye view\n// of components dependent on the z-axis and are designed to all work together.\n\n// scss-docs-start zindex-stack\n$zindex-dropdown:                   1000 !default;\n$zindex-sticky:                     1020 !default;\n$zindex-fixed:                      1030 !default;\n$zindex-offcanvas-backdrop:         1040 !default;\n$zindex-offcanvas:                  1045 !default;\n$zindex-modal-backdrop:             1050 !default;\n$zindex-modal:                      1055 !default;\n$zindex-popover:                    1070 !default;\n$zindex-tooltip:                    1080 !default;\n$zindex-toast:                      1090 !default;\n// scss-docs-end zindex-stack\n\n\n// Navs\n\n// scss-docs-start nav-variables\n$nav-link-padding-y:                .5rem !default;\n$nav-link-padding-x:                1rem !default;\n$nav-link-font-size:                null !default;\n$nav-link-font-weight:              null !default;\n$nav-link-color:                    var(--#{$prefix}link-color) !default;\n$nav-link-hover-color:              var(--#{$prefix}link-hover-color) !default;\n$nav-link-transition:               color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default;\n$nav-link-disabled-color:           $gray-600 !default;\n\n$nav-tabs-border-color:             $gray-300 !default;\n$nav-tabs-border-width:             $border-width !default;\n$nav-tabs-border-radius:            $border-radius !default;\n$nav-tabs-link-hover-border-color:  $gray-200 $gray-200 $nav-tabs-border-color !default;\n$nav-tabs-link-active-color:        $gray-700 !default;\n$nav-tabs-link-active-bg:           $body-bg !default;\n$nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default;\n\n$nav-pills-border-radius:           $border-radius !default;\n$nav-pills-link-active-color:       $component-active-color !default;\n$nav-pills-link-active-bg:          $component-active-bg !default;\n// scss-docs-end nav-variables\n\n\n// Navbar\n\n// scss-docs-start navbar-variables\n$navbar-padding-y:                  $spacer * .5 !default;\n$navbar-padding-x:                  null !default;\n\n$navbar-nav-link-padding-x:         .5rem !default;\n\n$navbar-brand-font-size:            $font-size-lg !default;\n// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link\n$nav-link-height:                   $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default;\n$navbar-brand-height:               $navbar-brand-font-size * $line-height-base !default;\n$navbar-brand-padding-y:            ($nav-link-height - $navbar-brand-height) * .5 !default;\n$navbar-brand-margin-end:           1rem !default;\n\n$navbar-toggler-padding-y:          .25rem !default;\n$navbar-toggler-padding-x:          .75rem !default;\n$navbar-toggler-font-size:          $font-size-lg !default;\n$navbar-toggler-border-radius:      $btn-border-radius !default;\n$navbar-toggler-focus-width:        $btn-focus-width !default;\n$navbar-toggler-transition:         box-shadow .15s ease-in-out !default;\n\n$navbar-light-color:                rgba($black, .55) !default;\n$navbar-light-hover-color:          rgba($black, .7) !default;\n$navbar-light-active-color:         rgba($black, .9) !default;\n$navbar-light-disabled-color:       rgba($black, .3) !default;\n$navbar-light-toggler-icon-bg:      url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>\") !default;\n$navbar-light-toggler-border-color: rgba($black, .1) !default;\n$navbar-light-brand-color:          $navbar-light-active-color !default;\n$navbar-light-brand-hover-color:    $navbar-light-active-color !default;\n// scss-docs-end navbar-variables\n\n// scss-docs-start navbar-dark-variables\n$navbar-dark-color:                 rgba($white, .55) !default;\n$navbar-dark-hover-color:           rgba($white, .75) !default;\n$navbar-dark-active-color:          $white !default;\n$navbar-dark-disabled-color:        rgba($white, .25) !default;\n$navbar-dark-toggler-icon-bg:       url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-dark-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>\") !default;\n$navbar-dark-toggler-border-color:  rgba($white, .1) !default;\n$navbar-dark-brand-color:           $navbar-dark-active-color !default;\n$navbar-dark-brand-hover-color:     $navbar-dark-active-color !default;\n// scss-docs-end navbar-dark-variables\n\n\n// Dropdowns\n//\n// Dropdown menu container and contents.\n\n// scss-docs-start dropdown-variables\n$dropdown-min-width:                10rem !default;\n$dropdown-padding-x:                0 !default;\n$dropdown-padding-y:                .5rem !default;\n$dropdown-spacer:                   .125rem !default;\n$dropdown-font-size:                $font-size-base !default;\n$dropdown-color:                    $body-color !default;\n$dropdown-bg:                       $white !default;\n$dropdown-border-color:             var(--#{$prefix}border-color-translucent) !default;\n$dropdown-border-radius:            $border-radius !default;\n$dropdown-border-width:             $border-width !default;\n$dropdown-inner-border-radius:      subtract($dropdown-border-radius, $dropdown-border-width) !default;\n$dropdown-divider-bg:               $dropdown-border-color !default;\n$dropdown-divider-margin-y:         $spacer * .5 !default;\n$dropdown-box-shadow:               $box-shadow !default;\n\n$dropdown-link-color:               $gray-900 !default;\n$dropdown-link-hover-color:         shade-color($dropdown-link-color, 10%) !default;\n$dropdown-link-hover-bg:            $gray-200 !default;\n\n$dropdown-link-active-color:        $component-active-color !default;\n$dropdown-link-active-bg:           $component-active-bg !default;\n\n$dropdown-link-disabled-color:      $gray-500 !default;\n\n$dropdown-item-padding-y:           $spacer * .25 !default;\n$dropdown-item-padding-x:           $spacer !default;\n\n$dropdown-header-color:             $gray-600 !default;\n$dropdown-header-padding-x:         $dropdown-item-padding-x !default;\n$dropdown-header-padding-y:         $dropdown-padding-y !default;\n// fusv-disable\n$dropdown-header-padding:           $dropdown-header-padding-y $dropdown-header-padding-x !default; // Deprecated in v5.2.0\n// fusv-enable\n// scss-docs-end dropdown-variables\n\n// scss-docs-start dropdown-dark-variables\n$dropdown-dark-color:               $gray-300 !default;\n$dropdown-dark-bg:                  $gray-800 !default;\n$dropdown-dark-border-color:        $dropdown-border-color !default;\n$dropdown-dark-divider-bg:          $dropdown-divider-bg !default;\n$dropdown-dark-box-shadow:          null !default;\n$dropdown-dark-link-color:          $dropdown-dark-color !default;\n$dropdown-dark-link-hover-color:    $white !default;\n$dropdown-dark-link-hover-bg:       rgba($white, .15) !default;\n$dropdown-dark-link-active-color:   $dropdown-link-active-color !default;\n$dropdown-dark-link-active-bg:      $dropdown-link-active-bg !default;\n$dropdown-dark-link-disabled-color: $gray-500 !default;\n$dropdown-dark-header-color:        $gray-500 !default;\n// scss-docs-end dropdown-dark-variables\n\n\n// Pagination\n\n// scss-docs-start pagination-variables\n$pagination-padding-y:              .375rem !default;\n$pagination-padding-x:              .75rem !default;\n$pagination-padding-y-sm:           .25rem !default;\n$pagination-padding-x-sm:           .5rem !default;\n$pagination-padding-y-lg:           .75rem !default;\n$pagination-padding-x-lg:           1.5rem !default;\n\n$pagination-font-size:              $font-size-base !default;\n\n$pagination-color:                  var(--#{$prefix}link-color) !default;\n$pagination-bg:                     $white !default;\n$pagination-border-radius:          $border-radius !default;\n$pagination-border-width:           $border-width !default;\n$pagination-margin-start:           ($pagination-border-width * -1) !default;\n$pagination-border-color:           $gray-300 !default;\n\n$pagination-focus-color:            var(--#{$prefix}link-hover-color) !default;\n$pagination-focus-bg:               $gray-200 !default;\n$pagination-focus-box-shadow:       $input-btn-focus-box-shadow !default;\n$pagination-focus-outline:          0 !default;\n\n$pagination-hover-color:            var(--#{$prefix}link-hover-color) !default;\n$pagination-hover-bg:               $gray-200 !default;\n$pagination-hover-border-color:     $gray-300 !default;\n\n$pagination-active-color:           $component-active-color !default;\n$pagination-active-bg:              $component-active-bg !default;\n$pagination-active-border-color:    $pagination-active-bg !default;\n\n$pagination-disabled-color:         $gray-600 !default;\n$pagination-disabled-bg:            $white !default;\n$pagination-disabled-border-color:  $gray-300 !default;\n\n$pagination-transition:              color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;\n\n$pagination-border-radius-sm:       $border-radius-sm !default;\n$pagination-border-radius-lg:       $border-radius-lg !default;\n// scss-docs-end pagination-variables\n\n\n// Placeholders\n\n// scss-docs-start placeholders\n$placeholder-opacity-max:           .5 !default;\n$placeholder-opacity-min:           .2 !default;\n// scss-docs-end placeholders\n\n// Cards\n\n// scss-docs-start card-variables\n$card-spacer-y:                     $spacer !default;\n$card-spacer-x:                     $spacer !default;\n$card-title-spacer-y:               $spacer * .5 !default;\n$card-border-width:                 $border-width !default;\n$card-border-color:                 var(--#{$prefix}border-color-translucent) !default;\n$card-border-radius:                $border-radius !default;\n$card-box-shadow:                   null !default;\n$card-inner-border-radius:          subtract($card-border-radius, $card-border-width) !default;\n$card-cap-padding-y:                $card-spacer-y * .5 !default;\n$card-cap-padding-x:                $card-spacer-x !default;\n$card-cap-bg:                       rgba($black, .03) !default;\n$card-cap-color:                    null !default;\n$card-height:                       null !default;\n$card-color:                        null !default;\n$card-bg:                           $white !default;\n$card-img-overlay-padding:          $spacer !default;\n$card-group-margin:                 $grid-gutter-width * .5 !default;\n// scss-docs-end card-variables\n\n// Accordion\n\n// scss-docs-start accordion-variables\n$accordion-padding-y:                     1rem !default;\n$accordion-padding-x:                     1.25rem !default;\n$accordion-color:                         $body-color !default; // Sass variable because of $accordion-button-icon\n$accordion-bg:                            $body-bg !default;\n$accordion-border-width:                  $border-width !default;\n$accordion-border-color:                  var(--#{$prefix}border-color) !default;\n$accordion-border-radius:                 $border-radius !default;\n$accordion-inner-border-radius:           subtract($accordion-border-radius, $accordion-border-width) !default;\n\n$accordion-body-padding-y:                $accordion-padding-y !default;\n$accordion-body-padding-x:                $accordion-padding-x !default;\n\n$accordion-button-padding-y:              $accordion-padding-y !default;\n$accordion-button-padding-x:              $accordion-padding-x !default;\n$accordion-button-color:                  $accordion-color !default;\n$accordion-button-bg:                     var(--#{$prefix}accordion-bg) !default;\n$accordion-transition:                    $btn-transition, border-radius .15s ease !default;\n$accordion-button-active-bg:              tint-color($component-active-bg, 90%) !default;\n$accordion-button-active-color:           shade-color($primary, 10%) !default;\n\n$accordion-button-focus-border-color:     $input-focus-border-color !default;\n$accordion-button-focus-box-shadow:       $btn-focus-box-shadow !default;\n\n$accordion-icon-width:                    1.25rem !default;\n$accordion-icon-color:                    $accordion-button-color !default;\n$accordion-icon-active-color:             $accordion-button-active-color !default;\n$accordion-icon-transition:               transform .2s ease-in-out !default;\n$accordion-icon-transform:                rotate(-180deg) !default;\n\n$accordion-button-icon:         url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>\") !default;\n$accordion-button-active-icon:  url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>\") !default;\n// scss-docs-end accordion-variables\n\n// Tooltips\n\n// scss-docs-start tooltip-variables\n$tooltip-font-size:                 $font-size-sm !default;\n$tooltip-max-width:                 200px !default;\n$tooltip-color:                     $white !default;\n$tooltip-bg:                        $black !default;\n$tooltip-border-radius:             $border-radius !default;\n$tooltip-opacity:                   .9 !default;\n$tooltip-padding-y:                 $spacer * .25 !default;\n$tooltip-padding-x:                 $spacer * .5 !default;\n$tooltip-margin:                    null !default; // TODO: remove this in v6\n\n$tooltip-arrow-width:               .8rem !default;\n$tooltip-arrow-height:              .4rem !default;\n// fusv-disable\n$tooltip-arrow-color:               null !default; // Deprecated in Bootstrap 5.2.0 for CSS variables\n// fusv-enable\n// scss-docs-end tooltip-variables\n\n// Form tooltips must come after regular tooltips\n// scss-docs-start tooltip-feedback-variables\n$form-feedback-tooltip-padding-y:     $tooltip-padding-y !default;\n$form-feedback-tooltip-padding-x:     $tooltip-padding-x !default;\n$form-feedback-tooltip-font-size:     $tooltip-font-size !default;\n$form-feedback-tooltip-line-height:   null !default;\n$form-feedback-tooltip-opacity:       $tooltip-opacity !default;\n$form-feedback-tooltip-border-radius: $tooltip-border-radius !default;\n// scss-docs-end tooltip-feedback-variables\n\n\n// Popovers\n\n// scss-docs-start popover-variables\n$popover-font-size:                 $font-size-sm !default;\n$popover-bg:                        $white !default;\n$popover-max-width:                 276px !default;\n$popover-border-width:              $border-width !default;\n$popover-border-color:              var(--#{$prefix}border-color-translucent) !default;\n$popover-border-radius:             $border-radius-lg !default;\n$popover-inner-border-radius:       subtract($popover-border-radius, $popover-border-width) !default;\n$popover-box-shadow:                $box-shadow !default;\n\n$popover-header-font-size:          $font-size-base !default;\n$popover-header-bg:                 shade-color($popover-bg, 6%) !default;\n$popover-header-color:              $headings-color !default;\n$popover-header-padding-y:          .5rem !default;\n$popover-header-padding-x:          $spacer !default;\n\n$popover-body-color:                $body-color !default;\n$popover-body-padding-y:            $spacer !default;\n$popover-body-padding-x:            $spacer !default;\n\n$popover-arrow-width:               1rem !default;\n$popover-arrow-height:              .5rem !default;\n// scss-docs-end popover-variables\n\n// fusv-disable\n// Deprecated in Bootstrap 5.2.0 for CSS variables\n$popover-arrow-color:               $popover-bg !default;\n$popover-arrow-outer-color:         var(--#{$prefix}border-color-translucent) !default;\n// fusv-enable\n\n\n// Toasts\n\n// scss-docs-start toast-variables\n$toast-max-width:                   350px !default;\n$toast-padding-x:                   .75rem !default;\n$toast-padding-y:                   .5rem !default;\n$toast-font-size:                   .875rem !default;\n$toast-color:                       null !default;\n$toast-background-color:            rgba($white, .85) !default;\n$toast-border-width:                $border-width !default;\n$toast-border-color:                var(--#{$prefix}border-color-translucent) !default;\n$toast-border-radius:               $border-radius !default;\n$toast-box-shadow:                  $box-shadow !default;\n$toast-spacing:                     $container-padding-x !default;\n\n$toast-header-color:                $gray-600 !default;\n$toast-header-background-color:     rgba($white, .85) !default;\n$toast-header-border-color:         rgba($black, .05) !default;\n// scss-docs-end toast-variables\n\n\n// Badges\n\n// scss-docs-start badge-variables\n$badge-font-size:                   .75em !default;\n$badge-font-weight:                 $font-weight-bold !default;\n$badge-color:                       $white !default;\n$badge-padding-y:                   .35em !default;\n$badge-padding-x:                   .65em !default;\n$badge-border-radius:               $border-radius !default;\n// scss-docs-end badge-variables\n\n\n// Modals\n\n// scss-docs-start modal-variables\n$modal-inner-padding:               $spacer !default;\n\n$modal-footer-margin-between:       .5rem !default;\n\n$modal-dialog-margin:               .5rem !default;\n$modal-dialog-margin-y-sm-up:       1.75rem !default;\n\n$modal-title-line-height:           $line-height-base !default;\n\n$modal-content-color:               null !default;\n$modal-content-bg:                  $white !default;\n$modal-content-border-color:        var(--#{$prefix}border-color-translucent) !default;\n$modal-content-border-width:        $border-width !default;\n$modal-content-border-radius:       $border-radius-lg !default;\n$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default;\n$modal-content-box-shadow-xs:       $box-shadow-sm !default;\n$modal-content-box-shadow-sm-up:    $box-shadow !default;\n\n$modal-backdrop-bg:                 $black !default;\n$modal-backdrop-opacity:            .5 !default;\n\n$modal-header-border-color:         var(--#{$prefix}border-color) !default;\n$modal-header-border-width:         $modal-content-border-width !default;\n$modal-header-padding-y:            $modal-inner-padding !default;\n$modal-header-padding-x:            $modal-inner-padding !default;\n$modal-header-padding:              $modal-header-padding-y $modal-header-padding-x !default; // Keep this for backwards compatibility\n\n$modal-footer-bg:                   null !default;\n$modal-footer-border-color:         $modal-header-border-color !default;\n$modal-footer-border-width:         $modal-header-border-width !default;\n\n$modal-sm:                          300px !default;\n$modal-md:                          500px !default;\n$modal-lg:                          800px !default;\n$modal-xl:                          1140px !default;\n\n$modal-fade-transform:              translate(0, -50px) !default;\n$modal-show-transform:              none !default;\n$modal-transition:                  transform .3s ease-out !default;\n$modal-scale-transform:             scale(1.02) !default;\n// scss-docs-end modal-variables\n\n\n// Alerts\n//\n// Define alert colors, border radius, and padding.\n\n// scss-docs-start alert-variables\n$alert-padding-y:               $spacer !default;\n$alert-padding-x:               $spacer !default;\n$alert-margin-bottom:           1rem !default;\n$alert-border-radius:           $border-radius !default;\n$alert-link-font-weight:        $font-weight-bold !default;\n$alert-border-width:            $border-width !default;\n$alert-bg-scale:                -80% !default;\n$alert-border-scale:            -70% !default;\n$alert-color-scale:             40% !default;\n$alert-dismissible-padding-r:   $alert-padding-x * 3 !default; // 3x covers width of x plus default padding on either side\n// scss-docs-end alert-variables\n\n\n// Progress bars\n\n// scss-docs-start progress-variables\n$progress-height:                   1rem !default;\n$progress-font-size:                $font-size-base * .75 !default;\n$progress-bg:                       $gray-200 !default;\n$progress-border-radius:            $border-radius !default;\n$progress-box-shadow:               $box-shadow-inset !default;\n$progress-bar-color:                $white !default;\n$progress-bar-bg:                   $primary !default;\n$progress-bar-animation-timing:     1s linear infinite !default;\n$progress-bar-transition:           width .6s ease !default;\n// scss-docs-end progress-variables\n\n\n// List group\n\n// scss-docs-start list-group-variables\n$list-group-color:                  $gray-900 !default;\n$list-group-bg:                     $white !default;\n$list-group-border-color:           rgba($black, .125) !default;\n$list-group-border-width:           $border-width !default;\n$list-group-border-radius:          $border-radius !default;\n\n$list-group-item-padding-y:         $spacer * .5 !default;\n$list-group-item-padding-x:         $spacer !default;\n$list-group-item-bg-scale:          -80% !default;\n$list-group-item-color-scale:       40% !default;\n\n$list-group-hover-bg:               $gray-100 !default;\n$list-group-active-color:           $component-active-color !default;\n$list-group-active-bg:              $component-active-bg !default;\n$list-group-active-border-color:    $list-group-active-bg !default;\n\n$list-group-disabled-color:         $gray-600 !default;\n$list-group-disabled-bg:            $list-group-bg !default;\n\n$list-group-action-color:           $gray-700 !default;\n$list-group-action-hover-color:     $list-group-action-color !default;\n\n$list-group-action-active-color:    $body-color !default;\n$list-group-action-active-bg:       $gray-200 !default;\n// scss-docs-end list-group-variables\n\n\n// Image thumbnails\n\n// scss-docs-start thumbnail-variables\n$thumbnail-padding:                 .25rem !default;\n$thumbnail-bg:                      $body-bg !default;\n$thumbnail-border-width:            $border-width !default;\n$thumbnail-border-color:            var(--#{$prefix}border-color) !default;\n$thumbnail-border-radius:           $border-radius !default;\n$thumbnail-box-shadow:              $box-shadow-sm !default;\n// scss-docs-end thumbnail-variables\n\n\n// Figures\n\n// scss-docs-start figure-variables\n$figure-caption-font-size:          $small-font-size !default;\n$figure-caption-color:              $gray-600 !default;\n// scss-docs-end figure-variables\n\n\n// Breadcrumbs\n\n// scss-docs-start breadcrumb-variables\n$breadcrumb-font-size:              null !default;\n$breadcrumb-padding-y:              0 !default;\n$breadcrumb-padding-x:              0 !default;\n$breadcrumb-item-padding-x:         .5rem !default;\n$breadcrumb-margin-bottom:          1rem !default;\n$breadcrumb-bg:                     null !default;\n$breadcrumb-divider-color:          $gray-600 !default;\n$breadcrumb-active-color:           $gray-600 !default;\n$breadcrumb-divider:                quote(\"/\") !default;\n$breadcrumb-divider-flipped:        $breadcrumb-divider !default;\n$breadcrumb-border-radius:          null !default;\n// scss-docs-end breadcrumb-variables\n\n// Carousel\n\n// scss-docs-start carousel-variables\n$carousel-control-color:             $white !default;\n$carousel-control-width:             15% !default;\n$carousel-control-opacity:           .5 !default;\n$carousel-control-hover-opacity:     .9 !default;\n$carousel-control-transition:        opacity .15s ease !default;\n\n$carousel-indicator-width:           30px !default;\n$carousel-indicator-height:          3px !default;\n$carousel-indicator-hit-area-height: 10px !default;\n$carousel-indicator-spacer:          3px !default;\n$carousel-indicator-opacity:         .5 !default;\n$carousel-indicator-active-bg:       $white !default;\n$carousel-indicator-active-opacity:  1 !default;\n$carousel-indicator-transition:      opacity .6s ease !default;\n\n$carousel-caption-width:             70% !default;\n$carousel-caption-color:             $white !default;\n$carousel-caption-padding-y:         1.25rem !default;\n$carousel-caption-spacer:            1.25rem !default;\n\n$carousel-control-icon-width:        2rem !default;\n\n$carousel-control-prev-icon-bg:      url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/></svg>\") !default;\n$carousel-control-next-icon-bg:      url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/></svg>\") !default;\n\n$carousel-transition-duration:       .6s !default;\n$carousel-transition:                transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`)\n// scss-docs-end carousel-variables\n\n// scss-docs-start carousel-dark-variables\n$carousel-dark-indicator-active-bg:  $black !default;\n$carousel-dark-caption-color:        $black !default;\n$carousel-dark-control-icon-filter:  invert(1) grayscale(100) !default;\n// scss-docs-end carousel-dark-variables\n\n\n// Spinners\n\n// scss-docs-start spinner-variables\n$spinner-width:           2rem !default;\n$spinner-height:          $spinner-width !default;\n$spinner-vertical-align:  -.125em !default;\n$spinner-border-width:    .25em !default;\n$spinner-animation-speed: .75s !default;\n\n$spinner-width-sm:        1rem !default;\n$spinner-height-sm:       $spinner-width-sm !default;\n$spinner-border-width-sm: .2em !default;\n// scss-docs-end spinner-variables\n\n\n// Close\n\n// scss-docs-start close-variables\n$btn-close-width:            1em !default;\n$btn-close-height:           $btn-close-width !default;\n$btn-close-padding-x:        .25em !default;\n$btn-close-padding-y:        $btn-close-padding-x !default;\n$btn-close-color:            $black !default;\n$btn-close-bg:               url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/></svg>\") !default;\n$btn-close-focus-shadow:     $input-btn-focus-box-shadow !default;\n$btn-close-opacity:          .5 !default;\n$btn-close-hover-opacity:    .75 !default;\n$btn-close-focus-opacity:    1 !default;\n$btn-close-disabled-opacity: .25 !default;\n$btn-close-white-filter:     invert(1) grayscale(100%) brightness(200%) !default;\n// scss-docs-end close-variables\n\n\n// Offcanvas\n\n// scss-docs-start offcanvas-variables\n$offcanvas-padding-y:               $modal-inner-padding !default;\n$offcanvas-padding-x:               $modal-inner-padding !default;\n$offcanvas-horizontal-width:        400px !default;\n$offcanvas-vertical-height:         30vh !default;\n$offcanvas-transition-duration:     .3s !default;\n$offcanvas-border-color:            $modal-content-border-color !default;\n$offcanvas-border-width:            $modal-content-border-width !default;\n$offcanvas-title-line-height:       $modal-title-line-height !default;\n$offcanvas-bg-color:                $modal-content-bg !default;\n$offcanvas-color:                   $modal-content-color !default;\n$offcanvas-box-shadow:              $modal-content-box-shadow-xs !default;\n$offcanvas-backdrop-bg:             $modal-backdrop-bg !default;\n$offcanvas-backdrop-opacity:        $modal-backdrop-opacity !default;\n// scss-docs-end offcanvas-variables\n\n// Code\n\n$code-font-size:                    $small-font-size !default;\n$code-color:                        $pink !default;\n\n$kbd-padding-y:                     .1875rem !default;\n$kbd-padding-x:                     .375rem !default;\n$kbd-font-size:                     $code-font-size !default;\n$kbd-color:                         var(--#{$prefix}body-bg) !default;\n$kbd-bg:                            var(--#{$prefix}body-color) !default;\n$nested-kbd-font-weight:            null !default; // Deprecated in v5.2.0, removing in v6\n\n$pre-color:                         null !default;\n"
  },
  {
    "path": "src/common/bootstrap/scss/bootstrap-grid.scss",
    "content": "@import \"mixins/banner\";\n@include bsBanner(Grid);\n\n$include-column-box-sizing: true !default;\n\n@import \"functions\";\n@import \"variables\";\n@import \"maps\";\n\n@import \"mixins/lists\";\n@import \"mixins/breakpoints\";\n@import \"mixins/container\";\n@import \"mixins/grid\";\n@import \"mixins/utilities\";\n\n@import \"vendor/rfs\";\n\n@import \"root\";\n\n@import \"containers\";\n@import \"grid\";\n\n@import \"utilities\";\n// Only use the utilities we need\n// stylelint-disable-next-line scss/dollar-variable-default\n$utilities: map-get-multiple(\n  $utilities,\n  (\n    \"display\",\n    \"order\",\n    \"flex\",\n    \"flex-direction\",\n    \"flex-grow\",\n    \"flex-shrink\",\n    \"flex-wrap\",\n    \"justify-content\",\n    \"align-items\",\n    \"align-content\",\n    \"align-self\",\n    \"margin\",\n    \"margin-x\",\n    \"margin-y\",\n    \"margin-top\",\n    \"margin-end\",\n    \"margin-bottom\",\n    \"margin-start\",\n    \"negative-margin\",\n    \"negative-margin-x\",\n    \"negative-margin-y\",\n    \"negative-margin-top\",\n    \"negative-margin-end\",\n    \"negative-margin-bottom\",\n    \"negative-margin-start\",\n    \"padding\",\n    \"padding-x\",\n    \"padding-y\",\n    \"padding-top\",\n    \"padding-end\",\n    \"padding-bottom\",\n    \"padding-start\",\n  )\n);\n\n@import \"utilities/api\";\n"
  },
  {
    "path": "src/common/bootstrap/scss/bootstrap-reboot.scss",
    "content": "@import \"mixins/banner\";\n@include bsBanner(Reboot);\n\n@import \"functions\";\n@import \"variables\";\n@import \"maps\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n"
  },
  {
    "path": "src/common/bootstrap/scss/bootstrap-utilities.scss",
    "content": "@import \"mixins/banner\";\n@include bsBanner(Utilities);\n\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"maps\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n"
  },
  {
    "path": "src/common/bootstrap/scss/bootstrap.scss",
    "content": "@import \"mixins/banner\";\n@include bsBanner(\"\");\n\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"maps\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n"
  },
  {
    "path": "src/common/bootstrap/scss/bootstrap5-css.asd",
    "content": "(defpackage :bootstrap5-css-assets\n  (:use :cl :asdf))\n(in-package :bootstrap5-css-assets)\n\n(defsystem :bootstrap5-css\n  :class \"build-utils:css-library\"\n  :defsystem-depends-on (:build-utils)\n  :import-path #P\"bootstrap/scss/\"\n  ;; (build-utils/css-package::get-css-component \"/home/arnold/builds/web/src/common/bootstrap-5.0.0-beta2/scss/\")\n  :components ((\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_progress\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_offcanvas\")               \n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_nav\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_images\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_card\")\n               (:MODULE\n                \"mixins\"\n                :COMPONENTS\n                ((\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_caret\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_border-radius\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_color-scheme\")                 \n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_table-variants\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_banner\")                 \n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_visually-hidden\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_reset-text\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_backdrop\")                 \n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_clearfix\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_resize\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_pagination\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_list-group\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_image\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_lists\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_buttons\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_transition\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_gradients\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_grid\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_breakpoints\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_alert\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_utilities\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_text-truncate\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_deprecate\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_forms\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_box-shadow\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_container\")))\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"bootstrap-utilities\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_reboot\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"bootstrap-reboot\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"bootstrap\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_navbar\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_maps\")               \n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_placeholders\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_pagination\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_accordion\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_list-group\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_containers\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_variables\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_type\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_mixins\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_popover\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_buttons\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_modal\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_transitions\")\n               (:MODULE\n                \"utilities\"\n                :COMPONENTS\n                ((\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_api\")))\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_carousel\")\n               (:MODULE\n                \"forms\"\n                :COMPONENTS\n                ((\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_form-select\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_form-check\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_form-text\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_form-control\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_input-group\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_form-range\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_validation\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_labels\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_floating-labels\")))\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_tables\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_dropdown\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_grid\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_toasts\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_close\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_functions\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_spinners\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_tooltip\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_button-group\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_alert\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_breadcrumb\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_utilities\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_helpers\")\n               (:MODULE\n                \"helpers\"\n                :COMPONENTS\n                ((\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_visually-hidden\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_stretched-link\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_vr\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_position\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_color-bg\")                 \n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_stacks\")                 \n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_clearfix\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_colored-links\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_text-truncation\")\n                 (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_ratio\")))\n               (:MODULE\n                \"vendor\"\n                :COMPONENTS\n                ((\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_rfs\")))\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_badge\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"bootstrap-grid\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_root\")\n               (\"BUILD-UTILS/CSS-PACKAGE:SCSS-FILE\" \"_forms\")))\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_floating-labels.scss",
    "content": ".form-floating {\n  position: relative;\n\n  > .form-control,\n  > .form-control-plaintext,\n  > .form-select {\n    height: $form-floating-height;\n    line-height: $form-floating-line-height;\n  }\n\n  > label {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%; // allow textareas\n    padding: $form-floating-padding-y $form-floating-padding-x;\n    overflow: hidden;\n    text-align: start;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    pointer-events: none;\n    border: $input-border-width solid transparent; // Required for aligning label's text with the input as it affects inner box model\n    transform-origin: 0 0;\n    @include transition($form-floating-transition);\n  }\n\n  > .form-control,\n  > .form-control-plaintext {\n    padding: $form-floating-padding-y $form-floating-padding-x;\n\n    &::placeholder {\n      color: transparent;\n    }\n\n    &:focus,\n    &:not(:placeholder-shown) {\n      padding-top: $form-floating-input-padding-t;\n      padding-bottom: $form-floating-input-padding-b;\n    }\n    // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped\n    &:-webkit-autofill {\n      padding-top: $form-floating-input-padding-t;\n      padding-bottom: $form-floating-input-padding-b;\n    }\n  }\n\n  > .form-select {\n    padding-top: $form-floating-input-padding-t;\n    padding-bottom: $form-floating-input-padding-b;\n  }\n\n  > .form-control:focus,\n  > .form-control:not(:placeholder-shown),\n  > .form-control-plaintext,\n  > .form-select {\n    ~ label {\n      opacity: $form-floating-label-opacity;\n      transform: $form-floating-label-transform;\n    }\n  }\n  // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped\n  > .form-control:-webkit-autofill {\n    ~ label {\n      opacity: $form-floating-label-opacity;\n      transform: $form-floating-label-transform;\n    }\n  }\n\n  > .form-control-plaintext {\n    ~ label {\n      border-width: $input-border-width 0; // Required to properly position label text - as explained above\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_form-check.scss",
    "content": "//\n// Check/radio\n//\n\n.form-check {\n  display: block;\n  min-height: $form-check-min-height;\n  padding-left: $form-check-padding-start;\n  margin-bottom: $form-check-margin-bottom;\n\n  .form-check-input {\n    float: left;\n    margin-left: $form-check-padding-start * -1;\n  }\n}\n\n.form-check-reverse {\n  padding-right: $form-check-padding-start;\n  padding-left: 0;\n  text-align: right;\n\n  .form-check-input {\n    float: right;\n    margin-right: $form-check-padding-start * -1;\n    margin-left: 0;\n  }\n}\n\n.form-check-input {\n  width: $form-check-input-width;\n  height: $form-check-input-width;\n  margin-top: ($line-height-base - $form-check-input-width) * .5; // line-height minus check height\n  vertical-align: top;\n  background-color: $form-check-input-bg;\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: contain;\n  border: $form-check-input-border;\n  appearance: none;\n  print-color-adjust: exact; // Keep themed appearance for print\n  @include transition($form-check-transition);\n\n  &[type=\"checkbox\"] {\n    @include border-radius($form-check-input-border-radius);\n  }\n\n  &[type=\"radio\"] {\n    // stylelint-disable-next-line property-disallowed-list\n    border-radius: $form-check-radio-border-radius;\n  }\n\n  &:active {\n    filter: $form-check-input-active-filter;\n  }\n\n  &:focus {\n    border-color: $form-check-input-focus-border;\n    outline: 0;\n    box-shadow: $form-check-input-focus-box-shadow;\n  }\n\n  &:checked {\n    background-color: $form-check-input-checked-bg-color;\n    border-color: $form-check-input-checked-border-color;\n\n    &[type=\"checkbox\"] {\n      @if $enable-gradients {\n        background-image: escape-svg($form-check-input-checked-bg-image), var(--#{$prefix}gradient);\n      } @else {\n        background-image: escape-svg($form-check-input-checked-bg-image);\n      }\n    }\n\n    &[type=\"radio\"] {\n      @if $enable-gradients {\n        background-image: escape-svg($form-check-radio-checked-bg-image), var(--#{$prefix}gradient);\n      } @else {\n        background-image: escape-svg($form-check-radio-checked-bg-image);\n      }\n    }\n  }\n\n  &[type=\"checkbox\"]:indeterminate {\n    background-color: $form-check-input-indeterminate-bg-color;\n    border-color: $form-check-input-indeterminate-border-color;\n\n    @if $enable-gradients {\n      background-image: escape-svg($form-check-input-indeterminate-bg-image), var(--#{$prefix}gradient);\n    } @else {\n      background-image: escape-svg($form-check-input-indeterminate-bg-image);\n    }\n  }\n\n  &:disabled {\n    pointer-events: none;\n    filter: none;\n    opacity: $form-check-input-disabled-opacity;\n  }\n\n  // Use disabled attribute in addition of :disabled pseudo-class\n  // See: https://github.com/twbs/bootstrap/issues/28247\n  &[disabled],\n  &:disabled {\n    ~ .form-check-label {\n      cursor: default;\n      opacity: $form-check-label-disabled-opacity;\n    }\n  }\n}\n\n.form-check-label {\n  color: $form-check-label-color;\n  cursor: $form-check-label-cursor;\n}\n\n//\n// Switch\n//\n\n.form-switch {\n  padding-left: $form-switch-padding-start;\n\n  .form-check-input {\n    width: $form-switch-width;\n    margin-left: $form-switch-padding-start * -1;\n    background-image: escape-svg($form-switch-bg-image);\n    background-position: left center;\n    @include border-radius($form-switch-border-radius);\n    @include transition($form-switch-transition);\n\n    &:focus {\n      background-image: escape-svg($form-switch-focus-bg-image);\n    }\n\n    &:checked {\n      background-position: $form-switch-checked-bg-position;\n\n      @if $enable-gradients {\n        background-image: escape-svg($form-switch-checked-bg-image), var(--#{$prefix}gradient);\n      } @else {\n        background-image: escape-svg($form-switch-checked-bg-image);\n      }\n    }\n  }\n\n  &.form-check-reverse {\n    padding-right: $form-switch-padding-start;\n    padding-left: 0;\n\n    .form-check-input {\n      margin-right: $form-switch-padding-start * -1;\n      margin-left: 0;\n    }\n  }\n}\n\n.form-check-inline {\n  display: inline-block;\n  margin-right: $form-check-inline-margin-end;\n}\n\n.btn-check {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n\n  &[disabled],\n  &:disabled {\n    + .btn {\n      pointer-events: none;\n      filter: none;\n      opacity: $form-check-btn-check-disabled-opacity;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_form-control.scss",
    "content": "//\n// General form controls (plus a few specific high-level interventions)\n//\n\n.form-control {\n  display: block;\n  width: 100%;\n  padding: $input-padding-y $input-padding-x;\n  font-family: $input-font-family;\n  @include font-size($input-font-size);\n  font-weight: $input-font-weight;\n  line-height: $input-line-height;\n  color: $input-color;\n  background-color: $input-bg;\n  background-clip: padding-box;\n  border: $input-border-width solid $input-border-color;\n  appearance: none; // Fix appearance for date inputs in Safari\n\n  // Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.\n  @include border-radius($input-border-radius, 0);\n\n  @include box-shadow($input-box-shadow);\n  @include transition($input-transition);\n\n  &[type=\"file\"] {\n    overflow: hidden; // prevent pseudo element button overlap\n\n    &:not(:disabled):not([readonly]) {\n      cursor: pointer;\n    }\n  }\n\n  // Customize the `:focus` state to imitate native WebKit styles.\n  &:focus {\n    color: $input-focus-color;\n    background-color: $input-focus-bg;\n    border-color: $input-focus-border-color;\n    outline: 0;\n    @if $enable-shadows {\n      @include box-shadow($input-box-shadow, $input-focus-box-shadow);\n    } @else {\n      // Avoid using mixin so we can pass custom focus shadow properly\n      box-shadow: $input-focus-box-shadow;\n    }\n  }\n\n  // Add some height to date inputs on iOS\n  // https://github.com/twbs/bootstrap/issues/23307\n  // TODO: we can remove this workaround once https://bugs.webkit.org/show_bug.cgi?id=198959 is resolved\n  &::-webkit-date-and-time-value {\n    // Multiply line-height by 1em if it has no unit\n    height: if(unit($input-line-height) == \"\", $input-line-height * 1em, $input-line-height);\n  }\n\n  // Placeholder\n  &::placeholder {\n    color: $input-placeholder-color;\n    // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.\n    opacity: 1;\n  }\n\n  // Disabled inputs\n  //\n  // HTML5 says that controls under a fieldset > legend:first-child won't be\n  // disabled if the fieldset is disabled. Due to implementation difficulty, we\n  // don't honor that edge case; we style them as disabled anyway.\n  &:disabled {\n    color: $input-disabled-color;\n    background-color: $input-disabled-bg;\n    border-color: $input-disabled-border-color;\n    // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.\n    opacity: 1;\n  }\n\n  // File input buttons theming\n  &::file-selector-button {\n    padding: $input-padding-y $input-padding-x;\n    margin: (-$input-padding-y) (-$input-padding-x);\n    margin-inline-end: $input-padding-x;\n    color: $form-file-button-color;\n    @include gradient-bg($form-file-button-bg);\n    pointer-events: none;\n    border-color: inherit;\n    border-style: solid;\n    border-width: 0;\n    border-inline-end-width: $input-border-width;\n    border-radius: 0; // stylelint-disable-line property-disallowed-list\n    @include transition($btn-transition);\n  }\n\n  &:hover:not(:disabled):not([readonly])::file-selector-button {\n    background-color: $form-file-button-hover-bg;\n  }\n}\n\n// Readonly controls as plain text\n//\n// Apply class to a readonly input to make it appear like regular plain\n// text (without any border, background color, focus indicator)\n\n.form-control-plaintext {\n  display: block;\n  width: 100%;\n  padding: $input-padding-y 0;\n  margin-bottom: 0; // match inputs if this class comes on inputs with default margins\n  line-height: $input-line-height;\n  color: $input-plaintext-color;\n  background-color: transparent;\n  border: solid transparent;\n  border-width: $input-border-width 0;\n\n  &:focus {\n    outline: 0;\n  }\n\n  &.form-control-sm,\n  &.form-control-lg {\n    padding-right: 0;\n    padding-left: 0;\n  }\n}\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// Repeated in `_input_group.scss` to avoid Sass extend issues.\n\n.form-control-sm {\n  min-height: $input-height-sm;\n  padding: $input-padding-y-sm $input-padding-x-sm;\n  @include font-size($input-font-size-sm);\n  @include border-radius($input-border-radius-sm);\n\n  &::file-selector-button {\n    padding: $input-padding-y-sm $input-padding-x-sm;\n    margin: (-$input-padding-y-sm) (-$input-padding-x-sm);\n    margin-inline-end: $input-padding-x-sm;\n  }\n}\n\n.form-control-lg {\n  min-height: $input-height-lg;\n  padding: $input-padding-y-lg $input-padding-x-lg;\n  @include font-size($input-font-size-lg);\n  @include border-radius($input-border-radius-lg);\n\n  &::file-selector-button {\n    padding: $input-padding-y-lg $input-padding-x-lg;\n    margin: (-$input-padding-y-lg) (-$input-padding-x-lg);\n    margin-inline-end: $input-padding-x-lg;\n  }\n}\n\n// Make sure textareas don't shrink too much when resized\n// https://github.com/twbs/bootstrap/pull/29124\n// stylelint-disable selector-no-qualifying-type\ntextarea {\n  &.form-control {\n    min-height: $input-height;\n  }\n\n  &.form-control-sm {\n    min-height: $input-height-sm;\n  }\n\n  &.form-control-lg {\n    min-height: $input-height-lg;\n  }\n}\n// stylelint-enable selector-no-qualifying-type\n\n.form-control-color {\n  width: $form-color-width;\n  height: $input-height;\n  padding: $input-padding-y;\n\n  &:not(:disabled):not([readonly]) {\n    cursor: pointer;\n  }\n\n  &::-moz-color-swatch {\n    border: 0 !important; // stylelint-disable-line declaration-no-important\n    @include border-radius($input-border-radius);\n  }\n\n  &::-webkit-color-swatch {\n    @include border-radius($input-border-radius);\n  }\n\n  &.form-control-sm { height: $input-height-sm; }\n  &.form-control-lg { height: $input-height-lg; }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_form-range.scss",
    "content": "// Range\n//\n// Style range inputs the same across browsers. Vendor-specific rules for pseudo\n// elements cannot be mixed. As such, there are no shared styles for focus or\n// active states on prefixed selectors.\n\n.form-range {\n  width: 100%;\n  height: add($form-range-thumb-height, $form-range-thumb-focus-box-shadow-width * 2);\n  padding: 0; // Need to reset padding\n  background-color: transparent;\n  appearance: none;\n\n  &:focus {\n    outline: 0;\n\n    // Pseudo-elements must be split across multiple rulesets to have an effect.\n    // No box-shadow() mixin for focus accessibility.\n    &::-webkit-slider-thumb { box-shadow: $form-range-thumb-focus-box-shadow; }\n    &::-moz-range-thumb     { box-shadow: $form-range-thumb-focus-box-shadow; }\n  }\n\n  &::-moz-focus-outer {\n    border: 0;\n  }\n\n  &::-webkit-slider-thumb {\n    width: $form-range-thumb-width;\n    height: $form-range-thumb-height;\n    margin-top: ($form-range-track-height - $form-range-thumb-height) * .5; // Webkit specific\n    @include gradient-bg($form-range-thumb-bg);\n    border: $form-range-thumb-border;\n    @include border-radius($form-range-thumb-border-radius);\n    @include box-shadow($form-range-thumb-box-shadow);\n    @include transition($form-range-thumb-transition);\n    appearance: none;\n\n    &:active {\n      @include gradient-bg($form-range-thumb-active-bg);\n    }\n  }\n\n  &::-webkit-slider-runnable-track {\n    width: $form-range-track-width;\n    height: $form-range-track-height;\n    color: transparent; // Why?\n    cursor: $form-range-track-cursor;\n    background-color: $form-range-track-bg;\n    border-color: transparent;\n    @include border-radius($form-range-track-border-radius);\n    @include box-shadow($form-range-track-box-shadow);\n  }\n\n  &::-moz-range-thumb {\n    width: $form-range-thumb-width;\n    height: $form-range-thumb-height;\n    @include gradient-bg($form-range-thumb-bg);\n    border: $form-range-thumb-border;\n    @include border-radius($form-range-thumb-border-radius);\n    @include box-shadow($form-range-thumb-box-shadow);\n    @include transition($form-range-thumb-transition);\n    appearance: none;\n\n    &:active {\n      @include gradient-bg($form-range-thumb-active-bg);\n    }\n  }\n\n  &::-moz-range-track {\n    width: $form-range-track-width;\n    height: $form-range-track-height;\n    color: transparent;\n    cursor: $form-range-track-cursor;\n    background-color: $form-range-track-bg;\n    border-color: transparent; // Firefox specific?\n    @include border-radius($form-range-track-border-radius);\n    @include box-shadow($form-range-track-box-shadow);\n  }\n\n  &:disabled {\n    pointer-events: none;\n\n    &::-webkit-slider-thumb {\n      background-color: $form-range-thumb-disabled-bg;\n    }\n\n    &::-moz-range-thumb {\n      background-color: $form-range-thumb-disabled-bg;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_form-select.scss",
    "content": "// Select\n//\n// Replaces the browser default select with a custom one, mostly pulled from\n// https://primer.github.io/.\n\n.form-select {\n  display: block;\n  width: 100%;\n  padding: $form-select-padding-y $form-select-indicator-padding $form-select-padding-y $form-select-padding-x;\n  -moz-padding-start: subtract($form-select-padding-x, 3px); // See https://github.com/twbs/bootstrap/issues/32636\n  font-family: $form-select-font-family;\n  @include font-size($form-select-font-size);\n  font-weight: $form-select-font-weight;\n  line-height: $form-select-line-height;\n  color: $form-select-color;\n  background-color: $form-select-bg;\n  background-image: escape-svg($form-select-indicator);\n  background-repeat: no-repeat;\n  background-position: $form-select-bg-position;\n  background-size: $form-select-bg-size;\n  border: $form-select-border-width solid $form-select-border-color;\n  @include border-radius($form-select-border-radius, 0);\n  @include box-shadow($form-select-box-shadow);\n  @include transition($form-select-transition);\n  appearance: none;\n\n  &:focus {\n    border-color: $form-select-focus-border-color;\n    outline: 0;\n    @if $enable-shadows {\n      @include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow);\n    } @else {\n      // Avoid using mixin so we can pass custom focus shadow properly\n      box-shadow: $form-select-focus-box-shadow;\n    }\n  }\n\n  &[multiple],\n  &[size]:not([size=\"1\"]) {\n    padding-right: $form-select-padding-x;\n    background-image: none;\n  }\n\n  &:disabled {\n    color: $form-select-disabled-color;\n    background-color: $form-select-disabled-bg;\n    border-color: $form-select-disabled-border-color;\n  }\n\n  // Remove outline from select box in FF\n  &:-moz-focusring {\n    color: transparent;\n    text-shadow: 0 0 0 $form-select-color;\n  }\n}\n\n.form-select-sm {\n  padding-top: $form-select-padding-y-sm;\n  padding-bottom: $form-select-padding-y-sm;\n  padding-left: $form-select-padding-x-sm;\n  @include font-size($form-select-font-size-sm);\n  @include border-radius($form-select-border-radius-sm);\n}\n\n.form-select-lg {\n  padding-top: $form-select-padding-y-lg;\n  padding-bottom: $form-select-padding-y-lg;\n  padding-left: $form-select-padding-x-lg;\n  @include font-size($form-select-font-size-lg);\n  @include border-radius($form-select-border-radius-lg);\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_form-text.scss",
    "content": "//\n// Form text\n//\n\n.form-text {\n  margin-top: $form-text-margin-top;\n  @include font-size($form-text-font-size);\n  font-style: $form-text-font-style;\n  font-weight: $form-text-font-weight;\n  color: $form-text-color;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_input-group.scss",
    "content": "//\n// Base styles\n//\n\n.input-group {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap; // For form validation feedback\n  align-items: stretch;\n  width: 100%;\n\n  > .form-control,\n  > .form-select,\n  > .form-floating {\n    position: relative; // For focus state's z-index\n    flex: 1 1 auto;\n    width: 1%;\n    min-width: 0; // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size\n  }\n\n  // Bring the \"active\" form control to the top of surrounding elements\n  > .form-control:focus,\n  > .form-select:focus,\n  > .form-floating:focus-within {\n    z-index: 5;\n  }\n\n  // Ensure buttons are always above inputs for more visually pleasing borders.\n  // This isn't needed for `.input-group-text` since it shares the same border-color\n  // as our inputs.\n  .btn {\n    position: relative;\n    z-index: 2;\n\n    &:focus {\n      z-index: 5;\n    }\n  }\n}\n\n\n// Textual addons\n//\n// Serves as a catch-all element for any text or radio/checkbox input you wish\n// to prepend or append to an input.\n\n.input-group-text {\n  display: flex;\n  align-items: center;\n  padding: $input-group-addon-padding-y $input-group-addon-padding-x;\n  @include font-size($input-font-size); // Match inputs\n  font-weight: $input-group-addon-font-weight;\n  line-height: $input-line-height;\n  color: $input-group-addon-color;\n  text-align: center;\n  white-space: nowrap;\n  background-color: $input-group-addon-bg;\n  border: $input-border-width solid $input-group-addon-border-color;\n  @include border-radius($input-border-radius);\n}\n\n\n// Sizing\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .form-select,\n.input-group-lg > .input-group-text,\n.input-group-lg > .btn {\n  padding: $input-padding-y-lg $input-padding-x-lg;\n  @include font-size($input-font-size-lg);\n  @include border-radius($input-border-radius-lg);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .form-select,\n.input-group-sm > .input-group-text,\n.input-group-sm > .btn {\n  padding: $input-padding-y-sm $input-padding-x-sm;\n  @include font-size($input-font-size-sm);\n  @include border-radius($input-border-radius-sm);\n}\n\n.input-group-lg > .form-select,\n.input-group-sm > .form-select {\n  padding-right: $form-select-padding-x + $form-select-indicator-padding;\n}\n\n\n// Rounded corners\n//\n// These rulesets must come after the sizing ones to properly override sm and lg\n// border-radius values when extending. They're more specific than we'd like\n// with the `.input-group >` part, but without it, we cannot override the sizing.\n\n// stylelint-disable-next-line no-duplicate-selectors\n.input-group {\n  &:not(.has-validation) {\n    > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n    > .dropdown-toggle:nth-last-child(n + 3),\n    > .form-floating:not(:last-child) > .form-control,\n    > .form-floating:not(:last-child) > .form-select {\n      @include border-end-radius(0);\n    }\n  }\n\n  &.has-validation {\n    > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),\n    > .dropdown-toggle:nth-last-child(n + 4),\n    > .form-floating:nth-last-child(n + 3) > .form-control,\n    > .form-floating:nth-last-child(n + 3) > .form-select {\n      @include border-end-radius(0);\n    }\n  }\n\n  $validation-messages: \"\";\n  @each $state in map-keys($form-validation-states) {\n    $validation-messages: $validation-messages + \":not(.\" + unquote($state) + \"-tooltip)\" + \":not(.\" + unquote($state) + \"-feedback)\";\n  }\n\n  > :not(:first-child):not(.dropdown-menu)#{$validation-messages} {\n    margin-left: -$input-border-width;\n    @include border-start-radius(0);\n  }\n\n  > .form-floating:not(:first-child) > .form-control,\n  > .form-floating:not(:first-child) > .form-select {\n    @include border-start-radius(0);\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_labels.scss",
    "content": "//\n// Labels\n//\n\n.form-label {\n  margin-bottom: $form-label-margin-bottom;\n  @include font-size($form-label-font-size);\n  font-style: $form-label-font-style;\n  font-weight: $form-label-font-weight;\n  color: $form-label-color;\n}\n\n// For use with horizontal and inline forms, when you need the label (or legend)\n// text to align with the form controls.\n.col-form-label {\n  padding-top: add($input-padding-y, $input-border-width);\n  padding-bottom: add($input-padding-y, $input-border-width);\n  margin-bottom: 0; // Override the `<legend>` default\n  @include font-size(inherit); // Override the `<legend>` default\n  font-style: $form-label-font-style;\n  font-weight: $form-label-font-weight;\n  line-height: $input-line-height;\n  color: $form-label-color;\n}\n\n.col-form-label-lg {\n  padding-top: add($input-padding-y-lg, $input-border-width);\n  padding-bottom: add($input-padding-y-lg, $input-border-width);\n  @include font-size($input-font-size-lg);\n}\n\n.col-form-label-sm {\n  padding-top: add($input-padding-y-sm, $input-border-width);\n  padding-bottom: add($input-padding-y-sm, $input-border-width);\n  @include font-size($input-font-size-sm);\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/forms/_validation.scss",
    "content": "// Form validation\n//\n// Provide feedback to users when form field values are valid or invalid. Works\n// primarily for client-side validation via scoped `:invalid` and `:valid`\n// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for\n// server-side validation.\n\n// scss-docs-start form-validation-states-loop\n@each $state, $data in $form-validation-states {\n  @include form-validation-state($state, $data...);\n}\n// scss-docs-end form-validation-states-loop\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_clearfix.scss",
    "content": ".clearfix {\n  @include clearfix();\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_color-bg.scss",
    "content": "// stylelint-disable function-name-case\n\n// All-caps `RGBA()` function used because of this Sass bug: https://github.com/sass/node-sass/issues/2251\n@each $color, $value in $theme-colors {\n  $color-rgb: to-rgb($value);\n  .text-bg-#{$color} {\n    color: color-contrast($value) if($enable-important-utilities, !important, null);\n    background-color: RGBA($color-rgb, var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null);\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_colored-links.scss",
    "content": "@each $color, $value in $theme-colors {\n  .link-#{$color} {\n    color: $value !important; // stylelint-disable-line declaration-no-important\n\n    @if $link-shade-percentage != 0 {\n      &:hover,\n      &:focus {\n        color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage)) !important; // stylelint-disable-line declaration-no-important\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_position.scss",
    "content": "// Shorthand\n\n.fixed-top {\n  position: fixed;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: $zindex-fixed;\n}\n\n.fixed-bottom {\n  position: fixed;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: $zindex-fixed;\n}\n\n// Responsive sticky top and bottom\n@each $breakpoint in map-keys($grid-breakpoints) {\n  @include media-breakpoint-up($breakpoint) {\n    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n    .sticky#{$infix}-top {\n      position: sticky;\n      top: 0;\n      z-index: $zindex-sticky;\n    }\n\n    .sticky#{$infix}-bottom {\n      position: sticky;\n      bottom: 0;\n      z-index: $zindex-sticky;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_ratio.scss",
    "content": "// Credit: Nicolas Gallagher and SUIT CSS.\n\n.ratio {\n  position: relative;\n  width: 100%;\n\n  &::before {\n    display: block;\n    padding-top: var(--#{$prefix}aspect-ratio);\n    content: \"\";\n  }\n\n  > * {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n  }\n}\n\n@each $key, $ratio in $aspect-ratios {\n  .ratio-#{$key} {\n    --#{$prefix}aspect-ratio: #{$ratio};\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_stacks.scss",
    "content": "// scss-docs-start stacks\n.hstack {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  align-self: stretch;\n}\n\n.vstack {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  align-self: stretch;\n}\n// scss-docs-end stacks\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_stretched-link.scss",
    "content": "//\n// Stretched link\n//\n\n.stretched-link {\n  &::#{$stretched-link-pseudo-element} {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    z-index: $stretched-link-z-index;\n    content: \"\";\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_text-truncation.scss",
    "content": "//\n// Text truncation\n//\n\n.text-truncate {\n  @include text-truncate();\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_visually-hidden.scss",
    "content": "//\n// Visually hidden\n//\n\n.visually-hidden,\n.visually-hidden-focusable:not(:focus):not(:focus-within) {\n  @include visually-hidden();\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/helpers/_vr.scss",
    "content": ".vr {\n  display: inline-block;\n  align-self: stretch;\n  width: 1px;\n  min-height: 1em;\n  background-color: currentcolor;\n  opacity: $hr-opacity;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_alert.scss",
    "content": "// scss-docs-start alert-variant-mixin\n@mixin alert-variant($background, $border, $color) {\n  --#{$prefix}alert-color: #{$color};\n  --#{$prefix}alert-bg: #{$background};\n  --#{$prefix}alert-border-color: #{$border};\n\n  @if $enable-gradients {\n    background-image: var(--#{$prefix}gradient);\n  }\n\n  .alert-link {\n    color: shade-color($color, 20%);\n  }\n}\n// scss-docs-end alert-variant-mixin\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_backdrop.scss",
    "content": "// Shared between modals and offcanvases\n@mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity) {\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: $zindex;\n  width: 100vw;\n  height: 100vh;\n  background-color: $backdrop-bg;\n\n  // Fade for backdrop\n  &.fade { opacity: 0; }\n  &.show { opacity: $backdrop-opacity; }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_banner.scss",
    "content": "@mixin bsBanner($file) {\n  /*!\n   * Bootstrap #{$file} v5.2.3 (https://getbootstrap.com/)\n   * Copyright 2011-2022 The Bootstrap Authors\n   * Copyright 2011-2022 Twitter, Inc.\n   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n   */\n}\n\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_border-radius.scss",
    "content": "// stylelint-disable property-disallowed-list\n// Single side border-radius\n\n// Helper function to replace negative values with 0\n@function valid-radius($radius) {\n  $return: ();\n  @each $value in $radius {\n    @if type-of($value) == number {\n      $return: append($return, max($value, 0));\n    } @else {\n      $return: append($return, $value);\n    }\n  }\n  @return $return;\n}\n\n// scss-docs-start border-radius-mixins\n@mixin border-radius($radius: $border-radius, $fallback-border-radius: false) {\n  @if $enable-rounded {\n    border-radius: valid-radius($radius);\n  }\n  @else if $fallback-border-radius != false {\n    border-radius: $fallback-border-radius;\n  }\n}\n\n@mixin border-top-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-top-left-radius: valid-radius($radius);\n    border-top-right-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-end-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-top-right-radius: valid-radius($radius);\n    border-bottom-right-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-bottom-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-bottom-right-radius: valid-radius($radius);\n    border-bottom-left-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-start-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-top-left-radius: valid-radius($radius);\n    border-bottom-left-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-top-start-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-top-left-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-top-end-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-top-right-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-bottom-end-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-bottom-right-radius: valid-radius($radius);\n  }\n}\n\n@mixin border-bottom-start-radius($radius: $border-radius) {\n  @if $enable-rounded {\n    border-bottom-left-radius: valid-radius($radius);\n  }\n}\n// scss-docs-end border-radius-mixins\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_box-shadow.scss",
    "content": "@mixin box-shadow($shadow...) {\n  @if $enable-shadows {\n    $result: ();\n\n    @each $value in $shadow {\n      @if $value != null {\n        $result: append($result, $value, \"comma\");\n      }\n      @if $value == none and length($shadow) > 1 {\n        @warn \"The keyword 'none' must be used as a single argument.\";\n      }\n    }\n\n    @if (length($result) > 0) {\n      box-shadow: $result;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_breakpoints.scss",
    "content": "// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n//    (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n//    >> breakpoint-next(sm)\n//    md\n//    >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))\n//    md\n//    >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl xxl))\n//    md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n  $n: index($breakpoint-names, $name);\n  @if not $n {\n    @error \"breakpoint `#{$name}` not found in `#{$breakpoints}`\";\n  }\n  @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n//    >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))\n//    576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n  $min: map-get($breakpoints, $name);\n  @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width.\n// The maximum value is reduced by 0.02px to work around the limitations of\n// `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n//    >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))\n//    767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n  $max: map-get($breakpoints, $name);\n  @return if($max and $max > 0, $max - .02, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.\n// Useful for making responsive utilities.\n//\n//    >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))\n//    \"\"  (Returns a blank string)\n//    >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))\n//    \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n  @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n  $min: breakpoint-min($name, $breakpoints);\n  @if $min {\n    @media (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @content;\n  }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n  $max: breakpoint-max($name, $breakpoints);\n  @if $max {\n    @media (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @content;\n  }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n  $min: breakpoint-min($lower, $breakpoints);\n  $max: breakpoint-max($upper, $breakpoints);\n\n  @if $min != null and $max != null {\n    @media (min-width: $min) and (max-width: $max) {\n      @content;\n    }\n  } @else if $max == null {\n    @include media-breakpoint-up($lower, $breakpoints) {\n      @content;\n    }\n  } @else if $min == null {\n    @include media-breakpoint-down($upper, $breakpoints) {\n      @content;\n    }\n  }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n  $min:  breakpoint-min($name, $breakpoints);\n  $next: breakpoint-next($name, $breakpoints);\n  $max:  breakpoint-max($next, $breakpoints);\n\n  @if $min != null and $max != null {\n    @media (min-width: $min) and (max-width: $max) {\n      @content;\n    }\n  } @else if $max == null {\n    @include media-breakpoint-up($name, $breakpoints) {\n      @content;\n    }\n  } @else if $min == null {\n    @include media-breakpoint-down($next, $breakpoints) {\n      @content;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_buttons.scss",
    "content": "// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n// scss-docs-start btn-variant-mixin\n@mixin button-variant(\n  $background,\n  $border,\n  $color: color-contrast($background),\n  $hover-background: if($color == $color-contrast-light, shade-color($background, $btn-hover-bg-shade-amount), tint-color($background, $btn-hover-bg-tint-amount)),\n  $hover-border: if($color == $color-contrast-light, shade-color($border, $btn-hover-border-shade-amount), tint-color($border, $btn-hover-border-tint-amount)),\n  $hover-color: color-contrast($hover-background),\n  $active-background: if($color == $color-contrast-light, shade-color($background, $btn-active-bg-shade-amount), tint-color($background, $btn-active-bg-tint-amount)),\n  $active-border: if($color == $color-contrast-light, shade-color($border, $btn-active-border-shade-amount), tint-color($border, $btn-active-border-tint-amount)),\n  $active-color: color-contrast($active-background),\n  $disabled-background: $background,\n  $disabled-border: $border,\n  $disabled-color: color-contrast($disabled-background)\n) {\n  --#{$prefix}btn-color: #{$color};\n  --#{$prefix}btn-bg: #{$background};\n  --#{$prefix}btn-border-color: #{$border};\n  --#{$prefix}btn-hover-color: #{$hover-color};\n  --#{$prefix}btn-hover-bg: #{$hover-background};\n  --#{$prefix}btn-hover-border-color: #{$hover-border};\n  --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix($color, $border, 15%))};\n  --#{$prefix}btn-active-color: #{$active-color};\n  --#{$prefix}btn-active-bg: #{$active-background};\n  --#{$prefix}btn-active-border-color: #{$active-border};\n  --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow};\n  --#{$prefix}btn-disabled-color: #{$disabled-color};\n  --#{$prefix}btn-disabled-bg: #{$disabled-background};\n  --#{$prefix}btn-disabled-border-color: #{$disabled-border};\n}\n// scss-docs-end btn-variant-mixin\n\n// scss-docs-start btn-outline-variant-mixin\n@mixin button-outline-variant(\n  $color,\n  $color-hover: color-contrast($color),\n  $active-background: $color,\n  $active-border: $color,\n  $active-color: color-contrast($active-background)\n) {\n  --#{$prefix}btn-color: #{$color};\n  --#{$prefix}btn-border-color: #{$color};\n  --#{$prefix}btn-hover-color: #{$color-hover};\n  --#{$prefix}btn-hover-bg: #{$active-background};\n  --#{$prefix}btn-hover-border-color: #{$active-border};\n  --#{$prefix}btn-focus-shadow-rgb: #{to-rgb($color)};\n  --#{$prefix}btn-active-color: #{$active-color};\n  --#{$prefix}btn-active-bg: #{$active-background};\n  --#{$prefix}btn-active-border-color: #{$active-border};\n  --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow};\n  --#{$prefix}btn-disabled-color: #{$color};\n  --#{$prefix}btn-disabled-bg: transparent;\n  --#{$prefix}btn-disabled-border-color: #{$color};\n  --#{$prefix}gradient: none;\n}\n// scss-docs-end btn-outline-variant-mixin\n\n// scss-docs-start btn-size-mixin\n@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) {\n  --#{$prefix}btn-padding-y: #{$padding-y};\n  --#{$prefix}btn-padding-x: #{$padding-x};\n  @include rfs($font-size, --#{$prefix}btn-font-size);\n  --#{$prefix}btn-border-radius: #{$border-radius};\n}\n// scss-docs-end btn-size-mixin\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_caret.scss",
    "content": "// scss-docs-start caret-mixins\n@mixin caret-down {\n  border-top: $caret-width solid;\n  border-right: $caret-width solid transparent;\n  border-bottom: 0;\n  border-left: $caret-width solid transparent;\n}\n\n@mixin caret-up {\n  border-top: 0;\n  border-right: $caret-width solid transparent;\n  border-bottom: $caret-width solid;\n  border-left: $caret-width solid transparent;\n}\n\n@mixin caret-end {\n  border-top: $caret-width solid transparent;\n  border-right: 0;\n  border-bottom: $caret-width solid transparent;\n  border-left: $caret-width solid;\n}\n\n@mixin caret-start {\n  border-top: $caret-width solid transparent;\n  border-right: $caret-width solid;\n  border-bottom: $caret-width solid transparent;\n}\n\n@mixin caret($direction: down) {\n  @if $enable-caret {\n    &::after {\n      display: inline-block;\n      margin-left: $caret-spacing;\n      vertical-align: $caret-vertical-align;\n      content: \"\";\n      @if $direction == down {\n        @include caret-down();\n      } @else if $direction == up {\n        @include caret-up();\n      } @else if $direction == end {\n        @include caret-end();\n      }\n    }\n\n    @if $direction == start {\n      &::after {\n        display: none;\n      }\n\n      &::before {\n        display: inline-block;\n        margin-right: $caret-spacing;\n        vertical-align: $caret-vertical-align;\n        content: \"\";\n        @include caret-start();\n      }\n    }\n\n    &:empty::after {\n      margin-left: 0;\n    }\n  }\n}\n// scss-docs-end caret-mixins\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_clearfix.scss",
    "content": "// scss-docs-start clearfix\n@mixin clearfix() {\n  &::after {\n    display: block;\n    clear: both;\n    content: \"\";\n  }\n}\n// scss-docs-end clearfix\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_color-scheme.scss",
    "content": "// scss-docs-start mixin-color-scheme\n@mixin color-scheme($name) {\n  @media (prefers-color-scheme: #{$name}) {\n    @content;\n  }\n}\n// scss-docs-end mixin-color-scheme\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_container.scss",
    "content": "// Container mixins\n\n@mixin make-container($gutter: $container-padding-x) {\n  --#{$prefix}gutter-x: #{$gutter};\n  --#{$prefix}gutter-y: 0;\n  width: 100%;\n  padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list\n  padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list\n  margin-right: auto;\n  margin-left: auto;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_deprecate.scss",
    "content": "// Deprecate mixin\n//\n// This mixin can be used to deprecate mixins or functions.\n// `$enable-deprecation-messages` is a global variable, `$ignore-warning` is a variable that can be passed to\n// some deprecated mixins to suppress the warning (for example if the mixin is still be used in the current version of Bootstrap)\n@mixin deprecate($name, $deprecate-version, $remove-version, $ignore-warning: false) {\n  @if ($enable-deprecation-messages != false and $ignore-warning != true) {\n    @warn \"#{$name} has been deprecated as of #{$deprecate-version}. It will be removed entirely in #{$remove-version}.\";\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_forms.scss",
    "content": "// This mixin uses an `if()` technique to be compatible with Dart Sass\n// See https://github.com/sass/sass/issues/1873#issuecomment-152293725 for more details\n\n// scss-docs-start form-validation-mixins\n@mixin form-validation-state-selector($state) {\n  @if ($state == \"valid\" or $state == \"invalid\") {\n    .was-validated #{if(&, \"&\", \"\")}:#{$state},\n    #{if(&, \"&\", \"\")}.is-#{$state} {\n      @content;\n    }\n  } @else {\n    #{if(&, \"&\", \"\")}.is-#{$state} {\n      @content;\n    }\n  }\n}\n\n@mixin form-validation-state(\n  $state,\n  $color,\n  $icon,\n  $tooltip-color: color-contrast($color),\n  $tooltip-bg-color: rgba($color, $form-feedback-tooltip-opacity),\n  $focus-box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($color, $input-btn-focus-color-opacity)\n) {\n  .#{$state}-feedback {\n    display: none;\n    width: 100%;\n    margin-top: $form-feedback-margin-top;\n    @include font-size($form-feedback-font-size);\n    font-style: $form-feedback-font-style;\n    color: $color;\n  }\n\n  .#{$state}-tooltip {\n    position: absolute;\n    top: 100%;\n    z-index: 5;\n    display: none;\n    max-width: 100%; // Contain to parent when possible\n    padding: $form-feedback-tooltip-padding-y $form-feedback-tooltip-padding-x;\n    margin-top: .1rem;\n    @include font-size($form-feedback-tooltip-font-size);\n    line-height: $form-feedback-tooltip-line-height;\n    color: $tooltip-color;\n    background-color: $tooltip-bg-color;\n    @include border-radius($form-feedback-tooltip-border-radius);\n  }\n\n  @include form-validation-state-selector($state) {\n    ~ .#{$state}-feedback,\n    ~ .#{$state}-tooltip {\n      display: block;\n    }\n  }\n\n  .form-control {\n    @include form-validation-state-selector($state) {\n      border-color: $color;\n\n      @if $enable-validation-icons {\n        padding-right: $input-height-inner;\n        background-image: escape-svg($icon);\n        background-repeat: no-repeat;\n        background-position: right $input-height-inner-quarter center;\n        background-size: $input-height-inner-half $input-height-inner-half;\n      }\n\n      &:focus {\n        border-color: $color;\n        box-shadow: $focus-box-shadow;\n      }\n    }\n  }\n\n  // stylelint-disable-next-line selector-no-qualifying-type\n  textarea.form-control {\n    @include form-validation-state-selector($state) {\n      @if $enable-validation-icons {\n        padding-right: $input-height-inner;\n        background-position: top $input-height-inner-quarter right $input-height-inner-quarter;\n      }\n    }\n  }\n\n  .form-select {\n    @include form-validation-state-selector($state) {\n      border-color: $color;\n\n      @if $enable-validation-icons {\n        &:not([multiple]):not([size]),\n        &:not([multiple])[size=\"1\"] {\n          padding-right: $form-select-feedback-icon-padding-end;\n          background-image: escape-svg($form-select-indicator), escape-svg($icon);\n          background-position: $form-select-bg-position, $form-select-feedback-icon-position;\n          background-size: $form-select-bg-size, $form-select-feedback-icon-size;\n        }\n      }\n\n      &:focus {\n        border-color: $color;\n        box-shadow: $focus-box-shadow;\n      }\n    }\n  }\n\n  .form-control-color {\n    @include form-validation-state-selector($state) {\n      @if $enable-validation-icons {\n        width: add($form-color-width, $input-height-inner);\n      }\n    }\n  }\n\n  .form-check-input {\n    @include form-validation-state-selector($state) {\n      border-color: $color;\n\n      &:checked {\n        background-color: $color;\n      }\n\n      &:focus {\n        box-shadow: $focus-box-shadow;\n      }\n\n      ~ .form-check-label {\n        color: $color;\n      }\n    }\n  }\n  .form-check-inline .form-check-input {\n    ~ .#{$state}-feedback {\n      margin-left: .5em;\n    }\n  }\n\n  .input-group {\n    > .form-control:not(:focus),\n    > .form-select:not(:focus),\n    > .form-floating:not(:focus-within) {\n      @include form-validation-state-selector($state) {\n        @if $state == \"valid\" {\n          z-index: 3;\n        } @else if $state == \"invalid\" {\n          z-index: 4;\n        }\n      }\n    }\n  }\n}\n// scss-docs-end form-validation-mixins\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_gradients.scss",
    "content": "// Gradients\n\n// scss-docs-start gradient-bg-mixin\n@mixin gradient-bg($color: null) {\n  background-color: $color;\n\n  @if $enable-gradients {\n    background-image: var(--#{$prefix}gradient);\n  }\n}\n// scss-docs-end gradient-bg-mixin\n\n// scss-docs-start gradient-mixins\n// Horizontal gradient, from left to right\n//\n// Creates two color stops, start and end, by specifying a color and position for each color stop.\n@mixin gradient-x($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) {\n  background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent);\n}\n\n// Vertical gradient, from top to bottom\n//\n// Creates two color stops, start and end, by specifying a color and position for each color stop.\n@mixin gradient-y($start-color: $gray-700, $end-color: $gray-800, $start-percent: null, $end-percent: null) {\n  background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent);\n}\n\n@mixin gradient-directional($start-color: $gray-700, $end-color: $gray-800, $deg: 45deg) {\n  background-image: linear-gradient($deg, $start-color, $end-color);\n}\n\n@mixin gradient-x-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) {\n  background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color);\n}\n\n@mixin gradient-y-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) {\n  background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color);\n}\n\n@mixin gradient-radial($inner-color: $gray-700, $outer-color: $gray-800) {\n  background-image: radial-gradient(circle, $inner-color, $outer-color);\n}\n\n@mixin gradient-striped($color: rgba($white, .15), $angle: 45deg) {\n  background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent);\n}\n// scss-docs-end gradient-mixins\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_grid.scss",
    "content": "// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n@mixin make-row($gutter: $grid-gutter-width) {\n  --#{$prefix}gutter-x: #{$gutter};\n  --#{$prefix}gutter-y: 0;\n  display: flex;\n  flex-wrap: wrap;\n  // TODO: Revisit calc order after https://github.com/react-bootstrap/react-bootstrap/issues/6039 is fixed\n  margin-top: calc(-1 * var(--#{$prefix}gutter-y)); // stylelint-disable-line function-disallowed-list\n  margin-right: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list\n  margin-left: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list\n}\n\n@mixin make-col-ready() {\n  // Add box sizing if only the grid is loaded\n  box-sizing: if(variable-exists(include-column-box-sizing) and $include-column-box-sizing, border-box, null);\n  // Prevent columns from becoming too narrow when at smaller grid tiers by\n  // always setting `width: 100%;`. This works because we set the width\n  // later on to override this initial width.\n  flex-shrink: 0;\n  width: 100%;\n  max-width: 100%; // Prevent `.col-auto`, `.col` (& responsive variants) from breaking out the grid\n  padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list\n  padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list\n  margin-top: var(--#{$prefix}gutter-y);\n}\n\n@mixin make-col($size: false, $columns: $grid-columns) {\n  @if $size {\n    flex: 0 0 auto;\n    width: percentage(divide($size, $columns));\n\n  } @else {\n    flex: 1 1 0;\n    max-width: 100%;\n  }\n}\n\n@mixin make-col-auto() {\n  flex: 0 0 auto;\n  width: auto;\n}\n\n@mixin make-col-offset($size, $columns: $grid-columns) {\n  $num: divide($size, $columns);\n  margin-left: if($num == 0, 0, percentage($num));\n}\n\n// Row columns\n//\n// Specify on a parent element(e.g., .row) to force immediate children into NN\n// number of columns. Supports wrapping to new lines, but does not do a Masonry\n// style grid.\n@mixin row-cols($count) {\n  > * {\n    flex: 0 0 auto;\n    width: divide(100%, $count);\n  }\n}\n\n// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `$grid-columns`.\n\n@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {\n  @each $breakpoint in map-keys($breakpoints) {\n    $infix: breakpoint-infix($breakpoint, $breakpoints);\n\n    @include media-breakpoint-up($breakpoint, $breakpoints) {\n      // Provide basic `.col-{bp}` classes for equal-width flexbox columns\n      .col#{$infix} {\n        flex: 1 0 0%; // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4\n      }\n\n      .row-cols#{$infix}-auto > * {\n        @include make-col-auto();\n      }\n\n      @if $grid-row-columns > 0 {\n        @for $i from 1 through $grid-row-columns {\n          .row-cols#{$infix}-#{$i} {\n            @include row-cols($i);\n          }\n        }\n      }\n\n      .col#{$infix}-auto {\n        @include make-col-auto();\n      }\n\n      @if $columns > 0 {\n        @for $i from 1 through $columns {\n          .col#{$infix}-#{$i} {\n            @include make-col($i, $columns);\n          }\n        }\n\n        // `$columns - 1` because offsetting by the width of an entire row isn't possible\n        @for $i from 0 through ($columns - 1) {\n          @if not ($infix == \"\" and $i == 0) { // Avoid emitting useless .offset-0\n            .offset#{$infix}-#{$i} {\n              @include make-col-offset($i, $columns);\n            }\n          }\n        }\n      }\n\n      // Gutters\n      //\n      // Make use of `.g-*`, `.gx-*` or `.gy-*` utilities to change spacing between the columns.\n      @each $key, $value in $gutters {\n        .g#{$infix}-#{$key},\n        .gx#{$infix}-#{$key} {\n          --#{$prefix}gutter-x: #{$value};\n        }\n\n        .g#{$infix}-#{$key},\n        .gy#{$infix}-#{$key} {\n          --#{$prefix}gutter-y: #{$value};\n        }\n      }\n    }\n  }\n}\n\n@mixin make-cssgrid($columns: $grid-columns, $breakpoints: $grid-breakpoints) {\n  @each $breakpoint in map-keys($breakpoints) {\n    $infix: breakpoint-infix($breakpoint, $breakpoints);\n\n    @include media-breakpoint-up($breakpoint, $breakpoints) {\n      @if $columns > 0 {\n        @for $i from 1 through $columns {\n          .g-col#{$infix}-#{$i} {\n            grid-column: auto / span $i;\n          }\n        }\n\n        // Start with `1` because `0` is and invalid value.\n        // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.\n        @for $i from 1 through ($columns - 1) {\n          .g-start#{$infix}-#{$i} {\n            grid-column-start: $i;\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_image.scss",
    "content": "// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n@mixin img-fluid {\n  // Part 1: Set a maximum relative to the parent\n  max-width: 100%;\n  // Part 2: Override the height to auto, otherwise images will be stretched\n  // when setting a width and height attribute on the img element.\n  height: auto;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_list-group.scss",
    "content": "// List Groups\n\n// scss-docs-start list-group-mixin\n@mixin list-group-item-variant($state, $background, $color) {\n  .list-group-item-#{$state} {\n    color: $color;\n    background-color: $background;\n\n    &.list-group-item-action {\n      &:hover,\n      &:focus {\n        color: $color;\n        background-color: shade-color($background, 10%);\n      }\n\n      &.active {\n        color: $white;\n        background-color: $color;\n        border-color: $color;\n      }\n    }\n  }\n}\n// scss-docs-end list-group-mixin\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_lists.scss",
    "content": "// Lists\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n@mixin list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_pagination.scss",
    "content": "// Pagination\n\n// scss-docs-start pagination-mixin\n@mixin pagination-size($padding-y, $padding-x, $font-size, $border-radius) {\n  --#{$prefix}pagination-padding-x: #{$padding-x};\n  --#{$prefix}pagination-padding-y: #{$padding-y};\n  @include rfs($font-size, --#{$prefix}pagination-font-size);\n  --#{$prefix}pagination-border-radius: #{$border-radius};\n}\n// scss-docs-end pagination-mixin\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_reset-text.scss",
    "content": "@mixin reset-text {\n  font-family: $font-family-base;\n  // We deliberately do NOT reset font-size or overflow-wrap / word-wrap.\n  font-style: normal;\n  font-weight: $font-weight-normal;\n  line-height: $line-height-base;\n  text-align: left; // Fallback for where `start` is not supported\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  white-space: normal;\n  word-spacing: normal;\n  line-break: auto;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_resize.scss",
    "content": "// Resize anything\n\n@mixin resizable($direction) {\n  overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`\n  resize: $direction; // Options: horizontal, vertical, both\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_table-variants.scss",
    "content": "// scss-docs-start table-variant\n@mixin table-variant($state, $background) {\n  .table-#{$state} {\n    $color: color-contrast(opaque($body-bg, $background));\n    $hover-bg: mix($color, $background, percentage($table-hover-bg-factor));\n    $striped-bg: mix($color, $background, percentage($table-striped-bg-factor));\n    $active-bg: mix($color, $background, percentage($table-active-bg-factor));\n    $table-border-color: mix($color, $background, percentage($table-border-factor));\n\n    --#{$prefix}table-color: #{$color};\n    --#{$prefix}table-bg: #{$background};\n    --#{$prefix}table-border-color: #{$table-border-color};\n    --#{$prefix}table-striped-bg: #{$striped-bg};\n    --#{$prefix}table-striped-color: #{color-contrast($striped-bg)};\n    --#{$prefix}table-active-bg: #{$active-bg};\n    --#{$prefix}table-active-color: #{color-contrast($active-bg)};\n    --#{$prefix}table-hover-bg: #{$hover-bg};\n    --#{$prefix}table-hover-color: #{color-contrast($hover-bg)};\n\n    color: var(--#{$prefix}table-color);\n    border-color: var(--#{$prefix}table-border-color);\n  }\n}\n// scss-docs-end table-variant\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_text-truncate.scss",
    "content": "// Text truncate\n// Requires inline-block or block for proper styling\n\n@mixin text-truncate() {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_transition.scss",
    "content": "// stylelint-disable property-disallowed-list\n@mixin transition($transition...) {\n  @if length($transition) == 0 {\n    $transition: $transition-base;\n  }\n\n  @if length($transition) > 1 {\n    @each $value in $transition {\n      @if $value == null or $value == none {\n        @warn \"The keyword 'none' or 'null' must be used as a single argument.\";\n      }\n    }\n  }\n\n  @if $enable-transitions {\n    @if nth($transition, 1) != null {\n      transition: $transition;\n    }\n\n    @if $enable-reduced-motion and nth($transition, 1) != null and nth($transition, 1) != none {\n      @media (prefers-reduced-motion: reduce) {\n        transition: none;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_utilities.scss",
    "content": "// Utility generator\n// Used to generate utilities & print utilities\n@mixin generate-utility($utility, $infix, $is-rfs-media-query: false) {\n  $values: map-get($utility, values);\n\n  // If the values are a list or string, convert it into a map\n  @if type-of($values) == \"string\" or type-of(nth($values, 1)) != \"list\" {\n    $values: zip($values, $values);\n  }\n\n  @each $key, $value in $values {\n    $properties: map-get($utility, property);\n\n    // Multiple properties are possible, for example with vertical or horizontal margins or paddings\n    @if type-of($properties) == \"string\" {\n      $properties: append((), $properties);\n    }\n\n    // Use custom class if present\n    $property-class: if(map-has-key($utility, class), map-get($utility, class), nth($properties, 1));\n    $property-class: if($property-class == null, \"\", $property-class);\n\n    // Use custom CSS variable name if present, otherwise default to `class`\n    $css-variable-name: if(map-has-key($utility, css-variable-name), map-get($utility, css-variable-name), map-get($utility, class));\n\n    // State params to generate pseudo-classes\n    $state: if(map-has-key($utility, state), map-get($utility, state), ());\n\n    $infix: if($property-class == \"\" and str-slice($infix, 1, 1) == \"-\", str-slice($infix, 2), $infix);\n\n    // Don't prefix if value key is null (e.g. with shadow class)\n    $property-class-modifier: if($key, if($property-class == \"\" and $infix == \"\", \"\", \"-\") + $key, \"\");\n\n    @if map-get($utility, rfs) {\n      // Inside the media query\n      @if $is-rfs-media-query {\n        $val: rfs-value($value);\n\n        // Do not render anything if fluid and non fluid values are the same\n        $value: if($val == rfs-fluid-value($value), null, $val);\n      }\n      @else {\n        $value: rfs-fluid-value($value);\n      }\n    }\n\n    $is-css-var: map-get($utility, css-var);\n    $is-local-vars: map-get($utility, local-vars);\n    $is-rtl: map-get($utility, rtl);\n\n    @if $value != null {\n      @if $is-rtl == false {\n        /* rtl:begin:remove */\n      }\n\n      @if $is-css-var {\n        .#{$property-class + $infix + $property-class-modifier} {\n          --#{$prefix}#{$css-variable-name}: #{$value};\n        }\n\n        @each $pseudo in $state {\n          .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} {\n            --#{$prefix}#{$css-variable-name}: #{$value};\n          }\n        }\n      } @else {\n        .#{$property-class + $infix + $property-class-modifier} {\n          @each $property in $properties {\n            @if $is-local-vars {\n              @each $local-var, $variable in $is-local-vars {\n                --#{$prefix}#{$local-var}: #{$variable};\n              }\n            }\n            #{$property}: $value if($enable-important-utilities, !important, null);\n          }\n        }\n\n        @each $pseudo in $state {\n          .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} {\n            @each $property in $properties {\n              @if $is-local-vars {\n                @each $local-var, $variable in $is-local-vars {\n                  --#{$prefix}#{$local-var}: #{$variable};\n                }\n              }\n              #{$property}: $value if($enable-important-utilities, !important, null);\n            }\n          }\n        }\n      }\n\n      @if $is-rtl == false {\n        /* rtl:end:remove */\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/mixins/_visually-hidden.scss",
    "content": "// stylelint-disable declaration-no-important\n\n// Hide content visually while keeping it accessible to assistive technologies\n//\n// See: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/\n// See: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/\n\n@mixin visually-hidden() {\n  position: absolute !important;\n  width: 1px !important;\n  height: 1px !important;\n  padding: 0 !important;\n  margin: -1px !important; // Fix for https://github.com/twbs/bootstrap/issues/25686\n  overflow: hidden !important;\n  clip: rect(0, 0, 0, 0) !important;\n  white-space: nowrap !important;\n  border: 0 !important;\n}\n\n// Use to only display content when it's focused, or one of its child elements is focused\n// (i.e. when focus is within the element/container that the class was applied to)\n//\n// Useful for \"Skip to main content\" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n\n@mixin visually-hidden-focusable() {\n  &:not(:focus):not(:focus-within) {\n    @include visually-hidden();\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/utilities/_api.scss",
    "content": "// Loop over each breakpoint\n@each $breakpoint in map-keys($grid-breakpoints) {\n\n  // Generate media query if needed\n  @include media-breakpoint-up($breakpoint) {\n    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n    // Loop over each utility property\n    @each $key, $utility in $utilities {\n      // The utility can be disabled with `false`, thus check if the utility is a map first\n      // Only proceed if responsive media queries are enabled or if it's the base media query\n      @if type-of($utility) == \"map\" and (map-get($utility, responsive) or $infix == \"\") {\n        @include generate-utility($utility, $infix);\n      }\n    }\n  }\n}\n\n// RFS rescaling\n@media (min-width: $rfs-mq-value) {\n  @each $breakpoint in map-keys($grid-breakpoints) {\n    $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n    @if (map-get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) {\n      // Loop over each utility property\n      @each $key, $utility in $utilities {\n        // The utility can be disabled with `false`, thus check if the utility is a map first\n        // Only proceed if responsive media queries are enabled or if it's the base media query\n        @if type-of($utility) == \"map\" and map-get($utility, rfs) and (map-get($utility, responsive) or $infix == \"\") {\n          @include generate-utility($utility, $infix, true);\n        }\n      }\n    }\n  }\n}\n\n\n// Print utilities\n@media print {\n  @each $key, $utility in $utilities {\n    // The utility can be disabled with `false`, thus check if the utility is a map first\n    // Then check if the utility needs print styles\n    @if type-of($utility) == \"map\" and map-get($utility, print) == true {\n      @include generate-utility($utility, \"-print\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/scss/vendor/_rfs.scss",
    "content": "// stylelint-disable property-blacklist, scss/dollar-variable-default\n\n// SCSS RFS mixin\n//\n// Automated responsive values for font sizes, paddings, margins and much more\n//\n// Licensed under MIT (https://github.com/twbs/rfs/blob/main/LICENSE)\n\n// Configuration\n\n// Base value\n$rfs-base-value: 1.25rem !default;\n$rfs-unit: rem !default;\n\n@if $rfs-unit != rem and $rfs-unit != px {\n  @error \"`#{$rfs-unit}` is not a valid unit for $rfs-unit. Use `px` or `rem`.\";\n}\n\n// Breakpoint at where values start decreasing if screen width is smaller\n$rfs-breakpoint: 1200px !default;\n$rfs-breakpoint-unit: px !default;\n\n@if $rfs-breakpoint-unit != px and $rfs-breakpoint-unit != em and $rfs-breakpoint-unit != rem {\n  @error \"`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`.\";\n}\n\n// Resize values based on screen height and width\n$rfs-two-dimensional: false !default;\n\n// Factor of decrease\n$rfs-factor: 10 !default;\n\n@if type-of($rfs-factor) != number or $rfs-factor <= 1 {\n  @error \"`#{$rfs-factor}` is not a valid  $rfs-factor, it must be greater than 1.\";\n}\n\n// Mode. Possibilities: \"min-media-query\", \"max-media-query\"\n$rfs-mode: min-media-query !default;\n\n// Generate enable or disable classes. Possibilities: false, \"enable\" or \"disable\"\n$rfs-class: false !default;\n\n// 1 rem = $rfs-rem-value px\n$rfs-rem-value: 16 !default;\n\n// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14\n$rfs-safari-iframe-resize-bug-fix: false !default;\n\n// Disable RFS by setting $enable-rfs to false\n$enable-rfs: true !default;\n\n// Cache $rfs-base-value unit\n$rfs-base-value-unit: unit($rfs-base-value);\n\n@function divide($dividend, $divisor, $precision: 10) {\n  $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1);\n  $dividend: abs($dividend);\n  $divisor: abs($divisor);\n  @if $dividend == 0 {\n    @return 0;\n  }\n  @if $divisor == 0 {\n    @error \"Cannot divide by 0\";\n  }\n  $remainder: $dividend;\n  $result: 0;\n  $factor: 10;\n  @while ($remainder > 0 and $precision >= 0) {\n    $quotient: 0;\n    @while ($remainder >= $divisor) {\n      $remainder: $remainder - $divisor;\n      $quotient: $quotient + 1;\n    }\n    $result: $result * 10 + $quotient;\n    $factor: $factor * .1;\n    $remainder: $remainder * 10;\n    $precision: $precision - 1;\n    @if ($precision < 0 and $remainder >= $divisor * 5) {\n      $result: $result + 1;\n    }\n  }\n  $result: $result * $factor * $sign;\n  $dividend-unit: unit($dividend);\n  $divisor-unit: unit($divisor);\n  $unit-map: (\n    \"px\": 1px,\n    \"rem\": 1rem,\n    \"em\": 1em,\n    \"%\": 1%\n  );\n  @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) {\n    $result: $result * map-get($unit-map, $dividend-unit);\n  }\n  @return $result;\n}\n\n// Remove px-unit from $rfs-base-value for calculations\n@if $rfs-base-value-unit == px {\n  $rfs-base-value: divide($rfs-base-value, $rfs-base-value * 0 + 1);\n}\n@else if $rfs-base-value-unit == rem {\n  $rfs-base-value: divide($rfs-base-value, divide($rfs-base-value * 0 + 1, $rfs-rem-value));\n}\n\n// Cache $rfs-breakpoint unit to prevent multiple calls\n$rfs-breakpoint-unit-cache: unit($rfs-breakpoint);\n\n// Remove unit from $rfs-breakpoint for calculations\n@if $rfs-breakpoint-unit-cache == px {\n  $rfs-breakpoint: divide($rfs-breakpoint, $rfs-breakpoint * 0 + 1);\n}\n@else if $rfs-breakpoint-unit-cache == rem or $rfs-breakpoint-unit-cache == \"em\" {\n  $rfs-breakpoint: divide($rfs-breakpoint, divide($rfs-breakpoint * 0 + 1, $rfs-rem-value));\n}\n\n// Calculate the media query value\n$rfs-mq-value: if($rfs-breakpoint-unit == px, #{$rfs-breakpoint}px, #{divide($rfs-breakpoint, $rfs-rem-value)}#{$rfs-breakpoint-unit});\n$rfs-mq-property-width: if($rfs-mode == max-media-query, max-width, min-width);\n$rfs-mq-property-height: if($rfs-mode == max-media-query, max-height, min-height);\n\n// Internal mixin used to determine which media query needs to be used\n@mixin _rfs-media-query {\n  @if $rfs-two-dimensional {\n    @if $rfs-mode == max-media-query {\n      @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}), (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {\n        @content;\n      }\n    }\n    @else {\n      @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) and (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {\n        @content;\n      }\n    }\n  }\n  @else {\n    @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) {\n      @content;\n    }\n  }\n}\n\n// Internal mixin that adds disable classes to the selector if needed.\n@mixin _rfs-rule {\n  @if $rfs-class == disable and $rfs-mode == max-media-query {\n    // Adding an extra class increases specificity, which prevents the media query to override the property\n    &,\n    .disable-rfs &,\n    &.disable-rfs {\n      @content;\n    }\n  }\n  @else if $rfs-class == enable and $rfs-mode == min-media-query {\n    .enable-rfs &,\n    &.enable-rfs {\n      @content;\n    }\n  }\n  @else {\n    @content;\n  }\n}\n\n// Internal mixin that adds enable classes to the selector if needed.\n@mixin _rfs-media-query-rule {\n\n  @if $rfs-class == enable {\n    @if $rfs-mode == min-media-query {\n      @content;\n    }\n\n    @include _rfs-media-query {\n      .enable-rfs &,\n      &.enable-rfs {\n        @content;\n      }\n    }\n  }\n  @else {\n    @if $rfs-class == disable and $rfs-mode == min-media-query {\n      .disable-rfs &,\n      &.disable-rfs {\n        @content;\n      }\n    }\n    @include _rfs-media-query {\n      @content;\n    }\n  }\n}\n\n// Helper function to get the formatted non-responsive value\n@function rfs-value($values) {\n  // Convert to list\n  $values: if(type-of($values) != list, ($values,), $values);\n\n  $val: '';\n\n  // Loop over each value and calculate value\n  @each $value in $values {\n    @if $value == 0 {\n      $val: $val + ' 0';\n    }\n    @else {\n      // Cache $value unit\n      $unit: if(type-of($value) == \"number\", unit($value), false);\n\n      @if $unit == px {\n        // Convert to rem if needed\n        $val: $val + ' ' + if($rfs-unit == rem, #{divide($value, $value * 0 + $rfs-rem-value)}rem, $value);\n      }\n      @else if $unit == rem {\n        // Convert to px if needed\n        $val: $val + ' ' + if($rfs-unit == px, #{divide($value, $value * 0 + 1) * $rfs-rem-value}px, $value);\n      }\n      @else {\n        // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value\n        $val: $val + ' ' + $value;\n      }\n    }\n  }\n\n  // Remove first space\n  @return unquote(str-slice($val, 2));\n}\n\n// Helper function to get the responsive value calculated by RFS\n@function rfs-fluid-value($values) {\n  // Convert to list\n  $values: if(type-of($values) != list, ($values,), $values);\n\n  $val: '';\n\n  // Loop over each value and calculate value\n  @each $value in $values {\n    @if $value == 0 {\n      $val: $val + ' 0';\n    }\n\n    @else {\n      // Cache $value unit\n      $unit: if(type-of($value) == \"number\", unit($value), false);\n\n      // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value\n      @if not $unit or $unit != px and $unit != rem {\n        $val: $val + ' ' + $value;\n      }\n\n      @else {\n        // Remove unit from $value for calculations\n        $value: divide($value, $value * 0 + if($unit == px, 1, divide(1, $rfs-rem-value)));\n\n        // Only add the media query if the value is greater than the minimum value\n        @if abs($value) <= $rfs-base-value or not $enable-rfs {\n          $val: $val + ' ' +  if($rfs-unit == rem, #{divide($value, $rfs-rem-value)}rem, #{$value}px);\n        }\n        @else {\n          // Calculate the minimum value\n          $value-min: $rfs-base-value + divide(abs($value) - $rfs-base-value, $rfs-factor);\n\n          // Calculate difference between $value and the minimum value\n          $value-diff: abs($value) - $value-min;\n\n          // Base value formatting\n          $min-width: if($rfs-unit == rem, #{divide($value-min, $rfs-rem-value)}rem, #{$value-min}px);\n\n          // Use negative value if needed\n          $min-width: if($value < 0, -$min-width, $min-width);\n\n          // Use `vmin` if two-dimensional is enabled\n          $variable-unit: if($rfs-two-dimensional, vmin, vw);\n\n          // Calculate the variable width between 0 and $rfs-breakpoint\n          $variable-width: #{divide($value-diff * 100, $rfs-breakpoint)}#{$variable-unit};\n\n          // Return the calculated value\n          $val: $val + ' calc(' + $min-width + if($value < 0, ' - ', ' + ') + $variable-width + ')';\n        }\n      }\n    }\n  }\n\n  // Remove first space\n  @return unquote(str-slice($val, 2));\n}\n\n// RFS mixin\n@mixin rfs($values, $property: font-size) {\n  @if $values != null {\n    $val: rfs-value($values);\n    $fluidVal: rfs-fluid-value($values);\n\n    // Do not print the media query if responsive & non-responsive values are the same\n    @if $val == $fluidVal {\n      #{$property}: $val;\n    }\n    @else {\n      @include _rfs-rule {\n        #{$property}: if($rfs-mode == max-media-query, $val, $fluidVal);\n\n        // Include safari iframe resize fix if needed\n        min-width: if($rfs-safari-iframe-resize-bug-fix, (0 * 1vw), null);\n      }\n\n      @include _rfs-media-query-rule {\n        #{$property}: if($rfs-mode == max-media-query, $fluidVal, $val);\n      }\n    }\n  }\n}\n\n// Shorthand helper mixins\n@mixin font-size($value) {\n  @include rfs($value);\n}\n\n@mixin padding($value) {\n  @include rfs($value, padding);\n}\n\n@mixin padding-top($value) {\n  @include rfs($value, padding-top);\n}\n\n@mixin padding-right($value) {\n  @include rfs($value, padding-right);\n}\n\n@mixin padding-bottom($value) {\n  @include rfs($value, padding-bottom);\n}\n\n@mixin padding-left($value) {\n  @include rfs($value, padding-left);\n}\n\n@mixin margin($value) {\n  @include rfs($value, margin);\n}\n\n@mixin margin-top($value) {\n  @include rfs($value, margin-top);\n}\n\n@mixin margin-right($value) {\n  @include rfs($value, margin-right);\n}\n\n@mixin margin-bottom($value) {\n  @include rfs($value, margin-bottom);\n}\n\n@mixin margin-left($value) {\n  @include rfs($value, margin-left);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"node\": false\n  },\n  \"plugins\": [\n    \"markdown\"\n  ],\n  \"overrides\": [\n    {\n      // 2. Enable the Markdown processor for all .md files.\n      \"files\": [\"./**/*.md\"],\n      \"processor\": \"markdown/markdown\"\n    },\n    {\n      // In v2, configuration for fenced code blocks is separate from the\n      // containing Markdown file. Each code block has a virtual filename\n      // appended to the Markdown file's path.\n      \"files\": [\n        \"./**/*.md/*.js\"\n      ],\n      // Configuration for fenced code blocks goes with the override for\n      // the code block's virtual filename, for example:\n      \"parserOptions\": {\n        \"ecmaFeatures\": {\n          \"impliedStrict\": true\n        }\n      },\n      \"rules\": {\n        \"no-array-for-each\": \"off\",\n        \"no-undef\": \"off\",\n        \"no-unused-vars\": \"off\",\n        \"unicorn/no-array-for-each\": \"off\",\n        \"unicorn/numeric-separators-style\": \"off\",\n        \"no-unused-expressions\": \"off\",\n        \"no-unused-labels\": \"off\",\n        \"no-labels\": \"off\",\n        \"no-redeclare\": \"off\"\n      }\n    }\n  ],\n  \"parserOptions\": {\n    \"sourceType\": \"script\"\n  },\n  \"extends\": \"../.eslintrc.json\",\n  \"rules\": {\n    \"no-new\": \"off\",\n    \"prefer-template\": \"error\",\n    \"strict\": \"error\",\n    \"unicorn/no-array-for-each\": \"off\",\n    \"unicorn/numeric-separators-style\": \"off\",\n    \"unicorn/prefer-node-protocol\": \"off\"\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/js/application.js",
    "content": "// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT\n// IT'S ALL JUST JUNK FOR OUR DOCS!\n// ++++++++++++++++++++++++++++++++++++++++++\n\n/*!\n * JavaScript for Bootstrap's docs (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under the Creative Commons Attribution 3.0 Unported License.\n * For details, see https://creativecommons.org/licenses/by/3.0/.\n */\n\n(() => {\n  'use strict'\n\n  // Scroll the active sidebar link into view\n  const sidenav = document.querySelector('.bd-sidebar')\n  if (sidenav) {\n    const sidenavHeight = sidenav.clientHeight\n    const sidenavActiveLink = document.querySelector('.bd-links-nav .active')\n    const sidenavActiveLinkTop = sidenavActiveLink.offsetTop\n    const sidenavActiveLinkHeight = sidenavActiveLink.clientHeight\n    const viewportTop = sidenavActiveLinkTop\n    const viewportBottom = viewportTop - sidenavHeight + sidenavActiveLinkHeight\n\n    if (sidenav.scrollTop > viewportTop || sidenav.scrollTop < viewportBottom) {\n      sidenav.scrollTop = viewportTop - (sidenavHeight / 2) + (sidenavActiveLinkHeight / 2)\n    }\n  }\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/js/code-examples.js",
    "content": "// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT\n// IT'S ALL JUST JUNK FOR OUR DOCS!\n// ++++++++++++++++++++++++++++++++++++++++++\n\n/*!\n * JavaScript for Bootstrap's docs (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under the Creative Commons Attribution 3.0 Unported License.\n * For details, see https://creativecommons.org/licenses/by/3.0/.\n */\n\n/* global ClipboardJS: false, bootstrap: false */\n\n(() => {\n  'use strict'\n  // Insert copy to clipboard button before .highlight\n  const btnTitle = 'Copy to clipboard'\n  const btnEdit = 'Edit on StackBlitz'\n\n  const btnHtml = [\n    '<div class=\"bd-code-snippet\">',\n    '   <div class=\"bd-clipboard\">',\n    '      <button type=\"button\" class=\"btn-clipboard\">',\n    '        <svg class=\"bi\" role=\"img\" aria-label=\"Copy\"><use xlink:href=\"#clipboard\"/></svg>',\n    '      </button>',\n    '   </div>',\n    '</div>'\n  ].join('')\n\n  // wrap programmatically code blocks and add copy btn.\n  document.querySelectorAll('.highlight')\n    .forEach(element => {\n      if (!element.closest('.bd-example-snippet')) { // Ignore examples made be shortcode\n        element.insertAdjacentHTML('beforebegin', btnHtml)\n        element.previousElementSibling.append(element)\n      }\n    })\n\n  /**\n   *\n   * @param {string} selector\n   * @param {string} title\n   */\n  function snippetButtonTooltip(selector, title) {\n    document.querySelectorAll(selector).forEach(btn => {\n      bootstrap.Tooltip.getOrCreateInstance(btn, { title })\n    })\n  }\n\n  snippetButtonTooltip('.btn-clipboard', btnTitle)\n  snippetButtonTooltip('.btn-edit', btnEdit)\n\n  const clipboard = new ClipboardJS('.btn-clipboard', {\n    target: trigger => trigger.closest('.bd-code-snippet').querySelector('.highlight')\n  })\n\n  clipboard.on('success', event => {\n    const iconFirstChild = event.trigger.querySelector('.bi').firstChild\n    const tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger)\n    const namespace = 'http://www.w3.org/1999/xlink'\n    const originalXhref = iconFirstChild.getAttributeNS(namespace, 'href')\n    const originalTitle = event.trigger.title\n\n    tooltipBtn.setContent({ '.tooltip-inner': 'Copied!' })\n    event.trigger.addEventListener('hidden.bs.tooltip', () => {\n      tooltipBtn.setContent({ '.tooltip-inner': btnTitle })\n    }, { once: true })\n    event.clearSelection()\n    iconFirstChild.setAttributeNS(namespace, 'href', originalXhref.replace('clipboard', 'check2'))\n\n    setTimeout(() => {\n      iconFirstChild.setAttributeNS(namespace, 'href', originalXhref)\n      event.trigger.title = originalTitle\n    }, 2000)\n  })\n\n  clipboard.on('error', event => {\n    const modifierKey = /mac/i.test(navigator.userAgent) ? '\\u2318' : 'Ctrl-'\n    const fallbackMsg = `Press ${modifierKey}C to copy`\n    const tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger)\n\n    tooltipBtn.setContent({ '.tooltip-inner': fallbackMsg })\n    event.trigger.addEventListener('hidden.bs.tooltip', () => {\n      tooltipBtn.setContent({ '.tooltip-inner': btnTitle })\n    }, { once: true })\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/js/search.js",
    "content": "// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT\n// IT'S ALL JUST JUNK FOR OUR DOCS!\n// ++++++++++++++++++++++++++++++++++++++++++\n\n(() => {\n  'use strict'\n\n  const searchElement = document.getElementById('docsearch')\n\n  if (!window.docsearch || !searchElement) {\n    return\n  }\n\n  const siteDocsVersion = searchElement.getAttribute('data-bd-docs-version')\n\n  window.docsearch({\n    apiKey: '3151f502c7b9e9dafd5e6372b691a24e',\n    indexName: 'bootstrap',\n    appId: 'AK7KMZKZHQ',\n    container: searchElement,\n    searchParameters: {\n      facetFilters: [`version:${siteDocsVersion}`]\n    },\n    transformItems(items) {\n      return items.map(item => {\n        const liveUrl = 'https://getbootstrap.com/'\n\n        item.url = window.location.origin.startsWith(liveUrl) ?\n          // On production, return the result as is\n          item.url :\n          // On development or Netlify, replace `item.url` with a trailing slash,\n          // so that the result link is relative to the server root\n          item.url.replace(liveUrl, '/')\n\n        // Prevent jumping to first header\n        if (item.anchor === 'content') {\n          item.url = item.url.replace(/#content$/, '')\n          item.anchor = null\n        }\n\n        return item\n      })\n    },\n    // Set debug to `true` if you want to inspect the dropdown\n    debug: false\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/js/snippets.js",
    "content": "// NOTICE!!! Initially embedded in our docs this JavaScript\n// file contains elements that can help you create reproducible\n// use cases in StackBlitz for instance.\n// In a real project please adapt this content to your needs.\n// ++++++++++++++++++++++++++++++++++++++++++\n\n/*!\n * JavaScript for Bootstrap's docs (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under the Creative Commons Attribution 3.0 Unported License.\n * For details, see https://creativecommons.org/licenses/by/3.0/.\n */\n\n/* global bootstrap: false */\n\n(() => {\n  'use strict'\n\n  // --------\n  // Tooltips\n  // --------\n  // Instantiate all tooltips in a docs or StackBlitz page\n  document.querySelectorAll('[data-bs-toggle=\"tooltip\"]')\n    .forEach(tooltip => {\n      new bootstrap.Tooltip(tooltip)\n    })\n\n  // --------\n  // Popovers\n  // --------\n  // Instantiate all popovers in a docs or StackBlitz page\n  document.querySelectorAll('[data-bs-toggle=\"popover\"]')\n    .forEach(popover => {\n      new bootstrap.Popover(popover)\n    })\n\n  // -------------------------------\n  // Toasts\n  // -------------------------------\n  // Used by 'Placement' example in docs or StackBlitz\n  const toastPlacement = document.getElementById('toastPlacement')\n  if (toastPlacement) {\n    document.getElementById('selectToastPlacement').addEventListener('change', function () {\n      if (!toastPlacement.dataset.originalClass) {\n        toastPlacement.dataset.originalClass = toastPlacement.className\n      }\n\n      toastPlacement.className = `${toastPlacement.dataset.originalClass} ${this.value}`\n    })\n  }\n\n  // Instantiate all toasts in a docs page only\n  document.querySelectorAll('.bd-example .toast')\n    .forEach(toastNode => {\n      const toast = new bootstrap.Toast(toastNode, {\n        autohide: false\n      })\n\n      toast.show()\n    })\n\n  // Instantiate all toasts in a docs page only\n  const toastTrigger = document.getElementById('liveToastBtn')\n  const toastLiveExample = document.getElementById('liveToast')\n  if (toastTrigger) {\n    toastTrigger.addEventListener('click', () => {\n      const toast = new bootstrap.Toast(toastLiveExample)\n\n      toast.show()\n    })\n  }\n\n  // -------------------------------\n  // Alerts\n  // -------------------------------\n  // Used in 'Show live toast' example in docs or StackBlitz\n  const alertPlaceholder = document.getElementById('liveAlertPlaceholder')\n  const alertTrigger = document.getElementById('liveAlertBtn')\n\n  const appendAlert = (message, type) => {\n    const wrapper = document.createElement('div')\n    wrapper.innerHTML = [\n      `<div class=\"alert alert-${type} alert-dismissible\" role=\"alert\">`,\n      `   <div>${message}</div>`,\n      '   <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>',\n      '</div>'\n    ].join('')\n\n    alertPlaceholder.append(wrapper)\n  }\n\n  if (alertTrigger) {\n    alertTrigger.addEventListener('click', () => {\n      appendAlert('Nice, you triggered this alert message!', 'success')\n    })\n  }\n\n  // -------------------------------\n  // Checks & Radios\n  // -------------------------------\n  // Indeterminate checkbox example in docs and StackBlitz\n  document.querySelectorAll('.bd-example-indeterminate [type=\"checkbox\"]')\n    .forEach(checkbox => {\n      if (checkbox.id.includes('Indeterminate')) {\n        checkbox.indeterminate = true\n      }\n    })\n\n  // -------------------------------\n  // Links\n  // -------------------------------\n  // Disable empty links in docs examples only\n  document.querySelectorAll('.bd-content [href=\"#\"]')\n    .forEach(link => {\n      link.addEventListener('click', event => {\n        event.preventDefault()\n      })\n    })\n\n  // -------------------------------\n  // Modal\n  // -------------------------------\n  // Modal 'Varying modal content' example in docs and StackBlitz\n  const exampleModal = document.getElementById('exampleModal')\n  if (exampleModal) {\n    exampleModal.addEventListener('show.bs.modal', event => {\n      // Button that triggered the modal\n      const button = event.relatedTarget\n      // Extract info from data-bs-* attributes\n      const recipient = button.getAttribute('data-bs-whatever')\n\n      // Update the modal's content.\n      const modalTitle = exampleModal.querySelector('.modal-title')\n      const modalBodyInput = exampleModal.querySelector('.modal-body input')\n\n      modalTitle.textContent = `New message to ${recipient}`\n      modalBodyInput.value = recipient\n    })\n  }\n\n  // -------------------------------\n  // Offcanvas\n  // -------------------------------\n  // 'Offcanvas components' example in docs only\n  const myOffcanvas = document.querySelectorAll('.bd-example-offcanvas .offcanvas')\n  if (myOffcanvas) {\n    myOffcanvas.forEach(offcanvas => {\n      offcanvas.addEventListener('show.bs.offcanvas', event => {\n        event.preventDefault()\n      }, false)\n    })\n  }\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_ads.scss",
    "content": "// stylelint-disable declaration-no-important, selector-max-id\n\n//\n// Carbon ads\n//\n\n#carbonads {\n  position: static;\n  display: block;\n  max-width: 400px;\n  padding: 15px 15px 15px 160px;\n  margin: 2rem 0;\n  overflow: hidden;\n  @include font-size(.8125rem);\n  line-height: 1.4;\n  text-align: left;\n  background-color: $gray-100;\n\n  a {\n    color: $gray-800;\n    text-decoration: none;\n  }\n\n  @include media-breakpoint-up(sm) {\n    @include border-radius(.5rem);\n  }\n}\n\n.carbon-img {\n  float: left;\n  margin-left: -145px;\n}\n\n.carbon-poweredby {\n  display: block;\n  margin-top: .75rem;\n  color: $gray-700 !important;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_anchor.scss",
    "content": ".anchor-link {\n  padding: 0 .175rem;\n  font-weight: 400;\n  color: rgba($link-color, .5);\n  text-decoration: none;\n  opacity: 0;\n  @include transition(color .15s ease-in-out, opacity .15s ease-in-out);\n\n  &::after {\n    content: \"#\";\n  }\n\n  &:focus,\n  &:hover,\n  :hover > &,\n  :target > & {\n    color: $link-color;\n    text-decoration: none;\n    opacity: 1;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_brand.scss",
    "content": "//\n// Brand guidelines\n//\n\n// Logo series wrapper\n.bd-brand-logos {\n  color: $bd-violet;\n\n  .inverse {\n    color: $white;\n    background-color: $bd-violet;\n  }\n}\n\n// Individual items\n.bd-brand-item {\n  + .bd-brand-item {\n    border-top: 1px solid $white;\n  }\n\n  @include media-breakpoint-up(md) {\n    + .bd-brand-item {\n      border-top: 0;\n      border-left: 1px solid $white;\n    }\n  }\n}\n\n\n//\n// Color swatches\n//\n\n.color-swatches {\n  margin: 0 -5px;\n\n  // Docs colors\n  .bd-purple {\n    background-color: $bd-purple;\n  }\n  .bd-purple-light {\n    background-color: $bd-purple-light;\n  }\n  .bd-purple-lighter {\n    background-color: #e5e1ea;\n  }\n  .bd-gray {\n    background-color: #f9f9f9;\n  }\n}\n\n.color-swatch {\n  width: 4rem;\n  height: 4rem;\n\n  @include media-breakpoint-up(md) {\n    width: 6rem;\n    height: 6rem;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_buttons.scss",
    "content": "// Buttons\n//\n// Custom buttons for the docs.\n\n// scss-docs-start btn-css-vars-example\n.btn-bd-primary {\n  --bs-btn-font-weight: 600;\n  --bs-btn-color: var(--bs-white);\n  --bs-btn-bg: var(--bd-violet);\n  --bs-btn-border-color: var(--bd-violet);\n  --bs-btn-border-radius: .5rem;\n  --bs-btn-hover-color: var(--bs-white);\n  --bs-btn-hover-bg: #{shade-color($bd-violet, 10%)};\n  --bs-btn-hover-border-color: #{shade-color($bd-violet, 10%)};\n  --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);\n  --bs-btn-active-color: var(--bs-btn-hover-color);\n  --bs-btn-active-bg: #{shade-color($bd-violet, 20%)};\n  --bs-btn-active-border-color: #{shade-color($bd-violet, 20%)};\n}\n// scss-docs-end btn-css-vars-example\n\n.btn-bd-accent {\n  --bs-btn-font-weight: 600;\n  --bs-btn-color: var(--bd-accent);\n  --bs-btn-border-color: var(--bd-accent);\n  --bs-btn-hover-color: var(--bd-dark);\n  --bs-btn-hover-bg: var(--bd-accent);\n  --bs-btn-hover-border-color: var(--bd-accent);\n  --bs-btn-focus-shadow-rgb: var(--bd-accent-rgb);\n  --bs-btn-active-color: var(--bs-btn-hover-color);\n  --bs-btn-active-bg: var(--bs-btn-hover-bg);\n  --bs-btn-active-border-color: var(--bs-btn-hover-border-color);\n}\n\n.btn-bd-light {\n  --bs-btn-color: var(--bs-gray-600);\n  --bs-btn-border-color: var(--bs-gray-400);\n  --bs-btn-hover-color: var(--bd-violet);\n  --bs-btn-hover-border-color: var(--bd-violet);\n  --bs-btn-active-color: var(--bd-violet);\n  --bs-btn-active-bg: var(--bs-white);\n  --bs-btn-active-border-color: var(--bd-violet);\n  --bs-btn-focus-border-color: var(--bd-violet);\n  --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_callouts.scss",
    "content": "//\n// Callouts\n//\n\n.bd-callout {\n  padding: 1.25rem;\n  margin-top: 1.25rem;\n  margin-bottom: 1.25rem;\n  background-color: var(--bd-callout-bg, var(--bs-gray-100));\n  border-left: .25rem solid var(--bd-callout-border, var(--bs-gray-300));\n\n  h4 {\n    margin-bottom: .25rem;\n  }\n\n  > :last-child {\n    margin-bottom: 0;\n  }\n\n  + .bd-callout {\n    margin-top: -.25rem;\n  }\n\n  .highlight {\n    background-color: rgba($black, .05);\n  }\n}\n\n// Variations\n@each $variant in $bd-callout-variants {\n  .bd-callout-#{$variant} {\n    --bd-callout-bg: rgba(var(--bs-#{$variant}-rgb), .075);\n    --bd-callout-border: rgba(var(--bs-#{$variant}-rgb), .5);\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_clipboard-js.scss",
    "content": "// clipboard.js\n//\n// JS-based `Copy` buttons for code snippets.\n\n.bd-clipboard,\n.bd-edit {\n  position: relative;\n  display: none;\n  float: right;\n\n  + .highlight {\n    margin-top: 0;\n  }\n\n  @include media-breakpoint-up(md) {\n    display: block;\n  }\n}\n\n.btn-clipboard,\n.btn-edit {\n  display: block;\n  padding: .5em;\n  line-height: 1;\n  color: $gray-900;\n  background-color: $gray-100;\n  border: 0;\n  @include border-radius(.25rem);\n\n  &:hover {\n    color: $primary;\n  }\n\n  &:focus {\n    z-index: 3;\n  }\n}\n\n.btn-clipboard {\n  position: relative;\n  z-index: 2;\n  margin-top: .75rem;\n  margin-right: .75rem;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_colors.scss",
    "content": "//\n// Docs color palette classes\n//\n\n@each $color, $value in map-merge($colors, (\"gray-500\": $gray-500)) {\n  .swatch-#{$color} {\n    color: color-contrast($value);\n    background-color: #{$value};\n\n    &::after {\n      $contrast-ratio: \"#{contrast-ratio($value, color-contrast($value))}\";\n      $against-white: \"#{contrast-ratio($value, $white)}\";\n      $against-black: \"#{contrast-ratio($value, $black)}\";\n      position: absolute;\n      top: 1rem;\n      right: 1rem;\n      padding-left: 1rem;\n      font-size: .75rem;\n      line-height: 1.35;\n      white-space: pre;\n      content:\n        str-slice($contrast-ratio, 1, 4) \"\\A\"\n        str-slice($against-white, 1, 4) \"\\A\"\n        str-slice($against-black, 1, 4);\n      background-color: $value;\n      background-image:\n        linear-gradient(\n          to bottom,\n          transparent .25rem,\n          color-contrast($value) .25rem .75rem,\n          transparent .75rem 1.25rem,\n          $white 1.25rem 1.75rem,\n          transparent 1.75rem 2.25rem,\n          $black 2.25rem 2.75rem,\n          transparent 2.75rem\n        );\n      background-repeat: no-repeat;\n      background-size: .5rem 100%;\n    }\n  }\n}\n\n// stylelint-disable declaration-block-single-line-max-declarations\n\n.bd-blue-100 { color: color-contrast($blue-100); background-color: $blue-100; }\n.bd-blue-200 { color: color-contrast($blue-200); background-color: $blue-200; }\n.bd-blue-300 { color: color-contrast($blue-300); background-color: $blue-300; }\n.bd-blue-400 { color: color-contrast($blue-400); background-color: $blue-400; }\n.bd-blue-500 { color: color-contrast($blue-500); background-color: $blue-500; }\n.bd-blue-600 { color: color-contrast($blue-600); background-color: $blue-600; }\n.bd-blue-700 { color: color-contrast($blue-700); background-color: $blue-700; }\n.bd-blue-800 { color: color-contrast($blue-800); background-color: $blue-800; }\n.bd-blue-900 { color: color-contrast($blue-900); background-color: $blue-900; }\n\n.bd-indigo-100 { color: color-contrast($indigo-100); background-color: $indigo-100; }\n.bd-indigo-200 { color: color-contrast($indigo-200); background-color: $indigo-200; }\n.bd-indigo-300 { color: color-contrast($indigo-300); background-color: $indigo-300; }\n.bd-indigo-400 { color: color-contrast($indigo-400); background-color: $indigo-400; }\n.bd-indigo-500 { color: color-contrast($indigo-500); background-color: $indigo-500; }\n.bd-indigo-600 { color: color-contrast($indigo-600); background-color: $indigo-600; }\n.bd-indigo-700 { color: color-contrast($indigo-700); background-color: $indigo-700; }\n.bd-indigo-800 { color: color-contrast($indigo-800); background-color: $indigo-800; }\n.bd-indigo-900 { color: color-contrast($indigo-900); background-color: $indigo-900; }\n\n.bd-purple-100 { color: color-contrast($purple-100); background-color: $purple-100; }\n.bd-purple-200 { color: color-contrast($purple-200); background-color: $purple-200; }\n.bd-purple-300 { color: color-contrast($purple-300); background-color: $purple-300; }\n.bd-purple-400 { color: color-contrast($purple-400); background-color: $purple-400; }\n.bd-purple-500 { color: color-contrast($purple-500); background-color: $purple-500; }\n.bd-purple-600 { color: color-contrast($purple-600); background-color: $purple-600; }\n.bd-purple-700 { color: color-contrast($purple-700); background-color: $purple-700; }\n.bd-purple-800 { color: color-contrast($purple-800); background-color: $purple-800; }\n.bd-purple-900 { color: color-contrast($purple-900); background-color: $purple-900; }\n\n.bd-pink-100 { color: color-contrast($pink-100); background-color: $pink-100; }\n.bd-pink-200 { color: color-contrast($pink-200); background-color: $pink-200; }\n.bd-pink-300 { color: color-contrast($pink-300); background-color: $pink-300; }\n.bd-pink-400 { color: color-contrast($pink-400); background-color: $pink-400; }\n.bd-pink-500 { color: color-contrast($pink-500); background-color: $pink-500; }\n.bd-pink-600 { color: color-contrast($pink-600); background-color: $pink-600; }\n.bd-pink-700 { color: color-contrast($pink-700); background-color: $pink-700; }\n.bd-pink-800 { color: color-contrast($pink-800); background-color: $pink-800; }\n.bd-pink-900 { color: color-contrast($pink-900); background-color: $pink-900; }\n\n.bd-red-100 { color: color-contrast($red-100); background-color: $red-100; }\n.bd-red-200 { color: color-contrast($red-200); background-color: $red-200; }\n.bd-red-300 { color: color-contrast($red-300); background-color: $red-300; }\n.bd-red-400 { color: color-contrast($red-400); background-color: $red-400; }\n.bd-red-500 { color: color-contrast($red-500); background-color: $red-500; }\n.bd-red-600 { color: color-contrast($red-600); background-color: $red-600; }\n.bd-red-700 { color: color-contrast($red-700); background-color: $red-700; }\n.bd-red-800 { color: color-contrast($red-800); background-color: $red-800; }\n.bd-red-900 { color: color-contrast($red-900); background-color: $red-900; }\n\n.bd-orange-100 { color: color-contrast($orange-100); background-color: $orange-100; }\n.bd-orange-200 { color: color-contrast($orange-200); background-color: $orange-200; }\n.bd-orange-300 { color: color-contrast($orange-300); background-color: $orange-300; }\n.bd-orange-400 { color: color-contrast($orange-400); background-color: $orange-400; }\n.bd-orange-500 { color: color-contrast($orange-500); background-color: $orange-500; }\n.bd-orange-600 { color: color-contrast($orange-600); background-color: $orange-600; }\n.bd-orange-700 { color: color-contrast($orange-700); background-color: $orange-700; }\n.bd-orange-800 { color: color-contrast($orange-800); background-color: $orange-800; }\n.bd-orange-900 { color: color-contrast($orange-900); background-color: $orange-900; }\n\n.bd-yellow-100 { color: color-contrast($yellow-100); background-color: $yellow-100; }\n.bd-yellow-200 { color: color-contrast($yellow-200); background-color: $yellow-200; }\n.bd-yellow-300 { color: color-contrast($yellow-300); background-color: $yellow-300; }\n.bd-yellow-400 { color: color-contrast($yellow-400); background-color: $yellow-400; }\n.bd-yellow-500 { color: color-contrast($yellow-500); background-color: $yellow-500; }\n.bd-yellow-600 { color: color-contrast($yellow-600); background-color: $yellow-600; }\n.bd-yellow-700 { color: color-contrast($yellow-700); background-color: $yellow-700; }\n.bd-yellow-800 { color: color-contrast($yellow-800); background-color: $yellow-800; }\n.bd-yellow-900 { color: color-contrast($yellow-900); background-color: $yellow-900; }\n\n.bd-green-100 { color: color-contrast($green-100); background-color: $green-100; }\n.bd-green-200 { color: color-contrast($green-200); background-color: $green-200; }\n.bd-green-300 { color: color-contrast($green-300); background-color: $green-300; }\n.bd-green-400 { color: color-contrast($green-400); background-color: $green-400; }\n.bd-green-500 { color: color-contrast($green-500); background-color: $green-500; }\n.bd-green-600 { color: color-contrast($green-600); background-color: $green-600; }\n.bd-green-700 { color: color-contrast($green-700); background-color: $green-700; }\n.bd-green-800 { color: color-contrast($green-800); background-color: $green-800; }\n.bd-green-900 { color: color-contrast($green-900); background-color: $green-900; }\n\n.bd-teal-100 { color: color-contrast($teal-100); background-color: $teal-100; }\n.bd-teal-200 { color: color-contrast($teal-200); background-color: $teal-200; }\n.bd-teal-300 { color: color-contrast($teal-300); background-color: $teal-300; }\n.bd-teal-400 { color: color-contrast($teal-400); background-color: $teal-400; }\n.bd-teal-500 { color: color-contrast($teal-500); background-color: $teal-500; }\n.bd-teal-600 { color: color-contrast($teal-600); background-color: $teal-600; }\n.bd-teal-700 { color: color-contrast($teal-700); background-color: $teal-700; }\n.bd-teal-800 { color: color-contrast($teal-800); background-color: $teal-800; }\n.bd-teal-900 { color: color-contrast($teal-900); background-color: $teal-900; }\n\n.bd-cyan-100 { color: color-contrast($cyan-100); background-color: $cyan-100; }\n.bd-cyan-200 { color: color-contrast($cyan-200); background-color: $cyan-200; }\n.bd-cyan-300 { color: color-contrast($cyan-300); background-color: $cyan-300; }\n.bd-cyan-400 { color: color-contrast($cyan-400); background-color: $cyan-400; }\n.bd-cyan-500 { color: color-contrast($cyan-500); background-color: $cyan-500; }\n.bd-cyan-600 { color: color-contrast($cyan-600); background-color: $cyan-600; }\n.bd-cyan-700 { color: color-contrast($cyan-700); background-color: $cyan-700; }\n.bd-cyan-800 { color: color-contrast($cyan-800); background-color: $cyan-800; }\n.bd-cyan-900 { color: color-contrast($cyan-900); background-color: $cyan-900; }\n\n.bd-gray-100 { color: color-contrast($gray-100); background-color: $gray-100; }\n.bd-gray-200 { color: color-contrast($gray-200); background-color: $gray-200; }\n.bd-gray-300 { color: color-contrast($gray-300); background-color: $gray-300; }\n.bd-gray-400 { color: color-contrast($gray-400); background-color: $gray-400; }\n.bd-gray-500 { color: color-contrast($gray-500); background-color: $gray-500; }\n.bd-gray-600 { color: color-contrast($gray-600); background-color: $gray-600; }\n.bd-gray-700 { color: color-contrast($gray-700); background-color: $gray-700; }\n.bd-gray-800 { color: color-contrast($gray-800); background-color: $gray-800; }\n.bd-gray-900 { color: color-contrast($gray-900); background-color: $gray-900; }\n\n.bd-white { color: color-contrast($white); background-color: $white; }\n.bd-black { color: color-contrast($black); background-color: $black; }\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_component-examples.scss",
    "content": "//\n// Docs examples\n//\n\n.bd-example-snippet {\n  border: solid $border-color;\n  border-width: 1px 0;\n\n  @include media-breakpoint-up(md) {\n    border-width: 1px;\n  }\n}\n\n.bd-example {\n  --bd-example-padding: 1rem;\n\n  position: relative;\n  padding: var(--bd-example-padding);\n  margin: 0 ($bd-gutter-x * -.5);\n  border: solid $border-color;\n  border-width: 1px 0;\n  @include clearfix();\n\n  @include media-breakpoint-up(md) {\n    --bd-example-padding: 1.5rem;\n\n    margin-right: 0;\n    margin-left: 0;\n    border-width: 1px;\n    @include border-top-radius(var(--bs-border-radius));\n  }\n\n  + .bd-code-snippet {\n    @include border-top-radius(0);\n    border: solid $border-color;\n    border-width: 0 1px 1px;\n  }\n\n  + p {\n    margin-top: 2rem;\n  }\n\n  > .form-control {\n    + .form-control {\n      margin-top: .5rem;\n    }\n  }\n\n  > .nav + .nav,\n  > .alert + .alert,\n  > .navbar + .navbar,\n  > .progress + .progress {\n    margin-top: $spacer;\n  }\n\n  > .dropdown-menu {\n    position: static;\n    display: block;\n  }\n\n  > :last-child {\n    margin-bottom: 0;\n  }\n\n  > hr:last-child {\n    margin-bottom: $spacer;\n  }\n\n  // Images\n  > svg + svg,\n  > img + img {\n    margin-left: .5rem;\n  }\n\n  // Buttons\n  > .btn,\n  > .btn-group {\n    margin: .25rem .125rem;\n  }\n  > .btn-toolbar + .btn-toolbar {\n    margin-top: .5rem;\n  }\n\n  // List groups\n  > .list-group {\n    max-width: 400px;\n  }\n\n  > [class*=\"list-group-horizontal\"] {\n    max-width: 100%;\n  }\n\n  // Navbars\n  .fixed-top,\n  .sticky-top {\n    position: static;\n    margin: calc(var(--bd-example-padding) * -1) calc(var(--bd-example-padding) * -1) var(--bd-example-padding); // stylelint-disable-line function-disallowed-list\n  }\n\n  .fixed-bottom,\n  .sticky-bottom {\n    position: static;\n    margin: var(--bd-example-padding) calc(var(--bd-example-padding) * -1) calc(var(--bd-example-padding) * -1); // stylelint-disable-line function-disallowed-list\n\n  }\n\n  // Pagination\n  .pagination {\n    margin-bottom: 0;\n  }\n}\n\n//\n// Grid examples\n//\n\n.bd-example-row [class^=\"col\"],\n.bd-example-cssgrid .grid > * {\n  padding-top: .75rem;\n  padding-bottom: .75rem;\n  background-color: rgba(var(--bd-violet-rgb), .1);\n  border: 1px solid rgba(var(--bd-violet-rgb), .25);\n}\n\n.bd-example-row .row + .row,\n.bd-example-cssgrid .grid + .grid {\n  margin-top: 1rem;\n}\n\n.bd-example-row-flex-cols .row {\n  min-height: 10rem;\n  background-color: rgba(255, 0, 0, .1);\n}\n\n.bd-example-flex div {\n  background-color: rgba($bd-purple, .15);\n  border: 1px solid rgba($bd-purple, .15);\n}\n\n// Grid mixins\n.example-container {\n  width: 800px;\n  @include make-container();\n}\n\n.example-row {\n  @include make-row();\n}\n\n.example-content-main {\n  @include make-col-ready();\n\n  @include media-breakpoint-up(sm) {\n    @include make-col(6);\n  }\n\n  @include media-breakpoint-up(lg) {\n    @include make-col(8);\n  }\n}\n\n.example-content-secondary {\n  @include make-col-ready();\n\n  @include media-breakpoint-up(sm) {\n    @include make-col(6);\n  }\n\n  @include media-breakpoint-up(lg) {\n    @include make-col(4);\n  }\n}\n\n// Ratio helpers\n.bd-example-ratios {\n  .ratio {\n    display: inline-block;\n    width: 10rem;\n    color: $gray-600;\n    background-color: $gray-100;\n    border: var(--#{$prefix}border-width) solid var(--#{$prefix}border-color);\n\n    > div {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    }\n  }\n}\n.bd-example-ratios-breakpoint {\n  .ratio-4x3 {\n    width: 16rem;\n\n    @include media-breakpoint-up(md) {\n      --bs-aspect-ratio: 50%; // 2x1\n    }\n  }\n}\n\n.bd-example-offcanvas {\n  .offcanvas {\n    position: static;\n    display: block;\n    height: 200px;\n    visibility: visible;\n    transform: translate(0);\n  }\n}\n\n// Tooltips\n.tooltip-demo a {\n  white-space: nowrap;\n}\n\n// scss-docs-start custom-tooltip\n.custom-tooltip {\n  --bs-tooltip-bg: var(--bs-primary);\n}\n// scss-docs-end custom-tooltip\n\n// scss-docs-start custom-popovers\n.custom-popover {\n  --bs-popover-max-width: 200px;\n  --bs-popover-border-color: var(--bs-primary);\n  --bs-popover-header-bg: var(--bs-primary);\n  --bs-popover-header-color: var(--bs-white);\n  --bs-popover-body-padding-x: 1rem;\n  --bs-popover-body-padding-y: .5rem;\n}\n// scss-docs-end custom-popovers\n\n// Scrollspy demo on fixed height div\n.scrollspy-example {\n  height: 200px;\n  margin-top: .5rem;\n  overflow: auto;\n}\n\n.scrollspy-example-2 {\n  height: 350px;\n  overflow: auto;\n}\n\n.simple-list-example-scrollspy {\n  .active {\n    background-color: rgba(var(--bd-violet-rgb), .15);\n  }\n}\n\n.bd-example-border-utils {\n  [class^=\"border\"] {\n    display: inline-block;\n    width: 5rem;\n    height: 5rem;\n    margin: .25rem;\n    background-color: #f5f5f5;\n  }\n}\n\n.bd-example-rounded-utils {\n  [class*=\"rounded\"] {\n    margin: .25rem;\n  }\n}\n\n.bd-example-position-utils {\n  position: relative;\n  padding: 2rem;\n\n  .position-relative {\n    height: 200px;\n    background-color: #f5f5f5;\n  }\n\n  .position-absolute {\n    width: 2rem;\n    height: 2rem;\n    background-color: $dark;\n    @include border-radius();\n  }\n}\n\n.bd-example-position-examples {\n  &::after {\n    content: none;\n  }\n}\n\n// Placeholders\n.bd-example-placeholder-cards {\n  &::after {\n    display: none;\n  }\n\n  .card {\n    width: 18rem;\n  }\n}\n\n// Toasts\n.bd-example-toasts {\n  min-height: 240px;\n}\n\n//\n// Code snippets\n//\n\n.highlight {\n  position: relative;\n  padding: .75rem ($bd-gutter-x * .5);\n  margin-bottom: 1rem;\n  background-color: var(--bs-gray-100);\n\n  @include media-breakpoint-up(md) {\n    padding: .75rem 1.25rem;\n    @include border-radius(var(--bs-border-radius));\n  }\n\n  pre {\n    padding: 0;\n    margin-top: .625rem;\n    margin-right: 1.875rem;\n    margin-bottom: .625rem;\n    white-space: pre;\n    background-color: transparent;\n    border: 0;\n  }\n\n  pre code {\n    @include font-size(inherit);\n    color: $gray-900; // Effectively the base text color\n    word-wrap: normal;\n  }\n}\n\n.bd-code-snippet {\n  margin: 0 ($bd-gutter-x * -.5) $spacer;\n\n  .highlight {\n    margin-bottom: 0;\n  }\n\n  .bd-example {\n    margin: 0;\n    border: 0;\n  }\n\n  @include media-breakpoint-up(md) {\n    margin-right: 0;\n    margin-left: 0;\n    @include border-radius($border-radius);\n  }\n}\n\n.highlight-toolbar {\n  border: solid $border-color;\n  border-width: 1px 0;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_content.scss",
    "content": "//\n// Bootstrap docs content theming\n//\n\n.bd-content {\n  // Offset content from fixed navbar when jumping to headings\n  > :target {\n    padding-top: 5rem;\n    margin-top: -5rem;\n  }\n\n  > h2:not(:first-child) {\n    margin-top: 3rem;\n  }\n\n  > h3 {\n    margin-top: 2rem;\n  }\n\n  > ul li,\n  > ol li {\n    margin-bottom: .25rem;\n\n    // stylelint-disable selector-max-type, selector-max-compound-selectors\n    > p ~ ul {\n      margin-top: -.5rem;\n      margin-bottom: 1rem;\n    }\n    // stylelint-enable selector-max-type, selector-max-compound-selectors\n  }\n\n  // Override Bootstrap defaults\n  > .table,\n  > .table-responsive .table {\n    margin-bottom: 1.5rem;\n    @include font-size(.875rem);\n\n    @include media-breakpoint-down(lg) {\n      &.table-bordered {\n        border: 0;\n      }\n    }\n\n    thead {\n      border-bottom: 2px solid currentcolor;\n    }\n\n    tbody:not(:first-child) {\n      border-top: 2px solid currentcolor;\n    }\n\n    th,\n    td {\n      &:first-child {\n        padding-left: 0;\n      }\n\n      &:not(:last-child) {\n        padding-right: 1.5rem;\n      }\n    }\n\n    // Prevent breaking of code\n    // stylelint-disable-next-line selector-max-compound-selectors\n    th,\n    td:first-child > code {\n      white-space: nowrap;\n    }\n  }\n}\n\n.table-options {\n  td:nth-child(2) {\n    min-width: 160px;\n  }\n}\n\n.table-options td:last-child,\n.table-utilities td:last-child {\n  min-width: 280px;\n}\n\n.bd-title {\n  @include font-size(3rem);\n}\n\n.bd-lead {\n  @include font-size(1.5rem);\n  font-weight: 300;\n}\n\n.bd-bg-violet {\n  background-color: $bd-violet;\n}\n\n.bi {\n  width: 1em;\n  height: 1em;\n  fill: currentcolor;\n}\n\n.icon-link {\n  display: flex;\n  align-items: center;\n  text-decoration-color: rgba($primary, .5);\n  text-underline-offset: .5rem;\n  backface-visibility: hidden;\n\n  .bi {\n    width: 1.5em;\n    height: 1.5em;\n    transition: .2s ease-in-out transform; // stylelint-disable-line property-disallowed-list\n  }\n\n  &:hover {\n    .bi {\n      transform: translate3d(5px, 0, 0);\n    }\n  }\n}\n\n.border-lg-start {\n  @include media-breakpoint-up(lg) {\n    border-left: $border-width solid $border-color;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_footer.scss",
    "content": "//\n// Footer\n//\n\n.bd-footer {\n  a {\n    color: $gray-700;\n    text-decoration: none;\n\n    &:hover,\n    &:focus {\n      color: $link-color;\n      text-decoration: underline;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_layout.scss",
    "content": ".bd-gutter {\n  --bs-gutter-x: #{$bd-gutter-x};\n}\n\n.bd-layout {\n\n  @include media-breakpoint-up(lg) {\n    display: grid;\n    grid-template-areas: \"sidebar main\";\n    grid-template-columns: 1fr 5fr;\n    gap: $grid-gutter-width;\n  }\n}\n\n.bd-sidebar {\n  grid-area: sidebar;\n}\n\n.bd-main {\n  grid-area: main;\n\n  @include media-breakpoint-down(lg) {\n    max-width: 760px;\n    margin-inline: auto;\n  }\n\n  @include media-breakpoint-up(md) {\n    display: grid;\n    grid-template-areas:\n      \"intro\"\n      \"toc\"\n      \"content\";\n    grid-template-rows: auto auto 1fr;\n    gap: inherit;\n  }\n\n  @include media-breakpoint-up(lg) {\n    grid-template-areas:\n      \"intro   toc\"\n      \"content toc\";\n    grid-template-rows: auto 1fr;\n    grid-template-columns: 4fr 1fr;\n  }\n}\n\n.bd-intro {\n  grid-area: intro;\n}\n\n.bd-toc {\n  grid-area: toc;\n}\n\n.bd-content {\n  grid-area: content;\n  min-width: 1px; // Fix width when bd-content contains a `<pre>` https://github.com/twbs/bootstrap/issues/25410\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_masthead.scss",
    "content": ".bd-masthead {\n  --bd-pink-rgb: #{to-rgb($pink)};\n  padding: 3rem 0;\n  // stylelint-disable\n  background-image: linear-gradient(180deg, rgba(var(--bs-body-bg-rgb), .01), rgba(var(--bs-body-bg-rgb), 1) 85%),\n                    radial-gradient(ellipse at top left, rgba(var(--bs-primary-rgb), .5), transparent 50%),\n                    radial-gradient(ellipse at top right, rgba(var(--bd-accent-rgb), .5), transparent 50%),\n                    radial-gradient(ellipse at center right, rgba(var(--bd-violet-rgb), .5), transparent 50%),\n                    radial-gradient(ellipse at center left, rgba(var(--bd-pink-rgb), .5), transparent 50%);\n  // stylelint-enable\n\n  h1 {\n    @include font-size(4rem);\n    line-height: 1;\n  }\n\n  .lead {\n    @include font-size(1rem);\n    font-weight: 400;\n    color: $gray-700;\n  }\n\n  .bd-code-snippet {\n    margin: 0;\n    @include border-radius(.5rem);\n  }\n\n  .highlight {\n    width: 100%;\n    padding: .5rem 1rem;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    background-color: rgba(var(--bs-body-color-rgb), .075);\n    @include border-radius(.5rem);\n\n    @include media-breakpoint-up(lg) {\n      padding-right: 4rem;\n    }\n  }\n  .btn-clipboard {\n    position: absolute;\n    top: -.125rem;\n    right: 0;\n    background-color: transparent;\n  }\n\n  #carbonads { // stylelint-disable-line selector-max-id\n    margin-right: auto;\n    margin-left: auto;\n  }\n\n  @include media-breakpoint-up(md) {\n    .lead {\n      @include font-size(1.5rem);\n    }\n  }\n}\n\n.masthead-followup {\n  .lead {\n    @include font-size(1rem);\n  }\n\n  .highlight {\n    @include border-radius(.5rem);\n  }\n\n  @include media-breakpoint-up(md) {\n    .lead {\n      @include font-size(1.25rem);\n    }\n  }\n}\n\n.bd-btn-lg {\n  padding: .8rem 2rem;\n}\n\n.masthead-followup-icon {\n  padding: 1rem;\n  color: rgba(var(--bg-rgb), 1);\n  background-color: rgba(var(--bg-rgb), .1);\n  background-blend-mode: multiple;\n  @include border-radius(1rem);\n  mix-blend-mode: darken;\n\n  svg {\n    filter: drop-shadow(0 1px 1px #fff);\n  }\n}\n\n.masthead-notice {\n  background-color: var(--bd-accent);\n  box-shadow: inset 0 -1px 1px rgba(var(--bs-body-color-rgb), .15), 0 .25rem 1.5rem rgba(var(--bs-body-bg-rgb), .75);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_navbar.scss",
    "content": ".bd-navbar {\n  padding: .75rem 0;\n  background-color: transparent;\n  background-image: linear-gradient(to bottom, rgba(var(--bd-violet-rgb), 1), rgba(var(--bd-violet-rgb), .95));\n  box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15), inset 0 -1px 0 rgba(0, 0, 0, .15);\n\n  .bd-navbar-toggle {\n    @include media-breakpoint-down(lg) {\n      width: 4.25rem;\n    }\n  }\n\n  .navbar-toggler {\n    padding: 0;\n    margin-right: -.5rem;\n    border: 0;\n\n    &:first-child {\n      margin-left: -.5rem;\n    }\n\n    .bi {\n      width: 1.5rem;\n      height: 1.5rem;\n    }\n\n    &:focus {\n      box-shadow: none;\n    }\n  }\n\n  .navbar-brand {\n    transition: .2s ease-in-out transform; // stylelint-disable-line property-disallowed-list\n\n    &:hover {\n      transform: rotate(-5deg) scale(1.1);\n    }\n  }\n\n  .navbar-toggler,\n  .nav-link {\n    padding-right: $spacer * .25;\n    padding-left: $spacer * .25;\n    color: rgba($white, .85);\n\n    &:hover,\n    &:focus {\n      color: $white;\n    }\n\n    &.active {\n      font-weight: 600;\n      color: $white;\n    }\n  }\n\n  .navbar-nav-svg {\n    display: inline-block;\n    vertical-align: -.125rem;\n  }\n\n  .offcanvas-lg {\n    background-color: var(--bd-violet);\n    border-left: 0;\n\n    @include media-breakpoint-down(lg) {\n      box-shadow: $box-shadow-lg;\n    }\n  }\n\n  .dropdown-toggle {\n    &:focus:not(:focus-visible) {\n      outline: 0;\n    }\n  }\n\n  .dropdown-menu {\n    --#{$prefix}dropdown-min-width: 12rem;\n    --#{$prefix}dropdown-link-hover-bg: rgba(var(--bd-violet-rgb), .1);\n    @include rfs(.875rem, --#{$prefix}dropdown-font-size);\n    box-shadow: $dropdown-box-shadow;\n  }\n\n  .dropdown-item.current {\n    font-weight: 600;\n    background-image: escape-svg($dropdown-active-icon);\n    background-repeat: no-repeat;\n    background-position: right $dropdown-item-padding-x top .6rem;\n    background-size: .75rem .75rem;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_placeholder-img.scss",
    "content": "//\n// Placeholder svg used in the docs.\n//\n\n// Remember to update `site/_layouts/examples.html` too if this changes!\n\n.bd-placeholder-img {\n  @include font-size(1.125rem);\n  user-select: none;\n  text-anchor: middle;\n}\n\n.bd-placeholder-img-lg {\n  @include font-size(3.5rem);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_search.scss",
    "content": "// stylelint-disable selector-class-pattern\n\n.bd-search {\n  position: relative;\n\n  @include media-breakpoint-up(lg) {\n    position: absolute;\n    top: .875rem;\n    left: 50%;\n    width: 200px;\n    margin-left: -100px;\n  }\n\n  @include media-breakpoint-up(xl) {\n    width: 280px;\n    margin-left: -140px;\n  }\n\n}\n\n.DocSearch-Container {\n  --docsearch-muted-color: #{$text-muted};\n  --docsearch-hit-shadow: none;\n\n  z-index: 2000; // Make sure to be over all components showcased in the documentation\n  cursor: auto; // Needed because of [role=\"button\"] in Algolia search modal. Remove once https://github.com/algolia/docsearch/issues/1370 is tackled.\n\n  @include media-breakpoint-up(lg) {\n    padding-top: 4rem;\n  }\n}\n\n.DocSearch-Button {\n  --docsearch-searchbox-background: #{rgba($black, .1)};\n  --docsearch-searchbox-color: #{$white};\n  --docsearch-searchbox-focus-background: #{rgba($black, .25)};\n  --docsearch-searchbox-shadow: #{0 0 0 .25rem rgba($bd-accent, .4)};\n  --docsearch-text-color: #{$white};\n  --docsearch-muted-color: #{rgba($white, .65)};\n\n  width: 100%;\n  height: 38px; // Match Bootstrap inputs\n  margin: 0;\n  border: 1px solid rgba($white, .4);\n  @include border-radius(.375rem);\n\n  .DocSearch-Search-Icon {\n    opacity: .65;\n  }\n\n  &:active,\n  &:focus,\n  &:hover {\n    border-color: rgba($bd-accent, 1);\n\n    .DocSearch-Search-Icon {\n      opacity: 1;\n    }\n  }\n\n  @include media-breakpoint-down(lg) {\n    &,\n    &:hover,\n    &:focus {\n      background: transparent;\n      border: 0;\n      box-shadow: none;\n    }\n    &:focus {\n      box-shadow: var(--docsearch-searchbox-shadow);\n    }\n  }\n}\n\n.DocSearch-Button-Keys,\n.DocSearch-Button-Placeholder {\n  @include media-breakpoint-down(lg) {\n    display: none;\n  }\n}\n\n.DocSearch-Button-Keys {\n  min-width: 0;\n  padding: .125rem .25rem;\n  background: rgba($black, .25);\n  @include border-radius(.25rem);\n}\n\n.DocSearch-Button-Key {\n  top: 0;\n  width: auto;\n  height: 1.25rem;\n  padding-right: .125rem;\n  padding-left: .125rem;\n  margin-right: 0;\n  font-size: .875rem;\n  background: none;\n  box-shadow: none;\n}\n\n.DocSearch-Commands-Key {\n  padding-left: 1px;\n  font-size: .875rem;\n  background-color: rgba($black, .1);\n  background-image: none;\n  box-shadow: none;\n}\n\n.DocSearch-Form {\n  @include border-radius(var(--bs-border-radius));\n}\n\n.DocSearch-Hits {\n  mark {\n    padding: 0;\n  }\n}\n\n.DocSearch-Hit {\n  padding-bottom: 0;\n  @include border-radius(0);\n\n  a {\n    @include border-radius(0);\n    border: solid var(--bs-border-color);\n    border-width: 0 1px 1px;\n  }\n\n  &:first-child a {\n    @include border-top-radius(var(--bs-border-radius));\n    border-top-width: 1px;\n  }\n  &:last-child a {\n    @include border-bottom-radius(var(--bs-border-radius));\n  }\n}\n\n.DocSearch-Hit-icon {\n  display: flex;\n  align-items: center;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_sidebar.scss",
    "content": ".bd-sidebar {\n  @include media-breakpoint-up(lg) {\n    position: sticky;\n    top: 5rem;\n    // Override collapse behaviors\n    // stylelint-disable-next-line declaration-no-important\n    display: block !important;\n    height: subtract(100vh, 6rem);\n    // Prevent focus styles to be cut off:\n    padding-left: .25rem;\n    margin-left: -.25rem;\n    overflow-y: auto;\n  }\n}\n\n.bd-links-nav {\n  @include media-breakpoint-down(lg) {\n    font-size: .875rem;\n  }\n\n  @include media-breakpoint-between(xs, lg) {\n    column-count: 2;\n    column-gap: 1.5rem;\n\n    .bd-links-group {\n      break-inside: avoid;\n    }\n\n    .bd-links-span-all {\n      column-span: all;\n    }\n  }\n}\n\n.bd-links-link {\n  padding: .1875rem .5rem;\n  margin-top: .125rem;\n  margin-left: 1rem;\n  color: rgba($black, .65);\n  text-decoration: if($link-decoration == none, null, none);\n\n  &:hover,\n  &:focus,\n  &.active {\n    color: rgba($black, .85);\n    text-decoration: if($link-hover-decoration == underline, none, null);\n    background-color: rgba(var(--bd-violet-rgb), .1);\n  }\n\n  &.active {\n    font-weight: 600;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_skippy.scss",
    "content": ".skippy {\n  background-color: $bd-purple;\n\n  a {\n    color: $white;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_syntax.scss",
    "content": ":root {\n  --base00: #fff;\n  --base01: #f5f5f5;\n  --base02: #c8c8fa;\n  --base03: #565c64;\n  --base04: #030303;\n  --base05: #333;\n  --base06: #fff;\n  --base07: #9a6700;\n  --base08: #bc4c00;\n  --base09: #087990;\n  --base0A: #795da3;\n  --base0B: #183691;\n  --base0C: #183691;\n  --base0D: #795da3;\n  --base0E: #a71d5d;\n  --base0F: #333;\n}\n\n.hl { background-color: var(--base02); }\n.c { color: var(--base03); }\n.err { color: var(--base08); }\n.k { color: var(--base0E); }\n.l { color: var(----base09); }\n.n { color: var(--base08); }\n.o { color: var(--base05); }\n.p { color: var(--base05); }\n.cm { color: var(--base04); }\n.cp { color: var(--base08); }\n.c1 { color: var(--base03); }\n.cs { color: var(--base04); }\n.gd { color: var(--base08); }\n.ge { font-style: italic; }\n.gh {\n  font-weight: 600;\n  color: #fff;\n}\n.gi { color: var(--bs-success); }\n.gp {\n  font-weight: 600;\n  color: var(--base04);\n}\n.gs { font-weight: 600; }\n.gu {\n  font-weight: 600;\n  color: var(--base0C);\n}\n.kc { color: var(--base0E); }\n.kd { color: var(--base0E); }\n.kn { color: var(--base0C); }\n.kp { color: var(--base0E); }\n.kr { color: var(--base0E); }\n.kt { color: var(--base0A); }\n.ld { color: var(--base0C); }\n.m { color: var(--base09); }\n.s { color: var(--base0C); }\n.na { color: var(--base0A); }\n.nb { color: var(--base05); }\n.nc { color: var(--base07); }\n.no { color: var(--base08); }\n.nd { color: var(--base07); }\n.ni { color: var(--base08); }\n.ne { color: var(--base08); }\n.nf { color: var(--base0B); }\n.nl { color: var(--base05); }\n.nn { color: var(--base0A); }\n.nx { color: var(--base0A); }\n.py { color: var(--base08); }\n.nt { color: var(--base08); }\n.nv { color: var(--base08); }\n.ow { color: var(--base0C); }\n.w { color: #fff; }\n.mf { color: var(--base09); }\n.mh { color: var(--base09); }\n.mi { color: var(--base09); }\n.mo { color: var(--base09); }\n.sb { color: var(--base0C); }\n.sc { color: #fff; }\n.sd { color: var(--base04); }\n.s2 { color: var(--base0C); }\n.se { color: var(--base09); }\n.sh { color: var(--base0C); }\n.si { color: var(--base09); }\n.sx { color: var(--base0C); }\n.sr { color: var(--base0C); }\n.s1 { color: var(--base0C); }\n.ss { color: var(--base0C); }\n.bp { color: var(--base05); }\n.vc { color: var(--base08); }\n.vg { color: var(--base08); }\n.vi { color: var(--base08); }\n.il { color: var(--base09); }\n\n// Color commas in rgba() values\n.m + .o { color: var(--base03); }\n\n// Fix bash\n.language-sh .c { color: var(--base03); }\n\n.chroma {\n  .language-bash,\n  .language-sh {\n    .line::before {\n      color: #777;\n      content: \"$ \";\n      user-select: none;\n    }\n  }\n\n  .language-powershell::before {\n    color: #009;\n    content: \"PM> \";\n    user-select: none;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_toc.scss",
    "content": "// stylelint-disable selector-max-type\n\n.bd-toc {\n  @include media-breakpoint-up(lg) {\n    position: sticky;\n    top: 5rem;\n    right: 0;\n    z-index: 2;\n    height: subtract(100vh, 7rem);\n    overflow-y: auto;\n  }\n\n  nav {\n    @include font-size(.875rem);\n\n    ul {\n      padding-left: 0;\n      margin-bottom: 0;\n      list-style: none;\n\n      ul {\n        padding-left: 1rem;\n        margin-top: .25rem;\n      }\n    }\n\n    li {\n      margin-bottom: .25rem;\n    }\n\n    a {\n      color: inherit;\n\n      &:not(:hover) {\n        text-decoration: none;\n      }\n\n      code {\n        font: inherit;\n      }\n    }\n  }\n}\n\n.bd-toc-toggle {\n  display: flex;\n  align-items: center;\n\n  @include media-breakpoint-down(sm) {\n    justify-content: space-between;\n    width: 100%;\n  }\n\n  @include media-breakpoint-down(md) {\n    border: 1px solid $border-color;\n    @include border-radius(.4rem);\n\n    &:hover,\n    &:focus,\n    &:active,\n    &[aria-expanded=\"true\"] {\n      color: var(--bd-violet);\n      background-color: $white;\n      border-color: var(--bd-violet);\n    }\n\n    &:focus,\n    &[aria-expanded=\"true\"] {\n      box-shadow: 0 0 0 3px rgba(var(--bd-violet-rgb), .25);\n    }\n  }\n}\n\n.bd-toc-collapse {\n  @include media-breakpoint-down(md) {\n    nav {\n      padding: 1.25rem;\n      background-color: var(--bs-gray-100);\n      border: 1px solid $border-color;\n      @include border-radius(.25rem);\n    }\n  }\n\n  @include media-breakpoint-up(md) {\n    display: block !important; // stylelint-disable-line declaration-no-important\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/_variables.scss",
    "content": "// stylelint-disable scss/dollar-variable-default\n\n// Local docs variables\n$bd-purple:        #4c0bce;\n$bd-violet:        lighten(saturate($bd-purple, 5%), 15%); // stylelint-disable-line function-disallowed-list\n$bd-purple-light:  lighten(saturate($bd-purple, 5%), 45%); // stylelint-disable-line function-disallowed-list\n$bd-accent:       #ffe484;\n$dropdown-active-icon: url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#292b2c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>\");\n\n$bd-gutter-x: 3rem;\n$bd-callout-variants: info, warning, danger !default;\n\n:root {\n  --bd-purple: #{$bd-purple};\n  --bd-violet: #{$bd-violet};\n  --bd-accent: #{$bd-accent};\n  --bd-violet-rgb: #{to-rgb($bd-violet)};\n  --bd-accent-rgb: #{to-rgb($bd-accent)};\n  --bd-pink-rgb: #{to-rgb($pink-500)};\n  --bd-teal-rgb: #{to-rgb($teal-500)};\n  --docsearch-primary-color: var(--bd-violet);\n  --docsearch-logo-color: var(--bd-violet);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/assets/scss/docs.scss",
    "content": "/*!\n * Bootstrap Docs (https://getbootstrap.com/)\n * Copyright 2011-2022 The Bootstrap Authors\n * Copyright 2011-2022 Twitter, Inc.\n * Licensed under the Creative Commons Attribution 3.0 Unported License.\n * For details, see https://creativecommons.org/licenses/by/3.0/.\n */\n\n// Dev notes\n//\n// Background information on nomenclature and architecture decisions here.\n//\n// - Bootstrap functions, variables, and mixins are included for easy reuse.\n//   Doing so gives us access to the same core utilities provided by Bootstrap.\n//   For example, consistent media queries through those mixins.\n//\n// - Bootstrap's **docs variables** are prefixed with `$bd-`.\n//   These custom colors avoid collision with the components Bootstrap provides.\n//\n// - Classes are prefixed with `.bd-`.\n//   These classes indicate custom-built or modified components for the design\n//   and layout of the Bootstrap docs. They are not included in our builds.\n//\n// Happy Bootstrapping!\n\n// Load Bootstrap variables and mixins\n@import \"../../../scss/functions\";\n@import \"../../../scss/variables\";\n@import \"../../../scss/mixins\";\n\n// fusv-disable\n$enable-grid-classes: false; // stylelint-disable-line scss/dollar-variable-default\n$enable-cssgrid: true; // stylelint-disable-line scss/dollar-variable-default\n// fusv-enable\n@import \"../../../scss/grid\";\n\n// Load docs components\n@import \"variables\";\n@import \"navbar\";\n@import \"search\";\n@import \"masthead\";\n@import \"ads\";\n@import \"content\";\n@import \"skippy\";\n@import \"sidebar\";\n@import \"layout\";\n@import \"toc\";\n@import \"footer\";\n@import \"component-examples\";\n@import \"buttons\";\n@import \"callouts\";\n@import \"brand\";\n@import \"colors\";\n@import \"clipboard-js\";\n@import \"placeholder-img\";\n\n// Load docs dependencies\n@import \"syntax\";\n@import \"anchor\";\n"
  },
  {
    "path": "src/common/bootstrap/site/content/404.md",
    "content": "---\ntitle: \"404 - File not found\"\nlayout: 404\ndescription: \"\"\nurl: /404.html\nrobots: noindex,follow\nsitemap_exclude: true\n---\n\n<div class=\"text-center py-5\">\n  <h1 class=\"display-1\">404</h1>\n  <h2>File not found</h2>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/_index.html",
    "content": "---\nlayout: redirect\nsitemap_exclude: true\nredirect: \"/docs/5.2/getting-started/introduction/\"\n---\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/about/brand.md",
    "content": "---\nlayout: docs\ntitle: Brand guidelines\ndescription: Documentation and examples for Bootstrap's logo and brand usage guidelines.\ngroup: about\ntoc: true\n---\n\nHave a need for Bootstrap's brand resources? Great! We have only a few guidelines we follow, and in turn ask you to follow as well.\n\n## Logo\n\nWhen referencing Bootstrap, use our logo mark. Do not modify our logos in any way. Do not use Bootstrap's branding for your own open or closed source projects. **Do not use the Twitter name or logo** in association with Bootstrap.\n\n<div class=\"bd-brand-item px-2 py-5 mb-3 bg-light rounded-lg\">\n  <img class=\"d-block img-fluid mx-auto\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"Bootstrap\" width=\"256\" height=\"204\">\n</div>\n\nOur logo mark is also available in black and white. All rules for our primary logo apply to these as well.\n\n<div class=\"bd-brand-logos d-sm-flex text-center bg-light rounded-lg overflow-hidden w-100 mb-3\">\n  <div class=\"bd-brand-item w-100 px-2 py-5\">\n    <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-black.svg\" alt=\"Bootstrap\" width=\"128\" height=\"102\" loading=\"lazy\">\n  </div>\n  <div class=\"bd-brand-item w-100 px-2 py-5 inverse\">\n    <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" alt=\"Bootstrap\" width=\"128\" height=\"102\" loading=\"lazy\">\n  </div>\n</div>\n\n## Name\n\nBootstrap should always be referred to as just **Bootstrap**. No Twitter before it and no capital _s_.\n\n<div class=\"bd-brand-logos d-sm-flex text-center bg-light rounded-lg overflow-hidden w-100 mb-3\">\n  <div class=\"bd-brand-item w-100 p-3\">\n    <div class=\"h3\">Bootstrap</div>\n    <strong class=\"text-success\">Correct</strong>\n  </div>\n  <div class=\"bd-brand-item w-100 p-3\">\n    <div class=\"h3 text-muted\">BootStrap</div>\n    <strong class=\"text-danger\">Incorrect</strong>\n  </div>\n  <div class=\"bd-brand-item w-100 p-3\">\n    <div class=\"h3 text-muted\">Twitter Bootstrap</div>\n    <strong class=\"text-danger\">Incorrect</strong>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/about/license.md",
    "content": "---\nlayout: docs\ntitle: License FAQs\ndescription: Commonly asked questions about Bootstrap's open source license.\ngroup: about\n---\n\nBootstrap is released under the MIT license and is copyright {{< year >}} Twitter. Boiled down to smaller chunks, it can be described with the following conditions.\n\n## It requires you to:\n\n- Keep the license and copyright notice included in Bootstrap's CSS and JavaScript files when you use them in your works\n\n## It permits you to:\n\n- Freely download and use Bootstrap, in whole or in part, for personal, private, company internal, or commercial purposes\n- Use Bootstrap in packages or distributions that you create\n- Modify the source code\n- Grant a sublicense to modify and distribute Bootstrap to third parties not included in the license\n\n## It forbids you to:\n\n- Hold the authors and license owners liable for damages as Bootstrap is provided without warranty\n- Hold the creators or copyright holders of Bootstrap liable\n- Redistribute any piece of Bootstrap without proper attribution\n- Use any marks owned by Twitter in any way that might state or imply that Twitter endorses your distribution\n- Use any marks owned by Twitter in any way that might state or imply that you created the Twitter software in question\n\n## It does not require you to:\n\n- Include the source of Bootstrap itself, or of any modifications you may have made to it, in any redistribution you may assemble that includes it\n- Submit changes that you make to Bootstrap back to the Bootstrap project (though such feedback is encouraged)\n\nThe full Bootstrap license is located [in the project repository]({{< param repo >}}/blob/v{{< param current_version >}}/LICENSE) for more information.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/about/overview.md",
    "content": "---\nlayout: docs\ntitle: About\ndescription: Learn more about the team maintaining Bootstrap, how and why the project started, and how to get involved.\ngroup: about\naliases:\n  - \"/about/\"\n  - \"/docs/5.2/about/\"\n---\n\n## Team\n\nBootstrap is maintained by a [small team of developers](https://github.com/orgs/twbs/people) on GitHub. We're actively looking to grow this team and would love to hear from you if you're excited about CSS at scale, writing and maintaining vanilla JavaScript plugins, and improving build tooling processes for frontend code.\n\n## History\n\nOriginally created by a designer and a developer at Twitter, Bootstrap has become one of the most popular front-end frameworks and open source projects in the world.\n\nBootstrap was created at Twitter in mid-2010 by [@mdo](https://twitter.com/mdo) and [@fat](https://twitter.com/fat). Prior to being an open-sourced framework, Bootstrap was known as _Twitter Blueprint_. A few months into development, Twitter held its [first Hack Week](https://blog.twitter.com/engineering/en_us/a/2010/hack-week.html) and the project exploded as developers of all skill levels jumped in without any external guidance. It served as the style guide for internal tools development at the company for over a year before its public release, and continues to do so today.\n\nOriginally [released](https://blog.twitter.com/developer/en_us/a/2011/bootstrap-twitter.html) on <time datetime=\"2011-08-19 11:25\">Friday, August 19, 2011</time>, we've since had over [twenty releases]({{< param repo >}}/releases), including two major rewrites with v2 and v3. With Bootstrap 2, we added responsive functionality to the entire framework as an optional stylesheet. Building on that with Bootstrap 3, we rewrote the library once more to make it responsive by default with a mobile first approach.\n\nWith Bootstrap 4, we once again rewrote the project to account for two key architectural changes: a migration to Sass and the move to CSS's flexbox. Our intention is to help in a small way to move the web development community forward by pushing for newer CSS properties, fewer dependencies, and new technologies across more modern browsers.\n\nOur latest release, Bootstrap 5, focuses on improving v4's codebase with as few major breaking changes as possible. We improved existing features and components, removed support for older browsers, dropped jQuery for regular JavaScript, and embraced more future-friendly technologies like CSS custom properties as part of our tooling.\n\n## Get involved\n\nGet involved with Bootstrap development by [opening an issue]({{< param repo >}}/issues/new/choose) or submitting a pull request. Read our [contributing guidelines]({{< param repo >}}/blob/v{{< param current_version >}}/.github/CONTRIBUTING.md) for information on how we develop.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/about/team.md",
    "content": "---\nlayout: docs\ntitle: Team\ndescription: An overview of the founding team and core contributors to Bootstrap.\ngroup: about\n---\n\nBootstrap is maintained by the founding team and a small group of invaluable core contributors, with the massive support and involvement of our community.\n\n{{< team.inline >}}\n<div class=\"list-group mb-3\">\n  {{- range (index $.Site.Data \"core-team\") }}\n    <a class=\"list-group-item list-group-item-action d-flex align-items-center\" href=\"https://github.com/{{ .user }}\">\n      <img src=\"https://github.com/{{ .user }}.png\" alt=\"@{{ .user }}\" width=\"32\" height=\"32\" class=\"rounded me-2\" loading=\"lazy\">\n      <span>\n        <strong>{{ .name }}</strong> @{{ .user }}\n      </span>\n    </a>\n  {{ end -}}\n</div>\n{{< /team.inline >}}\n\nGet involved with Bootstrap development by [opening an issue]({{< param repo >}}/issues/new/choose) or submitting a pull request. Read our [contributing guidelines]({{< param repo >}}/blob/v{{< param current_version >}}/.github/CONTRIBUTING.md) for information on how we develop.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/about/translations.md",
    "content": "---\nlayout: docs\ntitle: Translations\ndescription: Links to community-translated Bootstrap documentation sites.\ngroup: about\n---\n\nCommunity members have translated Bootstrap's documentation into various languages. None are officially supported and they may not always be up-to-date.\n\n{{< translations.inline >}}\n<ul>\n{{ range .Site.Data.translations -}}\n  <li><a href=\"{{ .url }}\" hreflang=\"{{ .code }}\">{{ .description }} ({{ .name }})</a></li>\n{{ end -}}\n</ul>\n{{< /translations.inline >}}\n\n**We don't help organize or host translations, we just link to them.**\n\nFinished a new or better translation? Open a pull request to add it to our list.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/accordion.md",
    "content": "---\nlayout: docs\ntitle: Accordion\ndescription: Build vertically collapsing accordions in combination with our Collapse JavaScript plugin.\ngroup: components\naliases:\n  - \"/components/\"\n  - \"/docs/5.2/components/\"\ntoc: true\n---\n\n## How it works\n\nThe accordion uses [collapse]({{< docsref \"/components/collapse\" >}}) internally to make it collapsible. To render an accordion that's expanded, add the `.open` class on the `.accordion`.\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Example\n\nClick the accordions below to expand/collapse the accordion content.\n\n{{< example >}}\n<div class=\"accordion\" id=\"accordionExample\">\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"headingOne\">\n      <button class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">\n        Accordion Item #1\n      </button>\n    </h2>\n    <div id=\"collapseOne\" class=\"accordion-collapse collapse show\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionExample\">\n      <div class=\"accordion-body\">\n        <strong>This is the first item's accordion body.</strong> It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n      </div>\n    </div>\n  </div>\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"headingTwo\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\">\n        Accordion Item #2\n      </button>\n    </h2>\n    <div id=\"collapseTwo\" class=\"accordion-collapse collapse\" aria-labelledby=\"headingTwo\" data-bs-parent=\"#accordionExample\">\n      <div class=\"accordion-body\">\n        <strong>This is the second item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n      </div>\n    </div>\n  </div>\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"headingThree\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseThree\" aria-expanded=\"false\" aria-controls=\"collapseThree\">\n        Accordion Item #3\n      </button>\n    </h2>\n    <div id=\"collapseThree\" class=\"accordion-collapse collapse\" aria-labelledby=\"headingThree\" data-bs-parent=\"#accordionExample\">\n      <div class=\"accordion-body\">\n        <strong>This is the third item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Flush\n\nAdd `.accordion-flush` to remove the default `background-color`, some borders, and some rounded corners to render accordions edge-to-edge with their parent container.\n\n{{< example class=\"bg-light\" >}}\n<div class=\"accordion accordion-flush\" id=\"accordionFlushExample\">\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"flush-headingOne\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#flush-collapseOne\" aria-expanded=\"false\" aria-controls=\"flush-collapseOne\">\n        Accordion Item #1\n      </button>\n    </h2>\n    <div id=\"flush-collapseOne\" class=\"accordion-collapse collapse\" aria-labelledby=\"flush-headingOne\" data-bs-parent=\"#accordionFlushExample\">\n      <div class=\"accordion-body\">Placeholder content for this accordion, which is intended to demonstrate the <code>.accordion-flush</code> class. This is the first item's accordion body.</div>\n    </div>\n  </div>\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"flush-headingTwo\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#flush-collapseTwo\" aria-expanded=\"false\" aria-controls=\"flush-collapseTwo\">\n        Accordion Item #2\n      </button>\n    </h2>\n    <div id=\"flush-collapseTwo\" class=\"accordion-collapse collapse\" aria-labelledby=\"flush-headingTwo\" data-bs-parent=\"#accordionFlushExample\">\n      <div class=\"accordion-body\">Placeholder content for this accordion, which is intended to demonstrate the <code>.accordion-flush</code> class. This is the second item's accordion body. Let's imagine this being filled with some actual content.</div>\n    </div>\n  </div>\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"flush-headingThree\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#flush-collapseThree\" aria-expanded=\"false\" aria-controls=\"flush-collapseThree\">\n        Accordion Item #3\n      </button>\n    </h2>\n    <div id=\"flush-collapseThree\" class=\"accordion-collapse collapse\" aria-labelledby=\"flush-headingThree\" data-bs-parent=\"#accordionFlushExample\">\n      <div class=\"accordion-body\">Placeholder content for this accordion, which is intended to demonstrate the <code>.accordion-flush</code> class. This is the third item's accordion body. Nothing more exciting happening here in terms of content, but just filling up the space to make it look, at least at first glance, a bit more representative of how this would look in a real-world application.</div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Always open\n\nOmit the `data-bs-parent` attribute on each `.accordion-collapse` to make accordion items stay open when another item is opened.\n\n{{< example >}}\n<div class=\"accordion\" id=\"accordionPanelsStayOpenExample\">\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"panelsStayOpen-headingOne\">\n      <button class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#panelsStayOpen-collapseOne\" aria-expanded=\"true\" aria-controls=\"panelsStayOpen-collapseOne\">\n        Accordion Item #1\n      </button>\n    </h2>\n    <div id=\"panelsStayOpen-collapseOne\" class=\"accordion-collapse collapse show\" aria-labelledby=\"panelsStayOpen-headingOne\">\n      <div class=\"accordion-body\">\n        <strong>This is the first item's accordion body.</strong> It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n      </div>\n    </div>\n  </div>\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"panelsStayOpen-headingTwo\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#panelsStayOpen-collapseTwo\" aria-expanded=\"false\" aria-controls=\"panelsStayOpen-collapseTwo\">\n        Accordion Item #2\n      </button>\n    </h2>\n    <div id=\"panelsStayOpen-collapseTwo\" class=\"accordion-collapse collapse\" aria-labelledby=\"panelsStayOpen-headingTwo\">\n      <div class=\"accordion-body\">\n        <strong>This is the second item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n      </div>\n    </div>\n  </div>\n  <div class=\"accordion-item\">\n    <h2 class=\"accordion-header\" id=\"panelsStayOpen-headingThree\">\n      <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#panelsStayOpen-collapseThree\" aria-expanded=\"false\" aria-controls=\"panelsStayOpen-collapseThree\">\n        Accordion Item #3\n      </button>\n    </h2>\n    <div id=\"panelsStayOpen-collapseThree\" class=\"accordion-collapse collapse\" aria-labelledby=\"panelsStayOpen-headingThree\">\n      <div class=\"accordion-body\">\n        <strong>This is the third item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Accessibility\n\nPlease read the [collapse accessibility section]({{< docsref \"/components/collapse#accessibility\" >}}) for more information.\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, accordions now use local CSS variables on `.accordion` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"accordion-css-vars\" file=\"scss/_accordion.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"accordion-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/alerts.md",
    "content": "---\nlayout: docs\ntitle: Alerts\ndescription: Provide contextual feedback messages for typical user actions with the handful of available and flexible alert messages.\ngroup: components\ntoc: true\n---\n\n## Examples\n\nAlerts are available for any length of text, as well as an optional close button. For proper styling, use one of the eight **required** contextual classes (e.g., `.alert-success`). For inline dismissal, use the [alerts JavaScript plugin](#dismissing).\n\n{{< example >}}\n{{< alerts.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"alert alert-{{ .name }}\" role=\"alert\">\n  A simple {{ .name }} alert—check it out!\n</div>{{- end -}}\n{{< /alerts.inline >}}\n{{< /example >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n### Live example\n\nClick the button below to show an alert (hidden with inline styles to start), then dismiss (and destroy) it with the built-in close button.\n\n{{< example stackblitz_add_js=\"true\" >}}\n<div id=\"liveAlertPlaceholder\"></div>\n<button type=\"button\" class=\"btn btn-primary\" id=\"liveAlertBtn\">Show live alert</button>\n{{< /example >}}\n\nWe use the following JavaScript to trigger our live alert demo:\n\n```js\nconst alertPlaceholder = document.getElementById('liveAlertPlaceholder')\n\nconst alert = (message, type) => {\n  const wrapper = document.createElement('div')\n  wrapper.innerHTML = [\n    `<div class=\"alert alert-${type} alert-dismissible\" role=\"alert\">`,\n    `   <div>${message}</div>`,\n    '   <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>',\n    '</div>'\n  ].join('')\n\n  alertPlaceholder.append(wrapper)\n}\n\nconst alertTrigger = document.getElementById('liveAlertBtn')\nif (alertTrigger) {\n  alertTrigger.addEventListener('click', () => {\n    alert('Nice, you triggered this alert message!', 'success')\n  })\n}\n```\n\n### Link color\n\nUse the `.alert-link` utility class to quickly provide matching colored links within any alert.\n\n{{< example >}}\n{{< alerts.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"alert alert-{{ .name }}\" role=\"alert\">\n  A simple {{ .name }} alert with <a href=\"#\" class=\"alert-link\">an example link</a>. Give it a click if you like.\n</div>{{ end -}}\n{{< /alerts.inline >}}\n{{< /example >}}\n\n### Additional content\n\nAlerts can also contain additional HTML elements like headings, paragraphs and dividers.\n\n{{< example >}}\n<div class=\"alert alert-success\" role=\"alert\">\n  <h4 class=\"alert-heading\">Well done!</h4>\n  <p>Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.</p>\n  <hr>\n  <p class=\"mb-0\">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>\n</div>\n{{< /example >}}\n\n### Icons\n\nSimilarly, you can use [flexbox utilities]({{< docsref \"/utilities/flex\" >}}) and [Bootstrap Icons]({{< param icons >}}) to create alerts with icons. Depending on your icons and content, you may want to add more utilities or custom styles.\n\n{{< example >}}\n<div class=\"alert alert-primary d-flex align-items-center\" role=\"alert\">\n  <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"bi bi-exclamation-triangle-fill flex-shrink-0 me-2\" viewBox=\"0 0 16 16\" role=\"img\" aria-label=\"Warning:\">\n    <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\"/>\n  </svg>\n  <div>\n    An example alert with an icon\n  </div>\n</div>\n{{< /example >}}\n\nNeed more than one icon for your alerts? Consider using more Bootstrap Icons and making a local SVG sprite like so to easily reference the same icons repeatedly.\n\n{{< example >}}\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"check-circle-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z\"/>\n  </symbol>\n  <symbol id=\"info-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z\"/>\n  </symbol>\n  <symbol id=\"exclamation-triangle-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\"/>\n  </symbol>\n</svg>\n\n<div class=\"alert alert-primary d-flex align-items-center\" role=\"alert\">\n  <svg class=\"bi flex-shrink-0 me-2\" role=\"img\" aria-label=\"Info:\"><use xlink:href=\"#info-fill\"/></svg>\n  <div>\n    An example alert with an icon\n  </div>\n</div>\n<div class=\"alert alert-success d-flex align-items-center\" role=\"alert\">\n  <svg class=\"bi flex-shrink-0 me-2\" role=\"img\" aria-label=\"Success:\"><use xlink:href=\"#check-circle-fill\"/></svg>\n  <div>\n    An example success alert with an icon\n  </div>\n</div>\n<div class=\"alert alert-warning d-flex align-items-center\" role=\"alert\">\n  <svg class=\"bi flex-shrink-0 me-2\" role=\"img\" aria-label=\"Warning:\"><use xlink:href=\"#exclamation-triangle-fill\"/></svg>\n  <div>\n    An example warning alert with an icon\n  </div>\n</div>\n<div class=\"alert alert-danger d-flex align-items-center\" role=\"alert\">\n  <svg class=\"bi flex-shrink-0 me-2\" role=\"img\" aria-label=\"Danger:\"><use xlink:href=\"#exclamation-triangle-fill\"/></svg>\n  <div>\n    An example danger alert with an icon\n  </div>\n</div>\n{{< /example >}}\n\n### Dismissing\n\nUsing the alert JavaScript plugin, it's possible to dismiss any alert inline. Here's how:\n\n- Be sure you've loaded the alert plugin, or the compiled Bootstrap JavaScript.\n- Add a [close button]({{< docsref \"/components/close-button\" >}}) and the `.alert-dismissible` class, which adds extra padding to the right of the alert and positions the close button.\n- On the close button, add the `data-bs-dismiss=\"alert\"` attribute, which triggers the JavaScript functionality. Be sure to use the `<button>` element with it for proper behavior across all devices.\n- To animate alerts when dismissing them, be sure to add the `.fade` and `.show` classes.\n\nYou can see this in action with a live demo:\n\n{{< example >}}\n<div class=\"alert alert-warning alert-dismissible fade show\" role=\"alert\">\n  <strong>Holy guacamole!</strong> You should check in on some of those fields below.\n  <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n</div>\n{{< /example >}}\n\n{{< callout warning >}}\nWhen an alert is dismissed, the element is completely removed from the page structure. If a keyboard user dismisses the alert using the close button, their focus will suddenly be lost and, depending on the browser, reset to the start of the page/document. For this reason, we recommend including additional JavaScript that listens for the `closed.bs.alert` event and programmatically sets `focus()` to the most appropriate location in the page. If you're planning to move focus to a non-interactive element that normally does not receive focus, make sure to add `tabindex=\"-1\"` to the element.\n{{< /callout >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, alerts now use local CSS variables on `.alert` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"alert-css-vars\" file=\"scss/_alert.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"alert-variables\" file=\"scss/_variables.scss\" >}}\n\n### Sass mixin\n\nUsed in combination with `$theme-colors` to create contextual modifier classes for our alerts.\n\n{{< scss-docs name=\"alert-variant-mixin\" file=\"scss/mixins/_alert.scss\" >}}\n\n### Sass loop\n\nLoop that generates the modifier classes with the `alert-variant()` mixin.\n\n{{< scss-docs name=\"alert-modifiers\" file=\"scss/_alert.scss\" >}}\n\n## JavaScript behavior\n\n### Initialize\n\nInitialize elements as alerts\n\n```js\nconst alertList = document.querySelectorAll('.alert')\nconst alerts = [...alertList].map(element => new bootstrap.Alert(element))\n```\n\n{{< callout info >}}\nFor the sole purpose of dismissing an alert, it isn't necessary to initialize the component manually via the JS API. By making use of `data-bs-dismiss=\"alert\"`, the component will be initialized automatically and properly dismissed.\n\nSee the [triggers](#triggers) section for more details.\n{{< /callout >}}\n\n### Triggers\n\n{{% js-dismiss \"alert\" %}}\n\n**Note that closing an alert will remove it from the DOM.**\n\n### Methods\n\nYou can create an alert instance with the alert constructor, for example:\n\n```js\nconst bsAlert = new bootstrap.Alert('#myAlert')\n```\n\nThis makes an alert listen for click events on descendant elements which have the `data-bs-dismiss=\"alert\"` attribute. (Not necessary when using the data-api’s auto-initialization.)\n\n{{< bs-table >}}\n| Method | Description |\n| --- | --- |\n| `close` | Closes an alert by removing it from the DOM. If the `.fade` and `.show` classes are present on the element, the alert will fade out before it is removed. |\n| `dispose` | Destroys an element's alert. (Removes stored data on the DOM element) |\n| `getInstance` | Static method which allows you to get the alert instance associated to a DOM element. For example: `bootstrap.Alert.getInstance(alert)`. |\n| `getOrCreateInstance` | Static method which returns an alert instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Alert.getOrCreateInstance(element)`. |\n{{< /bs-table >}}\n\nBasic usage:\n\n```js\nconst alert = bootstrap.Alert.getOrCreateInstance('#myAlert')\nalert.close()\n```\n\n### Events\n\nBootstrap's alert plugin exposes a few events for hooking into alert functionality.\n\n{{< bs-table >}}\n| Event | Description |\n| --- | --- |\n| `close.bs.alert` | Fires immediately when the `close` instance method is called. |\n| `closed.bs.alert` | Fired when the alert has been closed and CSS transitions have completed. |\n{{< /bs-table >}}\n\n```js\nconst myAlert = document.getElementById('myAlert')\nmyAlert.addEventListener('closed.bs.alert', event => {\n  // do something, for instance, explicitly move focus to the most appropriate element,\n  // so it doesn't get lost/reset to the start of the page\n  // document.getElementById('...').focus()\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/badge.md",
    "content": "---\nlayout: docs\ntitle: Badges\ndescription: Documentation and examples for badges, our small count and labeling component.\ngroup: components\ntoc: true\n---\n\n## Examples\n\nBadges scale to match the size of the immediate parent element by using relative font sizing and `em` units. As of v5, badges no longer have focus or hover styles for links.\n\n### Headings\n\n{{< example >}}\n<h1>Example heading <span class=\"badge bg-secondary\">New</span></h1>\n<h2>Example heading <span class=\"badge bg-secondary\">New</span></h2>\n<h3>Example heading <span class=\"badge bg-secondary\">New</span></h3>\n<h4>Example heading <span class=\"badge bg-secondary\">New</span></h4>\n<h5>Example heading <span class=\"badge bg-secondary\">New</span></h5>\n<h6>Example heading <span class=\"badge bg-secondary\">New</span></h6>\n{{< /example >}}\n\n### Buttons\n\nBadges can be used as part of links or buttons to provide a counter.\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary\">\n  Notifications <span class=\"badge text-bg-secondary\">4</span>\n</button>\n{{< /example >}}\n\nNote that depending on how they are used, badges may be confusing for users of screen readers and similar assistive technologies. While the styling of badges provides a visual cue as to their purpose, these users will simply be presented with the content of the badge. Depending on the specific situation, these badges may seem like random additional words or numbers at the end of a sentence, link, or button.\n\nUnless the context is clear (as with the \"Notifications\" example, where it is understood that the \"4\" is the number of notifications), consider including additional context with a visually hidden piece of additional text.\n\n### Positioned\n\nUse utilities to modify a `.badge` and position it in the corner of a link or button.\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary position-relative\">\n  Inbox\n  <span class=\"position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger\">\n    99+\n    <span class=\"visually-hidden\">unread messages</span>\n  </span>\n</button>\n{{< /example >}}\n\nYou can also replace the `.badge` class with a few more utilities without a count for a more generic indicator.\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary position-relative\">\n  Profile\n  <span class=\"position-absolute top-0 start-100 translate-middle p-2 bg-danger border border-light rounded-circle\">\n    <span class=\"visually-hidden\">New alerts</span>\n  </span>\n</button>\n{{< /example >}}\n\n## Background colors\n\n{{< added-in \"5.2.0\" >}}\n\nSet a `background-color` with contrasting foreground `color` with [our `.text-bg-{color}` helpers]({{< docsref \"helpers/color-background\" >}}). Previously it was required to manually pair your choice of [`.text-{color}`]({{< docsref \"/utilities/colors\" >}}) and [`.bg-{color}`]({{< docsref \"/utilities/background\" >}}) utilities for styling, which you still may use if you prefer.\n\n{{< example >}}\n{{< badge.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<span class=\"badge text-bg-{{ .name }}\">{{ .name | title }}</span>{{- end -}}\n{{< /badge.inline >}}\n{{< /example >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n## Pill badges\n\nUse the `.rounded-pill` utility class to make badges more rounded with a larger `border-radius`.\n\n{{< example >}}\n{{< badge.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<span class=\"badge rounded-pill text-bg-{{ .name }}\">{{ .name | title }}</span>{{- end -}}\n{{< /badge.inline >}}\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, badges now use local CSS variables on `.badge` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"badge-css-vars\" file=\"scss/_badge.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"badge-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/breadcrumb.md",
    "content": "---\nlayout: docs\ntitle: Breadcrumb\ndescription: Indicate the current page's location within a navigational hierarchy that automatically adds separators via CSS.\ngroup: components\ntoc: true\n---\n\n## Example\n\nUse an ordered or unordered list with linked list items to create a minimally styled breadcrumb. Use our utilities to add additional styles as desired.\n\n{{< example >}}\n<nav aria-label=\"breadcrumb\">\n  <ol class=\"breadcrumb\">\n    <li class=\"breadcrumb-item active\" aria-current=\"page\">Home</li>\n  </ol>\n</nav>\n\n<nav aria-label=\"breadcrumb\">\n  <ol class=\"breadcrumb\">\n    <li class=\"breadcrumb-item\"><a href=\"#\">Home</a></li>\n    <li class=\"breadcrumb-item active\" aria-current=\"page\">Library</li>\n  </ol>\n</nav>\n\n<nav aria-label=\"breadcrumb\">\n  <ol class=\"breadcrumb\">\n    <li class=\"breadcrumb-item\"><a href=\"#\">Home</a></li>\n    <li class=\"breadcrumb-item\"><a href=\"#\">Library</a></li>\n    <li class=\"breadcrumb-item active\" aria-current=\"page\">Data</li>\n  </ol>\n</nav>\n{{< /example >}}\n\n## Dividers\n\nDividers are automatically added in CSS through [`::before`](https://developer.mozilla.org/en-US/docs/Web/CSS/::before) and [`content`](https://developer.mozilla.org/en-US/docs/Web/CSS/content). They can be changed by modifying a local CSS custom property `--bs-breadcrumb-divider`, or through the `$breadcrumb-divider` Sass variable — and `$breadcrumb-divider-flipped` for its RTL counterpart, if needed. We default to our Sass variable, which is set as a fallback to the custom property. This way, you get a global divider that you can override without recompiling CSS at any time.\n\n{{< example >}}\n<nav style=\"--bs-breadcrumb-divider: '>';\" aria-label=\"breadcrumb\">\n  <ol class=\"breadcrumb\">\n    <li class=\"breadcrumb-item\"><a href=\"#\">Home</a></li>\n    <li class=\"breadcrumb-item active\" aria-current=\"page\">Library</li>\n  </ol>\n</nav>\n{{< /example >}}\n\nWhen modifying via Sass, the [quote](https://sass-lang.com/documentation/modules/string#quote) function is required to generate the quotes around a string. For example, using `>` as the divider, you can use this:\n\n```scss\n$breadcrumb-divider: quote(\">\");\n```\n\nIt's also possible to use an **embedded SVG icon**. Apply it via our CSS custom property, or use the Sass variable.\n\n\n{{< callout info >}}\n### Using embedded SVG\n\nInlining SVG as data URI requires to URL escape a few characters, most notably `<`, `>` and `#`. That's why the `$breadcrumb-divider` variable is passed through our [`escape-svg()` Sass function]({{< docsref \"/customize/sass#escape-svg\" >}}). When using the CSS custom property, you need to URL escape your SVG on your own. Read [Kevin Weber's explanations on CodePen](https://codepen.io/kevinweber/pen/dXWoRw ) for detailed information on what to escape.\n{{< /callout >}}\n\n{{< example >}}\n<nav style=\"--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E&#34;);\" aria-label=\"breadcrumb\">\n  <ol class=\"breadcrumb\">\n    <li class=\"breadcrumb-item\"><a href=\"#\">Home</a></li>\n    <li class=\"breadcrumb-item active\" aria-current=\"page\">Library</li>\n  </ol>\n</nav>\n{{< /example >}}\n\n```scss\n$breadcrumb-divider: url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8'><path d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='#{$breadcrumb-divider-color}'/></svg>\");\n```\n\nYou can also remove the divider setting `--bs-breadcrumb-divider: '';` (empty strings in CSS custom properties counts as a value), or setting the Sass variable to `$breadcrumb-divider: none;`.\n\n{{< example >}}\n<nav style=\"--bs-breadcrumb-divider: '';\" aria-label=\"breadcrumb\">\n  <ol class=\"breadcrumb\">\n    <li class=\"breadcrumb-item\"><a href=\"#\">Home</a></li>\n    <li class=\"breadcrumb-item active\" aria-current=\"page\">Library</li>\n  </ol>\n</nav>\n{{< /example >}}\n\n\n```scss\n$breadcrumb-divider: none;\n```\n\n## Accessibility\n\nSince breadcrumbs provide a navigation, it's a good idea to add a meaningful label such as `aria-label=\"breadcrumb\"` to describe the type of navigation provided in the `<nav>` element, as well as applying an `aria-current=\"page\"` to the last item of the set to indicate that it represents the current page.\n\nFor more information, see the [ARIA Authoring Practices Guide breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/).\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, breadcrumbs now use local CSS variables on `.breadcrumb` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"breadcrumb-css-vars\" file=\"scss/_breadcrumb.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"breadcrumb-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/button-group.md",
    "content": "---\nlayout: docs\ntitle: Button group\ndescription: Group a series of buttons together on a single line or stack them in a vertical column.\ngroup: components\ntoc: true\n---\n\n## Basic example\n\nWrap a series of buttons with `.btn` in `.btn-group`.\n\n{{< example >}}\n<div class=\"btn-group\" role=\"group\" aria-label=\"Basic example\">\n  <button type=\"button\" class=\"btn btn-primary\">Left</button>\n  <button type=\"button\" class=\"btn btn-primary\">Middle</button>\n  <button type=\"button\" class=\"btn btn-primary\">Right</button>\n</div>\n{{< /example >}}\n\n{{< callout warning >}}\n##### Ensure correct `role` and provide a label\n\nIn order for assistive technologies (such as screen readers) to convey that a series of buttons is grouped, an appropriate `role` attribute needs to be provided. For button groups, this would be `role=\"group\"`, while toolbars should have a `role=\"toolbar\"`.\n\nIn addition, groups and toolbars should be given an explicit label, as most assistive technologies will otherwise not announce them, despite the presence of the correct role attribute. In the examples provided here, we use `aria-label`, but alternatives such as `aria-labelledby` can also be used.\n{{< /callout >}}\n\nThese classes can also be added to groups of links, as an alternative to the [`.nav` navigation components]({{< docsref \"/components/navs-tabs\" >}}).\n\n{{< example >}}\n<div class=\"btn-group\">\n  <a href=\"#\" class=\"btn btn-primary active\" aria-current=\"page\">Active link</a>\n  <a href=\"#\" class=\"btn btn-primary\">Link</a>\n  <a href=\"#\" class=\"btn btn-primary\">Link</a>\n</div>\n{{< /example >}}\n\n## Mixed styles\n\n{{< example >}}\n<div class=\"btn-group\" role=\"group\" aria-label=\"Basic mixed styles example\">\n  <button type=\"button\" class=\"btn btn-danger\">Left</button>\n  <button type=\"button\" class=\"btn btn-warning\">Middle</button>\n  <button type=\"button\" class=\"btn btn-success\">Right</button>\n</div>\n{{< /example >}}\n\n## Outlined styles\n\n{{< example >}}\n<div class=\"btn-group\" role=\"group\" aria-label=\"Basic outlined example\">\n  <button type=\"button\" class=\"btn btn-outline-primary\">Left</button>\n  <button type=\"button\" class=\"btn btn-outline-primary\">Middle</button>\n  <button type=\"button\" class=\"btn btn-outline-primary\">Right</button>\n</div>\n{{< /example >}}\n\n## Checkbox and radio button groups\n\nCombine button-like checkbox and radio [toggle buttons]({{< docsref \"/forms/checks-radios\" >}}) into a seamless looking button group.\n\n{{< example >}}\n<div class=\"btn-group\" role=\"group\" aria-label=\"Basic checkbox toggle button group\">\n  <input type=\"checkbox\" class=\"btn-check\" id=\"btncheck1\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-primary\" for=\"btncheck1\">Checkbox 1</label>\n\n  <input type=\"checkbox\" class=\"btn-check\" id=\"btncheck2\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-primary\" for=\"btncheck2\">Checkbox 2</label>\n\n  <input type=\"checkbox\" class=\"btn-check\" id=\"btncheck3\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-primary\" for=\"btncheck3\">Checkbox 3</label>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"btn-group\" role=\"group\" aria-label=\"Basic radio toggle button group\">\n  <input type=\"radio\" class=\"btn-check\" name=\"btnradio\" id=\"btnradio1\" autocomplete=\"off\" checked>\n  <label class=\"btn btn-outline-primary\" for=\"btnradio1\">Radio 1</label>\n\n  <input type=\"radio\" class=\"btn-check\" name=\"btnradio\" id=\"btnradio2\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-primary\" for=\"btnradio2\">Radio 2</label>\n\n  <input type=\"radio\" class=\"btn-check\" name=\"btnradio\" id=\"btnradio3\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-primary\" for=\"btnradio3\">Radio 3</label>\n</div>\n{{< /example >}}\n\n## Button toolbar\n\nCombine sets of button groups into button toolbars for more complex components. Use utility classes as needed to space out groups, buttons, and more.\n\n{{< example >}}\n<div class=\"btn-toolbar\" role=\"toolbar\" aria-label=\"Toolbar with button groups\">\n  <div class=\"btn-group me-2\" role=\"group\" aria-label=\"First group\">\n    <button type=\"button\" class=\"btn btn-primary\">1</button>\n    <button type=\"button\" class=\"btn btn-primary\">2</button>\n    <button type=\"button\" class=\"btn btn-primary\">3</button>\n    <button type=\"button\" class=\"btn btn-primary\">4</button>\n  </div>\n  <div class=\"btn-group me-2\" role=\"group\" aria-label=\"Second group\">\n    <button type=\"button\" class=\"btn btn-secondary\">5</button>\n    <button type=\"button\" class=\"btn btn-secondary\">6</button>\n    <button type=\"button\" class=\"btn btn-secondary\">7</button>\n  </div>\n  <div class=\"btn-group\" role=\"group\" aria-label=\"Third group\">\n    <button type=\"button\" class=\"btn btn-info\">8</button>\n  </div>\n</div>\n{{< /example >}}\n\nFeel free to mix input groups with button groups in your toolbars. Similar to the example above, you'll likely need some utilities though to space things properly.\n\n{{< example >}}\n<div class=\"btn-toolbar mb-3\" role=\"toolbar\" aria-label=\"Toolbar with button groups\">\n  <div class=\"btn-group me-2\" role=\"group\" aria-label=\"First group\">\n    <button type=\"button\" class=\"btn btn-outline-secondary\">1</button>\n    <button type=\"button\" class=\"btn btn-outline-secondary\">2</button>\n    <button type=\"button\" class=\"btn btn-outline-secondary\">3</button>\n    <button type=\"button\" class=\"btn btn-outline-secondary\">4</button>\n  </div>\n  <div class=\"input-group\">\n    <div class=\"input-group-text\" id=\"btnGroupAddon\">@</div>\n    <input type=\"text\" class=\"form-control\" placeholder=\"Input group example\" aria-label=\"Input group example\" aria-describedby=\"btnGroupAddon\">\n  </div>\n</div>\n\n<div class=\"btn-toolbar justify-content-between\" role=\"toolbar\" aria-label=\"Toolbar with button groups\">\n  <div class=\"btn-group\" role=\"group\" aria-label=\"First group\">\n    <button type=\"button\" class=\"btn btn-outline-secondary\">1</button>\n    <button type=\"button\" class=\"btn btn-outline-secondary\">2</button>\n    <button type=\"button\" class=\"btn btn-outline-secondary\">3</button>\n    <button type=\"button\" class=\"btn btn-outline-secondary\">4</button>\n  </div>\n  <div class=\"input-group\">\n    <div class=\"input-group-text\" id=\"btnGroupAddon2\">@</div>\n    <input type=\"text\" class=\"form-control\" placeholder=\"Input group example\" aria-label=\"Input group example\" aria-describedby=\"btnGroupAddon2\">\n  </div>\n</div>\n{{< /example >}}\n\n## Sizing\n\nInstead of applying button sizing classes to every button in a group, just add `.btn-group-*` to each `.btn-group`, including each one when nesting multiple groups.\n\n{{< example >}}\n<div class=\"btn-group btn-group-lg\" role=\"group\" aria-label=\"Large button group\">\n  <button type=\"button\" class=\"btn btn-outline-dark\">Left</button>\n  <button type=\"button\" class=\"btn btn-outline-dark\">Middle</button>\n  <button type=\"button\" class=\"btn btn-outline-dark\">Right</button>\n</div>\n<br>\n<div class=\"btn-group\" role=\"group\" aria-label=\"Default button group\">\n  <button type=\"button\" class=\"btn btn-outline-dark\">Left</button>\n  <button type=\"button\" class=\"btn btn-outline-dark\">Middle</button>\n  <button type=\"button\" class=\"btn btn-outline-dark\">Right</button>\n</div>\n<br>\n<div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Small button group\">\n  <button type=\"button\" class=\"btn btn-outline-dark\">Left</button>\n  <button type=\"button\" class=\"btn btn-outline-dark\">Middle</button>\n  <button type=\"button\" class=\"btn btn-outline-dark\">Right</button>\n</div>\n{{< /example >}}\n\n## Nesting\n\nPlace a `.btn-group` within another `.btn-group` when you want dropdown menus mixed with a series of buttons.\n\n{{< example >}}\n<div class=\"btn-group\" role=\"group\" aria-label=\"Button group with nested dropdown\">\n  <button type=\"button\" class=\"btn btn-primary\">1</button>\n  <button type=\"button\" class=\"btn btn-primary\">2</button>\n\n  <div class=\"btn-group\" role=\"group\">\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropdown\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n    </ul>\n  </div>\n</div>\n{{< /example >}}\n\n## Vertical variation\n\nMake a set of buttons appear vertically stacked rather than horizontally. **Split button dropdowns are not supported here.**\n\n{{< example >}}\n<div class=\"btn-group-vertical\" role=\"group\" aria-label=\"Vertical button group\">\n  <button type=\"button\" class=\"btn btn-dark\">Button</button>\n  <button type=\"button\" class=\"btn btn-dark\">Button</button>\n  <button type=\"button\" class=\"btn btn-dark\">Button</button>\n  <button type=\"button\" class=\"btn btn-dark\">Button</button>\n  <button type=\"button\" class=\"btn btn-dark\">Button</button>\n  <button type=\"button\" class=\"btn btn-dark\">Button</button>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"btn-group-vertical\" role=\"group\" aria-label=\"Vertical button group\">\n  <button type=\"button\" class=\"btn btn-primary\">Button</button>\n  <button type=\"button\" class=\"btn btn-primary\">Button</button>\n  <div class=\"btn-group\" role=\"group\">\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropdown\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n    </ul>\n  </div>\n  <button type=\"button\" class=\"btn btn-primary\">Button</button>\n  <button type=\"button\" class=\"btn btn-primary\">Button</button>\n  <div class=\"btn-group\" role=\"group\">\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropdown\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group\" role=\"group\">\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropdown\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group\" role=\"group\">\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropdown\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Dropdown link</a></li>\n    </ul>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"btn-group-vertical\" role=\"group\" aria-label=\"Vertical radio toggle button group\">\n  <input type=\"radio\" class=\"btn-check\" name=\"vbtn-radio\" id=\"vbtn-radio1\" autocomplete=\"off\" checked>\n  <label class=\"btn btn-outline-danger\" for=\"vbtn-radio1\">Radio 1</label>\n  <input type=\"radio\" class=\"btn-check\" name=\"vbtn-radio\" id=\"vbtn-radio2\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-danger\" for=\"vbtn-radio2\">Radio 2</label>\n  <input type=\"radio\" class=\"btn-check\" name=\"vbtn-radio\" id=\"vbtn-radio3\" autocomplete=\"off\">\n  <label class=\"btn btn-outline-danger\" for=\"vbtn-radio3\">Radio 3</label>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/buttons.md",
    "content": "---\nlayout: docs\ntitle: Buttons\ndescription: Use Bootstrap's custom button styles for actions in forms, dialogs, and more with support for multiple sizes, states, and more.\ngroup: components\ntoc: true\n---\n\n## Examples\n\nBootstrap includes several predefined button styles, each serving its own semantic purpose, with a few extras thrown in for more control.\n\n{{< example >}}\n{{< buttons.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<button type=\"button\" class=\"btn btn-{{ .name }}\">{{ .name | title }}</button>\n{{- end -}}\n{{< /buttons.inline >}}\n\n<button type=\"button\" class=\"btn btn-link\">Link</button>\n{{< /example >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n## Disable text wrapping\n\nIf you don't want the button text to wrap, you can add the `.text-nowrap` class to the button. In Sass, you can set `$btn-white-space: nowrap` to disable text wrapping for each button.\n\n## Button tags\n\nThe `.btn` classes are designed to be used with the `<button>` element. However, you can also use these classes on `<a>` or `<input>` elements (though some browsers may apply a slightly different rendering).\n\nWhen using button classes on `<a>` elements that are used to trigger in-page functionality (like collapsing content), rather than linking to new pages or sections within the current page, these links should be given a `role=\"button\"` to appropriately convey their purpose to assistive technologies such as screen readers.\n\n{{< example >}}\n<a class=\"btn btn-primary\" href=\"#\" role=\"button\">Link</a>\n<button class=\"btn btn-primary\" type=\"submit\">Button</button>\n<input class=\"btn btn-primary\" type=\"button\" value=\"Input\">\n<input class=\"btn btn-primary\" type=\"submit\" value=\"Submit\">\n<input class=\"btn btn-primary\" type=\"reset\" value=\"Reset\">\n{{< /example >}}\n\n## Outline buttons\n\nIn need of a button, but not the hefty background colors they bring? Replace the default modifier classes with the `.btn-outline-*` ones to remove all background images and colors on any button.\n\n{{< example >}}\n{{< buttons.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<button type=\"button\" class=\"btn btn-outline-{{ .name }}\">{{ .name | title }}</button>\n{{- end -}}\n{{< /buttons.inline >}}\n{{< /example >}}\n\n{{< callout info >}}\nSome of the button styles use a relatively light foreground color, and should only be used on a dark background in order to have sufficient contrast.\n{{< /callout >}}\n\n## Sizes\n\nFancy larger or smaller buttons? Add `.btn-lg` or `.btn-sm` for additional sizes.\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary btn-lg\">Large button</button>\n<button type=\"button\" class=\"btn btn-secondary btn-lg\">Large button</button>\n{{< /example >}}\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary btn-sm\">Small button</button>\n<button type=\"button\" class=\"btn btn-secondary btn-sm\">Small button</button>\n{{< /example >}}\n\nYou can even roll your own custom sizing with CSS variables:\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary\"\n        style=\"--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;\">\n  Custom button\n</button>\n{{< /example >}}\n\n## Disabled state\n\nMake buttons look inactive by adding the `disabled` boolean attribute to any `<button>` element. Disabled buttons have `pointer-events: none` applied to, preventing hover and active states from triggering.\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary\" disabled>Primary button</button>\n<button type=\"button\" class=\"btn btn-secondary\" disabled>Button</button>\n<button type=\"button\" class=\"btn btn-outline-primary\" disabled>Primary button</button>\n<button type=\"button\" class=\"btn btn-outline-secondary\" disabled>Button</button>\n{{< /example >}}\n\nDisabled buttons using the `<a>` element behave a bit different:\n\n- `<a>`s don't support the `disabled` attribute, so you must add the `.disabled` class to make it visually appear disabled.\n- Some future-friendly styles are included to disable all `pointer-events` on anchor buttons.\n- Disabled buttons using `<a>` should include the `aria-disabled=\"true\"` attribute to indicate the state of the element to assistive technologies.\n- Disabled buttons using `<a>` *should not* include the `href` attribute.\n\n{{< example >}}\n<a class=\"btn btn-primary disabled\" role=\"button\" aria-disabled=\"true\">Primary link</a>\n<a class=\"btn btn-secondary disabled\" role=\"button\" aria-disabled=\"true\">Link</a>\n{{< /example >}}\n\n### Link functionality caveat\n\nTo cover cases where you have to keep the `href` attribute on a disabled link, the `.disabled` class uses `pointer-events: none` to try to disable the link functionality of `<a>`s. Note that this CSS property is not yet standardized for HTML, but all modern browsers support it. In addition, even in browsers that do support `pointer-events: none`, keyboard navigation remains unaffected, meaning that sighted keyboard users and users of assistive technologies will still be able to activate these links. So to be safe, in addition to `aria-disabled=\"true\"`, also include a `tabindex=\"-1\"` attribute on these links to prevent them from receiving keyboard focus, and use custom JavaScript to disable their functionality altogether.\n\n{{< example >}}\n<a href=\"#\" class=\"btn btn-primary disabled\" tabindex=\"-1\" role=\"button\" aria-disabled=\"true\">Primary link</a>\n<a href=\"#\" class=\"btn btn-secondary disabled\" tabindex=\"-1\" role=\"button\" aria-disabled=\"true\">Link</a>\n{{< /example >}}\n\n## Block buttons\n\nCreate responsive stacks of full-width, \"block buttons\" like those in Bootstrap 4 with a mix of our display and gap utilities. By using utilities instead of button specific classes, we have much greater control over spacing, alignment, and responsive behaviors.\n\n{{< example >}}\n<div class=\"d-grid gap-2\">\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n</div>\n{{< /example >}}\n\nHere we create a responsive variation, starting with vertically stacked buttons until the `md` breakpoint, where `.d-md-block` replaces the `.d-grid` class, thus nullifying the `gap-2` utility. Resize your browser to see them change.\n\n{{< example >}}\n<div class=\"d-grid gap-2 d-md-block\">\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n</div>\n{{< /example >}}\n\nYou can adjust the width of your block buttons with grid column width classes. For example, for a half-width \"block button\", use `.col-6`. Center it horizontally with `.mx-auto`, too.\n\n{{< example >}}\n<div class=\"d-grid gap-2 col-6 mx-auto\">\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n</div>\n{{< /example >}}\n\nAdditional utilities can be used to adjust the alignment of buttons when horizontal. Here we've taken our previous responsive example and added some flex utilities and a margin utility on the button to right align the buttons when they're no longer stacked.\n\n{{< example >}}\n<div class=\"d-grid gap-2 d-md-flex justify-content-md-end\">\n  <button class=\"btn btn-primary me-md-2\" type=\"button\">Button</button>\n  <button class=\"btn btn-primary\" type=\"button\">Button</button>\n</div>\n{{< /example >}}\n\n## Button plugin\n\nThe button plugin allows you to create simple on/off toggle buttons.\n\n{{< callout info >}}\nVisually, these toggle buttons are identical to the [checkbox toggle buttons]({{< docsref \"/forms/checks-radios#checkbox-toggle-buttons\" >}}). However, they are conveyed differently by assistive technologies: the checkbox toggles will be announced by screen readers as \"checked\"/\"not checked\" (since, despite their appearance, they are fundamentally still checkboxes), whereas these toggle buttons will be announced as \"button\"/\"button pressed\". The choice between these two approaches will depend on the type of toggle you are creating, and whether or not the toggle will make sense to users when announced as a checkbox or as an actual button.\n{{< /callout >}}\n\n### Toggle states\n\nAdd `data-bs-toggle=\"button\"` to toggle a button's `active` state. If you're pre-toggling a button, you must manually add the `.active` class **and** `aria-pressed=\"true\"` to ensure that it is conveyed appropriately to assistive technologies.\n\n{{< example >}}\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"button\">Toggle button</button>\n<button type=\"button\" class=\"btn btn-primary active\" data-bs-toggle=\"button\" aria-pressed=\"true\">Active toggle button</button>\n<button type=\"button\" class=\"btn btn-primary\" disabled data-bs-toggle=\"button\">Disabled toggle button</button>\n{{< /example >}}\n\n{{< example >}}\n<a href=\"#\" class=\"btn btn-primary\" role=\"button\" data-bs-toggle=\"button\">Toggle link</a>\n<a href=\"#\" class=\"btn btn-primary active\" role=\"button\" data-bs-toggle=\"button\" aria-pressed=\"true\">Active toggle link</a>\n<a class=\"btn btn-primary disabled\" aria-disabled=\"true\" role=\"button\" data-bs-toggle=\"button\">Disabled toggle link</a>\n{{< /example >}}\n\n### Methods\n\nYou can create a button instance with the button constructor, for example:\n\n```js\nconst bsButton = new bootstrap.Button('#myButton')\n```\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's button. (Removes stored data on the DOM element) |\n| `getInstance` | Static method which allows you to get the button instance associated to a DOM element, you can use it like this: `bootstrap.Button.getInstance(element)`. |\n| `getOrCreateInstance` | Static method which returns a button instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Button.getOrCreateInstance(element)`. |\n| `toggle` | Toggles push state. Gives the button the appearance that it has been activated. |\n{{< /bs-table >}}\n\nFor example, to toggle all buttons\n\n```js\ndocument.querySelectorAll('.btn').forEach(buttonElement => {\n  const button = bootstrap.Button.getOrCreateInstance(buttonElement)\n  button.toggle()\n})\n```\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, buttons now use local CSS variables on `.btn` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"btn-css-vars\" file=\"scss/_buttons.scss\" >}}\n\nEach `.btn-*` modifier class updates the appropriate CSS variables to minimize additional CSS rules with our `button-variant()`, `button-outline-variant()`, and `button-size()` mixins.\n\nHere's an example of building a custom `.btn-*` modifier class like we do for the buttons unique to our docs by reassigning Bootstrap's CSS variables with a mixture of our own CSS and Sass variables.\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-bd-primary\">Custom button</button>\n</div>\n\n{{< scss-docs name=\"btn-css-vars-example\" file=\"site/assets/scss/_buttons.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"btn-variables\" file=\"scss/_variables.scss\" >}}\n\n### Sass mixins\n\nThere are three mixins for buttons: button and button outline variant mixins (both based on `$theme-colors`), plus a button size mixin.\n\n{{< scss-docs name=\"btn-variant-mixin\" file=\"scss/mixins/_buttons.scss\" >}}\n\n{{< scss-docs name=\"btn-outline-variant-mixin\" file=\"scss/mixins/_buttons.scss\" >}}\n\n{{< scss-docs name=\"btn-size-mixin\" file=\"scss/mixins/_buttons.scss\" >}}\n\n### Sass loops\n\nButton variants (for regular and outline buttons) use their respective mixins with our `$theme-colors` map to generate the modifier classes in `scss/_buttons.scss`.\n\n{{< scss-docs name=\"btn-variant-loops\" file=\"scss/_buttons.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/card.md",
    "content": "---\nlayout: docs\ntitle: Cards\ndescription: Bootstrap's cards provide a flexible and extensible content container with multiple variants and options.\ngroup: components\ntoc: true\n---\n\n## About\n\nA **card** is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options. If you're familiar with Bootstrap 3, cards replace our old panels, wells, and thumbnails. Similar functionality to those components is available as modifier classes for cards.\n\n## Example\n\nCards are built with as little markup and styles as possible, but still manage to deliver a ton of control and customization. Built with flexbox, they offer easy alignment and mix well with other Bootstrap components. They have no `margin` by default, so use [spacing utilities]({{< docsref \"/utilities/spacing\" >}}) as needed.\n\nBelow is an example of a basic card with mixed content and a fixed width. Cards have no fixed width to start, so they'll naturally fill the full width of its parent element. This is easily customized with our various [sizing options](#sizing).\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n## Content types\n\nCards support a wide variety of content, including images, text, list groups, links, and more. Below are examples of what's supported.\n\n### Body\n\nThe building block of a card is the `.card-body`. Use it whenever you need a padded section within a card.\n\n{{< example >}}\n<div class=\"card\">\n  <div class=\"card-body\">\n    This is some text within a card body.\n  </div>\n</div>\n{{< /example >}}\n\n### Titles, text, and links\n\nCard titles are used by adding `.card-title` to a `<h*>` tag. In the same way, links are added and placed next to each other by adding `.card-link` to an `<a>` tag.\n\nSubtitles are used by adding a `.card-subtitle` to a `<h*>` tag. If the `.card-title` and the `.card-subtitle` items are placed in a `.card-body` item, the card title and subtitle are aligned nicely.\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <h6 class=\"card-subtitle mb-2 text-muted\">Card subtitle</h6>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n    <a href=\"#\" class=\"card-link\">Card link</a>\n    <a href=\"#\" class=\"card-link\">Another link</a>\n  </div>\n</div>\n{{< /example >}}\n\n### Images\n\n`.card-img-top` places an image to the top of the card. With `.card-text`, text can be added to the card. Text within `.card-text` can also be styled with the standard HTML tags.\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n  <div class=\"card-body\">\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n</div>\n{{< /example >}}\n\n### List groups\n\nCreate lists of content in a card with a flush list group.\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  <ul class=\"list-group list-group-flush\">\n    <li class=\"list-group-item\">An item</li>\n    <li class=\"list-group-item\">A second item</li>\n    <li class=\"list-group-item\">A third item</li>\n  </ul>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  <div class=\"card-header\">\n    Featured\n  </div>\n  <ul class=\"list-group list-group-flush\">\n    <li class=\"list-group-item\">An item</li>\n    <li class=\"list-group-item\">A second item</li>\n    <li class=\"list-group-item\">A third item</li>\n  </ul>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  <ul class=\"list-group list-group-flush\">\n    <li class=\"list-group-item\">An item</li>\n    <li class=\"list-group-item\">A second item</li>\n    <li class=\"list-group-item\">A third item</li>\n  </ul>\n  <div class=\"card-footer\">\n    Card footer\n  </div>\n</div>\n{{< /example >}}\n\n### Kitchen sink\n\nMix and match multiple content types to create the card you need, or throw everything in there. Shown below are image styles, blocks, text styles, and a list group—all wrapped in a fixed-width card.\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n  <ul class=\"list-group list-group-flush\">\n    <li class=\"list-group-item\">An item</li>\n    <li class=\"list-group-item\">A second item</li>\n    <li class=\"list-group-item\">A third item</li>\n  </ul>\n  <div class=\"card-body\">\n    <a href=\"#\" class=\"card-link\">Card link</a>\n    <a href=\"#\" class=\"card-link\">Another link</a>\n  </div>\n</div>\n{{< /example >}}\n\n### Header and footer\n\nAdd an optional header and/or footer within a card.\n\n{{< example >}}\n<div class=\"card\">\n  <div class=\"card-header\">\n    Featured\n  </div>\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\nCard headers can be styled by adding `.card-header` to `<h*>` elements.\n\n{{< example >}}\n<div class=\"card\">\n  <h5 class=\"card-header\">Featured</h5>\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"card\">\n  <div class=\"card-header\">\n    Quote\n  </div>\n  <div class=\"card-body\">\n    <blockquote class=\"blockquote mb-0\">\n      <p>A well-known quote, contained in a blockquote element.</p>\n      <footer class=\"blockquote-footer\">Someone famous in <cite title=\"Source Title\">Source Title</cite></footer>\n    </blockquote>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"card text-center\">\n  <div class=\"card-header\">\n    Featured\n  </div>\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n  <div class=\"card-footer text-muted\">\n    2 days ago\n  </div>\n</div>\n{{< /example >}}\n\n## Sizing\n\nCards assume no specific `width` to start, so they'll be 100% wide unless otherwise stated. You can change this as needed with custom CSS, grid classes, grid Sass mixins, or utilities.\n\n### Using grid markup\n\nUsing the grid, wrap cards in columns and rows as needed.\n\n{{< example >}}\n<div class=\"row\">\n  <div class=\"col-sm-6\">\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Special title treatment</h5>\n        <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n        <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n      </div>\n    </div>\n  </div>\n  <div class=\"col-sm-6\">\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Special title treatment</h5>\n        <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n        <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Using utilities\n\nUse our handful of [available sizing utilities]({{< docsref \"/utilities/sizing\" >}}) to quickly set a card's width.\n\n{{< example >}}\n<div class=\"card w-75\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Button</a>\n  </div>\n</div>\n\n<div class=\"card w-50\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Button</a>\n  </div>\n</div>\n{{< /example >}}\n\n### Using custom CSS\n\nUse custom CSS in your stylesheets or as inline styles to set a width.\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n## Text alignment\n\nYou can quickly change the text alignment of any card—in its entirety or specific parts—with our [text align classes]({{< docsref \"/utilities/text#text-alignment\" >}}).\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n\n<div class=\"card text-center\" style=\"width: 18rem;\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n\n<div class=\"card text-end\" style=\"width: 18rem;\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n## Navigation\n\nAdd some navigation to a card's header (or block) with Bootstrap's [nav components]({{< docsref \"/components/navs-tabs\" >}}).\n\n{{< example >}}\n<div class=\"card text-center\">\n  <div class=\"card-header\">\n    <ul class=\"nav nav-tabs card-header-tabs\">\n      <li class=\"nav-item\">\n        <a class=\"nav-link active\" aria-current=\"true\" href=\"#\">Active</a>\n      </li>\n      <li class=\"nav-item\">\n        <a class=\"nav-link\" href=\"#\">Link</a>\n      </li>\n      <li class=\"nav-item\">\n        <a class=\"nav-link disabled\">Disabled</a>\n      </li>\n    </ul>\n  </div>\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"card text-center\">\n  <div class=\"card-header\">\n    <ul class=\"nav nav-pills card-header-pills\">\n      <li class=\"nav-item\">\n        <a class=\"nav-link active\" href=\"#\">Active</a>\n      </li>\n      <li class=\"nav-item\">\n        <a class=\"nav-link\" href=\"#\">Link</a>\n      </li>\n      <li class=\"nav-item\">\n        <a class=\"nav-link disabled\">Disabled</a>\n      </li>\n    </ul>\n  </div>\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Special title treatment</h5>\n    <p class=\"card-text\">With supporting text below as a natural lead-in to additional content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n## Images\n\nCards include a few options for working with images. Choose from appending \"image caps\" at either end of a card, overlaying images with card content, or simply embedding the image in a card.\n\n### Image caps\n\nSimilar to headers and footers, cards can include top and bottom \"image caps\"—images at the top or bottom of a card.\n\n{{< example >}}\n<div class=\"card mb-3\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n    <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n  </div>\n</div>\n<div class=\"card\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n    <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n  </div>\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-bottom\" text=\"Image cap\" >}}\n</div>\n{{< /example >}}\n\n### Image overlays\n\nTurn an image into a card background and overlay your card's text. Depending on the image, you may or may not need additional styles or utilities.\n\n{{< example >}}\n<div class=\"card text-bg-dark\">\n  {{< placeholder width=\"100%\" height=\"270\" class=\"bd-placeholder-img-lg card-img\" text=\"Card image\" >}}\n  <div class=\"card-img-overlay\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n    <p class=\"card-text\"><small>Last updated 3 mins ago</small></p>\n  </div>\n</div>\n{{< /example >}}\n\n{{< callout info >}}\nNote that content should not be larger than the height of the image. If content is larger than the image the content will be displayed outside the image.\n{{< /callout >}}\n\n## Horizontal\n\nUsing a combination of grid and utility classes, cards can be made horizontal in a mobile-friendly and responsive way. In the example below, we remove the grid gutters with `.g-0` and use `.col-md-*` classes to make the card horizontal at the `md` breakpoint. Further adjustments may be needed depending on your card content.\n\n{{< example >}}\n<div class=\"card mb-3\" style=\"max-width: 540px;\">\n  <div class=\"row g-0\">\n    <div class=\"col-md-4\">\n      {{< placeholder width=\"100%\" height=\"250\" text=\"Image\" class=\"img-fluid rounded-start\" >}}\n    </div>\n    <div class=\"col-md-8\">\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n        <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Card styles\n\nCards include various options for customizing their backgrounds, borders, and color.\n\n### Background and color\n\n{{< added-in \"5.2.0\" >}}\n\nSet a `background-color` with contrasting foreground `color` with [our `.text-bg-{color}` helpers]({{< docsref \"helpers/color-background\" >}}). Previously it was required to manually pair your choice of [`.text-{color}`]({{< docsref \"/utilities/colors\" >}}) and [`.bg-{color}`]({{< docsref \"/utilities/background\" >}}) utilities for styling, which you still may use if you prefer.\n\n{{< example >}}\n{{< card.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"card text-bg-{{ .name }} mb-3\" style=\"max-width: 18rem;\">\n  <div class=\"card-header\">Header</div>\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">{{ .name | title }} card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n</div>\n{{- end -}}\n{{< /card.inline >}}\n{{< /example >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n### Border\n\nUse [border utilities]({{< docsref \"/utilities/borders\" >}}) to change just the `border-color` of a card. Note that you can put `.text-{color}` classes on the parent `.card` or a subset of the card's contents as shown below.\n\n{{< example >}}\n{{< card.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"card border-{{ .name }} mb-3\" style=\"max-width: 18rem;\">\n  <div class=\"card-header\">Header</div>\n  <div class=\"card-body{{ if not .contrast_color }} text-{{ .name }}{{ end }}\">\n    <h5 class=\"card-title\">{{ .name | title }} card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n</div>\n{{- end -}}\n{{< /card.inline >}}\n{{< /example >}}\n\n### Mixins utilities\n\nYou can also change the borders on the card header and footer as needed, and even remove their `background-color` with `.bg-transparent`.\n\n{{< example >}}\n<div class=\"card border-success mb-3\" style=\"max-width: 18rem;\">\n  <div class=\"card-header bg-transparent border-success\">Header</div>\n  <div class=\"card-body text-success\">\n    <h5 class=\"card-title\">Success card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n  <div class=\"card-footer bg-transparent border-success\">Footer</div>\n</div>\n{{< /example >}}\n\n## Card layout\n\nIn addition to styling the content within cards, Bootstrap includes a few options for laying out series of cards. For the time being, **these layout options are not yet responsive**.\n\n### Card groups\n\nUse card groups to render cards as a single, attached element with equal width and height columns. Card groups start off stacked and use `display: flex;` to become attached with uniform dimensions starting at the `sm` breakpoint.\n\n{{< example >}}\n<div class=\"card-group\">\n  <div class=\"card\">\n    {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n    <div class=\"card-body\">\n      <h5 class=\"card-title\">Card title</h5>\n      <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n    </div>\n  </div>\n  <div class=\"card\">\n    {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n    <div class=\"card-body\">\n      <h5 class=\"card-title\">Card title</h5>\n      <p class=\"card-text\">This card has supporting text below as a natural lead-in to additional content.</p>\n      <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n    </div>\n  </div>\n  <div class=\"card\">\n    {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n    <div class=\"card-body\">\n      <h5 class=\"card-title\">Card title</h5>\n      <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>\n      <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nWhen using card groups with footers, their content will automatically line up.\n\n{{< example >}}\n<div class=\"card-group\">\n  <div class=\"card\">\n    {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n    <div class=\"card-body\">\n      <h5 class=\"card-title\">Card title</h5>\n      <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n    </div>\n    <div class=\"card-footer\">\n      <small class=\"text-muted\">Last updated 3 mins ago</small>\n    </div>\n  </div>\n  <div class=\"card\">\n    {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n    <div class=\"card-body\">\n      <h5 class=\"card-title\">Card title</h5>\n      <p class=\"card-text\">This card has supporting text below as a natural lead-in to additional content.</p>\n    </div>\n    <div class=\"card-footer\">\n      <small class=\"text-muted\">Last updated 3 mins ago</small>\n    </div>\n  </div>\n  <div class=\"card\">\n    {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n    <div class=\"card-body\">\n      <h5 class=\"card-title\">Card title</h5>\n      <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>\n    </div>\n    <div class=\"card-footer\">\n      <small class=\"text-muted\">Last updated 3 mins ago</small>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Grid cards\n\nUse the Bootstrap grid system and its [`.row-cols` classes]({{< docsref \"/layout/grid#row-columns\" >}}) to control how many grid columns (wrapped around your cards) you show per row. For example, here's `.row-cols-1` laying out the cards on one column, and `.row-cols-md-2` splitting four cards to equal width across multiple rows, from the medium breakpoint up.\n\n{{< example >}}\n<div class=\"row row-cols-1 row-cols-md-2 g-4\">\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nChange it to `.row-cols-3` and you'll see the fourth card wrap.\n\n{{< example >}}\n<div class=\"row row-cols-1 row-cols-md-3 g-4\">\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nWhen you need equal height, add `.h-100` to the cards. If you want equal heights by default, you can set `$card-height: 100%` in Sass.\n\n{{< example >}}\n<div class=\"row row-cols-1 row-cols-md-3 g-4\">\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a short card.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content.</p>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"140\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nJust like with card groups, card footers will automatically line up.\n\n{{< example >}}\n<div class=\"row row-cols-1 row-cols-md-3 g-4\">\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n      </div>\n      <div class=\"card-footer\">\n        <small class=\"text-muted\">Last updated 3 mins ago</small>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This card has supporting text below as a natural lead-in to additional content.</p>\n      </div>\n      <div class=\"card-footer\">\n        <small class=\"text-muted\">Last updated 3 mins ago</small>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"card h-100\">\n      {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">Card title</h5>\n        <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>\n      </div>\n      <div class=\"card-footer\">\n        <small class=\"text-muted\">Last updated 3 mins ago</small>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Masonry\n\nIn `v4` we used a CSS-only technique to mimic the behavior of [Masonry](https://masonry.desandro.com/)-like columns, but this technique came with lots of unpleasant [side effects](https://github.com/twbs/bootstrap/pull/28922). If you want to have this type of layout in `v5`, you can just make use of Masonry plugin. **Masonry is not included in Bootstrap**, but we've made a [demo example]({{< docsref \"/examples/masonry\" >}}) to help you get started.\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, cards now use local CSS variables on `.card` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"card-css-vars\" file=\"scss/_card.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"card-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/carousel.md",
    "content": "---\nlayout: docs\ntitle: Carousel\ndescription: A slideshow component for cycling through elements—images or slides of text—like a carousel.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nThe carousel is a slideshow for cycling through a series of content, built with CSS 3D transforms and a bit of JavaScript. It works with a series of images, text, or custom markup. It also includes support for previous/next controls and indicators.\n\nIn browsers where the [Page Visibility API](https://www.w3.org/TR/page-visibility/) is supported, the carousel will avoid sliding when the webpage is not visible to the user (such as when the browser tab is inactive, the browser window is minimized, etc.).\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\nPlease be aware that nested carousels are not supported, and carousels are generally not compliant with accessibility standards.\n\n## Example\n\nCarousels don't automatically normalize slide dimensions. As such, you may need to use additional utilities or custom styles to appropriately size content. While carousels support previous/next controls and indicators, they're not explicitly required. Add and customize as you see fit.\n\n**The `.active` class needs to be added to one of the slides** otherwise the carousel will not be visible. Also be sure to set a unique `id` on the `.carousel` for optional controls, especially if you're using multiple carousels on a single page. Control and indicator elements must have a `data-bs-target` attribute (or `href` for links) that matches the `id` of the `.carousel` element.\n\n### Slides only\n\nHere's a carousel with slides only. Note the presence of the `.d-block` and `.w-100` on carousel images to prevent browser default image alignment.\n\n{{< example >}}\n<div id=\"carouselExampleSlidesOnly\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### With controls\n\nAdding in the previous and next controls. We recommend using `<button>` elements, but you can also use `<a>` elements with `role=\"button\"`.\n\n{{< example >}}\n<div id=\"carouselExampleControls\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleControls\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleControls\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n### With indicators\n\nYou can also add the indicators to the carousel, alongside the controls, too.\n\n{{< example >}}\n<div id=\"carouselExampleIndicators\" class=\"carousel slide\" data-bs-ride=\"true\">\n  <div class=\"carousel-indicators\">\n    <button type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n    <button type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n    <button type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n  </div>\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleIndicators\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n### With captions\n\nAdd captions to your slides easily with the `.carousel-caption` element within any `.carousel-item`. They can be easily hidden on smaller viewports, as shown below, with optional [display utilities]({{< docsref \"/utilities/display\" >}}). We hide them initially with `.d-none` and bring them back on medium-sized devices with `.d-md-block`.\n\n{{< example >}}\n<div id=\"carouselExampleCaptions\" class=\"carousel slide\" data-bs-ride=\"false\">\n  <div class=\"carousel-indicators\">\n    <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n    <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n    <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n  </div>\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n      <div class=\"carousel-caption d-none d-md-block\">\n        <h5>First slide label</h5>\n        <p>Some representative placeholder content for the first slide.</p>\n      </div>\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n      <div class=\"carousel-caption d-none d-md-block\">\n        <h5>Second slide label</h5>\n        <p>Some representative placeholder content for the second slide.</p>\n      </div>\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n      <div class=\"carousel-caption d-none d-md-block\">\n        <h5>Third slide label</h5>\n        <p>Some representative placeholder content for the third slide.</p>\n      </div>\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n### Crossfade\n\nAdd `.carousel-fade` to your carousel to animate slides with a fade transition instead of a slide. Depending on your carousel content (e.g., text only slides), you may want to add `.bg-body` or some custom CSS to the `.carousel-item`s for proper crossfading.\n\n{{< example >}}\n<div id=\"carouselExampleFade\" class=\"carousel slide carousel-fade\" data-bs-ride=\"carousel\">\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleFade\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleFade\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n### Individual `.carousel-item` interval\n\nAdd `data-bs-interval=\"\"` to a `.carousel-item` to change the amount of time to delay between automatically cycling to the next item.\n\n{{< example >}}\n<div id=\"carouselExampleInterval\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\" data-bs-interval=\"10000\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n    </div>\n    <div class=\"carousel-item\" data-bs-interval=\"2000\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleInterval\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleInterval\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n### Disable touch swiping\n\nCarousels support swiping left/right on touchscreen devices to move between slides. This can be disabled using the `data-bs-touch` attribute. The example below also does not include the `data-bs-ride` attribute so it doesn't autoplay.\n\n{{< example >}}\n<div id=\"carouselExampleControlsNoTouching\" class=\"carousel slide\" data-bs-touch=\"false\">\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleControlsNoTouching\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleControlsNoTouching\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n## Dark variant\n\nAdd `.carousel-dark` to the `.carousel` for darker controls, indicators, and captions. Controls have been inverted from their default white fill with the `filter` CSS property. Captions and controls have additional Sass variables that customize the `color` and `background-color`.\n\n{{< example >}}\n<div id=\"carouselExampleDark\" class=\"carousel carousel-dark slide\" data-bs-ride=\"carousel\">\n  <div class=\"carousel-indicators\">\n    <button type=\"button\" data-bs-target=\"#carouselExampleDark\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n    <button type=\"button\" data-bs-target=\"#carouselExampleDark\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n    <button type=\"button\" data-bs-target=\"#carouselExampleDark\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n  </div>\n  <div class=\"carousel-inner\">\n    <div class=\"carousel-item active\" data-bs-interval=\"10000\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#aaa\" background=\"#f5f5f5\" text=\"First slide\" >}}\n      <div class=\"carousel-caption d-none d-md-block\">\n        <h5>First slide label</h5>\n        <p>Some representative placeholder content for the first slide.</p>\n      </div>\n    </div>\n    <div class=\"carousel-item\" data-bs-interval=\"2000\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#bbb\" background=\"#eee\" text=\"Second slide\" >}}\n      <div class=\"carousel-caption d-none d-md-block\">\n        <h5>Second slide label</h5>\n        <p>Some representative placeholder content for the second slide.</p>\n      </div>\n    </div>\n    <div class=\"carousel-item\">\n      {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#999\" background=\"#e5e5e5\" text=\"Third slide\" >}}\n      <div class=\"carousel-caption d-none d-md-block\">\n        <h5>Third slide label</h5>\n        <p>Some representative placeholder content for the third slide.</p>\n      </div>\n    </div>\n  </div>\n  <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleDark\" data-bs-slide=\"prev\">\n    <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Previous</span>\n  </button>\n  <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleDark\" data-bs-slide=\"next\">\n    <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n    <span class=\"visually-hidden\">Next</span>\n  </button>\n</div>\n{{< /example >}}\n\n## Custom transition\n\nThe transition duration of `.carousel-item` can be changed with the `$carousel-transition-duration` Sass variable before compiling or custom styles if you're using the compiled CSS. If multiple transitions are applied, make sure the transform transition is defined first (e.g. `transition: transform 2s ease, opacity .5s ease-out`).\n\n## Sass\n\n### Variables\n\nVariables for all carousels:\n\n{{< scss-docs name=\"carousel-variables\" file=\"scss/_variables.scss\" >}}\n\nVariables for the [dark carousel](#dark-variant):\n\n{{< scss-docs name=\"carousel-dark-variables\" file=\"scss/_variables.scss\" >}}\n\n## Usage\n\n### Via data attributes\n\nUse data attributes to easily control the position of the carousel. `data-bs-slide` accepts the keywords `prev` or `next`, which alters the slide position relative to its current position. Alternatively, use `data-bs-slide-to` to pass a raw slide index to the carousel `data-bs-slide-to=\"2\"`, which shifts the slide position to a particular index beginning with `0`.\n\nThe `data-bs-ride=\"carousel\"` attribute is used to mark a carousel as animating starting at page load. If you don't use `data-bs-ride=\"carousel\"` to initialize your carousel, you have to initialize it yourself. **It cannot be used in combination with (redundant and unnecessary) explicit JavaScript initialization of the same carousel.**\n\n### Via JavaScript\n\nCall carousel manually with:\n\n```js\nconst carousel = new bootstrap.Carousel('#myCarousel')\n```\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `interval` | number | `5000` | The amount of time to delay between automatically cycling an item. |\n| `keyboard` | boolean | `true` | Whether the carousel should react to keyboard events. |\n| `pause` | string, boolean | `\"hover\"` | If set to `\"hover\"`, pauses the cycling of the carousel on `mouseenter` and resumes the cycling of the carousel on `mouseleave`. If set to `false`, hovering over the carousel won't pause it. On touch-enabled devices, when set to `\"hover\"`, cycling will pause on `touchend` (once the user finished interacting with the carousel) for two intervals, before automatically resuming. This is in addition to the mouse behavior. |\n| `ride` | string, boolean | `false` | If set to `true`, autoplays the carousel after the user manually cycles the first item. If set to `\"carousel\"`, autoplays the carousel on load. |\n| `touch` | boolean | `true` | Whether the carousel should support left/right swipe interactions on touchscreen devices. |\n| `wrap` | boolean | `true` | Whether the carousel should cycle continuously or have hard stops. |\n{{< /bs-table >}}\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\nYou can create a carousel instance with the carousel constructor, for example, to initialize with additional options and start cycling through items:\n\n```js\nconst myCarouselElement = document.querySelector('#myCarousel')\nconst carousel = new bootstrap.Carousel(myCarouselElement, {\n  interval: 2000,\n  wrap: false\n})\n```\n\n{{< bs-table >}}\n| Method | Description |\n| --- | --- |\n| `cycle` | Cycles through the carousel items from left to right. |\n| `dispose` | Destroys an element's carousel. (Removes stored data on the DOM element) |\n| `getInstance` | Static method which allows you to get the carousel instance associated to a DOM element, you can use it like this: `bootstrap.Carousel.getInstance(element)`. |\n| `getOrCreateInstance` | Static method which returns a carousel instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Carousel.getOrCreateInstance(element)`. |\n| `next` | Cycles to the next item. **Returns to the caller before the next item has been shown** (e.g., before the `slid.bs.carousel` event occurs). |\n| `nextWhenVisible` | Don't cycle carousel to next when the page isn't visible or the carousel or its parent isn't visible. **Returns to the caller before the target item has been shown**. |\n| `pause` | Stops the carousel from cycling through items. |\n| `prev` | Cycles to the previous item. **Returns to the caller before the previous item has been shown** (e.g., before the `slid.bs.carousel` event occurs). |\n| `to` | Cycles the carousel to a particular frame (0 based, similar to an array). **Returns to the caller before the target item has been shown** (e.g., before the `slid.bs.carousel` event occurs). |\n{{< /bs-table >}}\n\n### Events\n\nBootstrap's carousel class exposes two events for hooking into carousel functionality. Both events have the following additional properties:\n\n- `direction`: The direction in which the carousel is sliding (either `\"left\"` or `\"right\"`).\n- `relatedTarget`: The DOM element that is being slid into place as the active item.\n- `from`: The index of the current item\n- `to`: The index of the next item\n\nAll carousel events are fired at the carousel itself (i.e. at the `<div class=\"carousel\">`).\n\n{{< bs-table >}}\n| Event type | Description |\n| --- | --- |\n| `slid.bs.carousel` | Fired when the carousel has completed its slide transition. |\n| `slide.bs.carousel` | Fires immediately when the `slide` instance method is invoked. |\n{{< /bs-table >}}\n\n```js\nconst myCarousel = document.getElementById('myCarousel')\n\nmyCarousel.addEventListener('slide.bs.carousel', event => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/close-button.md",
    "content": "---\nlayout: docs\ntitle: Close button\ndescription: A generic close button for dismissing content like modals and alerts.\ngroup: components\ntoc: true\n---\n\n## Example\n\nProvide an option to dismiss or close a component with `.btn-close`. Default styling is limited, but highly customizable. Modify the Sass variables to replace the default `background-image`. **Be sure to include text for screen readers**, as we've done with `aria-label`.\n\n{{< example >}}\n<button type=\"button\" class=\"btn-close\" aria-label=\"Close\"></button>\n{{< /example >}}\n\n## Disabled state\n\nDisabled close buttons change their `opacity`. We've also applied `pointer-events: none` and `user-select: none` to preventing hover and active states from triggering.\n\n{{< example >}}\n<button type=\"button\" class=\"btn-close\" disabled aria-label=\"Close\"></button>\n{{< /example >}}\n\n## White variant\n\nChange the default `.btn-close` to be white with the `.btn-close-white` class. This class uses the `filter` property to invert the `background-image`.\n\n{{< example class=\"bg-dark\" >}}\n<button type=\"button\" class=\"btn-close btn-close-white\" aria-label=\"Close\"></button>\n<button type=\"button\" class=\"btn-close btn-close-white\" disabled aria-label=\"Close\"></button>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"close-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/collapse.md",
    "content": "---\nlayout: docs\ntitle: Collapse\ndescription: Toggle the visibility of content across your project with a few classes and our JavaScript plugins.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nThe collapse JavaScript plugin is used to show and hide content. Buttons or anchors are used as triggers that are mapped to specific elements you toggle. Collapsing an element will animate the `height` from its current value to `0`. Given how CSS handles animations, you cannot use `padding` on a `.collapse` element. Instead, use the class as an independent wrapping element.\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Example\n\nClick the buttons below to show and hide another element via class changes:\n\n- `.collapse` hides content\n- `.collapsing` is applied during transitions\n- `.collapse.show` shows content\n\nGenerally, we recommend using a button with the `data-bs-target` attribute. While not recommended from a semantic point of view, you can also use a link with the `href` attribute (and a `role=\"button\"`). In both cases, the `data-bs-toggle=\"collapse\"` is required.\n\n{{< example >}}\n<p>\n  <a class=\"btn btn-primary\" data-bs-toggle=\"collapse\" href=\"#collapseExample\" role=\"button\" aria-expanded=\"false\" aria-controls=\"collapseExample\">\n    Link with href\n  </a>\n  <button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseExample\" aria-expanded=\"false\" aria-controls=\"collapseExample\">\n    Button with data-bs-target\n  </button>\n</p>\n<div class=\"collapse\" id=\"collapseExample\">\n  <div class=\"card card-body\">\n    Some placeholder content for the collapse component. This panel is hidden by default but revealed when the user activates the relevant trigger.\n  </div>\n</div>\n{{< /example >}}\n\n## Horizontal\n\nThe collapse plugin also supports horizontal collapsing. Add the `.collapse-horizontal` modifier class to transition the `width` instead of `height` and set a `width` on the immediate child element. Feel free to write your own custom Sass, use inline styles, or use our [width utilities]({{< docsref \"/utilities/sizing\" >}}).\n\n{{< callout info >}}\nPlease note that while the example below has a `min-height` set to avoid excessive repaints in our docs, this is not explicitly required. **Only the `width` on the child element is required.**\n{{< /callout >}}\n\n{{< example >}}\n<p>\n  <button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseWidthExample\" aria-expanded=\"false\" aria-controls=\"collapseWidthExample\">\n    Toggle width collapse\n  </button>\n</p>\n<div style=\"min-height: 120px;\">\n  <div class=\"collapse collapse-horizontal\" id=\"collapseWidthExample\">\n    <div class=\"card card-body\" style=\"width: 300px;\">\n      This is some placeholder content for a horizontal collapse. It's hidden by default and shown when triggered.\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Multiple targets\n\nA `<button>` or `<a>` can show and hide multiple elements by referencing them with a selector in its `href` or `data-bs-target` attribute.\nMultiple `<button>` or `<a>` can show and hide an element if they each reference it with their `href` or `data-bs-target` attribute\n\n{{< example >}}\n<p>\n  <a class=\"btn btn-primary\" data-bs-toggle=\"collapse\" href=\"#multiCollapseExample1\" role=\"button\" aria-expanded=\"false\" aria-controls=\"multiCollapseExample1\">Toggle first element</a>\n  <button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#multiCollapseExample2\" aria-expanded=\"false\" aria-controls=\"multiCollapseExample2\">Toggle second element</button>\n  <button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\".multi-collapse\" aria-expanded=\"false\" aria-controls=\"multiCollapseExample1 multiCollapseExample2\">Toggle both elements</button>\n</p>\n<div class=\"row\">\n  <div class=\"col\">\n    <div class=\"collapse multi-collapse\" id=\"multiCollapseExample1\">\n      <div class=\"card card-body\">\n        Some placeholder content for the first collapse component of this multi-collapse example. This panel is hidden by default but revealed when the user activates the relevant trigger.\n      </div>\n    </div>\n  </div>\n  <div class=\"col\">\n    <div class=\"collapse multi-collapse\" id=\"multiCollapseExample2\">\n      <div class=\"card card-body\">\n        Some placeholder content for the second collapse component of this multi-collapse example. This panel is hidden by default but revealed when the user activates the relevant trigger.\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Accessibility\n\nBe sure to add `aria-expanded` to the control element. This attribute explicitly conveys the current state of the collapsible element tied to the control to screen readers and similar assistive technologies. If the collapsible element is closed by default, the attribute on the control element should have a value of `aria-expanded=\"false\"`. If you've set the collapsible element to be open by default using the `show` class, set `aria-expanded=\"true\"` on the control instead. The plugin will automatically toggle this attribute on the control based on whether or not the collapsible element has been opened or closed (via JavaScript, or because the user triggered another control element also tied to the same collapsible element). If the control element's HTML element is not a button (e.g., an `<a>` or `<div>`), the attribute `role=\"button\"` should be added to the element.\n\nIf your control element is targeting a single collapsible element – i.e. the `data-bs-target` attribute is pointing to an `id` selector – you should add the `aria-controls` attribute to the control element, containing the `id` of the collapsible element. Modern screen readers and similar assistive technologies make use of this attribute to provide users with additional shortcuts to navigate directly to the collapsible element itself.\n\nNote that Bootstrap's current implementation does not cover the various *optional* keyboard interactions described in the [ARIA Authoring Practices Guide accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/) - you will need to include these yourself with custom JavaScript.\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"collapse-transition\" file=\"scss/_variables.scss\" >}}\n\n### Classes\n\nCollapse transition classes can be found in `scss/_transitions.scss` as these are shared across multiple components (collapse and accordion).\n\n{{< scss-docs name=\"collapse-classes\" file=\"scss/_transitions.scss\" >}}\n\n## Usage\n\nThe collapse plugin utilizes a few classes to handle the heavy lifting:\n\n- `.collapse` hides the content\n- `.collapse.show` shows the content\n- `.collapsing` is added when the transition starts, and removed when it finishes\n\nThese classes can be found in `_transitions.scss`.\n\n### Via data attributes\n\nJust add `data-bs-toggle=\"collapse\"` and a `data-bs-target` to the element to automatically assign control of one or more collapsible elements. The `data-bs-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`.\n\nTo add accordion-like group management to a collapsible area, add the data attribute `data-bs-parent=\"#selector\"`. Refer to the [accordion page]({{< docsref \"/components/accordion\" >}}) for more information.\n\n### Via JavaScript\n\nEnable manually with:\n\n```js\nconst collapseElementList = document.querySelectorAll('.collapse')\nconst collapseList = [...collapseElementList].map(collapseEl => new bootstrap.Collapse(collapseEl))\n```\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n`parent` | selector, DOM element | `null` | If parent is provided, then all collapsible elements under the specified parent will be closed when this collapsible item is shown. (similar to traditional accordion behavior - this is dependent on the `card` class). The attribute has to be set on the target collapsible area. |\n`toggle` | boolean | `true` | Toggles the collapsible element on invocation. |\n{{< /bs-table >}}\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\nActivates your content as a collapsible element. Accepts an optional options `object`.\n\nYou can create a collapse instance with the constructor, for example:\n\n```js\nconst bsCollapse = new bootstrap.Collapse('#myCollapse', {\n  toggle: false\n})\n```\n\n{{< bs-table >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's collapse. (Removes stored data on the DOM element) |\n| `getInstance` | Static method which allows you to get the collapse instance associated to a DOM element, you can use it like this: `bootstrap.Collapse.getInstance(element)`. |\n| `getOrCreateInstance` | Static method which returns a collapse instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Collapse.getOrCreateInstance(element)`. |\n| `hide` | Hides a collapsible element. **Returns to the caller before the collapsible element has actually been hidden** (e.g., before the `hidden.bs.collapse` event occurs). |\n| `show` | Shows a collapsible element. **Returns to the caller before the collapsible element has actually been shown** (e.g., before the `shown.bs.collapse` event occurs). |\n| `toggle` | Toggles a collapsible element to shown or hidden. **Returns to the caller before the collapsible element has actually been shown or hidden** (i.e. before the `shown.bs.collapse` or `hidden.bs.collapse` event occurs). |\n{{< /bs-table >}}\n\n### Events\n\nBootstrap's collapse class exposes a few events for hooking into collapse functionality.\n\n{{< bs-table >}}\n| Event type | Description |\n| --- | --- |\n| `hide.bs.collapse` | This event is fired immediately when the `hide` method has been called. |\n| `hidden.bs.collapse` | This event is fired when a collapse element has been hidden from the user (will wait for CSS transitions to complete). |\n| `show.bs.collapse` | This event fires immediately when the `show` instance method is called. |\n| `shown.bs.collapse` | This event is fired when a collapse element has been made visible to the user (will wait for CSS transitions to complete). |\n{{< /bs-table >}}\n\n```js\nconst myCollapsible = document.getElementById('myCollapsible')\nmyCollapsible.addEventListener('hidden.bs.collapse', event => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/dropdowns.md",
    "content": "---\nlayout: docs\ntitle: Dropdowns\ndescription: Toggle contextual overlays for displaying lists of links and more with the Bootstrap dropdown plugin.\ngroup: components\ntoc: true\n---\n\n## Overview\n\nDropdowns are toggleable, contextual overlays for displaying lists of links and more. They're made interactive with the included Bootstrap dropdown JavaScript plugin. They're toggled by clicking, not by hovering; this is [an intentional design decision](https://markdotto.com/2012/02/27/bootstrap-explained-dropdowns/).\n\nDropdowns are built on a third party library, [Popper](https://popper.js.org/), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js]({{< param \"cdn.popper\" >}}) before Bootstrap's JavaScript or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains Popper. Popper isn't used to position dropdowns in navbars though as dynamic positioning isn't required.\n\n## Accessibility\n\nThe [<abbr title=\"Web Accessibility Initiative\">WAI</abbr> <abbr title=\"Accessible Rich Internet Applications\">ARIA</abbr>](https://www.w3.org/TR/wai-aria/) standard defines an actual [`role=\"menu\"` widget](https://www.w3.org/TR/wai-aria/#menu), but this is specific to application-like menus which trigger actions or functions. <abbr title=\"Accessible Rich Internet Applications\">ARIA</abbr> menus can only contain menu items, checkbox menu items, radio button menu items, radio button groups, and sub-menus.\n\nBootstrap's dropdowns, on the other hand, are designed to be generic and applicable to a variety of situations and markup structures. For instance, it is possible to create dropdowns that contain additional inputs and form controls, such as search fields or login forms. For this reason, Bootstrap does not expect (nor automatically add) any of the `role` and `aria-` attributes required for true <abbr title=\"Accessible Rich Internet Applications\">ARIA</abbr> menus. Authors will have to include these more specific attributes themselves.\n\nHowever, Bootstrap does add built-in support for most standard keyboard menu interactions, such as the ability to move through individual `.dropdown-item` elements using the cursor keys and close the menu with the <kbd>ESC</kbd> key.\n\n## Examples\n\nWrap the dropdown's toggle (your button or link) and the dropdown menu within `.dropdown`, or another element that declares `position: relative;`. Dropdowns can be triggered from `<a>` or `<button>` elements to better fit your potential needs. The examples shown here use semantic `<ul>` elements where appropriate, but custom markup is supported.\n\n### Single button\n\nAny single `.btn` can be turned into a dropdown toggle with some markup changes. Here's how you can put them to work with either `<button>` elements:\n\n{{< example >}}\n<div class=\"dropdown\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown button\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\nAnd with `<a>` elements:\n\n{{< example >}}\n<div class=\"dropdown\">\n  <a class=\"btn btn-secondary dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown link\n  </a>\n\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\nThe best part is you can do this with any button variant, too:\n\n<div class=\"bd-example\">\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Primary</button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Secondary</button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-success dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Success</button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-info dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Info</button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-warning dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Warning</button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-danger dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Danger</button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n</div>\n\n```html\n<!-- Example single danger button -->\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-danger dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Action\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n```\n\n### Split button\n\nSimilarly, create split button dropdowns with virtually the same markup as single button dropdowns, but with the addition of `.dropdown-toggle-split` for proper spacing around the dropdown caret.\n\nWe use this extra class to reduce the horizontal `padding` on either side of the caret by 25% and remove the `margin-left` that's added for regular button dropdowns. Those extra changes keep the caret centered in the split button and provide a more appropriately sized hit area next to the main button.\n\n<div class=\"bd-example\">\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-primary\">Primary</button>\n    <button type=\"button\" class=\"btn btn-primary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-secondary\">Secondary</button>\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-success\">Success</button>\n    <button type=\"button\" class=\"btn btn-success dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-info\">Info</button>\n    <button type=\"button\" class=\"btn btn-info dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-warning\">Warning</button>\n    <button type=\"button\" class=\"btn btn-warning dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-danger\">Danger</button>\n    <button type=\"button\" class=\"btn btn-danger dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div><!-- /btn-group -->\n</div>\n\n```html\n<!-- Example split danger button -->\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-danger\">Action</button>\n  <button type=\"button\" class=\"btn btn-danger dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropdown</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n```\n\n## Sizing\n\nButton dropdowns work with buttons of all sizes, including default and split dropdown buttons.\n\n<div class=\"bd-example\">\n  <div class=\"btn-group\">\n    <button class=\"btn btn-secondary btn-lg dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Large button\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-lg btn-secondary\">Large split button</button>\n    <button type=\"button\" class=\"btn btn-lg btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n</div>\n\n```html\n<!-- Large button groups (default and split) -->\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary btn-lg dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Large button\n  </button>\n  <ul class=\"dropdown-menu\">\n    ...\n  </ul>\n</div>\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary btn-lg\" type=\"button\">\n    Large split button\n  </button>\n  <button type=\"button\" class=\"btn btn-lg btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropdown</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    ...\n  </ul>\n</div>\n```\n\n<div class=\"bd-example\">\n  <div class=\"btn-group\">\n    <button class=\"btn btn-secondary btn-sm dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Small button\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-sm btn-secondary\">Small split button</button>\n    <button type=\"button\" class=\"btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n</div>\n\n```html\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary btn-sm dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Small button\n  </button>\n  <ul class=\"dropdown-menu\">\n    ...\n  </ul>\n</div>\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary btn-sm\" type=\"button\">\n    Small split button\n  </button>\n  <button type=\"button\" class=\"btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropdown</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    ...\n  </ul>\n</div>\n```\n\n## Dark dropdowns\n\nOpt into darker dropdowns to match a dark navbar or custom style by adding `.dropdown-menu-dark` onto an existing `.dropdown-menu`. No changes are required to the dropdown items.\n\n{{< example >}}\n<div class=\"dropdown\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown button\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-dark\">\n    <li><a class=\"dropdown-item active\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\nAnd putting it to use in a navbar:\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNavDarkDropdown\" aria-controls=\"navbarNavDarkDropdown\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarNavDarkDropdown\">\n      <ul class=\"navbar-nav\">\n        <li class=\"nav-item dropdown\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            Dropdown\n          </a>\n          <ul class=\"dropdown-menu dropdown-menu-dark\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n      </ul>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\n## Directions\n\n{{< callout info >}}\n#### RTL\nDirections are mirrored when using Bootstrap in RTL, meaning `.dropstart` will appear on the right side.\n{{< /callout >}}\n\n### Centered\n\nMake the dropdown menu centered below the toggle with `.dropdown-center` on the parent element.\n\n{{< example >}}\n<div class=\"dropdown-center\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Centered dropdown\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Action two</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Action three</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\n### Dropup\n\nTrigger dropdown menus above elements by adding `.dropup` to the parent element.\n\n<div class=\"bd-example\">\n  <div class=\"btn-group dropup\">\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropup\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group dropup\">\n    <button type=\"button\" class=\"btn btn-secondary\">\n      Split dropup\n    </button>\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n</div>\n\n```html\n<!-- Default dropup button -->\n<div class=\"btn-group dropup\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropup\n  </button>\n  <ul class=\"dropdown-menu\">\n    <!-- Dropdown menu links -->\n  </ul>\n</div>\n\n<!-- Split dropup button -->\n<div class=\"btn-group dropup\">\n  <button type=\"button\" class=\"btn btn-secondary\">\n    Split dropup\n  </button>\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropdown</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    <!-- Dropdown menu links -->\n  </ul>\n</div>\n```\n\n### Dropup centered\n\nMake the dropup menu centered above the toggle with `.dropup-center` on the parent element.\n\n{{< example >}}\n<div class=\"dropup-center dropup\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Centered dropup\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Action two</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Action three</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\n### Dropend\n\nTrigger dropdown menus at the right of the elements by adding `.dropend` to the parent element.\n\n<div class=\"bd-example\">\n  <div class=\"btn-group dropend\">\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropend\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group dropend\">\n    <button type=\"button\" class=\"btn btn-secondary\">\n      Split dropend\n    </button>\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropend</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n</div>\n\n```html\n<!-- Default dropend button -->\n<div class=\"btn-group dropend\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropend\n  </button>\n  <ul class=\"dropdown-menu\">\n    <!-- Dropdown menu links -->\n  </ul>\n</div>\n\n<!-- Split dropend button -->\n<div class=\"btn-group dropend\">\n  <button type=\"button\" class=\"btn btn-secondary\">\n    Split dropend\n  </button>\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropend</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    <!-- Dropdown menu links -->\n  </ul>\n</div>\n```\n\n### Dropstart\n\nTrigger dropdown menus at the left of the elements by adding `.dropstart` to the parent element.\n\n<div class=\"bd-example\">\n  <div class=\"btn-group dropstart\">\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      Dropstart\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group dropstart\">\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n      <span class=\"visually-hidden\">Toggle Dropstart</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n    <button type=\"button\" class=\"btn btn-secondary\">\n      Split dropstart\n    </button>\n  </div>\n</div>\n\n```html\n<!-- Default dropstart button -->\n<div class=\"btn-group dropstart\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropstart\n  </button>\n  <ul class=\"dropdown-menu\">\n    <!-- Dropdown menu links -->\n  </ul>\n</div>\n\n<!-- Split dropstart button -->\n<div class=\"btn-group dropstart\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropstart</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    <!-- Dropdown menu links -->\n  </ul>\n  <button type=\"button\" class=\"btn btn-secondary\">\n    Split dropstart\n  </button>\n</div>\n```\n\n## Menu items\n\nYou can use `<a>` or `<button>` elements as dropdown items.\n\n{{< example >}}\n<div class=\"dropdown\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><button class=\"dropdown-item\" type=\"button\">Action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Another action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Something else here</button></li>\n  </ul>\n</div>\n{{< /example >}}\n\nYou can also create non-interactive dropdown items with `.dropdown-item-text`. Feel free to style further with custom CSS or text utilities.\n\n{{< example >}}\n<ul class=\"dropdown-menu\">\n  <li><span class=\"dropdown-item-text\">Dropdown item text</span></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n</ul>\n{{< /example >}}\n\n### Active\n\nAdd `.active` to items in the dropdown to **style them as active**. To convey the active state to assistive technologies, use the `aria-current` attribute — using the `page` value for the current page, or `true` for the current item in a set.\n\n{{< example >}}\n<ul class=\"dropdown-menu\">\n  <li><a class=\"dropdown-item\" href=\"#\">Regular link</a></li>\n  <li><a class=\"dropdown-item active\" href=\"#\" aria-current=\"true\">Active link</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Another link</a></li>\n</ul>\n{{< /example >}}\n\n### Disabled\n\nAdd `.disabled` to items in the dropdown to **style them as disabled**.\n\n{{< example >}}\n<ul class=\"dropdown-menu\">\n  <li><a class=\"dropdown-item\" href=\"#\">Regular link</a></li>\n  <li><a class=\"dropdown-item disabled\">Disabled link</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Another link</a></li>\n</ul>\n{{< /example >}}\n\n## Menu alignment\n\nBy default, a dropdown menu is automatically positioned 100% from the top and along the left side of its parent. You can change this with the directional `.drop*` classes, but you can also control them with additional modifier classes.\n\nAdd `.dropdown-menu-end` to a `.dropdown-menu` to right align the dropdown menu. Directions are mirrored when using Bootstrap in RTL, meaning `.dropdown-menu-end` will appear on the left side.\n\n{{< callout info >}}\n**Heads up!** Dropdowns are positioned thanks to Popper except when they are contained in a navbar.\n{{< /callout >}}\n\n{{< example >}}\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Right-aligned menu example\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-end\">\n    <li><button class=\"dropdown-item\" type=\"button\">Action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Another action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Something else here</button></li>\n  </ul>\n</div>\n{{< /example >}}\n\n### Responsive alignment\n\nIf you want to use responsive alignment, disable dynamic positioning by adding the `data-bs-display=\"static\"` attribute and use the responsive variation classes.\n\nTo align **right** the dropdown menu with the given breakpoint or larger, add `.dropdown-menu{-sm|-md|-lg|-xl|-xxl}-end`.\n\n{{< example >}}\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\" aria-expanded=\"false\">\n    Left-aligned but right aligned when large screen\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-lg-end\">\n    <li><button class=\"dropdown-item\" type=\"button\">Action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Another action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Something else here</button></li>\n  </ul>\n</div>\n{{< /example >}}\n\nTo align **left** the dropdown menu with the given breakpoint or larger, add `.dropdown-menu-end` and `.dropdown-menu{-sm|-md|-lg|-xl|-xxl}-start`.\n\n{{< example >}}\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\" aria-expanded=\"false\">\n    Right-aligned but left aligned when large screen\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start\">\n    <li><button class=\"dropdown-item\" type=\"button\">Action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Another action</button></li>\n    <li><button class=\"dropdown-item\" type=\"button\">Something else here</button></li>\n  </ul>\n</div>\n{{< /example >}}\n\nNote that you don't need to add a `data-bs-display=\"static\"` attribute to dropdown buttons in navbars, since Popper isn't used in navbars.\n\n### Alignment options\n\nTaking most of the options shown above, here's a small kitchen sink demo of various dropdown alignment options in one place.\n\n{{< example >}}\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Right-aligned menu\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-end\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\" aria-expanded=\"false\">\n    Left-aligned, right-aligned lg\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-lg-end\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" data-bs-display=\"static\" aria-expanded=\"false\">\n    Right-aligned, left-aligned lg\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group dropstart\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropstart\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group dropend\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropend\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group dropup\">\n  <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropup\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\n## Menu content\n\n### Headers\n\nAdd a header to label sections of actions in any dropdown menu.\n\n{{< example >}}\n<ul class=\"dropdown-menu\">\n  <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n</ul>\n{{< /example >}}\n\n### Dividers\n\nSeparate groups of related menu items with a divider.\n\n{{< example >}}\n<ul class=\"dropdown-menu\">\n  <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n  <li><hr class=\"dropdown-divider\"></li>\n  <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n</ul>\n{{< /example >}}\n\n### Text\n\nPlace any freeform text within a dropdown menu with text and use [spacing utilities]({{< docsref \"/utilities/spacing\" >}}). Note that you'll likely need additional sizing styles to constrain the menu width.\n\n{{< example >}}\n<div class=\"dropdown-menu p-4 text-muted\" style=\"max-width: 200px;\">\n  <p>\n    Some example text that's free-flowing within the dropdown menu.\n  </p>\n  <p class=\"mb-0\">\n    And this is more example text.\n  </p>\n</div>\n{{< /example >}}\n\n### Forms\n\nPut a form within a dropdown menu, or make it into a dropdown menu, and use [margin or padding utilities]({{< docsref \"/utilities/spacing\" >}}) to give it the negative space you require.\n\n{{< example >}}\n<div class=\"dropdown-menu\">\n  <form class=\"px-4 py-3\">\n    <div class=\"mb-3\">\n      <label for=\"exampleDropdownFormEmail1\" class=\"form-label\">Email address</label>\n      <input type=\"email\" class=\"form-control\" id=\"exampleDropdownFormEmail1\" placeholder=\"email@example.com\">\n    </div>\n    <div class=\"mb-3\">\n      <label for=\"exampleDropdownFormPassword1\" class=\"form-label\">Password</label>\n      <input type=\"password\" class=\"form-control\" id=\"exampleDropdownFormPassword1\" placeholder=\"Password\">\n    </div>\n    <div class=\"mb-3\">\n      <div class=\"form-check\">\n        <input type=\"checkbox\" class=\"form-check-input\" id=\"dropdownCheck\">\n        <label class=\"form-check-label\" for=\"dropdownCheck\">\n          Remember me\n        </label>\n      </div>\n    </div>\n    <button type=\"submit\" class=\"btn btn-primary\">Sign in</button>\n  </form>\n  <div class=\"dropdown-divider\"></div>\n  <a class=\"dropdown-item\" href=\"#\">New around here? Sign up</a>\n  <a class=\"dropdown-item\" href=\"#\">Forgot password?</a>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"dropdown\">\n  <button type=\"button\" class=\"btn btn-primary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" data-bs-auto-close=\"outside\">\n    Dropdown form\n  </button>\n  <form class=\"dropdown-menu p-4\">\n    <div class=\"mb-3\">\n      <label for=\"exampleDropdownFormEmail2\" class=\"form-label\">Email address</label>\n      <input type=\"email\" class=\"form-control\" id=\"exampleDropdownFormEmail2\" placeholder=\"email@example.com\">\n    </div>\n    <div class=\"mb-3\">\n      <label for=\"exampleDropdownFormPassword2\" class=\"form-label\">Password</label>\n      <input type=\"password\" class=\"form-control\" id=\"exampleDropdownFormPassword2\" placeholder=\"Password\">\n    </div>\n    <div class=\"mb-3\">\n      <div class=\"form-check\">\n        <input type=\"checkbox\" class=\"form-check-input\" id=\"dropdownCheck2\">\n        <label class=\"form-check-label\" for=\"dropdownCheck2\">\n          Remember me\n        </label>\n      </div>\n    </div>\n    <button type=\"submit\" class=\"btn btn-primary\">Sign in</button>\n  </form>\n</div>\n{{< /example >}}\n\n## Dropdown options\n\nUse `data-bs-offset` or `data-bs-reference` to change the location of the dropdown.\n\n{{< example >}}\n<div class=\"d-flex\">\n  <div class=\"dropdown me-1\">\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" data-bs-offset=\"10,20\">\n      Offset\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    </ul>\n  </div>\n  <div class=\"btn-group\">\n    <button type=\"button\" class=\"btn btn-secondary\">Reference</button>\n    <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" data-bs-reference=\"parent\">\n      <span class=\"visually-hidden\">Toggle Dropdown</span>\n    </button>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </div>\n</div>\n{{< /example >}}\n\n### Auto close behavior\n\nBy default, the dropdown menu is closed when clicking inside or outside the dropdown menu. You can use the `autoClose` option to change this behavior of the dropdown.\n\n{{< example >}}\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"true\" aria-expanded=\"false\">\n    Default dropdown\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"inside\" aria-expanded=\"false\">\n    Clickable outside\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"outside\" aria-expanded=\"false\">\n    Clickable inside\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n\n<div class=\"btn-group\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" data-bs-auto-close=\"false\" aria-expanded=\"false\">\n    Manual close\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Menu item</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, dropdowns now use local CSS variables on `.dropdown-menu` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"dropdown-css-vars\" file=\"scss/_dropdown.scss\" >}}\n\nCustomization through CSS variables can be seen on the `.dropdown-menu-dark` class where we override specific values without adding duplicate CSS selectors.\n\n{{< scss-docs name=\"dropdown-dark-css-vars\" file=\"scss/_dropdown.scss\" >}}\n\n### Sass variables\n\nVariables for all dropdowns:\n\n{{< scss-docs name=\"dropdown-variables\" file=\"scss/_variables.scss\" >}}\n\nVariables for the [dark dropdown](#dark-dropdowns):\n\n{{< scss-docs name=\"dropdown-dark-variables\" file=\"scss/_variables.scss\" >}}\n\nVariables for the CSS-based carets that indicate a dropdown's interactivity:\n\n{{< scss-docs name=\"caret-variables\" file=\"scss/_variables.scss\" >}}\n\n### Mixins\n\nMixins are used to generate the CSS-based carets and can be found in `scss/mixins/_caret.scss`.\n\n{{< scss-docs name=\"caret-mixins\" file=\"scss/mixins/_caret.scss\" >}}\n\n## Usage\n\nVia data attributes or JavaScript, the dropdown plugin toggles hidden content (dropdown menus) by toggling the `.show` class on the parent `.dropdown-menu`. The `data-bs-toggle=\"dropdown\"` attribute is relied on for closing dropdown menus at an application level, so it's a good idea to always use it.\n\n{{< callout info >}}\nOn touch-enabled devices, opening a dropdown adds empty `mouseover` handlers to the immediate children of the `<body>` element. This admittedly ugly hack is necessary to work around a [quirk in iOS' event delegation](https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html), which would otherwise prevent a tap anywhere outside of the dropdown from triggering the code that closes the dropdown. Once the dropdown is closed, these additional empty `mouseover` handlers are removed.\n{{< /callout >}}\n\n### Via data attributes\n\nAdd `data-bs-toggle=\"dropdown\"` to a link or button to toggle a dropdown.\n\n```html\n<div class=\"dropdown\">\n  <button type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown trigger\n  </button>\n  <ul class=\"dropdown-menu\">\n    ...\n  </ul>\n</div>\n```\n\n### Via JavaScript\n\nCall the dropdowns via JavaScript:\n\n```js\nconst dropdownElementList = document.querySelectorAll('.dropdown-toggle')\nconst dropdownList = [...dropdownElementList].map(dropdownToggleEl => new bootstrap.Dropdown(dropdownToggleEl))\n```\n\n{{< callout info >}}\n##### `data-bs-toggle=\"dropdown\"` still required\n\nRegardless of whether you call your dropdown via JavaScript or instead use the data-api, `data-bs-toggle=\"dropdown\"` is always required to be present on the dropdown's trigger element.\n{{< /callout >}}\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `autoClose` | boolean, string | `true` | Configure the auto close behavior of the dropdown: <ul class=\"my-2\"><li>`true` - the dropdown will be closed by clicking outside or inside the dropdown menu.</li><li>`false` - the dropdown will be closed by clicking the toggle button and manually calling `hide` or `toggle` method. (Also will not be closed by pressing <kbd>esc</kbd> key)</li><li>`'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.</li> <li>`'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.</li></ul> Note: the dropdown can always be closed with the <kbd>ESC</kbd> key. |\n| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the dropdown menu (applies only to Popper's preventOverflow modifier). By default it's `clippingParents` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper's [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). |\n| `display` | string | `'dynamic'` | By default, we use Popper for dynamic positioning. Disable this with `static`. |\n| `offset` | array, string, function | `[0, 2]` | Offset of the dropdown relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset=\"10,20\"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper's [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). |\n| `popperConfig` | null, object, function | `null` | To change Bootstrap's default Popper config, see [Popper's configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it's called with an object that contains the Bootstrap's default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. |\n| `reference` | string, element, object | `'toggle'` | Reference element of the dropdown menu. Accepts the values of `'toggle'`, `'parent'`, an HTMLElement reference or an object providing `getBoundingClientRect`. For more information refer to Popper's [constructor docs](https://popper.js.org/docs/v2/constructors/#createpopper) and [virtual element docs](https://popper.js.org/docs/v2/virtual-elements/). |\n{{< /bs-table >}}\n\n#### Using function with `popperConfig`\n\n```js\nconst dropdown = new bootstrap.Dropdown(element, {\n  popperConfig(defaultBsPopperConfig) {\n    // const newPopperConfig = {...}\n    // use defaultBsPopperConfig if needed...\n    // return newPopperConfig\n  }\n})\n```\n\n### Methods\n\n{{< bs-table >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's dropdown. (Removes stored data on the DOM element) |\n| `getInstance` | Static method which allows you to get the dropdown instance associated to a DOM element, you can use it like this: `bootstrap.Dropdown.getInstance(element)`. |\n| `getOrCreateInstance` | Static method which returns a dropdown instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Dropdown.getOrCreateInstance(element)`. |\n| `hide` | Hides the dropdown menu of a given navbar or tabbed navigation. |\n| `show` | Shows the dropdown menu of a given navbar or tabbed navigation. |\n| `toggle` | Toggles the dropdown menu of a given navbar or tabbed navigation. |\n| `update` | Updates the position of an element's dropdown. |\n{{< /bs-table >}}\n\n### Events\n\nAll dropdown events are fired at the toggling element and then bubbled up. So you can also add event listeners on the `.dropdown-menu`'s parent element. `hide.bs.dropdown` and `hidden.bs.dropdown` events have a `clickEvent` property (only when the original Event type is `click`) that contains an Event Object for the click event.\n\n{{< bs-table >}}\n| Event type | Description |\n| --- | --- |\n| `hide.bs.dropdown` | Fires immediately when the `hide` instance method has been called. |\n| `hidden.bs.dropdown` | Fired when the dropdown has finished being hidden from the user and CSS transitions have completed. |\n| `show.bs.dropdown` | Fires immediately when the `show` instance method is called. |\n| `shown.bs.dropdown` | Fired when the dropdown has been made visible to the user and CSS transitions have completed. |\n{{< /bs-table >}}\n\n```js\nconst myDropdown = document.getElementById('myDropdown')\nmyDropdown.addEventListener('show.bs.dropdown', event => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/list-group.md",
    "content": "---\nlayout: docs\ntitle: List group\ndescription: List groups are a flexible and powerful component for displaying a series of content. Modify and extend them to support just about any content within.\ngroup: components\ntoc: true\n---\n\n## Basic example\n\nThe most basic list group is an unordered list with list items and the proper classes. Build upon it with the options that follow, or with your own CSS as needed.\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item\">An item</li>\n  <li class=\"list-group-item\">A second item</li>\n  <li class=\"list-group-item\">A third item</li>\n  <li class=\"list-group-item\">A fourth item</li>\n  <li class=\"list-group-item\">And a fifth one</li>\n</ul>\n{{< /example >}}\n\n## Active items\n\nAdd `.active` to a `.list-group-item` to indicate the current active selection.\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item active\" aria-current=\"true\">An active item</li>\n  <li class=\"list-group-item\">A second item</li>\n  <li class=\"list-group-item\">A third item</li>\n  <li class=\"list-group-item\">A fourth item</li>\n  <li class=\"list-group-item\">And a fifth one</li>\n</ul>\n{{< /example >}}\n\n## Disabled items\n\nAdd `.disabled` to a `.list-group-item` to make it _appear_ disabled. Note that some elements with `.disabled` will also require custom JavaScript to fully disable their click events (e.g., links).\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item disabled\" aria-disabled=\"true\">A disabled item</li>\n  <li class=\"list-group-item\">A second item</li>\n  <li class=\"list-group-item\">A third item</li>\n  <li class=\"list-group-item\">A fourth item</li>\n  <li class=\"list-group-item\">And a fifth one</li>\n</ul>\n{{< /example >}}\n\n## Links and buttons\n\nUse `<a>`s or `<button>`s to create _actionable_ list group items with hover, disabled, and active states by adding `.list-group-item-action`. We separate these pseudo-classes to ensure list groups made of non-interactive elements (like `<li>`s or `<div>`s) don't provide a click or tap affordance.\n\nBe sure to **not use the standard `.btn` classes here**.\n\n{{< example >}}\n<div class=\"list-group\">\n  <a href=\"#\" class=\"list-group-item list-group-item-action active\" aria-current=\"true\">\n    The current link item\n  </a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action\">A second link item</a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action\">A third link item</a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action\">A fourth link item</a>\n  <a class=\"list-group-item list-group-item-action disabled\">A disabled link item</a>\n</div>\n{{< /example >}}\n\nWith `<button>`s, you can also make use of the `disabled` attribute instead of the `.disabled` class. Sadly, `<a>`s don't support the disabled attribute.\n\n{{< example >}}\n<div class=\"list-group\">\n  <button type=\"button\" class=\"list-group-item list-group-item-action active\" aria-current=\"true\">\n    The current button\n  </button>\n  <button type=\"button\" class=\"list-group-item list-group-item-action\">A second button item</button>\n  <button type=\"button\" class=\"list-group-item list-group-item-action\">A third button item</button>\n  <button type=\"button\" class=\"list-group-item list-group-item-action\">A fourth button item</button>\n  <button type=\"button\" class=\"list-group-item list-group-item-action\" disabled>A disabled button item</button>\n</div>\n{{< /example >}}\n\n## Flush\n\nAdd `.list-group-flush` to remove some borders and rounded corners to render list group items edge-to-edge in a parent container (e.g., cards).\n\n{{< example >}}\n<ul class=\"list-group list-group-flush\">\n  <li class=\"list-group-item\">An item</li>\n  <li class=\"list-group-item\">A second item</li>\n  <li class=\"list-group-item\">A third item</li>\n  <li class=\"list-group-item\">A fourth item</li>\n  <li class=\"list-group-item\">And a fifth one</li>\n</ul>\n{{< /example >}}\n\n## Numbered\n\nAdd the `.list-group-numbered` modifier class (and optionally use an `<ol>` element) to opt into numbered list group items. Numbers are generated via CSS (as opposed to a `<ol>`s default browser styling) for better placement inside list group items and to allow for better customization.\n\nNumbers are generated by `counter-reset` on the `<ol>`, and then styled and placed with a `::before` pseudo-element on the `<li>` with `counter-increment` and `content`.\n\n{{< example >}}\n<ol class=\"list-group list-group-numbered\">\n  <li class=\"list-group-item\">A list item</li>\n  <li class=\"list-group-item\">A list item</li>\n  <li class=\"list-group-item\">A list item</li>\n</ol>\n{{< /example >}}\n\nThese work great with custom content as well.\n\n{{< example >}}\n<ol class=\"list-group list-group-numbered\">\n  <li class=\"list-group-item d-flex justify-content-between align-items-start\">\n    <div class=\"ms-2 me-auto\">\n      <div class=\"fw-bold\">Subheading</div>\n      Content for list item\n    </div>\n    <span class=\"badge bg-primary rounded-pill\">14</span>\n  </li>\n  <li class=\"list-group-item d-flex justify-content-between align-items-start\">\n    <div class=\"ms-2 me-auto\">\n      <div class=\"fw-bold\">Subheading</div>\n      Content for list item\n    </div>\n    <span class=\"badge bg-primary rounded-pill\">14</span>\n  </li>\n  <li class=\"list-group-item d-flex justify-content-between align-items-start\">\n    <div class=\"ms-2 me-auto\">\n      <div class=\"fw-bold\">Subheading</div>\n      Content for list item\n    </div>\n    <span class=\"badge bg-primary rounded-pill\">14</span>\n  </li>\n</ol>\n{{< /example >}}\n\n## Horizontal\n\nAdd `.list-group-horizontal` to change the layout of list group items from vertical to horizontal across all breakpoints. Alternatively, choose a responsive variant `.list-group-horizontal-{sm|md|lg|xl|xxl}` to make a list group horizontal starting at that breakpoint's `min-width`. Currently **horizontal list groups cannot be combined with flush list groups.**\n\n**ProTip:** Want equal-width list group items when horizontal? Add `.flex-fill` to each list group item.\n\n{{< example >}}\n{{< list-group.inline >}}\n{{- range $.Site.Data.breakpoints }}\n<ul class=\"list-group list-group-horizontal{{ .abbr }}\">\n  <li class=\"list-group-item\">An item</li>\n  <li class=\"list-group-item\">A second item</li>\n  <li class=\"list-group-item\">A third item</li>\n</ul>\n{{- end -}}\n{{< /list-group.inline >}}\n{{< /example >}}\n\n## Contextual classes\n\nUse contextual classes to style list items with a stateful background and color.\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item\">A simple default list group item</li>\n{{< list.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n  <li class=\"list-group-item list-group-item-{{ .name }}\">A simple {{ .name }} list group item</li>\n{{- end -}}\n{{< /list.inline >}}\n</ul>\n{{< /example >}}\n\nContextual classes also work with `.list-group-item-action`. Note the addition of the hover styles here not present in the previous example. Also supported is the `.active` state; apply it to indicate an active selection on a contextual list group item.\n\n{{< example >}}\n<div class=\"list-group\">\n  <a href=\"#\" class=\"list-group-item list-group-item-action\">A simple default list group item</a>\n{{< list.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n  <a href=\"#\" class=\"list-group-item list-group-item-action list-group-item-{{ .name }}\">A simple {{ .name }} list group item</a>\n{{- end -}}\n{{< /list.inline >}}\n</div>\n{{< /example >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n## With badges\n\nAdd badges to any list group item to show unread counts, activity, and more with the help of some [utilities]({{< docsref \"/utilities/flex\" >}}).\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item d-flex justify-content-between align-items-center\">\n    A list item\n    <span class=\"badge bg-primary rounded-pill\">14</span>\n  </li>\n  <li class=\"list-group-item d-flex justify-content-between align-items-center\">\n    A second list item\n    <span class=\"badge bg-primary rounded-pill\">2</span>\n  </li>\n  <li class=\"list-group-item d-flex justify-content-between align-items-center\">\n    A third list item\n    <span class=\"badge bg-primary rounded-pill\">1</span>\n  </li>\n</ul>\n{{< /example >}}\n\n## Custom content\n\nAdd nearly any HTML within, even for linked list groups like the one below, with the help of [flexbox utilities]({{< docsref \"/utilities/flex\" >}}).\n\n{{< example >}}\n<div class=\"list-group\">\n  <a href=\"#\" class=\"list-group-item list-group-item-action active\" aria-current=\"true\">\n    <div class=\"d-flex w-100 justify-content-between\">\n      <h5 class=\"mb-1\">List group item heading</h5>\n      <small>3 days ago</small>\n    </div>\n    <p class=\"mb-1\">Some placeholder content in a paragraph.</p>\n    <small>And some small print.</small>\n  </a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action\">\n    <div class=\"d-flex w-100 justify-content-between\">\n      <h5 class=\"mb-1\">List group item heading</h5>\n      <small class=\"text-muted\">3 days ago</small>\n    </div>\n    <p class=\"mb-1\">Some placeholder content in a paragraph.</p>\n    <small class=\"text-muted\">And some muted small print.</small>\n  </a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action\">\n    <div class=\"d-flex w-100 justify-content-between\">\n      <h5 class=\"mb-1\">List group item heading</h5>\n      <small class=\"text-muted\">3 days ago</small>\n    </div>\n    <p class=\"mb-1\">Some placeholder content in a paragraph.</p>\n    <small class=\"text-muted\">And some muted small print.</small>\n  </a>\n</div>\n{{< /example >}}\n\n## Checkboxes and radios\n\nPlace Bootstrap's checkboxes and radios within list group items and customize as needed. You can use them without `<label>`s, but please remember to include an `aria-label` attribute and value for accessibility.\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"checkbox\" value=\"\" id=\"firstCheckbox\">\n    <label class=\"form-check-label\" for=\"firstCheckbox\">First checkbox</label>\n  </li>\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"checkbox\" value=\"\" id=\"secondCheckbox\">\n    <label class=\"form-check-label\" for=\"secondCheckbox\">Second checkbox</label>\n  </li>\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"checkbox\" value=\"\" id=\"thirdCheckbox\">\n    <label class=\"form-check-label\" for=\"thirdCheckbox\">Third checkbox</label>\n  </li>\n</ul>\n{{< /example >}}\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"radio\" name=\"listGroupRadio\" value=\"\" id=\"firstRadio\" checked>\n    <label class=\"form-check-label\" for=\"firstRadio\">First radio</label>\n  </li>\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"radio\" name=\"listGroupRadio\" value=\"\" id=\"secondRadio\">\n    <label class=\"form-check-label\" for=\"secondRadio\">Second radio</label>\n  </li>\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"radio\" name=\"listGroupRadio\" value=\"\" id=\"thirdRadio\">\n    <label class=\"form-check-label\" for=\"thirdRadio\">Third radio</label>\n  </li>\n</ul>\n{{< /example >}}\n\nYou can use `.stretched-link` on `<label>`s to make the whole list group item clickable.\n\n{{< example >}}\n<ul class=\"list-group\">\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"checkbox\" value=\"\" id=\"firstCheckboxStretched\">\n    <label class=\"form-check-label stretched-link\" for=\"firstCheckboxStretched\">First checkbox</label>\n  </li>\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"checkbox\" value=\"\" id=\"secondCheckboxStretched\">\n    <label class=\"form-check-label stretched-link\" for=\"secondCheckboxStretched\">Second checkbox</label>\n  </li>\n  <li class=\"list-group-item\">\n    <input class=\"form-check-input me-1\" type=\"checkbox\" value=\"\" id=\"thirdCheckboxStretched\">\n    <label class=\"form-check-label stretched-link\" for=\"thirdCheckboxStretched\">Third checkbox</label>\n  </li>\n</ul>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, list groups now use local CSS variables on `.list-group` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"list-group-css-vars\" file=\"scss/_list-group.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"list-group-variables\" file=\"scss/_variables.scss\" >}}\n\n### Mixins\n\nUsed in combination with `$theme-colors` to generate the [contextual variant classes](#contextual-classes) for `.list-group-item`s.\n\n{{< scss-docs name=\"list-group-mixin\" file=\"scss/mixins/_list-group.scss\" >}}\n\n### Loop\n\nLoop that generates the modifier classes with the `list-group-item-variant()` mixin.\n\n{{< scss-docs name=\"list-group-modifiers\" file=\"scss/_list-group.scss\" >}}\n\n## JavaScript behavior\n\nUse the tab JavaScript plugin—include it individually or through the compiled `bootstrap.js` file—to extend our list group to create tabbable panes of local content.\n\n<div class=\"bd-example\" role=\"tabpanel\">\n  <div class=\"row\">\n    <div class=\"col-4\">\n      <div class=\"list-group\" id=\"list-tab\" role=\"tablist\">\n        <a class=\"list-group-item list-group-item-action active\" id=\"list-home-list\" data-bs-toggle=\"tab\" href=\"#list-home\" role=\"tab\" aria-controls=\"list-home\">Home</a>\n        <a class=\"list-group-item list-group-item-action\" id=\"list-profile-list\" data-bs-toggle=\"tab\" href=\"#list-profile\" role=\"tab\" aria-controls=\"list-profile\">Profile</a>\n        <a class=\"list-group-item list-group-item-action\" id=\"list-messages-list\" data-bs-toggle=\"tab\" href=\"#list-messages\" role=\"tab\" aria-controls=\"list-messages\">Messages</a>\n        <a class=\"list-group-item list-group-item-action\" id=\"list-settings-list\" data-bs-toggle=\"tab\" href=\"#list-settings\" role=\"tab\" aria-controls=\"list-settings\">Settings</a>\n      </div>\n    </div>\n    <div class=\"col-8\">\n      <div class=\"tab-content\" id=\"nav-tabContent\">\n        <div class=\"tab-pane fade show active\" id=\"list-home\" role=\"tabpanel\" aria-labelledby=\"list-home-list\">\n          <p>Some placeholder content in a paragraph relating to \"Home\". And some more content, used here just to pad out and fill this tab panel. In production, you would obviously have more real content here. And not just text. It could be anything, really. Text, images, forms.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"list-profile\" role=\"tabpanel\" aria-labelledby=\"list-profile-list\">\n          <p>Some placeholder content in a paragraph relating to \"Profile\". And some more content, used here just to pad out and fill this tab panel. In production, you would obviously have more real content here. And not just text. It could be anything, really. Text, images, forms.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"list-messages\" role=\"tabpanel\" aria-labelledby=\"list-messages-list\">\n          <p>Some placeholder content in a paragraph relating to \"Messages\". And some more content, used here just to pad out and fill this tab panel. In production, you would obviously have more real content here. And not just text. It could be anything, really. Text, images, forms.</p>\n        </div>\n        <div class=\"tab-pane fade\" id=\"list-settings\" role=\"tabpanel\" aria-labelledby=\"list-settings-list\">\n          <p>Some placeholder content in a paragraph relating to \"Settings\". And some more content, used here just to pad out and fill this tab panel. In production, you would obviously have more real content here. And not just text. It could be anything, really. Text, images, forms.</p>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n```html\n<div class=\"row\">\n  <div class=\"col-4\">\n    <div class=\"list-group\" id=\"list-tab\" role=\"tablist\">\n      <a class=\"list-group-item list-group-item-action active\" id=\"list-home-list\" data-bs-toggle=\"list\" href=\"#list-home\" role=\"tab\" aria-controls=\"list-home\">Home</a>\n      <a class=\"list-group-item list-group-item-action\" id=\"list-profile-list\" data-bs-toggle=\"list\" href=\"#list-profile\" role=\"tab\" aria-controls=\"list-profile\">Profile</a>\n      <a class=\"list-group-item list-group-item-action\" id=\"list-messages-list\" data-bs-toggle=\"list\" href=\"#list-messages\" role=\"tab\" aria-controls=\"list-messages\">Messages</a>\n      <a class=\"list-group-item list-group-item-action\" id=\"list-settings-list\" data-bs-toggle=\"list\" href=\"#list-settings\" role=\"tab\" aria-controls=\"list-settings\">Settings</a>\n    </div>\n  </div>\n  <div class=\"col-8\">\n    <div class=\"tab-content\" id=\"nav-tabContent\">\n      <div class=\"tab-pane fade show active\" id=\"list-home\" role=\"tabpanel\" aria-labelledby=\"list-home-list\">...</div>\n      <div class=\"tab-pane fade\" id=\"list-profile\" role=\"tabpanel\" aria-labelledby=\"list-profile-list\">...</div>\n      <div class=\"tab-pane fade\" id=\"list-messages\" role=\"tabpanel\" aria-labelledby=\"list-messages-list\">...</div>\n      <div class=\"tab-pane fade\" id=\"list-settings\" role=\"tabpanel\" aria-labelledby=\"list-settings-list\">...</div>\n    </div>\n  </div>\n</div>\n```\n\n### Using data attributes\n\nYou can activate a list group navigation without writing any JavaScript by simply specifying `data-bs-toggle=\"list\"` or on an element. Use these data attributes on `.list-group-item`.\n\n```html\n<div role=\"tabpanel\">\n  <!-- List group -->\n  <div class=\"list-group\" id=\"myList\" role=\"tablist\">\n    <a class=\"list-group-item list-group-item-action active\" data-bs-toggle=\"list\" href=\"#home\" role=\"tab\">Home</a>\n    <a class=\"list-group-item list-group-item-action\" data-bs-toggle=\"list\" href=\"#profile\" role=\"tab\">Profile</a>\n    <a class=\"list-group-item list-group-item-action\" data-bs-toggle=\"list\" href=\"#messages\" role=\"tab\">Messages</a>\n    <a class=\"list-group-item list-group-item-action\" data-bs-toggle=\"list\" href=\"#settings\" role=\"tab\">Settings</a>\n  </div>\n\n  <!-- Tab panes -->\n  <div class=\"tab-content\">\n    <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\">...</div>\n    <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\">...</div>\n    <div class=\"tab-pane\" id=\"messages\" role=\"tabpanel\">...</div>\n    <div class=\"tab-pane\" id=\"settings\" role=\"tabpanel\">...</div>\n  </div>\n</div>\n```\n\n### Via JavaScript\n\nEnable tabbable list item via JavaScript (each list item needs to be activated individually):\n\n```js\nconst triggerTabList = document.querySelectorAll('#myTab a')\ntriggerTabList.forEach(triggerEl => {\n  const tabTrigger = new bootstrap.Tab(triggerEl)\n\n  triggerEl.addEventListener('click', event => {\n    event.preventDefault()\n    tabTrigger.show()\n  })\n})\n```\n\nYou can activate individual list item in several ways:\n\n```js\nconst triggerEl = document.querySelector('#myTab a[href=\"#profile\"]')\nbootstrap.Tab.getInstance(triggerEl).show() // Select tab by name\n\nconst triggerFirstTabEl = document.querySelector('#myTab li:first-child a')\nbootstrap.Tab.getInstance(triggerFirstTabEl).show() // Select first tab\n```\n\n### Fade effect\n\nTo make tabs panel fade in, add `.fade` to each `.tab-pane`. The first tab pane must also have `.show` to make the initial content visible.\n\n```html\n<div class=\"tab-content\">\n  <div class=\"tab-pane fade show active\" id=\"home\" role=\"tabpanel\">...</div>\n  <div class=\"tab-pane fade\" id=\"profile\" role=\"tabpanel\">...</div>\n  <div class=\"tab-pane fade\" id=\"messages\" role=\"tabpanel\">...</div>\n  <div class=\"tab-pane fade\" id=\"settings\" role=\"tabpanel\">...</div>\n</div>\n```\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\nActivates your content as a tab element.\n\nYou can create a tab instance with the constructor, for example:\n\n```js\nconst bsTab = new bootstrap.Tab('#myTab')\n```\n\n{{< bs-table >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's tab. |\n| `getInstance` | Static method which allows you to get the tab instance associated with a DOM element, you can use it like this: `bootstrap.Tab.getInstance(element)`. |\n| `getOrCreateInstance` | Static method which returns a tab instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Tab.getOrCreateInstance(element)`. |\n| `show` | Selects the given tab and shows its associated pane. Any other tab that was previously selected becomes unselected and its associated pane is hidden. **Returns to the caller before the tab pane has actually been shown** (i.e. before the `shown.bs.tab` event occurs). |\n{{< /bs-table >}}\n\n### Events\n\nWhen showing a new tab, the events fire in the following order:\n\n1. `hide.bs.tab` (on the current active tab)\n2. `show.bs.tab` (on the to-be-shown tab)\n3. `hidden.bs.tab` (on the previous active tab, the same one as for the `hide.bs.tab` event)\n4. `shown.bs.tab` (on the newly-active just-shown tab, the same one as for the `show.bs.tab` event)\n\nIf no tab was already active, then the `hide.bs.tab` and `hidden.bs.tab` events will not be fired.\n\n{{< bs-table >}}\n| Event type | Description |\n| --- | --- |\n| `hide.bs.tab` | This event fires when a new tab is to be shown (and thus the previous active tab is to be hidden). Use `event.target` and `event.relatedTarget` to target the current active tab and the new soon-to-be-active tab, respectively. |\n| `hidden.bs.tab` | This event fires after a new tab is shown (and thus the previous active tab is hidden). Use `event.target` and `event.relatedTarget` to target the previous active tab and the new active tab, respectively. |\n| `show.bs.tab` | This event fires on tab show, but before the new tab has been shown. Use `event.target` and `event.relatedTarget` to target the active tab and the previous active tab (if available) respectively. |\n| `shown.bs.tab` | This event fires on tab show after a tab has been shown. Use `event.target` and `event.relatedTarget` to target the active tab and the previous active tab (if available) respectively. |\n{{< /bs-table >}}\n\n```js\nconst tabElms = document.querySelectorAll('a[data-bs-toggle=\"list\"]')\ntabElms.forEach(tabElm => {\n  tabElm.addEventListener('shown.bs.tab', event => {\n    event.target // newly activated tab\n    event.relatedTarget // previous active tab\n  })\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/modal.md",
    "content": "---\nlayout: docs\ntitle: Modal\ndescription: Use Bootstrap's JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nBefore getting started with Bootstrap's modal component, be sure to read the following as our menu options have recently changed.\n\n- Modals are built with HTML, CSS, and JavaScript. They're positioned over everything else in the document and remove scroll from the `<body>` so that modal content scrolls instead.\n- Clicking on the modal \"backdrop\" will automatically close the modal.\n- Bootstrap only supports one modal window at a time. Nested modals aren't supported as we believe them to be poor user experiences.\n- Modals use `position: fixed`, which can sometimes be a bit particular about its rendering. Whenever possible, place your modal HTML in a top-level position to avoid potential interference from other elements. You'll likely run into issues when nesting a `.modal` within another fixed element.\n- Once again, due to `position: fixed`, there are some caveats with using modals on mobile devices. [See our browser support docs]({{< docsref \"/getting-started/browsers-devices#modals-and-dropdowns-on-mobile\" >}}) for details.\n- Due to how HTML5 defines its semantics, [the `autofocus` HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-autofocus) has no effect in Bootstrap modals. To achieve the same effect, use some custom JavaScript:\n\n```js\nconst myModal = document.getElementById('myModal')\nconst myInput = document.getElementById('myInput')\n\nmyModal.addEventListener('shown.bs.modal', () => {\n  myInput.focus()\n})\n```\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\nKeep reading for demos and usage guidelines.\n\n## Examples\n\n### Modal components\n\nBelow is a _static_ modal example (meaning its `position` and `display` have been overridden). Included are the modal header, modal body (required for `padding`), and modal footer (optional). We ask that you include modal headers with dismiss actions whenever possible, or provide another explicit dismiss action.\n\n<div class=\"bd-example bg-light\">\n  <div class=\"modal position-static d-block\" tabindex=\"-1\">\n    <div class=\"modal-dialog\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h5 class=\"modal-title\">Modal title</h5>\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n        </div>\n        <div class=\"modal-body\">\n          <p>Modal body text goes here.</p>\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n          <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n```html\n<div class=\"modal\" tabindex=\"-1\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h5 class=\"modal-title\">Modal title</h5>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>Modal body text goes here.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\n{{< callout info >}}\nIn the above static example, we use `<h5>`, to avoid issues with the heading hierarchy in the documentation page. Structurally, however, a modal dialog represents its own separate document/context, so the `.modal-title` should ideally be an `<h1>`. If necessary, you can use the [font size utilities]({{< docsref \"/utilities/text#font-size\" >}}) to control the heading's appearance. All the following live examples use this approach.\n{{< /callout >}}\n\n### Live demo\n\nToggle a working modal demo by clicking the button below. It will slide down and fade in from the top of the page.\n\n<div class=\"modal fade\" id=\"exampleModalLive\" tabindex=\"-1\" aria-labelledby=\"exampleModalLiveLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalLiveLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>Woo-hoo, you're reading this text in a modal!</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalLive\">\n    Launch demo modal\n  </button>\n</div>\n\n```html\n<!-- Button trigger modal -->\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModal\">\n  Launch demo modal\n</button>\n\n<!-- Modal -->\n<div class=\"modal fade\" id=\"exampleModal\" tabindex=\"-1\" aria-labelledby=\"exampleModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\n### Static backdrop\n\nWhen backdrop is set to static, the modal will not close when clicking outside of it. Click the button below to try it.\n\n<div class=\"modal fade\" id=\"staticBackdropLive\" data-bs-backdrop=\"static\" data-bs-keyboard=\"false\" tabindex=\"-1\" aria-labelledby=\"staticBackdropLiveLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"staticBackdropLiveLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>I will not close if you click outside of me. Don't even try to press escape key.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Understood</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#staticBackdropLive\">\n    Launch static backdrop modal\n  </button>\n</div>\n\n```html\n<!-- Button trigger modal -->\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#staticBackdrop\">\n  Launch static backdrop modal\n</button>\n\n<!-- Modal -->\n<div class=\"modal fade\" id=\"staticBackdrop\" data-bs-backdrop=\"static\" data-bs-keyboard=\"false\" tabindex=\"-1\" aria-labelledby=\"staticBackdropLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"staticBackdropLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Understood</button>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\n### Scrolling long content\n\nWhen modals become too long for the user's viewport or device, they scroll independent of the page itself. Try the demo below to see what we mean.\n\n<div class=\"modal fade\" id=\"exampleModalLong\" tabindex=\"-1\" aria-labelledby=\"exampleModalLongTitle\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalLongTitle\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\" style=\"min-height: 1500px\">\n        <p>This is some placeholder content to show the scrolling behavior for modals. Instead of repeating the text the modal, we use an inline style set a minimum height, thereby extending the length of the overall modal and demonstrating the overflow scrolling. When content becomes longer than the height of the viewport, scrolling will move the modal as needed.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalLong\">\n    Launch demo modal\n  </button>\n</div>\n\nYou can also create a scrollable modal that allows scroll the modal body by adding `.modal-dialog-scrollable` to `.modal-dialog`.\n\n<div class=\"modal fade\" id=\"exampleModalScrollable\" tabindex=\"-1\" aria-labelledby=\"exampleModalScrollableTitle\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-dialog-scrollable\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalScrollableTitle\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>This is some placeholder content to show the scrolling behavior for modals. We use repeated line breaks to demonstrate how content can exceed minimum inner height, thereby showing inner scrolling. When content becomes longer than the predefined max-height of modal, content will be cropped and scrollable within the modal.</p>\n        <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>\n        <p>This content should appear at the bottom after you scroll.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalScrollable\">\n    Launch demo modal\n  </button>\n</div>\n\n```html\n<!-- Scrollable modal -->\n<div class=\"modal-dialog modal-dialog-scrollable\">\n  ...\n</div>\n```\n\n### Vertically centered\n\nAdd `.modal-dialog-centered` to `.modal-dialog` to vertically center the modal.\n\n<div class=\"modal fade\" id=\"exampleModalCenter\" tabindex=\"-1\" aria-labelledby=\"exampleModalCenterTitle\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-dialog-centered\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalCenterTitle\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>This is a vertically centered modal.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalCenteredScrollable\" tabindex=\"-1\" aria-labelledby=\"exampleModalCenteredScrollableTitle\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-dialog-centered modal-dialog-scrollable\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalCenteredScrollableTitle\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>This is some placeholder content to show a vertically centered modal. We've added some extra copy here to show how vertically centering the modal works when combined with scrollable modals. We also use some repeated line breaks to quickly extend the height of the content, thereby triggering the scrolling. When content becomes longer than the predefined max-height of modal, content will be cropped and scrollable within the modal.</p>\n        <br><br><br><br><br><br><br><br><br><br>\n        <p>Just like that.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalCenter\">\n    Vertically centered modal\n  </button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalCenteredScrollable\">\n    Vertically centered scrollable modal\n  </button>\n</div>\n\n```html\n<!-- Vertically centered modal -->\n<div class=\"modal-dialog modal-dialog-centered\">\n  ...\n</div>\n\n<!-- Vertically centered scrollable modal -->\n<div class=\"modal-dialog modal-dialog-centered modal-dialog-scrollable\">\n  ...\n</div>\n```\n\n### Tooltips and popovers\n\n[Tooltips]({{< docsref \"/components/tooltips\" >}}) and [popovers]({{< docsref \"/components/popovers\" >}}) can be placed within modals as needed. When modals are closed, any tooltips and popovers within are also automatically dismissed.\n\n<div class=\"modal fade\" id=\"exampleModalPopovers\" tabindex=\"-1\" aria-labelledby=\"exampleModalPopoversLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalPopoversLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <h2 class=\"fs-5\">Popover in a modal</h2>\n        <p>This <a href=\"#\" role=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"popover\" title=\"Popover title\" data-bs-content=\"Popover body content is set in this attribute.\" data-bs-container=\"#exampleModalPopovers\">button</a> triggers a popover on click.</p>\n        <hr>\n        <h2 class=\"fs-5\">Tooltips in a modal</h2>\n        <p><a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Tooltip\" data-bs-container=\"#exampleModalPopovers\">This link</a> and <a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Tooltip\" data-bs-container=\"#exampleModalPopovers\">that link</a> have tooltips on hover.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalPopovers\">\n    Launch demo modal\n  </button>\n</div>\n\n```html\n<div class=\"modal-body\">\n  <h2 class=\"fs-5\">Popover in a modal</h2>\n  <p>This <a href=\"#\" role=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"popover\" title=\"Popover title\" data-bs-content=\"Popover body content is set in this attribute.\">button</a> triggers a popover on click.</p>\n  <hr>\n  <h2 class=\"fs-5\">Tooltips in a modal</h2>\n  <p><a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Tooltip\">This link</a> and <a href=\"#\" data-bs-toggle=\"tooltip\" title=\"Tooltip\">that link</a> have tooltips on hover.</p>\n</div>\n```\n\n### Using the grid\n\nUtilize the Bootstrap grid system within a modal by nesting `.container-fluid` within the `.modal-body`. Then, use the normal grid system classes as you would anywhere else.\n\n<div class=\"modal fade\" id=\"gridSystemModal\" tabindex=\"-1\" aria-labelledby=\"gridModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"gridModalLabel\">Grids in modals</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <div class=\"container-fluid bd-example-row\">\n          <div class=\"row\">\n            <div class=\"col-md-4\">.col-md-4</div>\n            <div class=\"col-md-4 ms-auto\">.col-md-4 .ms-auto</div>\n          </div>\n          <div class=\"row\">\n            <div class=\"col-md-3 ms-auto\">.col-md-3 .ms-auto</div>\n            <div class=\"col-md-2 ms-auto\">.col-md-2 .ms-auto</div>\n          </div>\n          <div class=\"row\">\n            <div class=\"col-md-6 ms-auto\">.col-md-6 .ms-auto</div>\n          </div>\n          <div class=\"row\">\n            <div class=\"col-sm-9\">\n              Level 1: .col-sm-9\n              <div class=\"row\">\n                <div class=\"col-8 col-sm-6\">\n                  Level 2: .col-8 .col-sm-6\n                </div>\n                <div class=\"col-4 col-sm-6\">\n                  Level 2: .col-4 .col-sm-6\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#gridSystemModal\">\n  Launch demo modal\n</button>\n</div>\n\n```html\n<div class=\"modal-body\">\n  <div class=\"container-fluid\">\n    <div class=\"row\">\n      <div class=\"col-md-4\">.col-md-4</div>\n      <div class=\"col-md-4 ms-auto\">.col-md-4 .ms-auto</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-md-3 ms-auto\">.col-md-3 .ms-auto</div>\n      <div class=\"col-md-2 ms-auto\">.col-md-2 .ms-auto</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-md-6 ms-auto\">.col-md-6 .ms-auto</div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-sm-9\">\n        Level 1: .col-sm-9\n        <div class=\"row\">\n          <div class=\"col-8 col-sm-6\">\n            Level 2: .col-8 .col-sm-6\n          </div>\n          <div class=\"col-4 col-sm-6\">\n            Level 2: .col-4 .col-sm-6\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\n### Varying modal content\n\nHave a bunch of buttons that all trigger the same modal with slightly different contents? Use `event.relatedTarget` and [HTML `data-bs-*` attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) to vary the contents of the modal depending on which button was clicked.\n\nBelow is a live demo followed by example HTML and JavaScript. For more information, [read the modal events docs](#events) for details on `relatedTarget`.\n\n{{< example stackblitz_add_js=\"true\" >}}\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModal\" data-bs-whatever=\"@mdo\">Open modal for @mdo</button>\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModal\" data-bs-whatever=\"@fat\">Open modal for @fat</button>\n<button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModal\" data-bs-whatever=\"@getbootstrap\">Open modal for @getbootstrap</button>\n\n<div class=\"modal fade\" id=\"exampleModal\" tabindex=\"-1\" aria-labelledby=\"exampleModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalLabel\">New message</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <form>\n          <div class=\"mb-3\">\n            <label for=\"recipient-name\" class=\"col-form-label\">Recipient:</label>\n            <input type=\"text\" class=\"form-control\" id=\"recipient-name\">\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"message-text\" class=\"col-form-label\">Message:</label>\n            <textarea class=\"form-control\" id=\"message-text\"></textarea>\n          </div>\n        </form>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Send message</button>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n```js\nconst exampleModal = document.getElementById('exampleModal')\nexampleModal.addEventListener('show.bs.modal', event => {\n  // Button that triggered the modal\n  const button = event.relatedTarget\n  // Extract info from data-bs-* attributes\n  const recipient = button.getAttribute('data-bs-whatever')\n  // If necessary, you could initiate an AJAX request here\n  // and then do the updating in a callback.\n  //\n  // Update the modal's content.\n  const modalTitle = exampleModal.querySelector('.modal-title')\n  const modalBodyInput = exampleModal.querySelector('.modal-body input')\n\n  modalTitle.textContent = `New message to ${recipient}`\n  modalBodyInput.value = recipient\n})\n```\n\n### Toggle between modals\n\nToggle between multiple modals with some clever placement of the `data-bs-target` and `data-bs-toggle` attributes. For example, you could toggle a password reset modal from within an already open sign in modal. **Please note multiple modals cannot be open at the same time**—this method simply toggles between two separate modals.\n\n{{< example >}}\n<div class=\"modal fade\" id=\"exampleModalToggle\" aria-hidden=\"true\" aria-labelledby=\"exampleModalToggleLabel\" tabindex=\"-1\">\n  <div class=\"modal-dialog modal-dialog-centered\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalToggleLabel\">Modal 1</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        Show a second modal and hide this one with the button below.\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-bs-target=\"#exampleModalToggle2\" data-bs-toggle=\"modal\">Open second modal</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"exampleModalToggle2\" aria-hidden=\"true\" aria-labelledby=\"exampleModalToggleLabel2\" tabindex=\"-1\">\n  <div class=\"modal-dialog modal-dialog-centered\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalToggleLabel2\">Modal 2</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        Hide this modal and show the first with the button below.\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-bs-target=\"#exampleModalToggle\" data-bs-toggle=\"modal\">Back to first</button>\n      </div>\n    </div>\n  </div>\n</div>\n<a class=\"btn btn-primary\" data-bs-toggle=\"modal\" href=\"#exampleModalToggle\" role=\"button\">Open first modal</a>\n{{< /example >}}\n\n### Change animation\n\nThe `$modal-fade-transform` variable determines the transform state of `.modal-dialog` before the modal fade-in animation, the `$modal-show-transform` variable determines the transform of `.modal-dialog` at the end of the modal fade-in animation.\n\nIf you want for example a zoom-in animation, you can set `$modal-fade-transform: scale(.8)`.\n\n### Remove animation\n\nFor modals that simply appear rather than fade in to view, remove the `.fade` class from your modal markup.\n\n```html\n<div class=\"modal\" tabindex=\"-1\" aria-labelledby=\"...\" aria-hidden=\"true\">\n  ...\n</div>\n```\n\n### Dynamic heights\n\nIf the height of a modal changes while it is open, you should call `myModal.handleUpdate()` to readjust the modal's position in case a scrollbar appears.\n\n### Accessibility\n\nBe sure to add `aria-labelledby=\"...\"`, referencing the modal title, to `.modal`. Additionally, you may give a description of your modal dialog with `aria-describedby` on `.modal`. Note that you don't need to add `role=\"dialog\"` since we already add it via JavaScript.\n\n### Embedding YouTube videos\n\nEmbedding YouTube videos in modals requires additional JavaScript not in Bootstrap to automatically stop playback and more. [See this helpful Stack Overflow post](https://stackoverflow.com/questions/18622508/bootstrap-3-and-youtube-in-modal) for more information.\n\n## Optional sizes\n\nModals have three optional sizes, available via modifier classes to be placed on a `.modal-dialog`. These sizes kick in at certain breakpoints to avoid horizontal scrollbars on narrower viewports.\n\n{{< bs-table \"table\" >}}\n| Size | Class | Modal max-width\n| --- | --- | --- |\n| Small | `.modal-sm` | `300px` |\n| Default | <span class=\"text-muted\">None</span> | `500px` |\n| Large | `.modal-lg` | `800px` |\n| Extra large | `.modal-xl` | `1140px` |\n{{< /bs-table >}}\n\nOur default modal without modifier class constitutes the \"medium\" size modal.\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalXl\">Extra large modal</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalLg\">Large modal</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalSm\">Small modal</button>\n</div>\n\n```html\n<div class=\"modal-dialog modal-xl\">...</div>\n<div class=\"modal-dialog modal-lg\">...</div>\n<div class=\"modal-dialog modal-sm\">...</div>\n```\n\n<div class=\"modal fade\" id=\"exampleModalXl\" tabindex=\"-1\" aria-labelledby=\"exampleModalXlLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-xl\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalXlLabel\">Extra large modal</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalLg\" tabindex=\"-1\" aria-labelledby=\"exampleModalLgLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-lg\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalLgLabel\">Large modal</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalSm\" tabindex=\"-1\" aria-labelledby=\"exampleModalSmLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-sm\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalSmLabel\">Small modal</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n    </div>\n  </div>\n</div>\n\n## Fullscreen Modal\n\nAnother override is the option to pop up a modal that covers the user viewport, available via modifier classes that are placed on a `.modal-dialog`.\n\n{{< bs-table >}}\n| Class | Availability |\n| --- | --- | --- |\n| `.modal-fullscreen` | Always |\n| `.modal-fullscreen-sm-down` | `576px` |\n| `.modal-fullscreen-md-down` | `768px` |\n| `.modal-fullscreen-lg-down` | `992px` |\n| `.modal-fullscreen-xl-down` | `1200px` |\n| `.modal-fullscreen-xxl-down` | `1400px` |\n{{< /bs-table >}}\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreen\">Full screen</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreenSm\">Full screen below sm</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreenMd\">Full screen below md</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreenLg\">Full screen below lg</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreenXl\">Full screen below xl</button>\n  <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreenXxl\">Full screen below xxl</button>\n</div>\n\n```html\n<!-- Full screen modal -->\n<div class=\"modal-dialog modal-fullscreen-sm-down\">\n  ...\n</div>\n```\n\n<div class=\"modal fade\" id=\"exampleModalFullscreen\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenLabel\">Full screen modal</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalFullscreenSm\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenSmLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen-sm-down\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenSmLabel\">Full screen below sm</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalFullscreenMd\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenMdLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen-md-down\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenMdLabel\">Full screen below md</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalFullscreenLg\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenLgLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen-lg-down\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenLgLabel\">Full screen below lg</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalFullscreenXl\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenXlLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen-xl-down\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenXlLabel\">Full screen below xl</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalFullscreenXxl\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenXxlLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen-xxl-down\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenXxlLabel\">Full screen below xxl</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, modals now use local CSS variables on `.modal` and `.modal-backdrop` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"modal-css-vars\" file=\"scss/_modal.scss\" >}}\n\n{{< scss-docs name=\"modal-backdrop-css-vars\" file=\"scss/_modal.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"modal-variables\" file=\"scss/_variables.scss\" >}}\n\n### Loop\n\n[Responsive fullscreen modals](#fullscreen-modal) are generated via the `$breakpoints` map and a loop in `scss/_modal.scss`.\n\n{{< scss-docs name=\"modal-fullscreen-loop\" file=\"scss/_modal.scss\" >}}\n\n## Usage\n\nThe modal plugin toggles your hidden content on demand, via data attributes or JavaScript. It also overrides default scrolling behavior and generates a `.modal-backdrop` to provide a click area for dismissing shown modals when clicking outside the modal.\n\n### Via data attributes\n\n#### Toggle\n\nActivate a modal without writing JavaScript. Set `data-bs-toggle=\"modal\"` on a controller element, like a button, along with a `data-bs-target=\"#foo\"` or `href=\"#foo\"` to target a specific modal to toggle.\n\n```html\n<button type=\"button\" data-bs-toggle=\"modal\" data-bs-target=\"#myModal\">Launch modal</button>\n```\n\n#### Dismiss\n\n{{% js-dismiss \"modal\" %}}\n\n{{< callout warning >}}\nWhile both ways to dismiss a modal are supported, keep in mind that dismissing from outside a modal does not match the [ARIA Authoring Practices Guide dialog (modal) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/). Do this at your own risk.\n{{< /callout >}}\n\n### Via JavaScript\n\nCreate a modal with a single line of JavaScript:\n\n```js\nconst myModal = new bootstrap.Modal(document.getElementById('myModal'), options)\n// or\nconst myModalAlternative = new bootstrap.Modal('#myModal', options)\n```\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `backdrop` | boolean, `'static'` | `true` | Includes a modal-backdrop element. Alternatively, specify `static` for a backdrop which doesn't close the modal when clicked. |\n| `focus` | boolean | `true` | Puts the focus on the modal when initialized. |\n| `keyboard` | boolean | `true` | Closes the modal when escape key is pressed. |\n{{< /bs-table >}}\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\n#### Passing options\n\nActivates your content as a modal. Accepts an optional options `object`.\n\n```js\nconst myModal = new bootstrap.Modal('#myModal', {\n  keyboard: false\n})\n```\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's modal. (Removes stored data on the DOM element) |\n| `getInstance` | *Static* method which allows you to get the modal instance associated with a DOM element. |\n| `getOrCreateInstance` | *Static* method which allows you to get the modal instance associated with a DOM element, or create a new one in case it wasn't initialized. |\n| `handleUpdate` | Manually readjust the modal's position if the height of a modal changes while it is open (i.e. in case a scrollbar appears). |\n| `hide` | Manually hides a modal. **Returns to the caller before the modal has actually been hidden** (i.e. before the `hidden.bs.modal` event occurs). |\n| `show` | Manually opens a modal. **Returns to the caller before the modal has actually been shown** (i.e. before the `shown.bs.modal` event occurs). Also, you can pass a DOM element as an argument that can be received in the modal events (as the `relatedTarget` property). (i.e. `const modalToggle = document.getElementById('toggleMyModal'); myModal.show(modalToggle)`. |\n| `toggle` | Manually toggles a modal. **Returns to the caller before the modal has actually been shown or hidden** (i.e. before the `shown.bs.modal` or `hidden.bs.modal` event occurs). |\n{{< /bs-table >}}\n\n### Events\n\nBootstrap's modal class exposes a few events for hooking into modal functionality. All modal events are fired at the modal itself (i.e. at the `<div class=\"modal\">`).\n\n{{< bs-table >}}\n| Event | Description |\n| --- | --- |\n| `hide.bs.modal` | This event is fired immediately when the `hide` instance method has been called. |\n| `hidden.bs.modal` | This event is fired when the modal has finished being hidden from the user (will wait for CSS transitions to complete). |\n| `hidePrevented.bs.modal` | This event is fired when the modal is shown, its backdrop is `static` and a click outside of the modal is performed. The event is also fired when the escape key is pressed and the `keyboard` option is set to `false`. |\n| `show.bs.modal` | This event fires immediately when the `show` instance method is called. If caused by a click, the clicked element is available as the `relatedTarget` property of the event. |\n| `shown.bs.modal` | This event is fired when the modal has been made visible to the user (will wait for CSS transitions to complete). If caused by a click, the clicked element is available as the `relatedTarget` property of the event. |\n{{< /bs-table >}}\n\n```js\nconst myModalEl = document.getElementById('myModal')\nmyModalEl.addEventListener('hidden.bs.modal', event => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/navbar.md",
    "content": "---\nlayout: docs\ntitle: Navbar\ndescription: Documentation and examples for Bootstrap's powerful, responsive navigation header, the navbar. Includes support for branding, navigation, and more, including support for our collapse plugin.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nHere's what you need to know before getting started with the navbar:\n\n- Navbars require a wrapping `.navbar` with `.navbar-expand{-sm|-md|-lg|-xl|-xxl}` for responsive collapsing and [color scheme](#color-schemes) classes.\n- Navbars and their contents are fluid by default. Change the [container](#containers) to limit their horizontal width in different ways.\n- Use our [spacing]({{< docsref \"/utilities/spacing\" >}}) and [flex]({{< docsref \"/utilities/flex\" >}}) utility classes for controlling spacing and alignment within navbars.\n- Navbars are responsive by default, but you can easily modify them to change that. Responsive behavior depends on our Collapse JavaScript plugin.\n- Ensure accessibility by using a `<nav>` element or, if using a more generic element such as a `<div>`, add a `role=\"navigation\"` to every navbar to explicitly identify it as a landmark region for users of assistive technologies.\n- Indicate the current item by using `aria-current=\"page\"` for the current page or `aria-current=\"true\"` for the current item in a set.\n- **New in v5.2.0:** Navbars can be themed with CSS variables that are scoped to the `.navbar` base class. `.navbar-light` has been deprecated and `.navbar-dark` has been rewritten to override CSS variables instead of adding additional styles.\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Supported content\n\nNavbars come with built-in support for a handful of sub-components. Choose from the following as needed:\n\n- `.navbar-brand` for your company, product, or project name.\n- `.navbar-nav` for a full-height and lightweight navigation (including support for dropdowns).\n- `.navbar-toggler` for use with our collapse plugin and other [navigation toggling](#responsive-behaviors) behaviors.\n- Flex and spacing utilities for any form controls and actions.\n- `.navbar-text` for adding vertically centered strings of text.\n- `.collapse.navbar-collapse` for grouping and hiding navbar contents by a parent breakpoint.\n- Add an optional `.navbar-scroll` to set a `max-height` and [scroll expanded navbar content](#scrolling).\n\nHere's an example of all the sub-components included in a responsive light-themed navbar that automatically collapses at the `lg` (large) breakpoint.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item dropdown\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            Dropdown\n          </a>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><hr class=\"dropdown-divider\"></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\nThis example uses [background]({{< docsref \"/utilities/background\" >}}) (`bg-light`) and [spacing]({{< docsref \"/utilities/spacing\" >}}) (`me-auto`, `mb-2`, `mb-lg-0`, `me-2`) utility classes.\n\n### Brand\n\nThe `.navbar-brand` can be applied to most elements, but an anchor works best, as some elements might require utility classes or custom styles.\n\n#### Text\n\nAdd your text within an element with the `.navbar-brand` class.\n\n{{< example >}}\n<!-- As a link -->\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n  </div>\n</nav>\n\n<!-- As a heading -->\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <span class=\"navbar-brand mb-0 h1\">Navbar</span>\n  </div>\n</nav>\n{{< /example >}}\n\n#### Image\n\nYou can replace the text within the `.navbar-brand` with an `<img>`.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <div class=\"container\">\n    <a class=\"navbar-brand\" href=\"#\">\n      <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"Bootstrap\" width=\"30\" height=\"24\">\n    </a>\n  </div>\n</nav>\n{{< /example >}}\n\n#### Image and text\n\nYou can also make use of some additional utilities to add an image and text at the same time. Note the addition of `.d-inline-block` and `.align-text-top` on the `<img>`.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">\n      <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"Logo\" width=\"30\" height=\"24\" class=\"d-inline-block align-text-top\">\n      Bootstrap\n    </a>\n  </div>\n</nav>\n{{< /example >}}\n\n### Nav\n\nNavbar navigation links build on our `.nav` options with their own modifier class and require the use of [toggler classes](#toggler) for proper responsive styling. **Navigation in navbars will also grow to occupy as much horizontal space as possible** to keep your navbar contents securely aligned.\n\nAdd the `.active` class on `.nav-link` to indicate the current page.\n\nPlease note that you should also add the `aria-current` attribute on the active `.nav-link`.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNav\" aria-controls=\"navbarNav\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarNav\">\n      <ul class=\"navbar-nav\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Features</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Pricing</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\nAnd because we use classes for our navs, you can avoid the list-based approach entirely if you like.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNavAltMarkup\" aria-controls=\"navbarNavAltMarkup\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarNavAltMarkup\">\n      <div class=\"navbar-nav\">\n        <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        <a class=\"nav-link\" href=\"#\">Features</a>\n        <a class=\"nav-link\" href=\"#\">Pricing</a>\n        <a class=\"nav-link disabled\">Disabled</a>\n      </div>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\nYou can also use dropdowns in your navbar. Dropdown menus require a wrapping element for positioning, so be sure to use separate and nested elements for `.nav-item` and `.nav-link` as shown below.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNavDropdown\" aria-controls=\"navbarNavDropdown\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarNavDropdown\">\n      <ul class=\"navbar-nav\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Features</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Pricing</a>\n        </li>\n        <li class=\"nav-item dropdown\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            Dropdown link\n          </a>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n      </ul>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\n### Forms\n\nPlace various form controls and components within a navbar:\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <form class=\"d-flex\" role=\"search\">\n      <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n      <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n    </form>\n  </div>\n</nav>\n{{< /example >}}\n\nImmediate child elements of `.navbar` use flex layout and will default to `justify-content: space-between`. Use additional [flex utilities]({{< docsref \"/utilities/flex\" >}}) as needed to adjust this behavior.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\">Navbar</a>\n    <form class=\"d-flex\" role=\"search\">\n      <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n      <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n    </form>\n  </div>\n</nav>\n{{< /example >}}\n\nInput groups work, too. If your navbar is an entire form, or mostly a form, you can use the `<form>` element as the container and save some HTML.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <form class=\"container-fluid\">\n    <div class=\"input-group\">\n      <span class=\"input-group-text\" id=\"basic-addon1\">@</span>\n      <input type=\"text\" class=\"form-control\" placeholder=\"Username\" aria-label=\"Username\" aria-describedby=\"basic-addon1\">\n    </div>\n  </form>\n</nav>\n{{< /example >}}\n\nVarious buttons are supported as part of these navbar forms, too. This is also a great reminder that vertical alignment utilities can be used to align different sized elements.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <form class=\"container-fluid justify-content-start\">\n    <button class=\"btn btn-outline-success me-2\" type=\"button\">Main button</button>\n    <button class=\"btn btn-sm btn-outline-secondary\" type=\"button\">Smaller button</button>\n  </form>\n</nav>\n{{< /example >}}\n\n### Text\n\nNavbars may contain bits of text with the help of `.navbar-text`. This class adjusts vertical alignment and horizontal spacing for strings of text.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <span class=\"navbar-text\">\n      Navbar text with an inline element\n    </span>\n  </div>\n</nav>\n{{< /example >}}\n\nMix and match with other components and utilities as needed.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar w/ text</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarText\" aria-controls=\"navbarText\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarText\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Features</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Pricing</a>\n        </li>\n      </ul>\n      <span class=\"navbar-text\">\n        Navbar text with an inline element\n      </span>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\n## Color schemes\n\n{{< callout warning >}}\n**New in v5.2.0:** Navbar theming is now powered by CSS variables and `.navbar-light` has been deprecated. CSS variables are applied to `.navbar`, defaulting to the \"light\" appearance, and can be overridden with `.navbar-dark`.\n{{< /callout >}}\n\nNavbar themes are easier than ever thanks to Bootstrap's combination of Sass and CSS variables. The default is our \"light navbar\" for use with light background colors, but you can also apply `.navbar-dark` for dark background colors. Then, customize with `.bg-*` utilities.\n\n<div class=\"bd-example\">\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarColor01\" aria-controls=\"navbarColor01\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse\" id=\"navbarColor01\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Features</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Pricing</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">About</a>\n          </li>\n        </ul>\n        <form class=\"d-flex\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-outline-light\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarColor02\" aria-controls=\"navbarColor02\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse\" id=\"navbarColor02\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Features</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Pricing</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">About</a>\n          </li>\n        </ul>\n        <form class=\"d-flex\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-outline-light\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-lg\" style=\"background-color: #e3f2fd;\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarColor03\" aria-controls=\"navbarColor03\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse\" id=\"navbarColor03\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Features</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Pricing</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">About</a>\n          </li>\n        </ul>\n        <form class=\"d-flex\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-outline-primary\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </nav>\n</div>\n\n```html\n<nav class=\"navbar navbar-dark bg-dark\">\n  <!-- Navbar content -->\n</nav>\n\n<nav class=\"navbar navbar-dark bg-primary\">\n  <!-- Navbar content -->\n</nav>\n\n<nav class=\"navbar\" style=\"background-color: #e3f2fd;\">\n  <!-- Navbar content -->\n</nav>\n```\n\n## Containers\n\nAlthough it's not required, you can wrap a navbar in a `.container` to center it on a page–though note that an inner container is still required. Or you can add a container inside the `.navbar` to only center the contents of a [fixed or static top navbar](#placement).\n\n{{< example >}}\n<div class=\"container\">\n  <nav class=\"navbar navbar-expand-lg bg-light\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    </div>\n  </nav>\n</div>\n{{< /example >}}\n\nUse any of the responsive containers to change how wide the content in your navbar is presented.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-md\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n  </div>\n</nav>\n{{< /example >}}\n\n## Placement\n\nUse our [position utilities]({{< docsref \"/utilities/position\" >}}) to place navbars in non-static positions. Choose from fixed to the top, fixed to the bottom, stickied to the top (scrolls with the page until it reaches the top, then stays there), or stickied to the bottom (scrolls with the page until it reaches the bottom, then stays there).\n\nFixed navbars use `position: fixed`, meaning they're pulled from the normal flow of the DOM and may require custom CSS (e.g., `padding-top` on the `<body>`) to prevent overlap with other elements.\n\n{{< example >}}\n<nav class=\"navbar bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Default</a>\n  </div>\n</nav>\n{{< /example >}}\n\n{{< example >}}\n<nav class=\"navbar fixed-top bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Fixed top</a>\n  </div>\n</nav>\n{{< /example >}}\n\n{{< example >}}\n<nav class=\"navbar fixed-bottom bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Fixed bottom</a>\n  </div>\n</nav>\n{{< /example >}}\n\n{{< example >}}\n<nav class=\"navbar sticky-top bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Sticky top</a>\n  </div>\n</nav>\n{{< /example >}}\n\n{{< example >}}\n<nav class=\"navbar sticky-bottom bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Sticky bottom</a>\n  </div>\n</nav>\n{{< /example >}}\n\n## Scrolling\n\nAdd `.navbar-nav-scroll` to a `.navbar-nav` (or other navbar sub-component) to enable vertical scrolling within the toggleable contents of a collapsed navbar. By default, scrolling kicks in at `75vh` (or 75% of the viewport height), but you can override that with the local CSS custom property `--bs-navbar-height` or custom styles. At larger viewports when the navbar is expanded, content will appear as it does in a default navbar.\n\nPlease note that this behavior comes with a potential drawback of `overflow`—when setting `overflow-y: auto` (required to scroll the content here), `overflow-x` is the equivalent of `auto`, which will crop some horizontal content.\n\nHere's an example navbar using `.navbar-nav-scroll` with `style=\"--bs-scroll-height: 100px;\"`, with some extra margin utilities for optimum spacing.\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar scroll</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarScroll\" aria-controls=\"navbarScroll\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarScroll\">\n      <ul class=\"navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll\" style=\"--bs-scroll-height: 100px;\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item dropdown\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            Link\n          </a>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><hr class=\"dropdown-divider\"></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Link</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\n## Responsive behaviors\n\nNavbars can use `.navbar-toggler`, `.navbar-collapse`, and `.navbar-expand{-sm|-md|-lg|-xl|-xxl}` classes to determine when their content collapses behind a button. In combination with other utilities, you can easily choose when to show or hide particular elements.\n\nFor navbars that never collapse, add the `.navbar-expand` class on the navbar. For navbars that always collapse, don't add any `.navbar-expand` class.\n\n### Toggler\n\nNavbar togglers are left-aligned by default, but should they follow a sibling element like a `.navbar-brand`, they'll automatically be aligned to the far right. Reversing your markup will reverse the placement of the toggler. Below are examples of different toggle styles.\n\nWith no `.navbar-brand` shown at the smallest breakpoint:\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarTogglerDemo01\" aria-controls=\"navbarTogglerDemo01\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarTogglerDemo01\">\n      <a class=\"navbar-brand\" href=\"#\">Hidden brand</a>\n      <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\nWith a brand name shown on the left and toggler on the right:\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarTogglerDemo02\" aria-controls=\"navbarTogglerDemo02\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarTogglerDemo02\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\nWith a toggler on the left and brand name on the right:\n\n{{< example >}}\n<nav class=\"navbar navbar-expand-lg bg-light\">\n  <div class=\"container-fluid\">\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarTogglerDemo03\" aria-controls=\"navbarTogglerDemo03\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <div class=\"collapse navbar-collapse\" id=\"navbarTogglerDemo03\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\n### External content\n\nSometimes you want to use the collapse plugin to trigger a container element for content that structurally sits outside of the `.navbar` . Because our plugin works on the `id` and `data-bs-target` matching, that's easily done!\n\n{{< example >}}\n<div class=\"collapse\" id=\"navbarToggleExternalContent\">\n  <div class=\"bg-dark p-4\">\n    <h5 class=\"text-white h4\">Collapsed content</h5>\n    <span class=\"text-muted\">Toggleable via the navbar brand.</span>\n  </div>\n</div>\n<nav class=\"navbar navbar-dark bg-dark\">\n  <div class=\"container-fluid\">\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarToggleExternalContent\" aria-controls=\"navbarToggleExternalContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n  </div>\n</nav>\n{{< /example >}}\n\nWhen you do this, we recommend including additional JavaScript to move the focus programmatically to the container when it is opened. Otherwise, keyboard users and users of assistive technologies will likely have a hard time finding the newly revealed content - particularly if the container that was opened comes *before* the toggler in the document's structure. We also recommend making sure that the toggler has the `aria-controls` attribute, pointing to the `id` of the content container. In theory, this allows assistive technology users to jump directly from the toggler to the container it controls–but support for this is currently quite patchy.\n\n### Offcanvas\n\nTransform your expanding and collapsing navbar into an offcanvas drawer with the [offcanvas component]({{< docsref \"/components/offcanvas\" >}}). We extend both the offcanvas default styles and use our `.navbar-expand-*` classes to create a dynamic and flexible navigation sidebar.\n\nIn the example below, to create an offcanvas navbar that is always collapsed across all breakpoints, omit the `.navbar-expand-*` class entirely.\n\n{{< example >}}\n<nav class=\"navbar bg-light fixed-top\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Offcanvas navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasNavbar\" aria-controls=\"offcanvasNavbar\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"offcanvas offcanvas-end\" tabindex=\"-1\" id=\"offcanvasNavbar\" aria-labelledby=\"offcanvasNavbarLabel\">\n      <div class=\"offcanvas-header\">\n        <h5 class=\"offcanvas-title\" id=\"offcanvasNavbarLabel\">Offcanvas</h5>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"offcanvas-body\">\n        <ul class=\"navbar-nav justify-content-end flex-grow-1 pe-3\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropdown\n            </a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li>\n                <hr class=\"dropdown-divider\">\n              </li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form class=\"d-flex mt-3\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\nTo create an offcanvas navbar that expands into a normal navbar at a specific breakpoint like `lg`, use `.navbar-expand-lg`.\n\n```html\n<nav class=\"navbar navbar-expand-lg bg-light fixed-top\">\n  <a class=\"navbar-brand\" href=\"#\">Offcanvas navbar</a>\n  <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#navbarOffcanvasLg\" aria-controls=\"navbarOffcanvasLg\">\n    <span class=\"navbar-toggler-icon\"></span>\n  </button>\n  <div class=\"offcanvas offcanvas-end\" tabindex=\"-1\" id=\"navbarOffcanvasLg\" aria-labelledby=\"navbarOffcanvasLgLabel\">\n    ...\n  </div>\n</nav>\n```\n\nWhen using offcanvas in a dark navbar, be aware that you may need to have a dark background on the offcanvas content to avoid the text becoming illegible. In the example below, we add `.navbar-dark` and `.bg-dark` to the `.navbar`, `.text-bg-dark` to the `.offcanvas`, `.dropdown-menu-dark` to `.dropdown-menu`, and `.btn-close-white` to `.btn-close` for proper styling with a dark offcanvas.\n\n{{< example >}}\n<nav class=\"navbar navbar-dark bg-dark fixed-top\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Offcanvas dark navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasDarkNavbar\" aria-controls=\"offcanvasDarkNavbar\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"offcanvas offcanvas-end text-bg-dark\" tabindex=\"-1\" id=\"offcanvasDarkNavbar\" aria-labelledby=\"offcanvasDarkNavbarLabel\">\n      <div class=\"offcanvas-header\">\n        <h5 class=\"offcanvas-title\" id=\"offcanvasDarkNavbarLabel\">Dark offcanvas</h5>\n        <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"offcanvas-body\">\n        <ul class=\"navbar-nav justify-content-end flex-grow-1 pe-3\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropdown\n            </a>\n            <ul class=\"dropdown-menu dropdown-menu-dark\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li>\n                <hr class=\"dropdown-divider\">\n              </li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form class=\"d-flex mt-3\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-success\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </div>\n</nav>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, navbars now use local CSS variables on `.navbar` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"navbar-css-vars\" file=\"scss/_navbar.scss\" >}}\n\nSome additional CSS variables are also present on `.navbar-nav`:\n\n{{< scss-docs name=\"navbar-nav-css-vars\" file=\"scss/_navbar.scss\" >}}\n\nCustomization through CSS variables can be seen on the `.navbar-dark` class where we override specific values without adding duplicate CSS selectors.\n\n{{< scss-docs name=\"navbar-dark-css-vars\" file=\"scss/_navbar.scss\" >}}\n\n### Sass variables\n\nVariables for all navbars:\n\n{{< scss-docs name=\"navbar-variables\" file=\"scss/_variables.scss\" >}}\n\nVariables for the [dark navbar](#color-schemes):\n\n{{< scss-docs name=\"navbar-dark-variables\" file=\"scss/_variables.scss\" >}}\n\n### Sass loop\n\n[Responsive navbar expand/collapse classes](#responsive-behaviors) (e.g., `.navbar-expand-lg`) are combined with the `$breakpoints` map and generated through a loop in `scss/_navbar.scss`.\n\n{{< scss-docs name=\"navbar-expand-loop\" file=\"scss/_navbar.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/navs-tabs.md",
    "content": "---\nlayout: docs\ntitle: Navs and tabs\ndescription: Documentation and examples for how to use Bootstrap's included navigation components.\ngroup: components\naliases: \"/docs/5.2/components/navs/\"\ntoc: true\n---\n\n## Base nav\n\nNavigation available in Bootstrap share general markup and styles, from the base `.nav` class to the active and disabled states. Swap modifier classes to switch between each style.\n\nThe base `.nav` component is built with flexbox and provide a strong foundation for building all types of navigation components. It includes some style overrides (for working with lists), some link padding for larger hit areas, and basic disabled styling.\n\n{{< callout info >}}\nThe base `.nav` component does not include any `.active` state. The following examples include the class, mainly to demonstrate that this particular class does not trigger any special styling.\n\nTo convey the active state to assistive technologies, use the `aria-current` attribute — using the `page` value for current page, or `true` for the current item in a set.\n{{< /callout >}}\n\n{{< example >}}\n<ul class=\"nav\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\nClasses are used throughout, so your markup can be super flexible. Use `<ul>`s like above, `<ol>` if the order of your items is important, or roll your own with a `<nav>` element. Because the `.nav` uses `display: flex`, the nav links behave the same as nav items would, but without the extra markup.\n\n{{< example >}}\n<nav class=\"nav\">\n  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  <a class=\"nav-link\" href=\"#\">Link</a>\n  <a class=\"nav-link\" href=\"#\">Link</a>\n  <a class=\"nav-link disabled\">Disabled</a>\n</nav>\n{{< /example >}}\n\n## Available styles\n\nChange the style of `.nav`s component with modifiers and utilities. Mix and match as needed, or build your own.\n\n### Horizontal alignment\n\nChange the horizontal alignment of your nav with [flexbox utilities]({{< docsref \"/layout/grid#horizontal-alignment\" >}}). By default, navs are left-aligned, but you can easily change them to center or right aligned.\n\nCentered with `.justify-content-center`:\n\n{{< example >}}\n<ul class=\"nav justify-content-center\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\nRight-aligned with `.justify-content-end`:\n\n{{< example >}}\n<ul class=\"nav justify-content-end\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\n### Vertical\n\nStack your navigation by changing the flex item direction with the `.flex-column` utility. Need to stack them on some viewports but not others? Use the responsive versions (e.g., `.flex-sm-column`).\n\n{{< example >}}\n<ul class=\"nav flex-column\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\nAs always, vertical navigation is possible without `<ul>`s, too.\n\n{{< example >}}\n<nav class=\"nav flex-column\">\n  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  <a class=\"nav-link\" href=\"#\">Link</a>\n  <a class=\"nav-link\" href=\"#\">Link</a>\n  <a class=\"nav-link disabled\">Disabled</a>\n</nav>\n{{< /example >}}\n\n### Tabs\n\nTakes the basic nav from above and adds the `.nav-tabs` class to generate a tabbed interface. Use them to create tabbable regions with our [tab JavaScript plugin](#javascript-behavior).\n\n{{< example >}}\n<ul class=\"nav nav-tabs\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\n### Pills\n\nTake that same HTML, but use `.nav-pills` instead:\n\n{{< example >}}\n<ul class=\"nav nav-pills\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\n### Fill and justify\n\nForce your `.nav`'s contents to extend the full available width one of two modifier classes. To proportionately fill all available space with your `.nav-item`s, use `.nav-fill`. Notice that all horizontal space is occupied, but not every nav item has the same width.\n\n{{< example >}}\n<ul class=\"nav nav-pills nav-fill\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Much longer nav link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\nWhen using a `<nav>`-based navigation, you can safely omit `.nav-item` as only `.nav-link` is required for styling `<a>` elements.\n\n{{< example >}}\n<nav class=\"nav nav-pills nav-fill\">\n  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  <a class=\"nav-link\" href=\"#\">Much longer nav link</a>\n  <a class=\"nav-link\" href=\"#\">Link</a>\n  <a class=\"nav-link disabled\">Disabled</a>\n</nav>\n{{< /example >}}\n\nFor equal-width elements, use `.nav-justified`. All horizontal space will be occupied by nav links, but unlike the `.nav-fill` above, every nav item will be the same width.\n\n{{< example >}}\n<ul class=\"nav nav-pills nav-justified\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Much longer nav link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\nSimilar to the `.nav-fill` example using a `<nav>`-based navigation.\n\n{{< example >}}\n<nav class=\"nav nav-pills nav-justified\">\n  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  <a class=\"nav-link\" href=\"#\">Much longer nav link</a>\n  <a class=\"nav-link\" href=\"#\">Link</a>\n  <a class=\"nav-link disabled\">Disabled</a>\n</nav>\n\n{{< /example >}}\n## Working with flex utilities\n\nIf you need responsive nav variations, consider using a series of [flexbox utilities]({{< docsref \"/utilities/flex\" >}}). While more verbose, these utilities offer greater customization across responsive breakpoints. In the example below, our nav will be stacked on the lowest breakpoint, then adapt to a horizontal layout that fills the available width starting from the small breakpoint.\n\n{{< example >}}\n<nav class=\"nav nav-pills flex-column flex-sm-row\">\n  <a class=\"flex-sm-fill text-sm-center nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  <a class=\"flex-sm-fill text-sm-center nav-link\" href=\"#\">Longer nav link</a>\n  <a class=\"flex-sm-fill text-sm-center nav-link\" href=\"#\">Link</a>\n  <a class=\"flex-sm-fill text-sm-center nav-link disabled\">Disabled</a>\n</nav>\n{{< /example >}}\n\n## Regarding accessibility\n\nIf you're using navs to provide a navigation bar, be sure to add a `role=\"navigation\"` to the most logical parent container of the `<ul>`, or wrap a `<nav>` element around the whole navigation. Do not add the role to the `<ul>` itself, as this would prevent it from being announced as an actual list by assistive technologies.\n\nNote that navigation bars, even if visually styled as tabs with the `.nav-tabs` class, should **not** be given `role=\"tablist\"`, `role=\"tab\"` or `role=\"tabpanel\"` attributes. These are only appropriate for dynamic tabbed interfaces, as described in the [ARIA Authoring Practices Guide tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/). See [JavaScript behavior](#javascript-behavior) for dynamic tabbed interfaces in this section for an example. The `aria-current` attribute is not necessary on dynamic tabbed interfaces since our JavaScript handles the selected state by adding `aria-selected=\"true\"` on the active tab.\n\n## Using dropdowns\n\nAdd dropdown menus with a little extra HTML and the [dropdowns JavaScript plugin]({{< docsref \"/components/dropdowns#usage\" >}}).\n\n### Tabs with dropdowns\n\n{{< example >}}\n<ul class=\"nav nav-tabs\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item dropdown\">\n    <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\" role=\"button\" aria-expanded=\"false\">Dropdown</a>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\n### Pills with dropdowns\n\n{{< example >}}\n<ul class=\"nav nav-pills\">\n  <li class=\"nav-item\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n  </li>\n  <li class=\"nav-item dropdown\">\n    <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\" role=\"button\" aria-expanded=\"false\">Dropdown</a>\n    <ul class=\"dropdown-menu\">\n      <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      <li><hr class=\"dropdown-divider\"></li>\n      <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n    </ul>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </li>\n  <li class=\"nav-item\">\n    <a class=\"nav-link disabled\">Disabled</a>\n  </li>\n</ul>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, navs now use local CSS variables on `.nav`, `.nav-tabs`, and `.nav-pills` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\nOn the `.nav` base class:\n\n{{< scss-docs name=\"nav-css-vars\" file=\"scss/_nav.scss\" >}}\n\nOn the `.nav-tabs` modifier class:\n\n{{< scss-docs name=\"nav-tabs-css-vars\" file=\"scss/_nav.scss\" >}}\n\nOn the `.nav-pills` modifier class:\n\n{{< scss-docs name=\"nav-pills-css-vars\" file=\"scss/_nav.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"nav-variables\" file=\"scss/_variables.scss\" >}}\n\n## JavaScript behavior\n\nUse the tab JavaScript plugin—include it individually or through the compiled `bootstrap.js` file—to extend our navigational tabs and pills to create tabbable panes of local content.\n\n<div class=\"bd-example\">\n  <ul class=\"nav nav-tabs mb-3\" id=\"myTab\" role=\"tablist\">\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link active\" id=\"home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#home-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"home-tab-pane\" aria-selected=\"true\">Home</button>\n    </li>\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link\" id=\"profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#profile-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"profile-tab-pane\" aria-selected=\"false\">Profile</button>\n    </li>\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link\" id=\"contact-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#contact-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"contact-tab-pane\" aria-selected=\"false\">Contact</button>\n    </li>\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link\" id=\"disabled-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#disabled-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"disabled-tab-pane\" aria-selected=\"false\" disabled>Disabled</button>\n    </li>\n  </ul>\n  <div class=\"tab-content\" id=\"myTabContent\">\n    <div class=\"tab-pane fade show active\" id=\"home-tab-pane\" role=\"tabpanel\" aria-labelledby=\"home-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Home tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"profile-tab-pane\" role=\"tabpanel\" aria-labelledby=\"profile-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Profile tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"contact-tab-pane\" role=\"tabpanel\" aria-labelledby=\"contact-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Contact tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"disabled-tab-pane\" role=\"tabpanel\" aria-labelledby=\"disabled-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Disabled tab's</strong> associated content.</p>\n    </div>\n  </div>\n</div>\n\n```html\n<ul class=\"nav nav-tabs\" id=\"myTab\" role=\"tablist\">\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link active\" id=\"home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#home-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"home-tab-pane\" aria-selected=\"true\">Home</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#profile-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"profile-tab-pane\" aria-selected=\"false\">Profile</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"contact-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#contact-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"contact-tab-pane\" aria-selected=\"false\">Contact</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"disabled-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#disabled-tab-pane\" type=\"button\" role=\"tab\" aria-controls=\"disabled-tab-pane\" aria-selected=\"false\" disabled>Disabled</button>\n  </li>\n</ul>\n<div class=\"tab-content\" id=\"myTabContent\">\n  <div class=\"tab-pane fade show active\" id=\"home-tab-pane\" role=\"tabpanel\" aria-labelledby=\"home-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"profile-tab-pane\" role=\"tabpanel\" aria-labelledby=\"profile-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"contact-tab-pane\" role=\"tabpanel\" aria-labelledby=\"contact-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"disabled-tab-pane\" role=\"tabpanel\" aria-labelledby=\"disabled-tab\" tabindex=\"0\">...</div>\n</div>\n```\n\nTo help fit your needs, this works with `<ul>`-based markup, as shown above, or with any arbitrary \"roll your own\" markup. Note that if you're using `<nav>`, you shouldn't add `role=\"tablist\"` directly to it, as this would override the element's native role as a navigation landmark. Instead, switch to an alternative element (in the example below, a simple `<div>`) and wrap the `<nav>` around it.\n\n<div class=\"bd-example\">\n  <nav>\n    <div class=\"nav nav-tabs mb-3\" id=\"nav-tab\" role=\"tablist\">\n      <button class=\"nav-link active\" id=\"nav-home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-home\" type=\"button\" role=\"tab\" aria-controls=\"nav-home\" aria-selected=\"true\">Home</button>\n      <button class=\"nav-link\" id=\"nav-profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-profile\" type=\"button\" role=\"tab\" aria-controls=\"nav-profile\" aria-selected=\"false\">Profile</button>\n      <button class=\"nav-link\" id=\"nav-contact-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-contact\" type=\"button\" role=\"tab\" aria-controls=\"nav-contact\" aria-selected=\"false\">Contact</button>\n      <button class=\"nav-link\" id=\"nav-disabled-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-disabled\" type=\"button\" role=\"tab\" aria-controls=\"nav-disabled\" aria-selected=\"false\" disabled>Disabled</button>\n    </div>\n  </nav>\n  <div class=\"tab-content\" id=\"nav-tabContent\">\n    <div class=\"tab-pane fade show active\" id=\"nav-home\" role=\"tabpanel\" aria-labelledby=\"nav-home-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Home tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"nav-profile\" role=\"tabpanel\" aria-labelledby=\"nav-profile-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Profile tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"nav-contact\" role=\"tabpanel\" aria-labelledby=\"nav-contact-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Contact tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"nav-disabled\" role=\"tabpanel\" aria-labelledby=\"nav-disabled-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Disabled tab's</strong> associated content.</p>\n    </div>\n  </div>\n</div>\n\n```html\n<nav>\n  <div class=\"nav nav-tabs\" id=\"nav-tab\" role=\"tablist\">\n    <button class=\"nav-link active\" id=\"nav-home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-home\" type=\"button\" role=\"tab\" aria-controls=\"nav-home\" aria-selected=\"true\">Home</button>\n    <button class=\"nav-link\" id=\"nav-profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-profile\" type=\"button\" role=\"tab\" aria-controls=\"nav-profile\" aria-selected=\"false\">Profile</button>\n    <button class=\"nav-link\" id=\"nav-contact-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-contact\" type=\"button\" role=\"tab\" aria-controls=\"nav-contact\" aria-selected=\"false\">Contact</button>\n    <button class=\"nav-link\" id=\"nav-disabled-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-disabled\" type=\"button\" role=\"tab\" aria-controls=\"nav-disabled\" aria-selected=\"false\" disabled>Disabled</button>\n  </div>\n</nav>\n<div class=\"tab-content\" id=\"nav-tabContent\">\n  <div class=\"tab-pane fade show active\" id=\"nav-home\" role=\"tabpanel\" aria-labelledby=\"nav-home-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"nav-profile\" role=\"tabpanel\" aria-labelledby=\"nav-profile-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"nav-contact\" role=\"tabpanel\" aria-labelledby=\"nav-contact-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"nav-disabled\" role=\"tabpanel\" aria-labelledby=\"nav-disabled-tab\" tabindex=\"0\">...</div>\n</div>\n```\n\nThe tabs plugin also works with pills.\n\n<div class=\"bd-example\">\n  <ul class=\"nav nav-pills mb-3\" id=\"pills-tab\" role=\"tablist\">\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link active\" id=\"pills-home-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-home\" type=\"button\" role=\"tab\" aria-controls=\"pills-home\" aria-selected=\"true\">Home</button>\n    </li>\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link\" id=\"pills-profile-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-profile\" type=\"button\" role=\"tab\" aria-controls=\"pills-profile\" aria-selected=\"false\">Profile</button>\n    </li>\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link\" id=\"pills-contact-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-contact\" type=\"button\" role=\"tab\" aria-controls=\"pills-contact\" aria-selected=\"false\">Contact</button>\n    </li>\n    <li class=\"nav-item\" role=\"presentation\">\n      <button class=\"nav-link\" id=\"pills-disabled-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-disabled\" type=\"button\" role=\"tab\" aria-controls=\"pills-disabled\" aria-selected=\"false\" disabled>Disabled</button>\n    </li>\n  </ul>\n  <div class=\"tab-content\" id=\"pills-tabContent\">\n    <div class=\"tab-pane fade show active\" id=\"pills-home\" role=\"tabpanel\" aria-labelledby=\"pills-home-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Home tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"pills-profile\" role=\"tabpanel\" aria-labelledby=\"pills-profile-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Profile tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"pills-contact\" role=\"tabpanel\" aria-labelledby=\"pills-contact-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Contact tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n    </div>\n    <div class=\"tab-pane fade\" id=\"pills-disabled\" role=\"tabpanel\" aria-labelledby=\"pills-disabled-tab\" tabindex=\"0\">\n      <p>This is some placeholder content the <strong>Disabled tab's</strong> associated content.</p>\n    </div>\n  </div>\n</div>\n\n```html\n<ul class=\"nav nav-pills mb-3\" id=\"pills-tab\" role=\"tablist\">\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link active\" id=\"pills-home-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-home\" type=\"button\" role=\"tab\" aria-controls=\"pills-home\" aria-selected=\"true\">Home</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"pills-profile-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-profile\" type=\"button\" role=\"tab\" aria-controls=\"pills-profile\" aria-selected=\"false\">Profile</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"pills-contact-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-contact\" type=\"button\" role=\"tab\" aria-controls=\"pills-contact\" aria-selected=\"false\">Contact</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"pills-disabled-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#pills-disabled\" type=\"button\" role=\"tab\" aria-controls=\"pills-disabled\" aria-selected=\"false\" disabled>Disabled</button>\n  </li>\n</ul>\n<div class=\"tab-content\" id=\"pills-tabContent\">\n  <div class=\"tab-pane fade show active\" id=\"pills-home\" role=\"tabpanel\" aria-labelledby=\"pills-home-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"pills-profile\" role=\"tabpanel\" aria-labelledby=\"pills-profile-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"pills-contact\" role=\"tabpanel\" aria-labelledby=\"pills-contact-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"pills-disabled\" role=\"tabpanel\" aria-labelledby=\"pills-disabled-tab\" tabindex=\"0\">...</div>\n</div>\n```\n\nAnd with vertical pills. Ideally, for vertical tabs, you should also add `aria-orientation=\"vertical\"` to the tab list container.\n\n<div class=\"bd-example\">\n  <div class=\"d-flex align-items-start\">\n    <div class=\"nav flex-column nav-pills me-3\" id=\"v-pills-tab\" role=\"tablist\" aria-orientation=\"vertical\">\n      <button class=\"nav-link active\" id=\"v-pills-home-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-home\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-home\" aria-selected=\"true\">Home</button>\n      <button class=\"nav-link\" id=\"v-pills-profile-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-profile\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-profile\" aria-selected=\"false\">Profile</button>\n      <button class=\"nav-link\" id=\"v-pills-disabled-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-disabled\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-disabled\" aria-selected=\"false\" disabled>Disabled</button>\n      <button class=\"nav-link\" id=\"v-pills-messages-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-messages\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-messages\" aria-selected=\"false\">Messages</button>\n      <button class=\"nav-link\" id=\"v-pills-settings-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-settings\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-settings\" aria-selected=\"false\">Settings</button>\n    </div>\n    <div class=\"tab-content\" id=\"v-pills-tabContent\">\n      <div class=\"tab-pane fade show active\" id=\"v-pills-home\" role=\"tabpanel\" aria-labelledby=\"v-pills-home-tab\" tabindex=\"0\">\n        <p>This is some placeholder content the <strong>Home tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n      </div>\n      <div class=\"tab-pane fade\" id=\"v-pills-profile\" role=\"tabpanel\" aria-labelledby=\"v-pills-profile-tab\" tabindex=\"0\">\n        <p>This is some placeholder content the <strong>Profile tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n      </div>\n      <div class=\"tab-pane fade\" id=\"v-pills-disabled\" role=\"tabpanel\" aria-labelledby=\"v-pills-disabled-tab\" tabindex=\"0\">\n        <p>This is some placeholder content the <strong>Disabled tab's</strong> associated content.</p>\n      </div>\n      <div class=\"tab-pane fade\" id=\"v-pills-messages\" role=\"tabpanel\" aria-labelledby=\"v-pills-messages-tab\" tabindex=\"0\">\n        <p>This is some placeholder content the <strong>Messages tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n      </div>\n      <div class=\"tab-pane fade\" id=\"v-pills-settings\" role=\"tabpanel\" aria-labelledby=\"v-pills-settings-tab\" tabindex=\"0\">\n        <p>This is some placeholder content the <strong>Settings tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n      </div>\n    </div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-items-start\">\n  <div class=\"nav flex-column nav-pills me-3\" id=\"v-pills-tab\" role=\"tablist\" aria-orientation=\"vertical\">\n    <button class=\"nav-link active\" id=\"v-pills-home-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-home\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-home\" aria-selected=\"true\">Home</button>\n    <button class=\"nav-link\" id=\"v-pills-profile-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-profile\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-profile\" aria-selected=\"false\">Profile</button>\n    <button class=\"nav-link\" id=\"v-pills-disabled-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-disabled\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-disabled\" aria-selected=\"false\" disabled>Disabled</button>\n    <button class=\"nav-link\" id=\"v-pills-messages-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-messages\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-messages\" aria-selected=\"false\">Messages</button>\n    <button class=\"nav-link\" id=\"v-pills-settings-tab\" data-bs-toggle=\"pill\" data-bs-target=\"#v-pills-settings\" type=\"button\" role=\"tab\" aria-controls=\"v-pills-settings\" aria-selected=\"false\">Settings</button>\n  </div>\n  <div class=\"tab-content\" id=\"v-pills-tabContent\">\n    <div class=\"tab-pane fade show active\" id=\"v-pills-home\" role=\"tabpanel\" aria-labelledby=\"v-pills-home-tab\" tabindex=\"0\">...</div>\n    <div class=\"tab-pane fade\" id=\"v-pills-profile\" role=\"tabpanel\" aria-labelledby=\"v-pills-profile-tab\" tabindex=\"0\">...</div>\n    <div class=\"tab-pane fade\" id=\"v-pills-disabled\" role=\"tabpanel\" aria-labelledby=\"v-pills-disabled-tab\" tabindex=\"0\">...</div>\n    <div class=\"tab-pane fade\" id=\"v-pills-messages\" role=\"tabpanel\" aria-labelledby=\"v-pills-messages-tab\" tabindex=\"0\">...</div>\n    <div class=\"tab-pane fade\" id=\"v-pills-settings\" role=\"tabpanel\" aria-labelledby=\"v-pills-settings-tab\" tabindex=\"0\">...</div>\n  </div>\n</div>\n```\n\n### Accessibility\n\nDynamic tabbed interfaces, as described in the [ARIA Authoring Practices Guide tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/), require `role=\"tablist\"`, `role=\"tab\"`, `role=\"tabpanel\"`, and additional `aria-` attributes in order to convey their structure, functionality, and current state to users of assistive technologies (such as screen readers). As a best practice, we recommend using `<button>` elements for the tabs, as these are controls that trigger a dynamic change, rather than links that navigate to a new page or location.\n\nIn line with the ARIA Authoring Practices pattern, only the currently active tab receives keyboard focus. When the JavaScript plugin is initialized, it will set `tabindex=\"-1\"` on all inactive tab controls. Once the currently active tab has focus, the cursor keys activate the previous/next tab, with the plugin changing the [roving `tabindex`](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/) accordingly. However, note that the JavaScript plugin does not distinguish between horizontal and vertical tab lists when it comes to cursor key interactions: regardless of the tab list's orientation, both the up *and* left cursor go to the previous tab, and down *and* right cursor go to the next tab.\n\n{{< callout warning >}}\nIn general, to facilitate keyboard navigation, it's recommended to make the tab panels themselves focusable as well, unless the first element containing meaningful content inside the tab panel is already focusable. The JavaScript plugin does not try to handle this aspect—where appropriate, you'll need to explicitly make your tab panels focusable by adding `tabindex=\"0\"` in your markup.\n{{< /callout >}}\n\n{{< callout danger >}}\nThe tab JavaScript plugin **does not** support tabbed interfaces that contain dropdown menus, as these cause both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies.\n{{< /callout >}}\n\n### Using data attributes\n\nYou can activate a tab or pill navigation without writing any JavaScript by simply specifying `data-bs-toggle=\"tab\"` or `data-bs-toggle=\"pill\"` on an element. Use these data attributes on `.nav-tabs` or `.nav-pills`.\n\n```html\n<!-- Nav tabs -->\n<ul class=\"nav nav-tabs\" id=\"myTab\" role=\"tablist\">\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link active\" id=\"home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#home\" type=\"button\" role=\"tab\" aria-controls=\"home\" aria-selected=\"true\">Home</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#profile\" type=\"button\" role=\"tab\" aria-controls=\"profile\" aria-selected=\"false\">Profile</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"messages-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#messages\" type=\"button\" role=\"tab\" aria-controls=\"messages\" aria-selected=\"false\">Messages</button>\n  </li>\n  <li class=\"nav-item\" role=\"presentation\">\n    <button class=\"nav-link\" id=\"settings-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#settings\" type=\"button\" role=\"tab\" aria-controls=\"settings\" aria-selected=\"false\">Settings</button>\n  </li>\n</ul>\n\n<!-- Tab panes -->\n<div class=\"tab-content\">\n  <div class=\"tab-pane active\" id=\"home\" role=\"tabpanel\" aria-labelledby=\"home-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane\" id=\"profile\" role=\"tabpanel\" aria-labelledby=\"profile-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane\" id=\"messages\" role=\"tabpanel\" aria-labelledby=\"messages-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane\" id=\"settings\" role=\"tabpanel\" aria-labelledby=\"settings-tab\" tabindex=\"0\">...</div>\n</div>\n```\n\n### Via JavaScript\n\nEnable tabbable tabs via JavaScript (each tab needs to be activated individually):\n\n```js\nconst triggerTabList = document.querySelectorAll('#myTab button')\ntriggerTabList.forEach(triggerEl => {\n  const tabTrigger = new bootstrap.Tab(triggerEl)\n\n  triggerEl.addEventListener('click', event => {\n    event.preventDefault()\n    tabTrigger.show()\n  })\n})\n```\n\nYou can activate individual tabs in several ways:\n\n```js\nconst triggerEl = document.querySelector('#myTab button[data-bs-target=\"#profile\"]')\nbootstrap.Tab.getInstance(triggerEl).show() // Select tab by name\n\nconst triggerFirstTabEl = document.querySelector('#myTab li:first-child button')\nbootstrap.Tab.getInstance(triggerFirstTabEl).show() // Select first tab\n```\n\n### Fade effect\n\nTo make tabs fade in, add `.fade` to each `.tab-pane`. The first tab pane must also have `.show` to make the initial content visible.\n\n```html\n<div class=\"tab-content\">\n  <div class=\"tab-pane fade show active\" id=\"home\" role=\"tabpanel\" aria-labelledby=\"home-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"profile\" role=\"tabpanel\" aria-labelledby=\"profile-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"messages\" role=\"tabpanel\" aria-labelledby=\"messages-tab\" tabindex=\"0\">...</div>\n  <div class=\"tab-pane fade\" id=\"settings\" role=\"tabpanel\" aria-labelledby=\"settings-tab\" tabindex=\"0\">...</div>\n</div>\n```\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\nActivates your content as a tab element.\n\nYou can create a tab instance with the constructor, for example:\n\n```js\nconst bsTab = new bootstrap.Tab('#myTab')\n```\n\n{{< bs-table >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's tab. |\n| `getInstance` | Static method which allows you to get the tab instance associated with a DOM element, you can use it like this: `bootstrap.Tab.getInstance(element)`. |\n| `getOrCreateInstance` | Static method which returns a tab instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Tab.getOrCreateInstance(element)`. |\n| `show` | Selects the given tab and shows its associated pane. Any other tab that was previously selected becomes unselected and its associated pane is hidden. **Returns to the caller before the tab pane has actually been shown** (i.e. before the `shown.bs.tab` event occurs). |\n{{< /bs-table >}}\n\n### Events\n\nWhen showing a new tab, the events fire in the following order:\n\n1. `hide.bs.tab` (on the current active tab)\n2. `show.bs.tab` (on the to-be-shown tab)\n3. `hidden.bs.tab` (on the previous active tab, the same one as for the `hide.bs.tab` event)\n4. `shown.bs.tab` (on the newly-active just-shown tab, the same one as for the `show.bs.tab` event)\n\nIf no tab was already active, then the `hide.bs.tab` and `hidden.bs.tab` events will not be fired.\n\n{{< bs-table >}}\n| Event type | Description |\n| --- | --- |\n| `hide.bs.tab` | This event fires when a new tab is to be shown (and thus the previous active tab is to be hidden). Use `event.target` and `event.relatedTarget` to target the current active tab and the new soon-to-be-active tab, respectively. |\n| `hidden.bs.tab` | This event fires after a new tab is shown (and thus the previous active tab is hidden). Use `event.target` and `event.relatedTarget` to target the previous active tab and the new active tab, respectively. |\n| `show.bs.tab` | This event fires on tab show, but before the new tab has been shown. Use `event.target` and `event.relatedTarget` to target the active tab and the previous active tab (if available) respectively. |\n| `shown.bs.tab` | This event fires on tab show after a tab has been shown. Use `event.target` and `event.relatedTarget` to target the active tab and the previous active tab (if available) respectively. |\n{{< /bs-table >}}\n\n```js\nconst tabEl = document.querySelector('button[data-bs-toggle=\"tab\"]')\ntabEl.addEventListener('shown.bs.tab', event => {\n  event.target // newly activated tab\n  event.relatedTarget // previous active tab\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/offcanvas.md",
    "content": "---\nlayout: docs\ntitle: Offcanvas\ndescription: Build hidden sidebars into your project for navigation, shopping carts, and more with a few classes and our JavaScript plugin.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nOffcanvas is a sidebar component that can be toggled via JavaScript to appear from the left, right, top, or bottom edge of the viewport. Buttons or anchors are used as triggers that are attached to specific elements you toggle, and `data` attributes are used to invoke our JavaScript.\n\n- Offcanvas shares some of the same JavaScript code as modals. Conceptually, they are quite similar, but they are separate plugins.\n- Similarly, some [source Sass](#sass) variables for offcanvas's styles and dimensions are inherited from the modal's variables.\n- When shown, offcanvas includes a default backdrop that can be clicked to hide the offcanvas.\n- Similar to modals, only one offcanvas can be shown at a time.\n\n**Heads up!** Given how CSS handles animations, you cannot use `margin` or `translate` on an `.offcanvas` element. Instead, use the class as an independent wrapping element.\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Examples\n\n### Offcanvas components\n\nBelow is an offcanvas example that is shown by default (via `.show` on `.offcanvas`). Offcanvas includes support for a header with a close button and an optional body class for some initial `padding`. We suggest that you include offcanvas headers with dismiss actions whenever possible, or provide an explicit dismiss action.\n\n{{< example class=\"bd-example-offcanvas p-0 bg-light overflow-hidden\" >}}\n<div class=\"offcanvas offcanvas-start show\" tabindex=\"-1\" id=\"offcanvas\" aria-labelledby=\"offcanvasLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasLabel\">Offcanvas</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    Content for the offcanvas goes here. You can place just about any Bootstrap component or custom elements here.\n  </div>\n</div>\n{{< /example >}}\n\n### Live demo\n\nUse the buttons below to show and hide an offcanvas element via JavaScript that toggles the `.show` class on an element with the `.offcanvas` class.\n\n- `.offcanvas` hides content (default)\n- `.offcanvas.show` shows content\n\nYou can use a link with the `href` attribute, or a button with the `data-bs-target` attribute. In both cases, the `data-bs-toggle=\"offcanvas\"` is required.\n\n{{< example >}}\n<a class=\"btn btn-primary\" data-bs-toggle=\"offcanvas\" href=\"#offcanvasExample\" role=\"button\" aria-controls=\"offcanvasExample\">\n  Link with href\n</a>\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasExample\" aria-controls=\"offcanvasExample\">\n  Button with data-bs-target\n</button>\n\n<div class=\"offcanvas offcanvas-start\" tabindex=\"-1\" id=\"offcanvasExample\" aria-labelledby=\"offcanvasExampleLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasExampleLabel\">Offcanvas</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    <div class=\"\">\n      Some text as placeholder. In real life you can have the elements you have chosen. Like, text, images, lists, etc.\n    </div>\n    <div class=\"dropdown mt-3\">\n      <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\">\n        Dropdown button\n      </button>\n      <ul class=\"dropdown-menu\">\n        <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n      </ul>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Body scrolling\n\nScrolling the `<body>` element is disabled when an offcanvas and its backdrop are visible. Use the `data-bs-scroll` attribute to enable `<body>` scrolling.\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasScrolling\" aria-controls=\"offcanvasScrolling\">Enable body scrolling</button>\n\n<div class=\"offcanvas offcanvas-start\" data-bs-scroll=\"true\" data-bs-backdrop=\"false\" tabindex=\"-1\" id=\"offcanvasScrolling\" aria-labelledby=\"offcanvasScrollingLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasScrollingLabel\">Offcanvas with body scrolling</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    <p>Try scrolling the rest of the page to see this option in action.</p>\n  </div>\n</div>\n{{< /example >}}\n\n### Body scrolling and backdrop\n\nYou can also enable `<body>` scrolling with a visible backdrop.\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasWithBothOptions\" aria-controls=\"offcanvasWithBothOptions\">Enable both scrolling & backdrop</button>\n\n<div class=\"offcanvas offcanvas-start\" data-bs-scroll=\"true\" tabindex=\"-1\" id=\"offcanvasWithBothOptions\" aria-labelledby=\"offcanvasWithBothOptionsLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasWithBothOptionsLabel\">Backdrop with scrolling</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    <p>Try scrolling the rest of the page to see this option in action.</p>\n  </div>\n</div>\n{{< /example >}}\n\n### Static backdrop\n\nWhen backdrop is set to static, the offcanvas will not close when clicking outside of it.\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#staticBackdrop\" aria-controls=\"staticBackdrop\">\n  Toggle static offcanvas\n</button>\n\n<div class=\"offcanvas offcanvas-start\" data-bs-backdrop=\"static\" tabindex=\"-1\" id=\"staticBackdrop\" aria-labelledby=\"staticBackdropLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"staticBackdropLabel\">Offcanvas</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    <div>\n      I will not close if you click outside of me.\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Dark offcanvas\n\n{{< added-in \"5.2.0\" >}}\n\nChange the appearance of offcanvases with utilities to better match them to different contexts like dark navbars. Here we add `.text-bg-dark` to the `.offcanvas` and `.btn-close-white` to `.btn-close` for proper styling with a dark offcanvas. If you have dropdowns within, consider also adding `.dropdown-menu-dark` to `.dropdown-menu`.\n\n{{< example class=\"bd-example-offcanvas p-0 bg-light overflow-hidden\" >}}\n<div class=\"offcanvas offcanvas-start show text-bg-dark\" tabindex=\"-1\" id=\"offcanvasDark\" aria-labelledby=\"offcanvasDarkLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasDarkLabel\">Offcanvas</h5>\n    <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"offcanvasDark\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    <p>Place offcanvas content here.</p>\n  </div>\n</div>\n{{< /example >}}\n\n## Responsive\n\n{{< added-in \"5.2.0\" >}}\n\nResponsive offcanvas classes hide content outside the viewport from a specified breakpoint and down. Above that breakpoint, the contents within will behave as usual. For example, `.offcanvas-lg` hides content in an offcanvas below the `lg` breakpoint, but shows the content above the `lg` breakpoint.\n\n{{< example >}}\n<button class=\"btn btn-primary d-lg-none\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasResponsive\" aria-controls=\"offcanvasResponsive\">Toggle offcanvas</button>\n\n<div class=\"alert alert-info d-none d-lg-block\">Resize your browser to show the responsive offcanvas toggle.</div>\n\n<div class=\"offcanvas-lg offcanvas-end\" tabindex=\"-1\" id=\"offcanvasResponsive\" aria-labelledby=\"offcanvasResponsiveLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasResponsiveLabel\">Responsive offcanvas</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" data-bs-target=\"#offcanvasResponsive\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    <p class=\"mb-0\">This is content within an <code>.offcanvas-lg</code>.</p>\n  </div>\n</div>\n{{< /example >}}\n\nResponsive offcanvas classes are available across for each breakpoint.\n\n- `.offcanvas`\n- `.offcanvas-sm`\n- `.offcanvas-md`\n- `.offcanvas-lg`\n- `.offcanvas-xl`\n- `.offcanvas-xxl`\n\n## Placement\n\nThere's no default placement for offcanvas components, so you must add one of the modifier classes below.\n\n- `.offcanvas-start` places offcanvas on the left of the viewport (shown above)\n- `.offcanvas-end` places offcanvas on the right of the viewport\n- `.offcanvas-top` places offcanvas on the top of the viewport\n- `.offcanvas-bottom` places offcanvas on the bottom of the viewport\n\nTry the top, right, and bottom examples out below.\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasTop\" aria-controls=\"offcanvasTop\">Toggle top offcanvas</button>\n\n<div class=\"offcanvas offcanvas-top\" tabindex=\"-1\" id=\"offcanvasTop\" aria-labelledby=\"offcanvasTopLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasTopLabel\">Offcanvas top</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    ...\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasRight\" aria-controls=\"offcanvasRight\">Toggle right offcanvas</button>\n\n<div class=\"offcanvas offcanvas-end\" tabindex=\"-1\" id=\"offcanvasRight\" aria-labelledby=\"offcanvasRightLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasRightLabel\">Offcanvas right</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body\">\n    ...\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasBottom\" aria-controls=\"offcanvasBottom\">Toggle bottom offcanvas</button>\n\n<div class=\"offcanvas offcanvas-bottom\" tabindex=\"-1\" id=\"offcanvasBottom\" aria-labelledby=\"offcanvasBottomLabel\">\n  <div class=\"offcanvas-header\">\n    <h5 class=\"offcanvas-title\" id=\"offcanvasBottomLabel\">Offcanvas bottom</h5>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"offcanvas-body small\">\n    ...\n  </div>\n</div>\n{{< /example >}}\n\n## Accessibility\n\nSince the offcanvas panel is conceptually a modal dialog, be sure to add `aria-labelledby=\"...\"`—referencing the offcanvas title—to `.offcanvas`. Note that you don’t need to add `role=\"dialog\"` since we already add it via JavaScript.\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, offcanvas now uses local CSS variables on `.offcanvas` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"offcanvas-css-vars\" file=\"scss/_offcanvas.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"offcanvas-variables\" file=\"scss/_variables.scss\" >}}\n\n## Usage\n\nThe offcanvas plugin utilizes a few classes and attributes to handle the heavy lifting:\n\n- `.offcanvas` hides the content\n- `.offcanvas.show` shows the content\n- `.offcanvas-start` hides the offcanvas on the left\n- `.offcanvas-end` hides the offcanvas on the right\n- `.offcanvas-top` hides the offcanvas on the top\n- `.offcanvas-bottom` hides the offcanvas on the bottom\n\nAdd a dismiss button with the `data-bs-dismiss=\"offcanvas\"` attribute, which triggers the JavaScript functionality. Be sure to use the `<button>` element with it for proper behavior across all devices.\n\n### Via data attributes\n\n#### Toggle\n\nAdd `data-bs-toggle=\"offcanvas\"` and a `data-bs-target` or `href` to the element to automatically assign control of one offcanvas element. The `data-bs-target` attribute accepts a CSS selector to apply the offcanvas to. Be sure to add the class `offcanvas` to the offcanvas element. If you'd like it to default open, add the additional class `show`.\n\n#### Dismiss\n\n{{% js-dismiss \"offcanvas\" %}}\n\n{{< callout warning >}}\nWhile both ways to dismiss an offcanvas are supported, keep in mind that dismissing from outside an offcanvas does not match the [ARIA Authoring Practices Guide dialog (modal) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/). Do this at your own risk.\n{{< /callout >}}\n\n### Via JavaScript\n\nEnable manually with:\n\n```js\nconst offcanvasElementList = document.querySelectorAll('.offcanvas')\nconst offcanvasList = [...offcanvasElementList].map(offcanvasEl => new bootstrap.Offcanvas(offcanvasEl))\n```\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `backdrop` | boolean or the string `static` | `true` | Apply a backdrop on body while offcanvas is open. Alternatively, specify `static` for a backdrop which doesn't close the offcanvas when clicked. |\n| `keyboard` | boolean | `true` | Closes the offcanvas when escape key is pressed. |\n| `scroll` | boolean | `false` | Allow body scrolling while offcanvas is open. |\n{{< /bs-table >}}\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\nActivates your content as an offcanvas element. Accepts an optional options `object`.\n\nYou can create an offcanvas instance with the constructor, for example:\n\n```js\nconst bsOffcanvas = new bootstrap.Offcanvas('#myOffcanvas')\n```\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `getInstance` | *Static* method which allows you to get the offcanvas instance associated with a DOM element. |\n| `getOrCreateInstance` | *Static* method which allows you to get the offcanvas instance associated with a DOM element, or create a new one in case it wasn't initialized. |\n| `hide` | Hides an offcanvas element. **Returns to the caller before the offcanvas element has actually been hidden** (i.e. before the `hidden.bs.offcanvas` event occurs). |\n| `show` | Shows an offcanvas element. **Returns to the caller before the offcanvas element has actually been shown** (i.e. before the `shown.bs.offcanvas` event occurs). |\n| `toggle` | Toggles an offcanvas element to shown or hidden. **Returns to the caller before the offcanvas element has actually been shown or hidden** (i.e. before the `shown.bs.offcanvas` or `hidden.bs.offcanvas` event occurs). |\n{{< /bs-table >}}\n\n### Events\n\nBootstrap's offcanvas class exposes a few events for hooking into offcanvas functionality.\n\n{{< bs-table \"table\" >}}\n| Event type | Description |\n| --- | --- |\n| `hide.bs.offcanvas` | This event is fired immediately when the `hide` method has been called. |\n| `hidden.bs.offcanvas` | This event is fired when an offcanvas element has been hidden from the user (will wait for CSS transitions to complete). |\n| `hidePrevented.bs.offcanvas` | This event is fired when the offcanvas is shown, its backdrop is `static` and a click outside of the offcanvas is performed. The event is also fired when the escape key is pressed and the `keyboard` option is set to `false`. |\n| `show.bs.offcanvas` | This event fires immediately when the `show` instance method is called. |\n| `shown.bs.offcanvas` | This event is fired when an offcanvas element has been made visible to the user (will wait for CSS transitions to complete). |\n{{< /bs-table >}}\n\n```js\nconst myOffcanvas = document.getElementById('myOffcanvas')\nmyOffcanvas.addEventListener('hidden.bs.offcanvas', event => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/pagination.md",
    "content": "---\nlayout: docs\ntitle: Pagination\ndescription: Documentation and examples for showing pagination to indicate a series of related content exists across multiple pages.\ngroup: components\ntoc: true\n---\n\n## Overview\n\nWe use a large block of connected links for our pagination, making links hard to miss and easily scalable—all while providing large hit areas. Pagination is built with list HTML elements so screen readers can announce the number of available links. Use a wrapping `<nav>` element to identify it as a navigation section to screen readers and other assistive technologies.\n\nIn addition, as pages likely have more than one such navigation section, it's advisable to provide a descriptive `aria-label` for the `<nav>` to reflect its purpose. For example, if the pagination component is used to navigate between a set of search results, an appropriate label could be `aria-label=\"Search results pages\"`.\n\n{{< example >}}\n<nav aria-label=\"Page navigation example\">\n  <ul class=\"pagination\">\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">Previous</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">Next</a></li>\n  </ul>\n</nav>\n{{< /example >}}\n\n## Working with icons\n\nLooking to use an icon or symbol in place of text for some pagination links? Be sure to provide proper screen reader support with `aria` attributes.\n\n{{< example >}}\n<nav aria-label=\"Page navigation example\">\n  <ul class=\"pagination\">\n    <li class=\"page-item\">\n      <a class=\"page-link\" href=\"#\" aria-label=\"Previous\">\n        <span aria-hidden=\"true\">&laquo;</span>\n      </a>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n    <li class=\"page-item\">\n      <a class=\"page-link\" href=\"#\" aria-label=\"Next\">\n        <span aria-hidden=\"true\">&raquo;</span>\n      </a>\n    </li>\n  </ul>\n</nav>\n{{< /example >}}\n\n## Disabled and active states\n\nPagination links are customizable for different circumstances. Use `.disabled` for links that appear un-clickable and `.active` to indicate the current page.\n\nWhile the `.disabled` class uses `pointer-events: none` to _try_ to disable the link functionality of `<a>`s, that CSS property is not yet standardized and doesn't account for keyboard navigation. As such, you should always add `tabindex=\"-1\"` on disabled links and use custom JavaScript to fully disable their functionality.\n\n{{< example >}}\n<nav aria-label=\"...\">\n  <ul class=\"pagination\">\n    <li class=\"page-item disabled\">\n      <a class=\"page-link\">Previous</a>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n    <li class=\"page-item active\" aria-current=\"page\">\n      <a class=\"page-link\" href=\"#\">2</a>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n    <li class=\"page-item\">\n      <a class=\"page-link\" href=\"#\">Next</a>\n    </li>\n  </ul>\n</nav>\n{{< /example >}}\n\nYou can optionally swap out active or disabled anchors for `<span>`, or omit the anchor in the case of the prev/next arrows, to remove click functionality and prevent keyboard focus while retaining intended styles.\n\n{{< example >}}\n<nav aria-label=\"...\">\n  <ul class=\"pagination\">\n    <li class=\"page-item disabled\">\n      <span class=\"page-link\">Previous</span>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n    <li class=\"page-item active\" aria-current=\"page\">\n      <span class=\"page-link\">2</span>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n    <li class=\"page-item\">\n      <a class=\"page-link\" href=\"#\">Next</a>\n    </li>\n  </ul>\n</nav>\n{{< /example >}}\n\n## Sizing\n\nFancy larger or smaller pagination? Add `.pagination-lg` or `.pagination-sm` for additional sizes.\n\n{{< example >}}\n<nav aria-label=\"...\">\n  <ul class=\"pagination pagination-lg\">\n    <li class=\"page-item active\" aria-current=\"page\">\n      <span class=\"page-link\">1</span>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n  </ul>\n</nav>\n{{< /example >}}\n\n{{< example >}}\n<nav aria-label=\"...\">\n  <ul class=\"pagination pagination-sm\">\n    <li class=\"page-item active\" aria-current=\"page\">\n      <span class=\"page-link\">1</span>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n  </ul>\n</nav>\n{{< /example >}}\n\n## Alignment\n\nChange the alignment of pagination components with [flexbox utilities]({{< docsref \"/utilities/flex\" >}}). For example, with `.justify-content-center`:\n\n{{< example >}}\n<nav aria-label=\"Page navigation example\">\n  <ul class=\"pagination justify-content-center\">\n    <li class=\"page-item disabled\">\n      <a class=\"page-link\">Previous</a>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n    <li class=\"page-item\">\n      <a class=\"page-link\" href=\"#\">Next</a>\n    </li>\n  </ul>\n</nav>\n{{< /example >}}\n\nOr with `.justify-content-end`:\n\n{{< example >}}\n<nav aria-label=\"Page navigation example\">\n  <ul class=\"pagination justify-content-end\">\n    <li class=\"page-item disabled\">\n      <a class=\"page-link\">Previous</a>\n    </li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n    <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n    <li class=\"page-item\">\n      <a class=\"page-link\" href=\"#\">Next</a>\n    </li>\n  </ul>\n</nav>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, pagination now uses local CSS variables on `.pagination` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"pagination-css-vars\" file=\"scss/_pagination.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"pagination-variables\" file=\"scss/_variables.scss\" >}}\n\n### Sass mixins\n\n{{< scss-docs name=\"pagination-mixin\" file=\"scss/mixins/_pagination.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/placeholders.md",
    "content": "---\nlayout: docs\ntitle: Placeholders\ndescription: Use loading placeholders for your components or pages to indicate something may still be loading.\ngroup: components\ntoc: true\nadded: \"5.1\"\n---\n\n## About\n\nPlaceholders can be used to enhance the experience of your application. They're built only with HTML and CSS, meaning you don't need any JavaScript to create them. You will, however, need some custom JavaScript to toggle their visibility. Their appearance, color, and sizing can be easily customized with our utility classes.\n\n## Example\n\nIn the example below, we take a typical card component and recreate it with placeholders applied to create a \"loading card\". Size and proportions are the same between the two.\n\n<div class=\"bd-example bd-example-placeholder-cards d-flex justify-content-around\">\n<div class=\"card\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"false\" background=\"#20c997\" >}}\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n\n<div class=\"card\" aria-hidden=\"true\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"false\" >}}\n  <div class=\"card-body\">\n    <div class=\"h5 card-title placeholder-glow\">\n      <span class=\"placeholder col-6\"></span>\n    </div>\n    <p class=\"card-text placeholder-glow\">\n      <span class=\"placeholder col-7\"></span>\n      <span class=\"placeholder col-4\"></span>\n      <span class=\"placeholder col-4\"></span>\n      <span class=\"placeholder col-6\"></span>\n      <span class=\"placeholder col-8\"></span>\n    </p>\n    <a href=\"#\" tabindex=\"-1\" class=\"btn btn-primary disabled placeholder col-6\"></a>\n  </div>\n</div>\n</div>\n\n```html\n<div class=\"card\">\n  <img src=\"...\" class=\"card-img-top\" alt=\"...\">\n\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card title</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n    <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n  </div>\n</div>\n\n<div class=\"card\" aria-hidden=\"true\">\n  <img src=\"...\" class=\"card-img-top\" alt=\"...\">\n  <div class=\"card-body\">\n    <h5 class=\"card-title placeholder-glow\">\n      <span class=\"placeholder col-6\"></span>\n    </h5>\n    <p class=\"card-text placeholder-glow\">\n      <span class=\"placeholder col-7\"></span>\n      <span class=\"placeholder col-4\"></span>\n      <span class=\"placeholder col-4\"></span>\n      <span class=\"placeholder col-6\"></span>\n      <span class=\"placeholder col-8\"></span>\n    </p>\n    <a href=\"#\" tabindex=\"-1\" class=\"btn btn-primary disabled placeholder col-6\"></a>\n  </div>\n</div>\n```\n\n## How it works\n\nCreate placeholders with the `.placeholder` class and a grid column class (e.g., `.col-6`) to set the `width`. They can replace the text inside an element or be added as a modifier class to an existing component.\n\nWe apply additional styling to `.btn`s via `::before` to ensure the `height` is respected. You may extend this pattern for other situations as needed, or add a `&nbsp;` within the element to reflect the height when actual text is rendered in its place.\n\n{{< example >}}\n<p aria-hidden=\"true\">\n  <span class=\"placeholder col-6\"></span>\n</p>\n\n<a href=\"#\" tabindex=\"-1\" class=\"btn btn-primary disabled placeholder col-4\" aria-hidden=\"true\"></a>\n{{< /example >}}\n\n{{< callout info >}}\nThe use of `aria-hidden=\"true\"` only indicates that the element should be hidden to screen readers. The *loading* behavior of the placeholder depends on how authors will actually use the placeholder styles, how they plan to update things, etc. Some JavaScript code may be needed to *swap* the state of the placeholder and inform AT users of the update.\n{{< /callout >}}\n\n### Width\n\nYou can change the `width` through grid column classes, width utilities, or inline styles.\n\n{{< example >}}\n<span class=\"placeholder col-6\"></span>\n<span class=\"placeholder w-75\"></span>\n<span class=\"placeholder\" style=\"width: 25%;\"></span>\n{{< /example >}}\n\n### Color\n\nBy default, the `placeholder` uses `currentColor`. This can be overridden with a custom color or utility class.\n\n{{< example >}}\n<span class=\"placeholder col-12\"></span>\n{{< placeholders.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<span class=\"placeholder col-12 bg-{{ .name }}\"></span>\n{{- end -}}\n{{< /placeholders.inline >}}\n{{< /example >}}\n\n### Sizing\n\nThe size of `.placeholder`s are based on the typographic style of the parent element. Customize them with sizing modifiers: `.placeholder-lg`, `.placeholder-sm`, or `.placeholder-xs`.\n\n{{< example >}}\n<span class=\"placeholder col-12 placeholder-lg\"></span>\n<span class=\"placeholder col-12\"></span>\n<span class=\"placeholder col-12 placeholder-sm\"></span>\n<span class=\"placeholder col-12 placeholder-xs\"></span>\n{{< /example >}}\n\n### Animation\n\nAnimate placeholders with `.placeholder-glow` or `.placeholder-wave` to better convey the perception of something being _actively_ loaded.\n\n{{< example >}}\n<p class=\"placeholder-glow\">\n  <span class=\"placeholder col-12\"></span>\n</p>\n\n<p class=\"placeholder-wave\">\n  <span class=\"placeholder col-12\"></span>\n</p>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"placeholders\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/popovers.md",
    "content": "---\nlayout: docs\ntitle: Popovers\ndescription: Documentation and examples for adding Bootstrap popovers, like those found in iOS, to any element on your site.\ngroup: components\ntoc: true\n---\n\n## Overview\n\nThings to know when using the popover plugin:\n\n- Popovers rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param \"cdn.popper\" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.\n- Popovers require the [popover plugin]({{< docsref \"/components/popovers\" >}}) as a dependency.\n- Popovers are opt-in for performance reasons, so **you must initialize them yourself**.\n- Zero-length `title` and `content` values will never show a popover.\n- Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc).\n- Triggering popovers on hidden elements will not work.\n- Popovers for `.disabled` or `disabled` elements must be triggered on a wrapper element.\n- When triggered from anchors that wrap across multiple lines, popovers will be centered between the anchors' overall width. Use `.text-nowrap` on your `<a>`s to avoid this behavior.\n- Popovers must be hidden before their corresponding elements have been removed from the DOM.\n- Popovers can be triggered thanks to an element inside a shadow DOM.\n\n{{< callout info >}}\n{{< partial \"callout-info-sanitizer.md\" >}}\n{{< /callout >}}\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\nKeep reading to see how popovers work with some examples.\n\n## Examples\n\n### Enable popovers\n\nAs mentioned above, you must initialize popovers before they can be used. One way to initialize all popovers on a page would be to select them by their `data-bs-toggle` attribute, like so:\n\n```js\nconst popoverTriggerList = document.querySelectorAll('[data-bs-toggle=\"popover\"]')\nconst popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))\n```\n\n### Live demo\n\nWe use JavaScript similar to the snippet above to render the following live popover. Titles are set via `data-bs-title` and body content is set via `data-bs-content`.\n\n{{< callout warning >}}\n{{< partial \"callout-warning-data-bs-title-vs-title.md\" >}}\n{{< /callout >}}\n\n{{< example stackblitz_add_js=\"true\" >}}\n<button type=\"button\" class=\"btn btn-lg btn-danger\" data-bs-toggle=\"popover\" data-bs-title=\"Popover title\" data-bs-content=\"And here's some amazing content. It's very engaging. Right?\">Click to toggle popover</button>\n{{< /example >}}\n\n### Four directions\n\nFour options are available: top, right, bottom, and left. Directions are mirrored when using Bootstrap in RTL. Set `data-bs-placement` to change the direction.\n\n{{< example stackblitz_add_js=\"true\" >}}\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"top\" data-bs-content=\"Top popover\">\n  Popover on top\n</button>\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"right\" data-bs-content=\"Right popover\">\n  Popover on right\n</button>\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"bottom\" data-bs-content=\"Bottom popover\">\n  Popover on bottom\n</button>\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"left\" data-bs-content=\"Left popover\">\n  Popover on left\n</button>\n{{< /example >}}\n\n### Custom `container`\n\nWhen you have some styles on a parent element that interfere with a popover, you'll want to specify a custom `container` so that the popover's HTML appears within that element instead. This is common in responsive tables, input groups, and the like.\n\n```js\nconst popover = new bootstrap.Popover('.example-popover', {\n  container: 'body'\n})\n```\n\nAnother situation where you'll want to set an explicit custom `container` are popovers inside a [modal dialog]({{< docsref \"/components/modal\" >}}), to make sure that the popover itself is appended to the modal. This is particularly important for popovers that contain interactive elements – modal dialogs will trap focus, so unless the popover is a child element of the modal, users won't be able to focus or activate these interactive elements.\n\n```js\nconst popover = new bootstrap.Popover('.example-popover', {\n  container: '.modal-body'\n})\n```\n\n### Custom popovers\n\n{{< added-in \"5.2.0\" >}}\n\nYou can customize the appearance of popovers using [CSS variables](#variables). We set a custom class with `data-bs-custom-class=\"custom-popover\"` to scope our custom appearance and use it to override some of the local CSS variables.\n\n{{< scss-docs name=\"custom-popovers\" file=\"site/assets/scss/_component-examples.scss\" >}}\n\n{{< example class=\"custom-popover-demo\" stackblitz_add_js=\"true\" >}}\n<button type=\"button\" class=\"btn btn-secondary\"\n        data-bs-toggle=\"popover\" data-bs-placement=\"right\"\n        data-bs-custom-class=\"custom-popover\"\n        data-bs-title=\"Custom popover\"\n        data-bs-content=\"This popover is themed via CSS variables.\">\n  Custom popover\n</button>\n{{< /example >}}\n\n### Dismiss on next click\n\nUse the `focus` trigger to dismiss popovers on the user's next click of a different element than the toggle element.\n\n{{< callout danger >}}\n#### Specific markup required for dismiss-on-next-click\n\nFor proper cross-browser and cross-platform behavior, you must use the `<a>` tag, _not_ the `<button>` tag, and you also must include a [`tabindex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) attribute.\n{{< /callout >}}\n\n{{< example stackblitz_add_js=\"true\" >}}\n<a tabindex=\"0\" class=\"btn btn-lg btn-danger\" role=\"button\" data-bs-toggle=\"popover\" data-bs-trigger=\"focus\" data-bs-title=\"Dismissible popover\" data-bs-content=\"And here's some amazing content. It's very engaging. Right?\">Dismissible popover</a>\n{{< /example >}}\n\n```js\nconst popover = new bootstrap.Popover('.popover-dismiss', {\n  trigger: 'focus'\n})\n```\n\n### Disabled elements\n\nElements with the `disabled` attribute aren't interactive, meaning users cannot hover or click them to trigger a popover (or tooltip). As a workaround, you'll want to trigger the popover from a wrapper `<div>` or `<span>`, ideally made keyboard-focusable using `tabindex=\"0\"`.\n\nFor disabled popover triggers, you may also prefer `data-bs-trigger=\"hover focus\"` so that the popover appears as immediate visual feedback to your users as they may not expect to _click_ on a disabled element.\n\n{{< example stackblitz_add_js=\"true\" >}}\n<span class=\"d-inline-block\" tabindex=\"0\" data-bs-toggle=\"popover\" data-bs-trigger=\"hover focus\" data-bs-content=\"Disabled popover\">\n  <button class=\"btn btn-primary\" type=\"button\" disabled>Disabled button</button>\n</span>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap’s evolving CSS variables approach, popovers now use local CSS variables on `.popover` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"popover-css-vars\" file=\"scss/_popover.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"popover-variables\" file=\"scss/_variables.scss\" >}}\n\n## Usage\n\nEnable popovers via JavaScript:\n\n```js\nconst exampleEl = document.getElementById('example')\nconst popover = new bootstrap.Popover(exampleEl, options)\n```\n\n{{< callout warning >}}\n### Making popovers work for keyboard and assistive technology users\n\nTo allow keyboard users to activate your popovers, you should only add them to HTML elements that are traditionally keyboard-focusable and interactive (such as links or form controls). Although arbitrary HTML elements (such as `<span>`s) can be made focusable by adding the `tabindex=\"0\"` attribute, this will add potentially annoying and confusing tab stops on non-interactive elements for keyboard users, and most assistive technologies currently do not announce the popover's content in this situation. Additionally, do not rely solely on `hover` as the trigger for your popovers, as this will make them impossible to trigger for keyboard users.\n\nWhile you can insert rich, structured HTML in popovers with the `html` option, we strongly recommend that you avoid adding an excessive amount of content. The way popovers currently work is that, once displayed, their content is tied to the trigger element with the `aria-describedby` attribute. As a result, the entirety of the popover's content will be announced to assistive technology users as one long, uninterrupted stream.\n\nAdditionally, while it is possible to also include interactive controls (such as form elements or links) in your popover (by adding these elements to the `allowList` of allowed attributes and tags), be aware that currently the popover does not manage keyboard focus order. When a keyboard user opens a popover, focus remains on the triggering element, and as the popover usually does not immediately follow the trigger in the document's structure, there is no guarantee that moving forward/pressing <kbd>TAB</kbd> will move a keyboard user into the popover itself. In short, simply adding interactive controls to a popover is likely to make these controls unreachable/unusable for keyboard users and users of assistive technologies, or at the very least make for an illogical overall focus order. In these cases, consider using a modal dialog instead.\n{{< /callout >}}\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< callout warning >}}\nNote that for security reasons the `sanitize`, `sanitizeFn`, and `allowList` options cannot be supplied using data attributes.\n{{< /callout >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `allowList` | object | [Default value]({{< docsref \"/getting-started/javascript#sanitizer\" >}}) | Object which contains allowed attributes and tags. |\n| `animation` | boolean | `true` | Apply a CSS fade transition to the popover. |\n| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the popover (applies only to Popper's preventOverflow modifier). By default, it's `'clippingParents'` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper's [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). |\n| `container` | string, element, false | `false` | Appends the popover to a specific element. Example: `container: 'body'`. This option is particularly useful in that it allows you to position the popover in the flow of the document near the triggering element - which will prevent the popover from floating away from the triggering element during a window resize. |\n| `content` | string, element, function | `''` | Default content value if `data-bs-content` attribute isn't present. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. |\n| `customClass` | string, function | `''` | Add classes to the popover when it is shown. Note that these classes will be added in addition to any classes specified in the template. To add multiple classes, separate them with spaces: `'class-1 class-2'`. You can also pass a function that should return a single string containing additional class names. |\n| `delay` | number, object | `0` | Delay showing and hiding the popover (ms)—doesn't apply to manual trigger type. If a number is supplied, delay is applied to both hide/show. Object structure is: `delay: { \"show\": 500, \"hide\": 100 }`. |\n| `fallbackPlacements` | string, array | `['top', 'right', 'bottom', 'left']` | Define fallback placements by providing a list of placements in array (in order of preference). For more information refer to Popper's [behavior docs](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements). |\n| `html` | boolean | `false` | Allow HTML in the popover. If true, HTML tags in the popover's `title` will be rendered in the popover. If false, `innerText` property will be used to insert content into the DOM. Use text if you're worried about XSS attacks. |\n| `offset` | number, string, function | `[0, 0]` | Offset of the popover relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset=\"10,20\"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper's [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). |\n| `placement` | string, function | `'top'` | How to position the popover: auto, top, bottom, left, right. When `auto` is specified, it will dynamically reorient the popover. When a function is used to determine the placement, it is called with the popover DOM node as its first argument and the triggering element DOM node as its second. The `this` context is set to the popover instance. |\n| `popperConfig` | null, object, function | `null` | To change Bootstrap's default Popper config, see [Popper's configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it's called with an object that contains the Bootstrap's default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. |\n| `sanitize` | boolean | `true` | Enable or disable the sanitization. If activated `'template'`, `'content'` and `'title'` options will be sanitized. |\n| `sanitizeFn` | null, function | `null` | Here you can supply your own sanitize function. This can be useful if you prefer to use a dedicated library to perform sanitization. |\n| `selector` | string, false | `false` | If a selector is provided, popover objects will be delegated to the specified targets. In practice, this is used to also apply popovers to dynamically added DOM elements (`jQuery.on` support). See [this issue]({{< param repo >}}/issues/4215) and [an informative example](https://codepen.io/Johann-S/pen/djJYPb). **Note**: `title` attribute must not be used as a selector. |\n| `template` | string | `'<div class=\"popover\" role=\"popover\"><div class=\"popover-arrow\"></div><div class=\"popover-inner\"></div></div>'` | Base HTML to use when creating the popover. The popover's `title` will be injected into the `.popover-inner`. `.popover-arrow` will become the popover's arrow. The outermost wrapper element should have the `.popover` class and `role=\"popover\"`. |\n| `title` | string, element, function | `''` | Default title value if `title` attribute isn't present. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. |\n| `trigger` | string | `'hover focus'` | How popover is triggered: click, hover, focus, manual. You may pass multiple triggers; separate them with a space. `'manual'` indicates that the popover will be triggered programmatically via the `.popover('show')`, `.popover('hide')` and `.popover('toggle')` methods; this value cannot be combined with any other trigger. `'hover'` on its own will result in popovers that cannot be triggered via the keyboard, and should only be used if alternative methods for conveying the same information for keyboard users is present. |\n{{< /bs-table >}}\n\n{{< callout info >}}\n#### Data attributes for individual popovers\n\nOptions for individual popovers can alternatively be specified through the use of data attributes, as explained above.\n{{< /callout >}}\n\n#### Using function with `popperConfig`\n\n```js\nconst popover = new bootstrap.Popover(element, {\n  popperConfig(defaultBsPopperConfig) {\n    // const newPopperConfig = {...}\n    // use defaultBsPopperConfig if needed...\n    // return newPopperConfig\n  }\n})\n```\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `disable` | Removes the ability for an element's popover to be shown. The popover will only be able to be shown if it is re-enabled. |\n| `dispose` | Hides and destroys an element's popover (Removes stored data on the DOM element). Popovers that use delegation (which are created using [the `selector` option](#options)) cannot be individually destroyed on descendant trigger elements. |\n| `enable` | Gives an element's popover the ability to be shown. **Popovers are enabled by default.** |\n| `getInstance` | _Static_ method which allows you to get the popover instance associated with a DOM element. |\n| `getOrCreateInstance` | *Static* method which allows you to get the popover instance associated with a DOM element, or create a new one in case it wasn't initialized. |\n| `hide` | Hides an element's popover. **Returns to the caller before the popover has actually been hidden** (i.e. before the `hidden.bs.popover` event occurs). This is considered a \"manual\" triggering of the popover. |\n| `setContent` | Gives a way to change the popover's content after its initialization. |\n| `show` | Reveals an element's popover. **Returns to the caller before the popover has actually been shown** (i.e. before the `shown.bs.popover` event occurs). This is considered a \"manual\" triggering of the popover. Popovers whose title and content are both zero-length are never displayed. |\n| `toggle` | Toggles an element's popover. **Returns to the caller before the popover has actually been shown or hidden** (i.e. before the `shown.bs.popover` or `hidden.bs.popover` event occurs). This is considered a \"manual\" triggering of the popover. |\n| `toggleEnabled` | Toggles the ability for an element's popover to be shown or hidden. |\n| `update` | Updates the position of an element's popover. |\n{{< /bs-table >}}\n\n\n```js\n// getOrCreateInstance example\nconst popover = bootstrap.Popover.getOrCreateInstance('#example') // Returns a Bootstrap popover instance\n\n// setContent example\nmyPopover.setContent({\n  '.popover-header': 'another title',\n  '.popover-body': 'another content'\n})\n\n```\n\n{{< callout info >}}\nThe `setContent` method accepts an `object` argument, where each property-key is a valid `string` selector within the popover template, and each related property-value can be `string` | `element` | `function` | `null`\n{{< /callout >}}\n\n### Events\n\n{{< bs-table >}}\n| Event | Description |\n| --- | --- |\n| `hide.bs.popover` | This event is fired immediately when the `hide` instance method has been called. |\n| `hidden.bs.popover` | This event is fired when the popover has finished being hidden from the user (will wait for CSS transitions to complete). |\n| `inserted.bs.popover` | This event is fired after the `show.bs.popover` event when the popover template has been added to the DOM. |\n| `show.bs.popover` | This event fires immediately when the `show` instance method is called. |\n| `shown.bs.popover` | This event is fired when the popover has been made visible to the user (will wait for CSS transitions to complete). |\n{{< /bs-table >}}\n\n```js\nconst myPopoverTrigger = document.getElementById('myPopover')\nmyPopoverTrigger.addEventListener('hidden.bs.popover', () => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/progress.md",
    "content": "---\nlayout: docs\ntitle: Progress\ndescription: Documentation and examples for using Bootstrap custom progress bars featuring support for stacked bars, animated backgrounds, and text labels.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nProgress components are built with two HTML elements, some CSS to set the width, and a few attributes. We don't use [the HTML5 `<progress>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress), ensuring you can stack progress bars, animate them, and place text labels over them.\n\n- We use the `.progress` as a wrapper to indicate the max value of the progress bar.\n- We use the inner `.progress-bar` to indicate the progress so far.\n- The `.progress-bar` requires an inline style, utility class, or custom CSS to set their width.\n- The `.progress-bar` also requires some `role` and `aria` attributes to make it accessible, including an accessible name (using `aria-label`, `aria-labelledby`, or similar).\n\nPut that all together, and you have the following examples.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Basic example\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Basic example\" style=\"width: 25%\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Basic example\" style=\"width: 50%\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Basic example\" style=\"width: 75%\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Basic example\" style=\"width: 100%\" aria-valuenow=\"100\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n{{< /example >}}\n\nBootstrap provides a handful of [utilities for setting width]({{< docsref \"/utilities/sizing\" >}}). Depending on your needs, these may help with quickly configuring progress.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar w-75\" role=\"progressbar\" aria-label=\"Basic example\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n{{< /example >}}\n\n## Labels\n\nAdd labels to your progress bars by placing text within the `.progress-bar`.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Example with label\" style=\"width: 25%;\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\">25%</div>\n</div>\n{{< /example >}}\n\n## Height\n\nWe only set a `height` value on the `.progress`, so if you change that value the inner `.progress-bar` will automatically resize accordingly.\n\n{{< example >}}\n<div class=\"progress\" style=\"height: 1px;\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Example 1px high\" style=\"width: 25%;\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\" style=\"height: 20px;\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Example 20px high\" style=\"width: 25%;\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n{{< /example >}}\n\n## Backgrounds\n\nUse background utility classes to change the appearance of individual progress bars.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar bg-success\" role=\"progressbar\" aria-label=\"Success example\" style=\"width: 25%\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar bg-info\" role=\"progressbar\" aria-label=\"Info example\" style=\"width: 50%\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar bg-warning\" role=\"progressbar\" aria-label=\"Warning example\" style=\"width: 75%\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar bg-danger\" role=\"progressbar\" aria-label=\"Danger example\" style=\"width: 100%\" aria-valuenow=\"100\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n{{< /example >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n## Multiple bars\n\nInclude multiple progress bars in a progress component if you need.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Segment one\" style=\"width: 15%\" aria-valuenow=\"15\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n  <div class=\"progress-bar bg-success\" role=\"progressbar\" aria-label=\"Segment two\" style=\"width: 30%\" aria-valuenow=\"30\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n  <div class=\"progress-bar bg-info\" role=\"progressbar\" aria-label=\"Segment three\" style=\"width: 20%\" aria-valuenow=\"20\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n{{< /example >}}\n\n## Striped\n\nAdd `.progress-bar-striped` to any `.progress-bar` to apply a stripe via CSS gradient over the progress bar's background color.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar progress-bar-striped\" role=\"progressbar\" aria-label=\"Default striped example\" style=\"width: 10%\" aria-valuenow=\"10\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar progress-bar-striped bg-success\" role=\"progressbar\" aria-label=\"Success striped example\" style=\"width: 25%\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar progress-bar-striped bg-info\" role=\"progressbar\" aria-label=\"Info striped example\" style=\"width: 50%\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar progress-bar-striped bg-warning\" role=\"progressbar\" aria-label=\"Warning striped example\" style=\"width: 75%\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n<div class=\"progress\">\n  <div class=\"progress-bar progress-bar-striped bg-danger\" role=\"progressbar\" aria-label=\"Danger striped example\" style=\"width: 100%\" aria-valuenow=\"100\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n{{< /example >}}\n\n## Animated stripes\n\nThe striped gradient can also be animated. Add `.progress-bar-animated` to `.progress-bar` to animate the stripes right to left via CSS3 animations.\n\n{{< example >}}\n<div class=\"progress\">\n  <div class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" aria-label=\"Animated striped example\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"width: 75%\"></div>\n</div>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, progress bars now use local CSS variables on `.progress` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"progress-css-vars\" file=\"scss/_progress.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"progress-variables\" file=\"scss/_variables.scss\" >}}\n\n### Keyframes\n\nUsed for creating the CSS animations for `.progress-bar-animated`. Included in `scss/_progress-bar.scss`.\n\n{{< scss-docs name=\"progress-keyframes\" file=\"scss/_progress.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/scrollspy.md",
    "content": "---\nlayout: docs\ntitle: Scrollspy\ndescription: Automatically update Bootstrap navigation or list group components based on scroll position to indicate which link is currently active in the viewport.\ngroup: components\ntoc: true\n---\n\n## How it works\n\nScrollspy toggles the `.active` class on anchor (`<a>`) elements when the element with the `id` referenced by the anchor's `href` is scrolled into view. Scrollspy is best used in conjunction with a Bootstrap [nav component]({{< docsref \"/components/navs-tabs\" >}}) or [list group]({{< docsref \"/components/list-group\" >}}), but it will also work with any anchor elements in the current page. Here's how it works.\n\n- To start, scrollspy requires two things: a navigation, list group, or a simple set of links, plus a scrollable container. The scrollable container can be the `<body>` or a custom element with a set `height` and `overflow-y: scroll`.\n\n- On the scrollable container, add `data-bs-spy=\"scroll\"` and `data-bs-target=\"#navId\"` where `navId` is the unique `id` of the associated navigation. Be sure to also include a `tabindex=\"0\"` to ensure keyboard access.\n\n- As you scroll the \"spied\" container, an `.active` class is added and removed from anchor links within the associated navigation. Links must have resolvable `id` targets, otherwise they're ignored. For example, a `<a href=\"#home\">home</a>` must correspond to something in the DOM like `<div id=\"home\"></div>`\n\n- Target elements that are not visible will be ignored. See the [Non-visible elements](#non-visible-elements) section below.\n\n## Examples\n\n### Navbar\n\nScroll the area below the navbar and watch the active class change. Open the dropdown menu and watch the dropdown items be highlighted as well.\n\n<div class=\"bd-example\">\n  <nav id=\"navbar-example2\" class=\"navbar bg-light px-3 mb-3\">\n    <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n    <ul class=\"nav nav-pills\">\n      <li class=\"nav-item\">\n        <a class=\"nav-link\" href=\"#scrollspyHeading1\">First</a>\n      </li>\n      <li class=\"nav-item\">\n        <a class=\"nav-link\" href=\"#scrollspyHeading2\">Second</a>\n      </li>\n      <li class=\"nav-item dropdown\">\n        <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\" role=\"button\" aria-expanded=\"false\">Dropdown</a>\n        <ul class=\"dropdown-menu\">\n          <li><a class=\"dropdown-item\" href=\"#scrollspyHeading3\">Third</a></li>\n          <li><a class=\"dropdown-item\" href=\"#scrollspyHeading4\">Fourth</a></li>\n          <li><hr class=\"dropdown-divider\"></li>\n          <li><a class=\"dropdown-item\" href=\"#scrollspyHeading5\">Fifth</a></li>\n        </ul>\n      </li>\n    </ul>\n  </nav>\n  <div class=\"scrollspy-example bg-light p-3 rounded-2\" data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example2\" data-bs-root-margin=\"0px 0px -40%\" data-bs-smooth-scroll=\"true\" tabindex=\"0\">\n    <h4 id=\"scrollspyHeading1\">First heading</h4>\n    <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n    <h4 id=\"scrollspyHeading2\">Second heading</h4>\n    <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n    <h4 id=\"scrollspyHeading3\">Third heading</h4>\n    <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n    <h4 id=\"scrollspyHeading4\">Fourth heading</h4>\n    <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n    <h4 id=\"scrollspyHeading5\">Fifth heading</h4>\n    <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n  </div>\n</div>\n\n```html\n<nav id=\"navbar-example2\" class=\"navbar bg-light px-3 mb-3\">\n  <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n  <ul class=\"nav nav-pills\">\n    <li class=\"nav-item\">\n      <a class=\"nav-link\" href=\"#scrollspyHeading1\">First</a>\n    </li>\n    <li class=\"nav-item\">\n      <a class=\"nav-link\" href=\"#scrollspyHeading2\">Second</a>\n    </li>\n    <li class=\"nav-item dropdown\">\n      <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\" role=\"button\" aria-expanded=\"false\">Dropdown</a>\n      <ul class=\"dropdown-menu\">\n        <li><a class=\"dropdown-item\" href=\"#scrollspyHeading3\">Third</a></li>\n        <li><a class=\"dropdown-item\" href=\"#scrollspyHeading4\">Fourth</a></li>\n        <li><hr class=\"dropdown-divider\"></li>\n        <li><a class=\"dropdown-item\" href=\"#scrollspyHeading5\">Fifth</a></li>\n      </ul>\n    </li>\n  </ul>\n</nav>\n<div data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example2\" data-bs-root-margin=\"0px 0px -40%\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example bg-light p-3 rounded-2\" tabindex=\"0\">\n  <h4 id=\"scrollspyHeading1\">First heading</h4>\n  <p>...</p>\n  <h4 id=\"scrollspyHeading2\">Second heading</h4>\n  <p>...</p>\n  <h4 id=\"scrollspyHeading3\">Third heading</h4>\n  <p>...</p>\n  <h4 id=\"scrollspyHeading4\">Fourth heading</h4>\n  <p>...</p>\n  <h4 id=\"scrollspyHeading5\">Fifth heading</h4>\n  <p>...</p>\n</div>\n```\n\n### Nested nav\n\nScrollspy also works with nested `.nav`s. If a nested `.nav` is `.active`, its parents will also be `.active`. Scroll the area next to the navbar and watch the active class change.\n\n<div class=\"bd-example\">\n  <div class=\"row\">\n    <div class=\"col-4\">\n      <nav id=\"navbar-example3\" class=\"h-100 flex-column align-items-stretch pe-4 border-end\">\n        <nav class=\"nav nav-pills flex-column\">\n          <a class=\"nav-link\" href=\"#item-1\">Item 1</a>\n          <nav class=\"nav nav-pills flex-column\">\n            <a class=\"nav-link ms-3 my-1\" href=\"#item-1-1\">Item 1-1</a>\n            <a class=\"nav-link ms-3 my-1\" href=\"#item-1-2\">Item 1-2</a>\n          </nav>\n          <a class=\"nav-link\" href=\"#item-2\">Item 2</a>\n          <a class=\"nav-link\" href=\"#item-3\">Item 3</a>\n          <nav class=\"nav nav-pills flex-column\">\n            <a class=\"nav-link ms-3 my-1\" href=\"#item-3-1\">Item 3-1</a>\n            <a class=\"nav-link ms-3 my-1\" href=\"#item-3-2\">Item 3-2</a>\n          </nav>\n        </nav>\n      </nav>\n    </div>\n    <div class=\"col-8\">\n      <div data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example3\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example-2\" tabindex=\"0\">\n        <div id=\"item-1\">\n          <h4>Item 1</h4>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n        <div id=\"item-1-1\">\n          <h5>Item 1-1</h5>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n        <div id=\"item-1-2\">\n          <h5>Item 1-2</h5>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n        <div id=\"item-2\">\n          <h4>Item 2</h4>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n        <div id=\"item-3\">\n          <h4>Item 3</h4>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n        <div id=\"item-3-1\">\n          <h5>Item 3-1</h5>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n        <div id=\"item-3-2\">\n          <h5>Item 3-2</h5>\n          <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          <p>Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.</p>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n```html\n<div class=\"row\">\n  <div class=\"col-4\">\n    <nav id=\"navbar-example3\" class=\"h-100 flex-column align-items-stretch pe-4 border-end\">\n      <nav class=\"nav nav-pills flex-column\">\n        <a class=\"nav-link\" href=\"#item-1\">Item 1</a>\n        <nav class=\"nav nav-pills flex-column\">\n          <a class=\"nav-link ms-3 my-1\" href=\"#item-1-1\">Item 1-1</a>\n          <a class=\"nav-link ms-3 my-1\" href=\"#item-1-2\">Item 1-2</a>\n        </nav>\n        <a class=\"nav-link\" href=\"#item-2\">Item 2</a>\n        <a class=\"nav-link\" href=\"#item-3\">Item 3</a>\n        <nav class=\"nav nav-pills flex-column\">\n          <a class=\"nav-link ms-3 my-1\" href=\"#item-3-1\">Item 3-1</a>\n          <a class=\"nav-link ms-3 my-1\" href=\"#item-3-2\">Item 3-2</a>\n        </nav>\n      </nav>\n    </nav>\n  </div>\n\n  <div class=\"col-8\">\n    <div data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example3\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example-2\" tabindex=\"0\">\n      <div id=\"item-1\">\n        <h4>Item 1</h4>\n        <p>...</p>\n      </div>\n      <div id=\"item-1-1\">\n        <h5>Item 1-1</h5>\n        <p>...</p>\n      </div>\n      <div id=\"item-1-2\">\n        <h5>Item 1-2</h5>\n        <p>...</p>\n      </div>\n      <div id=\"item-2\">\n        <h4>Item 2</h4>\n        <p>...</p>\n      </div>\n      <div id=\"item-3\">\n        <h4>Item 3</h4>\n        <p>...</p>\n      </div>\n      <div id=\"item-3-1\">\n        <h5>Item 3-1</h5>\n        <p>...</p>\n      </div>\n      <div id=\"item-3-2\">\n        <h5>Item 3-2</h5>\n        <p>...</p>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\n### List group\n\nScrollspy also works with `.list-group`s. Scroll the area next to the list group and watch the active class change.\n\n<div class=\"bd-example\">\n  <div class=\"row\">\n    <div class=\"col-4\">\n      <div id=\"list-example\" class=\"list-group\">\n        <a class=\"list-group-item list-group-item-action\" href=\"#list-item-1\">Item 1</a>\n        <a class=\"list-group-item list-group-item-action\" href=\"#list-item-2\">Item 2</a>\n        <a class=\"list-group-item list-group-item-action\" href=\"#list-item-3\">Item 3</a>\n        <a class=\"list-group-item list-group-item-action\" href=\"#list-item-4\">Item 4</a>\n      </div>\n    </div>\n    <div class=\"col-8\">\n      <div data-bs-spy=\"scroll\" data-bs-target=\"#list-example\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example\" tabindex=\"0\">\n        <h4 id=\"list-item-1\">Item 1</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"list-item-2\">Item 2</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"list-item-3\">Item 3</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"list-item-4\">Item 4</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n      </div>\n    </div>\n  </div>\n</div>\n\n```html\n<div class=\"row\">\n  <div class=\"col-4\">\n    <div id=\"list-example\" class=\"list-group\">\n      <a class=\"list-group-item list-group-item-action\" href=\"#list-item-1\">Item 1</a>\n      <a class=\"list-group-item list-group-item-action\" href=\"#list-item-2\">Item 2</a>\n      <a class=\"list-group-item list-group-item-action\" href=\"#list-item-3\">Item 3</a>\n      <a class=\"list-group-item list-group-item-action\" href=\"#list-item-4\">Item 4</a>\n    </div>\n  </div>\n  <div class=\"col-8\">\n    <div data-bs-spy=\"scroll\" data-bs-target=\"#list-example\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example\" tabindex=\"0\">\n      <h4 id=\"list-item-1\">Item 1</h4>\n      <p>...</p>\n      <h4 id=\"list-item-2\">Item 2</h4>\n      <p>...</p>\n      <h4 id=\"list-item-3\">Item 3</h4>\n      <p>...</p>\n      <h4 id=\"list-item-4\">Item 4</h4>\n      <p>...</p>\n    </div>\n  </div>\n</div>\n```\n\n### Simple anchors\n\nScrollspy is not limited to nav components and list groups, so it will work on any `<a>` anchor elements in the current document. Scroll the area and watch the `.active` class change.\n\n<div class=\"bd-example\">\n  <div class=\"row\">\n    <div class=\"col-4\">\n      <div id=\"simple-list-example\" class=\"d-flex flex-column gap-2 simple-list-example-scrollspy text-center\">\n        <a class=\"p-1 rounded\" href=\"#simple-list-item-1\">Item 1</a>\n        <a class=\"p-1 rounded\" href=\"#simple-list-item-2\">Item 2</a>\n        <a class=\"p-1 rounded\" href=\"#simple-list-item-3\">Item 3</a>\n        <a class=\"p-1 rounded\" href=\"#simple-list-item-4\">Item 4</a>\n        <a class=\"p-1 rounded\" href=\"#simple-list-item-5\">Item 5</a>\n      </div>\n    </div>\n    <div class=\"col-8\">\n      <div data-bs-spy=\"scroll\" data-bs-target=\"#simple-list-example\" data-bs-offset=\"0\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example\" tabindex=\"0\">\n        <h4 id=\"simple-list-item-1\">Item 1</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"simple-list-item-2\">Item 2</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"simple-list-item-3\">Item 3</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"simple-list-item-4\">Item 4</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n        <h4 id=\"simple-list-item-5\">Item 5</h4>\n        <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n      </div>\n    </div>\n  </div>\n</div>\n\n```html\n<div class=\"row\">\n  <div class=\"col-4\">\n    <div id=\"simple-list-example\" class=\"d-flex flex-column gap-2 simple-list-example-scrollspy text-center\">\n      <a class=\"p-1 rounded\" href=\"#simple-list-item-1\">Item 1</a>\n      <a class=\"p-1 rounded\" href=\"#simple-list-item-2\">Item 2</a>\n      <a class=\"p-1 rounded\" href=\"#simple-list-item-3\">Item 3</a>\n      <a class=\"p-1 rounded\" href=\"#simple-list-item-4\">Item 4</a>\n      <a class=\"p-1 rounded\" href=\"#simple-list-item-5\">Item 5</a>\n    </div>\n  </div>\n  <div class=\"col-8\">\n    <div data-bs-spy=\"scroll\" data-bs-target=\"#simple-list-example\" data-bs-offset=\"0\" data-bs-smooth-scroll=\"true\" class=\"scrollspy-example\" tabindex=\"0\">\n      <h4 id=\"simple-list-item-1\">Item 1</h4>\n      <p>...</p>\n      <h4 id=\"simple-list-item-2\">Item 2</h4>\n      <p>...</p>\n      <h4 id=\"simple-list-item-3\">Item 3</h4>\n      <p>...</p>\n      <h4 id=\"simple-list-item-4\">Item 4</h4>\n      <p>...</p>\n      <h4 id=\"simple-list-item-5\">Item 5</h4>\n      <p>...</p>\n    </div>\n  </div>\n</div>\n```\n\n## Non-visible elements\n\nTarget elements that aren’t visible will be ignored and their corresponding nav items won't receive an `.active` class. Scrollspy instances initialized in a non-visible wrapper will ignore all target elements. Use the `refresh` method to check for observable elements once the wrapper becomes visible.\n\n```js\ndocument.querySelectorAll('#nav-tab>[data-bs-toggle=\"tab\"]').forEach(el => {\n  el.addEventListener('shown.bs.tab', () => {\n    const target = el.getAttribute('data-bs-target')\n    const scrollElem = document.querySelector(`${target} [data-bs-spy=\"scroll\"]`)\n    bootstrap.ScrollSpy.getOrCreateInstance(scrollElem).refresh()\n  })\n})\n```\n\n## Usage\n\n### Via data attributes\n\nTo easily add scrollspy behavior to your topbar navigation, add `data-bs-spy=\"scroll\"` to the element you want to spy on (most typically this would be the `<body>`). Then add the `data-bs-target` attribute with the `id` or class name of the parent element of any Bootstrap `.nav` component.\n\n```html\n<body data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example\">\n  ...\n  <div id=\"navbar-example\">\n    <ul class=\"nav nav-tabs\" role=\"tablist\">\n      ...\n    </ul>\n  </div>\n  ...\n</body>\n```\n\n### Via JavaScript\n\n```js\nconst scrollSpy = new bootstrap.ScrollSpy(document.body, {\n  target: '#navbar-example'\n})\n```\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `rootMargin` | string | `0px 0px -25%` | Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) valid units, when calculating scroll position. |\n| `smoothScroll` | boolean | `false` | Enables smooth scrolling when a user clicks on a link that refers to ScrollSpy observables. |\n| `target` | string, DOM element | `null` | Specifies element to apply Scrollspy plugin. |\n| `threshold` | array | `[0.1, 0.5, 1]` | `IntersectionObserver` [threshold](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#threshold) valid input, when calculating scroll position. |\n\n{{< /bs-table >}}\n\n{{< callout warning >}}\n**Deprecated Options**\n\nUp until v5.1.3 we were using `offset` & `method` options, which are now deprecated and replaced by `rootMargin`.\nTo keep backwards compatibility, we will continue to parse a given `offset` to `rootMargin`, but this feature will be removed in **v6**.\n{{< /callout >}}\n\n### Methods\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's scrollspy. (Removes stored data on the DOM element) |\n| `getInstance` | *Static* method to get the scrollspy instance associated with a DOM element. |\n| `getOrCreateInstance` | *Static* method to get the scrollspy instance associated with a DOM element, or to create a new one in case it wasn't initialized. |\n| `refresh` | When adding or removing elements in the DOM, you'll need to call the refresh method. |\n{{< /bs-table >}}\n\nHere's an example using the refresh method:\n\n```js\nconst dataSpyList = document.querySelectorAll('[data-bs-spy=\"scroll\"]')\ndataSpyList.forEach(dataSpyEl => {\n  bootstrap.ScrollSpy.getInstance(dataSpyEl).refresh()\n})\n```\n\n### Events\n\n{{< bs-table \"table\" >}}\n| Event | Description |\n| --- | --- |\n| `activate.bs.scrollspy` | This event fires on the scroll element whenever an anchor is activated by the scrollspy. |\n{{< /bs-table >}}\n\n```js\nconst firstScrollSpyEl = document.querySelector('[data-bs-spy=\"scroll\"]')\nfirstScrollSpyEl.addEventListener('activate.bs.scrollspy', () => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/spinners.md",
    "content": "---\nlayout: docs\ntitle: Spinners\ndescription: Indicate the loading state of a component or page with Bootstrap spinners, built entirely with HTML, CSS, and no JavaScript.\ngroup: components\ntoc: true\n---\n\n## About\n\nBootstrap \"spinners\" can be used to show the loading state in your projects. They're built only with HTML and CSS, meaning you don't need any JavaScript to create them. You will, however, need some custom JavaScript to toggle their visibility. Their appearance, alignment, and sizing can be easily customized with our amazing utility classes.\n\nFor accessibility purposes, each loader here includes `role=\"status\"` and a nested `<span class=\"visually-hidden\">Loading...</span>`.\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Border spinner\n\nUse the border spinners for a lightweight loading indicator.\n\n{{< example >}}\n<div class=\"spinner-border\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{< /example >}}\n\n### Colors\n\nThe border spinner uses `currentColor` for its `border-color`, meaning you can customize the color with [text color utilities][color]. You can use any of our text color utilities on the standard spinner.\n\n{{< example >}}\n{{< spinner.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"spinner-border text-{{ .name }}\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{- end -}}\n{{< /spinner.inline >}}\n{{< /example >}}\n\n{{< callout info >}}\n**Why not use `border-color` utilities?** Each border spinner specifies a `transparent` border for at least one side, so `.border-{color}` utilities would override that.\n{{< /callout >}}\n\n## Growing spinner\n\nIf you don't fancy a border spinner, switch to the grow spinner. While it doesn't technically spin, it does repeatedly grow!\n\n{{< example >}}\n<div class=\"spinner-grow\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{< /example >}}\n\nOnce again, this spinner is built with `currentColor`, so you can easily change its appearance with [text color utilities][color]. Here it is in blue, along with the supported variants.\n\n{{< example >}}\n{{< spinner.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"spinner-grow text-{{ .name }}\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{- end -}}\n{{< /spinner.inline >}}\n{{< /example >}}\n\n## Alignment\n\nSpinners in Bootstrap are built with `rem`s, `currentColor`, and `display: inline-flex`. This means they can easily be resized, recolored, and quickly aligned.\n\n### Margin\n\nUse [margin utilities][margin] like `.m-5` for easy spacing.\n\n{{< example >}}\n<div class=\"spinner-border m-5\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{< /example >}}\n\n### Placement\n\nUse [flexbox utilities][flex], [float utilities][float], or [text alignment][text] utilities to place spinners exactly where you need them in any situation.\n\n#### Flex\n\n{{< example >}}\n<div class=\"d-flex justify-content-center\">\n  <div class=\"spinner-border\" role=\"status\">\n    <span class=\"visually-hidden\">Loading...</span>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"d-flex align-items-center\">\n  <strong>Loading...</strong>\n  <div class=\"spinner-border ms-auto\" role=\"status\" aria-hidden=\"true\"></div>\n</div>\n{{< /example >}}\n\n#### Floats\n\n{{< example >}}\n<div class=\"clearfix\">\n  <div class=\"spinner-border float-end\" role=\"status\">\n    <span class=\"visually-hidden\">Loading...</span>\n  </div>\n</div>\n{{< /example >}}\n\n#### Text align\n\n{{< example >}}\n<div class=\"text-center\">\n  <div class=\"spinner-border\" role=\"status\">\n    <span class=\"visually-hidden\">Loading...</span>\n  </div>\n</div>\n{{< /example >}}\n\n## Size\n\nAdd `.spinner-border-sm` and `.spinner-grow-sm` to make a smaller spinner that can quickly be used within other components.\n\n{{< example >}}\n<div class=\"spinner-border spinner-border-sm\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n<div class=\"spinner-grow spinner-grow-sm\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{< /example >}}\n\nOr, use custom CSS or inline styles to change the dimensions as needed.\n\n{{< example >}}\n<div class=\"spinner-border\" style=\"width: 3rem; height: 3rem;\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n<div class=\"spinner-grow\" style=\"width: 3rem; height: 3rem;\" role=\"status\">\n  <span class=\"visually-hidden\">Loading...</span>\n</div>\n{{< /example >}}\n\n## Buttons\n\nUse spinners within buttons to indicate an action is currently processing or taking place. You may also swap the text out of the spinner element and utilize button text as needed.\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" disabled>\n  <span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span>\n  <span class=\"visually-hidden\">Loading...</span>\n</button>\n<button class=\"btn btn-primary\" type=\"button\" disabled>\n  <span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span>\n  Loading...\n</button>\n{{< /example >}}\n\n{{< example >}}\n<button class=\"btn btn-primary\" type=\"button\" disabled>\n  <span class=\"spinner-grow spinner-grow-sm\" role=\"status\" aria-hidden=\"true\"></span>\n  <span class=\"visually-hidden\">Loading...</span>\n</button>\n<button class=\"btn btn-primary\" type=\"button\" disabled>\n  <span class=\"spinner-grow spinner-grow-sm\" role=\"status\" aria-hidden=\"true\"></span>\n  Loading...\n</button>\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, spinners now use local CSS variables on `.spinner-border` and `.spinner-grow` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\nBorder spinner variables:\n\n{{< scss-docs name=\"spinner-border-css-vars\" file=\"scss/_spinners.scss\" >}}\n\nGrowing spinner variables:\n\n{{< scss-docs name=\"spinner-grow-css-vars\" file=\"scss/_spinners.scss\" >}}\n\nFor both spinners, small spinner modifier classes are used to update the values of these CSS variables as needed. For example, the `.spinner-border-sm` class does the following:\n\n{{< scss-docs name=\"spinner-border-sm-css-vars\" file=\"scss/_spinners.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"spinner-variables\" file=\"scss/_variables.scss\" >}}\n\n### Keyframes\n\nUsed for creating the CSS animations for our spinners. Included in `scss/_spinners.scss`.\n\n{{< scss-docs name=\"spinner-border-keyframes\" file=\"scss/_spinners.scss\" >}}\n\n{{< scss-docs name=\"spinner-grow-keyframes\" file=\"scss/_spinners.scss\" >}}\n\n\n[color]:   {{< docsref \"/utilities/colors\" >}}\n[display]: {{< docsref \"/utilities/display\" >}}\n[flex]:    {{< docsref \"/utilities/flex\" >}}\n[float]:   {{< docsref \"/utilities/float\" >}}\n[margin]:  {{< docsref \"/utilities/spacing\" >}}\n[sizing]:  {{< docsref \"/utilities/sizing\" >}}\n[text]:    {{< docsref \"/utilities/text\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/toasts.md",
    "content": "---\nlayout: docs\ntitle: Toasts\ndescription: Push notifications to your visitors with a toast, a lightweight and easily customizable alert message.\ngroup: components\ntoc: true\n---\n\nToasts are lightweight notifications designed to mimic the push notifications that have been popularized by mobile and desktop operating systems. They're built with flexbox, so they're easy to align and position.\n\n## Overview\n\nThings to know when using the toast plugin:\n\n- Toasts are opt-in for performance reasons, so **you must initialize them yourself**.\n- Toasts will automatically hide if you do not specify `autohide: false`.\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Examples\n\n### Basic\n\nTo encourage extensible and predictable toasts, we recommend a header and body. Toast headers use `display: flex`, allowing easy alignment of content thanks to our margin and flexbox utilities.\n\nToasts are as flexible as you need and have very little required markup. At a minimum, we require a single element to contain your \"toasted\" content and strongly encourage a dismiss button.\n\n{{< example class=\"bg-light\" >}}\n<div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  <div class=\"toast-header\">\n    {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n    <strong class=\"me-auto\">Bootstrap</strong>\n    <small>11 mins ago</small>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"toast-body\">\n    Hello, world! This is a toast message.\n  </div>\n</div>\n{{< /example >}}\n\n{{< callout warning >}}\nPreviously, our scripts dynamically added the `.hide` class to completely hide a toast (with `display:none`, rather than just with `opacity:0`). This is now not necessary anymore. However, for backwards compatibility, our script will continue to toggle the class (even though there is no practical need for it) until the next major version.\n{{< /callout >}}\n\n### Live example\n\nClick the button below to show a toast (positioned with our utilities in the lower right corner) that has been hidden by default.\n\n<div class=\"toast-container position-fixed bottom-0 end-0 p-3\">\n  <div id=\"liveToast\" class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n    <div class=\"toast-header\">\n      {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n      <strong class=\"me-auto\">Bootstrap</strong>\n      <small>11 mins ago</small>\n      <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n    </div>\n    <div class=\"toast-body\">\n      Hello, world! This is a toast message.\n    </div>\n  </div>\n</div>\n\n<div class=\"bd-example\">\n  <button type=\"button\" class=\"btn btn-primary\" id=\"liveToastBtn\">Show live toast</button>\n</div>\n\n```html\n<button type=\"button\" class=\"btn btn-primary\" id=\"liveToastBtn\">Show live toast</button>\n\n<div class=\"toast-container position-fixed bottom-0 end-0 p-3\">\n  <div id=\"liveToast\" class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n    <div class=\"toast-header\">\n      <img src=\"...\" class=\"rounded me-2\" alt=\"...\">\n      <strong class=\"me-auto\">Bootstrap</strong>\n      <small>11 mins ago</small>\n      <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n    </div>\n    <div class=\"toast-body\">\n      Hello, world! This is a toast message.\n    </div>\n  </div>\n</div>\n```\n\nWe use the following JavaScript to trigger our live toast demo:\n\n```js\nconst toastTrigger = document.getElementById('liveToastBtn')\nconst toastLiveExample = document.getElementById('liveToast')\nif (toastTrigger) {\n  toastTrigger.addEventListener('click', () => {\n    const toast = new bootstrap.Toast(toastLiveExample)\n\n    toast.show()\n  })\n}\n```\n\n### Translucent\n\nToasts are slightly translucent to blend in with what's below them.\n\n{{< example class=\"bg-dark\" >}}\n<div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  <div class=\"toast-header\">\n    {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n    <strong class=\"me-auto\">Bootstrap</strong>\n    <small class=\"text-muted\">11 mins ago</small>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"toast-body\">\n    Hello, world! This is a toast message.\n  </div>\n</div>\n{{< /example >}}\n\n### Stacking\n\nYou can stack toasts by wrapping them in a toast container, which will vertically add some spacing.\n\n{{< example class=\"bg-light\" >}}\n<div class=\"toast-container position-static\">\n  <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n    <div class=\"toast-header\">\n      {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n      <strong class=\"me-auto\">Bootstrap</strong>\n      <small class=\"text-muted\">just now</small>\n      <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n    </div>\n    <div class=\"toast-body\">\n      See? Just like this.\n    </div>\n  </div>\n\n  <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n    <div class=\"toast-header\">\n      {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n      <strong class=\"me-auto\">Bootstrap</strong>\n      <small class=\"text-muted\">2 seconds ago</small>\n      <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n    </div>\n    <div class=\"toast-body\">\n      Heads up, toasts will stack automatically\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Custom content\n\nCustomize your toasts by removing sub-components, tweaking them with [utilities]({{< docsref \"/utilities/api\" >}}), or by adding your own markup. Here we've created a simpler toast by removing the default `.toast-header`, adding a custom hide icon from [Bootstrap Icons]({{< param icons >}}), and using some [flexbox utilities]({{< docsref \"/utilities/flex\" >}}) to adjust the layout.\n\n{{< example class=\"bg-light\" >}}\n<div class=\"toast align-items-center\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  <div class=\"d-flex\">\n    <div class=\"toast-body\">\n      Hello, world! This is a toast message.\n    </div>\n    <button type=\"button\" class=\"btn-close me-2 m-auto\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n  </div>\n</div>\n{{< /example >}}\n\nAlternatively, you can also add additional controls and components to toasts.\n\n{{< example class=\"bg-light\" >}}\n<div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  <div class=\"toast-body\">\n    Hello, world! This is a toast message.\n    <div class=\"mt-2 pt-2 border-top\">\n      <button type=\"button\" class=\"btn btn-primary btn-sm\">Take action</button>\n      <button type=\"button\" class=\"btn btn-secondary btn-sm\" data-bs-dismiss=\"toast\">Close</button>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Color schemes\n\nBuilding on the above example, you can create different toast color schemes with our [color]({{< docsref \"/utilities/colors\" >}}) and [background]({{< docsref \"/utilities/background\" >}}) utilities. Here we've added `.text-bg-primary` to the `.toast`, and then added `.btn-close-white` to our close button. For a crisp edge, we remove the default border with `.border-0`.\n\n{{< example class=\"bg-light\" >}}\n<div class=\"toast align-items-center text-bg-primary border-0\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  <div class=\"d-flex\">\n    <div class=\"toast-body\">\n      Hello, world! This is a toast message.\n    </div>\n    <button type=\"button\" class=\"btn-close btn-close-white me-2 m-auto\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n  </div>\n</div>\n{{< /example >}}\n\n## Placement\n\nPlace toasts with custom CSS as you need them. The top right is often used for notifications, as is the top middle. If you're only ever going to show one toast at a time, put the positioning styles right on the `.toast`.\n\n{{< example stackblitz_add_js=\"true\" >}}\n<form>\n  <div class=\"mb-3\">\n    <label for=\"selectToastPlacement\">Toast placement</label>\n    <select class=\"form-select mt-2\" id=\"selectToastPlacement\">\n      <option value=\"\" selected>Select a position...</option>\n      <option value=\"top-0 start-0\">Top left</option>\n      <option value=\"top-0 start-50 translate-middle-x\">Top center</option>\n      <option value=\"top-0 end-0\">Top right</option>\n      <option value=\"top-50 start-0 translate-middle-y\">Middle left</option>\n      <option value=\"top-50 start-50 translate-middle\">Middle center</option>\n      <option value=\"top-50 end-0 translate-middle-y\">Middle right</option>\n      <option value=\"bottom-0 start-0\">Bottom left</option>\n      <option value=\"bottom-0 start-50 translate-middle-x\">Bottom center</option>\n      <option value=\"bottom-0 end-0\">Bottom right</option>\n    </select>\n  </div>\n</form>\n<div aria-live=\"polite\" aria-atomic=\"true\" class=\"bg-dark position-relative bd-example-toasts\">\n  <div class=\"toast-container p-3\" id=\"toastPlacement\">\n    <div class=\"toast\">\n      <div class=\"toast-header\">\n        {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n        <strong class=\"me-auto\">Bootstrap</strong>\n        <small>11 mins ago</small>\n      </div>\n      <div class=\"toast-body\">\n        Hello, world! This is a toast message.\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nFor systems that generate more notifications, consider using a wrapping element so they can easily stack.\n\n{{< example class=\"bg-dark bd-example-toasts p-0\" >}}\n<div aria-live=\"polite\" aria-atomic=\"true\" class=\"position-relative\">\n  <!-- Position it: -->\n  <!-- - `.toast-container` for spacing between toasts -->\n  <!-- - `top-0` & `end-0` to position the toasts in the upper right corner -->\n  <!-- - `.p-3` to prevent the toasts from sticking to the edge of the container  -->\n  <div class=\"toast-container top-0 end-0 p-3\">\n\n    <!-- Then put toasts within -->\n    <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n      <div class=\"toast-header\">\n        {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n        <strong class=\"me-auto\">Bootstrap</strong>\n        <small class=\"text-muted\">just now</small>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"toast-body\">\n        See? Just like this.\n      </div>\n    </div>\n\n    <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n      <div class=\"toast-header\">\n        {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n        <strong class=\"me-auto\">Bootstrap</strong>\n        <small class=\"text-muted\">2 seconds ago</small>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"toast-body\">\n        Heads up, toasts will stack automatically\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nYou can also get fancy with flexbox utilities to align toasts horizontally and/or vertically.\n\n{{< example class=\"bg-dark bd-example-toasts d-flex\" >}}\n<!-- Flexbox container for aligning the toasts -->\n<div aria-live=\"polite\" aria-atomic=\"true\" class=\"d-flex justify-content-center align-items-center w-100\">\n\n  <!-- Then put toasts within -->\n  <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n    <div class=\"toast-header\">\n      {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n      <strong class=\"me-auto\">Bootstrap</strong>\n      <small>11 mins ago</small>\n      <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n    </div>\n    <div class=\"toast-body\">\n      Hello, world! This is a toast message.\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Accessibility\n\nToasts are intended to be small interruptions to your visitors or users, so to help those with screen readers and similar assistive technologies, you should wrap your toasts in an [`aria-live` region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Changes to live regions (such as injecting/updating a toast component) are automatically announced by screen readers without needing to move the user's focus or otherwise interrupt the user. Additionally, include `aria-atomic=\"true\"` to ensure that the entire toast is always announced as a single (atomic) unit, rather than just announcing what was changed (which could lead to problems if you only update part of the toast's content, or if displaying the same toast content at a later point in time). If the information needed is important for the process, e.g. for a list of errors in a form, then use the [alert component]({{< docsref \"/components/alerts\" >}}) instead of toast.\n\nNote that the live region needs to be present in the markup *before* the toast is generated or updated. If you dynamically generate both at the same time and inject them into the page, they will generally not be announced by assistive technologies.\n\nYou also need to adapt the `role` and `aria-live` level depending on the content. If it's an important message like an error, use `role=\"alert\" aria-live=\"assertive\"`, otherwise use `role=\"status\" aria-live=\"polite\"` attributes.\n\nAs the content you're displaying changes, be sure to update the [`delay` timeout](#options) so that users have enough time to read the toast.\n\n```html\n<div class=\"toast\" role=\"alert\" aria-live=\"polite\" aria-atomic=\"true\" data-bs-delay=\"10000\">\n  <div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">...</div>\n</div>\n```\n\nWhen using `autohide: false`, you must add a close button to allow users to dismiss the toast.\n\n{{< example class=\"bg-light\" >}}\n<div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\" class=\"toast\" data-bs-autohide=\"false\">\n  <div class=\"toast-header\">\n    {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n    <strong class=\"me-auto\">Bootstrap</strong>\n    <small>11 mins ago</small>\n    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n  </div>\n  <div class=\"toast-body\">\n    Hello, world! This is a toast message.\n  </div>\n</div>\n{{< /example >}}\n\nWhile technically it's possible to add focusable/actionable controls (such as additional buttons or links) in your toast, you should avoid doing this for autohiding toasts. Even if you give the toast a long [`delay` timeout](#options), keyboard and assistive technology users may find it difficult to reach the toast in time to take action (since toasts don't receive focus when they are displayed). If you absolutely must have further controls, we recommend using a toast with `autohide: false`.\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap's evolving CSS variables approach, toasts now use local CSS variables on `.toast` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"toast-css-vars\" file=\"scss/_toasts.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"toast-variables\" file=\"scss/_variables.scss\" >}}\n\n## Usage\n\nInitialize toasts via JavaScript:\n\n```js\nconst toastElList = document.querySelectorAll('.toast')\nconst toastList = [...toastElList].map(toastEl => new bootstrap.Toast(toastEl, option))\n```\n\n### Triggers\n\n{{% js-dismiss \"toast\" %}}\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `animation` | boolean | `true` | Apply a CSS fade transition to the toast. |\n| `autohide` | boolean | `true` | Automatically hide the toast after the delay. |\n| `delay` | number | `5000` | Delay in milliseconds before hiding the toast. |\n{{< /bs-table >}}\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Hides an element's toast. Your toast will remain on the DOM but won't show anymore. |\n| `getInstance` | *Static* method which allows you to get the toast instance associated with a DOM element. <br> For example: `const myToastEl = document.getElementById('myToastEl')` `const myToast = bootstrap.Toast.getInstance(myToastEl)` Returns a Bootstrap toast instance. |\n| `getOrCreateInstance` | *Static* method which allows you to get the toast instance associated with a DOM element, or create a new one, in case it wasn't initialized. <br>`const myToastEl = document.getElementById('myToastEl')` `const myToast = bootstrap.Toast.getOrCreateInstance(myToastEl)` Returns a Bootstrap toast instance. |\n| `hide` | Hides an element's toast. **Returns to the caller before the toast has actually been hidden** (i.e. before the `hidden.bs.toast` event occurs). You have to manually call this method if you made `autohide` to `false`. |\n| `isShown` | Returns a boolean according to toast's visibility state. |\n| `show` | Reveals an element's toast. **Returns to the caller before the toast has actually been shown** (i.e. before the `shown.bs.toast` event occurs). You have to manually call this method, instead your toast won't show. |\n{{< /bs-table >}}\n\n### Events\n\n{{< bs-table \"table\" >}}\n| Event | Description |\n| --- | --- |\n| `hide.bs.toast` | This event is fired immediately when the `hide` instance method has been called. |\n| `hidden.bs.toast` | This event is fired when the toast has finished being hidden from the user. |\n| `show.bs.toast` | This event fires immediately when the `show` instance method is called. |\n| `shown.bs.toast` | This event is fired when the toast has been made visible to the user. |\n{{< /bs-table >}}\n\n```js\nconst myToastEl = document.getElementById('myToast')\nmyToastEl.addEventListener('hidden.bs.toast', () => {\n  // do something...\n})\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/components/tooltips.md",
    "content": "---\nlayout: docs\ntitle: Tooltips\ndescription: Documentation and examples for adding custom Bootstrap tooltips with CSS and JavaScript using CSS3 for animations and data-bs-attributes for local title storage.\ngroup: components\ntoc: true\n---\n\n## Overview\n\nThings to know when using the tooltip plugin:\n\n- Tooltips rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param \"cdn.popper\" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.\n- Tooltips are opt-in for performance reasons, so **you must initialize them yourself**.\n- Tooltips with zero-length titles are never displayed.\n- Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc).\n- Triggering tooltips on hidden elements will not work.\n- Tooltips for `.disabled` or `disabled` elements must be triggered on a wrapper element.\n- When triggered from hyperlinks that span multiple lines, tooltips will be centered. Use `white-space: nowrap;` on your `<a>`s to avoid this behavior.\n- Tooltips must be hidden before their corresponding elements have been removed from the DOM.\n- Tooltips can be triggered thanks to an element inside a shadow DOM.\n\nGot all that? Great, let's see how they work with some examples.\n\n{{< callout info >}}\n{{< partial \"callout-info-sanitizer.md\" >}}\n{{< /callout >}}\n\n{{< callout info >}}\n{{< partial \"callout-info-prefersreducedmotion.md\" >}}\n{{< /callout >}}\n\n## Examples\n\n### Enable tooltips\n\nAs mentioned above, you must initialize tooltips before they can be used. One way to initialize all tooltips on a page would be to select them by their `data-bs-toggle` attribute, like so:\n\n```js\nconst tooltipTriggerList = document.querySelectorAll('[data-bs-toggle=\"tooltip\"]')\nconst tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))\n```\n\n### Tooltips on links\n\nHover over the links below to see tooltips:\n\n{{< example class=\"tooltip-demo\" stackblitz_add_js=\"true\" >}}\n<p class=\"muted\">Placeholder text to demonstrate some <a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-title=\"Default tooltip\">inline links</a> with tooltips. This is now just filler, no killer. Content placed here just to mimic the presence of <a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-title=\"Another tooltip\">real text</a>. And all that just to give you an idea of how tooltips would look when used in real-world situations. So hopefully you've now seen how <a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-title=\"Another one here too\">these tooltips on links</a> can work in practice, once you use them on <a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-title=\"The last tip!\">your own</a> site or project.\n</p>\n{{< /example >}}\n\n{{< callout warning >}}\n{{< partial \"callout-warning-data-bs-title-vs-title.md\" >}}\n{{< /callout >}}\n\n### Custom tooltips\n\n{{< added-in \"5.2.0\" >}}\n\nYou can customize the appearance of tooltips using [CSS variables](#variables). We set a custom class with `data-bs-custom-class=\"custom-tooltip\"` to scope our custom appearance and use it to override a local CSS variable.\n\n{{< scss-docs name=\"custom-tooltip\" file=\"site/assets/scss/_component-examples.scss\" >}}\n\n\n{{< example class=\"tooltip-demo\" stackblitz_add_js=\"true\" >}}\n<button type=\"button\" class=\"btn btn-secondary\"\n        data-bs-toggle=\"tooltip\" data-bs-placement=\"top\"\n        data-bs-custom-class=\"custom-tooltip\"\n        data-bs-title=\"This top tooltip is themed via CSS variables.\">\n  Custom tooltip\n</button>\n{{< /example >}}\n\n### Directions\n\nHover over the buttons below to see the four tooltips directions: top, right, bottom, and left. Directions are mirrored when using Bootstrap in RTL.\n\n<div class=\"bd-example tooltip-demo\">\n  <div class=\"bd-example-tooltips\">\n    <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" data-bs-title=\"Tooltip on top\">Tooltip on top</button>\n    <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\" data-bs-title=\"Tooltip on right\">Tooltip on right</button>\n    <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"Tooltip on bottom\">Tooltip on bottom</button>\n    <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" data-bs-title=\"Tooltip on left\">Tooltip on left</button>\n    <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-html=\"true\" data-bs-title=\"<em>Tooltip</em> <u>with</u> <b>HTML</b>\">Tooltip with HTML</button>\n  </div>\n</div>\n\n```html\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" data-bs-title=\"Tooltip on top\">\n  Tooltip on top\n</button>\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\" data-bs-title=\"Tooltip on right\">\n  Tooltip on right\n</button>\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"Tooltip on bottom\">\n  Tooltip on bottom\n</button>\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" data-bs-title=\"Tooltip on left\">\n  Tooltip on left\n</button>\n```\n\nAnd with custom HTML added:\n\n```html\n<button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-html=\"true\" data-bs-title=\"<em>Tooltip</em> <u>with</u> <b>HTML</b>\">\n  Tooltip with HTML\n</button>\n```\n\nWith an SVG:\n\n<div class=\"bd-example tooltip-demo\">\n  <a href=\"#\" class=\"d-inline-block\" data-bs-toggle=\"tooltip\" data-bs-title=\"Default tooltip\">\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"50\" height=\"50\" viewBox=\"0 0 100 100\">\n      <rect width=\"100%\" height=\"100%\" fill=\"#563d7c\"/>\n      <circle cx=\"50\" cy=\"50\" r=\"30\" fill=\"#007bff\"/>\n    </svg>\n  </a>\n</div>\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\nAs part of Bootstrap’s evolving CSS variables approach, tooltips now use local CSS variables on `.tooltip` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.\n\n{{< scss-docs name=\"tooltip-css-vars\" file=\"scss/_tooltip.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"tooltip-variables\" file=\"scss/_variables.scss\" >}}\n\n## Usage\n\nThe tooltip plugin generates content and markup on demand, and by default places tooltips after their trigger element.\n\nTrigger the tooltip via JavaScript:\n\n```js\nconst exampleEl = document.getElementById('example')\nconst tooltip = new bootstrap.Tooltip(exampleEl, options)\n```\n\n{{< callout warning >}}\n##### Overflow `auto` and `scroll`\n\nTooltip position attempts to automatically change when a **parent container** has `overflow: auto` or `overflow: scroll` like our `.table-responsive`, but still keeps the original placement's positioning. To resolve this, set the [`boundary` option](https://popper.js.org/docs/v2/modifiers/flip/#boundary) (for the flip modifier using the `popperConfig` option) to any HTMLElement to override the default value, `'clippingParents'`, such as `document.body`:\n\n```js\nconst tooltip = new bootstrap.Tooltip('#example', {\n  boundary: document.body // or document.querySelector('#boundary')\n})\n```\n{{< /callout >}}\n\n### Markup\n\nThe required markup for a tooltip is only a `data` attribute and `title` on the HTML element you wish to have a tooltip. The generated markup of a tooltip is rather simple, though it does require a position (by default, set to `top` by the plugin).\n\n{{< callout warning >}}\n##### Making tooltips work for keyboard and assistive technology users\n\nYou should only add tooltips to HTML elements that are traditionally keyboard-focusable and interactive (such as links or form controls). Although arbitrary HTML elements (such as `<span>`s) can be made focusable by adding the `tabindex=\"0\"` attribute, this will add potentially annoying and confusing tab stops on non-interactive elements for keyboard users, and most assistive technologies currently do not announce the tooltip in this situation. Additionally, do not rely solely on `hover` as the trigger for your tooltip, as this will make your tooltips impossible to trigger for keyboard users.\n{{< /callout >}}\n\n```html\n<!-- HTML to write -->\n<a href=\"#\" data-bs-toggle=\"tooltip\" data-bs-title=\"Some tooltip text!\">Hover over me</a>\n\n<!-- Generated markup by the plugin -->\n<div class=\"tooltip bs-tooltip-top\" role=\"tooltip\">\n  <div class=\"tooltip-arrow\"></div>\n  <div class=\"tooltip-inner\">\n    Some tooltip text!\n  </div>\n</div>\n```\n\n### Disabled elements\n\nElements with the `disabled` attribute aren't interactive, meaning users cannot focus, hover, or click them to trigger a tooltip (or popover). As a workaround, you'll want to trigger the tooltip from a wrapper `<div>` or `<span>`, ideally made keyboard-focusable using `tabindex=\"0\"`.\n\n<div class=\"tooltip-demo\">\n{{< example >}}\n<span class=\"d-inline-block\" tabindex=\"0\" data-bs-toggle=\"tooltip\" data-bs-title=\"Disabled tooltip\">\n  <button class=\"btn btn-primary\" type=\"button\" disabled>Disabled button</button>\n</span>\n{{< /example >}}\n</div>\n\n### Options\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n{{< callout warning >}}\nNote that for security reasons the `sanitize`, `sanitizeFn`, and `allowList` options cannot be supplied using data attributes.\n{{< /callout >}}\n\n\n{{< bs-table \"table\" >}}\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `allowList` | object | [Default value]({{< docsref \"/getting-started/javascript#sanitizer\" >}}) | Object which contains allowed attributes and tags. |\n| `animation` | boolean | `true` | Apply a CSS fade transition to the tooltip. |\n| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the tooltip (applies only to Popper's preventOverflow modifier). By default, it's `'clippingParents'` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper's [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). |\n| `container` | string, element, false | `false` | Appends the tooltip to a specific element. Example: `container: 'body'`. This option is particularly useful in that it allows you to position the tooltip in the flow of the document near the triggering element - which will prevent the tooltip from floating away from the triggering element during a window resize. |\n| `customClass` | string, function | `''` | Add classes to the tooltip when it is shown. Note that these classes will be added in addition to any classes specified in the template. To add multiple classes, separate them with spaces: `'class-1 class-2'`. You can also pass a function that should return a single string containing additional class names. |\n| `delay` | number, object | `0` | Delay showing and hiding the tooltip (ms)—doesn't apply to manual trigger type. If a number is supplied, delay is applied to both hide/show. Object structure is: `delay: { \"show\": 500, \"hide\": 100 }`. |\n| `fallbackPlacements` | array | `['top', 'right', 'bottom', 'left']` | Define fallback placements by providing a list of placements in array (in order of preference). For more information refer to Popper's [behavior docs](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements). |\n| `html` | boolean | `false` | Allow HTML in the tooltip. If true, HTML tags in the tooltip's `title` will be rendered in the tooltip. If false, `innerText` property will be used to insert content into the DOM. Use text if you're worried about XSS attacks. |\n| `offset` | array, string, function | `[0, 0]` | Offset of the tooltip relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset=\"10,20\"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper's [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). |\n| `placement` | string, function | `'top'` | How to position the tooltip: auto, top, bottom, left, right. When `auto` is specified, it will dynamically reorient the tooltip. When a function is used to determine the placement, it is called with the tooltip DOM node as its first argument and the triggering element DOM node as its second. The `this` context is set to the tooltip instance. |\n| `popperConfig` | null, object, function | `null` | To change Bootstrap's default Popper config, see [Popper's configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it's called with an object that contains the Bootstrap's default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. |\n| `sanitize` | boolean | `true` | Enable or disable the sanitization. If activated `'template'`, `'content'` and `'title'` options will be sanitized. |\n| `sanitizeFn` | null, function | `null` | Here you can supply your own sanitize function. This can be useful if you prefer to use a dedicated library to perform sanitization. |\n| `selector` | string, false | `false` | If a selector is provided, tooltip objects will be delegated to the specified targets. In practice, this is used to also apply tooltips to dynamically added DOM elements (`jQuery.on` support). See [this issue]({{< param repo >}}/issues/4215) and [an informative example](https://codepen.io/Johann-S/pen/djJYPb). **Note**: `title` attribute must not be used as a selector. |\n| `template` | string | `'<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>'` | Base HTML to use when creating the tooltip. The tooltip's `title` will be injected into the `.tooltip-inner`. `.tooltip-arrow` will become the tooltip's arrow. The outermost wrapper element should have the `.tooltip` class and `role=\"tooltip\"`. |\n| `title` | string, element, function | `''` | Default title value if `title` attribute isn't present. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. |\n| `trigger` | string | `'hover focus'` | How tooltip is triggered: click, hover, focus, manual. You may pass multiple triggers; separate them with a space. `'manual'` indicates that the tooltip will be triggered programmatically via the `.tooltip('show')`, `.tooltip('hide')` and `.tooltip('toggle')` methods; this value cannot be combined with any other trigger. `'hover'` on its own will result in tooltips that cannot be triggered via the keyboard, and should only be used if alternative methods for conveying the same information for keyboard users is present. |\n{{< /bs-table >}}\n\n{{< callout info >}}\n#### Data attributes for individual tooltips\n\nOptions for individual tooltips can alternatively be specified through the use of data attributes, as explained above.\n{{< /callout >}}\n\n#### Using function with `popperConfig`\n\n```js\nconst tooltip = new bootstrap.Tooltip(element, {\n  popperConfig(defaultBsPopperConfig) {\n    // const newPopperConfig = {...}\n    // use defaultBsPopperConfig if needed...\n    // return newPopperConfig\n  }\n})\n```\n\n### Methods\n\n{{< callout danger >}}\n{{< partial \"callout-danger-async-methods.md\" >}}\n{{< /callout >}}\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `disable` | Removes the ability for an element's tooltip to be shown. The tooltip will only be able to be shown if it is re-enabled. |\n| `dispose` | Hides and destroys an element's tooltip (Removes stored data on the DOM element). Tooltips that use delegation (which are created using [the `selector` option](#options)) cannot be individually destroyed on descendant trigger elements. |\n| `enable` | Gives an element's tooltip the ability to be shown. **Tooltips are enabled by default.** |\n| `getInstance` | *Static* method which allows you to get the tooltip instance associated with a DOM element, or create a new one in case it wasn't initialized. |\n| `getOrCreateInstance` | *Static* method which allows you to get the tooltip instance associated with a DOM element, or create a new one in case it wasn't initialized. |\n| `hide` | Hides an element's tooltip. **Returns to the caller before the tooltip has actually been hidden** (i.e. before the `hidden.bs.tooltip` event occurs). This is considered a \"manual\" triggering of the tooltip. |\n| `setContent` | Gives a way to change the tooltip's content after its initialization. |\n| `show` | Reveals an element's tooltip. **Returns to the caller before the tooltip has actually been shown** (i.e. before the `shown.bs.tooltip` event occurs). This is considered a \"manual\" triggering of the tooltip. Tooltips with zero-length titles are never displayed. |\n| `toggle` | Toggles an element's tooltip. **Returns to the caller before the tooltip has actually been shown or hidden** (i.e. before the `shown.bs.tooltip` or `hidden.bs.tooltip` event occurs). This is considered a \"manual\" triggering of the tooltip. |\n| `toggleEnabled` | Toggles the ability for an element's tooltip to be shown or hidden. |\n| `update` | Updates the position of an element's tooltip. |\n{{< /bs-table >}}\n\n```js\nconst tooltip = bootstrap.Tooltip.getInstance('#example') // Returns a Bootstrap tooltip instance\n\n// setContent example\ntooltip.setContent({ '.tooltip-inner': 'another title' })\n\n```\n\n{{< callout info >}}\nThe `setContent` method accepts an `object` argument, where each property-key is a valid `string` selector within the popover template, and each related property-value can be `string` | `element` | `function` | `null`\n{{< /callout >}}\n\n### Events\n\n{{< bs-table >}}\n| Event | Description |\n| --- | --- |\n| `hide.bs.tooltip` | This event is fired immediately when the `hide` instance method has been called. |\n| `hidden.bs.tooltip` | This event is fired when the popover has finished being hidden from the user (will wait for CSS transitions to complete). |\n| `inserted.bs.tooltip` | This event is fired after the `show.bs.tooltip` event when the tooltip template has been added to the DOM. |\n| `show.bs.tooltip` | This event fires immediately when the `show` instance method is called. |\n| `shown.bs.tooltip` | This event is fired when the popover has been made visible to the user (will wait for CSS transitions to complete). |\n{{< /bs-table >}}\n\n```js\nconst myTooltipEl = document.getElementById('myTooltip')\nconst tooltip = bootstrap.Tooltip.getOrCreateInstance(myTooltipEl)\n\nmyTooltipEl.addEventListener('hidden.bs.tooltip', () => {\n  // do something...\n})\n\ntooltip.hide()\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/content/figures.md",
    "content": "---\nlayout: docs\ntitle: Figures\ndescription: Documentation and examples for displaying related images and text with the figure component in Bootstrap.\ngroup: content\ntoc: true\n---\n\nAnytime you need to display a piece of content—like an image with an optional caption, consider using a `<figure>`.\n\nUse the included `.figure`, `.figure-img` and `.figure-caption` classes to provide some baseline styles for the HTML5 `<figure>` and `<figcaption>` elements. Images in figures have no explicit size, so be sure to add the `.img-fluid` class to your `<img>` to make it responsive.\n\n{{< example >}}\n<figure class=\"figure\">\n  {{< placeholder width=\"400\" height=\"300\" class=\"figure-img img-fluid rounded\" >}}\n  <figcaption class=\"figure-caption\">A caption for the above image.</figcaption>\n</figure>\n{{< /example >}}\n\nAligning the figure's caption is easy with our [text utilities]({{< docsref \"/utilities/text#text-alignment\" >}}).\n\n{{< example >}}\n<figure class=\"figure\">\n  {{< placeholder width=\"400\" height=\"300\" class=\"figure-img img-fluid rounded\" >}}\n  <figcaption class=\"figure-caption text-end\">A caption for the above image.</figcaption>\n</figure>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"figure-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/content/images.md",
    "content": "---\nlayout: docs\ntitle: Images\ndescription: Documentation and examples for opting images into responsive behavior (so they never become wider than their parent) and add lightweight styles to them—all via classes.\ngroup: content\ntoc: true\n---\n\n## Responsive images\n\nImages in Bootstrap are made responsive with `.img-fluid`. This applies `max-width: 100%;` and `height: auto;` to the image so that it scales with the parent width.\n\n{{< example >}}\n{{< placeholder width=\"100%\" height=\"250\" class=\"bd-placeholder-img-lg img-fluid\" text=\"Responsive image\" >}}\n{{< /example >}}\n\n## Image thumbnails\n\nIn addition to our [border-radius utilities]({{< docsref \"/utilities/borders\" >}}), you can use `.img-thumbnail` to give an image a rounded 1px border appearance.\n\n{{< example >}}\n{{< placeholder width=\"200\" height=\"200\" class=\"img-thumbnail\" title=\"A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera\" >}}\n{{< /example >}}\n\n## Aligning images\n\nAlign images with the [helper float classes]({{< docsref \"/utilities/float\" >}}) or [text alignment classes]({{< docsref \"/utilities/text#text-alignment\" >}}). `block`-level images can be centered using [the `.mx-auto` margin utility class]({{< docsref \"/utilities/spacing#horizontal-centering\" >}}).\n\n{{< example >}}\n{{< placeholder width=\"200\" height=\"200\" class=\"rounded float-start\" >}}\n{{< placeholder width=\"200\" height=\"200\" class=\"rounded float-end\" >}}\n{{< /example >}}\n\n\n{{< example >}}\n{{< placeholder width=\"200\" height=\"200\" class=\"rounded mx-auto d-block\" >}}\n{{< /example >}}\n\n{{< example >}}\n<div class=\"text-center\">\n  {{< placeholder width=\"200\" height=\"200\" class=\"rounded\" >}}\n</div>\n{{< /example >}}\n\n\n## Picture\n\nIf you are using the `<picture>` element to specify multiple `<source>` elements for a specific `<img>`, make sure to add the `.img-*` classes to the `<img>` and not to the `<picture>` tag.\n\n```html\n<picture>\n  <source srcset=\"...\" type=\"image/svg+xml\">\n  <img src=\"...\" class=\"img-fluid img-thumbnail\" alt=\"...\">\n</picture>\n```\n\n## Sass\n\n### Variables\n\nVariables are available for image thumbnails.\n\n{{< scss-docs name=\"thumbnail-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/content/reboot.md",
    "content": "---\nlayout: docs\ntitle: Reboot\ndescription: Reboot, a collection of element-specific CSS changes in a single file, kickstart Bootstrap to provide an elegant, consistent, and simple baseline to build upon.\ngroup: content\naliases: \"/docs/5.2/content/\"\ntoc: true\n---\n\n## Approach\n\nReboot builds upon Normalize, providing many HTML elements with somewhat opinionated styles using only element selectors. Additional styling is done only with classes. For example, we reboot some `<table>` styles for a simpler baseline and later provide `.table`, `.table-bordered`, and more.\n\nHere are our guidelines and reasons for choosing what to override in Reboot:\n\n- Update some browser default values to use `rem`s instead of `em`s for scalable component spacing.\n- Avoid `margin-top`. Vertical margins can collapse, yielding unexpected results. More importantly though, a single direction of `margin` is a simpler mental model.\n- For easier scaling across device sizes, block elements should use `rem`s for `margin`s.\n- Keep declarations of `font`-related properties to a minimum, using `inherit` whenever possible.\n\n## CSS variables\n\n{{< added-in \"5.2.0\" >}}\n\nWith v5.1.1, we standardized our required `@import`s across all our CSS bundles (including `bootstrap.css`, `bootstrap-reboot.css`, and `bootstrap-grid.css`) to include `_root.scss`. This adds `:root` level CSS variables to all bundles, regardless of how many of them are used in that bundle. Ultimately Bootstrap 5 will continue to see more [CSS variables]({{< docsref \"/customize/css-variables\" >}}) added over time, in order to provide more real-time customization without the need to always recompile Sass. Our approach is to take our source Sass variables and transform them into CSS variables. That way, even if you don't use CSS variables, you still have all the power of Sass. **This is still in-progress and will take time to fully implement.**\n\nFor example, consider these `:root` CSS variables for common `<body>` styles:\n\n{{< scss-docs name=\"root-body-variables\" file=\"scss/_root.scss\" >}}\n\nIn practice, those variables are then applied in Reboot like so:\n\n{{< scss-docs name=\"reboot-body-rules\" file=\"scss/_reboot.scss\" >}}\n\nWhich allows you to make real-time customizations however you like:\n\n```html\n<body style=\"--bs-body-color: #333;\">\n  <!-- ... -->\n</body>\n```\n\n## Page defaults\n\nThe `<html>` and `<body>` elements are updated to provide better page-wide defaults. More specifically:\n\n- The `box-sizing` is globally set on every element—including `*::before` and `*::after`, to `border-box`. This ensures that the declared width of element is never exceeded due to padding or border.\n  - No base `font-size` is declared on the `<html>`, but `16px` is assumed (the browser default). `font-size: 1rem` is applied on the `<body>` for easy responsive type-scaling via media queries while respecting user preferences and ensuring a more accessible approach. This browser default can be overridden by modifying the `$font-size-root` variable.\n- The `<body>` also sets a global `font-family`, `font-weight`, `line-height`, and `color`. This is inherited later by some form elements to prevent font inconsistencies.\n- For safety, the `<body>` has a declared `background-color`, defaulting to `#fff`.\n\n## Native font stack\n\nBootstrap utilizes a \"native font stack\" or \"system font stack\" for optimum text rendering on every device and OS. These system fonts have been designed specifically with today's devices in mind, with improved rendering on screens, variable font support, and more. Read more about [native font stacks in this *Smashing Magazine* article](https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/).\n\n```scss\n$font-family-sans-serif:\n  // Cross-platform generic font family (default user interface font)\n  system-ui,\n  // Safari for macOS and iOS (San Francisco)\n  -apple-system,\n  // Windows\n  \"Segoe UI\",\n  // Android\n  Roboto,\n  // older macOS and iOS\n  \"Helvetica Neue\"\n  // Linux\n  \"Noto Sans\",\n  \"Liberation Sans\",\n  // Basic web fallback\n  Arial,\n  // Sans serif fallback\n  sans-serif,\n  // Emoji fonts\n  \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\" !default;\n```\n\nNote that because the font stack includes emoji fonts, many common symbol/dingbat Unicode characters will be rendered as multicolored pictographs. Their appearance will vary, depending on the style used in the browser/platform's native emoji font, and they won't be affected by any CSS `color` styles.\n\nThis `font-family` is applied to the `<body>` and automatically inherited globally throughout Bootstrap. To switch the global `font-family`, update `$font-family-base` and recompile Bootstrap.\n\n## Headings and paragraphs\n\nAll heading elements—e.g., `<h1>`—and `<p>` are reset to have their `margin-top` removed. Headings have `margin-bottom: .5rem` added and paragraphs `margin-bottom: 1rem` for easy spacing.\n\n{{< bs-table \"table\" >}}\n| Heading | Example |\n| --- | --- |\n| `<h1></h1>` | <span class=\"h1\">h1. Bootstrap heading</span> |\n| `<h2></h2>` | <span class=\"h2\">h2. Bootstrap heading</span> |\n| `<h3></h3>` | <span class=\"h3\">h3. Bootstrap heading</span> |\n| `<h4></h4>` | <span class=\"h4\">h4. Bootstrap heading</span> |\n| `<h5></h5>` | <span class=\"h5\">h5. Bootstrap heading</span> |\n| `<h6></h6>` | <span class=\"h6\">h6. Bootstrap heading</span> |\n{{< /bs-table >}}\n\n## Horizontal rules\n\nThe `<hr>` element has been simplified. Similar to browser defaults, `<hr>`s are styled via `border-top`, have a default `opacity: .25`, and automatically inherit their `border-color` via `color`, including when `color` is set via the parent. They can be modified with text, border, and opacity utilities.\n\n{{< example >}}\n<hr>\n\n<div class=\"text-success\">\n  <hr>\n</div>\n\n<hr class=\"border border-danger border-2 opacity-50\">\n<hr class=\"border border-primary border-3 opacity-75\">\n{{< /example >}}\n\n## Lists\n\nAll lists—`<ul>`, `<ol>`, and `<dl>`—have their `margin-top` removed and a `margin-bottom: 1rem`. Nested lists have no `margin-bottom`. We've also reset the `padding-left` on `<ul>` and `<ol>` elements.\n\n<div class=\"bd-example\">\n{{< markdown >}}\n* All lists have their top margin removed\n* And their bottom margin normalized\n* Nested lists have no bottom margin\n  * This way they have a more even appearance\n  * Particularly when followed by more list items\n* The left padding has also been reset\n\n1. Here's an ordered list\n2. With a few list items\n3. It has the same overall look\n4. As the previous unordered list\n{{< /markdown >}}\n</div>\n\nFor simpler styling, clear hierarchy, and better spacing, description lists have updated `margin`s. `<dd>`s reset `margin-left` to `0` and add `margin-bottom: .5rem`. `<dt>`s are **bolded**.\n\n<div class=\"bd-example\">\n  <dl>\n    <dt>Description lists</dt>\n    <dd>A description list is perfect for defining terms.</dd>\n    <dt>Term</dt>\n    <dd>Definition for the term.</dd>\n    <dd>A second definition for the same term.</dd>\n    <dt>Another term</dt>\n    <dd>Definition for this other term.</dd>\n  </dl>\n</div>\n\n## Inline code\n\nWrap inline snippets of code with `<code>`. Be sure to escape HTML angle brackets.\n\n{{< example >}}\nFor example, <code>&lt;section&gt;</code> should be wrapped as inline.\n{{< /example >}}\n\n## Code blocks\n\nUse `<pre>`s for multiple lines of code. Once again, be sure to escape any angle brackets in the code for proper rendering. The `<pre>` element is reset to remove its `margin-top` and use `rem` units for its `margin-bottom`.\n\n{{< example >}}\n<pre><code>&lt;p&gt;Sample text here...&lt;/p&gt;\n&lt;p&gt;And another line of sample text here...&lt;/p&gt;\n</code></pre>\n{{< /example >}}\n\n## Variables\n\nFor indicating variables use the `<var>` tag.\n\n{{< example >}}\n<var>y</var> = <var>m</var><var>x</var> + <var>b</var>\n{{< /example >}}\n\n## User input\n\nUse the `<kbd>` to indicate input that is typically entered via keyboard.\n\n{{< example >}}\nTo switch directories, type <kbd>cd</kbd> followed by the name of the directory.<br>\nTo edit settings, press <kbd><kbd>ctrl</kbd> + <kbd>,</kbd></kbd>\n{{< /example >}}\n\n## Sample output\n\nFor indicating sample output from a program use the `<samp>` tag.\n\n{{< example >}}\n<samp>This text is meant to be treated as sample output from a computer program.</samp>\n{{< /example >}}\n\n## Tables\n\nTables are slightly adjusted to style `<caption>`s, collapse borders, and ensure consistent `text-align` throughout. Additional changes for borders, padding, and more come with [the `.table` class]({{< docsref \"/content/tables\" >}}).\n\n{{< example >}}\n<table>\n  <caption>\n    This is an example table, and this is its caption to describe the contents.\n  </caption>\n  <thead>\n    <tr>\n      <th>Table heading</th>\n      <th>Table heading</th>\n      <th>Table heading</th>\n      <th>Table heading</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Table cell</td>\n      <td>Table cell</td>\n      <td>Table cell</td>\n      <td>Table cell</td>\n    </tr>\n    <tr>\n      <td>Table cell</td>\n      <td>Table cell</td>\n      <td>Table cell</td>\n      <td>Table cell</td>\n    </tr>\n    <tr>\n      <td>Table cell</td>\n      <td>Table cell</td>\n      <td>Table cell</td>\n      <td>Table cell</td>\n    </tr>\n  </tbody>\n</table>\n{{< /example >}}\n\n## Forms\n\nVarious form elements have been rebooted for simpler base styles. Here are some of the most notable changes:\n\n- `<fieldset>`s have no borders, padding, or margin so they can be easily used as wrappers for individual inputs or groups of inputs.\n- `<legend>`s, like fieldsets, have also been restyled to be displayed as a heading of sorts.\n- `<label>`s are set to `display: inline-block` to allow `margin` to be applied.\n- `<input>`s, `<select>`s, `<textarea>`s, and `<button>`s are mostly addressed by Normalize, but Reboot removes their `margin` and sets `line-height: inherit`, too.\n- `<textarea>`s are modified to only be resizable vertically as horizontal resizing often \"breaks\" page layout.\n- `<button>`s and `<input>` button elements have `cursor: pointer` when `:not(:disabled)`.\n\nThese changes, and more, are demonstrated below.\n\n<form class=\"bd-example\">\n  <fieldset>\n    <legend>Example legend</legend>\n    <p>\n      <label for=\"input\">Example input</label>\n      <input type=\"text\" id=\"input\" placeholder=\"Example input\">\n    </p>\n    <p>\n      <label for=\"email\">Example email</label>\n      <input type=\"email\" id=\"email\" placeholder=\"test@example.com\">\n    </p>\n    <p>\n      <label for=\"tel\">Example telephone</label>\n      <input type=\"tel\" id=\"tel\">\n    </p>\n    <p>\n      <label for=\"url\">Example url</label>\n      <input type=\"url\" id=\"url\">\n    </p>\n    <p>\n      <label for=\"number\">Example number</label>\n      <input type=\"number\" id=\"number\">\n    </p>\n    <p>\n      <label for=\"search\">Example search</label>\n      <input type=\"search\" id=\"search\">\n    </p>\n    <p>\n      <label for=\"range\">Example range</label>\n      <input type=\"range\" id=\"range\" min=\"0\" max=\"10\">\n    </p>\n    <p>\n      <label for=\"file\">Example file input</label>\n      <input type=\"file\" id=\"file\">\n    </p>\n    <p>\n      <label for=\"select\">Example select</label>\n      <select id=\"select\">\n        <option value=\"\">Choose...</option>\n        <optgroup label=\"Option group 1\">\n          <option value=\"\">Option 1</option>\n          <option value=\"\">Option 2</option>\n          <option value=\"\">Option 3</option>\n        </optgroup>\n        <optgroup label=\"Option group 2\">\n          <option value=\"\">Option 4</option>\n          <option value=\"\">Option 5</option>\n          <option value=\"\">Option 6</option>\n        </optgroup>\n      </select>\n    </p>\n    <p>\n      <label>\n        <input type=\"checkbox\" value=\"\">\n        Check this checkbox\n      </label>\n    </p>\n    <p>\n      <label>\n        <input type=\"radio\" name=\"optionsRadios\" id=\"optionsRadios1\" value=\"option1\" checked>\n        Option one is this and that\n      </label>\n      <label>\n        <input type=\"radio\" name=\"optionsRadios\" id=\"optionsRadios2\" value=\"option2\">\n        Option two is something else that's also super long to demonstrate the wrapping of these fancy form controls.\n      </label>\n      <label>\n        <input type=\"radio\" name=\"optionsRadios\" id=\"optionsRadios3\" value=\"option3\" disabled>\n        Option three is disabled\n      </label>\n    </p>\n    <p>\n      <label for=\"textarea\">Example textarea</label>\n      <textarea id=\"textarea\" rows=\"3\"></textarea>\n    </p>\n    <p>\n      <label for=\"date\">Example date</label>\n      <input type=\"date\" id=\"date\">\n    </p>\n    <p>\n      <label for=\"time\">Example time</label>\n      <input type=\"time\" id=\"time\">\n    </p>\n    <p>\n      <label for=\"password\">Example password</label>\n      <input type=\"password\" id=\"password\">\n    </p>\n    <p>\n      <label for=\"datetime-local\">Example datetime-local</label>\n      <input type=\"datetime-local\" id=\"datetime-local\">\n    </p>\n    <p>\n      <label for=\"week\">Example week</label>\n      <input type=\"week\" id=\"week\">\n    </p>\n    <p>\n      <label for=\"month\">Example month</label>\n      <input type=\"month\" id=\"month\">\n    </p>\n    <p>\n      <label for=\"color\">Example color</label>\n      <input type=\"color\" id=\"color\">\n    </p>\n    <p>\n      <label for=\"output\">Example output</label>\n      <output name=\"result\" id=\"output\">100</output>\n    </p>\n    <p>\n      <button type=\"submit\">Button submit</button>\n      <input type=\"submit\" value=\"Input submit button\">\n      <input type=\"reset\" value=\"Input reset button\">\n      <input type=\"button\" value=\"Input button\">\n    </p>\n    <p>\n      <button type=\"submit\" disabled>Button submit</button>\n      <input type=\"submit\" value=\"Input submit button\" disabled>\n      <input type=\"reset\" value=\"Input reset button\" disabled>\n      <input type=\"button\" value=\"Input button\" disabled>\n    </p>\n  </fieldset>\n</form>\n\n{{< callout warning >}}\n{{< partial \"callout-warning-input-support.md\" >}}\n{{< /callout >}}\n\n### Pointers on buttons\n\nReboot includes an enhancement for `role=\"button\"` to change the default cursor to `pointer`. Add this attribute to elements to help indicate elements are interactive. This role isn't necessary for `<button>` elements, which get their own `cursor` change.\n\n{{< example >}}\n<span role=\"button\" tabindex=\"0\">Non-button element button</span>\n{{< /example >}}\n\n## Misc elements\n\n### Address\n\nThe `<address>` element is updated to reset the browser default `font-style` from `italic` to `normal`. `line-height` is also now inherited, and `margin-bottom: 1rem` has been added. `<address>`s are for presenting contact information for the nearest ancestor (or an entire body of work). Preserve formatting by ending lines with `<br>`.\n\n<div class=\"bd-example\">\n  <address>\n    <strong>Twitter, Inc.</strong><br>\n    1355 Market St, Suite 900<br>\n    San Francisco, CA 94103<br>\n    <abbr title=\"Phone\">P:</abbr> (123) 456-7890\n  </address>\n\n  <address>\n    <strong>Full Name</strong><br>\n    <a href=\"mailto:first.last@example.com\">first.last@example.com</a>\n  </address>\n</div>\n\n### Blockquote\n\nThe default `margin` on blockquotes is `1em 40px`, so we reset that to `0 0 1rem` for something more consistent with other elements.\n\n<div class=\"bd-example\">\n  <blockquote class=\"blockquote\">\n    <p>A well-known quote, contained in a blockquote element.</p>\n  </blockquote>\n  <p>Someone famous in <cite title=\"Source Title\">Source Title</cite></p>\n</div>\n\n### Inline elements\n\nThe `<abbr>` element receives basic styling to make it stand out amongst paragraph text.\n\n<div class=\"bd-example\">\n  The <abbr title=\"HyperText Markup Language\">HTML</abbr> abbreviation element.\n</div>\n\n### Summary\n\nThe default `cursor` on summary is `text`, so we reset that to `pointer` to convey that the element can be interacted with by clicking on it.\n\n<div class=\"bd-example\">\n  <details>\n    <summary>Some details</summary>\n    <p>More info about the details.</p>\n  </details>\n\n  <details open>\n    <summary>Even more details</summary>\n    <p>Here are even more details about the details.</p>\n  </details>\n</div>\n\n## HTML5 `[hidden]` attribute\n\nHTML5 adds [a new global attribute named `[hidden]`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden), which is styled as `display: none` by default. Borrowing an idea from [PureCSS](https://purecss.io/), we improve upon this default by making `[hidden] { display: none !important; }` to help prevent its `display` from getting accidentally overridden.\n\n```html\n<input type=\"text\" hidden>\n```\n\n{{< callout warning >}}\n##### jQuery incompatibility\n\n`[hidden]` is not compatible with jQuery's `$(...).hide()` and `$(...).show()` methods. Therefore, we don't currently especially endorse `[hidden]` over other techniques for managing the `display` of elements.\n{{< /callout >}}\n\nTo merely toggle the visibility of an element, meaning its `display` is not modified and the element can still affect the flow of the document, use [the `.invisible` class]({{< docsref \"/utilities/visibility\" >}}) instead.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/content/tables.md",
    "content": "---\nlayout: docs\ntitle: Tables\ndescription: Documentation and examples for opt-in styling of tables (given their prevalent use in JavaScript plugins) with Bootstrap.\ngroup: content\ntoc: true\n---\n\n## Overview\n\nDue to the widespread use of `<table>` elements across third-party widgets like calendars and date pickers, Bootstrap's tables are **opt-in**. Add the base class `.table` to any `<table>`, then extend with our optional modifier classes or custom styles. All table styles are not inherited in Bootstrap, meaning any nested tables can be styled independent from the parent.\n\nUsing the most basic table markup, here's how `.table`-based tables look in Bootstrap.\n\n{{< table class=\"table\" simplified=\"false\" >}}\n\n## Variants\n\nUse contextual classes to color tables, table rows or individual cells.\n\n<div class=\"bd-example\">\n  <table class=\"table\">\n    <thead>\n      <tr>\n        <th scope=\"col\">Class</th>\n        <th scope=\"col\">Heading</th>\n        <th scope=\"col\">Heading</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        <th scope=\"row\">Default</th>\n        <td>Cell</td>\n        <td>Cell</td>\n      </tr>\n      {{< table.inline >}}\n      {{- range (index $.Site.Data \"theme-colors\") }}\n        <tr class=\"table-{{ .name }}\">\n          <th scope=\"row\">{{ .name | title }}</th>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n      {{- end -}}\n      {{< /table.inline >}}\n    </tbody>\n  </table>\n</div>\n\n{{< highlight html >}}\n<!-- On tables -->{{< table.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<table class=\"table-{{ .name }}\">...</table>\n{{- end -}}\n{{< /table.inline >}}\n\n<!-- On rows -->{{< table.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<tr class=\"table-{{ .name }}\">...</tr>\n{{- end -}}\n{{< /table.inline >}}\n\n<!-- On cells (`td` or `th`) -->\n<tr>{{< table.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n  <td class=\"table-{{ .name }}\">...</td>\n{{- end -}}\n{{< /table.inline >}}\n</tr>\n{{< /highlight >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n## Accented tables\n\n### Striped rows\n\nUse `.table-striped` to add zebra-striping to any table row within the `<tbody>`.\n\n{{< table class=\"table table-striped\" >}}\n\n### Striped columns\n\nUse `.table-striped-columns` to add zebra-striping to any table column.\n\n{{< table class=\"table table-striped-columns\" >}}\n\nThese classes can also be added to table variants:\n\n{{< table class=\"table table-dark table-striped\" >}}\n\n{{< table class=\"table table-dark table-striped-columns\" >}}\n\n{{< table class=\"table table-success table-striped\" >}}\n\n{{< table class=\"table table-success table-striped-columns\" >}}\n\n### Hoverable rows\n\nAdd `.table-hover` to enable a hover state on table rows within a `<tbody>`.\n\n{{< table class=\"table table-hover\" >}}\n\n{{< table class=\"table table-dark table-hover\" >}}\n\nThese hoverable rows can also be combined with the striped rows variant:\n\n{{< table class=\"table table-striped table-hover\" >}}\n\n### Active tables\n\nHighlight a table row or cell by adding a `.table-active` class.\n\n<div class=\"bd-example\">\n  <table class=\"table\">\n    <thead>\n      <tr>\n        <th scope=\"col\">#</th>\n        <th scope=\"col\">First</th>\n        <th scope=\"col\">Last</th>\n        <th scope=\"col\">Handle</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr class=\"table-active\">\n        <th scope=\"row\">1</th>\n        <td>Mark</td>\n        <td>Otto</td>\n        <td>@mdo</td>\n      </tr>\n      <tr>\n        <th scope=\"row\">2</th>\n        <td>Jacob</td>\n        <td>Thornton</td>\n        <td>@fat</td>\n      </tr>\n      <tr>\n        <th scope=\"row\">3</th>\n        <td colspan=\"2\" class=\"table-active\">Larry the Bird</td>\n        <td>@twitter</td>\n      </tr>\n    </tbody>\n  </table>\n</div>\n\n```html\n<table class=\"table\">\n  <thead>\n    ...\n  </thead>\n  <tbody>\n    <tr class=\"table-active\">\n      ...\n    </tr>\n    <tr>\n      ...\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td colspan=\"2\" class=\"table-active\">Larry the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n```\n\n<div class=\"bd-example\">\n  <table class=\"table table-dark\">\n    <thead>\n      <tr>\n        <th scope=\"col\">#</th>\n        <th scope=\"col\">First</th>\n        <th scope=\"col\">Last</th>\n        <th scope=\"col\">Handle</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr class=\"table-active\">\n        <th scope=\"row\">1</th>\n        <td>Mark</td>\n        <td>Otto</td>\n        <td>@mdo</td>\n      </tr>\n      <tr>\n        <th scope=\"row\">2</th>\n        <td>Jacob</td>\n        <td>Thornton</td>\n        <td>@fat</td>\n      </tr>\n      <tr>\n        <th scope=\"row\">3</th>\n        <td colspan=\"2\" class=\"table-active\">Larry the Bird</td>\n        <td>@twitter</td>\n      </tr>\n    </tbody>\n  </table>\n</div>\n\n```html\n<table class=\"table table-dark\">\n  <thead>\n    ...\n  </thead>\n  <tbody>\n    <tr class=\"table-active\">\n      ...\n    </tr>\n    <tr>\n      ...\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td colspan=\"2\" class=\"table-active\">Larry the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n```\n\n## How do the variants and accented tables work?\n\nFor the accented tables ([striped rows](#striped-rows), [striped columns](#striped-columns), [hoverable rows](#hoverable-rows), and [active tables](#active-tables)), we used some techniques to make these effects work for all our [table variants](#variants):\n\n- We start by setting the background of a table cell with the `--bs-table-bg` custom property. All table variants then set that custom property to colorize the table cells. This way, we don't get into trouble if semi-transparent colors are used as table backgrounds.\n- Then we add an inset box shadow on the table cells with `box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);` to layer on top of any specified `background-color`. Because we use a huge spread and no blur, the color will be monotone. Since `--bs-table-accent-bg` is unset by default, we don't have a default box shadow.\n- When either `.table-striped`, `.table-striped-columns`, `.table-hover` or `.table-active` classes are added, the `--bs-table-accent-bg` is set to a semitransparent color to colorize the background.\n- For each table variant, we generate a `--bs-table-accent-bg` color with the highest contrast depending on that color. For example, the accent color for `.table-primary` is darker while `.table-dark` has a lighter accent color.\n- Text and border colors are generated the same way, and their colors are inherited by default.\n\nBehind the scenes it looks like this:\n\n{{< scss-docs name=\"table-variant\" file=\"scss/mixins/_table-variants.scss\" >}}\n\n## Table borders\n\n### Bordered tables\n\nAdd `.table-bordered` for borders on all sides of the table and cells.\n\n{{< table class=\"table table-bordered\" >}}\n\n[Border color utilities]({{< docsref \"/utilities/borders#border-color\" >}}) can be added to change colors:\n\n{{< table class=\"table table-bordered border-primary\" >}}\n\n### Tables without borders\n\nAdd `.table-borderless` for a table without borders.\n\n{{< table class=\"table table-borderless\" >}}\n\n{{< table class=\"table table-dark table-borderless\" >}}\n\n## Small tables\n\nAdd `.table-sm` to make any `.table` more compact by cutting all cell `padding` in half.\n\n{{< table class=\"table table-sm\" >}}\n\n{{< table class=\"table table-dark table-sm\" >}}\n\n## Table group dividers\n\nAdd a thicker border, darker between table groups—`<thead>`, `<tbody>`, and `<tfoot>`—with `.table-group-divider`. Customize the color by changing the `border-top-color` (which we don't currently provide a utility class for at this time).\n\n{{< example >}}\n<table class=\"table\">\n  <thead>\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody class=\"table-group-divider\">\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">2</th>\n      <td>Jacob</td>\n      <td>Thornton</td>\n      <td>@fat</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td colspan=\"2\">Larry the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n{{< /example >}}\n\n## Vertical alignment\n\nTable cells of `<thead>` are always vertical aligned to the bottom. Table cells in `<tbody>` inherit their alignment from `<table>` and are aligned to the top by default. Use the [vertical align]({{< docsref \"/utilities/vertical-align\" >}}) classes to re-align where needed.\n\n<div class=\"bd-example\">\n  <div class=\"table-responsive\">\n    <table class=\"table align-middle\">\n      <thead>\n        <tr>\n          <th scope=\"col\" class=\"w-25\">Heading 1</th>\n          <th scope=\"col\" class=\"w-25\">Heading 2</th>\n          <th scope=\"col\" class=\"w-25\">Heading 3</th>\n          <th scope=\"col\" class=\"w-25\">Heading 4</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td>This cell inherits <code>vertical-align: middle;</code> from the table</td>\n          <td>This cell inherits <code>vertical-align: middle;</code> from the table</td>\n          <td>This cell inherits <code>vertical-align: middle;</code> from the table</td>\n          <td>This here is some placeholder text, intended to take up quite a bit of vertical space, to demonstrate how the vertical alignment works in the preceding cells.</td>\n        </tr>\n        <tr class=\"align-bottom\">\n          <td>This cell inherits <code>vertical-align: bottom;</code> from the table row</td>\n          <td>This cell inherits <code>vertical-align: bottom;</code> from the table row</td>\n          <td>This cell inherits <code>vertical-align: bottom;</code> from the table row</td>\n          <td>This here is some placeholder text, intended to take up quite a bit of vertical space, to demonstrate how the vertical alignment works in the preceding cells.</td>\n        </tr>\n        <tr>\n          <td>This cell inherits <code>vertical-align: middle;</code> from the table</td>\n          <td>This cell inherits <code>vertical-align: middle;</code> from the table</td>\n          <td class=\"align-top\">This cell is aligned to the top.</td>\n          <td>This here is some placeholder text, intended to take up quite a bit of vertical space, to demonstrate how the vertical alignment works in the preceding cells.</td>\n        </tr>\n      </tbody>\n    </table>\n  </div>\n</div>\n\n```html\n<div class=\"table-responsive\">\n  <table class=\"table align-middle\">\n    <thead>\n      <tr>\n        ...\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        ...\n      </tr>\n      <tr class=\"align-bottom\">\n        ...\n      </tr>\n      <tr>\n        <td>...</td>\n        <td>...</td>\n        <td class=\"align-top\">This cell is aligned to the top.</td>\n        <td>...</td>\n      </tr>\n    </tbody>\n  </table>\n</div>\n```\n\n## Nesting\n\nBorder styles, active styles, and table variants are not inherited by nested tables.\n\n<div class=\"bd-example\">\n<table class=\"table table-striped table-bordered\">\n  <thead>\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <td colspan=\"4\">\n        <table class=\"table mb-0\">\n          <thead>\n            <tr>\n              <th scope=\"col\">Header</th>\n              <th scope=\"col\">Header</th>\n              <th scope=\"col\">Header</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th scope=\"row\">A</th>\n              <td>First</td>\n              <td>Last</td>\n            </tr>\n            <tr>\n              <th scope=\"row\">B</th>\n              <td>First</td>\n              <td>Last</td>\n            </tr>\n            <tr>\n              <th scope=\"row\">C</th>\n              <td>First</td>\n              <td>Last</td>\n            </tr>\n          </tbody>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td>Larry</td>\n      <td>the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n</div>\n\n```html\n<table class=\"table table-striped\">\n  <thead>\n    ...\n  </thead>\n  <tbody>\n    ...\n    <tr>\n      <td colspan=\"4\">\n        <table class=\"table mb-0\">\n          ...\n        </table>\n      </td>\n    </tr>\n    ...\n  </tbody>\n</table>\n```\n\n## How nesting works\n\nTo prevent _any_ styles from leaking to nested tables, we use the child combinator (`>`) selector in our CSS. Since we need to target all the `td`s and `th`s in the `thead`, `tbody`, and `tfoot`, our selector would look pretty long without it. As such, we use the rather odd looking `.table > :not(caption) > * > *` selector to target all `td`s and `th`s of the `.table`, but none of any potential nested tables.\n\nNote that if you add `<tr>`s as direct children of a table, those `<tr>` will be wrapped in a `<tbody>` by default, thus making our selectors work as intended.\n\n## Anatomy\n\n### Table head\n\nSimilar to tables and dark tables, use the modifier classes `.table-light` or `.table-dark` to make `<thead>`s appear light or dark gray.\n\n<div class=\"bd-example\">\n<table class=\"table\">\n  <thead class=\"table-light\">\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">2</th>\n      <td>Jacob</td>\n      <td>Thornton</td>\n      <td>@fat</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td>Larry</td>\n      <td>the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n</div>\n\n```html\n<table class=\"table\">\n  <thead class=\"table-light\">\n    ...\n  </thead>\n  <tbody>\n    ...\n  </tbody>\n</table>\n```\n\n<div class=\"bd-example\">\n<table class=\"table\">\n  <thead class=\"table-dark\">\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">2</th>\n      <td>Jacob</td>\n      <td>Thornton</td>\n      <td>@fat</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td>Larry</td>\n      <td>the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n</div>\n\n```html\n<table class=\"table\">\n  <thead class=\"table-dark\">\n    ...\n  </thead>\n  <tbody>\n    ...\n  </tbody>\n</table>\n```\n\n### Table foot\n\n<div class=\"bd-example\">\n<table class=\"table\">\n  <thead class=\"table-light\">\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">2</th>\n      <td>Jacob</td>\n      <td>Thornton</td>\n      <td>@fat</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td>Larry</td>\n      <td>the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n  <tfoot>\n    <tr>\n      <td>Footer</td>\n      <td>Footer</td>\n      <td>Footer</td>\n      <td>Footer</td>\n    </tr>\n  </tfoot>\n</table>\n</div>\n\n```html\n<table class=\"table\">\n  <thead>\n    ...\n  </thead>\n  <tbody>\n    ...\n  </tbody>\n  <tfoot>\n    ...\n  </tfoot>\n</table>\n```\n\n### Captions\n\nA `<caption>` functions like a heading for a table. It helps users with screen readers to find a table and understand what it's about and decide if they want to read it.\n\n<div class=\"bd-example\">\n  <table class=\"table\">\n    <caption>List of users</caption>\n    {{< partial \"table-content\" >}}\n  </table>\n</div>\n\n```html\n<table class=\"table table-sm\">\n  <caption>List of users</caption>\n  <thead>\n    ...\n  </thead>\n  <tbody>\n    ...\n  </tbody>\n</table>\n```\n\nYou can also put the `<caption>` on the top of the table with `.caption-top`.\n\n{{< example >}}\n<table class=\"table caption-top\">\n  <caption>List of users</caption>\n  <thead>\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">2</th>\n      <td>Jacob</td>\n      <td>Thornton</td>\n      <td>@fat</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td>Larry</td>\n      <td>the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n</table>\n{{< /example >}}\n\n## Responsive tables\n\nResponsive tables allow tables to be scrolled horizontally with ease. Make any table responsive across all viewports by wrapping a `.table` with `.table-responsive`. Or, pick a maximum breakpoint with which to have a responsive table up to by using `.table-responsive{-sm|-md|-lg|-xl|-xxl}`.\n\n{{< callout warning >}}\n##### Vertical clipping/truncation\n\nResponsive tables make use of `overflow-y: hidden`, which clips off any content that goes beyond the bottom or top edges of the table. In particular, this can clip off dropdown menus and other third-party widgets.\n{{< /callout >}}\n\n### Always responsive\n\nAcross every breakpoint, use `.table-responsive` for horizontally scrolling tables.\n\n<div class=\"bd-example\">\n  <div class=\"table-responsive\">\n    <table class=\"table\">\n      <thead>\n        <tr>\n          <th scope=\"col\">#</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <th scope=\"row\">1</th>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n        <tr>\n          <th scope=\"row\">2</th>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n        <tr>\n          <th scope=\"row\">3</th>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n      </tbody>\n    </table>\n  </div>\n</div>\n\n```html\n<div class=\"table-responsive\">\n  <table class=\"table\">\n    ...\n  </table>\n</div>\n```\n\n### Breakpoint specific\n\nUse `.table-responsive{-sm|-md|-lg|-xl|-xxl}` as needed to create responsive tables up to a particular breakpoint. From that breakpoint and up, the table will behave normally and not scroll horizontally.\n\n**These tables may appear broken until their responsive styles apply at specific viewport widths.**\n\n{{< tables.inline >}}\n{{ range $.Site.Data.breakpoints }}\n{{ if not (eq . \"xs\") }}\n<div class=\"bd-example\">\n  <div class=\"table-responsive{{ .abbr }}\">\n    <table class=\"table\">\n      <thead>\n        <tr>\n          <th scope=\"col\">#</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n          <th scope=\"col\">Heading</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <th scope=\"row\">1</th>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n        <tr>\n          <th scope=\"row\">2</th>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n        <tr>\n          <th scope=\"row\">3</th>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n          <td>Cell</td>\n        </tr>\n      </tbody>\n    </table>\n  </div>\n</div>\n{{ end -}}\n{{- end -}}\n{{< /tables.inline >}}\n\n{{< highlight html >}}\n{{< tables.inline >}}\n{{- range $.Site.Data.breakpoints -}}\n{{- if not (eq . \"xs\") }}\n<div class=\"table-responsive{{ .abbr }}\">\n  <table class=\"table\">\n    ...\n  </table>\n</div>\n{{ end -}}\n{{- end -}}\n{{< /tables.inline >}}\n{{< /highlight >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"table-variables\" file=\"scss/_variables.scss\" >}}\n\n### Loop\n\n{{< scss-docs name=\"table-loop\" file=\"scss/_variables.scss\" >}}\n\n### Customizing\n\n- The factor variables (`$table-striped-bg-factor`, `$table-active-bg-factor` & `$table-hover-bg-factor`) are used to determine the contrast in table variants.\n- Apart from the light & dark table variants, theme colors are lightened by the `$table-bg-scale` variable.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/content/typography.md",
    "content": "---\nlayout: docs\ntitle: Typography\ndescription: Documentation and examples for Bootstrap typography, including global settings, headings, body text, lists, and more.\ngroup: content\ntoc: true\n---\n\n## Global settings\n\nBootstrap sets basic global display, typography, and link styles. When more control is needed, check out the [textual utility classes]({{< docsref \"/utilities/text\" >}}).\n\n- Use a [native font stack]({{< docsref \"/content/reboot#native-font-stack\" >}}) that selects the best `font-family` for each OS and device.\n- For a more inclusive and accessible type scale, we use the browser's default root `font-size` (typically 16px) so visitors can customize their browser defaults as needed.\n- Use the `$font-family-base`, `$font-size-base`, and `$line-height-base` attributes as our typographic base applied to the `<body>`.\n- Set the global link color via `$link-color`.\n- Use `$body-bg` to set a `background-color` on the `<body>` (`#fff` by default).\n\nThese styles can be found within `_reboot.scss`, and the global variables are defined in `_variables.scss`. Make sure to set `$font-size-base` in `rem`.\n\n## Headings\n\nAll HTML headings, `<h1>` through `<h6>`, are available.\n\n{{< bs-table >}}\n| Heading | Example |\n| --- | --- |\n| `<h1></h1>` | <span class=\"h1\">h1. Bootstrap heading</span> |\n| `<h2></h2>` | <span class=\"h2\">h2. Bootstrap heading</span> |\n| `<h3></h3>` | <span class=\"h3\">h3. Bootstrap heading</span> |\n| `<h4></h4>` | <span class=\"h4\">h4. Bootstrap heading</span> |\n| `<h5></h5>` | <span class=\"h5\">h5. Bootstrap heading</span> |\n| `<h6></h6>` | <span class=\"h6\">h6. Bootstrap heading</span> |\n{{< /bs-table >}}\n\n```html\n<h1>h1. Bootstrap heading</h1>\n<h2>h2. Bootstrap heading</h2>\n<h3>h3. Bootstrap heading</h3>\n<h4>h4. Bootstrap heading</h4>\n<h5>h5. Bootstrap heading</h5>\n<h6>h6. Bootstrap heading</h6>\n```\n\n`.h1` through `.h6` classes are also available, for when you want to match the font styling of a heading but cannot use the associated HTML element.\n\n{{< example >}}\n<p class=\"h1\">h1. Bootstrap heading</p>\n<p class=\"h2\">h2. Bootstrap heading</p>\n<p class=\"h3\">h3. Bootstrap heading</p>\n<p class=\"h4\">h4. Bootstrap heading</p>\n<p class=\"h5\">h5. Bootstrap heading</p>\n<p class=\"h6\">h6. Bootstrap heading</p>\n{{< /example >}}\n\n### Customizing headings\n\nUse the included utility classes to recreate the small secondary heading text from Bootstrap 3.\n\n{{< example >}}\n<h3>\n  Fancy display heading\n  <small class=\"text-muted\">With faded secondary text</small>\n</h3>\n{{< /example >}}\n\n## Display headings\n\nTraditional heading elements are designed to work best in the meat of your page content. When you need a heading to stand out, consider using a **display heading**—a larger, slightly more opinionated heading style.\n\n<div class=\"bd-example\">\n  <div class=\"display-1 pb-3 mb-3 border-bottom\">Display 1</div>\n  <div class=\"display-2 pb-3 mb-3 border-bottom\">Display 2</div>\n  <div class=\"display-3 pb-3 mb-3 border-bottom\">Display 3</div>\n  <div class=\"display-4 pb-3 mb-3 border-bottom\">Display 4</div>\n  <div class=\"display-5 pb-3 mb-3 border-bottom\">Display 5</div>\n  <div class=\"display-6\">Display 6</div>\n</div>\n\n```html\n<h1 class=\"display-1\">Display 1</h1>\n<h1 class=\"display-2\">Display 2</h1>\n<h1 class=\"display-3\">Display 3</h1>\n<h1 class=\"display-4\">Display 4</h1>\n<h1 class=\"display-5\">Display 5</h1>\n<h1 class=\"display-6\">Display 6</h1>\n```\n\nDisplay headings are configured via the `$display-font-sizes` Sass map and two variables, `$display-font-weight` and `$display-line-height`.\n\nDisplay headings are customizable via two variables, `$display-font-family` and `$display-font-style`.\n\n{{< scss-docs name=\"display-headings\" file=\"scss/_variables.scss\" >}}\n\n## Lead\n\nMake a paragraph stand out by adding `.lead`.\n\n{{< example >}}\n<p class=\"lead\">\n  This is a lead paragraph. It stands out from regular paragraphs.\n</p>\n{{< /example >}}\n\n## Inline text elements\n\nStyling for common inline HTML5 elements.\n\n{{< example >}}\n<p>You can use the mark tag to <mark>highlight</mark> text.</p>\n<p><del>This line of text is meant to be treated as deleted text.</del></p>\n<p><s>This line of text is meant to be treated as no longer accurate.</s></p>\n<p><ins>This line of text is meant to be treated as an addition to the document.</ins></p>\n<p><u>This line of text will render as underlined.</u></p>\n<p><small>This line of text is meant to be treated as fine print.</small></p>\n<p><strong>This line rendered as bold text.</strong></p>\n<p><em>This line rendered as italicized text.</em></p>\n{{< /example >}}\n\nBeware that those tags should be used for semantic purpose:\n\n- `<mark>` represents text which is marked or highlighted for reference or notation purposes.\n- `<small>` represents side-comments and small print, like copyright and legal text.\n- `<s>` represents element that are no longer relevant or no longer accurate.\n- `<u>` represents a span of inline text which should be rendered in a way that indicates that it has a non-textual annotation.\n\nIf you want to style your text, you should use the following classes instead:\n\n- `.mark` will apply the same styles as `<mark>`.\n- `.small` will apply the same styles as `<small>`.\n- `.text-decoration-underline` will apply the same styles as `<u>`.\n- `.text-decoration-line-through` will apply the same styles as `<s>`.\n\nWhile not shown above, feel free to use `<b>` and `<i>` in HTML5. `<b>` is meant to highlight words or phrases without conveying additional importance, while `<i>` is mostly for voice, technical terms, etc.\n\n## Text utilities\n\nChange text alignment, transform, style, weight, line-height, decoration and color with our [text utilities]({{< docsref \"/utilities/text\" >}}) and [color utilities]({{< docsref \"/utilities/colors\" >}}).\n\n## Abbreviations\n\nStylized implementation of HTML's `<abbr>` element for abbreviations and acronyms to show the expanded version on hover. Abbreviations have a default underline and gain a help cursor to provide additional context on hover and to users of assistive technologies.\n\nAdd `.initialism` to an abbreviation for a slightly smaller font-size.\n\n{{< example >}}\n<p><abbr title=\"attribute\">attr</abbr></p>\n<p><abbr title=\"HyperText Markup Language\" class=\"initialism\">HTML</abbr></p>\n{{< /example >}}\n\n## Blockquotes\n\nFor quoting blocks of content from another source within your document. Wrap `<blockquote class=\"blockquote\">` around any HTML as the quote.\n\n{{< example >}}\n<blockquote class=\"blockquote\">\n  <p>A well-known quote, contained in a blockquote element.</p>\n</blockquote>\n{{< /example >}}\n\n### Naming a source\n\nThe HTML spec requires that blockquote attribution be placed outside the `<blockquote>`. When providing attribution, wrap your `<blockquote>` in a `<figure>` and use a `<figcaption>` or a block level element (e.g., `<p>`) with the `.blockquote-footer` class. Be sure to wrap the name of the source work in `<cite>` as well.\n\n{{< example >}}\n<figure>\n  <blockquote class=\"blockquote\">\n    <p>A well-known quote, contained in a blockquote element.</p>\n  </blockquote>\n  <figcaption class=\"blockquote-footer\">\n    Someone famous in <cite title=\"Source Title\">Source Title</cite>\n  </figcaption>\n</figure>\n{{< /example >}}\n\n### Alignment\n\nUse text utilities as needed to change the alignment of your blockquote.\n\n{{< example >}}\n<figure class=\"text-center\">\n  <blockquote class=\"blockquote\">\n    <p>A well-known quote, contained in a blockquote element.</p>\n  </blockquote>\n  <figcaption class=\"blockquote-footer\">\n    Someone famous in <cite title=\"Source Title\">Source Title</cite>\n  </figcaption>\n</figure>\n{{< /example >}}\n\n{{< example >}}\n<figure class=\"text-end\">\n  <blockquote class=\"blockquote\">\n    <p>A well-known quote, contained in a blockquote element.</p>\n  </blockquote>\n  <figcaption class=\"blockquote-footer\">\n    Someone famous in <cite title=\"Source Title\">Source Title</cite>\n  </figcaption>\n</figure>\n{{< /example >}}\n\n## Lists\n\n### Unstyled\n\nRemove the default `list-style` and left margin on list items (immediate children only). **This only applies to immediate children list items**, meaning you will need to add the class for any nested lists as well.\n\n{{< example >}}\n<ul class=\"list-unstyled\">\n  <li>This is a list.</li>\n  <li>It appears completely unstyled.</li>\n  <li>Structurally, it's still a list.</li>\n  <li>However, this style only applies to immediate child elements.</li>\n  <li>Nested lists:\n    <ul>\n      <li>are unaffected by this style</li>\n      <li>will still show a bullet</li>\n      <li>and have appropriate left margin</li>\n    </ul>\n  </li>\n  <li>This may still come in handy in some situations.</li>\n</ul>\n{{< /example >}}\n\n### Inline\n\nRemove a list's bullets and apply some light `margin` with a combination of two classes, `.list-inline` and `.list-inline-item`.\n\n{{< example >}}\n<ul class=\"list-inline\">\n  <li class=\"list-inline-item\">This is a list item.</li>\n  <li class=\"list-inline-item\">And another one.</li>\n  <li class=\"list-inline-item\">But they're displayed inline.</li>\n</ul>\n{{< /example >}}\n\n### Description list alignment\n\nAlign terms and descriptions horizontally by using our grid system's predefined classes (or semantic mixins). For longer terms, you can optionally add a `.text-truncate` class to truncate the text with an ellipsis.\n\n{{< example >}}\n<dl class=\"row\">\n  <dt class=\"col-sm-3\">Description lists</dt>\n  <dd class=\"col-sm-9\">A description list is perfect for defining terms.</dd>\n\n  <dt class=\"col-sm-3\">Term</dt>\n  <dd class=\"col-sm-9\">\n    <p>Definition for the term.</p>\n    <p>And some more placeholder definition text.</p>\n  </dd>\n\n  <dt class=\"col-sm-3\">Another term</dt>\n  <dd class=\"col-sm-9\">This definition is short, so no extra paragraphs or anything.</dd>\n\n  <dt class=\"col-sm-3 text-truncate\">Truncated term is truncated</dt>\n  <dd class=\"col-sm-9\">This can be useful when space is tight. Adds an ellipsis at the end.</dd>\n\n  <dt class=\"col-sm-3\">Nesting</dt>\n  <dd class=\"col-sm-9\">\n    <dl class=\"row\">\n      <dt class=\"col-sm-4\">Nested definition list</dt>\n      <dd class=\"col-sm-8\">I heard you like definition lists. Let me put a definition list inside your definition list.</dd>\n    </dl>\n  </dd>\n</dl>\n{{< /example >}}\n\n## Responsive font sizes\n\nIn Bootstrap 5, we've enabled responsive font sizes by default, allowing text to scale more naturally across device and viewport sizes. Have a look at the [RFS page]({{< docsref \"/getting-started/rfs\" >}}) to find out how this works.\n\n## Sass\n\n### Variables\n\nHeadings have some dedicated variables for sizing and spacing.\n\n{{< scss-docs name=\"headings-variables\" file=\"scss/_variables.scss\" >}}\n\nMiscellaneous typography elements covered here and in [Reboot]({{< docsref \"/content/reboot\" >}}) also have dedicated variables.\n\n{{< scss-docs name=\"type-variables\" file=\"scss/_variables.scss\" >}}\n\n### Mixins\n\nThere are no dedicated mixins for typography, but Bootstrap does use [Responsive Font Sizing (RFS)]({{< docsref \"/getting-started/rfs\" >}}).\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/color.md",
    "content": "---\nlayout: docs\ntitle: Color\ndescription: Bootstrap is supported by an extensive color system that themes our styles and components. This enables more comprehensive customization and extension for any project.\ngroup: customize\ntoc: true\n---\n\n## Theme colors\n\nWe use a subset of all colors to create a smaller color palette for generating color schemes, also available as Sass variables and a Sass map in Bootstrap's `scss/_variables.scss` file.\n\n<div class=\"row\">\n  {{< theme-colors.inline >}}\n  {{- range (index $.Site.Data \"theme-colors\") }}\n    <div class=\"col-md-4\">\n      <div class=\"p-3 mb-3 bg-{{ .name }} {{ if .contrast_color }}text-{{ .contrast_color }}{{ else }}text-white{{ end }}\">{{ .name | title }}</div>\n    </div>\n  {{ end -}}\n  {{< /theme-colors.inline >}}\n</div>\n\nAll these colors are available as a Sass map, `$theme-colors`.\n\n{{< scss-docs name=\"theme-colors-map\" file=\"scss/_variables.scss\" >}}\n\nCheck out [our Sass maps and loops docs]({{< docsref \"/customize/sass#maps-and-loops\" >}}) for how to modify these colors.\n\n## All colors\n\nAll Bootstrap colors are available as Sass variables and a Sass map in `scss/_variables.scss` file. To avoid increased file sizes, we don't create text or background color classes for each of these variables. Instead, we choose a subset of these colors for a [theme palette](#theme-colors).\n\nBe sure to monitor contrast ratios as you customize colors. As shown below, we've added three contrast ratios to each of the main colors—one for the swatch's current colors, one for against white, and one for against black.\n\n<div class=\"row font-monospace\">\n  {{< theme-colors.inline >}}\n  {{- range $color := $.Site.Data.colors }}\n    {{- if (and (not (eq $color.name \"white\")) (not (eq $color.name \"gray\")) (not (eq $color.name \"gray-dark\"))) }}\n    <div class=\"col-md-4 mb-3\">\n      <div class=\"p-3 mb-2 position-relative swatch-{{ $color.name }}\">\n        <strong class=\"d-block\">${{ $color.name }}</strong>\n        {{ $color.hex }}\n      </div>\n      {{ range (seq 100 100 900) }}\n      <div class=\"p-3 bd-{{ $color.name }}-{{ . }}\">${{ $color.name }}-{{ . }}</div>\n      {{ end }}\n    </div>\n    {{ end -}}\n  {{ end -}}\n\n  <div class=\"col-md-4 mb-3\">\n    <div class=\"p-3 mb-2 position-relative swatch-gray-500\">\n      <strong class=\"d-block\">$gray-500</strong>\n      #adb5bd\n    </div>\n  {{- range $.Site.Data.grays }}\n    <div class=\"p-3 bd-gray-{{ .name }}\">$gray-{{ .name }}</div>\n  {{ end -}}\n  </div>\n  {{< /theme-colors.inline >}}\n\n  <div class=\"col-md-4 mb-3\">\n    <div class=\"p-3 mb-2 bd-black text-white\">\n      <strong class=\"d-block\">$black</strong>\n      #000\n    </div>\n    <div class=\"p-3 mb-2 bd-white border\">\n      <strong class=\"d-block\">$white</strong>\n      #fff\n    </div>\n  </div>\n</div>\n\n### Notes on Sass\n\nSass cannot programmatically generate variables, so we manually created variables for every tint and shade ourselves. We specify the midpoint value (e.g., `$blue-500`) and use custom color functions to tint (lighten) or shade (darken) our colors via Sass's `mix()` color function.\n\nUsing `mix()` is not the same as `lighten()` and `darken()`—the former blends the specified color with white or black, while the latter only adjusts the lightness value of each color. The result is a much more complete suite of colors, as [shown in this CodePen demo](https://codepen.io/emdeoh/pen/zYOQOPB).\n\nOur `tint-color()` and `shade-color()` functions use `mix()` alongside our `$theme-color-interval` variable, which specifies a stepped percentage value for each mixed color we produce. See the `scss/_functions.scss` and `scss/_variables.scss` files for the full source code.\n\n## Color Sass maps\n\nBootstrap's source Sass files include three maps to help you quickly and easily loop over a list of colors and their hex values.\n\n- `$colors` lists all our available base (`500`) colors\n- `$theme-colors` lists all semantically named theme colors (shown below)\n- `$grays` lists all tints and shades of gray\n\nWithin `scss/_variables.scss`, you'll find Bootstrap's color variables and Sass map. Here's an example of the `$colors` Sass map:\n\n{{< scss-docs name=\"colors-map\" file=\"scss/_variables.scss\" >}}\n\nAdd, remove, or modify values within the map to update how they're used in many other components. Unfortunately at this time, not _every_ component utilizes this Sass map. Future updates will strive to improve upon this. Until then, plan on making use of the `${color}` variables and this Sass map.\n\n### Example\n\nHere's how you can use these in your Sass:\n\n```scss\n.alpha { color: $purple; }\n.beta {\n  color: $yellow-300;\n  background-color: $indigo-900;\n}\n```\n\n[Color]({{< docsref \"/utilities/colors\" >}}) and [background]({{< docsref \"/utilities/background\" >}}) utility classes are also available for setting `color` and `background-color` using the `500` color values.\n\n## Generating utilities\n\n{{< added-in \"5.1.0\" >}}\n\nBootstrap doesn't include `color` and `background-color` utilities for every color variable, but you can generate these yourself with our [utility API]({{< docsref \"/utilities/api\" >}}) and our extended Sass maps added in v5.1.0.\n\n1. To start, make sure you've imported our functions, variables, mixins, and utilities.\n2. Use our `map-merge-multiple()` function to quickly merge multiple Sass maps together in a new map.\n3. Merge this new combined map to extend any utility with a `{color}-{level}` class name.\n\nHere's an example that generates text color utilities (e.g., `.text-purple-500`) using the above steps.\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$all-colors: map-merge-multiple($blues, $indigos, $purples, $pinks, $reds, $oranges, $yellows, $greens, $teals, $cyans);\n\n$utilities: map-merge(\n  $utilities,\n  (\n    \"color\": map-merge(\n      map-get($utilities, \"color\"),\n      (\n        values: map-merge(\n          map-get(map-get($utilities, \"color\"), \"values\"),\n          (\n            $all-colors\n          ),\n        ),\n      ),\n    ),\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\nThis will generate new `.text-{color}-{level}` utilities for every color and level. You can do the same for any other utility and property as well.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/components.md",
    "content": "---\nlayout: docs\ntitle: Components\ndescription: Learn how and why we build nearly all our components responsively and with base and modifier classes.\ngroup: customize\ntoc: true\n---\n\n## Base classes\n\nBootstrap's components are largely built with a base-modifier nomenclature. We group as many shared properties as possible into a base class, like `.btn`, and then group individual styles for each variant into modifier classes, like `.btn-primary` or `.btn-success`.\n\nTo build our modifier classes, we use Sass's `@each` loops to iterate over a Sass map. This is especially helpful for generating variants of a component by our `$theme-colors` and creating responsive variants for each breakpoint. As you customize these Sass maps and recompile, you'll automatically see your changes reflected in these loops.\n\nCheck out [our Sass maps and loops docs]({{< docsref \"/customize/sass#maps-and-loops\" >}}) for how to customize these loops and extend Bootstrap's base-modifier approach to your own code.\n\n## Modifiers\n\nMany of Bootstrap's components are built with a base-modifier class approach. This means the bulk of the styling is contained to a base class (e.g., `.btn`) while style variations are confined to modifier classes (e.g., `.btn-danger`). These modifier classes are built from the `$theme-colors` map to make customizing the number and name of our modifier classes.\n\nHere are two examples of how we loop over the `$theme-colors` map to generate modifiers to the `.alert` and `.list-group` components.\n\n{{< scss-docs name=\"alert-modifiers\" file=\"scss/_alert.scss\" >}}\n\n{{< scss-docs name=\"list-group-modifiers\" file=\"scss/_list-group.scss\" >}}\n\n## Responsive\n\nThese Sass loops aren't limited to color maps, either. You can also generate responsive variations of your components. Take for example our responsive alignment of the dropdowns where we mix an `@each` loop for the `$grid-breakpoints` Sass map with a media query include.\n\n{{< scss-docs name=\"responsive-breakpoints\" file=\"scss/_dropdown.scss\" >}}\n\nShould you modify your `$grid-breakpoints`, your changes will apply to all the loops iterating over that map.\n\n{{< scss-docs name=\"grid-breakpoints\" file=\"scss/_variables.scss\" >}}\n\nFor more information and examples on how to modify our Sass maps and variables, please refer to [the Sass section of the Grid documentation]({{< docsref \"/layout/grid#sass\" >}}).\n\n## Creating your own\n\nWe encourage you to adopt these guidelines when building with Bootstrap to create your own components. We've extended this approach ourselves to the custom components in our documentation and examples. Components like our callouts are built just like our provided components with base and modifier classes.\n\n<div class=\"bd-example\">\n  <div class=\"bd-callout my-0\">\n    <strong>This is a callout.</strong> We built it custom for our docs so our messages to you stand out. It has three variants via modifier classes.\n  </div>\n</div>\n\n```html\n<div class=\"callout\">...</div>\n```\n\nIn your CSS, you'd have something like the following where the bulk of the styling is done via `.callout`. Then, the unique styles between each variant is controlled via modifier class.\n\n```scss\n// Base class\n.callout {}\n\n// Modifier classes\n.callout-info {}\n.callout-warning {}\n.callout-danger {}\n```\n\nFor the callouts, that unique styling is just a `border-left-color`. When you combine that base class with one of those modifier classes, you get your complete component family:\n\n{{< callout info >}}\n**This is an info callout.** Example text to show it in action.\n{{< /callout >}}\n\n{{< callout warning >}}\n**This is a warning callout.** Example text to show it in action.\n{{< /callout >}}\n\n{{< callout danger >}}\n**This is a danger callout.** Example text to show it in action.\n{{< /callout >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/css-variables.md",
    "content": "---\nlayout: docs\ntitle: CSS variables\ndescription: Use Bootstrap's CSS custom properties for fast and forward-looking design and development.\ngroup: customize\ntoc: true\n---\n\nBootstrap includes many [CSS custom properties (variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in its compiled CSS for real-time customization without the need to recompile Sass. These provide easy access to commonly used values like our theme colors, breakpoints, and primary font stacks when working in your browser's inspector, a code sandbox, or general prototyping.\n\n**All our custom properties are prefixed with `bs-`** to avoid conflicts with third party CSS.\n\n## Root variables\n\nHere are the variables we include (note that the `:root` is required) that can be accessed anywhere Bootstrap's CSS is loaded. They're located in our `_root.scss` file and included in our compiled dist files.\n\n```css\n{{< root.inline >}}\n{{- $css := readFile \"dist/css/bootstrap.css\" -}}\n{{- $match := findRE \":root {([^}]*)}\" $css 1 -}}\n\n{{- if (eq (len $match) 0) -}}\n{{- errorf \"Got no matches for :root in %q!\" $.Page.Path -}}\n{{- end -}}\n\n{{- index $match 0 -}}\n\n{{< /root.inline >}}\n```\n\n## Component variables\n\nBootstrap 5 is increasingly making use of custom properties as local variables for various components. This way we reduce our compiled CSS, ensure styles aren't inherited in places like nested tables, and allow some basic restyling and extending of Bootstrap components after Sass compilation.\n\nHave a look at our table documentation for some [insight into how we're using CSS variables]({{< docsref \"/content/tables#how-do-the-variants-and-accented-tables-work\" >}}). Our [navbars also use CSS variables]({{< docsref \"/components/navbar#css\" >}}) as of v5.2.0. We're also using CSS variables across our grids—primarily for gutters the [new opt-in CSS grid]({{< docsref \"/layout/css-grid\" >}})—with more component usage coming in the future.\n\nWhenever possible, we'll assign CSS variables at the base component level (e.g., `.navbar` for navbar and its sub-components). This reduces guessing on where and how to customize, and allows for easy modifications by our team in future updates.\n\n## Prefix\n\nMost CSS variables use a prefix to avoid collisions with your own codebase. This prefix is in addition to the `--` that's required on every CSS variable.\n\nCustomize the prefix via the `$prefix` Sass variable. By default, it's set to `bs-` (note the trailing dash).\n\n## Examples\n\nCSS variables offer similar flexibility to Sass's variables, but without the need for compilation before being served to the browser. For example, here we're resetting our page's font and link styles with CSS variables.\n\n```css\nbody {\n  font: 1rem/1.5 var(--bs-font-sans-serif);\n}\na {\n  color: var(--bs-blue);\n}\n```\n\n## Grid breakpoints\n\nWhile we include our grid breakpoints as CSS variables (except for `xs`), be aware that **CSS variables do not work in media queries**. This is by design in the CSS spec for variables, but may change in coming years with support for `env()` variables. Check out [this Stack Overflow answer](https://stackoverflow.com/a/47212942) for some helpful links. In the meantime, you can use these variables in other CSS situations, as well as in your JavaScript.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/optimize.md",
    "content": "---\nlayout: docs\ntitle: Optimize\ndescription: Keep your projects lean, responsive, and maintainable so you can deliver the best experience and focus on more important jobs.\ngroup: customize\ntoc: true\n---\n\n## Lean Sass imports\n\nWhen using Sass in your asset pipeline, make sure you optimize Bootstrap by only `@import`ing the components you need. Your largest optimizations will likely come from the `Layout & Components` section of our `bootstrap.scss`.\n\n{{< scss-docs name=\"import-stack\" file=\"scss/bootstrap.scss\" >}}\n\n\nIf you're not using a component, comment it out or delete it entirely. For example, if you're not using the carousel, remove that import to save some file size in your compiled CSS. Keep in mind there are some dependencies across Sass imports that may make it more difficult to omit a file.\n\n## Lean JavaScript\n\nBootstrap's JavaScript includes every component in our primary dist files (`bootstrap.js` and `bootstrap.min.js`), and even our primary dependency (Popper) with our bundle files (`bootstrap.bundle.js` and `bootstrap.bundle.min.js`). While you're customizing via Sass, be sure to remove related JavaScript.\n\nFor instance, assuming you're using your own JavaScript bundler like Webpack, Parcel, or Vite, you'd only import the JavaScript you plan on using. In the example below, we show how to just include our modal JavaScript:\n\n<!-- eslint-skip -->\n```js\n// Import just what we need\n\n// import 'bootstrap/js/dist/alert';\n// import 'bootstrap/js/dist/button';\n// import 'bootstrap/js/dist/carousel';\n// import 'bootstrap/js/dist/collapse';\n// import 'bootstrap/js/dist/dropdown';\nimport 'bootstrap/js/dist/modal';\n// import 'bootstrap/js/dist/offcanvas';\n// import 'bootstrap/js/dist/popover';\n// import 'bootstrap/js/dist/scrollspy';\n// import 'bootstrap/js/dist/tab';\n// import 'bootstrap/js/dist/toast';\n// import 'bootstrap/js/dist/tooltip';\n```\n\nThis way, you're not including any JavaScript you don't intend to use for components like buttons, carousels, and tooltips. If you're importing dropdowns, tooltips or popovers, be sure to list the Popper dependency in your `package.json` file.\n\n{{< callout info >}}\n### Default Exports\n\nFiles in `bootstrap/js/dist` use the **default export**, so if you want to use one of them you have to do the following:\n\n<!-- eslint-skip -->\n```js\nimport Modal from 'bootstrap/js/dist/modal'\n\nconst modal = new Modal(document.getElementById('myModal'))\n```\n{{< /callout >}}\n\n## Autoprefixer .browserslistrc\n\nBootstrap depends on Autoprefixer to automatically add browser prefixes to certain CSS properties. Prefixes are dictated by our `.browserslistrc` file, found in the root of the Bootstrap repo. Customizing this list of browsers and recompiling the Sass will automatically remove some CSS from your compiled CSS, if there are vendor prefixes unique to that browser or version.\n\n## Unused CSS\n\n_Help wanted with this section, please consider opening a PR. Thanks!_\n\nWhile we don't have a prebuilt example for using [PurgeCSS](https://github.com/FullHuman/purgecss) with Bootstrap, there are some helpful articles and walkthroughs that the community has written. Here are some options:\n\n- <https://medium.com/dwarves-foundation/remove-unused-css-styles-from-bootstrap-using-purgecss-88395a2c5772>\n- <https://lukelowrey.com/automatically-removeunused-css-from-bootstrap-or-other-frameworks/>\n\nLastly, this [CSS Tricks article on unused CSS](https://css-tricks.com/how-do-you-remove-unused-css-from-a-site/) shows how to use PurgeCSS and other similar tools.\n\n## Minify and gzip\n\nWhenever possible, be sure to compress all the code you serve to your visitors. If you're using Bootstrap dist files, try to stick to the minified versions (indicated by the `.min.css` and `.min.js` extensions). If you're building Bootstrap from the source with your own build system, be sure to implement your own minifiers for HTML, CSS, and JS.\n\n## Non-blocking files\n\nWhile minifying and using compression might seem like enough, making your files non-blocking ones is also a big step in making your site well-optimized and fast enough.\n\nIf you are using a [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) plugin in Google Chrome, you may have stumbled over FCP. [The First Contentful Paint](https://web.dev/fcp/) metric measures the time from when the page starts loading to when any part of the page's content is rendered on the screen.\n\nYou can improve FCP by deferring non-critical JavaScript or CSS. What does that mean? Simply, JavaScript or stylesheets that don't need to be present on the first paint of your page should be marked with `async` or `defer` attributes.\n\nThis ensures that the less important resources are loaded later and not blocking the first paint. On the other hand, critical resources can be included as inline scripts or styles.\n\nIf you want to learn more about this, there are already a lot of great articles about it:\n\n- <https://web.dev/render-blocking-resources/>\n- <https://web.dev/defer-non-critical-css/>\n\n## Always use HTTPS\n\nYour website should only be available over HTTPS connections in production. HTTPS improves the security, privacy, and availability of all sites, and [there is no such thing as non-sensitive web traffic](https://https.cio.gov/everything/). The steps to configure your website to be served exclusively over HTTPS vary widely depending on your architecture and web hosting provider, and thus are beyond the scope of these docs.\n\nSites served over HTTPS should also access all stylesheets, scripts, and other assets over HTTPS connections. Otherwise, you'll be sending users [mixed active content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content), leading to potential vulnerabilities where a site can be compromised by altering a dependency. This can lead to security issues and in-browser warnings displayed to users. Whether you're getting Bootstrap from a CDN or serving it yourself, ensure that you only access it over HTTPS connections.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/options.md",
    "content": "---\nlayout: docs\ntitle: Options\ndescription: Quickly customize Bootstrap with built-in variables to easily toggle global CSS preferences for controlling style and behavior.\ngroup: customize\n---\n\nCustomize Bootstrap with our built-in custom variables file and easily toggle global CSS preferences with new `$enable-*` Sass variables. Override a variable's value and recompile with `npm run test` as needed.\n\nYou can find and customize these variables for key global options in Bootstrap's `scss/_variables.scss` file.\n\n{{< bs-table \"table table-options\" >}}\n| Variable                       | Values                             | Description                                                                            |\n| ------------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------- |\n| `$spacer`                      | `1rem` (default), or any value > 0 | Specifies the default spacer value to programmatically generate our [spacer utilities]({{< docsref \"/utilities/spacing\" >}}). |\n| `$enable-rounded`              | `true` (default) or `false`        | Enables predefined `border-radius` styles on various components. |\n| `$enable-shadows`              | `true` or `false` (default)        | Enables predefined decorative `box-shadow` styles on various components. Does not affect `box-shadow`s used for focus states. |\n| `$enable-gradients`            | `true` or `false` (default)        | Enables predefined gradients via `background-image` styles on various components. |\n| `$enable-transitions`          | `true` (default) or `false`        | Enables predefined `transition`s on various components. |\n| `$enable-reduced-motion`       | `true` (default) or `false`        | Enables the [`prefers-reduced-motion` media query]({{< docsref \"/getting-started/accessibility#reduced-motion\" >}}), which suppresses certain animations/transitions based on the users' browser/operating system preferences. |\n| `$enable-grid-classes`         | `true` (default) or `false`        | Enables the generation of CSS classes for the grid system (e.g. `.row`, `.col-md-1`, etc.). |\n| `$enable-container-classes`    | `true` (default) or `false`        | Enables the generation of CSS classes for layout containers. (New in v5.2.0) |\n| `$enable-caret`                | `true` (default) or `false`        | Enables pseudo element caret on `.dropdown-toggle`. |\n| `$enable-button-pointers`      | `true` (default) or `false`        | Add \"hand\" cursor to non-disabled button elements. |\n| `$enable-rfs`                  | `true` (default) or `false`        | Globally enables [RFS]({{< docsref \"/getting-started/rfs\" >}}). |\n| `$enable-validation-icons`     | `true` (default) or `false`        | Enables `background-image` icons within textual inputs and some custom forms for validation states. |\n| `$enable-negative-margins`     | `true` or `false` (default)        | Enables the generation of [negative margin utilities]({{< docsref \"/utilities/spacing#negative-margin\" >}}). |\n| `$enable-deprecation-messages` | `true` (default) or `false`        | Set to `false` to hide warnings when using any of the deprecated mixins and functions that are planned to be removed in `v6`. |\n| `$enable-important-utilities`  | `true` (default) or `false`        | Enables the `!important` suffix in utility classes. |\n| `$enable-smooth-scroll`        | `true` (default) or `false`        | Applies `scroll-behavior: smooth` globally, except for users asking for reduced motion through [`prefers-reduced-motion` media query]({{< docsref \"/getting-started/accessibility#reduced-motion\" >}}) |\n{{< /bs-table >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/overview.md",
    "content": "---\nlayout: docs\ntitle: Customize\ndescription: Learn how to theme, customize, and extend Bootstrap with Sass, a boatload of global options, an expansive color system, and more.\ngroup: customize\ntoc: false\naliases: \"/docs/5.2/customize/\"\nsections:\n  - title: Sass\n    description: Utilize our source Sass files to take advantage of variables, maps, mixins, and functions.\n  - title: Options\n    description: Customize Bootstrap with built-in variables to easily toggle global CSS preferences.\n  - title: Color\n    description: Learn about and customize the color systems that support the entire toolkit.\n  - title: Components\n    description: Learn how we build nearly all our components responsively and with base and modifier classes.\n  - title: CSS variables\n    description: Use Bootstrap's CSS custom properties for fast and forward-looking design and development.\n  - title: Optimize\n    description: Keep your projects lean, responsive, and maintainable so you can deliver the best experience.\n---\n\n## Overview\n\nThere are multiple ways to customize Bootstrap. Your best path can depend on your project, the complexity of your build tools, the version of Bootstrap you're using, browser support, and more.\n\nOur two preferred methods are:\n\n1. Using Bootstrap [via package manager]({{< docsref \"/getting-started/download#package-managers\" >}}) so you can use and extend our source files.\n2. Using Bootstrap's compiled distribution files or [jsDelivr]({{< docsref \"/getting-started/download#cdn-via-jsdelivr\" >}}) so you can add onto or override Bootstrap's styles.\n\nWhile we cannot go into details here on how to use every package manager, we can give some guidance on [using Bootstrap with your own Sass compiler]({{< docsref \"/customize/sass\" >}}).\n\nFor those who want to use the distribution files, review the [getting started page]({{< docsref \"/getting-started/introduction\" >}}) for how to include those files and an example HTML page. From there, consult the docs for the layout, components, and behaviors you'd like to use.\n\nAs you familiarize yourself with Bootstrap, continue exploring this section for more details on how to utilize our global options, making use of and changing our color system, how we build our components, how to use our growing list of CSS custom properties, and how to optimize your code when building with Bootstrap.\n\n## CSPs and embedded SVGs\n\nSeveral Bootstrap components include embedded SVGs in our CSS to style components consistently and easily across browsers and devices. **For organizations with more strict <abbr title=\"Content Security Policy\">CSP</abbr> configurations**, we've documented all instances of our embedded SVGs (all of which are applied via `background-image`) so you can more thoroughly review your options.\n\n- [Accordion]({{< docsref \"/components/accordion\" >}})\n- [Carousel controls]({{< docsref \"/components/carousel#with-controls\" >}})\n- [Close button]({{< docsref \"/components/close-button\" >}}) (used in alerts and modals)\n- [Form checkboxes and radio buttons]({{< docsref \"/forms/checks-radios\" >}})\n- [Form switches]({{< docsref \"/forms/checks-radios#switches\" >}})\n- [Form validation icons]({{< docsref \"/forms/validation#server-side\" >}})\n- [Navbar toggle buttons]({{< docsref \"/components/navbar#responsive-behaviors\" >}})\n- [Select menus]({{< docsref \"/forms/select\" >}})\n\nBased on [community conversation](https://github.com/twbs/bootstrap/issues/25394), some options for addressing this in your own codebase include [replacing the URLs with locally hosted assets]({{< docsref \"/getting-started/webpack#extracting-svg-files\" >}}), removing the images and using inline images (not possible in all components), and modifying your CSP. Our recommendation is to carefully review your own security policies and decide on the best path forward, if necessary.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/customize/sass.md",
    "content": "---\nlayout: docs\ntitle: Sass\ndescription: Utilize our source Sass files to take advantage of variables, maps, mixins, and functions to help you build faster and customize your project.\ngroup: customize\ntoc: true\n---\n\nUtilize our source Sass files to take advantage of variables, maps, mixins, and more.\n\n## File structure\n\nWhenever possible, avoid modifying Bootstrap's core files. For Sass, that means creating your own stylesheet that imports Bootstrap so you can modify and extend it. Assuming you're using a package manager like npm, you'll have a file structure that looks like this:\n\n```text\nyour-project/\n├── scss\n│   └── custom.scss\n└── node_modules/\n    └── bootstrap\n        ├── js\n        └── scss\n```\n\nIf you've downloaded our source files and aren't using a package manager, you'll want to manually create something similar to that structure, keeping Bootstrap's source files separate from your own.\n\n```text\nyour-project/\n├── scss\n│   └── custom.scss\n└── bootstrap/\n    ├── js\n    └── scss\n```\n\n## Importing\n\nIn your `custom.scss`, you'll import Bootstrap's source Sass files. You have two options: include all of Bootstrap, or pick the parts you need. We encourage the latter, though be aware there are some requirements and dependencies across our components. You also will need to include some JavaScript for our plugins.\n\n```scss\n// Custom.scss\n// Option A: Include all of Bootstrap\n\n// Include any default variable overrides here (though functions won't be available)\n\n@import \"../node_modules/bootstrap/scss/bootstrap\";\n\n// Then add additional custom code here\n```\n\n```scss\n// Custom.scss\n// Option B: Include parts of Bootstrap\n\n// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)\n@import \"../node_modules/bootstrap/scss/functions\";\n\n// 2. Include any default variable overrides here\n\n// 3. Include remainder of required Bootstrap stylesheets\n@import \"../node_modules/bootstrap/scss/variables\";\n\n// 4. Include any default map overrides here\n\n// 5. Include remainder of required parts\n@import \"../node_modules/bootstrap/scss/maps\";\n@import \"../node_modules/bootstrap/scss/mixins\";\n@import \"../node_modules/bootstrap/scss/root\";\n\n// 6. Optionally include any other parts as needed\n@import \"../node_modules/bootstrap/scss/utilities\";\n@import \"../node_modules/bootstrap/scss/reboot\";\n@import \"../node_modules/bootstrap/scss/type\";\n@import \"../node_modules/bootstrap/scss/images\";\n@import \"../node_modules/bootstrap/scss/containers\";\n@import \"../node_modules/bootstrap/scss/grid\";\n@import \"../node_modules/bootstrap/scss/helpers\";\n\n// 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`\n@import \"../node_modules/bootstrap/scss/utilities/api\";\n\n// 8. Add additional custom code here\n```\n\nWith that setup in place, you can begin to modify any of the Sass variables and maps in your `custom.scss`. You can also start to add parts of Bootstrap under the `// Optional` section as needed. We suggest using the full import stack from our `bootstrap.scss` file as your starting point.\n\n## Variable defaults\n\nEvery Sass variable in Bootstrap includes the `!default` flag allowing you to override the variable's default value in your own Sass without modifying Bootstrap's source code. Copy and paste variables as needed, modify their values, and remove the `!default` flag. If a variable has already been assigned, then it won't be re-assigned by the default values in Bootstrap.\n\nYou will find the complete list of Bootstrap's variables in `scss/_variables.scss`. Some variables are set to `null`, these variables don't output the property unless they are overridden in your configuration.\n\nVariable overrides must come after our functions are imported, but before the rest of the imports.\n\nHere's an example that changes the `background-color` and `color` for the `<body>` when importing and compiling Bootstrap via npm:\n\n```scss\n// Required\n@import \"../node_modules/bootstrap/scss/functions\";\n\n// Default variable overrides\n$body-bg: #000;\n$body-color: #111;\n\n// Required\n@import \"../node_modules/bootstrap/scss/variables\";\n@import \"../node_modules/bootstrap/scss/maps\";\n@import \"../node_modules/bootstrap/scss/mixins\";\n@import \"../node_modules/bootstrap/scss/root\";\n\n// Optional Bootstrap components here\n@import \"../node_modules/bootstrap/scss/reboot\";\n@import \"../node_modules/bootstrap/scss/type\";\n// etc\n```\n\nRepeat as necessary for any variable in Bootstrap, including the global options below.\n\n{{< callout info >}}\n{{< partial \"callout-info-npm-starter.md\" >}}\n{{< /callout >}}\n\n## Maps and loops\n\nBootstrap includes a handful of Sass maps, key value pairs that make it easier to generate families of related CSS. We use Sass maps for our colors, grid breakpoints, and more. Just like Sass variables, all Sass maps include the `!default` flag and can be overridden and extended.\n\nSome of our Sass maps are merged into empty ones by default. This is done to allow easy expansion of a given Sass map, but comes at the cost of making _removing_ items from a map slightly more difficult.\n\n### Modify map\n\nAll variables in the `$theme-colors` map are defined as standalone variables. To modify an existing color in our `$theme-colors` map, add the following to your custom Sass file:\n\n```scss\n$primary: #0074d9;\n$danger: #ff4136;\n```\n\nLater on, these variables are set in Bootstrap's `$theme-colors` map:\n\n```scss\n$theme-colors: (\n  \"primary\": $primary,\n  \"danger\": $danger\n);\n```\n\n### Add to map\n\nAdd new colors to `$theme-colors`, or any other map, by creating a new Sass map with your custom values and merging it with the original map. In this case, we'll create a new `$custom-colors` map and merge it with `$theme-colors`.\n\n```scss\n// Create your own map\n$custom-colors: (\n  \"custom-color\": #900\n);\n\n// Merge the maps\n$theme-colors: map-merge($theme-colors, $custom-colors);\n```\n\n### Remove from map\n\nTo remove colors from `$theme-colors`, or any other map, use `map-remove`. Be aware you must insert `$theme-colors` between our requirements just after its definition in `variables` and before its usage in `maps`:\n\n```scss\n// Required\n@import \"../node_modules/bootstrap/scss/functions\";\n@import \"../node_modules/bootstrap/scss/variables\";\n\n$theme-colors: map-remove($theme-colors, \"info\", \"light\", \"dark\");\n\n@import \"../node_modules/bootstrap/scss/maps\";\n@import \"../node_modules/bootstrap/scss/mixins\";\n@import \"../node_modules/bootstrap/scss/root\";\n\n// Optional\n@import \"../node_modules/bootstrap/scss/reboot\";\n@import \"../node_modules/bootstrap/scss/type\";\n// etc\n```\n\n## Required keys\n\nBootstrap assumes the presence of some specific keys within Sass maps as we used and extend these ourselves. As you customize the included maps, you may encounter errors where a specific Sass map's key is being used.\n\nFor example, we use the `primary`, `success`, and `danger` keys from `$theme-colors` for links, buttons, and form states. Replacing the values of these keys should present no issues, but removing them may cause Sass compilation issues. In these instances, you'll need to modify the Sass code that makes use of those values.\n\n## Functions\n\n### Colors\n\nNext to the [Sass maps]({{< docsref \"/customize/color#color-sass-maps\" >}}) we have, theme colors can also be used as standalone variables, like `$primary`.\n\n```scss\n.custom-element {\n  color: $gray-100;\n  background-color: $dark;\n}\n```\n\nYou can lighten or darken colors with Bootstrap's `tint-color()` and `shade-color()` functions. These functions will mix colors with black or white, unlike Sass' native `lighten()` and `darken()` functions which will change the lightness by a fixed amount, which often doesn't lead to the desired effect.\n\n{{< scss-docs name=\"color-functions\" file=\"scss/_functions.scss\" >}}\n\nIn practice, you'd call the function and pass in the color and weight parameters.\n\n```scss\n.custom-element {\n  color: tint-color($primary, 10%);\n}\n\n.custom-element-2 {\n  color: shade-color($danger, 30%);\n}\n```\n\n### Color contrast\n\nIn order to meet the [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/TR/WCAG/) contrast requirements, authors **must** provide a minimum [text color contrast of 4.5:1](https://www.w3.org/TR/WCAG/#contrast-minimum) and a minimum [non-text color contrast of 3:1](https://www.w3.org/TR/WCAG/#non-text-contrast), with very few exceptions.\n\nTo help with this, we included the `color-contrast` function in Bootstrap. It uses the [WCAG contrast ratio algorithm](https://www.w3.org/TR/WCAG/#dfn-contrast-ratio) for calculating contrast thresholds based on [relative luminance](https://www.w3.org/TR/WCAG/#dfn-relative-luminance) in an `sRGB` color space to automatically return a light (`#fff`), dark (`#212529`) or black (`#000`) contrast color based on the specified base color. This function is especially useful for mixins or loops where you're generating multiple classes.\n\nFor example, to generate color swatches from our `$theme-colors` map:\n\n```scss\n@each $color, $value in $theme-colors {\n  .swatch-#{$color} {\n    color: color-contrast($value);\n  }\n}\n```\n\nIt can also be used for one-off contrast needs:\n\n```scss\n.custom-element {\n  color: color-contrast(#000); // returns `color: #fff`\n}\n```\n\nYou can also specify a base color with our color map functions:\n\n```scss\n.custom-element {\n  color: color-contrast($dark); // returns `color: #fff`\n}\n```\n\n### Escape SVG\n\nWe use the `escape-svg` function to escape the `<`, `>` and `#` characters for SVG background images. When using the `escape-svg` function, data URIs must be quoted.\n\n### Add and Subtract functions\n\nWe use the `add` and `subtract` functions to wrap the CSS `calc` function. The primary purpose of these functions is to avoid errors when a \"unitless\" `0` value is passed into a `calc` expression. Expressions like `calc(10px - 0)` will return an error in all browsers, despite being mathematically correct.\n\nExample where the calc is valid:\n\n```scss\n$border-radius: .25rem;\n$border-width: 1px;\n\n.element {\n  // Output calc(.25rem - 1px) is valid\n  border-radius: calc($border-radius - $border-width);\n}\n\n.element {\n  // Output the same calc(.25rem - 1px) as above\n  border-radius: subtract($border-radius, $border-width);\n}\n```\n\nExample where the calc is invalid:\n\n```scss\n$border-radius: .25rem;\n$border-width: 0;\n\n.element {\n  // Output calc(.25rem - 0) is invalid\n  border-radius: calc($border-radius - $border-width);\n}\n\n.element {\n  // Output .25rem\n  border-radius: subtract($border-radius, $border-width);\n}\n```\n\n## Mixins\n\nOur `scss/mixins/` directory has a ton of mixins that power parts of Bootstrap and can also be used across your own project.\n\n### Color schemes\n\nA shorthand mixin for the `prefers-color-scheme` media query is available with support for `light`, `dark`, and custom color schemes.\n\n{{< scss-docs name=\"mixin-color-scheme\" file=\"scss/mixins/_color-scheme.scss\" >}}\n\n```scss\n.custom-element {\n  @include color-scheme(dark) {\n    // Insert dark mode styles here\n  }\n\n  @include color-scheme(custom-named-scheme) {\n    // Insert custom color scheme styles here\n  }\n}\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/.stylelintrc",
    "content": "{\n  \"extends\": [\n    \"stylelint-config-twbs-bootstrap\"\n  ],\n  \"rules\": {\n    \"at-rule-no-vendor-prefix\": null,\n    \"comment-empty-line-before\": null,\n    \"media-feature-name-no-vendor-prefix\": null,\n    \"property-disallowed-list\": null,\n    \"property-no-vendor-prefix\": null,\n    \"selector-no-qualifying-type\": null,\n    \"selector-no-vendor-prefix\": null,\n    \"value-no-vendor-prefix\": null\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/_index.md",
    "content": "---\nlayout: single\ntitle: Examples\ndescription: Quickly get a project started with any of our examples ranging from using parts of the framework to custom components and layouts.\naliases: \"/examples/\"\n---\n\n{{< list-examples.inline >}}\n{{ range $entry := $.Site.Data.examples -}}\n<div class=\"row g-lg-5 mb-5\">\n  <div class=\"bd-content col-lg-3\">\n    <h2 id=\"{{ $entry.category | urlize }}\">{{ $entry.category }}</h2>\n    <p>{{ $entry.description }}</p>\n    {{ if eq $entry.category \"RTL\" -}}\n      <div class=\"bd-callout bd-callout-warning small\">\n        <p>\n          <strong>RTL is still experimental</strong> and will evolve with feedback. Spotted something or have an improvement to suggest?\n        </p>\n        <p><a href=\"{{ $.Site.Params.repo }}/issues/new/choose\">Please open an issue.</a></p>\n      </div>\n    {{ end -}}\n  </div>\n\n  <div class=\"col-lg-9\">\n    {{ range $i, $example := $entry.examples -}}\n      {{- $len := len $entry.examples -}}\n      {{ if (eq $i 0) }}<div class=\"row\">{{ end }}\n        <div class=\"col-sm-6 col-md-4 mb-3\">\n          <a class=\"d-block\" href=\"/docs/{{ $.Site.Params.docs_version }}/examples/{{ $example.name | urlize }}/\"{{ if in $example.name \"RTL\" }} hreflang=\"ar\"{{ end }}>\n            <img class=\"img-thumbnail mb-3\" srcset=\"/docs/{{ $.Site.Params.docs_version }}/assets/img/examples/{{ $example.name | urlize }}.png,\n                                                    /docs/{{ $.Site.Params.docs_version }}/assets/img/examples/{{ $example.name | urlize }}@2x.png 2x\"\n                                            src=\"/docs/{{ $.Site.Params.docs_version }}/assets/img/examples/{{ $example.name | urlize }}.png\"\n                                            alt=\"\"\n                                            width=\"480\" height=\"300\"\n                                            loading=\"lazy\">\n            <h3 class=\"h5 mb-1\">{{ $example.name }}</h3>\n          </a>\n          <p class=\"text-muted\">{{ $example.description }}</p>\n        </div>\n      {{ if (eq (add $i 1) $len) }}</div>{{ end }}\n    {{ end -}}\n  </div>\n</div>\n{{ end -}}\n{{< /list-examples.inline >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/album/index.html",
    "content": "---\nlayout: examples\ntitle: Album example\n---\n\n<header>\n  <div class=\"collapse bg-dark\" id=\"navbarHeader\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"col-sm-8 col-md-7 py-4\">\n          <h4 class=\"text-white\">About</h4>\n          <p class=\"text-muted\">Add some information about the album below, the author, or any other background context. Make it a few sentences long so folks can pick up some informative tidbits. Then, link them off to some social networking sites or contact information.</p>\n        </div>\n        <div class=\"col-sm-4 offset-md-1 py-4\">\n          <h4 class=\"text-white\">Contact</h4>\n          <ul class=\"list-unstyled\">\n            <li><a href=\"#\" class=\"text-white\">Follow on Twitter</a></li>\n            <li><a href=\"#\" class=\"text-white\">Like on Facebook</a></li>\n            <li><a href=\"#\" class=\"text-white\">Email me</a></li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"navbar navbar-dark bg-dark shadow-sm\">\n    <div class=\"container\">\n      <a href=\"#\" class=\"navbar-brand d-flex align-items-center\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" aria-hidden=\"true\" class=\"me-2\" viewBox=\"0 0 24 24\"><path d=\"M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"13\" r=\"4\"/></svg>\n        <strong>Album</strong>\n      </a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarHeader\" aria-controls=\"navbarHeader\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n    </div>\n  </div>\n</header>\n\n<main>\n\n  <section class=\"py-5 text-center container\">\n    <div class=\"row py-lg-5\">\n      <div class=\"col-lg-6 col-md-8 mx-auto\">\n        <h1 class=\"fw-light\">Album example</h1>\n        <p class=\"lead text-muted\">Something short and leading about the collection below—its contents, the creator, etc. Make it short and sweet, but not too short so folks don’t simply skip over it entirely.</p>\n        <p>\n          <a href=\"#\" class=\"btn btn-primary my-2\">Main call to action</a>\n          <a href=\"#\" class=\"btn btn-secondary my-2\">Secondary action</a>\n        </p>\n      </div>\n    </div>\n  </section>\n\n  <div class=\"album py-5 bg-light\">\n    <div class=\"container\">\n\n      <div class=\"row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3\">\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"Thumbnail\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">View</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Edit</button>\n                </div>\n                <small class=\"text-muted\">9 mins</small>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n</main>\n\n<footer class=\"text-muted py-5\">\n  <div class=\"container\">\n    <p class=\"float-end mb-1\">\n      <a href=\"#\">Back to top</a>\n    </p>\n    <p class=\"mb-1\">Album example is &copy; Bootstrap, but please download and customize it for yourself!</p>\n    <p class=\"mb-0\">New to Bootstrap? <a href=\"/\">Visit the homepage</a> or read our <a href=\"{{< docsref \"/getting-started/introduction\" >}}\">getting started guide</a>.</p>\n  </div>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/album-rtl/index.html",
    "content": "---\nlayout: examples\ntitle: مثال الألبوم\ndirection: rtl\n---\n\n<header>\n  <div class=\"collapse bg-dark\" id=\"navbarHeader\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"col-sm-8 col-md-7 py-4\">\n          <h4 class=\"text-white\">حول</h4>\n          <p class=\"text-muted\">أضف بعض المعلومات حول الألبوم، المؤلف، أو أي سياق خلفية آخر. اجعلها بضع جمل حتى يتمكن الزوار من التقاط بعض التلميحات المفيدة. ثم اربطها ببعض مواقع التواصل الاجتماعي أو معلومات الاتصال.</p>\n        </div>\n        <div class=\"col-sm-4 offset-md-1 py-4\">\n          <h4 class=\"text-white\">تواصل معي</h4>\n          <ul class=\"list-unstyled\">\n            <li><a href=\"#\" class=\"text-white\">تابعني على تويتر</a></li>\n            <li><a href=\"#\" class=\"text-white\">شاركني الإعجاب في فيسبوك</a></li>\n            <li><a href=\"#\" class=\"text-white\">راسلني على البريد الإلكتروني</a></li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"navbar navbar-dark bg-dark shadow-sm\">\n    <div class=\"container\">\n      <a href=\"#\" class=\"navbar-brand d-flex align-items-center\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" aria-hidden=\"true\" class=\"me-2\" viewBox=\"0 0 24 24\"><path d=\"M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"13\" r=\"4\"/></svg>\n        <strong>الألبوم</strong>\n      </a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarHeader\" aria-controls=\"navbarHeader\" aria-expanded=\"false\" aria-label=\"تبديل التنقل\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n    </div>\n  </div>\n</header>\n\n<main>\n\n  <section class=\"py-5 text-center container\">\n    <div class=\"row py-lg-5\">\n      <div class=\"col-lg-6 col-md-8 mx-auto\">\n        <h1 class=\"fw-light\">مثال الألبوم</h1>\n        <p class=\"lead text-muted\">وصف قصير حول الألبوم أدناه (محتوياته ، ومنشؤه ، وما إلى ذلك). اجعله قصير ولطيف، ولكن ليست قصير جدًا حتى لا يتخطى الناس هذا الألبوم تمامًا.</p>\n        <p>\n          <a href=\"#\" class=\"btn btn-primary my-2\">الدعوة الرئيسية للعمل</a>\n          <a href=\"#\" class=\"btn btn-secondary my-2\">عمل ثانوي</a>\n        </p>\n      </div>\n    </div>\n  </section>\n\n  <div class=\"album py-5 bg-light\">\n    <div class=\"container\">\n\n      <div class=\"row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3\">\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"col\">\n          <div class=\"card shadow-sm\">\n            {{< placeholder width=\"100%\" height=\"225\" background=\"#55595c\" color=\"#eceeef\" class=\"card-img-top\" text=\"صورة مصغرة\" >}}\n            <div class=\"card-body\">\n              <p class=\"card-text\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n              <div class=\"d-flex justify-content-between align-items-center\">\n                <div class=\"btn-group\">\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">عرض</button>\n                  <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تعديل</button>\n                </div>\n                <small class=\"text-muted\">9 دقائق</small>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n</main>\n\n<footer class=\"text-muted py-5\">\n  <div class=\"container\">\n    <p class=\"float-end mb-1\">\n      <a href=\"#\">عد إلى الأعلى</a>\n    </p>\n    <p class=\"mb-1\">مثال الألبوم هو © Bootstrap ، ولكن يرجى تنزيله وتخصيصه لنفسك!</p>\n    <p class=\"mb-0\">جديد على Bootstrap؟ <a href=\"/\"> تفضل بزيارة الصفحة الرئيسية </a> أو اقرأ <a href=\"{{< docsref \"/getting-started/introduction\">}} \"> دليل البدء </a>.</p>\n  </div>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/blog/blog.css",
    "content": "/* stylelint-disable selector-list-comma-newline-after */\n\n.blog-header {\n  border-bottom: 1px solid #e5e5e5;\n}\n\n.blog-header-logo {\n  font-family: \"Playfair Display\", Georgia, \"Times New Roman\", serif/*rtl:Amiri, Georgia, \"Times New Roman\", serif*/;\n  font-size: 2.25rem;\n}\n\n.blog-header-logo:hover {\n  text-decoration: none;\n}\n\nh1, h2, h3, h4, h5, h6 {\n  font-family: \"Playfair Display\", Georgia, \"Times New Roman\", serif/*rtl:Amiri, Georgia, \"Times New Roman\", serif*/;\n}\n\n.display-4 {\n  font-size: 2.5rem;\n}\n@media (min-width: 768px) {\n  .display-4 {\n    font-size: 3rem;\n  }\n}\n\n.flex-auto {\n  flex: 0 0 auto;\n}\n\n.h-250 { height: 250px; }\n@media (min-width: 768px) {\n  .h-md-250 { height: 250px; }\n}\n\n/* Pagination */\n.blog-pagination {\n  margin-bottom: 4rem;\n}\n\n/*\n * Blog posts\n */\n.blog-post {\n  margin-bottom: 4rem;\n}\n.blog-post-title {\n  font-size: 2.5rem;\n}\n.blog-post-meta {\n  margin-bottom: 1.25rem;\n  color: #727272;\n}\n\n/*\n * Footer\n */\n.blog-footer {\n  padding: 2.5rem 0;\n  color: #727272;\n  text-align: center;\n  background-color: #f9f9f9;\n  border-top: .05rem solid #e5e5e5;\n}\n.blog-footer p:last-child {\n  margin-bottom: 0;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/blog/blog.rtl.css",
    "content": "/* stylelint-disable selector-list-comma-newline-after */\n\n.blog-header {\n  border-bottom: 1px solid #e5e5e5;\n}\n\n.blog-header-logo {\n  font-family: Amiri, Georgia, \"Times New Roman\", serif;\n  font-size: 2.25rem;\n}\n\n.blog-header-logo:hover {\n  text-decoration: none;\n}\n\nh1, h2, h3, h4, h5, h6 {\n  font-family: Amiri, Georgia, \"Times New Roman\", serif;\n}\n\n.display-4 {\n  font-size: 2.5rem;\n}\n@media (min-width: 768px) {\n  .display-4 {\n    font-size: 3rem;\n  }\n}\n\n.flex-auto {\n  flex: 0 0 auto;\n}\n\n.h-250 { height: 250px; }\n@media (min-width: 768px) {\n  .h-md-250 { height: 250px; }\n}\n\n/* Pagination */\n.blog-pagination {\n  margin-bottom: 4rem;\n}\n\n/*\n * Blog posts\n */\n.blog-post {\n  margin-bottom: 4rem;\n}\n.blog-post-title {\n  font-size: 2.5rem;\n}\n.blog-post-meta {\n  margin-bottom: 1.25rem;\n  color: #727272;\n}\n\n/*\n * Footer\n */\n.blog-footer {\n  padding: 2.5rem 0;\n  color: #727272;\n  text-align: center;\n  background-color: #f9f9f9;\n  border-top: .05rem solid #e5e5e5;\n}\n.blog-footer p:last-child {\n  margin-bottom: 0;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/blog/index.html",
    "content": "---\nlayout: examples\ntitle: Blog Template\nextra_css:\n  - \"https://fonts.googleapis.com/css?family=Playfair+Display:700,900&display=swap\"\n  - \"blog.css\"\ninclude_js: false\n---\n\n<div class=\"container\">\n  <header class=\"blog-header lh-1 py-3\">\n    <div class=\"row flex-nowrap justify-content-between align-items-center\">\n      <div class=\"col-4 pt-1\">\n        <a class=\"link-secondary\" href=\"#\">Subscribe</a>\n      </div>\n      <div class=\"col-4 text-center\">\n        <a class=\"blog-header-logo text-dark\" href=\"#\">Large</a>\n      </div>\n      <div class=\"col-4 d-flex justify-content-end align-items-center\">\n        <a class=\"link-secondary\" href=\"#\" aria-label=\"Search\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"mx-3\" role=\"img\" viewBox=\"0 0 24 24\"><title>Search</title><circle cx=\"10.5\" cy=\"10.5\" r=\"7.5\"/><path d=\"M21 21l-5.2-5.2\"/></svg>\n        </a>\n        <a class=\"btn btn-sm btn-outline-secondary\" href=\"#\">Sign up</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"nav-scroller py-1 mb-2\">\n    <nav class=\"nav d-flex justify-content-between\">\n      <a class=\"p-2 link-secondary\" href=\"#\">World</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">U.S.</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Technology</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Design</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Culture</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Business</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Politics</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Opinion</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Science</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Health</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Style</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">Travel</a>\n    </nav>\n  </div>\n</div>\n\n<main class=\"container\">\n  <div class=\"p-4 p-md-5 mb-4 rounded text-bg-dark\">\n    <div class=\"col-md-6 px-0\">\n      <h1 class=\"display-4 fst-italic\">Title of a longer featured blog post</h1>\n      <p class=\"lead my-3\">Multiple lines of text that form the lede, informing new readers quickly and efficiently about what’s most interesting in this post’s contents.</p>\n      <p class=\"lead mb-0\"><a href=\"#\" class=\"text-white fw-bold\">Continue reading...</a></p>\n    </div>\n  </div>\n\n  <div class=\"row mb-2\">\n    <div class=\"col-md-6\">\n      <div class=\"row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative\">\n        <div class=\"col p-4 d-flex flex-column position-static\">\n          <strong class=\"d-inline-block mb-2 text-primary\">World</strong>\n          <h3 class=\"mb-0\">Featured post</h3>\n          <div class=\"mb-1 text-muted\">Nov 12</div>\n          <p class=\"card-text mb-auto\">This is a wider card with supporting text below as a natural lead-in to additional content.</p>\n          <a href=\"#\" class=\"stretched-link\">Continue reading</a>\n        </div>\n        <div class=\"col-auto d-none d-lg-block\">\n          {{< placeholder width=\"200\" height=\"250\" background=\"#55595c\" color=\"#eceeef\" text=\"Thumbnail\" >}}\n        </div>\n      </div>\n    </div>\n    <div class=\"col-md-6\">\n      <div class=\"row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative\">\n        <div class=\"col p-4 d-flex flex-column position-static\">\n          <strong class=\"d-inline-block mb-2 text-success\">Design</strong>\n          <h3 class=\"mb-0\">Post title</h3>\n          <div class=\"mb-1 text-muted\">Nov 11</div>\n          <p class=\"mb-auto\">This is a wider card with supporting text below as a natural lead-in to additional content.</p>\n          <a href=\"#\" class=\"stretched-link\">Continue reading</a>\n        </div>\n        <div class=\"col-auto d-none d-lg-block\">\n          {{< placeholder width=\"200\" height=\"250\" background=\"#55595c\" color=\"#eceeef\" text=\"Thumbnail\" >}}\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"row g-5\">\n    <div class=\"col-md-8\">\n      <h3 class=\"pb-4 mb-4 fst-italic border-bottom\">\n        From the Firehose\n      </h3>\n\n      <article class=\"blog-post\">\n        <h2 class=\"blog-post-title mb-1\">Sample blog post</h2>\n        <p class=\"blog-post-meta\">January 1, 2021 by <a href=\"#\">Mark</a></p>\n\n        <p>This blog post shows a few different types of content that’s supported and styled with Bootstrap. Basic typography, lists, tables, images, code, and more are all supported as expected.</p>\n        <hr>\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <h2>Blockquotes</h2>\n        <p>This is an example blockquote in action:</p>\n        <blockquote class=\"blockquote\">\n          <p>Quoted text goes here.</p>\n        </blockquote>\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <h3>Example lists</h3>\n        <p>This is some additional paragraph placeholder content. It's a slightly shorter version of the other highly repetitive body text used throughout. This is an example unordered list:</p>\n        <ul>\n          <li>First list item</li>\n          <li>Second list item with a longer description</li>\n          <li>Third list item to close it out</li>\n        </ul>\n        <p>And this is an ordered list:</p>\n        <ol>\n          <li>First list item</li>\n          <li>Second list item with a longer description</li>\n          <li>Third list item to close it out</li>\n        </ol>\n        <p>And this is a definition list:</p>\n        <dl>\n          <dt>HyperText Markup Language (HTML)</dt>\n          <dd>The language used to describe and define the content of a Web page</dd>\n          <dt>Cascading Style Sheets (CSS)</dt>\n          <dd>Used to describe the appearance of Web content</dd>\n          <dt>JavaScript (JS)</dt>\n          <dd>The programming language used to build advanced Web sites and applications</dd>\n        </dl>\n        <h2>Inline HTML elements</h2>\n        <p>HTML defines a long list of available inline tags, a complete list of which can be found on the <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element\">Mozilla Developer Network</a>.</p>\n        <ul>\n          <li><strong>To bold text</strong>, use <code class=\"language-plaintext highlighter-rouge\">&lt;strong&gt;</code>.</li>\n          <li><em>To italicize text</em>, use <code class=\"language-plaintext highlighter-rouge\">&lt;em&gt;</code>.</li>\n          <li>Abbreviations, like <abbr title=\"HyperText Markup Language\">HTML</abbr> should use <code class=\"language-plaintext highlighter-rouge\">&lt;abbr&gt;</code>, with an optional <code class=\"language-plaintext highlighter-rouge\">title</code> attribute for the full phrase.</li>\n          <li>Citations, like <cite>— Mark Otto</cite>, should use <code class=\"language-plaintext highlighter-rouge\">&lt;cite&gt;</code>.</li>\n          <li><del>Deleted</del> text should use <code class=\"language-plaintext highlighter-rouge\">&lt;del&gt;</code> and <ins>inserted</ins> text should use <code class=\"language-plaintext highlighter-rouge\">&lt;ins&gt;</code>.</li>\n          <li>Superscript <sup>text</sup> uses <code class=\"language-plaintext highlighter-rouge\">&lt;sup&gt;</code> and subscript <sub>text</sub> uses <code class=\"language-plaintext highlighter-rouge\">&lt;sub&gt;</code>.</li>\n        </ul>\n        <p>Most of these elements are styled by browsers with few modifications on our part.</p>\n        <h2>Heading</h2>\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <h3>Sub-heading</h3>\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <pre><code>Example code block</code></pre>\n        <p>This is some additional paragraph placeholder content. It's a slightly shorter version of the other highly repetitive body text used throughout.</p>\n      </article>\n\n      <article class=\"blog-post\">\n        <h2 class=\"blog-post-title mb-1\">Another blog post</h2>\n        <p class=\"blog-post-meta\">December 23, 2020 by <a href=\"#\">Jacob</a></p>\n\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <blockquote>\n          <p>Longer quote goes here, maybe with some <strong>emphasized text</strong> in the middle of it.</p>\n        </blockquote>\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <h3>Example table</h3>\n        <p>And don't forget about tables in these posts:</p>\n        <table class=\"table\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Upvotes</th>\n              <th>Downvotes</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td>Alice</td>\n              <td>10</td>\n              <td>11</td>\n            </tr>\n            <tr>\n              <td>Bob</td>\n              <td>4</td>\n              <td>3</td>\n            </tr>\n            <tr>\n              <td>Charlie</td>\n              <td>7</td>\n              <td>9</td>\n            </tr>\n          </tbody>\n          <tfoot>\n            <tr>\n              <td>Totals</td>\n              <td>21</td>\n              <td>23</td>\n            </tr>\n          </tfoot>\n        </table>\n\n        <p>This is some additional paragraph placeholder content. It's a slightly shorter version of the other highly repetitive body text used throughout.</p>\n      </article>\n\n      <article class=\"blog-post\">\n        <h2 class=\"blog-post-title mb-1\">New feature</h2>\n        <p class=\"blog-post-meta\">December 14, 2020 by <a href=\"#\">Chris</a></p>\n\n        <p>This is some additional paragraph placeholder content. It has been written to fill the available space and show how a longer snippet of text affects the surrounding content. We'll repeat it often to keep the demonstration flowing, so be on the lookout for this exact same string of text.</p>\n        <ul>\n          <li>First list item</li>\n          <li>Second list item with a longer description</li>\n          <li>Third list item to close it out</li>\n        </ul>\n        <p>This is some additional paragraph placeholder content. It's a slightly shorter version of the other highly repetitive body text used throughout.</p>\n      </article>\n\n      <nav class=\"blog-pagination\" aria-label=\"Pagination\">\n        <a class=\"btn btn-outline-primary rounded-pill\" href=\"#\">Older</a>\n        <a class=\"btn btn-outline-secondary rounded-pill disabled\">Newer</a>\n      </nav>\n\n    </div>\n\n    <div class=\"col-md-4\">\n      <div class=\"position-sticky\" style=\"top: 2rem;\">\n        <div class=\"p-4 mb-3 bg-light rounded\">\n          <h4 class=\"fst-italic\">About</h4>\n          <p class=\"mb-0\">Customize this section to tell your visitors a little bit about your publication, writers, content, or something else entirely. Totally up to you.</p>\n        </div>\n\n        <div class=\"p-4\">\n          <h4 class=\"fst-italic\">Archives</h4>\n          <ol class=\"list-unstyled mb-0\">\n            <li><a href=\"#\">March 2021</a></li>\n            <li><a href=\"#\">February 2021</a></li>\n            <li><a href=\"#\">January 2021</a></li>\n            <li><a href=\"#\">December 2020</a></li>\n            <li><a href=\"#\">November 2020</a></li>\n            <li><a href=\"#\">October 2020</a></li>\n            <li><a href=\"#\">September 2020</a></li>\n            <li><a href=\"#\">August 2020</a></li>\n            <li><a href=\"#\">July 2020</a></li>\n            <li><a href=\"#\">June 2020</a></li>\n            <li><a href=\"#\">May 2020</a></li>\n            <li><a href=\"#\">April 2020</a></li>\n          </ol>\n        </div>\n\n        <div class=\"p-4\">\n          <h4 class=\"fst-italic\">Elsewhere</h4>\n          <ol class=\"list-unstyled\">\n            <li><a href=\"#\">GitHub</a></li>\n            <li><a href=\"#\">Twitter</a></li>\n            <li><a href=\"#\">Facebook</a></li>\n          </ol>\n        </div>\n      </div>\n    </div>\n  </div>\n\n</main>\n\n<footer class=\"blog-footer\">\n  <p>Blog template built for <a href=\"https://getbootstrap.com/\">Bootstrap</a> by <a href=\"https://twitter.com/mdo\">@mdo</a>.</p>\n  <p>\n    <a href=\"#\">Back to top</a>\n  </p>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/blog-rtl/index.html",
    "content": "---\nlayout: examples\ntitle: قالب المدونة\ndirection: rtl\nextra_css:\n  - \"https://fonts.googleapis.com/css?family=Amiri:wght@400;700&display=swap\"\n  - \"../blog/blog.rtl.css\"\ninclude_js: false\n---\n\n<div class=\"container\">\n  <header class=\"blog-header lh-1 py-3\">\n    <div class=\"row flex-nowrap justify-content-between align-items-center\">\n      <div class=\"col-4 pt-1\">\n        <a class=\"link-secondary\" href=\"#\">الإشتراك في النشرة البريدية</a>\n      </div>\n      <div class=\"col-4 text-center\">\n        <a class=\"blog-header-logo text-dark\" href=\"#\">كبير</a>\n      </div>\n      <div class=\"col-4 d-flex justify-content-end align-items-center\">\n        <a class=\"link-secondary\" href=\"#\" aria-label=\"بحث\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"mx-3\" role=\"img\" viewBox=\"0 0 24 24\"><title>بحث</title><circle cx=\"10.5\" cy=\"10.5\" r=\"7.5\"/><path d=\"M21 21l-5.2-5.2\"/></svg>\n        </a>\n        <a class=\"btn btn-sm btn-outline-secondary\" href=\"#\">إنشاء حساب</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"nav-scroller py-1 mb-2\">\n    <nav class=\"nav d-flex justify-content-between\">\n      <a class=\"p-2 link-secondary\" href=\"#\">العالم</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">الولايات المتحدة</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">التقنية</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">التصميم</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">الحضارة</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">المال والأعمال</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">السياسة</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">الرأي العام</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">العلوم</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">الصحة</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">الموضة</a>\n      <a class=\"p-2 link-secondary\" href=\"#\">السفر</a>\n    </nav>\n  </div>\n</div>\n\n<main class=\"container\">\n  <div class=\"p-4 p-md-5 mb-4 rounded text-bg-dark\">\n    <div class=\"col-md-6 px-0\">\n      <h1 class=\"display-4 fst-italic\">عنوان تدوينة مميزة أطول</h1>\n      <p class=\"lead my-3\">عدة أسطر نصية متعددة تعبر عن التدوية، وذلك لإعلام القراء الجدد بسرعة وكفاءة حول أكثر الأشياء إثارة للاهتمام في محتويات هذه التدوينة.</p>\n      <p class=\"lead mb-0\"><a href=\"#\" class=\"text-white fw-bold\">أكمل القراءة...</a></p>\n    </div>\n  </div>\n\n  <div class=\"row mb-2\">\n    <div class=\"col-md-6\">\n      <div class=\"row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative\">\n        <div class=\"col p-4 d-flex flex-column position-static\">\n          <strong class=\"d-inline-block mb-2 text-primary\">العالم</strong>\n          <h3 class=\"mb-0\">مشاركة مميزة</h3>\n          <div class=\"mb-1 text-muted\">نوفمبر 12</div>\n          <p class=\"card-text mb-auto\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي.</p>\n          <a href=\"#\" class=\"stretched-link\">أكمل القراءة</a>\n        </div>\n        <div class=\"col-auto d-none d-lg-block\">\n          {{< placeholder width=\"200\" height=\"250\" background=\"#55595c\" color=\"#eceeef\" text=\"صورة مصغرة\" >}}\n        </div>\n      </div>\n    </div>\n    <div class=\"col-md-6\">\n      <div class=\"row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative\">\n        <div class=\"col p-4 d-flex flex-column position-static\">\n          <strong class=\"d-inline-block mb-2 text-success\">التصميم</strong>\n          <h3 class=\"mb-0\">عنوان الوظيفة</h3>\n          <div class=\"mb-1 text-muted\">نوفمبر 11</div>\n          <p class=\"mb-auto\">هذه بطاقة أوسع مع نص داعم أدناه كمقدمة طبيعية لمحتوى إضافي.</p>\n          <a href=\"#\" class=\"stretched-link\">أكمل القراءة</a>\n        </div>\n        <div class=\"col-auto d-none d-lg-block\">\n          {{< placeholder width=\"200\" height=\"250\" background=\"#55595c\" color=\"#eceeef\" text=\"صورة مصغرة\" >}}\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"row\">\n    <div class=\"col-md-8\">\n      <h3 class=\"pb-4 mb-4 fst-italic border-bottom\">\n        من Firehose\n      </h3>\n\n      <article class=\"blog-post\">\n        <h2 class=\"blog-post-title mb-1\">مثال على تدوينة</h2>\n        <p class=\"blog-post-meta\">1 يناير 2021 بواسطة <a href=\"#\"> Mark </a></p>\n\n        <p>تعرض مشاركة المدونة هذه بضعة أنواع مختلفة من المحتوى الذي يتم دعمه وتصميمه باستخدام Bootstrap. النصوص الأساسية، الصور، والأكواد مدعومة بشكل كامل.</p>\n        <hr>\n        <p>يشكِّل تأمين الغذاء في المستقبل قضية تؤرِّق حكومات العالَم والعلماء على حدٍّ سواء. فخلال القرن العشرين ازداد عدد سكان الأرض أربعة أضعاف، وتشير التقديرات إلى أن العدد سوف يصل إلى عشرة مليارات إنسان بحلول عام 2050م. وسوف تمثل هذه الزيادة الهائلة تحدياً كبيراً وضغطاً متصاعداً على قدرة الإنتاج الزراعي. الأمر الذي كان ولا بد من أن يدفع إلى تطوير تقنيات مبتكرة في تصنيع الغذاء غير الزراعة، منها تقنية مستقبلية تقوم على تصنيع الغذاء من الهواء.</p>\n        <blockquote>\n          <p>تشغل الزراعة مساحات كبيرة من اليابسة، وتستهلك كميات هائلة من المياه، كما أن إنتاج الغذاء بواسطة الزراعة يسهم بنسبة عالية من انبعاثات غازات الاحتباس الحراري العالمية</p>\n        </blockquote>\n        <p>تشغل الزراعة مساحات كبيرة من اليابسة، وتستهلك كميات هائلة من المياه. كما أن إنتاج الغذاء بواسطة الزراعة يسهم بنسبة عالية من انبعاثات غازات الاحتباس الحراري العالمية، وللمقارنة فإن هذه النسبة من الانبعاثات هي أكبر مما ينتجه قطاع النقل بكل ما فيه من سيارات وشاحنات وطائرات وقطارات.</p>\n        <h2>عنوان</h2>\n        <p>تحصل النباتات على غذائها بواسطة عملية تسمى البناء الضوئي، حيث تقوم النباتات بتحويل ضوء الشمس والماء وثاني أكسيد الكربون الموجود في الغلاف الجوي إلى غذاء وتطلق الأكسجين كمنتج ثانوي لهذا التفاعل الكيميائي. وتحدث هذه العملية في \"البلاستيدات الخضراء\". فالنباتات تستفيد من طاقة ضوء الشمس في تقسيم الماء إلى هيدروجين وأكسجين، وتحدث تفاعلات كيميائية أخرى ينتج عنها سكر الجلكوز الذي تستخدمه كمصدر للغذاء وينطلق الأكسجين من النباتات إلى الغلاف الجوي. وهذا يعني أن النباتات تحوِّل ثاني أكسيد الكربون إلى غذاء من خلال تفاعلات كيميائية معقَّدة. ويُعد البناء الضوئي من أهم التفاعلات الكيميائية على كوكب الأرض، فقد ساعد في الماضي على تطوُّر كوكبنا وظهور الحياة عليه. فالنباتات تستخدم ثاني أكسيد الكربون لصنع غذائها، وتطلق الأكسجين لتساعد الكائنات الأخرى على التنفس!</p>\n        <h3>عنوان فرعي</h3>\n        <p>ألهمت هذه العملية علماء وكالة الفضاء الأمريكية (ناسا) خلال الستينيات من القرن الماضي، لبحث فكرة إطعام روَّاد الفضاء في مهمات الفضاء الطويلة مثل السفر إلى المريخ. وكانت واحدة من الأفكار الواعدة تصنيع الغذاء عن طريق ثاني أكسيد الكربون الذي ينتجه روَّاد الفضاء، لكن ليس بواسطة النباتات بل عن طريق ميكروبات صغيرة وحيدة الخلية قادرة على حصد ثاني أكسيد الكربون لإنتاج كميات وفيرة من البروتين المغذي على شكل مسحوق عديم النكهة، كما يمكن استخدام المادة في صنع الأطعمة المألوفة لدينا.</p>\n        <pre><code>Example code block</code></pre>\n        <p>وخلافاً لما هو الحال في عالم النبات، فإن هذه الميكروبات لا تستخدم الضوء كما يحدث في عملية البناء الضوئي التي تستخدمها النباتات للحصول على الغذاء، أي لأنها قادرة على النمو في الظلام. تسمى هذه البكتريا \"هيدروجينوتروف\" (Hydrogenotrophs)، وهي تستخدم الهيدروجين كوقود لإنتاج الغذاء من ثاني أكسيد الكربون. فعندما يُنتج روَّاد الفضاء ثاني أكسيد الكربون، تلتقطه الميكروبات، ويتحوَّل مع مدخلات أخرى إلى غذاء غني بالكربون. وبهذه الطريقة سوف نحصل على دورة كربون مغلقة الحلقة.</p>\n        <h3>عنوان فرعي</h3>\n        <p>بعد مرور أكثر من نصف قرن على أبحاث ناسا، تعمل حالياً عدة شركات في قطاع البيولوجيا التركيبية من ضمنها إير بروتين (Air Protein) وسولار فودز (Solar Foods) على تطوير جيل جديد من المنتجات الغذائية المستدامة، من دون وجود بصمة كربونية. ولن تقتصر هذه المنتجات الغذائية على روَّاد الفضاء فحسب، بل سوف تمتد لتشمل جميع سكان الأرض، وسوف تُنتَج في فترة زمنية قصيرة، بدلاً من الشهور، ومن دون الاعتماد على الأراضي الزراعية. وهذا يعني الحصول على منتجات غذائية بشكل سريع جداً. كما سيصبح من الممكن تصنيع الغذاء بطريقة عمودية من خلال هذه الميكروبات، بدلاً من الطريقة الأفقية التقليدية الشبيهة بتقنية الزراعة العمودية الحديثة. وهذا يعني توفير منتجات غذائية أكبر من المساحة نفسها.</p>\n        <p>يتكوَّن الغذاء البشري من ثلاثة أنواع رئيسة، هي:</p>\n        <ul>\n          <li>البروتينات</li>\n          <li>الكربوهيدرات</li>\n          <li>الدهون</li>\n        </ul>\n        <p>وتتكوَّن البروتينات من الأحماض الأمينية، وهي مجموعة من المركبات العضوية يبلغ عددها في جسم الإنسان عشرين حمضاً أمينياً، من بينها تسعة أساسية يحصل عليها الجسم من الغذاء. وتتكوَّن الأحماض الأمينية بشكل أساس من:</p>\n        <ol>\n          <li>الكربون</li>\n          <li>الهيدروجين</li>\n          <li>الأكسجين</li>\n          <li>النيتروجين</li>\n        </ol>\n        <p>ومن الملاحظ أن النيتروجين يشكِّل نسبة %78 من الهواء، كما أن الهيدروجين نحصل عليه من خلال التحليل الكهربائي للماء، ومن الممكن نظرياً سحب الكربون من الهواء لتشكيل هذه الأحماض، ذلك أن الكربون هو العمود الفقري للأحماض الأمينية، كما أن الحياة على كوكب الأرض قائمة على الكربون لقدرته على تكوين سلاسل كربونية طويلة، وهذا ما تفعله الميكروبات بتصنيع أحماض أمينية من ثاني أكسيد الكربون من خلال مجموعة من التفاعلات الكيميائية المعقَّدة. وإضافة إلى صنع وجبات غنية بالبروتين، فهذه الميكروبات تنتج منتجات أخرى مثل الزيوت التي لها عديد من الاستخدامات.</p>\n      </article>\n\n      <article class=\"blog-post\">\n        <h2 class=\"blog-post-title mb-1\">تدوينة أخرى</h2>\n        <p class=\"blog-post-meta\">23 ديسمبر 2020 بواسطة <a href=\"#\">Jacob</a></p>\n\n        <p>في الوقت الحالي، تدرس عدَّة شركات هذه الميكروبات بشكل أعمق، وتستزرعها من أجل الحصول على الغذاء. ففي عام 2019م، أعلن باحثون في شركة (Air Protein) الأمريكية نجاحهم في تحويل ثاني أكسيد الكربون الموجود في الهواء إلى لحوم صناعية مصنوعة من البروتين، التي لا تتطلَّب أي أرض زراعية، بل هي معتمدة بشكل أساسي على الهواء.</p>\n        <blockquote>\n          <p>تم تصنيع اللحوم بأنواع عديدة</p>\n        </blockquote>\n        <p>إذ استخدم هؤلاء الباحثون الهواء والطاقة المتجدِّدة كمدخلات في عملية مشابهة للتخمير، لإنتاج بروتين يحتوي على الأحماض الأمينية التسعة الأساسية وغني بالفيتامينات والمعادن، كما أنه خالٍ من الهرمونات والمضادات الحيوية والمبيدات الحشرية ومبيدات الأعشاب.</p>\n        <p> وتم تصنيع اللحوم بأنواع عديدة بما فيها الدواجن والأبقار والمأكولات البحرية، من دون حصول انبعاثات كربونية، على عكس تربية الأبقار التي تسهم في انبعاث غاز الميثان أحد غازات الاحتباس الحراري.</p>\n      </article>\n\n      <article class=\"blog-post\">\n        <h2 class=\"blog-post-title mb-1\">ميزة جديدة</h2>\n        <p class=\"blog-post-meta\">14 ديسمبر 2020 بواسطة <a href=\"#\">Jacob</a></p>\n\n        <p>كما أن الشركة الفنلندية (Solar Foods) طوَّرت تقنية لإنتاج البروتين من الهواء، حيث تبدأ العملية بتقسيم الماء إلى مكوناته الهيدروجين والأكسجين عن طريق الكهرباء. فالهيدروجين يوفِّر الطاقة للبكتريا لتحويل ثاني أكسيد الكربون والنيتروجين الموجودين في الهواء إلى مادة عضوية غنية بالبروتين بشكل أكفأ من نمو النباتات باستخدام البناء الضوئي. وهذا البروتين يشبه دقيق القمح وقد أطلق عليه اسم \"سولين\" (Solein).</p>\n        <p>وتقوم الشركة حالياً بجمع البيانات حول المنتج الغذائي لتقديمه إلى الاتحاد الأوروبي بهدف الحصول على ترخيص غذائي، كما أنها تخطط لبدء الإنتاج التجاري في العام المقبل 2021م. وقد أوضحت الشركة أنها مهتمة بإنتاج أطعمة صديقة للبيئة من خلال استخدام المواد الأساسية: الكهرباء وثاني أكسيد الكربون، وهذه الأطعمة سوف تجنبنا الأثر السلبي البيئي للزراعة التقليدية الذي يشمل كل شيء من استخدام الأرض والمياه إلى الانبعاثات الناتجة من تسميد المحاصيل أو تربية الحيوانات.</p>\n        <p>وعلى هذا، فإن البروتينات المشتقة من الميكروبات سوف:</p>\n        <ul>\n          <li>توفر حلاً ممكناً في ظل زيادة الطلب العالمي المستقبلي على الغذاء</li>\n          <li>تتوسع مصانع الغذاء في المستقبل لتكون أكفأ وأكثر استدامة</li>\n          <li>تصبح قادرة على توفير الغذاء لروَّاد الفضاء في سفرهم إلى المريخ وجميع سكان كوكب الأرض في عام 2050م</li>\n        </ul>\n        <p>فتخيّل أن الميكروبات ستكون مصانع المستقبل، وأن غذاء المستقبل سيكون مصنوعاً من الهواء! وأن عام 2050م سيكون مختلفاً تماماً عن عالمنا اليوم. فهو عالم من دون زراعة ولا تربية حيوانات من أجل الغذاء! قد يبدو ذلك خيالياً لكنه ليس مستحيلاً!</p>\n      </article>\n\n      <nav class=\"blog-pagination\" aria-label=\"Pagination\">\n        <a class=\"btn btn-outline-primary rounded-pill\" href=\"#\">تدوينات أقدم</a>\n        <a class=\"btn btn-outline-secondary rounded-pill disabled\">تدوينات أحدث</a>\n      </nav>\n\n    </div>\n\n    <div class=\"col-md-4\">\n      <div class=\"position-sticky\" style=\"top: 2rem;\">\n        <div class=\"p-4 mb-3 bg-light rounded\">\n          <h4 class=\"fst-italic\">حول</h4>\n          <p class=\"mb-0\">أقبلت، فأقبلت معك الحياة بجميع صنوفها وألوانها: فالنبات ينبت، والأشجار تورق وتزهر، والهرة تموء، والقمري يسجع، والغنم يثغو، والبقر يخور، وكل أليف يدعو أليفه. كل شيء يشعر بالحياة وينسي هموم الحياة، ولا يذكر إلا سعادة الحياة، فإن كان الزمان جسدا فأنت روحه، وإن كان عمرا فأنت شبابه.</p>\n        </div>\n\n        <div class=\"p-4\">\n          <h4 class=\"fst-italic\">الأرشيف</h4>\n          <ol class=\"list-unstyled mb-0\">\n            <li><a href=\"#\">مارس 2021</a></li>\n            <li><a href=\"#\">شباط 2021</a></li>\n            <li><a href=\"#\">يناير 2021</a></li>\n            <li><a href=\"#\">ديسمبر 2020</a></li>\n            <li><a href=\"#\">نوفمبر 2020</a></li>\n            <li><a href=\"#\">أكتوبر 2020</a></li>\n            <li><a href=\"#\">سبتمبر 2020</a></li>\n            <li><a href=\"#\">اغسطس 2020</a></li>\n            <li><a href=\"#\">يوليو 2020</a></li>\n            <li><a href=\"#\">يونيو 2020</a></li>\n            <li><a href=\"#\">مايو 2020</a></li>\n            <li><a href=\"#\">ابريل 2020</a></li>\n          </ol>\n        </div>\n\n        <div class=\"p-4\">\n          <h4 class=\"fst-italic\">في مكان آخر</h4>\n          <ol class=\"list-unstyled\">\n            <li><a href=\"#\">GitHub</a></li>\n            <li><a href=\"#\">Twitter</a></li>\n            <li><a href=\"#\">Facebook</a></li>\n          </ol>\n        </div>\n      </div>\n    </div>\n  </div>\n\n</main>\n\n<footer class=\"blog-footer\">\n  <p>تم تصميم نموذج المدونة لـ <a href=\"https://getbootstrap.com/\">Bootstrap</a> بواسطة <a href=\"https://twitter.com/mdo\"><bdi lang=\"en\" dir=\"ltr\">@mdo</bdi></a>.</p>\n  <p>\n    <a href=\"#\">عد إلى الأعلى</a>\n  </p>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/carousel/carousel.css",
    "content": "/* GLOBAL STYLES\n-------------------------------------------------- */\n/* Padding below the footer and lighter body text */\n\nbody {\n  padding-top: 3rem;\n  padding-bottom: 3rem;\n  color: #5a5a5a;\n}\n\n\n/* CUSTOMIZE THE CAROUSEL\n-------------------------------------------------- */\n\n/* Carousel base class */\n.carousel {\n  margin-bottom: 4rem;\n}\n/* Since positioning the image, we need to help out the caption */\n.carousel-caption {\n  bottom: 3rem;\n  z-index: 10;\n}\n\n/* Declare heights because of positioning of img element */\n.carousel-item {\n  height: 32rem;\n}\n\n\n/* MARKETING CONTENT\n-------------------------------------------------- */\n\n/* Center align the text within the three columns below the carousel */\n.marketing .col-lg-4 {\n  margin-bottom: 1.5rem;\n  text-align: center;\n}\n/* rtl:begin:ignore */\n.marketing .col-lg-4 p {\n  margin-right: .75rem;\n  margin-left: .75rem;\n}\n/* rtl:end:ignore */\n\n\n/* Featurettes\n------------------------- */\n\n.featurette-divider {\n  margin: 5rem 0; /* Space out the Bootstrap <hr> more */\n}\n\n/* Thin out the marketing headings */\n/* rtl:begin:remove */\n.featurette-heading {\n  letter-spacing: -.05rem;\n}\n\n/* rtl:end:remove */\n\n/* RESPONSIVE CSS\n-------------------------------------------------- */\n\n@media (min-width: 40em) {\n  /* Bump up size of carousel content */\n  .carousel-caption p {\n    margin-bottom: 1.25rem;\n    font-size: 1.25rem;\n    line-height: 1.4;\n  }\n\n  .featurette-heading {\n    font-size: 50px;\n  }\n}\n\n@media (min-width: 62em) {\n  .featurette-heading {\n    margin-top: 7rem;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/carousel/carousel.rtl.css",
    "content": "/* GLOBAL STYLES\n-------------------------------------------------- */\n/* Padding below the footer and lighter body text */\n\nbody {\n  padding-top: 3rem;\n  padding-bottom: 3rem;\n  color: #5a5a5a;\n}\n\n\n/* CUSTOMIZE THE CAROUSEL\n-------------------------------------------------- */\n\n/* Carousel base class */\n.carousel {\n  margin-bottom: 4rem;\n}\n/* Since positioning the image, we need to help out the caption */\n.carousel-caption {\n  bottom: 3rem;\n  z-index: 10;\n}\n\n/* Declare heights because of positioning of img element */\n.carousel-item {\n  height: 32rem;\n}\n\n\n/* MARKETING CONTENT\n-------------------------------------------------- */\n\n/* Center align the text within the three columns below the carousel */\n.marketing .col-lg-4 {\n  margin-bottom: 1.5rem;\n  text-align: center;\n}\n.marketing .col-lg-4 p {\n  margin-right: .75rem;\n  margin-left: .75rem;\n}\n\n\n/* Featurettes\n------------------------- */\n\n.featurette-divider {\n  margin: 5rem 0; /* Space out the Bootstrap <hr> more */\n}\n\n/* Thin out the marketing headings */\n\n/* RESPONSIVE CSS\n-------------------------------------------------- */\n\n@media (min-width: 40em) {\n  /* Bump up size of carousel content */\n  .carousel-caption p {\n    margin-bottom: 1.25rem;\n    font-size: 1.25rem;\n    line-height: 1.4;\n  }\n\n  .featurette-heading {\n    font-size: 50px;\n  }\n}\n\n@media (min-width: 62em) {\n  .featurette-heading {\n    margin-top: 7rem;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/carousel/index.html",
    "content": "---\nlayout: examples\ntitle: Carousel Template\nextra_css:\n  - \"carousel.css\"\n---\n\n<header>\n  <nav class=\"navbar navbar-expand-md navbar-dark fixed-top bg-dark\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Carousel</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-md-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n        </ul>\n        <form class=\"d-flex\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </nav>\n</header>\n\n<main>\n\n  <div id=\"myCarousel\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n    <div class=\"carousel-indicators\">\n      <button type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n      <button type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n      <button type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n    </div>\n    <div class=\"carousel-inner\">\n      <div class=\"carousel-item active\">\n        {{< placeholder width=\"100%\" height=\"100%\" background=\"#777\" color=\"#777\" text=\"false\" title=\"false\" >}}\n        <div class=\"container\">\n          <div class=\"carousel-caption text-start\">\n            <h1>Example headline.</h1>\n            <p>Some representative placeholder content for the first slide of the carousel.</p>\n            <p><a class=\"btn btn-lg btn-primary\" href=\"#\">Sign up today</a></p>\n          </div>\n        </div>\n      </div>\n      <div class=\"carousel-item\">\n        {{< placeholder width=\"100%\" height=\"100%\" background=\"#777\" color=\"#777\" text=\"false\" title=\"false\" >}}\n        <div class=\"container\">\n          <div class=\"carousel-caption\">\n            <h1>Another example headline.</h1>\n            <p>Some representative placeholder content for the second slide of the carousel.</p>\n            <p><a class=\"btn btn-lg btn-primary\" href=\"#\">Learn more</a></p>\n          </div>\n        </div>\n      </div>\n      <div class=\"carousel-item\">\n        {{< placeholder width=\"100%\" height=\"100%\" background=\"#777\" color=\"#777\" text=\"false\" title=\"false\" >}}\n        <div class=\"container\">\n          <div class=\"carousel-caption text-end\">\n            <h1>One more for good measure.</h1>\n            <p>Some representative placeholder content for the third slide of this carousel.</p>\n            <p><a class=\"btn btn-lg btn-primary\" href=\"#\">Browse gallery</a></p>\n          </div>\n        </div>\n      </div>\n    </div>\n    <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide=\"prev\">\n      <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n      <span class=\"visually-hidden\">Previous</span>\n    </button>\n    <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide=\"next\">\n      <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n      <span class=\"visually-hidden\">Next</span>\n    </button>\n  </div>\n\n\n  <!-- Marketing messaging and featurettes\n  ================================================== -->\n  <!-- Wrap the rest of the page in another container to center all the content. -->\n\n  <div class=\"container marketing\">\n\n    <!-- Three columns of text below the carousel -->\n    <div class=\"row\">\n      <div class=\"col-lg-4\">\n        {{< placeholder width=\"140\" height=\"140\" background=\"#777\" color=\"#777\" class=\"rounded-circle\" >}}\n        <h2 class=\"fw-normal\">Heading</h2>\n        <p>Some representative placeholder content for the three columns of text below the carousel. This is the first column.</p>\n        <p><a class=\"btn btn-secondary\" href=\"#\">View details &raquo;</a></p>\n      </div><!-- /.col-lg-4 -->\n      <div class=\"col-lg-4\">\n        {{< placeholder width=\"140\" height=\"140\" background=\"#777\" color=\"#777\" class=\"rounded-circle\" >}}\n        <h2 class=\"fw-normal\">Heading</h2>\n        <p>Another exciting bit of representative placeholder content. This time, we've moved on to the second column.</p>\n        <p><a class=\"btn btn-secondary\" href=\"#\">View details &raquo;</a></p>\n      </div><!-- /.col-lg-4 -->\n      <div class=\"col-lg-4\">\n        {{< placeholder width=\"140\" height=\"140\" background=\"#777\" color=\"#777\" class=\"rounded-circle\" >}}\n        <h2 class=\"fw-normal\">Heading</h2>\n        <p>And lastly this, the third column of representative placeholder content.</p>\n        <p><a class=\"btn btn-secondary\" href=\"#\">View details &raquo;</a></p>\n      </div><!-- /.col-lg-4 -->\n    </div><!-- /.row -->\n\n\n    <!-- START THE FEATURETTES -->\n\n    <hr class=\"featurette-divider\">\n\n    <div class=\"row featurette\">\n      <div class=\"col-md-7\">\n        <h2 class=\"featurette-heading fw-normal lh-1\">First featurette heading. <span class=\"text-muted\">It’ll blow your mind.</span></h2>\n        <p class=\"lead\">Some great placeholder content for the first featurette here. Imagine some exciting prose here.</p>\n      </div>\n      <div class=\"col-md-5\">\n        {{< placeholder width=\"500\" height=\"500\" background=\"#eee\" color=\"#aaa\" class=\"bd-placeholder-img-lg featurette-image img-fluid mx-auto\" >}}\n      </div>\n    </div>\n\n    <hr class=\"featurette-divider\">\n\n    <div class=\"row featurette\">\n      <div class=\"col-md-7 order-md-2\">\n        <h2 class=\"featurette-heading fw-normal lh-1\">Oh yeah, it’s that good. <span class=\"text-muted\">See for yourself.</span></h2>\n        <p class=\"lead\">Another featurette? Of course. More placeholder content here to give you an idea of how this layout would work with some actual real-world content in place.</p>\n      </div>\n      <div class=\"col-md-5 order-md-1\">\n        {{< placeholder width=\"500\" height=\"500\" background=\"#eee\" color=\"#aaa\" class=\"bd-placeholder-img-lg featurette-image img-fluid mx-auto\" >}}\n      </div>\n    </div>\n\n    <hr class=\"featurette-divider\">\n\n    <div class=\"row featurette\">\n      <div class=\"col-md-7\">\n        <h2 class=\"featurette-heading fw-normal lh-1\">And lastly, this one. <span class=\"text-muted\">Checkmate.</span></h2>\n        <p class=\"lead\">And yes, this is the last block of representative placeholder content. Again, not really intended to be actually read, simply here to give you a better view of what this would look like with some actual content. Your content.</p>\n      </div>\n      <div class=\"col-md-5\">\n        {{< placeholder width=\"500\" height=\"500\" background=\"#eee\" color=\"#aaa\" class=\"bd-placeholder-img-lg featurette-image img-fluid mx-auto\" >}}\n      </div>\n    </div>\n\n    <hr class=\"featurette-divider\">\n\n    <!-- /END THE FEATURETTES -->\n\n  </div><!-- /.container -->\n\n\n  <!-- FOOTER -->\n  <footer class=\"container\">\n    <p class=\"float-end\"><a href=\"#\">Back to top</a></p>\n    <p>&copy; 2017–{{< year >}} Company, Inc. &middot; <a href=\"#\">Privacy</a> &middot; <a href=\"#\">Terms</a></p>\n  </footer>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/carousel-rtl/index.html",
    "content": "---\nlayout: examples\ntitle: قالب  شرائح العرض\ndirection: rtl\nextra_css:\n  - \"../carousel/carousel.rtl.css\"\n---\n\n<header>\n  <nav class=\"navbar navbar-expand-md navbar-dark fixed-top bg-dark\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">شرائح العرض</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"تبديل التنقل\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-md-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">الصفحة الرئيسية</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">حلقة الوصل</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">رابط غير مفعل</a>\n          </li>\n        </ul>\n        <form class=\"d-flex\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"بحث\" aria-label=\"بحث\">\n          <button class=\"btn btn-outline-success\" type=\"submit\">بحث</button>\n        </form>\n      </div>\n    </div>\n  </nav>\n</header>\n\n<main>\n\n  <div id=\"myCarousel\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n    <div class=\"carousel-indicators\">\n      <button type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n      <button type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n      <button type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n    </div>\n    <div class=\"carousel-inner\">\n      <div class=\"carousel-item active\">\n        {{< placeholder width=\"100%\" height=\"100%\" background=\"#777\" color=\"#777\" text=\"false\" title=\"false\" >}}\n        <div class=\"container\">\n          <div class=\"carousel-caption text-start\">\n            <h1>عنوان المثال.</h1>\n            <p>تشير الدراسات الإحصائية حسب الجمعية الأمريكية للغات بأن الإقبال على العربية زاد %126 في الولايات المتحدة الأمريكية وحدها بين عامي 2002 و2009م.</p>\n            <p><a class=\"btn btn-lg btn-primary\" href=\"#\">سجل اليوم</a></p>\n          </div>\n        </div>\n      </div>\n      <div class=\"carousel-item\">\n        {{< placeholder width=\"100%\" height=\"100%\" background=\"#777\" color=\"#777\" text=\"false\" title=\"false\" >}}\n        <div class=\"container\">\n          <div class=\"carousel-caption\">\n            <h1>عنوان مثال آخر.</h1>\n            <p>حسب المجلس الثقافي البريطاني فإن تعليم الإنجليزية داخل بريطانيا يسهم في تعزيز اقتصادها بما يتجاوز ملياري جنيه سنوياً، كما أنه وفر أكثر من 26 ألف وظيفة.</p>\n            <p><a class=\"btn btn-lg btn-primary\" href=\"#\">أعرف أكثر</a></p>\n          </div>\n        </div>\n      </div>\n      <div class=\"carousel-item\">\n        {{< placeholder width=\"100%\" height=\"100%\" background=\"#777\" color=\"#777\" text=\"false\" title=\"false\" >}}\n        <div class=\"container\">\n          <div class=\"carousel-caption text-end\">\n            <h1>واحد أكثر لقياس جيد.</h1>\n            <p>الإحصاءات لحجم الاستثمار اللغوي خارج بريطانيا تتفاوت من سنة لأخرى إلا أن المدير التنفيذي للمجلس الثقافي البريطاني إدي بايرز يرى أن استثمار تعليم الإنجليزية في الخارج لا يحسب على المستوى المالي فحسب بل على المستوى السياسي أيضاً.</p>\n            <p><a class=\"btn btn-lg btn-primary\" href=\"#\">تصفح المعرض</a></p>\n          </div>\n        </div>\n      </div>\n    </div>\n    <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide=\"prev\">\n      <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n      <span class=\"visually-hidden\">السابق</span>\n    </button>\n    <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#myCarousel\" data-bs-slide=\"next\">\n      <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n      <span class=\"visually-hidden\">التالي</span>\n    </button>\n  </div>\n\n\n  <!-- Marketing messaging and featurettes\n  ================================================== -->\n  <!-- Wrap the rest of the page in another container to center all the content. -->\n\n  <div class=\"container marketing\">\n\n    <!-- Three columns of text below the carousel -->\n    <div class=\"row\">\n      <div class=\"col-lg-4\">\n        {{< placeholder width=\"140\" height=\"140\" background=\"#777\" color=\"#777\" class=\"rounded-circle\" >}}\n        <h2 class=\"fw-normal\">عنوان</h2>\n        <p>تذكر دائماً أن الحاسوب لا يمتلك ذكاءً، ولكنه يكتسب الذكاء الاصطناعي من خلال ثلاثة عناصر وظيفية رئيسة، هي: القدرة على التحليل، والقدرة على التأليف، والاستدلال المنطقي.</p>\n        <p><a class=\"btn btn-secondary\" href=\"#\">عرض التفاصيل</a></p>\n      </div><!-- /.col-lg-4 -->\n      <div class=\"col-lg-4\">\n        {{< placeholder width=\"140\" height=\"140\" background=\"#777\" color=\"#777\" class=\"rounded-circle\" >}}\n        <h2 class=\"fw-normal\">عنوان آخر</h2>\n        <p>إذا أردنا استخدام الحاسوب الذكي في معالجة اللغة العربية فإننا نجد أنفسنا أمام تحدٍّ كبير، خاصة وأن لغتنا تمتاز بتماسك منظوماتها وتداخلها، ومع ذلك فإن الذكاء الاصطناعي يمكّننا من الحصول على أربعة أنواع من المعالجة، هي: المعالجة الصوتية، والمعالجة الصرفية، والمعالجة النحوية، والمعالجة الدلالية.</p>\n        <p><a class=\"btn btn-secondary\" href=\"#\">عرض التفاصيل</a></p>\n      </div><!-- /.col-lg-4 -->\n      <div class=\"col-lg-4\">\n        {{< placeholder width=\"140\" height=\"140\" background=\"#777\" color=\"#777\" class=\"rounded-circle\" >}}\n        <h2 class=\"fw-normal\">عنوان ثالث لتأكيد المعلومة</h2>\n        <p>بفضل بحوث الذكاء الاصطناعي وتقنياته استطعنا الانتقال من مرحلة التعامل مع الفيزيائي إلى مرحلة التعامل مع المنطقي، وقد انعكس هذا الانتقال بصورة إيجابية على الكيفية التي تتعامل بها الشعوب مع لغاتها الحيَّة، وهذا يعني أنه يجب أن ينعكس بصورة إيجابية على كيفية تعاملنا مع لغتنا العربية.</p>\n        <p><a class=\"btn btn-secondary\" href=\"#\">عرض التفاصيل</a></p>\n      </div><!-- /.col-lg-4 -->\n    </div><!-- /.row -->\n\n\n    <!-- START THE FEATURETTES -->\n\n    <hr class=\"featurette-divider\">\n\n    <div class=\"row featurette\">\n      <div class=\"col-md-7\">\n        <h2 class=\"featurette-heading fw-normal lh-1\">العنوان الأول المميز. <span class=\"text-muted\"> سيذهل عقلك. </span></h2>\n        <p class=\"lead\">وجه الإنسان هو جزء معقَّد ومتميِّز للغاية من جسمه. وفي الواقع، إنه أحد أكثر أنظمة الإشارات المتاحة تعقيداً لدينا؛ فهو يتضمَّن أكثر من 40 عضلة مستقلة هيكلياً ووظيفياً، بحيث يمكن تشغيل كل منها بشكل مستقل عن البعض الآخر؛ وتشكِّل أحد أقوى مؤشرات العواطف.</p>\n      </div>\n      <div class=\"col-md-5\">\n        {{< placeholder width=\"500\" height=\"500\" background=\"#eee\" color=\"#aaa\" class=\"bd-placeholder-img-lg featurette-image img-fluid mx-auto\" >}}\n      </div>\n    </div>\n\n    <hr class=\"featurette-divider\">\n\n    <div class=\"row featurette\">\n      <div class=\"col-md-7 order-md-2\">\n        <h2 class=\"featurette-heading fw-normal lh-1\">أوه نعم، هذا جيد. <span class=\"text-muted\"> شاهد بنفسك. </span></h2>\n        <p class=\"lead\">عندما نضحك أو نبكي، فإننا نعرض عواطفنا، مما يسمح للآخرين بإلقاء نظرة خاطفة على أذهاننا أثناء \"قراءة\" وجوهنا بناءً على التغييرات في مكوّنات الوجه الرئيسة، مثل: العينين والحاجبين والجفنين والأنف والشفتين.</p>\n      </div>\n      <div class=\"col-md-5 order-md-1\">\n        {{< placeholder width=\"500\" height=\"500\" background=\"#eee\" color=\"#aaa\" class=\"bd-placeholder-img-lg featurette-image img-fluid mx-auto\" >}}\n      </div>\n    </div>\n\n    <hr class=\"featurette-divider\">\n\n    <div class=\"row featurette\">\n      <div class=\"col-md-7\">\n        <h2 class=\"featurette-heading fw-normal lh-1\">وأخيرًا، هذا. <span class=\"text-muted\"> كش ملك. </span></h2>\n        <p class=\"lead\">إن جميع العضلات في أجسامنا مدعمة بالأعصاب المتصلة من كافة أنحاء الجسم بالنخاع الشوكي والدماغ. وهذا الاتصال العصبي هو ثنائي الاتجاه، أي إن العصب يتسبَّب في تقلصات العضلات بناءً على إشارات الدماغ، ويقوم في الوقت نفسه بإرسال معلومات عن حالة العضلات إلى الدماغ</p>\n      </div>\n      <div class=\"col-md-5\">\n        {{< placeholder width=\"500\" height=\"500\" background=\"#eee\" color=\"#aaa\" class=\"bd-placeholder-img-lg featurette-image img-fluid mx-auto\" >}}\n      </div>\n    </div>\n\n    <hr class=\"featurette-divider\">\n\n    <!-- /END THE FEATURETTES -->\n\n  </div><!-- /.container -->\n\n\n  <!-- FOOTER -->\n  <footer class=\"container\">\n    <p class=\"float-end\"><a href=\"#\">عد إلى الأعلى</a></p>\n    <p>&copy; 2017–{{< year >}} Company, Inc. &middot; <a href=\"#\">سياسة الخصوصية</a> &middot; <a href=\"#\">شروط الاستخدام</a></p>\n  </footer>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cheatsheet/cheatsheet.css",
    "content": "body {\n  scroll-behavior: smooth;\n}\n\n/**\n * Bootstrap \"Journal code\" icon\n * @link https://icons.getbootstrap.com/icons/journal-code/\n */\n.bd-heading a::before {\n  display: inline-block;\n  width: 1em;\n  height: 1em;\n  margin-right: .25rem;\n  content: \"\";\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%25230d6efd' viewBox='0 0 16 16'%3E%3Cpath d='M4 1h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2h1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1H2a2 2 0 0 1 2-2z'/%3E%3Cpath d='M2 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2z'/%3E%3Cpath fill-rule='evenodd' d='M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z'/%3E%3C/svg%3E\");\n  background-size: 1em;\n}\n\n/* stylelint-disable-next-line selector-max-universal */\n.bd-heading + div > * + * {\n  margin-top: 3rem;\n}\n\n/* Table of contents */\n.bd-aside a {\n  padding: .1875rem .5rem;\n  margin-top: .125rem;\n  margin-left: .3125rem;\n  color: rgba(0, 0, 0, .65);\n}\n\n.bd-aside a:hover,\n.bd-aside a:focus {\n  color: rgba(0, 0, 0, .85);\n  background-color: rgba(121, 82, 179, .1);\n}\n\n.bd-aside .active {\n  font-weight: 600;\n  color: rgba(0, 0, 0, .85);\n}\n\n.bd-aside .btn {\n  padding: .25rem .5rem;\n  font-weight: 600;\n  color: rgba(0, 0, 0, .65);\n}\n\n.bd-aside .btn:hover,\n.bd-aside .btn:focus {\n  color: rgba(0, 0, 0, .85);\n  background-color: rgba(121, 82, 179, .1);\n}\n\n.bd-aside .btn:focus {\n  box-shadow: 0 0 0 1px rgba(121, 82, 179, .7);\n}\n\n.bd-aside .btn::before {\n  width: 1.25em;\n  line-height: 0;\n  content: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e\");\n  transition: transform .35s ease;\n\n  /* rtl:raw:\n  transform: rotate(180deg) translateX(-2px);\n  */\n  transform-origin: .5em 50%;\n}\n\n.bd-aside .btn[aria-expanded=\"true\"]::before {\n  transform: rotate(90deg)/* rtl:ignore */;\n}\n\n\n/* Examples */\n.scrollspy-example {\n  height: 200px;\n}\n\n[id=\"modal\"] .bd-example .btn,\n[id=\"buttons\"] .bd-example .btn,\n[id=\"tooltips\"] .bd-example .btn,\n[id=\"popovers\"] .bd-example .btn,\n[id=\"dropdowns\"] .bd-example .btn-group,\n[id=\"dropdowns\"] .bd-example .dropdown,\n[id=\"dropdowns\"] .bd-example .dropup,\n[id=\"dropdowns\"] .bd-example .dropend,\n[id=\"dropdowns\"] .bd-example .dropstart {\n  margin: 0 1rem 1rem 0;\n}\n\n/* Layout */\n@media (min-width: 1200px) {\n  body {\n    display: grid;\n    grid-template-rows: auto;\n    grid-template-columns: 1fr 4fr 1fr;\n    gap: 1rem;\n  }\n\n  .bd-header {\n    position: fixed;\n    top: 0;\n    /* rtl:begin:ignore */\n    right: 0;\n    left: 0;\n    /* rtl:end:ignore */\n    z-index: 1030;\n    grid-column: 1 / span 3;\n  }\n\n  .bd-aside,\n  .bd-cheatsheet {\n    padding-top: 4rem;\n  }\n\n  /**\n   * 1. Too bad only Firefox supports subgrids ATM\n   */\n  .bd-cheatsheet,\n  .bd-cheatsheet section,\n  .bd-cheatsheet article {\n    display: inherit; /* 1 */\n    grid-template-rows: auto;\n    grid-template-columns: 1fr 4fr;\n    grid-column: 1 / span 2;\n    gap: inherit; /* 1 */\n  }\n\n  .bd-aside {\n    grid-area: 1 / 3;\n    scroll-margin-top: 4rem;\n  }\n\n  .bd-cheatsheet section,\n  .bd-cheatsheet section > h2 {\n    top: 2rem;\n    scroll-margin-top: 2rem;\n  }\n\n  .bd-cheatsheet section > h2::before {\n    position: absolute;\n    /* rtl:begin:ignore */\n    top: 0;\n    right: 0;\n    bottom: -2rem;\n    left: 0;\n    /* rtl:end:ignore */\n    z-index: -1;\n    content: \"\";\n    background-image: linear-gradient(to bottom, rgba(255, 255, 255, 1) calc(100% - 3rem), rgba(255, 255, 255, .01));\n  }\n\n  .bd-cheatsheet article,\n  .bd-cheatsheet .bd-heading {\n    top: 8rem;\n    scroll-margin-top: 8rem;\n  }\n\n  .bd-cheatsheet .bd-heading {\n    z-index: 1;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cheatsheet/cheatsheet.js",
    "content": "/* global bootstrap: false */\n\n(() => {\n  'use strict'\n\n  // Tooltip and popover demos\n  document.querySelectorAll('.tooltip-demo')\n    .forEach(tooltip => {\n      new bootstrap.Tooltip(tooltip, {\n        selector: '[data-bs-toggle=\"tooltip\"]'\n      })\n    })\n\n  document.querySelectorAll('[data-bs-toggle=\"popover\"]')\n    .forEach(popover => {\n      new bootstrap.Popover(popover)\n    })\n\n  document.querySelectorAll('.toast')\n    .forEach(toastNode => {\n      const toast = new bootstrap.Toast(toastNode, {\n        autohide: false\n      })\n\n      toast.show()\n    })\n\n  // Disable empty links and submit buttons\n  document.querySelectorAll('[href=\"#\"], [type=\"submit\"]')\n    .forEach(link => {\n      link.addEventListener('click', event => {\n        event.preventDefault()\n      })\n    })\n\n  function setActiveItem() {\n    const { hash } = window.location\n\n    if (hash === '') {\n      return\n    }\n\n    const link = document.querySelector(`.bd-aside a[href=\"${hash}\"]`)\n\n    if (!link) {\n      return\n    }\n\n    const active = document.querySelector('.bd-aside .active')\n    const parent = link.parentNode.parentNode.previousElementSibling\n\n    link.classList.add('active')\n\n    if (parent.classList.contains('collapsed')) {\n      parent.click()\n    }\n\n    if (!active) {\n      return\n    }\n\n    const expanded = active.parentNode.parentNode.previousElementSibling\n\n    active.classList.remove('active')\n\n    if (expanded && parent !== expanded) {\n      expanded.click()\n    }\n  }\n\n  setActiveItem()\n  window.addEventListener('hashchange', setActiveItem)\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cheatsheet/cheatsheet.rtl.css",
    "content": "body {\n  scroll-behavior: smooth;\n}\n\n/**\n * Bootstrap \"Journal code\" icon\n * @link https://icons.getbootstrap.com/icons/journal-code/\n */\n.bd-heading a::before {\n  display: inline-block;\n  width: 1em;\n  height: 1em;\n  margin-left: .25rem;\n  content: \"\";\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%25230d6efd' viewBox='0 0 16 16'%3E%3Cpath d='M4 1h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2h1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1H2a2 2 0 0 1 2-2z'/%3E%3Cpath d='M2 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2z'/%3E%3Cpath fill-rule='evenodd' d='M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z'/%3E%3C/svg%3E\");\n  background-size: 1em;\n}\n\n/* stylelint-disable-next-line selector-max-universal */\n.bd-heading + div > * + * {\n  margin-top: 3rem;\n}\n\n/* Table of contents */\n.bd-aside a {\n  padding: .1875rem .5rem;\n  margin-top: .125rem;\n  margin-right: .3125rem;\n  color: rgba(0, 0, 0, .65);\n}\n\n.bd-aside a:hover,\n.bd-aside a:focus {\n  color: rgba(0, 0, 0, .85);\n  background-color: rgba(121, 82, 179, .1);\n}\n\n.bd-aside .active {\n  font-weight: 600;\n  color: rgba(0, 0, 0, .85);\n}\n\n.bd-aside .btn {\n  padding: .25rem .5rem;\n  font-weight: 600;\n  color: rgba(0, 0, 0, .65);\n}\n\n.bd-aside .btn:hover,\n.bd-aside .btn:focus {\n  color: rgba(0, 0, 0, .85);\n  background-color: rgba(121, 82, 179, .1);\n}\n\n.bd-aside .btn:focus {\n  box-shadow: 0 0 0 1px rgba(121, 82, 179, .7);\n}\n\n.bd-aside .btn::before {\n  width: 1.25em;\n  line-height: 0;\n  content: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e\");\n  transition: transform .35s ease;\n  transform: rotate(180deg) translateX(-2px);\n  transform-origin: .5em 50%;\n}\n\n.bd-aside .btn[aria-expanded=\"true\"]::before {\n  transform: rotate(90deg);\n}\n\n\n/* Examples */\n.scrollspy-example {\n  height: 200px;\n}\n\n[id=\"modal\"] .bd-example .btn,\n[id=\"buttons\"] .bd-example .btn,\n[id=\"tooltips\"] .bd-example .btn,\n[id=\"popovers\"] .bd-example .btn,\n[id=\"dropdowns\"] .bd-example .btn-group,\n[id=\"dropdowns\"] .bd-example .dropdown,\n[id=\"dropdowns\"] .bd-example .dropup,\n[id=\"dropdowns\"] .bd-example .dropend,\n[id=\"dropdowns\"] .bd-example .dropstart {\n  margin: 0 0 1rem 1rem;\n}\n\n/* Layout */\n@media (min-width: 1200px) {\n  body {\n    display: grid;\n    grid-template-rows: auto;\n    grid-template-columns: 1fr 4fr 1fr;\n    gap: 1rem;\n  }\n\n  .bd-header {\n    position: fixed;\n    top: 0;\n    right: 0;\n    left: 0;\n    z-index: 1030;\n    grid-column: 1 / span 3;\n  }\n\n  .bd-aside,\n  .bd-cheatsheet {\n    padding-top: 4rem;\n  }\n\n  /**\n   * 1. Too bad only Firefox supports subgrids ATM\n   */\n  .bd-cheatsheet,\n  .bd-cheatsheet section,\n  .bd-cheatsheet article {\n    display: inherit; /* 1 */\n    grid-template-rows: auto;\n    grid-template-columns: 1fr 4fr;\n    grid-column: 1 / span 2;\n    gap: inherit; /* 1 */\n  }\n\n  .bd-aside {\n    grid-area: 1 / 3;\n    scroll-margin-top: 4rem;\n  }\n\n  .bd-cheatsheet section,\n  .bd-cheatsheet section > h2 {\n    top: 2rem;\n    scroll-margin-top: 2rem;\n  }\n\n  .bd-cheatsheet section > h2::before {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: -2rem;\n    left: 0;\n    z-index: -1;\n    content: \"\";\n    background-image: linear-gradient(to bottom, rgba(255, 255, 255, 1) calc(100% - 3rem), rgba(255, 255, 255, .01));\n  }\n\n  .bd-cheatsheet article,\n  .bd-cheatsheet .bd-heading {\n    top: 8rem;\n    scroll-margin-top: 8rem;\n  }\n\n  .bd-cheatsheet .bd-heading {\n    z-index: 1;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cheatsheet/index.html",
    "content": "---\nlayout: examples\ntitle: Cheatsheet\nextra_css:\n  - \"cheatsheet.css\"\nextra_js:\n  - src: \"cheatsheet.js\"\nbody_class: \"bg-light\"\n---\n\n<header class=\"bd-header bg-dark py-3 d-flex align-items-stretch border-bottom border-dark\">\n  <div class=\"container-fluid d-flex align-items-center\">\n    <h1 class=\"d-flex align-items-center fs-4 text-white mb-0\">\n      <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" width=\"38\" height=\"30\" class=\"me-3\" alt=\"Bootstrap\">\n      Cheatsheet\n    </h1>\n    <a href=\"{{< docsref \"/examples/cheatsheet-rtl\" >}}\" class=\"ms-auto link-light\" hreflang=\"ar\">RTL cheatsheet</a>\n  </div>\n</header>\n<aside class=\"bd-aside sticky-xl-top text-muted align-self-start mb-3 mb-xl-5 px-2\">\n  <h2 class=\"h6 pt-4 pb-3 mb-4 border-bottom\">On this page</h2>\n  <nav class=\"small\" id=\"toc\">\n    <ul class=\"list-unstyled\">\n      <li class=\"my-2\">\n        <button class=\"btn d-inline-flex align-items-center collapsed border-0\" data-bs-toggle=\"collapse\" aria-expanded=\"false\" data-bs-target=\"#contents-collapse\" aria-controls=\"contents-collapse\">Contents</button>\n        <ul class=\"list-unstyled ps-3 collapse\" id=\"contents-collapse\">\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#typography\">Typography</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#images\">Images</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#tables\">Tables</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#figures\">Figures</a></li>\n        </ul>\n      </li>\n      <li class=\"my-2\">\n        <button class=\"btn d-inline-flex align-items-center collapsed border-0\" data-bs-toggle=\"collapse\" aria-expanded=\"false\" data-bs-target=\"#forms-collapse\" aria-controls=\"forms-collapse\">Forms</button>\n        <ul class=\"list-unstyled ps-3 collapse\" id=\"forms-collapse\">\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#overview\">Overview</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#disabled-forms\">Disabled forms</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#sizing\">Sizing</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#input-group\">Input group</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#floating-labels\">Floating labels</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#validation\">Validation</a></li>\n        </ul>\n      </li>\n      <li class=\"my-2\">\n        <button class=\"btn d-inline-flex align-items-center collapsed border-0\" data-bs-toggle=\"collapse\" aria-expanded=\"false\" data-bs-target=\"#components-collapse\" aria-controls=\"components-collapse\">Components</button>\n        <ul class=\"list-unstyled ps-3 collapse\" id=\"components-collapse\">\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#accordion\">Accordion</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#alerts\">Alerts</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#badge\">Badge</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#breadcrumb\">Breadcrumb</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#buttons\">Buttons</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#button-group\">Button group</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#card\">Card</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#carousel\">Carousel</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#dropdowns\">Dropdowns</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#list-group\">List group</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#modal\">Modal</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#navs\">Navs</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#navbar\">Navbar</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#pagination\">Pagination</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#popovers\">Popovers</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#progress\">Progress</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#scrollspy\">Scrollspy</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#spinners\">Spinners</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#toasts\">Toasts</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#tooltips\">Tooltips</a></li>\n        </ul>\n      </li>\n    </ul>\n  </nav>\n</aside>\n<div class=\"bd-cheatsheet container-fluid bg-body\">\n  <section id=\"content\">\n    <h2 class=\"sticky-xl-top fw-bold pt-3 pt-xl-5 pb-2 pb-xl-3\">Contents</h2>\n\n    <article class=\"my-3\" id=\"typography\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Typography</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/content/typography\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <p class=\"display-1\">Display 1</p>\n        <p class=\"display-2\">Display 2</p>\n        <p class=\"display-3\">Display 3</p>\n        <p class=\"display-4\">Display 4</p>\n        <p class=\"display-5\">Display 5</p>\n        <p class=\"display-6\">Display 6</p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <p class=\"h1\">Heading 1</p>\n        <p class=\"h2\">Heading 2</p>\n        <p class=\"h3\">Heading 3</p>\n        <p class=\"h4\">Heading 4</p>\n        <p class=\"h5\">Heading 5</p>\n        <p class=\"h6\">Heading 6</p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <p class=\"lead\">\n          This is a lead paragraph. It stands out from regular paragraphs.\n        </p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <p>You can use the mark tag to <mark>highlight</mark> text.</p>\n        <p><del>This line of text is meant to be treated as deleted text.</del></p>\n        <p><s>This line of text is meant to be treated as no longer accurate.</s></p>\n        <p><ins>This line of text is meant to be treated as an addition to the document.</ins></p>\n        <p><u>This line of text will render as underlined.</u></p>\n        <p><small>This line of text is meant to be treated as fine print.</small></p>\n        <p><strong>This line rendered as bold text.</strong></p>\n        <p><em>This line rendered as italicized text.</em></p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <hr>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <blockquote class=\"blockquote\">\n          <p>A well-known quote, contained in a blockquote element.</p>\n          <footer class=\"blockquote-footer\">Someone famous in <cite title=\"Source Title\">Source Title</cite></footer>\n        </blockquote>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-unstyled\">\n          <li>This is a list.</li>\n          <li>It appears completely unstyled.</li>\n          <li>Structurally, it's still a list.</li>\n          <li>However, this style only applies to immediate child elements.</li>\n          <li>Nested lists:\n            <ul>\n              <li>are unaffected by this style</li>\n              <li>will still show a bullet</li>\n              <li>and have appropriate left margin</li>\n            </ul>\n          </li>\n          <li>This may still come in handy in some situations.</li>\n        </ul>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-inline\">\n          <li class=\"list-inline-item\">This is a list item.</li>\n          <li class=\"list-inline-item\">And another one.</li>\n          <li class=\"list-inline-item\">But they're displayed inline.</li>\n        </ul>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"images\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Images</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/content/images\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< placeholder width=\"100%\" height=\"250\" class=\"bd-placeholder-img-lg img-fluid\" text=\"Responsive image\" >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< placeholder width=\"200\" height=\"200\" class=\"img-thumbnail\" title=\"A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera\" >}}\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"tables\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Tables</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/content/tables\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-striped\">\n          <thead>\n          <tr>\n            <th scope=\"col\">#</th>\n            <th scope=\"col\">First</th>\n            <th scope=\"col\">Last</th>\n            <th scope=\"col\">Handle</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">1</th>\n            <td>Mark</td>\n            <td>Otto</td>\n            <td>@mdo</td>\n          </tr>\n          <tr>\n            <th scope=\"row\">2</th>\n            <td>Jacob</td>\n            <td>Thornton</td>\n            <td>@fat</td>\n          </tr>\n          <tr>\n            <th scope=\"row\">3</th>\n            <td colspan=\"2\">Larry the Bird</td>\n            <td>@twitter</td>\n          </tr>\n          </tbody>\n        </table>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-dark table-borderless\">\n          <thead>\n          <tr>\n            <th scope=\"col\">#</th>\n            <th scope=\"col\">First</th>\n            <th scope=\"col\">Last</th>\n            <th scope=\"col\">Handle</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">1</th>\n            <td>Mark</td>\n            <td>Otto</td>\n            <td>@mdo</td>\n          </tr>\n          <tr>\n            <th scope=\"row\">2</th>\n            <td>Jacob</td>\n            <td>Thornton</td>\n            <td>@fat</td>\n          </tr>\n          <tr>\n            <th scope=\"row\">3</th>\n            <td colspan=\"2\">Larry the Bird</td>\n            <td>@twitter</td>\n          </tr>\n          </tbody>\n        </table>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-hover\">\n          <thead>\n          <tr>\n            <th scope=\"col\">Class</th>\n            <th scope=\"col\">Heading</th>\n            <th scope=\"col\">Heading</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">Default</th>\n            <td>Cell</td>\n            <td>Cell</td>\n          </tr>\n          {{< table.inline >}}\n          {{- range (index $.Site.Data \"theme-colors\") }}\n          <tr class=\"table-{{ .name }}\">\n            <th scope=\"row\">{{ .name | title }}</th>\n            <td>Cell</td>\n            <td>Cell</td>\n          </tr>\n          {{- end -}}\n          {{< /table.inline >}}\n          </tbody>\n        </table>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-sm table-bordered\">\n          <thead>\n          <tr>\n            <th scope=\"col\">#</th>\n            <th scope=\"col\">First</th>\n            <th scope=\"col\">Last</th>\n            <th scope=\"col\">Handle</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">1</th>\n            <td>Mark</td>\n            <td>Otto</td>\n            <td>@mdo</td>\n          </tr>\n          <tr>\n            <th scope=\"row\">2</th>\n            <td>Jacob</td>\n            <td>Thornton</td>\n            <td>@fat</td>\n          </tr>\n          <tr>\n            <th scope=\"row\">3</th>\n            <td colspan=\"2\">Larry the Bird</td>\n            <td>@twitter</td>\n          </tr>\n          </tbody>\n        </table>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"figures\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Figures</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/content/figures\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <figure class=\"figure\">\n          {{< placeholder width=\"400\" height=\"300\" class=\"figure-img img-fluid rounded\" >}}\n          <figcaption class=\"figure-caption\">A caption for the above image.</figcaption>\n        </figure>\n        {{< /example >}}\n      </div>\n    </article>\n  </section>\n\n  <section id=\"forms\">\n    <h2 class=\"sticky-xl-top fw-bold pt-3 pt-xl-5 pb-2 pb-xl-3\">Forms</h2>\n\n    <article class=\"my-3\" id=\"overview\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Overview</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/overview\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form>\n          <div class=\"mb-3\">\n            <label for=\"exampleInputEmail1\" class=\"form-label\">Email address</label>\n            <input type=\"email\" class=\"form-control\" id=\"exampleInputEmail1\" aria-describedby=\"emailHelp\">\n            <div id=\"emailHelp\" class=\"form-text\">We'll never share your email with anyone else.</div>\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"exampleInputPassword1\" class=\"form-label\">Password</label>\n            <input type=\"password\" class=\"form-control\" id=\"exampleInputPassword1\">\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"exampleSelect\" class=\"form-label\">Select menu</label>\n            <select class=\"form-select\" id=\"exampleSelect\">\n              <option selected>Open this select menu</option>\n              <option value=\"1\">One</option>\n              <option value=\"2\">Two</option>\n              <option value=\"3\">Three</option>\n            </select>\n          </div>\n          <div class=\"mb-3 form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"exampleCheck1\">\n            <label class=\"form-check-label\" for=\"exampleCheck1\">Check me out</label>\n          </div>\n          <fieldset class=\"mb-3\">\n            <legend>Radios buttons</legend>\n            <div class=\"form-check\">\n              <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"exampleRadio1\">\n              <label class=\"form-check-label\" for=\"exampleRadio1\">Default radio</label>\n            </div>\n            <div class=\"mb-3 form-check\">\n              <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"exampleRadio2\">\n              <label class=\"form-check-label\" for=\"exampleRadio2\">Another radio</label>\n            </div>\n          </fieldset>\n          <div class=\"mb-3\">\n            <label class=\"form-label\" for=\"customFile\">Upload</label>\n            <input type=\"file\" class=\"form-control\" id=\"customFile\">\n          </div>\n          <div class=\"mb-3 form-check form-switch\">\n            <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"flexSwitchCheckChecked\" checked>\n            <label class=\"form-check-label\" for=\"flexSwitchCheckChecked\">Checked switch checkbox input</label>\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"customRange3\" class=\"form-label\">Example range</label>\n            <input type=\"range\" class=\"form-range\" min=\"0\" max=\"5\" step=\"0.5\" id=\"customRange3\">\n          </div>\n          <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"disabled-forms\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Disabled forms</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/overview\" >}}#disabled-forms\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form>\n          <fieldset disabled aria-label=\"Disabled fieldset example\">\n            <div class=\"mb-3\">\n              <label for=\"disabledTextInput\" class=\"form-label\">Disabled input</label>\n              <input type=\"text\" id=\"disabledTextInput\" class=\"form-control\" placeholder=\"Disabled input\">\n            </div>\n            <div class=\"mb-3\">\n              <label for=\"disabledSelect\" class=\"form-label\">Disabled select menu</label>\n              <select id=\"disabledSelect\" class=\"form-select\">\n                <option>Disabled select</option>\n              </select>\n            </div>\n            <div class=\"mb-3\">\n              <div class=\"form-check\">\n                <input class=\"form-check-input\" type=\"checkbox\" id=\"disabledFieldsetCheck\" disabled>\n                <label class=\"form-check-label\" for=\"disabledFieldsetCheck\">\n                  Can't check this\n                </label>\n              </div>\n            </div>\n            <fieldset class=\"mb-3\">\n              <legend>Disabled radios buttons</legend>\n              <div class=\"form-check\">\n                <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"disabledRadio1\" disabled>\n                <label class=\"form-check-label\" for=\"disabledRadio1\">Disabled radio</label>\n              </div>\n              <div class=\"mb-3 form-check\">\n                <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"disabledRadio2\" disabled>\n                <label class=\"form-check-label\" for=\"disabledRadio2\">Another radio</label>\n              </div>\n            </fieldset>\n            <div class=\"mb-3\">\n              <label class=\"form-label\" for=\"disabledCustomFile\">Upload</label>\n              <input type=\"file\" class=\"form-control\" id=\"disabledCustomFile\" disabled>\n            </div>\n            <div class=\"mb-3 form-check form-switch\">\n              <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"disabledSwitchCheckChecked\" checked disabled>\n              <label class=\"form-check-label\" for=\"disabledSwitchCheckChecked\">Disabled checked switch checkbox input</label>\n            </div>\n            <div class=\"mb-3\">\n              <label for=\"disabledRange\" class=\"form-label\">Disabled range</label>\n              <input type=\"range\" class=\"form-range\" min=\"0\" max=\"5\" step=\"0.5\" id=\"disabledRange\">\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n          </fieldset>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"sizing\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Sizing</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/form-control\" >}}#sizing\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"mb-3\">\n          <input class=\"form-control form-control-lg\" type=\"text\" placeholder=\".form-control-lg\" aria-label=\".form-control-lg example\">\n        </div>\n        <div class=\"mb-3\">\n          <select class=\"form-select form-select-lg\" aria-label=\".form-select-lg example\">\n            <option selected>Open this select menu</option>\n            <option value=\"1\">One</option>\n            <option value=\"2\">Two</option>\n            <option value=\"3\">Three</option>\n          </select>\n        </div>\n        <div class=\"mb-3\">\n          <input type=\"file\" class=\"form-control form-control-lg\" aria-label=\"Large file input example\">\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"mb-3\">\n          <input class=\"form-control form-control-sm\" type=\"text\" placeholder=\".form-control-sm\" aria-label=\".form-control-sm example\">\n        </div>\n        <div class=\"mb-3\">\n          <select class=\"form-select form-select-sm\" aria-label=\".form-select-sm example\">\n            <option selected>Open this select menu</option>\n            <option value=\"1\">One</option>\n            <option value=\"2\">Two</option>\n            <option value=\"3\">Three</option>\n          </select>\n        </div>\n        <div class=\"mb-3\">\n          <input type=\"file\" class=\"form-control form-control-sm\" aria-label=\"Small file input example\">\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"input-group\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Input group</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/input-group\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"input-group mb-3\">\n          <span class=\"input-group-text\" id=\"basic-addon1\">@</span>\n          <input type=\"text\" class=\"form-control\" placeholder=\"Username\" aria-label=\"Username\" aria-describedby=\"basic-addon1\">\n        </div>\n        <div class=\"input-group mb-3\">\n          <input type=\"text\" class=\"form-control\" placeholder=\"Recipient's username\" aria-label=\"Recipient's username\" aria-describedby=\"basic-addon2\">\n          <span class=\"input-group-text\" id=\"basic-addon2\">@example.com</span>\n        </div>\n        <label for=\"basic-url\" class=\"form-label\">Your vanity URL</label>\n        <div class=\"input-group mb-3\">\n          <span class=\"input-group-text\" id=\"basic-addon3\">https://example.com/users/</span>\n          <input type=\"text\" class=\"form-control\" id=\"basic-url\" aria-describedby=\"basic-addon3\">\n        </div>\n        <div class=\"input-group mb-3\">\n          <span class=\"input-group-text\">$</span>\n          <input type=\"text\" class=\"form-control\" aria-label=\"Amount (to the nearest dollar)\">\n          <span class=\"input-group-text\">.00</span>\n        </div>\n        <div class=\"input-group\">\n          <span class=\"input-group-text\">With textarea</span>\n          <textarea class=\"form-control\" aria-label=\"With textarea\"></textarea>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"floating-labels\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Floating labels</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/floating-labels\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form>\n          <div class=\"form-floating mb-3\">\n            <input type=\"email\" class=\"form-control\" id=\"floatingInput\" placeholder=\"name@example.com\">\n            <label for=\"floatingInput\">Email address</label>\n          </div>\n          <div class=\"form-floating\">\n            <input type=\"password\" class=\"form-control\" id=\"floatingPassword\" placeholder=\"Password\">\n            <label for=\"floatingPassword\">Password</label>\n          </div>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"validation\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Validation</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/validation\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form class=\"row g-3\">\n          <div class=\"col-md-4\">\n            <label for=\"validationServer01\" class=\"form-label\">First name</label>\n            <input type=\"text\" class=\"form-control is-valid\" id=\"validationServer01\" value=\"Mark\" required>\n            <div class=\"valid-feedback\">\n              Looks good!\n            </div>\n          </div>\n          <div class=\"col-md-4\">\n            <label for=\"validationServer02\" class=\"form-label\">Last name</label>\n            <input type=\"text\" class=\"form-control is-valid\" id=\"validationServer02\" value=\"Otto\" required>\n            <div class=\"valid-feedback\">\n              Looks good!\n            </div>\n          </div>\n          <div class=\"col-md-4\">\n            <label for=\"validationServerUsername\" class=\"form-label\">Username</label>\n            <div class=\"input-group has-validation\">\n              <span class=\"input-group-text\" id=\"inputGroupPrepend3\">@</span>\n              <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServerUsername\" aria-describedby=\"inputGroupPrepend3\" required>\n              <div class=\"invalid-feedback\">\n                Please choose a username.\n              </div>\n            </div>\n          </div>\n          <div class=\"col-md-6\">\n            <label for=\"validationServer03\" class=\"form-label\">City</label>\n            <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServer03\" required>\n            <div class=\"invalid-feedback\">\n              Please provide a valid city.\n            </div>\n          </div>\n          <div class=\"col-md-3\">\n            <label for=\"validationServer04\" class=\"form-label\">State</label>\n            <select class=\"form-select is-invalid\" id=\"validationServer04\" required>\n              <option selected disabled value=\"\">Choose...</option>\n              <option>...</option>\n            </select>\n            <div class=\"invalid-feedback\">\n              Please select a valid state.\n            </div>\n          </div>\n          <div class=\"col-md-3\">\n            <label for=\"validationServer05\" class=\"form-label\">Zip</label>\n            <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServer05\" required>\n            <div class=\"invalid-feedback\">\n              Please provide a valid zip.\n            </div>\n          </div>\n          <div class=\"col-12\">\n            <div class=\"form-check\">\n              <input class=\"form-check-input is-invalid\" type=\"checkbox\" value=\"\" id=\"invalidCheck3\" required>\n              <label class=\"form-check-label\" for=\"invalidCheck3\">\n                Agree to terms and conditions\n              </label>\n              <div class=\"invalid-feedback\">\n                You must agree before submitting.\n              </div>\n            </div>\n          </div>\n          <div class=\"col-12\">\n            <button class=\"btn btn-primary\" type=\"submit\">Submit form</button>\n          </div>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n  </section>\n\n  <section id=\"components\">\n    <h2 class=\"sticky-xl-top fw-bold pt-3 pt-xl-5 pb-2 pb-xl-3\">Components</h2>\n\n    <article class=\"my-3\" id=\"accordion\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Accordion</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/accordion\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"accordion\" id=\"accordionExample\">\n          <div class=\"accordion-item\">\n            <h4 class=\"accordion-header\" id=\"headingOne\">\n              <button class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">\n                Accordion Item #1\n              </button>\n            </h4>\n            <div id=\"collapseOne\" class=\"accordion-collapse collapse show\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionExample\">\n              <div class=\"accordion-body\">\n                <strong>This is the first item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n              </div>\n            </div>\n          </div>\n          <div class=\"accordion-item\">\n            <h4 class=\"accordion-header\" id=\"headingTwo\">\n              <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\">\n                Accordion Item #2\n              </button>\n            </h4>\n            <div id=\"collapseTwo\" class=\"accordion-collapse collapse\" aria-labelledby=\"headingTwo\" data-bs-parent=\"#accordionExample\">\n              <div class=\"accordion-body\">\n                <strong>This is the second item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n              </div>\n            </div>\n          </div>\n          <div class=\"accordion-item\">\n            <h4 class=\"accordion-header\" id=\"headingThree\">\n              <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseThree\" aria-expanded=\"false\" aria-controls=\"collapseThree\">\n                Accordion Item #3\n              </button>\n            </h4>\n            <div id=\"collapseThree\" class=\"accordion-collapse collapse\" aria-labelledby=\"headingThree\" data-bs-parent=\"#accordionExample\">\n              <div class=\"accordion-body\">\n                <strong>This is the third item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.\n              </div>\n            </div>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"alerts\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Alerts</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/alerts\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< alerts.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <div class=\"alert alert-{{ .name }} alert-dismissible fade show\" role=\"alert\">\n          A simple {{ .name }} alert with <a href=\"#\" class=\"alert-link\">an example link</a>. Give it a click if you like.\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n        </div>{{ end -}}\n        {{< /alerts.inline >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"alert alert-success\" role=\"alert\">\n          <h4 class=\"alert-heading\">Well done!</h4>\n          <p>Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.</p>\n          <hr>\n          <p class=\"mb-0\">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"badge\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Badge</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/badge\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <p class=\"h1\">Example heading <span class=\"badge bg-primary\">New</span></p>\n        <p class=\"h2\">Example heading <span class=\"badge bg-secondary\">New</span></p>\n        <p class=\"h3\">Example heading <span class=\"badge bg-success\">New</span></p>\n        <p class=\"h4\">Example heading <span class=\"badge bg-danger\">New</span></p>\n        <p class=\"h5\">Example heading <span class=\"badge text-bg-warning\">New</span></p>\n        <p class=\"h6\">Example heading <span class=\"badge text-bg-info\">New</span></p>\n        <p class=\"h6\">Example heading <span class=\"badge text-bg-light\">New</span></p>\n        <p class=\"h6\">Example heading <span class=\"badge bg-dark\">New</span></p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< badge.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <span class=\"badge rounded-pill {{ if or (eq .name \"light\") (eq .name \"warning\") (eq .name \"info\") }}text-{{ end }}bg-{{ .name }}\">{{ .name | title }}</span>{{- end -}}\n        {{< /badge.inline >}}\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"breadcrumb\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Breadcrumb</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/breadcrumb\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"breadcrumb\">\n          <ol class=\"breadcrumb\">\n            <li class=\"breadcrumb-item\"><a href=\"#\">Home</a></li>\n            <li class=\"breadcrumb-item\"><a href=\"#\">Library</a></li>\n            <li class=\"breadcrumb-item active\" aria-current=\"page\">Data</li>\n          </ol>\n        </nav>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"buttons\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Buttons</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/buttons\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< buttons.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <button type=\"button\" class=\"btn btn-{{ .name }}\">{{ .name | title }}</button>\n        {{- end -}}\n        {{< /buttons.inline >}}\n\n        <button type=\"button\" class=\"btn btn-link\">Link</button>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< buttons.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <button type=\"button\" class=\"btn btn-outline-{{ .name }}\">{{ .name | title }}</button>\n        {{- end -}}\n        {{< /buttons.inline >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <button type=\"button\" class=\"btn btn-primary btn-sm\">Small button</button>\n        <button type=\"button\" class=\"btn btn-primary\">Standard button</button>\n        <button type=\"button\" class=\"btn btn-primary btn-lg\">Large button</button>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"button-group\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Button group</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/button-group\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-toolbar\" role=\"toolbar\" aria-label=\"Toolbar with button groups\">\n          <div class=\"btn-group me-2\" role=\"group\" aria-label=\"First group\">\n            <button type=\"button\" class=\"btn btn-secondary\">1</button>\n            <button type=\"button\" class=\"btn btn-secondary\">2</button>\n            <button type=\"button\" class=\"btn btn-secondary\">3</button>\n            <button type=\"button\" class=\"btn btn-secondary\">4</button>\n          </div>\n          <div class=\"btn-group me-2\" role=\"group\" aria-label=\"Second group\">\n            <button type=\"button\" class=\"btn btn-secondary\">5</button>\n            <button type=\"button\" class=\"btn btn-secondary\">6</button>\n            <button type=\"button\" class=\"btn btn-secondary\">7</button>\n          </div>\n          <div class=\"btn-group\" role=\"group\" aria-label=\"Third group\">\n            <button type=\"button\" class=\"btn btn-secondary\">8</button>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"card\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Card</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/card\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"row  row-cols-1 row-cols-md-2 g-4\">\n          <div class=\"col\">\n            <div class=\"card\">\n              {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"Image cap\" >}}\n              <div class=\"card-body\">\n                <h5 class=\"card-title\">Card title</h5>\n                <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n                <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n              </div>\n            </div>\n          </div>\n          <div class=\"col\">\n            <div class=\"card\">\n              <div class=\"card-header\">\n                Featured\n              </div>\n              <div class=\"card-body\">\n                <h5 class=\"card-title\">Card title</h5>\n                <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n                <a href=\"#\" class=\"btn btn-primary\">Go somewhere</a>\n              </div>\n              <div class=\"card-footer text-muted\">\n                2 days ago\n              </div>\n            </div>\n          </div>\n          <div class=\"col\">\n            <div class=\"card\">\n              <div class=\"card-body\">\n                <h5 class=\"card-title\">Card title</h5>\n                <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n              </div>\n              <ul class=\"list-group list-group-flush\">\n                <li class=\"list-group-item\">An item</li>\n                <li class=\"list-group-item\">A second item</li>\n                <li class=\"list-group-item\">A third item</li>\n              </ul>\n              <div class=\"card-body\">\n                <a href=\"#\" class=\"card-link\">Card link</a>\n                <a href=\"#\" class=\"card-link\">Another link</a>\n              </div>\n            </div>\n          </div>\n          <div class=\"col\">\n            <div class=\"card\">\n              <div class=\"row g-0\">\n                <div class=\"col-md-4\">\n                  {{< placeholder width=\"100%\" height=\"250\" text=\"Image\" >}}\n                </div>\n                <div class=\"col-md-8\">\n                  <div class=\"card-body\">\n                    <h5 class=\"card-title\">Card title</h5>\n                    <p class=\"card-text\">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n                    <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"carousel\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Carousel</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/carousel\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div id=\"carouselExampleCaptions\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n          <div class=\"carousel-indicators\">\n            <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"Slide 1\"></button>\n            <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"1\" aria-label=\"Slide 2\"></button>\n            <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"2\" aria-label=\"Slide 3\"></button>\n          </div>\n          <div class=\"carousel-inner\">\n            <div class=\"carousel-item active\">\n              {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"First slide\" >}}\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>First slide label</h5>\n                <p>Some representative placeholder content for the first slide.</p>\n              </div>\n            </div>\n            <div class=\"carousel-item\">\n              {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"Second slide\" >}}\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>Second slide label</h5>\n                <p>Some representative placeholder content for the second slide.</p>\n              </div>\n            </div>\n            <div class=\"carousel-item\">\n              {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"Third slide\" >}}\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>Third slide label</h5>\n                <p>Some representative placeholder content for the third slide.</p>\n              </div>\n            </div>\n          </div>\n          <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleCaptions\"  data-bs-slide=\"prev\">\n            <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n            <span class=\"visually-hidden\">Previous</span>\n          </button>\n          <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleCaptions\"  data-bs-slide=\"next\">\n            <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n            <span class=\"visually-hidden\">Next</span>\n          </button>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"dropdowns\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Dropdowns</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/dropdowns\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group w-100 align-items-center justify-content-between flex-wrap\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary btn-sm dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropdown button\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropdown button\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary btn-lg dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropdown button\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-primary\">Primary</button>\n          <button type=\"button\" class=\"btn btn-primary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">Toggle Dropdown</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-secondary\">Secondary</button>\n          <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">Toggle Dropdown</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-success\">Success</button>\n          <button type=\"button\" class=\"btn btn-success dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">Toggle Dropdown</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-info\">Info</button>\n          <button type=\"button\" class=\"btn btn-info dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">Toggle Dropdown</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-warning\">Warning</button>\n          <button type=\"button\" class=\"btn btn-warning dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">Toggle Dropdown</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-danger\">Danger</button>\n          <button type=\"button\" class=\"btn btn-danger dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">Toggle Dropdown</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group w-100 align-items-center justify-content-between flex-wrap\">\n          <div class=\"dropend\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropend button\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n          <div class=\"dropup\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropup button\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n          <div class=\"dropstart\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropstart button\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              End-aligned menu\n            </button>\n            <ul class=\"dropdown-menu dropdown-menu-end\">\n              <li><h6 class=\"dropdown-header\">Dropdown header</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"list-group\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>List group</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/list-group\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-group\">\n          <li class=\"list-group-item disabled\" aria-disabled=\"true\">A disabled item</li>\n          <li class=\"list-group-item\">A second item</li>\n          <li class=\"list-group-item\">A third item</li>\n          <li class=\"list-group-item\">A fourth item</li>\n          <li class=\"list-group-item\">And a fifth one</li>\n        </ul>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-group list-group-flush\">\n          <li class=\"list-group-item\">An item</li>\n          <li class=\"list-group-item\">A second item</li>\n          <li class=\"list-group-item\">A third item</li>\n          <li class=\"list-group-item\">A fourth item</li>\n          <li class=\"list-group-item\">And a fifth one</li>\n        </ul>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"list-group\">\n          <a href=\"#\" class=\"list-group-item list-group-item-action\">A simple default list group item</a>\n          {{< list.inline >}}\n          {{- range (index $.Site.Data \"theme-colors\") }}\n          <a href=\"#\" class=\"list-group-item list-group-item-action list-group-item-{{ .name }}\">A simple {{ .name }} list group item</a>\n          {{- end -}}\n          {{< /list.inline >}}\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"modal\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Modal</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/modal\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"d-flex justify-content-between flex-wrap\">\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalDefault\">\n            Launch demo modal\n          </button>\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#staticBackdropLive\">\n            Launch static backdrop modal\n          </button>\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalCenteredScrollable\">\n            Vertically centered scrollable modal\n          </button>\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreen\">\n            Full screen\n          </button>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"navs\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Navs</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/navs-tabs\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav class=\"nav\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n          <a class=\"nav-link\" href=\"#\">Link</a>\n          <a class=\"nav-link\" href=\"#\">Link</a>\n          <a class=\"nav-link disabled\">Disabled</a>\n        </nav>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <nav>\n          <div class=\"nav nav-tabs mb-3\" id=\"nav-tab\" role=\"tablist\">\n            <button class=\"nav-link active\" id=\"nav-home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-home\" type=\"button\" role=\"tab\" aria-controls=\"nav-home\" aria-selected=\"true\">Home</button>\n            <button class=\"nav-link\" id=\"nav-profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-profile\" type=\"button\" role=\"tab\" aria-controls=\"nav-profile\" aria-selected=\"false\">Profile</button>\n            <button class=\"nav-link\" id=\"nav-contact-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-contact\" type=\"button\" role=\"tab\" aria-controls=\"nav-contact\" aria-selected=\"false\">Contact</button>\n          </div>\n        </nav>\n        <div class=\"tab-content\" id=\"nav-tabContent\">\n          <div class=\"tab-pane fade show active\" id=\"nav-home\" role=\"tabpanel\" aria-labelledby=\"nav-home-tab\">\n            <p>This is some placeholder content the <strong>Home tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n          </div>\n          <div class=\"tab-pane fade\" id=\"nav-profile\" role=\"tabpanel\" aria-labelledby=\"nav-profile-tab\">\n            <p>This is some placeholder content the <strong>Profile tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n          </div>\n          <div class=\"tab-pane fade\" id=\"nav-contact\" role=\"tabpanel\" aria-labelledby=\"nav-contact-tab\">\n            <p>This is some placeholder content the <strong>Contact tab's</strong> associated content. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling. You can use it with tabs, pills, and any other <code>.nav</code>-powered navigation.</p>\n          </div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"nav nav-pills\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Active</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n        </ul>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"navbar\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Navbar</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/navbar\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav class=\"navbar navbar-expand-lg bg-light\">\n          <div class=\"container-fluid\">\n            <a class=\"navbar-brand\" href=\"#\">\n              <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" width=\"38\" height=\"30\" class=\"d-inline-block align-top\" alt=\"Bootstrap\" loading=\"lazy\"\n                   style=\"filter: invert(1) grayscale(100%) brightness(200%);\">\n            </a>\n            <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n              <span class=\"navbar-toggler-icon\"></span>\n            </button>\n            <div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\">\n              <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n                <li class=\"nav-item\">\n                  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link\" href=\"#\">Link</a>\n                </li>\n                <li class=\"nav-item dropdown\">\n                  <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                    Dropdown\n                  </a>\n                  <ul class=\"dropdown-menu\">\n                    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                    <li><hr class=\"dropdown-divider\"></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n                  </ul>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link disabled\">Disabled</a>\n                </li>\n              </ul>\n              <form class=\"d-flex\" role=\"search\">\n                <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n                <button class=\"btn btn-outline-dark\" type=\"submit\">Search</button>\n              </form>\n            </div>\n          </div>\n        </nav>\n\n        <nav class=\"navbar navbar-expand-lg navbar-dark bg-primary mt-5\">\n          <div class=\"container-fluid\">\n            <a class=\"navbar-brand\" href=\"#\">\n              <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" width=\"38\" height=\"30\" class=\"d-inline-block align-top\" alt=\"Bootstrap\" loading=\"lazy\">\n            </a>\n            <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent2\" aria-controls=\"navbarSupportedContent2\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n              <span class=\"navbar-toggler-icon\"></span>\n            </button>\n            <div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent2\">\n              <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n                <li class=\"nav-item\">\n                  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link\" href=\"#\">Link</a>\n                </li>\n                <li class=\"nav-item dropdown\">\n                  <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                    Dropdown\n                  </a>\n                  <ul class=\"dropdown-menu\">\n                    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                    <li><hr class=\"dropdown-divider\"></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n                  </ul>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link disabled\">Disabled</a>\n                </li>\n              </ul>\n              <form class=\"d-flex\" role=\"search\">\n                <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n                <button class=\"btn btn-outline-light\" type=\"submit\">Search</button>\n              </form>\n            </div>\n          </div>\n        </nav>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"pagination\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Pagination</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/pagination\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"Pagination example\">\n          <ul class=\"pagination pagination-sm\">\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n            <li class=\"page-item active\" aria-current=\"page\">\n              <a class=\"page-link\" href=\"#\">2</a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n          </ul>\n        </nav>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"Standard pagination example\">\n          <ul class=\"pagination\">\n            <li class=\"page-item\">\n              <a class=\"page-link\" href=\"#\" aria-label=\"Previous\">\n                <span aria-hidden=\"true\">&laquo;</span>\n              </a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n            <li class=\"page-item\">\n              <a class=\"page-link\" href=\"#\" aria-label=\"Next\">\n                <span aria-hidden=\"true\">&raquo;</span>\n              </a>\n            </li>\n          </ul>\n        </nav>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"Another pagination example\">\n          <ul class=\"pagination pagination-lg flex-wrap\">\n            <li class=\"page-item disabled\">\n              <a class=\"page-link\">Previous</a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n            <li class=\"page-item active\" aria-current=\"page\">\n              <a class=\"page-link\" href=\"#\">2</a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n            <li class=\"page-item\">\n              <a class=\"page-link\" href=\"#\">Next</a>\n            </li>\n          </ul>\n        </nav>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"popovers\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Popovers</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/popovers\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <button type=\"button\" class=\"btn btn-lg btn-danger\" data-bs-toggle=\"popover\" title=\"Popover title\" data-bs-content=\"And here's some amazing content. It's very engaging. Right?\">Click to toggle popover</button>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"top\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n          Popover on top\n        </button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"right\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n          Popover on end\n        </button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"bottom\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n          Popover on bottom\n        </button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"left\" data-bs-content=\"Vivamus sagittis lacus vel augue laoreet rutrum faucibus.\">\n          Popover on start\n        </button>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"progress\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Progress</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/progress\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Example with label\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\">0%</div>\n        </div>\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar bg-success w-25\" role=\"progressbar\" aria-label=\"Success example with label\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\">25%</div>\n        </div>\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar text-bg-info w-50\" role=\"progressbar\" aria-label=\"Info example with label\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\">50%</div>\n        </div>\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar text-bg-warning w-75\" role=\"progressbar\" aria-label=\"Warning example with label\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\">75%</div>\n        </div>\n        <div class=\"progress\">\n          <div class=\"progress-bar bg-danger w-100\" role=\"progressbar\" aria-label=\"Danger example with label\" aria-valuenow=\"100\" aria-valuemin=\"0\" aria-valuemax=\"100\">100%</div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"progress\">\n          <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Segment one - default example\" style=\"width: 15%\" aria-valuenow=\"15\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n          <div class=\"progress-bar progress-bar-striped progress-bar-animated bg-success\" role=\"progressbar\" aria-label=\"Segment two - animated striped success example\" style=\"width: 40%\" aria-valuenow=\"40\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"scrollspy\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Scrollspy</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/scrollspy\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        <div class=\"bd-example\">\n          <nav id=\"navbar-example2\" class=\"navbar bg-light px-3\">\n            <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n            <ul class=\"nav nav-pills\">\n              <li class=\"nav-item\">\n                <a class=\"nav-link active\" href=\"#scrollspyHeading1\">First</a>\n              </li>\n              <li class=\"nav-item\">\n                <a class=\"nav-link\" href=\"#scrollspyHeading2\">Second</a>\n              </li>\n              <li class=\"nav-item dropdown\">\n                <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\" role=\"button\" aria-expanded=\"false\">Dropdown</a>\n                <ul class=\"dropdown-menu\">\n                  <li><a class=\"dropdown-item\" href=\"#scrollspyHeading3\">Third</a></li>\n                  <li><a class=\"dropdown-item\" href=\"#scrollspyHeading4\">Fourth</a></li>\n                  <li><hr class=\"dropdown-divider\"></li>\n                  <li><a class=\"dropdown-item\" href=\"#scrollspyHeading5\">Fifth</a></li>\n                </ul>\n              </li>\n            </ul>\n          </nav>\n        <div data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example2\" data-bs-offset=\"0\" class=\"scrollspy-example position-relative mt-2 overflow-auto\" tabindex=\"0\">\n            <h4 id=\"scrollspyHeading1\">First heading</h4>\n            <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n            <h4 id=\"scrollspyHeading2\">Second heading</h4>\n            <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n            <h4 id=\"scrollspyHeading3\">Third heading</h4>\n            <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n            <h4 id=\"scrollspyHeading4\">Fourth heading</h4>\n            <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n            <h4 id=\"scrollspyHeading5\">Fifth heading</h4>\n            <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>\n          </div>\n        </div>\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"spinners\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Spinners</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/spinners\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< spinner.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <div class=\"spinner-border text-{{ .name }}\" role=\"status\">\n          <span class=\"visually-hidden\">Loading...</span>\n        </div>\n        {{- end -}}\n        {{< /spinner.inline >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< spinner.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <div class=\"spinner-grow text-{{ .name }}\" role=\"status\">\n          <span class=\"visually-hidden\">Loading...</span>\n        </div>\n        {{- end -}}\n        {{< /spinner.inline >}}\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"toasts\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Toasts</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/toasts\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" class=\"bg-dark p-5 align-items-center\" >}}\n        <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n          <div class=\"toast-header\">\n            {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n            <strong class=\"me-auto\">Bootstrap</strong>\n            <small class=\"text-muted\">11 mins ago</small>\n            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n          </div>\n          <div class=\"toast-body\">\n            Hello, world! This is a toast message.\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"mt-3 mb-5 pb-5\" id=\"tooltips\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>Tooltips</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/components/tooltips\" >}}\">Documentation</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" class=\"tooltip-demo\" >}}\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"Tooltip on top\">Tooltip on top</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\" title=\"Tooltip on end\">Tooltip on end</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" title=\"Tooltip on bottom\">Tooltip on bottom</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" title=\"Tooltip on start\">Tooltip on start</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-html=\"true\" title=\"<em>Tooltip</em> <u>with</u> <b>HTML</b>\">Tooltip with HTML</button>\n        {{< /example >}}\n      </div>\n    </article>\n  </section>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalDefault\" tabindex=\"-1\" aria-labelledby=\"exampleModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"staticBackdropLive\" data-bs-backdrop=\"static\" data-bs-keyboard=\"false\" tabindex=\"-1\" aria-labelledby=\"staticBackdropLiveLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"staticBackdropLiveLabel\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>I will not close if you click outside me. Don't even try to press escape key.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Understood</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"exampleModalCenteredScrollable\" tabindex=\"-1\" aria-labelledby=\"exampleModalCenteredScrollableTitle\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-dialog-centered modal-dialog-scrollable\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalCenteredScrollableTitle\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>This is some placeholder content to show the scrolling behavior for modals. We use repeated line breaks to demonstrate how content can exceed minimum inner height, thereby showing inner scrolling. When content becomes longer than the predefined max-height of modal, content will be cropped and scrollable within the modal.</p>\n        <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>\n        <p>This content should appear at the bottom after you scroll.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n        <button type=\"button\" class=\"btn btn-primary\">Save changes</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"exampleModalFullscreen\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenLabel\">Full screen modal</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cheatsheet-rtl/index.html",
    "content": "---\nlayout: examples\ntitle: ورقة الغش\nextra_css:\n  - \"../cheatsheet/cheatsheet.rtl.css\"\nextra_js:\n  - src: \"../cheatsheet/cheatsheet.js\"\nbody_class: \"bg-light\"\ndirection: rtl\n---\n\n<header class=\"bd-header bg-dark py-3 d-flex align-items-stretch border-bottom border-dark\">\n  <div class=\"container-fluid d-flex align-items-center\">\n    <h1 class=\"d-flex align-items-center fs-4 text-white mb-0\">\n      <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" width=\"38\" height=\"30\" class=\"me-3\" alt=\"Bootstrap\">\n      ورقة الغش\n    </h1>\n    <a href=\"{{< docsref \"/examples/cheatsheet\" >}}\" class=\"ms-auto link-light\" hreflang=\"en\">جدول بيانات LTR</a>\n  </div>\n</header>\n<aside class=\"bd-aside sticky-xl-top text-muted align-self-start mb-3 mb-xl-5 px-2\">\n  <h2 class=\"h6 pt-4 pb-3 mb-4 border-bottom\">على هذه الصفحة</h2>\n  <nav class=\"small\" id=\"toc\">\n    <ul class=\"list-unstyled\">\n      <li class=\"my-2\">\n        <button class=\"btn d-inline-flex align-items-center collapsed border-0\" data-bs-toggle=\"collapse\" aria-expanded=\"false\" data-bs-target=\"#contents-collapse\" aria-controls=\"contents-collapse\">المحتوى</button>\n        <ul class=\"list-unstyled ps-3 collapse\" id=\"contents-collapse\">\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#typography\">النصوص</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#images\">الصور</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#tables\">الجداول</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#figures\">النماذج البيانية</a></li>\n        </ul>\n      </li>\n      <li class=\"my-2\">\n        <button class=\"btn d-inline-flex align-items-center collapsed border-0\" data-bs-toggle=\"collapse\" aria-expanded=\"false\" data-bs-target=\"#forms-collapse\" aria-controls=\"forms-collapse\">النماذج</button>\n        <ul class=\"list-unstyled ps-3 collapse\" id=\"forms-collapse\">\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#overview\">نظرة عامة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#disabled-forms\">الحقول المعطلة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#sizing\">الأحجام</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#input-group\">مجموعة الإدخال</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#floating-labels\">الحقول ذوي العناوين العائمة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#validation\">التحقق</a></li>\n        </ul>\n      </li>\n      <li class=\"my-2\">\n        <button class=\"btn d-inline-flex align-items-center collapsed border-0\" data-bs-toggle=\"collapse\" aria-expanded=\"false\" data-bs-target=\"#components-collapse\" aria-controls=\"components-collapse\">مكونات</button>\n        <ul class=\"list-unstyled ps-3 collapse\" id=\"components-collapse\">\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#accordion\">المطوية</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#alerts\">الإنذارات</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#badge\">الشارة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#breadcrumb\">مسار التنقل التفصيلي</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#buttons\">الأزرار</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#button-group\">مجموعة الأزرار</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#card\">البطاقة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#carousel\">شرائح العرض</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#dropdowns\">القوائم المنسدلة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#list-group\">مجموعة العناصر</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#modal\">الصندوق العائم</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#navs\">التنقل</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#navbar\">شريط التنقل</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#pagination\">ترقيم الصفحات</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#popovers\">الصناديق المنبثقة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#progress\">شريط التقدم</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#scrollspy\">المخطوطة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#spinners\">الدوائر المتحركة</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#toasts\">الإشعارات</a></li>\n          <li><a class=\"d-inline-flex align-items-center rounded text-decoration-none\" href=\"#tooltips\">التلميحات</a></li>\n        </ul>\n      </li>\n    </ul>\n  </nav>\n</aside>\n<div class=\"bd-cheatsheet container-fluid bg-body\">\n  <section id=\"content\">\n    <h2 class=\"sticky-xl-top fw-bold pt-3 pt-xl-5 pb-2 pb-xl-3\">المحتوى</h2>\n\n    <article class=\"my-3\" id=\"typography\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>النصوص</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/content/typography\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <p class=\"display-1\">العرض 1</p>\n        <p class=\"display-2\">العرض 2</p>\n        <p class=\"display-3\">العرض 3</p>\n        <p class=\"display-4\">العرض 4</p>\n        <p class=\"display-5\">العرض 5</p>\n        <p class=\"display-6\">العرض 6</p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <p class=\"h1\">عنوان 1</p>\n        <p class=\"h2\">عنوان 2</p>\n        <p class=\"h3\">عنوان 3</p>\n        <p class=\"h4\">عنوان 4</p>\n        <p class=\"h5\">عنوان 5</p>\n        <p class=\"h6\">عنوان 6</p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <p class=\"lead\">\n          هذه قطعة إملائية متميزة، فهي مصممة لتكون بارزة من بين القطع الإملائية الأخرى.\n        </p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <p>يمكنك استخدام تصنيف mark <mark>لتحديد</mark> نص.</p>\n        <p><del>من المفترض أن يتم التعامل مع هذا السطر كنص محذوف.</del></p>\n        <p><s>من المفترض أن يتم التعامل مع هذا السطر على أنه لم يعد دقيقًا.</s></p>\n        <p><ins>من المفترض أن يتم التعامل مع هذا السطر كإضافة إلى المستند.</ins></p>\n        <p><u>سيتم عرض النص في هذا السطر كما وتحته خط.</u></p>\n        <p><small>من المفترض أن يتم التعامل مع هذا السطر على أنه يحوي تفاصيل صغيرة.</small></p>\n        <p><strong>هذا السطر يحوي نص عريض.</strong></p>\n        <p><em>هذا السطر يحوي نص مائل.</em></p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <hr>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <blockquote class=\"blockquote\">\n          <p>إقتباس مبهر، موضوع في عنصر blockquote</p>\n          <footer class=\"blockquote-footer\">شخص مشهور في <cite title= \"عنوان المصدر\"> عنوان المصدر </cite></footer>\n        </blockquote>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-unstyled\">\n          <li>هذه قائمة عناصر.</li>\n          <li>بالرغم من أنها مصممة كي لا تظهر كذلك.</li>\n          <li>إلا أنها مجهزة كـ قائمة خلف الكواليس</li>\n          <li>هذا التصميم ينطبق فقد على القائمة الرئيسية</li>\n          <li>القوائم الفرعية\n            <ul>\n              <li>لا تتأثر بهذا التصميم</li>\n              <li>فهي تظهر عليها علامات الترقيم</li>\n              <li>وتحتوي على مساحة فارغة بجوارها</li>\n            </ul>\n          </li>\n          <li>قد يكون هذا التصميم مفيدًا في بعض الأحيان.</li>\n        </ul>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-inline\">\n          <li class=\"list-inline-item\">هذا عنصر في قائمة.</li>\n          <li class=\"list-inline-item\">وهذا أيضًا.</li>\n          <li class=\"list-inline-item\">لكنهم يظهرون متجاورين.</li>\n        </ul>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"images\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الصور</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/content/images\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< placeholder width=\"100%\" height=\"250\" class=\"bd-placeholder-img-lg img-fluid\" text=\"صورة مستجيبة\" >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< placeholder width=\"200\" height=\"200\" class=\"img-thumbnail\" title=\"صورة عنصر نائب مربع عام مع حدود بيضاء حولها ، مما يجعلها تشبه صورة تم التقاطها بكاميرا فورية قديمة\" >}}\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"tables\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الجداول</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/content/tables\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-striped\">\n          <thead>\n          <tr>\n            <th scope=\"col\">#</th>\n            <th scope=\"col\">الاسم الاول</th>\n            <th scope=\"col\">الكنية</th>\n            <th scope=\"col\">الاسم المستعار</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">1</th>\n            <td>Mark</td>\n            <td>Otto</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@mdo</bdo></td>\n          </tr>\n          <tr>\n            <th scope=\"row\">2</th>\n            <td>Jacob</td>\n            <td>Thornton</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@fat</bdo></td>\n          </tr>\n          <tr>\n            <th scope=\"row\">3</th>\n            <td colspan=\"2\">Larry the Bird</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@twitter</bdo></td>\n          </tr>\n          </tbody>\n        </table>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-dark table-borderless\">\n          <thead>\n          <tr>\n            <th scope=\"col\">#</th>\n            <th scope=\"col\">الاسم الاول</th>\n            <th scope=\"col\">الكنية</th>\n            <th scope=\"col\">الاسم المستعار</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">1</th>\n            <td>Mark</td>\n            <td>Otto</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@mdo</bdo></td>\n          </tr>\n          <tr>\n            <th scope=\"row\">2</th>\n            <td>Jacob</td>\n            <td>Thornton</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@fat</bdo></td>\n          </tr>\n          <tr>\n            <th scope=\"row\">3</th>\n            <td colspan=\"2\">Larry the Bird</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@twitter</bdo></td>\n          </tr>\n          </tbody>\n        </table>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-hover\">\n          <thead>\n          <tr>\n            <th scope=\"col\">Class</th>\n            <th scope=\"col\">عنوان</th>\n            <th scope=\"col\">عنوان</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">Default</th>\n            <td>خلية</td>\n            <td>خلية</td>\n          </tr>\n          {{< table.inline >}}\n          {{- range (index $.Site.Data \"theme-colors\") }}\n          <tr class=\"table-{{ .name }}\">\n            <th scope=\"row\">{{ .name | title }}</th>\n            <td>خلية</td>\n            <td>خلية</td>\n          </tr>\n          {{- end -}}\n          {{< /table.inline >}}\n          </tbody>\n        </table>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <table class=\"table table-sm table-bordered\">\n          <thead>\n          <tr>\n            <th scope=\"col\">#</th>\n            <th scope=\"col\">الاسم الاول</th>\n            <th scope=\"col\">الكنية</th>\n            <th scope=\"col\">الاسم المستعار</th>\n          </tr>\n          </thead>\n          <tbody>\n          <tr>\n            <th scope=\"row\">1</th>\n            <td>Mark</td>\n            <td>Otto</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@mdo</bdo></td>\n          </tr>\n          <tr>\n            <th scope=\"row\">2</th>\n            <td>Jacob</td>\n            <td>Thornton</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@fat</bdo></td>\n          </tr>\n          <tr>\n            <th scope=\"row\">3</th>\n            <td colspan=\"2\">Larry the Bird</td>\n            <td><bdo lang=\"en\" dir=\"ltr\">@twitter</bdo></td>\n          </tr>\n          </tbody>\n        </table>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"figures\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>النماذج البيانية</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/content/figures\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <figure class=\"figure\">\n          {{< placeholder width=\"400\" height=\"300\" class=\"figure-img img-fluid rounded\" >}}\n          <figcaption class=\"figure-caption\">شرح للصورة أعلاه.</figcaption>\n        </figure>\n        {{< /example >}}\n      </div>\n    </article>\n  </section>\n\n  <section id=\"forms\">\n    <h2 class=\"sticky-xl-top fw-bold pt-3 pt-xl-5 pb-2 pb-xl-3\">النماذج</h2>\n\n    <article class=\"my-3\" id=\"overview\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>نظرة عامة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/forms/overview\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form>\n          <div class=\"mb-3\">\n            <label for=\"exampleInputEmail1\" class=\"form-label\">البريد الإلكتروني</label>\n            <input type=\"email\" class=\"form-control\" id=\"exampleInputEmail1\" aria-describedby=\"emailHelp\">\n            <div id=\"emailHelp\" class=\"form-text\">لن نقوم بمشاركة بريدك الإلكتروني مع أي شخص آخر.</div>\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"exampleInputPassword1\" class=\"form-label\">كلمة السر</label>\n            <input type=\"password\" class=\"form-control\" id=\"exampleInputPassword1\">\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"exampleSelect\" class=\"form-label\">قائمة اختيار</label>\n            <select class=\"form-select\" id=\"exampleSelect\">\n              <option selected>افتح قائمة الاختيار هذه</option>\n              <option value=\"1\">واحد</option>\n              <option value=\"2\">اثنان</option>\n              <option value=\"3\">ثلاثة</option>\n            </select>\n          </div>\n          <div class=\"mb-3 form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"exampleCheck1\">\n            <label class=\"form-check-label\" for=\"exampleCheck1\">اخترني</label>\n          </div>\n          <fieldset class=\"mb-3\">\n            <legend>أزرار الاختيار الأحادي</legend>\n            <div class=\"form-check\">\n              <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"exampleRadio1\" checked>\n              <label class=\"form-check-label\" for=\"exampleRadio1\">الخيار الافتراضي</label>\n            </div>\n            <div class=\"mb-3 form-check\">\n              <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"exampleRadio2\">\n              <label class=\"form-check-label\" for=\"exampleRadio2\">خيار آخر</label>\n            </div>\n          </fieldset>\n          <div class=\"mb-3\">\n            <label class=\"form-label\" for=\"customFile\">رفع</label>\n            <input type=\"file\" class=\"form-control\" id=\"customFile\">\n          </div>\n          <div class=\"mb-3 form-check form-switch\">\n            <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"flexSwitchCheckChecked\" checked>\n            <label class=\"form-check-label\" for=\"flexSwitchCheckChecked\">زر على شكل مفتاح اختيار.</label>\n          </div>\n          <div class=\"mb-3\">\n            <label for=\"customRange3\" class=\"form-label\">مثال على حقل اختيار نطاقي</label>\n            <input type=\"range\" class=\"form-range\" min=\"0\" max=\"5\" step=\"0.5\" id=\"customRange3\">\n          </div>\n          <button type=\"submit\" class=\"btn btn-primary\">إرسال</button>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"disabled-forms\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الحقول المعطلة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/forms/overview\" >}}#disabled-forms\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form>\n          <fieldset disabled aria-label=\"مثال على مجموعة الحقول المعطلة\">\n            <div class=\"mb-3\">\n              <label for=\"disabledTextInput\" class=\"form-label\">حقل إدخال معطل</label>\n              <input type=\"text\" id=\"disabledTextInput\" class=\"form-control\" placeholder=\"حقل إدخال معطل\">\n            </div>\n            <div class=\"mb-3\">\n              <label for=\"disabledSelect\" class=\"form-label\">قائمة اختيار معطلة</label>\n              <select id=\"disabledSelect\" class=\"form-select\">\n                <option>خيار معطل</option>\n              </select>\n            </div>\n            <div class=\"mb-3\">\n              <div class=\"form-check\">\n                <input class=\"form-check-input\" type=\"checkbox\" id=\"disabledFieldsetCheck\" disabled>\n                <label class=\"form-check-label\" for=\"disabledFieldsetCheck\">\n                  زر اختيار معطل\n                </label>\n              </div>\n            </div>\n            <fieldset class=\"mb-3\">\n              <legend>أزرار اختيار أحادي معطلين</legend>\n              <div class=\"form-check\">\n                <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"disabledRadio1\" disabled>\n                <label class=\"form-check-label\" for=\"disabledRadio1\">خيار معطل</label>\n              </div>\n              <div class=\"mb-3 form-check\">\n                <input type=\"radio\" name=\"radios\" class=\"form-check-input\" id=\"disabledRadio2\" disabled>\n                <label class=\"form-check-label\" for=\"disabledRadio2\">خيار آخر معطل</label>\n              </div>\n            </fieldset>\n            <div class=\"mb-3\">\n              <label class=\"form-label\" for=\"disabledCustomFile\">رفع معطل</label>\n              <input type=\"file\" class=\"form-control\" id=\"disabledCustomFile\" disabled>\n            </div>\n            <div class=\"mb-3 form-check form-switch\">\n              <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"disabledSwitchCheckChecked\" checked disabled>\n              <label class=\"form-check-label\" for=\"disabledSwitchCheckChecked\">زر معطل على شكل مفتاح اختيار.</label>\n            </div>\n            <div class=\"mb-3\">\n              <label for=\"disabledRange\" class=\"form-label\">حقل اختيار نطاقي معطل</label>\n              <input type=\"range\" class=\"form-range\" min=\"0\" max=\"5\" step=\"0.5\" id=\"disabledRange\">\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary\">إرسال</button>\n          </fieldset>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"sizing\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الأحجام</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/forms/form-control\" >}}#sizing\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"mb-3\">\n          <input class=\"form-control form-control-lg\" type=\"text\" placeholder=\"حقل إدخال كبير\" aria-label=\".form-control-lg مثال\">\n        </div>\n        <div class=\"mb-3\">\n          <select class=\"form-select form-select-lg\" aria-label=\".form-select-lg مثال\">\n            <option selected>افتح قائمة الاختيار هذه</option>\n            <option value=\"1\">واحد</option>\n            <option value=\"2\">اثنان</option>\n            <option value=\"3\">ثلاثة</option>\n          </select>\n        </div>\n        <div class=\"mb-3\">\n          <input type=\"file\" class=\"form-control form-control-lg\" aria-label=\"مثال على إدخال ملف كبير\">\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"mb-3\">\n          <input class=\"form-control form-control-sm\" type=\"text\" placeholder=\"حقل إدخال صغير\" aria-label=\".form-control-sm مثال\">\n        </div>\n        <div class=\"mb-3\">\n          <select class=\"form-select form-select-sm\" aria-label=\".form-select-sm مثال\">\n            <option selected>افتح قائمة الاختيار هذه</option>\n            <option value=\"1\">واحد</option>\n            <option value=\"2\">اثنان</option>\n            <option value=\"3\">ثلاثة</option>\n          </select>\n        </div>\n        <div class=\"mb-3\">\n          <input type=\"file\" class=\"form-control form-control-sm\" aria-label=\"مثال على إدخال ملف صغير\">\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"input-group\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>مجموعة الإدخال</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/forms/input-group\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"input-group mb-3\">\n          <span class=\"input-group-text\" id=\"basic-addon1\">أنا اسمي</span>\n          <input type=\"text\" class=\"form-control\" placeholder=\"فلان الفلاني\" aria-label=\"الاسم\" aria-describedby=\"basic-addon1\">\n        </div>\n        <div class=\"input-group mb-3\">\n          <input type=\"text\" class=\"form-control\" placeholder=\"أنا أحب الكعك والقهوة\" aria-label=\"الطعام المفضل\" aria-describedby=\"basic-addon2\">\n          <span class=\"input-group-text\" id=\"basic-addon2\">وغيرها</span>\n        </div>\n        <label for=\"basic-url\" class=\"form-label\">عنوان حسابك الشخصي</label>\n        <div class=\"input-group mb-3\">\n          <input type=\"text\" class=\"form-control\" id=\"basic-url\" aria-describedby=\"basic-addon3\">\n          <span class=\"input-group-text\" id=\"basic-addon3\"><bdo lang=\"en\" dir=\"ltr\">https://example.com/users/</bdo></span>\n        </div>\n        <div class=\"input-group mb-3\">\n          <span class=\"input-group-text\"><bdo lang=\"en\" dir=\"ltr\">.00</bdo></span>\n          <input type=\"text\" class=\"form-control\" aria-label=\"المبلغ (لأقرب دولار)\">\n          <span class=\"input-group-text\">$</span>\n        </div>\n        <div class=\"input-group\">\n          <span class=\"input-group-text\">مع textarea</span>\n          <textarea class=\"form-control\" aria-label=\"مع textarea\"></textarea>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"floating-labels\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الحقول ذوي العناوين العائمة</h3>\n        <a class=\"d-flex align-items-center\" href=\"{{< docsref \"/forms/floating-labels\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form>\n          <div class=\"form-floating mb-3\">\n            <input type=\"email\" class=\"form-control\" id=\"floatingInput\" placeholder=\"name@example.com\">\n            <label for=\"floatingInput\">البريد الالكتروني</label>\n          </div>\n          <div class=\"form-floating\">\n            <input type=\"password\" class=\"form-control\" id=\"floatingPassword\" placeholder=\"كلمة السر\">\n            <label for=\"floatingPassword\">كلمة السر</label>\n          </div>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"validation\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>التحقق</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/forms/validation\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <form class=\"row g-3\">\n          <div class=\"col-md-4\">\n            <label for=\"validationServer01\" class=\"form-label\">الاسم الاول</label>\n            <input type=\"text\" class=\"form-control is-valid\" id=\"validationServer01\" value=\"Mark\" required>\n            <div class=\"valid-feedback\">\n              يبدو صحيحًا!\n            </div>\n          </div>\n          <div class=\"col-md-4\">\n            <label for=\"validationServer02\" class=\"form-label\">الكنية</label>\n            <input type=\"text\" class=\"form-control is-valid\" id=\"validationServer02\" value=\"Otto\" required>\n            <div class=\"valid-feedback\">\n              يبدو صحيحًا!\n            </div>\n          </div>\n          <div class=\"col-md-4\">\n            <label for=\"validationServerUsername\" class=\"form-label\">اسم المستخدم</label>\n            <div class=\"input-group has-validation\">\n              <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServerUsername\" aria-describedby=\"inputGroupPrepend3\" required>\n              <span class=\"input-group-text\" id=\"inputGroupPrepend3\">@</span>\n              <div class=\"invalid-feedback\">\n                يرجى اختيار اسم مستخدم.\n              </div>\n            </div>\n          </div>\n          <div class=\"col-md-6\">\n            <label for=\"validationServer03\" class=\"form-label\">مدينة</label>\n            <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServer03\" required>\n            <div class=\"invalid-feedback\">\n              يرجى إدخال مدينة صحيحة.\n            </div>\n          </div>\n          <div class=\"col-md-3\">\n            <label for=\"validationServer04\" class=\"form-label\">حالة</label>\n            <select class=\"form-select is-invalid\" id=\"validationServer04\" required>\n              <option selected disabled value=\"\">اختر...</option>\n              <option>...</option>\n            </select>\n            <div class=\"invalid-feedback\">\n              يرجى اختيار ولاية صحيحة.\n            </div>\n          </div>\n          <div class=\"col-md-3\">\n            <label for=\"validationServer05\" class=\"form-label\">الرمز البريدي</label>\n            <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServer05\" required>\n            <div class=\"invalid-feedback\">\n              يرجى إدخال رمز بريدي صحيح.\n            </div>\n          </div>\n          <div class=\"col-12\">\n            <div class=\"form-check\">\n              <input class=\"form-check-input is-invalid\" type=\"checkbox\" value=\"\" id=\"invalidCheck3\" required>\n              <label class=\"form-check-label\" for=\"invalidCheck3\">\n                أوافق على الشروط والأحكام\n              </label>\n              <div class=\"invalid-feedback\">\n                تجب الموافقة قبل إرسال النموذج.\n              </div>\n            </div>\n          </div>\n          <div class=\"col-12\">\n            <button class=\"btn btn-primary\" type=\"submit\">إرسال النموذج</button>\n          </div>\n        </form>\n        {{< /example >}}\n      </div>\n    </article>\n  </section>\n\n  <section id=\"components\">\n    <h2 class=\"sticky-xl-top fw-bold pt-3 pt-xl-5 pb-2 pb-xl-3\">العناصر</h2>\n\n    <article class=\"my-3\" id=\"accordion\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>المطوية</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/accordion\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"accordion\" id=\"accordionExample\">\n          <div class=\"accordion-item\">\n            <h4 class=\"accordion-header\" id=\"headingOne\">\n              <button class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">\n                عنصر المطوية الأول\n              </button>\n            </h4>\n            <div id=\"collapseOne\" class=\"accordion-collapse collapse show\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionExample\">\n              <div class=\"accordion-body\">\n                <strong>هذا هو محتوى عنصر المطوية الأول.</strong> سيكون المحتوى مخفيًا بشكل إفتراضي حتى يقوم Bootstrap بإضافة الكلاسات اللازمة لكل عنصر في المطوية. هذه الكلاسات تتحكم بالمظهر العام ووتتحكم أيضا بإظهار وإخفاء أقسام المطوية عبر حركات CSS الإنتقالية. يمكنك تعديل أي من هذه عبر كلاسات CSS خاصة بك، او عبر تغيير القيم الإفتراضية المقدمة من Bootstrap. من الجدير بالذكر أنه يمكن وضع أي كود HTML هنا، ولكن الحركة الإنتقالية قد تحد من الoverflow.\n              </div>\n            </div>\n          </div>\n          <div class=\"accordion-item\">\n            <h4 class=\"accordion-header\" id=\"headingTwo\">\n              <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseTwo\" aria-expanded=\"false\" aria-controls=\"collapseTwo\">\n                عنصر المطوية الثاني\n              </button>\n            </h4>\n            <div id=\"collapseTwo\" class=\"accordion-collapse collapse\" aria-labelledby=\"headingTwo\" data-bs-parent=\"#accordionExample\">\n              <div class=\"accordion-body\">\n                <strong>هذا هو محتوى عنصر المطوية الثاني.</strong> سيكون المحتوى مخفيًا بشكل إفتراضي حتى يقوم Bootstrap بإضافة الكلاسات اللازمة لكل عنصر في المطوية. هذه الكلاسات تتحكم بالمظهر العام ووتتحكم أيضا بإظهار وإخفاء أقسام المطوية عبر حركات CSS الإنتقالية. يمكنك تعديل أي من هذه عبر كلاسات CSS خاصة بك، او عبر تغيير القيم الإفتراضية المقدمة من Bootstrap. من الجدير بالذكر أنه يمكن وضع أي كود HTML هنا، ولكن الحركة الإنتقالية قد تحد من الoverflow.\n              </div>\n            </div>\n          </div>\n          <div class=\"accordion-item\">\n            <h4 class=\"accordion-header\" id=\"headingThree\">\n              <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseThree\" aria-expanded=\"false\" aria-controls=\"collapseThree\">\n                عنصر المطوية الثالث\n              </button>\n            </h4>\n            <div id=\"collapseThree\" class=\"accordion-collapse collapse\" aria-labelledby=\"headingThree\" data-bs-parent=\"#accordionExample\">\n              <div class=\"accordion-body\">\n                <strong>هذا هو محتوى عنصر المطوية الثالث.</strong> سيكون المحتوى مخفيًا بشكل إفتراضي حتى يقوم Bootstrap بإضافة الكلاسات اللازمة لكل عنصر في المطوية. هذه الكلاسات تتحكم بالمظهر العام ووتتحكم أيضا بإظهار وإخفاء أقسام المطوية عبر حركات CSS الإنتقالية. يمكنك تعديل أي من هذه عبر كلاسات CSS خاصة بك، او عبر تغيير القيم الإفتراضية المقدمة من Bootstrap. من الجدير بالذكر أنه يمكن وضع أي كود HTML هنا، ولكن الحركة الإنتقالية قد تحد من الoverflow.\n              </div>\n            </div>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"alerts\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الإنذارات</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/alerts\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< alerts.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <div class=\"alert alert-{{ .name }} alert-dismissible fade show\" role=\"alert\">\n          تنبيه {{ .name }} بسيط مع <a href=\"#\" class=\"alert-link\">رابط مثال</a>. أعطها نقرة إذا أردت.\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"قريب\"></button>\n        </div>{{ end -}}\n        {{< /alerts.inline >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"alert alert-success\" role=\"alert\">\n          <h4 class=\"alert-heading\">أحسنت!</h4>\n          <p>لقد نجحت في قراءة رسالة التنبيه المهمة هذه. سيتم تشغيل نص المثال هذا لفترة أطول قليلاً حتى تتمكن من رؤية كيفية عمل التباعد داخل التنبيه مع هذا النوع من المحتوى.</p>\n          <hr>\n          <p class=\"mb-0\">كلما احتجت إلى ذلك ، تأكد من استخدام أدوات الهامش للحفاظ على الأشياء لطيفة ومرتبة.</p>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"badge\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الشارة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/badge\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <p class=\"h1\">مثال على عنوان <span class=\"badge bg-primary\">جديد</span></p>\n        <p class=\"h2\">مثال على عنوان <span class=\"badge bg-secondary\">جديد</span></p>\n        <p class=\"h3\">مثال على عنوان <span class=\"badge bg-success\">جديد</span></p>\n        <p class=\"h4\">مثال على عنوان <span class=\"badge bg-danger\">جديد</span></p>\n        <p class=\"h5\">مثال على عنوان <span class=\"badge text-bg-warning\">جديد</span></p>\n        <p class=\"h6\">مثال على عنوان <span class=\"badge text-bg-info\">جديد</span></p>\n        <p class=\"h6\">مثال على عنوان <span class=\"badge text-bg-light\">جديد</span></p>\n        <p class=\"h6\">مثال على عنوان <span class=\"badge bg-dark\">جديد</span></p>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< badge.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <span class=\"badge rounded-pill {{ if or (eq .name \"light\") (eq .name \"warning\") (eq .name \"info\") }}text-{{ end }}bg-{{ .name }}\">{{ .name | title }}</span>{{- end -}}\n        {{< /badge.inline >}}\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"breadcrumb\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>مسار التنقل التفصيلي (فتات الخبز)</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/breadcrumb\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"فتات الخبز\">\n          <ol class=\"breadcrumb\">\n            <li class=\"breadcrumb-item\"><a href=\"#\">الصفحة الرئيسية</a></li>\n            <li class=\"breadcrumb-item\"><a href=\"#\">المكتبة</a></li>\n            <li class=\"breadcrumb-item active\" aria-current=\"page\">البيانات</li>\n          </ol>\n        </nav>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"buttons\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الأزرار</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/buttons\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< buttons.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <button type=\"button\" class=\"btn btn-{{ .name }}\">{{ .name | title }}</button>\n        {{- end -}}\n        {{< /buttons.inline >}}\n\n        <button type=\"button\" class=\"btn btn-link\">رابط</button>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< buttons.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <button type=\"button\" class=\"btn btn-outline-{{ .name }}\">{{ .name | title }}</button>\n        {{- end -}}\n        {{< /buttons.inline >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <button type=\"button\" class=\"btn btn-primary btn-sm\">زر صغير</button>\n        <button type=\"button\" class=\"btn btn-primary\">زر قياسي</button>\n        <button type=\"button\" class=\"btn btn-primary btn-lg\">زر كبير</button>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"button-group\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>مجموعة الأزرار</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/button-group\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-toolbar\" role=\"toolbar\" aria-label=\"شريط أدوات مع مجموعات أزرار\">\n          <div class=\"btn-group me-2\" role=\"group\" aria-label=\"المجموعة الأولى\">\n            <button type=\"button\" class=\"btn btn-secondary\">1</button>\n            <button type=\"button\" class=\"btn btn-secondary\">2</button>\n            <button type=\"button\" class=\"btn btn-secondary\">3</button>\n            <button type=\"button\" class=\"btn btn-secondary\">4</button>\n          </div>\n          <div class=\"btn-group me-2\" role=\"group\" aria-label=\"المجموعة الثانية\">\n            <button type=\"button\" class=\"btn btn-secondary\">5</button>\n            <button type=\"button\" class=\"btn btn-secondary\">6</button>\n            <button type=\"button\" class=\"btn btn-secondary\">7</button>\n          </div>\n          <div class=\"btn-group\" role=\"group\" aria-label=\"المجموعة الثالثة\">\n            <button type=\"button\" class=\"btn btn-secondary\">8</button>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"card\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>البطاقة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/card\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"row  row-cols-1 row-cols-md-2 g-4\">\n          <div class=\"col\">\n            <div class=\"card\">\n              {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"غطاء الصورة\" >}}\n              <div class=\"card-body\">\n                <h5 class=\"card-title\">عنوان البطاقة</h5>\n                <p class=\"card-text\">بعض الأمثلة السريعة للنصوص للبناء على عنوان البطاقة وتشكيل الجزء الأكبر من محتوى البطاقة.</p>\n                <a href=\"#\" class=\"btn btn-primary\">اذهب لمكان ما</a>\n              </div>\n            </div>\n          </div>\n          <div class=\"col\">\n            <div class=\"card\">\n              <div class=\"card-header\">\n                متميز\n              </div>\n              <div class=\"card-body\">\n                <h5 class=\"card-title\">عنوان البطاقة</h5>\n                <p class=\"card-text\">بعض الأمثلة السريعة للنصوص للبناء على عنوان البطاقة وتشكيل الجزء الأكبر من محتوى البطاقة.</p>\n                <a href=\"#\" class=\"btn btn-primary\">اذهب لمكان ما</a>\n              </div>\n              <div class=\"card-footer text-muted\">\n                منذ يومان\n              </div>\n            </div>\n          </div>\n          <div class=\"col\">\n            <div class=\"card\">\n              <div class=\"card-body\">\n                <h5 class=\"card-title\">عنوان البطاقة</h5>\n                <p class=\"card-text\">بعض الأمثلة السريعة للنصوص للبناء على عنوان البطاقة وتشكيل الجزء الأكبر من محتوى البطاقة.</p>\n              </div>\n              <ul class=\"list-group list-group-flush\">\n                <li class=\"list-group-item\">عنصر</li>\n                <li class=\"list-group-item\">عنصر آخر</li>\n                <li class=\"list-group-item\">عنصر ثالث</li>\n              </ul>\n              <div class=\"card-body\">\n                <a href=\"#\" class=\"card-link\">رابط البطاقة</a>\n                <a href=\"#\" class=\"card-link\">رابط آخر</a>\n              </div>\n            </div>\n          </div>\n          <div class=\"col\">\n            <div class=\"card\">\n              <div class=\"row g-0\">\n                <div class=\"col-md-4\">\n                  {{< placeholder width=\"100%\" height=\"250\" text=\"صورة\" >}}\n                </div>\n                <div class=\"col-md-8\">\n                  <div class=\"card-body\">\n                    <h5 class=\"card-title\">عنوان البطاقة</h5>\n                    <p class=\"card-text\">هذه بطاقة أعرض مع نص داعم تحتها كمقدمة طبيعية لمحتوى إضافي. هذا المحتوى أطول قليلاً.</p>\n                    <p class=\"card-text\"><small class=\"text-muted\">آخر تحديث منذ 3 دقائق</small></p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"carousel\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>شرائح العرض</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/carousel\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div id=\"carouselExampleCaptions\" class=\"carousel slide\" data-bs-ride=\"carousel\">\n          <div class=\"carousel-indicators\">\n            <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"0\" class=\"active\" aria-current=\"true\" aria-label=\"الشريحة الأولى\"></button>\n            <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"1\" aria-label=\"الشريحة الثانية\"></button>\n            <button type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide-to=\"2\" aria-label=\"الشريحة الثالثة\"></button>\n          </div>\n          <div class=\"carousel-inner\">\n            <div class=\"carousel-item active\">\n              {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#555\" background=\"#777\" text=\"الشريحة الأولى\" >}}\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>عنوان الشريحة الأولى</h5>\n                <p>محتوى وصفي يعبئ فراغ الشريحة الأولى.</p>\n              </div>\n            </div>\n            <div class=\"carousel-item\">\n              {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#444\" background=\"#666\" text=\"الشريحة الثانية\" >}}\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>عنوان الشريحة الثانية</h5>\n                <p>محتوى وصفي يعبئ فراغ الشريحة الأولى.</p>\n              </div>\n            </div>\n            <div class=\"carousel-item\">\n              {{< placeholder width=\"800\" height=\"400\" class=\"bd-placeholder-img-lg d-block w-100\" color=\"#333\" background=\"#555\" text=\"الشريحة الثالثة\" >}}\n              <div class=\"carousel-caption d-none d-md-block\">\n                <h5>عنوان الشريحة الثالثة</h5>\n                <p>محتوى وصفي يعبئ فراغ الشريحة الأولى.</p>\n              </div>\n            </div>\n          </div>\n          <button class=\"carousel-control-prev\" type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide=\"prev\">\n            <span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n            <span class=\"visually-hidden\">السابق</span>\n          </button>\n          <button class=\"carousel-control-next\" type=\"button\" data-bs-target=\"#carouselExampleCaptions\" data-bs-slide=\"next\">\n            <span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n            <span class=\"visually-hidden\">التالي</span>\n          </button>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"dropdowns\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>القوائم المنسدلة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/dropdowns\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group w-100 align-items-center justify-content-between flex-wrap\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary btn-sm dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              زر القائمة المنسدلة\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              زر القائمة المنسدلة\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary btn-lg dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              زر القائمة المنسدلة\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-primary\">Primary</button>\n          <button type=\"button\" class=\"btn btn-primary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">تبديل القائمة المنسدلة</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-secondary\">Secondary</button>\n          <button type=\"button\" class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">تبديل القائمة المنسدلة</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-success\">Success</button>\n          <button type=\"button\" class=\"btn btn-success dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">تبديل القائمة المنسدلة</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-info\">Info</button>\n          <button type=\"button\" class=\"btn btn-info dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">تبديل القائمة المنسدلة</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-warning\">Warning</button>\n          <button type=\"button\" class=\"btn btn-warning dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">تبديل القائمة المنسدلة</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        <div class=\"btn-group\">\n          <button type=\"button\" class=\"btn btn-danger\">Danger</button>\n          <button type=\"button\" class=\"btn btn-danger dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <span class=\"visually-hidden\">تبديل القائمة المنسدلة</span>\n          </button>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n          </ul>\n        </div><!-- /btn-group -->\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group w-100 align-items-center justify-content-between flex-wrap\">\n          <div class=\"dropend\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              زر القائمة المنسدلة لليسار\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n          <div class=\"dropup\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              زر القائمة المنسدلة للأعلى\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n          <div class=\"dropstart\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              زر القائمة المنسدلة لليمين\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"btn-group\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              قائمة منسدلة بمحاذاة نهاية الزر\n            </button>\n            <ul class=\"dropdown-menu dropdown-menu-end\">\n              <li><h6 class=\"dropdown-header\">عنوان القائمة المنسدلة</h6></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n              <li><hr class=\"dropdown-divider\"></li>\n              <li><a class=\"dropdown-item\" href=\"#\">رابط منفصل</a></li>\n            </ul>\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"list-group\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>مجموعة العناصر</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/list-group\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-group\">\n          <li class=\"list-group-item disabled\" aria-disabled=\"true\">عنصر معطل</li>\n          <li class=\"list-group-item\">عنصر ثاني</li>\n          <li class=\"list-group-item\">عنصر ثالث</li>\n          <li class=\"list-group-item\">عنصر رابع</li>\n          <li class=\"list-group-item\">وعنصر خامس أيضًا</li>\n        </ul>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"list-group list-group-flush\">\n          <li class=\"list-group-item\">عنصر</li>\n          <li class=\"list-group-item\">عنصر ثاني</li>\n          <li class=\"list-group-item\">عنصر ثالث</li>\n          <li class=\"list-group-item\">عنصر رابع</li>\n          <li class=\"list-group-item\">وعنصر خامس أيضًا</li>\n        </ul>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"list-group\">\n          <a href=\"#\" class=\"list-group-item list-group-item-action\">عنصر مجموعة قائمة default بسيط</a>\n          {{< list.inline >}}\n          {{- range (index $.Site.Data \"theme-colors\") }}\n          <a href=\"#\" class=\"list-group-item list-group-item-action list-group-item-{{ .name }}\">عنصر مجموعة قائمة {{ .name }} بسيط</a>\n          {{- end -}}\n          {{< /list.inline >}}\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"modal\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الصندوق العائم</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/modal\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"d-flex justify-content-between flex-wrap\">\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalDefault\">\n            إطلاق صندوق عائم تجريبي\n          </button>\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#staticBackdropLive\">\n            إطلاق صندوق عائم عالق\n          </button>\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalCenteredScrollable\">\n            صندوق عائم متنصف عاموديًا وقابل للتمرير\n          </button>\n          <button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#exampleModalFullscreen\">\n            صندوق عائم يملأ الشاشة\n          </button>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"navs\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>التنقل</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/navs-tabs\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav class=\"nav\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">نشط</a>\n          <a class=\"nav-link\" href=\"#\">رابط</a>\n          <a class=\"nav-link\" href=\"#\">رابط</a>\n          <a class=\"nav-link disabled\">معطل</a>\n        </nav>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <nav>\n          <div class=\"nav nav-tabs mb-3\" id=\"nav-tab\" role=\"tablist\">\n            <button class=\"nav-link active\" id=\"nav-home-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-home\" type=\"button\" role=\"tab\" aria-controls=\"nav-home\" aria-selected=\"true\">الصفحة الرئيسية</button>\n            <button class=\"nav-link\" id=\"nav-profile-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-profile\" type=\"button\" role=\"tab\" aria-controls=\"nav-profile\" aria-selected=\"false\">الملف الشخصي</button>\n            <button class=\"nav-link\" id=\"nav-contact-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#nav-contact\" type=\"button\" role=\"tab\" aria-controls=\"nav-contact\" aria-selected=\"false\">اتصل بنا</button>\n          </div>\n        </nav>\n        <div class=\"tab-content\" id=\"nav-tabContent\">\n          <div class=\"tab-pane fade show active\" id=\"nav-home\" role=\"tabpanel\" aria-labelledby=\"nav-home-tab\">\n            <p class=\"px-3\">محتوى لتوضيح كيف يعمل التبويب. هذا المحتوى مرتبط بتبويب الصفحة الرئيسية. إذن، أمامنا بعض التحدّيات الصعبة. لكن لا يمكننا أن نعتمد على التطورات التكنولوجية وحدها في ميدان قوى السوق الحرة، لإخراجنا من هذه الورطة، لا سيّما أنها نفسها، مقرونة بالافتقار إلى البصيرة، هي التي أودت بنا إلى هذا التبدُّل المناخي في الدرجة الأولى.</p>\n          </div>\n          <div class=\"tab-pane fade\" id=\"nav-profile\" role=\"tabpanel\" aria-labelledby=\"nav-profile-tab\">\n            <p class=\"px-3\">محتوى لتوضيح كيف يعمل التبويب. هذا المحتوى مرتبط بتبويب الملف الشخصي. معظم البشر في بلدان العالَم النامي، لم يقتنوا بعد مكيّفهم الأول، والمشكلة إلى ازدياد. فمعظم البلدان النامية هي من البلدان الأشد حرارة والأكثر اكتظاظًا بالسكان في العالم.</p>\n          </div>\n          <div class=\"tab-pane fade\" id=\"nav-contact\" role=\"tabpanel\" aria-labelledby=\"nav-contact-tab\">\n            <p class=\"px-3\">محتوى لتوضيح كيف يعمل التبويب. هذا المحتوى مرتبط بتبويب الاتصال بنا. أمامنا بعض التحدّيات الصعبة. لكن لا يمكننا أن نعتمد على التطورات التكنولوجية وحدها في ميدان قوى السوق الحرة، بل يجب وضع معايير جدوى جديدة لشركات البناء ومعايير أعلى لجدوى التكييف من أجل تحفيز الحلول المستدامة قانونيًا.</p>\n          </div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <ul class=\"nav nav-pills\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">نشط</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">رابط</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">رابط</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">معطل</a>\n          </li>\n        </ul>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"navbar\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>شريط التنقل</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/navbar\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav class=\"navbar navbar-expand-lg bg-light\">\n          <div class=\"container-fluid\">\n            <a class=\"navbar-brand\" href=\"#\">\n              <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" width=\"38\" height=\"30\" class=\"d-inline-block align-top\" alt=\"Bootstrap\" loading=\"lazy\"\n                   style=\"filter: invert(1) grayscale(100%) brightness(200%);\">\n            </a>\n            <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"تبديل التنقل\">\n              <span class=\"navbar-toggler-icon\"></span>\n            </button>\n            <div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\">\n              <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n                <li class=\"nav-item\">\n                  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">الصفحة الرئيسية</a>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link\" href=\"#\">رابط</a>\n                </li>\n                <li class=\"nav-item dropdown\">\n                  <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                    قائمة منسدلة\n                  </a>\n                  <ul class=\"dropdown-menu\">\n                    <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n                    <li><hr class=\"dropdown-divider\"></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n                  </ul>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link disabled\">معطل</a>\n                </li>\n              </ul>\n              <form class=\"d-flex\" role=\"search\">\n                <input class=\"form-control me-2\" type=\"search\" placeholder=\"بحث\" aria-label=\"بحث\">\n                <button class=\"btn btn-outline-dark\" type=\"submit\">بحث</button>\n              </form>\n            </div>\n          </div>\n        </nav>\n\n        <nav class=\"navbar navbar-expand-lg navbar-dark bg-primary mt-5\">\n          <div class=\"container-fluid\">\n            <a class=\"navbar-brand\" href=\"#\">\n              <img src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" width=\"38\" height=\"30\" class=\"d-inline-block align-top\" alt=\"Bootstrap\" loading=\"lazy\">\n            </a>\n            <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent2\" aria-controls=\"navbarSupportedContent2\" aria-expanded=\"false\" aria-label=\"تبديل التنقل\">\n              <span class=\"navbar-toggler-icon\"></span>\n            </button>\n            <div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent2\">\n              <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n                <li class=\"nav-item\">\n                  <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">الصفحة الرئيسية</a>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link\" href=\"#\">رابط</a>\n                </li>\n                <li class=\"nav-item dropdown\">\n                  <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                    قائمة منسدلة\n                  </a>\n                  <ul class=\"dropdown-menu\">\n                    <li><a class=\"dropdown-item\" href=\"#\">عمل</a></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">عمل آخر</a></li>\n                    <li><hr class=\"dropdown-divider\"></li>\n                    <li><a class=\"dropdown-item\" href=\"#\">شيء آخر هنا</a></li>\n                  </ul>\n                </li>\n                <li class=\"nav-item\">\n                  <a class=\"nav-link disabled\">معطل</a>\n                </li>\n              </ul>\n              <form class=\"d-flex\" role=\"search\">\n                <input class=\"form-control me-2\" type=\"search\" placeholder=\"بحث\" aria-label=\"بحث\">\n                <button class=\"btn btn-outline-light\" type=\"submit\">بحث</button>\n              </form>\n            </div>\n          </div>\n        </nav>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"pagination\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>ترقيم الصفحات</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/pagination\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"مثال ترقيم الصفحات\">\n          <ul class=\"pagination pagination-sm\">\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n            <li class=\"page-item active\" aria-current=\"page\">\n              <a class=\"page-link\" href=\"#\">2</a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n          </ul>\n        </nav>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"مثال قياسي لترقيم الصفحات\">\n          <ul class=\"pagination\">\n            <li class=\"page-item\">\n              <a class=\"page-link\" href=\"#\" aria-label=\"السابق\">\n                <span aria-hidden=\"true\">&laquo;</span>\n              </a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">2</a></li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n            <li class=\"page-item\">\n              <a class=\"page-link\" href=\"#\" aria-label=\"التالي\">\n                <span aria-hidden=\"true\">&raquo;</span>\n              </a>\n            </li>\n          </ul>\n        </nav>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <nav aria-label=\"مثال آخر لترقيم الصفحات\">\n          <ul class=\"pagination pagination-lg flex-wrap\">\n            <li class=\"page-item disabled\">\n              <a class=\"page-link\">السابق</a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">1</a></li>\n            <li class=\"page-item active\" aria-current=\"page\">\n              <a class=\"page-link\" href=\"#\">2</a>\n            </li>\n            <li class=\"page-item\"><a class=\"page-link\" href=\"#\">3</a></li>\n            <li class=\"page-item\">\n              <a class=\"page-link\" href=\"#\">التالى</a>\n            </li>\n          </ul>\n        </nav>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"popovers\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الصناديق المنبثقة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/popovers\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <button type=\"button\" class=\"btn btn-lg btn-danger\" data-bs-toggle=\"popover\" title=\"عنوان الصندوق المنبثق\" data-bs-content=\"وإليك بعض المحتويات الرائعة. إنه آسر للغاية. أليس كذلك؟\">\n        انقر لعرض/إخفاء الصندوق المنبثق\n        </button>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"top\" data-bs-content=\"نجمٌ و لكنه ليس في السماء، بل في أعماق البحار و المحيطات!\">\n          انبثاق إلى الأعلى\n        </button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"right\" data-bs-content=\"نجمٌ و لكنه ليس في السماء، بل في أعماق البحار و المحيطات!\">\n          انبثاق إلى اليسار\n        </button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"bottom\" data-bs-content=\"نجمٌ و لكنه ليس في السماء، بل في أعماق البحار و المحيطات!\">\n          انبثاق إلى الأسفل\n        </button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-container=\"body\" data-bs-toggle=\"popover\" data-bs-placement=\"left\" data-bs-content=\"نجمٌ و لكنه ليس في السماء، بل في أعماق البحار و المحيطات!\">\n          انبثاق إلى اليمين\n        </button>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"progress\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>شريط التقدم</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/progress\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"مثال مع عنوان\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\">0%</div>\n        </div>\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar bg-success w-25\" role=\"progressbar\" aria-label=\"مثال ناجح مع عنوان\" aria-valuenow=\"25\" aria-valuemin=\"0\" aria-valuemax=\"100\">25%</div>\n        </div>\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar text-bg-info w-50\" role=\"progressbar\" aria-label=\"مثال توضيح مع عنوان\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\">50%</div>\n        </div>\n        <div class=\"progress mb-3\">\n          <div class=\"progress-bar text-bg-warning w-75\" role=\"progressbar\" aria-label=\"مثال تنبيه مع عنوان\" aria-valuenow=\"75\" aria-valuemin=\"0\" aria-valuemax=\"100\">75%</div>\n        </div>\n        <div class=\"progress\">\n          <div class=\"progress-bar bg-danger w-100\" role=\"progressbar\" aria-label=\"مثال خطر مع عنوان\" aria-valuenow=\"100\" aria-valuemin=\"0\" aria-valuemax=\"100\">100%</div>\n        </div>\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        <div class=\"progress\">\n          <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"القسم الأول - مثال افتراضي\" style=\"width: 15%\" aria-valuenow=\"15\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n          <div class=\"progress-bar progress-bar-striped progress-bar-animated bg-success\" role=\"progressbar\" aria-label=\"القسم الثاني - مثال ناجح مقلّم متحرك\" style=\"width: 40%\" aria-valuenow=\"40\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"scrollspy\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>المخطوطة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/scrollspy\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        <div class=\"bd-example\">\n          <nav id=\"navbar-example2\" class=\"navbar bg-light px-3\">\n            <a class=\"navbar-brand\" href=\"#\">شريط التنقل</a>\n            <ul class=\"nav nav-pills\">\n              <li class=\"nav-item\">\n                <a class=\"nav-link\" href=\"#fat\"><bdi lang=\"en\" dir=\"ltr\">@fat</bdi></a>\n              </li>\n              <li class=\"nav-item\">\n                <a class=\"nav-link\" href=\"#mdo\"><bdi lang=\"en\" dir=\"ltr\">@mdo</bdi></a>\n              </li>\n              <li class=\"nav-item dropdown\">\n                <a class=\"nav-link dropdown-toggle\" data-bs-toggle=\"dropdown\" href=\"#\" role=\"button\" aria-expanded=\"false\">قائمة منسدلة</a>\n                <ul class=\"dropdown-menu\">\n                  <li><a class=\"dropdown-item\" href=\"#one\">واحد</a></li>\n                  <li><a class=\"dropdown-item\" href=\"#two\">اثنان</a></li>\n                  <li><hr class=\"dropdown-divider\"></li>\n                  <li><a class=\"dropdown-item\" href=\"#three\">ثلاثة</a></li>\n                </ul>\n              </li>\n            </ul>\n          </nav>\n          <div data-bs-spy=\"scroll\" data-bs-target=\"#navbar-example2\" data-bs-offset=\"0\" class=\"scrollspy-example position-relative mt-2 overflow-auto\">\n            <h4 id=\"fat\"><bdi lang=\"en\" dir=\"ltr\">@fat</bdi></h4>\n            <p>محتوى لتوضيح كيف تعمل المخطوطة. ببساطة، المخطوطة عبارة عن منشور طويل يحتوي على عدة أقسام، ولديه شريط تنقل يسهل الوصول إلى هذه الأقسام الفرعية.</p>\n            <h4 id=\"mdo\"><bdi lang=\"en\" dir=\"ltr\">@mdo</bdi></h4>\n            <p>بصرف النظر عن تحسيننا جدوى المكيّفات أو عدم تحسينها، فإن الطلب على الطاقة سيزداد. وطبقاً لما جاء في مقالة معهد ماساشوستس للتكنولوجيا، السالف ذكره، ثمَّة أمر يجب عدم إغفاله، وهو كيف أن هذا الطلب سيضغط على نظم توفير الطاقة الحالية. إذ لا بد من إعادة تأهيل كل شبكات الكهرباء، وتوسيعها لتلبية طلب الطاقة في زمن الذروة، خلال موجات الحرارة المتزايدة. فحين يكون الحر شديداً يجنح الناس إلى البقاء في الداخل، وإلى زيادة تشغيل المكيّفات، سعياً إلى جو لطيف وهم يستخدمون أدوات وأجهزة مختلفة أخرى.</p>\n            <h4 id=\"one\">واحد</h4>\n            <p>وكل هذه الأمور المتزامنة من تشغيل الأجهزة، يزيد الضغط على شبكات الطاقة، كما أسلفنا. لكن مجرد زيادة سعة الشبكة ليس كافياً. إذ لا بد من تطوير الشبكات الذكية التي تستخدم الجسّاسات، ونظم المراقبة، والبرامج الإلكترونية، لتحديد متى يكون الشاغلون في المبنى، ومتى يكون ثمَّة حاجة إلى الطاقة، ومتى تكون الحرارة منخفضة، وبذلك يخرج الناس، فلا يستخدمون كثيراً من الكهرباء.</p>\n            <h4 id=\"two\">اثنان</h4>\n            <p>مع الأسف، كل هذه الحلول المبتكرة مكلِّفة، وهذا ما يجعلها عديمة الجدوى في نظر بعض الشركات الخاصة والمواطن المتقشّف. إن بعض الأفراد الواعين بيئياً يبذلون قصارى جهدهم في تقليص استهلاكهم من الطاقة، ويعون جيداً أهمية أجهزة التكييف المجدية والأرفق بالبيئة. ولكن جهات كثيرة لن تتحرّك لمجرد حافز سلامة المناخ ووقف هدر الطاقة، ما دامت لا تحركها حوافز قانونية. وعلى الحكومات أن تُقدِم عند الاهتمام بالتغيّر المناخي، على وضع التشريعات المناسبة. فبالنظم والحوافز والدعم، يمكن دفع الشركات إلى اعتماد الحلول الأجدى في مكاتبها.</p>\n            <h4 id=\"three\">ثلاثة</h4>\n            <p>وكما يتبيّن لنا، من عدد الحلول الملطِّفة للمشكلة، ومن تنوّعها، وهي الحلول التي أسلفنا الحديث عنها، فإن التكنولوجيا التي نحتاج إليها من أجل معالجة هذه التحديات، هي في مدى قدرتنا، لكنها ربما تتطلّب بعض التحسين، ودعماً استثمارياً أكبر!</p>\n            <p>ولا مانع من إضافة محتوى آخر ليس تحت أي قسم معين.</p>\n          </div>\n        </div>\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"spinners\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الدوائر المتحركة</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/spinners\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" >}}\n        {{< spinner.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <div class=\"spinner-border text-{{ .name }}\" role=\"status\">\n          <span class=\"visually-hidden\">جار التحميل...</span>\n        </div>\n        {{- end -}}\n        {{< /spinner.inline >}}\n        {{< /example >}}\n\n        {{< example show_markup=\"false\" >}}\n        {{< spinner.inline >}}\n        {{- range (index $.Site.Data \"theme-colors\") }}\n        <div class=\"spinner-grow text-{{ .name }}\" role=\"status\">\n          <span class=\"visually-hidden\">جار التحميل...</span>\n        </div>\n        {{- end -}}\n        {{< /spinner.inline >}}\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"my-3\" id=\"toasts\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>الإشعارات</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/toasts\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" class=\"bg-dark p-5 align-items-center\" >}}\n        <div class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n          <div class=\"toast-header\">\n            {{< placeholder width=\"20\" height=\"20\" background=\"#007aff\" class=\"rounded me-2\" text=\"false\" title=\"false\" >}}\n            <strong class=\"me-auto\">Bootstrap</strong>\n            <small class=\"text-muted\">قبل 11 دقيقة</small>\n            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"قريب\"></button>\n          </div>\n          <div class=\"toast-body\">\n            مرحبًا بالعالم! هذه رسالة إشعار.\n          </div>\n        </div>\n        {{< /example >}}\n      </div>\n    </article>\n    <article class=\"mt-3 mb-5 pb-5\" id=\"tooltips\">\n      <div class=\"bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2\">\n        <h3>التلميحات</h3>\n        <a class=\"d-flex align-items-center\" hreflang=\"en\" href=\"{{< docsref \"/components/tooltips\" >}}\">دليل الإستخدام</a>\n      </div>\n\n      <div>\n        {{< example show_markup=\"false\" class=\"tooltip-demo\" >}}\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" title=\"تلميح يظهر في الأعلى\">تلميح يظهر في الأعلى</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\" title=\"تلميح يظهر على اليسار\">تلميح يظهر على اليسار</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" title=\"تلميح يظهر في الأسفل\">تلميح يظهر في الأسفل</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-placement=\"left\" title=\"تلميح يظهر على اليمين\">تلميح يظهر على اليمين</button>\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"tooltip\" data-bs-html=\"true\" title=\"<em>تلميح</em> <u>مع</u> <b>HTML</b>\">تلميح مع HTML</button>\n        {{< /example >}}\n      </div>\n    </article>\n  </section>\n</div>\n\n<div class=\"modal fade\" id=\"exampleModalDefault\" tabindex=\"-1\" aria-labelledby=\"exampleModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalLabel\">عنوان الصندوق العائم</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"إغلاق\"></button>\n      </div>\n      <div class=\"modal-body\">\n        ...\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">إغلاق</button>\n        <button type=\"button\" class=\"btn btn-primary\">حفظ التغيرات</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"staticBackdropLive\" data-bs-backdrop=\"static\" data-bs-keyboard=\"false\" tabindex=\"-1\" aria-labelledby=\"staticBackdropLiveLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"staticBackdropLiveLabel\">عنوان الصندوق العائم</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"إغلاق\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>لن أغلق إذا نقرت خارجي. لا تحاول حتى الضغط على مفتاح الهروب.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">إغلاق</button>\n        <button type=\"button\" class=\"btn btn-primary\">حسنًا</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"exampleModalCenteredScrollable\" tabindex=\"-1\" aria-labelledby=\"exampleModalCenteredScrollableTitle\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-dialog-centered modal-dialog-scrollable\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-5\" id=\"exampleModalCenteredScrollableTitle\">عنوان الصندوق العائم</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"إغلاق\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>نص لتوضيح عمل الصندوق العائم المتنصّف عاموديًا القابل للتمرير</p>\n        <p>في هذه الحالة، هذا الصندوق يحتوي على محتوى أكثر قليلًا، ولذلك لتوضيح كيف  يمكن إضافة خاصية الإنتصاف العامودي لصندوق عائم قابل للتمرير.</p>\n        <p>يعتبر كوب أو فنجان القهوة عادة يومية عند غالبية سكان الكرة الأرضية في الصباح والمساء، وفي حين تكثر الدراسات حول إيجابياتها هناك أيضا سلبيات كثيرة للمشروب المفضل للكثيرين.</p>\n        <p>وفي هذا الشأن، أظهرت دراسة جديدة أن تناول الكافيين بانتظام يقلل من حجم المادة الرمادية في الدماغ، مما يشير إلى أن تناول القهوة يمكن أن يضعف القدرة على معالجة المعلومات، وفقاً لما نشرته \"ديلي ميل\" البريطانية.</p>\n        <p>وأعطى باحثون سويسريون متطوعين ثلاث حصص من الكافيين 150 ملغم يوميًا لمدة 10 أيام - وهو مقدار كافيين يعادل حوالي أربعة أو خمسة أكواب صغيرة من القهوة المخمرة يوميًا، أو سبعة إسبريسو فردي.</p>\n        <p>وتبين حدوث انخفاض في المادة الرمادية، والتي توجد غالبًا في الطبقة الخارجية للدماغ، أو القشرة، وتعمل على معالجة المعلومات.</p>\n        <p>كما كان الانخفاض مذهلاً بشكل خاص في الفص الصدغي الإنسي الأيمن، بما في ذلك الحُصين، وهي منطقة من الدماغ ضرورية لتقوية الذاكرة.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">إغلاق</button>\n        <button type=\"button\" class=\"btn btn-primary\">حفظ التغيرات</button>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"modal fade\" id=\"exampleModalFullscreen\" tabindex=\"-1\" aria-labelledby=\"exampleModalFullscreenLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-fullscreen\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h1 class=\"modal-title fs-4\" id=\"exampleModalFullscreenLabel\">صندوق عائم يملأ الشاشة</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"إغلاق\"></button>\n      </div>\n      <div class=\"modal-body\">\n        <p>في ما يلي، نص طويل لتعبئة كامل الشاشة. قد يبدو أنني أثرثر كثيرًا، لذا سأقوم بنسخ مقالة من إحدى الصحف العربية.</p>\n        <p>تكييف الهواء هو من التكنولوجيا التي ما إن نمتلكها حتى نصبح عاجزين عن تخيّل العيش من دونها. وتكييف الهواء في مناطق عديدة من العالم ليس ترفاً يمكن الاستغناء عنه. ولكن هذه الحاجة الحيوية تثير تحديات كبيرة على مستوى استهلاك الطاقة في معظم بلدان العالم. ويُتوقع أن تتفاقم هذه التحديات خلال العقود الثلاثة المقبلة، مع توقُّع ارتفاع عدد المكيفات في العالم نحو ثلاثة أضعاف. وفي حين أن الابتكارات التكنولوجية تحقِّق كل يوم إنجازاً جديداً، وعلى الرغم من بعض التحسينات والتدابير المحدودة التي طرأت على صناعة المكيفات، “فإن تكنولوجيتها الأساسية لا تزال تعمل كما كانت، منذ اعتمادها قبل نحو قرن من السنين\"، حسبما جاء في تقرير لمعهد ماساشوستس للتكنولوجيا (MIT)، في الأول من سبتمبر 2020م. ولمعالجة هذه المشكلات الخطيرة، لا مفر من إعادة التفكير بجد.</p>\n        <p>التكييف حاجة حيويّة. فالتعرّض للحرارة لمدة طويلة ضارٌ بالصحة. ووفقاً لمنظمة الصحة العالميّة يمكن للتعرُّض الطويل للحرارة أيضاً أن يؤدي إلى \"إعياء حراري، وضربة شمس، وتورّم في الرجلين، وطفح جلدي على العنق، وتشنج، وصداع، وحساسيّة، والخمول والوهَن. ويمكن للحرارة أن تسبِّب جفافاً خطراً، وأعراضاً حادة في أوعية الدماغ الدمويّة، وتسهم في تكوّن الجلطات\".</p>\n        <p>وموجات الحر أيضاً من أشد المخاطر الطبيعيّة المميتة. إذ يقدَّر أن بين عامي 1998 و2017م، توفي نحو 166 ألف شخص من موجات الحر. ومات 70 ألفاً من هؤلاء في أوروبا سنة 2003م وحدها. ومن دون أن يصل الخطر إلى الموت، تتسبَّب الحرارة العالية بانخفاض الأداء المعرفي لدى الشبان البالغين في المباني غير المكيّفة. فقد بيّنت دراسة أجرتها \"كليّة ت. هـ. تشان\" للصحة العامة في جامعة هارفرد، نشرتها \"بلوس ميديسين\" في 10 يوليو 2018م، أن التلاميذ الذين ينامون في مهاجع غير مكيّفة، يقل أداؤهم عن أولئك الذين ينامون في مهاجع مكيّفة. ولتجنّب مخاطر التعرّض للحرارة، يلتفت الناس إلى استخدام التكييف.</p>\n        <p>يتطلّب التكييف كثيراً من الطاقة. فنحو %10 من استهلاك الكهرباء في العالم يُنفَق في تشغيل المكيّفات. وتصل هذه النسبة إلى %50 في المملكة حسب \"المركز السعودي لكفاءة الطاقة\". أضف إلى ذلك أن معظم البشر في بلدان العالم النامي، لم يقتنوا بعد مكيّفهم الأول، والمشكلة إلى ازدياد. فمعظم البلدان النامية هي من البلدان الأشد حرارة والأكثر اكتظاظاً بالسكان في العالم. وجاء في تقرير لوكالة الطاقة الدوليّة بعنوان \"مستقبل التبريد\"، ونُشر في مايو 2018م، أن في أجزاء من أمريكا الجنوبيّة وإفريقيا وآسيا والشرق الأوسط، يعيش 2.8 مليار نسمة، ولا يملك وحدات تكييف سوى %8 من المنازل. في حين الدول المتقدِّمة مثل كوريا الجنوبيّة، واليابان، والولايات المتحدة، فإن %89 من المنازل تملك مكيفات، وفي الصين %60 من البيوت تملكها أيضاً.</p>\n        <p>ولكن مع تنامي الدخل في بلدان الاقتصاد الصاعد، يتوقّع أن تزداد المنازل التي تقتني مكيّفاً. وبحسب مقالة نُشرت في صحيفة \"نيويورك تايمز\"، في 15 مايو 2018م، سيرتفع عدد المكيّفات في العالم من نحو 1.6 مليار حالياً، إلى 5.6 مليارات عام 2050م، طبقاً لمعدَّلات النمو الاقتصادي.</p>\n        <p>ومع الافتقار إلى الابتكار وتطوير النظم لفرض معايير الجدوى الأعلى، فسيتضاعف استهلاك الطاقة لتشغيل المكيّفات ثلاثة أضعاف. وسينتج من ذلك طلب إضافي للطاقة يساوي مجموع إنتاج الطاقة في الصين حالياً. فمبيعات المكيّفات ترتفع اليوم في البلدان النامية، لكن فعاليّة هذه الوحدات مشكوك فيها. فمثلاً، أوسع وحدات التكييف انتشاراً في بعض الأسواق الآسيوية تتطلّب ضعفي ما تتطلّبه وحدات تكييف أجدى. والمكيّفات التي تباع في اليابان والاتحاد الأوروبي أجدى بنسبة %25 عادة، من تلك التي تباع في الصين والولايات المتحدة.</p>\n        <p>وبصرف النظر عن كثير من الطاقة التي يحتاج إليها المكيّف، فإنه يبث مقادير وفيرة من غازات الدفيئة نفسها. وطبقاً لموقع تكييف الهواء (airconditioning.com)، فإن المبرِّدات في معظم المكيّفات مثل مواد مركبات الكربون الكلورية فلورية أو مواد هيدروفلوروولفينات، ومركبات ثاني أكسيد الكربون الهيدروكلورية فلورية، أسوأ بكثير للبيئة من ثاني أكسيد الكربون، لأنها تحتبس من الحرارة مقادير أكبر حين تتسرّب إلى الجو.</p>\n        <p>وبالإضافة إلى ظاهرة الدفيئة، فهذه الغازات تسهم أيضاً بالإضرار بطبقة الأوزون. ويمكن لهذه المواد الكيميائية أن تتسرّب خلال عملية التصنيع أو التصليح. ويعرف كل من يملك في منزله مكيّفاً كم تتكرر أعطال هذه الأجهزة. ثم إن المكيف يُرمَى حين يتعطّل نهائياً ولا يعود قابلاً للإصلاح، والمواد المبرِّدة فيه ستتسرّب على الأرجح لتلوث الهواء.</p>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">إغلاق</button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/checkout/form-validation.css",
    "content": ".container {\n  max-width: 960px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/checkout/form-validation.js",
    "content": "// Example starter JavaScript for disabling form submissions if there are invalid fields\n(() => {\n  'use strict'\n\n  // Fetch all the forms we want to apply custom Bootstrap validation styles to\n  const forms = document.querySelectorAll('.needs-validation')\n\n  // Loop over them and prevent submission\n  Array.from(forms).forEach(form => {\n    form.addEventListener('submit', event => {\n      if (!form.checkValidity()) {\n        event.preventDefault()\n        event.stopPropagation()\n      }\n\n      form.classList.add('was-validated')\n    }, false)\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/checkout/index.html",
    "content": "---\nlayout: examples\ntitle: Checkout example\nextra_css:\n  - \"form-validation.css\"\nextra_js:\n  - src: \"form-validation.js\"\nbody_class: \"bg-light\"\n---\n\n<div class=\"container\">\n  <main>\n    <div class=\"py-5 text-center\">\n      <img class=\"d-block mx-auto mb-4\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"\" width=\"72\" height=\"57\">\n      <h2>Checkout form</h2>\n      <p class=\"lead\">Below is an example form built entirely with Bootstrap’s form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it.</p>\n    </div>\n\n    <div class=\"row g-5\">\n      <div class=\"col-md-5 col-lg-4 order-md-last\">\n        <h4 class=\"d-flex justify-content-between align-items-center mb-3\">\n          <span class=\"text-primary\">Your cart</span>\n          <span class=\"badge bg-primary rounded-pill\">3</span>\n        </h4>\n        <ul class=\"list-group mb-3\">\n          <li class=\"list-group-item d-flex justify-content-between lh-sm\">\n            <div>\n              <h6 class=\"my-0\">Product name</h6>\n              <small class=\"text-muted\">Brief description</small>\n            </div>\n            <span class=\"text-muted\">$12</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between lh-sm\">\n            <div>\n              <h6 class=\"my-0\">Second product</h6>\n              <small class=\"text-muted\">Brief description</small>\n            </div>\n            <span class=\"text-muted\">$8</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between lh-sm\">\n            <div>\n              <h6 class=\"my-0\">Third item</h6>\n              <small class=\"text-muted\">Brief description</small>\n            </div>\n            <span class=\"text-muted\">$5</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between bg-light\">\n            <div class=\"text-success\">\n              <h6 class=\"my-0\">Promo code</h6>\n              <small>EXAMPLECODE</small>\n            </div>\n            <span class=\"text-success\">−$5</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between\">\n            <span>Total (USD)</span>\n            <strong>$20</strong>\n          </li>\n        </ul>\n\n        <form class=\"card p-2\">\n          <div class=\"input-group\">\n            <input type=\"text\" class=\"form-control\" placeholder=\"Promo code\">\n            <button type=\"submit\" class=\"btn btn-secondary\">Redeem</button>\n          </div>\n        </form>\n      </div>\n      <div class=\"col-md-7 col-lg-8\">\n        <h4 class=\"mb-3\">Billing address</h4>\n        <form class=\"needs-validation\" novalidate>\n          <div class=\"row g-3\">\n            <div class=\"col-sm-6\">\n              <label for=\"firstName\" class=\"form-label\">First name</label>\n              <input type=\"text\" class=\"form-control\" id=\"firstName\" placeholder=\"\" value=\"\" required>\n              <div class=\"invalid-feedback\">\n                Valid first name is required.\n              </div>\n            </div>\n\n            <div class=\"col-sm-6\">\n              <label for=\"lastName\" class=\"form-label\">Last name</label>\n              <input type=\"text\" class=\"form-control\" id=\"lastName\" placeholder=\"\" value=\"\" required>\n              <div class=\"invalid-feedback\">\n                Valid last name is required.\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"username\" class=\"form-label\">Username</label>\n              <div class=\"input-group has-validation\">\n                <span class=\"input-group-text\">@</span>\n                <input type=\"text\" class=\"form-control\" id=\"username\" placeholder=\"Username\" required>\n              <div class=\"invalid-feedback\">\n                  Your username is required.\n                </div>\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"email\" class=\"form-label\">Email <span class=\"text-muted\">(Optional)</span></label>\n              <input type=\"email\" class=\"form-control\" id=\"email\" placeholder=\"you@example.com\">\n              <div class=\"invalid-feedback\">\n                Please enter a valid email address for shipping updates.\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"address\" class=\"form-label\">Address</label>\n              <input type=\"text\" class=\"form-control\" id=\"address\" placeholder=\"1234 Main St\" required>\n              <div class=\"invalid-feedback\">\n                Please enter your shipping address.\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"address2\" class=\"form-label\">Address 2 <span class=\"text-muted\">(Optional)</span></label>\n              <input type=\"text\" class=\"form-control\" id=\"address2\" placeholder=\"Apartment or suite\">\n            </div>\n\n            <div class=\"col-md-5\">\n              <label for=\"country\" class=\"form-label\">Country</label>\n              <select class=\"form-select\" id=\"country\" required>\n                <option value=\"\">Choose...</option>\n                <option>United States</option>\n              </select>\n              <div class=\"invalid-feedback\">\n                Please select a valid country.\n              </div>\n            </div>\n\n            <div class=\"col-md-4\">\n              <label for=\"state\" class=\"form-label\">State</label>\n              <select class=\"form-select\" id=\"state\" required>\n                <option value=\"\">Choose...</option>\n                <option>California</option>\n              </select>\n              <div class=\"invalid-feedback\">\n                Please provide a valid state.\n              </div>\n            </div>\n\n            <div class=\"col-md-3\">\n              <label for=\"zip\" class=\"form-label\">Zip</label>\n              <input type=\"text\" class=\"form-control\" id=\"zip\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                Zip code required.\n              </div>\n            </div>\n          </div>\n\n          <hr class=\"my-4\">\n\n          <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"same-address\">\n            <label class=\"form-check-label\" for=\"same-address\">Shipping address is the same as my billing address</label>\n          </div>\n\n          <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"save-info\">\n            <label class=\"form-check-label\" for=\"save-info\">Save this information for next time</label>\n          </div>\n\n          <hr class=\"my-4\">\n\n          <h4 class=\"mb-3\">Payment</h4>\n\n          <div class=\"my-3\">\n            <div class=\"form-check\">\n              <input id=\"credit\" name=\"paymentMethod\" type=\"radio\" class=\"form-check-input\" checked required>\n              <label class=\"form-check-label\" for=\"credit\">Credit card</label>\n            </div>\n            <div class=\"form-check\">\n              <input id=\"debit\" name=\"paymentMethod\" type=\"radio\" class=\"form-check-input\" required>\n              <label class=\"form-check-label\" for=\"debit\">Debit card</label>\n            </div>\n            <div class=\"form-check\">\n              <input id=\"paypal\" name=\"paymentMethod\" type=\"radio\" class=\"form-check-input\" required>\n              <label class=\"form-check-label\" for=\"paypal\">PayPal</label>\n            </div>\n          </div>\n\n          <div class=\"row gy-3\">\n            <div class=\"col-md-6\">\n              <label for=\"cc-name\" class=\"form-label\">Name on card</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-name\" placeholder=\"\" required>\n              <small class=\"text-muted\">Full name as displayed on card</small>\n              <div class=\"invalid-feedback\">\n                Name on card is required\n              </div>\n            </div>\n\n            <div class=\"col-md-6\">\n              <label for=\"cc-number\" class=\"form-label\">Credit card number</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-number\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                Credit card number is required\n              </div>\n            </div>\n\n            <div class=\"col-md-3\">\n              <label for=\"cc-expiration\" class=\"form-label\">Expiration</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-expiration\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                Expiration date required\n              </div>\n            </div>\n\n            <div class=\"col-md-3\">\n              <label for=\"cc-cvv\" class=\"form-label\">CVV</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-cvv\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                Security code required\n              </div>\n            </div>\n          </div>\n\n          <hr class=\"my-4\">\n\n          <button class=\"w-100 btn btn-primary btn-lg\" type=\"submit\">Continue to checkout</button>\n        </form>\n      </div>\n    </div>\n  </main>\n\n  <footer class=\"my-5 pt-5 text-muted text-center text-small\">\n    <p class=\"mb-1\">&copy; 2017–{{< year >}} Company Name</p>\n    <ul class=\"list-inline\">\n      <li class=\"list-inline-item\"><a href=\"#\">Privacy</a></li>\n      <li class=\"list-inline-item\"><a href=\"#\">Terms</a></li>\n      <li class=\"list-inline-item\"><a href=\"#\">Support</a></li>\n    </ul>\n  </footer>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/checkout-rtl/index.html",
    "content": "---\nlayout: examples\ntitle: مثال إتمام الشراء\ndirection: rtl\nextra_css:\n  - \"../checkout/form-validation.css\"\nextra_js:\n  - src: \"../checkout/form-validation.js\"\nbody_class: \"bg-light\"\n---\n\n<div class=\"container\">\n  <main>\n    <div class=\"py-5 text-center\">\n      <img class=\"d-block mx-auto mb-4\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"\" width=\"72\" height=\"57\">\n      <h2>نموذج إتمام الشراء</h2>\n      <p class=\"lead\">فيما يلي مثال على نموذج تم إنشاؤه بالكامل باستخدام عناصر تحكم النموذج في Bootstrap. لكل مجموعة نماذج مطلوبة حالة تحقق يمكن تشغيلها بمحاولة إرسال النموذج دون استكماله.</p>\n    </div>\n\n    <div class=\"row g-3\">\n      <div class=\"col-md-5 col-lg-4 order-md-last\">\n        <h4 class=\"d-flex justify-content-between align-items-center mb-3\">\n          <span class=\"text-muted\">عربة التسوق</span>\n          <span class=\"badge bg-secondary rounded-pill\">3</span>\n        </h4>\n        <ul class=\"list-group mb-3\">\n          <li class=\"list-group-item d-flex justify-content-between lh-sm\">\n            <div>\n              <h6 class=\"my-0\">اسم المنتج</h6>\n              <small class=\"text-muted\">وصف مختصر</small>\n            </div>\n            <span class=\"text-muted\">$12</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between lh-sm\">\n            <div>\n              <h6 class=\"my-0\">المنتج الثاني</h6>\n              <small class=\"text-muted\">وصف مختصر</small>\n            </div>\n            <span class=\"text-muted\">$8</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between lh-sm\">\n            <div>\n              <h6 class=\"my-0\">البند الثالث</h6>\n              <small class=\"text-muted\">وصف مختصر</small>\n            </div>\n            <span class=\"text-muted\">$5</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between bg-light\">\n            <div class=\"text-success\">\n              <h6 class=\"my-0\">رمز ترويجي</h6>\n              <small>EXAMPLECODE</small>\n            </div>\n            <span class=\"text-success\">-$5</span>\n          </li>\n          <li class=\"list-group-item d-flex justify-content-between\">\n            <span>مجموع (USD)</span>\n            <strong>$20</strong>\n          </li>\n        </ul>\n\n        <form class=\"card p-2\">\n          <div class=\"input-group\">\n            <input type=\"text\" class=\"form-control\" placeholder=\"رمز ترويجي\">\n            <button type=\"submit\" class=\"btn btn-secondary\">تحقق</button>\n          </div>\n        </form>\n      </div>\n      <div class=\"col-md-7 col-lg-8\">\n        <h4 class=\"mb-3\">عنوان الفوترة</h4>\n        <form class=\"needs-validation\" novalidate>\n          <div class=\"row g-3\">\n            <div class=\"col-sm-6\">\n              <label for=\"firstName\" class=\"form-label\">الاسم الأول</label>\n              <input type=\"text\" class=\"form-control\" id=\"firstName\" placeholder=\"\" value=\"\" required>\n              <div class=\"invalid-feedback\">\n                يرجى إدخال اسم أول صحيح.\n              </div>\n            </div>\n\n            <div class=\"col-sm-6\">\n              <label for=\"lastName\" class=\"form-label\">اسم العائلة</label>\n              <input type=\"text\" class=\"form-control\" id=\"lastName\" placeholder=\"\" value=\"\" required>\n              <div class=\"invalid-feedback\">\n                يرجى إدخال اسم عائلة صحيح.\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"username\" class=\"form-label\">اسم المستخدم</label>\n              <div class=\"input-group has-validation\">\n                <span class=\"input-group-text\">@</span>\n                <input type=\"text\" class=\"form-control\" id=\"username\" placeholder=\"اسم المستخدم\" required>\n              <div class=\"invalid-feedback\">\n                اسم المستخدم الخاص بك مطلوب.\n                </div>\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"email\" class=\"form-label\">البريد الإلكتروني <span class=\"text-muted\">(اختياري)</span></label>\n              <input type=\"email\" class=\"form-control\" id=\"email\" placeholder=\"you@example.com\">\n              <div class=\"invalid-feedback\">\n                يرجى إدخال عنوان بريد إلكتروني صحيح لتصلكم تحديثات الشحن.\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"address\" class=\"form-label\">العنوان</label>\n              <input type=\"text\" class=\"form-control\" id=\"address\" placeholder=\"1234 الشارع الأول\" required>\n              <div class=\"invalid-feedback\">\n                يرجى إدخال عنوان الشحن الخاص بك.\n              </div>\n            </div>\n\n            <div class=\"col-12\">\n              <label for=\"address2\" class=\"form-label\">عنوان 2 <span class=\"text-muted\">(اختياري)</span></label>\n              <input type=\"text\" class=\"form-control\" id=\"address2\" placeholder=\"شقة 24\">\n            </div>\n\n            <div class=\"col-md-5\">\n              <label for=\"country\" class=\"form-label\">البلد</label>\n              <select class=\"form-select\" id=\"country\" required>\n                <option value=\"\">اختر...</option>\n                <option>الولايات المتحدة الأمريكية</option>\n              </select>\n              <div class=\"invalid-feedback\">\n                يرجى اختيار بلد صحيح.\n              </div>\n            </div>\n\n            <div class=\"col-md-4\">\n              <label for=\"state\" class=\"form-label\">المنطقة</label>\n              <select class=\"form-select\" id=\"state\" required>\n                <option value=\"\">اختر...</option>\n                <option>كاليفورنيا</option>\n              </select>\n              <div class=\"invalid-feedback\">\n                يرجى اختيار اسم منطقة صحيح.\n              </div>\n            </div>\n\n            <div class=\"col-md-3\">\n              <label for=\"zip\" class=\"form-label\">الرمز البريدي</label>\n              <input type=\"text\" class=\"form-control\" id=\"zip\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                الرمز البريدي مطلوب.\n              </div>\n            </div>\n          </div>\n\n          <hr class=\"my-4\">\n\n          <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"same-address\">\n            <label class=\"form-check-label\" for=\"same-address\">عنوان الشحن هو نفس عنوان الفوترة الخاص بي</label>\n          </div>\n\n          <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"save-info\">\n            <label class=\"form-check-label\" for=\"save-info\">احفظ هذه المعلومات في المرة القادمة</label>\n          </div>\n\n          <hr class=\"my-4\">\n\n          <h4 class=\"mb-3\">طريقة الدفع</h4>\n\n          <div class=\"my-3\">\n            <div class=\"form-check\">\n              <input id=\"credit\" name=\"paymentMethod\" type=\"radio\" class=\"form-check-input\" checked required>\n              <label class=\"form-check-label\" for=\"credit\">بطاقة ائتمان</label>\n            </div>\n            <div class=\"form-check\">\n              <input id=\"cash\" name=\"paymentMethod\" type=\"radio\" class=\"form-check-input\" required>\n              <label class=\"form-check-label\" for=\"cash\">نقد</label>\n            </div>\n            <div class=\"form-check\">\n              <input id=\"paypal\" name=\"paymentMethod\" type=\"radio\" class=\"form-check-input\" required>\n              <label class=\"form-check-label\" for=\"paypal\">PayPal</label>\n            </div>\n          </div>\n\n          <div class=\"row gy-3\">\n            <div class=\"col-md-6\">\n              <label for=\"cc-name\" class=\"form-label\">الاسم على البطاقة</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-name\" placeholder=\"\" required>\n              <small class=\"text-muted\">الاسم الكامل كما هو معروض على البطاقة</small>\n              <div class=\"invalid-feedback\">\n                الاسم على البطاقة مطلوب\n              </div>\n            </div>\n\n            <div class=\"col-md-6\">\n              <label for=\"cc-number\" class=\"form-label\">رقم البطاقة</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-number\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                رقم بطاقة الائتمان مطلوب\n              </div>\n            </div>\n\n            <div class=\"col-md-3\">\n              <label for=\"cc-expiration\" class=\"form-label\">تاريخ انتهاء الصلاحية</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-expiration\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                تاريخ انتهاء الصلاحية مطلوب\n              </div>\n            </div>\n\n            <div class=\"col-md-3\">\n              <label for=\"cc-cvv\" class=\"form-label\">الرمز الثلاثي (CVV)</label>\n              <input type=\"text\" class=\"form-control\" id=\"cc-cvv\" placeholder=\"\" required>\n              <div class=\"invalid-feedback\">\n                رمز الحماية مطلوب\n              </div>\n            </div>\n          </div>\n\n          <hr class=\"my-4\">\n\n          <button class=\"w-100 btn btn-primary btn-lg\" type=\"submit\">الاستمرار بالدفع</button>\n        </form>\n      </div>\n    </div>\n  </main>\n  <footer class=\"my-5 pt-5 text-muted text-center text-small\">\n    <p class=\"mb-1\">&copy; {{< year >}}-2017 اسم الشركة</p>\n    <ul class=\"list-inline\">\n      <li class=\"list-inline-item\"><a href=\"#\">سياسة الخصوصية</a></li>\n      <li class=\"list-inline-item\"><a href=\"#\">اتفاقية الاستخدام</a></li>\n      <li class=\"list-inline-item\"><a href=\"#\">الدعم الفني</a></li>\n    </ul>\n  </footer>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cover/cover.css",
    "content": "/*\n * Globals\n */\n\n\n/* Custom default button */\n.btn-secondary,\n.btn-secondary:hover,\n.btn-secondary:focus {\n  color: #333;\n  text-shadow: none; /* Prevent inheritance from `body` */\n}\n\n\n/*\n * Base structure\n */\n\nbody {\n  text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);\n  box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);\n}\n\n.cover-container {\n  max-width: 42em;\n}\n\n\n/*\n * Header\n */\n\n.nav-masthead .nav-link {\n  color: rgba(255, 255, 255, .5);\n  border-bottom: .25rem solid transparent;\n}\n\n.nav-masthead .nav-link:hover,\n.nav-masthead .nav-link:focus {\n  border-bottom-color: rgba(255, 255, 255, .25);\n}\n\n.nav-masthead .nav-link + .nav-link {\n  margin-left: 1rem;\n}\n\n.nav-masthead .active {\n  color: #fff;\n  border-bottom-color: #fff;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/cover/index.html",
    "content": "---\nlayout: examples\ntitle: Cover Template\nextra_css:\n  - \"cover.css\"\nhtml_class: \"h-100\"\nbody_class: \"d-flex h-100 text-center text-bg-dark\"\ninclude_js: false\n---\n\n<div class=\"cover-container d-flex w-100 h-100 p-3 mx-auto flex-column\">\n  <header class=\"mb-auto\">\n    <div>\n      <h3 class=\"float-md-start mb-0\">Cover</h3>\n      <nav class=\"nav nav-masthead justify-content-center float-md-end\">\n        <a class=\"nav-link fw-bold py-1 px-0 active\" aria-current=\"page\" href=\"#\">Home</a>\n        <a class=\"nav-link fw-bold py-1 px-0\" href=\"#\">Features</a>\n        <a class=\"nav-link fw-bold py-1 px-0\" href=\"#\">Contact</a>\n      </nav>\n    </div>\n  </header>\n\n  <main class=\"px-3\">\n    <h1>Cover your page.</h1>\n    <p class=\"lead\">Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.</p>\n    <p class=\"lead\">\n      <a href=\"#\" class=\"btn btn-lg btn-secondary fw-bold border-white bg-white\">Learn more</a>\n    </p>\n  </main>\n\n  <footer class=\"mt-auto text-white-50\">\n    <p>Cover template for <a href=\"https://getbootstrap.com/\" class=\"text-white\">Bootstrap</a>, by <a href=\"https://twitter.com/mdo\" class=\"text-white\">@mdo</a>.</p>\n  </footer>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dashboard/dashboard.css",
    "content": "body {\n  font-size: .875rem;\n}\n\n.feather {\n  width: 16px;\n  height: 16px;\n}\n\n/*\n * Sidebar\n */\n\n.sidebar {\n  position: fixed;\n  top: 0;\n  /* rtl:raw:\n  right: 0;\n  */\n  bottom: 0;\n  /* rtl:remove */\n  left: 0;\n  z-index: 100; /* Behind the navbar */\n  padding: 48px 0 0; /* Height of navbar */\n  box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);\n}\n\n@media (max-width: 767.98px) {\n  .sidebar {\n    top: 5rem;\n  }\n}\n\n.sidebar-sticky {\n  height: calc(100vh - 48px);\n  overflow-x: hidden;\n  overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */\n}\n\n.sidebar .nav-link {\n  font-weight: 500;\n  color: #333;\n}\n\n.sidebar .nav-link .feather {\n  margin-right: 4px;\n  color: #727272;\n}\n\n.sidebar .nav-link.active {\n  color: #2470dc;\n}\n\n.sidebar .nav-link:hover .feather,\n.sidebar .nav-link.active .feather {\n  color: inherit;\n}\n\n.sidebar-heading {\n  font-size: .75rem;\n}\n\n/*\n * Navbar\n */\n\n.navbar-brand {\n  padding-top: .75rem;\n  padding-bottom: .75rem;\n  background-color: rgba(0, 0, 0, .25);\n  box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);\n}\n\n.navbar .navbar-toggler {\n  top: .25rem;\n  right: 1rem;\n}\n\n.navbar .form-control {\n  padding: .75rem 1rem;\n}\n\n.form-control-dark {\n  color: #fff;\n  background-color: rgba(255, 255, 255, .1);\n  border-color: rgba(255, 255, 255, .1);\n}\n\n.form-control-dark:focus {\n  border-color: transparent;\n  box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dashboard/dashboard.js",
    "content": "/* globals Chart:false, feather:false */\n\n(() => {\n  'use strict'\n\n  feather.replace({ 'aria-hidden': 'true' })\n\n  // Graphs\n  const ctx = document.getElementById('myChart')\n  // eslint-disable-next-line no-unused-vars\n  const myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n      labels: [\n        'Sunday',\n        'Monday',\n        'Tuesday',\n        'Wednesday',\n        'Thursday',\n        'Friday',\n        'Saturday'\n      ],\n      datasets: [{\n        data: [\n          15339,\n          21345,\n          18483,\n          24003,\n          23489,\n          24092,\n          12034\n        ],\n        lineTension: 0,\n        backgroundColor: 'transparent',\n        borderColor: '#007bff',\n        borderWidth: 4,\n        pointBackgroundColor: '#007bff'\n      }]\n    },\n    options: {\n      scales: {\n        yAxes: [{\n          ticks: {\n            beginAtZero: false\n          }\n        }]\n      },\n      legend: {\n        display: false\n      }\n    }\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dashboard/dashboard.rtl.css",
    "content": "body {\n  font-size: .875rem;\n}\n\n.feather {\n  width: 16px;\n  height: 16px;\n}\n\n/*\n * Sidebar\n */\n\n.sidebar {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 100; /* Behind the navbar */\n  padding: 48px 0 0; /* Height of navbar */\n  box-shadow: inset 1px 0 0 rgba(0, 0, 0, .1);\n}\n\n@media (max-width: 767.98px) {\n  .sidebar {\n    top: 5rem;\n  }\n}\n\n.sidebar-sticky {\n  height: calc(100vh - 48px);\n  overflow-x: hidden;\n  overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */\n}\n\n.sidebar .nav-link {\n  font-weight: 500;\n  color: #333;\n}\n\n.sidebar .nav-link .feather {\n  margin-left: 4px;\n  color: #727272;\n}\n\n.sidebar .nav-link.active {\n  color: #2470dc;\n}\n\n.sidebar .nav-link:hover .feather,\n.sidebar .nav-link.active .feather {\n  color: inherit;\n}\n\n.sidebar-heading {\n  font-size: .75rem;\n}\n\n/*\n * Navbar\n */\n\n.navbar-brand {\n  padding-top: .75rem;\n  padding-bottom: .75rem;\n  background-color: rgba(0, 0, 0, .25);\n  box-shadow: inset 1px 0 0 rgba(0, 0, 0, .25);\n}\n\n.navbar .navbar-toggler {\n  top: .25rem;\n  left: 1rem;\n}\n\n.navbar .form-control {\n  padding: .75rem 1rem;\n}\n\n.form-control-dark {\n  color: #fff;\n  background-color: rgba(255, 255, 255, .1);\n  border-color: rgba(255, 255, 255, .1);\n}\n\n.form-control-dark:focus {\n  border-color: transparent;\n  box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dashboard/index.html",
    "content": "---\nlayout: examples\ntitle: Dashboard Template\nextra_css:\n  - \"dashboard.css\"\nextra_js:\n  - src: \"https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js\"\n    integrity: \"sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE\"\n  - src: \"https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js\"\n    integrity: \"sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha\"\n  - src: \"dashboard.js\"\n---\n\n<header class=\"navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow\">\n  <a class=\"navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6\" href=\"#\">Company name</a>\n  <button class=\"navbar-toggler position-absolute d-md-none collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#sidebarMenu\" aria-controls=\"sidebarMenu\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n    <span class=\"navbar-toggler-icon\"></span>\n  </button>\n  <input class=\"form-control form-control-dark w-100 rounded-0 border-0\" type=\"text\" placeholder=\"Search\" aria-label=\"Search\">\n  <div class=\"navbar-nav\">\n    <div class=\"nav-item text-nowrap\">\n      <a class=\"nav-link px-3\" href=\"#\">Sign out</a>\n    </div>\n  </div>\n</header>\n\n<div class=\"container-fluid\">\n  <div class=\"row\">\n    <nav id=\"sidebarMenu\" class=\"col-md-3 col-lg-2 d-md-block bg-light sidebar collapse\">\n      <div class=\"position-sticky pt-3 sidebar-sticky\">\n        <ul class=\"nav flex-column\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">\n              <span data-feather=\"home\" class=\"align-text-bottom\"></span>\n              Dashboard\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file\" class=\"align-text-bottom\"></span>\n              Orders\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"shopping-cart\" class=\"align-text-bottom\"></span>\n              Products\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"users\" class=\"align-text-bottom\"></span>\n              Customers\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"bar-chart-2\" class=\"align-text-bottom\"></span>\n              Reports\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"layers\" class=\"align-text-bottom\"></span>\n              Integrations\n            </a>\n          </li>\n        </ul>\n\n        <h6 class=\"sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase\">\n          <span>Saved reports</span>\n          <a class=\"link-secondary\" href=\"#\" aria-label=\"Add a new report\">\n            <span data-feather=\"plus-circle\" class=\"align-text-bottom\"></span>\n          </a>\n        </h6>\n        <ul class=\"nav flex-column mb-2\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              Current month\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              Last quarter\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              Social engagement\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              Year-end sale\n            </a>\n          </li>\n        </ul>\n      </div>\n    </nav>\n\n    <main class=\"col-md-9 ms-sm-auto col-lg-10 px-md-4\">\n      <div class=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\">\n        <h1 class=\"h2\">Dashboard</h1>\n        <div class=\"btn-toolbar mb-2 mb-md-0\">\n          <div class=\"btn-group me-2\">\n            <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Share</button>\n            <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">Export</button>\n          </div>\n          <button type=\"button\" class=\"btn btn-sm btn-outline-secondary dropdown-toggle\">\n            <span data-feather=\"calendar\" class=\"align-text-bottom\"></span>\n            This week\n          </button>\n        </div>\n      </div>\n\n      <canvas class=\"my-4 w-100\" id=\"myChart\" width=\"900\" height=\"380\"></canvas>\n\n      <h2>Section title</h2>\n      <div class=\"table-responsive\">\n        <table class=\"table table-striped table-sm\">\n          <thead>\n            <tr>\n              <th scope=\"col\">#</th>\n              <th scope=\"col\">Header</th>\n              <th scope=\"col\">Header</th>\n              <th scope=\"col\">Header</th>\n              <th scope=\"col\">Header</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td>1,001</td>\n              <td>random</td>\n              <td>data</td>\n              <td>placeholder</td>\n              <td>text</td>\n            </tr>\n            <tr>\n              <td>1,002</td>\n              <td>placeholder</td>\n              <td>irrelevant</td>\n              <td>visual</td>\n              <td>layout</td>\n            </tr>\n            <tr>\n              <td>1,003</td>\n              <td>data</td>\n              <td>rich</td>\n              <td>dashboard</td>\n              <td>tabular</td>\n            </tr>\n            <tr>\n              <td>1,003</td>\n              <td>information</td>\n              <td>placeholder</td>\n              <td>illustrative</td>\n              <td>data</td>\n            </tr>\n            <tr>\n              <td>1,004</td>\n              <td>text</td>\n              <td>random</td>\n              <td>layout</td>\n              <td>dashboard</td>\n            </tr>\n            <tr>\n              <td>1,005</td>\n              <td>dashboard</td>\n              <td>irrelevant</td>\n              <td>text</td>\n              <td>placeholder</td>\n            </tr>\n            <tr>\n              <td>1,006</td>\n              <td>dashboard</td>\n              <td>illustrative</td>\n              <td>rich</td>\n              <td>data</td>\n            </tr>\n            <tr>\n              <td>1,007</td>\n              <td>placeholder</td>\n              <td>tabular</td>\n              <td>information</td>\n              <td>irrelevant</td>\n            </tr>\n            <tr>\n              <td>1,008</td>\n              <td>random</td>\n              <td>data</td>\n              <td>placeholder</td>\n              <td>text</td>\n            </tr>\n            <tr>\n              <td>1,009</td>\n              <td>placeholder</td>\n              <td>irrelevant</td>\n              <td>visual</td>\n              <td>layout</td>\n            </tr>\n            <tr>\n              <td>1,010</td>\n              <td>data</td>\n              <td>rich</td>\n              <td>dashboard</td>\n              <td>tabular</td>\n            </tr>\n            <tr>\n              <td>1,011</td>\n              <td>information</td>\n              <td>placeholder</td>\n              <td>illustrative</td>\n              <td>data</td>\n            </tr>\n            <tr>\n              <td>1,012</td>\n              <td>text</td>\n              <td>placeholder</td>\n              <td>layout</td>\n              <td>dashboard</td>\n            </tr>\n            <tr>\n              <td>1,013</td>\n              <td>dashboard</td>\n              <td>irrelevant</td>\n              <td>text</td>\n              <td>visual</td>\n            </tr>\n            <tr>\n              <td>1,014</td>\n              <td>dashboard</td>\n              <td>illustrative</td>\n              <td>rich</td>\n              <td>data</td>\n            </tr>\n            <tr>\n              <td>1,015</td>\n              <td>random</td>\n              <td>tabular</td>\n              <td>information</td>\n              <td>text</td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </main>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dashboard-rtl/dashboard.js",
    "content": "/* globals Chart:false, feather:false */\n\n(() => {\n  'use strict'\n\n  feather.replace({ 'aria-hidden': 'true' })\n\n  // Graphs\n  const ctx = document.getElementById('myChart')\n  // eslint-disable-next-line no-unused-vars\n  const myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n      labels: [\n        'الأحد',\n        'الإثنين',\n        'الثلاثاء',\n        'الأربعاء',\n        'الخميس',\n        'الجمعة',\n        'السبت'\n      ],\n      datasets: [{\n        data: [\n          15339,\n          21345,\n          18483,\n          24003,\n          23489,\n          24092,\n          12034\n        ],\n        lineTension: 0,\n        backgroundColor: 'transparent',\n        borderColor: '#007bff',\n        borderWidth: 4,\n        pointBackgroundColor: '#007bff'\n      }]\n    },\n    options: {\n      scales: {\n        yAxes: [{\n          ticks: {\n            beginAtZero: false\n          }\n        }]\n      },\n      legend: {\n        display: false\n      }\n    }\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dashboard-rtl/index.html",
    "content": "---\nlayout: examples\ntitle: قالب لوحة القيادة\ndirection: rtl\nextra_css:\n  - \"../dashboard/dashboard.rtl.css\"\nextra_js:\n  - src: \"https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js\"\n    integrity: \"sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE\"\n  - src: \"https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js\"\n    integrity: \"sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha\"\n  - src: \"dashboard.js\"\n---\n\n<header class=\"navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow\">\n  <a class=\"navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6\" href=\"#\">اسم الشركة</a>\n  <button class=\"navbar-toggler position-absolute d-md-none collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#sidebarMenu\" aria-controls=\"sidebarMenu\" aria-expanded=\"false\" aria-label=\"عرض/إخفاء لوحة التنقل\">\n    <span class=\"navbar-toggler-icon\"></span>\n  </button>\n  <input class=\"form-control form-control-dark w-100 rounded-0 border-0\" type=\"text\" placeholder=\"بحث\" aria-label=\"بحث\">\n  <div class=\"navbar-nav\">\n    <div class=\"nav-item text-nowrap\">\n      <a class=\"nav-link px-3\" href=\"#\">تسجيل الخروج</a>\n    </div>\n  </div>\n</header>\n\n<div class=\"container-fluid\">\n  <div class=\"row\">\n    <nav id=\"sidebarMenu\" class=\"col-md-3 col-lg-2 d-md-block bg-light sidebar collapse\">\n      <div class=\"position-sticky pt-3 sidebar-sticky\">\n        <ul class=\"nav flex-column\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">\n              <span data-feather=\"home\" class=\"align-text-bottom\"></span>\n              لوحة القيادة\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file\" class=\"align-text-bottom\"></span>\n              الطلبات\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"shopping-cart\" class=\"align-text-bottom\"></span>\n              المنتجات\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"users\" class=\"align-text-bottom\"></span>\n              الزبائن\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"bar-chart-2\" class=\"align-text-bottom\"></span>\n              التقارير\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"layers\" class=\"align-text-bottom\"></span>\n              التكاملات\n            </a>\n          </li>\n        </ul>\n\n        <h6 class=\"sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase\">\n          <span>التقارير المحفوظة</span>\n          <a class=\"link-secondary\" href=\"#\" aria-label=\"إضافة تقرير جديد\">\n            <span data-feather=\"plus-circle\" class=\"align-text-bottom\"></span>\n          </a>\n        </h6>\n        <ul class=\"nav flex-column mb-2\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              الشهر الحالي\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              الربع الأخير\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              التفاعل الإجتماعي\n            </a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">\n              <span data-feather=\"file-text\" class=\"align-text-bottom\"></span>\n              مبيعات نهاية العام\n            </a>\n          </li>\n        </ul>\n      </div>\n    </nav>\n\n    <main class=\"col-md-9 ms-sm-auto col-lg-10 px-md-4\">\n      <div class=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\">\n        <h1 class=\"h2\">لوحة القيادة</h1>\n        <div class=\"btn-toolbar mb-2 mb-md-0\">\n          <div class=\"btn-group me-2\">\n            <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">مشاركة</button>\n            <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\">تصدير</button>\n          </div>\n          <button type=\"button\" class=\"btn btn-sm btn-outline-secondary dropdown-toggle\">\n            <span data-feather=\"calendar\" class=\"align-text-bottom\"></span>\n            هذا الأسبوع\n          </button>\n        </div>\n      </div>\n\n      <canvas class=\"my-4 w-100\" id=\"myChart\" width=\"900\" height=\"380\"></canvas>\n\n      <h2>عنوان القسم</h2>\n      <div class=\"table-responsive\">\n        <table class=\"table table-striped table-sm\">\n          <thead>\n            <tr>\n              <th scope=\"col\">#</th>\n              <th scope=\"col\">عنوان</th>\n              <th scope=\"col\">عنوان</th>\n              <th scope=\"col\">عنوان</th>\n              <th scope=\"col\">عنوان</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td>1,001</td>\n              <td>بيانات</td>\n              <td>عشوائية</td>\n              <td>تثري</td>\n              <td>الجدول</td>\n            </tr>\n            <tr>\n              <td>1,002</td>\n              <td>تثري</td>\n              <td>مبهة</td>\n              <td>تصميم</td>\n              <td>تنسيق</td>\n            </tr>\n            <tr>\n              <td>1,003</td>\n              <td>عشوائية</td>\n              <td>غنية</td>\n              <td>قيمة</td>\n              <td>مفيدة</td>\n            </tr>\n            <tr>\n              <td>1,003</td>\n              <td>معلومات</td>\n              <td>تثري</td>\n              <td>توضيحية</td>\n              <td>عشوائية</td>\n            </tr>\n            <tr>\n              <td>1,004</td>\n              <td>الجدول</td>\n              <td>بيانات</td>\n              <td>تنسيق</td>\n              <td>قيمة</td>\n            </tr>\n            <tr>\n              <td>1,005</td>\n              <td>قيمة</td>\n              <td>مبهة</td>\n              <td>الجدول</td>\n              <td>تثري</td>\n            </tr>\n            <tr>\n              <td>1,006</td>\n              <td>قيمة</td>\n              <td>توضيحية</td>\n              <td>غنية</td>\n              <td>عشوائية</td>\n            </tr>\n            <tr>\n              <td>1,007</td>\n              <td>تثري</td>\n              <td>مفيدة</td>\n              <td>معلومات</td>\n              <td>مبهة</td>\n            </tr>\n            <tr>\n              <td>1,008</td>\n              <td>بيانات</td>\n              <td>عشوائية</td>\n              <td>تثري</td>\n              <td>الجدول</td>\n            </tr>\n            <tr>\n              <td>1,009</td>\n              <td>تثري</td>\n              <td>مبهة</td>\n              <td>تصميم</td>\n              <td>تنسيق</td>\n            </tr>\n            <tr>\n              <td>1,010</td>\n              <td>عشوائية</td>\n              <td>غنية</td>\n              <td>قيمة</td>\n              <td>مفيدة</td>\n            </tr>\n            <tr>\n              <td>1,011</td>\n              <td>معلومات</td>\n              <td>تثري</td>\n              <td>توضيحية</td>\n              <td>عشوائية</td>\n            </tr>\n            <tr>\n              <td>1,012</td>\n              <td>الجدول</td>\n              <td>تثري</td>\n              <td>تنسيق</td>\n              <td>قيمة</td>\n            </tr>\n            <tr>\n              <td>1,013</td>\n              <td>قيمة</td>\n              <td>مبهة</td>\n              <td>الجدول</td>\n              <td>تصميم</td>\n            </tr>\n            <tr>\n              <td>1,014</td>\n              <td>قيمة</td>\n              <td>توضيحية</td>\n              <td>غنية</td>\n              <td>عشوائية</td>\n            </tr>\n            <tr>\n              <td>1,015</td>\n              <td>بيانات</td>\n              <td>مفيدة</td>\n              <td>معلومات</td>\n              <td>الجدول</td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </main>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dropdowns/dropdowns.css",
    "content": ".dropdown-menu {\n  margin: 4rem auto;\n}\n\n.dropdown-item-danger {\n  color: var(--bs-red);\n}\n.dropdown-item-danger:hover,\n.dropdown-item-danger:focus {\n  color: #fff;\n  background-color: var(--bs-red);\n}\n.dropdown-item-danger.active {\n  background-color: var(--bs-red);\n}\n\n.btn-hover-light {\n  background-color: var(--bs-white);\n}\n.btn-hover-light:hover,\n.btn-hover-light:focus {\n  color: var(--bs-blue);\n  background-color: var(--bs-light);\n}\n\n.cal-month,\n.cal-days,\n.cal-weekdays {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  align-items: center;\n}\n.cal-month-name {\n  grid-column-start: 2;\n  grid-column-end: 7;\n  text-align: center;\n}\n.cal-weekday,\n.cal-btn {\n  display: flex;\n  flex-shrink: 0;\n  align-items: center;\n  justify-content: center;\n  height: 3rem;\n  padding: 0;\n}\n.cal-btn:not([disabled]) {\n  font-weight: 500;\n}\n.cal-btn:hover,\n.cal-btn:focus {\n  background-color: rgba(0, 0, 0, .05);\n}\n.cal-btn[disabled] {\n  border: 0;\n  opacity: .5;\n}\n\n.form-control-dark {\n  background-color: rgba(255, 255, 255, .05);\n  border-color: rgba(255, 255, 255, .15);\n}\n\n\n.w-220px {\n  width: 220px;\n}\n\n.w-280px {\n  width: 280px;\n}\n\n.w-340px {\n  width: 340px;\n}\n\n.w-600px {\n  width: 600px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/dropdowns/index.html",
    "content": "---\nlayout: examples\ntitle: Dropdowns\nextra_css:\n  - \"dropdowns.css\"\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n\n  <symbol id=\"exclamation-triangle-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\"/>\n  </symbol>\n\n  <symbol id=\"check2\" viewBox=\"0 0 16 16\">\n    <path d=\"M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z\"/>\n  </symbol>\n\n  <symbol id=\"check2-circle\" viewBox=\"0 0 16 16\">\n    <path d=\"M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0z\"/>\n    <path d=\"M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z\"/>\n  </symbol>\n\n  <symbol id=\"bookmark-star\" viewBox=\"0 0 16 16\">\n    <path d=\"M7.84 4.1a.178.178 0 0 1 .32 0l.634 1.285a.178.178 0 0 0 .134.098l1.42.206c.145.021.204.2.098.303L9.42 6.993a.178.178 0 0 0-.051.158l.242 1.414a.178.178 0 0 1-.258.187l-1.27-.668a.178.178 0 0 0-.165 0l-1.27.668a.178.178 0 0 1-.257-.187l.242-1.414a.178.178 0 0 0-.05-.158l-1.03-1.001a.178.178 0 0 1 .098-.303l1.42-.206a.178.178 0 0 0 .134-.098L7.84 4.1z\"/>\n    <path d=\"M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v13.5a.5.5 0 0 1-.777.416L8 13.101l-5.223 2.815A.5.5 0 0 1 2 15.5V2zm2-1a1 1 0 0 0-1 1v12.566l4.723-2.482a.5.5 0 0 1 .554 0L13 14.566V2a1 1 0 0 0-1-1H4z\"/>\n  </symbol>\n\n  <symbol id=\"grid-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zm8 0A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm-8 8A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm8 0A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3z\"/>\n  </symbol>\n\n  <symbol id=\"stars\" viewBox=\"0 0 16 16\">\n    <path d=\"M7.657 6.247c.11-.33.576-.33.686 0l.645 1.937a2.89 2.89 0 0 0 1.829 1.828l1.936.645c.33.11.33.576 0 .686l-1.937.645a2.89 2.89 0 0 0-1.828 1.829l-.645 1.936a.361.361 0 0 1-.686 0l-.645-1.937a2.89 2.89 0 0 0-1.828-1.828l-1.937-.645a.361.361 0 0 1 0-.686l1.937-.645a2.89 2.89 0 0 0 1.828-1.828l.645-1.937zM3.794 1.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387A1.734 1.734 0 0 0 4.593 5.69l-.387 1.162a.217.217 0 0 1-.412 0L3.407 5.69A1.734 1.734 0 0 0 2.31 4.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387A1.734 1.734 0 0 0 3.407 2.31l.387-1.162zM10.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732L9.1 2.137a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L10.863.1z\"/>\n  </symbol>\n\n  <symbol id=\"film\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1zm4 0v6h8V1H4zm8 8H4v6h8V9zM1 1v2h2V1H1zm2 3H1v2h2V4zM1 7v2h2V7H1zm2 3H1v2h2v-2zm-2 3v2h2v-2H1zM15 1h-2v2h2V1zm-2 3v2h2V4h-2zm2 3h-2v2h2V7zm-2 3v2h2v-2h-2zm2 3h-2v2h2v-2z\"/>\n  </symbol>\n\n  <symbol id=\"joystick\" viewBox=\"0 0 16 16\">\n    <path d=\"M10 2a2 2 0 0 1-1.5 1.937v5.087c.863.083 1.5.377 1.5.726 0 .414-.895.75-2 .75s-2-.336-2-.75c0-.35.637-.643 1.5-.726V3.937A2 2 0 1 1 10 2z\"/>\n    <path d=\"M0 9.665v1.717a1 1 0 0 0 .553.894l6.553 3.277a2 2 0 0 0 1.788 0l6.553-3.277a1 1 0 0 0 .553-.894V9.665c0-.1-.06-.19-.152-.23L9.5 6.715v.993l5.227 2.178a.125.125 0 0 1 .001.23l-5.94 2.546a2 2 0 0 1-1.576 0l-5.94-2.546a.125.125 0 0 1 .001-.23L6.5 7.708l-.013-.988L.152 9.435a.25.25 0 0 0-.152.23z\"/>\n  </symbol>\n\n  <symbol id=\"music-note-beamed\" viewBox=\"0 0 16 16\">\n    <path d=\"M6 13c0 1.105-1.12 2-2.5 2S1 14.105 1 13c0-1.104 1.12-2 2.5-2s2.5.896 2.5 2zm9-2c0 1.105-1.12 2-2.5 2s-2.5-.895-2.5-2 1.12-2 2.5-2 2.5.895 2.5 2z\"/>\n    <path fill-rule=\"evenodd\" d=\"M14 11V2h1v9h-1zM6 3v10H5V3h1z\"/>\n    <path d=\"M5 2.905a1 1 0 0 1 .9-.995l8-.8a1 1 0 0 1 1.1.995V3L5 4V2.905z\"/>\n  </symbol>\n\n  <symbol id=\"files\" viewBox=\"0 0 16 16\">\n    <path d=\"M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13V4a2 2 0 0 0-2-2H5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zM3 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z\"/>\n  </symbol>\n\n  <symbol id=\"image-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M.002 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-12a2 2 0 0 1-2-2V3zm1 9v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12zm5-6.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0z\"/>\n  </symbol>\n\n  <symbol id=\"trash\" viewBox=\"0 0 16 16\">\n    <path d=\"M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z\"/>\n    <path fill-rule=\"evenodd\" d=\"M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z\"/>\n  </symbol>\n\n  <symbol id=\"question-circle\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z\"/>\n    <path d=\"M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z\"/>\n  </symbol>\n\n  <symbol id=\"arrow-left-short\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M12 8a.5.5 0 0 1-.5.5H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5a.5.5 0 0 1 .5.5z\"/>\n  </symbol>\n\n  <symbol id=\"arrow-right-short\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z\"/>\n  </symbol>\n</svg>\n\n<div class=\"d-flex gap-5 justify-content-center\">\n  <ul class=\"dropdown-menu position-static d-grid gap-1 p-2 rounded-3 mx-0 shadow w-220px\">\n    <li><a class=\"dropdown-item rounded-2 active\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item rounded-2\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item rounded-2\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item rounded-2\" href=\"#\">Separated link</a></li>\n  </ul>\n  <ul class=\"dropdown-menu dropdown-menu-dark position-static d-grid gap-1 p-2 rounded-3 mx-0 border-0 shadow w-220px\">\n    <li><a class=\"dropdown-item rounded-2 active\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item rounded-2\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item rounded-2\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item rounded-2\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"d-flex gap-5 justify-content-center\">\n  <div class=\"dropdown-menu d-block position-static pt-0 mx-0 rounded-3 shadow overflow-hidden w-280px\">\n    <form class=\"p-2 mb-2 bg-light border-bottom\">\n      <input type=\"search\" class=\"form-control\" autocomplete=\"false\" placeholder=\"Type to filter...\">\n    </form>\n    <ul class=\"list-unstyled mb-0\">\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-success rounded-circle p-1\"></span>\n        Action\n      </a></li>\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-primary rounded-circle p-1\"></span>\n        Another action\n      </a></li>\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-danger rounded-circle p-1\"></span>\n        Something else here\n      </a></li>\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-info rounded-circle p-1\"></span>\n        Separated link\n      </a></li>\n    </ul>\n  </div>\n\n  <div class=\"dropdown-menu dropdown-menu-dark d-block position-static border-0 pt-0 mx-0 rounded-3 shadow overflow-hidden w-280px\">\n    <form class=\"p-2 mb-2 bg-dark border-bottom border-dark\">\n      <input type=\"search\" class=\"form-control form-control-dark\" autocomplete=\"false\" placeholder=\"Type to filter...\">\n    </form>\n    <ul class=\"list-unstyled mb-0\">\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-success rounded-circle p-1\"></span>\n        Action\n      </a></li>\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-primary rounded-circle p-1\"></span>\n        Another action\n      </a></li>\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-danger rounded-circle p-1\"></span>\n        Something else here\n      </a></li>\n      <li><a class=\"dropdown-item d-flex align-items-center gap-2 py-2\" href=\"#\">\n        <span class=\"d-inline-block bg-info rounded-circle p-1\"></span>\n        Separated link\n      </a></li>\n    </ul>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"d-flex gap-5 justify-content-center\">\n  <ul class=\"dropdown-menu d-block position-static mx-0 shadow w-220px\">\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#files\"/></svg>\n        Documents\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#image-fill\"/></svg>\n        Photos\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#film\"/></svg>\n        Movies\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#music-note-beamed\"/></svg>\n        Music\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#joystick\"/></svg>\n        Games\n      </a>\n    </li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li>\n      <a class=\"dropdown-item dropdown-item-danger d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#trash\"/></svg>\n        Trash\n      </a>\n    </li>\n  </ul>\n  <ul class=\"dropdown-menu dropdown-menu-dark d-block position-static mx-0 border-0 shadow w-220px\">\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#files\"/></svg>\n        Documents\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#image-fill\"/></svg>\n        Photos\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#film\"/></svg>\n        Movies\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#music-note-beamed\"/></svg>\n        Music\n      </a>\n    </li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#joystick\"/></svg>\n        Games\n      </a>\n    </li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li>\n      <a class=\"dropdown-item d-flex gap-2 align-items-center\" href=\"#\">\n        <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#trash\"/></svg>\n        Trash\n      </a>\n    </li>\n  </ul>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"dropdown-menu d-block position-static p-2 shadow rounded-3 w-340px\">\n  <div class=\"d-grid gap-1\">\n    <div class=\"cal\">\n      <div class=\"cal-month\">\n        <button class=\"btn cal-btn\" type=\"button\">\n          <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#arrow-left-short\"/></svg>\n        </button>\n        <strong class=\"cal-month-name\">June</strong>\n        <select class=\"form-select cal-month-name d-none\">\n          <option value=\"January\">January</option>\n          <option value=\"February\">February</option>\n          <option value=\"March\">March</option>\n          <option value=\"April\">April</option>\n          <option value=\"May\">May</option>\n          <option selected value=\"June\">June</option>\n          <option value=\"July\">July</option>\n          <option value=\"August\">August</option>\n          <option value=\"September\">September</option>\n          <option value=\"October\">October</option>\n          <option value=\"November\">November</option>\n          <option value=\"December\">December</option>\n        </select>\n        <button class=\"btn cal-btn\" type=\"button\">\n          <svg class=\"bi\" width=\"16\" height=\"16\"><use xlink:href=\"#arrow-right-short\"/></svg>\n        </button>\n      </div>\n      <div class=\"cal-weekdays text-muted\">\n        <div class=\"cal-weekday\">Sun</div>\n        <div class=\"cal-weekday\">Mon</div>\n        <div class=\"cal-weekday\">Tue</div>\n        <div class=\"cal-weekday\">Wed</div>\n        <div class=\"cal-weekday\">Thu</div>\n        <div class=\"cal-weekday\">Fri</div>\n        <div class=\"cal-weekday\">Sat</div>\n      </div>\n      <div class=\"cal-days\">\n        <button class=\"btn cal-btn\" disabled type=\"button\">30</button>\n        <button class=\"btn cal-btn\" disabled type=\"button\">31</button>\n\n        <button class=\"btn cal-btn\" type=\"button\">1</button>\n        <button class=\"btn cal-btn\" type=\"button\">2</button>\n        <button class=\"btn cal-btn\" type=\"button\">3</button>\n        <button class=\"btn cal-btn\" type=\"button\">4</button>\n        <button class=\"btn cal-btn\" type=\"button\">5</button>\n        <button class=\"btn cal-btn\" type=\"button\">6</button>\n        <button class=\"btn cal-btn\" type=\"button\">7</button>\n\n        <button class=\"btn cal-btn\" type=\"button\">8</button>\n        <button class=\"btn cal-btn\" type=\"button\">9</button>\n        <button class=\"btn cal-btn\" type=\"button\">10</button>\n        <button class=\"btn cal-btn\" type=\"button\">11</button>\n        <button class=\"btn cal-btn\" type=\"button\">12</button>\n        <button class=\"btn cal-btn\" type=\"button\">13</button>\n        <button class=\"btn cal-btn\" type=\"button\">14</button>\n\n        <button class=\"btn cal-btn\" type=\"button\">15</button>\n        <button class=\"btn cal-btn\" type=\"button\">16</button>\n        <button class=\"btn cal-btn\" type=\"button\">17</button>\n        <button class=\"btn cal-btn\" type=\"button\">18</button>\n        <button class=\"btn cal-btn\" type=\"button\">19</button>\n        <button class=\"btn cal-btn\" type=\"button\">20</button>\n        <button class=\"btn cal-btn\" type=\"button\">21</button>\n\n        <button class=\"btn cal-btn\" type=\"button\">22</button>\n        <button class=\"btn cal-btn\" type=\"button\">23</button>\n        <button class=\"btn cal-btn\" type=\"button\">24</button>\n        <button class=\"btn cal-btn\" type=\"button\">25</button>\n        <button class=\"btn cal-btn\" type=\"button\">26</button>\n        <button class=\"btn cal-btn\" type=\"button\">27</button>\n        <button class=\"btn cal-btn\" type=\"button\">28</button>\n\n        <button class=\"btn cal-btn\" type=\"button\">29</button>\n        <button class=\"btn cal-btn\" type=\"button\">30</button>\n        <button class=\"btn cal-btn\" type=\"button\">31</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"dropdown-menu position-static d-flex align-items-stretch p-3 rounded-3 shadow-lg w-600px\">\n  <nav class=\"d-grid gap-2 col-8\">\n    <a href=\"#\" class=\"btn btn-hover-light rounded-2 d-flex align-items-center gap-3 py-2 px-3 lh-sm text-start\">\n      <svg class=\"bi\" width=\"32\" height=\"32\"><use xlink:href=\"#image-fill\"/></svg>\n      <div>\n        <strong class=\"d-block\">Features</strong>\n        <small>Take a tour through the product</small>\n      </div>\n    </a>\n    <a href=\"#\" class=\"btn btn-hover-light rounded-2 d-flex align-items-center gap-3 py-2 px-3 lh-sm text-start\">\n      <svg class=\"bi\" width=\"32\" height=\"32\"><use xlink:href=\"#question-circle\"/></svg>\n      <div>\n        <strong class=\"d-block\">Support</strong>\n        <small>Get help from our support crew</small>\n      </div>\n    </a>\n  </nav>\n  <div class=\"col-4\">\n    ...\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/features/features.css",
    "content": ".feature-icon {\n  width: 4rem;\n  height: 4rem;\n  border-radius: .75rem;\n}\n\n.icon-link > .bi {\n  margin-top: .125rem;\n  margin-left: .125rem;\n  fill: currentcolor;\n  transition: transform .25s ease-in-out;\n}\n.icon-link:hover > .bi {\n  transform: translate(.25rem);\n}\n\n.icon-square {\n  width: 3rem;\n  height: 3rem;\n  border-radius: .75rem;\n}\n\n.text-shadow-1 { text-shadow: 0 .125rem .25rem rgba(0, 0, 0, .25); }\n.text-shadow-2 { text-shadow: 0 .25rem .5rem rgba(0, 0, 0, .25); }\n.text-shadow-3 { text-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .25); }\n\n.card-cover {\n  background-repeat: no-repeat;\n  background-position: center center;\n  background-size: cover;\n}\n\n.feature-icon-small {\n  width: 3rem;\n  height: 3rem;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/features/index.html",
    "content": "---\nlayout: examples\ntitle: Features\nextra_css:\n  - \"features.css\"\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n  <symbol id=\"home\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z\"/>\n  </symbol>\n  <symbol id=\"speedometer2\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z\"/>\n    <path fill-rule=\"evenodd\" d=\"M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z\"/>\n  </symbol>\n  <symbol id=\"table\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z\"/>\n  </symbol>\n  <symbol id=\"people-circle\" viewBox=\"0 0 16 16\">\n    <path d=\"M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z\"/>\n    <path fill-rule=\"evenodd\" d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z\"/>\n  </symbol>\n  <symbol id=\"grid\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z\"/>\n  </symbol>\n  <symbol id=\"collection\" viewBox=\"0 0 16 16\">\n    <path d=\"M2.5 3.5a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-11zm2-2a.5.5 0 0 1 0-1h7a.5.5 0 0 1 0 1h-7zM0 13a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 16 13V6a1.5 1.5 0 0 0-1.5-1.5h-13A1.5 1.5 0 0 0 0 6v7zm1.5.5A.5.5 0 0 1 1 13V6a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-13z\"/>\n  </symbol>\n  <symbol id=\"calendar3\" viewBox=\"0 0 16 16\">\n    <path d=\"M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z\"/>\n    <path d=\"M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z\"/>\n  </symbol>\n  <symbol id=\"chat-quote-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M16 8c0 3.866-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7zM7.194 6.766a1.688 1.688 0 0 0-.227-.272 1.467 1.467 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 5.734 6C4.776 6 4 6.746 4 7.667c0 .92.776 1.666 1.734 1.666.343 0 .662-.095.931-.26-.137.389-.39.804-.81 1.22a.405.405 0 0 0 .011.59c.173.16.447.155.614-.01 1.334-1.329 1.37-2.758.941-3.706a2.461 2.461 0 0 0-.227-.4zM11 9.073c-.136.389-.39.804-.81 1.22a.405.405 0 0 0 .012.59c.172.16.446.155.613-.01 1.334-1.329 1.37-2.758.942-3.706a2.466 2.466 0 0 0-.228-.4 1.686 1.686 0 0 0-.227-.273 1.466 1.466 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 10.07 6c-.957 0-1.734.746-1.734 1.667 0 .92.777 1.666 1.734 1.666.343 0 .662-.095.931-.26z\"/>\n  </symbol>\n  <symbol id=\"cpu-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z\"/>\n    <path d=\"M5.5.5a.5.5 0 0 0-1 0V2A2.5 2.5 0 0 0 2 4.5H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2A2.5 2.5 0 0 0 4.5 14v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14a2.5 2.5 0 0 0 2.5-2.5h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14A2.5 2.5 0 0 0 11.5 2V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5zm1 4.5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3A1.5 1.5 0 0 1 6.5 5z\"/>\n  </symbol>\n  <symbol id=\"gear-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z\"/>\n  </symbol>\n  <symbol id=\"speedometer\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 2a.5.5 0 0 1 .5.5V4a.5.5 0 0 1-1 0V2.5A.5.5 0 0 1 8 2zM3.732 3.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 8a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 8zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 7.31A.91.91 0 1 0 8.85 8.569l3.434-4.297a.389.389 0 0 0-.029-.518z\"/>\n    <path fill-rule=\"evenodd\" d=\"M6.664 15.889A8 8 0 1 1 9.336.11a8 8 0 0 1-2.672 15.78zm-4.665-4.283A11.945 11.945 0 0 1 8 10c2.186 0 4.236.585 6.001 1.606a7 7 0 1 0-12.002 0z\"/>\n  </symbol>\n  <symbol id=\"toggles2\" viewBox=\"0 0 16 16\">\n    <path d=\"M9.465 10H12a2 2 0 1 1 0 4H9.465c.34-.588.535-1.271.535-2 0-.729-.195-1.412-.535-2z\"/>\n    <path d=\"M6 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 1a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm.535-10a3.975 3.975 0 0 1-.409-1H4a1 1 0 0 1 0-2h2.126c.091-.355.23-.69.41-1H4a2 2 0 1 0 0 4h2.535z\"/>\n    <path d=\"M14 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0z\"/>\n  </symbol>\n  <symbol id=\"tools\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 0L0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.356 3.356a1 1 0 0 0 1.414 0l1.586-1.586a1 1 0 0 0 0-1.414l-3.356-3.356a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3c0-.269-.035-.53-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814L1 0zm9.646 10.646a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708zM3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026L3 11z\"/>\n  </symbol>\n  <symbol id=\"chevron-right\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n  </symbol>\n  <symbol id=\"geo-fill\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999zm2.493 8.574a.5.5 0 0 1-.411.575c-.712.118-1.28.295-1.655.493a1.319 1.319 0 0 0-.37.265.301.301 0 0 0-.057.09V14l.002.008a.147.147 0 0 0 .016.033.617.617 0 0 0 .145.15c.165.13.435.27.813.395.751.25 1.82.414 3.024.414s2.273-.163 3.024-.414c.378-.126.648-.265.813-.395a.619.619 0 0 0 .146-.15.148.148 0 0 0 .015-.033L12 14v-.004a.301.301 0 0 0-.057-.09 1.318 1.318 0 0 0-.37-.264c-.376-.198-.943-.375-1.655-.493a.5.5 0 1 1 .164-.986c.77.127 1.452.328 1.957.594C12.5 13 13 13.4 13 14c0 .426-.26.752-.544.977-.29.228-.68.413-1.116.558-.878.293-2.059.465-3.34.465-1.281 0-2.462-.172-3.34-.465-.436-.145-.826-.33-1.116-.558C3.26 14.752 3 14.426 3 14c0-.599.5-1 .961-1.243.505-.266 1.187-.467 1.957-.594a.5.5 0 0 1 .575.411z\"/>\n  </symbol>\n</svg>\n<main>\n  <h1 class=\"visually-hidden\">Features examples</h1>\n\n  <div class=\"container px-4 py-5\" id=\"featured-3\">\n    <h2 class=\"pb-2 border-bottom\">Columns with icons</h2>\n    <div class=\"row g-4 py-5 row-cols-1 row-cols-lg-3\">\n      <div class=\"feature col\">\n        <div class=\"feature-icon d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-2 mb-3\">\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#collection\"/></svg>\n        </div>\n        <h3 class=\"fs-2\">Featured title</h3>\n        <p>Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n        <a href=\"#\" class=\"icon-link d-inline-flex align-items-center\">\n          Call to action\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#chevron-right\"/></svg>\n        </a>\n      </div>\n      <div class=\"feature col\">\n        <div class=\"feature-icon d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-2 mb-3\">\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#people-circle\"/></svg>\n        </div>\n        <h3 class=\"fs-2\">Featured title</h3>\n        <p>Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n        <a href=\"#\" class=\"icon-link d-inline-flex align-items-center\">\n          Call to action\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#chevron-right\"/></svg>\n        </a>\n      </div>\n      <div class=\"feature col\">\n        <div class=\"feature-icon d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-2 mb-3\">\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#toggles2\"/></svg>\n        </div>\n        <h3 class=\"fs-2\">Featured title</h3>\n        <p>Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n        <a href=\"#\" class=\"icon-link d-inline-flex align-items-center\">\n          Call to action\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#chevron-right\"/></svg>\n        </a>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container px-4 py-5\" id=\"hanging-icons\">\n    <h2 class=\"pb-2 border-bottom\">Hanging icons</h2>\n    <div class=\"row g-4 py-5 row-cols-1 row-cols-lg-3\">\n      <div class=\"col d-flex align-items-start\">\n        <div class=\"icon-square text-bg-light d-inline-flex align-items-center justify-content-center fs-4 flex-shrink-0 me-3\">\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#toggles2\"/></svg>\n        </div>\n        <div>\n          <h3 class=\"fs-2\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n          <a href=\"#\" class=\"btn btn-primary\">\n            Primary button\n          </a>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <div class=\"icon-square text-bg-light d-inline-flex align-items-center justify-content-center fs-4 flex-shrink-0 me-3\">\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#cpu-fill\"/></svg>\n        </div>\n        <div>\n          <h3 class=\"fs-2\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n          <a href=\"#\" class=\"btn btn-primary\">\n            Primary button\n          </a>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <div class=\"icon-square text-bg-light d-inline-flex align-items-center justify-content-center fs-4 flex-shrink-0 me-3\">\n          <svg class=\"bi\" width=\"1em\" height=\"1em\"><use xlink:href=\"#tools\"/></svg>\n        </div>\n        <div>\n          <h3 class=\"fs-2\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n          <a href=\"#\" class=\"btn btn-primary\">\n            Primary button\n          </a>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container px-4 py-5\" id=\"custom-cards\">\n    <h2 class=\"pb-2 border-bottom\">Custom cards</h2>\n\n    <div class=\"row row-cols-1 row-cols-lg-3 align-items-stretch g-4 py-5\">\n      <div class=\"col\">\n        <div class=\"card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg\" style=\"background-image: url('unsplash-photo-1.jpg');\">\n          <div class=\"d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1\">\n            <h3 class=\"pt-5 mt-5 mb-4 display-6 lh-1 fw-bold\">Short title, long jacket</h3>\n            <ul class=\"d-flex list-unstyled mt-auto\">\n              <li class=\"me-auto\">\n                <img src=\"https://github.com/twbs.png\" alt=\"Bootstrap\" width=\"32\" height=\"32\" class=\"rounded-circle border border-white\">\n              </li>\n              <li class=\"d-flex align-items-center me-3\">\n                <svg class=\"bi me-2\" width=\"1em\" height=\"1em\"><use xlink:href=\"#geo-fill\"/></svg>\n                <small>Earth</small>\n              </li>\n              <li class=\"d-flex align-items-center\">\n                <svg class=\"bi me-2\" width=\"1em\" height=\"1em\"><use xlink:href=\"#calendar3\"/></svg>\n                <small>3d</small>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"col\">\n        <div class=\"card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg\" style=\"background-image: url('unsplash-photo-2.jpg');\">\n          <div class=\"d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1\">\n            <h3 class=\"pt-5 mt-5 mb-4 display-6 lh-1 fw-bold\">Much longer title that wraps to multiple lines</h3>\n            <ul class=\"d-flex list-unstyled mt-auto\">\n              <li class=\"me-auto\">\n                <img src=\"https://github.com/twbs.png\" alt=\"Bootstrap\" width=\"32\" height=\"32\" class=\"rounded-circle border border-white\">\n              </li>\n              <li class=\"d-flex align-items-center me-3\">\n                <svg class=\"bi me-2\" width=\"1em\" height=\"1em\"><use xlink:href=\"#geo-fill\"/></svg>\n                <small>Pakistan</small>\n              </li>\n              <li class=\"d-flex align-items-center\">\n                <svg class=\"bi me-2\" width=\"1em\" height=\"1em\"><use xlink:href=\"#calendar3\"/></svg>\n                <small>4d</small>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"col\">\n        <div class=\"card card-cover h-100 overflow-hidden text-bg-dark rounded-4 shadow-lg\" style=\"background-image: url('unsplash-photo-3.jpg');\">\n          <div class=\"d-flex flex-column h-100 p-5 pb-3 text-shadow-1\">\n            <h3 class=\"pt-5 mt-5 mb-4 display-6 lh-1 fw-bold\">Another longer title belongs here</h3>\n            <ul class=\"d-flex list-unstyled mt-auto\">\n              <li class=\"me-auto\">\n                <img src=\"https://github.com/twbs.png\" alt=\"Bootstrap\" width=\"32\" height=\"32\" class=\"rounded-circle border border-white\">\n              </li>\n              <li class=\"d-flex align-items-center me-3\">\n                <svg class=\"bi me-2\" width=\"1em\" height=\"1em\"><use xlink:href=\"#geo-fill\"/></svg>\n                <small>California</small>\n              </li>\n              <li class=\"d-flex align-items-center\">\n                <svg class=\"bi me-2\" width=\"1em\" height=\"1em\"><use xlink:href=\"#calendar3\"/></svg>\n                <small>5d</small>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container px-4 py-5\" id=\"icon-grid\">\n    <h2 class=\"pb-2 border-bottom\">Icon grid</h2>\n\n    <div class=\"row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4 py-5\">\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#bootstrap\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#cpu-fill\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#calendar3\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#home\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#speedometer2\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#toggles2\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#geo-fill\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n      <div class=\"col d-flex align-items-start\">\n        <svg class=\"bi text-muted flex-shrink-0 me-3\" width=\"1.75em\" height=\"1.75em\"><use xlink:href=\"#tools\"/></svg>\n        <div>\n          <h3 class=\"fw-bold mb-0 fs-4\">Featured title</h3>\n          <p>Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container px-4 py-5\">\n    <h2 class=\"pb-2 border-bottom\">Features with title</h2>\n\n    <div class=\"row row-cols-1 row-cols-md-2 align-items-md-center g-5 py-5\">\n      <div class=\"d-flex flex-column align-items-start gap-2\">\n        <h3 class=\"fw-bold\">Left-aligned title explaining these awesome features</h3>\n        <p class=\"text-muted\">Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.</p>\n        <a href=\"#\" class=\"btn btn-primary btn-lg\">Primary button</a>\n      </div>\n      <div class=\"row row-cols-1 row-cols-sm-2 g-4\">\n        <div class=\"d-flex flex-column gap-2\">\n          <div\n            class=\"feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3\">\n            <svg class=\"bi\" width=\"1em\" height=\"1em\">\n              <use xlink:href=\"#collection\" />\n            </svg>\n          </div>\n          <h4 class=\"fw-semibold mb-0\">Featured title</h4>\n          <p class=\"text-muted\">Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n\n        <div class=\"d-flex flex-column gap-2\">\n          <div\n            class=\"feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3\">\n            <svg class=\"bi\" width=\"1em\" height=\"1em\">\n              <use xlink:href=\"#gear-fill\" />\n            </svg>\n          </div>\n          <h4 class=\"fw-semibold mb-0\">Featured title</h4>\n          <p class=\"text-muted\">Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n\n        <div class=\"d-flex flex-column gap-2\">\n          <div\n            class=\"feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3\">\n            <svg class=\"bi\" width=\"1em\" height=\"1em\">\n              <use xlink:href=\"#speedometer\" />\n            </svg>\n          </div>\n          <h4 class=\"fw-semibold mb-0\">Featured title</h4>\n          <p class=\"text-muted\">Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n\n        <div class=\"d-flex flex-column gap-2\">\n          <div\n            class=\"feature-icon-small d-inline-flex align-items-center justify-content-center text-bg-primary bg-gradient fs-4 rounded-3\">\n            <svg class=\"bi\" width=\"1em\" height=\"1em\">\n              <use xlink:href=\"#table\" />\n            </svg>\n          </div>\n          <h4 class=\"fw-semibold mb-0\">Featured title</h4>\n          <p class=\"text-muted\">Paragraph of text beneath the heading to explain the heading.</p>\n        </div>\n      </div>\n    </div>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/footers/index.html",
    "content": "---\nlayout: examples\ntitle: Footers\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n  <symbol id=\"facebook\" viewBox=\"0 0 16 16\">\n    <path d=\"M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z\"/>\n  </symbol>\n  <symbol id=\"instagram\" viewBox=\"0 0 16 16\">\n      <path d=\"M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334z\"/>\n  </symbol>\n  <symbol id=\"twitter\" viewBox=\"0 0 16 16\">\n    <path d=\"M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z\"/>\n  </symbol>\n</svg>\n\n<div class=\"container\">\n  <footer class=\"d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top\">\n    <p class=\"col-md-4 mb-0 text-muted\">&copy; {{< year >}} Company, Inc</p>\n\n    <a href=\"/\" class=\"col-md-4 d-flex align-items-center justify-content-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none\">\n      <svg class=\"bi me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n    </a>\n\n    <ul class=\"nav col-md-4 justify-content-end\">\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">Home</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">Features</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">Pricing</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">FAQs</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">About</a></li>\n    </ul>\n  </footer>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"container\">\n  <footer class=\"d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top\">\n    <div class=\"col-md-4 d-flex align-items-center\">\n      <a href=\"/\" class=\"mb-3 me-2 mb-md-0 text-muted text-decoration-none lh-1\">\n        <svg class=\"bi\" width=\"30\" height=\"24\"><use xlink:href=\"#bootstrap\"/></svg>\n      </a>\n      <span class=\"mb-3 mb-md-0 text-muted\">&copy; {{< year >}} Company, Inc</span>\n    </div>\n\n    <ul class=\"nav col-md-4 justify-content-end list-unstyled d-flex\">\n      <li class=\"ms-3\"><a class=\"text-muted\" href=\"#\"><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#twitter\"/></svg></a></li>\n      <li class=\"ms-3\"><a class=\"text-muted\" href=\"#\"><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#instagram\"/></svg></a></li>\n      <li class=\"ms-3\"><a class=\"text-muted\" href=\"#\"><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#facebook\"/></svg></a></li>\n    </ul>\n  </footer>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"container\">\n  <footer class=\"py-3 my-4\">\n    <ul class=\"nav justify-content-center border-bottom pb-3 mb-3\">\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">Home</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">Features</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">Pricing</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">FAQs</a></li>\n      <li class=\"nav-item\"><a href=\"#\" class=\"nav-link px-2 text-muted\">About</a></li>\n    </ul>\n    <p class=\"text-center text-muted\">&copy; {{< year >}} Company, Inc</p>\n  </footer>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"container\">\n  <footer class=\"row row-cols-1 row-cols-sm-2 row-cols-md-5 py-5 my-5 border-top\">\n    <div class=\"col mb-3\">\n      <a href=\"/\" class=\"d-flex align-items-center mb-3 link-dark text-decoration-none\">\n        <svg class=\"bi me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n      </a>\n      <p class=\"text-muted\">&copy; {{< year >}}</p>\n    </div>\n\n    <div class=\"col mb-3\">\n\n    </div>\n\n    <div class=\"col mb-3\">\n      <h5>Section</h5>\n      <ul class=\"nav flex-column\">\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Home</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Features</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Pricing</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">FAQs</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">About</a></li>\n      </ul>\n    </div>\n\n    <div class=\"col mb-3\">\n      <h5>Section</h5>\n      <ul class=\"nav flex-column\">\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Home</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Features</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Pricing</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">FAQs</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">About</a></li>\n      </ul>\n    </div>\n\n    <div class=\"col mb-3\">\n      <h5>Section</h5>\n      <ul class=\"nav flex-column\">\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Home</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Features</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Pricing</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">FAQs</a></li>\n        <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">About</a></li>\n      </ul>\n    </div>\n  </footer>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n\n<div class=\"container\">\n  <footer class=\"py-5\">\n    <div class=\"row\">\n      <div class=\"col-6 col-md-2 mb-3\">\n        <h5>Section</h5>\n        <ul class=\"nav flex-column\">\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Home</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Features</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Pricing</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">FAQs</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">About</a></li>\n        </ul>\n      </div>\n\n      <div class=\"col-6 col-md-2 mb-3\">\n        <h5>Section</h5>\n        <ul class=\"nav flex-column\">\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Home</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Features</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Pricing</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">FAQs</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">About</a></li>\n        </ul>\n      </div>\n\n      <div class=\"col-6 col-md-2 mb-3\">\n        <h5>Section</h5>\n        <ul class=\"nav flex-column\">\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Home</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Features</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">Pricing</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">FAQs</a></li>\n          <li class=\"nav-item mb-2\"><a href=\"#\" class=\"nav-link p-0 text-muted\">About</a></li>\n        </ul>\n      </div>\n\n      <div class=\"col-md-5 offset-md-1 mb-3\">\n        <form>\n          <h5>Subscribe to our newsletter</h5>\n          <p>Monthly digest of what's new and exciting from us.</p>\n          <div class=\"d-flex flex-column flex-sm-row w-100 gap-2\">\n            <label for=\"newsletter1\" class=\"visually-hidden\">Email address</label>\n            <input id=\"newsletter1\" type=\"text\" class=\"form-control\" placeholder=\"Email address\">\n            <button class=\"btn btn-primary\" type=\"button\">Subscribe</button>\n          </div>\n        </form>\n      </div>\n    </div>\n\n    <div class=\"d-flex flex-column flex-sm-row justify-content-between py-4 my-4 border-top\">\n      <p>&copy; {{< year >}} Company, Inc. All rights reserved.</p>\n      <ul class=\"list-unstyled d-flex\">\n        <li class=\"ms-3\"><a class=\"link-dark\" href=\"#\"><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#twitter\"/></svg></a></li>\n        <li class=\"ms-3\"><a class=\"link-dark\" href=\"#\"><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#instagram\"/></svg></a></li>\n        <li class=\"ms-3\"><a class=\"link-dark\" href=\"#\"><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#facebook\"/></svg></a></li>\n      </ul>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/grid/grid.css",
    "content": ".themed-grid-col {\n  padding-top: .75rem;\n  padding-bottom: .75rem;\n  background-color: rgba(86, 61, 124, .15);\n  border: 1px solid rgba(86, 61, 124, .2);\n}\n\n.themed-container {\n  padding: .75rem;\n  margin-bottom: 1.5rem;\n  background-color: rgba(0, 123, 255, .15);\n  border: 1px solid rgba(0, 123, 255, .2);\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/grid/index.html",
    "content": "---\nlayout: examples\ntitle: Grid Template\nextra_css:\n  - \"grid.css\"\nbody_class: \"py-4\"\ninclude_js: false\n---\n\n<main>\n  <div class=\"container\">\n\n    <h1>Bootstrap grid examples</h1>\n    <p class=\"lead\">Basic grid layouts to get you familiar with building within the Bootstrap grid system.</p>\n    <p>In these examples the <code>.themed-grid-col</code> class is added to the columns to add some theming. This is not a class that is available in Bootstrap by default.</p>\n\n    <h2 class=\"mt-4\">Five grid tiers</h2>\n    <p>There are five tiers to the Bootstrap grid system, one for each range of devices we support. Each tier starts at a minimum viewport size and automatically applies to the larger devices unless overridden.</p>\n\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-4 themed-grid-col\">.col-4</div>\n      <div class=\"col-4 themed-grid-col\">.col-4</div>\n      <div class=\"col-4 themed-grid-col\">.col-4</div>\n    </div>\n\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-sm-4 themed-grid-col\">.col-sm-4</div>\n      <div class=\"col-sm-4 themed-grid-col\">.col-sm-4</div>\n      <div class=\"col-sm-4 themed-grid-col\">.col-sm-4</div>\n    </div>\n\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n    </div>\n\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-lg-4 themed-grid-col\">.col-lg-4</div>\n      <div class=\"col-lg-4 themed-grid-col\">.col-lg-4</div>\n      <div class=\"col-lg-4 themed-grid-col\">.col-lg-4</div>\n    </div>\n\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-xl-4 themed-grid-col\">.col-xl-4</div>\n      <div class=\"col-xl-4 themed-grid-col\">.col-xl-4</div>\n      <div class=\"col-xl-4 themed-grid-col\">.col-xl-4</div>\n    </div>\n\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-xxl-4 themed-grid-col\">.col-xxl-4</div>\n      <div class=\"col-xxl-4 themed-grid-col\">.col-xxl-4</div>\n      <div class=\"col-xxl-4 themed-grid-col\">.col-xxl-4</div>\n    </div>\n\n    <h2 class=\"mt-4\">Three equal columns</h2>\n    <p>Get three equal-width columns <strong>starting at desktops and scaling to large desktops</strong>. On mobile devices, tablets and below, the columns will automatically stack.</p>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n    </div>\n\n    <h2 class=\"mt-4\">Three equal columns alternative</h2>\n    <p>By using the <code>.row-cols-*</code> classes, you can easily create a grid with equal columns.</p>\n    <div class=\"row row-cols-md-3 mb-3 text-center\">\n      <div class=\"col themed-grid-col\"><code>.col</code> child of <code>.row-cols-md-3</code></div>\n      <div class=\"col themed-grid-col\"><code>.col</code> child of <code>.row-cols-md-3</code></div>\n      <div class=\"col themed-grid-col\"><code>.col</code> child of <code>.row-cols-md-3</code></div>\n    </div>\n\n    <h2 class=\"mt-4\">Three unequal columns</h2>\n    <p>Get three columns <strong>starting at desktops and scaling to large desktops</strong> of various widths. Remember, grid columns should add up to twelve for a single horizontal block. More than that, and columns start stacking no matter the viewport.</p>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-md-3 themed-grid-col\">.col-md-3</div>\n      <div class=\"col-md-6 themed-grid-col\">.col-md-6</div>\n      <div class=\"col-md-3 themed-grid-col\">.col-md-3</div>\n    </div>\n\n    <h2 class=\"mt-4\">Two columns</h2>\n    <p>Get two columns <strong>starting at desktops and scaling to large desktops</strong>.</p>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-md-8 themed-grid-col\">.col-md-8</div>\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n    </div>\n\n    <h2 class=\"mt-4\">Full width, single column</h2>\n    <p class=\"text-warning\">\n      No grid classes are necessary for full-width elements.\n    </p>\n\n    <hr class=\"my-4\">\n\n    <h2 class=\"mt-4\">Two columns with two nested columns</h2>\n    <p>Per the documentation, nesting is easy—just put a row of columns within an existing column. This gives you two columns <strong>starting at desktops and scaling to large desktops</strong>, with another two (equal widths) within the larger column.</p>\n    <p>At mobile device sizes, tablets and down, these columns and their nested columns will stack.</p>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-md-8 themed-grid-col\">\n        <div class=\"pb-3\">\n          .col-md-8\n        </div>\n        <div class=\"row\">\n          <div class=\"col-md-6 themed-grid-col\">.col-md-6</div>\n          <div class=\"col-md-6 themed-grid-col\">.col-md-6</div>\n        </div>\n      </div>\n      <div class=\"col-md-4 themed-grid-col\">.col-md-4</div>\n    </div>\n\n    <hr class=\"my-4\">\n\n    <h2 class=\"mt-4\">Mixed: mobile and desktop</h2>\n    <p>The Bootstrap v5 grid system has six tiers of classes: xs (extra small, this class infix is not used), sm (small), md (medium), lg (large), xl (x-large), and xxl (xx-large). You can use nearly any combination of these classes to create more dynamic and flexible layouts.</p>\n    <p>Each tier of classes scales up, meaning if you plan on setting the same widths for md, lg, xl and xxl, you only need to specify md.</p>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-md-8 themed-grid-col\">.col-md-8</div>\n      <div class=\"col-6 col-md-4 themed-grid-col\">.col-6 .col-md-4</div>\n    </div>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-6 col-md-4 themed-grid-col\">.col-6 .col-md-4</div>\n      <div class=\"col-6 col-md-4 themed-grid-col\">.col-6 .col-md-4</div>\n      <div class=\"col-6 col-md-4 themed-grid-col\">.col-6 .col-md-4</div>\n    </div>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-6 themed-grid-col\">.col-6</div>\n      <div class=\"col-6 themed-grid-col\">.col-6</div>\n    </div>\n\n    <hr class=\"my-4\">\n\n    <h2 class=\"mt-4\">Mixed: mobile, tablet, and desktop</h2>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-sm-6 col-lg-8 themed-grid-col\">.col-sm-6 .col-lg-8</div>\n      <div class=\"col-6 col-lg-4 themed-grid-col\">.col-6 .col-lg-4</div>\n    </div>\n    <div class=\"row mb-3 text-center\">\n      <div class=\"col-6 col-sm-4 themed-grid-col\">.col-6 .col-sm-4</div>\n      <div class=\"col-6 col-sm-4 themed-grid-col\">.col-6 .col-sm-4</div>\n      <div class=\"col-6 col-sm-4 themed-grid-col\">.col-6 .col-sm-4</div>\n    </div>\n\n    <hr class=\"my-4\">\n\n    <h2 class=\"mt-4\">Gutters</h2>\n    <p>With <code>.gx-*</code> classes, the horizontal gutters can be adjusted.</p>\n    <div class=\"row row-cols-1 row-cols-md-3 gx-4 text-center\">\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gx-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gx-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gx-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gx-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gx-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gx-4</code> gutters</div>\n    </div>\n    <p class=\"mt-4\">Use the <code>.gy-*</code> classes to control the vertical gutters.</p>\n    <div class=\"row row-cols-1 row-cols-md-3 gy-4 text-center\">\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gy-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gy-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gy-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gy-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gy-4</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.gy-4</code> gutters</div>\n    </div>\n    <p class=\"mt-4\">With <code>.g-*</code> classes, the gutters in both directions can be adjusted.</p>\n    <div class=\"row row-cols-1 row-cols-md-3 g-3 text-center\">\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.g-3</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.g-3</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.g-3</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.g-3</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.g-3</code> gutters</div>\n      <div class=\"col themed-grid-col\"><code>.col</code> with <code>.g-3</code> gutters</div>\n    </div>\n  </div>\n\n  <div class=\"container\" id=\"containers\">\n    <hr class=\"my-4\">\n\n    <h2 class=\"mt-4\">Containers</h2>\n    <p>Additional classes added in Bootstrap v4.4 allow containers that are 100% wide until a particular breakpoint. v5 adds a new <code>xxl</code> breakpoint.</p>\n  </div>\n\n  <div class=\"container themed-container text-center\">.container</div>\n  <div class=\"container-sm themed-container text-center\">.container-sm</div>\n  <div class=\"container-md themed-container text-center\">.container-md</div>\n  <div class=\"container-lg themed-container text-center\">.container-lg</div>\n  <div class=\"container-xl themed-container text-center\">.container-xl</div>\n  <div class=\"container-xxl themed-container text-center\">.container-xxl</div>\n  <div class=\"container-fluid themed-container text-center\">.container-fluid</div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/headers/headers.css",
    "content": ".form-control-dark {\n  border-color: var(--bs-gray);\n}\n.form-control-dark:focus {\n  border-color: #fff;\n  box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .25);\n}\n\n.text-small {\n  font-size: 85%;\n}\n\n.dropdown-toggle {\n  outline: 0;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/headers/index.html",
    "content": "---\nlayout: examples\ntitle: Headers\nextra_css:\n  - \"headers.css\"\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n  <symbol id=\"home\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z\"/>\n  </symbol>\n  <symbol id=\"speedometer2\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z\"/>\n    <path fill-rule=\"evenodd\" d=\"M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z\"/>\n  </symbol>\n  <symbol id=\"table\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z\"/>\n  </symbol>\n  <symbol id=\"people-circle\" viewBox=\"0 0 16 16\">\n    <path d=\"M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z\"/>\n    <path fill-rule=\"evenodd\" d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z\"/>\n  </symbol>\n  <symbol id=\"grid\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z\"/>\n  </symbol>\n</svg>\n\n<main>\n  <h1 class=\"visually-hidden\">Headers examples</h1>\n\n  <div class=\"container\">\n    <header class=\"d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom\">\n      <a href=\"/\" class=\"d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none\">\n        <svg class=\"bi me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n        <span class=\"fs-4\">Simple header</span>\n      </a>\n\n      <ul class=\"nav nav-pills\">\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link active\" aria-current=\"page\">Home</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">Features</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">Pricing</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">FAQs</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">About</a></li>\n      </ul>\n    </header>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container\">\n    <header class=\"d-flex justify-content-center py-3\">\n      <ul class=\"nav nav-pills\">\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link active\" aria-current=\"page\">Home</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">Features</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">Pricing</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">FAQs</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">About</a></li>\n      </ul>\n    </header>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container\">\n    <header class=\"d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom\">\n      <a href=\"/\" class=\"d-flex align-items-center col-md-3 mb-2 mb-md-0 text-dark text-decoration-none\">\n        <svg class=\"bi me-2\" width=\"40\" height=\"32\" role=\"img\" aria-label=\"Bootstrap\"><use xlink:href=\"#bootstrap\"/></svg>\n      </a>\n\n      <ul class=\"nav col-12 col-md-auto mb-2 justify-content-center mb-md-0\">\n        <li><a href=\"#\" class=\"nav-link px-2 link-secondary\">Home</a></li>\n        <li><a href=\"#\" class=\"nav-link px-2 link-dark\">Features</a></li>\n        <li><a href=\"#\" class=\"nav-link px-2 link-dark\">Pricing</a></li>\n        <li><a href=\"#\" class=\"nav-link px-2 link-dark\">FAQs</a></li>\n        <li><a href=\"#\" class=\"nav-link px-2 link-dark\">About</a></li>\n      </ul>\n\n      <div class=\"col-md-3 text-end\">\n        <button type=\"button\" class=\"btn btn-outline-primary me-2\">Login</button>\n        <button type=\"button\" class=\"btn btn-primary\">Sign-up</button>\n      </div>\n    </header>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <header class=\"p-3 text-bg-dark\">\n    <div class=\"container\">\n      <div class=\"d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start\">\n        <a href=\"/\" class=\"d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none\">\n          <svg class=\"bi me-2\" width=\"40\" height=\"32\" role=\"img\" aria-label=\"Bootstrap\"><use xlink:href=\"#bootstrap\"/></svg>\n        </a>\n\n        <ul class=\"nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0\">\n          <li><a href=\"#\" class=\"nav-link px-2 text-secondary\">Home</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 text-white\">Features</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 text-white\">Pricing</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 text-white\">FAQs</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 text-white\">About</a></li>\n        </ul>\n\n        <form class=\"col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3\" role=\"search\">\n          <input type=\"search\" class=\"form-control form-control-dark text-bg-dark\" placeholder=\"Search...\" aria-label=\"Search\">\n        </form>\n\n        <div class=\"text-end\">\n          <button type=\"button\" class=\"btn btn-outline-light me-2\">Login</button>\n          <button type=\"button\" class=\"btn btn-warning\">Sign-up</button>\n        </div>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"b-example-divider\"></div>\n\n  <header class=\"p-3 mb-3 border-bottom\">\n    <div class=\"container\">\n      <div class=\"d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start\">\n        <a href=\"/\" class=\"d-flex align-items-center mb-2 mb-lg-0 text-dark text-decoration-none\">\n          <svg class=\"bi me-2\" width=\"40\" height=\"32\" role=\"img\" aria-label=\"Bootstrap\"><use xlink:href=\"#bootstrap\"/></svg>\n        </a>\n\n        <ul class=\"nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0\">\n          <li><a href=\"#\" class=\"nav-link px-2 link-secondary\">Overview</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 link-dark\">Inventory</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 link-dark\">Customers</a></li>\n          <li><a href=\"#\" class=\"nav-link px-2 link-dark\">Products</a></li>\n        </ul>\n\n        <form class=\"col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3\" role=\"search\">\n          <input type=\"search\" class=\"form-control\" placeholder=\"Search...\" aria-label=\"Search\">\n        </form>\n\n        <div class=\"dropdown text-end\">\n          <a href=\"#\" class=\"d-block link-dark text-decoration-none dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <img src=\"https://github.com/mdo.png\" alt=\"mdo\" width=\"32\" height=\"32\" class=\"rounded-circle\">\n          </a>\n          <ul class=\"dropdown-menu text-small\">\n            <li><a class=\"dropdown-item\" href=\"#\">New project...</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Settings</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Profile</a></li>\n            <li><hr class=\"dropdown-divider\"></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Sign out</a></li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"b-example-divider\"></div>\n\n  <header class=\"py-3 mb-3 border-bottom\">\n    <div class=\"container-fluid d-grid gap-3 align-items-center\" style=\"grid-template-columns: 1fr 2fr;\">\n      <div class=\"dropdown\">\n        <a href=\"#\" class=\"d-flex align-items-center col-lg-4 mb-2 mb-lg-0 link-dark text-decoration-none dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n          <svg class=\"bi me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n        </a>\n        <ul class=\"dropdown-menu text-small shadow\">\n          <li><a class=\"dropdown-item active\" href=\"#\" aria-current=\"page\">Overview</a></li>\n          <li><a class=\"dropdown-item\" href=\"#\">Inventory</a></li>\n          <li><a class=\"dropdown-item\" href=\"#\">Customers</a></li>\n          <li><a class=\"dropdown-item\" href=\"#\">Products</a></li>\n          <li><hr class=\"dropdown-divider\"></li>\n          <li><a class=\"dropdown-item\" href=\"#\">Reports</a></li>\n          <li><a class=\"dropdown-item\" href=\"#\">Analytics</a></li>\n        </ul>\n      </div>\n\n      <div class=\"d-flex align-items-center\">\n        <form class=\"w-100 me-3\" role=\"search\">\n          <input type=\"search\" class=\"form-control\" placeholder=\"Search...\" aria-label=\"Search\">\n        </form>\n\n        <div class=\"flex-shrink-0 dropdown\">\n          <a href=\"#\" class=\"d-block link-dark text-decoration-none dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n            <img src=\"https://github.com/mdo.png\" alt=\"mdo\" width=\"32\" height=\"32\" class=\"rounded-circle\">\n          </a>\n          <ul class=\"dropdown-menu text-small shadow\">\n            <li><a class=\"dropdown-item\" href=\"#\">New project...</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Settings</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Profile</a></li>\n            <li><hr class=\"dropdown-divider\"></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Sign out</a></li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"container-fluid pb-3\">\n    <div class=\"d-grid gap-3\" style=\"grid-template-columns: 1fr 2fr;\">\n      <div class=\"bg-light border rounded-3\">\n        <br><br><br><br><br><br><br><br><br><br>\n      </div>\n      <div class=\"bg-light border rounded-3\">\n        <br><br><br><br><br><br><br><br><br><br>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <nav class=\"py-2 bg-light border-bottom\">\n    <div class=\"container d-flex flex-wrap\">\n      <ul class=\"nav me-auto\">\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2 active\" aria-current=\"page\">Home</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2\">Features</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2\">Pricing</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2\">FAQs</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2\">About</a></li>\n      </ul>\n      <ul class=\"nav\">\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2\">Login</a></li>\n        <li class=\"nav-item\"><a href=\"#\" class=\"nav-link link-dark px-2\">Sign up</a></li>\n      </ul>\n    </div>\n  </nav>\n  <header class=\"py-3 mb-4 border-bottom\">\n    <div class=\"container d-flex flex-wrap justify-content-center\">\n      <a href=\"/\" class=\"d-flex align-items-center mb-3 mb-lg-0 me-lg-auto text-dark text-decoration-none\">\n        <svg class=\"bi me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n        <span class=\"fs-4\">Double header</span>\n      </a>\n      <form class=\"col-12 col-lg-auto mb-3 mb-lg-0\" role=\"search\">\n        <input type=\"search\" class=\"form-control\" placeholder=\"Search...\" aria-label=\"Search\">\n      </form>\n    </div>\n  </header>\n\n  <div class=\"b-example-divider\"></div>\n\n  <header>\n    <div class=\"px-3 py-2 text-bg-dark\">\n      <div class=\"container\">\n        <div class=\"d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start\">\n          <a href=\"/\" class=\"d-flex align-items-center my-2 my-lg-0 me-lg-auto text-white text-decoration-none\">\n            <svg class=\"bi me-2\" width=\"40\" height=\"32\" role=\"img\" aria-label=\"Bootstrap\"><use xlink:href=\"#bootstrap\"/></svg>\n          </a>\n\n          <ul class=\"nav col-12 col-lg-auto my-2 justify-content-center my-md-0 text-small\">\n            <li>\n              <a href=\"#\" class=\"nav-link text-secondary\">\n                <svg class=\"bi d-block mx-auto mb-1\" width=\"24\" height=\"24\"><use xlink:href=\"#home\"/></svg>\n                Home\n              </a>\n            </li>\n            <li>\n              <a href=\"#\" class=\"nav-link text-white\">\n                <svg class=\"bi d-block mx-auto mb-1\" width=\"24\" height=\"24\"><use xlink:href=\"#speedometer2\"/></svg>\n                Dashboard\n              </a>\n            </li>\n            <li>\n              <a href=\"#\" class=\"nav-link text-white\">\n                <svg class=\"bi d-block mx-auto mb-1\" width=\"24\" height=\"24\"><use xlink:href=\"#table\"/></svg>\n                Orders\n              </a>\n            </li>\n            <li>\n              <a href=\"#\" class=\"nav-link text-white\">\n                <svg class=\"bi d-block mx-auto mb-1\" width=\"24\" height=\"24\"><use xlink:href=\"#grid\"/></svg>\n                Products\n              </a>\n            </li>\n            <li>\n              <a href=\"#\" class=\"nav-link text-white\">\n                <svg class=\"bi d-block mx-auto mb-1\" width=\"24\" height=\"24\"><use xlink:href=\"#people-circle\"/></svg>\n                Customers\n              </a>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n    <div class=\"px-3 py-2 border-bottom mb-3\">\n      <div class=\"container d-flex flex-wrap justify-content-center\">\n        <form class=\"col-12 col-lg-auto mb-2 mb-lg-0 me-lg-auto\" role=\"search\">\n          <input type=\"search\" class=\"form-control\" placeholder=\"Search...\" aria-label=\"Search\">\n        </form>\n\n        <div class=\"text-end\">\n          <button type=\"button\" class=\"btn btn-light text-dark me-2\">Login</button>\n          <button type=\"button\" class=\"btn btn-primary\">Sign-up</button>\n        </div>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"b-example-divider\"></div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/heroes/heroes.css",
    "content": "@media (min-width: 992px) {\n  .rounded-lg-3 { border-radius: .3rem; }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/heroes/index.html",
    "content": "---\nlayout: examples\ntitle: Heroes\nextra_css:\n  - \"heroes.css\"\nbody_class: \"\"\n---\n\n<main>\n  <h1 class=\"visually-hidden\">Heroes examples</h1>\n\n  <div class=\"px-4 py-5 my-5 text-center\">\n    <img class=\"d-block mx-auto mb-4\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"\" width=\"72\" height=\"57\">\n    <h1 class=\"display-5 fw-bold\">Centered hero</h1>\n    <div class=\"col-lg-6 mx-auto\">\n      <p class=\"lead mb-4\">Quickly design and customize responsive mobile-first sites with Bootstrap, the world’s most popular front-end open source toolkit, featuring Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful JavaScript plugins.</p>\n      <div class=\"d-grid gap-2 d-sm-flex justify-content-sm-center\">\n        <button type=\"button\" class=\"btn btn-primary btn-lg px-4 gap-3\">Primary button</button>\n        <button type=\"button\" class=\"btn btn-outline-secondary btn-lg px-4\">Secondary</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"px-4 pt-5 my-5 text-center border-bottom\">\n    <h1 class=\"display-4 fw-bold\">Centered screenshot</h1>\n    <div class=\"col-lg-6 mx-auto\">\n      <p class=\"lead mb-4\">Quickly design and customize responsive mobile-first sites with Bootstrap, the world’s most popular front-end open source toolkit, featuring Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful JavaScript plugins.</p>\n      <div class=\"d-grid gap-2 d-sm-flex justify-content-sm-center mb-5\">\n        <button type=\"button\" class=\"btn btn-primary btn-lg px-4 me-sm-3\">Primary button</button>\n        <button type=\"button\" class=\"btn btn-outline-secondary btn-lg px-4\">Secondary</button>\n      </div>\n    </div>\n    <div class=\"overflow-hidden\" style=\"max-height: 30vh;\">\n      <div class=\"container px-5\">\n        <img src=\"bootstrap-docs.png\" class=\"img-fluid border rounded-3 shadow-lg mb-4\" alt=\"Example image\" width=\"700\" height=\"500\" loading=\"lazy\">\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container col-xxl-8 px-4 py-5\">\n    <div class=\"row flex-lg-row-reverse align-items-center g-5 py-5\">\n      <div class=\"col-10 col-sm-8 col-lg-6\">\n        <img src=\"bootstrap-themes.png\" class=\"d-block mx-lg-auto img-fluid\" alt=\"Bootstrap Themes\" width=\"700\" height=\"500\" loading=\"lazy\">\n      </div>\n      <div class=\"col-lg-6\">\n        <h1 class=\"display-5 fw-bold lh-1 mb-3\">Responsive left-aligned hero with image</h1>\n        <p class=\"lead\">Quickly design and customize responsive mobile-first sites with Bootstrap, the world’s most popular front-end open source toolkit, featuring Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful JavaScript plugins.</p>\n        <div class=\"d-grid gap-2 d-md-flex justify-content-md-start\">\n          <button type=\"button\" class=\"btn btn-primary btn-lg px-4 me-md-2\">Primary</button>\n          <button type=\"button\" class=\"btn btn-outline-secondary btn-lg px-4\">Default</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container col-xl-10 col-xxl-8 px-4 py-5\">\n    <div class=\"row align-items-center g-lg-5 py-5\">\n      <div class=\"col-lg-7 text-center text-lg-start\">\n        <h1 class=\"display-4 fw-bold lh-1 mb-3\">Vertically centered hero sign-up form</h1>\n        <p class=\"col-lg-10 fs-4\">Below is an example form built entirely with Bootstrap’s form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it.</p>\n      </div>\n      <div class=\"col-md-10 mx-auto col-lg-5\">\n        <form class=\"p-4 p-md-5 border rounded-3 bg-light\">\n          <div class=\"form-floating mb-3\">\n            <input type=\"email\" class=\"form-control\" id=\"floatingInput\" placeholder=\"name@example.com\">\n            <label for=\"floatingInput\">Email address</label>\n          </div>\n          <div class=\"form-floating mb-3\">\n            <input type=\"password\" class=\"form-control\" id=\"floatingPassword\" placeholder=\"Password\">\n            <label for=\"floatingPassword\">Password</label>\n          </div>\n          <div class=\"checkbox mb-3\">\n            <label>\n              <input type=\"checkbox\" value=\"remember-me\"> Remember me\n            </label>\n          </div>\n          <button class=\"w-100 btn btn-lg btn-primary\" type=\"submit\">Sign up</button>\n          <hr class=\"my-4\">\n          <small class=\"text-muted\">By clicking Sign up, you agree to the terms of use.</small>\n        </form>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"container my-5\">\n    <div class=\"row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg\">\n      <div class=\"col-lg-7 p-3 p-lg-5 pt-lg-3\">\n        <h1 class=\"display-4 fw-bold lh-1\">Border hero with cropped image and shadows</h1>\n        <p class=\"lead\">Quickly design and customize responsive mobile-first sites with Bootstrap, the world’s most popular front-end open source toolkit, featuring Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful JavaScript plugins.</p>\n        <div class=\"d-grid gap-2 d-md-flex justify-content-md-start mb-4 mb-lg-3\">\n          <button type=\"button\" class=\"btn btn-primary btn-lg px-4 me-md-2 fw-bold\">Primary</button>\n          <button type=\"button\" class=\"btn btn-outline-secondary btn-lg px-4\">Default</button>\n        </div>\n      </div>\n      <div class=\"col-lg-4 offset-lg-1 p-0 overflow-hidden shadow-lg\">\n          <img class=\"rounded-lg-3\" src=\"bootstrap-docs.png\" alt=\"\" width=\"720\">\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider\"></div>\n\n  <div class=\"bg-dark text-secondary px-4 py-5 text-center\">\n    <div class=\"py-5\">\n      <h1 class=\"display-5 fw-bold text-white\">Dark mode hero</h1>\n      <div class=\"col-lg-6 mx-auto\">\n        <p class=\"fs-5 mb-4\">Quickly design and customize responsive mobile-first sites with Bootstrap, the world’s most popular front-end open source toolkit, featuring Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful JavaScript plugins.</p>\n        <div class=\"d-grid gap-2 d-sm-flex justify-content-sm-center\">\n          <button type=\"button\" class=\"btn btn-outline-info btn-lg px-4 me-sm-3 fw-bold\">Custom button</button>\n          <button type=\"button\" class=\"btn btn-outline-light btn-lg px-4\">Secondary</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider mb-0\"></div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/jumbotron/index.html",
    "content": "---\nlayout: examples\ntitle: Jumbotron example\ninclude_js: false\n---\n\n<main>\n  <div class=\"container py-4\">\n    <header class=\"pb-3 mb-4 border-bottom\">\n      <a href=\"/\" class=\"d-flex align-items-center text-dark text-decoration-none\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"32\" class=\"me-2\" viewBox=\"0 0 118 94\" role=\"img\"><title>Bootstrap</title><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\" fill=\"currentColor\"></path></svg>\n        <span class=\"fs-4\">Jumbotron example</span>\n      </a>\n    </header>\n\n    <div class=\"p-5 mb-4 bg-light rounded-3\">\n      <div class=\"container-fluid py-5\">\n        <h1 class=\"display-5 fw-bold\">Custom jumbotron</h1>\n        <p class=\"col-md-8 fs-4\">Using a series of utilities, you can create this jumbotron, just like the one in previous versions of Bootstrap. Check out the examples below for how you can remix and restyle it to your liking.</p>\n        <button class=\"btn btn-primary btn-lg\" type=\"button\">Example button</button>\n      </div>\n    </div>\n\n    <div class=\"row align-items-md-stretch\">\n      <div class=\"col-md-6\">\n        <div class=\"h-100 p-5 text-bg-dark rounded-3\">\n          <h2>Change the background</h2>\n          <p>Swap the background-color utility and add a `.text-*` color utility to mix up the jumbotron look. Then, mix and match with additional component themes and more.</p>\n          <button class=\"btn btn-outline-light\" type=\"button\">Example button</button>\n        </div>\n      </div>\n      <div class=\"col-md-6\">\n        <div class=\"h-100 p-5 bg-light border rounded-3\">\n          <h2>Add borders</h2>\n          <p>Or, keep it light and add a border for some added definition to the boundaries of your content. Be sure to look under the hood at the source HTML here as we've adjusted the alignment and sizing of both column's content for equal-height.</p>\n          <button class=\"btn btn-outline-secondary\" type=\"button\">Example button</button>\n        </div>\n      </div>\n    </div>\n\n    <footer class=\"pt-3 mt-4 text-muted border-top\">\n      &copy; {{< year >}}\n    </footer>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/list-groups/index.html",
    "content": "---\nlayout: examples\ntitle: List groups\nextra_css:\n  - \"list-groups.css\"\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n\n  <symbol id=\"calendar-event\" viewBox=\"0 0 16 16\">\n    <path d=\"M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z\"/>\n    <path d=\"M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z\"/>\n  </symbol>\n\n  <symbol id=\"alarm\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z\"/>\n    <path d=\"M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z\"/>\n  </symbol>\n\n  <symbol id=\"list-check\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z\"/>\n  </symbol>\n</svg>\n\n<div class=\"list-group w-auto\">\n  <a href=\"#\" class=\"list-group-item list-group-item-action d-flex gap-3 py-3\" aria-current=\"true\">\n    <img src=\"https://github.com/twbs.png\" alt=\"twbs\" width=\"32\" height=\"32\" class=\"rounded-circle flex-shrink-0\">\n    <div class=\"d-flex gap-2 w-100 justify-content-between\">\n      <div>\n        <h6 class=\"mb-0\">List group item heading</h6>\n        <p class=\"mb-0 opacity-75\">Some placeholder content in a paragraph.</p>\n      </div>\n      <small class=\"opacity-50 text-nowrap\">now</small>\n    </div>\n  </a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action d-flex gap-3 py-3\" aria-current=\"true\">\n    <img src=\"https://github.com/twbs.png\" alt=\"twbs\" width=\"32\" height=\"32\" class=\"rounded-circle flex-shrink-0\">\n    <div class=\"d-flex gap-2 w-100 justify-content-between\">\n      <div>\n        <h6 class=\"mb-0\">Another title here</h6>\n        <p class=\"mb-0 opacity-75\">Some placeholder content in a paragraph that goes a little longer so it wraps to a new line.</p>\n      </div>\n      <small class=\"opacity-50 text-nowrap\">3d</small>\n    </div>\n  </a>\n  <a href=\"#\" class=\"list-group-item list-group-item-action d-flex gap-3 py-3\" aria-current=\"true\">\n    <img src=\"https://github.com/twbs.png\" alt=\"twbs\" width=\"32\" height=\"32\" class=\"rounded-circle flex-shrink-0\">\n    <div class=\"d-flex gap-2 w-100 justify-content-between\">\n      <div>\n        <h6 class=\"mb-0\">Third heading</h6>\n        <p class=\"mb-0 opacity-75\">Some placeholder content in a paragraph.</p>\n      </div>\n      <small class=\"opacity-50 text-nowrap\">1w</small>\n    </div>\n  </a>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"d-flex gap-5 justify-content-center\">\n  <div class=\"list-group mx-0 w-auto\">\n    <label class=\"list-group-item d-flex gap-2\">\n      <input class=\"form-check-input flex-shrink-0\" type=\"checkbox\" value=\"\" checked>\n      <span>\n        First checkbox\n        <small class=\"d-block text-muted\">With support text underneath to add more detail</small>\n      </span>\n    </label>\n    <label class=\"list-group-item d-flex gap-2\">\n      <input class=\"form-check-input flex-shrink-0\" type=\"checkbox\" value=\"\">\n      <span>\n        Second checkbox\n        <small class=\"d-block text-muted\">Some other text goes here</small>\n      </span>\n    </label>\n    <label class=\"list-group-item d-flex gap-2\">\n      <input class=\"form-check-input flex-shrink-0\" type=\"checkbox\" value=\"\">\n      <span>\n        Third checkbox\n        <small class=\"d-block text-muted\">And we end with another snippet of text</small>\n      </span>\n    </label>\n  </div>\n\n  <div class=\"list-group mx-0 w-auto\">\n    <label class=\"list-group-item d-flex gap-2\">\n      <input class=\"form-check-input flex-shrink-0\" type=\"radio\" name=\"listGroupRadios\" id=\"listGroupRadios1\" value=\"\" checked>\n      <span>\n        First radio\n        <small class=\"d-block text-muted\">With support text underneath to add more detail</small>\n      </span>\n    </label>\n    <label class=\"list-group-item d-flex gap-2\">\n      <input class=\"form-check-input flex-shrink-0\" type=\"radio\" name=\"listGroupRadios\" id=\"listGroupRadios2\" value=\"\">\n      <span>\n        Second radio\n        <small class=\"d-block text-muted\">Some other text goes here</small>\n      </span>\n    </label>\n    <label class=\"list-group-item d-flex gap-2\">\n      <input class=\"form-check-input flex-shrink-0\" type=\"radio\" name=\"listGroupRadios\" id=\"listGroupRadios3\" value=\"\">\n      <span>\n        Third radio\n        <small class=\"d-block text-muted\">And we end with another snippet of text</small>\n      </span>\n    </label>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"list-group w-auto\">\n  <label class=\"list-group-item d-flex gap-3\">\n    <input class=\"form-check-input flex-shrink-0\" type=\"checkbox\" value=\"\" checked style=\"font-size: 1.375em;\">\n    <span class=\"pt-1 form-checked-content\">\n      <strong>Finish sales report</strong>\n      <small class=\"d-block text-muted\">\n        <svg class=\"bi me-1\" width=\"1em\" height=\"1em\"><use xlink:href=\"#calendar-event\"/></svg>\n        1:00–2:00pm\n      </small>\n    </span>\n  </label>\n  <label class=\"list-group-item d-flex gap-3\">\n    <input class=\"form-check-input flex-shrink-0\" type=\"checkbox\" value=\"\" style=\"font-size: 1.375em;\">\n    <span class=\"pt-1 form-checked-content\">\n      <strong>Weekly All Hands</strong>\n      <small class=\"d-block text-muted\">\n        <svg class=\"bi me-1\" width=\"1em\" height=\"1em\"><use xlink:href=\"#calendar-event\"/></svg>\n        2:00–2:30pm\n      </small>\n    </span>\n  </label>\n  <label class=\"list-group-item d-flex gap-3\">\n    <input class=\"form-check-input flex-shrink-0\" type=\"checkbox\" value=\"\" style=\"font-size: 1.375em;\">\n    <span class=\"pt-1 form-checked-content\">\n      <strong>Out of office</strong>\n      <small class=\"d-block text-muted\">\n        <svg class=\"bi me-1\" width=\"1em\" height=\"1em\"><use xlink:href=\"#alarm\"/></svg>\n        Tomorrow\n      </small>\n    </span>\n  </label>\n  <label class=\"list-group-item d-flex gap-3 bg-light\">\n    <input class=\"form-check-input form-check-input-placeholder bg-light flex-shrink-0 pe-none\" disabled type=\"checkbox\" value=\"\" style=\"font-size: 1.375em;\">\n    <span class=\"pt-1 form-checked-content\">\n      <span contenteditable=\"true\" class=\"w-100\">Add new task...</span>\n      <small class=\"d-block text-muted\">\n        <svg class=\"bi me-1\" width=\"1em\" height=\"1em\"><use xlink:href=\"#list-check\"/></svg>\n        Choose list...\n      </small>\n    </span>\n  </label>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"list-group list-group-checkable d-grid gap-2 border-0 w-auto\">\n  <input class=\"list-group-item-check pe-none\" type=\"radio\" name=\"listGroupCheckableRadios\" id=\"listGroupCheckableRadios1\" value=\"\" checked>\n  <label class=\"list-group-item rounded-3 py-3\" for=\"listGroupCheckableRadios1\">\n    First radio\n    <span class=\"d-block small opacity-50\">With support text underneath to add more detail</span>\n  </label>\n\n  <input class=\"list-group-item-check pe-none\" type=\"radio\" name=\"listGroupCheckableRadios\" id=\"listGroupCheckableRadios2\" value=\"\">\n  <label class=\"list-group-item rounded-3 py-3\" for=\"listGroupCheckableRadios2\">\n    Second radio\n    <span class=\"d-block small opacity-50\">Some other text goes here</span>\n  </label>\n\n  <input class=\"list-group-item-check pe-none\" type=\"radio\" name=\"listGroupCheckableRadios\" id=\"listGroupCheckableRadios3\" value=\"\">\n  <label class=\"list-group-item rounded-3 py-3\" for=\"listGroupCheckableRadios3\">\n    Third radio\n    <span class=\"d-block small opacity-50\">And we end with another snippet of text</span>\n  </label>\n\n  <input class=\"list-group-item-check pe-none\" type=\"radio\" name=\"listGroupCheckableRadios\" id=\"listGroupCheckableRadios4\" value=\"\" disabled>\n  <label class=\"list-group-item rounded-3 py-3\" for=\"listGroupCheckableRadios4\">\n    Fourth disabled radio\n    <span class=\"d-block small opacity-50\">This option is disabled</span>\n  </label>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"list-group list-group-radio d-grid gap-2 border-0 w-auto\">\n  <div class=\"position-relative\">\n    <input class=\"form-check-input position-absolute top-50 end-0 me-3 fs-5\" type=\"radio\" name=\"listGroupRadioGrid\" id=\"listGroupRadioGrid1\" value=\"\" checked>\n    <label class=\"list-group-item py-3 pe-5\" for=\"listGroupRadioGrid1\">\n      <strong class=\"fw-semibold\">First radio</strong>\n      <span class=\"d-block small opacity-75\">With support text underneath to add more detail</span>\n    </label>\n  </div>\n\n  <div class=\"position-relative\">\n    <input class=\"form-check-input position-absolute top-50 end-0 me-3 fs-5\" type=\"radio\" name=\"listGroupRadioGrid\" id=\"listGroupRadioGrid2\" value=\"\">\n    <label class=\"list-group-item py-3 pe-5\" for=\"listGroupRadioGrid2\">\n      <strong class=\"fw-semibold\">Second radio</strong>\n      <span class=\"d-block small opacity-75\">Some other text goes here</span>\n    </label>\n  </div>\n\n  <div class=\"position-relative\">\n    <input class=\"form-check-input position-absolute top-50 end-0 me-3 fs-5\" type=\"radio\" name=\"listGroupRadioGrid\" id=\"listGroupRadioGrid3\" value=\"\">\n    <label class=\"list-group-item py-3 pe-5\" for=\"listGroupRadioGrid3\">\n      <strong class=\"fw-semibold\">Third radio</strong>\n      <span class=\"d-block small opacity-75\">And we end with another snippet of text</span>\n    </label>\n  </div>\n\n  <div class=\"position-relative\">\n    <input class=\"form-check-input position-absolute top-50 end-0 me-3 fs-5\" type=\"radio\" name=\"listGroupRadioGrid\" id=\"listGroupRadioGrid4\" value=\"\" disabled>\n    <label class=\"list-group-item py-3 pe-5\" for=\"listGroupRadioGrid4\">\n      <strong class=\"fw-semibold\">Fourth disabled radio</strong>\n      <span class=\"d-block small opacity-75\">This option is disabled</span>\n    </label>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/list-groups/list-groups.css",
    "content": ".list-group {\n  max-width: 460px;\n  margin: 4rem auto;\n}\n\n.form-check-input:checked + .form-checked-content {\n  opacity: .5;\n}\n\n.form-check-input-placeholder {\n  border-style: dashed;\n}\n[contenteditable]:focus {\n  outline: 0;\n}\n\n.list-group-checkable .list-group-item {\n  cursor: pointer;\n}\n.list-group-item-check {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n}\n.list-group-item-check:hover + .list-group-item {\n  background-color: var(--bs-light);\n}\n.list-group-item-check:checked + .list-group-item {\n  color: #fff;\n  background-color: var(--bs-blue);\n}\n.list-group-item-check[disabled] + .list-group-item,\n.list-group-item-check:disabled + .list-group-item {\n  pointer-events: none;\n  filter: none;\n  opacity: .5;\n}\n\n.list-group-radio .list-group-item {\n  cursor: pointer;\n  border-radius: .5rem;\n}\n.list-group-radio .form-check-input {\n  z-index: 2;\n  margin-top: -.5em;\n}\n.list-group-radio .list-group-item:hover,\n.list-group-radio .list-group-item:focus {\n  background-color: var(--bs-light);\n}\n\n.list-group-radio .form-check-input:checked + .list-group-item {\n  background-color: var(--bs-body);\n  border-color: var(--bs-blue);\n  box-shadow: 0 0 0 2px var(--bs-blue);\n}\n.list-group-radio .form-check-input[disabled] + .list-group-item,\n.list-group-radio .form-check-input:disabled + .list-group-item {\n  pointer-events: none;\n  filter: none;\n  opacity: .5;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/masonry/index.html",
    "content": "---\nlayout: examples\ntitle: Masonry example\nextra_js:\n  - src: \"https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js\"\n    integrity: \"sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D\"\n    async: true\n---\n\n<main class=\"container py-5\">\n  <h1>Bootstrap and Masonry</h1>\n  <p class=\"lead\">Integrate <a href=\"https://masonry.desandro.com/\">Masonry</a> with the Bootstrap grid system and cards component.</p>\n\n  <p>Masonry is not included in Bootstrap. Add it by including the JavaScript plugin manually, or using a CDN like so:</p>\n\n  <pre><code>\n&lt;script src=&quot;https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js&quot; integrity=&quot;sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D&quot; crossorigin=&quot;anonymous&quot; async&gt;&lt;/script&gt;\n  </code></pre>\n\n  <p>By adding <code>data-masonry='{\"percentPosition\": true }'</code> to the <code>.row</code> wrapper, we can combine the powers of Bootstrap's responsive grid and Masonry's positioning.</p>\n\n  <hr class=\"my-5\">\n\n  <div class=\"row\" data-masonry='{\"percentPosition\": true }'>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card\">\n        {{< placeholder width=\"100%\" height=\"200\" class=\"card-img-top\" text=\"Image cap\" >}}\n        <div class=\"card-body\">\n          <h5 class=\"card-title\">Card title that wraps to a new line</h5>\n          <p class=\"card-text\">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>\n        </div>\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card p-3\">\n        <figure class=\"p-3 mb-0\">\n          <blockquote class=\"blockquote\">\n            <p>A well-known quote, contained in a blockquote element.</p>\n          </blockquote>\n          <figcaption class=\"blockquote-footer mb-0 text-muted\">\n            Someone famous in <cite title=\"Source Title\">Source Title</cite>\n          </figcaption>\n        </figure>\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card\">\n        {{< placeholder width=\"100%\" height=\"200\" class=\"card-img-top\" text=\"Image cap\" >}}\n        <div class=\"card-body\">\n          <h5 class=\"card-title\">Card title</h5>\n          <p class=\"card-text\">This card has supporting text below as a natural lead-in to additional content.</p>\n          <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n        </div>\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card text-bg-primary text-center p-3\">\n        <figure class=\"mb-0\">\n          <blockquote class=\"blockquote\">\n            <p>A well-known quote, contained in a blockquote element.</p>\n          </blockquote>\n          <figcaption class=\"blockquote-footer mb-0 text-white\">\n            Someone famous in <cite title=\"Source Title\">Source Title</cite>\n          </figcaption>\n        </figure>\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card text-center\">\n        <div class=\"card-body\">\n          <h5 class=\"card-title\">Card title</h5>\n          <p class=\"card-text\">This card has a regular title and short paragraph of text below it.</p>\n          <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n        </div>\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card\">\n        {{< placeholder width=\"100%\" height=\"260\" class=\"card-img\" text=\"Card image\" >}}\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card p-3 text-end\">\n        <figure class=\"mb-0\">\n          <blockquote class=\"blockquote\">\n            <p>A well-known quote, contained in a blockquote element.</p>\n          </blockquote>\n          <figcaption class=\"blockquote-footer mb-0 text-muted\">\n            Someone famous in <cite title=\"Source Title\">Source Title</cite>\n          </figcaption>\n        </figure>\n      </div>\n    </div>\n    <div class=\"col-sm-6 col-lg-4 mb-4\">\n      <div class=\"card\">\n        <div class=\"card-body\">\n          <h5 class=\"card-title\">Card title</h5>\n          <p class=\"card-text\">This is another card with title and supporting text below. This card has some additional content to make it slightly taller overall.</p>\n          <p class=\"card-text\"><small class=\"text-muted\">Last updated 3 mins ago</small></p>\n        </div>\n      </div>\n    </div>\n  </div>\n\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/modals/index.html",
    "content": "---\nlayout: examples\ntitle: Modals\nextra_css:\n  - \"modals.css\"\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n\n  <symbol id=\"exclamation-triangle-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\"/>\n  </symbol>\n\n  <symbol id=\"check2\" viewBox=\"0 0 16 16\">\n    <path d=\"M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z\"/>\n  </symbol>\n\n  <symbol id=\"check2-circle\" viewBox=\"0 0 16 16\">\n    <path d=\"M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0z\"/>\n    <path d=\"M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z\"/>\n  </symbol>\n\n  <symbol id=\"bookmark-star\" viewBox=\"0 0 16 16\">\n    <path d=\"M7.84 4.1a.178.178 0 0 1 .32 0l.634 1.285a.178.178 0 0 0 .134.098l1.42.206c.145.021.204.2.098.303L9.42 6.993a.178.178 0 0 0-.051.158l.242 1.414a.178.178 0 0 1-.258.187l-1.27-.668a.178.178 0 0 0-.165 0l-1.27.668a.178.178 0 0 1-.257-.187l.242-1.414a.178.178 0 0 0-.05-.158l-1.03-1.001a.178.178 0 0 1 .098-.303l1.42-.206a.178.178 0 0 0 .134-.098L7.84 4.1z\"/>\n    <path d=\"M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v13.5a.5.5 0 0 1-.777.416L8 13.101l-5.223 2.815A.5.5 0 0 1 2 15.5V2zm2-1a1 1 0 0 0-1 1v12.566l4.723-2.482a.5.5 0 0 1 .554 0L13 14.566V2a1 1 0 0 0-1-1H4z\"/>\n  </symbol>\n\n  <symbol id=\"grid-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zm8 0A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm-8 8A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm8 0A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3z\"/>\n  </symbol>\n\n  <symbol id=\"stars\" viewBox=\"0 0 16 16\">\n    <path d=\"M7.657 6.247c.11-.33.576-.33.686 0l.645 1.937a2.89 2.89 0 0 0 1.829 1.828l1.936.645c.33.11.33.576 0 .686l-1.937.645a2.89 2.89 0 0 0-1.828 1.829l-.645 1.936a.361.361 0 0 1-.686 0l-.645-1.937a2.89 2.89 0 0 0-1.828-1.828l-1.937-.645a.361.361 0 0 1 0-.686l1.937-.645a2.89 2.89 0 0 0 1.828-1.828l.645-1.937zM3.794 1.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387A1.734 1.734 0 0 0 4.593 5.69l-.387 1.162a.217.217 0 0 1-.412 0L3.407 5.69A1.734 1.734 0 0 0 2.31 4.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387A1.734 1.734 0 0 0 3.407 2.31l.387-1.162zM10.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732L9.1 2.137a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L10.863.1z\"/>\n  </symbol>\n\n  <symbol id=\"film\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1zm4 0v6h8V1H4zm8 8H4v6h8V9zM1 1v2h2V1H1zm2 3H1v2h2V4zM1 7v2h2V7H1zm2 3H1v2h2v-2zm-2 3v2h2v-2H1zM15 1h-2v2h2V1zm-2 3v2h2V4h-2zm2 3h-2v2h2V7zm-2 3v2h2v-2h-2zm2 3h-2v2h2v-2z\"/>\n  </symbol>\n\n  <symbol id=\"github\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z\"/>\n  </symbol>\n\n  <symbol id=\"twitter\" viewBox=\"0 0 16 16\">\n    <path d=\"M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z\"/>\n  </symbol>\n\n  <symbol id=\"facebook\" viewBox=\"0 0 16 16\">\n    <path d=\"M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z\"/>\n  </symbol>\n</svg>\n\n<div class=\"modal modal-sheet position-static d-block bg-secondary py-5\" tabindex=\"-1\" role=\"dialog\" id=\"modalSheet\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content rounded-4 shadow\">\n      <div class=\"modal-header border-bottom-0\">\n        <h1 class=\"modal-title fs-5\">Modal title</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n      <div class=\"modal-body py-0\">\n        <p>This is a modal sheet, a variation of the modal that docs itself to the bottom of the viewport like the newer share sheets in iOS.</p>\n      </div>\n      <div class=\"modal-footer flex-column border-top-0\">\n        <button type=\"button\" class=\"btn btn-lg btn-primary w-100 mx-0 mb-2\">Save changes</button>\n        <button type=\"button\" class=\"btn btn-lg btn-light w-100 mx-0\" data-bs-dismiss=\"modal\">Close</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"modal modal-alert position-static d-block bg-secondary py-5\" tabindex=\"-1\" role=\"dialog\" id=\"modalChoice\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content rounded-3 shadow\">\n      <div class=\"modal-body p-4 text-center\">\n        <h5 class=\"mb-0\">Enable this setting?</h5>\n        <p class=\"mb-0\">You can always change your mind in your account settings.</p>\n      </div>\n      <div class=\"modal-footer flex-nowrap p-0\">\n        <button type=\"button\" class=\"btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0 border-end\"><strong>Yes, enable</strong></button>\n        <button type=\"button\" class=\"btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0\" data-bs-dismiss=\"modal\">No thanks</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"modal modal-tour position-static d-block bg-secondary py-5\" tabindex=\"-1\" role=\"dialog\" id=\"modalTour\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content rounded-4 shadow\">\n      <div class=\"modal-body p-5\">\n        <h2 class=\"fw-bold mb-0\">What's new</h2>\n\n        <ul class=\"d-grid gap-4 my-5 list-unstyled\">\n          <li class=\"d-flex gap-4\">\n            <svg class=\"bi text-muted flex-shrink-0\" width=\"48\" height=\"48\"><use xlink:href=\"#grid-fill\"/></svg>\n            <div>\n              <h5 class=\"mb-0\">Grid view</h5>\n              Not into lists? Try the new grid view.\n            </div>\n          </li>\n          <li class=\"d-flex gap-4\">\n            <svg class=\"bi text-warning flex-shrink-0\" width=\"48\" height=\"48\"><use xlink:href=\"#bookmark-star\"/></svg>\n            <div>\n              <h5 class=\"mb-0\">Bookmarks</h5>\n              Save items you love for easy access later.\n            </div>\n          </li>\n          <li class=\"d-flex gap-4\">\n            <svg class=\"bi text-primary flex-shrink-0\" width=\"48\" height=\"48\"><use xlink:href=\"#film\"/></svg>\n            <div>\n              <h5 class=\"mb-0\">Video embeds</h5>\n              Share videos wherever you go.\n            </div>\n          </li>\n        </ul>\n        <button type=\"button\" class=\"btn btn-lg btn-primary mt-5 w-100\" data-bs-dismiss=\"modal\">Great, thanks!</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n\n<div class=\"modal modal-signin position-static d-block bg-secondary py-5\" tabindex=\"-1\" role=\"dialog\" id=\"modalSignin\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content rounded-4 shadow\">\n      <div class=\"modal-header p-5 pb-4 border-bottom-0\">\n        <!-- <h1 class=\"modal-title fs-5\" >Modal title</h1> -->\n        <h1 class=\"fw-bold mb-0 fs-2\">Sign up for free</h1>\n        <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n      </div>\n\n      <div class=\"modal-body p-5 pt-0\">\n        <form class=\"\">\n          <div class=\"form-floating mb-3\">\n            <input type=\"email\" class=\"form-control rounded-3\" id=\"floatingInput\" placeholder=\"name@example.com\">\n            <label for=\"floatingInput\">Email address</label>\n          </div>\n          <div class=\"form-floating mb-3\">\n            <input type=\"password\" class=\"form-control rounded-3\" id=\"floatingPassword\" placeholder=\"Password\">\n            <label for=\"floatingPassword\">Password</label>\n          </div>\n          <button class=\"w-100 mb-2 btn btn-lg rounded-3 btn-primary\" type=\"submit\">Sign up</button>\n          <small class=\"text-muted\">By clicking Sign up, you agree to the terms of use.</small>\n          <hr class=\"my-4\">\n          <h2 class=\"fs-5 fw-bold mb-3\">Or use a third-party</h2>\n          <button class=\"w-100 py-2 mb-2 btn btn-outline-dark rounded-3\" type=\"submit\">\n            <svg class=\"bi me-1\" width=\"16\" height=\"16\"><use xlink:href=\"#twitter\"/></svg>\n            Sign up with Twitter\n          </button>\n          <button class=\"w-100 py-2 mb-2 btn btn-outline-primary rounded-3\" type=\"submit\">\n            <svg class=\"bi me-1\" width=\"16\" height=\"16\"><use xlink:href=\"#facebook\"/></svg>\n            Sign up with Facebook\n          </button>\n          <button class=\"w-100 py-2 mb-2 btn btn-outline-secondary rounded-3\" type=\"submit\">\n            <svg class=\"bi me-1\" width=\"16\" height=\"16\"><use xlink:href=\"#github\"/></svg>\n            Sign up with GitHub\n          </button>\n        </form>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"b-example-divider\"></div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/modals/modals.css",
    "content": ".modal-sheet .modal-dialog {\n  width: 380px;\n  transition: bottom .75s ease-in-out;\n}\n.modal-sheet .modal-footer {\n  padding-bottom: 2rem;\n}\n\n.modal-alert .modal-dialog {\n  width: 380px;\n}\n\n.modal-tour .modal-dialog {\n  width: 380px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbar-bottom/index.html",
    "content": "---\nlayout: examples\ntitle: Bottom navbar example\n---\n\n<main class=\"container\">\n  <div class=\"bg-light p-5 rounded mt-3\">\n    <h1>Bottom Navbar example</h1>\n    <p class=\"lead\">This example is a quick exercise to illustrate how the bottom navbar works.</p>\n    <a class=\"btn btn-lg btn-primary\" href=\"{{< docsref \"/components/navbar\" >}}\" role=\"button\">View navbar docs &raquo;</a>\n  </div>\n</main>\n<nav class=\"navbar fixed-bottom navbar-expand-sm navbar-dark bg-dark\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Bottom navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n      <ul class=\"navbar-nav\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n        <li class=\"nav-item dropup\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropup</a>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n      </ul>\n    </div>\n  </div>\n</nav>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbar-fixed/index.html",
    "content": "---\nlayout: examples\ntitle: Fixed top navbar example\nextra_css:\n  - \"navbar-top-fixed.css\"\n---\n\n<nav class=\"navbar navbar-expand-md navbar-dark fixed-top bg-dark\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Fixed navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-md-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n\n<main class=\"container\">\n  <div class=\"bg-light p-5 rounded\">\n    <h1>Navbar example</h1>\n    <p class=\"lead\">This example is a quick exercise to illustrate how fixed to top navbar works. As you scroll, it will remain fixed to the top of your browser’s viewport.</p>\n    <a class=\"btn btn-lg btn-primary\" href=\"{{< docsref \"/components/navbar\" >}}\" role=\"button\">View navbar docs &raquo;</a>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbar-fixed/navbar-top-fixed.css",
    "content": "/* Show it is fixed to the top */\nbody {\n  min-height: 75rem;\n  padding-top: 4.5rem;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbar-static/index.html",
    "content": "---\nlayout: examples\ntitle: Top navbar example\nextra_css:\n  - \"navbar-top.css\"\n---\n\n<nav class=\"navbar navbar-expand-md navbar-dark bg-dark mb-4\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Top navbar</a>\n    <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-md-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Link</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link disabled\">Disabled</a>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n\n<main class=\"container\">\n  <div class=\"bg-light p-5 rounded\">\n    <h1>Navbar example</h1>\n    <p class=\"lead\">This example is a quick exercise to illustrate how the top-aligned navbar works. As you scroll, this navbar remains in its original position and moves with the rest of the page.</p>\n    <a class=\"btn btn-lg btn-primary\" href=\"{{< docsref \"/components/navbar\" >}}\" role=\"button\">View navbar docs &raquo;</a>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbar-static/navbar-top.css",
    "content": "/* Show it's not fixed to the top */\nbody {\n  min-height: 75rem;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbars/index.html",
    "content": "---\nlayout: examples\ntitle: Navbar Template\nextra_css:\n  - \"navbar.css\"\n---\n\n<main>\n  <nav class=\"navbar navbar-dark bg-dark\" aria-label=\"First navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Never expand</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample01\" aria-controls=\"navbarsExample01\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample01\">\n        <ul class=\"navbar-nav me-auto mb-2\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand navbar-dark bg-dark\" aria-label=\"Second navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Always expand</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample02\" aria-controls=\"navbarsExample02\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample02\">\n        <ul class=\"navbar-nav me-auto\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-sm navbar-dark bg-dark\" aria-label=\"Third navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Expand at sm</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample03\" aria-controls=\"navbarsExample03\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample03\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-sm-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-md navbar-dark bg-dark\" aria-label=\"Fourth navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Expand at md</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample04\" aria-controls=\"navbarsExample04\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample04\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-md-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\" aria-label=\"Fifth navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Expand at lg</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample05\" aria-controls=\"navbarsExample05\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample05\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-xl navbar-dark bg-dark\" aria-label=\"Sixth navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Expand at xl</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample06\" aria-controls=\"navbarsExample06\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample06\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-xl-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-xxl navbar-dark bg-dark\" aria-label=\"Seventh navbar example\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Expand at xxl</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExampleXxl\" aria-controls=\"navbarsExampleXxl\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExampleXxl\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-xl-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\" aria-label=\"Eighth navbar example\">\n    <div class=\"container\">\n      <a class=\"navbar-brand\" href=\"#\">Container</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample07\" aria-controls=\"navbarsExample07\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample07\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\" aria-label=\"Ninth navbar example\">\n    <div class=\"container-xl\">\n      <a class=\"navbar-brand\" href=\"#\">Container XL</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample07XL\" aria-controls=\"navbarsExample07XL\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse\" id=\"navbarsExample07XL\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n        <form role=\"search\">\n          <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        </form>\n      </div>\n    </div>\n  </nav>\n\n  <div class=\"container-xl mb-4\">\n    <p>Matching .container-xl...</p>\n  </div>\n\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\" aria-label=\"Tenth navbar example\">\n    <div class=\"container-fluid\">\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample08\" aria-controls=\"navbarsExample08\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n\n      <div class=\"collapse navbar-collapse justify-content-md-center\" id=\"navbarsExample08\">\n        <ul class=\"navbar-nav\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Centered nav only</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n          <li class=\"nav-item dropdown\">\n            <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n            </ul>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n\n  <div class=\"container\">\n    <nav class=\"navbar navbar-expand-lg bg-light rounded\" aria-label=\"Eleventh navbar example\">\n      <div class=\"container-fluid\">\n        <a class=\"navbar-brand\" href=\"#\">Navbar</a>\n        <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample09\" aria-controls=\"navbarsExample09\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n          <span class=\"navbar-toggler-icon\"></span>\n        </button>\n\n        <div class=\"collapse navbar-collapse\" id=\"navbarsExample09\">\n          <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n            <li class=\"nav-item\">\n              <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link disabled\">Disabled</a>\n            </li>\n            <li class=\"nav-item dropdown\">\n              <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n              <ul class=\"dropdown-menu\">\n                <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              </ul>\n            </li>\n          </ul>\n          <form role=\"search\">\n            <input class=\"form-control\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          </form>\n        </div>\n      </div>\n    </nav>\n\n    <nav class=\"navbar navbar-expand-lg bg-light rounded\" aria-label=\"Twelfth navbar example\">\n      <div class=\"container-fluid\">\n        <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarsExample10\" aria-controls=\"navbarsExample10\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n          <span class=\"navbar-toggler-icon\"></span>\n        </button>\n\n        <div class=\"collapse navbar-collapse justify-content-md-center\" id=\"navbarsExample10\">\n          <ul class=\"navbar-nav\">\n            <li class=\"nav-item\">\n              <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Centered nav only</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link disabled\">Disabled</a>\n            </li>\n            <li class=\"nav-item dropdown\">\n              <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</a>\n              <ul class=\"dropdown-menu\">\n                <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              </ul>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </nav>\n\n    <div>\n      <div class=\"bg-light p-5 rounded\">\n        <div class=\"col-sm-8 mx-auto\">\n          <h1>Navbar examples</h1>\n          <p>This example is a quick exercise to illustrate how the navbar and its contents work. Some navbars extend the width of the viewport, others are confined within a <code>.container</code>. For positioning of navbars, checkout the <a href=\"{{< docsref \"/examples/navbar-static\" >}}\">top</a> and <a href=\"{{< docsref \"/examples/navbar-fixed\" >}}\">fixed top</a> examples.</p>\n          <p>At the smallest breakpoint, the collapse plugin is used to hide the links and show a menu button to toggle the collapsed content.</p>\n          <p>\n            <a class=\"btn btn-primary\" href=\"{{< docsref \"/components/navbar\" >}}\" role=\"button\">View navbar docs &raquo;</a>\n          </p>\n        </div>\n      </div>\n    </div>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbars/navbar.css",
    "content": "body {\n  padding-bottom: 20px;\n}\n\n.navbar {\n  margin-bottom: 20px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbars-offcanvas/index.html",
    "content": "---\nlayout: examples\ntitle: Navbar Template\nextra_css:\n  - \"navbar.css\"\n---\n\n<main>\n  <nav class=\"navbar navbar-dark bg-dark\" aria-label=\"Dark offcanvas navbar\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Dark offcanvas navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasNavbarDark\" aria-controls=\"offcanvasNavbarDark\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"offcanvas offcanvas-end text-bg-dark\" tabindex=\"-1\" id=\"offcanvasNavbarDark\" aria-labelledby=\"offcanvasNavbarDarkLabel\">\n        <div class=\"offcanvas-header\">\n          <h5 class=\"offcanvas-title\" id=\"offcanvasNavbarDarkLabel\">Offcanvas</h5>\n          <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n        </div>\n        <div class=\"offcanvas-body\">\n          <ul class=\"navbar-nav justify-content-end flex-grow-1 pe-3\">\n            <li class=\"nav-item\">\n              <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item dropdown\">\n              <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                Dropdown\n              </a>\n              <ul class=\"dropdown-menu\">\n                <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                <li>\n                  <hr class=\"dropdown-divider\">\n                </li>\n                <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              </ul>\n            </li>\n          </ul>\n          <form class=\"d-flex mt-3\" role=\"search\">\n            <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n            <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n          </form>\n        </div>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar bg-light\" aria-label=\"Light offcanvas navbar\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Light offcanvas navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasNavbarLight\" aria-controls=\"offcanvasNavbarLight\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"offcanvas offcanvas-end\" tabindex=\"-1\" id=\"offcanvasNavbarLight\" aria-labelledby=\"offcanvasNavbarLightLabel\">\n        <div class=\"offcanvas-header\">\n          <h5 class=\"offcanvas-title\" id=\"offcanvasNavbarLightLabel\">Offcanvas</h5>\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n        </div>\n        <div class=\"offcanvas-body\">\n          <ul class=\"navbar-nav justify-content-end flex-grow-1 pe-3\">\n            <li class=\"nav-item\">\n              <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item dropdown\">\n              <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                Dropdown\n              </a>\n              <ul class=\"dropdown-menu\">\n                <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                <li>\n                  <hr class=\"dropdown-divider\">\n                </li>\n                <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              </ul>\n            </li>\n          </ul>\n          <form class=\"d-flex mt-3\" role=\"search\">\n            <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n            <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n          </form>\n        </div>\n      </div>\n    </div>\n  </nav>\n\n  <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark\" aria-label=\"Offcanvas navbar large\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Responsive offcanvas navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasNavbar2\" aria-controls=\"offcanvasNavbar2\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"offcanvas offcanvas-end text-bg-dark\" tabindex=\"-1\" id=\"offcanvasNavbar2\" aria-labelledby=\"offcanvasNavbar2Label\">\n        <div class=\"offcanvas-header\">\n          <h5 class=\"offcanvas-title\" id=\"offcanvasNavbar2Label\">Offcanvas</h5>\n          <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n        </div>\n        <div class=\"offcanvas-body\">\n          <ul class=\"navbar-nav justify-content-end flex-grow-1 pe-3\">\n            <li class=\"nav-item\">\n              <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n            </li>\n            <li class=\"nav-item\">\n              <a class=\"nav-link\" href=\"#\">Link</a>\n            </li>\n            <li class=\"nav-item dropdown\">\n              <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                Dropdown\n              </a>\n              <ul class=\"dropdown-menu\">\n                <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n                <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n                <li>\n                  <hr class=\"dropdown-divider\">\n                </li>\n                <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n              </ul>\n            </li>\n          </ul>\n          <form class=\"d-flex mt-3 mt-lg-0\" role=\"search\">\n            <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n            <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n          </form>\n        </div>\n      </div>\n    </div>\n  </nav>\n\n  <div class=\"container my-5\">\n    <div class=\"bg-light p-5 rounded\">\n      <div class=\"col-sm-8 py-5 mx-auto\">\n        <h1 class=\"display-5 fw-normal\">Navbar with offcanvas examples</h1>\n        <p class=\"fs-5\">This example shows how responsive offcanvas menus work within the navbar. For positioning of navbars, checkout the <a href=\"{{< docsref \"/examples/navbar-static\" >}}\">top</a> and <a href=\"{{< docsref \"/examples/navbar-fixed\" >}}\">fixed top</a> examples.</p>\n        <p>From the top down, you'll see a dark navbar, light navbar and a responsive navbar—each with offcanvases built in. Resize your browser window to the large breakpoint to see the toggle for the offcanvas.</p>\n        <p>\n          <a class=\"btn btn-primary\" href=\"{{< docsref \"/components/navbar#offcanvas\" >}}\" role=\"button\">Learn more about offcanvas navbars &raquo;</a>\n        </p>\n    </div>\n    </div>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/navbars-offcanvas/navbar.css",
    "content": "body {\n  padding-bottom: 20px;\n}\n\n.navbar {\n  margin-bottom: 20px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/offcanvas-navbar/index.html",
    "content": "---\nlayout: examples\ntitle: Offcanvas navbar template\nextra_css:\n  - \"offcanvas.css\"\nextra_js:\n  - src: \"offcanvas.js\"\nbody_class: \"bg-light\"\naliases: \"/docs/5.2/examples/offcanvas/\"\n---\n\n<nav class=\"navbar navbar-expand-lg fixed-top navbar-dark bg-dark\" aria-label=\"Main navigation\">\n  <div class=\"container-fluid\">\n    <a class=\"navbar-brand\" href=\"#\">Offcanvas navbar</a>\n    <button class=\"navbar-toggler p-0 border-0\" type=\"button\" id=\"navbarSideCollapse\" aria-label=\"Toggle navigation\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div class=\"navbar-collapse offcanvas-collapse\" id=\"navbarsExampleDefault\">\n      <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Dashboard</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Notifications</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Profile</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"#\">Switch account</a>\n        </li>\n        <li class=\"nav-item dropdown\">\n          <a class=\"nav-link dropdown-toggle\" href=\"#\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Settings</a>\n          <ul class=\"dropdown-menu\">\n            <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n            <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n          </ul>\n        </li>\n      </ul>\n      <form class=\"d-flex\" role=\"search\">\n        <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n        <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n      </form>\n    </div>\n  </div>\n</nav>\n\n<div class=\"nav-scroller bg-body shadow-sm\">\n  <nav class=\"nav\" aria-label=\"Secondary navigation\">\n    <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Dashboard</a>\n    <a class=\"nav-link\" href=\"#\">\n      Friends\n      <span class=\"badge text-bg-light rounded-pill align-text-bottom\">27</span>\n    </a>\n    <a class=\"nav-link\" href=\"#\">Explore</a>\n    <a class=\"nav-link\" href=\"#\">Suggestions</a>\n    <a class=\"nav-link\" href=\"#\">Link</a>\n    <a class=\"nav-link\" href=\"#\">Link</a>\n    <a class=\"nav-link\" href=\"#\">Link</a>\n    <a class=\"nav-link\" href=\"#\">Link</a>\n    <a class=\"nav-link\" href=\"#\">Link</a>\n  </nav>\n</div>\n\n<main class=\"container\">\n  <div class=\"d-flex align-items-center p-3 my-3 text-white bg-purple rounded shadow-sm\">\n    <img class=\"me-3\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo-white.svg\" alt=\"\" width=\"48\" height=\"38\">\n    <div class=\"lh-1\">\n      <h1 class=\"h6 mb-0 text-white lh-1\">Bootstrap</h1>\n      <small>Since 2011</small>\n    </div>\n  </div>\n\n  <div class=\"my-3 p-3 bg-body rounded shadow-sm\">\n    <h6 class=\"border-bottom pb-2 mb-0\">Recent updates</h6>\n    <div class=\"d-flex text-muted pt-3\">\n      {{< placeholder width=\"32\" height=\"32\" background=\"#007bff\" color=\"#007bff\" class=\"flex-shrink-0 me-2 rounded\" >}}\n      <p class=\"pb-3 mb-0 small lh-sm border-bottom\">\n        <strong class=\"d-block text-gray-dark\">@username</strong>\n        Some representative placeholder content, with some information about this user. Imagine this being some sort of status update, perhaps?\n      </p>\n    </div>\n    <div class=\"d-flex text-muted pt-3\">\n      {{< placeholder width=\"32\" height=\"32\" background=\"#e83e8c\" color=\"#e83e8c\" class=\"flex-shrink-0 me-2 rounded\" >}}\n      <p class=\"pb-3 mb-0 small lh-sm border-bottom\">\n        <strong class=\"d-block text-gray-dark\">@username</strong>\n        Some more representative placeholder content, related to this other user. Another status update, perhaps.\n      </p>\n    </div>\n    <div class=\"d-flex text-muted pt-3\">\n      {{< placeholder width=\"32\" height=\"32\" background=\"#6f42c1\" color=\"#6f42c1\" class=\"flex-shrink-0 me-2 rounded\" >}}\n      <p class=\"pb-3 mb-0 small lh-sm border-bottom\">\n        <strong class=\"d-block text-gray-dark\">@username</strong>\n        This user also gets some representative placeholder content. Maybe they did something interesting, and you really want to highlight this in the recent updates.\n      </p>\n    </div>\n    <small class=\"d-block text-end mt-3\">\n      <a href=\"#\">All updates</a>\n    </small>\n  </div>\n\n  <div class=\"my-3 p-3 bg-body rounded shadow-sm\">\n    <h6 class=\"border-bottom pb-2 mb-0\">Suggestions</h6>\n    <div class=\"d-flex text-muted pt-3\">\n      {{< placeholder width=\"32\" height=\"32\" background=\"#007bff\" color=\"#007bff\" class=\"flex-shrink-0 me-2 rounded\" >}}\n      <div class=\"pb-3 mb-0 small lh-sm border-bottom w-100\">\n        <div class=\"d-flex justify-content-between\">\n          <strong class=\"text-gray-dark\">Full Name</strong>\n          <a href=\"#\">Follow</a>\n        </div>\n        <span class=\"d-block\">@username</span>\n      </div>\n    </div>\n    <div class=\"d-flex text-muted pt-3\">\n      {{< placeholder width=\"32\" height=\"32\" background=\"#007bff\" color=\"#007bff\" class=\"flex-shrink-0 me-2 rounded\" >}}\n      <div class=\"pb-3 mb-0 small lh-sm border-bottom w-100\">\n        <div class=\"d-flex justify-content-between\">\n          <strong class=\"text-gray-dark\">Full Name</strong>\n          <a href=\"#\">Follow</a>\n        </div>\n        <span class=\"d-block\">@username</span>\n      </div>\n    </div>\n    <div class=\"d-flex text-muted pt-3\">\n      {{< placeholder width=\"32\" height=\"32\" background=\"#007bff\" color=\"#007bff\" class=\"flex-shrink-0 me-2 rounded\" >}}\n      <div class=\"pb-3 mb-0 small lh-sm border-bottom w-100\">\n        <div class=\"d-flex justify-content-between\">\n          <strong class=\"text-gray-dark\">Full Name</strong>\n          <a href=\"#\">Follow</a>\n        </div>\n        <span class=\"d-block\">@username</span>\n      </div>\n    </div>\n    <small class=\"d-block text-end mt-3\">\n      <a href=\"#\">All suggestions</a>\n    </small>\n  </div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/offcanvas-navbar/offcanvas.css",
    "content": "html,\nbody {\n  overflow-x: hidden; /* Prevent scroll on narrow devices */\n}\n\nbody {\n  padding-top: 56px;\n}\n\n@media (max-width: 991.98px) {\n  .offcanvas-collapse {\n    position: fixed;\n    top: 56px; /* Height of navbar */\n    bottom: 0;\n    left: 100%;\n    width: 100%;\n    padding-right: 1rem;\n    padding-left: 1rem;\n    overflow-y: auto;\n    visibility: hidden;\n    background-color: #343a40;\n    transition: transform .3s ease-in-out, visibility .3s ease-in-out;\n  }\n  .offcanvas-collapse.open {\n    visibility: visible;\n    transform: translateX(-100%);\n  }\n}\n\n.nav-scroller .nav {\n  color: rgba(255, 255, 255, .75);\n}\n\n.nav-scroller .nav-link {\n  padding-top: .75rem;\n  padding-bottom: .75rem;\n  font-size: .875rem;\n  color: #6c757d;\n}\n\n.nav-scroller .nav-link:hover {\n  color: #007bff;\n}\n\n.nav-scroller .active {\n  font-weight: 500;\n  color: #343a40;\n}\n\n.bg-purple {\n  background-color: #6f42c1;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/offcanvas-navbar/offcanvas.js",
    "content": "(() => {\n  'use strict'\n\n  document.querySelector('#navbarSideCollapse').addEventListener('click', () => {\n    document.querySelector('.offcanvas-collapse').classList.toggle('open')\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/pricing/index.html",
    "content": "---\nlayout: examples\ntitle: Pricing example\nextra_css:\n  - \"pricing.css\"\ninclude_js: false\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"check\" viewBox=\"0 0 16 16\">\n    <title>Check</title>\n    <path d=\"M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z\"/>\n  </symbol>\n</svg>\n\n<div class=\"container py-3\">\n  <header>\n    <div class=\"d-flex flex-column flex-md-row align-items-center pb-3 mb-4 border-bottom\">\n      <a href=\"/\" class=\"d-flex align-items-center text-dark text-decoration-none\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"32\" class=\"me-2\" viewBox=\"0 0 118 94\" role=\"img\"><title>Bootstrap</title><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\" fill=\"currentColor\"></path></svg>\n        <span class=\"fs-4\">Pricing example</span>\n      </a>\n\n      <nav class=\"d-inline-flex mt-2 mt-md-0 ms-md-auto\">\n        <a class=\"me-3 py-2 text-dark text-decoration-none\" href=\"#\">Features</a>\n        <a class=\"me-3 py-2 text-dark text-decoration-none\" href=\"#\">Enterprise</a>\n        <a class=\"me-3 py-2 text-dark text-decoration-none\" href=\"#\">Support</a>\n        <a class=\"py-2 text-dark text-decoration-none\" href=\"#\">Pricing</a>\n      </nav>\n    </div>\n\n    <div class=\"pricing-header p-3 pb-md-4 mx-auto text-center\">\n      <h1 class=\"display-4 fw-normal\">Pricing</h1>\n      <p class=\"fs-5 text-muted\">Quickly build an effective pricing table for your potential customers with this Bootstrap example. It’s built with default Bootstrap components and utilities with little customization.</p>\n    </div>\n  </header>\n\n  <main>\n    <div class=\"row row-cols-1 row-cols-md-3 mb-3 text-center\">\n      <div class=\"col\">\n        <div class=\"card mb-4 rounded-3 shadow-sm\">\n          <div class=\"card-header py-3\">\n            <h4 class=\"my-0 fw-normal\">Free</h4>\n          </div>\n          <div class=\"card-body\">\n            <h1 class=\"card-title pricing-card-title\">$0<small class=\"text-muted fw-light\">/mo</small></h1>\n            <ul class=\"list-unstyled mt-3 mb-4\">\n              <li>10 users included</li>\n              <li>2 GB of storage</li>\n              <li>Email support</li>\n              <li>Help center access</li>\n            </ul>\n            <button type=\"button\" class=\"w-100 btn btn-lg btn-outline-primary\">Sign up for free</button>\n          </div>\n        </div>\n      </div>\n      <div class=\"col\">\n        <div class=\"card mb-4 rounded-3 shadow-sm\">\n          <div class=\"card-header py-3\">\n            <h4 class=\"my-0 fw-normal\">Pro</h4>\n          </div>\n          <div class=\"card-body\">\n            <h1 class=\"card-title pricing-card-title\">$15<small class=\"text-muted fw-light\">/mo</small></h1>\n            <ul class=\"list-unstyled mt-3 mb-4\">\n              <li>20 users included</li>\n              <li>10 GB of storage</li>\n              <li>Priority email support</li>\n              <li>Help center access</li>\n            </ul>\n            <button type=\"button\" class=\"w-100 btn btn-lg btn-primary\">Get started</button>\n          </div>\n        </div>\n      </div>\n      <div class=\"col\">\n        <div class=\"card mb-4 rounded-3 shadow-sm border-primary\">\n          <div class=\"card-header py-3 text-bg-primary border-primary\">\n            <h4 class=\"my-0 fw-normal\">Enterprise</h4>\n          </div>\n          <div class=\"card-body\">\n            <h1 class=\"card-title pricing-card-title\">$29<small class=\"text-muted fw-light\">/mo</small></h1>\n            <ul class=\"list-unstyled mt-3 mb-4\">\n              <li>30 users included</li>\n              <li>15 GB of storage</li>\n              <li>Phone and email support</li>\n              <li>Help center access</li>\n            </ul>\n            <button type=\"button\" class=\"w-100 btn btn-lg btn-primary\">Contact us</button>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <h2 class=\"display-6 text-center mb-4\">Compare plans</h2>\n\n    <div class=\"table-responsive\">\n      <table class=\"table text-center\">\n        <thead>\n          <tr>\n            <th style=\"width: 34%;\"></th>\n            <th style=\"width: 22%;\">Free</th>\n            <th style=\"width: 22%;\">Pro</th>\n            <th style=\"width: 22%;\">Enterprise</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr>\n            <th scope=\"row\" class=\"text-start\">Public</th>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n          </tr>\n          <tr>\n            <th scope=\"row\" class=\"text-start\">Private</th>\n            <td></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n          </tr>\n        </tbody>\n\n        <tbody>\n          <tr>\n            <th scope=\"row\" class=\"text-start\">Permissions</th>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n          </tr>\n          <tr>\n            <th scope=\"row\" class=\"text-start\">Sharing</th>\n            <td></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n          </tr>\n          <tr>\n            <th scope=\"row\" class=\"text-start\">Unlimited members</th>\n            <td></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n          </tr>\n          <tr>\n            <th scope=\"row\" class=\"text-start\">Extra security</th>\n            <td></td>\n            <td></td>\n            <td><svg class=\"bi\" width=\"24\" height=\"24\"><use xlink:href=\"#check\"/></svg></td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  </main>\n\n  <footer class=\"pt-4 my-md-5 pt-md-5 border-top\">\n    <div class=\"row\">\n      <div class=\"col-12 col-md\">\n        <img class=\"mb-2\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"\" width=\"24\" height=\"19\">\n        <small class=\"d-block mb-3 text-muted\">&copy; 2017–{{< year >}}</small>\n      </div>\n      <div class=\"col-6 col-md\">\n        <h5>Features</h5>\n        <ul class=\"list-unstyled text-small\">\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Cool stuff</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Random feature</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Team feature</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Stuff for developers</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Another one</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Last time</a></li>\n        </ul>\n      </div>\n      <div class=\"col-6 col-md\">\n        <h5>Resources</h5>\n        <ul class=\"list-unstyled text-small\">\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Resource</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Resource name</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Another resource</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Final resource</a></li>\n        </ul>\n      </div>\n      <div class=\"col-6 col-md\">\n        <h5>About</h5>\n        <ul class=\"list-unstyled text-small\">\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Team</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Locations</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Privacy</a></li>\n          <li class=\"mb-1\"><a class=\"link-secondary text-decoration-none\" href=\"#\">Terms</a></li>\n        </ul>\n      </div>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/pricing/pricing.css",
    "content": "body {\n  background-image: linear-gradient(180deg, #eee, #fff 100px, #fff);\n}\n\n.container {\n  max-width: 960px;\n}\n\n.pricing-header {\n  max-width: 700px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/product/index.html",
    "content": "---\nlayout: examples\ntitle: Product example\nextra_css:\n  - \"product.css\"\n---\n\n<header class=\"site-header sticky-top py-1\">\n  <nav class=\"container d-flex flex-column flex-md-row justify-content-between\">\n    <a class=\"py-2\" href=\"#\" aria-label=\"Product\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"d-block mx-auto\" role=\"img\" viewBox=\"0 0 24 24\"><title>Product</title><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M14.31 8l5.74 9.94M9.69 8h11.48M7.38 12l5.74-9.94M9.69 16L3.95 6.06M14.31 16H2.83m13.79-4l-5.74 9.94\"/></svg>\n    </a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Tour</a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Product</a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Features</a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Enterprise</a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Support</a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Pricing</a>\n    <a class=\"py-2 d-none d-md-inline-block\" href=\"#\">Cart</a>\n  </nav>\n</header>\n\n<main>\n  <div class=\"position-relative overflow-hidden p-3 p-md-5 m-md-3 text-center bg-light\">\n    <div class=\"col-md-5 p-lg-5 mx-auto my-5\">\n      <h1 class=\"display-4 fw-normal\">Punny headline</h1>\n      <p class=\"lead fw-normal\">And an even wittier subheading to boot. Jumpstart your marketing efforts with this example based on Apple’s marketing pages.</p>\n      <a class=\"btn btn-outline-secondary\" href=\"#\">Coming soon</a>\n    </div>\n    <div class=\"product-device shadow-sm d-none d-md-block\"></div>\n    <div class=\"product-device product-device-2 shadow-sm d-none d-md-block\"></div>\n  </div>\n\n  <div class=\"d-md-flex flex-md-equal w-100 my-md-3 ps-md-3\">\n    <div class=\"text-bg-dark me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 py-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-light shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n    <div class=\"bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 p-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-dark shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n  </div>\n\n  <div class=\"d-md-flex flex-md-equal w-100 my-md-3 ps-md-3\">\n    <div class=\"bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 p-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-dark shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n    <div class=\"text-bg-primary me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 py-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-light shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n  </div>\n\n  <div class=\"d-md-flex flex-md-equal w-100 my-md-3 ps-md-3\">\n    <div class=\"bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 p-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-body shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n    <div class=\"bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 py-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-body shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n  </div>\n\n  <div class=\"d-md-flex flex-md-equal w-100 my-md-3 ps-md-3\">\n    <div class=\"bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 p-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-body shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n    <div class=\"bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden\">\n      <div class=\"my-3 py-3\">\n        <h2 class=\"display-5\">Another headline</h2>\n        <p class=\"lead\">And an even wittier subheading.</p>\n      </div>\n      <div class=\"bg-body shadow-sm mx-auto\" style=\"width: 80%; height: 300px; border-radius: 21px 21px 0 0;\"></div>\n    </div>\n  </div>\n</main>\n\n<footer class=\"container py-5\">\n  <div class=\"row\">\n    <div class=\"col-12 col-md\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"d-block mb-2\" role=\"img\" viewBox=\"0 0 24 24\"><title>Product</title><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M14.31 8l5.74 9.94M9.69 8h11.48M7.38 12l5.74-9.94M9.69 16L3.95 6.06M14.31 16H2.83m13.79-4l-5.74 9.94\"/></svg>\n      <small class=\"d-block mb-3 text-muted\">&copy; 2017–{{< year >}}</small>\n    </div>\n    <div class=\"col-6 col-md\">\n      <h5>Features</h5>\n      <ul class=\"list-unstyled text-small\">\n        <li><a class=\"link-secondary\" href=\"#\">Cool stuff</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Random feature</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Team feature</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Stuff for developers</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Another one</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Last time</a></li>\n      </ul>\n    </div>\n    <div class=\"col-6 col-md\">\n      <h5>Resources</h5>\n      <ul class=\"list-unstyled text-small\">\n        <li><a class=\"link-secondary\" href=\"#\">Resource name</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Resource</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Another resource</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Final resource</a></li>\n      </ul>\n    </div>\n    <div class=\"col-6 col-md\">\n      <h5>Resources</h5>\n      <ul class=\"list-unstyled text-small\">\n        <li><a class=\"link-secondary\" href=\"#\">Business</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Education</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Government</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Gaming</a></li>\n      </ul>\n    </div>\n    <div class=\"col-6 col-md\">\n      <h5>About</h5>\n      <ul class=\"list-unstyled text-small\">\n        <li><a class=\"link-secondary\" href=\"#\">Team</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Locations</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Privacy</a></li>\n        <li><a class=\"link-secondary\" href=\"#\">Terms</a></li>\n      </ul>\n    </div>\n  </div>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/product/product.css",
    "content": ".container {\n  max-width: 960px;\n}\n\n/*\n * Custom translucent site header\n */\n\n.site-header {\n  background-color: rgba(0, 0, 0, .85);\n  -webkit-backdrop-filter: saturate(180%) blur(20px);\n  backdrop-filter: saturate(180%) blur(20px);\n}\n.site-header a {\n  color: #8e8e8e;\n  transition: color .15s ease-in-out;\n}\n.site-header a:hover {\n  color: #fff;\n  text-decoration: none;\n}\n\n/*\n * Dummy devices (replace them with your own or something else entirely!)\n */\n\n.product-device {\n  position: absolute;\n  right: 10%;\n  bottom: -30%;\n  width: 300px;\n  height: 540px;\n  background-color: #333;\n  border-radius: 21px;\n  transform: rotate(30deg);\n}\n\n.product-device::before {\n  position: absolute;\n  top: 10%;\n  right: 10px;\n  bottom: 10%;\n  left: 10px;\n  content: \"\";\n  background-color: rgba(255, 255, 255, .1);\n  border-radius: 5px;\n}\n\n.product-device-2 {\n  top: -25%;\n  right: auto;\n  bottom: 0;\n  left: 5%;\n  background-color: #e5e5e5;\n}\n\n\n/*\n * Extra utilities\n */\n\n.flex-equal > * {\n  flex: 1;\n}\n@media (min-width: 768px) {\n  .flex-md-equal > * {\n    flex: 1;\n  }\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sidebars/index.html",
    "content": "---\nlayout: examples\ntitle: Sidebars\nextra_css:\n  - \"sidebars.css\"\nextra_js:\n  - src: \"sidebars.js\"\nbody_class: \"\"\n---\n\n<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"bootstrap\" viewBox=\"0 0 118 94\">\n    <title>Bootstrap</title>\n    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\"></path>\n  </symbol>\n  <symbol id=\"home\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z\"/>\n  </symbol>\n  <symbol id=\"speedometer2\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z\"/>\n    <path fill-rule=\"evenodd\" d=\"M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z\"/>\n  </symbol>\n  <symbol id=\"table\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z\"/>\n  </symbol>\n  <symbol id=\"people-circle\" viewBox=\"0 0 16 16\">\n    <path d=\"M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z\"/>\n    <path fill-rule=\"evenodd\" d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z\"/>\n  </symbol>\n  <symbol id=\"grid\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z\"/>\n  </symbol>\n  <symbol id=\"collection\" viewBox=\"0 0 16 16\">\n    <path d=\"M2.5 3.5a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-11zm2-2a.5.5 0 0 1 0-1h7a.5.5 0 0 1 0 1h-7zM0 13a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 16 13V6a1.5 1.5 0 0 0-1.5-1.5h-13A1.5 1.5 0 0 0 0 6v7zm1.5.5A.5.5 0 0 1 1 13V6a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-13z\"/>\n  </symbol>\n  <symbol id=\"calendar3\" viewBox=\"0 0 16 16\">\n    <path d=\"M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z\"/>\n    <path d=\"M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z\"/>\n  </symbol>\n  <symbol id=\"chat-quote-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M16 8c0 3.866-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7zM7.194 6.766a1.688 1.688 0 0 0-.227-.272 1.467 1.467 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 5.734 6C4.776 6 4 6.746 4 7.667c0 .92.776 1.666 1.734 1.666.343 0 .662-.095.931-.26-.137.389-.39.804-.81 1.22a.405.405 0 0 0 .011.59c.173.16.447.155.614-.01 1.334-1.329 1.37-2.758.941-3.706a2.461 2.461 0 0 0-.227-.4zM11 9.073c-.136.389-.39.804-.81 1.22a.405.405 0 0 0 .012.59c.172.16.446.155.613-.01 1.334-1.329 1.37-2.758.942-3.706a2.466 2.466 0 0 0-.228-.4 1.686 1.686 0 0 0-.227-.273 1.466 1.466 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 10.07 6c-.957 0-1.734.746-1.734 1.667 0 .92.777 1.666 1.734 1.666.343 0 .662-.095.931-.26z\"/>\n  </symbol>\n  <symbol id=\"cpu-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z\"/>\n    <path d=\"M5.5.5a.5.5 0 0 0-1 0V2A2.5 2.5 0 0 0 2 4.5H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2A2.5 2.5 0 0 0 4.5 14v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14a2.5 2.5 0 0 0 2.5-2.5h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14A2.5 2.5 0 0 0 11.5 2V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5zm1 4.5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3A1.5 1.5 0 0 1 6.5 5z\"/>\n  </symbol>\n  <symbol id=\"gear-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z\"/>\n  </symbol>\n  <symbol id=\"speedometer\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 2a.5.5 0 0 1 .5.5V4a.5.5 0 0 1-1 0V2.5A.5.5 0 0 1 8 2zM3.732 3.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 8a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 8zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 7.31A.91.91 0 1 0 8.85 8.569l3.434-4.297a.389.389 0 0 0-.029-.518z\"/>\n    <path fill-rule=\"evenodd\" d=\"M6.664 15.889A8 8 0 1 1 9.336.11a8 8 0 0 1-2.672 15.78zm-4.665-4.283A11.945 11.945 0 0 1 8 10c2.186 0 4.236.585 6.001 1.606a7 7 0 1 0-12.002 0z\"/>\n  </symbol>\n  <symbol id=\"toggles2\" viewBox=\"0 0 16 16\">\n    <path d=\"M9.465 10H12a2 2 0 1 1 0 4H9.465c.34-.588.535-1.271.535-2 0-.729-.195-1.412-.535-2z\"/>\n    <path d=\"M6 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 1a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm.535-10a3.975 3.975 0 0 1-.409-1H4a1 1 0 0 1 0-2h2.126c.091-.355.23-.69.41-1H4a2 2 0 1 0 0 4h2.535z\"/>\n    <path d=\"M14 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0z\"/>\n  </symbol>\n  <symbol id=\"tools\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 0L0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.356 3.356a1 1 0 0 0 1.414 0l1.586-1.586a1 1 0 0 0 0-1.414l-3.356-3.356a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3c0-.269-.035-.53-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814L1 0zm9.646 10.646a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708zM3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026L3 11z\"/>\n  </symbol>\n  <symbol id=\"chevron-right\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n  </symbol>\n  <symbol id=\"geo-fill\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999zm2.493 8.574a.5.5 0 0 1-.411.575c-.712.118-1.28.295-1.655.493a1.319 1.319 0 0 0-.37.265.301.301 0 0 0-.057.09V14l.002.008a.147.147 0 0 0 .016.033.617.617 0 0 0 .145.15c.165.13.435.27.813.395.751.25 1.82.414 3.024.414s2.273-.163 3.024-.414c.378-.126.648-.265.813-.395a.619.619 0 0 0 .146-.15.148.148 0 0 0 .015-.033L12 14v-.004a.301.301 0 0 0-.057-.09 1.318 1.318 0 0 0-.37-.264c-.376-.198-.943-.375-1.655-.493a.5.5 0 1 1 .164-.986c.77.127 1.452.328 1.957.594C12.5 13 13 13.4 13 14c0 .426-.26.752-.544.977-.29.228-.68.413-1.116.558-.878.293-2.059.465-3.34.465-1.281 0-2.462-.172-3.34-.465-.436-.145-.826-.33-1.116-.558C3.26 14.752 3 14.426 3 14c0-.599.5-1 .961-1.243.505-.266 1.187-.467 1.957-.594a.5.5 0 0 1 .575.411z\"/>\n  </symbol>\n</svg>\n\n<main class=\"d-flex flex-nowrap\">\n  <h1 class=\"visually-hidden\">Sidebars examples</h1>\n\n  <div class=\"d-flex flex-column flex-shrink-0 p-3 text-bg-dark\" style=\"width: 280px;\">\n    <a href=\"/\" class=\"d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none\">\n      <svg class=\"bi pe-none me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n      <span class=\"fs-4\">Sidebar</span>\n    </a>\n    <hr>\n    <ul class=\"nav nav-pills flex-column mb-auto\">\n      <li class=\"nav-item\">\n        <a href=\"#\" class=\"nav-link active\" aria-current=\"page\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#home\"/></svg>\n          Home\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link text-white\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#speedometer2\"/></svg>\n          Dashboard\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link text-white\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#table\"/></svg>\n          Orders\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link text-white\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#grid\"/></svg>\n          Products\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link text-white\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#people-circle\"/></svg>\n          Customers\n        </a>\n      </li>\n    </ul>\n    <hr>\n    <div class=\"dropdown\">\n      <a href=\"#\" class=\"d-flex align-items-center text-white text-decoration-none dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n        <img src=\"https://github.com/mdo.png\" alt=\"\" width=\"32\" height=\"32\" class=\"rounded-circle me-2\">\n        <strong>mdo</strong>\n      </a>\n      <ul class=\"dropdown-menu dropdown-menu-dark text-small shadow\">\n        <li><a class=\"dropdown-item\" href=\"#\">New project...</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Settings</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Profile</a></li>\n        <li><hr class=\"dropdown-divider\"></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Sign out</a></li>\n      </ul>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider b-example-vr\"></div>\n\n  <div class=\"d-flex flex-column flex-shrink-0 p-3 bg-light\" style=\"width: 280px;\">\n    <a href=\"/\" class=\"d-flex align-items-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none\">\n      <svg class=\"bi pe-none me-2\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n      <span class=\"fs-4\">Sidebar</span>\n    </a>\n    <hr>\n    <ul class=\"nav nav-pills flex-column mb-auto\">\n      <li class=\"nav-item\">\n        <a href=\"#\" class=\"nav-link active\" aria-current=\"page\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#home\"/></svg>\n          Home\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link link-dark\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#speedometer2\"/></svg>\n          Dashboard\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link link-dark\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#table\"/></svg>\n          Orders\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link link-dark\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#grid\"/></svg>\n          Products\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link link-dark\">\n          <svg class=\"bi pe-none me-2\" width=\"16\" height=\"16\"><use xlink:href=\"#people-circle\"/></svg>\n          Customers\n        </a>\n      </li>\n    </ul>\n    <hr>\n    <div class=\"dropdown\">\n      <a href=\"#\" class=\"d-flex align-items-center link-dark text-decoration-none dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n        <img src=\"https://github.com/mdo.png\" alt=\"\" width=\"32\" height=\"32\" class=\"rounded-circle me-2\">\n        <strong>mdo</strong>\n      </a>\n      <ul class=\"dropdown-menu text-small shadow\">\n        <li><a class=\"dropdown-item\" href=\"#\">New project...</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Settings</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Profile</a></li>\n        <li><hr class=\"dropdown-divider\"></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Sign out</a></li>\n      </ul>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider b-example-vr\"></div>\n\n  <div class=\"d-flex flex-column flex-shrink-0 bg-light\" style=\"width: 4.5rem;\">\n    <a href=\"/\" class=\"d-block p-3 link-dark text-decoration-none\" title=\"Icon-only\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\">\n      <svg class=\"bi pe-none\" width=\"40\" height=\"32\"><use xlink:href=\"#bootstrap\"/></svg>\n      <span class=\"visually-hidden\">Icon-only</span>\n    </a>\n    <ul class=\"nav nav-pills nav-flush flex-column mb-auto text-center\">\n      <li class=\"nav-item\">\n        <a href=\"#\" class=\"nav-link active py-3 border-bottom rounded-0\" aria-current=\"page\" title=\"Home\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\">\n          <svg class=\"bi pe-none\" width=\"24\" height=\"24\" role=\"img\" aria-label=\"Home\"><use xlink:href=\"#home\"/></svg>\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link py-3 border-bottom rounded-0\" title=\"Dashboard\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\">\n          <svg class=\"bi pe-none\" width=\"24\" height=\"24\" role=\"img\" aria-label=\"Dashboard\"><use xlink:href=\"#speedometer2\"/></svg>\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link py-3 border-bottom rounded-0\" title=\"Orders\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\">\n          <svg class=\"bi pe-none\" width=\"24\" height=\"24\" role=\"img\" aria-label=\"Orders\"><use xlink:href=\"#table\"/></svg>\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link py-3 border-bottom rounded-0\" title=\"Products\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\">\n          <svg class=\"bi pe-none\" width=\"24\" height=\"24\" role=\"img\" aria-label=\"Products\"><use xlink:href=\"#grid\"/></svg>\n        </a>\n      </li>\n      <li>\n        <a href=\"#\" class=\"nav-link py-3 border-bottom rounded-0\" title=\"Customers\" data-bs-toggle=\"tooltip\" data-bs-placement=\"right\">\n          <svg class=\"bi pe-none\" width=\"24\" height=\"24\" role=\"img\" aria-label=\"Customers\"><use xlink:href=\"#people-circle\"/></svg>\n        </a>\n      </li>\n    </ul>\n    <div class=\"dropdown border-top\">\n      <a href=\"#\" class=\"d-flex align-items-center justify-content-center p-3 link-dark text-decoration-none dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n        <img src=\"https://github.com/mdo.png\" alt=\"mdo\" width=\"24\" height=\"24\" class=\"rounded-circle\">\n      </a>\n      <ul class=\"dropdown-menu text-small shadow\">\n        <li><a class=\"dropdown-item\" href=\"#\">New project...</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Settings</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Profile</a></li>\n        <li><hr class=\"dropdown-divider\"></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Sign out</a></li>\n      </ul>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider b-example-vr\"></div>\n\n  <div class=\"flex-shrink-0 p-3 bg-white\" style=\"width: 280px;\">\n    <a href=\"/\" class=\"d-flex align-items-center pb-3 mb-3 link-dark text-decoration-none border-bottom\">\n      <svg class=\"bi pe-none me-2\" width=\"30\" height=\"24\"><use xlink:href=\"#bootstrap\"/></svg>\n      <span class=\"fs-5 fw-semibold\">Collapsible</span>\n    </a>\n    <ul class=\"list-unstyled ps-0\">\n      <li class=\"mb-1\">\n        <button class=\"btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#home-collapse\" aria-expanded=\"true\">\n          Home\n        </button>\n        <div class=\"collapse show\" id=\"home-collapse\">\n          <ul class=\"btn-toggle-nav list-unstyled fw-normal pb-1 small\">\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Overview</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Updates</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Reports</a></li>\n          </ul>\n        </div>\n      </li>\n      <li class=\"mb-1\">\n        <button class=\"btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#dashboard-collapse\" aria-expanded=\"false\">\n          Dashboard\n        </button>\n        <div class=\"collapse\" id=\"dashboard-collapse\">\n          <ul class=\"btn-toggle-nav list-unstyled fw-normal pb-1 small\">\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Overview</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Weekly</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Monthly</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Annually</a></li>\n          </ul>\n        </div>\n      </li>\n      <li class=\"mb-1\">\n        <button class=\"btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#orders-collapse\" aria-expanded=\"false\">\n          Orders\n        </button>\n        <div class=\"collapse\" id=\"orders-collapse\">\n          <ul class=\"btn-toggle-nav list-unstyled fw-normal pb-1 small\">\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">New</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Processed</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Shipped</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Returned</a></li>\n          </ul>\n        </div>\n      </li>\n      <li class=\"border-top my-3\"></li>\n      <li class=\"mb-1\">\n        <button class=\"btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed\" data-bs-toggle=\"collapse\" data-bs-target=\"#account-collapse\" aria-expanded=\"false\">\n          Account\n        </button>\n        <div class=\"collapse\" id=\"account-collapse\">\n          <ul class=\"btn-toggle-nav list-unstyled fw-normal pb-1 small\">\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">New...</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Profile</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Settings</a></li>\n            <li><a href=\"#\" class=\"link-dark d-inline-flex text-decoration-none rounded\">Sign out</a></li>\n          </ul>\n        </div>\n      </li>\n    </ul>\n  </div>\n\n  <div class=\"b-example-divider b-example-vr\"></div>\n\n  <div class=\"d-flex flex-column align-items-stretch flex-shrink-0 bg-white\" style=\"width: 380px;\">\n    <a href=\"/\" class=\"d-flex align-items-center flex-shrink-0 p-3 link-dark text-decoration-none border-bottom\">\n      <svg class=\"bi pe-none me-2\" width=\"30\" height=\"24\"><use xlink:href=\"#bootstrap\"/></svg>\n      <span class=\"fs-5 fw-semibold\">List group</span>\n    </a>\n    <div class=\"list-group list-group-flush border-bottom scrollarea\">\n      <a href=\"#\" class=\"list-group-item list-group-item-action active py-3 lh-sm\" aria-current=\"true\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small>Wed</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Tues</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Mon</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\" aria-current=\"true\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Wed</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Tues</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Mon</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\" aria-current=\"true\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Wed</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Tues</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Mon</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\" aria-current=\"true\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Wed</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Tues</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n      <a href=\"#\" class=\"list-group-item list-group-item-action py-3 lh-sm\">\n        <div class=\"d-flex w-100 align-items-center justify-content-between\">\n          <strong class=\"mb-1\">List group item heading</strong>\n          <small class=\"text-muted\">Mon</small>\n        </div>\n        <div class=\"col-10 mb-1 small\">Some placeholder content in a paragraph below the heading and date.</div>\n      </a>\n    </div>\n  </div>\n\n  <div class=\"b-example-divider b-example-vr\"></div>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sidebars/sidebars.css",
    "content": "body {\n  min-height: 100vh;\n  min-height: -webkit-fill-available;\n}\n\nhtml {\n  height: -webkit-fill-available;\n}\n\nmain {\n  height: 100vh;\n  height: -webkit-fill-available;\n  max-height: 100vh;\n  overflow-x: auto;\n  overflow-y: hidden;\n}\n\n.dropdown-toggle { outline: 0; }\n\n.btn-toggle {\n  padding: .25rem .5rem;\n  font-weight: 600;\n  color: rgba(0, 0, 0, .65);\n  background-color: transparent;\n}\n.btn-toggle:hover,\n.btn-toggle:focus {\n  color: rgba(0, 0, 0, .85);\n  background-color: #d2f4ea;\n}\n\n.btn-toggle::before {\n  width: 1.25em;\n  line-height: 0;\n  content: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e\");\n  transition: transform .35s ease;\n  transform-origin: .5em 50%;\n}\n\n.btn-toggle[aria-expanded=\"true\"] {\n  color: rgba(0, 0, 0, .85);\n}\n.btn-toggle[aria-expanded=\"true\"]::before {\n  transform: rotate(90deg);\n}\n\n.btn-toggle-nav a {\n  padding: .1875rem .5rem;\n  margin-top: .125rem;\n  margin-left: 1.25rem;\n}\n.btn-toggle-nav a:hover,\n.btn-toggle-nav a:focus {\n  background-color: #d2f4ea;\n}\n\n.scrollarea {\n  overflow-y: auto;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sidebars/sidebars.js",
    "content": "/* global bootstrap: false */\n(() => {\n  'use strict'\n  const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle=\"tooltip\"]'))\n  tooltipTriggerList.forEach(tooltipTriggerEl => {\n    new bootstrap.Tooltip(tooltipTriggerEl)\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sign-in/index.html",
    "content": "---\nlayout: examples\ntitle: Signin Template\nextra_css:\n  - \"signin.css\"\nbody_class: \"text-center\"\ninclude_js: false\n---\n\n<main class=\"form-signin w-100 m-auto\">\n  <form>\n    <img class=\"mb-4\" src=\"/docs/{{< param docs_version >}}/assets/brand/bootstrap-logo.svg\" alt=\"\" width=\"72\" height=\"57\">\n    <h1 class=\"h3 mb-3 fw-normal\">Please sign in</h1>\n\n    <div class=\"form-floating\">\n      <input type=\"email\" class=\"form-control\" id=\"floatingInput\" placeholder=\"name@example.com\">\n      <label for=\"floatingInput\">Email address</label>\n    </div>\n    <div class=\"form-floating\">\n      <input type=\"password\" class=\"form-control\" id=\"floatingPassword\" placeholder=\"Password\">\n      <label for=\"floatingPassword\">Password</label>\n    </div>\n\n    <div class=\"checkbox mb-3\">\n      <label>\n        <input type=\"checkbox\" value=\"remember-me\"> Remember me\n      </label>\n    </div>\n    <button class=\"w-100 btn btn-lg btn-primary\" type=\"submit\">Sign in</button>\n    <p class=\"mt-5 mb-3 text-muted\">&copy; 2017–{{< year >}}</p>\n  </form>\n</main>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sign-in/signin.css",
    "content": "html,\nbody {\n  height: 100%;\n}\n\nbody {\n  display: flex;\n  align-items: center;\n  padding-top: 40px;\n  padding-bottom: 40px;\n  background-color: #f5f5f5;\n}\n\n.form-signin {\n  max-width: 330px;\n  padding: 15px;\n}\n\n.form-signin .form-floating:focus-within {\n  z-index: 2;\n}\n\n.form-signin input[type=\"email\"] {\n  margin-bottom: -1px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n\n.form-signin input[type=\"password\"] {\n  margin-bottom: 10px;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/starter-template/index.html",
    "content": "---\nlayout: examples\ntitle: Starter Template\nextra_css:\n  - \"starter-template.css\"\n---\n\n<div class=\"col-lg-8 mx-auto p-4 py-md-5\">\n  <header class=\"d-flex align-items-center pb-3 mb-5 border-bottom\">\n    <a href=\"/\" class=\"d-flex align-items-center text-dark text-decoration-none\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"32\" class=\"me-2\" viewBox=\"0 0 118 94\" role=\"img\"><title>Bootstrap</title><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z\" fill=\"currentColor\"></path></svg>\n      <span class=\"fs-4\">Starter template</span>\n    </a>\n  </header>\n\n  <main>\n    <h1>Get started with Bootstrap</h1>\n    <p class=\"fs-5 col-md-8\">Quickly and easily get started with Bootstrap's compiled, production-ready files with this barebones example featuring some basic HTML and helpful links. Download all our examples to get started.</p>\n\n    <div class=\"mb-5\">\n      <a href=\"{{< docsref \"/examples\" >}}\" class=\"btn btn-primary btn-lg px-4\">Download examples</a>\n    </div>\n\n    <hr class=\"col-3 col-md-2 mb-5\">\n\n    <div class=\"row g-5\">\n      <div class=\"col-md-6\">\n        <h2>Starter projects</h2>\n        <p>Ready to beyond the starter template? Check out these open source projects that you can quickly duplicate to a new GitHub repository.</p>\n        <ul class=\"icon-list ps-0\">\n          <li class=\"d-flex align-items-start mb-1\"><a href=\"https://github.com/twbs/bootstrap-npm-starter\" rel=\"noopener\" target=\"_blank\">Bootstrap npm starter</a></li>\n          <li class=\"text-muted d-flex align-items-start mb-1\">Bootstrap Parcel starter (coming soon!)</li>\n        </ul>\n      </div>\n\n      <div class=\"col-md-6\">\n        <h2>Guides</h2>\n        <p>Read more detailed instructions and documentation on using or contributing to Bootstrap.</p>\n        <ul class=\"icon-list ps-0\">\n          <li class=\"d-flex align-items-start mb-1\"><a href=\"{{< docsref \"/getting-started/introduction\" >}}\">Bootstrap quick start guide</a></li>\n          <li class=\"d-flex align-items-start mb-1\"><a href=\"{{< docsref \"/getting-started/webpack\" >}}\">Bootstrap Webpack guide</a></li>\n          <li class=\"d-flex align-items-start mb-1\"><a href=\"{{< docsref \"/getting-started/parcel\" >}}\">Bootstrap Parcel guide</a></li>\n          <li class=\"d-flex align-items-start mb-1\"><a href=\"{{< docsref \"/getting-started/vite\" >}}\">Bootstrap Vite guide</a></li>\n          <li class=\"d-flex align-items-start mb-1\"><a href=\"{{< docsref \"/getting-started/contribute\" >}}\">Contributing to Bootstrap</a></li>\n        </ul>\n      </div>\n    </div>\n  </main>\n  <footer class=\"pt-5 my-5 text-muted border-top\">\n    Created by the Bootstrap team &middot; &copy; {{< year >}}\n  </footer>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/starter-template/starter-template.css",
    "content": ".icon-list li::before {\n  display: block;\n  flex-shrink: 0;\n  width: 1.5em;\n  height: 1.5em;\n  margin-right: .5rem;\n  content: \"\";\n  background: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23212529' viewBox='0 0 16 16'%3E%3Cpath d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z'/%3E%3C/svg%3E\") no-repeat center center / 100% auto;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sticky-footer/index.html",
    "content": "---\nlayout: examples\ntitle: Sticky Footer Template\nextra_css:\n  - \"sticky-footer.css\"\nhtml_class: \"h-100\"\nbody_class: \"d-flex flex-column h-100\"\ninclude_js: false\n---\n\n<!-- Begin page content -->\n<main class=\"flex-shrink-0\">\n  <div class=\"container\">\n    <h1 class=\"mt-5\">Sticky footer</h1>\n    <p class=\"lead\">Pin a footer to the bottom of the viewport in desktop browsers with this custom HTML and CSS.</p>\n    <p>Use <a href=\"{{< docsref \"/examples/sticky-footer-navbar\" >}}\">the sticky footer with a fixed navbar</a> if need be, too.</p>\n  </div>\n</main>\n\n<footer class=\"footer mt-auto py-3 bg-light\">\n  <div class=\"container\">\n    <span class=\"text-muted\">Place sticky footer content here.</span>\n  </div>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sticky-footer/sticky-footer.css",
    "content": "/* Custom page CSS\n-------------------------------------------------- */\n/* Not required for template or sticky footer method. */\n\n.container {\n  width: auto;\n  max-width: 680px;\n  padding: 0 15px;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sticky-footer-navbar/index.html",
    "content": "---\nlayout: examples\ntitle: Sticky Footer Navbar Template\nextra_css:\n  - \"sticky-footer-navbar.css\"\nhtml_class: \"h-100\"\nbody_class: \"d-flex flex-column h-100\"\n---\n\n<header>\n  <!-- Fixed navbar -->\n  <nav class=\"navbar navbar-expand-md navbar-dark fixed-top bg-dark\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#\">Fixed navbar</a>\n      <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n        <ul class=\"navbar-nav me-auto mb-2 mb-md-0\">\n          <li class=\"nav-item\">\n            <a class=\"nav-link active\" aria-current=\"page\" href=\"#\">Home</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link\" href=\"#\">Link</a>\n          </li>\n          <li class=\"nav-item\">\n            <a class=\"nav-link disabled\">Disabled</a>\n          </li>\n        </ul>\n        <form class=\"d-flex\" role=\"search\">\n          <input class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\">\n          <button class=\"btn btn-outline-success\" type=\"submit\">Search</button>\n        </form>\n      </div>\n    </div>\n  </nav>\n</header>\n\n<!-- Begin page content -->\n<main class=\"flex-shrink-0\">\n  <div class=\"container\">\n    <h1 class=\"mt-5\">Sticky footer with fixed navbar</h1>\n    <p class=\"lead\">Pin a footer to the bottom of the viewport in desktop browsers with this custom HTML and CSS. A fixed navbar has been added with <code class=\"small\">padding-top: 60px;</code> on the <code class=\"small\">main &gt; .container</code>.</p>\n    <p>Back to <a href=\"{{< docsref \"/examples/sticky-footer\" >}}\">the default sticky footer</a> minus the navbar.</p>\n  </div>\n</main>\n\n<footer class=\"footer mt-auto py-3 bg-light\">\n  <div class=\"container\">\n    <span class=\"text-muted\">Place sticky footer content here.</span>\n  </div>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/examples/sticky-footer-navbar/sticky-footer-navbar.css",
    "content": "/* Custom page CSS\n-------------------------------------------------- */\n/* Not required for template or sticky footer method. */\n\nmain > .container {\n  padding: 60px 15px 0;\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/extend/approach.md",
    "content": "---\nlayout: docs\ntitle: Approach\ndescription: Learn about the guiding principles, strategies, and techniques used to build and maintain Bootstrap so you can more easily customize and extend it yourself.\ngroup: extend\naliases:\n  - \"/docs/5.2/extend/\"\n---\n\nWhile the getting started pages provide an introductory tour of the project and what it offers, this document focuses on _why_ we do the things we do in Bootstrap. It explains our philosophy to building on the web so that others can learn from us, contribute with us, and help us improve.\n\nSee something that doesn't sound right, or perhaps could be done better? [Open an issue]({{< param repo >}}/issues/new/choose)—we'd love to discuss it with you.\n\n## Summary\n\nWe'll dive into each of these more throughout, but at a high level, here's what guides our approach.\n\n- Components should be responsive and mobile-first\n- Components should be built with a base class and extended via modifier classes\n- Component states should obey a common z-index scale\n- Whenever possible, prefer a HTML and CSS implementation over JavaScript\n- Whenever possible, use utilities over custom styles\n- Whenever possible, avoid enforcing strict HTML requirements (children selectors)\n\n## Responsive\n\nBootstrap's responsive styles are built to be responsive, an approach that's often referred to as _mobile-first_. We use this term in our docs and largely agree with it, but at times it can be too broad. While not every component _must_ be entirely responsive in Bootstrap, this responsive approach is about reducing CSS overrides by pushing you to add styles as the viewport becomes larger.\n\nAcross Bootstrap, you'll see this most clearly in our media queries. In most cases, we use `min-width` queries that begin to apply at a specific breakpoint and carry up through the higher breakpoints. For example, a `.d-none` applies from `min-width: 0` to infinity. On the other hand, a `.d-md-none` applies from the medium breakpoint and up.\n\nAt times we'll use `max-width` when a component's inherent complexity requires it. At times, these overrides are functionally and mentally clearer to implement and support than rewriting core functionality from our components. We strive to limit this approach, but will use it from time to time.\n\n## Classes\n\nAside from our Reboot, a cross-browser normalization stylesheet, all our styles aim to use classes as selectors. This means steering clear of type selectors (e.g., `input[type=\"text\"]`) and extraneous parent classes (e.g., `.parent .child`) that make styles too specific to easily override.\n\nAs such, components should be built with a base class that houses common, not-to-be overridden property-value pairs. For example, `.btn` and `.btn-primary`. We use `.btn` for all the common styles like `display`, `padding`, and `border-width`. We then use modifiers like `.btn-primary` to add the color, background-color, border-color, etc.\n\nModifier classes should only be used when there are multiple properties or values to be changed across multiple variants. Modifiers are not always necessary, so be sure you're actually saving lines of code and preventing unnecessary overrides when creating them. Good examples of modifiers are our theme color classes and size variants.\n\n## z-index scales\n\nThere are two `z-index` scales in Bootstrap—elements within a component and overlay components.\n\n### Component elements\n\n- Some components in Bootstrap are built with overlapping elements to prevent double borders without modifying the `border` property. For example, button groups, input groups, and pagination.\n- These components share a standard `z-index` scale of `0` through `3`.\n- `0` is default (initial), `1` is `:hover`, `2` is `:active`/`.active`, and `3` is `:focus`.\n- This approach matches our expectations of highest user priority. If an element is focused, it's in view and at the user's attention. Active elements are second highest because they indicate state. Hover is third highest because it indicates user intent, but nearly _anything_ can be hovered.\n\n### Overlay components\n\nBootstrap includes several components that function as an overlay of some kind. This includes, in order of highest `z-index`, dropdowns, fixed and sticky navbars, modals, tooltips, and popovers. These components have their own `z-index` scale that begins at `1000`. This starting number was chosen arbitrarily and serves as a small buffer between our styles and your project's custom styles.\n\nEach overlay component increases its `z-index` value slightly in such a way that common UI principles allow user focused or hovered elements to remain in view at all times. For example, a modal is document blocking (e.g., you cannot take any other action save for the modal's action), so we put that above our navbars.\n\nLearn more about this in our [`z-index` layout page]({{< docsref \"/layout/z-index\" >}}).\n\n## HTML and CSS over JS\n\nWhenever possible, we prefer to write HTML and CSS over JavaScript. In general, HTML and CSS are more prolific and accessible to more people of all different experience levels. HTML and CSS are also faster in your browser than JavaScript, and your browser generally provides a great deal of functionality for you.\n\nThis principle is our first-class JavaScript API using `data` attributes. You don't need to write nearly any JavaScript to use our JavaScript plugins; instead, write HTML. Read more about this in [our JavaScript overview page]({{< docsref \"/getting-started/javascript#data-attributes\" >}}).\n\nLastly, our styles build on the fundamental behaviors of common web elements. Whenever possible, we prefer to use what the browser provides. For example, you can put a `.btn` class on nearly any element, but most elements don't provide any semantic value or browser functionality. So instead, we use `<button>`s and `<a>`s.\n\nThe same goes for more complex components. While we *could* write our own form validation plugin to add classes to a parent element based on an input's state, thereby allowing us to style the text say red, we prefer using the `:valid`/`:invalid` pseudo-elements every browser provides us.\n\n## Utilities\n\nUtility classes—formerly helpers in Bootstrap 3—are a powerful ally in combating CSS bloat and poor page performance. A utility class is typically a single, immutable property-value pairing expressed as a class (e.g., `.d-block` represents `display: block;`). Their primary appeal is speed of use while writing HTML and limiting the amount of custom CSS you have to write.\n\nSpecifically regarding custom CSS, utilities can help combat increasing file size by reducing your most commonly repeated property-value pairs into single classes. This can have a dramatic effect at scale in your projects.\n\n## Flexible HTML\n\nWhile not always possible, we strive to avoid being overly dogmatic in our HTML requirements for components. Thus, we focus on single classes in our CSS selectors and try to avoid immediate children selectors (`>`). This gives you more flexibility in your implementation and helps keep our CSS simpler and less specific.\n\n## Code conventions\n\n[Code Guide](https://codeguide.co/) (from Bootstrap co-creator, @mdo) documents how we write our HTML and CSS across Bootstrap. It specifies guidelines for general formatting, common sense defaults, property and attribute orders, and more.\n\nWe use [Stylelint](https://stylelint.io/) to enforce these standards and more in our Sass/CSS. [Our custom Stylelint config](https://github.com/twbs/stylelint-config-twbs-bootstrap) is open source and available for others to use and extend.\n\nWe use [vnu-jar](https://www.npmjs.com/package/vnu-jar) to enforce standard and semantic HTML, as well as detecting common errors.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/extend/icons.md",
    "content": "---\nlayout: docs\ntitle: Icons\ndescription: Guidance and suggestions for using external icon libraries with Bootstrap.\ngroup: extend\n---\n\nWhile Bootstrap doesn't include an icon set by default, we do have our own comprehensive icon library called Bootstrap Icons. Feel free to use them or any other icon set in your project. We've included details for Bootstrap Icons and other preferred icon sets below.\n\nWhile most icon sets include multiple file formats, we prefer SVG implementations for their improved accessibility and vector support.\n\n## Bootstrap Icons\n\nBootstrap Icons is a growing library of SVG icons that are designed by [@mdo](https://github.com/mdo) and maintained by [the Bootstrap Team](https://github.com/orgs/twbs/people). The beginnings of this icon set come from Bootstrap's very own components—our forms, carousels, and more. Bootstrap has very few icon needs out of the box, so we didn't need much. However, once we got going, we couldn't stop making more.\n\nOh, and did we mention they're completely open source? Licensed under MIT, just like Bootstrap, our icon set is available to everyone.\n\n[Learn more about Bootstrap Icons]({{< param icons >}}), including how to install them and recommended usage.\n\n## Alternatives\n\nWe've tested and used these icon sets ourselves as preferred alternatives to Bootstrap Icons.\n\n{{< markdown >}}\n{{< icons.inline >}}\n{{- $type := .Get \"type\" | default \"preferred\" -}}\n\n{{- range (index .Site.Data.icons $type) }}\n- [{{ .name }}]({{ .website }})\n{{- end }}\n{{< /icons.inline >}}\n{{< /markdown >}}\n\n## More options\n\nWhile we haven't tried these out ourselves, they do look promising and provide multiple formats, including SVG.\n\n{{< markdown >}}\n{{< icons.inline type=\"more\" />}}\n{{< /markdown >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/checks-radios.md",
    "content": "---\nlayout: docs\ntitle: Checks and radios\ndescription: Create consistent cross-browser and cross-device checkboxes and radios with our completely rewritten checks component.\ngroup: forms\naliases: \"/docs/5.2/forms/checks/\"\ntoc: true\n---\n\n## Approach\n\nBrowser default checkboxes and radios are replaced with the help of `.form-check`, a series of classes for both input types that improves the layout and behavior of their HTML elements, that provide greater customization and cross browser consistency. Checkboxes are for selecting one or several options in a list, while radios are for selecting one option from many.\n\nStructurally, our `<input>`s and `<label>`s are sibling elements as opposed to an `<input>` within a `<label>`. This is slightly more verbose as you must specify `id` and `for` attributes to relate the `<input>` and `<label>`. We use the sibling selector (`~`) for all our `<input>` states, like `:checked` or `:disabled`. When combined with the `.form-check-label` class, we can easily style the text for each item based on the `<input>`'s state.\n\nOur checks use custom Bootstrap icons to indicate checked or indeterminate states.\n\n## Checks\n\n{{< example >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"flexCheckDefault\">\n  <label class=\"form-check-label\" for=\"flexCheckDefault\">\n    Default checkbox\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"flexCheckChecked\" checked>\n  <label class=\"form-check-label\" for=\"flexCheckChecked\">\n    Checked checkbox\n  </label>\n</div>\n{{< /example >}}\n\n### Indeterminate\n\nCheckboxes can utilize the `:indeterminate` pseudo class when manually set via JavaScript (there is no available HTML attribute for specifying it).\n\n{{< example class=\"bd-example-indeterminate\" stackblitz_add_js=\"true\" >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"flexCheckIndeterminate\">\n  <label class=\"form-check-label\" for=\"flexCheckIndeterminate\">\n    Indeterminate checkbox\n  </label>\n</div>\n{{< /example >}}\n\n### Disabled\n\nAdd the `disabled` attribute and the associated `<label>`s are automatically styled to match with a lighter color to help indicate the input's state.\n\n{{< example class=\"bd-example-indeterminate\" stackblitz_add_js=\"true\" >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"flexCheckIndeterminateDisabled\" disabled>\n  <label class=\"form-check-label\" for=\"flexCheckIndeterminateDisabled\">\n    Disabled indeterminate checkbox\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"flexCheckDisabled\" disabled>\n  <label class=\"form-check-label\" for=\"flexCheckDisabled\">\n    Disabled checkbox\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"flexCheckCheckedDisabled\" checked disabled>\n  <label class=\"form-check-label\" for=\"flexCheckCheckedDisabled\">\n    Disabled checked checkbox\n  </label>\n</div>\n{{< /example >}}\n\n## Radios\n\n{{< example >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"flexRadioDefault\" id=\"flexRadioDefault1\">\n  <label class=\"form-check-label\" for=\"flexRadioDefault1\">\n    Default radio\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"flexRadioDefault\" id=\"flexRadioDefault2\" checked>\n  <label class=\"form-check-label\" for=\"flexRadioDefault2\">\n    Default checked radio\n  </label>\n</div>\n{{< /example >}}\n\n### Disabled\n\nAdd the `disabled` attribute and the associated `<label>`s are automatically styled to match with a lighter color to help indicate the input's state.\n\n{{< example >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"flexRadioDisabled\" id=\"flexRadioDisabled\" disabled>\n  <label class=\"form-check-label\" for=\"flexRadioDisabled\">\n    Disabled radio\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"flexRadioDisabled\" id=\"flexRadioCheckedDisabled\" checked disabled>\n  <label class=\"form-check-label\" for=\"flexRadioCheckedDisabled\">\n    Disabled checked radio\n  </label>\n</div>\n{{< /example >}}\n\n## Switches\n\nA switch has the markup of a custom checkbox but uses the `.form-switch` class to render a toggle switch. Consider using `role=\"switch\"` to more accurately convey the nature of the control to assistive technologies that support this role. In older assistive technologies, it will simply be announced as a regular checkbox as a fallback. Switches also support the `disabled` attribute.\n\n{{< example >}}\n<div class=\"form-check form-switch\">\n  <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"flexSwitchCheckDefault\">\n  <label class=\"form-check-label\" for=\"flexSwitchCheckDefault\">Default switch checkbox input</label>\n</div>\n<div class=\"form-check form-switch\">\n  <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"flexSwitchCheckChecked\" checked>\n  <label class=\"form-check-label\" for=\"flexSwitchCheckChecked\">Checked switch checkbox input</label>\n</div>\n<div class=\"form-check form-switch\">\n  <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"flexSwitchCheckDisabled\" disabled>\n  <label class=\"form-check-label\" for=\"flexSwitchCheckDisabled\">Disabled switch checkbox input</label>\n</div>\n<div class=\"form-check form-switch\">\n  <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"flexSwitchCheckCheckedDisabled\" checked disabled>\n  <label class=\"form-check-label\" for=\"flexSwitchCheckCheckedDisabled\">Disabled checked switch checkbox input</label>\n</div>\n{{< /example >}}\n\n## Default (stacked)\n\nBy default, any number of checkboxes and radios that are immediate sibling will be vertically stacked and appropriately spaced with `.form-check`.\n\n{{< example >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"defaultCheck1\">\n  <label class=\"form-check-label\" for=\"defaultCheck1\">\n    Default checkbox\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"defaultCheck2\" disabled>\n  <label class=\"form-check-label\" for=\"defaultCheck2\">\n    Disabled checkbox\n  </label>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"exampleRadios\" id=\"exampleRadios1\" value=\"option1\" checked>\n  <label class=\"form-check-label\" for=\"exampleRadios1\">\n    Default radio\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"exampleRadios\" id=\"exampleRadios2\" value=\"option2\">\n  <label class=\"form-check-label\" for=\"exampleRadios2\">\n    Second default radio\n  </label>\n</div>\n<div class=\"form-check\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"exampleRadios\" id=\"exampleRadios3\" value=\"option3\" disabled>\n  <label class=\"form-check-label\" for=\"exampleRadios3\">\n    Disabled radio\n  </label>\n</div>\n{{< /example >}}\n\n## Inline\n\nGroup checkboxes or radios on the same horizontal row by adding `.form-check-inline` to any `.form-check`.\n\n{{< example >}}\n<div class=\"form-check form-check-inline\">\n  <input class=\"form-check-input\" type=\"checkbox\" id=\"inlineCheckbox1\" value=\"option1\">\n  <label class=\"form-check-label\" for=\"inlineCheckbox1\">1</label>\n</div>\n<div class=\"form-check form-check-inline\">\n  <input class=\"form-check-input\" type=\"checkbox\" id=\"inlineCheckbox2\" value=\"option2\">\n  <label class=\"form-check-label\" for=\"inlineCheckbox2\">2</label>\n</div>\n<div class=\"form-check form-check-inline\">\n  <input class=\"form-check-input\" type=\"checkbox\" id=\"inlineCheckbox3\" value=\"option3\" disabled>\n  <label class=\"form-check-label\" for=\"inlineCheckbox3\">3 (disabled)</label>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"form-check form-check-inline\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"inlineRadioOptions\" id=\"inlineRadio1\" value=\"option1\">\n  <label class=\"form-check-label\" for=\"inlineRadio1\">1</label>\n</div>\n<div class=\"form-check form-check-inline\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"inlineRadioOptions\" id=\"inlineRadio2\" value=\"option2\">\n  <label class=\"form-check-label\" for=\"inlineRadio2\">2</label>\n</div>\n<div class=\"form-check form-check-inline\">\n  <input class=\"form-check-input\" type=\"radio\" name=\"inlineRadioOptions\" id=\"inlineRadio3\" value=\"option3\" disabled>\n  <label class=\"form-check-label\" for=\"inlineRadio3\">3 (disabled)</label>\n</div>\n{{< /example >}}\n\n## Reverse\n\nPut your checkboxes, radios, and switches on the opposite side with the `.form-check-reverse` modifier class.\n\n{{< example >}}\n<div class=\"form-check form-check-reverse\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"reverseCheck1\">\n  <label class=\"form-check-label\" for=\"reverseCheck1\">\n    Reverse checkbox\n  </label>\n</div>\n<div class=\"form-check form-check-reverse\">\n  <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"reverseCheck2\" disabled>\n  <label class=\"form-check-label\" for=\"reverseCheck2\">\n    Disabled reverse checkbox\n  </label>\n</div>\n\n<div class=\"form-check form-switch form-check-reverse\">\n  <input class=\"form-check-input\" type=\"checkbox\" id=\"flexSwitchCheckReverse\">\n  <label class=\"form-check-label\" for=\"flexSwitchCheckReverse\">Reverse switch checkbox input</label>\n</div>\n{{< /example >}}\n\n## Without labels\n\nOmit the wrapping `.form-check` for checkboxes and radios that have no label text. Remember to still provide some form of accessible name for assistive technologies (for instance, using `aria-label`). See the [forms overview accessibility]({{< docsref \"/forms/overview#accessibility\" >}}) section for details.\n\n{{< example >}}\n<div>\n  <input class=\"form-check-input\" type=\"checkbox\" id=\"checkboxNoLabel\" value=\"\" aria-label=\"...\">\n</div>\n\n<div>\n  <input class=\"form-check-input\" type=\"radio\" name=\"radioNoLabel\" id=\"radioNoLabel1\" value=\"\" aria-label=\"...\">\n</div>\n{{< /example >}}\n\n## Toggle buttons\n\nCreate button-like checkboxes and radio buttons by using `.btn` styles rather than `.form-check-label` on the `<label>` elements. These toggle buttons can further be grouped in a [button group]({{< docsref \"/components/button-group\" >}}) if needed.\n\n### Checkbox toggle buttons\n\n{{< example >}}\n<input type=\"checkbox\" class=\"btn-check\" id=\"btn-check\" autocomplete=\"off\">\n<label class=\"btn btn-primary\" for=\"btn-check\">Single toggle</label>\n{{< /example >}}\n\n{{< example >}}\n<input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-2\" checked autocomplete=\"off\">\n<label class=\"btn btn-primary\" for=\"btn-check-2\">Checked</label>\n{{< /example >}}\n\n{{< example >}}\n<input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-3\" autocomplete=\"off\" disabled>\n<label class=\"btn btn-primary\" for=\"btn-check-3\">Disabled</label>\n{{< /example >}}\n\n{{< callout info >}}\nVisually, these checkbox toggle buttons are identical to the [button plugin toggle buttons]({{< docsref \"/components/buttons#button-plugin\" >}}). However, they are conveyed differently by assistive technologies: the checkbox toggles will be announced by screen readers as \"checked\"/\"not checked\" (since, despite their appearance, they are fundamentally still checkboxes), whereas the button plugin toggle buttons will be announced as \"button\"/\"button pressed\". The choice between these two approaches will depend on the type of toggle you are creating, and whether or not the toggle will make sense to users when announced as a checkbox or as an actual button.\n{{< /callout >}}\n\n### Radio toggle buttons\n\n{{< example >}}\n<input type=\"radio\" class=\"btn-check\" name=\"options\" id=\"option1\" autocomplete=\"off\" checked>\n<label class=\"btn btn-secondary\" for=\"option1\">Checked</label>\n\n<input type=\"radio\" class=\"btn-check\" name=\"options\" id=\"option2\" autocomplete=\"off\">\n<label class=\"btn btn-secondary\" for=\"option2\">Radio</label>\n\n<input type=\"radio\" class=\"btn-check\" name=\"options\" id=\"option3\" autocomplete=\"off\" disabled>\n<label class=\"btn btn-secondary\" for=\"option3\">Disabled</label>\n\n<input type=\"radio\" class=\"btn-check\" name=\"options\" id=\"option4\" autocomplete=\"off\">\n<label class=\"btn btn-secondary\" for=\"option4\">Radio</label>\n{{< /example >}}\n\n### Outlined styles\n\nDifferent variants of `.btn`, such at the various outlined styles, are supported.\n\n{{< example >}}\n<input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-outlined\" autocomplete=\"off\">\n<label class=\"btn btn-outline-primary\" for=\"btn-check-outlined\">Single toggle</label><br>\n\n<input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-2-outlined\" checked autocomplete=\"off\">\n<label class=\"btn btn-outline-secondary\" for=\"btn-check-2-outlined\">Checked</label><br>\n\n<input type=\"radio\" class=\"btn-check\" name=\"options-outlined\" id=\"success-outlined\" autocomplete=\"off\" checked>\n<label class=\"btn btn-outline-success\" for=\"success-outlined\">Checked success radio</label>\n\n<input type=\"radio\" class=\"btn-check\" name=\"options-outlined\" id=\"danger-outlined\" autocomplete=\"off\">\n<label class=\"btn btn-outline-danger\" for=\"danger-outlined\">Danger radio</label>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"form-check-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/floating-labels.md",
    "content": "---\nlayout: docs\ntitle: Floating labels\ndescription: Create beautifully simple form labels that float over your input fields.\ngroup: forms\ntoc: true\n---\n\n## Example\n\nWrap a pair of `<input class=\"form-control\">` and `<label>` elements in `.form-floating` to enable floating labels with Bootstrap's textual form fields. A `placeholder` is required on each `<input>` as our method of CSS-only floating labels uses the `:placeholder-shown` pseudo-element. Also note that the `<input>` must come first so we can utilize a sibling selector (e.g., `~`).\n\n{{< example >}}\n<div class=\"form-floating mb-3\">\n  <input type=\"email\" class=\"form-control\" id=\"floatingInput\" placeholder=\"name@example.com\">\n  <label for=\"floatingInput\">Email address</label>\n</div>\n<div class=\"form-floating\">\n  <input type=\"password\" class=\"form-control\" id=\"floatingPassword\" placeholder=\"Password\">\n  <label for=\"floatingPassword\">Password</label>\n</div>\n{{< /example >}}\n\nWhen there's a `value` already defined, `<label>`s will automatically adjust to their floated position.\n\n{{< example >}}\n<form class=\"form-floating\">\n  <input type=\"email\" class=\"form-control\" id=\"floatingInputValue\" placeholder=\"name@example.com\" value=\"test@example.com\">\n  <label for=\"floatingInputValue\">Input with value</label>\n</form>\n{{< /example >}}\n\nForm validation styles also work as expected.\n\n{{< example >}}\n<form class=\"form-floating\">\n  <input type=\"email\" class=\"form-control is-invalid\" id=\"floatingInputInvalid\" placeholder=\"name@example.com\" value=\"test@example.com\">\n  <label for=\"floatingInputInvalid\">Invalid input</label>\n</form>\n{{< /example >}}\n\n## Textareas\n\nBy default, `<textarea>`s with `.form-control` will be the same height as `<input>`s.\n\n{{< example >}}\n<div class=\"form-floating\">\n  <textarea class=\"form-control\" placeholder=\"Leave a comment here\" id=\"floatingTextarea\"></textarea>\n  <label for=\"floatingTextarea\">Comments</label>\n</div>\n{{< /example >}}\n\nTo set a custom height on your `<textarea>`, do not use the `rows` attribute. Instead, set an explicit `height` (either inline or via custom CSS).\n\n{{< example >}}\n<div class=\"form-floating\">\n  <textarea class=\"form-control\" placeholder=\"Leave a comment here\" id=\"floatingTextarea2\" style=\"height: 100px\"></textarea>\n  <label for=\"floatingTextarea2\">Comments</label>\n</div>\n{{< /example >}}\n\n## Selects\n\nOther than `.form-control`, floating labels are only available on `.form-select`s. They work in the same way, but unlike `<input>`s, they'll always show the `<label>` in its floated state. **Selects with `size` and `multiple` are not supported.**\n\n{{< example >}}\n<div class=\"form-floating\">\n  <select class=\"form-select\" id=\"floatingSelect\" aria-label=\"Floating label select example\">\n    <option selected>Open this select menu</option>\n    <option value=\"1\">One</option>\n    <option value=\"2\">Two</option>\n    <option value=\"3\">Three</option>\n  </select>\n  <label for=\"floatingSelect\">Works with selects</label>\n</div>\n{{< /example >}}\n\n## Readonly plaintext\n\nFloating labels also support `.form-control-plaintext`, which can be helpful for toggling from an editable `<input>` to a plaintext value without affecting the page layout.\n\n{{< example >}}\n<div class=\"form-floating mb-3\">\n  <input type=\"email\" readonly class=\"form-control-plaintext\" id=\"floatingEmptyPlaintextInput\" placeholder=\"name@example.com\">\n  <label for=\"floatingEmptyPlaintextInput\">Empty input</label>\n</div>\n<div class=\"form-floating mb-3\">\n  <input type=\"email\" readonly class=\"form-control-plaintext\" id=\"floatingPlaintextInput\" placeholder=\"name@example.com\" value=\"name@example.com\">\n  <label for=\"floatingPlaintextInput\">Input with value</label>\n</div>\n{{< /example >}}\n\n## Input groups\n\nFloating labels also support `.input-group`.\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <span class=\"input-group-text\">@</span>\n  <div class=\"form-floating\">\n    <input type=\"text\" class=\"form-control\" id=\"floatingInputGroup1\" placeholder=\"Username\">\n    <label for=\"floatingInputGroup1\">Username</label>\n  </div>\n</div>\n{{< /example >}}\n\nWhen using `.input-group` and `.form-floating` along with form validation, the `-feedback` should be placed outside of the `.form-floating`, but inside of the `.input-group`. This means that the feedback will need to be shown using javascript.\n\n{{< example >}}\n<div class=\"input-group has-validation\">\n  <span class=\"input-group-text\">@</span>\n  <div class=\"form-floating is-invalid\">\n    <input type=\"text\" class=\"form-control is-invalid\" id=\"floatingInputGroup2\" placeholder=\"Username\" required>\n    <label for=\"floatingInputGroup2\">Username</label>\n  </div>\n  <div class=\"invalid-feedback\">\n    Please choose a username.\n  </div>\n</div>\n{{< /example >}}\n\n## Layout\n\nWhen working with the Bootstrap grid system, be sure to place form elements within column classes.\n\n{{< example >}}\n<div class=\"row g-2\">\n  <div class=\"col-md\">\n    <div class=\"form-floating\">\n      <input type=\"email\" class=\"form-control\" id=\"floatingInputGrid\" placeholder=\"name@example.com\" value=\"mdo@example.com\">\n      <label for=\"floatingInputGrid\">Email address</label>\n    </div>\n  </div>\n  <div class=\"col-md\">\n    <div class=\"form-floating\">\n      <select class=\"form-select\" id=\"floatingSelectGrid\">\n        <option selected>Open this select menu</option>\n        <option value=\"1\">One</option>\n        <option value=\"2\">Two</option>\n        <option value=\"3\">Three</option>\n      </select>\n      <label for=\"floatingSelectGrid\">Works with selects</label>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"form-floating-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/form-control.md",
    "content": "---\nlayout: docs\ntitle: Form controls\ndescription: Give textual form controls like `<input>`s and `<textarea>`s an upgrade with custom styles, sizing, focus states, and more.\ngroup: forms\ntoc: true\n---\n\n## Example\n\n{{< example >}}\n<div class=\"mb-3\">\n  <label for=\"exampleFormControlInput1\" class=\"form-label\">Email address</label>\n  <input type=\"email\" class=\"form-control\" id=\"exampleFormControlInput1\" placeholder=\"name@example.com\">\n</div>\n<div class=\"mb-3\">\n  <label for=\"exampleFormControlTextarea1\" class=\"form-label\">Example textarea</label>\n  <textarea class=\"form-control\" id=\"exampleFormControlTextarea1\" rows=\"3\"></textarea>\n</div>\n{{< /example >}}\n\n## Sizing\n\nSet heights using classes like `.form-control-lg` and `.form-control-sm`.\n\n{{< example >}}\n<input class=\"form-control form-control-lg\" type=\"text\" placeholder=\".form-control-lg\" aria-label=\".form-control-lg example\">\n<input class=\"form-control\" type=\"text\" placeholder=\"Default input\" aria-label=\"default input example\">\n<input class=\"form-control form-control-sm\" type=\"text\" placeholder=\".form-control-sm\" aria-label=\".form-control-sm example\">\n{{< /example >}}\n\n## Disabled\n\nAdd the `disabled` boolean attribute on an input to give it a grayed out appearance, remove pointer events, and prevent focusing.\n\n{{< example >}}\n<input class=\"form-control\" type=\"text\" placeholder=\"Disabled input\" aria-label=\"Disabled input example\" disabled>\n<input class=\"form-control\" type=\"text\" value=\"Disabled readonly input\" aria-label=\"Disabled input example\" disabled readonly>\n{{< /example >}}\n\n## Readonly\n\nAdd the `readonly` boolean attribute on an input to prevent modification of the input's value. `readonly` inputs can still be focused and selected, while `disabled` inputs cannot.\n\n{{< example >}}\n<input class=\"form-control\" type=\"text\" value=\"Readonly input here...\" aria-label=\"readonly input example\" readonly>\n{{< /example >}}\n\n## Readonly plain text\n\nIf you want to have `<input readonly>` elements in your form styled as plain text, replace `.form-control` with `.form-control-plaintext` to remove the default form field styling and preserve the correct `margin` and `padding`.\n\n{{< example >}}\n  <div class=\"mb-3 row\">\n    <label for=\"staticEmail\" class=\"col-sm-2 col-form-label\">Email</label>\n    <div class=\"col-sm-10\">\n      <input type=\"text\" readonly class=\"form-control-plaintext\" id=\"staticEmail\" value=\"email@example.com\">\n    </div>\n  </div>\n  <div class=\"mb-3 row\">\n    <label for=\"inputPassword\" class=\"col-sm-2 col-form-label\">Password</label>\n    <div class=\"col-sm-10\">\n      <input type=\"password\" class=\"form-control\" id=\"inputPassword\">\n    </div>\n  </div>\n{{< /example >}}\n\n{{< example >}}\n<form class=\"row g-3\">\n  <div class=\"col-auto\">\n    <label for=\"staticEmail2\" class=\"visually-hidden\">Email</label>\n    <input type=\"text\" readonly class=\"form-control-plaintext\" id=\"staticEmail2\" value=\"email@example.com\">\n  </div>\n  <div class=\"col-auto\">\n    <label for=\"inputPassword2\" class=\"visually-hidden\">Password</label>\n    <input type=\"password\" class=\"form-control\" id=\"inputPassword2\" placeholder=\"Password\">\n  </div>\n  <div class=\"col-auto\">\n    <button type=\"submit\" class=\"btn btn-primary mb-3\">Confirm identity</button>\n  </div>\n</form>\n{{< /example >}}\n\n## File input\n\n{{< example >}}\n<div class=\"mb-3\">\n  <label for=\"formFile\" class=\"form-label\">Default file input example</label>\n  <input class=\"form-control\" type=\"file\" id=\"formFile\">\n</div>\n<div class=\"mb-3\">\n  <label for=\"formFileMultiple\" class=\"form-label\">Multiple files input example</label>\n  <input class=\"form-control\" type=\"file\" id=\"formFileMultiple\" multiple>\n</div>\n<div class=\"mb-3\">\n  <label for=\"formFileDisabled\" class=\"form-label\">Disabled file input example</label>\n  <input class=\"form-control\" type=\"file\" id=\"formFileDisabled\" disabled>\n</div>\n<div class=\"mb-3\">\n  <label for=\"formFileSm\" class=\"form-label\">Small file input example</label>\n  <input class=\"form-control form-control-sm\" id=\"formFileSm\" type=\"file\">\n</div>\n<div>\n  <label for=\"formFileLg\" class=\"form-label\">Large file input example</label>\n  <input class=\"form-control form-control-lg\" id=\"formFileLg\" type=\"file\">\n</div>\n{{< /example >}}\n\n## Color\n\nSet the `type=\"color\"` and add `.form-control-color` to the `<input>`. We use the modifier class to set fixed `height`s and override some inconsistencies between browsers.\n\n{{< example >}}\n<label for=\"exampleColorInput\" class=\"form-label\">Color picker</label>\n<input type=\"color\" class=\"form-control form-control-color\" id=\"exampleColorInput\" value=\"#563d7c\" title=\"Choose your color\">\n{{< /example >}}\n\n## Datalists\n\nDatalists allow you to create a group of `<option>`s that can be accessed (and autocompleted) from within an `<input>`. These are similar to `<select>` elements, but come with more menu styling limitations and differences. While most browsers and operating systems include some support for `<datalist>` elements, their styling is inconsistent at best.\n\nLearn more about [support for datalist elements](https://caniuse.com/datalist).\n\n{{< example >}}\n<label for=\"exampleDataList\" class=\"form-label\">Datalist example</label>\n<input class=\"form-control\" list=\"datalistOptions\" id=\"exampleDataList\" placeholder=\"Type to search...\">\n<datalist id=\"datalistOptions\">\n  <option value=\"San Francisco\">\n  <option value=\"New York\">\n  <option value=\"Seattle\">\n  <option value=\"Los Angeles\">\n  <option value=\"Chicago\">\n</datalist>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n`$input-*` are shared across most of our form controls (and not buttons).\n\n{{< scss-docs name=\"form-input-variables\" file=\"scss/_variables.scss\" >}}\n\n`$form-label-*` and `$form-text-*` are for our `<label>`s and `.form-text` component.\n\n{{< scss-docs name=\"form-label-variables\" file=\"scss/_variables.scss\" >}}\n\n{{< scss-docs name=\"form-text-variables\" file=\"scss/_variables.scss\" >}}\n\n`$form-file-*` are for file input.\n\n{{< scss-docs name=\"form-file-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/input-group.md",
    "content": "---\nlayout: docs\ntitle: Input group\ndescription: Easily extend form controls by adding text, buttons, or button groups on either side of textual inputs, custom selects, and custom file inputs.\ngroup: forms\ntoc: true\n---\n\n## Basic example\n\nPlace one add-on or button on either side of an input. You may also place one on both sides of an input. Remember to place `<label>`s outside the input group.\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <span class=\"input-group-text\" id=\"basic-addon1\">@</span>\n  <input type=\"text\" class=\"form-control\" placeholder=\"Username\" aria-label=\"Username\" aria-describedby=\"basic-addon1\">\n</div>\n\n<div class=\"input-group mb-3\">\n  <input type=\"text\" class=\"form-control\" placeholder=\"Recipient's username\" aria-label=\"Recipient's username\" aria-describedby=\"basic-addon2\">\n  <span class=\"input-group-text\" id=\"basic-addon2\">@example.com</span>\n</div>\n\n<label for=\"basic-url\" class=\"form-label\">Your vanity URL</label>\n<div class=\"input-group mb-3\">\n  <span class=\"input-group-text\" id=\"basic-addon3\">https://example.com/users/</span>\n  <input type=\"text\" class=\"form-control\" id=\"basic-url\" aria-describedby=\"basic-addon3\">\n</div>\n\n<div class=\"input-group mb-3\">\n  <span class=\"input-group-text\">$</span>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Amount (to the nearest dollar)\">\n  <span class=\"input-group-text\">.00</span>\n</div>\n\n<div class=\"input-group mb-3\">\n  <input type=\"text\" class=\"form-control\" placeholder=\"Username\" aria-label=\"Username\">\n  <span class=\"input-group-text\">@</span>\n  <input type=\"text\" class=\"form-control\" placeholder=\"Server\" aria-label=\"Server\">\n</div>\n\n<div class=\"input-group\">\n  <span class=\"input-group-text\">With textarea</span>\n  <textarea class=\"form-control\" aria-label=\"With textarea\"></textarea>\n</div>\n{{< /example >}}\n\n## Wrapping\n\nInput groups wrap by default via `flex-wrap: wrap` in order to accommodate custom form field validation within an input group. You may disable this with `.flex-nowrap`.\n\n{{< example >}}\n<div class=\"input-group flex-nowrap\">\n  <span class=\"input-group-text\" id=\"addon-wrapping\">@</span>\n  <input type=\"text\" class=\"form-control\" placeholder=\"Username\" aria-label=\"Username\" aria-describedby=\"addon-wrapping\">\n</div>\n{{< /example >}}\n\n## Sizing\n\nAdd the relative form sizing classes to the `.input-group` itself and contents within will automatically resize—no need for repeating the form control size classes on each element.\n\n**Sizing on the individual input group elements isn't supported.**\n\n{{< example >}}\n<div class=\"input-group input-group-sm mb-3\">\n  <span class=\"input-group-text\" id=\"inputGroup-sizing-sm\">Small</span>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Sizing example input\" aria-describedby=\"inputGroup-sizing-sm\">\n</div>\n\n<div class=\"input-group mb-3\">\n  <span class=\"input-group-text\" id=\"inputGroup-sizing-default\">Default</span>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Sizing example input\" aria-describedby=\"inputGroup-sizing-default\">\n</div>\n\n<div class=\"input-group input-group-lg\">\n  <span class=\"input-group-text\" id=\"inputGroup-sizing-lg\">Large</span>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Sizing example input\" aria-describedby=\"inputGroup-sizing-lg\">\n</div>\n{{< /example >}}\n\n## Checkboxes and radios\n\nPlace any checkbox or radio option within an input group's addon instead of text. We recommend adding `.mt-0` to the `.form-check-input` when there's no visible text next to the input.\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <div class=\"input-group-text\">\n    <input class=\"form-check-input mt-0\" type=\"checkbox\" value=\"\" aria-label=\"Checkbox for following text input\">\n  </div>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with checkbox\">\n</div>\n\n<div class=\"input-group\">\n  <div class=\"input-group-text\">\n    <input class=\"form-check-input mt-0\" type=\"radio\" value=\"\" aria-label=\"Radio button for following text input\">\n  </div>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with radio button\">\n</div>\n{{< /example >}}\n\n## Multiple inputs\n\nWhile multiple `<input>`s are supported visually, validation styles are only available for input groups with a single `<input>`.\n\n{{< example >}}\n<div class=\"input-group\">\n  <span class=\"input-group-text\">First and last name</span>\n  <input type=\"text\" aria-label=\"First name\" class=\"form-control\">\n  <input type=\"text\" aria-label=\"Last name\" class=\"form-control\">\n</div>\n{{< /example >}}\n\n## Multiple addons\n\nMultiple add-ons are supported and can be mixed with checkbox and radio input versions.\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <span class=\"input-group-text\">$</span>\n  <span class=\"input-group-text\">0.00</span>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Dollar amount (with dot and two decimal places)\">\n</div>\n\n<div class=\"input-group\">\n  <input type=\"text\" class=\"form-control\" aria-label=\"Dollar amount (with dot and two decimal places)\">\n  <span class=\"input-group-text\">$</span>\n  <span class=\"input-group-text\">0.00</span>\n</div>\n{{< /example >}}\n\n## Button addons\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\" id=\"button-addon1\">Button</button>\n  <input type=\"text\" class=\"form-control\" placeholder=\"\" aria-label=\"Example text with button addon\" aria-describedby=\"button-addon1\">\n</div>\n\n<div class=\"input-group mb-3\">\n  <input type=\"text\" class=\"form-control\" placeholder=\"Recipient's username\" aria-label=\"Recipient's username\" aria-describedby=\"button-addon2\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\" id=\"button-addon2\">Button</button>\n</div>\n\n<div class=\"input-group mb-3\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\">Button</button>\n  <button class=\"btn btn-outline-secondary\" type=\"button\">Button</button>\n  <input type=\"text\" class=\"form-control\" placeholder=\"\" aria-label=\"Example text with two button addons\">\n</div>\n\n<div class=\"input-group\">\n  <input type=\"text\" class=\"form-control\" placeholder=\"Recipient's username\" aria-label=\"Recipient's username with two button addons\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\">Button</button>\n  <button class=\"btn btn-outline-secondary\" type=\"button\">Button</button>\n</div>\n{{< /example >}}\n\n## Buttons with dropdowns\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <button class=\"btn btn-outline-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with dropdown button\">\n</div>\n\n<div class=\"input-group mb-3\">\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with dropdown button\">\n  <button class=\"btn btn-outline-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>\n  <ul class=\"dropdown-menu dropdown-menu-end\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n\n<div class=\"input-group\">\n  <button class=\"btn btn-outline-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action before</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action before</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with 2 dropdown buttons\">\n  <button class=\"btn btn-outline-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">Dropdown</button>\n  <ul class=\"dropdown-menu dropdown-menu-end\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\n## Segmented buttons\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <button type=\"button\" class=\"btn btn-outline-secondary\">Action</button>\n  <button type=\"button\" class=\"btn btn-outline-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropdown</span>\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with segmented dropdown button\">\n</div>\n\n<div class=\"input-group\">\n  <input type=\"text\" class=\"form-control\" aria-label=\"Text input with segmented dropdown button\">\n  <button type=\"button\" class=\"btn btn-outline-secondary\">Action</button>\n  <button type=\"button\" class=\"btn btn-outline-secondary dropdown-toggle dropdown-toggle-split\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <span class=\"visually-hidden\">Toggle Dropdown</span>\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-end\">\n    <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Separated link</a></li>\n  </ul>\n</div>\n{{< /example >}}\n\n## Custom forms\n\nInput groups include support for custom selects and custom file inputs. Browser default versions of these are not supported.\n\n### Custom select\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <label class=\"input-group-text\" for=\"inputGroupSelect01\">Options</label>\n  <select class=\"form-select\" id=\"inputGroupSelect01\">\n    <option selected>Choose...</option>\n    <option value=\"1\">One</option>\n    <option value=\"2\">Two</option>\n    <option value=\"3\">Three</option>\n  </select>\n</div>\n\n<div class=\"input-group mb-3\">\n  <select class=\"form-select\" id=\"inputGroupSelect02\">\n    <option selected>Choose...</option>\n    <option value=\"1\">One</option>\n    <option value=\"2\">Two</option>\n    <option value=\"3\">Three</option>\n  </select>\n  <label class=\"input-group-text\" for=\"inputGroupSelect02\">Options</label>\n</div>\n\n<div class=\"input-group mb-3\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\">Button</button>\n  <select class=\"form-select\" id=\"inputGroupSelect03\" aria-label=\"Example select with button addon\">\n    <option selected>Choose...</option>\n    <option value=\"1\">One</option>\n    <option value=\"2\">Two</option>\n    <option value=\"3\">Three</option>\n  </select>\n</div>\n\n<div class=\"input-group\">\n  <select class=\"form-select\" id=\"inputGroupSelect04\" aria-label=\"Example select with button addon\">\n    <option selected>Choose...</option>\n    <option value=\"1\">One</option>\n    <option value=\"2\">Two</option>\n    <option value=\"3\">Three</option>\n  </select>\n  <button class=\"btn btn-outline-secondary\" type=\"button\">Button</button>\n</div>\n{{< /example >}}\n\n### Custom file input\n\n{{< example >}}\n<div class=\"input-group mb-3\">\n  <label class=\"input-group-text\" for=\"inputGroupFile01\">Upload</label>\n  <input type=\"file\" class=\"form-control\" id=\"inputGroupFile01\">\n</div>\n\n<div class=\"input-group mb-3\">\n  <input type=\"file\" class=\"form-control\" id=\"inputGroupFile02\">\n  <label class=\"input-group-text\" for=\"inputGroupFile02\">Upload</label>\n</div>\n\n<div class=\"input-group mb-3\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\" id=\"inputGroupFileAddon03\">Button</button>\n  <input type=\"file\" class=\"form-control\" id=\"inputGroupFile03\" aria-describedby=\"inputGroupFileAddon03\" aria-label=\"Upload\">\n</div>\n\n<div class=\"input-group\">\n  <input type=\"file\" class=\"form-control\" id=\"inputGroupFile04\" aria-describedby=\"inputGroupFileAddon04\" aria-label=\"Upload\">\n  <button class=\"btn btn-outline-secondary\" type=\"button\" id=\"inputGroupFileAddon04\">Button</button>\n</div>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"input-group-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/layout.md",
    "content": "---\nlayout: docs\ntitle: Layout\ndescription: Give your forms some structure—from inline to horizontal to custom grid implementations—with our form layout options.\ngroup: forms\ntoc: true\n---\n\n## Forms\n\nEvery group of form fields should reside in a `<form>` element. Bootstrap provides no default styling for the `<form>` element, but there are some powerful browser features that are provided by default.\n\n- New to browser forms? Consider reviewing [the MDN form docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) for an overview and complete list of available attributes.\n- `<button>`s within a `<form>` default to `type=\"submit\"`, so strive to be specific and always include a `type`.\n\nSince Bootstrap applies `display: block` and `width: 100%` to almost all our form controls, forms will by default stack vertically. Additional classes can be used to vary this layout on a per-form basis.\n\n## Utilities\n\n[Margin utilities]({{< docsref \"/utilities/spacing\" >}}) are the easiest way to add some structure to forms. They provide basic grouping of labels, controls, optional form text, and form validation messaging. We recommend sticking to `margin-bottom` utilities, and using a single direction throughout the form for consistency.\n\nFeel free to build your forms however you like, with `<fieldset>`s, `<div>`s, or nearly any other element.\n\n{{< example >}}\n<div class=\"mb-3\">\n  <label for=\"formGroupExampleInput\" class=\"form-label\">Example label</label>\n  <input type=\"text\" class=\"form-control\" id=\"formGroupExampleInput\" placeholder=\"Example input placeholder\">\n</div>\n<div class=\"mb-3\">\n  <label for=\"formGroupExampleInput2\" class=\"form-label\">Another label</label>\n  <input type=\"text\" class=\"form-control\" id=\"formGroupExampleInput2\" placeholder=\"Another input placeholder\">\n</div>\n{{< /example >}}\n\n## Form grid\n\nMore complex forms can be built using our grid classes. Use these for form layouts that require multiple columns, varied widths, and additional alignment options. **Requires the `$enable-grid-classes` Sass variable to be enabled** (on by default).\n\n{{< example >}}\n<div class=\"row\">\n  <div class=\"col\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"First name\" aria-label=\"First name\">\n  </div>\n  <div class=\"col\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"Last name\" aria-label=\"Last name\">\n  </div>\n</div>\n{{< /example >}}\n\n## Gutters\n\nBy adding [gutter modifier classes]({{< docsref \"/layout/gutters\" >}}), you can have control over the gutter width in as well the inline as block direction. **Also requires the `$enable-grid-classes` Sass variable to be enabled** (on by default).\n\n{{< example >}}\n<div class=\"row g-3\">\n  <div class=\"col\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"First name\" aria-label=\"First name\">\n  </div>\n  <div class=\"col\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"Last name\" aria-label=\"Last name\">\n  </div>\n</div>\n{{< /example >}}\n\nMore complex layouts can also be created with the grid system.\n\n{{< example >}}\n<form class=\"row g-3\">\n  <div class=\"col-md-6\">\n    <label for=\"inputEmail4\" class=\"form-label\">Email</label>\n    <input type=\"email\" class=\"form-control\" id=\"inputEmail4\">\n  </div>\n  <div class=\"col-md-6\">\n    <label for=\"inputPassword4\" class=\"form-label\">Password</label>\n    <input type=\"password\" class=\"form-control\" id=\"inputPassword4\">\n  </div>\n  <div class=\"col-12\">\n    <label for=\"inputAddress\" class=\"form-label\">Address</label>\n    <input type=\"text\" class=\"form-control\" id=\"inputAddress\" placeholder=\"1234 Main St\">\n  </div>\n  <div class=\"col-12\">\n    <label for=\"inputAddress2\" class=\"form-label\">Address 2</label>\n    <input type=\"text\" class=\"form-control\" id=\"inputAddress2\" placeholder=\"Apartment, studio, or floor\">\n  </div>\n  <div class=\"col-md-6\">\n    <label for=\"inputCity\" class=\"form-label\">City</label>\n    <input type=\"text\" class=\"form-control\" id=\"inputCity\">\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"inputState\" class=\"form-label\">State</label>\n    <select id=\"inputState\" class=\"form-select\">\n      <option selected>Choose...</option>\n      <option>...</option>\n    </select>\n  </div>\n  <div class=\"col-md-2\">\n    <label for=\"inputZip\" class=\"form-label\">Zip</label>\n    <input type=\"text\" class=\"form-control\" id=\"inputZip\">\n  </div>\n  <div class=\"col-12\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input\" type=\"checkbox\" id=\"gridCheck\">\n      <label class=\"form-check-label\" for=\"gridCheck\">\n        Check me out\n      </label>\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <button type=\"submit\" class=\"btn btn-primary\">Sign in</button>\n  </div>\n</form>\n{{< /example >}}\n\n## Horizontal form\n\nCreate horizontal forms with the grid by adding the `.row` class to form groups and using the `.col-*-*` classes to specify the width of your labels and controls. Be sure to add `.col-form-label` to your `<label>`s as well so they're vertically centered with their associated form controls.\n\nAt times, you maybe need to use margin or padding utilities to create that perfect alignment you need. For example, we've removed the `padding-top` on our stacked radio inputs label to better align the text baseline.\n\n{{< example >}}\n<form>\n  <div class=\"row mb-3\">\n    <label for=\"inputEmail3\" class=\"col-sm-2 col-form-label\">Email</label>\n    <div class=\"col-sm-10\">\n      <input type=\"email\" class=\"form-control\" id=\"inputEmail3\">\n    </div>\n  </div>\n  <div class=\"row mb-3\">\n    <label for=\"inputPassword3\" class=\"col-sm-2 col-form-label\">Password</label>\n    <div class=\"col-sm-10\">\n      <input type=\"password\" class=\"form-control\" id=\"inputPassword3\">\n    </div>\n  </div>\n  <fieldset class=\"row mb-3\">\n    <legend class=\"col-form-label col-sm-2 pt-0\">Radios</legend>\n    <div class=\"col-sm-10\">\n      <div class=\"form-check\">\n        <input class=\"form-check-input\" type=\"radio\" name=\"gridRadios\" id=\"gridRadios1\" value=\"option1\" checked>\n        <label class=\"form-check-label\" for=\"gridRadios1\">\n          First radio\n        </label>\n      </div>\n      <div class=\"form-check\">\n        <input class=\"form-check-input\" type=\"radio\" name=\"gridRadios\" id=\"gridRadios2\" value=\"option2\">\n        <label class=\"form-check-label\" for=\"gridRadios2\">\n          Second radio\n        </label>\n      </div>\n      <div class=\"form-check disabled\">\n        <input class=\"form-check-input\" type=\"radio\" name=\"gridRadios\" id=\"gridRadios3\" value=\"option3\" disabled>\n        <label class=\"form-check-label\" for=\"gridRadios3\">\n          Third disabled radio\n        </label>\n      </div>\n    </div>\n  </fieldset>\n  <div class=\"row mb-3\">\n    <div class=\"col-sm-10 offset-sm-2\">\n      <div class=\"form-check\">\n        <input class=\"form-check-input\" type=\"checkbox\" id=\"gridCheck1\">\n        <label class=\"form-check-label\" for=\"gridCheck1\">\n          Example checkbox\n        </label>\n      </div>\n    </div>\n  </div>\n  <button type=\"submit\" class=\"btn btn-primary\">Sign in</button>\n</form>\n{{< /example >}}\n\n### Horizontal form label sizing\n\nBe sure to use `.col-form-label-sm` or `.col-form-label-lg` to your `<label>`s or `<legend>`s to correctly follow the size of `.form-control-lg` and `.form-control-sm`.\n\n{{< example >}}\n<div class=\"row mb-3\">\n  <label for=\"colFormLabelSm\" class=\"col-sm-2 col-form-label col-form-label-sm\">Email</label>\n  <div class=\"col-sm-10\">\n    <input type=\"email\" class=\"form-control form-control-sm\" id=\"colFormLabelSm\" placeholder=\"col-form-label-sm\">\n  </div>\n</div>\n<div class=\"row mb-3\">\n  <label for=\"colFormLabel\" class=\"col-sm-2 col-form-label\">Email</label>\n  <div class=\"col-sm-10\">\n    <input type=\"email\" class=\"form-control\" id=\"colFormLabel\" placeholder=\"col-form-label\">\n  </div>\n</div>\n<div class=\"row\">\n  <label for=\"colFormLabelLg\" class=\"col-sm-2 col-form-label col-form-label-lg\">Email</label>\n  <div class=\"col-sm-10\">\n    <input type=\"email\" class=\"form-control form-control-lg\" id=\"colFormLabelLg\" placeholder=\"col-form-label-lg\">\n  </div>\n</div>\n{{< /example >}}\n\n## Column sizing\n\nAs shown in the previous examples, our grid system allows you to place any number of `.col`s within a `.row`. They'll split the available width equally between them. You may also pick a subset of your columns to take up more or less space, while the remaining `.col`s equally split the rest, with specific column classes like `.col-sm-7`.\n\n{{< example >}}\n<div class=\"row g-3\">\n  <div class=\"col-sm-7\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"City\" aria-label=\"City\">\n  </div>\n  <div class=\"col-sm\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"State\" aria-label=\"State\">\n  </div>\n  <div class=\"col-sm\">\n    <input type=\"text\" class=\"form-control\" placeholder=\"Zip\" aria-label=\"Zip\">\n  </div>\n</div>\n{{< /example >}}\n\n## Auto-sizing\n\nThe example below uses a flexbox utility to vertically center the contents and changes `.col` to `.col-auto` so that your columns only take up as much space as needed. Put another way, the column sizes itself based on the contents.\n\n{{< example >}}\n<form class=\"row gy-2 gx-3 align-items-center\">\n  <div class=\"col-auto\">\n    <label class=\"visually-hidden\" for=\"autoSizingInput\">Name</label>\n    <input type=\"text\" class=\"form-control\" id=\"autoSizingInput\" placeholder=\"Jane Doe\">\n  </div>\n  <div class=\"col-auto\">\n    <label class=\"visually-hidden\" for=\"autoSizingInputGroup\">Username</label>\n    <div class=\"input-group\">\n      <div class=\"input-group-text\">@</div>\n      <input type=\"text\" class=\"form-control\" id=\"autoSizingInputGroup\" placeholder=\"Username\">\n    </div>\n  </div>\n  <div class=\"col-auto\">\n    <label class=\"visually-hidden\" for=\"autoSizingSelect\">Preference</label>\n    <select class=\"form-select\" id=\"autoSizingSelect\">\n      <option selected>Choose...</option>\n      <option value=\"1\">One</option>\n      <option value=\"2\">Two</option>\n      <option value=\"3\">Three</option>\n    </select>\n  </div>\n  <div class=\"col-auto\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input\" type=\"checkbox\" id=\"autoSizingCheck\">\n      <label class=\"form-check-label\" for=\"autoSizingCheck\">\n        Remember me\n      </label>\n    </div>\n  </div>\n  <div class=\"col-auto\">\n    <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n  </div>\n</form>\n{{< /example >}}\n\nYou can then remix that once again with size-specific column classes.\n\n{{< example >}}\n<form class=\"row gx-3 gy-2 align-items-center\">\n  <div class=\"col-sm-3\">\n    <label class=\"visually-hidden\" for=\"specificSizeInputName\">Name</label>\n    <input type=\"text\" class=\"form-control\" id=\"specificSizeInputName\" placeholder=\"Jane Doe\">\n  </div>\n  <div class=\"col-sm-3\">\n    <label class=\"visually-hidden\" for=\"specificSizeInputGroupUsername\">Username</label>\n    <div class=\"input-group\">\n      <div class=\"input-group-text\">@</div>\n      <input type=\"text\" class=\"form-control\" id=\"specificSizeInputGroupUsername\" placeholder=\"Username\">\n    </div>\n  </div>\n  <div class=\"col-sm-3\">\n    <label class=\"visually-hidden\" for=\"specificSizeSelect\">Preference</label>\n    <select class=\"form-select\" id=\"specificSizeSelect\">\n      <option selected>Choose...</option>\n      <option value=\"1\">One</option>\n      <option value=\"2\">Two</option>\n      <option value=\"3\">Three</option>\n    </select>\n  </div>\n  <div class=\"col-auto\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input\" type=\"checkbox\" id=\"autoSizingCheck2\">\n      <label class=\"form-check-label\" for=\"autoSizingCheck2\">\n        Remember me\n      </label>\n    </div>\n  </div>\n  <div class=\"col-auto\">\n    <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n  </div>\n</form>\n{{< /example >}}\n\n## Inline forms\n\nUse the `.row-cols-*` classes to create responsive horizontal layouts. By adding [gutter modifier classes]({{< docsref \"/layout/gutters\" >}}), we'll have gutters in horizontal and vertical directions. On narrow mobile viewports, the `.col-12` helps stack the form controls and more. The `.align-items-center` aligns the form elements to the middle, making the `.form-check` align properly.\n\n{{< example >}}\n<form class=\"row row-cols-lg-auto g-3 align-items-center\">\n  <div class=\"col-12\">\n    <label class=\"visually-hidden\" for=\"inlineFormInputGroupUsername\">Username</label>\n    <div class=\"input-group\">\n      <div class=\"input-group-text\">@</div>\n      <input type=\"text\" class=\"form-control\" id=\"inlineFormInputGroupUsername\" placeholder=\"Username\">\n    </div>\n  </div>\n\n  <div class=\"col-12\">\n    <label class=\"visually-hidden\" for=\"inlineFormSelectPref\">Preference</label>\n    <select class=\"form-select\" id=\"inlineFormSelectPref\">\n      <option selected>Choose...</option>\n      <option value=\"1\">One</option>\n      <option value=\"2\">Two</option>\n      <option value=\"3\">Three</option>\n    </select>\n  </div>\n\n  <div class=\"col-12\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input\" type=\"checkbox\" id=\"inlineFormCheck\">\n      <label class=\"form-check-label\" for=\"inlineFormCheck\">\n        Remember me\n      </label>\n    </div>\n  </div>\n\n  <div class=\"col-12\">\n    <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n  </div>\n</form>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/overview.md",
    "content": "---\nlayout: docs\ntitle: Forms\ndescription: Examples and usage guidelines for form control styles, layout options, and custom components for creating a wide variety of forms.\ngroup: forms\ntoc: true\naliases: \"/docs/5.2/forms/\"\nsections:\n  - title: Form control\n    description: Style textual inputs and textareas with support for multiple states.\n  - title: Select\n    description: Improve browser default select elements with a custom initial appearance.\n  - title: Checks & radios\n    description: Use our custom radio buttons and checkboxes in forms for selecting input options.\n  - title: Range\n    description: Replace browser default range inputs with our custom version.\n  - title: Input group\n    description: Attach labels and buttons to your inputs for increased semantic value.\n  - title: Floating labels\n    description: Create beautifully simple form labels that float over your input fields.\n  - title: Layout\n    description: Create inline, horizontal, or complex grid-based layouts with your forms.\n  - title: Validation\n    description: Validate your forms with custom or native validation behaviors and styles.\n---\n\n## Overview\n\nBootstrap's form controls expand on [our Rebooted form styles]({{< docsref \"/content/reboot#forms\" >}}) with classes. Use these classes to opt into their customized displays for a more consistent rendering across browsers and devices.\n\nBe sure to use an appropriate `type` attribute on all inputs (e.g., `email` for email address or `number` for numerical information) to take advantage of newer input controls like email verification, number selection, and more.\n\nHere's a quick example to demonstrate Bootstrap's form styles. Keep reading for documentation on required classes, form layout, and more.\n\n{{< example >}}\n<form>\n  <div class=\"mb-3\">\n    <label for=\"exampleInputEmail1\" class=\"form-label\">Email address</label>\n    <input type=\"email\" class=\"form-control\" id=\"exampleInputEmail1\" aria-describedby=\"emailHelp\">\n    <div id=\"emailHelp\" class=\"form-text\">We'll never share your email with anyone else.</div>\n  </div>\n  <div class=\"mb-3\">\n    <label for=\"exampleInputPassword1\" class=\"form-label\">Password</label>\n    <input type=\"password\" class=\"form-control\" id=\"exampleInputPassword1\">\n  </div>\n  <div class=\"mb-3 form-check\">\n    <input type=\"checkbox\" class=\"form-check-input\" id=\"exampleCheck1\">\n    <label class=\"form-check-label\" for=\"exampleCheck1\">Check me out</label>\n  </div>\n  <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n</form>\n{{< /example >}}\n\n## Form text\n\nBlock-level or inline-level form text can be created using `.form-text`.\n\n{{< callout warning >}}\n##### Associating form text with form controls\n\nForm text should be explicitly associated with the form control it relates to using the `aria-describedby` attribute. This will ensure that assistive technologies—such as screen readers—will announce this form text when the user focuses or enters the control.\n{{< /callout >}}\n\nForm text below inputs can be styled with `.form-text`. If a block-level element will be used, a top margin is added for easy spacing from the inputs above.\n\n{{< example >}}\n<label for=\"inputPassword5\" class=\"form-label\">Password</label>\n<input type=\"password\" id=\"inputPassword5\" class=\"form-control\" aria-describedby=\"passwordHelpBlock\">\n<div id=\"passwordHelpBlock\" class=\"form-text\">\n  Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji.\n</div>\n{{< /example >}}\n\nInline text can use any typical inline HTML element (be it a `<span>`, `<small>`, or something else) with nothing more than the `.form-text` class.\n\n{{< example >}}\n<div class=\"row g-3 align-items-center\">\n  <div class=\"col-auto\">\n    <label for=\"inputPassword6\" class=\"col-form-label\">Password</label>\n  </div>\n  <div class=\"col-auto\">\n    <input type=\"password\" id=\"inputPassword6\" class=\"form-control\" aria-describedby=\"passwordHelpInline\">\n  </div>\n  <div class=\"col-auto\">\n    <span id=\"passwordHelpInline\" class=\"form-text\">\n      Must be 8-20 characters long.\n    </span>\n  </div>\n</div>\n{{< /example >}}\n\n## Disabled forms\n\nAdd the `disabled` boolean attribute on an input to prevent user interactions and make it appear lighter.\n\n```html\n<input class=\"form-control\" id=\"disabledInput\" type=\"text\" placeholder=\"Disabled input here...\" disabled>\n```\n\nAdd the `disabled` attribute to a `<fieldset>` to disable all the controls within. Browsers treat all native form controls (`<input>`, `<select>`, and `<button>` elements) inside a `<fieldset disabled>` as disabled, preventing both keyboard and mouse interactions on them.\n\nHowever, if your form also includes custom button-like elements such as `<a class=\"btn btn-*\">...</a>`, these will only be given a style of `pointer-events: none`, meaning they are still focusable and operable using the keyboard. In this case, you must manually modify these controls by adding `tabindex=\"-1\"` to prevent them from receiving focus and `aria-disabled=\"disabled\"` to signal their state to assistive technologies.\n\n{{< example >}}\n<form>\n  <fieldset disabled>\n    <legend>Disabled fieldset example</legend>\n    <div class=\"mb-3\">\n      <label for=\"disabledTextInput\" class=\"form-label\">Disabled input</label>\n      <input type=\"text\" id=\"disabledTextInput\" class=\"form-control\" placeholder=\"Disabled input\">\n    </div>\n    <div class=\"mb-3\">\n      <label for=\"disabledSelect\" class=\"form-label\">Disabled select menu</label>\n      <select id=\"disabledSelect\" class=\"form-select\">\n        <option>Disabled select</option>\n      </select>\n    </div>\n    <div class=\"mb-3\">\n      <div class=\"form-check\">\n        <input class=\"form-check-input\" type=\"checkbox\" id=\"disabledFieldsetCheck\" disabled>\n        <label class=\"form-check-label\" for=\"disabledFieldsetCheck\">\n          Can't check this\n        </label>\n      </div>\n    </div>\n    <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\n  </fieldset>\n</form>\n{{< /example >}}\n\n## Accessibility\n\nEnsure that all form controls have an appropriate accessible name so that their purpose can be conveyed to users of assistive technologies. The simplest way to achieve this is to use a `<label>` element, or—in the case of buttons—to include sufficiently descriptive text as part of the `<button>...</button>` content.\n\nFor situations where it's not possible to include a visible `<label>` or appropriate text content, there are alternative ways of still providing an accessible name, such as:\n\n- `<label>` elements hidden using the `.visually-hidden` class\n- Pointing to an existing element that can act as a label using `aria-labelledby`\n- Providing a `title` attribute\n- Explicitly setting the accessible name on an element using `aria-label`\n\nIf none of these are present, assistive technologies may resort to using the `placeholder` attribute as a fallback for the accessible name on `<input>` and `<textarea>` elements. The examples in this section provide a few suggested, case-specific approaches.\n\nWhile using visually hidden content (`.visually-hidden`, `aria-label`, and even `placeholder` content, which disappears once a form field has content) will benefit assistive technology users, a lack of visible label text may still be problematic for certain users. Some form of visible label is generally the best approach, both for accessibility and usability.\n\n## Sass\n\nMany form variables are set at a general level to be re-used and extended by individual form components. You'll see these most often as `$input-btn-*` and `$input-*` variables.\n\n### Variables\n\n`$input-btn-*` variables are shared global variables between our [buttons]({{< docsref \"/components/buttons\" >}}) and our form components. You'll find these frequently reassigned as values to other component-specific variables.\n\n{{< scss-docs name=\"input-btn-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/range.md",
    "content": "---\nlayout: docs\ntitle: Range\ndescription: Use our custom range inputs for consistent cross-browser styling and built-in customization.\ngroup: forms\ntoc: true\n---\n\n## Overview\n\nCreate custom `<input type=\"range\">` controls with `.form-range`. The track (the background) and thumb (the value) are both styled to appear the same across browsers. As only Firefox supports \"filling\" their track from the left or right of the thumb as a means to visually indicate progress, we do not currently support it.\n\n{{< example >}}\n<label for=\"customRange1\" class=\"form-label\">Example range</label>\n<input type=\"range\" class=\"form-range\" id=\"customRange1\">\n{{< /example >}}\n\n## Disabled\n\nAdd the `disabled` boolean attribute on an input to give it a grayed out appearance, remove pointer events, and prevent focusing.\n\n{{< example >}}\n<label for=\"disabledRange\" class=\"form-label\">Disabled range</label>\n<input type=\"range\" class=\"form-range\" id=\"disabledRange\" disabled>\n{{< /example >}}\n\n## Min and max\n\nRange inputs have implicit values for `min` and `max`—`0` and `100`, respectively. You may specify new values for those using the `min` and `max` attributes.\n\n{{< example >}}\n<label for=\"customRange2\" class=\"form-label\">Example range</label>\n<input type=\"range\" class=\"form-range\" min=\"0\" max=\"5\" id=\"customRange2\">\n{{< /example >}}\n\n## Steps\n\nBy default, range inputs \"snap\" to integer values. To change this, you can specify a `step` value. In the example below, we double the number of steps by using `step=\"0.5\"`.\n\n{{< example >}}\n<label for=\"customRange3\" class=\"form-label\">Example range</label>\n<input type=\"range\" class=\"form-range\" min=\"0\" max=\"5\" step=\"0.5\" id=\"customRange3\">\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"form-range-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/select.md",
    "content": "---\nlayout: docs\ntitle: Select\ndescription: Customize the native `<select>`s with custom CSS that changes the element's initial appearance.\ngroup: forms\ntoc: true\n---\n\n## Default\n\nCustom `<select>` menus need only a custom class, `.form-select` to trigger the custom styles. Custom styles are limited to the `<select>`'s initial appearance and cannot modify the `<option>`s due to browser limitations.\n\n{{< example >}}\n<select class=\"form-select\" aria-label=\"Default select example\">\n  <option selected>Open this select menu</option>\n  <option value=\"1\">One</option>\n  <option value=\"2\">Two</option>\n  <option value=\"3\">Three</option>\n</select>\n{{< /example >}}\n\n## Sizing\n\nYou may also choose from small and large custom selects to match our similarly sized text inputs.\n\n{{< example >}}\n<select class=\"form-select form-select-lg mb-3\" aria-label=\".form-select-lg example\">\n  <option selected>Open this select menu</option>\n  <option value=\"1\">One</option>\n  <option value=\"2\">Two</option>\n  <option value=\"3\">Three</option>\n</select>\n\n<select class=\"form-select form-select-sm\" aria-label=\".form-select-sm example\">\n  <option selected>Open this select menu</option>\n  <option value=\"1\">One</option>\n  <option value=\"2\">Two</option>\n  <option value=\"3\">Three</option>\n</select>\n{{< /example >}}\n\nThe `multiple` attribute is also supported:\n\n{{< example >}}\n<select class=\"form-select\" multiple aria-label=\"multiple select example\">\n  <option selected>Open this select menu</option>\n  <option value=\"1\">One</option>\n  <option value=\"2\">Two</option>\n  <option value=\"3\">Three</option>\n</select>\n{{< /example >}}\n\nAs is the `size` attribute:\n\n{{< example >}}\n<select class=\"form-select\" size=\"3\" aria-label=\"size 3 select example\">\n  <option selected>Open this select menu</option>\n  <option value=\"1\">One</option>\n  <option value=\"2\">Two</option>\n  <option value=\"3\">Three</option>\n</select>\n{{< /example >}}\n\n## Disabled\n\nAdd the `disabled` boolean attribute on a select to give it a grayed out appearance and remove pointer events.\n\n{{< example >}}\n<select class=\"form-select\" aria-label=\"Disabled select example\" disabled>\n  <option selected>Open this select menu</option>\n  <option value=\"1\">One</option>\n  <option value=\"2\">Two</option>\n  <option value=\"3\">Three</option>\n</select>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"form-select-variables\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/forms/validation.md",
    "content": "---\nlayout: docs\ntitle: Validation\ndescription: Provide valuable, actionable feedback to your users with HTML5 form validation, via browser default behaviors or custom styles and JavaScript.\ngroup: forms\ntoc: true\nextra_js:\n  - src: \"/docs/5.2/assets/js/validate-forms.js\"\n    async: true\n---\n\n{{< callout warning >}}\nWe are aware that currently the client-side custom validation styles and tooltips are not accessible, since they are not exposed to assistive technologies. While we work on a solution, we'd recommend either using the server-side option or the default browser validation method.\n{{< /callout >}}\n\n## How it works\n\nHere's how form validation works with Bootstrap:\n\n- HTML form validation is applied via CSS's two pseudo-classes, `:invalid` and `:valid`. It applies to `<input>`, `<select>`, and `<textarea>` elements.\n- Bootstrap scopes the `:invalid` and `:valid` styles to parent `.was-validated` class, usually applied to the `<form>`. Otherwise, any required field without a value shows up as invalid on page load. This way, you may choose when to activate them (typically after form submission is attempted).\n- To reset the appearance of the form (for instance, in the case of dynamic form submissions using AJAX), remove the `.was-validated` class from the `<form>` again after submission.\n- As a fallback, `.is-invalid` and `.is-valid` classes may be used instead of the pseudo-classes for [server-side validation](#server-side). They do not require a `.was-validated` parent class.\n- Due to constraints in how CSS works, we cannot (at present) apply styles to a `<label>` that comes before a form control in the DOM without the help of custom JavaScript.\n- All modern browsers support the [constraint validation API](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-constraint-validation-api), a series of JavaScript methods for validating form controls.\n- Feedback messages may utilize the [browser defaults](#browser-defaults) (different for each browser, and unstylable via CSS) or our custom feedback styles with additional HTML and CSS.\n- You may provide custom validity messages with `setCustomValidity` in JavaScript.\n\nWith that in mind, consider the following demos for our custom form validation styles, optional server-side classes, and browser defaults.\n\n## Custom styles\n\nFor custom Bootstrap form validation messages, you'll need to add the `novalidate` boolean attribute to your `<form>`. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls.\n\nCustom feedback styles apply custom colors, borders, focus styles, and background icons to better communicate feedback. Background icons for `<select>`s are only available with `.form-select`, and not `.form-control`.\n\n{{< example >}}\n<form class=\"row g-3 needs-validation\" novalidate>\n  <div class=\"col-md-4\">\n    <label for=\"validationCustom01\" class=\"form-label\">First name</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationCustom01\" value=\"Mark\" required>\n    <div class=\"valid-feedback\">\n      Looks good!\n    </div>\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"validationCustom02\" class=\"form-label\">Last name</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationCustom02\" value=\"Otto\" required>\n    <div class=\"valid-feedback\">\n      Looks good!\n    </div>\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"validationCustomUsername\" class=\"form-label\">Username</label>\n    <div class=\"input-group has-validation\">\n      <span class=\"input-group-text\" id=\"inputGroupPrepend\">@</span>\n      <input type=\"text\" class=\"form-control\" id=\"validationCustomUsername\" aria-describedby=\"inputGroupPrepend\" required>\n      <div class=\"invalid-feedback\">\n        Please choose a username.\n      </div>\n    </div>\n  </div>\n  <div class=\"col-md-6\">\n    <label for=\"validationCustom03\" class=\"form-label\">City</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationCustom03\" required>\n    <div class=\"invalid-feedback\">\n      Please provide a valid city.\n    </div>\n  </div>\n  <div class=\"col-md-3\">\n    <label for=\"validationCustom04\" class=\"form-label\">State</label>\n    <select class=\"form-select\" id=\"validationCustom04\" required>\n      <option selected disabled value=\"\">Choose...</option>\n      <option>...</option>\n    </select>\n    <div class=\"invalid-feedback\">\n      Please select a valid state.\n    </div>\n  </div>\n  <div class=\"col-md-3\">\n    <label for=\"validationCustom05\" class=\"form-label\">Zip</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationCustom05\" required>\n    <div class=\"invalid-feedback\">\n      Please provide a valid zip.\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"invalidCheck\" required>\n      <label class=\"form-check-label\" for=\"invalidCheck\">\n        Agree to terms and conditions\n      </label>\n      <div class=\"invalid-feedback\">\n        You must agree before submitting.\n      </div>\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <button class=\"btn btn-primary\" type=\"submit\">Submit form</button>\n  </div>\n</form>\n{{< /example >}}\n\n{{< example lang=\"js\" show_preview=\"false\" >}}\n{{< js.inline >}}\n{{- readFile (path.Join \"site/static/docs\" .Site.Params.docs_version \"assets/js/validate-forms.js\") -}}\n{{< /js.inline >}}\n{{< /example >}}\n\n## Browser defaults\n\nNot interested in custom validation feedback messages or writing JavaScript to change form behaviors? All good, you can use the browser defaults. Try submitting the form below. Depending on your browser and OS, you'll see a slightly different style of feedback.\n\nWhile these feedback styles cannot be styled with CSS, you can still customize the feedback text through JavaScript.\n\n{{< example >}}\n<form class=\"row g-3\">\n  <div class=\"col-md-4\">\n    <label for=\"validationDefault01\" class=\"form-label\">First name</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationDefault01\" value=\"Mark\" required>\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"validationDefault02\" class=\"form-label\">Last name</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationDefault02\" value=\"Otto\" required>\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"validationDefaultUsername\" class=\"form-label\">Username</label>\n    <div class=\"input-group\">\n      <span class=\"input-group-text\" id=\"inputGroupPrepend2\">@</span>\n      <input type=\"text\" class=\"form-control\" id=\"validationDefaultUsername\"  aria-describedby=\"inputGroupPrepend2\" required>\n    </div>\n  </div>\n  <div class=\"col-md-6\">\n    <label for=\"validationDefault03\" class=\"form-label\">City</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationDefault03\" required>\n  </div>\n  <div class=\"col-md-3\">\n    <label for=\"validationDefault04\" class=\"form-label\">State</label>\n    <select class=\"form-select\" id=\"validationDefault04\" required>\n      <option selected disabled value=\"\">Choose...</option>\n      <option>...</option>\n    </select>\n  </div>\n  <div class=\"col-md-3\">\n    <label for=\"validationDefault05\" class=\"form-label\">Zip</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationDefault05\" required>\n  </div>\n  <div class=\"col-12\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input\" type=\"checkbox\" value=\"\" id=\"invalidCheck2\" required>\n      <label class=\"form-check-label\" for=\"invalidCheck2\">\n        Agree to terms and conditions\n      </label>\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <button class=\"btn btn-primary\" type=\"submit\">Submit form</button>\n  </div>\n</form>\n{{< /example >}}\n\n## Server side\n\nWe recommend using client-side validation, but in case you require server-side validation, you can indicate invalid and valid form fields with `.is-invalid` and `.is-valid`. Note that `.invalid-feedback` is also supported with these classes.\n\nFor invalid fields, ensure that the invalid feedback/error message is associated with the relevant form field using `aria-describedby` (noting that this attribute allows more than one `id` to be referenced, in case the field already points to additional form text).\n\nTo fix [issues with border radius](https://github.com/twbs/bootstrap/issues/25110), input groups require an additional `.has-validation` class.\n\n{{< example >}}\n<form class=\"row g-3\">\n  <div class=\"col-md-4\">\n    <label for=\"validationServer01\" class=\"form-label\">First name</label>\n    <input type=\"text\" class=\"form-control is-valid\" id=\"validationServer01\" value=\"Mark\" required>\n    <div class=\"valid-feedback\">\n      Looks good!\n    </div>\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"validationServer02\" class=\"form-label\">Last name</label>\n    <input type=\"text\" class=\"form-control is-valid\" id=\"validationServer02\" value=\"Otto\" required>\n    <div class=\"valid-feedback\">\n      Looks good!\n    </div>\n  </div>\n  <div class=\"col-md-4\">\n    <label for=\"validationServerUsername\" class=\"form-label\">Username</label>\n    <div class=\"input-group has-validation\">\n      <span class=\"input-group-text\" id=\"inputGroupPrepend3\">@</span>\n      <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServerUsername\" aria-describedby=\"inputGroupPrepend3 validationServerUsernameFeedback\" required>\n      <div id=\"validationServerUsernameFeedback\" class=\"invalid-feedback\">\n        Please choose a username.\n      </div>\n    </div>\n  </div>\n  <div class=\"col-md-6\">\n    <label for=\"validationServer03\" class=\"form-label\">City</label>\n    <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServer03\" aria-describedby=\"validationServer03Feedback\" required>\n    <div id=\"validationServer03Feedback\" class=\"invalid-feedback\">\n      Please provide a valid city.\n    </div>\n  </div>\n  <div class=\"col-md-3\">\n    <label for=\"validationServer04\" class=\"form-label\">State</label>\n    <select class=\"form-select is-invalid\" id=\"validationServer04\" aria-describedby=\"validationServer04Feedback\" required>\n      <option selected disabled value=\"\">Choose...</option>\n      <option>...</option>\n    </select>\n    <div id=\"validationServer04Feedback\" class=\"invalid-feedback\">\n      Please select a valid state.\n    </div>\n  </div>\n  <div class=\"col-md-3\">\n    <label for=\"validationServer05\" class=\"form-label\">Zip</label>\n    <input type=\"text\" class=\"form-control is-invalid\" id=\"validationServer05\" aria-describedby=\"validationServer05Feedback\" required>\n    <div id=\"validationServer05Feedback\" class=\"invalid-feedback\">\n      Please provide a valid zip.\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <div class=\"form-check\">\n      <input class=\"form-check-input is-invalid\" type=\"checkbox\" value=\"\" id=\"invalidCheck3\" aria-describedby=\"invalidCheck3Feedback\" required>\n      <label class=\"form-check-label\" for=\"invalidCheck3\">\n        Agree to terms and conditions\n      </label>\n      <div id=\"invalidCheck3Feedback\" class=\"invalid-feedback\">\n        You must agree before submitting.\n      </div>\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <button class=\"btn btn-primary\" type=\"submit\">Submit form</button>\n  </div>\n</form>\n{{< /example >}}\n\n## Supported elements\n\nValidation styles are available for the following form controls and components:\n\n- `<input>`s and `<textarea>`s with `.form-control` (including up to one `.form-control` in input groups)\n- `<select>`s with `.form-select`\n- `.form-check`s\n\n{{< example >}}\n<form class=\"was-validated\">\n  <div class=\"mb-3\">\n    <label for=\"validationTextarea\" class=\"form-label\">Textarea</label>\n    <textarea class=\"form-control\" id=\"validationTextarea\" placeholder=\"Required example textarea\" required></textarea>\n    <div class=\"invalid-feedback\">\n      Please enter a message in the textarea.\n    </div>\n  </div>\n\n  <div class=\"form-check mb-3\">\n    <input type=\"checkbox\" class=\"form-check-input\" id=\"validationFormCheck1\" required>\n    <label class=\"form-check-label\" for=\"validationFormCheck1\">Check this checkbox</label>\n    <div class=\"invalid-feedback\">Example invalid feedback text</div>\n  </div>\n\n  <div class=\"form-check\">\n    <input type=\"radio\" class=\"form-check-input\" id=\"validationFormCheck2\" name=\"radio-stacked\" required>\n    <label class=\"form-check-label\" for=\"validationFormCheck2\">Toggle this radio</label>\n  </div>\n  <div class=\"form-check mb-3\">\n    <input type=\"radio\" class=\"form-check-input\" id=\"validationFormCheck3\" name=\"radio-stacked\" required>\n    <label class=\"form-check-label\" for=\"validationFormCheck3\">Or toggle this other radio</label>\n    <div class=\"invalid-feedback\">More example invalid feedback text</div>\n  </div>\n\n  <div class=\"mb-3\">\n    <select class=\"form-select\" required aria-label=\"select example\">\n      <option value=\"\">Open this select menu</option>\n      <option value=\"1\">One</option>\n      <option value=\"2\">Two</option>\n      <option value=\"3\">Three</option>\n    </select>\n    <div class=\"invalid-feedback\">Example invalid select feedback</div>\n  </div>\n\n  <div class=\"mb-3\">\n    <input type=\"file\" class=\"form-control\" aria-label=\"file example\" required>\n    <div class=\"invalid-feedback\">Example invalid form file feedback</div>\n  </div>\n\n  <div class=\"mb-3\">\n    <button class=\"btn btn-primary\" type=\"submit\" disabled>Submit form</button>\n  </div>\n</form>\n{{< /example >}}\n\n## Tooltips\n\nIf your form layout allows it, you can swap the `.{valid|invalid}-feedback` classes for `.{valid|invalid}-tooltip` classes to display validation feedback in a styled tooltip. Be sure to have a parent with `position: relative` on it for tooltip positioning. In the example below, our column classes have this already, but your project may require an alternative setup.\n\n{{< example >}}\n<form class=\"row g-3 needs-validation\" novalidate>\n  <div class=\"col-md-4 position-relative\">\n    <label for=\"validationTooltip01\" class=\"form-label\">First name</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationTooltip01\" value=\"Mark\" required>\n    <div class=\"valid-tooltip\">\n      Looks good!\n    </div>\n  </div>\n  <div class=\"col-md-4 position-relative\">\n    <label for=\"validationTooltip02\" class=\"form-label\">Last name</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationTooltip02\" value=\"Otto\" required>\n    <div class=\"valid-tooltip\">\n      Looks good!\n    </div>\n  </div>\n  <div class=\"col-md-4 position-relative\">\n    <label for=\"validationTooltipUsername\" class=\"form-label\">Username</label>\n    <div class=\"input-group has-validation\">\n      <span class=\"input-group-text\" id=\"validationTooltipUsernamePrepend\">@</span>\n      <input type=\"text\" class=\"form-control\" id=\"validationTooltipUsername\" aria-describedby=\"validationTooltipUsernamePrepend\" required>\n      <div class=\"invalid-tooltip\">\n        Please choose a unique and valid username.\n      </div>\n    </div>\n  </div>\n  <div class=\"col-md-6 position-relative\">\n    <label for=\"validationTooltip03\" class=\"form-label\">City</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationTooltip03\" required>\n    <div class=\"invalid-tooltip\">\n      Please provide a valid city.\n    </div>\n  </div>\n  <div class=\"col-md-3 position-relative\">\n    <label for=\"validationTooltip04\" class=\"form-label\">State</label>\n    <select class=\"form-select\" id=\"validationTooltip04\" required>\n      <option selected disabled value=\"\">Choose...</option>\n      <option>...</option>\n    </select>\n    <div class=\"invalid-tooltip\">\n      Please select a valid state.\n    </div>\n  </div>\n  <div class=\"col-md-3 position-relative\">\n    <label for=\"validationTooltip05\" class=\"form-label\">Zip</label>\n    <input type=\"text\" class=\"form-control\" id=\"validationTooltip05\" required>\n    <div class=\"invalid-tooltip\">\n      Please provide a valid zip.\n    </div>\n  </div>\n  <div class=\"col-12\">\n    <button class=\"btn btn-primary\" type=\"submit\">Submit form</button>\n  </div>\n</form>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"form-feedback-variables\" file=\"scss/_variables.scss\" >}}\n\n### Mixins\n\nTwo mixins are combined together, through our [loop](#loop), to generate our form validation feedback styles.\n\n{{< scss-docs name=\"form-validation-mixins\" file=\"scss/mixins/_forms.scss\" >}}\n\n### Map\n\nThis is the validation Sass map from `_variables.scss`. Override or extend this to generate different or additional states.\n\n{{< scss-docs name=\"form-validation-states\" file=\"scss/_variables.scss\" >}}\n\nMaps of `$form-validation-states` can contain three optional parameters to override tooltips and focus styles.\n\n### Loop\n\nUsed to iterate over `$form-validation-states` map values to generate our validation styles. Any modifications to the above Sass map will be reflected in your compiled CSS via this loop.\n\n{{< scss-docs name=\"form-validation-states-loop\" file=\"scss/forms/_validation.scss\" >}}\n\n### Customizing\n\nValidation states can be customized via Sass with the `$form-validation-states` map. Located in our `_variables.scss` file, this Sass map is how we generate the default `valid`/`invalid` validation states. Included is a nested map for customizing each state's color, icon, tooltip color, and focus shadow. While no other states are supported by browsers, those using custom styles can easily add more complex form feedback.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/accessibility.md",
    "content": "---\nlayout: docs\ntitle: Accessibility\ndescription: A brief overview of Bootstrap's features and limitations for the creation of accessible content.\ngroup: getting-started\ntoc: true\n---\n\nBootstrap provides an easy-to-use framework of ready-made styles, layout tools, and interactive components, allowing developers to create websites and applications that are visually appealing, functionally rich, and accessible out of the box.\n\n## Overview and limitations\n\nThe overall accessibility of any project built with Bootstrap depends in large part on the author's markup, additional styling, and scripting they've included. However, provided that these have been implemented correctly, it should be perfectly possible to create websites and applications with Bootstrap that fulfill [<abbr title=\"Web Content Accessibility Guidelines\">WCAG</abbr> 2.1](https://www.w3.org/TR/WCAG/) (A/AA/AAA), [Section 508](https://www.section508.gov/), and similar accessibility standards and requirements.\n\n### Structural markup\n\nBootstrap's styling and layout can be applied to a wide range of markup structures. This documentation aims to provide developers with best practice examples to demonstrate the use of Bootstrap itself and illustrate appropriate semantic markup, including ways in which potential accessibility concerns can be addressed.\n\n### Interactive components\n\nBootstrap's interactive components—such as modal dialogs, dropdown menus, and custom tooltips—are designed to work for touch, mouse, and keyboard users. Through the use of relevant [<abbr title=\"Web Accessibility Initiative\">WAI</abbr>-<abbr title=\"Accessible Rich Internet Applications\">ARIA</abbr>](https://www.w3.org/WAI/standards-guidelines/aria/) roles and attributes, these components should also be understandable and operable using assistive technologies (such as screen readers).\n\nBecause Bootstrap's components are purposely designed to be fairly generic, authors may need to include further <abbr title=\"Accessible Rich Internet Applications\">ARIA</abbr> roles and attributes, as well as JavaScript behavior, to more accurately convey the precise nature and functionality of their component. This is usually noted in the documentation.\n\n### Color contrast\n\nSome combinations of colors that currently make up Bootstrap's default palette—used throughout the framework for things such as button variations, alert variations, form validation indicators—may lead to *insufficient* color contrast (below the recommended [WCAG 2.1 text color contrast ratio of 4.5:1](https://www.w3.org/TR/WCAG/#contrast-minimum) and the [WCAG 2.1 non-text color contrast ratio of 3:1](https://www.w3.org/TR/WCAG/#non-text-contrast)), particularly when used against a light background. Authors are encouraged to test their specific uses of color and, where necessary, manually modify/extend these default colors to ensure adequate color contrast ratios.\n\n### Visually hidden content\n\nContent which should be visually hidden, but remain accessible to assistive technologies such as screen readers, can be styled using the `.visually-hidden` class. This can be useful in situations where additional visual information or cues (such as meaning denoted through the use of color) need to also be conveyed to non-visual users.\n\n```html\n<p class=\"text-danger\">\n  <span class=\"visually-hidden\">Danger: </span>\n  This action is not reversible\n</p>\n```\n\nFor visually hidden interactive controls, such as traditional \"skip\" links, use the `.visually-hidden-focusable` class. This will ensure that the control becomes visible once focused (for sighted keyboard users). **Watch out, compared to the equivalent `.sr-only` and `.sr-only-focusable` classes in past versions, Bootstrap 5's `.visually-hidden-focusable` is a standalone class, and must not be used in combination with the `.visually-hidden` class.**\n\n```html\n<a class=\"visually-hidden-focusable\" href=\"#content\">Skip to main content</a>\n```\n\n### Reduced motion\n\nBootstrap includes support for the [`prefers-reduced-motion` media feature](https://www.w3.org/TR/mediaqueries-5/#prefers-reduced-motion). In browsers/environments that allow the user to specify their preference for reduced motion, most CSS transition effects in Bootstrap (for instance, when a modal dialog is opened or closed, or the sliding animation in carousels) will be disabled, and meaningful animations (such as spinners) will be slowed down.\n\nOn browsers that support `prefers-reduced-motion`, and where the user has *not* explicitly signaled that they'd prefer reduced motion (i.e. where `prefers-reduced-motion: no-preference`), Bootstrap enables smooth scrolling using the `scroll-behavior` property.\n\n## Additional resources\n\n- [Web Content Accessibility Guidelines (WCAG) 2.1](https://www.w3.org/TR/WCAG/)\n- [The A11Y Project](https://www.a11yproject.com/)\n- [MDN accessibility documentation](https://developer.mozilla.org/en-US/docs/Web/Accessibility)\n- [Tenon.io Accessibility Checker](https://tenon.io/)\n- [Color Contrast Analyser (CCA)](https://www.tpgi.com/color-contrast-checker/)\n- [\"HTML Codesniffer\" bookmarklet for identifying accessibility issues](https://github.com/squizlabs/HTML_CodeSniffer)\n- [Microsoft Accessibility Insights](https://accessibilityinsights.io/)\n- [Deque Axe testing tools](https://www.deque.com/axe/)\n- [Introduction to Web Accessibility](https://www.w3.org/WAI/fundamentals/accessibility-intro/)\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/best-practices.md",
    "content": "---\nlayout: docs\ntitle: Best practices\ndescription: Learn about some of the best practices we've gathered from years of working on and using Bootstrap.\ngroup: getting-started\n---\n\nWe've designed and developed Bootstrap to work in a number of environments. Here are some of the best practices we've gathered from years of working on and using it ourselves.\n\n{{< callout info >}}\n**Heads up!** This copy is a work in progress.\n{{< /callout >}}\n\n### General outline\n\n- Working with CSS\n- Working with Sass files\n- Building new CSS components\n- Working with flexbox\n- Ask in [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions)\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/browsers-devices.md",
    "content": "---\nlayout: docs\ntitle: Browsers and devices\ndescription: Learn about the browsers and devices, from modern to old, that are supported by Bootstrap, including known quirks and bugs for each.\ngroup: getting-started\ntoc: true\n---\n\n## Supported browsers\n\nBootstrap supports the **latest, stable releases** of all major browsers and platforms.\n\nAlternative browsers which use the latest version of WebKit, Blink, or Gecko, whether directly or via the platform's web view API, are not explicitly supported. However, Bootstrap should (in most cases) display and function correctly in these browsers as well. More specific support information is provided below.\n\nYou can find our supported range of browsers and their versions [in our `.browserslistrc file`]({{< param repo >}}/blob/v{{< param current_version >}}/.browserslistrc):\n\n```text\n{{< rf.inline >}}\n{{- readFile \".browserslistrc\" | chomp | htmlEscape -}}\n{{< /rf.inline >}}\n```\n\nWe use [Autoprefixer](https://github.com/postcss/autoprefixer) to handle intended browser support via CSS prefixes, which uses [Browserslist](https://github.com/browserslist/browserslist) to manage these browser versions. Consult their documentation for how to integrate these tools into your projects.\n\n### Mobile devices\n\nGenerally speaking, Bootstrap supports the latest versions of each major platform's default browsers. Note that proxy browsers (such as Opera Mini, Opera Mobile's Turbo mode, UC Browser Mini, Amazon Silk) are not supported.\n\n{{< bs-table \"table\" >}}\n| | Chrome | Firefox | Safari | Android Browser &amp; WebView |\n| --- | --- | --- | --- | --- |\n| **Android** | Supported | Supported | <span class=\"text-muted\">&mdash;</span> | v6.0+ |\n| **iOS** | Supported | Supported | Supported | <span class=\"text-muted\">&mdash;</span> |\n{{< /bs-table >}}\n\n### Desktop browsers\n\nSimilarly, the latest versions of most desktop browsers are supported.\n\n{{< bs-table \"table\" >}}\n| | Chrome | Firefox | Microsoft Edge | Opera | Safari |\n| --- | --- | --- | --- | --- | --- |\n| **Mac** | Supported | Supported | Supported | Supported | Supported |\n| **Windows** | Supported | Supported | Supported | Supported | <span class=\"text-muted\">&mdash;</span> |\n{{< /bs-table >}}\n\nFor Firefox, in addition to the latest normal stable release, we also support the latest [Extended Support Release (ESR)](https://www.mozilla.org/en-US/firefox/enterprise/) version of Firefox.\n\nUnofficially, Bootstrap should look and behave well enough in Chromium and Chrome for Linux, and Firefox for Linux, though they are not officially supported.\n\n## Internet Explorer\n\nInternet Explorer is not supported. **If you require Internet Explorer support, please use Bootstrap v4.**\n\n## Modals and dropdowns on mobile\n\n### Overflow and scrolling\n\nSupport for `overflow: hidden;` on the `<body>` element is quite limited in iOS and Android. To that end, when you scroll past the top or bottom of a modal in either of those devices' browsers, the `<body>` content will begin to scroll. See [Chrome bug #175502](https://bugs.chromium.org/p/chromium/issues/detail?id=175502) (fixed in Chrome v40) and [WebKit bug #153852](https://bugs.webkit.org/show_bug.cgi?id=153852).\n\n### iOS text fields and scrolling\n\nAs of iOS 9.2, while a modal is open, if the initial touch of a scroll gesture is within the boundary of a textual `<input>` or a `<textarea>`, the `<body>` content underneath the modal will be scrolled instead of the modal itself. See [WebKit bug #153856](https://bugs.webkit.org/show_bug.cgi?id=153856).\n\n### Navbar Dropdowns\n\nThe `.dropdown-backdrop` element isn't used on iOS in the nav because of the complexity of z-indexing. Thus, to close dropdowns in navbars, you must directly click the dropdown element (or [any other element which will fire a click event in iOS](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#Safari_Mobile)).\n\n## Browser zooming\n\nPage zooming inevitably presents rendering artifacts in some components, both in Bootstrap and the rest of the web. Depending on the issue, we may be able to fix it (search first and then open an issue if need be). However, we tend to ignore these as they often have no direct solution other than hacky workarounds.\n\n## Validators\n\nIn order to provide the best possible experience to old and buggy browsers, Bootstrap uses [CSS browser hacks](http://browserhacks.com/) in several places to target special CSS to certain browser versions in order to work around bugs in the browsers themselves. These hacks understandably cause CSS validators to complain that they are invalid. In a couple places, we also use bleeding-edge CSS features that aren't yet fully standardized, but these are used purely for progressive enhancement.\n\nThese validation warnings don't matter in practice since the non-hacky portion of our CSS does fully validate and the hacky portions don't interfere with the proper functioning of the non-hacky portion, hence why we deliberately ignore these particular warnings.\n\nOur HTML docs likewise have some trivial and inconsequential HTML validation warnings due to our inclusion of a workaround for [a certain Firefox bug](https://bugzilla.mozilla.org/show_bug.cgi?id=654072).\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/contents.md",
    "content": "---\nlayout: docs\ntitle: Contents\ndescription: Discover what's included in Bootstrap, including our compiled and source code flavors.\ngroup: getting-started\ntoc: true\n---\n\n## Compiled Bootstrap\n\nOnce downloaded, unzip the compressed folder and you'll see something like this:\n\n<!-- NOTE: This info is intentionally duplicated in the README. Copy any changes made here over to the README too, but be sure to keep in mind to add the `dist` folder. -->\n\n```text\nbootstrap/\n├── css/\n│   ├── bootstrap-grid.css\n│   ├── bootstrap-grid.css.map\n│   ├── bootstrap-grid.min.css\n│   ├── bootstrap-grid.min.css.map\n│   ├── bootstrap-grid.rtl.css\n│   ├── bootstrap-grid.rtl.css.map\n│   ├── bootstrap-grid.rtl.min.css\n│   ├── bootstrap-grid.rtl.min.css.map\n│   ├── bootstrap-reboot.css\n│   ├── bootstrap-reboot.css.map\n│   ├── bootstrap-reboot.min.css\n│   ├── bootstrap-reboot.min.css.map\n│   ├── bootstrap-reboot.rtl.css\n│   ├── bootstrap-reboot.rtl.css.map\n│   ├── bootstrap-reboot.rtl.min.css\n│   ├── bootstrap-reboot.rtl.min.css.map\n│   ├── bootstrap-utilities.css\n│   ├── bootstrap-utilities.css.map\n│   ├── bootstrap-utilities.min.css\n│   ├── bootstrap-utilities.min.css.map\n│   ├── bootstrap-utilities.rtl.css\n│   ├── bootstrap-utilities.rtl.css.map\n│   ├── bootstrap-utilities.rtl.min.css\n│   ├── bootstrap-utilities.rtl.min.css.map\n│   ├── bootstrap.css\n│   ├── bootstrap.css.map\n│   ├── bootstrap.min.css\n│   ├── bootstrap.min.css.map\n│   ├── bootstrap.rtl.css\n│   ├── bootstrap.rtl.css.map\n│   ├── bootstrap.rtl.min.css\n│   └── bootstrap.rtl.min.css.map\n└── js/\n    ├── bootstrap.bundle.js\n    ├── bootstrap.bundle.js.map\n    ├── bootstrap.bundle.min.js\n    ├── bootstrap.bundle.min.js.map\n    ├── bootstrap.esm.js\n    ├── bootstrap.esm.js.map\n    ├── bootstrap.esm.min.js\n    ├── bootstrap.esm.min.js.map\n    ├── bootstrap.js\n    ├── bootstrap.js.map\n    ├── bootstrap.min.js\n    └── bootstrap.min.js.map\n```\n\nThis is the most basic form of Bootstrap: compiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/).\n\n### CSS files\n\nBootstrap includes a handful of options for including some or all of our compiled CSS.\n\n{{< bs-table \"table\" >}}\n| CSS files | Layout | Content | Components | Utilities |\n| --- | --- | --- | --- | --- |\n| `bootstrap.css`<br> `bootstrap.min.css`<br> `bootstrap.rtl.css`<br> `bootstrap.rtl.min.css` | Included | Included | Included | Included |\n| `bootstrap-grid.css`<br> `bootstrap-grid.rtl.css`<br> `bootstrap-grid.min.css`<br> `bootstrap-grid.rtl.min.css` | [Only grid system]({{< docsref \"/layout/grid\" >}}) | — | — | [Only flex utilities]({{< docsref \"/utilities/flex\" >}}) |\n| `bootstrap-utilities.css`<br> `bootstrap-utilities.rtl.css`<br> `bootstrap-utilities.min.css`<br> `bootstrap-utilities.rtl.min.css` | — | — | — | Included |\n| `bootstrap-reboot.css`<br> `bootstrap-reboot.rtl.css`<br> `bootstrap-reboot.min.css`<br> `bootstrap-reboot.rtl.min.css` | — | [Only Reboot]({{< docsref \"/content/reboot\" >}}) | — | — |\n{{< /bs-table >}}\n\n### JS files\n\nSimilarly, we have options for including some or all of our compiled JavaScript.\n\n{{< bs-table \"table\" >}}\n| JS Files | Popper |\n| --- | --- |\n| `bootstrap.bundle.js`<br> `bootstrap.bundle.min.js`<br> | Included |\n| `bootstrap.js`<br> `bootstrap.min.js`<br> | – |\n{{< /bs-table >}}\n\n## Bootstrap source code\n\nThe Bootstrap source code download includes the compiled CSS and JavaScript assets, along with source Sass, JavaScript, and documentation. More specifically, it includes the following and more:\n\n```text\nbootstrap/\n├── dist/\n│   ├── css/\n│   └── js/\n├── site/\n│   └──content/\n│      └── docs/\n│          └── {{< param docs_version >}}/\n│              └── examples/\n├── js/\n└── scss/\n```\n\nThe `scss/` and `js/` are the source code for our CSS and JavaScript. The `dist/` folder includes everything listed in the compiled download section above. The `site/content/docs/` folder includes the source code for our hosted documentation, including our live examples of Bootstrap usage.\n\nBeyond that, any other included file provides support for packages, license information, and development.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/contribute.md",
    "content": "---\nlayout: docs\ntitle: Contribute\ndescription: Help develop Bootstrap with our documentation build scripts and tests.\ngroup: getting-started\ntoc: true\naliases: \"/docs/5.2/getting-started/build-tools/\"\n---\n\n## Tooling setup\n\nBootstrap uses [npm scripts](https://docs.npmjs.com/misc/scripts/) to build the documentation and compile source files. Our [package.json]({{< param repo >}}/blob/v{{< param current_version >}}/package.json) houses these scripts for compiling code, running tests, and more. These aren't intended for use outside our repository and documentation.\n\nTo use our build system and run our documentation locally, you'll need a copy of Bootstrap's source files and Node. Follow these steps and you should be ready to rock:\n\n1. [Download and install Node.js](https://nodejs.org/en/download/), which we use to manage our dependencies.\n2. Either [download Bootstrap's sources]({{< param \"download.source\" >}}) or fork [Bootstrap's repository]({{< param repo >}}).\n3. Navigate to the root `/bootstrap` directory and run `npm install` to install our local dependencies listed in [package.json]({{< param repo >}}/blob/v{{< param current_version >}}/package.json).\n\nWhen completed, you'll be able to run the various commands provided from the command line.\n\n## Using npm scripts\n\nOur [package.json]({{< param repo >}}/blob/v{{< param current_version >}}/package.json) includes numerous tasks for developing the project. Run `npm run` to see all the npm scripts in your terminal. **Primary tasks include:**\n\n{{< bs-table >}}\n| Task | Description |\n| --- | --- |\n| `npm start` | Compiles CSS and JavaScript, builds the documentation, and starts a local server. |\n| `npm run dist` | Creates the `dist/` directory with compiled files. Uses [Sass](https://sass-lang.com/), [Autoprefixer](https://github.com/postcss/autoprefixer), and [terser](https://github.com/terser/terser). |\n| `npm test` | Runs tests locally after running `npm run dist` |\n| `npm run docs-serve` | Builds and runs the documentation locally. |\n{{< /bs-table >}}\n\n{{< callout info >}}\n{{< partial \"callout-info-npm-starter.md\" >}}\n{{< /callout >}}\n\n## Sass\n\nBootstrap uses [Dart Sass](https://sass-lang.com/dart-sass) for compiling our Sass source files into CSS files (included in our build process), and we recommend you do the same if you're compiling Sass using your own asset pipeline. We previously used Node Sass for Bootstrap v4, but LibSass and packages built on top of it, including Node Sass, are now [deprecated](https://sass-lang.com/blog/libsass-is-deprecated).\n\nDart Sass uses a rounding precision of 10 and for efficiency reasons does not allow adjustment of this value. We don't lower this precision during further processing of our generated CSS, such as during minification, but if you chose to do so we recommend maintaining a precision of at least 6 to prevent issues with browser rounding.\n\n## Autoprefixer\n\nBootstrap uses [Autoprefixer](https://github.com/postcss/autoprefixer) (included in our build process) to automatically add vendor prefixes to some CSS properties at build time. Doing so saves us time and code by allowing us to write key parts of our CSS a single time while eliminating the need for vendor mixins like those found in v3.\n\nWe maintain the list of browsers supported through Autoprefixer in a separate file within our GitHub repository. See [.browserslistrc]({{< param repo >}}/blob/v{{< param current_version >}}/.browserslistrc) for details.\n\n## RTLCSS\n\nBootstrap uses [RTLCSS](https://rtlcss.com/) to process compiled CSS and convert them to RTL – basically replacing horizontal direction aware properties (e.g. `padding-left`) with their opposite. It allows us only write our CSS a single time and make minor tweaks using RTLCSS [control](https://rtlcss.com/learn/usage-guide/control-directives/) and [value](https://rtlcss.com/learn/usage-guide/value-directives/) directives.\n\n## Local documentation\n\nRunning our documentation locally requires the use of Hugo, which gets installed via the [hugo-bin](https://www.npmjs.com/package/hugo-bin) npm package. Hugo is a blazingly fast and quite extensible static site generator that provides us: basic includes, Markdown-based files, templates, and more. Here's how to get it started:\n\n1. Run through the [tooling setup](#tooling-setup) above to install all dependencies.\n2. From the root `/bootstrap` directory, run `npm run docs-serve` in the command line.\n3. Open `http://localhost:9001/` in your browser, and voilà.\n\nLearn more about using Hugo by reading its [documentation](https://gohugo.io/documentation/).\n\n## Troubleshooting\n\nShould you encounter problems with installing dependencies, uninstall all previous dependency versions (global and local). Then, rerun `npm install`.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/download.md",
    "content": "---\nlayout: docs\ntitle: Download\ndescription: Download Bootstrap to get the compiled CSS and JavaScript, source code, or include it with your favorite package managers like npm, RubyGems, and more.\ngroup: getting-started\ntoc: true\n---\n\n## Compiled CSS and JS\n\nDownload ready-to-use compiled code for **Bootstrap v{{< param current_version >}}** to easily drop into your project, which includes:\n\n- Compiled and minified CSS bundles (see [CSS files comparison]({{< docsref \"/getting-started/contents#css-files\" >}}))\n- Compiled and minified JavaScript plugins (see [JS files comparison]({{< docsref \"/getting-started/contents#js-files\" >}}))\n\nThis doesn't include documentation, source files, or any optional JavaScript dependencies like Popper.\n\n<a href=\"{{< param \"download.dist\" >}}\" class=\"btn btn-bd-primary\" onclick=\"ga('send', 'event', 'Getting started', 'Download', 'Download Bootstrap');\">Download</a>\n\n## Source files\n\nCompile Bootstrap with your own asset pipeline by downloading our source Sass, JavaScript, and documentation files. This option requires some additional tooling:\n\n- [Sass compiler]({{< docsref \"/getting-started/contribute#sass\" >}}) for compiling Sass source files into CSS files\n- [Autoprefixer](https://github.com/postcss/autoprefixer) for CSS vendor prefixing\n\nShould you require our full set of [build tools]({{< docsref \"/getting-started/contribute#tooling-setup\" >}}), they are included for developing Bootstrap and its docs, but they're likely unsuitable for your own purposes.\n\n<a href=\"{{< param \"download.source\" >}}\" class=\"btn btn-bd-primary\" onclick=\"ga('send', 'event', 'Getting started', 'Download', 'Download source');\">Download source</a>\n\n## Examples\n\nIf you want to download and examine our [examples]({{< docsref \"/examples\" >}}), you can grab the already built examples:\n\n<a href=\"{{< param \"download.dist_examples\" >}}\" class=\"btn btn-bd-primary\" onclick=\"ga('send', 'event', 'Getting started', 'Download', 'Download Examples');\">Download Examples</a>\n\n## CDN via jsDelivr\n\nSkip the download with [jsDelivr](https://www.jsdelivr.com/) to deliver cached version of Bootstrap's compiled CSS and JS to your project.\n\n```html\n<link href=\"{{< param \"cdn.css\" >}}\" rel=\"stylesheet\" integrity=\"{{< param \"cdn.css_hash\" >}}\" crossorigin=\"anonymous\">\n<script src=\"{{< param \"cdn.js_bundle\" >}}\" integrity=\"{{< param \"cdn.js_bundle_hash\" >}}\" crossorigin=\"anonymous\"></script>\n```\n\nIf you're using our compiled JavaScript and prefer to include Popper separately, add Popper before our JS, via a CDN preferably.\n\n```html\n<script src=\"{{< param \"cdn.popper\" >}}\" integrity=\"{{< param \"cdn.popper_hash\" >}}\" crossorigin=\"anonymous\"></script>\n<script src=\"{{< param \"cdn.js\" >}}\" integrity=\"{{< param \"cdn.js_hash\" >}}\" crossorigin=\"anonymous\"></script>\n```\n\n## Package managers\n\nPull in Bootstrap's **source files** into nearly any project with some of the most popular package managers. No matter the package manager, Bootstrap will **require a [Sass compiler]({{< docsref \"/getting-started/contribute#sass\" >}}) and [Autoprefixer](https://github.com/postcss/autoprefixer)** for a setup that matches our official compiled versions.\n\n### npm\n\nInstall Bootstrap in your Node.js powered apps with [the npm package](https://www.npmjs.com/package/bootstrap):\n\n```sh\nnpm install bootstrap@{{< param \"current_version\" >}}\n```\n\n`const bootstrap = require('bootstrap')` or `import bootstrap from 'bootstrap'` will load all of Bootstrap's plugins onto a `bootstrap` object.\nThe `bootstrap` module itself exports all of our plugins. You can manually load Bootstrap's plugins individually by loading the `/js/dist/*.js` files under the package's top-level directory.\n\nBootstrap's `package.json` contains some additional metadata under the following keys:\n\n- `sass` - path to Bootstrap's main [Sass](https://sass-lang.com/) source file\n- `style` - path to Bootstrap's non-minified CSS that's been compiled using the default settings (no customization)\n\n{{< callout info >}}\n{{< partial \"callout-info-npm-starter.md\" >}}\n{{< /callout >}}\n\n### yarn\n\nInstall Bootstrap in your Node.js powered apps with [the yarn package](https://yarnpkg.com/en/package/bootstrap):\n\n```sh\nyarn add bootstrap@{{< param \"current_version\" >}}\n```\n\n### RubyGems\n\nInstall Bootstrap in your Ruby apps using [Bundler](https://bundler.io/) (**recommended**) and [RubyGems](https://rubygems.org/) by adding the following line to your [`Gemfile`](https://bundler.io/gemfile.html):\n\n```ruby\ngem 'bootstrap', '~> {{< param current_ruby_version >}}'\n```\n\nAlternatively, if you're not using Bundler, you can install the gem by running this command:\n\n```sh\ngem install bootstrap -v {{< param current_ruby_version >}}\n```\n\n[See the gem's README](https://github.com/twbs/bootstrap-rubygem/blob/master/README.md) for further details.\n\n### Composer\n\nYou can also install and manage Bootstrap's Sass and JavaScript using [Composer](https://getcomposer.org/):\n\n```sh\ncomposer require twbs/bootstrap:{{< param current_version >}}\n```\n\n### NuGet\n\nIf you develop in .NET Framework, you can also install and manage Bootstrap's [CSS](https://www.nuget.org/packages/bootstrap/) or [Sass](https://www.nuget.org/packages/bootstrap.sass/) and JavaScript using [NuGet](https://www.nuget.org/). Newer projects should use [libman](https://docs.microsoft.com/en-us/aspnet/core/client-side/libman/) or another method as NuGet is designed for compiled code, not frontend assets.\n\n```powershell\nInstall-Package bootstrap\n```\n\n```powershell\nInstall-Package bootstrap.sass\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/introduction.md",
    "content": "---\nlayout: docs\ntitle: Get started with Bootstrap\ndescription: Bootstrap is a powerful, feature-packed frontend toolkit. Build anything—from prototype to production—in minutes.\ngroup: getting-started\naliases:\n  - \"/docs/5.2/getting-started/\"\n  - \"/docs/getting-started/\"\n  - \"/getting-started/\"\ntoc: true\n---\n\n## Quick start\n\nGet started by including Bootstrap's production-ready CSS and JavaScript via CDN without the need for any build steps. See it in practice with this [Bootstrap CodePen demo](https://codepen.io/team/bootstrap/pen/qBamdLj).\n\n<br>\n\n1. **Create a new `index.html` file in your project root.** Include the `<meta name=\"viewport\">` tag as well for [proper responsive behavior](https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag) in mobile devices.\n\n   ```html\n   <!doctype html>\n   <html lang=\"en\">\n     <head>\n       <meta charset=\"utf-8\">\n       <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n       <title>Bootstrap demo</title>\n     </head>\n     <body>\n       <h1>Hello, world!</h1>\n     </body>\n   </html>\n   ```\n\n2. **Include Bootstrap's CSS and JS.** Place the `<link>` tag in the `<head>` for our CSS, and the `<script>` tag for our JavaScript bundle (including Popper for positioning dropdowns, poppers, and tooltips) before the closing `</body>`. Learn more about our [CDN links](#cdn-links).\n\n   ```html\n   <!doctype html>\n   <html lang=\"en\">\n     <head>\n       <meta charset=\"utf-8\">\n       <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n       <title>Bootstrap demo</title>\n       <link href=\"{{< param \"cdn.css\" >}}\" rel=\"stylesheet\" integrity=\"{{< param \"cdn.css_hash\" >}}\" crossorigin=\"anonymous\">\n     </head>\n     <body>\n       <h1>Hello, world!</h1>\n       <script src=\"{{< param \"cdn.js_bundle\" >}}\" integrity=\"{{< param \"cdn.js_bundle_hash\" >}}\" crossorigin=\"anonymous\"></script>\n     </body>\n   </html>\n   ```\n\n   You can also include [Popper](https://popper.js.org/) and our JS separately. If you don't plan to use dropdowns, popovers, or tooltips, save some kilobytes by not including Popper.\n\n   ```html\n   <script src=\"{{< param \"cdn.popper\" >}}\" integrity=\"{{< param \"cdn.popper_hash\" >}}\" crossorigin=\"anonymous\"></script>\n   <script src=\"{{< param \"cdn.js\" >}}\" integrity=\"{{< param \"cdn.js_hash\" >}}\" crossorigin=\"anonymous\"></script>\n   ```\n\n3. **Hello, world!** Open the page in your browser of choice to see your Bootstrapped page. Now you can start building with Bootstrap by creating your own [layout]({{< docsref \"/layout/grid\" >}}), adding dozens of [components]({{< docsref \"/components/buttons\" >}}), and utilizing [our official examples]({{< docsref \"/examples\" >}}).\n\n## CDN links\n\nAs reference, here are our primary CDN links.\n\n{{< bs-table >}}\n| Description | URL |\n| --- | --- |\n| CSS | `{{< param \"cdn.css\" >}}` |\n| JS | `{{< param \"cdn.js_bundle\" >}}` |\n{{< /bs-table >}}\n\nYou can also use the CDN to fetch any of our [additional builds listed in the Contents page]({{< docsref \"/getting-started/contents\" >}}).\n\n## Next steps\n\n- Read a bit more about some [important global environment settings](#important-globals) that Bootstrap utilizes.\n\n- Read about what's included in Bootstrap in our [contents section]({{< docsref \"/getting-started/contents/\" >}}) and the list of [components that require JavaScript](#js-components) below.\n\n- Need a little more power? Consider building with Bootstrap by [including the source files via package manager]({{< docsref \"/getting-started/download#package-managers\" >}}).\n\n- Looking to use Bootstrap as a module with `<script type=\"module\">`? Please refer to our [using Bootstrap as a module]({{< docsref \"/getting-started/javascript#using-bootstrap-as-a-module\" >}}) section.\n\n## JS components\n\nCurious which components explicitly require our JavaScript and Popper? Click the show components link below. If you're at all unsure about the general page structure, keep reading for an example page template.\n\n<details>\n<summary class=\"text-primary mb-3\">Show components requiring JavaScript</summary>\n{{< markdown >}}\n- Alerts for dismissing\n- Buttons for toggling states and checkbox/radio functionality\n- Carousel for all slide behaviors, controls, and indicators\n- Collapse for toggling visibility of content\n- Dropdowns for displaying and positioning (also requires [Popper](https://popper.js.org/))\n- Modals for displaying, positioning, and scroll behavior\n- Navbar for extending our Collapse and Offcanvas plugins to implement responsive behaviors\n- Navs with the Tab plugin for toggling content panes\n- Offcanvases for displaying, positioning, and scroll behavior\n- Scrollspy for scroll behavior and navigation updates\n- Toasts for displaying and dismissing\n- Tooltips and popovers for displaying and positioning (also requires [Popper](https://popper.js.org/))\n{{< /markdown >}}\n</details>\n\n## Important globals\n\nBootstrap employs a handful of important global styles and settings, all of which are almost exclusively geared towards the *normalization* of cross browser styles. Let's dive in.\n\n### HTML5 doctype\n\nBootstrap requires the use of the HTML5 doctype. Without it, you'll see some funky and incomplete styling.\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  ...\n</html>\n```\n\n### Responsive meta tag\n\nBootstrap is developed *mobile first*, a strategy in which we optimize code for mobile devices first and then scale up components as necessary using CSS media queries. To ensure proper rendering and touch zooming for all devices, add the responsive viewport meta tag to your `<head>`.\n\n```html\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n```\n\nYou can see an example of this in action in the [quick start](#quick-start).\n\n### Box-sizing\n\nFor more straightforward sizing in CSS, we switch the global `box-sizing` value from `content-box` to `border-box`. This ensures `padding` does not affect the final computed width of an element, but it can cause problems with some third-party software like Google Maps and Google Custom Search Engine.\n\nOn the rare occasion you need to override it, use something like the following:\n\n```css\n.selector-for-some-widget {\n  box-sizing: content-box;\n}\n```\n\nWith the above snippet, nested elements—including generated content via `::before` and `::after`—will all inherit the specified `box-sizing` for that `.selector-for-some-widget`.\n\nLearn more about [box model and sizing at CSS Tricks](https://css-tricks.com/box-sizing/).\n\n### Reboot\n\nFor improved cross-browser rendering, we use [Reboot]({{< docsref \"/content/reboot\" >}}) to correct inconsistencies across browsers and devices while providing slightly more opinionated resets to common HTML elements.\n\n## Community\n\nStay up-to-date on the development of Bootstrap and reach out to the community with these helpful resources.\n\n- Read and subscribe to [The Official Bootstrap Blog]({{< param blog >}}).\n- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions).\n- Chat with fellow Bootstrappers in IRC. On the `irc.libera.chat` server, in the `#bootstrap` channel.\n- Implementation help may be found at Stack Overflow (tagged [`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5)).\n- Developers should use the keyword `bootstrap` on packages that modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/search?q=keywords:bootstrap) or similar delivery mechanisms for maximum discoverability.\n\nYou can also follow [@getbootstrap on Twitter](https://twitter.com/{{< param twitter >}}) for the latest gossip and awesome music videos.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/javascript.md",
    "content": "---\nlayout: docs\ntitle: JavaScript\ndescription: Bring Bootstrap to life with our optional JavaScript plugins. Learn about each plugin, our data and programmatic API options, and more.\ngroup: getting-started\ntoc: true\n---\n\n## Individual or compiled\n\nPlugins can be included individually (using Bootstrap's individual `js/dist/*.js`), or all at once using `bootstrap.js` or the minified `bootstrap.min.js` (don't include both).\n\nIf you use a bundler (Webpack, Parcel, Vite...), you can use `/js/dist/*.js` files which are UMD ready.\n\n## Usage with JavaScript frameworks\n\nWhile the Bootstrap CSS can be used with any framework, **the Bootstrap JavaScript is not fully compatible with JavaScript frameworks like React, Vue, and Angular** which assume full knowledge of the DOM. Both Bootstrap and the framework may attempt to mutate the same DOM element, resulting in bugs like dropdowns that are stuck in the \"open\" position.\n\nA better alternative for those using this type of frameworks is to use a framework-specific package **instead of** the Bootstrap JavaScript. Here are some of the most popular options:\n\n- React: [React Bootstrap](https://react-bootstrap.github.io/)\n- Vue: [BootstrapVue](https://bootstrap-vue.org/) (currently only supports Vue 2 and Bootstrap 4)\n- Angular: [ng-bootstrap](https://ng-bootstrap.github.io/)\n\n## Using Bootstrap as a module\n\n{{< callout >}}\n**Try it yourself!** Download the source code and working demo for using Bootstrap as an ES module from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/sass-js-esm). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/sass-js-esm?file=index.html).\n{{< /callout >}}\n\nWe provide a version of Bootstrap built as `ESM` (`bootstrap.esm.js` and `bootstrap.esm.min.js`) which allows you to use Bootstrap as a module in the browser, if your [targeted browsers support it](https://caniuse.com/es6-module).\n\n```html\n<script type=\"module\">\n  import { Toast } from 'bootstrap.esm.min.js'\n\n  Array.from(document.querySelectorAll('.toast'))\n    .forEach(toastNode => new Toast(toastNode))\n</script>\n```\n\nCompared to JS bundlers, using ESM in the browser requires you to use the full path and filename instead of the module name. [Read more about JS modules in the browser.](https://v8.dev/features/modules#specifiers) That's why we use `'bootstrap.esm.min.js'` instead of `'bootstrap'` above. However, this is further complicated by our Popper dependency, which imports Popper into our JavaScript like so:\n\n<!-- eslint-skip -->\n```js\nimport * as Popper from \"@popperjs/core\"\n```\n\nIf you try this as-is, you'll see an error in the console like the following:\n\n```text\nUncaught TypeError: Failed to resolve module specifier \"@popperjs/core\". Relative references must start with either \"/\", \"./\", or \"../\".\n```\n\nTo fix this, you can use an `importmap` to resolve the arbitrary module names to complete paths. If your [targeted browsers](https://caniuse.com/?search=importmap) do not support `importmap`, you'll need to use the [es-module-shims](https://github.com/guybedford/es-module-shims) project. Here's how it works for Bootstrap and Popper:\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"{{< param \"cdn.css\" >}}\" rel=\"stylesheet\" integrity=\"{{< param \"cdn.css_hash\" >}}\" crossorigin=\"anonymous\">\n    <title>Hello, modularity!</title>\n  </head>\n  <body>\n    <h1>Hello, modularity!</h1>\n    <button id=\"popoverButton\" type=\"button\" class=\"btn btn-primary btn-lg\" class=\"btn btn-lg btn-danger\" data-bs-toggle=\"popover\" title=\"ESM in Browser\" data-bs-content=\"Bang!\">Custom popover</button>\n\n    <script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js\" crossorigin=\"anonymous\"></script>\n    <script type=\"importmap\">\n    {\n      \"imports\": {\n        \"@popperjs/core\": \"{{< param \"cdn.popper\" >}}\",\n        \"bootstrap\": \"https://cdn.jsdelivr.net/npm/bootstrap@{{< param \"current_version\" >}}/dist/js/bootstrap.esm.min.js\"\n      }\n    }\n    </script>\n    <script type=\"module\">\n      import * as bootstrap from 'bootstrap'\n\n      new bootstrap.Popover(document.getElementById('popoverButton'))\n    </script>\n  </body>\n</html>\n```\n\n## Dependencies\n\nSome plugins and CSS components depend on other plugins. If you include plugins individually, make sure to check for these dependencies in the docs.\n\nOur dropdowns, popovers, and tooltips also depend on [Popper](https://popper.js.org/).\n\n## Data attributes\n\nNearly all Bootstrap plugins can be enabled and configured through HTML alone with data attributes (our preferred way of using JavaScript functionality). Be sure to **only use one set of data attributes on a single element** (e.g., you cannot trigger a tooltip and modal from the same button.)\n\n{{< markdown >}}\n{{< partial \"js-data-attributes.md\" >}}\n{{< /markdown >}}\n\n## Selectors\n\nWe use the native `querySelector` and `querySelectorAll` methods to query DOM elements for performance reasons, so you must use [valid selectors](https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier). If you use special selectors like `collapse:Example`, be sure to escape them.\n\n## Events\n\nBootstrap provides custom events for most plugins' unique actions. Generally, these come in an infinitive and past participle form - where the infinitive (ex. `show`) is triggered at the start of an event, and its past participle form (ex. `shown`) is triggered on the completion of an action.\n\nAll infinitive events provide [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) functionality. This provides the ability to stop the execution of an action before it starts. Returning false from an event handler will also automatically call `preventDefault()`.\n\n```js\nconst myModal = document.querySelector('#myModal')\n\nmyModal.addEventListener('show.bs.modal', event => {\n  if (!data) {\n    return event.preventDefault() // stops modal from being shown\n  }\n})\n```\n\n## Programmatic API\n\nAll constructors accept an optional options object or nothing (which initiates a plugin with its default behavior):\n\n```js\nconst myModalEl = document.querySelector('#myModal')\n\nconst modal = new bootstrap.Modal(myModalEl) // initialized with defaults\n\nconst configObject = { keyboard: false }\nconst modal1 = new bootstrap.Modal(myModalEl, configObject) // initialized with no keyboard\n```\n\nIf you'd like to get a particular plugin instance, each plugin exposes a `getInstance` method. For example, to retrieve an instance directly from an element:\n\n```js\nbootstrap.Popover.getInstance(myPopoverEl)\n```\n\nThis method will return `null` if an instance is not initiated over the requested element.\n\nAlternatively, `getOrCreateInstance` can be used to get the instance associated with a DOM element, or create a new one in case it wasn't initialized.\n\n```js\nbootstrap.Popover.getOrCreateInstance(myPopoverEl, configObject)\n```\n\nIn case an instance wasn't initialized, it may accept and use an optional configuration object as second argument.\n\n### CSS selectors in constructors\n\nIn addition to the `getInstance` and `getOrCreateInstance` methods, all plugin constructors can accept a DOM element or a valid [CSS selector](#selectors) as the first argument. Plugin elements are found with the `querySelector` method since our plugins only support a single element.\n\n```js\nconst modal = new bootstrap.Modal('#myModal')\nconst dropdown = new bootstrap.Dropdown('[data-bs-toggle=\"dropdown\"]')\nconst offcanvas = bootstrap.Offcanvas.getInstance('#myOffcanvas')\nconst alert = bootstrap.Alert.getOrCreateInstance('#myAlert')\n```\n\n### Asynchronous functions and transitions\n\nAll programmatic API methods are **asynchronous** and return to the caller once the transition is started, but **before it ends**. In order to execute an action once the transition is complete, you can listen to the corresponding event.\n\n```js\nconst myCollapseEl = document.querySelector('#myCollapse')\n\nmyCollapseEl.addEventListener('shown.bs.collapse', event => {\n  // Action to execute once the collapsible area is expanded\n})\n```\n\nIn addition, a method call on a **transitioning component will be ignored**.\n\n```js\nconst myCarouselEl = document.querySelector('#myCarousel')\nconst carousel = bootstrap.Carousel.getInstance(myCarouselEl) // Retrieve a Carousel instance\n\nmyCarouselEl.addEventListener('slid.bs.carousel', event => {\n  carousel.to('2') // Will slide to the slide 2 as soon as the transition to slide 1 is finished\n})\n\ncarousel.to('1') // Will start sliding to the slide 1 and returns to the caller\ncarousel.to('2') // !! Will be ignored, as the transition to the slide 1 is not finished !!\n```\n\n#### `dispose` method\n\nWhile it may seem correct to use the `dispose` method immediately after `hide()`, it will lead to incorrect results. Here's an example of the problem use:\n\n```js\nconst myModal = document.querySelector('#myModal')\nmyModal.hide() // it is asynchronous\n\nmyModal.addEventListener('shown.bs.hidden', event => {\n  myModal.dispose()\n})\n```\n\n### Default settings\n\nYou can change the default settings for a plugin by modifying the plugin's `Constructor.Default` object:\n\n```js\n// changes default for the modal plugin's `keyboard` option to false\nbootstrap.Modal.Default.keyboard = false\n```\n\n## Methods and properties\n\nEvery Bootstrap plugin exposes the following methods and static properties.\n\n{{< bs-table \"table\" >}}\n| Method | Description |\n| --- | --- |\n| `dispose` | Destroys an element's modal. (Removes stored data on the DOM element) |\n| `getInstance` | *Static* method which allows you to get the modal instance associated with a DOM element. |\n| `getOrCreateInstance` | *Static* method which allows you to get the modal instance associated with a DOM element, or create a new one in case it wasn't initialized. |\n{{< /bs-table >}}\n\n{{< bs-table \"table\" >}}\n| Static property | Description |\n| --- | --- |\n| `NAME` | Returns the plugin name. (Example: `bootstrap.Tooltip.NAME`) |\n| `VERSION` | The version of each of Bootstrap's plugins can be accessed via the `VERSION` property of the plugin's constructor (Example: `bootstrap.Tooltip.VERSION`) |\n{{< /bs-table >}}\n\n## Sanitizer\n\nTooltips and Popovers use our built-in sanitizer to sanitize options which accept HTML.\n\nThe default `allowList` value is the following:\n\n```js\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\nconst DefaultAllowlist = {\n  // Global attributes allowed on any supplied element below.\n  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n  a: ['target', 'href', 'title', 'rel'],\n  area: [],\n  b: [],\n  br: [],\n  col: [],\n  code: [],\n  div: [],\n  em: [],\n  hr: [],\n  h1: [],\n  h2: [],\n  h3: [],\n  h4: [],\n  h5: [],\n  h6: [],\n  i: [],\n  img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n  li: [],\n  ol: [],\n  p: [],\n  pre: [],\n  s: [],\n  small: [],\n  span: [],\n  sub: [],\n  sup: [],\n  strong: [],\n  u: [],\n  ul: []\n}\n```\n\nIf you want to add new values to this default `allowList` you can do the following:\n\n```js\nconst myDefaultAllowList = bootstrap.Tooltip.Default.allowList\n\n// To allow table elements\nmyDefaultAllowList.table = []\n\n// To allow td elements and data-bs-option attributes on td elements\nmyDefaultAllowList.td = ['data-bs-option']\n\n// You can push your custom regex to validate your attributes.\n// Be careful about your regular expressions being too lax\nconst myCustomRegex = /^data-my-app-[\\w-]+/\nmyDefaultAllowList['*'].push(myCustomRegex)\n```\n\nIf you want to bypass our sanitizer because you prefer to use a dedicated library, for example [DOMPurify](https://www.npmjs.com/package/dompurify), you should do the following:\n\n```js\nconst yourTooltipEl = document.querySelector('#yourTooltip')\nconst tooltip = new bootstrap.Tooltip(yourTooltipEl, {\n  sanitizeFn(content) {\n    return DOMPurify.sanitize(content)\n  }\n})\n```\n\n## Optionally using jQuery\n\n**You don't need jQuery in Bootstrap 5**, but it's still possible to use our components with jQuery. If Bootstrap detects `jQuery` in the `window` object, it'll add all of our components in jQuery's plugin system. This allows you to do the following:\n\n```js\n$('[data-bs-toggle=\"tooltip\"]').tooltip() // to enable tooltips, with default configuration\n\n$('[data-bs-toggle=\"tooltip\"]').tooltip({ boundary: 'clippingParents', customClass: 'myClass' }) // to initialize tooltips with given configuration\n\n$('#myTooltip').tooltip('show') // to trigger `show` method\n```\n\nThe same goes for our other components.\n\n### No conflict\n\nSometimes it is necessary to use Bootstrap plugins with other UI frameworks. In these circumstances, namespace collisions can occasionally occur. If this happens, you may call `.noConflict` on the plugin you wish to revert the value of.\n\n```js\nconst bootstrapButton = $.fn.button.noConflict() // return $.fn.button to previously assigned value\n$.fn.bootstrapBtn = bootstrapButton // give $().bootstrapBtn the Bootstrap functionality\n```\n\nBootstrap does not officially support third-party JavaScript libraries like Prototype or jQuery UI. Despite `.noConflict` and namespaced events, there may be compatibility problems that you need to fix on your own.\n\n### jQuery events\n\nBootstrap will detect jQuery if `jQuery` is present in the `window` object and there is no `data-bs-no-jquery` attribute set on `<body>`. If jQuery is found, Bootstrap will emit events thanks to jQuery's event system. So if you want to listen to Bootstrap's events, you'll have to use the jQuery methods (`.on`, `.one`) instead of `addEventListener`.\n\n```js\n$('#myTab a').on('shown.bs.tab', () => {\n  // do something...\n})\n```\n\n## Disabled JavaScript\n\nBootstrap's plugins have no special fallback when JavaScript is disabled. If you care about the user experience in this case, use [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript) to explain the situation (and how to re-enable JavaScript) to your users, and/or add your own custom fallbacks.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/parcel.md",
    "content": "---\nlayout: docs\ntitle: \"Bootstrap & Parcel\"\ndescription: The official guide for how to include and bundle Bootstrap's CSS and JavaScript in your project using Parcel.\ngroup: getting-started\ntoc: true\nthumbnail: guides/bootstrap-parcel@2x.png\n---\n\n<img class=\"mb-4 img-fluid rounded-3\" srcset=\"/docs/{{< param docs_version >}}/assets/img/guides/bootstrap-parcel.png, /docs/{{< param docs_version >}}/assets/img/guides/bootstrap-parcel@2x.png 2x\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/bootstrap-parcel.png\" width=\"2000\" height=\"1000\" alt=\"\">\n\n{{< callout >}}\n**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/parcel). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/parcel?file=index.html) but not run it because Parcel isn't currently supported there.\n{{< /callout >}}\n\n## Setup\n\nWe're building a Parcel project with Bootstrap from scratch, so there are some prerequisites and up front steps before we can really get started. This guide requires you to have Node.js installed and some familiarity with the terminal.\n\n1. **Create a project folder and setup npm.** We'll create the `my-project` folder and initialize npm with the `-y` argument to avoid it asking us all the interactive questions.\n\n   ```sh\n   mkdir my-project && cd my-project\n   npm init -y\n   ```\n\n2. **Install Parcel.** Unlike our Webpack guide, there's only a single build tool dependency here. Parcel will automatically install language transformers (like Sass) as it detects them. We use `--save-dev` to signal that this dependency is only for development use and not for production.\n\n   ```sh\n   npm i --save-dev parcel\n   ```\n\n3. **Install Bootstrap.** Now we can install Bootstrap. We'll also install Popper since our dropdowns, popovers, and tooltips depend on it for their positioning. If you don't plan on using those components, you can omit Popper here.\n\n   ```sh\n   npm i --save bootstrap @popperjs/core\n   ```\n\nNow that we have all the necessary dependencies installed, we can get to work creating the project files and importing Bootstrap.\n\n## Project structure\n\nWe've already created the `my-project` folder and initialized npm. Now we'll also create our `src` folder, stylesheet, and JavaScript file to round out the project structure. Run the following from `my-project`, or manually create the folder and file structure shown below.\n\n```sh\nmkdir {src,src/js,src/scss}\ntouch src/index.html src/js/main.js src/scss/styles.scss\n```\n\nWhen you're done, your complete project should look like this:\n\n```text\nmy-project/\n├── src/\n│   ├── js/\n│   │   └── main.js\n│   ├── scss/\n│   │   └── styles.scss\n│   └── index.html\n├── package-lock.json\n└── package.json\n```\n\nAt this point, everything is in the right place, but Parcel needs an HTML page and npm script to start our server.\n\n## Configure Parcel\n\nWith dependencies installed and our project folder ready for us to start coding, we can now configure Parcel and run our project locally. Parcel itself requires no configuration file by design, but we do need an npm script and an HTML file to start our server.\n\n1. **Fill in the `src/index.html` file.** Parcel needs a page to render, so we use our `index.html` page to set up some basic HTML, including our CSS and JavaScript files.\n\n   ```html\n   <!doctype html>\n   <html lang=\"en\">\n     <head>\n       <meta charset=\"utf-8\">\n       <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n       <title>Bootstrap w/ Parcel</title>\n       <link rel=\"stylesheet\" href=\"scss/styles.scss\">\n       <script type=\"module\" src=\"js/main.js\"></script>\n     </head>\n     <body>\n       <div class=\"container py-4 px-3 mx-auto\">\n         <h1>Hello, Bootstrap and Parcel!</h1>\n         <button class=\"btn btn-primary\">Primary button</button>\n       </div>\n     </body>\n   </html>\n   ```\n\n   We're including a little bit of Bootstrap styling here with the `div class=\"container\"` and `<button>` so that we see when Bootstrap's CSS is loaded by Webpack.\n\n   Parcel will automatically detect we're using Sass and install the [Sass Parcel plugin](https://parceljs.org/languages/sass/) to support it. However, if you wish, you can also manually run `npm i --save-dev @parcel/transformer-sass`.\n\n2. **Add the Parcel npm scripts.** Open the `package.json` and add the following `start` script to the `scripts` object. We'll use this script to start our Parcel development server and render the HTML file we created after it's compiled into the `dist` directory.\n\n   ```json\n   {\n      // ...\n      \"scripts\": {\n        \"start\": \"parcel serve src/index.html --public-url / --dist-dir dist\",\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n      },\n      // ...\n   }\n   ```\n\n3. **And finally, we can start Parcel.** From the `my-project` folder in your terminal, run that newly added npm script:\n\n   ```sh\n   npm start\n   ```\n\n   <img class=\"img-fluid\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/parcel-dev-server.png\" alt=\"Parcel dev server running\">\n\nIn the next and final section to this guide, we'll import all of Bootstrap's CSS and JavaScript.\n\n## Import Bootstrap\n\nImporting Bootstrap into Parcel requires two imports, one into our `styles.scss` and one into our `main.js`.\n\n1. **Import Bootstrap's CSS.** Add the following to `src/scss/styles.scss` to import all of Bootstrap's source Sass.\n\n   ```scss\n   // Import all of Bootstrap's CSS\n   @import \"~bootstrap/scss/bootstrap\";\n   ```\n\n   *You can also import our stylesheets individually if you want. [Read our Sass import docs]({{< docsref \"/customize/sass#importing\" >}}) for details.*\n\n2. **Import Bootstrap's JS.** Add the following to `src/js/main.js` to import all of Bootstrap's JS. Popper will be imported automatically through Bootstrap.\n\n   <!-- eslint-skip -->\n   ```js\n   // Import all of Bootstrap's JS\n   import * as bootstrap from 'bootstrap'\n   ```\n\n   You can also import JavaScript plugins individually as needed to keep bundle sizes down:\n\n   <!-- eslint-skip -->\n   ```js\n   import Alert from 'bootstrap/js/dist/alert'\n\n   // or, specify which plugins you need:\n   import { Tooltip, Toast, Popover } from 'bootstrap'\n   ```\n\n   *[Read our JavaScript docs]({{< docsref \"/getting-started/javascript/\" >}}) for more information on how to use Bootstrap's plugins.*\n\n3. **And you're done! 🎉** With Bootstrap's source Sass and JS fully loaded, your local development server should now look like this.\n\n   <img class=\"img-fluid\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/parcel-dev-server-bootstrap.png\" alt=\"Parcel dev server running with Bootstrap\">\n\n   Now you can start adding any Bootstrap components you want to use. Be sure to [check out the complete Parcel example project](https://github.com/twbs/examples/tree/main/parcel) for how to include additional custom Sass and optimize your build by importing only the parts of Bootstrap's CSS and JS that you need.\n\n{{< markdown >}}\n{{< partial \"guide-footer.md\" >}}\n{{< /markdown >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/rfs.md",
    "content": "---\nlayout: docs\ntitle: RFS\ndescription: Bootstrap's resizing engine responsively scales common CSS properties to better utilize available space across viewports and devices.\ngroup: getting-started\ntoc: true\n---\n\n## What is RFS?\n\nBootstrap's side project [RFS](https://github.com/twbs/rfs/tree/{{< param \"rfs_version\" >}}) is a unit resizing engine which was initially developed to resize font sizes (hence its abbreviation for Responsive Font Sizes). Nowadays RFS is capable of rescaling most CSS properties with unit values like `margin`, `padding`, `border-radius`, or even `box-shadow`.\n\nThe mechanism automatically calculates the appropriate values based on the dimensions of the browser viewport. It will be compiled into `calc()` functions with a mix of `rem` and viewport units to enable the responsive scaling behavior.\n\n## Using RFS\n\nThe mixins are included in Bootstrap and are available once you include Bootstrap's `scss`. RFS can also be [installed standalone](https://github.com/twbs/rfs/tree/{{< param \"rfs_version\" >}}#installation) if needed.\n\n### Using the mixins\n\nThe `rfs()` mixin has shorthands for `font-size`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, and `padding-left`. See the example below for source Sass and compiled CSS.\n\n```scss\n.title {\n  @include font-size(4rem);\n}\n```\n\n```css\n.title {\n  font-size: calc(1.525rem + 3.3vw);\n}\n\n@media (min-width: 1200px) {\n  .title {\n    font-size: 4rem;\n  }\n}\n```\n\nAny other property can be passed to the `rfs()` mixin like this:\n\n```scss\n.selector {\n  @include rfs(4rem, border-radius);\n}\n```\n\n`!important` can also just be added to whatever value you want:\n\n```scss\n.selector {\n  @include padding(2.5rem !important);\n}\n```\n\n### Using the functions\n\nWhen you don't want to use the includes, there are also two functions:\n\n- `rfs-value()` converts a value into a `rem` value if a `px` value is passed, in other cases it returns the same result.\n- `rfs-fluid-value()` returns the fluid version of a value if the property needs rescaling.\n\nIn this example, we use one of Bootstrap's built-in [responsive breakpoint mixins]({{< docsref \"/layout/breakpoints\" >}}) to only apply styling below the `lg` breakpoint.\n\n```scss\n.selector {\n  @include media-breakpoint-down(lg) {\n    padding: rfs-fluid-value(2rem);\n    font-size: rfs-fluid-value(1.125rem);\n  }\n}\n```\n\n```css\n@media (max-width: 991.98px) {\n  .selector {\n    padding: calc(1.325rem + 0.9vw);\n    font-size: 1.125rem; /* 1.125rem is small enough, so RFS won't rescale this */\n  }\n}\n```\n\n## Extended documentation\n\nRFS is a separate project under the Bootstrap organization. More about RFS and its configuration can be found on its [GitHub repository](https://github.com/twbs/rfs/tree/{{< param \"rfs_version\" >}}).\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/rtl.md",
    "content": "---\nlayout: docs\ntitle: RTL\ndescription: Learn how to enable support for right-to-left text in Bootstrap across our layout, components, and utilities.\ngroup: getting-started\ntoc: true\n---\n\n## Get familiar\n\nWe recommend getting familiar with Bootstrap first by reading through our [Getting Started Introduction page]({{< docsref \"/getting-started/introduction\" >}}). Once you've run through it, continue reading here for how to enable RTL.\n\nYou may also want to read up on [the RTLCSS project](https://rtlcss.com/), as it powers our approach to RTL.\n\n{{< callout warning >}}\n### Experimental feature\n\nThe RTL feature is still **experimental** and will probably evolve according to user feedback. Spotted something or have an improvement to suggest? [Open an issue]({{< param repo >}}/issues/new/choose), we'd love to get your insights.\n{{< /callout >}}\n\n## Required HTML\n\nThere are two strict requirements for enabling RTL in Bootstrap-powered pages.\n\n1. Set `dir=\"rtl\"` on the `<html>` element.\n2. Add an appropriate `lang` attribute, like `lang=\"ar\"`, on the `<html>` element.\n\nFrom there, you'll need to include an RTL version of our CSS. For example, here's the stylesheet for our compiled and minified CSS with RTL enabled:\n\n```html\n<link rel=\"stylesheet\" href=\"{{< param \"cdn.css_rtl\" >}}\" integrity=\"{{< param \"cdn.css_rtl_hash\" >}}\" crossorigin=\"anonymous\">\n```\n\n### Starter template\n\nYou can see the above requirements reflected in this modified RTL starter template.\n\n```html\n<!doctype html>\n<html lang=\"ar\" dir=\"rtl\">\n  <head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n    <!-- Bootstrap CSS -->\n    <link rel=\"stylesheet\" href=\"{{< param \"cdn.css_rtl\" >}}\" integrity=\"{{< param \"cdn.css_rtl_hash\" >}}\" crossorigin=\"anonymous\">\n\n    <title>مرحبًا بالعالم!</title>\n  </head>\n  <body>\n    <h1>مرحبًا بالعالم!</h1>\n\n    <!-- Optional JavaScript; choose one of the two! -->\n\n    <!-- Option 1: Bootstrap Bundle with Popper -->\n    <script src=\"{{< param \"cdn.js_bundle\" >}}\" integrity=\"{{< param \"cdn.js_bundle_hash\" >}}\" crossorigin=\"anonymous\"></script>\n\n    <!-- Option 2: Separate Popper and Bootstrap JS -->\n    <!--\n    <script src=\"{{< param \"cdn.popper\" >}}\" integrity=\"{{< param \"cdn.popper_hash\" >}}\" crossorigin=\"anonymous\"></script>\n    <script src=\"{{< param \"cdn.js\" >}}\" integrity=\"{{< param \"cdn.js_hash\" >}}\" crossorigin=\"anonymous\"></script>\n    -->\n  </body>\n</html>\n```\n\n### RTL examples\n\nGet started with one of our several [RTL examples]({{< docsref \"/examples/#rtl\" >}}).\n\n## Approach\n\nOur approach to building RTL support into Bootstrap comes with two important decisions that impact how we write and use our CSS:\n\n1. **First, we decided to build it with the [RTLCSS](https://rtlcss.com/) project.** This gives us some powerful features for managing changes and overrides when moving from LTR to RTL. It also allows us to build two versions of Bootstrap from one codebase.\n\n2. **Second, we've renamed a handful of directional classes to adopt a logical properties approach.** Most of you have already interacted with logical properties thanks to our flex utilities—they replace direction properties like `left` and `right` in favor `start` and `end`. That makes the class names and values appropriate for LTR and RTL without any overhead.\n\n  For example, instead of `.ml-3` for `margin-left`, use `.ms-3`.\n\nWorking with RTL, through our source Sass or compiled CSS, shouldn't be much different from our default LTR though.\n\n## Customize from source\n\nWhen it comes to [customization]({{< docsref \"/customize/sass\" >}}), the preferred way is to take advantage of variables, maps, and mixins. This approach works the same for RTL, even if it's post-processed from the compiled files, thanks to [how RTLCSS works](https://rtlcss.com/learn/getting-started/why-rtlcss/).\n\n### Custom RTL values\n\nUsing [RTLCSS value directives](https://rtlcss.com/learn/usage-guide/value-directives/), you can make a variable output a different value for RTL. For example, to decrease the weight for `$font-weight-bold` throughout the codebase, you may use the `/*rtl: {value}*/` syntax:\n\n```scss\n$font-weight-bold: 700 #{/* rtl:600 */} !default;\n```\n\nWhich would output to the following for our default CSS and RTL CSS:\n\n```css\n/* bootstrap.css */\ndt {\n  font-weight: 700 /* rtl:600 */;\n}\n\n/* bootstrap.rtl.css */\ndt {\n  font-weight: 600;\n}\n```\n\n### Alternative font stack\n\nIn the case you're using a custom font, be aware that not all fonts support the non-Latin alphabet. To switch from Pan-European to Arabic family, you may need to use `/*rtl:insert: {value}*/` in your font stack to modify the names of font families.\n\nFor example, to switch from `Helvetica Neue` font for LTR to `Helvetica Neue Arabic` for RTL, your Sass code could look like this:\n\n```scss\n$font-family-sans-serif:\n  Helvetica Neue #{\"/* rtl:insert:Arabic */\"},\n  // Cross-platform generic font family (default user interface font)\n  system-ui,\n  // Safari for macOS and iOS (San Francisco)\n  -apple-system,\n  // Chrome < 56 for macOS (San Francisco)\n  BlinkMacSystemFont,\n  // Windows\n  \"Segoe UI\",\n  // Android\n  Roboto,\n  // Basic web fallback\n  Arial,\n  // Linux\n  \"Noto Sans\",\n  // Sans serif fallback\n  sans-serif,\n  // Emoji fonts\n  \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\" !default;\n```\n\n### LTR and RTL at the same time\n\nNeed both LTR and RTL on the same page? Thanks to [RTLCSS String Maps](https://rtlcss.com/learn/usage-guide/string-map/), this is pretty straightforward. Wrap your `@import`s with a class, and set a custom rename rule for RTLCSS:\n\n```scss\n/* rtl:begin:options: {\n  \"autoRename\": true,\n  \"stringMap\":[ {\n    \"name\": \"ltr-rtl\",\n    \"priority\": 100,\n    \"search\": [\"ltr\"],\n    \"replace\": [\"rtl\"],\n    \"options\": {\n      \"scope\": \"*\",\n      \"ignoreCase\": false\n    }\n  } ]\n} */\n.ltr {\n  @import \"../node_modules/bootstrap/scss/bootstrap\";\n}\n/*rtl:end:options*/\n```\n\nAfter running Sass then RTLCSS, each selector in your CSS files will be prepended by `.ltr`, and `.rtl` for RTL files. Now you're able to use both files on the same page, and simply use `.ltr` or `.rtl` on your components wrappers to use one or the other direction.\n\n{{< callout warning >}}\n#### Edge cases and known limitations\n\nWhile this approach is understandable, please pay attention to the following:\n\n1. When switching `.ltr` and `.rtl`, make sure you add `dir` and `lang` attributes accordingly.\n2. Loading both files can be a real performance bottleneck: consider some [optimization]({{< docsref \"/customize/optimize\" >}}), and maybe try to [load one of those files asynchronously](https://www.filamentgroup.com/lab/load-css-simpler/).\n3. Nesting styles this way will prevent our `form-validation-state()` mixin from working as intended, thus require you tweak it a bit by yourself. [See #31223](https://github.com/twbs/bootstrap/issues/31223).\n{{< /callout >}}\n\n## The breadcrumb case\n\nThe [breadcrumb separator]({{< docsref \"/components/breadcrumb\" >}}/#changing-the-separator) is the only case requiring its own brand-new variable— namely `$breadcrumb-divider-flipped` —defaulting to `$breadcrumb-divider`.\n\n## Additional resources\n\n- [RTLCSS](https://rtlcss.com/)\n- [RTL Styling 101](https://rtlstyling.com/posts/rtl-styling)\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/vite.md",
    "content": "---\nlayout: docs\ntitle: \"Bootstrap & Vite\"\ndescription: The official guide for how to include and bundle Bootstrap's CSS and JavaScript in your project using Vite.\ngroup: getting-started\ntoc: true\nthumbnail: guides/bootstrap-vite@2x.png\n---\n\n<img class=\"mb-4 img-fluid rounded-3\" srcset=\"/docs/{{< param docs_version >}}/assets/img/guides/bootstrap-vite.png, /docs/{{< param docs_version >}}/assets/img/guides/bootstrap-vite@2x.png 2x\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/bootstrap-vite.png\" width=\"2000\" height=\"1000\" alt=\"\">\n\n{{< callout >}}\n**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/vite). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/vite?file=index.html) for live editing.\n{{< /callout >}}\n\n## Setup\n\nWe're building a Vite project with Bootstrap from scratch, so there are some prerequisites and up front steps before we can really get started. This guide requires you to have Node.js installed and some familiarity with the terminal.\n\n1. **Create a project folder and setup npm.** We'll create the `my-project` folder and initialize npm with the `-y` argument to avoid it asking us all the interactive questions.\n\n   ```sh\n   mkdir my-project && cd my-project\n   npm init -y\n   ```\n\n2. **Install Vite.** Unlike our Webpack guide, there’s only a single build tool dependency here. We use `--save-dev` to signal that this dependency is only for development use and not for production.\n\n   ```sh\n   npm i --save-dev vite\n   ```\n\n3. **Install Bootstrap.** Now we can install Bootstrap. We'll also install Popper since our dropdowns, popovers, and tooltips depend on it for their positioning. If you don't plan on using those components, you can omit Popper here.\n\n   ```sh\n   npm i --save bootstrap @popperjs/core\n   ```\n4. **Install additional dependency.** In addition to Vite and Bootstrap, we need another dependency (Sass) to properly import and bundle Bootstrap's CSS.\n\n   ```sh\n   npm i --save-dev sass\n   ```\n\nNow that we have all the necessary dependencies installed and setup, we can get to work creating the project files and importing Bootstrap.\n\n## Project structure\n\nWe've already created the `my-project` folder and initialized npm. Now we'll also create our `src` folder, stylesheet, and JavaScript file to round out the project structure. Run the following from `my-project`, or manually create the folder and file structure shown below.\n\n```sh\nmkdir {src,src/js,src/scss}\ntouch src/index.html src/js/main.js src/scss/styles.scss vite.config.js\n```\n\nWhen you're done, your complete project should look like this:\n\n```text\nmy-project/\n├── src/\n│   ├── js/\n│   │   └── main.js\n│   └── scss/\n│   |   └── styles.scss\n|   └── index.html\n├── package-lock.json\n├── package.json\n└── vite.config.js\n```\n\nAt this point, everything is in the right place, but Vite won't work because we haven't filled in our `vite.config.js` yet.\n\n## Configure Vite\n\nWith dependencies installed and our project folder ready for us to start coding, we can now configure Vite and run our project locally.\n\n1. **Open `vite.config.js` in your editor.** Since it's blank, we'll need to add some boilerplate config to it so we can start our server. This part of the config tells Vite where to look for our project's JavaScript and how the development server should behave (pulling from the `src` folder with hot reload).\n\n   <!-- eslint-skip -->\n   ```js\n   const path = require('path')\n\n   export default {\n     root: path.resolve(__dirname, 'src'),\n     server: {\n       port: 8080,\n       hot: true\n     }\n   }\n   ```\n\n2. **Next we fill in `src/index.html`.** This is the HTML page Vite will load in the browser to utilize the bundled CSS and JS we'll add to it in later steps.\n\n   ```html\n   <!doctype html>\n   <html lang=\"en\">\n     <head>\n       <meta charset=\"utf-8\">\n       <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n       <title>Bootstrap w/ Vite</title>\n     </head>\n     <body>\n       <div class=\"container py-4 px-3 mx-auto\">\n         <h1>Hello, Bootstrap and Vite!</h1>\n         <button class=\"btn btn-primary\">Primary button</button>\n       </div>\n       <script type=\"module\" src=\"./js/main.js\"></script>\n     </body>\n   </html>\n   ```\n\n   We're including a little bit of Bootstrap styling here with the `div class=\"container\"` and `<button>` so that we see when Bootstrap's CSS is loaded by Vite.\n\n3. **Now we need an npm script to run Vite.** Open `package.json` and add the `start` script shown below (you should already have the test script). We'll use this script to start our local Vite dev server.\n\n   ```json\n   {\n     // ...\n     \"scripts\": {\n       \"start\": \"vite\",\n       \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n     },\n     // ...\n   }\n   ```\n\n4. **And finally, we can start Vite.** From the `my-project` folder in your terminal, run that newly added npm script:\n\n   ```sh\n   npm start\n   ```\n\n   <img class=\"img-fluid\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/vite-dev-server.png\" alt=\"Vite dev server running\">\n\nIn the next and final section to this guide, we’ll import all of Bootstrap’s CSS and JavaScript.\n\n## Import Bootstrap\n\n1. **Set up Bootstrap's Sass import in `vite.config.js`.** Your configuration file is now complete and should match the snippet below. The only new part here is the `resolve` section—we use this to add an alias to our source files inside `node_modules` to keep imports as simple as possible.\n\n   <!-- eslint-skip -->\n   ```js\n   const path = require('path')\n\n   export default {\n     root: path.resolve(__dirname, 'src'),\n     resolve: {\n       alias: {\n         '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'),\n       }\n     },\n     server: {\n       port: 8080,\n       hot: true\n     }\n   }\n   ```\n\n2. **Now, let's import Bootstrap's CSS.** Add the following to `src/scss/styles.scss` to import all of Bootstrap's source Sass.\n\n   ```scss\n   // Import all of Bootstrap's CSS\n   @import \"~bootstrap/scss/bootstrap\";\n   ```\n\n   *You can also import our stylesheets individually if you want. [Read our Sass import docs]({{< docsref \"/customize/sass#importing\" >}}) for details.*\n\n3. **Next we load the CSS and import Bootstrap's JavaScript.** Add the following to `src/js/main.js` to load the CSS and import all of Bootstrap's JS. Popper will be imported automatically through Bootstrap.\n\n   <!-- eslint-skip -->\n   ```js\n   // Import our custom CSS\n   import '../scss/styles.scss'\n\n   // Import all of Bootstrap's JS\n   import * as bootstrap from 'bootstrap'\n   ```\n\n   You can also import JavaScript plugins individually as needed to keep bundle sizes down:\n\n   <!-- eslint-skip -->\n   ```js\n   import Alert from 'bootstrap/js/dist/alert';\n\n   // or, specify which plugins you need:\n   import { Tooltip, Toast, Popover } from 'bootstrap';\n   ```\n\n   *[Read our JavaScript docs]({{< docsref \"/getting-started/javascript/\" >}}) for more information on how to use Bootstrap's plugins.*\n\n4. **And you're done! 🎉** With Bootstrap's source Sass and JS fully loaded, your local development server should now look like this.\n\n   <img class=\"img-fluid\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/vite-dev-server-bootstrap.png\" alt=\"Vite dev server running with Bootstrap\">\n\n   Now you can start adding any Bootstrap components you want to use. Be sure to [check out the complete Vite example project](https://github.com/twbs/examples/tree/main/vite) for how to include additional custom Sass and optimize your build by importing only the parts of Bootstrap's CSS and JS that you need.\n\n{{< markdown >}}\n{{< partial \"guide-footer.md\" >}}\n{{< /markdown >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/getting-started/webpack.md",
    "content": "---\nlayout: docs\ntitle: \"Bootstrap & Webpack\"\ndescription: The official guide for how to include and bundle Bootstrap's CSS and JavaScript in your project using Webpack.\ngroup: getting-started\ntoc: true\nthumbnail: guides/bootstrap-webpack@2x.png\n---\n\n<img class=\"mb-4 img-fluid rounded-3\" srcset=\"/docs/{{< param docs_version >}}/assets/img/guides/bootstrap-webpack.png, /docs/{{< param docs_version >}}/assets/img/guides/bootstrap-webpack@2x.png 2x\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/bootstrap-webpack.png\" width=\"2000\" height=\"1000\" alt=\"\">\n\n{{< callout >}}\n**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/webpack). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/webpack?file=index.html) for live editing.\n{{< /callout >}}\n\n## Setup\n\nWe're building a Webpack project with Bootstrap from scratch, so there are some prerequisites and up front steps before we can really get started. This guide requires you to have Node.js installed and some familiarity with the terminal.\n\n1. **Create a project folder and setup npm.** We'll create the `my-project` folder and initialize npm with the `-y` argument to avoid it asking us all the interactive questions.\n\n   ```sh\n   mkdir my-project && cd my-project\n   npm init -y\n   ```\n\n2. **Install Webpack.** Next we need to install our Webpack development dependencies: `webpack` for the core of Webpack, `webpack-cli` so we can run Webpack commands from the terminal, and `webpack-dev-server` so we can run a local development server. We use `--save-dev` to signal that these dependencies are only for development use and not for production.\n\n   ```sh\n   npm i --save-dev webpack webpack-cli webpack-dev-server\n   ```\n\n3. **Install Bootstrap.** Now we can install Bootstrap. We'll also install Popper since our dropdowns, popovers, and tooltips depend on it for their positioning. If you don't plan on using those components, you can omit Popper here.\n\n   ```sh\n   npm i --save bootstrap @popperjs/core\n   ```\n\n4. **Install additional dependencies.** In addition to Webpack and Bootstrap, we need a few more dependencies to properly import and bundle Bootstrap's CSS and JS with Webpack. These include Sass, some loaders, and Autoprefixer.\n\n   ```sh\n   npm i --save-dev autoprefixer css-loader postcss-loader sass sass-loader style-loader\n   ```\n\nNow that we have all the necessary dependencies installed, we can get to work creating the project files and importing Bootstrap.\n\n## Project structure\n\nWe've already created the `my-project` folder and initialized npm. Now we'll also create our `src` and `dist` folders to round out the project structure. Run the following from `my-project`, or manually create the folder and file structure shown below.\n\n```sh\nmkdir {dist,src,src/js,src/scss}\ntouch dist/index.html src/js/main.js src/scss/styles.scss webpack.config.js\n```\n\nWhen you're done, your complete project should look like this:\n\n```text\nmy-project/\n├── dist/\n│   └── index.html\n├── src/\n│   ├── js/\n│   │   └── main.js\n│   └── scss/\n│       └── styles.scss\n├── package-lock.json\n├── package.json\n└── webpack.config.js\n```\n\nAt this point, everything is in the right place, but Webpack won't work because we haven't filled in our `webpack.config.js` yet.\n\n## Configure Webpack\n\nWith dependencies installed and our project folder ready for us to start coding, we can now configure Webpack and run our project locally.\n\n1. **Open `webpack.config.js` in your editor.** Since it's blank, we'll need to add some boilerplate config to it so we can start our server. This part of the config tells Webpack where to look for our project's JavaScript, where to output the compiled code to (`dist`), and how the development server should behave (pulling from the `dist` folder with hot reload).\n\n   ```js\n   const path = require('path')\n\n   module.exports = {\n     entry: './src/js/main.js',\n     output: {\n       filename: 'main.js',\n       path: path.resolve(__dirname, 'dist')\n     },\n     devServer: {\n       static: path.resolve(__dirname, 'dist'),\n       port: 8080,\n       hot: true\n     }\n   }\n   ```\n\n2. **Next we fill in our `dist/index.html`.** This is the HTML page Webpack will load in the browser to utilize the bundled CSS and JS we'll add to it in later steps. Before we can do that, we have to give it something to render and include the `output` JS from the previous step.\n\n   ```html\n   <!doctype html>\n   <html lang=\"en\">\n     <head>\n       <meta charset=\"utf-8\">\n       <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n       <title>Bootstrap w/ Webpack</title>\n     </head>\n     <body>\n       <div class=\"container py-4 px-3 mx-auto\">\n         <h1>Hello, Bootstrap and Webpack!</h1>\n         <button class=\"btn btn-primary\">Primary button</button>\n       </div>\n       <script src=\"./main.js\"></script>\n     </body>\n   </html>\n   ```\n\n   We're including a little bit of Bootstrap styling here with the `div class=\"container\"` and `<button>` so that we see when Bootstrap's CSS is loaded by Webpack.\n\n3. **Now we need an npm script to run Webpack.** Open `package.json` and add the `start` script shown below (you should already have the test script). We'll use this script to start our local Webpack dev server.\n\n   ```json\n   {\n     // ...\n     \"scripts\": {\n       \"start\": \"webpack serve --mode development\",\n       \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n     },\n     // ...\n   }\n   ```\n\n4. **And finally, we can start Webpack.** From the `my-project` folder in your terminal, run that newly added npm script:\n\n   ```sh\n   npm start\n   ```\n\n   <img class=\"img-fluid\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/webpack-dev-server.png\" alt=\"Webpack dev server running\">\n\nIn the next and final section to this guide, we'll set up the Webpack loaders and import all of Bootstrap's CSS and JavaScript.\n\n## Import Bootstrap\n\nImporting Bootstrap into Webpack requires the loaders we installed in the first section. We've installed them with npm, but now Webpack needs to be configured to use them.\n\n1. **Set up the loaders in `webpack.config.js`.** Your configuration file is now complete and should match the snippet below. The only new part here is the `module` section.\n\n   ```js\n   const path = require('path')\n\n   module.exports = {\n     entry: './src/js/main.js',\n     output: {\n       filename: 'main.js',\n       path: path.resolve(__dirname, 'dist')\n     },\n     devServer: {\n       static: path.resolve(__dirname, 'dist'),\n       port: 8080,\n       hot: true\n     },\n     module: {\n       rules: [\n         {\n           test: /\\.(scss)$/,\n           use: [\n             {\n               loader: 'style-loader'\n             },\n             {\n               loader: 'css-loader'\n             },\n             {\n               loader: 'postcss-loader',\n               options: {\n                 postcssOptions: {\n                   plugins: () => [\n                     require('autoprefixer')\n                   ]\n                 }\n               }\n             },\n             {\n               loader: 'sass-loader'\n             }\n           ]\n         }\n       ]\n     }\n   }\n   ```\n\n   Here's a recap of why we need all these loaders. `style-loader` injects the CSS into a `<style>` element in the `<head>` of the HTML page, `css-loader` helps with using `@import` and `url()`, `postcss-loader` is required for Autoprefixer, and `sass-loader` allows us to use Sass.\n\n2. **Now, let's import Bootstrap's CSS.** Add the following to `src/scss/styles.scss` to import all of Bootstrap's source Sass.\n\n   ```scss\n   // Import all of Bootstrap's CSS\n   @import \"~bootstrap/scss/bootstrap\";\n   ```\n\n   *You can also import our stylesheets individually if you want. [Read our Sass import docs]({{< docsref \"/customize/sass#importing\" >}}) for details.*\n\n3. **Next we load the CSS and import Bootstrap's JavaScript.** Add the following to `src/js/main.js` to load the CSS and import all of Bootstrap's JS. Popper will be imported automatically through Bootstrap.\n\n   <!-- eslint-skip -->\n   ```js\n   // Import our custom CSS\n   import '../scss/styles.scss'\n\n   // Import all of Bootstrap's JS\n   import * as bootstrap from 'bootstrap'\n   ```\n\n   You can also import JavaScript plugins individually as needed to keep bundle sizes down:\n\n   <!-- eslint-skip -->\n   ```js\n   import Alert from 'bootstrap/js/dist/alert'\n\n   // or, specify which plugins you need:\n   import { Tooltip, Toast, Popover } from 'bootstrap'\n   ```\n\n   *[Read our JavaScript docs]({{< docsref \"/getting-started/javascript/\" >}}) for more information on how to use Bootstrap's plugins.*\n\n4. **And you're done! 🎉** With Bootstrap's source Sass and JS fully loaded, your local development server should now look like this.\n\n   <img class=\"img-fluid\" src=\"/docs/{{< param docs_version >}}/assets/img/guides/webpack-dev-server-bootstrap.png\" alt=\"Webpack dev server running with Bootstrap\">\n\n   Now you can start adding any Bootstrap components you want to use. Be sure to [check out the complete Webpack example project](https://github.com/twbs/examples/tree/main/webpack) for how to include additional custom Sass and optimize your build by importing only the parts of Bootstrap's CSS and JS that you need.\n\n## Production optimizations\n\nDepending on your setup, you may want to implement some additional security and speed optimizations useful for running the project in production. Note that these optimizations are not applied on [the Webpack example project](https://github.com/twbs/examples/tree/main/webpack) and are up to you to implement.\n\n### Extracting CSS\n\nThe `style-loader` we configured above conveniently emits CSS into the bundle so that manually loading a CSS file in `dist/index.html` isn't necessary. This approach may not work with a strict Content Security Policy, however, and it may become a bottleneck in your application due to the large bundle size.\n\nTo separate the CSS so that we can load it directly from `dist/index.html`, use the `mini-css-extract-loader` Webpack plugin.\n\nFirst, install the plugin:\n\n```sh\nnpm install --save-dev mini-css-extract-plugin\n```\n\nThen instantiate and use the plugin in the Webpack configuration:\n\n```diff\n--- a/webpack/webpack.config.js\n+++ b/webpack/webpack.config.js\n@@ -1,8 +1,10 @@\n+const miniCssExtractPlugin = require('mini-css-extract-plugin')\n const path = require('path')\n \n module.exports = {\n   mode: 'development',\n   entry: './src/js/main.js',\n+  plugins: [new miniCssExtractPlugin()],\n   output: {\n     filename: \"main.js\",\n     path: path.resolve(__dirname, \"dist\"),\n@@ -18,8 +20,8 @@ module.exports = {\n         test: /\\.(scss)$/,\n         use: [\n           {\n-            // Adds CSS to the DOM by injecting a `<style>` tag\n-            loader: 'style-loader'\n+            // Extracts CSS for each JS file that includes CSS\n+            loader: miniCssExtractPlugin.loader\n           },\n           {\n```\n\nAfter running `npm run build` again, there will be a new file `dist/main.css`, which will contain all of the CSS imported by `src/js/main.js`. If you view `dist/index.html` in your browser now, the style will be missing, as it is now in `dist/main.css`. You can include the generated CSS in `dist/index.html` like this:\n\n```diff\n--- a/webpack/dist/index.html\n+++ b/webpack/dist/index.html\n@@ -3,6 +3,7 @@\n   <head>\n     <meta charset=\"utf-8\">\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n+    <link rel=\"stylesheet\" href=\"./main.css\">\n     <title>Bootstrap w/ Webpack</title>\n   </head>\n   <body>\n```\n\n### Extracting SVG files\n\nBootstrap's CSS includes multiple references to SVG files via inline `data:` URIs. If you define a Content Security Policy for your project that blocks `data:` URIs for images, then these SVG files will not load. You can get around this problem by extracting the inline SVG files using Webpack's asset modules feature.\n\nConfigure Webpack to extract inline SVG files like this:\n\n```diff\n--- a/webpack/webpack.config.js\n+++ b/webpack/webpack.config.js\n@@ -16,6 +16,14 @@ module.exports = {\n   },\n   module: {\n     rules: [\n+      {\n+        mimetype: 'image/svg+xml',\n+        scheme: 'data',\n+        type: 'asset/resource',\n+        generator: {\n+          filename: 'icons/[hash].svg'\n+        }\n+      },\n       {\n         test: /\\.(scss)$/,\n         use: [\n```\n\nAfter running `npm run build` again, you'll find the SVG files extracted into `dist/icons` and properly referenced from CSS.\n\n{{< markdown >}}\n{{< partial \"guide-footer.md\" >}}\n{{< /markdown >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/clearfix.md",
    "content": "---\nlayout: docs\ntitle: Clearfix\ndescription: Quickly and easily clear floated content within a container by adding a clearfix utility.\ngroup: helpers\naliases: \"/docs/5.2/helpers/\"\n---\n\nEasily clear `float`s by adding `.clearfix` **to the parent element**. Can also be used as a mixin.\n\nUse in HTML:\n\n```html\n<div class=\"clearfix\">...</div>\n```\n\nThe mixin source code:\n\n{{< scss-docs name=\"clearfix\" file=\"scss/mixins/_clearfix.scss\" >}}\n\nUse the mixin in SCSS:\n\n```scss\n.element {\n  @include clearfix;\n}\n```\n\nThe following example shows how the clearfix can be used. Without the clearfix the wrapping div would not span around the buttons which would cause a broken layout.\n\n{{< example >}}\n<div class=\"bg-info clearfix\">\n  <button type=\"button\" class=\"btn btn-secondary float-start\">Example Button floated left</button>\n  <button type=\"button\" class=\"btn btn-secondary float-end\">Example Button floated right</button>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/color-background.md",
    "content": "---\nlayout: docs\ntitle: Color & background\ndescription: Set a background color with contrasting foreground color.\ngroup: helpers\ntoc: true\nadded: \"5.2\"\n---\n\n## Overview\n\n{{< added-in \"5.2.0\" >}}\n\nColor and background helpers combine the power of our [`.text-*` utilities]({{< docsref \"/utilities/colors\" >}}) and [`.bg-*` utilities]({{< docsref \"/utilities/background\" >}}) in one class. Using our Sass `color-contrast()` function, we automatically determine a contrasting `color` for a particular `background-color`.\n\n{{< callout warning >}}\n**Heads up!** There's currently no support for a CSS-native `color-contrast` function, so we use our own via Sass. This means that customizing our theme colors via CSS variables may cause color contrast issues with these utilities.\n{{< /callout >}}\n\n{{< example >}}\n{{< text-bg.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"text-bg-{{ .name }} p-3\">{{ .name | title }} with contrasting color</div>\n{{- end -}}\n{{< /text-bg.inline >}}\n{{< /example >}}\n\n## With components\n\nUse them in place of combined `.text-*` and `.bg-*` classes, like on [badges]({{< docsref \"/components/badge#background-colors\" >}}):\n\n{{< example >}}\n<span class=\"badge text-bg-primary\">Primary</span>\n<span class=\"badge text-bg-info\">Info</span>\n{{< /example >}}\n\nOr on [cards]({{< docsref \"/components/card#background-and-color\" >}}):\n\n{{< example >}}\n<div class=\"card text-bg-primary mb-3\" style=\"max-width: 18rem;\">\n  <div class=\"card-header\">Header</div>\n  <div class=\"card-body\">\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n</div>\n<div class=\"card text-bg-info mb-3\" style=\"max-width: 18rem;\">\n  <div class=\"card-header\">Header</div>\n  <div class=\"card-body\">\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n  </div>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/colored-links.md",
    "content": "---\nlayout: docs\ntitle: Colored links\ndescription: Colored links with hover states\ngroup: helpers\ntoc: false\n---\n\nYou can use the `.link-*` classes to colorize links. Unlike the [`.text-*` classes]({{< docsref \"/utilities/colors\" >}}), these classes have a `:hover` and `:focus` state.\n\n{{< example >}}\n{{< colored-links.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<a href=\"#\" class=\"link-{{ .name }}\">{{ .name | title }} link</a>\n{{- end -}}\n{{< /colored-links.inline >}}\n{{< /example >}}\n\n{{< callout info >}}\nSome of the link styles use a relatively light foreground color, and should only be used on a dark background in order to have sufficient contrast.\n{{< /callout >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/position.md",
    "content": "---\nlayout: docs\ntitle: Position\ndescription: Use these helpers for quickly configuring the position of an element.\ngroup: helpers\ntoc: true\n---\n\n## Fixed top\n\nPosition an element at the top of the viewport, from edge to edge. Be sure you understand the ramifications of fixed position in your project; you may need to add additional CSS.\n\n```html\n<div class=\"fixed-top\">...</div>\n```\n\n## Fixed bottom\n\nPosition an element at the bottom of the viewport, from edge to edge. Be sure you understand the ramifications of fixed position in your project; you may need to add additional CSS.\n\n```html\n<div class=\"fixed-bottom\">...</div>\n```\n\n## Sticky top\n\nPosition an element at the top of the viewport, from edge to edge, but only after you scroll past it.\n\n```html\n<div class=\"sticky-top\">...</div>\n```\n\n## Responsive sticky top\n\nResponsive variations also exist for `.sticky-top` utility.\n\n```html\n<div class=\"sticky-sm-top\">Stick to the top on viewports sized SM (small) or wider</div>\n<div class=\"sticky-md-top\">Stick to the top on viewports sized MD (medium) or wider</div>\n<div class=\"sticky-lg-top\">Stick to the top on viewports sized LG (large) or wider</div>\n<div class=\"sticky-xl-top\">Stick to the top on viewports sized XL (extra-large) or wider</div>\n<div class=\"sticky-xxl-top\">Stick to the top on viewports sized XXL (extra-extra-large) or wider</div>\n```\n\n## Sticky bottom\n\nPosition an element at the bottom of the viewport, from edge to edge, but only after you scroll past it.\n\n```html\n<div class=\"sticky-bottom\">...</div>\n```\n\n## Responsive sticky bottom\n\nResponsive variations also exist for `.sticky-bottom` utility.\n\n```html\n<div class=\"sticky-sm-bottom\">Stick to the bottom on viewports sized SM (small) or wider</div>\n<div class=\"sticky-md-bottom\">Stick to the bottom on viewports sized MD (medium) or wider</div>\n<div class=\"sticky-lg-bottom\">Stick to the bottom on viewports sized LG (large) or wider</div>\n<div class=\"sticky-xl-bottom\">Stick to the bottom on viewports sized XL (extra-large) or wider</div>\n<div class=\"sticky-xxl-bottom\">Stick to the bottom on viewports sized XXL (extra-extra-large) or wider</div>\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/ratio.md",
    "content": "---\nlayout: docs\ntitle: Ratios\ndescription: Use generated pseudo elements to make an element maintain the aspect ratio of your choosing. Perfect for responsively handling video or slideshow embeds based on the width of the parent.\ngroup: helpers\ntoc: true\n---\n\n## About\n\nUse the ratio helper to manage the aspect ratios of external content like `<iframe>`s, `<embed>`s, `<video>`s, and `<object>`s. These helpers also can be used on any standard HTML child element (e.g., a `<div>` or `<img>`). Styles are applied from the parent `.ratio` class directly to the child.\n\nAspect ratios are declared in a Sass map and included in each class via CSS variable, which also allows [custom aspect ratios](#custom-ratios).\n\n{{< callout info >}}\n**Pro-Tip!** You don't need `frameborder=\"0\"` on your `<iframe>`s as we override that for you in [Reboot]({{< docsref \"/content/reboot\" >}}).\n{{< /callout >}}\n\n## Example\n\nWrap any embed, like an `<iframe>`, in a parent element with `.ratio` and an aspect ratio class. The immediate child element is automatically sized thanks to our universal selector `.ratio > *`.\n\n{{< example >}}\n<div class=\"ratio ratio-16x9\">\n  <iframe src=\"https://www.youtube.com/embed/zpOULjyy-n8?rel=0\" title=\"YouTube video\" allowfullscreen></iframe>\n</div>\n{{< /example >}}\n\n## Aspect ratios\n\nAspect ratios can be customized with modifier classes. By default the following ratio classes are provided:\n\n{{< example class=\"bd-example-ratios\" >}}\n<div class=\"ratio ratio-1x1\">\n  <div>1x1</div>\n</div>\n<div class=\"ratio ratio-4x3\">\n  <div>4x3</div>\n</div>\n<div class=\"ratio ratio-16x9\">\n  <div>16x9</div>\n</div>\n<div class=\"ratio ratio-21x9\">\n  <div>21x9</div>\n</div>\n{{< /example >}}\n\n## Custom ratios\n\nEach `.ratio-*` class includes a CSS custom property (or CSS variable) in the selector. You can override this CSS variable to create custom aspect ratios on the fly with some quick math on your part.\n\nFor example, to create a 2x1 aspect ratio, set `--bs-aspect-ratio: 50%` on the `.ratio`.\n\n{{< example class=\"bd-example-ratios\" >}}\n<div class=\"ratio\" style=\"--bs-aspect-ratio: 50%;\">\n  <div>2x1</div>\n</div>\n{{< /example >}}\n\nThis CSS variable makes it easy to modify the aspect ratio across breakpoints. The following is 4x3 to start, but changes to a custom 2x1 at the medium breakpoint.\n\n```scss\n.ratio-4x3 {\n  @include media-breakpoint-up(md) {\n    --bs-aspect-ratio: 50%; // 2x1\n  }\n}\n```\n\n{{< example class=\"bd-example-ratios bd-example-ratios-breakpoint\" >}}\n<div class=\"ratio ratio-4x3\">\n  <div>4x3, then 2x1</div>\n</div>\n{{< /example >}}\n\n\n## Sass map\n\nWithin `_variables.scss`, you can change the aspect ratios you want to use. Here's our default `$ratio-aspect-ratios` map. Modify the map as you like and recompile your Sass to put them to use.\n\n{{< scss-docs name=\"aspect-ratios\" file=\"scss/_variables.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/stacks.md",
    "content": "---\nlayout: docs\ntitle: Stacks\ndescription: Shorthand helpers that build on top of our flexbox utilities to make component layout faster and easier than ever.\ngroup: helpers\ntoc: true\nadded: \"5.1\"\n---\n\nStacks offer a shortcut for applying a number of flexbox properties to quickly and easily create layouts in Bootstrap. All credit for the concept and implementation goes to the open source [Pylon project](https://almonk.github.io/pylon/).\n\n{{< callout warning >}}\nHeads up! Support for gap utilities with flexbox was recently added to Safari, so consider verifying your intended browser support. Grid layout should have no issues. [Read more](https://caniuse.com/flexbox-gap).\n{{< /callout >}}\n\n## Vertical\n\nUse `.vstack` to create vertical layouts. Stacked items are full-width by default. Use `.gap-*` utilities to add space between items.\n\n{{< example >}}\n<div class=\"vstack gap-3\">\n  <div class=\"bg-light border\">First item</div>\n  <div class=\"bg-light border\">Second item</div>\n  <div class=\"bg-light border\">Third item</div>\n</div>\n{{< /example >}}\n\n## Horizontal\n\nUse `.hstack` for horizontal layouts. Stacked items are vertically centered by default and only take up their necessary width. Use `.gap-*` utilities to add space between items.\n\n{{< example >}}\n<div class=\"hstack gap-3\">\n  <div class=\"bg-light border\">First item</div>\n  <div class=\"bg-light border\">Second item</div>\n  <div class=\"bg-light border\">Third item</div>\n</div>\n{{< /example >}}\n\nUsing horizontal margin utilities like `.ms-auto` as spacers:\n\n{{< example >}}\n<div class=\"hstack gap-3\">\n  <div class=\"bg-light border\">First item</div>\n  <div class=\"bg-light border ms-auto\">Second item</div>\n  <div class=\"bg-light border\">Third item</div>\n</div>\n{{< /example >}}\n\nAnd with [vertical rules]({{< docsref \"/helpers/vertical-rule\" >}}):\n\n{{< example >}}\n<div class=\"hstack gap-3\">\n  <div class=\"bg-light border\">First item</div>\n  <div class=\"bg-light border ms-auto\">Second item</div>\n  <div class=\"vr\"></div>\n  <div class=\"bg-light border\">Third item</div>\n</div>\n{{< /example >}}\n\n## Examples\n\nUse `.vstack` to stack buttons and other elements:\n\n{{< example >}}\n<div class=\"vstack gap-2 col-md-5 mx-auto\">\n  <button type=\"button\" class=\"btn btn-secondary\">Save changes</button>\n  <button type=\"button\" class=\"btn btn-outline-secondary\">Cancel</button>\n</div>\n{{< /example >}}\n\nCreate an inline form with `.hstack`:\n\n{{< example >}}\n<div class=\"hstack gap-3\">\n  <input class=\"form-control me-auto\" type=\"text\" placeholder=\"Add your item here...\" aria-label=\"Add your item here...\">\n  <button type=\"button\" class=\"btn btn-secondary\">Submit</button>\n  <div class=\"vr\"></div>\n  <button type=\"button\" class=\"btn btn-outline-danger\">Reset</button>\n</div>\n{{< /example >}}\n\n## Sass\n\n{{< scss-docs name=\"stacks\" file=\"scss/helpers/_stacks.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/stretched-link.md",
    "content": "---\nlayout: docs\ntitle: Stretched link\ndescription: Make any HTML element or Bootstrap component clickable by \"stretching\" a nested link via CSS.\ngroup: helpers\n---\n\nAdd `.stretched-link` to a link to make its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block) clickable via a `::after` pseudo element. In most cases, this means that an element with `position: relative;` that contains a link with the `.stretched-link` class is clickable. Please note given [how CSS `position` works](https://www.w3.org/TR/CSS21/visuren.html#propdef-position), `.stretched-link` cannot be mixed with most table elements.\n\nCards have `position: relative` by default in Bootstrap, so in this case you can safely add the `.stretched-link` class to a link in the card without any other HTML changes.\n\nMultiple links and tap targets are not recommended with stretched links. However, some `position` and `z-index` styles can help should this be required.\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"false\" title=\"Card image cap\" >}}\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card with stretched link</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n    <a href=\"#\" class=\"btn btn-primary stretched-link\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\nMost custom components do not have `position: relative` by default, so we need to add the `.position-relative` here to prevent the link from stretching outside the parent element.\n\n{{< example >}}\n<div class=\"d-flex position-relative\">\n  {{< placeholder width=\"144\" height=\"144\" class=\"flex-shrink-0 me-3\" text=\"false\" title=\"Generic placeholder image\" >}}\n  <div>\n    <h5 class=\"mt-0\">Custom component with stretched link</h5>\n    <p>This is some placeholder content for the custom component. It is intended to mimic what some real-world content would look like, and we're using it here to give the component a bit of body and size.</p>\n    <a href=\"#\" class=\"stretched-link\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example >}}\n<div class=\"row g-0 bg-light position-relative\">\n  <div class=\"col-md-6 mb-md-0 p-md-4\">\n    {{< placeholder width=\"100%\" height=\"200\" class=\"w-100\" text=\"false\" title=\"Generic placeholder image\" >}}\n  </div>\n  <div class=\"col-md-6 p-4 ps-md-0\">\n    <h5 class=\"mt-0\">Columns with stretched link</h5>\n    <p>Another instance of placeholder content for this other custom component. It is intended to mimic what some real-world content would look like, and we're using it here to give the component a bit of body and size.</p>\n    <a href=\"#\" class=\"stretched-link\">Go somewhere</a>\n  </div>\n</div>\n{{< /example >}}\n\n## Identifying the containing block\n\nIf the stretched link doesn't seem to work, the [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block) will probably be the cause. The following CSS properties will make an element the containing block:\n\n- A `position` value other than `static`\n- A `transform` or `perspective` value other than `none`\n- A `will-change` value of `transform` or `perspective`\n- A `filter` value other than `none` or a `will-change` value of `filter` (only works on Firefox)\n\n{{< example >}}\n<div class=\"card\" style=\"width: 18rem;\">\n  {{< placeholder width=\"100%\" height=\"180\" class=\"card-img-top\" text=\"false\" title=\"Card image cap\" >}}\n  <div class=\"card-body\">\n    <h5 class=\"card-title\">Card with stretched links</h5>\n    <p class=\"card-text\">Some quick example text to build on the card title and make up the bulk of the card's content.</p>\n    <p class=\"card-text\">\n      <a href=\"#\" class=\"stretched-link text-danger\" style=\"position: relative;\">Stretched link will not work here, because <code>position: relative</code> is added to the link</a>\n    </p>\n    <p class=\"card-text bg-light\" style=\"transform: rotate(0);\">\n      This <a href=\"#\" class=\"text-warning stretched-link\">stretched link</a> will only be spread over the <code>p</code>-tag, because a transform is applied to it.\n    </p>\n  </div>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/text-truncation.md",
    "content": "---\nlayout: docs\ntitle: Text truncation\ndescription: Truncate long strings of text with an ellipsis.\ngroup: helpers\ntoc: false\n---\n\nFor longer content, you can add a `.text-truncate` class to truncate the text with an ellipsis. **Requires `display: inline-block` or `display: block`.**\n\n{{< example >}}\n<!-- Block level -->\n<div class=\"row\">\n  <div class=\"col-2 text-truncate\">\n    This text is quite long, and will be truncated once displayed.\n  </div>\n</div>\n\n<!-- Inline level -->\n<span class=\"d-inline-block text-truncate\" style=\"max-width: 150px;\">\n  This text is quite long, and will be truncated once displayed.\n</span>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/vertical-rule.md",
    "content": "---\nlayout: docs\ntitle: Vertical rule\ndescription: Use the custom vertical rule helper to create vertical dividers like the `<hr>` element.\ngroup: helpers\ntoc: true\nadded: \"5.1\"\n---\n\n## How it works\n\nVertical rules are inspired by the `<hr>` element, allowing you to create vertical dividers in common layouts. They're styled just like `<hr>` elements:\n\n- They're `1px` wide\n- They have `min-height` of `1em`\n- Their color is set via `currentColor` and `opacity`\n\nCustomize them with additional styles as needed.\n\n## Example\n\n{{< example >}}\n<div class=\"vr\"></div>\n{{< /example >}}\n\nVertical rules scale their height in flex layouts:\n\n{{< example >}}\n<div class=\"d-flex\" style=\"height: 200px;\">\n  <div class=\"vr\"></div>\n</div>\n{{< /example >}}\n\n## With stacks\n\nThey can also be used in [stacks]({{< docsref \"/helpers/stacks\" >}}):\n\n{{< example >}}\n<div class=\"hstack gap-3\">\n  <div class=\"bg-light border\">First item</div>\n  <div class=\"bg-light border ms-auto\">Second item</div>\n  <div class=\"vr\"></div>\n  <div class=\"bg-light border\">Third item</div>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/helpers/visually-hidden.md",
    "content": "---\nlayout: docs\ntitle: Visually hidden\ndescription: Use these helpers to visually hide elements but keep them accessible to assistive technologies.\ngroup: helpers\naliases: \"/docs/5.2/helpers/screen-readers/\"\n---\n\nVisually hide an element while still allowing it to be exposed to assistive technologies (such as screen readers) with `.visually-hidden`. Use `.visually-hidden-focusable` to visually hide an element by default, but to display it when it's focused (e.g. by a keyboard-only user). `.visually-hidden-focusable` can also be applied to a container–thanks to `:focus-within`, the container will be displayed when any child element of the container receives focus.\n\n{{< example >}}\n<h2 class=\"visually-hidden\">Title for screen readers</h2>\n<a class=\"visually-hidden-focusable\" href=\"#content\">Skip to main content</a>\n<div class=\"visually-hidden-focusable\">A container with a <a href=\"#\">focusable element</a>.</div>\n{{< /example >}}\n\nBoth `visually-hidden` and `visually-hidden-focusable` can also be used as mixins.\n\n```scss\n// Usage as a mixin\n\n.visually-hidden-title {\n  @include visually-hidden;\n}\n\n.skip-navigation {\n  @include visually-hidden-focusable;\n}\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/breakpoints.md",
    "content": "---\nlayout: docs\ntitle: Breakpoints\ndescription: Breakpoints are customizable widths that determine how your responsive layout behaves across device or viewport sizes in Bootstrap.\ngroup: layout\naliases: \"/docs/5.2/layout/\"\ntoc: true\n---\n\n## Core concepts\n\n- **Breakpoints are the building blocks of responsive design.** Use them to control when your layout can be adapted at a particular viewport or device size.\n\n- **Use media queries to architect your CSS by breakpoint.** Media queries are a feature of CSS that allow you to conditionally apply styles based on a set of browser and operating system parameters. We most commonly use `min-width` in our media queries.\n\n- **Mobile first, responsive design is the goal.** Bootstrap's CSS aims to apply the bare minimum of styles to make a layout work at the smallest breakpoint, and then layers on styles to adjust that design for larger devices. This optimizes your CSS, improves rendering time, and provides a great experience for your visitors.\n\n## Available breakpoints\n\nBootstrap includes six default breakpoints, sometimes referred to as _grid tiers_, for building responsively. These breakpoints can be customized if you're using our source Sass files.\n\n{{< bs-table \"table\" >}}\n| Breakpoint | Class infix | Dimensions |\n| --- | --- | --- |\n| Extra small | <em>None</em> |&lt;576px |\n| Small | `sm` | &ge;576px |\n| Medium | `md` | &ge;768px |\n| Large | `lg` | &ge;992px |\n| Extra large | `xl` | &ge;1200px |\n| Extra extra large | `xxl` | &ge;1400px |\n{{< /bs-table >}}\n\n\nEach breakpoint was chosen to comfortably hold containers whose widths are multiples of 12. Breakpoints are also representative of a subset of common device sizes and viewport dimensions—they don't specifically target every use case or device. Instead, the ranges provide a strong and consistent foundation to build on for nearly any device.\n\nThese breakpoints are customizable via Sass—you'll find them in a Sass map in our `_variables.scss` stylesheet.\n\n{{< scss-docs name=\"grid-breakpoints\" file=\"scss/_variables.scss\" >}}\n\nFor more information and examples on how to modify our Sass maps and variables, please refer to [the Sass section of the Grid documentation]({{< docsref \"/layout/grid#sass\" >}}).\n\n## Media queries\n\nSince Bootstrap is developed to be mobile first, we use a handful of [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries) to create sensible breakpoints for our layouts and interfaces. These breakpoints are mostly based on minimum viewport widths and allow us to scale up elements as the viewport changes.\n\n### Min-width\n\nBootstrap primarily uses the following media query ranges—or breakpoints—in our source Sass files for our layout, grid system, and components.\n\n```scss\n// Source mixins\n\n// No media query necessary for xs breakpoint as it's effectively `@media (min-width: 0) { ... }`\n@include media-breakpoint-up(sm) { ... }\n@include media-breakpoint-up(md) { ... }\n@include media-breakpoint-up(lg) { ... }\n@include media-breakpoint-up(xl) { ... }\n@include media-breakpoint-up(xxl) { ... }\n\n// Usage\n\n// Example: Hide starting at `min-width: 0`, and then show at the `sm` breakpoint\n.custom-class {\n  display: none;\n}\n@include media-breakpoint-up(sm) {\n  .custom-class {\n    display: block;\n  }\n}\n```\n\nThese Sass mixins translate in our compiled CSS using the values declared in our Sass variables. For example:\n\n```scss\n// X-Small devices (portrait phones, less than 576px)\n// No media query for `xs` since this is the default in Bootstrap\n\n// Small devices (landscape phones, 576px and up)\n@media (min-width: 576px) { ... }\n\n// Medium devices (tablets, 768px and up)\n@media (min-width: 768px) { ... }\n\n// Large devices (desktops, 992px and up)\n@media (min-width: 992px) { ... }\n\n// X-Large devices (large desktops, 1200px and up)\n@media (min-width: 1200px) { ... }\n\n// XX-Large devices (larger desktops, 1400px and up)\n@media (min-width: 1400px) { ... }\n```\n\n### Max-width\n\nWe occasionally use media queries that go in the other direction (the given screen size *or smaller*):\n\n```scss\n// No media query necessary for xs breakpoint as it's effectively `@media (max-width: 0) { ... }`\n@include media-breakpoint-down(sm) { ... }\n@include media-breakpoint-down(md) { ... }\n@include media-breakpoint-down(lg) { ... }\n@include media-breakpoint-down(xl) { ... }\n@include media-breakpoint-down(xxl) { ... }\n\n// Example: Style from medium breakpoint and down\n@include media-breakpoint-down(md) {\n  .custom-class {\n    display: block;\n  }\n}\n```\n\nThese mixins take those declared breakpoints, subtract `.02px` from them, and use them as our `max-width` values. For example:\n\n```scss\n// `xs` returns only a ruleset and no media query\n// ... { ... }\n\n// `sm` applies to x-small devices (portrait phones, less than 576px)\n@media (max-width: 575.98px) { ... }\n\n// `md` applies to small devices (landscape phones, less than 768px)\n@media (max-width: 767.98px) { ... }\n\n// `lg` applies to medium devices (tablets, less than 992px)\n@media (max-width: 991.98px) { ... }\n\n// `xl` applies to large devices (desktops, less than 1200px)\n@media (max-width: 1199.98px) { ... }\n\n// `xxl` applies to x-large devices (large desktops, less than 1400px)\n@media (max-width: 1399.98px) { ... }\n```\n\n{{< callout warning >}}\n{{< partial \"callout-info-mediaqueries-breakpoints.md\" >}}\n{{< /callout >}}\n\n### Single breakpoint\n\nThere are also media queries and mixins for targeting a single segment of screen sizes using the minimum and maximum breakpoint widths.\n\n```scss\n@include media-breakpoint-only(xs) { ... }\n@include media-breakpoint-only(sm) { ... }\n@include media-breakpoint-only(md) { ... }\n@include media-breakpoint-only(lg) { ... }\n@include media-breakpoint-only(xl) { ... }\n@include media-breakpoint-only(xxl) { ... }\n```\n\nFor example the `@include media-breakpoint-only(md) { ... }` will result in :\n\n```scss\n@media (min-width: 768px) and (max-width: 991.98px) { ... }\n```\n\n### Between breakpoints\n\nSimilarly, media queries may span multiple breakpoint widths:\n\n```scss\n@include media-breakpoint-between(md, xl) { ... }\n```\n\nWhich results in:\n\n```scss\n// Example\n// Apply styles starting from medium devices and up to extra large devices\n@media (min-width: 768px) and (max-width: 1199.98px) { ... }\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/columns.md",
    "content": "---\nlayout: docs\ntitle: Columns\ndescription: Learn how to modify columns with a handful of options for alignment, ordering, and offsetting thanks to our flexbox grid system. Plus, see how to use column classes to manage widths of non-grid elements.\ngroup: layout\ntoc: true\n---\n\n{{< callout info >}}\n**Heads up!** Be sure to [read the Grid page]({{< docsref \"/layout/grid\" >}}) first before diving into how to modify and customize your grid columns.\n{{< /callout >}}\n\n## How they work\n\n- **Columns build on the grid's flexbox architecture.** Flexbox means we have options for changing individual columns and [modifying groups of columns at the row level]({{< docsref \"/layout/grid#row-columns\" >}}). You choose how columns grow, shrink, or otherwise change.\n\n- **When building grid layouts, all content goes in columns.** The hierarchy of Bootstrap's grid goes from [container]({{< docsref \"/layout/containers\" >}}) to row to column to your content. On rare occasions, you may combine content and column, but be aware there can be unintended consequences.\n\n- **Bootstrap includes predefined classes for creating fast, responsive layouts.** With [six breakpoints]({{< docsref \"/layout/breakpoints\" >}}) and a dozen columns at each grid tier, we have dozens of classes already built for you to create your desired layouts. This can be disabled via Sass if you wish.\n\n## Alignment\n\nUse flexbox alignment utilities to vertically and horizontally align columns.\n\n### Vertical alignment\n\n{{< example class=\"bd-example-row bd-example-row-flex-cols\" >}}\n<div class=\"container text-center\">\n  <div class=\"row align-items-start\">\n    <div class=\"col\">\n      One of three columns\n    </div>\n    <div class=\"col\">\n      One of three columns\n    </div>\n    <div class=\"col\">\n      One of three columns\n    </div>\n  </div>\n  <div class=\"row align-items-center\">\n    <div class=\"col\">\n      One of three columns\n    </div>\n    <div class=\"col\">\n      One of three columns\n    </div>\n    <div class=\"col\">\n      One of three columns\n    </div>\n  </div>\n  <div class=\"row align-items-end\">\n    <div class=\"col\">\n      One of three columns\n    </div>\n    <div class=\"col\">\n      One of three columns\n    </div>\n    <div class=\"col\">\n      One of three columns\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-row bd-example-row-flex-cols\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col align-self-start\">\n      One of three columns\n    </div>\n    <div class=\"col align-self-center\">\n      One of three columns\n    </div>\n    <div class=\"col align-self-end\">\n      One of three columns\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Horizontal alignment\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row justify-content-start\">\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n  </div>\n  <div class=\"row justify-content-center\">\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n  </div>\n  <div class=\"row justify-content-end\">\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n  </div>\n  <div class=\"row justify-content-around\">\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n  </div>\n  <div class=\"row justify-content-between\">\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n  </div>\n  <div class=\"row justify-content-evenly\">\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n    <div class=\"col-4\">\n      One of two columns\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Column wrapping\n\nIf more than 12 columns are placed within a single row, each group of extra columns will, as one unit, wrap onto a new line.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container\">\n  <div class=\"row\">\n    <div class=\"col-9\">.col-9</div>\n    <div class=\"col-4\">.col-4<br>Since 9 + 4 = 13 &gt; 12, this 4-column-wide div gets wrapped onto a new line as one contiguous unit.</div>\n    <div class=\"col-6\">.col-6<br>Subsequent columns continue along the new line.</div>\n  </div>\n</div>\n{{< /example >}}\n\n### Column breaks\n\nBreaking columns to a new line in flexbox requires a small hack: add an element with `width: 100%` wherever you want to wrap your columns to a new line. Normally this is accomplished with multiple `.row`s, but not every implementation method can account for this.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-6 col-sm-3\">.col-6 .col-sm-3</div>\n    <div class=\"col-6 col-sm-3\">.col-6 .col-sm-3</div>\n\n    <!-- Force next columns to break to new line -->\n    <div class=\"w-100\"></div>\n\n    <div class=\"col-6 col-sm-3\">.col-6 .col-sm-3</div>\n    <div class=\"col-6 col-sm-3\">.col-6 .col-sm-3</div>\n  </div>\n</div>\n{{< /example >}}\n\nYou may also apply this break at specific breakpoints with our [responsive display utilities]({{< docsref \"/utilities/display\" >}}).\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-6 col-sm-4\">.col-6 .col-sm-4</div>\n    <div class=\"col-6 col-sm-4\">.col-6 .col-sm-4</div>\n\n    <!-- Force next columns to break to new line at md breakpoint and up -->\n    <div class=\"w-100 d-none d-md-block\"></div>\n\n    <div class=\"col-6 col-sm-4\">.col-6 .col-sm-4</div>\n    <div class=\"col-6 col-sm-4\">.col-6 .col-sm-4</div>\n  </div>\n</div>\n{{< /example >}}\n\n## Reordering\n\n### Order classes\n\nUse `.order-` classes for controlling the **visual order** of your content. These classes are responsive, so you can set the `order` by breakpoint (e.g., `.order-1.order-md-2`). Includes support for `1` through `5` across all six grid tiers.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col\">\n      First in DOM, no order applied\n    </div>\n    <div class=\"col order-5\">\n      Second in DOM, with a larger order\n    </div>\n    <div class=\"col order-1\">\n      Third in DOM, with an order of 1\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nThere are also responsive `.order-first` and `.order-last` classes that change the `order` of an element by applying `order: -1` and `order: 6`, respectively. These classes can also be intermixed with the numbered `.order-*` classes as needed.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col order-last\">\n      First in DOM, ordered last\n    </div>\n    <div class=\"col\">\n      Second in DOM, unordered\n    </div>\n    <div class=\"col order-first\">\n      Third in DOM, ordered first\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Offsetting columns\n\nYou can offset grid columns in two ways: our responsive `.offset-` grid classes and our [margin utilities]({{< docsref \"/utilities/spacing\" >}}). Grid classes are sized to match columns while margins are more useful for quick layouts where the width of the offset is variable.\n\n#### Offset classes\n\nMove columns to the right using `.offset-md-*` classes. These classes increase the left margin of a column by `*` columns. For example, `.offset-md-4` moves `.col-md-4` over four columns.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-md-4\">.col-md-4</div>\n    <div class=\"col-md-4 offset-md-4\">.col-md-4 .offset-md-4</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-md-3 offset-md-3\">.col-md-3 .offset-md-3</div>\n    <div class=\"col-md-3 offset-md-3\">.col-md-3 .offset-md-3</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-md-6 offset-md-3\">.col-md-6 .offset-md-3</div>\n  </div>\n</div>\n{{< /example >}}\n\nIn addition to column clearing at responsive breakpoints, you may need to reset offsets. See this in action in [the grid example]({{< docsref \"/examples/grid\" >}}).\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-sm-5 col-md-6\">.col-sm-5 .col-md-6</div>\n    <div class=\"col-sm-5 offset-sm-2 col-md-6 offset-md-0\">.col-sm-5 .offset-sm-2 .col-md-6 .offset-md-0</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-sm-6 col-md-5 col-lg-6\">.col-sm-6 .col-md-5 .col-lg-6</div>\n    <div class=\"col-sm-6 col-md-5 offset-md-2 col-lg-6 offset-lg-0\">.col-sm-6 .col-md-5 .offset-md-2 .col-lg-6 .offset-lg-0</div>\n  </div>\n</div>\n{{< /example >}}\n\n#### Margin utilities\n\nWith the move to flexbox in v4, you can use margin utilities like `.me-auto` to force sibling columns away from one another.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-md-4\">.col-md-4</div>\n    <div class=\"col-md-4 ms-auto\">.col-md-4 .ms-auto</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-md-3 ms-md-auto\">.col-md-3 .ms-md-auto</div>\n    <div class=\"col-md-3 ms-md-auto\">.col-md-3 .ms-md-auto</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-auto me-auto\">.col-auto .me-auto</div>\n    <div class=\"col-auto\">.col-auto</div>\n  </div>\n</div>\n{{< /example >}}\n\n## Standalone column classes\n\nThe `.col-*` classes can also be used outside a `.row` to give an element a specific width. Whenever column classes are used as non-direct children of a row, the paddings are omitted.\n\n{{< example >}}\n<div class=\"col-3 bg-light p-3 border\">\n  .col-3: width of 25%\n</div>\n<div class=\"col-sm-9 bg-light p-3 border\">\n  .col-sm-9: width of 75% above sm breakpoint\n</div>\n{{< /example >}}\n\nThe classes can be used together with utilities to create responsive floated images. Make sure to wrap the content in a [`.clearfix`]({{< docsref \"/helpers/clearfix\" >}}) wrapper to clear the float if the text is shorter.\n\n{{< example >}}\n<div class=\"clearfix\">\n  {{< placeholder width=\"100%\" height=\"210\" class=\"col-md-6 float-md-end mb-3 ms-md-3\" text=\"Responsive floated image\" >}}\n\n  <p>\n    A paragraph of placeholder text. We're using it here to show the use of the clearfix class. We're adding quite a few meaningless phrases here to demonstrate how the columns interact here with the floated image.\n  </p>\n\n  <p>\n    As you can see the paragraphs gracefully wrap around the floated image. Now imagine how this would look with some actual content in here, rather than just this boring placeholder text that goes on and on, but actually conveys no tangible information at. It simply takes up space and should not really be read.\n  </p>\n\n  <p>\n    And yet, here you are, still persevering in reading this placeholder text, hoping for some more insights, or some hidden easter egg of content. A joke, perhaps. Unfortunately, there's none of that here.\n  </p>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/containers.md",
    "content": "---\nlayout: docs\ntitle: Containers\ndescription: Containers are a fundamental building block of Bootstrap that contain, pad, and align your content within a given device or viewport.\ngroup: layout\ntoc: true\n---\n\n## How they work\n\nContainers are the most basic layout element in Bootstrap and are **required when using our default grid system**. Containers are used to contain, pad, and (sometimes) center the content within them. While containers *can* be nested, most layouts do not require a nested container.\n\nBootstrap comes with three different containers:\n\n- `.container`, which sets a `max-width` at each responsive breakpoint\n- `.container-{breakpoint}`, which is `width: 100%` until the specified breakpoint\n- `.container-fluid`, which is `width: 100%` at all breakpoints\n\nThe table below illustrates how each container's `max-width` compares to the original `.container` and `.container-fluid` across each breakpoint.\n\nSee them in action and compare them in our [Grid example]({{< docsref \"/examples/grid#containers\" >}}).\n\n{{< bs-table \"table\" >}}\n|  | Extra small<div class=\"fw-normal\">&lt;576px</div> | Small<div class=\"fw-normal\">&ge;576px</div> | Medium<div class=\"fw-normal\">&ge;768px</div> | Large<div class=\"fw-normal\">&ge;992px</div> | X-Large<div class=\"fw-normal\">&ge;1200px</div> | XX-Large<div class=\"fw-normal\">&ge;1400px</div> |\n| --- | --- | --- | --- | --- | --- | --- |\n| `.container` | <span class=\"text-muted\">100%</span> | 540px | 720px | 960px | 1140px | 1320px |\n| `.container-sm` | <span class=\"text-muted\">100%</span> | 540px | 720px | 960px | 1140px | 1320px |\n| `.container-md` | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | 720px | 960px | 1140px | 1320px |\n| `.container-lg` | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | 960px | 1140px | 1320px |\n| `.container-xl` | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | 1140px | 1320px |\n| `.container-xxl` | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | 1320px |\n| `.container-fluid` | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> | <span class=\"text-muted\">100%</span> |\n{{< /bs-table >}}\n\n## Default container\n\nOur default `.container` class is a responsive, fixed-width container, meaning its `max-width` changes at each breakpoint.\n\n```html\n<div class=\"container\">\n  <!-- Content here -->\n</div>\n```\n\n## Responsive containers\n\nResponsive containers allow you to specify a class that is 100% wide until the specified breakpoint is reached, after which we apply `max-width`s for each of the higher breakpoints. For example, `.container-sm` is 100% wide to start until the `sm` breakpoint is reached, where it will scale up with `md`, `lg`, `xl`, and `xxl`.\n\n```html\n<div class=\"container-sm\">100% wide until small breakpoint</div>\n<div class=\"container-md\">100% wide until medium breakpoint</div>\n<div class=\"container-lg\">100% wide until large breakpoint</div>\n<div class=\"container-xl\">100% wide until extra large breakpoint</div>\n<div class=\"container-xxl\">100% wide until extra extra large breakpoint</div>\n```\n\n## Fluid containers\n\nUse `.container-fluid` for a full width container, spanning the entire width of the viewport.\n\n```html\n<div class=\"container-fluid\">\n  ...\n</div>\n```\n\n## Sass\n\nAs shown above, Bootstrap generates a series of predefined container classes to help you build the layouts you desire. You may customize these predefined container classes by modifying the Sass map (found in `_variables.scss`) that powers them:\n\n{{< scss-docs name=\"container-max-widths\" file=\"scss/_variables.scss\" >}}\n\nIn addition to customizing the Sass, you can also create your own containers with our Sass mixin.\n\n```scss\n// Source mixin\n@mixin make-container($padding-x: $container-padding-x) {\n  width: 100%;\n  padding-right: $padding-x;\n  padding-left: $padding-x;\n  margin-right: auto;\n  margin-left: auto;\n}\n\n// Usage\n.custom-container {\n  @include make-container();\n}\n```\n\nFor more information and examples on how to modify our Sass maps and variables, please refer to [the Sass section of the Grid documentation]({{< docsref \"/layout/grid#sass\" >}}).\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/css-grid.md",
    "content": "---\nlayout: docs\ntitle: CSS Grid\ndescription: Learn how to enable, use, and customize our alternate layout system built on CSS Grid with examples and code snippets.\ngroup: layout\ntoc: true\nadded: \"5.1\"\n---\n\nBootstrap's default grid system represents the culmination of over a decade of CSS layout techniques, tried and tested by millions of people. But, it was also created without many of the modern CSS features and techniques we're seeing in browsers like the new CSS Grid.\n\n{{< callout warning >}}\n**Heads up—our CSS Grid system is experimental and opt-in as of v5.1.0!** We included it in our documentation's CSS to demonstrate it for you, but it's disabled by default. Keep reading to learn how to enable it in your projects.\n{{< /callout >}}\n\n## How it works\n\nWith Bootstrap 5, we've added the option to enable a separate grid system that's built on CSS Grid, but with a Bootstrap twist. You still get classes you can apply on a whim to build responsive layouts, but with a different approach under the hood.\n\n- **CSS Grid is opt-in.** Disable the default grid system by setting `$enable-grid-classes: false` and enable the CSS Grid by setting `$enable-cssgrid: true`. Then, recompile your Sass.\n\n- **Replace instances of `.row` with `.grid`.** The `.grid` class sets `display: grid` and creates a `grid-template` that you build on with your HTML.\n\n- **Replace `.col-*` classes with `.g-col-*` classes.** This is because our CSS Grid columns use the `grid-column` property instead of `width`.\n\n- **Columns and gutter sizes are set via CSS variables.** Set these on the parent `.grid` and customize however you want, inline or in a stylesheet, with `--bs-columns` and `--bs-gap`.\n\nIn the future, Bootstrap will likely shift to a hybrid solution as the `gap` property has achieved nearly full browser support for flexbox.\n\n## Key differences\n\nCompared to the default grid system:\n\n- Flex utilities don't affect the CSS Grid columns in the same way.\n\n- Gaps replaces gutters. The `gap` property replaces the horizontal `padding` from our default grid system and functions more like `margin`.\n\n- As such, unlike `.row`s, `.grid`s have no negative margins and margin utilities cannot be used to change the grid gutters. Grid gaps are applied horizontally and vertically by default. See the [customizing section](#customizing) for more details.\n\n- Inline and custom styles should be viewed as replacements for modifier classes (e.g., `style=\"--bs-columns: 3;\"` vs `class=\"row-cols-3\"`).\n\n- Nesting works similarly, but may require you to reset your column counts on each instance of a nested `.grid`. See the [nesting section](#nesting) for details.\n\n## Examples\n\n### Three columns\n\nThree equal-width columns across all viewports and devices can be created by using the `.g-col-4` classes. Add [responsive classes](#responsive) to change the layout by viewport size.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div class=\"g-col-4\">.g-col-4</div>\n  <div class=\"g-col-4\">.g-col-4</div>\n  <div class=\"g-col-4\">.g-col-4</div>\n</div>\n{{< /example >}}\n\n### Responsive\n\nUse responsive classes to adjust your layout across viewports. Here we start with two columns on the narrowest viewports, and then grow to three columns on medium viewports and above.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div class=\"g-col-6 g-col-md-4\">.g-col-6 .g-col-md-4</div>\n  <div class=\"g-col-6 g-col-md-4\">.g-col-6 .g-col-md-4</div>\n  <div class=\"g-col-6 g-col-md-4\">.g-col-6 .g-col-md-4</div>\n</div>\n{{< /example >}}\n\nCompare that to this two column layout at all viewports.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n</div>\n{{< /example >}}\n\n## Wrapping\n\nGrid items automatically wrap to the next line when there's no more room horizontally. Note that the `gap` applies to horizontal and vertical gaps between grid items.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n</div>\n{{< /example >}}\n\n## Starts\n\nStart classes aim to replace our default grid's offset classes, but they're not entirely the same. CSS Grid creates a grid template through styles that tell browsers to \"start at this column\" and \"end at this column.\" Those properties are `grid-column-start` and `grid-column-end`. Start classes are shorthand for the former. Pair them with the column classes to size and align your columns however you need. Start classes begin at `1` as `0` is an invalid value for these properties.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div class=\"g-col-3 g-start-2\">.g-col-3 .g-start-2</div>\n  <div class=\"g-col-4 g-start-6\">.g-col-4 .g-start-6</div>\n</div>\n{{< /example >}}\n\n## Auto columns\n\nWhen there are no classes on the grid items (the immediate children of a `.grid`), each grid item will automatically be sized to one column.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n</div>\n{{< /example >}}\n\nThis behavior can be mixed with grid column classes.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\">\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n  <div>1</div>\n</div>\n{{< /example >}}\n\n## Nesting\n\nSimilar to our default grid system, our CSS Grid allows for easy nesting of `.grid`s. However, unlike the default, this grid inherits changes in the rows, columns, and gaps. Consider the example below:\n\n- We override the default number of columns with a local CSS variable: `--bs-columns: 3`.\n- In the first auto-column, the column count is inherited and each column is one-third of the available width.\n- In the second auto-column, we've reset the column count on the nested `.grid` to 12 (our default).\n- The third auto-column has no nested content.\n\nIn practice this allows for more complex and custom layouts when compared to our default grid system.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-columns: 3;\">\n  <div>\n    First auto-column\n    <div class=\"grid\">\n      <div>Auto-column</div>\n      <div>Auto-column</div>\n    </div>\n  </div>\n  <div>\n    Second auto-column\n    <div class=\"grid\" style=\"--bs-columns: 12;\">\n      <div class=\"g-col-6\">6 of 12</div>\n      <div class=\"g-col-4\">4 of 12</div>\n      <div class=\"g-col-2\">2 of 12</div>\n    </div>\n  </div>\n  <div>Third auto-column</div>\n</div>\n{{< /example >}}\n\n## Customizing\n\nCustomize the number of columns, the number of rows, and the width of the gaps with local CSS variables.\n\n{{< bs-table \"table\" >}}\n| Variable | Fallback value | Description |\n| --- | --- | --- |\n| `--bs-rows` | `1` | The number of rows in your grid template |\n| `--bs-columns` | `12` | The number of columns in your grid template |\n| `--bs-gap` | `1.5rem` | The size of the gap between columns (vertical and horizontal) |\n{{< /bs-table >}}\n\nThese CSS variables have no default value; instead, they apply fallback values that are used _until_ a local instance is provided. For example, we use `var(--bs-rows, 1)` for our CSS Grid rows, which ignores `--bs-rows` because that hasn't been set anywhere yet. Once it is, the `.grid` instance will use that value instead of the fallback value of `1`.\n\n### No grid classes\n\nImmediate children elements of `.grid` are grid items, so they'll be sized without explicitly adding a `.g-col` class.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-columns: 3;\">\n  <div>Auto-column</div>\n  <div>Auto-column</div>\n  <div>Auto-column</div>\n</div>\n{{< /example >}}\n\n### Columns and gaps\n\nAdjust the number of columns and the gap.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-columns: 4; --bs-gap: 5rem;\">\n  <div class=\"g-col-2\">.g-col-2</div>\n  <div class=\"g-col-2\">.g-col-2</div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-columns: 10; --bs-gap: 1rem;\">\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-4\">.g-col-4</div>\n</div>\n{{< /example >}}\n\n### Adding rows\n\nAdding more rows and changing the placement of columns:\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-rows: 3; --bs-columns: 3;\">\n  <div>Auto-column</div>\n  <div class=\"g-start-2\" style=\"grid-row: 2\">Auto-column</div>\n  <div class=\"g-start-3\" style=\"grid-row: 3\">Auto-column</div>\n</div>\n{{< /example >}}\n\n### Gaps\n\nChange the vertical gaps only by modifying the `row-gap`. Note that we use `gap` on `.grid`s, but `row-gap` and `column-gap` can be modified as needed.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"row-gap: 0;\">\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n</div>\n{{< /example >}}\n\nBecause of that, you can have different vertical and horizontal `gap`s, which can take a single value (all sides) or a pair of values (vertical and horizontal). This can be applied with an inline style for `gap`, or with our `--bs-gap` CSS variable.\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-gap: .25rem 1rem;\">\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n\n  <div class=\"g-col-6\">.g-col-6</div>\n  <div class=\"g-col-6\">.g-col-6</div>\n</div>\n{{< /example >}}\n\n## Sass\n\nOne limitation of the CSS Grid is that our default classes are still generated by two Sass variables, `$grid-columns` and `$grid-gutter-width`. This effectively predetermines the number of classes generated in our compiled CSS. You have two options here:\n\n- Modify those default Sass variables and recompile your CSS.\n- Use inline or custom styles to augment the provided classes.\n\nFor example, you can increase the column count and change the gap size, and then size your \"columns\" with a mix of inline styles and predefined CSS Grid column classes (e.g., `.g-col-4`).\n\n{{< example class=\"bd-example-cssgrid\" >}}\n<div class=\"grid text-center\" style=\"--bs-columns: 18; --bs-gap: .5rem;\">\n  <div style=\"grid-column: span 14;\">14 columns</div>\n  <div class=\"g-col-4\">.g-col-4</div>\n</div>\n{{< /example >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/grid.md",
    "content": "---\nlayout: docs\ntitle: Grid system\ndescription: Use our powerful mobile-first flexbox grid to build layouts of all shapes and sizes thanks to a twelve column system, six default responsive tiers, Sass variables and mixins, and dozens of predefined classes.\ngroup: layout\ntoc: true\n---\n\n## Example\n\nBootstrap's grid system uses a series of containers, rows, and columns to layout and align content. It's built with [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) and is fully responsive. Below is an example and an in-depth explanation for how the grid system comes together.\n\n{{< callout info >}}\n**New to or unfamiliar with flexbox?** [Read this CSS Tricks flexbox guide](https://css-tricks.com/snippets/css/a-guide-to-flexbox/#flexbox-background) for background, terminology, guidelines, and code snippets.\n{{< /callout >}}\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col\">\n      Column\n    </div>\n    <div class=\"col\">\n      Column\n    </div>\n    <div class=\"col\">\n      Column\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nThe above example creates three equal-width columns across all devices and viewports using our predefined grid classes. Those columns are centered in the page with the parent `.container`.\n\n## How it works\n\nBreaking it down, here's how the grid system comes together:\n\n- **Our grid supports [six responsive breakpoints]({{< docsref \"/layout/breakpoints\" >}}).**  Breakpoints are based on `min-width` media queries, meaning they affect that breakpoint and all those above it (e.g., `.col-sm-4` applies to `sm`, `md`, `lg`, `xl`, and `xxl`). This means you can control container and column sizing and behavior by each breakpoint.\n\n- **Containers center and horizontally pad your content.** Use `.container` for a responsive pixel width, `.container-fluid` for `width: 100%` across all viewports and devices, or a responsive container (e.g., `.container-md`) for a combination of fluid and pixel widths.\n\n- **Rows are wrappers for columns.** Each column has horizontal `padding` (called a gutter) for controlling the space between them. This `padding` is then counteracted on the rows with negative margins to ensure the content in your columns is visually aligned down the left side. Rows also support modifier classes to [uniformly apply column sizing](#row-columns) and [gutter classes]({{< docsref \"/layout/gutters\" >}}) to change the spacing of your content.\n\n- **Columns are incredibly flexible.** There are 12 template columns available per row, allowing you to create different combinations of elements that span any number of columns. Column classes indicate the number of template columns to span (e.g., `col-4` spans four). `width`s are set in percentages so you always have the same relative sizing.\n\n- **Gutters are also responsive and customizable.** [Gutter classes are available]({{< docsref \"/layout/gutters\" >}}) across all breakpoints, with all the same sizes as our [margin and padding spacing]({{< docsref \"/utilities/spacing\" >}}). Change horizontal gutters with `.gx-*` classes, vertical gutters with `.gy-*`, or all gutters with `.g-*` classes. `.g-0` is also available to remove gutters.\n\n- **Sass variables, maps, and mixins power the grid.** If you don't want to use the predefined grid classes in Bootstrap, you can use our [grid's source Sass](#sass) to create your own with more semantic markup. We also include some CSS custom properties to consume these Sass variables for even greater flexibility for you.\n\nBe aware of the limitations and [bugs around flexbox](https://github.com/philipwalton/flexbugs), like the [inability to use some HTML elements as flex containers](https://github.com/philipwalton/flexbugs#flexbug-9).\n\n## Grid options\n\nBootstrap's grid system can adapt across all six default breakpoints, and any breakpoints you customize. The six default grid tiers are as follows:\n\n- Extra small (xs)\n- Small (sm)\n- Medium (md)\n- Large (lg)\n- Extra large (xl)\n- Extra extra large (xxl)\n\nAs noted above, each of these breakpoints have their own container, unique class prefix, and modifiers. Here's how the grid changes across these breakpoints:\n\n<div class=\"table-responsive\">\n  <table class=\"table mb-4\">\n    <thead>\n      <tr>\n        <th scope=\"col\"></th>\n        <th scope=\"col\">\n          xs<br>\n          <span class=\"fw-normal\">&lt;576px</span>\n        </th>\n        <th scope=\"col\">\n          sm<br>\n          <span class=\"fw-normal\">&ge;576px</span>\n        </th>\n        <th scope=\"col\">\n          md<br>\n          <span class=\"fw-normal\">&ge;768px</span>\n        </th>\n        <th scope=\"col\">\n          lg<br>\n          <span class=\"fw-normal\">&ge;992px</span>\n        </th>\n        <th scope=\"col\">\n          xl<br>\n          <span class=\"fw-normal\">&ge;1200px</span>\n        </th>\n        <th scope=\"col\">\n          xxl<br>\n          <span class=\"fw-normal\">&ge;1400px</span>\n        </th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\">Container <code class=\"fw-normal\">max-width</code></th>\n        <td>None (auto)</td>\n        <td>540px</td>\n        <td>720px</td>\n        <td>960px</td>\n        <td>1140px</td>\n        <td>1320px</td>\n      </tr>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\">Class prefix</th>\n        <td><code>.col-</code></td>\n        <td><code>.col-sm-</code></td>\n        <td><code>.col-md-</code></td>\n        <td><code>.col-lg-</code></td>\n        <td><code>.col-xl-</code></td>\n        <td><code>.col-xxl-</code></td>\n      </tr>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\"># of columns</th>\n        <td colspan=\"6\">12</td>\n      </tr>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\">Gutter width</th>\n        <td colspan=\"6\">1.5rem (.75rem on left and right)</td>\n      </tr>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\">Custom gutters</th>\n        <td colspan=\"6\"><a href=\"{{< docsref \"/layout/gutters\" >}}\">Yes</a></td>\n      </tr>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\">Nestable</th>\n        <td colspan=\"6\"><a href=\"#nesting\">Yes</a></td>\n      </tr>\n      <tr>\n        <th class=\"text-nowrap\" scope=\"row\">Column ordering</th>\n        <td colspan=\"6\"><a href=\"{{< docsref \"/layout/columns#reordering\" >}}\">Yes</a></td>\n      </tr>\n    </tbody>\n  </table>\n</div>\n\n## Auto-layout columns\n\nUtilize breakpoint-specific column classes for easy column sizing without an explicit numbered class like `.col-sm-6`.\n\n### Equal-width\n\nFor example, here are two grid layouts that apply to every device and viewport, from `xs` to `xxl`. Add any number of unit-less classes for each breakpoint you need and every column will be the same width.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col\">\n      1 of 2\n    </div>\n    <div class=\"col\">\n      2 of 2\n    </div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col\">\n      1 of 3\n    </div>\n    <div class=\"col\">\n      2 of 3\n    </div>\n    <div class=\"col\">\n      3 of 3\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Setting one column width\n\nAuto-layout for flexbox grid columns also means you can set the width of one column and have the sibling columns automatically resize around it. You may use predefined grid classes (as shown below), grid mixins, or inline widths. Note that the other columns will resize no matter the width of the center column.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col\">\n      1 of 3\n    </div>\n    <div class=\"col-6\">\n      2 of 3 (wider)\n    </div>\n    <div class=\"col\">\n      3 of 3\n    </div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col\">\n      1 of 3\n    </div>\n    <div class=\"col-5\">\n      2 of 3 (wider)\n    </div>\n    <div class=\"col\">\n      3 of 3\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n### Variable width content\n\nUse `col-{breakpoint}-auto` classes to size columns based on the natural width of their content.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row justify-content-md-center\">\n    <div class=\"col col-lg-2\">\n      1 of 3\n    </div>\n    <div class=\"col-md-auto\">\n      Variable width content\n    </div>\n    <div class=\"col col-lg-2\">\n      3 of 3\n    </div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col\">\n      1 of 3\n    </div>\n    <div class=\"col-md-auto\">\n      Variable width content\n    </div>\n    <div class=\"col col-lg-2\">\n      3 of 3\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Responsive classes\n\nBootstrap's grid includes six tiers of predefined classes for building complex responsive layouts. Customize the size of your columns on extra small, small, medium, large, or extra large devices however you see fit.\n\n### All breakpoints\n\nFor grids that are the same from the smallest of devices to the largest, use the `.col` and `.col-*` classes. Specify a numbered class when you need a particularly sized column; otherwise, feel free to stick to `.col`.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col\">col</div>\n    <div class=\"col\">col</div>\n    <div class=\"col\">col</div>\n    <div class=\"col\">col</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-8\">col-8</div>\n    <div class=\"col-4\">col-4</div>\n  </div>\n</div>\n{{< /example >}}\n\n### Stacked to horizontal\n\nUsing a single set of `.col-sm-*` classes, you can create a basic grid system that starts out stacked and becomes horizontal at the small breakpoint (`sm`).\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-sm-8\">col-sm-8</div>\n    <div class=\"col-sm-4\">col-sm-4</div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-sm\">col-sm</div>\n    <div class=\"col-sm\">col-sm</div>\n    <div class=\"col-sm\">col-sm</div>\n  </div>\n</div>\n{{< /example >}}\n\n### Mix and match\n\nDon't want your columns to simply stack in some grid tiers? Use a combination of different classes for each tier as needed. See the example below for a better idea of how it all works.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <!-- Stack the columns on mobile by making one full-width and the other half-width -->\n  <div class=\"row\">\n    <div class=\"col-md-8\">.col-md-8</div>\n    <div class=\"col-6 col-md-4\">.col-6 .col-md-4</div>\n  </div>\n\n  <!-- Columns start at 50% wide on mobile and bump up to 33.3% wide on desktop -->\n  <div class=\"row\">\n    <div class=\"col-6 col-md-4\">.col-6 .col-md-4</div>\n    <div class=\"col-6 col-md-4\">.col-6 .col-md-4</div>\n    <div class=\"col-6 col-md-4\">.col-6 .col-md-4</div>\n  </div>\n\n  <!-- Columns are always 50% wide, on mobile and desktop -->\n  <div class=\"row\">\n    <div class=\"col-6\">.col-6</div>\n    <div class=\"col-6\">.col-6</div>\n  </div>\n</div>\n{{< /example >}}\n\n### Row columns\n\nUse the responsive `.row-cols-*` classes to quickly set the number of columns that best render your content and layout. Whereas normal `.col-*` classes apply to the individual columns (e.g., `.col-md-4`), the row columns classes are set on the parent `.row` as a shortcut. With `.row-cols-auto` you can give the columns their natural width.\n\nUse these row columns classes to quickly create basic grid layouts or to control your card layouts.\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-2\">\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-3\">\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-auto\">\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-4\">\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-4\">\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col-6\">Column</div>\n    <div class=\"col\">Column</div>\n  </div>\n</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-1 row-cols-sm-2 row-cols-md-4\">\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n    <div class=\"col\">Column</div>\n  </div>\n</div>\n{{< /example >}}\n\nYou can also use the accompanying Sass mixin, `row-cols()`:\n\n```scss\n.element {\n  // Three columns to start\n  @include row-cols(3);\n\n  // Five columns from medium breakpoint up\n  @include media-breakpoint-up(md) {\n    @include row-cols(5);\n  }\n}\n```\n\n## Nesting\n\nTo nest your content with the default grid, add a new `.row` and set of `.col-sm-*` columns within an existing `.col-sm-*` column. Nested rows should include a set of columns that add up to 12 or fewer (it is not required that you use all 12 available columns).\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"container text-center\">\n  <div class=\"row\">\n    <div class=\"col-sm-3\">\n      Level 1: .col-sm-3\n    </div>\n    <div class=\"col-sm-9\">\n      <div class=\"row\">\n        <div class=\"col-8 col-sm-6\">\n          Level 2: .col-8 .col-sm-6\n        </div>\n        <div class=\"col-4 col-sm-6\">\n          Level 2: .col-4 .col-sm-6\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Sass\n\nWhen using Bootstrap's source Sass files, you have the option of using Sass variables and mixins to create custom, semantic, and responsive page layouts. Our predefined grid classes use these same variables and mixins to provide a whole suite of ready-to-use classes for fast responsive layouts.\n\n### Variables\n\nVariables and maps determine the number of columns, the gutter width, and the media query point at which to begin floating columns. We use these to generate the predefined grid classes documented above, as well as for the custom mixins listed below.\n\n```scss\n$grid-columns:      12;\n$grid-gutter-width: 1.5rem;\n$grid-row-columns:  6;\n```\n\n{{< scss-docs name=\"grid-breakpoints\" file=\"scss/_variables.scss\" >}}\n\n{{< scss-docs name=\"container-max-widths\" file=\"scss/_variables.scss\" >}}\n\n### Mixins\n\nMixins are used in conjunction with the grid variables to generate semantic CSS for individual grid columns.\n\n```scss\n// Creates a wrapper for a series of columns\n@include make-row();\n\n// Make the element grid-ready (applying everything but the width)\n@include make-col-ready();\n\n// Without optional size values, the mixin will create equal columns (similar to using .col)\n@include make-col();\n@include make-col($size, $columns: $grid-columns);\n\n// Offset with margins\n@include make-col-offset($size, $columns: $grid-columns);\n```\n\n### Example usage\n\nYou can modify the variables to your own custom values, or just use the mixins with their default values. Here's an example of using the default settings to create a two-column layout with a gap between.\n\n```scss\n.example-container {\n  @include make-container();\n  // Make sure to define this width after the mixin to override\n  // `width: 100%` generated by `make-container()`\n  width: 800px;\n}\n\n.example-row {\n  @include make-row();\n}\n\n.example-content-main {\n  @include make-col-ready();\n\n  @include media-breakpoint-up(sm) {\n    @include make-col(6);\n  }\n  @include media-breakpoint-up(lg) {\n    @include make-col(8);\n  }\n}\n\n.example-content-secondary {\n  @include make-col-ready();\n\n  @include media-breakpoint-up(sm) {\n    @include make-col(6);\n  }\n  @include media-breakpoint-up(lg) {\n    @include make-col(4);\n  }\n}\n```\n\n{{< example >}}\n<div class=\"example-container\">\n  <div class=\"example-row\">\n    <div class=\"example-content-main\">Main content</div>\n    <div class=\"example-content-secondary\">Secondary content</div>\n  </div>\n</div>\n{{< /example >}}\n\n## Customizing the grid\n\nUsing our built-in grid Sass variables and maps, it's possible to completely customize the predefined grid classes. Change the number of tiers, the media query dimensions, and the container widths—then recompile.\n\n### Columns and gutters\n\nThe number of grid columns can be modified via Sass variables. `$grid-columns` is used to generate the widths (in percent) of each individual column while `$grid-gutter-width` sets the width for the column gutters. `$grid-row-columns` is used to set the maximum number of columns of `.row-cols-*`, any number over this limit is ignored.\n\n```scss\n$grid-columns: 12 !default;\n$grid-gutter-width: 1.5rem !default;\n$grid-row-columns: 6 !default;\n```\n\n### Grid tiers\n\nMoving beyond the columns themselves, you may also customize the number of grid tiers. If you wanted just four grid tiers, you'd update the `$grid-breakpoints` and `$container-max-widths` to something like this:\n\n```scss\n$grid-breakpoints: (\n  xs: 0,\n  sm: 480px,\n  md: 768px,\n  lg: 1024px\n);\n\n$container-max-widths: (\n  sm: 420px,\n  md: 720px,\n  lg: 960px\n);\n```\n\nWhen making any changes to the Sass variables or maps, you'll need to save your changes and recompile. Doing so will output a brand-new set of predefined grid classes for column widths, offsets, and ordering. Responsive visibility utilities will also be updated to use the custom breakpoints. Make sure to set grid values in `px` (not `rem`, `em`, or `%`).\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/gutters.md",
    "content": "---\nlayout: docs\ntitle: Gutters\ndescription: Gutters are the padding between your columns, used to responsively space and align content in the Bootstrap grid system.\ngroup: layout\ntoc: true\n---\n\n## How they work\n\n- **Gutters are the gaps between column content, created by horizontal `padding`.** We set `padding-right` and `padding-left` on each column, and use negative `margin` to offset that at the start and end of each row to align content.\n\n- **Gutters start at `1.5rem` (`24px`) wide.** This allows us to match our grid to the [padding and margin spacers]({{< docsref \"/utilities/spacing\" >}}) scale.\n\n- **Gutters can be responsively adjusted.** Use breakpoint-specific gutter classes to modify horizontal gutters, vertical gutters, and all gutters.\n\n## Horizontal gutters\n\n`.gx-*` classes can be used to control the horizontal gutter widths. The `.container` or `.container-fluid` parent may need to be adjusted if larger gutters are used too to avoid unwanted overflow, using a matching padding utility. For example, in the following example we've increased the padding with `.px-4`:\n\n{{< example >}}\n<div class=\"container px-4 text-center\">\n  <div class=\"row gx-5\">\n    <div class=\"col\">\n     <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\nAn alternative solution is to add a wrapper around the `.row` with the `.overflow-hidden` class:\n\n{{< example >}}\n<div class=\"container overflow-hidden text-center\">\n  <div class=\"row gx-5\">\n    <div class=\"col\">\n     <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Vertical gutters\n\n`.gy-*` classes can be used to control the vertical gutter widths within a row when columns wrap to new lines. Like the horizontal gutters, the vertical gutters can cause some overflow below the `.row` at the end of a page. If this occurs, you add a wrapper around `.row` with the `.overflow-hidden` class:\n\n{{< example >}}\n<div class=\"container overflow-hidden text-center\">\n  <div class=\"row gy-5\">\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Horizontal & vertical gutters\n\n`.g-*` classes can be used to control the horizontal gutter widths, for the following example we use a smaller gutter width, so there won't be a need to add the `.overflow-hidden` wrapper class.\n\n{{< example >}}\n<div class=\"container text-center\">\n  <div class=\"row g-2\">\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"p-3 border bg-light\">Custom column padding</div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## Row columns gutters\n\nGutter classes can also be added to [row columns]({{< docsref \"/layout/grid#row-columns\" >}}). In the following example, we use responsive row columns and responsive gutter classes.\n\n{{< example >}}\n<div class=\"container text-center\">\n  <div class=\"row row-cols-2 row-cols-lg-5 g-2 g-lg-3\">\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n    <div class=\"col\">\n      <div class=\"p-3 border bg-light\">Row column</div>\n    </div>\n  </div>\n</div>\n{{< /example >}}\n\n## No gutters\n\nThe gutters between columns in our predefined grid classes can be removed with `.g-0`. This removes the negative `margin`s from `.row` and the horizontal `padding` from all immediate children columns.\n\n**Need an edge-to-edge design?** Drop the parent `.container` or `.container-fluid` and add `.mx-0` to the `.row` to prevent overflow.\n\nIn practice, here's how it looks. Note you can continue to use this with all other predefined grid classes (including column widths, responsive tiers, reorders, and more).\n\n{{< example class=\"bd-example-row\" >}}\n<div class=\"row g-0 text-center\">\n  <div class=\"col-sm-6 col-md-8\">.col-sm-6 .col-md-8</div>\n  <div class=\"col-6 col-md-4\">.col-6 .col-md-4</div>\n</div>\n{{< /example >}}\n\n## Change the gutters\n\nClasses are built from the `$gutters` Sass map which is inherited from the `$spacers` Sass map.\n\n```scss\n$grid-gutter-width: 1.5rem;\n$gutters: (\n  0: 0,\n  1: $spacer * .25,\n  2: $spacer * .5,\n  3: $spacer,\n  4: $spacer * 1.5,\n  5: $spacer * 3,\n);\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/utilities.md",
    "content": "---\nlayout: docs\ntitle: Utilities for layout\ndescription: For faster mobile-friendly and responsive development, Bootstrap includes dozens of utility classes for showing, hiding, aligning, and spacing content.\ngroup: layout\ntoc: true\n---\n\n## Changing `display`\n\nUse our [display utilities]({{< docsref \"/utilities/display\" >}}) for responsively toggling common values of the `display` property. Mix it with our grid system, content, or components to show or hide them across specific viewports.\n\n## Flexbox options\n\nBootstrap is built with flexbox, but not every element's `display` has been changed to `display: flex` as this would add many unnecessary overrides and unexpectedly change key browser behaviors. Most of [our components]({{< docsref \"/components/alerts\" >}}) are built with flexbox enabled.\n\nShould you need to add `display: flex` to an element, do so with `.d-flex` or one of the responsive variants (e.g., `.d-sm-flex`). You'll need this class or `display` value to allow the use of our extra [flexbox utilities]({{< docsref \"/utilities/flex\" >}}) for sizing, alignment, spacing, and more.\n\n## Margin and padding\n\nUse the `margin` and `padding` [spacing utilities]({{< docsref \"/utilities/spacing\" >}}) to control how elements and components are spaced and sized. Bootstrap includes a six-level scale for spacing utilities, based on a `1rem` value default `$spacer` variable. Choose values for all viewports (e.g., `.me-3` for `margin-right: 1rem` in LTR), or pick responsive variants to target specific viewports (e.g., `.me-md-3` for `margin-right: 1rem` —in LTR— starting at the `md` breakpoint).\n\n## Toggle `visibility`\n\nWhen toggling `display` isn't needed, you can toggle the `visibility` of an element with our [visibility utilities]({{< docsref \"/utilities/visibility\" >}}). Invisible elements will still affect the layout of the page, but are visually hidden from visitors.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/layout/z-index.md",
    "content": "---\nlayout: docs\ntitle: Z-index\ndescription: While not a part of Bootstrap's grid system, z-indexes play an important part in how our components overlay and interact with one another.\ngroup: layout\n---\n\nSeveral Bootstrap components utilize `z-index`, the CSS property that helps control layout by providing a third axis to arrange content. We utilize a default z-index scale in Bootstrap that's been designed to properly layer navigation, tooltips and popovers, modals, and more.\n\nThese higher values start at an arbitrary number, high and specific enough to ideally avoid conflicts. We need a standard set of these across our layered components—tooltips, popovers, navbars, dropdowns, modals—so we can be reasonably consistent in the behaviors. There's no reason we couldn't have used `100`+ or `500`+.\n\nWe don't encourage customization of these individual values; should you change one, you likely need to change them all.\n\n{{< scss-docs name=\"zindex-stack\" file=\"scss/_variables.scss\" >}}\n\nTo handle overlapping borders within components (e.g., buttons and inputs in input groups), we use low single digit `z-index` values of `1`, `2`, and `3` for default, hover, and active states. On hover/focus/active, we bring a particular element to the forefront with a higher `z-index` value to show their border over the sibling elements.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/migration.md",
    "content": "---\nlayout: docs\ntitle: Migrating to v5\ndescription: Track and review changes to the Bootstrap source files, documentation, and components to help you migrate from v4 to v5.\ngroup: migration\naliases: \"/migration/\"\ntoc: true\n---\n\n## v5.2.0\n\n<hr class=\"mb-4\">\n\n### Refreshed design\n\nBootstrap v5.2.0 features a subtle design update for a handful of components and properties across the project, **most notably through refined `border-radius` values on buttons and form controls**. Our documentation also has been updated with a new homepage, simpler docs layout that no longer collapses sections of the sidebar, and more prominent examples of [Bootstrap Icons](https://icons.getbootstrap.com).\n\n### More CSS variables\n\n**We've updated all our components to use CSS variables.** While Sass still underpins everything, each component has been updated to include CSS variables on the component base classes (e.g., `.btn`), allowing for more real-time customization of Bootstrap. In subsequent releases, we'll continue to expand our use of CSS variables into our layout, forms, helpers, and utilities. Read more about CSS variables in each component on their respective documentation pages.\n\nOur CSS variable usage will be somewhat incomplete until Bootstrap 6. While we'd love to fully implement these across the board, they do run the risk of causing breaking changes. For example, setting `$alert-border-width: var(--bs-border-width)` in our source code breaks potential Sass in your own code if you were doing `$alert-border-width * 2` for some reason.\n\nAs such, wherever possible, we will continue to push towards more CSS variables, but please recognize our implementation may be slightly limited in v5.\n\n### New `_maps.scss`\n\n**Bootstrap v5.2.0 introduced a new Sass file with `_maps.scss`.** It pulls out several Sass maps from `_variables.scss` to fix an issue where updates to an original map were not applied to secondary maps that extend them. For example, updates to `$theme-colors` were not being applied to other theme maps that relied on `$theme-colors`, breaking key customization workflows. In short, Sass has a limitation where once a default variable or map has been _used_, it cannot be updated. _There's a similar shortcoming with CSS variables when they're used to compose other CSS variables._\n\nThis is why variable customizations in Bootstrap have to come after `@import \"functions\"`, but before `@import \"variables\"` and the rest of our import stack. The same applies to Sass maps—you must override the defaults before they get used. The following maps have been moved to the new `_maps.scss`:\n\n- `$theme-colors-rgb`\n- `$utilities-colors`\n- `$utilities-text`\n- `$utilities-text-colors`\n- `$utilities-bg`\n- `$utilities-bg-colors`\n- `$negative-spacers`\n- `$gutters`\n\nYour custom Bootstrap CSS builds should now look something like this with a separate maps import.\n\n```diff\n  // Functions come first\n  @import \"functions\";\n\n  // Optional variable overrides here\n+ $custom-color: #df711b;\n+ $custom-theme-colors: (\n+   \"custom\": $custom-color\n+ );\n\n  // Variables come next\n  @import \"variables\";\n\n+ // Optional Sass map overrides here\n+ $theme-colors: map-merge($theme-colors, $custom-theme-colors);\n+\n+ // Followed by our default maps\n+ @import \"maps\";\n+\n  // Rest of our imports\n  @import \"mixins\";\n  @import \"utilities\";\n  @import \"root\";\n  @import \"reboot\";\n  // etc\n```\n\n### New utilities\n\n- Expanded [`font-weight` utilities]({{< docsref \"/utilities/text#font-weight-and-italics\" >}}) to include `.fw-semibold` for semibold fonts.\n- Expanded [`border-radius` utilities]({{< docsref \"/utilities/borders#sizes\" >}}) to include two new sizes, `.rounded-4` and `.rounded-5`, for more options.\n\n### Additional changes\n\n- **Introduced new `$enable-container-classes` option. —** Now when opting into the experimental CSS Grid layout, `.container-*` classes will still be compiled, unless this option is set to `false`. Containers also now keep their gutter values.\n\n- **Offcanvas component now has [responsive variations]({{< docsref \"/components/offcanvas#responsive\" >}}).** The original `.offcanvas` class remains unchanged—it hides content across all viewports. To make it responsive, change that `.offcanvas` class to any `.offcanvas-{sm|md|lg|xl|xxl}` class.\n\n- **Thicker table dividers are now opt-in. —** We've removed the thicker and more difficult to override border between table groups and moved it to an optional class you can apply, `.table-group-divider`. [See the table docs for an example.]({{< docsref \"/content/tables#table-group-dividers\" >}})\n\n- **[Scrollspy has been rewritten](https://github.com/twbs/bootstrap/pull/33421) to use the Intersection Observer API**, which means you no longer need relative parent wrappers, deprecates `offset` config, and more. Look for your Scrollspy implementations to be more accurate and consistent in their nav highlighting.\n\n- **Popovers and tooltips now use CSS variables.** Some CSS variables have been updated from their Sass counterparts to reduce the number of variables. As a result, three variables have been deprecated in this release: `$popover-arrow-color`, `$popover-arrow-outer-color`, and `$tooltip-arrow-color`.\n\n- **Added new `.text-bg-{color}` helpers.** Instead of setting individual `.text-*` and `.bg-*` utilities, you can now use [the `.text-bg-*` helpers]({{< docsref \"helpers/color-background\" >}}) to set a `background-color` with contrasting foreground `color`.\n\n- Added `.form-check-reverse` modifier to flip the order of labels and associated checkboxes/radios.\n\n- Added [striped columns]({{< docsref \"/content/tables#striped-columns\" >}}) support to tables via the new `.table-striped-columns` class.\n\nFor a complete list of changes, [see the v5.2.0 project on GitHub](https://github.com/twbs/bootstrap/projects/32).\n\n## v5.1.0\n\n<hr class=\"mb-4\">\n\n- **Added experimental support for [CSS Grid layout]({{< docsref \"/layout/css-grid\" >}}). —** This is a work in progress, and is not yet ready for production use, but you can opt into the new feature via Sass. To enable it, disable the default grid, by setting `$enable-grid-classes: false` and enable the CSS Grid by setting `$enable-cssgrid: true`.\n\n- **Updated navbars to support offcanvas. —** Add [offcanvas drawers in any navbar]({{< docsref \"/components/navbar#offcanvas\" >}}) with the responsive `.navbar-expand-*` classes and some offcanvas markup.\n\n- **Added new [placeholder component]({{< docsref \"/components/placeholders/\" >}}). —** Our newest component, a way to provide temporary blocks in lieu of real content to help indicate that something is still loading in your site or app.\n\n- **Collapse plugin now supports [horizontal collapsing]({{< docsref \"/components/collapse#horizontal\" >}}). —** Add `.collapse-horizontal` to your `.collapse` to collapse the `width` instead of the `height`. Avoid browser repainting by setting a `min-height` or `height`.\n\n- **Added new stack and vertical rule helpers. —** Quickly apply multiple flexbox properties to quickly create custom layouts with [stacks]({{< docsref \"/helpers/stacks/\" >}}). Choose from horizontal (`.hstack`) and vertical (`.vstack`) stacks. Add vertical dividers similar to `<hr>` elements with the [new `.vr` helpers]({{< docsref \"/helpers/vertical-rule/\" >}}).\n\n- **Added new global `:root` CSS variables. —** Added several new CSS variables to the `:root` level for controlling `<body>` styles. More are in the works, including across our utilities and components, but for now read up [CSS variables in the Customize section]({{< docsref \"/customize/css-variables/\" >}}).\n\n- **Overhauled color and background utilities to use CSS variables, and added new [text opacity]({{< docsref \"/utilities/text#opacity\" >}}) and [background opacity]({{< docsref \"/utilities/background#opacity\" >}}) utilities. —** `.text-*` and `.bg-*` utilities are now built with CSS variables and `rgba()` color values, allowing you to easily customize any utility with new opacity utilities.\n\n- **Added new snippet examples based to show how to customize our components. —** Pull ready to use customized components and other common design patterns with our new [Snippets examples]({{< docsref \"/examples#snippets\" >}}). Includes [footers]({{< docsref \"/examples/footers/\" >}}), [dropdowns]({{< docsref \"/examples/dropdowns/\" >}}), [list groups]({{< docsref \"/examples/list-groups/\" >}}), and [modals]({{< docsref \"/examples/modals/\" >}}).\n\n- **Removed unused positioning styles from popovers and tooltips** as these are handled solely by Popper. `$tooltip-margin` has been deprecated and set to `null` in the process.\n\nWant more information? [Read the v5.1.0 blog post.](https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/)\n\n<hr class=\"my-5\">\n\n{{< callout info >}}\n**Hey there!** Changes to our first major release of Bootstrap 5, v5.0.0, are documented below. They don't reflect the additional changes shown above.\n{{< /callout >}}\n\n## Dependencies\n\n- Dropped jQuery.\n- Upgraded from Popper v1.x to Popper v2.x.\n- Replaced Libsass with Dart Sass as our Sass compiler given Libsass was deprecated.\n- Migrated from Jekyll to Hugo for building our documentation\n\n## Browser support\n\n- Dropped Internet Explorer 10 and 11\n- Dropped Microsoft Edge < 16 (Legacy Edge)\n- Dropped Firefox < 60\n- Dropped Safari < 12\n- Dropped iOS Safari < 12\n- Dropped Chrome < 60\n\n<hr class=\"my-5\">\n\n## Documentation changes\n\n- Redesigned homepage, docs layout, and footer.\n- Added [new Parcel guide]({{< docsref \"/getting-started/parcel\" >}}).\n- Added [new Customize section]({{< docsref \"/customize/overview\" >}}), replacing [v4's Theming page](https://getbootstrap.com/docs/4.6/getting-started/theming/), with new details on Sass, global configuration options, color schemes, CSS variables, and more.\n- Reorganized all form documentation into [new Forms section]({{< docsref \"/forms/overview\" >}}), breaking apart the content into more focused pages.\n- Similarly, updated [the Layout section]({{< docsref \"/layout/breakpoints\" >}}), to flesh out grid content more clearly.\n- Renamed \"Navs\" component page to \"Navs & Tabs\".\n- Renamed \"Checks\" page to \"Checks & radios\".\n- Redesigned the navbar and added a new subnav to make it easier to get around our sites and docs versions.\n- Added new keyboard shortcut for the search field: <kbd>Ctrl + /</kbd>.\n\n## Sass\n\n- We've ditched the default Sass map merges to make it easier to remove redundant values. Keep in mind you now have to define all values in the Sass maps like `$theme-colors`. Check out how to deal with [Sass maps]({{< docsref \"/customize/sass#maps-and-loops\" >}}).\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `color-yiq()` function and related variables to `color-contrast()` as it's no longer related to YIQ color space. [See #30168.](https://github.com/twbs/bootstrap/pull/30168/)\n  - `$yiq-contrasted-threshold` is renamed to `$min-contrast-ratio`.\n  - `$yiq-text-dark` and `$yiq-text-light` are respectively renamed to `$color-contrast-dark` and `$color-contrast-light`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Media query mixins parameters have changed for a more logical approach.\n  - `media-breakpoint-down()` uses the breakpoint itself instead of the next breakpoint (e.g., `media-breakpoint-down(lg)` instead of `media-breakpoint-down(md)` targets viewports smaller than `lg`).\n  - Similarly, the second parameter in `media-breakpoint-between()` also uses the breakpoint itself instead of the next breakpoint (e.g., `media-between(sm, lg)` instead of `media-breakpoint-between(sm, md)` targets viewports between `sm` and `lg`).\n\n- <span class=\"badge bg-danger\">Breaking</span> Removed print styles and `$enable-print-styles` variable. Print display classes are still around. [See #28339](https://github.com/twbs/bootstrap/pull/28339).\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `color()`, `theme-color()`, and `gray()` functions in favor of variables. [See #29083](https://github.com/twbs/bootstrap/pull/29083).\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `theme-color-level()` function to `color-level()` and now accepts any color you want instead of only `$theme-color` colors. [See #29083](https://github.com/twbs/bootstrap/pull/29083) **Watch out:** `color-level()` was later on dropped in `v5.0.0-alpha3`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `$enable-prefers-reduced-motion-media-query` and `$enable-pointer-cursor-for-buttons` to `$enable-reduced-motion` and `$enable-button-pointers` for brevity.\n\n- <span class=\"badge bg-danger\">Breaking</span> Removed the `bg-gradient-variant()` mixin. Use the `.bg-gradient` class to add gradients to elements instead of the generated `.bg-gradient-*` classes.\n\n- <span class=\"badge bg-danger\">Breaking</span> **Removed previously deprecated mixins:**\n  - `hover`, `hover-focus`, `plain-hover-focus`, and `hover-focus-active`\n  - `float()`\n  - `form-control-mixin()`\n  - `nav-divider()`\n  - `retina-img()`\n  - `text-hide()` (also dropped the associated utility class, `.text-hide`)\n  - `visibility()`\n  - `form-control-focus()`\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `scale-color()` function to `shift-color()` to avoid collision with Sass's own color scaling function.\n\n- `box-shadow` mixins now allow `null` values and drop `none` from multiple arguments. [See #30394](https://github.com/twbs/bootstrap/pull/30394).\n\n- The `border-radius()` mixin now has a default value.\n\n## Color system\n\n- The color system which worked with `color-level()` and `$theme-color-interval` was removed in favor of a new color system. All `lighten()` and `darken()` functions in our codebase are replaced by `tint-color()` and `shade-color()`. These functions will mix the color with either white or black instead of changing its lightness by a fixed amount. The `shift-color()` will either tint or shade a color depending on whether its weight parameter is positive or negative. [See #30622](https://github.com/twbs/bootstrap/pull/30622) for more details.\n\n- Added new tints and shades for every color, providing nine separate colors for each base color, as new Sass variables.\n\n- Improved color contrast. Bumped color contrast ratio from 3:1 to 4.5:1 and updated blue, green, cyan, and pink colors to ensure WCAG 2.1 AA contrast. Also changed our color contrast color from `$gray-900` to `$black`.\n\n- To support our color system, we've added new custom `tint-color()` and `shade-color()` functions to mix our colors appropriately.\n\n## Grid updates\n\n- **New breakpoint!** Added new `xxl` breakpoint for `1400px` and up. No changes to all other breakpoints.\n\n- **Improved gutters.** Gutters are now set in rems, and are narrower than v4 (`1.5rem`, or about `24px`, down from `30px`). This aligns our grid system's gutters with our spacing utilities.\n  - Added new [gutter class]({{< docsref \"/layout/gutters\" >}}) (`.g-*`, `.gx-*`, and `.gy-*`) to control horizontal/vertical gutters, horizontal gutters, and vertical gutters.\n  - <span class=\"badge bg-danger\">Breaking</span> Renamed `.no-gutters` to `.g-0` to match new gutter utilities.\n\n- Columns no longer have `position: relative` applied, so you may have to add `.position-relative` to some elements to restore that behavior.\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped several `.order-*` classes that often went unused. We now only provide `.order-1` to `.order-5` out of the box.\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped the `.media` component as it can be easily replicated with utilities. [See #28265](https://github.com/twbs/bootstrap/pull/28265) and the [flex utilities page for an example]({{< docsref \"/utilities/flex#media-object\" >}}).\n\n- <span class=\"badge bg-danger\">Breaking</span> `bootstrap-grid.css` now only applies `box-sizing: border-box` to the column instead of resetting the global box-sizing. This way, our grid styles can be used in more places without interference.\n\n- `$enable-grid-classes` no longer disables the generation of container classes anymore. [See #29146.](https://github.com/twbs/bootstrap/pull/29146)\n\n- Updated the `make-col` mixin to default to equal columns without a specified size.\n\n## Content, Reboot, etc\n\n- **[RFS]({{< docsref \"/getting-started/rfs\" >}}) is now enabled by default.** Headings using the `font-size()` mixin will automatically adjust their `font-size` to scale with the viewport. _This feature was previously opt-in with v4._\n\n- <span class=\"badge bg-danger\">Breaking</span> Overhauled our display typography to replace our `$display-*` variables and with a `$display-font-sizes` Sass map. Also removed the individual `$display-*-weight` variables for a single `$display-font-weight` and adjusted `font-size`s.\n\n- Added two new `.display-*` heading sizes, `.display-5` and `.display-6`.\n\n- **Links are underlined by default** (not just on hover), unless they're part of specific components.\n\n- **Redesigned tables** to refresh their styles and rebuild them with CSS variables for more control over styling.\n\n- <span class=\"badge bg-danger\">Breaking</span> Nested tables do not inherit styles anymore.\n\n- <span class=\"badge bg-danger\">Breaking</span> `.thead-light` and `.thead-dark` are dropped in favor of the `.table-*` variant classes which can be used for all table elements (`thead`, `tbody`, `tfoot`, `tr`, `th` and `td`).\n\n- <span class=\"badge bg-danger\">Breaking</span> The `table-row-variant()` mixin is renamed to `table-variant()` and accepts only 2 parameters: `$color` (color name) and `$value` (color code). The border color and accent colors are automatically calculated based on the table factor variables.\n\n- Split table cell padding variables into `-y` and `-x`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `.pre-scrollable` class. [See #29135](https://github.com/twbs/bootstrap/pull/29135)\n\n- <span class=\"badge bg-danger\">Breaking</span> `.text-*` utilities do not add hover and focus states to links anymore. `.link-*` helper classes can be used instead. [See #29267](https://github.com/twbs/bootstrap/pull/29267)\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `.text-justify` class. [See #29793](https://github.com/twbs/bootstrap/pull/29793)\n\n- <span class=\"badge bg-danger\">Breaking</span> ~~`<hr>` elements now use `height` instead of `border` to better support the `size` attribute. This also enables use of padding utilities to create thicker dividers (e.g., `<hr class=\"py-1\">`).~~\n\n- Reset default horizontal `padding-left` on `<ul>` and `<ol>` elements from browser default `40px` to `2rem`.\n\n- Added `$enable-smooth-scroll`, which applies `scroll-behavior: smooth` globally—except for users asking for reduced motion through `prefers-reduced-motion` media query. [See #31877](https://github.com/twbs/bootstrap/pull/31877)\n\n## RTL\n\n- Horizontal direction specific variables, utilities, and mixins have all been renamed to use logical properties like those found in flexbox layouts—e.g., `start` and `end` in lieu of `left` and `right`.\n\n## Forms\n\n- **Added new floating forms!** We've promoted the Floating labels example to fully supported form components. [See the new Floating labels page.]({{< docsref \"/forms/floating-labels\" >}})\n\n- <span class=\"badge bg-danger\">Breaking</span> **Consolidated native and custom form elements.** Checkboxes, radios, selects, and other inputs that had native and custom classes in v4 have been consolidated. Now nearly all our form elements are entirely custom, most without the need for custom HTML.\n  - `.custom-control.custom-checkbox` is now `.form-check`.\n  - `.custom-control.custom-custom-radio` is now `.form-check`.\n  - `.custom-control.custom-switch` is now `.form-check.form-switch`.\n  - `.custom-select` is now `.form-select`.\n  - `.custom-file` and `.form-file` have been replaced by custom styles on top of `.form-control`.\n  - `.custom-range` is now `.form-range`.\n  - Dropped native `.form-control-file` and `.form-control-range`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `.input-group-append` and `.input-group-prepend`. You can now just add buttons and `.input-group-text` as direct children of the input groups.\n\n- The longstanding [Missing border radius on input group with validation feedback bug](https://github.com/twbs/bootstrap/issues/25110) is finally fixed by adding an additional `.has-validation` class to input groups with validation.\n\n- <span class=\"badge bg-danger\">Breaking</span> **Dropped form-specific layout classes for our grid system.** Use our grid and utilities instead of `.form-group`, `.form-row`, or `.form-inline`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Form labels now require `.form-label`.\n\n- <span class=\"badge bg-danger\">Breaking</span> `.form-text` no longer sets `display`, allowing you to create inline or block help text as you wish just by changing the HTML element.\n\n- Form controls no longer used fixed `height` when possible, instead deferring to `min-height` to improve customization and compatibility with other components.\n\n- Validation icons are no longer applied to `<select>`s with `multiple`.\n\n- Rearranged source Sass files under `scss/forms/`, including input group styles.\n\n<hr class=\"my-5\">\n\n## Components\n\n- Unified `padding` values for alerts, breadcrumbs, cards, dropdowns, list groups, modals, popovers, and tooltips to be based on our `$spacer` variable. [See #30564](https://github.com/twbs/bootstrap/pull/30564).\n\n### Accordion\n\n- Added [new accordion component]({{< docsref \"/components/accordion\" >}}).\n\n### Alerts\n\n- Alerts now have [examples with icons]({{< docsref \"/components/alerts#icons\" >}}).\n\n- Removed custom styles for `<hr>`s in each alert since they already use `currentColor`.\n\n### Badges\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped all `.badge-*` color classes for background utilities (e.g., use `.bg-primary` instead of `.badge-primary`).\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `.badge-pill`—use the `.rounded-pill` utility instead.\n\n- <span class=\"badge bg-danger\">Breaking</span> Removed hover and focus styles for `<a>` and `<button>` elements.\n\n- Increased default padding for badges from `.25em`/`.5em` to `.35em`/`.65em`.\n\n### Breadcrumbs\n\n- Simplified the default appearance of breadcrumbs by removing `padding`, `background-color`, and `border-radius`.\n\n- Added new CSS custom property `--bs-breadcrumb-divider` for easy customization without needing to recompile CSS.\n\n### Buttons\n\n- <span class=\"badge bg-danger\">Breaking</span> **[Toggle buttons]({{< docsref \"/forms/checks-radios#toggle-buttons\" >}}), with checkboxes or radios, no longer require JavaScript and have new markup.** We no longer require a wrapping element, add `.btn-check` to the `<input>`, and pair it with any `.btn` classes on the `<label>`. [See #30650](https://github.com/twbs/bootstrap/pull/30650). _The docs for this has moved from our Buttons page to the new Forms section._\n\n- <span class=\"badge bg-danger\">Breaking</span> **Dropped `.btn-block` for utilities.** Instead of using `.btn-block` on the `.btn`, wrap your buttons with `.d-grid` and a `.gap-*` utility to space them as needed. Switch to responsive classes for even more control over them. [Read the docs for some examples.]({{< docsref \"/components/buttons#block-buttons\" >}})\n\n- Updated our `button-variant()` and `button-outline-variant()` mixins to support additional parameters.\n\n- Updated buttons to ensure increased contrast on hover and active states.\n\n- Disabled buttons now have `pointer-events: none;`.\n\n### Card\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `.card-deck` in favor of our grid. Wrap your cards in column classes and add a parent `.row-cols-*` container to recreate card decks (but with more control over responsive alignment).\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `.card-columns` in favor of Masonry. [See #28922](https://github.com/twbs/bootstrap/pull/28922).\n\n- <span class=\"badge bg-danger\">Breaking</span> Replaced the `.card` based accordion with a [new Accordion component]({{< docsref \"/components/accordion\" >}}).\n\n### Carousel\n\n- Added new [`.carousel-dark` variant]({{< docsref \"/components/carousel#dark-variant\" >}}) for dark text, controls, and indicators (great for lighter backgrounds).\n\n- Replaced chevron icons for carousel controls with new SVGs from [Bootstrap Icons]({{< param \"icons\" >}}).\n\n### Close button\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `.close` to `.btn-close` for a less generic name.\n\n- Close buttons now use a `background-image` (embedded SVG) instead of a `&times;` in the HTML, allowing for easier customization without the need to touch your markup.\n\n- Added new `.btn-close-white` variant that uses `filter: invert(1)` to enable higher contrast dismiss icons against darker backgrounds.\n\n### Collapse\n\n- Removed scroll anchoring for accordions.\n\n### Dropdowns\n\n- Added new `.dropdown-menu-dark` variant and associated variables for on-demand dark dropdowns.\n\n- Added new variable for `$dropdown-padding-x`.\n\n- Darkened the dropdown divider for improved contrast.\n\n- <span class=\"badge bg-danger\">Breaking</span> All the events for the dropdown are now triggered on the dropdown toggle button and then bubbled up to the parent element.\n\n- Dropdown menus now have a `data-bs-popper=\"static\"` attribute set when the positioning of the dropdown is static, or dropdown is in the navbar. This is added by our JavaScript and helps us use custom position styles without interfering with Popper's positioning.\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped `flip` option for dropdown plugin in favor of native Popper configuration. You can now disable the flipping behavior by passing an empty array for [`fallbackPlacements`](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) option in [flip](https://popper.js.org/docs/v2/modifiers/flip/) modifier.\n\n- Dropdown menus can now be clickable with a new `autoClose` option to handle the [auto close behavior]({{< docsref \"/components/dropdowns#auto-close-behavior\" >}}). You can use this option to accept the click inside or outside the dropdown menu to make it interactive.\n\n- Dropdowns now support `.dropdown-item`s wrapped in `<li>`s.\n\n### Jumbotron\n\n- <span class=\"badge bg-danger\">Breaking</span> Dropped the jumbotron component as it can be replicated with utilities. [See our new Jumbotron example for a demo.]({{< docsref \"/examples/jumbotron\" >}})\n\n### List group\n\n- Added new [`.list-group-numbered` modifier]({{< docsref \"/components/list-group#numbered\" >}}) to list groups.\n\n### Navs and tabs\n\n- Added new `null` variables for `font-size`, `font-weight`, `color`, and `:hover` `color` to the `.nav-link` class.\n\n### Navbars\n\n- <span class=\"badge bg-danger\">Breaking</span> Navbars now require a container within (to drastically simplify spacing requirements and CSS required).\n- <span class=\"badge bg-danger\">Breaking</span> The `.active` class can no longer be applied to `.nav-item`s, it must be applied directly on `.nav-link`s.\n\n### Offcanvas\n\n- Added the new [offcanvas component]({{< docsref \"/components/offcanvas\" >}}).\n\n### Pagination\n\n- Pagination links now have customizable `margin-left` that are dynamically rounded on all corners when separated from one another.\n\n- Added `transition`s to pagination links.\n\n### Popovers\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `.arrow` to `.popover-arrow` in our default popover template.\n\n- Renamed `whiteList` option to `allowList`.\n\n### Spinners\n\n- Spinners now honor `prefers-reduced-motion: reduce` by slowing down animations. [See #31882](https://github.com/twbs/bootstrap/pull/31882).\n\n- Improved spinner vertical alignment.\n\n### Toasts\n\n- Toasts can now be [positioned]({{< docsref \"/components/toasts#placement\" >}}) in a `.toast-container` with the help of [positioning utilities]({{< docsref \"/utilities/position\" >}}).\n\n- Changed default toast duration to 5 seconds.\n\n- Removed `overflow: hidden` from toasts and replaced with proper `border-radius`s with `calc()` functions.\n\n### Tooltips\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `.arrow` to `.tooltip-arrow` in our default tooltip template.\n\n- <span class=\"badge bg-danger\">Breaking</span> The default value for the `fallbackPlacements` is changed to `['top', 'right', 'bottom', 'left']` for better placement of popper elements.\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `whiteList` option to `allowList`.\n\n## Utilities\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed several utilities to use logical property names instead of directional names with the addition of RTL support:\n  - Renamed `.left-*` and `.right-*` to `.start-*` and `.end-*`.\n  - Renamed `.float-left` and `.float-right` to `.float-start` and `.float-end`.\n  - Renamed `.border-left` and `.border-right` to `.border-start` and `.border-end`.\n  - Renamed `.rounded-left` and `.rounded-right` to `.rounded-start` and `.rounded-end`.\n  - Renamed `.ml-*` and `.mr-*` to `.ms-*` and `.me-*`.\n  - Renamed `.pl-*` and `.pr-*` to `.ps-*` and `.pe-*`.\n  - Renamed `.text-left` and `.text-right` to `.text-start` and `.text-end`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Disabled negative margins by default.\n\n- Added new `.bg-body` class for quickly setting the `<body>`'s background to additional elements.\n\n- Added new [position utilities]({{< docsref \"/utilities/position#arrange-elements\" >}}) for `top`, `right`, `bottom`, and `left`. Values include `0`, `50%`, and `100%` for each property.\n\n- Added new `.translate-middle-x` & `.translate-middle-y` utilities to horizontally or vertically center absolute/fixed positioned elements.\n\n- Added new [`border-width` utilities]({{< docsref \"/utilities/borders#border-width\" >}}).\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `.text-monospace` to `.font-monospace`.\n\n- <span class=\"badge bg-danger\">Breaking</span> Removed `.text-hide` as it's an antiquated method for hiding text that shouldn't be used anymore.\n\n- Added `.fs-*` utilities for `font-size` utilities (with RFS enabled). These use the same scale as HTML's default headings (1-6, large to small), and can be modified via Sass map.\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `.font-weight-*` utilities as `.fw-*` for brevity and consistency.\n\n- <span class=\"badge bg-danger\">Breaking</span> Renamed `.font-style-*` utilities as `.fst-*` for brevity and consistency.\n\n- Added `.d-grid` to display utilities and new `gap` utilities (`.gap`) for CSS Grid and flexbox layouts.\n\n- <span class=\"badge bg-danger\">Breaking</span> Removed `.rounded-sm` and `rounded-lg`, and introduced a new scale of classes, `.rounded-0` to `.rounded-3`. [See #31687](https://github.com/twbs/bootstrap/pull/31687).\n\n- Added new `line-height` utilities: `.lh-1`, `.lh-sm`, `.lh-base` and `.lh-lg`. See [here]({{< docsref \"/utilities/text#line-height\" >}}).\n\n- Moved the `.d-none` utility in our CSS to give it more weight over other display utilities.\n\n- Extended the `.visually-hidden-focusable` helper to also work on containers, using `:focus-within`.\n\n## Helpers\n\n- <span class=\"badge bg-danger\">Breaking</span> **Responsive embed helpers have been renamed to [ratio helpers]({{< docsref \"/helpers/ratio\" >}})** with new class names and improved behaviors, as well as a helpful CSS variable.\n  - Classes have been renamed to change `by` to `x` in the aspect ratio. For example, `.ratio-16by9` is now `.ratio-16x9`.\n  - We've dropped the `.embed-responsive-item` and element group selector in favor of a simpler `.ratio > *` selector. No more class is needed, and the ratio helper now works with any HTML element.\n  - The `$embed-responsive-aspect-ratios` Sass map has been renamed to `$aspect-ratios` and its values have been simplified to include the class name and the percentage as the `key: value` pair.\n  - CSS variables are now generated and included for each value in the Sass map. Modify the `--bs-aspect-ratio` variable on the `.ratio` to create any [custom aspect ratio]({{< docsref \"/helpers/ratio#custom-ratios\" >}}).\n\n- <span class=\"badge bg-danger\">Breaking</span> **\"Screen reader\" classes are now [\"visually hidden\" classes]({{< docsref \"/helpers/visually-hidden\" >}}).**\n  - Changed the Sass file from `scss/helpers/_screenreaders.scss` to `scss/helpers/_visually-hidden.scss`\n  - Renamed `.sr-only` and `.sr-only-focusable` to `.visually-hidden` and `.visually-hidden-focusable`\n  - Renamed `sr-only()` and `sr-only-focusable()` mixins to `visually-hidden()` and `visually-hidden-focusable()`.\n\n- `bootstrap-utilities.css` now also includes our helpers. Helpers don't need to be imported in custom builds anymore.\n\n## JavaScript\n\n- **Dropped jQuery dependency** and rewrote plugins to be in regular JavaScript.\n\n- <span class=\"badge bg-danger\">Breaking</span> Data attributes for all JavaScript plugins are now namespaced to help distinguish Bootstrap functionality from third parties and your own code. For example, we use `data-bs-toggle` instead of `data-toggle`.\n\n- **All plugins can now accept a CSS selector as the first argument.** You can either pass a DOM element or any valid CSS selector to create a new instance of the plugin:\n\n  ```js\n  const modal = new bootstrap.Modal('#myModal')\n  const dropdown = new bootstrap.Dropdown('[data-bs-toggle=\"dropdown\"]')\n  ```\n\n- `popperConfig` can be passed as a function that accepts the Bootstrap's default Popper config as an argument, so that you can merge this default configuration in your way. **Applies to dropdowns, popovers, and tooltips.**\n\n- The default value for the `fallbackPlacements` is changed to `['top', 'right', 'bottom', 'left']` for better placement of Popper elements. **Applies to dropdowns, popovers, and tooltips.**\n\n- Removed underscore from public static methods like `_getInstance()` → `getInstance()`.\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/api.md",
    "content": "---\nlayout: docs\ntitle: Utility API\ndescription: The utility API is a Sass-based tool to generate utility classes.\ngroup: utilities\naliases: \"/docs/5.2/utilities/\"\ntoc: true\n---\n\nBootstrap utilities are generated with our utility API and can be used to modify or extend our default set of utility classes via Sass. Our utility API is based on a series of Sass maps and functions for generating families of classes with various options. If you're unfamiliar with Sass maps, read up on the [official Sass docs](https://sass-lang.com/documentation/values/maps) to get started.\n\nThe `$utilities` map contains all our utilities and is later merged with your custom `$utilities` map, if present. The utility map contains a keyed list of utility groups which accept the following options:\n\n{{< bs-table \"table table-utilities\" >}}\n| Option | Type | Default&nbsp;value | Description |\n| --- | --- | --- | --- |\n| [`property`](#property) | **Required** | – | Name of the property, this can be a string or an array of strings (e.g., horizontal paddings or margins). |\n| [`values`](#values) | **Required** | – | List of values, or a map if you don't want the class name to be the same as the value. If `null` is used as map key, `class` is not prepended to the class name. |\n| [`class`](#class) | Optional | null | Name of the generated class. If not provided and `property` is an array of strings, `class` will default to the first element of the `property` array. If not provided and `property` is a string, the `values` keys are used for the `class` names. |\n| [`css-var`](#css-variable-utilities) | Optional | `false` | Boolean to generate CSS variables instead of CSS rules. |\n| [`css-variable-name`](#css-variable-utilities) | Optional | null | Custom un-prefixed name for the CSS variable inside the ruleset. |\n| [`local-vars`](#local-css-variables) | Optional | null | Map of local CSS variables to generate in addition to the CSS rules. |\n| [`state`](#states) | Optional | null | List of pseudo-class variants (e.g., `:hover` or `:focus`) to generate. |\n| [`responsive`](#responsive) | Optional | `false` | Boolean indicating if responsive classes should be generated. |\n| `rfs` | Optional | `false` | Boolean to enable [fluid rescaling with RFS]({{< docsref \"/getting-started/rfs\" >}}). |\n| [`print`](#print) | Optional | `false` | Boolean indicating if print classes need to be generated. |\n| `rtl` | Optional | `true` | Boolean indicating if utility should be kept in RTL. |\n{{< /bs-table >}}\n\n## API explained\n\nAll utility variables are added to the `$utilities` variable within our `_utilities.scss` stylesheet. Each group of utilities looks something like this:\n\n```scss\n$utilities: (\n  \"opacity\": (\n    property: opacity,\n    values: (\n      0: 0,\n      25: .25,\n      50: .5,\n      75: .75,\n      100: 1,\n    )\n  )\n);\n```\n\nWhich outputs the following:\n\n```css\n.opacity-0 { opacity: 0; }\n.opacity-25 { opacity: .25; }\n.opacity-50 { opacity: .5; }\n.opacity-75 { opacity: .75; }\n.opacity-100 { opacity: 1; }\n```\n\n### Property\n\nThe required `property` key must be set for any utility, and it must contain a valid CSS property. This property is used in the generated utility's ruleset. When the `class` key is omitted, it also serves as the default class name. Consider the `text-decoration` utility:\n\n```scss\n$utilities: (\n  \"text-decoration\": (\n    property: text-decoration,\n    values: none underline line-through\n  )\n);\n```\n\nOutput:\n\n```css\n.text-decoration-none { text-decoration: none !important; }\n.text-decoration-underline { text-decoration: underline !important; }\n.text-decoration-line-through { text-decoration: line-through !important; }\n```\n\n### Values\n\nUse the `values` key to specify which values for the specified `property` should be used in the generated class names and rules. Can be a list or map (set in the utilities or in a Sass variable).\n\nAs a list, like with [`text-decoration` utilities]({{< docsref \"/utilities/text#text-decoration\" >}}):\n\n```scss\nvalues: none underline line-through\n```\n\nAs a map, like with [`opacity` utilities]({{< docsref \"/utilities/opacity\" >}}):\n\n```scss\nvalues: (\n  0: 0,\n  25: .25,\n  50: .5,\n  75: .75,\n  100: 1,\n)\n```\n\nAs a Sass variable that sets the list or map, as in our [`position` utilities]({{< docsref \"/utilities/position\" >}}):\n\n```scss\nvalues: $position-values\n```\n\n### Class\n\nUse the `class` option to change the class prefix used in the compiled CSS. For example, to change from `.opacity-*` to `.o-*`:\n\n```scss\n$utilities: (\n  \"opacity\": (\n    property: opacity,\n    class: o,\n    values: (\n      0: 0,\n      25: .25,\n      50: .5,\n      75: .75,\n      100: 1,\n    )\n  )\n);\n```\n\nOutput:\n\n```css\n.o-0 { opacity: 0 !important; }\n.o-25 { opacity: .25 !important; }\n.o-50 { opacity: .5 !important; }\n.o-75 { opacity: .75 !important; }\n.o-100 { opacity: 1 !important; }\n```\n\nIf `class: null`, generates classes for each of the `values` keys:\n\n```scss\n$utilities: (\n  \"visibility\": (\n    property: visibility,\n    class: null,\n    values: (\n      visible: visible,\n      invisible: hidden,\n    )\n  )\n);\n```\n\nOutput:\n\n```css\n.visible { visibility: visible !important; }\n.invisible { visibility: hidden !important; }\n```\n\n### CSS variable utilities\n\nSet the `css-var` boolean option to `true` and the API will generate local CSS variables for the given selector instead of the usual `property: value` rules. Add an optional `css-variable-name` to set a different CSS variable name than the class name.\n\nConsider our `.text-opacity-*` utilities. If we add the `css-variable-name` option, we'll get a custom output.\n\n```scss\n$utilities: (\n  \"text-opacity\": (\n    css-var: true,\n    css-variable-name: text-alpha,\n    class: text-opacity,\n    values: (\n      25: .25,\n      50: .5,\n      75: .75,\n      100: 1\n    )\n  ),\n);\n```\n\nOutput:\n\n```css\n.text-opacity-25 { --bs-text-alpha: .25; }\n.text-opacity-50 { --bs-text-alpha: .5; }\n.text-opacity-75 { --bs-text-alpha: .75; }\n.text-opacity-100 { --bs-text-alpha: 1; }\n```\n\n### Local CSS variables\n\nUse the `local-vars` option to specify a Sass map that will generate local CSS variables within the utility class's ruleset. Please note that it may require additional work to consume those local CSS variables in the generated CSS rules. For example, consider our `.bg-*` utilities:\n\n```scss\n$utilities: (\n  \"background-color\": (\n    property: background-color,\n    class: bg,\n    local-vars: (\n      \"bg-opacity\": 1\n    ),\n    values: map-merge(\n      $utilities-bg-colors,\n      (\n        \"transparent\": transparent\n      )\n    )\n  )\n);\n```\n\nOutput:\n\n```css\n.bg-primary {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;\n}\n```\n\n### States\n\nUse the `state` option to generate pseudo-class variations. Example pseudo-classes are `:hover` and `:focus`. When a list of states are provided, classnames are created for that pseudo-class. For example, to change opacity on hover, add `state: hover` and you'll get `.opacity-hover:hover` in your compiled CSS.\n\nNeed multiple pseudo-classes? Use a space-separated list of states: `state: hover focus`.\n\n```scss\n$utilities: (\n  \"opacity\": (\n    property: opacity,\n    class: opacity,\n    state: hover,\n    values: (\n      0: 0,\n      25: .25,\n      50: .5,\n      75: .75,\n      100: 1,\n    )\n  )\n);\n```\n\nOutput:\n\n```css\n.opacity-0-hover:hover { opacity: 0 !important; }\n.opacity-25-hover:hover { opacity: .25 !important; }\n.opacity-50-hover:hover { opacity: .5 !important; }\n.opacity-75-hover:hover { opacity: .75 !important; }\n.opacity-100-hover:hover { opacity: 1 !important; }\n```\n\n### Responsive\n\nAdd the `responsive` boolean to generate responsive utilities (e.g., `.opacity-md-25`) across [all breakpoints]({{< docsref \"/layout/breakpoints\" >}}).\n\n```scss\n$utilities: (\n  \"opacity\": (\n    property: opacity,\n    responsive: true,\n    values: (\n      0: 0,\n      25: .25,\n      50: .5,\n      75: .75,\n      100: 1,\n    )\n  )\n);\n```\n\nOutput:\n\n```css\n.opacity-0 { opacity: 0 !important; }\n.opacity-25 { opacity: .25 !important; }\n.opacity-50 { opacity: .5 !important; }\n.opacity-75 { opacity: .75 !important; }\n.opacity-100 { opacity: 1 !important; }\n\n@media (min-width: 576px) {\n  .opacity-sm-0 { opacity: 0 !important; }\n  .opacity-sm-25 { opacity: .25 !important; }\n  .opacity-sm-50 { opacity: .5 !important; }\n  .opacity-sm-75 { opacity: .75 !important; }\n  .opacity-sm-100 { opacity: 1 !important; }\n}\n\n@media (min-width: 768px) {\n  .opacity-md-0 { opacity: 0 !important; }\n  .opacity-md-25 { opacity: .25 !important; }\n  .opacity-md-50 { opacity: .5 !important; }\n  .opacity-md-75 { opacity: .75 !important; }\n  .opacity-md-100 { opacity: 1 !important; }\n}\n\n@media (min-width: 992px) {\n  .opacity-lg-0 { opacity: 0 !important; }\n  .opacity-lg-25 { opacity: .25 !important; }\n  .opacity-lg-50 { opacity: .5 !important; }\n  .opacity-lg-75 { opacity: .75 !important; }\n  .opacity-lg-100 { opacity: 1 !important; }\n}\n\n@media (min-width: 1200px) {\n  .opacity-xl-0 { opacity: 0 !important; }\n  .opacity-xl-25 { opacity: .25 !important; }\n  .opacity-xl-50 { opacity: .5 !important; }\n  .opacity-xl-75 { opacity: .75 !important; }\n  .opacity-xl-100 { opacity: 1 !important; }\n}\n\n@media (min-width: 1400px) {\n  .opacity-xxl-0 { opacity: 0 !important; }\n  .opacity-xxl-25 { opacity: .25 !important; }\n  .opacity-xxl-50 { opacity: .5 !important; }\n  .opacity-xxl-75 { opacity: .75 !important; }\n  .opacity-xxl-100 { opacity: 1 !important; }\n}\n```\n\n### Print\n\nEnabling the `print` option will **also** generate utility classes for print, which are only applied within the `@media print { ... }` media query.\n\n```scss\n$utilities: (\n  \"opacity\": (\n    property: opacity,\n    print: true,\n    values: (\n      0: 0,\n      25: .25,\n      50: .5,\n      75: .75,\n      100: 1,\n    )\n  )\n);\n```\n\nOutput:\n\n```css\n.opacity-0 { opacity: 0 !important; }\n.opacity-25 { opacity: .25 !important; }\n.opacity-50 { opacity: .5 !important; }\n.opacity-75 { opacity: .75 !important; }\n.opacity-100 { opacity: 1 !important; }\n\n@media print {\n  .opacity-print-0 { opacity: 0 !important; }\n  .opacity-print-25 { opacity: .25 !important; }\n  .opacity-print-50 { opacity: .5 !important; }\n  .opacity-print-75 { opacity: .75 !important; }\n  .opacity-print-100 { opacity: 1 !important; }\n}\n```\n\n## Importance\n\nAll utilities generated by the API include `!important` to ensure they override components and modifier classes as intended. You can toggle this setting globally with the `$enable-important-utilities` variable (defaults to `true`).\n\n## Using the API\n\nNow that you're familiar with how the utilities API works, learn how to add your own custom classes and modify our default utilities.\n\n### Override utilities\n\nOverride existing utilities by using the same key. For example, if you want additional responsive overflow utility classes, you can do this:\n\n```scss\n$utilities: (\n  \"overflow\": (\n    responsive: true,\n    property: overflow,\n    values: visible hidden scroll auto,\n  ),\n);\n```\n\n### Add utilities\n\nNew utilities can be added to the default `$utilities` map with a `map-merge`. Make sure our required Sass files and `_utilities.scss` are imported first, then use the `map-merge` to add your additional utilities. For example, here's how to add a responsive `cursor` utility with three values.\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$utilities: map-merge(\n  $utilities,\n  (\n    \"cursor\": (\n      property: cursor,\n      class: cursor,\n      responsive: true,\n      values: auto pointer grab,\n    )\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\n### Modify utilities\n\nModify existing utilities in the default `$utilities` map with `map-get` and `map-merge` functions. In the example below, we're adding an additional value to the `width` utilities. Start with an initial `map-merge` and then specify which utility you want to modify. From there, fetch the nested `\"width\"` map with `map-get` to access and modify the utility's options and values.\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$utilities: map-merge(\n  $utilities,\n  (\n    \"width\": map-merge(\n      map-get($utilities, \"width\"),\n      (\n        values: map-merge(\n          map-get(map-get($utilities, \"width\"), \"values\"),\n          (10: 10%),\n        ),\n      ),\n    ),\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\n#### Enable responsive\n\nYou can enable responsive classes for an existing set of utilities that are not currently responsive by default. For example, to make the `border` classes responsive:\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$utilities: map-merge(\n  $utilities, (\n    \"border\": map-merge(\n      map-get($utilities, \"border\"),\n      ( responsive: true ),\n    ),\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\nThis will now generate responsive variations of `.border` and `.border-0` for each breakpoint. Your generated CSS will look like this:\n\n```css\n.border { ... }\n.border-0 { ... }\n\n@media (min-width: 576px) {\n  .border-sm { ... }\n  .border-sm-0 { ... }\n}\n\n@media (min-width: 768px) {\n  .border-md { ... }\n  .border-md-0 { ... }\n}\n\n@media (min-width: 992px) {\n  .border-lg { ... }\n  .border-lg-0 { ... }\n}\n\n@media (min-width: 1200px) {\n  .border-xl { ... }\n  .border-xl-0 { ... }\n}\n\n@media (min-width: 1400px) {\n  .border-xxl { ... }\n  .border-xxl-0 { ... }\n}\n```\n\n#### Rename utilities\n\nMissing v4 utilities, or used to another naming convention? The utilities API can be used to override the resulting `class` of a given utility—for example, to rename `.ms-*` utilities to oldish `.ml-*`:\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$utilities: map-merge(\n  $utilities, (\n    \"margin-start\": map-merge(\n      map-get($utilities, \"margin-start\"),\n      ( class: ml ),\n    ),\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\n### Remove utilities\n\nRemove any of the default utilities with the [`map-remove()` Sass function](https://sass-lang.com/documentation/modules/map#remove).\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n// Remove multiple utilities with a comma-separated list\n$utilities: map-remove($utilities, \"width\", \"float\");\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\nYou can also use the [`map-merge()` Sass function](https://sass-lang.com/documentation/modules/map#merge) and set the group key to `null` to remove the utility.\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$utilities: map-merge(\n  $utilities,\n  (\n    \"width\": null\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\n### Add, remove, modify\n\nYou can add, remove, and modify many utilities all at once with the [`map-merge()` Sass function](https://sass-lang.com/documentation/modules/map#merge). Here's how you can combine the previous examples into one larger map.\n\n```scss\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"bootstrap/scss/maps\";\n@import \"bootstrap/scss/mixins\";\n@import \"bootstrap/scss/utilities\";\n\n$utilities: map-merge(\n  $utilities,\n  (\n    // Remove the `width` utility\n    \"width\": null,\n\n    // Make an existing utility responsive\n    \"border\": map-merge(\n      map-get($utilities, \"border\"),\n      ( responsive: true ),\n    ),\n\n    // Add new utilities\n    \"cursor\": (\n      property: cursor,\n      class: cursor,\n      responsive: true,\n      values: auto pointer grab,\n    )\n  )\n);\n\n@import \"bootstrap/scss/utilities/api\";\n```\n\n#### Remove utility in RTL\n\nSome edge cases make [RTL styling difficult](https://rtlstyling.com/posts/rtl-styling#common-things-that-might-not-work-for-rtl), such as line breaks in Arabic. Thus utilities can be dropped from RTL output by setting the `rtl` option to `false`:\n\n```scss\n$utilities: (\n  \"word-wrap\": (\n    property: word-wrap word-break,\n    class: text,\n    values: (break: break-word),\n    rtl: false\n  ),\n);\n```\n\nOutput:\n\n```css\n/* rtl:begin:remove */\n.text-break {\n  word-wrap: break-word !important;\n  word-break: break-word !important;\n}\n/* rtl:end:remove */\n```\n\nThis doesn't output anything in RTL, thanks to [the RTLCSS `remove` control directive](https://rtlcss.com/learn/usage-guide/control-directives/#remove).\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/background.md",
    "content": "---\nlayout: docs\ntitle: Background\ndescription: Convey meaning through `background-color` and add decoration with gradients.\ngroup: utilities\ntoc: true\n---\n\n## Background color\n\nSimilar to the contextual text color classes, set the background of an element to any contextual class. Background utilities **do not set `color`**, so in some cases you'll want to use `.text-*` [color utilities]({{< docsref \"/utilities/colors\" >}}).\n\n{{< example >}}\n{{< colors.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"p-3 mb-2 bg-{{ .name }}{{ if .contrast_color }} text-{{ .contrast_color }}{{ else }} text-white{{ end }}\">.bg-{{ .name }}</div>\n{{- end -}}\n{{< /colors.inline >}}\n<div class=\"p-3 mb-2 bg-body text-dark\">.bg-body</div>\n<div class=\"p-3 mb-2 bg-white text-dark\">.bg-white</div>\n<div class=\"p-3 mb-2 bg-transparent text-dark\">.bg-transparent</div>\n{{< /example >}}\n\n## Background gradient\n\nBy adding a `.bg-gradient` class, a linear gradient is added as background image to the backgrounds. This gradient starts with a semi-transparent white which fades out to the bottom.\n\nDo you need a gradient in your custom CSS? Just add `background-image: var(--bs-gradient);`.\n\n{{< markdown >}}\n{{< colors.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<div class=\"p-3 mb-2 bg-{{ .name }} bg-gradient{{ with .contrast_color }} text-{{ . }}{{ else }} text-white{{ end }}\">.bg-{{ .name }}.bg-gradient</div>\n{{- end -}}\n{{< /colors.inline >}}\n{{< /markdown >}}\n\n## Opacity\n\n{{< added-in \"5.1.0\" >}}\n\nAs of v5.1.0, `background-color` utilities are generated with Sass using CSS variables. This allows for real-time color changes without compilation and dynamic alpha transparency changes.\n\n### How it works\n\nConsider our default `.bg-success` utility.\n\n```css\n.bg-success {\n  --bs-bg-opacity: 1;\n  background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;\n}\n```\n\nWe use an RGB version of our `--bs-success` (with the value of `25, 135, 84`) CSS variable and attached a second CSS variable, `--bs-bg-opacity`, for the alpha transparency (with a default value `1` thanks to a local CSS variable). That means anytime you use `.bg-success` now, your computed `color` value is `rgba(25, 135, 84, 1)`. The local CSS variable inside each `.bg-*` class avoids inheritance issues so nested instances of the utilities don't automatically have a modified alpha transparency.\n\n### Example\n\nTo change that opacity, override `--bs-bg-opacity` via custom styles or inline styles.\n\n{{< example >}}\n<div class=\"bg-success p-2 text-white\">This is default success background</div>\n<div class=\"bg-success p-2\" style=\"--bs-bg-opacity: .5;\">This is 50% opacity success background</div>\n{{< /example >}}\n\nOr, choose from any of the `.bg-opacity` utilities:\n\n{{< example >}}\n<div class=\"bg-success p-2 text-white\">This is default success background</div>\n<div class=\"bg-success p-2 text-white bg-opacity-75\">This is 75% opacity success background</div>\n<div class=\"bg-success p-2 text-dark bg-opacity-50\">This is 50% opacity success background</div>\n<div class=\"bg-success p-2 text-dark bg-opacity-25\">This is 25% opacity success background</div>\n<div class=\"bg-success p-2 text-dark bg-opacity-10\">This is 10% opacity success background</div>\n{{< /example >}}\n\n## Sass\n\nIn addition to the following Sass functionality, consider reading about our included [CSS custom properties]({{< docsref \"/customize/css-variables\" >}}) (aka CSS variables) for colors and more.\n\n### Variables\n\nMost `background-color` utilities are generated by our theme colors, reassigned from our generic color palette variables.\n\n{{< scss-docs name=\"color-variables\" file=\"scss/_variables.scss\" >}}\n\n{{< scss-docs name=\"theme-color-variables\" file=\"scss/_variables.scss\" >}}\n\n{{< scss-docs name=\"variable-gradient\" file=\"scss/_variables.scss\" >}}\n\nGrayscale colors are also available, but only a subset are used to generate any utilities.\n\n{{< scss-docs name=\"gray-color-variables\" file=\"scss/_variables.scss\" >}}\n\n### Map\n\nTheme colors are then put into a Sass map so we can loop over them to generate our utilities, component modifiers, and more.\n\n{{< scss-docs name=\"theme-colors-map\" file=\"scss/_variables.scss\" >}}\n\nGrayscale colors are also available as a Sass map. **This map is not used to generate any utilities.**\n\n{{< scss-docs name=\"gray-colors-map\" file=\"scss/_variables.scss\" >}}\n\nRGB colors are generated from a separate Sass map:\n\n{{< scss-docs name=\"theme-colors-rgb\" file=\"scss/_maps.scss\" >}}\n\nAnd background color opacities build on that with their own map that's consumed by the utilities API:\n\n{{< scss-docs name=\"utilities-bg-colors\" file=\"scss/_maps.scss\" >}}\n\n### Mixins\n\n**No mixins are used to generate our background utilities**, but we do have some additional mixins for other situations where you'd like to create your own gradients.\n\n{{< scss-docs name=\"gradient-bg-mixin\" file=\"scss/mixins/_gradients.scss\" >}}\n\n{{< scss-docs name=\"gradient-mixins\" file=\"scss/mixins/_gradients.scss\" >}}\n\n### Utilities API\n\nBackground utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-bg-color\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/borders.md",
    "content": "---\nlayout: docs\ntitle: Borders\ndescription: Use border utilities to quickly style the border and border-radius of an element. Great for images, buttons, or any other element.\ngroup: utilities\ntoc: true\n---\n\n## Border\n\nUse border utilities to add or remove an element's borders. Choose from all borders or one at a time.\n\n### Additive\n\nAdd borders to custom elements:\n\n{{< example class=\"bd-example-border-utils\" >}}\n<span class=\"border\"></span>\n<span class=\"border-top\"></span>\n<span class=\"border-end\"></span>\n<span class=\"border-bottom\"></span>\n<span class=\"border-start\"></span>\n{{< /example >}}\n\n### Subtractive\n\nOr remove borders:\n\n{{< example class=\"bd-example-border-utils\" >}}\n<span class=\"border border-0\"></span>\n<span class=\"border border-top-0\"></span>\n<span class=\"border border-end-0\"></span>\n<span class=\"border border-bottom-0\"></span>\n<span class=\"border border-start-0\"></span>\n{{< /example >}}\n\n## Color\n\nChange the border color using utilities built on our theme colors.\n\n{{< example class=\"bd-example-border-utils\" >}}\n{{< border.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<span class=\"border border-{{ .name }}\"></span>\n{{- end -}}\n{{< /border.inline >}}\n<span class=\"border border-white\"></span>\n{{< /example >}}\n\nOr modify the default `border-color` of a component:\n\n{{< example >}}\n<div class=\"mb-4\">\n  <label for=\"exampleFormControlInput1\" class=\"form-label\">Email address</label>\n  <input type=\"email\" class=\"form-control border-success\" id=\"exampleFormControlInput1\" placeholder=\"name@example.com\">\n</div>\n\n<div class=\"h4 pb-2 mb-4 text-danger border-bottom border-danger\">\n  Dangerous heading\n</div>\n\n<div class=\"p-3 bg-info bg-opacity-10 border border-info border-start-0 rounded-end\">\n  Changing border color and width\n</div>\n{{< /example >}}\n\n## Opacity\n\n{{< added-in \"5.2.0\" >}}\n\nBootstrap `border-{color}` utilities are generated with Sass using CSS variables. This allows for real-time color changes without compilation and dynamic alpha transparency changes.\n\n### How it works\n\nConsider our default `.border-success` utility.\n\n```css\n.border-success {\n  --bs-border-opacity: 1;\n  border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;\n}\n```\n\nWe use an RGB version of our `--bs-success` (with the value of `25, 135, 84`) CSS variable and attached a second CSS variable, `--bs-border-opacity`, for the alpha transparency (with a default value `1` thanks to a local CSS variable). That means anytime you use `.border-success` now, your computed `color` value is `rgba(25, 135, 84, 1)`. The local CSS variable inside each `.border-*` class avoids inheritance issues so nested instances of the utilities don't automatically have a modified alpha transparency.\n\n### Example\n\nTo change that opacity, override `--bs-border-opacity` via custom styles or inline styles.\n\n{{< example >}}\n<div class=\"border border-success p-2 mb-2\">This is default success border</div>\n<div class=\"border border-success p-2\" style=\"--bs-border-opacity: .5;\">This is 50% opacity success border</div>\n{{< /example >}}\n\nOr, choose from any of the `.border-opacity` utilities:\n\n{{< example >}}\n<div class=\"border border-success p-2 mb-2\">This is default success border</div>\n<div class=\"border border-success p-2 mb-2 border-opacity-75\">This is 75% opacity success border</div>\n<div class=\"border border-success p-2 mb-2 border-opacity-50\">This is 50% opacity success border</div>\n<div class=\"border border-success p-2 mb-2 border-opacity-25\">This is 25% opacity success border</div>\n<div class=\"border border-success p-2 border-opacity-10\">This is 10% opacity success border</div>\n{{< /example >}}\n\n## Width\n\n{{< example class=\"bd-example-border-utils\" >}}\n<span class=\"border border-1\"></span>\n<span class=\"border border-2\"></span>\n<span class=\"border border-3\"></span>\n<span class=\"border border-4\"></span>\n<span class=\"border border-5\"></span>\n{{< /example >}}\n\n## Radius\n\nAdd classes to an element to easily round its corners.\n\n{{< example class=\"bd-example-rounded-utils\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded\" title=\"Example rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-top\" title=\"Example top rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-end\" title=\"Example right rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-bottom\" title=\"Example bottom rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-start\" title=\"Example left rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-circle\" title=\"Completely round image\" >}}\n{{< placeholder width=\"150\" height=\"75\" class=\"rounded-pill\" title=\"Rounded pill image\" >}}\n{{< /example >}}\n\n### Sizes\n\nUse the scaling classes for larger or smaller rounded corners. Sizes range from `0` to `5`, and can be configured by modifying the utilities API.\n\n{{< example class=\"bd-example-rounded-utils\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-0\" title=\"Example non-rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-1\" title=\"Example small rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-2\" title=\"Example default rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-3\" title=\"Example large rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-4\" title=\"Example larger rounded image\" >}}\n{{< placeholder width=\"75\" height=\"75\" class=\"rounded-5\" title=\"Example extra large rounded image\" >}}\n{{< /example >}}\n\n## CSS\n\n### Variables\n\n{{< added-in \"5.2.0\" >}}\n\n{{< scss-docs name=\"root-border-var\" file=\"scss/_root.scss\" >}}\n\n### Sass variables\n\n{{< scss-docs name=\"border-variables\" file=\"scss/_variables.scss\" >}}\n\n{{< scss-docs name=\"border-radius-variables\" file=\"scss/_variables.scss\" >}}\n\n### Sass mixins\n\n{{< scss-docs name=\"border-radius-mixins\" file=\"scss/mixins/_border-radius.scss\" >}}\n\n### Utilities API\n\nBorder utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-borders\" file=\"scss/_utilities.scss\" >}}\n\n{{< scss-docs name=\"utils-border-radius\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/colors.md",
    "content": "---\nlayout: docs\ntitle: Colors\ndescription: Convey meaning through `color` with a handful of color utility classes. Includes support for styling links with hover states, too.\ngroup: utilities\ntoc: true\n---\n\n## Colors\n\nColorize text with color utilities. If you want to colorize links, you can use the [`.link-*` helper classes]({{< docsref \"/helpers/colored-links\" >}}) which have `:hover` and `:focus` states.\n\n{{< example >}}\n{{< colors.inline >}}\n{{- range (index $.Site.Data \"theme-colors\") }}\n<p class=\"text-{{ .name }}{{ with .contrast_color }} bg-{{ . }}{{ end }}\">.text-{{ .name }}</p>\n{{- end -}}\n{{< /colors.inline >}}\n<p class=\"text-body\">.text-body</p>\n<p class=\"text-muted\">.text-muted</p>\n<p class=\"text-white bg-dark\">.text-white</p>\n<p class=\"text-black-50\">.text-black-50</p>\n<p class=\"text-white-50 bg-dark\">.text-white-50</p>\n{{< /example >}}\n\n{{< callout warning >}}\n**Deprecation:** With the addition of `.text-opacity-*` utilities and CSS variables for text utilities, `.text-black-50` and `.text-white-50` are deprecated as of v5.1.0. They'll be removed in v6.0.0.\n{{< /callout >}}\n\n{{< callout info >}}\n{{< partial \"callout-warning-color-assistive-technologies.md\" >}}\n{{< /callout >}}\n\n## Opacity\n\n{{< added-in \"5.1.0\" >}}\n\nAs of v5.1.0, text color utilities are generated with Sass using CSS variables. This allows for real-time color changes without compilation and dynamic alpha transparency changes.\n\n### How it works\n\nConsider our default `.text-primary` utility.\n\n```css\n.text-primary {\n  --bs-text-opacity: 1;\n  color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;\n}\n```\n\nWe use an RGB version of our `--bs-primary` (with the value of `13, 110, 253`) CSS variable and attached a second CSS variable, `--bs-text-opacity`, for the alpha transparency (with a default value `1` thanks to a local CSS variable). That means anytime you use `.text-primary` now, your computed `color` value is `rgba(13, 110, 253, 1)`. The local CSS variable inside each `.text-*` class avoids inheritance issues so nested instances of the utilities don't automatically have a modified alpha transparency.\n\n### Example\n\nTo change that opacity, override `--bs-text-opacity` via custom styles or inline styles.\n\n{{< example >}}\n<div class=\"text-primary\">This is default primary text</div>\n<div class=\"text-primary\" style=\"--bs-text-opacity: .5;\">This is 50% opacity primary text</div>\n{{< /example >}}\n\nOr, choose from any of the `.text-opacity` utilities:\n\n{{< example >}}\n<div class=\"text-primary\">This is default primary text</div>\n<div class=\"text-primary text-opacity-75\">This is 75% opacity primary text</div>\n<div class=\"text-primary text-opacity-50\">This is 50% opacity primary text</div>\n<div class=\"text-primary text-opacity-25\">This is 25% opacity primary text</div>\n{{< /example >}}\n\n## Specificity\n\nSometimes contextual classes cannot be applied due to the specificity of another selector. In some cases, a sufficient workaround is to wrap your element's content in a `<div>` or more semantic element with the desired class.\n\n## Sass\n\nIn addition to the following Sass functionality, consider reading about our included [CSS custom properties]({{< docsref \"/customize/css-variables\" >}}) (aka CSS variables) for colors and more.\n\n### Variables\n\nMost `color` utilities are generated by our theme colors, reassigned from our generic color palette variables.\n\n{{< scss-docs name=\"color-variables\" file=\"scss/_variables.scss\" >}}\n\n{{< scss-docs name=\"theme-color-variables\" file=\"scss/_variables.scss\" >}}\n\nGrayscale colors are also available, but only a subset are used to generate any utilities.\n\n{{< scss-docs name=\"gray-color-variables\" file=\"scss/_variables.scss\" >}}\n\n### Map\n\nTheme colors are then put into a Sass map so we can loop over them to generate our utilities, component modifiers, and more.\n\n{{< scss-docs name=\"theme-colors-map\" file=\"scss/_variables.scss\" >}}\n\nGrayscale colors are also available as a Sass map. **This map is not used to generate any utilities.**\n\n{{< scss-docs name=\"gray-colors-map\" file=\"scss/_variables.scss\" >}}\n\nRGB colors are generated from a separate Sass map:\n\n{{< scss-docs name=\"theme-colors-rgb\" file=\"scss/_maps.scss\" >}}\n\nAnd color opacities build on that with their own map that's consumed by the utilities API:\n\n{{< scss-docs name=\"utilities-text-colors\" file=\"scss/_maps.scss\" >}}\n\n### Utilities API\n\nColor utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-color\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/display.md",
    "content": "---\nlayout: docs\ntitle: Display property\ndescription: Quickly and responsively toggle the display value of components and more with our display utilities. Includes support for some of the more common values, as well as some extras for controlling display when printing.\ngroup: utilities\ntoc: true\n---\n\n## How it works\n\nChange the value of the [`display` property](https://developer.mozilla.org/en-US/docs/Web/CSS/display) with our responsive display utility classes. We purposely support only a subset of all possible values for `display`. Classes can be combined for various effects as you need.\n\n## Notation\n\nDisplay utility classes that apply to all [breakpoints]({{< docsref \"/layout/breakpoints\" >}}), from `xs` to `xxl`, have no breakpoint abbreviation in them. This is because those classes are applied from `min-width: 0;` and up, and thus are not bound by a media query. The remaining breakpoints, however, do include a breakpoint abbreviation.\n\nAs such, the classes are named using the format:\n\n- `.d-{value}` for `xs`\n- `.d-{breakpoint}-{value}` for `sm`, `md`, `lg`, `xl`, and `xxl`.\n\nWhere *value* is one of:\n\n- `none`\n- `inline`\n- `inline-block`\n- `block`\n- `grid`\n- `table`\n- `table-cell`\n- `table-row`\n- `flex`\n- `inline-flex`\n\nThe display values can be altered by changing the `display` values defined in `$utilities` and recompiling the SCSS.\n\nThe media queries affect screen widths with the given breakpoint *or larger*. For example, `.d-lg-none` sets `display: none;` on `lg`, `xl`, and `xxl` screens.\n\n## Examples\n\n{{< example >}}\n<div class=\"d-inline p-2 text-bg-primary\">d-inline</div>\n<div class=\"d-inline p-2 text-bg-dark\">d-inline</div>\n{{< /example >}}\n\n{{< example >}}\n<span class=\"d-block p-2 text-bg-primary\">d-block</span>\n<span class=\"d-block p-2 text-bg-dark\">d-block</span>\n{{< /example >}}\n\n## Hiding elements\n\nFor faster mobile-friendly development, use responsive display classes for showing and hiding elements by device. Avoid creating entirely different versions of the same site, instead hide elements responsively for each screen size.\n\nTo hide elements simply use the `.d-none` class or one of the `.d-{sm,md,lg,xl,xxl}-none` classes for any responsive screen variation.\n\nTo show an element only on a given interval of screen sizes you can combine one `.d-*-none` class with a `.d-*-*` class, for example `.d-none .d-md-block .d-xl-none .d-xxl-none` will hide the element for all screen sizes except on medium and large devices.\n\n{{< bs-table >}}\n| Screen size | Class |\n| --- | --- |\n| Hidden on all | `.d-none` |\n| Hidden only on xs | `.d-none .d-sm-block` |\n| Hidden only on sm | `.d-sm-none .d-md-block` |\n| Hidden only on md | `.d-md-none .d-lg-block` |\n| Hidden only on lg | `.d-lg-none .d-xl-block` |\n| Hidden only on xl | `.d-xl-none` |\n| Hidden only on xxl | `.d-xxl-none .d-xxl-block` |\n| Visible on all | `.d-block` |\n| Visible only on xs | `.d-block .d-sm-none` |\n| Visible only on sm | `.d-none .d-sm-block .d-md-none` |\n| Visible only on md | `.d-none .d-md-block .d-lg-none` |\n| Visible only on lg | `.d-none .d-lg-block .d-xl-none` |\n| Visible only on xl | `.d-none .d-xl-block .d-xxl-none` |\n| Visible only on xxl | `.d-none .d-xxl-block` |\n{{< /bs-table >}}\n\n{{< example >}}\n<div class=\"d-lg-none\">hide on lg and wider screens</div>\n<div class=\"d-none d-lg-block\">hide on screens smaller than lg</div>\n{{< /example >}}\n\n## Display in print\n\nChange the `display` value of elements when printing with our print display utility classes. Includes support for the same `display` values as our responsive `.d-*` utilities.\n\n- `.d-print-none`\n- `.d-print-inline`\n- `.d-print-inline-block`\n- `.d-print-block`\n- `.d-print-grid`\n- `.d-print-table`\n- `.d-print-table-row`\n- `.d-print-table-cell`\n- `.d-print-flex`\n- `.d-print-inline-flex`\n\nThe print and display classes can be combined.\n\n{{< example >}}\n<div class=\"d-print-none\">Screen Only (Hide on print only)</div>\n<div class=\"d-none d-print-block\">Print Only (Hide on screen only)</div>\n<div class=\"d-none d-lg-block d-print-block\">Hide up to large on screen, but always show on print</div>\n{{< /example >}}\n\n## Sass\n\n### Utilities API\n\nDisplay utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-display\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/flex.md",
    "content": "---\nlayout: docs\ntitle: Flex\ndescription: Quickly manage the layout, alignment, and sizing of grid columns, navigation, components, and more with a full suite of responsive flexbox utilities. For more complex implementations, custom CSS may be necessary.\ngroup: utilities\ntoc: true\n---\n\n## Enable flex behaviors\n\nApply `display` utilities to create a flexbox container and transform **direct children elements** into flex items. Flex containers and items are able to be modified further with additional flex properties.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex p-2\">I'm a flexbox container!</div>\n{{< /example >}}\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-inline-flex p-2\">I'm an inline flexbox container!</div>\n{{< /example >}}\n\nResponsive variations also exist for `.d-flex` and `.d-inline-flex`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.d{{ .abbr }}-flex`\n- `.d{{ .abbr }}-inline-flex`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Direction\n\nSet the direction of flex items in a flex container with direction utilities. In most cases you can omit the horizontal class here as the browser default is `row`. However, you may encounter situations where you needed to explicitly set this value (like responsive layouts).\n\nUse `.flex-row` to set a horizontal direction (the browser default), or `.flex-row-reverse` to start the horizontal direction from the opposite side.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex flex-row mb-3\">\n  <div class=\"p-2\">Flex item 1</div>\n  <div class=\"p-2\">Flex item 2</div>\n  <div class=\"p-2\">Flex item 3</div>\n</div>\n<div class=\"d-flex flex-row-reverse\">\n  <div class=\"p-2\">Flex item 1</div>\n  <div class=\"p-2\">Flex item 2</div>\n  <div class=\"p-2\">Flex item 3</div>\n</div>\n{{< /example >}}\n\nUse `.flex-column` to set a vertical direction, or `.flex-column-reverse`  to start the vertical direction from the opposite side.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex flex-column mb-3\">\n  <div class=\"p-2\">Flex item 1</div>\n  <div class=\"p-2\">Flex item 2</div>\n  <div class=\"p-2\">Flex item 3</div>\n</div>\n<div class=\"d-flex flex-column-reverse\">\n  <div class=\"p-2\">Flex item 1</div>\n  <div class=\"p-2\">Flex item 2</div>\n  <div class=\"p-2\">Flex item 3</div>\n</div>\n{{< /example >}}\n\nResponsive variations also exist for `flex-direction`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.flex{{ .abbr }}-row`\n- `.flex{{ .abbr }}-row-reverse`\n- `.flex{{ .abbr }}-column`\n- `.flex{{ .abbr }}-column-reverse`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Justify content\n\nUse `justify-content` utilities on flexbox containers to change the alignment of flex items on the main axis (the x-axis to start, y-axis if `flex-direction: column`). Choose from `start` (browser default), `end`, `center`, `between`, `around`, or `evenly`.\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex justify-content-start mb-3\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex justify-content-end mb-3\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex justify-content-center mb-3\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex justify-content-between mb-3\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex justify-content-around mb-3\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex justify-content-evenly\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex justify-content-start\">...</div>\n<div class=\"d-flex justify-content-end\">...</div>\n<div class=\"d-flex justify-content-center\">...</div>\n<div class=\"d-flex justify-content-between\">...</div>\n<div class=\"d-flex justify-content-around\">...</div>\n<div class=\"d-flex justify-content-evenly\">...</div>\n```\n\nResponsive variations also exist for `justify-content`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.justify-content{{ .abbr }}-start`\n- `.justify-content{{ .abbr }}-end`\n- `.justify-content{{ .abbr }}-center`\n- `.justify-content{{ .abbr }}-between`\n- `.justify-content{{ .abbr }}-around`\n- `.justify-content{{ .abbr }}-evenly`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Align items\n\nUse `align-items` utilities on flexbox containers to change the alignment of flex items on the cross axis (the y-axis to start, x-axis if `flex-direction: column`). Choose from `start`, `end`, `center`, `baseline`, or `stretch` (browser default).\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-items-start mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex align-items-end mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex align-items-center mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex align-items-baseline mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex align-items-stretch\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-items-start\">...</div>\n<div class=\"d-flex align-items-end\">...</div>\n<div class=\"d-flex align-items-center\">...</div>\n<div class=\"d-flex align-items-baseline\">...</div>\n<div class=\"d-flex align-items-stretch\">...</div>\n```\n\nResponsive variations also exist for `align-items`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.align-items{{ .abbr }}-start`\n- `.align-items{{ .abbr }}-end`\n- `.align-items{{ .abbr }}-center`\n- `.align-items{{ .abbr }}-baseline`\n- `.align-items{{ .abbr }}-stretch`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Align self\n\nUse `align-self` utilities on flexbox items to individually change their alignment on the cross axis (the y-axis to start, x-axis if `flex-direction: column`). Choose from the same options as `align-items`: `start`, `end`, `center`, `baseline`, or `stretch` (browser default).\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"align-self-start p-2\">Aligned flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"align-self-end p-2\">Aligned flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"align-self-center p-2\">Aligned flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex mb-3\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"align-self-baseline p-2\">Aligned flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n  <div class=\"d-flex\" style=\"height: 100px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"align-self-stretch p-2\">Aligned flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"align-self-start\">Aligned flex item</div>\n<div class=\"align-self-end\">Aligned flex item</div>\n<div class=\"align-self-center\">Aligned flex item</div>\n<div class=\"align-self-baseline\">Aligned flex item</div>\n<div class=\"align-self-stretch\">Aligned flex item</div>\n```\n\nResponsive variations also exist for `align-self`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.align-self{{ .abbr }}-start`\n- `.align-self{{ .abbr }}-end`\n- `.align-self{{ .abbr }}-center`\n- `.align-self{{ .abbr }}-baseline`\n- `.align-self{{ .abbr }}-stretch`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Fill\n\nUse the `.flex-fill` class on a series of sibling elements to force them into widths equal to their content (or equal widths if their content does not surpass their border-boxes) while taking up all available horizontal space.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex\">\n  <div class=\"p-2 flex-fill\">Flex item with a lot of content</div>\n  <div class=\"p-2 flex-fill\">Flex item</div>\n  <div class=\"p-2 flex-fill\">Flex item</div>\n</div>\n{{< /example >}}\n\nResponsive variations also exist for `flex-fill`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.flex{{ .abbr }}-fill`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Grow and shrink\n\nUse `.flex-grow-*` utilities to toggle a flex item's ability to grow to fill available space. In the example below, the `.flex-grow-1` elements uses all available space it can, while allowing the remaining two flex items their necessary space.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex\">\n  <div class=\"p-2 flex-grow-1\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Third flex item</div>\n</div>\n{{< /example >}}\n\nUse `.flex-shrink-*` utilities to toggle a flex item's ability to shrink if necessary. In the example below, the second flex item with `.flex-shrink-1` is forced to wrap its contents to a new line, \"shrinking\" to allow more space for the previous flex item with `.w-100`.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex\">\n  <div class=\"p-2 w-100\">Flex item</div>\n  <div class=\"p-2 flex-shrink-1\">Flex item</div>\n</div>\n{{< /example >}}\n\nResponsive variations also exist for `flex-grow` and `flex-shrink`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.flex{{ .abbr }}-{grow|shrink}-0`\n- `.flex{{ .abbr }}-{grow|shrink}-1`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Auto margins\n\nFlexbox can do some pretty awesome things when you mix flex alignments with auto margins. Shown below are three examples of controlling flex items via auto margins: default (no auto margin), pushing two items to the right (`.me-auto`), and pushing two items to the left (`.ms-auto`).\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex mb-3\">\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n</div>\n\n<div class=\"d-flex mb-3\">\n  <div class=\"me-auto p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n</div>\n\n<div class=\"d-flex mb-3\">\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"ms-auto p-2\">Flex item</div>\n</div>\n{{< /example >}}\n\n### With align-items\n\nVertically move one flex item to the top or bottom of a container by mixing `align-items`, `flex-direction: column`, and `margin-top: auto` or `margin-bottom: auto`.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex align-items-start flex-column mb-3\" style=\"height: 200px;\">\n  <div class=\"mb-auto p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n</div>\n\n<div class=\"d-flex align-items-end flex-column mb-3\" style=\"height: 200px;\">\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"p-2\">Flex item</div>\n  <div class=\"mt-auto p-2\">Flex item</div>\n</div>\n{{< /example >}}\n\n## Wrap\n\nChange how flex items wrap in a flex container. Choose from no wrapping at all (the browser default) with `.flex-nowrap`, wrapping with `.flex-wrap`, or reverse wrapping with `.flex-wrap-reverse`.\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex flex-nowrap\" style=\"width: 8rem;\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex flex-nowrap\">\n  ...\n</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex flex-wrap\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex flex-wrap\">\n  ...\n</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex flex-wrap-reverse\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex flex-wrap-reverse\">\n  ...\n</div>\n```\n\n\nResponsive variations also exist for `flex-wrap`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.flex{{ .abbr }}-nowrap`\n- `.flex{{ .abbr }}-wrap`\n- `.flex{{ .abbr }}-wrap-reverse`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Order\n\nChange the _visual_ order of specific flex items with a handful of `order` utilities. We only provide options for making an item first or last, as well as a reset to use the DOM order. As `order` takes any integer value from 0 to 5, add custom CSS for any additional values needed.\n\n{{< example class=\"bd-example-flex\" >}}\n<div class=\"d-flex flex-nowrap\">\n  <div class=\"order-3 p-2\">First flex item</div>\n  <div class=\"order-2 p-2\">Second flex item</div>\n  <div class=\"order-1 p-2\">Third flex item</div>\n</div>\n{{< /example >}}\n\nResponsive variations also exist for `order`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $bp := $.Site.Data.breakpoints -}}\n{{- range (seq 0 5) }}\n- `.order{{ $bp.abbr }}-{{ . }}`\n{{- end -}}\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\nAdditionally there are also responsive `.order-first` and `.order-last` classes that change the `order` of an element by applying `order: -1` and `order: 6`, respectively.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $bp := $.Site.Data.breakpoints -}}\n{{- range (slice \"first\" \"last\") }}\n- `.order{{ $bp.abbr }}-{{ . }}`\n{{- end -}}\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Align content\n\nUse `align-content` utilities on flexbox containers to align flex items *together* on the cross axis. Choose from `start` (browser default), `end`, `center`, `between`, `around`, or `stretch`. To demonstrate these utilities, we've enforced `flex-wrap: wrap` and increased the number of flex items.\n\n**Heads up!** This property has no effect on single rows of flex items.\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-content-start flex-wrap mb-3\" style=\"height: 200px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-content-start flex-wrap\">\n  ...\n</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-content-end flex-wrap mb-3\" style=\"height: 200px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-content-end flex-wrap\">...</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-content-center flex-wrap mb-3\" style=\"height: 200px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-content-center flex-wrap\">...</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-content-between flex-wrap mb-3\" style=\"height: 200px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-content-between flex-wrap\">...</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-content-around flex-wrap mb-3\" style=\"height: 200px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-content-around flex-wrap\">...</div>\n```\n\n<div class=\"bd-example bd-example-flex\">\n  <div class=\"d-flex align-content-stretch flex-wrap mb-3\" style=\"height: 200px\">\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n    <div class=\"p-2\">Flex item</div>\n  </div>\n</div>\n\n```html\n<div class=\"d-flex align-content-stretch flex-wrap\">...</div>\n```\n\nResponsive variations also exist for `align-content`.\n\n{{< markdown >}}\n{{< flex.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.align-content{{ .abbr }}-start`\n- `.align-content{{ .abbr }}-end`\n- `.align-content{{ .abbr }}-center`\n- `.align-content{{ .abbr }}-between`\n- `.align-content{{ .abbr }}-around`\n- `.align-content{{ .abbr }}-stretch`\n{{- end -}}\n{{< /flex.inline >}}\n{{< /markdown >}}\n\n## Media object\n\nLooking to replicate the [media object component](https://getbootstrap.com/docs/4.6/components/media-object/) from Bootstrap 4? Recreate it in no time with a few flex utilities that allow even more flexibility and customization than before.\n\n{{< example >}}\n<div class=\"d-flex\">\n  <div class=\"flex-shrink-0\">\n    {{< placeholder width=\"100\" height=\"100\" color=\"#999\" background=\"#e5e5e5\" text=\"Image\" >}}\n  </div>\n  <div class=\"flex-grow-1 ms-3\">\n    This is some content from a media component. You can replace this with any content and adjust it as needed.\n  </div>\n</div>\n{{< /example >}}\n\nAnd say you want to vertically center the content next to the image:\n\n{{< example >}}\n<div class=\"d-flex align-items-center\">\n  <div class=\"flex-shrink-0\">\n    {{< placeholder width=\"100\" height=\"100\" color=\"#999\" background=\"#e5e5e5\" text=\"Image\" >}}\n  </div>\n  <div class=\"flex-grow-1 ms-3\">\n    This is some content from a media component. You can replace this with any content and adjust it as needed.\n  </div>\n</div>\n{{< /example >}}\n\n## Sass\n\n### Utilities API\n\nFlexbox utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-flex\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/float.md",
    "content": "---\nlayout: docs\ntitle: Float\ndescription: Toggle floats on any element, across any breakpoint, using our responsive float utilities.\ngroup: utilities\ntoc: true\n---\n\n## Overview\n\nThese utility classes float an element to the left or right, or disable floating, based on the current viewport size using the [CSS `float` property](https://developer.mozilla.org/en-US/docs/Web/CSS/float). `!important` is included to avoid specificity issues. These use the same viewport breakpoints as our grid system. Please be aware float utilities have no effect on flex items.\n\n{{< example >}}\n<div class=\"float-start\">Float start on all viewport sizes</div><br>\n<div class=\"float-end\">Float end on all viewport sizes</div><br>\n<div class=\"float-none\">Don't float on all viewport sizes</div>\n{{< /example >}}\n\n## Responsive\n\nResponsive variations also exist for each `float` value.\n\n{{< example >}}\n<div class=\"float-sm-start\">Float start on viewports sized SM (small) or wider</div><br>\n<div class=\"float-md-start\">Float start on viewports sized MD (medium) or wider</div><br>\n<div class=\"float-lg-start\">Float start on viewports sized LG (large) or wider</div><br>\n<div class=\"float-xl-start\">Float start on viewports sized XL (extra-large) or wider</div><br>\n{{< /example >}}\n\nHere are all the support classes:\n\n{{< markdown >}}\n{{< float.inline >}}\n{{- range $.Site.Data.breakpoints }}\n- `.float{{ .abbr }}-start`\n- `.float{{ .abbr }}-end`\n- `.float{{ .abbr }}-none`\n{{- end -}}\n{{< /float.inline >}}\n{{< /markdown >}}\n\n## Sass\n\n### Utilities API\n\nFloat utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-float\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/interactions.md",
    "content": "---\nlayout: docs\ntitle: Interactions\ndescription: Utility classes that change how users interact with contents of a website.\ngroup: utilities\ntoc: false\n---\n\n## Text selection\n\nChange the way in which the content is selected when the user interacts with it.\n\n{{< example >}}\n<p class=\"user-select-all\">This paragraph will be entirely selected when clicked by the user.</p>\n<p class=\"user-select-auto\">This paragraph has default select behavior.</p>\n<p class=\"user-select-none\">This paragraph will not be selectable when clicked by the user.</p>\n{{< /example >}}\n\n## Pointer events\n\nBootstrap provides `.pe-none` and `.pe-auto` classes to prevent or add element interactions.\n\n{{< example >}}\n<p><a href=\"#\" class=\"pe-none\" tabindex=\"-1\" aria-disabled=\"true\">This link</a> can not be clicked.</p>\n<p><a href=\"#\" class=\"pe-auto\">This link</a> can be clicked (this is default behavior).</p>\n<p class=\"pe-none\"><a href=\"#\" tabindex=\"-1\" aria-disabled=\"true\">This link</a> can not be clicked because the <code>pointer-events</code> property is inherited from its parent. However, <a href=\"#\" class=\"pe-auto\">this link</a> has a <code>pe-auto</code> class and can be clicked.</p>\n{{< /example >}}\n\nThe `.pe-none` class (and the `pointer-events` CSS property it sets) only prevents interactions with a pointer (mouse, stylus, touch). Links and controls with `.pe-none` are, by default, still focusable and actionable for keyboard users. To ensure that they are completely neutralized even for keyboard users, you may need to add further attributes such as `tabindex=\"-1\"` (to prevent them from receiving keyboard focus) and `aria-disabled=\"true\"` (to convey the fact they are effectively disabled to assistive technologies), and possibly use JavaScript to completely prevent them from being actionable.\n\nIf possible, the simpler solution is:\n\n- For form controls, add the `disabled` HTML attribute.\n* For links, remove the `href` attribute, making it a non-interactive anchor or placeholder link.\n\n## Sass\n\n### Utilities API\n\nInteraction utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-interaction\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/opacity.md",
    "content": "---\nlayout: docs\ntitle: Opacity\ndescription: Control the opacity of elements.\ngroup: utilities\nadded: \"5.1\"\n---\n\nThe `opacity` property sets the opacity level for an element. The opacity level describes the transparency level, where `1` is not transparent at all, `.5` is 50% visible, and `0` is completely transparent.\n\nSet the `opacity` of an element using `.opacity-{value}` utilities.\n\n<div class=\"bd-example d-sm-flex\">\n  <div class=\"opacity-100 p-3 m-2 bg-primary text-light fw-bold rounded\">100%</div>\n  <div class=\"opacity-75 p-3 m-2 bg-primary text-light fw-bold rounded\">75%</div>\n  <div class=\"opacity-50 p-3 m-2 bg-primary text-light fw-bold rounded\">50%</div>\n  <div class=\"opacity-25 p-3 m-2 bg-primary text-light fw-bold rounded\">25%</div>\n</div>\n\n```html\n<div class=\"opacity-100\">...</div>\n<div class=\"opacity-75\">...</div>\n<div class=\"opacity-50\">...</div>\n<div class=\"opacity-25\">...</div>\n```\n\n### Utilities API\n\nOpacity utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-opacity\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/overflow.md",
    "content": "---\nlayout: docs\ntitle: Overflow\ndescription: Use these shorthand utilities for quickly configuring how content overflows an element.\ngroup: utilities\n---\n\nAdjust the `overflow` property on the fly with four default values and classes. These classes are not responsive by default.\n\n<div class=\"bd-example d-md-flex\">\n  <div class=\"overflow-auto p-3 mb-3 mb-md-0 me-md-3 bg-light\" style=\"max-width: 260px; max-height: 100px;\">\n    This is an example of using <code>.overflow-auto</code> on an element with set width and height dimensions. By design, this content will vertically scroll.\n  </div>\n  <div class=\"overflow-hidden p-3 mb-3 mb-md-0 me-md-3 bg-light\" style=\"max-width: 260px; max-height: 100px;\">\n    This is an example of using <code>.overflow-hidden</code> on an element with set width and height dimensions.\n  </div>\n  <div class=\"overflow-visible p-3 mb-3 mb-md-0 me-md-3 bg-light\" style=\"max-width: 260px; max-height: 100px;\">\n    This is an example of using <code>.overflow-visible</code> on an element with set width and height dimensions.\n  </div>\n  <div class=\"overflow-scroll p-3 bg-light\" style=\"max-width: 260px; max-height: 100px;\">\n    This is an example of using <code>.overflow-scroll</code> on an element with set width and height dimensions.\n  </div>\n</div>\n\n```html\n<div class=\"overflow-auto\">...</div>\n<div class=\"overflow-hidden\">...</div>\n<div class=\"overflow-visible\">...</div>\n<div class=\"overflow-scroll\">...</div>\n```\n\nUsing Sass variables, you may customize the overflow utilities by changing the `$overflows` variable in `_variables.scss`.\n\n## Sass\n\n### Utilities API\n\nOverflow utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-overflow\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/position.md",
    "content": "---\nlayout: docs\ntitle: Position\ndescription: Use these shorthand utilities for quickly configuring the position of an element.\ngroup: utilities\ntoc: true\n---\n\n## Position values\n\nQuick positioning classes are available, though they are not responsive.\n\n```html\n<div class=\"position-static\">...</div>\n<div class=\"position-relative\">...</div>\n<div class=\"position-absolute\">...</div>\n<div class=\"position-fixed\">...</div>\n<div class=\"position-sticky\">...</div>\n```\n\n## Arrange elements\n\nArrange elements easily with the edge positioning utilities. The format is `{property}-{position}`.\n\nWhere *property* is one of:\n\n- `top` - for the vertical `top` position\n- `start` - for the horizontal `left` position (in LTR)\n- `bottom` - for the vertical `bottom` position\n- `end` - for the horizontal `right` position (in LTR)\n\nWhere *position* is one of:\n\n- `0` - for `0` edge position\n- `50` - for `50%` edge position\n- `100` - for `100%` edge position\n\n(You can add more position values by adding entries to the `$position-values` Sass map variable.)\n\n{{< example class=\"bd-example-position-utils\" >}}\n<div class=\"position-relative\">\n  <div class=\"position-absolute top-0 start-0\"></div>\n  <div class=\"position-absolute top-0 end-0\"></div>\n  <div class=\"position-absolute top-50 start-50\"></div>\n  <div class=\"position-absolute bottom-50 end-50\"></div>\n  <div class=\"position-absolute bottom-0 start-0\"></div>\n  <div class=\"position-absolute bottom-0 end-0\"></div>\n</div>\n{{< /example >}}\n\n## Center elements\n\nIn addition, you can also center the elements with the transform utility class `.translate-middle`.\n\nThis class applies the transformations `translateX(-50%)` and `translateY(-50%)` to the element which, in combination with the edge positioning utilities, allows you to absolute center an element.\n\n{{< example class=\"bd-example-position-utils\" >}}\n<div class=\"position-relative\">\n  <div class=\"position-absolute top-0 start-0 translate-middle\"></div>\n  <div class=\"position-absolute top-0 start-50 translate-middle\"></div>\n  <div class=\"position-absolute top-0 start-100 translate-middle\"></div>\n  <div class=\"position-absolute top-50 start-0 translate-middle\"></div>\n  <div class=\"position-absolute top-50 start-50 translate-middle\"></div>\n  <div class=\"position-absolute top-50 start-100 translate-middle\"></div>\n  <div class=\"position-absolute top-100 start-0 translate-middle\"></div>\n  <div class=\"position-absolute top-100 start-50 translate-middle\"></div>\n  <div class=\"position-absolute top-100 start-100 translate-middle\"></div>\n</div>\n{{< /example >}}\n\nBy adding `.translate-middle-x` or `.translate-middle-y` classes, elements can be positioned only in horizontal or vertical direction.\n\n{{< example class=\"bd-example-position-utils\" >}}\n<div class=\"position-relative\">\n  <div class=\"position-absolute top-0 start-0\"></div>\n  <div class=\"position-absolute top-0 start-50 translate-middle-x\"></div>\n  <div class=\"position-absolute top-0 end-0\"></div>\n  <div class=\"position-absolute top-50 start-0 translate-middle-y\"></div>\n  <div class=\"position-absolute top-50 start-50 translate-middle\"></div>\n  <div class=\"position-absolute top-50 end-0 translate-middle-y\"></div>\n  <div class=\"position-absolute bottom-0 start-0\"></div>\n  <div class=\"position-absolute bottom-0 start-50 translate-middle-x\"></div>\n  <div class=\"position-absolute bottom-0 end-0\"></div>\n</div>\n{{< /example >}}\n\n## Examples\n\nHere are some real life examples of these classes:\n\n{{< example class=\"bd-example-position-examples d-flex justify-content-around\" >}}\n<button type=\"button\" class=\"btn btn-primary position-relative\">\n  Mails <span class=\"position-absolute top-0 start-100 translate-middle badge rounded-pill bg-secondary\">+99 <span class=\"visually-hidden\">unread messages</span></span>\n</button>\n\n<button type=\"button\" class=\"btn btn-dark position-relative\">\n  Marker <svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"position-absolute top-100 start-50 translate-middle mt-1\" fill=\"#212529\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/></svg>\n</button>\n\n<button type=\"button\" class=\"btn btn-primary position-relative\">\n  Alerts <span class=\"position-absolute top-0 start-100 translate-middle badge border border-light rounded-circle bg-danger p-2\"><span class=\"visually-hidden\">unread messages</span></span>\n</button>\n{{< /example >}}\n\nYou can use these classes with existing components to create new ones. Remember that you can extend its functionality by adding entries to the `$position-values` variable.\n\n{{< example class=\"bd-example-position-examples\" >}}\n<div class=\"position-relative m-4\">\n  <div class=\"progress\" style=\"height: 1px;\">\n    <div class=\"progress-bar\" role=\"progressbar\" aria-label=\"Progress\" style=\"width: 50%;\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n  </div>\n  <button type=\"button\" class=\"position-absolute top-0 start-0 translate-middle btn btn-sm btn-primary rounded-pill\" style=\"width: 2rem; height:2rem;\">1</button>\n  <button type=\"button\" class=\"position-absolute top-0 start-50 translate-middle btn btn-sm btn-primary rounded-pill\" style=\"width: 2rem; height:2rem;\">2</button>\n  <button type=\"button\" class=\"position-absolute top-0 start-100 translate-middle btn btn-sm btn-secondary rounded-pill\" style=\"width: 2rem; height:2rem;\">3</button>\n</div>\n{{< /example >}}\n\n## Sass\n\n### Maps\n\nDefault position utility values are declared in a Sass map, then used to generate our utilities.\n\n{{< scss-docs name=\"position-map\" file=\"scss/_variables.scss\" >}}\n\n### Utilities API\n\nPosition utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-position\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/shadows.md",
    "content": "---\nlayout: docs\ntitle: Shadows\ndescription: Add or remove shadows to elements with box-shadow utilities.\ngroup: utilities\ntoc: true\n---\n\n## Examples\n\nWhile shadows on components are disabled by default in Bootstrap and can be enabled via `$enable-shadows`, you can also quickly add or remove a shadow with our `box-shadow` utility classes. Includes support for `.shadow-none` and three default sizes (which have associated variables to match).\n\n{{< example >}}\n<div class=\"shadow-none p-3 mb-5 bg-light rounded\">No shadow</div>\n<div class=\"shadow-sm p-3 mb-5 bg-body rounded\">Small shadow</div>\n<div class=\"shadow p-3 mb-5 bg-body rounded\">Regular shadow</div>\n<div class=\"shadow-lg p-3 mb-5 bg-body rounded\">Larger shadow</div>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"box-shadow-variables\" file=\"scss/_variables.scss\" >}}\n\n### Utilities API\n\nShadow utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-shadow\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/sizing.md",
    "content": "---\nlayout: docs\ntitle: Sizing\ndescription: Easily make an element as wide or as tall with our width and height utilities.\ngroup: utilities\ntoc: true\n---\n\n## Relative to the parent\n\nWidth and height utilities are generated from the utility API in `_utilities.scss`. Includes support for `25%`, `50%`, `75%`, `100%`, and `auto` by default. Modify those values as you need to generate different utilities here.\n\n{{< example >}}\n<div class=\"w-25 p-3\" style=\"background-color: #eee;\">Width 25%</div>\n<div class=\"w-50 p-3\" style=\"background-color: #eee;\">Width 50%</div>\n<div class=\"w-75 p-3\" style=\"background-color: #eee;\">Width 75%</div>\n<div class=\"w-100 p-3\" style=\"background-color: #eee;\">Width 100%</div>\n<div class=\"w-auto p-3\" style=\"background-color: #eee;\">Width auto</div>\n{{< /example >}}\n\n{{< example >}}\n<div style=\"height: 100px; background-color: rgba(255,0,0,0.1);\">\n  <div class=\"h-25 d-inline-block\" style=\"width: 120px; background-color: rgba(0,0,255,.1)\">Height 25%</div>\n  <div class=\"h-50 d-inline-block\" style=\"width: 120px; background-color: rgba(0,0,255,.1)\">Height 50%</div>\n  <div class=\"h-75 d-inline-block\" style=\"width: 120px; background-color: rgba(0,0,255,.1)\">Height 75%</div>\n  <div class=\"h-100 d-inline-block\" style=\"width: 120px; background-color: rgba(0,0,255,.1)\">Height 100%</div>\n  <div class=\"h-auto d-inline-block\" style=\"width: 120px; background-color: rgba(0,0,255,.1)\">Height auto</div>\n</div>\n{{< /example >}}\n\nYou can also use `max-width: 100%;` and `max-height: 100%;` utilities as needed.\n\n{{< example >}}\n{{< placeholder width=\"100%\" height=\"100\" class=\"mw-100\" text=\"Max-width 100%\" >}}\n{{< /example >}}\n\n{{< example >}}\n<div style=\"height: 100px; background-color: rgba(255,0,0,.1);\">\n  <div class=\"mh-100\" style=\"width: 100px; height: 200px; background-color: rgba(0,0,255,.1);\">Max-height 100%</div>\n</div>\n{{< /example >}}\n\n## Relative to the viewport\n\nYou can also use utilities to set the width and height relative to the viewport.\n\n```html\n<div class=\"min-vw-100\">Min-width 100vw</div>\n<div class=\"min-vh-100\">Min-height 100vh</div>\n<div class=\"vw-100\">Width 100vw</div>\n<div class=\"vh-100\">Height 100vh</div>\n```\n\n## Sass\n\n### Utilities API\n\nSizing utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-sizing\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/spacing.md",
    "content": "---\nlayout: docs\ntitle: Spacing\ndescription: Bootstrap includes a wide range of shorthand responsive margin, padding, and gap utility classes to modify an element's appearance.\ngroup: utilities\ntoc: true\n---\n\n## Margin and padding\n\nAssign responsive-friendly `margin` or `padding` values to an element or a subset of its sides with shorthand classes. Includes support for individual properties, all properties, and vertical and horizontal properties. Classes are built from a default Sass map ranging from `.25rem` to `3rem`.\n\n{{< callout >}}\n**Using the CSS Grid layout module?** Consider using [the gap utility](#gap) instead.\n{{< /callout >}}\n\n### Notation\n\nSpacing utilities that apply to all breakpoints, from `xs` to `xxl`, have no breakpoint abbreviation in them. This is because those classes are applied from `min-width: 0` and up, and thus are not bound by a media query. The remaining breakpoints, however, do include a breakpoint abbreviation.\n\nThe classes are named using the format `{property}{sides}-{size}` for `xs` and `{property}{sides}-{breakpoint}-{size}` for `sm`, `md`, `lg`, `xl`, and `xxl`.\n\nWhere *property* is one of:\n\n- `m` - for classes that set `margin`\n- `p` - for classes that set `padding`\n\nWhere *sides* is one of:\n\n- `t` - for classes that set `margin-top` or `padding-top`\n- `b` - for classes that set `margin-bottom` or `padding-bottom`\n- `s` - (start) for classes that set `margin-left` or `padding-left` in LTR, `margin-right` or `padding-right` in RTL\n- `e` - (end) for classes that set `margin-right` or `padding-right` in LTR, `margin-left` or `padding-left` in RTL\n- `x` - for classes that set both `*-left` and `*-right`\n- `y` - for classes that set both `*-top` and `*-bottom`\n- blank - for classes that set a `margin` or `padding` on all 4 sides of the element\n\nWhere *size* is one of:\n\n- `0` - for classes that eliminate the `margin` or `padding` by setting it to `0`\n- `1` - (by default) for classes that set the `margin` or `padding` to `$spacer * .25`\n- `2` - (by default) for classes that set the `margin` or `padding` to `$spacer * .5`\n- `3` - (by default) for classes that set the `margin` or `padding` to `$spacer`\n- `4` - (by default) for classes that set the `margin` or `padding` to `$spacer * 1.5`\n- `5` - (by default) for classes that set the `margin` or `padding` to `$spacer * 3`\n- `auto` - for classes that set the `margin` to auto\n\n(You can add more sizes by adding entries to the `$spacers` Sass map variable.)\n\n### Examples\n\nHere are some representative examples of these classes:\n\n```scss\n.mt-0 {\n  margin-top: 0 !important;\n}\n\n.ms-1 {\n  margin-left: ($spacer * .25) !important;\n}\n\n.px-2 {\n  padding-left: ($spacer * .5) !important;\n  padding-right: ($spacer * .5) !important;\n}\n\n.p-3 {\n  padding: $spacer !important;\n}\n```\n\n### Horizontal centering\n\nAdditionally, Bootstrap also includes an `.mx-auto` class for horizontally centering fixed-width block level content—that is, content that has `display: block` and a `width` set—by setting the horizontal margins to `auto`.\n\n<div class=\"bd-example\">\n  <div class=\"mx-auto\" style=\"width: 200px; background-color: rgba(86,61,124,.15);\">\n    Centered element\n  </div>\n</div>\n\n```html\n<div class=\"mx-auto\" style=\"width: 200px;\">\n  Centered element\n</div>\n```\n\n## Negative margin\n\nIn CSS, `margin` properties can utilize negative values (`padding` cannot). These negative margins are **disabled by default**, but can be enabled in Sass by setting `$enable-negative-margins: true`.\n\nThe syntax is nearly the same as the default, positive margin utilities, but with the addition of `n` before the requested size. Here's an example class that's the opposite of `.mt-1`:\n\n```scss\n.mt-n1 {\n  margin-top: -0.25rem !important;\n}\n```\n\n## Gap\n\nWhen using `display: grid`, you can make use of `gap` utilities on the parent grid container. This can save on having to add margin utilities to individual grid items (children of a `display: grid` container). Gap utilities are responsive by default, and are generated via our utilities API, based on the `$spacers` Sass map.\n\n{{< example html >}}\n<div class=\"d-grid gap-3\">\n  <div class=\"p-2 bg-light border\">Grid item 1</div>\n  <div class=\"p-2 bg-light border\">Grid item 2</div>\n  <div class=\"p-2 bg-light border\">Grid item 3</div>\n</div>\n{{< /example >}}\n\nSupport includes responsive options for all of Bootstrap's grid breakpoints, as well as six sizes from the `$spacers` map (`0`–`5`). There is no `.gap-auto` utility class as it's effectively the same as `.gap-0`.\n\n## Sass\n\n### Maps\n\nSpacing utilities are declared via Sass map and then generated with our utilities API.\n\n{{< scss-docs name=\"spacer-variables-maps\" file=\"scss/_variables.scss\" >}}\n\n### Utilities API\n\nSpacing utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-spacing\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/text.md",
    "content": "---\nlayout: docs\ntitle: Text\ndescription: Documentation and examples for common text utilities to control alignment, wrapping, weight, and more.\ngroup: utilities\ntoc: true\n---\n\n## Text alignment\n\nEasily realign text to components with text alignment classes. For start, end, and center alignment, responsive classes are available that use the same viewport width breakpoints as the grid system.\n\n{{< example >}}\n<p class=\"text-start\">Start aligned text on all viewport sizes.</p>\n<p class=\"text-center\">Center aligned text on all viewport sizes.</p>\n<p class=\"text-end\">End aligned text on all viewport sizes.</p>\n\n<p class=\"text-sm-start\">Start aligned text on viewports sized SM (small) or wider.</p>\n<p class=\"text-md-start\">Start aligned text on viewports sized MD (medium) or wider.</p>\n<p class=\"text-lg-start\">Start aligned text on viewports sized LG (large) or wider.</p>\n<p class=\"text-xl-start\">Start aligned text on viewports sized XL (extra-large) or wider.</p>\n{{< /example >}}\n\n{{< callout info >}}\nNote that we don't provide utility classes for justified text. While, aesthetically, justified text might look more appealing, it does make word-spacing more random and therefore harder to read.\n{{< /callout >}}\n\n## Text wrapping and overflow\n\nWrap text with a `.text-wrap` class.\n\n{{< example >}}\n<div class=\"badge bg-primary text-wrap\" style=\"width: 6rem;\">\n  This text should wrap.\n</div>\n{{< /example >}}\n\nPrevent text from wrapping with a `.text-nowrap` class.\n\n{{< example >}}\n<div class=\"text-nowrap bg-light border\" style=\"width: 8rem;\">\n  This text should overflow the parent.\n</div>\n{{< /example >}}\n\n## Word break\n\nPrevent long strings of text from breaking your components' layout by using `.text-break` to set `word-wrap: break-word` and `word-break: break-word`. We use `word-wrap` instead of the more common `overflow-wrap` for wider browser support, and add the deprecated `word-break: break-word` to avoid issues with flex containers.\n\n{{< example >}}\n<p class=\"text-break\">mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm</p>\n{{< /example >}}\n\n{{< callout warning >}}\nNote that [breaking words isn't possible in Arabic](https://rtlstyling.com/posts/rtl-styling#3.-line-break), which is the most used RTL language. Therefore `.text-break` is removed from our RTL compiled CSS.\n{{< /callout >}}\n\n## Text transform\n\nTransform text in components with text capitalization classes.\n\n{{< example >}}\n<p class=\"text-lowercase\">Lowercased text.</p>\n<p class=\"text-uppercase\">Uppercased text.</p>\n<p class=\"text-capitalize\">CapiTaliZed text.</p>\n{{< /example >}}\n\nNote how `.text-capitalize` only changes the first letter of each word, leaving the case of any other letters unaffected.\n\n## Font size\n\nQuickly change the `font-size` of text. While our heading classes (e.g., `.h1`–`.h6`) apply `font-size`, `font-weight`, and `line-height`, these utilities _only_ apply `font-size`. Sizing for these utilities matches HTML's heading elements, so as the number increases, their size decreases.\n\n{{< example >}}\n<p class=\"fs-1\">.fs-1 text</p>\n<p class=\"fs-2\">.fs-2 text</p>\n<p class=\"fs-3\">.fs-3 text</p>\n<p class=\"fs-4\">.fs-4 text</p>\n<p class=\"fs-5\">.fs-5 text</p>\n<p class=\"fs-6\">.fs-6 text</p>\n{{< /example >}}\n\nCustomize your available `font-size`s by modifying the `$font-sizes` Sass map.\n\n## Font weight and italics\n\nQuickly change the `font-weight` or `font-style` of text with these utilities. `font-style` utilities are abbreviated as `.fst-*` and `font-weight` utilities are abbreviated as `.fw-*`.\n\n{{< example >}}\n<p class=\"fw-bold\">Bold text.</p>\n<p class=\"fw-bolder\">Bolder weight text (relative to the parent element).</p>\n<p class=\"fw-semibold\">Semibold weight text.</p>\n<p class=\"fw-normal\">Normal weight text.</p>\n<p class=\"fw-light\">Light weight text.</p>\n<p class=\"fw-lighter\">Lighter weight text (relative to the parent element).</p>\n<p class=\"fst-italic\">Italic text.</p>\n<p class=\"fst-normal\">Text with normal font style</p>\n{{< /example >}}\n\n## Line height\n\nChange the line height with `.lh-*` utilities.\n\n{{< example >}}\n<p class=\"lh-1\">This is a long paragraph written to show how the line-height of an element is affected by our utilities. Classes are applied to the element itself or sometimes the parent element. These classes can be customized as needed with our utility API.</p>\n<p class=\"lh-sm\">This is a long paragraph written to show how the line-height of an element is affected by our utilities. Classes are applied to the element itself or sometimes the parent element. These classes can be customized as needed with our utility API.</p>\n<p class=\"lh-base\">This is a long paragraph written to show how the line-height of an element is affected by our utilities. Classes are applied to the element itself or sometimes the parent element. These classes can be customized as needed with our utility API.</p>\n<p class=\"lh-lg\">This is a long paragraph written to show how the line-height of an element is affected by our utilities. Classes are applied to the element itself or sometimes the parent element. These classes can be customized as needed with our utility API.</p>\n{{< /example >}}\n\n## Monospace\n\nChange a selection to our monospace font stack with `.font-monospace`.\n\n{{< example >}}\n<p class=\"font-monospace\">This is in monospace</p>\n{{< /example >}}\n\n## Reset color\n\nReset a text or link's color with `.text-reset`, so that it inherits the color from its parent.\n\n{{< example >}}\n<p class=\"text-muted\">\n  Muted text with a <a href=\"#\" class=\"text-reset\">reset link</a>.\n</p>\n{{< /example >}}\n\n## Text decoration\n\nDecorate text in components with text decoration classes.\n\n{{< example >}}\n<p class=\"text-decoration-underline\">This text has a line underneath it.</p>\n<p class=\"text-decoration-line-through\">This text has a line going through it.</p>\n<a href=\"#\" class=\"text-decoration-none\">This link has its text decoration removed</a>\n{{< /example >}}\n\n## Sass\n\n### Variables\n\n{{< scss-docs name=\"font-variables\" file=\"scss/_variables.scss\" >}}\n\n### Maps\n\nFont-size utilities are generated from this map, in combination with our utilities API.\n\n{{< scss-docs name=\"font-sizes\" file=\"scss/_variables.scss\" >}}\n\n### Utilities API\n\nFont and text utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-text\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/vertical-align.md",
    "content": "---\nlayout: docs\ntitle: Vertical alignment\ndescription: Easily change the vertical alignment of inline, inline-block, inline-table, and table cell elements.\ngroup: utilities\n---\n\nChange the alignment of elements with the [`vertical-alignment`](https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align) utilities. Please note that vertical-align only affects inline, inline-block, inline-table, and table cell elements.\n\nChoose from `.align-baseline`, `.align-top`, `.align-middle`, `.align-bottom`, `.align-text-bottom`, and `.align-text-top` as needed.\n\nTo vertically center non-inline content (like `<div>`s and more), use our [flex box utilities]({{< docsref \"/utilities/flex#align-items\" >}}).\n\nWith inline elements:\n\n{{< example >}}\n<span class=\"align-baseline\">baseline</span>\n<span class=\"align-top\">top</span>\n<span class=\"align-middle\">middle</span>\n<span class=\"align-bottom\">bottom</span>\n<span class=\"align-text-top\">text-top</span>\n<span class=\"align-text-bottom\">text-bottom</span>\n{{< /example >}}\n\nWith table cells:\n\n{{< example >}}\n<table style=\"height: 100px;\">\n  <tbody>\n    <tr>\n      <td class=\"align-baseline\">baseline</td>\n      <td class=\"align-top\">top</td>\n      <td class=\"align-middle\">middle</td>\n      <td class=\"align-bottom\">bottom</td>\n      <td class=\"align-text-top\">text-top</td>\n      <td class=\"align-text-bottom\">text-bottom</td>\n    </tr>\n  </tbody>\n</table>\n{{< /example >}}\n\n## Sass\n\n### Utilities API\n\nVertical align utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-vertical-align\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/5.2/utilities/visibility.md",
    "content": "---\nlayout: docs\ntitle: Visibility\ndescription: Control the visibility of elements, without modifying their display, with visibility utilities.\ngroup: utilities\n---\n\nSet the `visibility` of elements with our visibility utilities. These utility classes do not modify the `display` value at all and do not affect layout – `.invisible` elements still take up space in the page.\n\n{{< callout warning >}}\nElements with the `.invisible` class will be hidden *both* visually and for assistive technology/screen reader users.\n{{< /callout >}}\n\nApply `.visible` or `.invisible` as needed.\n\n```html\n<div class=\"visible\">...</div>\n<div class=\"invisible\">...</div>\n```\n\n```scss\n// Class\n.visible {\n  visibility: visible !important;\n}\n.invisible {\n  visibility: hidden !important;\n}\n```\n\n## Sass\n\n### Utilities API\n\nVisibility utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref \"/utilities/api#using-the-api\" >}})\n\n{{< scss-docs name=\"utils-visibility\" file=\"scss/_utilities.scss\" >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/_index.html",
    "content": "---\nlayout: redirect\nsitemap_exclude: true\nredirect: \"/docs/5.2/getting-started/introduction/\"\n---\n"
  },
  {
    "path": "src/common/bootstrap/site/content/docs/versions.md",
    "content": "---\ntitle: Versions\ndescription: An appendix of hosted documentation for nearly every release of Bootstrap, from v1 through v5.\n---\n\n{{< list-versions.inline >}}\n<div class=\"row\">\n  {{- range $release := sort (index $.Site.Data \"docs-versions\") \"group\" \"desc\" }}\n  <div class=\"col-md-6 col-lg-4 col-xl mb-4\">\n    <h2>{{ $release.group }}</h2>\n    <p>{{ $release.description }}</p>\n    {{- $versions := sort $release.versions \"v\" \"desc\" -}}\n    {{- range $i, $version := $versions }}\n      {{- $len := len $versions -}}\n      {{ if (eq $i 0) }}<div class=\"list-group\">{{ end }}\n        <a class=\"list-group-item list-group-item-action py-2 text-primary{{ if (eq $version.v $.Site.Params.docs_version) }} d-flex justify-content-between align-items-center{{ end }}\" href=\"{{ $release.baseurl }}/{{ $version.v }}/\">\n          {{ $version.v }}\n          {{ if (eq $version.v $.Site.Params.docs_version) -}}\n          <span class=\"badge bg-primary\">Latest</span>\n          {{- end }}\n        </a>\n      {{ if (eq (add $i 1) $len) }}</div>{{ end }}\n    {{ end -}}\n  </div>\n  {{ end -}}\n</div>\n{{< /list-versions.inline >}}\n"
  },
  {
    "path": "src/common/bootstrap/site/data/breakpoints.yml",
    "content": "- breakpoint: xs\n  abbr: \"\"\n  name: X-Small\n  min-width: 0px\n  container: \"\"\n\n- breakpoint: sm\n  abbr: -sm\n  name: Small\n  min-width: 576px\n  container: 540px\n\n- breakpoint: md\n  abbr: -md\n  name: Medium\n  min-width: 768px\n  container: 720px\n\n- breakpoint: lg\n  abbr: -lg\n  name: Large\n  min-width: 992px\n  container: 960px\n\n- breakpoint: xl\n  abbr: -xl\n  name: X-Large\n  min-width: 1200px\n  container: 1140px\n\n- breakpoint: xxl\n  abbr: -xxl\n  name: XX-Large\n  min-width: 1400px\n  container: 1320px\n"
  },
  {
    "path": "src/common/bootstrap/site/data/colors.yml",
    "content": "- name: blue\n  hex: \"#0d6efd\"\n- name: indigo\n  hex: \"#6610f2\"\n- name: purple\n  hex: \"#6f42c1\"\n- name: pink\n  hex: \"#d63384\"\n- name: red\n  hex: \"#dc3545\"\n- name: orange\n  hex: \"#fd7e14\"\n- name: yellow\n  hex: \"#ffc107\"\n- name: green\n  hex: \"#198754\"\n- name: teal\n  hex: \"#20c997\"\n- name: cyan\n  hex: \"#0dcaf0\"\n- name: white\n  hex: \"#fff\"\n- name: gray\n  hex: \"#6c757d\"\n- name: gray-dark\n  hex: \"#343a40\"\n"
  },
  {
    "path": "src/common/bootstrap/site/data/core-team.yml",
    "content": "- name: Mark Otto\n  user: mdo\n\n- name: Jacob Thornton\n  user: fat\n\n- name: XhmikosR\n  user: xhmikosr\n\n- name: GeoSot\n  user: geosot\n\n- name: Rohit Sharma\n  user: rohit2sharma95\n\n- name: alpadev\n  user: alpadev\n\n- name: Gaël Poupard\n  user: ffoodd\n\n- name: Patrick H. Lauke\n  user: patrickhlauke\n\n- name: Martijn Cuppens\n  user: martijncuppens\n\n- name: Johann-S\n  user: johann-s\n\n- name: Gleb Mazovetskiy\n  user: glebm\n"
  },
  {
    "path": "src/common/bootstrap/site/data/docs-versions.yml",
    "content": "- group: v1.x\n  baseurl: \"https://getbootstrap.com\"\n  description: \"Every minor and patch release from v1 is listed below.\"\n  versions:\n    - v: \"1.0.0\"\n    - v: \"1.1.0\"\n    - v: \"1.1.1\"\n    - v: \"1.2.0\"\n    - v: \"1.3.0\"\n    - v: \"1.4.0\"\n\n- group: v2.x\n  baseurl: \"https://getbootstrap.com\"\n  description: \"Every minor and patch release from v2 is listed below.\"\n  versions:\n    - v: \"2.0.0\"\n    - v: \"2.0.1\"\n    - v: \"2.0.2\"\n    - v: \"2.0.3\"\n    - v: \"2.0.4\"\n    - v: \"2.1.0\"\n    - v: \"2.1.1\"\n    - v: \"2.2.0\"\n    - v: \"2.2.1\"\n    - v: \"2.2.2\"\n    - v: \"2.3.0\"\n    - v: \"2.3.1\"\n    - v: \"2.3.2\"\n\n- group: v3.x\n  baseurl: \"https://getbootstrap.com/docs\"\n  description: \"Every minor and patch release from v3 is listed below. Last update was v3.4.1.\"\n  versions:\n    - v: \"3.3\"\n    - v: \"3.4\"\n\n- group: v4.x\n  baseurl: \"https://getbootstrap.com/docs\"\n  description: \"Our previous major release with its minor releases. Last update was v4.6.0.\"\n  versions:\n    - v: \"4.0\"\n    - v: \"4.1\"\n    - v: \"4.2\"\n    - v: \"4.3\"\n    - v: \"4.4\"\n    - v: \"4.5\"\n    - v: \"4.6\"\n\n- group: v5.x\n  baseurl: \"https://getbootstrap.com/docs\"\n  description: \"Current major release. Last update was v5.2.3.\"\n  versions:\n    - v: \"5.0\"\n    - v: \"5.1\"\n    - v: \"5.2\"\n"
  },
  {
    "path": "src/common/bootstrap/site/data/examples.yml",
    "content": "- category: Snippets\n  description: \"Common patterns for building sites and apps that build on existing components and utilities with custom CSS and more.\"\n  examples:\n    - name: Headers\n      description: \"Display your branding, navigation, search, and more with these header components\"\n    - name: Heroes\n      description: \"Set the stage on your homepage with heroes that feature clear calls to action.\"\n    - name: Features\n      description: \"Explain the features, benefits, or other details in your marketing content.\"\n    - name: Sidebars\n      description: \"Common navigation patterns ideal for offcanvas or multi-column layouts.\"\n    - name: Footers\n      description: \"Finish every page strong with an awesome footer, big or small.\"\n    - name: Dropdowns\n      description: \"Enhance your dropdowns with filters, icons, custom styles, and more.\"\n    - name: List groups\n      description: \"Extend list groups with utilities and custom styles for any content.\"\n    - name: Modals\n      description: \"Transform modals to serve any purpose, from feature tours to dialogs.\"\n\n- category: Custom Components\n  description: \"Brand-new components and templates to help folks quickly get started with Bootstrap and demonstrate best practices for adding onto the framework.\"\n  examples:\n    - name: Album\n      description: \"Simple one-page template for photo galleries, portfolios, and more.\"\n    - name: Pricing\n      description: \"Example pricing page built with Cards and featuring a custom header and footer.\"\n    - name: Checkout\n      description: \"Custom checkout form showing our form components and their validation features.\"\n    - name: Product\n      description: \"Lean product-focused marketing page with extensive grid and image work.\"\n    - name: Cover\n      description: \"A one-page template for building simple and beautiful home pages.\"\n    - name: Carousel\n      description: \"Customize the navbar and carousel, then add some new components.\"\n    - name: Blog\n      description: \"Magazine like blog template with header, navigation, featured content.\"\n    - name: Dashboard\n      description: \"Basic admin dashboard shell with fixed sidebar and navbar.\"\n    - name: Sign-in\n      description: \"Custom form layout and design for a simple sign in form.\"\n    - name: Sticky footer\n      description: \"Attach a footer to the bottom of the viewport when page content is short.\"\n    - name: Sticky footer navbar\n      description: \"Attach a footer to the bottom of the viewport with a fixed top navbar.\"\n    - name: Jumbotron\n      description: \"Use utilities to recreate and enhance Bootstrap 4's jumbotron.\"\n\n- category: Framework\n  description: \"Examples that focus on implementing uses of built-in components provided by Bootstrap.\"\n  examples:\n    - name: \"Starter template\"\n      description: \"Nothing but the basics: compiled CSS and JavaScript.\"\n    - name: Grid\n      description: \"Multiple examples of grid layouts with all four tiers, nesting, and more.\"\n    - name: Cheatsheet\n      description: \"Kitchen sink of Bootstrap components.\"\n    - name: Cheatsheet RTL\n      description: \"Kitchen sink of Bootstrap components, RTL.\"\n\n- category: Navbars\n  description: \"Taking the default navbar component and showing how it can be moved, placed, and extended.\"\n  examples:\n    - name: Navbars\n      description: \"Demonstration of all responsive and container options for the navbar.\"\n    - name: Navbars offcanvas\n      description: \"Same as the Navbars example, but with our offcanvas component.\"\n    - name: Navbar static\n      description: \"Single navbar example of a static top navbar along with some additional content.\"\n    - name: Navbar fixed\n      description: \"Single navbar example with a fixed top navbar along with some additional content.\"\n    - name: Navbar bottom\n      description: \"Single navbar example with a bottom navbar along with some additional content.\"\n    - name: Offcanvas navbar\n      description: \"Turn your expandable navbar into a sliding offcanvas menu (doesn't use our offcanvas component).\"\n\n- category: RTL\n  description: \"See Bootstrap's RTL version in action with these modified Custom Components examples.\"\n  examples:\n    - name: Album RTL\n      description: \"Simple one-page template for photo galleries, portfolios, and more.\"\n    - name: Checkout RTL\n      description: \"Custom checkout form showing our form components and their validation features.\"\n    - name: Carousel RTL\n      description: \"Customize the navbar and carousel, then add some new components.\"\n    - name: Blog RTL\n      description: \"Magazine like blog template with header, navigation, featured content.\"\n    - name: Dashboard RTL\n      description: \"Basic admin dashboard shell with fixed sidebar and navbar.\"\n\n- category: Integrations\n  description: \"Integrations with external libraries.\"\n  examples:\n    - name: \"Masonry\"\n      description: \"Combine the powers of the Bootstrap grid and the Masonry layout.\"\n"
  },
  {
    "path": "src/common/bootstrap/site/data/grays.yml",
    "content": "- name: 100\n  hex: \"#f8f9fa\"\n- name: 200\n  hex: \"#e9ecef\"\n- name: 300\n  hex: \"#dee2e6\"\n- name: 400\n  hex: \"#ced4da\"\n- name: 500\n  hex: \"#adb5bd\"\n- name: 600\n  hex: \"#868e96\"\n- name: 700\n  hex: \"#495057\"\n- name: 800\n  hex: \"#343a40\"\n- name: 900\n  hex: \"#212529\"\n"
  },
  {
    "path": "src/common/bootstrap/site/data/icons.yml",
    "content": "preferred:\n  - name: Font Awesome\n    website: https://fontawesome.com/\n  - name: Feather\n    website: https://feathericons.com/\n  - name: Octicons\n    website: https://primer.style/octicons/\n\nmore:\n  - name: Bytesize\n    website: https://github.com/danklammer/bytesize-icons\n  - name: CoreUI Icons\n    website: https://icons.coreui.io/\n  - name: Google Material icons\n    website: https://fonts.google.com/icons\n  - name: Ionicons\n    website: https://ionic.io/ionicons\n  - name: Dripicons\n    website: http://demo.amitjakhu.com/dripicons/\n  - name: Ikons\n    website: http://ikons.piotrkwiatkowski.co.uk/\n  - name: Icons8\n    website: https://icons8.com/\n  - name: icofont\n    website: https://icofont.com/\n  - name: Tabler Icons\n    website: https://tabler-icons.io/\n"
  },
  {
    "path": "src/common/bootstrap/site/data/plugins.yml",
    "content": "- name: Alert\n  description: Show and hide alert messages to your users.\n  link: components/alerts/#javascript-behavior\n\n- name: Button\n  description: Programmatically control the active state for buttons.\n  link: components/buttons/#button-plugin\n\n- name: Carousel\n  description: Add slideshows to any page, including support for crossfade.\n  link: components/carousel/\n\n- name: Collapse\n  description: Expand and collapse areas of content, or create accordions.\n  link: components/collapse/\n\n- name: Dropdown\n  description: Create menus of links, actions, forms, and more.\n  link: components/dropdowns/\n\n- name: Modal\n  description: Add flexible and responsive dialogs to your project.\n  link: components/modal/\n\n- name: Offcanvas\n  description: Build and toggle hidden sidebars into any page.\n  link: components/offcanvas/\n\n- name: Popover\n  description: Create custom overlays. Built on Popper.\n  link: components/popovers/\n\n- name: Scrollspy\n  description: Automatically update active nav links based on page scroll.\n  link: components/scrollspy/\n\n- name: Tab\n  description: Allow Bootstrap nav components to toggle contents.\n  link: components/navs-tabs/\n\n- name: Toast\n  description: Show and hide notifications to your visitors.\n  link: components/toasts/\n\n- name: Tooltip\n  description: Replace browser tooltips with custom ones. Built on Popper.\n  link: components/tooltips/\n"
  },
  {
    "path": "src/common/bootstrap/site/data/sidebar.yml",
    "content": "# This file holds all sidebar menu's entries.\n# The logic for the sidebar generation is in \"site/layouts/partials/docs-sidebar.html\".\n\n- title: Getting started\n  icon: book-half\n  icon_color: indigo\n  pages:\n    - title: Introduction\n    - title: Download\n    - title: Contents\n    - title: Browsers & devices\n    - title: JavaScript\n    - title: Webpack\n    - title: Parcel\n    - title: Vite\n    - title: Accessibility\n    - title: RFS\n    - title: RTL\n    - title: Contribute\n\n- title: Customize\n  icon: palette2\n  icon_color: pink\n  pages:\n    - title: Overview\n    - title: Sass\n    - title: Options\n    - title: Color\n    - title: Components\n    - title: CSS variables\n    - title: Optimize\n\n- title: Layout\n  icon: grid-fill\n  icon_color: teal\n  pages:\n    - title: Breakpoints\n    - title: Containers\n    - title: Grid\n    - title: Columns\n    - title: Gutters\n    - title: Utilities\n    - title: Z-index\n    - title: CSS Grid\n\n- title: Content\n  icon: file-earmark-richtext\n  icon_color: gray\n  pages:\n    - title: Reboot\n    - title: Typography\n    - title: Images\n    - title: Tables\n    - title: Figures\n\n- title: Forms\n  icon: ui-radios\n  icon_color: blue\n  pages:\n    - title: Overview\n    - title: Form control\n    - title: Select\n    - title: Checks & radios\n    - title: Range\n    - title: Input group\n    - title: Floating labels\n    - title: Layout\n    - title: Validation\n\n- title: Components\n  icon: menu-button-wide-fill\n  icon_color: cyan\n  pages:\n    - title: Accordion\n    - title: Alerts\n    - title: Badge\n    - title: Breadcrumb\n    - title: Buttons\n    - title: Button group\n    - title: Card\n    - title: Carousel\n    - title: Close button\n    - title: Collapse\n    - title: Dropdowns\n    - title: List group\n    - title: Modal\n    - title: Navbar\n    - title: Navs & tabs\n    - title: Offcanvas\n    - title: Pagination\n    - title: Placeholders\n    - title: Popovers\n    - title: Progress\n    - title: Scrollspy\n    - title: Spinners\n    - title: Toasts\n    - title: Tooltips\n\n- title: Helpers\n  icon: magic\n  icon_color: orange\n  pages:\n    - title: Clearfix\n    - title: Color & background\n    - title: Colored links\n    - title: Position\n    - title: Ratio\n    - title: Stacks\n    - title: Stretched link\n    - title: Text truncation\n    - title: Vertical rule\n    - title: Visually hidden\n\n- title: Utilities\n  icon: braces-asterisk\n  icon_color: red\n  pages:\n    - title: API\n    - title: Background\n    - title: Borders\n    - title: Colors\n    - title: Display\n    - title: Flex\n    - title: Float\n    - title: Interactions\n    - title: Opacity\n    - title: Overflow\n    - title: Position\n    - title: Shadows\n    - title: Sizing\n    - title: Spacing\n    - title: Text\n    - title: Vertical align\n    - title: Visibility\n\n- title: Extend\n  icon: tools\n  icon_color: blue\n  pages:\n    - title: Approach\n    - title: Icons\n\n- title: About\n  icon: globe2\n  icon_color: indigo\n  pages:\n    - title: Overview\n    - title: Team\n    - title: Brand\n    - title: License\n    - title: Translations\n\n- title: Migration\n"
  },
  {
    "path": "src/common/bootstrap/site/data/theme-colors.yml",
    "content": "- name: primary\n  hex: \"#0d6efd\"\n- name: secondary\n  hex: \"#6c757d\"\n- name: success\n  hex: \"#28a745\"\n- name: danger\n  hex: \"#dc3545\"\n- name: warning\n  hex: \"#ffc107\"\n  contrast_color: dark\n- name: info\n  hex: \"#17a2b8\"\n  contrast_color: dark\n- name: light\n  hex: \"#f8f9fa\"\n  contrast_color: dark\n- name: dark\n  hex: \"#343a40\"\n"
  },
  {
    "path": "src/common/bootstrap/site/data/translations.yml",
    "content": "- name: 中文(繁體)\n  code: zh-tw\n  description: Bootstrap 4 繁體中文手冊\n  url: https://bootstrap.hexschool.com/\n\n- name: Chinese\n  code: zh\n  description: Bootstrap 4 · 全球最流行的 HTML、CSS 和 JS 工具库。\n  url: https://code.z01.com/v4/\n\n- name: Brazilian Portuguese\n  code: pt-BR\n  description: Bootstrap 4 Português do Brasil\n  url: https://getbootstrap.com.br/v4/\n\n- name: Japanese\n  code: ja\n  description: Bootstrap 5 日本語リファレンス\n  url: https://getbootstrap.jp/\n\n- name: Russian\n  code: ru\n  description: Bootstrap 5 на русском\n  url: https://getbootstrap.su/\n\n- name: Korean\n  code: ko\n  description: Bootstrap 5 한국어 문서\n  url: https://getbootstrap.kr/\n\n- name: 中文(繁體)\n  code: zh-tw\n  description: Bootstrap 5 繁體中文手冊\n  url: https://bootstrap5.hexschool.com/\n\n- name: Simplified Chinese\n  code: zh-CN\n  description: Bootstrap 5 中文文档\n  url: https://v5.bootcss.com/\n  \n- name: Spanish\n  code: es\n  description: Bootstrap 5 Español\n  url: https://bootstrap.esdocu.com/\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/404.html",
    "content": "{{ define \"body_override\" }}<body class=\"d-flex flex-column min-vh-100\">{{ end }}\n{{ define \"main\" }}\n  <main class=\"my-auto p-5\" id=\"content\">\n    {{ .Content }}\n  </main>\n{{ end }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/_markup/render-heading.html",
    "content": "<h{{ .Level }} id=\"{{ .Anchor | safeURL }}\">{{ .Text | safeHTML }}\n{{- if and (ge .Level .Page.Site.Params.anchors.min) (le .Level .Page.Site.Params.anchors.max) }}{{\" \" -}}\n<a class=\"anchor-link\" href=\"#{{ .Anchor | safeURL }}\" aria-label=\"Link to this section: {{ .Text | safeHTML }}\"></a>\n{{- end -}}\n</h{{ .Level }}>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/baseof.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    {{ partial \"header\" . }}\n  </head>\n  {{ block \"body_override\" . }}<body>{{ end }}\n    {{ partial \"skippy\" . }}\n    {{ partial \"icons\" . }}\n\n    {{ partial \"docs-navbar\" . }}\n\n    {{ block \"main\" . }}\n    {{ end }}\n\n    {{ partial \"footer\" . }}\n    {{ partial \"scripts\" . }}\n\n    {{ block \"footer\" . }}\n    {{ end }}\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/docs.html",
    "content": "{{ define \"main\" }}\n  <div class=\"container-xxl bd-gutter mt-3 my-md-4 bd-layout\">\n    <aside class=\"bd-sidebar\">\n      <div class=\"offcanvas-lg offcanvas-start\" tabindex=\"-1\" id=\"bdSidebar\" aria-labelledby=\"bdSidebarOffcanvasLabel\">\n        <div class=\"offcanvas-header border-bottom\">\n          <h5 class=\"offcanvas-title\" id=\"bdSidebarOffcanvasLabel\">Browse docs</h5>\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\" data-bs-target=\"#bdSidebar\"></button>\n        </div>\n\n        <div class=\"offcanvas-body\">\n          {{ partial \"docs-sidebar\" . }}\n        </div>\n      </div>\n    </aside>\n\n    <main class=\"bd-main order-1\">\n      <div class=\"bd-intro pt-2 ps-lg-2\">\n        <div class=\"d-md-flex flex-md-row-reverse align-items-center justify-content-between\">\n          <a class=\"btn btn-sm btn-bd-light mb-3 mb-md-0 rounded-2\" href=\"{{ .Site.Params.repo }}/blob/v{{ .Site.Params.current_version }}/site/content/{{ .Page.File.Path | replaceRE `\\\\` \"/\" }}\" title=\"View and edit this file on GitHub\" target=\"_blank\" rel=\"noopener\">\n            View on GitHub\n          </a>\n          <h1 class=\"bd-title mb-0\" id=\"content\">{{ .Title | markdownify }}</h1>\n        </div>\n        <p class=\"bd-lead\">{{ .Page.Params.Description | markdownify }}</p>\n        {{ partial \"ads\" . }}\n      </div>\n\n      {{ if (eq .Page.Params.toc true) }}\n        <div class=\"bd-toc mt-3 mb-5 my-lg-0 ps-xl-3 mb-lg-5 text-muted\">\n          <button class=\"btn btn-link link-dark p-md-0 mb-2 mb-md-0 text-decoration-none bd-toc-toggle d-md-none\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#tocContents\" aria-expanded=\"false\" aria-controls=\"tocContents\">\n            On this page\n            <svg class=\"bi d-md-none ms-2\" aria-hidden=\"true\"><use xlink:href=\"#chevron-expand\"></use></svg>\n          </button>\n          <strong class=\"d-none d-md-block h6 my-2\">On this page</strong>\n          <hr class=\"d-none d-md-block my-2\">\n          <div class=\"collapse bd-toc-collapse\" id=\"tocContents\">\n            {{ .TableOfContents }}\n          </div>\n        </div>\n      {{ end }}\n\n      <div class=\"bd-content ps-lg-2\">\n        {{ if .Page.Params.sections }}\n          <div class=\"row g-3\">\n            {{ range .Page.Params.sections }}\n            <div class=\"col-md-6\">\n              <a class=\"d-block text-decoration-none\" href=\"../{{ urlize .title }}/\">\n                <strong class=\"d-block h5 mb-0\">{{ .title }}</strong>\n                <span class=\"text-secondary\">{{ .description }}</span>\n              </a>\n            </div>\n            {{ end }}\n          </div>\n        {{ end }}\n\n        {{ .Content }}\n      </div>\n    </main>\n  </div>\n{{ end }}\n{{ define \"footer\" }}\n  {{ range .Page.Params.extra_js -}}\n    <script{{ with .async }} async{{ end }} src=\"{{ .src }}\"></script>\n  {{- end -}}\n  <div class=\"position-fixed\"><input type=\"text\" tabindex=\"-1\"></div>\n{{ end }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/examples.html",
    "content": "<!doctype html>\n<html {{ if eq .Page.Params.direction \"rtl\" }}lang=\"ar\" dir=\"rtl\"{{ else }}lang=\"en\"{{ end }}{{ with .Page.Params.html_class }} class=\"{{ . }}\"{{ end }}>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"{{ .Site.Params.authors }}\">\n    <meta name=\"generator\" content=\"Hugo {{ hugo.Version }}\">\n    <title>{{ .Page.Title | markdownify }} · {{ .Site.Title | markdownify }} v{{ .Site.Params.docs_version }}</title>\n\n    <link rel=\"canonical\" href=\"{{ .Permalink }}\">\n\n    {{ with .Params.robots -}}\n    <meta name=\"robots\" content=\"{{ . }}\">\n    {{- end }}\n\n    {{ partial \"stylesheet\" . }}\n    {{ partial \"favicons\" . }}\n\n    <style>\n      .bd-placeholder-img {\n        font-size: 1.125rem;\n        text-anchor: middle;\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        user-select: none;\n      }\n\n      @media (min-width: 768px) {\n        .bd-placeholder-img-lg {\n          font-size: 3.5rem;\n        }\n      }\n\n      .b-example-divider {\n        height: 3rem;\n        background-color: rgba(0, 0, 0, .1);\n        border: solid rgba(0, 0, 0, .15);\n        border-width: 1px 0;\n        box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);\n      }\n\n      .b-example-vr {\n        flex-shrink: 0;\n        width: 1.5rem;\n        height: 100vh;\n      }\n\n      .bi {\n        vertical-align: -.125em;\n        fill: currentColor;\n      }\n\n      .nav-scroller {\n        position: relative;\n        z-index: 2;\n        height: 2.75rem;\n        overflow-y: hidden;\n      }\n\n      .nav-scroller .nav {\n        display: flex;\n        flex-wrap: nowrap;\n        padding-bottom: 1rem;\n        margin-top: -1px;\n        overflow-x: auto;\n        text-align: center;\n        white-space: nowrap;\n        -webkit-overflow-scrolling: touch;\n      }\n    </style>\n\n    {{ range .Page.Params.extra_css }}\n    {{ \"<!-- Custom styles for this template -->\" | safeHTML }}\n    <link href=\"{{ . }}\" rel=\"stylesheet\">\n    {{- end }}\n  </head>\n  <body{{ with .Page.Params.body_class }} class=\"{{ . }}\"{{ end }}>\n    {{ .Content }}\n\n    {{ if ne .Page.Params.include_js false -}}\n      {{- if eq hugo.Environment \"production\" -}}\n        <script src=\"/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.min.js\" {{ printf \"integrity=%q\" .Site.Params.cdn.js_bundle_hash | safeHTMLAttr }} crossorigin=\"anonymous\"></script>\n      {{- else -}}\n        <script src=\"/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.js\"></script>\n      {{- end }}\n\n      {{ range .Page.Params.extra_js -}}\n        <script{{ with .async }} async{{ end }} src=\"{{ .src }}\"{{ with .integrity }} {{ printf \"integrity=%q\" . | safeHTMLAttr }} crossorigin=\"anonymous\"{{ end }}></script>\n      {{- end -}}\n    {{- end }}\n  </body>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/home.html",
    "content": "{{ define \"main\" }}\n  <main>\n    {{ partial \"home/masthead\" . }}\n    {{ partial \"home/masthead-followup\" . }}\n  </main>\n\n  {{ .Content }}\n{{ end }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/redirect.html",
    "content": "{{ partial \"redirect\" (.Page.Params.redirect | absURL) }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/_default/single.html",
    "content": "{{ define \"main\" }}\n  <header class=\"py-5 border-bottom\">\n  <div class=\"container-xxl bd-gutter pt-md-1 pb-md-4\">\n      <div class=\"row\">\n        <div class=\"col-xl-8\">\n          <h1 class=\"bd-title mt-0\">{{ .Title | markdownify }}</h1>\n          <p class=\"bd-lead\">{{ .Page.Params.Description | markdownify }}</p>\n          {{ if eq .Title \"Examples\" }}\n          <div class=\"d-flex flex-column flex-md-row gap-3\">\n            <a href=\"{{ .Site.Params.download.dist_examples }}\" class=\"btn btn-lg bd-btn-lg btn-bd-primary d-flex align-items-center justify-content-center fw-semibold\" onclick=\"ga('send', 'event', 'Examples', 'Hero', 'Download Examples');\">\n              <svg class=\"bi me-2\" aria-hidden=\"true\"><use xlink:href=\"#box-seam\"></use></svg>\n              Download examples\n            </a>\n            <a href=\"{{ .Site.Params.download.source }}\" class=\"btn btn-lg bd-btn-lg btn-outline-secondary\" onclick=\"ga('send', 'event', 'Examples', 'Hero', 'Download');\">\n              Download source code\n            </a>\n          </div>\n          {{ end }}\n        </div>\n        <div class=\"col-xl-4 d-lg-flex justify-content-xl-end\">\n          {{ partial \"ads\" . }}\n        </div>\n      </div>\n    </div>\n  </header>\n\n  <main class=\"bd-content order-1 py-5\" id=\"content\">\n    <div class=\"container-xxl bd-gutter\">\n      {{ .Content }}\n\n      {{ if eq .Title \"Examples\" }}\n        <hr class=\"my-5\">\n        <div class=\"container\">\n          <div class=\"text-center\">\n            <div class=\"masthead-followup-icon d-inline-block mb-2 text-bg-danger\">\n              {{ partial \"icons/droplet-fill.svg\" (dict \"width\" \"32\" \"height\" \"32\") }}\n            </div>\n            <h2 class=\"display-6 fw-normal\">Go further with Bootstrap Themes</h2>\n            <p class=\"col-md-10 col-lg-8 mx-auto lead\">\n              Need something more than these examples? Take Bootstrap to the next level with premium themes from the <a href=\"{{ .Site.Params.themes }}\">official Bootstrap Themes marketplace</a>. They’re built as their own extended frameworks, rich with new components and plugins, documentation, and powerful build tools.\n            </p>\n            <a href=\"{{ .Site.Params.themes }}\" class=\"btn btn-lg btn-outline-primary mb-3\">Browse themes</a>\n          </div>\n          <img class=\"d-block img-fluid mt-3 mx-auto\" srcset=\"/docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-themes-collage.png,\n                                                              /docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-themes-collage@2x.png 2x\"\n                                                        src=\"/docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-themes-collage.png\"\n                                                        alt=\"Bootstrap Themes\" width=\"1150\" height=\"320\" loading=\"lazy\">\n        </div>\n      {{ end }}\n    </div>\n  </main>\n{{ end }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/alias.html",
    "content": "{{ partial \"redirect\" .Permalink }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/ads.html",
    "content": "<script async src=\"https://cdn.carbonads.com/carbon.js?serve=CKYIKKJL&placement=getbootstrapcom\" id=\"_carbonads_js\"></script>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/analytics.html",
    "content": "<script defer src=\"https://cdn.usefathom.com/script.js\" data-site=\"ITUSEYJG\"></script>\n\n<script>\n  window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;\n  ga('create', 'UA-146052-10', 'getbootstrap.com');\n  ga('set', 'anonymizeIp', true);\n  ga('send', 'pageview');\n</script>\n<script async src=\"https://www.google-analytics.com/analytics.js\"></script>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-danger-async-methods.md",
    "content": "#### Asynchronous methods and transitions\n\nAll API methods are **asynchronous** and start a **transition**. They return to the caller as soon as the transition is started but **before it ends**. In addition, a method call on a **transitioning component will be ignored**.\n\n[See our JavaScript documentation for more information](/docs/{{ .Site.Params.docs_version }}/getting-started/javascript/#asynchronous-functions-and-transitions).\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-info-mediaqueries-breakpoints.md",
    "content": "**Why subtract .02px?** Browsers don't currently support [range context queries](https://www.w3.org/TR/mediaqueries-4/#range-context), so we work around the limitations of [`min-` and `max-` prefixes](https://www.w3.org/TR/mediaqueries-4/#mq-min-max) and viewports with fractional widths (which can occur under certain conditions on high-dpi devices, for instance) by using values with higher precision.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-info-npm-starter.md",
    "content": "**Get started with Bootstrap via npm with our starter project!** Head to the [twbs/bootstrap-npm-starter](https://github.com/twbs/bootstrap-npm-starter) template repository to see how to build and customize Bootstrap in your own npm project. Includes Sass compiler, Autoprefixer, Stylelint, PurgeCSS, and Bootstrap Icons.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-info-prefersreducedmotion.md",
    "content": "The animation effect of this component is dependent on the `prefers-reduced-motion` media query. See the [reduced motion section of our accessibility documentation](/docs/{{ .Site.Params.docs_version }}/getting-started/accessibility/#reduced-motion).\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-info-sanitizer.md",
    "content": "By default, this component uses the built-in content sanitizer, which strips out any HTML elements that are not explicitly allowed. See the [sanitizer section in our JavaScript documentation](/docs/{{ .Site.Params.docs_version }}/getting-started/javascript/#sanitizer) for more details.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-warning-color-assistive-technologies.md",
    "content": "##### Conveying meaning to assistive technologies\n\nUsing color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies – such as screen readers. Ensure that information denoted by the color is either obvious from the content itself (e.g. the visible text), or is included through alternative means, such as additional text hidden with the `.visually-hidden` class.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-warning-data-bs-title-vs-title.md",
    "content": "Feel free to use either `title` or `data-bs-title` in your HTML. When `title` is used, Popper will replace it automatically with `data-bs-title` when the element is rendered.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/callout-warning-input-support.md",
    "content": "##### Date & color input support\n\nKeep in mind date inputs are [not fully supported](https://caniuse.com/input-datetime) by all browsers, namely Safari.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/docs-navbar.html",
    "content": "<header class=\"navbar navbar-expand-lg navbar-dark bd-navbar sticky-top\">\n  <nav class=\"container-xxl bd-gutter flex-wrap flex-lg-nowrap\" aria-label=\"Main navigation\">\n    {{- if eq .Layout \"docs\" }}\n    <div class=\"bd-navbar-toggle\">\n      <button class=\"navbar-toggler p-2\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#bdSidebar\" aria-controls=\"bdSidebar\" aria-label=\"Toggle docs navigation\">\n        {{ partial \"icons/hamburger.svg\" (dict \"class\" \"bi\" \"width\" \"24\" \"height\" \"24\") }}\n        <span class=\"d-none fs-6 pe-1\">Browse</span>\n      </button>\n    </div>\n    {{- else }}\n      <div class=\"d-lg-none\" style=\"width: 1.5rem;\"></div>\n    {{- end }}\n\n    <a class=\"navbar-brand p-0 me-0 me-lg-2\" href=\"/\" aria-label=\"Bootstrap\">\n      {{ partial \"icons/bootstrap-white-fill.svg\" (dict \"class\" \"d-block my-1\" \"width\" \"40\" \"height\" \"32\") }}\n    </a>\n\n    <div class=\"d-flex\">\n      {{ if eq .Layout \"docs\" }}\n        <div class=\"bd-search\" id=\"docsearch\" data-bd-docs-version=\"{{ .Site.Params.docs_version }}\"></div>\n      {{ end }}\n\n      <button class=\"navbar-toggler d-flex d-lg-none order-3 p-2\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#bdNavbar\" aria-controls=\"bdNavbar\" aria-label=\"Toggle navigation\">\n        <svg class=\"bi\" aria-hidden=\"true\"><use xlink:href=\"#three-dots\"></use></svg>\n      </button>\n    </div>\n\n    <div class=\"offcanvas-lg offcanvas-end flex-grow-1\" tabindex=\"-1\" id=\"bdNavbar\" aria-labelledby=\"bdNavbarOffcanvasLabel\" data-bs-scroll=\"true\">\n      <div class=\"offcanvas-header px-4 pb-0\">\n        <h5 class=\"offcanvas-title text-white\" id=\"bdNavbarOffcanvasLabel\">Bootstrap</h5>\n        <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\" data-bs-target=\"#bdNavbar\"></button>\n      </div>\n\n      <div class=\"offcanvas-body p-4 pt-0 p-lg-0\">\n        <hr class=\"d-lg-none text-white-50\">\n        <ul class=\"navbar-nav flex-row flex-wrap bd-navbar-nav\">\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2{{ if eq .Page.Layout \"docs\" }} active\" aria-current=\"true{{ end }}\" href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/introduction/\" onclick=\"ga('send', 'event', 'Navbar', 'Community links', 'Docs');\">Docs</a>\n          </li>\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2{{ if eq .Page.Title \"Examples\" }} active\" aria-current=\"true{{ end }}\" href=\"/docs/{{ .Site.Params.docs_version }}/examples/\" onclick=\"ga('send', 'event', 'Navbar', 'Community links', 'Examples');\">Examples</a>\n          </li>\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2\" href=\"{{ .Site.Params.icons }}\" onclick=\"ga('send', 'event', 'Navbar', 'Community links', 'Icons');\" target=\"_blank\" rel=\"noopener\">Icons</a>\n          </li>\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2\" href=\"{{ .Site.Params.themes }}\" onclick=\"ga('send', 'event', 'Navbar', 'Community links', 'Themes');\" target=\"_blank\" rel=\"noopener\">Themes</a>\n          </li>\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2\" href=\"{{ .Site.Params.blog }}\" onclick=\"ga('send', 'event', 'Navbar', 'Community links', 'Blog');\" target=\"_blank\" rel=\"noopener\">Blog</a>\n          </li>\n        </ul>\n\n        <hr class=\"d-lg-none text-white-50\">\n\n        <ul class=\"navbar-nav flex-row flex-wrap ms-md-auto\">\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2\" href=\"{{ .Site.Params.github_org }}\" target=\"_blank\" rel=\"noopener\">\n              {{ partial \"icons/github.svg\" (dict \"class\" \"navbar-nav-svg\" \"width\" \"16\" \"height\" \"16\") }}\n              <small class=\"d-lg-none ms-2\">GitHub</small>\n            </a>\n          </li>\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2\" href=\"https://twitter.com/{{ .Site.Params.twitter }}\" target=\"_blank\" rel=\"noopener\">\n              {{ partial \"icons/twitter.svg\" (dict \"class\" \"navbar-nav-svg\" \"width\" \"16\" \"height\" \"16\") }}\n              <small class=\"d-lg-none ms-2\">Twitter</small>\n            </a>\n          </li>\n          <li class=\"nav-item col-6 col-lg-auto\">\n            <a class=\"nav-link py-2 px-0 px-lg-2\" href=\"{{ .Site.Params.opencollective }}\" target=\"_blank\" rel=\"noopener\">\n              {{ partial \"icons/opencollective.svg\" (dict \"class\" \"navbar-nav-svg\" \"width\" \"16\" \"height\" \"16\") }}\n              <small class=\"d-lg-none ms-2\">Open Collective</small>\n            </a>\n          </li>\n          <li class=\"nav-item py-1 col-12 col-lg-auto\">\n            <div class=\"vr d-none d-lg-flex h-100 mx-lg-2 text-white\"></div>\n            <hr class=\"d-lg-none text-white-50\">\n          </li>\n          {{ partial \"docs-versions\" . }}\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/docs-sidebar.html",
    "content": "<nav class=\"bd-links w-100\" id=\"bd-docs-nav\" aria-label=\"Docs navigation\">\n  {{- $url := split .Permalink \"/\" -}}\n  {{- $page_slug := index $url (sub (len $url) 2) -}}\n\n  <ul class=\"bd-links-nav list-unstyled mb-0 pb-3 pb-md-2 pe-lg-2\">\n  {{- range $group := .Site.Data.sidebar -}}\n    {{- $link := $group.title -}}\n    {{- $link_slug := $link | urlize -}}\n\n    {{- if $group.pages -}}\n      {{- $link = index $group.pages 0 -}}\n      {{- $link_slug = $link.title | urlize -}}\n    {{- end -}}\n\n    {{- $group_slug := $group.title | urlize -}}\n    {{- $is_active_group := eq $.Page.Params.group $group_slug -}}\n\n    {{- if $group.pages }}\n      <li class=\"bd-links-group py-2\">\n        <strong class=\"bd-links-heading d-flex w-100 align-items-center fw-semibold\">\n          {{- if $group.icon }}\n            <svg class=\"bi me-2\"{{- if $group.icon_color }} style=\"color: var(--bs-{{ $group.icon_color }});\"{{- end }} aria-hidden=\"true\"><use xlink:href=\"#{{ $group.icon }}\"></use></svg>\n          {{- end }}\n          {{ $group.title }}\n        </strong>\n\n        <ul class=\"list-unstyled fw-normal pb-2 small\">\n          {{- range $doc := $group.pages -}}\n            {{- $doc_slug := $doc.title | urlize -}}\n            {{- $is_active := and $is_active_group (eq $page_slug $doc_slug) -}}\n            {{- $href := printf \"/docs/%s/%s/%s/\" $.Site.Params.docs_version $group_slug $doc_slug }}\n            <li><a href=\"{{ $href }}\" class=\"bd-links-link d-inline-block rounded{{ if $is_active }} active{{ end }}\"{{ if $is_active }} aria-current=\"page\"{{ end }}>{{ $doc.title }}</a></li>\n          {{- end }}\n        </ul>\n      </li>\n    {{- else }}\n      <li class=\"bd-links-span-all mt-1 mb-3 mx-4 border-top\"></li>\n      <li class=\"bd-links-span-all\">\n        <a href=\"/docs/{{ $.Site.Params.docs_version }}/{{ $group_slug }}/\" class=\"bd-links-link d-inline-block rounded small {{ if $is_active_group }} active{{ end }}\"{{ if $is_active_group }} aria-current=\"page\"{{ end }}>\n          {{ $group.title }}\n        </a>\n      </li>\n    {{- end }}\n  {{- end }}\n  </ul>\n</nav>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/docs-versions.html",
    "content": "{{- $url := split .Permalink \"/\" -}}\n{{- $page_version := index $url (sub (len $url) 4) -}}\n{{- $group_slug := index $url (sub (len $url) 3) -}}\n{{- $page_slug := index $url (sub (len $url) 2) -}}\n\n{{- $versions_link := \"\" -}}\n{{- if and (eq .Layout \"docs\") (eq $page_version .Site.Params.docs_version) -}}\n  {{- $versions_link = printf \"%s/%s/\" $group_slug $page_slug -}}\n{{- else if (eq .Layout \"single\") }}\n  {{- $versions_link = printf \"%s/\" $page_slug -}}\n{{- end }}\n\n<li class=\"nav-item dropdown\">\n  <button type=\"button\" class=\"btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" data-bs-display=\"static\">\n    <span class=\"d-lg-none\" aria-hidden=\"true\">Bootstrap</span><span class=\"visually-hidden\">Bootstrap&nbsp;</span> v{{ .Site.Params.docs_version }} <span class=\"visually-hidden\">(switch to other versions)</span>\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-end\">\n    <li><h6 class=\"dropdown-header\">v5 releases</h6></li>\n    <li>\n      <a class=\"dropdown-item current\" aria-current=\"true\" href=\"{{ if .IsHome }}/{{ else }}/docs/{{ .Site.Params.docs_version }}/{{ $versions_link }}{{ end }}\">\n        Latest ({{ .Site.Params.docs_version }}.x)\n      </a>\n    </li>\n    <li>\n      {{- if (eq .Page.Params.added \"5.2\") }}\n        <div class=\"dropdown-item disabled\">v5.1.3</div>\n      {{- else }}\n        <a class=\"dropdown-item\" href=\"https://getbootstrap.com/docs/5.1/{{ $versions_link }}\">v5.1.3</a>\n      {{- end }}\n    </li>\n    <li>\n      {{- if or (eq .Page.Params.added \"5.1\") (eq .Page.Params.added \"5.2\") }}\n        <div class=\"dropdown-item disabled\">v5.0.2</div>\n      {{- else }}\n        <a class=\"dropdown-item\" href=\"https://getbootstrap.com/docs/5.0/{{ $versions_link }}\">v5.0.2</a>\n      {{- end }}\n    </li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><h6 class=\"dropdown-header\">Previous releases</h6></li>\n    <li><a class=\"dropdown-item\" href=\"https://getbootstrap.com/docs/4.6/\">v4.6.x</a></li>\n    <li><a class=\"dropdown-item\" href=\"https://getbootstrap.com/docs/3.4/\">v3.4.1</a></li>\n    <li><a class=\"dropdown-item\" href=\"https://getbootstrap.com/2.3.2/\">v2.3.2</a></li>\n    <li><hr class=\"dropdown-divider\"></li>\n    <li><a class=\"dropdown-item\" href=\"/docs/versions/\">All versions</a></li>\n  </ul>\n</li>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/favicons.html",
    "content": "{{ \"<!-- Favicons -->\" | safeHTML }}\n<link rel=\"apple-touch-icon\" href=\"/docs/{{ .Site.Params.docs_version }}/assets/img/favicons/apple-touch-icon.png\" sizes=\"180x180\">\n<link rel=\"icon\" href=\"/docs/{{ .Site.Params.docs_version }}/assets/img/favicons/favicon-32x32.png\" sizes=\"32x32\" type=\"image/png\">\n<link rel=\"icon\" href=\"/docs/{{ .Site.Params.docs_version }}/assets/img/favicons/favicon-16x16.png\" sizes=\"16x16\" type=\"image/png\">\n<link rel=\"manifest\" href=\"/docs/{{ .Site.Params.docs_version }}/assets/img/favicons/manifest.json\">\n<link rel=\"mask-icon\" href=\"/docs/{{ .Site.Params.docs_version }}/assets/img/favicons/safari-pinned-tab.svg\" color=\"#712cf9\">\n<link rel=\"icon\" href=\"/docs/{{ .Site.Params.docs_version }}/assets/img/favicons/favicon.ico\">\n<meta name=\"theme-color\" content=\"#712cf9\">\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/footer.html",
    "content": "<footer class=\"bd-footer py-4 py-md-5 mt-5 bg-light\">\n  <div class=\"container py-4 py-md-5 px-4 px-md-3\">\n    <div class=\"row\">\n      <div class=\"col-lg-3 mb-3\">\n        <a class=\"d-inline-flex align-items-center mb-2 link-dark text-decoration-none\" href=\"/\" aria-label=\"Bootstrap\">\n          {{ partial \"icons/bootstrap-white-fill.svg\" (dict \"class\" \"d-block me-2\" \"width\" \"40\" \"height\" \"32\") }}\n          <span class=\"fs-5\">Bootstrap</span>\n        </a>\n        <ul class=\"list-unstyled small text-muted\">\n          <li class=\"mb-2\">Designed and built with all the love in the world by the <a href=\"/docs/{{ .Site.Params.docs_version }}/about/team/\">Bootstrap team</a> with the help of <a href=\"{{ .Site.Params.repo }}/graphs/contributors\">our contributors</a>.</li>\n          <li class=\"mb-2\">Code licensed <a href=\"{{ .Site.Params.repo }}/blob/main/LICENSE\" target=\"_blank\" rel=\"license noopener\">MIT</a>, docs <a href=\"https://creativecommons.org/licenses/by/3.0/\" target=\"_blank\" rel=\"license noopener\">CC BY 3.0</a>.</li>\n          <li class=\"mb-2\">Currently v{{ .Site.Params.current_version }}.</li>\n        </ul>\n      </div>\n      <div class=\"col-6 col-lg-2 offset-lg-1 mb-3\">\n        <h5>Links</h5>\n        <ul class=\"list-unstyled\">\n          <li class=\"mb-2\"><a href=\"/\">Home</a></li>\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/\">Docs</a></li>\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/examples/\">Examples</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.icons }}\">Icons</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.themes }}\">Themes</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.blog }}\">Blog</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.swag }}\">Swag Store</a></li>\n        </ul>\n      </div>\n      <div class=\"col-6 col-lg-2 mb-3\">\n        <h5>Guides</h5>\n        <ul class=\"list-unstyled\">\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/\">Getting started</a></li>\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/examples/starter-template/\">Starter template</a></li>\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/webpack/\">Webpack</a></li>\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/parcel/\">Parcel</a></li>\n          <li class=\"mb-2\"><a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/vite/\">Vite</a></li>\n        </ul>\n      </div>\n      <div class=\"col-6 col-lg-2 mb-3\">\n        <h5>Projects</h5>\n        <ul class=\"list-unstyled\">\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/bootstrap\">Bootstrap 5</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/bootstrap/tree/v4-dev\">Bootstrap 4</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/icons\">Icons</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/rfs\">RFS</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/bootstrap-npm-starter\">npm starter</a></li>\n        </ul>\n      </div>\n      <div class=\"col-6 col-lg-2 mb-3\">\n        <h5>Community</h5>\n        <ul class=\"list-unstyled\">\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/bootstrap/issues\">Issues</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.github_org }}/bootstrap/discussions\">Discussions</a></li>\n          <li class=\"mb-2\"><a href=\"https://github.com/sponsors/twbs\">Corporate sponsors</a></li>\n          <li class=\"mb-2\"><a href=\"{{ .Site.Params.opencollective }}\">Open Collective</a></li>\n          <li class=\"mb-2\"><a href=\"https://stackoverflow.com/questions/tagged/bootstrap-5\">Stack Overflow</a></li>\n        </ul>\n      </div>\n    </div>\n  </div>\n</footer>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/guide-footer.md",
    "content": "<hr class=\"my-5\">\n\n_See something wrong or out of date here? Please [open an issue on GitHub]({{ .Site.Params.repo }}/issues/new/choose). Need help troubleshooting? [Search or start a discussion]({{ .Site.Params.repo }}/discussions) on GitHub._\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/header.html",
    "content": "<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"description\" content=\"{{ .Page.Params.description | default .Site.Params.description | markdownify }}\">\n<meta name=\"author\" content=\"{{ .Site.Params.authors }}\">\n<meta name=\"generator\" content=\"Hugo {{ hugo.Version }}\">\n\n<meta name=\"docsearch:language\" content=\"en\">\n<meta name=\"docsearch:version\" content=\"{{ .Site.Params.docs_version }}\">\n\n<title>{{ if .IsHome }}{{ .Site.Title | markdownify }} · {{ .Site.Params.subtitle | markdownify }}{{ else }}{{ .Title | markdownify }} · {{ .Site.Title | markdownify }} v{{ .Site.Params.docs_version }}{{ end }}</title>\n\n<link rel=\"canonical\" href=\"{{ .Permalink }}\">\n\n{{- if eq .Page.Layout \"docs\" -}}\n<link rel=\"preconnect\" href=\"https://AK7KMZKZHQ-dsn.algolia.net\" crossorigin>\n{{- end }}\n\n{{ with .Params.robots -}}\n<meta name=\"robots\" content=\"{{ . }}\">\n{{- end }}\n\n{{ partial \"stylesheet\" . }}\n{{ partial \"favicons\" . }}\n{{ partial \"social\" . }}\n{{ partial \"analytics\" . }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/home/masthead-followup.html",
    "content": "<div class=\"container-xxl bd-gutter masthead-followup\">\n  <div class=\"col-lg-7 mx-auto pb-3 mb-3 mb-md-5 text-md-center\">\n    <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bd-violet-rgb);\">\n      <svg class=\"bi fs-1\"><use xlink:href=\"#code\"></use></svg>\n    </div>\n    <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Get started any way you&nbsp;want</h2>\n    <p class=\"lead fw-normal\">\n      Jump right into building with Bootstrap—use the CDN, install it via package manager, or download the source code.\n    </p>\n    <p class=\"d-flex justify-content-md-start justify-content-md-center lead fw-normal\">\n      <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/download/\" class=\"icon-link fw-semibold justify-content-center ps-md-4\">\n        Read installation docs\n        <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n      </a>\n    </p>\n  </div>\n\n  <section class=\"row g-3 g-md-5 mb-5 pb-5 justify-content-center\">\n    <div class=\"col-lg-6 py-lg-4 pe-lg-5\">\n      <svg class=\"bi mb-2 fs-2 text-muted\"><use xlink:href=\"#box-seam\"></use></svg>\n      <h3 class=\"fw-semibold\">Install via package manager</h3>\n      <p class=\"pe-lg-5\">\n        Install Bootstrap’s source Sass and JavaScript files via npm, RubyGems, Composer, or Meteor. Package managed installs don’t include documentation or our full build scripts. You can also <a href=\"https://github.com/twbs/bootstrap-npm-starter\">use our npm template repo</a> to quickly generate a Bootstrap project via npm.\n      </p>\n      {{ highlight (printf (\"npm install bootstrap@%s\") .Site.Params.current_version) \"sh\" \"\" }}\n      {{ highlight (printf (\"gem install bootstrap -v %s\") .Site.Params.current_ruby_version) \"sh\" \"\" }}\n      <p>\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/download/\">Read our installation docs</a> for more info and additional package managers.\n      </p>\n    </div>\n    <div class=\"col-lg-6 py-lg-4 ps-lg-5 border-lg-start\">\n      <svg class=\"bi mb-2 fs-2 text-muted\"><use xlink:href=\"#globe2\"></use></svg>\n      <h3 class=\"fw-semibold\">Include via CDN</h3>\n      <p class=\"pe-lg-5\">\n        When you only need to include Bootstrap’s compiled CSS or JS, you can use <a href=\"https://www.jsdelivr.com/package/npm/bootstrap\">jsDelivr</a>. See it in action with our simple <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/introduction/#quick-start\">quick start</a>, or <a href=\"/docs/{{ .Site.Params.docs_version }}/examples/\">browse the examples</a> to jumpstart your next project. You can also choose to include Popper and our JS <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/introduction/#separate\">separately</a>.\n      </p>\n      {{ highlight (printf (`<!-- CSS only -->\n<link href=\"%s\" rel=\"stylesheet\" integrity=%q crossorigin=\"anonymous\">\n`) .Site.Params.cdn.css (.Site.Params.cdn.css_hash | safeHTMLAttr)) \"html\" \"\" }}\n      {{ highlight (printf (`<!-- JavaScript Bundle with Popper -->\n<script src=\"%s\" integrity=%q crossorigin=\"anonymous\"></script>\n`) .Site.Params.cdn.js_bundle (.Site.Params.cdn.js_bundle_hash | safeHTMLAttr)) \"html\" \"\" }}\n    </div>\n\n    <div class=\"col-md-8 mx-auto text-center\">\n      <h4 class=\"fw-semibold\">Read our getting started guides</h4>\n      <p>Get a jump on including Bootstrap's source files in a new project with our official guides.</p>\n      <div class=\"d-flex flex-wrap align-items-center justify-content-center gap-4 mt-4\">\n        <a class=\"d-flex flex-column align-items-center text-decoration-none\" href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/webpack\">\n          <img class=\"d-block mb-2\" src=\"/docs/{{ .Site.Params.docs_version }}/assets/img/webpack.svg\" alt=\"\" width=\"72\" height=\"72\" loading=\"lazy\">\n          <span class=\"text-muted\">Webpack</span>\n        </a>\n        <a class=\"d-flex flex-column align-items-center text-decoration-none\" href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/parcel\">\n          <img class=\"d-block mb-2\" src=\"/docs/{{ .Site.Params.docs_version }}/assets/img/parcel.png\" alt=\"\" width=\"72\" height=\"72\" loading=\"lazy\">\n          <span class=\"text-muted\">Parcel</span>\n        </a>\n        <a class=\"d-flex flex-column align-items-center text-decoration-none\" href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/vite\">\n          <img class=\"d-block mb-2\" src=\"/docs/{{ .Site.Params.docs_version }}/assets/img/vite.svg\" alt=\"\" width=\"72\" height=\"72\" loading=\"lazy\">\n          <span class=\"text-muted\">Vite</span>\n        </a>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"col-lg-7 mb-5\">\n    <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bs-primary-rgb);\">\n      <svg class=\"bi fs-1\"><use xlink:href=\"#palette2\"></use></svg>\n    </div>\n    <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Customize everything with&nbsp;Sass</h2>\n    <p class=\"lead fw-normal\">\n      Bootstrap utilizes Sass for a modular and customizable architecture. Import only the components you need, enable global options like gradients and shadows, and write your own CSS with our variables, maps, functions, and mixins.\n    </p>\n    <p class=\"d-flex justify-content-start lead fw-normal\">\n      <a href=\"/docs/{{ .Site.Params.docs_version }}/customize/overview/\" class=\"icon-link fw-semibold\">\n        Learn more about customizing\n        <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n      </a>\n    </p>\n  </section>\n\n  <section class=\"row g-md-5 mb-5 pb-md-5\">\n    <div class=\"col-lg-6\">\n      <h3>Include all of Bootstrap’s Sass</h3>\n      <p>Import one stylesheet and you're off to the races with every feature of our CSS.</p>\n      {{ highlight (printf `// Variable overrides first\n$primary: #900;\n$enable-shadows: true;\n$prefix: \"mo-\";\n\n// Then import Bootstrap\n@import \"../node_modules/bootstrap/scss/bootstrap\";\n`) \"scss\" \"\" }}\n      <p>Learn more about our <a href=\"/docs/{{ .Site.Params.docs_version }}/customize/options/\">global Sass options</a>.</p>\n    </div>\n    <div class=\"col-lg-6\">\n      <h3>Include what you need</h3>\n      <p>The easiest way to customize Bootstrap—include only the CSS you need.</p>\n{{ highlight (printf `// Functions first\n@import \"../node_modules/bootstrap/scss/functions\";\n\n// Variable overrides second\n$primary: #900;\n$enable-shadows: true;\n$prefix: \"mo-\";\n\n// Required Bootstrap imports\n@import \"../node_modules/bootstrap/scss/variables\";\n@import \"../node_modules/bootstrap/scss/maps\";\n@import \"../node_modules/bootstrap/scss/mixins\";\n@import \"../node_modules/bootstrap/scss/root\";\n\n// Optional components\n@import \"../node_modules/bootstrap/scss/utilities\";\n@import \"../node_modules/bootstrap/scss/reboot\";\n@import \"../node_modules/bootstrap/scss/containers\";\n@import \"../node_modules/bootstrap/scss/grid\";\n@import \"../node_modules/bootstrap/scss/helpers\";\n@import \"../node_modules/bootstrap/scss/utilities/api\";\n`) \"scss\" \"\" }}\n      <p>Learn more about <a href=\"/docs/{{ .Site.Params.docs_version }}/customize/sass/\">using Bootstrap with Sass</a>.</p>\n    </div>\n  </section>\n\n  <section class=\"row g-md-5 pb-md-5 mb-5 align-items-center\">\n    <div class=\"col-lg-8 mb-5\">\n      <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bd-pink-rgb);\">\n        <svg class=\"bi fs-1\"><use xlink:href=\"#braces\"></use></svg>\n      </div>\n      <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Build and extend in real-time with CSS&nbsp;variables</h2>\n      <p class=\"lead fw-normal\">\n        Bootstrap 5 is evolving with each release to better utilize CSS variables for global theme styles, individual components, and even utilities. We provide dozens of variables for colors, font styles, and more at a <code>:root</code> level for use anywhere. On components and utilities, CSS variables are scoped to the relevant class and can easily be modified.\n      </p>\n      <p class=\"d-flex align-items-start flex-column lead fw-normal mb-0\">\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/customize/css-variables/\" class=\"icon-link fw-semibold mb-3\">\n          Learn more about CSS variables\n          <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n        </a>\n      </p>\n    </div>\n    <div class=\"row gx-md-5\">\n      <div class=\"col-lg-6 mb-3\">\n        <h3 class=\"fw-semibold\">Using CSS variables</h3>\n        <p>Use any of our <a href=\"/docs/{{ .Site.Params.docs_version }}/customize/css-variables/#root-variables\">global <code>:root</code> variables</a> to write new styles. CSS variables use the <code>var(--bs-variableName)</code> syntax and can be inherited by children elements.</p>\n        {{ highlight (printf `.component {\n  color: var(--bs-gray-800);\n  background-color: var(--bs-gray-100);\n  border: 1px solid var(--bs-gray-200);\n  border-radius: .25rem;\n}\n\n.component-header {\n  color: var(--bs-purple);\n}`) \"scss\" \"\" }}\n      </div>\n      <div class=\"col-lg-6 mb-3\">\n        <h3 class=\"fw-semibold\">Customizing via CSS variables</h3>\n        <p>Override global, component, or utility class variables to customize Bootstrap just how you like. No need to redeclare each rule, just a new variable value.</p>\n        {{ highlight (printf `body {\n  --bs-body-font-family: var(--bs-font-monospace);\n  --bs-body-line-height: 1.4;\n  --bs-body-bg: var(--bs-gray-100);\n}\n\n.table {\n  --bs-table-color: var(--bs-gray-600);\n  --bs-table-bg: var(--bs-gray-100);\n  --bs-table-border-color: transparent;\n}`) \"scss\" \"\" }}\n      </div>\n    </div>\n  </section>\n\n  <section class=\"row g-md-5 pb-md-5 mb-5 align-items-center\">\n    <div class=\"col-lg-6 mb-5 mb-md-0\">\n      <div class=\"masthead-followup-icon d-inline-block mb-3 me-2\" style=\"--bg-rgb: var(--bs-danger-rgb);\">\n        <svg class=\"bi fs-1\"><use xlink:href=\"#menu-button-wide-fill\"></use></svg>\n      </div>\n      <svg class=\"bi me-2 fs-2 text-muted\"><use xlink:href=\"#plus\"></use></svg>\n      <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bs-info-rgb);\">\n        <svg class=\"bi fs-1\"><use xlink:href=\"#braces-asterisk\"></use></svg>\n      </div>\n      <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Components, meet the Utility&nbsp;API</h2>\n      <p class=\"lead fw-normal\">\n        New in Bootstrap 5, our utilities are now generated by our <a href=\"/docs/{{ .Site.Params.docs_version }}/utilities/api/\">Utility API</a>. We built it as a feature-packed Sass map that can be quickly and easily customized. It's never been easier to add, remove, or modify any utility classes. Make utilities responsive, add pseudo-class variants, and give them custom names.\n      </p>\n      <p class=\"d-flex align-items-start flex-column lead fw-normal mb-0\">\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/utilities/api/\" class=\"icon-link fw-semibold mb-3\">\n          Learn more about utilities\n          <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n        </a>\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/examples#snippets\" class=\"icon-link fw-semibold\">\n          Explore customized components\n          <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n        </a>\n      </p>\n    </div>\n    <div class=\"col-lg-6\">\n      <div class=\"p-4 border rounded-3 mb-4\">\n        <h6>Quickly customize components</h6>\n        <hr class=\"mb-4\">\n        <ul class=\"nav nav-pills mb-4\" id=\"pillNav\" role=\"tablist\">\n          <li class=\"nav-item\" role=\"presentation\">\n            <button class=\"nav-link active\" id=\"home-tab\" data-bs-toggle=\"tab\" type=\"button\" role=\"tab\" aria-selected=\"true\">Home</button>\n          </li>\n          <li class=\"nav-item\" role=\"presentation\">\n            <button class=\"nav-link\" id=\"profile-tab\" data-bs-toggle=\"tab\" type=\"button\" role=\"tab\" aria-selected=\"false\">Profile</button>\n          </li>\n          <li class=\"nav-item\" role=\"presentation\">\n            <button class=\"nav-link\" id=\"contact-tab\" data-bs-toggle=\"tab\" type=\"button\" role=\"tab\" aria-selected=\"false\">Contact</button>\n          </li>\n        </ul>\n        <ul class=\"nav nav-pills nav-fill gap-2 p-1 small bg-white border rounded-5 shadow-sm\" id=\"pillNav2\" role=\"tablist\">\n          <li class=\"nav-item\" role=\"presentation\">\n            <button class=\"nav-link active rounded-5\" id=\"home-tab2\" data-bs-toggle=\"tab\" type=\"button\" role=\"tab\" aria-selected=\"true\">Home</button>\n          </li>\n          <li class=\"nav-item\" role=\"presentation\">\n            <button class=\"nav-link rounded-5\" id=\"profile-tab2\" data-bs-toggle=\"tab\" type=\"button\" role=\"tab\" aria-selected=\"false\">Profile</button>\n          </li>\n          <li class=\"nav-item\" role=\"presentation\">\n            <button class=\"nav-link rounded-5\" id=\"contact-tab2\" data-bs-toggle=\"tab\" type=\"button\" role=\"tab\" aria-selected=\"false\">Contact</button>\n          </li>\n        </ul>\n      </div>\n  {{ highlight (printf `// Create and extend utilities with the Utility API\n\n@import \"bootstrap/scss/bootstrap\";\n\n$utilities: map-merge(\n  $utilities,\n  (\n    \"cursor\": (\n      property: cursor,\n      class: cursor,\n      responsive: true,\n      values: auto pointer grab,\n    )\n  )\n);\n`) \"scss\" \"\" }}\n    </div>\n  </section>\n\n  <section class=\"pb-md-5 mb-5\">\n    <div class=\"col-lg-8 mb-5\">\n      <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bs-warning-rgb);\">\n        <svg class=\"bi fs-1\"><use xlink:href=\"#plugin\"></use></svg>\n      </div>\n      <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Powerful JavaScript plugins without&nbsp;jQuery</h2>\n      <p class=\"lead fw-normal\">\n        Easily add toggleable hidden elements, modals and offcanvas menus, popovers and tooltips, and so much more—all without jQuery. JavaScript in Bootstrap is HTML-first, which means adding plugins is as easy as adding <code>data</code> attributes. Need more control? Include individual plugins programmatically.\n      </p>\n      <p class=\"d-flex justify-content-start lead fw-normal mb-md-0\">\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/javascript/\" class=\"icon-link fw-semibold\">\n          Learn more about Bootstrap JavaScript\n          <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n        </a>\n      </p>\n    </div>\n    <div class=\"row gx-md-5\">\n      <div class=\"col-lg-6 mb-3\">\n        <h3 class=\"fw-semibold\">Data attribute API</h3>\n        <p>Why write more JavaScript when you can write HTML? Nearly all of Bootstrap's JavaScript plugins feature a first-class data API, allowing you to use JavaScript just by adding <code>data</code> attributes.</p>\n        <div class=\"p-4 mb-3 border rounded-3\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              Dropdown\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li><a class=\"dropdown-item\" href=\"#\">Dropdown item</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Dropdown item</a></li>\n              <li><a class=\"dropdown-item\" href=\"#\">Dropdown item</a></li>\n            </ul>\n          </div>\n        </div>\n\n        {{ highlight (printf `<div class=\"dropdown\">\n  <button class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    Dropdown\n  </button>\n  <ul class=\"dropdown-menu\">\n    <li><a class=\"dropdown-item\" href=\"#\">Dropdown item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Dropdown item</a></li>\n    <li><a class=\"dropdown-item\" href=\"#\">Dropdown item</a></li>\n  </ul>\n</div>\n`) \"html\" \"\" }}\n        <p>Learn more about <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/javascript/#using-bootstrap-as-a-module\">our JavaScript as modules</a> and <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/javascript/#programmatic-api\">using the programmatic API</a>.</p>\n      </div>\n      <div class=\"col-lg-6 mb-3\">\n        <h3 class=\"fw-semibold\">Comprehensive set of plugins</h3>\n        <p>Bootstrap features a dozen plugins that you can drop into any project. Drop them in all at once, or choose just the ones you need.</p>\n        <hr class=\"my-4\">\n        <div class=\"row g-3\">\n          {{- range $plugin := .Site.Data.plugins -}}\n            {{- $href := printf \"/docs/%s/%s\" $.Site.Params.docs_version $plugin.link }}\n            <div class=\"col-sm-6 mb-2\">\n              <a class=\"d-block pe-lg-4 text-decoration-none lh-sm\" href=\"{{ $href }}\">\n                <h4 class=\"mb-0 fs-5 fw-semibold\">{{ $plugin.name }}</h4>\n                <small class=\"text-muted\">{{ $plugin.description }}</small>\n              </a>\n            </div>\n          {{- end }}\n        </div>\n      </div>\n    </div>\n\n  </section>\n\n  <section class=\"row g-3 g-md-5 pb-md-5 mb-5 align-items-center\">\n    <div class=\"col-lg-6\">\n      <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bd-teal-rgb);\">\n        {{ partial \"icons/circle-square.svg\" (dict \"width\" \"32\" \"height\" \"32\") }}\n      </div>\n      <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Personalize it with Bootstrap&nbsp;Icons</h2>\n      <p class=\"lead fw-normal\">\n        <a href=\"{{ .Site.Params.icons }}\">Bootstrap Icons</a> is an open source SVG icon library featuring over 1,500 glyphs, with more added every release. They're designed to work in any project, whether you use Bootstrap itself or not. Use them as SVGs or icon fonts—both options give you vector scaling and easy customization via CSS.\n      </p>\n      <p class=\"d-flex justify-content-start lead fw-normal mb-md-0\">\n        <a href=\"{{ .Site.Params.icons }}\" class=\"icon-link fw-semibold\">\n          Get Bootstrap Icons\n          <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n        </a>\n      </p>\n    </div>\n    <div class=\"col-lg-6\">\n        <img class=\"img-fluid mt-3 mx-auto\" srcset=\"/docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-icons.png,\n                                                    /docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-icons@2x.png 2x\"\n                                            src=\"/docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-icons.png\"\n                                            alt=\"Bootstrap Icons\" width=\"700\" height=\"425\" loading=\"lazy\">\n    </div>\n  </section>\n\n  <section class=\"row g-3 g-md-5 pb-md-5 mb-5 align-items-center\">\n    <div class=\"col-lg-6\">\n      <div class=\"masthead-followup-icon d-inline-block mb-3\" style=\"--bg-rgb: var(--bd-violet-rgb);\">\n        {{ partial \"icons/droplet-fill.svg\" (dict \"width\" \"32\" \"height\" \"32\") }}\n      </div>\n      <h2 class=\"display-5 mb-3 fw-semibold lh-sm\">Make it yours with official Bootstrap Themes</h2>\n      <p class=\"lead fw-normal\">\n        Take Bootstrap to the next level with premium themes from the <a href=\"{{ .Site.Params.themes }}\">official Bootstrap Themes marketplace</a>. Themes are built on Bootstrap as their own extended frameworks, rich with new components and plugins, documentation, and powerful build tools.\n      </p>\n      <p class=\"d-flex justify-content-start lead fw-normal mb-md-0\">\n        <a href=\"{{ .Site.Params.themes }}\" class=\"icon-link fw-semibold\">\n          Browse Bootstrap Themes\n          <svg class=\"bi\"><use xlink:href=\"#arrow-right-short\"></use></svg>\n        </a>\n      </p>\n    </div>\n    <div class=\"col-lg-6\">\n        <img class=\"img-fluid mt-3 mx-auto\" srcset=\"/docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-themes.png,\n                                                    /docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-themes@2x.png 2x\"\n                                            src=\"/docs/{{ .Site.Params.docs_version }}/assets/img/bootstrap-themes.png\"\n                                            alt=\"Bootstrap Themes\" width=\"700\" height=\"500\" loading=\"lazy\">\n    </div>\n  </section>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/home/masthead.html",
    "content": "<div class=\"bd-masthead mb-3\" id=\"content\">\n  <div class=\"container-xxl bd-gutter\">\n    <div class=\"col-md-8 mx-auto text-center\">\n      <a class=\"d-flex flex-column flex-lg-row justify-content-center align-items-center mb-4 text-dark lh-sm text-decoration-none\" href=\"https://blog.getbootstrap.com/2022/07/19/bootstrap-5-2-0/\">\n        <strong class=\"d-sm-inline-block p-2 me-2 mb-2 mb-lg-0 rounded-3 masthead-notice\">New in v5.2</strong>\n        <span class=\"text-muted\">CSS variables, responsive offcanvas, new utilities, and more!</span>\n      </a>\n      <img src=\"/docs/{{ .Site.Params.docs_version }}/assets/brand/bootstrap-logo-shadow.png\" width=\"200\" height=\"165\" alt=\"Bootstrap\" class=\"d-block mx-auto mb-3\">\n      <h1 class=\"mb-3 fw-semibold\">Build fast, responsive sites with&nbsp;Bootstrap</h1>\n      <p class=\"lead mb-4\">\n        Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins.\n      </p>\n      <div class=\"d-flex flex-column flex-lg-row align-items-md-stretch justify-content-md-center gap-3 mb-4\">\n        <div class=\"d-inline-block v-align-middle fs-5\">\n          {{ highlight (printf (\"npm i bootstrap@%s\") .Site.Params.current_version) \"sh\" \"\" }}\n        </div>\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/introduction/\" class=\"btn btn-lg bd-btn-lg btn-bd-primary d-flex align-items-center justify-content-center fw-semibold\" onclick=\"ga('send', 'event', 'Jumbotron actions', 'Get started', 'Get started');\">\n          <svg class=\"bi me-2\" aria-hidden=\"true\"><use xlink:href=\"#book-half\"></use></svg>\n          Read the docs\n        </a>\n      </div>\n      <p class=\"text-muted mb-0\">\n        Currently <strong>v{{ .Site.Params.current_version }}</strong>\n        <span class=\"px-1\">&middot;</span>\n        <a href=\"/docs/{{ .Site.Params.docs_version }}/getting-started/download/\" class=\"link-secondary\">Download</a>\n        <span class=\"px-1\">&middot;</span>\n        <a href=\"https://getbootstrap.com/docs/4.6/getting-started/introduction/\" class=\"link-secondary text-nowrap\">v4.6.x docs</a>\n        <span class=\"px-1\">&middot;</span>\n        <a href=\"/docs/versions/\" class=\"link-secondary text-nowrap\">All releases</a>\n      </p>\n      {{ partial \"ads\" . }}\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/icons.html",
    "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n  <symbol id=\"arrow-right-short\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z\"/>\n  </symbol>\n  <symbol id=\"book-half\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z\"/>\n  </symbol>\n  <symbol id=\"box-seam\" viewBox=\"0 0 16 16\">\n    <path d=\"M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2l-2.218-.887zm3.564 1.426L5.596 5 8 5.961 14.154 3.5l-2.404-.961zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923l6.5 2.6zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z\"/>\n  </symbol>\n  <symbol id=\"braces\" viewBox=\"0 0 16 16\">\n    <path d=\"M2.114 8.063V7.9c1.005-.102 1.497-.615 1.497-1.6V4.503c0-1.094.39-1.538 1.354-1.538h.273V2h-.376C3.25 2 2.49 2.759 2.49 4.352v1.524c0 1.094-.376 1.456-1.49 1.456v1.299c1.114 0 1.49.362 1.49 1.456v1.524c0 1.593.759 2.352 2.372 2.352h.376v-.964h-.273c-.964 0-1.354-.444-1.354-1.538V9.663c0-.984-.492-1.497-1.497-1.6zM13.886 7.9v.163c-1.005.103-1.497.616-1.497 1.6v1.798c0 1.094-.39 1.538-1.354 1.538h-.273v.964h.376c1.613 0 2.372-.759 2.372-2.352v-1.524c0-1.094.376-1.456 1.49-1.456V7.332c-1.114 0-1.49-.362-1.49-1.456V4.352C13.51 2.759 12.75 2 11.138 2h-.376v.964h.273c.964 0 1.354.444 1.354 1.538V6.3c0 .984.492 1.497 1.497 1.6z\"/>\n  </symbol>\n  <symbol id=\"braces-asterisk\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M1.114 8.063V7.9c1.005-.102 1.497-.615 1.497-1.6V4.503c0-1.094.39-1.538 1.354-1.538h.273V2h-.376C2.25 2 1.49 2.759 1.49 4.352v1.524c0 1.094-.376 1.456-1.49 1.456v1.299c1.114 0 1.49.362 1.49 1.456v1.524c0 1.593.759 2.352 2.372 2.352h.376v-.964h-.273c-.964 0-1.354-.444-1.354-1.538V9.663c0-.984-.492-1.497-1.497-1.6ZM14.886 7.9v.164c-1.005.103-1.497.616-1.497 1.6v1.798c0 1.094-.39 1.538-1.354 1.538h-.273v.964h.376c1.613 0 2.372-.759 2.372-2.352v-1.524c0-1.094.376-1.456 1.49-1.456v-1.3c-1.114 0-1.49-.362-1.49-1.456V4.352C14.51 2.759 13.75 2 12.138 2h-.376v.964h.273c.964 0 1.354.444 1.354 1.538V6.3c0 .984.492 1.497 1.497 1.6ZM7.5 11.5V9.207l-1.621 1.621-.707-.707L6.792 8.5H4.5v-1h2.293L5.172 5.879l.707-.707L7.5 6.792V4.5h1v2.293l1.621-1.621.707.707L9.208 7.5H11.5v1H9.207l1.621 1.621-.707.707L8.5 9.208V11.5h-1Z\"/>\n  </symbol>\n  <symbol id=\"check2\" viewBox=\"0 0 16 16\">\n    <title>Check</title>\n    <path d=\"M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z\"/>\n  </symbol>\n  <symbol id=\"chevron-expand\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708z\"/>\n  </symbol>\n  <symbol id=\"clipboard\" viewBox=\"0 0 16 16\">\n    <path d=\"M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z\"/>\n    <path d=\"M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z\"/>\n  </symbol>\n  <symbol id=\"code\" viewBox=\"0 0 16 16\">\n    <path d=\"M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8l3.147-3.146zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8l-3.147-3.146z\"/>\n  </symbol>\n  <symbol id=\"file-earmark-richtext\" viewBox=\"0 0 16 16\">\n    <path d=\"M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z\"/>\n    <path d=\"M4.5 12.5A.5.5 0 0 1 5 12h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm0-2A.5.5 0 0 1 5 10h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm1.639-3.708 1.33.886 1.854-1.855a.25.25 0 0 1 .289-.047l1.888.974V8.5a.5.5 0 0 1-.5.5H5a.5.5 0 0 1-.5-.5V8s1.54-1.274 1.639-1.208zM6.25 6a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5z\"/>\n  </symbol>\n  <symbol id=\"globe2\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z\"/>\n  </symbol>\n  <symbol id=\"grid-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zm8 0A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm-8 8A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm8 0A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3z\"/>\n  </symbol>\n  <symbol id=\"lightning-charge-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M11.251.068a.5.5 0 0 1 .227.58L9.677 6.5H13a.5.5 0 0 1 .364.843l-8 8.5a.5.5 0 0 1-.842-.49L6.323 9.5H3a.5.5 0 0 1-.364-.843l8-8.5a.5.5 0 0 1 .615-.09z\"/>\n  </symbol>\n  <symbol id=\"list\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z\"/>\n  </symbol>\n  <symbol id=\"magic\" viewBox=\"0 0 16 16\">\n    <path d=\"M9.5 2.672a.5.5 0 1 0 1 0V.843a.5.5 0 0 0-1 0v1.829Zm4.5.035A.5.5 0 0 0 13.293 2L12 3.293a.5.5 0 1 0 .707.707L14 2.707ZM7.293 4A.5.5 0 1 0 8 3.293L6.707 2A.5.5 0 0 0 6 2.707L7.293 4Zm-.621 2.5a.5.5 0 1 0 0-1H4.843a.5.5 0 1 0 0 1h1.829Zm8.485 0a.5.5 0 1 0 0-1h-1.829a.5.5 0 0 0 0 1h1.829ZM13.293 10A.5.5 0 1 0 14 9.293L12.707 8a.5.5 0 1 0-.707.707L13.293 10ZM9.5 11.157a.5.5 0 0 0 1 0V9.328a.5.5 0 0 0-1 0v1.829Zm1.854-5.097a.5.5 0 0 0 0-.706l-.708-.708a.5.5 0 0 0-.707 0L8.646 5.94a.5.5 0 0 0 0 .707l.708.708a.5.5 0 0 0 .707 0l1.293-1.293Zm-3 3a.5.5 0 0 0 0-.706l-.708-.708a.5.5 0 0 0-.707 0L.646 13.94a.5.5 0 0 0 0 .707l.708.708a.5.5 0 0 0 .707 0L8.354 9.06Z\"/>\n  </symbol>\n  <symbol id=\"menu-button-wide-fill\" viewBox=\"0 0 16 16\">\n    <path d=\"M1.5 0A1.5 1.5 0 0 0 0 1.5v2A1.5 1.5 0 0 0 1.5 5h13A1.5 1.5 0 0 0 16 3.5v-2A1.5 1.5 0 0 0 14.5 0h-13zm1 2h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm9.927.427A.25.25 0 0 1 12.604 2h.792a.25.25 0 0 1 .177.427l-.396.396a.25.25 0 0 1-.354 0l-.396-.396zM0 8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8zm1 3v2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2H1zm14-1V8a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2h14zM2 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0 4a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z\"/>\n  </symbol>\n  <symbol id=\"palette2\" viewBox=\"0 0 16 16\">\n    <path d=\"M0 .5A.5.5 0 0 1 .5 0h5a.5.5 0 0 1 .5.5v5.277l4.147-4.131a.5.5 0 0 1 .707 0l3.535 3.536a.5.5 0 0 1 0 .708L10.261 10H15.5a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5H3a2.99 2.99 0 0 1-2.121-.879A2.99 2.99 0 0 1 0 13.044m6-.21 7.328-7.3-2.829-2.828L6 7.188v5.647zM4.5 13a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0zM15 15v-4H9.258l-4.015 4H15zM0 .5v12.495V.5z\"/>\n    <path d=\"M0 12.995V13a3.07 3.07 0 0 0 0-.005z\"/>\n  </symbol>\n  <symbol id=\"plugin\" viewBox=\"0 0 16 16\">\n    <path fill-rule=\"evenodd\" d=\"M1 8a7 7 0 1 1 2.898 5.673c-.167-.121-.216-.406-.002-.62l1.8-1.8a3.5 3.5 0 0 0 4.572-.328l1.414-1.415a.5.5 0 0 0 0-.707l-.707-.707 1.559-1.563a.5.5 0 1 0-.708-.706l-1.559 1.562-1.414-1.414 1.56-1.562a.5.5 0 1 0-.707-.706l-1.56 1.56-.707-.706a.5.5 0 0 0-.707 0L5.318 5.975a3.5 3.5 0 0 0-.328 4.571l-1.8 1.8c-.58.58-.62 1.6.121 2.137A8 8 0 1 0 0 8a.5.5 0 0 0 1 0Z\"/>\n  </symbol>\n  <symbol id=\"plus\" viewBox=\"0 0 16 16\">\n    <path d=\"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z\"/>\n  </symbol>\n  <symbol id=\"three-dots\" viewBox=\"0 0 16 16\">\n    <path d=\"M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z\"/>\n  </symbol>\n  <symbol id=\"tools\" viewBox=\"0 0 16 16\">\n    <path d=\"M1 0 0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.356 3.356a1 1 0 0 0 1.414 0l1.586-1.586a1 1 0 0 0 0-1.414l-3.356-3.356a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3c0-.269-.035-.53-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814L1 0zm9.646 10.646a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708zM3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026L3 11z\"/>\n  </symbol>\n  <symbol id=\"ui-radios\" viewBox=\"0 0 16 16\">\n    <path d=\"M7 2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-1zM0 12a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm7-1.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-1zm0-5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 8a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zM3 1a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm0 4.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z\"/>\n  </symbol>\n</svg>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/js-data-attributes.md",
    "content": "As options can be passed via data attributes or JavaScript, you can append an option name to `data-bs-`, as in `data-bs-animation=\"{value}\"`. Make sure to change the case type of the option name from \"_camelCase_\" to \"_kebab-case_\" when passing the options via data attributes. For example, use `data-bs-custom-class=\"beautifier\"` instead of `data-bs-customClass=\"beautifier\"`.\n\nAs of Bootstrap 5.2.0, all components support an **experimental** reserved data attribute `data-bs-config` that can house simple component configuration as a JSON string. When an element has `data-bs-config='{\"delay\":0, \"title\":123}'` and `data-bs-title=\"456\"` attributes, the final `title` value will be `456` and the separate data attributes will override values given on `data-bs-config`. In addition, existing data attributes are able to house JSON values like `data-bs-delay='{\"show\":0,\"hide\":150}'`.\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/redirect.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>{{ . }}</title>\n    <link rel=\"canonical\" href=\"{{ . }}\">\n    <meta name=\"robots\" content=\"noindex\">\n    <meta http-equiv=\"refresh\" content=\"0; url={{ . }}\">\n  </head>\n</html>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/scripts.html",
    "content": "{{ if eq hugo.Environment \"production\" -}}\n  <script src=\"/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.min.js\" {{ printf \"integrity=%q\" .Site.Params.cdn.js_bundle_hash | safeHTMLAttr }} crossorigin=\"anonymous\"></script>\n{{ else -}}\n  <script src=\"/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.js\"></script>\n{{- end }}\n\n{{ if eq .Page.Layout \"docs\" -}}\n<script src=\"https://cdn.jsdelivr.net/npm/@docsearch/js@3\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/@stackblitz/sdk@1/bundles/sdk.umd.js\"></script>\n{{- end }}\n\n{{- $vendor := resources.Match \"js/vendor/*.js\" -}}\n{{- $js := resources.Match \"js/*.js\" -}}\n{{- $targetDocsJSPath := path.Join \"/docs\" .Site.Params.docs_version \"assets/js/docs.js\" -}}\n{{- $docsJs := append $js $vendor | resources.Concat $targetDocsJSPath -}}\n\n{{- if eq hugo.Environment \"production\" -}}\n  {{- $docsJs = $docsJs | resources.Minify -}}\n{{- end }}\n\n<script src=\"{{ $docsJs.Permalink | relURL }}\"></script>\n\n{{ if eq .Page.Layout \"docs\" -}}\n<script>\n  // Open in StackBlitz logic\n  document.querySelectorAll('.btn-edit').forEach(btn => {\n    btn.addEventListener('click', event => {\n      const htmlSnippet = event.target.closest('.bd-code-snippet').querySelector('.bd-example').innerHTML\n\n      // Get extra classes for this example\n      const classes = Array.from(event.target.closest('.bd-code-snippet').querySelector('.bd-example').classList).join(' ')\n\n      const jsSnippet = event.target.closest('.bd-code-snippet').querySelector('.btn-edit').getAttribute('data-sb-js-snippet')\n      StackBlitzSDK.openBootstrapSnippet(htmlSnippet, jsSnippet, classes)\n    })\n  })\n\n  StackBlitzSDK.openBootstrapSnippet = (htmlSnippet, jsSnippet, classes) => {\n    const markup = `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"{{ .Site.Params.cdn.css }}\" rel=\"stylesheet\">\n    <link href=\"https://getbootstrap.com/docs/{{ .Site.Params.docs_version }}/assets/css/docs.css\" rel=\"stylesheet\">\n    <title>Bootstrap Example</title>\n    <${'script'} src=\"{{ .Site.Params.cdn.js_bundle }}\"></${'script'}>\n  </head>\n  <body class=\"p-3 m-0 border-0 ${classes}\">\n\n    <!-- Example Code -->\n${htmlSnippet.replace(/^/gm, '    ')}\n    <!-- End Example Code -->\n  </body>\n</html>`\n\n    const jsSnippetContent = jsSnippet ? '{{ os.ReadFile \"site/assets/js/snippets.js\" }}' : null\n    const project = {\n      files: {\n        'index.html': markup,\n        'index.js': jsSnippetContent\n      },\n      title: 'Bootstrap Example',\n      description: `Official example from ${window.location.href}`,\n      template: jsSnippet ? 'javascript' : 'html',\n      tags: ['bootstrap']\n    }\n\n    StackBlitzSDK.openProject(project, { openFile: 'index.html' })\n  }\n</script>\n{{- end }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/skippy.html",
    "content": "<div class=\"skippy visually-hidden-focusable overflow-hidden\">\n  <div class=\"container-xl\">\n    <a class=\"d-inline-flex p-2 m-1\" href=\"#content\">Skip to main content</a>\n    {{ if (eq .Page.Layout \"docs\") -}}\n    <a class=\"d-none d-md-inline-flex p-2 m-1\" href=\"#bd-docs-nav\">Skip to docs navigation</a>\n    {{- end }}\n  </div>\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/social.html",
    "content": "<meta name=\"twitter:card\" content=\"summary_large_image\">\n<meta name=\"twitter:site\" content=\"@{{ .Site.Params.twitter }}\">\n<meta name=\"twitter:creator\" content=\"@{{ .Site.Params.twitter }}\">\n<meta name=\"twitter:title\" content=\"{{ .Title | markdownify }}\">\n<meta name=\"twitter:description\" content=\"{{ .Page.Params.description | default .Site.Params.description | markdownify }}\">\n<meta name=\"twitter:image\" content=\"/docs/{{ .Site.Params.docs_version }}/assets/{{ if .Page.Params.thumbnail }}img/{{ .Page.Params.thumbnail }}{{else}}brand/bootstrap-social.png{{end}}\">\n\n<meta property=\"og:url\" content=\"{{ .Permalink }}\">\n<meta property=\"og:title\" content=\"{{ .Title | markdownify }}\">\n<meta property=\"og:description\" content=\"{{ .Page.Params.description | default .Site.Params.description | markdownify }}\">\n<meta property=\"og:type\" content=\"{{ if .IsPage }}article{{ else }}website{{ end }}\">\n<meta property=\"og:image:type\" content=\"image/png\">\n<meta property=\"og:image:width\" content=\"1000\">\n<meta property=\"og:image:height\" content=\"500\">\n<meta property=\"og:image\" content=\"/docs/{{ .Site.Params.docs_version }}/assets/{{ if .Page.Params.thumbnail }}img/{{ .Page.Params.thumbnail }}{{else}}brand/bootstrap-social.png{{end}}\">\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/stylesheet.html",
    "content": "{{ if eq .Page.Layout \"docs\" -}}\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@docsearch/css@3\">\n{{- end }}\n\n{{ if eq hugo.Environment \"production\" -}}\n{{ if eq .Page.Params.direction \"rtl\" -}}\n<link href=\"/docs/{{ .Site.Params.docs_version }}/dist/css/bootstrap.rtl.min.css\" rel=\"stylesheet\" {{ printf \"integrity=%q\" .Site.Params.cdn.css_rtl_hash | safeHTMLAttr }} crossorigin=\"anonymous\">\n{{- else -}}\n<link href=\"/docs/{{ .Site.Params.docs_version }}/dist/css/bootstrap.min.css\" rel=\"stylesheet\" {{ printf \"integrity=%q\" .Site.Params.cdn.css_hash | safeHTMLAttr }} crossorigin=\"anonymous\">\n{{- end -}}\n{{- else -}}\n<link href=\"/docs/{{ .Site.Params.docs_version }}/dist/css/bootstrap{{ if eq .Page.Params.direction \"rtl\" }}.rtl{{ end }}.css\" rel=\"stylesheet\">\n{{- end }}\n\n{{- if (ne .Page.Layout \"examples\") }}\n{{- $targetDocsCssPath := path.Join \"/docs\" .Site.Params.docs_version \"assets/css/docs.css\" -}}\n{{- $sassOptions := dict \"targetPath\" $targetDocsCssPath \"outputStyle\" \"expanded\" \"precision\" 6 -}}\n{{- $postcssOptions := dict \"use\" \"autoprefixer\" \"noMap\" true -}}\n\n{{ if eq hugo.Environment \"production\" -}}\n  {{- $sassOptions = merge $sassOptions (dict \"outputStyle\" \"compressed\") -}}\n{{- end -}}\n\n{{- $style := resources.Get \"scss/docs.scss\" | toCSS $sassOptions | postCSS $postcssOptions }}\n\n<link href=\"{{ $style.Permalink | relURL }}\" rel=\"stylesheet\">\n{{- end }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/partials/table-content.html",
    "content": "  <thead>\n    <tr>\n      <th scope=\"col\">#</th>\n      <th scope=\"col\">First</th>\n      <th scope=\"col\">Last</th>\n      <th scope=\"col\">Handle</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">1</th>\n      <td>Mark</td>\n      <td>Otto</td>\n      <td>@mdo</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">2</th>\n      <td>Jacob</td>\n      <td>Thornton</td>\n      <td>@fat</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">3</th>\n      <td colspan=\"2\">Larry the Bird</td>\n      <td>@twitter</td>\n    </tr>\n  </tbody>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/robots.txt",
    "content": "# www.robotstxt.org\n\n{{- $isProduction := eq hugo.Environment \"production\" -}}\n{{- $isNetlify := eq (getenv \"NETLIFY\") \"true\" -}}\n{{- $allowCrawling := and (not $isNetlify) $isProduction -}}\n\n{{ if $allowCrawling }}\n# Allow crawling of all content\n{{- end }}\nUser-agent: *\nDisallow:{{ if not $allowCrawling }} /{{ end }}\nSitemap: {{ \"/sitemap.xml\" | absURL }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/added-in.html",
    "content": "{{- /* Outputs badge to identify the first version something was added */ -}}\n\n{{- $version := .Get 0 -}}\n\n<small class=\"d-inline-flex mb-3 px-2 py-1 fw-semibold text-success bg-success bg-opacity-10 border border-success border-opacity-10 rounded-2\">Added in v{{ $version }}</small>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/bs-table.html",
    "content": "{{- /*\n  Usage: `bs-table \"class class-foo\"`, where class can be any string\n*/ -}}\n\n{{- $css_class := .Get 0 | default \"table\" -}}\n{{- $html_table := .Inner | markdownify -}}\n{{- $html_table = replace $html_table \"<table>\" (printf `<div class=\"table-responsive\"><table class=\"%s\">` $css_class) -}}\n{{- $html_table = replace $html_table \"</table>\" \"</table></div>\" -}}\n{{- $html_table | safeHTML -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/callout.html",
    "content": "{{- /*\n  Usage: `callout \"type\"`, where `type` is one of info (default), danger, or warning\n*/ -}}\n\n{{- $css_class := .Get 0 | default \"info\" -}}\n\n<div class=\"bd-callout bd-callout-{{ $css_class }}\">\n{{ .Inner | markdownify }}\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/docsref.html",
    "content": "{{- relref . ((path.Join \"docs\" $.Site.Params.docs_version (.Get 0)) | relURL) -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/example.html",
    "content": "{{- /*\n  Usage: `example args`\n\n  `args` are all optional and can be one of the following:\n    * id: the `div`'s id - default: \"\"\n    * class: any extra class(es) to be added to the `div` - default: \"\"\n    * lang: language used to display the code - default: \"html\"\n    * show_markup: if the markup should be output in the HTML - default: `true`\n    * show_preview: if the preview should be output in the HTML - default: `true`\n    * stackblitz_add_js: if extra JS snippet should be added to StackBlitz - default: `false`\n*/ -}}\n\n{{- $id := .Get \"id\" -}}\n{{- $class := .Get \"class\" -}}\n{{- $lang := .Get \"lang\" | default \"html\" -}}\n{{- $stackblitz_add_js := .Get \"stackblitz_add_js\" | default false -}}\n{{- $show_markup := .Get \"show_markup\" | default true -}}\n{{- $show_preview := .Get \"show_preview\" | default true -}}\n{{- $input := .Inner -}}\n\n<div class=\"bd-example-snippet bd-code-snippet\">\n  {{- if eq $show_preview true -}}\n  <div{{ with $id }} id=\"{{ . }}\"{{ end }} class=\"bd-example{{ with $class }} {{ . }}{{ end }}\">\n    {{- $input -}}\n  </div>\n  {{- end -}}\n\n  {{- if eq $show_markup true -}}\n    {{- if eq $show_preview true -}}\n      <div class=\"d-flex align-items-center highlight-toolbar bg-light ps-3 pe-2 py-1\">\n        <small class=\"font-monospace text-muted text-uppercase\">{{- $lang -}}</small>\n        <div class=\"d-flex ms-auto\">\n          <button type=\"button\" class=\"btn-edit text-nowrap\"{{ with $stackblitz_add_js }} data-sb-js-snippet=\"{{ $stackblitz_add_js }}\"{{ end }} title=\"Try it on StackBlitz\">\n            <svg class=\"bi\" role=\"img\" aria-label=\"Try it\"><use xlink:href=\"#lightning-charge-fill\"/></svg>\n          </button>\n          <button type=\"button\" class=\"btn-clipboard mt-0 me-0\" title=\"Copy to clipboard\">\n            <svg class=\"bi\" role=\"img\" aria-label=\"Copy\"><use xlink:href=\"#clipboard\"/></svg>\n          </button>\n        </div>\n      </div>\n    {{- end -}}\n\n    {{- $content := replaceRE `<svg class=\"bd-placeholder-img(?:-lg)?(?: *?bd-placeholder-img-lg)? ?(.*?)\".*?<\\/svg>\\n` `<img src=\"...\" class=\"$1\" alt=\"...\">` $input -}}\n    {{- $content = replaceRE ` (class=\" *?\")` \"\" $content -}}\n    {{- highlight (trim $content \"\\n\") $lang \"\" -}}\n  {{- end -}}\n</div>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/js-dismiss.html",
    "content": "{{- /* Usage: js-dismiss \"ComponentName\" */ -}}\n\n{{- $name := .Get 0 -}}\n\nDismissal can be achieved with the `data` attribute on a button **within the {{ $name }}** as demonstrated below:\n\n```html\n<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"{{ $name }}\" aria-label=\"Close\"></button>\n```\n\nor on a button **outside the {{ $name }}** using the `data-bs-target` as demonstrated below:\n\n```html\n<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"{{ $name }}\" data-bs-target=\"#my-{{ $name }}\" aria-label=\"Close\"></button>\n```\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/markdown.html",
    "content": "{{- .Inner | markdownify -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/param.html",
    "content": "{{- /*\n  Work around wrong escapes in integrity attributes.\n  Original: https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/shortcodes/param.html\n*/ -}}\n\n{{- $name := .Get 0 -}}\n{{- with $name -}}\n{{- $value := $.Page.Param . -}}\n{{- /* If any parameter ends with `_hash`, mark the string as safe HTML */ -}}\n{{- if (strings.HasSuffix $name \"_hash\") -}}\n  {{- $value = $value | safeHTML -}}\n{{- end -}}\n{{- with $value }}{{ . }}{{ else }}{{ errorf \"Param %q not found: %s\" $name $.Position }}{{ end -}}\n{{- else }}{{ errorf \"Missing param key: %s\" $.Position }}{{ end -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/partial.html",
    "content": "{{ partial (.Get 0) . }}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/placeholder.html",
    "content": "{{- /*\n  Usage: `placeholder args`\n\n  `args` are all optional and can be one of the following:\n    * title: Used in the SVG `title` tag - default: \"Placeholder\"\n    * text: The text to show in the image - default: \"width x height\"\n    * class: Class to add to the `svg` - default: \"bd-placeholder-img\"\n    * color: The text color (foreground) - default: \"#dee2e6\"\n    * background: The background color - default: \"#868e96\"\n    * width: default: \"100%\"\n    * height: default: \"180px\"\n*/ -}}\n\n{{- $grays := $.Site.Data.grays -}}\n{{- $default_color := (index $grays 2).hex -}}\n{{- $default_background := (index $grays 5).hex -}}\n\n{{- $title := .Get \"title\" | default \"Placeholder\" -}}\n{{- $class := .Get \"class\" -}}\n{{- $color := .Get \"color\" | default $default_color -}}\n{{- $background := .Get \"background\" | default $default_background -}}\n{{- $width := .Get \"width\" | default \"100%\" -}}\n{{- $height := .Get \"height\" | default \"180\" -}}\n{{- $text := .Get \"text\" | default (printf \"%sx%s\" $width $height) -}}\n\n{{- $show_title := not (eq $title \"false\") -}}\n{{- $show_text := not (eq $text \"false\") -}}\n\n<svg class=\"bd-placeholder-img{{ with $class }} {{ . }}{{ end }}\" width=\"{{ $width }}\" height=\"{{ $height }}\" xmlns=\"http://www.w3.org/2000/svg\"{{ if (or $show_title $show_text) }} role=\"img\" aria-label=\"{{ if $show_title }}{{ $title }}{{ if $show_text }}: {{ end }}{{ end }}{{ if ($show_text) }}{{ $text }}{{ end }}\"{{ else }} aria-hidden=\"true\"{{ end }} preserveAspectRatio=\"xMidYMid slice\" focusable=\"false\">\n  {{- if $show_title }}<title>{{ $title }}</title>{{ end -}}\n  <rect width=\"100%\" height=\"100%\" fill=\"{{ $background }}\"/>\n  {{- if $show_text }}<text x=\"50%\" y=\"50%\" fill=\"{{ $color }}\" dy=\".3em\">{{ $text }}</text>{{ end -}}\n</svg>\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/scss-docs.html",
    "content": "{{- /*\n  Usage: `scss-docs name=\"name\" file=\"file/_location.scss\"`\n\n  Prints everything between `// scss-docs-start \"name\"` and `// scss-docs-end \"name\"`\n  comments in the docs.\n\n  Optional parameters:\n    * strip-default: Remove the ` !default` flag from variable assignments - default: `true`\n*/ -}}\n\n{{- $name := .Get \"name\" -}}\n{{- $file := .Get \"file\" -}}\n{{- $strip_default := .Get \"strip-default\" | default \"true\" -}}\n\n{{- /* If any parameters are missing, print an error and exit */ -}}\n{{- if or (not $name) (not $file) -}}\n  {{- errorf \"%s: %q: Missing required parameters! Got: name=%q file=%q!\" .Position .Name $name $file -}}\n{{- else -}}\n  {{- $capture_start := printf \"// scss-docs-start %s\\n\" $name -}}\n  {{- $capture_end := printf \"// scss-docs-end %s\" $name -}}\n  {{- $regex := printf `%s((?:.|\\n)*)%s` $capture_start $capture_end -}}\n\n  {{- /*\n    TODO: figure out why we can't do the following and get the first group (the only capturing one)...\n    $regex := printf `(?:// scss-docs-start %s\\n)((?:.|\\n)*)(?:\\n// scss-docs-end %s)` $name $name\n  */ -}}\n\n  {{- $match := findRE $regex (readFile $file) -}}\n  {{- $match = index $match 0 -}}\n\n  {{- if not $match -}}\n    {{- errorf \"%s: %q: Got no matches for name=%q in file=%q!\" .Position .Name $name $file -}}\n  {{- end -}}\n\n  {{- $match = replace $match $capture_start \"\" -}}\n  {{- $match = replace $match $capture_end \"\" -}}\n\n  {{- if (ne $strip_default \"false\") -}}\n    {{- $match = replace $match \" !default\" \"\" -}}\n  {{- end -}}\n\n  {{- highlight $match \"scss\" \"\" -}}\n{{- end -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/table.html",
    "content": "{{- /*\n  Usage: `table [args]`\n\n  `args` are optional and can be one of the following:\n    * class: any class(es) to be added to the `table` - default \"\"\n    * simplified: show a simplified version in the examples - default `true`\n*/ -}}\n\n{{- $class := .Get \"class\" -}}\n{{- $simplified := .Get \"simplified\" | default true -}}\n\n{{- $table_attributes := \"\" -}}\n{{- $table_content := \"  ...\\n\" -}}\n\n{{- with $class -}}\n  {{- $table_attributes = printf ` class=\"%s\"` . -}}\n{{- end -}}\n\n{{- if eq $simplified \"false\" -}}\n  {{- $table_content = partialCached \"table-content\" . -}}\n{{- end -}}\n\n{{- $table := printf \"<table%s>\\n%s</table>\" $table_attributes $table_content -}}\n\n<div class=\"bd-example\">\n  <table{{ with $class }} class=\"{{ . }}\"{{ end }}>\n    {{ partialCached \"table-content\" . }}\n  </table>\n</div>\n\n{{- highlight $table \"html\" \"\" -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/shortcodes/year.html",
    "content": "{{- /* Outputs the current year */ -}}\n\n{{- now.Format \"2006\" -}}\n"
  },
  {
    "path": "src/common/bootstrap/site/layouts/sitemap.xml",
    "content": "{{ printf \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\" standalone=\\\"yes\\\"?>\" | safeHTML }}\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n  {{- range .Data.Pages -}}{{ if and .Permalink (ne .Params.sitemap_exclude true) }}\n  <url>\n    <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}\n    <lastmod>{{ safeHTML (.Lastmod.Format \"2006-01-02T15:04:05-07:00\") }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}\n    <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}\n    <priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}\n    <xhtml:link rel=\"alternate\" hreflang=\"{{ .Language.Lang }}\" href=\"{{ .Permalink }}\"/>{{ end }}\n    <xhtml:link rel=\"alternate\" hreflang=\"{{ .Language.Lang }}\" href=\"{{ .Permalink }}\"/>{{ end }}\n  </url>{{ end }}{{ end }}\n</urlset>\n"
  },
  {
    "path": "src/common/bootstrap/site/static/CNAME",
    "content": "getbootstrap.com\n"
  },
  {
    "path": "src/common/bootstrap/site/static/docs/5.2/assets/img/favicons/manifest.json",
    "content": "{\n  \"name\": \"Bootstrap\",\n  \"short_name\": \"Bootstrap\",\n  \"icons\": [\n    {\n      \"src\": \"android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"android-chrome-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"start_url\": \"/?utm_source=a2hs\",\n  \"theme_color\": \"#7952b3\",\n  \"background_color\": \"#7952b3\",\n  \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "src/common/bootstrap/site/static/docs/5.2/assets/js/validate-forms.js",
    "content": "// Example starter JavaScript for disabling form submissions if there are invalid fields\n(() => {\n  'use strict'\n\n  // Fetch all the forms we want to apply custom Bootstrap validation styles to\n  const forms = document.querySelectorAll('.needs-validation')\n\n  // Loop over them and prevent submission\n  Array.from(forms).forEach(form => {\n    form.addEventListener('submit', event => {\n      if (!form.checkValidity()) {\n        event.preventDefault()\n        event.stopPropagation()\n      }\n\n      form.classList.add('was-validated')\n    }, false)\n  })\n})()\n"
  },
  {
    "path": "src/common/bootstrap/site/static/sw.js",
    "content": "// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT\n// IT'S ALL JUST JUNK FOR OUR DOCS!\n// ++++++++++++++++++++++++++++++++++++++++++\n\n(function () {\n  'use strict'\n\n  if ('serviceWorker' in navigator) {\n    window.addEventListener('load', function () {\n      navigator.serviceWorker.getRegistrations().then(function (registrations) {\n        for (var registration of registrations) {\n          registration.unregister()\n            .then(function () {\n              return self.clients.matchAll()\n            })\n            .then(function (clients) {\n              clients.forEach(function (client) {\n                if (client.url && 'navigate' in client) {\n                  client.navigate(client.url)\n                }\n              })\n            })\n        }\n      })\n    })\n  }\n})()\n"
  },
  {
    "path": "src/control-socket/control-socket.asd",
    "content": "(defsystem :control-socket\n  :serial t\n  :depends-on (:unix-sockets\n               :util.threading\n               :log4cl\n               (:feature :lispworks (:require \"remote-debugger-full\")))\n  :components ((:file \"server\" :if-feature (:and :lispworks (:not :mswindows)))))\n\n#+(and :lispworks (:not :mswindows))\n(defsystem :control-socket/tests\n  :serial t\n  :depends-on (:control-socket\n               :util/fiveam))\n"
  },
  {
    "path": "src/control-socket/server.lisp",
    "content": "(defpackage :control-socket/server\n  (:use #:cl)\n  (:import-from #:util/threading\n                #:make-thread))\n(in-package :control-socket/server)\n\n(defclass control-socket ()\n  ((socket :initarg :socket\n           :reader socket)\n   (thread :initarg :thread\n           :accessor thread)))\n\n(define-condition stop-control-socket (condition)\n  ())\n\n(defun make-control-socket (pathname)\n  (let ((socket (unix-sockets:make-unix-socket pathname)))\n    (let ((self (make-instance 'control-socket :socket socket)))\n      (setf (thread self)\n            (make-thread (lambda ()\n                           (unwind-protect\n                                (cs-listen self)\n                             (delete-file pathname)))))\n      self)))\n\n(defmethod control-socket-stop ((self control-socket))\n  (bt:interrupt-thread (thread self)\n                       (lambda ()\n                         (signal 'stop-control-socket))))\n\n\n(defmethod cs-listen ((self control-socket))\n  (handler-case\n      (loop\n        (let ((socket (unix-sockets:accept-unix-socket (socket self))))\n          (make-thread\n           (lambda ()\n             (dispatch-request self socket))\n           :name \"control-socket-worker-thread\")))\n    (stop-control-socket ()\n      (log:info \"Stopping supervisor\")\n      (unix-sockets:close-unix-socket (socket self)))))\n\n(defvar *num* 0)\n\n(defmethod dispatch-request ((self control-socket) socket)\n  (let ((stream (unix-sockets:unix-socket-stream socket)))\n    (dbg:create-client-remote-debugging-connection\n     (format nil \"Control socket connection ~a\" (incf *num*))\n     :stream stream\n     :log-stream t)))\n"
  },
  {
    "path": "src/core/active-users/active-users.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/active-users/active-users\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:index-get)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:screenshotbot/impersonation\n                #:make-impersonation\n                #:impersonatedp)\n  (:export\n   #:mark-active-user\n   #:active-users-for-company))\n(in-package :core/active-users/active-users)\n\n(defvar *lock* (bt:make-lock))\n\n(defindex +lookup-index+\n  'fset-unique-index\n  :slots '(%date %user %company))\n\n(with-class-validation\n  (defclass active-user (store-object)\n    ((%user :initarg :user\n            :reader user)\n     (%company :initarg :company\n               :reader company)\n     (%date :initarg :date\n            :reader active-user-date)\n     (%ip-address :initform (make-hash-table :test #'equal)\n                  :transient t\n                  :accessor active-user-ip-addresses\n                  :documentation \"All the IP addresses this has been accessed from. This is a transient\nslot and is not stored to disk, so we don't need to hash the IP\naddresses.\"))\n    (:class-indices (3d-coords :index +lookup-index+))\n    (:metaclass persistent-class)))\n\n\n(defun active-users-for-company (company &key (days-ago 60)\n                                           (company-test #'eql))\n  \"COMPANY-TEST is called on the company of the active-user and the\ncompany provided, in that order. In particular this is used to support\nsub-companies, which this code can't be aware of.\"\n  (let ((start-date (format-date\n                     (local-time:timestamp-to-universal\n                      (local-time:timestamp-\n                       (local-time:now)\n                       days-ago :day)))))\n    (loop for active-user in (bknr.datastore:class-instances 'active-user)\n          if (and (funcall company-test (company active-user) company)\n                  (string>= (active-user-date active-user) start-date))\n              collect active-user)))\n\n(defun format-date (ts)\n  (multiple-value-bind (second minute hour date month year) (decode-universal-time ts)\n    (declare (ignore second minute hour))\n    (format nil \"~4,'0d-~2,'0d-~2,'0d\" year month date)))\n\n(defun mark-active-user-impl (&key user company (date (get-universal-time)) ip)\n  (when (and user company)\n    (let ((date (format-date date)))\n      (let ((event\n              (or (index-get +lookup-index+ (list date user company))\n                  (make-instance 'active-user\n                                 :user user\n                                 :company company\n                                 :date date))))\n        (when ip\n          (setf (gethash ip (active-user-ip-addresses event)) t))\n        event))))\n\n(defvar *events* nil)\n\n(defun mark-active-user (&rest args)\n  \"We don't want to wait on locks in the happy path, and we don't want\nreads to depend on writes.\"\n  (unless (%impersonatedp)\n    (atomics:atomic-push\n     (lambda ()\n       (apply #'mark-active-user-impl args))\n     *events*)))\n\n(defun %impersonatedp ()\n  (and\n   (boundp 'hunchentoot:*request*)\n   (impersonatedp (make-impersonation))))\n\n(defun flush-events ()\n  (bt:with-lock-held (*lock*)\n   (let ((events (util/atomics:atomic-exchange *events* nil)))\n     (loop for event in events\n           do (funcall event)))))\n\n(def-cron flush-events ()\n  (flush-events))\n"
  },
  {
    "path": "src/core/active-users/core.active-users.asd",
    "content": "(defsystem :core.active-users\n  :serial t\n  :depends-on (:util.store\n               :util/atomics\n               :auth.login\n               :hunchentoot\n               :util/cron\n               :local-time)\n  :components ((:file \"active-users\")))\n\n(defsystem :core.active-users/tests\n  :serial t\n  :depends-on (:core.active-users\n               :fiveam-matchers\n               :util.testing\n               :util/fiveam)\n  :components ((:file \"test-active-users\")))\n"
  },
  {
    "path": "src/core/active-users/test-active-users.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/active-users/test-active-users\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:core/active-users/active-users\n                #:active-user-ip-addresses\n                #:*lock*\n                #:flush-events\n                #:*events*\n                #:mark-active-user-impl\n                #:active-user-date\n                #:mark-active-user\n                #:active-user)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:alexandria\n                #:hash-table-keys)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:contains))\n(in-package :core/active-users/test-active-users)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (unwind-protect\n         (&body)\n      (setf *events* nil))))\n\n(test happy-path\n  (with-fixture state ()\n    (let ((date (get-universal-time)))\n      (mark-active-user-impl :user :foo :company :bleh :date date)\n      (assert-that (bknr.datastore:class-instances 'active-user)\n                   (has-length 1)))))\n\n(test parses-date-correctly\n  (with-fixture state ()\n    (let ((date 3917969422 #| picked on the day I implemented this |#))\n      (mark-active-user-impl :user :foo :company :bleh :date date)\n      (assert-that (active-user-date (car (bknr.datastore:class-instances 'active-user)))\n                   (is-equal-to \"2024-02-26\")))))\n\n(test marks-only-once\n  (with-fixture state ()\n    (let ((date (get-universal-time)))\n      (mark-active-user-impl :user :foo :company :bleh :date date)\n      (mark-active-user-impl :user :foo :company :bleh :date date)\n      (assert-that (class-instances 'active-user)\n                   (has-length 1)))))\n\n(test different-company-gets-different-note\n  (with-fixture state ()\n    (let ((date (get-universal-time)))\n      (mark-active-user-impl :user :foo :company :bleh :date date)\n      (mark-active-user-impl :user :foo :company :another :date date)\n      (assert-that (class-instances 'active-user)\n                   (has-length 2)))))\n\n(test nil-company-or-user-doesnt-get-logged\n  (with-fixture state ()\n    (let ((date (get-universal-time)))\n      (mark-active-user-impl :user nil :company :bleh :date date)\n      (mark-active-user-impl :user :foo :company nil :date date)\n      (assert-that (class-instances 'active-user)\n                   (has-length 0)))))\n\n(test happy-path-to-check-events\n  (with-fixture state ()\n    ;; The lock avoids a flush from happening in between\n    (bt:with-lock-held (*lock*)\n      (with-fake-request ()\n        (mark-active-user :user :foo :company :bleh :date (get-universal-time)))\n      (assert-that (class-instances 'active-user)\n                   (has-length 0)))\n    (flush-events)\n    (assert-that (class-instances 'active-user)\n                 (has-length 1))))\n\n(test mark-with-ip-address-logs-ip\n  (with-fixture state ()\n    (mark-active-user :user :foo :company :bleh :date (get-universal-time) :ip \"1.2.3.4\")\n    (finishes (flush-events))\n    (let ((event (first (class-instances 'active-user))))\n      (assert-that (hash-table-keys (active-user-ip-addresses event))\n                   (contains \"1.2.3.4\")))\n    (mark-active-user :user :foo :company :bleh :date (get-universal-time) :ip \"1.2.3.4\")\n    (mark-active-user :user :foo :company :bleh :date (get-universal-time) :ip \"1::2\")\n    (flush-events)\n    (let ((event (first (class-instances 'active-user))))\n      (assert-that (hash-table-keys (active-user-ip-addresses event))\n                   (contains-in-any-order \"1.2.3.4\" \"1::2\")))))\n"
  },
  {
    "path": "src/core/api/acceptor.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/api/acceptor\n  (:use #:cl)\n  (:export\n   #:api-acceptor-mixin\n   #:api-token-mode-p))\n(in-package :core/api/acceptor)\n\n(defclass api-acceptor-mixin ()\n  ((api-token-mode-p :initarg :api-token-mode-p\n                     :initform nil\n                     :reader api-token-mode-p)))\n"
  },
  {
    "path": "src/core/api/api-key-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n;; This package is no longer needed, technically. It could be safe to\n;; delete this, and replace it all with model/api-key. But, it WILL\n;; require a server restart, so beware.\n(uiop/package:define-package :screenshotbot/api-key-api\n  (:use #:cl #:alexandria)\n  (:nicknames :core/api/api-key-api)\n  (:export\n   #:api-key-key\n   #:api-key\n   #:api-key-secret-key\n   #:delete-api-key))\n(in-package :screenshotbot/api-key-api)\n\n(defgeneric api-key-key (api-key))\n(defgeneric api-key-secret-key (api-key))\n(defgeneric delete-api-key (api-key))\n"
  },
  {
    "path": "src/core/api/core.api.asd",
    "content": "(defsystem :core.api\n  :serial t\n  :depends-on (:markup\n               :auth.login\n               :gatekeeper\n               :parenscript\n               :core.ui\n               :util/timeago\n               :util.store)\n  :components ((:file \"acceptor\")\n               (:file \"api-key-api\")\n               (:module \"model\"\n                :components ((:file \"api-key\")))\n               (:module \"dashboard\"\n                :components ((:file \"api-keys\")))))\n\n;; NOTE: dashboard/test-api-keys is still in screenshotbot\n;; TODO(T1005, T1006)\n(defsystem :core.api/tests\n  :serial t\n  :depends-on (:core.api\n               :util/fiveam\n               :util.testing\n               :fiveam-matchers)\n  :components ((:module \"model\"\n                :components ((:file \"test-api-key\")))\n               (:module \"dashboard\"\n                :components ((:file \"test-api-keys\")))))\n"
  },
  {
    "path": "src/core/api/dashboard/api-keys.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/api-keys\n  (:use :cl)\n  (:nicknames :core/api/dashboard/api-keys)\n  (:import-from #:auth\n                #:current-company\n                #:current-user\n                #:user-full-name)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page\n                #:simple-card-page)\n  (:import-from #:core/ui/taskie\n                #:taskie-list\n                #:taskie-page-title\n                #:taskie-row)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key\n                #:api-key-key\n                #:delete-api-key)\n  (:import-from #:screenshotbot/login/common\n                #:with-login)\n  (:import-from #:screenshotbot/model/api-key\n                #:last-used\n                #:api-key-description\n                #:cli-api-key\n                #:expires-at\n                #:render-api-token\n                #:company-api-keys\n                #:user-api-keys)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler)\n  (:import-from #:util/timeago\n                #:timeago)\n  (:import-from #:core/ui/template\n                #:app-template)\n  (:import-from #:core/api/acceptor\n                #:api-token-mode-p\n                #:api-acceptor-mixin)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:import-from #:core/api/model/api-key\n                #:encode-api-token\n                #:api-key-permissions\n                #:api-key-user\n                #:api-key-company))\n(in-package :screenshotbot/dashboard/api-keys)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *throttler* (make-instance 'throttler\n                                   :tokens 100))\n\n(defclass permission ()\n  ((name :initarg :name\n         :reader permission-name)\n   (default :initarg :default\n            :initform nil\n            :reader permission-default-value)\n   (label :initarg :label\n          :reader permission-label)))\n\n(defmethod permission-input-name ((self permission))\n  (format nil \"permission--~a\" (permission-name self)))\n\n(defmethod api-key-available-permissions (installation)\n  nil)\n\n(defun %can-user-modify (user api-key)\n  (or\n   (roles:has-role-p (api-key-company api-key) user 'roles:admin)\n   (eq user (api-key-user api-key))))\n\n(defun %read-permissions ()\n  (if (gk:check :api-key-roles (auth:current-company))\n      (loop for permission in (api-key-available-permissions *installation*)\n            if (hunchentoot:parameter (permission-input-name permission))\n              collect (permission-name permission))\n      (%default-permissions)))\n\n(def-easy-macro with-description (&binding final-description &key &binding final-permissions description\n                                           permissions (action \"Create Key\")  &fn fn)\n  <simple-card-page form-action= (nibble (description) (fn description (%read-permissions))) >\n    <div class= \"card-header\">\n      <h3>API-Key Options</h3>\n    </div>\n\n    <div class= \"mb-3\">\n         <label class= \"form-label\" for= \"#description\">\n           This human-readable description let's you distinguish keys on the API keys dashboard.\n         </label>\n         <textarea name= \"description\" class= \"form-control\" placeholder= \"Enter text here\" id= \"description\" >,(progn description)</textarea>\n    </div>\n\n      ,(when (gk:check :api-key-roles (auth:current-company))\n         <div>\n           ,@ (loop for permission in (api-key-available-permissions *installation*)\n                    collect\n                    <div class= \"form-check mb-2\" >\n                      <input type= \"checkbox\" class= \"form-check-input\" value= \"\" checked= (if (member (permission-name permission) permissions) \"checked\") id= \"ci-access\" name= (permission-input-name permission) />\n                      ,(permission-label permission)\n                    </div>)\n         </div>)\n\n    <div class= \"card-footer\">\n      <input type= \"submit\" class= \"btn btn-primary\" value= action />\n      <a href= \"/api-keys\" class= \"btn btn-secondary\" >Cancel</a>\n    </div>\n  </simple-card-page>)\n\n(defun %default-permissions ()\n  (loop for permission in (api-key-available-permissions *installation*)\n        if (permission-default-value permission)\n          collect (permission-name permission)))\n\n(defun %create-api-key (user company)\n  (with-description (description :final-permissions final-permissions\n                                 :permissions (%default-permissions))\n    (let ((api-key (make-instance 'api-key\n                                  :user user\n                                  :description description\n                                  :permissions final-permissions\n                                  :company company)))\n      (hex:safe-redirect\n       (nibble ()\n         (%render-api-key api-key))))))\n\n\n(markup:deftag export-sh-var (&key name value)\n  <markup:merge-tag><code style=\"float: left; padding-right: 10px;\"><span class=\"react-syntax-highlighter-line-number\"></span></code><span class=\"language-built_in\">export</span><span> ,(progn name)=</span><span class=\"language-string\">,(progn value)</span></markup:merge-tag>)\n\n(markup:deftag code-sample-pre (children)\n  \"Similar to <pre> tag, but treats all of the children tags as its own line, making it easier to work with since we don't have to deal with whitespaces.\"\n  (let* ((children (remove-if 'stringp children))\n         (children (butlast (loop for child in children\n                                  append (list child (format nil \"~%\"))))))\n    <pre class=\"syntax-highlighter code-sample-pre\">,@ (progn children)</pre>))\n\n(defun %render-api-key (api-key)\n  (let ((result-api-key (api-key-key api-key))\n        (result-api-key-secret\n          ;; Externally, it's a secret, but internally it's been replace with the token\n          (encode-api-token api-key)))\n      <simple-card-page max-width= \"80em\" >\n     <div class= \"card-header\">\n     <h3>New API Key</h3>\n     </div>\n     <p>Please copy paste the API secret, you won't have access to it again. But you will be able to create new API keys as needed.</p>\n\n     ,(cond\n        ((api-token-mode-p hunchentoot:*acceptor*)\n         <div>\n           <b>API Token</b>:\n           ,(encode-api-token api-key)\n         </div>)\n        (t\n         <div>\n           <b>API Key</b>: ,(progn result-api-key)<br />\n           <b>API Secret</b>: ,(progn result-api-key-secret)<br/>\n           <br>\n           <div class=\"code-sample\">\n             <div class=\"code-sample-header\">\n               <div class=\"code-sample-title\">Example: use these variables on macOS or Linux</div>\n             </div>\n             <div class=\"code-sample-body\">\n               <code-sample-pre>\n                 <export-sh-var name= \"SCREENSHOTBOT_API_KEY\" value=result-api-key />\n                 <export-sh-var name= \"SCREENSHOTBOT_API_SECRET\" value=result-api-key-secret />\n                 ,(let ((hostname (installation-domain *installation*)))\n                    (unless (equal \"https://screenshotbot.io\" hostname)\n                      <export-sh-var name= \"SCREENSHOTBOT_API_HOSTNAME\" value=hostname />))\n               </code-sample-pre>\n             </div>\n           </div>\n         </div>))\n  \n     <div class= \"card-footer\">\n     <a href= \"/api-keys\">Go back</a>\n     </div>\n\n     </simple-card-page>))\n\n\n(defun %confirm-delete (api-key)\n  (confirmation-page\n   :yes (nibble ()\n          (delete-api-key api-key)\n          (hex:safe-redirect \"/api-keys\"))\n   :no (nibble ()\n         (hex:safe-redirect \"/api-keys\"))\n   <p>Are you sure you want to delete API Key ,(api-key-key api-key)</p>))\n\n(defun edit-api-key (api-key)\n  (with-description (description :description (api-key-description api-key)\n                                 :permissions (api-key-permissions api-key)\n                                 :final-permissions final-permissions\n                     :action \"Update\")\n    (setf (api-key-description api-key) description)\n    (setf (api-key-permissions api-key) final-permissions)\n    (hex:safe-redirect \"/api-keys\")))\n\n(defun %api-key-page (&key (user (current-user))\n                        (company (current-company))\n                        (script-name \"/api-keys\"))\n  (declare (ignore script-name))\n  (auth:can-view! company)\n  (let* ((api-keys (reverse (company-api-keys company)))\n         (create-api-key (nibble ()\n                           (%create-api-key user company))))\n    <app-template title= \"Screenshotbot: API Keys\" >\n      <taskie-page-title title=\"API keys\" >\n            <form method= \"post\">\n              <input type= \"submit\" formaction=create-api-key formmethod= \"post\"\n                     class= \"btn btn-success btn-sm\" value= \"New API Key\" />\n            </form>\n      </taskie-page-title>\n\n      ,(taskie-list\n        :items api-keys\n        :headers (list\n                  (if (api-token-mode-p hunchentoot:*acceptor*)\n                      \"\"  \"API Key\")\n                  (if (api-token-mode-p hunchentoot:*acceptor*)\n                      \"Token\" \"Secret\")\n                  \"Description\"\n                  \"Creator\"\n                  \"Expires\"\n                  \"Last used\" \"Actions\")\n        :empty-message \"You haven't created an API Key yet\"\n        :checkboxes nil\n        :row-generator (lambda (api-key)\n                         (let* ((coded-secret (format nil\n                                                      \"~a~a\"\n                                                      \"****\"\n                                                      (let ((secret-key (encode-api-token api-key)))\n                                                        (str:substring (- (length secret-key) 4) nil secret-key))))\n                                (api-key-creator (api-key-user api-key))\n                                (delete-api-key (nibble ()\n                                                  (%confirm-delete api-key))))\n\n                           <taskie-row>\n                             <span>\n                               ,(unless (api-token-mode-p hunchentoot:*acceptor*)\n                                  <span class= \"\" >,(api-key-key api-key)</span>)\n                             </span>\n                             <span>,(progn coded-secret)</span>\n                               <span class= \"d-inline-block text-truncate\" style= \"max-width: 20em\" title= (api-key-description api-key) >\n                                 ,(or (api-key-description api-key) \"\")\n                               </span>\n                               <span>\n                                 ,(user-full-name api-key-creator)\n                               </span>\n                               <span>\n                                 ,(let ((expires (expires-at api-key)))\n                                    (cond\n                                      (expires\n                                       (timeago :timestamp expires))\n                                      (t\n                                       \"Never\")))\n                               </span>\n\n                               <span>\n                                 ,(when-let ((last-used (last-used api-key)))\n                                    (timeago :timestamp last-used))\n                               </span>\n\n                               ,(when (%can-user-modify user api-key)\n                                  <span>\n                                    <a href= (nibble () (edit-api-key api-key)) >\n                                        <mdi name= \"edit\" />\n                                    </a>\n                                    <form style=\"display:inline-block\" class= \"ml-4\" method= \"post\" >\n                                        <button type= \"submit\" formaction=delete-api-key\n                                                formmethod= \"post\"\n                                                class= \"btn btn-link\"\n                                                value= \"Delete\" >\n                                        <mdi name=\"delete\" class= \"text-danger\" />\n                                        </button>\n                                    </form>\n                                  </span>)\n                           </taskie-row>)))\n    </app-template>))\n\n(hex:def-clos-dispatch ((self api-acceptor-mixin) \"/api-keys\") ()\n  (with-login ()\n   (%api-key-page)))\n\n(defun api-key-cli-generate ()\n  (with-login ()\n    (throttle! *throttler* :key (current-user))\n    (let ((key (make-instance 'cli-api-key\n                              :user (current-user)\n                              :company (current-company))))\n      <simple-card-page max-width= \"40rem\" >\n        <div class= \"card-header\">\n          <h3>Grant Account Access</h3>\n        </div>\n\n        <div class= \"row\">\n          <p>Copy-paste the API Token below to grant access to your account.</p>\n\n          <div class= \"mt-2 mb-3\" >\n            <label for= \"token\" class= \"form-label\" >API Token</label>\n            <input type= \"text\" value= (render-api-token key) name= \"token\" class= \"form-control\" />\n          </div>\n\n          <p>This will authorize the requesting script to act on your behalf permanently. If you change your mind you can revoke this token from the <a href= \"/api-keys\">API Keys</a> page.</p>\n        </div>\n\n        <div class= \"card-footer\">\n          <a href= \"/api-keys\" class= \"btn btn-secondary\">Cancel</a>\n        </div>\n      </simple-card-page>)))\n\n(hex:def-clos-dispatch ((self api-acceptor-mixin) \"/api-keys/cli\") ()\n  (api-key-cli-generate))\n"
  },
  {
    "path": "src/core/api/dashboard/test-api-keys.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/api/dashboard/test-api-keys\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/installation/installation\n                #:abstract-installation\n                #:*installation*)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:screenshotbot/dashboard/api-keys\n                #:%render-api-key\n                #:permission\n                #:%read-permissions\n                #:%can-user-modify\n                #:api-key-available-permissions)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:core/api/acceptor\n                #:api-token-mode-p)\n  (:import-from #:cl-mock\n                #:answer))\n(in-package :core/api/dashboard/test-api-keys)\n\n\n;; there are more tests in src/screenshotbot/dashboard/test-api-keys :/\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (abstract-installation)\n  ())\n\n(defmethod api-key-available-permissions ((self my-installation))\n  (list\n   (make-instance\n    'permission\n    :name :ci\n    :default t)\n   (make-instance\n    'permission\n    :name :full)))\n\n(def-fixture state (&key api-key-roles (domain \"https://example.com\"))\n  (cl-mock:with-mocks ()\n   (with-test-store ()\n     (let ((*installation* (make-instance 'my-installation\n                                          :domain domain)))\n       (gk:create :api-key-roles)\n       (funcall (if api-key-roles #'gk:enable #'gk:disable) :api-key-roles)\n       (&body)))))\n\n(test %read-permissions-without-gk\n  (with-fixture state (:api-key-roles nil)\n    (is (equal '(:ci) (%read-permissions)))))\n\n(defclass fake-company (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(test %can-user-modify-by-owner-only\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key :user 'user)))\n      (is-true (%can-user-modify 'user api-key))\n      (is-false (%can-user-modify 'other api-key)))))\n\n(test %can-user-modify-by-admin\n  (with-fixture state ()\n    (let* ((api-key (make-instance 'api-key :user 'user))\n           (company (make-instance 'fake-company)))\n      (roles:ensure-has-role company 'user 'roles:admin)\n      (is-true (%can-user-modify 'user api-key)))))\n\n(test api-hostname-is-shown-for-enterprise-and-oss-installs\n  (with-fixture state (:domain \"https://example.com\")\n    (with-fake-request ()\n      (answer (api-token-mode-p *acceptor*) nil)\n      (let ((api-key (make-instance 'api-key)))\n        (let ((content (%render-api-key api-key)))\n          (assert-that (markup:write-html content)\n                       (contains-string \"SCREENSHOTBOT_API_HOSTNAME\")))))))\n\n(test api-hostname-is-not-shown-for-screenshotbot.io\n  (with-fixture state (:domain \"https://screenshotbot.io\")\n    (with-fake-request ()\n      (answer (api-token-mode-p *acceptor*) nil)\n      (let ((api-key (make-instance 'api-key)))\n        (let ((content (%render-api-key api-key)))\n          (assert-that (markup:write-html content)\n                       (does-not\n                        (contains-string \"example.com\")))\n          (assert-that (markup:write-html content)\n                       (does-not\n                        (contains-string \"SCREENSHOTBOT_API_HOSTNAME\"))))))))\n"
  },
  {
    "path": "src/core/api/model/api-key.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/api/model/api-key\n  (:use :cl)\n  (:nicknames :screenshotbot/model/api-key)\n  (:import-from #:bknr.datastore\n                #:deftransaction\n                #:persistent-class\n                #:store-object\n                #:store-object-id\n                #:with-transaction)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index\n                #:fset-set-index\n                #:index-least)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key-key\n                #:api-key\n                #:api-key-secret-key\n                #:delete-api-key)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:util/atomics\n                #:atomic-exchange)\n  (:export\n   #:%find-api-key\n   #:api-key\n   #:api-key-company\n   #:api-key-description\n   #:api-key-key\n   #:api-key-secret-key\n   #:api-key-user\n   #:delete-api-key\n   #:make-transient-key\n   #:render-api-token\n   #:company-api-keys\n   #:api-key-for-secret\n   #:validate-api-key-secret\n   #:make-encoded-secret))\n(in-package :core/api/model/api-key)\n\n(defvar *lock* (bt:make-lock \"random-string\"))\n\n(defvar *generator* (make-instance 'secure-random::open-ssl-generator))\n\n(defparameter +secret-length+ 40)\n\n(defun %all-alpha (&optional (from #\\a) (to #\\z))\n  (let ((From (char-code from))\n        (to (char-code to)))\n    (assert (< from to))\n   (loop for i from from to to collect\n        (code-char i))))\n\n(defun %random (num)\n  (secure-random:number num *generator*))\n\n(defun generate-random-string (length chars)\n  (bt:with-lock-held (*lock*)\n    (coerce (loop repeat length collect (aref chars (%random (length chars))))\n            'string)))\n\n\n(defun generate-api-key ()\n  (generate-random-string 20 (concatenate 'string (%all-alpha #\\A #\\Z)\n                                          (%all-alpha #\\0 #\\9))))\n\n(Defun generate-api-secret ()\n  (generate-random-string +secret-length+ (concatenate 'string\n                                                       (%all-alpha #\\A #\\Z)\n                                                       (%all-alpha #\\a #\\z)\n                                                       (%all-alpha #\\0 #\\9))))\n\n(defindex +expires-index+\n  'fset-set-index\n  :slot-name 'expires-at)\n\n(defindex +key-index+\n  'fset-unique-index\n  :slot-name 'api-key)\n\n(defindex +secret-index+\n  'fset-unique-index\n  :slot-name 'api-secret-key)\n\n(with-class-validation\n  (defclass api-key (store-object)\n    ((user\n      :initarg :user\n      :accessor api-key-user)\n     (company\n      :initarg :company\n      :initform nil\n      :accessor api-key-company)\n     (api-key\n      :type string\n      :accessor api-key-key\n      :initarg :api-key\n      :index +key-index+\n      :index-reader %%find-api-key\n      :initform nil)\n     (api-secret-key\n      :type string\n      :initarg :api-secret-key\n      :accessor api-key-secret-key\n      :index +secret-index+\n      :index-reader api-key-for-secret\n      :initform nil)\n     (%description\n      :type string\n      :initarg :description\n      :initform nil\n      :accessor api-key-description)\n     (expired-p\n      :type boolean\n      :reader expired-p\n      :initform nil)\n     (%permissions\n      :initarg :permissions\n      :accessor api-key-permissions)\n     (last-used\n      :initarg :last-used\n      :accessor last-used))\n    (:metaclass persistent-class)\n    (:default-initargs\n     :api-key (generate-api-key)\n     :last-used (get-universal-time)\n     :api-secret-key (generate-api-secret)\n     :permissions nil)))\n\n(defmethod last-used :around ((self api-key))\n  (ignore-errors\n   (call-next-method)))\n\n(defmethod api-hostname (installation)\n  (installation-domain installation))\n\n(defun make-encoded-secret (api-key-string hostname secret)\n  \"Create an encoded secret from the api-key string, hostname, and secret.\nReturns the full encoded secret string.\"\n  (flet ((make-encoded (&key (padding \"\"))\n           (str:join\n           \",\"\n           (list\n            api-key-string\n            (format nil \"~a1\" padding)\n            hostname\n            ;; Add a trailing comma to make it easier to decode if\n            ;; +secret-length+ changes\n            \"\"))))\n   (let ((result-pre (make-encoded\n                      :padding\n                      (str:join\n                       \"\"\n                       (loop for i below (- 3 (mod (length (make-encoded)) 3))\n                             collect \" \")))))\n     (let ((result (base64:string-to-base64-string\n                    result-pre)))\n       (assert (not (str:containsp \"+\" result)))\n       (assert (not (str:containsp \"/\" result)))\n       (assert (not (str:containsp \"=\" result)))\n       (format nil \"~a~a\" result secret)))))\n\n(defmethod encode-api-token ((self api-key))\n  (make-encoded-secret\n   (api-key-key self)\n   (api-hostname *installation*)\n   (api-key-secret-key self)))\n\n\n(defmethod expires-at ((self api-key))\n  nil)\n\n(with-class-validation\n  (defclass cli-api-key (api-key)\n    ((expires-at\n      :initarg :expires-at\n      :index +expires-index+\n      :accessor expires-at))\n    (:metaclass persistent-class)\n    (:default-initargs\n     :api-key nil ;; We're going to use cli-NNN instead\n     :expires-at (+ 3600 (get-universal-time)))))\n\n(defmethod api-key-key ((self cli-api-key))\n  (format nil \"cli-~a\"\n          (store-object-id self)))\n\n(defmethod expired-p ((self cli-api-key))\n  (and\n   (expires-at self)\n   (< (expires-at self) (get-universal-time))))\n\n(defvar *transient-keys* (make-mp-hash-table :test #'equal))\n\n(defclass transient-api-key ()\n  ((api-key\n    :initform (format nil \"tmp_~a\" (generate-api-key))\n    :reader api-key-key)\n   (api-key-secret\n    :initform (generate-api-secret)\n    :reader api-key-secret-key)\n   (user\n    :initarg :user\n    :reader api-key-user)\n   (company\n    :initarg :company\n    :reader api-key-company)\n   (%last-used\n    :accessor last-used\n    :documentation \"This is just to be in sync with api-key for simplicity.\")\n   (created-at :initform (get-universal-time)))\n  (:documentation \"A transient key generated for communication between\n  the replay service and this server.\"))\n\n(defmethod expired-p ((self transient-api-key))\n  nil)\n\n(defvar *last-used-cache* (fset:empty-map))\n\n(defun mark-api-key-used (api-key)\n  (when api-key\n   (let ((ts (get-universal-time)))\n     (atomics:atomic-update\n      *last-used-cache*\n      (lambda (map)\n        (fset:with map api-key ts))))))\n\n(defun flush-last-used-cache ()\n  (let ((last-used-cache (atomic-exchange *last-used-cache* (fset:empty-map))))\n    (fset:do-map (api-key ts last-used-cache)\n      (setf (last-used api-key) ts))))\n\n(def-cron flush-last-used-cache ()\n  (flush-last-used-cache))\n\n(defun %find-api-key (str)\n  \"TODO(T1681): Rename to find-api-key (without prefix)\"\n  (let ((result (or\n                 (cond\n                   ((str:starts-with-p \"cli-\" str)\n                    (let ((res (bknr.datastore:store-object-with-id\n                                (parse-integer (second (str:split \"-\" str :limit 2))))))\n                      (when (typep res 'cli-api-key)\n                        res)))\n                   (t\n                    (%%find-api-key str)))\n                 (gethash str *transient-keys*))))\n    (when (and\n           result\n           (not (expired-p result)))\n      result)))\n\n(defun make-transient-key (&key user company)\n  (let ((key (make-instance 'transient-api-key\n                             :user user\n                             :company company)))\n    (setf (gethash (api-key-key key) *transient-keys*)\n          key)\n    key))\n\n(defun decode-api-token (token)\n  (let ((encoded (str:substring 0 (- (length token) +secret-length+) token))\n        (secret (str:substring (- (length token) +secret-length+) nil token)))\n   (destructuring-bind (key version url empty-string)\n       (str:split \",\" (base64:base64-string-to-string encoded))\n     (assert (equal  \"\" empty-string))\n     (values key secret url))))\n\n\n(defun validate-api-key-secret (api-key provided-secret)\n  \"Validates that the provided secret matches the api-key's secret.\nReturns T if valid, NIL otherwise.\"\n  (or\n   (equal provided-secret (api-key-secret-key api-key))\n   (ignore-errors\n    (multiple-value-bind (key secret) (decode-api-token provided-secret)\n      (declare (ignore key))\n      (equal secret (api-key-secret-key api-key))))))\n\n\n(defmethod render-api-token ((self api-key))\n  (format nil \"cli-~a:~a\"\n          (store-object-id self)\n          (api-key-secret-key self)))\n\n(deftransaction tx-delete-api-key (api-key)\n  \"History: we've been using slot-makunbound to delete api\nkeys. However, slot-makunbound doesn't create a transaction\ncurrently (we need to fix this).\n\nIn the meantime, we're using a transaction to fix this. We probably\nneed a better deletion model in the future.\"\n  (slot-makunbound api-key 'api-key))\n\n\n(defmethod delete-api-key ((api-key api-key))\n  (tx-delete-api-key api-key))\n\n(defmethod company-api-keys (company)\n  (loop for api-key in (bknr.datastore:class-instances 'api-key)\n        if (and (slot-boundp api-key 'api-key) ;; See T929\n                (eq company (api-key-company api-key)))\n          collect api-key))\n\n(defmethod user-api-keys (user company)\n  (loop for api-key in (company-api-keys company)\n        if (eq user (api-key-user api-key))\n          collect api-key))\n\n(defmethod cleanup-expired-api-keys ()\n  (let ((next (index-least +expires-index+))\n        (now (get-universal-time)))\n    (when (and next (< (expires-at next) now))\n      (bknr.datastore:delete-object next)\n      (cleanup-expired-api-keys))))\n\n(def-cron cleanup-expired-api-keys (:step-min 5)\n  (cleanup-expired-api-keys))\n\n(def-store-migration (\"Propagate old api-key deletions\" :version 19)\n  \"See T1182\"\n  (dolist (api-key (bknr.datastore:class-instances 'api-key))\n    (unless (slot-boundp api-key 'api-key)\n      (delete-api-key api-key))))\n\n(def-store-migration (\"Add default permissions\" :version 24)\n  \"See T1388\"\n  (let ((defaults (uiop:call-function \"core/api/dashboard/api-keys::%default-permissions\")))\n    (dolist (api-key (bknr.datastore:class-instances 'api-key))\n      (unless (slot-boundp api-key '%permissions)\n        (setf (api-key-permissions api-key)\n              defaults)))))\n\n;; (encode-api-token (elt (bknr.datastore:class-instances 'api-key) 10))\n"
  },
  {
    "path": "src/core/api/model/test-api-key.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/api/model/test-api-key\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/api-key-api\n                #:delete-api-key\n                #:api-key-secret-key\n                #:api-key-key\n                #:api-key)\n  (:import-from #:screenshotbot/model/api-key\n                #:last-used\n                #:flush-last-used-cache\n                #:*last-used-cache*\n                #:mark-api-key-used\n                #:api-key-for-secret\n                #:generate-api-key\n                #:cleanup-expired-api-keys\n                #:make-transient-key\n                #:expired-p\n                #:cli-api-key\n                #:%find-api-key)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:util/store/store-version\n                #:*snapshot-store-version*\n                #:*store-version*)\n  (:import-from #:util/store/store-migrations\n                #:run-migrations)\n  (:import-from #:core/api/model/api-key\n                #:decode-api-token\n                #:validate-api-key-secret\n                #:encode-api-token\n                #:user-api-keys\n                #:api-key-permissions\n                #:company-api-keys\n                #:%permissions)\n  (:import-from #:core/installation/installation\n                #:installation\n                #:abstract-installation\n                #:*installation*))\n(in-package :core/api/model/test-api-key)\n\n\n(util/fiveam:def-suite)\n\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'abstract-installation)))\n   (with-test-store ()\n     (&body))))\n\n\n(test simple-creation\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (is (not (str:emptyp (api-key-key api-key))))\n      (is (not (str:emptyp (api-key-secret-key api-key)))))))\n\n(test find-api-key\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (is (eql api-key\n               (%find-api-key (api-key-key api-key)))))))\n\n(test expired-api-key\n  (with-fixture state ()\n    (let ((api-key (make-instance 'cli-api-key)))\n      (is (eql api-key\n               (%find-api-key (api-key-key api-key)))))\n    (let ((api-key (make-instance 'cli-api-key\n                                  :expires-at (- (get-universal-time) 3600))))\n      (is (eql nil\n               (%find-api-key (api-key-key api-key)))))))\n\n(test cleanup-expired-api-keys\n  (with-fixture state ()\n    (let ((api-key-1 (make-instance 'cli-api-key))\n          (api-key-2 (make-instance 'cli-api-key\n                                    :expires-at (- (get-universal-time) 3600)))\n          (api-key-3 (make-instance 'cli-api-key\n                                    :expires-at (- (get-universal-time) 3800))))\n      (cleanup-expired-api-keys)\n      (assert-that (bknr.datastore:class-instances 'cli-api-key)\n                   (contains api-key-1)))))\n\n(test cleanup-expired-api-keys-for-everything\n  (with-fixture state ()\n    (let ((api-key-2 (make-instance 'cli-api-key\n                                    :expires-at (- (get-universal-time) 3600)))\n          (api-key-3 (make-instance 'cli-api-key\n                                    :expires-at (- (get-universal-time) 3800))))\n      (cleanup-expired-api-keys)\n      (assert-that (bknr.datastore:class-instances 'cli-api-key)\n                   (contains)))))\n\n(test cli-api-key-that-never-expires\n  (with-fixture state ()\n    (let ((api-key (make-instance 'cli-api-key :expires-at nil)))\n      (assert-that (api-key-key api-key)\n                   (matches-regex \"cli-.*\"))\n      (is (eql api-key\n               (%find-api-key (api-key-key api-key)))))))\n\n(test we-cant-cheat-our-way-with-api-id\n  (with-fixture state ()\n    (let ((api-key-old (make-instance 'api-key)))\n      (is-false\n       (%find-api-key (format nil \"cli-~a\" (bknr.datastore:store-object-id api-key-old)))))))\n\n(test transient-api-keys-are-never-expired\n  (is-false (expired-p (make-transient-key :user :company))))\n\n(test generate-api-key\n  (is (equal 20 (length (generate-api-key))))\n  (is (not (equal (generate-api-key)\n                  (generate-api-key)))))\n\n(test find-api-key-by-secret\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (is (eql api-key\n               (api-key-for-secret\n                (api-key-secret-key api-key)))))))\n\n(test delete-api-key\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (finishes\n        (delete-api-key api-key))\n      (is-false\n       (slot-boundp api-key 'api-key))\n      (finishes\n        (delete-api-key api-key)))))\n\n(test store-migration\n  (with-fixture state ()\n    (let ((api-key-1 (make-instance 'api-key))\n          (api-key-2 (make-instance 'api-key)))\n      (delete-api-key api-key-2)\n      (let ((*snapshot-store-version* 18)\n            (*store-version* 19))\n        (run-migrations))\n      (is-true (slot-boundp api-key-1 'api-key))\n      (is-false (slot-boundp api-key-2 'api-key)))))\n\n(test user-api-keys-should-work-even-with-deleted-api-keys\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key\n                                  :user :user1\n                                  :company :company1)))\n      (is (equal (list api-key)\n                 (user-api-keys :user1 :company1)))\n      (is (equal (list api-key)\n                 (company-api-keys :company1)))\n      (delete-api-key api-key)\n      (is (equal nil\n                 (user-api-keys :user1 :company1)))\n      (is (equal nil\n                 (company-api-keys :company1))))))\n\n\n(test mark-api-key-used\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (mark-api-key-used api-key)\n      (mark-api-key-used api-key)\n      (is (eql 1 (fset:size *last-used-cache*))))))\n\n\n(test flush-last-used\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (mark-api-key-used api-key)\n      (mark-api-key-used api-key)\n      (is (eql 1 (fset:size *last-used-cache*)))\n      (flush-last-used-cache)\n      (is (eql 0 (fset:size *last-used-cache*)))\n      (is (> (last-used api-key) 0)))))\n\n(test migration-for-api-key-permissions\n  (with-fixture state ()\n    (let ((*installation* (make-instance 'abstract-installation))\n          (api-key (make-instance 'api-key)))\n      (slot-makunbound api-key '%permissions)\n      (let ((*store-version* 24)\n            (*snapshot-store-version* 23))\n        (run-migrations))\n      (is (eql nil (api-key-permissions api-key))))))\n\n(defclass fake-company (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(test company-api-keys\n  (with-fixture state ()\n    (let* ((company-1 (make-instance 'fake-company))\n           (company-2 (make-instance 'fake-company))\n           (api-key-1 (make-instance 'api-key :company company-1))\n           (api-key-2 (make-instance 'api-key :company company-2)))\n      (is (equal (list api-key-1) (company-api-keys company-1)))\n      (is (equal (list api-key-2) (company-api-keys company-2)))\n      (is (not (equal (list api-key-1) (company-api-keys company-2))))\n      (is (not (equal (list api-key-2) (company-api-keys company-1)))))))\n\n(defparameter *all-chars*\n  (loop for x across \".abcdefghijklmnopqrstuvwxyz0987654321}ABCDEFGHIJKLMNOPQRSTUVWXYZ\\\": +/,\"\n        collect x))\n\n(test demonstrate-that-/-or+-will-never-show-up!\n  (dolist (a *all-chars*)\n    (dolist (b *all-chars*)\n      (dolist (c *all-chars*)\n        (let* ((str (format nil \"~a~a~a\" a b c))\n               (base (base64:string-to-base64-string str)))\n          (when (or\n                 (str:containsp \"/\" base)\n                 (str:containsp \"+\" base))\n            (error \"Failed invariant! ~a ~a~%\" str base)))))))\n\n(test encode-api-token\n  (with-fixture state ()\n    (finishes\n      (encode-api-token\n       (make-instance 'api-key)))))\n\n(test validate-api-key-secret\n  (with-fixture state ()\n    (let ((api-key (make-instance 'api-key)))\n      (is-true (validate-api-key-secret api-key (api-key-secret-key api-key)))\n      (is-false (validate-api-key-secret api-key \"wrong-secret\"))\n      (is-false (validate-api-key-secret api-key \"abcd\"))      \n      (is-false (validate-api-key-secret api-key \"\"))\n      (is-false (validate-api-key-secret api-key nil)))))\n\n(test validate-api-key-secret-allows-token\n  (with-fixture state ()\n    (let* ((api-key (make-instance 'api-key))\n           (token (encode-api-token api-key))\n           (api-key-2 (make-instance 'api-key)))\n      (is-true (validate-api-key-secret api-key (api-key-secret-key api-key)))\n      (is-true (validate-api-key-secret api-key token))\n      (is-false (validate-api-key-secret api-key (encode-api-token api-key-2))))))\n\n(test decode-api-token\n  (with-fixture state ()\n    (let* ((api-key (make-instance 'api-key))\n           (token (encode-api-token api-key)))\n      (multiple-value-bind (key secret url)\n          (decode-api-token token)\n        (is (equal (api-key-key api-key) key))\n        (is (equal (api-key-secret-key api-key) secret))\n        (is (equal \"https://example.com\" url))))))\n\n\n"
  },
  {
    "path": "src/core/cli/core.cli.asd",
    "content": "(defsystem :core.cli\n  :serial t\n  :depends-on (:util.threading\n               :util/native-module\n               :easy-macros)\n  :components ((:file \"sentry\")))\n\n(defsystem :core.cli/tests\n  :serial t\n  :depends-on (:core.cli\n               :util/fiveam)\n  :components ((:file \"test-sentry\")))\n\n(defsystem :core.cli/deliver\n  :serial t\n  :depends-on (:core.cli\n               :build-utils/deliver-script)\n  :components ((:file \"deliver\")))\n"
  },
  {
    "path": "src/core/cli/darwin-template.lisp",
    "content": "(FLI::DEFINE-PRECOMPILED-FOREIGN-OBJECT-ACCESSOR-FUNCTIONS ((CARBON:CF-STRING-REF :NO-ALLOC-P :ERROR :SIZE COMMON-LISP:T)))\n"
  },
  {
    "path": "src/core/cli/deliver.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/cli/deliver\n  (:use #:cl)\n  (:export\n   #:deliver-end-user-cli))\n(in-package :core/cli/deliver)\n\n(defun safe-delete-file (x)\n  (when (uiop:file-exists-p x)\n    (delete-file x)))\n\n(defun fake-lw-find-dpsec-location (frame)\n  nil)\n\n(defun win-ssl-dir ()\n  \"C:/Program Files/OpenSSL-Win64/\")\n\n(defvar *libssl* (util/native-module:make-system-module\n                     'openssl\n                     :file-name (path:catfile (win-ssl-dir)\n                                              \"libssl-3-x64.dll\")))\n\n(defvar *libcrypto* (util/native-module:make-system-module\n                     'openssl\n                     :file-name (path:catfile (win-ssl-dir)\n                                              \"libcrypto-3-x64.dll\")))\n\n(defun embed-ssl ()\n  (util/native-module:embed-module *libssl*)\n  (util/native-module:embed-module *libcrypto*))\n\n(defun write-embedded (name dir filename)\n  (with-open-file (stream (path:catfile dir filename) :direction :output :if-exists :supersede)\n    (write-sequence (fli:get-embedded-module-data name)\n                    stream)))\n\n(defun install-ssl ()\n  (let ((dir (tmpdir:mkdtemp)))\n    (setf util/native-module::*lib-cache-dir* dir)\n    (util/native-module::load-embedded-module *libcrypto* :load nil)\n    (util/native-module::load-embedded-module *libssl* :load nil)\n\n    (comm:set-ssl-library-path\n     (list\n      (path:catfile dir \"libcrypto-3-x64.dll\")\n      (path:catfile dir \"libssl-3-x64.dll\")))\n\n    (comm:ensure-ssl)))\n\n(defun compile-and-load-template-file (path)\n  (let ((output (compile-file path)))\n    (load output)))\n\n#+lispworks\n(defun include-template-file (template-builder)\n  (let ((path (asdf:system-relative-pathname :screenshotbot.sdk \"template.lisp\")))\n    (with-open-file (out path :direction :output\n                              :if-exists :supersede)\n\n      (fli:start-collecting-template-info)\n      (funcall template-builder)\n      (fli:print-collected-template-info :output-stream out))\n    (format t \"~%~%The FLI template content is: ~A~%~%~%\" (uiop:read-file-string path))\n\n    (compile-and-load-template-file path)))\n\n(defun deliver-main (output-file template-builder-fn\n                     &key restart-fn universal (level 5))\n  (when (find-package :cl+ssl)\n    (error \"CL+SSL is present in this image, this can lead to problems when delivering this\non Mac. (e.g., the image will try to load libcrypto etc.\"))\n  (when (find-package :bknr.cluster/server)\n    (error \"BKNR.CLUSTER is present in this image\"))\n  (unless universal\n    (safe-delete-file output-file))\n\n  #+lispworks\n  (when nil\n    ;; We used to generate and load the template during the deliver\n    ;; process. But that causes instability in the sense that OpenSSL\n    ;; is loaded into the image. We've noticed that that the template\n    ;; is always fixed, and only required for Mac, so that's what\n    ;; we'll do.\n    ;;\n    ;; Enable this temporarily to see what the expected version of the\n    ;; template should look like.\n    (include-template-file template-builder-fn))\n\n  #+ (and lispworks darwin)\n  (compile-and-load-template-file\n   (asdf:system-relative-pathname :core.cli \"darwin-template.lisp\"))\n\n  #+mswindows\n  (embed-ssl)\n\n  (setf asdf:*central-registry* nil)\n  (setf ql:*local-project-directories* nil)\n\n  ;; Causes a warning early on from cl+ssl not finding libcrypto or\n  ;; some such.\n  (lw:undefine-action \"When starting image\" \"Reset cl-mongo-id state\")\n\n  ;; See Lisp Support Call #43182\n  ;; The goal is to allow us to run the CLI on Alpine Linux (or any linux that\n  ;; is shipped with musl instead of glibc.)\n  #+(and linux arm64)\n  (setf (symbol-function 'sys::set-signals-mask) #'lw:false)\n\n  (build-utils/deliver-script:default-deliver\n   (lambda ()\n     #+mswindows\n     (install-ssl)\n     (funcall restart-fn))\n   output-file\n   level\n   :keep-function-name t\n   :keep-debug-mode (/= 0 level)\n   #+mswindows :console #+mswindows :init\n   #+mswindows :startup-bitmap-file #+mswindows nil\n   :keep-clos :meta-object-slots\n   :keep-pretty-printer t\n   :keep-clos-object-printing t\n   :kill-dspec-table nil ;; for defadvice, in particular clingon:exit\n   :keep-lisp-reader t\n   ;; temporary: get the build green\n   :keep-symbols `(system:pipe-exit-status\n                   dspec:find-dspec-locations)\n   :packages-to-keep-symbol-names :all\n   :multiprocessing t))\n\n\n\n(defun deliver-end-user-cli (&key deliver-script\n                               restart-fn\n                               (universal #+darwin t)\n                               output-file\n                               (level 5)\n                               template-builder-fn)\n  (setf (fdefinition 'sentry-client::lw-find-dspec-location)\n        #'fake-lw-find-dpsec-location)\n  (format t \"We'll be writing to ~A~%\" output-file)\n\n  (ensure-directories-exist output-file)\n\n  (flet ((call-next ()\n           (deliver-main output-file template-builder-fn\n                         :level level\n                         :restart-fn restart-fn\n                         :universal universal)))\n    (cond\n     (universal\n      (cond\n       ((hcl:building-universal-intermediate-p)\n        (call-next))\n       (t\n        (safe-delete-file output-file)\n        (hcl:save-universal-from-script deliver-script))))\n     (t\n      (call-next)))))\n"
  },
  {
    "path": "src/core/cli/sentry.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/cli/sentry\n  (:use #:cl)\n  (:import-from #:util/threading\n                #:with-tags\n                #:maybe-log-sentry\n                #:*warning-count*\n                #:with-extras\n                #:*extras*\n                #:funcall-with-sentry-logs)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:default-sentry-output-stream\n   #:with-cli-sentry))\n(in-package :core/cli/sentry)\n\n(defun trim-arg (arg)\n  (let ((size 6)\n        (prefix 3))\n   (cond\n     ((<= (length arg) size)\n      arg)\n     (t\n      (format nil \"~a...~a\"\n              (str:substring 0 prefix arg)\n              (str:substring (- (length arg) (- size prefix)) nil arg))))))\n\n\n(defun default-sentry-output-stream ()\n  #+lispworks\n  (system:make-stderr-stream)\n  #-lispworks\n  *standard-output*)\n\n(def-easy-macro with-cli-sentry (&key verbose\n                                      (dry-run nil)\n                                      (stream\n                                       (default-sentry-output-stream))\n                                      (on-error (lambda ()\n                                                  (uiop:quit 1)))\n                                      &fn fn)\n  (let ((start-time (get-universal-time)))\n    (with-extras (#+lispworks\n                  (\"cmd-line-trimmed\"\n                   (mapcar\n                    #'trim-arg\n                    sys:*line-arguments-list*))\n                  (\"hostname\" (uiop:hostname))\n                  #+lispworks\n                  (\"openssl-version\" (comm:openssl-version))\n                  (\"start-time\" start-time)\n                  (\"current-time\" (get-universal-time)))\n     (let ((error-handler (lambda (e)\n                            (format stream \"~%~a~%~%\" e)\n                            #+lispworks\n                            (dbg:output-backtrace (if verbose :bug-form :brief)\n                                                  :stream stream)\n                            #-lispworks\n                            (trivial-backtrace:print-backtrace e stream)\n                            (unless dry-run\n                              #-screenshotbot-oss\n                              (util/threading:log-sentry e))\n                            (funcall on-error))))\n       (let ((*warning-count* 0))\n         (handler-case\n             (handler-bind (#+lispworks\n                            (error error-handler)\n                            #+lispworks\n                            (conditions:stack-overflow error-handler))\n               ;; We put the warning handler inside here, so that if an\n               ;; error happens in the warning handler, we can log that.\n               (handler-bind (#+lispworks\n                              (warning #'maybe-log-sentry))\n                 (funcall fn)))\n           #+lispworks\n           (conditions:stack-overflow (e)\n             (funcall error-handler e))\n           #+nil ;; We could keep this here, but :error-handler :btrace is probably good \n           (error (e)\n             ;; This is here just in case \n             (format t \"Unrecoverable error happened: ~a (~a)~%\" e (type-of e)))\n           #+nil ;; We could keep this here, but :error-handler :btrace is probably good \n           (serious-condition (e)\n             ;; In particular, this could've handled stack-overflow,\n             ;; which is of type serious-condition, and not an error.\n             (format t \"Serious error happened: ~a (~a)~%\" e (type-of e)))))))))\n"
  },
  {
    "path": "src/core/cli/test-sentry.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/cli/test-sentry\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/cli/sentry\n                #:trim-arg\n                #:with-cli-sentry)\n  (:import-from #:util/threading\n                #:*tags*\n                #:*extras*))\n(in-package :core/cli/test-sentry)\n\n(util/fiveam:def-suite)\n\n(define-condition my-error (error)\n  ())\n\n(test happy-path\n  (finishes\n    (with-cli-sentry (:dry-run t\n                      :on-error (lambda ()))\n      (+ 1 1)))\n  (signals my-error\n    (with-cli-sentry (:dry-run t\n                      :stream (make-string-output-stream)\n                      :on-error (lambda ()))\n      (error 'my-error))))\n\n(test all-tags-and-extras-evaluate\n  (finishes\n    (with-cli-sentry (:dry-run t\n                      :on-error (lambda ()))\n      (loop for fn in *extras*\n            do (funcall fn (make-condition 'my-error)))\n      (loop for fn in *tags*\n            do (funcall fn (make-condition 'my-error))))))\n\n\n(test trim-arg\n  (is (equal \"foo\" (trim-arg \"foo\")))\n  (is (equal \"--f...bar\" (trim-arg \"--foobar\"))))\n"
  },
  {
    "path": "src/core/config/api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/config/api\n  (:use #:cl)\n  (:export\n   #:config\n   #:validate))\n(in-package :core/config/api)\n\n(defmethod validate (sym value)\n  \"Extend this\n to validate the value for a config. The SYM will the\nupcased name of the config interned in :KEYWORD\"\n  nil)\n\n(defgeneric config (key))\n\n(defgeneric (setf config) (value key))\n\n\n"
  },
  {
    "path": "src/core/config/core.config.asd",
    "content": "(defsystem :core.config/api\n  :depends-on ()\n  :components ((:file \"api\")))\n\n(defsystem :core.config\n  :serial t\n  :depends-on (:bknr.datastore\n               :util.misc\n               :core.config/api\n               :util.store)\n  :components ((:file \"model\")))\n\n(defsystem :core.config/tests\n  :serial t\n  :depends-on (:core.config\n               :util/fiveam)\n  :components ((:file \"test-model\")))\n"
  },
  {
    "path": "src/core/config/model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/config/model\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:core/config/api\n                #:validate\n                #:config))\n(in-package :core/config/model)\n\n(defindex +keys+\n  'unique-index\n  :test 'equal\n  :slot-name '%key)\n\n(defclass config-setting (store-object)\n  ((%key :initarg :key\n         :initform nil\n         :reader config-setting-key\n         :index +keys+\n         :index-reader config-setting-for-key\n         :documentation \"A string value for the config setting (it's a string because humans have to configure it.\")\n   (%value :initarg :value\n          :initform nil\n           :reader config-setting-value))\n  (:metaclass persistent-class))\n\n(defmethod config (key)\n  (?. config-setting-value (config-setting-for-key key)))\n\n(define-condition value-must-be-string (error)\n  ())\n\n(defmethod (setf config) (value key)\n  (error 'value-must-be-string))\n\n(defmethod (setf config) ((value string) key)\n  (handler-bind ((error (lambda (e)\n                          (format t \"Got error: ~a~%\" e))))\n    (validate (intern (str:upcase key) :keyword) value))\n  (?. bknr.datastore:delete-object (config-setting-for-key key))\n  (make-instance 'config-setting\n                 :key key\n                 :value value))\n\n\n\n\n"
  },
  {
    "path": "src/core/config/test-model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :core/config/test-model\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/config/model\n                #:value-must-be-string\n                #:config)\n  (:import-from #:util/store/store\n                #:with-test-store))\n(in-package :core/config/test-model)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test simple-get-set-etc\n  (with-fixture state ()\n    (is (equal nil (config \"foo.bar\")))\n    (setf (config \"foo.bar\") \"car\")\n    (is (equal \"car\" (config \"foo.bar\")))\n    (setf (config \"foo.bar\") \"zoidberg\")\n    (is (equal \"zoidberg\" (config \"foo.bar\")))))\n\n(test cant-set-integer\n  (with-fixture state ()\n    (signals value-must-be-string\n      (setf (config \"foo.bar\") 15))\n    (is (equal nil (config \"foo.bar\")))))\n"
  },
  {
    "path": "src/core/installation/auth-provider.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/installation/auth-provider\n  (:use #:cl)\n  (:export\n   #:auth-provider\n   #:auth-provider-signin-form\n   #:auth-provider-signup-form\n   #:company-sso-auth-provider\n   #:call-with-company-login\n   #:auth-providers\n   #:default-oidc-provider))\n(in-package :core/installation/auth-provider)\n\n(defclass auth-provider ()\n  ())\n\n(defgeneric auth-provider-signin-form (auth-provider redirect))\n\n(defgeneric auth-provider-signup-form (auth-provider invite\n                                       plan\n                                       redirect))\n\n(defgeneric company-sso-auth-provider (company)\n  (:method (company)\n    nil)\n  (:documentation \"An self-service SSO auth provider for the given company. This is an\nincomplete implementation, but we plan to build this out at some point\nof time.\"))\n\n(defmethod call-with-company-login (auth-provider company fn)\n  \"Given the auth provider, generate a page with the given auth-provider.\"\n\n  (error \"call-with-company-login unimplemented\"))\n\n(defgeneric auth-providers (installation)\n  (:method (self)\n    nil))\n\n(defgeneric default-oidc-provider (installation)\n  (:method (self)\n    nil))\n\n(defgeneric on-user-sign-in (auth user)\n  (:documentation \"Called when a user is signed in with SSO or login. This might be a\ngood time to ensure the user is added to a specific company etc.\")\n  (:method (auth user)))\n"
  },
  {
    "path": "src/core/installation/auth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/installation/auth\n  (:use #:cl)\n  (:export\n   #:call-with-ensure-user-prepared\n   #:company-for-request\n   #:find-user))\n(in-package :core/installation/auth)\n\n(defgeneric call-with-ensure-user-prepared (installation user\n                                            fn)\n  (:method (installation user fn)\n    (funcall fn))\n  (:documentation \"A web callback to ensure that a user is prepared before rending a\nwith-login page.\"))\n\n(defgeneric company-for-request (installation request))\n"
  },
  {
    "path": "src/core/installation/core.installation.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem #:core.installation\n  :serial t\n  :depends-on (#:util/html2text\n               #:lparallel\n               #:util.threading\n               #:auto-restart\n               #:core.config/api\n               #:quri\n               #:easy-macros\n               #:cl-smtp)\n  :components ((:file \"installation\")\n               (:file \"request\")\n               (:file \"auth-provider\")\n               (:file \"mailer\")\n               (:file \"auth\")))\n\n(defsystem #:core.installation/tests\n  :serial t\n  :depends-on (#:core.installation\n               #:core.config\n               #:fiveam-matchers\n               #:util/fiveam)\n  :components ((:file \"test-mailer\")\n               (:file \"test-installation\")\n               (:file \"test-request\")))\n"
  },
  {
    "path": "src/core/installation/installation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/installation/installation\n  (:use #:cl)\n  (:import-from #:core/config/api\n                #:validate\n                #:config)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:abstract-installation\n   #:*installation*\n   #:installation-domain\n   #:installation-name))\n(in-package :core/installation/installation)\n\n(defclass abstract-installation ()\n  ((domain :initarg :domain\n           :initform \"https://example.com\"\n           :reader installation-domain)\n   (name :initform :name\n         :initarg :name\n         :reader installation-name\n         :documentation \"A symbol representing an installation name. This\nname will be used from data to represent which installation it belongs to.\")))\n\n(defmethod installation-domain ((self null))\n  nil)\n\n(defmethod installation-domain :around (installation)\n  (let ((config-value (ignore-errors (config \"installation.domain\")))\n        (installation-value (call-next-method)))\n\n    (when (and\n           config-value\n           installation-value\n           (not (equal \"https://example.com\" installation-value))\n           (not (equal config-value installation-value)))\n      (warn \"Domain from config and installation does not match: \"))\n    (or\n     config-value\n     installation-value)))\n\n(defvar *secondary-installations* nil\n  \"Non default installations, when allowing for multiple installations\nper process.\")\n\n(defvar *installation*)\n\n(pushnew '*installation* util/threading:*propagated-symbols*)\n\n(defmethod site-alert (installation)\n  nil)\n\n(defmethod validate ((key (eql :installation.domain)) value)\n  (unless (or\n           (str:starts-with-p \"http://\" value)\n           (str:starts-with-p \"https://\" value))\n    (error \"URL must be of the form https://... or http://...\"))\n\n  ;; validate parsing\n  (let ((uri (quri:uri value)))\n    (when (equal \"/\" (quri:uri-path uri))\n      (error \"Don't use the trailing / in the URI\"))\n    (unless (str:emptyp (quri:uri-path uri))\n      (error \"The URL must be a root URL, not ~a\" (quri:uri-path uri)))))\n"
  },
  {
    "path": "src/core/installation/mailer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/mailer\n  (:nicknames :core/installation/mailer)\n  (:use :cl)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:export\n   #:host\n   #:local-smtp-mailer\n   #:noop-mailer\n   #:send-mail\n   #:smtp-mailer\n   #:mailer*\n   #:mailer\n   #:wrap-template))\n(in-package :core/installation/mailer)\n\n(defclass mailer ()\n  ((from :initarg :from\n         :accessor from)))\n\n(defclass noop-mailer (mailer)\n  ()\n  (:documentation \"A mailer that does nothing\"))\n\n(defclass background-mailer-mixin ()\n  ())\n\n(defclass smtp-mailer (mailer\n                       background-mailer-mixin)\n  ((host :initarg :hostname\n         :accessor host)\n   (port :initarg :port\n         :initform 587\n         :accessor port)\n   (ssl :initarg :ssl\n        :initform t\n        :accessor ssl)\n   (user :initarg :user\n         :accessor user)\n   (password :initarg :password\n             :accessor password)))\n\n\n(defclass local-smtp-mailer (smtp-mailer)\n  ()\n  (:default-initargs\n   :hostname \"localhost\"\n   :port 25\n   :ssl nil)\n  (:documentation \"A mailer that uses the local SMTP port. We expect\n  that this SMTP server shouldn't require authentication.\"))\n\n(defgeneric send-mail (mailer &key from subject to html-message bcc\n                                reply-to\n                                display-name\n                                extra-headers))\n\n(defmethod authentication ((mailer local-smtp-mailer))\n  nil)\n\n(defmethod authentication ((mailer smtp-mailer))\n  `(:plain ,(user mailer) ,(password mailer)))\n\n(defmethod send-mail ((mailer noop-mailer) &key &allow-other-keys)\n  (declare (ignore mailer)))\n\n(defun parse-from (from display-name)\n  \"If the from is provides as Foo Bar <foo@bar>, parse out the from and\n display-name from it. This is required for some SMTP servers, but not\n all.\"\n  (cond\n    (display-name\n     (values from display-name))\n    (t\n     (multiple-value-bind (parse parts)\n         (cl-ppcre:scan-to-strings\n          \"(.*)<(.*)>.*\" from)\n       (cond\n         (parse\n          (values\n           (str:trim (elt parts 1))\n           (str:trim (elt parts 0))))\n         (t\n          (values from display-name)))))))\n\n(defun fix-email-list (emails)\n  (cond\n    ((listp emails)\n     (loop for email in emails\n           collect (parse-from email nil)))\n    (t\n     (parse-from emails nil))))\n\n(defmethod wrap-template (mailer html-message)\n  html-message)\n\n(auto-restart:with-auto-restart (:retries 2 :sleep 30)\n  (defun send-email-with-retries (&rest args)\n    (apply #'cl-smtp:send-email args)))\n\n(defmethod send-mail ((mailer smtp-mailer)\n                      &rest args\n                      &key from subject to html-message\n                        display-name\n                        reply-to\n                        bcc\n                        extra-headers)\n  (log:info \"Sending mail ~S\" args)\n  (restart-case\n      (multiple-value-bind (from display-name)\n          (parse-from from display-name)\n       (send-email-with-retries\n        (host mailer)\n        (or from (from mailer))\n        (fix-email-list to)\n        subject\n        (util/html2text:html2text html-message)\n        :ssl (ssl mailer)\n        :bcc (fix-email-list bcc)\n        :port (port mailer)\n        :reply-to reply-to\n        :display-name display-name\n        :extra-headers extra-headers\n        :authentication (authentication mailer)\n        :port (port mailer)\n        :html-message (markup:write-html (wrap-template mailer html-message))))\n    (dont-send-the-mail ()\n      nil)))\n\n(defvar *mailer-pool* (make-instance 'max-pool))\n\n(defmethod send-mail :around ((mailer background-mailer-mixin) &rest args)\n  (let ((promise (lparallel:promise)))\n    (prog1\n        promise\n      (make-thread\n       (lambda ()\n         (lparallel:fulfill promise\n           (call-next-method)))\n       :pool *mailer-pool*\n       :name \"email thread\"))))\n\n(defgeneric mailer (installation))\n\n(defun mailer* (&optional (installation *installation*))\n  (mailer installation))\n"
  },
  {
    "path": "src/core/installation/request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :core/installation/request\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:core/installation/installation\n                #:*secondary-installations*\n                #:*installation*)\n  (:export\n   #:with-installation-for-request))\n(in-package :core/installation/request)\n\n(defmethod installation-matches-request-p (installation request)\n  nil)\n\n(def-easy-macro with-installation-for-request (request &fn fn)\n  (declare (ignore request))\n  (block outer\n   (dolist (installation *secondary-installations*)\n     (when (installation-matches-request-p installation request)\n       (return-from outer\n         (let ((*installation* installation))\n           (funcall fn)))))\n   (funcall fn)))\n"
  },
  {
    "path": "src/core/installation/test-installation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/installation/test-installation\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:abstract-installation\n                #:installation\n                #:*installation*)\n  (:import-from #:core/config/model\n                #:config))\n(in-package :core/installation/test-installation)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((*installation* (make-instance 'abstract-installation\n                                         :domain \"https://example.com/\")))\n      (&body))))\n\n(test installation-domain-without-any-store\n  (with-fixture state ()\n    (is (equal \"https://example.com/\" (installation-domain *installation*)))))\n\n(test we-prefer-the-config-value\n  (with-fixture state ()\n    (is (equal \"https://example.com/\" (installation-domain *installation*)))\n    (setf (config \"installation.domain\") \"https://foobar.com\")\n    (is (equal \"https://foobar.com\" (installation-domain *installation*)))))\n\n(test signals-warning-when-not-matching\n  (with-fixture state ()\n    (is (equal \"https://example.com/\" (installation-domain *installation*)))\n    (setf (config \"installation.domain\") \"https://foobar.com\")\n    (signals simple-warning\n      (installation-domain *installation*))))\n\n\n(test validation-of-domain\n  (with-fixture state ()\n    (setf (config \"installation.domain\") \"https://example.com\")\n    (signals error\n      (setf (config \"installation.domain\") \"foobar\"))\n    (signals error\n      (setf (config \"installation.domain\") \"https://example.com/bar/\"))\n    (signals error\n      ;; trailing /\n      (setf (config \"installation.domain\") \"https://example.com/\"))))\n"
  },
  {
    "path": "src/core/installation/test-mailer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/installation/test-mailer\n  (:use :cl)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:equal-to)\n  (:import-from #:it.bese.fiveam\n                #:is\n                #:test)\n  (:import-from #:screenshotbot/mailer\n                #:background-mailer-mixin\n                #:fix-email-list\n                #:host\n                #:local-smtp-mailer\n                #:parse-from\n                #:port\n                #:send-mail))\n(in-package :core/installation/test-mailer)\n\n(util/fiveam:def-suite)\n\n(test local-smtp-mailer\n  (let ((mailer (make-instance 'local-smtp-mailer)))\n    (assert-that (host mailer)\n                 (equal-to \"localhost\"))\n    (assert-that (port mailer)\n                 (equal-to 25))))\n\n(test parse-from\n  (flet ((parse (x y)\n           (multiple-value-list (parse-from x y))))\n    (is (equal (list \"foo@goo.com\" \"Foo Goo\")\n               (parse \"foo@goo.com\" \"Foo Goo\")))\n    (is (equal (list \"foo@goo.com\" nil)\n               (parse \"foo@goo.com\" nil)))\n    (is (equal (list \"foo@goo.com\" \"Foo Goo\")\n               (parse \"Foo Goo<foo@goo.com>\" nil)))\n    (is (equal (list \"foo@goo.com\" \"Foo Goo\")\n               (parse \"Foo Goo <foo@goo.com>\" nil)))))\n\n(defclass dummy-mailer ()\n  ())\n\n(defclass dummy-background-mailer (dummy-mailer\n                                   background-mailer-mixin)\n  ())\n\n(defmethod send-mail ((mailer dummy-mailer) &rest args)\n  :pass)\n\n(test future-from-background-mailer\n  (let ((mailer (make-instance 'dummy-background-mailer)))\n    (let ((promise (send-mail mailer :from \"foo\" :to \"bar\")))\n      (is (not (eql :pass promise)))\n      (is\n       (eql :pass\n            (lparallel:force promise))))))\n\n\n(test fix-email-list\n  (is (equal \"arnold@screenshotbot.io\"\n             (fix-email-list \"Arnold Noronha <arnold@screenshotbot.io>\")))\n  (is (equal \"arnold@screenshotbot.io\"\n             (fix-email-list \"arnold@screenshotbot.io\")))\n  (is (equal (list \"arnold@screenshotbot.io\")\n             (fix-email-list (list \"Arnold Noronha <arnold@screenshotbot.io>\"))))\n    (is (equal (list \"arnold@screenshotbot.io\")\n             (fix-email-list (list \"arnold@screenshotbot.io\"))))\n  (is (equal nil\n             (fix-email-list nil))))\n"
  },
  {
    "path": "src/core/installation/test-request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :core/installation/test-request\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/installation/installation\n                #:*secondary-installations*\n                #:*installation*)\n  (:import-from #:core/installation/request\n                #:installation-matches-request-p\n                #:with-installation-for-request)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:is-equal-to))\n(in-package :core/installation/test-request)\n\n\n(util/fiveam:def-suite)\n\n(test happy-path-for-request\n  (let ((*installation* 'foo))\n    (assert-that\n     (with-installation-for-request (:fake-request)\n       :after)\n     (is-equal-to :after))))\n\n(test happy-path-when-there-are-secondary-requests\n  (let ((*installation* 'foo)\n        (*secondary-installations* (list 'bar 'nar)))\n    (assert-that\n     (with-installation-for-request (:fake-request)\n       *installation*)\n     (is-equal-to 'foo))))\n\n(defmethod installation-matches-request-p ((installation (eql 'boo)) request)\n  t)\n\n\n(test secondary-installation-wins\n  (let ((*installation* 'foo)\n        (*secondary-installations* (list 'bar 'boo 'nar)))\n    (assert-that\n     (with-installation-for-request (:fake-request)\n       *installation*)\n     (is-equal-to 'boo))))\n"
  },
  {
    "path": "src/core/rpc/core.rpc.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :core.rpc\n  :serial t\n  :depends-on (:core.api\n               :server\n               :util.threading\n               (:feature (:and :linux :lispworks) :bknr.cluster)\n               :encrypt)\n  :components ((:file \"rpc\")))\n\n(defsystem :core.rpc/tests\n  :serial t\n  :depends-on (:core.rpc\n               :util.testing\n               :util/fiveam)\n  :components ((:file \"test-rpc\")))\n"
  },
  {
    "path": "src/core/rpc/rpc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/rpc/rpc\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:*store*\n                #:encode\n                #:decode\n                #:persistent-class\n                #:store-object)\n  (:import-from #:screenshotbot/model/api-key\n                #:generate-api-secret)\n  (:import-from #:util/store/store\n                #:with-class-validation)\n  (:import-from #:encrypt/hmac\n                #:encode-signed-string\n                #:decode-signed-string)\n  (:import-from #:util/store/encodable\n                #:encodable)\n  (:import-from #:util/request\n                #:http-request)\n  #+bknr.cluster\n  (:import-from #:bknr.cluster/server\n                #:leader-id)\n  (:export\n   #:authenticate-rpc-request))\n(in-package :core/rpc/rpc)\n\n(with-class-validation\n  (defclass rpc-auth-id (store-object)\n    ((%secret :initarg :secret\n              :reader secret)\n     (%created-at :initarg :created-at))\n    (:metaclass persistent-class)\n    (:default-initargs :secret (generate-api-secret)\n                      :created-at (get-universal-time))))\n\n(hunchentoot:define-easy-handler (rpc-handler :uri \"/intern/rpc\") ()\n  (perform-rpc hunchentoot:*request*))\n\n\n(define-condition rpc-authentication-failed (error)\n  ())\n\n(defun authenticate-rpc-request (request)\n  (multiple-value-bind (key secret) (hunchentoot:authorization request)\n    (let ((key (parse-integer key :junk-allowed t)))\n      (unless key\n        (error 'rpc-authentication-failed))\n      (let ((auth-id (bknr.datastore:store-object-with-id key)))\n        (unless (typep auth-id 'rpc-auth-id)\n          (error 'rpc-authentication-failed))\n        (unless (equal secret (secret auth-id))\n          (error 'rpc-authentication-failed))))))\n\n(defun encode-bknr-object (obj)\n  (let ((stream (flex:make-in-memory-output-stream)))\n    (encode obj stream)\n    (let* ((base64 (base64:usb8-array-to-base64-string\n                    (flex:get-output-stream-sequence stream)))\n           (content (encode-signed-string base64)))\n      content)))\n\n(defun decode-bknr-object (body)\n  (let ((base64 (decode-signed-string body)))\n    (let ((arr (base64:base64-string-to-usb8-array base64)))\n      (decode (flex:make-in-memory-input-stream arr)))))\n\n(defun assert-direct-request (request)\n  \"RPC requests shouldn't go through LB or Nginx.\"\n  (assert (eql :post (hunchentoot:request-method request)))\n  (assert (not (hunchentoot:header-in :x-forwarded-for request)))\n  (assert (not (hunchentoot:header-in :x-real-ip request))))\n\n(defmethod perform-rpc (request)\n  (assert-direct-request request)\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (log:info \"Delegating request: ~a\" request)\n    (let ((result\n            (call-rpc\n             (decode-bknr-object body))))\n      (encode-bknr-object result))))\n\n(defclass hello-world-rpc (encodable)\n  ())\n\n(defmethod call-rpc ((self hello-world-rpc))\n  (log:info \"hello world!!\")\n  \"123\")\n\n(defun send-rpc (url rpc &key (read-timeout 600))\n  (decode-bknr-object\n   (handler-bind ((drakma::drakma-simple-error\n                    (lambda (e)\n                      (error \"RPC request to ~a probably timed out: ~a\"\n                             url e))))\n     (http-request\n      url\n      :method :post\n      :read-timeout read-timeout\n      :content (encode-bknr-object rpc)))))\n\n(defun id-to-ip (conf)\n  (first (str:rsplit \":\" conf :limit 4)))\n\n(defun %servers ()\n  (typecase bknr.datastore:*store*\n    #+bknr.cluster\n    (bknr.cluster/server::lisp-state-machine\n     (loop for conf in (bknr.cluster/server:list-peers bknr.datastore:*store*)\n           collect (id-to-ip conf)))\n    (t\n     (list \"127.0.0.1\"))))\n\n(defun %port (&key (default-port 4001))\n  \"If we're not using multi-acceptor, then I don't know how to find the\nport. For now, we're standardizing on 4001, which is what the\nscreenshotbot acceptor hardcodes as.\"\n  (cond\n    ((boundp 'server::*multi-acceptor*)\n     (hunchentoot:acceptor-port server::*multi-acceptor*))\n    (t\n     default-port)))\n\n(defun send-rpc-to-server (server rpc &key (read-timeout 600))\n  (send-rpc\n   (format nil \"http://~a:~a/intern/rpc\"\n           server\n           (%port))\n   rpc\n   :read-timeout read-timeout))\n\n(defun send-rpc-to-leader (rpc &key (read-timeout 600))\n  #+bknr.cluster\n  (send-rpc-to-server\n   (id-to-ip (leader-id *store*))\n   rpc\n   :read-timeout read-timeout))\n\n(defun map-rpc (rpc &key (read-timeout 600))\n  \"Map the RPC across all the peers in the cluster (including the current-one)\"\n  (let* ((servers (%servers))\n         (results (make-array (length servers))))\n    (mapc\n     #'bt:join-thread\n     (loop for server in servers\n           for i from 0\n           collect\n           (let ((i i)\n                 (server server))\n             (util/threading:make-thread\n              (lambda ()\n                (setf\n                 (aref results i)\n                 (send-rpc-to-server\n                  server\n                  rpc\n                  :read-timeout read-timeout)))))))\n    (loop for result across results\n          collect result)))\n\n;; (map-rpc (make-instance 'hello-world-rpc))\n\n;;(send-rpc \"http://localhost:4001/intern/rpc\" (make-instance 'hello-world-rpc))\n"
  },
  {
    "path": "src/core/rpc/test-rpc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/rpc/test-rpc\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:core/rpc/rpc\n                #:%port\n                #:id-to-ip\n                #:rpc-auth-id\n                #:rpc-authentication-failed\n                #:authenticate-rpc-request)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:bknr.datastore\n                #:store-object-id))\n(in-package :core/rpc/test-rpc)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (unwind-protect\n       (with-test-store ()\n         (with-fake-request ()\n           (&body)))\n    (makunbound 'server::*multi-acceptor*)))\n\n(defun make-authorization (key pass)\n  `((:authorization . ,(format nil \"Basic ~a\"\n                               (base64:string-to-base64-string (format nil \"~a:~a\" key pass))))))\n\n(test authenticate-rpc ()\n  (with-fixture state ()\n    (signals rpc-authentication-failed\n      (authenticate-rpc-request (make-instance 'hunchentoot:request\n                                               :uri \"foo\"\n                                               :headers-in (make-authorization \"foo\" \"bar\"))))\n    (let ((auth-id (make-instance 'rpc-auth-id\n                                  :secret \"bleh\")))\n\n     (finishes\n       (authenticate-rpc-request\n        (make-instance 'hunchentoot:request\n                       :uri \"foo\"\n                       :headers-in (make-authorization (store-object-id auth-id)\n                                                       \"bleh\"))))\n      (signals rpc-authentication-failed\n        (authenticate-rpc-request\n         (make-instance 'hunchentoot:request\n                        :uri \"foo\"\n                        :headers-in (make-authorization (store-object-id auth-id)\n                                                        \"car\")))))))\n\n(test id-to-ip\n  (is (equal \"172.30.1.199\" (id-to-ip \"172.30.1.199:7070:0:0\")))\n  (is (equal \"[::1]\" (id-to-ip \"[::1]:7070:0:0\"))))\n\n(test %port\n  (with-fixture state ()\n    (progn\n      (setf server::*multi-acceptor*\n            (make-instance 'hunchentoot:easy-acceptor\n                           :port 8080))\n      (is (= 8080 (%port))))))\n\n(test %port-without-multi-acceptor\n  (with-fixture state ()\n    (is (= 5001 (%port :default-port 5001)))\n    (is (= 4001 (%port)))))\n\n\n"
  },
  {
    "path": "src/core/ui/assets/README",
    "content": "These assets are currently served by fonts-acceptor-mixin. If you want\nto add other shared assets here, you might want to modify and rename\nthat mixin."
  },
  {
    "path": "src/core/ui/assets.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/assets\n  (:use #:cl)\n  (:export\n   #:*asset-list*\n   #:ensure-asset\n   #:define-css\n   #:handle-asdf-output\n   #:define-js))\n(in-package :core/ui/assets)\n\n(defvar *lock* (bt:make-lock \"assets-lock\"))\n\n(defvar *asset-list* nil\n  \"This is used in the desktop app, to precompute assets into memory.\n\nIn other situations, you can use this to iterate through all the\npossible assets.\")\n\n(defun ensure-asset (system)\n  \"Just adds the system to the list of asset-cache. Note that this not\ncause the asset to be immediately compiled.\"\n  (pushnew system *asset-list*))\n\n(defmacro handle-asdf-output (op component &optional (output-num 0))\n  (let ((output-files (eval `(util:relative-output-files ,op (asdf:find-component ,component nil)))))\n    `(%handle-asdf-output-v2\n      ,op\n      ,component\n      ',output-files\n      ,output-num)))\n\n(defmacro define-css (class uri asdf-target)\n  \"Defines a CSS asset for the acceptor of type CLASS\"\n  (let ((map-uri (format nil \"~a.map\" uri)))\n    `(progn\n       (ensure-asset ,asdf-target)\n       (hex:def-clos-dispatch ((self ,class) ,uri) ()\n         (setf (hunchentoot:content-type*)  \"text/css; charset=utf-8\")\n         (handle-asdf-output 'asdf:compile-op  ,asdf-target))\n       (hex:def-clos-dispatch ((self ,class) ,map-uri) ()\n         (handle-asdf-output 'asdf:compile-op ,asdf-target 1)))))\n\n(defmacro define-js (class url system)\n  (let ((map-url (format nil \"~a.map\" url)))\n    `(progn\n       (ensure-asset ,system)\n       (hex:def-clos-dispatch ((self ,class) ,url) ()\n         (setf (hunchentoot:content-type*) \"application/javascript\")\n         (setf (hunchentoot:header-out :x-sourcemap) ,map-url)\n         (handle-asdf-output 'asdf:compile-op ,system))\n       (hex:def-clos-dispatch ((self ,class) ,map-url) ()\n         (handle-asdf-output 'asdf:compile-op ,system 1)))))\n\n\n\n(defun %handle-asdf-output-v2 (op\n                                   component\n                                   output-files\n                                   output-num )\n  (bt:with-lock-held (*lock*)\n    (let ((output (elt output-files output-num)))\n      (when (or\n             #+lispworks\n             (not (hcl:delivered-image-p))\n             ;; in case we delete ~/.cache\n             (not (path:-e output)))\n        (asdf:operate op component))\n      (assert (path:-e output))\n      (handler-case\n          (hunchentoot:handle-static-file output)\n        #+lispworks\n        (comm:socket-io-error (e)\n          (values))))))\n"
  },
  {
    "path": "src/core/ui/core.ui.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :core.ui\n  :serial t\n  :depends-on (:markup\n               :trivial-garbage\n               :parenscript\n               :util/timeago\n               :core.installation\n               :hunchentoot-extensions\n               :hunchentoot\n               :util\n               :fset)\n  :components ((:file \"template\")\n               (:file \"post\")\n               (:file \"assets\")\n               (:file \"image\")\n               (:file \"simple-card-page\")\n               (:file \"mdi\")\n               (:file \"paginated\")\n               (:file \"taskie\")\n               (:file \"fonts\")\n               (:file \"nibble-span\")\n               (:file \"left-side-bar\")))\n\n(defsystem :core.ui/tests\n  :serial t\n  :depends-on (:core.ui\n               :fiveam-matchers\n               :util/fiveam)\n  :components ((:file \"test-paginated\")\n               (:file \"test-taskie\")))\n"
  },
  {
    "path": "src/core/ui/fonts.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/fonts\n  (:use #:cl)\n  (:export\n   #:fonts-acceptor-mixin))\n(in-package :core/ui/fonts)\n\n(defclass fonts-acceptor-mixin ()\n  ()\n  (:documentation \"Provides access to our shared fonts\"))\n\n(defvar *prefix* \"/assets/fonts/\")\n\n(defvar *fonts-dir*\n  \"src/core/ui/assets/fonts/\")\n\n(defmethod hunchentoot:acceptor-dispatch-request ((self fonts-acceptor-mixin) request)\n  (cond\n    ((str:starts-with-p *prefix*\n                        (hunchentoot:script-name*))\n     (let ((font-name (str:substring (length *prefix*) nil (hunchentoot:script-name*))))\n       (assert (not (str:starts-with-p \"/\" font-name)))\n       (assert (not (str:containsp \"..\" font-name)))\n       (assert (not (str:containsp \"~\" font-name)))\n       (assert (not (str:containsp \"\\\\\" font-name)))\n       (assert (not (str:containsp \"//\" font-name)))\n       (assert (path:-d *fonts-dir*))\n\n       (hunchentoot:handle-static-file\n        (path:catfile *fonts-dir* font-name))))\n    (t\n     (call-next-method))))\n"
  },
  {
    "path": "src/core/ui/image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/image\n  (:use #:cl)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors))\n(in-package :core/ui/image)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *dim-cache* (make-mp-hash-table :test #'equal)\n  \"Cache of image dimensions for every possible image.\")\n\n(defun image-dimensions (url)\n  (when (str:starts-with-p \"/assets/\" url)\n    (multiple-value-bind (result present-p)\n        (gethash url *dim-cache*)\n      (cond\n        (present-p\n         result)\n        (t\n         (setf (gethash url *dim-cache*)\n               (ignore-and-log-errors ()\n                 (let ((file-path (path:catfile\n                                   (hunchentoot:acceptor-document-root hunchentoot:*acceptor*)\n                                   (str:substring 1 nil url))))\n                   (when (probe-file file-path)\n                     (let ((output (uiop:run-program \n                                    (list \"magick\" \"identify\" \"-format\" \"%w %h\" (namestring file-path))\n                                    :output :string\n                                    :error-output nil\n                                    :ignore-error-status t)))\n                       (when (and output (not (str:emptyp output)))\n                         (mapcar #'parse-integer (str:split \" \" (str:trim output))))))))))))))\n\n\n(markup:deftag img-with-fallback (&key class src alt loading)\n  (assert (str:containsp \".webp\" src))\n  (let ((dims (image-dimensions src)))\n    <picture class=class >\n      <source srcset= (util.cdn:make-cdn src) />\n      <:img src= (util.cdn:make-cdn (str:replace-all \".webp\" \".png\" src))\n            width= (first dims)\n            height= (second dims)\n            loading=loading\n            alt=alt />\n    </picture>))\n"
  },
  {
    "path": "src/core/ui/left-side-bar.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/left-side-bar\n  (:use #:cl)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:core/ui/image\n                #:img-with-fallback)\n  (:export\n   #:left-side-bar-container))\n(in-package :core/ui/left-side-bar)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(deftag left-nav-item (children &key href image-class target\n                       (script-name (error \"need script-name\")))\n  (declare (ignore target))\n  (let ((activep (str:starts-with-p href script-name)))\n    <li class= \"nav-item\"  >\n      <a href= href class= (format nil \"nav-link ~a text-dark\" (if activep \"active\" \"\")) >\n        <mdi name=image-class />\n        <span class= \"text\">,@children </span>\n      </a>\n    </li>))\n\n(deftag left-side-bar-container (children &key logo-small-src\n                                 logo-src\n                                 logo-alt)\n    <div class=\"d-flex flex-column leftside-menu collapse\" >\n\n    <div class= \"text-center p-3\" >\n      <button type= \"button\" href= \"#\" class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\".leftside-menu\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n        <mdi name= \"menu\" />\n      </button>\n\n      <a href=\"/runs\" class=\"align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none\">\n\n\n        <span class=\"logo logo-lg fs-4\">\n          <img src= logo-src\n                             alt= logo-alt\n                             loading= \"lazy\" />\n        </span>\n        <span class=\"logo logo-sm fs-4 ms-2\">\n          <img src= logo-small-src\n               alt= logo-alt\n               loading= \"lazy\" />\n        </span>\n      </a>\n    </div>\n  <hr class= \"mt-0\" />\n\n  ,@children\n  </div>)\n"
  },
  {
    "path": "src/core/ui/mdi.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/mdi\n  (:use #:cl)\n  (:export\n   #:mdi))\n(in-package :core/ui/mdi)\n\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag mdi (&key name class style)\n  <i class= (format nil \"material-icons ~a\" class) style=style >,(progn name)</i>)\n"
  },
  {
    "path": "src/core/ui/nibble-span.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/nibble-span\n  (:use #:cl))\n(in-package :core/ui/nibble-span)\n\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag nibble-span (&key nibble)\n  (let ((id (format nil \"a~a\" (random 1000000000))))\n    <markup:merge-tag>\n      <span id=id data-nibble-span=nibble class= \"nibble-span\" >\n      </span>\n    </markup:merge-tag>))\n\n"
  },
  {
    "path": "src/core/ui/paginated.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/paginated\n  (:use #:cl)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:export\n   #:paginated\n   #:apply-map-filter))\n(in-package :core/ui/paginated)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *filter-cache* (trivial-garbage:make-weak-hash-table\n                        :weakness :value\n                        :test #'equal\n                        #+sbcl\n                        :synchronized #+sbcl t))\n\n(defun apply-map-filter (map filter)\n  (util:or-setf\n   (gethash (cons map filter) *filter-cache*)\n   (cond\n     ((eql #'identity filter)\n      map)\n     (t\n      (fset:filter (lambda (x v)\n                     (declare (ignore v))\n                     (funcall filter x))\n                   map)))))\n\n(defun get-first-n (iterator num &optional res)\n  \"Returns two values: the first num elements returned as a list, and a\nfunctional iterator to the rest\"\n  (flet ((return-res (iterator)\n           (let ((x (reverse res)))\n             (values (mapcar #'first x)\n                     iterator\n                     (mapcar #'second x)))))\n   (cond\n     ((<= num 0)\n      (return-res iterator))\n     (t\n      (multiple-value-bind (next next-iter) (funcall iterator)\n        (cond\n          ((null next)\n           (return-res nil))\n          (t\n           (get-first-n next-iter (1- num) (list* (list next iterator) res)))))))))\n\n(defun pagination-helper (&key\n                           (filter #'identity)\n                           (empty-view)\n                           (num 24)\n                           (items nil)\n                           (start-counter 0)\n                           (iterator nil)\n                           renderer)\n  \"The renderer here is a function that takes three arguments: the list of objects to render,\n a lambda that calls pagination-helper on the remaining objects with\n the same arguments, and the start-counter of the first element.\n\nFor a map, the filter filters on keys, not on values.\n\nThe pagination-helper doesn't handle rendering on its own, for testability purposes.\"\n  (multiple-value-bind (this-page rest iterators)\n      (cond\n        (iterator\n         (get-first-n iterator num))\n        ((fset:map? items)\n         (let ((items (apply-map-filter items filter)))\n          (values\n           (loop for i below num\n                 for j from start-counter below (fset:size items)\n                 collect (multiple-value-bind (key value)\n                             (fset:at-rank items j)\n                           (cons key value)))\n           ;; We can use the same map for the rest, since we're\n           ;; indexing by numbers.\n           (when (< (+ start-counter num) (fset:size items))\n             items))))\n        (t\n         (util/misc/lists:head items num :filter filter)))\n    (cond\n      (this-page\n       (let* ((load-more (when rest\n                           (lambda ()\n                             (pagination-helper\n                              :num num :items (if iterator nil rest)\n                              :start-counter (+ start-counter (length this-page))\n                              :iterator (if iterator rest nil)\n                              :filter filter\n                              :renderer renderer)))))\n         (funcall renderer this-page load-more start-counter :iterators iterators)))\n      (t\n       empty-view))))\n\n(defun paginated (fn &rest args &key pass-index-p (infinite-scroll nil) &allow-other-keys)\n  \"Creates a paginated view. If no element matches, then we return\nNIL, which can be used as a way of determining whether to render an\nempty message.\"\n  (let ((fn (if pass-index-p\n                fn\n                (lambda (name i &rest args)\n                  (declare (ignore i))\n                  (apply fn name\n                         args)))))\n   (apply #'pagination-helper\n          :renderer (lambda (this-page load-more start-counter &key iterators)\n                      <div class= \"row pb-4 load-more-container\" >\n                      ,@(loop for page in this-page\n                              for idx from 0\n                              for i from start-counter\n                              collect (apply fn page i\n                                             (when iterators\n                                               (list :iterator\n                                                (elt iterators idx)))))\n\n                      ,(when load-more\n                         <div class= \"col-12 d-flex justify-content-center\">\n                         <button class= \"btn btn-primary load-more-button\"\n                                 data-load-more= (nibble (:name (if infinite-scroll\n                                                                 :load-more-inf\n                                                                 :load-more))\n                                                    (funcall load-more))\n                                 data-infinite= (if infinite-scroll \"true\")\n                                 >Load More</button>\n                         </div>)\n                      </div>)\n          (remove-from-plist args :pass-index-p :infinite-scroll))))\n"
  },
  {
    "path": "src/core/ui/post.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/post\n  (:use #:cl)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:parenscript\n                #:ps)\n  (:export\n   #:post-a))\n(in-package :core/ui/post)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag post-a (children &key href class action title)\n  (let ((form-id (format nil \"post-a-~a\" (random 10000000000000000000))))\n    <form action=action method= \"POST\" id=form-id class= \"d-inline-block\" >\n      <a href=href class=class title=title\n         onclick= (ps (submit (get-parent-element this))) >,@ (progn children)</a>\n    </form>))\n"
  },
  {
    "path": "src/core/ui/simple-card-page.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :core/ui/simple-card-page\n  (:use #:cl #:alexandria)\n  (:import-from #:core/ui/template\n                #:app-template)\n  (:export #:simple-card-page\n           #:confirmation-page\n           #:confirmation-modal))\n(in-package :core/ui/simple-card-page)\n\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag simple-card-page (children &key (col-class \"col-lg-4 col-md-8\")\n                                 title\n                                 (max-width \"30rem\")\n                                 script-name\n                                 stripe\n                                 form-action\n                                 form-id)\n  (let* ((children (remove-if 'stringp children))\n         (footer (if (mquery:has-class-p (last children) \"card-footer\")\n                     (car (last children))\n                     nil))\n         (children (if footer\n                       (butlast children)\n                       children))\n         (header (if (mquery:has-class-p (first children) \"card-header\")\n                     (car children)\n                     nil))\n         (children (if header\n                       (cdr children)\n                       children))\n         (inner (progn\n                  <div class= \"card\" >\n                    ,(progn header)\n                    <div class= \"card-body\">\n                      ,@children\n                    </div>\n                    ,(progn footer)\n                  </div>)))\n    <app-template title=title stripe=stripe >\n      <div class= \"main-content\" >\n        <div class= \"card-page-container mt-3 mx-auto\"\n             style= (when max-width\n                     (format nil \"max-width: ~a\" max-width)) >\n          ,(cond\n             (form-action\n              <form action=form-action id=form-id method= \"POST\" >\n                ,(progn inner)\n              </form>)\n             (t\n              inner))\n        </div>\n      </div>\n    </app-template>))\n\n\n(markup:deftag confirmation-page (children &key  yes no\n                                  (danger nil))\n  <simple-card-page>\n    <p>,@(progn children)</p>\n    <div class= \"card-footer\">\n      <a href= yes class= (format nil \"btn ~a\" (if danger \"btn-danger\" \"btn-primary\")) >Yes</a>\n      <a href= no class= \"btn btn-secondary\">No</a>\n    </div>\n  </simple-card-page>)\n\n(markup:deftag confirmation-modal (children &key yes title)\n  (let ((id (format nil \"a-~a\" (random 1000000))))\n    <div class= \"modal fade\" id=id tabindex= \"-1\" role= \"dialog\">\n      <div class= \"modal-dialog\" role= \"dialog\">\n        <div class= \"modal-content\">\n\n          ,(when title\n          <div class= \"modal-header\">\n            <strong>,(progn title)</strong>\n          </div>)\n\n    <div class= \"modal-body\">\n      ,@children\n    </div>\n\n    <div class= \"modal-footer\">\n      <form action=yes method= \"post\">\n        <input type= \"submit\" value= \"Yes\" class= \"btn btn-primary\" />\n      </form>\n      <input type= \"button\" class= \"btn btn-secondary\" value= \"No\"\n             data-bs-dismiss= \"modal\"\n             data-bs-target= (format nil \"#~a\" id) />\n    </div>\n        </div>\n      </div>\n    </div>))\n"
  },
  {
    "path": "src/core/ui/taskie.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/taskie\n  (:use :cl)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:markup/markup\n                #:deftag\n                #:unescaped)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/misc/lists\n                #:head)\n  (:import-from #:util/store/object-id\n                #:object-with-oid\n                #:oid)\n  (:import-from #:util/timeago\n                #:timeago)\n  (:export\n   #:taskie-list\n   #:taskie-page-item\n   #:taskie-page-title\n   #:taskie-row\n   #:taskie-timestamp\n   #:timeago\n   #:with-pagination))\n(in-package :core/ui/taskie)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag taskie-page-title (children &key title class)\n  <div class= (format nil \"page-title-box main-content ~a\" class) >\n    <h4 class= \"page-title\" >,(progn title)\n    </h4>\n\n    <div class= \"float-end\">\n      ,@children\n    </div>\n  </div>)\n\n(deftag taskie-page-item (children &key href)\n  <li class= (format nil \"page-item ~a\" (unless href \"disabled\"))>\n    <a class= \"page-link\" href=href >\n      ,@children\n    </a>\n  </li>)\n\n(defvar *checkboxes*)\n\n(deftag taskie-list (children &key empty-message items row-generator\n                     headers\n                     class\n                     (checkboxes t)\n                     next-link\n                     prev-link)\n  <markup:merge-tag>\n  <table class= (format nil \"main-content taskie-list mt-3 mb-3 ~a ~a ~a\"\n                      (if items\n                          \"nonempty\"\n                          \"empty\")\n                      (if checkboxes \"checkboxes\")\n                      class) >\n\n      <thead>\n        <tr>\n          ,(when checkboxes\n             <th>\n             </th>)\n\n          ,@ (loop for header in headers\n                   collect <th>,(progn header)</th>)\n        </tr>\n      </thead>\n      <tbody>\n\n      ,@ (cond\n           (items\n            (loop for item in items collect\n                  (let ((*checkboxes* checkboxes))\n                   (funcall row-generator item))))\n           (t\n            (list\n             <tr class= \"empty\" >\n               <td colspan= \"100%\" class= \"text-center pt-3 pb-3 text-muted\" >\n                 ,(progn empty-message)</td></tr>)))\n      </tbody>\n\n  </table>\n  ,(when (or prev-link next-link)\n       <nav aria-label=\"Page navigation\" class= \"mt-3\" >\n         <ul class=\"pagination justify-content-center\">\n           <taskie-page-item href=prev-link >Previous</taskie-page-item>\n           <taskie-page-item href=next-link >Next</taskie-page-item>\n  </ul>\n</nav>\n)\n           </markup:merge-tag>)\n\n(defvar *id-counter* 0)\n\n(deftag taskie-row (children &key object)\n  (let ((children (remove-if 'stringp children))\n        (id-name (format nil \"check-~a\"(incf *id-counter*))))\n    <tr>\n      ,(when *checkboxes*\n         <td>\n           <div class=\"form-check\">\n             <input type=\"checkbox\" class=\"recent-run-item form-check-input\"\n             id=id-name data-model-id= (if (typep object 'object-with-oid) (ignore-errors (oid object)) (store-object-id object)) />\n        </div> <!-- end checkbox -->\n\n      </td>)\n\n      ,@ (loop for child in children\n               collect <td>,(progn child)</td>)\n    </tr>))\n\n(deftag taskie-timestamp (&key prefix timestamp)\n\n    <span class= \"taskie-timestamp\" >\n      <mdi name= \"today\" />\n      ,(progn prefix)\n      <timeago timestamp=timestamp />\n    </span>)\n\n(defmethod head-and-tail (elements n)\n  (head elements n))\n\n\n(defmethod head-and-tail ((s fset:wb-set) n)\n  \"Our sorting order on datastore objects is by store object ids. This\nmeans, that the newest elements are the greatest elements in our set.\"\n  (labels ((build-page (s n so-far)\n             (cond\n               ((or (fset:empty? s) (= n 0))\n                (values (nreverse so-far) s))\n               (t\n                (let ((next (fset:greatest s)))\n                  (build-page\n                   (fset:less s next)\n                   (1- n)\n                   (list* next so-far)))))))\n    (build-page s n nil)))\n\n(defmethod head-and-tail ((map fset:wb-map) n)\n  \"This is similar to set, but we treat the key as an ascending ID. So\nwe'll return the last N values, and the remaining map as a tail.\"\n  (let ((tail map)\n        (head nil))\n    (loop for i from 0 below n\n          while (not (fset:empty? tail))\n          do\n          (let* ((next-key (fset:greatest tail))\n                 (next-value (fset:lookup tail next-key)))\n            (push next-value head)\n            (setf tail (fset:less tail next-key))))\n    (values (nreverse head) tail)))\n\n(defun empty-data-p (next-page-data)\n  \"Either nil, or an empty set\"\n  (or\n   (null next-page-data)\n   (and\n      (fset:set? next-page-data)\n      (fset:empty? next-page-data))))\n\n(defun %with-pagination (data body &key prev)\n  (let ((n 50))\n    (multiple-value-bind (this-page next-page-data)\n        (head-and-tail data n)\n     (let* ((this (nibble ()\n                    (%with-pagination data body\n                                      :prev prev)))\n            (next (unless (empty-data-p next-page-data)\n                    (nibble ()\n                      (%with-pagination next-page-data body\n                                        :prev this)))))\n       (funcall body this-page next prev)))))\n\n(defmacro with-pagination ((page data &key (next-link (gensym \"NEXT-LINK\"))\n                                        (prev-link (gensym \"PREV-LINK\")))\n                           &body body)\n  `(flet ((body (,page ,next-link ,prev-link) ,@body))\n     (%with-pagination ,data #'body)))\n"
  },
  {
    "path": "src/core/ui/template.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/template\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:*app-template*\n   #:app-template))\n(in-package :core/ui/template)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass base-template ()\n  ())\n\n(defvar *app-template* (make-instance 'base-template))\n\n(defgeneric render-template (template children &key &allow-other-keys)\n  (:method ((self base-template) children &key &allow-other-keys)\n    <html>\n      <body>\n        ,@children\n      </body>\n    </html>))\n\n(markup:deftag app-template (children &key title stripe)\n  (render-template\n   *app-template*\n   children\n   :title title\n   :stripe stripe))\n"
  },
  {
    "path": "src/core/ui/test-paginated.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/test-paginated\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/ui/paginated\n                #:paginated\n                #:pagination-helper)\n  (:shadowing-import-from #:new-let\n                          #:let)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:equal-to\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains))\n(in-package :core/ui/test-paginated)\n\n(util/fiveam:def-suite)\n\n(defun %pagination-helper (&rest args)\n  \"A version of pagination helper that instead of calling the renderer,\njust returns three values: the list of objects and a lambda to return\nthe next list of objects and the start-index\"\n  (apply #'pagination-helper\n         :renderer (lambda (objects next start-counter &key iterators)\n                     (declare (ignore iterators))\n                     (values objects next start-counter))\n         :empty-view :empty\n         args))\n\n(defun make-test-list (num)\n  (loop for i from 0 below num\n        collect (format nil \"~a\" i)))\n\n(defun make-test-iterator (num &key (from 0))\n  (cond\n    ((= from num)\n     (lambda () nil))\n    (t\n     (lambda ()\n       (values (format nil \"~a\" from) (make-test-iterator num :from (1+ from)))))))\n\n(defun make-test-map (num)\n  (fset:convert\n   'fset:map\n   (loop for i from 0 below num\n         collect (cons\n                  (format nil \"~a\" i)\n                  (+ 100 i)))))\n\n(test empty-view\n  (is (eql :empty (%pagination-helper :num 5\n                                      :items (make-test-list 0))))\n  (is (not (eql :empty (%pagination-helper :num 5\n                                           :items (make-test-list 3)))))\n  (is (not (eql :empty (%pagination-helper :num 5\n                                           :items (make-test-list 13)))))\n  (is (eql :empty (%pagination-helper :num 5\n                                      :filter (lambda (x) nil)\n                                      :items (make-test-list 13))))\n  (assert-that (%pagination-helper :num 5\n                                   :filter (lambda (x)\n                                             (string= x \"3\"))\n                                   :items (make-test-list 13))\n               (contains\n                (equal-to \"3\"))))\n\n(test more-link\n  (let ((items more (%pagination-helper :num 3\n                                        :items (make-test-list 7))))\n    (assert-that items\n                 (contains \"0\" \"1\" \"2\"))\n    (let ((items more (funcall more)))\n      (assert-that items (contains \"3\" \"4\" \"5\"))\n      (let ((items more (funcall more)))\n        (assert-that items (contains \"6\"))\n        (is (null more))))))\n\n(test more-link-with-filter\n  (let ((items more (%pagination-helper :num 3\n                                        :filter (lambda (x)\n                                                  (< (parse-integer x) 7))\n                                        :items (make-test-list 10))))\n    (assert-that items\n                 (contains \"0\" \"1\" \"2\"))\n    (let ((items more (funcall more)))\n      (assert-that items (contains \"3\" \"4\" \"5\"))\n      (let ((items more (funcall more)))\n        (assert-that items (contains \"6\"))\n        (is (null more))))))\n\n(test more-link-with-maps\n  (let ((items more (%pagination-helper :num 3\n                                        :items (make-test-map 7))))\n    (assert-that items\n                 (contains (cons \"0\" 100) (cons \"1\" 101) (cons \"2\" 102)))\n    (let ((items more (funcall more)))\n      (assert-that items (contains (cons \"3\" 103) (cons \"4\" 104) (cons \"5\" 105)))\n      (let ((items more (funcall more)))\n        (assert-that items (contains (cons \"6\" 106)))\n        (is (null more))))))\n\n(test more-link-with-filtered-maps\n  (let ((items more (%pagination-helper :num 3\n                                        :filter (lambda (x)\n                                                  (< (parse-integer x) 7))\n                                        :items (make-test-map 10))))\n    (assert-that items\n                 (contains (cons \"0\" 100) (cons \"1\" 101) (cons \"2\" 102)))\n    (let ((items more (funcall more)))\n      (assert-that items (contains (cons \"3\" 103) (cons \"4\" 104) (cons \"5\" 105)))\n      (let ((items more (funcall more)))\n        (assert-that items (contains (cons \"6\" 106)))\n        (is (null more))))))\n\n\n(test more-link-with-iterator\n  (let ((items more (%pagination-helper :num 3\n                                        :iterator (make-test-iterator 7))))\n    (assert-that items\n                 (contains \"0\" \"1\" \"2\"))\n    (let ((items more (funcall more)))\n      (assert-that items (contains \"3\" \"4\" \"5\"))\n      (let ((items more (funcall more)))\n        (assert-that items (contains \"6\"))\n        (is (null more))))))\n\n(test happy-path-with-infinite-scroll\n  (paginated (lambda (x))\n             :items (list 1 2 3)\n             :infinite-scroll t))\n"
  },
  {
    "path": "src/core/ui/test-taskie.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :core/ui/test-taskie\n  (:use :cl)\n  (:import-from #:it.bese.fiveam\n                #:finishes\n                #:is\n                #:is-false\n                #:test)\n  (:import-from #:core/ui/taskie\n                #:taskie-list\n                #:taskie-row\n                #:taskie-timestamp\n                #:with-pagination)\n  (:import-from #:util/timeago\n                #:timeago))\n(in-package :core/ui/test-taskie)\n\n(util/fiveam:def-suite)\n\n(defclass my-object ()\n  ((val :initarg :val)))\n\n(defmethod fset:compare ((a my-object)\n                         (b my-object))\n  (fset:compare-slots a  b 'val))\n\n(defmethod bknr.datastore:store-object-id ((obj my-object))\n  (slot-value obj 'val))\n\n(test happy-path\n  (taskie-list :empty-message \"No recent runs to show. But that's okay, it's easy to get started!\"\n               :items (list (make-instance 'my-object :val 1)\n                            (make-instance 'my-object :val 2)\n                            (make-instance 'my-object :val 3))\n               :next-link \"/foo/next\"\n               :prev-link \"/foo/prev\"\n               :row-generator (lambda (x)\n                                (taskie-row\n                                 :object x\n                                 (taskie-timestamp :prefix \"\" :timestamp (local-time:now))))))\n\n(test with-pagination-happy-path\n  (let ((data (loop for i from 1 to 110 collect (make-instance 'my-object :val i))))\n    (with-pagination (page data :next-link next-link :prev-link prev-link)\n      (is (eql 50 (length page))))))\n\n(test with-pagination-happy-path-with-set\n  (let ((data\n          (fset:convert 'fset:set\n                        (loop for i from 1 to 110 collect (make-instance 'my-object :val i)))))\n    (with-pagination (page data :next-link next-link :prev-link prev-link)\n      (is (eql 50 (length page))))))\n\n\n(test with-pagination-empty-list\n  (let ((data nil))\n    (with-pagination (page data :next-link next-link :prev-link prev-link)\n      (is (equal nil page))\n      (is-false next-link)\n      (is-false prev-link))))\n\n(test with-pagination-empty-set\n  (let ((data (fset:empty-set)))\n    (with-pagination (page data :next-link next-link :prev-link prev-link)\n      (is (equal nil page))\n      (is-false next-link)\n      (is-false prev-link))))\n\n(test timeago-happy-path\n  (finishes (timeago :timestamp 40))\n  (finishes (timeago :timestamp (local-time:universal-to-timestamp 40)))\n  (is (equal\n       (markup:write-html\n        (timeago :timestamp 40))\n       (markup:write-html\n        (timeago :timestamp (local-time:universal-to-timestamp 40))))))\n"
  },
  {
    "path": "src/encrypt/encrypt.asd",
    "content": "(defsystem encrypt\n  :serial t\n  :depends-on (:ironclad\n               :secure-random\n               :util.store\n               :cl-intbytes\n               :util.misc\n               :cl-base64)\n  :components ((:file \"encrypt\")\n               (:file \"hmac\")))\n\n(defsystem encrypt/tests\n  :serial t\n  :depends-on (:encrypt\n               :util/fiveam\n               :fiveam-matchers\n               :tmpdir\n               :cl-mongo-id)\n  :components ((:file \"test-encrypt\")\n               (:file \"test-hmac\")))\n"
  },
  {
    "path": "src/encrypt/encrypt.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:encrypt\n  (:use #:cl)\n  (:import-from #:flexi-streams\n                #:octets-to-string)\n  (:export :encrypt\n   :decrypt\n           :encrypt-mongoid\n           :decrypt-mongoid\n           :encrypt-number\n           :decrypt-number))\n\n(in-package #:encrypt)\n\n(defvar *lock* (bt:make-lock))\n\n(defun key-from-disk (filename bytes)\n  (base64:base64-string-to-usb8-array\n   (bt:with-lock-held (*lock*)\n     (let ((file (path:catfile\n                  (cond\n                    ((ignore-errors util/store:*object-store*)\n                     (util/store:object-store))\n                    (t\n                     ;; just for test convenience\n                     (bknr.datastore::store-directory bknr.datastore:*store*)))\n                  filename)))\n       (flet ((read-file () (str:trim\n                             (uiop:read-file-string file))))\n         (cond\n           ((path:-e file)\n            (read-file))\n           (t\n            (let ((string (base64:usb8-array-to-base64-string\n                           (secure-random:bytes bytes secure-random:*generator*))))\n              (with-open-file (stream file :direction :output)\n                (write-string string stream))\n              (read-file)))))))))\n\n(defun blowfish-key ()\n  (key-from-disk \"blowfish-key.txt\" 8))\n\n(defvar *zero-byte* (coerce 0 '(unsigned-byte 8)))\n\n(defvar *blowfish-cipher* nil)\n(defun get-blowfish-cipher ()\n  (util/misc:or-setf\n   *blowfish-cipher*\n   (ironclad:make-cipher 'ironclad:blowfish :mode :ecb :key (blowfish-key))\n   :thread-safe t))\n\n(defvar *aes-128-cipher* nil)\n\n(defun get-aes-128-cipher ()\n  (util/misc:or-setf\n   *aes-128-cipher*\n   (ironclad:make-cipher 'ironclad:aes\n                          :mode :ecb :key (key-from-disk \"aes-128-key.txt\" 16))))\n\n(defun coerce-to-byte-array (a)\n  (coerce a '(vector (unsigned-byte 8))))\n\n(defun add-padding (a)\n  (let ((pad-length 8))\n   (let ((l (length a)))\n     (let ((padding (make-array  (mod (- pad-length (mod l pad-length)) pad-length) :initial-element *zero-byte*)))\n       (coerce-to-byte-array (concatenate 'vector a padding))))))\n\n(defun remove-padding (a)\n  (coerce-to-byte-array (apply 'vector\n                 (loop for c across a\n                    if (not (eq *zero-byte* c))\n                    collect c))))\n\n(defun encrypt (a)\n  (encrypt-array (ironclad:ascii-string-to-byte-array a)))\n\n\n(defun encrypt-array (arr &optional (cipher (get-blowfish-cipher))\n                            (base64-padding 0)\n                            (version 0))\n  (let* ((in-array (add-padding arr))\n         (output (make-array (+\n                              (length in-array)\n                              base64-padding) :element-type '(unsigned-byte 8)\n                                               :initial-element 0)))\n    (multiple-value-bind (consumed produced)\n        (ironclad:encrypt cipher in-array output :padding 0 :handle-final-block t)\n      (assert (= consumed (length in-array)))\n      (assert (= produced (length in-array))))\n\n    (when (> base64-padding 0)\n     (setf (elt output (1- (length output))) version))\n\n    (base64:usb8-array-to-base64-string output :uri t)))\n\n(defun decrypt (a)\n  (let ((in-array (base64:base64-string-to-usb8-array a :uri t)))\n    (ironclad:decrypt-in-place (get-blowfish-cipher) in-array)\n    (map 'string #'code-char (remove-padding in-array))))\n\n(defun decrypt-array (arr length &optional (cipher (get-blowfish-cipher)))\n  \"Decrypt an encoded array. However, the length of the array is not\n  known, so you must provide that as an argument.\"\n  (let ((in-array (base64:base64-string-to-usb8-array arr :uri t)))\n    (ironclad:decrypt-in-place cipher in-array)\n    (subseq in-array 0 length)))\n\n(defconstant +mongoid-length+ 12)\n\n(defconstant +num-max-bytes+ 32)\n\n(defun encrypt-mongoid (mongoid)\n  \"For encrypting mongoids, we use AES-128. Using a 64-bit blowfish key\n can reveal some information that can be used to recreate mongoids,\n even though the likelyhood is really low. Basically the first 8 bytes\n are timestamp and process id, and the last 8 bytes has only about\n 2^24 different values, (assuming the process id remains the same). So\n if you know a timestamp, you can try 16m different second-halfs to\n retrieve another valid mongoid within the same timestamp. You could\n probably be more clever about limiting the search set, or by forcing\n generations of mongoids to get back to a previously known number. But\n again, the likelyhood is very low.\n\n Encrypting the 12 bytes with a 16-byte cipher will result in a 16-byte\n output. Base64 encoding this will require 4/3*18 = 24 bytes. This\n means we have two extra bytes to store an unencryption version\n information of the encoding. If we see padding characters `..`\n instead we assuming it's the v0 blowfish encryption.\n \"\n  (when (stringp mongoid)\n    (error \"Please provide the mongoid as an array\"))\n  (unless (= +mongoid-length+ (length mongoid))\n    (error \"Invalid mongoid length\"))\n  (encrypt-array mongoid (get-aes-128-cipher) 2 1))\n\n(defun decrypt-mongoid (encrypted-mongoid)\n  (cond\n    ((and\n      (= 24 (length encrypted-mongoid))\n      (eql #\\. (elt encrypted-mongoid 23)))\n     (decrypt-array  encrypted-mongoid +mongoid-length+\n                    (get-blowfish-cipher)))\n    (t\n     (decrypt-array encrypted-mongoid +mongoid-length+\n                    (get-aes-128-cipher)))))\n\n\n(defun encrypt-number (x)\n  (assert (< x (ash 1 (* 8 +num-max-bytes+))))\n  (format nil \"encn_~a\"\n          (encrypt-array (cl-intbytes:int->octets x +num-max-bytes+)\n                         (get-aes-128-cipher)\n                         +num-max-bytes+)))\n\n(defun decrypt-number (encrypted-number)\n  (assert (str:starts-with-p \"encn_\" encrypted-number))\n  (let ((base64 (str:substring 5 nil encrypted-number)))\n    (cl-intbytes:octets->int\n     (decrypt-array base64 +num-max-bytes+\n                    (get-aes-128-cipher))\n     +num-max-bytes+)))\n"
  },
  {
    "path": "src/encrypt/hmac.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :encrypt/hmac\n  (:use #:cl)\n  (:import-from #:encrypt\n                #:key-from-disk)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/misc\n                #:or-setf)\n  (:export\n   #:sign-hmac\n   #:verify-hmac))\n(in-package :encrypt/hmac)\n\n(defvar *hmac-key* nil)\n\n(defun hmac-key ()\n  (or-setf\n   *hmac-key*\n   (key-from-disk \"sha256-hmac-key.bin\" 64)\n   :thread-safe t))\n\n(defmethod sign-hmac (bytes)\n  (let ((mac (ironclad:make-mac :hmac\n                                (hmac-key)\n                                :sha256)))\n    (ironclad:update-mac mac bytes)\n    (ironclad:produce-mac mac)))\n\n(defmethod sign-hmac ((str string))\n  (sign-hmac (flex:string-to-octets str)))\n\n(defmethod verify-hmac (input hmac)\n  (let ((sign (sign-hmac input)))\n    (equalp sign hmac)))\n\n\n(defmethod encode-signed-string ((str string))\n  \"Returns a string that is signed and timestamped\"\n  (let* ((ts (get-universal-time))\n         (sign (sign-hmac (format nil \"~a.~a\" str ts))))\n    (json:encode-json-to-string\n     `((:input . ,str)\n       (:ts . ,ts)\n       (:sign . ,(ironclad:byte-array-to-hex-string sign))))))\n\n(define-condition invalid-signature-error (error)\n  ())\n\n(define-condition invalid-signature-timestamp (error)\n  ())\n\n(defmethod decode-signed-string ((str string))\n  (let ((obj (json:decode-json-from-string str)))\n    (let ((ts (get-universal-time)))\n      (unless (< (abs (- ts (assoc-value obj :ts))) 300)\n        (error 'invalid-signature-timestamp)))\n    (unless (verify-hmac (format nil \"~a.~a\" (assoc-value obj :input)\n                                 (assoc-value obj :ts))\n                         (ironclad:hex-string-to-byte-array\n                          (assoc-value obj :sign)))\n      (error 'invalid-signature-error))\n    (assoc-value obj :input)))\n"
  },
  {
    "path": "src/encrypt/test-encrypt.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :encrypt/test-encrypt\n  (:use #:cl\n        #:fiveam\n        #:encrypt)\n  (:import-from #:encrypt\n                #:add-padding\n                #:encrypt-number\n                #:*aes-128-cipher*\n                #:get-blowfish-cipher\n                #:*blowfish-cipher*\n                #:blowfish-key)\n  (:import-from #:fiveam-matchers/core\n                #:equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:starts-with)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :encrypt/test-encrypt)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key dir)\n  (tmpdir:with-tmpdir (tmpdir)\n    (let ((util/store:*object-store* (namestring (or dir tmpdir))))\n      (let ((old-cipher *blowfish-cipher*)\n            (old-aes-cipher *aes-128-cipher*))\n        (unwind-protect\n             (progn\n               (setf *blowfish-cipher* nil)\n               (setf *aes-128-cipher* nil)\n               (&body))\n          (setf *blowfish-cipher* old-cipher)\n          (setf *aes-128-cipher* old-aes-cipher))))))\n\n(test blowfish-key-happy-path\n  (with-fixture state ()\n    (is (arrayp (blowfish-key)))))\n\n(test blowfish-key-is-always-the-same\n  (with-fixture state ()\n    (is (equalp (blowfish-key)\n                (blowfish-key)))))\n\n(test multithreaded-key-is-always-the-same\n  (with-fixture state ()\n   (let ((num-threads 100)\n         (correct 0)\n         (incorrect nil)\n         (lock (bt:make-lock))\n         (store util/store:*object-store*))\n     (let ((encrypted (encrypt \"foobar\")))\n       (flet ((thread-call ()\n                (let* ((util/store:*object-store* store)\n                       (*cipher* nil)\n                       (test-encryption (encrypt \"foobar\")))\n                  (log:info \"Store is: \" util/store:*object-store*)\n                  (cond\n                    ((equal test-encryption encrypted)\n                     (bt:with-lock-held (lock)\n                       (incf correct)))\n                    (t\n                     (bt:with-lock-held (lock)\n                       (push test-encryption incorrect)))))))\n         (thread-call)\n         (thread-call)\n         (let ((threads (loop for i from 2 below num-threads\n                             collect (bt:make-thread\n                                      (lambda ()\n                                        (ignore-errors\n                                         (thread-call)))))))\n           (loop for thread in threads\n                do (bt:join-thread thread))\n           (is (equal nil incorrect))\n           (is (eql num-threads correct))))))))\n\n(test encode-decode\n  (with-fixture state ()\n    (is (not (equal \"foobar\" (encrypt \"foobar\"))))\n    (is (stringp (encrypt \"foobar\")))\n    (is (equal \"foobar\" (decrypt (encrypt \"foobar\"))))))\n\n(test encryption-always-results-in-the-same-output\n  (let ((expected nil))\n    (tmpdir:with-tmpdir (tmp)\n      (with-fixture state (:dir tmp)\n        (setf expected (encrypt \"foobar\")))\n      (with-fixture state (:dir tmp)\n        (is (equal expected (encrypt \"foobar\")))))))\n\n(test padding\n  (with-fixture state ()\n    (is (equal \"foo\" (decrypt (encrypt \"foo\"))))))\n\n(test |we're actually encrypting!|\n  (with-fixture state ()\n    (is (not (equalp\n              (flexi-streams:string-to-octets \"foo\")\n              (subseq (base64:base64-string-to-usb8-array (encrypt \"foo\") :uri t) 0 3))))))\n\n\n(test minimum-encrypted-length\n  (with-fixture state ()\n    (is (<= 8 (length (str:replace-all \"=\" \"\" (encrypt \"fo\")))))))\n\n\n(test encrypt-mongoid\n  (with-fixture state ()\n    (let ((mongoid (mongoid:oid)))\n      (let ((encrypted (encrypt-mongoid mongoid)))\n        (is (stringp encrypted))\n        (is (eql 24 (length encrypted)))\n        (is (equalp mongoid (decrypt-mongoid encrypted)))))))\n\n(test decrypt-old-format\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n      (let ((key (make-array 8 :element-type '(unsigned-byte 8)\n                               :initial-contents #(1 2 3 4 5 6 7 8)))\n            (mongoid (ironclad:hex-string-to-byte-array\n                     \"6335a9540900bb3e0bed4375\")))\n       (cl-mock:if-called\n        'get-blowfish-cipher\n        (lambda ()\n          (ironclad:make-cipher 'ironclad:blowfish\n                                 :mode :ecb\n                                 :key key)))\n        ;; It's okay to remove this next line if we change the format,\n        ;; just keep the test after this.\n        (is (not (equal \"aZ5wagzYQY5Xwt9lowj1JA..\"\n                    (encrypt-mongoid mongoid))))\n        (is (equalp\n             mongoid\n             (decrypt-mongoid \"aZ5wagzYQY5Xwt9lowj1JA..\")))))))\n\n(test add-padding-doesnt-pad-multiples-of-8\n  (let ((input #(64 13 3 0 0 0 0 0 0 0 0 0 0 0 0 0)))\n    (is (eql 16 (length (add-padding input)))))\n  (let ((input #(64 13 3 0 0 0 0 0 0 0 0 0 0 0 0)))\n    (is (eql 16 (length (add-padding input))))))\n;; (length (encrypt-number 200))\n\n(test encrypt-number\n  (with-fixture state ()\n    (let ((encrypted (encrypt-number 200000)))\n      (assert-that\n       encrypted\n       (starts-with \"encn_\"))\n      (assert-that\n       (decrypt-number encrypted)\n       (equal-to 200000)))\n    (let ((encrypted (encrypt-number 200000000000000)))\n      (assert-that\n       encrypted\n       (starts-with \"encn_\"))\n      (assert-that\n       (decrypt-number encrypted)\n       (equal-to 200000000000000)))))\n"
  },
  {
    "path": "src/encrypt/test-hmac.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :encrypt/test-hmac\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:encrypt/hmac\n                #:invalid-signature-error\n                #:encode-signed-string\n                #:decode-signed-string\n                #:verify-hmac\n                #:sign-hmac\n                #:hmac-key)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :encrypt/test-hmac)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test hmac-key-happy-path\n  (with-fixture state ()\n    (finishes (hmac-key))\n    (finishes (hmac-key))))\n\n(test digest-string\n  (with-fixture state ()\n    (assert-that (sign-hmac (flex:string-to-octets \"foobar\"))\n                 (has-length 32))))\n\n(test digest-string\n  (with-fixture state ()\n    (assert-that (sign-hmac \"foobar\")\n                 (has-length 32))))\n\n\n(test verification\n  (with-fixture state ()\n    (let ((signature (sign-hmac \"foobar\"))\n          (sign2 (sign-hmac \"bar\")))\n      (is-true (verify-hmac \"foobar\" signature))\n      (is-false (verify-hmac \"foobar\" sign2)))))\n\n\n(test encode-signed-string\n  (with-fixture state ()\n    (is (equal \"foobar\"\n               (decode-signed-string (encode-signed-string \"foobar\"))))\n    (let ((obj (str:replace-all \"bar\" \"car\" (encode-signed-string \"foobar\"))))\n      (signals invalid-signature-error\n        (decode-signed-string obj)))))\n"
  },
  {
    "path": "src/file-lock/.circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    docker:\n      - image: cimg/base:2021.04\n    steps:\n      - checkout\n      - run:\n          name: Install SBCL\n          command: sudo apt-get update && sudo apt-get install -y sbcl\n      - run:\n          name: Install quicklisp\n          command: |\n            curl -O https://beta.quicklisp.org/quicklisp.lisp\n            sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)'\n      - run:\n          name: Run tests\n          command: sbcl --script run-circleci.lisp\n"
  },
  {
    "path": "src/file-lock/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019-Present Modern Interpreters Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "src/file-lock/README.md",
    "content": "\n[![tdrhq](https://circleci.com/gh/tdrhq/file-lock.svg?style=shield)](https://app.circleci.com/pipelines/github/tdrhq/file-lock?branch=main)\n\n# file-lock: Simple flock based file locking for CL\n\nThere's not much to this. Currently only supported on POSIX systems\n(so no Windows, though I'll be happy get a PR for that.)\n\nOnly supported on SBCL and Lispworks.\n\nOn any other CL and OS combination, we revert to a noop lock (i.e. we\nwon't throw an error, so beware).\n\nThis uses a polling mechanism. (i.e.,  it doesn't block on\n`flock`. Instead, it keeps polling `flock` in a non-blocking\nmanner. Otherwise the CL thread would be uninterruptable which isn't a\ngreat developer experience.)\n\nThe most common use case is pretty straightforward:\n\n```lisp\n(with-file-lock (:file \"...\")\n  (do-stuff))\n```\n\n## Author\n\nArnold Noronha <arnold@screenshotbot.io>\n"
  },
  {
    "path": "src/file-lock/file-lock.asd",
    "content": "(defsystem :file-lock\n  :description \"File lock library on POSIX systems\"\n  :author \"Arnold Noronha <arnold@screenshotbot.io>\"\n  :license \"MIT License\"\n  :version \"0.0.1\"\n  :serial t\n  :depends-on ((:feature (:not :lispworks) :cffi)\n               :easy-macros\n               :log4cl)\n  :components ((:file \"file-lock\")))\n\n(defsystem :file-lock/tests\n  :serial t\n  :depends-on (:file-lock\n               :fiveam\n               :tmpdir)\n  :components ((:file \"test-file-lock\"\n                :if-feature (:not :windows))))\n"
  },
  {
    "path": "src/file-lock/file-lock.lisp",
    "content": "(defpackage :util/file-lock\n  (:nicknames :file-lock)\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:release-file-lock\n   #:file-lock\n   #:make-file-lock\n   #:with-file-lock))\n(in-package :file-lock)\n\n(defclass file-lock ()\n  ((file :initarg :file\n         :reader filename)\n   (stream :initform nil\n           :accessor file-lock-stream)))\n\n(defconstant lock-sh 1 \"Shared lock.\")\n(defconstant lock-ex 2 \"Exclusive lock.\")\n(defconstant lock-un 8 \"Unlock.\")\n(defconstant lock-nb 4 \"Don't block when locking.\")\n\n;; In theory, this is identical to the cffi:defcfun in the next step.\n;; But, our code is a bit reliant on this and we don't like dealing\n;; making too many FLI changes on our production server. We know the\n;; FLI version works for us, so we're leaving it as is. (PS. If you\n;; the reader are trying to add Windows support, feel free to use\n;; CFFI.)\n#+lispworks\n(fli:define-foreign-function (flock \"flock\")\n    ((fd :int)\n     (operation :int))\n  :result-type :int)\n\n#-lispworks\n(cffi:defcfun (\"flock\" flock) :int\n  (fd :int)\n  (operation :int))\n\n(define-condition lock-not-held (error)\n  ()\n  (:report \"Attempting to unlock a lock that is not being held\"))\n\n(define-condition could-not-get-lock (error)\n  ()\n  (:report \"Could not get lock\"))\n\n(defmethod initialize-instance :after ((self file-lock) &key file\n                                                          (sharedp nil)\n                                                          (acquire t)\n                                                          (timeout 600))\n  (when acquire\n    (acquire-file-lock self\n                       :timeout timeout\n                       :sharedp sharedp)))\n\n(defmethod file-handle ((self file-lock))\n  (let ((stream (file-lock-stream self)))\n    #+lispworks\n    (let ((handle (slot-value stream 'stream::file-handle)))\n      handle)\n    #+sbcl\n    (sb-posix:file-descriptor stream)\n    #- (or sbcl lispworks)\n    (error \"Can't get file-handle on this platform, only SBCL and Lispworks supported. Please send a Pull Request, it shouldn't be too hard:)\")))\n\n(defun get-unix-error ()\n  (or\n   #+(and lispworks linux)\n   (lw:get-unix-error (lw:errno-value))))\n\n(defmethod ensure-stream ((self file-lock))\n  (unless (file-lock-stream self)\n    (setf (file-lock-stream self)\n          (open (ensure-directories-exist\n                 (filename self))\n                :if-does-not-exist :create\n                :if-exists :append\n\n                #+lispworks\n                :external-format\n                #+lispworks\n                :latin-1\n                :direction :output))))\n\n(defmethod acquire-file-lock ((self file-lock) &key (sharedp nil)\n                                                 (timeout 600))\n  (ensure-stream self)\n  (log:info \"Waiting for file lock: ~a (shared: ~a, timeout: ~a)\" (filename self)\n            sharedp timeout)\n  (let ((start-time (get-universal-time)))\n    (loop for res = (flock\n                     (file-handle self)\n                     (logior\n                      (if sharedp lock-sh lock-ex)\n                      lock-nb))\n          for ctr from 0\n          while (< res 0)\n          do (progn\n               (when (> (get-universal-time)\n                        (+ start-time timeout))\n                 (log:info \"Could not get file lock: ~a\" (get-unix-error))\n                 (error 'could-not-get-lock))\n               (when (= 0 (mod ctr 25))\n                 (log:info \"Could not get file lock ~a, will try again on ~a: ~a\"\n                           (get-unix-error)\n                           (bt:current-thread)\n                           (filename self)))\n               (sleep 0.2))\n          finally\n             (progn\n               (log:info \"Got file lock\")))))\n\n(defmethod release-file-lock ((self file-lock))\n  (unless (file-lock-stream self)\n    (error 'lock-not-held))\n  (let ((res (flock\n              (file-handle self)\n              (logior\n               lock-un lock-nb))))\n    (when (< res 0)\n      (error \"Could not unlock: ~a\"\n             (get-unix-error)))\n\n    (close (file-lock-stream self))\n    (setf (file-lock-stream self) nil)))\n\n(defclass noop-file-lock ()\n  ((file :initarg :file)))\n\n(defmethod acquire-file-lock ((self noop-file-lock) &key &allow-other-keys)\n  (values))\n\n(defmethod release-file-lock ((self noop-file-lock))\n  (values))\n\n(defun make-file-lock (&rest args &key (file (error \"must provide filename\"))\n                       &allow-other-keys)\n  (declare (ignorable file))\n  (cond\n    #+(or (not (:or :sbcl :lispworks)) windows)\n    (t\n     (warn \"Lock files not supported on this platform, and the lock will be a no-op.\")\n     (make-instance 'noop-file-lock :file file))\n    (t\n     (apply #'make-instance 'file-lock args))))\n\n(def-easy-macro with-file-lock (&key file &binding lock\n                                     &fn fn)\n  (let ((lock (make-file-lock :file file)))\n    (unwind-protect\n         (fn lock)\n      (release-file-lock lock))))\n"
  },
  {
    "path": "src/file-lock/run-circleci.lisp",
    "content": "(load \"~/quicklisp/setup.lisp\")\n\n(push #P \"./\" asdf:*central-registry*)\n\n(ql:quickload :file-lock/tests)\n\n(unless (fiveam:run-all-tests)\n  (uiop:quit 1))\n\n(uiop:quit 0)\n"
  },
  {
    "path": "src/file-lock/test-file-lock.lisp",
    "content": "(defpackage :file-lock/test-file-lock\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:file-lock\n                #:file-lock\n                #:release-file-lock\n                #:could-not-get-lock\n                #:lock-not-held)\n  (:import-from #:util/file-lock\n                #:with-file-lock))\n(in-package :file-lock/test-file-lock)\n\n(fiveam:def-suite* :file-lock)\n\n(test simple-lock-unlock\n  (tmpdir:with-tmpdir (dir)\n    (let ((lock (make-instance 'file-lock :file (path:catfile dir \"test.lock\"))))\n      (release-file-lock lock)\n      (pass))))\n\n(test simple-macro-use\n  (tmpdir:with-tmpdir (dir)\n    (with-file-lock (:file (path:catfile dir \"test.lock\"))\n      (pass))))\n\n(test second-unlock-errors\n  (tmpdir:with-tmpdir (dir)\n    (let ((lock (make-instance 'file-lock :file (path:catfile dir \"test.lock\"))))\n      (release-file-lock lock)\n      (signals lock-not-held\n        (release-file-lock lock)))))\n\n(test shared-lock-can-be-held-multiple-times\n  (tmpdir:with-tmpdir (dir)\n    (let ((file (path:catfile dir \"test.lock\")))\n     (let ((lock (make-instance 'file-lock :file file\n                                           :sharedp t)))\n       (let ((lock-2 (make-instance 'file-lock :file file\n                                               :sharedp t)))\n         (finishes (release-file-lock lock-2)))\n       (finishes (release-file-lock lock))))))\n\n(test acquire-lock-after-unlocking\n  (tmpdir:with-tmpdir (dir)\n    (let ((file (path:catfile dir \"test.lock\")))\n     (let ((lock (make-instance 'file-lock :file file)))\n       (release-file-lock lock)\n       (let ((lock-2 (make-instance 'file-lock :file file\n                                               :timeout 5)))\n         (release-file-lock lock-2)\n         (pass))))))\n\n(test write-lock-after-shared-lock\n  (tmpdir:with-tmpdir (dir)\n    (let ((file (path:catfile dir \"test.lock\")))\n     (let ((lock (make-instance 'file-lock :file file\n                                           :sharedp t)))\n       (signals could-not-get-lock\n         (make-instance 'file-lock :file file\n                        :timeout -10))\n       (finishes (release-file-lock lock))))))\n"
  },
  {
    "path": "src/gatekeeper/gatekeeper.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :gatekeeper\n  :serial t\n  :depends-on (:bknr.datastore\n               :util.store)\n  :components ((:file \"gatekeeper\")))\n\n(defsystem :gatekeeper/tests\n  :serial t\n  :depends-on (:util/fiveam\n               :gatekeeper)\n  :components ((:file \"test-gatekeeper\")))\n"
  },
  {
    "path": "src/gatekeeper/gatekeeper.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :gatekeeper/gatekeeper\n  (:nicknames :gk)\n  (:use #:cl)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:export\n   #:check\n   #:create\n   #:show\n   #:insert-acl\n   #:enable\n   #:disable\n   #:normalize-object\n   #:allow\n   #:deny\n   #:show-all\n   #:compute-default-value))\n(in-package :gatekeeper/gatekeeper)\n\n(defindex +name-index+\n  'fset-unique-index\n  :slot-name 'name)\n\n(with-class-validation\n  (defclass gatekeeper (store-object)\n    ((name :initarg :name\n           :reader gk-name\n           :index +name-index+\n           :index-reader %gk-with-name\n           :index-values all-gks)\n     (default-value :initform nil\n                    :initarg :default\n                    :accessor gk-default-value)\n     (access-controls :initform nil\n                      :accessor access-controls))\n    (:metaclass persistent-class)))\n\n(defun gk-with-name (name)\n  (%gk-with-name (string name)))\n\n(defun gk-with-name! (name)\n  (let ((ret (gk-with-name name)))\n    (assert ret)\n    ret))\n\n(defmethod compute-default-value (name object)\n  (let ((gk (gk-with-name name)))\n    (when gk\n      (gk-default-value gk))))\n\n(with-class-validation\n  (defclass access-control (store-object)\n    ((type :initarg :type\n           :documentation \"Either :allow or :deny\"\n           :accessor acl-type)\n     (objects :initarg :objects\n              :accessor access-control-objects\n              :documentation \"All the objects on which this ACL applies\")\n     (access-control-comment :initarg :comment\n                             :accessor access-control-comment\n                             :documentation \"Arbitrary comment, typically maniphest task\")\n     (created-at :initarg :created-at))\n    (:metaclass persistent-class)\n    (:default-initargs :created-at (get-universal-time))))\n\n(defun check (name object &key (default nil default-provided-p))\n  (let ((gk (gk-with-name name)))\n    (cond\n      (gk\n       (loop for acl in (access-controls gk)\n             if (member object (access-control-objects acl))\n               return (eql :allow (acl-type acl))\n             finally\n                (return (compute-default-value name object))))\n      (default-provided-p\n       default)\n      (t\n       (compute-default-value name object)))))\n\n(defun create (name &key default)\n  (make-instance\n   'gatekeeper :name (string name) :default default))\n\n(defun enable (name &key (default t))\n  (let ((gk (gk-with-name! name)))\n   (with-transaction ()\n     (setf (gk-default-value gk) default))))\n\n(defun disable (name)\n  (enable name :default nil))\n\n(defun show-gk (gk)\n  (cond\n    (gk\n     (format t \"~a~%\" (gk-name gk))\n     (format t \"  Default value: ~a~%\" (gk-default-value gk))\n     (format t \"  ~a ACL items~%\" (length (access-controls gk)))\n     (loop for acl in (access-controls gk)\n           do\n              (format t \"  | ~a: ~s~%\"\n                      (string (acl-type acl))\n                      (access-control-objects acl))))\n    (t\n     (log:info \"No such gk\"))))\n\n(defun show (name)\n  (let ((gk (gk-with-name (string name))))\n    (show-gk gk)))\n\n(defun show-all ()\n  (loop for gk in (class-instances 'gatekeeper)\n        if (ignore-errors (gk-name gk))\n        do (show-gk gk)))\n\n\n(defmethod normalize-object (obj)\n  obj)\n\n(defmethod normalize-object :around (obj)\n  (let ((ret (call-next-method)))\n    (when (or (null ret) (keywordp ret))\n      (error \"~a could not be normalized\" ret))\n    ret))\n\n(defun push-acl (name type obj &key comment)\n  (let ((gk (gk-with-name! name)))\n    (let ((acl (make-instance 'access-control\n                              :type type\n                              :comment comment\n                              :objects (list (normalize-object obj)))))\n      (with-transaction ()\n\n        (push acl (access-controls gk))))))\n\n(defun allow (name obj &key comment)\n  (push-acl name :allow obj :comment comment))\n\n(defun deny (name obj &key comment)\n  (push-acl name :deny obj :comment comment))\n"
  },
  {
    "path": "src/gatekeeper/test-gatekeeper.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :gatekeeper/test-gatekeeper\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:gatekeeper/gatekeeper\n                #:access-control\n                #:access-controls\n                #:gatekeeper)\n  (:import-from #:bknr.datastore\n                #:with-transaction))\n(in-package :gatekeeper/test-gatekeeper)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key name default)\n  (with-test-store ()\n    (let ((gk (make-instance 'gatekeeper\n                     :name name\n                     :default default)))\n     (let ((obj :dummy-obj))\n       (&body)))))\n\n(test simple-gk-check\n  (with-fixture state ()\n    (is (eql t (gk:check :non-exist obj :default t)))\n    (is (eql nil (gk:check :non-exist obj :default nil)))))\n\n(test gk-if-gk-exists\n  (with-fixture state (:name \"BLEH\" :default t)\n    (is (eql t (gk:check :bleh obj :default nil)))\n    (is (eql t (gk:check :bleh obj :default t)))\n    (with-transaction ()\n     (setf (access-controls gk)\n           (list\n            (make-instance 'access-control\n                           :type :deny\n                           :objects (list obj)))))\n    (is (eql nil (gk:check :bleh obj :default nil)))))\n\n(test gk-if-gk-exists-but-denys-by-default\n  (with-fixture state (:name \"BLEH\" :default nil)\n    (is (eql nil (gk:check :bleh obj :default t)))\n    (is (eql nil (gk:check :bleh obj :default nil)))\n    (with-transaction ()\n     (setf (access-controls gk)\n           (list\n            (make-instance 'access-control\n                           :type :allow\n                           :objects (list obj)))))\n    (is (eql t (gk:check :bleh obj :default nil)))))\n\n(defmethod gk:compute-default-value ((name (eql 'test-gk))\n                                     obj)\n  (= 0 (mod (length obj) 2)))\n\n(test default-value\n  (with-fixture state ()\n    (is (eql t (gk:check 'test-gk \"foobar\")))\n    (is (eql nil (gk:check 'test-gk \"foo\")))\n    ;; but is overridden with :default\n    (is (eql nil (gk:check 'test-gk \"foobar\" :default nil)))\n    (is (eql t (gk:check 'test-gk \"foo\" :default t)))))\n\n(test default-value-when-gk-is-defined\n  (with-fixture state ()\n    (gk:create 'test-gk :default nil)\n    (is (eql t (gk:check 'test-gk \"foobar\")))\n    (is (eql nil (gk:check 'test-gk \"foo\")))\n    ;; and is overridden with :default\n    (is (eql t (gk:check 'test-gk \"foobar\" :default nil)))\n    (is (eql nil (gk:check 'test-gk \"foo\" :default t)))))\n"
  },
  {
    "path": "src/graphs/all.lisp",
    "content": "(uiop:define-package :graphs\n  (:use-reexport :graphs/dfs))\n"
  },
  {
    "path": "src/graphs/dfs.lisp",
    "content": "(defpackage :graphs/dfs\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:dfs-traversal-order\n   #:dfs))\n(in-package :graphs/dfs)\n\n(defun ignore-hook (node)\n  (declare (ignore node)))\n\n(defclass frame ()\n  ((node :initarg :node)\n   (children :initform nil)\n   (state :initform :init)\n   (results :initform nil)))\n\n(defun dfs (root-or-roots\n            &key\n              children\n              (node-test #'eql)\n              (before-hook #'ignore-hook)\n              (after-hook (lambda (node results)\n                            (declare (ignore node results)))))\n  \"A non-recursive DFS that gives the appearance of a recursive DFS\n\n  before-hook is called before recursing into the children, and is\n  called with one argument, the node being processed.\n\n  after-hook is called after recusing into the children, and is called\n  with two arguments, the node being processed, and the list of\n  results from all the children. Note that the latter only makes sense\n  in the case of a DAG. If there are loops, we may not be able to\n  compute the return value for one of the children when the parent is\n  done, and so we'll return nil. Because of how DFS works, in this\n  case, we can't give much guarantees as to which call might result in\n  a nil, and so it's best not to rely on results in a non-DAG.\"\n  (let* ((seen (make-hash-table :test node-test))\n         (cached-result (make-hash-table :test node-test))\n         (roots (if (listp root-or-roots)\n                    root-or-roots\n                    (list root-or-roots)))\n         (children-fn children)\n         (stack nil)\n         (register nil #|A register to store result of last frame|#))\n    (loop for root in roots\n          collect\n          (progn\n            (push (make-instance 'frame :node root) stack)\n            (loop while stack do\n              (let ((frame (car stack)))\n                (with-slots (node children state results) frame\n                  (ecase state\n                    (:init\n                     (cond\n                       ((gethash node seen)\n                        ;; If this is DAG we can guarantee that the\n                        ;; result has been set by this point. If it's\n                        ;; not a DAG, then (gethash node\n                        ;; cached-result) can return nil, because it\n                        ;; may actually be computed at a later\n                        ;; point. e.g. in the graph A -> B -> A, we'll\n                        ;; first call A, then call B. When we call A\n                        ;; the next time, it's in the seen list, but\n                        ;; doesn't have a cached result.\n                        (setf register (gethash node cached-result))\n                        (pop stack))\n                       (t\n                        ;; we haven't seen this yet!\n                        (setf (gethash node seen) t)\n                        (funcall before-hook node)\n                        (setf children (funcall children-fn node))\n                        (setf state :next-child))))\n                    (:next-child\n                     (cond\n                       (children\n                        (push (make-instance 'frame :node (pop children))\n                              stack)\n                        (setf state :wait-child))\n                       (t\n                        ;; we're done\n                        (setf state :finally))))\n                    (:wait-child\n                     (push register results)\n                     (setf state :next-child))\n                    (:finally\n                     (let ((res (funcall after-hook node (nreverse results))))\n                       (setf (gethash node cached-result) res)\n                       (setf register res))\n                     (pop stack))))))\n            register))))\n\n(defun dfs-traversal-order (root-or-roots\n                            &rest dfs-args)\n  (let (before-order\n        after-order)\n    (apply #'dfs root-or-roots\n             :before-hook (lambda (x)\n                            (push x before-order))\n             :after-hook (lambda (x results)\n                          (push x after-order))\n            dfs-args)\n    (values (nreverse before-order)\n            (nreverse after-order))))\n"
  },
  {
    "path": "src/graphs/graphs.asd",
    "content": "(defsystem :graphs\n  :serial t\n  :depends-on (:alexandria)\n  :components ((:file \"dfs\")\n               (:file \"all\")))\n\n(defsystem :graphs/tests\n  :serial t\n  :depends-on (:graphs\n               :util/fiveam)\n  :components ((:file \"test-dfs\")))\n"
  },
  {
    "path": "src/graphs/test-dfs.lisp",
    "content": "(defpackage :graphs/test-dfs\n  (:use #:cl\n        #:graphs/dfs\n        #:fiveam)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :graphs/test-dfs)\n\n(util/fiveam:def-suite)\n\n(defclass node ()\n  ((id :initarg :id\n       :reader node-id)\n   (children :initarg :children\n             :initform nil\n             :accessor children)))\n\n(defmethod print-object ((node node) stream)\n  (format stream \"#node<~d>\" (node-id node)))\n\n(defun make-graph (desc)\n  (let* ((max (1+ (loop for x in desc maximizing\n                                      (loop for y in x maximizing y))))\n         (arr (make-array max :initial-contents (loop for i below max\n                                                      collect (make-instance 'node :id i)))))\n    (loop for (x . edges) in desc do\n      (setf (children (elt arr x))\n            (loop for y in edges collect (elt arr y))))\n    arr))\n\n(test preconditions\n  (let ((graph (make-graph `((0 1)\n                             (1 2)))))\n    (is (equal (list 1)\n               (mapcar #'node-id\n                         (children (elt graph 0)))))))\n\n(test simple-traversal\n  (let ((graph (make-graph `((0 1)\n                             (1 2)))))\n    (is (equal (list 0 1 2)\n               (mapcar #'node-id\n                         (dfs-traversal-order (elt graph 0) :children #'children))))))\n\n(test simple-traversal-after-order\n  (let ((graph (make-graph `((0 1)\n                             (1 2)))))\n    (is (equal (list 2 1 0)\n               (mapcar #'node-id\n                         (nth-value 1\n                          (dfs-traversal-order (elt graph 0) :children #'children)))))))\n\n(test order-when-we-point-to-the-same-thing\n  (let ((graph (make-graph `((0 1)\n                             (1 2 0)))))\n    (is (equal (list 0 1 2)\n               (mapcar #'node-id\n                         (dfs-traversal-order (elt graph 0) :children #'children))))))\n"
  },
  {
    "path": "src/http-proxy/http-proxy.asd",
    "content": "(defsystem :http-proxy\n  :serial t\n  :depends-on (:hunchentoot\n               :util/request\n               :util/lru-cache\n               :util/digests\n               :tmpdir\n               :ironclad\n               :quri)\n  :components ((:file \"server\")))\n\n(defsystem :http-proxy/tests\n  :serial t\n  :depends-on (:http-proxy\n               :util/fiveam)\n  :components ((:file \"test-server\")))\n"
  },
  {
    "path": "src/http-proxy/server.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :http-proxy/server\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/lru-cache\n                #:with-cache-file\n                #:lru-cache))\n(in-package :http-proxy/server)\n\n(defclass http-proxy (hunchentoot:acceptor)\n  ((lru-cache :accessor lru-cache)\n   (cache-size :initarg :cache-size\n               :initform \"4GB\"\n               :reader cache-size)\n   (cache-dir :initform (tmpdir:mkdtemp)\n              :reader cache-dir)))\n\n(defparameter *bad-request-headers*\n  (list\n   :accept-encoding))\n\n(defmethod hunchentoot:start :before ((self http-proxy))\n  (log:info \"Starting HTTP proxy\")\n  (setf (lru-cache self)\n        (make-instance 'lru-cache\n                       :max-size (cache-size self)\n                       :dir (ensure-directories-exist (cache-dir self)))))\n\n(defmethod hunchentoot:stop :after ((self http-proxy) &key &allow-other-keys)\n  (log:info \"Stopping HTTP proxy\")\n  (tmpdir::%delete-directory (cache-dir self)))\n\n\n(defun fix-proxy-headers (headers)\n  (loop for (key . value) in headers\n        unless (member key *bad-request-headers*)\n          collect (cons key value)))\n\n(defparameter *bad-headers*\n  (list\n   :content-length\n   :content-encoding\n   :keep-alive\n   :close\n   :connection\n   :transfer-encoding))\n\n(defmethod cache-key ((self http-proxy) uri)\n  (ironclad:byte-array-to-hex-string\n   (ironclad:digest-sequence 'ironclad:sha1 (flex:string-to-octets uri))))\n\n(defmethod make-forward-request ((self http-proxy)\n                                 uri output-file\n                                 &rest args &key &allow-other-keys)\n  \"Like util/request:http-request, but returns a file instead of a stream\nfor the content.\"\n  (multiple-value-bind (data ret headers)\n      (apply #'util/request:http-request\n             (quri:render-uri uri)\n             args)\n    (with-open-file (out-stream output-file :direction :output\n                                            :if-exists :supersede\n                                            :element-type 'flex:octet)\n      (write-sequence data out-stream)\n      (values ret headers))))\n\n(defmethod allowedp ((self http-proxy) host)\n  t)\n\n(defmethod hunchentoot:acceptor-dispatch-request ((self http-proxy)\n                                                  request)\n  (cond\n    ((or\n      (member (hunchentoot:request-method request)\n              '(:connect :post :put :delete))\n      (not (allowedp self (hunchentoot:host request))))\n     (setf (hunchentoot:return-code hunchentoot:*reply*) 403)\n     \"Access denied for this request from http-proxy\")\n    (t\n     (assert (eql :get (hunchentoot:request-method request)))\n     (let ((uri (quri:merge-uris\n                 (hunchentoot:request-uri request)\n                 (format nil \"http://~a\"(hunchentoot:host request)))))\n       (log:info \"Requesting: ~a\" uri)\n       (with-cache-file (tmp-file (lru-cache self) (cache-key self (quri:render-uri uri)))\n         (multiple-value-bind (ret headers)\n             (make-forward-request self\n                                   uri\n                                   tmp-file\n                                   :additional-headers (fix-proxy-headers (hunchentoot:headers-in request))\n                                   :want-stream nil\n                                   :keep-alive nil\n                                   :close t\n                                   :redirect nil\n                                   :force-binary t)\n           (hunchentoot:handle-static-file\n            tmp-file\n            (assoc-value headers :content-type)\n            (lambda (pathname content-type)\n              (declare (ignore pathname content-type))\n              (loop for (key . value) in headers\n                    unless\n                    (member key *bad-headers*)\n                    do (setf (hunchentoot:header-out key) value))\n              (setf (hunchentoot:header-out :x-screenshotbot-relay) \"1\")\n              (setf (hunchentoot:return-code hunchentoot:*reply*) ret)))\n           (log:info \"Finished streaming response for ~a\" uri))))))\n)\n\n#+nil\n(defvar *acceptor* (make-instance 'http-proxy :port 3127))\n\n#+nil\n(hunchentoot:start *acceptor*)\n"
  },
  {
    "path": "src/http-proxy/test-server.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :http-proxy/test-server\n  (:use #:cl\n        #:fiveam))\n(in-package :http-proxy/test-server)\n\n\n(util/fiveam:def-suite)\n"
  },
  {
    "path": "src/hunchentoot-extensions/acceptor-with-plugins.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :hunchentoot-extensions)\n\n;; each element is: (list handler-name plugin-name matcher handler)\n(defvar *acceptor-plugins-table* nil)\n\n(defclass acceptor-plugin ()\n  ((prefix :initarg :prefix\n           :accessor acceptor-plugin-prefix)\n   (regex :accessor acceptor-plugin-regex)))\n\n(defclass plugin-url-handler (hex::url-handler)\n  ((plugin-name :initarg :plugin-name)))\n\n(defmethod hex::url-handler-prefix ((handler plugin-url-handler))\n  (with-slots (plugin-name) handler\n   (loop for plugin in (acceptor-plugins hunchentoot:*acceptor*)\n         if (eq (acceptor-plugin-name plugin) plugin-name)\n           do\n              (return\n                (let ((prefix (acceptor-plugin-prefix plugin)))\n                 (str:substring 0 (1- (length prefix))\n                                prefix)))\n         finally\n         (error \"no plugin by name: ~a\" plugin-name))))\n\n(defun make-prefix-matcher (prefix)\n  (assert (str:ends-with-p \"/\" prefix))\n  (cl-ppcre:create-scanner\n   `(:sequence :start-anchor ,(str:substring 0 (- (length prefix) 1) prefix)\n              (:regex \"(/.*)?$\"))))\n\n(defmethod initialize-instance :after ((plugin acceptor-plugin) &key &allow-other-keys)\n  (with-slots (prefix) plugin\n   (setf (acceptor-plugin-regex plugin)\n         (make-prefix-matcher prefix))))\n\n(defmethod acceptor-plugin-name (plugin)\n  (type-of plugin))\n\n(defvar *acceptor-plugin*)\n\n(defclass acceptor-with-plugins (hunchentoot:easy-acceptor)\n  ((acceptor-plugins :accessor acceptor-plugins\n                     :initarg :acceptor-plugins\n                     :initform nil)))\n\n(defun register-plugin (acceptor name &rest args)\n  (loop for plugin in (acceptor-plugins acceptor)\n        if (typep plugin name)\n          return (values)\n        finally\n           (push (apply #'make-instance\n                        name\n                        args)\n                 (acceptor-plugins acceptor))))\n\n(defmacro declare-handler (name)\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n    (setf (get ,name 'handlerp) t)))\n\n\n(defmacro define-plugin-handler ((name &key uri method plugin-name) params &body body)\n  (declare (ignore method))\n  (unless uri\n    (setf uri \"/nil\"))\n\n\n  (multiple-value-bind (full-regex full-var-list)\n      (hex:make-uri-regex uri)\n    (declare (ignore full-regex))\n   (let* ((param-names (loop for param in params\n                             collect (if (listp param) (car param) param)))\n          (direct-params (loop for param in param-names\n                               if (not (member (string param) full-var-list :test 'string=))\n                                 collect param)))\n     `(eval-when (:compile-toplevel :load-toplevel :execute)\n        (declare-handler ',name)\n        (eval-when (:load-toplevel :execute)\n         (multiple-value-bind (full-regex vars parse-tree) (hex:make-uri-regex ,uri)\n           (declare (ignore vars))\n           (defun ,name (&key ,@param-names)\n             ,@body)\n           (setf\n            (assoc-value *acceptor-plugins-table* ',name)\n            (list ,plugin-name\n                  full-regex\n                  (lambda (relative-script-name)\n                    (multiple-value-bind (res args)\n                        (hex::matches-regex full-regex nil\n                                            :script-name relative-script-name)\n                      (declare (ignore res))\n                      (,name\n                       ,@ (loop for param in direct-params\n                                appending\n                                (list (intern (string param) (symbol-package :foo))\n                                      `(hunchentoot:parameter ,(string param))))\n                       ,@ (loop for param in full-var-list\n                                for i from 0 to 1000\n                                appending\n                                (list (intern (string param) (symbol-package :foo))\n                                      `(elt args ,i))))))))\n           (setf (alexandria:assoc-value hex::*url-list* ',name)\n                 (make-instance 'plugin-url-handler\n                                :parse-tree parse-tree\n                                :plugin-name ,plugin-name\n                                :request-args ',param-names))))))))\n\n\n(defmethod dispatch-plugin-request ((plugin acceptor-plugin)\n                                    request\n                                    relative-script-name)\n  (loop for (nil plugin-name matcher handler) in *acceptor-plugins-table*\n        if (and (eq plugin-name (acceptor-plugin-name plugin))\n                (cl-ppcre:scan matcher relative-script-name))\n          do\n             (progn\n               (return\n                 (let ((*acceptor-plugin* plugin))\n                   (process-plugin-request hunchentoot:*acceptor*\n                                           plugin\n                                           relative-script-name\n                                           handler))))\n        finally\n           (error \"could not dispatch, technically a 404\")))\n\n(defmethod process-plugin-request (acceptor (plugin acceptor-plugin)\n                                   relative-script-name\n                                   handler)\n  (funcall handler relative-script-name))\n\n(defmethod wrap-template (acceptor plugin output)\n  output)\n\n(defmethod hunchentoot:acceptor-dispatch-request ((acceptor acceptor-with-plugins)\n                                                  request)\n  (loop for plugin in (acceptor-plugins acceptor)\n        if (cl-ppcre:scan (acceptor-plugin-regex plugin) (hunchentoot:script-name request))\n          do\n             (return\n               (markup:write-html\n                (wrap-template acceptor plugin\n                               (dispatch-plugin-request\n                                plugin\n                                request\n                                (let ((url (str:substring (- (length (acceptor-plugin-prefix plugin)) 1) nil\n                                                          (hunchentoot:script-name request))))\n                                  (cond\n                                    ((eq 0 (length url))\n                                     \"/\")\n                                    (t url)))))))\n        finally (return (call-next-method))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/asdf-acceptor.lisp",
    "content": "(defpackage :hunchentoot-extensions/asdf-acceptor\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:asdf-acceptor\n   #:define-asdf-handler))\n(in-package :hunchentoot-extensions/asdf-acceptor)\n\n(defclass asdf-asset ()\n  ((system :initarg :system\n           :reader system)\n   (url :initarg :url)\n   (content-type :initarg :content-type\n                :reader content-type)))\n\n(defclass asdf-acceptor ()\n  ((assets :initform (make-hash-table :test #'equal)\n           :reader assets))\n  (:documentation \"An acceptor that responds to requests from ASDF (whether pre-compiled or dynamic)\"))\n\n(defmethod define-asdf-handler ((self asdf-acceptor)\n                                &rest args\n                                &key (url (error \"must provide :url\"))\n                                  (system (error \"must provide :system\"))\n                                  content-type)\n  (declare (ignore system content-type))\n  (setf (gethash url (assets self))\n        (apply #'make-instance 'asdf-asset\n               args)))\n\n(defmethod handle-asdf-output ((self asdf-asset))\n  (asdf:operate 'asdf:compile-op (system self))\n  (setf (hunchentoot:content-type*) (content-type self))\n  (hunchentoot:handle-static-file\n   (car (asdf:output-files 'asdf:compile-op (system self)))))\n\n(defmethod hunchentoot:acceptor-dispatch-request ((self asdf-acceptor) request)\n  (let* ((script-name (hunchentoot:script-name request))\n         (asset (gethash script-name (assets self))))\n    (cond\n      (asset\n       (handle-asdf-output asset))\n      (t\n       (call-next-method)))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/async.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/async\n  (:use #:cl\n        #:hex)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:hunchentoot\n                #:reset-connection-stream\n                #:*acceptor*)\n  (:import-from #:chunga\n                #:chunked-output-stream)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:prepare-async-response))\n(in-package :hunchentoot-extensions/async)\n\n(defvar *in-progress* (make-hash-table))\n\n(defclass async-response ()\n  ((stream :initarg :stream\n           :reader response-stream)\n   (headers :initarg :headers\n            :reader response-headers)))\n\n(def-easy-macro prepare-async-response (&binding async-response &fn body)\n  \"Handle slow HTTP requests 'asynchronously'.\n\nThis macro takes a body, and has one binding. The body is executed\n with the binding, which is an async-response object, and after that\n the response thread is aborted.\n\nWithin the body, you SHOULD NOT attempt to access or modify any\n hunchentoot data structures. Any headers should've been set before\n this call. The only calls you may do are hex:handle-async-static-file\n and hex:handle-async-error.\n\nYou MUST call one of these functions eventually. Forgetting to do so\n will leak the stream. (However, you can go back and debug leaked\n streams in the *in-progress* variable. In the future, we'll add an\n automatic timeout to destroy old async-responses.)\n\nYou can call those functions synchronously in the body, or you can\n call it from a different thread after the body is done. Both will\n behave correctly.\n\nThe current implementation has limited functionality with what you can\n do with the response. We'll add more if needed in the future.\"\n  (let* ((stream hunchentoot::*hunchentoot-stream*)\n         (headers (flex:with-output-to-sequence (stream)\n                    (let ((hunchentoot::*hunchentoot-stream* stream))\n                      (hunchentoot:send-headers)\n                      (finish-output hunchentoot::*hunchentoot-stream*)\n                      (finish-output stream))))\n         (response (make-instance 'async-response\n                                  :stream stream\n                                  :headers headers)))\n    (setf (gethash response *in-progress*) t)\n    (hunchentoot:detach-socket hunchentoot:*acceptor*)\n    (funcall body response)\n    (hunchentoot:abort-request-handler)))\n\n(defun cleanup (response)\n  (let ((stream (response-stream response)))\n    (when (open-stream-p stream)\n      (finish-output stream)\n      (close stream)))\n  (remhash response *in-progress*))\n\n(defun hex:handle-async-static-file (async-response output-file)\n  (unwind-protect\n       (let ((stream (response-stream async-response))\n             (headers (response-headers async-response)))\n         (assert (not (typep stream 'chunked-output-stream)))\n         (write-sequence headers stream)\n         (finish-output stream)\n         (let ((stream (chunga:make-chunked-stream stream)))\n           (setf (chunga:chunked-stream-output-chunking-p stream) t)\n           (with-open-file (file output-file :element-type '(unsigned-byte 8))\n             (uiop:copy-stream-to-stream file\n                                         stream\n                                         :element-type '(unsigned-byte 8))\n             (finish-output stream)\n             ;; this close is required.. not sure why though\n             (close stream))))\n    (cleanup async-response)))\n\n(defun hex:handle-async-error (async-response &key (code 500)\n                                                (message \"Something went wrong internally.\"))\n  ;; We don't have prebuilt headers to work with here, so let's mock\n  ;; the HTTP response.\n  (unwind-protect\n       (let ((stream (flex:make-flexi-stream\n                      (response-stream async-response)\n                      :external-format (flex:make-external-format :latin-1 :eol-style :crlf))))\n         (format stream \"HTTP/1.1 ~a Internal Server Error~%\" code)\n         (format stream \"Content-Length: ~a~%~%\" (length message))\n         (format stream \"~a\" message)\n         (finish-output stream))\n    (cleanup async-response)))\n"
  },
  {
    "path": "src/hunchentoot-extensions/better-easy-handler.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :hunchentoot-extensions)\n\n(defparameter *disable-sentry* nil)\n\n(defvar *url-list* nil\n  \"alist of name to mapping of parse tree for url, for use with make-url\")\n\n(defclass url-handler ()\n  ((parse-tree :initarg :parse-tree\n               :reader url-handler-parse-tree)\n   (plugin :initarg :plugin\n           :initform nil)\n   (request-args :initarg :request-args\n                 :reader url-handler-request-args)))\n\n(defmethod url-handler-prefix ((url-handler url-handler))\n  \"\")\n\n(defclass secure-acceptor ()\n  ()\n  (:documentation \"A base class to add some security checks. This is extracted to a\ndifferent class so it can be used with multi-acceptor too.\"))\n\n(defclass base-acceptor (secure-acceptor hunchentoot:easy-acceptor)\n  ((db-config :initarg :db-config\n              :accessor acceptor-db-config)))\n\n(defvar *logger* (log4cl:make-logger))\n\n(defun trim-output (output)\n  (let ((size 400))\n   (cond\n     ((< (length output) size)\n      output)\n     (t\n      (format nil \"~A <...trimmed...>\"\n       (str:substring 0 size output))))))\n\n(defmethod hunchentoot:acceptor-log-message ((acceptor base-acceptor) log-level format-string &rest format-arguments)\n  (let* ((output (apply 'format nil format-string format-arguments))\n         (output (trim-output output)))\n    (case log-level\n      (:info (log:info output))\n      (:error (log:error output))\n      (:warning (log:warn output))\n      (otherwise (log:info \"Could not find log-level ~S\" log-level)))))\n\n;; parse tree format:\n;; tree -> (:join tree1 tree2)\n;; tree -> (:optional tree)\n;; tree -> (:variable \"VAR-NAME\")\n;; tree -> (:path \"pathname\")\n;; tree -> (:regex-tree generic-regex-parse-tree)\n;; tree -> (:function 'function-name)\n\n\n(let ((regex \"^/([^/(]*)([/(].*)?$\"))\n  (defun split-url-parts (url)\n    (cl-ppcre:do-register-groups (first rest)\n        (regex url)\n      (return  (list\n                first\n                rest)))))\n\n(defun %make-uri-parse-tree (uri)\n  (assert (not (functionp uri)))\n  (assert (not (equal \"/\" uri)))\n  (cond\n    (t\n     (assert (str:starts-with-p \"/\" uri))\n     (labels ((inner-regex (url)\n                (declare (ignore uri))\n                (cond\n                  ((str:starts-with-p \"(\" url)\n                   (assert (str:ends-with-p \")\" url))\n                   (let ((inner-str (str:substring 1 (1- (length url)) url)))\n                    (multiple-value-bind (regex vars)\n                        (inner-regex inner-str)\n                      (values `(:optional ,regex)\n                              vars))))\n                  (t\n                   (assert (str:starts-with-p \"/\" url))\n                   (destructuring-bind (first-part &optional rest)\n                       (split-url-parts url)\n                     (declare (ignore unused))\n                     (multiple-value-bind (first-regex first-vars)\n                         (cond\n                           ((str:starts-with-p \":\" first-part)\n                            (let ((variable-name (str:substring 1 nil first-part)))\n                             (values\n                              `(:variable ,variable-name)\n                              (list (string-upcase variable-name)))))\n                           (t\n                            (values `(:path ,first-part) nil)))\n                       (cond\n                         (rest\n                          (multiple-value-bind (inner-regex inner-vars)\n                              (inner-regex rest)\n                            (values `(:join\n                                      ,first-regex\n                                      ,inner-regex)\n                                    (append first-vars inner-vars))))\n                         (t\n                          (values first-regex first-vars)))))))))\n       (multiple-value-bind (regex vars) (inner-regex uri)\n         (values regex vars))))))\n\n(defun %make-uri-regex (tree)\n  \"Returns two values, a regex that matches the request, and a list of\n  all the arguments.\"\n  (log:debug \"looking at: ~s\" tree)\n  (destructuring-bind (type val &optional val2) tree\n    (ecase type\n      (:path\n       (assert (not val2))\n       `(:sequence \"/\" ,val))\n      (:variable\n       `(:sequence \"/\" (:register (:greedy-repetition 1 nil (:inverted-char-class #\\/)))))\n      (:optional\n       (assert (not val2))\n       `(:greedy-repetition 0 1 ,(%make-uri-regex val)))\n      (:join (list :sequence (%make-uri-regex val) (%make-uri-regex val2)))\n      (:regex-tree `(:sequence :start-anchor ,val (:regex \"/?\") :end-anchor)))))\n\n(defun make-uri-regex (uri)\n  \"Returns three values: a compiled regex, list of variables, and the parse-tree\"\n  (cond\n    ((functionp uri)\n     `(:function ,uri))\n    ((equal \"/\" uri)\n     (values (cl-ppcre:create-scanner \"^/$\")\n             nil\n              '(:root)))\n    (t\n     (multiple-value-bind (parse-tree vars) (%make-uri-parse-tree uri)\n       (log:debug \"Got parse tree: ~a\" parse-tree)\n       (let* ((regex-str (%make-uri-regex parse-tree))\n              (regex-str `(:sequence :start-anchor ,regex-str (:regex \"/?\") :end-anchor)))\n         (log:debug \"Got regex-str: ~s\" regex-str)\n         (values (cl-ppcre:create-scanner regex-str) vars parse-tree regex-str))))))\n\n(defgeneric acceptor-funcall-handler (acceptor fn)\n  (:documentation \"better-easy-handler will call this to funcall the handler implementation\"))\n\n(defun matches-regex (regex request &key script-name)\n  (declare (optimize (debug 3) (speed 0)))\n  (let ((script-name (or script-name (hunchentoot:script-name request))))\n    (cond\n      ((and (consp regex)\n            (eql :function (car regex)))\n       (funcall (cadr regex) request))\n      (t\n       (cl-ppcre:scan-to-strings regex script-name)))))\n\n(defmacro better-easy-handler ((name &key uri method acceptor-names intern) params &body body)\n  (unless uri\n    ;; if we don't do this the whole site crashes.\n    (setf uri \"/nil\"))\n\n  (let ((name (or name (make-name uri method)))\n        (regex (gensym \"REGEX\"))\n        (vars (gensym \"VARS\"))\n        (parse-tree (gensym \"PARSE-TREE\")))\n    (multiple-value-bind (body decls) (uiop:parse-body body)\n     (multiple-value-bind (full-regex full-var-list) (when (stringp uri) (make-uri-regex uri))\n       (declare (ignore full-regex))\n       ;; we call make-uri-regex twice, once for getting the variables\n       ;; at macro time, and the second for use in the closure.\n       (let ((acceptor-names (if (symbolp (eval acceptor-names)) `(list ,acceptor-names) acceptor-names)))\n         `(eval-when (:compile-toplevel :load-toplevel :execute)\n            (declare-handler ',name)\n            (eval-when (:load-toplevel :execute)\n             (multiple-value-bind (,regex ,vars ,parse-tree) (make-uri-regex ,uri)\n               (declare (ignorable ,vars))\n               (hunchentoot:define-easy-handler (,name :uri (%only-request-of-type ,regex ,method) :acceptor-names ,acceptor-names) ,params\n                 ,@decls\n                 (when ,intern\n                   (%assert-is-intern))\n                 ;; todo: double regexing, but then, the acceptor is\n                 ;; regexing a hundred or so different urls, so perhaps it\n                 ;; doesn't matter.\n                 ,(when full-var-list\n                    `(multiple-value-bind (res args)\n                         (matches-regex ,regex hunchentoot:*request*)\n                       (declare (ignore res))\n                       ,@ (loop for v in full-var-list\n                                for i from 0 to 100\n                                appending\n                                (loop for param in params\n                                      if (equal v (symbol-name param))\n                                        collect `(unless ,param (setf ,param (elt args ,i)))))))\n                 (%easy-handler-wrap\n                  (lambda ()\n                    (progn ,@body))))\n               (when ',name\n                 (setf (alexandria:assoc-value *url-list* ',name)\n                       (make-instance 'url-handler\n                                      :parse-tree ,parse-tree\n                                      :request-args (loop for p in ',params collect\n                                                                            (if (listp p) (car p) p)))))))))))))\n\n\n(defmethod acceptor-funcall-handler (acceptor fn)\n  (funcall fn))\n\n(defun make-name (uri method)\n  ;; interned in current package!\n  (intern (format nil \"~a-~a\" uri method) *package*))\n\n(defun prod-request? ()\n  (destructuring-bind (host &optional (port 80)) (str:split \":\" (hunchentoot:host))\n    (declare (ignore port))\n    (not (str:starts-with-p \"staging.\" host))))\n\n(defun discard-condition-p (condition)\n  #+sbcl (typep condition 'sb-int:broken-pipe)\n  #-sbcl nil)\n\n(defmethod log-crash-extras ((acceptor base-acceptor) condition)\n  `((\"url\" .\n           ,(hunchentoot:request-uri*))\n    (\"user-agent\" . , (hunchentoot:header-in* :user-agent))))\n\n(defmethod log-crash-extras :around ((acceptor base-acceptor) condition)\n  (loop for (k . v) in (call-next-method)\n        collect (cons k (format nil \"~a\" v))))\n\n(defun %sentry-capture-error (condition &key extras)\n  (unless *disable-sentry*\n   (util/threading:log-sentry condition)))\n\n\n(easy-macros:def-easy-macro with-warning-logger (&fn fn)\n  (handler-bind ((warning #'util/threading:log-sentry))\n    (funcall fn)))\n\n#-(or screenshotbot-oss eaase-oss)\n(defmethod hunchentoot:maybe-invoke-debugger :after (condition)\n  (when (and hunchentoot:*catch-errors-p*\n             (not *disable-sentry*)\n             #+lispworks\n             (not\n              (or\n               (typep condition 'comm:socket-error)\n               (typep condition 'comm:socket-io-error)))\n             (prod-request?))\n    ;; There's an error in trivial-backtrace:map-backtrace in SBCL\n    ;; if we don't set sb-debug:*stack-top-hint* to NIL\n    (let (#+sbcl (sb-debug:*stack-top-hint* nil))\n      (unless (discard-condition-p condition)\n        (%sentry-capture-error\n         condition)))))\n\n(defmethod hunchentoot:acceptor-log-access ((acceptor base-acceptor) &key return-code)\n  \"Default method for access logging.  It logs the information to the\ndestination determined by (ACCEPTOR-ACCESS-LOG-DESTINATION ACCEPTOR)\n\\(unless that value is NIL) in a format that can be parsed by most\nApache log analysis tools.)\"\n\n  (log:info \"~:[-~@[ (~A)~]~;~:*~A~@[ (~A)~]~] ~:[-~;~:*~A~] [~A] \\\"~A ~A~@[?~A~] ~A\\\" ~D ~:[-~;~:*~D~] \\\"~:[-~;~:*~A~]\\\" \\\"~:[-~;~:*~A~]\\\"\"\n          (hunchentoot:remote-addr*)\n          (hunchentoot:header-in* :x-forwarded-for)\n          (hunchentoot:authorization)\n          (hunchentoot::iso-time)\n          (hunchentoot:request-method*)\n          (hunchentoot:script-name*)\n          (hunchentoot:query-string*)\n          (hunchentoot:server-protocol*)\n          return-code\n          (hunchentoot:content-length*)\n          (hunchentoot:referer)\n          (hunchentoot:user-agent)))\n\n(defun %easy-handler-wrap (body)\n  (declare (ignore html))\n  (let* ((acceptor (hunchentoot:request-acceptor hunchentoot:*request*)))\n    (acceptor-funcall-handler acceptor body)))\n\n(defun socket-detached-p ()\n  \"In some cases, we might detach the socket to asynchronously respond to the request\"\n  (null hunchentoot::*close-hunchentoot-stream*))\n\n(defun %only-request-of-type (uri type)\n  (assert (member type '(nil :get :post :delete :put)))\n  (lambda (request)\n    (and (or (not type)\n             (eq (hunchentoot:request-method request) type))\n         (matches-regex uri request))))\n\n(defun is-intern? ()\n  (let ((remote-addr (hunchentoot:real-remote-addr)))\n   (or\n    (member remote-addr '(\"192.168.1.1\" \"127.0.0.1\") :test #'equal)\n    (str:starts-with? \"10.1.10.\" remote-addr)\n    (equal \"172.58.38.219\" remote-addr) ;; temporary, change when needed\n    (str:starts-with? \"2603:3024:e9c:b0\" remote-addr))))\n\n(defun %assert-is-intern ()\n  (assert (is-intern? )))\n\n(define-condition redirected (condition)\n  ((url :initarg :url)))\n\n(defmethod safe-redirect (target &rest args)\n  (let ((target (if (and target (not (equal \"\" target)))\n                    target\n                    (hunchentoot:script-name hunchentoot:*request*))))\n    (let ((target (apply 'make-url target args)))\n      (signal 'redirected :url target)\n      (setf (hunchentoot:header-out :location) target\n            (hunchentoot:return-code*) hunchentoot:+http-moved-temporarily+)\n      (hunchentoot:abort-request-handler))))\n\n(defmethod hunchentoot:acceptor-dispatch-request :around ((acceptor base-acceptor) request)\n  (restart-case\n      (let ((util/threading:*warning-count* 0)\n            (util/threading:*extras*\n              (list*\n               (lambda (condition)\n                 (when (boundp 'hunchentoot:*acceptor*)\n                   (log-crash-extras acceptor condition)))\n               util/threading:*extras*)))\n        (with-warning-logger ()\n          (call-next-method)))\n    (redispatch-request ()\n      (hunchentoot:acceptor-dispatch-request acceptor request))))\n\n(defmethod hunchentoot:acceptor-dispatch-request :around ((acceptor secure-acceptor) request)\n  (cond\n    ((str:starts-with-p \"/~\" (hunchentoot:script-name request))\n     (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+)\n     (hunchentoot:abort-request-handler))\n    (t\n     (call-next-method))))\n\n(defmethod hunchentoot:acceptor-dispatch-request ((acceptor base-acceptor) request)\n  (let ((response (call-next-method)))\n    (cond\n      ((markup:xml-tag-p response)\n       (setf (hunchentoot:content-type*) \"text/html; charset=utf-8\")\n       (setf (hunchentoot:header-out :last-modified)\n             (hunchentoot::rfc-1123-date (get-universal-time)))\n       (markup:write-html response))\n      (t\n       response))))\n\n(defmacro def-named-url (name url)\n  \"Give a hardcoded URL a symbol name or an alias. This might be useful\nfor giving a name to a def-clos-dispatch, or giving an existing\nendpoint an alias.\"\n  (multiple-value-bind (regex vars parse-tree)\n      (make-uri-regex url)\n    (declare (ignore regex))\n    (when vars\n      (error \"Using :vars in def-named-url is unsupported. It could make sense in\nsome context, but not in most cases where this is used.\"))\n    `(progn\n       (setf (alexandria:assoc-value *url-list* ',name)\n             (make-instance 'url-handler\n                            :parse-tree ',parse-tree\n                            :request-args nil)))))\n\n(defmethod hunchentoot:acceptor-log-access :around ((acceptor base-acceptor) &key return-code)\n  (declare (ignore return-code))\n  (handler-case\n      (call-next-method)\n    (cl-base64:incomplete-base64-data ()\n      ;; Quieten some noise from bad requests sending in incomplete\n      ;; Base 64 data. See T859.\n      (values))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/clos-dispatcher.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/clos-dispatcher\n  (:use #:cl)\n  (:import-from #:hunchentoot-extensions\n                #:def-clos-dispatch\n                #:clos-dispatcher)\n  (:export\n   #:clos-dispatcher))\n(in-package :hunchentoot-extensions/clos-dispatcher)\n\n(defun %make-hash-table (&rest args)\n  (apply #'make-hash-table\n         #+sbcl #+sbcl\n         :synchronized t\n         args))\n\n(defvar *bindings* (%make-hash-table :test #'equal))\n\n(defvar *lock* (bt:make-lock))\n\n(defclass clos-dispatcher ()\n  ()\n  (:documentation \"An acceptor mixin that let's you create routes for scripts using CLOS.\"))\n\n\n(defmacro def-clos-dispatch (((var class) script-name) params &body body)\n  `(bt:with-lock-held (*lock*)\n     (let ((old\n             (util/misc:or-setf\n              (gethash ,script-name *bindings*)\n              (%make-hash-table :test #'eql))))\n       (setf\n        (gethash (find-class ',class) old)\n        (lambda (,var)\n          (declare (ignorable ,var))\n          (let ,(loop for param in params\n                      collect `(,param (hunchentoot:parameter ,(string-downcase param))))\n            ,@body))))))\n\n(defgeneric %normalize (input)\n  (:method ((input string))\n    input)\n  (:method ((input markup:abstract-xml-tag))\n    (markup:write-html input))\n  (:method (input)\n    \"This could happen if the dispatcher handled the request directly, and\nreturned NIL or didn't care about the return value.\"\n    input))\n\n(defmethod dispatch-clos-request ((self clos-dispatcher) dispatcher)\n  (setf (hunchentoot:header-out \"X-clos-acceptor\") \"1\")\n  (%normalize (funcall dispatcher self)))\n\n(defmethod hunchentoot:acceptor-dispatch-request ((self clos-dispatcher) request)\n  (let ((script-name (hunchentoot:script-name request)))\n    (let ((binding (gethash script-name *bindings*)))\n      (cond\n        (binding\n         (let ((classes (closer-mop:class-precedence-list (class-of self))))\n           (loop for class in classes\n                 for dispatcher = (gethash class binding)\n                 if dispatcher\n                   return (dispatch-clos-request self dispatcher)\n                 finally\n                 (call-next-method))))\n        (t\n         (call-next-method))))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/existing-socket.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/existing-socket\n  (:use #:cl)\n  (:import-from #:hunchentoot\n                #:acceptor-process\n                #:acceptor-taskmaster\n                #:handle-incoming-connection\n                #:acceptor-shutdown-p\n                #:start-listening)\n  (:export\n   #:existing-socket\n   #:acceptor-with-existing-socket))\n(in-package :hunchentoot-extensions/existing-socket)\n\n(defclass acceptor-with-existing-socket ()\n  ((existing-socket :initarg :existing-socket\n                    :initform nil\n                    :accessor existing-socket\n                    :documentation \"On Lispworks, this is a file descriptor. On other platforms it should be a usocket socket.\")))\n\n(defmethod start-listening ((acceptor acceptor-with-existing-socket))\n  (cond\n    ((not (existing-socket acceptor))\n     (call-next-method))\n    (t\n     #-lispworks\n     (setf (hunchentoot::acceptor-listen-socket acceptor)\n           (existing-socket acceptor))\n     #+lispworks\n     (let ((process\n             (bt:make-thread\n              (lambda ()\n                (catch 'out\n                  (loop until (acceptor-shutdown-p acceptor)\n                        do\n                           (handle-incoming-connection\n                            (acceptor-taskmaster acceptor)\n                            (comm::get-fd-from-socket (existing-socket acceptor)))))))))\n       (mp:process-stop process)\n       (setf (acceptor-process acceptor) process)\n       (values)))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/forward.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/forward\n  (:use #:cl)\n  (:import-from #:hex\n                #:forward-request)\n  (:import-from #:alexandria\n                #:when-let\n                #:assoc-value))\n(in-package :hunchentoot-extensions/forward)\n\n\n(auto-restart:with-auto-restart ()\n  (defun forward-request (url &key (request hunchentoot:*request*)\n                                (keep-current-host nil)\n                                (extra-headers nil))\n    \"Forward the current request to the destination URL. If\nkeep-current-host is T, then we'll forward to the destination, but\nkeep the Host header the same.\"\n    ;; We'll avoid using util/http-request for now. It adds a depedency,\n    ;; but probably we want a lot more control over the behavior.\n    (let* ((body (hunchentoot:raw-post-data :force-binary t))\n           (uri (quri:uri url))\n           (dest-uri (quri:render-uri\n                      (quri:make-uri\n                       :scheme (quri:uri-scheme uri)\n                       :host (if keep-current-host\n                                 (cl-ppcre:regex-replace-all \":[0-9]*$\" (hunchentoot:host request) \"\")\n                                 (quri:uri-host uri))\n                       :port (quri:uri-port uri)\n                       :defaults (quri:uri (hunchentoot:request-uri request))))))\n      (multiple-value-bind (response code headers)\n          (drakma:http-request\n           dest-uri\n           :additional-headers (append\n                                (list (cons :x-forwarded-host (hunchentoot:host request)))\n                                (loop for (key . value) in (hunchentoot:headers-in request)\n                                      unless (member key '(:content-length :user-agent :host\n                                                           :|:AUTHORITY:|\n                                                           :|:METHOD:|\n                                                           :|:PATH:|\n                                                           :|:SCHEME:|\n                                                           :accept-encoding))\n                                        collect (cons key value))\n                                extra-headers)\n           :method (hunchentoot:Request-method request)\n           :real-host (quri:uri-host uri)\n           :force-binary t\n           :want-stream t\n           :decode-content nil\n           :redirect nil\n           :preserve-uri t\n           :user-agent (hunchentoot:header-in :User-agent request)\n           :content-type (hunchentoot:header-in :content-type request)\n           :content body)\n        (setf (hunchentoot:return-code*) code)\n\n        (loop for (key . value) in headers\n              if (not (member key '(:content-length) ))\n                do (setf (hunchentoot:header-out key) value))\n\n        (when-let ((Content-length (assoc-value headers :content-length)))\n          (setf (hunchentoot:header-out :content-length)\n                (parse-integer content-length)))\n\n        (setf (hunchentoot:header-out :x-final-code) code)\n        (Setf (hunchentoot:header-out :x-final-headers) (format nil \"~a\" headers))\n        (setf (hunchentoot:Header-out :x-dest-uri) (format nil \"~a\" dest-uri))\n\n        (let* ((stream (hunchentoot:send-headers)))\n          (uiop:copy-stream-to-stream response stream\n                                      :element-type 'flex:octet)\n          (finish-output stream)\n          (hunchentoot:abort-request-handler))))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/hunchentoot-extensions.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :hunchentoot-extensions\n  :serial t\n  :depends-on (:hunchentoot\n               :str\n               :markup\n               :auto-restart\n               :quri\n               :easy-macros\n               :closer-mop\n               :do-urlencode\n               :util.threading\n               :log4cl)\n  :components ((:file \"package\")\n               (:file \"url\")\n               (:file \"existing-socket\")\n               (:file \"random-port\")\n               (:file \"acceptor-with-plugins\")\n               (:file \"clos-dispatcher\")\n               (:file \"better-easy-handler\")\n               (:file \"postdata\")\n               (:file \"async\")\n               (:file \"forward\")\n               (:file \"asdf-acceptor\")\n               (:file \"webp\")))\n\n(defsystem :hunchentoot-extensions/tests\n  :serial t\n  :depends-on (:hunchentoot-extensions\n               :lparallel\n               :util/request\n               :util.testing\n               :util/fiveam\n               :dexador\n               :fiveam-matchers\n               :fiveam)\n  :components ((:file \"test-acceptor-with-plugins\")\n               (:file \"test-random-port\")\n               (:file \"test-clos-dispatcher\")\n               (:file \"test-better-easy-handler\")\n               (:file \"test-url\")\n               (:file \"test-async\")\n               (:file \"test-forward\")))\n"
  },
  {
    "path": "src/hunchentoot-extensions/package.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions\n  (:nicknames :hex)\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:util/threading\n                #:*extras*)\n  (:export #:base-acceptor\n           #:better-easy-handler\n           #:acceptor-funcall-handler\n           #:redirected\n           #:make-full-url\n           #:acceptor-plugin-name\n           #:acceptor-plugin-prefix\n           #:write-postdata-to-file\n           #:wrap-template\n           #:acceptor-plugin\n           #:define-plugin-handler\n           #:acceptor-with-plugins\n           #:log-crash-extras\n           #:*acceptor-plugin*\n           #:make-uri-regex\n           #:acceptor-db-config\n           #:make-uri-regex\n           #:add-get-param-to-url\n           #:declare-handler\n           #:acceptor-plugins\n           #:safe-redirect\n           #:make-url\n           #:supports-webp?\n           #:fix-for-webp\n           #:prepare-async-response\n           #:handle-async-static-file\n           #:handle-async-error\n           #:register-plugin\n           #:process-plugin-request\n           #:dispatch-plugin-request\n           #:secure-acceptor\n           #:clos-dispatcher\n           #:forward-request\n           #:def-clos-dispatch\n           #:def-named-url))\n"
  },
  {
    "path": "src/hunchentoot-extensions/postdata.lisp",
    "content": "(defpackage :hunchentoot-extensions/postdata\n  (:use #:cl\n        #:hex)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :hunchentoot-extensions/postdata)\n\n(defun write-postdata-to-file (file-name)\n  (uiop:with-staging-pathname (file-name)\n    (with-open-file (output file-name :direction :output\n                                      :element-type '(unsigned-byte 8)\n                                      :if-exists :supersede)\n      (let ((content-length (parse-integer (hunchentoot:header-in* :content-length))))\n        (let ((buf (make-array 4096 :element-type '(unsigned-byte 8))))\n          (let ((input (hunchentoot:raw-post-data :force-binary t\n                                                  :want-stream t)))\n            (loop while (> content-length 0) do\n              (let ((bytes (read-sequence buf input)))\n                (write-sequence buf output :end bytes)\n                (decf content-length bytes)))))))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/random-port.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/random-port\n  (:use #:cl)\n  (:import-from #:hunchentoot\n                #:acceptor-port\n                #:start-listening)\n  (:import-from #:hunchentoot-extensions/existing-socket\n                #:existing-socket\n                #:acceptor-with-existing-socket)\n  (:export\n   #:acceptor-on-random-port))\n(in-package :hunchentoot-extensions/random-port)\n\n(defclass acceptor-on-random-port (acceptor-with-existing-socket)\n  ((started-p :initform nil\n              :accessor started-p))\n  (:default-initargs :port 0))\n\n(defmethod start-listening :before ((acceptor acceptor-on-random-port))\n  #+lispworks\n  (setf (existing-socket acceptor)\n        (comm::create-tcp-socket-for-service\n         0 :address \"127.0.0.1\" :backlog 30))\n  #-lispworks\n  (let* ((usocket (usocket:socket-listen \"127.0.0.1\" 0\n                                         :element-type '(unsigned-byte 8))))\n    (setf (existing-socket acceptor)\n          usocket)))\n\n(defmethod start-listening :after ((acceptor acceptor-on-random-port))\n  (setf (started-p acceptor) t))\n\n;; On usocket based implementations, hunchentoot already supports 0 as\n;; the acceptor port.\n#+lispworks\n(defmethod acceptor-port ((acceptor acceptor-on-random-port))\n  (cond\n    ((existing-socket acceptor)\n     (nth-value\n      1\n      (comm:get-socket-address (existing-socket acceptor))))\n    (t\n     0)))\n\n(defmethod hunchentoot:stop :around ((acceptor acceptor-on-random-port) &key soft)\n  (declare (ignore soft))\n  (when (started-p acceptor)\n    (setf (started-p acceptor) nil)\n    (call-next-method))\n  (setf (existing-socket acceptor) nil)\n  #-lispworks\n  (setf (slot-value acceptor 'hunchentoot::port) 0))\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-acceptor-with-plugins.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/test-acceptor-with-plugins\n  (:use #:cl\n        #:alexandria\n        #:fiveam)\n  (:import-from #:hex\n                #:make-prefix-matcher)\n  (:import-from #:hunchentoot-extensions\n                #:acceptor-with-plugins)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package #:hunchentoot-extensions/test-acceptor-with-plugins)\n\n(def-suite* :hunchentoot-extensions/test-acceptor-with-plugins)\n\n(test check-prefix-matcher ()\n  (let ((matcher (make-prefix-matcher \"/foo/\")))\n    (is-true (cl-ppcre:scan matcher \"/foo\"))\n    (is-false (cl-ppcre:scan matcher \"/foot\"))\n    (is-true (cl-ppcre:scan matcher \"/foo/\"))\n    (is-true (cl-ppcre:scan matcher \"/foo/bar/car.txt\"))\n    (is-false (cl-ppcre:scan matcher \"/blah/foo/\"))\n    (is-false (cl-ppcre:scan matcher \"/blah/foo\"))))\n\n(defclass my-plugin (hex:acceptor-plugin)\n  ())\n\n(test register-plugin-only-registers-once\n  (let ((acceptor (make-instance 'acceptor-with-plugins)))\n    (hex:register-plugin acceptor 'my-plugin :prefix \"/foo/\")\n    (assert-that (hex:acceptor-plugins acceptor)\n                 (has-length 1))\n    (hex:register-plugin acceptor 'my-plugin :prefix \"/foo/\")\n    (assert-that (hex:acceptor-plugins acceptor)\n                 (has-length 1))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-async.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/test-async\n  (:use #:cl\n        #:fiveam)\n  (:shadow :get)\n  (:import-from #:util/testing\n                #:with-global-binding\n                #:with-local-acceptor\n                #:test-acceptor\n                #:with-fake-request)\n  (:import-from #:hunchentoot\n                #:content-type*\n                #:define-easy-handler)\n  (:import-from #:hunchentoot-extensions\n                #:better-easy-handler)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:lparallel.promise\n                #:future)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :hunchentoot-extensions/test-async)\n\n\n(util/fiveam:def-suite)\n\n(defclass my-acceptor (test-acceptor)\n  ()\n  (:default-initargs\n   :name 'my-acceptor))\n\n(defvar *host*)\n(defvar *tmp*)\n\n(defmacro defhandler ((uri) () &body body)\n  `(better-easy-handler (nil :uri ,uri :acceptor-names '(my-acceptor)) ()\n     ,@body))\n\n(def-fixture state ()\n  (with-local-acceptor (*host*) ('my-acceptor)\n    (uiop:with-temporary-file (:stream s :pathname p :external-format :latin-1)\n      (write-string \"bar1\" s)\n      (finish-output s)\n      (with-global-binding ((*tmp* p))\n       (&body)))))\n\n(defhandler (\"/happy-path\") ()\n  \"foo\")\n\n(defun get (url)\n  (http-request (format nil \"~a~a\" *host* url)\n                :want-string t))\n\n(test happy-path\n  (with-fixture state ()\n    (is (equal \"foo\" (get \"/happy-path\")))))\n\n(defhandler (\"/happy-async-path\") ()\n  (setf (hunchentoot:header-out :content-type) \"image/png\")\n  (hex:prepare-async-response (r)\n    (hex:handle-async-static-file r *tmp*))\n  \"Bad\")\n\n(test happy-async-path\n  (with-fixture state ()\n    (is (equal \"bar1\" (get \"/happy-async-path\")))))\n\n(defhandler (\"/happy-really-async-path\") ()\n  (setf (hunchentoot:header-out :content-type) \"image/png\")\n  (hex:prepare-async-response (r)\n    (bt:make-thread\n     (lambda ()\n       (sleep 0.1)\n       (hex:handle-async-static-file r *tmp*))))\n  \"Bad\")\n\n(test happy-really-async-path\n  (with-fixture state ()\n    (is (equal \"bar1\" (get \"/happy-really-async-path\")))))\n\n(defhandler (\"/errors\") ()\n  (hex:prepare-async-response (r)\n    (hex:handle-async-error r)))\n\n(test has-errors\n  (with-fixture state ()\n    (is (eql 500\n             (second (multiple-value-list (get \"/errors\")))))))\n\n(defhandler (\"/async-errors\") ()\n  (hex:prepare-async-response (r)\n    (bt:make-thread\n     (lambda ()\n       (sleep 0.1)\n       (hex:handle-async-error r :code 501)))))\n\n(test has-errors-2\n  (with-fixture state ()\n    (is (eql 501\n             (second (multiple-value-list (get \"/async-errors\")))))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-better-easy-handler.lisp",
    "content": "(defpackage :hunchentoot-extensions/test-better-easy-handler\n  (:use :cl\n        :fiveam\n        :hex)\n  (:import-from :hex\n                :make-uri-regex\n                :%make-uri-regex\n                :better-easy-handler\n                :url-handler-parse-tree\n                :url-handler-request-args\n                :split-url-parts\n                :*url-list*)\n  (:import-from :cl-ppcre\n                :scan-to-strings)\n  (:import-from #:hunchentoot-extensions\n                #:def-named-url)\n  (:export))\n(in-package :hunchentoot-extensions/test-better-easy-handler)\n\n(util/fiveam:def-suite)\n\n(test split-url-parts\n  (is (equal (list \"foo\" \"/bar\")\n             (split-url-parts \"/foo/bar\")))\n  (is (equal (list \"foo\" nil)\n             (split-url-parts \"/foo\")))\n  (is (equal (list \"foo\" \"(/bar)\")\n             (split-url-parts \"/foo(/bar)\"))))\n\n(test make-uri-regex\n  (multiple-value-bind (regex vars) (make-uri-regex \"/blog/:name\")\n    (is (equal (list \"NAME\") vars))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog/foo-bar\")\n      (is-true res)\n      (is (equalp #(\"foo-bar\") args)))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog/foo-bar/\")\n      (is-true res)\n      (is (equalp #(\"foo-bar\") args)))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog\")\n      (is-false res)\n      (is (equalp nil args)))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog/\")\n      (is-false res)\n      (is (equalp nil args)))))\n\n(test without-any-args\n  (multiple-value-bind (regex vars) (make-uri-regex \"/blog\")\n    (is (equal nil vars))\n    (multiple-value-bind  (res args) (scan-to-strings regex \"/blog\")\n      (is-true res)\n      (is (equalp #() args)))\n    (multiple-value-bind  (res args) (scan-to-strings regex \"/blog/\")\n      (is-true res)\n      (is (equalp #() args)))))\n\n(test optional-arg\n  (multiple-value-bind (regex vars) (make-uri-regex \"/blog(/:name)\")\n    (is (equal (list \"NAME\") vars))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog/foo-bar\")\n      (is-true res)\n      (is (equalp #(\"foo-bar\")\n                  args)))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog/\")\n      (is-true res)\n      (is (equalp #(nil) args)))\n    (multiple-value-bind (res args) (scan-to-strings regex \"/blog\")\n      (is-true res)\n      (is (equalp #(nil) args)))))\n\n(test long-name\n  (multiple-value-bind (regex vars) (make-uri-regex \"/assets/css/default.css\")\n    (is-true (scan-to-strings regex \"/assets/css/default.css\"))))\n\n(test matches-root-url\n  (multiple-value-bind (regex vars) (make-uri-regex \"/\")\n    (is-true (scan-to-strings regex \"/\"))\n    (is-false (scan-to-strings regex \"/blah\"))\n    (is-false (scan-to-strings regex \"blah/\"))))\n\n(test creates-proper-url-list\n  (let ((*url-list* nil))\n    (better-easy-handler (unused1 :uri \"/foo(/:bar)\") (car)\n      nil)\n    (is (= 1 (length *url-list*)))\n    (is (eql 'unused1 (caar *url-list*)))\n    (is (equal '(car) (url-handler-request-args (cdar *url-list*))))\n    (is (equal '(:join (:path \"foo\") (:optional (:variable \"bar\"))) (url-handler-parse-tree (cdar *url-list*))))\n    (is (equal \"/foo/zoidberg\" (hex:make-url 'unused1 :bar \"zoidberg\")))))\n\n(test doesnt-match-subdirs\n  (let ((regex (make-uri-regex \"/foo/:bar\")))\n    (is-false (scan-to-strings regex \"/foo/dfdfd/hello/world\"))))\n\n(def-named-url foo \"/bar/car/dar/foo\")\n\n(test named-url-gets-mapped\n  (is (equal \"/bar/car/dar/foo\"\n             (hex:make-url 'foo))))\n\n(test redirect-signal\n  (signals hex:redirected\n    (hex:safe-redirect \"/\")))\n\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-clos-dispatcher.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/test-clos-dispatcher\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that))\n(in-package :hunchentoot-extensions/test-clos-dispatcher)\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass simple-acceptor (hex:clos-dispatcher\n                           hunchentoot:acceptor)\n  ())\n\n(defclass another-acceptor (simple-acceptor)\n  ())\n\n(def-fixture state ()\n  (let ((hunchentoot:*reply* (make-instance 'hunchentoot:reply)))\n    (&body)))\n\n(hex:def-clos-dispatch ((self simple-acceptor) \"/hello\") ()\n  \"hello world\")\n\n(defclass fake-request ()\n  ((script-name :initarg :script-name\n                :reader hunchentoot:script-name)))\n\n(test simple-dispatch\n  (with-fixture state ()\n   (is (equal \"hello world\"\n              (hunchentoot:acceptor-dispatch-request\n               (make-instance 'another-acceptor)\n               (make-instance 'fake-request\n                              :script-name \"/hello\"))))))\n\n(test simple-dispatch-on-parent\n  (with-fixture state ()\n   (is (equal \"hello world\"\n              (hunchentoot:acceptor-dispatch-request\n               (make-instance 'simple-acceptor)\n               (make-instance 'fake-request\n                              :script-name \"/hello\"))))))\n\n(test failed-dispatch\n  (with-fixture state ()\n   (is (equal nil\n              (catch 'hunchentoot::handler-done\n                (hunchentoot:acceptor-dispatch-request\n                 (make-instance 'another-acceptor)\n                 (make-instance 'fake-request\n                                :script-name \"/hello2\")))))))\n\n(hex:def-clos-dispatch ((self simple-acceptor) \"/hello3\") ()\n  \"one\")\n\n(hex:def-clos-dispatch ((self another-acceptor) \"/hello3\") ()\n  \"two\")\n\n(test use-the-highest-precedence-possible\n  (with-fixture state ()\n   (is (equal \"two\"\n              (hunchentoot:acceptor-dispatch-request\n               (make-instance 'another-acceptor)\n               (make-instance 'fake-request\n                              :script-name \"/hello3\"))))))\n\n(hex:def-clos-dispatch ((self simple-acceptor) \"/markup\") ()\n  <html>\n  hello\n  </html>)\n\n(test handles-markup\n  (with-fixture state ()\n   (assert-that\n    (hunchentoot:acceptor-dispatch-request\n     (make-instance 'simple-acceptor)\n     (make-instance 'fake-request\n                    :script-name \"/markup\"))\n    (contains-string \"<html>\")\n    (contains-string \"hello\"))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-forward.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/test-forward\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/testing\n                #:with-global-binding\n                #:with-local-acceptor\n                #:test-acceptor)\n  (:import-from #:hunchentoot\n                #:define-easy-handler)\n  (:import-from #:hunchentoot-extensions\n                #:better-easy-handler)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :hunchentoot-extensions/test-forward)\n\n(util/fiveam:def-suite)\n\n;; Destination acceptor that will receive forwarded requests\n(defclass destination-acceptor (test-acceptor)\n  ()\n  (:default-initargs\n   :name 'destination-acceptor))\n\n;; Source acceptor that will forward requests\n(defclass source-acceptor (test-acceptor)\n  ()\n  (:default-initargs\n   :name 'source-acceptor))\n\n(defvar *destination-host*)\n(defvar *received-headers* nil)\n(defvar *received-body* nil)\n(defvar *received-method* nil)\n\n;; Handler on destination that echoes back what it received\n(better-easy-handler (echo-handler :uri \"/echo\"\n                                   :acceptor-names '(destination-acceptor))\n    ()\n  (setf *received-headers* (hunchentoot:headers-in hunchentoot:*request*))\n  (setf *received-method* (hunchentoot:request-method hunchentoot:*request*))\n  (setf *received-body* (hunchentoot:raw-post-data :force-text t))\n  (setf (hunchentoot:content-type*) \"text/plain\")\n  (format nil \"Received: ~a ~a\"\n          *received-method*\n          (hunchentoot:script-name hunchentoot:*request*)))\n\n;; Handler that returns a specific status code (on destination)\n(better-easy-handler (status-handler :uri \"/status/:code\"\n                                     :acceptor-names '(destination-acceptor))\n    (code)\n  (setf (hunchentoot:return-code*) (parse-integer code))\n  (format nil \"Status: ~a\" code))\n\n;; Handler on source that forwards status requests\n(better-easy-handler (forward-status-handler :uri \"/status/:code\"\n                                             :acceptor-names '(source-acceptor))\n    (code)\n  (declare (ignore code)) ; path is preserved by forward-request\n  (hex:forward-request *destination-host* :keep-current-host nil))\n\n;; Handler for testing that source acceptor routing works\n(better-easy-handler (test-forward-exists :uri \"/test-forward-exists\"\n                                          :acceptor-names '(source-acceptor))\n    ()\n  \"forward handler exists\")\n\n;; Handler on source that forwards to destination\n;; Note: forward-request preserves the request path, so we forward /echo to /echo\n(better-easy-handler (forward-echo-handler :uri \"/echo\"\n                                           :acceptor-names '(source-acceptor))\n    (with-extra-headers)\n  (if with-extra-headers\n      (hex:forward-request *destination-host*\n                           :keep-current-host nil\n                           :extra-headers (list (cons :x-raft-forwarded \"test-value\")\n                                                (cons :x-custom-loop-detection \"enabled\")))\n      (hex:forward-request *destination-host* :keep-current-host nil)))\n\n\n(def-fixture state ()\n  (with-local-acceptor (host) ('destination-acceptor)\n    (with-global-binding ((*destination-host* host))\n      (&body))))\n\n(test test-echo-handler-directly\n  (with-fixture state ()\n    (setf *received-method* nil)\n    (let ((response (http-request (format nil \"~a/echo\" *destination-host*)\n                                  :want-string t)))\n      (is (equal \"Received: GET /echo\" response))\n      (is (eql :get *received-method*)))))\n\n(test test-source-acceptor-routing\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (let ((response (http-request (format nil \"~a/test-forward-exists\" source-host)\n                                    :want-string t)))\n        (is (equal \"forward handler exists\" response))))))\n\n(test test-basic-forward\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-headers* nil\n            *received-body* nil\n            *received-method* nil)\n      (let ((response (http-request (format nil \"~a/echo\" source-host)\n                                    :want-string t)))\n        ;; Verify request was forwarded to destination and processed\n        (is (equal \"Received: GET /echo\" response))\n        (is (eql :get *received-method*))))))\n\n(test test-forward-with-custom-headers\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-headers* nil)\n      (http-request (format nil \"~a/echo\" source-host)\n                    :additional-headers '((:x-custom-header . \"custom-value\"))\n                    :want-string t)\n      ;; Verify custom header was forwarded\n      (is (equal \"custom-value\"\n                 (cdr (assoc :x-custom-header *received-headers*)))))))\n\n(test test-forward-post-body\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-body* nil\n            *received-method* nil)\n      (http-request (format nil \"~a/echo\" source-host)\n                    :method :post\n                    :content \"test body content\"\n                    :want-string t)\n      ;; Verify POST method and body were forwarded\n      (is (eql :post *received-method*))\n      (is (equal \"test body content\" *received-body*)))))\n\n(test test-forward-status-code\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (multiple-value-bind (body code)\n          (http-request (format nil \"~a/status/404\" source-host)\n                        :want-string t)\n        (declare (ignore body))\n        ;; Verify status code was forwarded\n        (is (eql 404 code))))))\n\n(test test-keep-current-host-false\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-headers* nil)\n      (http-request (format nil \"~a/echo\" source-host)\n                    :want-string t)\n      ;; When keep-current-host is false, host should be the destination host\n      ;; X-Forwarded-Host should contain the original host\n      (is (not (null (cdr (assoc :x-forwarded-host *received-headers*))))))))\n\n(test test-keep-current-host-true\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-headers* nil)\n      (let ((uri (quri:uri source-host)))\n       (http-request (format nil \"http://example.com:~a/echo\" (quri:uri-port uri))\n                     :real-host (quri:uri-host uri)\n                     :want-string t))\n      ;; X-Forwarded-Host should still be set\n      (is (not (null (cdr (assoc :x-forwarded-host *received-headers*)))))\n      (assert-that (assoc-value *received-headers* :x-forwarded-host)\n                   (matches-regex \"example.com:.*\")))))\n\n(test test-forward-preserves-uri-path\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-headers* nil)\n      ;; The forwarding should preserve query parameters\n      (let ((response (http-request (format nil \"~a/echo?foo=bar\" source-host)\n                                    :want-string t)))\n        (is (str:contains? \"Received:\" response))))))\n\n(test test-forward-with-extra-headers-parameter\n  \"Test that extra-headers parameter correctly forwards custom headers for loop detection\"\n  (with-fixture state ()\n    (with-local-acceptor (source-host) ('source-acceptor)\n      (setf *received-headers* nil)\n      ;; Request the handler with the with-extra-headers parameter\n      (http-request (format nil \"~a/echo?with-extra-headers=t\" source-host)\n                    :want-string t)\n      ;; Verify both extra headers were forwarded to destination\n      (is (equal \"test-value\"\n                 (cdr (assoc :x-raft-forwarded *received-headers*))))\n      (is (equal \"enabled\"\n                 (cdr (assoc :x-custom-loop-detection *received-headers*)))))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-random-port.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/test-random-port\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:hunchentoot-extensions/random-port\n                #:acceptor-on-random-port))\n(in-package :hunchentoot-extensions/test-random-port)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (&body))\n\n(defclass test-acc (acceptor-on-random-port\n                    hunchentoot:easy-acceptor)\n  ())\n\n(hunchentoot:define-easy-handler (my-handler :uri \"/hello\" :acceptor-names '(foobar)) ()\n  \"OK\")\n\n;; TODO(T1368) This test is currently broken on our AWS ARM64 machine.\n#-(and linux arm64)\n(test happy-path-random-port\n  (let ((acceptor (make-instance 'test-acc :name 'foobar)))\n    (is-false (hunchentoot:started-p acceptor))\n    (hunchentoot:start acceptor)\n    (is-true (hunchentoot:started-p acceptor))\n    (is\n     (equal \"OK\"\n            (dex:get (format nil \"http://127.0.0.1:~a/hello\" (hunchentoot:acceptor-port acceptor))\n                     :read-timeout 3\n                     :connect-timeout 3)))\n    (hunchentoot:stop acceptor)\n    (is-false (hunchentoot:started-p acceptor))\n    (pass)))\n\n(test stop-then-start\n  (let ((acceptor (make-instance 'test-acc :name 'foobar)))\n    (is-false (hunchentoot:started-p acceptor))\n    (hunchentoot:start acceptor)\n    (is-true (hunchentoot:started-p acceptor))\n    (hunchentoot:stop acceptor)\n    (is-false (hunchentoot:started-p acceptor))\n    (hunchentoot:start acceptor)\n    (is-true (hunchentoot:started-p acceptor))\n    (is\n     (equal \"OK\"\n            (dex:get (format nil \"http://127.0.0.1:~a/hello\" (hunchentoot:acceptor-port acceptor))\n                     :read-timeout 3\n                     :connect-timeout 3)))\n    (hunchentoot:stop acceptor)\n    (is-false (hunchentoot:started-p acceptor))))\n\n(test allow-shutting-down-twice\n  (let ((acceptor (make-instance 'test-acc :name 'foobar)))\n    (hunchentoot:start acceptor)\n    (hunchentoot:stop acceptor)\n    (is-false (hunchentoot:started-p acceptor))\n    (finishes\n      (hunchentoot:stop acceptor))\n    (is-false (hunchentoot:started-p acceptor))))\n\n(test allow-shutting-down-without-starting\n  (let ((acceptor (make-instance 'test-acc :name 'foobar)))\n    (finishes\n     (hunchentoot:stop acceptor))\n    (finishes\n      (hunchentoot:stop acceptor))\n    (is-false (hunchentoot:started-p acceptor))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/test-url.lisp",
    "content": "(defpackage :util.test-url\n  (:use :cl\n        :fiveam\n        :hex)\n  (:import-from :hex\n                :missing-required-arg)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:hunchentoot-extensions\n                #:get-request-domain-prefix))\n(in-package :util.test-url)\n\n(def-suite* :util.test-url)\n\n(test simple-make-url\n  (is (equal \"/foo/bar\" (make-url \"/foo/bar\"))))\n\n(test simple-param\n  (is (equal \"/foo/bar?key=val\" (make-url \"/foo/bar\" :key \"val\")))\n  (is (equal \"/foo/bar?key=val&boo=eey\" (make-url \"/foo/bar\" :key \"val\" :boo \"eey\"))))\n\n(test there's-already-a-?-mark\n  (is (equal \"/foo/bar?key=val&boo=eey\" (make-url \"/foo/bar?key=val\" :boo \"eey\"))))\n\n(test what-if-arg-is-hash-map\n  (is (equal \"/foo/bar?add{line1}=foo\"\n             (make-url \"/foo/bar\" :add (alexandria:plist-hash-table `(\"line1\" \"foo\"))))))\n\n(test what-if-arg-is-hash-map-with-multiple-value\n  (let ((result (make-url \"/foo/bar\" :add (alexandria:plist-hash-table `(\"line1\" \"foo\" \"line2\" \"bar\")))))\n    (is (or\n         (equal \"/foo/bar?add{line1}=foo&add{line2}=bar\"\n                result)\n         (equal \"/foo/bar?add{line2}=bar&add{line1}=foo\" result)))))\n\n(test url-with-arg\n  (is (equal \"/foo/zoidberg\" (make-url \"/foo/:bar\" :bar \"zoidberg\")))\n  (is (equal \"/foo/zoidberg?car=2\" (make-url \"/foo/:bar\" :bar \"zoidberg\" :car 2)))\n  (signals missing-required-arg\n    (make-url \"/foo/:bar\")))\n\n(test variable-right-at-top\n  (is (equal \"/zoidberg\" (make-url \"/:bar\" :bar \"zoidberg\")))\n  (is (equal \"/\" (make-url \"/\"))))\n\n(test url-with-optional-arg\n  (is (equal \"/foo/page/zoidberg\" (make-url \"/foo(/page/:bar)\" :bar \"zoidberg\")))\n  (is (equal \"/foo/page/zoidberg?car=2\" (make-url \"/foo(/page/:bar)\" :bar \"zoidberg\" :car 2)))\n  (is (equal \"/foo\" (make-url \"/foo(/page/:bar)\"))))\n\n(test make-url-with-/\n  (is (equal \"/\" (make-url \"/\"))))\n\n(test use-hash-in-name\n  (is (equal \"/foo/zoid%23berg\" (make-url \"/foo/:bar\" :bar \"zoid#berg\"))))\n\n(test use-integer-in-name\n  (is (equal \"/foo/23\" (make-url \"/foo/:bar\" :bar 23))))\n\n(test make-url-with-http\n  (is (equal \"https://example.com\" (make-url \"https://example.com\")))\n  (is (equal \"https://example.com?foo=bar\" (make-url \"https://example.com\" :foo \"bar\"))))\n\n(test make-full-url-with\n  (with-fake-request ()\n   (is (equal \"http://example.com/foo\" (make-full-url\n                                      (make-instance\n                                       'hunchentoot:request\n                                       :uri \"/car/bar\"\n                                       :headers-in `((:host . \"example.com\")))\n                                      \"/foo\")))\n    (let ((req (make-instance\n                'hunchentoot:request\n                :uri \"/car/bar\"\n                :headers-in `((:host . \"example.com\")\n                              (:x-forwarded-proto . \"https\")))))\n      (is (equal \"https\" (hunchentoot:header-in :x-forwarded-proto req)))\n      (is (equal \"https://example.com/foo\" (make-full-url\n                                            req\n                                            \"/foo\"))))))\n\n(test get-request-domain-prefix\n  (with-fake-request ()\n    (is (equal \"http://example.com\" \n               (get-request-domain-prefix\n                (make-instance\n                 'hunchentoot:request\n                 :uri \"/foo\"\n                 :headers-in `((:host . \"example.com\"))))))\n    \n    (is (equal \"https://example.com\" \n               (get-request-domain-prefix\n                (make-instance\n                 'hunchentoot:request\n                 :uri \"/foo\"\n                 :headers-in `((:host . \"example.com\")\n                               (:x-forwarded-proto . \"https\"))))))\n    \n    (is (equal \"https://secure.com\" \n               (get-request-domain-prefix\n                (make-instance\n                 'hunchentoot:request\n                 :uri \"/foo\"\n                 :headers-in `((:host . \"secure.com:443\"))))))\n    \n    (is (equal \"http://localhost:3000\" \n               (get-request-domain-prefix\n                (make-instance\n                 'hunchentoot:request\n                 :uri \"/foo\"\n                 :headers-in `((:host . \"localhost:3000\"))))))\n    \n    (is (equal \"https://localhost:8080\" \n               (get-request-domain-prefix\n                (make-instance\n                 'hunchentoot:request\n                 :uri \"/foo\"\n                 :headers-in `((:host . \"localhost:8080\")\n                               (:x-forwarded-proto . \"https\"))))))))\n\n"
  },
  {
    "path": "src/hunchentoot-extensions/url.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :hex)\n\n(defun add-get-param-to-url (url name value)\n  \"URL is assumed to be a http URL. The pair of NAME and VALUE will be\nadded as a GET parameter to this URL. Assumes that there's no other\nparameter of the same name. Only checks if #\\? is part of the string\nto decide how to attach the new parameter to the end of the string.\"\n  ;; possible bug: doesn't check for #\\? which is written as, say,\n  ;; \"&x3f;\" - also, is there any other way a question mark could be a\n  ;; legitimate part of a URL?\n  (concatenate 'string\n               url\n               (if (find #\\? url :test #'char=)\n                 \"&\"\n                 \"?\")\n               name\n               \"=\"\n               (hunchentoot:url-encode value)))\n\n(defun %required-args (path)\n  (ecase (car path)\n    (:path\n     nil)\n    (:root\n     nil)\n    (:variable\n     (let ((name (intern (string-upcase (cadr path)) \"KEYWORD\")))\n       (list name)))\n    (:join\n     (append (%required-args (cadr path))\n             (%required-args (caddr path))))\n    (:optional nil)))\n\n(define-condition missing-required-arg ()\n  ((arg :initarg :arg)))\n\n(defun %apply-args-to-parse-tree (path args)\n  (assert path)\n  (ecase (car path)\n    (:root\n     \"/\")\n    (:path\n     (values (format nil \"/~a\" (cadr path))args))\n    (:variable\n     (let ((name (intern (string-upcase (cadr path)) \"KEYWORD\")))\n       (let ((value (assoc-value args name)))\n         (unless value\n           (error 'missing-required-arg :arg name))\n         (values (format nil \"/~a\" (urlencode:urlencode (format nil \"~a\"  value)))\n                 (remove name args :key 'car)))))\n    (:optional\n     ;; only render this if all required args are present\n     (let ((req (%required-args (cadr path))))\n       (log:info \"required args: ~s\" req)\n       (if (loop for r in req always\n                              (assoc-value args r))\n           (%apply-args-to-parse-tree (cadr path) args)\n           \"\")))\n    (:join\n     (multiple-value-bind (path1 args) (%apply-args-to-parse-tree (cadr path) args)\n       (multiple-value-bind (path2 args)\n           (%apply-args-to-parse-tree (caddr path) args)\n         (values (format nil \"~a~a\" path1 path2) args))))))\n\n(defun make-full-url (request &rest make-url-args)\n  (let ((part (apply 'make-url make-url-args)))\n    (format nil \"~a~a\"\n            (get-request-domain-prefix request)\n            part)))\n\n(defun make-url (path &rest rest &key &allow-other-keys)\n  (cond\n    ((and\n      (stringp path)\n      (or\n       (str:starts-with-p \"http:\" path)\n       (str:starts-with-p \"https:\" path)))\n     ;; note that this path of addint params is very different from\n     ;; the when we're dealint giwht url handlers since, in that case\n     ;; you might have things like /foo/:bar.\n     (let ((url (quri:uri path)))\n       (loop for (key value)  on rest by #'cddr do\n         (push (cons (string-downcase key) value) (quri:uri-query-params url)))\n       (quri:render-uri url)))\n    ((string= \"/\" path)\n     \"/\")\n    (t\n     (let* ((url-handler (when (symbolp path)\n                           (let ((url-handler(alexandria:assoc-value *url-list* path)))\n                             (unless url-handler\n                               (error \"No url handler for the name: ~s\" path))\n                               url-handler)))\n            (parse-tree (if url-handler\n                            (url-handler-parse-tree url-handler)\n                            (%make-uri-parse-tree path))))\n       (let ((args (alexandria:plist-alist rest)))\n         (multiple-value-bind (path args) (%apply-args-to-parse-tree parse-tree args)\n\n           (let ((ret (format nil \"~a~a\" (if url-handler\n                                             (url-handler-prefix url-handler)\n                                             \"\")\n                              path)))\n             (flet ((add (key value) (setf ret (add-get-param-to-url ret (string-downcase key)\n                                                                     (format nil \"~a\" value)))))\n               (loop for (key . value) in args do\n                    (cond\n                      ((hash-table-p value)\n                       (loop for (inner-key . inner-value) in (alexandria:hash-table-alist value) do\n                            (add (format nil \"~a{~a}\" key inner-key) inner-value)))\n                      (t (add key value)))))\n             ret)))))))\n\n(define-compiler-macro make-url (&whole whole name &rest args)\n    (cond\n      ((and (listp name)\n            (eql 'quote (car name)))\n       (let ((name (cadr name)))\n         (unless (get name 'handlerp)\n           #-ccl ;; the tests fail on CCL for some reason\n           (error \"make-url called with symbol ~S, but it has not been created as a handler yet\"\n                  name)))\n       whole)\n      (t whole)))\n\n(defun get-request-domain-prefix (&optional (request hunchentoot:*request*))\n  \"Like https://xyz.com or http://localhost:3014. Appropriate for use\n  in emails\"\n  (destructuring-bind (host &optional (port \"80\")) (str:split \":\" (hunchentoot:host request))\n    (let ((port (parse-integer port)))\n     (case port\n       (443 (format nil \"https://~a\" host))\n       (80 (format nil \"~a://~a\"\n                          (or\n                           (hunchentoot:header-in :x-forwarded-proto request)\n                           \"http\")\n                          host))\n       (otherwise\n        (format nil \"~a://~a:~a\"\n                          (or\n                           (hunchentoot:header-in :x-forwarded-proto request)\n                           \"http\")\n                          host\n                          port))))))\n"
  },
  {
    "path": "src/hunchentoot-extensions/webp.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :hunchentoot-extensions/webp\n  (:use #:cl\n        #:hex)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :hunchentoot-extensions/webp)\n\n(defun supports-webp? ()\n  (if (boundp 'hunchentoot:*request*)\n      (str:contains?  \"image/webp\" (hunchentoot:header-in :accept hunchentoot:*request*))))\n\n(defun fix-for-webp (url &key force)\n  (cond\n    ((or force (supports-webp?))\n     (let ((parts (str:split \"?\" url)))\n       (str:join \"?\" (cons\n                      (ppcre:regex-replace \"\\\\.[a-z]*$\" (car parts) \".webp\")\n                      (cdr parts)))))\n    (t\n     url)))\n"
  },
  {
    "path": "src/java/libs/java.libs.asd",
    "content": ";; Autogenerated: do not modify\n(defpackage :java.libs-asdf\n  (:use :cl :asdf))\n(in-package :java.libs-asdf)\n\n(defsystem java.libs\n   :class \"build-utils:java-library\"\n   :defsystem-depends-on (:build-utils)\n   :components (\n(\"build-utils:jar-file\" \"slf4j-simple-1.7.25\")\n(\"build-utils:jar-file\" \"jira-rest-java-client-core-5.2.4\")\n(\"build-utils:jar-file\" \"trello-java-wrapper-0.14\")\n(\"build-utils:jar-file\" \"slf4j-api-1.7.30\")\n(\"build-utils:jar-file\" \"org.eclipse.egit.github.core-5.10.0.202012080955-r\")\n(\"build-utils:jar-file\" \"commons-lang3-3.11\")\n(\"build-utils:jar-file\" \"jira-rest-java-client-api-5.2.4\")\n(\"build-utils:jar-file\" \"fugue-4.7.2\")\n(\"build-utils:jar-file\" \"asana-0.10.3\")\n(\"build-utils:jar-file\" \"bcprov-jdk15on-1.68\")\n(\"build-utils:jar-file\" \"json-20210307\")\n(\"build-utils:jar-file\" \"atlassian-util-concurrent-4.0.1\")\n(\"build-utils:jar-file\" \"atlassian-httpclient-library-2.1.5\")\n(\"build-utils:jar-file\" \"atlassian-httpclient-api-2.1.5\")\n(\"build-utils:jar-file\" \"joda-time-2.9.9\")\n(\"build-utils:jar-file\" \"guava-30.1.1-jre\")\n(\"build-utils:jar-file\" \"google-http-client-gson-1.20.0\")\n(\"build-utils:jar-file\" \"google-oauth-client-1.20.0\")\n(\"build-utils:jar-file\" \"google-http-client-1.20.0\")\n(\"build-utils:jar-file\" \"jsr305-3.0.2\")\n(\"build-utils:jar-file\" \"jersey-client-2.35\")\n(\"build-utils:jar-file\" \"jersey-media-json-jettison-2.35\")\n(\"build-utils:jar-file\" \"sal-api-4.4.2\")\n(\"build-utils:jar-file\" \"atlassian-event-4.1.1\")\n(\"build-utils:jar-file\" \"spring-beans-5.3.6\")\n(\"build-utils:jar-file\" \"httpmime-4.5.13\")\n(\"build-utils:jar-file\" \"httpasyncclient-cache-4.1.4\")\n(\"build-utils:jar-file\" \"httpclient-cache-4.5.13\")\n(\"build-utils:jar-file\" \"httpasyncclient-4.1.4\")\n(\"build-utils:jar-file\" \"httpclient-4.5.13\")\n(\"build-utils:jar-file\" \"commons-codec-1.15\")\n(\"build-utils:jar-file\" \"gson-2.3.1\")\n(\"build-utils:jar-file\" \"commons-logging-1.2\")\n(\"build-utils:jar-file\" \"jackson-databind-2.9.8\")\n(\"build-utils:jar-file\" \"jackson-core-2.9.8\")\n(\"build-utils:jar-file\" \"jackson-annotations-2.9.8\")\n(\"build-utils:jar-file\" \"failureaccess-1.0.1\")\n(\"build-utils:jar-file\" \"listenablefuture-9999.0-empty-to-avoid-conflict-with-guava\")\n(\"build-utils:jar-file\" \"checker-qual-3.8.0\")\n(\"build-utils:jar-file\" \"error_prone_annotations-2.5.1\")\n(\"build-utils:jar-file\" \"j2objc-annotations-1.3\")\n(\"build-utils:jar-file\" \"jersey-media-jaxb-2.35\")\n(\"build-utils:jar-file\" \"jersey-common-2.35\")\n(\"build-utils:jar-file\" \"jakarta.ws.rs-api-2.1.6\")\n(\"build-utils:jar-file\" \"jakarta.inject-2.6.1\")\n(\"build-utils:jar-file\" \"jettison-1.3.7\")\n(\"build-utils:jar-file\" \"spring-core-5.3.6\")\n(\"build-utils:jar-file\" \"jakarta.annotation-api-1.3.5\")\n(\"build-utils:jar-file\" \"osgi-resource-locator-1.0.3\")\n(\"build-utils:jar-file\" \"spring-jcl-5.3.6\")\n(\"build-utils:jar-file\" \"httpcore-nio-4.4.10\")\n(\"build-utils:jar-file\" \"httpcore-4.4.13\")\n))\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/ClientMain.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class ClientMain {\n\n    public static void main(String[] args) throws Exception {\n        if (args.length == 0) {\n            throw new IllegalArgumentException(\"No command specified. Use one of \" + Command.names());\n        }\n\n        PropertiesClient propertiesClient = new PropertiesClient();\n        JiraOAuthClient jiraOAuthClient = new JiraOAuthClient(propertiesClient);\n\n        List<String> argumentsWithoutFirst = Arrays.asList(args).subList(1, args.length);\n\n        new OAuthClient(propertiesClient, jiraOAuthClient).execute(Command.fromString(args[0]), argumentsWithoutFirst);\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/Command.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport java.util.Arrays;\nimport java.util.stream.Collectors;\n\npublic enum Command {\n    REQUEST_TOKEN(\"requestToken\"),\n    ACCESS_TOKEN(\"accessToken\"),\n    REQUEST(\"request\");\n\n    private final String name;\n\n    Command(final String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public static String names() {\n        return Arrays.asList(values()).stream()\n                .map(Command::getName)\n                .collect(Collectors.toList())\n                .toString();\n    }\n\n    public static Command fromString(String name) {\n        if (name != null) {\n            for (Command b : Command.values()) {\n                if (name.equalsIgnoreCase(b.name)) {\n                    return b;\n                }\n            }\n        }\n        return null;\n    }\n}"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/JiraOAuthClient.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl;\nimport com.google.api.client.auth.oauth.OAuthCredentialsResponse;\nimport com.google.api.client.auth.oauth.OAuthParameters;\n\nimport java.io.IOException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.spec.InvalidKeySpecException;\n\nimport static com.atlassian.oauth.client.example.PropertiesClient.JIRA_HOME;\n\npublic class JiraOAuthClient {\n\n    public final String jiraBaseUrl;\n    private final JiraOAuthTokenFactory oAuthGetAccessTokenFactory;\n    private final String authorizationUrl;\n\n    public JiraOAuthClient(PropertiesClient propertiesClient) throws Exception {\n        this(propertiesClient.getPropertiesOrDefaults().get(JIRA_HOME));\n    }\n\n    public JiraOAuthClient(String jiraBaseUrl) {\n        this.jiraBaseUrl = jiraBaseUrl;\n        this.oAuthGetAccessTokenFactory = new JiraOAuthTokenFactory(this.jiraBaseUrl);\n        authorizationUrl = jiraBaseUrl + \"/plugins/servlet/oauth/authorize\";\n    }\n\n    /**\n     * Gets temporary request token and creates url to authorize it\n     *\n     * @param consumerKey consumer key\n     * @param privateKey  private key in PKCS8 format\n     * @return request token value\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     * @throws IOException\n     */\n    public String getAndAuthorizeTemporaryToken(String consumerKey, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {\n        JiraOAuthGetTemporaryToken temporaryToken = oAuthGetAccessTokenFactory.getTemporaryToken(consumerKey, privateKey);\n        OAuthCredentialsResponse response = temporaryToken.execute();\n\n        //System.out.println(\"Token:\\t\\t\\t\" + response.token);\n        //System.out.println(\"Token secret:\\t\" + response.tokenSecret);\n\n        OAuthAuthorizeTemporaryTokenUrl authorizationURL = new OAuthAuthorizeTemporaryTokenUrl(authorizationUrl);\n        authorizationURL.temporaryToken = response.token;\n        //System.out.println(\"Retrieve request token. Go to \" + authorizationURL.toString() + \" to authorize it.\");\n\n        return response.token;\n    }\n\n    /**\n     * Gets acces token from JIRA\n     *\n     * @param tmpToken    temporary request token\n     * @param secret      secret (verification code provided by JIRA after request token authorization)\n     * @param consumerKey consumer ey\n     * @param privateKey  private key in PKCS8 format\n     * @return access token value\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     * @throws IOException\n     */\n    public String getAccessToken(String tmpToken, String secret, String consumerKey, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {\n        JiraOAuthGetAccessToken oAuthAccessToken = oAuthGetAccessTokenFactory.getJiraOAuthGetAccessToken(tmpToken, secret, consumerKey, privateKey);\n        OAuthCredentialsResponse response = oAuthAccessToken.execute();\n\n        //System.out.println(\"Access token:\\t\\t\\t\" + response.token);\n        return response.token;\n    }\n\n    /**\n     * Creates OAuthParameters used to make authorized request to JIRA\n     *\n     * @param tmpToken\n     * @param secret\n     * @param consumerKey\n     * @param privateKey\n     * @return\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     */\n    public OAuthParameters getParameters(String tmpToken, String secret, String consumerKey, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {\n        JiraOAuthGetAccessToken oAuthAccessToken = oAuthGetAccessTokenFactory.getJiraOAuthGetAccessToken(tmpToken, secret, consumerKey, privateKey);\n        oAuthAccessToken.verifier = secret;\n        return oAuthAccessToken.createParameters();\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/JiraOAuthGetAccessToken.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthGetAccessToken;\n\npublic class JiraOAuthGetAccessToken extends OAuthGetAccessToken {\n\n    /**\n     * @param authorizationServerUrl encoded authorization server URL\n     */\n    public JiraOAuthGetAccessToken(String authorizationServerUrl) {\n        super(authorizationServerUrl);\n        this.usePost = true;\n    }\n\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/JiraOAuthGetTemporaryToken.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthGetTemporaryToken;\n\npublic class JiraOAuthGetTemporaryToken extends OAuthGetTemporaryToken {\n\n    /**\n     * @param authorizationServerUrl encoded authorization server URL\n     */\n    public JiraOAuthGetTemporaryToken(String authorizationServerUrl) {\n        super(authorizationServerUrl);\n        this.usePost = true;\n    }\n\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/JiraOAuthTokenFactory.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthRsaSigner;\nimport com.google.api.client.http.apache.ApacheHttpTransport;\nimport org.apache.commons.codec.binary.Base64;\n\nimport java.security.KeyFactory;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PrivateKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\n\npublic class JiraOAuthTokenFactory {\n    protected final String accessTokenUrl;\n    protected final String requestTokenUrl;\n\n\n    public JiraOAuthTokenFactory(String jiraBaseUrl) {\n        this.accessTokenUrl = jiraBaseUrl + \"/plugins/servlet/oauth/access-token\";\n        ;\n        requestTokenUrl = jiraBaseUrl + \"/plugins/servlet/oauth/request-token\";\n    }\n\n    /**\n     * Initialize JiraOAuthGetAccessToken\n     * by setting it to use POST method, secret, request token\n     * and setting consumer and private keys.\n     *\n     * @param tmpToken    request token\n     * @param secret      secret (verification code provided by JIRA after request token authorization)\n     * @param consumerKey consumer ey\n     * @param privateKey  private key in PKCS8 format\n     * @return JiraOAuthGetAccessToken request\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     */\n    public JiraOAuthGetAccessToken getJiraOAuthGetAccessToken(String tmpToken, String secret, String consumerKey, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {\n        JiraOAuthGetAccessToken accessToken = new JiraOAuthGetAccessToken(accessTokenUrl);\n        accessToken.consumerKey = consumerKey;\n        accessToken.signer = getOAuthRsaSigner(privateKey);\n        accessToken.transport = new ApacheHttpTransport();\n        accessToken.verifier = secret;\n        accessToken.temporaryToken = tmpToken;\n        return accessToken;\n    }\n\n\n    /**\n     * Initialize JiraOAuthGetTemporaryToken\n     * by setting it to use POST method, oob (Out of Band) callback\n     * and setting consumer and private keys.\n     *\n     * @param consumerKey consumer key\n     * @param privateKey  private key in PKCS8 format\n     * @return JiraOAuthGetTemporaryToken request\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     */\n    public JiraOAuthGetTemporaryToken getTemporaryToken(String consumerKey, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {\n        JiraOAuthGetTemporaryToken oAuthGetTemporaryToken = new JiraOAuthGetTemporaryToken(requestTokenUrl);\n        oAuthGetTemporaryToken.consumerKey = consumerKey;\n        oAuthGetTemporaryToken.signer = getOAuthRsaSigner(privateKey);\n        oAuthGetTemporaryToken.transport = new ApacheHttpTransport();\n        oAuthGetTemporaryToken.callback = \"oob\";\n        return oAuthGetTemporaryToken;\n    }\n\n    /**\n     * @param privateKey private key in PKCS8 format\n     * @return OAuthRsaSigner\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     */\n    private OAuthRsaSigner getOAuthRsaSigner(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {\n        OAuthRsaSigner oAuthRsaSigner = new OAuthRsaSigner();\n        oAuthRsaSigner.privateKey = getPrivateKey(privateKey);\n        return oAuthRsaSigner;\n    }\n\n    /**\n     * Creates PrivateKey from string\n     *\n     * @param privateKey private key in PKCS8 format\n     * @return private key\n     * @throws NoSuchAlgorithmException\n     * @throws InvalidKeySpecException\n     */\n    private PrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {\n        byte[] privateBytes = Base64.decodeBase64(privateKey);\n        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateBytes);\n        KeyFactory kf = KeyFactory.getInstance(\"RSA\");\n        return kf.generatePrivate(keySpec);\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/OAuthAuthenticationHandler.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl;\nimport com.google.api.client.auth.oauth.OAuthCredentialsResponse;\nimport com.google.api.client.auth.oauth.OAuthParameters;\n\nimport java.io.IOException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.spec.InvalidKeySpecException;\nimport com.atlassian.jira.rest.client.api.AuthenticationHandler;\n    import com.atlassian.httpclient.api.Request;\n\nimport com.google.api.client.http.GenericUrl;\nimport java.security.*;\n\nimport static com.atlassian.oauth.client.example.PropertiesClient.JIRA_HOME;\n\n/**\n * Not part of original SDK. Built by arnold.\n */\npublic class OAuthAuthenticationHandler implements AuthenticationHandler {\n    private OAuthParameters parameters;\n    public OAuthAuthenticationHandler(OAuthParameters parameters) {\n        this.parameters = parameters;\n    }\n    @Override\n    public void configure(final Request.Builder builder) {\n        parameters.computeNonce();\n        parameters.computeTimestamp();\n        Request r = builder.build();\n        GenericUrl genericUrl = new GenericUrl(r.getUri());\n        System.out.println(\"From java: \" + genericUrl + \" \" + r.getMethod().toString());\n        try {\n            parameters.computeSignature(r.getMethod().toString(), genericUrl);\n        } catch (GeneralSecurityException e) {\n            throw new RuntimeException(e);\n        }\n        builder.setHeader(\"Authorization\", parameters.getAuthorizationHeader());\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/OAuthClient.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthParameters;\nimport com.google.api.client.http.GenericUrl;\nimport com.google.api.client.http.HttpRequest;\nimport com.google.api.client.http.HttpRequestFactory;\nimport com.google.api.client.http.HttpResponse;\nimport com.google.api.client.http.javanet.NetHttpTransport;\nimport com.google.common.collect.ImmutableMap;\nimport org.json.JSONObject;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Scanner;\nimport java.util.function.Function;\n\nimport static com.atlassian.oauth.client.example.PropertiesClient.ACCESS_TOKEN;\nimport static com.atlassian.oauth.client.example.PropertiesClient.CONSUMER_KEY;\nimport static com.atlassian.oauth.client.example.PropertiesClient.PRIVATE_KEY;\nimport static com.atlassian.oauth.client.example.PropertiesClient.REQUEST_TOKEN;\nimport static com.atlassian.oauth.client.example.PropertiesClient.SECRET;\n\npublic class OAuthClient {\n\n    private final Map<Command, Function<List<String>, Optional<Exception>>> actionHandlers;\n\n    private final PropertiesClient propertiesClient;\n    private final JiraOAuthClient jiraOAuthClient;\n\n    public OAuthClient(PropertiesClient propertiesClient, JiraOAuthClient jiraOAuthClient) {\n        this.propertiesClient = propertiesClient;\n        this.jiraOAuthClient = jiraOAuthClient;\n\n        actionHandlers = ImmutableMap.<Command, Function<List<String>, Optional<Exception>>>builder()\n                .put(Command.REQUEST_TOKEN, this::handleGetRequestTokenAction)\n                .put(Command.ACCESS_TOKEN, this::handleGetAccessToken)\n                .put(Command.REQUEST, this::handleGetRequest)\n                .build();\n    }\n\n    /**\n     * Executes action (if found) with  given lists of arguments\n     *\n     * @param action\n     * @param arguments\n     */\n    public void execute(Command action, List<String> arguments) {\n        actionHandlers.getOrDefault(action, this::handleUnknownCommand)\n                .apply(arguments)\n                .ifPresent(Throwable::printStackTrace);\n    }\n\n    private Optional<Exception> handleUnknownCommand(List<String> arguments) {\n        System.out.println(\"Command not supported. Only \" + Command.names() + \" are supported.\");\n        return Optional.empty();\n    }\n\n    /**\n     * Gets request token and saves it to properties file\n     *\n     * @param arguments list of arguments: no arguments are needed here\n     * @return\n     */\n    private Optional<Exception> handleGetRequestTokenAction(List<String> arguments) {\n        Map<String, String> properties = propertiesClient.getPropertiesOrDefaults();\n        try {\n            String requestToken = jiraOAuthClient.getAndAuthorizeTemporaryToken(properties.get(CONSUMER_KEY), properties.get(PRIVATE_KEY));\n            properties.put(REQUEST_TOKEN, requestToken);\n            propertiesClient.savePropertiesToFile(properties);\n            return Optional.empty();\n        } catch (Exception e) {\n            return Optional.of(e);\n        }\n    }\n\n    /**\n     * Gets access token and saves it to properties file\n     *\n     * @param arguments list of arguments: first argument should be secert (verification code provided by JIRA after request token authorization)\n     * @return\n     */\n    private Optional<Exception> handleGetAccessToken(List<String> arguments) {\n        Map<String, String> properties = propertiesClient.getPropertiesOrDefaults();\n        String tmpToken = properties.get(REQUEST_TOKEN);\n        String secret = arguments.get(0);\n\n        try {\n            String accessToken = jiraOAuthClient.getAccessToken(tmpToken, secret, properties.get(CONSUMER_KEY), properties.get(PRIVATE_KEY));\n            properties.put(ACCESS_TOKEN, accessToken);\n            properties.put(SECRET, secret);\n            propertiesClient.savePropertiesToFile(properties);\n            return Optional.empty();\n        } catch (Exception e) {\n            return Optional.of(e);\n        }\n    }\n\n    /**\n     * Makes request to JIRA to provided url and prints response contect\n     *\n     * @param arguments list of arguments: first argument should be request url\n     * @return\n     */\n    private Optional<Exception> handleGetRequest(List<String> arguments) {\n        Map<String, String> properties = propertiesClient.getPropertiesOrDefaults();\n        String tmpToken = properties.get(ACCESS_TOKEN);\n        String secret = properties.get(SECRET);\n        String url = arguments.get(0);\n        propertiesClient.savePropertiesToFile(properties);\n\n        try {\n            OAuthParameters parameters = jiraOAuthClient.getParameters(tmpToken, secret, properties.get(CONSUMER_KEY), properties.get(PRIVATE_KEY));\n            HttpResponse response = getResponseFromUrl(parameters, new GenericUrl(url));\n            parseResponse(response);\n            return Optional.empty();\n        } catch (Exception e) {\n            return Optional.of(e);\n        }\n    }\n\n    /**\n     * Prints response content\n     * if response content is valid JSON it prints it in 'pretty' format\n     *\n     * @param response\n     * @throws IOException\n     */\n    private void parseResponse(HttpResponse response) throws IOException {\n        Scanner s = new Scanner(response.getContent()).useDelimiter(\"\\\\A\");\n        String result = s.hasNext() ? s.next() : \"\";\n\n        try {\n            JSONObject jsonObj = new JSONObject(result);\n            System.out.println(jsonObj.toString(2));\n        } catch (Exception e) {\n            System.out.println(result);\n        }\n    }\n\n    /**\n     * Authanticates to JIRA with given OAuthParameters and makes request to url\n     *\n     * @param parameters\n     * @param jiraUrl\n     * @return\n     * @throws IOException\n     */\n    private static HttpResponse getResponseFromUrl(OAuthParameters parameters, GenericUrl jiraUrl) throws IOException {\n        HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(parameters);\n        HttpRequest request = requestFactory.buildGetRequest(jiraUrl);\n        return request.execute();\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/PropertiesClient.java",
    "content": "package com.atlassian.oauth.client.example;\n\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Maps;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.stream.Collectors;\n\npublic class PropertiesClient {\n    public static final String CONSUMER_KEY = \"consumer_key\";\n    public static final String PRIVATE_KEY = \"private_key\";\n    public static final String REQUEST_TOKEN = \"request_token\";\n    public static final String ACCESS_TOKEN = \"access_token\";\n    public static final String SECRET = \"secret\";\n    public static final String JIRA_HOME = \"jira_home\";\n\n\n    private final static Map<String, String> DEFAULT_PROPERTY_VALUES = ImmutableMap.<String, String>builder()\n            .put(JIRA_HOME, \"unused\")\n            .put(CONSUMER_KEY, \"unused\")\n            .put(PRIVATE_KEY, \"unused\")\n            .build();\n\n    private final String fileUrl;\n    private final String propFileName = \"config.properties\";\n\n    public PropertiesClient() throws Exception {\n        fileUrl = \"./\" + propFileName;\n    }\n\n    public Map<String, String> getPropertiesOrDefaults() {\n        try {\n            Map<String, String> map = toMap(tryGetProperties());\n            map.putAll(Maps.difference(map, DEFAULT_PROPERTY_VALUES).entriesOnlyOnRight());\n            return map;\n        } catch (FileNotFoundException e) {\n            tryCreateDefaultFile();\n            return new HashMap<>(DEFAULT_PROPERTY_VALUES);\n        } catch (IOException e) {\n            return new HashMap<>(DEFAULT_PROPERTY_VALUES);\n        }\n    }\n\n    private Map<String, String> toMap(Properties properties) {\n        return properties.entrySet().stream()\n                .filter(entry -> entry.getValue() != null)\n                .collect(Collectors.toMap(o -> o.getKey().toString(), t -> t.getValue().toString()));\n    }\n\n    private Properties toProperties(Map<String, String> propertiesMap) {\n        Properties properties = new Properties();\n        propertiesMap.entrySet()\n                .stream()\n                .forEach(entry -> properties.put(entry.getKey(), entry.getValue()));\n        return properties;\n    }\n\n    private Properties tryGetProperties() throws IOException {\n        InputStream inputStream = new FileInputStream(new File(fileUrl));\n        Properties prop = new Properties();\n        prop.load(inputStream);\n        return prop;\n    }\n\n    public void savePropertiesToFile(Map<String, String> properties) {\n        OutputStream outputStream = null;\n        File file = new File(fileUrl);\n\n        try {\n            outputStream = new FileOutputStream(file);\n            Properties p = toProperties(properties);\n            p.store(outputStream, null);\n        } catch (Exception e) {\n            System.out.println(\"Exception: \" + e);\n        } finally {\n            closeQuietly(outputStream);\n        }\n    }\n\n    public void tryCreateDefaultFile() {\n        System.out.println(\"Creating default properties file: \" + propFileName);\n        tryCreateFile().ifPresent(file -> savePropertiesToFile(DEFAULT_PROPERTY_VALUES));\n    }\n\n    private Optional<File> tryCreateFile() {\n        try {\n            File file = new File(fileUrl);\n            file.createNewFile();\n            return Optional.of(file);\n        } catch (IOException e) {\n            return Optional.empty();\n        }\n    }\n\n    private void closeQuietly(Closeable closeable) {\n        try {\n            if (closeable != null) {\n                closeable.close();\n            }\n        } catch (IOException e) {\n            // ignored\n        }\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/atlassian/oauth/client/example/TokenAuthenticationHandler.java",
    "content": "package com.atlassian.oauth.client.example;\n\nimport com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl;\nimport com.google.api.client.auth.oauth.OAuthCredentialsResponse;\nimport com.google.api.client.auth.oauth.OAuthParameters;\n\nimport java.io.IOException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.spec.InvalidKeySpecException;\nimport com.atlassian.jira.rest.client.api.AuthenticationHandler;\n    import com.atlassian.httpclient.api.Request;\n\nimport com.google.api.client.http.GenericUrl;\nimport java.security.*;\n\nimport static com.atlassian.oauth.client.example.PropertiesClient.JIRA_HOME;\n\n/**\n * Not part of original SDK. Built by arnold.\n */\npublic class TokenAuthenticationHandler implements AuthenticationHandler {\n    private String token;\n    public TokenAuthenticationHandler(String token) {\n        this.token = token;\n    }\n\n    @Override\n    public void configure(final Request.Builder builder) {\n        builder.setHeader(\"Authorization\",\n                          String.format(\"Bearer %s\", token));\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/tdrhq/PrimitiveWrapper.java",
    "content": "// Copyright 2018-Present Modern Interpreters Inc.\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\npackage com.tdrhq;\n\npublic class PrimitiveWrapper {\n    private Object obj;\n    public PrimitiveWrapper(Object obj) {\n        this.obj = obj;\n    }\n\n    /**\n     *\n     */\n    public int getType() {\n        if (obj instanceof Boolean) {\n            return 1;\n        } else if (obj instanceof Character) {\n            return 2;\n        }\n\n        throw new RuntimeException(\"unsupported type:\" + obj);\n     }\n\n    public Boolean asBoolean() {\n        return (Boolean) obj;\n    }\n\n    public Character asCharacter() {\n        return (Character) obj;\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/tdrhq/SimpleNativeLibrary.java",
    "content": "// Copyright 2018-Present Modern Interpreters Inc.\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\npackage com.tdrhq;\n\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Serializable;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.logging.Logger;\nimport org.apache.commons.lang3.reflect.ConstructorUtils;\nimport org.apache.commons.lang3.reflect.FieldUtils;\n\npublic class SimpleNativeLibrary implements Serializable {\n    private static final long serialVersionUID = -1372385985412438808L;\n    public static Object bfalse = new Object();\n    public static Object btrue = new Object();\n\n    public SimpleNativeLibrary() {\n    }\n\n    public static void copyResource(String resourceName, String outputFile) throws Exception {\n        InputStream is = SimpleNativeLibrary.class.getResourceAsStream(resourceName);\n        if (is == null) {\n            throw new RuntimeException(\"Could not find resource: \" + resourceName);\n        }\n        OutputStream os = new FileOutputStream(outputFile);\n\n        byte[] buff = new byte[4096];\n        int len;\n        while ((len = is.read(buff)) > 0) {\n            os.write(buff, 0, len);\n        }\n\n        os.close();\n        is.close();\n    }\n\n    public static Object send_static_method_wrapped(Class klass, String methodName, Object[] args) throws Exception {\n        Object ret = send_static_method(klass, methodName, args);\n        return wrapPrimitives(ret);\n    }\n\n    public static Object wrapPrimitives(Object ret) {\n        if (ret == null) {\n            return ret;\n        }\n\n        if (ret instanceof Boolean) {\n            return new PrimitiveWrapper(ret);\n        }\n\n        if (ret instanceof Character) {\n            return new PrimitiveWrapper(ret);\n        }\n\n        return ret;\n    }\n\n    public static Object send_static_method(Class klass, String methodName, Object[] args) throws Exception {\n        if (klass == null) {\n            throw new RuntimeException(\"null class for \" + methodName);\n        }\n        if (args == null) {\n            //            args = new Object[0];\n        }\n        try {\n            Object ret =  Whitebox.invokeStaticMethod(klass, methodName, args);\n            return ret;\n        } catch (NoSuchMethodException e) {\n            throw new RuntimeException(e);\n        } catch (InvocationTargetException e) {\n            if (e.getTargetException() instanceof Exception) {\n                throw (Exception) e.getTargetException();\n            }\n\n            e.printStackTrace();\n            throw new RuntimeException(e);\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n\n    public static Object send_method(Object o, String methodName, Object[] args) throws Throwable {\n        try {\n            return Whitebox.invokeMethod(o, methodName, args);\n        } catch (NoSuchMethodException e) {\n            e.printStackTrace();\n            throw new RuntimeException(e);\n        } catch (InvocationTargetException e) {\n            e.printStackTrace();\n            throw e.getCause();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n            throw new RuntimeException(e);\n        }\n    }\n\n\n    public static Object send_method_wrapped(Object o, String methodName, Object[] args) throws Throwable {\n        return wrapPrimitives(send_method(o, methodName, args));\n    }\n\n    public static Object newInstance(Class klass, Object[] args)\n        throws Throwable {\n        try {\n            return ConstructorUtils.invokeConstructor(klass, args);\n        } catch (InvocationTargetException e) {\n            throw e.getCause();\n        }\n    }\n\n    public static Object newInstance_wrapped(Class klass, Object[] args)\n        throws Throwable {\n        if (args == null) {\n            System.out.println(\"got null args\");\n            args = new Object[0];\n        }\n        if (klass == null) {\n            throw new RuntimeException(\"klass is null\");\n        }\n\n        try {\n            return wrapPrimitives(newInstance(klass, args));\n        } catch (Exception e) {\n            e.printStackTrace();\n            throw e;\n        }\n    }\n\n    public static Object getLogger() {\n        return Logger.getLogger(\"io.jipr.lisp\");\n    }\n\n    public static Character charFromCodePoint(char ch) {\n        return Character.valueOf(ch);\n    }\n\n    public static Object readStaticField(Class klass, String field) throws Exception {\n        return FieldUtils.readStaticField(klass, field, true);\n    }\n\n    public static Object writeStaticField(Class klass, String field, Object val) throws Exception {\n        FieldUtils.writeStaticField(klass, field, val);\n        return val;\n    }\n\n    public static Object readField(Object o, String field) throws Exception {\n        return FieldUtils.readField(o, field, true);\n    }\n\n    public static Object writeField(Object o, String field, Object val) throws Exception {\n        FieldUtils.writeField(o, field, val);\n        return val;\n    }\n\n    public static void throwJavaException(Exception e) throws Exception {\n        throw e;\n    }\n}\n"
  },
  {
    "path": "src/java/main/com/tdrhq/Whitebox.java",
    "content": "// Copyright 2018-Present Modern Interpreters Inc.\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\npackage com.tdrhq;\n\nimport java.lang.reflect.Array;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.ClassUtils;\nimport org.apache.commons.lang3.reflect.MethodUtils;\n\nclass Whitebox {\n    public final static Class[] EMPTY_CLASSES = new Class[0];\n\n    public static class CacheKey {\n        public final Class klass;\n        public final String method;\n        public final Class[] args;\n\n        public CacheKey(Class klass, String method, Class[] args) {\n            this.klass = klass;\n            this.method = method;\n            this.args = args;\n        }\n\n        @Override\n        public boolean equals(Object _other) {\n            CacheKey other = (CacheKey) _other;\n            return klass.equals(other.klass) &&\n                    method.equals(other.method) &&\n                    Arrays.equals(args, other.args);\n        }\n\n        @Override\n        public int hashCode() {\n            return klass.hashCode() | method.hashCode() | Arrays.hashCode(args);\n        }\n    }\n\n    static Map<CacheKey, Method> methodCache = new ConcurrentHashMap<>();\n\n    static Object invokeMethod(Object o, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n        Method method = getMatchingAccessibleMethod(o.getClass(), methodName, args);\n        if (method == null) {\n            throw new RuntimeException(\"couldn't find an appropriate method\");\n        }\n\n        // This *shouldn't* be needed, but the JVM causes some problems in particular\n        // with some methods in ArrayList$Itr, oh well.\n        method.setAccessible(true);\n\n        return method.invoke(o, toVarArgs(method, args));\n    }\n\n    static Method getMatchingAccessibleMethod(Class klass, String methodName, Object[] args) {\n        Class[] classes = EMPTY_CLASSES;\n\n        if (args.length > 0) {\n            classes = new Class[args.length];\n            for (int i = 0; i < args.length; i++) {\n                classes[i] = (args[i] == null) ? null : args[i].getClass();\n            }\n        }\n        CacheKey cacheKey = new CacheKey(klass, methodName, classes);\n        Method old = methodCache.get(cacheKey);\n        if (old == null) {\n            old = MethodUtils.getMatchingAccessibleMethod(klass, methodName, classes);\n            if (old == null) {\n                String str = \"could not find appropriate method for: \" + klass\n                        + \", \" + methodName + \" and args: \";\n                for (Class param : classes) {\n                    str += param.getName() + \", \";\n                }\n                str = str.substring(0, str.length() - 2);\n                throw new RuntimeException(str);\n            }\n            methodCache.put(cacheKey, old);\n        }\n        return old;\n    }\n\n    static Object invokeStaticMethod(Class klass, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n        Method method = getMatchingAccessibleMethod(klass, methodName, args);\n\n        if (method == null) {\n            throw new RuntimeException(\"couldn't find an appropriate method\");\n        }\n\n        return method.invoke(null, toVarArgs(method, args));\n    }\n\n\n\n    private static Object[] toVarArgs(final Method method, Object[] args) {\n        if (method.isVarArgs()) {\n            final Class<?>[] methodParameterTypes = method.getParameterTypes();\n            args = getVarArgs(args, methodParameterTypes);\n        }\n        return args;\n    }\n\n    /**\n     * <p>Given an arguments array passed to a varargs method, return an array of arguments in the canonical form,\n     * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type.\n     * </p>\n     *\n     * @param args the array of arguments passed to the varags method\n     * @param methodParameterTypes the declared array of method parameter types\n     * @return an array of the variadic arguments passed to the method\n     * @since 3.5\n     */\n    static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {\n        if (args.length == methodParameterTypes.length\n                && args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) {\n            // The args array is already in the canonical form for the method.\n            return args;\n        }\n\n        // Construct a new array matching the method's declared parameter types.\n        final Object[] newArgs = new Object[methodParameterTypes.length];\n\n        // Copy the normal (non-varargs) parameters\n        System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);\n\n        // Construct a new array for the variadic parameters\n        final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();\n        final int varArgLength = args.length - methodParameterTypes.length + 1;\n\n        Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);\n        // Copy the variadic arguments into the varargs array.\n        System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);\n\n        if(varArgComponentType.isPrimitive()) {\n            // unbox from wrapper type to primitive type\n            varArgsArray = ArrayUtils.toPrimitive(varArgsArray);\n        }\n\n        // Store the varargs array in the last position of the array to return\n        newArgs[methodParameterTypes.length - 1] = varArgsArray;\n\n        // Return the canonical varargs array.\n        return newArgs;\n    }\n\n}\n"
  },
  {
    "path": "src/java/main/java.main.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :java.main-asdf\n  (:use :cl :asdf))\n(in-package :java.main-asdf)\n\n(defsystem java.main\n  :class \"build-utils:java-library\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:java.libs)\n  :components ((:module \"com/atlassian/oauth/client/example\"\n                :components ((\"build-utils:java-file\" \"ClientMain\")\n                             (\"build-utils:java-file\" \"JiraOAuthClient\")\n                             (\"build-utils:java-file\" \"JiraOAuthGetTemporaryToken\")\n                             (\"build-utils:java-file\" \"OAuthAuthenticationHandler\")\n                             (\"build-utils:java-file\" \"TokenAuthenticationHandler\")\n                             (\"build-utils:java-file\" \"PropertiesClient\")\n                             (\"build-utils:java-file\" \"Command\")\n                             (\"build-utils:java-file\" \"JiraOAuthGetAccessToken\")\n                             (\"build-utils:java-file\" \"JiraOAuthTokenFactory\")\n                             (\"build-utils:java-file\" \"OAuthClient\")))\n               (:module \"com/tdrhq\"\n                :components (#-screenshotbot-oss\n                             (\"build-utils:java-file\" \"CustomCommitService\")\n                             #-screenshotbot-oss\n                             (\"build-utils:java-file\" \"Github\")\n                             #-screenshotbot-oss\n                             (\"build-utils:java-file\" \"PemUtils\")\n                             (\"build-utils:java-file\" \"PrimitiveWrapper\")\n                             (\"build-utils:java-file\" \"SimpleNativeLibrary\")\n                             #-screenshotbot-oss\n                             (\"build-utils:java-file\" \"TdrhqS3\")\n                             (\"build-utils:java-file\" \"Whitebox\")))))\n"
  },
  {
    "path": "src/jvm/jvm.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :jvm.system\n  (:use #:cl\n        #:asdf))\n(in-package :jvm.system)\n\n(defsystem :jvm\n  :serial t\n  :depends-on (:str\n               :cffi\n               :cl-fad\n               :java.main\n               (:feature :lispworks :jvm/lispcalls)\n               (:feature :lispworks :deliver-utils)\n               :log4cl\n               :util.misc\n               :util/health-check\n               :util\n               :trivial-garbage)\n  :components ((:file \"jvm\")))\n\n(defclass lispcalls-system (asdf:system)\n  ())\n\n#+lispworks\n(defmethod perform ((o compile-op) (s lispcalls-system))\n  (let ((output (asdf:output-file o s)))\n    (uiop:copy-file (sys:lispworks-file \"etc/lispcalls.jar\")\n                    output)))\n\n(defmethod operation-done-p ((o compile-op) (s lispcalls-system))\n  (let ((output (asdf:output-file o s)))\n    (uiop:file-exists-p output)))\n\n(defmethod asdf:output-files ((o compile-op) (s lispcalls-system))\n  (list #P \"lispcalls.jar\"))\n\n#+lispworks\n(defsystem :jvm/lispcalls\n  :class lispcalls-system)\n"
  },
  {
    "path": "src/jvm/jvm.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :jvm\n  (:use :cl\n   :alexandria)\n  #+lispworks\n  (:import-from #:deliver-utils/common\n                #:guess-root)\n  (:import-from #:util/misc\n                #:relpath)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:export :jvm-init\n           :*libjvm*\n           :free-memory\n   :total-memory\n   :*initedp*\n   :jvm-supported-p))\n(in-package :jvm)\n\n(defvar *initedp* nil)\n\n#+lispworks\n(eval-when (:compile-toplevel  :load-toplevel)\n  (require \"java-interface\"))\n\n(defun root ()\n  (cond\n    #+lispworks\n    ((hcl:delivered-image-p)\n     (guess-root))\n    (t\n     (truename\n      (path:catdir\n       (asdf:system-source-directory :java.main)\n       \"../../../\")))))\n\n(defvar *classpath-relative*\n  (let ((root (root)))\n   (loop for path in (build-utils:java-class-path\n                      (asdf:find-system :java.main))\n         collect\n         (relpath path root))))\n\n(defun jvm-get-classpath ()\n  (let ((class-path\n          (let ((root (root)))\n            (loop for path in *classpath-relative*\n                  collect (path:catfile root path)))))\n    #+ccl\n    (pushnew (asdf:system-relative-pathname :cl+j \"cl_j.jar\")\n             class-path)\n\n    #+lispworks\n    (pushnew\n     (relpath (asdf:output-file 'asdf:compile-op (asdf:find-system :jvm/lispcalls)) (root))\n     class-path)\n    (log:info \"Using classpath: ~S\" class-path)\n\n    class-path))\n\n(defvar *libjvm* nil\n  \"Configure with --libjvm\")\n\n(defun libjvm.so ()\n  (or\n   *libjvm*\n   (cond\n    ((uiop:os-windows-p)\n     (let* ((openjdk #P\"C:/Program Files/OpenJDK/\")\n            (versions (fad:list-directory openjdk)))\n       (loop for version in versions\n             for jvm = (path:catfile version \"bin/server/jvm.dll\")\n               if (path:-e jvm)\n               do (return jvm)\n             finally\n               (error \"Could not find java, pass '--libjvm' argument\"))))\n\n    (t\n     (let* ((platform #+arm64 \"arm64\"\n                      #-arm64 \"amd64\")\n            (guesses (list\n                      ;; Debian Trixie\n                      (format nil \"/usr/lib/jvm/java-21-openjdk-~a/lib/server/libjvm.so\" platform)\n                      ;; Debian bookworm\n                      (format nil \"/usr/lib/jvm/java-17-openjdk-~a/lib/server/libjvm.so\" platform)\n                      ;; Debian Bullseye\n                      (format nil \"/usr/lib/jvm/java-11-openjdk-~a/lib/server/libjvm.so\" platform)\n                      \"/usr/lib/jvm/java-11-openjdk/lib/server/libjvm.so\"\n                      \"/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/lib/server/libjvm.dylib\")))\n       (loop for guess in guesses\n             if (path:-e guess)\n               do (return guess)\n             finally\n               (error \"Could not find java or libjvm.so, pass --libjvm argument\")))))))\n\n#+ccl\n(defun jvm-init-for-ccl ()\n  (setf cl-user::*jvm-path* (libjvm.so))\n  (setf cl-user::*jvm-options*\n        (list \"-Xrs\"\n              (format nil \"-Djava.class.path=~a\"\n                      (str:join \":\" (mapcar 'namestring (jvm-get-classpath)))))))\n\n(defun jvm-init ()\n  #+lispworks\n  (lw-ji:init-java-interface\n   :java-class-path (jvm-get-classpath)\n   :option-strings (list #+nil\"-verbose\")\n   :jvm-library-path (libjvm.so))\n\n  #+ccl\n  (progn\n    (jvm-init-for-ccl)\n    (pushnew \"local-projects/cl+j-0.4/\" asdf:*central-registry*)\n    (asdf:load-system :cl+j)\n    (funcall (find-symbol \"JAVA-INIT\" \"CL+J\")))\n\n  (setf *initedp* t)\n\n  #+(and :lispworks (not :screenshotbot-oss))\n  (lw-ji:find-java-class \"io.tdrhq.TdrhqS3\"))\n\n#+lispworks\n(lw-ji:define-java-callers \"java.lang.Runtime\"\n  (get-runtime \"getRuntime\")\n  (%total-memory \"totalMemory\")\n  (%free-memory \"freeMemory\"))\n\n(defun total-memory ()\n  #+lispworks\n  (if *initedp*\n      (%total-memory (get-runtime))\n      0))\n\n(defun free-memory ()\n  #+lispworks\n  (if *initedp*\n      (%free-memory (get-runtime))\n      0))\n\n#+lispworks\n(def-health-check lispcalls.jar-is-accessible ()\n  (lw-ji:find-java-class \"com.lispworks.LispCalls\"))\n\n(defun jvm-supported-p ()\n  #+(and :lispworks :linux)\n  t)\n\n#+ (and :lispworks :linux)\n(pushnew :jvm-supported-p *features*)\n"
  },
  {
    "path": "src/nibble/nibble.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :nibble\n  :serial t\n  :depends-on (:log4cl\n               :auth\n               :cl-cron\n               :quri\n               :hunchentoot-extensions\n               :secure-random)\n  :components ((:file \"package\")\n               (:file \"nibble\")))\n\n(defsystem :nibble/tests\n  :serial t\n  :depends-on (:nibble\n                  :util.testing\n                :fiveam-matchers\n                :util/fiveam\n                :fiveam)\n  :components ((:file \"test-nibble\")))\n"
  },
  {
    "path": "src/nibble/nibble.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :nibble)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *nibbles* (make-hash-table))\n(defvar *named-nibbles* nil)\n\n(defvar *lock* (bt:make-lock))\n(defparameter *last-gc* 0)\n\n(defclass nibble ()\n  ((impl :initarg :impl)\n   (id :initarg :id\n       :reader nibble-id)\n   (name :initarg :name\n         :initform nil\n         :reader nibble-name\n         :documentation \"A name use for analytics and debugging purposes\")\n   (args :initarg :args)\n   (once :initarg :once\n         :initform nil)\n   (calledp :initform nil\n            :accessor calledp)\n   (acceptor :initarg :acceptor\n             :initform nil\n             :reader nibble-acceptor\n             :documentation \"The acceptor where this nibble was created from\")\n   (session :initarg :session)\n   (user :initarg :user\n         :initform nil\n         :accessor nibble-user)\n   (check-session-p :initarg :check-session-p\n                    :initform nil)\n   (ts :initarg :ts)))\n\n(defmethod allow-user-change ((nibble nibble))\n  \"Allow this nibble to switch users (i.e. this nibble will be used during a login flow.)\n\nReturns the nibble for convenience.\"\n  (setf (nibble-user nibble) nil)\n  nibble)\n\n(defmethod allow-user-change ((url string))\n  \"A URL will always allow user change. This defmethod is a convenience\nso we can propagate user changes without knowing if the next url is a\nnibble or not.\"\n  url)\n\n(defun all-nibbles ()\n  (bt:with-lock-held (*lock*)\n    (loop for v being the hash-values of *nibbles* collect v)))\n\n(defun gc (&key force)\n  (let ((ts (get-universal-time)))\n   (when (or force\n             (> ts (+ *last-gc* 3600)))\n     (setf *last-gc* ts)\n     (let ((exp-time (- (get-universal-time)\n                        (* 3 24 3600))))\n       (loop for nib in (all-nibbles)\n             if  (< (slot-value nib 'ts)\n                    exp-time)\n               do (bt:with-lock-held (*lock*)\n                    (remhash (nibble-id nib) *nibbles*)))))))\n\n(defclass nibble-acceptor-mixin ()\n  ((nibble-prefix :initarg :nibble-prefix\n                  :reader nibble-prefix))\n  (:default-initargs :nibble-prefix \"/n/\"))\n\n(defun make-id (ts)\n  ;; We'll end up with a 120bit integer.\n  (let ((entropy 88))\n    (logior\n     (ash (get-universal-time) entropy)\n     (secure-random:number (ash 1 entropy)))))\n\n(defun push-nibble (id nibble)\n  (bt:with-lock-held (*lock*)\n    (setf (gethash id *nibbles*)\n          nibble)))\n\n(defun get-nibble (id)\n  (bt:with-lock-held (*lock*)\n    (let ((nibble (gethash id *nibbles*)))\n      nibble)))\n\n\n(defun current-session ()\n  (when (boundp 'hunchentoot:*request*)\n   (auth:current-session)))\n\n(defmacro nibble (args-and-options &body body)\n  (cond\n    ((and\n      args-and-options\n      (symbolp args-and-options))\n     ;; this is a named-nibble\n     (let ((args (assoc-value *named-nibbles* args-and-options)))\n       `(nibble ()\n          (funcall ',args-and-options ,@ (loop for arg in args\n                                               appending\n                                               (list (intern (string arg) \"KEYWORD\")\n                                                     `(safe-parameter ',arg))))))\n     )\n    (t\n     (let* ((position (position-if 'keywordp args-and-options))\n            (args (subseq args-and-options 0 position))\n            (options (when position (subseq args-and-options position))))\n       (destructuring-bind (&key once name method (check-session-p t)) options\n         (declare (ignore method)) ;; I don't remember why we introduced this\n                                   ;; in the first place\n         `(call-nibble :once ,once\n                       :name ,name\n                       :args ',args\n                       :check-session-p ,check-session-p\n                       :impl (lambda ,args ,@body)))))))\n\n(defun call-nibble (&key once name args check-session-p impl)\n  (let* ((ts (get-universal-time))\n         (id (make-id ts))\n         (session (current-session))\n         (nibble (make-instance 'nibble\n                                 :impl impl\n                                 :name name\n                                 :session (when session\n                                            ;; In a test, we might not\n                                            ;; always call\n                                            ;; auth:with-sessions\n                                            ;; Which will cause nibble\n                                            ;; to fail. We could\n                                            ;; potentially fix all the\n                                            ;; tests to wrap with\n                                            ;; auth:with-session, but\n                                            ;; this works for now.\n                                            (auth:ensure-session-created session))\n                                 :acceptor (when (boundp 'hunchentoot:*acceptor*)\n                                                    hunchentoot:*acceptor*)\n                                 :user\n                                 (cond\n                                   ((boundp 'hunchentoot:*request*)\n                                    (auth:request-user hunchentoot:*request*))\n                                   (t\n                                    ;; We're most likely running in tests. As a\n                                    ;; safety measure, let's set it up\n                                    ;; so that we'll never be able to\n                                    ;; render this nibble again.\n                                    (gensym \"FAKE-NIBBLE-USER\")))\n                                 :check-session-p check-session-p\n                                 :once once\n                                 :args args\n                                 :ts ts\n                                 :id id)))\n    (push-nibble id nibble)))\n\n(defmethod hunchentoot:acceptor-dispatch-request ((self nibble-acceptor-mixin)\n                                                  request)\n  (cond\n    ((str:starts-with-p (nibble-prefix self) (hunchentoot:script-name request))\n     (let ((id-str (third (str:split \"/\" (hunchentoot:script-name request)))))\n       (cond\n         ((equal \"nnnnnnn\" id-str)\n          ;; Weird case. A version of our blog post had a reference to\n          ;; /n/nnnnnnn, and for a short while Wordpress made it a\n          ;; link. This resulted in crawlers continuing to crawl this\n          ;; page, and it messes up our Sentry logs. Oh well. T1420.\n          (setf (hunchentoot:return-code*) 404)          \n          \"Invalid URL\")\n         ((equal \"\" id-str)\n          (setf (hunchentoot:return-code*) 404)\n          \"Invalid URL\")\n         (t\n          (render-nibble self (parse-integer id-str))))))\n    (t\n     (call-next-method))))\n\n(defmethod render-nibble ((plugin nibble-acceptor-mixin) (nibble nibble))\n  (render-nibble plugin (nibble-id nibble)))\n\n(defun safe-parameter (arg)\n  (hunchentoot:parameter (str:downcase arg)))\n\n(defmethod nibble-render-logged-out (acceptor nibble)\n  <html>\n    <body>\n      Please log back in to view this page.\n      <a href= \"/\">Home</a>\n    </body>\n  </html>)\n\n(define-condition expired-nibble (warning)\n  ((name :initarg :name\n         :initform nil)\n   (src :initarg :src\n        :reader expired-nibble-src\n        :initform nil))\n  (:report (lambda (self stream)\n             (with-slots (name src) self\n               (format stream \"Expired nibble with name ~a (from ~a) was accessed\" name src)))))\n\n(defmethod render-nibble ((plugin nibble-acceptor-mixin) (id string))\n  (render-nibble plugin (parse-integer id)))\n\n(defmethod nibble-funcall ((plugin nibble-acceptor-mixin) nibble)\n  (with-slots (once impl args) nibble\n    (when once\n      ;; we need to make sure only one call of this nibble\n      ;; happens.\n      (bt:with-lock-held (*lock*)\n        (when (calledp nibble)\n          (error \"Calling nibble multiple times\"))\n        (setf (calledp nibble) t)))\n    (let ((args (loop for arg in args\n                      collect (safe-parameter arg))))\n      (apply impl args))))\n\n(defmethod maybe-render-html (self)\n  self)\n\n(defmethod maybe-render-html ((self markup:abstract-xml-tag))\n  (markup:write-html self))\n\n(defmethod render-nibble ((plugin nibble-acceptor-mixin) (id number))\n  (maybe-render-html\n   (let ((nibble (get-nibble id)))\n     (cond\n       ((null nibble)\n        (unless (str:containsp \"Bytespider\" (hunchentoot:user-agent))\n          (warn 'expired-nibble\n                :name (safe-parameter :_n)\n                :src (safe-parameter :_src)))\n        (setf (hunchentoot:return-code*) 410 #| GONE |# )\n        (setf (hunchentoot:header-out :x-expired-nibble) \"1\")\n        <html>\n        <body>\n        The page you're looking for has expired.\n        <a href= \"/\">Go back</a>\n        </body>\n        </html>)\n       (t\n        (with-slots (session check-session-p) nibble\n          (flet ((final-render ()\n                   (nibble-funcall plugin nibble)))\n            (cond\n              ((and (boundp 'hunchentoot:*request*) check-session-p)\n               ;; Before we call final-render, we should check\n               ;; the session and logged in user.\n               (let ((current-session (current-session)))\n                 (cond\n                   ((not (auth:is-same-session-disregarding-resets-p\n                          session current-session))\n                    ;; This is quite noisy, so we're getting rid of it for now\n                    #+nil\n                    (warn \"Incorrect session: this is not a cause for concern unless it's spiking\")\n                    (render-incorrect-session plugin))\n                   ((different-user-viewing-p nibble)\n                    (nibble-render-logged-out\n                     hunchentoot:*acceptor*\n                     nibble))\n                   (t\n                    (final-render)))))\n              (t\n               (final-render))))))))))\n\n(defun different-user-viewing-p (nibble)\n  \"Check that if it's the same user that created the nibble, except in\ncertain cases where the user object might change.\"\n  (let ((nibble-user (nibble-user nibble)))\n    (and\n     ;; if the nibble was created when they\n     ;; weren't logged in, and they are logged\n     ;; in now, that's always okay\n     nibble-user\n     (not (eql (auth:request-user hunchentoot:*request*)\n               nibble-user)))))\n\n(defmethod render-incorrect-session ((plugin nibble-acceptor-mixin))\n  <html>\n    <body>\n      <h1>You cannot view this page.</h1>\n      <p>This URL is tied to a specific browser and user. You should not share this URL, or copy-and-paste\n        it into another browser.</p>\n\n      <p>If this URL wasn't shared with you, please reach out to <a href= \"mailto:support@screenshotbot.io\">support@screenshotbot.io</a> and let us know. It might be a bug on our end.</p>\n      <a href= \"/\">Go back</a>\n    </body>\n  </html>)\n\n(defun nibble-full-url (nibble)\n  (hex:make-full-url\n   hunchentoot:*request*\n   (nibble-url nibble)))\n\n\n(defun guess-src ()\n  \"The original root script that generated this nibble. This isn't\nstored in the nibble itself, because we want this for debugging\npurposes to be maintained across server restarts.\"\n  (when (boundp 'hunchentoot:*request*)\n    (let ((param (hunchentoot:get-parameter \"_src\")))\n     (cond\n       (param\n        param)\n       (t\n        (hunchentoot:script-name*))))))\n\n(defun nibble-url (nibble)\n  (quri:render-uri\n   (quri:make-uri\n    :query `((\"_src\" . ,(guess-src))\n             (\"_n\" . ,(string-downcase (nibble-name nibble))))\n    :defaults (format nil \"~a~a\"\n                      (nibble-prefix (nibble-acceptor nibble))\n                      (slot-value nibble 'id)))))\n\n\n(defmethod markup:format-attr-val (stream (nibble nibble))\n  (format stream \"\\\"~a\\\"\"\n          (nibble-url nibble)))\n\n(defmethod hex:safe-redirect ((nibble nibble) &rest args)\n  (apply 'hex:safe-redirect (nibble-url nibble)\n          args))\n\n(defmacro defnibble (name args &body body)\n  `(progn\n     (defun ,name (&key ,@args)\n       ,@body)\n     (setf (assoc-value *named-nibbles* ',name)\n           ',args)))\n\n\n(cl-cron:make-cron-job 'gc :minute 43\n                       :hash-key 'gc)\n\n(defmethod print-object ((nibble nibble) out)\n  (format out \"#<NIBBLE ~a>\" (ignore-errors (slot-value nibble 'impl))))\n\n \n"
  },
  {
    "path": "src/nibble/package.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :nibble\n  (:use :cl\n   :alexandria)\n  (:export #:nibble\n           #:nibble-full-url\n           #:nibble-url\n           #:get-nibble\n           #:render-nibble\n           #:nibble-id\n           #:defnibble\n           #:nibble-current-user\n           #:nibble-acceptor-mixin\n           #:allow-user-change\n           #:nibble-funcall\n           #:expired-nibble-src\n           #:expired-nibble))\n"
  },
  {
    "path": "src/nibble/test-nibble.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :nibble/test-nibble\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:nibble\n                #:different-user-viewing-p\n                #:allow-user-change\n                #:nibble-full-url\n                #:expired-nibble\n                #:make-id\n                #:nibble-url\n                #:nibble-current-user\n                #:defnibble\n                #:nibble\n                #:render-nibble)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:starts-with\n                #:contains-string)\n  (:import-from #:hunchentoot\n                #:acceptor-dispatch-request)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :nibble/test-nibble)\n\n(def-suite* :nibble)\n(def-suite* :nibble/test-nibble :in :nibble)\n\n(defclass fake-acceptor (nibble:nibble-acceptor-mixin)\n  ())\n\n(defmethod nibble-current-user ((acceptor fake-acceptor))\n  :dummy-user)\n\n(def-fixture state ()\n  (let ((hunchentoot:*acceptor* (make-instance 'fake-acceptor)))\n   (&body)))\n\n(test preconditions\n  (with-fixture state ()\n   (is (equal \"foobar\"\n              (render-nibble\n               hunchentoot:*acceptor*\n               (nibble ()\n                 \"foobar\"))))))\n\n\n(defnibble foo ()\n  \"foobar2\")\n\n(test named-nibble\n  (with-fixture state ()\n    (is (equal \"foobar2\"\n               (render-nibble\n                hunchentoot:*acceptor*\n                (nibble foo))))))\n\n(defnibble foo-with-args (name)\n  (format nil \"hello ~a\" name))\n\n(test named-nibble-with-args\n  (with-fixture state ()\n    (util/testing:with-fake-request (:script-name \"/?name=arnold\")\n      (auth:with-sessions ()\n       (is (equal \"hello arnold\"\n                  (render-nibble\n                   hunchentoot:*acceptor*\n                   (nibble foo-with-args))))))))\n\n(test with-name-renders-url\n  (with-fixture state ()\n    (util/testing:with-fake-request ()\n      (auth:with-sessions ()\n       (let ((nibble (nibble (:name :foobar)\n                       \"\")))\n\n         (is (str:ends-with-p \"&_n=foobar\"\n                              (nibble-url  nibble)))\n         (assert-that (nibble-url nibble)\n                      (starts-with \"/n/\")))))))\n\n(test render-nibble\n  (with-fixture state ()\n    (is (str:containsp \"NIBBLE\"\n                       (format nil \"~a\" (nibble () \"dummy\"))))))\n\n(test make-id-happy-path\n  (with-fixture state ()\n    (is (> (make-id (get-universal-time)) 10000000))))\n\n(test expired-nibble\n  (with-fixture state ()\n    (util/testing:with-fake-request ()\n      (render-nibble hunchentoot:*acceptor* \"342343243343432232213123123\")\n      (is (eql 410 (hunchentoot:return-code hunchentoot:*reply*)))\n      (is (equal \"1\" (hunchentoot:header-out :x-expired-nibble))))))\n\n(test expired-nibble-signals-warnings\n  (with-fixture state ()\n    (util/testing:with-fake-request ()\n      (signals expired-nibble\n       (render-nibble hunchentoot:*acceptor* \"342343243343432232213123123\")))))\n\n\n(test nibble-full-url-happy-path\n  (with-fixture state ()\n    (util/testing:with-fake-request ()\n      (auth:with-sessions ()\n        (nibble-full-url (nibble ()))))))\n\n(test rendering-a-nibble-that-already-sent-a-response\n  (with-fixture state ()\n    (util/testing:with-fake-request ()\n      (auth:with-sessions ()\n        (finishes\n          (render-nibble\n           hunchentoot:*acceptor*\n           (nibble ()\n             ;; We might have already returned a response by this\n             ;; point.\n             nil)))))))\n\n(test expired-nibble-format\n  (assert-that (format nil \"~a\"\n                       (make-condition 'expired-nibble\n                                       :name \"foo\"\n                                       :src \"bleh\"))\n               (contains-string \"foo\")\n               (contains-string \"bleh\")))\n\n(test nnnnnnn-gives-invalid-url\n  (assert-that\n   (util/testing:with-fake-request (:script-name \"/n/nnnnnnn\")\n     (acceptor-dispatch-request hunchentoot:*acceptor*\n                                hunchentoot:*request*))))\n\n(test allow-user-change\n  (with-fixture state ()\n    (let ((nibble (nibble ()\n                    \"hellO\")))\n      (util/testing:with-fake-request ()\n        (is-true (different-user-viewing-p nibble))\n        (is (eql nibble (allow-user-change nibble)))\n        (is-false (different-user-viewing-p nibble))))))\n\n(test allow-user-change-for-strings\n  (with-fixture state ()\n    (is (equal \"foobar\"\n               (allow-user-change \"foobar\")))))\n"
  },
  {
    "path": "src/oidc/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package #:oidc\n  (:use-reexport #:oidc/oidc\n                 #:oidc/oauth))\n"
  },
  {
    "path": "src/oidc/oauth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :oidc/oauth\n    (:use #:cl)\n  (:import-from #:nibble\n                #:nibble\n                #:nibble-id)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:export #:make-oauth-url))\n(in-package :oidc/oauth)\n\n(defun make-oauth-url (url callback &rest args &key redirect-uri via &allow-other-keys)\n  \"Takes a base OAuth URL and a redirect nibble, and returns the complete URL\n   with redirect_uri and state parameters added.\n\nCALLBACK is called with three named arguments: code, error and the\nredirect-uri used. You can ignore the redirect-uri, but in many cases\nyou need the redirect-uri to request the token later.\n\nREDIRECT-URI overrides the redirect-uri. This is only useful for our\nGitHub implementation which is trying to redirect to\naccounts.screenshotbot.io\n\nSee documentation for VIA in oidc/oidc:OIDC class. It's a non-standard\npart of our implementation.\n\nAny other arguments will be added like (hex:make-url ... ),\ni.e. encoded as http arguments\"\n\n  (let ((url (apply #'hex:make-url url (remove-from-plist args :redirect-uri :via))))\n   (let* ((redirect-uri (or\n                         redirect-uri\n                         (hex:make-full-url hunchentoot:*request* \"/account/oauth-callback\")))\n          (redirect-uri\n            (cond\n              ((null via)\n               redirect-uri)\n              (t\n               (let ((via (quri:uri via))\n                     (uri (quri:uri redirect-uri)))\n                 (quri:render-uri\n                  (quri:make-uri\n                   :scheme (quri:uri-scheme via)\n                   :host (quri:uri-host via)\n                   :port (quri:uri-port via)\n                   :defaults uri))))))\n          (redirect-nibble (nibble (code error error_description)\n                             (funcall callback\n                                      :redirect-uri redirect-uri\n                                      :code code\n                                      :error-description error_description\n                                      :error error\n                                      :allow-other-keys t)))\n          (state (nibble-id redirect-nibble))\n          (state\n            (cond\n              ((null via)\n               state)\n              (t\n               (format nil \"~a,~a\" state (hex:make-full-url hunchentoot:*request* \"/\")))))\n          (uri (quri:uri url)))\n     (quri:render-uri\n      (quri:make-uri\n       :defaults uri\n       :query (append (quri:uri-query-params uri)\n                      `((\"redirect_uri\" . ,redirect-uri)\n                        (\"state\" . ,(princ-to-string state)))))))))\n"
  },
  {
    "path": "src/oidc/oidc.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :oidc\n  :serial t\n  :depends-on (#:util\n               #:alexandria\n               #:nibble\n               #:hunchentoot-extensions\n               #:cl-json\n               #:util/request\n               #:dexador\n               #:pkg\n               #:hunchentoot\n               #:quri)\n  :components ((:file \"oauth\")\n               (:file \"oidc\")\n               (:file \"all\")))\n\n(defsystem :oidc/tests\n  :serial t\n  :depends-on (#:oidc\n               #:util/fiveam\n               #:util.testing\n               #:cl-mock\n               #:fiveam-matchers)\n  :components ((:file \"test-oauth\")\n               (:file \"test-oidc\")))\n"
  },
  {
    "path": "src/oidc/oidc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :oidc/oidc\n    (:use #:cl\n          #:alexandria)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:oidc/oauth\n                #:make-oauth-url)\n  (:export #:oidc\n           #:issuer\n           #:discover\n           #:authorization-endpoint\n           #:token-endpoint\n           #:userinfo-endpoint\n           #:end-session-endpoint\n           #:client-id\n           #:client-secret\n           #:scope\n           #:oauth-access-token\n           #:access-token-str\n           #:oauth-get-access-token\n           #:oidc-callback\n           #:on-successful-oauth\n           #:after-authentication\n           #:make-oidc-auth-link\n           #:logout-link))\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass oauth-access-token ()\n  ((access-token :type (or null string)\n                 :initarg :access-token\n                 :accessor access-token-str)\n   (expires-in :type (or null integer)\n               :initarg :expires-in)\n   (refresh-token :type (or null string)\n                  :initarg :refresh-token)\n   (refresh-token-expires-in :type (or null integer)\n                             :initarg :refresh-token-expires-in)\n   (scope :type (or null string)\n          :initarg :scope)\n   (token-type :type (or null string)\n               :initarg :token-type)))\n\n(defclass oidc ()\n  ((issuer :initarg :issuer\n           :accessor issuer\n           :documentation \"The issuer URL, such as\n           https://accounts.google.com. We'll use OpenID discovery to\n           discover the rest.\")\n   (client-id :initarg :client-id\n              :accessor client-id)\n   (client-secret :initarg :client-secret\n                  :accessor client-secret)\n   (scope :initarg :scope\n          :accessor scope\n          :initform \"openid\"\n          :documentation \"The default scope used for authorization\")\n   (cached-discovery :initform nil\n                     :accessor cached-discovery)\n   (via :initarg :via\n        :initform nil\n        :reader oidc-via\n        :documentation \"Typically NIL, or perhaps https://screenshotbot.io. This adds a via\npoint for redirects. This is not a standard part of the OIDC protocol,\nbut rather a convenience for us to only have one domain listed in the\nIdP. For this to work, you must have created an ENTERPRISE-INSTALL\nobject on prod, other screenshotbot.io will reject it.\")))\n\n(defmethod logout-link ((self oidc))\n  \"RP initiated logout link: https://openid.net/specs/openid-connect-rpinitiated-1_0.html.\n\n  In theory, this should should be implemented using the end_session_endpoint, but for now we're only implementing it for AWS Cognito.\"\n  (error \"logout link not implemented for this\"))\n\n(defun check-https (url)\n  \"Our OpenID Connect implementation does not do id-token\n  verification. But to still ensure security we have to make sure all\n  our calls to the Auth server go ever HTTPS\"\n  (when url\n   (unless (equal \"https\" (quri:uri-scheme (quri:uri url)))\n     (error \"Using non https endpoint ~a for authentication\" url)))\n  url)\n\n(auto-restart:with-auto-restart (:retries 3 :sleep 1)\n  (defmethod discover ((oidc oidc))\n    \"Returns an alist of all the fields in the discovery document\"\n    (or\n     (cached-discovery oidc)\n     (setf (cached-discovery oidc)\n           (let ((url (format nil \"~a/.well-known/openid-configuration\"\n                              (check-https\n                               (issuer oidc)))))\n             (let ((ret\n                     (json:decode-json-from-string (dex:get url))))\n               (log:info \"Got ~S~%\" ret)\n               ret))))))\n\n\n(defmethod authorization-endpoint ((oidc oidc))\n  (check-https\n   (assoc-value (discover oidc) :authorization--endpoint)))\n\n(defmethod token-endpoint ((oidc oidc))\n  (check-https\n   (assoc-value (discover oidc) :token--endpoint)))\n\n(defmethod userinfo-endpoint ((oidc oidc))\n  (check-https\n   (assoc-value (discover oidc) :userinfo--endpoint)))\n\n(defmethod end-session-endpoint ((oidc oidc))\n  (check-https\n   (assoc-value (discover oidc) :end--session--endpoint)))\n\n(auto-restart:with-auto-restart (:attempt attempt)\n (defun oauth-get-access-token (token-url &key client_id client_secret code\n                                            redirect_uri)\n   (assert code)\n   (multiple-value-bind (resp token-resp-code)\n       (http-request token-url\n                     :method :post\n                     :want-string t\n                     :accept \"application/json\"\n                     :parameters\n                     `((\"client_id\" . ,client_id)\n                       (\"client_secret\" . ,client_secret)\n                       (\"code\" . ,code)\n                       (\"grant_type\" . \"authorization_code\")\n                       (\"redirect_uri\" . ,redirect_uri)))\n     (when (and\n            (< attempt 2)\n            (eql 502 token-resp-code))\n       ;; See T796. In particular, Okta seems to occassional return a\n       ;; 502. Are we allowed to retry in this situation? I don't know\n       ;; yet, but we'll find out right now. If this doesn't work, a\n       ;; more appropriate fix might be to restart the authentication\n       ;; flow all over again.\n       (warn \"Got a 502, retrying token-url.\")\n       (sleep 1)\n       (invoke-restart 'retry-oauth-get-access-token))\n     (let* ((resp\n              (with-extras ((\"response\" resp)\n                            (\"response-code\" token-resp-code))\n                (json:decode-json-from-string resp))))\n       (when (assoc-value resp :error)\n         (error \"oauth error: ~s\" (assoc-value resp :error--description)))\n       (flet ((v (x) (assoc-value resp x)))\n         (let ((access-token (make-instance 'oauth-access-token\n                                            :access-token (v :access--token)\n                                            :expires-in (v :expires--in)\n                                            :refresh-token (v :refresh--token)\n                                            :refresh-token-expires-in (v :refresh--token--expires--in)\n                                            :scope (v :scope)\n                                            :token-type (v :token--type))))\n           access-token))))))\n\n(defun make-json-request (url &rest args)\n  (multiple-value-bind (stream status-code)\n      (apply 'util/request:http-request url\n             :want-stream t\n             args)\n    (with-open-stream (stream stream)\n     (case status-code\n       (200\n        (values\n         (json:decode-json stream)\n         status-code))\n       (otherwise\n        (error \"Failed to make json request, status code ~a\" status-code))))))\n\n(define-condition authentication-error (error)\n  ((message :initarg :message\n            :reader authentication-error-message)))\n\n(defmethod user-info ((auth oidc) token)\n  (make-json-request (userinfo-endpoint auth)\n                     :method :post\n                     :parameters `((\"alt\" . \"json\"))\n                     :additional-headers `((\"Authorization\".\n                                                           ,(format nil \"Bearer ~a\"\n                                                                    (Access-token-str token))))))\n\n(defmethod oauth-callback ((auth oidc))\n  (error \"deprecated\"))\n\n\n(defmethod oidc-callback ((auth oidc) code redirect\n                          &key error\n                            error-description\n                            (error-redirect \"/\")\n                            original-redirect-uri)\n  (flet ((error-authenticating (message)\n           (hex:safe-redirect\n            (nibble ()\n              <html>\n                <body>\n                  There was an error authenticating: ,(progn message)\n                  <a href= error-redirect >Go Back</a>\n                </body>\n              </html>))))\n   (cond\n     (code\n      (let ((token (oauth-get-access-token\n                    (token-endpoint auth)\n                    :client_id (client-id auth)\n                    :client_secret (client-secret auth)\n                    :code code\n                    :redirect_uri original-redirect-uri)))\n        (let ((user-info\n                (user-info auth token)))\n          (log:debug \"Got user info ~S\" user-info)\n          (verify-userinfo auth user-info)\n          (handler-case\n              (after-authentication\n               auth\n               :user-id (assoc-value user-info :sub)\n               :email (assoc-value user-info :email)\n               :full-name (assoc-value user-info :name)\n               :avatar (assoc-value user-info :picture)\n               ;; We don't save the token, but in some cases, we need\n               ;; the token to fetch the avatar.\n               :token token)\n            (authentication-error (e)\n              (error-authenticating (authentication-error-message e)))))))\n     (t\n      ;; error the OAuth flow, most likely\n      (let ((message (format nil \"~a: ~a\"\n                             error\n                             (or error-description \"No details provided\"))))\n        (warn \"Oauth failed: ~a\" message)\n        (error-authenticating\n         message))))))\n\n(defmethod verify-userinfo (oidc user-info))\n\n(defgeneric after-authentication (oidc &key\n                                         user-id\n                                         email\n                                         full-name\n                                         avatar\n                                         token))\n\n(defmethod oidc-callback :after ((auth oidc) code redirect &key &allow-other-keys)\n  (declare (ignore code))\n  (hex:safe-redirect redirect))\n\n(defmethod make-oidc-auth-link ((oauth oidc) redirect\n                                &key (error-redirect \"/\"))\n  (let* ((callback (lambda (&key code error error-description redirect-uri)\n                     (oidc-callback oauth code redirect\n                                    :error error\n                                    :error-description error-description\n                                    :error-redirect error-redirect\n                                    :original-redirect-uri redirect-uri))))\n    (make-oauth-url\n     (authorization-endpoint oauth)\n     callback\n     :via (oidc-via oauth)\n     :client_id (client-id oauth)\n     :response_type \"code\"\n     :scope (scope oauth))))\n"
  },
  {
    "path": "src/oidc/test-oauth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :oidc/test-oauth\n  (:use :cl\n        :fiveam)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:is-equal-to)\n  (:import-from #:oidc/oauth\n                #:make-oauth-url)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :oidc/test-oauth)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (with-fake-request ()\n     (auth:with-sessions ()\n       (&body)))))\n\n(test make-oauth-url-basic\n  (with-fixture state ()\n    (with-fake-request (:host \"example.com\")\n      (let* ((oauth-url))\n        (cl-mock:if-called 'nibble:nibble-id\n                           (lambda (nibble)\n                             (declare (ignore nibble))\n                             12345))\n        (setf oauth-url (make-oauth-url\n                         \"https://oauth.provider.com/authorize?client_id=abc123&scope=read\"\n                         (lambda (&key code error redirect-uri)\n                           (declare (ignore code error redirect-uri)))))\n        (let ((parsed (quri:uri oauth-url)))\n          (is (equal (quri:uri-scheme parsed) \"https\"))\n          (is (equal (quri:uri-host parsed) \"oauth.provider.com\"))\n          (is (equal (quri:uri-path parsed) \"/authorize\"))\n          (let ((params (quri:uri-query-params parsed)))\n            (assert-that (a:assoc-value params \"client_id\" :test #'equal)\n                         (is-equal-to \"abc123\"))\n            (assert-that (a:assoc-value params \"scope\" :test #'equal)\n                         (is-equal-to \"read\"))\n            (assert-that (a:assoc-value params \"redirect_uri\" :test #'equal)\n                         (is-equal-to \"http://example.com/account/oauth-callback\"))\n            (assert-that (a:assoc-value params \"state\" :test #'equal)\n                         (is-equal-to \"12345\"))))))))\n\n(test make-oauth-url-without-existing-params\n  (with-fixture state ()\n    (with-fake-request (:host \"example.com\")\n      (let* ((oauth-url))\n        (cl-mock:if-called 'nibble:nibble-id\n                           (lambda (nibble)\n                             (declare (ignore nibble))\n                             67890))\n        (setf oauth-url (make-oauth-url\n                         \"https://oauth.provider.com/authorize\"\n                         (lambda (&key code error redirect-uri)\n                           (declare (ignore code error redirect-uri)))))\n        (let ((parsed (quri:uri oauth-url)))\n          (is (equal (quri:uri-path parsed) \"/authorize\"))\n          (let ((params (quri:uri-query-params parsed)))\n            (is (= (length params) 2))\n            (assert-that (a:assoc-value params \"redirect_uri\" :test #'equal)\n                         (is-equal-to \"http://example.com/account/oauth-callback\"))\n            (assert-that (a:assoc-value params \"state\" :test #'equal)\n                         (is-equal-to \"67890\"))))))))\n\n(test make-oauth-with-via\n  (with-fixture state ()\n    (with-fake-request (:host \"example.com\")\n      (let* ((oauth-url))\n        (cl-mock:if-called 'nibble:nibble-id\n                           (lambda (nibble)\n                             (declare (ignore nibble))\n                             12345))\n        (setf oauth-url (make-oauth-url\n                         \"/authorize?client_id=abc123&scope=read\"\n                         (lambda (&key code error redirect-uri)\n                           (declare (ignore code error redirect-uri)))\n                         :via \"https://screenshotbot.io\"))\n        (let ((parsed (quri:uri oauth-url)))\n          (let ((params (quri:uri-query-params parsed)))\n            (assert-that (a:assoc-value params \"client_id\" :test #'equal)\n                         (is-equal-to \"abc123\"))\n            (assert-that (a:assoc-value params \"scope\" :test #'equal)\n                         (is-equal-to \"read\"))\n            (assert-that (a:assoc-value params \"redirect_uri\" :test #'equal)\n                         (is-equal-to \"https://screenshotbot.io/account/oauth-callback\"))\n            (assert-that (a:assoc-value params \"state\" :test #'equal)\n                         (is-equal-to \"12345,http://example.com/\"))))))))\n"
  },
  {
    "path": "src/oidc/test-oidc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :oidc/test-oidc\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:oidc/oidc\n                #:after-authentication\n                #:user-info\n                #:oauth-access-token\n                #:userinfo-endpoint\n                #:oauth-get-access-token\n                #:token-endpoint\n                #:oidc-callback\n                #:oidc)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer))\n(in-package :oidc/test-oidc)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (with-fake-request ()\n     (auth:with-sessions ()\n       (let ((auth (make-instance 'oidc\n                                  :client-id \"meh-client-id\"\n                                  :client-secret \"meh-client-secret\"\n                                  :issuer \"https://example.com\")))\n         (&body))))))\n\n(test simple-oidc-callback\n  (let ((token (make-instance 'oauth-access-token\n                              :access-token \"my-token\")))\n   (with-fixture state ()\n     (answer (token-endpoint auth) \"https://example.com/token\")\n     (answer (userinfo-endpoint auth) \"https://example.com/userinfo\")\n     (answer (oauth-get-access-token\n              \"https://example.com/token\"\n              :CLIENT_ID \"meh-client-id\"\n              :CLIENT_SECRET \"meh-client-secret\"\n              :CODE \"code\"\n              :REDIRECT_URI \"http://localhost/account/oauth-callback\")\n       token)\n     (answer (user-info auth token)\n       (json:decode-json-from-string \"{\\\"sub\\\":2,\\\"email\\\":\\\"arnold@tdrhq.com\\\",\\\"fullName\\\":\\\"Arnold Noronha\\\",\\\"avatar\\\":\\\"https://imgur.com/foo.png\\\"}\"))\n     (answer (after-authentication\n              auth\n              :user-id 2\n              :email \"arnold@tdrhq.com\"\n              :full-name \"Arnold Noronha\"\n              :avatar \"https://imgur.com/foo.png\")\n       nil)\n     (catch 'hunchentoot::handler-done\n       (oidc-callback\n        auth\n        \"code\" \"/\"\n        :original-redirect-uri \"http://localhost/account/oauth-callback\"))\n     (pass))))\n"
  },
  {
    "path": "src/pixel-diff/CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to PixelDiff will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [1.0.3] - 2025-09-02\n\n### Changed\n\n- Fixed ASDF-related bootup crash\n\n\n## [1.0.2] - 2025-08-31\n\n### Changed\n\n- Maintain the view state when switching between images\n\n## [1.0.1] - 2025-08-09\n\n### Added\n\n- CLI support for comparing git references: `./pixel-diff <ref1> <ref2>` or `./pixel-diff <ref>`\n\n### Changed\n\n- Reduced CPU usage during mouse move\n- Improved Zoom performance\n\n## [0.2.0] - 2024-01-15\n\n### Added\n- First public release\n\n\n## [0.1.0] - 2024-01-01\n\n### Added\n- Initial release of PixelDiff\n- At the moment we can only compare two screenshots\n- App store releases will be in a future version\n\n"
  },
  {
    "path": "src/pixel-diff/LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "src/pixel-diff/README.md",
    "content": "<div align=\"center\">\n\n![PixelDiff Logo](src/pixel-diff/logo.svg)\n\n![PixelDiff Logo](src/pixel-diff/full-logo.png)\n\n</div>\n\n# PixelDiff\n\nA visual image comparison tool for pixel-perfect difference\ndetection. Designed for use with Screenshot tests.\n\n![PixelDiff Logo](src/pixel-diff/screenshot.png)\n\n\n## Overview\n\nPixelDiff is an open source GUI application for comparing two images,\nor multiple images between Git branches.\n\n## Features\n\n- **Difference Highlighting**: Visual overlay showing exactly which pixels have changed\n- **Interactive Navigation**: Pan and zoom to examine differences in detail\n- **Multiple View Modes**: Toggle between previous image, updated image, and difference overlay\n- **Zoom to Changes**: Automatically focus on areas with detected differences\n- **Pixel-Level Analysis**: Hover over pixels to see exact color values and changes\n- **Transparency Support**: Proper handling of images with alpha channels\n\n## Usage\n\nIf you're inside a Git repository, you can compare any two branches:\n\n```bash\n# Compare all the changes in the current checkout with the main branches\npixel-diff main\n\n# Compare all the changes betweens two branches\npixel-diff tags/release-1.0 tags/release-2.0\n```\n\n\nYou can also just compare any two image files, whether or not they are in Git:\n\n```bash\npixel-diff file-before.png file-after.png\n```\n\nFor example, in this repository we have two sample images to test this against:\n\n```bash\npixel-diff src/pixel-diff/examples/image1.png src/pixel-diff/examples/image2.png\n```\n\n\n\n## View Modes\n\n- **Previous**: Shows only the original image\n- **Diff**: Highlights changed pixels in red overlay (default)\n- **Updated**: Shows only the new image\n\nPress `v` to toggle between Previous and Updated, which is useful to\nvisuable differences in a zoomed-in version of the image.\n\n## Controls\n\n### Mouse Controls\n- **Click & Drag**: Pan around the image\n- **Mouse wheel**: Zoom into parts of the image\n- **Mouse Hover**: Display pixel color information\n\n### Keyboard Shortcuts\n- **V**: Toggle between Previous/Updated views\n- **+ / -**: Zoom in/out at cursor position\n\n### Interface Elements\n- **View Radio Panel**: Switch between Previous, Diff, and Updated modes\n- **Zoom to Change Button**: Automatically center on first detected difference\n- **Status Bar**: Shows pixel information and color values\n\n## Implementation\n\nPixelDiff is implemented in Common Lisp using the CAPI (Common\nApplication Programmer Interface) for cross-platform GUI\nfunctionality.\n\n## License\n\nMozilla Public License v2.0\n\n## Author\n\nPixelDiff is built and maintained by\n(https://screenshotbot.io)[Screenshotbot]. \n\nScreenshotbot is the industry standard screenshot testing SaaS\nsolution that works with your existing screenshot tests. Screenshotbot\nstores screenshots, produces clean reports on Pull Requests, reduces\noverhead with scaling up your screenshot testing efforts. Much of\nimage processing algorithms used by PixelDiff is available on our web\nbased image comparison tools.\n\n"
  },
  {
    "path": "src/pixel-diff/about.lisp",
    "content": ";; -*- coding: utf-8 -*-\n\n(defpackage :pixel-diff/about\n  (:use #:cl\n        #:capi)\n  (:export #:show-about-dialog))\n(in-package :pixel-diff/about)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defparameter *pixel-diff-version*\n    (or (asdf:component-version (asdf:find-system :pixel-diff))\n        \"Unknown\")\n    \"Version of pixel-diff captured at compile time\"))\n\n(defun get-pixel-diff-version ()\n  \"Get the version of the pixel-diff system\"\n  *pixel-diff-version*)\n\n(defun show-about-dialog (&optional parent)\n  \"Create and display an About dialog\"\n  (let ((dialog\n          (make-instance 'interface\n                         :title \"About Pixel Diff\"\n                         :layout (make-instance 'column-layout\n                                                :description\n                                                (list\n                                                 (make-instance 'display-pane\n                                                                :text \"Pixel Diff\"\n                                                                :font (gp:make-font-description\n                                                                       :size 16\n                                                                       :weight :bold)\n                                                                :visible-min-width 600\n                                                                :visible-min-height 60)\n                                                 (make-instance 'display-pane\n                                                                :text (format nil \"Version ~A\" (get-pixel-diff-version))\n                                                                :visible-min-width 600\n                                                                :visible-min-height 40)\n                                                 (make-instance 'display-pane\n                                                                :text \"A tool for comparing images and visualizing differences\"\n                                                                :visible-min-width 600\n                                                                :visible-min-height 80)\n                                                 (make-instance 'display-pane\n                                                                :text \"© 2025 Modern Interpreters Inc. (Screenshotbot)\"\n                                                                :visible-min-width 600\n                                                                :visible-min-height 40)\n                                                 (make-instance 'row-layout\n                                                                :description\n                                                                (list\n                                                                 nil\n                                                                 (make-instance 'push-button\n                                                                                :text \"OK\"\n                                                                                :callback (lambda (data interface)\n                                                                                            (declare (ignore data))\n                                                                                            (capi:quit-interface interface))\n                                                                                :callback-type :data-interface)\n                                                                 nil))))\n                         :visible-min-width 640\n                         :visible-min-height 400\n                         :owner parent)))\n    (capi:display-dialog dialog)))\n"
  },
  {
    "path": "src/pixel-diff/browser.lisp",
    "content": "(defpackage :pixel-diff/browser\n  (:use #:cl\n        #:capi)\n  (:import-from #:pixel-diff/differ\n                #:post-process-image\n                #:cached-image\n                #:set-image-pair\n                #:open-menu-available-p\n                #:comparison-image-layer\n                #:image-main-layout\n                #:image-pane\n                #:image-window\n                #:image-layer)\n  (:local-nicknames (#:image-pair #:pixel-diff/image-pair)))\n(in-package :pixel-diff/browser)\n\n(define-interface image-browser-window (image-window)\n  ((image-pair-list :initarg :image-pair-list :initform nil\n                    :accessor image-pair-list))\n  (:panes\n   (image-list-selector\n    list-panel\n    :items (image-pair-list interface)\n    :print-function 'image-pair:image-pair-title\n    :selection-callback 'image-list-selection-callback\n    :reader image-list-selector))\n  (:layouts\n   (main-layout\n    row-layout\n    '(image-list-selector :divider image-main-layout)\n    :x-ratios `(1 nil 8)))\n  (:default-initargs\n   :create-callback '%create-callback\n   :title \"Image Browser\"))\n\n(defclass left-image-pane (image-pane)\n  ((num :initarg :num\n        :documentation \"only for debugging\")))\n\n(defclass thumbnail-image-layer (image-layer)\n  ())\n\n(defun calc-scale (image)\n  (let ((width (gp:image-width image))\n        (height (gp:image-height image)))\n    (/ 400 (max width height))))\n\n(defmethod post-process-image (pane (self thumbnail-image-layer) image)\n  image)\n\n(defun %create-callback (interface))\n\n\n(defun image-list-selection-callback (selected-image-pair interface)\n  \"Callback function for the image list selector panel\"\n  (set-image-pair\n   (image-pane interface)\n   selected-image-pair))\n\n\n\n(defmethod open-menu-available-p ((self image-browser-window))\n  nil)\n\n(defun test-browser-window ()\n  \"Create a test browser window with three image pairs using the same example images\"\n  (let ((image-pairs (list\n                      (pixel-diff/image-pair:make-image-pair\n                       \"src/pixel-diff/examples/image1.png\"\n                       \"src/pixel-diff/examples/image2.png\")\n                      (pixel-diff/image-pair:make-image-pair\n                       \"src/pixel-diff/examples/image1.png\"\n                       \"src/pixel-diff/examples/image2.png\")\n                      (pixel-diff/image-pair:make-image-pair\n                       \"src/pixel-diff/examples/image1.png\"\n                       \"src/pixel-diff/examples/image2.png\"))))\n    (capi:contain\n     (let ((image-layer1 (make-instance 'image-layer\n                                        :image \"src/pixel-diff/examples/image1.png\"\n                                        :alpha 0.1))\n           (image-layer2 (make-instance 'image-layer\n                                        :image \"src/pixel-diff/examples/image2.png\"\n                                        :alpha 0)))\n       (make-instance 'image-browser-window\n                      :image1 image-layer1\n                      :image2 image-layer2\n                      :image-pair-list image-pairs)))))\n\n\n\n;; (Test-browser-window)\n"
  },
  {
    "path": "src/pixel-diff/common-ps.lisp",
    "content": "(in-package :screenshotbot-js)\n\n;; These are all utility functions for canvas, in particular\n;; loadIntoCanvas, for testability reasons.\n\n(defparameter *min-zoom* 0.2\n  \"min-zoom is interesting. Let's call this z_0 for now.\n\nIt's not the minimum zoom level. Instead, it's saying that for a given dimention (width or height), it's saying that the number of pixels the rendered image took divided by the client dimenion is greater than z_0.\n\nLet's do some math. If M is the final transformation matrix:\n\n|| M[w 0 0] - M[0 0 0] || >= z_o * w_c\n\nWhere w_c is the client width, and w is the image width.\n\nThis gives us\n|| M[w 0 0] || >= z_0 *w_c\n\nor, simply:\nz*w >= z_0 * w_c\n\nSo we know, z >= z_0 * w_c / w.\n\")\n\n(defmacro with-css-zoom-calcs (&body body)\n  `(flet ((zor (x y)\n            (if (= x 0)\n                y\n                x)))\n     (let* ((client-width (zor client-width width))\n            (client-height (zor client-height height)))\n       ,@body)))\n\n(defun calc-core-transform (client-width\n                            client-height\n                            width\n                            height)\n  (with-css-zoom-calcs\n    (let* ((w-ratio (/ client-width width))\n           (h-ratio (/ client-height height))\n           (z (max\n               (min w-ratio\n                    h-ratio)\n               ;; See documentation for *min-zoom*\n               (* w-ratio *min-zoom*)\n               (* h-ratio *min-zoom*))))\n      (flet ((calc-t (client-dim dim)\n               (/ (- client-dim (* z dim)) 2)))\n        (let ((tx (calc-t client-width width))\n              (ty (calc-t client-height height)))\n          (make-matrix\n           z 0 0 z (max tx 0) (max ty 0)))))))\n\n(defun calc-transform-for-center (client-width\n                                  client-height\n                                  width\n                                  height\n                                  x y\n                                  zoom)\n  \"Given an x,y position, figure out the translation matrix such that x,y\nwill be in the center of the screen and zoomed in at level z.\n\nWe know p=(x,y) should end up being at d=(client-width/2, client-height/2).\n\nd = MCp\n\nWhere M is the final transform that we want to solve for. If we break\nup M as a zoom matrix and a translation matrix we get:\n\nd = (Z + T)Cp\n\nWe know that Z is just zoom*I.\n\nso we get:\n\nd = (zI + T)Cp\nor\nTCp = d - zCp.\n\nNow notice, that T looks like:\n\n[0 0 tx\n 0 0 ty\n 0 0 0]\n\nCx is a vector like [x' y' c]. So TCp just evaluates to t = [tx ty c], for some constants c,\nirrespective of what's in Cp.\n\nWe can thus write:\n\nt = d - zCp.\n\nOnce we have t, we can construct our final matrix.\n\"\n\n  (with-css-zoom-calcs\n    (let* ((C (calc-core-transform client-width\n                                   client-height\n                                   width\n                                   height))\n           (d (3d-vectors:vec3 (/ client-width 2)\n                               (/ client-height 2)\n                               0))\n           (Z (make-matrix zoom 0\n                           0 zoom\n                           0 0))\n           (tt (v- d\n                   (m* Z (m* C (3d-vectors:vec3 x y 1))))))\n      (make-matrix\n       zoom 0\n       0 zoom\n       (3d-vectors:vx3 tt) (3d-vectors:vy3 tt)))))\n\n(defun animate-transform (old-transform new-transform progress)\n  (m+\n   (m* (- 1 progress)\n       old-transform)\n   (m* progress\n       new-transform)))\n\n(defun calc-transform-for-zoom (client-x client-y\n                                transform\n                                delta-zoom)\n  \"Calculate the delta transformation matrix such that\nnew-transform*old-transform*core-transform will result in\nthe (client-x, client-y) pointing to the same underlying (x,y).\n\nAt time of writing, this code is duplicated in the Javascript, and is\nbeing only used for PixelDiff.\n\nLet q be the client point, and p be the source point.\n\nInitially q_0 = M_0Cp_0\n\nAfter the transform q_1 = M_1Cp_1.\n\nSince q_0 = q_1, and p_0 = p_1, we have:\n\nM_0 C p = M_1 C p\n\n\nBut, M_1 = TZM_0, where Z is the zoom matrix from delta-zoom, and T is a\npure translation matrix.\n\nM_0 C p = T Z M_0 C p.\n\nSince T looks like:\n\n[0 0 tx\n 0 0 ty    + I\n 0 0 0]\n\nWe can write:\n\nZM_0Cp + t = M_0Cp\n\nt = (I-Z) M_0 C p = (I - Z) M_0 C C^-1 M_0^-1 q\n  = (I - Z) q\n\nOnce we calculate that, we reconstruct T, and return T*Z, which is just\n\nz 0 tx\n0 z ty\n0 0 1.\n\nIt's the callers responsibility to multiply the resulting transforms.\n\"\n\n  (let ((I (3d-matrices:mat3 #(1 0 0\n                               0 1 0\n                               0 1 1)))\n        (Z (3d-matrices:mat3 `#(,delta-zoom 0 0\n                                0 ,delta-zoom 0\n                                0 0 1))))\n    (let ((t-vec (m* (m- I Z) (vec3 client-x client-y 1))))\n      (3d-vectors:with-vec3 (x y z) t-vec\n        (make-matrix\n         delta-zoom 0 0 delta-zoom x y)))))\n"
  },
  {
    "path": "src/pixel-diff/deliver.lisp",
    "content": ";;;; Delivery script for pixel-diff binary\n\n(load \"scripts/prepare-image.lisp\")\n(load \"scripts/init.lisp\")\n(ql:quickload :deliver-utils)\n(ql:quickload :pixel-diff)\n\n(defpackage :pixel-diff/deliver\n  (:use #:cl)\n  (:import-from #:deliver-utils/common\n                #:deliver-common))\n(in-package :pixel-diff/deliver)\n\n(defun deliver-target ()\n   \"pixel-diff\"\n   #+darwin\n   (if (str:s-member sys:*line-arguments-list* \"--debug\")\n    \"pixel-diff\"\n    (hcl:create-macos-application-bundle \n     \"pixel-diff.app\"\n     ;; Don't copy LispWorks file associations\n     :document-types nil\n     :identifier \"io.screenshotbot.pixeldiff\"\n     :application-icns (asdf:system-relative-pathname :pixel-diff \"PixelDiff.icns\")\n     :version (asdf:component-version (asdf:find-system :pixel-diff))\n     :bundle-name \"Pixel Diff\")))\n\n\n(defun deliver-pixel-diff ()\n  \"Deliver the pixel-diff binary\"\n  (deliver-common \n   (deliver-target)\n   :restart-fn #'pixel-diff/main::main\n   :deliver-level 5\n   :interface :capi\n   :icon-file (asdf:system-relative-pathname :pixel-diff \"logo.ico\")\n   :warn-on-missing-templates t\n   :console :input ;; only if input is attempted\n   :startup-bitmap-file nil\n   :prepare-asdf nil\n   :keep-modules nil))\n\n;; Run delivery when this file is loaded\n(deliver-pixel-diff)\n\n"
  },
  {
    "path": "src/pixel-diff/differ.lisp",
    "content": "(defpackage :pixel-diff/differ\n  (:use #:cl\n        #:capi)\n  (:import-from #:screenshotbot-js\n                #:animate-transform)\n  (:import-from #:pixel-diff/about\n                #:show-about-dialog)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:pixel-diff/external-images\n                #:with-image-from-external)\n  (:export\n   #:create-empty-interface\n   #:image-pane\n   #:set-image-pair\n   #:open-menu-available-p\n   #:comparison-image-layer\n   #:abstract-image-layer\n   #:image-main-layout\n   #:image-window\n   #:image-layer\n   #:load-image)\n  (:local-nicknames (#:image-pair #:pixel-diff/image-pair)))\n(in-package :pixel-diff/differ)\n\n(defun %internal-time ()\n  (/ (* (get-internal-real-time) 1000)\n     internal-time-units-per-second))\n\n(defun make-identity ()\n  (gp:make-transform 1 0 0 1 0 0))\n\n(defmacro or-setf (accessor expr)\n  `(or\n    ,accessor\n    (setf ,accessor ,expr)))\n\n(def-easy-macro with-image-access (&binding image-access pane image &key write &fn fn)\n  (let ((image-access (gp:make-image-access pane image)))\n    (gp:image-access-transfer-from-image image-access)\n    (unwind-protect\n         (prog1\n             (fn image-access)\n           (when write\n             (gp:image-access-transfer-to-image image-access)))\n      (gp:free-image-access image-access))))\n\n\n(defun create-empty-pane ()\n  (make-instance 'output-pane\n                 :background :white\n                 :width 400\n                 :height 300))\n\n(defmethod alpha ((image-layer null))\n  ;; Never attempt to render a null image-layer\n  0)\n\n(defun draw-image-layer (pane image-layer  x y width height)\n  (declare (ignore x y width height))\n\n  (read-image-async pane image-layer)\n  (when (image-layer-ready-p image-layer)\n    (when (> (alpha image-layer) 0)\n      (with-image-from-external (image pane (read-image pane image-layer))\n        (when (< (alpha image-layer) 0.9) ;; not fully opaque\n          (gp:draw-rectangle pane 0 0 (gp:image-width image) (gp:image-height image)\n                             :filled t\n                             :foreground :white))\n        (when image\n          (log:debug \"drawing image ~a\" image-layer)\n          (gp:draw-image pane image 0 0 :global-alpha (alpha image-layer)))))))\n\n\n(defun draw-background (pane x y width height)\n  \"Draw a solid gray background. In the future we might bring back \"\n  (gp:draw-rectangle pane\n                     0 0\n                     (gp:port-width pane)\n                     (gp:port-height pane)\n                     :filled t\n                     :foreground :gray90))\n\n\n\n(defun draw-image-callback (pane x y width height)\n  \"Callback function to draw the image in the display pane\"\n  (log:debug \"Draw callback\")\n  (draw-background pane x y width height)\n  (maybe-init-core-transform pane (gp:port-width pane) (gp:port-height pane))\n  (assert (image-transform pane))\n  (let ((transform (gp:copy-transform (or\n                                       (core-transform pane)\n                                       (make-identity)))))\n    (gp:postmultiply-transforms\n     transform\n     (image-transform pane))\n    (gp:with-graphics-transform (pane transform)\n      (draw-image-layer pane (image1 pane) x y width height)\n      (draw-image-layer pane (image2 pane) x y width height)\n      (draw-image-layer pane (comparison pane) x y width height))))\n\n(defun maybe-init-core-transform (pane width height)\n  (read-image-async pane (image1 pane))\n  (when (image-layer-ready-p (image1 pane))\n    (unless (and\n             (core-transform pane)\n             (eql width (last-width pane))\n             (eql height (last-height pane)))\n      (with-image-from-external (image pane (read-image pane (image1 pane)))\n        (let ((screenshotbot-js-stubs::*make-matrix-impl* #'gp:make-transform))\n          (setf (last-width pane) width)\n          (setf (last-height pane) height)\n          (setf (core-transform pane)\n                (screenshotbot-js::calc-core-transform\n                 width\n                 height\n                 (gp:image-width image)\n                 (gp:image-height image))))))))\n\n(defmethod load-image (pane pathname callback)\n  (let ((external-image (gp:read-external-image pathname)))\n   (apply-in-pane-process\n    pane\n    (lambda ()\n      (funcall callback\n               external-image)))))\n\n(defclass abstract-image-layer ()\n  ((cached-image :initform nil\n                 :accessor cached-image)\n   (loading-p :initform nil\n              :initarg :loading-p\n              :accessor loading-p)\n   (alpha :initarg :alpha\n          :accessor alpha)\n   (name :initarg :name\n         :reader image-layer-name)) )\n\n(defmethod image-layer-ready-p ((self abstract-image-layer))\n  (not (null (cached-image self))))\n\n(defmethod image-layer-ready-p ((self null))\n  t)\n\n(defmethod read-image :before (pane self)\n  (assert (or\n           (image-layer-ready-p self)\n           (loading-p self))))\n\n(defclass image-layer (abstract-image-layer)\n  ((image :initarg :image\n          :reader image)))\n\n(defmethod read-image (pane (self image-layer))\n  (cond\n    ((cached-image self)\n     (cached-image self))\n    (t\n     nil)))\n\n\n(defmethod free-image-layer (pane (self abstract-image-layer))\n  (when (cached-image self)\n    (setf (cached-image self) nil)))\n\n\n(defmethod post-process-image (pane image-layer image)\n  image)\n\n(defmethod read-image-async (pane (self null))\n  nil)\n\n(defmethod read-image-async (pane (self abstract-image-layer))\n  \"If the image is not already loaded, it starts the process to load the\nimage. The callback is only called if the image loading process was\ntrigegred.\"\n  ;; TODO: don't load twice!\n  (unless (or\n           (image-layer-ready-p self)\n           (loading-p self))\n    (setf (loading-p self) t)\n    (schedule-timer-in-pane\n     pane\n     0\n     (lambda ()\n       (load-image pane (image self)\n                   (lambda (image)\n                     (setf (cached-image self) (post-process-image pane self image))\n                     (setf (loading-p self) nil)\n                     (gp:invalidate-rectangle pane)))))))\n\n\n(defclass image-pane (output-pane)\n  ((image1 :initarg :image1 :initform nil\n           :accessor image1)\n   (image2 :initarg :image2 :initform nil\n           :accessor image2)\n   (comparison :initarg :comparison :initform nil\n               :accessor comparison)\n   (press-start :initform nil\n                :accessor press-start\n                :documentation \"The coordinates of an initial press start\")\n   (scroll-max :initarg :scroll-max\n               :accessor scroll-max)\n   (last-width :initform nil\n               :accessor last-width)\n   (last-height :initform nil\n                :accessor last-height)\n   (image-transform :initform (make-identity)\n                    :accessor image-transform\n                    :documentation \"The transform for the image\")   \n   (core-transform :initform nil\n                   :accessor core-transform)\n   (mouse-move-blocked-until :initform 0\n                                  :accessor mouse-move-blocked-until\n                                  :documentation \"If the internal time is less than this, then hover insights will be disabled.\")\n   (last-mouse-move-pos :initform (list 0 0)\n                        :accessor last-mouse-move-pos\n                        :documentation \"Used for throttling the mouse-move callback\")\n   (last-mouse-move-updated-pos :initform (list 0 0)\n                                :accessor last-mouse-move-updated-pos\n                                :documentation \"There might be a delay between when the mouse move was detected, and\nprocessed. This is the position at the time of being processed.\"))\n  (:default-initargs :draw-with-buffer t\n   ;; Is there a more systematic way to figure out this number? It's\n   ;; probably going to be proportional to how many pixels move with\n   ;; one typical mouse-wheel movement.\n                     :scroll-max (* 4 (capi:screen-height (capi:convert-to-screen)))\n                     :display-callback 'draw-image-callback\n                     :resize-callback 'image-pane-resize-callback\n                     :create-callback 'image-pane-create-callback\n                     :coordinate-origin :fixed-graphics))\n\n(defmethod disable-mouse-move ((self image-pane) &key (duration 500))\n  (setf\n   (mouse-move-blocked-until self)\n   (max\n    (mouse-move-blocked-until self)\n    (+ duration (%internal-time)))))\n\n(defmethod initialize-instance :around ((self image-pane) &rest args &key image1 image2 scroll-max)\n  (apply #'call-next-method\n         self\n         :scroll-height scroll-max\n         :scroll-initial-y (floor scroll-max 2)\n         args))\n\n(defmethod update-comparison ((self image-pane))\n  (let ((image1 (image1 self))\n        (image2 (image2 self)))\n   (when (and image1 image2)\n     (check-type image1 image-layer)\n     (check-type image2 image-layer)\n     (setf (comparison self)\n           (make-instance 'comparison-image-layer\n                          :image1-layer image1\n                          :image2-layer image2)))))\n\n(defmethod initialize-instance :after ((self image-pane) &rest args)\n  (update-comparison self))\n\n(defmethod (setf image1) :after (val (self image-pane))\n  (update-comparison self))\n\n(defmethod (setf image2) :after (val (self image-pane))\n  (update-comparison self))\n\n(defun image-pane-create-callback (pane)\n  \"Callback function called when image pane is created\"\n\n  ;; It is safe to disable this for a second, if you're debugging any\n  ;; code releated to zooming/scrolling.\n  (capi:simple-pane-show-scroll-bars pane :vertical nil :horizontal nil)\n\n  (capi:set-vertical-scroll-parameters pane :slug-position (floor (scroll-max pane) 2)))\n\n\n(define-interface image-window ()\n  ()\n  (:panes\n   (image-pane image-pane\n               :reader image-pane\n               :background :white\n               :visible-min-width 400\n               :visible-min-height 300\n               :vertical-scroll t\n               :scroll-callback 'image-pane-scroll-callback\n               :input-model `(((:button-1 :press)\n                               image-pane-press)\n                              ((:button-1 :release)\n                               image-pane-release)\n                              ((:motion :button-1)\n                               image-pane-drag)\n                              ((:motion)\n                               image-pane-mouse-move)\n                              (:character\n                               image-pane-char-press)))\n   (view-radio-panel capi:radio-button-panel\n                     :reader view-radio-panel\n                     :items '(:previous :diff :updated)\n                     :print-function (lambda (item)\n                                       (string-capitalize (symbol-name item)))\n                     :selected-item :diff\n                     :layout-class 'row-layout\n                     :callback-type :data-interface\n                     :selection-callback 'view-radio-panel-callback)\n   (zoom-button push-button\n                :reader zoom-button\n                :text \"Zoom to change\"\n                :callback 'zoom-to-change-callback)\n   (status-text display-pane\n                :reader status-text\n                :text \"Ready - Move mouse over image to see pixel info\"))\n  (:layouts\n   (image-main-layout\n    column-layout\n    '(image-pane bottom-bar status-text))\n   (bottom-bar\n    row-layout\n    '(view-radio-panel nil  zoom-button)))\n  (:menus\n   (file-menu \"File\"\n              ((\"Open (Previous)...\" :data :open-previous\n                                     :callback-type :interface\n                                     :enabled-function 'open-menu-available-p\n                                     :callback 'open-previous-callback)\n               (\"Open (Updated)...\" :data :open-updated\n                                    :callback-type :interface\n                                    :enabled-function 'open-menu-available-p\n                                    :callback 'open-updated-callback)))\n   (view-menu \"View\"\n              ((\"Toggle Previous/Updated\" :data :toggle-previous-updated\n                                          :callback-type :interface\n                                          :callback #'toggle-previous-updated\n                                          :accelerator \"v\")\n               (\"Zoom In\" :data :zoom-in\n                          :callback-type :interface\n                          :callback #'zoom-in-callback\n                          :accelerator \"+\")\n               (\"Zoom Out\" :data :zoom-out\n                           :callback-type :interface\n                           :callback #'zoom-out-callback\n                           :accelerator \"-\")))\n   (help-menu \"Help\"\n              ((\"About\" :data :about\n                        :callback-type :interface\n                        :callback #'show-about-dialog))))\n  (:menu-bar file-menu view-menu help-menu)\n  (:default-initargs\n   :title \"Image Display Window\"\n   :width (floor (capi:screen-width (capi:convert-to-screen)) 2)\n   :height (floor (capi:screen-height (capi:convert-to-screen)) 2)))\n\n(defmethod initialize-instance :after ((self image-window) &rest args &key image1 image2 &allow-other-keys)\n  (declare (ignore args))\n  (when (and image1 image2 (image-pane self))\n    (setf (image1 (image-pane self)) image1)\n    (setf (image2 (image-pane self)) image2)))\n\n(defmethod open-menu-available-p (interface)\n  t)\n\n(defun get-current-zoom (image-window)\n  \"Get the current zoom level from the image-transform of the image-window\"\n  (when (image-transform (image-pane image-window))\n    (let ((transform (image-transform (image-pane image-window))))\n      (destructuring-bind (a b c d e f) transform\n        (declare (ignore b c d e f))\n        a))))\n\n(defun scroll-pos-to-expected-zoom (pane scroll-value)\n  (let ((scroll-max (scroll-max pane)))\n    (let ((t-param (- 1.0 (/ scroll-value scroll-max))))\n      (* 0.1 (expt 100 t-param)))))\n\n(defun zoom-to-scroll-pos (pane zoom)\n  (let ((scroll-max (scroll-max pane)))\n    (* scroll-max\n       (- 1.0\n          (/ (log (/ zoom 0.1)) (log 100))))))\n\n(defmethod image-pane-scroll-callback (pane (scroll-dimension (eql :vertical))\n                                       (scroll-operation (eql :move))\n                                       scroll-value\n                                       &key interactive)\n  \"Handle scroll events for zooming in/out on the image pane\"\n  (when interactive\n    (let ((current-zoom (get-current-zoom (capi:element-interface pane)))\n          (current-position (capi:get-vertical-scroll-parameters pane :slug-position)))\n      (log:debug \"Current zoom is: ~a\" current-zoom)\n      (let* ((expected-zoom (scroll-pos-to-expected-zoom pane scroll-value)))\n        (log:debug \"existing slug pos: ~a\" (capi:get-vertical-scroll-parameters pane :slug-position))\n        (multiple-value-bind (x y) (capi:current-pointer-position :relative-to pane)\n          (log:debug \"Got pos ~a, ~a \" x y)\n          (process-zoom pane x (- y current-position)\n                        (/ expected-zoom current-zoom)))\n        (gp:invalidate-rectangle pane)))))\n\n(defmethod image-pane-scroll-callback (pane (scroll-dimension (eql :vertical))\n                                            (scroll-operation (eql :step))\n                                            delta\n                                            &key interactive)\n  ;; We've only seen :step in Windows so far\n  (capi:set-vertical-scroll-parameters\n   pane\n   :slug-position\n   (+ (capi:get-vertical-scroll-parameters pane :slug-position)\n      (* delta (/ (scroll-max pane) 100))))\n  (image-pane-scroll-callback\n   pane :vertical :move\n   (capi:get-vertical-scroll-parameters pane :slug-position) :interactive t))\n\n(defmethod image-pane-scroll-callback (pane direction scroll-operation scroll-value &key interactive &allow-other-keys)\n  (when interactive\n    (log:debug \"scrolled (unhandled) ~a ~a ~a ~a\" direction scroll-operation scroll-value interactive)))\n\n\n\n(defun open-image-file (interface slot-name prompt-title)\n  \"Generic function for opening image files and updating the interface\"\n  (let ((image-layer (slot-value (image-pane interface) slot-name))\n        (file (capi:prompt-for-file prompt-title\n                                    :operation :open\n                                    :filter \"*.png;*.jpg;*.jpeg;*.bmp;*.gif\"\n                                    :filters '(\"Image files\" \"*.png;*.jpg;*.jpeg;*.bmp;*.gif\"\n                                              \"All files\" \"*.*\"))))\n    (when file\n      (let ((new-image-layer (make-instance 'image-layer\n                                            :image (namestring file)\n                                            :alpha (alpha image-layer))))\n        (setf (slot-value (image-pane interface) slot-name) new-image-layer)\n        (setf (core-transform (image-pane interface)) nil)\n        (setf (slot-value (image-pane interface) 'comparison)\n              (make-instance 'comparison-image-layer\n                             :image1-layer (image1 (image-pane interface))\n                             :image2-layer (image2 (image-pane interface))\n                             :alpha 1))\n        (gp:invalidate-rectangle (image-pane interface))))))\n\n(defun open-previous-callback (interface)\n  \"Callback function for opening a previous image file\"\n  (open-image-file interface 'image1 \"Select Previous Image\"))\n\n(defun open-updated-callback (interface)\n  \"Callback function for opening an updated image file\"\n  (open-image-file interface 'image2 \"Select Updated Image\"))\n\n\n(defun view-radio-panel-callback (item interface)\n  \"Callback function for view radio panel selection changes\"\n  (log:debug \"View changed to: ~a\" item)\n  (let ((pane (image-pane interface)))\n    (case item\n     (:previous\n      (setf (alpha (image1 pane)) 1.0)\n      (setf (alpha (image2 pane)) 0.0)\n      (setf (alpha (comparison pane)) 0.0))\n     (:diff\n      (setf (alpha (image1 pane)) 0.1)\n      (setf (alpha (image2 pane)) 0.0)\n      (setf (alpha (comparison pane)) 1.0))\n     (:updated\n      (setf (alpha (image1 pane)) 0.0)\n      (setf (alpha (image2 pane)) 1.0)\n      (setf (alpha (comparison pane)) 0.0))))\n  (gp:invalidate-rectangle (image-pane interface)))\n\n\n(defun render-color (color)\n  (format nil \"#~2,'0x~2,'0x~2,'0x~2,'0x\"\n          (floor (* 255 (color:color-red color)))\n          (floor (* 255 (color:color-green color)))\n          (floor (* 255 (color:color-blue color)))\n          (floor (* 255 (color:color-alpha color)))))\n\n\n(defun get-image-layer-color (pane image-layer image-x image-y)\n  \"Get the color of a pixel from an image layer at the specified coordinates\"\n  (when image-layer\n    (with-image-from-external (image pane (read-image pane image-layer))\n      (when (and image \n                 (< image-x (gp:image-width image))\n                 (< image-y (gp:image-height image)))\n        (with-image-access (image-access pane image)\n          (color:unconvert-color\n          pane\n          (gp:image-access-pixel image-access image-x image-y)))))))\n\n\n(defun schedule-timer-in-pane (pane timeout callback)\n  \"Schedule a timer to run a callback in the pane's process\"\n  (mp:schedule-timer-relative\n   (mp:make-timer\n    (lambda ()\n      (apply-in-pane-process-if-alive\n       pane\n       callback)))\n   timeout))\n\n(defun image-pane-mouse-move (pane x y)\n  \"Handle mouse movement over image pane to show pixel information\"\n  (let ((y (- y (capi:get-vertical-scroll-parameters pane :slug-position))))\n    (setf (last-mouse-move-pos pane) (list x y))\n    (schedule-timer-in-pane\n     pane\n     0.2\n     (lambda ()\n       (unless (equalp (last-mouse-move-pos pane)\n                       (last-mouse-move-updated-pos pane))\n         (setf (last-mouse-move-updated-pos pane)\n               (last-mouse-move-pos pane))\n         (destructuring-bind (last-x last-y) (last-mouse-move-pos pane)\n           (handle-mouse-move pane last-x last-y)))))))\n(defun handle-mouse-move (pane x y)\n  (when (> (%internal-time) (mouse-move-blocked-until pane))\n   (let ((interface (capi:element-interface pane)))\n     (when (and (core-transform pane) (image-transform pane))\n       (let ((combined-transform (gp:copy-transform (core-transform pane))))\n         (gp:postmultiply-transforms combined-transform (image-transform pane))\n         (setf\n          (capi:display-pane-text (status-text interface))\n          (or\n           (multiple-value-bind (image-x image-y)\n               (gp:transform-point (gp:invert-transform combined-transform) x y)\n             (let ((image-x (round image-x))\n                   (image-y (round image-y)))\n               (when (and (>= image-x 0) (>= image-y 0))\n                 (let* ((color-before (get-image-layer-color pane (image1 pane) image-x image-y))\n                        (color-after (get-image-layer-color pane (image2 pane) image-x image-y)))\n                   (cond\n                     ((and color-before color-after\n                           (color:colors= color-before color-after))\n                      (format nil \"Identical color: ~a\" (render-color color-before)))\n                     ((and color-before color-after)\n                      (format nil \"Color changed from ~a to ~a\" (render-color color-before) (render-color color-after)))\n                     (color-before\n                      (format nil \"Color was ~a, now out of bounds\" (render-color color-before)))\n                     (color-after\n                      (format nil \"Out of bounds earlier, not color is: ~a\" (render-color color-after) ))\n                     (t\n                      (log:warn \"Not showing colors at (~a,~a): ~a, ~a\" image-x image-y color-before color-after)\n                      nil))))))\n           \"Ready - Move mouse over image to see pixel info\")))))))\n\n\n\n(defun start-animation-timer (pane duration-seconds callback\n                              &key (finally (lambda ())))\n  \"Start an animation timer that calls callback with progress from 0.0 to 1.0\"\n  (let ((start-time (get-internal-real-time))\n        (duration-internal (* duration-seconds internal-time-units-per-second))\n        (count-cons (list 1)))\n    (labels ((timer-tick (count-cons)\n               (let* ((current-time (get-internal-real-time))\n                      (elapsed (- current-time start-time))\n                      (progress (min 1.0 (/ elapsed duration-internal))))\n                 (when (> (car count-cons) 0)\n                   (capi:apply-in-pane-process-if-alive\n                    pane\n                    (lambda ()\n                      (funcall callback progress)))\n                   (cond\n                     ((< progress 1.0)\n                      (values))\n                     ((>= progress 1.0)\n                      (setf (car count-cons) -1)\n                      (capi:apply-in-pane-process-if-alive\n                       pane\n                       finally)\n                      :stop))))))\n      (let ((timer (mp:make-timer #'timer-tick count-cons)))\n        (mp:schedule-timer-relative-milliseconds timer 32 32)))))\n\n\n\n(defun %zoom-to (interface x y &key (zoom 5) (finally (lambda ())))\n  (let ((pane (slot-value interface 'image-pane)))\n    (with-image-from-external (image pane (read-image pane (image1 (image-pane interface))))\n     (let* (\n            (start-mat (transform-to-3dmat (image-transform pane)))\n            (final-mat (screenshotbot-js::calc-transform-for-center\n                        (gp:port-width pane)\n                        (gp:port-height pane)\n                        (gp:image-width image)\n                        (gp:image-height image)\n                        x y zoom)))\n       (start-animation-timer\n        (image-pane interface)\n        2\n        (lambda (progress)\n          (disable-mouse-move (image-pane interface))\n          (setf (image-transform pane)\n                (3dmat-to-transform\n                 (animate-transform start-mat final-mat progress)))\n          (invalidate-scroll-position interface)\n          (gp:invalidate-rectangle pane))\n        :finally finally)))))\n\n(defmethod invalidate-scroll-position (interface)\n  (let ((pane (image-pane interface)))\n    (capi:set-vertical-scroll-parameters pane :slug-position (zoom-to-scroll-pos\n                                                              pane\n                                                              (get-current-zoom interface)))))\n\n\n(defmethod find-non-transparent-pixel (pane (image gp:image))\n  (with-image-access (image-access pane image)\n   (let ((width (gp:image-width image))\n         (height (gp:image-height image)))\n     (loop for y from 0 below height do\n       (loop for x from 0 below width do\n         (let ((color (color:unconvert-color\n                       pane\n                       (gp:image-access-pixel image-access x y))))\n           (when (> (color:color-alpha color) 0.5)\n             (hcl:gc-generation t)\n             (return-from find-non-transparent-pixel (values x y))))))\n     nil)))\n\n(defun zoom-to-change-callback (data interface)\n  \"Callback function for zoom-to-change button - currently just logs\"\n  (declare (ignore data))\n  (with-image-from-external (image (image-pane interface)\n                             (read-image (image-pane interface)\n                                         (comparison (image-pane interface))))\n    (multiple-value-bind (x y)\n       (find-non-transparent-pixel\n        (image-pane interface)\n        image)\n     (setf (capi:button-enabled (zoom-button interface))  nil)\n     (%zoom-to interface x y\n               :finally\n               (lambda ()\n                 (setf (capi:button-enabled (zoom-button interface)) t)))))\n  (log:debug \"Zoom to change button pressed for interface: ~a\" interface))\n\n(defun zoom-in-callback (interface)\n  \"Callback function for zoom in menu item\"\n  (let ((pane (image-pane interface)))\n    (let ((center-x (/ (gp:port-width pane) 2))\n          (center-y (/ (gp:port-height pane) 2)))\n      (process-zoom pane center-x center-y 1.2))))\n\n(defun zoom-out-callback (interface)\n  \"Callback function for zoom out menu item\"\n  (let ((pane (image-pane interface)))\n    (let ((center-x (/ (gp:port-width pane) 2))\n          (center-y (/ (gp:port-height pane) 2)))\n      (process-zoom pane center-x center-y 0.8))))\n\n\n\n(defun transform-to-3dmat (transform)\n  (destructuring-bind (a b c d e f) transform\n    (3d-matrices:mat3 (vector a c e\n                              b d f\n                              0 0 1))))\n\n(defun 3dmat-to-transform (mat3)\n  \"Convert a 3d-matrices:mat3 matrix to a graphics port transform\"\n  (let ((m (3d-matrices:marr mat3)))\n    (gp:make-transform (aref m 0) (aref m 3) (aref m 1) (aref m 4) (aref m 2) (aref m 5))))\n\n\n\n(defun process-zoom (pane x y delta)\n  (disable-mouse-move pane)\n  (let ((interface (capi:element-interface pane)))\n    (let ((screenshotbot-js-stubs::*make-matrix-impl* #'gp:make-transform))\n      (let ((dm (screenshotbot-js::calc-transform-for-zoom x y\n                                                           (transform-to-3dmat\n                                                            (image-transform pane))\n                                                           delta)))\n        (gp:postmultiply-transforms\n         (image-transform pane)\n         dm)\n        (invalidate-scroll-position interface)\n        (gp:invalidate-rectangle pane)))))\n\n(defun toggle-previous-updated (interface)\n  \"Toggle between showing the previous image and the updated image\"\n  (let* ((current-selection (capi:choice-selected-item (view-radio-panel interface))))\n    (case current-selection\n      (:previous\n       (setf (capi:choice-selected-item (view-radio-panel interface)) :updated)\n       (view-radio-panel-callback :updated interface))\n      (otherwise\n       (setf (capi:choice-selected-item (view-radio-panel interface)) :previous)\n       (view-radio-panel-callback :previous interface)))))\n\n(defun image-pane-char-press (pane x y character)\n  (log:debug \"Got ~a for ~a,~a\" character x y)\n  (case character\n    (#\\+\n     (process-zoom pane x y 1.3))\n    (#\\-\n     (process-zoom pane x y (/ 1 1.3)))))\n\n(defun image-pane-press (pane x y)\n  \"Handle mouse button press on image pane\"\n  (setf (press-start pane)\n        (cons x y))\n  (log:debug \"Image pane press at (~a, ~a)\" x y))\n\n(defun image-pane-release (pane x y)\n  \"Handle mouse button release on image pane\"\n  (setf (press-start pane) nil)\n  (log:debug \"Image pane release at (~a, ~a)\" x y))\n\n(defun image-pane-drag (pane x y)\n  \"Handle mouse drag on image pane\"\n  (log:debug \"Image pane drag at (~a, ~a)\" x y)\n  (when (press-start pane)\n    (destructuring-bind (startx . starty)\n        (press-start pane)\n      (gp:postmultiply-transforms\n       (image-transform pane)\n       (gp:make-transform 1 0 0 1 (- x startx) (- y starty)))\n      (setf (press-start pane)\n            (cons x y))\n      (gp:invalidate-rectangle pane))))\n\n\n(defclass comparison-image-layer (abstract-image-layer)\n  ((image1-layer :initarg :image1-layer\n                 :reader image1-layer)\n   (image2-layer :initarg :image2-layer\n                 :reader image2-layer))\n  (:default-initargs\n   :alpha 1))\n\n(defmethod image-layer-read-p ((self comparison-image-layer))\n  (and\n   (image1-layer self)\n   (image2-layer self)\n   (image-layer-ready-p (image1-layer self))\n   (image-layer-read-p (image2-layer self))))\n\n(defmethod loading-p ((self comparison-image-layer))\n  (call-next-method))\n\n\n(defmethod compare-images (pane (before gp:image) (after gp:image))\n  (let ((transparent (color:convert-color pane :transparent))\n        (red (color:convert-color pane :red)))\n   (let* ((width (max (gp:image-width before) (gp:image-width after)))\n          (height (max (gp:image-height before) (gp:image-height after)))\n          (result (gp:make-image pane width height :alpha t)))\n     (with-image-access (before-access pane before)\n       (with-image-access (after-access pane after)\n         (with-image-access (result-access pane result :write t)\n           (flet ((safe-image-access-pixel (access x y width height)\n                    (if (and (< x width) (< y height))\n                        (gp:image-access-pixel access x y)\n                        (color:convert-color pane :transparent))))\n             (loop for y from 0 below height do\n               (loop for x from 0 below width do\n                 (let ((before-color (color:unconvert-color pane (safe-image-access-pixel before-access x y (gp:image-width before) (gp:image-height before))))\n                       (after-color (color:unconvert-color pane (safe-image-access-pixel after-access x y (gp:image-width after) (gp:image-height after)))))\n                   (if (color:colors= before-color after-color)\n                       (setf (gp:image-access-pixel result-access x y) transparent)\n                       (setf (gp:image-access-pixel result-access x y) red)))))))))\n     (unwind-protect\n          (gp:externalize-image pane result :type :png :quality 100)\n       (gp:free-image pane result)))))\n\n(defmethod read-image (pane (self comparison-image-layer))\n  (log:debug \"Loading comparison for ~a\" self)\n  (cached-image self))\n\n(defmethod read-image-async (pane (self comparison-image-layer))\n  ;; Comparison-image-layer is a synchronous layer.\n  (when (and\n         (image-layer-ready-p (image1-layer self))\n         (image-layer-ready-p (image2-layer self)))\n    (let ((last (cached-image self)))\n      (or-setf\n       (cached-image self)\n       (alexandria:when-let* ((before-image (cached-image (image1-layer self)))\n                              (after-image (cached-image (image2-layer self))))\n         (with-image-from-external (before-image pane before-image)\n           (with-image-from-external (after-image pane after-image)\n             (let ((comparison-image (compare-images pane before-image after-image)))\n               comparison-image))))))))\n\n(defun create-empty-interface (&key image1 image2 destroy-callback)\n  (let ((image1-layer (make-instance 'image-layer\n                                     :image image1\n                                     :alpha 0.1))\n        (image2-layer (make-instance 'image-layer\n                                     :image image2\n                                     :alpha 0)))\n    (make-instance 'image-window\n           :title \"Pixel Diff\"\n           :image1 image1-layer\n           :image2 image2-layer\n           :destroy-callback destroy-callback)))\n\n\n(defun image-pane-resize-callback (pane x y width height)\n  \"Handle resize events for the image pane\"\n  (declare (ignore x y width height))\n  (gp:invalidate-rectangle pane))\n\n\n(defun open-interface (image))\n\n(defun test-example ()\n  (display (create-empty-interface\n            :image1 \"/home/arnold/builds/fast-example/screenshots/image.png\"\n            :image2 \"/home/arnold/builds/fast-example/screenshots-copy/image.png\")))\n\n\n;; (test-example)\n\n\n\n(defun set-image-pair (image-pane image-pair)\n  (let ((interface (capi:element-interface image-pane)))\n    (free-image-layer image-pane (image1 image-pane))\n    (free-image-layer image-pane (image2 image-pane))\n    (free-image-layer image-pane (comparison image-pane))\n    (let ((image1-layer (make-instance 'image-layer\n                                       :image (image-pair:previous image-pair)\n                                       :alpha 0.1))\n          (image2-layer (make-instance 'image-layer\n                                       :image (image-pair:updated image-pair)\n                                       :alpha 0)))\n      (setf (image1 (image-pane interface)) image1-layer)\n      (setf (image2 (image-pane interface)) image2-layer)\n      (setf (core-transform (image-pane interface)) nil)\n      (setf (image-transform (image-pane interface)) (make-identity))\n      (view-radio-panel-callback\n       (capi:choice-selected-item (view-radio-panel interface))\n       interface)\n      (gp:invalidate-rectangle (image-pane interface)))))\n\n"
  },
  {
    "path": "src/pixel-diff/external-images.lisp",
    "content": "(defpackage :pixel-diff/external-images\n  (:use #:cl\n        #:capi)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:with-image-from-external))\n(in-package :pixel-diff/external-images)\n\n(defvar *clock* 0)\n\n(defclass cache-entry ()\n  ((pane :initarg :pane\n         :reader pane)\n   (external-image :initarg :external-image\n                   :reader external-image)\n   (image :initarg :image\n          :reader image)\n   (timestamp :initform *clock*\n              :accessor timestamp)))\n\n(defvar *cache* nil)\n(defvar *max-cache-size* 5)\n\n(defun maybe-evict-cache ()\n  (when (>= (length *cache*) *max-cache-size*)\n    (let ((oldest-entry (reduce (lambda (a b)\n                                  (if (< (timestamp a) (timestamp b))\n                                      a\n                                      b))\n                                *cache*)))\n      (gp:free-image\n       (pane oldest-entry)\n       (image oldest-entry))\n      (setf *cache* (remove oldest-entry *cache*)))))\n\n(defun find-cache-entry (pane external-image)\n  (find-if (lambda (entry)\n             (and (eq (pane entry) pane)\n                  (equal (external-image entry) external-image)))\n           *cache*))\n\n\n(def-easy-macro with-image-from-external (&binding image pane external-image &fn fn)\n  (let ((existing (find-cache-entry pane external-image)))\n    (cond\n      (existing\n       (log:debug \"using cached image\")\n       (fn (image existing)))\n      (t\n       (log:debug \"Loading fresh image\")\n       (let ((image (gp:load-image pane external-image :editable t)))\n         (maybe-evict-cache)\n         (push\n          (make-instance 'cache-entry\n                         :pane pane\n                         :external-image external-image\n                         :image image)\n          *cache*)\n         (fn image))))))\n"
  },
  {
    "path": "src/pixel-diff/fli-templates.lisp",
    "content": "(OBJC::DEFINE-PRECOMPILED-INVOKE-FUNCTIONS ((:FUNCTION (OBJC:OBJC-OBJECT-POINTER OBJC:SEL) (:POINTER (OBJC::OBJC-NAMED-STRUCT \"CGContext\"))))) \n"
  },
  {
    "path": "src/pixel-diff/git-diff.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :pixel-diff/git-diff\n  (:use #:cl)\n  (:import-from #:pixel-diff/image-pair\n                #:updated\n                #:previous\n                #:image-pair)\n  (:import-from #:pixel-diff/browser\n                #:image-browser-window)\n  (:import-from #:pixel-diff/differ\n                #:load-image\n                #:image-layer)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool)\n  (:import-from #:alexandria\n                #:when-let)\n  (:export\n   #:git-repo\n   #:make-git-diff-browser))\n(in-package :pixel-diff/git-diff)\n\n(defvar *max-pool* (make-instance 'max-pool :max 10))\n\n(defclass git-repo ()\n  ((directory :initarg :directory\n              :reader repo-directory)\n   (tree-cache :initform (make-hash-table :test #'equal)\n               :reader tree-cache\n               :documentation \"For a given rev, the tree associated with the parsed commit\")))\n\n(defmethod make-git-command ((self git-repo)\n                              &rest\n                                args)\n  (list*\n   #-mswindows\n   \"/usr/bin/env\"\n    \"git\" \"--git-dir\" (namestring\n                       (path:catdir (repo-directory self) \".git/\"))\n    args))\n\n(defmethod $git ((self git-repo) args &rest rest)\n  (log:debug \"Running: ~a\" args)\n  (apply #'uiop:run-program\n   (apply #'make-git-command self args)\n   :ignore-error-status nil\n   :output 'string\n   :error-output t\n   rest))\n\n(defmethod files-changed ((self git-repo) ref1 ref2)\n  \"Return list of modified files between REF1 and REF2.\n   If REF2 is NIL, compares REF1 against the working directory.\n   Returns filenames of files that have been modified\"\n  (loop for line in (str:lines\n                     ($git self\n                           (remove-if\n                            #'null\n                            (list \"diff\" \"--name-status\" ref1 ref2))))\n        for status = (elt line 0)\n        if (eql status #\\M)\n          collect (str:substring 2 nil line)))\n\n(defmethod pngs-changed ((self git-repo) ref1 ref2)\n  \"Return list of PNG files that changed between REF1 and REF2.\n   If REF2 is NIL, compares REF1 against the working directory.\"\n  (loop for file in (files-changed self ref1 ref2)\n        if (str:ends-with-p \".png\" file)\n          collect file))\n\n\n(defmethod rev-parse ((self git-repo) ref)\n  (str:trim ($git self (list \"rev-parse\" ref))))\n\n(defmethod parse-tree ((self git-repo) ref)\n  \n  (symbol-macrolet ((cache (gethash ref (tree-cache self))))\n    (or\n     cache\n     (setf\n      cache\n      (let ((commit (rev-parse self ref)))\n        (log:debug \"Uncached rev-parse\")\n        (str:trim\n         ($git self (list \"show\" \"--pretty=%T\" \"--no-patch\" commit))))))))\n\n(defmethod open-git-file ((self git-repo) ref pathname &key (output :stream))\n  (let ((ref (parse-tree self ref)))\n    (sys:run-shell-command\n     (make-git-command self \"show\" (format nil \"~a:~a\" ref pathname))\n     :output output\n     :if-output-exists :supersede)))\n\n(defclass git-blob ()\n  ((repo :initarg :repo\n         :reader repo)\n   (ref :initarg :ref\n        :reader ref)\n   (pathname :initarg :pathname\n             :reader git-pathname)))\n\n\n(defmethod load-image (pane (blob git-blob) callback)\n  (log:debug \"Loading blob: ~a:~a\" (ref blob) (git-pathname blob))\n  (uiop:with-temporary-file (:pathname p :type \"png\" :keep t)\n    (make-thread\n     (lambda ()\n       (open-git-file (repo blob)\n                      (ref blob)\n                      (git-pathname blob)\n                      :output p)\n       (capi:apply-in-pane-process-if-alive\n        pane\n        (lambda ()\n          (load-image pane p callback)\n          #-mswindows\n          (delete-file p))))\n     :pool *max-pool*)))\n\n\n;; (hcl:profile (Sleep 5))\n\n\n\n\n;; (pngs-changed (make-instance 'git-repo :directory \"/home/arnold/builds/ios-oss/\") \"HEAD\" \"HEAD^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\")\n\n;; (open-git-file (make-instance 'git-repo :directory \"/home/arnold/builds/ios-oss/\") \"HEAD^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\" \"Kickstarter-iOS/Features/Activities/Controller/__Snapshots__/ActivitiesViewControllerTests/testMultipleSurveys_NotFacebookConnected_YouLaunched.lang_de_device_phone4_7inch.png\" :output #P\"/tmp/test.png\")\n\n;; \n\n\n(defun test-example ()\n  (capi:contain  (make-git-diff-browser (make-instance 'git-repo :directory \"/home/arnold/builds/ios-oss/\") \"HEAD\" \"HEAD^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\")))\n\n(defun make-git-diff-browser (repo ref1 ref2 &rest args)\n  \"Create a git diff browser comparing REF1 and REF2.\n   If REF2 is NIL, compares REF1 against the working directory files.\n   Returns NIL if no PNG files have changed.\"\n  (when-let ((pngs-changed (pngs-changed repo ref1 ref2)))\n    (let ((image-pairs\n            (loop for png in pngs-changed\n                  collect\n                  (make-instance 'image-pair\n                                 :title png\n                                 :previous (make-instance 'git-blob\n                                                          :repo repo\n                                                          :ref ref1\n                                                          :pathname png)\n                                 :updated (if ref2\n                                              ;; Normal case: compare against another ref\n                                              (make-instance 'git-blob\n                                                             :repo repo\n                                                             :ref ref2\n                                                             :pathname png)\n                                              ;; Special case: compare against working directory\n                                              (path:catfile (repo-directory repo) png))))))\n      (apply #'make-instance 'image-browser-window\n             :image1 (make-instance 'image-layer\n                                    :image (previous (car image-pairs))\n                                    :alpha 0.1)\n             :image2 (make-instance 'image-layer\n                                    :image (updated (car image-pairs))\n                                    :alpha 0)\n             :image-pair-list image-pairs\n             args))))\n\n\n\n"
  },
  {
    "path": "src/pixel-diff/image-pair.lisp",
    "content": "(defpackage :pixel-diff/image-pair\n  (:use #:cl)\n  (:export #:image-pair\n           #:previous\n           #:updated\n           #:make-image-pair\n           #:image-pair-title))\n(in-package :pixel-diff/image-pair)\n\n(defclass image-pair ()\n  ((title :initarg :title\n          :initform nil\n          :reader image-pair-title)\n   (previous :initarg :previous\n             :accessor previous\n             :type (or string pathname null)\n             :documentation \"Image-layer of the previous file\")\n   (updated :initarg :updated\n            :accessor updated\n            :type (or string pathname null)\n            :documentation \"Image-layer of the next file\"))\n  (:documentation \"Represents a pair of image files for comparison\"))\n\n(defun make-image-pair (previous updated)\n  \"Create a new image-pair instance with the given previous and updated image paths.\"\n  (make-instance 'image-pair :previous previous :updated updated))\n\n\n"
  },
  {
    "path": "src/pixel-diff/lisp-stubs.lisp",
    "content": "(in-package :screenshotbot-js-stubs)\n\n\n(defun make-matrix-impl (a b c d e f)\n  (3d-matrices:mat\n   a c e\n   b d f\n   0 0 1))\n\n(defvar *make-matrix-impl* #'make-matrix-impl)\n\n(defun make-matrix (&rest args)\n  (apply *make-matrix-impl* args))\n\n(make-matrix 1 1 1 1 1 1 )\n"
  },
  {
    "path": "src/pixel-diff/lispworks.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>com.apple.security.cs.allow-jit</key>\n    <true/>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n    <key>com.apple.security.cs.disable-library-validation</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "src/pixel-diff/main.lisp",
    "content": "(defpackage :pixel-diff/main\n  (:use #:cl)\n  (:import-from #:pixel-diff/git-diff\n                #:git-repo\n                #:make-git-diff-browser)\n  (:import-from #:pixel-diff/usage\n                #:show-usage-dialog)\n  (:import-from #:uiop\n                #:run-program\n                #:getcwd))\n(in-package :pixel-diff/main)\n\n(define-condition usage-error (error)\n  ((format-string :initarg :format-string :reader usage-error-format-string)\n   (arguments :initarg :arguments :reader usage-error-arguments))\n  (:report (lambda (condition stream)\n             (apply #'format stream \n                    (usage-error-format-string condition)\n                    (usage-error-arguments condition)))))\n\n(defun usage-error (format-string &rest arguments)\n  \"Signal a usage-error condition with formatted message\"\n  (error 'usage-error \n         :format-string format-string \n         :arguments arguments))\n\n(defun is-git-ref-p (ref)\n  \"Check if a string is a valid git reference\"\n  (handler-case\n      (progn\n        (run-program \n         (list \"git\" \"rev-parse\" \"--verify\" (format nil \"~a^{commit}\" ref))\n         :output :string\n         :error-output :string\n         :ignore-error-status nil)\n        t)\n    (error () nil)))\n\n(defun is-file-p (path)\n  \"Check if a path is a file that exists\"\n  (and (stringp path) (probe-file path)))\n\n(defun show-marketing-message ()\n  \"Display marketing message about Screenshotbot\"\n  (format t \"~%~%💡 Found UI changes? Screenshotbot automates screenshot comparisons in your CI/CD pipeline.\n   Screenshotbot stores your screenshots, and no more manual \\\"record\\\"-ing.~%~%\")\n  (format t \"   Catch regressions on your Pull Request  → https://screenshotbot.io~%~%\"))\n\n\n\n(defun git-diff-command ()\n  (clingon:make-command\n   :name \"git-diff\"\n   :description \"Compare git revisions of images\"\n   :usage \"git-diff <image-path> [revision1] [revision2]\"\n   :options (list)\n   :handler (lambda (cmd)\n              (declare (ignore cmd))\n              (error \"unimplemented\"))))\n\n(defun pixel-diff-command/handler (cmd)\n  (let ((args (clingon:command-arguments cmd))\n        (verbose (clingon:getopt cmd :verbose)))\n    \n    ;; Configure logging based on verbose flag\n    (when verbose\n      (log:config :debug))\n    \n    (when (< (length args) 1)\n      (show-usage-dialog)\n      (return-from pixel-diff-command/handler 0))\n    \n    (let ((arg1 (first args))\n          (arg2 (second args)))\n      (cond\n        ;; Both are files - use file comparison\n        ((and (is-file-p arg1) (is-file-p arg2))\n         (log:debug \"Comparing image files:~%  Before: ~a~%  After:  ~a~%\" arg1 arg2)\n         (let ((interface (pixel-diff/differ:create-empty-interface\n                           :image1 (namestring arg1)\n                           :image2 (namestring arg2)\n                           :destroy-callback (lambda (interface)\n                                               (declare (ignore interface))\n                                               (show-marketing-message)))))\n           (capi:display interface)\n           0))\n        \n        ;; Both are git refs - use git comparison\n        ((and (is-git-ref-p arg1) (is-git-ref-p arg2))\n         (log:debug \"Comparing git references:~%  Before: ~a~%  After:  ~a~%\" arg1 arg2)\n         (let* ((repo (make-instance 'git-repo :directory (getcwd)))\n                (interface (make-git-diff-browser repo arg1 arg2\n                                                  :destroy-callback (lambda (interface)\n                                                                      (declare (ignore interface))\n                                                                      (show-marketing-message)))))\n           (cond\n             (interface\n              (capi:display interface)\n              0)\n             (t\n              (format t \"No PNG images changed between ~a and ~a~%\" arg1 arg2)\n              0))))\n        \n        ;; Single git ref - compare against working directory\n        ((and (is-git-ref-p arg1) (null arg2))\n         (log:debug \"Comparing git reference against working directory:~%  Before: ~a~%  After:  working directory~%\" arg1)\n         (let* ((repo (make-instance 'git-repo :directory (getcwd)))\n                (interface (make-git-diff-browser repo arg1 nil\n                                                  :destroy-callback (lambda (interface)\n                                                                      (declare (ignore interface))\n                                                                      (show-marketing-message)))))\n           (cond\n             (interface\n              (capi:display interface)\n              0)\n             (t\n              (format t \"No PNG images changed between ~a and working directory~%\" arg1)\n              0))))\n        \n        ;; Mixed or invalid arguments\n        (t\n         (cond\n           ((not (or (is-file-p arg1) (is-git-ref-p arg1)))\n            (usage-error \"Error: '~a' is neither a valid file nor a git reference~%\" arg1))\n           ((and arg2 (not (or (is-file-p arg2) (is-git-ref-p arg2))))\n            (usage-error \"Error: '~a' is neither a valid file nor a git reference~%\" arg2))\n           (t\n            (usage-error \"Error: Cannot mix files and git references. Use git-ref + git-ref, git-ref alone, or file + file~%\"))))))))\n\n(defun pixel-diff-command ()\n  (clingon:make-command\n   :name \"pixel-diff\"\n   :description \"Compare two images and display the differences\"\n   :usage \"pixel-diff <subcommand> [options] [args...]\"\n   :options (list\n             (clingon:make-option\n              :flag\n              :description \"Enable verbose debug logging\"\n              :short-name #\\v\n              :long-name \"verbose\"\n              :key :verbose))\n   :sub-commands (list (git-diff-command)\n                       (help-command))\n   :handler #'pixel-diff-command/handler))\n\n(defun help-command ()\n  (clingon:make-command\n   :name \"help\"\n   :description \"Show help information\"\n   :usage \"help [subcommand]\"\n   :options (list)\n   :handler (lambda (cmd)\n              (let ((args (clingon:command-arguments cmd)))\n                (format t \"Help for subcommand: ~a~%\" (first args))\n                (format t \"pixel-diff - Image comparison tool~%~%\")\n                (format t \"Usage: pixel-diff <subcommand> [options] [args...]~%~%\")\n                (format t \"Available subcommands:~%\")\n                (format t \"  help      Show this help message~%\")\n                (format t \"  git-diff  Compare git revisions of images~%~%\")\n                (format t \"Direct usage:~%\")\n                (format t \"  pixel-diff <image1> <image2>   Compare two image files~%\")\n                (format t \"  pixel-diff <ref1> <ref2>       Compare two git references~%\")\n                (format t \"  pixel-diff <ref1>              Compare git ref vs working directory~%\")\n                0))))\n\n\n(defun %main (args)\n  \"Main entry point for the pixel-diff application\"\n  (let ((app (pixel-diff-command)))\n    (clingon:run app args)))\n\n(defun main ()\n  (%main (cdr sys:*line-arguments-list*)))\n\n(defun test-%main ()\n  \"Test function for %main using example images\"\n  (let ((image1 (namestring (asdf:system-relative-pathname :pixel-diff \"examples/image1.png\")))\n        (image2 (namestring (asdf:system-relative-pathname :pixel-diff \"examples/image2.png\"))))\n    (unless (probe-file image1)\n      (format t \"Error: Test image not found: ~a~%\" image1)\n      (return-from test-%main 1))\n    \n    (unless (probe-file image2)\n      (format t \"Error: Test image not found: ~a~%\" image2)\n      (return-from test-%main 1))\n    \n    (format t \"Running test with example images...~%\")\n    (%main (list image1 image2))))\n\n;; (test-%main)\n\n\n\n\n"
  },
  {
    "path": "src/pixel-diff/make-dmg.sh",
    "content": "#!/bin/bash\n\n# Configuration\nAPP_PATH=\"pixel-diff.app\"\nDMG_NAME=\"pixel-diff-installer.dmg\"\n\n# Signing configuration\nAPPLE_ID=\"arnstein87@gmail.com\"\nDEVELOPER_ID=\"Developer ID Application: Modern Interpreters Inc (HQ25CUJ52L)\"\nTEAM_ID=\"HQ25CUJ52L\"\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m'\n\necho -e \"${YELLOW}Creating signed and notarized DMG installer...${NC}\"\n\n# Check if password is set\nif [ -z \"$NOTARIZATION_PASSWORD\" ]; then\n    echo -e \"${RED}Error: NOTARIZATION_PASSWORD environment variable not set${NC}\"\n    echo -e \"${YELLOW}Run: export NOTARIZATION_PASSWORD=\\\"your-app-specific-password\\\"${NC}\"\n    exit 1\nfi\n\n# Check if app exists\nif [ ! -d \"$APP_PATH\" ]; then\n    echo -e \"${RED}Error: $APP_PATH not found${NC}\"\n    exit 1\nfi\n\n# Remove any existing DMG\nif [ -f \"$DMG_NAME\" ]; then\n    echo -e \"${YELLOW}Removing existing DMG...${NC}\"\n    rm \"$DMG_NAME\"\nfi\n\n# Create a clean temporary directory\nTEMP_DIR=$(mktemp -d)\necho -e \"${YELLOW}Step 1: Preparing DMG contents in $TEMP_DIR${NC}\"\n\n# Copy app to temp directory\ncp -R \"$APP_PATH\" \"$TEMP_DIR/\"\n\n# Create Applications symlink\nln -s /Applications \"$TEMP_DIR/Applications\"\n\n# Create DMG directly from temp directory\necho -e \"${YELLOW}Step 2: Creating DMG...${NC}\"\nhdiutil create -volname \"Pixel Diff Installer\" \\\n    -srcfolder \"$TEMP_DIR\" \\\n    -ov \\\n    -format UDZO \\\n    -imagekey zlib-level=9 \\\n    \"$DMG_NAME\"\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Error: DMG creation failed${NC}\"\n    rm -rf \"$TEMP_DIR\"\n    exit 1\nfi\n\n# Clean up temp directory\nrm -rf \"$TEMP_DIR\"\n\n# Step 3: Sign the DMG\necho -e \"${YELLOW}Step 3: Code signing the DMG...${NC}\"\ncodesign --force --sign \"$DEVELOPER_ID\" \"$DMG_NAME\"\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Error: DMG code signing failed${NC}\"\n    exit 1\nfi\n\n# Step 4: Notarize the DMG\necho -e \"${YELLOW}Step 4: Submitting DMG for notarization (this may take a few minutes)...${NC}\"\nxcrun notarytool submit \"$DMG_NAME\" \\\n  --apple-id \"$APPLE_ID\" \\\n  --password \"$NOTARIZATION_PASSWORD\" \\\n  --team-id \"$TEAM_ID\" \\\n  --wait\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Error: DMG notarization failed${NC}\"\n    exit 1\nfi\n\n# Step 5: Staple the notarization ticket to DMG\necho -e \"${YELLOW}Step 5: Stapling notarization ticket to DMG...${NC}\"\nxcrun stapler staple \"$DMG_NAME\"\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Warning: DMG stapling failed${NC}\"\nfi\n\n# Verify the DMG\necho -e \"${YELLOW}Step 6: Verifying signed DMG...${NC}\"\ncodesign -dv --verbose=4 \"$DMG_NAME\"\nhdiutil verify \"$DMG_NAME\"\n\nif [ $? -eq 0 ]; then\n    echo -e \"${GREEN}✅ Signed and notarized DMG created successfully!${NC}\"\n    echo -e \"${GREEN}File: $DMG_NAME${NC}\"\n    echo -e \"${GREEN}Size: $(du -h \"$DMG_NAME\" | cut -f1)${NC}\"\n    echo -e \"${GREEN}🎉 Ready for public distribution!${NC}\"\nelse\n    echo -e \"${RED}❌ DMG verification failed${NC}\"\n    exit 1\nfi\n"
  },
  {
    "path": "src/pixel-diff/package.lisp",
    "content": "(defpackage :screenshotbot-js-stubs\n  (:use #:cl\n        #:ps)\n  (:export #:make-matrix))\n\n(defpackage :screenshotbot-js\n  (:use #:cl\n        #:ps\n        #:screenshotbot-js-stubs)\n  (:import-from #:3d-vectors\n                #:v*\n                #:vec3\n                #:vx3\n                #:vec\n                #:v-)\n  (:import-from #:3d-matrices\n                #:m+\n                #:m-\n                #:m*\n                #:minv))\n\n(setf (ps:ps-package-prefix :3d-matrices) \"_mat_\")\n(setf (ps:ps-package-prefix :3d-vectors) \"_vec_\")\n"
  },
  {
    "path": "src/pixel-diff/pixel-diff.asd",
    "content": "(defsystem :pixel-diff\n  :version \"1.0.3\"\n  :serial t\n  :depends-on (:pixel-diff.math\n               :easy-macros\n               :clingon\n               :util.threading\n               :cl-fad\n               :log4cl)\n  :components ((:file \"about\")\n               (:file \"usage\")\n               (:file \"image-pair\")\n               #+darwin\n               (:file \"fli-templates\")\n               (:file \"external-images\")\n               (:file \"differ\")\n               (:file \"browser\")\n               (:file \"git-diff\")\n               (:file \"main\")))\n\n#+lispworks\n(defsystem :pixel-diff/tests\n  :serial t\n  :depends-on (:pixel-diff\n               :fiveam)\n  :components ((:file \"suite\")\n               (:file \"test-differ\")))\n"
  },
  {
    "path": "src/pixel-diff/pixel-diff.math-js.asd",
    "content": "(defsystem pixel-diff.math-js\n  :class \"build-utils:js-system\"\n  :serial t\n  :defsystem-depends-on (:build-utils)\n  :depends-on ()\n  :components ((\"build-utils:ps-file\" \"common-ps\")))\n"
  },
  {
    "path": "src/pixel-diff/pixel-diff.math.asd",
    "content": "(defsystem pixel-diff.math/package\n  :depends-on (:parenscript\n               :3d-matrices)\n  :components ((:file \"package\")))\n\n(defsystem pixel-diff.math\n  :depends-on (:pixel-diff.math/package)\n  :serial t\n  :components ((:file \"lisp-stubs\")\n               (:file \"common-ps\")))\n\n(defsystem pixel-diff.math/tests\n  :serial t\n  :depends-on (:pixel-diff.math\n               :util/fiveam)\n  :components ((:file \"test-common-ps\")))\n\n\n"
  },
  {
    "path": "src/pixel-diff/sign-mac-app.sh",
    "content": "#!/bin/bash\n\n# Configuration - UPDATE THESE VALUES\nAPP_PATH=\"pixel-diff.app\"\nAPPLE_ID=\"arnstein87@gmail.com\"\nDEVELOPER_ID=\"Developer ID Application: Modern Interpreters Inc (HQ25CUJ52L)\"\nTEAM_ID=\"HQ25CUJ52L\"\nAPP_PASSWORD=\"${NOTARIZATION_PASSWORD}\"  # Set via: export NOTARIZATION_PASSWORD=\"your-password\"\nBUNDLE_ID=\"io.screenshotbot.pixeldiff\"  # Matches Info.plist\nENTITLEMENTS_FILE=\"src/pixel-diff/lispworks.entitlements\"\n\n\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\necho -e \"${YELLOW}Starting Mac app signing and notarization process...${NC}\"\n\n# Check if password is set\nif [ -z \"$NOTARIZATION_PASSWORD\" ]; then\n    echo -e \"${RED}Error: NOTARIZATION_PASSWORD environment variable not set${NC}\"\n    echo -e \"${YELLOW}Run: export NOTARIZATION_PASSWORD=\\\"your-app-specific-password\\\"${NC}\"\n    exit 1\nfi\n\n# Check if app exists\nif [ ! -d \"$APP_PATH\" ]; then\n    echo -e \"${RED}Error: $APP_PATH not found${NC}\"\n    exit 1\nfi\n\n# Step 1: Remove quarantine attributes\necho -e \"${YELLOW}Step 1: Removing quarantine attributes...${NC}\"\nxattr -dr com.apple.quarantine \"$APP_PATH\" 2>/dev/null || true\nxattr -c \"$APP_PATH\" 2>/dev/null || true\n\n# Step 2: Make executable if needed\necho -e \"${YELLOW}Step 2: Setting executable permissions...${NC}\"\nchmod +x \"$APP_PATH/Contents/MacOS\"/*\n\n# Step 3: Code signing\necho -e \"${YELLOW}Step 3: Code signing the app...${NC}\"\ncodesign --force --deep --entitlements $ENTITLEMENTS_FILE --options runtime --sign \"$DEVELOPER_ID\" \"$APP_PATH\"\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Error: Code signing failed${NC}\"\n    exit 1\nfi\n\n# Step 4: Verify code signing\necho -e \"${YELLOW}Step 4: Verifying code signature...${NC}\"\ncodesign -dv --verbose=4 \"$APP_PATH\"\nspctl -a -t exec -vv \"$APP_PATH\"\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Warning: Code signature verification failed${NC}\"\nfi\n\n# Step 5: Create zip for notarization\necho -e \"${YELLOW}Step 5: Creating zip file for notarization...${NC}\"\nZIP_NAME=\"pixel-diff-notarization.zip\"\nditto -c -k --keepParent \"$APP_PATH\" \"$ZIP_NAME\"\n\n# Step 6: Submit for notarization\necho -e \"${YELLOW}Step 6: Submitting for notarization (this may take several minutes)...${NC}\"\nxcrun notarytool submit \"$ZIP_NAME\" \\\n    --apple-id \"$APPLE_ID\" \\\n    --password \"$APP_PASSWORD\" \\\n    --team-id \"$TEAM_ID\" \\\n    --wait\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Error: Notarization failed${NC}\"\n    exit 1\nfi\n\n# Step 7: Staple the notarization ticket\necho -e \"${YELLOW}Step 7: Stapling notarization ticket...${NC}\"\nxcrun stapler staple \"$APP_PATH\"\n\nif [ $? -ne 0 ]; then\n    echo -e \"${RED}Warning: Stapling failed${NC}\"\nfi\n\n# Step 8: Final verification\necho -e \"${YELLOW}Step 8: Final verification...${NC}\"\nspctl -a -t exec -vv \"$APP_PATH\"\n\n# Cleanup\necho -e \"${YELLOW}Cleaning up...${NC}\"\nrm \"$ZIP_NAME\"\n\necho -e \"${GREEN}✅ App signing and notarization complete!${NC}\"\necho -e \"${GREEN}Your app is now ready for distribution.${NC}\"\n\n# Final check\necho -e \"${YELLOW}Final check - trying to run the app...${NC}\"\nopen \"$APP_PATH\"\n"
  },
  {
    "path": "src/pixel-diff/suite.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :pixel-diff/suite\n  (:use #:cl))\n(in-package :pixel-diff/suite)\n\n(defvar *pixel-diff-suite* (fiveam:def-suite :pixel-diff))\n\n(defun run-pixel-diff-tests ()\n  \"Run all pixel-diff tests.\"\n  (fiveam:run! :pixel-diff))\n\n\n\n\n"
  },
  {
    "path": "src/pixel-diff/test-common-ps.lisp",
    "content": "(defpackage :screenshotbot/js/test-common-ps\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot-js\n                #:calc-transform-for-zoom\n                #:calc-transform-for-center\n                #:*min-zoom*\n                #:calc-core-transform)\n  (:import-from #:3d-matrices\n                #:mat\n                #:m*\n                #:m=)\n  (:import-from #:3d-vectors\n                #:v=\n                #:vec))\n(in-package :screenshotbot/js/test-common-ps)\n\n(util/fiveam:def-suite)\n\n(defvar *id* (mat\n              1 0 0\n              0 1 0\n              0 0 1))\n\n(test calc-core-transform\n  (is\n   (m=\n    *id*\n    (calc-core-transform 0 0 10 20))))\n\n(test calc-core-transform-with-same-dims\n  (is\n   (m=\n    *id*\n    (calc-core-transform 10 20 10 20))))\n\n(test calc-core-transform-with-double-dims\n  (is\n   (m=\n    (mat\n     2 0 0\n     0 2 0\n     0 0 1)\n    (calc-core-transform 20 40 10 20))))\n\n(test calc-core-transform-with-not-enough-height\n  (is\n   (m=\n    (mat\n     1 0 5 ;; translate the x by 5\n     0 1 0\n     0 0 1)\n    (calc-core-transform 20 20 10 20))))\n\n(test calc-core-transform-with-not-enough-height-and-also-zoom\n  (is\n   (m=\n    (mat\n     1/2 0 5 ;; translate the x by 5\n     0 1/2 0\n     0 0 1)\n    (calc-core-transform 20 20 20 40))))\n\n(test calc-core-transform-with-not-enough-width\n  (is\n   (m=\n    (mat\n     1 0 0\n     0 1 5\n     0 0 1)\n    ;; css zoom will be 1\n    (calc-core-transform 20 20 20 10))))\n\n(test calc-core-transform-with-not-enough-height-2\n  (let ((*min-zoom* 0.5))\n   (is\n    (m=\n     (mat\n      1 0 5 ;; translate the x by 5\n      0 1 0\n      0 0 1)\n     (calc-core-transform 20 20 10 2000)))))\n\n(test calc-core-transform-with-not-enough-height-with-different-client-width\n  (let ((*min-zoom* 0.5))\n   (is\n    (m=\n     (mat\n      0.1 0 5 ;; translate the x by 5\n      0 0.1 0\n      0 0 1)\n     (calc-core-transform 20 20 100 20000)))))\n\n\n(test calc-transform-for-center-simple\n  (is\n   (m=\n    (mat\n     1 0 0\n     0 1 0\n     0 0 1)\n    (calc-transform-for-center 10 20\n                               10 20\n                               5 10\n                               1))))\n\n(test calc-transform-for-center-with-some-zoom\n  (is\n   (m=\n    (mat\n     2 0 -5\n     0 2 -10\n     0 0 1)\n    (calc-transform-for-center 10 20\n                               10 20\n                               5 10\n                               2))))\n\n(test calc-transform-for-center-with-a-core-transform\n  (let ((transform\n          (calc-transform-for-center 800 500\n                                     360 360\n                                     0 0\n                                     1))\n        (core\n          (calc-core-transform 800 500\n                               360 360)))\n\n    ;; Now 0,0 should be mapped to the center\n    (log:info \"Transform: ~a\" transform)\n    (log:info \"Core: ~a\" core)\n    (is\n     (v=\n      (vec 400 250 1)\n      (m* transform core (vec 0 0 1))))))\n\n(test calc-transform-for-zoom-happy-path\n  (is\n   (m=\n    (mat\n     2 0 0\n     0 2 0\n     0 0 1)\n    (calc-transform-for-zoom\n     0 0\n     (mat\n      1 0 0\n      0 1 0\n      0 0 1)\n     2))))\n\n(defun m~= (a b &optional (tolerance 1e-3))\n  (let ((diff (3d-matrices:m- a b)))\n    (loop for i from 0 to 8\n          always (< (abs (aref (3d-matrices::marr3 diff) i)) tolerance))))\n\n(test calc-transform-for-zoom-invariant-after-multiple-zooms\n  (let ((initial-transform (mat\n                            1 0 0\n                            0 1 0\n                            0 0 1))\n        (mouse-x 100)\n        (mouse-y 150)\n        (zoom-factor 1.5))\n    (let ((transform-after-zooms initial-transform))\n      ;; Zoom in 5 times\n      (loop for i from 1 to 5 do\n        (setf transform-after-zooms\n              (m* (calc-transform-for-zoom mouse-x mouse-y\n                                           transform-after-zooms\n                                           zoom-factor)\n                  transform-after-zooms)))\n      ;; Zoom out 5 times\n      (loop for i from 1 to 5 do\n        (setf transform-after-zooms\n              (m* (calc-transform-for-zoom mouse-x mouse-y\n                                           transform-after-zooms\n                                           (/ 1 zoom-factor))\n                  transform-after-zooms)))\n      ;; Should be back to identity matrix\n      (is (m~= initial-transform transform-after-zooms)))))\n\n(test calc-transform-for-zoom-invariant-after-multiple-zooms-with-initial-translation\n  (let ((initial-transform (mat\n                            1 0 -1000\n                            0 1 -2000\n                            0 0 1))\n        (mouse-x 100)\n        (mouse-y 150)\n        (zoom-factor 1.5))\n    (let ((transform-after-zooms initial-transform))\n      ;; Zoom in 5 times\n      (loop for i from 1 to 5 do\n        (setf transform-after-zooms\n              (m* (calc-transform-for-zoom mouse-x mouse-y\n                                           transform-after-zooms\n                                           zoom-factor)\n                  transform-after-zooms)))\n      ;; Zoom out 5 times\n      (loop for i from 1 to 5 do\n        (setf transform-after-zooms\n              (m* (calc-transform-for-zoom mouse-x mouse-y\n                                           transform-after-zooms\n                                           (/ 1 zoom-factor))\n                  transform-after-zooms)))\n      ;; Should be back to identity matrix\n      (is (m~= initial-transform transform-after-zooms)))))\n\n(test calc-transform-for-zoom-invariant-after-multiple-zooms-back-and-forths\n  (let ((initial-transform (mat\n                            1 0 0\n                            0 1 0\n                            0 0 1))\n        (mouse-x 100)\n        (mouse-y 1500)\n        (zoom-factor 1.5))\n    (let ((transform-after-zooms initial-transform))\n      (dotimes (i 3)\n        ;; Zoom in 5 times\n        (loop for i from 1 to 5 do\n          (setf transform-after-zooms\n                (m* (calc-transform-for-zoom mouse-x mouse-y\n                                             transform-after-zooms\n                                             zoom-factor)\n                    transform-after-zooms)))\n        ;; Zoom out 5 times\n        (loop for i from 1 to 5 do\n          (setf transform-after-zooms\n                (m* (calc-transform-for-zoom mouse-x mouse-y\n                                             transform-after-zooms\n                                             (/ 1 zoom-factor))\n                    transform-after-zooms))))\n      ;; Should be back to identity matrix\n      (is (m~= initial-transform transform-after-zooms 0.1)))))\n\n\n\n"
  },
  {
    "path": "src/pixel-diff/test-differ.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :pixel-diff/test-differ\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:pixel-diff/differ\n                #:transform-to-3dmat\n                #:3dmat-to-transform)\n  (:import-from #:3d-matrices)\n  (:import-from #:gp))\n(in-package :pixel-diff/test-differ)\n\n(in-suite :pixel-diff)\n\n(test 3dmat-to-transform-and-inverse-happy-path-tests\n  (let* ((test-cases '((1 0 0 1 0 0)              ; identity transform\n                       (2 0 0 2 10 20)            ; scale and translate\n                       (1.5 0 0 1.5 -5 15)        ; scale and translate with negatives\n                       (0.5 0 0 0.5 100 -50))))    ; fractional scale\n    (dolist (transform-data test-cases)\n      (let* ((original-transform (apply #'gp:make-transform transform-data))\n             (mat3 (transform-to-3dmat original-transform))\n             (reconstructed-transform (3dmat-to-transform mat3)))\n        (destructuring-bind (a1 b1 c1 d1 e1 f1) original-transform\n          (destructuring-bind (a2 b2 c2 d2 e2 f2) reconstructed-transform\n            (is (< (abs (- a1 a2)) 1e-10))\n            (is (< (abs (- b1 b2)) 1e-10))\n            (is (< (abs (- c1 c2)) 1e-10))\n            (is (< (abs (- d1 d2)) 1e-10))\n            (is (< (abs (- e1 e2)) 1e-10))\n            (is (< (abs (- f1 f2)) 1e-10))))))))\n\n(test 3dmat-to-transform-preserves-transformation-properties\n  (let* ((original-transform (gp:make-transform 2 0 0 3 15 25))\n         (mat3 (transform-to-3dmat original-transform))\n         (reconstructed-transform (3dmat-to-transform mat3))\n         (test-points '((0 0) (10 10) (-5 7) (100 -50))))\n    (dolist (point test-points)\n      (destructuring-bind (x y) point\n        (multiple-value-bind (x1 y1)\n            (gp:transform-point original-transform x y)\n          (multiple-value-bind (x2 y2)\n              (gp:transform-point reconstructed-transform x y)\n            (is (< (abs (- x1 x2)) 1e-10))\n            (is (< (abs (- y1 y2)) 1e-10))))))))\n\n(test transform-to-3dmat-creates-valid-3x3-matrix\n  (let* ((transform (gp:make-transform 1.5 0.2 -0.1 2.0 10 -15))\n         (mat3 (transform-to-3dmat transform))\n         (m (3d-matrices:marr mat3)))\n    (destructuring-bind (a b c d e f) transform\n      (is (= (aref m 0) a))\n      (is (= (aref m 1) c))\n      (is (= (aref m 2) e))\n      (is (= (aref m 3) b))\n      (is (= (aref m 4) d))\n      (is (= (aref m 5) f))\n      (is (= (aref m 6) 0))\n      (is (= (aref m 7) 0))\n      (is (= (aref m 8) 1)))))\n"
  },
  {
    "path": "src/pixel-diff/usage.lisp",
    "content": "(defpackage :pixel-diff/usage\n  (:use #:cl\n        #:capi)\n  (:export #:show-usage-dialog))\n(in-package :pixel-diff/usage)\n\n(defun show-usage-dialog ()\n  \"Display a dialog showing usage instructions for pixel-diff\"\n  (format t \"Usage: pixel-diff <image1> <image2>~%\")\n  (format t \"       pixel-diff <ref1> <ref2>~%\")\n  (format t \"       pixel-diff <ref1>~%\")\n  (format t \"  Compare two images, git references, or git ref vs working directory~%\")\n  (let ((dialog (make-instance 'capi:interface\n                               :title \"Pixel Diff - Usage\"\n                               :layout (make-instance 'capi:column-layout\n                                                      :description\n                                                      (list\n                                                       (make-instance 'capi:display-pane\n                                                                      :text \"Pixel Diff Usage\"\n                                                                      :font (gp:make-font-description\n                                                                             :size 16\n                                                                             :weight :bold)\n                                                                      :visible-min-width 400\n                                                                      :visible-min-height 40)\n                                                       (make-instance 'capi:display-pane\n                                                                      :text \"pixel-diff <image1> <image2>   - Compare two image files\"\n                                                                      :visible-min-width 400\n                                                                      :visible-min-height 30)\n                                                       (make-instance 'capi:display-pane\n                                                                      :text \"pixel-diff <ref1> <ref2>     - Compare two git references\"\n                                                                      :visible-min-width 400\n                                                                      :visible-min-height 30)\n                                                       (make-instance 'capi:display-pane\n                                                                      :text \"pixel-diff <ref1>            - Compare git ref vs working directory\"\n                                                                      :visible-min-width 400\n                                                                      :visible-min-height 30)\n                                                       (make-instance 'capi:row-layout\n                                                                      :description\n                                                                      (list\n                                                                       nil\n                                                                       (make-instance 'capi:push-button\n                                                                                      :text \"OK\"\n                                                                                      :callback (lambda (data interface)\n                                                                                                  (declare (ignore data))\n                                                                                                  (capi:quit-interface interface))\n                                                                                      :callback-type :data-interface)\n                                                                       nil))))\n                               :visible-min-width 450\n                               :visible-min-height 250)))\n    (capi:display-dialog dialog)))\n"
  },
  {
    "path": "src/pkg/cl-pkg.el",
    "content": ";; coding: -*- lexical-binding: t -*-\n\n(defun cl-pkg-search-buffer-package ()\n  (or\n   (let ((case-fold-search t)\n         (regexp (concat \"^[ \\t]*(\\\\(pkg:\\\\)?define-package\\\\>[ \\t']*\"\n                         \"\\\\([^ )]+\\\\)[ \\t]*$\")))\n     (save-excursion\n       (when (or (re-search-backward regexp nil t)\n                 (re-search-forward regexp nil t))\n         (match-string-no-properties 2))))\n   (sly-search-buffer-package)))\n\n(setf sly-find-buffer-package-function 'cl-pkg-search-buffer-package)\n\n(defun cl-pkg--jump-to-define-package ()\n  (interactive)\n  (goto-char 0)\n  (re-search-forward \"(defpackage\\\\|(pkg:define-package\\\\|(uiop:define-package\")\n  (beginning-of-line))\n\n(defun cl-pkg--jump-to-import-from (import-from)\n  (interactive)\n  (cl-flet ((start-of-next-sexp\n          ()\n          (ignore-errors\n            (forward-sexp 2)\n            (backward-sexp 1)\n            t)))\n\n    (forward-char)\n    (forward-sexp 1)\n\n    ;; I'm at the first argument of defpackage\n    (or\n     (cl-loop while (start-of-next-sexp)\n              do\n              (progn\n                (when(string-prefix-p \"(:import-from\" (thing-at-point 'sexp))\n                  (message \"at: %s\" (thing-at-point 'sexp))\n                  (save-excursion\n                    (forward-char)\n                    (forward-sexp 2)\n                    (when (equal import-from (thing-at-point 'sexp))\n                      ;; we're here\n                      (cl-return nil)))))\n              finally\n              (progn\n                ;; if we're here, we didn't find the appropriate import from :/\n                (forward-sexp 1)\n                (insert (format \"\\n(:import-from %s)\" import-from))\n                (funcall indent-line-function)\n                (backward-sexp 1))))))\n\n(defun cl-pkg--insert-tail (name)\n  \"Insert the name to the tail of the current sexp (we're at the opening parenthesis\"\n  (forward-sexp)\n  (backward-char)\n  (insert \"\\n\")\n  (insert name)\n  (funcall indent-line-function))\n\n(defun cl-pkg--reorder-imports ()\n  (save-excursion\n    (indent-pp-sexp)\n    (let ((end (- (save-excursion (end-of-sexp)\n                                  (point)) 1)))\n      (save-excursion\n        (goto-char end)\n        (insert \"\\n\"))\n      (forward-char)\n      (forward-sexp 2)\n      (forward-line)\n      (sort-lines nil (point) end)))\n  ;; finally remove any newlines before the end of the sexp\n  (forward-sexp)\n  (backward-char 2)\n\n  (delete-char 1))\n\n(defun cl-pkg--format-import (x)\n  (cond\n   ((string-prefix-p \"#:\" x)\n    x)\n   (t\n    (format \"#:~s\" x))))\n\n(defvar cl-pkg--add-import-package-history nil)\n\n(defun cl-pkg-add-import (name to)\n  (interactive\n   (list\n    (read-from-minibuffer \"Symbol:\"\n                          (thing-at-point 'sexp))\n    (read-from-minibuffer \"Package:\"\n                          nil\n                          nil\n                          'cl-pkg--add-import-package-history)))\n  (save-excursion\n    (cl-pkg--jump-to-define-package)\n    (cl-pkg--jump-to-import-from to)\n    (save-excursion\n      (cl-pkg--insert-tail name))\n    (save-excursion\n      (cl-pkg--reorder-imports))))\n\n\n(defvar sly-defpackage-regexp\n  \"^(\\\\(cl:\\\\|common-lisp:\\\\|pkg:\\\\|uiop:\\\\|\\\\uiop/package:\\\\)?\\\\(defpackage\\\\|define-package\\\\)\\\\>[ \\t']*\")\n"
  },
  {
    "path": "src/pkg/pkg.asd",
    "content": "(defsystem :pkg\n    :serial t\n    :depends-on (:str)\n    :components ((:file \"pkg\")))\n"
  },
  {
    "path": "src/pkg/pkg.lisp",
    "content": "(defpackage :pkg\n  (:use :cl)\n  (:export :define-package))\n(in-package :pkg)\n\n(defun fix-name (to from)\n  (let ((to (str:split \"/\" (string to)))\n        (from (str:split \"/\" (string from))))\n    (make-symbol\n     (str:join\n      \"/\"\n      (cond\n        ((equal \"..\" (car from))\n         (append (butlast (butlast to))\n                 (cdr from)))\n        ((equal \".\" (car from))\n         (append (butlast to) (cdr from)))\n        (t\n         from))))))\n\n(defun fix-clause (name clause)\n  (labels ((fix-rel (other)\n           (fix-name name other))\n           (fix-import-from (clause)\n             `(,(car clause) ,(cadr clause)\n               ,@(cddr clause))))\n   (case (car clause)\n     (:import-from\n      (fix-import-from clause))\n     (:use-reexport\n      `(:use-reexport ,@ (cdr clause)))\n     (:use\n      `(:use ,@ (cdr clause)))\n     (:shadowing-import-from\n      clause)\n     (otherwise clause))))\n\n(defmacro define-package (name &rest clauses)\n  `(progn\n    (uiop:define-package ,name\n        ,@ (loop for clause in clauses collect\n                                       (fix-clause name clause)))\n    (in-package ,name)))\n"
  },
  {
    "path": "src/quick-patch/.circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    docker:\n      - image: cimg/base:2021.04\n    steps:\n      - checkout\n      - run:\n          name: Install SBCL\n          command: sudo apt-get update && sudo apt-get install -y sbcl\n      - run:\n          name: Install quicklisp\n          command: |\n            curl -O https://beta.quicklisp.org/quicklisp.lisp\n            sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)'\n      - run:\n          name: Run tests\n          command: sbcl --script run-circleci.lisp\n"
  },
  {
    "path": "src/quick-patch/LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "src/quick-patch/README.md",
    "content": "\n# Quick-Patch\n\n[![tdrhq](https://circleci.com/gh/tdrhq/quick-patch.svg?style=shield)](https://app.circleci.com/pipelines/github/tdrhq/quick-patch?branch=main)\n\nThis is a really simply library to override sytems in Quicklisp, or to\nadd libraries that are not available in Quicklisp.\n\nThe goal of this project is to make it easy for people to contribute\nto existing projects. Currently with Quicklisp, if somebody chooses to\ncontribute, they'd have to wait a month before they can get the\nupdated version, or they'd have to use git submodules to manage their\npatches. Submodules are super hard to maintain, especially across\nmultiple developers or CI machines.\n\nQuick-patch does one thing, and does it really simply: it checks out a\nrepository at a commit that you specify, and adds it to\n`asdf:*central-registry*`. That's it. On subsequent runs if you set it\nup correctly it won't hit the network.\n\n# Usage\n\nCurrently Quick-patch isn't designed to be interactive. In theory\nit'll work, as long as you're adding new repos, but don't rely on it\nfor removing patches interactively.\n\nUsually your project has a top-level script that sets things up. Or\nmaybe you're just using an init file. In either case, before you start\nloading other quicklisp projects, you want to do:\n\n```\n(ql:quickload :quick-patch)\n```\n\nCurrently we don't have any dependencies, so you can override just\nabout any system in quicklisp.\n\nNow you can set up an override. For example, I recently sent a pull\nrequest to `cl+ssl`. At this point, I needed to use my own patched\nversion, so I added this to my loading script:\n\n```\n(quick-patch:register \"https://github.com/tdrhq/cl-plus-ssl\"\n                      \"4c614fc3f28017f5c5f4c72a8ce413dd042bfb09\")\n```\n\nNotice I used the full git commit hash. A partial hash or a tag/branch\nname will work, but it will cause quick-patch to hit the network and\ndo a fetch on every startup, and it can get really annoying. With the\nfull SHA hash, we can quickly check on subsequent runs that the repo\nwe checked-out is on the correct commit. So this is how I recommend\nyou use it.\n\nFinally, we need to tell quick-patch to do all the work required to\nfetch stuff.\n\n```\n(quick-patch:checkout-all \"build/quick-patch/\")\n```\n\n`checkout-all` takes one argument which is going to be your cache\ndirectory. If this is under a git repository, make sure your cache\ndirectory is in your `.gitignore`.\n\nNow I can go ahead and `(ql:quickload ...)` or `(asdf:load-system\n...)` any other project that depends on `cl+ssl`, and it'll correctly\npick out my patched version.\n\nHappy hacking!\n\n# License\n\nMozilla Public License, v2.\n\n(No specific reason for the license, it's mostly arbitrarily chosen,\nbut this is what it is at the moment.)\n\nWe have copy-pasted some code in `quickpatch/util` from other\nlibraries to keep our dependencies minimal, and this code might be\nunder a difference license:\n\n`directory-exists-p` and `catdir` are taken from `cl-fad` which is\nCopyright Edmund Weitz and released under BSD 2 Clause.\n\n`trim` is taken from `cl-str` which is Copyright @vindarel, and under\nthe MIT License.\n\n# Authors\n\nArnold Noronha <arnold@tdrhq.com>. While you're here, may I recommend\n[Screenshotbot](https://github.com/screenshotbot/screenshotbot-oss)?\n\n## wait wait, a real world example\n\nSpeaking of Screenshotbot, you can see how we use it\nquick-patch in the real-world:\nhttps://github.com/screenshotbot/screenshotbot-oss/blob/main/scripts/init.lisp\n\nRoughly speaking, we build an image that we always work with, and the\nimage has a hook that runs every time the image starts, and that hooks\nsets up quick-patch.\n"
  },
  {
    "path": "src/quick-patch/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :quick-patch\n    (:use #:cl)\n  (:use-reexport #:quick-patch/impl))\n"
  },
  {
    "path": "src/quick-patch/impl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :quick-patch/impl\n  (:use #:cl)\n  (:import-from #:quick-patch/util\n                #:trim\n                #:catdir\n                #:directory-exists-p)\n  (:export\n   ;; Deprecated exports\n   #:register-external\n   #:prepare-externals)\n  (:export\n   #:*cache-dir*\n   #:checkout-all\n   #:register))\n(in-package :quick-patch/impl)\n\n(defvar *externals* nil)\n\n(defvar *cache-dir* nil)\n\n(defun register (repo commit &key (subdirs  (list (make-pathname :directory '(:relative)))))\n  \"Clone the repo specified by REPO, and checkout COMMIT. If SUBDIRS\nis provided, add each of the directories to asdf:*central-registry*\"\n  (pushnew (list repo commit subdirs)\n           *externals*\n           :test #'equal))\n\n(defun register-external (&rest args)\n  (apply #'register args))\n\n(defun run-program-with-errors (cmd &key (directory (uiop:getcwd)))\n  (multiple-value-bind (out err ret)\n      (uiop:run-program cmd\n                        :output 'string\n                        :ignore-error-status t\n                        :error-output 'string\n                        :directory directory)\n    (unless (eql 0 ret)\n      (error \"Shell command `~a` failed: ~%stdout: ~a~%~% stderr:~%~A~%\"\n             cmd\n             out err))\n    (trim out)))\n\n\n(defun prepare-git-repo (repo commit cache-dir)\n  (format t \"quick-patch: preparing ~a~%\" repo)\n  (let ((git-dir (catdir cache-dir \".git/\")))\n    (labels ((git (&rest args)\n               (apply #'list\n                        \"git\" \"--work-tree\" (namestring cache-dir)\n                        \"--git-dir\" (namestring git-dir)\n                        args))\n             (rev-parse (commit)\n               (run-program-with-errors (git\n                                         \"rev-parse\"\n                                         commit)))\n             (checkout ()\n               (let ((rev (rev-parse commit)))\n                 (format t \"For ~a, Checking out: ~a~%\" repo rev)\n                 (run-program-with-errors (git\n                                           \"checkout\"\n                                           \"-f\"\n                                           rev)))))\n      (cond\n        ((directory-exists-p git-dir)\n         (cond\n           ((equal commit (rev-parse \"HEAD\"))\n            ;; The most common situation, do nothing\n            (values))\n           (t\n            (run-program-with-errors (git \"remote\" \"set-url\"\n                                          \"origin\" repo))\n            (run-program-with-errors (git\n                                      \"fetch\"\n                                      \"origin\"\n                                      commit))\n\n            ;; At this point commit could be either something like\n            ;; \"master\", in which case we need to reroute this to\n            ;; origin/master, or it could be a commit hash?\n            (let ((commit (or\n                            (ignore-errors\n                             (when (= 40 (length commit))\n                               (rev-parse commit)))\n                            (ignore-errors\n                             (rev-parse (format nil \"origin/~a\" commit))))))\n             (run-program-with-errors (git \"checkout\" commit))))))\n        (t\n         (run-program-with-errors (list\n                                   \"git\" \"clone\"\n                                   repo\n                                   (namestring  cache-dir)\n                                   ;; Should this be configurable? In\n                                   ;; most cases we aren't editing the\n                                   ;; cloned repository directly, so\n                                   ;; it's okay to disable\n                                   ;; autocrlf. In particular, couple\n                                   ;; of common repositories will fail\n                                   ;; to compile under SBCL without\n                                   ;; autocrlf.\n                                   #+ (or windows win32 mswindows) #+ (or windows win32 mswindows)\n                                   \"--config\" \"core.autocrlf=false\"))\n         (checkout))))))\n\n(defun name-from-repo-name (repo-name)\n  (let ((pos (position #\\/ repo-name :from-end t)))\n    (subseq repo-name (+ 1 pos))))\n\n(defun checkout-all (cache-dir)\n  (let ((cache-dir (merge-pathnames cache-dir (uiop:getcwd))))\n    (setf *cache-dir* cache-dir)\n    (loop for (repo commit subdirs) in *externals*\n          do\n             (let* ((name (name-from-repo-name repo))\n                    (cache-dir (catdir *cache-dir* (format nil \"~a/\" name))))\n               (dolist (subdir subdirs)\n                 (assert subdir)\n                 (pushnew (catdir cache-dir subdir)\n                          asdf:*central-registry*\n                          :test 'equal))\n               (prepare-git-repo repo commit cache-dir)))))\n\n(defun prepare-externals (&rest args)\n  (apply #'checkout-all args))\n"
  },
  {
    "path": "src/quick-patch/quick-patch.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :quick-patch\n  :serial t\n  :author \"Arnold Noronha <arnold@jipr.io>\"\n  :license  \"Mozilla Public License 2.0\"\n  :version \"0.0.1\"\n  :description \"Easily override quicklisp projects without using git submodules\"\n  :depends-on ()\n  :components ((:file \"util\")\n               (:file \"impl\")\n               (:file \"all\")))\n\n(defsystem :quick-patch/tests\n  :serial t\n  :depends-on (:quick-patch\n               :cl-mock\n               :tmpdir\n               :fiveam-matchers\n               :str\n               :fiveam)\n  :components ((:file \"test-impl\")))\n"
  },
  {
    "path": "src/quick-patch/run-circleci.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(load \"~/quicklisp/setup.lisp\")\n\n(push #P \"./\" asdf:*central-registry*)\n\n(ql:quickload :quick-patch/tests)\n\n(unless (fiveam:run-all-tests)\n  (uiop:quit 1))\n\n(uiop:quit 0)\n"
  },
  {
    "path": "src/quick-patch/test-impl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :quick-patch/test-impl\n    (:use #:cl\n          #:fiveam\n          #:fiveam-matchers)\n  (:import-from #:quick-patch/impl\n                #:catdir\n                #:name-from-repo-name\n                #:run-program-with-errors\n                #:prepare-git-repo\n                #:checkout-all\n                #:*cache-dir*\n                #:*externals*\n                #:register)\n  (:import-from #:asdf\n                #:*central-registry*))\n(in-package :quick-patch/test-impl)\n\n(def-suite* :quick-patch/test-impl)\n\n(defun add-external-repo ()\n  (register \"https://github.com/m0cchi/cl-slack\"\n                     \"019ecb3\"))\n\n(test name-from-repo-name\n  (is (equal \"foo\" (name-from-repo-name \"https://github.com/xyz/foo\"))))\n\n(test catdir\n  (is (equal #P \"/foo/bar/\" (catdir \"/foo/\" \"bar/\"))))\n\n(test test-registry\n  (let ((*externals* nil))\n    (add-external-repo)\n    (is (equal *externals*\n               (list (list\n                      \"https://github.com/m0cchi/cl-slack\"\n                      \"019ecb3\"\n                      (list (make-pathname :directory `(:relative)))))))))\n\n(test load-externals\n  (cl-mock:with-mocks ()\n    (tmpdir:with-tmpdir (dir)\n      (let ((*cache-dir* nil)\n            (*externals* nil)\n            (*central-registry* *central-registry*))\n        (add-external-repo)\n        (let ((expected-dir (path:catdir dir \"cl-slack/\")))\n          (cl-mock:if-called 'prepare-git-repo\n                              (lambda (repo commit cache-dir)\n                                (is (equal repo \"https://github.com/m0cchi/cl-slack\"))\n                                (is (equal commit \"019ecb3\"))))\n          (checkout-all dir)\n          (assert-that (mapcar #'namestring *central-registry*)\n                       (has-item\n                        (matches-regex \".*cl-slack[/\\\\\\\\]$\"))))\n        (pass)))))\n\n\n(test prepare-git-repo-integration-test\n  (tmpdir:with-tmpdir (dir)\n    (let ((source (ensure-directories-exist (path:catdir dir \"source/\")))\n          (dest (path:catdir dir \"dest/\")))\n      (flet ((bash (x)\n               (run-program-with-errors (str:split \" \" x)  :directory source))\n             (bash-dest (x)\n               (run-program-with-errors (str:split \" \" x) :directory dest)\n        (bash \"git init .\")\n        (bash \"git config user.email foo@tdrhq.com\")\n        (bash \"git config user.name FooBar\")\n        (with-open-file (file.txt (path:catfile source file.txt)\n                                  :direction :output)\n          (write-string \"hello\" file.txt))\n        (bash \"git add file.txt\")\n        (bash \"git commit -a -m first-commit\")\n        (with-open-file (file.txt (path:catfile source file.txt)\n                                  :direction :output\n                                  :if-exists :supersede)\n          (write-string \"hello2\" file.txt))\n        (bash \"git commit -a -m second-commit\")\n        (let ((first-commit (bash \"git rev-parse HEAD^\"))\n              (second-commit (bash \"git rev-parse HEAD\")))\n          ;; let's use this repo to make our new clone\n          (finishes\n            (prepare-git-repo (namestring source)\n                              first-commit\n                              dest))\n\n        (is (equal first-commit (bash-dest \"git rev-parse HEAD\")))\n        (is (equal \"hello\" (str:trim (uiop:read-file-string (path:catfile dir \"dest/file.txt\")))))\n\n        ;; can I clone to the same directory again?\n        (finishes\n          (prepare-git-repo (namestring source)\n                            first-commit\n                            dest))\n\n        ;; can I switch the commit?\n        (finishes\n          (prepare-git-repo (namestring source)\n                            second-commit\n                            dest))\n\n        (is (equal \"hello2\" (str:trim (uiop:read-file-string (path:catfile dir \"dest/file.txt\"))))))))))))\n"
  },
  {
    "path": "src/quick-patch/util.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :quick-patch/util\n    (:use #:cl))\n(in-package :quick-patch/util)\n\n;;;; from cl-str\n\n(defvar *whitespaces* '(#\\Space #\\Newline #\\Backspace #\\Tab\n                        #\\Linefeed #\\Page #\\Return #\\Rubout))\n\n(defun trim (value)\n  (string-trim *whitespaces* value))\n\n;;;; end: from cl-str\n\n;;;; from cl-fad (with slight modifications)\n\n(defun catdir (x y)\n  (let ((x (pathname x))\n        (y (pathname y)))\n    (assert (eql :relative (car (pathname-directory y))))\n    (make-pathname\n     :directory (cons\n                 (car (pathname-directory x))\n                 (append (cdr (pathname-directory x)) (cdr (pathname-directory y))))\n     :defaults x)))\n\n(defun directory-exists-p (pathspec)\n  \"Checks whether the file named by the pathname designator PATHSPEC\nexists and if it is a directory.  Returns its truename if this is the\ncase, NIL otherwise.  The truename is returned in directory form as if\nby PATHNAME-AS-DIRECTORY.\"\n  #+:allegro\n  (and (excl:probe-directory pathspec)\n       (truename pathspec))\n  #+:lispworks\n  (and (lw:file-directory-p pathspec)\n       (truename pathspec))\n  #-(or :allegro :lispworks)\n  (let ((result (probe-file pathspec)))\n    (and result\n         (not (pathname-name result)))))\n\n;;;; end: from cl-fad\n"
  },
  {
    "path": "src/remark/all.lisp",
    "content": "(uiop:define-package :remark\n  (:use #:cl\n        #:remark/nodes\n        #:remark/render)\n  (:use-reexport :remark/markdown)\n  (:export #:defpage\n           #:find-node\n           #:defsection\n           #:locate-page\n           #:deftoplevel\n           #:render-table-of-contents\n           #:render-page\n           #:section-title\n           #:toplevel-prefix))\n"
  },
  {
    "path": "src/remark/js/remark.js",
    "content": "\n/*\n * Code for Remark\n */\n$(function () {\n    var url = $(\".doc-search\").find(\"input\").data(\"search\");\n    var timeout = null;\n    var lastQuery = null;\n\n    function undoSearch($this) {\n        $(\".actual-content\").show();\n        $(\".remark-outline\").css(\"visibility\", \"visible\");\n        $(\".search-results\").hide();\n    }\n\n    function update($this) {\n        let query = $this.val();\n\n        if (query === lastQuery) {\n            return;\n        }\n        lastQuery = query;\n\n        if (query === \"\") {\n            undoSearch($this);\n            return;\n        }\n\n        console.log(\"querying: \", url, \"lastQuery: \", lastQuery, \"query: \", query, $this);\n        $.ajax({\n            url: url,\n            method: \"POST\",\n            data: {\n                query: query,\n            },\n            success: (result) => {\n                let $results = $(\".search-results\");\n                console.log(\"results: \", $results);\n                $results.html(result)\n                $results.show();\n                $(\".actual-content\").hide();\n                $(\".remark-outline\").css(\"visibility\", \"hidden\");\n            }\n        });\n    }\n\n    let $input = $(\".doc-search input\");\n    $input.on(\"keyup\", () => {\n        if (timeout) {\n            clearTimeout(timeout);\n            timeout = null;\n        }\n        timeout = setTimeout(() => {\n            update($input);\n        }, 250);\n    });\n});\n"
  },
  {
    "path": "src/remark/markdown.lisp",
    "content": "(defpackage :remark/markdown\n  (:use #:cl\n        #:iterate)\n  (:shadow #:merge)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:md\n   #:header-id))\n(in-package :remark/markdown)\n\n(named-readtables:in-readtable markup:syntax)\n\n;; TODO: \\\\W is incorrect here\n\n(defparameter *scanner* (cl-ppcre:create-scanner \"\\\\n(\\\\W*\\\\n)+\" :multi-line-mode t))\n\n\n(defun header-tag-for (&key length)\n  \"By keeping header-tag-for as a separate function, we can\nprogrammatically change the depth at which a specific node might be.\"\n  (ecase length\n    (1 'h1)\n    (2 'h2)\n    (3 'h3)))\n\n(defun header-id (content)\n  (string-downcase\n   (str:replace-all \"'\" \"\"\n    (str:replace-all \" \" \"-\"\n                     (format nil \"~a\" content)))))\n\n(defun format-para (para)\n  (let* ((nodes (paragraph-nodes para))\n         (first-node (car nodes)))\n    (cond\n      ((and\n        (stringp first-node)\n        (str:starts-with-p \"#\" (str:trim-left first-node)))\n       (let* ((parts (str:split \" \" (str:trim-left first-node) :limit 2))\n              (h (car parts))\n              (content (cadr parts)))\n         (let ((h-type (cond\n                         ((or\n                           (equal \"#\" h)\n                           (equal \"##\" h)\n                           (equal \"###\" h))\n                          (header-tag-for :length (length h)))\n                         (t\n                          (error \"Invalid header directive: `~a`\" h)))))\n           (markup:make-xml-tag h-type\n                                :attributes (list\n                                             (cons \"id\" (header-id content)))\n                                :children (list*\n                                           (str:trim content)\n                                           (cdr nodes))))))\n      ((stringp first-node)\n       <p>,(progn (str:trim-left first-node)),@(cdr nodes)</p>)\n      (t\n       <p>,@(progn nodes)</p>))))\n\n\n(defclass paragraph ()\n  ((nodes :initarg :nodes\n          :initform nil\n          :accessor %paragraph-nodes\n          :documentation \"The reverse of the nodes in the paragraph (note that the\nPARAGRAPH-NODES accessor reverses this finally).\")))\n\n\n(defun paragraph-nodes (paragraph)\n  (reverse (%paragraph-nodes paragraph)))\n\n(defun push-to-para (node paragraph)\n  (push node (%paragraph-nodes paragraph)))\n\n(defun paragraphp (x)\n  (typep x 'paragraph))\n\n(defun merge-adjacent-strings (list)\n  (declare (optimize (debug 3)) )\n  (labels ((merge (current-para list ret)\n             \"Processes a flat list of mixed content (strings and XML tags) and groups them into\n              logical paragraphs. Strings are split on paragraph boundaries (double\n              newlines) and merged with adjacent inline elements. Block-level\n              elements are treated as separate paragraphs. Returns a list of\n              paragraph objects that can be formatted appropriately.\n\n              CURRENT-PARA is the current paragraph being processed.\n              LIST is the list of elements remain to be processed\n              RET is the reverse result being built\"\n             (declare (optimize (Debug 3)))\n             (cond\n               ((not list)\n                (nreverse (list* current-para ret)))\n               (t\n                (destructuring-bind (next . rest) list\n                  (cond\n                    ((stringp next)\n                     (destructuring-bind (open &optional remaining-para)\n                         (cl-ppcre:split *scanner* next :limit 2)\n                       (let ((current-para (make-instance 'paragraph\n                                                           :nodes (list*\n                                                                   open\n                                                                   (%paragraph-nodes current-para)))))\n                         (cond\n                           (remaining-para\n                            (merge\n                             (make-instance 'paragraph)\n                             (list* remaining-para  rest)\n                             (list* current-para ret)))\n                           (t\n                            (merge\n                             current-para\n                             rest\n                             ret))))\n                       ))\n                    ((typep next 'markup:abstract-xml-tag)\n                     (cond\n                       ((member (markup:xml-tag-name next) '(:b :em :a :tt :span))\n                        (merge\n                         (para*\n                          current-para\n                          next)\n                           rest\n                         ret))\n                       (t\n                        (merge\n                         (make-instance 'paragraph)\n                         rest\n                         (list* (make-instance 'paragraph :nodes (list next))\n                                (build-para-list*\n                                 current-para\n                                 ret))))))\n                    (t\n                     (error \"unknown type: ~a\" (type-of next)))))))))\n    (merge\n     (make-instance 'paragraph)\n     list\n     nil)))\n\n(defun build-para-list* (current-para list)\n  \"Builds the result list, adding current-para to ret if it has content\"\n  (if (%paragraph-nodes current-para)\n      (list* current-para list)\n      list))\n\n(defun para* (current-para next)\n  \"Combines the current paragraph with the next node\"\n  (make-instance 'paragraph\n                 :nodes (list*\n                         next\n                         (%paragraph-nodes current-para))))\n\n(markup:deftag md (children)\n  (markup:make-merge-tag\n   (mapcar #'format-para (merge-adjacent-strings children))))\n"
  },
  {
    "path": "src/remark/nodes.lisp",
    "content": "(defpackage :remark/nodes\n  (:use #:cl)\n  (:import-from #:util/html2text\n                #:html2text)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:defpage\n   #:find-node\n   #:defsection\n   #:deftoplevel\n   #:toplevel-prefix\n   #:section-children\n   #:section-title\n   #:section\n   #:toplevel\n   #:section-id\n   #:locate-page\n   #:page-generator\n   #:*toplevel*\n   #:toplevel-landing-page\n   #:locate-page-by-alias\n   #:page-name))\n(in-package :remark/nodes)\n\n(defvar *nodes* (make-hash-table))\n\n(defclass node ()\n  ((staging? :initarg :staging?\n             :initform nil\n             :reader only-show-on-staging?)\n   (default :initform nil\n            :initarg :default\n            :reader page-default-p)))\n\n(defclass page (node)\n  ((fn :initarg :fn\n     :reader page-generator)\n   (name :initarg :name\n         :reader page-name)\n   (title :initarg :title\n          :initform \"Untitled page\"\n          :reader section-title)\n   (aliases :initarg :aliases\n            :initform nil\n            :reader page-aliases)\n   (text :initform nil\n         :accessor %page-text\n         :documentation \"A cached version of the text of the page\")))\n\n(defclass section (node)\n  ((title :initarg :title\n          :initform \"Untitled\"\n          :reader section-title)\n   (children :initarg :children\n             :reader section-children)))\n\n(defmethod section-id (page-or-section)\n  (str:replace-all \" \" \"-\"\n   (string-downcase (section-title page-or-section))))\n\n(defclass toplevel (section)\n  ((prefix :initarg :prefix\n           :reader toplevel-prefix)\n   (landing-page :initarg :landing-page\n                 :reader toplevel-landing-page)))\n\n(defvar *toplevel* nil\n  \"The top level context. Bound when calling most of the methods\n  related to remarks.\")\n\n(defmacro defpage (name args &body body)\n  `(setf\n    (gethash ',name *nodes*)\n    (make-instance 'page\n                   :name ',name\n                   :fn (lambda () ,@body)\n                   ,@args)))\n\n(defmethod initialize-instance :after ((page page) &key aliases &allow-other-keys)\n  (assert (listp aliases))\n  (loop for alias in aliases\n        do (assert (stringp alias))))\n\n\n(defmacro defsection (name args &body body)\n  `(setf\n    (gethash ',name *nodes*)\n    (make-instance 'section\n                    :children\n                    ',body\n                     ,@args)))\n\n(defmacro deftoplevel (name (&rest args) &body body)\n  `(defparameter ,name\n     (make-instance 'toplevel\n                     :children ',body\n                     ,@args)))\n\n\n(defun find-node (name)\n  (gethash name *nodes*))\n\n(defmethod locate-page ((section section) path)\n  (assert path)\n  (destructuring-bind (name &optional rest) (str:split \"/\" path :limit 2)\n    (cond\n      ((and (str:emptyp name) rest)\n       (locate-page section rest))\n      ((str:emptyp name)\n       ;; find a default page\n       (loop for child-name in (section-children section)\n             for node = (find-node child-name)\n             if (page-default-p node) do\n               (return (locate-page node \"\"))\n             finally\n              (return section)))\n      (t\n       (loop for child-name in (section-children section)\n             for child = (find-node child-name)\n             if (string= (section-id child) name)\n               return\n               (if rest\n                   (locate-page child rest)\n                   (values child section)))))))\n\n(defmethod locate-page ((section page) path)\n  (when (str:emptyp path)\n    section))\n\n(defmethod locate-page ((toplevel toplevel) path)\n  (cond\n    ((str:starts-with-p (toplevel-prefix toplevel) path)\n     (call-next-method toplevel\n                       (str:substring (length (toplevel-prefix toplevel)) nil\n                                      path)))\n    (t\n     (call-next-method))))\n\n(defmethod locate-page-by-alias ((node node) path)\n  (loop for child-name in (section-children node)\n        for child = (find-node child-name)\n        for page = (locate-page-by-alias child path)\n        if page\n          return page))\n\n(defmethod locate-page-by-alias ((page page) path)\n  (when (str:s-member (page-aliases page) path)\n    page))\n\n(defmethod page-text ((page page))\n  (util/misc:or-setf\n   (%page-text page)\n   (str:downcase\n    (str:join\n     \" \"\n     (loop for line in (str:lines\n                        (html2text\n                         (funcall (page-generator page))))\n           collect\n           (cl-ppcre:regex-replace-all \"][(].*[)]\"\n                                       (cl-ppcre:regex-replace-all \"^[!]\\\\[\\\\][(].*[)]\"\n                                                                   line\n                                                                   \"\")\n                                       \"\"))))))\n\n\n"
  },
  {
    "path": "src/remark/remark.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :remark\n  :serial t\n  :depends-on (:markup\n               :str\n               :nibble\n               :util/throttler\n               :util\n               :util/events\n               :iterate)\n  :components ((:file \"nodes\")\n               (:file \"markdown\")\n               (:file \"search\")\n               (:file \"render\")\n               (:file \"all\")))\n\n(defsystem :remark/js\n  :serial t\n  :class \"build-utils:js-system\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:jquery-js)\n  :components ((:module \"js\"\n                :components ((\"build-utils:js-file\" \"remark\")))))\n\n(defsystem :remark/tests\n  :serial t\n  :depends-on (:remark\n               :fiveam\n               :fiveam-matchers\n               :util/fiveam)\n  :components ((:file \"test-nodes\")\n               (:file \"test-search\")\n               (:file \"test-markdown\")))\n"
  },
  {
    "path": "src/remark/render.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :remark/render\n  (:use #:cl)\n  (:import-from #:remark/nodes\n                #:page-default-p\n                #:page-name\n                #:locate-page-by-alias\n                #:only-show-on-staging?\n                #:toplevel-landing-page\n                #:*toplevel*\n                #:page-generator\n                #:locate-page\n                #:section-id\n                #:toplevel-prefix\n                #:find-node\n                #:section-title\n                #:toplevel\n                #:section\n                #:section-children\n                #:page-text\n                #:page-aliases\n                #:page)\n  (:import-from #:remark/markdown\n                #:header-id)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:remark/search\n                #:search-remarks)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:render-table-of-contents\n   #:render-page\n   #:generate-documentation-context))\n(in-package :remark/render)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(defmethod link-to (page &key (toplevel *toplevel*))\n  (labels ((%find (node prefix)\n             (when (eql node page)\n               (return-from link-to prefix))\n             (when (typep node 'section)\n              (loop for child-name in (section-children node)\n                    for child = (find-node child-name)\n                    do\n                       (%find child (format nil \"~a/~a\" prefix (section-id child)))))))\n    (%find toplevel (toplevel-prefix toplevel))\n    :not-found))\n\n(defun render-page-name (page)\n  (let ((*package* nil)) (format nil \"~s\" (page-name page))))\n\n(defmethod render-table-of-contents ((section section) prefix current-page\n                                     &key staging?)\n  <ul>\n    ,@ (loop for child-name in (section-children section)\n             for child = (find-node child-name)\n             for activep = (eql child current-page)\n             if (or staging?\n                    (not (only-show-on-staging? child)))\n             collect\n             <li>\n               ,(cond\n                  ((typep child 'section)\n                   <markup:merge-tag>\n                     <span class= \"section-title\">,(section-title child)</span>\n                     ,(render-table-of-contents child (format nil \"~a/~a\" prefix (section-id child)) current-page)\n                   </markup:merge-tag>)\n                  (t\n                   <span class= \"pagep-title\" data-page-name= (render-page-name child) > <a href= (format nil \"~a/~a\" prefix (section-id  child)) class= (when activep \"active\") >,(section-title child)</a></span>\n))\n             </li>)\n  </ul>)\n\n\n(defmethod render-outline (page)\n  <div class= \"remark-outline\">\n    <div class= \"content\">\n      <h4>Outline</h4>\n      <ul>\n        ,@(let ((ret))\n            (mquery:with-document (page)\n              (loop for item in (mquery:$ \"h2\")\n                    for title = (or\n                                 (mquery:attr item \"title\")\n                                 (car (markup:xml-tag-children item)))\n                    do\n                       (push <li><a href= (format nil \"#~a\" (header-id title)) >,(progn title)</a> </li> ret)))\n            (nreverse ret))\n      </ul>\n    </div>\n  </div>)\n\n(defmethod page-generator ((section section))\n  (lambda ()\n    <div>\n      <h3>In this section</h3>\n\n      <ul>\n        ,@ (loop for page in (section-children section)\n                 collect\n                 <li><a href= (link-to (find-node page)) >,(section-title (find-node page))</a></li>)\n      </ul>\n    </div>))\n\n(defun %search (toplevel query)\n  (let ((results (search-remarks toplevel query)))\n    (cond\n      ((not results)\n       \"No search results. Contact us at support@screenshotbot.io for help!\")\n      (t\n       <div>\n         <h3>Search Results</h3>\n           ,@ (loop for result in results\n                    collect\n                    <div>\n                      <a href= (link-to result :toplevel toplevel) >,(section-title result)</a>\n                    </div>)\n       </div>))))\n\n(defmethod render-page ((toplevel toplevel) script &key staging?)\n  (let ((*toplevel* toplevel))\n   (multiple-value-bind (page section)\n       (locate-page toplevel script)\n\n     (unless page\n       (let ((page (locate-page-by-alias toplevel script)))\n         (when page\n           (hex:safe-redirect\n            (link-to page :toplevel toplevel))))\n       (push-event :docs.404 :script script))\n     (let* ((page-content (when page\n                            (funcall (page-generator page)))))\n       <div class= \"remarks\" >\n         <!-- Mobile Navigation Offcanvas -->\n         <div class=\"offcanvas offcanvas-start\" tabindex=\"-1\" id=\"docsOffcanvas\" aria-labelledby=\"docsOffcanvasLabel\">\n           <div class=\"offcanvas-header\">\n             <h5 class=\"offcanvas-title\" id=\"docsOffcanvasLabel\">\n               ,(cond\n                  (section (section-title section))\n                  (t \"Documentation\"))\n             </h5>\n             <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n           </div>\n           <div class=\"offcanvas-body\">\n             ,(render-table-of-contents toplevel (toplevel-prefix toplevel) page\n                                        :staging? staging?)\n           </div>\n         </div>\n\n         <div class= \"remark-toc\">\n           <div class= \"remark-toc-dropdown\">\n             <button type= \"button\" data-bs-toggle= \"collapse\"\n                     data-bs-target= \".remark-toc .content\"\n                     class= \"navbar-toggler\" >\n               <span class= \"navbar-toggler-icon\" />\n             </button>\n             ,(cond\n                (section (section-title section))\n                (t \"Documentation\"))\n           </div>\n           <div class= \"content\">\n             ,(render-table-of-contents toplevel (toplevel-prefix toplevel) page\n                                        :staging? staging?)\n           </div>\n         </div>\n\n       ,(when page\n          <div class= \"remark-body\">\n            <div class= \"search-results \" style= \"display:none\" >\n            </div>\n            <div class= \"actual-content\">\n              ,(progn page-content)\n            </div>\n          </div>)\n\n       ,(when page\n          (render-outline page-content))\n       </div>))))\n\n(defun generate-documentation-context (toplevel-node &key (max-depth 3) (current-depth 0))\n  \"Generates a hierarchical documentation context from a toplevel node,\nsuitable for passing to Claude for queries. Returns a formatted string\ncontaining the structure and content of all documentation pages.\"\n  (let ((context (make-string-output-stream))\n        (*toplevel* toplevel-node))\n    (labels ((write-section (node depth prefix)\n               (when (> depth max-depth)\n                 (return-from write-section))\n               (let ((indent (make-string (* depth 2) :initial-element #\\Space)))\n                 (format context \"~a~a ~a~%\" \n                         indent\n                         (make-string (max 1 (- 4 depth)) :initial-element #\\>)\n                         (section-title node))\n                 (when (typep node 'page)\n                   (let ((page-content (funcall (page-generator node))))\n                     (when page-content\n                       (format context \"~a~a~2%\" indent \n                               (util/html2text:html2text \n                                (markup:write-html page-content)))))\n                   (when (page-aliases node)\n                     (format context \"~aAliases: ~{~a~^, ~}~2%\" indent (page-aliases node))))\n                 (when (typep node 'section)\n                   (loop for child-name in (section-children node)\n                         for child = (find-node child-name)\n                         when child do\n                           (write-section child \n                                        (1+ depth)\n                                        (if (typep node 'toplevel)\n                                            (toplevel-prefix node)\n                                            prefix)))))))\n      (write-section toplevel-node current-depth (toplevel-prefix toplevel-node))\n      (get-output-stream-string context))))\n"
  },
  {
    "path": "src/remark/search.lisp",
    "content": "(defpackage :remark/search\n  (:use #:cl)\n  (:import-from #:remark/nodes\n                #:page-text\n                #:find-node\n                #:section-children\n                #:section\n                #:page)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:ip-throttler)\n  (:export\n   #:search-remarks))\n(in-package :remark/search)\n\n(defparameter *throttler* (make-instance 'ip-throttler\n                                         :tokens 3600\n                                         :period 3600))\n\n(defmethod search-remarks ((page page) query)\n  (throttle! *throttler*)\n  (push-event :search-docs :query query)\n  (when (str:containsp (str:downcase query) (page-text page))\n    ;;(log:info \"Page text is: ~a\" (page-text page))\n    (list page)))\n\n(defmethod search-remarks ((section section) query)\n  (loop for child in (section-children section)\n        appending (search-remarks (find-node child) query)))\n"
  },
  {
    "path": "src/remark/test-markdown.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :remark/test-markdown\n  (:use #:cl\n        #:fiveam\n        #:remark)\n  (:import-from #:markup\n                #:xml-tag-children)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :remark/test-markdown)\n\n(def-suite* :remark/test-markdown :in :remark)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun markup= (x y)\n  (string=\n   (markup:write-html x)\n   (markup:write-html y)))\n\n(test markdown\n  (is-true <md>foobar</md>))\n\n(test real-content\n  (let ((content\n          <md>\n            Hello world\n\n            This is a test. what do you know. sdfasdf sdf dfsd fsdf sdf dsf\n            sdf sdf sdf sd fsd f sdf dsf sdf.\n          </md>))\n    (is (typep\n         content 'markup:xml-merge-tag))\n    (let ((children (markup:xml-merge-tag-children content)))\n      (is (equal 2 (length children)))\n      (is (markup= <p>Hello world</p> (car children))))))\n\n\n(test headers\n  (let ((content\n          <md>\n            # Hello world\n\n            This is a test. what do you know. sdfasdf sdf dfsd fsdf sdf dsf\n            sdf sdf sdf sd fsd f sdf dsf sdf.\n          </md>))\n    (is (typep\n         content 'markup:xml-merge-tag))\n    (let ((children (markup:xml-merge-tag-children content)))\n      (is (equal 2 (length children)))\n      (is (markup= <h1 id= \"hello-world\" >Hello world</h1> (car children))))))\n\n(test headers-level-3\n  (let ((content\n          <md>\n            ## Hello world\n\n            This is a test. what do you know. sdfasdf sdf dfsd fsdf sdf dsf\n            sdf sdf sdf sd fsd f sdf dsf sdf.\n          </md>))\n    (is (typep\n         content 'markup:xml-merge-tag))\n    (let ((children (markup:xml-merge-tag-children content)))\n      (is (equal 2 (length children)))\n      (is (markup= <h2 id= \"hello-world\" >Hello world</h2> (car children))))))\n\n(markup:deftag code-block (children)\n  <div>,@ (progn children)</div>)\n\n(test paragraph-before-code-block\n  (let ((content\n          <md>\n            hello\n            \n            Once you have this you would still update your <tt>.gitignore</tt>:<code-block>\n  **/__Snapshots__/**/*.png\n            </code-block>\n          </md>))\n    (is (typep\n         content 'markup:xml-merge-tag))\n    (let ((children (markup:xml-merge-tag-children content)))\n      (assert-that children\n                   ;; There's an additional empty <p> at the bottom for now\n                   (has-length 4))\n      (is (markup= <p>Once you have this you would still update your <tt>.gitignore</tt>:</p> (second children))))))\n\n\n"
  },
  {
    "path": "src/remark/test-nodes.lisp",
    "content": "(defpackage :remark/test-nodes\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:remark/nodes\n                #:*nodes*)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :remark/test-nodes)\n\n(def-suite :remark)\n(def-suite* :remark/test-nodes :in :remark)\n\n(def-fixture state ()\n  (let ((*nodes* (make-hash-table)))\n    (&body)))\n\n(test preconditions\n  (with-fixture state ()\n    (unwind-protect\n         (pass)\n      (setf (symbol-value '*dummy-docs*)  nil))))\n\n(test defpage\n  (with-fixture state ()\n    (remark:defpage foo ()\n      <p>hello world</p>)\n    (is-true (gethash 'foo *nodes*))\n    (is-true (remark:find-node 'foo))))\n\n\n(test defsection\n  (with-fixture state ()\n    (remark:defpage foo ()\n      <p>hello world</p>)\n    (remark:defsection bar ()\n      foo)\n    (is-true (remark:find-node 'bar))))\n\n(test deftoplevel\n  (with-fixture state ()\n    (remark:deftoplevel *dummy-docs* ()\n      foo)\n    (is-true *dummy-docs*)))\n"
  },
  {
    "path": "src/remark/test-search.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :remark/test-search\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:remark/nodes\n                #:find-node\n                #:defpage\n                #:defsection\n                #:deftoplevel)\n  (:import-from #:remark/search\n                #:search-remarks)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that))\n(in-package :remark/test-search)\n\n(util/fiveam:def-suite)\n\n(deftoplevel *toplevel* ()\n  one)\n\n(defsection one ()\n  my-page)\n\n(defpage my-page ()\n  \"foo bar\")\n\n(test test-search-has-path ()\n  (assert-that (search-remarks *toplevel* \"foo\")\n               (has-item (find-node 'my-page))))\n"
  },
  {
    "path": "src/scheduled-jobs/bindings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :scheduled-jobs/bindings\n  (:use #:cl)\n  (:import-from #:local-time\n                #:unix-to-timestamp\n                #:timestamp-to-unix)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:local-nicknames (#:a #:alexandria)\n                    #-lispworks\n                    (#:fli #:util/fake-fli))\n  (:import-from #:util/native-module\n                #:load-module\n                #:define-embedded-module\n                #:make-native-module)\n  (:export\n   #:cron-parse-expr\n   #:cron-next\n   #:invalid-cron-expr))\n(in-package :scheduled-jobs/bindings)\n\n(defvar *native-module* (make-native-module 'ccronexpr\n                                            :scheduled-jobs\n                                            \"ccronexpr\"))\n\n(define-embedded-module *native-module*)\n\n(fli:define-c-struct fli-cron-expr\n    (seconds (:c-array :uint8 8))\n  (minutes (:c-array :uint8 8))\n  (hours (:c-array :uint8 3))\n  (days-of-week (:c-array :uint8 1))\n  (days-of-month (:c-array :uint8 4))\n  (months (:c-array :uint8 2)))\n\n(fli:define-foreign-function (%cron-next \"cron_next\")\n  ((expr (:pointer fli-cron-expr))\n   (date :time-t))\n  :result-type :time-t)\n\n(fli:define-foreign-function (%cron-parse-expr \"cron_parse_expr\")\n    ((expr (:reference-pass :ef-mb-string))\n     (target (:pointer fli-cron-expr))\n     (err (:pointer (:pointer :char))))\n  :result-type :void)\n\n(define-condition invalid-cron-expr (error)\n  ((message :initarg :message\n            :reader invalid-cron-expr-message)))\n\n(defmethod print-object ((e invalid-cron-expr) out)\n  (format out \"#<CRON ~a>\" (invalid-cron-expr-message e)))\n\n(defclass cron-expr ()\n  ((fli-cron-expr :initarg :fli-cron-expr\n                  :reader fli-cron-expr)))\n\n\n(defun cron-parse-expr (expr)\n  (load-module *native-module*)\n  (fli:with-dynamic-foreign-objects ((err (:pointer :char) :fill 0))\n   (let ((fli-cron-expr (fli:malloc :type 'fli-cron-expr)))\n     (%cron-parse-expr\n      expr\n      fli-cron-expr\n      err)\n     (unless (fli:null-pointer-p (fli:dereference err))\n       (error 'invalid-cron-expr :message\n               (fli:convert-from-foreign-string (fli:dereference err))))\n     (let ((ret (make-instance 'cron-expr\n                                :fli-cron-expr fli-cron-expr)))\n\n       (trivial-garbage:finalize ret\n                                 (lambda ()\n                                   (fli:free fli-cron-expr)))\n       ret))))\n\n\n;;;;;;;;;;;;;;;;;;;;;;\n;; https://lisptips.com/post/11649360174/the-common-lisp-and-unix-epochs\n(defvar *unix-epoch-difference*\n  (encode-universal-time 0 0 0 1 1 1970 0))\n\n(defun universal-to-unix-time (universal-time)\n  (- universal-time *unix-epoch-difference*))\n\n(defun unix-to-universal-time (unix-time)\n  (+ unix-time *unix-epoch-difference*))\n\n(defun get-unix-time ()\n  (universal-to-unix-time (get-universal-time)))\n;;;;;;;;;;;;;;;;;;;;;;;\n\n(defmethod cron-next ((self cron-expr) &key (now (get-universal-time))\n                                         (timezone 0))\n  (let ((offset (* timezone 3600)))\n    (- (unix-to-universal-time\n        (%cron-next\n         (fli-cron-expr self)\n         (+ (universal-to-unix-time now) offset)))\n       offset)))\n\n(def-health-check verify-ccronexpr-is-loaded ()\n  (cron-parse-expr \"*/3 * * * * *\"))\n"
  },
  {
    "path": "src/scheduled-jobs/ccronexpr.c",
    "content": "/*\n * Copyright 2015, alex at staticlibs.net\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * File:   ccronexpr.c\n * Author: alex\n *\n * Created on February 24, 2015, 9:35 AM\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <string.h>\n#include <math.h>\n\n#include \"ccronexpr.h\"\n\n#define CRON_MAX_SECONDS 60\n#define CRON_MAX_MINUTES 60\n#define CRON_MAX_HOURS 24\n#define CRON_MAX_DAYS_OF_WEEK 8\n#define CRON_MAX_DAYS_OF_MONTH 32\n#define CRON_MAX_MONTHS 12\n#define CRON_MAX_YEARS_DIFF 4\n\n#define CRON_CF_SECOND 0\n#define CRON_CF_MINUTE 1\n#define CRON_CF_HOUR_OF_DAY 2\n#define CRON_CF_DAY_OF_WEEK 3\n#define CRON_CF_DAY_OF_MONTH 4\n#define CRON_CF_MONTH 5\n#define CRON_CF_YEAR 6\n\n#define CRON_CF_ARR_LEN 7\n\n#define CRON_INVALID_INSTANT ((time_t) -1)\n\nstatic const char* const DAYS_ARR[] = { \"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\" };\n#define CRON_DAYS_ARR_LEN 7\nstatic const char* const MONTHS_ARR[] = { \"FOO\", \"JAN\", \"FEB\", \"MAR\", \"APR\", \"MAY\", \"JUN\", \"JUL\", \"AUG\", \"SEP\", \"OCT\", \"NOV\", \"DEC\" };\n#define CRON_MONTHS_ARR_LEN 13\n\n#define CRON_MAX_STR_LEN_TO_SPLIT 256\n#define CRON_MAX_NUM_TO_SRING 1000000000\n/* computes number of digits in decimal number */\n#define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \\\n                                (abs(num) < 100 ? 2 : \\\n                                (abs(num) < 1000 ? 3 : \\\n                                (abs(num) < 10000 ? 4 : \\\n                                (abs(num) < 100000 ? 5 : \\\n                                (abs(num) < 1000000 ? 6 : \\\n                                (abs(num) < 10000000 ? 7 : \\\n                                (abs(num) < 100000000 ? 8 : \\\n                                (abs(num) < 1000000000 ? 9 : 10)))))))))\n\n#ifndef CRON_TEST_MALLOC\n#define cron_malloc(x) malloc(x);\n#define cron_free(x) free(x);\n#else /* CRON_TEST_MALLOC */\nvoid* cron_malloc(size_t n);\nvoid cron_free(void* p);\n#endif /* CRON_TEST_MALLOC */\n\n/**\n * Time functions from standard library.\n * This part defines: cron_mktime: create time_t from tm\n *                    cron_time: create tm from time_t\n */\n\n/* forward declarations for platforms that may need them */\n/* can be hidden in time.h */\n#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) && !defined(TARGET_LIKE_MBED)\nstruct tm *gmtime_r(const time_t *timep, struct tm *result);\ntime_t timegm(struct tm* __tp);\nstruct tm *localtime_r(const time_t *timep, struct tm *result);\n#endif /* PLEASE CHECK _WIN32 AND ANDROID NEEDS FOR THESE DECLARATIONS */\n#ifdef __MINGW32__\n/* To avoid warning when building with mingw */\ntime_t _mkgmtime(struct tm* tm);\n#endif /* __MINGW32__ */\n\n/* function definitions */\ntime_t cron_mktime_gm(struct tm* tm) {\n#if defined(_WIN32)\n/* http://stackoverflow.com/a/22557778 */\n    return _mkgmtime(tm);\n#elif defined(__AVR__)\n/* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */\n    return mk_gmtime(tm);\n#elif defined(ESP8266) || defined(ESP_PLATFORM) || defined(TARGET_LIKE_MBED)\n    /* https://linux.die.net/man/3/timegm */\n    /* http://www.catb.org/esr/time-programming/ */\n    /* portable version of timegm() */\n    time_t ret;\n    char *tz;\n    tz = getenv(\"TZ\");\n    if (tz)\n        tz = strdup(tz);\n    setenv(\"TZ\", \"UTC+0\", 1);\n    tzset();\n    ret = mktime(tm);\n    if (tz) {\n        setenv(\"TZ\", tz, 1);\n        free(tz);\n    } else\n        unsetenv(\"TZ\");\n    tzset();\n    return ret;\n#elif defined(ANDROID)\n    /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */\n    static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1));\n    static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1));\n    time64_t result = timegm64(tm);\n    if (result < kTimeMin || result > kTimeMax) return -1;\n    return result;\n#else\n    return timegm(tm);\n#endif\n}\n\nstruct tm* cron_time_gm(time_t* date, struct tm* out) {\n#if defined(__MINGW32__)\n    (void)(out); /* To avoid unused warning */\n    return gmtime(date);\n#elif defined(_WIN32)\n    errno_t err = gmtime_s(out, date);\n    return 0 == err ? out : NULL;\n#elif defined(__AVR__)\n    /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */\n    gmtime_r(date, out);\n    return out;\n#else\n    return gmtime_r(date, out);\n#endif\n}\n\ntime_t cron_mktime_local(struct tm* tm) {\n    tm->tm_isdst = -1;\n    return mktime(tm);\n}\n\nstruct tm* cron_time_local(time_t* date, struct tm* out) {\n#if defined(_WIN32)\n    errno_t err = localtime_s(out, date);\n    return 0 == err ? out : NULL;\n#elif defined(__AVR__)\n    /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */\n    localtime_r(date, out);\n    return out;\n#else\n    return localtime_r(date, out);\n#endif\n}\n\n/* Defining 'cron_' time functions to use use UTC (default) or local time */\n#ifndef CRON_USE_LOCAL_TIME\ntime_t cron_mktime(struct tm* tm) {\n    return cron_mktime_gm(tm);\n}\n\nstruct tm* cron_time(time_t* date, struct tm* out) {\n    return cron_time_gm(date, out);\n}\n\n#else /* CRON_USE_LOCAL_TIME */\ntime_t cron_mktime(struct tm* tm) {\n    return cron_mktime_local(tm);\n}\n\nstruct tm* cron_time(time_t* date, struct tm* out) {\n    return cron_time_local(date, out);\n}\n\n#endif /* CRON_USE_LOCAL_TIME */\n\n/**\n * Functions.\n */\n\nvoid cron_set_bit(uint8_t* rbyte, int idx) {\n    uint8_t j = (uint8_t) (idx / 8);\n    uint8_t k = (uint8_t) (idx % 8);\n\n    rbyte[j] |= (1 << k);\n}\n\nvoid cron_del_bit(uint8_t* rbyte, int idx) {\n    uint8_t j = (uint8_t) (idx / 8);\n    uint8_t k = (uint8_t) (idx % 8);\n\n    rbyte[j] &= ~(1 << k);\n}\n\nuint8_t cron_get_bit(uint8_t* rbyte, int idx) {\n    uint8_t j = (uint8_t) (idx / 8);\n    uint8_t k = (uint8_t) (idx % 8);\n\n    if (rbyte[j] & (1 << k)) {\n        return 1;\n    } else {\n        return 0;\n    }\n}\n\nstatic void free_splitted(char** splitted, size_t len) {\n    size_t i;\n    if (!splitted) return;\n    for (i = 0; i < len; i++) {\n        if (splitted[i]) {\n            cron_free(splitted[i]);\n        }\n    }\n    cron_free(splitted);\n}\n\nstatic char* strdupl(const char* str, size_t len) {\n    if (!str) return NULL;\n    char* res = (char*) cron_malloc(len + 1);\n    if (!res) return NULL;\n    memset(res, 0, len + 1);\n    memcpy(res, str, len);\n    return res;\n}\n\nstatic unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) {\n    unsigned int i;\n    if (!bits) {\n        *notfound = 1;\n        return 0;\n    }\n    for (i = from_index; i < max; i++) {\n        if (cron_get_bit(bits, i)) return i;\n    }\n    *notfound = 1;\n    return 0;\n}\n\nstatic void push_to_fields_arr(int* arr, int fi) {\n    int i;\n    if (!arr || -1 == fi) {\n        return;\n    }\n    for (i = 0; i < CRON_CF_ARR_LEN; i++) {\n        if (arr[i] == fi) return;\n    }\n    for (i = 0; i < CRON_CF_ARR_LEN; i++) {\n        if (-1 == arr[i]) {\n            arr[i] = fi;\n            return;\n        }\n    }\n}\n\nstatic int add_to_field(struct tm* calendar, int field, int val) {\n    if (!calendar || -1 == field) {\n        return 1;\n    }\n    switch (field) {\n    case CRON_CF_SECOND:\n        calendar->tm_sec = calendar->tm_sec + val;\n        break;\n    case CRON_CF_MINUTE:\n        calendar->tm_min = calendar->tm_min + val;\n        break;\n    case CRON_CF_HOUR_OF_DAY:\n        calendar->tm_hour = calendar->tm_hour + val;\n        break;\n    case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */\n    case CRON_CF_DAY_OF_MONTH:\n        calendar->tm_mday = calendar->tm_mday + val;\n        break;\n    case CRON_CF_MONTH:\n        calendar->tm_mon = calendar->tm_mon + val;\n        break;\n    case CRON_CF_YEAR:\n        calendar->tm_year = calendar->tm_year + val;\n        break;\n    default:\n        return 1; /* unknown field */\n    }\n    time_t res = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == res) {\n        return 1;\n    }\n    return 0;\n}\n\n/**\n * Reset the calendar setting all the fields provided to zero.\n */\nstatic int reset_min(struct tm* calendar, int field) {\n    if (!calendar || -1 == field) {\n        return 1;\n    }\n    switch (field) {\n    case CRON_CF_SECOND:\n        calendar->tm_sec = 0;\n        break;\n    case CRON_CF_MINUTE:\n        calendar->tm_min = 0;\n        break;\n    case CRON_CF_HOUR_OF_DAY:\n        calendar->tm_hour = 0;\n        break;\n    case CRON_CF_DAY_OF_WEEK:\n        calendar->tm_wday = 0;\n        break;\n    case CRON_CF_DAY_OF_MONTH:\n        calendar->tm_mday = 1;\n        break;\n    case CRON_CF_MONTH:\n        calendar->tm_mon = 0;\n        break;\n    case CRON_CF_YEAR:\n        calendar->tm_year = 0;\n        break;\n    default:\n        return 1; /* unknown field */\n    }\n    time_t res = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == res) {\n        return 1;\n    }\n    return 0;\n}\n\nstatic int reset_all_min(struct tm* calendar, int* fields) {\n    int i;\n    int res = 0;\n    if (!calendar || !fields) {\n        return 1;\n    }\n    for (i = 0; i < CRON_CF_ARR_LEN; i++) {\n        if (-1 != fields[i]) {\n            res = reset_min(calendar, fields[i]);\n            if (0 != res) return res;\n        }\n    }\n    return 0;\n}\n\nstatic int set_field(struct tm* calendar, int field, int val) {\n    if (!calendar || -1 == field) {\n        return 1;\n    }\n    switch (field) {\n    case CRON_CF_SECOND:\n        calendar->tm_sec = val;\n        break;\n    case CRON_CF_MINUTE:\n        calendar->tm_min = val;\n        break;\n    case CRON_CF_HOUR_OF_DAY:\n        calendar->tm_hour = val;\n        break;\n    case CRON_CF_DAY_OF_WEEK:\n        calendar->tm_wday = val;\n        break;\n    case CRON_CF_DAY_OF_MONTH:\n        calendar->tm_mday = val;\n        break;\n    case CRON_CF_MONTH:\n        calendar->tm_mon = val;\n        break;\n    case CRON_CF_YEAR:\n        calendar->tm_year = val;\n        break;\n    default:\n        return 1; /* unknown field */\n    }\n    time_t res = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == res) {\n        return 1;\n    }\n    return 0;\n}\n\n/**\n * Search the bits provided for the next set bit after the value provided,\n * and reset the calendar.\n */\nstatic unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) {\n    int notfound = 0;\n    int err = 0;\n    unsigned int next_value = next_set_bit(bits, max, value, &notfound);\n    /* roll over if needed */\n    if (notfound) {\n        err = add_to_field(calendar, nextField, 1);\n        if (err) goto return_error;\n        err = reset_min(calendar, field);\n        if (err) goto return_error;\n        notfound = 0;\n        next_value = next_set_bit(bits, max, 0, &notfound);\n    }\n    if (notfound || next_value != value) {\n        err = set_field(calendar, field, next_value);\n        if (err) goto return_error;\n        err = reset_all_min(calendar, lower_orders);\n        if (err) goto return_error;\n    }\n    return next_value;\n\n    return_error:\n    *res_out = 1;\n    return 0;\n}\n\nstatic unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) {\n    int err;\n    unsigned int count = 0;\n    unsigned int max = 366;\n    while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) {\n        err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1);\n\n        if (err) goto return_error;\n        day_of_month = calendar->tm_mday;\n        day_of_week = calendar->tm_wday;\n        reset_all_min(calendar, resets);\n    }\n    return day_of_month;\n\n    return_error:\n    *res_out = 1;\n    return 0;\n}\n\nstatic int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) {\n    int i;\n    int res = 0;\n    int* resets = NULL;\n    int* empty_list = NULL;\n    unsigned int second = 0;\n    unsigned int update_second = 0;\n    unsigned int minute = 0;\n    unsigned int update_minute = 0;\n    unsigned int hour = 0;\n    unsigned int update_hour = 0;\n    unsigned int day_of_week = 0;\n    unsigned int day_of_month = 0;\n    unsigned int update_day_of_month = 0;\n    unsigned int month = 0;\n    unsigned int update_month = 0;\n\n    resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int));\n    if (!resets) goto return_result;\n    empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int));\n    if (!empty_list) goto return_result;\n    for (i = 0; i < CRON_CF_ARR_LEN; i++) {\n        resets[i] = -1;\n        empty_list[i] = -1;\n    }\n\n    second = calendar->tm_sec;\n    update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res);\n    if (0 != res) goto return_result;\n    if (second == update_second) {\n        push_to_fields_arr(resets, CRON_CF_SECOND);\n    }\n\n    minute = calendar->tm_min;\n    update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res);\n    if (0 != res) goto return_result;\n    if (minute == update_minute) {\n        push_to_fields_arr(resets, CRON_CF_MINUTE);\n    } else {\n        res = do_next(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n\n    hour = calendar->tm_hour;\n    update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res);\n    if (0 != res) goto return_result;\n    if (hour == update_hour) {\n        push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY);\n    } else {\n        res = do_next(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n\n    day_of_week = calendar->tm_wday;\n    day_of_month = calendar->tm_mday;\n    update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res);\n    if (0 != res) goto return_result;\n    if (day_of_month == update_day_of_month) {\n        push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH);\n    } else {\n        res = do_next(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n\n    month = calendar->tm_mon; /*day already adds one if no day in same month is found*/\n    update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res);\n    if (0 != res) goto return_result;\n    if (month != update_month) {\n        if (calendar->tm_year - dot > 4) {\n            res = -1;\n            goto return_result;\n        }\n        res = do_next(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n    goto return_result;\n\n    return_result:\n    if (!resets || !empty_list) {\n        res = -1;\n    }\n    if (resets) {\n        cron_free(resets);\n    }\n    if (empty_list) {\n        cron_free(empty_list);\n    }\n    return res;\n}\n\nstatic int to_upper(char* str) {\n    if (!str) return 1;\n    int i;\n    for (i = 0; '\\0' != str[i]; i++) {\n        int c = (int)str[i];\n        str[i] = (char) toupper(c);\n    }\n    return 0;\n}\n\nstatic char* to_string(int num) {\n    if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL;\n    char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1);\n    if (!str) return NULL;\n    int res = sprintf(str, \"%d\", num);\n    if (res < 0) {\n        cron_free(str);\n        return NULL;\n    }\n    return str;\n}\n\nstatic char* str_replace(char *orig, const char *rep, const char *with) {\n    char *result; /* the return string */\n    char *ins; /* the next insert point */\n    char *tmp; /* varies */\n    size_t len_rep; /* length of rep */\n    size_t len_with; /* length of with */\n    size_t len_front; /* distance between rep and end of last rep */\n    int count; /* number of replacements */\n    if (!orig) return NULL;\n    if (!rep) rep = \"\";\n    if (!with) with = \"\";\n    len_rep = strlen(rep);\n    len_with = strlen(with);\n\n    ins = orig;\n    for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) {\n        ins = tmp + len_rep;\n    }\n\n    /* first time through the loop, all the variable are set correctly\n     from here on,\n     tmp points to the end of the result string\n     ins points to the next occurrence of rep in orig\n     orig points to the remainder of orig after \"end of rep\"\n     */\n    tmp = result = (char*) cron_malloc(strlen(orig) + (len_with - len_rep) * count + 1);\n    if (!result) return NULL;\n\n    while (count--) {\n        ins = strstr(orig, rep);\n        len_front = ins - orig;\n        tmp = strncpy(tmp, orig, len_front) + len_front;\n        tmp = strcpy(tmp, with) + len_with;\n        orig += len_front + len_rep; /* move to next \"end of rep\" */\n    }\n    strcpy(tmp, orig);\n    return result;\n}\n\nstatic unsigned int parse_uint(const char* str, int* errcode) {\n    char* endptr;\n    errno = 0;\n    long int l = strtol(str, &endptr, 10);\n    if (errno == ERANGE || *endptr != '\\0' || l < 0 || l > INT_MAX) {\n        *errcode = 1;\n        return 0;\n    } else {\n        *errcode = 0;\n        return (unsigned int) l;\n    }\n}\n\nstatic char** split_str(const char* str, char del, size_t* len_out) {\n    size_t i;\n    size_t stlen = 0;\n    size_t len = 0;\n    int accum = 0;\n    char* buf = NULL;\n    char** res = NULL;\n    size_t bi = 0;\n    size_t ri = 0;\n    char* tmp;\n\n    if (!str) goto return_error;\n    for (i = 0; '\\0' != str[i]; i++) {\n        stlen += 1;\n        if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error;\n    }\n\n    for (i = 0; i < stlen; i++) {\n        int c = str[i];\n        if (del == str[i]) {\n            if (accum > 0) {\n                len += 1;\n                accum = 0;\n            }\n        } else if (!isspace(c)) {\n            accum += 1;\n        }\n    }\n    /* tail */\n    if (accum > 0) {\n        len += 1;\n    }\n    if (0 == len) return NULL;\n\n    buf = (char*) cron_malloc(stlen + 1);\n    if (!buf) goto return_error;\n    memset(buf, 0, stlen + 1);\n    res = (char**) cron_malloc(len * sizeof(char*));\n    if (!res) goto return_error;\n    memset(res, 0, len * sizeof(char*));\n\n    for (i = 0; i < stlen; i++) {\n        int c = str[i];\n        if (del == str[i]) {\n            if (bi > 0) {\n                tmp = strdupl(buf, bi);\n                if (!tmp) goto return_error;\n                res[ri++] = tmp;\n                memset(buf, 0, stlen + 1);\n                bi = 0;\n            }\n        } else if (!isspace(c)) {\n            buf[bi++] = str[i];\n        }\n    }\n    /* tail */\n    if (bi > 0) {\n        tmp = strdupl(buf, bi);\n        if (!tmp) goto return_error;\n        res[ri++] = tmp;\n    }\n    cron_free(buf);\n    *len_out = len;\n    return res;\n\n    return_error:\n    if (buf) {\n        cron_free(buf);\n    }\n    free_splitted(res, len);\n    *len_out = 0;\n    return NULL;\n}\n\nstatic char* replace_ordinals(char* value, const char* const * arr, size_t arr_len) {\n    size_t i;\n    char* cur = value;\n    char* res = NULL;\n    int first = 1;\n    for (i = 0; i < arr_len; i++) {\n        char* strnum = to_string((int) i);\n        if (!strnum) {\n            if (!first) {\n                cron_free(cur);\n            }\n            return NULL;\n        }\n        res = str_replace(cur, arr[i], strnum);\n        cron_free(strnum);\n        if (!first) {\n            cron_free(cur);\n        }\n        if (!res) {\n            return NULL;\n        }\n        cur = res;\n        if (first) {\n            first = 0;\n        }\n    }\n    return res;\n}\n\nstatic int has_char(char* str, char ch) {\n    size_t i;\n    size_t len = 0;\n    if (!str) return 0;\n    len = strlen(str);\n    for (i = 0; i < len; i++) {\n        if (str[i] == ch) return 1;\n    }\n    return 0;\n}\n\nstatic unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) {\n\n    char** parts = NULL;\n    size_t len = 0;\n    unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int));\n    if (!res) goto return_error;\n\n    res[0] = 0;\n    res[1] = 0;\n    if (1 == strlen(field) && '*' == field[0]) {\n        res[0] = min;\n        res[1] = max - 1;\n    } else if (!has_char(field, '-')) {\n        int err = 0;\n        unsigned int val = parse_uint(field, &err);\n        if (err) {\n            *error = \"Unsigned integer parse error 1\";\n            goto return_error;\n        }\n\n        res[0] = val;\n        res[1] = val;\n    } else {\n        parts = split_str(field, '-', &len);\n        if (2 != len) {\n            *error = \"Specified range requires two fields\";\n            goto return_error;\n        }\n        int err = 0;\n        res[0] = parse_uint(parts[0], &err);\n        if (err) {\n            *error = \"Unsigned integer parse error 2\";\n            goto return_error;\n        }\n        res[1] = parse_uint(parts[1], &err);\n        if (err) {\n            *error = \"Unsigned integer parse error 3\";\n            goto return_error;\n        }\n    }\n    if (res[0] >= max || res[1] >= max) {\n        *error = \"Specified range exceeds maximum\";\n        goto return_error;\n    }\n    if (res[0] < min || res[1] < min) {\n        *error = \"Specified range is less than minimum\";\n        goto return_error;\n    }\n    if (res[0] > res[1]) {\n        *error = \"Specified range start exceeds range end\";\n        goto return_error;\n    }\n\n    free_splitted(parts, len);\n    *error = NULL;\n    return res;\n\n    return_error:\n    free_splitted(parts, len);\n    if (res) {\n        cron_free(res);\n    }\n\n    return NULL;\n}\n\nstatic void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) {\n    size_t i;\n    unsigned int i1;\n    size_t len = 0;\n\n    char** fields = split_str(value, ',', &len);\n    if (!fields) {\n        *error = \"Comma split error\";\n        goto return_result;\n    }\n\n    for (i = 0; i < len; i++) {\n        if (!has_char(fields[i], '/')) {\n            /* Not an incrementer so it must be a range (possibly empty) */\n\n            unsigned int* range = get_range(fields[i], min, max, error);\n\n            if (*error) {\n                if (range) {\n                    cron_free(range);\n                }\n                goto return_result;\n\n            }\n\n            for (i1 = range[0]; i1 <= range[1]; i1++) {\n                cron_set_bit(target, i1);\n\n            }\n            cron_free(range);\n\n        } else {\n            size_t len2 = 0;\n            char** split = split_str(fields[i], '/', &len2);\n            if (2 != len2) {\n                *error = \"Incrementer must have two fields\";\n                free_splitted(split, len2);\n                goto return_result;\n            }\n            unsigned int* range = get_range(split[0], min, max, error);\n            if (*error) {\n                if (range) {\n                    cron_free(range);\n                }\n                free_splitted(split, len2);\n                goto return_result;\n            }\n            if (!has_char(split[0], '-')) {\n                range[1] = max - 1;\n            }\n            int err = 0;\n            unsigned int delta = parse_uint(split[1], &err);\n            if (err) {\n                *error = \"Unsigned integer parse error 4\";\n                cron_free(range);\n                free_splitted(split, len2);\n                goto return_result;\n            }\n            if (0 == delta) {\n                *error = \"Incrementer may not be zero\";\n                cron_free(range);\n                free_splitted(split, len2);\n                goto return_result;\n            }\n            for (i1 = range[0]; i1 <= range[1]; i1 += delta) {\n                cron_set_bit(target, i1);\n            }\n            free_splitted(split, len2);\n            cron_free(range);\n\n        }\n    }\n    goto return_result;\n\n    return_result:\n    free_splitted(fields, len);\n\n}\n\nstatic void set_months(char* value, uint8_t* targ, const char** error) {\n    unsigned int i;\n    unsigned int max = 12;\n\n    char* replaced = NULL;\n\n    to_upper(value);\n    replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN);\n    if (!replaced) {\n        *error = \"Invalid month format\";\n        return;\n    }\n    set_number_hits(replaced, targ, 1, max + 1, error);\n    cron_free(replaced);\n\n    /* ... and then rotate it to the front of the months */\n    for (i = 1; i <= max; i++) {\n        if (cron_get_bit(targ, i)) {\n            cron_set_bit(targ, i - 1);\n            cron_del_bit(targ, i);\n        }\n    }\n}\n\nstatic void set_days_of_week(char* field, uint8_t* targ, const char** error) {\n    unsigned int max = 7;\n    char* replaced = NULL;\n\n    if (1 == strlen(field) && '?' == field[0]) {\n        field[0] = '*';\n    }\n    to_upper(field);\n    replaced = replace_ordinals(field, DAYS_ARR, CRON_DAYS_ARR_LEN);\n    if (!replaced) {\n        *error = \"Invalid day format\";\n        return;\n    }\n    set_number_hits(replaced, targ, 0, max + 1, error);\n    cron_free(replaced);\n    if (cron_get_bit(targ, 7)) {\n        /* Sunday can be represented as 0 or 7*/\n        cron_set_bit(targ, 0);\n        cron_del_bit(targ, 7);\n    }\n}\n\nstatic void set_days_of_month(char* field, uint8_t* targ, const char** error) {\n    /* Days of month start with 1 (in Cron and Calendar) so add one */\n    if (1 == strlen(field) && '?' == field[0]) {\n        field[0] = '*';\n    }\n    set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error);\n}\n\nvoid cron_parse_expr(const char* expression, cron_expr* target, const char** error) {\n    const char* err_local;\n    size_t len = 0;\n    char** fields = NULL;\n    if (!error) {\n        error = &err_local;\n    }\n    *error = NULL;\n    if (!expression) {\n        *error = \"Invalid NULL expression\";\n        goto return_res;\n    }\n    if (!target) {\n        *error = \"Invalid NULL target\";\n        goto return_res;\n    }\n\n    fields = split_str(expression, ' ', &len);\n    if (len != 6) {\n        *error = \"Invalid number of fields, expression must consist of 6 fields\";\n        goto return_res;\n    }\n    memset(target, 0, sizeof(*target));\n    set_number_hits(fields[0], target->seconds, 0, 60, error);\n    if (*error) goto return_res;\n    set_number_hits(fields[1], target->minutes, 0, 60, error);\n    if (*error) goto return_res;\n    set_number_hits(fields[2], target->hours, 0, 24, error);\n    if (*error) goto return_res;\n    set_days_of_month(fields[3], target->days_of_month, error);\n    if (*error) goto return_res;\n    set_months(fields[4], target->months, error);\n    if (*error) goto return_res;\n    set_days_of_week(fields[5], target->days_of_week, error);\n    if (*error) goto return_res;\n\n    goto return_res;\n\n    return_res:\n    free_splitted(fields, len);\n}\n\ntime_t cron_next(cron_expr* expr, time_t date) {\n    /*\n     The plan:\n\n     1 Round up to the next whole second\n\n     2 If seconds match move on, otherwise find the next match:\n     2.1 If next match is in the next minute then roll forwards\n\n     3 If minute matches move on, otherwise find the next match\n     3.1 If next match is in the next hour then roll forwards\n     3.2 Reset the seconds and go to 2\n\n     4 If hour matches move on, otherwise find the next match\n     4.1 If next match is in the next day then roll forwards,\n     4.2 Reset the minutes and seconds and go to 2\n\n     ...\n     */\n    if (!expr) return CRON_INVALID_INSTANT;\n    struct tm calval;\n    memset(&calval, 0, sizeof(struct tm));\n    struct tm* calendar = cron_time(&date, &calval);\n    if (!calendar) return CRON_INVALID_INSTANT;\n    time_t original = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT;\n\n    int res = do_next(expr, calendar, calendar->tm_year);\n    if (0 != res) return CRON_INVALID_INSTANT;\n\n    time_t calculated = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT;\n    if (calculated == original) {\n        /* We arrived at the original timestamp - round up to the next whole second and try again... */\n        res = add_to_field(calendar, CRON_CF_SECOND, 1);\n        if (0 != res) return CRON_INVALID_INSTANT;\n        res = do_next(expr, calendar, calendar->tm_year);\n        if (0 != res) return CRON_INVALID_INSTANT;\n    }\n\n    return cron_mktime(calendar);\n}\n\n\n/* https://github.com/staticlibs/ccronexpr/pull/8 */\n\nstatic unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) {\n    int i;\n    if (!bits) {\n        *notfound = 1;\n        return 0;\n    }\n    for (i = from_index; i >= to_index; i--) {\n        if (cron_get_bit(bits, i)) return i;\n    }\n    *notfound = 1;\n    return 0;\n}\n\nstatic int last_day_of_month(int month, int year) {\n    struct tm cal;\n    time_t t;\n    memset(&cal,0,sizeof(cal));\n    cal.tm_sec=0;\n    cal.tm_min=0;\n    cal.tm_hour=0;\n    cal.tm_mon = month+1;\n    cal.tm_mday = 0;\n    cal.tm_year=year;\n    t=mktime(&cal);\n    return gmtime(&t)->tm_mday;\n}\n\n/**\n * Reset the calendar setting all the fields provided to zero.\n */\nstatic int reset_max(struct tm* calendar, int field) {\n    if (!calendar || -1 == field) {\n        return 1;\n    }\n    switch (field) {\n    case CRON_CF_SECOND:\n        calendar->tm_sec = 59;\n        break;\n    case CRON_CF_MINUTE:\n        calendar->tm_min = 59;\n        break;\n    case CRON_CF_HOUR_OF_DAY:\n        calendar->tm_hour = 23;\n        break;\n    case CRON_CF_DAY_OF_WEEK:\n        calendar->tm_wday = 6;\n        break;\n    case CRON_CF_DAY_OF_MONTH:\n        calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year);\n        break;\n    case CRON_CF_MONTH:\n        calendar->tm_mon = 11;\n        break;\n    case CRON_CF_YEAR:\n        /* I don't think this is supposed to happen ... */\n        fprintf(stderr, \"reset CRON_CF_YEAR\\n\");\n        break;\n    default:\n        return 1; /* unknown field */\n    }\n    time_t res = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == res) {\n        return 1;\n    }\n    return 0;\n}\n\nstatic int reset_all_max(struct tm* calendar, int* fields) {\n    int i;\n    int res = 0;\n    if (!calendar || !fields) {\n        return 1;\n    }\n    for (i = 0; i < CRON_CF_ARR_LEN; i++) {\n        if (-1 != fields[i]) {\n            res = reset_max(calendar, fields[i]);\n            if (0 != res) return res;\n        }\n    }\n    return 0;\n}\n\n/**\n * Search the bits provided for the next set bit after the value provided,\n * and reset the calendar.\n */\nstatic unsigned int find_prev(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) {\n    int notfound = 0;\n    int err = 0;\n    unsigned int next_value = prev_set_bit(bits, value, 0, &notfound);\n    /* roll under if needed */\n    if (notfound) {\n        err = add_to_field(calendar, nextField, -1);\n        if (err) goto return_error;\n        err = reset_max(calendar, field);\n        if (err) goto return_error;\n        notfound = 0;\n        next_value = prev_set_bit(bits, max - 1, value, &notfound);\n    }\n    if (notfound || next_value != value) {\n        err = set_field(calendar, field, next_value);\n        if (err) goto return_error;\n        err = reset_all_max(calendar, lower_orders);\n        if (err) goto return_error;\n    }\n    return next_value;\n\n    return_error:\n    *res_out = 1;\n    return 0;\n}\n\nstatic unsigned int find_prev_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) {\n    int err;\n    unsigned int count = 0;\n    unsigned int max = 366;\n    while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) {\n        err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, -1);\n\n        if (err) goto return_error;\n        day_of_month = calendar->tm_mday;\n        day_of_week = calendar->tm_wday;\n        reset_all_max(calendar, resets);\n    }\n    return day_of_month;\n\n    return_error:\n    *res_out = 1;\n    return 0;\n}\n\nstatic int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) {\n    int i;\n    int res = 0;\n    int* resets = NULL;\n    int* empty_list = NULL;\n    unsigned int second = 0;\n    unsigned int update_second = 0;\n    unsigned int minute = 0;\n    unsigned int update_minute = 0;\n    unsigned int hour = 0;\n    unsigned int update_hour = 0;\n    unsigned int day_of_week = 0;\n    unsigned int day_of_month = 0;\n    unsigned int update_day_of_month = 0;\n    unsigned int month = 0;\n    unsigned int update_month = 0;\n\n    resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int));\n    if (!resets) goto return_result;\n    empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int));\n    if (!empty_list) goto return_result;\n    for (i = 0; i < CRON_CF_ARR_LEN; i++) {\n        resets[i] = -1;\n        empty_list[i] = -1;\n    }\n\n    second = calendar->tm_sec;\n    update_second = find_prev(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res);\n    if (0 != res) goto return_result;\n    if (second == update_second) {\n        push_to_fields_arr(resets, CRON_CF_SECOND);\n    }\n\n    minute = calendar->tm_min;\n    update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res);\n    if (0 != res) goto return_result;\n    if (minute == update_minute) {\n        push_to_fields_arr(resets, CRON_CF_MINUTE);\n    } else {\n        res = do_prev(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n\n    hour = calendar->tm_hour;\n    update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res);\n    if (0 != res) goto return_result;\n    if (hour == update_hour) {\n        push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY);\n    } else {\n        res = do_prev(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n\n    day_of_week = calendar->tm_wday;\n    day_of_month = calendar->tm_mday;\n    update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res);\n    if (0 != res) goto return_result;\n    if (day_of_month == update_day_of_month) {\n        push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH);\n    } else {\n        res = do_prev(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n\n    month = calendar->tm_mon; /*day already adds one if no day in same month is found*/\n    update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res);\n    if (0 != res) goto return_result;\n    if (month != update_month) {\n        if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) {\n            res = -1;\n            goto return_result;\n        }\n        res = do_prev(expr, calendar, dot);\n        if (0 != res) goto return_result;\n    }\n    goto return_result;\n\n    return_result:\n    if (!resets || !empty_list) {\n        res = -1;\n    }\n    if (resets) {\n        cron_free(resets);\n    }\n    if (empty_list) {\n        cron_free(empty_list);\n    }\n    return res;\n}\n\ntime_t cron_prev(cron_expr* expr, time_t date) {\n    /*\n     The plan:\n\n     1 Round down to a whole second\n\n     2 If seconds match move on, otherwise find the next match:\n     2.1 If next match is in the next minute then roll forwards\n\n     3 If minute matches move on, otherwise find the next match\n     3.1 If next match is in the next hour then roll forwards\n     3.2 Reset the seconds and go to 2\n\n     4 If hour matches move on, otherwise find the next match\n     4.1 If next match is in the next day then roll forwards,\n     4.2 Reset the minutes and seconds and go to 2\n\n     ...\n     */\n    if (!expr) return CRON_INVALID_INSTANT;\n    struct tm calval;\n    memset(&calval, 0, sizeof(struct tm));\n    struct tm* calendar = cron_time(&date, &calval);\n    if (!calendar) return CRON_INVALID_INSTANT;\n    time_t original = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT;\n\n    /* calculate the previous occurrence */\n    int res = do_prev(expr, calendar, calendar->tm_year);\n    if (0 != res) return CRON_INVALID_INSTANT;\n\n    /* check for a match, try from the next second if one wasn't found */\n    time_t calculated = cron_mktime(calendar);\n    if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT;\n    if (calculated == original) {\n        /* We arrived at the original timestamp - round up to the next whole second and try again... */\n        res = add_to_field(calendar, CRON_CF_SECOND, -1);\n        if (0 != res) return CRON_INVALID_INSTANT;\n        res = do_prev(expr, calendar, calendar->tm_year);\n        if (0 != res) return CRON_INVALID_INSTANT;\n    }\n\n    return cron_mktime(calendar);\n}\n"
  },
  {
    "path": "src/scheduled-jobs/ccronexpr.h",
    "content": "/*\n * Copyright 2015, alex at staticlibs.net\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * File:   ccronexpr.h\n * Author: alex\n *\n * Created on February 24, 2015, 9:35 AM\n */\n\n#ifndef CCRONEXPR_H\n#define CCRONEXPR_H\n\n#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX)\nextern \"C\" {\n#endif\n\n#ifndef ANDROID\n#include <time.h>\n#else /* ANDROID */\n#include <time64.h>\n#endif /* ANDROID */\n\n#include <stdint.h> /*added for use if uint*_t data types*/\n\n/**\n * Parsed cron expression\n */\ntypedef struct {\n    uint8_t seconds[8];\n    uint8_t minutes[8];\n    uint8_t hours[3];\n    uint8_t days_of_week[1];\n    uint8_t days_of_month[4];\n    uint8_t months[2];\n} cron_expr;\n\n/**\n * Parses specified cron expression.\n *\n * @param expression cron expression as nul-terminated string,\n *        should be no longer that 256 bytes\n * @param pointer to cron expression structure, it's client code responsibility\n *        to free/destroy it afterwards\n * @param error output error message, will be set to string literal\n *        error message in case of error. Will be set to NULL on success.\n *        The error message should NOT be freed by client.\n */\nvoid cron_parse_expr(const char* expression, cron_expr* target, const char** error);\n\n/**\n * Uses the specified expression to calculate the next 'fire' date after\n * the specified date. All dates are processed as UTC (GMT) dates\n * without timezones information. To use local dates (current system timezone)\n * instead of GMT compile with '-DCRON_USE_LOCAL_TIME'\n *\n * @param expr parsed cron expression to use in next date calculation\n * @param date start date to start calculation from\n * @return next 'fire' date in case of success, '((time_t) -1)' in case of error.\n */\n\n        time_t cron_next(cron_expr* expr, time_t date);\n\n/**\n * Uses the specified expression to calculate the previous 'fire' date after\n * the specified date. All dates are processed as UTC (GMT) dates\n * without timezones information. To use local dates (current system timezone)\n * instead of GMT compile with '-DCRON_USE_LOCAL_TIME'\n *\n * @param expr parsed cron expression to use in previous date calculation\n * @param date start date to start calculation from\n * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error.\n */\ntime_t cron_prev(cron_expr* expr, time_t date);\n\n\n#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX)\n} /* extern \"C\"*/\n#endif\n\n#endif /* CCRONEXPR_H */\n"
  },
  {
    "path": "src/scheduled-jobs/model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :scheduled-jobs/model\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:initialize-transient-instance)\n  (:import-from #:bknr.datastore\n                #:*store*)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:index-least\n                #:fset-set-index)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:scheduled-job\n   #:scheduled-job-function\n   #:scheduled-job-args\n   #:%at\n   #:job\n   #:cronexpr\n   #:next-scheduled-job\n   #:find-jobs-by-function))\n(in-package :scheduled-jobs/model)\n\n(defvar *lock* (bt:make-recursive-lock))\n\n;; TODO: replace this logic with def-store-local. It's best to time\n;; this with a restart\n\n(defindex +scheduled-job-index+\n  'fset-set-index\n  :slot-name 'at)\n\n(defclass scheduled-job (store-object)\n  ((at :initarg :at\n       :reader at\n       :index +scheduled-job-index+\n       :writer (setf %at) #| For internal use only |#)\n   (function\n    :initarg :function\n    :reader scheduled-job-function)\n   (args\n    :initarg :args\n    :reader scheduled-job-args)\n   (cronexpr\n    :initarg :cronexpr\n    :initform nil\n    :reader cronexpr\n    :writer (setf %cronexpr))\n   (tzname\n    :initarg :tzname\n    :initform nil\n    :reader scheduled-job-tzname\n    :documentation \"When used with cronexpr, this determins the\n    timezone in which to calculate the cron job.\")\n   (periodic\n    :initarg :periodic\n    :initform nil\n    :reader periodic)\n   (wrapper :initform nil\n            :accessor wrapper\n            :transient t))\n  (:metaclass persistent-class))\n\n(defun find-jobs-by-function (function)\n  (loop for job in (bknr.datastore:class-instances 'scheduled-job)\n        if (eql function (scheduled-job-function job))\n          collect job))\n\n(defun next-scheduled-job ()\n  (index-least +scheduled-job-index+))\n\n\n(defmethod bknr.datastore:initialize-transient-instance ((self scheduled-job))\n  ;; TODO: delete\n  (call-next-method))\n\n(defmethod tz-offset ((self scheduled-job))\n  (let ((tzname (scheduled-job-tzname self)))\n    (cond\n      (tzname\n       (tz-offset tzname))\n      (t 0))))\n\n(defmethod tz-offset ((tzname string))\n  (let* ((tzname (if (uiop:os-windows-p) (str:replace-all \"/\" \"\\\\\" tzname) tzname))\n         (tz (local-time:find-timezone-by-location-name tzname)))\n    (cond\n      (tz\n       (/ (local-time:timestamp-subtimezone\n           (local-time:now)\n           tz)\n          3600))\n      (t\n       (warn \"Could not find timezone ~a\" tzname)\n       0))))\n\n(defmethod tz-offset ((location null))\n  0)\n"
  },
  {
    "path": "src/scheduled-jobs/scheduled-jobs.asd",
    "content": "(defpackage :scheduled-jobs/scheduled-jobs.asd\n  (:use #:cl\n        #:asdf))\n(in-package :scheduled-jobs/scheduled-jobs.asd)\n\n(defclass lib-source-file (c-source-file)\n  ((extra-args :initarg :extra-args\n               :initform nil\n               :reader extra-args)))\n\n(defun default-foreign-library-type ()\n  \"Returns string naming default library type for platform\"\n  #+(or win32 win64 cygwin mswindows windows) \"dll\"\n  #+(or macosx darwin ccl-5.0) \"dylib\"\n  #-(or win32 win64 cygwin mswindows windows macosx darwin ccl-5.0) \"so\"\n)\n\n(defmethod output-files ((o compile-op) (c lib-source-file))\n  (let ((library-file-type\n          (default-foreign-library-type)))\n    (list (make-pathname :name (component-name c)\n                         :type library-file-type\n                         :defaults (asdf:component-pathname c)))))\n\n(defmethod perform ((o load-op) (c lib-source-file))\n  t)\n\n(defmethod perform ((o compile-op) (c lib-source-file))\n  (let ((output-file (car (output-files o c))))\n    (restart-case\n        (uiop:delete-file-if-exists output-file)\n      (ignore-deletion ()\n        (values)))\n    (uiop:with-staging-pathname (output-file output-file)\n      (uiop:run-program (list* \"gcc\" \"-shared\"\n                               \"-o\" (namestring output-file)\n                               \"-Werror\"\n                               \"-fPIC\"\n                               (namestring\n                                (asdf:component-pathname c))\n                               (extra-args c))\n                        :output t\n                        :error-output t))))\n\n(defsystem #:scheduled-jobs/headers\n  :serial t\n  :components ((static-file \"ccronexpr\" :type \"h\")))\n\n(defsystem #:scheduled-jobs\n  :serial t\n  :depends-on (#:cl-cron\n               #:scheduled-jobs/headers\n               #:util/throttler\n               #:alexandria\n               #:util/native-module\n               #:util\n               #:util/health-check\n               #:util.threading)\n  :components ((lib-source-file \"ccronexpr\")\n               (:file \"model\")\n               (:file \"bindings\")\n               (:file \"scheduled-jobs\")))\n\n(defsystem #:scheduled-jobs/tests\n  :serial t\n  :depends-on (#:scheduled-jobs\n               #:fiveam\n               #:util/fiveam)\n  :components ((:file \"test-scheduled-jobs\")\n               (:file \"test-bindings\")))\n"
  },
  {
    "path": "src/scheduled-jobs/scheduled-jobs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :scheduled-jobs\n  (:use #:cl)\n  (:import-from #:scheduled-jobs/model\n                #:next-scheduled-job\n                #:tz-offset\n                #:cronexpr\n                #:job\n                #:%at\n                #:*lock*\n                #:at\n                #:periodic\n                #:scheduled-job-args\n                #:scheduled-job-function\n                #:scheduled-job)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p)\n  (:import-from #:bknr.datastore\n                #:deftransaction\n                #:*store*\n                #:delete-object)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:scheduled-jobs/bindings\n                #:cron-next\n                #:cron-parse-expr)\n  #+ (and lispworks linux)\n  (:import-from #:bknr.cluster/server\n                #:leaderp)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:make-scheduled-job\n   #:now\n   #:at\n   #:bad-argument-error\n   #:call-job\n   #:*scheduled-job*))\n(in-package :scheduled-jobs)\n\n\n(defvar *safety-throttler* (make-instance 'throttler\n                                          :tokens 600)\n  \"A throttler to prevent bugs in scheduled-jobs from taking over system\nresources.\")\n\n(defvar *enabledp* t\n  \"A flag to disable scheduled jobs if there's a bug\")\n\n(defclass threaded-executor ()\n  ())\n\n(defvar *executor* (make-instance 'threaded-executor))\n\n(defvar *scheduled-job* nil\n  \"The current scheduled job, bound when a job is being executed\")\n\n(defun now ()\n  (get-universal-time))\n\n(define-condition bad-argument-error (error)\n  ((argument :initarg :argument)))\n\n(defun is-valid-arg (arg)\n  \"A sanity check on the argument, to make sure the argument is\nserializable. This does not gurrantee serializability though, since\nwe're not looking in to what the object references.\"\n  (or\n   (null arg)\n   (stringp arg)\n   (symbolp arg)\n   (arrayp arg)\n   (numberp arg)\n   (listp arg)\n   (typep arg 'bknr.datastore:store-object)))\n\n(defmethod (setf cronexpr) ((val string) (self scheduled-job))\n  (let ((parsed (cron-parse-expr val)))\n    (with-transaction ()\n      (setf (%at self) (cron-next parsed :now (now)\n                                         :timezone (tz-offset self))))))\n\n(defun make-scheduled-job (&key at\n                             cronexpr\n                             tzname\n                             (function (error \"must provide :function\"))\n                             (args (error \"must provide :args\"))\n                             periodic)\n  (unless (or at cronexpr)\n    (error \"must provide at least one of :at or :cronexpr\"))\n  (when (and at cronexpr)\n    (error \"Don't provide both :at and :cronexpr\"))\n\n  (when cronexpr\n    (let ((parsed (cron-parse-expr cronexpr)))\n      (setf at (cron-next parsed :now (now)\n                          :timezone (tz-offset tzname)))))\n\n  (loop for arg in args\n        unless (is-valid-arg arg)\n          do (error 'bad-argument-error :argument arg))\n  (make-instance 'scheduled-job\n                  :at at\n                  :cronexpr cronexpr\n                  :tzname tzname\n                  :function function\n                  :args args\n                  :periodic periodic))\n\n\n(defvar *call-pending-scheduled-jobs-lock* (bt:make-lock \"call-pending-scheduled-job\"))\n\n(defun %call-pending-scheduled-jobs ()\n  (throttle! *safety-throttler* :key t)\n  (when *enabledp*\n   (let ((now (now)))\n     (when (bt:with-lock-held (*lock*)\n             (let ((next (next-scheduled-job)))\n               (and\n                next\n                (< (at next) now))))\n       (multiple-value-bind (next)\n           ;; TODO: refactor this logic, but be very very careful\n           (bt:with-lock-held (*lock*)\n             (let ((next (next-scheduled-job)))\n               (setf (%at next) nil)\n               next))\n         (cond\n           ((null next)\n            :no-more)\n           (t\n            (unwind-protect\n                 (call-job *executor*\n                           (lambda (&rest args)\n                             (let ((*scheduled-job* next))\n                               (apply (scheduled-job-function next) args)))\n                           (scheduled-job-args next))\n              (%reschedule-job next now))\n            (%call-pending-scheduled-jobs))))))))\n\n(deftransaction update-at (next at)\n  \"Transaction to update both the AT slot, and the queue at the same\ntime. TODO: delete this.\"\n  (setf (%at next) at))\n\n(defun %reschedule-job (next now)\n  \"After a job has just been run, call this to reschedule the job onto\nthe queue. Internal detail.\"\n  (flet ((schedule-at (at)\n           (update-at next at)))\n    (cond\n      ((cronexpr next)\n       ;; Figure out the next run time based on the cronexpr\n       (let ((cron-expr (ignore-errors\n                         (cron-parse-expr (cronexpr next)))))\n         (when cron-expr\n           (schedule-at (cron-next cron-expr :now (now)\n                                             :timezone (tz-offset next))))))\n      ((periodic next)\n       (schedule-at (+ (periodic next) now)))\n      (t\n       (delete-object next)))))\n\n(defun reschedule-all-jobs ()\n  (loop for scheduled-job in (bknr.datastore:class-instances 'scheduled-job)\n        if (and\n            (not  (at scheduled-job))\n            (cronexpr scheduled-job))\n          do\n             (warn \"Scheduled job has missing AT field: ~a\" scheduled-job)\n             (%reschedule-job scheduled-job (now))))\n\n\n(defun call-pending-scheduled-jobs ()\n  (bt:with-lock-held (*call-pending-scheduled-jobs-lock*)\n    (%call-pending-scheduled-jobs)))\n\n(defmethod call-job ((executor t) fn args)\n  (apply fn args))\n\n(defmethod call-job ((executor threaded-executor) fn args)\n  (util/threading:make-thread\n   (lambda ()\n     ;; I don't know if call-next-method will work reliably across\n     ;; implementations. But it's not really needed for this.\n     (call-job nil fn args))\n   :name \"schedule-job\"))\n\n(def-cron call-pending-scheduled-job ()\n  (when *enabledp*\n    (when (and\n           (boundp 'bknr.datastore:*store*)\n           #+ (and lispworks linux)\n           (leaderp bknr.datastore:*store*))\n      (call-pending-scheduled-jobs))))\n"
  },
  {
    "path": "src/scheduled-jobs/test-bindings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :scheduled-jobs/test-bindings\n  (:use #:cl\n        #:fiveam\n        #:scheduled-jobs/bindings)\n  (:import-from #:scheduled-jobs/bindings\n                #:fli-cron-expr)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :scheduled-jobs/test-bindings)\n\n\n(util/fiveam:def-suite)\n\n(test preconditions\n  (is-true (cron-parse-expr \"* * * * * *\"))\n  (is-true (fli-cron-expr (cron-parse-expr \"* * * * * *\"))))\n\n(test parse-invalid\n  (signals invalid-cron-expr\n    (cron-parse-expr \"dfdfsdf\")))\n\n;;;; NOTE!!!!  decode|encode-universal-time use the negative of the\n;;;; standard time-zone offset! For instance, New York is technically,\n;;;; GMT-5:00, but in CL, that would be considered timezone of 5.\n\n(test cron-next\n  (handler-bind ((error (lambda (e)\n                          (trivial-backtrace:print-backtrace e))))\n   (let ((cron-expr (cron-parse-expr \"0 0 8 * * *\"))\n         (time-zone -4))\n     (is (equal (multiple-value-list\n                 (decode-universal-time\n                  (encode-universal-time\n                   0 0 8 2 1 2021 (- time-zone))))\n                (multiple-value-list\n                 (decode-universal-time\n                  (cron-next cron-expr\n                             :now (encode-universal-time 0 0 6 2 1 2021\n                                                         (- time-zone))\n                             :timezone time-zone))))))))\n\n(test cron-next-if-in-current-timezone-we-would-hit-sooner-than-gmt\n  (handler-bind ((error (lambda (e)\n                          (trivial-backtrace:print-backtrace e))))\n   (let ((cron-expr (cron-parse-expr \"0 0 8 * * *\"))\n         (time-zone 6))\n     (is (equal (multiple-value-list\n                 (decode-universal-time\n                  (encode-universal-time\n                   0 0 8 2 1 2021 (- time-zone))))\n                (multiple-value-list\n                 (decode-universal-time\n                  (cron-next cron-expr\n                             :now (encode-universal-time 0 0 6 2 1 2021\n                                                         (- time-zone))\n                             :timezone time-zone))))))))\n"
  },
  {
    "path": "src/scheduled-jobs/test-scheduled-jobs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :scheduled-jobs/test-scheduled-jobs\n  (:use #:cl\n        #:fiveam\n        #:scheduled-jobs)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:scheduled-jobs\n                #:*scheduled-job*\n                #:*executor*\n                #:call-pending-scheduled-jobs\n                #:now\n                #:scheduled-job)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p)\n  (:import-from #:scheduled-jobs/model\n                #:tz-offset\n                #:cronexpr\n                #:%at\n                #:at)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:scheduled-jobs/bindings\n                #:*unix-epoch-difference*)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :scheduled-jobs/test-scheduled-jobs)\n\n(util/fiveam:def-suite)\n\n(defvar *state* nil)\n\n(def-fixture state ()\n  (with-test-store ()\n    (cl-mock:with-mocks ()\n      (let ((time 0)\n            (*executor* nil))\n        (cl-mock:if-called 'now\n                            (lambda () time))\n        (unwind-protect\n             (&body)\n          (setf *state* nil))))))\n\n(test make-model\n  (with-fixture state ()\n    (finishes\n      (make-instance 'scheduled-job :at 0))))\n\n\n(test make-scheduled-job\n  (with-fixture state ()\n    (finishes\n      (make-scheduled-job :at 20\n                          :function 'foo\n                          :args nil))))\n\n(test make-model-with-cron\n  (with-fixture state ()\n    (setf time (unix 0))\n    (let ((job (make-scheduled-job :cronexpr \"0 0 * * * *\"\n                                   :function 'foo\n                                   :args nil)))\n      (is (eql (at job) (unix 3600))))))\n\n(defclass zoidberg () ())\n\n(test check-args-for-store-objects\n  (with-fixture state ()\n    (signals bad-argument-error\n      (make-scheduled-job :at 20\n                          :function 'foo\n                          :args (list\n                                 (make-instance 'zoidberg))))))\n\n(defun foo ()\n  (setf *state* :done))\n\n(test sanity-check\n  (with-fixture state ()\n    (finishes\n     (make-scheduled-job :at 0\n                         :function 'foo\n                         :args '()))\n    (setf time 1)\n    (call-pending-scheduled-jobs)\n    (is (eql :done *state*))))\n\n(defun unix (x)\n  (+ x *unix-epoch-difference*\n     ;; allow for negative timezones etc\n     (* 3600 24 10)))\n\n\n(test always-looking-at-the-time\n  (with-fixture state ()\n    (setf time 0)\n    (finishes\n     (make-scheduled-job :at 20\n                         :function 'foo\n                         :args '()))\n    (setf time 10)\n    (call-pending-scheduled-jobs)\n    (is (eql nil *state*))\n    (setf time 30)\n    (call-pending-scheduled-jobs)\n    (is (eql :done *state*))))\n\n(test always-looking-at-the-time-for-cronexpr\n  (with-fixture state ()\n    (setf time (unix 0))\n    (finishes\n     (make-scheduled-job :cronexpr \"20 * * * * *\"\n                         :function 'foo\n                         :args '()))\n    (setf time (unix 10))\n    (call-pending-scheduled-jobs)\n    (is (eql nil *state*))\n    (setf time (unix 30))\n    (call-pending-scheduled-jobs)\n    (is (eql :done *state*))))\n\n(test dont-run-deleted-job\n  (with-fixture state ()\n    (let ((job (make-scheduled-job :at 20\n                                   :function 'foo\n                                   :args '())))\n      (setf time 30)\n      (delete-object job)\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*)))))\n\n(test dont-run-deleted-job-for-cronexpr\n  (with-fixture state ()\n    (setf time (unix 0))\n    (let ((job (make-scheduled-job :cronexpr \"20 * * * * *\"\n                                   :function 'foo\n                                   :args '())))\n      (setf time (unix 30))\n      (delete-object job)\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*)))))\n\n(test old-objects-are-immediately-deleted\n  (with-fixture state ()\n    (let ((job (make-scheduled-job :at 20\n                                   :function 'foo\n                                   :args '())))\n      (setf time 30)\n      (call-pending-scheduled-jobs)\n      (is (object-destroyed-p job)))))\n\n(test periodic-job-is-rescheduled\n  (with-fixture state ()\n    (let ((job (make-scheduled-job :at 20\n                                   :function 'foo\n                                   :periodic 15\n                                   :args '())))\n      (setf time 30)\n      (call-pending-scheduled-jobs)\n      (is (eql :done *state*))\n      (is (eql 45 (at job)))\n      (setf *state* nil)\n      (setf time 40)\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*))\n      (setf time 50)\n      (call-pending-scheduled-jobs)\n      (is (eql :done *state*)))))\n\n(test periodic-job-is-rescheduled-for-cronexpr\n  (with-fixture state ()\n    (setf time (unix 0))\n    (let ((job (make-scheduled-job :cronexpr \"20,35,50 * * * * *\"\n                                   :function 'foo\n                                   :args '())))\n      ;; Note that for cron jobs, it isn't scheduled immediately after\n      ;; the previous job is done, instead it's always scheduled on\n      ;; the clock.\n      (setf time (unix 30))\n      (call-pending-scheduled-jobs)\n      (is (eql :done *state*))\n      (is (eql (unix 35) (at job)))\n      (setf *state* nil)\n      (setf time (unix 33))\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*))\n      (setf time (unix 50))\n      (call-pending-scheduled-jobs)\n      (is (eql :done *state*)))))\n\n(test if-AT-is-changed-we-update-queue\n  (with-fixture state ()\n    (let ((job (make-scheduled-job :at 20\n                                   :function 'foo\n                                   :args '())))\n      (setf time 30)\n      (with-transaction ()\n        (setf (%at job) 40))\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*)))))\n\n(test if-cronexpr-is-changed-we-update-queue\n  (with-fixture state ()\n    (setf time (unix 0))\n    (let ((job (make-scheduled-job :cronexpr \"20 * * * * *\"\n                                   :function 'foo\n                                   :args '())))\n      (setf time (unix 30))\n      (setf (cronexpr job) \"40 * * * * *\")\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*)))))\n\n(test tz-offset-is-correctly-computed\n  (with-fixture state ()\n    (setf time (unix 0))\n    (let ((job (make-scheduled-job :cronexpr \"20 * * * * *\"\n                                   :function 'foo\n                                   :tzname \"Asia/Calcutta\"\n                                   :args '())))\n      (is (equal 11/2 (tz-offset job))))))\n\n(defvar *ctr*)\n\n(defun incr-ctr ()\n  (incf *ctr*))\n\n(test if-AT-is-changed-multiple-times-we-only-call-once-we-update-queue\n  (with-fixture state ()\n    (let ((*ctr* 0))\n     (let ((job (make-scheduled-job :at 20\n                                    :function 'incr-ctr\n                                    :args '())))\n       (with-transaction ()\n         (setf (%at job) 20))\n       (with-transaction ()\n         (setf (%at job) 20))\n       (setf time 30)\n       (call-pending-scheduled-jobs)\n       (is (eql 1 *ctr*))))))\n\n(test if-AT-is-changed-multiple-times-we-only-call-once-we-update-queue-periodic\n  (with-fixture state ()\n    (let ((*ctr* 0))\n     (let ((job (make-scheduled-job :at 20\n                                    :function 'incr-ctr\n                                    :periodic 100\n                                    :args '())))\n       (with-transaction ()\n         (setf (%at job) 20))\n       (with-transaction ()\n         (setf (%at job) 20))\n       (setf time 30)\n       (call-pending-scheduled-jobs)\n       (is (eql 1 *ctr*))))))\n\n(test multiple-jobs-get-called\n  (with-fixture state ()\n    (let ((*ctr* 0))\n      (make-scheduled-job :at 20\n                          :function 'incr-ctr\n                          :args '())\n      (make-scheduled-job :at 19\n                          :function 'incr-ctr\n                          :args '())\n      (setf time 21)\n      (call-pending-scheduled-jobs)\n      (is (eql 2 *ctr*)))))\n\n(defun test-binding ()\n  (setf *state* (type-of *scheduled-job*)))\n\n(test scheduled-job-is-bound\n  (with-fixture state ()\n    (make-scheduled-job :at 20\n                        :function 'test-binding\n                        :args '())\n    (setf time 21)\n    (call-pending-scheduled-jobs)\n    (is (eql 'scheduled-job *state*))))\n\n\n(test update-queue-for-for-replayed-transactions\n  (with-fixture state ()\n    (let ((job (make-scheduled-job :at 20\n                                   :function 'foo\n                                   :args '())))\n      (setf time 5)\n      (call-pending-scheduled-jobs)\n      (is (eql nil *state*))\n      (setf (%at job) 1)\n      (call-pending-scheduled-jobs)\n      (is (eql :done *state*)))))\n"
  },
  {
    "path": "src/screenshotbot/.gitignore",
    "content": "selenium-output\n"
  },
  {
    "path": "src/screenshotbot/abstract-pr-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/abstract-pr-promoter\n  (:use #:cl)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-send-tasks\n                #:maybe-promote\n                #:promoter)\n  (:import-from #:screenshotbot/report-api\n                #:report-previous-run\n                #:report-run\n                #:report-acceptable\n                #:report)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-active-commits\n                #:channel-deleted-p\n                #:channel-company\n                #:production-run-for)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:created-at\n                #:current-user\n                #:user-email\n                #:user-full-name\n                #:recorder-run-commit\n                #:recorder-run-channel\n                #:channel-repo)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-empty-p)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/diff-report\n                #:make-diff-report\n                #:diff-report-title)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/git-repo\n                #:commit-graph\n                #:commit-graph-dag\n                #:compute-merge-base\n                #:get-parent-commit\n                #:repo-link)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-author\n                #:push-run-warning\n                #:runs-for-channel\n                #:recorder-run-branch-hash\n                #:recorder-run-branch\n                #:recorder-run-work-branch\n                #:abstract-run\n                #:unchanged-run\n                #:override-commit-hash\n                #:recorder-run-batch\n                #:recorder-run\n                #:merge-base-failed-warning\n                #:recorder-run-merge-base\n                #:recorder-run-company)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:lparallel.promise\n                #:future)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-reviewer\n                #:reports-for-run\n                #:acceptable-report\n                #:acceptable-state\n                #:base-acceptable)\n  (:import-from #:util/lparallel\n                #:immediate-promise)\n  (:import-from #:local-time\n                #:timestamp-\n                #:timestamp<)\n  (:import-from #:alexandria\n                #:when-let*\n                #:when-let)\n  (:import-from #:anaphora\n                #:it)\n  (:import-from #:screenshotbot/model/failed-run\n                #:run-failed-on-commit-p)\n  (:import-from #:util/logger\n                #:format-log)\n  (:import-from #:util/threading\n                #:scheduled-future)\n  (:import-from #:screenshotbot/model/finalized-commit\n                #:commit-finalized-p)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/simple-queue\n                #:make-queue\n                #:enqueue-with-max-length\n                #:queue-items)\n  (:import-from #:util/fset\n                #:do-reverse-set)\n  (:import-from #:screenshotbot/model/pr-rollout-rule\n                #:disable-pull-request-checks-p\n                #:pr-rollout-rule-for-company)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:warmup-report)\n  (:export\n   #:check\n   #:check-status\n   #:report\n   #:check-title\n   #:details-url\n   #:check-summary\n   #:pull-request-info\n   #:retrieve-run\n   #:send-task-args\n   #:run-retriever\n   #:valid-repo?\n   #:plugin-installed?\n   #:make-acceptable\n   #:pr-merge-base\n   #:format-updated-summary\n   #:abstract-pr-promoter\n   #:abstract-pr-acceptable\n   #:make-promoter-for-acceptable\n   #:promoter-pull-id\n   #:check-sha\n   #:push-remote-check-via-batching\n   #:+nothing-to-review+))\n\n(in-package :screenshotbot/abstract-pr-promoter)\n\n;; TODO: replace with defvar. This was just for upgrading from a list\n;; to a queue.\n(defparameter *logs* (make-queue)\n  \"All the messages we've sent out\")\n\n(defun print-logs (&key (substring \"\"))\n  (loop for log in (queue-items *logs*)\n        if (str:containsp substring (check-title (second log)))\n          do\n             (format t \"~a: ~a (at ~a)~%\"\n                     (recorder-run-company (first log))\n                     (check-title (second log))\n                     (created-at (first log)))))\n\n(defun total-summary-length ()\n  \"Compute the sum of the length of all summaries in *logs*.\"\n  (loop for log in (queue-items *logs*)\n        for check = (second log)\n        sum (length (check-summary check))))\n\n(defun format-updated-summary (state user)\n  (let ((summary\n          (str:downcase (string state))))\n    (when user\n      (setf summary (format nil \"~a by ~a\"\n                            summary\n                            (or\n                             (user-full-name user)\n                             (user-email user)))))))\n\n(defclass pull-request-info ()\n  ())\n\n(defclass check ()\n  ((status :initarg :status\n           :accessor check-status)\n   (user :initarg :user\n         :initform nil\n         :reader check-user\n         :documentation \"The user who initiated this check\")\n   (report :initarg :report\n           :initform nil\n           :accessor report)\n   (key :initarg :key\n        :initform (error \"must provide :key for check\")\n        :accessor check-key)\n   (title :initarg :title\n          :accessor check-title)\n   (details-url :initarg :details-url\n                :initform nil\n                :accessor details-url)\n   (summary :initarg :summary\n            :initform \"NA\"\n            :accessor check-summary)\n   (sha :initarg :sha\n        :initform (error \"must provide :sha for check\")\n        :accessor check-sha)))\n\n(defclass run-retriever ()\n  ((sleep-time :initarg :sleep-time\n               :initform 30\n               :reader sleep-time)))\n\n\n(defun find-last-green-run (channel commit &key (depth 100))\n  (when commit\n   (or\n    (production-run-for channel :commit commit)\n    (when (> depth 0)\n      (when-let* ((repo (channel-repo channel))\n                  (parent-commit (get-parent-commit repo commit)))\n        (find-last-green-run\n         channel\n         parent-commit\n         :depth (1- depth)))))))\n\n(defmethod retrieve-run ((retriever run-retriever)\n                         channel\n                         base-commit\n                         logger)\n  (labels ((failover ()\n             (immediate-promise (find-last-green-run channel base-commit)))\n           (produce (base-commit retries)\n             (assert (not (channel-deleted-p channel)))\n             (anaphora:acond\n               ((null base-commit)\n                (immediate-promise nil))\n               ((production-run-for channel :commit base-commit)\n                (immediate-promise it))\n               ((or\n                 (commit-finalized-p (channel-company channel) base-commit)\n                 (run-failed-on-commit-p channel base-commit))\n                (failover))\n               ((>= retries 0)\n                (format-log logger :info \"Waiting ~as before checking again for ~a\"\n                            (sleep-time retriever)\n                            base-commit)\n                (scheduled-future ((sleep-time retriever))\n                  (lparallel:chain (produce base-commit (1- retries)))))\n               (t\n                (failover)))))\n    (produce base-commit 10)))\n\n(defclass abstract-pr-acceptable (base-acceptable)\n  ()\n  (:metaclass persistent-class))\n\n(defclass abstract-pr-promoter (promoter)\n  ((pull-request-info :accessor pull-request-info\n                      :initarg :pull-request-info\n                      :initform (make-instance 'pull-request-info))\n   (run-retriever :accessor run-retriever\n                  :initarg :run-retriever\n                  :initform (make-instance 'run-retriever))\n   (send-task-args :accessor send-task-args\n                   :initform nil)\n   (%pr-merge-base-cache :accessor %pr-merge-base-cache\n                         :initform nil)))\n\n(defgeneric valid-repo? (promoter repo)\n  (:method (promoter repo)\n    nil))\n\n(defgeneric plugin-installed? (promoter company repo-url))\n\n(defgeneric make-acceptable (promoter report &rest args))\n\n(defmethod %compute-pr-merge-base-from-graph ((promoter abstract-pr-promoter)\n                                              (run recorder-run))\n  \"We currently use the merge base computed on the client and sent via\nAPI. We're eventually going to replace it with this.\"\n  (util:or-setf\n   (slot-value promoter '%pr-merge-base-cache)\n   (when-let* ((channel (recorder-run-channel run))\n               (repo (channel-repo channel))\n               (master-commit (recorder-run-branch-hash run))\n               (this-commit\n                ;; We intentionally don't use ACTUAL-SHA here. If this\n                ;; commit was merged into the main branch just before\n                ;; generating the screenshots, then we must use the\n                ;; non-overriden COMMIT to compare the merge base.\n                (recorder-run-commit run)))\n     (let* ((dag (commit-graph-dag (commit-graph repo)))\n            (active-commits (channel-active-commits channel))\n            ;; Filter out commits that aren't in the DAG to avoid warnings\n            (active-commits-in-dag (remove-if-not\n                                    (lambda (commit)\n                                      (dag:get-commit dag commit))\n                                    active-commits)))\n       (compute-merge-base repo\n                           (list*\n                            master-commit\n                            active-commits-in-dag)\n                           this-commit)))))\n\n(defmethod pr-merge-base ((promoter abstract-pr-promoter)\n                          (run recorder-run))\n  (let ((computed\n          (%compute-pr-merge-base-from-graph promoter run)))\n    (push-event :computed-merge-base\n                :run (oid run)\n                :computed-value computed\n                :provided-value (recorder-run-merge-base run))\n    (or\n     computed\n     (call-next-method))))\n\n\n(defmethod pr-merge-base ((promoter abstract-pr-promoter) run)\n  (recorder-run-merge-base run))\n\n(defun make-details-url (&rest args)\n  (format nil\n          \"~a~a\"\n          (installation-domain (installation))\n          (apply #'hex:make-url args)))\n\n(defgeneric push-remote-check (promoter-or-acceptable\n                               run\n                               check)\n  (:documentation \"Push the CHECK to the corresponding run remotely \"))\n\n(defgeneric push-remote-check-via-batching (promoter\n                                            batch\n                                            run\n                                            check))\n\n(defmethod push-remote-check :around (promoter\n                                      (run abstract-run)\n                                      check)\n  (let ((batch (recorder-run-batch run)))\n    (cond\n      (batch\n       (push-remote-check-via-batching promoter batch run check))\n      (t\n       (call-next-method)))))\n\n(defmethod push-remote-check :before (promoter run check)\n  (enqueue-with-max-length (list run check) *logs* :max-length 1000))\n\n(defgeneric make-promoter-for-acceptable (acceptable))\n\n(defmethod (setf acceptable-state) :after (state (self abstract-pr-acceptable)\n                                           &key user)\n  (push-remote-check\n   (make-promoter-for-acceptable self)\n   (report-run (acceptable-report self))\n   (make-check-for-report\n    (acceptable-report self)\n    :status state\n    :user user)))\n\n(defmethod actual-sha ((run abstract-run))\n  (or\n   (override-commit-hash run)\n   (recorder-run-commit run)))\n\n(defmethod make-check ((run abstract-run) &rest args)\n  (apply #'make-instance 'check\n         :sha (actual-sha run)\n         :key (channel-name (recorder-run-channel run))\n         args))\n\n\n(defmethod run-details-url ((run recorder-run))\n  (make-details-url 'screenshotbot/dashboard/run-page:run-page\n                    :id (oid run)))\n\n(defmethod run-details-url ((run unchanged-run))\n  (make-details-url \"/unchanged-runs/:id\" :id (bknr.datastore:store-object-id run)))\n\n(defun make-run-check (run &rest args)\n  \"Creates a check that points directly to the run\"\n  (apply #'make-check run\n         :details-url (run-details-url run)\n         args))\n\n(defmethod unreviewable-run-p (promoter (run recorder-run))\n  \"Check if the run is running on a main branch, or some other flow\nwhich is not reviewable but where we still need to send check\nresults. (In particular, on GitHub merge-queue runs are also not\nreviewable.)\"\n  (and (recorder-run-branch run)\n       (equal (recorder-run-work-branch run)\n              (recorder-run-branch run))))\n\n(defmethod unreviewable-run-p ((promoter t) (run unchanged-run))\n  t)\n\n(defparameter +nothing-to-review+ \"Nothing to review\"\n  \"This is used by batch-promoter to change the final output by a bit.\")\n\n(defmethod maybe-promote ((promoter abstract-pr-promoter)\n                          run)\n  (let* ((repo (channel-repo (recorder-run-channel run)))\n         (repo-url (repo-link repo))\n         (company (recorder-run-company run)))\n    (cond\n      ((not (valid-repo? promoter repo))\n       (format-log run :info \"Not a valid repo for this promoter\"))\n      ((not (plugin-installed? promoter company repo-url))\n       (format-log run :info \"Plugin is not installed for this company/repository\"))\n      ((unreviewable-run-p promoter run)\n       ;; TODO(T1096): do we have to worry about overwriting a\n       ;; previously reviewable report? This might happen if a commit\n       ;; was part of a PR, and then merged into the main branch, and\n       ;; the build was run both times. Perhaps in this case we should\n       ;; keep a track of history of notifications?\n       (format-log run :info \"This run is not expected to be reviewed, will probably be handled by the master-promoter\")\n       (push-remote-check\n        promoter\n        run\n        (make-run-check run :status :success\n                            :title +nothing-to-review+\n                            :summary \"NA\")))\n      ((not (pr-merge-base promoter run))\n       (unless (recorder-run-branch-hash run)\n         ;; This is likely because there's a bug in the logic of how\n         ;; we computed the main branch hash. See T2274 for an example\n         (warn \"[BAD!] Main branch was not provided, and we could not compute the merge base\"))\n       (format-log run :info \"No merge base on run, this is probably a bug on the CI job (Usually missing a `git fetch origin main`\"))\n      ((equal (pr-merge-base promoter run)\n              (actual-sha run))\n       (format-log run :info \"Ignoring since the merge-base is same as the commit-hash\"))\n      ((disable-pull-request-checks-p (pr-rollout-rule-for-company company) run)\n       (push-event :rollout-blocked-pr-request :run (oid run))\n       (format-log run :info \"A rollout rule is preventing us from sending notifications\"))\n      (t\n       (format-log run :info \"Base commit is: ~S\" (pr-merge-base promoter run))\n       (let ((base-run-promise (retrieve-run\n                                (run-retriever promoter)\n                                (recorder-run-channel run)\n                                (pr-merge-base promoter run)\n                                ;; We're using the run as a logger!\n                                (identity run))))\n         (unless (lparallel:fulfilledp base-run-promise)\n           (format-log run :info \"Base commit is not available yet, waiting for upto 5 minutes\")\n           (push-remote-check\n            promoter\n            run\n            ;; TODO: this can use make-run-check? For a different diff.\n            (make-check run\n                        :status :pending\n                        :details-url (run-details-url run)\n                        :title (format nil \"Waiting for screenshots on ~a to be available\"\n                                       (str:substring 0 4 (pr-merge-base promoter run))))))\n         (let* ((base-run (lparallel:force base-run-promise))\n                (merge-base (pr-merge-base promoter run))\n                (check (cond\n                         ((null merge-base)\n                          (format-log run :info \"No base-commit provided in run\")\n                          (make-run-check\n                           run\n                           :status :failure\n                           :title \"Base SHA not available for comparison, please check CI setup\"\n                           :summary \"Screenshots unavailable for base commit, perhaps the build was red? Try rebasing.\"))\n                         (t\n                          (format-log run :info \"Base run is ~a, preparing notification from diff-report\" base-run)\n                          (when base-run\n                            (warn-if-not-merge-base promoter run base-run))\n                          (make-check-result-from-diff-report\n                           promoter\n                           run\n                           base-run)))))\n           (assert (not (channel-deleted-p (recorder-run-channel run))))\n           (push-remote-check promoter run check)))))))\n\n(defmethod warn-if-not-merge-base ((promoter abstract-pr-promoter)\n                                   (run recorder-run)\n                                   (base-run recorder-run))\n  \"If the base run we're using is not the merge-base, add a warning\"\n  (unless (equal (pr-merge-base promoter run)\n                 (recorder-run-commit base-run))\n    (push-run-warning run 'merge-base-failed-warning\n                      :compared-against base-run)))\n\n(defmethod warn-if-not-merge-base (promoter (run unchanged-run) base-run)\n  (values))\n\n(defgeneric promoter-pull-id (promoter run)\n  (:documentation \"Get a unique identifier identify the pull request for this run. This\nmight be a Pull Request URL, or a Merge Request IID, or a Phabricator\nRevision. It will be tested with EQUAL\"))\n\n(defun review-status (run)\n  (loop for report in (reports-for-run run)\n        for acceptable = (report-acceptable report)\n        if (and acceptable (acceptable-state acceptable)\n                (acceptable-reviewer acceptable))\n          return acceptable))\n\n(defmethod same-pull-request-p (promoter run previous-run)\n  \"Check if two runs are on the same Pull Request. \"\n  (flet ((emptyp (id)\n           (or\n            (not id)\n            (equal \"\" id))))\n   (let ((pull-id (promoter-pull-id promoter run))\n         (previous-pull-id (promoter-pull-id promoter previous-run)))\n     (cond\n       ((and (not (emptyp pull-id))\n             (not (emptyp previous-pull-id)))\n        (equal previous-pull-id pull-id))\n       (t\n        ;; We didn't have the Pull Request URL on one of the runs. This\n        ;; could be because the CI started the job before the Pull\n        ;; Request was created, but it could also be a complex CI job\n        ;; where the Pull Request information was not able to be\n        ;; extracted. For now, we're matching by branch-name in that\n        ;; case.  There's a chance that branch name might overlap\n        ;; across multiple PRs, but for that to actually be an issue we\n        ;; also have to have the screenshots overlapping across those\n        ;; two PRs. And even then that's only when the PR URL is not\n        ;; available.\n        (let ((branch1 (recorder-run-work-branch run))\n              (branch2 (recorder-run-work-branch previous-run)))\n          (unless (or\n                   (str:emptyp branch1)\n                   (str:emptyp branch2))\n            (equal branch1 branch2))))))))\n\n(defun %find-reusable-acceptable (promoter run previous-run)\n  \"Find an acceptable from the previous-run that can be re-used for this\nrun. If the previous-run or any of its acceptables cannot be re-used,\nwe return NIL.\"\n  (flet ((p (x message)\n           (format-log run :info \"For ~a: ~a: Got ~a\" previous-run message x)\n           x))\n    (and\n     (not (eql previous-run run))\n     (timestamp< (created-at previous-run)\n                 (created-at run))\n     (same-pull-request-p promoter run previous-run)\n     (p (diff-report-empty-p\n         (make-diff-report run previous-run))\n        \"Check if diff-report is empty\")\n     (p\n      (review-status previous-run)\n      \"Check if report is accepted or rejected\"))))\n\n(defmethod previous-review (promoter run)\n  (let ((pull-id (promoter-pull-id promoter run)))\n    (format-log run :info \"Looking for previous reports on ~a\" pull-id)\n    (let ((cut-off (timestamp- (local-time:now) 30 :day))\n          (channel (recorder-run-channel run)))\n      (do-reverse-set (previous-run (runs-for-channel channel))\n        (cond\n          ((local-time:timestamp> cut-off (created-at previous-run))\n           (return nil))\n          (t\n           (let ((acceptable (%find-reusable-acceptable promoter run previous-run)))\n             (when acceptable\n               (return acceptable)))))))))\n\n(defmethod make-check-result-from-diff-report (promoter (run unchanged-run)\n                                           base-run)\n  ;; TODO: technically it might be possible to generate a report at\n  ;; this point, because screenshots *might* have changed from the\n  ;; base-run.  But for now, we're assuming that if we're passing an\n  ;; unchanged-run, then, the user has determined that there are no\n  ;; changes from the base.\n  (make-check run\n              :status :success\n              :title \"No screenshots changed\"\n              :summary \"No action required on your part\"\n              :details-url (run-details-url run)))\n\n(defmethod make-check-result-from-diff-report (promoter run base-run)\n  (let ((diff-report (make-diff-report run base-run)))\n   (cond\n     ((diff-report-empty-p diff-report)\n      (make-check run\n                  :status :success\n                  :title \"No screenshots changed\"\n                  :summary \"No action required on your part\"\n                  :details-url\n                  (make-details-url 'screenshotbot/dashboard/run-page:run-page\n                                    :id (oid run))))\n     (t\n      (let ((report (make-instance 'report\n                                   :run run\n                                   :previous-run base-run\n                                   :channel (when run (recorder-run-channel run))\n                                   :title  (diff-report-title diff-report))))\n        (warmup-report report)\n        (flet ((setup-acceptable (&rest args)\n                 (with-transaction ()\n                   (setf (report-acceptable report)\n                         (apply #'make-acceptable promoter report\n                                args)))))\n          (trivia:match (previous-review promoter run)\n           (nil\n            (format-log run :info \"Did not find any previous reviewed reports\")\n            (setup-acceptable)\n            (make-check-for-report\n             report\n             :status :action-required\n             :summary \"Please verify that the images look reasonable to you\"))\n           (acceptable\n            (format-log run :info \"Found a previous review: ~a\" acceptable)\n            (setup-acceptable\n             :state (acceptable-state acceptable)\n             :user (acceptable-reviewer acceptable))\n            (make-check-for-report\n             report\n             :status (acceptable-state acceptable)\n             :user (acceptable-reviewer acceptable)\n             :previousp t\n             :summary \"An identical run was previously reviewed on this Pull Request\")))))))))\n\n(defun format-check-title (title &key user previousp status)\n  (cond\n    (user\n     (format nil \"~a, ~a~a\"\n             title\n             (if previousp \"previously \" \"\")\n             (format-updated-summary status user)))\n    (t\n     title)))\n\n(defun make-check-for-report (report &key status (summary \"\") user\n                                       previousp)\n  (let* ((title (diff-report-title (make-diff-report\n                                    (report-run report)\n                                    (report-previous-run report))))\n         (title (format-check-title\n                 title\n                 :user user\n                 :previousp previousp\n                 :status status)))\n   (make-check (report-run report)\n               :status status\n               :user user\n               :report report\n               :title title\n               :summary summary\n               :details-url (make-details-url 'screenshotbot/dashboard/reports:report-page\n                                              :id (oid report)))))\n\n(defmethod maybe-send-tasks ((promoter abstract-pr-promoter) run)\n  (values))\n\n(defmethod maybe-promote :around ((promoter abstract-pr-promoter) (run unchanged-run))\n  (log:info \"calling promotion for unchanged-run\")\n  (call-next-method))\n"
  },
  {
    "path": "src/screenshotbot/admin/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/admin/core\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/user\n        #:screenshotbot/user-api\n        #:screenshotbot/template)\n  (:export\n   #:defadminhandler\n   #:register-admin-menu\n   #:*index*\n   #:admin-app-template)\n  (:import-from\n   #:screenshotbot/server\n   #:defhandler\n   #:with-login)\n  (:use-reexport\n   #:nibble\n   #:markup\n   #:screenshotbot/template\n   #:screenshotbot/server\n   #:bknr.datastore\n   #:screenshotbot/ui))\n(in-package :screenshotbot/admin/core)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *index* nil)\n\n(defmacro register-admin-menu (title destination)\n  `(setf (assoc-value *index* ,title :test #'equal)\n         ,destination))\n\n(defmacro defadminhandler ((&rest args) params &body body)\n  `(defhandler ,args ,params\n     (with-login ()\n      (cond\n        ((not (and (current-user) (adminp (current-user))))\n         <app-template>\n         Please login as an admin to view this page\n         </app-template>)\n        (t\n         (assert (adminp (current-user)))\n         ,@body)))))\n\n(deftag admin-app-template (children)\n  <app-template admin=t jquery-ui=t >\n  <script src= \"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js\" />\n  ,@children\n  </app-template>)\n"
  },
  {
    "path": "src/screenshotbot/admin/index.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/admin/index\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/admin/core\n        #:screenshotbot/user-api\n        #:screenshotbot/model/user)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page))\n(in-package :screenshotbot/admin/index)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(defadminhandler (nil :uri \"/admin\") ()\n  (let ((reload-nibble (nibble (:once t)\n                         (confirmation-page\n                          :yes (nibble ()\n                                 (admin-reload))\n                          :no \"/admin\"\n                          \"Danger: Are you sure you want to restart the server?\"))))\n    <app-template>\n      <h1>Admin panel (Internal use only)</h1>\n      <ul>\n\n        ,@ (loop for (title . destination) in (reverse *index*)\n                 collect\n                 <li>\n                   <a href= (hex:make-url destination)>,(progn title)</a>\n                 </li>)\n      <li>\n    <a href= reload-nibble\n       >Reload</a>\n      </li>\n\n    <li>\n    <a href= (nibble () (do-snapshot)) >Create Datastore Snapshot </a>\n    </li>\n    </ul>\n  </app-template>)\n)\n\n\n(defun do-snapshot ()\n  (bknr.datastore:snapshot)\n  (hex:safe-redirect \"/admin\"))\n\n(defun admin-reload ()\n  (snapshot)\n  (asdf:load-system :screenshotbot)\n  (snapshot)\n  (hex:safe-redirect \"/admin\"))\n"
  },
  {
    "path": "src/screenshotbot/admin/test-writes.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/admin/test-writes\n  (:use #:cl)\n  (:import-from #:screenshotbot/admin/core\n                #:register-admin-menu\n                #:admin-app-template\n                #:defadminhandler)\n  (:import-from #:bknr.datastore\n                #:deftransaction)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool))\n(in-package :screenshotbot/admin/test-writes)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(deftransaction tx-noop (msg)\n  (declare (ignore msg)))\n\n(defvar *pool*\n  (make-instance 'max-pool :max 100))\n\n(defun do-test (&key (count 1) (profile nil) (parallel nil))\n\n  (let ((output (with-output-to-string (*trace-output*)\n                  (flet ((inner-work ()\n                           (let ((res (loop for i below count\n                                            collect\n                                               (cond\n                                                 (parallel\n                                                  (let ((promise (lparallel:promise)))\n                                                    (make-thread\n                                                     (lambda ()\n                                                       (lparallel:fulfill\n                                                           promise\n                                                         (tx-noop \"message\")))\n                                                     :pool *pool*)\n                                                    promise))\n                                                 (t\n                                                  (tx-noop \"message\"))))))\n                             (cond\n                               (parallel\n                                (log:info \"All futures: ~S\" res)\n                                (time\n                                 (loop for promise in res\n                                       do (lparallel:force promise))))\n                               (t\n                                res)))))\n                    (cond\n                      (profile\n                       (#+lispworks hcl:profile #-lispworks time\n                          (inner-work)))\n                      (t\n                       (time\n                        (inner-work))))))) )\n    <html>\n      <body>\n        Success. <a href= \"/admin/test-writes\">Go Back.</a>\n\n        <pre>,(progn output)</pre>\n      </body>\n    </html>))\n\n(defadminhandler (test-writes :uri \"/admin/test-writes\") ()\n  <admin-app-template>\n    <form action= (nibble ()  (do-test) ) >\n      <input type= \"submit\" />\n    </form>\n\n    <form action= (nibble ()  (do-test :count 100) ) >\n      <input type= \"submit\" value= \"Benchmark 100 times\" />\n    </form>\n\n    <form action= (nibble ()  (do-test :count 1000 :profile t) ) >\n      <input type= \"submit\" value= \"Profile 1000 times\" />\n    </form>\n\n    <form action= (nibble ()  (do-test :count 1000 :parallel t) ) >\n      <input type= \"submit\" value= \"Benchmark 1000 times in parallel\" />\n    </form>\n  </admin-app-template>)\n\n(register-admin-menu \"Test Writes\" 'test-writes)\n"
  },
  {
    "path": "src/screenshotbot/analytics.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/analytics\n    (:use #:cl\n          #:alexandria\n          #:iterate)\n  (:import-from #:screenshotbot/ignore-and-log-errors\n                #:ignore-and-log-errors)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/store\n                #:object-store)\n  (:import-from #:screenshotbot/events\n                #:event-engine\n                #:safe-installation\n                #:insert-multiple-items\n                #:db-engine\n                #:with-db)\n  (:import-from #:util/misc/lists\n                #:with-batches)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  (:export #:push-analytics-event\n           #:analytics-event-ts\n           #:analytics-event-script-name\n           #:map-analytics-events))\n(in-package :screenshotbot/analytics)\n\n(defvar *events* nil)\n\n(clsql:def-view-class analytics-event ()\n  ((ip-address\n    :initform nil\n    :initarg :ip-address)\n   (session\n    :initarg :session\n    :initform nil\n    :accessor event-session)\n   (script-name\n    :initarg :script-name\n    :initform nil\n    :reader analytics-event-script-name)\n   (query-string\n    :initarg :query-string\n    :initform nil)\n   (ts :initform (local-time:now)\n       :initarg :ts\n       :accessor analytics-event-ts)\n   (cli-session-id :initarg :cli-session-id\n                   :accessor cli-session-id)\n   (referrer :initarg :referrer)\n   (user-agent :initarg :user-agent))\n  (:base-table \"analytics\"))\n\n(defun write-analytics-events ()\n  ;; if we enter the debugger with the lock, then the website will be\n  ;; down. So let's always, forcefully never enter the debugger.\n  (ignore-and-log-errors ()\n    (%write-analytics-events)))\n\n\n(defun make-digest (str)\n  (ironclad:byte-array-to-hex-string\n   (ironclad:digest-sequence\n    :sha256 (flexi-streams:string-to-octets str))))\n\n(defun hash-session-id (ev)\n  (when (stringp (event-session ev))\n    (setf (event-session ev)\n          (make-digest (event-session ev)))))\n\n(defmethod write-analytics-events-to-engine ((engine null) events))\n\n(defmethod write-analytics-events-to-engine ((engine db-engine) events)\n  (with-db (db engine)\n    (let ((hostname\n            (uiop:hostname))\n          (domain\n            (?. installation-domain (safe-installation))))\n     (with-batches (events events)\n       (insert-multiple-items db \"analytics\" events\n                              '(\"ip_address\" \"session\" \"script_name\"\n                                \"referrer\"\n                                \"user_agent\"\n                                \"query_string\" \"ts\"\n                                \"hostname\"\n                                \"domain\"\n                                \"cli_session_id\")\n                              (lambda (event)\n                                (list\n                                 (slot-value event 'ip-address)\n                                 (event-session event)\n                                 (analytics-event-script-name event)\n                                 (ignore-errors\n                                  (slot-value event 'referrer))\n                                 (ignore-errors\n                                  (slot-value event 'user-agent))\n                                 (slot-value event 'query-string)\n                                 (analytics-event-ts event)\n                                 hostname\n                                 domain\n                                 (ignore-errors\n                                  (cli-session-id event)))))))))\n\n(defun %write-analytics-events ()\n  (let ((old-events (util/atomics:atomic-exchange *events* nil)))\n    (write-analytics-events-to-engine (event-engine (safe-installation))\n                                      old-events)))\n\n(defun all-analytics-events ()\n  (map-analytics-events #'identity))\n\n(defun ensure-local-time-ts (ev)\n  (when (typep (analytics-event-ts ev) 'string)\n    (setf (analytics-event-ts ev)\n          (local-time:parse-timestring (str:replace-all \" \" \"T\" (analytics-event-ts ev)))))\n  ev)\n\n(defun map-analytics-events (function &key (keep-if (lambda (x) (declare (ignore x)) t))\n                                        (limit 10000))\n  (when-let ((engine (event-engine (safe-installation))))\n    (let ((res\n            (flet ((call-on (events)\n                     (loop for ev in events\n                           while (> limit 0)\n                           if (funcall keep-if ev)\n                             collect (progn\n                                       (decf limit)\n                                       (funcall function (ensure-local-time-ts ev))))))\n              (append\n               (call-on *events*)\n               (call-on\n                (with-db (db engine)\n                  (clsql:select 'analytics-event :database db\n                                :where (clsql:sql-operation\n                                        '= (clsql:sql-expression :attribute \"domain\")\n                                        (installation-domain (safe-installation)))\n                    :order-by (list\n                               (make-instance\n                                'clsql:sql :string \"ts desc\"))\n                    :limit 20000\n                    :flatp t)))))))\n      res)))\n\n\n(defun push-analytics-event ()\n  (let ((ev (make-instance 'analytics-event\n                            :ip-address (hunchentoot:real-remote-addr)\n                            :user-agent (hunchentoot:user-agent)\n                            :session\n                            (cond\n                              ((auth:session-created-p (auth:current-session))\n                               (make-digest (car (auth:session-key (auth:current-session)))))\n                              (t\n                               \"no-session\"))\n                            :referrer (hunchentoot:referer)\n                            :cli-session-id (hunchentoot:header-in* :x-cli-session-id)\n                            :script-name (hunchentoot:script-name hunchentoot:*request*)\n                            :query-string (hunchentoot:query-string*))))\n    (atomics:atomic-push ev *events*)))\n\n(def-cron write-analytics-events (:step-min 10 :only-on-leader nil)\n  (write-analytics-events))\n\n(defmethod cleanup-old-analytics (engine)\n  nil)\n\n(defmethod cleanup-old-analytics ((engine db-engine))\n  (with-db (db engine)\n    (clsql:execute-command \"delete from analytics where ts < date_sub(now(), interval 1 month);\"\n                           :database db)))\n\n(def-cron cleanup-old-analytics (:minute 30 :hour 7)\n  (cleanup-old-analytics (event-engine (safe-installation))))\n"
  },
  {
    "path": "src/screenshotbot/android/activity.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :screenshotbot/android/activity\n  (:use #:cl)\n  (:import-from :screenshotbot/android/api\n                :context-delegate)\n  (:import-from #:screenshotbot/android/view\n                #:add-view\n                #:delegate\n                #:view)\n  (:import-from #:screenshotbot/android/text-view\n                #:text-view)\n  (:import-from #:screenshotbot/android/edit-text\n                #:edit-text)\n  (:import-from #:screenshotbot/android/linear-layout\n                #:set-orientation\n                #:linear-layout)\n  (:export\n   #:set-current-activity))\n(in-package :screenshotbot/android/activity)\n\n(defvar *current-activity*)\n\n(defclass activity ()\n  ((delegate :initarg :delegate\n             :reader delegate\n             :reader context-delegate)))\n\n(lw-ji:define-java-callers \"android.app.Activity\"\n  (%set-content-view \"setContentView\"))\n\n(defmethod set-content-view ((self activity)\n                             (view view))\n  (%set-content-view\n   (delegate self)\n   (delegate view)))\n\n(defun set-current-activity (activity)\n  (setf *current-activity* (make-instance 'activity :delegate activity))\n  nil)\n\n(defun simple-test ()\n  (hcl:android-funcall-in-main-thread\n   (lambda ()\n     (let ((view1 (make-instance 'text-view :context *current-activity*\n                                            :text \"foo\"))\n           (view2 (make-instance 'text-view :context *current-activity*\n                                            :text \"bar\"))\n           (username (make-instance 'edit-text :context *current-activity*\n                                    :text \"carbar\"))\n           (linear-layout (make-instance 'linear-layout :context *current-activity*)))\n       (set-orientation linear-layout :vertical)\n       (add-view linear-layout view1)\n       (add-view linear-layout view2)\n       (add-view linear-layout username)\n      (set-content-view *current-activity* linear-layout)))))\n\n;; (simple-test)\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/android/api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/android/api\n  (:use #:cl))\n(in-package :screenshotbot/android/api)\n\n(defgeneric context-delegate (context))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/android/edit-text.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/android/edit-text\n  (:use #:cl)\n  (:import-from #:screenshotbot/android/text-view\n                #:text-view)\n  (:import-from #:screenshotbot/android/view\n                #:def-view))\n(in-package :screenshotbot/android/edit-text)\n\n(def-view edit-text (text-view) \"android.widget.EditText\"\n  ())\n\n"
  },
  {
    "path": "src/screenshotbot/android/linear-layout.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/android/linear-layout\n  (:use #:cl)\n  (:import-from #:screenshotbot/android/view\n                #:delegate\n                #:view-group\n                #:def-view))\n(in-package :screenshotbot/android/linear-layout)\n\n\n(def-view linear-layout (view-group) \"android.widget.LinearLayout\"\n  ())\n\n(lw-ji:define-java-callers \"android.widget.LinearLayout\"\n  (%set-orientation \"setOrientation\"))\n\n(defmethod set-orientation ((self linear-layout)\n                            orientation)\n  (%set-orientation\n   (delegate self)\n   (ecase orientation\n     (:horizontal\n      0)\n     (:vertical\n      1))))\n"
  },
  {
    "path": "src/screenshotbot/android/screenshotbot.android.asd",
    "content": "(defsystem :screenshotbot.android\n  :serial t\n  :components ((:file \"api\")\n               (:file \"view\")\n               (:file \"text-view\")\n               (:file \"edit-text\")\n               (:file \"linear-layout\")\n               (:file \"activity\")))\n"
  },
  {
    "path": "src/screenshotbot/android/text-view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/android/text-view\n  (:use #:cl)\n  (:import-from #:screenshotbot/android/api\n                #:context-delegate)\n  (:import-from #:screenshotbot/android/view\n                #:def-view\n                #:view\n                #:delegate))\n(in-package :screenshotbot/android/text-view)\n\n(def-view text-view (view) \"android.widget.TextView\"\n  ())\n\n(lw-ji:define-java-callers \"android.widget.TextView\"\n  (%set-text \"setText\"))\n\n(defmethod initialize-instance :after ((self text-view) &key context text &allow-other-keys)\n  (when text\n    (set-text self text)))\n\n(defmethod set-text ((self text-view) text)\n  (%set-text (delegate self)\n             text))\n\n;;(%new-text-view (context-delegate screenshotbot/android/activity::*current-activity*))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/android/view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/android/view\n  (:use #:cl)\n  (:import-from #:screenshotbot/android/api\n                #:context-delegate)\n  (:export\n   #:def-view))\n(in-package :screenshotbot/android/view)\n\n(defclass view ()\n  ((delegate :initarg :delegate\n             :accessor delegate)\n   (context :initarg :context)\n   (constructor :initarg :constructor\n                :reader java-constructor)))\n\n(defmacro def-view (name parents java-class-name\n                    slots)\n  (let ((constructor (intern (format nil \"%MAKE-~a\" name))))\n    `(progn\n       (lw-ji:define-java-constructor ,constructor\n         ,java-class-name)\n       (defclass ,name ,parents\n         ,slots\n         (:default-initargs :constructor ',constructor)))))\n\n(defmethod initialize-instance :after ((self view) &key context)\n  (setf (delegate self)\n        (funcall (java-constructor self) (context-delegate context))))\n\n(def-view view-group (view) \"android.view.ViewGroup\"\n  ())\n\n(lw-ji:define-java-callers \"android.view.ViewGroup\"\n  (%add-view \"addView\"))\n\n(defmethod add-view ((self view-group)\n                     (view view))\n  (%add-view (delegate self) (delegate view)))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/CHANGELOG.md",
    "content": "\nVersion\n\n* 21: /api/commit-graph/check-wants now accepts POST (for large SHA lists). See T2237.\n* 20: Added comparePixelTolerance to runs\n* Version unchanged\n  - Added \"features\" to /api/version for server side control of features\n* 19: Added /api/commit-graph/check-wants API\n* 18: Added api support for commit refs\n  - New model git-ref\n  - POST /api/commit-graph/refs\n  - Added support for `refs` param in POST /api/commit-graph \n* Version unchanged\n  - Added /api/report/<id>/comparison\n  - Added fields changes,added,deleted to dto:comparison\n* 17: Added isReleaseBranch to run model. In older versions, prefer to set mainBranch to workBranch on the client side.\n* 16: Added metadata and run-metadata.\n* 15: shard-spec was added to the API\n* 14: When an API request fails, we set error code to 400 or 500\n  (Previously, we would return 200 with a JSON body).\n* 13: Added GET /api/run/:oid, and url field to screnshot\n* 12: Added /api/analytics-event\n* 11: Add batch model and POST /api/batch\n* 10: Add author field to run\n* 9: Introduced the installation URL in version\n* 8: Add `tags` to recorder-run\n* 7: POST /api/finalized-commit added\n* 6: POST /api/unchanged-run added\n* 5: PUT /api/image/blob now requires authentication\n* 4: Added PUT for /api/run\n* 3: added /api/failed-run\n"
  },
  {
    "path": "src/screenshotbot/api/active-runs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/active-runs\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:run-to-dto)\n  (:import-from #:screenshotbot/model/company\n                #:find-channel)\n  (:import-from #:screenshotbot/model/channel\n                #:all-active-runs)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/active-runs)\n\n(defapi (%active-runs :uri \"/api/runs/active\" :method :get\n                      :wrap-success nil)\n        (channel)\n  \"Find all the active runs for a given channel\"\n  (let ((channel (find-channel (auth:current-company) channel)))\n    (or\n     (when channel\n       (loop for (nil . run) in (all-active-runs channel)\n             if (auth:can-viewer-view (auth:viewer-context hunchentoot:*request*)\n                                      run)\n               collect (run-to-dto run)))\n     (make-array 0))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/analytics-event.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/analytics-event\n  (:use #:cl)\n  (:import-from #:hunchentoot\n                #:raw-post-data)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:ip-throttler))\n(in-package :screenshotbot/api/analytics-event)\n\n(defvar *ip-throttler* (make-instance 'ip-throttler\n                                      :tokens 3600))\n\n(defhandler (nil :uri \"/api/analytics-event\" :method :post) ()\n  (parse-all-events (hunchentoot:raw-post-data :force-text t)))\n\n(defun parse-all-events (content)\n  (let ((body (yason:parse content)))\n    (loop for ev in body do\n      (push-event-from-json ev))))\n\n(defun push-event-from-json (ev)\n  (throttle! *ip-throttler*)\n  (apply\n   #'push-event\n   :web-event\n   :ip-address (hunchentoot:real-remote-addr)\n   (loop for key being the hash-keys of ev\n         using (hash-value val)\n         if (atom val)\n           appending (list key val))))\n"
  },
  {
    "path": "src/screenshotbot/api/api-key.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/api-key\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:*api-key*\n                #:defapi)\n  (:import-from #:screenshotbot/model/api-key\n                #:expires-at\n                #:api-key-description\n                #:cli-api-key))\n(in-package :screenshotbot/api/api-key)\n\n(defapi (register-cli-api-key :uri \"/api/finalize-cli\" :method :post) (hostname)\n  \"Finalized a CLI-API-KEY, i.e. it marks it as a key in use, that never expires\"\n  (cond\n    ((and\n      *api-key*\n      (typep *api-key* 'cli-api-key))\n     (setf (expires-at *api-key*) nil)\n     (when (and\n            (str:emptyp (api-key-description *api-key*))\n            (not (str:emptyp hostname)))\n       (setf (api-key-description *api-key*)\n             (format nil \"CLI use on ~a\" hostname)))\n     \"OK\")\n    (t\n     (error \"This API key has expired, try again\"))))\n"
  },
  {
    "path": "src/screenshotbot/api/batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/batch\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-item-report\n                #:batch-items\n                #:batch-commit\n                #:batch-name\n                #:find-or-create-batch\n                #:batch)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-repo-url)\n  (:import-from #:util/store/object-id\n                #:find-by-oid)\n  (:import-from #:screenshotbot/model/report\n                #:report-to-dto)\n  (:import-from #:screenshotbot/api/doc\n                #:def-api-doc)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/batch)\n\n(define-condition validation-error (error)\n  ((message :initarg :message))\n  (:report (lambda (e out)\n            (format out (slot-value e 'message)))))\n\n(defmethod validate-dto ((batch dto:batch))\n  (flet ((verify (test message &rest args)\n           (unless test\n             (error 'validation-error\n                    :message\n                    (apply #'format nil message args)))))\n    (verify (< (length (dto:batch-name batch)) 256)\n            \"Batch name too long\")\n    (verify (eql (length (dto:batch-commit batch)) 40)\n            \"Commit should be exactly 40 characters long\")\n    (verify (< (length (dto:batch-repo batch)) 256)\n            \"Batch repo too long\")\n    (verify (< (length (dto:pull-request-url batch)) 256)\n            \"Pull request URL too long\")\n    (verify (or (null (dto:phabricator-diff-id batch))\n                (numberp (dto:phabricator-diff-id batch)))\n            \"Phabricator diff id must be a number\")))\n\n(defapi (post-batch :uri \"/api/batch\" :method :post :use-yason t) ()\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (let ((dto (json-mop:json-to-clos body 'dto:batch)))\n      (batch-to-dto\n       (make-batch-from-dto dto (auth:current-company))))))\n\n(defun make-batch-from-dto (dto company)\n  (validate-dto dto)\n  (find-or-create-batch\n   :company company\n   :repo (dto:batch-repo dto)\n   :commit (dto:batch-commit dto)\n   :name (dto:batch-name dto)\n   :pull-request-url (dto:pull-request-url dto)\n   :phabricator-diff-id (dto:phabricator-diff-id dto)))\n\n(defun batch-to-dto (batch)\n  (make-instance 'dto:batch\n                 :id (store-object-id batch)\n                 :github-repo (recorder-run-repo-url batch)\n                 :name (batch-name batch)\n                 :commit (batch-commit batch)))\n\n(defapi (get-reports :uri \"/api/batch/:oid/reports\" :use-yason t :wrap-success nil) (oid)\n  (let ((batch (find-by-oid oid)))\n    (coerce\n     (loop for item in (fset:convert 'list (batch-items batch))\n           for report = (batch-item-report item)\n           if report\n             collect\n             (progn\n               (auth:can-view! report)\n               (report-to-dto report)))\n     'vector)))\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/build-info.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/build-info\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/build-info\n                #:find-or-create-build-info\n                #:find-build-info\n                #:build-info-repo-url\n                #:build-url\n                #:build-info)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/build-info)\n\n(defmethod to-dto ((self build-info))\n  (make-instance 'dto:build-info\n                 :build-url (build-url self)\n                 :repo-url (build-info-repo-url self)))\n\n(defun %parse-build-info-body ()\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (dto:decode-json body 'dto:build-info)))\n\n(defapi (%get-build-info :uri \"/api/build-info\" :method :get\n                         :use-yason t) (build-url)\n  (let ((build-info (find-build-info\n                     (auth:current-company)\n                     build-url)))\n    (when build-info\n      (to-dto build-info))))\n\n(defapi (%post-build-info :uri \"/api/build-info\" :method :put\n                          :use-yason t) ()\n  (let ((input (%parse-build-info-body)))\n    (let ((build-info (find-or-create-build-info\n                       (auth:current-company)\n                       (dto:build-info-build-url input))))\n      (setf (build-info-repo-url build-info)\n            (dto:build-info-repo-url input))\n      (to-dto build-info))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/cli-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/cli-log\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:defapi))\n(in-package :screenshotbot/api/cli-log)\n\n(defapi (nil :uri \"/api/cli-log\" :method :post) ()\n  (log:info \"CLI logs (session: ~a): ~a\"\n            (hunchentoot:header-in* :x-cli-session-id)\n            (hunchentoot:raw-post-data :force-text t))\n  nil)\n\n"
  },
  {
    "path": "src/screenshotbot/api/commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/api/commit-graph\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/api/core\n        #:screenshotbot/model/commit-graph\n        #:screenshotbot/user-api)\n  (:import-from #:util/events\n                #:push-event\n                #:with-tracing)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:commit-graph-refs\n                #:commit-graph-set-ref\n                #:merge-dag-into-commit-graph)\n  (:import-from #:serapeum\n                #:collecting)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/commit-graph)\n\n(defun %merge-dags (repo-url new-dag)\n  (log:info \"Updating commit graph for ~S and ~S\" (current-company) repo-url)\n  (with-tracing (:merge-dags :repo-url repo-url)\n    (let* ((commit-graph (find-or-create-commit-graph\n                          (current-company)\n                          repo-url)))\n      (merge-dag-into-commit-graph commit-graph\n                                   new-dag))))\n\n\n(auto-restart:with-auto-restart ()\n (defun %update-commit-graph-v2 (repo-url graph-json)\n   (%merge-dags repo-url (dag:read-from-stream (make-string-input-stream graph-json)))))\n\n(defun update-commit-graph-for-text (repo-url data)\n  (%merge-dags repo-url\n               (dag:read-from-stream\n                (make-string-input-stream data)\n                :format :text)))\n\n(defapi (update-commit-graph :uri \"/api/commit-graph\" :method :post) (repo-url graph-json format\n                                                                               refs)\n  ;; do nothing with this for the moment\n  (cond\n    ((string-equal \"text\" format)\n     (update-commit-graph-for-text repo-url\n                                   (hunchentoot:raw-post-data :want-stream nil\n                                                              :force-text t)))\n    (t\n     (%update-commit-graph-v2 repo-url graph-json)\n     \n     (cond\n       ((str:non-empty-string-p refs)\n        (log:info \"Using new commit graph api\")\n        (push-event :commit-graph-api-with-refs :company (format nil \"~a\" (auth:current-company)))\n        (update-refs :repo-url repo-url\n                     :refs refs))\n       (t\n        (push-event :commit-graph-old-flow :session-id (hunchentoot:header-in* :x-cli-session-id)\n                                           :branch (hunchentoot:parameter :branch)\n                                           :repo-url repo-url\n                    :refs refs\n                    :client-version (hunchentoot:header-in* :x-client-version))))\n     \"OK\")))\n\n(defapi (get-refs :uri \"/api/commit-graph/refs\" :method :get :wrap-success nil) (repo-url)\n  (let ((commit-graph (find-or-create-commit-graph (current-company) repo-url)))\n    (or\n     (collecting\n       (fset:do-map (key value (commit-graph-refs commit-graph))\n         (collect (make-instance 'dto:git-ref\n                                  :name key\n                                  :sha value))))\n     #())))\n\n\n(defapi (update-refs :uri \"/api/commit-graph/refs\" :method :post) (repo-url refs)\n  \"This isn't intended to be used in prod at the moment, it's mostly a convenience for testing\"\n  (let ((commit-graph (find-or-create-commit-graph (current-company) repo-url))\n        (refs (dto:decode-json refs '(:list dto:git-ref))))\n    (loop for ref in refs\n          do (commit-graph-set-ref commit-graph (dto:git-ref-name ref)\n                                   (dto:git-ref-sha ref)))))\n\n(defapi (%check-shas :uri \"/api/commit-graph/check-wants\" :wrap-success nil) (repo-url shas)\n  \"Given a set of SHAs, return a list of all SHAs that are not available\nin the repo. The `want` is a reference to the git-upload-pack API.\"\n  (let* ((commit-graph (find-or-create-commit-graph (current-company) repo-url))\n         (dag (commit-graph-dag commit-graph)))\n    (let ((shas (json:decode-json-from-string shas)))\n      (unless shas\n        (push-event :check-wants-with-empty-shas))\n      (loop for sha in shas\n            unless (dag:get-commit dag sha)\n              collect sha))))\n"
  },
  {
    "path": "src/screenshotbot/api/compare.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/compare\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:recorder-run-channel\n                #:screenshot-name\n                #:current-company\n                #:can-view!)\n  (:import-from #:util/store/object-id\n                #:oid\n                #:find-by-oid)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-title\n                #:diff-report-empty-p\n                #:make-diff-report)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-company\n                #:recorder-run)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/report-api\n                #:report-previous-run\n                #:report-run\n                #:report)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler)\n  (:local-nicknames\n   (#:dto #:screenshotbot/api/model)\n   (#:diff-report #:screenshotbot/diff-report)))\n(in-package :screenshotbot/api/compare)\n\n(defvar *throttler*\n  (make-instance 'throttler :tokens 1000))\n\n(defun %api-compare-runs (run to)\n  (assert-company run)\n  (?. assert-company to)\n  (throttle! *throttler* :key (auth:current-company))\n  (let ((diff-report (make-diff-report run to)))\n    (if (diff-report-empty-p diff-report)\n        (make-instance 'dto:comparison\n                       :samep t)\n        (make-instance 'dto:comparison\n                       :samep nil\n                       :title (diff-report-title diff-report)\n                       :channel (?. channel-name (recorder-run-channel run))\n                       :changes\n                       (or\n                        (loop for change in (diff-report:diff-report-changes diff-report)\n                              collect (screenshot-name (diff-report:after change)))\n                        #())\n                       :added\n                       (or\n                        (mapcar #'screenshot-name (diff-report:diff-report-added diff-report))\n                        #())\n                       :deleted\n                       (or\n                        (mapcar #'screenshot-name (diff-report:diff-report-deleted diff-report))\n                        #())                       \n                       :url (format nil \"~a/runs/~a/compare/~a\"\n                                    (installation-domain (installation))\n                                    (oid run)\n                                    (?. oid to))))))\n\n(defun assert-company (run)\n  \"A site-admin key could technically read every run, and a key from one\nuser could technically read runs from every company they are part\nof. So we add an extra assertion here. We probably need to make this\npart of the access check framework.\"\n  (assert run)\n  (assert (eql (current-company) (recorder-run-company run))))\n\n(defapi (api-compare-runs :uri \"/api/run/:id/compare/:to\") (id to)\n  (flet ((find-run (id)\n           (let ((ret (find-by-oid id 'recorder-run)))\n             (assert ret)\n             ret)))\n   (let ((run (find-run id))\n         (to (find-run to)))\n     (can-view! run to)\n     (%api-compare-runs run to))))\n\n(defapi (api-compare-report :uri \"/api/report/:id/comparison\" :wrap-success nil) (id)\n  (let ((report (find-by-oid id 'report)))\n    (can-view! report)\n    (%api-compare-runs\n     (report-run report)\n     (report-previous-run report))))\n"
  },
  {
    "path": "src/screenshotbot/api/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/api/core\n  (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/model/api-key\n                #:mark-api-key-used\n                #:%find-api-key\n                #:validate-api-key-secret\n                #:decode-api-token)\n  (:import-from #:screenshotbot/user-api\n                #:api-key-company\n                #:api-key-user\n                #:current-user)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:hunchentoot-extensions\n                #:make-name)\n  (:import-from #:util/json-mop\n                #:json-mop-to-string\n                #:ext-json-serializable-class)\n  (:import-from #:screenshotbot/events\n                #:with-tracing)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:screenshotbot/model/company\n                #:redirect-url\n                #:maybe-redirect-for-company)\n  (:import-from #:auth/viewer-context\n                #:api-viewer-context)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:export\n   #:defapi\n   #:result\n   #:error-result\n   #:api-error\n   #:id\n   #:type\n   #:api-result\n   #:api-response\n   #:*dtd*\n   #:*api-key*))\n(in-package :screenshotbot/api/core)\n\n(defparameter *dtd*\n  (asdf:system-relative-pathname :screenshotbot \"dtd/api.dtd\"))\n\n(defvar *api-key* nil\n  \"The current API key being used\")\n\n(defvar *wrap-internal-errors* t\n  \"Wrap internal errors in a way that can be presented better to the\nuser. The intention of this flag is to set to NIL for some tests.\")\n\n(def-easy-macro with-api-key (&binding api-key &binding api-secret &fn fn)\n  (multiple-value-bind (key secret) (hunchentoot:authorization)\n    (cond\n      (key\n       (funcall fn key secret))\n      (t\n       (funcall fn (hunchentoot:parameter \"api-key\")\n                (hunchentoot:parameter \"api-secret-key\"))))))\n\n(defmethod authenticate-api-request ((request auth:authenticated-request))\n  \"Like auth:authenticate-request, but for API handling, and modifies the request\"\n  (with-api-key (api-key api-secret-key)\n    ;; temporary hack: PS. (we don't consider the key id a secret)\n    (when (and\n           (equal api-key \"56E7E9FLRVOTTJ5SUV0J\" )\n           (equal \"https://screenshotbot.io\" (installation-domain *installation*)))\n      (hex:forward-request \"https://postcodelottery.screenshotbot.io\"))\n    \n    ;; If api-key is not provided, try to extract it from api-secret\n    (let ((api-key (if (or (null api-key) (equal \"\" api-key))\n                       (ignore-errors\n                        (multiple-value-bind (extracted-key extracted-secret)\n                            (decode-api-token api-secret-key)\n                          (declare (ignore extracted-secret))\n                          extracted-key))\n                       api-key)))\n      (let ((key (%find-api-key  api-key)))\n        (unless key\n          ;; Remove some noise from sdk-integration-tests\n          (unless (equal \"deliver-sdk\" api-key)\n            (warn \"No such API key: ~a\" api-key))\n          (error 'api-error\n                 :message (format nil \"No such API key: ~a\" api-key)))\n        (unless (or\n                 (not key) ;; setings api\n                 (validate-api-key-secret key api-secret-key))\n          (error 'api-error\n                 :message \"API secret key doesn't match what we have on record\"))\n        (mark-api-key-used key)\n        (prog1\n            (authenticate-request-from-key request key)\n\n          ;; TODO: this probably never happens\n          (unless (current-user)\n            (error 'api-error\n                   :message (format nil \"API key appears to be invalid or non-existant, got: ~a\" api-key))))))))\n\n(defmethod authenticate-request-from-key ((request auth:authenticated-request) key)\n  (let ((user (api-key-user key)))\n    (setf\n     (auth:request-user hunchentoot:*request*) user)\n    (setf\n     (auth:viewer-context hunchentoot:*request*)\n     (make-instance 'api-viewer-context\n                    :user user\n                    :api-key key)))\n  (let ((company (api-key-company key)))\n    (when (redirect-url company)\n      (hex:forward-request (redirect-url company)))\n    ;; Do not check this! Old keys will still be valid against the\n    ;; company!\n    ;;(auth:can-view! company)\n    (setf\n     (auth:request-account hunchentoot:*request*) company))\n\n  key)\n\n(defun %funcall-with-api-handling (fn)\n  (log:trace \"Got parameters: ~s\" (hunchentoot:post-parameters hunchentoot:*request*))\n  (push-event :api-client-version\n              :version (hunchentoot:header-in* :x-client-version))\n  (let ((*api-key* (authenticate-api-request hunchentoot:*request*)))\n    (funcall fn)))\n\n(defun %set-error-code (e)\n  ;; For api-version < 14, or SDK version <= 2.8.14, we would always\n  ;; return error code 200.\n  (setf (hunchentoot:return-code*) (api-error-code e))\n  (setf (hunchentoot:content-type*) \"application/json\"))\n\n(def-easy-macro with-error-handling (&key wrap-success &fn fn)\n  \"Converts internal errors into a JSON renderable object.\n\nIt should be safe to mock call-with-error-handling to just call\nfunction fn for the purpose of tests.\"\n  (let ((stacktrace-id (random 1000000000)))\n    (with-extras ((\"stacktrace-id\" stacktrace-id)\n                  (\"x-client-version\" (hunchentoot:header-in* :x-client-version))\n                  (\"x-client-api-version\" (hunchentoot:header-in* :x-client-api-version)))\n      (block error-handling\n        (flet ((%trace ()\n                 (format nil \"Stacktrace ID: ~a. Please share this ID if contacting support.\" stacktrace-id)))\n          (handler-bind ((api-error (lambda (e)\n                                      (when *wrap-internal-errors*\n                                        (%set-error-code e)\n                                        (log:warn \"API error: ~a\" (api-error-msg e))\n                                        (return-from error-handling\n                                          (make-instance 'error-result\n                                                         :success nil\n                                                         :stacktrace (%trace)\n                                                         :error (princ-to-string e))))))\n                         (error  (lambda (e)\n                                   (when *wrap-internal-errors*\n                                     (%set-error-code e)\n                                     (warn \"Unhandled API error call: ~a\" e)\n                                     (sentry-client:capture-exception e)\n                                     (return-from error-handling\n                                       (make-instance 'error-result\n                                                      :success nil\n                                                      :stacktrace (%trace)\n                                                      :error (format nil\n                                                                     \"Internal error, please contact support@screenshotbot.io\"))))\n                                   )))\n            (cond\n              (wrap-success\n               (make-instance 'result\n                              :success t\n                              :response\n                              (fn)))\n              (t\n               (fn)))))))))\n\n(defmacro defapi ((name &key uri method intern\n                          (type :v1)\n                          (use-yason nil)\n                          (wrap-success t)\n                          (listp nil))\n                  params &body body)\n  (declare (ignore type))\n  (let* ((param-names (loop for param in params\n                            if (symbolp param)\n                              collect param\n                            else\n                              collect (car param)))\n         (name (or name (make-name uri method)))\n         (handler-name\n           (when name (intern (format nil \"~a-API-HANDLER\" name) (symbol-package name)) )))\n    (multiple-value-bind (body decls doc) (uiop:parse-body body)\n      `(progn\n         (defun ,name (&key ,@param-names)\n           ,doc\n           ,@decls\n           (with-tracing (,uri)\n             ,@body))\n         (defhandler (,handler-name :uri ,uri :method ,method :intern ,intern) ,params\n           ,@decls\n           (flet ((ret ()\n                    (%funcall-with-api-handling\n                     (lambda ()\n                      (,name\n                       ,@ (loop for name in param-names\n                                appending (list (intern (string name) \"KEYWORD\") name)))))))\n             (setf (hunchentoot:header-out :content-type) \"application/json; charset=utf-8\")\n             (,(cond\n                 (use-yason\n                  'json-mop-to-string)\n                 (t\n                  'json:encode-json-to-string))\n              (with-error-handling (:wrap-success ,wrap-success)\n                (let ((obj (ret)))\n                  (or\n                   obj\n                   (when ,listp\n                     #())))))))))))\n\n(defun write-xml-output (ret)\n  (setf (hunchentoot:header-out :content-type) \"applicaton/xml\")\n  (with-output-to-string (out)\n    (json-mop:encode ret out)))\n\n(defclass result ()\n  ((success :type boolean\n            :initform t\n            :json-key \"success\"\n            :json-type :bool\n            :initarg :success)\n   (response :initarg :response\n             :json-key \"response\"\n             :json-type (or null :any)))\n  (:metaclass ext-json-serializable-class))\n\n(defclass error-result (result)\n  ((error :initarg :error\n          :reader error-result-message\n          :json-key \"error\"\n          :json-type :string)\n   (stacktrace :initarg :stacktrace\n               :reader error-result-stacktrace\n               :json-key \"stacktrace\"\n               :json-type (or null :string)))\n  (:metaclass ext-json-serializable-class))\n\n(define-condition api-error (error)\n  ((message :initarg :message\n            :initform \"[api-error]\"\n            :reader api-error-msg)\n   (code :initarg :code\n         :initform 400\n         :reader api-error-code))\n  (:report (lambda (e out)\n             (format out \"~a\" (api-error-msg e)))))\n\n(defun api-error (fmt &rest args)\n  (error 'api-error\n         :message (apply #'format nil fmt args)))\n\n(defmethod api-error-code (error)\n  500)\n\n(defapi (nil :uri \"/api/test\") ()\n  3)\n\n(defapi (nil :uri \"/api/test-error\") ()\n  (error 'api-error :message \"Foo\"))\n\n(defapi (nil :uri \"/api/test-internal-error\") ()\n  (error \"bad stuff\"))\n\n(defclass api-result () ())\n\n(defclass api-response () ())\n"
  },
  {
    "path": "src/screenshotbot/api/doc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/doc\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:screenshotbot/server\n                #:staging-p)\n  (:export\n   #:def-api-doc\n   #:render-all-api-docs))\n(in-package :screenshotbot/api/doc)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *docs* nil)\n\n(defclass api-doc ()\n  ((endpoint :initarg :endpoint\n             :reader endpoint)\n   (input-type :initarg :input-type\n               :reader input-type)\n   (output-type :initarg :output-type\n                :reader output-type)\n   (title :initarg :title\n          :reader title)\n   (method :initarg :method\n           :reader api-method)\n   (generator :initarg :generator\n              :reader generator\n              :documentation \"A function that generates the given documentation\")))\n\n(easy-macros:def-easy-macro def-api-doc (endpoint &fn fn\n                                                  &rest args)\n  (setf (assoc-value *docs* endpoint :test #'equal)\n        (apply #'make-instance\n               'api-doc\n               :endpoint endpoint\n               :generator #'fn\n               args)))\n\n(markup:deftag render-all-api-docs ()\n  (cond\n   ((staging-p)\n     <div>\n     ,@(loop for (nil . doc) in *docs*\n             collect\n             <div>\n             <h2 id= (title doc)>,(title doc)</h2>\n\n             <p>\n             <span class= \"text-success\">,(string (api-method doc))</span>\n             ,(endpoint doc)\n             </p>\n             ,(funcall (generator doc))\n             </div>)\n     </div>)\n   (t\n    <div />)))\n\n"
  },
  {
    "path": "src/screenshotbot/api/docs/recorder-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/docs/recorder-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/doc\n                #:def-api-doc)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/docs/recorder-run)\n\n(named-readtables:in-readtable markup:syntax)\n\n(def-api-doc (\"/api/run/:id\" :input-type nil\n              :title \"Retrieve a run\"\n              :method :get\n                             :output-type 'dto:run)\n  <remark:md>\n    Get the run associated with a given ID.\n  </remark:md>)\n\n"
  },
  {
    "path": "src/screenshotbot/api/failed-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/failed-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:api-error\n                #:*dtd*\n                #:defapi)\n  (:import-from #:screenshotbot/model/failed-run\n                #:failed-runs-for-company\n                #:failed-run-commit\n                #:failed-run-channel\n                #:failed-run)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:current-company)\n  (:import-from #:json-mop\n                #:json-serializable-class)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/failed-run)\n\n(defun parse-body (class-name)\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (json-mop:json-to-clos body class-name)))\n\n(defun to-dto (ret)\n  (make-instance 'dto:failed-run\n                 :id (bknr.datastore:store-object-id ret)\n                 :channel (channel-name (failed-run-channel ret))\n                 :commit (failed-run-commit ret)))\n\n(defapi (%put-failed-run :uri \"/api/failed-run\" :method :put\n         :use-yason t) ()\n  (assert (current-company))\n  (let ((input (parse-body 'dto:failed-run)))\n    (let ((ret\n            (make-instance 'failed-run\n                           :channel (find-or-create-channel\n                                     (current-company)\n                                     (dto:failed-run-channel input))\n                           :company (current-company)\n                           :commit (dto:failed-run-commit input))))\n      (to-dto ret))))\n\n(defapi (%list-failed-runs :uri \"/api/failed-run\" :method :get\n                           :use-yason t\n                           :listp t) ()\n  (let ((runs (failed-runs-for-company (current-company))))\n    (loop for run in runs\n          collect (to-dto run))))\n"
  },
  {
    "path": "src/screenshotbot/api/finalized-commit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/finalized-commit\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:import-from #:screenshotbot/model/finalized-commit\n                #:finalized-commit-company\n                #:finalized-commit-hash\n                #:finalized-commit\n                #:find-or-create-finalized-commit)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:screenshotbot/model/batch\n                #:finalize-batch\n                #:find-batches-for-commit)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/finalized-commit)\n\n(defun %parse-body ()\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (decode-json body 'dto:finalized-commit)))\n\n(defmethod to-dto ((self finalized-commit))\n  (make-instance 'dto:finalized-commit\n                 :commit (finalized-commit-hash self)))\n\n(defapi (%post-finalized-commit :uri \"/api/finalized-commit\" :method :post\n                                :use-yason t) ()\n  (assert (current-company))\n  (let ((input (%parse-body)))\n    (let ((finalized-commit (find-or-create-finalized-commit\n                             (current-company)\n                             (dto:finalized-commit-hash input))))\n      (trigger-callbacks finalized-commit)\n      (to-dto finalized-commit))))\n\n(defun trigger-callbacks (finalized-commit)\n  (loop for batch in (find-batches-for-commit\n                      :commit (finalized-commit-hash finalized-commit)\n                      :company (finalized-commit-company finalized-commit))\n        do (finalize-batch batch)))\n"
  },
  {
    "path": "src/screenshotbot/api/image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/api/image\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/api/core\n        #:screenshotbot/model/image\n        #:screenshotbot/model/screenshot)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:util\n                #:oid)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:screenshotbot/model/image\n                #:image-blob\n                #:update-image\n                #:make-image)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:screenshotbot/api/core\n                #:with-error-handling\n                #:authenticate-api-request)\n  (:import-from #:screenshotbot/model/company\n                #:find-image-by-id)\n  (:import-from #:util/events\n                #:with-tracing)\n  (:export\n   #:with-raw-post-data-as-tmp-file))\n(in-package :screenshotbot/api/image)\n\n(defvar *bucket* \"screenshotbot\")\n\n(defclass temporary-credential ()\n  ((api-key :type string\n            :initarg :api-key\n            :reader api-key)\n   (api-secret :type string\n               :initarg :api-secret\n               :reader api-secret)))\n\n(defmethod print-object ((c temporary-credential) out)\n  (format out \"#<TEMPORARY-CREDENTIALS ~A ~A>\" (api-key c)\n          (api-secret c)))\n\n(defun get-sts-token ()\n  (multiple-value-bind (out err ret)\n   (uiop:run-program (list \"aws\" \"sts\" \"get-federation-token\" \"--duration-seconds\" \"900\"\n                           \"--name\" \"silkwrmuploader\"\n                           \"--policy\" (uiop:read-file-string (path:catfile *root* \"s3-policy.json\")))\n                     :output 'string)\n    (declare (ignore err))\n    (assert (eql 0 ret))\n    (let* ((resp (json:decode-json-from-string out))\n           (cred (assoc-value resp :*credentials)))\n      (make-instance 'temporary-credential\n                     :api-key (assoc-value cred :*access-key-id)\n                     :api-secret (assoc-value cred :*secret-access-key)))))\n\n(defclass upload-response (api-response)\n  ((type :initform \"image\")\n   (id :type string\n       :initarg :id\n       :reader upload-response-id)\n   (upload-url :type (or null string)\n               :initarg :upload-url\n               :reader upload-response-upload-url)))\n\n(defparameter *use-blob-store-p* t)\n\n(defun prepare-single-upload (hash content-type)\n  (declare (ignore content-type))\n  (let* ((image (find-image (current-company) hash))\n         (uploadp (or (not image)\n                      (not (verified-p image))))\n         (image\n           (or\n            image\n            (make-image :hash hash\n                        :company (current-company))))\n         (upload-url (when uploadp\n                       (cond\n                         (*use-blob-store-p*\n                          (hex:make-full-url\n                           hunchentoot:*request*\n                           'api-upload-image-blob\n                           :oid (oid image)))\n                         (t\n                          (error \"S3 Blob store no longer supported\"))))))\n    (make-instance 'upload-response\n                   :id (oid image)\n                   :upload-url upload-url)))\n\n(defapi (prepare-upload-api :uri \"/api/screenshot\") (hash content-type hash-list)\n  (cond\n    (hash\n     (prepare-single-upload hash nil))\n    (hash-list\n     (loop for hash in (json:decode-json-from-string hash-list)\n           collect\n           (cons hash (prepare-single-upload hash nil))))\n    (t\n     (error 'api-error\n            :message\n            \"provide either hash or hash-list argument\"))))\n\n(defhandler (nil :uri \"/api/prepare-upload\" :method :post) (hash content-type)\n  (prepare-upload-api :hash hash :content-type content-type))\n\n;; I'm skeptical of the correctness of this, see D5631\n(defun %with-raw-post-data-as-tmp-file (fn)\n  (uiop:with-temporary-file (:stream s :pathname p :direction :output\n                             :element-type 'flexi-streams:octet)\n    (let* (;;(content-length (parse-integer (hunchentoot:header-in* :content-length)))\n           (in-stream (hunchentoot:raw-post-data :force-binary t\n                                                 :want-stream t)))\n      (let ((buf (make-array 4096 :element-type '(unsigned-byte 8))))\n        (loop for bytes = (read-sequence buf in-stream)\n              while (> bytes 0)\n              do (write-sequence buf s :end bytes))))\n    (finish-output s)\n    (funcall fn p)))\n\n(defmacro with-raw-post-data-as-tmp-file ((tmpfile) &body body)\n  `(%with-raw-post-data-as-tmp-file (lambda (,tmpfile) ,@body)))\n\n(defhandler (api-upload-image-blob :uri \"/api/image/blob\" :method :put) (oid)\n  \"Just a heads up, there are some integration tests from this in\nscreenshotbot.sdk if you need to test it.\"\n  (authenticate-api-request hunchentoot:*request*)\n  (with-error-handling (:wrap-success nil) \n   (with-tracing (\"image-blob-put\" :oid oid)\n     (let ((image (find-image-by-id (current-company) oid)))\n       (with-raw-post-data-as-tmp-file (p)\n         (verify-and-upload-from-path image p))))))\n\n(defmethod verify-and-upload-from-path (image p)\n  (verify-image-hash p (image-hash image))\n  (update-image image :pathname p)\n  (setf (verified-p image) t)\n  \"0\")\n\n\n(defun verify-image-hash (file hash)\n  (let ((etag\n          (md5-file file)))\n    (cond\n      ((equalp etag hash)\n       ;; We used to set verified-p from here, but no longer\n       (values))\n      (t\n       (error 'api-error\n              :message\n              (format nil\n                      \"md5sum mismatch from what was uploaded for image: ~a was uploaded vs ~a was expected\"\n                      (ironclad:byte-array-to-hex-string\n                       etag)\n                      (ironclad:byte-array-to-hex-string\n                       hash)))))))\n\n"
  },
  {
    "path": "src/screenshotbot/api/model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/model\n            (:use #:cl)\n            (:import-from #:json-mop\n                          #:json-serializable-class)\n            (:import-from #:util/json-mop\n                          #:ext-json-serializable-class)\n            (:local-nicknames (#:a #:alexandria))\n            (:export\n             #:encode-json\n             #:*api-version*\n             #:version-number\n             #:failed-run\n             #:failed-run-channel\n             #:failed-run-commit\n             #:screenshot\n             #:screenshot-name\n             #:screenshot-image-id\n             #:screenshot-lang\n             #:screenshot-device\n             #:screenshot-list\n             #:run\n             #:run-id\n             #:run-channel\n             #:run-screenshots\n             #:run-commit\n             #:should-create-github-issue-p\n             #:trunkp\n             #:periodic-job-p\n             #:cleanp\n             #:pull-request-url\n             #:main-branch-hash\n             #:merge-base\n             #:commit\n             #:main-branch\n             #:gitlab-merge-request-iid\n             #:phabricator-diff-id\n             #:run-repo\n             #:build-url\n             #:override-commit-hash\n             #:compare-threshold\n             #:report\n             #:work-branch\n             #:unchanged-run-other-commit\n             #:unchanged-run-commit\n             #:unchanged-run-channel\n             #:unchanged-run\n             #:finalized-commit\n             #:finalized-commit-hash\n             #:recorder-run-url\n             #:run-batch\n             #:comparison\n             #:comparison-samep\n             #:comparison-title\n             #:comparison-url\n             #:run-tags\n             #:run-author\n             #:batch\n             #:batch-repo\n             #:batch-commit\n             #:batch-name\n             #:screenshot-url\n             #:shard-spec-count\n             #:shard-spec-number\n             #:shard-spec-key\n             #:shard-spec\n             #:image-upload-response\n             #:image-md5sum\n             #:image-upload-url\n             #:image-id\n             #:metadata\n             #:run-metadata\n             #:metadata-key\n             #:metadata-value\n             #:release-branch-p\n             #:report-id\n             #:report-acceptable-state\n             #:report-run-id\n             #:report-previous-run-id\n             #:report-title\n             #:commit-graph\n             #:decode-json\n             #:git-ref\n             #:git-ref-name\n             #:git-ref-sha\n             #:api-features\n             #:build-info\n             #:build-info-build-url\n             #:build-info-repo-url\n             #:compare-pixel-tolerance))\n\n(in-package :screenshotbot/api/model)\n\n;; Please update CHANGELOG.md\n(defparameter *api-version* 21)\n\n(defclass version ()\n  ((version :initarg :version\n            :json-key \"version\"\n            :json-type :number\n            :reader version-number)\n   (url :initarg :url\n        :json-key \"url\"\n        :json-type (or null :string)\n        :reader installation-url\n        :documentation \"The installation's URL\")\n   (features :initarg :features\n             :json-key \"features\"\n             :json-type (:list :string)\n             :documentation \"List of features enabled. This is only listed if the API call is authenticated.\"\n             :reader api-features))\n  (:metaclass ext-json-serializable-class))\n\n(defmethod encode-json (object)\n  (with-output-to-string (out)\n    (json-mop:encode object out)))\n\n(defmethod decode-json (json type)\n  (declare (optimize (speed 0) (debug 3)))\n  (cond\n    ((and\n      (listp type)\n      (eql 'or (first type))\n      (eql 'null (second type)))\n     (cond\n       ((equal \"null\" json)\n        nil)\n       (t\n        (decode-json json (third type)))))\n    ((and\n      (listp type)\n      (eql :list (car type)))\n     (loop for item across (yason:parse (make-string-input-stream json)\n                                        :object-as :hash-table\n                                        :json-arrays-as-vectors t\n                                        :json-booleans-as-symbols t\n                                        :json-nulls-as-keyword t)\n           collect (json-mop:json-to-clos item (second type))))\n    (t\n     (json-mop:json-to-clos json type))))\n\n(defclass failed-run ()\n  ((id :initarg :id\n       :json-type (or null :number)\n       :initform nil\n       :json-key \"id\")\n   (channel :initarg :channel\n            :json-key \"channel\"\n            :json-type (or null :string)\n            :initform nil\n            :reader failed-run-channel)\n   (commit :initarg :commit\n           :json-key \"commit\"\n           :json-type (or null :string)\n           :initform nil\n           :reader failed-run-commit))\n  (:metaclass ext-json-serializable-class))\n\n(defclass abstract-run-dto ()\n  ((batch :initarg :batch\n          :json-key \"batch\"\n          :initform nil\n          :json-type (or null :string)\n          :reader run-batch\n          :documentation \"The batch name associated with this run\")\n   (repo :initarg :github-repo\n         :json-key \"repo\"\n         :json-type (or null :string)\n         :initform nil\n         ;; Internally this is github-repo :/\n         :reader run-repo\n         :documentation \"The repository URL\")\n   (override-commit-hash :initarg :override-commit-hash\n                         :json-key \"overrideCommitHash\"\n                         :json-type (or null :string)\n                         :initform nil\n                         :reader override-commit-hash\n                         :documentation \"The Git hash associated with the current run. This might be different from `commit` if the CI job had a step of rebasing the changes onto the master branch.\")\n   (pull-request-url :initarg :pull-request\n                     :json-key \"pullRequestUrl\"\n                     :json-type (or null :string)\n                     :initform nil\n                     :reader pull-request-url\n                     :documentation \"The pull request URL associated with this run, if any.\")\n   (phabrictor-diff-id :initarg :phabricator-diff-id\n                       :json-key \"phabricatorDiff\"\n                       :json-type (or null :number)\n                       :initform nil\n                       :reader phabricator-diff-id\n                       :documentation \"A Phabricator Diff ID associated with the run, if any.\")\n   (work-branch :initarg :work-branch\n                :json-key \"workBranch\"\n                :json-type (or null :string)\n                :initform nil\n                :reader work-branch\n                :documentation \"The branch on which the CI job was run\")\n   (release-branch-p :initarg :release-branch-p\n                     :json-key \"isReleaseBranch\"\n                     :json-type (or null :bool)\n                     :initform nil\n                     :reader release-branch-p\n                     :documentation \"Is the work-branch a release branch, typically this\nis computed on the CLI via a regex.\")\n   (merge-base :initarg :merge-base\n               :json-key \"mergeBase\"\n               :json-type (or null :string)\n               :initform nil\n               :reader merge-base\n               :documentation \"The commit hash of the merge base of this commit with the main branch.\"))\n  (:metaclass ext-json-serializable-class))\n\n(defclass unchanged-run (abstract-run-dto)\n  ((id :initarg :id\n       :json-type (or null :number)\n       :initform nil\n       :json-key \"id\")\n   (channel :initarg :channel\n            :json-key \"channel\"\n            :json-type (or null :string)\n            :initform nil\n            :reader unchanged-run-channel)\n   (commit :initarg :commit\n           :json-key \"commit\"\n           :json-type (or null :string)\n           :initform nil\n           :reader unchanged-run-commit\n           :reader run-commit)\n   (other-commit :initarg :other-commit\n                 :json-key \"other-commit\"\n                 :json-type (or null :string)\n                 :initform nil\n                 :reader unchanged-run-other-commit))\n  (:metaclass ext-json-serializable-class))\n\n(defclass finalized-commit ()\n  ((id :initarg :id\n       :json-type (or null :number)\n       :initform nil\n       :json-key \"id\")\n   (commit :initarg :commit\n           :json-key \"commit\"\n           :json-type (or null :string)\n           :reader finalized-commit-hash))\n  (:metaclass ext-json-serializable-class))\n\n(defclass batch ()\n  ((id :initarg :id\n       :json-type (or null :number)\n       :initform nil\n       :json-key \"id\")\n   (repo :initarg :github-repo\n         :json-key \"repo\"\n         :json-type (or null :string)\n         :initform nil\n         :reader batch-repo\n         :documentation \"The repository URL\")\n   (commit :initarg :commit\n           :json-key \"commit\"\n           :json-type (or null :string)\n           :reader batch-commit)\n   (name :initarg :name\n         :json-key \"name\"\n         :json-type :string\n         :reader batch-name)\n   (phabrictor-diff-id :initarg :phabricator-diff-id\n                       :json-key \"phabricatorDiff\"\n                       :json-type (or null :number)\n                       :initform nil\n                       :reader phabricator-diff-id\n                       :documentation \"A Phabricator Diff ID associated with the run, if any.\")\n   (pull-request-url :initarg :pull-request\n                     :json-key \"pullRequestUrl\"\n                     :json-type (or null :string)\n                     :initform nil\n                     :reader pull-request-url\n                     :documentation \"The pull request URL associated with this run, if any.\"))\n  (:metaclass ext-json-serializable-class))\n\n(defclass screenshot ()\n  ((name :initarg :name\n         :json-type :string\n         :json-key \"name\"\n         :reader screenshot-name\n         :documentation \"The name associated with this screenshot\")\n   (image-id :initarg :image-id\n             :json-type :string\n             :json-key \"imageId\"\n             :reader screenshot-image-id\n             :documentation \"The ID of the image associated with this screenshot\")\n   (url :initarg :url\n        :json-type :string\n        :json-key \"url\"\n        :reader screenshot-url\n        :documentation \"The URL to download the original image from\")\n   (lang :initarg :lang\n         :initform nil\n         :json-type (or null :string)\n         :json-key \"lang\"\n         :reader screenshot-lang)\n   (device :initarg :device\n           :initform nil\n           :json-type (or null :string)\n           :json-key \"device\"\n           :reader screenshot-device))\n  (:metaclass ext-json-serializable-class))\n\n(defclass shard-spec ()\n  ((key :initarg :key\n        :json-type :string\n        :json-key \"key\"\n        :reader shard-spec-key\n        :documentation \"A unique, but arbitrary, identifier that identifies all\nthe shards. For instance this might be the CircleCI root build id.\")\n   (number :initarg :number\n           :json-type :number\n           :json-key \"number\"\n           :reader shard-spec-number\n           :documentation \"The number of the shard, 0-indexed.\")\n   (count :initarg :count\n          :json-type :number\n          :json-key \"count\"\n          :reader shard-spec-count\n          :documentation \"The total count of shards\"))\n  (:documentation \"If we're sending a run in multiple shards, this specifies the current\nshard.\")\n  (:metaclass ext-json-serializable-class))\n\n(defclass metadata ()\n  ((key :initarg :key\n        :reader metadata-key\n        :json-type :string\n        :json-key \"key\")\n   (value :initarg :value\n          :reader metadata-value\n          :json-type :string\n          :json-key \"value\"))\n  (:documentation \"An aribtrary key-value pair, typically used for storing debugging\ninformation on runs.\")\n  (:metaclass ext-json-serializable-class))\n\n(defclass run (abstract-run-dto)\n  ((id :initarg :id\n       :json-key \"id\"\n       :json-type :string\n       :reader run-id\n       :documentation \"The ID of this run\")\n   (channel :initarg :channel\n            :json-key \"channel\"\n            :json-type :string\n            :reader run-channel\n            :documentation \"The channel name used with this run\")\n   (screenshots :initarg :screenshots\n                :json-key \"screenshots\"\n                :json-type (:list screenshot)\n                :reader run-screenshots\n                :documentation \"A list of screenshots for this run. This field may not be present when querying a run.\")\n   (commit :initarg :commit-hash\n           :json-key \"commit\"\n           :json-type (or null :string)\n           :initform nil\n           :reader run-commit\n           :documentation \"The Git commit hash for this run\")\n   (create-github-issue :initarg :create-github-issue-p\n                        :json-key \"shouldCreateGithubIssue\"\n                        :initform nil\n                        :json-type :bool\n                        :reader should-create-github-issue-p)\n   (trunkp :initarg :trunkp\n           :json-key \"isTrunk\"\n           :json-type :bool\n           :initform nil\n           :reader trunkp)\n   (periodic-job-p\n    :initarg :periodic-job-p\n    :json-key \"isPeriodicJob\"\n    :json-type :bool\n    :initform nil\n    :reader periodic-job-p)\n   (cleanp\n    :initarg :cleanp\n    :json-key \"isClean\"\n    :json-type :bool\n    :initform nil\n    :reader cleanp)\n   (main-branch-hash :initarg :main-branch-hash\n                     :json-key \"mainBranchCommit\"\n                     :json-type (or null :string)\n                     :initform nil\n                     :reader main-branch-hash\n                     :documentation \"The Git hash of the main branch at the time that this run was created.\")\n   (build-url :initarg :build-url\n              :json-key \"buildUrl\"\n              :json-type (or null :string)\n              :initform nil\n              :reader build-url\n              :documentation \"The URL of the build job that created this run\")\n   (main-branch :initarg :main-branch\n                :json-key \"mainBranch\"\n                :json-type (or null :string)\n                :initform nil\n                :reader main-branch\n                :documentation \"The main branch, usually `main` or `master`.\")\n   (gitlab-merge-request-iid :initarg :gitlab-merge-request-iid\n                             :json-key \"gitlabMergeRequestIID\"\n                             :json-type (or null :number)\n                             :initform nil\n                             :reader gitlab-merge-request-iid\n                             :documentation \"A GitLab merge request IID associated with the run, if any.\")\n   (compare-threshold :initarg :compare-threshold\n                      :json-key \"compareThreshold\"\n                      :json-type (or null :number)\n                      :initform nil\n                      :reader compare-threshold\n                      :documentation \"The comparison threshold used for comparisons associated with this run.\")\n   (compare-pixel-tolerance :initarg :compare-pixel-tolerance\n                            :json-key \"comparePixelTolerance\"\n                            :json-type (or null :number)\n                            :initform 0\n                            :reader compare-pixel-tolerance\n                            :documentation \"The square of the euclidian distance between two pixels when\nconsidering them different or not.\")\n   (url :initarg :url\n        :json-key \"url\"\n        :json-type (or null :string)\n        :reader recorder-run-url\n        :documentation \"The URL of this run\")\n   (tags :initarg :tags\n         :json-key \"tags\"\n         :initform nil\n         :json-type (:list :string)\n         :reader run-tags\n         :documentation \"A list of arbitrary tags associated with this run\")\n   (author :initarg :author\n           :reader run-author\n           :json-key \"author\"\n           :initform nil\n           :json-type (or null :string)\n           :documentation \"The author of this run. This is used when implementing policies around reviews.\")\n   (metadata :initarg :metadata\n             :initform nil\n             :reader run-metadata\n             :json-key \"metadata\"\n             :json-type (:list metadata)\n             :documentation \"A list of metadata elements. This information will typically not affect\nruns, but only for debugging.\")\n   (shard-spec :initarg :shard-spec\n               :reader shard-spec\n               :json-key \"shard\"\n               :initform nil\n               :json-type (or null shard-spec)\n               :documentation \"An optional shard-spec. This information is not present when reading a run. When creating a run, the presence of a shard-spec might delay the actual creation until all of the shards are available.\"))\n  (:metaclass ext-json-serializable-class))\n\n(defmethod compare-pixel-tolerance :around ((self run))\n  (or\n   (call-next-method)\n   0))\n\n\n(defclass report ()\n  ((id :initarg :id\n       :json-key \"id\"\n       :json-type :string\n       :reader report-id\n       :documentation \"The ID of this report\")\n   (run :initarg :run\n        :json-key \"run\"\n        :json-type :string\n        :reader report-run-id\n        :documentation \"The ID of the run that generated this\")\n   (channel :initarg :channel\n            :json-key \"channel\"\n            :json-type (or null :string)\n            :documentation \"The channel name\")\n   (title :initarg :title\n          :json-key \"title\"\n          :json-type (or null :string)\n          :reader report-title\n          :documentation \"The report title, something like `N changes, M added`\")\n   (previous-run :initarg :previous-run\n                 :json-key \"previousRun\"\n                 :json-type (or null :string)\n                 :reader report-previous-run-id)\n   (review-state :initarg :acceptable-state\n                 :json-key \"reviewState\"\n                 :json-type :string\n                 :reader report-acceptable-state\n                 :documentation \"The review status of the report. One of 'accepted', 'rejected', 'none' or 'na'.\")\n   (reviewer-name :initarg :reviewer-name\n                  :json-key \"reviewerName\"\n                  :json-type (or null :string)\n                  :documentation \"If the review state 'accepted' or 'rejected', this will be the name of the reviewer\"))\n  (:metaclass ext-json-serializable-class))\n\n(defmethod json-mop:json-to-clos ((items vector) (class (eql 'screenshot-list))\n                                  &rest initargs)\n  (loop for x across items\n        collect (apply #'json-mop:json-to-clos x 'screenshot initargs)))\n\n(defclass comparison ()\n  ((samep :initarg :samep\n          :json-key \"isSame\"\n          :json-type :boolean\n          :reader comparison-samep)\n   (changes :initarg :changes\n            :json-key \"changes\"\n            :json-type (:list :string)\n            :reader comparison-changes\n            :documentation \"List of names of all screenshots that were changed\")\n   (added :initarg :added\n          :json-key \"added\"\n          :json-type (:list :string)\n          :reader comparison-added\n          :documentation \"List of names of screenshots that were added\")\n   (deleted :initarg :deleted\n            :json-key \"deleted\"\n            :json-type (:list :string)\n            :documentation \"List of names of screenshots that were deleted\")\n   (channel :initarg :channel\n            :json-key \"channel\"\n            :json-type (or null :string)\n            :documentation \"The channel name. If the runs being compared are from different channels then this field is undefined.\")\n   (title :initarg :title\n          :json-key \"title\"\n          :json-type (or null :string)\n          :reader comparison-title)\n   (url :initarg :url\n        :json-key \"url\"\n        :json-type (or null :string)\n        :reader comparison-url))\n  (:metaclass ext-json-serializable-class))\n\n(defclass image-upload-response ()\n  ((image-id :initarg :image-id\n             :reader image-id\n             :json-key \"imageId\"\n             :json-type :string)\n   (md5sum :initarg :md5sum\n           :reader image-md5sum\n           :json-key \"md5sum\"\n           :json-type :string)\n   (upload-url :initarg :upload-url\n               :accessor image-upload-url\n               :json-key \"uploadUrl\"\n               :json-type (or null :string)))\n  (:metaclass ext-json-serializable-class)\n  (:documentation \"TODO: at time of writing, we're not sending this over the API, and\nit's just being constructed in the SDK. This will be used to send a\nresponse back from /api/screenshot.\"))\n\n(defclass commit ()\n  ((sha :initarg :sha\n        :json-key \"sha\"\n        :json-type :string)\n   (parents :initarg :parents\n            :json-key \"parents\"\n            :json-type (:list :string)))\n  (:metaclass ext-json-serializable-class))\n\n(defclass git-ref ()\n  ((name :initarg :name\n         :json-key \"name\"\n         :json-type :string\n         :reader git-ref-name)\n   (sha :initarg :sha\n        :json-key \"sha\"\n        :json-type :string\n        :reader git-ref-sha))\n  (:metaclass ext-json-serializable-class))\n\n(defclass commit-graph ()\n  ((commits :initarg :commits\n            :json-key \"commits\"\n            :json-type (:list commit))\n   (repo :initarg :repo\n         :json-key \"repo\"\n         :json-type :string)\n   (refs :initarg :refs\n         :json-key \"refs\"\n         :json-type (:list git-ref)))\n  (:metaclass ext-json-serializable-class))\n\n(defclass build-info ()\n  ((build-url :initarg :build-url\n              :json-key \"buildUrl\"\n              :json-type (or null :string)\n              :reader build-info-build-url\n              :documentation \"The URL of the build job\")\n   (repo-url :initarg :repo-url\n             :json-key \"repoUrl\"\n             :json-type (or null :string)\n             :reader build-info-repo-url\n             :documentation \"The repository URL associated with the build\"))\n  (:metaclass ext-json-serializable-class)\n  (:documentation \"DTO for build information across CI steps\"))\n\n"
  },
  {
    "path": "src/screenshotbot/api/promote.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/api/promote\n  (:use #:cl\n        #:alexandria\n        #:anaphora\n        #:screenshotbot/promote-api\n        #:screenshotbot/api/core\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/channel\n        #:screenshotbot/git-repo)\n  (:shadow #:log)\n  (:import-from #:bknr.datastore\n                #:with-transaction\n                #:store-object-id)\n  (:import-from #:screenshotbot/github/access-checks\n                #:fix-github-link)\n  (:import-from #:screenshotbot/server\n                #:register-init-hook\n                #:*init-hooks*)\n  (:import-from #:util/threading\n                #:make-thread\n                #:with-extras\n                #:ignore-and-log-errors)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:import-from #:util/logger\n                #:noop-logger\n                #:logger\n                #:format-log)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-work-branch\n                #:push-run-warning\n                #:gitlab-merge-request-iid\n                #:unchanged-run\n                #:not-fast-forward-promotion-warning\n                #:promotion-log)\n  (:import-from #:screenshotbot/model/batch\n                #:finalize-batch\n                #:batch)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-send-tasks)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:core/config/api\n                #:config)\n  (:export\n   #:with-promotion-log\n   #:default-promoter)\n  ;; forward decls\n  (:export\n   #:maybe-promote-run\n   #:master-promoter\n   #:%maybe-send-tasks))\n(in-package :screenshotbot/api/promote)\n\n(defvar *current-logger* nil)\n\n(defvar *loggers* (make-mp-hash-table\n                   #+lispworks\n                   :weak-kind\n                   #+lispworks\n                   :key)\n  \"A mapping from runs to a util/logger:LOGGER instance\")\n\n\n(defun log (level message &rest args)\n  (cond\n    (*current-logger*\n     (apply #'format-log *current-logger* level\n            message args))\n    (t\n     (warn \"Attempted to call promotion log when no promotion is running: ~a\" message))))\n\n(defmethod promotion-logger (run #| or batch! |#)\n  (util:or-setf\n   (gethash run *loggers*)\n   (make-instance 'logger\n                  :file (blob-pathname\n                         (promotion-log run)))\n   :thread-safe t))\n\n(defmethod promotion-logger ((run unchanged-run))\n  (make-instance 'noop-logger))\n\n(defmethod promotion-logger ((run recorder-run))\n  (call-next-method))\n\n(defmethod format-log (run #| or batch |#\n                       level message &rest args)\n  (let ((logger (promotion-logger run)))\n    (apply #'format-log\n           logger\n           level\n           message\n           args)))\n\n(defun %with-promotion-log (run fn)\n  (let ((*current-logger* run))\n    (unwind-protect\n         (progn\n           ;;(format *current-promotion-stream* \"BEGIN~%\")\n           (log :info \"Beginning promotion: ~S\" run)\n           (funcall fn))\n      (log :info \"End promotion\"))))\n\n;; In order to this this, you need\n;;\n\n\n(defmacro with-promotion-log ((run) &body body)\n  `(%with-promotion-log ,run (lambda () ,@body)))\n\n(defun fix-github-link (repo)\n  (cond\n    ((str:starts-with-p \"git@github.com:\" repo)\n     (cl-ppcre:regex-replace\n      \"[.]git$\"\n      (str:replace-all \"git@github.com:\" \"https://github.com/\" repo)\n      \"\"))\n    (t\n     repo)))\n\n\n(defclass master-promoter (promoter)\n  ())\n\n(register-promoter 'master-promoter)\n\n(defmethod maybe-promote :around (promoter run)\n  (with-extras ((\"run\" run))\n    (call-next-method)))\n\n(defmethod maybe-promote ((promoter master-promoter) run)\n  (maybe-promote-run run))\n\n(defclass delegating-promoter (promoter)\n  ((delegates :initarg :delegates\n              :accessor delegates)))\n\n(defclass default-promoter (delegating-promoter)\n  ((delegates :initform (list-promoters))))\n\n(def-easy-macro with-log-errors (run &fn fn)\n  \"Logs any errors to the promotion log\"\n  (handler-bind ((error (lambda (e)\n                          (format-log run :error \"Promotion error: ~a\" e))))\n    (fn)))\n\n\n(defmethod maybe-promote ((promoter delegating-promoter) run)\n  (restart-case\n      (dolist (delegate (delegates promoter))\n        (format-log run :info \"Delegating to promoter ~s\" delegate)\n        (ignore-and-log-errors ()\n          (with-log-errors (run)\n            (maybe-promote delegate run))))\n    (dangerous-restart-all-promotions ()\n      (maybe-promote promoter run))))\n\n(defmethod maybe-promote :before (promoter run)\n  (format-log run :info \"Running promoter: ~s on ~s\" promoter run))\n\n(defmethod maybe-send-tasks ((promoter delegating-promoter) run)\n  (dolist (delegate (delegates promoter))\n    (maybe-send-tasks delegate run)))\n\n(defmethod channel-ancestorp ((channel channel) commit1 commit2)\n  \"check if COMMIT1 is an ancestor of COMMIT2\"\n  (log :info \"Checking ancestry (channel: ~a): ~a ~a\" (store-object-id channel) commit1 commit2)\n  (restart-case\n      (repo-ancestor-p (channel-repo channel)\n                       commit1\n                       commit2)\n    (retry-channel-ancestor-p ()\n      (channel-ancestorp channel commit1 commit2))))\n\n\n(defmethod %channel-publicp ((channel channel))\n  (restart-case\n      (let ((publicp (public-repo-p (channel-repo channel))))\n        (with-transaction ()\n          (setf (publicp channel) publicp)))\n    (continue ()\n      nil)\n    (retry ()\n      (%channel-publicp channel))))\n\n(defun work-branch-matches-p (run)\n  \"If a work branch is present, it must match the main branch identically\"\n  (let ((work-branch (recorder-run-work-branch run))\n        (main-branch (recorder-run-branch run)))\n    (or\n     (str:emptyp work-branch)\n     (equal work-branch main-branch))))\n\n(define-condition parent-not-ready (error)\n  ((parent-commit :initarg :parent-commit\n                  :reader parent-commit)))\n\n(defmethod estimate-wait-timeout (channel)\n  \"Returns the timeout in minutes to wait for a previous commit's screenshots\"\n  (declare  (ignore channel))\n  (let ((config (config \"promotion.wait-timeout\")))\n    (cond\n      ((str:emptyp config)\n       1)\n      (t\n       (parse-integer config)))))\n\n(defun maybe-promote-run (run &rest args &key channel\n                                           wait-timeout\n                                           (start-time (get-universal-time)))\n  (declare (type recorder-run run)\n           (optimize (debug 3) (speed 0)))\n  (unless channel\n    (setf channel (recorder-run-channel run)))\n\n  (let ((wait-timeout (or wait-timeout\n                          (estimate-wait-timeout channel))))\n    (restart-case\n        (loop for i from 1 to 1000\n              do\n                 (handler-case\n                     (return (promotion-with-lock run channel :start-time start-time :wait-timeout wait-timeout))\n                   (parent-not-ready (e)\n                     ;; Why don't we just do an exponential backoff here? Well,\n                     ;; consider the case that there are 10 commits, and the last\n                     ;; nine are done: if the first commit comes in, some of the\n                     ;; promotions will be haphazard. (Because with exponential\n                     ;; backoff, definitely all 9 commits won't be able to complete\n                     ;; in the 5 minutes).\n                     (wait-for-run channel (parent-commit e) wait-timeout :minute)))\n              finally\n                 (error \"Promotion didn't succeed after 1000 attempts, we should not be here\"))\n      (retry-promote ()\n        (apply 'maybe-promote-run run args)))))\n\n(defun promotion-with-lock (run channel &key start-time wait-timeout)\n  (let ((*channel-repo-overrides*\n          (cons\n           (cons channel (github-repo run))\n           *channel-repo-overrides*)))\n    (bt:with-lock-held ((channel-promotion-lock channel))\n      (log :info \"Inside promotion logic\")\n      (let ((run-branch (recorder-run-branch run)))\n        (log :info \"Branch on run: ~a\" run-branch)\n        (cond\n          ((pull-request-id run)\n           ;; Note that we looked for pull-request-id, not\n           ;; pull-request-url. This is to handle the case of an\n           ;; invalid pull-request-url being provided because of\n           ;; custom CI scripts.\n           (log :error \"Looks like there's a Pull Request attached, not promoting\"))\n          ((not (work-branch-matches-p run))\n           (log :error \"Work-branch doesn't match main-branch ~a ~a\"\n                (recorder-run-work-branch run)\n                (recorder-run-branch run)))\n          ((gitlab-merge-request-iid run)\n           (log :error \"Looks like there's a GitLab Merge Request attached, not promoting\"))\n          ((periodic-job-p run)\n           (log :info \"This is a periodic job, running promotions\")\n           (finalize-promotion\n            run\n            (active-run channel (master-branch channel))\n            channel\n            run-branch))\n          ((null run-branch)\n           (log :error \"No branch set, not promoting\"))\n          (t\n           (maybe-promote-run-on-branch run channel run-branch\n                                        :start-time start-time\n                                        :wait-timeout wait-timeout)))))))\n\n(defmethod %maybe-promote-run-on-branch ((run recorder-run)\n                                         previous-run\n                                         (channel channel)\n                                         branch)\n  (declare (optimize debug))\n  (flet ((in-branch-p (commit sbranch)\n           (declare (optimize debug))\n           (let ((branch-commit\n                   (cond\n                     ((equal branch sbranch)\n                      (recorder-run-branch-hash run))\n                     (t\n                      sbranch))))\n            (channel-ancestorp channel commit branch-commit))))\n    (log :info \"previous run: ~s\" previous-run)\n\n    (when previous-run\n      (log :info \"Attempting switch: ~a -> ~a\"\n                (recorder-run-commit previous-run)\n                (recorder-run-commit run)))\n    (log :info \"According to run master has branch-hash ~A\"\n              (recorder-run-branch-hash run))\n    (cond\n      ((not (trunkp run))\n       (log :info  \"not promoting run because it's not production run\"))\n      ((not (recorder-run-branch-hash run))\n       (log :error  \"branch-hash not present in run\"))\n      ((not (in-branch-p (recorder-run-commit run) branch))\n       (log :error \"The commit ~a is not in branch ~a\"\n                  (recorder-run-commit run)\n                  branch))\n      ((and previous-run\n            (string= (recorder-run-commit previous-run)\n                     (recorder-run-commit run)))\n       (log :info \"The current promoted run is already on the same commit\"))\n      ((and previous-run  #| TAG, see below |#\n            (channel-ancestorp\n             channel\n             (recorder-run-commit run)\n             (recorder-run-commit previous-run)))\n       (log :info  \"The current run is an ancestor of the previous run, skipping promotion\"))\n      (t\n       (cond\n         ((not (if previous-run\n                   (eql previous-run (active-run channel branch))\n                   t))\n          (log :error \"Another run got promoted while we were debating this one.\"))\n         (t\n          (flet ((previous-run-is-ancestor-p ()\n                   (channel-ancestorp\n                    channel\n                    (recorder-run-commit previous-run)\n                    (recorder-run-commit run)))\n                 (call-finalize ()\n                   (finalize-promotion run previous-run\n                                       channel branch)))\n            ;; Because of the check on line marked `TAG`, we know run\n            ;; is not an ancestor of previous-run. If the other\n            ;; direction is also not true, then we apply the warning\n            ;;\n            ;; TODO(T1691) avoid non-fast-forwards if we know we can\n            ;; fast-forward later?\n            (cond\n              ((and previous-run (not (previous-run-is-ancestor-p)))\n               (cond\n                 ((< (store-object-id run)\n                     (store-object-id previous-run))\n                  (log :info \"Not promoting this because this run was created before the previous-run\"))\n                 (t\n                  (add-not-fast-forward-promotion-warning run)\n                  (call-finalize))))\n              (t\n               (call-finalize))))))))))\n\n(defun add-not-fast-forward-promotion-warning (run)\n  (push-event :not-fast-forward :run (oid run))\n  (push-run-warning run\n                    'not-fast-forward-promotion-warning))\n\n(defmethod find-complete-run ((channel channel) commit)\n  \"A synchronous way to find a run for a channel that is PROMOTION-COMPLETE-P.\n\n(As opposed to wait-for-run, which waits for such a run.)\"\n  (let ((run (production-run-for channel :commit commit)))\n    (when (and run (promotion-complete-p run))\n      run)))\n\n(defmethod wait-for-run ((channel channel) commit\n                     &optional (amount 15) (unit :minute))\n  \"Wait AMOUNT time (with UNIT) until there's just a production run on\n  channel with commit COMMIT. At the moment we do not wait for the\n  promotion to complete on this run.\"\n  (restart-case\n      (let ((end-time (local-time:timestamp+ (local-time:now) amount unit)))\n        (bt:with-lock-held ((channel-lock channel))\n          (loop while (local-time:timestamp<= (local-time:now) end-time)\n                do\n                   (anaphora:acond\n                     ((production-run-for channel :commit commit)\n                      (wait-until-run-complete it end-time :lock (channel-lock channel)\n                                                           :cv (channel-cv channel))\n                      (return t))\n                     (t\n                      ;; nothing to do. wait on the CV and try again\n                      (log :info \"Waiting for commit `~a` to be available (best effort, will not fail if it's unavailable)\" commit)\n                      (bt:condition-wait (channel-cv channel)\n                                         (channel-lock channel)\n                                         :timeout 15))))))\n    (ignore-and-dont-want-for-run ()\n      nil)))\n\n(defmethod wait-until-run-complete ((run recorder-run) end-time &key lock cv)\n  \"Wait until END-TIME for run to be PROMOTION-COMPLETE-P. If we reach\n  end time, then we just return\"\n  (loop while (local-time:timestamp<= (local-time:now) end-time)\n        do\n           (cond\n             ((promotion-complete-p run)\n              (return t))\n             (t\n              (log :info \"Waiting for run ~a to be completed\" run)\n              (bt:condition-wait cv lock :timeout 5)))))\n\n(defun maybe-promote-run-on-branch (run channel branch &key\n                                                         (start-time 0)\n                                                         wait-timeout)\n  (cond\n    ((str:emptyp (github-repo run))\n     (log :error \"No repo link provided, cannot promote\"))\n    ((null (recorder-run-commit run))\n     (log :error \"No commit specified, not promoting\"))\n    (t\n     (let ((parent-commit (get-parent-commit\n                           (channel-repo channel)\n                           (recorder-run-commit run))))\n       (cond\n        (parent-commit\n         (unless (find-complete-run channel parent-commit)\n           (cond\n             ((or\n               (< start-time (- (get-universal-time) wait-timeout))\n               ;; For testing, to ensure that it can be synchronous\n               (eql 0 wait-timeout))\n              (log :info \"Not waiting for the parent anymore\")\n              (values))\n             (t\n              (error 'parent-not-ready :parent-commit parent-commit)))))\n        (t\n         (log :info \"No parent commit at all, first commit in the repo?\")))\n       ;; TODO: Just because we found a run for the parent commit\n       ;; doesn't mean that it's the previous-run! Ideally, we want to\n       ;; wait a bit more until the commit of the previous-run matches\n       ;; parent-commit. For example, this might happen because of a\n       ;; merge-queue run: the merge queue triggers a run on the same\n       ;; commit which will be complete by the time it's on the main\n       ;; branch.\n       (let* ((previous-run\n                (active-run channel branch)))\n         (%maybe-promote-run-on-branch\n          run\n          previous-run\n          channel\n          branch))))))\n\n(auto-restart:with-auto-restart ()\n (defun finalize-promotion (run previous-run channel branch)\n   (log :info \"All checks passed, promoting run\")\n   (when previous-run\n     (assert (equal branch\n                    (recorder-run-branch run)))\n     (when (or (not (recorder-previous-run run))\n               (equal (master-branch channel)\n                      branch))\n       (with-transaction ()\n         (setf (recorder-previous-run run)\n               previous-run))))\n\n   (unless (equal branch (master-branch channel))\n     (with-transaction ()\n       (setf (master-branch channel)\n             branch)))\n\n   (with-transaction ()\n     (setf (active-run channel branch)\n           run)\n     (setf (github-repo channel)\n           (github-repo run)))\n\n   (let ((publicp (%channel-publicp channel)))\n     (with-transaction ()\n       (setf (publicp channel) publicp)))))\n\n(defmethod finalize-batch ((self batch)))\n\n(defmethod maybe-promote ((promoter master-promoter) (batch batch))\n  ;; Do nothing!\n  (values))\n\n(defmethod maybe-promote ((promoter master-promoter) (run unchanged-run))\n  ;; Do nothing!\n  (values))\n\n(defmethod maybe-send-tasks ((promoter master-promoter) (run unchanged-run))\n  (values))\n"
  },
  {
    "path": "src/screenshotbot/api/recorder-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/api/recorder-run\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/api/core\n        #:screenshotbot/api/promote\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/model/image\n        #:screenshotbot/model/channel\n        #:screenshotbot/promote-api\n        #:screenshotbot/api/image\n        #:screenshotbot/model/recorder-run)\n  (:import-from #:screenshotbot/api/promote\n                #:default-promo)\n  (:import-from #:screenshotbot/model/company\n                #:find-channel\n                #:company\n                #:find-or-create-channel\n                #:find-image-by-id\n                #:company-runs)\n  (:import-from #:screenshotbot/server\n                #:make-thread)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at\n                #:current-user\n                #:current-company)\n  (:import-from #:util #:oid)\n  (:import-from #:bknr.datastore\n                #:store-object-id\n                #:with-transaction)\n  (:import-from #:screenshotbot/model/company\n                #:add-company-run)\n  (:import-from #:screenshotbot/dashboard/image\n                #:handle-resized-image)\n  (:import-from #:util/store\n                #:location-for-oid)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:make-screenshot-map)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:run-id-to-run-map\n                #:shard-key\n                #:shard-screenshots\n                #:shard-number\n                #:shard-count\n                #:find-shards\n                #:shard\n                #:unchanged-run\n                #:recorder-run-author\n                #:recorder-run-tags\n                #:make-recorder-run)\n  (:import-from #:util/misc\n                #:not-null!\n                #:?.)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-link)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/model/batch\n                #:find-or-create-batch)\n  (:import-from #:util/throttler\n                #:throttler\n                #:throttle!\n                #:keyed-throttler)\n  (:import-from #:util/events\n                #:push-event\n                #:with-tracing)\n  (:import-from #:screenshotbot/screenshot-api\n                #:image-public-url)\n  (:import-from #:hunchentoot-extensions\n                #:make-full-url)\n  (:import-from #:util.cdn\n                #:*cdn-domain*)\n  (:import-from #:auth/viewer-context\n                #:viewer-context-api-key)\n  (:import-from #:core/api/model/api-key\n                #:transient-api-key\n                #:cli-api-key\n                #:api-key-permissions)\n  (:import-from #:screenshotbot/api/core\n                #:api-error)\n  (:import-from #:util/hash-lock\n                #:hash-lock\n                #:with-hash-lock-held)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:serapeum\n                #:collecting)\n  (:import-from #:screenshotbot/billing-meter\n                #:incr-billing-meter)\n  (:export\n   #:%recorder-run-post\n   #:run-response-id\n   #:start-promotion-thread\n   #:prepare-recorder-run)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/recorder-run)\n\n\n(defclass create-run-response (api-response)\n  ((type :initform \"run\")\n   (id :type integer\n       :initarg :id\n       :reader run-response-id)))\n\n(defparameter *synchronous-promotion* nil)\n\n(defvar *non-production-throttler* (make-instance 'keyed-throttler\n                                                  :tokens 1200)\n  \"A throttler for non-production runs (i.e. `ci record` and `ci verify`)\")\n\n(defvar *shard-hash-lock* (make-instance 'hash-lock\n                                         :test #'equal))\n\n\n(defun js-boolean (x)\n  (string= \"true\" x))\n\n\n(flet ((fix (x)\n         (let ((x (if (listp x) x (list x))))\n           (list (car x)\n                 (or (cadr x) (car x))\n                 (or (caddr x) 'identity))))\n       (nil-if-empty (x) (if (str:emptyp x) nil x)))\n (defparameter *run-meta-fields* (mapcar #'fix `((:pull-request nil ,#'nil-if-empty)\n                                                 (:branch-hash :main-branch-hash)\n                                                 :build-url\n                                                 :merge-base\n                                                 :override-commit-hash\n                                                 (:commit :commit-hash ,#'nil-if-empty)\n                                                 (:main-branch nil ,#'nil-if-empty)\n                                                 (:phabricator-diff-id nil ,#'nil-if-empty)\n                                                 (:gitlab-merge-request-iid nil ,#'nil-if-empty)\n                                                 (:github-repo nil ,#'nil-if-empty)))))\n\n\n(defapi (nil :uri \"/api/run\" :method :post) (channel screenshot-records\n                                                     commit ;; also in *run-meta-fields*\n                                                     (create-github-issue :parameter-type 'js-boolean)\n                                                     (is-trunk :parameter-type 'js-boolean)\n                                                     (periodic-job-p :parameter-type 'js-boolean)\n                                                     (is-clean :parameter-type 'js-boolean))\n  (let ((screenshot-records (json-mop:json-to-clos\n                             screenshot-records\n                             'dto:screenshot-list)))\n    (log:info \"creating run for ~a\" channel)\n\n    (log:info \"Got commit as: ~a, clean:~a trunk:~a\" commit is-clean is-trunk)\n\n    (multiple-value-bind (resp recorder-run)\n        (apply '%recorder-run-post\n         :channel channel\n         :screenshot-records screenshot-records\n         :create-github-issue-p create-github-issue\n         :is-trunk is-trunk\n         :periodic-job-p periodic-job-p\n         :is-clean is-clean\n         (loop for field in *run-meta-fields*\n               appending\n               (let ((field (car field)))\n                (list field (hunchentoot:parameter (str:downcase (string field)))))))\n      (after-run-created recorder-run)\n      resp)))\n\n(defparameter *fetch-throttler* (make-instance 'throttler\n                                               :tokens 1000))\n\n(defapi (api-run-get :uri \"/api/run/:oid\" :method :get :wrap-success nil) (oid)\n  (let ((run (util:find-by-oid oid)))\n    ;; If someone has an API key, prevent them from guessing object IDs.\n    (throttle! *fetch-throttler* :key (auth:current-company))\n\n    (auth:can-view! run)\n    (run-to-dto run :include-screenshots t)))\n\n(defun run-to-dto (run &key include-screenshots)\n  (make-instance 'dto:run\n                 :id (oid run)\n                 :url (quri:render-uri\n                       (quri:merge-uris\n                        (run-link run)\n                        (installation-domain (installation))))\n                 :screenshots\n                 (when include-screenshots\n                  (loop for screenshot in (recorder-run-screenshots run)\n                        if (screenshot-image screenshot) ;; only for testing convenience\n                          collect\n                          (make-instance 'dto:screenshot\n                                         :name (screenshot-name screenshot)\n                                         :url\n                                         (let ((*cdn-domain*\n                                                 (or *cdn-domain*\n                                                     (installation-domain *installation*))))\n                                           (util.cdn:make-cdn\n                                            (image-public-url\n                                             (screenshot-image screenshot)\n                                             :originalp t)))\n                                         :image-id (util:oid (screenshot-image screenshot)))))\n                 :main-branch-hash (recorder-run-branch run)\n                 :commit-hash (recorder-run-commit run)\n                 :author (recorder-run-author run)\n                 :main-branch (recorder-run-branch run)\n                 :merge-base (recorder-run-merge-base run)\n                 :channel (?. channel-name (recorder-run-channel run))\n                 :tags (recorder-run-tags run)\n                 :pull-request (pull-request-url run)))\n\n(defapi (api-run-put :uri \"/api/run\" :method :put :use-yason t) ()\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (let ((dto (json-mop:json-to-clos body 'dto:run)))\n      (destructuring-bind (resp run channel)\n          (%put-run (current-company) dto)\n        (declare (ignore resp channel))\n        (process-created-run run)))))\n\n(defmethod process-created-run ((run recorder-run))\n  (after-run-created run)\n  (run-to-dto run))\n\n(defmethod process-created-run ((self shard))\n  \"We only created a shard, not a run.\"\n  (make-instance 'dto:run\n                 :shard-spec (make-instance 'dto:shard-spec\n                                            :key (shard-key self)\n                                            :number (shard-number self)\n                                            :count (shard-count self))))\n\n(defmethod run-or-shard-to-dto (run)\n  (run-to-dto run))\n\n(defmethod run-or-shard-to-dto ((self shard))\n  (make-instance 'dto:run\n                 :shard-spec (make-instance 'dto:shard-spec)))\n\n(defun after-run-created (recorder-run)\n  (flet ((promotion ()\n           (declare (optimize (debug 3) (speed 0)))\n           (log:info \"Being promotion logic\")\n           (start-promotion-thread recorder-run)\n           (with-tracing (:warmup-image-caches)\n             (with-extras ((\"run\" recorder-run))\n              (warmup-image-caches recorder-run)))))\n    (cond\n      (*synchronous-promotion*\n       (promotion))\n      (t\n       (make-thread\n        #'promotion\n        :name (format nil \"Promotion ~a\" (recorder-run-channel recorder-run)))))))\n\n(defmethod warmup-image-caches (run)\n  (log:info \"Warming up small screenshots for ~s\" run)\n  (loop for screenshot in (recorder-run-screenshots run)\n        do\n           (progn\n             (handle-resized-image (screenshot-image screenshot)\n                                   :small :warmup t)))\n  (log:info \"Warming up full-page screenshots for ~s\" run)\n  (loop for screenshot in (recorder-run-screenshots run)\n        for i from 0\n        do\n           (when (> i 100)\n             (sleep 1))\n           (handle-resized-image (screenshot-image screenshot)\n                                 :full-page :warmup t)))\n\n(defmethod warmup-image-caches ((run unchanged-run))\n  (values))\n\n(defun %recorder-run-post (&rest args\n                           &key channel\n                             screenshot-records\n                             pull-request\n                             github-repo ;; also from meta-field\n                             branch ;; also from meta-field\n                             create-github-issue-p\n                             periodic-job-p\n                             (company (current-company))\n                             is-trunk is-clean\n                           &allow-other-keys)\n  (declare (optimize (debug 3) (speed 0))\n           (ignore pull-request))\n  (apply\n   'values\n   (let ((dto-run (apply 'make-instance 'dto:run\n                         :channel channel\n                         :create-github-issue-p create-github-issue-p\n                         :screenshots screenshot-records\n                         :periodic-job-p periodic-job-p\n                         :cleanp is-clean\n                         :trunkp is-trunk\n                         (loop for field in *run-meta-fields*\n                               appending\n                               (destructuring-bind (arg field-name fn) field\n                                 (list field-name\n                                       (funcall fn (getf args arg))))))))\n     (%put-run company dto-run))))\n\n(defun emptify (x)\n  (if (str:emptyp x) nil x))\n\n(define-condition validation-error (error)\n  ((message :initarg :message))\n  (:report (lambda (e out)\n             (format out (slot-value e 'message)))))\n\n(defmethod validate-dto ((run dto:run))\n  (flet ((verify (test message &rest args)\n           (unless test\n             (error 'validation-error\n                    :message\n                    (apply #'format nil message args)))))\n    (verify (< (length (dto:run-tags run)) 10)\n            \"Only 10 tags are allowed on a run\")\n    (dolist (tag (dto:run-tags run))\n      (verify (< (length tag) 300)\n              \"Tag too long: ~a\" tag))\n    (verify (<= (length (dto:run-metadata run)) 10)\n            \"Only 10 metadata items are allowed on a run\")\n    (dolist (metadata (dto:run-metadata run))\n      (verify (< (length (dto:metadata-key metadata)) 300)\n              \"Metadata key too long\")\n      (verify (< (length (dto:metadata-value metadata)) 10240)\n              \"Metadata value too long\"))\n    (when (dto:run-author run)\n      (verify (< (length (dto:run-author run)) 100)\n              \"Author name too long\"))\n\n    (when (dto:compare-pixel-tolerance run)\n      (verify (<= 0 (dto:compare-pixel-tolerance run) 16)\n              \"pixel-threshold must be between 0 and 16.\"))\n    (when-let ((shard (dto:shard-spec run)))\n      (verify (< (length (dto:shard-spec-key shard)) 200)\n              \"Shard key too long\")\n      (verify (<= 0 (dto:shard-spec-number shard)\n                  (dto:shard-spec-count shard))\n              \"Invalid shard number: ~a\" (dto:shard-spec-number shard))\n      (verify (and\n               (integerp (dto:shard-spec-number shard))\n               (integerp (dto:shard-spec-count shard)))\n              \"Shard number and count must be present\"))))\n\n(defmethod batch-for-run (company run)\n  (when-let ((batch (dto:run-batch run)))\n    (find-or-create-batch\n     :company company\n     :repo (dto:run-repo run)\n     :commit (or\n              (emptify (dto:override-commit-hash run))\n              (dto:run-commit run))\n     :name batch\n     :pull-request-url (dto:pull-request-url run)\n     :phabricator-diff-id (dto:phabricator-diff-id run))))\n\n(define-condition production-run-without-ci-permission (api-error)\n  ()\n  (:report \"Trying to create a CI run with a key that does not have the CI permission.\"))\n\n(defmethod has-ci-permission-p (api-key)\n  (member :ci (api-key-permissions api-key)))\n\n(defmethod has-ci-permission-p ((self cli-api-key))\n  nil)\n\n(defmethod has-ci-permission-p ((self transient-api-key))\n  t)\n\n(defmethod %put-run (company (run dto:run) &key (api-key\n                                                 (viewer-context-api-key\n                                                  (auth:viewer-context\n                                                   hunchentoot:*request*))))\n  (unless (dto:trunkp run)\n    (throttle! *non-production-throttler* :key (not-null! (current-user))))\n\n  (validate-dto run)\n\n  (when (and\n         (gk:check :api-key-roles company)\n         (dto:trunkp run)\n         api-key)\n    (unless (has-ci-permission-p api-key)\n      (error 'production-run-without-ci-permission)))\n\n  (let* ((channel (find-or-create-channel company (dto:run-channel run)))\n         (screenshots (with-tracing (\"screenshot-records-api-to-internal\")\n                       (screenshot-records-api-to-internal\n                        company\n                        channel\n                        (dto:run-screenshots run)))))\n    (cond\n      ((dto:shard-spec run)\n       (let ((shard (dto:shard-spec run)))\n         (with-hash-lock-held ((list company (dto:shard-spec-key shard))\n                               *shard-hash-lock*)\n           #+nil\n           (push-event :create.shard :name (dto:shard-spec-key shard) :company company)\n           (let ((persisted-shard (make-instance 'shard\n                                                 :company company\n                                                 :channel channel\n                                                 :key (dto:shard-spec-key shard)\n                                                 :number (dto:shard-spec-number shard)\n                                                 :count (dto:shard-spec-count shard)\n                                                 :screenshots screenshots)))\n             (cond\n              ((shard-complete-p channel (dto:shard-spec-key shard))\n               (prog1\n                   (%put-run-helper run\n                                    :company company\n                                    :channel channel\n                                    :screenshots (build-shard-screenshots\n                                                  channel\n                                                  (dto:shard-spec-key shard)))\n                 (mapcar #'bknr.datastore:delete-object (find-shards channel\n                                                                     (dto:shard-spec-key shard)))))\n              (t\n               (list nil persisted-shard nil)))))))\n      (t\n       (%put-run-helper run :company company\n                            :channel channel\n                            :screenshots screenshots)))))\n\n(defun build-shard-screenshots (channel shard-key)\n  (check-type channel channel)\n  (when-let* ((shards (find-shards channel shard-key))\n              (count (shard-count (car shards))))\n    (dolist (shard shards)\n      (assert (= count (shard-count shard))))\n    (let ((res (make-array count :initial-element nil)))\n      (dolist (shard shards)\n        (util:or-setf\n         (aref res (mod (shard-number shard) count))\n         shard))\n      (cond\n        ((not (every #'identity (loop for shard across res\n                                      collect shard)))\n         (values nil nil))\n        (t\n         (values\n          (loop for shard across res\n                appending (shard-screenshots shard))\n          t))))))\n\n(defun shard-complete-p (channel shard-key)\n  (nth-value 1 (build-shard-screenshots channel shard-key)))\n\n(defun parse-metadata (metadata-list)\n  (loop for metadata in metadata-list\n        collect (cons (dto:metadata-key metadata)\n                      (dto:metadata-value metadata))))\n\n(defun default-pixel-tolerance (company)\n  \"Add a global pixel tolerance for all runs in the company, useful if\nthe company is having flaky screenshots.\"\n  (cond\n    ((gk:check :default-pixel-tolerance company)\n     4)\n    (t\n     0)))\n\n(defun %put-run-helper (run &key\n                              (company (error \"provide :company\"))\n                              (channel (error \"provide :channel\"))\n                              (screenshots (error \"provide :screenshots\")))\n  \"Step two of %put-run, after validations and after we've processed\nsomethings like SCREENSHOTS. In particular SCREENSHOTS might be\ncomputed differently if we're using sharding.\"\n  (let* ((batch (batch-for-run company run))\n         (recorder-run (make-recorder-run\n                        :company company\n                        :channel channel\n                        :batch batch\n                        :screenshots screenshots\n                        :metadata (acons\n                                   \"client-version\" (when (boundp 'hunchentoot:*request*)\n                                                      (hunchentoot:header-in* :x-client-version))\n                                   (parse-metadata (dto:run-metadata run)))\n                        :commit-hash (dto:run-commit run)\n                        :author (dto:run-author run)\n                        :create-github-issue-p (dto:should-create-github-issue-p run)\n                        :trunkp (dto:trunkp run)\n                        :periodic-job-p (dto:periodic-job-p run)\n                        :cleanp (dto:cleanp run)\n                        :pull-request (dto:pull-request-url run)\n                        :release-branch-p (dto:release-branch-p run)\n                        :branch (cond\n                                  ((dto:release-branch-p run)\n                                   ;; Heads up: we're logging the\n                                   ;; old main-branch in the\n                                   ;; :run-created event below.\n                                   (dto:work-branch run))\n                                  (t\n                                   (dto:main-branch run)))\n                        :work-branch (dto:work-branch run)\n                        :branch-hash (dto:main-branch-hash run)\n                        :override-commit-hash (dto:override-commit-hash run)\n                        :build-url (dto:build-url run)\n                        :merge-base (dto:merge-base run)\n                        :phabricator-diff-id (dto:phabricator-diff-id run)\n                        :gitlab-merge-request-iid (dto:gitlab-merge-request-iid run)\n                        :github-repo (dto:run-repo run)\n                        :tags (dto:run-tags run)\n                        :compare-threshold (dto:compare-threshold run)\n                        :compare-tolerance\n                        (max\n                         (dto:compare-pixel-tolerance run)\n                         (default-pixel-tolerance company)))))\n    (incr-billing-meter\n     company\n     :screenshot\n     (length screenshots))\n    (with-transaction ()\n      (setf (channel-branch channel) (dto:main-branch run)))\n    (with-transaction ()\n      (setf (github-repo channel)\n            (dto:run-repo run)))\n    (push-event :run-created\n                :oid (oid recorder-run)\n                :company (oid company)\n                ;; We're logging this in case we want to verify the\n                ;; screenshot-map is behaving correctly.\n                :original-main-branch (dto:work-branch run)\n                :num-screenshots (length screenshots)\n                ;; This let's us monitor that a specific company is\n                ;; using the right domain name. See T1124.\n                :api-hostname (when (boundp 'hunchentoot:*request*)\n                                (or\n                                 (hunchentoot:header-in* :x-forwarded-host)\n                                 (hunchentoot:host))))\n    (prepare-recorder-run :run recorder-run)\n    (list\n     (make-instance 'create-run-response\n                    :id (store-object-id recorder-run))\n     recorder-run\n     channel)))\n\n(defun prepare-recorder-run (&key (run (error \"must provide run\")))\n  \"Common preparation steps for a recorder-run, even before the\npromotion thread starts. Used by the API and by Replay\"\n  (let ((channel (recorder-run-channel run))\n        (company (recorder-run-company run)))\n    (check-type channel channel)\n    (check-type company company)\n\n    (add-company-run company run)\n    (bt:with-lock-held ((channel-lock channel))\n      (bt:condition-notify (channel-cv channel)))))\n\n(defun start-promotion-thread (run)\n  (with-tracing (:promotion-time :run (format nil \"~a\" run))\n    (let ((channel (recorder-run-channel run)))\n      (unwind-protect\n           (with-promotion-log (run)\n             (unwind-protect\n                  (let ((promoter (make-instance 'default-promoter)))\n                    (maybe-promote promoter run)\n                    (maybe-send-tasks promoter run))\n               (with-transaction ()\n                 (setf (promotion-complete-p run) t))\n               (bt:with-lock-held ((channel-lock channel))\n                 (bt:condition-notify (channel-cv channel)))))))))\n\n(defun screenshot-records-api-to-internal (company channel screenshot-records)\n  \"Convert the json list of screenshot-records to a list of SCREENSHOT\n  objects\"\n  (loop for rec in screenshot-records\n        collect\n        (let ((name (dto:screenshot-name rec))\n              (image-id (dto:screenshot-image-id rec))\n              (lang (dto:screenshot-lang rec))\n              (device (dto:screenshot-device rec)))\n          (assert name)\n          (assert image-id)\n          (let ((image (find-image-by-id company image-id)))\n            (unless image\n              (error \"could not find image-id: ~a\" image-id))\n            (make-screenshot-for-channel\n             channel\n             :name name\n             :lang lang\n             :device device\n             :image image)))))\n\n(defun make-screenshot-for-channel (channel &rest args)\n  (let ((masks (assoc-value (masks channel)\n                            (getf args :name)\n                            :test 'string=)))\n    (apply 'make-screenshot :masks masks args)))\n\n(defapi (%find-runs :uri \"/api/run\" :method :get :wrap-success nil)\n        (commit)\n  \"Find all the runs that match the given requirements. Currently, only\nCOMMIT is supported.\"\n  (or\n   ;; The sorting is only to make it deterministic for tests\n   (mapcar\n    #'run-to-dto\n    (sort\n     (when commit\n       (collecting\n         (let ((run-id-map (run-id-to-run-map (auth:current-company))))\n           (fset:do-map (run-id run run-id-map)\n             (declare (ignore run-id))\n             (when (equal (recorder-run-commit run) commit)\n               (collect run))))))\n     #'>\n     :key #'%created-at))\n   #()))\n\n\n(defapi (%find-base-run :uri \"/api/find-base-run\" :wrap-success nil) (channel commit)\n  \"Find an appropriate base run for the given channel and commit.\n\nIf we could not find an appropriate run, then we'll return nil\"\n  (let* ((channel-name channel)\n         (channel (find-channel (auth:current-company) channel-name)))\n    (unless channel\n      (api-error \"No such channel: ~a\" channel-name))\n    (let ((run (production-run-for channel :commit commit)))\n      (cond\n        ((null run)\n         nil)\n        (t\n         (auth:can-view! run)\n         (run-to-dto run))))))\n"
  },
  {
    "path": "src/screenshotbot/api/replay-asset.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/replay-asset\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:core #:screenshotbot/api/core)))\n(in-package :screenshotbot/api/replay-asset)\n"
  },
  {
    "path": "src/screenshotbot/api/report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/report\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:screenshotbot/report-api\n                #:report-acceptable)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/model/report\n                #:report-to-dto\n                #:acceptable-state))\n(in-package :screenshotbot/api/report)\n\n(defapi (%report-accept :uri \"/api/report/:oid/review/accept\"\n                        :method :post\n                        :wrap-success t)\n        (oid)\n  (let ((report (util:find-by-oid oid)))\n    (auth:can-view! report)\n    (when-let ((acceptable (report-acceptable report)))\n      (setf (acceptable-state acceptable) :accepted))\n    (report-to-dto report)))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/test-active-runs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-active-runs\n  (:use #:cl\n        #:fiveam))\n(in-package :screenshotbot/api/test-active-runs)\n\n(util/fiveam:def-suite)\n\n"
  },
  {
    "path": "src/screenshotbot/api/test-analytics-event.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :screenshotbot/api/test-analytics-event\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/api/analytics-event\n                #:parse-all-events)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :screenshotbot/api/test-analytics-event)\n\n\n(util/fiveam:def-suite)\n\n(test simple-analytics-event\n  (with-fake-request ()\n    (parse-all-events\n     \"[{\\\"foo\\\":\\\"bar\\\"}]\" )))\n"
  },
  {
    "path": "src/screenshotbot/api/test-batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-batch\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-item\n                #:batch-commit\n                #:find-or-create-batch)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/api/batch\n                #:get-reports\n                #:make-batch-from-dto\n                #:batch-to-dto)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run-company)\n  (:import-from #:screenshotbot/user-api\n                #:channel\n                #:recorder-run-commit)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-batch)\n\n\n(util/fiveam:def-suite)\n\n(defun make-hash (str)\n  (replace\n   (copy-seq \"0000000000000000000000000000000000000000\")\n   str))\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-test-user (:logged-in-p t\n                     :company company)\n     (let* ((batch (find-or-create-batch\n                    :company company\n                    :name \"foo\"\n                    :repo \"https://github.com/tdrhq/fast-example\"\n                    :commit (make-hash \"abcd\")))\n            (channel (make-instance 'channel\n                                    :company company))\n            (run (make-recorder-run\n                  :channel channel\n                  :company company\n                  :screenshots nil)))\n       (&body)))))\n\n(test batch-to-dto-happy-path\n  (with-fixture state ()\n    (let ((dto (batch-to-dto batch)))\n      (is (equal (make-hash \"abcd\") (dto:batch-commit dto))))))\n\n(test make-batch-from-dto\n  (with-fixture state ()\n    (let ((dto (make-instance 'dto:batch\n                              :name \"foo\"\n                              :github-repo \"https://github.com/tdrhq/fast-example\"\n                              :commit (make-hash \"abcdef\"))))\n      (let ((batch (make-batch-from-dto dto company)))\n        (is (eql company (recorder-run-company batch)))\n        (is (equal (make-hash \"abcdef\") (batch-commit batch)))))))\n\n(test get-reports\n  (with-fixture state ()\n    (is (equalp #()  (get-reports :oid (oid batch))))\n    (make-instance 'batch-item\n                   :batch batch\n                   :report (make-instance 'report\n                                          :run run\n                                          :previous-run nil))\n    (assert-that (coerce (get-reports :oid (oid batch)) 'list)\n                 (contains\n                  (has-typep 'dto:report)))))\n\n(test get-reports-when-some-reports-are-NIL\n  (with-fixture state ()\n    (is (equalp #()  (get-reports :oid (oid batch))))\n    (make-instance 'batch-item\n                   :batch batch\n                   :report (make-instance 'report\n                                          :run run\n                                          :previous-run nil))\n    (make-instance 'batch-item\n                   :batch batch\n                   :run run)\n    (assert-that (coerce (get-reports :oid (oid batch)) 'list)\n                 (contains\n                  (has-typep 'dto:report)))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-build-info.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-build-info\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/api/build-info\n                #:%get-build-info\n                #:%post-build-info\n                #:to-dto)\n  (:import-from #:screenshotbot/model/build-info\n                #:build-info\n                #:find-build-info\n                #:find-or-create-build-info\n                #:build-url\n                #:build-info-repo-url)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:screenshotbot/api/model\n                #:build-info-build-url)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-build-info)\n\n(util/fiveam:def-suite)\n\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n   (with-installation ()\n     (with-test-store ()\n       (with-test-user (:company company :logged-in-p t)\n         (auth:with-sessions ()\n           (&body)))))))\n\n(test to-dto-conversion\n  (with-fixture state ()\n    (let ((build-info (make-instance 'build-info\n                                     :build-url \"https://ci.example.com/build/123\"\n                                     :company company\n                                     :repo-url \"https://github.com/example/repo\")))\n      (let ((dto (to-dto build-info)))\n        (is (string= \"https://ci.example.com/build/123\" (dto:build-info-build-url dto)))\n        (is (string= \"https://github.com/example/repo\" (dto:build-info-repo-url dto)))))))\n\n(test get-build-info-existing\n  (with-fixture state ()\n    (let ((build-info (find-or-create-build-info company \"https://ci.example.com/build/456\")))\n      (setf (build-info-repo-url build-info) \"https://github.com/example/repo2\")\n      (let ((result (%get-build-info :build-url \"https://ci.example.com/build/456\")))\n        (is (not (null result)))\n        (is (string= \"https://ci.example.com/build/456\" (dto:build-info-build-url result)))\n        (is (string= \"https://github.com/example/repo2\" (dto:build-info-repo-url result)))))))\n\n(test get-build-info-non-existing\n  (with-fixture state ()\n    (let ((result (%get-build-info :build-url \"https://ci.example.com/nonexistent\")))\n      (is (null result)))))\n\n(test post-build-info-create-new\n  (with-fixture state ()\n    (let ((input (make-instance 'dto:build-info\n                                :build-url \"https://ci.example.com/build/789\"\n                                :repo-url \"https://github.com/example/repo3\")))\n      (cl-mock:if-called 'hunchentoot:raw-post-data\n                         (lambda (&rest args)\n                           (dto:encode-json input)))\n      (let ((result (%post-build-info)))\n        (is (not (null result)))\n        (is (string= \"https://ci.example.com/build/789\" (build-info-build-url result)))\n        (is (string= \"https://github.com/example/repo3\" (dto:build-info-repo-url result)))\n        (let ((stored (find-build-info company \"https://ci.example.com/build/789\")))\n          (is (not (null stored)))\n          (is (string= \"https://github.com/example/repo3\" (build-info-repo-url stored))))))))\n\n(test post-build-info-update-existing\n  (with-fixture state ()\n    (let ((existing (find-or-create-build-info company \"https://ci.example.com/build/update\")))\n      (setf (build-info-repo-url existing) \"https://github.com/example/old-repo\")\n      (let ((input (make-instance 'dto:build-info\n                                  :build-url \"https://ci.example.com/build/update\"\n                                  :repo-url \"https://github.com/example/new-repo\")))\n        (cl-mock:if-called 'hunchentoot:raw-post-data\n                           (lambda (&rest args)\n                             (dto:encode-json input)))\n        (let ((result (%post-build-info)))\n          (is (not (null result)))\n          (is (string= \"https://ci.example.com/build/update\" (build-info-build-url result)))\n          (is (string= \"https://github.com/example/new-repo\" (dto:build-info-repo-url result)))\n          (is (string= \"https://github.com/example/new-repo\" (build-info-repo-url existing))))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/test-commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-commit-graph\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/api/commit-graph\n                #:%check-shas\n                #:get-refs\n                #:update-refs\n                #:update-commit-graph\n                #:%update-commit-graph-v2)\n  (:import-from #:screenshotbot/git-repo\n                #:find-or-create-commit-graph)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:commit-graph-refs)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:contains)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-commit-graph)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-test-user (:logged-in-p t)\n     (&body))))\n\n(test update-commit-graph-happy-path ()\n  (with-fixture state ()\n   (let ((dag (make-instance 'dag:dag)))\n     (dag:add-commit dag (make-instance 'dag:commit\n                                        :sha \"abcd\"\n                                        :author \"zoidberg\") )\n     (finishes\n       (%update-commit-graph-v2 \"https://github.com/foo/bar\"\n                                (with-output-to-string (out)\n                                  (dag:write-to-stream dag out)))))))\n\n(test check-shas ()\n  (with-fixture state ()\n   (let ((dag (make-instance 'dag:dag)))\n     (dag:add-commit dag (make-instance 'dag:commit\n                                        :sha \"abcd\"\n                                        :author \"zoidberg\") )\n     (finishes\n       (%update-commit-graph-v2 \"https://github.com/foo/bar\"\n                                (with-output-to-string (out)\n                                  (dag:write-to-stream dag out))))\n     (assert-that\n      (%check-shas :repo-url \"https://github.com/foo/bar\" :shas (json:encode-json-to-string (list \"abcd\" \"0011\")))\n      (contains \"0011\")))))\n\n\n(test cannot-update-commit-graph-with-empty-repo\n  (with-fixture state ()\n    (finishes\n     (update-commit-graph\n      :repo-url \"\"\n      :graph-json \"{}\"\n      :format \"json\"))\n    (signals error\n      (update-commit-graph\n      :repo-url nil\n      :graph-json \"{}\"\n      :format \"json\"))))\n\n(test update-commit-graph-with-refs\n  (with-fixture state ()\n    (finishes\n     (update-commit-graph\n      :repo-url \"\"\n      :graph-json \"{}\"\n      :format \"json\"\n      :refs \"[]\"))))\n\n(test update-commit-graph-without-refs\n  (with-fixture state ()\n    (finishes\n     (update-commit-graph\n      :repo-url \"\"\n      :graph-json \"{}\"\n      :format \"json\"\n      :refs nil))))\n\n(defvar *repo* \"https://github.com/tdrhq/fast-example.git\")\n\n(test update-refs\n  (with-fixture state ()\n   (finishes\n     (update-refs :repo-url *repo*\n                  :refs \"[{\\\"name\\\":\\\"master\\\",\\\"sha\\\":\\\"abcd\\\"}]\"))\n    (let ((cg (find-or-create-commit-graph (auth:current-company) *repo*)))\n      (assert-that\n       (fset:convert 'list (commit-graph-refs cg))\n       (contains\n        '(\"master\" . \"abcd\")))\n      (update-refs :repo-url *repo*\n                   :refs \"[{\\\"name\\\":\\\"master\\\",\\\"sha\\\":\\\"0011\\\"},{\\\"name\\\":\\\"feature\\\",\\\"sha\\\":\\\"abcd\\\"}]\")\n      (assert-that\n       (fset:convert 'list (commit-graph-refs cg))\n       (contains-in-any-order\n        '(\"master\" . \"0011\")\n        '(\"feature\" . \"abcd\"))))))\n\n(test get-refs\n  (with-fixture state ()\n    (finishes\n      (update-refs :repo-url *repo*\n                   :refs \"[{\\\"name\\\":\\\"master\\\",\\\"sha\\\":\\\"abcd\\\"}]\"))\n    (assert-that (get-refs :repo-url *repo*)\n                 (contains\n                  (has-typep 'dto:git-ref)))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-compare.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-compare\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/api/compare\n                #:%api-compare-runs\n                #:api-compare-runs)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:current-user)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-compare)\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'my-installation)))\n    (with-test-store ()\n      (with-test-user (:user user :company company)\n        (with-fake-request ()\n         (auth:with-sessions ()\n           (tmpdir:with-tmpdir (dir)\n             (let ((im1 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\"))\n                   (im2 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-2.png\"))\n                   (im3 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-3.png\"))\n                   (objs))\n               (flet ((make-screenshot (img)\n                        (let* ((image (make-image :pathname img :for-tests t)))\n                          (make-screenshot\n                           :name \"foobar\"\n                           :image image))))\n                 (setf (current-user) user)\n                 (setf (current-company) company)\n\n                 (&body))))))))))\n\n(test compare-identical-runs\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :company company\n                :screenshots (list (make-screenshot im1))))\n          (run2 (make-recorder-run\n                 :company company\n                 :screenshots (list (make-screenshot im1)))))\n      (let ((compare (%api-compare-runs run run2)))\n        (is (eql t (dto:comparison-samep compare)))))))\n\n(test compare-different-runs\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :company company\n                :screenshots (list (make-screenshot im1))))\n          (run2 (make-recorder-run\n                 :company company\n                 :screenshots (list (make-screenshot im2)))))\n      (let ((compare (%api-compare-runs run run2)))\n        (is (eql nil (dto:comparison-samep compare)))\n        (is (equal \"1 changes\"\n                   (dto:comparison-title compare)))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-core.lisp",
    "content": "(defpackage :screenshotbot/api/test-core\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/api/core\n                #:*wrap-internal-errors*\n                #:authenticate-request-from-key\n                #:authenticate-api-request\n                #:result\n                #:api-error\n                #:error-result-message\n                #:error-result-stacktrace\n                #:with-error-handling\n                #:with-api-key\n                #:defapi)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer\n                #:with-mocks)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:equal-to)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:util/json-mop\n                #:json-mop-to-string)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:core/api/model/api-key\n                #:encode-api-token\n                #:api-key\n                #:api-key-secret-key)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:auth/model/roles\n                #:user-role)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/api/test-core)\n\n\n(util/fiveam:def-suite)\n\n(defapi (%dummy-1 :uri \"/api/dummy\") ()\n  \"OK\")\n\n(test returns\n  (is (equal \"OK\" (%dummy-1))))\n\n(defapi (%dummy-2 :uri \"/api/dummy-2\") (name)\n  (format nil \"OK ~a\" name))\n\n(test simple-param\n  (is (equal \"OK zoidberg\"\n             (%dummy-2 :name \"zoidberg\"))))\n\n(defapi (%dummy-with-int :uri \"/api/dummy-2\") ((name :parameter-type 'integer))\n  (format nil \"OK ~d\" name))\n\n(test dummy-with-int\n  (is (equal \"OK 3\"\n             (%dummy-with-int :name 3))))\n\n(define-condition my-error (error)\n  ())\n\n(test with-api-key-for-parameters\n  (with-mocks ()\n    (answer (hunchentoot:authorization) nil)\n    (answer (hunchentoot:parameter \"api-key\")  \"foo\")\n    (answer (hunchentoot:parameter \"api-secret-key\") \"bar\")\n    (with-api-key (key secret)\n      (is (equal \"foo\" key))\n      (is (equal \"bar\" secret)))))\n\n(test with-api-key-for-authorization\n  (with-mocks ()\n    (answer (hunchentoot:authorization)\n      (values \"foo\" \"bar\"))\n    (with-api-key (key secret)\n      (is (equal \"foo\" key))\n      (is (equal \"bar\" secret)))))\n\n(test internal-error-gets-logged\n  (with-mocks ()\n    (with-fake-request ()\n     (let ((calledp nil))\n       (if-called 'sentry-client:capture-exception\n                  (lambda (e)\n                    (setf calledp t)))\n       (let ((message\n               (with-error-handling ()\n                 (error 'my-error))))\n\n         (assert-that (error-result-stacktrace message)\n                      (contains-string \"Stacktrace ID\" ))\n         (assert-that (error-result-message message)\n                      (contains-string \"Internal error\"))\n         (assert-that calledp\n                      (described-as\n                          \"capture-exception should've been called\"\n                        (equal-to t))))))))\n\n(define-condition my-simple-error-1 (error)\n  ())\n\n(test flag-to-wrap-internal-errors\n  (let ((*wrap-internal-errors* nil))\n    (signals my-simple-error-1\n      (with-error-handling ()\n        (error 'my-simple-error-1)))))\n\n\n(test api-error-is-propagated-but-not-logged\n  (with-mocks ()\n    (with-fake-request ()\n     (let ((calledp nil))\n       (if-called 'sentry-client:capture-exception\n                  (lambda (e)\n                    (setf calledp t)))\n       (let ((message\n               (with-error-handling ()\n                 (error 'api-error :message \"bleh bleh\"))))\n\n         (assert-that (error-result-stacktrace message)\n                      (contains-string \"Stacktrace ID\" ))\n         (assert-that (error-result-message message)\n                      (equal-to \"bleh bleh\"))\n         (assert-that calledp\n                      (described-as\n                          \"capture-exception should not be called\"\n                        (equal-to nil))))))))\n\n\n(test api-result-can-be-encoded\n  (assert-that (json-mop-to-string (make-instance 'result :success t))\n               (contains-string \"success\")))\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(def-fixture state ()\n  (with-installation (:installation (make-instance 'my-installation))\n   (with-test-store ()\n     (with-test-user (:user user :company company)\n       (let ((api-key (make-instance 'api-key\n                                     :user user\n                                     :company company)))\n         (&body))))))\n\n(test authenticate-api-request\n  (with-fixture state ()\n    (with-fake-request ()\n      (finishes\n       (authenticate-request-from-key hunchentoot:*request*\n                                      api-key)))))\n\n(test if-you-create-an-api-key-for-a-company-you-no-longer-can-view\n  (with-fixture state ()\n    (with-fake-request ()\n      (setf (user-role company user) nil)\n      ;; The API key might still be used by CI jobs! See T1138\n      (finishes\n        (authenticate-request-from-key hunchentoot:*request*\n                                       api-key)))))\n\n(test authenticate-api-request-extracts-key-from-secret\n  \"Test that API key is automatically extracted from API secret when not provided\"\n  (with-fixture state ()\n    (with-fake-request ()\n      (with-mocks ()\n        ;; Provide empty api-key but full encoded token as api-secret\n        (let ((encoded-token (encode-api-token api-key)))\n          (answer (hunchentoot:authorization) (values \"\" encoded-token))\n          (finishes\n            (authenticate-api-request hunchentoot:*request*)))))))\n\n(test authenticate-api-request-fails-with-mismatched-key\n  \"Test that authentication fails when encoded secret has mismatched API key\"\n  (with-fixture state ()\n    (with-fake-request ()\n      (with-mocks ()\n        ;; Provide a different api-key than what's in the encoded token\n        (let ((encoded-token (encode-api-token api-key)))\n          (answer (hunchentoot:authorization) (values \"wrong-key\" encoded-token))\n          (signals api-error\n            (authenticate-api-request hunchentoot:*request*)))))))\n\n(test authenticate-api-request-fails-without-key-and-plain-secret\n  \"Test that authentication fails when using plain secret without API key\"\n  (with-fixture state ()\n    (with-fake-request ()\n      (with-mocks ()\n        ;; Provide empty api-key and plain (non-encoded) secret\n        ;; This should fail because we can't extract key from plain secret\n        (let ((plain-secret (api-key-secret-key api-key)))\n          (answer (hunchentoot:authorization) (values \"\" plain-secret))\n          (signals api-error\n            (authenticate-api-request hunchentoot:*request*)))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-failed-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-failed-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/api/failed-run\n                #:%list-failed-runs\n                #:parse-body\n                #:%put-failed-run)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:multi-org-test-installation\n                #:with-test-user)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-failed-run)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (with-installation (:installation (make-instance 'multi-org-test-installation))\n     (with-test-store ()\n       (&body)))))\n\n(test simple-put-list\n  (with-fixture state ()\n    (let ((failed-run-dto (make-instance 'dto:failed-run\n                                         :channel \"bleh\"\n                                         :commit \"foo\")))\n      (answer (parse-body 'dto:failed-run)\n        failed-run-dto)\n      (with-test-user (:logged-in-p t)\n        (finishes\n          (%put-failed-run))\n        (is (eql 1 (length (%list-failed-runs))))\n        (is (equal \"bleh\" (dto:failed-run-channel (car (%list-failed-runs))))))\n      (with-test-user (:logged-in-p t :company-name \"two\")\n        (is (eql 0 (length (%list-failed-runs))))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-finalized-commit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-finalized-commit\n  (:use #:cl\n        #:fiveam\n        #:fiveam-matchers)\n  (:import-from #:screenshotbot/api/finalized-commit\n                #:trigger-callbacks\n                #:%post-finalized-commit\n                #:%parse-body)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:screenshotbot/model/finalized-commit\n                #:commit-finalized-p\n                #:finalized-commit)\n  (:import-from #:screenshotbot/model/batch\n                #:find-or-create-batch\n                #:finalize-batch)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-finalized-commit)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (with-test-store ()\n      (let ((company (make-instance 'company)))\n        (&body)))))\n\n(test simple-post\n  (with-fixture state ()\n    (let ((dto (make-instance 'dto:finalized-commit\n                              :commit \"abcd0000\")))\n      (answer (%parse-body)\n        dto)\n      (with-test-user (:logged-in-p t :company company)\n        (finishes (%post-finalized-commit))\n        (is (eql 1 (length (bknr.datastore:class-instances\n                            'finalized-commit))))\n        (is-true (commit-finalized-p company \"abcd0000\"))))))\n\n(test trigger-callbacks-integration-happy-path\n  (with-fixture state ()\n    (let ((called nil))\n      (if-called 'finalize-batch\n                 (lambda (batch)\n                   (setf called t)))\n      (let ((batch (find-or-create-batch\n                    :repo \"https://foo.git\"\n                    :company company\n                    :commit \"abcd\"\n                    :name \"foobar\")))\n        (trigger-callbacks (make-instance 'finalized-commit\n                                          :commit \"abcd\"\n                                          :company company))\n        (is-true called)))))\n\n(test duplicate-commits-should-be-deduped\n  \"Demonstrates that posting the same commit multiple times creates duplicates.\n   This should be fixed to deduplicate by company+commit.\"\n  (with-fixture state ()\n    (let ((dto (make-instance 'dto:finalized-commit\n                              :commit \"abcd0000\")))\n      (answer (%parse-body)\n        dto)\n      (with-test-user (:logged-in-p t :company company)\n        ;; First call\n        (finishes (%post-finalized-commit))\n        (is (eql 1 (length (bknr.datastore:class-instances\n                            'finalized-commit))))\n\n        ;; Second call with same commit - should NOT create a duplicate\n        (finishes (%post-finalized-commit))\n\n        (is (eql 1 (length (bknr.datastore:class-instances\n                            'finalized-commit))))\n\n        (is-true (commit-finalized-p company \"abcd0000\"))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/api/test-image\n  (:use #:cl\n        #:alexandria\n        #:bknr.datastore\n        #:fiveam)\n  (:import-from #:util\n                #:oid)\n  (:import-from #:screenshotbot/api/image\n                #:verify-and-upload-from-path\n                #:upload-response-upload-url\n                #:upload-response-id\n                #:prepare-upload-api-api-handler\n                #:prepare-upload-api\n                #:*use-blob-store-p*\n                #:*build-presigned-put*)\n  (:import-from #:screenshotbot/model/company\n                #:verified-p\n                #:company)\n  (:import-from #:screenshotbot/model/user\n                #:user)\n  (:import-from #:screenshotbot/model/image\n                #:with-local-image\n                #:image)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/installation\n                #:*installation*\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/api/core\n                #:api-error))\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'my-installation)))\n   (with-test-store ()\n     (with-fake-request ()\n       (with-test-user (:company company\n                        :user user\n                        :api-key api-key\n                        :logged-in-p t)\n         (let ((*build-presigned-put* (lambda (bucket key) \"https://example.com\")))\n           (&body)))))))\n\n(test simple-upload\n  (with-fixture state ()\n    (let ((response (prepare-upload-api\n                 :hash \"abcd\"\n                 :content-type \"image/png\")))\n      (is (not (str:emptyp (upload-response-id response))))\n      (is-true (upload-response-upload-url response)))))\n\n(test reupload-same-url\n  (with-fixture state ()\n    (let ((old-im (make-image :hash \"abcd\"\n                              :company company\n                              :verified-p t)))\n     (let ((response (prepare-upload-api\n                  :hash \"abcd\"\n                  :content-type \"image/png\")))\n       (is (equal (oid old-im)\n                  (upload-response-id response)))\n       (is-false (upload-response-upload-url response))))))\n\n(test reupload-unverified-image\n  (with-fixture state ()\n    (let ((old-im (make-image :hash \"abcd\"\n                              :company company\n                              :verified-p nil)))\n     (let ((response (prepare-upload-api\n                      :hash \"abcd\"\n                      :content-type \"image/png\")))\n       (is (not (equal (oid old-im)\n                       (upload-response-id response))))\n       (is-true (upload-response-upload-url response))))))\n\n(test verify-and-upload-from-path-happy-path\n  (with-fixture state ()\n    (let ((im (make-image :hash \"3858f62230ac3c915f300c664312c63f\"\n                          :company company\n                          :verified-p nil)))\n      (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n        (write-string \"foobar\" s)\n        (finish-output s)\n        (is-false (verified-p im))\n        (is\n         (equal \"0\"\n                (verify-and-upload-from-path im p)))\n        (is-true (verified-p im))\n        (with-local-image (file im)\n          (is (equal \"foobar\"\n                     (uiop:read-file-string file))))))))\n\n(test verify-and-upload-from-path-when-file-doesnt-match\n  (with-fixture state ()\n    (let ((im (make-image :hash \"3858f62230ac3c915f300c664312c63f\"\n                          :company company\n                          :verified-p nil)))\n      (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n        (write-string \"carbar\" s) ;; the actual image should be \"foobar\"\n        (finish-output s)\n        (is-false (verified-p im))\n        (signals api-error\n          (verify-and-upload-from-path im p))\n        (is-false (verified-p im))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-model.lisp",
    "content": "(defpackage :screenshotbot/api/test-model\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/api/model\n                #:failed-run\n                #:screenshot\n                #:screenshot-list\n                #:decode-json\n                #:encode-json\n                #:version)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-model)\n\n(util/fiveam:def-suite)\n\n#+nil\n(test simple-encoding\n  (let ((ret (encode-tree (make-instance 'version :version 20))))\n    (is (equal \"version\" (gethash \"_type\" ret)))\n    (is (equal 20 (gethash \"version\" ret)))))\n\n(test simple-json-encoding\n  (let ((ret (make-instance 'version :version 2)))\n    (is (equal (str:trim \" {\\\"version\\\":2} \")\n               (encode-json ret)))))\n\n(test simple-decoding\n  (let ((ret\n         (decode-json (str:trim \" {\\\"version\\\": 2} \")\n                      'version)))\n    (is (typep ret 'version))\n    (is (equal 2 (slot-value ret 'version)))))\n\n(test decoding-list\n  (let ((ret\n         (decode-json (str:trim \" [{\\\"version\\\": 2},{\\\"version\\\":3}] \")\n                      '(:list version))))\n    (is (listp ret))\n    (is (equal 2 (slot-value (car ret) 'version)))\n    (is (equal 3 (slot-value (cadr ret) 'version)))))\n\n(test unrecognized-fields\n  (let ((ret\n         (decode-json (str:trim \" {\\\"foo\\\": \\\"car\\\", \\\"version\\\": 2, \\\"bleh\\\": \\\"ten\\\"} \")\n                      'version)))\n    (is (typep ret 'version))\n    (is (equal 2 (slot-value ret 'version)))))\n\n(test decode-json-for-optional-root-value\n  (let ((ret\n          (decode-json (str:trim \" {\\\"foo\\\": \\\"car\\\", \\\"version\\\": 2, \\\"bleh\\\": \\\"ten\\\"} \")\n                       '(or null version))))\n    (is (typep ret 'version))\n    (is (equal 2 (slot-value ret 'version)))\n    (is (eql\n         nil\n         (decode-json \"null\"\n                       '(or null version))))))\n\n(test missing-fields-is-initformed\n  (let ((ret\n         (decode-json (str:trim \" {\\\"foo\\\": \\\"car\\\"} \")\n                      'failed-run)))\n    (is (typep ret 'failed-run))\n    (is (equal nil (slot-value ret 'dto::id))))\n  (let ((ret\n          (decode-json (str:trim \" {\\\"foo\\\": \\\"car\\\"} \")\n                       'version)))\n    (is (typep ret 'version))\n    (is-false (slot-boundp ret 'version))))\n\n(test parse-screenshot-list\n  (assert-that\n   (json-mop:json-to-clos\n    \"[{\\\"name\\\":\\\"bleh\\\"}] \"\n    'screenshot-list)\n   (contains (has-typep 'screenshot))))\n\n\n(test can-encode-null-screenshot\n  (let ((screenshot (make-instance 'screenshot\n                                   :name \"foo\"\n                                   :image-id \"bleh\"\n                                   :lang nil)))\n    (let ((str (with-output-to-string (out)\n                 (yason:encode screenshot out))))\n      (assert-that str\n                   (contains-string \"\\\"lang\\\":null\")))))\n\n(test can-encode-empty-screenshot-list\n  (let ((run (make-instance 'dto:run\n                            :screenshots nil)))\n    (let ((output (with-output-to-string (out)\n                    (yason:encode run out))))\n      (assert-that output\n                   (contains-string \"\\\"screenshots\\\":[]\")))))\n\n\n(test can-encode-run-tags\n  (let ((Run (make-instance 'dto:run\n                            :tags (list \"Foo\" \"bar\"))))\n    (finishes\n      (with-output-to-string (out)\n        (yason:encode run out))))\n\n    (let ((Run (make-instance 'dto:run\n                            :tags nil)))\n      (finishes\n        (with-output-to-string (out)\n          (yason:encode run out)))))\n\n(test can-encode-run-metadata\n  (let ((Run (make-instance 'dto:run\n                            :metadata (list\n                                       (make-instance 'dto:metadata\n                                                      :key \"foo\"\n                                                      :value \"bar\")))))\n    (finishes\n      (with-output-to-string (out)\n        (yason:encode run out))))\n\n    (let ((run (make-instance 'dto:run\n                            :metadata nil)))\n      (finishes\n        (with-output-to-string (out)\n          (yason:encode run out)))))\n\n(test run-author-is-nil-if-not-bound\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\"} \"\n                          'dto:run)))\n    (is (eql nil (dto:run-author run)))))\n\n(test release-branch-p-is-nil-if-not-bound\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\"} \"\n                          'dto:run)))\n    (is (eql nil (dto:release-branch-p run)))))\n\n(test release-branch-p-is-set-in-a-few-cases\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\", \\\"isReleaseBranch\\\":true} \"\n                          'dto:run)))\n    (is (eql t (dto:release-branch-p run))))\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\", \\\"isReleaseBranch\\\":false} \"\n                          'dto:run)))\n    (is (eql nil (dto:release-branch-p run)))))\n\n(test abstract-run-args-are-parsed\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\"} \"\n                          'dto:run)))\n    (is (equal \"foo\" (dto:run-batch run)))))\n\n(test abstract-run-args-are-encoded\n  (let ((run (make-instance 'dto:run :batch \"foo\")))\n    (assert-that (encode-json run)\n                 (contains-string \"\\\"batch\\\":\\\"foo\\\"\"))))\n\n(test shard-spec-doesnt-need-to-be-present\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\"} \"\n                          'dto:run)))\n    (is (equal nil (dto:shard-spec run))))\n  (let ((run (decode-json \"{\\\"batch\\\": \\\"foo\\\", \\\"shard\\\":{}} \"\n                          'dto:run)))\n    (assert-that (dto:shard-spec run)\n                 (has-typep 'dto:shard-spec))))\n\n\n(test shard-spec-serialization\n  (finishes\n   (encode-json\n    (make-instance 'dto:shard-spec\n                   :key \"foo\"\n                   :number 0\n                   :count 10))))\n\n(test reviewer-can-be-NIL\n  (finishes\n    (encode-json\n     (make-instance 'dto:report\n                    :reviewer-name nil))))\n\n(test compare-pixel-tolerance-is-always-a-numer\n  (is (eql 1 (dto:compare-pixel-tolerance (make-instance 'dto:run :compare-pixel-tolerance 1))))\n  (is (eql 0 (dto:compare-pixel-tolerance (make-instance 'dto:run :compare-pixel-tolerance nil)))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-promote.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-promote\n  (:use #:cl\n        #:alexandria\n        #:bknr.datastore\n        #:fiveam\n        #:screenshotbot/api/promote\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/company\n        #:screenshotbot/model/user\n        #:screenshotbot/git-repo\n        #:screenshotbot/user-api\n        #:screenshotbot/model/api-key)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:equal-to\n                #:assert-that)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo\n                #:commit-graph)\n  (:import-from #:screenshotbot/sdk/git\n                #:git-repo)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:gitlab-merge-request-iid\n                #:assert-no-loops\n                #:make-recorder-run\n                #:not-fast-forward-promotion-warning\n                #:recorder-run-warnings)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:screenshotbot/api/promote\n                #:estimate-wait-timeout\n                #:find-complete-run\n                #:work-branch-matches-p\n                #:with-log-errors\n                #:delegating-promoter)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-promote)\n  (:import-from #:util/logger\n                #:format-log)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:*synchronous-promotion*)\n  (:import-from #:core/config/api\n                #:config))\n(in-package :screenshotbot/api/test-promote)\n\n(util/fiveam:def-suite)\n\n(defun make-dag (commits)\n  (let ((dag (make-instance 'dag:dag)))\n    (loop for (key . parent) in commits\n          do\n             (dag:add-commit dag\n                             (make-instance 'dag:commit\n                                            :sha key\n                                            :parents (list\n                                                      parent))))\n    dag))\n\n(defparameter *tree* `((\"a4\" . \"a2\")\n                       (\"a5\" . \"a3\")\n                       (\"a7\" . \"a6\")\n                       (\"a3\" . \"a2\")\n                       (\"a2\" . \"a1\")))\n\n(defclass dummy-repo (generic-git-repo)\n  ((commits :initform *tree*)\n   (dag :initform (make-dag *tree*)\n        :reader repo-dag)))\n\n(defclass test-commit-graph ()\n  ((dag :initarg :dag\n        :reader commit-graph-dag)))\n\n(defmethod commit-graph ((repo dummy-repo))\n  (make-instance 'test-commit-graph\n                 :dag (repo-dag repo)))\n\n(defclass test-channel (channel)\n  ((%repo :initform (make-instance 'dummy-repo)\n          :reader channel-repo))\n  (:metaclass persistent-class))\n\n(defmethod public-repo-p ((repo dummy-repo))\n  nil)\n\n(def-fixture state ()\n  (with-test-store ()\n   (with-fake-request ()\n     (auth:with-sessions ()\n       (with-test-user (:company company\n                        :user user)\n         (let* ((channel (make-instance 'test-channel :name \"foo\")))\n           (with-transaction ()\n             (push channel (company-channels company)))\n           (flet ((make-run (&rest args)\n                    (let ((run (apply #'make-recorder-run\n                                      (append\n                                       args\n                                       (list\n                                        :channel channel\n                                        :github-repo \"foo\"\n                                        :commit-hash nil\n                                        :cleanp t\n                                        :branch \"master\"\n                                        :branch-hash \"a1\"\n                                        :trunkp t\n                                        :company company)))))\n                      (with-transaction ()\n                        (push run (company-runs company)))\n                      run)))\n            (let* ((run1 (make-run :commit-hash \"a1\"\n                                   :branch \"master\"\n                                   :branch-hash \"a1\"))\n                   (run2 (make-run :commit-hash \"a2\"\n                                   :branch \"master\"\n                                   :branch-hash \"a2\")))\n              (&body)))))))))\n\n(defun %maybe-promote-run (run channel &key (wait-timeout 1))\n  (maybe-promote-run run\n                     :channel channel\n                     :wait-timeout wait-timeout)\n  (with-transaction ()\n    (setf (promotion-complete-p run) channel)))\n\n(test simple-promote-with-no-runs\n  (with-fixture state ()\n    (is-false (activep run1))\n    (%maybe-promote-run run1 channel)\n    (is-true (activep run1))\n    (is-true (activep run1))\n\n    ;; verify that run2 is untouched\n    (is-false (activep run2))))\n\n(test promote-second-run\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel)\n    (%maybe-promote-run run2 channel)\n\n    (is-true (activep run2))\n    (is-false (activep run1))\n    (is (eql\n         run1\n         (recorder-previous-run run2)))))\n\n(test dont-promote-a-run-with-a-merge-request\n  (with-fixture state ()\n    (setf (slot-value run2 'gitlab-merge-request-iid) 3443)\n    (%maybe-promote-run run1 channel)\n    (%maybe-promote-run run2 channel)\n\n    (is-false (activep run2))\n    (is-true (activep run1))))\n\n(test dont-promote-a-run-with-a-pull-request\n  (with-fixture state ()\n    (setf (slot-value run2 'screenshotbot/model/recorder-run::pull-request) \"https://github.com/tdrhq/fast-example/pull/2\")\n    (%maybe-promote-run run1 channel)\n    (%maybe-promote-run run2 channel)\n\n    (is-false (activep run2))\n    (is-true (activep run1))))\n\n(test ignore-an-invalid-pull-request-url\n  (with-fixture state ()\n    (setf (slot-value run2 'screenshotbot/model/recorder-run::pull-request) \"https://github.com/tdrhq/fast-example/pull/\")\n    (%maybe-promote-run run1 channel)\n    (%maybe-promote-run run2 channel)\n\n    (is-true (activep run2))\n    (is-false (activep run1))))\n\n(test dont-promote-run-with-identical-hash\n  (with-fixture state ()\n    (with-transaction ()\n     (setf (recorder-run-commit run2) (recorder-run-commit run1)))\n    (%maybe-promote-run run1 channel)\n    (%maybe-promote-run run2 channel)\n\n    (is-true (activep run1))\n    (is-false (activep run2))))\n\n(test dont-promote-older-hash\n  (with-fixture state ()\n    (%maybe-promote-run run2 channel :wait-timeout 0)\n    (%maybe-promote-run run1 channel)\n    (is-true (activep run2))\n    (is-false (activep run1))))\n\n(test wait-timeout-nil-happy-path\n  (cl-mock:with-mocks ()\n   (with-fixture state ()\n     (cl-mock:if-called 'estimate-wait-timeout\n                        (lambda (channel)\n                          0))\n     (finishes\n       (%maybe-promote-run run2 channel :wait-timeout nil)))) ())\n\n(test estimate-wait-timeout-happy-path\n  (with-fixture state ()\n    (is (eql 1 (estimate-wait-timeout channel)))))\n\n(test estimate-wait-timeout-without-override-config\n  (with-fixture state ()\n    (setf (config \"promotion.wait-timeout\") \"15\")\n    (is (eql 15 (estimate-wait-timeout channel)))))\n\n(test invoking-estimation-happy-path\n  (with-fixture state ()\n    (%maybe-promote-run run2 channel :wait-timeout 0)\n    (%maybe-promote-run run1 channel)\n    (is-true (activep run2))\n    (is-false (activep run1))))\n\n(test unrelated-branch\n  \"If somebody `git push -f`-ed a new commit, then we'll get into this\nsituation. Can also happen on a developer branch.\"\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel :wait-timeout 0)\n    (let ((new-master-run (make-run :branch \"master\"\n                                    :commit-hash \"a6\"\n                                    :branch-hash \"a6\")))\n      (%maybe-promote-run run2 channel :wait-timeout 0)\n      (assert-that (activep run2)\n                   (described-as \"Sanity check\"\n                     (equal-to t)))\n\n      (log:info \"Starting next run\")\n      (%maybe-promote-run new-master-run channel :wait-timeout 0)\n\n      (is-false (activep run2))\n      (is-true (activep new-master-run))\n\n      (assert-that (recorder-run-warnings new-master-run)\n                   (contains\n                    (has-typep 'not-fast-forward-promotion-warning))))))\n\n\n(test work-branch-must-match-master-if-set\n  \"T1690\"\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel :wait-timeout 0)\n    (let ((run2 (make-run :branch \"master\"\n                                    :work-branch \"foobar\"\n                                    :commit-hash \"a6\"\n                                    :branch-hash \"a6\")))\n      (%maybe-promote-run run2 channel :wait-timeout 0)\n      (is-false (work-branch-matches-p run2))\n      (assert-that (activep run2)\n                   (described-as \"should not have been promoted\"\n                     (equal-to nil))))))\n\n(test unrelated-branch-does-not-create-a-loop\n  \"When promoting unrelated branches, you could sometimes create a loop in\nthe promotion history.\"\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel :wait-timeout 0)\n    (let ((new-master-run (make-run :branch \"master\"\n                                    :commit-hash \"a6\"\n                                    :branch-hash \"a6\")))\n      (%maybe-promote-run run2 channel :wait-timeout 0)\n      (assert-that (activep run2)\n                   (described-as \"Sanity check\"\n                     (equal-to t)))\n\n      (log:info \"Starting next run\")\n      (%maybe-promote-run new-master-run channel :wait-timeout 0)\n\n      (is-false (activep run2))\n      (is-true (activep new-master-run))\n\n      (%maybe-promote-run run2 channel :wait-timeout 0)\n      (assert-no-loops run2))))\n\n(defclass fake-promoter ()\n  ((invokedp :initform nil\n             :accessor invokedp)))\n\n(defmethod maybe-promote ((Self fake-promoter) run)\n  (setf (invokedp self)  t))\n\n(test delegating-promoter-delegates\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run :channel channel\n                                   :screenshots nil))\n           (delegate (make-instance 'fake-promoter))           \n           (dp (make-instance 'delegating-promoter\n                              :delegates (list\n                                          delegate))))\n      (finishes\n        (maybe-promote dp run))\n      (is-true (invokedp delegate)))))\n\n(defclass crashing-promoter ()\n  ((invokedp :initform nil\n             :accessor invokedp)))\n\n(defmethod maybe-promote ((Self crashing-promoter) run)\n  (error \"should be logged\"))\n\n(test delegating-promoter-logs-errors\n  (with-fixture state ()\n    (let* ((*synchronous-promotion* t)\n           (channel (make-instance 'channel))\n           (run (make-recorder-run :channel channel\n                                   :screenshots nil))\n           (delegate (make-instance 'crashing-promoter))           \n           (dp (make-instance 'delegating-promoter\n                              :delegates (list\n                                          delegate)))\n           (loggedp nil))\n      (cl-mock:with-mocks ()\n        (cl-mock:if-called 'trivial-backtrace:print-backtrace\n                           (lambda (e &rest args)))\n        (cl-mock:if-called 'format-log\n                           (lambda (logger level &rest args)\n                             (let ((str (apply #'format  nil args)))\n                               (if (str:containsp \"should be logged\" str)\n                                   (setf loggedp t)))))\n        (finishes\n          (maybe-promote dp run))\n        (is-true loggedp)))))\n\n(Define-condition logged-error (error)\n  ())\n\n(test with-log-errors-happy-integration-path\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run :channel channel\n                                   :screenshots nil)))\n      (signals logged-error\n        (with-log-errors (run)\n          (error 'logged-error))))))\n\n(test find-complete-run-with-no-runs\n  (with-fixture state ()\n    (is (eql nil\n         (find-complete-run channel \"a1\")))))\n\n(test find-complete-run-with-incomplete-run\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel)\n    ;; Don't mark as promotion-complete\n    (with-transaction ()\n      (setf (promotion-complete-p run1) nil))\n    (is (eql\n         nil\n         (find-complete-run channel \"a1\")))))\n\n(test find-complete-run-with-complete-run\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel)\n    ;; run1 should be marked as promotion-complete by %maybe-promote-run\n    (is (eql run1 (find-complete-run channel \"a1\")))))\n\n(test find-complete-run-with-multiple-runs\n  (with-fixture state ()\n    (%maybe-promote-run run1 channel)\n    (%maybe-promote-run run2 channel)\n    ;; run2 should be the complete run for \"a2\"\n    (is (eql run2 (find-complete-run channel \"a2\")))\n    ;; I should still be able to find run1, since it was promoted in\n    ;; the past\n    (is\n     (eql run1 (find-complete-run channel \"a1\")))))\n\n(test find-complete-run-returns-nil-when-production-run-for-returns-nil\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n      (is (eql nil\n               (find-complete-run channel \"ca\"))))))\n\n(test parent-not-ready-handler-waits-for-parent\n  \"Test that parent-not-ready condition triggers wait-for-run with correct arguments.\nThis test ensures that when a parent commit is not ready, the handler-case catches\nthe parent-not-ready condition and calls wait-for-run with the proper wait-timeout.\"\n  (with-fixture state ()\n    ;; First promote run1 (parent commit \"a1\") and run2 (parent \"a2\")\n    (%maybe-promote-run run1 channel :wait-timeout 0)\n    (%maybe-promote-run run2 channel :wait-timeout 0)\n\n    ;; Mark run2 as NOT promotion-complete, so run3 will wait for it\n    (with-transaction ()\n      (setf (promotion-complete-p run2) nil))\n\n    ;; Create run3 with commit \"a3\" which has parent \"a2\"\n    (let* ((run3 (make-run :commit-hash \"a3\"\n                           :branch \"master\"\n                           :branch-hash \"a3\"))\n           (wait-for-run-called-with nil))\n\n      ;; Mock the wait-for-run function to capture what happens\n      ;; when the parent-not-ready condition is handled\n      (cl-mock:with-mocks ()\n        (cl-mock:if-called 'screenshotbot/api/promote::wait-for-run\n                           (lambda (channel commit &optional amount unit)\n                             (setf wait-for-run-called-with\n                                   (list channel commit amount unit))\n                             ;; After being called, mark run2 as complete to prevent infinite recursion\n                             (with-transaction ()\n                               (setf (promotion-complete-p run2) channel))\n                             ;; Return t to indicate parent was found\n                             t))\n\n        ;; This should trigger the parent-not-ready condition\n        ;; which should be caught by handler-case and call wait-for-run\n        ;; with wait-timeout of 1 and unit :minute\n        (%maybe-promote-run run3 channel :wait-timeout 1)\n\n        ;; Verify that wait-for-run was called with the correct arguments\n        (is-true wait-for-run-called-with\n                 \"wait-for-run should have been called via the handler-case\")\n\n        ;; Verify the arguments: channel, parent-commit \"a2\", amount 1, unit :minute\n        (when wait-for-run-called-with\n          (destructuring-bind (chan commit amount unit) wait-for-run-called-with\n            (is (eql channel chan) \"Channel should match\")\n            (is (equal \"a2\" commit) \"Parent commit should be a2\")\n            (is (eql 1 amount) \"Wait timeout should be 1\")\n            (is (eql :minute unit) \"Unit should be :minute\")))))))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/test-recorder-runs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/api/test-recorder-runs\n  (:use #:cl\n        #:bknr.datastore\n        #:screenshotbot/model/user\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/image\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/model/company\n        #:screenshotbot/model/api-key\n        #:screenshotbot/user-api\n        #:fiveam)\n  (:import-from #:screenshotbot/server\n                #:logged-in-p)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:%find-runs\n                #:%find-base-run\n                #:production-run-without-ci-permission\n                #:validation-error\n                #:validate-dto\n                #:%put-run\n                #:run-to-dto\n                #:warmup-image-caches\n                #:api-run-put\n                #:make-screenshot-for-channel\n                #:*synchronous-promotion*\n                #:*synchronous-promotion*\n                #:%recorder-run-post)\n  (:import-from #:util\n                #:oid)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/model/screenshot\n                #:*screenshot-cache*)\n  (:import-from #:screenshotbot/installation\n                #:*installation*\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:import-from #:util/object-id\n                #:%make-oid\n                #:make-oid)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:compare-tolerance\n                #:release-branch-p\n                #:recorder-run-work-branch\n                #:recorder-run-branch\n                #:recorder-run-metadata\n                #:shard\n                #:trunkp\n                #:recorder-run-batch\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)  \n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:has-typep\n                #:assert-that\n                #:equal-to)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex\n                #:starts-with)\n  (:import-from #:fiveam-matchers/misc\n                #:is-null\n                #:is-not-null)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-commit\n                #:batch)\n  (:import-from #:auth/viewer-context\n                #:api-viewer-context)\n  (:import-from #:core/api/model/api-key\n                #:transient-api-key\n                #:cli-api-key\n                #:api-key-permissions)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:util/hunchentoot-engine\n                #:hunchentoot-engine)\n  (:import-from #:screenshotbot/api/core\n                #:api-error)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(defun fix (name)\n  (path:catfile\n   #.(asdf:system-relative-pathname :screenshotbot\n                                    \"fixture/\")\n   name))\n\n(def-fixture state (&key (api-key-roles :both))\n  (dolist (api-key-roles\n           (ecase api-key-roles\n             (:both\n              '(gk:enable gk:disable))\n             (:disable\n              '(gk:disable))\n             (:enable\n              '(gk:enable))))\n    (let ((*installation* (make-instance 'my-installation))\n          (engine (make-instance 'hunchentoot-engine)))\n      (with-test-store ()\n        (gk:create :api-key-roles)\n        (funcall api-key-roles :api-key-roles)\n        (cl-mock:with-mocks ()\n          (with-test-user (:company company\n                           :user user)\n            (let* ((api-key (make-instance 'api-key\n                                           :user user\n                                           :company company))\n                   (*synchronous-promotion* t)\n                   (api-key (make-instance 'api-key :user user :company company\n                                                    :permissions '(:ci)))\n                   (img1 (make-image :company company :pathname (fix \"rose.png\")))\n                   (img2 (make-image :company company :pathname (fix \"wizard.png\")))\n                   (vc (make-instance 'api-viewer-context\n                                      :api-key api-key)))\n              (with-fake-request ()\n                (auth:with-sessions ()\n                  (setf (current-user) user)\n                  (setf (current-company) company)\n                  (setf (auth:viewer-context hunchentoot:*request*)\n                        vc)\n                  (assert (logged-in-p))\n                  (assert (current-user))\n                  (&body))))))))))\n\n(defun serial-recorder-run-post (&rest args)\n  (multiple-value-bind (val verify)\n      (apply '%recorder-run-post args)\n    ;;(funcall verify)\n    val))\n\n\n(test preconditions\n  (with-fixture state ()\n    (multiple-value-bind (val verify-fn)\n        (serial-recorder-run-post\n         :channel \"foobar\"\n         :screenshot-records\n         (list\n          (make-instance 'dto:screenshot\n                         :name \"img1\"\n                         :image-id (oid img1))))\n      (pass))))\n\n(defun test-adds-channel-mask ()\n  (with-fixture state ()\n    (let ((channel (make-instance 'channel :company company))\n          (rects (list\n                  (make-instance 'mask-rect :left 0 :top 1 :width 2 :height 3))))\n      (set-channel-screenshot-mask\n       channel \"img1\" rects)\n      (let ((screenshot (make-screenshot-for-channel channel\n                                                     :name \"img2\")))\n        (is (eql nil (screenshot-masks screenshot))))\n      (let ((screenshot (make-screenshot-for-channel channel\n                                                     :name \"img1\")))\n        (is (equal rects (screenshot-masks screenshot)))))))\n\n(test adds-channel-mask\n  (test-adds-channel-mask))\n\n(test adds-channel-mask-2\n  ;; ensure the test fixture is cleaning up properly. In the past,\n  ;; there was a time when *screenshot-cache* was not being cleaned up\n  ;; properly between tests\n  (test-adds-channel-mask))\n\n(defun call-api-put-run (company dto)\n  (if-called 'warmup-image-caches\n             (lambda (run) (declare (ignore run))))\n  (answer (hunchentoot:raw-post-data :force-text t)\n    (with-output-to-string (out)\n      (yason:encode dto out)))\n  (answer (current-company)\n    company)\n  (api-run-put))\n\n(test recorder-run-put-happy-path\n  (with-fixture state ()\n    (let ((dto (make-instance 'dto:run\n                              :commit-hash \"bleh\"\n                              :channel \"blah\"\n                              :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :image-id (oid img1)\n                                                           :name \"bleh\")))))\n      (finishes\n        (call-api-put-run company dto)))))\n\n(test recorder-run-put-only-shard\n  (with-fixture state ()\n    (let ((dto (make-instance 'dto:run\n                              :commit-hash \"bleh\"\n                              :channel \"blah\"\n                              :shard-spec (make-instance 'dto:shard-spec\n                                                         :key \"foobar\"\n                                                         :number 0\n                                                         :count 2)\n                              :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :image-id (oid img1)\n                                                           :name \"bleh\")))))\n      (finishes\n       (call-api-put-run company dto)))))\n\n(test run-to-dto\n  (with-fixture state ()\n    (finishes\n      (run-to-dto (make-recorder-run\n                   :branch \"abcd\"\n                   :screenshots (list\n                                 (make-screenshot :image img1 :name \"foo\")))))))\n\n(test run-to-dto-has-uri\n  (with-fixture state ()\n    (assert-that\n     (dto:recorder-run-url\n      (run-to-dto (make-recorder-run\n                   :branch \"abcd\"\n                   :screenshots (list\n                                 (make-screenshot :image img1 :name \"foo\")))))\n     (starts-with \"https://example.com/runs/\"))))\n\n(test api-key-can-be-nil-for-populate\n  \"populate calls into %put-run but provides a NIL :api-key\"\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :trunkp t\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key nil)\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true (trunkp run)))))\n\n(test run-with-trunkp-as-t\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :trunkp t\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true (trunkp run)))))\n\n(test run-with-trunkp-as-t-but-no-ci-permission\n  (with-fixture state (:api-key-roles :enable)\n    (assert company)\n    (setf (api-key-permissions api-key) '(:full))\n    (signals production-run-without-ci-permission\n      (%put-run company\n                (make-instance 'dto:run\n                               :channel \"foo\"\n                               :commit-hash \"deadbeef\"\n                               :trunkp t\n                               :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n                :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 0))))\n\n(test run-with-trunkp-as-t-for-cli-api-key\n  (with-fixture state (:api-key-roles :enable)\n    (assert company)\n    (let ((api-key (make-instance 'cli-api-key\n                                  :user user\n                                  :permissions '(:ci)\n                                  :company company)))\n      (signals production-run-without-ci-permission\n        (%put-run company\n                  (make-instance 'dto:run\n                                 :channel \"foo\"\n                                 :commit-hash \"deadbeef\"\n                                 :trunkp t\n                                 :screenshots (list\n                                               (make-instance 'dto:screenshot\n                                                              :name \"foo\"\n                                                              :image-id (oid img1))))\n                  :api-key api-key)))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 0))))\n\n(test run-with-trunkp-as-t-for-transient-api-key\n  (with-fixture state ()\n    (assert company)\n    (let ((api-key (make-instance 'transient-api-key\n                                  :user user\n                                  :company company)))\n      (%put-run company\n                (make-instance 'dto:run\n                               :channel \"foo\"\n                               :commit-hash \"deadbeef\"\n                               :trunkp t\n                               :screenshots (list\n                                             (make-instance 'dto:screenshot\n                                                            :name \"foo\"\n                                                            :image-id (oid img1))))\n                :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))))\n\n(test run-with-trunkp-as-t-but-no-ci-permission-with-gk-disabled\n  (with-fixture state (:api-key-roles :disable)\n    (assert company)\n    (setf (api-key-permissions api-key) '(:full))\n    (finishes\n      (%put-run company\n                (make-instance 'dto:run\n                               :channel \"foo\"\n                               :commit-hash \"deadbeef\"\n                               :trunkp t\n                               :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n                :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))))\n\n(test metadata-is-saved\n  (with-fixture state ()\n    (finishes\n      (%put-run company\n                (make-instance 'dto:run\n                               :channel \"foo\"\n                               :commit-hash \"deadbeef\"\n                               :metadata (list\n                                          (make-instance 'dto:metadata\n                                                         :key \"foo\"\n                                                         :value \"bar\"))\n                               :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n                :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))\n    (let ((run (first (class-instances 'recorder-run))))\n      (assert-that (recorder-run-metadata run)\n                   (has-item \n                    '(\"foo\" . \"bar\"))))))\n\n(test main-branch-is-set-to-release-branch\n  \"This is a temporary test. (T1667). Once we have a specific model for\nstoring release-branch-p, we'll update this test.\"\n  (with-fixture state ()\n    (finishes\n      (%put-run company\n                (make-instance 'dto:run\n                               :channel \"foo\"\n                               :commit-hash \"deadbeef\"\n                               :work-branch \"foo\"\n                               :main-branch \"main\"\n                               :release-branch-p t\n                               :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n                :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))\n    (let ((run (first (class-instances 'recorder-run))))\n      (assert-that (recorder-run-branch run)\n                   (is-equal-to \"foo\"))\n      (is-true (release-branch-p run))\n      (assert-that (recorder-run-work-branch run)\n                   (is-equal-to \"foo\")))))\n\n(test main-branch-is-***NOT***-set-to-release-branch\n  \"This is a temporary test. (T1667). Once we have a specific model for\nstoring release-branch-p, we'll update this test.\"\n  (with-fixture state ()\n    (finishes\n      (%put-run company\n                (make-instance 'dto:run\n                               :channel \"foo\"\n                               :commit-hash \"deadbeef\"\n                               :work-branch \"foo\"\n                               :main-branch \"main\"\n                               :release-branch-p nil\n                               :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n                :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))\n    (let ((run (first (class-instances 'recorder-run))))\n      (is-false (release-branch-p run))      \n      (assert-that (recorder-run-branch run)\n                   (is-equal-to \"main\"))\n      (assert-that (recorder-run-work-branch run)\n                   (is-equal-to \"foo\")))))\n\n\n(test batch-is-added\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :batch \"dummy-batch\"\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1)))))\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (recorder-run-batch run)\n                   (is-not-null)\n                   (has-typep 'batch))\n      (is (equal \"deadbeef\" (batch-commit (recorder-run-batch run)))))))\n\n(test batch-uses-does-not-use-empty-override-commit-hash\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :override-commit-hash \"\"\n                             :batch \"dummy-batch\"\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1)))))\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (recorder-run-batch run)\n                   (is-not-null)\n                   (has-typep 'batch))\n      (is (equal \"deadbeef\" (batch-commit (recorder-run-batch run)))))))\n\n(test batch-uses-does-not-uses-override-commit-hash\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :override-commit-hash \"baadf00d\"\n                             :batch \"dummy-batch\"\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1)))))\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (recorder-run-batch run)\n                   (is-not-null)\n                   (has-typep 'batch))\n      (is (equal \"baadf00d\" (batch-commit (recorder-run-batch run)))))))\n\n(test batch-is-nil\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :batch nil\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (recorder-run-batch run)\n                   (is-null)))))\n\n(defun make-long-string (&key (length 1000))\n  (make-string length :initial-element #\\a))\n\n(test validate-tag-name-too-long\n  (with-fixture state ()\n    (finishes\n      (validate-dto (make-instance 'dto:run\n                                   :tags nil)))\n    (finishes\n      (validate-dto (make-instance 'dto:run\n                                   :tags (list \"foo\" \"bar\"))))\n    (signals validation-error\n      (validate-dto (make-instance\n                     'dto:run\n                     :tags (list\n                            (make-array 500 :element-type 'character\n                                            :initial-element #\\a)))))))\n\n(test validate-metadata-too-long\n  (with-fixture state ()\n    (finishes\n      (validate-dto (make-instance 'dto:run\n                                   :metadata nil)))\n    (finishes\n      (validate-dto (make-instance 'dto:run\n                                   :metadata (list\n                                              (make-instance 'dto:metadata\n                                                             :key \"foo\"\n                                                             :value \"bar\")))))\n    (signals validation-error\n      (validate-dto (make-instance\n                     'dto:run\n                     :metadata (list\n                                (make-instance 'dto:metadata\n                                               :key (make-long-string :length 500)\n                                               :value \"foo\")))))\n    (signals validation-error\n      (validate-dto (make-instance\n                     'dto:run\n                     :metadata (list\n                                (make-instance 'dto:metadata\n                                               :key \"foo\"\n                                               :value (make-long-string :length 10500))))))))\n\n(test too-many-metadata\n  (with-fixture state ()\n    (signals validation-error\n      (validate-dto (make-instance\n                     'dto:run\n                     :metadata (loop for i from 1 to 100\n                                     collect\n                                     (make-instance 'dto:metadata\n                                                    :key \"foo\"\n                                                    :value \"value\")))))))\n\n(test if-a-shard-spec-is-present-we-dont-create-a-run-immediately\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :batch nil\n                             :shard-spec (make-instance 'dto:shard-spec\n                                                        :key \"shard-key\"\n                                                        :number 0\n                                                        :count 10)\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 0))\n    (assert-that (class-instances 'shard)\n                 (has-length 1))))\n\n\n(test for-the-last-shard-create-the-run-immediately\n  (with-fixture state ()\n    (assert company)\n    (loop for i from 1 below 10\n          do\n             (make-instance 'shard\n                            :key \"shard-key\"\n                            :screenshots (list\n                                          (make-screenshot\n                                           :name (format nil \"img~a\" i)\n                                           :image img1))\n                            :channel (find-or-create-channel company \"foo\")\n                            :number i\n                            :count 10))\n    (assert-that\n     (%put-run company\n               (make-instance 'dto:run\n                              :channel \"foo\"\n                              :batch nil\n                              :shard-spec (make-instance 'dto:shard-spec\n                                                         :key \"shard-key\"\n                                                         :number 0\n                                                         :count 10)\n                              :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n               :api-key api-key)\n     (contains\n      (has-typep t)\n      (has-typep 'recorder-run)\n      (has-typep t)))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))\n    (let ((run (car (class-instances 'recorder-run))))\n      (assert-that (recorder-run-screenshots run)\n                   (has-length 10)))))\n\n(test we-also-handle-1-indexed-shards\n  (with-fixture state ()\n    (assert company)\n    (loop for i from 1 below 10\n          do\n             (make-instance 'shard\n                            :key \"shard-key\"\n                            :screenshots (list\n                                          (make-screenshot\n                                           :name (format nil \"img~a\" i)\n                                           :image img1))\n                            :channel (find-or-create-channel company \"foo\")\n                            :number i\n                            :count 10))\n    (assert-that\n     (%put-run company\n               (make-instance 'dto:run\n                              :channel \"foo\"\n                              :batch nil\n                              :shard-spec (make-instance 'dto:shard-spec\n                                                         :key \"shard-key\"\n                                                         :number 10\n                                                         :count 10)\n                              :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n               :api-key api-key)\n     (contains\n      (has-typep t)\n      (has-typep 'recorder-run)\n      (has-typep t)))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))\n    (let ((run (car (class-instances 'recorder-run))))\n      (assert-that (recorder-run-screenshots run)\n                   (has-length 10)))))\n\n(test we-dont-create-the-run-if-a-shard-was-repeated\n  (with-fixture state ()\n    (assert company)\n    (loop for i from 1 below 10\n          do\n             (make-instance 'shard\n                            :key \"shard-key\"\n                            :screenshots (list\n                                          (make-screenshot\n                                           :name (format nil \"img~a\" i)\n                                           :image img1))\n                            :channel (find-or-create-channel company \"foo\")\n                            :number i\n                            :count 10))\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :batch nil\n                             :shard-spec (make-instance 'dto:shard-spec\n                                                        :key \"shard-key\"\n                                                        :number 1\n                                                        :count 10)\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 0))))\n\n(test once-the-run-is-created-we-dont-create-more-runs-for-the-shard\n  (with-fixture state ()\n    (loop for i from 1 below 10\n          do\n             (make-instance 'shard\n                            :key \"shard-key\"\n                            :screenshots (list\n                                          (make-screenshot\n                                           :name (format nil \"img~a\" i)\n                                           :image img1))\n                            :channel (find-or-create-channel company \"foo\")\n                            :number i\n                            :count 10))\n    (dotimes (i 2)\n     (%put-run company\n               (make-instance 'dto:run\n                              :channel \"foo\"\n                              :batch nil\n                              :shard-spec (make-instance 'dto:shard-spec\n                                                         :key \"shard-key\"\n                                                         :number 0\n                                                         :count 10)\n                              :screenshots (list\n                                            (make-instance 'dto:screenshot\n                                                           :name \"foo\"\n                                                           :image-id (oid img1))))\n               :api-key api-key))\n    (assert-that (class-instances 'recorder-run)\n                 (has-length 1))\n    (let ((run (car (class-instances 'recorder-run))))\n      (assert-that (recorder-run-screenshots run)\n                   (has-length 10)))))\n\n(test validation-failure-for-long-shard-key-name\n  (finishes\n    (validate-dto\n     (make-instance 'dto:run\n                    :shard-spec (make-instance 'dto:shard-spec\n                                               :key \"shard-key\"\n                                               :number 0\n                                               :count 20))))\n  (signals validation-error\n    (validate-dto\n     (make-instance 'dto:run\n                    :shard-spec (make-instance 'dto:shard-spec\n                                               :key (make-long-string)\n                                               :number 0\n                                               :count 20)))))\n\n\n\n\n(test find-base-run-when-channel-is-invalid\n  (with-fixture state (:api-key-roles :disable)\n    (is-true (auth:current-company))\n    (let* ((channel (find-or-create-channel company \"zoidberg\"))\n           (run (make-recorder-run\n                 :company company\n                 :branch \"carbar\"\n                 :commit-hash \"0011\"\n                 :trunkp t\n                 :channel channel\n                 :screenshots (list\n                               (make-screenshot :image img1 :name \"foo\")))))\n      (signals-error-matching (api-error)\n                              (%find-base-run\n                               :channel \"foobarcar\" :commit \"abcd\")\n                              (error-with-string-matching\n                               (matches-regex \".*No such channel.*\")))\n      (is\n       (eql nil\n        (%find-base-run\n         :channel \"zoidberg\"\n         :commit \"abcd\")))\n      (assert-that\n       (%find-base-run\n        :channel \"zoidberg\"\n        :commit \"0011\")\n       (has-typep 'dto:run)))))\n\n(test pixel-tolerance-is-propagated-to-recorder-run\n  \"Test that compare-pixel-tolerance from DTO is properly passed to the recorder-run model\"\n  (with-fixture state ()\n    (assert company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :compare-pixel-tolerance 5\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (compare-tolerance run)\n                   (is-equal-to 5)))))\n\n(test pixel-tolerance-with-gk\n  (with-fixture state ()\n    (gk:create :default-pixel-tolerance)\n    (gk:allow :default-pixel-tolerance company)\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (compare-tolerance run)\n                   (is-equal-to 4)))))\n\n(test pixel-tolerance-without-gk\n  (with-fixture state ()\n    (%put-run company\n              (make-instance 'dto:run\n                             :channel \"foo\"\n                             :commit-hash \"deadbeef\"\n                             :screenshots (list\n                                           (make-instance 'dto:screenshot\n                                                          :name \"foo\"\n                                                          :image-id (oid img1))))\n              :api-key api-key)\n    (let ((run (car (last (class-instances 'recorder-run)))))\n      (is-true run)\n      (assert-that (compare-tolerance run)\n                   (is-equal-to 0)))))\n\n(test validate-pixel-tolerance-too-bid\n  (with-fixture state ()\n    (validate-dto\n     (make-instance 'dto:run\n                    :compare-pixel-tolerance 1))\n    (signals\n        validation-error\n      (validate-dto\n       (make-instance 'dto:run\n                      :compare-pixel-tolerance 100)))\n    (validate-dto\n     (make-instance 'dto:run\n                    :compare-pixel-tolerance 0))))\n\n(test fetch-runs-for-commit\n  (with-fixture state ()\n    (let ((run1 (make-recorder-run :company company\n                                   :channel (find-or-create-channel company \"foo\")\n                                   :commit-hash \"deadbeef\"\n                                   :screenshots nil)))\n      (assert-that\n       (%find-runs\n        :commit \"deadbeef\")\n       (has-length 1))\n      (assert-that\n       (%find-runs\n        :commit \"beefdead\")\n       (has-length 0))\n      (let* ((other-company (make-instance 'company))\n             (run2 (make-recorder-run :company other-company\n                                      :channel (find-or-create-channel company \"foo\")\n                                      :commit-hash \"deadbeef\"\n                                      :screenshots nil)))\n        (assert-that\n         (%find-runs\n          :commit \"deadbeef\")\n         (has-length 1))\n        (assert-that\n         (dto:run-id (elt (%find-runs :commit \"deadbeef\") 0))\n         (is-equal-to (oid run1)))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-report\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/testing\n                #:multi-org-test-installation\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/api/report\n                #:%report-accept)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-state\n                #:base-acceptable)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-report)\n\n(util/fiveam:def-suite)\n\n(defclass fake-acceptable (base-acceptable)\n  ()\n  (:metaclass persistent-class))\n\n\n\n(def-fixture state ()\n  (with-installation (:installation (make-instance 'multi-org-test-installation))\n   (with-test-store ()\n     (with-test-user (:logged-in-p t\n                      :company company)\n       (let* ((channel (make-instance 'channel :company company))\n              (run (make-recorder-run  :channel channel\n                                       :company company\n                                       :screenshots nil))\n              (acceptable (make-instance 'fake-acceptable))\n              (report (make-instance 'report\n                                     :run run\n                                     :acceptable acceptable\n                                     :previous-run nil)))\n         (&body))))))\n\n(test simple-accept-test\n  (with-fixture state ()\n    (assert-that (%report-accept :oid (oid report))\n                 (has-typep 'dto:report))\n    (is (eql :accepted (acceptable-state acceptable)))))\n\n(test cannot-access-other-reports\n  (with-fixture state ()\n    (with-test-user (:logged-in-p t\n                     :company-name \"other company\"\n                     :company other-company)\n      (is (not (eql other-company company)))\n      (is (not (eql company (auth:current-company))))\n      (signals auth:no-access-error\n       (%report-accept :oid (oid report))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/api/test-send-tasks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/api/test-send-tasks\n  (:use #:cl\n        #:alexandria\n        #:bknr.datastore\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/user\n        #:screenshotbot/user-api\n        #:screenshotbot/api/promote\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/promote-api\n        #:screenshotbot/model/api-key\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/company\n        #:fiveam)\n  (:import-from #:screenshotbot/tasks/common\n                #:noop-task-integration\n                #:get-enabled-task-integrations)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:make-screenshot-map)\n  (:import-from #:screenshotbot/model/image\n                #:make-image))\n\n(util/fiveam:def-suite)\n\n(defclass test-company (company)\n  ()\n  (:metaclass persistent-class))\n\n\n(defmethod get-enabled-task-integrations ((company test-company) channel)\n  (list\n   (make-instance 'noop-task-integration\n                  :company company)))\n\n(def-fixture state (&key run1-screenshots)\n  (with-test-store ()\n   (with-fake-request ()\n     (auth:with-sessions ()\n       (let ((promoter (make-instance 'master-promoter)))\n         (with-test-user (:company company\n                          :company-name \"foobar enterprises\"\n                          :company-class 'test-company\n                          :user user\n                          :api-key api-key)\n           (let* ((channel (make-instance 'channel :name \"dfdfdf\"\n                                           ;; give a repo, just in case we\n                                           ;; have a bug and we're actually\n                                           ;; hitting github\n                                           :github-repo \"https://github.com/tdrhq/screenshotbot-example\"\n                                           :branch \"master\"))\n                  (run1 (make-recorder-run\n                         :was-promoted-p t\n                         :channel channel\n                         :company company\n                         :screenshots (loop for name in run1-screenshots\n                                            collect\n                                            (make-instance 'screenshot :name name))\n                         :commit-hash \"car\"\n                         :cleanp t\n                         :trunkp t))\n                  (screenshot (make-instance 'screenshot\n                                             :run nil\n                                             :name \"foo\"))\n                  (run2 (make-recorder-run\n                         :channel channel\n                         :commit-hash \"car2\"\n                         :was-promoted-p t\n                         :cleanp t\n                         :screenshots (list screenshot)\n                         :previous-run run1\n                         :trunkp t\n                         :company company)))\n             (&body))))))))\n\n(test happy-path\n  (with-fixture state ()\n    (is-false (company-reports company))\n    (maybe-send-tasks promoter run2)\n    (is-true (company-reports company))\n    #+nil\n    (is (equal \"http://foo/1\"\n               (github-task (car (company-reports company)))))))\n\n(test no-screenshots-no-task\n  (with-fixture state ()\n    (with-transaction ()\n      (setf (run-screenshot-map run1)\n            (make-screenshot-map channel nil))\n      (setf (run-screenshot-map run2)\n            (make-screenshot-map channel nil)))\n    (maybe-send-tasks promoter run2)\n    (is-false (company-reports company))))\n\n(test happy-path-for-first-task\n  (with-fixture state ()\n    (maybe-send-tasks promoter run1)\n    ;; Since there were no screenshots in run1 \n    (is-false (company-reports company))))\n\n(test we-send-a-report-for-the-first-run\n  (with-fixture state (:run1-screenshots '(\"bar\"))\n    (maybe-send-tasks promoter run1)\n    ;; Since there were no screenshots in run1 \n    (is-true (company-reports company))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-unchanged-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/test-unchanged-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/api/unchanged-run\n                #:%post-unchanged-run\n                #:parse-body)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:unchanged-run-for-commit\n                #:unchanged-run-other-commit)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:*synchronous-promotion*)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/test-unchanged-run)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (with-test-store ()\n      (let ((*synchronous-promotion* t))\n       (&body)))))\n\n\n(test simple-create-api\n  (with-fixture state ()\n    (let ((unchanged-run-dto (make-instance 'dto:unchanged-run\n                                            :channel \"bleh\"\n                                            :commit \"foo\"\n                                            :other-commit \"bar\")))\n      (answer (parse-body 'dto:unchanged-run)\n        unchanged-run-dto)\n      (with-test-user (:logged-in-p t :company company)\n        (finishes\n          (%post-unchanged-run)))\n      (let ((ur (unchanged-run-for-commit\n                  (car (bknr.datastore:class-instances 'channel))\n                  \"foo\")))\n        (is (not (null ur)))\n        (is (equal \"bar\" (unchanged-run-other-commit ur)))))))\n\n(test create-unchanged-run-with-batch\n  (with-fixture state ()\n    (let ((unchanged-run-dto (make-instance 'dto:unchanged-run\n                                            :channel \"bleh\"\n                                            :batch \"foobar\"\n                                            :commit \"foo\"\n                                            :other-commit \"bar\")))\n      (answer (parse-body 'dto:unchanged-run)\n        unchanged-run-dto)\n      (with-test-user (:logged-in-p t :company company)\n        (finishes\n          (%post-unchanged-run)))\n      (let ((ur (unchanged-run-for-commit\n                  (car (bknr.datastore:class-instances 'channel))\n                  \"foo\")))\n        (is (not (null ur)))\n        (is (equal \"bar\" (unchanged-run-other-commit ur)))))))\n"
  },
  {
    "path": "src/screenshotbot/api/test-version.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :screenshotbot/api/test-version\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/api/version\n                #:*gk-list*\n                #:api-version)\n  (:import-from #:screenshotbot/api/model\n                #:api-features\n                #:installation-url\n                #:version-number\n                #:*api-version*\n                #:version\n                #:decode-json)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:assert-that))\n(in-package :screenshotbot/api/test-version)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n   (with-test-store ()\n     (with-installation ()\n       (let* ((company (make-instance 'company))\n              (api-key (make-instance 'api-key\n                                      :company company\n                                      :api-key \"foobar\"\n                                      :api-secret-key \"T0pSecret\"\n                                      :user 'fake-user))\n              (*gk-list*\n                (append\n                 `((:fake-gk nil)\n                   (:fake-gk-2 nil)\n                   (:fake-gk-3 t))\n                 *gk-list*)))\n         (gk:create :fake-gk)\n         (&body))))))\n\n(test api-version-happy-path\n  (with-fixture state ()\n    (with-fake-request ()\n      (let ((content (api-version)))\n        (let ((version (decode-json content 'version)))\n          (is (eql *api-version* (version-number version)))\n          (is (equal \"https://example.com\" (installation-url version))))))))\n\n(test features-are-set\n  (with-fixture state ()\n    (with-fake-request ()\n      (gk:allow :fake-gk company)\n      (answer (hunchentoot:authorization)\n        (values \"foobar\" \"T0pSecret\"))\n      (let ((content (api-version)))\n        (let ((version (decode-json content 'version)))\n          (is (eql *api-version* (version-number version)))\n          (is (equal \"https://example.com\" (installation-url version)))\n          (assert-that (api-features version)\n                       (has-item \"fake-gk\")\n                       (does-not (has-item \"fake-gk-2\"))))))))\n\n(test default-value-is-respected\n  (with-fixture state ()\n    (with-fake-request ()\n      (gk:allow :fake-gk company)\n      (answer (hunchentoot:authorization)\n        (values \"foobar\" \"T0pSecret\"))\n      (let ((content (api-version)))\n        (let ((version (decode-json content 'version)))\n          (is (eql *api-version* (version-number version)))\n          (is (equal \"https://example.com\" (installation-url version)))\n          (assert-that (api-features version)\n                       (has-item \"fake-gk\")\n                       (has-item \"fake-gk-3\")\n                       (does-not (has-item \"fake-gk-2\"))))))))\n"
  },
  {
    "path": "src/screenshotbot/api/unchanged-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/unchanged-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:unchanged-run-other-commit\n                #:unchanged-run-commit\n                #:unchanged-run-channel\n                #:unchanged-run)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:current-company)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:after-run-created\n                #:batch-for-run)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/api/unchanged-run)\n\n(defun parse-body (class-name)\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (json-mop:json-to-clos body class-name)))\n\n(defun to-dto (ret)\n  (make-instance 'dto:unchanged-run\n                 :id (bknr.datastore:store-object-id ret)\n                 :channel (channel-name (unchanged-run-channel ret))\n                 :commit (unchanged-run-commit ret)\n                 :other-commit (unchanged-run-other-commit ret)))\n\n(defapi (%post-unchanged-run :uri \"/api/unchanged-run\" :method :post\n         :use-yason t) ()\n  (assert (current-company))\n  (log:debug \"Unchanged run: ~a to ~a\"\n             (dto:unchanged-run-commit input)\n             (dto:unchanged-run-other-commit input))\n  (let ((input (parse-body 'dto:unchanged-run)))\n    (let ((unchanged-run\n            (make-instance 'unchanged-run\n                           :channel (find-or-create-channel\n                                     (current-company)\n                                     (dto:unchanged-run-channel input))\n                           :commit (dto:unchanged-run-commit input)\n                           :batch (batch-for-run (current-company) input)\n                           :work-branch (dto:work-branch input)\n                           :merge-base (dto:merge-base input)\n                           :override-commit-hash (dto:override-commit-hash input)\n                           :other-commit (dto:unchanged-run-other-commit input))))\n      (after-run-created unchanged-run)\n      (to-dto unchanged-run))))\n"
  },
  {
    "path": "src/screenshotbot/api/version.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/api/version\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/api/model\n                #:version\n                #:*api-version*\n                #:encode-json)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/api/core\n                #:authenticate-api-request)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors)\n  (:import-from #:util/events\n                #:push-event))\n(in-package :screenshotbot/api/version)\n\n(defhandler (api-version :uri \"/api/version\") ()\n  (setf (hunchentoot:content-type*)\n        \"application/json; charset=utf-8\")\n  (when (hunchentoot:authorization)\n    (ignore-and-log-errors ()\n      ;; We don't need to be authenticated for this endpoint. In\n      ;; particular the client may not be handling failure\n      ;; properly. So we always return.\n      (authenticate-api-request hunchentoot:*request*)))\n\n  (push-event\n   :session-id-version-map\n   :client-session-id (hunchentoot:header-in* :x-cli-session-id)\n   :client-version (hunchentoot:header-in* :x-client-version))\n\n  (encode-json\n   (make-instance 'version\n                  :version *api-version*\n                  :url (installation-domain (installation))\n                  :features\n                  (%build-features (auth:current-company)))))\n\n(defparameter *gk-list* `((:cli-shallow-clones t)\n                          (:server-cli-logs nil)\n                          (:debug-cli-logs nil)\n\n                          ;; Always run `git fetch origin main` before\n                          ;; running the commit-graph code, even in\n                          ;; the new upload-pack style.\n                          (:always-git-fetch t)))\n\n(defmethod %build-features ((company company))\n  (loop for (gk default) in *gk-list*\n        if (gk:check gk company :default default)\n          collect (string-downcase (string gk))))\n\n(defmethod %build-features ((company null))\n  nil)\n"
  },
  {
    "path": "src/screenshotbot/artifacts.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/artifacts\n  (:use :cl)\n  (:import-from #:bknr.datastore\n                #:blob\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:hunchentoot-extensions\n                #:make-url)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:util/store/store\n                #:object-store)\n  (:export\n   #:artifact\n   #:artifact-link\n   #:artifact-with-name\n   #:def-artifact-hook\n   #:md5-hex)\n  (:local-nicknames (#:dns-client #:org.shirakumo.dns-client)))\n(in-package :screenshotbot/artifacts)\n\n(defun artifacts-dir ()\n  (ensure-directories-exist\n   (path:catdir\n    (object-store)\n    \"artifacts/\")))\n\n;; DEPRECATED: DO NOT USE.\n(defclass artifact (blob)\n  ((name :initarg :name\n         :index-initargs (:test #'equal)\n         :index-type unique-index\n         :index-reader artifact-with-name\n         :accessor artifact-name))\n  (:metaclass persistent-class))\n\n(defvar *artifact-hooks* nil)\n\n(defclass artifact-hook ()\n  ((dep :initarg :dep\n        :reader artifact-hook-dep)\n   (callback :initarg :callback\n             :reader artifact-hook-callback)))\n\n(defclass generated-artifact ()\n  ((key :initarg :key\n        :reader generated-artifact-key)\n   (name :initarg :name\n         :reader generated-artifact-name)\n   (deps :initarg :deps\n         :reader generated-artifact-deps)\n   (generator :initarg :generator\n              :reader generated-artifact-generator)))\n\n(defvar *in-test-p* nil)\n\n(defun md5-hex (f)\n  (ironclad:byte-array-to-hex-string (md5:md5sum-file f)))\n\n(defun artifact-file-name (name)\n  (assert (not (str:containsp \"/.\" name)))\n  (assert (not (str:containsp \"..\" name)))\n  (path:catfile (artifacts-dir)  (str:downcase name)))\n\n(defhandler (artifact-get :uri \"/artifact/:name\") (name cache-key)\n  (declare (ignore cache-key))\n  (let ((file (artifact-file-name name)))\n    (assert (path:-e file))\n    (hunchentoot:handle-static-file file)))\n\n\n(defmethod artifact-link ((name string) &key (cdn t))\n  (let ((file-name (artifact-file-name name)))\n    (let ((util.cdn:*cdn-cache-key*\n            (ignore-errors\n             (file-write-date file-name))))\n      (let ((url (make-url 'artifact-get :name name)))\n        (cond\n          (cdn\n           (util.cdn:make-cdn url))\n          (t\n           (format nil \"~a~a\"\n                   (installation-domain *installation*)\n                   url)))))))\n\n(def-easy-macro def-artifact-hook (key artifact-name &fn fn)\n  (setf\n   (alexandria:assoc-value *artifact-hooks* key :test #'equal)\n   (make-instance 'artifact-hook\n                   :dep artifact-name\n                   :callback fn)))\n\n(defun call-hooks (dep)\n  (loop for (nil . hook) in *artifact-hooks*\n        if (equal dep (artifact-hook-dep hook))\n          do\n        (funcall (artifact-hook-callback hook))))\n"
  },
  {
    "path": "src/screenshotbot/assets.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/assets\n  (:use :cl)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  (:import-from #:core/ui/assets\n                #:*asset-list*\n                #:define-css\n                #:define-js\n                #:ensure-asset)\n  (:import-from #:screenshotbot/artifacts\n                #:artifact-file-name\n                #:artifact-link\n                #:def-artifact-hook)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-cdn\n                #:pre-compiled-assets)\n  (:import-from #:screenshotbot/server\n                #:acceptor\n                #:defhandler)\n  (:import-from #:util/store/store\n                #:add-datastore-hook)\n  (:export\n   #:*asset-list*\n   #:define-css\n   #:define-js\n   #:ensure-asset))\n(in-package :screenshotbot/assets)\n(named-readtables:in-readtable :interpol-syntax)\n\n\n(defhandler (static-recorder-master :uri \"/release/recorder-master.jar\") ()\n  (hunchentoot:handle-static-file #P \"/home/arnold/builds/silkwrmsdk/binary/build/libs/binary-0.1.0-all.jar\"))\n\n\n(flet ((output-file ()  (asdf:output-file 'asdf:compile-op\n                                          (asdf:find-component :screenshotbot.sdk/deliver \"deliver-sdk\"))) )\n (defhandler (static-recorder-linux :uri \"/release/recorder-linux.sh\") ()\n   (hunchentoot:handle-static-file\n    (output-file)))\n\n  (defhandler (nil :uri \"/recorder-linux.sh\") ()\n    (let ((util.cdn:*cdn-cache-key* (format nil \"~d\"\n                                            (ignore-errors\n                                             (file-write-date (output-file))))))\n     (hunchentoot:redirect (util.cdn:make-cdn \"/release/recorder-linux.sh\")))))\n\n(defhandler (nil :uri \"/recorder-master.jar\") ()\n  (hex:safe-redirect \"/release/recorder-master.jar\"))\n\n(define-css acceptor \"/assets/css/default.css\" :screenshotbot.css-assets)\n\n(defun generate-.sh (name)\n  (let ((domain (or\n                 (installation-cdn (installation))\n                 ;; A hack for staging:\n                 (installation-domain (installation)))))\n    (flet ((make-link (platform)\n             (format nil \"~a/artifact/${VERSION}~a-~a\" domain name platform))\n           (fetch (link)\n             (format nil \"$CURL --progress-bar ~a --output $INSTALLER\"\n                     link)))\n     (let* ((darwin-link (make-link \"darwin\"))\n            (linux-link (make-link \"linux\"))\n            (arm64-link (format nil \"~a-arm64\" linux-link))\n            (domain (installation-domain (installation))))\n       #?\"#!/bin/sh\nset -e\n\ntype=`uname`\n\nINSTALLER=screenshotbot-installer.sh\nCURL=\\\"curl --retry 3 \\\"\nVERSION=`$CURL --fail ${domain}/recorder-version/current || true`\n\nif [ $type = \\\"Linux\\\" ] ; then\n  if [ \\\"`uname -m`\\\" = \\\"aarch64\\\" ] ; then\n    ${(fetch arm64-link)}\n  else\n    ${(fetch linux-link)}\n  fi\nelif [ $type = \\\"Darwin\\\" ] ; then\n  ${(fetch darwin-link)}\nelse\n  echo Unknown uname type: $type, please message support@screenshotbot.io\nfi\nsh ./$INSTALLER\nrm -f $INSTALLER\n\"))))\n\n\n(defmacro define-platform-asset (name)\n  (let ((generate-fn (intern (format nil \"GENERATE-~a-PLATFORM-ASSETS\" (str:upcase name)))))\n   `(progn\n      (flet ((generate ()\n               (uiop:with-staging-pathname (output\n                                            (artifact-file-name ,(format nil \"~a.sh\" name)))\n                 (with-open-file (output output :direction :output\n                                                :if-exists :append)\n                   (write-string (generate-.sh ,name)\n                                 output)))))\n\n        (defun ,generate-fn ()\n          (generate))\n\n        ,@ (loop for suffix in '(\"darwin\" \"linux\")\n                 for full-name = (format nil \"~a-~a\"\n                                         name suffix)\n                 collect\n                 `(def-artifact-hook (',(intern full-name) ,full-name)\n                    (generate)))\n        (add-datastore-hook\n         ',generate-fn\n         :immediate t))\n\n      (defhandler (nil :uri ,(format nil \"/~a.sh\" name)) ()\n        (setf (hunchentoot:content-type*) \"application/x-sh\")\n        (hunchentoot:handle-static-file\n         (artifact-file-name (format nil \"~a.sh\" ,name))))\n\n      (defhandler (nil :uri ,(format nil \"/~a.exe\" name)) ()\n        (hunchentoot:redirect\n         (artifact-link ,(format nil \"~a-win.exe\" name) :cdn t))))))\n\n;; (call-hooks \"recorder-linux\")\n(define-platform-asset \"recorder\")\n\n(defhandler (recorder-incorrect :uri \"/recorder\") ()\n  (setf (hunchentoot:return-code*) 404)\n  \"Page not found. Did you mean /recorder.sh? Or for Windows, this would be /recorder-win.exe\")\n\n\n(define-js acceptor \"/assets/js/dashboard.js\" :screenshotbot.js-assets)\n\n(defhandler (nil :uri \"/setup-oss.sh\") ()\n  (setf (hunchentoot:content-type*) \"application/x-sh\")\n  (hunchentoot:handle-static-file\n   (asdf:system-relative-pathname :screenshotbot \"setup-oss.sh\")))\n\n\n(defhandler (nil :uri \"/screenshotbot-cli-versions\") ()\n  \"Used by `mise` to figure out all the versions of the CLI\"\n  (let ((lines (mapcar #'str:trim\n                       (str:lines\n                        (uiop:run-program\n                         \"ls /mnt/efs/fs1/production/object-store/artifacts/releases/*/recorder-darwin-without-installer.tar.gz\"\n                         :output 'string\n                         :error-output t)))))\n    (json:encode-json-to-string\n     (sort\n      (loop for line in lines\n            for ver = (elt (reverse (str:split \"/\" line)) 1)\n            unless (or (equal ver \"NIL\")\n                       (str:containsp \"-\" ver))\n              collect ver)\n      #'uiop:version<))))\n"
  },
  {
    "path": "src/screenshotbot/async.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/async\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:server\n                #:*shutdown-hooks*)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool)\n  (:import-from #:lparallel.promise\n                #:promise)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:sb/future\n   #:*magick-pool*))\n(in-package :screenshotbot/async)\n\n(defvar *kernel* nil)\n\n(defvar *magick-pool* nil)\n\n(defun reinit-pool ()\n  (setf *magick-pool* (make-instance 'max-pool :max (serapeum:count-cpus :default 4))))\n\n(reinit-pool)\n\n#+lispworks\n(lw:define-action \"When starting image\" \"Reset magick pool\"\n  #'reinit-pool)\n\n(def-easy-macro magick-future (&fn fn)\n  (let ((promise (lparallel:promise)))\n    (prog1\n        promise\n      (make-thread\n       (lambda ()\n         (lparallel:fulfill promise (fn)))\n       :pool *magick-pool*))))\n"
  },
  {
    "path": "src/screenshotbot/audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/audit-log\n  (:use #:cl)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/model/auto-cleanup\n                #:register-auto-cleanup)\n  (:import-from #:util/misc\n                #:uniq)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/simple-queue\n                #:enqueue-with-max-length\n                #:make-queue)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:base-audit-log\n   #:audit-logs-for-company\n   #:audit-log-error\n   #:with-audit-log\n   #:audit-log-company))\n(in-package :screenshotbot/audit-log)\n\n(defindex +company-index+\n  'fset-set-index\n  :slot-name '%%company)\n\n(defvar *error-log* (make-queue))\n\n(with-class-validation\n  (defclass base-audit-log (store-object)\n    ((%%company :initarg :company\n                :index +company-index+\n                :index-reader %audit-logs-for-company\n                :reader audit-log-company)\n     (%%err :initarg :error\n            :initform nil\n            :accessor audit-log-error)\n     (%%ts :initarg :ts\n           :reader %created-at))\n    (:default-initargs :ts (get-universal-time))\n    (:metaclass persistent-class)))\n\n(register-auto-cleanup 'base-audit-log :timestamp #'%created-at)\n\n(defmethod audit-logs-for-company (company type)\n  (remove-if-not\n   (lambda (log)\n     (typep log type))\n   (reverse\n    (fset:convert\n     'list\n     (%audit-logs-for-company company)))))\n\n(def-easy-macro with-audit-log (&binding audit-log expr &fn fn)\n  (handler-bind ((error\n                   (lambda (e)\n                     (enqueue-with-max-length e *error-log*\n                                              :max-length 10000)\n                     (unless (audit-log-error expr)\n                       (with-transaction ()\n                         (setf (audit-log-error expr)\n                               (format nil \"~a\" e)))))))\n    (funcall fn expr)))\n"
  },
  {
    "path": "src/screenshotbot/aws/aws.lisp",
    "content": "(defpackage :screenshotbot/aws/aws\n  (:use #:cl))\n(in-package :screenshotbot/aws/aws)\n\n\n(defvar *aws-profile* \"AdministratorAccess-096795202767\")\n(defvar *vpc-id* \"vpc-0cb0e16b\" #| primary-vpc |#)\n\n(defun %aws (args &key (output 'string))\n  (uiop:run-program\n   `(\"aws\"\n     ,@args\n     \"--profile\" ,*aws-profile*)\n   :output output\n   :error-output t))\n\n(defun aws (&rest args)\n  (%aws args ))\n\n(defvar *region* \"us-east-1b\")\n\n(defun create-security-group (group)\n  (aws\n   \"ec2\" \"create-security-group\"\n   \"--group-name\" group\n   \"--vpc-id\" *vpc-id*\n   \"--description\" (format nil \"Security group for cluster ~a\" group)))\n\n(defvar *cluster* \"wdb-sandbox\")\n\n(defun create-cluster (group)\n  (let ((*cluster* group))\n    (setf (uiop:getenv \"AWS_PROFILE\")  *aws-profile*)\n    (create-security-group *cluster*)))\n"
  },
  {
    "path": "src/screenshotbot/aws/screenshotbot.aws.asd",
    "content": "(defsystem :screenshotbot.aws\n  :serial t\n  :depends-on ()\n  :components ((:file \"aws\")))\n"
  },
  {
    "path": "src/screenshotbot/azure/audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/audit-log\n  (:use #:cl)\n  (:import-from #:screenshotbot/audit-log\n                #:base-audit-log)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:with-class-validation)\n  (:export\n   #:pr-update-request\n   #:commit-update-request))\n(in-package :screenshotbot/azure/audit-log)\n\n(with-class-validation\n  (defclass audit-log (base-audit-log)\n    ()\n    (:metaclass persistent-class)))\n\n(defclass pr-update-request (audit-log)\n  ((pr-id :initarg :pr-id\n          :reader pr-id)\n   (repository-id :initarg :repository-id\n                  :reader audit-log-repository-id))\n  (:metaclass persistent-class))\n\n(defclass commit-update-request (audit-log)\n  ((commit-id :initarg :commit-id\n              :reader commit-id)\n   (repository-id :initarg :repository-id\n                  :reader audit-log-repository-id))\n  (:metaclass persistent-class))\n"
  },
  {
    "path": "src/screenshotbot/azure/plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/plugin\n  (:use #:cl)\n  (:import-from #:screenshotbot/plugin\n                #:plugin-parse-repo\n                #:plugin)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/installation\n                #:find-plugin\n                #:installation)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo)\n  (:export\n   #:azure-plugin\n   #:azure-server\n   #:azure-settings\n   #:azure-access-token\n   #:azure-settings-for-company\n   #:azure-git-repo\n   #:azure-hostname))\n(in-package :screenshotbot/azure/plugin)\n\n(defclass azure-plugin (plugin)\n  ())\n\n(with-class-validation\n  (defclass azure-settings (store-object)\n    ((%server :initarg :server\n              :reader azure-server)\n     (%personal-access-token\n      :initarg :access-token\n      :reader azure-access-token)\n     (%company :reader company\n               :index-type unique-index\n               :index-reader azure-settings-for-company\n               :initarg :company))\n    (:metaclass persistent-class)))\n\n(defmethod azure-hostname ((self azure-settings))\n  (quri:uri-host (quri:uri (azure-server self))))\n\n(defclass azure-git-repo (generic-git-repo)\n  ())\n\n(defmethod plugin-parse-repo ((plugin azure-plugin)\n                              company\n                              repo-url)\n  (when-let ((settings (azure-settings-for-company company)))\n    (let ((hostname (quri:uri-host (quri:uri (azure-server settings)))))\n      (when (str:containsp hostname repo-url)\n        (make-instance 'azure-git-repo\n                       :company company\n                       :link repo-url)))))\n\n(defun azure-plugin (&key (installation (installation)))\n  (find-plugin installation 'azure-plugin))\n"
  },
  {
    "path": "src/screenshotbot/azure/promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/promoter\n  (:use #:cl\n        #:screenshotbot/azure/plugin)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:check-sha\n                #:check-key\n                #:make-promoter-for-acceptable\n                #:details-url\n                #:check-status\n                #:check-title\n                #:check\n                #:push-remote-check\n                #:abstract-pr-acceptable\n                #:make-acceptable\n                #:promoter-pull-id\n                #:plugin-installed?\n                #:valid-repo?\n                #:abstract-pr-promoter)\n  (:import-from #:screenshotbot/promote-api\n                #:plugin-promoter)\n  (:import-from #:screenshotbot/azure/plugin\n                #:azure-access-token\n                #:azure-plugin)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:push-run-warning\n                #:recorder-run-company\n                #:github-repo\n                #:pull-request-id\n                #:recorder-run)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/azure/request\n                #:create-commit-status\n                #:azure-unauthorized-error\n                #:git-status-context\n                #:pull-request-status\n                #:create-pull-request-status\n                #:azure)\n  (:import-from #:screenshotbot/user-api\n                #:recorder-run-channel\n                #:channel-name)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/azure/run-warnings\n                #:azure-unauthorized-warning))\n(in-package :screenshotbot/azure/promoter)\n\n\n(defclass azure-promoter (abstract-pr-promoter)\n  ())\n\n(with-class-validation\n  (defclass azure-acceptable (abstract-pr-acceptable)\n    ()\n    (:metaclass persistent-class)))\n\n(defmethod plugin-promoter ((self azure-plugin))\n  (make-instance 'azure-promoter))\n\n(defmethod valid-repo? ((self azure-promoter) (repo azure-git-repo))\n  t)\n\n(defmethod plugin-installed? ((self azure-promoter)\n                              (company company)\n                              url)\n  (azure-settings-for-company company))\n\n(defmethod promoter-pull-id ((self azure-promoter)\n                             (run recorder-run))\n  (pull-request-id run))\n\n(defmethod make-acceptable ((self azure-promoter)\n                            report\n                            &rest args)\n  (apply #'make-instance 'azure-acceptable\n         :report report\n         args))\n\n(defun parse-org-and-project (url hostname)\n  (let* ((parts (str:split \"/\" url))\n         (host-pos (loop for part in parts\n                         for i from 0\n                         if (str:containsp hostname part)\n                           return i)))\n    (values\n     (elt parts (+ host-pos 1))\n     (elt parts (+ host-pos 2))\n     (car (last parts)))))\n\n#|\nDocumentation here: https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-statuses/create?view=azure-devops-rest-7.1&tabs=HTTP#gitstatusstate\n|#\n\n(defvar +succeeded+ \"succeeded\")\n(defvar +failed+ \"failed\")\n(defvar +pending+ \"pending\")\n\n(def-easy-macro with-run-warnings (run &fn fn)\n  \"A helper function to check for certain AZURE-ERRORs and convert them\ninto run warnings\"\n  (handler-bind ((azure-unauthorized-error (lambda (e)\n                                             (declare (ignore e))\n                                             (push-run-warning\n                                              run\n                                              'azure-unauthorized-warning))))\n   (fn)))\n\n(defmethod push-remote-check ((self azure-promoter)\n                              run\n                              (check check))\n  (util/threading:with-extras ((\"run\" run))\n    (let ((settings (azure-settings-for-company\n                     (recorder-run-company run))))\n      (multiple-value-bind (org project repo)\n          (parse-org-and-project (github-repo run)\n                                 (azure-hostname settings))\n        (let ((azure (make-instance 'azure\n                                    :hostname (azure-hostname settings)\n                                    :token (azure-access-token settings)\n                                    :organization org\n                                    :project project)))\n          (with-run-warnings (run)\n            (push-remote-check-impl azure\n                                    :run run\n                                    :repo repo\n                                    :check check)))))))\n\n(defmethod push-remote-check-impl (azure &key repo run check)\n  (let ((status (make-instance 'pull-request-status\n                               :description (format nil \"Screenshotbot: ~a\"\n                                                    (check-title check))\n                               :state (ecase (check-status check)\n                                        (:accepted +succeeded+)\n                                        (:pending +pending+)\n                                        (:rejected +failed+)\n                                        (:success +succeeded+)\n                                        (:failure +failed+)\n                                        (:action-required +failed+))\n                               :target-url (details-url check)\n                               :context (make-instance 'git-status-context\n                                                       :name (check-key check)))))\n   (cond\n     ((gk:check :azure-commit-status (recorder-run-company run))\n      ;; This doesn't work yet. By default it seems like DevOps runs\n      ;; the build against the merge commit, and the override commit\n      ;; hash is not properly computed.\n      (create-commit-status\n       azure\n       status\n       :company (recorder-run-company run)\n       :repository-id repo\n       :commit-id (check-sha check)))\n     (t\n      (create-pull-request-status\n       azure\n       status\n       :company (recorder-run-company run)\n       :repository-id repo\n       :pull-request-id (pull-request-id run))))))\n\n(defmethod make-promoter-for-acceptable ((acceptable azure-acceptable))\n  (make-instance 'azure-promoter))\n"
  },
  {
    "path": "src/screenshotbot/azure/request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/request\n  (:use #:cl)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:screenshotbot/audit-log\n                #:with-audit-log)\n  (:import-from #:screenshotbot/azure/audit-log\n                #:pr-update-request\n                #:commit-update-request)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:export\n   #:azure\n   #:azure-error\n   #:azure-unauthorized-error\n   #:git-status-context\n   #:pull-request-status\n   #:create-pull-request-status\n   #:create-commit-status))\n(in-package :screenshotbot/azure/request)\n\n\n(defclass git-status-context ()\n  ((genre :initform \"screenshotbot\"\n          :json-key \"genre\"\n          :json-type :string)\n   (name :initarg :name\n         :json-key \"name\"\n         :json-type :string))\n  (:metaclass ext-json-serializable-class))\n\n(defclass pull-request-status ()\n  ((description :json-key \"description\"\n                :json-type :string\n                :initarg :description)\n   (state :json-key \"state\"\n          :json-type :string\n          :initarg :state)\n   (context :initarg :context\n            :json-key \"context\"\n            :json-type git-status-context)\n   (target-url :initarg :target-url\n               :json-key \"targetUrl\"\n               :json-type :string))\n  (:metaclass ext-json-serializable-class))\n\n\n(defvar *token* nil)\n\n(defclass azure ()\n  ((token :initarg :token\n          :reader token)\n   (hostname :initarg :hostname\n             :initform \"dev.azure.com\"\n             :reader hostname)\n   (organization :initarg :organization\n                 :reader organization)\n   (project :initarg :project\n            :reader project)))\n\n(defparameter *test-azure* (make-instance 'azure :token *token*\n                                                 :organization \"testsbot\"\n                                                 :project \"fast-example\"))\n\n(define-condition azure-error (simple-error)\n  ((headers :initarg :headers)))\n\n(define-condition azure-unauthorized-error (azure-error)\n  ()\n  (:report \"Unauthorized: check your access token to make sure it hasn't expired, and it is valid for the given project.\"))\n\n\n(defun azure-request (azure url &key\n                                  method\n                                  response-type\n                                  parameters\n                                  content)\n  (declare (optimize (speed 0) (debug 3)))\n  (let ((content (with-output-to-string (out)\n                   (yason:encode content out)))\n        (url (format nil \"https://~a/~a/~a/_apis/~a?api-version=7.0\"\n                (hostname azure)\n                (organization azure)\n                (project azure)\n                url)))\n    (log:info \"Sending body: ~a to ~a\" content url)\n    (multiple-value-bind (response code headers)\n       (http-request\n        url\n        :content content\n        :content-type \"application/json\"\n        :basic-authorization (list \"\" (token azure))\n        :accept \"application/json\"\n        :parameters parameters\n        :method method\n        :want-string t)\n      (cond\n        ((eql 401 code)\n         (error 'azure-unauthorized-error\n                :headers headers))\n        ((<= 400 code 510)\n         (error 'azure-error\n                :headers headers\n                :format-control \"Azure returned code ~a with body ~a\"\n                :format-arguments (list code response)))\n        ((str:containsp \"text/html\" (assoc-value headers :content-type))\n         (error 'azure-error\n                :headers headers\n                :format-control \"API returned HTML, not JSON. This is likely because the Personal Access Token is incorrect.\"))\n        (t\n         (json-mop:json-to-clos response response-type))))))\n\n(defmethod create-pull-request-status (azure status\n                                       &key repository-id\n                                         company\n                                         pull-request-id)\n  (declare (optimize (speed 0) (debug 3)))\n  (with-audit-log (audit-log (make-instance 'pr-update-request\n                                            :pr-id pull-request-id\n                                            :company company\n                                            :repository-id repository-id))\n    (declare (ignore audit-log))\n    (azure-request\n     azure\n     (format nil\n             \"git/repositories/~a/pullRequests/~a/statuses\"\n             repository-id\n             pull-request-id)\n     :method :post\n     :response-type 'pull-request-status\n     :content status)))\n\n(defmethod create-commit-status (azure status\n                                 &key repository-id\n                                   company\n                                   commit-id)\n  (declare (optimize (speed 0) (debug 3)))\n  (with-audit-log (audit-log (make-instance 'commit-update-request\n                                            :commit-id commit-id\n                                            :company company\n                                            :repository-id repository-id))\n    (declare (ignore audit-log))\n    (azure-request\n     azure\n     (format nil\n             \"git/repositories/~a/commits/~a/statuses\"\n             repository-id\n             commit-id)\n     :method :post\n     :response-type 'pull-request-status\n     :content status)))\n\n\n\n;; See https://phabricator.tdrhq.com/w/azure_devops/\n\n\n#|\nhttps://learn.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops&tabs=browser#build-validation\n|#\n\n#+nil\n(create-pull-request-status\n *test-azure*\n (make-instance 'pull-request-status\n                :description \"some stuff\"\n                :state \"failed\"\n                :target-url \"https://screenshotbot.io/runs\"\n                :context (make-instance 'git-status-context\n                                        :name \"foobar\"))\n :repository-id \"fast-example\"\n :pull-request-id 1)\n\n#+nil\n(create-commit-status\n *test-azure*\n (make-instance 'pull-request-status\n                :description \"some stuff\"\n                :state \"succeeded\"\n                :target-url \"https://screenshotbot.io/runs\"\n                :context (make-instance 'git-status-context\n                                        :name \"foobar\"))\n :repository-id \"fast-example\"\n :commit-id \"55da297c51d36e34297004fd414ac14ea643342d\")\n"
  },
  {
    "path": "src/screenshotbot/azure/run-warnings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/run-warnings\n  (:use #:cl)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:warning-alert\n                #:render-run-warning)\n  (:import-from #:screenshotbot/model/core\n                #:non-root-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:base-run-warning)\n  (:export\n   #:azure-unauthorized-warning))\n(in-package :screenshotbot/azure/run-warnings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass azure-unauthorized-warning (base-run-warning)\n  ()\n  (:metaclass persistent-class))\n\n\n(defmethod render-run-warning (run (self azure-unauthorized-warning))\n  <warning-alert type= \"danger\" call-out= \"\" >\n    <span>Failed to update Azure build status. Please verify that the Personal Access Token is still valid, and has permissions to access this project. You can update the token <a href= \"/settings/azure\">here</a>.</span>\n  </warning-alert>)\n\n\n"
  },
  {
    "path": "src/screenshotbot/azure/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/settings\n  (:use #:cl\n        #:screenshotbot/azure/plugin)\n  (:import-from #:screenshotbot/azure/plugin\n                #:azure-plugin)\n  (:import-from #:screenshotbot/settings-api\n                #:settings-template\n                #:defsettings)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/form-errors\n                #:with-error-builder)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page)\n  (:import-from #:screenshotbot/azure/audit-log\n                #:audit-log-repository-id\n                #:pr-id\n                #:pr-update-request\n                #:audit-log)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:describe-audit-log\n                #:render-audit-logs)\n  (:import-from #:screenshotbot/azure/request\n                #:repository-id))\n(in-package :screenshotbot/azure/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar +default-hostname+ \"https://dev.azure.com\")\n\n\n(defun go-home ()\n  (hex:safe-redirect \"/settings/azure\"))\n\n(defun finalize-settings (server access-token)\n  (let ((existing (azure-settings-for-company (current-company))))\n    (when existing\n      (delete-object existing))\n    (make-instance 'azure-settings\n                   :company (current-company)\n                   :access-token access-token\n                   :server server))\n  (go-home))\n\n(defun save-settings (server access-token)\n  (with-error-builder (:check check\n                       :success (finalize-settings server\n                                                   access-token)\n                       :form-builder (azure-settings-page)\n                       :form-args (:server server\n                                   :access-token access-token)\n                       :errors errors)\n    (check :server (not (str:emptyp server))\n           \"Server should not be empty\")\n    (check :server (ignore-errors (quri:uri server))\n           \"Could not parse URI provided\")\n    (check :access-token (not (str:emptyp access-token))\n           \"Personal access token cannot be empty\")))\n\n(defun disconnect-azure ()\n  (confirmation-page\n   :yes (nibble ()\n          (?. delete-object (azure-settings-for-company (current-company)))\n          (go-home))\n   :no (nibble () (go-home))\n   :danger t\n   <span>Are you sure you want to clear the access token information? Re-enabling it might require special permissions on your Azure installation.</span>))\n\n(defun azure-settings-page ()\n  (let* ((settings (azure-settings-for-company (current-company)))\n         (existing-token (?. azure-access-token settings))\n         (submit (nibble (server access-token)\n                   (save-settings server\n                                  (cond\n                                    ((equal \"unchanged\" access-token)\n                                     existing-token)\n                                    (t\n                                     access-token))))))\n    <settings-template>\n      <form action=submit method= \"POST\">\n        <div class= \"card mt-3\">\n          <div class= \"card-header\">\n            <h3>Azure DevOps Integration</h3>\n          </div>\n\n          <div class= \"card-body\">\n            <div class= \"mb-3\">\n              <label for= \"server\" class= \"form-label\" >\n                Server URL\n              </label>\n\n              <input type= \"text\" class= \"form-control\" name= \"server\"\n                     value= (or (?. azure-server settings) +default-hostname+)\n                     placeholder= +default-hostname+ />\n            </div>\n\n            <div class= \"mb-3\">\n              <label for= \"access-token\" class= \"form-label\">\n                Personal Access Token\n              </label>\n\n              <input type= \"password\" class= \"form-control\" name= \"access-token\"\n                     value= (when settings \"unchanged\")\n                     placeholder= \"*****\" />\n            </div>\n          </div>\n\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n            ,(when settings\n               <a href= (nibble () (disconnect-azure)) class= \"btn btn-danger\" >\n                 Disconnect\n               </a>)\n          </div>\n        </div>\n      </form>\n\n      ,(render-audit-logs\n        :type 'audit-log)\n    </settings-template>))\n\n(defmethod describe-audit-log ((self pr-update-request))\n  <span>\n    Update Pull Request <tt>,(pr-id self)</tt> for ,(audit-log-repository-id self)\n  </span>)\n\n(defsettings azure-settings\n  :name \"azure\"\n  :title \"Azure DevOps\"\n  :section :vcs\n  :plugin 'azure-plugin\n  :handler 'azure-settings-page)\n"
  },
  {
    "path": "src/screenshotbot/azure/test-plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/test-plugin\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/azure/plugin\n                #:azure-git-repo\n                #:azure-settings\n                #:azure-plugin)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:fiveam-matchers/misc\n                #:is-null)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/plugin\n                #:plugin-parse-repo)\n  (:import-from #:screenshotbot/model/company\n                #:company))\n(in-package :screenshotbot/azure/test-plugin)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation (:installation (make-instance\n                                     'installation\n                                     :plugins (list\n                                               (make-instance 'azure-plugin))))\n    (with-test-store ()\n      (let ((company (make-instance 'company)))\n       (&body)))))\n\n(test azure-plugin-happy-path\n  (with-fixture state ()\n    (is-true (azure-plugin))))\n\n(test plugin-parse-repo-for-non-azure\n  (with-fixture state ()\n    (assert-that\n     (plugin-parse-repo (azure-plugin)\n                        company\n                        \"https://github.com/tdrhq/fast-example\")\n     (is-null))))\n\n(test plugin-parse-repo-for-azure\n  (with-fixture state ()\n    (make-instance 'azure-settings\n                   :company company\n                   :server \"https://dev.azure.com\"\n                   :access-token \"foo\")\n    (let ((repo (plugin-parse-repo (azure-plugin)\n                                   company\n                                   \"https://testsbot@dev.azure.com/testsbot/fast-example/_git/fast-example\")))\n      (assert-that\n       repo\n      (has-typep 'azure-git-repo))\n      (is (eql company (screenshotbot/git-repo::company repo))))))\n"
  },
  {
    "path": "src/screenshotbot/azure/test-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :screenshotbot/azure/test-promoter\n  (:use #:cl\n        #:fiveam\n        #:screenshotbot/abstract-pr-promoter)\n  (:import-from #:screenshotbot/azure/promoter\n                #:with-run-warnings\n                #:parse-org-and-project\n                #:azure-promoter)\n  (:import-from #:screenshotbot/azure/plugin\n                #:azure-git-repo)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-warnings\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/azure/request\n                #:azure-unauthorized-error)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :screenshotbot/azure/test-promoter)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n   (let ((promoter (make-instance 'azure-promoter)))\n     (&body))))\n\n(test valid-repo-for-azure\n  (with-fixture state ()\n    (is-true (valid-repo? promoter\n                         (make-instance 'azure-git-repo\n                                        :link \"foo\")))\n    (is-false (valid-repo? promoter\n                           (make-instance 'generic-git-repo\n                                          :link \"foo\")))))\n\n\n(test parse-org-and-project\n  (multiple-value-bind\n        (org project repo)\n      (parse-org-and-project \"git@ssh.dev.azure.com:v3/foogroup/Foo%20app/foo-app-flutter\" \"dev.azure.com\")\n    (is (equal org \"foogroup\"))\n    (is (equal project \"Foo%20app\"))\n    (is (equal repo \"foo-app-flutter\"))))\n\n(test add-run-warnings\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run :channel channel\n                                   :screenshots nil)))\n      (is\n       (eql\n        :foobar\n        (with-run-warnings (run)\n          :Foobar)))\n      (signals azure-unauthorized-error\n        (with-run-warnings (run)\n          (error 'azure-unauthorized-error)))\n      (assert-that\n       (recorder-run-warnings run)\n       (has-length 1)))))\n"
  },
  {
    "path": "src/screenshotbot/azure/test-request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :screenshotbot/azure/test-request\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/request\n                #:*engine*\n                #:http-request-impl)\n  (:import-from #:screenshotbot/azure/request\n                #:azure-unauthorized-error\n                #:azure-request\n                #:azure)\n  (:import-from #:screenshotbot/azure/promoter\n                #:push-remote-check-impl)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:check))\n(in-package :screenshotbot/azure/test-request)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    ;; if you need a non-401-engine, still keep this around as a\n    ;; default for these dtests.\n    (with-test-store ()\n      (let* ((*engine* (make-instance '401-engine))\n             (company (make-instance 'company))\n             (run (make-recorder-run :screenshots nil :company company)))\n        (gk:create :azure-commit-status)\n        (let ((azure (make-instance 'azure\n                                    :token \"dfd\"\n                                    :organization \"testsbot\"\n                                    :project \"fast-example\")))\n          (&body))))))\n\n(defclass 401-engine ()\n  ())\n\n(defvar *request-callback* (lambda (url &key &allow-other-keys)\n                             (values \"\" 401 nil)))\n\n(defmethod http-request-impl ((self 401-engine)\n                              url &rest args &key &allow-other-keys)\n  (apply *request-callback* self args))\n\n(test handles-401-more-gracefully\n  (with-fixture state ()\n    (signals azure-unauthorized-error\n      (azure-request\n       azure\n       \"foo/bar\"\n       :method :post))))\n\n(test push-remote-check-impl-happy-path\n  (let ((*request-callback* (lambda (url &key &allow-other-keys)\n                    (values \"{}\" 200 nil))))\n    (with-fixture state ()\n      (finishes\n       (push-remote-check-impl\n        azure\n        :run run\n        :repo \"testbot/fast-example\"\n        :check (make-instance 'check\n                              :key \"blahblah\"\n                              :sha \"abcd\"\n                              :title \"Foobarxs\"\n                              :details-url \"https://example.com\"\n                              :status :accepted))))))\n\n(test push-remote-check-impl-with-new-azure-commit-status\n  (let ((*request-callback* (lambda (url &key &allow-other-keys)\n                              (values \"{}\" 200 nil))))\n    (with-fixture state ()\n      (gk:allow :azure-commit-status company)\n      (finishes\n       (push-remote-check-impl\n        azure\n        :run run\n        :repo \"testbot/fast-example\"\n        :check (make-instance 'check\n                              :key \"blahblah\"\n                              :sha \"abcd\"\n                              :title \"Foobarxs\"\n                              :details-url \"https://example.com\"\n                              :status :accepted))))))\n\n"
  },
  {
    "path": "src/screenshotbot/azure/test-run-warnings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/test-run-warnings\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:fix-timestamps\n                #:with-test-user\n                #:screenshot-test)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:render-run-page\n                #:run-page)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:push-run-warning\n                #:make-recorder-run)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/azure/run-warnings\n                #:azure-unauthorized-warning))\n(in-package :screenshotbot/azure/test-run-warnings)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-test-user (:company company\n                     :logged-in-p t)\n     (let* ((channel (make-instance 'channel :company company))\n            (run (make-recorder-run :channel channel\n                                    :screenshots nil)))\n       (&body)))))\n\n(screenshot-test azure-unauthorized-warning\n  (with-fixture state ()\n    (push-run-warning run 'azure-unauthorized-warning)\n    (fix-timestamps\n     (render-run-page run))))\n\n"
  },
  {
    "path": "src/screenshotbot/azure/test-settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/azure/test-settings\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:screenshotbot/azure/settings\n                #:azure-settings-page)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :screenshotbot/azure/test-settings)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n       (&body)))))\n\n(screenshot-test azure-empty-settings-page\n  (with-fixture state ()\n   (azure-settings-page)))\n"
  },
  {
    "path": "src/screenshotbot/batch-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/batch-promoter\n  (:use #:cl)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:+nothing-to-review+\n                #:check-title\n                #:check-status\n                #:check-user\n                #:check\n                #:make-check\n                #:push-remote-check\n                #:push-remote-check-via-batching)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-item-title\n                #:batch-item-status\n                #:batch\n                #:batch-name\n                #:batch-commit\n                #:batch-item-report\n                #:batch-item-run\n                #:batch-item-channel\n                #:find-batch-item\n                #:batch-item\n                #:batch-items)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:recorder-run-commit\n                #:recorder-run-channel)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:unchanged-run\n                #:recorder-run-company\n                #:github-repo)\n  (:import-from #:hunchentoot-extensions\n                #:make-full-url)\n  (:import-from #:screenshotbot/dashboard/batch\n                #:batch-item-link\n                #:sort-items\n                #:batch-handler)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:local-nicknames (#:batch #:screenshotbot/model/batch)))\n(in-package :screenshotbot/batch-promoter)\n\n(named-readtables:in-readtable markup:syntax)\n\n(auto-restart:with-auto-restart ()\n (defmethod push-remote-check-via-batching (promoter\n                                            batch\n                                            run\n                                            check)\n   (bt:with-lock-held ((batch:lock batch))\n     (let ((item (or\n                  (find-batch-item batch :channel (recorder-run-channel run))\n                  (make-instance 'batch-item\n                                 :channel (recorder-run-channel run)\n                                 :batch batch))))\n       (setf (batch-item-channel item) (recorder-run-channel run))\n       (setf (batch-item-run item) run)\n       (setf (batch-item-report item) (report check))\n       (setf (batch-item-status item) (check-status check))\n       (setf (batch-item-title item) (check-title check))\n       (setf (batch:state-invalidated-p batch) t)))\n\n   (unless (check-status check)\n     (warn \"Got a NIL status for ~a\" run))\n\n   (maybe-push-remote-check promoter batch :user (check-user check))))\n\n(defmethod push-remote-check-via-batching (promoter\n                                           batch\n                                           (run unchanged-run)\n                                           check)\n  (declare (ignore check))\n\n  ;; Note that an unchanged run will not cause the batch state to be\n  ;; invalidated. This means if it's a new batch without any runs,\n  ;; this check will go through, otherwise nothing happens right now.\n  (maybe-push-remote-check promoter batch :user nil))\n\n(defmethod maybe-push-remote-check (promoter batch &key (user (error \"must provide user for audit-logs\")))\n  (bt:with-lock-held ((batch:push-lock batch))\n    (let ((check (compute-check-if-invalidated\n                  batch user)))\n      (when check\n        (push-remote-check\n         promoter\n         batch\n         check))\n      ;; Avoid returning any unwanted values\n      nil)))\n\n(defun compute-check-if-invalidated (batch user)\n  (bt:with-lock-held ((batch:lock batch))\n    (when (batch:state-invalidated-p batch)\n      (prog1\n          (compute-check batch\n                         :user user)\n        ;; This next change needs to be made while the lock is held\n        ;; for concurrency reasons\n        (setf (batch:state-invalidated-p batch) nil)))))\n\n(defun compute-status (items)\n  (cond\n    ((fset:empty? items)\n     :success)\n    (t\n     (let ((statuses (fset:image #'batch-item-status items)))\n       (loop for status in (list :rejected\n                                 :failure\n                                 :action-required\n                                 :pending\n                                 :accepted\n                                 :success)\n             if (fset:contains? statuses status)\n               return status)))))\n\n(defun compute-title (items)\n  (cond\n    ((= 1 (fset:size items))\n     (batch-item-title (fset:least items)))\n    (t\n     (ecase (compute-status items)\n       (:rejected \"Some screenshots were rejected\")\n       (:failure \"Failures\")\n       (:action-required \"Some screenshots need review\")\n       (:pending \"Waiting for a previous run\")\n       (:accepted \"All screenshots accepted\")\n       (:success\n        (cond\n         ((and\n           (not (fset:empty? items))\n           (fset:every (lambda (item)\n                         (equal +nothing-to-review+ (batch-item-title item)))\n                       items))\n          +nothing-to-review+)\n         (t\n          \"No screenshots changed\")))))))\n\n(defmethod compute-check ((batch batch)\n                          &key user)\n  (make-instance 'check\n                 :sha (batch-commit batch)\n                 :key (batch-name batch)\n                 :user user ;; The user who initiated this check request, for audit-logs\n                 :title (compute-title (batch-items batch))\n                 :details-url (quri:render-uri\n                               (quri:merge-uris\n                                (hex:make-url\n                                 'batch-handler\n                                 :oid (oid batch))\n                                (installation-domain (installation))))\n                 :status (compute-status (batch-items batch))\n                 :summary\n                 (cond\n                   ((fset:empty? (batch-items batch))\n                    \"Nothing to review\")\n                   (t\n                    (if (gk:check :markdown-summary (recorder-run-company batch) :default t)\n                        (build-check-summary batch)\n                        \"Please review the changes to make sure they look reasonable\")))))\n\n(defun build-check-summary (batch)\n  ;; CAUTION: EMPTY LINES MATTER IN THIS CODE, else GitHub won't parse\n  ;; the table correctly.\n  (markup:write-html\n   (let ((items (sort-items (fset:convert 'list (batch-items batch)))))\n     <table>\n     ,@ (loop for item in items collect\n              <tr>\n                <td>\n                  ,(emoticon-for-status (batch-item-status item))\n                </td>\n                <td>\n                  <a href= (quri:render-uri (quri:merge-uris\n                     (batch-item-link item)\n                     (installation-domain (installation))))\n                     >,(channel-name (batch-item-channel item))</a>\n                </td>\n                <td>,(batch-item-title item) </td>\n              </tr>)\n     </table>)))\n\n(defun emoticon-for-status (status)\n  (ecase status\n    (:accepted \":white_check_mark:\")\n    (:rejected \":x:\")\n    (:success \":white_check_mark:\")\n    (:failure \":x:\")\n    (:pending \":eyes:\")\n    (:action-required \":x:\")))\n"
  },
  {
    "path": "src/screenshotbot/billing-meter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/billing-meter\n  (:use #:cl)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors))\n(in-package :screenshotbot/billing-meter)\n\n(defgeneric incr-billing-meter-impl (installation company name value)\n  (:documentation \"Increment a billing metric for the given company.\n\nname is a symbol for the metric name.\n\nvalue is a number.\n\nBy default this does nothing.\")\n  (:method (installation company name value)\n    nil))\n\n(defmethod incr-billing-meter (company name value)\n  (ignore-and-log-errors ()\n   (incr-billing-meter-impl\n    *installation*\n    company\n    name\n    value)))\n\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/audit-log\n  (:nicknames :screenshotbot/pro/bitbucket/audit-log)\n  (:use #:cl)\n  (:import-from #:bknr.indices\n                #:skip-list-index)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/pro/bitbucket/core\n                #:bitbucket-error)\n  (:import-from #:util/misc\n                #:uniq)\n  (:import-from #:screenshotbot/model/auto-cleanup\n                #:register-auto-cleanup)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors)\n  (:import-from #:screenshotbot/audit-log\n                #:audit-log-error\n                #:audit-logs-for-company\n                #:base-audit-log\n                #:with-audit-log)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:audit-log\n   #:build-status-audit-log\n   #:bitbucket-audit-logs-for-company\n   #:audit-log-error-response\n   #:build-status-audit-log-commit\n   #:build-status-audit-log-full-name\n   #:http-result-code\n   #:access-token-audit-log\n   #:access-token-audit-log-grant-type\n   #:with-audit-log))\n(in-package :screenshotbot/bitbucket/audit-log)\n\n(with-class-validation\n (defclass audit-log (base-audit-log)\n   ((%company :index-type hash-index\n              :index-reader %bitbucket-audit-logs-for-company)\n    (err :initform nil\n         :accessor %audit-log-error)\n    (error-response :initform nil\n                    :accessor audit-log-error-response)\n    (http-result-code :initform nil\n                      :accessor http-result-code)\n    (ts :initarg :ts\n        :reader %created-at))\n   (:default-initargs :ts (get-universal-time))\n   (:metaclass persistent-class)))\n\n(register-auto-cleanup 'audit-log :timestamp #'%created-at)\n\n(defmethod audit-log-error ((self audit-log))\n  ;; For migration\n  (or\n   (%audit-log-error self)\n   (call-next-method)))\n\n(with-class-validation\n (defclass build-status-audit-log (audit-log)\n   ((commit :initarg :commit\n            :reader build-status-audit-log-commit\n            :initform nil)\n    (full-name :initarg :full-name\n               :reader build-status-audit-log-full-name\n               :initform nil))\n   (:metaclass persistent-class)))\n\n(with-class-validation\n (defclass access-token-audit-log (audit-log)\n   ((grant-type :initarg :grant-type\n                :reader access-token-audit-log-grant-type))\n   (:metaclass persistent-class)))\n\n(defun bitbucket-audit-logs-for-company (company)\n  (append\n   (audit-logs-for-company company 'audit-log)\n   (let ((elems (%bitbucket-audit-logs-for-company company)))\n     (uniq (sort (copy-list elems) #'> :key 'bknr.datastore:store-object-id)))))\n\n(defun parse-error-response (response result-code audit-log)\n  (let* ((response-obj (json:decode-json-from-string response))\n         (errors (a:assoc-value response-obj :errors)))\n    (let ((message (or\n                    (a:assoc-value (car errors) :message)\n\n                    ;; See test-parses-error-correctly\n                    (when (equal \"error\" (a:assoc-value response-obj :type))\n                      (a:assoc-value (a:assoc-value response-obj :error)\n                                     :message))\n\n                    ;; If we can't parse an actual response, just use the\n                    ;; whole json. Suitable for OAuth.\n                    response)))\n      (warn \"Bitbucket api failed with: ~a\" message)\n      (with-transaction ()\n        (setf (http-result-code audit-log) result-code)\n        (setf (audit-log-error audit-log) message)\n        (setf (audit-log-error-response audit-log) response))))\n  (error 'bitbucket-error :audit-log audit-log))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/core\n  (:nicknames :screenshotbot/pro/bitbucket/core)\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:bitbucket-error\n   #:http-success-response?))\n(in-package :screenshotbot/bitbucket/core)\n\n(define-condition bitbucket-error (error)\n  ((%audit-log :initarg :audit-log)))\n\n(defun http-success-response? (response-code)\n  (<= 200 response-code 204))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/error-response-1.json",
    "content": "{\"type\": \"error\", \"error\": {\"message\": \"key: Ensure this value has at most 40 characters (it has 44).\", \"fields\": {\"key\": [\"Ensure this value has at most 40 characters (it has 44).\"]}}}\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/plugin\n  (:nicknames :screenshotbot/pro/bitbucket/plugin)\n  (:use #:cl)\n  (:import-from #:screenshotbot/plugin\n                #:plugin-parse-repo\n                #:plugin)\n  (:import-from #:screenshotbot/installation\n                #:find-plugin\n                #:installation)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url\n                #:commit-link)\n  (:import-from #:screenshotbot/github/access-checks\n                #:get-repo-id)\n  (:import-from #:screenshotbot/git-repo\n                #:repo-link\n                #:generic-git-repo)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:describe-pull-request)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-work-branch)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:bitbucket-plugin\n   #:bitbucket-plugin-secret\n   #:bitbucket-plugin-key\n   #:bitbucket-repo\n   #:bitbucket-repo-link))\n(in-package :screenshotbot/bitbucket/plugin)\n\n(with-class-validation\n (defclass bitbucket-plugin (plugin)\n   ((key :initarg :key\n         :reader bitbucket-plugin-key)\n    (secret :initarg :secret\n            :reader bitbucket-plugin-secret))))\n\n(defun bitbucket-plugin (&key (installation (installation)))\n  (find-plugin installation 'bitbucket-plugin))\n\n(defclass bitbucket-repo (generic-git-repo)\n  ())\n\n(defmethod plugin-parse-repo ((plugin bitbucket-plugin)\n                              company\n                              repo-str)\n  (when (str:containsp \"bitbucket.org\" repo-str)\n    (make-instance 'bitbucket-repo :link repo-str\n                                   :company company)))\n\n(defun get-bitbucket-repo-id (repo)\n  (cl-ppcre:regex-replace-all\n   \"^(git@bitbucket.org:|https://bitbucket.org/)([^.]*)([.]git)?$\"\n   repo\n   \"\\\\2\"))\n\n(defmethod commit-link ((repo bitbucket-repo) hash)\n  (format nil \"https://bitbucket.org/~a/commits/~a\"\n          (get-bitbucket-repo-id (repo-link repo))\n          hash))\n\n(defmethod describe-pull-request ((repo bitbucket-repo) run)\n  (let ((url (pull-request-url run)))\n    (multiple-value-bind (all parts)\n        (cl-ppcre:scan-to-strings \".*/pull-requests/((\\\\d)*)$\" url)\n      (cond\n        (all\n         (format nil \"Pull ~a\" (elt parts 0)))\n        (t\n         (call-next-method))))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/promoter\n  (:nicknames :screenshotbot/pro/bitbucket/promoter)\n  (:use #:cl\n        #:screenshotbot/abstract-pr-promoter)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-send-tasks\n                #:plugin-promoter)\n  (:import-from #:screenshotbot/pro/bitbucket/plugin\n                #:bitbucket-repo\n                #:bitbucket-plugin)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:check-key\n                #:promoter-pull-id\n                #:make-promoter-for-acceptable\n                #:abstract-pr-acceptable\n                #:push-remote-check\n                #:format-updated-summary\n                #:check-title\n                #:check-summary\n                #:make-acceptable\n                #:details-url\n                #:send-task-args\n                #:check-status\n                #:valid-repo?\n                #:plugin-installed?)\n  (:import-from #:screenshotbot/pro/bitbucket/settings\n                #:get-access-token-from-refresh-token\n                #:refresh-token\n                #:bitbucket-settings-for-company)\n  (:import-from #:screenshotbot/model/channel\n                #:github-get-canonical-repo)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-repo-url\n                #:override-commit-hash\n                #:recorder-run-company)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url\n                #:channel-repo\n                #:current-user\n                #:recorder-run-channel\n                #:channel-name\n                #:recorder-run-commit)\n  (:import-from #:screenshotbot/model/report\n                #:base-acceptable\n                #:acceptable-state)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/report-api\n                #:report-run)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-page)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/pro/bitbucket/audit-log\n                #:with-audit-log\n                #:parse-error-response\n                #:http-result-code\n                #:audit-log-error-response\n                #:audit-log-error\n                #:build-status-audit-log)\n  (:import-from #:util/misc\n                #:not-empty!\n                #:not-null!)\n  (:import-from #:screenshotbot/pro/bitbucket/core\n                #:http-success-response?\n                #:bitbucket-error)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:abstract-pr-promoter)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/bitbucket/promoter)\n\n(with-class-validation\n (defclass bitbucket-acceptable (abstract-pr-acceptable)\n   ((send-task-args :initarg :report\n                    :accessor send-task-args)\n    (%company :initarg :company\n              :reader company))\n   (:metaclass persistent-class)))\n\n\n(defclass bitbucket-promoter (abstract-pr-promoter)\n  ((plugin :initarg :plugin\n           :reader plugin)))\n\n(defmethod make-promoter-for-acceptable ((self bitbucket-acceptable))\n  (make-instance 'bitbucket-promoter))\n\n(defmethod plugin-installed? ((promoter bitbucket-promoter)\n                              company\n                              repo-url)\n  (bitbucket-settings-for-company company))\n\n(defmethod make-acceptable ((promoter bitbucket-promoter) report\n                            &rest args)\n  (apply #'make-instance\n         'bitbucket-acceptable\n         :company (recorder-run-company (report-run report))\n         :report report\n         args))\n\n(defmethod valid-repo? ((promoter bitbucket-promoter)\n                        repo)\n  (typep repo 'bitbucket-repo))\n\n(defun build-status-url (full-name commit)\n  \"See https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commit-statuses/\"\n  (format nil \"https://api.bitbucket.org/2.0/repositories/~a/commit/~a/statuses/build/\"\n          full-name\n          commit))\n\n(auto-restart:with-auto-restart (:retries 3)\n  (defun actually-push-remote-check (&key token url args audit-log)\n    (multiple-value-bind (stream result-code)\n        (util/request:http-request\n         url\n         :method :post\n         :content-type \"application/json\"\n         :want-stream t\n         :additional-headers\n         `((\"Authorization\" . ,(Format nil \"Bearer ~a\" token)))\n         :force-binary nil\n         :content (json:encode-json-to-string args))\n      (let ((ret (uiop:slurp-input-stream 'string stream)))\n        (cond\n          ((http-success-response? result-code)\n           (push-event :bitbucket.update-success)\n           (log:info \"Got bitbucket result: ~a\" ret))\n          (t ;; error\n           (push-event :bitbucket.update-failure)\n           ;; We do both a warning and an error to both get notified,\n           ;; and also trigger the auto-retry\n           (log:info \"Got BitBucket response code: ~a\" result-code)\n\n           ;; Note that this will both warn and raise an error, the\n           ;; error will trigger the auto retry.\n           (parse-error-response ret result-code audit-log)))))))\n\n(defmethod push-remote-check ((promoter bitbucket-promoter)\n                              run\n                              check)\n  \"Send the build status. Log any error message, but don't propagate the errors\"\n  (handler-case\n      (let* ((company (recorder-run-company run))\n             (bitbucket-token (not-null! (car (bitbucket-settings-for-company company))))\n             (token (get-access-token-from-refresh-token\n                     company\n                     (refresh-token bitbucket-token)))\n             (args (make-build-status-args run check)))\n        (assert token)\n        (let* ((commit (not-empty! (a:assoc-value args :commit)))\n               (full-name (not-empty! (a:assoc-value args :full-name))))\n          (with-audit-log (audit-log (make-instance 'build-status-audit-log\n                                                    :company company\n                                                    :commit commit\n                                                    :full-name full-name))\n           (let* ((url (build-status-url\n                        full-name\n                        commit)))\n             (actually-push-remote-check\n              :token token\n              :url url\n              :args args\n              :audit-log audit-log)))))\n    (bitbucket-error (e)\n      (values))))\n\n(auto-restart:with-auto-restart ()\n  (defmethod maybe-send-tasks ((promoter bitbucket-promoter) run)\n    (values)))\n\n\n(defmethod plugin-promoter ((plugin bitbucket-plugin))\n  (make-instance 'bitbucket-promoter\n                  :plugin plugin))\n\n(defun nullify (str)\n  (if (str:emptyp str) nil str))\n\n(defun make-key (channel-name)\n  (let ((old-key (format nil \"screenshotbot--~a\" channel-name)))\n    (cond\n      ((<= (length old-key) 40)\n       old-key)\n      (t\n       (ironclad:byte-array-to-hex-string (md5:md5sum-string old-key))))))\n\n(defmethod promoter-pull-id ((promoter bitbucket-promoter) run)\n  (pull-request-url run))\n\n\n(defun make-build-status-args (run\n                               check)\n  (let* ((repo-url\n           (recorder-run-repo-url run)))\n    (flet ((make-details-url (&rest args)\n             (format nil\n                    \"~a~a\"\n                    (installation-domain (installation))\n                    (apply #'hex:make-url args))))\n     `((:key . ,(make-key (check-key check)))\n       ;; TODO: refactor repo-full-name to not use GitHub specific code.\n       (:full-name . ,(screenshotbot/github/pull-request-promoter::repo-full-name repo-url))\n       (:commit . ,(check-sha check))\n       (:state . ,(ecase (check-status check)\n                    (:success \"SUCCESSFUL\")\n                    (:failure \"FAILED\")\n                    (:accepted \"SUCCESSFUL\")\n                    (:rejected \"FAILED\")\n                    (:pending \"INPROGRESS\")\n                    (:action-required \"FAILED\")))\n       (:name . ,(format nil \"Screenshots for ~a\" (check-key check)))\n       (:url . ,(or (details-url check)\n                    (make-details-url 'run-page :id (oid run))))\n       (:description . ,(check-title check))))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/review-link\n  (:use #:cl)\n  (:import-from #:screenshotbot/pro/bitbucket/plugin\n                #:bitbucket-repo)\n  (:import-from #:screenshotbot/model/channel\n                #:github-get-canonical-repo)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:get-canonical-pull-request-url)\n  (:import-from #:screenshotbot/git-repo\n                #:repo-link))\n(in-package :screenshotbot/bitbucket/review-link)\n\n(defmethod get-canonical-pull-request-url ((repo bitbucket-repo) pull-request-id)\n  (format nil \"~a/pull-requests/~d\"\n          (github-get-canonical-repo\n           (repo-link repo))\n          pull-request-id))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/settings\n  (:nicknames :screenshotbot/pro/bitbucket/settings)\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:settings-template\n                #:defsettings)\n  (:import-from #:screenshotbot/pro/bitbucket/plugin\n                #:bitbucket-plugin-secret\n                #:bitbucket-plugin-key\n                #:bitbucket-plugin)\n  (:import-from #:nibble\n                #:nibble-id\n                #:nibble)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at\n                #:current-company)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:screenshotbot/pro/bitbucket/audit-log\n                #:with-audit-log\n                #:audit-log\n                #:access-token-audit-log-grant-type\n                #:access-token-audit-log\n                #:parse-error-response\n                #:audit-log-error\n                #:bitbucket-audit-logs-for-company\n                #:build-status-audit-log-commit\n                #:build-status-audit-log-full-name\n                #:build-status-audit-log)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:core/ui/taskie\n                #:timeago)\n  (:import-from #:screenshotbot/pro/bitbucket/core\n                #:http-success-response?)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:screenshotbot/model/company\n                #:company-with-name)\n  (:import-from #:alexandria\n                #:when-let*)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:screenshotbot/login/common\n                #:allow-oauth-redirect-p)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:refresh-token))\n(in-package :screenshotbot/bitbucket/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(with-class-validation\n (defclass bitbucket-token (store-object)\n   ((refresh-token :initarg :refresh-token\n                   :reader refresh-token)\n    (company :initarg :company\n             :reader bitbucket-setting-company\n             :index-type hash-index\n             :index-reader %bitbucket-settings-for-company)\n    (created-at :initarg :created-at))\n   (:metaclass persistent-class)\n   (:default-initargs :created-at (get-universal-time))))\n\n\n;; OLD: DO NOT USE.\n(defclass bitbucket-setting (store-object)\n  ((refresh-token :initarg :refresh-token\n                  :reader refresh-token)\n   (company :initarg :company)\n   (created-at :initarg :created-at))\n  (:metaclass persistent-class))\n\n(defun bitbucket-settings-for-company (company)\n  (sort (copy-list (%bitbucket-settings-for-company company)) #'>\n        :key #'bknr.datastore:store-object-id))\n\n(auto-restart:with-auto-restart (:retries 3)\n  (defun access-token-for-args (args &key company)\n    (with-audit-log (audit-log\n                     (make-instance 'access-token-audit-log\n                                    :company company\n                                    :grant-type (a:assoc-value args \"grant_type\" :test #'string-equal)))\n      (let ((plugin (bitbucket-plugin)))\n        (multiple-value-bind (stream result-code)\n            (util/request:http-request\n             (format nil \"https://bitbucket.org/site/oauth2/access_token\")\n             :parameters args\n             :basic-authorization (list (bitbucket-plugin-key plugin)\n                                        (bitbucket-plugin-secret plugin))\n             :method :post\n             :want-stream t)\n          (let ((body (uiop:slurp-input-stream 'string stream)))\n            (cond\n              ((http-success-response? result-code)\n               (json:decode-json-from-string\n                body))\n              (t\n               (parse-error-response\n                body result-code audit-log)))))))))\n\n(defun ! (x)\n  (assert x)\n  x)\n\n(defun update-from-refresh-token (company response)\n  #+nil\n  (log:info \"Updating refresh token from: ~S\" response)\n  (let ((refresh-token (a:assoc-value response :refresh--token))\n        (last-settings (car (bitbucket-settings-for-company company))))\n    (assert (not (str:emptyp refresh-token)))\n    (when (or (not last-settings)\n              (not (equal refresh-token (refresh-token last-settings))))\n     (make-instance 'bitbucket-token\n                     :refresh-token (! refresh-token)\n                     :company company))))\n\n(auto-restart:with-auto-restart ()\n  (defun swap-for-token (code)\n    (let ((ret\n            (access-token-for-args `((\"grant_type\" . \"authorization_code\")\n                                     (\"code\" . ,code))\n                                   :company (current-company))))\n      (clear-tokens)\n      (update-from-refresh-token (current-company) ret)\n\n      (hex:safe-redirect \"/settings/bitbucket\"))))\n\n(defun clear-tokens ()\n  (loop for token in (bitbucket-settings-for-company (current-company))\n        do (bknr.datastore:delete-object token)))\n\n(defun get-access-token-from-refresh-token (company refresh-token)\n  (let* ((response (access-token-for-args `((\"grant_type\" . \"refresh_token\")\n                                            (\"refresh_token\" . ,refresh-token))\n                                          :company company)))\n    (update-from-refresh-token company response)\n    (let ((token\n            (a:assoc-value\n             response\n             :access--token)))\n     (cond\n       (token token)\n       (t (error \"could not get token: ~a\" response))))))\n\n(defhandler (bitbucket-oauth-callback :uri \"/bitbucket/oauth-callback\") (code state)\n  \"BitBucket Oauth consumers only allow one single redirect URL, so we don't want to use /account/oauth-callback here. Instead we want to create a custom callback that handles multiple redirect URLs.\"\n  (cond\n    ((str:containsp \",\" state)\n     (destructuring-bind (state domain) (str:split \",\" state)\n       (unless (allow-oauth-redirect-p *installation* domain)\n         (error \"Cannot allow redirect for ~a\" domain))\n       (hunchentoot:redirect\n        (quri:render-uri\n         (quri:make-uri\n          :path \"/bitbucket/oauth-callback\"\n          :query `((\"code\" . ,code)\n                   (\"state\" . ,state))\n          :defaults domain)))))\n    (t\n     (nibble:render-nibble hunchentoot:*acceptor* (encrypt:decrypt-number state)))))\n\n(defun redirect-to-oauth ()\n\n  (let ((callback (nibble (code)\n                    (swap-for-token code))))\n   (hunchentoot:redirect\n    (format nil \"https://bitbucket.org/site/oauth2/authorize?client_id=~a&response_type=code&state=~a\"\n            (bitbucket-plugin-key (bitbucket-plugin))\n            (format nil \"~a,~a\"\n                    (encrypt:encrypt-number (nibble-id callback))\n                    (installation-domain *installation*))))))\n\n(defun disconnect ()\n  (let ((actually-disconnect\n          (nibble ()\n            (clear-tokens)\n            (hex:safe-redirect \"/settings/bitbucket\"))))\n    (confirmation-page\n     :yes actually-disconnect\n     :no \"/settings/bitbucket\"\n     :danger t\n     <p>Are you sure you want to disconnect BitBucket? Reconnecting it will require access to your organization BitBucket account.</p>)))\n\n(defun settings-bitbucket-page ()\n  (let ()\n    <settings-template>\n      <div class= \"card mt-3\">\n        <div class= \"card-header\">\n          <h3>BitBucket Integration</h3>\n        </div>\n\n        <div class= \"card-body\">\n          <p>Screenshotbot can update build statuses on your BitBucket Pull Requests</p>\n        </div>\n\n        <div class= \"card-footer\">\n          ,(cond\n             ((bitbucket-settings-for-company (current-company))\n              <form method= \"post\" action= (nibble () (disconnect))>\n                <input type= \"submit\" class= \"btn btn-danger\" value= \"Disconnect\" />\n              </form>)\n             (t <a href= (nibble () (redirect-to-oauth)) class= \"btn btn-success\">Install on BitBucket</a>))\n        </div>\n      </div>\n\n      <render-audit-logs />\n    </settings-template>))\n\n(deftag render-audit-logs ()\n  <div class= \"card mt-3 audit-log-card\">\n    <div class= \"card-header\">\n      <h5>API Audit Logs</h5>\n    </div>\n\n  <div class= \"card-body\">\n    <p class= \"text-muted\">All API calls to BitBucket made by Screenshotbot in the last 30 days will be listed here.</p>\n\n    <ul>\n      ,(paginated\n        (lambda (x)\n          <li>,(render-audit-log-item x)</li>)\n        :items (bitbucket-audit-logs-for-company (current-company)))\n    </ul>\n  </div>\n  </div>)\n\n(defmethod %timeago ((self audit-log))\n  (timeago :timestamp (%created-at self)))\n\n(defmethod render-audit-log-item ((self build-status-audit-log))\n  (let ((commit (build-status-audit-log-commit self))\n        (err (audit-log-error self)))\n    <span class= (if err \"text-danger\" nil) >,(if err \"Failed to push\" \"Pushed\") build status for commit <code title= commit >,(str:shorten 8 commit)</code>\n      on repository <code>,(build-status-audit-log-full-name self)</code> at ,(%timeago self) ,(if err (format nil \": ~a\" err)) </span>))\n\n(defmethod render-audit-log-item ((self t))\n  <span> [Missing renderer] Audit log item of type ,(type-of self) </span>)\n\n(defmethod render-audit-log-item ((self access-token-audit-log))\n  (let ((err (audit-log-error self)))\n      <span class= (if err \"text-danger\" \"\") >\n    Requested access token for grant type\n    <code>,(access-token-audit-log-grant-type self)</code>,\n    ,(%timeago self)\n\n    ,(when err\n       <span>: ,(progn err)</span>)\n    </span>))\n\n(defsettings bitbucket\n  :name \"bitbucket\"\n  :title \"BitBucket\"\n  :section :vcs\n  :plugin 'bitbucket-plugin\n  :handler 'settings-bitbucket-page)\n\n#-screenshotbot-oss\n(def-health-check production-bitbucket-api-key ()\n  #+bknr.cluster\n  (when (bknr.cluster:leaderp bknr.datastore:*store*)\n    (when-let* ((company (company-with-name \"Modern Interpreters\"))\n                (token (car (bitbucket-settings-for-company company))))\n      (let ((access-token (get-access-token-from-refresh-token\n                           company\n                           (refresh-token token))))\n        (assert (stringp access-token))\n        (assert (> (length access-token) 1))))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/test-audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/test-audit-log\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/bitbucket/audit-log\n                #:audit-log-error\n                #:parse-error-response\n                #:audit-log)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/pro/bitbucket/core\n                #:bitbucket-error)\n  (:import-from #:screenshotbot/pro/bitbucket/audit-log\n                #:with-audit-log)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:fiveam-matchers/core\n                #:equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/misc\n                #:is-null)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string\n                #:starts-with)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/bitbucket/test-audit-log)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test test-parses-error-correctly\n  (with-fixture state ()\n   (let ((response (uiop:read-file-string\n                    #.(asdf:system-relative-pathname\n                       :screenshotbot \"bitbucket/error-response-1.json\")))\n         (audit-log (make-instance 'audit-log)))\n     (signals bitbucket-error\n      (parse-error-response\n       response\n       500\n       audit-log))\n     (is (equal\n          \"key: Ensure this value has at most 40 characters (it has 44).\"\n          (audit-log-error audit-log))))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/test-plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/test-plugin\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:describe-pull-request)\n  (:import-from #:screenshotbot/pro/bitbucket/plugin\n                #:bitbucket-repo)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run))\n(in-package :screenshotbot/bitbucket/test-plugin)\n\n\n(util/fiveam:def-suite)\n\n(test describe-pull-request-for-bitbucket\n  (with-test-store ()\n    (let ((repo (make-instance 'bitbucket-repo)))\n      (is (equal \"Pull Request\"\n                 (describe-pull-request repo (make-recorder-run\n                                              :pull-request \"https://google.com\"))))\n      (is (equal \"Pull 2\"\n                 (describe-pull-request repo\n                                        (make-recorder-run\n                                         :pull-request \"https://bitbucket.org/tdrhq/fast-example/pull-requests/2\")))))))\n\n(test describe-pull-request-for-bitbucket-for-run-with-branch\n  (with-test-store ()\n    (let ((repo (make-instance 'bitbucket-repo)))\n      (is (equal \"Pull Request (foobar)\"\n                 (describe-pull-request repo (make-recorder-run\n                                              :work-branch \"foobar\"\n                                              :pull-request \"https://google.com\"))))\n      (is (equal \"Pull 2 (foobar)\"\n                 (describe-pull-request repo\n                                        (make-recorder-run\n                                         :work-branch \"foobar\"\n                                         :pull-request \"https://bitbucket.org/tdrhq/fast-example/pull-requests/2\")))))))\n\n(test describe-pull-request-for-bitbucket-for-empty-string-branch\n  (with-test-store ()\n    (let ((repo (make-instance 'bitbucket-repo)))\n      (is (equal \"Pull Request\"\n                 (describe-pull-request repo (make-recorder-run\n                                              :work-branch \"\"\n                                              :pull-request \"https://google.com\"))))\n      (is (equal \"Pull 2\"\n                 (describe-pull-request repo\n                                        (make-recorder-run\n                                         :work-branch \"\"\n                                         :pull-request \"https://bitbucket.org/tdrhq/fast-example/pull-requests/2\")))))))\n\n(test describe-pull-request-for-bitbucket-for-branch-with-/-in-name\n  (with-test-store ()\n    (let ((repo (make-instance 'bitbucket-repo)))\n      (is (equal \"Pull Request\"\n                 (describe-pull-request repo (make-recorder-run\n                                              :work-branch \"\"\n                                              :pull-request \"https://google.com\"))))\n      (is (equal \"Pull 2 (blah)\"\n                 (describe-pull-request repo\n                                        (make-recorder-run\n                                         :work-branch \"feature/blah\"\n                                         :pull-request \"https://bitbucket.org/tdrhq/fast-example/pull-requests/2\")))))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/test-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/test-promoter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/pro/bitbucket/promoter\n                #:make-build-status-args\n                #:make-key\n                #:bitbucket-promoter)\n  (:import-from #:screenshotbot/github/pull-request-promoter\n                #:check)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:screenshotbot/pro/bitbucket/settings\n                #:get-access-token-from-refresh-token\n                #:bitbucket-token)\n  (:import-from #:screenshotbot/pro/bitbucket/audit-log\n                #:audit-log-error-response\n                #:audit-log-error\n                #:bitbucket-audit-logs-for-company)\n  (:import-from #:screenshotbot/pro/bitbucket/core\n                #:bitbucket-error)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/pro/bitbucket/plugin\n                #:bitbucket-repo)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-promote)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:make-check\n                #:promoter-pull-id\n                #:push-remote-check\n                #:check)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/bitbucket/test-promoter)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (let* ((auto-restart:*global-enable-auto-retries-p* nil)\n            (channel (make-instance 'channel :name \"channel-0\"\n                                    :github-repo \"https://bitbucket.org/tdrhq/dummy\"))\n            (company (make-instance 'company))\n            (run (make-recorder-run :company company\n                                    :github-repo \"https://bitbucket.org/tdrhq/dummy\"\n                                    :commit-hash \"abcd\"\n                                    :channel channel))\n            (bitbucket-token (make-instance 'bitbucket-token\n                                            :refresh-token \"fake-refresh-token\"\n                                            :company company))\n            (check (make-check run\n                              :title \"No screenshots changed\"\n                              :status :success)))\n       (cl-mock:with-mocks ()\n         (if-called 'bitbucket-settings-for-company\n                    (lambda (c)\n                      (assert (eql c company))\n                      (list bitbucket-token)))\n         (if-called 'util/request:http-request\n                    (lambda (&rest args)\n                      (error \"Unimplemented http-request mock for args ~a\" args)))\n         (&body))))))\n\n(test make-task-args-happy-path\n  (with-fixture state ()\n    (let* ((run (make-recorder-run :channel channel\n                                   :github-repo \"https://bitbucket.org/tdrhq/dummy\"))\n           (promoter (make-instance 'bitbucket-promoter)))\n      (let ((result (make-build-status-args run check)))\n        (is (equal \"SUCCESSFUL\" (a:assoc-value result :state)))\n        (is (equal \"tdrhq/dummy\" (a:assoc-value result :full-name)))))))\n\n(test make-task-args-for-every-version-of-state\n  (with-fixture state ()\n    (dolist (state (list :accepted :rejected :success :failure :action-required :pending))\n      (let* ((run (make-recorder-run\n                   :github-repo \"https://bitbucket.org/tdrhq/dummy\"\n                   :channel channel\n                   :commit-hash \"zoidberg\"))\n             (promoter (make-instance 'bitbucket-promoter))\n             (check (make-check run\n                                :status state\n                                :title \"foobar\")))\n        (let ((result (make-build-status-args run check)))\n         (is (equal \"zoidberg\" (a:assoc-value result :commit)))\n         (is (str:s-member (list \"SUCCESSFUL\" \"FAILED\" \"INPROGRESS\")\n                           (assoc-value result :state))))))))\n\n(test send-build-status-makes-audit-log\n  (with-fixture state ()\n    (if-called 'get-access-token-from-refresh-token\n               (lambda (company token)\n                 (assert (equal \"fake-refresh-token\" token))\n                 \"fake-access-token\"))\n    (if-called 'util/request:http-request\n               (lambda (url &key &allow-other-keys)\n                 (assert (str:ends-with-p \"statuses/build/\" url))\n                 (values\n                  (make-string-input-stream\n                   (json:encode-json-to-string\n                    `((:key . \"screenshotbot--blehbleh\"))))\n                  204))\n               :at-start t)\n    (push-remote-check\n     (make-instance 'bitbucket-promoter)\n     run\n     (make-check run :title \"hello\"\n                     :status :success))\n    (is (eql 1 (length (bitbucket-audit-logs-for-company company))))\n    (let ((audit-log (car (bitbucket-audit-logs-for-company company))))\n      (is (eql nil (audit-log-error audit-log)))\n      (is (eql nil (audit-log-error-response audit-log))))))\n\n(test send-build-status-has-error\n  (with-fixture state ()\n    (if-called 'get-access-token-from-refresh-token\n                (lambda (company token)\n                  (assert (equal \"fake-refresh-token\" token))\n                  \"fake-access-token\"))\n    (if-called 'util/request:http-request\n                (lambda (url &key &allow-other-keys)\n                  (assert (str:ends-with-p \"statuses/build/\" url))\n                  (values\n                   (make-string-input-stream\n                    \"{\n  \\\"errors\\\": [\n    {\n      \\\"exceptionName\\\": \\\"<string>\\\",\n      \\\"message\\\": \\\"<string>\\\",\n      \\\"context\\\": \\\"<string>\\\"\n    }\n  ]\n}\")\n                   401))\n                :at-start t)\n    (push-remote-check\n     (make-instance 'bitbucket-promoter)\n     run (make-check run\n                     :status :success\n                     :title \"hello\"))\n    (is (eql 1 (length (bitbucket-audit-logs-for-company company))))\n    (let ((audit-log (car (bitbucket-audit-logs-for-company company))))\n     (is (not (str:emptyp (audit-log-error audit-log)))))))\n\n(test make-key\n  (is (equal \"screenshotbot--xx-yyy-android\"\n             (make-key \"xx-yyy-android\")))\n  (let ((name \"12345678-12345678-123-android\"))\n    (is (equal (ironclad:byte-array-to-hex-string\n                (md5:md5sum-string (format nil \"screenshotbot--~a\" name)))\n               (make-key name)))\n    (is (<= (length (make-key name)) 40))))\n\n(test maybe-promote-happy-path\n  (with-fixture state ()\n    (let ((run (make-recorder-run :company company\n                                  :channel channel\n                                  :pull-request \"https://bitbucket.com/tdrhq/fast-example/pull-request/20\"))\n          (promoter (make-instance 'bitbucket-promoter)))\n      (finishes\n        (maybe-promote promoter run)))))\n\n\n(test bitbucket-promoter-pull-id\n  (with-fixture state ()\n   (let ((run (make-recorder-run :pull-request \"https://bitbucket.com/tdrhq/fast-example/pull-request/20\")))\n     (is (equal \"https://bitbucket.com/tdrhq/fast-example/pull-request/20\"\n                (promoter-pull-id (make-instance 'bitbucket-promoter) run))))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/test-review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/test-review-link\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/pro/bitbucket/plugin\n                #:bitbucket-repo)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:get-canonical-pull-request-url))\n(in-package :screenshotbot/bitbucket/test-review-link)\n\n(util/fiveam:def-suite)\n\n(test bitbucket-review-link\n  (let ((repo (make-instance 'bitbucket-repo\n                             :link \"git@bitbucket.org:tdrhq/fast-example.git\")))\n    (is (equal \"https://bitbucket.org/tdrhq/fast-example/pull-requests/2\"\n               (get-canonical-pull-request-url\n                repo 2)))))\n"
  },
  {
    "path": "src/screenshotbot/bitbucket/test-settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/bitbucket/test-settings\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/pro/bitbucket/settings\n                #:update-from-refresh-token\n                #:refresh-token\n                #:bitbucket-settings-for-company)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/bitbucket/test-settings)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((auto-restart:*global-enable-auto-retries-p* nil))\n   (with-test-store ()\n     (let ((company (make-instance 'company)))\n       (&body)))))\n\n(test preconditions\n  (with-fixture state ()\n    (update-from-refresh-token company\n                               '((:refresh--token . \"foo\")))\n    (is (equal 1 (length (bitbucket-settings-for-company company))))\n    (is (equal\n         \"foo\"\n         (refresh-token (car (bitbucket-settings-for-company company)))))))\n\n(test updating-refresh-token\n  (with-fixture state ()\n    (update-from-refresh-token company '((:refresh--token . \"foo\")))\n    (update-from-refresh-token company '((:refresh--token . \"bar\")))\n    (is (equal 2 (length (bitbucket-settings-for-company company))))\n    (is (equal\n         \"bar\"\n         (refresh-token (car (bitbucket-settings-for-company company)))))))\n\n(test when-refresh-token-doesnt-change\n  (with-fixture state ()\n    (update-from-refresh-token company '((:refresh--token . \"foo\")))\n    (update-from-refresh-token company '((:refresh--token . \"foo\")))\n    (is (equal 1 (length (bitbucket-settings-for-company company))))\n    (is (equal\n         \"foo\"\n         (refresh-token (car (bitbucket-settings-for-company company)))))))\n"
  },
  {
    "path": "src/screenshotbot/cdn.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :screenshotbot/cdn\n    (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:document-root)\n  (:import-from #:core/ui/image\n                #:img-with-fallback\n                #:image-dimensions)\n  (:export #:script #:link #:img\n           #:make-image-cdn-url\n           #:img-with-fallback))\n(in-package :screenshotbot/cdn)\n\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag script (children &key src type async)\n  <:script src= (when src (util.cdn:make-cdn src)) type=type async=async >,@ (progn children)</:script>)\n\n(markup:deftag link (&key rel as href type crossorigin media id)\n  <:link rel=rel as=as type=type crossorigin=crossorigin media=media\n  id=id\n  href= (util.cdn:make-cdn href) />)\n\n(defun image-url-p (src)\n  (str:starts-with-p \"/image/blob/\" src))\n\n(defun cdn-for-image-url (src)\n  (if (image-url-p src)\n      (let ((util.cdn:*cdn-cache-key* \"i6\" ))\n        (util.cdn:make-cdn src))\n      (util.cdn:make-cdn src)))\n\n(markup:deftag img (&key src (alt \"Image\") srcset class style height width id loading)\n  (let ((dims (image-dimensions src)))\n    <:img src= (cdn-for-image-url src)  alt=alt srcset=srcset class=class style=style\n          data-hj-suppress=(when (image-url-p src) \"true\")\n          width= (or width (first dims))\n          height= (or height (second dims))\n          id=id\n          loading=loading\n        />))\n\n(defun make-image-cdn-url (url)\n  \"This is a specific CDN to use for actual screenshot images. For now\n  we're using our default CDN, but we might change this in the\n  future. For instance, separate installations might have a different\n  CDN.\"\n  ;; si: screenshot-image\n  (let ((util.cdn:*cdn-cache-key* \"si-3\" ))\n    (util.cdn:make-cdn url)))\n"
  },
  {
    "path": "src/screenshotbot/cleanup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/cleanup\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/image\n                #:mask-rect\n                #:image-blob\n                #:image)\n  (:import-from #:screenshotbot/model/image-comparison\n                #:image-comparison-after\n                #:image-comparison-before\n                #:image-comparison)\n  (:import-from #:util/store\n                #:find-any-refs)\n  (:import-from #:screenshotbot/diff-report\n                #:hash-set-difference)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p)\n  (:import-from #:util/object-id\n                #:creation-time-from-oid)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/cleanup)\n\n(defun bad? (x)\n  (or (null x)\n      (object-destroyed-p x)))\n\n#|\n\nIt's a little manual, but the current process of clearning up images\ninvolves multiple passes. (In the future, once we're confident that\nthey work correctly, we'll automate it all.)\n\nFirst, we DELETE-UNREFERNCED-IMAGES. Verify that the output looks\nreasonable, and call the DELETE-ALL-THESE-OBJECTS restart. Next we\ncall DELETE-ORPHANED-IMAGE-COMPARISONS. This function is relatively\nsafe since it deletes objects that are essentially caches. Finally\ncall DELETE-UNREFERENCED-IMAGES again.\n\nBut we're not done, we still need to delete unused images. Consider\ndelaying this step if the disk usage isn't critical, since image blobs\ndon't have transaction logs.\n\n|#\n\n(defun delete-orphaned-image-comparisons ()\n  (loop for ic in (bknr.datastore:store-objects-with-class 'image-comparison)\n        if (or\n            (bad? (image-comparison-before ic))\n            (bad? (image-comparison-after ic)))\n          do\n             (bknr.datastore:delete-object ic)))\n\n;; (delete-orphaned-image-comparisons)\n\n(defun oldp (obj)\n  (< (creation-time-from-oid obj)\n     (- (get-universal-time) (* 7 24 3600))))\n\n(defun delete-unreferenced-images ()\n  (let ((types '(screenshot\n                 image\n                 image-blob\n                 mask-rect)))\n    (let ((objects\n            (loop for type in types\n                  appending (bknr.datastore:store-objects-with-class type))))\n      (let* ((refs (find-any-refs objects))\n             (deletable (hash-set-difference objects refs\n                                             :test #'eql))\n             (deletable\n               (remove-if-not #'oldp deletable )))\n        (restart-case\n            (when deletable\n              (error \"~d out of ~d elements can be deleted: ~S\"\n                     (length deletable)\n                     (length objects)\n                     deletable))\n          (delete-all-these-objects ()\n            (bknr.datastore:snapshot) ;; just to be sure\n            (loop for x in deletable\n                  do (bknr.datastore:delete-object x))))))))\n\n;; (delete-unreferenced-images)\n;; (bknr.datastore::delete-orphaned-blob-files nil)\n"
  },
  {
    "path": "src/screenshotbot/client-hub/deliver-client-hub.lisp",
    "content": "(defpackage :screenshotbot/client-hub/deliver-client-hub\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/client-hub/deliver-client-hub)\n\n(ql:quickload :screenshotbot.client-hub)\n\n(defun output-file ()\n  (car (asdf:output-files 'asdf:compile-op\n                          (asdf:find-component\n                           :screenshotbot.client-hub/deliver \"deliver-client-hub\"))))\n\n(defun deliver-main ()\n  (let ((output-file (output-file)))\n    #-darwin\n    (uiop:delete-file-if-exists output-file)\n    (lw:deliver 'screenshotbot/client-hub/main:main\n             output-file\n             5\n             :keep-function-name t\n             #+mswindows :console #+mswindows :init\n             #+mswindows :startup-bitmap-file #+mswindows nil\n             :keep-debug-mode t\n             :keep-pretty-printer t\n             :keep-clos-object-printing t\n             :keep-lisp-reader t\n             ;; temporary: get the build green\n             :keep-eval t\n             :keep-symbols `(system:pipe-exit-status)\n             :packages-to-keep-symbol-names :all\n             :multiprocessing t)))\n\n#-darwin\n(deliver-main)\n\n#+darwin\n(cond\n  ((hcl:building-universal-intermediate-p)\n   (deliver-main))\n  (t\n   (uiop:delete-file-if-exists (output-file))\n   (hcl:save-universal-from-script \"src/screenshotbot/client-hub/deliver-client-hub.lisp\")))\n\n(uiop:quit)\n"
  },
  {
    "path": "src/screenshotbot/client-hub/flags.lisp",
    "content": "(uiop:define-package :screenshotbot/client-hub/flags\n  (:use #:cl\n        #:com.google.flag)\n  (:use-reexport\n   #:screenshotbot/sdk/common-flags)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:*port*))\n(in-package :screenshotbot/client-hub/flags)\n\n(define-flag *port*\n  :selector \"port\"\n  :default-value 4444\n  :type integer)\n"
  },
  {
    "path": "src/screenshotbot/client-hub/main.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/client-hub/main\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:main))\n(in-package :screenshotbot/client-hub/main)\n\n(defun main ()\n  (format t \"hello world~%\"))\n"
  },
  {
    "path": "src/screenshotbot/client-hub/screenshotbot.client-hub.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :screenshotbot.client-hub\n  :depends-on (#:screenshotbot.sdk/common-flags\n               #:screenshotbot/hub\n               #:server/interrupts\n               #:screenshotbot/replay)\n  :components ((:file \"flags\")\n               (:file \"selenium\")\n               (:file \"main\")))\n\n(defsystem :screenshotbot.client-hub/deliver\n  :defsystem-depends-on (:build-utils/deliver-script)\n  :depends-on ()\n  :components ((\"build-utils/deliver-script:deliver-script\"\n                \"deliver-client-hub\")))\n"
  },
  {
    "path": "src/screenshotbot/client-hub/selenium.lisp",
    "content": "(defpackage :screenshotbot/client-hub/selenium\n  (:use #:cl)\n  (:import-from #:screenshotbot/hub/server\n                #:direct-selenium-url\n                #:relay-session-request\n                #:request-session-and-respond\n                #:*hub*)\n  (:import-from #:screenshotbot/replay/proxy\n                #:replay-proxy)\n  (:import-from #:server/interrupts\n                #:unwind-on-interrupt)\n  (:import-from #:scale/vagrant\n                #:vagrant)\n  (:import-from #:scale/core\n                #:with-cached-ssh-connections\n                #:http-request-via\n                #:ssh-run\n                #:delete-instance\n                #:create-instance)\n  (:import-from #:scale/image\n                #:create-image-instance)\n  (:import-from #:screenshotbot/replay/services\n                #:firefox)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags #:screenshotbot/client-hub/flags))\n  (:export\n   #:start-hub))\n(in-package :screenshotbot/client-hub/selenium)\n\n(defvar *lock* (bt:make-lock))\n\n(defclass session (store-object)\n  ((id :initarg :id\n       :reader session-id\n       :index-type unique-index\n       :index-initargs (:test #'equal)\n       :index-reader session-by-id)\n   (instance :initarg :instance\n             :relaxed-object-reference t\n             :accessor session-instance)\n   (last-used :initform (get-universal-time)\n              :accessor last-used))\n  (:metaclass persistent-class))\n\n(defclass client-hub ()\n  ((sessions :initform nil\n             :accessor sessions)))\n\n(defun find-session (hub session-id)\n  (let ((sessions (bt:with-lock-held (*lock*) (sessions hub))))\n    (loop for session in sessions\n          if (string-equal (session-id session) session-id)\n            return session)))\n\n(defmethod touch-session (session)\n  (with-transaction ()\n   (setf (last-used session) (get-universal-time))))\n\n(defclass vagrant-based-hub (client-hub)\n  ())\n\n\n(defvar *vagrant-service* scale/vagrant:*vagrant*)\n\n(defvar *vagrant-hub* (make-instance 'vagrant-based-hub))\n\n(defmethod request-session-and-respond ((hub client-hub)\n                                        (arguments string))\n  (error \"unimpl\"))\n\n(defmethod request-session-and-respond ((hub vagrant-based-hub)\n                                        (arguments string))\n  (with-cached-ssh-connections ()\n   (let ((instance (create-image-instance\n                    '(firefox :version \"102.0\")\n                    *vagrant-service*\n                    :size :small)))\n     (restart-case\n         (process-vagrant-instance hub instance arguments)\n       (cleanup-image-and-error ()\n         (delete-instance instance)\n         (error \"No instance to work with since we cleaned up the image\"))))))\n\n(defmethod direct-selenium-url ((hub client-hub)\n                                session-id)\n  \"http://localhost:4444\")\n\n(defmethod relay-session-request ((hub client-hub)\n                                  &key method content content-type\n                                    script-name)\n  (let* ((session-id (elt (str:split \"/\" script-name) 4))\n         (rest-script (a:lastcar (str:split \"/\" script-name :limit 4)))\n         (url (format nil \"~a/~a\"\n                      (direct-selenium-url hub session-id)\n                      rest-script)))\n    (log:info \"Delegating request for session: ~a to ~a\" session-id url)\n    (let* ((session (find-session hub session-id))\n           (instance (session-instance session)))\n      (touch-session session)\n      (multiple-value-bind (data ret headers)\n          (http-request-via instance\n                            url\n                            :method method\n                            :want-string t\n                            :content content\n                            :content-type content-type)\n        ;;(assert (not (eql ret 500)))\n        (setf (hunchentoot:return-code*) ret)\n        (setf (hunchentoot:content-type*) (a:assoc-value headers :content-type))\n        (log:info \"Relaying back response: ~a\" (str:shorten 80 data))\n\n        (maybe-delete-session hub session rest-script method)\n\n        data))))\n\n\n(defun maybe-delete-session (hub session rest-script method)\n  (log:trace \"Got query: ~a ~a\" method rest-script)\n  (when (and (eql method :delete)\n             (eql 2 (length (str:split \"/\" rest-script))))\n    (bt:with-lock-held (*lock*)\n      (a:removef (sessions hub) session))\n    (log:info \"Deleting instance\")\n    (delete-instance (session-instance session))))\n\n(auto-restart:with-auto-restart ()\n  (defun wait-for-driver-ready (instance url)\n    (loop while t do\n      (let ((response (ignore-errors\n                       (http-request-via instance\n                                         (format nil \"~a/status\" url)\n                                         :want-string t))))\n        (let ((body (ignore-errors\n                     (json:decode-json-from-string response))))\n          (cond\n            ((a:assoc-value (a:assoc-value body :value) :ready)\n             (return t))\n            (t\n             (log:info \"driver not ready yet\")\n             (sleep 0.1))))))))\n\n(auto-restart:with-auto-restart ()\n  (defun process-vagrant-instance (hub instance arguments)\n    (ssh-run instance \"xvfb-run ./geckodriver -vv --binary firefox/firefox < /dev/null > geckodriver_output 2>&1 &\")\n    (wait-for-driver-ready instance \"http://localhost:4444\")\n    (multiple-value-bind (resp ret headers)\n        (http-request-via instance\n                          \"http://localhost:4444/session\"\n                          :method :post\n                          :content arguments\n                          :content-type \"application/json\"\n                          :want-string t)\n      (assert (not (eql ret 500)))\n      (log:info \"Got response: ~a\" resp)\n      (setf (hunchentoot:return-code*) ret)\n      (setf (hunchentoot:content-type*) (a:assoc-value headers :content-type))\n\n      (let* ((resp (json:decode-json-from-string resp))\n             (session-id (a:assoc-value (a:assoc-value resp :value) :session-id)))\n        (log:info \"Got session-id ~a\" session-id)\n        (assert session-id)\n        (bt:with-lock-held (*lock*)\n          (push\n           (make-instance 'session\n                          :id session-id\n                          :instance instance)\n           (sessions hub))))\n      resp)))\n\n\n(defun start-hub ()\n  (log:info \"Starting selenium hub on ~a\" flags:*port*)\n  (let ((proxy\n          (make-instance 'replay-proxy\n                          :hub *vagrant-hub*\n                          :cache-dir (ensure-directories-exist #P\"~/screenshotbot/proxy-cache/\")\n                          :port flags:*port*)))\n    (unwind-on-interrupt ()\n        (hunchentoot:start proxy)\n        (progn\n          (log:info \"terminating\")\n          (hunchentoot:stop proxy)))))\n\n;; (progn (setf flags:*port* 9515) (start-hub))\n\n;; (setf hunchentoot:*catch-errors-p* nil)\n"
  },
  {
    "path": "src/screenshotbot/company/members.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/company/members\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:settings-template\n                #:defsettings)\n  (:import-from #:screenshotbot/user-api\n                #:user-image-url\n                #:current-user\n                #:user-email\n                #:user-full-name\n                #:current-company)\n  (:import-from #:screenshotbot/model/company\n                #:company-admin-p\n                #:company-owner)\n  (:import-from #:screenshotbot/model/user\n                #:users-for-company)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/model/invite\n                #:invite-email)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page\n                #:confirmation-page)\n  (:import-from #:core/ui/taskie\n                #:taskie-list\n                #:taskie-row\n                #:taskie-page-title)\n  (:import-from #:screenshotbot/template\n                #:app-template\n                #:mdi)\n  (:import-from #:auth/model/invite\n                #:all-unused-invites)\n  (:import-from #:screenshotbot/invite\n                #:user-can-invite-p\n                #:invite-enabled-p)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:screenshotbot/login/common\n                #:with-login)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:roles #:auth/model/roles)))\n(in-package :screenshotbot/company/members)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defsettings members-page\n  :name \"members\"\n  :staging-p t\n  :section :organization\n  :title \"Members\"\n  :handler (lambda ()\n             (members-page)))\n(defun mailto (x)\n  <a href= (format nil \"mailto:~a\" x)>,(progn x)</a>)\n\n(defun delete-user (user company &aux (back \"/team\"))\n  (assert (not (roles:has-role-p company user 'roles:admin)))\n  (confirmation-page\n   :yes (nibble ()\n          (setf (roles:user-role company user) nil)\n          (hex:safe-redirect back))\n   :no back\n   <p>Remove ,(user-full-name user) from this Organization?</p>))\n\n(markup:deftag delete-button (&key action)\n  <form>\n    <button type= \"submit\" class=\"btn btn-link\" value= \"Delete\" formaction=action >\n      <mdi name= \"delete\" class= \"text-danger\" />\n    </button>\n  </form>)\n\n(defun can-change-user-role-p (company user)\n  (and\n   (not (eql user (auth:current-user)))\n   (roles:has-role-p company (auth:current-user) 'roles:admin)\n   (not (roles:has-role-p company user 'roles:owner))))\n\n(defvar *allowed-roles*\n  (list\n   'roles:admin\n   'roles:standard-member\n   'roles:external-member))\n\n(defun %save-role (&key user company role)\n  (assert (can-change-user-role-p company user))\n  (let ((role (loop for r in *allowed-roles*\n                    if (string-equal r role)\n                      return r)))\n    (assert role)\n    (setf (roles:user-role company user)\n          role)\n    (hex:safe-redirect \"/team\")))\n\n(defun %edit-role (&key user company)\n  (assert (can-change-user-role-p company user))\n  (let ((submit (nibble (role :method :post) (%save-role\n                                              :company company\n                                              :user user\n                                              :role role))))\n    <simple-card-page  form-action= submit  >\n      <div class= \"card-body\">\n        <label for= \"role-selector\" class= \"form-label\">Choose role for ,(auth:user-email user):</label>\n        <select class= \"form-select mb-2\" id= \"role-selector\" name= \"role\" >\n          ,@ (loop for role in *allowed-roles*\n                   for selected = (typep (roles:user-role company user) role)\n                   collect\n                   <option value= (string role)\n                           selected= (when selected \"selected\") >\n                     ,(roles:role-friendly-name (make-instance role))\n                   </option>)\n        </select>\n\n      </div>\n\n      <div class= \"card-footer\">\n        <input type= \"submit\" class= \"btn btn-primary\" value= \"Change role\" />\n        <a href= \"/team\" class= \"btn btn-secondary\" >Cancel</a>\n      </div>\n    </simple-card-page>))\n\n(defun render-user-row (user company)\n  (let* ((adminp (company-admin-p company user))\n         (can-delete-p\n           (and\n            (not adminp)\n            (company-admin-p company (current-user))))\n         (delete (nibble ()\n                   (assert can-delete-p)\n                   (delete-user user company)))\n         (role (roles:user-role company user)))\n    <taskie-row>\n      <span><img class= \"rounded-circle avatar\" src= (user-image-url user) /></span>\n      <span >,(user-full-name user)</span>\n      <span>,(mailto (user-email user)) </span>\n      <span>\n        ,(roles:role-friendly-name role)\n        ,(when (can-change-user-role-p company user)\n           <a href= (nibble () (%edit-role :user user :company company)) title= \"Edit role\">\n             <mdi name= \"edit\" class= \"ps-1\" />\n           </a>)\n      </span>\n      <span>\n        ,(when can-delete-p\n           <delete-button action=delete />)\n      </span>\n    </taskie-row>))\n\n(defun no-permission-to-delete ()\n  <simple-card-page>\n    <p>\n    You do not have permission to delete invitations.\n    Please contact support@screenshotbot.io if you think this is an error.\n    </p>\n  </simple-card-page>)\n\n(defun delete-invite (invite company)\n  (cond\n    ((user-can-invite-p company (auth:current-user))\n     (confirmation-page\n      :yes (nibble ()\n             (with-transaction ()\n               (bknr.datastore:delete-object invite))\n             (hex:safe-redirect \"/team\"))\n      :no \"/team\"\n      <span>Delete invitation to ,(invite-email invite)?</span>))\n    (t\n     (no-permission-to-delete))))\n\n\n(defun render-invite-row (invite company)\n  (let* ((delete (nibble ()\n                   (delete-invite invite company))))\n    <taskie-row>\n      <span><em>Unknown</em></span>\n      <span>,(mailto (invite-email invite)) </span>\n      <span>\n        Invited\n      </span>\n      <span>\n        <delete-button action=delete />\n      </span>\n    </taskie-row>))\n\n(defun invites-section (company)\n  <markup:merge-tag>\n    ,(taskie-page-title\n      :title \"Pending Invites\"\n      <a href= \"/invite\" class= \"btn btn-success btn-sm\">\n        <mdi name= \"person_add\" />\n        Invite\n      </a>)\n    ,(taskie-list\n      :headers '(\"Name\" \"Email\" \"Status\" \"Actions\")\n      :items (all-unused-invites :company company)\n      :checkboxes nil\n      :empty-message \"No pending invites\"\n      :row-generator (lambda (invite)\n                       (render-invite-row invite company)))\n  </markup:merge-tag>)\n\n(markup:deftag %members-page ()\n  (let ((company (current-company)))\n\n     <markup:merge-tag>\n       ,(when (invite-enabled-p company)\n          (invites-section company))\n\n       ,(taskie-page-title :title \"Team\" :class \"pt-3\")\n\n       ,(taskie-list\n         :headers '(\"\" \"Name\" \"Email\" \"Status\" \"Actions\")\n         :class \"with-avatar\"\n         :items (remove-if\n                 (lambda (user)\n                   (or\n                    (not user)\n                    (roles:has-role-p company user 'roles:hidden-user)))\n                 (roles:users-for-company company))\n         :checkboxes nil\n         :empty-message \"No users\"\n         :row-generator (lambda (user)\n                          (render-user-row user company)))\n\n     </markup:merge-tag>\n))\n\n(defun members-page ()\n  (settings-template\n   (%members-page)))\n\n(defhandler (nil :uri \"/team\") ()\n  (with-login ()\n    <app-template>\n      ,(%members-page)\n    </app-template>))\n\n(defhandler (nil :uri \"/settings/members\") ()\n  (hex:safe-redirect \"/team\"))\n"
  },
  {
    "path": "src/screenshotbot/company/new.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/company/new\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:screenshotbot/user-api\n        #:screenshotbot/model/company\n        #:screenshotbot/model/user\n        #:screenshotbot/invite)\n  (:import-from #:screenshotbot/server\n                #:home-url\n                #:redirect-home\n                #:defhandler)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/login/populate\n                #:populate-company)\n  (:import-from #:hex #:make-url)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/dashboard/ensure-company\n                #:post-new-company)\n  (:import-from #:screenshotbot/dashboard/explain\n                #:explain)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature))\n(in-package :screenshotbot/company/new)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag organization-new-page ()\n  <simple-card-page form-action= \"/organization/new\" >\n    <div class= \"card-header\">\n      <h4>Create a new Organization</h4>\n    </div>\n    <div class= \"form-group mb-3\">\n      <label for= \"org-name\">Organization Name</label>\n      <input type= \"text\" class= \"form-control\" name= \"name\" placeholder= \"Acme Inc\" />\n    </div>\n\n    ,(when (gk:check :sub-companies (auth:current-company))\n       <div class= \"form-group mb-3\">\n         <label for= \"parent\">Parent Organization <explain>Inherits users and roles from a parent organization. Leave empty to not share users.</explain></label>\n         <select class= \"form-select\" name= \"parent\" id= \"parent\" >\n           ,(default-parent-options *installation*)\n           ,@ (loop for company in (roles:companies-for-user (auth:current-user))\n                    collect\n                    <option value= (bknr.datastore:store-object-id company) >,(company-name company)</option>)\n         </select>\n       </div>)\n\n    <!--\n      <div class= \"form-group\">\n        <label for= \"org-id\">Organization Id</label>\n        <input type= \"text\" class= \"form-control\" placeholder= \"xyzinc (Unique identifier)\" />\n      </div>\n      -->\n\n    <div class= \"form-check small\">\n      <input type= \"checkbox\" class= \"form-check-input\" id= \"i-agree\" name= \"i-agree\" />\n      <label for= \"i-agree\" class= \"form-check-label\" >Billing for this Organization will be on your billing information for your personal account. Depending on your plan, you might be charged for each user (apart from you) that will be added to this organization. We can transfer billing to another account on request.</label>\n    </div>\n    <div class= \"card-footer\">\n      <input type= \"submit\" class= \"btn btn-success form-control\" value= \"Create\" />\n    </div>\n  </simple-card-page>)\n\n(defgeneric default-parent-options (installation)\n  (:method (self)\n    nil)\n  (:method ((self multi-org-feature))\n    <option value= \"-1\">None</option>))\n\n(defhandler (nil :uri \"/organization/new\" :method :get) ()\n  (let ((user (current-user)))\n   (cond\n     (nil ;; (not (professionalp user))\n      <simple-card-page>\n        <p>In order to create an organization, please upgrade to the Professional plan.</p>\n        <a href= \"/upgrade\" type= \"button\" class= \"btn btn-success\">Upgrade</a>\n      </simple-card-page>)\n     (t\n      <organization-new-page />))))\n\n(defhandler (nil :uri \"/organization/new\" :method :post) (name i-agree parent)\n  (let ((parent (when (and parent (not (equal \"-1\" parent)))\n                  (bknr.datastore:store-object-with-id (parse-integer parent)))))\n    (when parent\n      (check-type parent company)\n      (auth:can-view! parent))\n    (post-new-company name i-agree\n                      :form #'organization-new-page\n                      :parent parent\n                      :redirect 'company-create-confirmation-page)))\n\n(defhandler (company-create-confirmation-page\n             :uri \"/organizations/confirmed\") ()\n  <simple-card-page>\n    <div class= \"card-header\">\n      <h3>Your organization has been created</h3>\n    </div>\n\n    <p> Next, invite your coworkers and collaborators to this organization.</p>\n\n    <div class= \"card-footer\">\n      <a href= (make-url 'invite-page) class= \"btn btn-primary\">\n        <i class= \"uil uil-user-plus\" />\n        Invite Members\n      </a>\n      <a href= (home-url) class= \"btn btn-secondary\">\n        <i class= \"mdi mdi-home\" />\n        Home\n      </a>\n\n    </div>\n  </simple-card-page>)\n\n\n(defun company-switch-page (company)\n  (check-type company company)\n  (can-view! company)\n  (setf (current-company) company)\n  (push-event \"company.switched\")\n  (redirect-home))\n"
  },
  {
    "path": "src/screenshotbot/company/rename.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/company/rename\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:settings-template\n                #:defsettings)\n  (:import-from #:screenshotbot/model/company\n                #:emails-enabled-by-default-p\n                #:company-invitation-role\n                #:company-with-name\n                #:company-admin-p)\n  (:import-from #:screenshotbot/user-api\n                #:personalp\n                #:company-name\n                #:current-company\n                #:current-user)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/form-errors\n                #:with-error-builder\n                #:with-form-errors)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:screenshotbot/invite\n                #:invite-enabled-p)\n  (:local-nicknames (#:roles #:auth/model/roles)))\n(in-package :screenshotbot/company/rename)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defsettings  org-general\n  :name \"organization\"\n  :section :organization\n  :title \"General\"\n  :handler 'general-company-page)\n\n(defparameter *roles*\n  `((\"site-admin\" \"Do not allow invitations\" roles:site-admin)\n    (\"admin\" \"Only Admins\" roles:admin)\n    (\"member\" \"Any member (excluding guests)\" roles:standard-member)))\n\n(defun general-company-page (&key (company (current-company))\n                          success)\n  (let ((post (nibble (name invitation-role emails-enabled-by-default-p\n                            :name :company-name-change-post)\n                (%post :company company\n                       :invitation-role invitation-role\n                       :emails-enabled-by-default-p emails-enabled-by-default-p\n                       :name name))))\n    <settings-template>\n      <div class= \"alert alert-danger d-none mt-3\">\n      </div>\n\n      ,(when success\n         <div class= \"alert alert-success mt-3\">\n           Organization updated.\n         </div>)\n\n      <form action=post method= \"POST\">\n        <div class= \"card mt-3\" >\n          <div class= \"card-header\">\n            <h3>Organization settings</h3>\n          </div>\n          <div class= \"card-body\">\n            <div class= \"form-group\">\n              <label for= \"name\" class= \"form-label\" >Organization name</label>\n              <input type= \"text\" value= (company-name company) class= \"form-control\"\n                     name= \"name\"\n                     id= \"name\" />\n            </div>\n\n            <!-- putting this under a (when ...) block instead of d-none will cause the post to fail -->\n            <div class= (format nil \"form-group mt-3 ~a\" (unless (invite-enabled-p company) \"d-none\")) >\n              <label for= \"invitation-role\" class= \"form-label\">Who can invite other members?</label>\n              <select name= \"invitation-role\" id= \"invitation-role\" class= \"form-select\" >\n                ,@ (loop for (type desc role) in *roles*\n                         collect\n                         <option value= type selected= (when (eql role (company-invitation-role company)) \"selected\") >,(progn desc)</option>)\n              </select>\n             </div>\n\n          </div>\n\n          <div class= \"card-body\">\n            <h5 class= \"card-title\">New user behavior</h5>\n            <div class= \"form-check mt-3\">\n              <input type= \"checkbox\" name= \"emails-enabled-by-default-p\"\n                     id= \"emails-enabled-by-default-p\"\n                     class= \"form-check-input\"\n                     checked= (if (emails-enabled-by-default-p company) \"checked\")\n                     />\n              <label for= \"emails-enabled-by-default-p\" class= \"form-check-label\">\n                For new users, enable email notifications by default\n              </label>\n            </div>\n\n          </div>\n\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Change\" />\n          </div>\n        </div>\n      </form>\n    </settings-template>))\n\n(defun %post (&key company name invitation-role emails-enabled-by-default-p)\n  (let ((invitation-role-sym\n          (second (assoc-value *roles* invitation-role :test #'equal))))\n   (with-error-builder (:check check\n                        :errors errors\n                        :form-builder (general-company-page :company company)\n                        :form-args (:name name :invitation-role invitation-role\n                                          :emails-enabled-by-default-p emails-enabled-by-default-p)\n                        :success (%finish-post :company company\n                                               :invitation-role invitation-role-sym\n                                               :emails-enabled-by-default-p emails-enabled-by-default-p\n                                               :name name))\n     (check :name\n            (or\n             (equal name (company-name company))\n             (not (company-with-name name)))\n            \"This company name is already in use\")\n     (check :invitation-role\n            invitation-role-sym\n            \"Invalid invitation role\")\n     (check :name (>= (length name) 6)\n            \"Company name must be at least 6 letters long\")\n     (check nil (company-admin-p company (current-user))\n            \"You must be an admin on this organization to change this page\")\n     (check nil (not (personalp company))\n            \"This organizaton is a legacy personal organization, and its name can't be changed.\"))))\n\n(defun %finish-post (&key company name invitation-role emails-enabled-by-default-p)\n  (setf (company-name company)\n        name)\n  (setf (company-invitation-role company)\n        invitation-role)\n  (setf (emails-enabled-by-default-p company)\n        (not (not emails-enabled-by-default-p)))\n  (hex:safe-redirect\n   (nibble (:name :success-name-change)\n     (general-company-page :company company :success t))))\n"
  },
  {
    "path": "src/screenshotbot/company/request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/company/request\n  (:use #:cl)\n  (:import-from #:core/installation/auth\n                #:company-for-request)\n  (:import-from #:screenshotbot/installation\n                #:base-multi-org-feature\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/user-api\n                #:created-at\n                #:can-view)\n  (:import-from #:screenshotbot/model/user\n                #:user-personal-company)\n  (:import-from #:local-time\n                #:timestamp>\n                #:timestamp-)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:run-id-to-run-map)\n  (:import-from #:auth/viewer-context\n                #:viewer-context-user)\n  (:import-from #:alexandria\n                #:when-let))\n(in-package :screenshotbot/company/request)\n\n(defmethod company-for-request ((installation base-multi-org-feature) request)\n  (cond\n    ((not (auth:request-user request))\n     nil)\n    (t\n     (guess-best-company\n      (auth:session-value :company)\n      (auth:viewer-context request)))))\n\n(defmethod company-for-request ((installation multi-org-feature) request)\n  (call-next-method))\n\n(defun guess-best-company (company viewer-context)\n  (when-let ((user (viewer-context-user viewer-context)))\n    (if (and company (auth:can-viewer-view viewer-context company))\n        company\n        (let ((companies (remove-if-not\n                          (alexandria:curry #'auth:can-viewer-view viewer-context)\n                          (roles:companies-for-user user))))\n          (or\n           (most-recent-company companies)\n           (user-personal-company user)\n           (car companies))))))\n\n(defun most-recent-company (companies)\n  \"Returns the most recently updated company in the list. If none are\n  updated in the last month, then return the personal company\"\n  (cdar\n   (sort\n    (let ((cutoff (timestamp- (local-time:now) 60 :day)))\n      (loop for company in companies\n            for map = (run-id-to-run-map company)\n            for run-id = (fset:greatest map)\n            for run = (fset:lookup map run-id)\n            for created-at = (when run (created-at run))\n            if (and created-at (timestamp> created-at cutoff))\n              collect\n              (cons created-at company)))\n    #'timestamp>\n      :key #'car)))\n"
  },
  {
    "path": "src/screenshotbot/company/test-rename.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/company/test-rename\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:screenshot-test\n                #:with-test-user)\n  (:import-from #:screenshotbot/company/rename\n                #:general-company-page))\n(in-package :screenshotbot/company/test-rename)\n\n(util/fiveam:def-suite)\n\n(screenshot-test company-general-settings\n  (with-test-store ()\n    (with-test-user (:company company :logged-in-p t)\n      (general-company-page))))\n"
  },
  {
    "path": "src/screenshotbot/company/test-request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/company/test-request\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/model/company\n                #:get-singleton-company\n                #:prepare-singleton-company)\n  (:import-from #:screenshotbot/company/request\n                #:most-recent-company\n                #:guess-best-company)\n    (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/model/company\n                #:company\n                #:prepare-singleton-company\n                #:get-singleton-company)\n  (:import-from #:screenshotbot/user-api\n                #:user\n                #:current-company)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/user\n                #:user-personal-company\n                #:make-user)\n  (:import-from #:screenshotbot/login/common\n                #:signin-get)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/company/test-request)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (cl-mock:with-mocks ()\n       (&body)))))\n\n(test current-company-for-common\n  (with-fixture state ()\n    (cl-mock:answer\n        (auth:session-value :company)\n      nil)\n    (let ((*installation* (make-instance 'installation)))\n\n      (let* ((company (prepare-singleton-company))\n             (user (make-instance 'user))\n             (vc (make-instance 'normal-viewer-context\n                                :user user)))\n        (setf (roles:user-role company user) 'roles:standard-member)\n        (assert-that (roles:companies-for-user user)\n                     (has-length 1))\n        (is-true (guess-best-company nil vc))))))\n\n(defclass multi-org (multi-org-feature\n                     installation)\n  ())\n\n(test current-company-for-multi-org\n  (with-fixture state ()\n    (let* ((*installation* (make-instance 'multi-org))\n           (user (make-user))\n           (company (user-personal-company user))\n           (vc (make-instance 'normal-viewer-context\n                              :user user)))\n      (is (eql company (guess-best-company company vc)) ))) ())\n\n(test most-recent-company\n  (with-fixture state ()\n    (let ((company-1 (make-instance 'company)))\n      (is (eql nil (most-recent-company (list company-1))))\n      (let ((run (make-recorder-run\n                     :screenshot-map nil\n                     :company company-1)))\n        (is (eql company-1 (most-recent-company (list company-1))))))))\n"
  },
  {
    "path": "src/screenshotbot/config.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/config\n    (:use #:cl\n          #:alexandria\n          #:screenshotbot/mailer)\n  (:import-from #:screenshotbot/installation\n                #:oss-installation)\n  (:import-from #:screenshotbot/github\n                #:github-plugin)\n  (:import-from #:screenshotbot/phabricator\n                #:phabricator-plugin)\n  (:import-from #:screenshotbot/mailer\n                #:local-smtp-mailer\n                #:noop-mailer)\n  #+ (or ccl lispworks)\n  (:import-from #:screenshotbot/slack\n                #:slack-plugin)\n  (:import-from #:screenshotbot/login/github-oauth\n                #:github-oauth-provider)\n  (:import-from #:screenshotbot/login/google-oauth\n                #:google-oauth-provider)\n  (:import-from #:screenshotbot/login/oidc\n                #:oidc-provider)\n  (:export #:load-config))\n(in-package :screenshotbot/config)\n\n(defun find-config.lisp ()\n  \"Search for an appropriate config.lisp file that is created by site-admin\"\n  (flet ((check (filename)\n           (let ((filename (pathname filename)))\n             (when (path:-e filename)\n               (return-from find-config.lisp filename)))))\n    (check \"config.lisp\")\n    (check \"~/.config/screenshotbot/config.lisp\")\n    nil))\n\n(defclass installation (oss-installation)\n  ())\n\n(defun (setf installation) (val)\n  (setf (screenshotbot/installation:installation) val))\n\n(defun load-config ()\n  \"Load an appropriate config.lisp file if it exists\"\n  (let ((config.lisp (find-config.lisp)))\n    (cond\n      (config.lisp\n       (log:info \"Loading config at ~a\" config.lisp)\n       (let ((*package* (find-package :screenshotbot/config)))\n         (load config.lisp)))\n      (t\n       (log:info \"No config.lisp found\")\n       (setf (screenshotbot/installation:installation) (make-instance 'oss-installation))))))\n\n;;;; I suppose I'm misusing the screenshotbot/config package for bot\n;;;; this file and for the package the config.lisp is loaded from. In\n;;;; any case, I didn't want to pollute the package so that's why the\n;;;; following symbols are not imported.\n\n(screenshotbot/admin/core:defadminhandler (reload-config-page :uri \"/admin/reload-config\") ()\n  (hex:safe-redirect (nibble:nibble (:once t)\n                       (core/ui/simple-card-page:confirmation-page\n                        :yes (nibble:nibble (:once t)\n                               (load-config)\n                               (hex:safe-redirect \"/admin\"))\n                        :no \"/admin\"\n                        \"Reload the config.lisp?\"))))\n\n(screenshotbot/admin/core:register-admin-menu \"Reload config.lisp\" 'reload-config-page)\n"
  },
  {
    "path": "src/screenshotbot/css/auth.scss",
    "content": ".auth-pages {\n    display: flex;\n    flex-direction: row;\n\n\n    font-family: Metropolis;\n\n    h1, h2, h3, h4, h5, h6 {\n        font-family: Montserrat !important;\n        font-weight: bold;\n        color: white;\n    }\n\n    .left-image {\n        height: 100vh;\n        width: 50vw;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n\n        .copy {\n            position: absolute;\n            bottom: 5em;\n        }\n\n        .navbar-brand {\n            position: absolute;\n            top: 1rem;\n            left: 1rem;\n        }\n    }\n\n    .alert {\n        width: 49.3rem;\n\n        .alert-danger {\n            background: #dd5068;\n        }\n\n        padding: 1em;\n        h4 {\n            font-size: 2rem !important;\n            padding-bottom: 0.5rem !important;\n            margin-bottom: 0.5rem !important;\n        }\n        @include media-breakpoint-down(sm) {\n            width: 100%;\n            max-width: 49.3rem;\n        }\n    }\n\n\n    .form-container {\n        min-height: 100vh;\n        width: 50vw;\n        background: #52ad77;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n\n        @media(max-height: 700px) {\n            align-items: flex-start;\n        }\n\n        .home-link {\n            position: absolute;\n            top: 1rem;\n            left: 1rem;\n            color: black;\n            display: none;\n\n            @include media-breakpoint-down(lg) {\n                display: block;\n            }\n        }\n\n        .card {\n            background: none;\n            border: none;\n            width: 49.3rem;\n            padding: 0 !important;\n\n            @include media-breakpoint-down(sm) {\n                width: 100%;\n                max-width: 49.3rem;\n\n            }\n\n        }\n\n        h4 {\n            font-weight: bold;\n            font-size: 4rem;\n        }\n\n        input[type=\"email\"],input[type=\"text\"] {\n            margin-bottom: 1.6rem !important;\n        }\n        input[type=\"text\"], input[type=\"email\"], input[type=\"password\"], button, a.btn {\n            height: 6.4rem;\n            font-size: 2rem;\n            padding-left: 3.4rem;\n            padding-right: 3.4rem;\n            width: 100%;\n            border-radius: 5px;\n        }\n\n        input[type=\"checkbox\"] {\n            width: 1.9rem;\n            height: 1.9rem;\n            border: none !important;\n            border-radius: 5px;\n            margin-left: 0 !important;\n            padding: 0;\n        }\n\n        button, a.btn {\n            border-radius: 999px;\n            font-family: \"Montserrat\";\n            font-weight: 500;\n            font-size: 1.8rem;\n            border: none;\n        }\n\n        .btn-primary {\n            background: black;\n            &:hover {\n                background: lighten(black, 20%);\n            }\n            color: white;\n            margin-bottom: 8.8rem !important;\n            margin-top: 3.6rem;\n        }\n\n        a.btn {\n            background: #f2f2f2;\n            &:hover {\n                background: darken(#f2f2f2, 10%);\n            }\n            color: black;\n            margin: auto;\n            line-height: 6.4rem;\n            padding-top: 0;\n            padding-bottom: 0;\n\n            svg {\n                height: 3rem;\n                width: 3rem;\n                vertical-align: middle !important;\n                margin-right: 1.8rem;\n            }\n        }\n\n        .form-check-label {\n            font-size: 1.4rem;\n        }\n\n        a.links {\n            color: white;\n            text-decoration: none;\n        }\n\n        a.links:hover {\n            text-decoration: underline;\n        }\n\n        label {\n            color: white;\n        }\n\n        .signup-message {\n            color: white;\n            a {\n                color: white;\n            }\n        }\n\n        .invalid-feedback {\n            font-size: 1.6rem;\n            padding: 0.5em;\n            width: auto;\n            border-radius: 5px;\n            font-weight: bold;\n        }\n    }\n\n    a, p, a:hover {\n        color: white;\n    }\n\n    &.signup {\n        .alert {\n            background: #52ad77;\n        }\n        .form-container {\n            background: #dd5068;\n        }\n\n        .invalid-feedback {\n            color: black;\n        }\n\n        input.is-invalid {\n            border: solid 2px red;\n            color: red;\n        }\n\n        .form-check-input:is-invalid {\n            border:solid 2px red;\n        }\n\n        .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n            color: black;\n        }\n\n        .btn-primary {\n            margin-bottom: 5.8rem !important;\n        }\n    }\n\n    &.simple {\n        .left-image {\n            display:none;\n        }\n\n        .form-container {\n            background: white;\n        }\n    }\n\n    .left-image {\n        .botty-image {\n            width: auto;\n            height: auto;\n        }\n    }\n\n    @include media-breakpoint-down(lg) {\n        .left-image {\n            display: none;\n        }\n\n        .form-container {\n            width: 100vw;\n            padding: 2em;\n        }\n    }\n\n\n\n}\n\n.or-signup-with {\n    text-align: center;\n    text-transform: lowercase;\n    display: grid;\n    grid-template-columns: 1fr max-content 1fr;\n    align-items: center;\n\n    margin-bottom: 4rem;\n    grid-gap: 0.5rem;\n    color: #eee;\n    &::before, &::after {\n        content: '';\n        border-bottom: 1px solid #eee;\n        transform: translateY(-1px);\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/autocomplete.scss",
    "content": "\n\n// Taken from: https://jsfiddle.net/gotemkfr/\n.ui-autocomplete {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: 1000;\n  display: none;\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0;\n  list-style: none;\n  font-size: 14px;\n  text-align: left;\n  background-color: #ffffff;\n  border: 1px solid #cccccc;\n  border: 1px solid rgba(0, 0, 0, 0.15);\n  border-radius: 4px;\n  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n  background-clip: padding-box;\n}\n\n.ui-autocomplete > li > div {\n  display: block;\n  padding: 3px 20px;\n  clear: both;\n  font-weight: normal;\n  line-height: 1.42857143;\n  color: #333333;\n  white-space: nowrap;\n}\n\n.ui-state-hover,\n.ui-state-active,\n.ui-state-focus {\n  text-decoration: none;\n  color: #262626;\n  background-color: #f5f5f5;\n  cursor: pointer;\n}\n\n.ui-helper-hidden-accessible {\n  border: 0;\n  clip: rect(0 0 0 0);\n  height: 1px;\n  margin: -1px;\n  overflow: hidden;\n  padding: 0;\n  position: absolute;\n  width: 1px;\n}\n\n"
  },
  {
    "path": "src/screenshotbot/css/avatar.scss",
    "content": "//\n//\n// avatars.scss\n//\n//\n\n.avatar{\n  flex-shrink: 0;\n  width: $spacer*2;\n  height: $spacer*2;\n  border-radius: 50%;\n  &.avatar-sm{\n    width: $spacer*1.5;\n    height: $spacer*1.5;\n  }\n  &.avatar-lg{\n    width: $spacer*3;\n    height: $spacer*3;\n  }\n  &.avatar-xlg{\n    width: $spacer*5;\n    height: $spacer*5;\n  }\n}\n\n@include media-breakpoint-up(lg) {\n  .avatar{\n    &.avatar-lg{\n      width: $spacer*3;\n      height: $spacer*3;\n    }\n  }\n}\n\n.avatars{\n  padding-left: 0;\n  list-style: none;\n  display: flex;\n  margin-bottom: 0;\n  > li{\n    position: relative;\n    &:not(:last-child){\n      margin-right: -$spacer/2;\n    }\n  }\n  .avatar{\n    border: 2px solid $white;\n  }\n}\n\n.avatar-author{\n  display: flex;\n  h6{\n    margin-bottom: 0;\n  }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/billing.scss",
    "content": ".card-list {\n    li:hover {\n        background-color: $gray-100;\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/bootstrap-icons.css",
    "content": "@font-face {\n  font-family: \"bootstrap-icons\";\n  src: url(\"../fonts/bootstrap-icons.woff2?231ce25e89ab5804f9a6c427b8d325c9\") format(\"woff2\"),\nurl(\"../fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9\") format(\"woff\");\n}\n\n[class^=\"bi-\"]::before,\n[class*=\" bi-\"]::before {\n  display: inline-block;\n  font-family: bootstrap-icons !important;\n  font-style: normal;\n  font-weight: normal !important;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  vertical-align: -.125em;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.bi-alarm-fill::before { content: \"\\f101\"; }\n.bi-alarm::before { content: \"\\f102\"; }\n.bi-align-bottom::before { content: \"\\f103\"; }\n.bi-align-center::before { content: \"\\f104\"; }\n.bi-align-end::before { content: \"\\f105\"; }\n.bi-align-middle::before { content: \"\\f106\"; }\n.bi-align-start::before { content: \"\\f107\"; }\n.bi-align-top::before { content: \"\\f108\"; }\n.bi-alt::before { content: \"\\f109\"; }\n.bi-app-indicator::before { content: \"\\f10a\"; }\n.bi-app::before { content: \"\\f10b\"; }\n.bi-archive-fill::before { content: \"\\f10c\"; }\n.bi-archive::before { content: \"\\f10d\"; }\n.bi-arrow-90deg-down::before { content: \"\\f10e\"; }\n.bi-arrow-90deg-left::before { content: \"\\f10f\"; }\n.bi-arrow-90deg-right::before { content: \"\\f110\"; }\n.bi-arrow-90deg-up::before { content: \"\\f111\"; }\n.bi-arrow-bar-down::before { content: \"\\f112\"; }\n.bi-arrow-bar-left::before { content: \"\\f113\"; }\n.bi-arrow-bar-right::before { content: \"\\f114\"; }\n.bi-arrow-bar-up::before { content: \"\\f115\"; }\n.bi-arrow-clockwise::before { content: \"\\f116\"; }\n.bi-arrow-counterclockwise::before { content: \"\\f117\"; }\n.bi-arrow-down-circle-fill::before { content: \"\\f118\"; }\n.bi-arrow-down-circle::before { content: \"\\f119\"; }\n.bi-arrow-down-left-circle-fill::before { content: \"\\f11a\"; }\n.bi-arrow-down-left-circle::before { content: \"\\f11b\"; }\n.bi-arrow-down-left-square-fill::before { content: \"\\f11c\"; }\n.bi-arrow-down-left-square::before { content: \"\\f11d\"; }\n.bi-arrow-down-left::before { content: \"\\f11e\"; }\n.bi-arrow-down-right-circle-fill::before { content: \"\\f11f\"; }\n.bi-arrow-down-right-circle::before { content: \"\\f120\"; }\n.bi-arrow-down-right-square-fill::before { content: \"\\f121\"; }\n.bi-arrow-down-right-square::before { content: \"\\f122\"; }\n.bi-arrow-down-right::before { content: \"\\f123\"; }\n.bi-arrow-down-short::before { content: \"\\f124\"; }\n.bi-arrow-down-square-fill::before { content: \"\\f125\"; }\n.bi-arrow-down-square::before { content: \"\\f126\"; }\n.bi-arrow-down-up::before { content: \"\\f127\"; }\n.bi-arrow-down::before { content: \"\\f128\"; }\n.bi-arrow-left-circle-fill::before { content: \"\\f129\"; }\n.bi-arrow-left-circle::before { content: \"\\f12a\"; }\n.bi-arrow-left-right::before { content: \"\\f12b\"; }\n.bi-arrow-left-short::before { content: \"\\f12c\"; }\n.bi-arrow-left-square-fill::before { content: \"\\f12d\"; }\n.bi-arrow-left-square::before { content: \"\\f12e\"; }\n.bi-arrow-left::before { content: \"\\f12f\"; }\n.bi-arrow-repeat::before { content: \"\\f130\"; }\n.bi-arrow-return-left::before { content: \"\\f131\"; }\n.bi-arrow-return-right::before { content: \"\\f132\"; }\n.bi-arrow-right-circle-fill::before { content: \"\\f133\"; }\n.bi-arrow-right-circle::before { content: \"\\f134\"; }\n.bi-arrow-right-short::before { content: \"\\f135\"; }\n.bi-arrow-right-square-fill::before { content: \"\\f136\"; }\n.bi-arrow-right-square::before { content: \"\\f137\"; }\n.bi-arrow-right::before { content: \"\\f138\"; }\n.bi-arrow-up-circle-fill::before { content: \"\\f139\"; }\n.bi-arrow-up-circle::before { content: \"\\f13a\"; }\n.bi-arrow-up-left-circle-fill::before { content: \"\\f13b\"; }\n.bi-arrow-up-left-circle::before { content: \"\\f13c\"; }\n.bi-arrow-up-left-square-fill::before { content: \"\\f13d\"; }\n.bi-arrow-up-left-square::before { content: \"\\f13e\"; }\n.bi-arrow-up-left::before { content: \"\\f13f\"; }\n.bi-arrow-up-right-circle-fill::before { content: \"\\f140\"; }\n.bi-arrow-up-right-circle::before { content: \"\\f141\"; }\n.bi-arrow-up-right-square-fill::before { content: \"\\f142\"; }\n.bi-arrow-up-right-square::before { content: \"\\f143\"; }\n.bi-arrow-up-right::before { content: \"\\f144\"; }\n.bi-arrow-up-short::before { content: \"\\f145\"; }\n.bi-arrow-up-square-fill::before { content: \"\\f146\"; }\n.bi-arrow-up-square::before { content: \"\\f147\"; }\n.bi-arrow-up::before { content: \"\\f148\"; }\n.bi-arrows-angle-contract::before { content: \"\\f149\"; }\n.bi-arrows-angle-expand::before { content: \"\\f14a\"; }\n.bi-arrows-collapse::before { content: \"\\f14b\"; }\n.bi-arrows-expand::before { content: \"\\f14c\"; }\n.bi-arrows-fullscreen::before { content: \"\\f14d\"; }\n.bi-arrows-move::before { content: \"\\f14e\"; }\n.bi-aspect-ratio-fill::before { content: \"\\f14f\"; }\n.bi-aspect-ratio::before { content: \"\\f150\"; }\n.bi-asterisk::before { content: \"\\f151\"; }\n.bi-at::before { content: \"\\f152\"; }\n.bi-award-fill::before { content: \"\\f153\"; }\n.bi-award::before { content: \"\\f154\"; }\n.bi-back::before { content: \"\\f155\"; }\n.bi-backspace-fill::before { content: \"\\f156\"; }\n.bi-backspace-reverse-fill::before { content: \"\\f157\"; }\n.bi-backspace-reverse::before { content: \"\\f158\"; }\n.bi-backspace::before { content: \"\\f159\"; }\n.bi-badge-3d-fill::before { content: \"\\f15a\"; }\n.bi-badge-3d::before { content: \"\\f15b\"; }\n.bi-badge-4k-fill::before { content: \"\\f15c\"; }\n.bi-badge-4k::before { content: \"\\f15d\"; }\n.bi-badge-8k-fill::before { content: \"\\f15e\"; }\n.bi-badge-8k::before { content: \"\\f15f\"; }\n.bi-badge-ad-fill::before { content: \"\\f160\"; }\n.bi-badge-ad::before { content: \"\\f161\"; }\n.bi-badge-ar-fill::before { content: \"\\f162\"; }\n.bi-badge-ar::before { content: \"\\f163\"; }\n.bi-badge-cc-fill::before { content: \"\\f164\"; }\n.bi-badge-cc::before { content: \"\\f165\"; }\n.bi-badge-hd-fill::before { content: \"\\f166\"; }\n.bi-badge-hd::before { content: \"\\f167\"; }\n.bi-badge-tm-fill::before { content: \"\\f168\"; }\n.bi-badge-tm::before { content: \"\\f169\"; }\n.bi-badge-vo-fill::before { content: \"\\f16a\"; }\n.bi-badge-vo::before { content: \"\\f16b\"; }\n.bi-badge-vr-fill::before { content: \"\\f16c\"; }\n.bi-badge-vr::before { content: \"\\f16d\"; }\n.bi-badge-wc-fill::before { content: \"\\f16e\"; }\n.bi-badge-wc::before { content: \"\\f16f\"; }\n.bi-bag-check-fill::before { content: \"\\f170\"; }\n.bi-bag-check::before { content: \"\\f171\"; }\n.bi-bag-dash-fill::before { content: \"\\f172\"; }\n.bi-bag-dash::before { content: \"\\f173\"; }\n.bi-bag-fill::before { content: \"\\f174\"; }\n.bi-bag-plus-fill::before { content: \"\\f175\"; }\n.bi-bag-plus::before { content: \"\\f176\"; }\n.bi-bag-x-fill::before { content: \"\\f177\"; }\n.bi-bag-x::before { content: \"\\f178\"; }\n.bi-bag::before { content: \"\\f179\"; }\n.bi-bar-chart-fill::before { content: \"\\f17a\"; }\n.bi-bar-chart-line-fill::before { content: \"\\f17b\"; }\n.bi-bar-chart-line::before { content: \"\\f17c\"; }\n.bi-bar-chart-steps::before { content: \"\\f17d\"; }\n.bi-bar-chart::before { content: \"\\f17e\"; }\n.bi-basket-fill::before { content: \"\\f17f\"; }\n.bi-basket::before { content: \"\\f180\"; }\n.bi-basket2-fill::before { content: \"\\f181\"; }\n.bi-basket2::before { content: \"\\f182\"; }\n.bi-basket3-fill::before { content: \"\\f183\"; }\n.bi-basket3::before { content: \"\\f184\"; }\n.bi-battery-charging::before { content: \"\\f185\"; }\n.bi-battery-full::before { content: \"\\f186\"; }\n.bi-battery-half::before { content: \"\\f187\"; }\n.bi-battery::before { content: \"\\f188\"; }\n.bi-bell-fill::before { content: \"\\f189\"; }\n.bi-bell::before { content: \"\\f18a\"; }\n.bi-bezier::before { content: \"\\f18b\"; }\n.bi-bezier2::before { content: \"\\f18c\"; }\n.bi-bicycle::before { content: \"\\f18d\"; }\n.bi-binoculars-fill::before { content: \"\\f18e\"; }\n.bi-binoculars::before { content: \"\\f18f\"; }\n.bi-blockquote-left::before { content: \"\\f190\"; }\n.bi-blockquote-right::before { content: \"\\f191\"; }\n.bi-book-fill::before { content: \"\\f192\"; }\n.bi-book-half::before { content: \"\\f193\"; }\n.bi-book::before { content: \"\\f194\"; }\n.bi-bookmark-check-fill::before { content: \"\\f195\"; }\n.bi-bookmark-check::before { content: \"\\f196\"; }\n.bi-bookmark-dash-fill::before { content: \"\\f197\"; }\n.bi-bookmark-dash::before { content: \"\\f198\"; }\n.bi-bookmark-fill::before { content: \"\\f199\"; }\n.bi-bookmark-heart-fill::before { content: \"\\f19a\"; }\n.bi-bookmark-heart::before { content: \"\\f19b\"; }\n.bi-bookmark-plus-fill::before { content: \"\\f19c\"; }\n.bi-bookmark-plus::before { content: \"\\f19d\"; }\n.bi-bookmark-star-fill::before { content: \"\\f19e\"; }\n.bi-bookmark-star::before { content: \"\\f19f\"; }\n.bi-bookmark-x-fill::before { content: \"\\f1a0\"; }\n.bi-bookmark-x::before { content: \"\\f1a1\"; }\n.bi-bookmark::before { content: \"\\f1a2\"; }\n.bi-bookmarks-fill::before { content: \"\\f1a3\"; }\n.bi-bookmarks::before { content: \"\\f1a4\"; }\n.bi-bookshelf::before { content: \"\\f1a5\"; }\n.bi-bootstrap-fill::before { content: \"\\f1a6\"; }\n.bi-bootstrap-reboot::before { content: \"\\f1a7\"; }\n.bi-bootstrap::before { content: \"\\f1a8\"; }\n.bi-border-all::before { content: \"\\f1a9\"; }\n.bi-border-bottom::before { content: \"\\f1aa\"; }\n.bi-border-center::before { content: \"\\f1ab\"; }\n.bi-border-inner::before { content: \"\\f1ac\"; }\n.bi-border-left::before { content: \"\\f1ad\"; }\n.bi-border-middle::before { content: \"\\f1ae\"; }\n.bi-border-outer::before { content: \"\\f1af\"; }\n.bi-border-right::before { content: \"\\f1b0\"; }\n.bi-border-style::before { content: \"\\f1b1\"; }\n.bi-border-top::before { content: \"\\f1b2\"; }\n.bi-border-width::before { content: \"\\f1b3\"; }\n.bi-border::before { content: \"\\f1b4\"; }\n.bi-bounding-box-circles::before { content: \"\\f1b5\"; }\n.bi-bounding-box::before { content: \"\\f1b6\"; }\n.bi-box-arrow-down-left::before { content: \"\\f1b7\"; }\n.bi-box-arrow-down-right::before { content: \"\\f1b8\"; }\n.bi-box-arrow-down::before { content: \"\\f1b9\"; }\n.bi-box-arrow-in-down-left::before { content: \"\\f1ba\"; }\n.bi-box-arrow-in-down-right::before { content: \"\\f1bb\"; }\n.bi-box-arrow-in-down::before { content: \"\\f1bc\"; }\n.bi-box-arrow-in-left::before { content: \"\\f1bd\"; }\n.bi-box-arrow-in-right::before { content: \"\\f1be\"; }\n.bi-box-arrow-in-up-left::before { content: \"\\f1bf\"; }\n.bi-box-arrow-in-up-right::before { content: \"\\f1c0\"; }\n.bi-box-arrow-in-up::before { content: \"\\f1c1\"; }\n.bi-box-arrow-left::before { content: \"\\f1c2\"; }\n.bi-box-arrow-right::before { content: \"\\f1c3\"; }\n.bi-box-arrow-up-left::before { content: \"\\f1c4\"; }\n.bi-box-arrow-up-right::before { content: \"\\f1c5\"; }\n.bi-box-arrow-up::before { content: \"\\f1c6\"; }\n.bi-box-seam::before { content: \"\\f1c7\"; }\n.bi-box::before { content: \"\\f1c8\"; }\n.bi-braces::before { content: \"\\f1c9\"; }\n.bi-bricks::before { content: \"\\f1ca\"; }\n.bi-briefcase-fill::before { content: \"\\f1cb\"; }\n.bi-briefcase::before { content: \"\\f1cc\"; }\n.bi-brightness-alt-high-fill::before { content: \"\\f1cd\"; }\n.bi-brightness-alt-high::before { content: \"\\f1ce\"; }\n.bi-brightness-alt-low-fill::before { content: \"\\f1cf\"; }\n.bi-brightness-alt-low::before { content: \"\\f1d0\"; }\n.bi-brightness-high-fill::before { content: \"\\f1d1\"; }\n.bi-brightness-high::before { content: \"\\f1d2\"; }\n.bi-brightness-low-fill::before { content: \"\\f1d3\"; }\n.bi-brightness-low::before { content: \"\\f1d4\"; }\n.bi-broadcast-pin::before { content: \"\\f1d5\"; }\n.bi-broadcast::before { content: \"\\f1d6\"; }\n.bi-brush-fill::before { content: \"\\f1d7\"; }\n.bi-brush::before { content: \"\\f1d8\"; }\n.bi-bucket-fill::before { content: \"\\f1d9\"; }\n.bi-bucket::before { content: \"\\f1da\"; }\n.bi-bug-fill::before { content: \"\\f1db\"; }\n.bi-bug::before { content: \"\\f1dc\"; }\n.bi-building::before { content: \"\\f1dd\"; }\n.bi-bullseye::before { content: \"\\f1de\"; }\n.bi-calculator-fill::before { content: \"\\f1df\"; }\n.bi-calculator::before { content: \"\\f1e0\"; }\n.bi-calendar-check-fill::before { content: \"\\f1e1\"; }\n.bi-calendar-check::before { content: \"\\f1e2\"; }\n.bi-calendar-date-fill::before { content: \"\\f1e3\"; }\n.bi-calendar-date::before { content: \"\\f1e4\"; }\n.bi-calendar-day-fill::before { content: \"\\f1e5\"; }\n.bi-calendar-day::before { content: \"\\f1e6\"; }\n.bi-calendar-event-fill::before { content: \"\\f1e7\"; }\n.bi-calendar-event::before { content: \"\\f1e8\"; }\n.bi-calendar-fill::before { content: \"\\f1e9\"; }\n.bi-calendar-minus-fill::before { content: \"\\f1ea\"; }\n.bi-calendar-minus::before { content: \"\\f1eb\"; }\n.bi-calendar-month-fill::before { content: \"\\f1ec\"; }\n.bi-calendar-month::before { content: \"\\f1ed\"; }\n.bi-calendar-plus-fill::before { content: \"\\f1ee\"; }\n.bi-calendar-plus::before { content: \"\\f1ef\"; }\n.bi-calendar-range-fill::before { content: \"\\f1f0\"; }\n.bi-calendar-range::before { content: \"\\f1f1\"; }\n.bi-calendar-week-fill::before { content: \"\\f1f2\"; }\n.bi-calendar-week::before { content: \"\\f1f3\"; }\n.bi-calendar-x-fill::before { content: \"\\f1f4\"; }\n.bi-calendar-x::before { content: \"\\f1f5\"; }\n.bi-calendar::before { content: \"\\f1f6\"; }\n.bi-calendar2-check-fill::before { content: \"\\f1f7\"; }\n.bi-calendar2-check::before { content: \"\\f1f8\"; }\n.bi-calendar2-date-fill::before { content: \"\\f1f9\"; }\n.bi-calendar2-date::before { content: \"\\f1fa\"; }\n.bi-calendar2-day-fill::before { content: \"\\f1fb\"; }\n.bi-calendar2-day::before { content: \"\\f1fc\"; }\n.bi-calendar2-event-fill::before { content: \"\\f1fd\"; }\n.bi-calendar2-event::before { content: \"\\f1fe\"; }\n.bi-calendar2-fill::before { content: \"\\f1ff\"; }\n.bi-calendar2-minus-fill::before { content: \"\\f200\"; }\n.bi-calendar2-minus::before { content: \"\\f201\"; }\n.bi-calendar2-month-fill::before { content: \"\\f202\"; }\n.bi-calendar2-month::before { content: \"\\f203\"; }\n.bi-calendar2-plus-fill::before { content: \"\\f204\"; }\n.bi-calendar2-plus::before { content: \"\\f205\"; }\n.bi-calendar2-range-fill::before { content: \"\\f206\"; }\n.bi-calendar2-range::before { content: \"\\f207\"; }\n.bi-calendar2-week-fill::before { content: \"\\f208\"; }\n.bi-calendar2-week::before { content: \"\\f209\"; }\n.bi-calendar2-x-fill::before { content: \"\\f20a\"; }\n.bi-calendar2-x::before { content: \"\\f20b\"; }\n.bi-calendar2::before { content: \"\\f20c\"; }\n.bi-calendar3-event-fill::before { content: \"\\f20d\"; }\n.bi-calendar3-event::before { content: \"\\f20e\"; }\n.bi-calendar3-fill::before { content: \"\\f20f\"; }\n.bi-calendar3-range-fill::before { content: \"\\f210\"; }\n.bi-calendar3-range::before { content: \"\\f211\"; }\n.bi-calendar3-week-fill::before { content: \"\\f212\"; }\n.bi-calendar3-week::before { content: \"\\f213\"; }\n.bi-calendar3::before { content: \"\\f214\"; }\n.bi-calendar4-event::before { content: \"\\f215\"; }\n.bi-calendar4-range::before { content: \"\\f216\"; }\n.bi-calendar4-week::before { content: \"\\f217\"; }\n.bi-calendar4::before { content: \"\\f218\"; }\n.bi-camera-fill::before { content: \"\\f219\"; }\n.bi-camera-reels-fill::before { content: \"\\f21a\"; }\n.bi-camera-reels::before { content: \"\\f21b\"; }\n.bi-camera-video-fill::before { content: \"\\f21c\"; }\n.bi-camera-video-off-fill::before { content: \"\\f21d\"; }\n.bi-camera-video-off::before { content: \"\\f21e\"; }\n.bi-camera-video::before { content: \"\\f21f\"; }\n.bi-camera::before { content: \"\\f220\"; }\n.bi-camera2::before { content: \"\\f221\"; }\n.bi-capslock-fill::before { content: \"\\f222\"; }\n.bi-capslock::before { content: \"\\f223\"; }\n.bi-card-checklist::before { content: \"\\f224\"; }\n.bi-card-heading::before { content: \"\\f225\"; }\n.bi-card-image::before { content: \"\\f226\"; }\n.bi-card-list::before { content: \"\\f227\"; }\n.bi-card-text::before { content: \"\\f228\"; }\n.bi-caret-down-fill::before { content: \"\\f229\"; }\n.bi-caret-down-square-fill::before { content: \"\\f22a\"; }\n.bi-caret-down-square::before { content: \"\\f22b\"; }\n.bi-caret-down::before { content: \"\\f22c\"; }\n.bi-caret-left-fill::before { content: \"\\f22d\"; }\n.bi-caret-left-square-fill::before { content: \"\\f22e\"; }\n.bi-caret-left-square::before { content: \"\\f22f\"; }\n.bi-caret-left::before { content: \"\\f230\"; }\n.bi-caret-right-fill::before { content: \"\\f231\"; }\n.bi-caret-right-square-fill::before { content: \"\\f232\"; }\n.bi-caret-right-square::before { content: \"\\f233\"; }\n.bi-caret-right::before { content: \"\\f234\"; }\n.bi-caret-up-fill::before { content: \"\\f235\"; }\n.bi-caret-up-square-fill::before { content: \"\\f236\"; }\n.bi-caret-up-square::before { content: \"\\f237\"; }\n.bi-caret-up::before { content: \"\\f238\"; }\n.bi-cart-check-fill::before { content: \"\\f239\"; }\n.bi-cart-check::before { content: \"\\f23a\"; }\n.bi-cart-dash-fill::before { content: \"\\f23b\"; }\n.bi-cart-dash::before { content: \"\\f23c\"; }\n.bi-cart-fill::before { content: \"\\f23d\"; }\n.bi-cart-plus-fill::before { content: \"\\f23e\"; }\n.bi-cart-plus::before { content: \"\\f23f\"; }\n.bi-cart-x-fill::before { content: \"\\f240\"; }\n.bi-cart-x::before { content: \"\\f241\"; }\n.bi-cart::before { content: \"\\f242\"; }\n.bi-cart2::before { content: \"\\f243\"; }\n.bi-cart3::before { content: \"\\f244\"; }\n.bi-cart4::before { content: \"\\f245\"; }\n.bi-cash-stack::before { content: \"\\f246\"; }\n.bi-cash::before { content: \"\\f247\"; }\n.bi-cast::before { content: \"\\f248\"; }\n.bi-chat-dots-fill::before { content: \"\\f249\"; }\n.bi-chat-dots::before { content: \"\\f24a\"; }\n.bi-chat-fill::before { content: \"\\f24b\"; }\n.bi-chat-left-dots-fill::before { content: \"\\f24c\"; }\n.bi-chat-left-dots::before { content: \"\\f24d\"; }\n.bi-chat-left-fill::before { content: \"\\f24e\"; }\n.bi-chat-left-quote-fill::before { content: \"\\f24f\"; }\n.bi-chat-left-quote::before { content: \"\\f250\"; }\n.bi-chat-left-text-fill::before { content: \"\\f251\"; }\n.bi-chat-left-text::before { content: \"\\f252\"; }\n.bi-chat-left::before { content: \"\\f253\"; }\n.bi-chat-quote-fill::before { content: \"\\f254\"; }\n.bi-chat-quote::before { content: \"\\f255\"; }\n.bi-chat-right-dots-fill::before { content: \"\\f256\"; }\n.bi-chat-right-dots::before { content: \"\\f257\"; }\n.bi-chat-right-fill::before { content: \"\\f258\"; }\n.bi-chat-right-quote-fill::before { content: \"\\f259\"; }\n.bi-chat-right-quote::before { content: \"\\f25a\"; }\n.bi-chat-right-text-fill::before { content: \"\\f25b\"; }\n.bi-chat-right-text::before { content: \"\\f25c\"; }\n.bi-chat-right::before { content: \"\\f25d\"; }\n.bi-chat-square-dots-fill::before { content: \"\\f25e\"; }\n.bi-chat-square-dots::before { content: \"\\f25f\"; }\n.bi-chat-square-fill::before { content: \"\\f260\"; }\n.bi-chat-square-quote-fill::before { content: \"\\f261\"; }\n.bi-chat-square-quote::before { content: \"\\f262\"; }\n.bi-chat-square-text-fill::before { content: \"\\f263\"; }\n.bi-chat-square-text::before { content: \"\\f264\"; }\n.bi-chat-square::before { content: \"\\f265\"; }\n.bi-chat-text-fill::before { content: \"\\f266\"; }\n.bi-chat-text::before { content: \"\\f267\"; }\n.bi-chat::before { content: \"\\f268\"; }\n.bi-check-all::before { content: \"\\f269\"; }\n.bi-check-circle-fill::before { content: \"\\f26a\"; }\n.bi-check-circle::before { content: \"\\f26b\"; }\n.bi-check-square-fill::before { content: \"\\f26c\"; }\n.bi-check-square::before { content: \"\\f26d\"; }\n.bi-check::before { content: \"\\f26e\"; }\n.bi-check2-all::before { content: \"\\f26f\"; }\n.bi-check2-circle::before { content: \"\\f270\"; }\n.bi-check2-square::before { content: \"\\f271\"; }\n.bi-check2::before { content: \"\\f272\"; }\n.bi-chevron-bar-contract::before { content: \"\\f273\"; }\n.bi-chevron-bar-down::before { content: \"\\f274\"; }\n.bi-chevron-bar-expand::before { content: \"\\f275\"; }\n.bi-chevron-bar-left::before { content: \"\\f276\"; }\n.bi-chevron-bar-right::before { content: \"\\f277\"; }\n.bi-chevron-bar-up::before { content: \"\\f278\"; }\n.bi-chevron-compact-down::before { content: \"\\f279\"; }\n.bi-chevron-compact-left::before { content: \"\\f27a\"; }\n.bi-chevron-compact-right::before { content: \"\\f27b\"; }\n.bi-chevron-compact-up::before { content: \"\\f27c\"; }\n.bi-chevron-contract::before { content: \"\\f27d\"; }\n.bi-chevron-double-down::before { content: \"\\f27e\"; }\n.bi-chevron-double-left::before { content: \"\\f27f\"; }\n.bi-chevron-double-right::before { content: \"\\f280\"; }\n.bi-chevron-double-up::before { content: \"\\f281\"; }\n.bi-chevron-down::before { content: \"\\f282\"; }\n.bi-chevron-expand::before { content: \"\\f283\"; }\n.bi-chevron-left::before { content: \"\\f284\"; }\n.bi-chevron-right::before { content: \"\\f285\"; }\n.bi-chevron-up::before { content: \"\\f286\"; }\n.bi-circle-fill::before { content: \"\\f287\"; }\n.bi-circle-half::before { content: \"\\f288\"; }\n.bi-circle-square::before { content: \"\\f289\"; }\n.bi-circle::before { content: \"\\f28a\"; }\n.bi-clipboard-check::before { content: \"\\f28b\"; }\n.bi-clipboard-data::before { content: \"\\f28c\"; }\n.bi-clipboard-minus::before { content: \"\\f28d\"; }\n.bi-clipboard-plus::before { content: \"\\f28e\"; }\n.bi-clipboard-x::before { content: \"\\f28f\"; }\n.bi-clipboard::before { content: \"\\f290\"; }\n.bi-clock-fill::before { content: \"\\f291\"; }\n.bi-clock-history::before { content: \"\\f292\"; }\n.bi-clock::before { content: \"\\f293\"; }\n.bi-cloud-arrow-down-fill::before { content: \"\\f294\"; }\n.bi-cloud-arrow-down::before { content: \"\\f295\"; }\n.bi-cloud-arrow-up-fill::before { content: \"\\f296\"; }\n.bi-cloud-arrow-up::before { content: \"\\f297\"; }\n.bi-cloud-check-fill::before { content: \"\\f298\"; }\n.bi-cloud-check::before { content: \"\\f299\"; }\n.bi-cloud-download-fill::before { content: \"\\f29a\"; }\n.bi-cloud-download::before { content: \"\\f29b\"; }\n.bi-cloud-drizzle-fill::before { content: \"\\f29c\"; }\n.bi-cloud-drizzle::before { content: \"\\f29d\"; }\n.bi-cloud-fill::before { content: \"\\f29e\"; }\n.bi-cloud-fog-fill::before { content: \"\\f29f\"; }\n.bi-cloud-fog::before { content: \"\\f2a0\"; }\n.bi-cloud-fog2-fill::before { content: \"\\f2a1\"; }\n.bi-cloud-fog2::before { content: \"\\f2a2\"; }\n.bi-cloud-hail-fill::before { content: \"\\f2a3\"; }\n.bi-cloud-hail::before { content: \"\\f2a4\"; }\n.bi-cloud-haze-1::before { content: \"\\f2a5\"; }\n.bi-cloud-haze-fill::before { content: \"\\f2a6\"; }\n.bi-cloud-haze::before { content: \"\\f2a7\"; }\n.bi-cloud-haze2-fill::before { content: \"\\f2a8\"; }\n.bi-cloud-lightning-fill::before { content: \"\\f2a9\"; }\n.bi-cloud-lightning-rain-fill::before { content: \"\\f2aa\"; }\n.bi-cloud-lightning-rain::before { content: \"\\f2ab\"; }\n.bi-cloud-lightning::before { content: \"\\f2ac\"; }\n.bi-cloud-minus-fill::before { content: \"\\f2ad\"; }\n.bi-cloud-minus::before { content: \"\\f2ae\"; }\n.bi-cloud-moon-fill::before { content: \"\\f2af\"; }\n.bi-cloud-moon::before { content: \"\\f2b0\"; }\n.bi-cloud-plus-fill::before { content: \"\\f2b1\"; }\n.bi-cloud-plus::before { content: \"\\f2b2\"; }\n.bi-cloud-rain-fill::before { content: \"\\f2b3\"; }\n.bi-cloud-rain-heavy-fill::before { content: \"\\f2b4\"; }\n.bi-cloud-rain-heavy::before { content: \"\\f2b5\"; }\n.bi-cloud-rain::before { content: \"\\f2b6\"; }\n.bi-cloud-slash-fill::before { content: \"\\f2b7\"; }\n.bi-cloud-slash::before { content: \"\\f2b8\"; }\n.bi-cloud-sleet-fill::before { content: \"\\f2b9\"; }\n.bi-cloud-sleet::before { content: \"\\f2ba\"; }\n.bi-cloud-snow-fill::before { content: \"\\f2bb\"; }\n.bi-cloud-snow::before { content: \"\\f2bc\"; }\n.bi-cloud-sun-fill::before { content: \"\\f2bd\"; }\n.bi-cloud-sun::before { content: \"\\f2be\"; }\n.bi-cloud-upload-fill::before { content: \"\\f2bf\"; }\n.bi-cloud-upload::before { content: \"\\f2c0\"; }\n.bi-cloud::before { content: \"\\f2c1\"; }\n.bi-clouds-fill::before { content: \"\\f2c2\"; }\n.bi-clouds::before { content: \"\\f2c3\"; }\n.bi-cloudy-fill::before { content: \"\\f2c4\"; }\n.bi-cloudy::before { content: \"\\f2c5\"; }\n.bi-code-slash::before { content: \"\\f2c6\"; }\n.bi-code-square::before { content: \"\\f2c7\"; }\n.bi-code::before { content: \"\\f2c8\"; }\n.bi-collection-fill::before { content: \"\\f2c9\"; }\n.bi-collection-play-fill::before { content: \"\\f2ca\"; }\n.bi-collection-play::before { content: \"\\f2cb\"; }\n.bi-collection::before { content: \"\\f2cc\"; }\n.bi-columns-gap::before { content: \"\\f2cd\"; }\n.bi-columns::before { content: \"\\f2ce\"; }\n.bi-command::before { content: \"\\f2cf\"; }\n.bi-compass-fill::before { content: \"\\f2d0\"; }\n.bi-compass::before { content: \"\\f2d1\"; }\n.bi-cone-striped::before { content: \"\\f2d2\"; }\n.bi-cone::before { content: \"\\f2d3\"; }\n.bi-controller::before { content: \"\\f2d4\"; }\n.bi-cpu-fill::before { content: \"\\f2d5\"; }\n.bi-cpu::before { content: \"\\f2d6\"; }\n.bi-credit-card-2-back-fill::before { content: \"\\f2d7\"; }\n.bi-credit-card-2-back::before { content: \"\\f2d8\"; }\n.bi-credit-card-2-front-fill::before { content: \"\\f2d9\"; }\n.bi-credit-card-2-front::before { content: \"\\f2da\"; }\n.bi-credit-card-fill::before { content: \"\\f2db\"; }\n.bi-credit-card::before { content: \"\\f2dc\"; }\n.bi-crop::before { content: \"\\f2dd\"; }\n.bi-cup-fill::before { content: \"\\f2de\"; }\n.bi-cup-straw::before { content: \"\\f2df\"; }\n.bi-cup::before { content: \"\\f2e0\"; }\n.bi-cursor-fill::before { content: \"\\f2e1\"; }\n.bi-cursor-text::before { content: \"\\f2e2\"; }\n.bi-cursor::before { content: \"\\f2e3\"; }\n.bi-dash-circle-dotted::before { content: \"\\f2e4\"; }\n.bi-dash-circle-fill::before { content: \"\\f2e5\"; }\n.bi-dash-circle::before { content: \"\\f2e6\"; }\n.bi-dash-square-dotted::before { content: \"\\f2e7\"; }\n.bi-dash-square-fill::before { content: \"\\f2e8\"; }\n.bi-dash-square::before { content: \"\\f2e9\"; }\n.bi-dash::before { content: \"\\f2ea\"; }\n.bi-diagram-2-fill::before { content: \"\\f2eb\"; }\n.bi-diagram-2::before { content: \"\\f2ec\"; }\n.bi-diagram-3-fill::before { content: \"\\f2ed\"; }\n.bi-diagram-3::before { content: \"\\f2ee\"; }\n.bi-diamond-fill::before { content: \"\\f2ef\"; }\n.bi-diamond-half::before { content: \"\\f2f0\"; }\n.bi-diamond::before { content: \"\\f2f1\"; }\n.bi-dice-1-fill::before { content: \"\\f2f2\"; }\n.bi-dice-1::before { content: \"\\f2f3\"; }\n.bi-dice-2-fill::before { content: \"\\f2f4\"; }\n.bi-dice-2::before { content: \"\\f2f5\"; }\n.bi-dice-3-fill::before { content: \"\\f2f6\"; }\n.bi-dice-3::before { content: \"\\f2f7\"; }\n.bi-dice-4-fill::before { content: \"\\f2f8\"; }\n.bi-dice-4::before { content: \"\\f2f9\"; }\n.bi-dice-5-fill::before { content: \"\\f2fa\"; }\n.bi-dice-5::before { content: \"\\f2fb\"; }\n.bi-dice-6-fill::before { content: \"\\f2fc\"; }\n.bi-dice-6::before { content: \"\\f2fd\"; }\n.bi-disc-fill::before { content: \"\\f2fe\"; }\n.bi-disc::before { content: \"\\f2ff\"; }\n.bi-discord::before { content: \"\\f300\"; }\n.bi-display-fill::before { content: \"\\f301\"; }\n.bi-display::before { content: \"\\f302\"; }\n.bi-distribute-horizontal::before { content: \"\\f303\"; }\n.bi-distribute-vertical::before { content: \"\\f304\"; }\n.bi-door-closed-fill::before { content: \"\\f305\"; }\n.bi-door-closed::before { content: \"\\f306\"; }\n.bi-door-open-fill::before { content: \"\\f307\"; }\n.bi-door-open::before { content: \"\\f308\"; }\n.bi-dot::before { content: \"\\f309\"; }\n.bi-download::before { content: \"\\f30a\"; }\n.bi-droplet-fill::before { content: \"\\f30b\"; }\n.bi-droplet-half::before { content: \"\\f30c\"; }\n.bi-droplet::before { content: \"\\f30d\"; }\n.bi-earbuds::before { content: \"\\f30e\"; }\n.bi-easel-fill::before { content: \"\\f30f\"; }\n.bi-easel::before { content: \"\\f310\"; }\n.bi-egg-fill::before { content: \"\\f311\"; }\n.bi-egg-fried::before { content: \"\\f312\"; }\n.bi-egg::before { content: \"\\f313\"; }\n.bi-eject-fill::before { content: \"\\f314\"; }\n.bi-eject::before { content: \"\\f315\"; }\n.bi-emoji-angry-fill::before { content: \"\\f316\"; }\n.bi-emoji-angry::before { content: \"\\f317\"; }\n.bi-emoji-dizzy-fill::before { content: \"\\f318\"; }\n.bi-emoji-dizzy::before { content: \"\\f319\"; }\n.bi-emoji-expressionless-fill::before { content: \"\\f31a\"; }\n.bi-emoji-expressionless::before { content: \"\\f31b\"; }\n.bi-emoji-frown-fill::before { content: \"\\f31c\"; }\n.bi-emoji-frown::before { content: \"\\f31d\"; }\n.bi-emoji-heart-eyes-fill::before { content: \"\\f31e\"; }\n.bi-emoji-heart-eyes::before { content: \"\\f31f\"; }\n.bi-emoji-laughing-fill::before { content: \"\\f320\"; }\n.bi-emoji-laughing::before { content: \"\\f321\"; }\n.bi-emoji-neutral-fill::before { content: \"\\f322\"; }\n.bi-emoji-neutral::before { content: \"\\f323\"; }\n.bi-emoji-smile-fill::before { content: \"\\f324\"; }\n.bi-emoji-smile-upside-down-fill::before { content: \"\\f325\"; }\n.bi-emoji-smile-upside-down::before { content: \"\\f326\"; }\n.bi-emoji-smile::before { content: \"\\f327\"; }\n.bi-emoji-sunglasses-fill::before { content: \"\\f328\"; }\n.bi-emoji-sunglasses::before { content: \"\\f329\"; }\n.bi-emoji-wink-fill::before { content: \"\\f32a\"; }\n.bi-emoji-wink::before { content: \"\\f32b\"; }\n.bi-envelope-fill::before { content: \"\\f32c\"; }\n.bi-envelope-open-fill::before { content: \"\\f32d\"; }\n.bi-envelope-open::before { content: \"\\f32e\"; }\n.bi-envelope::before { content: \"\\f32f\"; }\n.bi-eraser-fill::before { content: \"\\f330\"; }\n.bi-eraser::before { content: \"\\f331\"; }\n.bi-exclamation-circle-fill::before { content: \"\\f332\"; }\n.bi-exclamation-circle::before { content: \"\\f333\"; }\n.bi-exclamation-diamond-fill::before { content: \"\\f334\"; }\n.bi-exclamation-diamond::before { content: \"\\f335\"; }\n.bi-exclamation-octagon-fill::before { content: \"\\f336\"; }\n.bi-exclamation-octagon::before { content: \"\\f337\"; }\n.bi-exclamation-square-fill::before { content: \"\\f338\"; }\n.bi-exclamation-square::before { content: \"\\f339\"; }\n.bi-exclamation-triangle-fill::before { content: \"\\f33a\"; }\n.bi-exclamation-triangle::before { content: \"\\f33b\"; }\n.bi-exclamation::before { content: \"\\f33c\"; }\n.bi-exclude::before { content: \"\\f33d\"; }\n.bi-eye-fill::before { content: \"\\f33e\"; }\n.bi-eye-slash-fill::before { content: \"\\f33f\"; }\n.bi-eye-slash::before { content: \"\\f340\"; }\n.bi-eye::before { content: \"\\f341\"; }\n.bi-eyedropper::before { content: \"\\f342\"; }\n.bi-eyeglasses::before { content: \"\\f343\"; }\n.bi-facebook::before { content: \"\\f344\"; }\n.bi-file-arrow-down-fill::before { content: \"\\f345\"; }\n.bi-file-arrow-down::before { content: \"\\f346\"; }\n.bi-file-arrow-up-fill::before { content: \"\\f347\"; }\n.bi-file-arrow-up::before { content: \"\\f348\"; }\n.bi-file-bar-graph-fill::before { content: \"\\f349\"; }\n.bi-file-bar-graph::before { content: \"\\f34a\"; }\n.bi-file-binary-fill::before { content: \"\\f34b\"; }\n.bi-file-binary::before { content: \"\\f34c\"; }\n.bi-file-break-fill::before { content: \"\\f34d\"; }\n.bi-file-break::before { content: \"\\f34e\"; }\n.bi-file-check-fill::before { content: \"\\f34f\"; }\n.bi-file-check::before { content: \"\\f350\"; }\n.bi-file-code-fill::before { content: \"\\f351\"; }\n.bi-file-code::before { content: \"\\f352\"; }\n.bi-file-diff-fill::before { content: \"\\f353\"; }\n.bi-file-diff::before { content: \"\\f354\"; }\n.bi-file-earmark-arrow-down-fill::before { content: \"\\f355\"; }\n.bi-file-earmark-arrow-down::before { content: \"\\f356\"; }\n.bi-file-earmark-arrow-up-fill::before { content: \"\\f357\"; }\n.bi-file-earmark-arrow-up::before { content: \"\\f358\"; }\n.bi-file-earmark-bar-graph-fill::before { content: \"\\f359\"; }\n.bi-file-earmark-bar-graph::before { content: \"\\f35a\"; }\n.bi-file-earmark-binary-fill::before { content: \"\\f35b\"; }\n.bi-file-earmark-binary::before { content: \"\\f35c\"; }\n.bi-file-earmark-break-fill::before { content: \"\\f35d\"; }\n.bi-file-earmark-break::before { content: \"\\f35e\"; }\n.bi-file-earmark-check-fill::before { content: \"\\f35f\"; }\n.bi-file-earmark-check::before { content: \"\\f360\"; }\n.bi-file-earmark-code-fill::before { content: \"\\f361\"; }\n.bi-file-earmark-code::before { content: \"\\f362\"; }\n.bi-file-earmark-diff-fill::before { content: \"\\f363\"; }\n.bi-file-earmark-diff::before { content: \"\\f364\"; }\n.bi-file-earmark-easel-fill::before { content: \"\\f365\"; }\n.bi-file-earmark-easel::before { content: \"\\f366\"; }\n.bi-file-earmark-excel-fill::before { content: \"\\f367\"; }\n.bi-file-earmark-excel::before { content: \"\\f368\"; }\n.bi-file-earmark-fill::before { content: \"\\f369\"; }\n.bi-file-earmark-font-fill::before { content: \"\\f36a\"; }\n.bi-file-earmark-font::before { content: \"\\f36b\"; }\n.bi-file-earmark-image-fill::before { content: \"\\f36c\"; }\n.bi-file-earmark-image::before { content: \"\\f36d\"; }\n.bi-file-earmark-lock-fill::before { content: \"\\f36e\"; }\n.bi-file-earmark-lock::before { content: \"\\f36f\"; }\n.bi-file-earmark-lock2-fill::before { content: \"\\f370\"; }\n.bi-file-earmark-lock2::before { content: \"\\f371\"; }\n.bi-file-earmark-medical-fill::before { content: \"\\f372\"; }\n.bi-file-earmark-medical::before { content: \"\\f373\"; }\n.bi-file-earmark-minus-fill::before { content: \"\\f374\"; }\n.bi-file-earmark-minus::before { content: \"\\f375\"; }\n.bi-file-earmark-music-fill::before { content: \"\\f376\"; }\n.bi-file-earmark-music::before { content: \"\\f377\"; }\n.bi-file-earmark-person-fill::before { content: \"\\f378\"; }\n.bi-file-earmark-person::before { content: \"\\f379\"; }\n.bi-file-earmark-play-fill::before { content: \"\\f37a\"; }\n.bi-file-earmark-play::before { content: \"\\f37b\"; }\n.bi-file-earmark-plus-fill::before { content: \"\\f37c\"; }\n.bi-file-earmark-plus::before { content: \"\\f37d\"; }\n.bi-file-earmark-post-fill::before { content: \"\\f37e\"; }\n.bi-file-earmark-post::before { content: \"\\f37f\"; }\n.bi-file-earmark-ppt-fill::before { content: \"\\f380\"; }\n.bi-file-earmark-ppt::before { content: \"\\f381\"; }\n.bi-file-earmark-richtext-fill::before { content: \"\\f382\"; }\n.bi-file-earmark-richtext::before { content: \"\\f383\"; }\n.bi-file-earmark-ruled-fill::before { content: \"\\f384\"; }\n.bi-file-earmark-ruled::before { content: \"\\f385\"; }\n.bi-file-earmark-slides-fill::before { content: \"\\f386\"; }\n.bi-file-earmark-slides::before { content: \"\\f387\"; }\n.bi-file-earmark-spreadsheet-fill::before { content: \"\\f388\"; }\n.bi-file-earmark-spreadsheet::before { content: \"\\f389\"; }\n.bi-file-earmark-text-fill::before { content: \"\\f38a\"; }\n.bi-file-earmark-text::before { content: \"\\f38b\"; }\n.bi-file-earmark-word-fill::before { content: \"\\f38c\"; }\n.bi-file-earmark-word::before { content: \"\\f38d\"; }\n.bi-file-earmark-x-fill::before { content: \"\\f38e\"; }\n.bi-file-earmark-x::before { content: \"\\f38f\"; }\n.bi-file-earmark-zip-fill::before { content: \"\\f390\"; }\n.bi-file-earmark-zip::before { content: \"\\f391\"; }\n.bi-file-earmark::before { content: \"\\f392\"; }\n.bi-file-easel-fill::before { content: \"\\f393\"; }\n.bi-file-easel::before { content: \"\\f394\"; }\n.bi-file-excel-fill::before { content: \"\\f395\"; }\n.bi-file-excel::before { content: \"\\f396\"; }\n.bi-file-fill::before { content: \"\\f397\"; }\n.bi-file-font-fill::before { content: \"\\f398\"; }\n.bi-file-font::before { content: \"\\f399\"; }\n.bi-file-image-fill::before { content: \"\\f39a\"; }\n.bi-file-image::before { content: \"\\f39b\"; }\n.bi-file-lock-fill::before { content: \"\\f39c\"; }\n.bi-file-lock::before { content: \"\\f39d\"; }\n.bi-file-lock2-fill::before { content: \"\\f39e\"; }\n.bi-file-lock2::before { content: \"\\f39f\"; }\n.bi-file-medical-fill::before { content: \"\\f3a0\"; }\n.bi-file-medical::before { content: \"\\f3a1\"; }\n.bi-file-minus-fill::before { content: \"\\f3a2\"; }\n.bi-file-minus::before { content: \"\\f3a3\"; }\n.bi-file-music-fill::before { content: \"\\f3a4\"; }\n.bi-file-music::before { content: \"\\f3a5\"; }\n.bi-file-person-fill::before { content: \"\\f3a6\"; }\n.bi-file-person::before { content: \"\\f3a7\"; }\n.bi-file-play-fill::before { content: \"\\f3a8\"; }\n.bi-file-play::before { content: \"\\f3a9\"; }\n.bi-file-plus-fill::before { content: \"\\f3aa\"; }\n.bi-file-plus::before { content: \"\\f3ab\"; }\n.bi-file-post-fill::before { content: \"\\f3ac\"; }\n.bi-file-post::before { content: \"\\f3ad\"; }\n.bi-file-ppt-fill::before { content: \"\\f3ae\"; }\n.bi-file-ppt::before { content: \"\\f3af\"; }\n.bi-file-richtext-fill::before { content: \"\\f3b0\"; }\n.bi-file-richtext::before { content: \"\\f3b1\"; }\n.bi-file-ruled-fill::before { content: \"\\f3b2\"; }\n.bi-file-ruled::before { content: \"\\f3b3\"; }\n.bi-file-slides-fill::before { content: \"\\f3b4\"; }\n.bi-file-slides::before { content: \"\\f3b5\"; }\n.bi-file-spreadsheet-fill::before { content: \"\\f3b6\"; }\n.bi-file-spreadsheet::before { content: \"\\f3b7\"; }\n.bi-file-text-fill::before { content: \"\\f3b8\"; }\n.bi-file-text::before { content: \"\\f3b9\"; }\n.bi-file-word-fill::before { content: \"\\f3ba\"; }\n.bi-file-word::before { content: \"\\f3bb\"; }\n.bi-file-x-fill::before { content: \"\\f3bc\"; }\n.bi-file-x::before { content: \"\\f3bd\"; }\n.bi-file-zip-fill::before { content: \"\\f3be\"; }\n.bi-file-zip::before { content: \"\\f3bf\"; }\n.bi-file::before { content: \"\\f3c0\"; }\n.bi-files-alt::before { content: \"\\f3c1\"; }\n.bi-files::before { content: \"\\f3c2\"; }\n.bi-film::before { content: \"\\f3c3\"; }\n.bi-filter-circle-fill::before { content: \"\\f3c4\"; }\n.bi-filter-circle::before { content: \"\\f3c5\"; }\n.bi-filter-left::before { content: \"\\f3c6\"; }\n.bi-filter-right::before { content: \"\\f3c7\"; }\n.bi-filter-square-fill::before { content: \"\\f3c8\"; }\n.bi-filter-square::before { content: \"\\f3c9\"; }\n.bi-filter::before { content: \"\\f3ca\"; }\n.bi-flag-fill::before { content: \"\\f3cb\"; }\n.bi-flag::before { content: \"\\f3cc\"; }\n.bi-flower1::before { content: \"\\f3cd\"; }\n.bi-flower2::before { content: \"\\f3ce\"; }\n.bi-flower3::before { content: \"\\f3cf\"; }\n.bi-folder-check::before { content: \"\\f3d0\"; }\n.bi-folder-fill::before { content: \"\\f3d1\"; }\n.bi-folder-minus::before { content: \"\\f3d2\"; }\n.bi-folder-plus::before { content: \"\\f3d3\"; }\n.bi-folder-symlink-fill::before { content: \"\\f3d4\"; }\n.bi-folder-symlink::before { content: \"\\f3d5\"; }\n.bi-folder-x::before { content: \"\\f3d6\"; }\n.bi-folder::before { content: \"\\f3d7\"; }\n.bi-folder2-open::before { content: \"\\f3d8\"; }\n.bi-folder2::before { content: \"\\f3d9\"; }\n.bi-fonts::before { content: \"\\f3da\"; }\n.bi-forward-fill::before { content: \"\\f3db\"; }\n.bi-forward::before { content: \"\\f3dc\"; }\n.bi-front::before { content: \"\\f3dd\"; }\n.bi-fullscreen-exit::before { content: \"\\f3de\"; }\n.bi-fullscreen::before { content: \"\\f3df\"; }\n.bi-funnel-fill::before { content: \"\\f3e0\"; }\n.bi-funnel::before { content: \"\\f3e1\"; }\n.bi-gear-fill::before { content: \"\\f3e2\"; }\n.bi-gear-wide-connected::before { content: \"\\f3e3\"; }\n.bi-gear-wide::before { content: \"\\f3e4\"; }\n.bi-gear::before { content: \"\\f3e5\"; }\n.bi-gem::before { content: \"\\f3e6\"; }\n.bi-geo-alt-fill::before { content: \"\\f3e7\"; }\n.bi-geo-alt::before { content: \"\\f3e8\"; }\n.bi-geo-fill::before { content: \"\\f3e9\"; }\n.bi-geo::before { content: \"\\f3ea\"; }\n.bi-gift-fill::before { content: \"\\f3eb\"; }\n.bi-gift::before { content: \"\\f3ec\"; }\n.bi-github::before { content: \"\\f3ed\"; }\n.bi-globe::before { content: \"\\f3ee\"; }\n.bi-globe2::before { content: \"\\f3ef\"; }\n.bi-google::before { content: \"\\f3f0\"; }\n.bi-graph-down::before { content: \"\\f3f1\"; }\n.bi-graph-up::before { content: \"\\f3f2\"; }\n.bi-grid-1x2-fill::before { content: \"\\f3f3\"; }\n.bi-grid-1x2::before { content: \"\\f3f4\"; }\n.bi-grid-3x2-gap-fill::before { content: \"\\f3f5\"; }\n.bi-grid-3x2-gap::before { content: \"\\f3f6\"; }\n.bi-grid-3x2::before { content: \"\\f3f7\"; }\n.bi-grid-3x3-gap-fill::before { content: \"\\f3f8\"; }\n.bi-grid-3x3-gap::before { content: \"\\f3f9\"; }\n.bi-grid-3x3::before { content: \"\\f3fa\"; }\n.bi-grid-fill::before { content: \"\\f3fb\"; }\n.bi-grid::before { content: \"\\f3fc\"; }\n.bi-grip-horizontal::before { content: \"\\f3fd\"; }\n.bi-grip-vertical::before { content: \"\\f3fe\"; }\n.bi-hammer::before { content: \"\\f3ff\"; }\n.bi-hand-index-fill::before { content: \"\\f400\"; }\n.bi-hand-index-thumb-fill::before { content: \"\\f401\"; }\n.bi-hand-index-thumb::before { content: \"\\f402\"; }\n.bi-hand-index::before { content: \"\\f403\"; }\n.bi-hand-thumbs-down-fill::before { content: \"\\f404\"; }\n.bi-hand-thumbs-down::before { content: \"\\f405\"; }\n.bi-hand-thumbs-up-fill::before { content: \"\\f406\"; }\n.bi-hand-thumbs-up::before { content: \"\\f407\"; }\n.bi-handbag-fill::before { content: \"\\f408\"; }\n.bi-handbag::before { content: \"\\f409\"; }\n.bi-hash::before { content: \"\\f40a\"; }\n.bi-hdd-fill::before { content: \"\\f40b\"; }\n.bi-hdd-network-fill::before { content: \"\\f40c\"; }\n.bi-hdd-network::before { content: \"\\f40d\"; }\n.bi-hdd-rack-fill::before { content: \"\\f40e\"; }\n.bi-hdd-rack::before { content: \"\\f40f\"; }\n.bi-hdd-stack-fill::before { content: \"\\f410\"; }\n.bi-hdd-stack::before { content: \"\\f411\"; }\n.bi-hdd::before { content: \"\\f412\"; }\n.bi-headphones::before { content: \"\\f413\"; }\n.bi-headset::before { content: \"\\f414\"; }\n.bi-heart-fill::before { content: \"\\f415\"; }\n.bi-heart-half::before { content: \"\\f416\"; }\n.bi-heart::before { content: \"\\f417\"; }\n.bi-heptagon-fill::before { content: \"\\f418\"; }\n.bi-heptagon-half::before { content: \"\\f419\"; }\n.bi-heptagon::before { content: \"\\f41a\"; }\n.bi-hexagon-fill::before { content: \"\\f41b\"; }\n.bi-hexagon-half::before { content: \"\\f41c\"; }\n.bi-hexagon::before { content: \"\\f41d\"; }\n.bi-hourglass-bottom::before { content: \"\\f41e\"; }\n.bi-hourglass-split::before { content: \"\\f41f\"; }\n.bi-hourglass-top::before { content: \"\\f420\"; }\n.bi-hourglass::before { content: \"\\f421\"; }\n.bi-house-door-fill::before { content: \"\\f422\"; }\n.bi-house-door::before { content: \"\\f423\"; }\n.bi-house-fill::before { content: \"\\f424\"; }\n.bi-house::before { content: \"\\f425\"; }\n.bi-hr::before { content: \"\\f426\"; }\n.bi-hurricane::before { content: \"\\f427\"; }\n.bi-image-alt::before { content: \"\\f428\"; }\n.bi-image-fill::before { content: \"\\f429\"; }\n.bi-image::before { content: \"\\f42a\"; }\n.bi-images::before { content: \"\\f42b\"; }\n.bi-inbox-fill::before { content: \"\\f42c\"; }\n.bi-inbox::before { content: \"\\f42d\"; }\n.bi-inboxes-fill::before { content: \"\\f42e\"; }\n.bi-inboxes::before { content: \"\\f42f\"; }\n.bi-info-circle-fill::before { content: \"\\f430\"; }\n.bi-info-circle::before { content: \"\\f431\"; }\n.bi-info-square-fill::before { content: \"\\f432\"; }\n.bi-info-square::before { content: \"\\f433\"; }\n.bi-info::before { content: \"\\f434\"; }\n.bi-input-cursor-text::before { content: \"\\f435\"; }\n.bi-input-cursor::before { content: \"\\f436\"; }\n.bi-instagram::before { content: \"\\f437\"; }\n.bi-intersect::before { content: \"\\f438\"; }\n.bi-journal-album::before { content: \"\\f439\"; }\n.bi-journal-arrow-down::before { content: \"\\f43a\"; }\n.bi-journal-arrow-up::before { content: \"\\f43b\"; }\n.bi-journal-bookmark-fill::before { content: \"\\f43c\"; }\n.bi-journal-bookmark::before { content: \"\\f43d\"; }\n.bi-journal-check::before { content: \"\\f43e\"; }\n.bi-journal-code::before { content: \"\\f43f\"; }\n.bi-journal-medical::before { content: \"\\f440\"; }\n.bi-journal-minus::before { content: \"\\f441\"; }\n.bi-journal-plus::before { content: \"\\f442\"; }\n.bi-journal-richtext::before { content: \"\\f443\"; }\n.bi-journal-text::before { content: \"\\f444\"; }\n.bi-journal-x::before { content: \"\\f445\"; }\n.bi-journal::before { content: \"\\f446\"; }\n.bi-journals::before { content: \"\\f447\"; }\n.bi-joystick::before { content: \"\\f448\"; }\n.bi-justify-left::before { content: \"\\f449\"; }\n.bi-justify-right::before { content: \"\\f44a\"; }\n.bi-justify::before { content: \"\\f44b\"; }\n.bi-kanban-fill::before { content: \"\\f44c\"; }\n.bi-kanban::before { content: \"\\f44d\"; }\n.bi-key-fill::before { content: \"\\f44e\"; }\n.bi-key::before { content: \"\\f44f\"; }\n.bi-keyboard-fill::before { content: \"\\f450\"; }\n.bi-keyboard::before { content: \"\\f451\"; }\n.bi-ladder::before { content: \"\\f452\"; }\n.bi-lamp-fill::before { content: \"\\f453\"; }\n.bi-lamp::before { content: \"\\f454\"; }\n.bi-laptop-fill::before { content: \"\\f455\"; }\n.bi-laptop::before { content: \"\\f456\"; }\n.bi-layer-backward::before { content: \"\\f457\"; }\n.bi-layer-forward::before { content: \"\\f458\"; }\n.bi-layers-fill::before { content: \"\\f459\"; }\n.bi-layers-half::before { content: \"\\f45a\"; }\n.bi-layers::before { content: \"\\f45b\"; }\n.bi-layout-sidebar-inset-reverse::before { content: \"\\f45c\"; }\n.bi-layout-sidebar-inset::before { content: \"\\f45d\"; }\n.bi-layout-sidebar-reverse::before { content: \"\\f45e\"; }\n.bi-layout-sidebar::before { content: \"\\f45f\"; }\n.bi-layout-split::before { content: \"\\f460\"; }\n.bi-layout-text-sidebar-reverse::before { content: \"\\f461\"; }\n.bi-layout-text-sidebar::before { content: \"\\f462\"; }\n.bi-layout-text-window-reverse::before { content: \"\\f463\"; }\n.bi-layout-text-window::before { content: \"\\f464\"; }\n.bi-layout-three-columns::before { content: \"\\f465\"; }\n.bi-layout-wtf::before { content: \"\\f466\"; }\n.bi-life-preserver::before { content: \"\\f467\"; }\n.bi-lightbulb-fill::before { content: \"\\f468\"; }\n.bi-lightbulb-off-fill::before { content: \"\\f469\"; }\n.bi-lightbulb-off::before { content: \"\\f46a\"; }\n.bi-lightbulb::before { content: \"\\f46b\"; }\n.bi-lightning-charge-fill::before { content: \"\\f46c\"; }\n.bi-lightning-charge::before { content: \"\\f46d\"; }\n.bi-lightning-fill::before { content: \"\\f46e\"; }\n.bi-lightning::before { content: \"\\f46f\"; }\n.bi-link-45deg::before { content: \"\\f470\"; }\n.bi-link::before { content: \"\\f471\"; }\n.bi-linkedin::before { content: \"\\f472\"; }\n.bi-list-check::before { content: \"\\f473\"; }\n.bi-list-nested::before { content: \"\\f474\"; }\n.bi-list-ol::before { content: \"\\f475\"; }\n.bi-list-stars::before { content: \"\\f476\"; }\n.bi-list-task::before { content: \"\\f477\"; }\n.bi-list-ul::before { content: \"\\f478\"; }\n.bi-list::before { content: \"\\f479\"; }\n.bi-lock-fill::before { content: \"\\f47a\"; }\n.bi-lock::before { content: \"\\f47b\"; }\n.bi-mailbox::before { content: \"\\f47c\"; }\n.bi-mailbox2::before { content: \"\\f47d\"; }\n.bi-map-fill::before { content: \"\\f47e\"; }\n.bi-map::before { content: \"\\f47f\"; }\n.bi-markdown-fill::before { content: \"\\f480\"; }\n.bi-markdown::before { content: \"\\f481\"; }\n.bi-mask::before { content: \"\\f482\"; }\n.bi-megaphone-fill::before { content: \"\\f483\"; }\n.bi-megaphone::before { content: \"\\f484\"; }\n.bi-menu-app-fill::before { content: \"\\f485\"; }\n.bi-menu-app::before { content: \"\\f486\"; }\n.bi-menu-button-fill::before { content: \"\\f487\"; }\n.bi-menu-button-wide-fill::before { content: \"\\f488\"; }\n.bi-menu-button-wide::before { content: \"\\f489\"; }\n.bi-menu-button::before { content: \"\\f48a\"; }\n.bi-menu-down::before { content: \"\\f48b\"; }\n.bi-menu-up::before { content: \"\\f48c\"; }\n.bi-mic-fill::before { content: \"\\f48d\"; }\n.bi-mic-mute-fill::before { content: \"\\f48e\"; }\n.bi-mic-mute::before { content: \"\\f48f\"; }\n.bi-mic::before { content: \"\\f490\"; }\n.bi-minecart-loaded::before { content: \"\\f491\"; }\n.bi-minecart::before { content: \"\\f492\"; }\n.bi-moisture::before { content: \"\\f493\"; }\n.bi-moon-fill::before { content: \"\\f494\"; }\n.bi-moon-stars-fill::before { content: \"\\f495\"; }\n.bi-moon-stars::before { content: \"\\f496\"; }\n.bi-moon::before { content: \"\\f497\"; }\n.bi-mouse-fill::before { content: \"\\f498\"; }\n.bi-mouse::before { content: \"\\f499\"; }\n.bi-mouse2-fill::before { content: \"\\f49a\"; }\n.bi-mouse2::before { content: \"\\f49b\"; }\n.bi-mouse3-fill::before { content: \"\\f49c\"; }\n.bi-mouse3::before { content: \"\\f49d\"; }\n.bi-music-note-beamed::before { content: \"\\f49e\"; }\n.bi-music-note-list::before { content: \"\\f49f\"; }\n.bi-music-note::before { content: \"\\f4a0\"; }\n.bi-music-player-fill::before { content: \"\\f4a1\"; }\n.bi-music-player::before { content: \"\\f4a2\"; }\n.bi-newspaper::before { content: \"\\f4a3\"; }\n.bi-node-minus-fill::before { content: \"\\f4a4\"; }\n.bi-node-minus::before { content: \"\\f4a5\"; }\n.bi-node-plus-fill::before { content: \"\\f4a6\"; }\n.bi-node-plus::before { content: \"\\f4a7\"; }\n.bi-nut-fill::before { content: \"\\f4a8\"; }\n.bi-nut::before { content: \"\\f4a9\"; }\n.bi-octagon-fill::before { content: \"\\f4aa\"; }\n.bi-octagon-half::before { content: \"\\f4ab\"; }\n.bi-octagon::before { content: \"\\f4ac\"; }\n.bi-option::before { content: \"\\f4ad\"; }\n.bi-outlet::before { content: \"\\f4ae\"; }\n.bi-paint-bucket::before { content: \"\\f4af\"; }\n.bi-palette-fill::before { content: \"\\f4b0\"; }\n.bi-palette::before { content: \"\\f4b1\"; }\n.bi-palette2::before { content: \"\\f4b2\"; }\n.bi-paperclip::before { content: \"\\f4b3\"; }\n.bi-paragraph::before { content: \"\\f4b4\"; }\n.bi-patch-check-fill::before { content: \"\\f4b5\"; }\n.bi-patch-check::before { content: \"\\f4b6\"; }\n.bi-patch-exclamation-fill::before { content: \"\\f4b7\"; }\n.bi-patch-exclamation::before { content: \"\\f4b8\"; }\n.bi-patch-minus-fill::before { content: \"\\f4b9\"; }\n.bi-patch-minus::before { content: \"\\f4ba\"; }\n.bi-patch-plus-fill::before { content: \"\\f4bb\"; }\n.bi-patch-plus::before { content: \"\\f4bc\"; }\n.bi-patch-question-fill::before { content: \"\\f4bd\"; }\n.bi-patch-question::before { content: \"\\f4be\"; }\n.bi-pause-btn-fill::before { content: \"\\f4bf\"; }\n.bi-pause-btn::before { content: \"\\f4c0\"; }\n.bi-pause-circle-fill::before { content: \"\\f4c1\"; }\n.bi-pause-circle::before { content: \"\\f4c2\"; }\n.bi-pause-fill::before { content: \"\\f4c3\"; }\n.bi-pause::before { content: \"\\f4c4\"; }\n.bi-peace-fill::before { content: \"\\f4c5\"; }\n.bi-peace::before { content: \"\\f4c6\"; }\n.bi-pen-fill::before { content: \"\\f4c7\"; }\n.bi-pen::before { content: \"\\f4c8\"; }\n.bi-pencil-fill::before { content: \"\\f4c9\"; }\n.bi-pencil-square::before { content: \"\\f4ca\"; }\n.bi-pencil::before { content: \"\\f4cb\"; }\n.bi-pentagon-fill::before { content: \"\\f4cc\"; }\n.bi-pentagon-half::before { content: \"\\f4cd\"; }\n.bi-pentagon::before { content: \"\\f4ce\"; }\n.bi-people-fill::before { content: \"\\f4cf\"; }\n.bi-people::before { content: \"\\f4d0\"; }\n.bi-percent::before { content: \"\\f4d1\"; }\n.bi-person-badge-fill::before { content: \"\\f4d2\"; }\n.bi-person-badge::before { content: \"\\f4d3\"; }\n.bi-person-bounding-box::before { content: \"\\f4d4\"; }\n.bi-person-check-fill::before { content: \"\\f4d5\"; }\n.bi-person-check::before { content: \"\\f4d6\"; }\n.bi-person-circle::before { content: \"\\f4d7\"; }\n.bi-person-dash-fill::before { content: \"\\f4d8\"; }\n.bi-person-dash::before { content: \"\\f4d9\"; }\n.bi-person-fill::before { content: \"\\f4da\"; }\n.bi-person-lines-fill::before { content: \"\\f4db\"; }\n.bi-person-plus-fill::before { content: \"\\f4dc\"; }\n.bi-person-plus::before { content: \"\\f4dd\"; }\n.bi-person-square::before { content: \"\\f4de\"; }\n.bi-person-x-fill::before { content: \"\\f4df\"; }\n.bi-person-x::before { content: \"\\f4e0\"; }\n.bi-person::before { content: \"\\f4e1\"; }\n.bi-phone-fill::before { content: \"\\f4e2\"; }\n.bi-phone-landscape-fill::before { content: \"\\f4e3\"; }\n.bi-phone-landscape::before { content: \"\\f4e4\"; }\n.bi-phone-vibrate-fill::before { content: \"\\f4e5\"; }\n.bi-phone-vibrate::before { content: \"\\f4e6\"; }\n.bi-phone::before { content: \"\\f4e7\"; }\n.bi-pie-chart-fill::before { content: \"\\f4e8\"; }\n.bi-pie-chart::before { content: \"\\f4e9\"; }\n.bi-pin-angle-fill::before { content: \"\\f4ea\"; }\n.bi-pin-angle::before { content: \"\\f4eb\"; }\n.bi-pin-fill::before { content: \"\\f4ec\"; }\n.bi-pin::before { content: \"\\f4ed\"; }\n.bi-pip-fill::before { content: \"\\f4ee\"; }\n.bi-pip::before { content: \"\\f4ef\"; }\n.bi-play-btn-fill::before { content: \"\\f4f0\"; }\n.bi-play-btn::before { content: \"\\f4f1\"; }\n.bi-play-circle-fill::before { content: \"\\f4f2\"; }\n.bi-play-circle::before { content: \"\\f4f3\"; }\n.bi-play-fill::before { content: \"\\f4f4\"; }\n.bi-play::before { content: \"\\f4f5\"; }\n.bi-plug-fill::before { content: \"\\f4f6\"; }\n.bi-plug::before { content: \"\\f4f7\"; }\n.bi-plus-circle-dotted::before { content: \"\\f4f8\"; }\n.bi-plus-circle-fill::before { content: \"\\f4f9\"; }\n.bi-plus-circle::before { content: \"\\f4fa\"; }\n.bi-plus-square-dotted::before { content: \"\\f4fb\"; }\n.bi-plus-square-fill::before { content: \"\\f4fc\"; }\n.bi-plus-square::before { content: \"\\f4fd\"; }\n.bi-plus::before { content: \"\\f4fe\"; }\n.bi-power::before { content: \"\\f4ff\"; }\n.bi-printer-fill::before { content: \"\\f500\"; }\n.bi-printer::before { content: \"\\f501\"; }\n.bi-puzzle-fill::before { content: \"\\f502\"; }\n.bi-puzzle::before { content: \"\\f503\"; }\n.bi-question-circle-fill::before { content: \"\\f504\"; }\n.bi-question-circle::before { content: \"\\f505\"; }\n.bi-question-diamond-fill::before { content: \"\\f506\"; }\n.bi-question-diamond::before { content: \"\\f507\"; }\n.bi-question-octagon-fill::before { content: \"\\f508\"; }\n.bi-question-octagon::before { content: \"\\f509\"; }\n.bi-question-square-fill::before { content: \"\\f50a\"; }\n.bi-question-square::before { content: \"\\f50b\"; }\n.bi-question::before { content: \"\\f50c\"; }\n.bi-rainbow::before { content: \"\\f50d\"; }\n.bi-receipt-cutoff::before { content: \"\\f50e\"; }\n.bi-receipt::before { content: \"\\f50f\"; }\n.bi-reception-0::before { content: \"\\f510\"; }\n.bi-reception-1::before { content: \"\\f511\"; }\n.bi-reception-2::before { content: \"\\f512\"; }\n.bi-reception-3::before { content: \"\\f513\"; }\n.bi-reception-4::before { content: \"\\f514\"; }\n.bi-record-btn-fill::before { content: \"\\f515\"; }\n.bi-record-btn::before { content: \"\\f516\"; }\n.bi-record-circle-fill::before { content: \"\\f517\"; }\n.bi-record-circle::before { content: \"\\f518\"; }\n.bi-record-fill::before { content: \"\\f519\"; }\n.bi-record::before { content: \"\\f51a\"; }\n.bi-record2-fill::before { content: \"\\f51b\"; }\n.bi-record2::before { content: \"\\f51c\"; }\n.bi-reply-all-fill::before { content: \"\\f51d\"; }\n.bi-reply-all::before { content: \"\\f51e\"; }\n.bi-reply-fill::before { content: \"\\f51f\"; }\n.bi-reply::before { content: \"\\f520\"; }\n.bi-rss-fill::before { content: \"\\f521\"; }\n.bi-rss::before { content: \"\\f522\"; }\n.bi-rulers::before { content: \"\\f523\"; }\n.bi-save-fill::before { content: \"\\f524\"; }\n.bi-save::before { content: \"\\f525\"; }\n.bi-save2-fill::before { content: \"\\f526\"; }\n.bi-save2::before { content: \"\\f527\"; }\n.bi-scissors::before { content: \"\\f528\"; }\n.bi-screwdriver::before { content: \"\\f529\"; }\n.bi-search::before { content: \"\\f52a\"; }\n.bi-segmented-nav::before { content: \"\\f52b\"; }\n.bi-server::before { content: \"\\f52c\"; }\n.bi-share-fill::before { content: \"\\f52d\"; }\n.bi-share::before { content: \"\\f52e\"; }\n.bi-shield-check::before { content: \"\\f52f\"; }\n.bi-shield-exclamation::before { content: \"\\f530\"; }\n.bi-shield-fill-check::before { content: \"\\f531\"; }\n.bi-shield-fill-exclamation::before { content: \"\\f532\"; }\n.bi-shield-fill-minus::before { content: \"\\f533\"; }\n.bi-shield-fill-plus::before { content: \"\\f534\"; }\n.bi-shield-fill-x::before { content: \"\\f535\"; }\n.bi-shield-fill::before { content: \"\\f536\"; }\n.bi-shield-lock-fill::before { content: \"\\f537\"; }\n.bi-shield-lock::before { content: \"\\f538\"; }\n.bi-shield-minus::before { content: \"\\f539\"; }\n.bi-shield-plus::before { content: \"\\f53a\"; }\n.bi-shield-shaded::before { content: \"\\f53b\"; }\n.bi-shield-slash-fill::before { content: \"\\f53c\"; }\n.bi-shield-slash::before { content: \"\\f53d\"; }\n.bi-shield-x::before { content: \"\\f53e\"; }\n.bi-shield::before { content: \"\\f53f\"; }\n.bi-shift-fill::before { content: \"\\f540\"; }\n.bi-shift::before { content: \"\\f541\"; }\n.bi-shop-window::before { content: \"\\f542\"; }\n.bi-shop::before { content: \"\\f543\"; }\n.bi-shuffle::before { content: \"\\f544\"; }\n.bi-signpost-2-fill::before { content: \"\\f545\"; }\n.bi-signpost-2::before { content: \"\\f546\"; }\n.bi-signpost-fill::before { content: \"\\f547\"; }\n.bi-signpost-split-fill::before { content: \"\\f548\"; }\n.bi-signpost-split::before { content: \"\\f549\"; }\n.bi-signpost::before { content: \"\\f54a\"; }\n.bi-sim-fill::before { content: \"\\f54b\"; }\n.bi-sim::before { content: \"\\f54c\"; }\n.bi-skip-backward-btn-fill::before { content: \"\\f54d\"; }\n.bi-skip-backward-btn::before { content: \"\\f54e\"; }\n.bi-skip-backward-circle-fill::before { content: \"\\f54f\"; }\n.bi-skip-backward-circle::before { content: \"\\f550\"; }\n.bi-skip-backward-fill::before { content: \"\\f551\"; }\n.bi-skip-backward::before { content: \"\\f552\"; }\n.bi-skip-end-btn-fill::before { content: \"\\f553\"; }\n.bi-skip-end-btn::before { content: \"\\f554\"; }\n.bi-skip-end-circle-fill::before { content: \"\\f555\"; }\n.bi-skip-end-circle::before { content: \"\\f556\"; }\n.bi-skip-end-fill::before { content: \"\\f557\"; }\n.bi-skip-end::before { content: \"\\f558\"; }\n.bi-skip-forward-btn-fill::before { content: \"\\f559\"; }\n.bi-skip-forward-btn::before { content: \"\\f55a\"; }\n.bi-skip-forward-circle-fill::before { content: \"\\f55b\"; }\n.bi-skip-forward-circle::before { content: \"\\f55c\"; }\n.bi-skip-forward-fill::before { content: \"\\f55d\"; }\n.bi-skip-forward::before { content: \"\\f55e\"; }\n.bi-skip-start-btn-fill::before { content: \"\\f55f\"; }\n.bi-skip-start-btn::before { content: \"\\f560\"; }\n.bi-skip-start-circle-fill::before { content: \"\\f561\"; }\n.bi-skip-start-circle::before { content: \"\\f562\"; }\n.bi-skip-start-fill::before { content: \"\\f563\"; }\n.bi-skip-start::before { content: \"\\f564\"; }\n.bi-slack::before { content: \"\\f565\"; }\n.bi-slash-circle-fill::before { content: \"\\f566\"; }\n.bi-slash-circle::before { content: \"\\f567\"; }\n.bi-slash-square-fill::before { content: \"\\f568\"; }\n.bi-slash-square::before { content: \"\\f569\"; }\n.bi-slash::before { content: \"\\f56a\"; }\n.bi-sliders::before { content: \"\\f56b\"; }\n.bi-smartwatch::before { content: \"\\f56c\"; }\n.bi-snow::before { content: \"\\f56d\"; }\n.bi-snow2::before { content: \"\\f56e\"; }\n.bi-snow3::before { content: \"\\f56f\"; }\n.bi-sort-alpha-down-alt::before { content: \"\\f570\"; }\n.bi-sort-alpha-down::before { content: \"\\f571\"; }\n.bi-sort-alpha-up-alt::before { content: \"\\f572\"; }\n.bi-sort-alpha-up::before { content: \"\\f573\"; }\n.bi-sort-down-alt::before { content: \"\\f574\"; }\n.bi-sort-down::before { content: \"\\f575\"; }\n.bi-sort-numeric-down-alt::before { content: \"\\f576\"; }\n.bi-sort-numeric-down::before { content: \"\\f577\"; }\n.bi-sort-numeric-up-alt::before { content: \"\\f578\"; }\n.bi-sort-numeric-up::before { content: \"\\f579\"; }\n.bi-sort-up-alt::before { content: \"\\f57a\"; }\n.bi-sort-up::before { content: \"\\f57b\"; }\n.bi-soundwave::before { content: \"\\f57c\"; }\n.bi-speaker-fill::before { content: \"\\f57d\"; }\n.bi-speaker::before { content: \"\\f57e\"; }\n.bi-speedometer::before { content: \"\\f57f\"; }\n.bi-speedometer2::before { content: \"\\f580\"; }\n.bi-spellcheck::before { content: \"\\f581\"; }\n.bi-square-fill::before { content: \"\\f582\"; }\n.bi-square-half::before { content: \"\\f583\"; }\n.bi-square::before { content: \"\\f584\"; }\n.bi-stack::before { content: \"\\f585\"; }\n.bi-star-fill::before { content: \"\\f586\"; }\n.bi-star-half::before { content: \"\\f587\"; }\n.bi-star::before { content: \"\\f588\"; }\n.bi-stars::before { content: \"\\f589\"; }\n.bi-stickies-fill::before { content: \"\\f58a\"; }\n.bi-stickies::before { content: \"\\f58b\"; }\n.bi-sticky-fill::before { content: \"\\f58c\"; }\n.bi-sticky::before { content: \"\\f58d\"; }\n.bi-stop-btn-fill::before { content: \"\\f58e\"; }\n.bi-stop-btn::before { content: \"\\f58f\"; }\n.bi-stop-circle-fill::before { content: \"\\f590\"; }\n.bi-stop-circle::before { content: \"\\f591\"; }\n.bi-stop-fill::before { content: \"\\f592\"; }\n.bi-stop::before { content: \"\\f593\"; }\n.bi-stoplights-fill::before { content: \"\\f594\"; }\n.bi-stoplights::before { content: \"\\f595\"; }\n.bi-stopwatch-fill::before { content: \"\\f596\"; }\n.bi-stopwatch::before { content: \"\\f597\"; }\n.bi-subtract::before { content: \"\\f598\"; }\n.bi-suit-club-fill::before { content: \"\\f599\"; }\n.bi-suit-club::before { content: \"\\f59a\"; }\n.bi-suit-diamond-fill::before { content: \"\\f59b\"; }\n.bi-suit-diamond::before { content: \"\\f59c\"; }\n.bi-suit-heart-fill::before { content: \"\\f59d\"; }\n.bi-suit-heart::before { content: \"\\f59e\"; }\n.bi-suit-spade-fill::before { content: \"\\f59f\"; }\n.bi-suit-spade::before { content: \"\\f5a0\"; }\n.bi-sun-fill::before { content: \"\\f5a1\"; }\n.bi-sun::before { content: \"\\f5a2\"; }\n.bi-sunglasses::before { content: \"\\f5a3\"; }\n.bi-sunrise-fill::before { content: \"\\f5a4\"; }\n.bi-sunrise::before { content: \"\\f5a5\"; }\n.bi-sunset-fill::before { content: \"\\f5a6\"; }\n.bi-sunset::before { content: \"\\f5a7\"; }\n.bi-symmetry-horizontal::before { content: \"\\f5a8\"; }\n.bi-symmetry-vertical::before { content: \"\\f5a9\"; }\n.bi-table::before { content: \"\\f5aa\"; }\n.bi-tablet-fill::before { content: \"\\f5ab\"; }\n.bi-tablet-landscape-fill::before { content: \"\\f5ac\"; }\n.bi-tablet-landscape::before { content: \"\\f5ad\"; }\n.bi-tablet::before { content: \"\\f5ae\"; }\n.bi-tag-fill::before { content: \"\\f5af\"; }\n.bi-tag::before { content: \"\\f5b0\"; }\n.bi-tags-fill::before { content: \"\\f5b1\"; }\n.bi-tags::before { content: \"\\f5b2\"; }\n.bi-telegram::before { content: \"\\f5b3\"; }\n.bi-telephone-fill::before { content: \"\\f5b4\"; }\n.bi-telephone-forward-fill::before { content: \"\\f5b5\"; }\n.bi-telephone-forward::before { content: \"\\f5b6\"; }\n.bi-telephone-inbound-fill::before { content: \"\\f5b7\"; }\n.bi-telephone-inbound::before { content: \"\\f5b8\"; }\n.bi-telephone-minus-fill::before { content: \"\\f5b9\"; }\n.bi-telephone-minus::before { content: \"\\f5ba\"; }\n.bi-telephone-outbound-fill::before { content: \"\\f5bb\"; }\n.bi-telephone-outbound::before { content: \"\\f5bc\"; }\n.bi-telephone-plus-fill::before { content: \"\\f5bd\"; }\n.bi-telephone-plus::before { content: \"\\f5be\"; }\n.bi-telephone-x-fill::before { content: \"\\f5bf\"; }\n.bi-telephone-x::before { content: \"\\f5c0\"; }\n.bi-telephone::before { content: \"\\f5c1\"; }\n.bi-terminal-fill::before { content: \"\\f5c2\"; }\n.bi-terminal::before { content: \"\\f5c3\"; }\n.bi-text-center::before { content: \"\\f5c4\"; }\n.bi-text-indent-left::before { content: \"\\f5c5\"; }\n.bi-text-indent-right::before { content: \"\\f5c6\"; }\n.bi-text-left::before { content: \"\\f5c7\"; }\n.bi-text-paragraph::before { content: \"\\f5c8\"; }\n.bi-text-right::before { content: \"\\f5c9\"; }\n.bi-textarea-resize::before { content: \"\\f5ca\"; }\n.bi-textarea-t::before { content: \"\\f5cb\"; }\n.bi-textarea::before { content: \"\\f5cc\"; }\n.bi-thermometer-half::before { content: \"\\f5cd\"; }\n.bi-thermometer-high::before { content: \"\\f5ce\"; }\n.bi-thermometer-low::before { content: \"\\f5cf\"; }\n.bi-thermometer-snow::before { content: \"\\f5d0\"; }\n.bi-thermometer-sun::before { content: \"\\f5d1\"; }\n.bi-thermometer::before { content: \"\\f5d2\"; }\n.bi-three-dots-vertical::before { content: \"\\f5d3\"; }\n.bi-three-dots::before { content: \"\\f5d4\"; }\n.bi-toggle-off::before { content: \"\\f5d5\"; }\n.bi-toggle-on::before { content: \"\\f5d6\"; }\n.bi-toggle2-off::before { content: \"\\f5d7\"; }\n.bi-toggle2-on::before { content: \"\\f5d8\"; }\n.bi-toggles::before { content: \"\\f5d9\"; }\n.bi-toggles2::before { content: \"\\f5da\"; }\n.bi-tools::before { content: \"\\f5db\"; }\n.bi-tornado::before { content: \"\\f5dc\"; }\n.bi-trash-fill::before { content: \"\\f5dd\"; }\n.bi-trash::before { content: \"\\f5de\"; }\n.bi-trash2-fill::before { content: \"\\f5df\"; }\n.bi-trash2::before { content: \"\\f5e0\"; }\n.bi-tree-fill::before { content: \"\\f5e1\"; }\n.bi-tree::before { content: \"\\f5e2\"; }\n.bi-triangle-fill::before { content: \"\\f5e3\"; }\n.bi-triangle-half::before { content: \"\\f5e4\"; }\n.bi-triangle::before { content: \"\\f5e5\"; }\n.bi-trophy-fill::before { content: \"\\f5e6\"; }\n.bi-trophy::before { content: \"\\f5e7\"; }\n.bi-tropical-storm::before { content: \"\\f5e8\"; }\n.bi-truck-flatbed::before { content: \"\\f5e9\"; }\n.bi-truck::before { content: \"\\f5ea\"; }\n.bi-tsunami::before { content: \"\\f5eb\"; }\n.bi-tv-fill::before { content: \"\\f5ec\"; }\n.bi-tv::before { content: \"\\f5ed\"; }\n.bi-twitch::before { content: \"\\f5ee\"; }\n.bi-twitter::before { content: \"\\f5ef\"; }\n.bi-type-bold::before { content: \"\\f5f0\"; }\n.bi-type-h1::before { content: \"\\f5f1\"; }\n.bi-type-h2::before { content: \"\\f5f2\"; }\n.bi-type-h3::before { content: \"\\f5f3\"; }\n.bi-type-italic::before { content: \"\\f5f4\"; }\n.bi-type-strikethrough::before { content: \"\\f5f5\"; }\n.bi-type-underline::before { content: \"\\f5f6\"; }\n.bi-type::before { content: \"\\f5f7\"; }\n.bi-ui-checks-grid::before { content: \"\\f5f8\"; }\n.bi-ui-checks::before { content: \"\\f5f9\"; }\n.bi-ui-radios-grid::before { content: \"\\f5fa\"; }\n.bi-ui-radios::before { content: \"\\f5fb\"; }\n.bi-umbrella-fill::before { content: \"\\f5fc\"; }\n.bi-umbrella::before { content: \"\\f5fd\"; }\n.bi-union::before { content: \"\\f5fe\"; }\n.bi-unlock-fill::before { content: \"\\f5ff\"; }\n.bi-unlock::before { content: \"\\f600\"; }\n.bi-upc-scan::before { content: \"\\f601\"; }\n.bi-upc::before { content: \"\\f602\"; }\n.bi-upload::before { content: \"\\f603\"; }\n.bi-vector-pen::before { content: \"\\f604\"; }\n.bi-view-list::before { content: \"\\f605\"; }\n.bi-view-stacked::before { content: \"\\f606\"; }\n.bi-vinyl-fill::before { content: \"\\f607\"; }\n.bi-vinyl::before { content: \"\\f608\"; }\n.bi-voicemail::before { content: \"\\f609\"; }\n.bi-volume-down-fill::before { content: \"\\f60a\"; }\n.bi-volume-down::before { content: \"\\f60b\"; }\n.bi-volume-mute-fill::before { content: \"\\f60c\"; }\n.bi-volume-mute::before { content: \"\\f60d\"; }\n.bi-volume-off-fill::before { content: \"\\f60e\"; }\n.bi-volume-off::before { content: \"\\f60f\"; }\n.bi-volume-up-fill::before { content: \"\\f610\"; }\n.bi-volume-up::before { content: \"\\f611\"; }\n.bi-vr::before { content: \"\\f612\"; }\n.bi-wallet-fill::before { content: \"\\f613\"; }\n.bi-wallet::before { content: \"\\f614\"; }\n.bi-wallet2::before { content: \"\\f615\"; }\n.bi-watch::before { content: \"\\f616\"; }\n.bi-water::before { content: \"\\f617\"; }\n.bi-whatsapp::before { content: \"\\f618\"; }\n.bi-wifi-1::before { content: \"\\f619\"; }\n.bi-wifi-2::before { content: \"\\f61a\"; }\n.bi-wifi-off::before { content: \"\\f61b\"; }\n.bi-wifi::before { content: \"\\f61c\"; }\n.bi-wind::before { content: \"\\f61d\"; }\n.bi-window-dock::before { content: \"\\f61e\"; }\n.bi-window-sidebar::before { content: \"\\f61f\"; }\n.bi-window::before { content: \"\\f620\"; }\n.bi-wrench::before { content: \"\\f621\"; }\n.bi-x-circle-fill::before { content: \"\\f622\"; }\n.bi-x-circle::before { content: \"\\f623\"; }\n.bi-x-diamond-fill::before { content: \"\\f624\"; }\n.bi-x-diamond::before { content: \"\\f625\"; }\n.bi-x-octagon-fill::before { content: \"\\f626\"; }\n.bi-x-octagon::before { content: \"\\f627\"; }\n.bi-x-square-fill::before { content: \"\\f628\"; }\n.bi-x-square::before { content: \"\\f629\"; }\n.bi-x::before { content: \"\\f62a\"; }\n.bi-youtube::before { content: \"\\f62b\"; }\n.bi-zoom-in::before { content: \"\\f62c\"; }\n.bi-zoom-out::before { content: \"\\f62d\"; }\n"
  },
  {
    "path": "src/screenshotbot/css/breakpoints.scss",
    "content": "// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n//    (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n//    >> breakpoint-next(sm)\n//    md\n//    >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n//    md\n//    >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n//    md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n  $n: index($breakpoint-names, $name);\n  @return if($n != null and $n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n//    >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n//    576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n  $min: map-get($breakpoints, $name);\n  @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width. Null for the largest (last) breakpoint.\n// The maximum value is calculated as the minimum of the next one less 0.02px\n// to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n//    >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n//    767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n  $next: breakpoint-next($name, $breakpoints);\n  @return if($next, breakpoint-min($next, $breakpoints) - .02, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.\n// Useful for making responsive utilities.\n//\n//    >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n//    \"\"  (Returns a blank string)\n//    >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n//    \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n  @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n  $min: breakpoint-min($name, $breakpoints);\n  @if $min {\n    @media (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @content;\n  }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n  $max: breakpoint-max($name, $breakpoints);\n  @if $max {\n    @media (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @content;\n  }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n  $min: breakpoint-min($lower, $breakpoints);\n  $max: breakpoint-max($upper, $breakpoints);\n\n  @if $min != null and $max != null {\n    @media (min-width: $min) and (max-width: $max) {\n      @content;\n    }\n  } @else if $max == null {\n    @include media-breakpoint-up($lower, $breakpoints) {\n      @content;\n    }\n  } @else if $min == null {\n    @include media-breakpoint-down($upper, $breakpoints) {\n      @content;\n    }\n  }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n  $min: breakpoint-min($name, $breakpoints);\n  $max: breakpoint-max($name, $breakpoints);\n\n  @if $min != null and $max != null {\n    @media (min-width: $min) and (max-width: $max) {\n      @content;\n    }\n  } @else if $max == null {\n    @include media-breakpoint-up($name, $breakpoints) {\n      @content;\n    }\n  } @else if $min == null {\n    @include media-breakpoint-down($name, $breakpoints) {\n      @content;\n    }\n  }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/dashboard.scss",
    "content": "/* Dashboard-specific styles */\n\n.dashboard-overview {\n  margin-top: 2rem;\n}\n\n.dashboard-overview .card {\n  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;\n  border: none;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.dashboard-overview .card:hover {\n  transform: translateY(-2px);\n  box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n}\n\n.dashboard-overview .card-body {\n  padding: 1.5rem;\n}\n\n.dashboard-overview .card-title {\n  font-size: 2.5rem;\n  font-weight: bold;\n  margin: 0.5rem 0;\n}\n\n.recent-activity-feed .list-group-item {\n  border-left: none;\n  border-right: none;\n  border-top: 1px solid rgba(0,0,0,0.125);\n  border-bottom: none;\n  transition: background-color 0.2s ease;\n}\n\n.recent-activity-feed .list-group-item:last-child {\n  border-bottom: 1px solid rgba(0,0,0,0.125);\n}\n\n.recent-activity-feed .list-group-item:hover {\n  background-color: #f8f9fa;\n}\n\n/* Activity Feed Specific Styling */\n.recent-activity-feed .mdi {\n  font-size: 1.25rem;\n}\n\n.recent-activity-feed .fw-normal {\n  font-weight: 400 !important;\n}\n\n.recent-activity-feed h6 {\n  font-size: 0.95rem;\n  line-height: 1.4;\n}\n\n.activity-accepted {\n  border-left: 3px solid #28a745;\n}\n\n.activity-rejected {\n  border-left: 3px solid #dc3545;\n}\n\n.activity-timeline-icon {\n  background-color: #f8f9fa;\n  border-radius: 50%;\n  width: 2.5rem;\n  height: 2.5rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.recent-activity-feed .rounded-circle {\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.activity-avatar {\n  position: relative;\n  display: inline-block;\n}\n\n.activity-badge {\n  position: absolute;\n  bottom: -2px;\n  right: -2px;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  background-color: rgba(255, 255, 255, 0.9);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 12px;\n  z-index: 10;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n.activity-badge .mdi {\n  font-size: 10px;\n}\n\n.channel-status-grid .progress {\n  height: 8px;\n  background-color: #e9ecef;\n  border-radius: 4px;\n  overflow: hidden;\n}\n\n.channel-status-grid .progress .progress-bar {\n  transition: width 0.3s ease;\n}\n\n.channel-health-item {\n  padding: 0.75rem 0;\n  border-bottom: 1px solid #f1f3f4;\n}\n\n.channel-health-item:last-child {\n  border-bottom: none;\n}\n\n.attention-required .alert {\n  border-left: 4px solid;\n}\n\n.attention-required .alert-warning {\n  border-left-color: #ffc107;\n  background-color: #fff8e1;\n}\n\n.status-icon-success {\n  color: #28a745;\n}\n\n.status-icon-danger {\n  color: #dc3545;\n}\n\n.status-icon-warning {\n  color: #ffc107;\n}\n\n.dashboard-metric-icon {\n  font-size: 2.5rem;\n  margin-bottom: 0.5rem;\n}\n\n.channel-grid-card {\n  height: 100%;\n}\n\n.dashboard-section-header {\n  border-bottom: 2px solid #e9ecef;\n  padding-bottom: 0.5rem;\n  margin-bottom: 1rem;\n}\n\n.health-badge {\n  font-size: 0.875rem;\n  min-width: 3rem;\n  text-align: center;\n}\n\n.empty-state {\n  padding: 3rem 1rem;\n  text-align: center;\n  color: #6c757d;\n}\n\n.empty-state .mdi {\n  opacity: 0.5;\n}\n\n@media (max-width: 768px) {\n  .dashboard-overview .card-title {\n    font-size: 2rem;\n  }\n  \n  .recent-activity-feed .d-flex {\n    flex-direction: column;\n    align-items: flex-start !important;\n  }\n  \n  .recent-activity-feed .d-flex .d-flex:last-child {\n    margin-top: 0.5rem;\n    width: 100%;\n    justify-content: space-between;\n  }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/default-toplevel.scss",
    "content": "@import \"default\";\n@import \"settings\";\n@import \"dashboard\";\n"
  },
  {
    "path": "src/screenshotbot/css/default.scss",
    "content": "@charset \"UTF-8\";\n\n// fake message to trigger build, delete line\n\n$lili-purple: #4d196a;\n$lili-red:    #dd5068;\n$lili-yellow: #f9d25d;\n$lili-light-green: #6acaae;\n$lili-green: #4fa96f;\n\n$left-side-menu-width: 14rem;\n\n$section-green: #52ad77;\n$section-yellow: #F9D25D;\n$section-pink: #DD5068;\n$section-blue: #3C42A7;\n\n@import \"./bootstrap/scss/functions\";\n@import \"./bootstrap/scss/variables\";\n$body-background: #f4f5f6;\n$card-border-color: darken($body-background, 10%);\n@import \"./bootstrap/scss/bootstrap\";\n\n@import \"variables\";\n@import \"breakpoints\";\n@import \"avatar\";\n@import \"headroom-custom\";\n@import \"vendor/baguetteBox\";\n@import \"sidebar\";\n@import \"bootstrap-icons\";\n@import \"split\";\n\n@import \"material-icons\";\n@import \"montserrat\";\n@import \"roboto\";\n\n@import \"pro-common\";\n@import \"auth\";\n@import \"billing\";\n@import \"mask-builder\";\n\n@import \"autocomplete\";\n\n$site-alert-height: 2em;\n\nbody {\n    font-family: Roboto, sans-serif;\n    font-size: 0.875rem;\n}\n\nh4 {\n    font-size: 1.25rem;\n    letter-spacing: 0.0073529412em;\n}\n\nul.compare-image-header {\n    list-style:none;\n    padding-left: 0;\n    li {\n        display: inline-block;\n\n        &::after {\n            content: \"|\";\n            color: #888;\n            font-size: 1.1rem;\n        }\n\n        &:first-child::after {\n            /* right after the name of the image */\n            display:none;\n        }\n\n        &:last-child::after {\n            display:none;\n        }\n    }\n\n}\n\n.change-image {\n    max-width: 100%;\n    max-height: 80vh;\n}\n\n.change-arrow {\n    font-size: 2em;\n    margin-top: auto;\n    margin-bottom: auto;\n}\n\n.screenshot-box-image {\n    max-width: 100%;\n}\n\n.screenshot-image {\n    background-image: url(\"/assets/images/img-background.png\");\n    margin-top: 5px;\n    position: relative;\n    border: 1px solid lightgrey;\n    height: auto;\n    width: auto;\n}\n\n.landing-change-example .screenshot-image {\n    background-image: none;\n    margin-left: auto;\n    margin-right: auto;\n    box-shadow: 0 0 3px lightgrey;\n}\n\n@media(max-width: 768px) {\n    // make change image look downwards\n    .change-image {\n        display: inline-block;\n        max-width: 95%;\n    }\n\n    .change-arrow {\n        display:block;\n        width: 100%;\n    }\n\n}\n\n/**\n * The CSS shown here will not be introduced in the Quickstart guide, but shows\n * how you can use CSS to style your Element's container.\n */\n.StripeElement {\n  box-sizing: border-box;\n\n  height: 40px;\n\n  padding: 10px 12px;\n\n  border: 1px solid lightgrey;\n  border-radius: 4px;\n  background-color: white;\n\n  box-shadow: 0 1px 3px 0 #e6ebf1;\n  -webkit-transition: box-shadow 150ms ease;\n  transition: box-shadow 150ms ease;\n}\n\n.StripeElement--focus {\n  box-shadow: 0 1px 3px 0 #cfd7df;\n}\n\n.StripeElement--invalid {\n  border-color: #fa755a;\n}\n\n.StripeElement--webkit-autofill {\n  background-color: #fefde5 !important;\n}\n\n/* body { */\n/*     display: flex; */\n/*     flex-direction: column; */\n/*     min-height: 100vh; */\n/* } */\n\n/* section:last-of-type { */\n/*     flex-grow:1; */\n/* } */\n\n\n.full-height {\n    min-height: 80vh;\n}\n\nh1 {\n    padding-top: 0em;\n}\n\n.doc-left-nav h2 {\n    font-size: 1em;\n}\n\n.doc-left-nav li {\n    font-size: 0.7em;\n}\n\n.doc-left-nav ul {\n    list-style-type: none;\n    padding-left: 0.5em;\n}\n\n.landing-hero-submit {\n}\n\nul.settings-nav {\n    padding-left: 0;\n    font-size: 1.25em;\n}\n\nli.settings-nav-item {\n    list-style: none;\n    padding-left: 0;\n\n    .active {\n        font-weight: bold;\n    }\n}\n\n.settings-nav-title {\n    text-transform: uppercase;\n    padding: 1.5em 0em;\n}\n\n\n.setting-item-title {\n    font-weight: bold;\n    font-size: 1.25em;\n    margin-top: 0;\n}\n\n.setting-item-subtitle {\n    font-weight: 300;\n}\n\n.setting-form-group {\n    padding-right: 3em;\n}\n\n.feature-check-mark.icon-round  {\n    font-size: 1rem;\n    height: 100%;\n    width: 1.5rem;\n    border-radius: 50%;\n    display: block;\n    margin-left: auto;\n    margin-right: auto;\n}\n\n.feature-check-mark.icon-round > .icon {\n    height: 1rem;\n}\n\n.feature-check-mark .icon path {\n    fill: #f8f9fa;\n}\n\n.bg-dark h3 {\n    color: $gray-100;\n}\n\n.landing-summary {\n}\n\nfooter ul, footer p {\n}\n\nsection.features-section .h6 {\n    font-size: 1rem;\n}\n\nsection.features-section .text-muted {\n}\n\nsection.features-section .text-small {\n    font-size: 1rem;\n}\n\n.nav-user .account-user-name {\n    font-size: 1rem;\n}\n\n.taskie-list.nonempty {\n    color: $gray-700;\n}\n\ntable.taskie-list {\n    width: 100%;\n    background: white;\n    padding-left: 0.5rem;\n    padding-right: 0.5rem;\n\n    th, td {\n        padding-left: 1em;\n        padding-right: 1em;\n    }\n\n    thead tr {\n        border-bottom: 2px solid $body-background;\n    }\n\n    tr {\n        border-top: 1px solid $body-background;\n        border-bottom: 1px solid $body-background;\n        height: 3em;\n\n        & td:first-child, & th:first-child {\n            padding-left: 1.5rem;\n            margin-right: 1.5rem;\n        }\n\n        & td:last-child, & th:last-child {\n            padding-right: 1.5rem;\n        }\n\n        transition: background 0.1s;\n    }\n\n    .material-icons {\n        color: $gray-500;\n    }\n\n    tbody {\n        tr:hover {\n            background: lighten($primary, 44%);\n            transition: background 0.1s;\n            padding-left: 0.5rem;\n        }\n\n        tr.empty {\n            background: white !important;\n        }\n    }\n\n    td.taskie-timestamp {\n    }\n\n    &.checkboxes th:first-child {\n        width: 3em;\n        border-right: none;\n    }\n\n    &.with-avatar th:first-child {\n        width: 2em;\n        border-right: none;\n    }\n\n    &.checkboxes th:nth-child(2) {\n        border-left: none;\n    }\n\n    &.with-avatar th:nth-child(2) {\n        border-left: none;\n    }\n\n    &.with-avatar td:first-child {\n        width: 2em;\n        padding: 0 1em;\n    }\n\n    th {\n        height: 3.2rem;\n        background: #f9fafa;\n        color: #888f91;\n        border: 1px solid $body-background;\n\n        &:first-child {\n            border-left: 0px solid $body-background;\n        }\n    }\n\n}\n\nbody.screenshotbot {\n    table.taskie-list {\n        tr {\n            transition: none !important;\n        }\n    }\n}\n\n.taskie-row {\n\n    ul, li {\n        margin-bottom: 0;\n        margin-top: 0;\n        padding-top: 0;\n        padding-bottom:0;\n    }\n    padding-left: 0.5rem;\n    padding-right: 0.5rem;\n\n}\n\n\nul.landing-summary li {\n    list-style: none;\n    padding-left: 0;\n}\n\nfooter label {\n    color: $gray-100;\n}\n\n\n.about-us-image {\n    object-fit:cover;\n    border-radius: 50%;\n    width: 250px;\n    height: 250px;\n    box-shadow: 0px 0px 10px 5px $gray-200;\n\n}\n\n.navbar-brand-logo {\n    height: 1.5rem;\n    margin-bottom: 0.17em;\n}\n\n.navbar-brand {\n    display: inline-block;\n    vertical-align: middle;\n}\n\n.footer-brand-image {\n    height: 1.5rem;\n}\n\n.auth-brand-image {\n    height: 1.5rem;\n}\n\n.leftside-menu .logo {\n    margin-left: auto;\n    margin-right: auto;\n    img {\n        max-width: 6em;\n    }\n}\n\n.leftside-menu .logo-lg img {\n    height: 1.4rem;\n    width: auto;\n}\n\n.leftside-menu .logo-sm img {\n    height: 1.4rem;\n    width: auto;\n}\n\n.landing-pitch .text-muted {\n    color: $gray-700 !important;\n}\n\n.code-sample {\n    display: flex;\n    flex-direction: column;\n    border-radius: 8px;\n    position: relative;\n    border: .5px solid #ececf1;\n    background: #f7f7f8;\n}\n\n.code-sample-header {\n    display: flex;\n    align-items: center;\n    padding: 4px 8px 4px 14px;\n    border-top-left-radius: 8px;\n    border-top-right-radius: 8px;\n    overflow: auto;\n    flex-shrink: 0;\n    border-bottom: .5px solid #ececf1;\n    gap: 4px;\n}\n\n.code-sample-title {\n    flex: 1 1 auto;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    margin-right: 12px;\n    color: #353740;\n    font-weight: 400;\n    font-size: 12px;\n    font-family: monospace;\n    user-select: none;\n}\n\n.code-sample-pre {\n    border-top-left-radius: 0;\n    border-top-right-radius: 0;\n}\n\n.code-sample-body .code-sample-pre {\n    margin: 0;\n    border-radius: 8px;\n    padding: 12px 16px;\n    font-size: 12px;\n    line-height: 20px;\n    height: 100%;\n}\n\n.language-built_in {\n    color: #c0660d;\n}\n\n.language-string {\n    color: #008c6a;\n}\n\n.syntax-highlighter {\n    counter-reset: line;\n}\n\n.syntax-highlighter {\n    .react-syntax-highlighter-line-number {\n        color: #8e8ea0;\n        user-select: none;\n        opacity: .5;\n        text-align: right;\n    }\n\n    // See https://stackoverflow.com/questions/40842277/create-line-numbers-on-pre-with-css-only\n    .react-syntax-highlighter-line-number:before {\n        counter-increment: line;\n        content: counter(line);\n    }\n}\n\n.card-pricing .card-footer {\n    border-top: none;\n    margin-bottom: 1em;\n}\n\n.card-pricing {\n    height: 100%;\n}\n\n.navbar {\n    position: fixed;\n    top: 0;\n    bottom: auto;\n    left: 0;\n    right: 0;\n    z-index: 9999;\n}\n\n.headroom--not-top.navbar-dark {\n    background: #00116edd ;\n}\n\n.headroom--not-top.navbar-light {\n    background: #fafbfeee;\n}\n\nbody > section:nth-child(2) {\n    padding-top: 6em !important;\n}\n\n.image-comparison-modal-image {\n    height: 70vh;\n    width: 100%;\n}\n\n.image-comparison-modal-image {\n}\n\n\n.image-comparison-modal .modal-dialog {\n    min-width: 80%;\n}\n\n.doc-image {\n    max-width: 100%;\n    box-shadow: 0px 0px 8px #888888;\n}\n\n.home-brand-logo {\n    max-width: 100%;\n\n}\n\n.home-brand-wrapper {\n    display: flex;\n    align-items: center;\n}\n\n.run-page-image {\n    max-width: 100%;\n    max-height: 25em;\n    width: auto;\n    height: auto;\n}\n\n.screenshot-header {\n    h4 {\n        max-width: 100%;\n        margin: 0;\n        font-size: 1em;\n    }\n\n    h6 {\n        color: gray;\n        font-size: 0.7em;\n        margin: 0;\n    }\n    ul.screenshot-options-menu {\n        padding-left: 0;\n        display: inline;\n        font-size: 0.75rem;\n\n        margin-bottom: 0;\n        & > li {\n            margin-left: 0;\n            list-style: none;\n            display: inline;\n            padding-left: 0;\n        }\n\n        & > li + li:before {\n            content: \" | \";\n        }\n\n    }\n    margin-bottom: 1em;\n}\n\n.report-result {\n    .card-body {\n        padding-top: 0.375rem;\n    }\n}\n\n\n.page-title-box {\n    margin-top: 2em;\n    .page-title {\n        display: inline;\n    }\n    .dropdown {\n        display: inline-block;\n    }\n}\n\n.acceptable {\n    /* the btn adds a border radius, remove it */\n    border-radius: 0;\n    &.accept-link {\n        color: $green !important;\n    }\n\n    &.reject-link {\n        color: $red !important;\n    }\n}\n\n.dropdown-report-accepted {\n    background: $green;\n}\n\n.dropdown-report-accepted {\n    background: $green !important;\n\n    &:hover {\n        background: darken($green, 5%) !important;\n    }\n}\n\n.dropdown-report-rejected {\n    background-color: $red !important;\n\n    &:hover {\n        background-color: darken($red, 5%) !important;\n    }\n}\n\n#baguetteBox-overlay .full-image img {\n    background: url(\"/assets/images/img-background.png\");\n}\n\n\n.pricing-table {\n    th {\n        .h6 {\n            margin-top: 0;\n            padding-top: 0;\n        }\n    }\n}\n\n.content-page-old {\n    flex: 1;\n    padding: 0;\n    margin: 0;\n    padding-left: 1em;\n    padding-right: 1em;\n    overflow-x: hidden;\n    overflow-y: scroll;\n}\n\nbody.dashboard {\n    background: $body-background;\n\n    .leftside-menu {\n        width: $left-side-menu-width;\n        z-index: 100;\n\n        bottom: 0;\n        top: 0;\n        float: left;\n        position: fixed;\n\n        .nav-link:hover {\n            background: $gray-200;\n        }\n\n        .nav-link.active {\n            background: white;\n            font-weight: 900;\n        }\n\n\n        .nav-link.active:hover {\n        }\n\n        .nav-link {\n            margin-bottom: 2px;\n        }\n\n        .user-full-name {\n            letter-spacing: 0.03333333em;\n            font-size: 1rem;\n            width: 100%;\n            overflow: hidden;\n            text-overflow: ellipsis;\n        }\n    }\n\n    .site-alert-container.billing-banner {\n        display:none;\n    }\n    \n    @include media-breakpoint-up(xl) {\n        &:has(.site-alert-container:not(.d-none)) { // the d-none is only for testing\n            .leftside-menu {\n                top: 3.5rem;\n            }\n\n            .site-alert-container {\n                display: block;\n\n                .alert {\n                    margin-bottom: 0;\n                }\n            }\n        }\n    }\n\n    overflow-x: hidden;\n\n    .content-page {\n        margin-left: 1em;\n        margin-right: 1em;\n        .main-content, .content {\n            max-width: 1280px;\n            margin-left: auto;\n            margin-right: auto;\n        }\n    }\n\n    @include media-breakpoint-up(xl) {\n        .leftside-menu + .content-page {\n            margin-left: calc(#{$left-side-menu-width} + 1em);\n        }\n    }\n\n    .logo-sm {\n        display:none;\n    }\n\n    .navbar-toggler {\n        display: none;\n        color: black;\n\n        &:focus {\n            outline: none;\n            box-shadow: none;\n        }\n    }\n\n    @include media-breakpoint-down(sm) {\n        .leftside-menu.show {\n            width: 100%;\n        }\n    }\n\n    @media(max-width: 400px) {\n        .leftside-menu:not(.show) {\n            .logo {\n                display: none;\n            }\n        }\n    }\n\n    @mixin wide-screen () {\n        .leftside-menu.show {\n            z-index: 1000;\n            .navbar-toggler {\n                display: inline;\n            }\n            .dropdown {\n                padding-top: 0;\n            }\n\n            // Give some space for the toggler\n            width: calc(#{$left-side-menu-width} + 4em);\n        }\n\n        .leftside-menu.collapsing {\n            transition: none;\n        }\n\n        .leftside-menu:not(.show) {\n            flex-direction: row !important;\n            flex-wrap: none !important;\n            justify-content: space-between;\n            width: 100%;\n            float: none;\n            position: relative;\n            height: auto;\n\n\n            .navbar-toggler {\n                display: inline-block;\n                color: black;\n            }\n\n            .nav-link {\n                padding-right: 0.5em;\n                padding-left: 0.5em;\n            }\n            hr {\n                display: none;\n            }\n            #dropdownUser1 {\n                vertical-align: middle;\n                height: 100%;\n            }\n\n            .nav-item .material-icons {\n                padding-right: 0;\n            }\n\n            .nav-link .text, .user-full-name {\n                display: none;\n            }\n\n            ul.nav {\n                display:none;\n            }\n\n            li.nav-item {\n                display: none;\n            }\n        }\n\n        .content-page {\n            margin-left: 1em;\n        }\n\n    }\n\n    @include media-breakpoint-down(lg) {\n        @include wide-screen;\n    }\n\n    &.wide-screen {\n        @include wide-screen;\n    }\n\n}\n\n\na {\n    text-decoration: none;\n}\n\n\n.material-icons {\n    vertical-align: middle;\n}\n\n\n.account-pages {\n    margin-top: 3.125rem !important;\n\n    .auth-brand-image {\n        height: 1.5rem;\n    }\n\n    input, .btn {\n        font-size: 0.9375rem;\n        padding: 0.5rem 1.25rem;\n    }\n\n    h4 {\n        padding-top: 1em;\n        font-size: 1.5rem;\n        font-weight: 500;\n    }\n\n    .card-header {\n        padding: 1.5rem !important;\n    }\n\n    .card-body {\n        padding: 1.5rem !important;\n        @include media-breakpoint-down(sm) {\n            padding: 0rem !important;\n        }\n    }\n\n}\n\n.error-500 {\n    text-align: center;\n    margin-top: 10em;\n}\n\n\n.load-more-button {\n    margin-top: 2em;\n    margin-bottom: 2em;\n    align: center;\n}\n\n.card-body .load-more-button {\n    margin-bottom: 0em;\n    margin-top: 1em;\n}\n\n.load-more-button.spinner:disabled {\n  position: relative;\n  color: transparent; /* Hide button text while loading */\n}\n\n.load-more-button.spinner:disabled::after {\n  content: \"\";\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 20px;\n  height: 20px;\n  margin: -10px 0 0 -10px;\n  border: 2px solid #fff;\n  border-top-color: lighten($primary, 10%);\n  border-radius: 50%;\n  animation: spin 0.6s linear infinite;\n}\n\n@keyframes spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.card-page-container {\n    max-width: 50em;\n}\n\n.card {\n    .card-header {\n        h1, h2, h3, h4, h5, h6 {\n            margin-top: 0.5rem;\n            color: darken(#888f91, 10%);\n        }\n\n        h3 {\n            font-size: 1.2rem;\n        }\n    }\n\n    .card-footer {\n        padding-top: 1rem;\n        padding-bottom: 1rem;\n    }\n}\n\n.compare-header {\n\n    .report-search-wrapper {\n        width: 20em;\n    }\n\n    .nav-item {\n        margin-left: 0.25rem;\n    }\n\n    @include media-breakpoint-down(md) {\n\n        display: block;\n        justify-content: none;\n\n        .report-search-wrapper {\n            display: block;\n            margin-bottom: 0.5rem;\n            width: 100%;\n        }\n\n        .options {\n            width: 100%;\n        }\n\n        .options .nav {\n            width: 100%;\n            display: flex;\n            justify-content: space-evenly;\n        }\n    }\n}\n\n.acceptance-left-side-bar {\n    a.nav-link.text-black {\n        color: black !important;\n    }\n\n    a.nav-link.text-warning {\n        color: $gray-600 !important;\n    }\n}\n\n.review-panel {\n    a.acceptable {\n        text-decoration: none;\n    }\n\n    .review-tab {\n\n        .close {\n            display: none;\n        }\n\n        .check {\n            display: none;\n        }\n        &.text-success {\n            .check {\n                display: inline-block;\n            }\n        }\n\n        &.text-danger {\n            .close {\n                display: inline-block;\n            }\n        }\n\n\n    }\n    .alert-heading {\n        font-size: 1.0rem;\n        padding-bottom: 0;\n        margin-bottom: 0;\n    }\n\n    .alert p {\n        margin-bottom: 0;\n    }\n}\n\n.explain-icon {\n    color: $gray-400;\n    i {\n        padding: 0;\n        font-size: 1rem;\n    }\n}\n\nh4.screenshot-title {\n    text-overflow: ellipsis;\n    overflow:hidden;\n    white-space: nowrap;\n    font-size: 1rem;\n    color: $gray-700;\n    line-spacing: 0.033333em;\n\n    &:hover {\n        white-space: normal;\n    }\n\n    .index {\n        color: $gray-500;\n    }\n}\n\n.btn {\n    // text-transform: uppercase;\n    //letter-spacing: 0.0892857143em;\n}\n\ntable.git-graph {\n    tr:target {\n        background-color: $gray-300;\n    }\n\n    tr.highlighted {\n        background-color: $gray-500;\n    }\n}\n\n.single-screenshot-modal {\n    .modal-title {\n        text-overflow: ellipsis;\n        overflow: hidden;\n        white-space: nowrap;\n        max-height: 90vh;\n    }\n\n    .modal-body-content {\n        height: 100%;\n        display: flex;\n        flex-flow: column;\n\n        .content-header {\n            flex: 0 1 auto;\n        }\n\n        .canvas-container {\n            flex: 1 1 auto;\n        }\n    }\n}\n\ncanvas.load-into-canvas {\n    object-fit: none;\n    object-position: 0 0;\n}\n\n.canvas-container {\n    overflow: hidden;\n}\n\n.history-page {\n    .screenshot-image {\n        margin-top: 0;\n    }\n}\n\n.body-vh-100 {\n    height: 100vh !important;\n}\n\n.form-control:focus, .btn:focus, .form-select:focus {\n    box-shadow: none;\n}\n\nbody.login-simple {\n    background: white;\n    .account-card .card-header {\n        background: white;\n        border: 0;\n    }\n\n    .account-card {\n        .btn-primary {\n            width: 100%;\n        }\n\n    }\n\n    .auth-small-logo {\n        margin-top: 4rem;\n\n        max-width: 14em;\n        height: 3em;\n    }\n\n    @include media-breakpoint-down(md) {\n        .auth-small-logo {\n            margin-top: 1em;\n        }\n    }\n\n    .form-check input {\n        padding: 0;\n    }\n\n    .sales-pitch {\n        background: $lili-green;\n        height: calc(-192px - 0rem + 100vh);\n        border-radius: 24px;\n        padding: 3em 2em;\n        color: white;\n        max-height: 60em;\n        @media (max-height: 1000px) {\n            height: 100%;\n        }\n\n        ul {\n            list-style-type: none;\n            padding-left: 0.5em;\n        }\n\n        strong {\n            margin-right: 0.15em;\n            display: block;\n        }\n\n        strong, p {\n            margin-left: 3em;\n        }\n\n        i.material-icons {\n            padding-top: 0.1em;\n            float: left;\n            font-size: 2em;\n        }\n\n        li {\n            margin: 1em 0em;\n        }\n\n        img {\n            position:relative;\n            width: 100%;\n            margin-top: 1em;\n            margin-left: auto;\n            margin-right: auto;\n            border-radius: 6px;\n            opacity: 0.91;\n        }\n    }\n\n    .disabled-email {\n        background: white;\n        color: $gray-500;\n        border-color: $gray-200;\n    }\n\n    .form-group:has(.disabled-email) {\n        margin-bottom: 3em !important;\n    }\n}\n\n.or-wrapper {\n    .text {\n        text-align: center;\n        vertical-align: middle;\n        height: 100%;\n    }\n\n\n    .strikethrough {\n        margin-top: -0.25em;\n    }\n}\n\ninput.number-to-text::-webkit-outer-spin-button,\ninput.number-to-text::-webkit-inner-spin-button {\n    -webkit-appearance: none;\n    margin: 0;\n}\n\ninput[type=number].number-to-text {\n    -moz-appearance: textfield;\n}\n\n\n.nav-item.github {\n    img {\n        width: 24px;\n        height: 16px;\n        padding-right: 0.5em;\n    }\n}\n\n.analytics-row {\n    .chart-container {\n        min-height: 30em;\n        background: white;\n    }\n}\n\n.channel-view {\n    ul.channel-links {\n        padding-left: 0;\n        li {\n            display: block;\n\n            padding: 0.2rem 0;\n            &:last-child::after {\n                display:none;\n            }\n        }\n    }\n}\n\n\nscreenshot-image-badge {\n    font-size: 0.7em;\n    top: -0.25rem;\n    left: 0;\n    margin-left: 0.25rem;\n    z-index: 10;\n    line-height: 1;\n    vertical-align: top;\n}\n\n.change-image-row {\n    display: flex;\n    align-items: center;\n    gap: 1em 0.5em;\n    max-width: 100%;\n    min-width: 0;\n}\n\n.image-container {\n    max-width: 100%;\n}\n\n.leftside-menu {\n    hr {\n        visibility: hidden;\n    }\n\n}\n\n.leftside-menu.collapse.show {\n    background: $body-background;\n    border-right: 1px solid $card-border-color;\n}\n\n.site-alert-container {\n    position: sticky;\n    top: 0;\n    z-index: 1000;\n\n    .alert {\n        border-radius: 0;\n    }\n}\n\n\n.compare-header-wrapper {\n    position: sticky;\n    top: 0;\n    z-index: 1000;\n    background-color: none;\n    backdrop-filter: blur(10px);\n    padding-top: 1rem;\n    padding-bottom: 1rem;\n    transition: transform 0.3s ease-in-out;\n\n\n    &.headroom--pinned {\n        transform: translateY(0%);\n    }\n\n    &.headroom--unpinned {\n        transform: translateY(-100%);\n    }\n\n    &.headroom--not-top {\n    }\n}\n\n.content-page {\n    display: flow-root;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/doks-default.scss",
    "content": "@import \"default\";\n@import \"doks/app\";\n"
  },
  {
    "path": "src/screenshotbot/css/headroom-custom.scss",
    "content": ".headroom {\n    will-change: transform;\n    transition: transform 200ms linear;\n}\n.headroom--pinned {\n    transform: translateY(0%);\n}\n.headroom--unpinned {\n    transform: translateY(-100%);\n}\n"
  },
  {
    "path": "src/screenshotbot/css/mask-builder.scss",
    "content": "#mask-editor-form {\n    .canvas-container {\n        max-width: 100%;\n        height: auto !important;\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/material-icons.scss",
    "content": "@font-face {\n  font-family: 'Material Icons';\n  font-style: normal;\n  font-weight: 400;\n  src: url(/assets/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */\n  src: local('Material Icons'),\n    local('MaterialIcons-Regular'),\n    url(/assets/fonts/MaterialIcons-Regular.woff2) format('woff2'),\n    url(/assets/fonts/MaterialIcons-Regular.woff) format('woff'),\n    url(/assets/fonts/MaterialIcons-Regular.ttf) format('truetype');\n}\n\n.material-icons {\n  font-family: 'Material Icons', sans-serif;\n  font-weight: normal;\n  font-style: normal;\n  font-size: 1rem;\n  line-height: 1;\n  letter-spacing: normal;\n  text-transform: none;\n  display: inline-block;\n  white-space: nowrap;\n  word-wrap: normal;\n  direction: ltr;\n  -moz-font-feature-settings: 'liga';\n  -moz-osx-font-smoothing: grayscale;\n  max-width: 1.5em;\n  overflow: hidden;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/montserrat.scss",
    "content": "/* Generated with https://google-webfonts-helper.herokuapp.com/fonts/montserrat?subsets=latin */\n\n/* montserrat-regular - latin */\n@font-face {\n  font-family: 'Montserrat';\n  font-style: normal;\n  font-weight: 400;\n  src: local(''),\n       url('/assets/fonts/montserrat/montserrat-v25-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/montserrat/montserrat-v25-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n  font-display: swap;\n}\n\n/* montserrat-500 - latin */\n@font-face {\n  font-family: 'Montserrat';\n  font-style: normal;\n  font-weight: 500;\n  src: local(''),\n       url('/assets/fonts/montserrat/montserrat-v25-latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/montserrat/montserrat-v25-latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n  font-display: swap;\n}\n\n/* montserrat-600 - latin */\n@font-face {\n  font-family: 'Montserrat';\n  font-style: normal;\n  font-weight: 600;\n  src: local(''),\n       url('/assets/fonts/montserrat/montserrat-v25-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/montserrat/montserrat-v25-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n  font-display: swap;\n}\n\n/* montserrat-700 - latin */\n@font-face {\n  font-family: 'Montserrat';\n  font-style: normal;\n  font-weight: 700;\n  src: local(''),\n       url('/assets/fonts/montserrat/montserrat-v25-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/montserrat/montserrat-v25-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n  font-display: swap;\n}\n\n/* montserrat-italic - latin */\n@font-face {\n  font-family: 'Montserrat';\n  font-style: italic;\n  font-weight: 400;\n  src: local(''),\n       url('/assets/fonts/montserrat/montserrat-v25-latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/montserrat/montserrat-v25-latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n  font-display: swap;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/pro-common.scss",
    "content": "/*\n * Common logic that's both in OSS auth pages, and landing pages in\n * Pro.  Caveat: As of now the landing pages in Pro are designed to\n * font-size as 10px, whereas the dashboard pages are designed with\n * 16px. We're not redesigning for this at the moment though.\n */\n\n.navbar-brand img {\n    height: 3rem;\n    width: auto;\n    margin-right: 0.05em;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/roboto.scss",
    "content": "/* Generated with https://google-webfonts-helper.herokuapp.com/fonts */\n\n/* roboto-regular - latin */\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 400;\n  src: local(''),\n       url('/assets/fonts/roboto/roboto-v30-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/roboto/roboto-v30-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n}\n\n/* roboto-italic - latin */\n@font-face {\n  font-family: 'Roboto';\n  font-style: italic;\n  font-weight: 400;\n  src: local(''),\n       url('/assets/fonts/roboto/roboto-v30-latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/roboto/roboto-v30-latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n}\n\n/* roboto-500 - latin */\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 500;\n  src: local(''),\n       url('/assets/fonts/roboto/roboto-v30-latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/roboto/roboto-v30-latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n}\n\n/* roboto-700 - latin */\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 700;\n  src: local(''),\n       url('/assets/fonts/roboto/roboto-v30-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/roboto/roboto-v30-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n}\n\n/* roboto-900 - latin */\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 900;\n  src: local(''),\n       url('/assets/fonts/roboto/roboto-v30-latin-900.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */\n       url('/assets/fonts/roboto/roboto-v30-latin-900.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */\n}\n"
  },
  {
    "path": "src/screenshotbot/css/screenshotbot.css-assets.asd",
    "content": "(defpackage :screenshotbot-system.css-assets\n  (:use :cl :asdf))\n(in-package :screenshotbot-system.css-assets)\n\n(defsystem :screenshotbot.css-assets/montserrat\n  :class \"BUILD-UTILS:CSS-LIBRARY\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:bootstrap5-css)\n  :components ((\"BUILD-UTILS:SCSS-FILE\" \"montserrat\")))\n\n(defsystem :screenshotbot.css-assets/roboto\n  :class \"BUILD-UTILS:CSS-LIBRARY\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:bootstrap5-css)\n  :components ((\"BUILD-UTILS:SCSS-FILE\" \"roboto\")))\n\n(defsystem screenshotbot.css-assets/library\n  :class \"BUILD-UTILS:CSS-LIBRARY\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:bootstrap5-css\n               :screenshotbot.css-assets/montserrat\n               :screenshotbot.css-assets/roboto)\n  :components ((\"BUILD-UTILS:SCSS-FILE\" \"sidebar\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"material-icons\")\n               (\"BUILD-UTILS:CSS-FILE\" \"bootstrap-icons\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"split\")\n               (:MODULE \"vendor\"\n                :COMPONENTS ((\"BUILD-UTILS:CSS-FILE\" \"baguetteBox\")\n                             (\"BUILD-UTILS::CSS-FILE\" \"jquery-jvectormap-1.2.2\")\n                             (\"BUILD-UTILS::CSS-FILE\" \"dataTables.bootstrap4\")\n                             (\"BUILD-UTILS::CSS-FILE\" \"select.bootstrap4\")\n                             (\"BUILD-UTILS:SCSS-FILE\" \"_jquery.bootstrap-touchspin.min\")\n                             (\"BUILD-UTILS:SCSS-FILE\" \"_daterangepicker\")\n                             (\"BUILD-UTILS::CSS-FILE\" \"frappe-gantt\")\n                             (\"BUILD-UTILS:SCSS-FILE\" \"_select2.min\")\n                             (\"BUILD-UTILS::CSS-FILE\" \"responsive.bootstrap4\") (\"BUILD-UTILS::CSS-FILE\" \"summernote-bs4\") (\"BUILD-UTILS:SCSS-FILE\" \"_bootstrap-datepicker.min\") (\"BUILD-UTILS:SCSS-FILE\" \"_bootstrap-timepicker.min\") (\"BUILD-UTILS::CSS-FILE\" \"britecharts.min\") (\"BUILD-UTILS:SCSS-FILE\" \"_jquery.toast.min\") (\"BUILD-UTILS::CSS-FILE\" \"simplemde.min\") (\"BUILD-UTILS::CSS-FILE\" \"fullcalendar.min\") (\"BUILD-UTILS::CSS-FILE\" \"buttons.bootstrap4\")))  (\"BUILD-UTILS:SCSS-FILE\" \"headroom-custom\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"default\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"settings\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"dashboard\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"variables\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"avatar\") (\"BUILD-UTILS:SCSS-FILE\" \"breakpoints\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"pro-common\")\n               (\"BUILD-UTILS:SCSS-FILE\" \"auth\")\n               (\"build-utils:scss-file\" \"billing\")\n               (\"build-utils:scss-file\" \"mask-builder\")\n               (\"build-utils:scss-file\" \"autocomplete\")))\n\n;;(build-utils::get-css-component #P \"~/builds/web/screenshotbot/static/assets/css/\")\n\n(defsystem screenshotbot.css-assets\n  :class \"build-utils:css-system\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:screenshotbot.css-assets/library)\n  :components ((\"BUILD-UTILS:SCSS-FILE\" \"default-toplevel\")))\n\n(defsystem screenshotbot.css-assets/doks\n  :class \"build-utils:css-system\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:bootstrap-css\n               :doks-css\n               :screenshotbot.css-assets/library)\n  :components ((\"BUILD-UTILS:SCSS-FILE\" \"doks-default\")))\n"
  },
  {
    "path": "src/screenshotbot/css/settings.scss",
    "content": ".settings-sidebar {\n    position: sticky;\n    top: 1rem;\n    max-height: calc(100vh - 2rem);\n    overflow-y: auto;\n}\n\n.settings-sidebar .nav-link {\n    margin-bottom: 4px;\n    transition: all 0.2s ease-in-out;\n    font-size: 0.9rem;\n}\n\n.settings-sidebar .nav-link:hover:not(.active) {\n    background-color: rgba(0,0,0,0.05);\n    transform: translateX(2px);\n}\n\n.settings-sidebar .nav-link.active {\n    box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.settings-section-title h6 {\n    margin-bottom: 8px;\n    margin-top: 16px;\n    font-size: 0.75rem;\n    letter-spacing: 0.5px;\n}\n\n.settings-section-title:first-child h6 {\n    margin-top: 0;\n}\n\n\n.settings-content {\n    min-height: calc(100vh - 6rem);\n}\n\n@media (max-width: 767.98px) {\n    .settings-sidebar {\n        position: static;\n        max-height: none;\n        margin: 1rem !important;\n        margin-top: 0 !important;\n    }\n    \n    .settings-content {\n        margin: 1rem !important;\n        margin-top: 0 !important;\n        padding: 1rem !important;\n        min-height: auto;\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/sidebar.scss",
    "content": "\nbody > * {\n    flex-shrink: 0;\n    /*\n     I don't know why this is here. Ideally this should only effect\n     the sidebar, but this also affects the popovers. With this on the\n     popovers become full screen height on Chrome.\n    */\n\n  /* min-height: -webkit-fill-available; */\n}\n\n.b-example-divider {\n  width: 2.4rem;\n  height: 100%;\n  background-color: rgba(0, 0, 0, .1);\n  border: solid rgba(0, 0, 0, .15);\n  border-width: 1px 0;\n  box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);\n}\n\n.bi {\n  vertical-align: -.125em;\n  pointer-events: none;\n  fill: currentColor;\n}\n\n.dropdown-toggle { outline: 0; }\n\n.nav-flush .nav-link {\n  border-radius: 0;\n}\n\n.btn-toggle {\n  display: inline-flex;\n  align-items: center;\n  padding: .384rem .8rem;\n  font-weight: 600;\n  color: rgba(0, 0, 0, .65);\n  background-color: transparent;\n  border: 0;\n}\n.btn-toggle:hover,\n.btn-toggle:focus {\n  color: rgba(0, 0, 0, .85);\n  background-color: #d2f4ea;\n}\n\n.btn-toggle::before {\n  width: 1.25em;\n  line-height: 0;\n  content: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e\");\n  transition: transform .35s ease;\n  transform-origin: .5em 50%;\n}\n\n.btn-toggle[aria-expanded=\"true\"] {\n  color: rgba(0, 0, 0, .85);\n}\n.btn-toggle[aria-expanded=\"true\"]::before {\n  transform: rotate(90deg);\n}\n\n.btn-toggle-nav a {\n  display: inline-flex;\n  padding: .3rem .8rem;\n  margin-top: .2rem;\n  margin-left: 2rem;\n  text-decoration: none;\n}\n.btn-toggle-nav a:hover,\n.btn-toggle-nav a:focus {\n  background-color: #d2f4ea;\n}\n\n.scrollarea {\n  overflow-y: auto;\n}\n\n.fw-semibold { font-weight: 600; }\n.lh-tight { line-height: 1.25rem; }\n\n\n.nav-item .material-icons {\n    vertical-align: middle;\n    padding-right: 0.5em;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/split.scss",
    "content": ".split-container {\n    height: 100vh;\n    width: 100vw;\n}\n\n.split {\n    display: flex;\n    flex-direction: row;\n    height: 100%;\n}\n\n.gutter {\n    background-color: #eee;\n    background-repeat: no-repeat;\n    background-position: 50%;\n}\n\n.gutter.gutter-horizontal {\n    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');\n    cursor: col-resize;\n}\n\n.image-list {\n    margin: 0em 1em;\n    img {\n        margin: 1em 0em;\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/css/variables.scss",
    "content": "$spacer: 1rem;\n\n$grid-breakpoints: (\n  xs: 0,\n  sm: 576px,\n  md: 768px,\n  lg: 992px,\n  xl: 1200px\n) !default;\n\n$white:    #fff !default;\n$gray-100: #f8f9fa !default;\n$gray-200: #e9ecef !default;\n$gray-300: #dee2e6 !default;\n$gray-400: #ced4da !default;\n$gray-500: #adb5bd !default;\n$gray-600: #6c757d !default;\n$gray-700: #495057 !default;\n$gray-800: #343a40 !default;\n$gray-900: #212529 !default;\n$black:    #000 !default;\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/_bootstrap-datepicker.min.scss",
    "content": "/*!\n * Datepicker for Bootstrap v1.9.0 (https://github.com/uxsolutions/bootstrap-datepicker)\n *\n * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n */\n\n.datepicker{padding:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker-rtl{direction:rtl}.datepicker-rtl.dropdown-menu{left:auto}.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #999;border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker td,.datepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.day.focused,.datepicker table tr td.day:hover{background:#eee;cursor:pointer}.datepicker table tr td.new,.datepicker table tr td.old{color:#999}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{background:#d9edf7;border-radius:0}.datepicker table tr td.today,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today:hover{background-color:#fde19a;background-image:-moz-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-o-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#000}.datepicker table tr td.today.active,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled.disabled,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled:hover.disabled,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.disabled:hover:hover,.datepicker table tr td.today.disabled:hover[disabled],.datepicker table tr td.today.disabled[disabled],.datepicker table tr td.today:active,.datepicker table tr td.today:hover,.datepicker table tr td.today:hover.active,.datepicker table tr td.today:hover.disabled,.datepicker table tr td.today:hover:active,.datepicker table tr td.today:hover:hover,.datepicker table tr td.today:hover[disabled],.datepicker table tr td.today[disabled]{background-color:#fdf59a}.datepicker table tr td.today.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today:active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today:hover:active{background-color:#fbf069\\9}.datepicker table tr td.today:hover:hover{color:#000}.datepicker table tr td.today.active:hover{color:#fff}.datepicker table tr td.range,.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range:hover{background:#eee;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today:hover{background-color:#f3d17a;background-image:-moz-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-ms-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f3c17a),to(#f3e97a));background-image:-webkit-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-o-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:linear-gradient(to bottom,#f3c17a,#f3e97a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);border-color:#f3e97a #f3e97a #edde34;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today.active,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled.disabled,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled:hover.disabled,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.disabled:hover:hover,.datepicker table tr td.range.today.disabled:hover[disabled],.datepicker table tr td.range.today.disabled[disabled],.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today:hover.disabled,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today:hover:hover,.datepicker table tr td.range.today:hover[disabled],.datepicker table tr td.range.today[disabled]{background-color:#f3e97a}.datepicker table tr td.range.today.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today:hover:active{background-color:#efe24b\\9}.datepicker table tr td.selected,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected:hover{background-color:#9e9e9e;background-image:-moz-linear-gradient(to bottom,#b3b3b3,grey);background-image:-ms-linear-gradient(to bottom,#b3b3b3,grey);background-image:-webkit-gradient(linear,0 0,0 100%,from(#b3b3b3),to(grey));background-image:-webkit-linear-gradient(to bottom,#b3b3b3,grey);background-image:-o-linear-gradient(to bottom,#b3b3b3,grey);background-image:linear-gradient(to bottom,#b3b3b3,grey);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);border-color:grey grey #595959;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected.active,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled.disabled,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled:hover.disabled,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.disabled:hover:hover,.datepicker table tr td.selected.disabled:hover[disabled],.datepicker table tr td.selected.disabled[disabled],.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected:hover.disabled,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected:hover:hover,.datepicker table tr td.selected:hover[disabled],.datepicker table tr td.selected[disabled]{background-color:grey}.datepicker table tr td.selected.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected:hover:active{background-color:#666\\9}.datepicker table tr td.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled.disabled,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled:hover.disabled,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.disabled:hover:hover,.datepicker table tr td.active.disabled:hover[disabled],.datepicker table tr td.active.disabled[disabled],.datepicker table tr td.active:active,.datepicker table tr td.active:hover,.datepicker table tr td.active:hover.active,.datepicker table tr td.active:hover.disabled,.datepicker table tr td.active:hover:active,.datepicker table tr td.active:hover:hover,.datepicker table tr td.active:hover[disabled],.datepicker table tr td.active[disabled]{background-color:#04c}.datepicker table tr td.active.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active:active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active:hover:active{background-color:#039\\9}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datepicker table tr td span.focused,.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled.disabled,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled:hover.disabled,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active.disabled:hover[disabled],.datepicker table tr td span.active.disabled[disabled],.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active:hover.disabled,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active:hover[disabled],.datepicker table tr td span.active[disabled]{background-color:#04c}.datepicker table tr td span.active.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active:hover:active{background-color:#039\\9}.datepicker table tr td span.new,.datepicker table tr td span.old{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .next,.datepicker .prev,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .next:hover,.datepicker .prev:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .next.disabled,.datepicker .prev.disabled{visibility:hidden}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-append.date .add-on,.input-prepend.date .add-on{cursor:pointer}.input-append.date .add-on i,.input-prepend.date .add-on i{margin-top:3px}.input-daterange input{text-align:center}.input-daterange input:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-daterange input:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-daterange .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:400;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc;margin-left:-5px;margin-right:-5px}"
  },
  {
    "path": "src/screenshotbot/css/vendor/_bootstrap-timepicker.min.scss",
    "content": "/*!\n * Timepicker Component for Twitter Bootstrap\n *\n * Copyright 2013 Joris de Wit\n *\n * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */.bootstrap-timepicker{position:relative}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu{left:auto;right:0}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before{left:auto;right:12px}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after{left:auto;right:13px}.bootstrap-timepicker .input-group-addon{cursor:pointer}.bootstrap-timepicker .input-group-addon i{display:inline-block;width:16px;height:16px}.bootstrap-timepicker-widget.dropdown-menu{padding:4px}.bootstrap-timepicker-widget.dropdown-menu.open{display:inline-block}.bootstrap-timepicker-widget.dropdown-menu:before{border-bottom:7px solid rgba(0,0,0,0.2);border-left:7px solid transparent;border-right:7px solid transparent;content:\"\";display:inline-block;position:absolute}.bootstrap-timepicker-widget.dropdown-menu:after{border-bottom:6px solid #fff;border-left:6px solid transparent;border-right:6px solid transparent;content:\"\";display:inline-block;position:absolute}.bootstrap-timepicker-widget.timepicker-orient-left:before{left:6px}.bootstrap-timepicker-widget.timepicker-orient-left:after{left:7px}.bootstrap-timepicker-widget.timepicker-orient-right:before{right:6px}.bootstrap-timepicker-widget.timepicker-orient-right:after{right:7px}.bootstrap-timepicker-widget.timepicker-orient-top:before{top:-7px}.bootstrap-timepicker-widget.timepicker-orient-top:after{top:-6px}.bootstrap-timepicker-widget.timepicker-orient-bottom:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.bootstrap-timepicker-widget.timepicker-orient-bottom:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.bootstrap-timepicker-widget a.btn,.bootstrap-timepicker-widget input{border-radius:4px}.bootstrap-timepicker-widget table{width:100%;margin:0}.bootstrap-timepicker-widget table td{text-align:center;height:30px;margin:0;padding:2px}.bootstrap-timepicker-widget table td:not(.separator){min-width:30px}.bootstrap-timepicker-widget table td span{width:100%}.bootstrap-timepicker-widget table td a{border:1px transparent solid;width:100%;display:inline-block;margin:0;padding:8px 0;outline:0;color:#333}.bootstrap-timepicker-widget table td a:hover{text-decoration:none;background-color:#eee;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-color:#ddd}.bootstrap-timepicker-widget table td a i{margin-top:2px;font-size:18px}.bootstrap-timepicker-widget table td input{width:25px;margin:0;text-align:center}.bootstrap-timepicker-widget .modal-content{padding:4px}@media(min-width:767px){.bootstrap-timepicker-widget.modal{width:200px;margin-left:-100px}}@media(max-width:767px){.bootstrap-timepicker{width:100%}.bootstrap-timepicker .dropdown-menu{width:100%}}"
  },
  {
    "path": "src/screenshotbot/css/vendor/_daterangepicker.scss",
    "content": ".daterangepicker {\r\n  position: absolute;\r\n  color: inherit;\r\n  background-color: #fff;\r\n  border-radius: 4px;\r\n  border: 1px solid #ddd;\r\n  width: 278px;\r\n  max-width: none;\r\n  padding: 0;\r\n  margin-top: 7px;\r\n  top: 100px;\r\n  left: 20px;\r\n  z-index: 3001;\r\n  display: none;\r\n  font-family: arial;\r\n  font-size: 15px;\r\n  line-height: 1em;\r\n}\r\n\r\n.daterangepicker:before, .daterangepicker:after {\r\n  position: absolute;\r\n  display: inline-block;\r\n  border-bottom-color: rgba(0, 0, 0, 0.2);\r\n  content: '';\r\n}\r\n\r\n.daterangepicker:before {\r\n  top: -7px;\r\n  border-right: 7px solid transparent;\r\n  border-left: 7px solid transparent;\r\n  border-bottom: 7px solid #ccc;\r\n}\r\n\r\n.daterangepicker:after {\r\n  top: -6px;\r\n  border-right: 6px solid transparent;\r\n  border-bottom: 6px solid #fff;\r\n  border-left: 6px solid transparent;\r\n}\r\n\r\n.daterangepicker.opensleft:before {\r\n  right: 9px;\r\n}\r\n\r\n.daterangepicker.opensleft:after {\r\n  right: 10px;\r\n}\r\n\r\n.daterangepicker.openscenter:before {\r\n  left: 0;\r\n  right: 0;\r\n  width: 0;\r\n  margin-left: auto;\r\n  margin-right: auto;\r\n}\r\n\r\n.daterangepicker.openscenter:after {\r\n  left: 0;\r\n  right: 0;\r\n  width: 0;\r\n  margin-left: auto;\r\n  margin-right: auto;\r\n}\r\n\r\n.daterangepicker.opensright:before {\r\n  left: 9px;\r\n}\r\n\r\n.daterangepicker.opensright:after {\r\n  left: 10px;\r\n}\r\n\r\n.daterangepicker.drop-up {\r\n  margin-top: -7px;\r\n}\r\n\r\n.daterangepicker.drop-up:before {\r\n  top: initial;\r\n  bottom: -7px;\r\n  border-bottom: initial;\r\n  border-top: 7px solid #ccc;\r\n}\r\n\r\n.daterangepicker.drop-up:after {\r\n  top: initial;\r\n  bottom: -6px;\r\n  border-bottom: initial;\r\n  border-top: 6px solid #fff;\r\n}\r\n\r\n.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar {\r\n  float: none;\r\n}\r\n\r\n.daterangepicker.single .drp-selected {\r\n  display: none;\r\n}\r\n\r\n.daterangepicker.show-calendar .drp-calendar {\r\n  display: block;\r\n}\r\n\r\n.daterangepicker.show-calendar .drp-buttons {\r\n  display: block;\r\n}\r\n\r\n.daterangepicker.auto-apply .drp-buttons {\r\n  display: none;\r\n}\r\n\r\n.daterangepicker .drp-calendar {\r\n  display: none;\r\n  max-width: 270px;\r\n}\r\n\r\n.daterangepicker .drp-calendar.left {\r\n  padding: 8px 0 8px 8px;\r\n}\r\n\r\n.daterangepicker .drp-calendar.right {\r\n  padding: 8px;\r\n}\r\n\r\n.daterangepicker .drp-calendar.single .calendar-table {\r\n  border: none;\r\n}\r\n\r\n.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {\r\n  color: #fff;\r\n  border: solid black;\r\n  border-width: 0 2px 2px 0;\r\n  border-radius: 0;\r\n  display: inline-block;\r\n  padding: 3px;\r\n}\r\n\r\n.daterangepicker .calendar-table .next span {\r\n  transform: rotate(-45deg);\r\n  -webkit-transform: rotate(-45deg);\r\n}\r\n\r\n.daterangepicker .calendar-table .prev span {\r\n  transform: rotate(135deg);\r\n  -webkit-transform: rotate(135deg);\r\n}\r\n\r\n.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {\r\n  white-space: nowrap;\r\n  text-align: center;\r\n  vertical-align: middle;\r\n  min-width: 32px;\r\n  width: 32px;\r\n  height: 24px;\r\n  line-height: 24px;\r\n  font-size: 12px;\r\n  border-radius: 4px;\r\n  border: 1px solid transparent;\r\n  white-space: nowrap;\r\n  cursor: pointer;\r\n}\r\n\r\n.daterangepicker .calendar-table {\r\n  border: 1px solid #fff;\r\n  border-radius: 4px;\r\n  background-color: #fff;\r\n}\r\n\r\n.daterangepicker .calendar-table table {\r\n  width: 100%;\r\n  margin: 0;\r\n  border-spacing: 0;\r\n  border-collapse: collapse;\r\n}\r\n\r\n.daterangepicker td.available:hover, .daterangepicker th.available:hover {\r\n  background-color: #eee;\r\n  border-color: transparent;\r\n  color: inherit;\r\n}\r\n\r\n.daterangepicker td.week, .daterangepicker th.week {\r\n  font-size: 80%;\r\n  color: #ccc;\r\n}\r\n\r\n.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {\r\n  background-color: #fff;\r\n  border-color: transparent;\r\n  color: #999;\r\n}\r\n\r\n.daterangepicker td.in-range {\r\n  background-color: #ebf4f8;\r\n  border-color: transparent;\r\n  color: #000;\r\n  border-radius: 0;\r\n}\r\n\r\n.daterangepicker td.start-date {\r\n  border-radius: 4px 0 0 4px;\r\n}\r\n\r\n.daterangepicker td.end-date {\r\n  border-radius: 0 4px 4px 0;\r\n}\r\n\r\n.daterangepicker td.start-date.end-date {\r\n  border-radius: 4px;\r\n}\r\n\r\n.daterangepicker td.active, .daterangepicker td.active:hover {\r\n  background-color: #357ebd;\r\n  border-color: transparent;\r\n  color: #fff;\r\n}\r\n\r\n.daterangepicker th.month {\r\n  width: auto;\r\n}\r\n\r\n.daterangepicker td.disabled, .daterangepicker option.disabled {\r\n  color: #999;\r\n  cursor: not-allowed;\r\n  text-decoration: line-through;\r\n}\r\n\r\n.daterangepicker select.monthselect, .daterangepicker select.yearselect {\r\n  font-size: 12px;\r\n  padding: 1px;\r\n  height: auto;\r\n  margin: 0;\r\n  cursor: default;\r\n}\r\n\r\n.daterangepicker select.monthselect {\r\n  margin-right: 2%;\r\n  width: 56%;\r\n}\r\n\r\n.daterangepicker select.yearselect {\r\n  width: 40%;\r\n}\r\n\r\n.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {\r\n  width: 50px;\r\n  margin: 0 auto;\r\n  background: #eee;\r\n  border: 1px solid #eee;\r\n  padding: 2px;\r\n  outline: 0;\r\n  font-size: 12px;\r\n}\r\n\r\n.daterangepicker .calendar-time {\r\n  text-align: center;\r\n  margin: 4px auto 0 auto;\r\n  line-height: 30px;\r\n  position: relative;\r\n}\r\n\r\n.daterangepicker .calendar-time select.disabled {\r\n  color: #ccc;\r\n  cursor: not-allowed;\r\n}\r\n\r\n.daterangepicker .drp-buttons {\r\n  clear: both;\r\n  text-align: right;\r\n  padding: 8px;\r\n  border-top: 1px solid #ddd;\r\n  display: none;\r\n  line-height: 12px;\r\n  vertical-align: middle;\r\n}\r\n\r\n.daterangepicker .drp-selected {\r\n  display: inline-block;\r\n  font-size: 12px;\r\n  padding-right: 8px;\r\n}\r\n\r\n.daterangepicker .drp-buttons .btn {\r\n  margin-left: 8px;\r\n  font-size: 12px;\r\n  font-weight: bold;\r\n  padding: 4px 8px;\r\n}\r\n\r\n.daterangepicker.show-ranges.single.rtl .drp-calendar.left {\r\n  border-right: 1px solid #ddd;\r\n}\r\n\r\n.daterangepicker.show-ranges.single.ltr .drp-calendar.left {\r\n  border-left: 1px solid #ddd;\r\n}\r\n\r\n.daterangepicker.show-ranges.rtl .drp-calendar.right {\r\n  border-right: 1px solid #ddd;\r\n}\r\n\r\n.daterangepicker.show-ranges.ltr .drp-calendar.left {\r\n  border-left: 1px solid #ddd;\r\n}\r\n\r\n.daterangepicker .ranges {\r\n  float: none;\r\n  text-align: left;\r\n  margin: 0;\r\n}\r\n\r\n.daterangepicker.show-calendar .ranges {\r\n  margin-top: 8px;\r\n}\r\n\r\n.daterangepicker .ranges ul {\r\n  list-style: none;\r\n  margin: 0 auto;\r\n  padding: 0;\r\n  width: 100%;\r\n}\r\n\r\n.daterangepicker .ranges li {\r\n  font-size: 12px;\r\n  padding: 8px 12px;\r\n  cursor: pointer;\r\n}\r\n\r\n.daterangepicker .ranges li:hover {\r\n  background-color: #eee;\r\n}\r\n\r\n.daterangepicker .ranges li.active {\r\n  background-color: #08c;\r\n  color: #fff;\r\n}\r\n\r\n/*  Larger Screen Styling */\r\n@media (min-width: 564px) {\r\n  .daterangepicker {\r\n    width: auto;\r\n  }\r\n\r\n  .daterangepicker .ranges ul {\r\n    width: 140px;\r\n  }\r\n\r\n  .daterangepicker.single .ranges ul {\r\n    width: 100%;\r\n  }\r\n\r\n  .daterangepicker.single .drp-calendar.left {\r\n    clear: none;\r\n  }\r\n\r\n  .daterangepicker.single .ranges, .daterangepicker.single .drp-calendar {\r\n    float: left;\r\n  }\r\n\r\n  .daterangepicker {\r\n    direction: ltr;\r\n    text-align: left;\r\n  }\r\n\r\n  .daterangepicker .drp-calendar.left {\r\n    clear: left;\r\n    margin-right: 0;\r\n  }\r\n\r\n  .daterangepicker .drp-calendar.left .calendar-table {\r\n    border-right: none;\r\n    border-top-right-radius: 0;\r\n    border-bottom-right-radius: 0;\r\n  }\r\n\r\n  .daterangepicker .drp-calendar.right {\r\n    margin-left: 0;\r\n  }\r\n\r\n  .daterangepicker .drp-calendar.right .calendar-table {\r\n    border-left: none;\r\n    border-top-left-radius: 0;\r\n    border-bottom-left-radius: 0;\r\n  }\r\n\r\n  .daterangepicker .drp-calendar.left .calendar-table {\r\n    padding-right: 8px;\r\n  }\r\n\r\n  .daterangepicker .ranges, .daterangepicker .drp-calendar {\r\n    float: left;\r\n  }\r\n}\r\n\r\n@media (min-width: 730px) {\r\n  .daterangepicker .ranges {\r\n    width: auto;\r\n  }\r\n\r\n  .daterangepicker .ranges {\r\n    float: left;\r\n  }\r\n\r\n  .daterangepicker.rtl .ranges {\r\n    float: right;\r\n  }\r\n\r\n  .daterangepicker .drp-calendar.left {\r\n    clear: none !important;\r\n  }\r\n}\r\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/_jquery.bootstrap-touchspin.min.scss",
    "content": ".bootstrap-touchspin .input-group-btn-vertical{position:absolute;right:0;height:100%;z-index:11}.bootstrap-touchspin .input-group-btn-vertical>.btn{position:absolute;right:0;height:50%;padding:0;width:2em;text-align:center;line-height:1}.bootstrap-touchspin .input-group-btn-vertical .bootstrap-touchspin-up{border-radius:0 4px 0 0;top:0}.bootstrap-touchspin .input-group-btn-vertical .bootstrap-touchspin-down{border-radius:0 0 4px 0;bottom:0}"
  },
  {
    "path": "src/screenshotbot/css/vendor/_jquery.toast.min.scss",
    "content": ".jq-toast-wrap,.jq-toast-wrap *{margin:0;padding:0}.jq-toast-wrap{display:block;position:fixed;width:250px;pointer-events:none!important;letter-spacing:normal;z-index:9000!important}.jq-toast-wrap.bottom-left{bottom:20px;left:20px}.jq-toast-wrap.bottom-right{bottom:20px;right:40px}.jq-toast-wrap.top-left{top:20px;left:20px}.jq-toast-wrap.top-right{top:20px;right:40px}.jq-toast-single{display:block;width:100%;padding:10px;margin:0 0 5px;border-radius:4px;font-size:12px;font-family:arial,sans-serif;line-height:17px;position:relative;pointer-events:all!important;background-color:#444;color:#fff}.jq-toast-single h2{font-family:arial,sans-serif;font-size:14px;margin:0 0 7px;background:0 0;color:inherit;line-height:inherit;letter-spacing:normal}.jq-toast-single a{color:#eee;text-decoration:none;font-weight:700;border-bottom:1px solid #fff;padding-bottom:3px;font-size:12px}.jq-toast-single ul{margin:0 0 0 15px;background:0 0;padding:0}.jq-toast-single ul li{list-style-type:disc!important;line-height:17px;background:0 0;margin:0;padding:0;letter-spacing:normal}.close-jq-toast-single{position:absolute;top:3px;right:7px;font-size:14px;cursor:pointer}.jq-toast-loader{display:block;position:absolute;top:-2px;height:5px;width:0;left:0;border-radius:5px;background:red}.jq-toast-loaded{width:100%}.jq-has-icon{padding:10px 10px 10px 50px;background-repeat:no-repeat;background-position:10px}.jq-icon-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=);background-color:#31708f;color:#d9edf7;border-color:#bce8f1}.jq-icon-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=);background-color:#8a6d3b;color:#fcf8e3;border-color:#faebcc}.jq-icon-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=);background-color:#a94442;color:#f2dede;border-color:#ebccd1}.jq-icon-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==);color:#dff0d8;background-color:#3c763d;border-color:#d6e9c6}"
  },
  {
    "path": "src/screenshotbot/css/vendor/_select2.min.scss",
    "content": ".select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir=\"rtl\"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir=\"rtl\"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir=\"rtl\"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir=\"rtl\"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir=\"rtl\"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir=\"rtl\"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir=\"rtl\"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir=\"rtl\"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir=\"rtl\"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir=\"rtl\"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir=\"rtl\"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/baguetteBox.css",
    "content": "/*!\n * baguetteBox.js\n * @author  feimosi\n * @version 1.11.1\n * @url https://github.com/feimosi/baguetteBox.js\n */\n#baguetteBox-overlay {\n  display: none;\n  opacity: 0;\n  position: fixed;\n  overflow: hidden;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 1000000;\n  background-color: #222;\n  background-color: rgba(0, 0, 0, 0.8);\n  -webkit-transition: opacity .5s ease;\n  transition: opacity .5s ease; }\n  #baguetteBox-overlay.visible {\n    opacity: 1; }\n  #baguetteBox-overlay .full-image {\n    display: inline-block;\n    position: relative;\n    width: 100%;\n    height: 100%;\n    text-align: center; }\n    #baguetteBox-overlay .full-image figure {\n      display: inline;\n      margin: 0;\n      height: 100%; }\n    #baguetteBox-overlay .full-image img {\n      display: inline-block;\n      width: auto;\n      height: auto;\n      max-height: 100%;\n      max-width: 100%;\n      vertical-align: middle;\n      -webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);\n         -moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);\n              box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); }\n    #baguetteBox-overlay .full-image figcaption {\n      display: block;\n      position: absolute;\n      bottom: 0;\n      width: 100%;\n      text-align: center;\n      line-height: 1.8;\n      white-space: normal;\n      color: #ccc;\n      background-color: #000;\n      background-color: rgba(0, 0, 0, 0.6);\n      font-family: sans-serif; }\n    #baguetteBox-overlay .full-image:before {\n      content: \"\";\n      display: inline-block;\n      height: 50%;\n      width: 1px;\n      margin-right: -1px; }\n\n#baguetteBox-slider {\n  position: absolute;\n  left: 0;\n  top: 0;\n  height: 100%;\n  width: 100%;\n  white-space: nowrap;\n  -webkit-transition: left .4s ease, -webkit-transform .4s ease;\n  transition: left .4s ease, -webkit-transform .4s ease;\n  transition: left .4s ease, transform .4s ease;\n  transition: left .4s ease, transform .4s ease, -webkit-transform .4s ease, -moz-transform .4s ease; }\n  #baguetteBox-slider.bounce-from-right {\n    -webkit-animation: bounceFromRight .4s ease-out;\n            animation: bounceFromRight .4s ease-out; }\n  #baguetteBox-slider.bounce-from-left {\n    -webkit-animation: bounceFromLeft .4s ease-out;\n            animation: bounceFromLeft .4s ease-out; }\n\n@-webkit-keyframes bounceFromRight {\n  0% {\n    margin-left: 0; }\n  50% {\n    margin-left: -30px; }\n  100% {\n    margin-left: 0; } }\n\n@keyframes bounceFromRight {\n  0% {\n    margin-left: 0; }\n  50% {\n    margin-left: -30px; }\n  100% {\n    margin-left: 0; } }\n\n@-webkit-keyframes bounceFromLeft {\n  0% {\n    margin-left: 0; }\n  50% {\n    margin-left: 30px; }\n  100% {\n    margin-left: 0; } }\n\n@keyframes bounceFromLeft {\n  0% {\n    margin-left: 0; }\n  50% {\n    margin-left: 30px; }\n  100% {\n    margin-left: 0; } }\n\n.baguetteBox-button#next-button, .baguetteBox-button#previous-button {\n  top: 50%;\n  top: calc(50% - 30px);\n  width: 44px;\n  height: 60px; }\n\n.baguetteBox-button {\n  position: absolute;\n  cursor: pointer;\n  outline: none;\n  padding: 0;\n  margin: 0;\n  border: 0;\n  -moz-border-radius: 15%;\n       border-radius: 15%;\n  background-color: #323232;\n  background-color: rgba(50, 50, 50, 0.5);\n  color: #ddd;\n  font: 1.6em sans-serif;\n  -webkit-transition: background-color .4s ease;\n  transition: background-color .4s ease; }\n  .baguetteBox-button:focus, .baguetteBox-button:hover {\n    background-color: rgba(50, 50, 50, 0.9); }\n  .baguetteBox-button#next-button {\n    right: 2%; }\n  .baguetteBox-button#previous-button {\n    left: 2%; }\n  .baguetteBox-button#close-button {\n    top: 20px;\n    right: 2%;\n    right: calc(2% + 6px);\n    width: 30px;\n    height: 30px; }\n  .baguetteBox-button svg {\n    position: absolute;\n    left: 0;\n    top: 0; }\n\n/*\n    Preloader\n    Borrowed from http://tobiasahlin.com/spinkit/\n*/\n.baguetteBox-spinner {\n  width: 40px;\n  height: 40px;\n  display: inline-block;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin-top: -20px;\n  margin-left: -20px; }\n\n.baguetteBox-double-bounce1,\n.baguetteBox-double-bounce2 {\n  width: 100%;\n  height: 100%;\n  -moz-border-radius: 50%;\n       border-radius: 50%;\n  background-color: #fff;\n  opacity: .6;\n  position: absolute;\n  top: 0;\n  left: 0;\n  -webkit-animation: bounce 2s infinite ease-in-out;\n          animation: bounce 2s infinite ease-in-out; }\n\n.baguetteBox-double-bounce2 {\n  -webkit-animation-delay: -1s;\n          animation-delay: -1s; }\n\n@-webkit-keyframes bounce {\n  0%, 100% {\n    -webkit-transform: scale(0);\n            transform: scale(0); }\n  50% {\n    -webkit-transform: scale(1);\n            transform: scale(1); } }\n\n@keyframes bounce {\n  0%, 100% {\n    -webkit-transform: scale(0);\n       -moz-transform: scale(0);\n            transform: scale(0); }\n  50% {\n    -webkit-transform: scale(1);\n       -moz-transform: scale(1);\n            transform: scale(1); } }\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/buttons.bootstrap4.css",
    "content": "@keyframes dtb-spinner {\n  100% {\n    transform: rotate(360deg);\n  }\n}\n@-o-keyframes dtb-spinner {\n  100% {\n    -o-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n@-ms-keyframes dtb-spinner {\n  100% {\n    -ms-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n@-webkit-keyframes dtb-spinner {\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n@-moz-keyframes dtb-spinner {\n  100% {\n    -moz-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\ndiv.dt-button-info {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  width: 400px;\n  margin-top: -100px;\n  margin-left: -200px;\n  background-color: white;\n  border: 2px solid #111;\n  box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.3);\n  border-radius: 3px;\n  text-align: center;\n  z-index: 21;\n}\ndiv.dt-button-info h2 {\n  padding: 0.5em;\n  margin: 0;\n  font-weight: normal;\n  border-bottom: 1px solid #ddd;\n  background-color: #f3f3f3;\n}\ndiv.dt-button-info > div {\n  padding: 1em;\n}\n\ndiv.dt-button-collection-title {\n  text-align: center;\n  padding: 0.3em 0 0.5em;\n  font-size: 0.9em;\n}\n\ndiv.dt-button-collection-title:empty {\n  display: none;\n}\n\ndiv.dt-button-collection {\n  position: absolute;\n  z-index: 2001;\n}\ndiv.dt-button-collection div.dropdown-menu {\n  display: block;\n  z-index: 2002;\n  min-width: 100%;\n}\ndiv.dt-button-collection div.dt-button-collection-title {\n  background-color: white;\n  border: 1px solid rgba(0, 0, 0, 0.15);\n}\ndiv.dt-button-collection.fixed {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  margin-left: -75px;\n  border-radius: 0;\n}\ndiv.dt-button-collection.fixed.two-column {\n  margin-left: -200px;\n}\ndiv.dt-button-collection.fixed.three-column {\n  margin-left: -225px;\n}\ndiv.dt-button-collection.fixed.four-column {\n  margin-left: -300px;\n}\ndiv.dt-button-collection > :last-child {\n  display: block !important;\n  -webkit-column-gap: 8px;\n  -moz-column-gap: 8px;\n  -ms-column-gap: 8px;\n  -o-column-gap: 8px;\n  column-gap: 8px;\n}\ndiv.dt-button-collection > :last-child > * {\n  -webkit-column-break-inside: avoid;\n  break-inside: avoid;\n}\ndiv.dt-button-collection.two-column {\n  width: 400px;\n}\ndiv.dt-button-collection.two-column > :last-child {\n  padding-bottom: 1px;\n  -webkit-column-count: 2;\n  -moz-column-count: 2;\n  -ms-column-count: 2;\n  -o-column-count: 2;\n  column-count: 2;\n}\ndiv.dt-button-collection.three-column {\n  width: 450px;\n}\ndiv.dt-button-collection.three-column > :last-child {\n  padding-bottom: 1px;\n  -webkit-column-count: 3;\n  -moz-column-count: 3;\n  -ms-column-count: 3;\n  -o-column-count: 3;\n  column-count: 3;\n}\ndiv.dt-button-collection.four-column {\n  width: 600px;\n}\ndiv.dt-button-collection.four-column > :last-child {\n  padding-bottom: 1px;\n  -webkit-column-count: 4;\n  -moz-column-count: 4;\n  -ms-column-count: 4;\n  -o-column-count: 4;\n  column-count: 4;\n}\ndiv.dt-button-collection .dt-button {\n  border-radius: 0;\n}\ndiv.dt-button-collection.fixed {\n  max-width: none;\n}\ndiv.dt-button-collection.fixed:before, div.dt-button-collection.fixed:after {\n  display: none;\n}\n\ndiv.dt-button-background {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 999;\n}\n\n@media screen and (max-width: 767px) {\n  div.dt-buttons {\n    float: none;\n    width: 100%;\n    text-align: center;\n    margin-bottom: 0.5em;\n  }\n  div.dt-buttons a.btn {\n    float: none;\n  }\n}\ndiv.dt-buttons button.btn.processing,\ndiv.dt-buttons div.btn.processing,\ndiv.dt-buttons a.btn.processing {\n  color: rgba(0, 0, 0, 0.2);\n}\ndiv.dt-buttons button.btn.processing:after,\ndiv.dt-buttons div.btn.processing:after,\ndiv.dt-buttons a.btn.processing:after {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 16px;\n  height: 16px;\n  margin: -8px 0 0 -8px;\n  box-sizing: border-box;\n  display: block;\n  content: ' ';\n  border: 2px solid #282828;\n  border-radius: 50%;\n  border-left-color: transparent;\n  border-right-color: transparent;\n  animation: dtb-spinner 1500ms infinite linear;\n  -o-animation: dtb-spinner 1500ms infinite linear;\n  -ms-animation: dtb-spinner 1500ms infinite linear;\n  -webkit-animation: dtb-spinner 1500ms infinite linear;\n  -moz-animation: dtb-spinner 1500ms infinite linear;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/dataTables.bootstrap4.css",
    "content": "table.dataTable {\n  clear: both;\n  margin-top: 6px !important;\n  margin-bottom: 6px !important;\n  max-width: none !important;\n  border-collapse: separate !important;\n  border-spacing: 0;\n}\ntable.dataTable td,\ntable.dataTable th {\n  -webkit-box-sizing: content-box;\n  box-sizing: content-box;\n}\ntable.dataTable td.dataTables_empty,\ntable.dataTable th.dataTables_empty {\n  text-align: center;\n}\ntable.dataTable.nowrap th,\ntable.dataTable.nowrap td {\n  white-space: nowrap;\n}\n\ndiv.dataTables_wrapper div.dataTables_length label {\n  font-weight: normal;\n  text-align: left;\n  white-space: nowrap;\n}\ndiv.dataTables_wrapper div.dataTables_length select {\n  width: auto;\n  display: inline-block;\n}\ndiv.dataTables_wrapper div.dataTables_filter {\n  text-align: right;\n}\ndiv.dataTables_wrapper div.dataTables_filter label {\n  font-weight: normal;\n  white-space: nowrap;\n  text-align: left;\n}\ndiv.dataTables_wrapper div.dataTables_filter input {\n  margin-left: 0.5em;\n  display: inline-block;\n  width: auto;\n}\ndiv.dataTables_wrapper div.dataTables_info {\n  padding-top: 0.85em;\n  white-space: nowrap;\n}\ndiv.dataTables_wrapper div.dataTables_paginate {\n  margin: 0;\n  white-space: nowrap;\n  text-align: right;\n}\ndiv.dataTables_wrapper div.dataTables_paginate ul.pagination {\n  margin: 2px 0;\n  white-space: nowrap;\n  justify-content: flex-end;\n}\ndiv.dataTables_wrapper div.dataTables_processing {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 200px;\n  margin-left: -100px;\n  margin-top: -26px;\n  text-align: center;\n  padding: 1em 0;\n}\n\ntable.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting,\ntable.dataTable thead > tr > td.sorting_asc,\ntable.dataTable thead > tr > td.sorting_desc,\ntable.dataTable thead > tr > td.sorting {\n  padding-right: 30px;\n}\ntable.dataTable thead > tr > th:active,\ntable.dataTable thead > tr > td:active {\n  outline: none;\n}\ntable.dataTable thead .sorting,\ntable.dataTable thead .sorting_asc,\ntable.dataTable thead .sorting_desc,\ntable.dataTable thead .sorting_asc_disabled,\ntable.dataTable thead .sorting_desc_disabled {\n  cursor: pointer;\n  position: relative;\n}\ntable.dataTable thead .sorting:before, table.dataTable thead .sorting:after,\ntable.dataTable thead .sorting_asc:before,\ntable.dataTable thead .sorting_asc:after,\ntable.dataTable thead .sorting_desc:before,\ntable.dataTable thead .sorting_desc:after,\ntable.dataTable thead .sorting_asc_disabled:before,\ntable.dataTable thead .sorting_asc_disabled:after,\ntable.dataTable thead .sorting_desc_disabled:before,\ntable.dataTable thead .sorting_desc_disabled:after {\n  position: absolute;\n  bottom: 0.9em;\n  display: block;\n  opacity: 0.3;\n}\ntable.dataTable thead .sorting:before,\ntable.dataTable thead .sorting_asc:before,\ntable.dataTable thead .sorting_desc:before,\ntable.dataTable thead .sorting_asc_disabled:before,\ntable.dataTable thead .sorting_desc_disabled:before {\n  right: 1em;\n  content: \"\\2191\";\n}\ntable.dataTable thead .sorting:after,\ntable.dataTable thead .sorting_asc:after,\ntable.dataTable thead .sorting_desc:after,\ntable.dataTable thead .sorting_asc_disabled:after,\ntable.dataTable thead .sorting_desc_disabled:after {\n  right: 0.5em;\n  content: \"\\2193\";\n}\ntable.dataTable thead .sorting_asc:before,\ntable.dataTable thead .sorting_desc:after {\n  opacity: 1;\n}\ntable.dataTable thead .sorting_asc_disabled:before,\ntable.dataTable thead .sorting_desc_disabled:after {\n  opacity: 0;\n}\n\ndiv.dataTables_scrollHead table.dataTable {\n  margin-bottom: 0 !important;\n}\n\ndiv.dataTables_scrollBody table {\n  border-top: none;\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\ndiv.dataTables_scrollBody table thead .sorting:before,\ndiv.dataTables_scrollBody table thead .sorting_asc:before,\ndiv.dataTables_scrollBody table thead .sorting_desc:before,\ndiv.dataTables_scrollBody table thead .sorting:after,\ndiv.dataTables_scrollBody table thead .sorting_asc:after,\ndiv.dataTables_scrollBody table thead .sorting_desc:after {\n  display: none;\n}\ndiv.dataTables_scrollBody table tbody tr:first-child th,\ndiv.dataTables_scrollBody table tbody tr:first-child td {\n  border-top: none;\n}\n\ndiv.dataTables_scrollFoot > .dataTables_scrollFootInner {\n  box-sizing: content-box;\n}\ndiv.dataTables_scrollFoot > .dataTables_scrollFootInner > table {\n  margin-top: 0 !important;\n  border-top: none;\n}\n\n@media screen and (max-width: 767px) {\n  div.dataTables_wrapper div.dataTables_length,\n  div.dataTables_wrapper div.dataTables_filter,\n  div.dataTables_wrapper div.dataTables_info,\n  div.dataTables_wrapper div.dataTables_paginate {\n    text-align: center;\n  }\n  div.dataTables_wrapper div.dataTables_paginate ul.pagination {\n    justify-content: center !important;\n  }\n}\ntable.dataTable.table-sm > thead > tr > th :not(.sorting_disabled) {\n  padding-right: 20px;\n}\ntable.dataTable.table-sm .sorting:before,\ntable.dataTable.table-sm .sorting_asc:before,\ntable.dataTable.table-sm .sorting_desc:before {\n  top: 5px;\n  right: 0.85em;\n}\ntable.dataTable.table-sm .sorting:after,\ntable.dataTable.table-sm .sorting_asc:after,\ntable.dataTable.table-sm .sorting_desc:after {\n  top: 5px;\n}\n\ntable.table-bordered.dataTable {\n  border-right-width: 0;\n}\ntable.table-bordered.dataTable th,\ntable.table-bordered.dataTable td {\n  border-left-width: 0;\n}\ntable.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,\ntable.table-bordered.dataTable td:last-child,\ntable.table-bordered.dataTable td:last-child {\n  border-right-width: 1px;\n}\ntable.table-bordered.dataTable tbody th,\ntable.table-bordered.dataTable tbody td {\n  border-bottom-width: 0;\n}\n\ndiv.dataTables_scrollHead table.table-bordered {\n  border-bottom-width: 0;\n}\n\ndiv.table-responsive > div.dataTables_wrapper > div.row {\n  margin: 0;\n}\ndiv.table-responsive > div.dataTables_wrapper > div.row > div[class^=\"col-\"]:first-child {\n  padding-left: 0;\n}\ndiv.table-responsive > div.dataTables_wrapper > div.row > div[class^=\"col-\"]:last-child {\n  padding-right: 0;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/frappe-gantt.css",
    "content": ".gantt .grid-background {\n  fill: none; }\n\n.gantt .grid-header {\n  fill: #ffffff;\n  stroke: #e0e0e0;\n  stroke-width: 1.4; }\n\n.gantt .grid-row {\n  fill: #ffffff; }\n\n.gantt .grid-row:nth-child(even) {\n  fill: #f5f5f5; }\n\n.gantt .row-line {\n  stroke: #ebeff2; }\n\n.gantt .tick {\n  stroke: #e0e0e0;\n  stroke-width: 0.2; }\n  .gantt .tick.thick {\n    stroke-width: 0.4; }\n\n.gantt .today-highlight {\n  fill: #fcf8e3;\n  opacity: 0.5; }\n\n.gantt .arrow {\n  fill: none;\n  stroke: #666;\n  stroke-width: 1.4; }\n\n.gantt .bar {\n  fill: #b8c2cc;\n  stroke: #8D99A6;\n  stroke-width: 0;\n  transition: stroke-width .3s ease;\n  user-select: none; }\n\n.gantt .bar-progress {\n  fill: #a3a3ff; }\n\n.gantt .bar-invalid {\n  fill: transparent;\n  stroke: #8D99A6;\n  stroke-width: 1;\n  stroke-dasharray: 5; }\n  .gantt .bar-invalid ~ .bar-label {\n    fill: #555; }\n\n.gantt .bar-label {\n  fill: #fff;\n  dominant-baseline: central;\n  text-anchor: middle;\n  font-size: 12px;\n  font-weight: lighter; }\n  .gantt .bar-label.big {\n    fill: #555;\n    text-anchor: start; }\n\n.gantt .handle {\n  fill: #ddd;\n  cursor: ew-resize;\n  opacity: 0;\n  visibility: hidden;\n  transition: opacity .3s ease; }\n\n.gantt .bar-wrapper {\n  cursor: pointer;\n  outline: none; }\n  .gantt .bar-wrapper:hover .bar {\n    fill: #a9b5c1; }\n  .gantt .bar-wrapper:hover .bar-progress {\n    fill: #8a8aff; }\n  .gantt .bar-wrapper:hover .handle {\n    visibility: visible;\n    opacity: 1; }\n  .gantt .bar-wrapper.active .bar {\n    fill: #a9b5c1; }\n  .gantt .bar-wrapper.active .bar-progress {\n    fill: #8a8aff; }\n\n.gantt .lower-text, .gantt .upper-text {\n  font-size: 12px;\n  text-anchor: middle; }\n\n.gantt .upper-text {\n  fill: #555; }\n\n.gantt .lower-text {\n  fill: #333; }\n\n.gantt .hide {\n  display: none; }\n\n.gantt-container {\n  position: relative;\n  overflow: auto;\n  font-size: 12px; }\n  .gantt-container .popup-wrapper {\n    position: absolute;\n    top: 0;\n    left: 0;\n    background: rgba(0, 0, 0, 0.8);\n    padding: 0;\n    color: #959da5;\n    border-radius: 3px; }\n    .gantt-container .popup-wrapper .title {\n      border-bottom: 3px solid #a3a3ff;\n      padding: 10px; }\n    .gantt-container .popup-wrapper .subtitle {\n      padding: 10px;\n      color: #dfe2e5; }\n    .gantt-container .popup-wrapper .pointer {\n      position: absolute;\n      height: 5px;\n      margin: 0 0 0 -5px;\n      border: 5px solid transparent;\n      border-top-color: rgba(0, 0, 0, 0.8); }\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/jquery-jvectormap-1.2.2.css",
    "content": ".jvectormap-label {\r\n    position: absolute;\r\n    display: none;\r\n    border: solid 1px #CDCDCD;\r\n    -webkit-border-radius: 3px;\r\n    -moz-border-radius: 3px;\r\n    border-radius: 3px;\r\n    background: #292929;\r\n    color: white;\r\n    font-family: sans-serif, Verdana;\r\n    font-size: smaller;\r\n    padding: 3px;\r\n}\r\n\r\n.jvectormap-zoomin, .jvectormap-zoomout {\r\n    position: absolute;\r\n    left: 10px;\r\n    -webkit-border-radius: 3px;\r\n    -moz-border-radius: 3px;\r\n    background: #424242;\r\n    padding: 2px;\r\n    color: white;\r\n    width: 15px;\r\n    height: 15px;\r\n    cursor: pointer;\r\n    line-height: 10px;\r\n    text-align: center;\r\n}\r\n\r\n.jvectormap-zoomin {\r\n    top: 10px;\r\n}\r\n\r\n.jvectormap-zoomout {\r\n    top: 30px;\r\n}"
  },
  {
    "path": "src/screenshotbot/css/vendor/responsive.bootstrap4.css",
    "content": "table.dataTable.dtr-inline.collapsed > tbody > tr > td.child,\ntable.dataTable.dtr-inline.collapsed > tbody > tr > th.child,\ntable.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty {\n  cursor: default !important;\n}\ntable.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,\ntable.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,\ntable.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {\n  display: none !important;\n}\ntable.dataTable.dtr-inline.collapsed > tbody > tr[role=\"row\"] > td.dtr-control,\ntable.dataTable.dtr-inline.collapsed > tbody > tr[role=\"row\"] > th.dtr-control {\n  position: relative;\n  padding-left: 30px;\n  cursor: pointer;\n}\ntable.dataTable.dtr-inline.collapsed > tbody > tr[role=\"row\"] > td.dtr-control:before,\ntable.dataTable.dtr-inline.collapsed > tbody > tr[role=\"row\"] > th.dtr-control:before {\n  top: 50%;\n  left: 5px;\n  height: 14px;\n  width: 14px;\n  margin-top: -9px;\n  display: block;\n  position: absolute;\n  color: white;\n  border: 2px solid white;\n  border-radius: 14px;\n  box-shadow: 0 0 3px #444;\n  box-sizing: content-box;\n  text-align: center;\n  text-indent: 0 !important;\n  font-family: 'Courier New', Courier, monospace;\n  line-height: 14px;\n  content: '+';\n  background-color: #0275d8;\n}\ntable.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before,\ntable.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before {\n  content: '-';\n  background-color: #d33333;\n}\ntable.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control,\ntable.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control {\n  padding-left: 27px;\n}\ntable.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control:before,\ntable.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control:before {\n  left: 4px;\n  height: 14px;\n  width: 14px;\n  border-radius: 14px;\n  line-height: 14px;\n  text-indent: 3px;\n}\ntable.dataTable.dtr-column > tbody > tr > td.control,\ntable.dataTable.dtr-column > tbody > tr > th.control {\n  position: relative;\n  cursor: pointer;\n}\ntable.dataTable.dtr-column > tbody > tr > td.control:before,\ntable.dataTable.dtr-column > tbody > tr > th.control:before {\n  top: 50%;\n  left: 50%;\n  height: 16px;\n  width: 16px;\n  margin-top: -10px;\n  margin-left: -10px;\n  display: block;\n  position: absolute;\n  color: white;\n  border: 2px solid white;\n  border-radius: 14px;\n  box-shadow: 0 0 3px #444;\n  box-sizing: content-box;\n  text-align: center;\n  text-indent: 0 !important;\n  font-family: 'Courier New', Courier, monospace;\n  line-height: 14px;\n  content: '+';\n  background-color: #0275d8;\n}\ntable.dataTable.dtr-column > tbody > tr.parent td.control:before,\ntable.dataTable.dtr-column > tbody > tr.parent th.control:before {\n  content: '-';\n  background-color: #d33333;\n}\ntable.dataTable > tbody > tr.child {\n  padding: 0.5em 1em;\n}\ntable.dataTable > tbody > tr.child:hover {\n  background: transparent !important;\n}\ntable.dataTable > tbody > tr.child ul.dtr-details {\n  display: inline-block;\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n}\ntable.dataTable > tbody > tr.child ul.dtr-details > li {\n  border-bottom: 1px solid #efefef;\n  padding: 0.5em 0;\n}\ntable.dataTable > tbody > tr.child ul.dtr-details > li:first-child {\n  padding-top: 0;\n}\ntable.dataTable > tbody > tr.child ul.dtr-details > li:last-child {\n  border-bottom: none;\n}\ntable.dataTable > tbody > tr.child span.dtr-title {\n  display: inline-block;\n  min-width: 75px;\n  font-weight: bold;\n}\n\ndiv.dtr-modal {\n  position: fixed;\n  box-sizing: border-box;\n  top: 0;\n  left: 0;\n  height: 100%;\n  width: 100%;\n  z-index: 100;\n  padding: 10em 1em;\n}\ndiv.dtr-modal div.dtr-modal-display {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  width: 50%;\n  height: 50%;\n  overflow: auto;\n  margin: auto;\n  z-index: 102;\n  overflow: auto;\n  background-color: #f5f5f7;\n  border: 1px solid black;\n  border-radius: 0.5em;\n  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);\n}\ndiv.dtr-modal div.dtr-modal-content {\n  position: relative;\n  padding: 1em;\n}\ndiv.dtr-modal div.dtr-modal-close {\n  position: absolute;\n  top: 6px;\n  right: 6px;\n  width: 22px;\n  height: 22px;\n  border: 1px solid #eaeaea;\n  background-color: #f9f9f9;\n  text-align: center;\n  border-radius: 3px;\n  cursor: pointer;\n  z-index: 12;\n}\ndiv.dtr-modal div.dtr-modal-close:hover {\n  background-color: #eaeaea;\n}\ndiv.dtr-modal div.dtr-modal-background {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 101;\n  background: rgba(0, 0, 0, 0.6);\n}\n\n@media screen and (max-width: 767px) {\n  div.dtr-modal div.dtr-modal-display {\n    width: 95%;\n  }\n}\ndiv.dtr-bs-modal table.table tr:first-child td {\n  border-top: none;\n}\n\ntable.dataTable.dtr-inline.collapsed.table-sm > tbody > tr > td:first-child:before,\ntable.dataTable.dtr-inline.collapsed.table-sm > tbody > tr > th:first-child:before {\n  top: 5px;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/select.bootstrap4.css",
    "content": "table.dataTable tbody > tr.selected,\ntable.dataTable tbody > tr > .selected {\n  background-color: #0275d8;\n}\ntable.dataTable.stripe tbody > tr.odd.selected,\ntable.dataTable.stripe tbody > tr.odd > .selected, table.dataTable.display tbody > tr.odd.selected,\ntable.dataTable.display tbody > tr.odd > .selected {\n  background-color: #0172d2;\n}\ntable.dataTable.hover tbody > tr.selected:hover,\ntable.dataTable.hover tbody > tr > .selected:hover, table.dataTable.display tbody > tr.selected:hover,\ntable.dataTable.display tbody > tr > .selected:hover {\n  background-color: #0170d0;\n}\ntable.dataTable.order-column tbody > tr.selected > .sorting_1,\ntable.dataTable.order-column tbody > tr.selected > .sorting_2,\ntable.dataTable.order-column tbody > tr.selected > .sorting_3,\ntable.dataTable.order-column tbody > tr > .selected, table.dataTable.display tbody > tr.selected > .sorting_1,\ntable.dataTable.display tbody > tr.selected > .sorting_2,\ntable.dataTable.display tbody > tr.selected > .sorting_3,\ntable.dataTable.display tbody > tr > .selected {\n  background-color: #0172d3;\n}\ntable.dataTable.display tbody > tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody > tr.odd.selected > .sorting_1 {\n  background-color: #016ecc;\n}\ntable.dataTable.display tbody > tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody > tr.odd.selected > .sorting_2 {\n  background-color: #016fcd;\n}\ntable.dataTable.display tbody > tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody > tr.odd.selected > .sorting_3 {\n  background-color: #0170cf;\n}\ntable.dataTable.display tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody > tr.even.selected > .sorting_1 {\n  background-color: #0172d3;\n}\ntable.dataTable.display tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody > tr.even.selected > .sorting_2 {\n  background-color: #0173d5;\n}\ntable.dataTable.display tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody > tr.even.selected > .sorting_3 {\n  background-color: #0174d7;\n}\ntable.dataTable.display tbody > tr.odd > .selected, table.dataTable.order-column.stripe tbody > tr.odd > .selected {\n  background-color: #016ecc;\n}\ntable.dataTable.display tbody > tr.even > .selected, table.dataTable.order-column.stripe tbody > tr.even > .selected {\n  background-color: #0172d3;\n}\ntable.dataTable.display tbody > tr.selected:hover > .sorting_1, table.dataTable.order-column.hover tbody > tr.selected:hover > .sorting_1 {\n  background-color: #016bc6;\n}\ntable.dataTable.display tbody > tr.selected:hover > .sorting_2, table.dataTable.order-column.hover tbody > tr.selected:hover > .sorting_2 {\n  background-color: #016cc7;\n}\ntable.dataTable.display tbody > tr.selected:hover > .sorting_3, table.dataTable.order-column.hover tbody > tr.selected:hover > .sorting_3 {\n  background-color: #016dca;\n}\ntable.dataTable.display tbody > tr:hover > .selected,\ntable.dataTable.display tbody > tr > .selected:hover, table.dataTable.order-column.hover tbody > tr:hover > .selected,\ntable.dataTable.order-column.hover tbody > tr > .selected:hover {\n  background-color: #016bc6;\n}\ntable.dataTable tbody td.select-checkbox,\ntable.dataTable tbody th.select-checkbox {\n  position: relative;\n}\ntable.dataTable tbody td.select-checkbox:before, table.dataTable tbody td.select-checkbox:after,\ntable.dataTable tbody th.select-checkbox:before,\ntable.dataTable tbody th.select-checkbox:after {\n  display: block;\n  position: absolute;\n  top: 1.2em;\n  left: 50%;\n  width: 12px;\n  height: 12px;\n  box-sizing: border-box;\n}\ntable.dataTable tbody td.select-checkbox:before,\ntable.dataTable tbody th.select-checkbox:before {\n  content: ' ';\n  margin-top: -6px;\n  margin-left: -6px;\n  border: 1px solid black;\n  border-radius: 3px;\n}\ntable.dataTable tr.selected td.select-checkbox:after,\ntable.dataTable tr.selected th.select-checkbox:after {\n  content: '\\2714';\n  margin-top: -11px;\n  margin-left: -4px;\n  text-align: center;\n  text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9;\n}\n\ndiv.dataTables_wrapper span.select-info,\ndiv.dataTables_wrapper span.select-item {\n  margin-left: 0.5em;\n}\n\n@media screen and (max-width: 640px) {\n  div.dataTables_wrapper span.select-info,\n  div.dataTables_wrapper span.select-item {\n    margin-left: 0;\n    display: block;\n  }\n}\ntable.dataTable tbody tr.selected,\ntable.dataTable tbody th.selected,\ntable.dataTable tbody td.selected {\n  color: white;\n}\ntable.dataTable tbody tr.selected a,\ntable.dataTable tbody th.selected a,\ntable.dataTable tbody td.selected a {\n  color: #a2d4ed;\n}\n"
  },
  {
    "path": "src/screenshotbot/css/vendor/summernote-bs4.css",
    "content": "/*!\n * \n * Super simple wysiwyg editor v0.8.18\n * https://summernote.org\n * \n * \n * Copyright 2013- Alan Hong. and other contributors\n * summernote may be freely distributed under the MIT license.\n * \n * Date: 2020-05-20T16:47Z\n * \n */\n@font-face{font-family:\"summernote\";font-style:normal;font-weight:400;font-display:auto;src:url(font/summernote.eot);src:url(font/summernote.eot?#iefix) format(\"embedded-opentype\"),url(font/summernote.woff2) format(\"woff2\"),url(font/summernote.woff) format(\"woff\"),url(font/summernote.ttf) format(\"truetype\")}[class^=note-icon]:before,[class*=\" note-icon\"]:before{display:inline-block;font-family:summernote;font-style:normal;font-size:inherit;text-decoration:inherit;text-rendering:auto;text-transform:none;vertical-align:middle;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;speak:none}.note-icon-fw{text-align:center;width:1.25em}.note-icon-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.note-icon-pull-left{float:left}.note-icon-pull-right{float:right}.note-icon.note-icon-pull-left{margin-right:.3em}.note-icon.note-icon-pull-right{margin-left:.3em}.note-icon-align::before{content:\"\"}.note-icon-align-center::before{content:\"\"}.note-icon-align-indent::before{content:\"\"}.note-icon-align-justify::before{content:\"\"}.note-icon-align-left::before{content:\"\"}.note-icon-align-outdent::before{content:\"\"}.note-icon-align-right::before{content:\"\"}.note-icon-arrow-circle-down::before{content:\"\"}.note-icon-arrow-circle-left::before{content:\"\"}.note-icon-arrow-circle-right::before{content:\"\"}.note-icon-arrow-circle-up::before{content:\"\"}.note-icon-arrows-alt::before{content:\"\"}.note-icon-arrows-h::before{content:\"\"}.note-icon-arrows-v::before{content:\"\"}.note-icon-bold::before{content:\"\"}.note-icon-caret::before{content:\"\"}.note-icon-chain-broken::before{content:\"\"}.note-icon-circle::before{content:\"\"}.note-icon-close::before{content:\"\"}.note-icon-code::before{content:\"\"}.note-icon-col-after::before{content:\"\"}.note-icon-col-before::before{content:\"\"}.note-icon-col-remove::before{content:\"\"}.note-icon-eraser::before{content:\"\"}.note-icon-float-left::before{content:\"\"}.note-icon-float-none::before{content:\"\"}.note-icon-float-right::before{content:\"\"}.note-icon-font::before{content:\"\"}.note-icon-frame::before{content:\"\"}.note-icon-italic::before{content:\"\"}.note-icon-link::before{content:\"\"}.note-icon-magic::before{content:\"\"}.note-icon-menu-check::before{content:\"\"}.note-icon-minus::before{content:\"\"}.note-icon-orderedlist::before{content:\"\"}.note-icon-pencil::before{content:\"\"}.note-icon-picture::before{content:\"\"}.note-icon-question::before{content:\"\"}.note-icon-redo::before{content:\"\"}.note-icon-rollback::before{content:\"\"}.note-icon-row-above::before{content:\"\"}.note-icon-row-below::before{content:\"\"}.note-icon-row-remove::before{content:\"\"}.note-icon-special-character::before{content:\"\"}.note-icon-square::before{content:\"\"}.note-icon-strikethrough::before{content:\"\"}.note-icon-subscript::before{content:\"\"}.note-icon-summernote::before{content:\"\"}.note-icon-superscript::before{content:\"\"}.note-icon-table::before{content:\"\"}.note-icon-text-height::before{content:\"\"}.note-icon-trash::before{content:\"\"}.note-icon-underline::before{content:\"\"}.note-icon-undo::before{content:\"\"}.note-icon-unorderedlist::before{content:\"\"}.note-icon-video::before{content:\"\"}.note-editor{position:relative}.note-editor .note-dropzone{position:absolute;display:none;z-index:100;color:#87cefa;background-color:#fff;opacity:.95}.note-editor .note-dropzone .note-dropzone-message{display:table-cell;vertical-align:middle;text-align:center;font-size:28px;font-weight:700}.note-editor .note-dropzone.hover{color:#098ddf}.note-editor.dragover .note-dropzone{display:table}.note-editor .note-editing-area{position:relative}.note-editor .note-editing-area .note-editable{outline:none}.note-editor .note-editing-area .note-editable sup{vertical-align:super}.note-editor .note-editing-area .note-editable sub{vertical-align:sub}.note-editor .note-editing-area .note-editable img.note-float-left{margin-right:10px}.note-editor .note-editing-area .note-editable img.note-float-right{margin-left:10px}.note-editor.note-frame,.note-editor.note-airframe{border:1px solid #00000032}.note-editor.note-frame.codeview .note-editing-area .note-editable,.note-editor.note-airframe.codeview .note-editing-area .note-editable{display:none}.note-editor.note-frame.codeview .note-editing-area .note-codable,.note-editor.note-airframe.codeview .note-editing-area .note-codable{display:block}.note-editor.note-frame .note-editing-area,.note-editor.note-airframe .note-editing-area{overflow:hidden}.note-editor.note-frame .note-editing-area .note-editable,.note-editor.note-airframe .note-editing-area .note-editable{padding:10px;overflow:auto;word-wrap:break-word}.note-editor.note-frame .note-editing-area .note-editable[contenteditable=false],.note-editor.note-airframe .note-editing-area .note-editable[contenteditable=false]{background-color:#8080801d}.note-editor.note-frame .note-editing-area .note-codable,.note-editor.note-airframe .note-editing-area .note-codable{display:none;width:100%;padding:10px;border:none;box-shadow:none;font-family:Menlo,Monaco,monospace,sans-serif;font-size:14px;color:#ccc;background-color:#222;resize:none;outline:none;-ms-box-sizing:border-box;box-sizing:border-box;border-radius:0;margin-bottom:0}.note-editor.note-frame.fullscreen,.note-editor.note-airframe.fullscreen{position:fixed;top:0;left:0;width:100% !important;z-index:1050}.note-editor.note-frame.fullscreen .note-resizebar,.note-editor.note-airframe.fullscreen .note-resizebar{display:none}.note-editor.note-frame .note-status-output,.note-editor.note-airframe .note-status-output{display:block;width:100%;font-size:14px;line-height:1.42857143;height:20px;margin-bottom:0;color:#000;border:0;border-top:1px solid #e2e2e2}.note-editor.note-frame .note-status-output:empty,.note-editor.note-airframe .note-status-output:empty{height:0;border-top:0 solid transparent}.note-editor.note-frame .note-status-output .pull-right,.note-editor.note-airframe .note-status-output .pull-right{float:right !important}.note-editor.note-frame .note-status-output .text-muted,.note-editor.note-airframe .note-status-output .text-muted{color:#777}.note-editor.note-frame .note-status-output .text-primary,.note-editor.note-airframe .note-status-output .text-primary{color:#286090}.note-editor.note-frame .note-status-output .text-success,.note-editor.note-airframe .note-status-output .text-success{color:#3c763d}.note-editor.note-frame .note-status-output .text-info,.note-editor.note-airframe .note-status-output .text-info{color:#31708f}.note-editor.note-frame .note-status-output .text-warning,.note-editor.note-airframe .note-status-output .text-warning{color:#8a6d3b}.note-editor.note-frame .note-status-output .text-danger,.note-editor.note-airframe .note-status-output .text-danger{color:#a94442}.note-editor.note-frame .note-status-output .alert,.note-editor.note-airframe .note-status-output .alert{margin:-7px 0 0 0;padding:7px 10px 2px 10px;border-radius:0;color:#000;background-color:#f5f5f5}.note-editor.note-frame .note-status-output .alert .note-icon,.note-editor.note-airframe .note-status-output .alert .note-icon{margin-right:5px}.note-editor.note-frame .note-status-output .alert-success,.note-editor.note-airframe .note-status-output .alert-success{color:#3c763d !important;background-color:#dff0d8 !important}.note-editor.note-frame .note-status-output .alert-info,.note-editor.note-airframe .note-status-output .alert-info{color:#31708f !important;background-color:#d9edf7 !important}.note-editor.note-frame .note-status-output .alert-warning,.note-editor.note-airframe .note-status-output .alert-warning{color:#8a6d3b !important;background-color:#fcf8e3 !important}.note-editor.note-frame .note-status-output .alert-danger,.note-editor.note-airframe .note-status-output .alert-danger{color:#a94442 !important;background-color:#f2dede !important}.note-editor.note-frame .note-statusbar,.note-editor.note-airframe .note-statusbar{background-color:#8080801d;border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-top:1px solid #00000032}.note-editor.note-frame .note-statusbar .note-resizebar,.note-editor.note-airframe .note-statusbar .note-resizebar{padding-top:1px;height:9px;width:100%;cursor:ns-resize}.note-editor.note-frame .note-statusbar .note-resizebar .note-icon-bar,.note-editor.note-airframe .note-statusbar .note-resizebar .note-icon-bar{width:20px;margin:1px auto;border-top:1px solid #00000032}.note-editor.note-frame .note-statusbar.locked .note-resizebar,.note-editor.note-airframe .note-statusbar.locked .note-resizebar{cursor:default}.note-editor.note-frame .note-statusbar.locked .note-resizebar .note-icon-bar,.note-editor.note-airframe .note-statusbar.locked .note-resizebar .note-icon-bar{display:none}.note-editor.note-frame .note-placeholder,.note-editor.note-airframe .note-placeholder{padding:10px}.note-editor.note-airframe{border:0}.note-editor.note-airframe .note-editing-area .note-editable{padding:0}.note-popover.popover{display:none;max-width:none}.note-popover.popover .popover-content a{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.note-popover.popover .arrow{left:20px !important}.note-toolbar{position:relative}.note-popover .popover-content,.note-editor .note-toolbar{margin:0;padding:0 0 5px 5px}.note-popover .popover-content>.note-btn-group,.note-editor .note-toolbar>.note-btn-group{margin-top:5px;margin-left:0;margin-right:5px}.note-popover .popover-content .note-btn-group .note-table,.note-editor .note-toolbar .note-btn-group .note-table{min-width:0;padding:5px}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker{font-size:18px}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-mousecatcher,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-mousecatcher{position:absolute !important;z-index:3;width:10em;height:10em;cursor:pointer}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-unhighlighted,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-unhighlighted{position:relative !important;z-index:1;width:5em;height:5em;background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIj4+Pjp6ekKlAqjAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKhmnaJzPAAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC\") repeat}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-highlighted,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-highlighted{position:absolute !important;z-index:2;width:1em;height:1em;background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIjd6vvD2f9LKLW+AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKwNDEVT0AAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC\") repeat}.note-popover .popover-content .note-style .dropdown-style blockquote,.note-popover .popover-content .note-style .dropdown-style pre,.note-editor .note-toolbar .note-style .dropdown-style blockquote,.note-editor .note-toolbar .note-style .dropdown-style pre{margin:0;padding:5px 10px}.note-popover .popover-content .note-style .dropdown-style h1,.note-popover .popover-content .note-style .dropdown-style h2,.note-popover .popover-content .note-style .dropdown-style h3,.note-popover .popover-content .note-style .dropdown-style h4,.note-popover .popover-content .note-style .dropdown-style h5,.note-popover .popover-content .note-style .dropdown-style h6,.note-popover .popover-content .note-style .dropdown-style p,.note-editor .note-toolbar .note-style .dropdown-style h1,.note-editor .note-toolbar .note-style .dropdown-style h2,.note-editor .note-toolbar .note-style .dropdown-style h3,.note-editor .note-toolbar .note-style .dropdown-style h4,.note-editor .note-toolbar .note-style .dropdown-style h5,.note-editor .note-toolbar .note-style .dropdown-style h6,.note-editor .note-toolbar .note-style .dropdown-style p{margin:0;padding:0}.note-popover .popover-content .note-color-all .note-dropdown-menu,.note-editor .note-toolbar .note-color-all .note-dropdown-menu{min-width:337px}.note-popover .popover-content .note-color .dropdown-toggle,.note-editor .note-toolbar .note-color .dropdown-toggle{width:20px;padding-left:5px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette{display:inline-block;margin:0;width:160px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette:first-child,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette:first-child{margin:0 5px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-palette-title,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-palette-title{font-size:12px;margin:2px 7px;text-align:center;border-bottom:1px solid #eee}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-reset,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-reset,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select{font-size:11px;margin:3px;padding:0 3px;cursor:pointer;width:100%;border-radius:5px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-reset:hover,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select:hover,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-reset:hover,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select:hover{background:#eee}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-row,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-row{height:20px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select-btn,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select-btn{display:none}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn{border:1px solid #eee}.note-popover .popover-content .note-para .note-dropdown-menu,.note-editor .note-toolbar .note-para .note-dropdown-menu{min-width:228px;padding:5px}.note-popover .popover-content .note-para .note-dropdown-menu>div+div,.note-editor .note-toolbar .note-para .note-dropdown-menu>div+div{margin-left:5px}.note-popover .popover-content .note-dropdown-menu,.note-editor .note-toolbar .note-dropdown-menu{min-width:160px}.note-popover .popover-content .note-dropdown-menu.right,.note-editor .note-toolbar .note-dropdown-menu.right{right:0;left:auto}.note-popover .popover-content .note-dropdown-menu.right::before,.note-editor .note-toolbar .note-dropdown-menu.right::before{right:9px;left:auto !important}.note-popover .popover-content .note-dropdown-menu.right::after,.note-editor .note-toolbar .note-dropdown-menu.right::after{right:10px;left:auto !important}.note-popover .popover-content .note-dropdown-menu.note-check a i,.note-editor .note-toolbar .note-dropdown-menu.note-check a i{color:#00bfff;visibility:hidden}.note-popover .popover-content .note-dropdown-menu.note-check a.checked i,.note-editor .note-toolbar .note-dropdown-menu.note-check a.checked i{visibility:visible}.note-popover .popover-content .note-fontsize-10,.note-editor .note-toolbar .note-fontsize-10{font-size:10px}.note-popover .popover-content .note-color-palette,.note-editor .note-toolbar .note-color-palette{line-height:1}.note-popover .popover-content .note-color-palette div .note-color-btn,.note-editor .note-toolbar .note-color-palette div .note-color-btn{width:20px;height:20px;padding:0;margin:0;border:0;border-radius:0}.note-popover .popover-content .note-color-palette div .note-color-btn:hover,.note-editor .note-toolbar .note-color-palette div .note-color-btn:hover{transform:scale(1.2);transition:all .2s}.note-modal .modal-dialog{outline:0;border-radius:5px;box-shadow:0 3px 9px rgba(0,0,0,.5)}.note-modal .form-group{margin-left:0;margin-right:0}.note-modal .note-modal-form{margin:0}.note-modal .note-image-dialog .note-dropzone{min-height:100px;font-size:30px;line-height:4;color:#d3d3d3;text-align:center;border:4px dashed #d3d3d3;margin-bottom:10px}@-moz-document url-prefix(){.note-modal .note-image-input{height:auto}}.note-placeholder{position:absolute;display:none;color:gray}.note-handle .note-control-selection{position:absolute;display:none;border:1px solid #000}.note-handle .note-control-selection>div{position:absolute}.note-handle .note-control-selection .note-control-selection-bg{width:100%;height:100%;background-color:#000;-webkit-opacity:.3;-khtml-opacity:.3;-moz-opacity:.3;opacity:.3;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=30);filter:alpha(opacity=30)}.note-handle .note-control-selection .note-control-handle,.note-handle .note-control-selection .note-control-sizing,.note-handle .note-control-selection .note-control-holder{width:7px;height:7px;border:1px solid #000}.note-handle .note-control-selection .note-control-sizing{background-color:#000}.note-handle .note-control-selection .note-control-nw{top:-5px;left:-5px;border-right:none;border-bottom:none}.note-handle .note-control-selection .note-control-ne{top:-5px;right:-5px;border-bottom:none;border-left:none}.note-handle .note-control-selection .note-control-sw{bottom:-5px;left:-5px;border-top:none;border-right:none}.note-handle .note-control-selection .note-control-se{right:-5px;bottom:-5px;cursor:se-resize}.note-handle .note-control-selection .note-control-se.note-control-holder{cursor:default;border-top:none;border-left:none}.note-handle .note-control-selection .note-control-selection-info{right:0;bottom:0;padding:5px;margin:5px;color:#fff;background-color:#000;font-size:12px;border-radius:5px;-webkit-opacity:.7;-khtml-opacity:.7;-moz-opacity:.7;opacity:.7;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=70);filter:alpha(opacity=70)}.note-hint-popover{min-width:100px;padding:2px}.note-hint-popover .popover-content{padding:3px;max-height:150px;overflow:auto}.note-hint-popover .popover-content .note-hint-group .note-hint-item{display:block !important;padding:3px}.note-hint-popover .popover-content .note-hint-group .note-hint-item.active,.note-hint-popover .popover-content .note-hint-group .note-hint-item:hover{display:block;clear:both;font-weight:400;line-height:1.4;color:#fff;white-space:nowrap;text-decoration:none;background-color:#428bca;outline:0;cursor:pointer}.note-toolbar{background:#8080801d}.note-btn-group .note-btn{border-color:#00000032;padding:.28rem .65rem;font-size:13px}\n"
  },
  {
    "path": "src/screenshotbot/dag/dag.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :dag\n  :serial t\n  :depends-on (:pkg\n               :util/simple-queue\n               :util.threading\n               :util.misc\n               :cl-json\n               :util.store/encodable\n               :bknr.datastore\n               :ironclad)\n  :components ((:file \"package\")\n               (:file \"dag\")))\n\n(defsystem :dag/tests\n  :serial t\n  :depends-on (:dag\n               :fiveam\n               :fiveam-matchers\n               :util/fiveam)\n  :components ((:file \"test-dag\")))\n"
  },
  {
    "path": "src/screenshotbot/dag/dag.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :dag)\n\n(define-condition node-already-exists (error)\n  ())\n\n(defvar *magic* (flex:string-to-octets \"bdag\" :external-format :latin1))\n\n(defclass commit (encodable)\n  ((sha :initarg :sha\n        :accessor sha)\n   (author :initarg :author\n           :initform nil\n           :accessor author)\n   (ts :initarg :ts\n       :initform 0\n       :accessor commit-timestamp\n       :documentation \"The timestamp this commit was first seen\")\n   (parents :initarg :parents\n            :initform nil\n            :accessor parents\n            :documentation \"array of shas\"))\n  (:default-initargs :ts (get-universal-time)))\n\n(defmacro pp (x)\n  `(let ((y ,x))\n     ;;(log:info \"Got ~S for ~S\" y ',x)\n     y))\n\n(defvar *node-id-cache* (make-hash-table :test #'equal #+sbcl :synchronized #+sbcl t))\n\n(defun commit-node-id (x)\n  \"The node id used for queries. This is a historical artifact of when we\nused cl-graph. In the future, we should move to using the string as\nthe node-id directly.\"\n  (check-type x string)\n  (assert (> (length x) 0))\n  (assert x)\n  (util/misc:or-setf\n   (gethash x *node-id-cache*)\n   (when-let ((arr\n               (ignore-errors\n                (ironclad:hex-string-to-byte-array x))))\n     (let ((ret 0))\n       (loop for i across arr do\n         (setf ret\n               (+ (ash ret 8) i)))\n       ret))))\n\n(defclass dag (encodable)\n  ((commits :initform (make-hash-table :test 'equal)\n            :initarg :commit-map\n            :accessor commit-map\n            :documentation \"A map from COMMIT-NODE-ID (number) to COMMIT. We eventually plan to replace this with sha to COMMIT.\"))\n  (:documentation \"\nWARNING!\n\nEven though we call this a DAG, you must not assume it's Acyclic. Sorry :(\n\nThere's limited checks to how users can construct the graph, so by the\ntime you're running graph algorithms on it, it might actually be a\ncyclical graph.\n\"))\n\n(defmethod clone-dag ((dag dag))\n  \"This is not bad in terms of performance! But obviously, we can optimize this in future with either FSET of CoW.\"\n  (make-instance\n   'dag\n   :commit-map (alexandria:copy-hash-table (commit-map dag))))\n\n(defmethod all-commits ((dag dag))\n  (loop for commit being the hash-values of (commit-map dag)\n        collect commit))\n\n(defmethod ordered-commits ((dag dag))\n  \"This function isn't being used in any non-debug paths (only in health\nchecks and on the commit graph debug page.\"\n  (let ((sorted (safe-topological-sort dag :use-dfs-p t)))\n    (loop for id in sorted\n          collect (gethash id (commit-map dag)))))\n\n(defmethod get-commit ((dag dag) (sha string))\n  (when-let ((id (commit-node-id sha)))\n    (log:debug \"Commit id for ~a is ~a\" sha id)\n    (gethash id (commit-map dag))))\n\n(defmethod get-commit-with-parent ((dag dag) (parent-sha string))\n  \"Find a commit that has the given parent-sha as one of its parents. For\ndebugging only.\"\n  (loop for commit being the hash-values of (commit-map dag)\n        when (member parent-sha (parents commit) :test #'equal)\n          return commit))\n\n(defmethod bfs-search ((dag dag) start end)\n  \"This is technically an iterative DFS. We could replace this with\nREACHABLE-NODES, or use a simple-queue instead of the stack, but for\nnow I'm avoiding this, because it can be hard to test edge cases of\nthis behavior in prod.. In the future, consider making either of these\nchanges.\"\n  (let ((seen (make-hash-table :test 'equal))\n        (stack nil))\n    (setf (gethash start seen) t)\n    (push start stack)\n    (loop while stack do\n      (let ((next (pop stack)))\n        (when (equal (commit-node-id end)\n                     (commit-node-id next))\n          (return-from bfs-search t))\n        (loop for parent in (alexandria:if-let (commit (get-commit dag next))\n                              (parents commit)\n                              (progn\n                                (log:info \"Commit ~a not in graph\" next)\n                                nil))\n              do\n                 (unless (gethash parent seen)\n                   (setf (gethash parent seen) t)\n                   (push parent stack)))))\n    nil))\n\n\n(defmethod ancestorp ((dag dag) (sha-old string) (sha-new string))\n  (bfs-search\n   dag\n   sha-new\n   sha-old))\n\n(defmethod add-commit ((dag dag) (commit  commit))\n  (assert commit)\n  (let* ((map (commit-map dag))\n         (node-id (commit-node-id (sha commit)))\n         (existing-node (gethash node-id map)))\n    (cond\n      ((and existing-node\n            ;; But we're allowed to overwrite shallow nodes\n            (dag:parents existing-node))\n       (error 'node-already-exists))\n      (t\n       (setf\n        (gethash node-id map)\n        commit)\n       (loop for p in (parents commit) do\n         (progn\n           #+nil(log:info \"adding edge from: ~S to ~S\" node-id\n                          (commit-node-id p))\n           (assert (numberp node-id))\n           (assert (numberp (commit-node-id p)))))))))\n\n(defmethod full-commit-on-graph-p ((dag dag) node-id)\n  \"Not all nodes in the graph are \\\"complete\\\". Some nodes are referenced\nby other nodes, but are not themselves available. This returns if the\nnode-id is a valid full node on the graph.\"\n  (gethash node-id (commit-map dag)))\n\n(defun %dfs-topo-sort-from-starting-nodes (dag start-nodes)\n  \"This function is mainly to make the starting of the DFS\ndeterministic, which makes it easier to test.\n\nThe reason this code is so hard to read: I first wrote a basic\nrecursive DFS. I then replaced it with a continuation passing style\nversion. Finally, I refactored it with a stack (where the stack is a\nstack of lambdas, not the state itself).\"\n  (let ((visited (make-hash-table :test #'equal))\n        (result nil)\n        (stack nil))\n    (labels ((dfs (sha callback)\n               (block nil\n                (let ((commit (get-commit dag sha)))\n                  (cond\n                    ((and commit (not (gethash commit visited)))\n                     (setf (gethash commit visited) t)\n                    \n                     (let ((parents (dag:parents commit)))\n                       (labels ((process-end ()\n                                  (push sha result)\n                                  (push callback stack))\n                                (process-next-parent ()\n                                  (cond\n                                    (parents\n                                     (let ((parent (pop parents)))\n                                       (push\n                                        (lambda ()\n                                          (dfs parent #'process-next-parent))\n                                        stack)))\n                                    (t\n                                     (process-end)))))\n                         (process-next-parent))))\n                    (t\n                     (push callback stack)))))))\n\n      (labels ((process-stack (start)\n                 (push start stack)\n                 (loop while stack\n                       do\n                          (let ((next (pop stack)))\n                            (funcall next)))))\n        (loop for commit in start-nodes\n              do\n                 (process-stack\n                  (lambda ()\n                    (?. dfs (dag:sha commit)  (lambda ())))))))\n    (mapcar #'commit-node-id result)))\n\n(defmethod dfs-topo-sort (dag)\n  \"This is modified from GRAPH. Sadly that library uses recursion for\nsome of their graph algorithms, which doesn't work nicely for a 'deep'\ntree. This version uses the Kahn's algorithm instead of DFS\n\nReturns the \\\"newest\\\" first and ancestors last. \"\n  ;;(declare (optimize (speed 3) (debug 0)))\n  (%dfs-topo-sort-from-starting-nodes\n   dag\n   (sort\n    (loop for x being the hash-values of (commit-map dag)\n          collect x)\n    ;; Why start at the oldest commit? That way those paths gets\n    ;; DFS-ed first, and gets pushed onto the result earlier.\n    #'<\n    :key\n    (lambda (commit)\n      (handler-case\n          (commit-timestamp commit)\n        (unbound-slot ()\n          -1))))))\n\n(defmethod kahns-algo-topo-sort (dag)\n  (let* ((rL nil)\n         (out-degrees (make-hash-table :test #'equal))\n         (children (make-hash-table :test #'equal)))\n\n    (loop for commit being the hash-values of (commit-map dag)\n          do\n             (setf (gethash (sha commit) out-degrees)\n                   (length (parents commit)))\n             (loop for parent in (parents commit) do\n               ;; Ensure that parent nodes are present in here too\n               (symbol-macrolet ((slot (gethash parent out-degrees)))\n                 (unless slot\n                   (setf slot 0)))\n\n               ;; build precedents map\n               (push (sha commit)\n                     (gethash parent children nil))))\n\n    (let ((S (loop for x being the hash-keys of out-degrees\n                   if (eql 0 (gethash x out-degrees))\n                     collect x)))\n      (loop while S do\n        (progn\n          (let ((n (pop S)))\n            (push n rL)\n            (dolist (m (gethash n children))\n              (decf (gethash m out-degrees))\n              (assert (>= (gethash m out-degrees) 0))\n              (when (eql (gethash m out-degrees) 0)\n                (push m S)))))))\n    (remove-if-not (curry #'full-commit-on-graph-p dag)\n                   (mapcar #'commit-node-id rL))))\n\n(defvar *use-dfs-p* nil\n  \"We needed the DFS version of topo sort just for rending the graph,\nbecause it makes for a better human readable graph.\n\nHowever, the Kahn's algorithm version is much better tested and I\ndon't want to switch it yet in the hot path. There are a bunch of\ncases.\")\n\n(defmethod safe-topological-sort (dag &key (use-dfs-p *use-dfs-p*))\n  \"This is modified from GRAPH. Sadly that library uses recursion for\nsome of their graph algorithms, which doesn't work nicely for a 'deep'\ntree. This version uses the Kahn's algorithm instead of DFS\"\n  (cond\n    (use-dfs-p\n     (dfs-topo-sort dag))\n    (t\n     (kahns-algo-topo-sort dag))))\n\n(defmethod write-to-stream ((dag dag) stream &key (format :json))\n  (let ((sorted-nodes (loop for node-id being the hash-keys of (commit-map dag)\n                            collect node-id))\n        (commit-map (commit-map dag)))\n    (ecase format\n      (:json\n       (json:encode-json\n        `((:commits .\n                    ,(loop for node-id in sorted-nodes\n                           for commit = (gethash node-id commit-map)\n                           if commit\n                             collect\n                           `((:sha . ,(sha commit))\n                             (:author . ,(author commit))\n                             (:parents . ,(parents commit)))))\n          (:dummy . \"0\"))\n        stream))\n      (:binary\n       (write-sequence\n        *magic*\n        stream)\n       (write-byte 0 stream)\n       (let ((version 1))\n         (write-byte version stream))\n       (encode-integer (length sorted-nodes) stream)\n       (dolist (node-id sorted-nodes)\n         (let ((commit (gethash node-id commit-map)))\n           ;; TODO: can be further optimized to use integers, but this\n           ;; should do for now\n           (encode (sha commit) stream)\n           (encode (author commit) stream)\n           (encode (parents commit) stream))))))\n  (finish-output stream))\n\n(defun assert-commit (commit line)\n  (unless (eql 40 (length commit))\n    (error \"`~a` does not look like a valid Git commit SHA1 string, read from `~a`\"\n           commit\n           line)))\n\n\n(defun read-from-stream (stream &key (format :json))\n  (let ((dag (make-instance 'dag)))\n    (ecase format\n     (:json\n      (let ((data (json:decode-json stream)))\n        (loop for commit in (assoc-value data :commits) do\n          (add-commit dag\n                      (apply 'make-instance 'commit\n                             (alist-plist commit))))\n        dag))\n     (:binary\n      (let ((magic (make-array 4 :element-type '(unsigned-byte 8))))\n        (read-sequence magic stream)\n        (assert (equalp magic *magic*)))\n      ;; version\n      (read-byte stream)\n      (let ((version (read-byte stream)))\n        (assert (= 1 version)))\n      (let ((length (decode stream)))\n        (dotimes (i length)\n          (let ((sha (decode stream))\n                (author (decode stream))\n                (parents (decode stream)))\n            (add-commit dag\n                        (make-instance 'commit\n                                       :sha sha\n                                       :author author\n                                       :parents parents)))))\n      dag)\n     (:text\n      (loop for line = (read-line stream nil)\n            while (and line\n                       (not (str:emptyp (str:trim line))))\n            do\n               (destructuring-bind (sha &rest parents) (str:split \" \" (str:trim line))\n                 (assert-commit sha line)\n                 (loop for parent in parents do\n                   (assert-commit parent line))\n                 (dag:add-commit dag (make-instance 'dag:commit\n                                                    :sha sha\n                                                    :parents parents))))\n      dag))))\n\n(defmethod merge-dag ((dag dag) (from-dag dag))\n  (loop for node-id being the hash-keys of (commit-map from-dag)\n        do\n           (unless (gethash node-id (commit-map dag))\n             (let ((commit (gethash node-id (commit-map from-dag))))\n               (assert commit)\n               (add-commit dag commit)\n               (assert (gethash node-id (commit-map dag)))))))\n\n(defmethod dag-difference ((dag dag) (other dag))\n  (let ((result (make-instance 'dag)))\n    (dolist (commit (all-commits dag))\n      (let ((other-commit (get-commit other (sha commit))))\n        (unless (and other-commit\n                     (dag:parents other-commit))\n          (add-commit result commit))))\n    result))\n\n(defun listify (x)\n  (if (listp x)\n      x\n      (list x)))\n\n(defmethod reachable-nodes ((dag dag) commits &key (depth 1000)\n                                                (seen-callback #'identity)\n                                                excludes)\n  \"Find all the reachable nodes from a specific commit, upto the given DEPTH. Does not return partial nodes (i.e. commits whose information we don't have).\n\nCOMMITS can either be a single commit or a list of commits.\n\nIf SEEN-CALLBACK is provided, then that is called with the commit each\ntime we see a new node. This will include partial nodes.\n\nEXCLUDES is an optional list of SHAs. If provided, we don't find reachable nodes that require a\npath via any SHA in EXCLUDES.\n\"\n  (let ((commits (listify commits))\n        (depths (make-hash-table :test #'equal))\n        (sha-seen (make-hash-table :test #'equal))\n        (queue (make-queue)))\n\n    (loop for sha in excludes\n          do (setf (gethash sha sha-seen) t))\n\n    \n    (dolist (commit commits)\n      (enqueue commit queue)\n      (setf (gethash commit depths) 1))\n\n    (macrolet ((sha-seen (hash)\n                 `(gethash ,hash sha-seen)))\n      (dolist (commit commits)\n        (setf (sha-seen commit) t))\n\n      (loop while (not (queue-emptyp queue))\n            for sha = (dequeue queue)\n           do\n              (let ((commit (get-commit dag sha))\n                    (this-depth (gethash sha depths)))\n                (cond\n                  (commit\n                   (funcall seen-callback commit)\n                   (when (< this-depth depth)\n                     (loop for parent in (parents commit) do\n                       (unless (gethash parent depths)\n                         (setf (gethash parent depths) (1+ this-depth)))\n                       (unless (sha-seen parent)\n                         (setf (sha-seen parent) t)\n                         (enqueue parent queue)))))\n                  (t\n                   ;; This node isn't present in the graph, but we\n                   ;; still want to indicate that we've seen it.\n                   (funcall seen-callback\n                            (make-instance 'commit :sha sha)))))))\n    (loop for sha in excludes\n          do (remhash sha sha-seen))\n    (loop for sha being the hash-keys of sha-seen\n          for node = (get-commit dag sha)\n          if node\n            collect node)))\n\n(defun hash-table-intersection (ht1 ht2)\n  (let ((result (make-hash-table)))\n   (loop for k being the hash-keys of ht1\n         if (gethash k ht2)\n           do (setf (gethash k result) t))\n    result))\n\n(defun list-to-hash-table (list)\n  (let ((hash-table (make-hash-table)))\n    (loop for elem in list\n          do (setf (gethash elem hash-table) t))\n    hash-table))\n\n(defmethod merge-bases-for-depth ((dag dag) commit-1s commit-2\n                                  &key\n                                    depth\n                                    exclude-commit-2)\n  \"See https://stackoverflow.com/questions/14865081/algorithm-to-find-lowest-common-ancestor-in-directed-acyclic-graph\n\nHowever, here's a better description. Find all the ancestors, this\nforms a subgraph. Now find all the source nodes of that subgraph.\"\n  (with-extras ((\"commit-1\" commit-1s)\n                (\"commit-2\" commit-2))\n   (let* ((excludes (when exclude-commit-2\n                      (list commit-2)))\n          (reachable-1 (list-to-hash-table (reachable-nodes dag commit-1s :depth depth\n                                                                          :excludes excludes)))\n          (reachable-2 (list-to-hash-table (reachable-nodes dag commit-2 :depth depth))))\n     (let ((intersection (hash-table-intersection reachable-1 reachable-2))\n           (definitely-not-source-nodes (make-hash-table :test #'equal)))\n       (loop for commit being the hash-keys of intersection\n             do\n                (dolist (parent (parents commit))\n                  (setf (gethash parent definitely-not-source-nodes) t)))\n\n       (let ((merge-bases (loop for commit being the hash-keys of intersection\n                                unless (gethash (sha commit) definitely-not-source-nodes)\n                                  collect (sha commit))))\n         merge-bases)))))\n\n(defmethod merge-base ((dag dag) commit-1 commit-2\n                       &key (exclude-commit-2 t))\n  \"Find the merge-base for merging commit-2 into the branch indicated\nwith commit-1. Note that the merge-base might not be symmetrical. If\nthere are multiple equivalent merge-bases, then one will be returned\narbitrarily.\n\nCOMMIT-1 can either be a string or a list of strings. If it's a list,\nthen it's equivalent to a new commit that has all those commits as\nparents. If COMMIT-1 is empty, then we always return NIL.\n\nIf EXCLUDE-COMMIT-2 is true, then we'll exclude any node that's only\nan ancestor of COMMIT-1 via COMMIT-2. This is useful for pull-requests\nthat might've already merged.\"\n\n  (flet ((check-commit (commits)\n           (loop for commit in (listify commits)\n                 do\n                    (unless (dag:get-commit dag commit)\n                      (warn \"The commit ~a is not in the dag. Potentially an issue with the commit-graph flow.\" commit)))))\n    (check-commit commit-1)\n    (check-commit commit-2))\n  (let ((commit-1 (listify commit-1)))\n   (cond\n     ((equal commit-1 (list commit-2))\n      ;; Technically this is incorrect when EXCLUDE-COMMIT-2 is provided,\n      ;; but abstract-pr-promoter depends on this behavior.\n      commit-2)\n     (t\n      (let* ((merge-bases\n               (flet ((try-depth (depth)\n                        (merge-bases-for-depth dag commit-1 commit-2\n                                               :depth depth\n                                               :exclude-commit-2 exclude-commit-2)))\n                 (or\n                  ;; Micro optimization: First look only at the last 100 nodes.\n                  (try-depth 100)\n                  (try-depth 1000)\n                  ;; This likely means that all paths from commit-1 go\n                  ;; through commit-2.\n                  (loop for commit-1 in commit-1\n                   if (ancestorp dag commit-2 commit-1)\n                       return (list commit-2))))))\n        (let ((merge-bases (sort (copy-list merge-bases)\n                                 #'>\n                                 :key (lambda (sha)\n                                        (commit-timestamp (get-commit dag sha))))))\n          (when (> (length merge-bases) 1)\n            (with-extras ((\"result-merge-bases\" merge-bases))\n             (warn \"fun warning! we hit multiple merge-bases in production!\")))\n          (values (car merge-bases)\n                  merge-bases)))))))\n\n(defmethod best-path ((dag dag) (sha-1 string) (sha-2 string) &key (max-depth 100))\n  \"Find the best path from SHA-1 to SHA-2, where SHA-2 is the ancestor.\n\nGiven two paths, x0->x1->....->xn and y0->y1->...>yn, find the first\nposition `i` (0<i<n) where they differ. Look at the edges from\nx_{i-1}->x_i and y{i-1}->y_i. Whichever one is the leftmost parent,\nthat path is the better path.\n\nThis approximately translates to trying to find a path between the\ncommits using leftmost parents when possible.\n\nBy this definition, the best solution is to use a DFS. (An alternate\ndefinition is to give a high cost to the non-leftmost\nparents. However, it adds more complexity, and does not guarantee a\nunique solution.)\n\nWe don't attempt to find a path if the path is longer than\nMAX-DEPTH. This makes it acceptable to use recursion for now.\n\"\n  (let ((seen (make-hash-table :test #'equal)))\n    (labels ((dfs (sha max-depth path)\n               (let ((parents (?. parents (get-commit dag sha))))\n                 (cond\n                   ((gethash sha seen)\n                    nil)\n                   ((<= max-depth 0)\n                    ;; could not find a path\n                    nil)\n                   ((equal sha sha-2)\n                    (list* sha path))\n                   (t\n                    (setf (gethash sha seen) t)\n                    (loop for parent in parents\n                          for solved-path = (dfs parent (1- max-depth) (list* sha path))\n                          if solved-path\n                            return solved-path)))))\n             )\n      (reverse\n       (dfs sha-1 max-depth nil)))))\n"
  },
  {
    "path": "src/screenshotbot/dag/package.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :dag\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:bknr.datastore\n                #:encode-integer)\n  (:import-from #:bknr.datastore\n                #:%decode-integer)\n  (:import-from #:bknr.datastore\n                #:encode-object)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:bknr.datastore\n                #:encode-string)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:alexandria\n                #:curry)\n  (:import-from #:util/simple-queue\n                #:dequeue\n                #:queue-emptyp\n                #:enqueue\n                #:make-queue)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:util/store/encodable\n                #:encodable)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export #:dag\n           #:add-commit\n           #:commit\n           #:merge-dag\n           #:parents\n           #:ancestorp\n           #:get-commit\n           #:write-to-stream\n           #:read-from-stream\n           #:ordered-commits\n           #:sha\n           #:author\n           #:merge-base\n           #:best-path\n           #:dag-difference\n           #:clone-dag\n           #:all-commits\n           #:commit-timestamp))\n(in-package :dag)\n"
  },
  {
    "path": "src/screenshotbot/dag/test-dag.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dag/test-dag\n  (:use #:cl\n        #:dag\n        #:fiveam\n        #:fiveam-matchers)\n  (:import-from #:dag\n                #:%dfs-topo-sort-from-starting-nodes\n                #:dfs-topo-sort\n                #:*use-dfs-p*\n                #:commit-timestamp\n                #:all-commits\n                #:dag-difference\n                #:best-path\n                #:ancestorp\n                #:merge-base\n                #:reachable-nodes\n                #:assert-commit\n                #:ordered-commits\n                #:commit-map\n                #:commit-node-id\n                #:node-id\n                #:safe-topological-sort\n                #:dag\n                #:merge-dag\n                #:get-commit\n                #:commit\n                #:node-already-exists\n                #:add-commit)\n  (:import-from #:flexi-streams\n                #:with-output-to-sequence)\n  (:import-from #:fiveam-matchers/errors\n                #:error-with-string-matching)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:has-typep\n                #:has-any\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:contains)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string))\n(in-package :screenshotbot/dag/test-dag)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (flet ((run-inner ()\n           (let ((dag (make-instance 'dag)))\n             (flet ((add-edge (from to &key (dag dag))\n                      (add-commit dag (make-instance 'commit :sha from :parents (cond\n                                                                                  ((listp to)\n                                                                                   to)\n                                                                                  (t (list to)))\n                                                             :author \"Arnold Noronha <arnold@tdrhq.com>\"))))\n               (add-edge \"bb\" nil)\n               (add-edge \"aa\" \"bb\")\n               (&body)))))\n    (let ((dag::*use-dfs-p* nil))\n      (run-inner))\n    (let ((dag::*use-dfs-p* t))\n      (run-inner))))\n\n(defun make-big-linear-dag (&key (num 100))\n  (let* ((dag (make-instance 'dag))\n         (commits (loop for i from 1 to num collect\n                                            (format nil \"~4,'0X\" i)))\n         (fns nil))\n    (flet ((add-edge (from to)\n             (add-commit dag (make-instance 'commit :sha from :parents (cond\n                                                                         ((listp to)\n                                                                          to)\n                                                                         (t (list to)))))))\n      (loop for to in commits\n            for from in (cdr commits)\n            do\n               (let ((to to) (from from))\n                 (push\n                  (lambda ()\n                    (add-edge from to))\n                  fns)))\n\n      (add-edge (car commits) nil))\n\n    (loop for fn in (reverse fns)\n          do (funcall fn))\n\n    (values dag commits)))\n\n(defmacro pp (x)\n  `(let ((y ,x))\n     (log:info \"Got ~S for ~S\" y ',x)\n     y))\n\n(test preconditions\n  (with-fixture state ()\n    (pass)))\n\n(test wont-let-me-add-the-same-nodes-again\n  (with-fixture state ()\n    (signals node-already-exists\n      (add-edge \"aa\" \"bb\"))))\n\n(test allows-me-to-overwrite-shallow-nodes\n  (with-fixture state ()\n    (is (equal nil\n               (dag:parents (get-commit dag \"bb\"))))\n    (add-edge \"bb\" \"cc\")\n    (is (equal\n         (list \"cc\")\n         (dag:parents (get-commit dag \"bb\"))))))\n\n(test allows-me-to-overwrite-shallow-nodes-with-the-same-shallow-node\n  (with-fixture state ()\n    (is (equal nil\n               (dag:parents (get-commit dag \"bb\"))))\n    (add-edge \"bb\" nil)\n    (is (equal\n         nil\n         (dag:parents (get-commit dag \"bb\"))))))\n\n(test serialize\n  (with-fixture state ()\n    (let ((json (with-output-to-string (s)\n                  (write-to-stream dag s))))\n      (is (equal `(((:sha . \"bb\")\n                    (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n                    (:parents . nil))\n                   ((:sha . \"aa\")\n                    (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n                    (:parents . ,(list \"bb\"))))\n                 (assoc-value (json:decode-json-from-string json)\n                              :commits))))))\n\n(test technically-we-can-still-create-a-cyclical-graph\n  (with-fixture state ()\n    (add-edge \"cc\" \"dd\")\n    (finishes\n     (add-edge \"dd\" \"cc\"))))\n\n(test serialize-incomplete-graph\n  (with-fixture state ()\n    (add-edge \"cc\" (list \"aa\" \"dd\"))\n    (let ((json (with-output-to-string (s)\n                  (write-to-stream dag s))))\n      (assert-that\n       (assoc-value (json:decode-json-from-string json)\n                    :commits)\n       (apply #'contains-in-any-order\n        `(((:sha . \"bb\")\n           (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n           (:parents . nil))\n          ((:sha . \"aa\")\n           (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n           (:parents . ,(list \"bb\")))\n          ((:sha . \"cc\")\n           (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n           (:parents . ,(list \"aa\" \"dd\"))))\n        ))\n      (let ((dag (read-from-stream (make-string-input-stream json))))\n        (is (typep dag 'dag))))))\n\n(test serialize-binary\n  (with-fixture state ()\n    (let ((output (flex:with-output-to-sequence (s)\n                    (write-to-stream dag s :format :binary))))\n      (let ((read-dag\n              (read-from-stream (flex:make-in-memory-input-stream output)\n                                :format :binary)))\n        (is (typep read-dag 'dag))))))\n\n(test read\n  (with-fixture state ()\n    (let ((json (with-output-to-string (s)\n                  (write-to-stream dag s))))\n      (let ((dag (read-from-stream (make-string-input-stream json))))\n        (is (equal `(((:sha . \"bb\")\n                      (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n                      (:parents . nil))\n                     ((:sha . \"aa\")\n                      (:author . \"Arnold Noronha <arnold@tdrhq.com>\")\n                      (:parents . ,(list \"bb\"))))\n                   (assoc-value (json:decode-json-from-string\n                                 (with-output-to-string (s)\n                                   (write-to-stream dag s)))\n                                :commits)))))))\n\n(test merge-dag\n  (with-fixture state ()\n    (let ((to-dag (make-instance 'dag)))\n      (merge-dag to-dag dag)\n      (is-true (get-commit to-dag \"aa\"))\n      (is-true (get-commit to-dag \"bb\"))\n      (uiop:with-temporary-file (:stream s :pathname p)\n        (dag:write-to-stream to-dag s)\n        (with-open-file (input p :direction :input)\n          (let ((re-read (dag:read-from-stream input)))\n            (is-true (get-commit re-read \"aa\"))\n            (is-true (get-commit re-read \"bb\"))))))))\n\n(test big-dag-topological-sort\n  (multiple-value-bind (dag commits) (make-big-linear-dag)\n    (let ((final-order (loop for x in (assoc-value\n                                       (json:decode-json-from-string\n                                        (with-output-to-string (s)\n                                          (write-to-stream dag s)))\n                                       :commits)\n                             collect (assoc-value x :sha))))\n      (is (equal final-order commits)))))\n\n(test dfs-topo-sort-recursion\n  (multiple-value-bind (dag commits) (make-big-linear-dag :num #xf000)\n    (let ((start-commit (get-commit dag \"F000\")))\n      (is (not (null start-commit)))\n      (%dfs-topo-sort-from-starting-nodes\n       dag\n       (list start-commit))\n      (pass))\n    (finishes\n      (dfs-topo-sort dag))))\n\n(test merge-existing-commits\n  (with-fixture state ()\n   (let ((dag1 (make-instance 'dag))\n         (dag2 (make-instance 'dag)))\n     (add-edge \"bb\" nil :dag dag1)\n     (add-edge \"bb\" nil :dag dag2)\n     (add-edge \"aa\" \"bb\" :dag dag1)\n     (add-edge \"cc\" \"bb\" :dag dag2)\n     (merge-dag dag1 dag2))))\n\n(test document-the-right-order-topo-sort\n  \"Oldest first? Or newest first? You'll always wonder, so here's a test\nfor you.\"\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (flet ((find-commit (id)\n               (gethash id (commit-map dag))))\n        (add-edge \"bb\" nil :dag dag)\n        (add-edge \"aa\" \"bb\" :dag dag)\n        #+nil\n        (assert-that (car (safe-topological-sort dag))\n                     (has-typep 'commit))\n        (let ((commits (mapcar #'sha (mapcar #'find-commit (safe-topological-sort dag)))))\n          (assert-that commits\n                       (contains \"aa\" \"bb\")))\n        (add-edge \"cc\" \"bb\" :dag dag)\n        (add-edge \"dd\" (list \"aa\" \"cc\") :dag dag)\n        (let ((commits (mapcar #'sha (mapcar #'find-commit (safe-topological-sort  dag)))))\n          (assert-that commits\n                       (has-any\n                        (contains \"dd\" \"cc\" \"aa\" \"bb\" )\n                      (contains \"dd\" \"aa\" \"cc\" \"bb\" ))))))))\n\n\n(test document-the-right-order-topo-sort-long-names\n  \"Oldest first? Or newest first? You'll always wonder, so here's a test\nfor you.\"\n  (flet ((ll (name)\n           (let ((name (str:join \"\" (list name name))))\n            (str:join \"\"\n                      (list name name name name name\n                            name name name name name\n                            name name name name name\n                            name name name name name)))))\n    (assert (not (typep (commit-node-id (ll \"aa\")) 'fixnum)))\n    (with-fixture state ()\n      (let ((dag (make-instance 'dag)))\n       (flet ((find-commit (id)\n                (gethash id (commit-map dag))))\n         (add-edge (ll \"bb\") nil :dag dag)\n         (add-edge (ll \"aa\") (ll \"bb\") :dag dag)\n         (let ((commits (mapcar #'sha (mapcar #'find-commit (safe-topological-sort dag)))))\n           (assert-that commits\n                        (contains (ll \"aa\") (ll \"bb\"))))\n         (add-edge (ll \"cc\") (ll \"bb\") :dag dag)\n         (add-edge (ll \"dd\") (list (ll \"aa\") (ll \"cc\")) :dag dag)\n         (let ((commits (mapcar #'sha (mapcar #'find-commit (safe-topological-sort dag)))))\n           (assert-that commits\n                        (contains (ll \"dd\") (ll \"cc\") (ll \"aa\") (ll \"bb\") ))))))))\n\n(test topo-sort-on-incomplete-graph\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (flet ((find-commit (id)\n               (gethash id (commit-map dag))))\n        (add-edge \"aa\" \"bb\" :dag dag)\n        (let ((commits (mapcar #'sha (mapcar #'find-commit (safe-topological-sort dag)))))\n          (assert-that commits\n                       (contains \"aa\")))))))\n\n(test merge-incomplete-graph-to-empty-graph\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag))\n          (old-dag (make-instance 'dag)))\n      (flet ((find-commit (id)\n               (gethash id (commit-map old-dag))))\n        (add-edge \"aa\" \"bb\" :dag dag)\n        (merge-dag old-dag dag)\n        (let ((commits (mapcar #'sha (mapcar #'find-commit (safe-topological-sort old-dag)))))\n          (assert-that commits\n                       (contains \"aa\")))))))\n\n(test ordered-commits\n  \"This is being called from the SDK\"\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (add-edge \"aa\" \"bb\" :dag dag)\n      (assert-that (mapcar #'sha (ordered-commits dag))\n                   (contains \"aa\")))))\n\n(test assert-commit\n  (handler-case\n      (progn\n        (assert-commit \"foo\" \"foo bar\")\n        (fail \"expected error\"))\n    (error (e)\n      (assert-that (format nil \"~a\" e)\n                   (contains-string \"`foo` does not\")))))\n\n(test reachable-nodes\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (add-edge \"bb\" nil :dag dag)\n      (add-edge \"aa\" \"bb\" :dag dag)\n      (assert-that\n       (mapcar #'sha\n               (reachable-nodes dag \"aa\"))\n       (has-item \"aa\")\n       (has-item \"bb\"))\n      (assert-that\n       (mapcar #'sha\n               (reachable-nodes dag \"bb\"))\n       (contains \"bb\"))\n\n      (add-edge \"dd\" \"aa\" :dag dag)\n      (assert-that\n       (mapcar #'sha\n               (reachable-nodes dag \"dd\"))\n       (has-item \"bb\"))\n      (assert-that\n       (mapcar #'sha\n               (reachable-nodes dag \"dd\" :depth 2))\n       (is-not (has-item \"bb\"))\n       (has-item \"aa\")\n       (has-length 2)))))\n\n(test we-get-callbacks-for-nodes-that-arent-present\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (add-edge \"cc\" \"dd\" :dag dag)\n\n      (let ((seen nil))\n        (reachable-nodes dag \"cc\"\n                         :seen-callback (lambda (commit)\n                                          (push (sha commit) seen)))\n        (assert-that seen\n                     (contains \"dd\" \"cc\"))))))\n\n(test we-get-callbacks-for-nodes-that-arent-present--but-only-once!\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (add-edge \"cc\" \"dd\" :dag dag)\n      (add-edge \"ee\" (list \"cc\" \"dd\") :dag dag)\n\n      (let ((seen nil))\n        (reachable-nodes dag \"ee\"\n                         :seen-callback (lambda (commit)\n                                          (push (sha commit) seen)))\n        (assert-that seen\n                     (contains \"dd\" \"cc\" \"ee\"))))))\n\n(def-fixture F279319 ()\n  (add-edge \"cc\" \"bb\" :dag dag)\n  (is (equal \"bb\" (merge-base dag \"aa\" \"cc\")))\n  (add-edge \"dd\" \"ee\")\n  ;; At this point, the graph looks like: https://phabricator.tdrhq.com/F279319\n  (&body))\n\n(test merge-base\n  (with-fixture state ()\n    (with-fixture F279319 ()\n      (is (equal nil (merge-base dag \"aa\" \"dd\")))\n      (is (equal \"aa\" (merge-base dag \"aa\" \"aa\" :exclude-commit-2 nil)))\n      (is (equal \"aa\" (merge-base dag \"aa\" \"aa\" :exclude-commit-2 t))))))\n\n(test merge-base-allows-multiple-commit-1s\n  (with-fixture state ()\n    (with-fixture F279319 ()\n      (is (equal \"bb\" (merge-base dag (list \"aa\" \"dd\") \"cc\"))))))\n\n(test merge-base-allows-completely-invalid-commits\n  (with-fixture state ()\n    (with-fixture F279319 ()\n      (is (equal \"bb\" (merge-base dag (list \"aa\" \"ff\") \"cc\")))\n      (is (equal nil (merge-base dag \"ff\" \"cc\")))\n      (is (equal nil (merge-base dag nil \"cc\"))))))\n\n(test merge-base-v2\n  (with-fixture state ()\n    (add-edge \"cc\" \"bb\" :dag dag)\n    (is (equal \"bb\" (merge-base dag \"aa\" \"cc\")))\n    (add-edge \"dd\" \"ee\")\n    ;; At this point, the graph looks like: https://phabricator.tdrhq.com/F279319\n    (is (equal nil (merge-base dag \"aa\" \"dd\")))\n    (is (equal \"aa\" (merge-base dag \"aa\" \"aa\" :exclude-commit-2 nil)))\n    (is (equal \"aa\" (merge-base dag \"aa\" \"aa\" :exclude-commit-2 t)))))\n\n(test merge-base-finds-*greatest*-common-ancestor-not-just-any-ancestor\n  (with-fixture state ()\n    ;; This is the graph we're creating: https://phabricator.tdrhq.com/F279321\n    (let ((dag (make-instance 'dag:dag)))\n      (add-edge \"aa\" \"bb\" :dag dag)\n      (add-edge \"bb\" \"cc\" :dag dag)\n      (add-edge \"cc\" \"ff\" :dag dag)\n      (add-edge \"ff\" \"11\" :dag dag)\n      (add-edge \"ee\" (list \"dd\" \"ff\") :dag dag)\n      (add-edge \"dd\" \"cc\" :dag dag)\n      (is (equal \"cc\" (merge-base dag \"aa\" \"ee\")))\n      (is (equal \"cc\" (merge-base dag \"ee\" \"aa\"))))))\n\n(test merge-base-finds-*greatest*-common-ancestor-not-just-any-ancestor-2\n  (with-fixture state ()\n    ;; This is the graph we're creating:\n    ;; https://phabricator.tdrhq.com/F279320 This is almost like the\n    ;; above one, but one extra node in the history, which showed up\n    ;; as a bug when writing the previous one.\n    (let ((dag (make-instance 'dag:dag)))\n      (add-edge \"aa\" \"bb\" :dag dag)\n      (add-edge \"bb\" \"cc\" :dag dag)\n      (add-edge \"cc\" \"ff\" :dag dag)\n      (add-edge \"ee\" (list \"dd\" \"ff\") :dag dag)\n      (add-edge \"dd\" \"cc\" :dag dag)\n      (is (equal \"cc\" (merge-base dag \"aa\" \"ee\")))\n      (is (equal \"cc\" (merge-base dag \"ee\" \"aa\"))))))\n\n(test there-can-be-multiple-greatest-common-ancestors\n  (with-fixture state ()\n    ;; This is the graph we're creating:\n    ;; https://phabricator.tdrhq.com/F279382\n    (let ((dag (make-instance 'dag:dag)))\n      (add-edge \"aa\" (list \"bb\" \"33\") :dag dag)\n      (add-edge \"bb\" \"cc\" :dag dag)\n      (add-edge \"cc\" \"ff\" :dag dag)\n      (add-edge \"ff\" \"11\" :dag dag)\n      (add-edge \"ee\" (list \"dd\" \"33\") :dag dag)\n      (add-edge \"33\" \"11\" :dag dag)\n      (add-edge \"dd\" \"cc\" :dag dag)\n\n      (assert-that\n       (nth-value 1 (merge-base dag \"aa\" \"ee\"))\n       (contains-in-any-order \"cc\" \"33\"))\n      (assert-that\n       (nth-value 1 (merge-base dag \"ee\" \"aa\"))\n       (contains-in-any-order \"cc\" \"33\")))))\n\n(defun set-timestamp (dag sha ts)\n  (let ((commit (get-commit dag sha)))\n    (setf (commit-timestamp commit) ts)))\n\n(test there-can-be-multiple-greatest-common-ancestors--but-we-always-prefer-the-later-one\n  (with-fixture state ()\n    ;; This is the graph we're creating:\n    ;; https://phabricator.tdrhq.com/F279382\n    (let ((dag (make-instance 'dag:dag)))\n      (add-edge \"aa\" (list \"bb\" \"33\") :dag dag)\n      (add-edge \"bb\" \"cc\" :dag dag)\n      (add-edge \"cc\" \"ff\" :dag dag)\n      (add-edge \"ff\" \"11\" :dag dag)\n      (add-edge \"ee\" (list \"dd\" \"33\") :dag dag)\n      (add-edge \"33\" \"11\" :dag dag)\n      (add-edge \"dd\" \"cc\" :dag dag)\n\n      (set-timestamp dag \"33\" 101)\n      (set-timestamp dag \"cc\" 100);\n\n      (assert-that\n       (nth-value 1 (merge-base dag \"aa\" \"ee\"))\n       (contains \"33\" \"cc\"))\n      (is (equal \"33\" (merge-base dag \"aa\" \"ee\")))\n      (assert-that\n       (nth-value 1 (merge-base dag \"ee\" \"aa\"))\n       (contains \"33\" \"cc\"))\n      (is (equal \"33\" (merge-base dag \"ee\" \"aa\"))))))\n\n(test there-can-be-multiple-greatest-common-ancestors--but-we-always-prefer-the-later-one-flipped\n  (with-fixture state ()\n    ;; This is the graph we're creating:\n    ;; https://phabricator.tdrhq.com/F279382\n    (let ((dag (make-instance 'dag:dag)))\n      (add-edge \"aa\" (list \"bb\" \"33\") :dag dag)\n      (add-edge \"bb\" \"cc\" :dag dag)\n      (add-edge \"cc\" \"ff\" :dag dag)\n      (add-edge \"ff\" \"11\" :dag dag)\n      (add-edge \"ee\" (list \"dd\" \"33\") :dag dag)\n      (add-edge \"33\" \"11\" :dag dag)\n      (add-edge \"dd\" \"cc\" :dag dag)\n\n      (set-timestamp dag \"33\" 100)\n      (set-timestamp dag \"cc\" 101);\n\n      (assert-that\n       (nth-value 1 (merge-base dag \"aa\" \"ee\"))\n       (contains \"cc\" \"33\"))\n      (is (equal \"cc\" (merge-base dag \"aa\" \"ee\")))\n      (assert-that\n       (nth-value 1 (merge-base dag \"ee\" \"aa\"))\n       (contains \"cc\" \"33\"))\n      (is (equal \"cc\" (merge-base dag \"ee\" \"aa\"))))))\n\n(test excludes-commit-2-when-finding-merge-base\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (flet ((add (a &rest b)\n               (add-edge a b :dag dag)))\n        ;; This is the graph: https://phabricator.tdrhq.com/F279537\n        ;; We're trying to merge \"ee\" into \"22\"\n        (add \"22\" \"aa\")\n        (add \"aa\" \"bb\" \"ee\")\n        (add \"bb\" \"cc\")\n        (add \"ee\" \"dd\" \"33\")\n        (add \"dd\" \"cc\")\n        (add \"cc\" \"ff\")\n        (add \"ff\" \"11\"))\n      (is (equal \"cc\" (merge-base dag \"22\" \"ee\"))))))\n\n(test cannot-exclude-commit-2-when-commit-2-all-paths-go-through-commit-2\n  (with-fixture state ()\n    (let ((dag (make-instance 'dag)))\n      (flet ((add (a &rest b)\n               (add-edge a b :dag dag)))\n        ;; This is the graph: https://phabricator.tdrhq.com/F279537\n        ;; We're trying to merge \"ff\" into \"22\"\n        (add \"22\" \"aa\")\n        (add \"aa\" \"bb\" \"ee\")\n        (add \"bb\" \"cc\")\n        (add \"ee\" \"dd\" \"33\")\n        (add \"dd\" \"cc\")\n        (add \"cc\" \"ff\")\n        (add \"ff\" \"11\")\n        ;; This is the case we actually care about:\n        (is (equal \"ff\" (merge-base dag \"22\" \"ff\")))\n\n        ;; But this should also hold true,\n        (is (equal \"ff\" (merge-base dag \"ff\" \"22\")))        \n\n        (add \"44\" \"55\")\n        ;; But let's also test the case where it's not an ancestor\n        (is (equal nil (merge-base dag \"22\" \"44\")))))))\n\n(test ancestorp\n  (with-fixture state ()\n    (add-edge \"cc\" \"aa\" :dag dag)\n    (is (ancestorp dag \"bb\" \"cc\"))\n    (is (ancestorp dag \"cc\" \"cc\"))\n    (is (ancestorp dag \"aa\" \"cc\"))\n    ;; What if the commit doesn't exist at all?\n    (is (ancestorp dag \"a1\" \"a1\"))\n    (is (not (ancestorp dag \"cc\" \"bb\")))))\n\n(def-fixture best-path ()\n  (add-edge \"cc\" (list \"ff\" \"ee\") :dag dag)\n  (add-edge \"ff\" \"dd\" :dag dag)\n  (add-edge \"dd\" (list \"ee\" \"11\") :dag dag)\n  (add-edge \"22\" \"33\")\n  (&body))\n\n(test best-path\n  (with-fixture state ()\n    (with-fixture best-path ()\n     (assert-that (best-path dag \"cc\" \"ee\")\n                  (contains \"cc\" \"ff\" \"dd\" \"ee\")))))\n\n(test best-path-with-max-depth\n  (with-fixture state ()\n   (with-fixture best-path ()\n     (assert-that (best-path dag \"cc\" \"ee\" :max-depth 1)\n                  (contains))\n     (assert-that (best-path dag \"cc\" \"ee\" :max-depth 2)\n                  (described-as \"Even though there's a longer 'better-path' the max-depth means we'll use the smaller path\"\n                    (contains \"cc\" \"ee\")))\n     (assert-that (best-path dag \"cc\" \"11\" :max-depth 2)\n                  (described-as \"Even though there's a longer 'better-path' the max-depth means we'll use the smaller path\"\n                    (contains))))))\n\n(test best-path-when-theres-not-path\n  (with-fixture state ()\n    (with-fixture best-path ()\n      (assert-that (best-path dag \"cc\" \"33\")\n                   (contains)))))\n\n(test best-path-when-original-commit-is-not-present\n  (with-fixture state ()\n    (with-fixture best-path ()\n      (assert-that (best-path dag \"44\" \"55\")\n                   (contains))\n      (assert-that (best-path dag \"44\" \"33\")\n                   (contains)))))\n\n(test dag-difference\n  (with-fixture state ()\n    (let ((other (make-instance 'dag)))\n      (add-edge \"aa\" \"bb\" :dag other)\n      (add-edge \"dd\" \"ee\" :dag other)\n      (let ((result (dag:dag-difference dag other)))\n        (assert-that (mapcar #'sha (all-commits result))\n                     (has-item \"bb\")\n                     (does-not (has-item \"aa\"))\n                     (does-not (has-item \"dd\"))\n                     (has-length 1))))))\n\n(test dag-difference-includes-previously-shallow-clones\n  (with-fixture state ()\n    (let ((other (make-instance 'dag)))\n      (add-edge \"aa\" nil :dag other)\n      (add-edge \"dd\" \"ee\" :dag other)\n      (let ((result (dag:dag-difference dag other)))\n        (assert-that (mapcar #'sha (all-commits result))\n                     (has-item \"aa\"))))))\n\n(test invalid-commit-hash\n  (with-fixture state ()\n    (is (eql nil (dag:get-commit dag \"abcdef2\")))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/api-key-impl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/api-key-impl\n  (:use #:cl)\n  (:import-from #:screenshotbot/dashboard/api-keys\n                #:permission\n                #:api-key-available-permissions)\n  (:import-from #:screenshotbot/installation\n                #:installation))\n(in-package :screenshotbot/dashboard/api-key-impl)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod api-key-available-permissions ((self installation))\n  (list\n   (make-instance\n    'permission\n    :name :ci\n    :default t\n    :label\n    <label class= \"form-check-label\" for= \"#ci-access\">\n      CI Access<br/>\n      <span class= \"text-muted\">Upload screenshots from CI jobs</span>\n    </label>)\n   (make-instance\n    'permission\n    :name :full\n    :label\n    <label class= \"form-check-label\" for= \"#ci-access\">\n      Full access<br/>\n      <span class= \"text-muted\">Suitable for custom scripts using the API</span>\n    </label>)))\n\n"
  },
  {
    "path": "src/screenshotbot/dashboard/audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/audit-log\n  (:use #:cl\n        #:screenshotbot/audit-log)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:created-at)\n  (:import-from #:core/ui/taskie\n                #:timeago)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:render-audit-logs\n   #:commit-tag))\n(in-package :screenshotbot/dashboard/audit-log)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod render-audit-log ((self base-audit-log))\n  (let ((timeago (timeago :timestamp (created-at self))))\n    (cond\n      ((audit-log-error self)\n       <span class= \"text-danger\">\n         ,(describe-audit-log self): <b>error</b>:\n       ,(audit-log-error self), tried ,(progn timeago)\n       </span>)\n      (t\n       <span class= \"\">\n         ,(describe-audit-log self) at ,(progn timeago)\n       </span>))))\n\n(defmethod describe-audit-log ((self base-audit-log))\n  (str:downcase\n   (str:replace-all\n    \"-AUDIT-LOG\" \"\"\n    (string (type-of self)))))\n\n(deftag render-audit-logs (&key type (company (current-company))\n                           subtitle\n                           (title \"API Audit Logs\"))\n  (let ((audit-logs (audit-logs-for-company company type)))\n    <div class= \"card mt-3 pb-0\">\n      <div class= \"card-header\">\n        <h5>,(progn title) </h5>\n      </div>\n\n      <div class= \"card-body pb-0\">\n        <p class= \"text-muted\">,(progn subtitle)</p>\n        <ul class= \"mb-0\" >\n          ,(paginated\n            (lambda (audit-log)\n              <li>,(render-audit-log audit-log)</li>)\n            :items audit-logs)\n        </ul>\n      </div>\n    </div>))\n\n(markup:deftag commit-tag (children)\n  (let ((commit (markup:write-html (car children))))\n    <code title=commit >,(str:shorten 8 commit)</code>))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/batch\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:with-login\n                #:defhandler)\n  (:import-from #:util/store/object-id\n                #:find-by-oid)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:can-view!)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-item-title\n                #:batch-item-status\n                #:batch-item-run\n                #:batch-item-report\n                #:batch-item\n                #:batch-item-channel\n                #:batch-items)\n  (:import-from #:core/ui/taskie\n                #:taskie-row\n                #:taskie-list)\n  (:import-from #:anaphora\n                #:it\n                #:acond)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-link)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:report-link)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:export\n   #:batch-handler\n   #:sort-items)\n  (:local-nicknames (#:batch #:screenshotbot/model/batch)))\n(in-package :screenshotbot/dashboard/batch)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defhandler (batch-handler :uri \"/batch/:oid\") (oid)\n  (setf (hunchentoot:header-out :cache-control)\n        \"no-cache, no-store, must-revalidate\")\n  (with-login (:allow-url-redirect t)\n    (let ((batch (find-by-oid oid)))\n      (assert batch)\n      (can-view! batch)\n      (render-batch batch))))\n\n(defmethod batch-item-link ((item batch-item))\n  (acond\n    ((batch-item-report item)\n     (report-link it))\n    ((batch-item-run item)\n     (run-link it))\n    (t\n     (error \"no run or report attached to this item\"))))\n\n(defun render-batch-item (item)\n  (let ((class (ecase (batch-item-status item)\n                 (:accepted \"success\")\n                 (:rejected \"danger\")\n                 (:success \"muted\")\n                 (:failure \"danger\")\n                 (:pending \"muted\")\n                 (:action-required \"danger\")))\n        (icon (ecase (batch-item-status item)\n                (:accepted \"done\")\n                (:rejected \"close\")\n                (:success \"done\")\n                (:pending \"pending\")\n                (:failure \"dangerous\")\n                (:action-required \"report\"))))\n   (taskie-row\n    :object item\n    <span>\n      <div>\n        <mdi name=icon class= (format nil \"text-~a\" class) />\n        ,(let ((link (batch-item-link item))\n               (link-class (format nil \"link-~a ~a\" class (if (eql :rejected (batch-item-status item)) \"fw-bold\"))))\n           (cond\n             (link\n              <a href= link class= link-class >,(channel-name (batch-item-channel item))</a>)\n             (t\n              <span>(channel-name (batch-item-channel item))</span>)))\n        <span class= \"text-muted ms-2\" >,(batch-item-title item) </span>\n      </div>\n    </span>)))\n\n(defparameter *order*\n  (list\n   :rejected\n   :failure\n   :action-required\n   :accepted\n   :pending\n   :success))\n\n(defun status-order (item)\n  (position (batch-item-status item) *order*))\n\n(defun sort-items (items)\n  (sort (copy-list items)\n        (lambda (a b)\n          (or\n           (|<| (status-order a) (status-order b))\n           (and\n                (= (status-order a) (status-order b))\n                (string-lessp\n                 (channel-name (batch-item-channel a))\n                 (channel-name (batch-item-channel b))))))))\n\n(defmethod render-batch (batch)\n  (bt:with-lock-held ((batch:lock batch))\n    (let ((items (fset:convert 'list (sort-items (fset:convert 'list (batch-items batch))))))\n      <app-template>\n        ,(taskie-list :empty-message \"No runs in this batch yet\"\n                      :items items\n                      :headers (list \"Channel\")\n                      :row-generator #'render-batch-item)\n      </app-template>)))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/bisect.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/bisect\n  (:use #:cl)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:link-to-run\n                #:screenshot-box)\n  (:import-from #:screenshotbot/user-api\n                #:channel-repo\n                #:recorder-run-channel\n                #:recorder-run-commit)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:render-modal\n                #:screenshots-viewer\n                #:modal-id)\n  (:import-from #:screenshotbot/dashboard/recent-runs\n                #:conditional-commit)\n  (:import-from #:util/misc\n                #:?.)\n  (:export\n   #:bisect-item))\n(in-package :screenshotbot/dashboard/bisect)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass bisect-item ()\n  ((screenshot :initarg :screenshot\n               :reader item-screenshot)\n   (run :initarg :run\n        :reader item-run)))\n\n(defclass state ()\n  ((items :initarg :items\n          :reader items\n          :documentation \"An fset sequence of items that are currently being bisected.\")))\n\n(defun midpoint-pos (state)\n  (floor (fset:size (items state)) 2))\n\n(defun midpoint (state)\n  (let ((pos (midpoint-pos state)))\n    (values (fset:lookup (items state) pos) pos)))\n\n(defun state-if-good (state)\n  \"Returns a state if the midpoint is good\"\n  (make-instance 'state\n                 :items (fset:subseq (items state)\n                                     0 (1+ (midpoint-pos state)))))\n\n(defun state-if-bad (state)\n  (make-instance 'state\n                 :items (fset:subseq (items state)\n                                     (midpoint-pos state)\n                                     (fset:size (items state)))))\n\n(defun state-if-skip (state)\n  (make-instance 'state\n                 :items (fset:less (items state)\n                                   (midpoint-pos state))))\n\n(deftag screenshot-box-with-viewer (&key screenshot)\n  (let ((screenshots-viewer (make-instance 'screenshots-viewer\n                                           :screenshots (list screenshot)\n                                           :navigationp nil)))\n    <markup:merge-tag>\n      ,(render-modal screenshots-viewer)\n      <a href= \"#\"\n         class= \"screenshot-run-image\"\n         data-image-number=0\n         data-target= (format nil \"#~a\" (modal-id screenshots-viewer)) >\n        <screenshot-box screenshot=screenshot />\n      </a>\n    </markup:merge-tag>))\n\n(deftag link-to-run-with-commit (&key run)\n  <span>\n    <link-to-run run=run />\n    <conditional-commit repo= (?. channel-repo (recorder-run-channel run))\n                        hash=(recorder-run-commit run) />\n  </span>)\n\n(defmethod render-bisection (state)\n  (let ((num-items (fset:size (items state))))\n    (assert (>= num-items 2))\n    (cond\n      ((= (fset:size (items state)) 2)\n       ;; We got our result\n       (render-result (fset:lookup (items state) 0)))\n      (t\n       (flet ((bisect-nibble (fn)\n                (nibble (:name :bisect-continue)\n                  (render-bisection\n                   (funcall fn state)))))\n         (let ((midpoint (midpoint state))\n                (pos (midpoint-pos state)))\n            (assert (> pos 0))\n            (assert midpoint)\n           <simple-card-page max-width= \"50rem\" >\n             <div class= \"card-header\">\n               <h3>Bisecting</h3>\n               <p class= \"text-muted mb-1\">\n                 Currently looking at <link-to-run-with-commit run= (item-run midpoint) />\n               </p>\n             </div>\n             <div  class= \"card-body\" style= \"overflow: hidden\" >\n               <screenshot-box-with-viewer screenshot= (item-screenshot midpoint) />\n             </div>\n\n             <div class=\"card-footer\">\n               <div class= \"text-muted mb-2\">\n                 Is this image good or bad?\n               </div>\n               <a href= (bisect-nibble #'state-if-good) class= \"btn btn-success\" >Good</a>\n               <a href= (bisect-nibble #'state-if-bad) class= \"btn btn-danger\" >Bad</a>\n               <a href= (bisect-nibble #'state-if-skip) class= \"ms-2\" >Skip this run</a>\n             </div>\n           </simple-card-page>))))))\n\n(defun render-result (item)\n  <simple-card-page>\n    <div>\n      The first bad run is: <link-to-run-with-commit run= (item-run item) />\n    </div>\n  </simple-card-page>)\n\n(defun bisect-page (items)\n  (render-bisection\n   (make-instance 'state\n                  :items (fset:convert 'fset:seq items))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/channels.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/channels\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/template\n        #:screenshotbot/user-api\n        #:core/ui/taskie\n        #:nibble\n        #:screenshotbot/ui)\n  (:import-from #:screenshotbot/server\n                #:with-login\n                #:defhandler)\n  (:import-from #:markup #:deftag)\n  (:import-from #:screenshotbot/dashboard/explain\n                #:explain)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-masks\n                #:allow-public-badge-p\n                #:review-policy-name\n                #:with-channel-lock\n                #:channel-slack-channels\n                #:channel-subscribers\n                #:channel-company\n                #:all-active-runs)\n  (:import-from #:util/object-id\n                #:find-by-oid\n                #:oid)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:mask-editor\n                #:run-page)\n  (:import-from #:core/ui/taskie\n                #:taskie-page-title)\n  (:import-from #:screenshotbot/model/company\n                #:company-admin-p\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:can-view!\n                #:recorder-run-channel\n                #:company-runs\n                #:recorder-run-screenshots\n                #:current-company\n                #:company-channels)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:runs-for-channel\n                #:delete-run\n                #:recorder-run-company\n                #:active-run)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page\n                #:simple-card-page)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:import-from #:bknr.datastore\n                #:store-object-id\n                #:without-sync\n                #:deftransaction)\n  (:import-from #:bknr.datastore\n                #:deftransaction)\n  (:import-from #:screenshotbot/model/report\n                #:report-channel)\n  (:import-from #:alexandria\n                #:curry\n                #:removef)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:bknr.datastore\n                #:store-object-with-id)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:util/form-errors\n                #:with-error-builder)\n  (:import-from #:screenshotbot/dashboard/recent-runs\n                #:render-recent-runs)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:screenshotbot/dashboard/flaky-screenshots\n                #:view-noisy-screenshots\n                #:view-flaky-screenshots)\n  (:import-from #:util/copying\n                #:copying)\n  (:export\n   #:microsoft-teams-card))\n(in-package :screenshotbot/dashboard/channels)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(defgeneric microsoft-teams-card (channel))\n\n(deftag subscription-card (&key channel)\n  (let ((subscribe (nibble (:method :post)\n                     (with-transaction ()\n                       (pushnew (current-user)\n                                (channel-subscribers channel)))\n                     (go-back channel)))\n        (subscribedp (member (current-user) (channel-subscribers channel)))\n        (subscribers (channel-subscribers channel))\n        (unsubscribe (nibble (:method :post)\n                       (with-transaction ()\n                         (removef (channel-subscribers channel)\n                                  (current-user)))\n                       (go-back channel))))\n    <div class= \"card mt-3\">\n      <div class= \"card-body\">\n\n        ,(cond\n           (subscribedp\n            <markup:merge-tag>\n              <p>You are subscribed to this channel, and will always get notified by email when then screenshots change on the main branch.</p>\n              <form action=unsubscribe method= \"post\">\n                <input type= \"submit\" class= \"btn btn-warning\" value= \"Unsubscribe\" />\n              </form>\n            </markup:merge-tag>)\n           (t\n            <markup:merge-tag>\n              <p>You are <b>not</b> subscribed to this channel, and might not get notified when screenshots change on the main branch.</p>\n\n              <form action=subscribe method= \"post\">\n                <input type= \"submit\" class= \"btn btn-primary\" value = \"Subscribe\" />\n              </form>\n            </markup:merge-tag>))\n\n        ,(when subscribers\n           <markup:merge-tag>\n             <hr class= \"my-3\" />\n             <h5 class= \"mb-3\">Current Subscribers</h5>\n             <ul class= \"list-unstyled\">\n               ,@ (loop for user in subscribers\n                        collect\n                        <li class= \"mb-2 d-flex align-items-center\">\n                          <img class= \"rounded-circle avatar me-2\" src= (user-image-url user) style= \"width: 32px; height: 32px;\" />\n                          <span>\n                            <strong>,(user-full-name user)</strong>\n                            <span class= \"text-muted ms-2\">,(user-email user)</span>\n                          </span>\n                        </li>)\n             </ul>\n           </markup:merge-tag>)\n\n      </div>\n    </div>))\n\n(deftag settings-card (&key channel)\n  (let ((slack-update (nibble (slack-channels review-policy allow-public-badge-p :method :post)\n                        (settings-card-post channel\n                                            :slack-channels slack-channels\n                                            :allow-public-badge-p allow-public-badge-p\n                                            :review-policy review-policy)))\n        (slack-channels (str:join \", \"\n                           (mapcar (curry #'str:concat \"#\")\n                           (channel-slack-channels channel)))))\n    <div class= \"card mt-3\">\n      <div class= \"card-body\">\n        <h4 class= \"card-title mb-3\">\n          Settings\n        </h4>\n\n\n        <form method= \"POST\" action=slack-update >\n          <div class= \"mb-3\">\n            <label for= \"slack-channels\" class= \"form-label\">Slack channels to notify\n              <explain>\n                If you have configured <a href= \"/settings/slack\">Slack</a>, these Slack channels will always be notified of <tt>,(safe-channel-name channel)</tt>'s changes.\n              </explain>\n            </label>\n            <input name= \"slack-channels\" class= \"form-control\"\n                   id= \"slack-channels\"\n                   value= slack-channels\n                   placeholder= \"#channel1, #channel2\" />\n<div class= \"mt-1 mb-2 d-none\" class= \"text-muted\">If you have configured <a href= \"/settings/slack\">Slack</a>, these Slack channels will always be notified of <tt>,(safe-channel-name channel)</tt>'s changes.</div>\n\n          </div>\n\n          <div class= \"mb-3\" >\n            <label for= \"review-policy\" class= \"form-label\" >Review policy\n              <explain>\n                This setting only affects screenshot on Pull Requests or Merge Requests. Our\n                recommendation is to allow authors to review their own screenshots.\n              </explain>\n            </label>\n            ,(flet ((selected (name)\n                      (when (string-equal name (string (review-policy-name channel)))\n                        \"selected\")))\n               <select class=\"form-select\" id= \"review-policy\" name= \"review-policy\" >\n                 <option value=\"allow-author\" selected= (selected \"allow-author\") >Authors can accept their own changes (Default) </option>\n                 <option value=\"disallow-author\" selected= (selected \"disallow-author\") >Authors cannot accept their own changes</option>\n               </select>)\n          </div>\n\n          <div class= \"form-check mb-3\">\n            <input class= \"form-check-input\" type= \"checkbox\"   id= \"allowPublicBadge\"\n                   name= \"allow-public-badge-p\"\n                   checked= (when (allow-public-badge-p channel) \"checked\") />\n            <label for= \"allowPublicBadge\" class= \"form-check-label\">Allow public Build Badge for private repositories</label>\n            <explain>\n              By default the Build Badge is only accessible to logged-in users, unless the\n              repository is public. Enabling public access will be required before using the build\n              badge on a private repository.\n            </explain>\n          </div>\n\n          <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n        </form>\n      </div>\n    </div>))\n\n(defun settings-card-post (channel &key slack-channels review-policy allow-public-badge-p)\n  (with-error-builder (:check check :errors errors)\n    (flet ((parse-channel (channel)\n             (let ((channel (str:trim channel)))\n               (if (str:starts-with-p \"#\" channel)\n                   (str:substring 1 nil channel)\n                   channel))))\n      (let ((slack-channels (remove-if #'str:emptyp\n                                       (mapcar #'str:trim\n                                               (mapcar (curry #'str:replace-all \"#\" \"\")\n                                                (str:split \",\" slack-channels))))))\n\n        (assert (str:s-member '(\"allow-author\" \"disallow-author\")\n                              review-policy))\n        (assert (< (length slack-channels) 100))\n\n        (setf (channel-slack-channels channel)\n              slack-channels)\n        (setf (review-policy-name channel)\n              (find-symbol (str:upcase review-policy) :keyword))\n        (setf (allow-public-badge-p channel)\n              allow-public-badge-p))\n      (go-back channel))))\n\n(defhandler (single-channel-page :uri \"/channels/:id\") (id)\n  (with-login ()\n    (let* ((id (parse-integer id))\n           (channel (store-object-with-id id)))\n      (check-type channel channel)\n      (can-view! channel)\n      (single-channel-view channel))))\n\n(defun go-back (channel)\n  (hex:safe-redirect\n   (hex:make-url\n    'single-channel-page :id (store-object-id channel))))\n\n(defun view-channel-runs (channel)\n  (render-recent-runs (runs-for-channel channel)\n                      :title (format nil \"Runs for ~a\" (channel-name channel))))\n\n(defhandler (channel-static-handler :uri \"/active-run\") (org channel branch)\n  (let ((run (run-for-channel\n              :channel channel\n              :company (or org (current-company))\n              :branch branch)))\n    (cond\n      ((not run)\n       <simple-card-page>\n         <div class= \"card-body\">\n           No such channel or no active runs for this channel.\n         </div>\n       </simple-card-page>)\n      (t\n       (can-view! run)\n       (hex:safe-redirect 'run-page :id (oid run))))))\n\n\n(defun single-channel-view (channel)\n  <app-template >\n    <div class= \"main-content channel-view\">\n      <div class= \"card-page-container mt-3 mx-auto\" style= \"max-width: 60em\" >\n        <div class= \"card\">\n          <div class= \"card-header d-flex justify-content-between\">\n            <h3>,(safe-channel-name channel)</h3>\n            <div>\n              <a href= (nibble () (confirm-delete channel)) class= \"btn btn-danger ms-2\"  >Delete</a>\n            </div>\n          </div>\n          <div class= \"card-body\">\n            <p>First seen: <timeago timestamp= (created-at channel) />\n            </p>\n            <p>\n              <ul class= \"channel-links\" >\n                ,@ (or\n                    (loop for (branch . nil) in (all-active-runs channel)\n                          collect\n                          <li>\n                            <a href=(hex:make-url 'channel-static-handler\n                                                  :org (oid (company channel))\n                                                  :channel (channel-name channel)\n                                                  :branch branch) >\n                              <mdi name= \"image\" />\n                              View promoted screenshots on <tt>,(progn branch)</tt>\n                            </a>\n                          </li>)\n                    (list <li>\n                            <mdi name= \"image\" />\n                            No promoted screenshots\n                          </li>))\n                 <li>\n                   <a href= (nibble () (view-channel-runs channel)) >\n                     <mdi name= \"view_list\" />\n                     View recent runs\n                   </a>\n                 </li>\n\n                 <li>\n                   <a href= (nibble () (view-noisy-screenshots channel)) >\n                     <mdi name= \"flaky\" />\n                     Debug flaky screenshots\n                   </a>\n                 </li>\n\n                 <li>\n                   <a href= (nibble () (view-masks channel)) >\n                     <mdi name= \"masks\" />\n                     View masks\n                   </a>\n                 </li>\n                          \n              </ul>\n            </p>\n          </div>\n        </div>\n\n\n        <subscription-card channel=channel />\n\n        <settings-card channel=channel />\n\n        ,(microsoft-teams-card channel)\n\n        <div class= \"card mt-3\">\n          <div class= \"card-body\">\n            <h4 class= \"card-title mb-2\" >Build Badge</h4>\n\n            ,(let* ((args (guess-channel-args channel))\n                    (link (apply #'hex:make-full-url\n                                 hunchentoot:*request*\n                                 'channel-static-handler\n                                 args))\n                    (badge (apply #'hex:make-full-url\n                                  hunchentoot:*request*\n                                  'badge-handler\n                                  args))\n                    (local-badge\n                      ;; A local-badge is better for screenshot tests\n                      (apply #'hex:make-url\n                             'badge-handler\n                             args)))\n               <markup:merge-tag>\n                 <a href=link >\n                   <:img src= local-badge />\n                 </a>\n\n                 <p class= \"mt-3\" >To use this build badge, you can use the following template in a GitHub flavored markdown file. </p>\n\n\n                 <div>\n                   <tt>\n                     [![Screenshots](,(progn badge))](,(progn link))\n                   </tt>\n                 </div>\n\n                 <div class= \"alert alert-warning mt-3\">\n                   The badge works on private channels only if you are logged in, but\n                   will always work on public channel. GitHub renders\n                   the badges behind a proxy which might break the badges for\n                   private channels.\n                   Reach out to us if the badge doesn't render,\n                   and we'll help you fix it.\n                 </div>\n               </markup:merge-tag>)\n\n          </div>\n        </div>\n\n      </div>\n    </div>\n  </app-template>)\n\n(defun view-masks (channel)\n  (let ((run (fset:greatest (runs-for-channel channel)))\n        (mask-map (loop for (name . masks) in (channel-masks channel)\n                        if masks\n                          collect (cons name masks))))\n    (cond\n      ((not mask-map)\n       <simple-card-page>\n         <span>No masks are set for this channel</span>\n       </simple-card-page>)\n      ((not run)\n       <simple-card-page>\n         <span>No runs available for this channel</span>\n       </simple-card-page>)\n      (t\n       <app-template>\n         <h4 class= \"mt-3\" >All masks for ,(channel-name channel)</h4>\n         <table class= \"table table-striped\" >\n         ,@ (loop for (name . masks) in mask-map\n                  collect\n                  (copying (name)\n                    <tr>\n                      <td>\n                        <div>\n                          <a href= (nibble () (%edit-masks-for-screenshot run name)) >,(progn name)</a>\n                        </div>\n                      </td>\n                      <td>\n                        ,(length masks) masks\n                      </td>\n                    </tr>))\n         </table>\n       </app-template>))))\n\n(defun %delete-mask (channel name)\n  (setf\n   (channel-masks channel)\n   (remove name (channel-masks channel)\n           :test #'equal\n           :key #'car))\n  (hex:safe-redirect (nibble () (view-masks channel))))\n\n(defmethod %edit-masks-for-screenshot (run (name string))\n  (let* ((screenshots (recorder-run-screenshots run))\n         (channel (recorder-run-channel run))\n         (screenshot (loop for screenshot in screenshots\n                           if (equal name (screenshot-name screenshot))\n                             return screenshot))\n         (delete-action (nibble ()\n                          (%delete-mask channel name))))\n\n    (cond\n      ((not screenshot)\n       <simple-card-page form-action=delete-action >\n         <span>This screenshot is no longer active and the mask cannot be edited</span>\n         <div class= \"card-footer\">\n           <input type= \"submit\" class= \"btn btn-danger\" value= \"Delete Mask\" />\n         </div>\n       </simple-card-page>)\n      (t\n       (mask-editor channel screenshot :redirect (format nil \"/channels/~a\" (store-object-id channel)))))))\n\n(defun confirm-delete (channel)\n  (cond\n    ((company-admin-p (channel-company channel)\n                      (current-user))\n     (confirmation-page\n      :yes (nibble ()\n             (perform-delete channel)\n             (channel-deleted-confirmation))\n      :no (nibble ()\n            (go-back channel))\n      :danger t\n      <span>Deleting this channel will delete all associated runs and reports. This cannot be\n        undone. Are you sure you want to continue?</span>))\n    (t\n     <simple-card-page>\n       <span>You must be a company admin to delete channels.</span>\n     </simple-card-page>)))\n\n(defun channel-deleted-confirmation ()\n  <simple-card-page>\n    <span>Channel deleted. <a href= \"/channels\">Back to channels</a></span>\n  </simple-card-page>)\n\n(defun perform-delete (channel)\n  (check-type channel channel)\n  (without-sync ()\n   (with-channel-lock (channel)\n     (let ((company (company channel)))\n       (assert company)\n       (with-transaction ()\n         (setf (company-reports company)\n               (remove channel (company-reports company)\n                       :key #'report-channel)))\n       (fset:do-set (run (runs-for-channel channel))\n         (delete-run run))\n       (with-transaction ()\n         (setf (company-channels company)\n               (remove channel (company-channels company))))\n       (with-transaction ()\n         (setf (company channel) nil))))))\n\n\n(defun guess-channel-args (channel)\n  (list\n   :org (oid (channel-company channel))\n   :channel (channel-name channel)\n   :branch (caar (all-active-runs channel))))\n\n(defun safe-channel-name (channel)\n  (let ((name (channel-name channel)))\n    (if (str:emptyp name)\n        <em>empty</em>\n        name)))\n\n(deftag channel-list-row (&key channel)\n  <taskie-row object=channel >\n    <a href=(hex:make-url\n             'single-channel-page\n             :id (store-object-id channel))>\n      ,(safe-channel-name channel)\n    </a>\n    ,(taskie-timestamp :timestamp (created-at channel))\n  </taskie-row>)\n\n(deftag explain-channels ()\n  <div>\n    <p>A <b>channel</b> is a name used to track a collection of screenshots.</p>\n\n    <p>Channel names are arbitrary. It could refer to build targets in your repository, or maybe <code>staging</code> or <code>production</code>, or just your repository name on GitHub.</p>\n\n    <p>You don't have to explicitly create a channel before you use it. Calling the SDK with <code>--channel</code> will automatically create the channel for you.</p>\n\n    <p>Web projects will also be associated with a channel by the same name as the project.</p>\n  </div>)\n\n(defun channel-page-title ()\n  <span>\n    Channel List <explain title= \"Channels\">,(explain-channels)</explain>\n  </span>)\n\n(defun run-for-channel (&key channel company branch)\n  \"Use this for external links that need to reference a specific channel.\n We'll return the based run for the given arguments\"\n  (let ((company (if (typep company 'company)\n                     company\n                     (find-by-oid company))))\n    (check-type company company)\n    (let ((channel (loop for c in (company-channels company)\n                         if (string-equal channel (channel-name c))\n                           return c)))\n      (let ((run (active-run channel branch)))\n        run))))\n\n(defvar *badge-cache* (make-mp-hash-table :test #'equal))\n\n(defun badge-data (&key label message color)\n  (util:or-setf\n   (gethash (list :v1 label message color) *badge-cache*)\n   ;; TODO: use something like\n   ;; https://github.com/dsibilio/badge-maker. Easier to unit test\n   ;; that way.\n   (let ((url \"https://img.shields.io/static/v1\"))\n     (http-request\n      url\n      :parameters `((\"label\" . ,label)\n                    (\"message\" . ,message)\n                    (\"color\" . ,color))\n      :accept \"image/svg+xml\"\n      :want-string t))))\n\n(defhandler (badge-handler :uri \"/badge\") (org channel branch)\n  (let ((run (run-for-channel\n              :channel channel\n              :company (or org (current-company))\n              :branch branch)))\n    (when run\n     (unless (allow-public-badge-p (recorder-run-channel run))\n       (auth:can-view! run)))\n    \n    (let ((data (badge-data\n                 :label \"Screenshotbot\"\n                 :message (if run (format nil \"~a screenshots\" (length (recorder-run-screenshots run)))\n                              \"No active run for parameters\")\n                 :color (if run \"green\" \"red\"))))\n      (setf (hunchentoot:header-out :cache-control) \"max-age=600\")\n      (setf (hunchentoot:content-type*) \"image/svg+xml\")\n      data)))\n\n(defhandler (badge-handler-svg :uri \"/badge.svg\") (org channel branch)\n  (badge-handler :org org :channel channel :branch branch))\n\n(defun %render-channels-as-taskie (channels &key next-link prev-link)\n  (taskie-list :empty-message \"No projects to show! Projects are\n                                   automatically created when you start a run\"\n               :items channels\n               :headers (list \"Channel\" \"First created\")\n               :next-link next-link\n               :checkboxes nil\n               :prev-link prev-link\n               :row-generator (lambda (channel)\n                                (channel-list-row :channel channel))))\n\n(defun %render-channels-for-search-query (company query)\n  (let* ((query (str:downcase query))\n         (channels (company-channels company))\n         (channels (loop for channel in channels\n                         if (str:containsp query (str:downcase (channel-name channel)))\n                           collect channel)))\n    (%render-channels-as-taskie\n     (loop for channel in channels\n           for i from 0 below 200\n           collect channel))))\n\n(defun %list-projects (&key\n                         (user (current-user))\n                         (company (current-company)))\n  (auth:can-view! company)\n  (let ((channel-search (nibble (search)\n                          (markup:write-html\n                           (%render-channels-for-search-query company search))))\n        (channels (sort (copy-list (company-channels company))\n                        '|STRING<| :key 'channel-name)))\n    (with-pagination (channels channels :next-link next-link :prev-link prev-link)\n      (dashboard-template :user user :company company :script-name \"/channels\" :title \"Screenshotbot: Channels\"\n       <taskie-page-title title= (channel-page-title) >\n\n         <div class= \"input-group mb-3\">\n           <span class= \"input-group-text channel-search border-0\" >\n             <mdi name= \"search\" />\n           </span>\n           <input class= \"form-control search d-inline-block border-0\" type= \"text\" autocomplete= \"off\"\n                  placeholder= \"Search channels\"\n                  data-target= \"#channel-result\" />\n          </div>\n       </taskie-page-title>\n\n       <div id= \"channel-result\" data-args= \"{}\" data-update=channel-search\n            data-save-original= \"true\" >\n         ,(%render-channels-as-taskie channels :next-link next-link\n                                      :prev-link prev-link)\n       </div>))))\n\n\n(defhandler (projects-page :uri \"/channels\") ()\n  (with-login ()\n    (%list-projects)))\n\n(defhandler (channels-page :uri \"/projects\") ()\n  (hex:safe-redirect 'projects-page))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/commit-graph\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:screenshotbot/template)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:commit-graph-dag)\n  (:import-from #:screenshotbot/git-repo\n                #:commit-graph)\n  (:import-from #:dag\n                #:ordered-commits)\n  (:import-from #:util/timeago\n                #:timeago)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:nibble\n                #:nibble)\n  (:export\n   #:view-git-graph))\n(in-package :screenshotbot/dashboard/commit-graph)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun %draw-graph (commit-graph)\n  (let* ((input (with-output-to-string (s)\n                  (loop for commit in (loop for x in (ordered-commits commit-graph)\n                                            for i below 1000\n                                            collect x)\n                        do (format s \"~a~{ ~a~}~%\"\n                                   (dag:sha commit)\n                                   (dag:parents commit)))))\n         (output (uiop:run-program (list (namestring (asdf:system-relative-pathname :screenshotbot \"dashboard/git-graph\")))\n                           :input (make-string-input-stream input)\n                           :output :string)))\n    <app-template>\n      <pre>,(progn output)</pre>\n    </app-template>))\n\n(deftag view-git-graph (repo)\n  (let* ((commit-graph (commit-graph-dag (commit-graph (car repo))))\n         (commits (dag:ordered-commits commit-graph)))\n    <app-template>\n      <div class= \"alert alert-info mt-3\">\n        This shows all the information we have about your Git commit\n history. In particular, we only store the Git hashes. This\n information here is for debugging information only when reaching out\n to Screenshotbot support. (ID ,(progn (bknr.datastore:store-object-id (commit-graph (car repo)))))\n\n        <a href= (nibble () (%draw-graph commit-graph) )> Draw Graph </a>\n      </div>\n      <table class= \"table git-graph\" >\n        <thead>\n          <tr>\n            <th>Commit hash</th>\n            <th>Parents</th>\n            <th>First seen</th>\n            <th>Actions</th>\n          </tr>\n        </thead>\n        <tbody>\n        ,@ (loop for commit in commits collect\n                 <tr id= (dag:sha commit) >\n                   <td class= \"font-monospace\" >,(str:shorten 13 (dag:sha commit)) </td>\n                   <td class= \"font-monospace\" >\n                     ,@ (loop for parent in (dag:parents commit)\n                              collect\n                              <span>\n                                <a href= (format nil \"#~a\" parent) class= \"commit-link\" >,(str:shorten 13 parent)</a>\n                              </span>)\n\n                   </td>\n                   <td>\n                     <timeago timestamp= (ignore-errors (dag:commit-timestamp commit)) />\n                   </td>\n                   <td><button class= \"highlight-branch btn btn-link\" data-commit= (dag:sha commit) >Highlight Branch</button></td>\n                 </tr>)\n        </tbody>\n      </table>\n    </app-template>))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/compare-branches.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/compare-branches\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/form-errors\n                #:with-error-builder)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:recorder-run-channel\n                #:recorder-run-commit)\n  (:import-from #:screenshotbot/model/run-commit-lookup\n                #:find-runs-by-commit)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-title\n                #:diff-report-run\n                #:diff-report-empty-p\n                #:make-diff-report)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:screenshotbot/model/channel\n                #:repos-for-company)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler))\n(in-package :screenshotbot/dashboard/compare-branches)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *throttler* (make-instance 'throttler\n                                   :tokens 100))\n\n(defhandler (nil :uri \"/compare-branches/search-sha\") ()\n  (json:encode-json-to-string\n   #(\"foo\" \"bar\")))\n\n(defun maybe-parameter (param)\n  (hunchentoot:parameter (str:downcase param)))\n\n(defun %form ()\n  (let* ((repos (repos-for-company (auth:current-company)))\n         (action (nibble (sha1 sha2 repo-idx)\n                  (%post :sha1 sha1 :sha2 sha2\n                         :repo (elt repos (parse-integer repo-idx)))))\n         (autocomplete \"/compare-branches/search-sha\"))\n    <simple-card-page form-action=action >\n      <div class= \"card-header\">\n        <h4>Compare branches or commits</h4>\n      </div>\n      ,(unless (and (maybe-parameter :repo)\n                    (maybe-parameter :sha1)\n                    (maybe-parameter :sha2))\n         <div class= \"alert alert-info mb-3\">\n           We currently do not support branch names, but you can auto-fill commit information when linking from external tools. Use <tt>?repo=<em>repo</em>&sha1=<em>sha</em>&sha2=<em>sha</em></tt> to prefill this form.\n         </div>)\n      <div class= \"\">\n\n        <div class= \"mb-3\">\n          <label for= \"repo\" class= \"form-label\">\n            Repository\n          </label>\n          <select class= \"form-select\" id= \"repo\" name= \"repo-idx\" >\n            ,@ (loop for repo in repos\n                     for idx from 0\n                     collect <option value= idx >,(progn repo)</option>)\n          </select>\n        </div>\n        <div class= \"mb-2\">\n          <label for= \"sha1\" class= \"form-label\" >\n            First SHA\n          </label>\n          <input type= \"text\" class= \"form-control sha-autocomplete\" id= \"sha1\" name= \"sha1\" placeholder= \"abcdef0102\" data-autocomplete=autocomplete value= (maybe-parameter :sha1) />\n        </div>\n\n        <div class= \"mb-2\">\n          <label for= \"sha2\" class= \"form-label\" >\n            Second SHA\n          </label>\n          <input type= \"text\" class= \"form-control sha-autocomplete\" id= \"sha2\" name= \"sha2\" placeholder= \"abcdef0102\"\n                 data-autocomplete=autocomplete\n                 value= (maybe-parameter :sha2) />\n        </div>\n\n      </div>\n      \n      <div class= \"card-footer\">\n        <input type= \"submit\" value= \"Compare\" class= \"btn btn-primary\" />\n        <a href= \"/\" class= \"btn btn-secondary\" >Cancel</a>\n      </div>\n    </simple-card-page>))\n\n(defhandler (nil :uri \"/compare-branches\") ()\n  (%form))\n\n(defun resolve-commits (company prefix &key repo)\n  (fset:convert 'fset:set\n                (mapcar #'recorder-run-commit\n                        (find-runs-by-commit prefix :company company :repo repo))))\n\n(defun %post (&key sha1 sha2 repo)\n  (throttle! *throttler* :key (auth:current-user))\n  (with-error-builder (:check check\n                       :errors errors\n                       :form-builder (%form)\n                       :form-args (:sha1 sha1\n                                   :sha2 sha2)\n                       :success (%perform :sha1 sha1 :sha2 sha2 :repo repo))\n    (assert repo)\n    (macrolet ((both-check (expr message)\n                 \"Simple macro to run a check on both sha1 and sha2. Use SHA in the\n                  expression instead of SHA1/SHA2\"\n                 `(progn\n                    ,@(loop for key in '(sha1 sha2)\n                            collect\n                            `(check ,(intern (string key) :keyword)\n                                    (let ((sha ,key))\n                                      ,expr)\n                                    ,message)))\n                 ))\n      (both-check (> (length sha) 0)\n                  \"Commit SHA should not be empty\")\n      (unless errors\n        (both-check (<= (fset:size (resolve-commits (auth:current-company) sha :repo repo)) 1)\n                    \"Prefix does not uniquely resolve to a commit\"))\n      (unless errors\n        (both-check (eql (fset:size (resolve-commits (auth:current-company) sha :repo repo)) 1)\n                    \"Could not find a commit with that prefix\"))\n      (unless errors\n        (both-check\n         (< (length (find-runs-by-commit sha\n                                         :company (auth:current-company)\n                                         :repo repo))\n            1000)\n         \"Too many runs for that commit to compare\")))\n    \n    (unless errors\n      (check :sha2 (not (equal sha1 sha2))\n             \"The SHAs shouldn't be the same\"))))\n\n(defun remove-dup-runs (runs)\n  (remove-duplicates runs\n                     :key #'recorder-run-channel))\n\n(defun find-run (runs channel)\n  (let ((run (find channel runs :key #'recorder-run-channel)))\n    (when run\n      (auth:can-view! run))\n    run))\n\n(defun diff-report-channel (diff-report)\n  (recorder-run-channel (diff-report-run diff-report)))\n\n(defun %perform (&key sha1 sha2 repo)\n  (let* ((company (auth:current-company))\n         (runs1 (remove-dup-runs (find-runs-by-commit sha1 :company company :repo repo)))\n         (runs2 (remove-dup-runs (find-runs-by-commit sha2 :company company :repo repo)))\n         (channels1 (mapcar #'recorder-run-channel runs1))\n         (channels2 (mapcar #'recorder-run-channel runs2))\n         (common-channels (intersection channels1 channels2))\n         (added (set-difference channels1 channels2))\n         (removed (set-difference channels2 channels1))\n         (diff-reports (loop for channel in common-channels\n                             collect\n                             (make-diff-report\n                              (find-run runs1 channel)\n                              (find-run runs2 channel))))\n         (changed-diff-reports (remove-if #'diff-report-empty-p diff-reports))\n         (unchanged-diff-reports (remove-if-not #'diff-report-empty-p diff-reports)))\n    (declare (ignore unchanged-diff-reports\n                     added\n                     removed))\n    (flet ((list-channels (diff-reports)\n             <table class= \"table border table-striped table-hover\" >\n               ,@ (loop for diff-report in diff-reports\n                        for channel = (diff-report-channel diff-report)\n                        for href = (format nil \"/runs/~a/compare/~a\"\n                                           (oid (find-run runs1 channel))\n                                           (oid (find-run runs2 channel)))\n                        collect\n                        <tr>\n                          <td>\n                            <a href= href >,(channel-name channel)</a>\n                          </td>\n                          <td>\n                            ,(diff-report-title diff-report)\n                          </td>\n                        </tr>)\n             </table>))\n      <app-template>\n        <h4 class= \"mt-2 mb-2\" >Changed channels</h4>\n        ,(list-channels changed-diff-reports)\n      </app-template>)))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/compare.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/compare\n  (:nicknames #:screenshotbot/compare #|For bknr|#)\n  (:use #:cl\n        #:alexandria\n        #:nibble\n        #:screenshotbot/template\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/model/image-comparison\n        #:screenshotbot/model/image\n        #:screenshotbot/model/view\n        #:screenshotbot/model/report\n        #:screenshotbot/model/channel\n        #:screenshotbot/ignore-and-log-errors\n        #:screenshotbot/model/recorder-run)\n  (:import-from #:util\n                #:find-by-oid\n                #:oid)\n  (:import-from #:markup #:deftag)\n  (:import-from #:screenshotbot/server\n                #:make-thread\n                #:defhandler)\n  (:import-from #:screenshotbot/report-api\n                #:render-diff-report)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:render-run-tags\n                #:modal-id\n                #:render-modal\n                #:screenshots-viewer\n                #:run-row-filter\n                #:page-nav-dropdown\n                #:row-filter\n                #:mask-editor\n                #:commit)\n  (:import-from #:screenshotbot/model/image\n                #:dimension-width\n                #:dimension-height\n                #:image-dimensions\n                #:map-unequal-pixels\n                #:image-blob\n                #:rect-as-list)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:bknr.datastore\n                #:make-blob-from-file)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:can-view!\n                #:current-user\n                #:created-at\n                #:current-company)\n  (:import-from #:screenshotbot/dashboard/image\n                #:handle-resized-image)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:nibble\n                #:nibble-url)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:get-px-as-string\n                #:magick-get-image-width\n                #:magick-get-image-height\n                #:get-non-alpha-pixels\n                #:with-wand)\n  (:import-from #:screenshotbot/diff-report\n                #:group-diff-report\n                #:diff-report-run\n                #:group-renamed-p\n                #:group\n                #:deleted-groups\n                #:added-groups\n                #:diff-report-changes\n                #:make-diff-report\n                #:actual-item\n                #:changes-groups\n                #:get-tab-title)\n  (:import-from #:bknr.datastore\n                #:cascading-delete-object)\n  (:import-from #:core/ui/taskie\n                #:timeago)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/model/transient-object\n                #:with-transient-copy)\n  (:import-from #:screenshotbot/model/image-comparison\n                #:image-comparison-before\n                #:find-image-comparison-on-images)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:screenshotbot/model/view\n                #:can-edit\n                #:can-edit!)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:review-link)\n  (:import-from #:screenshotbot/cdn\n                #:make-image-cdn-url)\n  (:import-from #:screenshotbot/model/screenshot\n                #:abstract-screenshot)\n  (:import-from #:alexandria\n                #:remove-from-plist\n                #:when-let)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-branch\n                #:recorder-run-tags\n                #:run-build-url)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page\n                #:simple-card-page)\n  (:import-from #:screenshotbot/model/review-policy\n                #:can-review?)\n  (:import-from #:screenshotbot/model/channel\n                #:shortened-channel-name\n                #:review-policy)\n  (:import-from #:screenshotbot/model/report\n                #:report-channel)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:screenshot-key)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo\n                #:commit-graph-dag\n                #:commit-graph)\n  (:import-from #:screenshotbot/login/common\n                #:with-login)\n  (:import-from #:screenshotbot/model/figma\n                #:figma-link-url\n                #:figma-link-image\n                #:find-existing-figma-link)\n  (:import-from #:screenshotbot/figma/dropdown\n                #:figma-drop-down)\n  (:export\n   #:find-commit-path-to-ancestor\n   #:render-acceptable\n   #:render-diff-report\n   #:warmup-comparison-images)\n  (:local-nicknames (#:diff-report #:screenshotbot/diff-report)))\n(in-package :screenshotbot/dashboard/compare)\n\n;; fake symbols for bknr migration\n(progn\n '(result\n   before\n   after\n   identical-p\n   result))\n\n(defvar *summarizer* nil)\n\n(defparameter *always-async-p* nil\n  \"Always use async for WITH-ASYNC-DIFF-REPORT. Only useful for testing the async flow.\")\n\n(named-readtables:in-readtable markup:syntax)\n\n(defhandler (acceptable-review-url :uri \"/acceptable/:id/review\" :method :post)\n            (id action redirect csrf)\n  (assert (equal csrf (auth:csrf-token)))\n  (with-login ()\n   (let ((acceptable (bknr.datastore:store-object-with-id (parse-integer id))))\n     (can-edit! acceptable)\n     (let ((state (cond\n                    ((string-equal \"accept\" action)\n                     :accepted)\n                    ((string-equal \"reject\" action)\n                     :rejected)\n                    (t\n                     (error \"Unknown acceptable review action: ~a\" action)))))\n       (setf (acceptable-state acceptable :user (current-user))\n             state)\n       (hex:safe-redirect redirect)))))\n\n(deftag render-acceptable (&key acceptable)\n  (let ((review-url (hex:make-url 'acceptable-review-url :id (bknr.datastore:store-object-id\n                                                              acceptable)))\n        (btn-class\n          (ecase (acceptable-state acceptable)\n            (:accepted\n             \"dropdown-report-accepted\")\n            (:rejected\n             \"dropdown-report-rejected\")\n            ((nil)\n             \"\")))\n        (btn-text\n          (ecase (acceptable-state acceptable)\n            (:accepted\n             \"Accepted\")\n            (:rejected\n             \"Rejected\")\n            ((nil)\n             \"Review\"))))\n    (flet ((form-menu (&key title action mdi)\n             <form action=review-url method= \"POST\" >\n               <button action= \"submit\" class= (format nil \"btn btn-link acceptable ~a-link dropdown-item\" action) >\n                 <input type= \"hidden\" name= \"action\" value= action />\n                 <input type= \"hidden\" name= \"csrf\" value= (auth:csrf-token) />\n                 <input type= \"hidden\" name= \"redirect\"\n                        value= (hunchentoot:script-name*) />\n                 <mdi name=mdi />\n                 ,(progn title)\n               </button>\n             </form>))\n<markup:merge-tag>\n        <button class= (format nil\"btn  btn-secondary dropdown-toggle ~a\" btn-class) type=\"button\" id=\"reviewDropdownMenuButton\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          ,(progn btn-text)\n        </button>\n\n        ,(let ((channel (report-channel (acceptable-report acceptable))))\n           (cond\n             ((null (auth:current-user))\n              (let ((redirect (hunchentoot:script-name*)))\n                <div class=\"dropdown-menu dropdown-menu-end review-dropdown-menu\" aria-labelledby=\"reviewDropdownMenuButton\" style= \"z-index: 99999999; position: static\" >\n                  <a class= \"dropdown-item\" href= (nibble ()\n                                                    (with-login ()\n                                                      (hex:safe-redirect redirect))) >\n                    Sign in to review\n                  </a>\n                </div>))\n             ((can-review? (review-policy channel)\n                           (acceptable-report acceptable)\n                           (auth:current-user))\n              <div class=\"dropdown-menu dropdown-menu-end review-dropdown-menu\" aria-labelledby=\"reviewDropdownMenuButton\" style= \"z-index: 99999999; position: static\" >\n                ,(form-menu :title \"Accept\" :action \"accept\" :mdi \"check\" )\n                ,(form-menu :title \"Reject\" :action \"reject\" :mdi \"close\")\n              </div>)\n             (t\n              <div class=\"dropdown-menu p-4 text-muted\" style=\"max-width: 200px;\">\n                <p class= \"mb-0\" >\n                  The <a href= (format nil \"/channels/~a#review-policy\" (store-object-id channel))>review policy</a> for this channel does not let authors review their own screenshots.\n                </p>\n              </div>)))\n      </markup:merge-tag>)))\n\n(def-easy-macro with-runs-for-comparison (&binding run &binding to\n                                                   &key run-id to-id\n                                                   &fn fn)\n  (flet ((find-run (id)\n           (let ((ret (find-by-oid id 'recorder-run)))\n             (assert ret)\n             ret)))\n    (let* ((run (find-run run-id))\n           (to (find-run to-id)))\n      (can-view! run to)\n      (fn run to))))\n\n(defhandler (compare-page :uri \"/runs/:id/compare/:to\") (id to)\n  (when (string= \"254\" id)\n    (hex:safe-redirect \"/report/5fd16bcf4f4b3822fd000146\"))\n  (with-runs-for-comparison (run to :run-id id :to-id to)\n    <app-template body-class= \"dashboard\" content=nil >\n      ,(async-diff-report :run run :to to\n                          :script-name (format nil \"/runs/~a/compare/~a\" (oid run) (oid to)))\n    </app-template>))\n\n(deftag picture-with-img (&key image dimensions alt class)\n  <picture>\n    <source srcset= (image-public-url image :size :full-page :type :webp) type= \"image/webp\" />\n    <img class= (format nil \"screenshot-image change-image ~a\" class) src= (image-public-url image :size :full-page :type :png)\n         loading= \"lazy\"\n         alt=alt\n         width= (?. dimension-width dimensions)\n         height= (?. dimension-height dimensions)\n/>\n\n  </picture>)\n\n(markup:deftag image-badge (children &key class)\n  <span class= (format nil \"screenshot-image-badge position-absolute badge bg-primary text-white ~a\" class)\n        style=\"font-size: 0.7em; top: -0.25rem; left: 0; margin-left: 0.25rem; z-index: 10;  line-height: 1; vertical-align: top;\">\n    ,@children\n  </span>)\n\n(markup:deftag image-container (children)\n  \"A wrapper for images, that allows us to place widgets around the image.\"\n  <div class= \"d-inline-block image-container\">\n    <div class= \"position-relative\">\n        ,@children\n      </div>\n  </div>)\n\n(deftag change-image-row (&key before-image\n                          after-image\n                          before-dims\n                          after-dims\n                          figma-link)\n  <div class=\"change-image-row\">\n\n    <image-container>\n      <picture-with-img\n        image=before-image\n        dimensions=before-dims\n        alt= \"before image\"\n        class= \"change-image-left\" />\n      <image-badge class= \"bg-secondary non-hover-badge\" >\n        Previous\n      </image-badge>\n\n      <image-badge class= \"bg-secondary d-none hover-badge\" >\n        Updated (toggled by hover)\n      </image-badge>\n    </image-container>\n\n    <div class= \"arrow-forward\" >\n      <mdi name= \"arrow_forward\" />\n    </div>\n\n    <image-container>\n      <picture-with-img\n        image=after-image\n        dimensions=after-dims\n        alt=\"after image\"\n        class= \"change-image-right\" />\n      <image-badge class= \"bg-success\" >\n        Updated\n      </image-badge>\n    </image-container>\n\n    ,(when figma-link\n       <div class=\"figma-link mt-2\">\n         <image-container>\n           <a href=(figma-link-url figma-link)\n              target=\"_blank\"\n              >       \n             <picture-with-img\n               image=(figma-link-image figma-link)\n               dimensions=(ignore-errors (image-dimensions (screenshotbot/model/figma:figma-link-image figma-link)))\n               alt=\"Figma component\"\n               class=\"figma-preview me-2\"\n               />\n             <image-badge>\n                  Figma\n             </image-badge>\n           </a>\n         </image-container>\n       </div>)\n\n\n\n  </div>)\n\n\n(defclass image-comparison-job ()\n  ((donep :initform nil\n          :accessor donep)\n   (lock :initform (bt:make-lock \"image-comparison\")\n         :reader lock)\n   (before-image :initarg :before-image\n                 :reader before-image)\n   (after-image :initarg :after-image\n                :reader after-image)\n   (image-comparison\n    :initarg :image-comparison\n    :accessor image-comparison\n    :documentation \"The actual image-comparison object. You can get\n    the resulting image and other context from this object\")))\n\n\n(defmethod find-image-comparison ((before-screenshot abstract-screenshot)\n                                  (after-screenshot abstract-screenshot))\n  ;; second level of caching, we're going to look through the\n  ;; datastore to see if there are any previous images\n  \n  (let ((before (screenshot-image before-screenshot))\n        (after (screenshot-image after-screenshot)))\n    ;; Avoid computation for large reverts\n    (find-image-comparison-on-images\n     before\n     after)))\n\n(defmethod prepare-image-comparison-file ((self image-comparison-job))\n  (bt:with-lock-held ((lock self))\n    (cond\n      ((donep self)\n       (image-comparison self))\n      (t\n       (setf (image-comparison self)\n             (find-image-comparison (before-image self)\n                                    (after-image self)))))))\n\n(defmethod prepare-image-comparison ((self image-comparison-job)\n                                     &key\n                                       ;; I can't use :full-page here because the JS isn't\n                                       ;; designed to handle that yet.\n                                       (size nil)\n                                       (warmup nil))\n  (let ((image-comparison (prepare-image-comparison-file self)))\n    (cond\n      (warmup\n       (when size\n        (handle-resized-image (image-comparison-result image-comparison) size :warmup t)))\n      (t\n       (setf (hunchentoot:content-type*) \"application/json\")\n       (let ((masks (screenshot-masks (after-image self))))\n        (json:encode-json-to-string\n         `((:identical . ,(identical-p image-comparison))\n           ;; for debugging: e.g. if we need to delete the comparison\n           (:store-object-id\n            .\n            ,(when (typep image-comparison 'store-object)\n               (bknr.datastore:store-object-id image-comparison)))\n           (:zoom-to . ,(nibble-url (nibble (:name :zoom) (random-zoom-to-on-result\n                                                           image-comparison masks))))\n           (:metrics . ,(nibble-url (nibble (:name :metrics)\n                                      (metrics-page image-comparison masks))))\n           (:src . ,(make-image-cdn-url (image-public-url (image-comparison-result image-comparison) :size size)))\n           (:background . ,(make-image-cdn-url (image-public-url (screenshot-image (before-image self)) :size size)))\n           (:after-image . ,(make-image-cdn-url (image-public-url (screenshot-image (after-image self)) :size size)))\n           (:masks .\n                   ,(or\n                     (loop for mask in masks\n                           collect\n                           `((:left . ,(mask-rect-left mask))\n                             (:top . ,(mask-rect-top mask))\n                             (:width . ,(mask-rect-width mask))\n                             (:height . ,(mask-rect-height mask))))\n                     #())))))))))\n\n(defun random-non-alpha-px (wand masks)\n  (let ((pxs (get-non-alpha-pixels wand\n                                   :masks masks)))\n    (let ((num (car (array-dimensions pxs))))\n     (cond\n       ((= num 0)\n        (values -1 -1))\n       (t\n        (let ((i (random num)))\n          (values\n           (aref pxs i 0)\n           (aref pxs i 1))))))))\n\n(defun random-zoom-to-on-result (image-comparison masks)\n  (setf (hunchentoot:content-type*) \"application/json\")\n  (with-local-image (file (image-comparison-result image-comparison))\n    (with-wand (wand :file file)\n      (log:debug\"random-zoom-to-on-result on ~a\" file)\n      (multiple-value-bind (x y) (random-non-alpha-px wand masks)\n        (let ((dims (image-dimensions (image-comparison-result image-comparison))))\n          (json:encode-json-to-string\n           `((:y . ,y)\n             (:x . ,x)\n             (:width . ,(dimension-width dims))\n             (:height . ,(dimension-height dims)))))))))\n\n(def-easy-macro with-async-diff-report (&binding diff-report &key run to &fn fn)\n  \"Returns a HTML tag that asynchronoously creates a diff-report using\nMAKE-DIFF-REPORT, and finally when that is complete, it renders the\ncontent of the HTML tag using the nested body.\n\nIf the diff-report is cached, then we process the body immediately instead.\"\n  (let ((cached (make-diff-report run to :only-cached-p t)))\n   (cond\n     ((and cached (not *always-async-p*))\n      (fn cached))\n     (t\n      (let* ((data nil)\n             (data-check-nibble (nibble (:name :data-check)\n                                  (setf (hunchentoot:content-type*) \"application/json\")\n                                  (json:encode-json-to-string\n                                   (cond\n                                     ((eql :error data)\n                                      `((:state . \"error\")))\n                                     (data\n                                      `((:data . ,(markup:write-html (fn data)))\n                                        (:state . \"done\")))\n                                     (t\n                                      `((:state . \"processing\"))))))))\n        (make-thread (lambda ()\n                       (handler-bind ((error (lambda (e)\n                                               (declare (ignore e))\n                                               (setf data :error))))\n                         (let ()\n                           (setf data (make-diff-report run to))))))\n        <div class= \"async-fetch spinner-border\" role= \"status\" data-check-nibble=data-check-nibble />)))))\n\n\n(defun async-diff-report (&rest args &key run to &allow-other-keys)\n  (with-async-diff-report (diff-report :run run :to to)\n    (apply #'render-diff-report\n           :diff-report diff-report\n           (remove-from-plist args :run :to))))\n\n(defun warmup-report (report)\n  (when (report-previous-run report)\n    (warmup-comparison-images (report-run report) (report-previous-run report))))\n\n\n(defun warmup-comparison-images (run previous-run)\n  (make-thread\n   (lambda ()\n     (warmup-comparison-images-sync run previous-run))\n   :name \"warmup-comparison-images\"))\n\n(auto-restart:with-auto-restart ()\n  (defun warmup-comparison-images-sync (run previous-run)\n    (let ((report (diff-report:make-diff-report run previous-run)))\n      ;; warmup in the order that the report would be typically\n      ;; viewed.\n      (dolist (group (changes-groups report))\n        (let ((changes (mapcar #'actual-item (diff-report:group-items group))))\n          (loop for change in changes\n                for i from 1\n                for before = (diff-report:before change)\n                for after = (diff-report:after change)\n                for image-comparison-job = (make-instance 'image-comparison-job\n                                                          :before-image before\n                                                          :after-image after)\n                do\n                   (progn\n                     (when (> i 100)\n                       ;; Throttle\n                       (sleep 1))\n                     (log:info \"Warming up compare image ~d of ~d (~a)\" i (length changes)\n                               (screenshot-name after))\n                     (restart-case\n                         (prepare-image-comparison image-comparison-job :size nil\n                                                                        :warmup t)\n                       (ignore-this-image ()\n                         nil)))))))))\n\n\n(deftag progress-img (&key (alt \"Image Difference\") src class)\n  \"An IMG with a progress indicator for the image loading.\"\n\n  <div class= (format nil  \"progress-image-wrapper ~a\" class) >\n  <div class= \"alert alert-danger images-identical\" style= \"display:none\" >\n    <p>\n      <strong>The two images are identical.</strong> This is likely because the images still have their EXIF data, e.g. timestamps.\n    </p>\n\n    <p class= \"mb-0\" >\n      You can pre-process images to remove timestamps. One way to do this is to use: `<:tt>exiftool -all= *.png</:tt>`.\n    </p>\n\n  </div>\n    <div class= \"loading\">\n      <div class=\"spinner-border\" role=\"status\">\n        <!-- < class=\"sr-only\">Loading...</span> -->\n      </div>\n      Loading (this could take upto 30s in some cases)\n    </div>\n\n    <div>\n      <div class= \"alert alert-info\">\n        <strong>New interactive comparisons!</strong> Use your mouse to pan through the image. Use the <strong>mouse wheel</strong> to zoom into a location.\n      </div>\n      <div class= \"canvas-container image-comparison-modal-image\"\n           data-src=src />\n    </div>\n  </div>)\n\n(deftag zoom-to-change-button ()\n  <button type=\"button\" class=\"btn btn-secondary zoom-to-change\">\n    <div class=\"spinner-border\" role=\"status\" style=\"display:none; height: 1em; width: 1em\" />\n    Zoom to change\n  </button>)\n\n(defclass tab ()\n  ((title :initarg :title\n          :reader tab-title)\n   (content :initarg :content\n            :reader tab-content)))\n\n(defun maybe-tabulate (tabs &key header &aux (id (format nil \"a~a\" (random 10000000))))\n  (cond\n    ((and (eql 1 (length tabs))\n          (str:emptyp (tab-title (car tabs))))\n     ;; don't show the tabulation\n     <markup:merge-tag>\n       <div class= \"card-header\">\n         ,(progn header)\n       </div>\n       <div class= \"card-body\">\n         ,(tab-content (car tabs))\n       </div>\n     </markup:merge-tag>)\n    (t\n     <markup:merge-tag>\n       <div class= \"card-header\">\n         ,(progn header)\n         <ul class= \"nav nav-tabs card-header-tabs\" role= \"tablist\" >\n           ,@ (loop for tab in tabs\n                    for ctr from 0\n                    collect\n                    <li class= \"nav-item\" role= \"presentation\" >\n                      <button class= (format nil \"nav-link ~a\" (if (= ctr 0) \"active\" \"\"))\n                              data-bs-toggle= \"tab\"\n                              data-bs-target= (format nil \"#~a-~a\" id ctr)\n                              data-title= (tab-title tab)\n                              role= \"tab\"\n                              aria-controls= (format nil \"~a-~a\" id ctr)\n                              aria-selector=(if (= ctr 0) \"true\" \"false\") >\n                        ,(tab-title tab)\n                      </button>\n                    </li>)\n         </ul>\n       </div>\n\n       <div class= \"card-body\">\n         <div class= \"tab-content\">\n           ,@(loop for tab in tabs\n           for ctr from 0\n           collect\n           <div class= (format nil \"tab-pane  ~a\" (if (= ctr 0) \"show active\" \"\"))\n                id= (format nil \"~a-~a\" id ctr)\n                role= \"tab-panel\"\n                aria-labelled-by= (tab-title tab) >\n             ,(tab-content tab)\n           </div>)\n         </div>\n         </div>\n\n  </markup:merge-tag>)))\n\n(defun make-overlay-image (before after)\n  (let ((image-comparison-job (make-instance 'image-comparison-job\n     :before-image before\n     :after-image after)))\n     (image-comparison-result\n      (prepare-image-comparison-file image-comparison-job))))\n\n(defun render-change-group (group run script-name &key search\n                                                    index)\n  <div class= \"col-12\">\n  <div class= \"card mb-3\">\n    ,(maybe-tabulate\n      (loop for group-item in (diff-report:group-items group)\n            for change = (actual-item group-item)\n            for next-id = (random 1000000000000000)\n            for after = (diff-report:after change)\n            for before = (diff-report:before change)\n            collect\n    (make-instance\n    'tab\n    :title (diff-report:group-item-subtitle group-item)\n    :content\n    (let* ((after after)\n           (before before)\n           (toggle-id (format nil \"toggle-id-~a\" next-id))\n           (history-url (hex:make-url \"/channel/:channel/history\" :channel (store-object-id (recorder-run-channel run))\n                                                                  :screenshot-name (screenshot-name before)\n                                                                  :branch (or (recorder-run-branch run) \"\"))))\n      \n    <div class= \"\" >\n      <div class= \"screenshot-header\" >\n        <ul class= \"screenshot-options-menu\" >\n          <li>\n            <a href= \"#\" data-bs-toggle= \"modal\" data-bs-target= (format nil \"#~a\" toggle-id) >Compare</a>\n          </li>\n          <li>\n            <a href= history-url >\n              Full History\n            </a>\n          </li>\n          <li>\n            <a href= (nibble (:name :mask-editor) (mask-editor (recorder-run-channel run) after\n               :redirect script-name\n               :overlay (make-overlay-image before after)))\n               target= \"_blank\" >\n              Edit Masks\n            </a>\n          </li>\n\n          <li>\n            <a href= (format nil \"~a/image/~a\" script-name (bknr.datastore:store-object-id\n               (screenshot-key after))) >\n              Permalink\n            </a>\n          </li>\n\n          ,(let ((id (format nil \"a~a\" (random 10000000000))))\n             <li>\n               <a href= \"#\" class= \"dropdown-toggle\" data-bs-toggle= \"dropdown\"\n                  data-bs-target= id\n                  aria-expanded= \"false\" >Download Original</a>\n               <ul class= \"dropdown-menu\" >\n                 <li>\n                   <a class= \"dropdown-item\" href= (image-public-url (screenshot-image before) :originalp t)\n                      >Download Previous image</a>\n                 </li>\n                 <li>\n                   <a class= \"dropdown-item\" href= (image-public-url (screenshot-image after) :originalp t)\n                      >Download Updated image</a>\n                 </li>\n               </ul>\n             </li>)\n\n          <figma-drop-down run=run screenshot=before script-name=script-name />\n        </ul>\n        \n      </div>\n      <change-image-row before-image=(screenshot-image before)\n                        after-image=(screenshot-image after)\n                        before-dims= (ignore-errors (image-dimensions (screenshot-image before)))\n                        after-dims= (ignore-errors (image-dimensions (screenshot-image after)))\n                        figma-link= (find-existing-figma-link \n                                     :channel (recorder-run-channel run) \n                                     :screenshot-name (screenshot-name before))\n                        />\n      <comparison-modal before=before after=after toggle-id=toggle-id />\n    </div>)))\n      :header  (cond\n                 ((stringp (diff-report:group-title group))\n                  <h4 class= \"screenshot-title\"><span class= \"index\">,(1+ index)</span> ,(highlight-search-term search (diff-report:group-title group))</h4>)\n                 (t\n                  (diff-report:group-title group))))\n  </div>\n  </div>)\n\n(defun highlight-search-term (search title)\n  (cond\n    ((str:emptyp search)\n     title)\n    (t\n\n     (let* ((start (search (str:downcase search) (str:downcase title)))\n            (end (+ start (length search))))\n       (markup:make-merge-tag\n        (list\n         <span class= \"text-muted\">,(subseq title 0 start)</span>\n         <span class= \"\" >,(subseq title start end)</span>\n         <span class= \"text-muted\">,(subseq title end)</span>))))))\n\n(deftag comparison-modal (&key toggle-id before after)\n  (let* ((modal-label (format nil \"~a-modal-label\" toggle-id))\n         (image-comparison-job\n           (make-instance 'image-comparison-job\n                           :before-image before\n                           :after-image after))\n         (compare-nibble (nibble (:name :compare-link)\n                           (prepare-image-comparison\n                            image-comparison-job))))\n    <div class= \"modal fade image-comparison-modal\" id= toggle-id tabindex= \"-1\" role= \"dialog\"\n         aria-labelledby=modal-label\n         aria-hidden= \"true\" >\n      <div class=\"modal-dialog\" role=\"document\">\n        <div class=\"modal-content\">\n          <div class=\"modal-header\">\n            <h5 class=\"modal-title\" id=modal-label >Image Comparison</h5>\n            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\" />\n          </div>\n          <div class=\"modal-body\">\n            <progress-img\n              src=compare-nibble\n              alt= \"Image difference\" />\n          </div>\n          <div class=\"modal-footer\">\n            <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\" style= \"margin-right: auto\" >\n              More\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li>\n                <a class= \"dropdown-item view-item view-diff\">\n                  Show Diff\n                </a>\n              </li>\n              <li>\n                <a class= \"dropdown-item view-item view-previous\">\n                  Show Previous Image\n                </a>\n              </li>\n              <li>\n                <a class= \"dropdown-item view-item view-updated\">\n                  Show Updated Image\n                </a>\n              </li>\n              <li>\n                <a class= \"dropdown-item view-toggle\">\n                  Toggle View <div class= \"text-muted float-end\">V</div>\n                </a>\n              </li>\n              <li>\n                <hr class= \"dropdown-divider\" />\n              </li>\n              <li>\n                <a class= \"dropdown-item metrics-link\" href= \"#\" >\n                  Metrics\n                </a>\n              </li>\n\n            </ul>\n\n            <zoom-to-change-button />\n\n            <button type=\"button\" class=\"btn btn-primary\" data-bs-dismiss=\"modal\">Close</button>\n          </div>\n        </div>\n      </div>\n    </div>))\n\n(defvar +metrics-page-changed-limit+ 9999\n  \"The max number of pixels we're willing to detect.\")\n\n(defun pixel-str-to-components (pixel-str)\n  \"Technically I shouldn't have to do this, I'm doing this to avoid a\nnative change in prod.\"\n  (multiple-value-bind (res parts)\n      (cl-ppcre:scan-to-strings \"^srgb[(](.*),(.*),(.*)[)]$\" pixel-str)\n    (assert res)\n    (loop for part across parts\n          collect (parse-integer part))))\n\n(defun sort-pixels (pixels before-values after-values)\n  \"Sort the pixels in descending order of which pixels changed the most. Stable sorted.\"\n  (let ((indices (loop for x below (first (array-dimensions pixels))\n                       collect x)))\n    (let ((indices (stable-sort\n                    indices\n                    #'>\n                    :key (lambda (i)\n                           (let ((px1 (pixel-str-to-components (elt before-values i)))\n                                 (px2 (pixel-str-to-components (elt after-values i))))\n                             (labels ((sq (x) (* x x))\n                                      (diff (j)\n                                        (sq (- (elt px1 j) (elt px2 j))) ))\n                               (+ (diff 0) (diff 1) (diff 2))))))))\n      indices)))\n\n(defun metrics-page (image-comparison masks)\n  \n  (with-local-image (file (image-comparison-result image-comparison))\n    (with-wand (wand :file file)\n      (log:debug\"random-zoom-to-on-result on ~a\" file)\n      (multiple-value-bind (pixels) \n          (get-non-alpha-pixels wand :masks masks :limit (1+ +metrics-page-changed-limit+))\n        (flet ((read-px-values (image)\n                 (with-local-image (file image)\n                   (with-wand (wand :file file)\n                     (loop for i from 0 below (car (array-dimensions pixels))\n                           collect\n                           (get-px-as-string wand\n                                             (aref pixels i 0)\n                                             (aref pixels i 1)))))))\n         (let* ((num-changed (car (array-dimensions pixels)))\n                (width (magick-get-image-width wand))\n                (height (magick-get-image-height wand))\n                (before-values (read-px-values\n                                (image-comparison-before image-comparison)))\n                (after-values (read-px-values\n                               (image-comparison-after image-comparison)))\n                (pixel-indices (sort-pixels pixels before-values after-values)))\n           <app-template>\n             <div class= \"main-content\">\n               <div class= \"card-page-container mx-auto\">\n                 <div class= \"card mt-2\">\n                   <div class= \"card-body\">\n\n                     <table class= \"table\" >\n                       <thead>\n                         <tr>\n                           <td>\n                             Metric\n                           </td>\n                           <td>\n                             Value\n                           </td>\n                         </tr>\n                       </thead>\n                       <tbody>\n                         <tr>\n                           <td>\n                             Dimensions\n                           </td>\n                           <td>\n                             <span>\n                               ,(progn width)x,(progn height)\n                             </span>\n                           </td>\n                         </tr>\n\n                         <tr>\n                           <td>\n                             # changed pixels\n                           </td>\n                           <td>\n                             ,(if (>= num-changed +metrics-page-changed-limit+)\n                                  <span>&gt;</span>\n                                  )\n                             ,(progn num-changed)\n                           </td>\n                         </tr>\n\n                         <tr>\n                           <td>Fraction changed pixels</td>\n                           <td>\n                             ,(if (>= num-changed +metrics-page-changed-limit+)\n                                  <span>&gt;</span>\n                                  )                             \n                             ,(/ (* 1.0 num-changed) (* height width))\n                           </td>\n                         </tr>\n\n\n                       </tbody>\n\n                     </table>\n                   </div>\n                 </div>\n\n                 <div class= \"card mt-2\">\n                   <div class= \"card-body\">\n\n                     <table class= \"table\">\n                       <thead>\n                         <tr>\n                           <td>Position</td>\n                           <td>Previous color</td>\n                           <td>After color</td>\n                         </tr>\n                       </thead>\n                       <tbody>\n                       ,@ (loop for i in pixel-indices\n                                for _ below 4\n                                for before = (elt before-values i)\n                                for after = (elt after-values i)\n                                collect\n                                <tr>\n                                  <td> ,(aref pixels i 0),,(aref pixels i 1)</td>\n                                  <td>,(progn before) </td>\n                                  <td>,(progn after)</td>\n                                </tr>)\n                       </tbody>\n                     </table>\n                   </div>\n                 </div>\n               </div>\n             </div>\n           </app-template>))))))\n\n\n(deftag compare-tab-a (body &key type default-type)\n  <a class= (format nil \"nav-link ~a\" (when (string= type default-type) \"active\"))\n     href= \"#\" data-type= type >\n    ,@body\n  </a>)\n\n(deftag render-compare-header (&key more acceptable report run\n                               default-type\n                               changes-groups\n                               added-groups\n                               deleted-groups)\n  <div class= \"compare-header-wrapper headroom\">\n    <div class= \"content\">\n      <div class= \"d-flex  flex-wrap justify-content-between compare-header\" >\n\n        <div class= \"report-search-wrapper\"  >\n          <div class= \"input-group\">\n            <span class= \"input-group-text report-search\" >\n              <mdi name= \"search\" />\n            </span>\n            <input class= \"form-control search d-inline-block\" type= \"text\" autocomplete= \"off\"\n                   placeholder= \"Search...\"\n                   data-target= \".report-result\" />\n            <render-run-tags tags= (recorder-run-tags run) />\n          </div>\n        </div>\n\n        <div class= \"options\" >\n          <ul class= \"nav nav-pills report-selector\" data-target= \".report-result\" >\n            <li class= \"nav-item\">\n              <compare-tab-a type= \"changes\" default-type=default-type >\n                ,(length changes-groups) changes\n              </compare-tab-a>\n            </li>\n            <li class= \"nav-item\">\n              <compare-tab-a type= \"added\" default-type=default-type >\n                ,(length added-groups) added\n              </compare-tab-a>\n            </li>\n            <li class= \"nav-item\">\n              <compare-tab-a type= \"deleted\" default-type=default-type >\n                ,(length deleted-groups) deleted\n              </compare-tab-a>\n            </li>\n\n            <markup:merge-tag>\n              <li class= \"nav-item\" >\n                <button type=\"button\" class=\"btn btn-secondary dropdown-toggle\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n                  More\n                </button>\n                <ul class=\"dropdown-menu dropdown-menu-end\">\n                  ,@ (loop for (name . url) in more\n                           collect\n                           <li><a class=\"dropdown-item\" href=url >,(progn name)</a></li>)\n                           <li><a role= \"button\" class= \"dropdown-item\" href= \"#\" data-bs-toggle=\"modal\" data-bs-target= \"#comparison-info-modal\"><mdi name= \"info\"/> Info</a></li>\n                           ,(when-let ((url (?. run-build-url run)))\n                              <li><a role= \"button\" class= \"dropdown-item\" href=url target= \"_blank\">\n                                  <mdi name= \"build\" /> View Build</a></li>)\n                           ,(when (and\n                                   (?. adminp (current-user))\n                                   *summarizer*)\n                              <li><a class= \"dropdown-item\" href= (nibble () (funcall *summarizer* report)) > [Admin] Summarize</a></li>)\n\n\n                </ul>\n\n              </li>\n\n              ,(when acceptable\n                 <li class= \"nav-item\" >\n                   <render-acceptable acceptable=acceptable />\n                 </li>)\n            </markup:merge-tag>\n          </ul>\n\n\n\n        </div>\n\n      </div>\n    </div>\n  </div>)\n    \n\n(deftag render-diff-report (children &key\n                            more\n                            diff-report\n                            script-name #| optional |#\n                            acceptable)\n  \"Renders a diff-report.\n\nCHILDREN are rendered after the info bar, and before the images.\n\nSCRIPT-NAME is typically either \\\"/runs/:id/compare/:to\\\" or\n\\\"/report/:oid\\\", and is used to figure out links to sub-pages.\n\nIf ACCEPTABLE is not NIL, then it is rendered as a way to review the\ndiff.\n\nMORE is an alist of names mapped to URLs, which lists all the\nadditional actions in the More dropdown menu.\n\n\"/\n  (let* ((report diff-report)\n         (run (diff-report:diff-report-run diff-report))\n         (to (diff-report:diff-report-previous-run diff-report))\n         (script-name (or script-name (hunchentoot:script-name*))))\n    (let* ((changes-groups (diff-report:changes-groups report))\n           (added-groups (diff-report:added-groups report))\n           (deleted-groups (diff-report:deleted-groups report))\n           (default-type\n             (or\n               (hunchentoot:parameter \"type\")\n               (cond\n                  (changes-groups \"changes\")\n                  (added-groups \"added\")\n                  (deleted-groups \"deleted\")\n                  (t \"changes\")))))\n      <markup:merge-tag>\n        <render-compare-header acceptable=acceptable more=more\n                               report=report\n                               run=run\n                               default-type=default-type\n                               changes-groups=changes-groups\n                               added-groups=added-groups\n                               deleted-groups=deleted-groups />\n\n        <div class= \"content\">\n          ,@children\n\n          <div class= \"report-result\"\n               data-update= (nibble (:name :u-r-res) (report-result run\n               changes-groups\n               added-groups\n               deleted-groups\n               :script-name script-name))\n               data-args= (json:encode-json-to-string `((:type . ,default-type))) >\n            ,(report-result run\n                            changes-groups\n                            added-groups\n                            deleted-groups\n                            :script-name script-name\n                            :type default-type)\n          </div>\n\n          ,(info-modal run to)\n        </div>\n\n      </markup:merge-tag>)))\n\n(deftag link-to-run (&key run)\n  (cond\n    (run\n     <span><a href= (hex:make-url \"/runs/:id\" :id (oid run))>run from ,(timeago :timestamp (created-at run))</a><render-run-tags tags= (recorder-run-tags run) /></span>)\n    (t\n     <span>empty run</span>)))\n\n(defun find-commit-path-to-ancestor (run to)\n  \"Find the commits from the current run to a previous run. If no path\nexists (or is too long), we return NIL.\n\nWe return three values: the path, the hash associated with the current\nrun, and the hash associated with the previous run.\"\n  (when-let* ((repo (channel-repo (recorder-run-channel run)))\n              (this-hash (recorder-run-commit run))\n              (prev-hash (?. recorder-run-commit to))\n              (commit-graph (commit-graph repo))\n              (dag (commit-graph-dag commit-graph)))\n    (when (and this-hash prev-hash)\n      (values\n       (dag:best-path dag\n                      this-hash\n                      prev-hash)\n       this-hash\n       prev-hash))))\n\n(defun %commits-between (run to)\n  (let ((repo (channel-repo (recorder-run-channel run))))\n    (declare (ignore ancestorp))\n    (multiple-value-bind (path this-hash prev-hash)\n        (find-commit-path-to-ancestor run to)\n      (%render-commits-path repo path this-hash prev-hash))))\n\n(defmethod %render-commits-path ((repo generic-git-repo) path this-hash prev-hash)\n  (cond\n    ((not path)\n     <simple-card-page>\n       <div class= \"card-body\" >\n         Could not find path in Git graph from <commit repo=repo hash=this-hash />\n         to <commit repo=repo hash=prev-hash />\n       </div>\n     </simple-card-page>)\n    (t\n     <simple-card-page>\n       <div class= \"card-header\">\n         <h4>Blame commits</h4>\n       </div>\n       <p>\n         This change could be blamed to one or more of these commits:\n       </p>\n       <ol>\n         ,@ (loop for node in (butlast path)\n                  collect <li><commit repo=repo hash=node /></li>)\n       </ol>\n     </simple-card-page>)))\n\n(defhandler (nil :uri \"/blame/:run/to/:to\") (run to)\n  (let ((run (util:find-by-oid run))\n        (to (util:find-by-oid to)))\n    (auth:can-view! run to)\n    (%commits-between run to)))\n\n(defun info-modal (run to)\n  (let ((repo (channel-repo (recorder-run-channel run)))\n        (this-hash (recorder-run-commit run))\n        (prev-hash (?. recorder-run-commit to)))\n    <div class=\"modal\" tabindex=\"-1\" id= \"comparison-info-modal\" >\n      <div class=\"modal-dialog\">\n        <div class=\"modal-content\">\n          <div class=\"modal-header\">\n            <h5 class=\"modal-title\"\n                title= (channel-name (recorder-run-channel run)) >,(shortened-channel-name (channel-name (recorder-run-channel run)) :length 40) </h5>\n            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n          </div>\n          <div class=\"modal-body\">\n            <p>\n\n              <table class= \"table border\" >\n                <thead>\n                  <th>Comparing:</th>\n                  <th>Link to run</th>\n                  <th>Commit</th>\n                </thead>\n                <tr>\n                  <td>This run</td>\n                  <td><link-to-run run=run /></td>\n                  <td>\n                    ,(when (and repo this-hash)\n                       <commit repo=repo hash=this-hash />)\n                    ,(let ((review-link (review-link :run run)))\n                       (when review-link\n                         <span> on ,(progn review-link)</span>))\n                  </td>\n                </tr>\n                <tr>\n                  <td>Previous Baseline</td>\n                  <td><link-to-run run=to /></td>\n                  <td>,(when (and repo prev-hash)\n                         <commit repo=repo hash=prev-hash />)\n                  </td>\n                </tr>\n              </table>\n            </p>\n\n            ,(when (and to run)\n               <p class= \"mt-2\" >\n                 <a href= (format nil \"/blame/~a/to/~a\" (oid run) (oid to)) >View commits between these commits</a>\n               </p>)\n          </div>\n          <div class=\"modal-footer\">\n            <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n          </div>\n        </div>\n      </div>\n    </div>))\n\n(defun group-matches-p (group search)\n  (or\n   (str:emptyp search)\n   (str:contains? search (diff-report:group-title group) :ignore-case t)))\n\n(defun report-result (run changes-groups added-groups deleted-groups\n                      &key\n                        (type (hunchentoot:parameter \"type\"))\n                        (script-name (error \"must provide :script-name\")))\n\n  (let ((search (hunchentoot:parameter \"search\")))\n    (cond\n      ((string-equal \"added\" type)\n       (render-single-group-list added-groups :search search :script-name script-name))\n      ((string-equal \"deleted\" type)\n       (render-single-group-list deleted-groups :search search :script-name script-name))\n      (t\n       <div class= \"\">\n           ,(paginated\n             (lambda (group index)\n               (render-change-group group run script-name  :search search\n                                    :index index))\n             :num 10\n             :pass-index-p t\n             :filter (lambda (group)\n                       (group-matches-p group search))\n             :items changes-groups\n             :infinite-scroll t\n             :empty-view (no-screenshots))\n       </div>))))\n\n(defun render-single-group-list (groups &key search (script-name (error \"must provide script-name\")))\n  (let* ((filter (lambda (group)\n                   (group-matches-p group search)))\n         (screenshots-viewer (make-instance 'screenshots-viewer\n                                            :screenshots groups\n                                            :filter filter\n                                            :mapper (lambda (group)\n                                                      (actual-item (car (diff-report:group-items group)))))))\n    <div>\n      ,(render-modal screenshots-viewer)\n\n      ,(paginated\n        (lambda (group i)\n          <div class= \"col-md-6\">\n            <div class= \"card mb-3\">\n              ,(maybe-tabulate\n                (loop for group-item in (diff-report:group-items group)\n                      for screenshot = (actual-item group-item)\n                      collect\n                      (make-instance\n                       'tab\n                       :title (get-tab-title screenshot group)\n                       :content\n                       <div>\n                         <div class= \"screenshot-header\" >\n                           <ul class= \"screenshot-options-menu\" >\n                             <li>\n                               <a href= (format nil \"~a/image/~a\" script-name (store-object-id (screenshot-key screenshot))) >Permalink</a>\n                              </li>\n                             <li>\n                               <a href= (image-public-url (screenshot-image screenshot) :originalp t)\n                                  >Download Original</a>\n                             </li>\n\n                             <figma-drop-down run= (diff-report-run (group-diff-report group))\n                                              screenshot=screenshot\n                                              script-name=script-name />\n                             \n                           </ul>\n                         </div>\n\n                         <a href= \"#\"\n                            class= \"screenshot-run-image\"\n                            data-image-number=i\n                            data-target= (format nil \"#~a\" (modal-id screenshots-viewer)) >\n\n                           <screenshot-box  screenshot=screenshot title= (diff-report:group-title group) />\n                         </a>\n                       </div>))\n                :header\n                <span>\n                  <h4 class= \"screenshot-title\" >\n                    ,(when (group-renamed-p group)\n                       <span class= \"badge bg-warning\">Renamed</span>)\n                    <span class= \"index\">,(1+ i)</span>\n                      ,(highlight-search-term search (diff-report:group-title group))\n                  </h4>\n\n                </span>)\n            </div>\n          </div>)\n        :num 12\n        :filter filter\n        :infinite-scroll t\n        :items groups\n        :pass-index-p t\n        :empty-view (no-screenshots))\n    </div>))\n\n(Deftag screenshot-box (&key screenshot title\n                        (image nil))\n  (let ((dimensions (ignore-errors (image-dimensions\n                                    (or image\n                                        (screenshot-image screenshot))))))\n    <div class= \"mt-1\" >\n      <picture-with-img\n        class= \"mt-2\"\n        image= (or image\n                (screenshot-image screenshot))\n        dimensions=dimensions\n        alt=title />\n    </div>))\n\n(defun no-screenshots ()\n  <div class= \"text-muted text-center\">\n    No changes match filters\n  </div>)\n\n\n(defmethod render-single-change-permalink (diff-report key-id report-link &key run #| todo: only channel should be required |#)\n  \"Renders a single change from the DIFF-REPORT. Does not render the app-template, so make sure to wrap it. \"\n  (or\n   (%find-single-change-row (diff-report:diff-report-changes diff-report) key-id report-link :run run)\n   (%find-single-added-or-removed (diff-report:diff-report-added diff-report) key-id report-link\n                                  :diff-report diff-report\n                                  :why \"added\")\n   (%find-single-added-or-removed (diff-report:diff-report-deleted diff-report) key-id report-link\n                                  :diff-report diff-report\n                                  :why \"deleted\")   \n   (progn\n     (setf (hunchentoot:return-code*) 404)\n     (hunchentoot:abort-request-handler))))\n\n(deftag single-change-header (children &key report-link)\n  <div class= \"page-title-box mb-3\">\n    <div class= \"mb-2\" ><a href= report-link >Back to report</a></div>\n    <h4 class= \"page-title\" >,@ (progn children)</h4>\n  </div>)\n\n(defun %find-single-added-or-removed (list key-id report-link\n                                      &key diff-report\n                                        why )\n  \"Finds the screenshot associated-with screenshot-key KEY-ID in a LIST of screenshots.\n\nWHY is either \\\"added\\\" or \\\"deleted\\\", and just describes why we're showing this \"\n  (loop for screenshot in list\n        for key = (screenshot-key screenshot)\n        if (eql key-id (store-object-id key))\n           return\n        <markup:merge-tag >\n          <single-change-header report-link=report-link >\n            Newly ,(progn why) screenshot\n          </single-change-header>\n          ,(render-single-group-list\n            (list\n             (make-instance 'diff-report::added-group\n                            :diff-report diff-report\n                            :title (screenshot-name key)\n                            :items (list\n                                    (make-instance 'diff-report:group-item\n                                                   :subtitle nil\n                                                   :actual-item screenshot))))\n            :script-name report-link)\n        </markup:merge-tag>))\n\n(defun %find-single-change-row (changes key-id report-link &key run)\n  (loop for change in changes\n        for index from 0\n        for key = (screenshot-key (diff-report:before change))\n        if (eql\n            key-id\n            (bknr.datastore:store-object-id key))\n          return\n        <markup:merge-tag>\n          <single-change-header report-link=report-link >\n            Change for ,(screenshot-name key) in report\n          </single-change-header>\n          ,(render-change-group\n          (make-instance 'diff-report:group\n                         :title (screenshot-name key)\n                         :items (list\n                                 (make-instance 'diff-report:group-item\n                                                :subtitle nil\n                                                :actual-item change)))\n          run\n          report-link\n          :index index)\n        </markup:merge-tag>))\n\n(defhandler (compare-single-image :uri \"/runs/:run/compare/:to/image/:key-id\")\n    (run to key-id)\n  (with-runs-for-comparison (run to :run-id run :to-id to)\n    <app-template body-class= \"dashboard\" >\n      ,(with-async-diff-report (diff-report :run run :to to)\n         (render-single-change-permalink\n          diff-report\n          (parse-integer key-id)\n          (format nil \"/runs/~a/compare/~a\" (oid run) (oid to))\n          :run run))\n    </app-template>))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/dashboard.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/dashboard\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/template\n        #:screenshotbot/user-api\n        #:core/ui/taskie\n        #:nibble\n        #:screenshotbot/ui)\n  (:import-from #:screenshotbot/server\n                #:with-login\n                #:defhandler)\n  (:import-from #:markup #:deftag)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-name\n                #:channel-company)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-channel\n                #:recorder-run-commit\n                #:recorder-run-screenshots\n                #:activep)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:company-channels\n                #:company-runs\n                #:user-full-name\n                #:user-image-url\n                #:pull-request-url\n                #:can-view!)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-link)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:import-from #:util/timeago\n                #:timeago)\n  (:import-from #:screenshotbot/model/report\n                #:report-title\n                #:report-channel\n                #:report-run\n                #:acceptable-report\n                #:acceptable-state\n                #:acceptable-reviewer\n                #:acceptable-history\n                #:acceptable-history-item-state\n                #:acceptable-history-item-user\n                #:acceptable-history-item-ts\n                #:base-acceptable\n                #:acceptable)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:export #:dashboard-page))\n(in-package :screenshotbot/dashboard/dashboard)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun get-recent-activity (company &optional (limit 10))\n  \"Get recent Accept/Reject activity from base-acceptable objects for this company\"\n  (let ((company-channels (company-channels company))\n        (activity-items '()))\n    ;; Get all acceptables for company channels and extract history items\n    (loop for acceptable in (class-instances 'base-acceptable)\n          for report = (ignore-errors (acceptable-report acceptable))\n          for channel = (ignore-errors (report-channel report))\n          when (and channel (member channel company-channels))\n            do (handler-case\n                   (progn\n                     ;; Check authorization for all objects before adding to activity\n                     (can-view! acceptable)\n                     (can-view! report)\n                     (can-view! channel)\n                     (loop for history-item in (acceptable-history acceptable)\n                           for state = (acceptable-history-item-state history-item)\n                           for user = (acceptable-history-item-user history-item) \n                           for timestamp = (acceptable-history-item-ts history-item)\n                           when (member state '(:accepted :rejected))\n                             do (push (list :acceptable acceptable\n                                            :action state\n                                            :user user\n                                            :timestamp timestamp\n                                            :report report\n                                            :channel channel)\n                                      activity-items)))\n                 (error ())))\n    ;; Sort by timestamp (newest first) and limit\n    (subseq (sort activity-items (lambda (a b) (> (getf a :timestamp) (getf b :timestamp))))\n            0 (min limit (length activity-items)))))\n\n(defun calculate-dashboard-metrics (company)\n  \"Calculate key metrics for the dashboard overview cards\"\n  (can-view! company)\n  (let* ((all-runs (company-runs company))\n         (all-channels (company-channels company))\n         ;; Filter runs and channels to only those the user can view\n         (runs (remove-if-not (lambda (run)\n                                (handler-case\n                                    (progn (can-view! run) t)\n                                  (error () nil)))\n                              all-runs))\n         (channels (remove-if-not (lambda (channel)\n                                    (handler-case\n                                        (progn (can-view! channel) t)\n                                      (error () nil)))\n                                  all-channels)))\n    (let ((total-runs (length runs))\n          (active-runs (count-if #'activep runs))\n          (recent-activity (get-recent-activity company))\n          (failed-runs\n            10\n            #+nil\n            (count-if (lambda (run)\n                                   (and (< (length (recorder-run-screenshots run)) 1)\n                                        (< (- (get-universal-time) (created-at run)) (* 24 3600))))\n                                 runs)))\n      (list :total-runs total-runs\n            :active-runs active-runs\n            :failed-runs failed-runs\n            :success-rate (if (> total-runs 0)\n                             (float (* 100 (/ (- total-runs failed-runs) total-runs)))\n                             100.0)\n            :recent-activity recent-activity\n            :channels channels))))\n\n(deftag dashboard-overview-cards (&key metrics)\n  <div class=\"row mb-4\">\n    <div class=\"col-md-3\">\n      <div class=\"card text-center\">\n        <div class=\"card-body\">\n          <mdi name=\"camera\" class=\"text-primary mb-2\" style=\"font-size: 2rem;\" />\n          <h4 class=\"card-title\">,(getf metrics :total-runs)</h4>\n          <p class=\"card-text text-muted\">Total Runs</p>\n        </div>\n      </div>\n    </div>\n    <div class=\"col-md-3\">\n      <div class=\"card text-center\">\n        <div class=\"card-body\">\n          <mdi name=\"flash_on\" class=\"text-success mb-2\" style=\"font-size: 2rem;\" />\n          <h4 class=\"card-title\">,(getf metrics :active-runs)</h4>\n          <p class=\"card-text text-muted\">Active Tests</p>\n        </div>\n      </div>\n    </div>\n    <div class=\"col-md-3\">\n      <div class=\"card text-center\">\n        <div class=\"card-body\">\n          <mdi name=\"error\" class=\"text-danger mb-2\" style=\"font-size: 2rem;\" />\n          <h4 class=\"card-title\">,(getf metrics :failed-runs)</h4>\n          <p class=\"card-text text-muted\">Failed Tests</p>\n        </div>\n      </div>\n    </div>\n    <div class=\"col-md-3\">\n      <div class=\"card text-center\">\n        <div class=\"card-body\">\n          <mdi name=\"trending_up\" class=\"text-info mb-2\" style=\"font-size: 2rem;\" />\n          <h4 class=\"card-title\">,(format nil \"~,1f%\" (getf metrics :success-rate))</h4>\n          <p class=\"card-text text-muted\">Success Rate</p>\n        </div>\n      </div>\n    </div>\n  </div>)\n\n(defun get-status-icon (run)\n  \"Get appropriate status icon for a run\"\n  (cond\n    ((activep run) \"check_circle\")\n    ((< (length (recorder-run-screenshots run)) 1) \"error\")\n    (t \"schedule\")))\n\n(defun get-status-class (run)\n  \"Get appropriate CSS class for run status\"\n  (cond\n    ((activep run) \"text-success\")\n    ((< (length (recorder-run-screenshots run)) 1) \"text-danger\")\n    (t \"text-warning\")))\n\n(defun get-activity-icon (action)\n  \"Get appropriate icon for accept/reject action\"\n  (case action\n    (:accepted \"check_circle\")\n    (:rejected \"cancel\")\n    (t \"help\")))\n\n(defun get-activity-class (action)\n  \"Get appropriate CSS class for accept/reject action\"\n  (case action\n    (:accepted \"text-success\")\n    (:rejected \"text-danger\")\n    (t \"text-muted\")))\n\n(defun get-activity-text (action)\n  \"Get human-readable text for action\"\n  (case action\n    (:accepted \"accepted\")\n    (:rejected \"rejected\")\n    (t \"unknown\")))\n\n(deftag activity-feed (&key recent-activity)\n  <div class=\"card mb-4 recent-activity-feed\">\n    <div class=\"card-header d-flex justify-content-between align-items-center\">\n      <h5 class=\"mb-0\">\n        <mdi name=\"timeline\" class=\"text-primary me-2\" />\n        Activity Feed\n      </h5>\n      <a href=\"/runs\" class=\"btn btn-outline-primary btn-sm\">View All Runs</a>\n    </div>\n    <div class=\"card-body p-0\">\n      ,(if recent-activity\n         <div class=\"list-group list-group-flush\">\n           ,@(loop for activity-item in recent-activity\n                   for action = (getf activity-item :action)\n                   for user = (getf activity-item :user)\n                   for timestamp = (getf activity-item :timestamp)\n                   for channel = (getf activity-item :channel)\n                   for report = (getf activity-item :report)\n                   collect\n                   (let ((activity-class (case action\n                                         (:accepted \"activity-accepted\")\n                                         (:rejected \"activity-rejected\")\n                                         (t \"\"))))\n                     <div class=(format nil \"list-group-item d-flex justify-content-between align-items-center py-3 ~a\" activity-class)>\n                     <div class=\"d-flex align-items-center\">\n                       ,(if user\n                          <div class=\"position-relative me-3 activity-avatar\">\n                            <img class=\"rounded-circle\" src=(user-image-url user) width=\"32\" height=\"32\" alt=\"Profile\" />\n                            <span class=(format nil \"activity-badge ~a\" (get-activity-class action))>\n                              <mdi name=(get-activity-icon action) />\n                            </span>\n                          </div>\n                          <mdi name=(get-activity-icon action) class=(format nil \"me-3 ~a\" (get-activity-class action)) />)\n                       <div>\n                         <h6 class=\"mb-1\">\n                           ,(if user\n                              (user-full-name user)\n                              \"Unknown user\")\n                           <span class=\"fw-normal\">,(get-activity-text action)</span>\n                           <span class=\"fw-normal\">screenshots for</span>\n                           <strong>,(channel-name channel)</strong>\n                         </h6>\n                         <small class=\"text-muted\">\n                           ,(let ((run (report-run report)))\n                              (if (and run (pull-request-url run))\n                                  <a href=(pull-request-url run) target=\"_blank\" class=\"text-decoration-none\">PR Review</a>\n                                  <span>PR Review</span>))\n                           ,(when (report-run report)\n                              <span>• ,(recorder-run-commit (report-run report))</span>)\n                         </small>\n                       </div>\n                     </div>\n                     <div class=\"d-flex align-items-center\">\n                       <small class=\"text-muted me-3\"><timeago timestamp=timestamp /></small>\n                       <a href=(format nil \"/report/~a\" (oid report)) class=\"btn btn-outline-primary btn-sm\">View</a>\n                     </div>\n                   </div>))\n         </div>\n         <div class=\"text-center py-4 text-muted\">\n           <mdi name=\"timeline\" style=\"font-size: 3rem;\" class=\"mb-2\" />\n           <p>No recent activity</p>\n           <small>Accept or reject screenshot changes to see activity here</small>\n         </div>)\n    </div>\n  </div>)\n\n(defun calculate-channel-health (channel)\n  \"Calculate health percentage for a channel based on recent runs\"\n  100)\n\n(defun get-health-color (percentage)\n  \"Get Bootstrap color class based on health percentage\"\n  (cond\n    ((>= percentage 80) \"success\")\n    ((>= percentage 60) \"warning\")\n    (t \"danger\")))\n\n(deftag progress-bar (&key percentage)\n  (let ((color (get-health-color percentage)))\n    <div class=\"progress\" style=\"height: 8px;\">\n      <div class=\"progress-bar\" role=\"progressbar\" \n           style=(format nil \"width: ~a%; background-color: var(--bs-~a);\" percentage color)\n           aria-valuenow=percentage aria-valuemin=\"0\" aria-valuemax=\"100\">\n      </div>\n    </div>))\n\n(deftag channel-status-grid (&key channels)\n  <div class=\"row channel-status-grid\">\n    <div class=\"col-md-8\">\n      <div class=\"card channel-grid-card\">\n        <div class=\"card-header\">\n          <h5 class=\"mb-0\">\n            <mdi name=\"list\" class=\"me-2\" />\n            Active Channels\n          </h5>\n        </div>\n        <div class=\"card-body\">\n          ,(if channels\n             <div>\n               ,@(loop for channel in (subseq channels 0 (min 6 (length channels)))\n                       for health = (calculate-channel-health channel)\n                       collect\n                       <div class=\"d-flex justify-content-between align-items-center mb-3\">\n                         <div class=\"d-flex align-items-center\">\n                           <mdi name=\"book\" class=\"me-3 text-primary\" />\n                           <div>\n                             <h6 class=\"mb-1\">,(channel-name channel)</h6>\n                             <progress-bar percentage=health />\n                           </div>\n                         </div>\n                         <span class=\"badge bg-light text-dark\">,(format nil \"~a%\" (round health))</span>\n                       </div>)\n               <div class=\"mt-3\">\n                 <a href=\"/channels/new\" class=\"btn btn-outline-primary btn-sm\">\n                   <mdi name=\"add\" class=\"me-1\" />\n                   Add Channel\n                 </a>\n               </div>\n             </div>\n             <div class=\"text-center py-4 text-muted\">\n               <mdi name=\"book\" style=\"font-size: 3rem;\" class=\"mb-2\" />\n               <p>No channels found</p>\n               <a href=\"/channels/new\" class=\"btn btn-primary\">Create your first channel</a>\n             </div>)\n        </div>\n      </div>\n    </div>\n    <div class=\"col-md-4\">\n      <div class=\"card channel-grid-card attention-required\">\n        <div class=\"card-header\">\n          <h5 class=\"mb-0\">\n            <mdi name=\"warning\" class=\"me-2 text-warning\" />\n            Attention Required\n          </h5>\n        </div>\n        <div class=\"card-body\">\n          ,(let ((problem-channels (remove-if (lambda (channel)\n                                               (>= (calculate-channel-health channel) 80))\n                                             channels)))\n             (if problem-channels\n               <div>\n                 ,@(loop for channel in (subseq problem-channels 0 (min 3 (length problem-channels)))\n                         for health = (calculate-channel-health channel)\n                         collect\n                         <div class=\"alert alert-warning mb-2 py-2\">\n                           <div class=\"d-flex align-items-center\">\n                             <mdi name=\"error_outline\" class=\"me-2\" />\n                             <div>\n                               <h6 class=\"mb-1\">,(channel-name channel)</h6>\n                               <small>Health: ,(format nil \"~a%\" (round health))</small>\n                             </div>\n                           </div>\n                         </div>)\n               </div>\n               <div class=\"text-center text-muted\">\n                 <mdi name=\"check_circle\" class=\"text-success mb-2\" style=\"font-size: 2rem;\" />\n                 <p>All channels healthy!</p>\n               </div>))\n        </div>\n      </div>\n    </div>\n  </div>)\n\n(defun dashboard-view ()\n  \"Main dashboard view\"\n  (assert (gk:check :new-dashboard-view (auth:current-company)))\n  (let* ((company (current-company))\n         (metrics (calculate-dashboard-metrics company)))\n    <app-template>\n      <div class=\"main-content\">\n        <div class=\"container-fluid mt-4 dashboard-overview\">\n          <div class=\"d-flex justify-content-between align-items-center mb-4\">\n            <h2>\n              <mdi name=\"dashboard\" class=\"me-2\" />\n              Dashboard Overview\n            </h2>\n            <a href=\"/settings/general\" class=\"btn btn-outline-secondary\">\n              <mdi name=\"settings\" class=\"me-1\" />\n              Settings\n            </a>\n          </div>\n          \n          <dashboard-overview-cards metrics=metrics />\n          \n          <activity-feed recent-activity=(getf metrics :recent-activity) />\n          \n          <channel-status-grid channels=(getf metrics :channels) />\n        </div>\n      </div>\n    </app-template>))\n\n(defhandler (dashboard-page :uri \"/dashboard\") ()\n  (with-login ()\n    (dashboard-view)))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/ensure-company.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/ensure-company\n  (:use #:cl)\n  (:import-from #:screenshotbot/user-api\n                #:company-name\n                #:user-full-name\n                #:unaccepted-invites\n                #:current-company\n                #:current-user\n                #:adminp)\n  (:import-from #:screenshotbot/installation\n                #:call-with-ensure-user-prepared\n                #:one-owned-company-per-user)\n  (:import-from #:screenshotbot/template\n                #:dashboard-template\n                #:app-template)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/model/company\n                #:redirect-url\n                #:sub-company\n                #:company\n                #:company-with-name)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:screenshotbot/invite\n                #:accept-invite)\n  (:import-from #:screenshotbot/model/invite\n                #:inviter)\n  (:import-from #:screenshotbot/notice-api\n                #:invite-company)\n  (:export\n   #:post-new-company))\n(in-package :screenshotbot/dashboard/ensure-company)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod call-with-ensure-user-prepared ((installation one-owned-company-per-user)\n                                           user\n                                           fn)\n  ;; If the user is not\n  (cond\n    (user\n     (ensure-company-or-invite\n      user\n      fn\n      (unaccepted-invites user)))\n    (t\n     (funcall fn))))\n\n(defun ensure-company-or-invite (user fn invites)\n  (let* ((companies (roles:companies-for-user user))\n         (redirect-url (some #'redirect-url companies)))\n    (cond\n      (redirect-url\n       (%redirect-message redirect-url))\n      (companies\n       (funcall fn))\n      (invites\n       (let ((invite (car invites)))\n         (%accept-invite user invite\n                         :accept (nibble ()\n                                   (accept-invite invite)\n                                   (hex:safe-redirect\n                                    (nibble ()\n                                      (funcall fn))))\n                         :reject (nibble ()\n                                   (ensure-company-or-invite\n                                    user fn (cdr invites))))))\n      (t\n       (%new-company :redirect (nibble () (funcall fn))\n                     :user user)))))\n\n(defun %redirect-message (url)\n  <center-box>\n    <p>This organization has been migrated to <a href=url >,(progn url)</a>. Please click that link and log in again.</p>\n  </center-box>)\n\n(markup:deftag center-box (children)\n  <dashboard-template left-nav-bar=nil >\n    <div class= \"container body-vh-100\" style= \"max-width: 40em\" >\n      <div class= \"row h-100\">\n        <div class= \"my-auto\" style= \"padding-bottom: 20vh;\" >\n          ,@children\n        </div>\n      </div>\n    </div>\n  </dashboard-template>)\n\n(defun %accept-invite (user invite &key accept reject)\n  <center-box>\n    <h4>You have an invitation</h4>\n\n    <p>You have a pending invite from ,(user-full-name (inviter invite)) to join\n      <b>,(company-name (invite-company invite))</b> </p>\n\n    <form action=accept method= \"post\" class= \"text-center\" >\n      <div class= \"form-group mt-3\">\n        <input type= \"submit\" value= \"Accept and continue\" class= \"btn btn-primary form-control\" />\n      </div>\n      <div class= \"mt-3\">\n        <a href= reject class= \"mt-3\" >Decline for now</a>\n      </div>\n\n    </form>\n  </center-box>)\n\n(defun %new-company (&key redirect\n                       user)\n  (let ((post (nibble (name)\n                (post-new-company name t\n                                  :user user\n                                  :form (lambda ()\n                                          (%new-company :redirect redirect\n                                                        :user user))\n                                  :redirect redirect))))\n  <center-box>\n    <h4 class= \"mb-3\" >\n      Create Organization\n    </h4>\n    <p class= \"mb-3\" >Before we can begin, let's create an organization for your\n      projects. Organizations also let you collaborate with other users.</p>\n\n    <p class= \"text-muted\">You will be able to change this name later.</p>\n\n    <form action=post method= \"post\" >\n      <div class= \"form-group pt-3\">\n\n        <label for= \"name\" class= \"form-label text-muted\">\n          Organization name\n        </label>\n        <input type= \"text\" placeholder= \"My company, Inc.\"\n               name= \"name\"\n               id= \"name\"\n               class= \"form-control\" />\n      </div>\n\n      <div class= \"form-group mt-3\">\n        <input type= \"submit\" class= \"btn btn-primary form-control\"\n               value= \"Create and continue\" />\n      </div>\n    </form>\n  </center-box>))\n\n(defun post-new-company (name i-agree\n                         &key form\n                           parent\n                           (user (current-user))\n                           redirect)\n  \"This is also called by /organization/new\"\n  (let ((errors))\n    (flet ((check (name test message)\n             (unless test\n               (push (cons name message) errors))))\n      (check :name (and name (>= (length name) 6))\n             \"Organization name must be at least 6 letters long\")\n      (check :i-agree i-agree\n             \"You must accept that you understood how billing works\")\n      (check :name\n             (not (company-with-name  name))\n             \"There's already a company with that name. If you think\n              you should be the appropriate admin for this company,\n              please contact us.\")\n      (check :name\n             (or (adminp user)\n                 parent\n                 (< (length (roles:companies-for-user user)) 4))\n             \"You have reached the limit of organizations you can\n             create. We have this limit just to prevent abuse. Please\n             contact us to create this organization for you.\"))\n    (cond\n      (errors\n       (with-form-errors (:name name\n                          :errors errors\n                          :was-validated t)\n         (funcall form)))\n      (t\n       (prepare-company user name :parent parent)\n       (hex:safe-redirect redirect)))))\n\n(defun prepare-company (user name &key parent)\n  (let* ((company (apply #'make-instance (cond\n                                           (parent 'sub-company)\n                                           (t 'company))\n                         :name name\n                         :admins (list user)\n                         :owner user\n                         (when parent\n                           (list :parent parent)))))\n    (setf (current-company) company)\n    (push-event :company.new)\n    (populate-company company)))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/explain.lisp",
    "content": "(defpackage :screenshotbot/dashboard/explain\n  (:use #:cl)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:screenshotbot/template\n                #:mdi)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:explain))\n(in-package :screenshotbot/dashboard/explain)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag explain (explanation &key title)\n  <sup><a data-bs-toggle= \"popover\" title= title\n          class= \"explain-icon\"\n           data-bs-trigger= \"focus\"\n           data-bs-html= \"true\"\n           data-bs-content= (markup:write-html <div>,@ (progn explanation)</div>) href= \"#\" ><mdi name= \"help\" /></a></sup>)\n"
  },
  {
    "path": "src/screenshotbot/dashboard/flaky-screenshots.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/flaky-screenshots\n  (:use #:cl)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:runs-for-channel)\n  (:import-from #:local-time\n                #:timestamp-)\n  (:import-from #:screenshotbot/user-api\n                #:screenshot-name\n                #:recorder-run-screenshots\n                #:created-at)\n  (:import-from #:screenshotbot/screenshot-api\n                #:screenshot-image)\n  (:import-from #:alexandria\n                #:when-let\n                #:hash-table-keys\n                #:hash-table-alist)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:screenshot-box)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:screenshotbot/diff-report\n                #:after\n                #:diff-report-changes\n                #:make-diff-report)\n  (:import-from #:screenshotbot/report-api\n                #:report-previous-run\n                #:report-run)\n  (:import-from #:screenshotbot/model/report\n                #:reports-for-run)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:nibble\n                #:nibble)\n  (:export\n   #:view-flaky-screenshots))\n(in-package :screenshotbot/dashboard/flaky-screenshots)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(defun view-noisy-screenshots (channel)\n  \"A view of potential flaky screenshots in the channel.\"\n  (auth:can-view! channel)\n  (let ((map (report-count-map (runs-from-30-days channel))))\n    <app-template>\n      <div class= \"main-content mt-3\">\n        <h3>Noisy screenshots over the last 7 days</h3>\n\n        <p>See also: <a href= (nibble () (view-flaky-screenshots channel)) >Screenshots with animations</a></p>\n        <div class= \"alert alert-info\">\n          This is useful for debugging flaky screenshots. For each screenshot name, we track the\n          number of times it showed up as changed on a PR report or main-branch report. \n        </div>\n        <div>\n          ,(paginated\n            (lambda (pair)\n              (destructuring-bind (name . samples) pair\n                (destructuring-bind (image sample-report) (car samples)\n                    <div>\n                      ,(progn name) (Occurrences: ,(length samples), <a href= (hex:make-url \"/report/:id\" :id (oid sample-report)) >Sample Report</a>)\n                      <screenshot-box image= image />\n                    </div>)))\n            :items map)\n\n        </div>\n      </div>\n    </app-template>))\n\n(defun view-flaky-screenshots (channel)\n  \"A view of potential flaky screenshots in the channel.\"\n  (auth:can-view! channel)\n  (let ((map (screenshot-variant-map (runs-from-30-days channel))))\n    <app-template>\n      <div class= \"main-content mt-3\">\n        <h3>Screenshots with animations </h3>\n\n        <p>See also: <a href= (nibble () (view-noisy-screenshots channel)) >Noisy screenshots</a></p>\n\n        <div class= \"alert alert-info\">\n          This is useful for debugging flaky screenshots. For each screenshot name, we\n          list the number of unique screenshots we have seen for it over the last 30 days.\n          <b>Masks are not factored into this count</b>, i.e. if you have a screenshot with mask\n          each variant will get counted here even if changes were in a masked area.\n        </div>\n        <div>\n          ,(paginated\n            (lambda (pair)\n              (destructuring-bind (name . screenshots) pair\n                <div>\n                  ,(progn name) (variants: ,(length screenshots))\n                  <screenshot-box image= (car screenshots) />\n                </div>))\n            :items map)\n\n        </div>\n      </div>\n    </app-template>))\n\n(defun report-count-map (runs)\n  (let ((map (make-hash-table :test #'equal)))\n    (dolist (run runs)\n      (when-let ((report (car (reports-for-run run))))\n        (let ((diff-report (make-diff-report\n                            (report-run report)\n                            (report-previous-run report))))\n          (loop for change in (diff-report-changes diff-report)\n                do\n                   (push (list (screenshot-image (after change)) report)\n                         (gethash (screenshot-name (after change))\n                                  map))))))\n    (sort\n     (hash-table-alist map)\n     #'>\n     :key (lambda (pair)\n            (length (cdr pair))))))\n\n(defun screenshot-variant-map (runs)\n  (let ((map (make-hash-table :test #'equal)))\n    (dolist (run runs)\n      (loop for screenshot in (recorder-run-screenshots run) do\n        (let ((name (screenshot-name screenshot)))\n          (unless (gethash name map)\n            (setf (gethash name map) (make-hash-table :test #'equal)))\n          (setf (gethash (screenshot-image screenshot)\n                         (gethash name map))\n                t))))\n    (remove-if\n     (lambda (pair)\n       (= 1 (length (cdr pair))))\n     (sort\n      (loop for key being the hash-keys of map\n            using (hash-value v)\n            collect (cons key (hash-table-keys v)))\n      #'>\n      :key (lambda (pair)\n             (length (cdr pair)))))))\n\n\n\n(defun runs-from-30-days (channel)\n  (let ((runs (runs-for-channel channel))\n        (cutoff (timestamp- (local-time:now) 7 :day)))\n    (loop for rank from (1- (fset:size runs)) above -1\n          for run = (fset:at-rank runs rank)\n          if (local-time:timestamp<\n              (created-at run)\n              cutoff)\n                     do (return result)\n          else \n            collect run into result)))\n\n"
  },
  {
    "path": "src/screenshotbot/dashboard/history.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/history\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/user-api\n        #:screenshotbot/report-api\n        #:screenshotbot/cdn\n        #:screenshotbot/template\n        #:screenshotbot/screenshot-api)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:modal-id\n                #:render-modal\n                #:screenshots-viewer\n                #:commit)\n  (:import-from #:bknr.datastore\n                #:store-object-with-id)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:history-page)\n  (:import-from #:core/ui/taskie\n                #:timeago)\n  (:import-from #:util\n                #:oid)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:comparison-modal)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:screenshotbot/user-api\n                #:screenshot-name)\n  (:import-from #:screenshotbot/model/image\n                #:image=)\n  (:import-from #:screenshotbot/model/image-comparer\n                #:make-image-comparer)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/dashboard/bisect\n                #:bisect-page\n                #:bisect-item)\n  (:import-from #:screenshotbot/login/common\n                #:with-login))\n(in-package :screenshotbot/dashboard/history)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun render-single-screenshot (r s &key previous-screenshot\n                                       channel\n                                       bisect-options\n                                       iterator #| the *current* iterator |#)\n  (let* ((screenshots-viewer (make-instance 'screenshots-viewer\n                                            :navigationp nil\n                                            :screenshots (list s)))\n         (image-comparer (make-image-comparer r))\n         (toggle-id (format nil \"compare-~a\" (random 1000000)))\n         (name-change-p (and\n                         previous-screenshot\n                         (not (string= (screenshot-name s)\n                                       (screenshot-name previous-screenshot)))))\n         (image-change-p (and\n                          previous-screenshot\n                          (not (image=\n                                image-comparer\n                                (screenshot-image s)\n                                (screenshot-image previous-screenshot)\n                                nil)))))\n   (cond\n     (s\n      <div class= \"mb-4\" >\n      <h6 class= \"mb-0\" >,(screenshot-name s)\n      ,(when name-change-p\n         <span class= \"badge bg-warning\">\n           Renamed or copied\n         </span>)\n      </h6>\n\n      ,(when (and previous-screenshot image-change-p)\n         (comparison-modal :toggle-id toggle-id\n                           :before previous-screenshot\n                           :after s))\n      <div class= \"screenshot-header\">\n      <ul class= \"screenshot-options-menu\">\n      <li>\n      ,(cond\n         ((recorder-run-commit r)\n          <span>First seen in <commit repo= (channel-repo channel)\n          hash= (recorder-run-commit r) />, ,(timeago :timestamp (created-at r)) </span>)\n         (t\n          <span>First seen <a href= (hex:make-url \"/runs/:id\" :id (oid r))>,(timeago :timestamp (created-at r))</a></span>))\n      </li>\n\n      ,(when (and previous-screenshot image-change-p)\n         <li>\n         <a href= \"#\" data-bs-toggle= \"modal\" data-bs-target = (format nil \"#~a\" toggle-id) >  Compare</a>\n         </li>)\n\n      ,(funcall bisect-options\n                r s iterator)\n      </ul>\n      </div>\n      ,(render-modal screenshots-viewer)\n      <a href= (image-public-url (screenshot-image s) :size :full-page)\n         class= \"screenshot-run-image\"\n         title= \"Full screenshot\"\n         data-image-number=0\n         data-target= (format nil \"#~a\" (modal-id screenshots-viewer)) >\n      <img class= \"screenshot-image\" src=(image-public-url (screenshot-image s) :size :small) />\n      </a>\n\n      </div>)\n     (t\n      <div>\n      <h4>Deleted</h4>\n      </div>))))\n\n(markup:deftag render-history (&key screenshot-name channel branch)\n  (render-history-from-iterator\n   (get-screenshot-history channel screenshot-name :iterator t :branch branch)\n   screenshot-name\n   channel\n   :bisect-options (lambda (run screenshot iterator)\n                     (declare (ignore run))\n                     (let ((start-bisect (nibble (:name :bisect-step-one)\n                                           (start-bisect-from iterator\n                                                              :screenshot-name screenshot-name\n                                                              :bad-screenshot screenshot\n                                                              :channel channel))))\n                       <li>\n                         <a href= start-bisect >Start bisect</a>\n                       </li>))))\n\n(defun start-bisect-from (iterator &key screenshot-name channel\n                                     bad-screenshot)\n  (app-template\n   (render-history-from-iterator\n    iterator\n    screenshot-name\n    channel\n    :alert\n    <div class= \"alert alert-info mt-2\">\n      Select a <em>good</em> screenshot as a starting point for bisect\n    </div>\n    :bisect-options (lambda (run screenshot good-iterator)\n                      (declare (ignore good-iterator))\n                      (let ((start (nibble (:name :bisect-step-two)\n                                     (let-the-bisect-begin iterator\n                                                           :end-run run))))\n                        (cond\n                          ;; Is this check good enough? What happens\n                          ;; when the same screenshot appears multiple\n                          ;; times in the history?\n                          ((fset:equal? screenshot bad-screenshot)\n                           <li>\n                             <span class= \"text-danger\">Bisect ending point (<em>bad</em> screenshot) </span>\n                           </li>)\n                          (t\n                           <li>\n                             <a href=start >Mark <em>good</em> for bisect</a>\n                           </li>)))))))\n\n(defun let-the-bisect-begin (iterator &key end-run)\n  (labels ((build-bisect-list (iterator end-run &key result)\n             (multiple-value-bind (args next-iter) (funcall iterator)\n               (assert args)\n               (destructuring-bind (screenshot run prev-run) args\n                 (declare (ignore prev-run))\n                 (let ((new-result (list*\n                                    (make-instance 'bisect-item\n                                                   :screenshot screenshot\n                                                   :run run)\n                                    result)))\n                   (cond\n                     ((eql run end-run)\n                      ;; we're done\n                      (reverse\n                       new-result))\n                     (t\n                      (build-bisect-list next-iter\n                                         end-run\n                                         :result new-result))))))))\n    (let ((bisect-list (build-bisect-list iterator end-run)))\n      (bisect-page bisect-list))))\n\n(defun render-history-from-iterator (iterator screenshot-name channel\n                                     &key bisect-options\n                                       alert)\n  <div class= \"content-page bg-light-lighten\">\n    <div class= \"content history-page\" >\n      <h3 class= \"mt-3 mb-0\" >,(progn screenshot-name)</h3>\n      ,(cond\n         (alert\n          alert)\n         (t\n          <h6 class= \"text-muted mb-3\">Promotion History</h6>))\n\n      ,(paginated\n        (lambda (args &key iterator)\n          (destructuring-bind (screenshot run previous-screenshot)\n              args\n            <div>\n              ,(render-single-screenshot run screenshot\n                                         :previous-screenshot previous-screenshot\n                                         :iterator iterator\n                                         :channel channel\n                                         :bisect-options bisect-options)\n            </div>))\n\n        :num 12\n        :iterator iterator)\n    </div>\n  </div>)\n\n(defhandler (history-page :uri \"/channel/:channel/history\")\n            (channel screenshot-name branch)\n  (when (equal \"\" branch)\n    (setf branch nil))\n  (with-login ()\n   (let ((channel (store-object-with-id (parse-integer channel))))\n     (can-view! channel)\n     (app-template\n      (render-history\n       :screenshot-name screenshot-name\n       :channel channel\n       :branch branch)))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/home.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/home\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/server))\n(in-package :screenshotbot/dashboard/home)\n\n(defhandler (dashboard :uri \"/dashboard\") ()\n  (hex:safe-redirect \"/runs\"))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/image\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/image)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:lparallel\n                #:delay\n                #:future\n                #:chain\n                #:promise\n                #:force)\n  (:import-from #:hunchentoot\n                #:handle-static-file)\n  (:import-from #:screenshotbot/model/image\n                #:with-invalid-image-handling\n                #:invalid-image\n                #:abstract-image\n                #:no-image-uploaded-yet\n                #:find-image-by-oid\n                #:with-local-image)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:util/hash-lock\n                #:with-hash-lock-held\n                #:hash-lock)\n  (:import-from #:screenshotbot/async\n                #:magick-future)\n  (:import-from #:util/threading\n                #:with-extras\n                #:ignore-and-log-errors)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:screenshotbot-resize\n                #:magick-crop-image\n                #:save-wand-to-file\n                #:magick-write-image\n                #:with-wand\n                #:resize-image)\n  (:import-from #:util/store\n                #:location-for-oid)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/cdn\n                #:make-image-cdn-url)\n  (:import-from #:util/events\n                #:push-event\n                #:with-tracing)\n  (:import-from #:util/throttler\n                #:throttler\n                #:throttle!\n                #:ip-throttler)\n  (:import-from #:encrypt/hmac\n                #:sign-hmac)\n  (:import-from #:util/store/object-id\n                #:oid-array)\n  (:export\n   #:handle-resized-image))\n(in-package :screenshotbot/dashboard/image)\n\n(defvar *image-resize-lock* (make-instance 'hash-lock\n                                           :test 'equal))\n\n(defvar *semaphore* nil)\n\n#+lispworks\n(defun semaphore ()\n  (util:or-setf\n   *semaphore*\n   (mp:make-semaphore :count (serapeum:count-cpus))\n   :thread-safe t))\n\n#-lispworks\n(def-easy-macro with-semaphore (&fn fn)\n  (fn))\n\n#+lispworks\n(def-easy-macro with-semaphore (&fn fn)\n  (let ((sem (semaphore)))\n    (case (mp:semaphore-acquire sem :timeout 60)\n      (nil\n       (error \"could not get semaphore in time\"))\n      (t\n       (unwind-protect\n            (fn)\n         (mp:semaphore-release sem))))))\n\n(defun cache-dir ()\n  (let ((dir (path:catdir util/store:*object-store* \"image-cache/\")))\n    (ensure-directories-exist dir)\n    dir))\n\n(defun %ignore (x)\n  (declare (ignore x)))\n\n(defun pathname-for-resized-image (oid &key type size\n                                         (version :new))\n  (ecase version\n    (:old\n     ;; Removing this code requires *min-store-version* to be >= 6!\n     (make-pathname\n      :type type\n      :defaults (cache-dir)\n      :name (format nil \"~a-~a\" oid size)))\n    (:new\n     (location-for-oid\n      #P\"image-cache/\"\n      (ironclad:hex-string-to-byte-array oid)\n      :suffix size\n      :type type))))\n\n(defparameter *size-map*\n  `((\"small\" . \"300x300\")\n    (\"half-page\" . \"600x600\")\n    (\"full-page\" . \"2000x2000\")\n    ;; For testing only:\n    (\"tiny\" . \"5x5\")))\n\n(defun %build-resized-image (image size-name &key (type :webp))\n  \"Synchronous version. Do not call directly.\"\n  (with-invalid-image-handling ()\n    (let ((size (or\n                 (assoc-value *size-map* size-name :test #'string-equal)\n                 (error \"invalid image size: ~a\" size-name))))\n      (flet ((output-file (type)\n               (pathname-for-resized-image (oid image) :type type :size size))\n             (respond (res)\n               res))\n        (ecase type\n          (:png\n           (let ((webp (%build-resized-image\n                        image size-name\n                        :type :webp)))\n             (let ((png (output-file \"png\")))\n               (unless (uiop:file-exists-p png)\n                 (push-event :live-png-creation\n                             :name (oid image)\n                             :size-name size-name)\n                 (with-wand (wand :file webp)\n                   (uiop:with-staging-pathname (png)\n                     (save-wand-to-file\n                      wand png))))\n               (respond png))))\n          (:webp\n           (let* ((output-file (output-file \"webp\")))\n             (cond\n               ((uiop:file-exists-p output-file)\n                (respond output-file))\n               (t\n                (unless (uiop:file-exists-p output-file)\n                  (with-local-image (input image)\n                    (uiop:with-staging-pathname (output-file)\n                      (resize-image input\n                                    :output output-file\n                                    :size size))))\n                (respond output-file))))))))))\n\n(defun handle-resized-image (image size &key warmup\n                                          type)\n  (handler-case\n      (with-extras ((\"image\" image))\n        (with-hash-lock-held ((list image size type) *image-resize-lock*)\n          (cond\n            (warmup\n             (with-semaphore ()\n               (%build-resized-image image size)))\n            (t\n             (let ((output-file\n                     (with-semaphore ()\n                       (%build-resized-image\n                        image size\n                        :type (cond\n                                ((string= type \"png\")\n                                 :png)\n                                (t\n                                 :webp))))))\n               (handle-static-file\n                output-file\n                (format nil \"image/~a\" (pathname-type output-file))))))))\n    (invalid-image ()\n      (unless warmup\n       (hex:safe-redirect (util.cdn:make-cdn \"/assets/images/invalid-image.png\"))))))\n\n(defun send-404 (reason)\n  (setf (hunchentoot:header-out :content-type) \"text/html\")\n  (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+)\n  (hunchentoot:abort-request-handler reason))\n\n(defparameter *signature-expiry* (* 30 24 3600))\n\n(defmethod %sign-oid ((oid string) &key ts)\n  (ironclad:byte-array-to-hex-string (sign-hmac (format nil \"~a.~a\" oid ts))))\n\n(define-condition timestamp-is-too-old (error)\n  ())\n\n(define-condition signature-not-present (error)\n  ()\n  (:report \"signature not present in image URL\"))\n\n(defun %decode-oid (oid &key ts signature)\n  (cond\n    ((not (str:emptyp signature))\n     (let ((ts (parse-integer ts)))\n       (when (< (+ ts *signature-expiry*)\n                (get-universal-time))\n         (error 'timestamp-is-too-old))\n       (let ((actual-signature (%sign-oid oid :ts ts)))\n         (unless (equal signature actual-signature)\n           (error \"signature does not match\")))\n\n       ;; We're returning an array for legacy reasons. It might be\n       ;; safe to just pass the string.\n       (ironclad:hex-string-to-byte-array oid)))\n    (t\n     (error 'signature-not-present))))\n\n(def-easy-macro with-access-checked-image (&binding image oid &key ts signature &fn fn)\n  (handler-case\n      (let ((oid (%decode-oid oid :ts ts :signature signature)))\n        (assert oid)\n        (let* ((image (find-image-by-oid oid)))\n          (fn image)))\n    (timestamp-is-too-old ()\n      (send-404 \"Timestamp is too old\"))\n    (no-image-uploaded-yet ()\n      (send-404 \"No image uploaded yet for this image\"))))\n\n(defun set-cors-header ()\n  (setf (hunchentoot:header-out :access-control-allow-origin)  \"*\"))\n\n(defhandler (image-blob-get :uri \"/image/blob/:oid/default.webp\") (oid size type\n                                                                       ts signature)\n  (with-access-checked-image (image oid :ts ts :signature signature)\n    (setf (hunchentoot:header-out :content-type) \"image/png\")\n    (set-cors-header)\n    (cond\n      (size\n       (handle-resized-image image size :type type))\n      (t\n       (with-local-image (file image)\n         (handle-static-file file))))))\n\n(defvar *resize-throttler* (make-instance 'ip-throttler\n                                          :tokens 100\n                                          :period 120))\n\n(defvar *resize-per-company-throttler* (make-instance 'throttler\n                                                      :tokens 1000))\n\n(def-easy-macro with-cropped-and-resized (image x0 y0 w0 h0 z &key &binding output &fn fn)\n  (throttle! *resize-throttler*)\n\n  ;; Sanity check, even though MagickWand should check this too.\n  (assert (< (* z w0) 16000))\n  (assert (< (* z h0) 16000))\n\n  (throttle! *resize-per-company-throttler* :key (screenshotbot/model/company:company image))\n\n  (uiop:with-temporary-file (:pathname p :type \"webp\")\n    (with-local-image (file image)\n      (with-semaphore ()\n        (with-wand (wand :file file)\n          ;; TODO: sanity check input?\n          (unless (magick-crop-image wand\n                                     w0\n                                     h0\n                                     x0\n                                     y0)\n            (error \"Could not crop image\"))\n          (unless (screenshotbot-resize wand\n                                        (ceiling (* z w0))\n                                        (ceiling (* z h0)))\n            (error \"Could not resize image\"))\n          (save-wand-to-file wand p))))\n    (fn p)))\n\n(defhandler (image-resized-blob :uri \"/image/resized.webp\")\n    (eoid (x0 :parameter-type 'integer)\n          (y0 :parameter-type 'integer)\n          (w0 :parameter-type 'integer)\n          (h0 :parameter-type 'integer)\n          z)\n  \"Given an image (EOID), an initial coordate [x0 y0] and a height and\n width [w0 h0], return the region corresponding to that [x0 y0] and\n [w0 h0] zoomed to a scale z.\n\nIt is expected that the client will call this via a CDN, and will be\ncareful about not making multiple requests. One such strategy will be\nthat if we want the image for [x0 y0 w0 h0], we would instead request\nthe image for [x0-(x0 % 2w0) y0 - (y0 % 2h0) 2w0 2h0] and retrieve the\nright region within that.\n\"\n\n  (let ((z (#+lispworks hcl:parse-float #-lispworks parse-float:parse-float z)))\n    (with-access-checked-image (image eoid)\n     (with-cropped-and-resized (image x0 y0 w0 h0 z :output p)\n       (setf (hunchentoot:header-out :content-type) \"image/webp\")\n       (handle-static-file p)))))\n\n(defun guess-content-type (type)\n  (cond\n    ((equal \"bin\" type)\n     \"application/octet-stream\")\n    (t\n     (format nil \"image/~a\"  type))))\n\n\n(defhandler (image-blob-get-original :uri \"/image/original/:eoid\") (eoid ts signature)\n  (destructuring-bind (oid &optional type) (str:split \".\" eoid)\n    (with-access-checked-image (image oid :ts ts :signature signature)\n      (with-local-image (file image)\n        (set-cors-header)\n        (cond\n          (type\n           (assert (str:s-member '(\"png\" \"webp\" \"jpeg\" \"bin\")\n                                 type))\n           (setf (hunchentoot:header-out :content-type) (guess-content-type type))\n           (handle-static-file file))\n          (t\n           ;; No type argument provided, let's figure it out and redirect\n           (hex:safe-redirect\n            (make-image-cdn-url\n             (hex:make-url\n              'image-blob-get-original\n              :eoid (format nil \"~a.~a\"\n                            eoid\n                            (str:downcase\n                             (image-format image))))))))))))\n\n(def-store-migration (\"Move image-cache to nested directories\" :version 6)\n  (dolist (image (bknr.datastore:class-instances 'image))\n    (dolist (size (mapcar #'cdr *size-map*))\n      (dolist (type (list \"webp\" \"png\"))\n        (let* ((args (list (oid image) :size size :type type))\n               (old (apply #'pathname-for-resized-image (append\n                                                         args\n                                                         (list :version :old))))\n               (new (apply #'pathname-for-resized-image args)))\n          (when (and\n                 (path:-e old)\n                 (not (path:-e new)))\n            (log:info \"Renaming ~a to ~a\" old new)\n            (rename-file old new)))))))\n\n(defun quantized-timestamp (ts)\n  (- ts (mod ts (/ *signature-expiry* 2))))\n\n(defmethod image-public-url ((image abstract-image) &key size type originalp)\n  (let* ((oid (oid image))\n         (ts (get-universal-time))\n         (ts (quantized-timestamp ts))\n         (signature (%sign-oid oid :ts ts)))\n    (let ((url\n            (let ((args nil))\n              (when size\n                (setf args `(:size ,(string-downcase size))))\n              (when type\n                (setf args (list* :type (str:downcase type) args)))\n              (apply #'hex:make-url 'image-blob-get :oid oid\n                                                    :ts ts\n                                                    :signature signature\n                                                    args))))\n      (cond\n        (originalp\n         (make-image-cdn-url\n          (hex:make-url \"/image/original/:oid\"\n                        :oid (format nil \"~a.~a\" oid (str:downcase (handler-case\n                                                                       (image-format image)\n                                                                     (no-image-uploaded-yet ()\n                                                                       \"webp\")\n                                                                     (invalid-image ()\n                                                                       \"bin\"))))\n                        :ts ts\n                        :signature signature)))\n        (type\n         (make-image-cdn-url url))\n        (t\n         ;; the image endpoint needs to guess the type based on Accept:\n         ;; headers. So we don't cache this for now.\n         url)))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/mask-builder.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/mask-builder\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:screenshotbot/model/screenshot\n        #:nibble\n        #:screenshotbot/model/image\n        #:screenshotbot/model/channel\n        #:screenshotbot/template)\n  (:import-from #:screenshotbot/server\n                #:with-login)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:mask-editor)\n  (:import-from #:screenshotbot/model/image\n                #:dimension-height\n                #:dimension-width\n                #:image-dimensions)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:screenshotbot/mask-rect-api\n                #:mask-rect-width\n                #:mask-rect-top\n                #:mask-rect-left))\n(in-package :screenshotbot/dashboard/mask-builder)\n\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun mask-editor (channel screenshot &key (redirect \"/runs\")\n                                         overlay)\n  (with-login ()\n   (let* ((dim (image-dimensions screenshot))\n          (mask (assoc-value (masks channel) (screenshot-name screenshot) :test 'equal))\n          (save (nibble (json :method :post)\n                  (let ((new-mask (loop for x in (json:decode-json-from-string json)\n                                        collect\n                                        (apply 'make-instance\n                                                'mask-rect\n                                                 :allow-other-keys t\n                                                 (alist-plist x)))))\n                    (set-channel-screenshot-mask\n                     channel\n                     (screenshot-name screenshot)\n                     new-mask))\n                  (hex:safe-redirect redirect))))\n     <app-template>\n       <form action=save method= \"POST\" id= \"mask-editor-form\" >\n         <div class= \"page-title-box main-content\" >\n           <h4 class= \"page-title\" >Edit Masks</h4>\n\n           <div class= \"float-end\" >\n             <input type= \"hidden\" name= \"json\" value= \"\" />\n             <a href= \"#\" id= \"clear-masks\" class= \"btn btn-danger btn-sm\">\n               <mdi name= \"delete_sweep\" />\n               Clear All\n             </a>\n             <a href= \"javascript:window.history.back()\" class= \"btn btn-sm btn-secondary\">\n               Discard Changes\n             </a>\n             <input type= \"submit\" id= \"save-masks\" class= \"btn btn-primary btn-sm\" value = \"Save Masks\" />\n\n           </div>\n         </div>\n         <div class= \"main-content pb-3\" >\n\n           <div class= \"info-box text-muted\" style= \"max-width: 50em\" >\n             <p class= \"mt-3 text-muted\" >\n               Masks specifies areas of the image that won't count towards screenshot comparison. For instance, you may use this to mask out animations and timestamps. Draw rectangles in the image below to specify the masked area.\n             </p>\n\n             <p>\n               Any modifications to the mask will only affect future runs. Existing runs and reports will not be affected.\n             </p>\n\n             <p>Use <code>Alt+Mouse</code> to pan, and <code>Mousewheel</code> to zoom.</p>\n           </div>\n\n\n\n           <div class= \"mt-3 mb-3\" >\n           </div>\n\n\n             <canvas id= \"mask-editor\" width=(dimension-width dim) height= (dimension-height dim)\n                     data-rects= (json:encode-json-to-string (coerce (mapcar #'fix-mask mask) 'vector ))\n                     style= \"max-width: 100%; max-height: 100%\"\n                     />\n             <style>\n               .canvas-container {\n                 aspect-ratio: ,(dimension-width dim) / ,(dimension-height dim);\n               }\n             </style>\n\n           <img id= \"mask-editor-image\"\n                src=(image-public-url (screenshot-image screenshot))\n                style= \"display:none\" />\n           ,(when overlay\n              <img id= \"mask-editor-overlay\"\n                   src= (image-public-url overlay)\n                   style= \"display:none\" />)\n           <script src= \"/assets/js/fabric.min.js\" />\n         </div>\n       </form>\n     </app-template>)))\n\n(defun fix-mask (mask)\n  (let ((ht (make-hash-table :test #'equal)))\n    (setf (gethash \"left\" ht)\n          (mask-rect-left mask))\n    (setf (gethash \"top\" ht)\n          (mask-rect-top mask))\n    (setf (gethash \"width\" ht)\n          (mask-rect-width mask))\n    (setf (gethash \"height\" ht)\n          (mask-rect-height mask))\n    ht))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/notes.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/notes\n  (:use #:cl\n        #:screenshotbot/template)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/form-state\n                #:form-state-initargs\n                #:read-form-state\n                #:form-state-class)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:screenshotbot/model/note\n                #:message\n                #:find-notes-for\n                #:note)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:user-full-name\n                #:user\n                #:current-user)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:screenshotbot/impersonation\n                #:make-impersonation\n                #:admin-user\n                #:impersonatedp\n                #:impersonation)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:create-note-page\n   #:render-notes))\n(in-package :screenshotbot/dashboard/notes)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun create-note-page (&rest args)\n  (nibble ()\n    (apply #'%create-note-page-with-impersonation-check args)))\n\n(defclass form-state ()\n  ((message :initarg :message\n            :reader message))\n  (:metaclass form-state-class))\n\n(defun %create-note-page-with-impersonation-check (&rest args)\n  (flet ((create-with (author)\n           (apply '%create-note-page\n                   :author author\n                   args)))\n    (let ((impersonation (make-impersonation)))\n      (cond\n        ((impersonatedp impersonation)\n         (let ((admin-user (admin-user impersonation)))\n           (assert admin-user)\n           <app-template>\n             (internal only) Who should we create this as?\n             <ul>\n               <li>\n                 <a href= (nibble () (create-with admin-user))>,(user-full-name admin-user)</a>\n               </li>\n               <li>\n                 <a href= (nibble () (create-with (current-user)))>,(user-full-name (current-user))</a>\n               </li>\n             </ul>\n           </app-template>))\n        (t\n         (create-with (current-user)))))))\n\n(defun %create-note-page (&key for redirect author)\n  (assert redirect)\n  (auth:can-view! for)\n  <simple-card-page form-action= (nibble () (submit-create :for for :redirect redirect :author author)) >\n      <div class= \"card-header\">\n        <h4>Add a note</h4>\n      </div>\n      <div class= \"form-group\">\n        <label for= \"message\" class= \"form-label\" >Message</label>\n        <textarea id= \"message\" name= \"message\" class= \"form-control mb-3\" />\n      </div>\n      <div class= \"card-footer\">\n        <input type= \"submit\" class= \"btn btn-primary\" value= \"Done\" />\n      </div>\n  </simple-card-page>)\n\n(defun submit-create (&key for redirect author)\n  (assert redirect)\n  (let ((form-state (read-form-state 'form-state))\n        errors)\n    (with-slots (message) form-state\n     (flet ((check (field test message)\n              (unless test\n                (push (cons field message) errors))))\n       (check :message (not (str:emptyp message))\n              \"Please provide a message\")\n       (cond\n         (errors\n          ;; TODO: if we ever do an edit, look at form-state-validate\n          (with-form-errors (:errors errors\n                             :message message\n                             :was-validated t)\n            (%create-note-page :for for :redirect redirect)))\n         (t\n          (let ((note (apply #'make-instance 'note\n                               :for for\n                               :user author\n                               (form-state-initargs form-state))))\n            (declare (ignore note))\n            (hex:safe-redirect redirect))))))))\n\n(defun render-user-name (user)\n  (cond\n    #-screenshotbot-oss\n    ((adminp user)\n     \"Screenshotbot Support\")\n    (t\n     (user-full-name user))))\n\n(defun render-notes (&key for)\n  (let ((notes (find-notes-for for)))\n    (when notes\n      <div class= \"mt-3\" >\n        ,@ (loop for note in notes collect\n        <div class= \"alert alert-info mt-1\" >\n          <h6><b>,(util/misc:?. render-user-name (user note))</b> added a note:</h6>\n          <div>,(message note)</div>\n        </div>)\n      </div>)))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/notices.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/notices\n  (:use :cl)\n  (:import-from #:auth\n                #:current-user)\n  (:import-from #:bknr.datastore\n                #:store-object-id\n                #:with-transaction)\n  (:import-from #:markup/markup\n                #:deftag\n                #:unescaped)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/invite\n                #:accept-invite)\n  (:import-from #:screenshotbot/notice-api\n                #:invite-company\n                #:notice-summary\n                #:notice-title)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/template\n                #:user-notice-list)\n  (:import-from #:screenshotbot/user-api\n                #:company-name\n                #:unaccepted-invites\n                #:user\n                #:user-notices)\n  (:import-from #:util/store/object-id\n                #:oid))\n(in-package :screenshotbot/dashboard/notices)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag user-notice-list (&key (user (current-user)))\n  <div id= \"user-notice-list\" >\n    <!-- user notice list-->\n    ,@ (loop for invite in (unaccepted-invites user) collect\n                                                     (render-invite invite))\n    ,@ (loop for notice in (user-notices user) collect\n                                               (render-notice notice))\n  </div>)\n\n(deftag notice-toast (children)\n  <div class=\"toast show position-fixed bottom-0 end-0 m-3 \" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\" data-bs-autohide=\"false\">\n    ,@children\n  </div>)\n\n(defmethod render-invite (invite)\n  <notice-toast>\n    <div class=\"toast-header\">\n      <strong class=\"me-auto\">Invitation to ,(company-name (invite-company invite))</strong>\n      <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n    </div>\n    <div class=\"toast-body\">\n      <p>You've been invited to collaborate in this Organization.</p>\n      <form action= (util:copying (invite)  (nibble () (accept-invite invite))) method= \"POST\" >\n        <input type= \"hidden\"\n               name= \"invite-id\"\n               value= (store-object-id invite) />\n        <input type= \"submit\" class= \"btn btn-success\" value= \"Accept Invitation\" />\n      </form>\n    </div>\n  </notice-toast>)\n\n(defmethod render-notice (notice)\n  <notice-toast>\n    <div class=\"toast-header\">\n      <strong class=\"me-auto\">,(notice-title notice)</strong>\n      <button type=\"button\" class=\"btn-close user-notice-dismiss\" data-bs-dismiss=\"toast\" aria-label=\"Close\" data-notice-id=(oid notice)></button>\n    </div>\n    <div class=\"toast-body\">\n      ,(notice-summary notice)\n    </div>\n  </notice-toast>)\n\n(defhandler (nil :uri \"/notice/dismiss\" :method :post) (notice-id)\n  (with-transaction ()\n    (setf (user-notices (current-user))\n          (remove-if (lambda (x)\n                       (equal notice-id\n                              (oid x)))\n                     (user-notices (current-user)))))\n  <user-notice-list />)\n"
  },
  {
    "path": "src/screenshotbot/dashboard/recent-runs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/recent-runs\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/user-api\n        #:screenshotbot/template\n        #:core/ui/taskie\n        #:markup)\n  (:import-from #:screenshotbot/server\n                #:with-login\n                #:defhandler)\n  (:import-from #:util #:make-url #:oid)\n  (:import-from #:screenshotbot/ui\n                #:ui/a\n                #:ui/div)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:render-run-tags\n                #:run-link\n                #:commit)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-page)\n  (:import-from #:screenshotbot/dashboard/explain\n                #:explain)\n  (:import-from #:screenshotbot/installation\n                #:default-logged-in-page)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:review-link)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:run-id-to-run-map\n                #:recorder-run-work-branch\n                #:recorder-run-branch\n                #:was-promoted-p\n                #:recorder-run-tags\n                #:runs-for-tag)\n  (:export #:recent-runs))\n(in-package :screenshotbot/dashboard/recent-runs)\n\n(named-readtables:in-readtable markup:syntax)\n\n(hex:declare-handler 'run-page)\n\n(defun find-recent-runs ()\n  (let ((company (current-company)))\n    (can-view! company)\n    (run-id-to-run-map company)))\n\n(deftag conditional-commit (&key repo hash)\n\n  (cond\n    ((and repo hash)\n       <span>on <commit repo= repo\n                        hash= hash /></span>)\n    (t\n     nil)))\n\n(deftag promoted-tooltip ()\n  <div>\n    <p>\n      A <b>promoted</b> run is the current \"golden\" run on your master branch.\n    </p>\n\n    <p>Promotion logic is used to send notifications for changes on your master branch,\n      and to track the history of a given screenshot. For projects not associated with a\n      repository, the promoted run is usually the most recent run.</p>\n\n    <p>Promoted runs are <b>not</b> used for determining changes on Pull Requests. For that we just use the first known run on the merge-base.</p>\n\n  </div>)\n\n(defmethod render-run-headline (run)\n  (ui/div\n   (let ((review-link (review-link :run run)))\n     (cond\n       ((activep run)\n        <span>\n          Promoted<explain title= \"Promoted run\"><promoted-tooltip /></explain>  run\n          <conditional-commit repo= (channel-repo (recorder-run-channel run))\n                              hash= (recorder-run-commit run) />\n        </span>)\n       ((or\n         (was-promoted-p run)\n         ;; legacy: we could probably remove this in the future\n         (recorder-previous-run run))\n        <span>Previously promoted run\n          <conditional-commit repo= (channel-repo (recorder-run-channel run))\n                              hash= (recorder-run-commit run) />\n        </span>)\n       (review-link\n        <span>\n          Run on ,(progn review-link)\n        </span>)\n       ((str:starts-with-p \"gh-readonly-queue/\" (recorder-run-work-branch run))\n        <span>\n          Run on <commit repo= (channel-repo (recorder-run-channel run)) hash= (recorder-run-commit run) /> from the merge queue\n        </span>)\n       (t\n        <span>\n          Unpromoted run\n          ,(when-let ((repo (channel-repo (recorder-run-channel run)))\n                      (hash (recorder-run-commit run)))\n             <span>\n               on\n               <commit repo= repo hash=hash />\n               ,(when (recorder-run-work-branch run)\n                  <span>(,(recorder-run-work-branch run))</span>)\n             </span>)\n        </span>)))))\n\n(deftag recorder-run-row (&key run)\n  (taskie-row :object run\n              <span>\n                <a href=(format nil \"/runs/~a\" (oid run))>\n                  ,(channel-name (recorder-run-channel run))\n                </a>\n                <render-run-tags tags= (recorder-run-tags run) />\n              </span>\n              (render-run-headline run)\n\n              (taskie-timestamp :prefix \"\" :timestamp (created-at run))))\n\n(defun render-recent-runs (runs &key (user (current-user))\n                                (title \"Recent Runs\")\n                                  (check-access-p t)\n                                  (script-name (hunchentoot:script-name*))\n                                  (company (current-company)))\n  (declare (ignore script-name))\n  (with-pagination (runs runs :next-link next-link\n                              :prev-link prev-link)\n    (when check-access-p\n     (apply 'can-view! runs))\n    (dashboard-template\n     :user user\n     :title \"Screenshotbot: Runs\"\n     :company company\n     :script-name \"/runs\"\n     <taskie-page-title title=title >\n       <a id= \"delete-runs\" class= \"btn btn-sm btn-danger\" >\n         Delete Selected\n       </a>\n       <div class= \"dropdown ms-1\">\n         <button class=\"btn btn-sm btn-success dropdown-toggle\" type=\"button\" id=\"compare\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n           Compare\n         </button>\n\n         <ul class= \"dropdown-menu dropdown-menu-end\" aria-labbelledby= \"compareDropdown\">\n           <li>\n             <a id= \"compare-runs\" class= \"dropdown-item\" >\n               Compare selected\n             </a>\n           </li>\n\n           <li>\n             <a class= \"dropdown-item\" href= \"/compare-branches\" >\n               Compare branches\n             </a>\n           </li>\n         </ul>\n\n       </div>\n     </taskie-page-title>\n\n     (taskie-list :empty-message \"No recent runs to show. But that's okay, it's easy to get started!\"\n                  :items runs\n                  :headers (list \"Channel\" \"Status\" \"Date\")\n                  :next-link next-link\n                  :prev-link prev-link\n                  :row-generator (lambda (run)\n                                   (recorder-run-row :run run))))))\n\n(defun %recent-runs ()\n  (with-login ()\n   (let ((runs (find-recent-runs)))\n     (render-recent-runs runs))))\n\n(defhandler (recent-runs :uri \"/runs\") ()\n  (%recent-runs))\n\n(defmethod default-logged-in-page ((installation t))\n  (%recent-runs))\n\n(defhandler (nil :uri \"/runs/by-tag/:tag\") (tag)\n  (with-login ()\n    (let ((company (current-company)))\n      (can-view! company)\n      (let ((runs (runs-for-tag company tag)))\n        (cond\n          ((= 1 (length runs))\n           (hex:safe-redirect\n            (run-link (car runs))))\n          (t\n           (render-recent-runs runs)))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/reports.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/reports\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/template\n        #:screenshotbot/user-api\n        #:screenshotbot/report-api\n        #:core/ui/taskie)\n  (:import-from #:screenshotbot/server\n                #:home-url\n                #:staging-p\n                #:with-login\n                #:defhandler)\n  (:import-from #:util\n                #:make-url\n                #:find-by-oid\n                #:oid)\n  (:import-from #:bknr.datastore\n                #:store-object-with-id)\n  (:import-from #:screenshotbot/dashboard/notes\n                #:render-notes\n                #:create-note-page)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:screenshotbot/model/sharing\n                #:share-expired-p\n                #:share-object\n                #:share)\n  (:import-from #:util/object-id\n                #:oid-array)\n  (:import-from #:core/ui/taskie\n                #:taskie-page-title)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-report\n                #:acceptable-history-item-state\n                #:acceptable-history-item-ts\n                #:accepable-history-item-user\n                #:acceptable-history-item\n                #:acceptable-history\n                #:report-company)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:render-warnings)\n  (:import-from #:screenshotbot/user-api\n                #:screenshot-name\n                #:user-full-name)\n  (:import-from #:util/timeago\n                #:timeago)\n  (:import-from #:screenshotbot/model/company\n                #:maybe-redirect-for-company)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:screenshot-key)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:render-single-group-list\n                #:render-single-change-permalink\n                #:render-change-group)\n  (:import-from #:screenshotbot/diff-report\n                #:make-diff-report)\n  (:import-from #:screenshotbot/model/image-comparison\n                #:image-comparison-difference-value\n                #:find-image-comparison-on-images)\n  (:import-from #:screenshotbot/screenshot-api\n                #:screenshot-image)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:screenshotbot/dashboard/explain\n                #:explain)\n  (:export #:report-page #:report-link\n           #:shared-report-page)\n  (:local-nicknames (#:diff-report #:screenshotbot/diff-report)))\n(in-package :screenshotbot/dashboard/reports)\n\n(named-readtables:in-readtable markup:syntax)\n\n(def-easy-macro with-report-login (report &fn fn)\n  (maybe-redirect-for-company (report-company report))\n  (with-login (:needs-login (not (can-public-view report))\n               :allow-url-redirect t\n                       :company (report-company report))\n    (auth:can-view! report)\n    (fn)))\n\n(defhandler (report-page :uri \"/report/:id\" :method :get) (id)\n  (cond\n    ((member (string id) (list \"2\" \"3\" \"5fd16bcf4f4b3822fd000146\"\n                               \"5fd16bcf4f4b3822fd000144\"\n                               \"814\")\n             :test 'equal)\n     (expired-report))\n    (t\n     (let ((report (ignore-errors (find-by-oid id))))\n       (cond\n         ((or\n           (not report)\n           (not (typep report 'report)))\n          (push-event :invalid-report :id id)\n          ;; We don't use template because this is messing up our Google\n          ;; Analytics. This is most likely trigged by Microsoft Outlook's\n          ;; preview.\n          <simple-card-page>\n            <div>\n              Invalid Report link. <a href= \"/report\">Click here to view recent reports.</a>\n            </div>\n          </simple-card-page>)\n         (t\n          (with-report-login (report)\n            (render-report-page report))))))))\n\n(defun expired-report ()\n  <app-template>\n    <div class= \"card mt-3\" style= \"max-width: 40em\" >\n      <div class= \"card-header\">\n        <h3 class= \"mt-0\" >Expired report</h3>\n      </div>\n      <div class= \"card-body\">\n        This report is expired! If you're here from GitHub, please reach out to us at <a href= \"mailto:support@screenshotbot.io\">support@screenshotbot.io</a> for a more recent demo.\n      </div>\n\n      <div class= \"card-footer\">\n        <a href= (home-url) class = \"btn btn-lg btn-primary\">Home</a>\n      </div>\n    </div>\n  </app-template>)\n\n(defun report-diff-report (report)\n  (make-diff-report (report-run report) (report-previous-run report)))\n\n(defun render-report-page (report &key alert skip-access-checks)\n  (check-type report report)\n  (unless skip-access-checks\n    (can-view! report))\n\n  <app-template body-class= \"dashboard\" title= (report-title report) content=nil >\n\n    <div class= \"content\">\n      ,(when (and nil (can-public-view report))\n         <section class= \"mt-3\" >\n           <div class= \"alert alert-danger\">\n             This report can be viewed by public, because the underlying repository is public\n           </div>\n         </section>)\n\n      ,(when alert\n         alert)\n\n      ,(render-notes :for report)\n\n    </div>\n\n\n    <render-diff-report diff-report= (report-diff-report report)\n                        acceptable= (report-acceptable report)\n                        more= (remove-if #'null (more-links-for-report report))\n                        >\n      ,@(render-warnings (report-run report))\n    </render-diff-report>\n    \n  </app-template>)\n\n(defun share-report (report)\n  (let ((submit (nibble (expiry-date)\n                  (submit-share-report report expiry-date))))\n    <simple-card-page form-action=submit >\n      <div class= \"card-header\">\n        <h3>Create public link for report</h3>\n      </div>\n\n      <div>\n        <div class= \"alert alert-warning\">\n          Any person with access to the link will be able to access the report, and all the images associated with it. They will not be able to edit or perform any actions on the report.\n        </div>\n      </div>\n\n      <div class= \"mb-3\">\n        <label class= \"form-label\" for= \"expiry-date\">Expiration date <span class= \"text-muted\">(leave empty to never expire)</span></label>\n        <input type= \"date\" id= \"expiry-date\" name= \"expiry-date\" class= \"form-control\" />\n      </div>\n\n      <div class= \"card-footer\">\n        <input type= \"submit\" class= \"btn btn-primary\" value= \"Create Public Link\" />\n        <a href= (report-link report) class= \"btn btn-outline-secondary\" >Cancel</a>\n      </div>\n    </simple-card-page>))\n\n(defun submit-share-report (report expiry-date)\n  (push-event :share.create)\n  (let ((errors))\n    (flet ((check (field check message)\n             (unless check\n               (push (cons field message) errors))))\n      (unless (str:emptyp expiry-date)\n        (let ((parsed (local-time:parse-timestring expiry-date)))\n          (check :expiry-date parsed \"Invalid date\")\n          (when parsed\n            (or\n             (check :expiry-date\n                    (local-time:timestamp>\n                     parsed\n                     (local-time:now))\n                    \"Date can't be in the past\")\n             (check :expiry-date\n                    (local-time:timestamp>\n                     parsed\n                     (local-time:timestamp+ (local-time:now) 2 :day))\n                    \"Choose a date at least two days in the future\"))))\n        (check :expiry-date\n               (cl-ppcre:scan \"\\\\d{4}-\\\\d{2}-\\\\d{2}\"\n                expiry-date)\n               \"Invalid date format, perhaps you're using an old browser? Try YYYY-MM-DD format.\"))\n\n      (cond\n        (errors\n         (with-form-errors (:errors errors\n                            :was-validated t\n                            :expiry-date expiry-date)\n           (share-report report)))\n        (t\n         (let ((share (make-instance 'share\n                                      :object report\n                                      :creator (current-user)\n                                      :company (current-company)\n                                      :expiry-date expiry-date)))\n           (hex:safe-redirect\n            (nibble ()\n              (let ((link (hex:make-full-url\n                           hunchentoot:*request*\n                           'shared-report-page\n                            :eoid (encrypt:encrypt-mongoid (oid-array share)))))\n               (render-report-page report\n                                   :alert\n                                   <div class= \"alert alert-info mt-3\">\n                                     <p class= \"mb-0\" >\n                                       Public link to report: <a href=link >,(progn link)</a>\n                                     </p>\n                                     ,(unless (str:emptyp expiry-date)\n                                        <p class= \"mb-0\" >\n                                          This link will expire ,(timeago :timestamp (local-time:parse-timestring expiry-date)).\n                                        </p>)\n                                   </div>))))))))))\n\n(defun more-links-for-report (report)\n  \"More links for report. The returned list may have null-values which\n will be ignored.\"\n  (list\n   (when (current-user)\n     (cons\n      <span><mdi name= \"share\" /> Share</span>\n      (nibble (:name \"share\")\n        (share-report report))))\n   (when (current-user)\n     (cons\n      <span><mdi name= \"chat\" /> Add Note</span>\n      (create-note-page :for report :redirect (make-url 'report-page :id (oid report)))))\n   (when-let ((acceptable (report-acceptable report)))\n    (cons\n     <span><mdi name= \"history\"/> Feedback history</span>\n     (nibble (:name \"review-history\")\n       (render-acceptable-history acceptable))))\n   (cons\n    <span><mdi name= \"sort\" />Sorted by changes</span>\n    (format nil \"/report/~a/sorted\" (oid report)))))\n\n(defun render-acceptable-history (acceptable)\n  (let ((history-items (acceptable-history acceptable)))\n    <simple-card-page>\n\n      <div class= \"card-header\" >\n        <h4 class= \"\">\n          Review History\n        </h4>\n      </div>\n      <div class= \"card-body\">\n        ,(cond\n           (history-items\n            <ul>\n              ,@ (loop for item in history-items\n                       collect (render-acceptable-history-item item))\n            </ul>)\n           (t\n            <span>This report has not been reviewed.</span>))\n\n      </div>\n      <div class= \"card-footer\">\n        <a class= \"btn btn-link\" href= (report-link (acceptable-report acceptable))>Back to report</a>\n      </div>\n    </simple-card-page>))\n\n(defmethod render-acceptable-history-item ((item acceptable-history-item))\n  (let ((class (ecase (acceptable-history-item-state item)\n                 (:accepted\n                  \"text-success\")\n                 (:rejected\n                  \"text-danger\"))))\n    <li>\n      <span class=class >,(progn (str:sentence-case (string (acceptable-history-item-state item))))</span>\n      by\n      ,(user-full-name (accepable-history-item-user item))\n      at\n      ,(timeago :timestamp (acceptable-history-item-ts item))\n    </li>))\n\n\n\n(defhandler (nil :uri \"/reports\" :method :get) ()\n  (hex:safe-redirect \"/report\"))\n\n(defhandler (report-list :uri \"/report\" :method :get\n                         :want-login t) ()\n  (auth:can-view! (current-company))\n  (flet ((row-generator (row)\n           <taskie-row object=row >\n             <a href= (make-url 'report-page\n                                 :id (oid row))>\n                                 ,(channel-name (recorder-run-channel (report-run row)))\n             </a>\n           <div>,(report-title row) </div>\n           <span>\n           <mdi name= \"today\" />\n           <:time class= \"timeago\" datetime= (created-at row) >\n           ,(created-at row)\n           </:time>\n           </span>\n           </taskie-row>))\n    (let ((reports (company-reports (current-company))))\n      (with-pagination (reports reports\n                                :next-link next-link\n                                :prev-link prev-link)\n        <app-template title= \"Screenshotbot: Reports\" >\n          <taskie-page-title title= \"Recent Reports\" />\n\n          <taskie-list empty-message=\"No reports to show! Reports are created when\n                                      your CI builds create a run with differing images.\"\n                       items=reports\n                       headers= (list \"Channel\" \"State\" \"Time\")\n                       next-link=next-link\n                       checkboxes=nil\n                       prev-link=prev-link\n                       row-generator=#'row-generator\n                       />\n\n        </app-template>))))\n\n(defun report-link (report)\n  (format nil \"~a~a\"\n          (installation-domain (installation))\n          (make-url 'report-page :id (oid report))))\n\n(defhandler (shared-report-page :uri \"/report/:eoid/public\") (eoid)\n  (let* ((oid (encrypt:decrypt-mongoid eoid))\n         (share (find-by-oid oid)))\n    (check-type share share)\n    (cond\n      ((share-expired-p share)\n       <app-template>\n         <div class= \"alert alert-danger mt-3\">\n           This shared URL has expired.\n         </div>\n       </app-template>)\n      (t\n       (let ((report (share-object share)))\n         (check-type report report)\n\n         (add-sales-toast\n          (render-report-page report\n                              :skip-access-checks t\n                              :alert\n                              <div class= \"alert alert-warning mt-3\">\n                              <b>Caution!</b> This is a publicly shared URL of a private report. Some actions on this page will require an authorized logged-in user. <a href= (report-link report)>Click here to view the private report.</a>\n                              </div>)))))))\n\n(defun add-sales-toast (html)\n  (mquery:with-document (html)\n    (let ((toast <div class=\"toast position-fixed bottom-0 end-0 m-3 show\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\" data-bs-autohide=\"false\" style=\"z-index: 9999; transition: opacity 0.3s ease, transform 0.3s ease;\" id= \"upsell-toast\" >\n                   <div class=\"toast-header\">\n                     <strong class=\"me-auto\">Would you like to see reports like these on your Pull Requests?</strong>\n                     <button type=\"button\" class=\"btn-close me-2\" data-bs-dismiss=\"toast\" onclick=\"var toast = this.closest('.toast'); toast.style.opacity = '0'; toast.style.transform = 'translateX(100%)'; setTimeout(function() { toast.remove(); }, 300);\" aria-label=\"Close\"></button>\n                   </div>\n                                \n                   <div class=\"toast-body\">\n                     <p>Screenshotbot works with most platforms, and helps you write screenshot tests with existing testing libraries.</p>\n                     <p>Take a look at our documentation for:</p>\n\n                     <ul>\n                       <li><a href=\"/documentation/platforms/android-apps\">Android</a></li>\n                       <li><a href=\"/documentation/platforms/ios-apps\">iOS</a></li>\n                       <li><a href=\"/documentation/platforms/flutter-apps\">Flutter</a></li>\n                       <li><a href=\"/documentation/web-projects/introduction\">Web</a></li>\n                     </ul>\n\n                     <a href= \"/request-demo\" target= \"_blank\" class= \"btn btn-success w-100\" >Contact us</a>\n                   </div>\n\n\n                 </div>))\n      (mquery:mqappend (mquery:$ \"body\") toast))))\n\n\n\n(defhandler (single-image-page :uri \"/report/:oid/image/:key-id\") (oid\n                                                                key-id)\n  (let ((report (util:find-by-oid oid))\n        (key-id (parse-integer key-id)))\n    (with-report-login (report)\n      (let ((diff-report (report-diff-report report)))\n        (let ((report-link (format nil \"/report/~a\" oid)))\n          <app-template body-class= \"dashboard\">\n            ,(render-single-change-permalink diff-report key-id report-link\n                                             :run (report-run report))\n          </app-template>)))))\n\n(markup:deftag sorted-template (children &key report)\n  <app-template>\n    <div class= \"page-title-box mb-3\">\n      <div class= \"mb-2\" ><a href= (report-link report) >Back to report</a></div>\n    </div>\n    ,@children\n  </app-template>)\n\n(defhandler (sorted-by-changes-page :uri \"/report/:oid/sorted\") (oid)\n  (let ((report (util:find-by-oid oid)))\n    (with-report-login (report)\n      (%render-sorted-by-changes report))))\n\n(defmethod %render-sorted-by-changes ((report report))\n  (flet ((change-to-comparison (change)\n           (find-image-comparison-on-images\n            (screenshot-image (diff-report:before change))\n            (screenshot-image (diff-report:after change))\n            :only-cached-p t)))\n    (let* ((diff-report (report-diff-report report))\n           (changes (diff-report:diff-report-changes diff-report))\n           (comparisons (loop for change in changes\n                              collect (change-to-comparison change))))\n      (cond\n        ((remove-if-not #'null comparisons)\n         <sorted-template report=report >\n           <div class= \"alert alert-danger mt-2\">\n             Image processing for this report is not complete yet, so we're unable to show you\n             the changes sorted by difference. Please refresh in a few minutes.\n           </div>\n         </sorted-template>)\n        ((some (alexandria:compose #'null #'image-comparison-difference-value) comparisons)\n         (Warn \"comparison for older reports\")\n         <sorted-template report=report >\n           <div class= \"alert alert-danger mt-2\">\n             We were unable to generate the report, probably because this is an older report. Please contact support@screenshotbot.io if you think this is an error.\n           </div>\n         </sorted-template>)\n        (t\n         (let ((changes (sort (copy-list changes)\n                              #'>\n                              :key (lambda (change)\n                                     (image-comparison-difference-value\n                                      (change-to-comparison change))))))\n           <sorted-template report=report >\n             ,(paginated\n                (lambda (change index)\n                  (let ((key (screenshot-key (diff-report:before change))))\n                   (render-change-group\n                    (make-instance 'diff-report:group\n                                   :title\n                                   <div>\n                                     <h4 class= \"screenshot-title\"><span class= \"index\">,(1+ index)</span> ,(screenshot-name key) <tt class= \"text-small\" >,(format nil \"~1,4f\"(image-comparison-difference-value (change-to-comparison change))) </tt>\n\n                                     <explain>\n                                       We use the Root-Mean-Squared-Error to compute this difference. This might not\n                                       correspond to whether or not this is a regression. Many real regressions\n                                       have small RMSE values.\n                                     </explain>\n\n                                   </h4>\n                                   </div>\n                                   \n                                   \n                                   :items (list\n                                           (make-instance 'diff-report:group-item\n                                                          :subtitle nil\n                                                          :actual-item change)))\n                    (report-run report)\n                    (format nil \"/report/:oid\" (oid report)))))\n                :infinite-scroll t\n                :pass-index-p t\n                :items changes)\n           </sorted-template>)\n         )))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/dashboard/review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/review-link\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-work-branch\n                #:pull-request-id\n                #:gitlab-merge-request-iid\n                #:phabricator-diff-id)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url\n                #:recorder-run-channel\n                #:channel-repo)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:screenshotbot/model/channel\n                #:github-get-canonical-repo)\n  (:import-from #:screenshotbot/git-repo\n                #:repo-link)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:review-link\n   #:review-link-impl\n   #:reunder-pull-request-link\n   #:describe-pull-request\n   #:get-canonical-pull-request-url))\n(in-package :screenshotbot/dashboard/review-link)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun review-link (&key run)\n  (cond\n    ((pull-request-id run)\n     (render-pull-request-link\n      (?. channel-repo (recorder-run-channel run))\n      run))\n    (t\n     (review-link-impl (?. channel-repo (recorder-run-channel run)) run))))\n\n(defmethod review-link-impl (repo run)\n  nil)\n\n(defgeneric get-canonical-pull-request-url (repo pull-request-id)\n  (:method (repo pull-request-id)\n    \"#\"))\n\n(defmethod render-pull-request-link (repo run)\n  (let ((href (get-canonical-pull-request-url\n               repo\n               (pull-request-id run))))\n    <a href=href >,(describe-pull-request repo run)</a>))\n\n(defmethod describe-pull-request (repo run)\n  \"Pull Request\")\n\n(defmethod describe-pull-request :around (repo run)\n  (str:concat\n   (call-next-method)\n   (let ((branch (recorder-run-work-branch run)))\n     (unless (or\n              (str:emptyp branch)\n              (equal \"HEAD\" branch))\n       (format nil \" (~a)\" (car (last (str:split \"/\" branch))))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/run-page.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/run-page\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:nibble\n        #:screenshotbot/promote-api\n        #:screenshotbot/report-api\n        #:screenshotbot/user-api\n        #:screenshotbot/git-repo\n        #:screenshotbot/screenshot-api\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/view\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/template\n        #:screenshotbot/model/image\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/company)\n  (:import-from #:screenshotbot/server\n                #:with-login\n                #:make-thread\n                #:defhandler)\n  (:import-from #:util\n                #:oid\n                #:find-by-oid)\n  (:import-from #:hex #:make-url)\n  (:import-from #:screenshotbot/ui\n                #:ui/a\n                #:ui/div)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:core/ui/paginated\n                #:apply-map-filter\n                #:paginated)\n  (:import-from #:screenshotbot/model/report\n                #:reports-for-run)\n  (:import-from #:screenshotbot/report-api\n                #:report-title)\n  (:import-from #:screenshotbot/model/image\n                #:image-dimensions)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-id\n                #:compare-tolerance\n                #:delete-run\n                #:recorder-run-tags\n                #:gitlab-merge-request-iid\n                #:run-screenshot-map\n                #:compare-threshold\n                #:compared-against\n                #:merge-base-failed-warning\n                #:recorder-run-warnings\n                #:override-commit-hash)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:adminp)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/model/view\n                #:can-edit)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:review-link\n                #:describe-pull-request)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler)\n  (:import-from #:screenshotbot/model/downloadable-run\n                #:downloadable-run-state\n                #:find-or-create-downloadable-run\n                #:find-downloadable-run)\n  (:import-from #:screenshotbot/model/channel\n                #:all-active-runs)\n  (:import-from #:screenshotbot/model/company\n                #:company-admin-p)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-name)\n  (:import-from #:util/timeago\n                #:timeago)\n  (:import-from #:screenshotbot/figma/dropdown\n                #:figma-drop-down)\n  (:import-from #:screenshotbot/model/archived-run\n                #:load-archived-run)\n  (:import-from #:screenshotbot/dashboard/commit-graph\n                #:view-git-graph)\n  (:export\n   #:*create-issue-popup*\n   #:run-page\n   #:run-row-filter\n   #:row-filter\n   #:commit)\n  (:export #:mask-editor\n           #:start-review-enabled-p)\n  (:local-nicknames (#:screenshot-map #:screenshotbot/model/screenshot-map)))\n(in-package :screenshotbot/dashboard/run-page)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *download-throttler* (make-instance 'throttler\n                                            :tokens 100))\n\n(defvar *create-issue-popup* nil\n  \"On the non-OSS Screenshotbot, we have a feature to Jira tasks\n  directly from Screenshotbot by annotating images. This is just a\n  reference to that function. If there are requests for it, we'll open\n  source it too.\")\n\n(deftag commit (&key repo hash)\n  <a href= (commit-link repo hash) >,(str:substring 0 8 hash)</a>)\n\n(defun find-run-by-oid (oid)\n  (let ((run (find-by-oid oid)))\n    (when run\n      (check-type run recorder-run))\n    (or\n     run\n     (load-archived-run oid))))\n\n(defhandler (run-page :uri \"/runs/:id\" :method :get) (id name)\n  (let* ((run (find-run-by-oid id)))\n    (maybe-redirect-for-company (recorder-run-company run))\n    (flet ((render ()\n             (render-run-page run :name name)))\n     (cond\n       ((can-public-view run)\n        (render))\n       (t\n        (with-login (:company (recorder-run-company run)\n                     :allow-url-redirect t)\n          (render)))))))\n\n(deftag render-run-tags (&key tags)\n  (when tags ;; avoid whitespace\n    <span>\n      ,@(loop for tag in tags\n              collect <span class= \"badge bg-light text-dark ms-1\" >,(progn tag)</span>)\n    </span>))\n\n(deftag page-nav-dropdown (children &key title (btn-class \"btn-secondary\"))\n  (let ()\n\n    (mquery:add-class (mquery:$ \"a\" children) \"dropdown-item\")\n\n    <div class=\"dropdown\">\n      <button class= (format nil \"btn btn-sm ~a dropdown-toggle\" btn-class) type=\"button\" id=\"dropdownMenuButton\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n        ,(progn title)\n      </button>\n      <div class=\"dropdown-menu dropdown-menu-end\" aria-labelledby=\"dropdownMenuButton\" style= \"z-index: 99999999\" >\n        ,@(progn children)\n      </div>\n    </div>))\n\n(defun promotion-log-page (run)\n  <app-template>\n  <section>\n  <pre>\n  --BEGIN-LOGS--\n,(let ((file (bknr.datastore:blob-pathname (promotion-log run))))\n      (cond\n        ((path:-e file)\n         (uiop:read-file-string file))\n        (t\n         \"Log file unavailable\")))\n  --END-LOGS--\n  </pre>\n  </section>\n  </app-template>)\n\n(defun run-size (run)\n  \"Get the total size of all the screenshots associated with this run\"\n  (floor\n   (loop for screenshot in (recorder-run-screenshots run)\n         summing\n         (with-local-image (file screenshot)\n           (trivial-file-size:file-size-in-octets file)))\n   1024))\n\n(defun screenshots-above-16k-dim (run)\n  \"Get the number of screenshots that have a dimension above 16k. This\n  is the limit imposed by webp images.\"\n  (let ((+limit+ 16383))\n    (loop for screenshot in (recorder-run-screenshots run)\n          for dim = (image-dimensions screenshot)\n          if (or\n              (> (dimension-height dim) +limit+)\n              (> (dimension-width dim) +limit+))\n            collect screenshot)))\n\n(defun download-run (run)\n  (with-login ()\n    (let* ((existing (find-downloadable-run run))\n           (prepare (nibble ()\n                      (throttle! *download-throttler* :key (current-company))\n                      (find-or-create-downloadable-run run :prepare t)\n                      (sleep 1)\n                      (download-run run)))\n           (refresh (nibble ()\n                      (sleep 1)\n                      (download-run run)))\n           (download (nibble ()\n                      (throttle! *download-throttler* :key (current-company))\n                       (setf (hunchentoot:header-out :content-disposition)\n                             (format nil \"attachment; filename=~a.zip\"\n                                     (oid run)))\n                       (hunchentoot:handle-static-file\n                        (bknr.datastore:blob-pathname existing)))))\n      (cond\n        ((> (run-size run)\n            (* (if (gk:check :allow-large-run-downloads (auth:current-company))\n                   300\n                   100) 1024))\n         <simple-card-page>\n           <div>This run is too large (,(run-size run)kB) to download with this tool. Contact support@screenshotbot.io for alternative options.</div>\n         </simple-card-page>)\n        ((null existing)\n         <simple-card-page>\n           <h5 class= \"card-title\" >Download as a ZIP file</h5>\n           <form class= \"mt-3\" action=prepare >\n             <input class= \"btn btn-primary\" type= \"submit\" value= \"Build archive\" />\n           </form>\n         </simple-card-page>)\n        ((eql :pending (downloadable-run-state existing))\n         <simple-card-page>\n           <h5 class= \"card-title\" >Download as a ZIP file</h5>\n           <div>\n             Building archive <a href= refresh >Refresh</a>\n           </div>\n         </simple-card-page>)\n        (t\n         <simple-card-page>\n           <h5 class= \"card-title\" >Download as a ZIP file</h5>\n           <div>\n             <a href= download >Download</a>\n           </div>\n         </simple-card-page>)))))\n\n(defun unpromote-run-flow (run)\n  (confirmation-page\n   :yes (nibble ()\n          (unpromote-run run)\n          (hex:safe-redirect (nibble () (advanced-run-page :run run\n                                                           :alert \"This run has been un-promoted\"))))\n   :no (nibble ()\n         (advanced-run-page :run run))\n\n     <span>\n       Are you sure you want to unpromote this run? Usually you only have to do this if you rewrite your Git history. If you're unsure please reach out to Screenshotbot support before doing this.\n     </span>\n))\n\n\n(deftag advanced-run-page (&key run alert)\n  (let* ((channel (recorder-run-channel run))\n         (repo (channel-repo channel)))\n    <simple-card-page max-width= \"40rem\" >\n        <div class=\"card-header\">\n          <h3>Debug Run Information</h3>\n        </div>\n        <div class=\"card-body\">\n          ,(when alert\n             <div class=\"alert alert-info mt-3\">,(progn alert)</div>)\n\n          <ul>\n            <li>Run ID: ,(ignore-errors (recorder-run-id run)) </li>\n            <li>Organization: ,(?. company-name (recorder-run-company run))</li>\n            <li>Channel: <a href= (format nil \"/channels/~a\" (store-object-id channel)) >,(channel-name channel)</a> </li>            \n            <li>Repo url: ,(github-repo run)</li>\n            <li>Commit: ,(commit :repo repo :hash (recorder-run-commit run)) </li>\n            <li>Override Commit hash for Pull Requests: ,(commit :repo repo :hash (override-commit-hash run))</li>\n            <li>Main Branch: ,(recorder-run-branch run)</li>\n            <li>Branch: ,(recorder-run-work-branch run)</li>\n            <li>Commit on main branch: ,(commit :repo repo :hash (ignore-errors\n                                                             (recorder-run-branch-hash run)))</li>\n            <li>\n              Author: ,(or (recorder-run-author run)\n                             \"No author provided\")\n            </li>\n            <li>Merge base: ,(commit :repo repo :hash (recorder-run-merge-base run))</li>\n            <li>Pull request: ,(or (pull-request-url run) \"NA\")</li>\n            <li>Phabricator Diff-id: ,(or (phabricator-diff-id run) \"NA\")</li>\n            <li>Merge request IID: ,(or (gitlab-merge-request-iid run) \"NA\")</li>\n            <li>Build URL: <a href=(run-build-url run)>,(run-build-url run)</a> </li>\n            <li>Production?: ,(if (trunkp run) \"true\" \"false\")</li>\n            <li>Periodic job?: ,(if (periodic-job-p run) \"true\" \"false\")</li>\n            <li>Number of screenshots: ,(length (recorder-run-screenshots run))</li>\n            <li>Total run size: ,(run-size run)kB</li>\n            <li>Comparison threshold: ,(compare-threshold run)</li>\n            <li>Pixel tolerance: ,(compare-tolerance run)</li>\n            <li>Batch: ,(when-let ((batch (recorder-run-batch run)))\n                          <span>\n                            ,(batch-name batch)\n                            <a href= (format nil \"/batch/~a\" (oid batch)) >link</a>\n                          </span>)\n            </li>\n\n            <li>Screenshots that are above 16k dimensions:\n\n            <a href= (nibble ()\n                               <app-template>\n                                 List of screenshots with large dimensions ,(length (screenshots-above-16k-dim run))\n                                 <ul>\n                                   ,@(loop for x in (screenshots-above-16k-dim run)\n                                           collect\n                                           <li>\n                                             <a href= (make-url 'run-page :id (oid run) :name (screenshot-name x))>\n                                               ,(screenshot-name x)\n                                             </a>\n                                           </li>)\n                                 </ul>\n                               </app-template>)>\n                               List\n    </a>\n            </li>\n          </ul>\n\n\n          ,(when (recorder-run-metadata run)\n             <div>\n               <h4 class= \"card-title\">\n                 Machine Metadata\n               </h4>\n               <p class= \"text-muted\">\n                 Device information can be useful to for debugging rendering flakiness.\n               </p>\n               <table class= \"table border\" >\n                 <tbody>\n                   ,@ (loop for (key . value) in (recorder-run-metadata run)\n                            if (can-view-metadata-p key)\n                            collect <tr><td><tt>,(progn key)</tt></td><td><tt>,(progn value)</tt></td></tr>)\n                 </tbody>\n               </table>\n             </div>)\n    </div>\n\n    <div class=\"card-footer\">\n      <form>\n        <a href= (nibble () (view-git-graph repo))\n           class= \"btn btn-secondary\">Debug Git Graph</a>\n        ,(when (and (activep run)\n                    (or\n                     (company-admin-p (current-company) (current-user))\n                     (adminp (current-user))))\n           <a\n             href=(nibble () (unpromote-run-flow run))\n             class=\"btn btn-danger\" >\n\n             Undo promotion\n           </a>)\n\n        <a class = \"ms-2 btn btn-secondary\" href= (nibble ()\n                           (promotion-log-page run)) >\n          Promotion logs\n        </a>\n      </form>\n        </div>\n\n    </simple-card-page>))\n\n(defun can-view-metadata-p (key)\n  \"Hide metadata that starts with % from end-users\"\n  (or\n   (not (str:starts-with-p \"%\" key))\n   (adminp (auth:current-user))))\n\n(DEFMETHOD extra-advanced-options (run)\n  )\n\n(defmethod start-review-enabled-p ((installation t) (run t))\n  nil)\n\n(defun re-run-promotions (run)\n  (flet ((go-back ()\n           (hex:safe-redirect 'run-page\n                              :id (oid run))))\n   (confirmation-page\n    :yes (nibble ()\n           (make-thread\n            (lambda ()\n              (log:info \"Starting re-run promotion thread\")\n              (start-promotion-thread\n               run)))\n           (go-back))\n    :no (nibble ()\n          (go-back))\n    <p class= \"card-title\" >Re-run promotion scripts?</p>\n    <p>You probably don't want to do this, unless you know what you're doing. Re-running promotions can send spurious promotion notifications to subscribers, and can sometimes alter the history of screenshots.</p>\n\n    <p>This might still be useful when you're testing out an integration with an external service for notifications, such as Slack or Jira.</p>)))\n\n(defhandler (%advanced-run-page :uri \"/runs/:oid/debug\") (oid)\n  (with-login ()\n    (let ((run (find-run-by-oid  oid)))\n      (can-view! run)\n      (advanced-run-page :run run))))\n\n(deftag run-advanced-menu (&key run)\n  (let ((promotion-logs (nibble ()\n                          (promotion-log-page run)))\n        (rerun-promotions (nibble ()\n                            (re-run-promotions run)))\n        (debug-info (make-url '%advanced-run-page :oid (oid run)))\n        (download-run (nibble ()\n                        (download-run run))))\n    <page-nav-dropdown title= \"Advanced\">\n      <a href= promotion-logs >Promotion Logs</a>\n      <a href= rerun-promotions >Re-Run Promotions</a>\n      <a href=debug-info >Debug Info</a>\n      ,(when (start-review-enabled-p (installation) run)\n         <a href= (format nil \"/review/~a\" (oid run)) >Start Review</a>)\n      <a href= download-run >Download run</a>\n    </page-nav-dropdown>))\n\n(defun create-filter-matcher (filter &key key)\n  (cond\n    ((eq t filter)\n     'identity)\n    (t (lambda (x) (equal (funcall key x)\n                          filter)))))\n\n(defclass row-filter ()\n  ((key :initarg :key\n        :initform 'identity\n        :accessor row-filter-key)\n   (value :initarg :value\n          :accessor row-filter-value)))\n\n(defun run-row-filter (row-filter list)\n  (let ((list (if (listp list) list (list list))))\n   (let ((filter (row-filter-value row-filter)))\n     (loop for x in list\n           if (or\n               (eq filter t)\n               (equal filter (funcall (row-filter-key row-filter) x)))\n             collect x))))\n\n(deftag warning-alert (children &key (type \"warning\")\n                       (call-out \"Caution!\"))\n  <div class= (format nil \"alert alert-~a mt-2\" type) >\n    ,(unless (str:emptyp call-out)\n       <strong class= \"pe-1\" >,(progn call-out)</strong>)\n    ,@children\n  </div>)\n\n(defgeneric render-run-warning (run warning)\n  (:method (run warning)\n    (warn \"render-run-warning not implemented\")\n    nil))\n\n(defmethod render-run-warning (run (warning merge-base-failed-warning))\n  (let ((repo (channel-repo (recorder-run-channel run))))\n   (flet ((link (hash)\n            (commit-link repo\n                         hash)))\n     <warning-alert>\n       <span>The <a href= (link (recorder-run-merge-base run))>merge base</a> for ,(review-link :run run) had a failing build. Screenshotbot used <a href= (link (recorder-run-commit (compared-against warning)))>this commit</a> to generate reports for this run. Consider rebasing to avoid this message.\n       </span>\n     </warning-alert>)))\n\n(defmethod render-run-warning (run (warning not-fast-forward-promotion-warning))\n  (when-let ((previous-run (recorder-previous-run run)))\n    <warning-alert>\n      <span>\n        This run was not a fast-forward of the commit from the <a href= (run-link previous-run)>previous promoted run</a>. This might be the result of rewriting Git history with <tt>git push -f</tt>, or because of an incorrect invocation of the CLI tool on a developer device.\n      </span>\n    </warning-alert>))\n\n(defun render-warnings (run)\n  (when-let ((warnings (recorder-run-warnings run)))\n    (loop for warning in warnings\n          collect\n          (render-run-warning run warning))))\n\n(defun %compare-to-link (run to)\n  (make-url \"/runs/:id/compare/:to\" :id (oid run)\n                                    :to (oid to)))\n\n(deftag comparison-menu (&key run)\n  (let* ((channel (recorder-run-channel run))\n         (active-runs (all-active-runs channel)))\n    (when active-runs\n       <page-nav-dropdown title= \"Compare\" btn-class= \"btn-outline-success\" >\n         ,@ (loop for (branch . other-run) in active-runs\n                  if other-run\n                    collect\n                  <markup:merge-tag>\n                    <a href= (%compare-to-link run other-run) >To <tt>,(progn branch)</tt></a>\n               </markup:merge-tag>)\n       </page-nav-dropdown>)))\n\n\n(defun render-run-page (run &key name)\n  (can-view! run)\n  (let* ((channel (recorder-run-channel run))\n         (screenshots (screenshot-map:to-map (run-screenshot-map run)))\n         (filter (cond\n                   (name\n                    (lambda (screenshot-key)\n                      (string-equal (screenshot-name screenshot-key) name)))\n                   (t\n                    #'identity)))\n         (filtered-screenshots (apply-map-filter screenshots filter)))\n    <app-template body-class= \"dashboard\" >\n      <div class= \"page-title-box\">\n        <h4 class= \"page-title\" >Run from\n          <:time class= \"timeago\" datetime= (created-at run)>\n            ,(created-at run)\n          </:time>\n          <render-run-tags tags= (recorder-run-tags run) />\n        </h4>\n\n        <div class= \"d-flex justify-content-between mt-3 mb-3\">\n          <div class= \"\" style= \"width: 20em\" >\n            <div class= \"input-group\">\n              <span class= \"input-group-text\" >\n                <mdi name= \"search\" />\n              </span>\n              <input class= \"form-control search d-inline-block\" type= \"text\" autocomplete= \"off\"\n                     placeholder= \"Search...\"\n                     data-target= \"#run-page-results\" />\n            </div>\n          </div>\n\n\n        ,(when (auth:can-viewer-edit\n                (auth:viewer-context hunchentoot:*request*)\n                run)\n           <div class= \"\">\n             <comparison-menu run=run />\n             <run-advanced-menu run=run />\n           </div>)\n        </div>\n\n      </div>\n\n      ,@(render-warnings run)\n\n      ,(when-let (reports (reports-for-run run))\n         <div class= \"alert alert-info mt-2\" >\n           This run created a report with\n\n           ,(let ((report (car reports)))\n              <a href= (format nil \"/report/~a\" (oid report)) >,(report-title report)</a>)\n         </div>)\n\n      <div id= \"run-page-results\" class= \"search-results\" data-update= (nibble () (update-content run channel))\n           data-args= \"{}\" >\n        ,(run-page-contents run channel filtered-screenshots)\n      </div>\n\n    </app-template>))\n\n(defun update-content (run channel)\n  (let* ((query (hunchentoot:parameter \"search\")))\n    (run-page-contents\n     run channel\n     (screenshot-map:to-map (run-screenshot-map run))\n     :filter (lambda (screenshot)\n               (or (null query)\n                   (str:contains? query (screenshot-name screenshot)\n                                  :ignore-case t))))))\n\n(defun run-link (run)\n  (make-url 'run-page :id (oid run)))\n\n(defun make-id ()\n  (format nil \"a-~a\" (random 10000000)))\n\n(defclass screenshots-viewer ()\n  ((screenshots :initarg :screenshots\n                :reader screenshots-viewer-screenshots)\n   (filter :initarg :filter\n           :initform #'identity\n           :reader screenshots-viewer-filter)\n   (navigationp :initarg :navigationp\n                :initform t\n                :documentation \"Whether to show the Prev/Next bar\"\n                :reader navigationp)\n   (mapper :initarg :mapper\n           :initform #'identity\n           :reader screenshots-viewer-mapper\n           :documentation \"map the object in the list to get the screenshot\")\n   (modal-id :initform (make-id)\n             :reader modal-id)))\n\n(defmethod filtered-screenshots ((self screenshots-viewer))\n  (let ((screenshots (screenshots-viewer-screenshots self)))\n    (cond\n      ((fset:map? (screenshots-viewer-screenshots self))\n       ;; This will be weakly cached\n       (apply-map-filter screenshots (screenshots-viewer-filter self)))\n      (t\n       (remove-if-not (screenshots-viewer-filter self)\n                      (screenshots-viewer-screenshots self))))))\n\n(defun safe-elt (x i)\n  (cond\n    ((fset:map? x)\n     (multiple-value-bind (key value)\n         (fset:at-rank x i)\n       (make-screenshot :key key :image value)))\n    (t\n     (elt x i))))\n\n(defun safe-length (x)\n  (cond\n    ((fset:map? x)\n     (fset:size x))\n    (t\n     (length x))))\n\n(defmethod render-modal ((self screenshots-viewer))\n  (let ((get-ith-image (nibble (n)\n                         (setf (hunchentoot:content-type*) \"application/json\")\n                         (let ((screenshot (funcall\n                                            (screenshots-viewer-mapper self)\n                                            (safe-elt (filtered-screenshots self)\n                                                      (parse-integer n)))))\n                           (auth:can-view! screenshot)\n                           (json:encode-json-to-string\n                            `((:src . ,(image-public-url\n                                        (screenshot-image screenshot)\n                                        :originalp t))\n                              (:title . ,(screenshot-name screenshot))))))))\n    <div class= \"modal fade single-screenshot-modal\" id= (modal-id self) tabindex= \"-1\" role= \"dialog\"\n         aria-hidden= \"true\" >\n      <div class= \"modal-dialog modal-fullscreen \" role= \"document\">\n        <div class= \"modal-content\">\n          <div class= \"modal-header\">\n            <h5 class= \"modal-title\"></h5>\n            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n          </div>\n\n          <div class= \"modal-body\">\n            <div class= \"modal-body-content\">\n              ,(when (navigationp self)\n                 <div class= \"d-flex justify-content-between mb-2 align-items-center content-header\">\n                   <a href= \"#\" class= \"btn previous\"><mdi name= \"navigate_before\"/>Previous</a>\n                   <span class= \"page-num\" />\n                   <a href= \"#\" class= \"btn next\">Next<mdi name= \"navigate_next\" /></a>\n                 </div>)\n              <div class= \"canvas-container \"\n                   data-length= (safe-length (filtered-screenshots self))\n                   data-transparency= \"true\"\n                   data-src=get-ith-image >\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>))\n\n\n(defun run-page-contents (run channel screenshot-map &key (filter #'identity))\n  (let ((screenshots-viewer (make-instance 'screenshots-viewer\n                                           :screenshots screenshot-map\n                                           :filter filter)))\n   <div id= (make-id) data-company-name= (?. company-name (recorder-run-company run)) >\n     ,(render-modal screenshots-viewer)\n     ,(paginated\n       (lambda (pair i)\n         (destructuring-bind (screenshot-key . image) pair\n          (let* ((screenshot (make-screenshot :image image :key screenshot-key))\n                 (name-parts (str:rsplit \"--\" (screenshot-name screenshot) :limit 2))\n                 (history-url (make-url 'history-page :channel (store-object-id channel)\n                                                      :screenshot-name (screenshot-name screenshot)\n                                                      :branch (recorder-run-branch run))))\n            <div class= \" col-sm-12 col-md-4 col-lg-3 mb-1 mt-2\">\n              <div class=\"card\">\n                <div class=\"card-body\">\n                  <div class= \"screenshot-header\" >\n                    <h4 class= \"screenshot-title\" >,(car name-parts)</h4>\n                    ,(when (cadr name-parts)\n                       <h6>,(cadr name-parts)</h6>)\n                    <ul class= \"screenshot-options-menu\">\n                      <li>\n                        <a href= history-url                           >\n                          History\n                        </a>\n                      </li>\n\n                      <li>\n                        <a href= (nibble () (mask-editor (recorder-run-channel run) screenshot\n                           :redirect (run-link run)))\n                           target= \"_blank\"\n                           >Edit Masks</a>\n\n                      </li>\n\n                      ,(when *create-issue-popup*\n                         <li>\n                           <a target= \"_blank\"\n                              href= (nibble ()\n                                              (funcall *create-issue-popup* run screenshot)) >\n                             Create Issue\n                           </a>\n                         </li>)\n\n                      <li>\n                        <a target= \"_blank\"\n                           href= (image-public-url (screenshot-image screenshot) :originalp t)  >\n                          Download Original\n                        </a>\n\n                      </li>\n\n                      <figma-drop-down script-name= (run-link run) run=run screenshot=screenshot />\n                    </ul>\n                  </div>\n                  <a href= (image-public-url (screenshot-image screenshot) :size :full-page :type \"webp\") title= (screenshot-name screenshot)\n                     class= \"screenshot-run-image\"\n                     data-image-number=i\n                     data-target= (format nil \"#~a\" (modal-id screenshots-viewer))>\n                    ,(let ((dimensions (ignore-errors (image-dimensions (screenshot-image screenshot)))))\n                       <picture class=\"\">\n                         <source srcset= (image-public-url (screenshot-image screenshot) :size :small :type :webp) />\n                         <:img\n                           class= \"screenshot-image run-page-image\"\n                           src= (image-public-url (screenshot-image screenshot)  :size :small\n                                                                               :type :png)\n                           width= (?. dimension-width dimensions)\n                           height= (?. dimension-height dimensions)\n                           />\n                       </picture>)\n                  </a>\n                </div> <!-- end card-body-->\n              </div>\n\n            </div>)))\n       :pass-index-p t\n       :infinite-scroll t\n       :items screenshot-map\n       :filter filter\n       :empty-view  <p class= \"text-muted\" >No screenshots found</p>)\n   </div>))\n\n(defclass js-api-result () ())\n\n(defclass js-api-success (js-api-result)\n  ((success :type boolean\n            :initform t)\n   (was-promoted :type boolean\n                 :initarg :was-promoted\n                 :initform nil)))\n\n(defhandler (run-delete-page :uri \"/runs/:id\" :method :delete) (id)\n  (setf (hunchentoot:content-type*) \"application/json\")\n  (let ((run (find-by-oid id 'recorder-run)))\n    (with-login (:company (recorder-run-company run))\n      (can-view! run)\n      (cond\n        ((or (activep run)\n             (recorder-previous-run run))\n         (log:info \"Can't delete: ~s this run seems to be a master run\" run)\n         (json:encode-json-to-string (make-instance 'js-api-success\n                                                     :was-promoted t)))\n        (t\n         (delete-run run)\n         (json:encode-json-to-string (make-instance 'js-api-success)))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/site-admin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/site-admin\n  (:use #:cl #:alexandria #:nibble)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/model/user\n                #:adminp)\n  (:import-from #:screenshotbot/user-api\n                #:current-user)\n  (:import-from #:screenshotbot/server\n                #:defhandler\n                #:with-login)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/model/core\n                #:generate-api-secret)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page))\n(in-package :screenshotbot/dashboard/site-admin)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun finish-self-promotion (secret-file)\n  (cond\n    ((path:-e secret-file)\n     (delete-file secret-file)\n     (with-transaction ()\n       (setf (adminp (current-user)) t))\n     <app-template>\n       You're now a site admin!\n     </app-template>)\n    (t\n     <app-template>\n       <p> Couldn't find the file ,(progn (namestring secret-file))</p>\n       <a class= \"btn btn-secondary\" href= (nibble () (%self-promotion :secret-file secret-file)) >\n         Try Again\n       </a>\n     </app-template>\n       )))\n\n(defun %self-promotion (&key secret-file)\n  (with-login ()\n   (let* ((secret (generate-api-secret))\n          (secret-file (or secret-file\n                           (path:catfile\n                            (asdf:system-relative-pathname :screenshotbot \"../../\")\n                            (format nil \"ADMIN-PRIVILEGES-~a\" secret)))))\n     <simple-card-page>\n\n       <div class= \"card-header\">\n         <h4>Become a Site-Admin</h4>\n       </div>\n\n       <p>\n         As the user <tt>,(uiop:getenv \"USER\")</tt>, run the following command:\n       </p>\n\n       <div class= \"mt-4 mb-4\">\n         <code >\n           touch ,(progn secret-file)\n         </code>\n       </div>\n\n       <p class= \"mt-2\" >\n         Once you have done that hit continue.\n       </p>\n\n       <div class= \"card-footer\">\n         <a href= (nibble () (finish-self-promotion secret-file)) class= \"btn btn-primary\" >\n           Continue\n         </a>\n       </div>\n\n     </simple-card-page>)))\n\n(defhandler (nil :uri \"/site-admin/self-promotion\") ()\n  (%self-promotion))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-api-keys.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-api-keys\n  (:use :cl)\n  (:import-from #:cl-mock\n                #:answer\n                #:if-called)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:core/ui/template\n                #:*app-template*)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/dashboard/api-keys\n                #:%render-api-key\n                #:%create-api-key\n                #:%api-key-page\n                #:api-key-cli-generate\n                #:with-description)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/model/api-key\n                #:render-api-token)\n  (:import-from #:screenshotbot/server\n                #:screenshotbot-template)\n  (:import-from #:screenshotbot/testing\n                #:fix-timestamps\n                #:screenshot-test\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:test-acceptor\n                #:screenshot-static-page\n                #:with-fake-request)\n  (:import-from #:core/api/acceptor\n                #:api-token-mode-p\n                #:api-acceptor-mixin)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key\n                #:api-key-secret-key\n                #:api-key-key)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:screenshotbot/model/company\n                #:company))\n(in-package :screenshotbot/dashboard/test-api-keys)\n\n\n(util/fiveam:def-suite)\n\n(defclass my-acceptor (api-acceptor-mixin\n                       test-acceptor)\n  ())\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(def-fixture state ()\n  (let* ((*installation* (make-instance 'installation))\n         (*app-template* (make-instance 'screenshotbot-template)))\n    (cl-mock:with-mocks ()\n      (with-test-store ()\n       (&body)))))\n\n\n(test simple-page-test\n  (with-fixture state ()\n    (with-fake-request (:acceptor 'my-acceptor)\n      (answer (auth:can-view! nil))\n      (auth:with-sessions ()\n        (let* ((test-user (make-instance 'user\n                                         :full-name \"Arnold Noronha\"))\n               (test-company (make-instance 'company))\n               (test-api-keys (list (make-instance 'api-key\n                                                  :api-key \"foo\"\n                                                  :company test-company\n                                                  :api-secret-key \"sdfsdfdfdfs\"\n                                                  :user test-user)))\n               (content (markup:write-html\n                         (fix-timestamps\n                          (%api-key-page :user test-user\n                                         :company test-company)))))\n          (screenshot-static-page\n           :screenshotbot\n           \"api-key-page\"\n           content))))))\n\n(test empty-api-keys-page-test\n  (with-fixture state ()\n    (with-fake-request (:acceptor 'my-acceptor)\n      (answer (auth:can-view! nil))\n      (auth:with-sessions ()\n       (screenshot-static-page\n        :screenshotbot\n        \"api-key-page-empty\"\n        (markup:write-html\n         (%api-key-page :user (make-instance 'user)\n                        :company (make-instance 'company))))))))\n\n(screenshot-test api-key-page-description-page\n  (with-installation ()\n   (with-fake-request ()\n     (auth:with-sessions ()\n       (with-description (description)\n         (values))))))\n\n(screenshot-test cli-generation-page\n  (with-installation ()\n    (with-test-store ()\n     (with-fake-request ()\n       (with-test-user (:logged-in-p t)\n         (auth:with-sessions ()\n           (cl-mock:with-mocks ()\n             (if-called 'render-api-token\n                        (lambda (key)\n                          \"cli-0000:xyzd\"))\n             (api-key-cli-generate))))))))\n\n\n(screenshot-test api-key-rendered-page\n  (with-installation ()\n    (with-test-store ()\n      (cl-mock:with-mocks ()\n       (with-fake-request ()\n         (cl-mock:if-called 'api-token-mode-p\n                            (lambda (acceptor)\n                              nil))\n         (let ((api-key (make-instance 'api-key\n                                       :api-key \"asdfsdfdsfdsf\"\n                                       :api-secret-key \"df23rsdf23rsdfsdfsdfdsfsdfsdfsd\")))\n           (%render-api-key api-key)))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-batch\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-item\n                #:batch)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:screenshotbot/dashboard/batch\n                #:batch-item-link\n                #:render-batch)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/report-api\n                #:report))\n(in-package :screenshotbot/dashboard/test-batch)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-installation ()\n     (with-fake-request ()\n       (let* ((company (make-instance 'company))\n              (batch (make-instance 'batch))\n              (run (make-recorder-run))\n              (report (make-instance 'report)))\n         (&body))))))\n\n(screenshot-test batch-item-empty-view\n  (with-fixture state ()\n    (render-batch batch)))\n\n(screenshot-test batch-item-with-a-single-batches\n  (with-fixture state ()\n    (let ((channel (make-instance 'channel\n                                  :company company\n                                  :name \"//foo:bar\")))\n      (make-instance 'batch-item\n                     :run run\n                     :status :accepted\n                     :channel channel\n                     :batch batch)\n      (render-batch batch))))\n\n(screenshot-test batch-item-with-few-batches\n  (with-fixture state ()\n    (flet ((mc (name)\n             (make-instance 'channel\n                            :company company\n                            :name name)))\n     (make-instance 'batch-item\n                    :run run\n                    :status :accepted\n                    :channel (mc \"//one\")\n                    :batch batch)\n      (make-instance 'batch-item\n                     :run run\n                     :status :rejected\n                     :channel (mc \"//two\")\n                     :batch batch)\n      (make-instance 'batch-item\n                     :run run\n                     :status :action-required\n                     :channel (mc \"//three\")\n                     :batch batch)\n      (make-instance 'batch-item\n                     :run run\n                     :status :action-required\n                     :channel (mc \"//four\")\n                     :batch batch)\n      (make-instance 'batch-item\n                     :run run\n                     :status :success\n                     :channel (mc \"//five\")\n                     :batch batch)\n      (render-batch batch))))\n\n\n(test batch-item-link\n  (with-fixture state ()\n    (is (not (str:emptyp\n              (batch-item-link\n               (make-instance 'batch-item\n                              :run run)))))\n    (is (not (str:emptyp\n              (batch-item-link\n               (make-instance 'batch-item\n                              :run report)))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-bisect.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-bisect\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/testing\n                #:fix-timestamps\n                #:snap-all-images\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/dashboard/bisect\n                #:render-bisection\n                #:bisect-item\n                #:state)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:easy-macros\n                #:def-easy-macro))\n(in-package :screenshotbot/dashboard/test-bisect)\n\n(util/fiveam:def-suite)\n\n(defun %make-image (x)\n  (make-image\n   :pathname (path:catfile\n              #.(asdf:system-relative-pathname\n                 :screenshotbot \"fixture/\")\n              x)))\n\n(def-easy-macro wrap-snapshot (&fn fn)\n  (snap-all-images)\n  (fix-timestamps (fn)))\n\n(def-fixture test-state ()\n  (with-installation ()\n    (with-test-store ()\n      (with-fake-request ()\n        (auth:with-sessions ()\n         (let* ((im-1 (%make-image \"wizard.png\"))\n                (screenshot-1 (make-screenshot :image im-1\n                                               :name \"foo\"))\n                (channel (make-instance 'channel))\n                (run-1 (make-recorder-run\n                        :channel channel\n                        :screenshots (list screenshot-1)))\n                (run-2 (make-recorder-run\n                        :channel channel\n                        :screenshots (list screenshot-1))))\n           (&body)))))))\n\n(screenshot-test bisect-with-user-interaction-required\n  (with-fixture test-state ()\n    (wrap-snapshot ()\n     (render-bisection\n      (make-instance 'state\n                     :items (loop for i from 0 to 5\n                                  collect\n                                  (make-instance 'bisect-item\n                                                 :screenshot screenshot-1\n                                                 :run run-1)))))))\n\n(screenshot-test bisect-end\n  (with-fixture test-state ()\n    (wrap-snapshot ()\n     (render-bisection\n      (make-instance 'state\n                     :items (loop for i from 0 to 1\n                                  collect\n                                  (make-instance 'bisect-item\n                                                 :screenshot screenshot-1\n                                                 :run run-1)))))))\n\n(screenshot-test bisect-with-wide-image\n  (with-fixture test-state ()\n    (let* ((wide-im (%make-image \"wide.png\"))\n           (screenshot (make-screenshot :image wide-im\n                                        :name \"foo\"))\n           (run (make-recorder-run\n                 :channel channel\n                 :screenshots (list screenshot))))\n      (wrap-snapshot ()\n        (render-bisection\n         (make-instance 'state\n                        :items (loop for i from 0 to 5\n                                     collect\n                                     (make-instance 'bisect-item\n                                                    :screenshot screenshot\n                                                    :run run))))))))\n\n(screenshot-test bisect-with-large-square\n  (with-fixture test-state ()\n    (let* ((wide-im (%make-image \"large-square.png\"))\n           (screenshot (make-screenshot :image wide-im\n                                        :name \"foo\"))\n           (run (make-recorder-run\n                 :channel channel\n                 :screenshots (list screenshot))))\n      (wrap-snapshot ()\n        (render-bisection\n         (make-instance 'state\n                        :items (loop for i from 0 to 5\n                                     collect\n                                     (make-instance 'bisect-item\n                                                    :screenshot screenshot\n                                                    :run run))))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-channels.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/dashboard/test-channels\n    (:use #:cl\n          #:alexandria\n          #:screenshotbot/user-api\n          #:fiveam)\n  (:import-from #:screenshotbot/dashboard/channels\n                #:badge-data\n                #:badge-handler\n                #:confirm-delete\n                #:channel-deleted-confirmation\n                #:perform-delete\n                #:single-channel-view\n                #:run-for-channel\n                #:%list-projects)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:company)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-subscribers)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/misc\n                #:is-not-null)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:runs-for-company\n                #:make-recorder-run\n                #:active-run\n                #:recorder-run)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/user-api\n                #:channel\n                #:user\n                #:user-image-url\n                #:can-view!\n                #:can-view)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:fix-timestamps\n                #:screenshot-test)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:with-test-store))\n(in-package :screenshotbot/dashboard/test-channels)\n\n(util/fiveam:def-suite)\n\n(defclass company-with-channels (company)\n  ()\n  (:metaclass persistent-class))\n\n(defmethod company-channels ((company company-with-channels))\n  (list (make-instance 'channel :name \"Foobar\")))\n\n(test simple-view\n  (with-test-store ()\n   (with-installation ()\n     (cl-mock:with-mocks ()\n       (let ((user (make-instance 'user))\n             (company (make-instance 'company-with-channels :name \"Foobar\")))\n         (answer (auth:can-view! nil))\n         (finishes\n           (%list-projects :user user\n                           :company company)))))))\n\n(def-fixture state ()\n  (with-installation ()\n   (cl-mock:with-mocks ()\n     (with-test-store ()\n       (let* ((company (make-instance 'company :name \"Dummy company\"))\n              (channel (find-or-create-channel company \"foobar\"))\n              (user (make-user :companies (list company)))\n              (run (make-recorder-run\n                    :channel channel\n                    :company company))\n              (channel-2 (find-or-create-channel company \"foobar-2\")))\n         (setf (active-run channel \"master\") run)\n         (cl-mock:if-called 'can-view!\n                            (lambda (x) t))\n         (with-fake-request ()\n           (auth:with-sessions ()\n             (&body))))))))\n\n(test run-for-channel\n  (with-fixture state ()\n    (assert-that\n     (run-for-channel :channel \"foobar\"\n                      :company (oid company)\n                      :branch \"master\")\n     (is-not-null))))\n\n(screenshot-test channel-page\n  (with-fixture state ()\n    (cl-mock:if-called 'oid\n                       (lambda (arg)\n                         \"deadbeef\"))\n    (fix-timestamps\n     (single-channel-view channel))))\n\n(screenshot-test channel-page-with-subscribers\n  (with-fixture state ()\n    (let ((subscriber-1 (make-user :email \"subscriber1@example.com\"\n                                   :full-name \"Alice Johnson\"\n                                   :companies (list company)))\n          (subscriber-2 (make-user :email \"subscriber2@example.com\"\n                                   :full-name \"Bob Smith\"\n                                   :companies (list company))))\n      (bknr.datastore:with-transaction ()\n        (pushnew subscriber-1 (channel-subscribers channel))\n        (pushnew subscriber-2 (channel-subscribers channel)))\n      (cl-mock:if-called 'oid\n                         (lambda (arg)\n                           \"deadbeef\"))\n      (cl-mock:if-called 'user-image-url\n                         (lambda (user)\n                           \"https://example.com/avatar.png\"))\n      (fix-timestamps\n       (single-channel-view channel)))))\n\n(test delete-channel\n  (with-fixture state ()\n    (perform-delete channel)\n    (is (fset:empty? (runs-for-company company)))))\n\n(screenshot-test channel-deleted-confirmation\n  (with-fixture state ()\n    (channel-deleted-confirmation)))\n\n(screenshot-test confirm-channel-deletion\n  (with-fixture state ()\n    (confirm-delete channel)))\n\n(screenshot-test list-of-channels\n  (with-fixture state ()\n    (fix-timestamps\n     (%list-projects :user user :company company))))\n\n(test badge-handler-happy-path\n  (with-fixture state ()\n    (if-called\n     'badge-data (lambda (&key label message color)\n                   message))\n    (is\n     (equal \"0 screenshots\"\n      (badge-handler :org (oid company)\n                     :channel (channel-name channel)\n                     :branch \"master\")))))\n\n(test badge-handler-when-branch-doesnt-exist\n  (with-fixture state ()\n    (if-called\n     'badge-data (lambda (&key label message color)\n                   message))\n    (is\n     (equal \"No active run for parameters\"\n      (badge-handler :org (oid company)\n                     :channel (channel-name channel)\n                     :branch \"fake-branch\")))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-commit-graph\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:dag\n                #:add-commit\n                #:commit\n                #:dag\n                #:ordered-commits)\n  (:import-from #:screenshotbot/dashboard/commit-graph\n                #:format-graph)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:has-length))\n(in-package :screenshotbot/dashboard/test-commit-graph)\n\n(util/fiveam:def-suite)\n\n(defun add-edge (dag from to)\n  \"Add a commit with parents to the DAG.\"\n  (add-commit dag (make-instance 'commit\n                                 :sha from\n                                 :parents (cond\n                                            ((null to) nil)\n                                            ((listp to) to)\n                                            (t (list to))))))\n\n(defun graph-max-width (graph-str)\n  \"Calculate the maximum graph width (columns) from output.\n   Each column is 2 chars wide (char + space).\"\n  (let ((max-width 0))\n    (loop for line in (str:split #\\Newline graph-str)\n          do (let* ((graph-part (subseq line 0 (or (position #\\Space line\n                                                            :start (position-if-not\n                                                                    (lambda (c)\n                                                                      (member c '(#\\* #\\| #\\/ #\\\\ #\\Space)))\n                                                                    line))\n                                                   (length line))))\n                    ;; Count actual column characters (skip trailing spaces)\n                    (trimmed (string-right-trim '(#\\Space) graph-part))\n                    (width (ceiling (length trimmed) 2)))\n               (setf max-width (max max-width width))))\n    max-width))\n\n(defun count-graph-columns (graph-str)\n  \"Count the max number of active columns in the graph output.\"\n  (let ((max-cols 0))\n    (loop for line in (str:split #\\Newline graph-str)\n          when (> (length line) 0)\n          do (let ((cols 0))\n               ;; Count column markers: * | / \\\n               (loop for i from 0 below (length line) by 2\n                     for c = (char line i)\n                     while (member c '(#\\* #\\| #\\/ #\\\\  #\\Space))\n                     when (member c '(#\\* #\\| #\\/ #\\\\))\n                     do (incf cols))\n               (setf max-cols (max max-cols cols))))\n    max-cols))\n\n(test linear-history-uses-one-column\n  \"A simple linear history should only use 1 column.\"\n  (let ((dag (make-instance 'dag)))\n    (add-edge dag \"aa\" nil)\n    (add-edge dag \"bb\" \"aa\")\n    (add-edge dag \"cc\" \"bb\")\n    (add-edge dag \"dd\" \"cc\")\n    (let* ((commits (ordered-commits dag))\n           (output (format-graph commits)))\n      (is (= 1 (count-graph-columns output))\n          \"Linear history should use exactly 1 column, got output:~%~a\" output))))\n\n(test simple-branch-merge-uses-two-columns\n  \"A branch that merges back should use at most 2 columns.\"\n  (let ((dag (make-instance 'dag)))\n    ;; Create:  dd\n    ;;         /  \\\n    ;;        bb  cc\n    ;;         \\  /\n    ;;          aa\n    (add-edge dag \"aa\" nil)\n    (add-edge dag \"bb\" \"aa\")\n    (add-edge dag \"cc\" \"aa\")\n    (add-edge dag \"dd\" (list \"bb\" \"cc\"))\n    (let* ((commits (ordered-commits dag))\n           (output (format-graph commits)))\n      (is (<= (count-graph-columns output) 2)\n          \"Branch+merge should use at most 2 columns, got output:~%~a\" output))))\n\n(test lanes-are-reused-after-merge\n  \"After a branch merges, its lane should be reused for new branches.\"\n  (let ((dag (make-instance 'dag)))\n    ;; Create multiple sequential merges that should reuse columns:\n    ;;   ff\n    ;;   |\n    ;;   ee (merge bb+cc)\n    ;;  /|\n    ;; bb cc\n    ;;  \\|\n    ;;   dd (merge)\n    ;;   |\n    ;;   aa\n    (add-edge dag \"aa\" nil)\n    (add-edge dag \"bb\" \"aa\")\n    (add-edge dag \"cc\" \"aa\")\n    (add-edge dag \"dd\" (list \"bb\" \"cc\"))  ; First merge\n    (add-edge dag \"ee\" \"dd\")\n    (add-edge dag \"ff\" \"ee\")\n    (let* ((commits (ordered-commits dag))\n           (output (format-graph commits)))\n      ;; After the merge at dd, we should be back to 1 column\n      ;; The later commits ee, ff should not expand to more columns\n      (is (<= (count-graph-columns output) 2)\n          \"Lanes should be reused after merge, got output:~%~a\" output))))\n\n(test multiple-merges-reuse-lanes\n  \"Multiple sequential merges should reuse lanes, not keep adding new ones.\"\n  (let ((dag (make-instance 'dag)))\n    ;; aa (root)\n    ;; bb <- aa\n    ;; cc <- aa  (branch)\n    ;; dd <- bb, cc (merge 1)\n    ;; ee <- dd\n    ;; ff <- dd  (branch)\n    ;; gg <- ee, ff (merge 2)\n    (add-edge dag \"aa\" nil)\n    (add-edge dag \"bb\" \"aa\")\n    (add-edge dag \"cc\" \"aa\")\n    (add-edge dag \"dd\" (list \"bb\" \"cc\"))\n    (add-edge dag \"ee\" \"dd\")\n    (add-edge dag \"ff\" \"dd\")\n    (add-edge dag \"gg\" (list \"ee\" \"ff\"))\n    (let* ((commits (ordered-commits dag))\n           (output (format-graph commits)))\n      ;; With proper lane reuse, should never need more than 2 columns\n      (is (<= (count-graph-columns output) 2)\n          \"Multiple merges should reuse lanes, got output:~%~a\" output))))\n\n(test print-graph-output\n  \"Helper test to visualize graph output.\"\n  (let ((dag (make-instance 'dag)))\n    (add-edge dag \"aa\" nil)\n    (add-edge dag \"bb\" \"aa\")\n    (add-edge dag \"cc\" \"aa\")\n    (add-edge dag \"dd\" (list \"bb\" \"cc\"))\n    (add-edge dag \"ee\" \"dd\")\n    (add-edge dag \"ff\" \"dd\")\n    (add-edge dag \"gg\" (list \"ee\" \"ff\"))\n    (let* ((commits (ordered-commits dag))\n           (output (format-graph commits)))\n      (format t \"~%Graph output:~%~a~%\" output)\n      (format t \"Max columns: ~a~%\" (count-graph-columns output))\n      (pass))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-compare-branches.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-compare-branches\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:screenshotbot/dashboard/compare-branches\n                #:resolve-commits\n                #:%post\n                #:%perform\n                #:%form)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run))\n(in-package :screenshotbot/dashboard/test-compare-branches)\n\n\n(util/fiveam:def-suite)\n\n\n(def-fixture state ()\n  (with-test-store ()\n   (cl-mock:with-mocks ()\n     (with-installation ()\n       (with-test-user (:company company\n                        :user user\n                        :logged-in-p t)\n         (auth:with-sessions ()\n           (&body)))))))\n\n(screenshot-test compare-branches-form\n  (with-fixture state ()\n    (%form)))\n\n\n(test %post-happy-path-without-result\n  (with-fixture state ()\n    (answer (%perform :sha1 \"ab\" :sha2 \"cd\" :repo \"foo\")\n      \"hello\")\n    (answer (resolve-commits company \"ab\" :repo \"foo\") (list :run1))\n    (answer (resolve-commits company \"cd\" :repo \"foo\") (list :run2))    \n    (is\n     (equal \"hello\"\n            (%post :sha1 \"ab\" :sha2 \"cd\" :repo \"foo\")))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-compare.lisp",
    "content": "(defpackage :screenshotbot/dashboard/test-compare\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:info-modal\n                #:sort-pixels\n                #:pixel-str-to-components\n                #:%render-commits-path\n                #:find-commit-path-to-ancestor\n                #:render-diff-report\n                #:render-single-change-permalink\n                #:metrics-page\n                #:warmup-comparison-images-sync\n                #:link-to-run\n                #:random-non-alpha-px\n                #:image-comparison-job\n                #:prepare-image-comparison\n                #:random-zoom-to-on-result\n                #:image-comparison)\n  (:import-from #:screenshotbot/model/image\n                #:make-image\n                #:image\n                #:local-image)\n  (:import-from #:bknr.datastore\n                #:store-object-id\n                #:blob-pathname)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:screenshot-static-page\n                #:with-fake-request)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/testing\n                #:screenshot-test\n                #:snap-all-images\n                #:snap-image-blob\n                #:with-test-user)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:render-report-page)\n  (:import-from #:screenshotbot/model/channel\n                #:channel)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:fiveam-matchers\n                #:is-string\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:with-pixel-wand\n                #:pixel-set-color\n                #:magick-new-image\n                #:with-wand)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:screenshotbot/diff-report\n                #:make-diff-report)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:screenshot-key)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/model/figma\n                #:update-figma-link)\n  (:import-from #:screenshotbot/model/report\n                #:base-acceptable)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/user-api\n                #:channel-repo)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo\n                #:commit-graph\n                #:commit-graph-dag)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:merge-dag-into-commit-graph\n                #:find-or-create-commit-graph)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/dashboard/test-compare)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'installation)))\n   (with-test-store ()\n     (tmpdir:with-tmpdir (dir)\n       (with-fake-request ()\n         (let ((im1 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\"))\n               (im2 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-2.png\"))\n               (im3 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-3.png\"))\n               (objs))\n           (flet ((make-screenshot (img &key (name \"foobar\"))\n                    (let* ((image (make-image :pathname img :for-tests t)))\n                      (make-screenshot\n                       :name name\n                       :image image))))\n             (&body))))))))\n\n\n(test random-zoom-to-on-result\n  (with-fixture state ()\n    (let ((imc (make-instance 'image-comparison\n                               :result (make-screenshot im1))))\n      (finishes (random-zoom-to-on-result imc nil))\n      (let ((res\n              (json:decode-json-from-string (random-zoom-to-on-result imc nil))))\n        (is (eql 360 (a:assoc-value res :width)))\n        (is (eql 360 (a:assoc-value res :height)))\n        (is (< -1 (a:assoc-value res :x) 360))\n        (is (< -1 (a:assoc-value res :y) 360))))))\n\n(test metrics-page-happy-path\n  \"Since this uses FLI, we need to be extra careful about testing it.\"\n  (with-fixture state ()\n    (let ((imc (make-instance 'image-comparison\n                              :before (make-image :pathname im1 :for-tests t)\n                              :after (make-image :pathname im2 :for-tests t)\n                              :result (make-screenshot im1))))\n      (finishes (metrics-page imc nil)))))\n\n(test random-non-alpha-for-no-alphas\n  (with-fixture state ()\n    (with-wand (wand)\n      (with-pixel-wand (pw)\n        (pixel-set-color pw \"none\")\n        (magick-new-image wand 10 10 pw)\n        (multiple-value-bind (x y) (random-non-alpha-px wand nil)\n          (is (eql -1 x))\n          (is (eql -1 y)))))))\n\n(def-fixture single-change ()\n  (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im1))))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2))))\n             (report (make-instance 'report\n                                    :title \"foobar\"\n                                    :run one\n                                    :previous-run two)))\n        (&body))))\n\n(test report-screenshot-test\n  (with-fixture state ()\n    (with-fixture single-change ()\n      (snap-all-images)\n      (screenshot-static-page\n       :screenshotbot\n       \"report-page\"\n       (render-report-page report :skip-access-checks t)))))\n\n(screenshot-test report-change-but-with-figma\n  (with-fixture state ()\n    (with-fixture single-change ()\n      (update-figma-link :channel channel \n                   :screenshot-name \"foobar\"\n                   :url \"https://www.figma.com/file/ABC123/Screenshotbot-Report-UI?node-id=1%3A2\"\n                   :image (make-image :pathname im1 :for-tests t))\n      (snap-all-images)\n      (render-report-page report :skip-access-checks t))))\n\n(test report-screenshot-test-with-one-screenshot-added\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im1))))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2 :name \"renamed-screenshot\"))))\n             (report (make-instance 'report\n                                    :title \"foobar\"\n                                    :run one\n                                    :previous-run two)))\n        (snap-all-images)\n        (screenshot-static-page\n         :screenshotbot\n         \"report-page-with-one-screenshot-added\"\n         (render-report-page report :skip-access-checks t))))))\n\n\n(test report-with-only-added-screenshots\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots nil))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2))))\n             (report (make-instance 'report\n                                    :title \"foobar\"\n                                    :run one\n                                    :previous-run two)))\n        (snap-all-images)\n        (screenshot-static-page\n         :screenshotbot\n         \"report-page-only-added-screenshots\"\n         (render-report-page report :skip-access-checks t))))))\n\n(test prepare-image-comparison\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let ((job (make-instance 'image-comparison-job\n                                :before-image (make-screenshot im1)\n                                :after-image (make-screenshot im2))))\n\n        (let ((image-comparison (prepare-image-comparison job)))\n          (assert-that image-comparison\n                       (described-as \"Expected to get a json response\"\n                           (is-string))))))))\n\n(test link-to-empty-run\n  (assert-that (format nil \"~a\" (link-to-run :run nil))\n               (contains-string \"empty run\")))\n\n(test warmup-comparison-images\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :screenshots (list (make-screenshot im1))))\n          (to (make-recorder-run\n               :screenshots (list (make-screenshot im2)))))\n      (finishes\n       (warmup-comparison-images-sync run to)))))\n\n(screenshot-test single-change-permalink\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im1))))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2)))))\n        (snap-all-images)\n        (app-template\n         :body-class \"dashboard\"\n         (render-single-change-permalink\n          (make-diff-report one two)\n          (store-object-id (screenshot-key (make-screenshot im1)))\n          \"/report/dfdfd\"\n          :run one))))))\n\n(screenshot-test single-added--permalink\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots nil))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2)))))\n        (snap-all-images)\n        (app-template\n         :body-class \"dashboard\"\n         (render-single-change-permalink\n          (make-diff-report two one)\n          (store-object-id (screenshot-key (make-screenshot im2)))\n          \"/report/dfdfd\"\n          :run one))))))\n\n(screenshot-test comparison-with-acceptable-review-button\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im1))))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2))))\n             (report (make-instance 'report\n                                    :title \"foobar\"\n                                    :channel channel\n                                    :run one\n                                    :previous-run two))\n             (acceptable (make-instance 'base-acceptable\n                                        :report report)))\n        (snap-all-images)\n        (app-template\n         :body-class \"dashboard\"\n         (render-diff-report\n          :diff-report (make-diff-report one two)\n          :acceptable acceptable\n          :script-name \"/report/test\"))))))\n\n\n(screenshot-test comparison-with-acceptable-review-button-with-review-button-clicked\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"bleh\"\n                                     :github-repo \"git@github.com:a/b.gitq\"))\n             (one (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im1))))\n             (two (make-recorder-run\n                   :channel channel\n                   :company company\n                   :screenshots (list (make-screenshot im2))))\n             (report (make-instance 'report\n                                    :title \"foobar\"\n                                    :channel channel\n                                    :run one\n                                    :previous-run two))\n             (acceptable (make-instance 'base-acceptable\n                                        :report report)))\n        (snap-all-images)\n        (expand-review-button\n         (app-template\n          :body-class \"dashboard\"\n          (render-diff-report\n           :diff-report (make-diff-report one two)\n           :acceptable acceptable\n           :script-name \"/report/test\")))))))\n\n(defun expand-review-button (html)\n  (mquery:with-document (html)\n    (let ((dropdown-button (mquery:$ \"[id='reviewDropdownMenuButton']\")))\n      (assert-that dropdown-button (has-length 1))\n      (mquery:add-class dropdown-button \"show\")\n      (let ((dropdown-menu (mquery:$ \".review-dropdown-menu\")))\n        (assert-that dropdown-menu (has-length 1))\n        (mquery:add-class dropdown-menu \"show\")))))\n\n(screenshot-test review-button-dropdown-when-not-logged-in\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p nil)\n      (with-fake-request ()\n        (auth:with-sessions ()\n          (let* ((channel (make-instance 'channel\n                                         :company company\n                                         :name \"bleh\"\n                                         :github-repo \"git@github.com:a/b.gitq\"))\n                 (one (make-recorder-run\n                       :channel channel\n                       :company company\n                       :screenshots (list (make-screenshot im1))))\n                 (two (make-recorder-run\n                       :channel channel\n                       :company company\n                       :screenshots (list (make-screenshot im2))))\n                 (report (make-instance 'report\n                                        :title \"foobar\"\n                                        :channel channel\n                                        :run one\n                                        :previous-run two))\n                 (acceptable (make-instance 'base-acceptable\n                                            :report report)))\n            (snap-all-images)\n            (expand-review-button\n             (app-template\n              :body-class \"dashboard\"\n              (render-diff-report\n               :diff-report (make-diff-report one two)\n               :acceptable acceptable\n               :script-name \"/report/test\")))))))))\n\n(test find-commit-path-to-ancestor-happy-path\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((repo-url \"https://github.com/test/repo\")\n             ;; Create commit hashes (40 hex chars)\n             (commit-a \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")\n             (commit-b \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\")\n             (commit-c \"cccccccccccccccccccccccccccccccccccccccc\")\n             ;; Set up the commit graph with a DAG: A -> B -> C\n             (cg (find-or-create-commit-graph company repo-url))\n             (dag (make-instance 'dag:dag)))\n        ;; Add commits: A has parent B, B has parent C, C is root\n        (dag:add-commit dag (make-instance 'dag:commit\n                                           :sha commit-c\n                                           :parents nil))\n        (dag:add-commit dag (make-instance 'dag:commit\n                                           :sha commit-b\n                                           :parents (list commit-c)))\n        (dag:add-commit dag (make-instance 'dag:commit\n                                           :sha commit-a\n                                           :parents (list commit-b)))\n        (merge-dag-into-commit-graph cg dag)\n        ;; Create channel with the repo URL\n        (let* ((channel (make-instance 'channel\n                                       :company company\n                                       :name \"test-channel\"\n                                       :github-repo repo-url))\n               ;; Create runs with commits\n               (run (make-recorder-run\n                     :channel channel\n                     :company company\n                     :commit-hash commit-a))\n               (to (make-recorder-run\n                    :channel channel\n                    :company company\n                    :commit-hash commit-c)))\n          ;; Test find-commit-path-to-ancestor\n          (multiple-value-bind (path this-hash prev-hash)\n              (find-commit-path-to-ancestor run to)\n            (is (equal (list commit-a commit-b commit-c) path))\n            (is (equal commit-a this-hash))\n            (is (equal commit-c prev-hash))))))))\n\n(test find-commit-path-to-ancestor-when-no-commit-hashes\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      ;; Create channel with the repo URL\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"test-channel\"))\n             ;; Create runs with commits\n             (run (make-recorder-run\n                   :channel channel\n                   :company company))\n             (to (make-recorder-run\n                  :channel channel\n                  :company company)))\n        ;; Test find-commit-path-to-ancestor\n        (multiple-value-bind (path this-hash prev-hash)\n            (find-commit-path-to-ancestor run to)\n          (is (equal nil path)))))))\n\n(test find-commit-path-when-{to}-does-not-exist\n  (with-fixture state ()\n    (with-test-user (:user user\n                     :company company\n                     :logged-in-p t)\n      (let* ((repo-url \"https://github.com/test/repo\"))\n        ;; Create channel with the repo URL\n        (let* ((channel (make-instance 'channel\n                                       :company company\n                                       :name \"test-channel\"\n                                       :github-repo repo-url))\n               ;; Create runs with commits\n               (run (make-recorder-run\n                     :channel channel\n                     :company company)))\n          ;; Test find-commit-path-to-ancestor\n          (multiple-value-bind (path this-hash prev-hash)\n              (find-commit-path-to-ancestor run nil)\n            (is (equal nil path))))))))\n\n(screenshot-test render-commits-path\n  (with-fixture state ()\n   (let ((repo (make-instance 'generic-git-repo :link \"https://github.com/tdrhq/fast-example\"))\n         (commit-a \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")\n         (commit-b \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\")\n         (commit-c \"cccccccccccccccccccccccccccccccccccccccc\"))\n     (%render-commits-path\n      repo\n      (list commit-a commit-b commit-c)\n      commit-a\n      commit-c))))\n\n(screenshot-test could-not-find-commit-path\n  (with-fixture state ()\n   (let ((repo (make-instance 'generic-git-repo :link \"https://github.com/tdrhq/fast-example\"))\n         (commit-a \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")\n         (commit-c \"cccccccccccccccccccccccccccccccccccccccc\"))\n     (%render-commits-path\n      repo\n      nil\n      commit-a\n      commit-c))))\n\n\n\n\n(test pixel-str-to-components\n  (is (equal '(10 20 30)\n             (pixel-str-to-components\n              \"srgb(10,20,30)\"))))\n\n(test sort-pixels\n  (is\n   (equalp\n    '(2 0 1)\n    (sort-pixels\n     #2A((1 2) (3 4) (5 6))\n     '(\"srgb(243,246,248)\"\n       \"srgb(242,244,247)\"\n       \"srgb(242,245,248)\")\n     '(\"srgb(243,245,248)\"\n       \"srgb(242,245,247)\"\n       \"srgb(242,245,246)\")))))\n\n(test info-modal-when-theres-no-TO-provided\n  (with-fixture state ()\n   (with-fixture single-change ()\n     (finishes\n       (info-modal one nil)))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-dashboard.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-dashboard\n  (:use #:cl\n        #:alexandria\n        #:fiveam\n        #:screenshotbot/dashboard/dashboard)\n  (:import-from #:screenshotbot/testing\n                #:fix-timestamps\n                #:with-installation\n                #:screenshot-test\n                #:with-test-user)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/channel\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/user\n                #:user)\n  (:import-from #:screenshotbot/model/report\n                #:report\n                #:base-acceptable\n                #:acceptable-history\n                #:acceptable-history-item-state\n                #:acceptable-history-item-user\n                #:acceptable-history-item-ts\n                #:acceptable-history-item)\n  (:import-from #:bknr.datastore\n                #:with-transaction\n                #:make-instance)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/dashboard/dashboard\n                #:activity-feed\n                #:get-recent-activity))\n(in-package :screenshotbot/dashboard/test-dashboard)\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun create-test-activity-data (company)\n  \"Create test acceptables with history for the activity feed\"\n  (let* ((channel1 (make-instance 'channel :name \"web-frontend\" :company company))\n         (channel2 (make-instance 'channel :name \"mobile-app\" :company company))\n         (user1 (make-instance 'user \n                               :full-name \"John Doe\"\n                               :email \"john@example.com\"))\n         (user2 (make-instance 'user \n                               :full-name \"Jane Smith\" \n                               :email \"jane@example.com\"))\n         (run1 (make-recorder-run :channel channel1 \n                                  :pull-request \"https://github.com/example/repo/pull/123\"))\n         (run2 (make-recorder-run :channel channel2 \n                                  :pull-request \"https://github.com/example/mobile/pull/456\"))\n         (run3 (make-recorder-run :channel channel1))\n         (report1 (make-instance 'report :run run1 :channel channel1))\n         (report2 (make-instance 'report :run run2 :channel channel2))\n         (report3 (make-instance 'report :run run3 :channel channel1)))\n    \n    ;; Create acceptables with history\n    (let ((acceptable1 (make-instance 'base-acceptable :report report1))\n          (acceptable2 (make-instance 'base-acceptable :report report2))\n          (acceptable3 (make-instance 'base-acceptable :report report3)))\n      \n      ;; Add history items with different timestamps\n      (with-transaction ()\n        (setf (acceptable-history acceptable1)\n              (list (make-instance 'acceptable-history-item\n                                   :state :accepted\n                                   :user user1\n                                   :ts (- (get-universal-time) 3600)) ; 1 hour ago\n                    (make-instance 'acceptable-history-item\n                                   :state :rejected\n                                   :user user2\n                                   :ts (- (get-universal-time) 7200)))) ; 2 hours ago\n        \n        (setf (acceptable-history acceptable2)\n              (list (make-instance 'acceptable-history-item\n                                   :state :rejected\n                                   :user user1\n                                   :ts (- (get-universal-time) 1800)))) ; 30 minutes ago\n        \n        (setf (acceptable-history acceptable3)\n              (list (make-instance 'acceptable-history-item\n                                   :state :accepted\n                                   :user user2\n                                   :ts (- (get-universal-time) 600))))) ; 10 minutes ago\n    \n    (values company (list channel1 channel2)))))\n\n(defun render-activity-feed-only (company)\n  \"Render just the activity feed component wrapped in app template\"\n  (let ((recent-activity (get-recent-activity company)))\n    <app-template>\n      <link rel=\"stylesheet\" href=\"/css/dashboard.css\" />\n      <div class=\"container-fluid mt-4\">\n        <h2 class=\"mb-4\">Activity Feed Test</h2>\n        <activity-feed recent-activity=recent-activity />\n      </div>\n    </app-template>))\n\n(screenshot-test activity-feed-screenshot\n  (fix-timestamps\n   (with-installation ()\n     (with-test-store ()\n       (with-test-user (:company company :logged-in-p t)\n         (multiple-value-bind (test-company channels) (create-test-activity-data company)\n           ;; Set the current company to our test company for the activity feed\n           (setf (screenshotbot/user-api:current-company) test-company)\n          \n           (render-activity-feed-only test-company)))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-ensure-company.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-ensure-company\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/dashboard/ensure-company\n                #:prepare-company\n                #:ensure-company-or-invite\n                #:%redirect-message\n                #:%new-company)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/model/invite\n                #:invite)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/installation\n                #:call-with-ensure-user-prepared\n                #:installation\n                #:multi-org-feature\n                #:one-owned-company-per-user)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string))\n(in-package :screenshotbot/dashboard/test-ensure-company)\n\n\n(util/fiveam:def-suite)\n\n(screenshot-test empty-new-company-for-companyless-user\n  (with-installation ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (%new-company)))))\n\n(screenshot-test redirect-message-for-migrated-org\n  (with-installation ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (%redirect-message \"https://new-instance.example.com\")))))\n\n(defclass my-installation (one-owned-company-per-user\n                           multi-org-feature\n                           installation)\n  ())\n\n(screenshot-test ensure-invite-too\n  (with-installation (:installation (make-instance 'my-installation))\n    (with-fake-request ()\n      (with-test-store ()\n       (auth:with-sessions ()\n         (let* ((company (make-instance 'company\n                                        :name \"FooBar Enterprises\"))\n                (user (make-instance 'user\n                                     :full-name \"Arnold Noronha\"))\n                (invite (make-instance 'invite\n                                       :inviter user\n                                       :company company)))\n           (let ((test-user (make-instance 'user :full-name \"Another user\")))\n             (is (eql nil (roles:companies-for-user test-user)))\n             (let ((ret (ensure-company-or-invite\n                         test-user\n                         (lambda ()\n                           \"Should not see this\")\n                         (list invite))))\n               (is (not (equal \"Should not see this\" ret)))\n               ret))))))))\n\n(test call-with-happy-path\n  (with-test-store ()\n    (with-installation (:installation (make-instance 'my-installation))\n      (with-fake-request ()\n        (auth:with-sessions ()\n         (let ((val 0))\n           (is (eql 1\n                    (call-with-ensure-user-prepared\n                     (make-instance 'my-installation)\n                     nil\n                     (lambda ()\n                       (incf val)))))\n           ;; When there is no user, there's nothing to prepare\n           (is (eql 1 val))\n           (call-with-ensure-user-prepared\n            (make-instance 'my-installation)\n            (make-instance 'user)\n            (lambda ()\n              (incf val)))\n           ;; The body should not be called in this case because the user\n           ;; wasn't prepared\n           (is (eql 1 val))))))))\n\n(test prepare-company-happy-path\n  (with-test-store ()\n    (with-installation (:installation (make-instance 'my-installation))\n      (with-fake-request ()\n        (auth:with-sessions ()\n         (let ((user (make-instance 'user)))\n           (setf (auth:current-user) user)\n           (finishes\n             (prepare-company user \"foobar\"))\n           (assert-that\n            (roles:companies-for-user user)\n            (has-length 1))))))))\n\n(test ensure-company-with-redirect-url-shows-migration-message\n  (with-test-store ()\n    (with-installation (:installation (make-instance 'my-installation))\n      (with-fake-request ()\n        (auth:with-sessions ()\n          (let* ((redirect-company (make-instance 'company\n                                                   :name \"Migrated Company\"\n                                                   :redirect-url \"https://new-instance.example.com\"))\n                 (user (make-user :companies (list redirect-company))))\n            (let ((output (ensure-company-or-invite\n                           user\n                           (lambda ()\n                             \"Should not see this\")\n                           nil)))\n              (assert-that\n               (markup:write-html output)\n               (contains-string \"has been migrated\")))))))))\n\n(test ensure-company-with-redirect-url-does-not-call-function\n  (with-test-store ()\n    (with-installation (:installation (make-instance 'my-installation))\n      (with-fake-request ()\n        (auth:with-sessions ()\n          (let* ((redirect-company (make-instance 'company\n                                                   :name \"Migrated Company\"\n                                                   :redirect-url \"https://new-instance.example.com\"))\n                 (user (make-user :companies (list redirect-company)))\n                 (function-called nil))\n            (ensure-company-or-invite\n             user\n             (lambda ()\n               (setf function-called t)\n               \"Should not see this\")\n             nil)\n            (is (not function-called))))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-flaky-screenshots.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-flaky-screenshots\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/dashboard/flaky-screenshots\n                #:screenshot-variant-map)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:company)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:runs-for-channel\n                #:make-recorder-run))\n(in-package :screenshotbot/dashboard/test-flaky-screenshots)\n\n\n(util/fiveam:def-suite)\n\n(defun %make-image (name)\n  (make-image\n   :pathname (asdf:system-relative-pathname :screenshotbot\n                                            (format nil \"fixture/~a\" name))))\n\n(def-fixture state ()\n  (with-test-store ()\n    (let* ((company (make-instance 'company\n                                   :name \"foo\"))\n           (channel (find-or-create-channel company \"foobar\"))\n           (im1 (%make-image \"rose.png\"))\n           (im2 (%make-image \"point.png\")))\n      \n     (&body))))\n\n(test screenshot-variant-map-happy-path\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :screenshots (list\n                              (make-screenshot :name \"foo\"\n                                               :image im1)))))\n     (finishes\n       (screenshot-variant-map (list run))))))\n\n(test screenshot-variant-map-for-multiple-names\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :screenshots (list\n                              (make-screenshot :name \"foo\"\n                                               :image im1)\n                              (make-screenshot :name \"bar\"\n                                               :image im2)))))\n     (finishes\n       (screenshot-variant-map (list run))))))\n\n(test ordering\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :screenshots (list\n                              (make-screenshot :name \"foo\"\n                                               :image im1)\n                              (make-screenshot :name \"bar\"\n                                               :image im2))))\n          (run2 (make-recorder-run\n                 :channel channel\n                 :screenshots (list\n                               (make-screenshot :name \"foo\"\n                                                :image im2)\n                               (make-screenshot :name \"bar\"\n                                                :image im2)))))\n      (let ((result (screenshot-variant-map (list run run2))))\n        (is (equal \"foo\" (car (first result))))\n        (is (equal 2 (length (cdr (first result)))))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-history.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/dashboard/test-history\n    (:use #:cl\n          #:alexandria\n          #:fiveam\n          #:screenshotbot/screenshot-api)\n  (:import-from #:screenshotbot/dashboard/history\n                #:render-history)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot\n                #:get-screenshot-history)\n  (:import-from #:screenshotbot/model/image\n                #:make-image\n                #:image-hash)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/testing\n                #:with-installation))\n\n(util/fiveam:def-suite)\n\n(defun make-list-iterator (screenshots runs)\n  (cond\n    (screenshots\n     (lambda ()\n       (values (list (first screenshots) (first runs) (second screenshots))\n               (make-list-iterator (cdr screenshots) (cdr runs) ))))\n    (t\n     (lambda ()\n       (values nil nil)))))\n\n(defun make-history-iterator ()\n  (let ((screenshots\n          (list (make-screenshot\n                 :image\n                 (make-image\n                  :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\"))\n                 :name \"one\")\n                (make-screenshot\n                 :image\n                 (make-image\n                  :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\"))\n                 :name \"one\")\n                (make-screenshot\n                 :image\n                 (make-image\n                  :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\"))\n                 :name \"one\")))\n        (runs (list (make-recorder-run\n                     :commit-hash \"one\")\n                    (make-recorder-run\n                     :commit-hash \"two\")\n                    (make-recorder-run\n                     :commit-hash \"three\"))))\n    (make-list-iterator screenshots runs)))\n\n(test simple-render-history\n  (with-installation ()\n   (with-test-store ()\n     (let ((ctr 0))\n       (cl-mock:with-mocks ()\n         (cl-mock:if-called 'get-screenshot-history\n                            (lambda (channel screenshot-name &key iterator branch)\n                              (declare (ignore channel screenshot-name))\n                              (is-true iterator)\n                              (make-history-iterator)))\n         (cl-mock:if-called 'image-hash\n                            (lambda (image)\n                              (incf ctr)))\n         (with-fake-request ()\n           (auth:with-sessions ()\n             (render-history\n              :screenshot-name \"foo\"\n              :channel (make-instance 'channel :name \"foobar\")))))))) ()\n  (pass))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-image\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/dashboard/image\n                #:timestamp-is-too-old\n                #:*signature-expiry*\n                #:quantized-timestamp\n                #:%sign-oid\n                #:%decode-oid\n                #:with-cropped-and-resized\n                #:with-access-checked-image\n                #:send-404\n                #:%build-resized-image\n                #:handle-resized-image)\n  (:import-from #:lparallel\n                #:force\n                #:chain\n                #:future)\n  (:import-from #:screenshotbot/model/image\n                #:image-file-deleted\n                #:with-local-image\n                #:image-blob-get\n                #:make-image\n                #:image)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/hash-lock\n                #:hash-lock)\n  (:import-from #:lparallel.kernel\n                #:*debug-tasks-p*)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:util/testing\n                #:with-fake-request\n                #:with-global-binding)\n  (:import-from #:util/threading\n                #:*log-sentry-p*)\n  (:import-from #:screenshotbot/async\n                #:magick-future)\n  (:import-from #:util/store/object-id\n                #:make-oid\n                #:%make-oid\n                #:oid-array\n                #:oid)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:with-wand\n                #:magick-get-image-width\n                #:magick-get-image-height)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)  \n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex\n                #:contains-string)\n  (:import-from #:screenshotbot/screenshot-api\n                #:image-public-url)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/dashboard/test-image)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-global-binding ((*installation* (make-instance 'installation))\n                        (*log-sentry-p* nil))\n   (with-test-store (:globally t)\n     (let ((debug-tasks-p *debug-tasks-p*))\n       (let* ((im1 #.(asdf:system-relative-pathname\n                      :screenshotbot\n                      \"dashboard/fixture/image.png\"))\n              (im (make-image :pathname im1 :for-tests t)))\n         (unwind-protect\n              (progn\n                (setf *debug-tasks-p* nil)\n                (&body))\n           (setf *debug-tasks-p* debug-tasks-p)))))))\n\n(test future-has-*store*\n  (with-fixture state ()\n    (let ((random-value (random 1000)))\n      (symbol-macrolet ((query (list\n                                random-value\n                                bknr.datastore:*store*\n                                util/store:*object-store*)))\n        (is (equal query query))\n        (is (equal query\n                   (force\n                    (magick-future ()\n                      query))))\n        (is (equal query\n                   (force (magick-future ()\n                            (chain (magick-future () query))))))\n        (let ((one (magick-future () query))\n              (two (magick-future () query))\n              (three (magick-future () query)))\n          (is (equal query (force one)))\n          (is (equal query (force two)))\n          (is (equal query (force three))))))))\n\n(test handle-resized-image-warmup-happy-path\n  (with-fixture state ()\n    (is (uiop:file-exists-p im1))\n    (let ((output-file (handle-resized-image im :tiny :warmup t)))\n      (unwind-protect\n           (progn\n             (is (equal \"webp\" (pathname-type output-file)))\n             (is (equal output-file\n                        (handle-resized-image im :tiny :warmup t))))\n        (uiop:file-exists-p output-file)))))\n\n(test build-resized-image-for-png\n  (with-fixture state ()\n    (let ((output-file (%build-resized-image im :tiny :type :png)))\n      (is (equal \"png\" (pathname-type output-file)))\n      (%build-resized-image im :tiny :type :png))))\n\n(test build-resized-image-for-png-when-webp-alread-exists\n  (with-fixture state ()\n    (%build-resized-image im :tiny :type :webp)\n    (let ((output-file (%build-resized-image im :tiny :type :png)))\n      (is (equal \"png\" (pathname-type output-file)))\n      (%build-resized-image im :tiny :type :png))))\n\n\n(test send-404-happy-path\n  (with-fixture state ()\n    (with-fake-request ()\n      (is\n       (equal \"this is a test\"\n        (catch 'hunchentoot::handler-done\n          (send-404 \"this is a test\")))))))\n\n(test with-access-checked-image\n  (with-fixture state ()\n    (let ((result-im))\n      (signals error\n       (with-access-checked-image (image (encrypt:encrypt-mongoid (oid-array im)))\n         (setf result-im image))))))\n\n(test |/image/resized.webp happy path|\n  (with-fixture state ()\n    (with-cropped-and-resized (im 3 3 10 15 2 :output p)\n      (with-wand (wand :file p)\n        (is (eql 20 (magick-get-image-width wand)))\n        (is (eql 30 (magick-get-image-height wand) ))))))\n\n(length (mongoid:oid))\n\n(test %decode-oid-on-eoid\n  (with-fixture state ()\n   (let ((oid (mongoid:oid)))\n     (signals-error-matching (timestamp-is-too-old)\n       (%decode-oid\n        (mongoid:oid-str oid)\n        :ts \"22\"\n        :signature \"bar\"))\n     (signals-error-matching ()\n       (%decode-oid\n        (mongoid:oid-str oid)\n        :ts (format nil \"~a\" (get-universal-time))\n        :signature \"bar\")\n       (error-with-string-matching\n        (contains-string \"signature does not match\"))))))\n\n(test %decode-oid-correctly-when-signature-is-present\n  (with-fixture state ()\n    (let* ((oid (mongoid:oid))\n           (oid-str (mongoid:oid-str oid)))\n      (let ((ts (- (get-universal-time) 500)))\n        (is (equalp oid (%decode-oid\n                         oid-str\n                         :ts (format nil \"~a\" ts)\n                         :signature (%sign-oid oid-str :ts ts))))))))\n\n(test image-public-url\n  (is (equal \"/image/blob/bar/default.webp\" (util:make-url 'image-blob-get :oid \"bar\"))))\n\n(test image-public-url-originalp\n  (with-fixture state ()\n    (assert-that (image-public-url im)\n                 (matches-regex \"/image/blob/.*/default.webp\"))\n    (assert-that (image-public-url im :originalp t)\n                 (matches-regex \"/image/original/.*\\\\.png\"))))\n\n(test quantized-timestamp\n  (let ((*signature-expiry* 100))\n    (is (eql 400 (quantized-timestamp 420)))\n    (is (eql 400 (quantized-timestamp 400)))\n    (is (eql 450 (quantized-timestamp 470)))))\n\n(test handle-nonexistent-image-file\n  (with-fixture state ()\n    (let* ((temp-image im))\n      (unwind-protect\n           (progn\n             (with-local-image (pathname im)\n               (delete-file pathname))\n             (signals image-file-deleted\n               (handle-resized-image temp-image :tiny :warmup t)))\n        (ignore-errors (delete-object temp-image))))))\n\n\n(test invalid-image-url\n  (with-fixture state ()\n    (let* ((filename #. (asdf:system-relative-pathname :screenshotbot \"fixture/invalid-image.png\"))\n           (image (make-image :pathname filename)))\n      (assert-that\n       (image-public-url image :originalp t)\n       (contains-string \".bin\")))))\n\n(test resizing-invalid-image\n  (with-fixture state ()\n    (let* ((filename #. (asdf:system-relative-pathname :screenshotbot \"fixture/invalid-image.png\"))\n           (image (make-image :pathname filename)))\n      (with-fake-request ()\n        (finishes\n         (catch 'hunchentoot::handler-done\n           (handle-resized-image image :tiny)))\n        (assert-that\n         (assoc-value (hunchentoot:headers-out hunchentoot:*reply*)\n                      :location)\n         (contains-string\n          \"invalid-image.png\"))))))\n\n(test warmup-of-invalid-image\n  (with-fixture state ()\n    (let* ((filename #. (asdf:system-relative-pathname :screenshotbot \"fixture/invalid-image.png\"))\n           (image (make-image :pathname filename)))\n      (with-fake-request ()\n        (finishes\n         (handle-resized-image image :tiny :warmup t))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-notices.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-notices\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:multi-org-test-installation\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:screenshotbot/user-api\n                #:unaccepted-invites)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/model/invite\n                #:invite)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:screenshot-static-page)\n  (:import-from #:screenshotbot/template\n                #:user-notice-list\n                #:dashboard-template)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/dashboard/test-notices)\n\n\n(util/fiveam:def-suite)\n\n(test notices-screenshot-test\n  (with-installation (:installation (make-instance 'multi-org-test-installation))\n   (with-test-store ()\n     (with-test-user (:user user\n                      :company company\n                      :logged-in-p t)\n       (with-transaction ()\n         (setf (unaccepted-invites user)\n               (list\n                (make-instance 'invite\n                               :code \"dfd\"\n                               :used-p nil\n                               :company company))))\n       (screenshot-static-page\n        :screenshotbot\n        \"notices\"\n        (dashboard-template))))))\n\n(test two-notices-screenshot-test\n  (with-installation (:installation (make-instance 'multi-org-test-installation))\n   (with-test-store ()\n     (with-test-user (:user user\n                      :company company\n                      :logged-in-p t)\n       (with-transaction ()\n         (setf (unaccepted-invites user)\n               (list\n                (make-instance 'invite\n                               :code \"first-invite\"\n                               :used-p nil\n                               :company company)\n                (make-instance 'invite\n                               :code \"second-invite\"\n                               :used-p nil\n                               :company company))))\n       (screenshot-static-page\n        :screenshotbot\n        \"two-notices\"\n        (dashboard-template))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-recent-runs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/dashboard/test-recent-runs\n    (:use #:cl\n          #:alexandria\n          #:fiveam\n          #:screenshotbot/user-api\n          #:screenshotbot/model/github\n          #:screenshotbot/model/recorder-run\n          #:screenshotbot/dashboard/recent-runs)\n  (:import-from #:screenshotbot/dashboard/recent-runs\n                #:render-run-headline\n                #:recorder-run-row\n                #:render-recent-runs)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/model/company\n                #:company\n                #:company-with-name)\n  (:import-from #:screenshotbot/model/user\n                #:user-with-email)\n  (:import-from #:screenshotbot/dashboard/recent-runs\n                #:find-recent-runs)\n  (:import-from #:screenshotbot/user-api\n                #:user\n                #:pull-request-url)\n  (:import-from #:util/testing\n                #:screenshot-static-page\n                #:with-fake-request)\n  (:import-from #:markup\n                #:write-html)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-tags\n                #:%pull-request-url\n                #:gitlab-merge-request-iid\n                #:phabricator-diff-id)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:fix-timestamps)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex))\n\n(util/fiveam:def-suite)\n\n(defclass test-run ()\n  ((phabricator-diff-id\n    :initform nil\n    :reader phabricator-diff-id)\n   (merge-request-iid\n    :initform nil\n    :reader gitlab-merge-request-iid)\n   (pull-request-url\n    :initform nil\n    :reader pull-request-url)\n   (tags\n    :initform nil\n    :initarg :tags\n    :reader recorder-run-tags)))\n\n(defclass test-channel ()\n  ())\n\n(defvar *channel* (make-instance 'test-channel))\n\n(defmethod util:oid ((run test-run) &key (stringp t))\n  (assert stringp)\n  \"foobar\")\n\n(defmethod channel-name ((channel test-channel))\n  \"blah-channel\")\n\n(defmethod recorder-run-channel ((run test-run))\n  *channel*)\n\n(defmethod recorder-run-commit ((run test-run))\n  \"quick-patch\")\n\n(defmethod created-at ((run test-run))\n  (local-time:now))\n\n(defmethod activep ((run test-run))\n  t)\n\n(defmethod channel-repo ((run test-channel))\n  (make-instance 'github-repo\n                  :link\n                  \"https://github.com/foo/bar.git\"))\n\n(defmethod store-object-id ((Run test-run))\n  1)\n\n(test simple-recorder-run-row\n  (let ((core/ui/taskie::*checkboxes* t))\n   (let ((run (make-instance 'test-run)))\n     (recorder-run-row :run run)\n     (pass))))\n\n(test recent-runs\n  (with-test-store ()\n   (let ((*installation* (make-instance 'installation)))\n     (with-fake-request ()\n       (auth:with-sessions ()\n         (let ((runs (loop for i from 1 to 100 collect\n                                               (make-instance 'test-run\n                                                              :tags (if (= i 2)\n                                                                        (list \"foo\")\n                                                                        nil)))))\n           (let ((company (make-instance 'company :name \"bleh\"))\n                 (user (make-instance 'user)))\n             (screenshot-static-page\n              :screenshotbot\n              \"recent-runs\"\n              (fix-timestamps\n               (render-recent-runs runs\n                                   :user user\n                                   :check-access-p nil\n                                   :script-name \"/runs\"\n                                   :company company))))))))))\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (let* ((company (make-instance 'company))\n            (channel (make-instance 'channel :company company)))\n       (&body)))))\n\n(defun %render-run-headline-to-str (run)\n  (str:join \"\" (str:lines (markup:write-html (render-run-headline run)))))\n\n(test render-run-headline\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :commit-hash \"abcd\"\n                :screenshots nil)))\n      (assert-that\n       (%render-run-headline-to-str run)\n       (matches-regex \".*Unpromoted run.*on.*abcd.*\")))))\n\n(test render-run-headline-for-merge-queue\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"gh-readonly-queue/foo/dfdfd\"\n                :branch \"main\"\n                :commit-hash \"abcd\"\n                :screenshots nil)))\n      (assert-that\n       (%render-run-headline-to-str run)\n       (matches-regex \".*Run.*on.*abcd.*from the merge queue.*\")))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-reports.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-reports\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:%render-sorted-by-changes\n                #:report-page\n                #:render-acceptable-history\n                #:submit-share-report)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:snap-all-images\n                #:fix-timestamps\n                #:screenshot-test\n                #:with-test-user)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:equal-to\n                #:has-typep\n                #:assert-that)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/model/sharing\n                #:share)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-history-item\n                #:base-acceptable)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/model/company\n                #:redirect-url\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:warmup-comparison-images-sync\n                #:warmup-report)\n  (:import-from #:screenshotbot/model/testing\n                #:with-test-image)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/dashboard/test-reports)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store (:globally t)\n    (with-test-user (:company company :user user\n                     :logged-in-p t)\n      (let ((report (make-instance 'report)))\n       (&body)))))\n\n(test submit-share-report\n  (with-fixture state ()\n    (catch 'hunchentoot::handler-done\n      (submit-share-report report nil))\n\n    (let ((shares (class-instances 'share)))\n      (assert-that shares\n                   (has-length 1)))))\n\n(screenshot-test render-acceptable-history-empty ()\n  (with-fixture state ()\n    (let ((report (make-instance 'report))\n          (acceptable (make-instance 'base-acceptable\n                                     :report report)))\n      (render-acceptable-history acceptable))))\n\n(screenshot-test render-acceptable-history-non-empty ()\n  (with-fixture state ()\n    (let ((report (make-instance 'report))\n          (acceptable (make-instance 'base-acceptable\n                                     :report report\n                                     :history (list\n                                               (make-instance 'acceptable-history-item\n                                                              :state :accepted\n                                                              :user user)\n                                               (make-instance 'acceptable-history-item\n                                                              :state :rejected\n                                                              :user user)))))\n      (fix-timestamps\n       (render-acceptable-history acceptable)))))\n\n(def-fixture report-page (&key (logged-in-p t))\n  (with-test-store ()\n    (with-test-user (:user user :company company :logged-in-p logged-in-p)\n     (let* ((channel (make-instance 'channel :company company :name \"bleh\"))\n            (run1 (make-recorder-run :company company :channel channel))\n            (run2 (make-recorder-run :company company :channel channel))\n            (report (make-instance 'report\n                                   :title \"1 changes\"\n                                   :run run1\n                                   :previous-run run2)))\n       (&body)))))\n\n(test report-page-happy-path ()\n  (with-fixture report-page ()\n    (assert-that\n     (markup:write-html\n      (report-page :id (oid report)))\n     (contains-string \"1 changes\"))))\n\n\n(def-easy-macro get-redirect (&fn fn)\n  (catch 'hunchentoot::handler-done\n    (funcall fn))\n  (hunchentoot:header-out :location))\n\n(test report-page-redirects ()\n  (with-fixture report-page ()\n    (assert-that\n     (get-redirect ()\n       (report-page :id (oid report)))\n     (equal-to nil))\n    (setf (redirect-url company) \"https://foo.example.com\")\n    (assert-that\n     (get-redirect ()\n       (report-page :id (oid report)))\n     (Contains-string \"https://foo.example.com/\"))))\n\n\n(screenshot-test image-processing-is-not-complete-yet\n  (with-fixture report-page ()\n    (let* ((im1 (make-image :pathname\n                            (asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\")))\n           (im2 (make-image :pathname\n                            (asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-3.png\")))\n           (run1 (make-recorder-run :channel channel\n                                    :screenshots (list (make-screenshot\n                                                        :name \"foo\"\n                                                        :image im1))))\n           (run2 (make-recorder-run :channel channel\n                                    :screenshots (list\n                                                  (make-screenshot\n                                                   :name \"foo\"\n                                                   :image im2))))\n           (report (make-instance 'report\n                                  :run run2\n                                  :previous-run run1)))\n      (let ((res (%render-sorted-by-changes report)))\n        (assert-that\n         (markup:write-html res)\n         (contains-string \"not complete\"))\n        res))))\n\n(screenshot-test sorted-by-changes\n  (with-fixture report-page ()\n    (let* ((im1 (make-image :pathname\n                            (asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\")))\n           (im2 (make-image :pathname\n                            (asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-3.png\")))\n           (run1 (make-recorder-run :channel channel\n                                    :screenshots (list (make-screenshot\n                                                        :name \"foo\"\n                                                        :image im1))))\n           (run2 (make-recorder-run :channel channel\n                                    :screenshots (list\n                                                  (make-screenshot\n                                                   :name \"foo\"\n                                                   :image im2))))\n           (report (make-instance 'report\n                                  :run run2\n                                  :previous-run run1)))\n      (warmup-comparison-images-sync run2 run1)\n      (snap-all-images)\n      (let ((res (%render-sorted-by-changes report)))\n        (assert-that\n         (markup:write-html res)\n         (does-not (contains-string \"not complete\")))\n        res))))\n\n(screenshot-test report-page-happy-path-2\n  (with-fixture report-page ()\n    (with-fake-request ()\n      (with-test-user (:company company\n                       :logged-in-p t)\n       (with-test-image (im1-path :pixels '((1 1) (2 2)))\n         (with-test-image (im2-path :pixels '((3 3) (4 4)) :color \"blue\")\n           (let* ((im1 (make-image :pathname im1-path))\n                  (im2 (make-image :pathname im2-path))\n                  (run1 (make-recorder-run :channel channel\n                                           :company company\n                                           :screenshots (list (make-screenshot\n                                                               :name \"FakeName\"\n                                                               :image im1))))\n                  (run2 (make-recorder-run :channel channel\n                                           :company company\n                                           :screenshots (list\n                                                         (make-screenshot\n                                                          :name \"FakeName\"\n                                                          :image im2))))\n                  (report (make-instance 'report\n                                         :run run2\n                                         :previous-run run1)))\n             (warmup-comparison-images-sync run2 run1)\n             (snap-all-images)\n             (let ((res (report-page :id (oid report))))\n               (assert-that\n                (markup:write-html res)\n                (contains-string \"FakeName\"))\n               res))))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-review-link\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/channel\n                #:repo)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:describe-pull-request)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run))\n(in-package :screenshotbot/dashboard/test-review-link)\n\n(util/fiveam:def-suite)\n\n(test describe-pull-request-url-for-branch\n  (with-test-store ()\n    (let ((repo (make-instance 'generic-git-repo)))\n      (is (equal \"Pull Request\"\n                 (describe-pull-request repo (make-recorder-run\n                                              :work-branch \"\"\n                                              :pull-request \"https://google.com\"))))\n      (is (equal \"Pull Request (blah)\"\n                 (describe-pull-request repo\n                                        (make-recorder-run\n                                         :work-branch \"feature/blah\"\n                                         :pull-request \"https://bitbucket.org/tdrhq/fast-example/pull-requests/2\")))))))\n\n(test describe-pull-request-url-for-HEAD\n  (with-test-store ()\n    (let ((repo (make-instance 'generic-git-repo)))\n      (is (equal \"Pull Request\"\n                 (describe-pull-request repo (make-recorder-run\n                                              :work-branch \"HEAD\"\n                                              :pull-request \"https://google.com\")))))))\n"
  },
  {
    "path": "src/screenshotbot/dashboard/test-run-page.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/dashboard/test-run-page\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:fix-timestamps\n                #:with-test-user\n                #:screenshot-test\n                #:snap-all-images)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:runs-for-company\n                #:make-recorder-run\n                #:not-fast-forward-promotion-warning\n                #:merge-base-failed-warning\n                #:recorder-run-warnings\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:%advanced-run-page\n                #:can-view-metadata-p\n                #:render-run-warning\n                #:run-delete-page\n                #:run-page\n                #:render-run-page)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains)\n  (:import-from #:screenshotbot/model/archived-run\n                #:save-archived-run\n                #:archived-run)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:screenshot-map)\n  (:import-from #:bknr.datastore\n                #:store-object-id))\n(in-package :screenshotbot/dashboard/test-run-page)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-test-user (:company company\n                     :user user\n                     :logged-in-p t)\n      (labels ((make-screenshot (img)\n                      (let* ((image (make-image :pathname img :for-tests t)))\n                        (make-instance 'screenshot\n                                       :name \"foobar\"\n                                       :image image))))\n        (let* ((channel (make-instance 'channel\n                                       :publicp t\n                                       :company company\n                                       :name \"bleh\"\n                                       :github-repo \"git@github.com:a/b.gitq\"))\n               (im1 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\"))\n               (run (make-recorder-run\n                     :company company\n                     :channel channel\n                     :company company\n                     :screenshots (list (make-screenshot im1))))\n               (another-run (make-recorder-run\n                             :commit-hash \"foo\")))\n          (&body))))))\n\n(def-easy-macro wrap-snapshot (&fn fn)\n  (snap-all-images)\n  (fix-timestamps (fn)))\n\n(def-easy-macro with-archived-run (&key (oid \"507f1f77bcf86cd799439011\") &binding archived-run company &fn fn)\n  (let* ((channel (make-instance 'channel\n                                 :company company\n                                 :name \"test-channel\"))\n         (screenshot-map (make-instance 'screenshot-map\n                                        :channel channel\n                                        :screenshots nil))\n         (archived-run (make-instance 'archived-run\n                                      :oid oid\n                                      :channel (store-object-id channel)\n                                      :company (oid company :stringp t)\n                                      :screenshots (store-object-id screenshot-map)\n                                      :commit-hash \"test-commit\"\n                                      :created-at (get-universal-time))))\n    ;; Save the archived-run to disk so it can be found\n    (save-archived-run archived-run)\n    (fn archived-run)))\n\n(screenshot-test simple-run-page-screenshots\n  (with-fixture state ()\n    (wrap-snapshot ()\n     (render-run-page run))))\n\n(screenshot-test run-page-with-warnings\n  (with-fixture state ()\n    (with-transaction ()\n      (setf (recorder-run-warnings run)\n            (list (make-instance 'merge-base-failed-warning\n                                 :compared-against another-run))))\n    (wrap-snapshot ()\n     (render-run-page run))))\n\n(screenshot-test run-page-with-not-fast-forard-warnings\n  (with-fixture state ()\n    (let ((run2 (make-recorder-run\n                 :company company\n                 :channel channel\n                 :company company\n                 :previous-run run\n                 :screenshots (list (make-screenshot im1)))))\n     (with-transaction ()\n       (setf (recorder-run-warnings run2)\n             (list (make-instance 'not-fast-forward-promotion-warning))))\n      (wrap-snapshot ()\n        (render-run-page run2)))))\n\n(screenshot-test run-page-with-tags\n  (with-fixture state ()\n    (let ((run2 (make-recorder-run\n                 :company company\n                 :channel channel\n                 :company company\n                 :previous-run run\n                 :tags (list \"9823\" \"release-branch\")\n                 :screenshots (list (make-screenshot im1)))))\n\n      (wrap-snapshot ()\n        (render-run-page run2)))))\n\n(test run-page-happy-path\n  (with-fixture state ()\n    (finishes\n     (run-page :id (oid run)))))\n\n\n(test run-delete-page\n  (with-fixture state ()\n    (let ((run (make-recorder-run :company company\n                                  :channel channel)))\n      (assert-that (fset:convert 'list (runs-for-company company))\n                   (has-item run))\n      (finishes\n        (run-delete-page :id (oid run)))\n      (assert-that (fset:convert 'list (runs-for-company company))\n                   (is-not (has-item run))))))\n\n(test render-run-warning-has-default-value\n  (with-fixture state ()\n    (is (eql nil\n             (render-run-warning run :foo)))))\n\n(test can-view-metadata-p\n  (with-test-store ()\n    (with-test-user (:logged-in-p t)\n      (is-true (can-view-metadata-p \"foo\"))\n      (is-false (can-view-metadata-p \"%foo\"))\n      (setf (adminp (auth:current-user)) t)\n      (is-true (can-view-metadata-p \"%foo\")))))\n\n(test archived-run-page-happy-path\n  \"Test that visiting the run page with an archived-run doesn't crash\"\n  (with-test-store ()\n    (with-test-user (:company company\n                     :user user\n                     :logged-in-p t)\n      (roles:ensure-has-role company user 'roles:standard-member)\n      (with-archived-run (:company company :archived-run archived-run)\n        (finishes\n          (run-page :id (oid archived-run :stringp t)))))))\n\n(test archived-run-debug-page-happy-path\n  \"Test that visiting the debug page with an archived-run doesn't crash\"\n  (with-test-store ()\n    (with-test-user (:company company\n                     :user user\n                     :logged-in-p t)\n      (roles:ensure-has-role company user 'roles:standard-member)\n      (with-archived-run (:oid \"507f1f77bcf86cd799439012\" :company company :archived-run archived-run)\n        (finishes\n          (%advanced-run-page :oid (oid archived-run :stringp t)))))))\n"
  },
  {
    "path": "src/screenshotbot/debugging.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/debugging\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler))\n(in-package :screenshotbot/debugging)\n\n(defun enabledp ()\n  (equal \"thecharmer\" (uiop:hostname)))\n\n(defhandler (nil :uri \"/test-timeout\") ()\n  \"For testing timeout handling from the SDK\"\n  (when (enabledp)\n    (sleep 120)))\n\n(defhandler (nil :uri \"/test-timeout-while-sending\") ()\n  \"What happens if we timeout half-way through? Can we detect it?\"\n  (when (enabledp)\n    (let ((stream (hunchentoot:send-headers)))\n      (write-string \"arnold\" stream)\n      (force-output stream)\n      (sleep 30)\n      (write-string \"foobar\" stream)\n      (close stream))))\n\n"
  },
  {
    "path": "src/screenshotbot/default-oidc-provider.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/default-oidc-provider\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:screenshotbot/login/oidc\n                #:oidc-provider)\n  (:export\n   #:make-oidc-provider))\n(in-package :screenshotbot/default-oidc-provider)\n\n(defclass default-oidc-provider (store-object)\n  ((%scope :initarg :scope\n          :reader scope)\n   (%client-id :initarg :client-id\n               :reader client-id)\n   (%client-secret :initarg :client-secret\n                   :reader client-secret)\n   (%expiration :initarg :expiration-seconds\n                :initform (* 20 3600)\n                :reader expiration)\n   (%issuer :initarg :issuer\n            :reader issuer)\n   (%company :initarg :company\n             :reader company))\n  (:metaclass persistent-class))\n\n(defun make-oidc-provider (stored-provider)\n  \"Create an oidc-provider from a default-oidc-provider instance.\"\n  (make-instance 'oidc-provider\n                 :issuer (issuer stored-provider)\n                 :client-id (client-id stored-provider)\n                 :client-secret (client-secret stored-provider)\n                 :scope (scope stored-provider)\n                 :identifier 'default-oidc-provider\n                 :company-provider (lambda (&rest args)\n                                     (declare (ignore args))\n                                     (company stored-provider))\n                 :expiration-seconds (expiration stored-provider)))\n\n"
  },
  {
    "path": "src/screenshotbot/diff-report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/diff-report\n  (:use #:cl)\n  (:import-from #:screenshotbot/user-api\n                #:Screenshot-name)\n  (:import-from #:screenshotbot/model/image\n                #:image-hash\n                #:image=)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:group-separator\n                #:recorder-run-screenshots)\n  (:import-from #:screenshotbot/screenshot-api\n                #:screenshot-image)\n  (:import-from #:screenshotbot/model/screenshot\n                #:abstract-screenshot\n                #:screenshot-masks)\n  (:import-from #:screenshotbot/model/image-comparer\n                #:make-image-comparer)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/misc\n                #:?.)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:change\n   #:before\n   #:after\n   #:change-masks\n   #:diff-report-added\n   #:diff-report-deleted\n   #:diff-report-changes\n   #:make-diff-report\n   #:diff-report\n   #:diff-report-title\n   #:changes-groups\n   #:added-groups\n   #:deleted-groups\n   #:group-items\n   #:group-title\n   #:actual-item\n   #:group-item-subtitle\n   #:diff-report-empty-p\n   #:deleted-hashes-set\n   #:added-hashes-set\n   #:group-renamed-p\n   #:group\n   #:group-item\n   #:diff-report-run\n   #:diff-report-previous-run))\n\n\n(in-package :screenshotbot/diff-report)\n\n(defvar *cache* (trivial-garbage:make-weak-hash-table :test #'equal\n                                                      :weakness :value)\n  \"diff reports are generated on the fly. Generally it's fast enough\n  for most use, so we don't persist it. However, the diff-reports can\n  quickly become a dominant source of objects in memory (because of\n  nibbles). For that reason we keep this cache, but keep the cache\n  weak on the values.\n\n  Even without weakness this would be technically still correct, since\n  we won't store a diff-report more than once.\")\n\n(defclass change ()\n  ((before :initarg :before\n           :reader before)\n   (after :initarg :after\n          :reader after)\n   (masks :initarg :masks\n          :reader change-masks)))\n\n(defmethod screenshot-hash ((screenshot abstract-screenshot))\n  (?. image-hash (screenshot-image screenshot)))\n\n(defun make-image-hashes (screenshots)\n  (reduce\n   (lambda (result screenshot)\n     (when-let ((hash (screenshot-hash screenshot)))\n       (cond\n         (hash\n          (fset:with result hash))\n         (t\n          result))))\n   screenshots\n   :initial-value (fset:empty-set)))\n\n(defclass diff-report ()\n  ((run :initarg :run\n        :reader diff-report-run)\n   (previous-run :initarg :previous-run\n           :reader diff-report-previous-run)\n   (added :initarg :added\n          :reader diff-report-added\n          :initform nil)\n   (deleted :initarg :deleted\n            :reader diff-report-deleted\n            :initform nil)\n   (changes :initarg :changes\n            :initform nil\n            :accessor diff-report-changes\n            :documentation \"List of all CHANGEs\")\n   (deleted-hashes-set :accessor deleted-hashes-set\n                       :initform (fset:empty-set)\n                       :documentation \"A set of all the image hashes in the added set\")\n   (added-hashes-set :accessor added-hashes-set\n                     :initform (fset:empty-set)\n                     :documentation \"A set of all the image hashes in the deleted set\")\n   (added-groups :initform nil\n                 :accessor %added-groups)\n   (deleted-groups :initform nil\n                   :accessor %deleted-groups)\n   (changes-groups :initform nil\n                   :accessor %changes-groups)\n   (%group-separator :initarg :group-separator\n                     :initform \"--\"\n                     :reader group-separator)))\n\n(defmethod initialize-instance :after ((self diff-report) &key deleted added)\n  (setf (deleted-hashes-set self)\n        (make-image-hashes deleted))\n  (setf (added-hashes-set self)\n        (make-image-hashes added)))\n\n(defclass group-item ()\n  ((subtitle :reader group-item-subtitle\n             :initarg :subtitle)\n   (item :initarg :actual-item\n         :reader actual-item)))\n\n(defclass group ()\n  ((title :initarg :title\n          :reader group-title)\n   (items :initarg :items\n          :reader group-items)\n   (diff-report :initarg :diff-report\n                :initform nil\n                :reader group-diff-report\n                :documentation \"the diff report this group belongs to.\")))\n\n(defmethod group-separator ((self group))\n  (group-separator (group-diff-report self)))\n\n(defclass changed-group (group)\n  ())\n\n(defclass added-group (group)\n  ())\n\n(defclass deleted-group (group)\n  ())\n\n(defmethod group-renamed-p ((group group))\n  \"Check if the given group is just a renamed\"\n  (when-let ((diff-report (group-diff-report group)))\n   (let ((compare-to\n           (etypecase group\n             (added-group\n              (deleted-hashes-set diff-report))\n             (deleted-group\n              (added-hashes-set diff-report)))))\n     (every (lambda (group-item)\n              (fset:lookup compare-to (screenshot-hash (actual-item group-item))))\n            (group-items group)))))\n\n\n(defun make-groups (type items &key key subtitle (diff-report (error \"must provide :diff-report\")))\n  (let ((res (make-hash-table :test #'equal)))\n    (loop for item in items\n          do (push item\n                   (gethash (funcall key item) res nil)))\n    (sort\n     (loop for key being the hash-keys of res\n           collect (make-instance type\n                                  :title key\n                                  :diff-report diff-report\n                                  :items\n                                  (loop for item in (gethash key res)\n                                        collect (make-instance 'group-item\n                                                               :subtitle (funcall subtitle item)\n                                                               :actual-item item))))\n     #'string<\n     :key #'group-title)))\n(defun get-only-screenshot-name (screenshot run)\n  (car\n   (str:split (group-separator run) (screenshot-name screenshot) :limit 2)))\n\n\n(defun get-tab-title (screenshot run)\n  (cadr\n   (str:split (group-separator run) (screenshot-name screenshot) :limit 2)))\n\n\n(defmethod changes-groups ((self diff-report))\n  (util:or-setf\n   (%changes-groups self)\n   (let ((changes (diff-report-changes self)))\n     (make-groups 'changed-group changes\n                  :key (lambda (change)\n                         (get-only-screenshot-name (before change) self))\n                  :diff-report self\n                  :subtitle (lambda (change)\n                              (get-tab-title (before change) self))))))\n\n(defmethod added-groups ((self diff-report))\n  (util:or-setf\n   (%added-groups self)\n   (let ((added (diff-report-added self)))\n     (make-groups 'added-group added\n                  :key (alexandria:rcurry #'get-only-screenshot-name self)\n                  :diff-report self\n                  :subtitle (alexandria:rcurry #'get-tab-title self)))))\n\n(defmethod deleted-groups ((self diff-report))\n  (util:or-setf\n   (%deleted-groups self)\n   (let ((deleted (diff-report-deleted self)))\n     (make-groups 'deleted-group deleted\n                  :key (alexandria:rcurry #'get-only-screenshot-name self)\n                  :diff-report self\n                  :subtitle (alexandria:rcurry #'get-tab-title self)))))\n\n\n(defun diff-report-title (diff-report)\n  (let ((added (added-groups diff-report))\n        (deleted (deleted-groups diff-report))\n        (changes (changes-groups diff-report)))\n    (str:join \", \"\n              (remove-if 'null\n               (list\n                (when changes\n                  (format nil \"~d changes\" (length changes)))\n                (when added\n                  (format nil \"~d added\" (length added)))\n                (when deleted\n                  (format nil \"~d deleted\" (length deleted))))))))\n\n(defun hash-set-difference (left right &key test (key #'identity))\n  \"Similar to set-difference, but more performant\"\n  (let ((table (make-hash-table :test test)))\n    (dolist (x left)\n      (setf (gethash (funcall key x) table) x))\n    (dolist (x right)\n      (remhash (funcall key x) table))\n    (alexandria:hash-table-values table)))\n\n(defun sort-screenshots (list)\n  (sort (copy-list list) #'string< :key #'screenshot-name))\n\n(defun %make-diff-report (run to)\n  (restart-case\n      (let ((names (recorder-run-screenshots run))\n            (to-names (when to\n                        (recorder-run-screenshots to))))\n        (make-instance\n         'diff-report\n         :run run\n         :previous-run to\n         :added (sort-screenshots\n                 (hash-set-difference\n                  names to-names\n                  :key #'screenshot-name\n                  :test #'equal))\n         :deleted (sort-screenshots\n                   (hash-set-difference\n                    to-names names\n                    :key #'screenshot-name\n                    :test #'equal))\n         :changes (%find-changes (make-image-comparer run) names to-names)\n         :group-separator (group-separator run)))\n    (retry-make-diff-report ()\n      (make-diff-report run to))))\n\n(defun make-diff-report (run to &key (only-cached-p nil))\n  \"Make a diff report between RUN and another run TO. The diff-reports\nare typically cached in-memory.\n\nIf ONLY-CACHED-P is true, then we'll only return the cached value and\nnot try to create the diff report, which might be an expensive operation.\"\n  (util:or-setf\n   (gethash (list run to :v5) *cache*)\n   (unless only-cached-p\n     (%make-diff-report run to))))\n\n(defmethod %find-changes (image-comparer screenshots to-screenshots)\n  (let ((hash-table (make-hash-table :test #'equal)))\n    (loop for before in to-screenshots\n          do\n          (setf (gethash (screenshot-name before) hash-table) before))\n    (loop for after in screenshots\n          for before = (gethash (screenshot-name after) hash-table)\n          if (and\n              before\n              (not (image=\n                    image-comparer\n                    (screenshot-image after)\n                    (Screenshot-image before)\n                    ;; always use the new mask\n                    (screenshot-masks after))))\n            collect\n            (make-instance 'change\n                           :before before\n                           :masks (screenshot-masks after)\n                           :after after))))\n\n(defun diff-report-empty-p (diff-report)\n  (not\n   (or (diff-report-added diff-report)\n       (diff-report-deleted diff-report)\n       (diff-report-changes diff-report))))\n"
  },
  {
    "path": "src/screenshotbot/dtd/api.dtd",
    "content": "<!ELEMENT failed-run (channel, commit) >\n<!ATTLIST failed-run id CDATA #IMPLIED>\n<!ELEMENT channel #PCDATA>\n<!ELEMENT commit #PCDATA>\n\n<!ELEMENT failed-runs (failed-run)* >\n"
  },
  {
    "path": "src/screenshotbot/dtd/secret.dtd",
    "content": "<!ELEMENT secret (name,public-id,value)>\n<!ELEMENT value #PCDATA>\n<!ELEMENT public-id #PCDATA>\n<!ELEMENT name #PCDATA>\n<!ATTLIST secret environment CDATA \"production\" >\n"
  },
  {
    "path": "src/screenshotbot/email-tasks/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/email-tasks/settings\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:settings-template\n                #:defsettings)\n  (:import-from #:screenshotbot/user-api\n                #:current-user\n                #:personalp\n                #:current-company\n                #:company-name)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/object-id\n                #:find-by-oid)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:company #:screenshotbot/model/company)))\n(in-package :screenshotbot/email-tasks/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass email-setting (store-object)\n  ((%user :initarg :user\n         :index-type hash-index\n         :index-reader email-settings-for-user)\n   (%company :initarg :company\n            :reader company)\n   (enabledp :initarg :enabledp\n             :accessor emails-enabledp\n             :initform t))\n  (:metaclass persistent-class))\n\n(defvar *lock* (bt:make-lock))\n\n(defmethod emails-enabled-by-default-p (installation)\n  nil)\n\n(defmethod emails-enabled-by-default-p :after (installation)\n  (warn \"Deprecated emails-enabled-by-default-p called. Should call company:emails-enabled-by-default-p instead. This function is only here for migration purposes.\"))\n\n(defun email-setting (&key user company)\n  (flet ((old ()\n           (loop for setting in (email-settings-for-user user)\n                 if (eql company (company setting))\n                   return setting)))\n    (or\n     (old)\n     (bt:with-lock-held (*lock*)\n       (or\n        (old)\n        (make-instance 'email-setting\n                        :user user\n                        :company company\n                        :enabledp (company:emails-enabled-by-default-p company)))))))\n\n(defun save-settings (settings enabledp)\n  ;; there's not much in terms of validation to do here.\n  (with-transaction ()\n    (setf (emails-enabledp settings)\n          (if enabledp t)))\n  (hex:safe-redirect \"/settings/email-tasks\"))\n\n(defun get-email-settings (&key (user (current-user))\n                             (company (current-company)))\n\n  (let ((company-oid (hunchentoot:parameter \"company\")))\n    (when company-oid\n      (let ((company (find-by-oid company-oid)))\n        (check-type company company)\n        (assert (roles:has-role-p company user t))\n        ;; If we're here, we're probably clicking the email control\n        ;; link in an email. Since we need to change the setting for\n        ;; specific organization, let's change the organization, and\n        ;; then redirect back to the settings page.\n        (setf (current-company) company)\n        (hex:safe-redirect \"/settings/email-tasks\"))))\n  (let* ((settings (email-setting :user user\n                                  :company company))\n         (save (nibble (enabledp)\n                 (save-settings settings enabledp))))\n    <settings-template>\n      <form action=save method= \"POST\">\n        <div class= \"card mt-3\">\n          <div class= \"card-header\">\n            <h3>Email notifications for tasks</h3>\n          </div>\n          <div class= \"card-body\" >\n            <p class= \"text-muted\" >By default, we send notifications to every user on the account. Admins can change this behavior <a href= \"/settings/organization\">here</a>. You can control whether your account should receive emails on this page.</p>\n\n            <div class= \"form-check\">\n              <input type= \"checkbox\" name= \"enabledp\"\n                     id= \"enabledp\"\n                     class= \"form-check-input\"\n                     checked= (if (emails-enabledp settings) \"checked\")\n                     />\n              <label for= \"enabledp\" class= \"form-check-label\">\n                Email me when reports are generated for\n\n                ,(cond\n                   ((personalp company)\n                    \"my organization\")\n                   (t\n                    (company-name company)))\n              </label>\n            </div>\n          </div>\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n          </div>\n        </div> <!-- card -->\n      </form>\n    </settings-template>))\n\n(defsettings email-tasks\n  :name \"email-tasks\"\n  :title \"Email Tasks\"\n  :section :tasks\n  :handler 'get-email-settings)\n"
  },
  {
    "path": "src/screenshotbot/email-tasks/task-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/email-tasks/task-integration\n  (:use #:cl)\n  (:import-from #:screenshotbot/task-integration-api\n                #:send-task\n                #:enabledp\n                #:task-integration-company\n                #:register-task-integration\n                #:task-integration)\n  (:import-from #:screenshotbot/model/user\n                #:user-with-email)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:screenshotbot/mailer\n                #:send-mail)\n  (:import-from #:screenshotbot/installation\n                #:installation-domain\n                #:installation\n                #:mailer*)\n  (:import-from #:screenshotbot/user-api\n                #:can-view\n                #:user-email\n                #:channel-name\n                #:recorder-run-channel\n                #:user-email)\n  (:import-from #:screenshotbot/report-api\n                #:report-run\n                #:report-title)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:report-link)\n  (:import-from #:screenshotbot/email-tasks/settings\n                #:email-setting\n                #:emails-enabledp)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-subscribers\n                #:channel-company)\n  (:import-from #:screenshotbot/model/report\n                #:report-company\n                #:report-channel)\n  (:import-from #:screenshotbot/dashboard/channels\n                #:single-channel-page)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:alexandria\n                #:curry)\n  (:import-from #:auth/viewer-context\n                #:email-viewer-context)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/email-tasks/task-integration)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass email-task-integration (task-integration)\n  ()\n  (:documentation \"A task integration that just sends an email when a\n  report is created.\"))\n\n(register-task-integration 'email-task-integration)\n\n(defmethod enabledp ((self email-task-integration))\n  ;; TODO: if no other task integrations are enabled, then enable the\n  ;; email task-integration.\n  t)\n\n(defun include-arnold (users)\n  \"Include Arnold in the list of users\"\n  (cond\n    #-screenshotbot-oss\n    (t\n     (union\n      (a:when-let ((user (user-with-email \"arnold@tdrhq.com\")))\n        (list user))\n      users))\n    (t\n     users)))\n\n(defun users-to-email (channel)\n  \"Who should we email when this channel changes? Returns a list of users.\"\n  (let ((company (channel-company channel)))\n    (funcall\n     (if (gk:check :cc-arnold (channel-company channel))\n         #'include-arnold\n         #'identity)\n     (union\n      (remove-if-not\n       (lambda (user)\n         (auth:can-viewer-view\n          (make-instance 'email-viewer-context\n                         :user user)\n          channel))\n       (channel-subscribers channel))\n      (remove-if-not\n       (lambda (user)\n         (emails-enabledp (email-setting :user user\n                                         :company company)))\n       (roles:users-for-company company))))))\n\n(defmethod send-task ((self email-task-integration) report)\n  (dolist (user (users-to-email (report-channel report)))\n    (send-email-to-user user report)))\n\n(defun maybe-redact-token (token)\n  (if (util:token-safe-for-email-p token)\n      token\n      \"[filtered]\"))\n\n(with-auto-restart ()\n  (defun send-email-to-user (user report)\n    (push-event :email-task-notification)\n    (send-mail\n     (mailer*)\n     :to (user-email user)\n     :subject (format nil \"Screenshots changed in ~a\" (maybe-redact-token (channel-name (recorder-run-channel (report-run report)))))\n     :from\n     #-screenshotbot-oss \"notifications@screenshotbot.io\"\n     #+screenshotbot-oss nil ;; default mailer setting\n     :display-name \"Screenshotbot Notifications\"\n     :reply-to (progn\n                 #-screenshotbot-oss \"support@screenshotbot.io\"\n                 #+screenshotbot-oss nil)\n     ;; RFC 3834: Suppress out-of-office and other auto-replies while still\n     ;; allowing users to manually respond to these notification emails\n     :extra-headers '((\"Auto-Submitted\" \"auto-generated\"))\n     :html-message\n     (email-content report))))\n\n(defun %make-full-url (&rest args)\n  (format nil \"~a~a\"\n          (installation-domain (installation))\n          (apply #'hex:make-url args)))\n\n(defun email-content (report)\n  (let ((company (report-company report))\n        (channel (report-channel report)))\n    <html>\n      <body>\n        <p>\n          <a href= (report-link report) >\n            ,(report-title report)\n          </a>\n        </p>\n\n        <p style= \"color: #222\" >\n          Screenshotbot sends email notifications on main or release branches. Update your global preferences for\n          email notifications by           <a href= (%make-full-url\n                    \"/settings/email-tasks\"\n                    :company (oid company)) >clicking here</a>.\n\n          You can enable emails for specific channels by subscribing\n          to them. Manage your subscription to <tt>,(channel-name channel)</tt>\n          by <a href= (%make-full-url 'single-channel-page :id (store-object-id channel)) >clicking here</a>.\n        </p>\n\n        <p style= \"color: #222\">\n          You can respond to this email for support from Screenshotbot staff.\n        </p>\n      </body>\n    </html>))\n"
  },
  {
    "path": "src/screenshotbot/email-tasks/test-task-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/email-tasks/test-task-integration\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/email-tasks/settings\n                #:emails-enabled-by-default-p\n                #:emails-enabledp\n                #:email-setting\n                #:get-email-settings)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:channel\n                #:current-company\n                #:current-user\n                #:user)\n  (:import-from #:util/testing\n                #:screenshot-static-page\n                #:with-fake-request)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/model/user\n                #:make-user\n                #:users-for-company\n                #:user-personal-company)\n  (:import-from #:screenshotbot/testing\n                #:screenshot-test\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:screenshotbot/email-tasks/task-integration\n                #:include-arnold\n                #:email-content\n                #:users-to-email\n                #:email-task-integration\n                #:send-email-to-user)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/task-integration-api\n                #:send-task)\n  (:import-from #:screenshotbot/mailer\n                #:send-mail)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:contains)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-subscribers)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/email-tasks/test-task-integration)\n\n(util/fiveam:def-suite)\n\n(defclass multi-installation (installation multi-org-feature)\n  ())\n\n(defmethod emails-enabled-by-default-p ((self multi-installation))\n  t)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test preconditions\n  (with-installation ()\n   (with-fixture state ()\n     (with-fake-request ()\n       (auth:with-sessions ()\n         (let* ((*installation* (make-instance 'multi-installation)))\n           (with-test-user (:company company\n                            :user user)\n             (setf (current-user) user)\n             (setf (current-company) company)\n             (screenshot-static-page\n              :screenshotbot\n              \"email-settings-page\"\n              (markup:write-html\n               (get-email-settings)))\n             (pass))))))))\n\n(defun disable-emails (user1 company)\n  (with-transaction ()\n    (setf\n     (emails-enabledp\n      (email-setting :user user1 :company company))\n     nil)))\n\n(test only-sends-to-enabled-users\n  (with-fixture state ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (let* ((*installation* (make-instance 'multi-installation)))\n          (let ((calls nil))\n           (cl-mock:with-mocks ()\n             (cl-mock:if-called 'send-email-to-user\n                                 (lambda (&rest args)\n                                   (push args calls)))\n             (let* ((company (make-instance 'company :name \"foo\"))\n                    (channel (make-instance 'channel :company company))\n                    (user1 (make-user :companies (list company)))\n                    (user2 (make-user :companies (list company)))\n                    (report (make-instance 'report\n                                           :channel channel)))\n               (is (equal (list user1 user2)\n                          (users-for-company company)))\n               (disable-emails user1 company)\n               (send-task (make-instance 'email-task-integration\n                                          :company company)\n                          report)\n               (is (eql 1 (length calls)))\n               (is (equal (list user2 report)\n                          (first calls)))))))))))\n\n\n(test send-email-to-user-happy-path\n  (with-fixture state ()\n    (with-fake-request ()\n     (with-test-user (:user user :company company)\n       (let* ((channel (make-instance 'channel\n                                      :name \"foobar\"))\n              (run (make-recorder-run\n                    :company company\n                    :channel channel))\n              (report (make-instance 'report\n                                     :title \"1 changes, 2 added\"\n                                     :channel channel\n                                     :run run)))\n         (cl-mock:with-mocks ()\n           (cl-mock:if-called 'send-mail\n                              (lambda (&rest args)))\n           (finishes\n             (send-email-to-user user report))))))))\n\n(test users-to-email-will-include-subscribers\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let* ((*installation* (make-instance 'multi-installation)))\n       (let ((channel (make-instance 'channel :company company)))\n         (assert-that (users-to-email channel)\n                      (contains user))\n         (disable-emails user company)\n         (is (equal nil (users-to-email channel)))\n         (with-transaction ()\n           (push user (channel-subscribers channel)))\n         (assert-that (users-to-email channel)\n                      (contains user)))))))\n\n#-screenshotbot-oss\n(test include-arnold-when-user-exists\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (assert-that (include-arnold\n                    (list :foo))\n                   (contains :foo))\n      (setf (auth:user-email user) \"arnold@tdrhq.com\")\n      (assert-that (include-arnold\n                    (list :foo))\n                   (contains-in-any-order\n                    user :foo)))))\n\n(test users-to-email-with-cc-arnold-happy-path\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (gk:create :cc-arnold)\n      (gk:enable :cc-arnold)\n      (let* ((*installation* (make-instance 'multi-installation)))\n       (let ((channel (make-instance 'channel :company company)))\n         (assert-that (users-to-email channel)\n                      (contains user))\n         (disable-emails user company)\n         (is (equal nil (users-to-email channel)))\n         (with-transaction ()\n           (push user (channel-subscribers channel)))\n         (assert-that (users-to-email channel)\n                      (contains user)))))))\n\n(test users-to-email-will-not-include-users-removed-from-company\n  (with-fixture state ()\n    (let* ((*installation* (make-instance 'multi-installation)))\n      (let ((user-2 (make-user)))\n        (with-test-user (:user user :company company)\n          ;; user-2 is not part of company, probably because they were\n          ;; removed from the company\n          (let ((channel (make-instance 'channel :company company)))\n            (assert-that (users-to-email channel)\n                         (contains user))\n            (disable-emails user company)\n            (is (equal nil (users-to-email channel)))\n            (with-transaction ()\n              (setf (channel-subscribers channel)\n                    (list user user-2)))\n            (assert-that (users-to-email channel)\n                         (contains user))))))))\n\n(screenshot-test email-for-report-notification\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let* ((channel (make-instance 'channel\n                                     :name \"foobar\"))\n             (run (make-recorder-run\n                   :company company\n                   :channel channel))\n             (report (make-instance 'report\n                                    :title \"1 changes, 2 added\"\n                                    :channel channel\n                                    :run run)))\n        (email-content report)))))\n"
  },
  {
    "path": "src/screenshotbot/email-template.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/email-template\n  (:use #:cl)\n  (:import-from #:screenshotbot/mailer\n                #:smtp-mailer\n                #:wrap-template\n                #:mailer*\n                #:send-mail)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:export\n   #:templated-smtp-mailer))\n(in-package :screenshotbot/email-template)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun logo-dark ()\n  (quri:render-uri\n   (quri:merge-uris\n    \"/assets/images/logo-dark.png\"\n    (installation-domain *installation*))))\n\n(markup:deftag email-template (body)\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>Your Company Name - Email</title>\n  <style>\n    /* You can add your own styles here */\n    body {\n      font-family: sans-serif;\n      font-size: 16px;\n      line-height: 1.5;\n      margin: 0;\n      padding: 0;\n      color: #333;\n    }\n    a {\n      color: #333;\n      text-decoration: none;\n    }\n    a:hover {\n      color: #007bff;\n    }\n    .container {\n      padding: 20px;\n      max-width: 600px;\n      margin: 0 auto;\n    }\n    .header {\n      display: flex;\n      justify-content: center;\n      align-items: center;\n    }\n    .logo {\n      max-height: 50px;\n      max-width: 300px;\n      margin-bottom: 2em;\n      margin-top: 1em;\n    }\n    .footer {\n      text-align: center;\n      font-size: 12px;\n      margin-top: 20px;\n      color: #aaa;\n    background-color: #f0f0f0;;\n      padding: 1em 1em;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <header class=\"header\">\n      <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n        <tr>\n          <td align=\"center\">\n            <a href= \"https://screenshotbot.io\">\n              <img src= (logo-dark) alt=\"Screenshotbot\" class=\"logo\" />\n            </a>\n          </td>\n        </tr>\n      </table>\n    </header>\n\n    ,@body\n    <footer class=\"footer\">\n      <p>Modern Interpreters Inc, 75 Liberty Ave D1, Jersey City, NJ 07306, United States</p>\n      <!-- <p><a href=\"[unsubscribe link]\">Unsubscribe</a></p> -->\n    </footer>\n  </div>\n</body>\n</html>\n)\n\n#+nil\n(send-mail\n (mailer*)\n :to \"arnstein87@gmail.com\"\n :subject \"This is a test\"\n :html-message <email-template>\n    <p>hello world</p>\n               </email-template>)\n\n\n(defclass templated-mailer ()\n  ())\n\n(defclass templated-smtp-mailer (templated-mailer\n                                 smtp-mailer)\n  ())\n\n(defmethod wrap-template ((mailer templated-mailer) html-message)\n  (mquery:with-document ((call-next-method))\n    (let ((body (car (mquery:$ \"body\"))))\n     (cond\n       ((or\n         (not body)\n         (mquery:$ \"head\"))\n        ;; If there's a head tag, then it's already templated. The\n        ;; with-document returns the original message.\n        (Return-from wrap-template\n          html-message))\n       (t\n        (return-from wrap-template\n          <email-template>\n          ,@ (markup:xml-tag-children body)\n          </email-template>))))))\n"
  },
  {
    "path": "src/screenshotbot/figma/dropdown.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/figma/dropdown\n  (:use #:cl)\n  (:import-from #:markup #:deftag)\n  (:import-from #:gk #:check)\n  (:import-from #:auth #:current-company)\n  (:import-from #:screenshotbot/model/figma\n                #:figma-link-url\n                #:find-existing-figma-link)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-channel)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot-name)\n  (:import-from #:nibble #:nibble)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page)\n  (:import-from #:screenshotbot/figma/view\n                #:associate-figma)\n  (:import-from #:core/ui/mdi\n                #:mdi))\n(in-package :screenshotbot/figma/dropdown)\n\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag figma-drop-down (&key script-name run screenshot)\n  (let ((id (format nil \"a~a\" (random 10000000000)))\n        (existing-figma (find-existing-figma-link :channel\n                                                  (recorder-run-channel run)\n                                                  :screenshot-name\n                                                  (screenshot-name screenshot))))\n    <li>\n      <a href= \"#\" class= \"dropdown-toggle\" data-bs-toggle= \"dropdown\"\n         data-bs-target= id\n         aria-expanded= \"false\" >Figma</a>\n      <ul class= \"dropdown-menu\" >\n        ,(unless existing-figma\n           <li>\n             <a class= \"dropdown-item\" href= (nibble () (associate-figma :channel (recorder-run-channel run) :screenshot-name (screenshot-name screenshot)  :redirect script-name))\n                > Link to Figma</a>\n           </li>)\n\n        ,(when existing-figma\n           <li>\n             <a class= \"dropdown-item d-flex align-items-center\" href= (figma-link-url existing-figma) target= \"_blank\" >\n               <mdi name= \"open_in_new\" class= \"me-2\"/>\n               <span>View in Figma</span>\n             </a>\n           </li>)\n        ,(when existing-figma\n           <li>\n             <a class= \"dropdown-item d-flex align-items-center\" href= (nibble ()\n                                                       (delete-figma existing-figma :redirect script-name)) >\n               <mdi name= \"delete\" class= \"me-2\"/>\n               <span>Delete Figma</span>\n             </a>\n           </li>)        \n      </ul>\n    </li>))\n\n\n(defun delete-figma (existing-figma &key redirect)\n  (confirmation-page\n   :yes (nibble () (bknr.datastore:delete-object existing-figma)\n          (hex:safe-redirect redirect))\n   :no redirect\n   <div>\n     Are you sure you want to delete this association from Figma?\n   </div>))\n\n"
  },
  {
    "path": "src/screenshotbot/figma/figma.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/figma/figma\n  (:use #:cl)\n  (:export\n   #:figma))\n(in-package :screenshotbot/figma/figma)\n\n(defclass figma ()\n  ((client-id :initarg :client-id\n              :accessor figma-client-id\n              :type string\n              :documentation \"The Figma OAuth client ID\")\n   (client-secret :initarg :client-secret\n                  :accessor figma-client-secret\n                  :type string\n                  :documentation \"The Figma OAuth client secret\")))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/figma/test-view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/figma/test-view\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/figma/view\n                #:associate-figma\n                #:perform-update-image-link\n                #:download-figma-image)\n  (:import-from #:screenshotbot/model/figma\n                #:figma-link-image\n                #:figma-link-screenshot-name\n                #:figma-link-channel\n                #:figma-link-url\n                #:find-existing-figma-link)\n  (:import-from #:util/store/store\n                #:with-test-store))\n(in-package :screenshotbot/figma/test-view)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n   (with-installation ()\n     (with-fake-request ()\n       (auth:with-sessions ()\n         (&body))))))\n\n(screenshot-test associate-figma-form\n  (with-fixture state ()\n    (associate-figma)))\n\n(test perform-update-image-link-happy-path\n  (with-fixture state ()\n    (let ((channel (make-instance 'screenshotbot/model/channel:channel\n                                  :name \"test-channel\"))\n          (screenshot-name \"test-screenshot\")\n          (figma-url \"https://www.figma.com/file/ABC123/Design-Name?node-id=1%3A2\")\n          (image-url \"https://cdn.figma.com/test-image.png\")\n          (mock-image-data (make-array 100 :element-type '(unsigned-byte 8) :initial-element 42)))\n      \n      (cl-mock:with-mocks ()\n        (cl-mock:answer (download-figma-image image-url)\n          mock-image-data)\n        \n        (let ((result (catch 'hunchentoot::handler-done\n                        (perform-update-image-link\n                         :channel channel\n                         :screenshot-name screenshot-name\n                         :figma-url figma-url\n                         :image-url image-url))))\n          \n          ;; Verify that a figma-link was created\n          (let ((figma-link (find-existing-figma-link\n                             :channel channel\n                             :screenshot-name screenshot-name)))\n            (is (not (null figma-link)))\n            (is (equal figma-url (figma-link-url figma-link)))\n            (is (equal channel (figma-link-channel figma-link)))\n            (is (equal screenshot-name (figma-link-screenshot-name figma-link)))\n            (is (not (null (figma-link-image figma-link))))))))))\n\n\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/figma/view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/figma/view\n  (:use #:cl)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:hunchentoot-extensions\n                #:make-url)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/figma/figma\n                #:figma-client-secret\n                #:figma-client-id)\n  (:import-from #:screenshotbot/installation\n                #:installation-figma)\n  (:import-from #:alexandria\n                #:when-let\n                #:assoc-value)\n  (:import-from #:screenshotbot/model/image\n                #:with-tmp-image-file\n                #:make-image)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/figma\n                #:update-figma-link)\n  (:import-from #:util/form-errors\n                #:with-form-errors\n                #:with-error-builder)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:auth/login/common\n                #:make-oauth-url))\n(in-package :screenshotbot/figma/view)\n\n(named-readtables:in-readtable markup:syntax)\n\n;; TODO: take in image etc.\n(defun associate-figma (&key channel screenshot-name\n                          redirect)\n  \"REDIRECT is where we want to redirect to after the change is made.\"\n  (push-event :associate-figma)\n  (let ((submit (nibble (url)\n                  (validate-input\n                   url\n                   :channel channel\n                   :screenshot-name screenshot-name\n                   :redirect redirect))))\n    <simple-card-page form-action= submit >\n      <div class=\"form-group mb-3\">\n        <label for=\"figma-url\" class=\"form-label\">Figma Component URL</label>\n        <input type=\"url\" \n               class=\"form-control\" \n               id=\"figma-url\" \n               name=\"url\" \n               placeholder=\"https://www.figma.com/file/...\" \n               required />\n        <div class=\"form-text\">\n          Enter the URL of the Figma component or frame you want to associate\n        </div>\n      </div>\n      <button type=\"submit\" class=\"btn btn-primary\">Associate with Figma</button>\n    </simple-card-page>))\n\n(defun validate-input (url &key channel screenshot-name redirect)\n  (with-error-builder (:check check :errors errors\n                       :form-builder\n                       (associate-figma :channel channel :screenshot-name screenshot-name\n                                        :redirect redirect)\n                       :form-args (:url url)\n                       :success\n                       (%start-oauth-flow\n                        url\n                        (lambda (image-url)\n                          (perform-update-image-link :channel channel\n                                                     :screenshot-name screenshot-name\n                                                     :figma-url url\n                                                     :image-url image-url\n                                                     :redirect redirect))))\n\n    (check :url (ignore-errors (quri:uri url))\n           \"Could not parse URL\")\n    (when-let ((uri (quri:uri url)))\n      (check :url (or\n                   (equal \"www.figma.com\" (quri:uri-host uri))\n                   (equal \"figma.com\" (quri:uri-host uri)))\n             \"Must be a figma.com URL. Click on the component in Figma, and copy paste the URL here.\"))))\n\n(defun make-image-from-data (image-data &key company)\n  \"Create an IMAGE instance from binary image data\"\n  (with-tmp-image-file (:stream stream :direction :output \n                        :element-type '(unsigned-byte 8)\n                        :pathname temp-file)\n    (write-sequence image-data stream)\n    (finish-output stream)\n    (make-image :pathname temp-file\n                :company company)))\n\n(defun perform-update-image-link (&key channel screenshot-name figma-url image-url\n                                    redirect)\n  \"Downloads the Figma image and creates/updates the figma-link record\"\n  (let* ((image-data (download-figma-image image-url))\n         (image (make-image-from-data image-data\n                                      :company (company channel)))\n         (figma-link (update-figma-link\n                      :channel channel\n                      :screenshot-name screenshot-name\n                      :url figma-url\n                      :image image)))\n    (declare (ignore figma-link))\n    (hex:safe-redirect\n     (nibble ()\n       (summary-screen :image image :redirect redirect)))))\n\n(defun summary-screen (&key image redirect)\n  <simple-card-page>\n    <div class=\"text-center\">\n      <h3>Linked Figma component</h3>\n      <div class=\"mt-4\">\n        <img src= (screenshotbot/model/image:image-public-url image)\n             class=\"img-fluid\"\n             alt=\"Figma Component\" />\n      </div>\n    </div>\n\n    <div class= \"card-footer\">\n      <a href=redirect class= \"btn btn-primary\" >Go back</a>\n    </div>\n  </simple-card-page>)\n\n\n\n(defun %start-oauth-flow (figma-url callback)\n  \"Initiates the Figma OAuth flow to get access to the user's Figma files\"\n  (let* ((figma (installation-figma (screenshotbot/installation:installation)))\n         (client-id\n           (figma-client-id figma))\n         (client-secret\n           (figma-client-secret figma))\n         (callback (lambda (&key code error redirect-uri)\n                     (cond\n                       (error\n                        (error \"OAuth error: ~a\" error))\n                       (code\n                        (handle-after-oauth-response\n                         figma-url\n                         callback\n                         :code code\n                         :redirect-uri redirect-uri\n                         :client-id client-id\n                         :client-secret client-secret))\n                       (t\n                        (error \"Invalid OAuth callback state\")))))\n         (oauth-url (make-oauth-url \"https://www.figma.com/oauth\"\n                                    callback\n                                    :client_id client-id\n                                    :response_type \"code\"\n                                    :scope \"file_content:read\")))\n    (hex:safe-redirect oauth-url)))\n\n(defun parse-figma-url (url)\n  \"Parse a Figma URL to extract file-key and node-id.\n   Handles file, board, and design URLs:\n   - https://www.figma.com/file/ABC123/Design-Name?node-id=1%3A2\n   - https://www.figma.com/board/ABC123/Board-Name?node-id=1%3A2\n   - https://www.figma.com/design/ABC123/Design-Name?node-id=1%3A2\"\n  (let ((parsed (quri:uri url)))\n    (when (and (string= (quri:uri-host parsed) \"www.figma.com\")\n               (or (str:starts-with-p \"/file/\" (quri:uri-path parsed))\n                   (str:starts-with-p \"/board/\" (quri:uri-path parsed))\n                   (str:starts-with-p \"/design/\" (quri:uri-path parsed))))\n      (let* ((path-parts (str:split \"/\" (quri:uri-path parsed)))\n             (file-key (third path-parts)) ; /file/FILE_KEY/... or /board/FILE_KEY/... or /design/FILE_KEY/...\n             (query-params (quri:uri-query-params parsed))\n             (node-id (cdr (assoc \"node-id\" query-params :test #'string=))))\n        (when (and file-key node-id)\n          (values file-key\n                  (str:replace-all \"-\" \":\" (quri:url-decode node-id))))))))\n\n\n(defun fetch-figma-image (access-token file-key node-id)\n  \"Fetch image URL from Figma API for given file and node\"\n  (declare (optimize (debug 3) (speed 0)))\n  (let* ((api-url (format nil \"https://api.figma.com/v1/images/~A\" file-key))\n         (params `((\"ids\" . ,node-id)\n                   (\"format\" . \"png\")))\n         (headers `((\"Authorization\" . ,(format nil \"Bearer ~A\" access-token)))))\n    \n    (multiple-value-bind (body status-code)\n        (util/request:http-request api-url\n                                   :method :get\n                                   :parameters params\n                                   :additional-headers headers\n                                   :want-string t)\n      (if (= status-code 200)\n          (let* ((json-response (let ((json:*json-identifier-name-to-lisp* #'identity))\n                                  (json:decode-json-from-string body)))\n                 (images (assoc-value json-response \"images\" :test #'string-equal))\n                 (image-url (assoc-value images node-id :test #'string-equal)))\n            (if image-url\n                image-url\n                (hex:safe-redirect\n                 (nibble ()\n                   <simple-card-page>\n                     <p>\n                       Oops, failed to find that image. Please make sure you copy pasted the URL correctly.\n                     </p>\n                   </simple-card-page>))))\n          (error \"Figma API error: ~A\" status-code)))))\n\n(defun handle-after-oauth-response (figma-url callback &key code client-secret client-id redirect-uri)\n  \"Handle the OAuth response by exchanging the code for an access token\"\n  (let* ((token-response (util/request:http-request \n                          \"https://api.figma.com/v1/oauth/token\"\n                          :method :post\n                          :parameters `((\"client_id\" . ,client-id)\n                                        (\"client_secret\" . ,client-secret)\n                                        (\"redirect_uri\" . ,redirect-uri)\n                                        (\"code\" . ,code)\n                                        (\"grant_type\" . \"authorization_code\"))\n                          :want-string t))\n         (token-data (json:decode-json-from-string token-response))\n         (access-token (cdr (assoc :access--token token-data))))\n    (process-figma-url-for-screenshot\n     figma-url\n     callback\n     access-token)))\n\n(defun download-figma-image (image-url)\n  \"Download the actual image from Figma's CDN\"\n  (multiple-value-bind (image-data status-code)\n      (drakma:http-request image-url :want-stream nil)\n    (if (= status-code 200)\n        image-data\n        (error \"Failed to download image: ~A\" status-code))))\n\n(defun process-figma-url-for-screenshot (figma-url callback access-token)\n  \"Complete flow: parse URL, fetch from API, download image\"\n  (multiple-value-bind (file-key node-id)\n      (parse-figma-url figma-url)\n    (unless (and file-key node-id)\n      (error \"Invalid Figma URL format\"))\n    \n    (let* ((image-url (fetch-figma-image access-token file-key node-id)))\n      (funcall callback image-url))))\n\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/fixture/secrets.xml",
    "content": "<secrets>\n  <secret>\n    <name>foobar</name>\n    <value>blah</value>\n  </secret>\n  <secret environment=\"staging\">\n    <name>foobar</name>\n    <value>blah</value>\n  </secret>\n</secrets>\n"
  },
  {
    "path": "src/screenshotbot/git-repo.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/git-repo\n    (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/user-api\n                #:commit-link)\n  (:import-from #:alexandria\n                #:when-let\n                #:when-let*)\n  (:import-from #:util/events\n                #:with-tracing)\n  (:export\n   #:generic-git-repo\n   #:commit-graph\n   #:get-parent-commit\n   #:repo-ancestor-p\n   #:public-repo-p\n   #:repo-link\n   #:commit-link\n   #:find-or-create-commit-graph\n   #:commit-graph-dag\n   #:compute-merge-base\n   #:null-repo))\n(in-package :screenshotbot/git-repo)\n\n\n(defclass generic-git-repo ()\n  ((link :initarg :link\n         :accessor repo-link)\n   (company :initarg :company\n            :accessor company)))\n\n(defclass null-repo ()\n  ((company :initarg :company\n            :accessor company))\n  (:documentation \"Indicating that there's no repo attached\"))\n\n(defmethod repo-link ((self null-repo))\n  nil)\n\n\n(defmethod commit-graph ((repo generic-git-repo))\n  (find-or-create-commit-graph\n   (company repo)\n   (repo-link repo)))\n\n(defmethod get-parent-commit ((repo generic-git-repo)\n                              commit)\n  (declare (optimize (debug 3)))\n  (assert commit)\n  (when-let* ((dag (commit-graph-dag (commit-graph repo))))\n    (when-let ((commit (dag:get-commit dag commit)))\n      (car (dag:parents commit)))))\n\n(defmethod repo-ancestor-p ((repo generic-git-repo)\n                            commit\n                            branch-commit)\n  (dag:ancestorp (commit-graph-dag (commit-graph repo))\n                 commit\n                 branch-commit))\n\n(defmethod public-repo-p (repo)\n  nil)\n\n(defmethod commit-link ((repo generic-git-repo) hash)\n  \"#\")\n\n(defmethod commit-link ((repo null-repo) hash)\n  \"#\")\n\n(defmethod compute-merge-base (repo commit-1 commit-2)\n  (with-tracing (:merge-base :commit-1 commit-1 :commit-2 commit-2)\n    (dag:merge-base (commit-graph-dag (commit-graph repo))\n                    commit-1\n                    commit-2)))\n"
  },
  {
    "path": "src/screenshotbot/github/access-checks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/access-checks\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:util/java\n                #:read-java-field\n                #:java-list->list\n                #:new-instance)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object\n                #:unique-index)\n  (:import-from #:screenshotbot/secret\n                #:defsecret\n                #:secret)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:github-repo)\n  (:import-from #:screenshotbot/git-repo\n                #:public-repo-p\n                #:get-parent-commit\n                #:repo-link\n                #:commit-link\n                #:generic-git-repo)\n  (:import-from #:oidc/oidc\n                #:oauth-access-token)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/model/channel\n                #:github-get-canonical-repo)\n  (:export\n   #:github-repo\n   #:github-repos-for-user\n   #:get-repo-id\n   #:with-throttler\n   #:throttler\n   #:with-cache)\n  (:export #:fix-github-link\n           #:github-api-request))\n(in-package :screenshotbot/github/access-checks)\n\n(import 'repo-name)\n\n;; TODO: security\n\n(defclass throttler ()\n  ((lock :initform (bt:make-lock)\n         :reader throttler-lock)\n   (last-ts :initform (local-time:now)\n            :accessor last-ts)))\n\n(defparameter *github-throttler* (make-instance 'throttler))\n\n(defun %with-throttler (throttler fn)\n  (bt:with-lock-held ((throttler-lock throttler))\n    (loop while (local-time:timestamp> (last-ts throttler)\n                                       (local-time:timestamp- (local-time:now)\n                                                              (* 800 1000 1000)\n                                                              :nsec))\n          do (progn\n               (log:debug \"Waiting for throttling lock\")\n               (sleep 0.2)))\n    (setf (last-ts throttler) (local-time:now)))\n  (funcall fn))\n\n(defmacro with-throttler ((throttler) &body body)\n  `(flet ((body () ,@body))\n     (%with-throttler ,throttler #'body)))\n\n\n(defclass github-repo (generic-git-repo)\n  ((cache :initform (make-hash-table :test 'equal)\n          :accessor github-repo-cache)\n   (commit-cache :initform (make-hash-table :test 'equal)\n                 :accessor commit-cache)))\n\n(defsecret :github-user\n  \"Github user used for accessing the GitHub API. Practically, the\n  only time this is used is to check if a repository is public. We\n  also use it for some internal code at Screenshotbot.io.\n\n  If you don't care about the public repo logic, you can safely ignore\n  this. Leaving this out will treat all GitHub repos as private.\")\n\n(defsecret :github-api-secret\n  \"Github API secret key corresponding to :github-user\")\n\n\n(defun github-api-request (url &key access-token\n                                 installation-token)\n  (when (typep access-token 'oauth-access-token)\n    (setf access-token (oidc/oidc:access-token-str access-token)))\n  (check-type access-token (or null string))\n  (assert (or access-token installation-token))\n  (multiple-value-bind (response code)\n      (util/request:http-request\n       (format nil \"https://api.github.com~a\" url)\n       :want-string t\n       :additional-headers `((\"Accept\" . \"application/vnd.github+json\")\n                             (\"Authorization\" .\n                                              ,(cond\n                                                 (installation-token\n                                                  (format nil \"token ~a\" installation-token))\n                                                 (t\n                                                  (format nil \"Bearer ~a\" access-token))))\n                             (\"X-GitHub-Api-Version\" . \"2022-11-28\")))\n    (values\n     (cond\n       ((str:emptyp response)\n        ;; Some api requests don't return a body, we just use the\n        ;; response code.\n        nil)\n       (t\n        (json:decode-json-from-string\n         response)))\n     code)))\n\n(defmacro with-cache ((place args) &body body)\n  `(flet ((body () ,@body))\n     (let ((args ,args)\n           (place ,place))\n      (symbol-macrolet ((hash-val (gethash  args place)))\n        (or\n         hash-val\n         (setf hash-val (body)))))))\n\n(defun github-repos-for-user (user)\n  (let ((res (github-api-request\n              (format nil \"/users/~a/repos\" user)\n              :access-token (secret :github-api-secret))))\n    (loop for repo in res\n          collect (assoc-value repo :html--url))))\n\n\n(defun get-repo-stars (org repo)\n  (with-throttler (*github-throttler*)\n    (let ((res (github-api-request\n                (format nil \"/repos/~a/~a\" org repo)\n                :access-token (secret :github-api-secret))))\n      (values\n       (alexandria:assoc-value res :stargazers--count)\n       (alexandria:assoc-value res :forks--count)))))\n\n(defun remove-trailing-/ (url)\n  (cl-ppcre:regex-replace-all\n   \"/$\" url \"\"))\n\n(defun repo-string-identifier (repo-url)\n  (destructuring-bind (prefix org name)\n      (str:rsplit \"/\" (str:replace-all \":\" \"/\"\n                                       (cl-ppcre:regex-replace-all\n                                        \"[.]git$\"\n                                        (remove-trailing-/\n                                         repo-url) \"\"))\n                  :limit 3)\n    (format nil \"~a/~a\" org name)))\n\n\n(defvar *public-repo-p-cache* (make-hash-table :test #'equal))\n\n(defmethod public-repo-p ((repo github-repo))\n  (when (secret :github-api-secret)\n    ;; We use the fake list to make it easier to work with the NIL\n    ;; case.\n    (first\n     (util:or-setf\n      (gethash (repo-link repo) *public-repo-p-cache*)\n      (let ((repo-id (get-repo-id (repo-link repo))))\n        (list\n         (not (null (apply #'get-repo-stars\n                           (str:split \"/\" repo-id))))))))))\n\n(defun github-integration-test ()\n  (let ((repo (make-instance 'github-repo :link \"https://github.com/tdrhq/screenshotbot-example\")))\n    (assert\n     (public-repo-p repo))\n    (assert (not (public-repo-p (make-instance 'github-repo :link \"https://github.com/tdrhq/web\"))))))\n\n;; (github-integration-test)\n\n(defun get-repo-id (repo)\n  \"An alias for repo-string-identifier\"\n  (repo-string-identifier repo))\n\n\n(defmethod commit-link ((repo github-repo) hash)\n  (format nil \"https://github.com/~a/commit/~a\"\n          (get-repo-id (repo-link repo))\n          hash))\n"
  },
  {
    "path": "src/screenshotbot/github/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/github\n    (:use #:cl\n          #:alexandria)\n  (:use-reexport #:screenshotbot/github/marketplace\n                 #:screenshotbot/github/webhook\n                 #:screenshotbot/github/access-checks\n                 #:screenshotbot/github/pr-checks\n                 #:screenshotbot/github/pull-request-promoter\n                 #:screenshotbot/github/plugin))\n"
  },
  {
    "path": "src/screenshotbot/github/app-installation.lisp",
    "content": "(defpackage :screenshotbot/github/app-installation\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:screenshotbot/github/jwt-token\n                #:github-api-error-code\n                #:github-api-error\n                #:github-create-jwt-token\n                #:github-request)\n  (:import-from #:screenshotbot/github/access-checks\n                #:*github-throttler*\n                #:with-throttler)\n  (:import-from #:screenshotbot/github/plugin\n                #:private-key\n                #:github-plugin\n                #:app-id)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:github-get-access-token-for-installation\n   #:app-installed-p\n   #:*repo-added-hook*))\n(in-package :screenshotbot/github/app-installation)\n\n(defclass app-installation (store-object)\n  ((installation-id\n    :initarg :installation-id\n    :reader installation-id\n    :index-type unique-index\n    :index-reader app-installation-by-id)\n   (repos\n    :initform nil\n    :accessor app-installation-repos)\n   (updated-ts\n    :initform nil\n    :accessor updated-ts))\n  (:metaclass persistent-class))\n\n(defvar *lock* (bt:make-lock))\n\n(defvar *repo-added-hook* nil)\n\n(auto-restart:with-auto-restart ()\n  (defun handle-webhook (json)\n    (log:warn \"Handle webhook is a no-op right now\")))\n\n(defun github-get-access-token-for-installation (installation-id\n                                                 &key\n                                                   app-id\n                                                   private-key)\n  (with-throttler (*github-throttler*)\n   (a:assoc-value (github-request\n                   (format nil \"/app/installations/~a/access_tokens\" installation-id)\n                   :jwt-token (github-create-jwt-token\n                               :app-id app-id\n                               :private-key private-key)\n                   :method :post)\n                  :token)))\n\n(defun app-installed-p (repo-id)\n  (not (null (app-installation-id repo-id))))\n\n(defvar *app-installation-cache* (make-hash-table :test #'equal))\n\n(defun %app-installation-id (repo-id &key force)\n  (a:assoc-value\n   (flet ((%compute ()\n            (block inner\n              (handler-bind ((github-api-error (lambda (e)\n                                                 (when (eql 404 (github-api-error-code e))\n                                                   (return-from inner `((:id . nil)))))))\n                (github-request\n                 (format nil \"/repos/~a/installation\" repo-id)\n                 :jwt-token (github-create-jwt-token\n                             :app-id (app-id (github-plugin))\n                             :private-key (private-key (github-plugin))))))))\n     (cond\n       (force\n        (setf (gethash repo-id *app-installation-cache*)\n              (%compute)))\n       (t\n        (util:or-setf\n         (gethash repo-id *app-installation-cache*)\n         (%compute)))))\n   :id))\n\n(def-cron clr-cache (:step-min 5)\n  (clrhash *app-installation-cache*))\n\n\n(defun app-installation-id (repo-id &key force)\n  \"Get the GitHub app installation id for the given\nrepo-id (e.g. 'tdrhq/fast-example'). If FORCE is T, then we will not\nuse a cached value.\"\n  (%app-installation-id repo-id :force force))\n\n\n(def-store-migration (\"Delete app-installation objects -- T1963\" :version 35)\n  (mapc #'bknr.datastore:delete-object\n        (bknr.datastore:class-instances 'app-installation)))\n"
  },
  {
    "path": "src/screenshotbot/github/audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/audit-log\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:util/misc\n                #:?.\n                #:uniq)\n  (:import-from #:screenshotbot/audit-log\n                #:base-audit-log)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:commit-tag\n                #:describe-audit-log)\n  (:import-from #:screenshotbot/user-api\n                #:user-full-name)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:updated-check-run\n   #:user-updated-check-run\n   #:check-collaborator\n   #:updated-check-run-check))\n(in-package :screenshotbot/github/audit-log)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass github-audit-log (base-audit-log)\n  ((%company :initarg :company\n             :index-type hash-index\n             :index-reader %github-audit-logs-for-company)\n   (err :initarg :error\n        :initform nil\n        :accessor audit-log-error)\n   (error-response :initform nil\n                   :accessor audit-log-error-response)\n   (http-result-code :initform nil\n                     :accessor http-result-code)\n   (ts :initarg :ts\n       :reader %created-at))\n  (:default-initargs :ts (get-universal-time))\n  (:metaclass persistent-class))\n\n(defclass updated-check-run (github-audit-log)\n  ((commit :initarg :commit\n           :reader commit)\n   (check :transient t\n          :accessor updated-check-run-check\n          :documentation \"This is only used for debugging old checks\"))\n  (:metaclass persistent-class))\n\n(defmethod (setf updated-check-run-check) :around (value audit-log)\n  \"The checks are stored in abstract-pr-promoter's *logs* list, so as\nlong as that list is not trimmed, we'll hold onto the the check here.\"\n  (call-next-method\n   (trivial-garbage:make-weak-pointer value)\n   audit-log))\n\n(defmethod describe-audit-log ((self updated-check-run))\n  <span>\n    Updated check run on commit <commit-tag>,(commit self)</commit-tag>\n  </span>)\n\n(defclass user-updated-check-run (github-audit-log)\n  ((%%user :initarg :user\n           :reader %user)\n   (commit :initarg :commit\n           :reader commit)\n   (check :transient t\n          :accessor updated-check-run-check\n          :documentation \"This is only used for debugging old checks\"))\n  (:metaclass persistent-class))\n\n(defmethod describe-audit-log ((self user-updated-check-run))\n  <span>\n    Updated check run on commit <commit-tag>,(commit self)</commit-tag>\n    for review by ,(user-full-name (%user self))\n  </span>)\n\n(defclass user-oauth-api-request (github-audit-log)\n  ((%%user :initarg :user\n           :initform nil\n           :reader %user))\n  (:metaclass persistent-class))\n\n(defmethod describe-audit-log :around ((self user-oauth-api-request))\n  <span>\n    ,(call-next-method)\n    (using <em>,(?. user-full-name (%user self))</em>'s temporary token)\n  </span>)\n\n(defclass check-collaborator (github-audit-log)\n  ((github-login :initarg :login\n                 :initform nil\n                 :reader github-login)\n   (repo :initarg :repo\n         :initform nil\n         :reader github-repo))\n  (:metaclass persistent-class))\n\n(defmethod describe-audit-log ((self check-collaborator))\n  <span>\n    Checked if <code>,(github-login self)</code> is a collaborator on <code>,(github-repo self)</code>\n  </span>)\n"
  },
  {
    "path": "src/screenshotbot/github/fixture/private-key.README.md",
    "content": "\n# How I generated private-key-traditional.pem:\n\nopenssl genrsa -out private-key.pem 3072\n\nhttps://stackoverflow.com/questions/2957742/how-to-convert-pkcs8-formatted-pem-private-key-to-the-traditional-format:\n\nopenssl pkey -in private-key.pem -traditional > private-key-traditional.pem\n"
  },
  {
    "path": "src/screenshotbot/github/fixture/private-key.test-pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIG5AIBAAKCAYEAyXyWqL4eWAlQGsl9bR5M23mCCThA8aajm9cB2VcqJyH/Ll0F\nTAzcrKBdnp+1b0xrRA1vDGIU0ynuCH5mSV1TzrLkLcIn+dO7diCxibXbmrKxlWFi\nI/EahHhUpTPeLAqjkvWDns2yw+XGpv8pn5W3wTqLfMmPmRPlaWXHqTXQeqRqCA6t\nDGMgYOkCLPjvbl7caLh5hrZTOsKOSsAnf+S7BH8htyWAIFaPXq6BRjnI4jZsbIwn\nkCX6fs2iQBRLskK/zVgAXMzqcsKF16CcvZfqwH1zqFKiwmIF9CGCm525pDth4iGG\niVNUKxo4mdXj936I7PSMcsvPhbU3C4yNo7sXnzy1RjIlyCIa7tRJLIn4aWZW0+Q2\nhh1nnqucGREj/SFKsCBMKoM839jUmL/d79l3HfE4EKPpw9+yaMCFIbvrzXu4gY24\nfeERMAdCxHu6nrYJzGz/KsRT2YY2eeOnysrv+aXQ6Y4OManCDd3eyCdl4qkk3wRL\nwEWKqK7SoUMFxxR5AgMBAAECggGAWWptAd29YZxgbELW4sH7obq+tpC8YYFuQg6c\nx3MjRdKUiGLHowRiOknAwlBmGEp1n/HGQlyUmVGWTTaq9gvw0V4aXSQ40WnhW38O\nyupl1rVOkdhx0TmCZx9vkRD/m9Ps258RFjx7BOzHx/yc4DF1Js9wb6STkX9+49ND\nVBiJuKOWWpUIvuKOz/MYABVJTFXUwrR7Wgk3zm/g7dNwtBUx2cEBmWGPBsrjPUV8\nIUormBhG7bUVKadxDyz6wC1Bmo57/SJoRdWcb8z7v/gpXqlWsxkJ0RBArC8HS4pr\nYh1nK17/uSedmbqPrzFgYasHiI3n6UzYFVk5GFHjqT0xW6FZ5Hyu5hAN7383Ez+z\nr0Y0KLpIaqZsTo3TgOEMFR5yNM8V6A48a7Gd+G1edQT2DvHKUsfA5VDyz3rOxOMz\nJYHIRiNOMmUD3koEqe/ueGWL5hBbkczNEjauS+IIqxsHaWJQeaoIriXdKveChiXq\n2AvoPvYSzbndUwO00QQ1ACUOI8DxAoHBAP6kdn3VYR3QvyWzw8UjAPSAXKT7SCv5\nxIBfJSc0Pc9mFILUnxKqJV5ySxyH+BLERmmjbR1QRTByl7k49LM8/AKHWtybFsLJ\nqF0QWaISJThK4u/AuXra1ats8R06cFW+FwTFZ6ZgLpoB+wndbLTWiuROCRN4sEj4\nBq3NtOB0UiLAVZGoIEcjNksACSlZvIZsuCUclEXruSNUJEkqeyvFSxUixN3tWGv9\nAxwmp/yPG03oagcjfFX1EdeR/C2Dgts8SwKBwQDKj5QUSoCoezvKJjmywWi4GSSV\nwSzHEV2zrrpvcNS5xiutL+dGkJhNr25KQeejDSLeHNNnx4ihJKkC9Zp7OZQ4RYBp\nz39o8JhukVnGBiRkR1jy1KqGS2Opnogkw4TnWYflM32DxFSi5pkJEAuVtaEV4faB\nXCm8+hCj3qOIvpa+zfA2miiJ/iedMqsmarr9vK6wtiT6kmT2D4bcXeRet2UPX6Ra\n7gicMYDkeTQRJHQIAS5LEvSNaHNIY7rGeDrFr8sCgcEA++Y3rU0rMDJzFNna3SY+\naKCehTpq78253H/CoiCk0Ikb2zx4TB9yrhaERSogdngha5kWdLwWZdHGU52LO9Al\nQF8jvD3Ta0fPvJhTAufOV/yEuEW/dlAKtPUlJBPOUpOj871QRp65GvLAosafJdli\nNm0Xv2JRiS2Kdd6bgrqvEd+QBnH51EV6DQFxDq3NuHDOTTATYYPJdrBi4NocVHVs\nS/FELIIBSXpV3EBFR6tNiGlUkdFBy0qOpuCsRV3mUfg3AoHAIEL57LlpAGyQO0QX\nUHcpfJjUkN1xiksjx2h+3Rz1ZJlBf5XvmfeRql3UjRMuF3QSJ5ojuZW4FyJFoOkJ\nL7Md/lBjgXbA7i/Tt1iDGd0EkswiSJrChh0fxHYxbNmPI2HJGSI4TXJJoKBlo2Hg\n1nlVLK0Om8/X/fc+Czt8hF0GmWEnbyriZggKuyjExZueKKeSkyaPgTErz1ztt89k\nfglrmSU8ghcDXA0uYDVFq61IpbW6b86XEKagJdlseAvSIIY9AoHBALqUtgiVM3Dt\nqLKvJoLfNAkP5plzIzSazPJyPom7ZdlDOU4pBvCgvkb4N+8S0zKZSIcX/hksHnP4\n1tYON1K6bsaLhpBAR72ZZz/4TpgbekexyTNKNdrMkpAi/LS6rHuKS+KUDK7NwNnR\nNUZVkf1Ws38Gm+TDYG/T4fqnqOU639zI4pUvbO0QjVlZCBU7tezW1aEQUM2kiJXq\n+44y8wEz/oPWavc+nJJkQ6KzeeD54XeHEnfcn/8pWyv8hA0i2TyZiA==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/screenshotbot/github/github-installation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/github-installation\n  (:use #:cl #:alexandria)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:unique-index\n                #:store-object)\n  (:import-from #:screenshotbot/model/company\n                #:installation-id)\n  (:export\n   #:github-installation\n   #:github-installation-with-repo-name\n   #:installation-id))\n(in-package :screenshotbot/github/github-installation)\n\n(defclass github-installation (store-object)\n  ((repo-name\n    :initarg :repo-name\n    :index-type unique-index\n    :index-initargs (:test #'equal)\n    :index-reader github-installation-with-repo-name)\n   (installation-id\n    :initarg :installation-id\n    :accessor installation-id))\n  (:metaclass persistent-class)\n  (:documentation \"UNUSED! Do not use for anything, this is here for backward\n compatibility with the store.\"))\n"
  },
  {
    "path": "src/screenshotbot/github/jwt-token.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :screenshotbot/github/jwt-token\n    (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/server\n                #:*root*)\n  (:import-from #:util/request\n                #:http-request)\n  (:export\n   #:github-request\n   #:github-create-jwt-token))\n(in-package :screenshotbot/github/jwt-token)\n\n\n(defun to-unix-time (time)\n  \"Convert universal time to New Jersey time\"\n  (when time (- time (encode-universal-time 0 0 0 1 1 1970 0))))\n\n(defun github-create-jwt-token (&key app-id\n                                  private-key\n                                  pem-file)\n  (when pem-file\n    (setf private-key (uiop:read-file-string pem-file)))\n  ;; todo: this seems unnecessary. The PEM library currently only\n  ;; exposes files. On a multi-tenant server, this might also leak the\n  ;; private key.\n  (uiop:with-temporary-file (:stream s :pathname pem-file\n                             :direction :output :type \"pem\")\n    (write-string private-key s)\n    (finish-output s)\n    (let* ((key (pem:read-from-file pem-file))\n           (ts (to-unix-time (get-universal-time))))\n      (assert key)\n      (jose:encode :rs256\n                   key\n                   `((\"iss\" . ,(format nil \"~a\" app-id))\n                     (\"iat\" . ,ts)\n                     (\"exp\" . ,(+ 300 ts)))))))\n\n(define-condition github-api-error (error)\n  ((code :initarg :code\n         :reader github-api-error-code)\n   (message :initarg :message\n            :reader message)\n   (url :initarg :url\n        :initform nil\n        :reader github-api-error-url\n        :documentation \"This is mainly used for filtering Sentry crashes\"))\n  (:report (lambda (e output)\n             (with-slots (code message) e\n               (format output \"Got bad github error code: ~a (~S)\"\n                       code message)))))\n\n(auto-restart:with-auto-restart (:retries 3)\n  (defun github-request (url\n                         &key parameters installation-token\n                           jwt-token\n                           (json-parameters nil) ;; boolean\n                           (method :get))\n    (when (and parameters (eql method :get))\n      (error \"parameters not supported with :GET\"))\n    (multiple-value-bind (s res)\n        (http-request\n         (format nil \"https://api.github.com~a\" url)\n         :method method\n         :want-string t\n         :additional-headers\n         `((\"Accept\" . \"application/vnd.github.v3+json\")\n           (\"Authorization\"\n            .\n            ,(cond\n               (installation-token\n                (format nil \"token ~a\" installation-token))\n               (jwt-token\n                (format nil \"Bearer ~a\" jwt-token))\n               (t\n                (error \"specify either :jwt-token or :installation-token\")))))\n         :content (if json-parameters\n                      (json:encode-json-to-string parameters)\n                      parameters))\n      (unless (or (eql res 200) (eql res 201))\n        (error 'github-api-error\n               :code res\n               :message s\n               :url url))\n      (json:decode-json-from-string s))))\n"
  },
  {
    "path": "src/screenshotbot/github/marketplace.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/marketplace\n  (:use #:cl #:alexandria)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util\n                #:object-with-oid\n                #:oid)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:export #:github-marketplace-event))\n(in-package :screenshotbot/github/marketplace)\n\n\n\n(defclass github-marketplace-event (object-with-oid)\n  ((payload :initarg :payload\n            :reader %payload)\n   (secret :initarg :secret)\n   (signature :initarg :signature)\n   (verified-p :initform nil)\n   (created-at :initform (get-universal-time)))\n  (:metaclass persistent-class))\n\n(defmethod payload ((ev github-marketplace-event))\n  (json:decode-json-from-string (%payload ev)))\n\n(defhandler (nil :uri \"/gh-marketplace-event\" :method :post) (payload secret)\n  (let ((event (make-instance 'github-marketplace-event\n                              :payload payload\n                              :signature (hunchentoot:header-in* \"X-Hub-Signature-256\" )\n                              :secret secret)))\n    (log:info \"Got marketplace-event: ~a\" (oid event))\n    \"OK\"))\n"
  },
  {
    "path": "src/screenshotbot/github/plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/plugin\n  (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/plugin\n                #:plugin\n                #:plugin-parse-repo)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:github-repo)\n  (:import-from #:screenshotbot/installation\n                #:find-plugin\n                #:installation)\n  (:export\n   #:github-plugin\n   ;; todo: separate these to\n   #:github-repo\n   #:app-id\n   #:private-key\n   #:app-name\n   #:webhook-secret\n   #:webhook-relays\n   #:verification-oauth-provider))\n(in-package :screenshotbot/github/plugin)\n\n(defclass github-plugin (plugin)\n  ((app-name :initarg :app-name\n             :accessor app-name)\n   (app-id :initarg :app-id\n           :accessor app-id)\n   (private-key :initarg :private-key\n                :accessor private-key)\n   (%verified-orgs :initarg :verified-orgs\n                   :initform nil\n                   :reader verified-orgs\n                   :documentation \"A list of orgs for which we don't have to do the manual OAuth verification. If T, then all orgs will be automatically verified. This is reasonable if you have control\nover the instance and it's only used by people you know.\")\n   (verification-oauth-provider\n    :initarg :verification-oauth-provider\n    :accessor verification-oauth-provider\n    :documentation \"Similar to the login OAuth for GitHub, it's only used as an authorization to verify that the user can access the specific repo.\")))\n\n(defmethod initialize-instance :after ((plugin github-plugin)\n                                       &key private-key-file &allow-other-keys)\n  (when private-key-file\n    (setf (private-key plugin)\n          (uiop:read-file-string private-key-file))))\n\n(let ((cache nil)\n      (lock (bt:make-lock)))\n  (defun make-github-repo (&key link company)\n    (bt:with-lock-held (lock)\n      (symbol-macrolet ((place (assoc-value cache (cons link company) :test 'equal)))\n        (or place\n            (setf place (make-instance 'github-repo :link link\n                                                    :company company)))))))\n\n(defmethod plugin-parse-repo ((plugin github-plugin)\n                              company\n                              repo-str)\n  (log:debug \"Parsing repo for company ~a\" company)\n  (when (str:containsp \"github\" repo-str)\n    (make-github-repo :link repo-str\n                      :company company)))\n\n(defun github-plugin (&key (installation (installation)))\n  (find-plugin installation 'github-plugin))\n"
  },
  {
    "path": "src/screenshotbot/github/pr-checks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/pr-checks\n  (:use #:cl #:alexandria)\n  (:import-from #:util/java\n                #:read-java-field\n                #:new-instance)\n  (:import-from #:screenshotbot/server\n                #:*root*)\n  (:import-from #:screenshotbot/github/access-checks\n                #:with-throttler\n                #:*github-throttler*)\n  (:import-from #:screenshotbot/github/jwt-token\n                #:github-create-jwt-token\n                #:github-request)\n  (:import-from #:screenshotbot/github/app-installation\n                #:github-get-access-token-for-installation)\n  (:import-from #:screenshotbot/events\n                #:with-event)\n  (:export\n   #:github-update-pull-request))\n(in-package :screenshotbot/github/pr-checks)\n\n\n;; (github-get-access-token-for-installation 16121814)\n\n(defun github-create-check-run (full-name\n                                &key name\n                                  installation-token\n                                  head-sha\n                                  details-url\n                                  status\n                                  output\n                                  conclusion\n                                  external-id)\n  (with-throttler (*github-throttler*)\n    (let ((response\n           (github-request\n            (format nil \"/repos/~a/check-runs\" full-name)\n            :method :post\n            :json-parameters t\n            :installation-token installation-token\n            :parameters `((\"name\" . ,name)\n                          (\"head_sha\" . ,head-sha)\n                          (\"external_id\" . ,(or external-id \"internal\"))\n                          ,@ (when conclusion\n                               `((\"conclusion\" . ,conclusion)))\n                          ,@ (if details-url\n                                 `((\"details_url\" . ,details-url)))\n                          (\"status\" . ,(or status \"\"))\n                          (\"output\" . ,output)))))\n      (log:debug \"Got response: ~S\" response)\n      response)))\n\n\n(defun github-update-pull-request (&rest all-args &key\n                                                    app-id\n                                                    private-key\n\n                                                    full-name\n                                                    check-name\n                                                    status\n                                                    (installation-id (error \"Missing :installation-id\"))\n                                                    details-url\n                                                    output\n                                                    conclusion\n                                                    head-sha)\n  (assert (member status (list nil :queued :in-progress :completed)))\n  (log:debug \"Updating pull request on ~s\" full-name)\n  (with-event (:github.create-check-run)\n    (github-create-check-run\n     full-name\n     :name check-name\n     :head-sha head-sha\n     :conclusion conclusion\n     :details-url details-url\n     :output output\n     :status (when status (str:replace-all \"-\" \"_\" (str:downcase status)))\n     :installation-token\n     (github-get-access-token-for-installation\n      installation-id\n      :app-id app-id\n      :private-key private-key))))\n"
  },
  {
    "path": "src/screenshotbot/github/pull-request-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/pull-request-promoter\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/promote-api\n        #:screenshotbot/dashboard/reports\n        #:screenshotbot/model/report\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/github/plugin\n        #:screenshotbot/model/channel\n        #:screenshotbot/compare\n        #:screenshotbot/github/pr-checks\n        #:screenshotbot/git-repo\n        #:screenshotbot/user-api\n        #:screenshotbot/github/access-checks\n        #:screenshotbot/github/github-installation\n        #:screenshotbot/model/user\n        #:screenshotbot/model/company\n        #:screenshotbot/abstract-pr-promoter)\n  (:nicknames #:sb.pr #:screenshotbot.pr)\n  (:import-from #:util #:oid)\n  (:import-from #:bknr.datastore\n                #:with-transaction\n                #:persistent-class)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-title)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-page)\n  (:import-from #:screenshotbot/github/settings\n                #:verified-repo-p)\n  (:import-from #:screenshotbot/github/app-installation\n                #:app-installation-id\n                #:app-installed-p)\n  (:import-from #:screenshotbot/github/access-checks\n                #:repo-string-identifier)\n  (:import-from #:screenshotbot/audit-log\n                #:with-audit-log)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-report\n                #:report-company)\n  (:import-from #:screenshotbot/github/audit-log\n                #:updated-check-run-check\n                #:user-updated-check-run\n                #:updated-check-run)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:unreviewable-run-p\n                #:check-key\n                #:check-sha\n                #:make-promoter-for-acceptable\n                #:check-user\n                #:push-remote-check\n                #:abstract-pr-acceptable\n                #:abstract-pr-promoter)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/github/plugin\n                #:github-plugin)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-work-branch)\n  (:import-from #:util/logger\n                #:format-log)\n  (:import-from #:screenshotbot/batch-promoter\n                #:emoticon-for-status)\n  (:export\n   #:pull-request-promoter\n   #:pr-acceptable\n   #:format-updated-summary)\n  (:local-nicknames (#:batch #:screenshotbot/model/batch)))\n(in-package :screenshotbot/github/pull-request-promoter)\n\n(named-readtables:in-readtable markup:syntax)\n\n(with-class-validation\n (defclass pr-acceptable (abstract-pr-acceptable)\n   ((send-task-args :accessor send-task-args\n                    :initarg :send-task-args))\n   (:metaclass persistent-class)))\n\n\n(defclass pull-request-promoter (abstract-pr-promoter)\n  ())\n\n(defmethod plugin-promoter ((plugin github-plugin))\n  (make-instance 'pull-request-promoter))\n\n(defmethod make-promoter-for-acceptable ((self pr-acceptable))\n  (make-instance 'pull-request-promoter))\n\n(defmethod plugin-installed? ((promoter pull-request-promoter)\n                               company\n                              repo-url)\n  (cond\n    ((not (verified-repo-p repo-url company))\n     (log:info \"Repo ~a not verified\" repo-url)\n     nil)\n    ((not (app-installed-p (repo-string-identifier repo-url)))\n     (log:info \"The Screenshotbot app is not installed on ~a\" repo-url)\n     nil)\n    (t t)))\n\n(defmethod valid-repo? ((promoter pull-request-promoter)\n                        repo)\n  (typep repo 'github-repo))\n\n(defmethod repo-full-name (repo)\n  (repo-full-name (repo-link repo)))\n\n(defmethod repo-full-name ((repo-link string))\n  (multiple-value-bind (full parts)\n      (cl-ppcre:scan-to-strings \"^https://.*/(.*/.*)$\"\n                                (github-get-canonical-repo\n                                 repo-link))\n    (assert full)\n    (elt parts 0)))\n\n\n(defmethod unreviewable-run-p ((promoter pull-request-promoter) (run recorder-run))\n  (or\n   (has-merge-queue-branch run)\n   (call-next-method)))\n\n(defun has-merge-queue-branch (run)\n  (let ((branch (recorder-run-work-branch run)))\n    (and branch\n         (or\n          (str:starts-with-p \"gh-readonly-queue/\" branch)\n          ;; For ease of testing merge queue behavior:\n          (str:starts-with-p \"sbfake-gh-readonly-queue-\" branch)))))\n\n;; TODO: delete\n(defmethod maybe-promote ((promoter pull-request-promoter) run)\n  (call-next-method))\n\n(auto-restart:with-auto-restart (:retries 5)\n  (defmethod push-remote-check ((promoter pull-request-promoter)\n                                run check)\n    (let ((args (make-github-args run check))\n          (audit-log-args (list\n                           :company (recorder-run-company run)\n                           :commit (check-sha check))))\n      (with-audit-log (updated-check-run\n                       (trivia:match (check-user check)\n                         (nil\n                          (apply #'make-instance 'updated-check-run\n                                 audit-log-args))\n                         (user\n                          (apply #'make-instance 'user-updated-check-run\n                                 :user user\n                                 audit-log-args))))\n        (setf (updated-check-run-check updated-check-run) check)\n        (apply #'github-update-pull-request args)))))\n\n(defmethod promoter-pull-id ((promoter pull-request-promoter)\n                             run)\n  (pull-request-url run))\n\n\n(defmethod %make-github-summary ((run recorder-run) check)\n  (markup:write-html\n   ;; CAUTION: EMPTY LINES MATTER IN THIS CODE, else GitHub won't parse\n   ;; the table correctly.\n   <table>\n     <tr>\n       <td>\n         ,(emoticon-for-status (check-status check))\n       </td>\n       <td>\n         <a href= (details-url check) >\n           ,(check-key check)\n         </a>\n       </td>\n       <td>\n         ,(check-title check)\n       </td>\n     </tr>\n   </table>))\n\n(defmethod %make-github-summary ((batch batch:batch) check)\n  \"If it's already a batch, don't tweak the summary. Currently batches\ngenerate a GitHub markdown summary.\"\n  (check-summary check))\n\n(defmethod %make-github-summary ((run unchanged-run) check)\n  \"For unchanged runs, just use the check summary directly.\"\n  (check-summary check))\n\n(defun make-github-args (run check)\n  (let* ((repo-url (recorder-run-repo-url run))\n         (full-name (repo-full-name repo-url))\n         (github-plugin (github-plugin)))\n    (list :app-id (app-id github-plugin)\n          :private-key (private-key github-plugin)\n          :full-name full-name\n          :check-name (format nil \"Screenshotbot Changes: ~a \"\n                              (check-key check))\n          :output `((\"title\" . ,(check-title check))\n                    (\"summary\" . ,(%make-github-summary run check)))\n          :details-url (details-url check)\n          :status (if (eql (check-status check) :pending)\n                      :in-progress\n                      :completed)\n          :installation-id (app-installation-id (repo-string-identifier repo-url))\n          :conclusion (unless (eql (check-status check) :pending)\n                        (ecase (check-status check)\n                          (:accepted \"success\")\n                          (:rejected \"failure\")\n                          (:success \"success\")\n                          (:failure \"failure\")\n                          (:action-required \"action_required\")))\n          :head-sha (check-sha check))))\n\n\n(defmethod make-acceptable ((promoter pull-request-promoter) report\n                            &rest args)\n  (apply #'make-instance 'pr-acceptable\n         :report report\n         args))\n\n(defmethod notify-pr ((acceptable pr-acceptable)\n                      &key title\n                        summary\n                        ))\n\n(defmethod maybe-send-tasks ((promoter pull-request-promoter)\n                             run)\n  \"TODO: delete\"\n  (values))\n"
  },
  {
    "path": "src/screenshotbot/github/read-repos.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/read-repos\n  (:use #:cl)\n  (:import-from #:screenshotbot/github/access-checks\n                #:github-api-request\n                #:get-repo-id)\n  (:import-from #:oidc/oidc\n                #:access-token-str)\n  (:import-from #:screenshotbot/audit-log\n                #:with-audit-log)\n  (:import-from #:screenshotbot/github/audit-log\n                #:check-collaborator)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:util/misc\n                #:not-null!)\n  (:import-from #:screenshotbot/github/jwt-token\n                #:github-request)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:read-repo-list\n   #:can-edit-repo))\n(in-package :screenshotbot/github/read-repos)\n\n(defun whoami (access-token)\n  (let ((response (github-api-request \"/user\"\n                                      :access-token access-token)))\n    (not-null!\n     (a:assoc-value\n      response\n      :login))))\n\n(defun repo-collaborator-p (repo handle &key access-token\n                                          installation-token\n                                          company)\n  (log:info \"Checking for repo collaboractor: ~a, ~a\" repo handle)\n  (with-audit-log (log (make-instance 'check-collaborator :login handle\n                                                          :company company\n                                                          :repo repo))\n    (declare (ignore log))\n    (let ((url (format nil \"/repos/~a/collaborators/~a\"\n                                    (get-repo-id repo)\n                                    handle)))\n      (log:info \"url is: ~a \" url)\n      (multiple-value-bind (response ret)\n          (github-api-request url\n                              :access-token access-token\n                              :installation-token installation-token)\n        (unless (= ret 204)\n          (warn \"not a collaborator: ~a\" response))\n        (values\n         (= ret 204)\n         (a:assoc-value response :message))))))\n\n(defun can-edit-repo (access-token repo\n                      &key user company)\n  (let* ((access-token (cond\n                         ((stringp access-token)\n                          access-token)\n                         (t\n                          (access-token-str access-token))))\n         (handle (whoami access-token)))\n    (repo-collaborator-p repo handle\n                         :access-token access-token\n                         :company company)))\n"
  },
  {
    "path": "src/screenshotbot/github/repo-push-webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/repo-push-webhook\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/misc\n                #:not-null!))\n(in-package :screenshotbot/github/repo-push-webhook)\n\n(defhandler (nil :uri \"/github/:companyid/push/:org/:repo\") (companyid org repo)\n  (let* ((payload (hunchentoot:raw-post-data :force-binary t)))\n    (let* ((payload (json:decode-json-from-string (flex:octets-to-string payload :external-format :utf-8)))\n           (Commits (assoc-value payload :Commits)))\n      (warn \"unimpl ~a\" (hunchentoot:headers-in*) ))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/github/review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/review-link\n  (:use #:cl)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:get-canonical-pull-request-url)\n  (:import-from #:screenshotbot/git-repo\n                #:repo-link)\n  (:import-from #:screenshotbot/github/access-checks\n                #:github-repo)\n  (:import-from #:screenshotbot/model/channel\n                #:github-get-canonical-repo))\n(in-package :screenshotbot/github/review-link)\n\n(defmethod get-canonical-pull-request-url ((repo github-repo) pull-request-id)\n  (format nil \"~a/pull/~d\"\n          (github-get-canonical-repo\n           (repo-link repo))\n          pull-request-id))\n"
  },
  {
    "path": "src/screenshotbot/github/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/settings\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:screenshotbot/model/company\n        #:screenshotbot/model/github\n        #:screenshotbot/user-api\n        #:screenshotbot/settings-api)\n  (:import-from #:screenshotbot/github/plugin\n                #:verified-orgs\n                #:verification-oauth-provider\n                #:private-key\n                #:app-id\n                #:app-name\n                #:github-plugin)\n  (:import-from #:screenshotbot/server\n                #:staging-p\n                #:defhandler\n                #:with-login)\n  (:import-from\n   #:bknr.datastore\n   #:with-transaction)\n  (:local-nicknames (#:a #:alexandria))\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/github/read-repos\n                #:repo-collaborator-p\n                #:whoami\n                #:can-edit-repo\n                #:read-repo-list)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:screenshotbot/github/access-checks\n                #:get-repo-id\n                #:repo-string-identifier)\n  (:import-from #:screenshotbot/github/app-installation\n                #:*repo-added-hook*\n                #:github-get-access-token-for-installation\n                #:app-installation-id\n                #:app-installed-p)\n  (:import-from #:screenshotbot/template\n                #:mdi)\n  (:import-from #:screenshotbot/github/audit-log\n                #:github-audit-log\n                #:github-audit-logs-for-company)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:render-audit-logs)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors)\n  (:export\n   #:verified-repo-p))\n(in-package :screenshotbot/github/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun github-app-installation-callback (state installation-id setup-action)\n  (restart-case\n      (with-login ()\n        (let ((config (github-config (current-company))))\n          (cond\n            ((str:s-member (list \"install\" \"update\") setup-action)\n             (with-transaction ()\n               (setf (installation-id config)\n                     (parse-integer installation-id))))\n            (t\n             (error \"unsupported setup-action: ~S\" setup-action))))\n        (hex:safe-redirect \"/settings/github\"))\n    (retry-app-installation-callback ()\n      (github-app-installation-callback state installation-id setup-action))))\n\n\n(with-class-validation\n  (defclass verified-repo (store-object)\n    ((%company :initarg :company\n               :reader company\n               :index-type hash-index\n               :index-reader %verified-repos-for-company)\n     (repo-id :initarg :repo-id\n              :reader repo-id)\n     (installer-login :initarg :installer-login\n                      :reader installer-login\n                      :documentation \"The github login (e.g. tdrhq) of\nthe person who installed this repo on Screenshotbot. This is used later\nto verify by the GitHub app to verify that this user can access this repository.\")\n     (verified-p :initform nil\n                 :accessor verified-p\n                 :documentation \"Verified that we have access to this repo, or at\nleast that we had access to this repo at some point in the past. The app could've been uninstalled at\nthis point, but the verification will remain. (i.e. `verification` doesn't track\nwhether the app is installed, use APP-INSTALLATION-ID to test that instead\nindependetly)\")\n     (verification-failure-message :initform nil\n                                   :accessor verification-failure-message))\n    (:metaclass persistent-class)))\n\n(defun repo-added-hook (full-name)\n  (ignore-and-log-errors ()\n    (log:info \"Processing ~A\" full-name)\n    (loop for verified-repo in (bknr.datastore:class-instances 'verified-repo)\n          if (string-equal full-name (repo-id verified-repo))\n            do\n               (maybe-verify-repo verified-repo))))\n\n(pushnew 'repo-added-hook *repo-added-hook*)\n\n(deftag refresh (&key repo)\n  (let* ((post (nibble ()\n                 (maybe-verify-repo repo)\n                 (hex:safe-redirect \"/settings/github\")))\n         (form-id (format nil \"a-~a\" (random 10000000000)))\n         (href (format nil \"javascript:document.getElementById(\\\"~a\\\").submit()\"\n                       form-id)))\n    <form action=post method=\"POST\"  class= \"d-inline-block\" id=form-id >\n      <a href=href >\n        <mdi name= \"refresh\" />\n      </a>\n    </form>))\n\n(defun verify-repo (repo access-token)\n  (let* ((errors)\n         (repo (str:trim repo))\n         (repo-id (ignore-errors (get-repo-id repo))))\n    (flet ((check (field test message)\n             (unless test\n               (push (cons field message)\n                     errors))))\n      (check :repo\n             (and\n              (cl-ppcre:scan \"https://github.com/.*/.*\" repo)\n              repo-id)\n             \"You must provide a valid GitHub repository\")\n      (when repo-id\n        (check :repo\n               (not (str:s-member (verified-repos (current-company)) repo-id))\n               \"We're already tracking a repo with that ID\"))\n\n      (cond\n        (errors\n         (with-form-errors (:was-validated t\n                            :repo repo\n                            :errors errors)\n           (settings-github-page)))\n        (t\n         (let ((verified-repo\n                 (make-instance 'verified-repo\n                                :installer-login (whoami access-token)\n                                :company (current-company)\n                                :repo-id (repo-string-identifier repo))))\n           (maybe-verify-repo verified-repo))\n         (hex:safe-redirect \"/settings/github\"))))))\n\n(defmethod maybe-verify-repo ((self verified-repo))\n  (log:info \"Verifying: ~a\" (repo-id self))\n  (a:when-let ((installation-id (app-installation-id (repo-id self) :force t)))\n    (log:info \"using installation-id: ~a\" installation-id)\n    (multiple-value-bind (can-edit-p message)\n        (repo-collaborator-p (format nil \"https://github.com/~a\" (repo-id self))\n                             (installer-login self)\n                             :installation-token\n                             (github-get-access-token-for-installation\n                              installation-id\n                              :app-id (app-id (github-plugin))\n                              :private-key (private-key (github-plugin)))\n                             :company (company self))\n      (cond\n        (can-edit-p\n         (log:info \"Can edit the repository ~a\" (repo-id self))\n         (with-transaction ()\n           (setf (verified-p self) t\n                 (verification-failure-message self) nil)))\n        ((not (verified-p self))\n         (with-transaction ()\n           (setf (verification-failure-message self) message)))\n        (t\n         ;; We can't re-verify, but the repository has been verified\n         ;; in the past so we don't change anything. This might happen\n         ;; because the GitHub user has been booted off the org, but\n         ;; that's fine.\n         (values))))))\n\n(defun verified-repos (company)\n  \"Returns all tracked repos, but importantly, not all the repos here\nmight have verified-p=t. :/ We should consolidate this later.\"\n  (let ((repos (%verified-repos-for-company company)))\n    (let ((table (make-hash-table :test #'equal)))\n      (loop for repo in repos do\n        (setf (gethash (repo-id repo) table) t))\n      (sort (a:hash-table-keys table)\n       #'string<))))\n\n(defun verified-repo-p (repo company)\n  (let ((repo-id (repo-string-identifier repo)))\n    (destructuring-bind (org &rest project-args) (str:split \"/\" repo-id)\n      (declare (ignore project-args))\n      (or\n       (loop for repo in (%verified-repos-for-company company)\n             if (and (equal repo-id (repo-id repo))\n                     (verified-p repo))\n               return t)\n       (eql t (verified-orgs (github-plugin)))\n       (str:s-member (verified-orgs (github-plugin)) org)))))\n\n(defun remove-verification (verified-repo)\n  (let ((redirect \"/settings/github\"))\n   (confirmation-page\n    :yes (nibble ()\n           (bknr.datastore:delete-object verified-repo)\n           (hex:safe-redirect redirect))\n    :no redirect\n    :danger t\n    <div>\n      <p>Removing verification does <b>not</b> uninstall the GitHub app.</p>\n\n      <p>Removing the verification prevents Screenshotbot from sending build statuses to GitHub.</p>\n\n      <p>Are you sure you want to remove this verification?</p>\n    </div>)))\n\n(defun settings-github-page ()\n  (let* ((installation-id (installation-id (github-config (current-company))))\n         access-token\n         (app-configuration-url\n           (format nil \"https://github.com/apps/~a/installations/new\"\n                    (app-name (github-plugin))))\n         (verify-repo (nibble (repo)\n                        (hex:safe-redirect\n                         (uiop:call-function\n                          ;; TODO: cleanup dependency\n                          \"screenshotbot/login/github-oauth:make-gh-oauth-link\"\n                          (verification-oauth-provider (github-plugin))\n                          (nibble ()\n                            (verify-repo repo access-token))\n                          :access-token-callback (lambda (token)\n                                                   (setf access-token token))\n                          :scope \"user:email read:org repo\")))))\n    <settings-template>\n      <div class= \"card mt-3\" style= \"max-width: 80em;\" >\n        <div class= \"card-header\">\n          <h3>Setup GitHub Checks</h3>\n        </div>\n\n        <div class= \"card-body\" >\n          <p>In order to enable GitHub Checks you first need to verify that you have access to the repository, and then install the Screenshotbot app on the repository or organization.</p>\n\n          <form class= \"mb-3 mt-3\" action=verify-repo method= \"POST\" >\n            <label for= \"repo\" class= \"form-label\" >Verify your GitHub repository</label>\n            <div class= \"input-group\" style= \"max-width: 50em\" >\n              <input id= \"repo\" name= \"repo\"\n                     type= \"text\" class= \"form-control\" placeholder= \"https://github.com/org/repo\" />\n              <input type= \"submit\" class= \"btn btn-primary\" value= \"Verify Repository\" />\n            </div>\n          </form>\n\n        ,(let ((verified-repos (%verified-repos-for-company (current-company))))\n           (cond\n             (verified-repos\n              <div style=\"margin-top: 3em\" >\n\n                <h4>Verified repositories</h4>\n                <table class= \"table table-borderless table-hover\" >\n                  <thead>\n                    <tr>\n                      <th>Repository</th>\n                      <th>Status</th>\n                      <th>Actions</th>\n                    </tr>\n                  </thead>\n                  <tbody>\n\n                  ,@ (loop for repo in verified-repos\n                           for app-installed-p = (app-installed-p (repo-id repo))\n                           collect\n                           (util:copying (repo)\n                             (let ((remove-verification (nibble ()\n                                                          (remove-verification repo))))\n                               <tr class= \"vertical-align-middle\" >\n                                 <td>\n                                   ,(repo-id repo)\n                                 </td>\n                                 ,(cond\n                                    ((and\n                                      app-installed-p\n                                      (verified-p repo))\n                                     <markup:merge-tag>\n                                       <td>\n                                         <span>\n                                           <span class= \"text-success\">\n                                             <mdi name= \"done\" /> Verified, and App is installed\n                                           </span>\n                                         </span>\n                                       </td>\n                                       <td>\n                                         <a href= app-configuration-url >\n                                           Configure on GitHub\n                                         </a>\n                                         ,(progn \"|\")\n                                         <a href= remove-verification >Remove</a>\n                                       </td>\n                                     </markup:merge-tag>)\n                                    ((and\n                                      app-installed-p\n                                      (not (verified-p repo))\n                                      (not (verification-failure-message repo)))\n                                     <markup:merge-tag>\n                                       <td>\n                                         <span>Awaiting verification <refresh repo=repo /></span>\n                                       </td>\n                                       <td>\n                                         <a href= remove-verification >Remove</a>\n                                       </td>\n                                     </markup:merge-tag>)\n                                    ((and\n                                      app-installed-p\n                                      (not (verified-p repo)))\n                                     <markup:merge-tag>\n                                       <td>\n                                         <span>\n                                           <span class= \"text-danger\">\n                                             <mdi name= \"error\" />Could not verify\n                                             (GitHub said: ,(verification-failure-message repo))\n                                             <refresh repo=repo />\n                                           </span>\n                                         </span>\n                                       </td>\n                                       <td>\n                                         <a href= app-configuration-url >\n                                           Configure on GitHub\n                                         </a>\n                                         ,(progn \"|\")\n                                         <a href= remove-verification >Remove</a>\n                                       </td>\n                                     </markup:merge-tag>)\n                                    (t ;; (not app-installed-p)\n                                     <markup:merge-tag>\n                                       <td>\n                                         <span class= \"text-danger\">\n                                           <mdi name= \"error\" />\n                                           App not installed\n                                           <refresh repo=repo />\n\n                                         </span>\n                                       </td>\n                                       <td>\n                                         <a href= app-configuration-url >Install App</a>\n                                         ,(progn \"|\")\n                                         <a href= remove-verification >Remove</a>\n                                       </td>\n                                     </markup:merge-tag>))\n                               </tr>)))\n\n                             </tbody>\n                           </table>\n              </div>)))\n\n\n\n          <div class= \"alert alert-info\" >\n            The GitHub app does <b>not</b> get permissions to access to your repositories, it only needs write access to the Checks API. We will request access to read repository metadata only during the verification step.\n          </div>\n        </div>\n\n        <div class= \"card-footer\">\n\n          <a href= app-configuration-url\n             class= (if installation-id \"btn btn-outline-secondary\" \"btn btn-outline-primary\") >\n            ,(if installation-id\n                 \"Configure\"\n                 \"Install App on GitHub\")\n          </a>\n        </div>\n      </div>\n\n      ,(render-audit-logs\n        :type 'github-audit-log\n        :subtitle \"All API calls to GitHub made by Screenshotbot in the last 30 days will be listed here. This does not include OAuth calls since that's made on the user's behalf.\")\n    </settings-template>))\n\n\n(defsettings settings-github-page\n  :name \"github\"\n  :section :vcs\n  :title \"GitHub\"\n  :plugin 'github-plugin\n  :handler 'settings-github-page)\n\n\n(defhandler (nil :uri \"/github-app-install-callback\") (state installation_id setup_action)\n  (github-app-installation-callback state installation_id setup_action))\n"
  },
  {
    "path": "src/screenshotbot/github/task-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/task-integration\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/report\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/task-integration-api)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/github/access-checks\n                #:fix-github-link))\n(in-package :screenshotbot/github/task-integration)\n\n\n(defclass github-task-integration (task-integration)\n  ())\n\n(defmethod enabledp ((inst github-task-integration))\n  t)\n\n(defmethod send-task ((inst github-task-integration) report)\n  (when (create-github-issue-p (report-run report))\n    (warn \"GitHub task-integration has been deprecated\")))\n\n(register-task-integration 'github-task-integration)\n"
  },
  {
    "path": "src/screenshotbot/github/test-access-checks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/test-access-checks\n  (:use #:cl\n        #:fiveam\n        #:screenshotbot/github/access-checks\n        #:screenshotbot/user-api)\n  (:import-from #:screenshotbot/github/access-checks\n                #:*public-repo-p-cache*\n                #:get-repo-id\n                #:get-repo-stars\n                #:repo-string-identifier)\n  (:import-from #:util/mock-recording\n                #:track\n                #:with-recording)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:screenshotbot/git-repo\n                #:public-repo-p)\n  (:import-from #:screenshotbot/secret\n                #:secret))\n(in-package :screenshotbot/github/test-access-checks)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (unwind-protect\n       (&body)\n    (clrhash *public-repo-p-cache*)))\n\n\n(test github-repo-commit-link\n  (is (equal\n       \"https://github.com/tdrhq/web/commit/abcd12\"\n       (commit-link\n        (make-instance 'github-repo\n                        :link \"https://github.com/tdrhq/web\")\n        \"abcd12\")))\n  (is (equal\n       \"https://github.com/tdrhq/web/commit/abcd12\"\n       (commit-link\n        (make-instance 'github-repo\n                        :link \"https://github.com/tdrhq/web.git\")\n        \"abcd12\")))\n  (is (equal\n       \"https://github.com/tdrhq/web/commit/abcd12\"\n       (commit-link\n        (make-instance 'github-repo\n                        :link \"git@github.com:tdrhq/web\")\n        \"abcd12\")))\n  (is (equal\n       \"https://github.com/tdrhq/web/commit/abcd12\"\n       (commit-link\n        (make-instance 'github-repo\n                        :link \"git@github.com:tdrhq/web.git\")\n        \"abcd12\"))))\n\n(test repo-string-identifier\n  (is (equal \"tdrhq/fast-example\"\n             (repo-string-identifier \"https://github.com/tdrhq/fast-example\")))\n  (is (equal \"tdrhq/fast-example\"\n             (repo-string-identifier \"git@github.com.com:tdrhq/fast-example\")))\n  (is (equal \"tdrhq/fast-example\"\n             (repo-string-identifier \"git@github.com.com:tdrhq/fast-example/\")))\n  (is (equal \"tdrhq/fast-example\"\n             (repo-string-identifier \"git@github.com.com:tdrhq/fast-example.git\"))))\n\n(test get-repo-id\n  (is (equal \"tdrhq/fast-example\"\n             (get-repo-id \"https://github.com/tdrhq/fast-example\")))\n  (is (equal \"tdrhq/fast-example\"\n             (get-repo-id \"git@github.com.com:tdrhq/fast-example\")))\n  (is (equal \"tdrhq/fast-example\"\n             (get-repo-id \"git@github.com.com:tdrhq/fast-example.git\"))))\n\n(test get-repo-stars ()\n  (with-recording (#.(asdf:system-relative-pathname :screenshotbot \"github/fixture/get-repo-stars.rec\"))\n    (track 'github-api-request :skip-args '(2))\n    (is (equal (list 42 2)\n               (multiple-value-list\n                (get-repo-stars \"tdrhq\" \"slite\"))))))\n\n(test public-repo-p\n  (with-fixture state ()\n   (cl-mock:with-mocks ()\n     (answer (secret :github-api-secret) \"secret\")\n     (answer (get-repo-stars \"foo\" \"bar\")\n       20)\n     (is (eql t\n              (public-repo-p (make-instance 'github-repo\n                                            :link \"git@github.com:foo/bar\"))))\n     (answer (get-repo-stars \"foo\" \"bar\")\n       nil)\n     ;; Ensure this is cached:\n     (is (eql t\n              (public-repo-p (make-instance 'github-repo\n                                            :link \"git@github.com:foo/bar\")))))))\n\n(test public-repo-not-public\n  (with-fixture state ()\n   (cl-mock:with-mocks ()\n     (answer (secret :github-api-secret) \"secret\")\n     (answer (get-repo-stars \"foo\" \"bar\")\n       nil)\n     (is-false\n      (public-repo-p (make-instance 'github-repo\n                                    :link \"git@github.com:foo/bar\"))))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-app-installation.lisp",
    "content": "(defpackage :screenshotbot/github/test-app-installation\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/github/app-installation\n                #:%app-installation-id\n                #:*app-installation-cache*\n                #:app-installation-repos\n                #:repos\n                #:app-installation-by-id\n                #:github-get-access-token-for-installation)\n  (:import-from #:screenshotbot/github/plugin\n                #:github-plugin)\n  (:import-from #:screenshotbot/github/jwt-token\n                #:github-api-error\n                #:github-request)\n  (:import-from #:util/mock-recording\n                #:track-during-recording\n                #:with-recording)\n  (:import-from #:cl-mock\n                #:answer\n                #:if-called)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/github/test-app-installation)\n\n\n(util/fiveam:def-suite)\n\n(defparameter *private-key*\n  (uiop:read-file-string\n   (asdf:system-relative-pathname\n    :screenshotbot\n    \"github/fixture/private-key.test-pem\"))\n  \"A fake private key generated just for this test. See\nprivate-key.README.md for how this was generated.\")\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((*app-installation-cache* (make-hash-table :test #'equal)))\n      (cl-mock:with-mocks ()\n        (cl-mock:if-called 'github-plugin\n                           (lambda ()\n                             (make-instance 'github-plugin\n                                            :app-id 4242\n                                            :private-key *private-key*)))\n        (cl-mock:if-called 'github-get-access-token-for-installation\n                           (lambda (install-id\n                                    &key app-id\n                                      private-key)\n                             \"dummy-token\"))\n        (&body)))))\n\n(test app-installation-id\n  (with-fixture state ()\n    (if-called 'github-request\n               (lambda (url &key jwt-token)\n                 (is (equal \"/repos/tdrhq/fast-example/installation\" url))\n                 `((:id . 222)\n                   ;; some other info\n                   (:installtion . nil))))\n    (is (eql 222\n             (%app-installation-id  \"tdrhq/fast-example\")))))\n\n(test app-installation-id-nil\n  (with-fixture state ()\n    (if-called 'github-request\n               (lambda (url &key jwt-token)\n                 (error 'github-api-error\n                        :code 404\n                        :message \"foo\")))\n    (is (eql nil (%app-installation-id \"tdrhq/fast-example\")))))\n\n(test app-installation-id-other-crashes\n  (with-fixture state ()\n    (if-called 'github-request\n               (lambda (url &key jwt-token)\n                 (error 'github-api-error\n                        :code 500\n                        :message \"foo\")))\n    (signals github-api-error\n      (%app-installation-id \"tdrhq/fast-example\"))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-jwt-token.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/github/test-jwt-token\n    (:use #:cl\n          #:fiveam\n          #:alexandria)\n  (:import-from #:screenshotbot/github/jwt-token\n                #:github-create-jwt-token))\n\n(util/fiveam:def-suite)\n\n;; this is a dummy PEM file. I would put it in the repo, but then bots\n;; like GitGuardian will start pinging me. I do like their pings, so\n;; let's make it less noisy.\n(defvar *dummy-pem*\n  \"-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA1G9Ca5q+4HNH8b514pvg9cDlb/dZCsYLzOBwhayW2OPtrAQU\ng3NwmiV44xE9TKj2mF+ru35hZvnOosU3WfJDU83lsTWX3ZVfvZ6BkFsJogFe8hLV\n4AdvwKVK0ZoYG0In3+pQ2d5AYENeyskeQrOO7rYk0KVTtECdcbKiL6aB5hCfqXS7\nNURyqwMXUmJTiuNeylksd51bb4WksYDbOpYxfUOLdeygMd+8tGXsviQWXX8KkYQ2\ncLHX4zOpVa3qPa3RsnTaIL+mQEHDBs3HygGeuV0fr3M6qi37MG6vVB/UmHcS6Kws\nostZs4u9DNinzc1Yl0f3hjitiYc0KrYdlccPqwIDAQABAoIBAQCmrABl4oyeF3S7\n894UBI8U4dph5aRD8qyxeuptxsK3uuTv0Gez1u/i0UGujgkVS3/mfzGDMp4DKD36\nsVdDR/ORHft39P+JB7iNUn5/Hx5IsGCo9yQ82DjS4hz4pLkDMf2NDg+PUHQb0t/d\npLwMQ0sCeYAa/4vT4dplqWzci/xg3v3IZcK4goJt15GX2lRpBDQg/6nzQhhDCDRe\nW/qAeoUDiCwzJbaM+cJ9SYIgJQjHD9rvIXmKiumj+i6g3PcP+nRqzrpXfd3T03EF\nytEzFuRnDR2lBU4TiSXwagODzLfmAa4Fr8Ni+z8kZUhpPRf2P7e3k0/zLvlIu8CC\n3syMstEZAoGBAPPehA0C9TCG1oYFSvMiQZhi4jR7xfpRZUrXHrFHNSL9XiCEwUHl\nitRt+Rw9jCy5wqrz5/I4T+LwXQy9rPVzAgvOlvvkYFVVW2BCyou/BRYnFcNhO1Kw\nLLuq2XiiXb2WisV+EIPOJsVUUOQt4zUJkXBMg7veQfdTE2BsEgxuZw33AoGBAN8A\nctVJt+QgbhwszgdtdpQ826X3CBu72/8RjP3K09VPGA63B03a5TPqk/MzXOpXUxLs\nfGoUBqBmmVxUE4mAOhFAXsp5aQNUux3b90ppc7JCJNg/7L3/N2oAlkO28JjytdH/\nXOM7a4xDclNowHqXYy8/QXCaJzZENVus1ylO2W7tAoGBAIlxN2s954JZ/D28belp\nvR5tXJ/HwmS5yyTK6Plw8Hmv4oThTSoefIgNEwDfj0kFyLkgjfDt29hDL64mmHwd\nPWH6JQ4CQGjXmpA+FHl+RxedH57mBdEsiYmbWMWsfLiFR+DWk+g1H5THWG/BjPQv\nWFC6TuRq9zK1F46YWfO3pU0xAoGBAJcYobX8lfmfM5wpi4ui0oaMWbMxFzBbcCt6\nQ9KuPCu6xK0rvGo3F7e+iHJvadRqSKJ406+4U+kYu27AlOBEnpOTzuZXrxyPq50G\nrp6fpsGwaXCQl6MAqxZKwYWuDYVEZoecA97/ItbN2EfFoS0vKAgaTwexm0H8oz6z\ndLHLeC3dAoGAISq6GFWVHBYK+M7zMEHruw/6pdwLdpJMCSEIzEjm882nxVhS5uQ5\nPET7y5rB7N8YS86UW/DChxt8AxsJ8qvmPm1sG1oo/Wg3jwiMfSyWCEdSiVhLo/2h\nJFpoRQ/PLL5bbSS5bo+qdpMFG0H0jBhSHmPem2HWjto/HpqteH4NxOI=\n-----END RSA PRIVATE KEY-----\n\")\n\n(test create-jwt-token-happy-path\n  ;; todo: this test will break in OSS unless we specify the key\n  (is (stringp (github-create-jwt-token\n                :app-id \"323434\"\n                :private-key *dummy-pem*))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-plugin.lisp",
    "content": "(uiop:define-package :screenshotbot/github/test-plugin\n  (:use #:cl\n        #:fiveam\n        #:alexandria)\n  (:import-from #:screenshotbot/github/plugin\n                #:make-github-repo))\n(in-package :screenshotbot/github/test-plugin)\n\n\n(util/fiveam:def-suite)\n\n(test make-github-repo-cache\n  (let ((company-1 :fake-company)\n        (company-2 :fake-company-2)\n        (link \"https://github.com/foo/bar.git\"))\n    (is (eql (make-github-repo :link link :company company-1)\n             (make-github-repo :link link :company company-1)))\n    (is (not\n         (eql (make-github-repo :link link :company company-1)\n              (make-github-repo :link link :company company-2))))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-pull-request-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/test-pull-request-promoter\n  (:use #:cl\n        #:alexandria\n        #:bknr.datastore\n        #:screenshotbot/github\n        #:screenshotbot/model/test-object\n        #:screenshotbot/model/company\n        #:screenshotbot/compare\n        #:screenshotbot/diff-report\n        #:screenshotbot/github/access-checks\n        #:screenshotbot/promote-api\n        #:screenshotbot/model/channel\n        #:screenshotbot/git-repo\n        #:fiveam)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:unchanged-run\n                #:make-recorder-run\n                #:recorder-run\n                #:pull-request-url)\n  (:import-from #:screenshotbot/api/promote\n                #:maybe-promote-run)\n  (:import-from #:screenshotbot/github/pull-request-promoter\n                #:%make-github-summary\n                #:make-github-args\n                #:send-task-args\n                #:check-status\n                #:check-title\n                #:retrieve-run\n                #:report)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:make-check\n                #:check\n                #:push-remote-check\n                #:check-status\n                #:pr-merge-base\n                #:make-check-result-from-diff-report)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot)\n  (:import-from #:screenshotbot/diff-report\n                #:make-diff-report\n                #:change)\n  (:import-from #:screenshotbot/github/app-installation\n                #:app-installation-id\n                #:app-installed-p)\n  (:import-from #:screenshotbot/github/settings\n                #:verified-repo-p)\n  (:import-from #:screenshotbot/github/pull-request-promoter\n                #:plugin-installed?)\n  (:import-from #:screenshotbot/github/pr-checks\n                #:github-update-pull-request)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:assert-that)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-state)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:screenshotbot/github/plugin\n                #:github-plugin)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:alexandria\n                #:plist-alist\n                #:alist-plist\n                #:assoc-value)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-item\n                #:batch)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:warmup-comparison-images))\n(in-package :screenshotbot/github/test-pull-request-promoter)\n\n(util/fiveam:def-suite)\n\n(defvar *base-run*)\n\n(defclass pull-request-info ()\n  ())\n\n(defclass my-run-retriever ()\n  ())\n\n(defclass dummy-repo (github-repo)\n  ())\n\n(defclass dummy-channel (channel)\n  ((name :accessor channel-name\n         :initform \"foo\")\n   (repo :transient t\n         :initform (make-instance 'dummy-repo)\n         :accessor channel-repo))\n  (:metaclass persistent-class))\n\n(defmethod repo-link ((repo dummy-repo))\n  \"https://github.com/foo/bar.git\")\n\n(defmethod retrieve-run ((retriever my-run-retriever)\n                         channel base-commit\n                         logger)\n  (is (equal \"car\" base-commit))\n  (lparallel:delay\n   *base-run*))\n\n\n(def-fixture state (&key (run-retriever 'my-run-retriever))\n  (with-installation ()\n    (with-test-store ()\n      (cl-mock:with-mocks ()\n        (cl-mock:if-called 'verified-repo-p\n                           (lambda (repo company)\n                             t))\n        (cl-mock:if-called 'app-installed-p\n                           (lambda (repo)\n                             t))\n        (cl-mock:answer (github-plugin)\n          (make-instance 'github-plugin\n                         :app-id \"dummy-app-id\"\n                         :private-key \"dummy-private-key\"))\n        (cl-mock:if-called 'github-update-pull-request\n                           (lambda (&rest args)\n                             (values)))\n        (let ((auto-restart:*global-enable-auto-retries-p* nil)\n              (company (make-instance 'company))\n              (promoter (make-instance 'pull-request-promoter\n                                       :pull-request-info\n                                       (make-instance 'pull-request-info)\n                                       :run-retriever\n                                       (make-instance run-retriever))))\n          (flet ((the-only-report ()\n                   (let ((reports (class-instances 'report)))\n                     (trivia:match reports\n                       (nil\n                        nil)\n                       ((list report)\n                        report)\n                       (t\n                        (error \"Expected to have only one report but got: ~a\" reports))))))\n            (&body)))))))\n\n(test run-without-pr-does-not-create-report\n  (with-fixture state ()\n    (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n      22)\n    (let* ((*base-run* nil)\n           (run (make-recorder-run\n                 :company company\n                 :github-repo \"https://github.com/tdrhq/fast-example\"\n                 :channel (make-instance 'dummy-channel :company company)\n                 :merge-base \"car\"\n                 :commit-hash \"foo\")))\n      (maybe-promote promoter run)\n      (is-false (the-only-report))\n      (is (equal \"car\" (pr-merge-base promoter run))))))\n\n(test bitbucket-repo-doesnt-cause-promoter-to-crash\n  (with-fixture state ()\n    (let* ((repo (make-instance 'generic-git-repo\n                                 :link \"foo\"))\n           (channel (let ((channel (make-instance 'dummy-channel)))\n                      (setf (channel-repo channel) repo)\n                      channel))\n           (run (make-recorder-run\n                 :company company\n                 :channel channel\n                 :merge-base \"car\"\n                 :commit-hash \"foo\")))\n      (maybe-promote promoter run))))\n\n(test plugin-installed?\n  (with-fixture state ()\n    (is-true (plugin-installed?\n              promoter company \"https://github.com/far/bar\"))\n    (cl-mock:if-called 'verified-repo-p\n                        (lambda (repo company)\n                          nil)\n                        :at-start t)\n    (is-false (plugin-installed?\n               promoter company \"https://github.com/far/bar\"))))\n\n(test plugin-installed?-should-return-false-if-app-not-installed\n  (with-fixture state ()\n    (cl-mock:if-called 'app-installed-p\n                        (lambda (repo)\n                          nil)\n                        :at-start t)\n    (is-false (plugin-installed?\n               promoter company \"https://github.com/far/bar\"))))\n\n(test run-with-pr-creates-a-report\n  (with-fixture state ()\n    (let ((*base-run* (make-recorder-run\n                        :company company\n                        :channel (make-instance 'dummy-channel :company company)\n                        :merge-base \"dfdfdf\"\n                        :commit-hash \"car\"))\n          (check))\n      (cl-mock:if-called 'push-remote-check\n                         (lambda (promoter run %check)\n                           (declare (ignore promoter run))\n                           (setf check %check)))\n      (let ((run (make-recorder-run\n                  :company company\n                  :channel (make-instance 'dummy-channel :company company)\n                  :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                  :merge-base \"car\"\n                  :commit-hash \"foo\")))\n        (maybe-promote promoter run)\n        (is-true check)\n        (is (equal \"car\" (pr-merge-base promoter run)))\n        (is (eql :success (check-status check)))))))\n\n(test run-on-merge-queue-is-ignored\n  (with-fixture state ()\n    (let ((*base-run* (make-recorder-run\n                        :company company\n                        :channel (make-instance 'dummy-channel)\n                        :merge-base \"dfdfdf\"\n                        :commit-hash \"car\"))\n          (check))\n      (cl-mock:if-called 'push-remote-check\n                         (lambda (promoter run %check)\n                           (declare (ignore promoter run))\n                           (setf check %check)))\n      (let ((run (make-recorder-run\n                  :company company\n                  :channel (make-instance 'dummy-channel)\n                  :work-branch \"gh-readonly-queue/main/pr-45902-592fa2c43487bf\"\n                  :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                  :merge-base \"car\"\n                  :commit-hash \"foo\")))\n        (maybe-promote promoter run)\n        (is-true check)\n        (is (eql :success (check-status check)))\n        (is (equal \"Nothing to review\" (check-title check)))))))'\n\n(test run-on-main-is-ignored----sort-of\n  (with-fixture state ()\n    (let ((*base-run* (make-recorder-run\n                        :company company\n                        :channel (make-instance 'dummy-channel)\n                        :merge-base \"dfdfdf\"\n                        :commit-hash \"car\"))\n          (check))\n      (cl-mock:if-called 'push-remote-check\n                         (lambda (promoter run %check)\n                           (declare (ignore promoter run))\n                           (setf check %check)))\n      (let ((run (make-recorder-run\n                  :company company\n                  :channel (make-instance 'dummy-channel)\n                  :work-branch \"master\"\n                  :branch \"master\"\n                  :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                  :merge-base \"car\"\n                  :commit-hash \"foo\")))\n        (maybe-promote promoter run)\n        (is-true check)\n        (is (eql :success (check-status check)))\n        (is (equal \"Nothing to review\" (check-title check)))))))\n\n(test without-a-base-run-we-get-an-error\n  (with-fixture state ()\n    (let ((*base-run* nil))\n      (let* ((image (make-image\n                     :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\")))\n             (screenshot (make-screenshot\n                          :image image\n                          :name \"foobar\"))\n             (run (make-recorder-run\n                   :channel (make-instance 'dummy-channel :company company)\n                   :company company\n                   :screenshots (list screenshot)\n                   :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                   :merge-base \"car\"\n                   :commit-hash \"foo\"))\n           (check))\n       (cl-mock:if-called 'push-remote-check\n                          (lambda (promoter run %check)\n                            (declare (ignore promoter run))\n                            (setf check %check)))\n       (maybe-promote promoter run)\n       (is-true check)\n       (is (equal \"car\" (pr-merge-base promoter run)))\n       (is (eql :action-required (check-status check)))\n       (is (equal \"1 added\" (check-title check)))))))\n\n(test check-result-for-diff-report\n  (with-installation ()\n   (with-test-store ()\n     (let* ((company (make-instance 'company))\n            (channel (make-instance 'channel\n                                    :company company\n                                    :name \"github-test-channel\"))\n            (empty-run (make-recorder-run :company company\n                                          :channel channel))\n            (another-empty-run (make-recorder-run :company company\n                                                  :channel channel))\n            (empty-report (make-instance 'diff-report :added nil\n                                                     :deleted nil\n                                                     :changes nil)))\n       (let ((check (make-check-result-from-diff-report\n                     (make-instance 'pull-request-promoter)\n                     empty-run another-empty-run)))\n         (is (eql :success (check-status check)))\n         (is (equal \"No screenshots changed\"\n                    (check-title check))))))))\n\n(test check-result-for-unempty-diff-report\n  (cl-mock:with-mocks ()\n   (with-installation ()\n     (with-test-store ()\n       (cl-mock:if-called 'warmup-comparison-images\n                          (lambda (&rest args)))\n       (let ((company (make-instance 'company))\n             (diff-report (make-instance\n                           'diff-report\n                           :added nil\n                           :deleted nil\n                           :changes (list\n                                     (make-instance\n                                      'change\n                                      :before (make-instance 'screenshot :name \"foo\")\n                                      :after (make-instance 'screenshot :name \"foo\"))))))\n         (let ((run (make-recorder-run\n                                   :company company\n                                   :channel (make-instance 'dummy-channel)))\n               (another-run (make-recorder-run\n                             :company company\n                             :channel (make-instance 'dummy-channel))))\n           (answer (make-diff-report run another-run)\n             diff-report)\n\n           (let ((check (make-check-result-from-diff-report\n                         (make-instance 'pull-request-promoter)\n                         run another-run)))\n             (is (eql :action-required(check-status check)))\n             (is (cl-ppcre:scan \"1 change.*\" (check-title check))))))))))\n\n(test report-has-acceptable\n  (with-fixture state ()\n    (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n      22)\n\n    (let ((*base-run* (make-recorder-run\n                       :company company\n                       :channel (make-instance 'dummy-channel :company company)\n                       :commit-hash \"car\")))\n      (let ((run (make-recorder-run\n                  :channel (make-instance 'dummy-channel :company company)\n                  :company company\n                  :github-repo \"https://github.com/tdrhq/fast-example\"\n                  :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                  :screenshots (list (make-instance 'screenshot :name \"foobar\"))\n                  :merge-base \"car\"\n                  :commit-hash \"foo\")))\n        (maybe-promote promoter run)\n        (is-true (the-only-report))))))\n\n(test maybe-send-tasks-happy-path\n  (with-fixture state ()\n    (let (calls)\n      (cl-mock:if-called 'github-update-pull-request\n                         (lambda (&rest args)\n                           (push args calls))\n                         :at-start t)\n      (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n        22)\n      (setf (send-task-args promoter) '(:dummy))\n      (let ((run (make-recorder-run\n                  :channel (make-instance 'dummy-channel)\n                  :company company\n                  :github-repo \"https://github.com/tdrhq/fast-example\"\n                  :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                  :screenshots (list (make-instance 'screenshot :name \"foobar\"))\n                  :merge-base \"car\"\n                  :commit-hash \"foo\")))\n        (push-remote-check promoter run (make-check run\n                                                    :status :accepted\n                                                    :title \"foobar\"))\n        (assert-that calls\n                     (has-length 1))))))\n\n(test setf-acceptable-state-happy-path\n  (with-fixture state ()\n    (with-test-user (:logged-in-p t)\n      (let (calls)\n        (cl-mock:if-called 'github-update-pull-request\n                           (lambda (&rest args)\n                             (push args calls))\n                           :at-start t)\n        (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n          22)\n        (let* ((run (make-recorder-run\n                     :channel (make-instance 'dummy-channel)\n                     :company company\n                     :github-repo \"https://github.com/tdrhq/fast-example\"\n                     :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                     :screenshots (list (make-instance 'screenshot :name \"foobar\"))\n                     :merge-base \"car\"\n                     :commit-hash \"foo\"))\n               (report (make-instance 'report :run run\n                                              :previous-run (make-recorder-run)))\n               (acceptable (make-instance 'pr-acceptable\n                                          :send-task-args nil\n                                          :report report)))\n          (setf (acceptable-state acceptable) :accepted)\n          (assert-that calls\n                       (has-length 1)))))))\n\n(test we-dont-overwrite-the-check-summary-for-batch--integration-test\n  ;; Does this test belong in test-pull-request-promoter, or here?\n  (with-fixture state ()\n    (with-test-user (:logged-in-p t)\n      (let ((*base-run* (make-recorder-run\n                        :company company\n                        :channel (make-instance 'dummy-channel :company company)\n                        :commit-hash \"car\")))\n        (let ((calls))\n          (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n            22)\n\n          (cl-mock:if-called 'github-update-pull-request\n                             (lambda (&rest args)\n                               (push args calls))\n                             :at-start t)\n          (let* ((channel (make-instance 'dummy-channel :company company))\n                 (batch (make-instance 'batch\n                                       :repo \"https://github.com/tdrhq/fast-example\"\n                                       :commit \"foo\"\n                                       :pull-request-url \"https://github.com/tdrhq/fast-example/pull/2\"\n                                       :name \"batchName\"\n                                       :company company))\n                 (last-run))\n            (dotimes (i 5)\n              (let ((run (make-recorder-run\n                          :channel (make-instance 'dummy-channel :name (format nil \"Foobar~a\" i)\n                                                  :company company)\n                          :company company\n                          :batch batch\n                          :github-repo \"https://github.com/tdrhq/fast-example\"\n                          :pull-request \"https://github.com/tdrhq/fast-example/pull/2\"\n                          :screenshots (list (make-instance 'screenshot :name \"foobar\"))\n                          :merge-base \"car\"\n                          :commit-hash \"foo\")))\n                (setf last-run run)\n                (make-instance 'batch-item\n                               :batch batch\n                               :run run\n                               :channel channel\n                               :title \"Foobar\"\n                               :status :rejected)))\n            (maybe-promote promoter last-run)\n            ;; The first one is \"waiting for screenshots to be available..\"\n            (assert-that calls\n                         (has-length 2))\n\n            ;; Both outputs should have the channel name listed five times!\n            (loop for args in calls do\n              (let ((summary\n                      (assoc-value\n                       (getf args :output)\n                       \"summary\" :test #'equal)))\n                (assert-that\n                 (str:join \"\" (str:lines summary))\n                 (described-as \"We expect to see a table with five rows\"\n                   (matches-regex \".*Foobar.*Foobar.*Foobar.*Foobar.*Foobar.*\")))))))))))\n\n(test make-github-for-every-version-of-state\n  (with-fixture state ()\n    (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n      22)\n\n    (dolist (state (list :accepted :rejected :success :failure :action-required))\n      (let* ((channel (make-instance 'channel\n                                     :company company\n                                     :name \"test-channel\"\n                                     :github-repo \"https://github.com/tdrhq/fast-example\"))\n             (run (make-recorder-run\n                   :github-repo \"https://github.com/tdrhq/fast-example\"\n                   :channel channel\n                   :commit-hash \"zoidberg\"))\n             (promoter (make-instance 'pull-request-promoter))\n             (check (make-check run\n                                :status state\n                                :title \"foobar\")))\n        (let ((result (plist-alist (make-github-args run check))))\n          (is (equal \"zoidberg\" (assoc-value result :head-sha)))\n          ;; See: https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28\n          (is (str:s-member (list \"action_required\" \"cancelled\" \"failure\" \"neutral\"\n                                  \"success\" \"skipped\" \"stale\" \"timed_out\")\n                            (assoc-value result :conclusion))))))))\n\n(test ensure-no-newlines-in-summary\n  (with-fixture state ()\n    (let ((summary (%make-github-summary\n                    (make-recorder-run)\n                    (make-instance 'check\n                                   :status :accepted\n                                   :sha \"abcd\"\n                                   :key \"foobar\"\n                                   :title \"This is a test\"))))\n      (assert-that (mapcar #'str:trim (str:lines summary))\n                   (is-not (has-item \"\" ))))))\n\n;; T2236: unchanged-run without batch crashes when calling make-github-args\n(test push-remote-check-for-unchanged-run-without-batch\n  (with-fixture state ()\n    (cl-mock:answer (app-installation-id \"tdrhq/fast-example\")\n      22)\n    (let* ((channel (make-instance 'dummy-channel\n                                   :company company\n                                   :github-repo \"https://github.com/tdrhq/fast-example\"))\n           (unchanged-run (make-instance 'unchanged-run\n                                         :channel channel\n                                         :commit \"abcd\"\n                                         :other-commit \"1234\"))\n           (check (make-check unchanged-run\n                              :status :success\n                              :title \"No screenshots changed\")))\n      (finishes\n        (push-remote-check promoter unchanged-run check)))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-read-repos.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/test-read-repos\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/mock-recording\n                #:track\n                #:with-recording)\n  (:import-from #:screenshotbot/github/read-repos\n                #:can-edit-repo\n                #:whoami)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:screenshotbot/github/access-checks\n                #:github-api-request)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/github/test-read-repos)\n\n(util/fiveam:def-suite)\n\n#|\nTo record this test, you need an access token with GitHub.\n\nYou can use a personal access\ntoken (https://github.com/settings/tokens), run the tests, and then\ndelete the token. Leave the token here.\n\nThe token should have permission to access all repositories, and\nshould have read-only access to `Metadata`.\n\nThe repos used here are the ones that I have in my account. So if\nyou're not me, you might have to modify the repo names if you're\nre-recording this test.\n\n|#\n\n(defvar *access-token*\n  \"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\")\n\n(def-fixture state (name &key (record nil))\n  (with-test-store ()\n    (let ((pathname (path:catfile\n                     #.(asdf:system-relative-pathname\n                        :screenshotbot\n                        \"github/fixture/\")\n                     (format nil \"~a.rec\" name))))\n     (with-recording (pathname :record record)\n       (track 'github-api-request)\n       (&body)))))\n\n(defmacro %test (name (&rest args) &body body)\n  `(test ,name\n     (with-fixture state (,(str:downcase name) ,@args)\n       ,@body)))\n\n(%test whoami-integration-test ()\n  (is (equal \"tdrhq\"\n             (whoami *access-token*))))\n\n(%test can-edit-simple-repo ()\n  (is-true (can-edit-repo *access-token*  \"https://github.com/tdrhq/slite\")))\n\n(%test can-edit-for-private-repo ()\n  (is-true (can-edit-repo *access-token* \"https://github.com/tdrhq/web\")))\n\n(%test cannot-edit-repo-owned-somewhere-else ()\n  (assert-that\n   (multiple-value-list (can-edit-repo *access-token*\n                                       \"https://github.com/pointfreeco/swift-snapshot-testing\"))\n   (contains\n    nil\n    (contains-string \"not accessible\"))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-repo-push-webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/test-repo-push-webhook\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/request\n                #:http-request\n                #:*engine*)\n  (:import-from #:util/hunchentoot-engine\n                #:hunchentoot-engine)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:screenshotbot/server\n                #:*acceptor*)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching))\n(in-package :screenshotbot/github/test-repo-push-webhook)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n   (let ((*engine* (make-instance 'hunchentoot-engine\n                                  :acceptor *acceptor*)))\n     (with-test-store ()\n       (let ((company (make-instance 'company)))\n         (&body))))))\n\n(test simple-request\n  (with-fixture state ()\n    (is (eql 0 (store-object-id company)))\n    (http-request\n     \"https://localhost/github/0/push/tdrhq/fast-example\"\n     :method :post\n     :content \"{}\"\n     :additional-headers `((:x-hub-signature-256 . \"sha256=004835e2ffdb07054a2a5fa4137321ce04474fc9ef48d75a7de6711dd5820fc5\")))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/github/test-review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/test-review-link\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/github/access-checks\n                #:github-repo)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:get-canonical-pull-request-url))\n(in-package :screenshotbot/github/test-review-link)\n\n\n(util/fiveam:def-suite)\n\n(test github-review-link\n  (let ((repo (make-instance 'github-repo\n                             :link \"git@github.com:tdrhq/fast-example.git\")))\n    (is (equal \"https://github.com/tdrhq/fast-example/pull/2\"\n               (get-canonical-pull-request-url\n                repo 2)))))\n"
  },
  {
    "path": "src/screenshotbot/github/test-settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/github/test-settings\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/github/settings\n                #:verified-repo\n                #:verified-repo-p)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:verified-p\n                #:company)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/github/plugin\n                #:github-plugin))\n(in-package :screenshotbot/github/test-settings)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key verified-orgs)\n  (with-installation (:installation\n                      (make-instance 'installation\n                                     :plugins\n                                     (list\n                                      (apply #'make-instance 'github-plugin\n                                             (when verified-orgs\n                                               ;; this is only to verify the case of :verified-orgs not being provided at all (i.e. not bound)\n                                               (list :verified-orgs verified-orgs))))))\n   (with-test-store ()\n     (let ((company (make-instance 'company))\n           (company-2 (make-instance 'company)))\n       (&body)))))\n\n(test verified-repo-p\n  (with-fixture state ()\n    (make-instance 'verified-repo\n                   :company company\n                   :repo-id \"tdrhq/web\")\n\n    (is-false (verified-repo-p \"https://github.com/tdrhq/web\" company))\n    (is-false (verified-repo-p \"https://github.com/tdrhq/web\" company-2))))\n\n(test verified-repo-p-really-good-path\n  (with-fixture state ()\n    (let ((obj (make-instance 'verified-repo\n                              :company company\n                              :repo-id \"tdrhq/web\")))\n      (setf (verified-p obj) t))\n\n    (is-true (verified-repo-p \"https://github.com/tdrhq/web\" company))\n\n    (handler-case\n        (verified-repo-p \"https://github.com/tdrhq/web\" company)\n      (simple-warning ()\n        (fail \"should not have got warning\")))\n\n    (is-false (verified-repo-p \"https://github.com/tdrhq/web\" company-2))))\n\n(test auto-verify-repo-with-verified-orgs\n  (with-fixture state (:verified-orgs '(\"tdrhq\"))\n    (is-true (verified-repo-p \"https://github.com/tdrhq/fast-example\" company))\n    (is-false (verified-repo-p \"https://github.com/screenshotbot/fast-example\" company))))\n\n(test auto-verify-repo-with-verified-orgs-eql-t\n  (with-fixture state (:verified-orgs t)\n    (is-true (verified-repo-p \"https://github.com/tdrhq/fast-example\" company))\n    (is-true (verified-repo-p \"https://github.com/screenshotbot/fast-example\" company))))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/github/test-webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/github/test-webhook\n  (:use #:cl\n        #:alexandria\n        #:fiveam)\n  (:import-from #:screenshotbot/model/core\n                #:generate-api-secret)\n  (:import-from #:screenshotbot/github/webhook\n                #:pull-request\n                #:pull-request-head\n                #:pull-request-base\n                #:repo-full-name\n                #:github-get-canonical-repo\n                #:pull-request-with-url)\n  (:import-from #:util/store\n                #:with-test-store))\n\n(util/fiveam:def-suite)\n\n(test github-get-cannonical-repo\n  (is (equal \"https://github.com/foo/bar\"\n             (github-get-canonical-repo \"https://github.com/foo/bar.git\")))\n  (is (equal \"https://github.com/foo/bar\"\n             ;; Github actions uses this syntax\n             (github-get-canonical-repo \"git://github.com/foo/bar.git\")))\n  (is (equal \"https://github.com/foo/bar\"\n             (github-get-canonical-repo \"https://api.github.com/foo/bar.git\")))\n  (is (equal \"https://github.com/foo/bar\"\n             (github-get-canonical-repo \"git@github.com:foo/bar.git\")))\n  (is (equal \"https://github.com/foo/bar\"\n             (github-get-canonical-repo \"ssh://git@github.com:foo/bar.git\")))\n  ;; This one is a hack to deal with people using an incorrect repo URL\n  (is (equal \"https://github.com/foo/bar\"\n             (github-get-canonical-repo \"ssh://git@github.com/foo/bar.git\"))))\n\n(test github-get-canonical-repo-for-bitbucket\n  (is (equal \"https://bitbucket.org/tdrhq/fast-example\"\n             (github-get-canonical-repo\n              \"git@bitbucket.org:tdrhq/fast-example.git\"))))\n"
  },
  {
    "path": "src/screenshotbot/github/webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/github/webhook\n  (:use #:cl #:alexandria)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class\n                #:hash-index\n                #:with-transaction)\n  (:import-from #:screenshotbot/model/channel\n                #:github-get-canonical-repo)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/model/company\n                #:installation-id)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:github-repo)\n  (:import-from #:screenshotbot/github/plugin\n                #:webhook-secret\n                #:github-plugin)\n  (:import-from #:util/threading\n                #:with-extras\n                #:make-thread\n                #:max-pool\n                #:ignore-and-log-errors)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:export\n   #:pull-request\n   #:github-get-canonical-repo\n   #:repo-full-name\n   #:pull-request-id\n   #:pull-request-head\n   #:pull-request-base\n   #:all-pull-requests\n   #:pull-request-with-url))\n(in-package :screenshotbot/github/webhook)\n\n(defmethod github-get-canonical-repo (repo)\n  (let ((host (if (str:containsp \"bitbucket\" repo)\n                  \"bitbucket.org\"\n                  \"github.com\")))\n   (cl-ppcre:regex-replace-all\n    (format nil \"^(ssh://)?git@~a[:/]\" host)\n    (cl-ppcre:regex-replace-all\n     \"https://api.\"\n     (cl-ppcre:regex-replace-all \"[.]git$\"\n                                 (cl-ppcre:regex-replace-all \"^git://\"\n                                  repo \"https://\")\n                                 \"\")\n     \"https://\")\n    (format nil \"https://~a/\" host))))\n\n\n(defhandler (nil :uri \"/github-webhook\") ()\n  (error \"No longer implemented\"))\n\n\n(def-store-migration (\"Delete pull-request objects -- T1966\" :version 35)\n  (mapc #'bknr.datastore:delete-object\n        (bknr.datastore:class-instances 'pull-request)))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/gitlab\n    (:use #:cl)\n  (:use-reexport #:screenshotbot/gitlab/repo\n                 #:screenshotbot/gitlab/merge-request-promoter\n                 #:screenshotbot/gitlab/plugin))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/audit-logs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/audit-logs\n  (:use #:cl)\n  (:import-from #:screenshotbot/audit-log\n                #:base-audit-log)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:commit-tag\n                #:describe-audit-log)\n  (:import-from #:screenshotbot/user-api\n                #:user-full-name)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:update-status-for-user-audit-log\n   #:config-updated-audit-log\n   #:gitlab-audit-log\n   #:check-personal-access-token))\n(in-package :screenshotbot/gitlab/audit-logs)\n\n(named-readtables:in-readtable markup:syntax)\n\n(with-class-validation\n  (defclass gitlab-audit-log (base-audit-log)\n    ()\n    (:metaclass persistent-class)))\n\n(with-class-validation\n  (defclass update-status-audit-log (gitlab-audit-log)\n    ((commit :initarg :commit\n             :reader %commit)\n     (state :initarg :state\n            :reader %state))\n    (:metaclass persistent-class)\n    (:default-initargs :state nil)))\n\n(with-class-validation\n  (defclass check-personal-access-token (gitlab-audit-log)\n    ()\n    (:metaclass persistent-class)))\n\n(defmethod describe-audit-log ((self update-status-audit-log))\n  <span>\n    Updated build status\n    ,(when (and (slot-boundp self 'state)\n                 (%state self))\n       <span> to  <strong>,(%state self)</strong></span>)\n    on commit\n    <commit-tag>,(%commit self)</commit-tag>\n  </span>)\n\n(with-class-validation\n  (defclass config-updated-audit-log (gitlab-audit-log)\n    ((%user :initarg :user\n            :reader %user))\n    (:metaclass persistent-class)))\n\n(defmethod describe-audit-log ((self config-updated-audit-log))\n  <span>\n    Configuration updated by ,(user-full-name (%user self))\n  </span>)\n"
  },
  {
    "path": "src/screenshotbot/gitlab/merge-request-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/gitlab/merge-request-promoter\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/promote-api\n        #:screenshotbot/abstract-pr-promoter\n        #:util/java\n        #:screenshotbot/model/channel\n        #:screenshotbot/compare\n        #:screenshotbot/model/report\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/gitlab/repo\n        #:bknr.datastore)\n  (:nicknames\n   :screenshotbot/pro/gitlab/merge-request-promoter) ;; for bknr\n  (:import-from #:screenshotbot/model/report\n                #:base-acceptable)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:*gitlab-url*\n                #:repo-access-token)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-title)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/user-api\n                #:current-user\n                #:current-company)\n  (:import-from #:screenshotbot/gitlab/settings\n                #:enable-webhooks-p\n                #:gitlab-request\n                #:gitlab-settings\n                #:company\n                #:gitlab-token\n                #:gitlab-url\n                #:gitlab-settings-for-company)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:report-link)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:check-key\n                #:make-promoter-for-acceptable\n                #:abstract-pr-acceptable\n                #:push-remote-check\n                #:format-updated-summary\n                #:valid-repo?\n                #:send-task-args\n                #:check-status\n                #:details-url\n                #:check-title\n                #:make-acceptable\n                #:plugin-installed?\n                #:pull-request-promoter)\n  (:import-from #:screenshotbot/gitlab/plugin\n                #:gitlab-plugin)\n  (:import-from #:screenshotbot/dashboard/run-page\n                #:run-page)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/audit-log\n                #:with-audit-log)\n  (:import-from #:screenshotbot/gitlab/audit-logs\n                #:update-status-audit-log)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-repo-url\n                #:gitlab-merge-request-iid)\n  (:import-from #:screenshotbot/webhook/webhook\n                #:send-webhook)\n  (:import-from #:screenshotbot/gitlab/webhook\n                #:gitlab-update-build-status-payload)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:merge-request-promoter\n   #:gitlab-acceptable))\n(in-package :screenshotbot/gitlab/merge-request-promoter)\n\n\n(defclass merge-request-promoter (abstract-pr-promoter)\n  ((comments :initform nil\n             :accessor comments)\n   (report :initform nil\n           :accessor promoter-report)\n   (plugin :initarg :plugin\n           :reader plugin)))\n\n(defmethod plugin-installed? ((promoter merge-request-promoter)\n                              company\n                              repo-url)\n  (alexandria:when-let ((plugin (gitlab-settings-for-company company)))\n    (and\n     (gitlab-url plugin)\n     (or\n      (not (str:emptyp (gitlab-token plugin)))\n      (enable-webhooks-p plugin)))))\n\n(defun safe-get-mr-id (run)\n  (let ((mr-id (format nil \"~a\" (gitlab-merge-request-iid run))))\n    (unless (str:emptyp mr-id)\n      (parse-integer mr-id))))\n\n(defclass merge-request ()\n  ((base-sha :initarg :base-sha\n             :accessor base-sha)))\n\n(defun get-merge-request (run)\n  (let ((mr-id (gitlab-merge-request-iid run)))\n    (when mr-id\n      (let ((project-path (project-path (recorder-run-repo-url run)))\n            (mr-id (safe-get-mr-id run)))\n        (when mr-id\n          (let ((res (json:decode-json-from-string\n                      (gitlab-request (recorder-run-company run)\n                                      (format nil \"/projects/~a/merge_requests/~a\"\n                                              (urlencode:urlencode project-path)\n                                              mr-id)))))\n            (make-instance 'merge-request\n                           :base-sha (assoc-value (assoc-value res :diff--refs) :base--sha ))))))))\n\n(defmethod promoter-pull-id ((promoter merge-request-promoter) run)\n  (gitlab-merge-request-iid run))\n\n(defun comment (promoter message)\n  (push message (comments promoter)))\n\n(with-class-validation\n (defclass gitlab-acceptable (abstract-pr-acceptable)\n   ((report :initarg :report\n            :accessor acceptable-report)\n    (company :initarg :company\n             :accessor acceptable-company)\n    (send-task-args :accessor send-task-args)\n    (discussion-id :accessor discussion-id))\n   (:metaclass bknr.datastore:persistent-class)))\n\n(defmethod make-acceptable((promoter merge-request-promoter) report\n                           &rest args)\n  (apply #'make-instance 'gitlab-acceptable\n         :company (recorder-run-company (report-run report))\n         :report report\n         args))\n\n(defvar *last-state-update*\n  (make-hash-table :test #'equal\n                   #+sbcl #+sbcl\n                   :synchronized t)\n  \"GitLab doesn't like it if we try to update the build status to\nsomething it already is at, i.e. a no-op, even if the description\nchanges. (T2107) This in-memory cache avoids that.\")\n\n(def-easy-macro with-noop-prevention (&key company commit project-path name state &fn fn)\n  \"If the state for the given arguments is the same, then don't attempt to send a new request\"\n  (let ((key (list :company company :commit commit :project-path project-path :name name)))\n   (cond\n     ((equal (gethash key *last-state-update*) state)\n      (log:info \"Ignoring GitLab request because it will be a no-op\")\n      (values))\n     (t\n      (prog1\n          (fn)\n        (setf (gethash key *last-state-update*) state))))))\n\n(auto-restart:with-auto-restart (:retries 4)\n  (defun post-build-status (&key\n                              company\n                              project-path\n                              sha\n                              state\n                              (name\n                               \"Screenshotbot\")\n                              target-url\n                              description)\n    (assert\n     (str:s-member (str:split \", \" \"pending, running, success, failed, canceled\")\n                   state))\n    (with-noop-prevention (:company company\n                           :commit sha\n                           :project-path project-path\n                           :name name\n                           :state state)\n      (with-audit-log (audit-log (make-instance 'update-status-audit-log\n                                                :company company\n                                                :commit sha\n                                                :state state))\n        (declare (ignore audit-log))\n        (log:info \"Sending GitLab status\")\n        (gitlab-request company\n                        (format nil \"/projects/~a/statuses/~a\"\n                                (urlencode:urlencode project-path)\n                                sha)\n                        :method :post\n                        :content `((\"name\" . ,name)\n                                   (\"target_url\" . ,target-url)\n                                   (\"state\" . ,state)\n                                   (\"description\" . ,description)))))))\n\n(defmethod make-promoter-for-acceptable ((self gitlab-acceptable))\n  (make-instance 'merge-request-promoter))\n\n(defmethod valid-repo? ((promoter merge-request-promoter)\n                        repo)\n  (typep repo 'gitlab-repo))\n\n\n(defmethod make-gitlab-args (run\n                             check)\n  (let ((project-path (project-path (recorder-run-repo-url run))))\n   (list\n    :company (recorder-run-company run)\n    :project-path project-path\n    :sha (check-sha check)\n    :name (format nil \"Screenshotbot: ~a\" (check-key check))\n    :state (ecase (check-status check)\n             (:success \"success\")\n             (:accepted \"success\")\n             (:rejected \"failed\")\n             (:pending \"pending\")\n             (:failure \"failed\")\n             (:action-required \"failed\"))\n    :target-url (or\n                 (details-url check)\n                 (format nil \"~a~a\"\n                         (installation-domain (installation))\n                         (hex:make-url 'run-page :id (oid run))))\n    :description (check-title check))))\n\n(defmethod push-remote-check ((promoter merge-request-promoter)\n                              run check)\n  (let* ((args (make-gitlab-args run check))\n         (company (recorder-run-company run))\n         (settings (gitlab-settings-for-company company)))\n    (when (gitlab-token settings)\n      (apply #'post-build-status args))\n    (when (enable-webhooks-p settings)\n      (send-webhook\n       company\n       (apply #'make-instance 'gitlab-update-build-status-payload\n              args)))))\n\n(auto-restart:with-auto-restart ()\n  (defmethod maybe-send-tasks ((promoter merge-request-promoter) run)\n    (values)))\n\n\n(register-promoter 'merge-request-promoter)\n"
  },
  {
    "path": "src/screenshotbot/gitlab/plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/gitlab/plugin\n  (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/plugin\n                #:plugin\n                #:plugin-parse-repo)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:gitlab-repo)\n  (:import-from #:screenshotbot/model/channel\n                #:github-repo)\n  (:export\n   #:gitlab-plugin\n   #:gitlab-client-id\n   #:gitlab-client-secret))\n(in-package :screenshotbot/gitlab/plugin)\n\n(defclass gitlab-plugin (plugin)\n  ((client-id :initarg :client-id\n              :reader gitlab-client-id\n              :documentation \"The OAuth client id. Unused at the moment.\")\n   (client-secret :initarg :client-secret\n                  :reader gitlab-client-secret\n                  :documentation \"The OAuth client secret. Unused at the moment.\")))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/repo.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/repo\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/channel)\n  (:import-from #:screenshotbot/model/channel\n                #:make-gitlab-repo)\n  (:import-from #:screenshotbot/secret\n                #:secret\n                #:defsecret)\n  (:import-from #:screenshotbot/git-repo\n                #:public-repo-p\n                #:generic-git-repo)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:commit-link)\n  (:export\n   #:gitlab-repo\n   #:project-path\n   #:make-gitlab-repo\n   #:*gitlab-url*))\n(in-package :screenshotbot/gitlab/repo)\n\n;; TODO: move this into a config\n(defvar *gitlab-url* \"https://gitlab.com/api/v4\")\n\n(defclass gitlab-repo (generic-git-repo)\n  ((link :initarg :link\n         :accessor repo-link)\n   (company :initarg :company\n            :accessor company)))\n\n(defun make-gitlab-repo (&href link)\n  (make-instance 'gitlab-repo\n                 :link link))\n\n(defmethod project-path ((repo gitlab-repo))\n  (project-path (repo-link repo)))\n\n(defmethod project-path ((repo-url string))\n  (str:substring 1 nil (elt (multiple-value-list (quri:parse-uri repo-url)) 4)))\n\n(defmethod public-repo-p ((repo gitlab-repo))\n  (restart-case\n    (handler-case\n        (let ((api (format nil \"~a/projects/~a\" *gitlab-url*\n                           (urlencode:urlencode (project-path repo)))))\n          (dex:get api))\n      (dexador.error:http-request-not-found ()\n        nil))\n    (retry-public-repo-p ()\n      (public-repo-p repo))))\n\n(defmethod commit-link ((repo gitlab-repo)\n                        hash)\n  (format nil \"\"))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/review-link\n  (:use #:cl)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:review-link-impl)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:repo-link\n                #:gitlab-repo)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:gitlab-merge-request-iid)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:normalize-url)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/gitlab/review-link)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun gitlab-review-link (repo run)\n  (format nil \"~a/-/merge_requests/~a\"\n          (normalize-url (repo-link repo))\n          (gitlab-merge-request-iid run)))\n\n(defmethod review-link-impl ((repo gitlab-repo) run)\n  (when (gitlab-merge-request-iid run)\n    <a href= (gitlab-review-link repo run) >\n      Merge Request\n    </a>))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/settings\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:defsettings\n                #:settings-template)\n  (:import-from #:screenshotbot/gitlab/plugin\n                #:gitlab-plugin)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url\n                #:commit-link\n                #:current-user\n                #:current-company)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/plugin\n                #:plugin-parse-repo)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:gitlab-repo)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/gitlab/audit-logs\n                #:check-personal-access-token\n                #:gitlab-audit-log\n                #:config-updated-audit-log)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:render-audit-logs)\n  (:import-from #:screenshotbot/audit-log\n                #:with-audit-log\n                #:audit-log-error)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:parenscript\n                #:ps)\n  (:import-from #:screenshotbot/git-repo\n                #:repo-link)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:get-canonical-pull-request-url\n                #:describe-pull-request)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:enable-webhooks-p))\n(in-package :screenshotbot/gitlab/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar +unchanged+ \"unchanged\")\n\n(with-class-validation\n (defclass gitlab-settings (store-object)\n   ((%company :initarg :company\n              :reader company\n              :index-type unique-index\n              :index-reader gitlab-settings-for-company)\n    (url :initarg :url\n         :accessor gitlab-url)\n    (token :initarg :token\n           :accessor gitlab-token\n           :documentation \"Personal Access Token, to be specific\")\n    (%enable-webhooks-p :initarg :enable-webhooks-p\n                        :accessor enable-webhooks-p))\n   (:default-initargs :enable-webhooks-p nil)\n   (:metaclass persistent-class)))\n\n(defmethod enable-webhooks-p :around ((self gitlab-settings))\n  (ignore-errors (call-next-method)))\n\n(defmethod plugin-parse-repo ((plugin gitlab-plugin)\n                              company\n                              repo-str)\n  (let ((settings (gitlab-settings-for-company company)))\n    (when (and\n           settings\n           (str:starts-with-p (gitlab-url settings)\n                              repo-str))\n      (make-instance 'gitlab-repo\n                      :company company\n                      :link repo-str))))\n\n(defvar *lock* (bt:make-lock))\n\n(defun finish-save-settings (gitlab-url token enable-webhooks)\n  (let ((company (current-company)))\n   (let ((settings (bt:with-lock-held (*lock*)\n                     (or\n                      (gitlab-settings-for-company company)\n                      (make-instance 'gitlab-settings\n                                     :company company)))))\n     (with-transaction ()\n       (setf (gitlab-url settings) gitlab-url)\n       (setf (gitlab-token settings)\n             (if (str:emptyp token)\n                 nil\n                 token))\n       (setf (enable-webhooks-p settings)\n             (not (null enable-webhooks))))\n     (make-instance 'config-updated-audit-log\n                    :company company\n                    :user (current-user))\n     (hex:safe-redirect \"/settings/gitlab\"))))\n\n(defun save-settings (gitlab-url token enable-webhooks)\n  (let ((errors))\n    (flet ((check (key test message)\n             (unless test\n               (push (cons key message) errors))))\n      (check :gitlab-url (not (str:emptyp gitlab-url))\n             \"GitLab URL cannot be empty\")\n      (check :token\n             (or\n              (not (str:emptyp token))\n              enable-webhooks)\n             \"Personal Access Token cannot be empty (or you must Enable Webhooks as an alternative)\")\n      (check nil\n             (roles:has-role-p (auth:current-company)\n                               (auth:current-user)\n                               'roles:admin)\n             \"You need admin permissions to change the access token\")\n      (multiple-value-bind (validp error)\n          (validate-token :gitlab-url gitlab-url :token token)\n        (check :token\n               validp\n               error))\n      (cond\n        (errors\n         (with-form-errors (:gitlab-url gitlab-url\n                            :token token\n                            :errors errors\n                            :enable-webhooks enable-webhooks\n                            :was-validated t)\n           (settings-page)))\n        (t\n         (finish-save-settings gitlab-url token enable-webhooks))))))\n\n(defun validate-token (&rest args &key gitlab-url token)\n  \"Validate the given token against the endpoint, and returns two values:\nT if the token is valid, and if it's not valid, a second value will be\nthe list of reasons why it's not valid.\"\n  (declare (ignore gitlab-url token))\n  (with-audit-log (audit-log (make-instance 'check-personal-access-token\n                                             :company (current-company)))\n   (multiple-value-bind (body code)\n       (apply #'gitlab-request (current-company)\n              \"/personal_access_tokens/self\"\n              :ensure-success nil\n              args)\n     (cond\n       ((> code 400)\n        (let ((error (format nil \"The token appears to be invalid, GitLab responded with ~a\" code)))\n          (setf (audit-log-error audit-log) error)\n          (values nil error)))\n       (t\n        (let ((json (json:decode-json-from-string body)))\n          (let ((scopes (assoc-value json :scopes)))\n            (cond\n              ((str:s-member scopes \"api\")\n               (values t nil))\n              (t\n               (values nil \"The access token does not have the `api` scope.\"))))))))))\n\n\n(defun test-gitlab-settings ()\n  (flet ((settings-page (&rest args)\n           (hex:safe-redirect\n            (nibble ()\n              (apply #'settings-page args)))))\n   (multiple-value-bind (validp error)\n       (validate-token)\n     (cond\n       (validp\n        (settings-page :success \"Access token validated successfully.\"))\n       (t\n        (settings-page :error error))))))\n\n(defun settings-page (&key error success)\n  (let* ((settings (gitlab-settings-for-company (current-company)))\n         (current-token (?. gitlab-token settings))\n         (save (nibble (gitlab-url token enable-webhooks)\n                 (let ((token (cond\n                                ((equal token +unchanged+)\n                                 current-token)\n                                (t\n                                 token))))\n                  (save-settings gitlab-url token enable-webhooks))))\n         (disable-test-settings (ps (setf (ps:@ (get-element-by-id \"test-settings\") disabled) t)))\n         (test (nibble ()\n                 (test-gitlab-settings))))\n    <settings-template>\n      ,(when error\n         <div class= \"alert alert-danger mt-3\">\n           ,(progn error)\n         </div>)\n      ,(when success\n         <div class= \"alert alert-success mt-3\">\n           ,(progn success)\n         </div>)\n      <form action=save method= \"POST\" >\n        <div class= \"card mt-3\" style= \"max-width: 80em\">\n          <div class= \"card-header\">\n            <h3>GitLab Settings</h3>\n          </div>\n\n          <div class= \"card-body\">\n            <div class= \"alert alert-danger mt-2 d-none\" />\n            \n              <div class= \"mb-3\">\n                <label for= \"gitlab-url\" class= \"form-label\">GitLab URL</label>\n                <input id= \"gitlab-url\" type= \"url\" name= \"gitlab-url\" class= \"form-control\"\n                       value= (or (?. gitlab-url settings ) \"https://gitlab.com\")\n                       oninput=disable-test-settings />\n              </div>\n\n              <div class= \"mb-3\">\n                <label for= \"token\" class= \"form-label\">Personal Access Token</label>\n                <input id= \"token\" type= \"password\" name= \"token\" class= \"form-control\"\n                       value= (when (and settings (not (str:emptyp (gitlab-token settings)))) +unchanged+)\n                       oninput= disable-test-settings />\n                <div class= \"text-muted mt-1\">User must have <em>Maintainer</em> or <em>Owner</em> role on projects integrated with Screenshotbot. Access token needs the <tt>api</tt> scope.</div>\n              </div>\n\n              <div class= \"form-check\">\n                <input class= \"form-check-input\" type= \"checkbox\" id= \"enableWebhooks\"\n                       name= \"enable-webhooks\"\n                       checked=(when (?. enable-webhooks-p settings) \"checked\") />\n                <label class= \"form-check-label\" for= \"enableWebhooks\">\n                  Enable webhooks\n                </label>\n              </div>\n              <div class= \"\">\n                <p class= \"text-muted\">\n                  Using a Personal Access Token is easier, but you can also use webhooks for your GitLab integration. <a href= \"https://screenshotbot.io/documentation/code-review/gitlab#webhook-integration\">Read More</a>\n                </p>\n              </div>\n          </div>\n\n          <div class= \"card-footer\">\n            <input type= \"submit\" value= \"Save\" class= \"btn btn-primary\"/>\n            <input type= \"submit\" formaction=test class= \"btn btn-secondary\" id= \"test-settings\" value= \"Test Settings\" />\n          </div>\n        </div>\n      </form>\n\n      ,(render-audit-logs :type 'gitlab-audit-log\n                          :subtitle \"All API calls made by Screenshotbot to GitLab in the last 30 days will be listed here.\")\n    </settings-template>))\n\n(defsettings settings-gitlab-page\n  :name \"gitlab\"\n  :section :vcs\n  :title \"GitLab\"\n  :plugin 'gitlab-plugin\n  :handler 'settings-page)\n\n(defun gitlab-request (repo-or-company url &key (method :get) content\n                                             token\n                                             gitlab-url\n                                             (ensure-success t))\n  \"If GITLAB-URL or TOKEN is provided we use it, otherwise we use the\ninformation saved in the settings\"\n  (let* ((company (if (typep repo-or-company 'company)\n                      repo-or-company\n                      (company repo-or-company)))\n        (settings (gitlab-settings-for-company company)))\n    (util/request:http-request\n     (format nil \"~a/api/v4~a\" (or gitlab-url (gitlab-url settings)) url)\n     :method method\n     :additional-headers `((\"PRIVATE-TOKEN\" . ,(or token (gitlab-token settings))))\n     :want-string t\n     :content-type \"application/json\"\n     :ensure-success ensure-success\n     :content (json:encode-json-to-string content))))\n\n(defmethod commit-link ((repo gitlab-repo) hash)\n  (format nil \"~a/-/commit/~a\"\n          (repo-link repo)\n          hash))\n\n(defmethod describe-pull-request ((repo gitlab-repo) run)\n  (format nil \"!~a\"\n          (car (last\n                (str:split \"/\"\n                       (pull-request-url run))))))\n\n(defmethod get-canonical-pull-request-url ((repo gitlab-repo) id)\n  (format nil \"~a/-/merge_requests/~a\"\n          (repo-link repo)\n          id))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/test-merge-request-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/test-merge-request-promoter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/gitlab/merge-request-promoter\n                #:with-noop-prevention\n                #:*last-state-update*\n                #:make-gitlab-args\n                #:base-sha\n                #:gitlab-request\n                #:get-merge-request\n                #:gitlab-acceptable\n                #:post-build-status\n                #:merge-request-promoter)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:recorder-run-commit\n                #:recorder-run-channel\n                #:channel-repo\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run-merge-base\n                #:recorder-run)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-promote)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:push-remote-check\n                #:make-check\n                #:promoter-pull-id\n                #:retrieve-run\n                #:valid-repo?\n                #:plugin-installed?\n                #:send-task-args)\n  (:import-from #:screenshotbot/gitlab/settings\n                #:enable-webhooks-p\n                #:gitlab-token\n                #:gitlab-settings)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:gitlab-repo)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/gitlab/plugin\n                #:gitlab-plugin)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-state))\n(in-package :screenshotbot/gitlab/test-merge-request-promoter)\n\n\n(util/fiveam:def-suite)\n\n(defvar *base-run* nil)\n\n(defclass my-run-retriever ()\n  ())\n\n(defmethod retrieve-run ((retriever my-run-retriever)\n                         channel base-commit\n                         logger)\n  (is (equal \"aaa\" base-commit))\n  (lparallel:delay\n   *base-run*))\n\n(def-fixture state ()\n  (with-test-store ()\n    (cl-mock:with-mocks ()\n      (clrhash *last-state-update*)\n      (with-installation (:installation\n                          (make-instance 'installation\n                                         :plugins\n                                         (list\n                                          (make-instance 'gitlab-plugin))))\n        (let* ((company (make-instance 'company))\n               (channel (make-instance 'channel\n                                       :name \"gitlab-test-channel\"\n                                       :company company\n                                       :github-repo \"https://gitlab.com/tdrhq/fast-example\"))\n               (settings (make-instance 'gitlab-settings\n                                        :company company\n                                        :url \"https://gitlab.com\"\n                                        :token \"foobar\"))\n               (run (make-recorder-run :company company\n                                       :commit-hash \"baa\"\n                                       :merge-base \"aaa\"\n                                       :channel channel\n                                       :github-repo \"https://gitlab.com/tdrhq/fast-example\"\n                                       :gitlab-merge-request-iid 7))\n               (another-run (make-recorder-run))\n               (*base-run* (make-recorder-run :company company))\n               (last-build-status))\n          (cl-mock:if-called 'post-build-status\n                             (lambda (&rest args)\n                               (setf last-build-status args)))\n          (&body))))))\n\n(test valid-repo\n  (with-fixture state ()\n    (is (equal \"baa\" (recorder-run-commit run)))\n    (is (equal \"aaa\" (recorder-run-merge-base run)))\n    (assert-that\n     (channel-repo (recorder-run-channel run))\n     (has-typep 'gitlab-repo))\n    (is-true (valid-repo?\n              (make-instance 'merge-request-promoter)\n              (channel-repo (recorder-run-channel run))))))\n\n(test plugin-installed\n  (with-fixture state ()\n    (is-true (plugin-installed?\n              (make-instance 'merge-request-promoter)\n              company\n              \"https://gitlab.com/tdrhq/fast-example\"))))\n\n(test maybe-promote-happy-path\n  (with-fixture state ()\n    (let ((promoter (make-instance 'merge-request-promoter\n                                   :run-retriever (make-instance 'my-run-retriever))))\n      (finishes\n        (maybe-promote promoter run))\n      (is (not (null last-build-status))))))\n\n\n(test update-acceptable-state\n  (with-fixture state ()\n    (let* ((report (make-instance 'report\n                                  :run run\n                                  :previous-run another-run))\n           (acceptable (make-instance 'gitlab-acceptable\n                                      :report report)))\n      (setf (acceptable-state acceptable)\n            :accepted))))\n\n(test gitlab-promoter-pull-request\n  (with-fixture state ()\n    (let* ((promoter (make-instance 'merge-request-promoter)))\n      (is (equal 7 (promoter-pull-id promoter run))))))\n\n\n(test get-merge-request\n  (with-fixture state ()\n    #+nil\n    (cl-mock:if-called 'gitlab-request\n                       (lambda (company url)\n                         (error \"unimpl\")))\n    (cl-mock:answer\n        (gitlab-request company\n                        \"/projects/tdrhq%2Ffast-example/merge_requests/7\")\n      \"{\\\"diff_refs\\\": {\\\"base_sha\\\":\\\"foo\\\"}}\")\n    (is (equal \"foo\" (base-sha (get-merge-request run))))))\n\n\n(test push-remote-check-with-only-webhook-payload\n  (with-fixture state ()\n    (setf (enable-webhooks-p settings) t)\n    (setf (gitlab-token settings) nil)\n    (let ((promoter (make-instance 'merge-request-promoter)))\n      (push-remote-check promoter\n                         run (make-check run :status :accepted :title \"1 accepted\")))))\n\n(test make-gitlab-args-name\n  (with-fixture state ()\n    (let* ((promoter (make-instance 'merge-request-promoter))\n           (check (make-check run :status :accepted :title \"Test check\"))\n           (args (make-gitlab-args run check)))\n      (is (equal \"Screenshotbot: gitlab-test-channel\" (getf args :name))))))\n\n\n(test noop-prevention\n  (let ((var :unset))\n    (with-noop-prevention (:company 'foo :commit \"abcd\" :project-path \"car\" :name \"Screenshotbot\" :state \"pending\")\n      (setf var :first-set))\n    (is (eql :first-set var))\n    (with-noop-prevention (:company 'foo :commit \"abcd\" :project-path \"car\" :name \"Screenshotbot\" :state \"pending\")\n      (setf var :second-set))\n    (is (eql :first-set var))\n    (with-noop-prevention (:company 'foo :commit \"abcd\" :project-path \"car\" :name \"Screenshotbot\" :state \"success\")\n      (setf var :third-set))\n    (is (eql :third-set var))\n    ;; Different name should not be prevented\n    (with-noop-prevention (:company 'foo :commit \"abcd\" :project-path \"car\" :name \"Screenshotbot: Batch-2\" :state \"success\")\n      (setf var :fourth-set))\n    (is (eql :fourth-set var))))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/test-review-link.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/test-review-link\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:gitlab-repo)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/gitlab/review-link\n                #:gitlab-review-link))\n(in-package :screenshotbot/gitlab/test-review-link)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((repo (make-instance 'gitlab-repo\n                               :link \"https://gitlab.com/tdrhq/bar/car.git\"))\n          (run (make-recorder-run\n                :gitlab-merge-request-iid 123)))\n      (&body))))\n\n(test review-link-is-normalized\n  (with-fixture state ()\n    (is (equal \"https://gitlab.com/tdrhq/bar/car/-/merge_requests/123\"\n               (gitlab-review-link repo run)))))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/test-settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/test-settings\n  (:use #:cl\n        #:fiveam\n        #:fiveam-matchers)\n  (:import-from #:screenshotbot/gitlab/repo\n                #:gitlab-repo)\n  (:import-from #:screenshotbot/gitlab/plugin\n                #:gitlab-plugin)\n  (:import-from #:screenshotbot/plugin\n                #:plugin-parse-repo)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/gitlab/settings\n                #:validate-token\n                #:gitlab-request\n                #:gitlab-url\n                #:gitlab-token\n                #:save-settings\n                #:settings-page\n                #:gitlab-settings)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer))\n(in-package :screenshotbot/gitlab/test-settings)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n   (with-installation ()\n     (with-test-store ()\n       (if-called 'validate-token\n                  (lambda (&rest args)\n                    t))\n       (let* ((company (make-instance 'company))\n              (plugin (make-instance 'gitlab-plugin)))\n         (&body))))))\n\n(test gitlab-plugin-parse-repo\n  (with-fixture state ()\n    (let ((settings (make-instance 'gitlab-settings\n                                   :company company\n                                   :url \"https://gitlab.com\")))\n     (assert-that\n      (plugin-parse-repo plugin\n                         company\n                         \"https://gitlab.com/tdrhq/fast-example.git\")\n      (has-typep 'gitlab-repo)))))\n\n(screenshot-test gitlab-settings-page\n  (with-fixture state ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (settings-page)))))\n\n(test save-settings\n  (with-fixture state ()\n    (with-test-user (:user user :company company :logged-in-p t)\n      (setf (roles:user-role company user) 'roles:admin)\n      (let ((settings (make-instance 'gitlab-settings\n                                     :company company)))\n        (signals hex:redirected\n          (save-settings \"https://gitlab.example.com\"\n                         \"test-token\"\n                         \"\"))\n        (assert-that (gitlab-url settings)\n                     (equal-to \"https://gitlab.example.com\"))\n        (assert-that (gitlab-token settings)\n                     (equal-to \"test-token\"))))))\n\n(screenshot-test save-settings-non-admin\n  (with-fixture state ()\n    (with-test-user (:user user :company company :logged-in-p t)\n      (setf (roles:user-role user company) 'roles:standard-member)\n      (let ((settings (make-instance 'gitlab-settings\n                                     :company company\n                                     :url \"https://example.com\"\n                                     :token \"foobar\")))\n        (prog1\n            (save-settings \"https://gitlab.example.com\"\n                        \"test-token\"\n                        \"\")\n          (assert-that (gitlab-url settings)\n                       (equal-to \"https://example.com\"))\n          (assert-that (gitlab-token settings)\n                       (equal-to \"foobar\")))))))\n\n(test gitlab-request-uses-token\n  (with-fixture state ()\n    (let ((saved-additional-headers))\n     (with-test-user (:company company)\n       (cl-mock:if-called\n        'util/request:http-request\n        (lambda (url &key additional-headers &allow-other-keys)\n          (setf saved-additional-headers additional-headers)))\n       (gitlab-request company\n                       \"/api/foo\"\n                       :token \"fOObARDummyToken\"\n                       :gitlab-url \"https://example.com\")\n       (is\n        (equal \"fOObARDummyToken\"\n               (assoc-value saved-additional-headers\n                            \"PRIVATE-TOKEN\"\n                            :test #'equal)))))))\n"
  },
  {
    "path": "src/screenshotbot/gitlab/webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/gitlab/webhook\n  (:use #:cl)\n  (:import-from #:screenshotbot/webhook/model\n                #:webhook-payload)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class))\n(in-package :screenshotbot/gitlab/webhook)\n\n(defclass gitlab-update-build-status-payload (webhook-payload)\n  ((repo-id :initarg :project-path\n            :json-key \"repoId\"\n            :json-type :string\n            :documentation \"The repository ID. e.g. myorg/myproject. Typically, you'll url-encode this when passing it to the GitLab API. e.g. /projects/myorg%2Fmyproject/statuses/abcd0000.\")\n   (sha :initarg :sha\n        :json-key \"sha\"\n        :json-type :string\n        :documentation \"The commit SHA being updated\")\n   (state :initarg :state\n          :json-key \"state\"\n          :json-type :string\n          :documentation \"The state of the build status, one of: success, failed or pending\")\n   (target-url :initarg :target-url\n               :json-key \"targetUrl\"\n               :json-type :string\n               :documentation \"The URL to be linked to, typically a link to Screenshotbot report or a run.\")\n   (name :initarg :name\n         :json-key \"name\"\n         :json-type :string\n         :documentation \"The name of the build status. Typically it might just say \\\"Screenshotbot\\\" but if used with batching it might be something different.\")\n   (description :initarg :description\n                :json-key \"description\"\n                :json-type :string\n                :documentation \"The description of the build status\")\n   (company :initarg :company\n            :documentation \"Ignored in the JSON payload\"))\n  (:metaclass ext-json-serializable-class)\n  (:default-initargs :event \"gitlab.update-build-status\")\n  (:documentation \"If webhooks are enabled for GitLab integration, this webhook is\ndispatched each time we update your GitLab build status. This webhook\nis also dispatched if you don't provide us a GitLab access token, so\nit can be used to set up an integration with GitLab in a manner that\nrestricts Screenshotbot's access to your GitLab.\"))\n"
  },
  {
    "path": "src/screenshotbot/hub/api.lisp",
    "content": ""
  },
  {
    "path": "src/screenshotbot/hub/container.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/hub/container\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/hub/container)\n\n(defclass container ()\n  ((cid :accessor container-id\n        :initarg :container-id)\n   (container-ipaddr\n    :initarg :container-ipaddr\n    :reader container-ipaddr)))\n\n(defmethod print-object ((self container) out)\n  (format out \"#<CONTAINER ~a>\" (str:substring 0 8 (container-id self))))\n\n(defun run* (args &key (output 'string))\n  (uiop:run-program\n   (mapcar (lambda (x)\n             (if (pathnamep x) (namestring x) x))\n           args)\n   :error-output *standard-output*\n   :output output))\n\n(defun build-image (tag dockerfile)\n  (run*\n   (list \"docker\" \"build\" \"-t\" tag\n         \"-f\" dockerfile \".\")\n   :output *standard-output*))\n\n(defun make-container (image &key port)\n  (let ((args (list image)))\n    (let* ((container-id  (str:trim (run*\n                                     (list* \"docker\" \"run\" \"-d\" args))))\n           (ipaddr (str:trim (run*\n                              (list \"docker\" \"inspect\"\n                                     \"--format\" \"{{ .NetworkSettings.IPAddress }}\"\n                                     container-id)))))\n\n\n      (make-instance 'container\n                      :container-id container-id\n                      :container-ipaddr ipaddr))))\n\n\n\n(defmethod container-stop ((self container))\n  (run* (list \"docker\" \"stop\" (container-id self))))\n"
  },
  {
    "path": "src/screenshotbot/hub/server.lisp",
    "content": "(defpackage :screenshotbot/hub/server\n  (:use #:cl)\n  (:import-from #:util/misc\n                #:or-setf)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:hub\n   #:relay-session-request\n   #:*hub*\n   #:direct-selenium-url\n   #:img-with-fallback))\n(in-package :screenshotbot/hub/server)\n\n(defclass local-hub ()\n  ())\n\n\n(defparameter +json-content-type+\n  \"application/json; charset=UTF-8\")\n\n(defvar *hub* nil)\n\n(defun hub ()\n  (or-setf\n   *hub*\n   (let ((hub (make-instance 'local-hub)))\n     (start-hub hub)\n     hub)\n   :thread-safe t))\n\n(defgeneric direct-selenium-url (hub session-id)\n  (:method (hub (session-id string))\n    \"http://localhost:4444\")\n  (:documentation \"A direct URL to access the selenium server while avoiding the\n intermediate hub. This should only be used by the\n /full-page-screenshot, since outside of this you might be firewalled\n against accessing this URL directly.\"))\n\n(defmethod start-hub ((self local-hub)))\n\n(auto-restart:with-auto-restart ()\n (defmethod request-session-and-respond ((hub local-hub)\n                                         (arguments string))\n   (multiple-value-bind (data ret)\n       (util/request:http-request\n        (format nil \"http://localhost:4444/wd/hub/session\")\n        :method :post\n        :want-string t\n        :content-type +json-content-type+\n        :read-timeout 300\n        :content arguments\n        :external-format-out :utf-8)\n     (assert (not (eql ret 500)))\n     (setf (hunchentoot:return-code*) ret)\n     (setf (hunchentoot:content-type*) +json-content-type+)\n     data)))\n\n(auto-restart:with-auto-restart ()\n  (defmethod relay-session-request ((hub local-hub)\n                                    &key (method (error \"provide method\"))\n                                      (script-name (error \"provide-script-name\"))\n                                      (content (error \"provide content\"))\n                                      (content-type (error \"provide-content-type\")))\n    (log:info \"Relaying request for ~a\" script-name)\n    (multiple-value-bind (data ret headers)\n        (util/request:http-request\n         (format nil \"http://localhost:4444~a\"\n                 script-name)\n         :method method\n         :want-string t\n         :content-type content-type\n         :read-timeout 45\n         :content content)\n      (when (eql ret 500)\n        (error \"Got bad response code for remote hub: ~a ~S\" data headers))\n      (setf (hunchentoot:return-code*) ret)\n      (setf (hunchentoot:content-type*) (a:assoc-value headers :content-type))\n      data)))\n"
  },
  {
    "path": "src/screenshotbot/ignore-and-log-errors.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/ignore-and-log-errors\n  (:use :cl)\n  (:import-from :util/threading\n                :ignore-and-log-errors)\n  (:export :ignore-and-log-errors))\n(in-package :screenshotbot/ignore-and-log-errors)\n"
  },
  {
    "path": "src/screenshotbot/image-comparison.lisp",
    "content": "(defpackage :screenshotbot/image-comparison\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/image-comparison)\n\n(loop for sym in `(#:image-comparison\n                   #:before\n                   #:after\n                   #:masks\n                   #:identical-p\n                   #:result)\n      do\n         (import (find-symbol (string sym) :screenshotbot/compare)))\n"
  },
  {
    "path": "src/screenshotbot/insights/dashboard.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/dashboard\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:ps\n                #:@)\n  (:import-from #:core/active-users/active-users\n                #:format-date\n                #:active-user-date\n                #:active-users-for-company)\n  (:import-from #:screenshotbot/login/common\n                #:with-login)\n  (:import-from #:screenshotbot/user-api\n                #:company-name)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/model/company\n                #:has-root-company-p)\n  (:import-from #:screenshotbot/insights/runs\n                #:active-screenshot-key-screenshot-key\n                #:active-screenshot-key-date\n                #:active-screenshot-keys)\n  (:import-from #:screenshotbot/insights/pull-requests\n                #:user-reviews-last-n-days\n                #:pr-to-actions)\n  (:import-from #:core/ui/taskie\n                #:taskie-page-title)\n  (:import-from #:auth\n                #:user-full-name)\n  (:import-from #:util/throttler\n                #:throttle!\n                #:throttler)\n  (:import-from #:screenshotbot/insights/variables\n                #:*num-days*)\n  (:local-nicknames (#:active-users\n                     #:core/active-users/active-users)))\n(in-package :screenshotbot/insights/dashboard)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass dataset ()\n  ((label :initarg :label\n          :reader dataset-label)\n   (data-labels :initarg :data-labels\n                :reader dataset-data-labels\n                :documentation \"A hash-table. The keys are the keys\nprovided to generate-chart, and the value is the label we will show\")\n   (data :initarg :data\n         :reader dataset-data\n         :documentation \"A hash-table. The keys are the keys provided to generate-chart.\"))\n  (:metaclass ext-json-serializable-class))\n\n(defvar *throttler* (make-instance 'throttler\n                                   :tokens 600))\n\n\n(defun generate-chart-on-canvas (canvas-name &key keys\n                                               (index-axis)\n                                               (legend t)\n                                               (mirror nil)\n                                               (default-value 0)\n                                               (data-labels nil)\n                                               (background-colors nil)\n                                               (title \"No title\")\n                                               (type \"line\")\n                                               datasets)\n  \"DEFAULT-VALUE is the value used if the data for the key is not present in data.\"\n  (ps:ps\n    (funcall\n     (lambda ()\n       (let* ((ctx ((@ document get-element-by-id)  (ps:lisp canvas-name)))\n              (datasets ((@ -J-S-O-N parse) (ps:lisp\n                                             (json:encode-json-to-string datasets))))\n              (labels (ps:lisp `(list ,@keys)))\n              (parsed-datasets (loop for dataset in datasets\n                                   collect\n                                   (ps:create\n                                    :label (@ dataset label)\n                                    :data (loop for x in labels\n                                                collect\n                                                (or (aref (@ dataset data) x)\n                                                    (ps:lisp default-value)))\n                                    \"backgroundColor\" (ps:lisp (if background-colors `(list ,@background-colors)))\n                                    :cubic-interpolation-mode \"monotone\"\n                                    :tension 0.2\n                                    :border-width 1))))\n\n         (ps:new\n          (-Chart\n           ctx\n           (ps:create\n            :type (ps:lisp type)\n            :data (ps:create\n                   :labels labels\n                   :datasets parsed-datasets)\n            :plugins (ps:lisp (if data-labels `(list -Chart-Data-Labels)))\n            :options (ps:create\n                      :layout (ps:create\n                               :padding 25)\n                      \"indexAxis\" (ps:lisp index-axis)\n                      :responsive t\n                      \"maintainAspectRatio\" ps:false\n                      :tooltips (ps:create\n                                 :enabled t)\n                      :plugins (ps:create\n                                :title (ps:create\n                                        :display t\n                                        :text (ps:lisp title))\n                                :legend (ps:create\n                                         :display (ps:lisp legend))\n\n                                :datalabels (ps:create\n                                             :formatter (lambda (value ctx)\n                                                          ((@ console log) \"ctx is\" ctx)\n                                                          (let ((key (aref labels (@ ctx data-index)))\n                                                                ;; Which dataset are we looking at? For a Pie chart this will typically be zero.\n                                                                (dataset-index (@ ctx dataset-index)))\n                                                            (let ((data-labels (@ (aref datasets dataset-index) data-labels)))\n                                                              ((@ console log) \"data labels map is\" data-labels)\n                                                             (aref\n                                                              data-labels\n                                                              (@ ctx data-index)))))))\n                      :scales (unless (equal \"pie\" (ps:lisp type))\n                                  (ps:create\n                                      :y (ps:create\n                                          \"beginAtZero\" t))))))))))))\n\n;; (generate-chart-on-canvas \"foo\")\n\n(defun daily-active-users (active-users)\n  (weekly-active-users active-users :trail-size 1))\n\n(defun last-30-days (&key (n *num-days*))\n  (let ((now (get-universal-time)))\n    (reverse\n     (loop for i from 0 to n\n           collect (format-date (- now (* i 24 3600)))))))\n\n(defun last-60-days ()\n  (last-30-days :n (+ 30 *num-days*)))\n\n\n(defun n-day-active-count (active-objects ;; Should store <date> and the actual object\n                           &key trail-size\n                             value-accessor\n                             date-accessor)\n    (let ((day-map\n          ;; A map from day to list of users (not active-user objs)\n          (make-hash-table :test #'equal)))\n    (loop for active-user in active-objects\n          do (push (funcall value-accessor active-user) (gethash (funcall date-accessor active-user) day-map nil)))\n\n    (let ((current-users (make-hash-table :test #'equal))\n          (ans (make-hash-table :test #'equal)))\n      (loop for day in (last-60-days)\n            for 7-days-ago  in (append\n                                (loop for i from 0 upto trail-size collect nil)\n                                (last-60-days))\n\n            do\n               (loop for user in (gethash day day-map)\n                     do (incf (gethash user current-users 0)))\n               (loop for user in (gethash 7-days-ago day-map)\n                     do\n                        (decf (gethash user current-users))\n                        (when (= 0 (gethash user current-users))\n                          (remhash user current-users)))\n               (setf (gethash day ans) (hash-table-count current-users)))\n      ans)))\n\n(defun weekly-active-users (active-users &key (trail-size 7 #| week by default |#))\n  \"There are certainly more efficient ways of doing this, but doesn't\nmatter. The code was initially hard-coded for trail-size=7,\ni.e. weekly-active, hence the name, but it can also be used for\nmonthly-active.\"\n  (n-day-active-count active-users\n                      :trail-size trail-size\n                      :value-accessor #'active-users::user\n                      :date-accessor #'active-user-date))\n\n(defun monthly-active-users (active-users)\n  (weekly-active-users active-users :trail-size 30))\n\n(defun bad-active-user-p (active-user)\n  (let ((user (core/active-users/active-users::user active-user))\n        (company (core/active-users/active-users::company active-user)))\n    (roles:has-role-p company user 'roles:hidden-user)))\n\n(defun generate-daily-active-users (company id &key days)\n  (let* ((*num-days* days)\n         (active-users\n           (remove-if\n            #'bad-active-user-p\n            (active-users-for-company company :company-test #'has-root-company-p)))\n         (data (daily-active-users active-users)))\n    (generate-chart-on-canvas id\n                              :keys (last-30-days)\n                              :title (format nil \"Active Users on ~a\" (company-name company))\n                              :datasets\n                              (list\n                               (make-instance 'dataset\n                                              :label \"Daily Active\"\n                                              :data data)\n                               (make-instance 'dataset\n                                              :label \"Weekly Active (trailing)\"\n                                              :data (weekly-active-users active-users))\n                               (make-instance 'dataset\n                                              :label \"Monthly Active (trailing)\"\n                                              :data (monthly-active-users active-users))))))\n\n(defun generate-active-screenshots (company id &key days\n                                                 channel-filter)\n  (let* ((*num-days* days)\n         (data-list  (active-screenshot-keys company\n                                             :channel-filter channel-filter))\n         (data (make-hash-table :test #'equal)))\n    (loop for (key value) in data-list\n          do (setf (gethash key data) value))\n    (generate-chart-on-canvas id\n                              :keys (last-30-days)\n                              :title (format nil \"Active screenshots over the last 30 days\")\n                              :datasets\n                              (list\n                               (make-instance 'dataset\n                                              :label \"Screenshots\"\n                                              :data data)))))\n\n\n(defun generate-pull-requests-chart (company id &key days)\n  (let ((*num-days* days)\n        (none \"PRs with no screenshot changes\")\n        (changed \"PRs with screenshot changes, but no actions on Screenshotbot\")\n        (rejected \"PRs with at least one rejection\")\n        (accepted \"PRs with only accepted screenshots\"))\n   (let ((data (make-hash-table :test #'equal)))\n     (loop for action being the hash-values of (pr-to-actions company)\n           do\n              (ecase action\n                (:accepted\n                 (incf (gethash accepted data 0)))\n                (:rejected\n                 (incf (gethash rejected data 0)))\n                (:changed\n                 (incf (gethash changed data 0)))\n                (:none\n                 (incf (gethash none data 0)))))\n     (flet ((pct (label)\n              (format nil \"~,1f%\" (* 100\n                                    (/ (gethash label data 0)\n                                       (max (loop for val being the hash-values of data\n                                                  summing val)\n                                            1))))))\n      (generate-chart-on-canvas id\n                                :type \"pie\"\n                                :keys (list none\n                                            changed\n                                            accepted\n                                            rejected)\n                                :title (format nil \"Percentage of PRs with activity on Screenshotbot over last ~a days\" *num-days*)\n                                :data-labels t\n\n                                ;; ChartJS brand colors taken from:\n                                ;; https://github.com/chartjs/Chart.js/blob/ea88dba68d41d4974c1fff5ce1c60f5d68279c13/docs/scripts/utils.js#L127\n                                :background-colors (list\n                                                    \"rgb(201, 203, 207)\" ;; grey\n                                                    \"rgb(54, 162, 235)\" ;; blue\n                                                    \"rgb(75, 192, 192)\" ;; green\n                                                    \"rgb(255, 99, 132)\" ;; red\n                                                    )\n                                :datasets\n                                (list\n                                 (make-instance 'dataset\n                                                :label \"Number of PRs\"\n                                                :data-labels (list\n                                                              (pct none)\n                                                              (pct changed)\n                                                              (pct accepted)\n                                                              (pct rejected))\n                                                :data  data)))))))\n\n(defvar *random-names*\n  (list\n   \"Oakley Berry\"\n   \"Adonis Powell\"\n   \"Vivian Carson\"\n   \"Ares Sandoval\"\n   \"Elsie Villalobos\"))\n\n(defun generate-top-users (company id &key fuzz days)\n  (let ((*num-days* days)\n        (top-users (user-reviews-last-n-days company))\n        (data (make-hash-table :test #'equal)))\n    (flet ((user-name (idx)\n             (if fuzz\n                 (elt *random-names* (mod idx 5))\n                 (user-full-name (car (elt top-users idx))))))\n     (loop for (nil count) in top-users\n           for i below 5\n           do\n              (setf (gethash (user-name i) data) count))\n      (generate-chart-on-canvas id\n                                :type \"bar\"\n                                :mirror t\n                                :index-axis \"y\"\n                                :title (format nil \"Top users by review count over last ~a days\" *num-days*)\n                                :keys (loop\n                                        for i below (min 5 (length top-users))\n                                        collect (user-name i))\n                                :data-labels t\n                                :legend nil\n                                :datasets\n                                (list\n                                 (make-instance 'dataset\n                                                :label \"Number of reviews\"\n                                                :data-labels (loop for (nil count) in top-users\n                                                                   for i below 5\n                                                                   collect (format nil \"~a\" count))\n                                                :data data))))))\n\n(defun generate-top-users-csv (company &key num-days output)\n  (with-open-file (output output :if-exists :supersede :direction :output)\n    (let ((data (let ((*num-days* num-days))\n                  (user-reviews-last-n-days company))))\n      (loop for (user num) in data\n            do\n            (format output \"~a,~a~%\" (user-full-name user) num)))))\n\n(easy-macros:def-easy-macro script-tag (&fn fn)\n  (throttle! *throttler* :key (auth:current-company))\n  <script async= \"async\" type= \"text/javascript\" src=\n          (nibble ()\n                    (setf (hunchentoot:content-type*) \"application/javascript\")\n                    (fn))\n          />)\n\n(defun render-analytics (company &key fuzz days channel-filter)\n  (auth:can-view! company)\n  <app-template>\n\n    <div class= \"container\">\n\n      ,(taskie-page-title :title \"Insights\")\n      <div class= \"main-content\">\n        <p class= \"\" >We provide these Insights to help you quantify the impact of your screenshot tests. These metrics are computed in near real time, with just a few minutes lag.</p>\n\n        <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n        <script src= \"https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2\" />\n\n        <div class= \"row analytics-row mt-3 mb-3 pt-3 g-3\">\n          <div class= \"col-md-6\">\n            <div class= \"chart-container\" >\n              <canvas id=\"myChart\"></canvas>\n            </div>\n          </div>\n\n          <div class= \"col-md-6\">\n            <div class= \"chart-container\" >\n              <canvas id= \"active-screenshots\" />\n            </div>\n          </div>\n\n\n          <div class= \"col-md-6\">\n            <div class= \"chart-container\" >\n              <canvas id= \"pull-requests\" />\n            </div>\n          </div>\n\n          <div class= \"col-md-6\">\n            <div class= \"chart-container\" >\n              <canvas id= \"top-users\" />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n\n    ,(script-tag ()\n       (generate-daily-active-users company \"myChart\"\n                                    ;; We don't have more data than this\n                                    :days 30))\n\n    ,(script-tag ()\n       (generate-active-screenshots company \"active-screenshots\"\n                                    :days days\n                                    :channel-filter channel-filter))\n\n    ,(script-tag ()\n       (generate-pull-requests-chart company \"pull-requests\" :days days))\n\n    ,(script-tag ()\n       (generate-top-users company \"top-users\" :fuzz fuzz :days days))\n  </app-template>)\n\n(defhandler (nil :uri \"/insights\") ((days :parameter-type 'integer :init-form *num-days*)\n                                    (channel-filter :init-form \"\"))\n  (assert (< days 366))\n  (with-login ()\n    (render-analytics (auth:current-company)\n                      :days days\n                      :channel-filter channel-filter)))\n\n\n"
  },
  {
    "path": "src/screenshotbot/insights/date.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/date\n  (:use #:cl)\n  (:import-from #:cl-utilities\n                #:collecting)\n  (:export\n   #:format-date))\n(in-package :screenshotbot/insights/date)\n\n(defun %format-date (ts)\n  (multiple-value-bind (second minute hour date month year) (decode-universal-time ts 0)\n    (declare (ignore second minute hour))\n    (format nil \"~4,'0d-~2,'0d-~2,'0d\" year month date)))\n\n(defparameter *format-date-cache* (make-hash-table))\n\n(defun format-date (ts)\n  \"format-date can be surprisingly slow, and is a bottleneck.\"\n  (util:or-setf\n   (gethash ts *format-date-cache*)\n   (%format-date ts)))\n\n(defun %date-to-universal (date)\n  (destructuring-bind (yyyy mm dd)\n      (mapcar #'parse-integer (str:split \"-\" date))\n    (encode-universal-time 0 0 0 dd mm yyyy 0)))\n\n(defun increment-date (date &optional (count 1))\n  (format-date\n   (+\n    (%date-to-universal date)\n    (* count 86400)\n    ;; Factor in some leap seconds?\n    10)))\n\n(defun list-dates (&key from to)\n  \"List all dates from FROM and to TO, both inclusive.\"\n  (collecting\n   (do ((date from (increment-date date)))\n       ((string> date to))\n     (cl-utilities:collect date))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/insights/fuzz.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/fuzz\n  (:use #:cl)\n  (:import-from #:screenshotbot/user-api\n                #:singletonp\n                #:company-name\n                #:personalp\n                #:adminp)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/login/common\n                #:with-login)\n  (:import-from #:screenshotbot/model/company\n                #:company\n                #:has-root-company-p\n                #:sub-companies-of)\n  (:import-from #:screenshotbot/insights/dashboard\n                #:render-analytics))\n(in-package :screenshotbot/insights/fuzz)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass fake-company ()\n  ((sub-companies :initarg :sub-companies\n                  :reader sub-companies-of)\n   (screenshotbot/model/company::name :initform \"Acme Corporation\")))\n\n(defmethod has-root-company-p ((a company) (self fake-company))\n  (or\n   (eql a self)\n   (member a (sub-companies-of self))))\n\n(defmethod personalp ((Self fake-company))\n  nil)\n\n(defmethod singletonp ((self fake-company))\n  nil)\n\n(defmethod auth:can-view ((self fake-company) user)\n  t)\n\n;; use ids: 32046 to test on staging\n;; on prod: https://phabricator.tdrhq.com/P115\n(defhandler (nil :uri \"/insights/fuzz\") (ids)\n  (with-login ()\n    (assert (adminp (auth:current-user)))\n    (let ((companies (mapcar #'bknr.datastore:store-object-with-id\n                             (mapcar #'parse-integer (str:split \",\" ids)))))\n      (render-analytics\n       (make-instance 'fake-company :sub-companies companies)\n       :fuzz t))))\n"
  },
  {
    "path": "src/screenshotbot/insights/maps.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/maps\n  (:use #:cl)\n  (:import-from #:screenshotbot/insights/date\n                #:increment-date\n                #:list-dates\n                #:format-date)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:recorder-run-channel\n                #:%created-at)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:run-id-to-run-map\n                #:run-screenshot-map)\n  (:import-from #:priority-queue\n                #:pqueue-empty-p\n                #:pqueue-front\n                #:pqueue-pop\n                #:pqueue-push\n                #:make-pqueue)\n  (:import-from #:serapeum/iter\n                #:collecting)\n  (:local-nicknames (#:screenshot-map #:screenshotbot/model/screenshot-map))\n  (:export\n   #:screenshot-count-map))\n(in-package :screenshotbot/insights/maps)\n\n(defun %map-to-list (map)\n  (loop for key being the hash-keys of map\n            using (hash-value val)\n          collect (list key val)))\n\n(defun date-map (runs)\n  \"From runs create a map of (<date>, runs)\"\n  (let ((map (make-hash-table :test #'equal)))\n    (dolist (run runs)\n      (pushnew run (gethash (format-date (%created-at run)) map)))\n    (%map-to-list map)))\n\n(defun channel-map (runs)\n  \"From a list of runs, generate <channel>,runs\"\n  (let ((map (make-hash-table)))\n    (dolist (run runs)\n      (pushnew run (gethash (recorder-run-channel run) map)))\n    (%map-to-list map)))\n\n(defun date-channel-map (runs)\n  \"From a list of runs, generate <date>,<channel>,runs\"\n  (loop for (date date-runs) in (date-map runs)\n        for channel-map = (channel-map date-runs)\n        appending\n        (loop for item in channel-map\n              collect (list* date item))))\n\n(defun max-run-length (runs)\n  (loop for run in runs\n        for map = (run-screenshot-map run)\n        if map\n          maximizing (fset:size (screenshot-map:to-map\n                                 map))))\n\n(defun date-channel-maxLength (date-channel-map)\n  \"From a DATE-CHANNEL-MAP, compute <date>,<channel>,<max length of run\nwe saw in that day>. If a channel didn't trigger on a specific day, it\nwon't be listed here.\"\n  (loop for (date channel runs) in date-channel-map\n        collect\n        (list date channel (max-run-length runs))))\n\n(defun %add-sentinal-date (list)\n  \"Adds one fake date to the end of the list\"\n  (let ((last-date (first (car (last list)))))\n    (let ((sentinel-item (list (increment-date last-date) :channel 0)))\n      (append\n       list\n       (list\n        sentinel-item)))))\n\n(defun date-to-screenshots-count (date-channel-maxLength &key (channel-expiration-window 30))\n  (when date-channel-maxLength\n    (let* ((date-channel-maxLength (%add-sentinal-date\n                                    (sort\n                                     (copy-list date-channel-maxLength)\n                                     #'string<\n                                     :key #'first)))\n          (result)\n\n          (channel-size (make-hash-table))\n          ;; For every channel, track at what point we'll automatically\n          ;; delete it from the running count.\n          (channel-expiry (make-hash-table))\n          ;; The queue might have outdated entries, so beware!\n          (expiry-queue (make-pqueue #'string<))\n          (curr-date (first (first date-channel-maxLength)))\n          (size 0))\n     (labels ((bump-expiry (channel date)\n                ;;(log:debug \"Bumping expiry for ~a to ~a\" channel date)\n                (setf (gethash channel channel-expiry) date)\n                (pqueue-push channel date expiry-queue))\n              (expire-entries (date)\n                (unless (pqueue-empty-p expiry-queue)\n                  (multiple-value-bind (channel expiry-date)\n                      (pqueue-front expiry-queue)\n                    (when (string< expiry-date date)\n                      (pqueue-pop expiry-queue)\n                      (when (equal expiry-date (gethash channel channel-expiry))\n                        (let ((curr-size (gethash channel channel-size)))\n                          (unless curr-size\n                            (error \"Hmm, expected to see a the channel-size since it's not expired yet\"))\n                          (decf size curr-size))\n                        (remhash channel channel-expiry)\n                        (remhash channel channel-size))\n                      (expire-entries date))))))\n       (loop for (date channel maxLength) in date-channel-maxLength do\n         (bump-expiry channel (increment-date date channel-expiration-window))\n         (expire-entries date)\n         (let ((last-size (or (gethash channel channel-size) 0)))\n           (loop while (string< curr-date date) do\n             (push (list curr-date size) result)\n             (setf curr-date (increment-date curr-date)))\n        \n           ;; At this point curr-date = date\n           (decf size last-size)\n           (setf (gethash channel channel-size) maxLength)\n           (incf size (gethash channel channel-size)))))\n     (nreverse result))))\n\n(defun screenshot-count-map (runs)\n  (date-to-screenshots-count (date-channel-maxLength (date-channel-map runs))))\n\n(defun screenshot-count-map-by-filter (company prefix &key file)\n  (let* ((runs (collecting\n                 (fset:do-map (run-id run (run-id-to-run-map company))\n                   (declare (ignore run-id))\n                   (collect run))))\n         (runs (loop for run in runs\n                     if (str:starts-with-p prefix (channel-name (recorder-run-channel run)))\n                       collect run)))\n    (let ((result (screenshot-count-map runs)))\n      (when file\n        (with-open-file (s file :direction :output)\n          (loop for (date count) in result\n                do (format s \"~a,~a~%\" date count))))\n      result)))\n\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/insights/pull-requests.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/pull-requests\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:%run-build-url\n                #:build-url\n                #:phabricator-diff-id\n                #:gitlab-merge-request-iid)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url)\n  (:import-from #:screenshotbot/insights/runs\n                #:runs-for-last-60-days)\n  (:import-from #:screenshotbot/model/report\n                #:acceptable-history-item-user\n                #:acceptable-history\n                #:acceptable-state\n                #:reports-for-run)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/report-api\n                #:report-run\n                #:report-acceptable)\n  (:import-from #:screenshotbot/insights/variables\n                #:*num-days*)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:report-link))\n(in-package :screenshotbot/insights/pull-requests)\n\n(defun safe-pr (run)\n  (or\n   (pull-request-url run)\n   (gitlab-merge-request-iid run)\n   ;; This is incorrect, needs revision here:\n   (phabricator-diff-id run)))\n\n(defvar *reports-for-run* (make-hash-table :test #'eql))\n\n(defun fast-reports-for-run (run)\n  (cond\n    ((> (screenshotbot/model/recorder-run::%created-at run)\n        (- (get-universal-time)\n           3600))\n     (reports-for-run run))\n    (t\n     (cdr\n      (util:or-setf\n       (gethash run *reports-for-run*)\n       (list*\n        :dummy\n        (reports-for-run run)))))))\n\n(easy-macros:def-easy-macro do-run-report (&binding run &binding report\n                                                    company\n                                                    &key num-days\n                                                    &fn fn)\n  (loop for run in (runs-for-last-60-days company :num-days num-days) do\n    (loop for report in (fast-reports-for-run run) do\n      (fn run report))))\n\n(defun pr-to-actions (company &key (num-days 30))\n  (let ((actions (make-hash-table :test #'equal))\n        (runs (runs-for-last-60-days company :num-days num-days))\n        (failure-examples (make-hash-table :test #'equal)))\n    (loop for run in runs\n          if (safe-pr run)\n          do (setf (gethash (safe-pr run) actions)\n                   :none))\n    (do-run-report (run report company :num-days num-days)\n      (when (eql :none (gethash (safe-pr run) actions))\n        (setf (gethash (safe-pr run) actions)\n              :changed)\n        (setf (gethash (safe-pr run) failure-examples) report))\n      (when-let ((acceptable (report-acceptable report)))\n        (case (acceptable-state acceptable)\n          (:rejected\n           (setf (gethash (safe-pr run) actions)\n                 :rejected)\n           (setf (gethash (safe-pr run) failure-examples) report))\n          (:accepted\n           (when (eql :changed #| should not be :none |#\n                      (gethash (safe-pr run) actions))\n             (setf (gethash (safe-pr run) actions)\n                   :accepted))))))\n    (values actions failure-examples)))\n\n(defun pr-to-actions-to-csv (company output &key (num-days 60))\n  \"Meant to sending over this data manually to customers\"\n  (with-open-file (output output :direction :output :if-exists :supersede)\n    (multiple-value-bind (actions failure-examples)\n        (pr-to-actions company :num-days num-days)\n      (format output\n              \"PR URL,INTERESTING FEEDBACK,REPORT URL,BUILD URL~%\")\n      (loop for pr being the hash-keys of actions\n              using (hash-value state)\n           do\n              (format output \"~a,~a,~a,~a~%\" pr (string-downcase state)\n                      (util/misc:?.\n                       report-link\n                       (gethash pr failure-examples))\n                      (util/misc:?.\n                       %run-build-url\n                       (util/misc:?. report-run (gethash pr failure-examples))))))))\n\n(defun user-reviews-last-n-days (company &key (num-days *num-days*))\n  (let ((result (make-hash-table)))\n    (do-run-report (run report company :num-days num-days)\n      (when-let ((acceptable (report-acceptable report)))\n        (dolist (user\n                 (remove-duplicates\n                  (loop for history-item in (acceptable-history acceptable)\n                        collect (acceptable-history-item-user history-item))))\n          (incf (gethash user result 0)))))\n    (sort\n     (loop for user being the hash-keys of result\n             using (hash-value count)\n           if user ;; temp fix for T1872\n           collect (list user count))\n     #'>\n     :key #'second)))\n\n(defun user-reviews-last-n-days-csv (company &key num-days output)\n  \"Prints the user-reviews for num-days as a CSV into stdout\"\n  (with-open-file (out output :direction :output)\n    (format out \"EMAIL,NUMBER OF REVIEWS~%\")\n    (loop for (user count) in (user-reviews-last-n-days company :num-days num-days)\n          do\n             (format out \"~a,~a~%\" (auth:user-email user) count))))\n\n"
  },
  {
    "path": "src/screenshotbot/insights/runs.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/runs\n  (:use #:cl)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:%created-at\n                #:recorder-run-channel\n                #:screenshot-name\n                #:created-at)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:run-id-to-run-map\n                #:run-screenshot-map)\n  (:import-from #:screenshotbot/model/company\n                #:sub-companies-of)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:screenshot-map-channel)\n  (:import-from #:util/hash-lock\n                #:with-hash-lock-held\n                #:hash-lock)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:screenshotbot/insights/variables\n                #:*num-days*)\n  (:import-from #:screenshotbot/insights/date\n                #:format-date)\n  (:import-from #:screenshotbot/insights/maps\n                #:screenshot-count-map)\n  (:local-nicknames (:screenshot-map #:screenshotbot/model/screenshot-map)))\n(in-package :screenshotbot/insights/runs)\n\n(defun runs-for-last-60-days (company &key (num-days (+ 30 *num-days*))\n                                        (channel-filter nil))\n  \"Find all the runs in the last 60 days for the given company\"\n\n  (append\n\n   ;; Bring children too\n   (loop for company in (fset:convert 'list (sub-companies-of company))\n         appending (runs-for-last-60-days company :num-days num-days))\n\n   (let* ((start-time (local-time:timestamp-\n                       (local-time:now)\n                       num-days :day))\n          (all-runs (run-id-to-run-map company))\n          (next-rank (1- (fset:size all-runs)))\n          (res nil))\n     (loop for next = (and\n                       (>= next-rank 0)\n                       (nth-value 1 (fset:at-rank all-runs next-rank)))\n           if (not next)\n             return res\n           else if (local-time:timestamp< (created-at next)\n                                          start-time)\n                  return res\n           else do\n             (when (run-matches-channel-filter-p next channel-filter)\n               (push next res))\n             (decf next-rank)))))\n\n(defun run-matches-channel-filter-p (run channel-filter)\n  (or\n   (null channel-filter)\n   (str:starts-with-p channel-filter (channel-name (recorder-run-channel run)))))\n\n(defstruct active-screenshot-key\n  date screenshot-key)\n\n(defun fast-remove-duplicates (list &key (test #'equalp))\n  (let ((hash-table (make-hash-table :test test)))\n    (loop for x in list\n          do (setf (gethash x hash-table) t))\n    (alexandria:hash-table-keys hash-table)))\n\n\n\n(defvar *hash-lock* (make-instance 'hash-lock))\n\n(defun runs-to-date-map (runs)\n  \"Return a list, with keys being date, and values being list of all distinct screenshot-maps\"\n  (let ((map (make-hash-table :test #'equal)))\n    (loop for run in runs\n          for date = (format-date (%created-at run))\n          do\n             (push (run-screenshot-map run)\n                   (gethash date map)))\n    (loop for date being the hash-keys of map\n            using (hash-value screenshot-maps)\n          collect\n          (list date (fast-remove-duplicates screenshot-maps :test #'eql)))))\n\n(defun %active-screenshot-keys (company &key channel-filter)\n  (screenshot-count-map\n   (runs-for-last-60-days company :channel-filter channel-filter)))\n\n(defparameter *ans-cache* (make-hash-table :test #'equal))\n\n(defun active-screenshot-keys (company &key channel-filter)\n  (with-hash-lock-held (company *hash-lock*)\n    (util:or-setf\n     (gethash (list company *num-days* channel-filter) *ans-cache*)\n     (%active-screenshot-keys company :channel-filter channel-filter))))\n\n(def-cron clear-ans-cache ()\n  (clrhash *ans-cache*))\n\n\n;; (hcl:profile (active-screenshot-keys (screenshotbot/model/company:company-with-name \"Apadmi Ltd\")))\n"
  },
  {
    "path": "src/screenshotbot/insights/screenshotbot.insights.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :screenshotbot.insights\n  :serial t\n  :depends-on (:screenshotbot\n               :priority-queue)\n  :components ((:file \"variables\")\n               (:file \"date\")\n               (:file \"maps\")\n               (:file \"runs\")\n               (:file \"pull-requests\")\n               (:file \"dashboard\")\n               (:file \"fuzz\")))\n\n(defsystem :screenshotbot.insights/tests\n  :serial t\n  :depends-on (:screenshotbot.insights\n               :util/fiveam)\n  :components ((:file \"test-date\")))\n"
  },
  {
    "path": "src/screenshotbot/insights/test-date.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/test-date\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/insights/date\n                #:list-dates\n                #:increment-date\n                #:%date-to-universal\n                #:format-date))\n(in-package :screenshotbot/insights/test-date)\n\n\n(util/fiveam:def-suite)\n\n(test simple-format\n  (is (equal \"1900-01-01\"\n             (format-date 10))))\n\n(test %date-to-universal\n  (is (equal 0 (%date-to-universal \"1900-01-01\")))\n  (is (equal 86400 (%date-to-universal \"1900-01-02\"))))\n\n(test increment-date\n  (is (equal \"1900-01-02\" (increment-date \"1900-01-01\")))\n  (is (equal \"2024-02-01\" (increment-date \"2024-01-31\"))))\n\n(test increment-date-by-multiple\n  (is (equal \"2024-03-01\" (increment-date \"2024-01-31\" 30))))\n\n(test list-dates\n  (is (equal\n       (list \"2024-01-31\"\n             \"2024-02-01\"\n             \"2024-02-02\")\n       (list-dates\n        :from \"2024-01-31\"\n        :to \"2024-02-02\"))))\n\n"
  },
  {
    "path": "src/screenshotbot/insights/variables.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/insights/variables\n  (:use #:cl))\n(in-package :screenshotbot/insights/variables)\n\n(defvar *num-days* 30\n  \"The default number of days being used to generate this graph.\")\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/installation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;;;; Singleton state representing the current installation\n\n(uiop/package:define-package :screenshotbot/installation\n  (:use #:cl #:screenshotbot/plugin\n        #:core/installation/auth-provider\n        #:core/installation/auth)\n  (:import-from #:core/installation/mailer\n                #:noop-mailer\n                #:mailer*\n                #:mailer)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:abstract-installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/login/common\n                #:standard-auth-provider)\n  (:import-from #:util/recaptcha\n                #:installation-with-recaptcha)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:core/config/api\n                #:config)\n  (:export\n   #:installation\n   #:installation-domain\n   #:plugin\n   #:find-plugin\n   #:with-plugin\n   #:plugins\n   #:mailer\n   #:auth-provider\n   #:auth-providers\n   #:mailer*\n   #:auth-provider-signin-form\n   #:auth-provider-signup-form\n   #:standard-auth-provider\n   #:multi-org-feature\n   #:default-oidc-provider\n   #:installation-domain\n   #:default-logged-in-page\n   #:installation-s3-store\n   #:null-s3-store\n   #:pre-compiled-assets\n   #:one-owned-company-per-user\n   #:call-with-ensure-user-prepared\n   #:replay-password\n   #:oss-installation))\n(in-package :screenshotbot/installation)\n\n(defclass installation (abstract-installation\n                        installation-with-recaptcha)\n  ((plugins :initform nil\n            :initarg :plugins\n            :accessor plugins)\n   (mailer :initform (make-instance 'noop-mailer)\n           :initarg :mailer\n           :accessor mailer)\n   (auth-providers :initform (list\n                              (make-instance 'standard-auth-provider))\n                   :initarg :auth-providers\n                   :accessor auth-providers)\n   (s3-store :initform (make-instance 'null-s3-store)\n             :initarg :s3-store\n             :accessor installation-s3-store)\n   (replay-password :initarg :replay-password\n                    :initform nil\n                    :accessor replay-password)\n   (default-oidc-provider :initform nil\n                          :initarg :default-oidc-provider\n                          :accessor default-oidc-provider)\n   (figma :initarg :figma\n          :initform nil\n          :accessor installation-figma)\n   (cdn :initarg :cdn\n        :initform nil\n        :accessor installation-cdn)))\n\n\n(defclass oss-installation (installation)\n  ())\n\n\n(defclass base-multi-org-feature ()\n  ())\n\n(defclass sub-companies-feature (base-multi-org-feature)\n  ()\n  (:documentation \"There can be multiple organizations, but they must all have the same\nroot.\"))\n\n(defclass multi-org-feature (base-multi-org-feature)\n  ()\n  (:documentation \"subclassing multi-org-feature, turns on the ability\n  for each user to create their own organizations. Most installations\n  don't really want that s so it's turned off by default. It's used on\n  screenshotbot.io\"))\n\n(defclass one-owned-company-per-user ()\n  ()\n  (:documentation \"When added with multi-org-feature, this ensures that\nevery user will have only one company that they are an owner of.\"))\n\n(defclass email-auth-provider ()\n  ())\n\n(defclass null-s3-store ()\n  ())\n\n(defun installation ()\n  *installation*)\n\n(defun (setf installation) (inst)\n  (setf *installation* inst))\n\n(defmethod find-plugin ((installation installation) plugin-class)\n  (loop for x in (plugins installation)\n        if (typep x plugin-class)\n          return x\n        finally\n           (error \"plugin ~a not configured\" plugin-class)))\n\n(defmacro with-plugin ((name) &body body)\n  `(let ((,name (find-plugin (installation) ',name)))\n     ,@body))\n\n(defgeneric default-logged-in-page (installation))\n\n(defmethod default-oidc-provider :around ((self installation))\n  (or\n   (call-next-method)\n   (get-default-oidc-provider-from-config)))\n\n(defun get-default-oidc-provider-from-config ()\n  (when-let ((client-id (config \"sso.oidc.client-id\"))\n             (client-secret (config \"sso.oidc.client-secret\"))\n             (issuer (config \"sso.oidc.issuer\"))\n             (scope (or (config \"sso.oidc.scope\")\n                        \"openid email profile\")))\n    (make-instance 'screenshotbot/login/oidc:oidc-provider\n                   :client-id client-id\n                   :client-secret client-secret\n                   :issuer issuer\n                   :scope scope\n                   :identifier 'default-oidc-provider)))\n"
  },
  {
    "path": "src/screenshotbot/invite.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/invite\n  (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/server\n                #:redirect-home\n                #:with-login\n                #:defhandler)\n  (:import-from #:screenshotbot/model/invite\n                #:invite-used-p\n                #:invite-with-code\n                #:invite\n                #:email-count\n                #:invite-code\n                #:invite-company\n                #:invite-email\n                #:inviter\n                #:invites-with-email)\n  (:import-from #:screenshotbot/model/user\n                #:user-with-email\n                #:personalp\n                #:professionalp\n                #:unaccepted-invites\n                #:user-full-name)\n  (:import-from #:bknr.datastore\n                #:with-transaction\n                #:store-object-with-id)\n  (:import-from #:screenshotbot/model/company\n                #:company-invitation-role\n                #:company-name)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:current-user)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:util #:make-url)\n  (:import-from #:screenshotbot/installation\n                #:mailer\n                #:installation)\n  (:import-from #:screenshotbot/mailer\n                #:send-mail)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/plan\n                #:plan)\n  (:import-from #:auth/model/invite\n                #:all-invites)\n  (:import-from #:core/installation/auth-provider\n                #:auth-providers\n                #:default-oidc-provider\n                #:company-sso-auth-provider)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:auth/login/roles-auth-provider\n                #:company-provider)\n  (:local-nicknames (#:roles #:auth/model/roles))\n  (:export\n   #:invite-page\n   #:render-invite-page\n   #:accept-invite\n   #:invite-enabled-p))\n(in-package :screenshotbot/invite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod invite-enabled-p (company)\n  (and\n   (not (company-sso-auth-provider company))\n   (not (default-oidc-provider *installation*))\n   (not\n    (every #'has-company-provider-p\n           (auth-providers *installation*)))))\n\n\n(defun has-company-provider-p (auth-provider)\n  (when-let ((company-provider (company-provider auth-provider)))\n    (funcall company-provider)))\n\n\n(defmethod user-can-invite-p (company user)\n  (and\n   (invite-enabled-p company)\n   (roles:has-role-p company user\n                     (company-invitation-role company))))\n\n(defmethod render-invite-page (installation)\n  (%invite-page))\n\n(defhandler (invite-page :uri \"/invite\" :method :get) ()\n  (with-login ()\n    (cond\n      ((not (user-can-invite-p (auth:current-company)\n                               (auth:current-user)))\n       (no-permission-to-invite))\n      (t\n       (render-invite-page (installation))))))\n\n(defun no-permission-to-invite ()\n  <simple-card-page>\n    <p>\n    You do not have permission to invite members to this organization.\n    Please contact support@screenshotbot.io if you think this is an error.\n    </p>\n  </simple-card-page>)\n\n(defun %invite-page ()\n  <simple-card-page form-action= \"/invite\" >\n    <div class= \"card-header\">\n      <h3>Invite Member</h3>\n    </div>\n\n    <div class= \"alert alert-danger d-none\">\n    </div>\n    <div class= \"form-group mb-3\">\n      <label class= \"form-label\" for= \"email\">Email address (we'll send them an invite)</label>\n      <input type= \"email\" class= \"form-control\" name= \"email\" id= \"email\"\n             placeholder= \"melissa@example.com\" />\n    </div>\n\n    <div class= \"card-footer\">\n      <input type= \"submit\" class= \"btn btn-primary form-control\"\n             value= \"Invite\" />\n    </div>\n\n  </simple-card-page>)\n\n(defun %user-count (company)\n  (+\n   (length\n    (roles:users-for-company company))\n   (length\n    (loop for invite in (all-invites :company company)\n          unless (invite-used-p invite)\n            collect invite))))\n\n(defhandler (invite-post :uri \"/invite\" :method :post) (email)\n  (when (personalp (current-company))\n    (hex:safe-redirect 'invite-page))\n  (let ((errors))\n    (flet ((check (name test message)\n             (unless test\n               (push (cons name message) errors))))\n      (check :email (not (str:emptyp email))\n           \"We need an email before we can invite\")\n      (check :email (user-can-invite-p (auth:current-company) (auth:current-user))\n             \"You do not have the permission to invite a user\")\n      (check :email (let ((user (user-with-email email)))\n                      (or\n                       (not user)\n                       (not (roles:has-role-p (current-company) user t))))\n             \"A user with that email is already a part of this organization\")\n      (check :email (not (invites-with-email email\n                                            :company (current-company)))\n             ;; todo: resend anyway\n             \"There's already a pending invite to that email address\")\n      (when\n          (or\n           ;; new style:\n           (and\n            (gk:check :limit-invites (auth:current-company))\n            (>= (%user-count (auth:current-company)) 5)))\n        (warn \"Invite limit reached for ~a [not a crash, just a warning to go upsell now]\" (current-company))\n        (push\n         <span>\n           You have reached the limit of users and invites on this account.\n           You can <a href= \"/team\">remove users or invites</a>,\n           or you can <a href= \"/billing/dashboard\">upgrade your plan from here</a>.\n         </span>\n         errors))\n      (cond\n        (errors\n         (with-form-errors (:email email\n                            :errors (reverse errors)\n                            :was-validated t)\n           (%invite-page)))\n        (t\n         (let ((invite (make-instance 'invite\n                                      :company (current-company)\n                                      :inviter (current-user)\n                                      :email email)))\n           (let ((user (user-with-email email)))\n             (when user\n               (with-transaction ()\n                (pushnew invite (unaccepted-invites user)))))\n           (send-email-for-invite invite)\n           (hex:safe-redirect 'invite-successful\n                               :email email)))))))\n\n(defun invite-alert (invite)\n  <div class= \"alert alert-info\">\n    <h4 class= \"mb-3 mt-0 pt-0\" >Your invitation from ,(user-full-name (inviter invite)) is pending.</h4>\n    <p class= \"mb-0 pb-0\" >Once you create your account you will have an option of accepting the invite.</p>\n  </div>)\n\n(defhandler (invite-signup-page :uri \"/invite/signup/:invite-code\") (invite-code)\n  (let ((invite (invite-with-code invite-code)))\n    (cond\n       ((or (null invite)\n            (invite-used-p invite))\n        <simple-card-page>\n          <p>The invite link has expired. Please ask your administrator to send you a new link.</p>\n        </simple-card-page>)\n      (t\n       (with-login (:signup t :alert (invite-alert invite)\n                    :invite invite\n                    :ensure-prepared nil)\n         (cond\n           ((roles:has-role-p (invite-company invite) (current-user) t)\n            (setf (auth:current-company) (invite-company invite))\n            (redirect-home))\n           (t\n            (with-transaction ()\n              (push invite (unaccepted-invites (current-user)))\n              (setf (invite-used-p invite) t))\n            (redirect-home))))))))\n\n(defhandler (invite-successful :uri \"/invite/success/:email\") (email)\n  <simple-card-page>\n    <div class= \"card-header\">\n      <h3>Invite sent</h3>\n    </div>\n\n    <p>,(progn email) should have received an email with details on how to sign-up.\n    </p>\n\n    <p>After their account is created, they will be able to add themselves to your organization.</p>\n\n    <div class= \"card-footer\">\n      <a href= (make-url 'invite-page) class= \"btn btn-primary\" >Invite More People</a>\n      <a href= \"/team\" class= \"ms-3\" >Manage Invitations</a>\n    </div>\n  </simple-card-page>)\n\n(defmethod invite-signup-link ((invite invite))\n  (hex:make-full-url\n                  hunchentoot:*request*\n                  'invite-signup-page\n                   :invite-code (invite-code invite)))\n\n(defun send-email-for-invite (invite)\n  (send-mail\n   (mailer (installation))\n   :subject (format nil \"~a has invited you to join ~a on Screenshotbot\"\n                    (user-full-name\n                     (inviter invite))\n                    (company-name\n                     (invite-company invite)))\n   :to (invite-email invite)\n   :html-message\n   <html>\n     <body>\n       <p>You have been invited to participate in your organization's Screenshotbot\n         account.</p>\n\n       <p><a href= (invite-signup-link invite)>Click here to sign up or join</a>.</p>\n\n       <p>Your friendly neighborhood bot, <br/>\n         Screenshotbot</p>\n     </body>\n   </html>)\n  (with-transaction ()\n    (incf (email-count invite))))\n\n(defun accept-invite (invite &key (redirect t)\n                               (user (current-user)))\n  (%accept-invite invite user)\n  (Setf (current-company) (invite-company invite))\n  (when redirect\n    (redirect-home)))\n\n(defun %accept-invite (invite user)\n  (when (or\n         (member invite (unaccepted-invites user))\n         (string-equal (invite-email invite)\n                       (auth:user-email user)))\n    ;; Let's do the accept\n    (setf (roles:user-role (invite-company invite) user)\n          ;; TODO: We should be able to default this to roles:guest\n          'roles:standard-member)\n    (setf (invite-used-p invite) t)\n    (deletef (unaccepted-invites user) invite)))\n\n(defhandler (invite-accept  :uri  \"/invite/accept\") (invite-id)\n  (let ((invite (store-object-with-id (parse-integer invite-id))))\n    (accept-invite invite)))\n"
  },
  {
    "path": "src/screenshotbot/js/acceptance.js",
    "content": "\nsetupLiveOnAttach(\".acceptance-left-side-bar\", function () {\n    $(\".review-list a\", $(this)).click(function (e) {\n\n        var href = $(this).attr(\"href\");\n\n        $.ajax({\n            url: href,\n            error: function () {\n                swAlert(\"Something went wrong, please refresh the page and try again\");\n            },\n            success: function(data){\n                var html = $(data);\n                $(\".review-panel\").html(html);\n                callLiveOnAttach($(\".review-panel\"));\n            }\n        });\n\n        e.preventDefault();\n    });\n});\n\n\nsetupLiveOnAttach(\".review-input\", function () {\n    var reviewInput = $(this);\n    function replace(href, comment) {\n        $.ajax({\n            url: href,\n            data: {\n                comment: comment,\n            },\n            error: function () {\n                swAlert(\"Something went wrong, please refresh the page and try again\");\n            },\n            success: function(data) {\n                var html = $(data);\n                $(\".modal\", reviewInput).modal('hide');\n                reviewInput.closest(\".review-input-container\").replaceWith(html);\n                callLiveOnAttach(html);\n            }\n        });\n    };\n\n    function refreshLeftSideBar() {\n        var panel = $(\".acceptance-left-side-bar\");\n        var href = panel.data(\"refresh\");\n        $.ajax({\n            url: href,\n            error: function () {\n                swAlert(\"Something went wrong, please refresh the page and try again\");\n            },\n            success: function (data) {\n                var html = $(data);\n                panel.replaceWith(html);\n                callLiveOnAttach(html);\n            }\n        });\n    }\n\n    function updateTab(klass) {\n        var id = reviewInput.closest(\".tab-pane\").attr(\"id\");\n        var title = $(\"[data-bs-target='#\" + id + \"'] > span\");\n\n        title.removeClass(\"text-success\")\n            .removeClass(\"text-danger\");\n\n        title.addClass(klass);\n        refreshLeftSideBar();\n    }\n    $(\".accept-link\", $(this)).click(function (e) {\n        replace($(this).attr(\"href\"));\n        updateTab(\"text-success\");\n        e.preventDefault();\n    });\n\n    $(\".save-rejection\", $(this)).click(function (e) {\n        replace($(\".reject-link\", reviewInput).attr(\"href\"), $(\"[name='comment']\", reviewInput).val());\n        updateTab(\"text-danger\");\n        e.preventDefault();\n    });\n\n});\n"
  },
  {
    "path": "src/screenshotbot/js/common.js",
    "content": "$sb = {\n    ajax: function(data) {\n        let updated = {\n            ...data,\n            error: function (xmlHttpRequest) {\n                if (xmlHttpRequest.status == 410) {\n                    alert(\"This page has expired. Please refresh and try again.\");\n                } else {\n                    if (data.error) {\n                        data.error(xmlHttpRequest);\n                    }\n                }\n            }\n        }\n\n        console.log(\"Using\", updated);\n        $.ajax(updated);\n    }\n}\n\nfunction setupLiveOnAttach(selector, fn) {\n    if (!window.liveAttachEvents) {\n        window.liveAttachEvents = [];\n    }\n\n    window.liveAttachEvents.push([selector, fn])\n    $(selector).map(fn);\n}\n\nfunction callLiveOnAttach(nodes) {\n    console.log(\"calling live on attach\", nodes, window.liveAttachEvents);\n    $(nodes).map(function () {\n        var node  = this;\n        window.liveAttachEvents.forEach(function (ev) {\n            $(ev[0], node).map(ev[1]);\n\n            if ($(node).is(ev[0])) {\n                ev[1].call(node);\n            }\n        });\n\n    });\n}\n\nlet _identity = new DOMMatrixReadOnly([1,0,0,1,0,0]);\n\n\nfunction updateResizeObserver(el, obs) {\n    var key = \"r-obs\";\n    var prevObserver = $(el).data(key);\n    if (prevObserver) {\n        prevObserver.disconnect();\n    }\n    $(el).data(key, obs);\n    obs.observe(el);\n}\n\n/*\n * Toggles visibility between two children of the parent.\n *\n * If isSelector2 is set, the selector2 gets the visibility, otherwise\n * selector1 does.\n */\nfunction toggleVisibilityForTwoChildren(parent,\n                                        selector1,\n                                        selector2,\n                                        isSelector2) {\n    let child1 = $(parent).find(selector1);\n    let child2 = $(parent).find(selector2);\n\n    function maybeShow(child, show) {\n        if (show) {\n            child.removeClass(\"d-none\");\n        } else {\n            child.addClass(\"d-none\");\n        }\n    }\n\n    maybeShow(child1, !isSelector2);\n    maybeShow(child2, isSelector2);\n}\n                                        \n\n\nfunction prepareReportJs () {\n    setupLiveOnAttach(\".change-image-left\", function () {\n        //console.log(\"Setting up mouseover\", this);\n        var img = $(this);\n        var oldSrc = $(this).attr(\"src\");\n        var newImg = $(this).closest(\".change-image-row\")\n            .find(\".change-image-right\");\n        var newSrc = newImg.attr(\"src\");\n\n        function findSourceTag(img) {\n            return $(img).closest(\"picture\").find(\"source[type='image/webp'\");\n        }\n\n        var oldWebpSrcset = findSourceTag(this).attr(\"srcset\");\n        var newWebpSrcset = findSourceTag(newImg).attr(\"srcset\");\n\n        var isTouching = false;\n        var isMousing = false;\n\n        function setImg() {\n            let isUpdated = (isTouching || isMousing);\n            let [src, webpSrcset] =\n                isUpdated\n                ? [newSrc, newWebpSrcset]\n                : [oldSrc, oldWebpSrcset];\n            if (src === undefined) {\n                throw 'No src available';\n            }\n\n            if (webpSrcset ==- undefined) {\n                throw 'No webp srcset available';\n            }\n\n            if (!$.fx.off) {\n                $(img).attr(\"src\", src);\n                findSourceTag(img).attr(\"srcset\", webpSrcset);\n            }\n\n            toggleVisibilityForTwoChildren($(img).closest(\"div\"),\n                                           \".non-hover-badge\",\n                                           \".hover-badge\",\n                                           isUpdated);\n        }\n\n        $(this).mouseover(function () {\n            isMousing = true;\n            setImg();\n        });\n\n        $(this).mouseout(function () {\n            isMousing = false;\n            setImg();\n        });\n\n        $(this).contextmenu(function () {\n            isMousing = false;\n            setImg();\n        });\n\n        $(this).on(\"touchstart\", function (){\n            isTouching = true;\n            setImg();\n            return false;\n        });\n\n        $(this).on(\"touchend\", function () {\n            isTouching = false;\n\n            // This is a lie, because on mobile, we still want the\n            // touchend to look like a mouseout\n            isMousing = false;\n            setImg();\n            return false;\n        });\n    });\n\n    function resetImageZoom(img) {\n        img.stop();\n        img.css(\"background-image\", \"none\");\n        img.css(\"object-position\", \"0px 0px\");\n        img.css(\"background-size\", \"100%\");\n        img.css(\"background-position\", \"0 0\");\n    }\n\n\n    function setupImageComparison() {\n        var modal = this;\n        var img = $(\".image-comparison-modal-image\", this);\n        var canvasContainer = $(\".canvas-container\",this);\n\n        var wrapper = $(img).closest(\".progress-image-wrapper\");\n        var loading = $(wrapper).find(\".loading\");\n\n\n        var zoomToLink = undefined; // loaded from comparison nibble\n        var zoomToChange = $(\".zoom-to-change\", this);\n        var zoomToChangeSpinner = $(zoomToChange).find(\".spinner-border\");\n\n        var src = img.data(\"src\");\n        resetImageZoom(img);\n\n        zoomToChange.prop(\"disabled\", true);\n        loading.show();\n        img.hide();\n\n        var beforeAlpha = 0.2;\n        var diffAlpha = 1;\n        var afterAlpha = 0;\n\n\n        function refresh() {\n            $(canvasContainer).trigger(\"sb:refresh\");\n        }\n\n        function resetMenu() {\n            $(\".view-item\").removeClass(\"fw-bold\");\n        }\n        resetMenu();\n\n        function setupViewItem(name, before, diff, after) {\n            return $(name, modal).click((e) => {\n                console.log(\"Switching to updated view\");\n                beforeAlpha = before;\n                diffAlpha = diff;\n                afterAlpha = after;\n                refresh();\n                resetMenu();\n                $(name, modal).addClass(\"fw-bold\");\n                e.preventDefault();\n            }).css(\"cursor\", \"pointer\");\n        }\n\n        setupViewItem(\".view-updated\", 0, 0, 1);\n        setupViewItem(\".view-diff\", 0.2, 1, 0)\n            .addClass(\"fw-bold\");\n        setupViewItem(\".view-previous\", 1, 0, 0);\n\n        $sb.ajax({\n            url: src,\n            success: function (data) {\n                loading.hide();\n                img.show();\n                zoomToLink = data.zoomTo;\n                metricsLink = data.metrics;\n                $(\".metrics-link\", modal).attr(\"href\", metricsLink);\n\n                console.log(\"Loading into canvas\", data);\n                new SbImageCanvas(\n                    canvasContainer.get(0),\n                    [{\n                        alpha: () => beforeAlpha,\n                        src: data.background,\n                    },\n                     {\n                         alpha: () => diffAlpha,\n                         src: data.src,\n                     },\n                     {\n                         alpha: () => afterAlpha,\n                         src: data.afterImage,\n                     }\n                    ],\n                    data.masks, {\n                        onImagesLoaded: function () {\n                            zoomToChange.prop(\"disabled\", false);\n                        },\n                        onZoomComplete: function () {\n                            zoomToChange.prop(\"disabled\", false);\n                        },\n                    }).load();\n\n            },\n            error: function () {\n                swAlert(\"Network request failed! This could be because we're still processing the image. Please try again in a minute\");\n            }\n        });\n\n        zoomToChange.on(\"click\", function (e) {\n            console.log(\"zoom to change clicked\");\n            // move the image out of the way\n\n            zoomToChange.prop(\"disabled\", true);\n            zoomToChangeSpinner.show();\n\n            $sb.ajax({\n                url: zoomToLink,\n                success: function(data) {\n                    if (data.x < 0) {\n                        alert(\"Both images are identical in content\");\n                    } else {\n                        var event = new CustomEvent(\"zoomToChange\", { detail: data });\n                        $(\"canvas\", canvasContainer).get(0).dispatchEvent(event);\n                    }\n                    zoomToChangeSpinner.hide();\n                },\n                error: function () {\n                    alert(\"We couldn't find changed pixels! This is likely a bug on our end.\");\n                    zoomToChange.prop(\"disabled\", false);\n                    zoomToChangeSpinner.hide();\n                }\n            });\n\n            e.preventDefault();\n        });\n\n        $(modal).on(\"hide.bs.modal\", function () {\n            zoomToChange.off(\"click\");\n        });\n\n\n        function actuallyToggleCompareView(e) {\n            var views = ['.view-previous', '.view-updated'];\n            for (var view of views) {\n                var $view = $(view, $(modal));\n                if ($view.length > 1) {\n                    console.log(\"There's more than one of these menu items to click\", $view);\n                }\n                if (!$view.hasClass('fw-bold')) {\n                    console.log(\"clicking\", view);\n                    $view.click();\n                    return;\n                }\n            }\n        }\n\n        function toggleCompareView(e) {\n            if (e.key === 'v') {\n                actuallyToggleCompareView(e);\n            }\n        }\n\n        $(\".view-toggle\", modal).click(actuallyToggleCompareView);\n        \n        function removeToggleCompareView() {\n            $(document).off(\"keyup\", toggleCompareView);\n        }\n\n        $(document).on(\"keyup\", toggleCompareView);\n        $(this).on(\"hide.bs.modal\", removeToggleCompareView);\n    }\n\n    setupLiveOnAttach(\".image-comparison-modal\", function () {\n        $(this).on(\"show.bs.modal\", setupImageComparison);\n    });\n\n    setupLiveOnAttach(\n        \".image-comparison-wrapper\",\n        function () {\n            var img = $(this);\n            //console.log(\"Setting up image comparison on: \", img);\n            setupImageComparison.call(img);\n        });\n}\n\n$(document).on(\"click\", \"a.screenshot-run-image\", function (e) {\n    e.preventDefault();\n    var modal = $($(this).data(\"target\"));\n    var canvas = $(\".canvas-container\", modal);\n\n    var src = canvas.data(\"src\");\n    canvas.data(\"image-number\", $(this).data(\"image-number\"));\n\n    (new bootstrap.Modal(modal.get(0), {})).show();\n});\n\n$(document).on(\"show.bs.modal\", \".single-screenshot-modal\", function () {\n    console.log(\"callback called\");\n    var title = $(this).find(\".modal-title\");\n    var canvas = $(this).find(\".canvas-container\");\n    var next = $(this).find(\".next\");\n    var prev = $(this).find(\".previous\");\n    var page = $(this).find(\".page-num\");\n\n    next.unbind();\n    prev.unbind();\n\n    function fetchData() {\n        var n = $(canvas).data(\"image-number\");\n        var src = $(canvas).data(\"src\");\n        $sb.ajax({\n            url: src,\n            data: {\n                n: n,\n            },\n            error: function () {\n                swAlert(\"Something went wrong, please try refreshing the page\");\n            },\n            success: function (data) {\n                updateData(data);\n            }\n\n        });\n    }\n\n    function updateData(data) {\n        $(title).text(data.title);\n        new SbImageCanvas(canvas.get(0),\n                          [{\n                              alpha: 1,\n                              src: data.src,\n                          }],\n                          [], {}).load();\n    }\n\n    function setEnabled(link, enabled) {\n        if (enabled) {\n            link.removeClass(\"disabled\");\n        } else {\n            link.addClass(\"disabled\");\n        }\n    }\n\n    function updateN(fn) {\n        var num = $(canvas).data(\"image-number\");\n        var length = $(canvas).data(\"length\");\n        num = fn(num);\n\n        page.html(\"\" + (num + 1) + \"/\" + length);\n\n        setEnabled(prev, num > 0);\n        setEnabled(next, num < length - 1);\n\n        $(canvas).data(\"image-number\", num);\n        fetchData();\n    }\n\n    next.on(\"click\", function () {\n        updateN((n) => n+1);\n    });\n\n    prev.on(\"click\", function () {\n        updateN((n) => n-1);\n    });\n\n    // Hacky way of making sure both the initializer and the Next/prev\n    // use the same code paths.\n    updateN((x) => x);\n});\n\n\nprepareReportJs();\n\nfunction setupHeadroom() {\n    var myElement = document.querySelector(\".headroom\");\n    // construct an instance of Headroom, passing the element\n\n    if (myElement !== null) {\n        var headroom = new Headroom(myElement, {\n        });\n        // initialise\n        headroom.init();\n    }\n}\n\n$(setupHeadroom);\n\nsetupLiveOnAttach(\".baguetteBox\", function () {\n    prepareBaguetteBox(this);\n});\n\n\nfunction prepareBaguetteBox(el) {\n    if (typeof baguetteBox !== 'undefined') {\n        var id = $(el).attr(\"id\");\n        if (id) {\n            baguetteBox.run(\"#\" + id);\n        } else {\n            console.log(\"Baguettebox didn't have an id: \", el);\n        }\n    }\n}\n\nsetupLiveOnAttach(\".modal-link\", function () {\n    $(this).click(function (e) {\n        var href = $(this).data(\"href\");\n        $sb.ajax({\n            url: href,\n            error: function () {\n                swAlert(\"Something went wrong. Please reload and try again\");\n            },\n            success: function (data) {\n                console.log(\"got data\", data);\n                var modal = $(data);\n                $(\"body\").append(modal);\n                (new bootstrap.Modal(modal.get(0), {})).show();\n                $(modal).on(\"data.bs.dismiss\", function () {\n                    $(modal).remove();\n                });\n            },\n        });\n        e.preventDefault();\n    });\n});\n\n\nfunction setUrlParameter(key, value) {\n    var url = new URL(window.location);\n    url.searchParams.set(key, value);\n    history.replaceState(null, null, url.href);\n}\n\n"
  },
  {
    "path": "src/screenshotbot/js/compare-branches.js",
    "content": "\n/*($(\".sha-autocomplete\").autocomplete({\n    source: \"/compare-branches/search-sha\",\n    minLength: 1,\n})*/\n"
  },
  {
    "path": "src/screenshotbot/js/default.js",
    "content": "jQuery.timeago.settings.allowFuture = true;\n\n// Stop JS animations if we're using Selenium\nif ($(\"link[href='/assets/css/selenium.css']\").length) {\n    $.fx.off = true;\n}\n\n$(function () {\n\n    var stripeKey = $(\"#stripe-key\").val();\n\n    if (stripeKey === undefined) {\n        return;\n    }\n\n    console.log(\"dfdfdfdf\");\n\n    // Create a Stripe client.\n    var stripe = Stripe(stripeKey);\n\n    // Create an instance of Elements.\n    var elements = stripe.elements();\n\n    // Custom styling can be passed to options when creating an Element.\n    // (Note that this demo uses a wider set of styles than the guide below.)\n    var style = {\n        base: {\n            color: '#32325d',\n            fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif',\n            fontSmoothing: 'antialiased',\n            fontSize: '16px',\n            '::placeholder': {\n                color: '#aab7c4'\n            }\n        },\n        invalid: {\n            color: '#fa755a',\n            iconColor: '#fa755a'\n        }\n    };\n\n    // Create an instance of the card Element.\n    var card = elements.create('card', {style: style});\n\n    // Add an instance of the card Element into the `card-element` <div>.\n    card.mount('#card-element');\n\n    // Handle real-time validation errors from the card Element.\n    card.addEventListener('change', function(event) {\n        var displayError = document.getElementById('card-errors');\n        if (event.error) {\n            displayError.textContent = event.error.message;\n        } else {\n            displayError.textContent = '';\n        }\n    });\n    // Handle form submission.\n    var form = document.getElementById('payment-form');\n    form.addEventListener('submit', function(event) {\n        event.preventDefault();\n\n        stripe.createToken(card).then(function(result) {\n            if (result.error) {\n                // Inform the user if there was an error.\n                var errorElement = document.getElementById('card-errors');\n                errorElement.textContent = result.error.message;\n            } else {\n                // Send the token to your server.\n                stripeTokenHandler(result.token);\n            }\n        });\n    });\n\n    // Submit the form with the token ID.\n    function stripeTokenHandler(token) {\n        // Insert the token ID into the form so it gets submitted to the server\n        makeHiddenField(\"stripe-token\", token.id);\n        makeHiddenField(\"card\", JSON.stringify(token.card));\n        // Submit the form\n        form.submit();\n    }\n\n    function makeHiddenField(name, value) {\n        var form = document.getElementById('payment-form');\n        var hiddenInput = document.createElement('input');\n        hiddenInput.setAttribute('type', 'hidden');\n        hiddenInput.setAttribute('name', name);\n        hiddenInput.setAttribute('value', value);\n        form.appendChild(hiddenInput);\n\n    }\n});\n\n\n$(\"time.timeago\").timeago();\n\nfunction getSelectedRuns() {\n    var ret = [];\n    $(\".recent-run-item\").map(function () {\n        if ($(this).prop(\"checked\")) {\n            var id = $(this).data(\"model-id\");\n            ret.push(id);\n        }\n    });\n    return ret;\n}\n\nfunction swAlert(x) {\n    alert(x);\n}\n\n$(\"#delete-runs\").click(function (e) {\n    console.log(\"#delete-runs clicked\");\n    var runs = getSelectedRuns();\n    var hadPromotedRuns = false;\n    function deleteNextRun() {\n        if (runs.length == 0) {\n            if (hadPromotedRuns) {\n                swAlert(\"Some runs could not be deleted because they were previously promoted\");\n            }\n            location.reload();\n            return;\n        }\n\n        var id = runs.pop();\n        console.log(\"deleting: \" + id);\n        $.ajax({\n            url: \"/runs/\" + id,\n            method: \"DELETE\",\n            success: function (data) {\n                if (data.wasPromoted) {\n                    hadPromotedRuns = true;\n                }\n                deleteNextRun();\n            },\n            error: function () {\n                swAlert(\"Could not delete run, maybe refresh and retry?\");\n            }\n        });\n    }\n    deleteNextRun();\n    e.preventDefault()\n});\n\n$(\"#compare-runs\").click(function () {\n    var runs = getSelectedRuns();\n    if (runs.length > 2) {\n        swAlert(\"Select only two runs to compare.\");\n        return;\n    }\n\n    if (runs.length != 2) {\n        swAlert(\"Select two runs to compare\");\n        return;\n    }\n\n    var min = runs[1];\n    var max = runs[0];\n\n    location.href = \"/runs/\" + max + \"/compare/\" + min;\n});\n\n$(function () {\n    $(\".github-test-connection\").click(function () {\n        var button = this;\n        $(\".spinner\", this).removeClass(\"d-none\");\n        var oldtext = $(\".text\", this).text();\n        $(\".text\", this).text(\"Checking...\");\n        function reset () {\n            $(\".spinner\", button).addClass(\"d-none\");\n            $(\".text\", button).text(oldtext);\n        }\n        $.ajax({\n            dataType: \"json\",\n            method: \"POST\",\n            data: {\n                \"github-link\": $(\"#github-link\").val(),\n            },\n            url: \"/github/test-integration\",\n            success: function (data) {\n                var feedback =\n                    $(\".invalid-feedback\", $(button).parent());\n                var input =\n                    $(\"#github-link\");\n\n\n                if (data[\"result\"]) {\n                    $(input).addClass(\"is-valid\");\n                    $(input).removeClass(\"is-invalid\");\n                    $(\".valid-feedback\", $(button).parent())\n                        .text(\"We are able to connect to this repository\");\n                } else {\n                    $(input).addClass(\"is-invalid\");\n                    $(input).removeClass(\"is-valid\");\n                    $(feedback).text(\"Unable to connect to this repository\");\n                }\n                reset();\n            },\n            error: function() {\n                reset();\n                alert(\"something went wrong, try again in a minute\");\n            }\n        });\n        return false;\n    });\n});\n\nfunction setupNoticeDismiss() {\n    $(\".user-notice-dismiss\").click(function () {\n        var noticeId = $(this).data(\"notice-id\");\n        console.log(\"dismissing notification\");\n        $.ajax({\n            method: \"POST\",\n            url: \"/notice/dismiss\",\n            data: {\n                \"notice-id\": noticeId\n            },\n            success: function (data) {\n                $(\"#user-notice-list\").replaceWith(data);\n                setupNoticeDismiss();\n            },\n            error: function () {\n                console.log(\"something went wrong. Please contact support@\");\n            }\n        });\n    });\n}\n\nsetupNoticeDismiss();\n\n$(\".async-fetch\").map(function (idx, elm) {\n    console.log(\"Element is\", elm);\n    function onError() {\n        $(elm).replaceWith(\"<h3>Error while loading</h3>\");\n    }\n\n    function tryNext() {\n        var status = $(elm).data(\"check-nibble\");\n        console.log(\"Fetching state from \" + status);\n        $.ajax({\n            method: \"GET\",\n            url: status,\n            dataType: \"json\",\n            success: function (data) {\n                console.log(data);\n                if (data[\"state\"] == \"processing\") {\n                    setTimeout(tryNext, 1000);\n                } else if (data[\"state\"] == \"done\") {\n                    var newData = $(data[\"data\"]);\n                    $(elm).replaceWith(newData);\n                    callLiveOnAttach(newData);\n                } else {\n                    onError();\n                }\n            },\n            error: onError,\n        });\n    }\n\n    tryNext();\n});\n\n\nsetupLiveOnAttach(\".load-more-button\", function () {\n    var isInfinite = $(this).data(\"infinite\");\n\n    var disabled = false;\n\n    \n    $(this).click(function (e) {\n        var button = $(this);\n        var link = $(button).data(\"load-more\");\n        console.log(\"Fetching next page\");\n\n        function setDisabled(val) {\n            $(button).prop(\"disabled\", val);\n            disabled = val;\n\n            setTimeout(() => {\n                // We don't want the spinner to show up if it's less\n                // than a certain amount of time since it's distracting.\n                $(button).addClass(\"spinner\");\n            }, 250);\n        }\n\n        setDisabled(true);\n\n        $.ajax({\n            method: \"GET\",\n            url: link,\n            success: function (data) {\n                console.log(\"Got next page\");\n                var div = $(data);\n                var container = $(button).closest(\".load-more-container\");\n                var children = div.children();\n                children.appendTo(container);\n                callLiveOnAttach(children);\n                var bb = button.closest(\".baguetteBox\");\n                button.parent().remove();\n                if (bb.length > 0) {\n                    baguetteBox.destroy(\"#\" + bb.attr(\"id\"));\n                    prepareBaguetteBox(bb);\n                }\n            },\n            error: function(data) {\n                setDisabled(false);\n                alert(\"Something went wrong, please try refreshing the page\");\n            }\n        });\n\n        e.preventDefault();\n    });\n\n    if (isInfinite) {\n        console.log(\"Setting up infinite scroll\");\n        var button = $(this);\n        var called = false;\n        var callback = () => {\n            if (disabled) {\n                return;\n            }\n\n            const scrollTop = window.scrollY; // How much we've scrolled from the top\n            const windowHeight = window.innerHeight; // The height of the visible window\n            const documentHeight = document.documentElement.scrollHeight; // Total height of the page\n            \n            const scrollPercentage = (scrollTop + windowHeight) / documentHeight;\n            \n            if (scrollPercentage >= 0.8) {\n                console.log(\"Hit target for infinite scroll \", this);\n                button.click();\n            }\n        }\n\n        $(window).on(\"scroll\", callback);\n    }\n});\n\n\n$(function () {\n    $(\".codemirror-textarea\").map(function () {\n        var codeMirror = CodeMirror.fromTextArea(\n            this, {\n                value: \"foobar:\",\n                mode: \"text/x-yaml\",\n                lineNumbers: true,\n            });\n        codeMirror.setSize(\"100%\", \"100%\");\n    });\n});\n\n\n$(\".new-compare\").each(function () {\n    Split([$(this).find(\".image-list\").get(0), $(this).find(\".image-details\").get(0)]);\n});\n\n\nvar tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle=\"tooltip\"]'))\nvar tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {\n  return new bootstrap.Tooltip(tooltipTriggerEl)\n})\n\n$(function () {\n    var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle=\"popover\"]'))\n    var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {\n        return new bootstrap.Popover(popoverTriggerEl)\n    })\n});\n\n\n$(function () {\n    if (window.location.pathname == \"/\") {\n        if ($(\"body\").hasClass(\"dashboard\")) {\n            // To avoid redirection when you first load screenshotbot,\n            // we'll load /, and then replace the URL.\n            history.replaceState(null, null, \"/runs\");\n        }\n    }\n});\n\n$(function () {\n    $(\".nibble-span\").map(function () {\n        var $span = $(this);\n        var url = $(this).data(\"nibble-span\");\n        $.ajax({\n            method: \"GET\",\n            url: url,\n            success: function (data) {\n                $span.html(data);\n            },\n            error: function(data) {\n                $span.html(\"error\");\n            }\n        });\n    });\n});\n"
  },
  {
    "path": "src/screenshotbot/js/dummy.lisp",
    "content": "(in-package :cl-user)\n\n(defun hello-world ()\n  (alert \"hello world!\"))\n"
  },
  {
    "path": "src/screenshotbot/js/git-graph.js",
    "content": "/*\n * Code related to the the logic with debugging Git graphs. (See run-page.lisp).\n *\n * If this gets too complicated, remove this from the main default.js,\n * since it's not required.\n */\n(function () {\n\n    function setFontWeight(link, fontWeight) {\n        var $ref = $($(link).attr(\"href\"));\n        $ref.find(\"td:first-of-type\").css(\"font-weight\", fontWeight);\n    }\n\n    $(\"table.git-graph .commit-link\").mouseover(\n        function () {\n            // The href is of the form #commit-id, so we can pass it as a\n            // jquery selector.\n            setFontWeight(this, \"bold\");\n        },\n    ).mouseout(\n        function () {\n            setFontWeight(this, \"normal\");\n        }\n    );\n\n    function highlightBranch(commit) {\n        var seen = new Set();\n        var queue = [];\n        var pos = 0;\n        queue.push(\"#\" + commit);\n\n        while (pos < queue.length) {\n            var commit = queue[pos++];\n\n            if (seen.has(commit)) {\n                continue;\n            }\n\n            seen.add(commit);\n\n            var $td = $(commit);\n            $td.addClass(\"highlighted\");\n            $td.find(\".commit-link\").each(function () {\n                queue.push($(this).attr(\"href\"));\n            });\n        }\n    }\n\n    $(\"table.git-graph .highlight-branch\").click(function () {\n        $(\"table.git-graph tr\").removeClass(\"highlighted\");\n        highlightBranch($(this).data(\"commit\"));\n    });\n})();\n"
  },
  {
    "path": "src/screenshotbot/js/home.js",
    "content": ""
  },
  {
    "path": "src/screenshotbot/js/image-canvas.js",
    "content": "class SbImageCanvas {\n    constructor(canvasContainer,\n                layers,\n                masks,\n                callbacks) {\n        this.canvasContainer = canvasContainer;\n        this.layers = layers;\n        this.masks = masks;\n        this.callbacks = callbacks;\n\n        // Set later.\n        this.ctx = null;\n        this.transform = null;\n        this.coreTranslation = null;\n        this.canvasEl = null;\n        this.imgSize = {}\n\n        this.images = null;\n\n        this.drawTimeout = null;\n\n        $(canvasContainer).data(\"image-canvas\", this);\n\n        /* constants?  At time of writing, unused. */\n\n        var dpr = devicePixelRatio || 1;\n        this.dprTransform = new DOMMatrix([dpr, 0, 0, dpr, 0, 0]);\n        this.dprInv = this.dprTransform.inverse();\n        this.forceTransform = null;\n\n        this.background = null;\n    }\n\n    callCallback(fn) {\n        if (fn) {\n            fn();\n        }\n    }\n\n    drawMasks() {\n        var ctx = this.ctx;\n        ctx.globalAlpha = 1;\n        for (var mask of this.masks) {\n            ctx.beginPath();\n\n            ctx.rect(mask.left,\n                     mask.top,\n                     mask.width,\n                     mask.height);\n            ctx.fillStyle = \"rgba(255, 255, 0, 0.8)\";\n            ctx.fill();\n        }\n    }\n\n    setZoom(z) {\n        this.transform.a = z;\n        this.transform.d = z;\n    }\n\n    getZoom() {\n        return this.transform.a;\n    }\n\n    setTranslate(x, y) {\n        this.transform.e = x;\n        this.transform.f = y;\n    }\n\n    updateTransform () {\n        var mat = this.transform;\n        var res = mat.multiply(this.coreTranslation);\n        //console.log(\"new transform\", res);\n        this.ctx.setTransform(this.forceTransform || res);\n    }\n\n    setForceTransform(z, x, y) {\n        this.forceTransform = new DOMMatrix([z, 0, 0, z, x, y]);\n        this.draw();\n    }\n\n    animateTo(newTransform, callback) {\n        var self = this;\n        console.log(\"animating to: \", newTransform);\n        var oldTransform = self.transform;\n        $(self.canvasEl).animate(\n            {fake:100},\n            {\n                duration: 1000,\n                complete: callback,\n                progress: function (animation, progress) {\n                    self.transform = animateTransform(oldTransform, newTransform, progress);\n                    self.scheduleDraw();\n                },\n            });\n    }\n\n    zoomToImagePx(data) {\n        var self = this;\n        console.log(\"data\", data);\n        var rect = self.canvasEl.getBoundingClientRect();\n        var scale = Math.min(self.canvasEl.width / rect.width, self.canvasEl.height / rect.height);\n\n        var newTransform = calcTransformForCenter(\n            rect.width,\n            rect.height,\n            self.imgSize.width,\n            self.imgSize.height,\n            data.x,\n            data.y,\n            4 /* new zoom */);\n\n        self.animateTo(newTransform, function () {\n            console.log(\"animation done\");\n            self.callCallback(self.callbacks.onZoomComplete);\n        });\n    }\n\n\n\n    updateCoreTransform() {\n\n        let rect = this.canvasContainer.getBoundingClientRect();\n        let scrollWidth = rect.width;\n        let scrollHeight = rect.height;\n\n        var canvasEl = this.canvasEl;\n        if (canvasEl.height != scrollHeight ||\n            canvasEl.width != scrollWidth) {\n            canvasEl.height = scrollHeight;\n            canvasEl.width = scrollWidth;\n        }\n\n        this.coreTranslation = calcCoreTransform(scrollWidth,\n                                                 scrollHeight,\n                                                 this.imgSize.width,\n                                                 this.imgSize.height);\n        /*console.log(\"got core translation\", this.coreTranslation,\n          \" for \", scrollWidth, scrollHeight, this.canvasEl.width,\n          this.canvasEl.height); */\n    }\n\n    clearCtx() {\n        this.ctx.setTransform(_identity);\n        this.ctx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);\n        this.updateCoreTransform();\n        this.drawBackground();\n        this.updateTransform();\n    }\n\n    drawBackground() {\n        var ctx = this.ctx;\n\n        ctx.fillStyle=\"rgba(255, 255, 255, 1)\";\n\n        if (this.background) {\n            const pattern = ctx.createPattern(this.background, \"repeat\");\n            ctx.fillStyle = pattern;\n        }\n\n        ctx.rect(0, 0, this.canvasEl.width,\n                 this.canvasEl.height);\n\n        ctx.fill();\n    }\n\n    draw() {\n        var self = this;\n        self.clearCtx();\n        if (!this.images) {\n            return;\n        }\n\n        var ctx = self.ctx;\n\n        function doDraw(image) {\n            /* Disabling imageSmoothing is not great, I think. See T1295. */\n            //ctx.imageSmoothingEnabled = false;\n            ctx.imageSmoothingQuality = \"high\";\n            if (image) { // The image might not have been loaded yet.\n                var matrix = ctx.getTransform();\n                ctx.setTransform(_identity);\n                var sStart = new DOMPoint(0, 0);\n                var sEnd = new DOMPoint(image.width, image.height)\n                var dStart = matrix.transformPoint(sStart);\n                var dEnd = matrix.transformPoint(sEnd);\n\n                if (dStart.x < 0) dStart.x = 0;\n                if (dStart.y < 0) dStart.y = 0;\n                if (dEnd.x >= self.canvasEl.width) dEnd.x = self.canvasEl.width;\n                if (dEnd.y >= self.canvasEl.height) dEnd.y = self.canvasEl.height;\n\n                // Now fix sStart and sEnd againt\n                sStart = matrix.inverse().transformPoint(dStart);\n                sEnd = matrix.inverse().transformPoint(dEnd);\n\n                ctx.drawImage(image,\n                              // src:\n                              sStart.x, sStart.y,\n                              sEnd.x - sStart.x,\n                              sEnd.y - sStart.y,\n                              // dest:\n                              dStart.x, dStart.y,\n                              dEnd.x - dStart.x, dEnd.y - dStart.y)\n\n                ctx.setTransform(matrix);\n            }\n        }\n\n\n\n        self.updateTransform();\n        for(let i in self.layers) {\n\n            function getAlpha(layer) {\n                var alpha = layer.alpha;\n                if (alpha === 0) {\n                    return 0;\n                } else if (!alpha) {\n                    return 1;\n                } else if ((typeof alpha) === \"number\") {\n                    return alpha;\n                } else {\n                    return alpha();\n                }\n            }\n\n            ctx.globalAlpha = getAlpha(self.layers[i]);\n            if (ctx.globalAlpha > 0) {\n                doDraw(self.images[i]);\n            }\n        }\n\n        self.drawMasks();\n    }\n\n    scheduleDraw () {\n        var self = this;\n        if (!self.drawTimeout) {\n            self.drawTimeout = setTimeout(function () {\n                self.drawTimeout = null;\n                self.draw();\n            }, 16);\n        }\n    }\n\n    onImagesLoaded() {\n        var self = this;\n        self.callCallback(self.callbacks.onImagesLoaded);\n\n        // The last image determines the canvas size.\n        var image = self.images[self.images.length - 1];\n\n        self.imgSize = {\n            height: image.height,\n            width: image.width,\n        }\n        self.scheduleDraw();\n    }\n\n\n    getEventPositionOnCanvas(e) {\n        var self = this;\n        var rect = self.canvasEl.getBoundingClientRect();\n        // Since we're using `cover` as object-fit, the scale\n        // will be the higher of these two\n        var scale = Math.min(self.canvasEl.width / rect.width, self.canvasEl.height / rect.height);\n\n        var thisX = (e.clientX - rect.left) * scale;\n        var thisY = (e.clientY - rect.top) * scale;\n\n        return new DOMPoint(thisX, thisY);\n    }\n\n    setupDragging() {\n        var self = this;\n        // Mapping from pointerId to { x: 0, y: 0, translateX: 0, translateY: 0 };\n        var dragStart = {};\n\n        function onMouseDown(e) {\n            if (e.which != 1) {\n                return;\n            }\n\n            var pointerId = e.pointerId;\n            dragStart[pointerId] = self.getEventPositionOnCanvas(e);\n            var ds = dragStart[pointerId];\n            ds.translateX = self.transform.e;\n            ds.translateY = self.transform.f;\n            ds.startTime = Date.now();\n\n            e.preventDefault();\n        }\n\n        function onMouseMove(e) {\n            var pos = self.getEventPositionOnCanvas(e);\n            var ds = dragStart[e.pointerId]\n            if (ds && ds.startTime < Date.now() - 100) {\n                self.transform.e = pos.x - ds.x + ds.translateX;\n                self.transform.f = pos.y - ds.y + ds.translateY;\n                self.scheduleDraw();\n            }\n\n            if (ds) {\n                e.preventDefault();\n            }\n        }\n\n        function onMouseEnd(e) {\n            if (dragStart[e.pointerId]) {\n                delete dragStart[e.pointerId];\n                e.preventDefault();\n            }\n        }\n\n        document.addEventListener(\"pointermove\", onMouseMove);\n        document.addEventListener(\"pointerup\", onMouseEnd);\n        document.addEventListener(\"pointercancel\", onMouseEnd);\n        $(self.canvasEl).on(\"pointerdown\", onMouseDown);\n\n        $(self.canvasEl).on(\"remove\", function () {\n            console.log(\"removing listeners\");\n            document.removeEventListener(\"pointermove\", onMouseMove);\n            document.removeEventListener(\"pointerup\", onMouseEnd);\n            document.removeEventListener(\"pointercancel\", onMouseEnd);\n        });\n\n    }\n\n\n    setupZoomWheel() {\n        var self = this;\n        function onZoomWheel(e) {\n            var change = e.originalEvent.deltaY * 0.0005;\n            var zoom0 = self.getZoom();\n\n            self.setZoom(self.getZoom() - change);\n\n            if (self.getZoom() > 5) {\n                self.setZoom(5);\n            }\n\n            if (self.getZoom() < 0.1) {\n                self.setZoom(0.1);\n            }\n            //console.log(\"new zoom is\", zoom);\n\n            // But I want the mouse to be on the same location\n            // that we started with. For this we need to move translate.x\n\n\n\n            var canvasPos = self.getEventPositionOnCanvas(e);\n\n            var zoom = self.getZoom();\n            var translate = {\n                x: canvasPos.x - (zoom/zoom0) * (canvasPos.x - self.transform.e),\n                y: canvasPos.y - (zoom/zoom0) * (canvasPos.y - self.transform.f)\n            }\n\n            self.setTranslate(translate.x, translate.y);\n\n            self.scheduleDraw();\n            e.preventDefault();\n\n        }\n\n        $(self.canvasEl).on(\"wheel\", function (e) {\n            onZoomWheel(e);\n        });\n    }\n\n    load() {\n        var self = this;\n\n        console.log(\"loadIntoCanvas\", self.canvasContainer, this.layers);\n\n        updateResizeObserver(self.canvasContainer, new ResizeObserver((entries) => {\n            self.scheduleDraw();\n        }));\n\n        var $canvas = $(\"<canvas draggable='false' class='load-into-canvas' style='touch-action:none; '/>\");\n        this.canvasEl = $canvas.get(0);\n\n        $(self.canvasContainer).empty();\n        self.canvasContainer.appendChild(self.canvasEl);\n\n        /* If the window is resized, or image is reloated, this is the, first translation\n           that happens independently of mouse zooms etc. */\n        this.coreTranslation = _identity;\n\n        self.transform = new DOMMatrix([1, 0, 0, 1, 0, 0]);\n\n        self.images = [];\n\n        if ($(this.canvasContainer).data(\"transparency\")) {\n            self.backgroundPromise = fetch(\"/assets/images/img-background.png\")\n                .then((response) => response.blob())\n                .then(blob => createImageBitmap(blob));\n        } else {\n            self.backgroundPromise = Promise.resolve(null);\n        }\n\n        self.image_promises = Promise.all(this.layers.map((layer) => {\n            return fetch(layer.src)\n                .then((response) => response.blob())\n                .then(blob => createImageBitmap(blob));\n        })).then((images) => {\n            this.backgroundPromise.then((background) => {\n                self.background = background;\n                self.images = images;\n                self.onImagesLoaded();\n            });\n        });\n\n\n        var ctx = self.canvasEl.getContext('2d', {\n            alpha: false,\n        });\n        self.ctx = ctx;\n\n        this.setupDragging();\n        this.setupZoomWheel();\n\n        $(self.canvasContainer).on(\"sb:refresh\",\n                                   () => self.scheduleDraw());\n\n        function getEventPositionOnImage(e) {\n            var canvasPos = self.getEventPositionOnCanvas(e);\n            return canvasPos.matrixTransform(ctx.getTransform().inverse());\n        }\n\n\n        $(self.canvasEl).dblclick(function (e) {\n            var imPos = getEventPositionOnImage(e);\n            self.zoomToImagePx(imPos);\n            e.preventDefault();\n        });\n\n        $(self.canvasEl).on(\"zoomToChange\", function (e) {\n            console.log(\"zoomToChange\", e);\n            var data = e.originalEvent.detail;\n\n            self.zoomToImagePx(data);\n        });\n    }\n}\n\n// convenience for debugging()\nfunction getSbCanvas() {\n    return $(\"canvas\").parent().data(\"image-canvas\");\n}\n"
  },
  {
    "path": "src/screenshotbot/js/jquery-js.asd",
    "content": "(defpackage :screenshotbot-system.js-assets\n  (:use :cl :asdf))\n(in-package :screenshotbot-system.js-assets)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n   (unless (find-package 'build-utils)\n     (asdf:operate 'asdf:load-op 'build-utils)\n     (use-package :build-utils)))\n\n(defsystem :jquery-js\n  :class build-utils:js-library\n  :defsystem-depends-on (:build-utils)\n  :components ((:module \"vendor\"\n                :components ((build-utils:js-file \"jquery-3.5.1\")))))\n"
  },
  {
    "path": "src/screenshotbot/js/jquery.timeago.js",
    "content": "/**\n * Timeago is a jQuery plugin that makes it easy to support automatically\n * updating fuzzy timestamps (e.g. \"4 minutes ago\" or \"about 1 day ago\").\n *\n * @name timeago\n * @version 1.6.7\n * @requires jQuery >=1.5.0 <4.0\n * @author Ryan McGeary\n * @license MIT License - http://www.opensource.org/licenses/mit-license.php\n *\n * For usage and examples, visit:\n * http://timeago.yarp.com/\n *\n * Copyright (c) 2008-2019, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)\n */\n\n(function (factory) {\n  if (typeof define === 'function' && define.amd) {\n    // AMD. Register as an anonymous module.\n    define(['jquery'], factory);\n  } else if (typeof module === 'object' && typeof module.exports === 'object') {\n    factory(require('jquery'));\n  } else {\n    // Browser globals\n    factory(jQuery);\n  }\n}(function ($) {\n  $.timeago = function(timestamp) {\n    if (timestamp instanceof Date) {\n      return inWords(timestamp);\n    } else if (typeof timestamp === \"string\") {\n      return inWords($.timeago.parse(timestamp));\n    } else if (typeof timestamp === \"number\") {\n      return inWords(new Date(timestamp));\n    } else {\n      return inWords($.timeago.datetime(timestamp));\n    }\n  };\n  var $t = $.timeago;\n\n  $.extend($.timeago, {\n    settings: {\n      refreshMillis: 60000,\n      allowPast: true,\n      allowFuture: false,\n      localeTitle: false,\n      cutoff: 0,\n      autoDispose: true,\n      strings: {\n        prefixAgo: null,\n        prefixFromNow: null,\n        suffixAgo: \"ago\",\n        suffixFromNow: \"from now\",\n        inPast: \"any moment now\",\n        seconds: \"less than a minute\",\n        minute: \"about a minute\",\n        minutes: \"%d minutes\",\n        hour: \"about an hour\",\n        hours: \"about %d hours\",\n        day: \"a day\",\n        days: \"%d days\",\n        month: \"about a month\",\n        months: \"%d months\",\n        year: \"about a year\",\n        years: \"%d years\",\n        wordSeparator: \" \",\n        numbers: []\n      }\n    },\n\n    inWords: function(distanceMillis) {\n      if (!this.settings.allowPast && ! this.settings.allowFuture) {\n          throw 'timeago allowPast and allowFuture settings can not both be set to false.';\n      }\n\n      var $l = this.settings.strings;\n      var prefix = $l.prefixAgo;\n      var suffix = $l.suffixAgo;\n      if (this.settings.allowFuture) {\n        if (distanceMillis < 0) {\n          prefix = $l.prefixFromNow;\n          suffix = $l.suffixFromNow;\n        }\n      }\n\n      if (!this.settings.allowPast && distanceMillis >= 0) {\n        return this.settings.strings.inPast;\n      }\n\n      var seconds = Math.abs(distanceMillis) / 1000;\n      var minutes = seconds / 60;\n      var hours = minutes / 60;\n      var days = hours / 24;\n      var years = days / 365;\n\n      function substitute(stringOrFunction, number) {\n        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;\n        var value = ($l.numbers && $l.numbers[number]) || number;\n        return string.replace(/%d/i, value);\n      }\n\n      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||\n        seconds < 90 && substitute($l.minute, 1) ||\n        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||\n        minutes < 90 && substitute($l.hour, 1) ||\n        hours < 24 && substitute($l.hours, Math.round(hours)) ||\n        hours < 42 && substitute($l.day, 1) ||\n        days < 30 && substitute($l.days, Math.round(days)) ||\n        days < 45 && substitute($l.month, 1) ||\n        days < 365 && substitute($l.months, Math.round(days / 30)) ||\n        years < 1.5 && substitute($l.year, 1) ||\n        substitute($l.years, Math.round(years));\n\n      var separator = $l.wordSeparator || \"\";\n      if ($l.wordSeparator === undefined) { separator = \" \"; }\n      return $.trim([prefix, words, suffix].join(separator));\n    },\n\n    parse: function(iso8601) {\n      var s = $.trim(iso8601);\n      s = s.replace(/\\.\\d+/,\"\"); // remove milliseconds\n      s = s.replace(/-/,\"/\").replace(/-/,\"/\");\n      s = s.replace(/T/,\" \").replace(/Z/,\" UTC\");\n      s = s.replace(/([\\+\\-]\\d\\d)\\:?(\\d\\d)/,\" $1$2\"); // -04:00 -> -0400\n      s = s.replace(/([\\+\\-]\\d\\d)$/,\" $100\"); // +09 -> +0900\n      return new Date(s);\n    },\n    datetime: function(elem) {\n      var iso8601 = $t.isTime(elem) ? $(elem).attr(\"datetime\") : $(elem).attr(\"title\");\n      return $t.parse(iso8601);\n    },\n    isTime: function(elem) {\n      // jQuery's `is()` doesn't play well with HTML5 in IE\n      return $(elem).get(0).tagName.toLowerCase() === \"time\"; // $(elem).is(\"time\");\n    }\n  });\n\n  // functions that can be called via $(el).timeago('action')\n  // init is default when no action is given\n  // functions are called with context of a single element\n  var functions = {\n    init: function() {\n      functions.dispose.call(this);\n      var refresh_el = $.proxy(refresh, this);\n      refresh_el();\n      var $s = $t.settings;\n      if ($s.refreshMillis > 0) {\n        this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis);\n      }\n    },\n    update: function(timestamp) {\n      var date = (timestamp instanceof Date) ? timestamp : $t.parse(timestamp);\n      $(this).data('timeago', { datetime: date });\n      if ($t.settings.localeTitle) {\n        $(this).attr(\"title\", date.toLocaleString());\n      }\n      refresh.apply(this);\n    },\n    updateFromDOM: function() {\n      $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr(\"datetime\") : $(this).attr(\"title\") ) });\n      refresh.apply(this);\n    },\n    dispose: function () {\n      if (this._timeagoInterval) {\n        window.clearInterval(this._timeagoInterval);\n        this._timeagoInterval = null;\n      }\n    }\n  };\n\n  $.fn.timeago = function(action, options) {\n    var fn = action ? functions[action] : functions.init;\n    if (!fn) {\n      throw new Error(\"Unknown function name '\"+ action +\"' for timeago\");\n    }\n    // each over objects here and call the requested function\n    this.each(function() {\n      fn.call(this, options);\n    });\n    return this;\n  };\n\n  function refresh() {\n    var $s = $t.settings;\n\n    //check if it's still visible\n    if ($s.autoDispose && !$.contains(document.documentElement,this)) {\n      //stop if it has been removed\n      $(this).timeago(\"dispose\");\n      return this;\n    }\n\n    var data = prepareData(this);\n\n    if (!isNaN(data.datetime)) {\n      if ( $s.cutoff === 0 || Math.abs(distance(data.datetime)) < $s.cutoff) {\n        $(this).text(inWords(data.datetime));\n      } else {\n        if ($(this).attr('title').length > 0) {\n            $(this).text($(this).attr('title'));\n        }\n      }\n    }\n    return this;\n  }\n\n  function prepareData(element) {\n    element = $(element);\n    if (!element.data(\"timeago\")) {\n      element.data(\"timeago\", { datetime: $t.datetime(element) });\n      var text = $.trim(element.text());\n      if ($t.settings.localeTitle) {\n        element.attr(\"title\", element.data('timeago').datetime.toLocaleString());\n      } else if (text.length > 0 && !($t.isTime(element) && element.attr(\"title\"))) {\n        element.attr(\"title\", text);\n      }\n    }\n    return element.data(\"timeago\");\n  }\n\n  function inWords(date) {\n    return $t.inWords(distance(date));\n  }\n\n  function distance(date) {\n    return (new Date().getTime() - date.getTime());\n  }\n\n  // fix for IE6 suckage\n  document.createElement(\"abbr\");\n  document.createElement(\"time\");\n}));\n"
  },
  {
    "path": "src/screenshotbot/js/js-stubs.js",
    "content": "function makeMatrix(a, b, c, d, e, f) {\n    return new DOMMatrix([a, b, c, d, e, f]);\n}\n\nfunction _mat_mstar(a, b) {\n    if (a instanceof DOMMatrixReadOnly && b instanceof DOMPointReadOnly) {\n        return b.matrixTransform(a);\n    } else if (b instanceof DOMMatrixReadOnly) {\n        // Scalar times matrix. Tell me a better way to do this, please.\n        return new DOMMatrixReadOnly([\n            a * b.a,\n            a * b.b,\n            a * b.c,\n            a * b.d,\n            a * b.e,\n            a * b.f]);\n    } else {\n        throw new Error(\"unimpl for \" + a + \" \" + b);\n    }\n}\n\nfunction _vec_vec3(a, b) {\n    return new DOMPoint(a, b);\n}\n\nfunction _vec_v(a, b) { // this is actually `v-`.\n    return new DOMPoint(a.x - b.x,\n                        a.y - b.y);\n}\n\nfunction _vec_vstar(mult, vec) {\n    return new DOMPoint(mult * vec.x,\n                        mult * vec.y);\n}\n\nfunction _vec_vx3(vec) {\n    return vec.x;\n}\n\nfunction _vec_vy3(vec) {\n    return vec.y;\n}\n\nfunction _mat_mplus(v1, v2) {\n    if (v1 instanceof DOMPointReadOnly && v2 instanceof DOMPointReadOnly) {\n        return new DOMPoint(v1.x + v2.x,\n                            v1.y + v2.y);\n    } else if (v1 instanceof DOMMatrixReadOnly && v2 instanceof DOMMatrixReadOnly) {\n        return new DOMMatrix([\n            v1.a + v2.a,\n            v1.b + v2.b,\n            v1.c + v2.c,\n            v1.d + v2.d,\n            v1.e + v2.e,\n            v1.f + v2.f]);\n    } else {\n        throw new Error(\"mplus unimplemented for \" +  v1 + \" \" + v2);\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/js/mask-editor.js",
    "content": "function getOriginalPoint(canvas, point) {\n    var matrix = canvas.viewportTransform;\n    var inverse = fabric.util.invertTransform(matrix);\n    return fabric.util.transformPoint(point, inverse);\n}\n\nfunction prepareMaskEditor() {\n    var ctr = 0;\n    function makeIm(im) {\n        var callback = function () {\n            ctr ++;\n\n            if (ctr == 2) {\n                console.log(\"everything is ready\", img, overlay);\n                prepareMaskEditorHelper(img, overlay);\n            }\n        }\n\n        if (!im || im.complete) {\n            console.log(\"image was already available\");\n            setTimeout(callback, 0);\n        } else {\n            console.log(\"waiting in the background\");\n            im.onload = callback;\n        }\n        return im;\n    }\n\n    var img = makeIm(document.getElementById(\"mask-editor-image\"));\n    var overlay = makeIm(document.getElementById(\"mask-editor-overlay\"));\n}\n\n\nfunction prepareMaskEditorHelper(img, overlay) {\n    // Take from: http://fabricjs.com/custom-control-render\n    var deleteIcon = \"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E\";\n\n    var delImg = document.createElement('img');\n    delImg.src = deleteIcon;\n\n\n    fabric.Object.prototype.controls.deleteControl = new fabric.Control({\n        x: 0.5,\n        y: -0.5,\n        offsetY: 16,\n        cursorStyle: 'pointer',\n        mouseUpHandler: deleteObject,\n        render: renderIcon,\n        cornerSize: 24\n    });\n\n\n    function deleteObject(eventData, transform) {\n\t\tvar target = transform.target;\n\t\tvar canvas = target.canvas;\n\t\tcanvas.remove(target);\n        canvas.requestRenderAll();\n\t}\n\n    function renderIcon(ctx, left, top, styleOverride, fabricObject) {\n        var size = this.cornerSize;\n        ctx.save();\n        ctx.translate(left, top);\n        ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));\n        ctx.drawImage(delImg, -size/2, -size/2, size, size);\n        ctx.restore();\n    }\n\n    console.log(\"Setting up mask editor\");\n    var canvas = new fabric.Canvas(\"mask-editor\");\n    var imgInstance = new fabric.Image(img, {\n        left: 0,\n        top: 0,\n        opacity: 0.7,\n        selectable: false,\n    });\n\n\n    canvas.add(imgInstance);\n\n    if (overlay) {\n        var overlayInstance = new fabric.Image(overlay, {\n            left: 0,\n            top: 0,\n            selectable: false,\n        });\n        canvas.add(overlayInstance);\n    }\n\n\n    canvas.on('selection:created', function (options) {\n        console.log(\"options: \", options);\n    });\n\n    var startX, startY;\n    var isDragging = false;\n\n    canvas.on('mouse:down', function (opt) {\n        if (opt.e.altKey !== true) {\n            console.log(opt);\n            var point = getOriginalPoint(canvas, opt.pointer);\n            startX = point.x;\n            startY = point.y;\n            isDragging = true;\n        }\n    });\n\n    function addRect(props) {\n        var rect = new fabric.Rect(Object.assign(\n            {},\n            props,\n            {\n                lockRotation: true,\n                centeredRotation: true,\n                fill: \"#ffff00aa\",\n            }\n        ));\n\n        rect.on('mousedown', function() {\n            isDragging = false;\n        });\n\n        rect.setControlVisible('mtr', false);\n\n        rect.on('object:moving', function () {\n            isDragging = false;\n        });\n\n        canvas.add(rect);\n        return rect;\n    }\n\n    var rectsData = $(\"#mask-editor\").data(\"rects\");\n    console.log(\"Got rects data: \", rectsData);\n    rectsData.forEach(function (x) {\n        addRect(x);\n    });\n\n    canvas.on('mouse:up', function (e) {\n        if (!isDragging)  {\n            return;\n        }\n\n        isDragging = false;\n        console.log(e);\n        var point = getOriginalPoint(canvas, e.pointer);\n        var x = point.x, y = point.y;\n        console.log(\"will draw on: \", startX, startY, x, y);\n\n        var rect = addRect({\n            left: Math.min(startX, x),\n            top: Math.min(startY, y),\n            height: Math.abs(y - startY),\n            width: Math.abs(x - startX),\n        });\n\n        canvas.setActiveObject(rect);\n        console.log(\"added rectangle\");\n\n    });\n\n    var getRects = function () {\n        var ret = [];\n        getRectObjects().forEach(function (x) {\n            ret.push({\n                left: x.left,\n                top: x.top,\n                width: x.width * x.scaleX,\n                height: x.height * x.scaleY,\n            });\n        });\n        return ret;\n    };\n\n    var getRectObjects = function() {\n        var ret = [];\n        canvas.getObjects().forEach(function (x) {\n            console.log(\"saw object\", x);\n            if (x.isType(\"rect\")) {\n                ret.push(x);\n            }\n        });\n        return ret;\n        \n    };\n\n    $(\"#clear-masks\").click(function () {\n        getRectObjects().forEach(function (x) {\n            console.log(\"removing \", x);\n            canvas.remove(x);\n        });\n        console.log(\"clearing masks\");\n        console.log(\"and we're done\");\n        canvas.requestRenderAll();\n    });\n\n    $(\"#mask-editor-form\").submit(function () {\n        console.log(\"Submitting form\");\n        $(\"input[name='json']\").val(JSON.stringify(getRects()));\n        console.log(\"here we go...\");\n        return true;\n    });\n\n    enableZoomOnCanvas(canvas);\n    enablePanningOnCanvas(canvas);\n}\n\n\n\n$(function () {\n    if ($(\"#mask-editor\").length > 0) {\n        prepareMaskEditor();\n    }\n});\n\nfunction enableZoomOnCanvas(canvas) {\n    canvas.on('mouse:wheel', function(opt) {\n        console.log(\"on wheel\", opt);\n        var e = opt.e;\n        var delta = e.deltaY * 0.001;\n        var vpt = this.viewportTransform;\n        var zoom0 = vpt[0];\n        var zoom = zoom0 - delta;\n\n        if (zoom > 20) zoom = 20;\n        if (zoom < 0.1) zoom = 0.1;\n\n\n        // The original code used offset{X|Y}\n        var canvasPos = getOriginalPoint(canvas, opt.pointer);\n\n\n\n        console.log(\"old values\", vpt, canvasPos, zoom, zoom0);\n\n        // We come to do this solution with some simple Matrix algebra\n        vpt[4] = (zoom0 - zoom) * canvasPos.x + vpt[4];\n        vpt[5] = (zoom0 - zoom) * canvasPos.y + vpt[5];\n        vpt[0] = zoom;\n        vpt[3] = zoom;\n        console.log(\"before zoom\", vpt);\n        //canvas.setZoom(zoom);\n        //console.log(\"after zoom\", vpt);\n\n        this.requestRenderAll();\n        e.preventDefault();\n\n\n    });\n}\n\n\nfunction enablePanningOnCanvas(canvas) {\n    canvas.on('mouse:down', function(opt) {\n        var evt = opt.e;\n        if (evt.altKey === true) {\n            this.isDragging = true;\n            this.selection = false;\n            this.lastPosX = evt.clientX;\n            this.lastPosY = evt.clientY;\n        }\n    });\n    canvas.on('mouse:move', function(opt) {\n        if (this.isDragging) {\n            var e = opt.e;\n            var vpt = this.viewportTransform;\n            vpt[4] += e.clientX - this.lastPosX;\n            vpt[5] += e.clientY - this.lastPosY;\n            this.requestRenderAll();\n            this.lastPosX = e.clientX;\n            this.lastPosY = e.clientY;\n        }\n    });\n    canvas.on('mouse:up', function(opt) {\n        // on mouse up we want to recalculate new interaction\n        // for all objects, so we call setViewportTransform\n        this.setViewportTransform(this.viewportTransform);\n        this.isDragging = false;\n        this.selection = true;\n    });\n\n}\n"
  },
  {
    "path": "src/screenshotbot/js/runs.js",
    "content": "\n\nfunction updateAjaxResults($target) {\n    $.ajax({\n        url: $target.data(\"update\"),\n        data: $target.data(\"args\"),\n        success: function (result) {\n            $target.html(result);\n            callLiveOnAttach($target);\n        },\n        error: function () {\n            alert(\"Something went wrong\");\n        },\n    });\n}\n\nsetupLiveOnAttach(\".search\", function () {\n    $(this).on(\"input\", function () {\n        var val = $(this).val();\n        var targetName = $(this).data(\"target\");\n        console.log(\"Got targetName\", targetName);\n        var $target = $(targetName);\n\n        console.log(\"Got target: \", $target);\n        var params = $target.data(\"args\");\n\n        params[\"search\"] = val;\n        var timeout = $target.data(\"timeout\");\n        if (timeout) {\n            clearTimeout(timeout);\n        }\n\n        if (!$target.data(\"original\")) {\n            $target.data(\"original\", $target.children());\n        }\n\n        $target.data(\"timeout\", setTimeout(function () {\n            if (val == \"\" && $target.data(\"save-original\")) {\n                $target.html($target.data(\"original\"));\n            } else {\n                updateAjaxResults($target);\n            }\n        }, 250));\n    });\n});\n\n\nsetupLiveOnAttach(\"ul.report-selector > li > a\", function () {\n    var currentState = history.state;\n    var type = $(this).data(\"type\");\n\n    function switchTab() {\n        var $ul = $(this).closest(\"ul\");\n        $ul.find(\"a\").removeClass(\"active\");\n        $(this).addClass(\"active\");\n        var $target = $($ul.data(\"target\"));\n        var args = $target.data(\"args\");\n\n        args[\"type\"] = type;\n        setUrlParameter(\"type\", type);\n\n        updateAjaxResults($target);\n    }\n\n    $(this).click(function (e) {\n        switchTab.call(this);\n\n        e.preventDefault();\n    });\n\n});\n"
  },
  {
    "path": "src/screenshotbot/js/screenshotbot.js-assets.asd",
    "content": "(defpackage :screenshotbot-system.js-assets\n  (:use :cl :asdf))\n(in-package :screenshotbot-system.js-assets)\n\n(defsystem :screenshotbot.js-assets/split\n  :class \"build-utils:js-library\"\n  :defsystem-depends-on (:build-utils)\n  :components ((\"build-utils:js-file\" \"split\")))\n\n(defsystem screenshotbot.js-assets/headroom\n  :class \"build-utils:js-library\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on ()\n  :components ((:module \"vendor\"\n                :components ((\"build-utils:js-file\" \"headroom\")))))\n\n(defsystem screenshotbot.js-assets/common\n  :class \"build-utils:js-library\"\n  :defsystem-depends-on (:build-utils)\n  :depends-on ()\n  :components ((\"build-utils:js-file\" \"common\")))\n\n(defsystem screenshotbot.js-assets\n  :class \"build-utils:js-system\"\n  :serial t\n  :defsystem-depends-on (:build-utils)\n  :depends-on (:pixel-diff.math/package\n               #-screenshotbot-oss\n               :sentry-js\n               :bootstrap5-js\n               :screenshotbot.js-assets/headroom\n               :screenshotbot.js-assets/split\n               :pixel-diff.math-js)\n  :components ((:module \"vendor\"\n                :components ((\"build-utils:js-file\" \"jquery-ui\")\n                             (\"build-utils:js-file\" \"baguetteBox\")\n                             (\"build-utils:js-file\" \"metisMenu\")\n                             (\"build-utils:js-file\" \"select2\")\n                             (\"build-utils:js-file\" \"moment\")))\n               (\"build-utils:js-file\" \"jquery.timeago\")\n               (\"build-utils:js-file\" \"default\")\n               (\"build-utils:js-file\" \"js-stubs\")\n               (\"build-utils:js-file\" \"common\")\n               (\"build-utils:js-file\" \"image-canvas\")\n               (\"build-utils:js-file\" \"runs\")\n               (\"build-utils:js-file\" \"mask-editor\")\n               (\"build-utils:js-file\" \"compare-branches\")\n               (\"build-utils:js-file\" \"websocket-logs\")\n               (\"build-utils:js-file\" \"acceptance\")\n               (\"build-utils:js-file\" \"git-graph\")\n               (\"build-utils:ps-file\" \"dummy\")))\n\n\n"
  },
  {
    "path": "src/screenshotbot/js/split.js",
    "content": "/*! Split.js - v1.6.2 */\n\n(function (global, factory) {\n    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n    typeof define === 'function' && define.amd ? define(factory) :\n    (global = global || self, global.Split = factory());\n}(this, (function () { 'use strict';\n\n    // The programming goals of Split.js are to deliver readable, understandable and\n    // maintainable code, while at the same time manually optimizing for tiny minified file size,\n    // browser compatibility without additional requirements\n    // and very few assumptions about the user's page layout.\n    var global = typeof window !== 'undefined' ? window : null;\n    var ssr = global === null;\n    var document = !ssr ? global.document : undefined;\n\n    // Save a couple long function names that are used frequently.\n    // This optimization saves around 400 bytes.\n    var addEventListener = 'addEventListener';\n    var removeEventListener = 'removeEventListener';\n    var getBoundingClientRect = 'getBoundingClientRect';\n    var gutterStartDragging = '_a';\n    var aGutterSize = '_b';\n    var bGutterSize = '_c';\n    var HORIZONTAL = 'horizontal';\n    var NOOP = function () { return false; };\n\n    // Helper function determines which prefixes of CSS calc we need.\n    // We only need to do this once on startup, when this anonymous function is called.\n    //\n    // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow:\n    // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167\n    var calc = ssr\n        ? 'calc'\n        : ((['', '-webkit-', '-moz-', '-o-']\n              .filter(function (prefix) {\n                  var el = document.createElement('div');\n                  el.style.cssText = \"width:\" + prefix + \"calc(9px)\";\n\n                  return !!el.style.length\n              })\n              .shift()) + \"calc\");\n\n    // Helper function checks if its argument is a string-like type\n    var isString = function (v) { return typeof v === 'string' || v instanceof String; };\n\n    // Helper function allows elements and string selectors to be used\n    // interchangeably. In either case an element is returned. This allows us to\n    // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`.\n    var elementOrSelector = function (el) {\n        if (isString(el)) {\n            var ele = document.querySelector(el);\n            if (!ele) {\n                throw new Error((\"Selector \" + el + \" did not match a DOM element\"))\n            }\n            return ele\n        }\n\n        return el\n    };\n\n    // Helper function gets a property from the properties object, with a default fallback\n    var getOption = function (options, propName, def) {\n        var value = options[propName];\n        if (value !== undefined) {\n            return value\n        }\n        return def\n    };\n\n    var getGutterSize = function (gutterSize, isFirst, isLast, gutterAlign) {\n        if (isFirst) {\n            if (gutterAlign === 'end') {\n                return 0\n            }\n            if (gutterAlign === 'center') {\n                return gutterSize / 2\n            }\n        } else if (isLast) {\n            if (gutterAlign === 'start') {\n                return 0\n            }\n            if (gutterAlign === 'center') {\n                return gutterSize / 2\n            }\n        }\n\n        return gutterSize\n    };\n\n    // Default options\n    var defaultGutterFn = function (i, gutterDirection) {\n        var gut = document.createElement('div');\n        gut.className = \"gutter gutter-\" + gutterDirection;\n        return gut\n    };\n\n    var defaultElementStyleFn = function (dim, size, gutSize) {\n        var style = {};\n\n        if (!isString(size)) {\n            style[dim] = calc + \"(\" + size + \"% - \" + gutSize + \"px)\";\n        } else {\n            style[dim] = size;\n        }\n\n        return style\n    };\n\n    var defaultGutterStyleFn = function (dim, gutSize) {\n        var obj;\n\n        return (( obj = {}, obj[dim] = (gutSize + \"px\"), obj ));\n    };\n\n    // The main function to initialize a split. Split.js thinks about each pair\n    // of elements as an independant pair. Dragging the gutter between two elements\n    // only changes the dimensions of elements in that pair. This is key to understanding\n    // how the following functions operate, since each function is bound to a pair.\n    //\n    // A pair object is shaped like this:\n    //\n    // {\n    //     a: DOM element,\n    //     b: DOM element,\n    //     aMin: Number,\n    //     bMin: Number,\n    //     dragging: Boolean,\n    //     parent: DOM element,\n    //     direction: 'horizontal' | 'vertical'\n    // }\n    //\n    // The basic sequence:\n    //\n    // 1. Set defaults to something sane. `options` doesn't have to be passed at all.\n    // 2. Initialize a bunch of strings based on the direction we're splitting.\n    //    A lot of the behavior in the rest of the library is paramatized down to\n    //    rely on CSS strings and classes.\n    // 3. Define the dragging helper functions, and a few helpers to go with them.\n    // 4. Loop through the elements while pairing them off. Every pair gets an\n    //    `pair` object and a gutter.\n    // 5. Actually size the pair elements, insert gutters and attach event listeners.\n    var Split = function (idsOption, options) {\n        if ( options === void 0 ) options = {};\n\n        if (ssr) { return {} }\n\n        var ids = idsOption;\n        var dimension;\n        var clientAxis;\n        var position;\n        var positionEnd;\n        var clientSize;\n        var elements;\n\n        // Allow HTMLCollection to be used as an argument when supported\n        if (Array.from) {\n            ids = Array.from(ids);\n        }\n\n        // All DOM elements in the split should have a common parent. We can grab\n        // the first elements parent and hope users read the docs because the\n        // behavior will be whacky otherwise.\n        var firstElement = elementOrSelector(ids[0]);\n        var parent = firstElement.parentNode;\n        var parentStyle = getComputedStyle ? getComputedStyle(parent) : null;\n        var parentFlexDirection = parentStyle ? parentStyle.flexDirection : null;\n\n        // Set default options.sizes to equal percentages of the parent element.\n        var sizes = getOption(options, 'sizes') || ids.map(function () { return 100 / ids.length; });\n\n        // Standardize minSize to an array if it isn't already. This allows minSize\n        // to be passed as a number.\n        var minSize = getOption(options, 'minSize', 100);\n        var minSizes = Array.isArray(minSize) ? minSize : ids.map(function () { return minSize; });\n\n        // Get other options\n        var expandToMin = getOption(options, 'expandToMin', false);\n        var gutterSize = getOption(options, 'gutterSize', 10);\n        var gutterAlign = getOption(options, 'gutterAlign', 'center');\n        var snapOffset = getOption(options, 'snapOffset', 30);\n        var dragInterval = getOption(options, 'dragInterval', 1);\n        var direction = getOption(options, 'direction', HORIZONTAL);\n        var cursor = getOption(\n            options,\n            'cursor',\n            direction === HORIZONTAL ? 'col-resize' : 'row-resize'\n        );\n        var gutter = getOption(options, 'gutter', defaultGutterFn);\n        var elementStyle = getOption(\n            options,\n            'elementStyle',\n            defaultElementStyleFn\n        );\n        var gutterStyle = getOption(options, 'gutterStyle', defaultGutterStyleFn);\n\n        // 2. Initialize a bunch of strings based on the direction we're splitting.\n        // A lot of the behavior in the rest of the library is paramatized down to\n        // rely on CSS strings and classes.\n        if (direction === HORIZONTAL) {\n            dimension = 'width';\n            clientAxis = 'clientX';\n            position = 'left';\n            positionEnd = 'right';\n            clientSize = 'clientWidth';\n        } else if (direction === 'vertical') {\n            dimension = 'height';\n            clientAxis = 'clientY';\n            position = 'top';\n            positionEnd = 'bottom';\n            clientSize = 'clientHeight';\n        }\n\n        // 3. Define the dragging helper functions, and a few helpers to go with them.\n        // Each helper is bound to a pair object that contains its metadata. This\n        // also makes it easy to store references to listeners that that will be\n        // added and removed.\n        //\n        // Even though there are no other functions contained in them, aliasing\n        // this to self saves 50 bytes or so since it's used so frequently.\n        //\n        // The pair object saves metadata like dragging state, position and\n        // event listener references.\n\n        function setElementSize(el, size, gutSize, i) {\n            // Split.js allows setting sizes via numbers (ideally), or if you must,\n            // by string, like '300px'. This is less than ideal, because it breaks\n            // the fluid layout that `calc(% - px)` provides. You're on your own if you do that,\n            // make sure you calculate the gutter size by hand.\n            var style = elementStyle(dimension, size, gutSize, i);\n\n            Object.keys(style).forEach(function (prop) {\n                // eslint-disable-next-line no-param-reassign\n                el.style[prop] = style[prop];\n            });\n        }\n\n        function setGutterSize(gutterElement, gutSize, i) {\n            var style = gutterStyle(dimension, gutSize, i);\n\n            Object.keys(style).forEach(function (prop) {\n                // eslint-disable-next-line no-param-reassign\n                gutterElement.style[prop] = style[prop];\n            });\n        }\n\n        function getSizes() {\n            return elements.map(function (element) { return element.size; })\n        }\n\n        // Supports touch events, but not multitouch, so only the first\n        // finger `touches[0]` is counted.\n        function getMousePosition(e) {\n            if ('touches' in e) { return e.touches[0][clientAxis] }\n            return e[clientAxis]\n        }\n\n        // Actually adjust the size of elements `a` and `b` to `offset` while dragging.\n        // calc is used to allow calc(percentage + gutterpx) on the whole split instance,\n        // which allows the viewport to be resized without additional logic.\n        // Element a's size is the same as offset. b's size is total size - a size.\n        // Both sizes are calculated from the initial parent percentage,\n        // then the gutter size is subtracted.\n        function adjust(offset) {\n            var a = elements[this.a];\n            var b = elements[this.b];\n            var percentage = a.size + b.size;\n\n            a.size = (offset / this.size) * percentage;\n            b.size = percentage - (offset / this.size) * percentage;\n\n            setElementSize(a.element, a.size, this[aGutterSize], a.i);\n            setElementSize(b.element, b.size, this[bGutterSize], b.i);\n        }\n\n        // drag, where all the magic happens. The logic is really quite simple:\n        //\n        // 1. Ignore if the pair is not dragging.\n        // 2. Get the offset of the event.\n        // 3. Snap offset to min if within snappable range (within min + snapOffset).\n        // 4. Actually adjust each element in the pair to offset.\n        //\n        // ---------------------------------------------------------------------\n        // |    | <- a.minSize               ||              b.minSize -> |    |\n        // |    |  | <- this.snapOffset      ||     this.snapOffset -> |  |    |\n        // |    |  |                         ||                        |  |    |\n        // |    |  |                         ||                        |  |    |\n        // ---------------------------------------------------------------------\n        // | <- this.start                                        this.size -> |\n        function drag(e) {\n            var offset;\n            var a = elements[this.a];\n            var b = elements[this.b];\n\n            if (!this.dragging) { return }\n\n            // Get the offset of the event from the first side of the\n            // pair `this.start`. Then offset by the initial position of the\n            // mouse compared to the gutter size.\n            offset =\n                getMousePosition(e) -\n                this.start +\n                (this[aGutterSize] - this.dragOffset);\n\n            if (dragInterval > 1) {\n                offset = Math.round(offset / dragInterval) * dragInterval;\n            }\n\n            // If within snapOffset of min or max, set offset to min or max.\n            // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both.\n            // Include the appropriate gutter sizes to prevent overflows.\n            if (offset <= a.minSize + snapOffset + this[aGutterSize]) {\n                offset = a.minSize + this[aGutterSize];\n            } else if (\n                offset >=\n                this.size - (b.minSize + snapOffset + this[bGutterSize])\n            ) {\n                offset = this.size - (b.minSize + this[bGutterSize]);\n            }\n\n            // Actually adjust the size.\n            adjust.call(this, offset);\n\n            // Call the drag callback continously. Don't do anything too intensive\n            // in this callback.\n            getOption(options, 'onDrag', NOOP)(getSizes());\n        }\n\n        // Cache some important sizes when drag starts, so we don't have to do that\n        // continously:\n        //\n        // `size`: The total size of the pair. First + second + first gutter + second gutter.\n        // `start`: The leading side of the first element.\n        //\n        // ------------------------------------------------\n        // |      aGutterSize -> |||                      |\n        // |                     |||                      |\n        // |                     |||                      |\n        // |                     ||| <- bGutterSize       |\n        // ------------------------------------------------\n        // | <- start                             size -> |\n        function calculateSizes() {\n            // Figure out the parent size minus padding.\n            var a = elements[this.a].element;\n            var b = elements[this.b].element;\n\n            var aBounds = a[getBoundingClientRect]();\n            var bBounds = b[getBoundingClientRect]();\n\n            this.size =\n                aBounds[dimension] +\n                bBounds[dimension] +\n                this[aGutterSize] +\n                this[bGutterSize];\n            this.start = aBounds[position];\n            this.end = aBounds[positionEnd];\n        }\n\n        function innerSize(element) {\n            // Return nothing if getComputedStyle is not supported (< IE9)\n            // Or if parent element has no layout yet\n            if (!getComputedStyle) { return null }\n\n            var computedStyle = getComputedStyle(element);\n\n            if (!computedStyle) { return null }\n\n            var size = element[clientSize];\n\n            if (size === 0) { return null }\n\n            if (direction === HORIZONTAL) {\n                size -=\n                    parseFloat(computedStyle.paddingLeft) +\n                    parseFloat(computedStyle.paddingRight);\n            } else {\n                size -=\n                    parseFloat(computedStyle.paddingTop) +\n                    parseFloat(computedStyle.paddingBottom);\n            }\n\n            return size\n        }\n\n        // When specifying percentage sizes that are less than the computed\n        // size of the element minus the gutter, the lesser percentages must be increased\n        // (and decreased from the other elements) to make space for the pixels\n        // subtracted by the gutters.\n        function trimToMin(sizesToTrim) {\n            // Try to get inner size of parent element.\n            // If it's no supported, return original sizes.\n            var parentSize = innerSize(parent);\n            if (parentSize === null) {\n                return sizesToTrim\n            }\n\n            if (minSizes.reduce(function (a, b) { return a + b; }, 0) > parentSize) {\n                return sizesToTrim\n            }\n\n            // Keep track of the excess pixels, the amount of pixels over the desired percentage\n            // Also keep track of the elements with pixels to spare, to decrease after if needed\n            var excessPixels = 0;\n            var toSpare = [];\n\n            var pixelSizes = sizesToTrim.map(function (size, i) {\n                // Convert requested percentages to pixel sizes\n                var pixelSize = (parentSize * size) / 100;\n                var elementGutterSize = getGutterSize(\n                    gutterSize,\n                    i === 0,\n                    i === sizesToTrim.length - 1,\n                    gutterAlign\n                );\n                var elementMinSize = minSizes[i] + elementGutterSize;\n\n                // If element is too smal, increase excess pixels by the difference\n                // and mark that it has no pixels to spare\n                if (pixelSize < elementMinSize) {\n                    excessPixels += elementMinSize - pixelSize;\n                    toSpare.push(0);\n                    return elementMinSize\n                }\n\n                // Otherwise, mark the pixels it has to spare and return it's original size\n                toSpare.push(pixelSize - elementMinSize);\n                return pixelSize\n            });\n\n            // If nothing was adjusted, return the original sizes\n            if (excessPixels === 0) {\n                return sizesToTrim\n            }\n\n            return pixelSizes.map(function (pixelSize, i) {\n                var newPixelSize = pixelSize;\n\n                // While there's still pixels to take, and there's enough pixels to spare,\n                // take as many as possible up to the total excess pixels\n                if (excessPixels > 0 && toSpare[i] - excessPixels > 0) {\n                    var takenPixels = Math.min(\n                        excessPixels,\n                        toSpare[i] - excessPixels\n                    );\n\n                    // Subtract the amount taken for the next iteration\n                    excessPixels -= takenPixels;\n                    newPixelSize = pixelSize - takenPixels;\n                }\n\n                // Return the pixel size adjusted as a percentage\n                return (newPixelSize / parentSize) * 100\n            })\n        }\n\n        // stopDragging is very similar to startDragging in reverse.\n        function stopDragging() {\n            var self = this;\n            var a = elements[self.a].element;\n            var b = elements[self.b].element;\n\n            if (self.dragging) {\n                getOption(options, 'onDragEnd', NOOP)(getSizes());\n            }\n\n            self.dragging = false;\n\n            // Remove the stored event listeners. This is why we store them.\n            global[removeEventListener]('mouseup', self.stop);\n            global[removeEventListener]('touchend', self.stop);\n            global[removeEventListener]('touchcancel', self.stop);\n            global[removeEventListener]('mousemove', self.move);\n            global[removeEventListener]('touchmove', self.move);\n\n            // Clear bound function references\n            self.stop = null;\n            self.move = null;\n\n            a[removeEventListener]('selectstart', NOOP);\n            a[removeEventListener]('dragstart', NOOP);\n            b[removeEventListener]('selectstart', NOOP);\n            b[removeEventListener]('dragstart', NOOP);\n\n            a.style.userSelect = '';\n            a.style.webkitUserSelect = '';\n            a.style.MozUserSelect = '';\n            a.style.pointerEvents = '';\n\n            b.style.userSelect = '';\n            b.style.webkitUserSelect = '';\n            b.style.MozUserSelect = '';\n            b.style.pointerEvents = '';\n\n            self.gutter.style.cursor = '';\n            self.parent.style.cursor = '';\n            document.body.style.cursor = '';\n        }\n\n        // startDragging calls `calculateSizes` to store the inital size in the pair object.\n        // It also adds event listeners for mouse/touch events,\n        // and prevents selection while dragging so avoid the selecting text.\n        function startDragging(e) {\n            // Right-clicking can't start dragging.\n            if ('button' in e && e.button !== 0) {\n                return\n            }\n\n            // Alias frequently used variables to save space. 200 bytes.\n            var self = this;\n            var a = elements[self.a].element;\n            var b = elements[self.b].element;\n\n            // Call the onDragStart callback.\n            if (!self.dragging) {\n                getOption(options, 'onDragStart', NOOP)(getSizes());\n            }\n\n            // Don't actually drag the element. We emulate that in the drag function.\n            e.preventDefault();\n\n            // Set the dragging property of the pair object.\n            self.dragging = true;\n\n            // Create two event listeners bound to the same pair object and store\n            // them in the pair object.\n            self.move = drag.bind(self);\n            self.stop = stopDragging.bind(self);\n\n            // All the binding. `window` gets the stop events in case we drag out of the elements.\n            global[addEventListener]('mouseup', self.stop);\n            global[addEventListener]('touchend', self.stop);\n            global[addEventListener]('touchcancel', self.stop);\n            global[addEventListener]('mousemove', self.move);\n            global[addEventListener]('touchmove', self.move);\n\n            // Disable selection. Disable!\n            a[addEventListener]('selectstart', NOOP);\n            a[addEventListener]('dragstart', NOOP);\n            b[addEventListener]('selectstart', NOOP);\n            b[addEventListener]('dragstart', NOOP);\n\n            a.style.userSelect = 'none';\n            a.style.webkitUserSelect = 'none';\n            a.style.MozUserSelect = 'none';\n            a.style.pointerEvents = 'none';\n\n            b.style.userSelect = 'none';\n            b.style.webkitUserSelect = 'none';\n            b.style.MozUserSelect = 'none';\n            b.style.pointerEvents = 'none';\n\n            // Set the cursor at multiple levels\n            self.gutter.style.cursor = cursor;\n            self.parent.style.cursor = cursor;\n            document.body.style.cursor = cursor;\n\n            // Cache the initial sizes of the pair.\n            calculateSizes.call(self);\n\n            // Determine the position of the mouse compared to the gutter\n            self.dragOffset = getMousePosition(e) - self.end;\n        }\n\n        // adjust sizes to ensure percentage is within min size and gutter.\n        sizes = trimToMin(sizes);\n\n        // 5. Create pair and element objects. Each pair has an index reference to\n        // elements `a` and `b` of the pair (first and second elements).\n        // Loop through the elements while pairing them off. Every pair gets a\n        // `pair` object and a gutter.\n        //\n        // Basic logic:\n        //\n        // - Starting with the second element `i > 0`, create `pair` objects with\n        //   `a = i - 1` and `b = i`\n        // - Set gutter sizes based on the _pair_ being first/last. The first and last\n        //   pair have gutterSize / 2, since they only have one half gutter, and not two.\n        // - Create gutter elements and add event listeners.\n        // - Set the size of the elements, minus the gutter sizes.\n        //\n        // -----------------------------------------------------------------------\n        // |     i=0     |         i=1         |        i=2       |      i=3     |\n        // |             |                     |                  |              |\n        // |           pair 0                pair 1             pair 2           |\n        // |             |                     |                  |              |\n        // -----------------------------------------------------------------------\n        var pairs = [];\n        elements = ids.map(function (id, i) {\n            // Create the element object.\n            var element = {\n                element: elementOrSelector(id),\n                size: sizes[i],\n                minSize: minSizes[i],\n                i: i,\n            };\n\n            var pair;\n\n            if (i > 0) {\n                // Create the pair object with its metadata.\n                pair = {\n                    a: i - 1,\n                    b: i,\n                    dragging: false,\n                    direction: direction,\n                    parent: parent,\n                };\n\n                pair[aGutterSize] = getGutterSize(\n                    gutterSize,\n                    i - 1 === 0,\n                    false,\n                    gutterAlign\n                );\n                pair[bGutterSize] = getGutterSize(\n                    gutterSize,\n                    false,\n                    i === ids.length - 1,\n                    gutterAlign\n                );\n\n                // if the parent has a reverse flex-direction, switch the pair elements.\n                if (\n                    parentFlexDirection === 'row-reverse' ||\n                    parentFlexDirection === 'column-reverse'\n                ) {\n                    var temp = pair.a;\n                    pair.a = pair.b;\n                    pair.b = temp;\n                }\n            }\n\n            // Determine the size of the current element. IE8 is supported by\n            // staticly assigning sizes without draggable gutters. Assigns a string\n            // to `size`.\n            //\n            // Create gutter elements for each pair.\n            if (i > 0) {\n                var gutterElement = gutter(i, direction, element.element);\n                setGutterSize(gutterElement, gutterSize, i);\n\n                // Save bound event listener for removal later\n                pair[gutterStartDragging] = startDragging.bind(pair);\n\n                // Attach bound event listener\n                gutterElement[addEventListener](\n                    'mousedown',\n                    pair[gutterStartDragging]\n                );\n                gutterElement[addEventListener](\n                    'touchstart',\n                    pair[gutterStartDragging]\n                );\n\n                parent.insertBefore(gutterElement, element.element);\n\n                pair.gutter = gutterElement;\n            }\n\n            setElementSize(\n                element.element,\n                element.size,\n                getGutterSize(\n                    gutterSize,\n                    i === 0,\n                    i === ids.length - 1,\n                    gutterAlign\n                ),\n                i\n            );\n\n            // After the first iteration, and we have a pair object, append it to the\n            // list of pairs.\n            if (i > 0) {\n                pairs.push(pair);\n            }\n\n            return element\n        });\n\n        function adjustToMin(element) {\n            var isLast = element.i === pairs.length;\n            var pair = isLast ? pairs[element.i - 1] : pairs[element.i];\n\n            calculateSizes.call(pair);\n\n            var size = isLast\n                ? pair.size - element.minSize - pair[bGutterSize]\n                : element.minSize + pair[aGutterSize];\n\n            adjust.call(pair, size);\n        }\n\n        elements.forEach(function (element) {\n            var computedSize = element.element[getBoundingClientRect]()[dimension];\n\n            if (computedSize < element.minSize) {\n                if (expandToMin) {\n                    adjustToMin(element);\n                } else {\n                    // eslint-disable-next-line no-param-reassign\n                    element.minSize = computedSize;\n                }\n            }\n        });\n\n        function setSizes(newSizes) {\n            var trimmed = trimToMin(newSizes);\n            trimmed.forEach(function (newSize, i) {\n                if (i > 0) {\n                    var pair = pairs[i - 1];\n\n                    var a = elements[pair.a];\n                    var b = elements[pair.b];\n\n                    a.size = trimmed[i - 1];\n                    b.size = newSize;\n\n                    setElementSize(a.element, a.size, pair[aGutterSize], a.i);\n                    setElementSize(b.element, b.size, pair[bGutterSize], b.i);\n                }\n            });\n        }\n\n        function destroy(preserveStyles, preserveGutter) {\n            pairs.forEach(function (pair) {\n                if (preserveGutter !== true) {\n                    pair.parent.removeChild(pair.gutter);\n                } else {\n                    pair.gutter[removeEventListener](\n                        'mousedown',\n                        pair[gutterStartDragging]\n                    );\n                    pair.gutter[removeEventListener](\n                        'touchstart',\n                        pair[gutterStartDragging]\n                    );\n                }\n\n                if (preserveStyles !== true) {\n                    var style = elementStyle(\n                        dimension,\n                        pair.a.size,\n                        pair[aGutterSize]\n                    );\n\n                    Object.keys(style).forEach(function (prop) {\n                        elements[pair.a].element.style[prop] = '';\n                        elements[pair.b].element.style[prop] = '';\n                    });\n                }\n            });\n        }\n\n        return {\n            setSizes: setSizes,\n            getSizes: getSizes,\n            collapse: function collapse(i) {\n                adjustToMin(elements[i]);\n            },\n            destroy: destroy,\n            parent: parent,\n            pairs: pairs,\n        }\n    };\n\n    return Split;\n\n})));\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/baguetteBox.js",
    "content": "/*!\n * baguetteBox.js\n * @author  feimosi\n * @version 1.11.1\n * @url https://github.com/feimosi/baguetteBox.js\n */\n\n/* global define, module */\n\n(function (root, factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        define(factory);\n    } else if (typeof exports === 'object') {\n        module.exports = factory();\n    } else {\n        root.baguetteBox = factory();\n    }\n}(this, function () {\n    'use strict';\n\n    // SVG shapes used on the buttons\n    var leftArrow = '<svg width=\"44\" height=\"60\">' +\n            '<polyline points=\"30 10 10 30 30 50\" stroke=\"rgba(255,255,255,0.5)\" stroke-width=\"4\"' +\n              'stroke-linecap=\"butt\" fill=\"none\" stroke-linejoin=\"round\"/>' +\n            '</svg>',\n        rightArrow = '<svg width=\"44\" height=\"60\">' +\n            '<polyline points=\"14 10 34 30 14 50\" stroke=\"rgba(255,255,255,0.5)\" stroke-width=\"4\"' +\n              'stroke-linecap=\"butt\" fill=\"none\" stroke-linejoin=\"round\"/>' +\n            '</svg>',\n        closeX = '<svg width=\"30\" height=\"30\">' +\n            '<g stroke=\"rgb(160,160,160)\" stroke-width=\"4\">' +\n            '<line x1=\"5\" y1=\"5\" x2=\"25\" y2=\"25\"/>' +\n            '<line x1=\"5\" y1=\"25\" x2=\"25\" y2=\"5\"/>' +\n            '</g></svg>';\n    // Global options and their defaults\n    var options = {},\n        defaults = {\n            captions: true,\n            buttons: 'auto',\n            fullScreen: false,\n            noScrollbars: false,\n            bodyClass: 'baguetteBox-open',\n            titleTag: false,\n            async: false,\n            preload: 2,\n            animation: 'slideIn',\n            afterShow: null,\n            afterHide: null,\n            onChange: null,\n            overlayBackgroundColor: 'rgba(0,0,0,.8)'\n        };\n    // Object containing information about features compatibility\n    var supports = {};\n    // DOM Elements references\n    var overlay, slider, previousButton, nextButton, closeButton;\n    // An array with all images in the current gallery\n    var currentGallery = [];\n    // Current image index inside the slider\n    var currentIndex = 0;\n    // Visibility of the overlay\n    var isOverlayVisible = false;\n    // Touch event start position (for slide gesture)\n    var touch = {};\n    // If set to true ignore touch events because animation was already fired\n    var touchFlag = false;\n    // Regex pattern to match image files\n    var regex = /.+\\.(gif|jpe?g|png|webp)/i;\n    // Object of all used galleries\n    var data = {};\n    // Array containing temporary images DOM elements\n    var imagesElements = [];\n    // The last focused element before opening the overlay\n    var documentLastFocus = null;\n    var overlayClickHandler = function(event) {\n        // Close the overlay when user clicks directly on the background\n        if (event.target.id.indexOf('baguette-img') !== -1) {\n            hideOverlay();\n        }\n    };\n    var previousButtonClickHandler = function(event) {\n        event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true; // eslint-disable-line no-unused-expressions\n        showPreviousImage();\n    };\n    var nextButtonClickHandler = function(event) {\n        event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true; // eslint-disable-line no-unused-expressions\n        showNextImage();\n    };\n    var closeButtonClickHandler = function(event) {\n        event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true; // eslint-disable-line no-unused-expressions\n        hideOverlay();\n    };\n    var touchstartHandler = function(event) {\n        touch.count++;\n        if (touch.count > 1) {\n            touch.multitouch = true;\n        }\n        // Save x and y axis position\n        touch.startX = event.changedTouches[0].pageX;\n        touch.startY = event.changedTouches[0].pageY;\n    };\n    var touchmoveHandler = function(event) {\n        // If action was already triggered or multitouch return\n        if (touchFlag || touch.multitouch) {\n            return;\n        }\n        event.preventDefault ? event.preventDefault() : event.returnValue = false; // eslint-disable-line no-unused-expressions\n        var touchEvent = event.touches[0] || event.changedTouches[0];\n        // Move at least 40 pixels to trigger the action\n        if (touchEvent.pageX - touch.startX > 40) {\n            touchFlag = true;\n            showPreviousImage();\n        } else if (touchEvent.pageX - touch.startX < -40) {\n            touchFlag = true;\n            showNextImage();\n        // Move 100 pixels up to close the overlay\n        } else if (touch.startY - touchEvent.pageY > 100) {\n            hideOverlay();\n        }\n    };\n    var touchendHandler = function() {\n        touch.count--;\n        if (touch.count <= 0) {\n            touch.multitouch = false;\n        }\n        touchFlag = false;\n    };\n    var contextmenuHandler = function() {\n        touchendHandler();\n    };\n\n    var trapFocusInsideOverlay = function(event) {\n        if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(event.target))) {\n            event.stopPropagation();\n            initFocus();\n        }\n    };\n\n    // forEach polyfill for IE8\n    // http://stackoverflow.com/a/14827443/1077846\n    /* eslint-disable */\n    if (![].forEach) {\n        Array.prototype.forEach = function(callback, thisArg) {\n            for (var i = 0; i < this.length; i++) {\n                callback.call(thisArg, this[i], i, this);\n            }\n        };\n    }\n\n    // filter polyfill for IE8\n    // https://gist.github.com/eliperelman/1031656\n    if (![].filter) {\n        Array.prototype.filter = function(a, b, c, d, e) {\n            c = this;\n            d = [];\n            for (e = 0; e < c.length; e++)\n                a.call(b, c[e], e, c) && d.push(c[e]);\n            return d;\n        };\n    }\n    /* eslint-enable */\n\n    // Script entry point\n    function run(selector, userOptions) {\n        // Fill supports object\n        supports.transforms = testTransformsSupport();\n        supports.svg = testSvgSupport();\n        supports.passiveEvents = testPassiveEventsSupport();\n\n        buildOverlay();\n        removeFromCache(selector);\n        return bindImageClickListeners(selector, userOptions);\n    }\n\n    function bindImageClickListeners(selector, userOptions) {\n        // For each gallery bind a click event to every image inside it\n        var galleryNodeList = document.querySelectorAll(selector);\n        var selectorData = {\n            galleries: [],\n            nodeList: galleryNodeList\n        };\n        data[selector] = selectorData;\n\n        [].forEach.call(galleryNodeList, function(galleryElement) {\n            if (userOptions && userOptions.filter) {\n                regex = userOptions.filter;\n            }\n\n            // Get nodes from gallery elements or single-element galleries\n            var tagsNodeList = [];\n            if (galleryElement.tagName === 'A') {\n                tagsNodeList = [galleryElement];\n            } else {\n                tagsNodeList = galleryElement.getElementsByTagName('a');\n            }\n\n            // Filter 'a' elements from those not linking to images\n            tagsNodeList = [].filter.call(tagsNodeList, function(element) {\n                if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {\n                    return regex.test(element.href);\n                }\n            });\n            if (tagsNodeList.length === 0) {\n                return;\n            }\n\n            var gallery = [];\n            [].forEach.call(tagsNodeList, function(imageElement, imageIndex) {\n                var imageElementClickHandler = function(event) {\n                    event.preventDefault ? event.preventDefault() : event.returnValue = false; // eslint-disable-line no-unused-expressions\n                    prepareOverlay(gallery, userOptions);\n                    showOverlay(imageIndex);\n                };\n                var imageItem = {\n                    eventHandler: imageElementClickHandler,\n                    imageElement: imageElement\n                };\n                bind(imageElement, 'click', imageElementClickHandler);\n                gallery.push(imageItem);\n            });\n            selectorData.galleries.push(gallery);\n        });\n\n        return selectorData.galleries;\n    }\n\n    function clearCachedData() {\n        for (var selector in data) {\n            if (data.hasOwnProperty(selector)) {\n                removeFromCache(selector);\n            }\n        }\n    }\n\n    function removeFromCache(selector) {\n        if (!data.hasOwnProperty(selector)) {\n            return;\n        }\n        var galleries = data[selector].galleries;\n        [].forEach.call(galleries, function(gallery) {\n            [].forEach.call(gallery, function(imageItem) {\n                unbind(imageItem.imageElement, 'click', imageItem.eventHandler);\n            });\n\n            if (currentGallery === gallery) {\n                currentGallery = [];\n            }\n        });\n\n        delete data[selector];\n    }\n\n    function buildOverlay() {\n        overlay = getByID('baguetteBox-overlay');\n        // Check if the overlay already exists\n        if (overlay) {\n            slider = getByID('baguetteBox-slider');\n            previousButton = getByID('previous-button');\n            nextButton = getByID('next-button');\n            closeButton = getByID('close-button');\n            return;\n        }\n        // Create overlay element\n        overlay = create('div');\n        overlay.setAttribute('role', 'dialog');\n        overlay.id = 'baguetteBox-overlay';\n        document.getElementsByTagName('body')[0].appendChild(overlay);\n        // Create gallery slider element\n        slider = create('div');\n        slider.id = 'baguetteBox-slider';\n        overlay.appendChild(slider);\n        // Create all necessary buttons\n        previousButton = create('button');\n        previousButton.setAttribute('type', 'button');\n        previousButton.id = 'previous-button';\n        previousButton.setAttribute('aria-label', 'Previous');\n        previousButton.innerHTML = supports.svg ? leftArrow : '&lt;';\n        overlay.appendChild(previousButton);\n\n        nextButton = create('button');\n        nextButton.setAttribute('type', 'button');\n        nextButton.id = 'next-button';\n        nextButton.setAttribute('aria-label', 'Next');\n        nextButton.innerHTML = supports.svg ? rightArrow : '&gt;';\n        overlay.appendChild(nextButton);\n\n        closeButton = create('button');\n        closeButton.setAttribute('type', 'button');\n        closeButton.id = 'close-button';\n        closeButton.setAttribute('aria-label', 'Close');\n        closeButton.innerHTML = supports.svg ? closeX : '&times;';\n        overlay.appendChild(closeButton);\n\n        previousButton.className = nextButton.className = closeButton.className = 'baguetteBox-button';\n\n        bindEvents();\n    }\n\n    function keyDownHandler(event) {\n        switch (event.keyCode) {\n        case 37: // Left arrow\n            showPreviousImage();\n            break;\n        case 39: // Right arrow\n            showNextImage();\n            break;\n        case 27: // Esc\n            hideOverlay();\n            break;\n        case 36: // Home\n            showFirstImage(event);\n            break;\n        case 35: // End\n            showLastImage(event);\n            break;\n        }\n    }\n\n    function bindEvents() {\n        var passiveEvent = supports.passiveEvents ? { passive: false } : null;\n        var nonPassiveEvent = supports.passiveEvents ? { passive: true } : null;\n\n        bind(overlay, 'click', overlayClickHandler);\n        bind(previousButton, 'click', previousButtonClickHandler);\n        bind(nextButton, 'click', nextButtonClickHandler);\n        bind(closeButton, 'click', closeButtonClickHandler);\n        bind(slider, 'contextmenu', contextmenuHandler);\n        bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);\n        bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);\n        bind(overlay, 'touchend', touchendHandler);\n        bind(document, 'focus', trapFocusInsideOverlay, true);\n    }\n\n    function unbindEvents() {\n        var passiveEvent = supports.passiveEvents ? { passive: false } : null;\n        var nonPassiveEvent = supports.passiveEvents ? { passive: true } : null;\n\n        unbind(overlay, 'click', overlayClickHandler);\n        unbind(previousButton, 'click', previousButtonClickHandler);\n        unbind(nextButton, 'click', nextButtonClickHandler);\n        unbind(closeButton, 'click', closeButtonClickHandler);\n        unbind(slider, 'contextmenu', contextmenuHandler);\n        unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);\n        unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);\n        unbind(overlay, 'touchend', touchendHandler);\n        unbind(document, 'focus', trapFocusInsideOverlay, true);\n    }\n\n    function prepareOverlay(gallery, userOptions) {\n        // If the same gallery is being opened prevent from loading it once again\n        if (currentGallery === gallery) {\n            return;\n        }\n        currentGallery = gallery;\n        // Update gallery specific options\n        setOptions(userOptions);\n        // Empty slider of previous contents (more effective than .innerHTML = \"\")\n        while (slider.firstChild) {\n            slider.removeChild(slider.firstChild);\n        }\n        imagesElements.length = 0;\n\n        var imagesFiguresIds = [];\n        var imagesCaptionsIds = [];\n        // Prepare and append images containers and populate figure and captions IDs arrays\n        for (var i = 0, fullImage; i < gallery.length; i++) {\n            fullImage = create('div');\n            fullImage.className = 'full-image';\n            fullImage.id = 'baguette-img-' + i;\n            imagesElements.push(fullImage);\n\n            imagesFiguresIds.push('baguetteBox-figure-' + i);\n            imagesCaptionsIds.push('baguetteBox-figcaption-' + i);\n            slider.appendChild(imagesElements[i]);\n        }\n        overlay.setAttribute('aria-labelledby', imagesFiguresIds.join(' '));\n        overlay.setAttribute('aria-describedby', imagesCaptionsIds.join(' '));\n    }\n\n    function setOptions(newOptions) {\n        if (!newOptions) {\n            newOptions = {};\n        }\n        // Fill options object\n        for (var item in defaults) {\n            options[item] = defaults[item];\n            if (typeof newOptions[item] !== 'undefined') {\n                options[item] = newOptions[item];\n            }\n        }\n        /* Apply new options */\n        // Change transition for proper animation\n        slider.style.transition = slider.style.webkitTransition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :\n            options.animation === 'slideIn' ? '' : 'none');\n        // Hide buttons if necessary\n        if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) {\n            options.buttons = false;\n        }\n        // Set buttons style to hide or display them\n        previousButton.style.display = nextButton.style.display = (options.buttons ? '' : 'none');\n        // Set overlay color\n        try {\n            overlay.style.backgroundColor = options.overlayBackgroundColor;\n        } catch (e) {\n            // Silence the error and continue\n        }\n    }\n\n    function showOverlay(chosenImageIndex) {\n        if (options.noScrollbars) {\n            document.documentElement.style.overflowY = 'hidden';\n            document.body.style.overflowY = 'scroll';\n        }\n        if (overlay.style.display === 'block') {\n            return;\n        }\n\n        bind(document, 'keydown', keyDownHandler);\n        currentIndex = chosenImageIndex;\n        touch = {\n            count: 0,\n            startX: null,\n            startY: null\n        };\n        loadImage(currentIndex, function() {\n            preloadNext(currentIndex);\n            preloadPrev(currentIndex);\n        });\n\n        updateOffset();\n        overlay.style.display = 'block';\n        if (options.fullScreen) {\n            enterFullScreen();\n        }\n        // Fade in overlay\n        setTimeout(function() {\n            overlay.className = 'visible';\n            if (options.bodyClass && document.body.classList) {\n                document.body.classList.add(options.bodyClass);\n            }\n            if (options.afterShow) {\n                options.afterShow();\n            }\n        }, 50);\n        if (options.onChange) {\n            options.onChange(currentIndex, imagesElements.length);\n        }\n        documentLastFocus = document.activeElement;\n        initFocus();\n        isOverlayVisible = true;\n    }\n\n    function initFocus() {\n        if (options.buttons) {\n            previousButton.focus();\n        } else {\n            closeButton.focus();\n        }\n    }\n\n    function enterFullScreen() {\n        if (overlay.requestFullscreen) {\n            overlay.requestFullscreen();\n        } else if (overlay.webkitRequestFullscreen) {\n            overlay.webkitRequestFullscreen();\n        } else if (overlay.mozRequestFullScreen) {\n            overlay.mozRequestFullScreen();\n        }\n    }\n\n    function exitFullscreen() {\n        if (document.exitFullscreen) {\n            document.exitFullscreen();\n        } else if (document.mozCancelFullScreen) {\n            document.mozCancelFullScreen();\n        } else if (document.webkitExitFullscreen) {\n            document.webkitExitFullscreen();\n        }\n    }\n\n    function hideOverlay() {\n        if (options.noScrollbars) {\n            document.documentElement.style.overflowY = 'auto';\n            document.body.style.overflowY = 'auto';\n        }\n        if (overlay.style.display === 'none') {\n            return;\n        }\n\n        unbind(document, 'keydown', keyDownHandler);\n        // Fade out and hide the overlay\n        overlay.className = '';\n        setTimeout(function() {\n            overlay.style.display = 'none';\n            if (document.fullscreen) {\n                exitFullscreen();\n            }\n            if (options.bodyClass && document.body.classList) {\n                document.body.classList.remove(options.bodyClass);\n            }\n            if (options.afterHide) {\n                options.afterHide();\n            }\n            documentLastFocus && documentLastFocus.focus();\n            isOverlayVisible = false;\n        }, 500);\n    }\n\n    function loadImage(index, callback) {\n        var imageContainer = imagesElements[index];\n        var galleryItem = currentGallery[index];\n\n        // Return if the index exceeds prepared images in the overlay\n        // or if the current gallery has been changed / closed\n        if (typeof imageContainer === 'undefined' || typeof galleryItem === 'undefined') {\n            return;\n        }\n\n        // If image is already loaded run callback and return\n        if (imageContainer.getElementsByTagName('img')[0]) {\n            if (callback) {\n                callback();\n            }\n            return;\n        }\n\n        // Get element reference, optional caption and source path\n        var imageElement = galleryItem.imageElement;\n        var thumbnailElement = imageElement.getElementsByTagName('img')[0];\n        var imageCaption = typeof options.captions === 'function' ?\n            options.captions.call(currentGallery, imageElement) :\n            imageElement.getAttribute('data-caption') || imageElement.title;\n        var imageSrc = getImageSrc(imageElement);\n\n        // Prepare figure element\n        var figure = create('figure');\n        figure.id = 'baguetteBox-figure-' + index;\n        figure.innerHTML = '<div class=\"baguetteBox-spinner\">' +\n            '<div class=\"baguetteBox-double-bounce1\"></div>' +\n            '<div class=\"baguetteBox-double-bounce2\"></div>' +\n            '</div>';\n        // Insert caption if available\n        if (options.captions && imageCaption) {\n            var figcaption = create('figcaption');\n            figcaption.id = 'baguetteBox-figcaption-' + index;\n            figcaption.innerHTML = imageCaption;\n            figure.appendChild(figcaption);\n        }\n        imageContainer.appendChild(figure);\n\n        // Prepare gallery img element\n        var image = create('img');\n        image.onload = function() {\n            // Remove loader element\n            var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');\n            figure.removeChild(spinner);\n            if (!options.async && callback) {\n                callback();\n            }\n        };\n        image.setAttribute('src', imageSrc);\n        image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';\n        if (options.titleTag && imageCaption) {\n            image.title = imageCaption;\n        }\n        figure.appendChild(image);\n\n        // Run callback\n        if (options.async && callback) {\n            callback();\n        }\n    }\n\n    // Get image source location, mostly used for responsive images\n    function getImageSrc(image) {\n        // Set default image path from href\n        var result = image.href;\n        // If dataset is supported find the most suitable image\n        if (image.dataset) {\n            var srcs = [];\n            // Get all possible image versions depending on the resolution\n            for (var item in image.dataset) {\n                if (item.substring(0, 3) === 'at-' && !isNaN(item.substring(3))) {\n                    srcs[item.replace('at-', '')] = image.dataset[item];\n                }\n            }\n            // Sort resolutions ascending\n            var keys = Object.keys(srcs).sort(function(a, b) {\n                return parseInt(a, 10) < parseInt(b, 10) ? -1 : 1;\n            });\n            // Get real screen resolution\n            var width = window.innerWidth * window.devicePixelRatio;\n            // Find the first image bigger than or equal to the current width\n            var i = 0;\n            while (i < keys.length - 1 && keys[i] < width) {\n                i++;\n            }\n            result = srcs[keys[i]] || result;\n        }\n        return result;\n    }\n\n    // Return false at the right end of the gallery\n    function showNextImage() {\n        return show(currentIndex + 1);\n    }\n\n    // Return false at the left end of the gallery\n    function showPreviousImage() {\n        return show(currentIndex - 1);\n    }\n\n    // Return false at the left end of the gallery\n    function showFirstImage(event) {\n        if (event) {\n            event.preventDefault();\n        }\n        return show(0);\n    }\n\n    // Return false at the right end of the gallery\n    function showLastImage(event) {\n        if (event) {\n            event.preventDefault();\n        }\n        return show(currentGallery.length - 1);\n    }\n\n    /**\n     * Move the gallery to a specific index\n     * @param `index` {number} - the position of the image\n     * @param `gallery` {array} - gallery which should be opened, if omitted assumes the currently opened one\n     * @return {boolean} - true on success or false if the index is invalid\n     */\n    function show(index, gallery) {\n        if (!isOverlayVisible && index >= 0 && index < gallery.length) {\n            prepareOverlay(gallery, options);\n            showOverlay(index);\n            return true;\n        }\n        if (index < 0) {\n            if (options.animation) {\n                bounceAnimation('left');\n            }\n            return false;\n        }\n        if (index >= imagesElements.length) {\n            if (options.animation) {\n                bounceAnimation('right');\n            }\n            return false;\n        }\n\n        currentIndex = index;\n        loadImage(currentIndex, function() {\n            preloadNext(currentIndex);\n            preloadPrev(currentIndex);\n        });\n        updateOffset();\n\n        if (options.onChange) {\n            options.onChange(currentIndex, imagesElements.length);\n        }\n\n        return true;\n    }\n\n    /**\n     * Triggers the bounce animation\n     * @param {('left'|'right')} direction - Direction of the movement\n     */\n    function bounceAnimation(direction) {\n        slider.className = 'bounce-from-' + direction;\n        setTimeout(function() {\n            slider.className = '';\n        }, 400);\n    }\n\n    function updateOffset() {\n        var offset = -currentIndex * 100 + '%';\n        if (options.animation === 'fadeIn') {\n            slider.style.opacity = 0;\n            setTimeout(function() {\n                supports.transforms ?\n                    slider.style.transform = slider.style.webkitTransform = 'translate3d(' + offset + ',0,0)'\n                    : slider.style.left = offset;\n                slider.style.opacity = 1;\n            }, 400);\n        } else {\n            supports.transforms ?\n                slider.style.transform = slider.style.webkitTransform = 'translate3d(' + offset + ',0,0)'\n                : slider.style.left = offset;\n        }\n    }\n\n    // CSS 3D Transforms test\n    function testTransformsSupport() {\n        var div = create('div');\n        return typeof div.style.perspective !== 'undefined' || typeof div.style.webkitPerspective !== 'undefined';\n    }\n\n    // Inline SVG test\n    function testSvgSupport() {\n        var div = create('div');\n        div.innerHTML = '<svg/>';\n        return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';\n    }\n\n    // Borrowed from https://github.com/seiyria/bootstrap-slider/pull/680/files\n    /* eslint-disable getter-return */\n    function testPassiveEventsSupport() {\n        var passiveEvents = false;\n        try {\n            var opts = Object.defineProperty({}, 'passive', {\n                get: function() {\n                    passiveEvents = true;\n                }\n            });\n            window.addEventListener('test', null, opts);\n        } catch (e) { /* Silence the error and continue */ }\n\n        return passiveEvents;\n    }\n    /* eslint-enable getter-return */\n\n    function preloadNext(index) {\n        if (index - currentIndex >= options.preload) {\n            return;\n        }\n        loadImage(index + 1, function() {\n            preloadNext(index + 1);\n        });\n    }\n\n    function preloadPrev(index) {\n        if (currentIndex - index >= options.preload) {\n            return;\n        }\n        loadImage(index - 1, function() {\n            preloadPrev(index - 1);\n        });\n    }\n\n    function bind(element, event, callback, options) {\n        if (element.addEventListener) {\n            element.addEventListener(event, callback, options);\n        } else {\n            // IE8 fallback\n            element.attachEvent('on' + event, function(event) {\n                // `event` and `event.target` are not provided in IE8\n                event = event || window.event;\n                event.target = event.target || event.srcElement;\n                callback(event);\n            });\n        }\n    }\n\n    function unbind(element, event, callback, options) {\n        if (element.removeEventListener) {\n            element.removeEventListener(event, callback, options);\n        } else {\n            // IE8 fallback\n            element.detachEvent('on' + event, callback);\n        }\n    }\n\n    function getByID(id) {\n        return document.getElementById(id);\n    }\n\n    function create(element) {\n        return document.createElement(element);\n    }\n\n    function destroyPlugin() {\n        unbindEvents();\n        clearCachedData();\n        unbind(document, 'keydown', keyDownHandler);\n        document.getElementsByTagName('body')[0].removeChild(document.getElementById('baguetteBox-overlay'));\n        data = {};\n        currentGallery = [];\n        currentIndex = 0;\n    }\n\n    return {\n        run: run,\n        show: show,\n        showNext: showNextImage,\n        showPrevious: showPreviousImage,\n        hide: hideOverlay,\n        destroy: destroyPlugin\n    };\n}));\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/bootstrap-modal.js",
    "content": "/* ===========================================================\n * bootstrap-modal.js v2.2.5\n * ===========================================================\n * Copyright 2012 Jordan Schroter\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n\n!function ($) {\n\n\t\"use strict\"; // jshint ;_;\n\n\t/* MODAL CLASS DEFINITION\n\t* ====================== */\n\n\tvar Modal = function (element, options) {\n\t\tthis.init(element, options);\n\t};\n\n\tModal.prototype = {\n\n\t\tconstructor: Modal,\n\n\t\tinit: function (element, options) {\n\t\t\tvar that = this;\n\n\t\t\tthis.options = options;\n\n\t\t\tthis.$element = $(element)\n\t\t\t\t.delegate('[data-dismiss=\"modal\"]', 'click.dismiss.modal', $.proxy(this.hide, this));\n\n\t\t\tthis.options.remote && this.$element.find('.modal-body').load(this.options.remote, function () {\n\t\t\t\tvar e = $.Event('loaded');\n\t\t\t\tthat.$element.trigger(e);\n\t\t\t});\n\n\t\t\tvar manager = typeof this.options.manager === 'function' ?\n\t\t\t\tthis.options.manager.call(this) : this.options.manager;\n\n\t\t\tmanager = manager.appendModal ?\n\t\t\t\tmanager : $(manager).modalmanager().data('modalmanager');\n\n\t\t\tmanager.appendModal(this);\n\t\t},\n\n\t\ttoggle: function () {\n\t\t\treturn this[!this.isShown ? 'show' : 'hide']();\n\t\t},\n\n\t\tshow: function () {\n\t\t\tvar e = $.Event('show');\n\n\t\t\tif (this.isShown) return;\n\n\t\t\tthis.$element.trigger(e);\n\n\t\t\tif (e.isDefaultPrevented()) return;\n\n\t\t\tthis.escape();\n\n\t\t\tthis.tab();\n\n\t\t\tthis.options.loading && this.loading();\n\t\t},\n\n\t\thide: function (e) {\n\t\t\te && e.preventDefault();\n\n\t\t\te = $.Event('hide');\n\n\t\t\tthis.$element.trigger(e);\n\n\t\t\tif (!this.isShown || e.isDefaultPrevented()) return;\n\n\t\t\tthis.isShown = false;\n\n\t\t\tthis.escape();\n\n\t\t\tthis.tab();\n\n\t\t\tthis.isLoading && this.loading();\n\n\t\t\t$(document).off('focusin.modal');\n\n\t\t\tthis.$element\n\t\t\t\t.removeClass('in')\n\t\t\t\t.removeClass('animated')\n\t\t\t\t.removeClass(this.options.attentionAnimation)\n\t\t\t\t.removeClass('modal-overflow')\n\t\t\t\t.attr('aria-hidden', true);\n\n\t\t\t$.support.transition && this.$element.hasClass('fade') ?\n\t\t\t\tthis.hideWithTransition() :\n\t\t\t\tthis.hideModal();\n\t\t},\n\n\t\tlayout: function () {\n\t\t\tvar prop = this.options.height ? 'height' : 'max-height',\n\t\t\t\tvalue = this.options.height || this.options.maxHeight;\n\n\t\t\tif (this.options.width){\n\t\t\t\tthis.$element.css('width', this.options.width);\n\n\t\t\t\tvar that = this;\n\t\t\t\tthis.$element.css('margin-left', function () {\n\t\t\t\t\tif (/%/ig.test(that.options.width)){\n\t\t\t\t\t\treturn -(parseInt(that.options.width) / 2) + '%';\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn -($(this).width() / 2) + 'px';\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.$element.css('width', '');\n\t\t\t\tthis.$element.css('margin-left', '');\n\t\t\t}\n\n\t\t\tthis.$element.find('.modal-body')\n\t\t\t\t.css('overflow', '')\n\t\t\t\t.css(prop, '');\n\n\t\t\tif (value){\n\t\t\t\tthis.$element.find('.modal-body')\n\t\t\t\t\t.css('overflow', 'auto')\n\t\t\t\t\t.css(prop, value);\n\t\t\t}\n\n\t\t\tvar modalOverflow = $(window).height() - 10 < this.$element.height();\n            \n\t\t\tif (modalOverflow || this.options.modalOverflow) {\n\t\t\t\tthis.$element\n\t\t\t\t\t.css('margin-top', 0)\n\t\t\t\t\t.addClass('modal-overflow');\n\t\t\t} else {\n\t\t\t\tthis.$element\n\t\t\t\t\t.css('margin-top', 0 - this.$element.height() / 2)\n\t\t\t\t\t.removeClass('modal-overflow');\n\t\t\t}\n\t\t},\n\n\t\ttab: function () {\n\t\t\tvar that = this;\n\n\t\t\tif (this.isShown && this.options.consumeTab) {\n\t\t\t\tthis.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) {\n\t\t\t    \tif (e.keyCode && e.keyCode == 9){\n\t\t\t\t\t\tvar elements = [],\n\t\t\t\t\t\t\ttabindex = Number($(this).data('tabindex'));\n\n\t\t\t\t\t\tthat.$element.find('[data-tabindex]:enabled:visible:not([readonly])').each(function (ev) {\n\t\t\t\t\t\t\telements.push(Number($(this).data('tabindex')));\n\t\t\t\t\t\t});\n\t\t\t\t\t\telements.sort(function(a,b){return a-b});\n\t\t\t\t\t\t\n\t\t\t\t\t\tvar arrayPos = $.inArray(tabindex, elements);\n\t\t\t\t\t\tif (!e.shiftKey){\n\t\t\t\t\t\t \t\tarrayPos < elements.length-1 ?\n\t\t\t\t\t\t\t\t\tthat.$element.find('[data-tabindex='+elements[arrayPos+1]+']').focus() :\n\t\t\t\t\t\t\t\t\tthat.$element.find('[data-tabindex='+elements[0]+']').focus();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tarrayPos == 0 ?\n\t\t\t\t\t\t\t\t\tthat.$element.find('[data-tabindex='+elements[elements.length-1]+']').focus() :\n\t\t\t\t\t\t\t\t\tthat.$element.find('[data-tabindex='+elements[arrayPos-1]+']').focus();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else if (!this.isShown) {\n\t\t\t\tthis.$element.off('keydown.tabindex.modal');\n\t\t\t}\n\t\t},\n\n\t\tescape: function () {\n\t\t\tvar that = this;\n\t\t\tif (this.isShown && this.options.keyboard) {\n\t\t\t\tif (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1);\n\n\t\t\t\tthis.$element.on('keyup.dismiss.modal', function (e) {\n\t\t\t\t\te.which == 27 && that.hide();\n\t\t\t\t});\n\t\t\t} else if (!this.isShown) {\n\t\t\t\tthis.$element.off('keyup.dismiss.modal')\n\t\t\t}\n\t\t},\n\n\t\thideWithTransition: function () {\n\t\t\tvar that = this\n\t\t\t\t, timeout = setTimeout(function () {\n\t\t\t\t\tthat.$element.off($.support.transition.end);\n\t\t\t\t\tthat.hideModal();\n\t\t\t\t}, 500);\n\n\t\t\tthis.$element.one($.support.transition.end, function () {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthat.hideModal();\n\t\t\t});\n\t\t},\n\n\t\thideModal: function () {\n\t\t\tvar prop = this.options.height ? 'height' : 'max-height';\n\t\t\tvar value = this.options.height || this.options.maxHeight;\n\n\t\t\tif (value){\n\t\t\t\tthis.$element.find('.modal-body')\n\t\t\t\t\t.css('overflow', '')\n\t\t\t\t\t.css(prop, '');\n\t\t\t}\n\n\t\t\tthis.$element\n\t\t\t\t.hide()\n\t\t\t\t.trigger('hidden');\n\t\t},\n\n\t\tremoveLoading: function () {\n\t\t\tthis.$loading.remove();\n\t\t\tthis.$loading = null;\n\t\t\tthis.isLoading = false;\n\t\t},\n\n\t\tloading: function (callback) {\n\t\t\tcallback = callback || function () {};\n\n\t\t\tvar animate = this.$element.hasClass('fade') ? 'fade' : '';\n\n\t\t\tif (!this.isLoading) {\n\t\t\t\tvar doAnimate = $.support.transition && animate;\n\n\t\t\t\tthis.$loading = $('<div class=\"loading-mask ' + animate + '\">')\n\t\t\t\t\t.append(this.options.spinner)\n\t\t\t\t\t.appendTo(this.$element);\n\n\t\t\t\tif (doAnimate) this.$loading[0].offsetWidth; // force reflow\n\n\t\t\t\tthis.$loading.addClass('in');\n\n\t\t\t\tthis.isLoading = true;\n\n\t\t\t\tdoAnimate ?\n\t\t\t\t\tthis.$loading.one($.support.transition.end, callback) :\n\t\t\t\t\tcallback();\n\n\t\t\t} else if (this.isLoading && this.$loading) {\n\t\t\t\tthis.$loading.removeClass('in');\n\n\t\t\t\tvar that = this;\n\t\t\t\t$.support.transition && this.$element.hasClass('fade')?\n\t\t\t\t\tthis.$loading.one($.support.transition.end, function () { that.removeLoading() }) :\n\t\t\t\t\tthat.removeLoading();\n\n\t\t\t} else if (callback) {\n\t\t\t\tcallback(this.isLoading);\n\t\t\t}\n\t\t},\n\n\t\tfocus: function () {\n\t\t\tvar $focusElem = this.$element.find(this.options.focusOn);\n\n\t\t\t$focusElem = $focusElem.length ? $focusElem : this.$element;\n\n\t\t\t$focusElem.focus();\n\t\t},\n\n\t\tattention: function (){\n\t\t\t// NOTE: transitionEnd with keyframes causes odd behaviour\n\n\t\t\tif (this.options.attentionAnimation){\n\t\t\t\tthis.$element\n\t\t\t\t\t.removeClass('animated')\n\t\t\t\t\t.removeClass(this.options.attentionAnimation);\n\n\t\t\t\tvar that = this;\n\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\tthat.$element\n\t\t\t\t\t\t.addClass('animated')\n\t\t\t\t\t\t.addClass(that.options.attentionAnimation);\n\t\t\t\t}, 0);\n\t\t\t}\n\n\n\t\t\tthis.focus();\n\t\t},\n\n\n\t\tdestroy: function () {\n\t\t\tvar e = $.Event('destroy');\n\n\t\t\tthis.$element.trigger(e);\n\n\t\t\tif (e.isDefaultPrevented()) return;\n\n\t\t\tthis.$element\n\t\t\t\t.off('.modal')\n\t\t\t\t.removeData('modal')\n\t\t\t\t.removeClass('in')\n\t\t\t\t.attr('aria-hidden', true);\n\t\t\t\n\t\t\tif (this.$parent !== this.$element.parent()) {\n\t\t\t\tthis.$element.appendTo(this.$parent);\n\t\t\t} else if (!this.$parent.length) {\n\t\t\t\t// modal is not part of the DOM so remove it.\n\t\t\t\tthis.$element.remove();\n\t\t\t\tthis.$element = null;\n\t\t\t}\n\n\t\t\tthis.$element.trigger('destroyed');\n\t\t}\n\t};\n\n\n\t/* MODAL PLUGIN DEFINITION\n\t* ======================= */\n\n\t$.fn.modal = function (option, args) {\n\t\treturn this.each(function () {\n\t\t\tvar $this = $(this),\n\t\t\t\tdata = $this.data('modal'),\n\t\t\t\toptions = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option);\n\n\t\t\tif (!data) $this.data('modal', (data = new Modal(this, options)));\n\t\t\tif (typeof option == 'string') data[option].apply(data, [].concat(args));\n\t\t\telse if (options.show) data.show()\n\t\t})\n\t};\n\n\t$.fn.modal.defaults = {\n\t\tkeyboard: true,\n\t\tbackdrop: true,\n\t\tloading: false,\n\t\tshow: true,\n\t\twidth: null,\n\t\theight: null,\n\t\tmaxHeight: null,\n\t\tmodalOverflow: false,\n\t\tconsumeTab: true,\n\t\tfocusOn: null,\n\t\treplace: false,\n\t\tresize: false,\n\t\tattentionAnimation: 'shake',\n\t\tmanager: 'body',\n\t\tspinner: '<div class=\"loading-spinner\" style=\"width: 200px; margin-left: -100px;\"><div class=\"progress progress-striped active\"><div class=\"bar\" style=\"width: 100%;\"></div></div></div>',\n\t\tbackdropTemplate: '<div class=\"modal-backdrop\" />'\n\t};\n\n\t$.fn.modal.Constructor = Modal;\n\n\n\t/* MODAL DATA-API\n\t* ============== */\n\n\t$(function () {\n\t\t$(document).off('click.modal').on('click.modal.data-api', '[data-toggle=\"modal\"]', function ( e ) {\n\t\t\tvar $this = $(this),\n\t\t\t\thref = $this.attr('href'),\n\t\t\t\t$target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))), //strip for ie7\n\t\t\t\toption = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data());\n\n\t\t\te.preventDefault();\n\t\t\t$target\n\t\t\t\t.modal(option)\n\t\t\t\t.one('hide', function () {\n\t\t\t\t\t$this.focus();\n\t\t\t\t})\n\t\t});\n\t});\n\n}(window.jQuery);\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/bootstrap-modalmanager.js",
    "content": "/* ===========================================================\n * bootstrap-modalmanager.js v2.2.5\n * ===========================================================\n * Copyright 2012 Jordan Schroter.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n!function ($) {\n\n\t\"use strict\"; // jshint ;_;\n\n\t/* MODAL MANAGER CLASS DEFINITION\n\t* ====================== */\n\n\tvar ModalManager = function (element, options) {\n\t\tthis.init(element, options);\n\t};\n\n\tModalManager.prototype = {\n\n\t\tconstructor: ModalManager,\n\n\t\tinit: function (element, options) {\n\t\t\tthis.$element = $(element);\n\t\t\tthis.options = $.extend({}, $.fn.modalmanager.defaults, this.$element.data(), typeof options == 'object' && options);\n\t\t\tthis.stack = [];\n\t\t\tthis.backdropCount = 0;\n\n\t\t\tif (this.options.resize) {\n\t\t\t\tvar resizeTimeout,\n\t\t\t\t\tthat = this;\n\n\t\t\t\t$(window).on('resize.modal', function(){\n\t\t\t\t\tresizeTimeout && clearTimeout(resizeTimeout);\n\t\t\t\t\tresizeTimeout = setTimeout(function(){\n\t\t\t\t\t\tfor (var i = 0; i < that.stack.length; i++){\n\t\t\t\t\t\t\tthat.stack[i].isShown && that.stack[i].layout();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 10);\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\tcreateModal: function (element, options) {\n\t\t\t$(element).modal($.extend({ manager: this }, options));\n\t\t},\n\n\t\tappendModal: function (modal) {\n\t\t\tthis.stack.push(modal);\n\n\t\t\tvar that = this;\n\n\t\t\tmodal.$element.on('show.modalmanager', targetIsSelf(function (e) {\n\n\t\t\t\tvar showModal = function(){\n\t\t\t\t\tmodal.isShown = true;\n\n\t\t\t\t\tvar transition = $.support.transition && modal.$element.hasClass('fade');\n\n\t\t\t\t\tthat.$element\n\t\t\t\t\t\t.toggleClass('modal-open', that.hasOpenModal())\n\t\t\t\t\t\t.toggleClass('page-overflow', $(window).height() < that.$element.height());\n\n\t\t\t\t\tmodal.$parent = modal.$element.parent();\n\n\t\t\t\t\tmodal.$container = that.createContainer(modal);\n\n\t\t\t\t\tmodal.$element.appendTo(modal.$container);\n\n\t\t\t\t\tthat.backdrop(modal, function () {\n\t\t\t\t\t\tmodal.$element.show();\n\n\t\t\t\t\t\tif (transition) {       \n\t\t\t\t\t\t\t//modal.$element[0].style.display = 'run-in';       \n\t\t\t\t\t\t\tmodal.$element[0].offsetWidth;\n\t\t\t\t\t\t\t//modal.$element.one($.support.transition.end, function () { modal.$element[0].style.display = 'block' });  \n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tmodal.layout();\n\n\t\t\t\t\t\tmodal.$element\n\t\t\t\t\t\t\t.addClass('in')\n\t\t\t\t\t\t\t.attr('aria-hidden', false);\n\n\t\t\t\t\t\tvar complete = function () {\n\t\t\t\t\t\t\tthat.setFocus();\n\t\t\t\t\t\t\tmodal.$element.trigger('shown');\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\ttransition ?\n\t\t\t\t\t\t\tmodal.$element.one($.support.transition.end, complete) :\n\t\t\t\t\t\t\tcomplete();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tmodal.options.replace ?\n\t\t\t\t\tthat.replace(showModal) :\n\t\t\t\t\tshowModal();\n\t\t\t}));\n\n\t\t\tmodal.$element.on('hidden.modalmanager', targetIsSelf(function (e) {\n\t\t\t\tthat.backdrop(modal);\n\t\t\t\t// handle the case when a modal may have been removed from the dom before this callback executes\n\t\t\t\tif (!modal.$element.parent().length) {\n\t\t\t\t\tthat.destroyModal(modal);\n\t\t\t\t} else if (modal.$backdrop){\n\t\t\t\t\tvar transition = $.support.transition && modal.$element.hasClass('fade');\n\n\t\t\t\t\t// trigger a relayout due to firebox's buggy transition end event \n\t\t\t\t\tif (transition) { modal.$element[0].offsetWidth; }\n\t\t\t\t\t$.support.transition && modal.$element.hasClass('fade') ?\n\t\t\t\t\t\tmodal.$backdrop.one($.support.transition.end, function () { modal.destroy(); }) :\n\t\t\t\t\t\tmodal.destroy();\n\t\t\t\t} else {\n\t\t\t\t\tmodal.destroy();\n\t\t\t\t}\n\n\t\t\t}));\n\n\t\t\tmodal.$element.on('destroyed.modalmanager', targetIsSelf(function (e) {\n\t\t\t\tthat.destroyModal(modal);\n\t\t\t}));\n\t\t},\n\n\t\tgetOpenModals: function () {\n\t\t\tvar openModals = [];\n\t\t\tfor (var i = 0; i < this.stack.length; i++){\n\t\t\t\tif (this.stack[i].isShown) openModals.push(this.stack[i]);\n\t\t\t}\n\n\t\t\treturn openModals;\n\t\t},\n\n\t\thasOpenModal: function () {\n\t\t\treturn this.getOpenModals().length > 0;\n\t\t},\n\n\t\tsetFocus: function () {\n\t\t\tvar topModal;\n\n\t\t\tfor (var i = 0; i < this.stack.length; i++){\n\t\t\t\tif (this.stack[i].isShown) topModal = this.stack[i];\n\t\t\t}\n\n\t\t\tif (!topModal) return;\n\n\t\t\ttopModal.focus();\n\t\t},\n\n\t\tdestroyModal: function (modal) {\n\t\t\tmodal.$element.off('.modalmanager');\n\t\t\tif (modal.$backdrop) this.removeBackdrop(modal);\n\t\t\tthis.stack.splice(this.getIndexOfModal(modal), 1);\n\n\t\t\tvar hasOpenModal = this.hasOpenModal();\n\n\t\t\tthis.$element.toggleClass('modal-open', hasOpenModal);\n\n\t\t\tif (!hasOpenModal){\n\t\t\t\tthis.$element.removeClass('page-overflow');\n\t\t\t}\n\n\t\t\tthis.removeContainer(modal);\n\n\t\t\tthis.setFocus();\n\t\t},\n\n\t\tgetModalAt: function (index) {\n\t\t\treturn this.stack[index];\n\t\t},\n\n\t\tgetIndexOfModal: function (modal) {\n\t\t\tfor (var i = 0; i < this.stack.length; i++){\n\t\t\t\tif (modal === this.stack[i]) return i;\n\t\t\t}\n\t\t},\n\n\t\treplace: function (callback) {\n\t\t\tvar topModal;\n\n\t\t\tfor (var i = 0; i < this.stack.length; i++){\n\t\t\t\tif (this.stack[i].isShown) topModal = this.stack[i];\n\t\t\t}\n\n\t\t\tif (topModal) {\n\t\t\t\tthis.$backdropHandle = topModal.$backdrop;\n\t\t\t\ttopModal.$backdrop = null;\n\n\t\t\t\tcallback && topModal.$element.one('hidden',\n\t\t\t\t\ttargetIsSelf( $.proxy(callback, this) ));\n\n\t\t\t\ttopModal.hide();\n\t\t\t} else if (callback) {\n\t\t\t\tcallback();\n\t\t\t}\n\t\t},\n\n\t\tremoveBackdrop: function (modal) {\n\t\t\tmodal.$backdrop.remove();\n\t\t\tmodal.$backdrop = null;\n\t\t},\n\n\t\tcreateBackdrop: function (animate, tmpl) {\n\t\t\tvar $backdrop;\n\n\t\t\tif (!this.$backdropHandle) {\n\t\t\t\t$backdrop = $(tmpl)\n\t\t\t\t\t.addClass(animate)\n\t\t\t\t\t.appendTo(this.$element);\n\t\t\t} else {\n\t\t\t\t$backdrop = this.$backdropHandle;\n\t\t\t\t$backdrop.off('.modalmanager');\n\t\t\t\tthis.$backdropHandle = null;\n\t\t\t\tthis.isLoading && this.removeSpinner();\n\t\t\t}\n\n\t\t\treturn $backdrop;\n\t\t},\n\n\t\tremoveContainer: function (modal) {\n\t\t\tmodal.$container.remove();\n\t\t\tmodal.$container = null;\n\t\t},\n\n\t\tcreateContainer: function (modal) {\n\t\t\tvar $container;\n\n\t\t\t$container = $('<div class=\"modal-scrollable\">')\n\t\t\t\t.css('z-index', getzIndex('modal', this.getOpenModals().length))\n\t\t\t\t.appendTo(this.$element);\n\n\t\t\tif (modal && modal.options.backdrop != 'static') {\n\t\t\t\t$container.on('click.modal', targetIsSelf(function (e) {\n\t\t\t\t\tmodal.hide();\n\t\t\t\t}));\n\t\t\t} else if (modal) {\n\t\t\t\t$container.on('click.modal', targetIsSelf(function (e) {\n\t\t\t\t\tmodal.attention();\n\t\t\t\t}));\n\t\t\t}\n\n\t\t\treturn $container;\n\n\t\t},\n\n\t\tbackdrop: function (modal, callback) {\n\t\t\tvar animate = modal.$element.hasClass('fade') ? 'fade' : '',\n\t\t\t\tshowBackdrop = modal.options.backdrop &&\n\t\t\t\t\tthis.backdropCount < this.options.backdropLimit;\n\n\t\t\tif (modal.isShown && showBackdrop) {\n\t\t\t\tvar doAnimate = $.support.transition && animate && !this.$backdropHandle;\n\n\t\t\t\tmodal.$backdrop = this.createBackdrop(animate, modal.options.backdropTemplate);\n\n\t\t\t\tmodal.$backdrop.css('z-index', getzIndex( 'backdrop', this.getOpenModals().length ));\n\n\t\t\t\tif (doAnimate) modal.$backdrop[0].offsetWidth; // force reflow\n\n\t\t\t\tmodal.$backdrop.addClass('in');\n\n\t\t\t\tthis.backdropCount += 1;\n\n\t\t\t\tdoAnimate ?\n\t\t\t\t\tmodal.$backdrop.one($.support.transition.end, callback) :\n\t\t\t\t\tcallback();\n\n\t\t\t} else if (!modal.isShown && modal.$backdrop) {\n\t\t\t\tmodal.$backdrop.removeClass('in');\n\n\t\t\t\tthis.backdropCount -= 1;\n\n\t\t\t\tvar that = this;\n\n\t\t\t\t$.support.transition && modal.$element.hasClass('fade')?\n\t\t\t\t\tmodal.$backdrop.one($.support.transition.end, function () { that.removeBackdrop(modal) }) :\n\t\t\t\t\tthat.removeBackdrop(modal);\n\n\t\t\t} else if (callback) {\n\t\t\t\tcallback();\n\t\t\t}\n\t\t},\n\n\t\tremoveSpinner: function(){\n\t\t\tthis.$spinner && this.$spinner.remove();\n\t\t\tthis.$spinner = null;\n\t\t\tthis.isLoading = false;\n\t\t},\n\n\t\tremoveLoading: function () {\n\t\t\tthis.$backdropHandle && this.$backdropHandle.remove();\n\t\t\tthis.$backdropHandle = null;\n\t\t\tthis.removeSpinner();\n\t\t},\n\n\t\tloading: function (callback) {\n\t\t\tcallback = callback || function () { };\n\n\t\t\tthis.$element\n\t\t\t\t.toggleClass('modal-open', !this.isLoading || this.hasOpenModal())\n\t\t\t\t.toggleClass('page-overflow', $(window).height() < this.$element.height());\n\n\t\t\tif (!this.isLoading) {\n\n\t\t\t\tthis.$backdropHandle = this.createBackdrop('fade', this.options.backdropTemplate);\n\n\t\t\t\tthis.$backdropHandle[0].offsetWidth; // force reflow\n\n\t\t\t\tvar openModals = this.getOpenModals();\n\n\t\t\t\tthis.$backdropHandle\n\t\t\t\t\t.css('z-index', getzIndex('backdrop', openModals.length + 1))\n\t\t\t\t\t.addClass('in');\n\n\t\t\t\tvar $spinner = $(this.options.spinner)\n\t\t\t\t\t.css('z-index', getzIndex('modal', openModals.length + 1))\n\t\t\t\t\t.appendTo(this.$element)\n\t\t\t\t\t.addClass('in');\n\n\t\t\t\tthis.$spinner = $(this.createContainer())\n\t\t\t\t\t.append($spinner)\n\t\t\t\t\t.on('click.modalmanager', $.proxy(this.loading, this));\n\n\t\t\t\tthis.isLoading = true;\n\n\t\t\t\t$.support.transition ?\n\t\t\t\t\tthis.$backdropHandle.one($.support.transition.end, callback) :\n\t\t\t\t\tcallback();\n\n\t\t\t} else if (this.isLoading && this.$backdropHandle) {\n\t\t\t\tthis.$backdropHandle.removeClass('in');\n\n\t\t\t\tvar that = this;\n\t\t\t\t$.support.transition ?\n\t\t\t\t\tthis.$backdropHandle.one($.support.transition.end, function () { that.removeLoading() }) :\n\t\t\t\t\tthat.removeLoading();\n\n\t\t\t} else if (callback) {\n\t\t\t\tcallback(this.isLoading);\n\t\t\t}\n\t\t}\n\t};\n\n\t/* PRIVATE METHODS\n\t* ======================= */\n\n\t// computes and caches the zindexes\n\tvar getzIndex = (function () {\n\t\tvar zIndexFactor,\n\t\t\tbaseIndex = {};\n\n\t\treturn function (type, pos) {\n\n\t\t\tif (typeof zIndexFactor === 'undefined'){\n\t\t\t\tvar $baseModal = $('<div class=\"modal hide\" />').appendTo('body'),\n\t\t\t\t\t$baseBackdrop = $('<div class=\"modal-backdrop hide\" />').appendTo('body');\n\n\t\t\t\tbaseIndex['modal'] = +$baseModal.css('z-index');\n\t\t\t\tbaseIndex['backdrop'] = +$baseBackdrop.css('z-index');\n\t\t\t\tzIndexFactor = baseIndex['modal'] - baseIndex['backdrop'];\n\n\t\t\t\t$baseModal.remove();\n\t\t\t\t$baseBackdrop.remove();\n\t\t\t\t$baseBackdrop = $baseModal = null;\n\t\t\t}\n\n\t\t\treturn baseIndex[type] + (zIndexFactor * pos);\n\n\t\t}\n\t}());\n\n\t// make sure the event target is the modal itself in order to prevent\n\t// other components such as tabsfrom triggering the modal manager.\n\t// if Boostsrap namespaced events, this would not be needed.\n\tfunction targetIsSelf(callback){\n\t\treturn function (e) {\n\t\t\tif (e && this === e.target){\n\t\t\t\treturn callback.apply(this, arguments);\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/* MODAL MANAGER PLUGIN DEFINITION\n\t* ======================= */\n\n\t$.fn.modalmanager = function (option, args) {\n\t\treturn this.each(function () {\n\t\t\tvar $this = $(this),\n\t\t\t\tdata = $this.data('modalmanager');\n\n\t\t\tif (!data) $this.data('modalmanager', (data = new ModalManager(this, option)));\n\t\t\tif (typeof option === 'string') data[option].apply(data, [].concat(args))\n\t\t})\n\t};\n\n\t$.fn.modalmanager.defaults = {\n\t\tbackdropLimit: 999,\n\t\tresize: true,\n\t\tspinner: '<div class=\"loading-spinner fade\" style=\"width: 200px; margin-left: -100px;\"><div class=\"progress progress-striped active\"><div class=\"bar\" style=\"width: 100%;\"></div></div></div>',\n\t\tbackdropTemplate: '<div class=\"modal-backdrop\" />'\n\t};\n\n\t$.fn.modalmanager.Constructor = ModalManager\n\n\t// ModalManager handles the modal-open class so we need \n\t// to remove conflicting bootstrap 3 event handlers\n\t$(function () {\n\t\t$(document).off('show.bs.modal').off('hidden.bs.modal');\n\t});\n\n}(jQuery);\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/headroom.js",
    "content": "/*!\n * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it\n * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js\n * License: MIT\n */\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = global || self, global.Headroom = factory());\n}(this, function () { 'use strict';\n\n  function isBrowser() {\n    return typeof window !== \"undefined\";\n  }\n\n  /**\n   * Used to detect browser support for adding an event listener with options\n   * Credit: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener\n   */\n  function passiveEventsSupported() {\n    var supported = false;\n\n    try {\n      var options = {\n        // eslint-disable-next-line getter-return\n        get passive() {\n          supported = true;\n        }\n      };\n      window.addEventListener(\"test\", options, options);\n      window.removeEventListener(\"test\", options, options);\n    } catch (err) {\n      supported = false;\n    }\n\n    return supported;\n  }\n\n  function isSupported() {\n    return !!(\n      isBrowser() &&\n      function() {}.bind &&\n      \"classList\" in document.documentElement &&\n      Object.assign &&\n      Object.keys &&\n      requestAnimationFrame\n    );\n  }\n\n  function isDocument(obj) {\n    return obj.nodeType === 9; // Node.DOCUMENT_NODE === 9\n  }\n\n  function isWindow(obj) {\n    // `obj === window` or `obj instanceof Window` is not sufficient,\n    // as the obj may be the window of an iframe.\n    return obj && obj.document && isDocument(obj.document);\n  }\n\n  function windowScroller(win) {\n    var doc = win.document;\n    var body = doc.body;\n    var html = doc.documentElement;\n\n    return {\n      /**\n       * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/\n       * @return {Number} the scroll height of the document in pixels\n       */\n      scrollHeight: function() {\n        return Math.max(\n          body.scrollHeight,\n          html.scrollHeight,\n          body.offsetHeight,\n          html.offsetHeight,\n          body.clientHeight,\n          html.clientHeight\n        );\n      },\n\n      /**\n       * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript\n       * @return {Number} the height of the viewport in pixels\n       */\n      height: function() {\n        return win.innerHeight || html.clientHeight || body.clientHeight;\n      },\n\n      /**\n       * Gets the Y scroll position\n       * @return {Number} pixels the page has scrolled along the Y-axis\n       */\n      scrollY: function() {\n        if (win.pageYOffset !== undefined) {\n          return win.pageYOffset;\n        }\n\n        return (html || body.parentNode || body).scrollTop;\n      }\n    };\n  }\n\n  function elementScroller(element) {\n    return {\n      /**\n       * @return {Number} the scroll height of the element in pixels\n       */\n      scrollHeight: function() {\n        return Math.max(\n          element.scrollHeight,\n          element.offsetHeight,\n          element.clientHeight\n        );\n      },\n\n      /**\n       * @return {Number} the height of the element in pixels\n       */\n      height: function() {\n        return Math.max(element.offsetHeight, element.clientHeight);\n      },\n\n      /**\n       * Gets the Y scroll position\n       * @return {Number} pixels the element has scrolled along the Y-axis\n       */\n      scrollY: function() {\n        return element.scrollTop;\n      }\n    };\n  }\n\n  function createScroller(element) {\n    return isWindow(element) ? windowScroller(element) : elementScroller(element);\n  }\n\n  /**\n   * @param element EventTarget\n   */\n  function trackScroll(element, options, callback) {\n    var isPassiveSupported = passiveEventsSupported();\n    var rafId;\n    var scrolled = false;\n    var scroller = createScroller(element);\n    var lastScrollY = scroller.scrollY();\n    var details = {};\n\n    function update() {\n      var scrollY = Math.round(scroller.scrollY());\n      var height = scroller.height();\n      var scrollHeight = scroller.scrollHeight();\n\n      // reuse object for less memory churn\n      details.scrollY = scrollY;\n      details.lastScrollY = lastScrollY;\n      details.direction = scrollY > lastScrollY ? \"down\" : \"up\";\n      details.distance = Math.abs(scrollY - lastScrollY);\n      details.isOutOfBounds = scrollY < 0 || scrollY + height > scrollHeight;\n      details.top = scrollY <= options.offset[details.direction];\n      details.bottom = scrollY + height >= scrollHeight;\n      details.toleranceExceeded =\n        details.distance > options.tolerance[details.direction];\n\n      callback(details);\n\n      lastScrollY = scrollY;\n      scrolled = false;\n    }\n\n    function handleScroll() {\n      if (!scrolled) {\n        scrolled = true;\n        rafId = requestAnimationFrame(update);\n      }\n    }\n\n    var eventOptions = isPassiveSupported\n      ? { passive: true, capture: false }\n      : false;\n\n    element.addEventListener(\"scroll\", handleScroll, eventOptions);\n    update();\n\n    return {\n      destroy: function() {\n        cancelAnimationFrame(rafId);\n        element.removeEventListener(\"scroll\", handleScroll, eventOptions);\n      }\n    };\n  }\n\n  function normalizeUpDown(t) {\n    return t === Object(t) ? t : { down: t, up: t };\n  }\n\n  /**\n   * UI enhancement for fixed headers.\n   * Hides header when scrolling down\n   * Shows header when scrolling up\n   * @constructor\n   * @param {DOMElement} elem the header element\n   * @param {Object} options options for the widget\n   */\n  function Headroom(elem, options) {\n    options = options || {};\n    Object.assign(this, Headroom.options, options);\n    this.classes = Object.assign({}, Headroom.options.classes, options.classes);\n\n    this.elem = elem;\n    this.tolerance = normalizeUpDown(this.tolerance);\n    this.offset = normalizeUpDown(this.offset);\n    this.initialised = false;\n    this.frozen = false;\n  }\n  Headroom.prototype = {\n    constructor: Headroom,\n\n    /**\n     * Start listening to scrolling\n     * @public\n     */\n    init: function() {\n      if (Headroom.cutsTheMustard && !this.initialised) {\n        this.addClass(\"initial\");\n        this.initialised = true;\n\n        // defer event registration to handle browser\n        // potentially restoring previous scroll position\n        setTimeout(\n          function(self) {\n            self.scrollTracker = trackScroll(\n              self.scroller,\n              { offset: self.offset, tolerance: self.tolerance },\n              self.update.bind(self)\n            );\n          },\n          100,\n          this\n        );\n      }\n\n      return this;\n    },\n\n    /**\n     * Destroy the widget, clearing up after itself\n     * @public\n     */\n    destroy: function() {\n      this.initialised = false;\n      Object.keys(this.classes).forEach(this.removeClass, this);\n      this.scrollTracker.destroy();\n    },\n\n    /**\n     * Unpin the element\n     * @public\n     */\n    unpin: function() {\n      if (this.hasClass(\"pinned\") || !this.hasClass(\"unpinned\")) {\n        this.addClass(\"unpinned\");\n        this.removeClass(\"pinned\");\n\n        if (this.onUnpin) {\n          this.onUnpin.call(this);\n        }\n      }\n    },\n\n    /**\n     * Pin the element\n     * @public\n     */\n    pin: function() {\n      if (this.hasClass(\"unpinned\")) {\n        this.addClass(\"pinned\");\n        this.removeClass(\"unpinned\");\n\n        if (this.onPin) {\n          this.onPin.call(this);\n        }\n      }\n    },\n\n    /**\n     * Freezes the current state of the widget\n     * @public\n     */\n    freeze: function() {\n      this.frozen = true;\n      this.addClass(\"frozen\");\n    },\n\n    /**\n     * Re-enables the default behaviour of the widget\n     * @public\n     */\n    unfreeze: function() {\n      this.frozen = false;\n      this.removeClass(\"frozen\");\n    },\n\n    top: function() {\n      if (!this.hasClass(\"top\")) {\n        this.addClass(\"top\");\n        this.removeClass(\"notTop\");\n\n        if (this.onTop) {\n          this.onTop.call(this);\n        }\n      }\n    },\n\n    notTop: function() {\n      if (!this.hasClass(\"notTop\")) {\n        this.addClass(\"notTop\");\n        this.removeClass(\"top\");\n\n        if (this.onNotTop) {\n          this.onNotTop.call(this);\n        }\n      }\n    },\n\n    bottom: function() {\n      if (!this.hasClass(\"bottom\")) {\n        this.addClass(\"bottom\");\n        this.removeClass(\"notBottom\");\n\n        if (this.onBottom) {\n          this.onBottom.call(this);\n        }\n      }\n    },\n\n    notBottom: function() {\n      if (!this.hasClass(\"notBottom\")) {\n        this.addClass(\"notBottom\");\n        this.removeClass(\"bottom\");\n\n        if (this.onNotBottom) {\n          this.onNotBottom.call(this);\n        }\n      }\n    },\n\n    shouldUnpin: function(details) {\n      var scrollingDown = details.direction === \"down\";\n\n      return scrollingDown && !details.top && details.toleranceExceeded;\n    },\n\n    shouldPin: function(details) {\n      var scrollingUp = details.direction === \"up\";\n\n      return (scrollingUp && details.toleranceExceeded) || details.top;\n    },\n\n    addClass: function(className) {\n      this.elem.classList.add.apply(\n        this.elem.classList,\n        this.classes[className].split(\" \")\n      );\n    },\n\n    removeClass: function(className) {\n      this.elem.classList.remove.apply(\n        this.elem.classList,\n        this.classes[className].split(\" \")\n      );\n    },\n\n    hasClass: function(className) {\n      return this.classes[className].split(\" \").every(function(cls) {\n        return this.classList.contains(cls);\n      }, this.elem);\n    },\n\n    update: function(details) {\n      if (details.isOutOfBounds) {\n        // Ignore bouncy scrolling in OSX\n        return;\n      }\n\n      if (this.frozen === true) {\n        return;\n      }\n\n      if (details.top) {\n        this.top();\n      } else {\n        this.notTop();\n      }\n\n      if (details.bottom) {\n        this.bottom();\n      } else {\n        this.notBottom();\n      }\n\n      if (this.shouldUnpin(details)) {\n        this.unpin();\n      } else if (this.shouldPin(details)) {\n        this.pin();\n      }\n    }\n  };\n\n  /**\n   * Default options\n   * @type {Object}\n   */\n  Headroom.options = {\n    tolerance: {\n      up: 0,\n      down: 0\n    },\n    offset: 0,\n    scroller: isBrowser() ? window : null,\n    classes: {\n      frozen: \"headroom--frozen\",\n      pinned: \"headroom--pinned\",\n      unpinned: \"headroom--unpinned\",\n      top: \"headroom--top\",\n      notTop: \"headroom--not-top\",\n      bottom: \"headroom--bottom\",\n      notBottom: \"headroom--not-bottom\",\n      initial: \"headroom\"\n    }\n  };\n\n  Headroom.cutsTheMustard = isSupported();\n\n  return Headroom;\n\n}));\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/jquery-3.5.1.js",
    "content": "/*!\n * jQuery JavaScript Library v3.5.1\n * https://jquery.com/\n *\n * Includes Sizzle.js\n * https://sizzlejs.com/\n *\n * Copyright JS Foundation and other contributors\n * Released under the MIT license\n * https://jquery.org/license\n *\n * Date: 2020-05-04T22:49Z\n */\n( function( global, factory ) {\n\n\t\"use strict\";\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\n\t\t// For CommonJS and CommonJS-like environments where a proper `window`\n\t\t// is present, execute the factory and get jQuery.\n\t\t// For environments that do not have a `window` with a `document`\n\t\t// (such as Node.js), expose a factory as module.exports.\n\t\t// This accentuates the need for the creation of a real `window`.\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket #14549 for more info.\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n} )( typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1\n// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode\n// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common\n// enough that all such attempts are guarded in a try block.\n\"use strict\";\n\nvar arr = [];\n\nvar getProto = Object.getPrototypeOf;\n\nvar slice = arr.slice;\n\nvar flat = arr.flat ? function( array ) {\n\treturn arr.flat.call( array );\n} : function( array ) {\n\treturn arr.concat.apply( [], array );\n};\n\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar fnToString = hasOwn.toString;\n\nvar ObjectFunctionString = fnToString.call( Object );\n\nvar support = {};\n\nvar isFunction = function isFunction( obj ) {\n\n      // Support: Chrome <=57, Firefox <=52\n      // In some browsers, typeof returns \"function\" for HTML <object> elements\n      // (i.e., `typeof document.createElement( \"object\" ) === \"function\"`).\n      // We don't want to classify *any* DOM node as a function.\n      return typeof obj === \"function\" && typeof obj.nodeType !== \"number\";\n  };\n\n\nvar isWindow = function isWindow( obj ) {\n\t\treturn obj != null && obj === obj.window;\n\t};\n\n\nvar document = window.document;\n\n\n\n\tvar preservedScriptAttributes = {\n\t\ttype: true,\n\t\tsrc: true,\n\t\tnonce: true,\n\t\tnoModule: true\n\t};\n\n\tfunction DOMEval( code, node, doc ) {\n\t\tdoc = doc || document;\n\n\t\tvar i, val,\n\t\t\tscript = doc.createElement( \"script\" );\n\n\t\tscript.text = code;\n\t\tif ( node ) {\n\t\t\tfor ( i in preservedScriptAttributes ) {\n\n\t\t\t\t// Support: Firefox 64+, Edge 18+\n\t\t\t\t// Some browsers don't support the \"nonce\" property on scripts.\n\t\t\t\t// On the other hand, just using `getAttribute` is not enough as\n\t\t\t\t// the `nonce` attribute is reset to an empty string whenever it\n\t\t\t\t// becomes browsing-context connected.\n\t\t\t\t// See https://github.com/whatwg/html/issues/2369\n\t\t\t\t// See https://html.spec.whatwg.org/#nonce-attributes\n\t\t\t\t// The `node.getAttribute` check was added for the sake of\n\t\t\t\t// `jQuery.globalEval` so that it can fake a nonce-containing node\n\t\t\t\t// via an object.\n\t\t\t\tval = node[ i ] || node.getAttribute && node.getAttribute( i );\n\t\t\t\tif ( val ) {\n\t\t\t\t\tscript.setAttribute( i, val );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdoc.head.appendChild( script ).parentNode.removeChild( script );\n\t}\n\n\nfunction toType( obj ) {\n\tif ( obj == null ) {\n\t\treturn obj + \"\";\n\t}\n\n\t// Support: Android <=2.3 only (functionish RegExp)\n\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\tclass2type[ toString.call( obj ) ] || \"object\" :\n\t\ttypeof obj;\n}\n/* global Symbol */\n// Defining this global in .eslintrc.json would create a danger of using the global\n// unguarded in another place, it seems safer to define global only for this module\n\n\n\nvar\n\tversion = \"3.5.1\",\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init( selector, context );\n\t};\n\njQuery.fn = jQuery.prototype = {\n\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\n\t\t// Return all the elements in a clean array\n\t\tif ( num == null ) {\n\t\t\treturn slice.call( this );\n\t\t}\n\n\t\t// Return just the one element from the set\n\t\treturn num < 0 ? this[ num + this.length ] : this[ num ];\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\teach: function( callback ) {\n\t\treturn jQuery.each( this, callback );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map( this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t} ) );\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teven: function() {\n\t\treturn this.pushStack( jQuery.grep( this, function( _elem, i ) {\n\t\t\treturn ( i + 1 ) % 2;\n\t\t} ) );\n\t},\n\n\todd: function() {\n\t\treturn this.pushStack( jQuery.grep( this, function( _elem, i ) {\n\t\t\treturn i % 2;\n\t\t} ) );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor();\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[ 0 ] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\n\t\t// Skip the boolean and the target\n\t\ttarget = arguments[ i ] || {};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !isFunction( target ) ) {\n\t\ttarget = {};\n\t}\n\n\t// Extend jQuery itself if only one argument is passed\n\tif ( i === length ) {\n\t\ttarget = this;\n\t\ti--;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\n\t\t// Only deal with non-null/undefined values\n\t\tif ( ( options = arguments[ i ] ) != null ) {\n\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent Object.prototype pollution\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( name === \"__proto__\" || target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject( copy ) ||\n\t\t\t\t\t( copyIsArray = Array.isArray( copy ) ) ) ) {\n\t\t\t\t\tsrc = target[ name ];\n\n\t\t\t\t\t// Ensure proper type for the source value\n\t\t\t\t\tif ( copyIsArray && !Array.isArray( src ) ) {\n\t\t\t\t\t\tclone = [];\n\t\t\t\t\t} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {\n\t\t\t\t\t\tclone = {};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src;\n\t\t\t\t\t}\n\t\t\t\t\tcopyIsArray = false;\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend( {\n\n\t// Unique for each copy of jQuery on the page\n\texpando: \"jQuery\" + ( version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\tnoop: function() {},\n\n\tisPlainObject: function( obj ) {\n\t\tvar proto, Ctor;\n\n\t\t// Detect obvious negatives\n\t\t// Use toString instead of jQuery.type to catch host objects\n\t\tif ( !obj || toString.call( obj ) !== \"[object Object]\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tproto = getProto( obj );\n\n\t\t// Objects with no prototype (e.g., `Object.create( null )`) are plain\n\t\tif ( !proto ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Objects with prototype are plain iff they were constructed by a global Object function\n\t\tCtor = hasOwn.call( proto, \"constructor\" ) && proto.constructor;\n\t\treturn typeof Ctor === \"function\" && fnToString.call( Ctor ) === ObjectFunctionString;\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\t// Evaluates a script in a provided context; falls back to the global one\n\t// if not specified.\n\tglobalEval: function( code, options, doc ) {\n\t\tDOMEval( code, { nonce: options && options.nonce }, doc );\n\t},\n\n\teach: function( obj, callback ) {\n\t\tvar length, i = 0;\n\n\t\tif ( isArrayLike( obj ) ) {\n\t\t\tlength = obj.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor ( i in obj ) {\n\t\t\t\tif ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArrayLike( Object( arr ) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpush.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\treturn arr == null ? -1 : indexOf.call( arr, elem, i );\n\t},\n\n\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t// push.apply(_, arraylike) throws on ancient WebKit\n\tmerge: function( first, second ) {\n\t\tvar len = +second.length,\n\t\t\tj = 0,\n\t\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, invert ) {\n\t\tvar callbackInverse,\n\t\t\tmatches = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tcallbackInverse = !callback( elems[ i ], i );\n\t\t\tif ( callbackInverse !== callbackExpect ) {\n\t\t\t\tmatches.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar length, value,\n\t\t\ti = 0,\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif ( isArrayLike( elems ) ) {\n\t\t\tlength = elems.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn flat( ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n} );\n\nif ( typeof Symbol === \"function\" ) {\n\tjQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];\n}\n\n// Populate the class2type map\njQuery.each( \"Boolean Number String Function Array Date RegExp Object Error Symbol\".split( \" \" ),\nfunction( _i, name ) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n} );\n\nfunction isArrayLike( obj ) {\n\n\t// Support: real iOS 8.2 only (not reproducible in simulator)\n\t// `in` check used to prevent JIT error (gh-2145)\n\t// hasOwn isn't used here due to false negatives\n\t// regarding Nodelist length in IE\n\tvar length = !!obj && \"length\" in obj && obj.length,\n\t\ttype = toType( obj );\n\n\tif ( isFunction( obj ) || isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\treturn type === \"array\" || length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n}\nvar Sizzle =\n/*!\n * Sizzle CSS Selector Engine v2.3.5\n * https://sizzlejs.com/\n *\n * Copyright JS Foundation and other contributors\n * Released under the MIT license\n * https://js.foundation/\n *\n * Date: 2020-03-14\n */\n( function( window ) {\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\ttokenize,\n\tcompile,\n\tselect,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + 1 * new Date(),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tnonnativeSelectorCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// Instance methods\n\thasOwn = ( {} ).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpushNative = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\n\t// Use a stripped-down indexOf as it's faster than native\n\t// https://jsperf.com/thor-indexof-vs-for/5\n\tindexOf = function( list, elem ) {\n\t\tvar i = 0,\n\t\t\tlen = list.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( list[ i ] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|\" +\n\t\t\"ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\n\t// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram\n\tidentifier = \"(?:\\\\\\\\[\\\\da-fA-F]{1,6}\" + whitespace +\n\t\t\"?|\\\\\\\\[^\\\\r\\\\n\\\\f]|[\\\\w-]|[^\\0-\\\\x7f])+\",\n\n\t// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + identifier + \")(?:\" + whitespace +\n\n\t\t// Operator (capture 2)\n\t\t\"*([*^$|!~]?=)\" + whitespace +\n\n\t\t// \"Attribute values must be CSS identifiers [capture 5]\n\t\t// or strings [capture 3 or capture 4]\"\n\t\t\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\" + identifier + \"))|)\" +\n\t\twhitespace + \"*\\\\]\",\n\n\tpseudos = \":(\" + identifier + \")(?:\\\\((\" +\n\n\t\t// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:\n\t\t// 1. quoted (capture 3; capture 4 or capture 5)\n\t\t\"('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|\" +\n\n\t\t// 2. simple (capture 6)\n\t\t\"((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes + \")*)|\" +\n\n\t\t// 3. anything else (capture 2)\n\t\t\".*\" +\n\t\t\")\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trwhitespace = new RegExp( whitespace + \"+\", \"g\" ),\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" +\n\t\twhitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace +\n\t\t\"*\" ),\n\trdescend = new RegExp( whitespace + \"|>\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + identifier + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + identifier + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + identifier + \"|[*])\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" +\n\t\t\twhitespace + \"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" +\n\t\t\twhitespace + \"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace +\n\t\t\t\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" + whitespace +\n\t\t\t\"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trhtml = /HTML$/i,\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\n\t// CSS escapes\n\t// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\[\\\\da-fA-F]{1,6}\" + whitespace + \"?|\\\\\\\\([^\\\\r\\\\n\\\\f])\", \"g\" ),\n\tfunescape = function( escape, nonHex ) {\n\t\tvar high = \"0x\" + escape.slice( 1 ) - 0x10000;\n\n\t\treturn nonHex ?\n\n\t\t\t// Strip the backslash prefix from a non-hex escape sequence\n\t\t\tnonHex :\n\n\t\t\t// Replace a hexadecimal escape sequence with the encoded Unicode code point\n\t\t\t// Support: IE <=11+\n\t\t\t// For values outside the Basic Multilingual Plane (BMP), manually construct a\n\t\t\t// surrogate pair\n\t\t\thigh < 0 ?\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t},\n\n\t// CSS string/identifier serialization\n\t// https://drafts.csswg.org/cssom/#common-serializing-idioms\n\trcssescape = /([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,\n\tfcssescape = function( ch, asCodePoint ) {\n\t\tif ( asCodePoint ) {\n\n\t\t\t// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER\n\t\t\tif ( ch === \"\\0\" ) {\n\t\t\t\treturn \"\\uFFFD\";\n\t\t\t}\n\n\t\t\t// Control characters and (dependent upon position) numbers get escaped as code points\n\t\t\treturn ch.slice( 0, -1 ) + \"\\\\\" +\n\t\t\t\tch.charCodeAt( ch.length - 1 ).toString( 16 ) + \" \";\n\t\t}\n\n\t\t// Other potentially-special ASCII characters get backslash-escaped\n\t\treturn \"\\\\\" + ch;\n\t},\n\n\t// Used for iframes\n\t// See setDocument()\n\t// Removing the function wrapper causes a \"Permission Denied\"\n\t// error in IE\n\tunloadHandler = function() {\n\t\tsetDocument();\n\t},\n\n\tinDisabledFieldset = addCombinator(\n\t\tfunction( elem ) {\n\t\t\treturn elem.disabled === true && elem.nodeName.toLowerCase() === \"fieldset\";\n\t\t},\n\t\t{ dir: \"parentNode\", next: \"legend\" }\n\t);\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t( arr = slice.call( preferredDoc.childNodes ) ),\n\t\tpreferredDoc.childNodes\n\t);\n\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\t// eslint-disable-next-line no-unused-expressions\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpushNative.apply( target, slice.call( els ) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( ( target[ j++ ] = els[ i++ ] ) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar m, i, elem, nid, match, groups, newSelector,\n\t\tnewContext = context && context.ownerDocument,\n\n\t\t// nodeType defaults to 9, since context defaults to document\n\t\tnodeType = context ? context.nodeType : 9;\n\n\tresults = results || [];\n\n\t// Return early from calls with invalid selector or context\n\tif ( typeof selector !== \"string\" || !selector ||\n\t\tnodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {\n\n\t\treturn results;\n\t}\n\n\t// Try to shortcut find operations (as opposed to filters) in HTML documents\n\tif ( !seed ) {\n\t\tsetDocument( context );\n\t\tcontext = context || document;\n\n\t\tif ( documentIsHTML ) {\n\n\t\t\t// If the selector is sufficiently simple, try using a \"get*By*\" DOM method\n\t\t\t// (excepting DocumentFragment context, where the methods don't exist)\n\t\t\tif ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {\n\n\t\t\t\t// ID selector\n\t\t\t\tif ( ( m = match[ 1 ] ) ) {\n\n\t\t\t\t\t// Document context\n\t\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\t\tif ( ( elem = context.getElementById( m ) ) ) {\n\n\t\t\t\t\t\t\t// Support: IE, Opera, Webkit\n\t\t\t\t\t\t\t// TODO: identify versions\n\t\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// Element context\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t// Support: IE, Opera, Webkit\n\t\t\t\t\t\t// TODO: identify versions\n\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\tif ( newContext && ( elem = newContext.getElementById( m ) ) &&\n\t\t\t\t\t\t\tcontains( context, elem ) &&\n\t\t\t\t\t\t\telem.id === m ) {\n\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t// Type selector\n\t\t\t\t} else if ( match[ 2 ] ) {\n\t\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\t\treturn results;\n\n\t\t\t\t// Class selector\n\t\t\t\t} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&\n\t\t\t\t\tcontext.getElementsByClassName ) {\n\n\t\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Take advantage of querySelectorAll\n\t\t\tif ( support.qsa &&\n\t\t\t\t!nonnativeSelectorCache[ selector + \" \" ] &&\n\t\t\t\t( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&\n\n\t\t\t\t// Support: IE 8 only\n\t\t\t\t// Exclude object elements\n\t\t\t\t( nodeType !== 1 || context.nodeName.toLowerCase() !== \"object\" ) ) {\n\n\t\t\t\tnewSelector = selector;\n\t\t\t\tnewContext = context;\n\n\t\t\t\t// qSA considers elements outside a scoping root when evaluating child or\n\t\t\t\t// descendant combinators, which is not what we want.\n\t\t\t\t// In such cases, we work around the behavior by prefixing every selector in the\n\t\t\t\t// list with an ID selector referencing the scope context.\n\t\t\t\t// The technique has to be used as well when a leading combinator is used\n\t\t\t\t// as such selectors are not recognized by querySelectorAll.\n\t\t\t\t// Thanks to Andrew Dupont for this technique.\n\t\t\t\tif ( nodeType === 1 &&\n\t\t\t\t\t( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {\n\n\t\t\t\t\t// Expand context for sibling selectors\n\t\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) ||\n\t\t\t\t\t\tcontext;\n\n\t\t\t\t\t// We can use :scope instead of the ID hack if the browser\n\t\t\t\t\t// supports it & if we're not changing the context.\n\t\t\t\t\tif ( newContext !== context || !support.scope ) {\n\n\t\t\t\t\t\t// Capture the context ID, setting it first if necessary\n\t\t\t\t\t\tif ( ( nid = context.getAttribute( \"id\" ) ) ) {\n\t\t\t\t\t\t\tnid = nid.replace( rcssescape, fcssescape );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontext.setAttribute( \"id\", ( nid = expando ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prefix every selector in the list\n\t\t\t\t\tgroups = tokenize( selector );\n\t\t\t\t\ti = groups.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tgroups[ i ] = ( nid ? \"#\" + nid : \":scope\" ) + \" \" +\n\t\t\t\t\t\t\ttoSelector( groups[ i ] );\n\t\t\t\t\t}\n\t\t\t\t\tnewSelector = groups.join( \",\" );\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch ( qsaError ) {\n\t\t\t\t\tnonnativeSelectorCache( selector, true );\n\t\t\t\t} finally {\n\t\t\t\t\tif ( nid === expando ) {\n\t\t\t\t\t\tcontext.removeAttribute( \"id\" );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {function(string, object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn ( cache[ key + \" \" ] = value );\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created element and returns a boolean result\n */\nfunction assert( fn ) {\n\tvar el = document.createElement( \"fieldset\" );\n\n\ttry {\n\t\treturn !!fn( el );\n\t} catch ( e ) {\n\t\treturn false;\n\t} finally {\n\n\t\t// Remove from its parent by default\n\t\tif ( el.parentNode ) {\n\t\t\tel.parentNode.removeChild( el );\n\t\t}\n\n\t\t// release memory in IE\n\t\tel = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split( \"|\" ),\n\t\ti = arr.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[ i ] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\ta.sourceIndex - b.sourceIndex;\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( ( cur = cur.nextSibling ) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn ( name === \"input\" || name === \"button\" ) && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for :enabled/:disabled\n * @param {Boolean} disabled true for :disabled; false for :enabled\n */\nfunction createDisabledPseudo( disabled ) {\n\n\t// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable\n\treturn function( elem ) {\n\n\t\t// Only certain elements can match :enabled or :disabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled\n\t\tif ( \"form\" in elem ) {\n\n\t\t\t// Check for inherited disabledness on relevant non-disabled elements:\n\t\t\t// * listed form-associated elements in a disabled fieldset\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#category-listed\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled\n\t\t\t// * option elements in a disabled optgroup\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled\n\t\t\t// All such elements have a \"form\" property.\n\t\t\tif ( elem.parentNode && elem.disabled === false ) {\n\n\t\t\t\t// Option elements defer to a parent optgroup if present\n\t\t\t\tif ( \"label\" in elem ) {\n\t\t\t\t\tif ( \"label\" in elem.parentNode ) {\n\t\t\t\t\t\treturn elem.parentNode.disabled === disabled;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn elem.disabled === disabled;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Support: IE 6 - 11\n\t\t\t\t// Use the isDisabled shortcut property to check for disabled fieldset ancestors\n\t\t\t\treturn elem.isDisabled === disabled ||\n\n\t\t\t\t\t// Where there is no isDisabled, check manually\n\t\t\t\t\t/* jshint -W018 */\n\t\t\t\t\telem.isDisabled !== !disabled &&\n\t\t\t\t\tinDisabledFieldset( elem ) === disabled;\n\t\t\t}\n\n\t\t\treturn elem.disabled === disabled;\n\n\t\t// Try to winnow out elements that can't be disabled before trusting the disabled property.\n\t\t// Some victims get caught in our net (label, legend, menu, track), but it shouldn't\n\t\t// even exist on them, let alone have a boolean value.\n\t\t} else if ( \"label\" in elem ) {\n\t\t\treturn elem.disabled === disabled;\n\t\t}\n\n\t\t// Remaining elements are neither :enabled nor :disabled\n\t\treturn false;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction( function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction( function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ ( j = matchIndexes[ i ] ) ] ) {\n\t\t\t\t\tseed[ j ] = !( matches[ j ] = seed[ j ] );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t} );\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== \"undefined\" && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\tvar namespace = elem.namespaceURI,\n\t\tdocElem = ( elem.ownerDocument || elem ).documentElement;\n\n\t// Support: IE <=8\n\t// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes\n\t// https://bugs.jquery.com/ticket/4833\n\treturn !rhtml.test( namespace || docElem && docElem.nodeName || \"HTML\" );\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare, subWindow,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc;\n\n\t// Return early if doc is invalid or already selected\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Update global variables\n\tdocument = doc;\n\tdocElem = document.documentElement;\n\tdocumentIsHTML = !isXML( document );\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t// Accessing iframe documents after unload throws \"permission denied\" errors (jQuery #13936)\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( preferredDoc != document &&\n\t\t( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {\n\n\t\t// Support: IE 11, Edge\n\t\tif ( subWindow.addEventListener ) {\n\t\t\tsubWindow.addEventListener( \"unload\", unloadHandler, false );\n\n\t\t// Support: IE 9 - 10 only\n\t\t} else if ( subWindow.attachEvent ) {\n\t\t\tsubWindow.attachEvent( \"onunload\", unloadHandler );\n\t\t}\n\t}\n\n\t// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,\n\t// Safari 4 - 5 only, Opera <=11.6 - 12.x only\n\t// IE/Edge & older browsers don't support the :scope pseudo-class.\n\t// Support: Safari 6.0 only\n\t// Safari 6.0 supports :scope but it's an alias of :root there.\n\tsupport.scope = assert( function( el ) {\n\t\tdocElem.appendChild( el ).appendChild( document.createElement( \"div\" ) );\n\t\treturn typeof el.querySelectorAll !== \"undefined\" &&\n\t\t\t!el.querySelectorAll( \":scope fieldset div\" ).length;\n\t} );\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties\n\t// (excepting IE8 booleans)\n\tsupport.attributes = assert( function( el ) {\n\t\tel.className = \"i\";\n\t\treturn !el.getAttribute( \"className\" );\n\t} );\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert( function( el ) {\n\t\tel.appendChild( document.createComment( \"\" ) );\n\t\treturn !el.getElementsByTagName( \"*\" ).length;\n\t} );\n\n\t// Support: IE<9\n\tsupport.getElementsByClassName = rnative.test( document.getElementsByClassName );\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programmatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert( function( el ) {\n\t\tdocElem.appendChild( el ).id = expando;\n\t\treturn !document.getElementsByName || !document.getElementsByName( expando ).length;\n\t} );\n\n\t// ID filter and find\n\tif ( support.getById ) {\n\t\tExpr.filter[ \"ID\" ] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute( \"id\" ) === attrId;\n\t\t\t};\n\t\t};\n\t\tExpr.find[ \"ID\" ] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar elem = context.getElementById( id );\n\t\t\t\treturn elem ? [ elem ] : [];\n\t\t\t}\n\t\t};\n\t} else {\n\t\tExpr.filter[ \"ID\" ] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== \"undefined\" &&\n\t\t\t\t\telem.getAttributeNode( \"id\" );\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\n\t\t// Support: IE 6 - 7 only\n\t\t// getElementById is not reliable as a find shortcut\n\t\tExpr.find[ \"ID\" ] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar node, i, elems,\n\t\t\t\t\telem = context.getElementById( id );\n\n\t\t\t\tif ( elem ) {\n\n\t\t\t\t\t// Verify the id attribute\n\t\t\t\t\tnode = elem.getAttributeNode( \"id\" );\n\t\t\t\t\tif ( node && node.value === id ) {\n\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fall back on getElementsByName\n\t\t\t\t\telems = context.getElementsByName( id );\n\t\t\t\t\ti = 0;\n\t\t\t\t\twhile ( ( elem = elems[ i++ ] ) ) {\n\t\t\t\t\t\tnode = elem.getAttributeNode( \"id\" );\n\t\t\t\t\t\tif ( node && node.value === id ) {\n\t\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn [];\n\t\t\t}\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[ \"TAG\" ] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\n\t\t\t// DocumentFragment nodes don't have gEBTN\n\t\t\t} else if ( support.qsa ) {\n\t\t\t\treturn context.querySelectorAll( tag );\n\t\t\t}\n\t\t} :\n\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\n\t\t\t\t// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( ( elem = results[ i++ ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[ \"CLASS\" ] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== \"undefined\" && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See https://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {\n\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert( function( el ) {\n\n\t\t\tvar input;\n\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// https://bugs.jquery.com/ticket/12359\n\t\t\tdocElem.appendChild( el ).innerHTML = \"<a id='\" + expando + \"'></a>\" +\n\t\t\t\t\"<select id='\" + expando + \"-\\r\\\\' msallowcapture=''>\" +\n\t\t\t\t\"<option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 11-12.16\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\t// The test attribute must be unknown in Opera but \"safe\" for WinRT\n\t\t\t// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section\n\t\t\tif ( el.querySelectorAll( \"[msallowcapture^='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !el.querySelectorAll( \"[selected]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+\n\t\t\tif ( !el.querySelectorAll( \"[id~=\" + expando + \"-]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"~=\" );\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 15 - 18+\n\t\t\t// IE 11/Edge don't find elements on a `[name='']` query in some cases.\n\t\t\t// Adding a temporary attribute to the document before the selection works\n\t\t\t// around the issue.\n\t\t\t// Interestingly, IE 10 & older don't seem to have the issue.\n\t\t\tinput = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"name\", \"\" );\n\t\t\tel.appendChild( input );\n\t\t\tif ( !el.querySelectorAll( \"[name='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*name\" + whitespace + \"*=\" +\n\t\t\t\t\twhitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !el.querySelectorAll( \":checked\" ).length ) {\n\t\t\t\trbuggyQSA.push( \":checked\" );\n\t\t\t}\n\n\t\t\t// Support: Safari 8+, iOS 8+\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t\t// In-page `selector#id sibling-combinator selector` fails\n\t\t\tif ( !el.querySelectorAll( \"a#\" + expando + \"+*\" ).length ) {\n\t\t\t\trbuggyQSA.push( \".#.+[+~]\" );\n\t\t\t}\n\n\t\t\t// Support: Firefox <=3.6 - 5 only\n\t\t\t// Old Firefox doesn't throw on a badly-escaped identifier.\n\t\t\tel.querySelectorAll( \"\\\\\\f\" );\n\t\t\trbuggyQSA.push( \"[\\\\r\\\\n\\\\f]\" );\n\t\t} );\n\n\t\tassert( function( el ) {\n\t\t\tel.innerHTML = \"<a href='' disabled='disabled'></a>\" +\n\t\t\t\t\"<select disabled='disabled'><option/></select>\";\n\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tel.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( el.querySelectorAll( \"[name=d]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( el.querySelectorAll( \":enabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: IE9-11+\n\t\t\t// IE's :disabled selector does not pick up the children of disabled fieldsets\n\t\t\tdocElem.appendChild( el ).disabled = true;\n\t\t\tif ( el.querySelectorAll( \":disabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: Opera 10 - 11 only\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tel.querySelectorAll( \"*,:x\" );\n\t\t\trbuggyQSA.push( \",.*:\" );\n\t\t} );\n\t}\n\n\tif ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||\n\t\tdocElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector ) ) ) ) {\n\n\t\tassert( function( el ) {\n\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( el, \"*\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( el, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t} );\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( \"|\" ) );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( \"|\" ) );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully self-exclusive\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t) );\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( ( b = b.parentNode ) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t// two documents; shallow comparisons work.\n\t\t// eslint-disable-next-line eqeqeq\n\t\tcompare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif ( a == document || a.ownerDocument == preferredDoc &&\n\t\t\t\tcontains( preferredDoc, a ) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif ( b == document || b.ownerDocument == preferredDoc &&\n\t\t\t\tcontains( preferredDoc, b ) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t/* eslint-disable eqeqeq */\n\t\t\treturn a == document ? -1 :\n\t\t\t\tb == document ? 1 :\n\t\t\t\t/* eslint-enable eqeqeq */\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( ( cur = cur.parentNode ) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( ( cur = cur.parentNode ) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[ i ] === bp[ i ] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[ i ], bp[ i ] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t/* eslint-disable eqeqeq */\n\t\t\tap[ i ] == preferredDoc ? -1 :\n\t\t\tbp[ i ] == preferredDoc ? 1 :\n\t\t\t/* eslint-enable eqeqeq */\n\t\t\t0;\n\t};\n\n\treturn document;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\tsetDocument( elem );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t!nonnativeSelectorCache[ expr + \" \" ] &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\n\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t// fragment in IE 9\n\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\tnonnativeSelectorCache( expr, true );\n\t\t}\n\t}\n\n\treturn Sizzle( expr, document, null, [ elem ] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( ( context.ownerDocument || context ) != document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( ( elem.ownerDocument || elem ) != document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t( val = elem.getAttributeNode( name ) ) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.escape = function( sel ) {\n\treturn ( sel + \"\" ).replace( rcssescape, fcssescape );\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( ( elem = results[ i++ ] ) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( ( node = elem[ i++ ] ) ) {\n\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[ 1 ] = match[ 1 ].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[ 3 ] = ( match[ 3 ] || match[ 4 ] ||\n\t\t\t\tmatch[ 5 ] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[ 2 ] === \"~=\" ) {\n\t\t\t\tmatch[ 3 ] = \" \" + match[ 3 ] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[ 1 ] = match[ 1 ].toLowerCase();\n\n\t\t\tif ( match[ 1 ].slice( 0, 3 ) === \"nth\" ) {\n\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[ 3 ] ) {\n\t\t\t\t\tSizzle.error( match[ 0 ] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[ 4 ] = +( match[ 4 ] ?\n\t\t\t\t\tmatch[ 5 ] + ( match[ 6 ] || 1 ) :\n\t\t\t\t\t2 * ( match[ 3 ] === \"even\" || match[ 3 ] === \"odd\" ) );\n\t\t\t\tmatch[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === \"odd\" );\n\n\t\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[ 3 ] ) {\n\t\t\t\tSizzle.error( match[ 0 ] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[ 6 ] && match[ 2 ];\n\n\t\t\tif ( matchExpr[ \"CHILD\" ].test( match[ 0 ] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[ 3 ] ) {\n\t\t\t\tmatch[ 2 ] = match[ 4 ] || match[ 5 ] || \"\";\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t( excess = tokenize( unquoted, true ) ) &&\n\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t( excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length ) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[ 0 ] = match[ 0 ].slice( 0, excess );\n\t\t\t\tmatch[ 2 ] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() {\n\t\t\t\t\treturn true;\n\t\t\t\t} :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t( pattern = new RegExp( \"(^|\" + whitespace +\n\t\t\t\t\t\")\" + className + \"(\" + whitespace + \"|$)\" ) ) && classCache(\n\t\t\t\t\t\tclassName, function( elem ) {\n\t\t\t\t\t\t\treturn pattern.test(\n\t\t\t\t\t\t\t\ttypeof elem.className === \"string\" && elem.className ||\n\t\t\t\t\t\t\t\ttypeof elem.getAttribute !== \"undefined\" &&\n\t\t\t\t\t\t\t\t\telem.getAttribute( \"class\" ) ||\n\t\t\t\t\t\t\t\t\"\"\n\t\t\t\t\t\t\t);\n\t\t\t\t} );\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\t/* eslint-disable max-len */\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result.replace( rwhitespace, \" \" ) + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t\t/* eslint-enable max-len */\n\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, _argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, _context, xml ) {\n\t\t\t\t\tvar cache, uniqueCache, outerCache, node, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType,\n\t\t\t\t\t\tdiff = false;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( ( node = node[ dir ] ) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ?\n\t\t\t\t\t\t\t\t\t\tnode.nodeName.toLowerCase() === name :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType === 1 ) {\n\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\n\t\t\t\t\t\t\t// ...in a gzip-friendly way\n\t\t\t\t\t\t\tnode = parent;\n\t\t\t\t\t\t\touterCache = node[ expando ] || ( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\tcache = uniqueCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];\n\t\t\t\t\t\t\tdiff = nodeIndex && cache[ 2 ];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( ( node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t( diff = nodeIndex = 0 ) || start.pop() ) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\tuniqueCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t\tif ( useCache ) {\n\n\t\t\t\t\t\t\t\t// ...in a gzip-friendly way\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\touterCache = node[ expando ] || ( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\t\tcache = uniqueCache[ type ] || [];\n\t\t\t\t\t\t\t\tnodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];\n\t\t\t\t\t\t\t\tdiff = nodeIndex;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// xml :nth-child(...)\n\t\t\t\t\t\t\t// or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t\tif ( diff === false ) {\n\n\t\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\t\twhile ( ( node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t\t( diff = nodeIndex = 0 ) || start.pop() ) ) {\n\n\t\t\t\t\t\t\t\t\tif ( ( ofType ?\n\t\t\t\t\t\t\t\t\t\tnode.nodeName.toLowerCase() === name :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType === 1 ) &&\n\t\t\t\t\t\t\t\t\t\t++diff ) {\n\n\t\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t\touterCache = node[ expando ] ||\n\t\t\t\t\t\t\t\t\t\t\t\t( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\t\t\t\t\tuniqueCache[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction( function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf( seed, matched[ i ] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t} ) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction( function( selector ) {\n\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction( function( seed, matches, _context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( ( elem = unmatched[ i ] ) ) {\n\t\t\t\t\t\t\tseed[ i ] = !( matches[ i ] = elem );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} ) :\n\t\t\t\tfunction( elem, _context, xml ) {\n\t\t\t\t\tinput[ 0 ] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\n\t\t\t\t\t// Don't keep the element (issue #299)\n\t\t\t\t\tinput[ 0 ] = null;\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t} ),\n\n\t\t\"has\": markFunction( function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t} ),\n\n\t\t\"contains\": markFunction( function( text ) {\n\t\t\ttext = text.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t} ),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test( lang || \"\" ) ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( ( elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute( \"xml:lang\" ) || elem.getAttribute( \"lang\" ) ) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t} ),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement &&\n\t\t\t\t( !document.hasFocus || document.hasFocus() ) &&\n\t\t\t\t!!( elem.type || elem.href || ~elem.tabIndex );\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": createDisabledPseudo( false ),\n\t\t\"disabled\": createDisabledPseudo( true ),\n\n\t\t\"checked\": function( elem ) {\n\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn ( nodeName === \"input\" && !!elem.checked ) ||\n\t\t\t\t( nodeName === \"option\" && !!elem.selected );\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\t// eslint-disable-next-line no-unused-expressions\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[ \"empty\" ]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE<8\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( ( attr = elem.getAttribute( \"type\" ) ) == null ||\n\t\t\t\t\tattr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo( function() {\n\t\t\treturn [ 0 ];\n\t\t} ),\n\n\t\t\"last\": createPositionalPseudo( function( _matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t} ),\n\n\t\t\"eq\": createPositionalPseudo( function( _matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t} ),\n\n\t\t\"even\": createPositionalPseudo( function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"odd\": createPositionalPseudo( function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"lt\": createPositionalPseudo( function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ?\n\t\t\t\targument + length :\n\t\t\t\targument > length ?\n\t\t\t\t\tlength :\n\t\t\t\t\targument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"gt\": createPositionalPseudo( function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} )\n\t}\n};\n\nExpr.pseudos[ \"nth\" ] = Expr.pseudos[ \"eq\" ];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\ntokenize = Sizzle.tokenize = function( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || ( match = rcomma.exec( soFar ) ) ) {\n\t\t\tif ( match ) {\n\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[ 0 ].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( ( tokens = [] ) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( ( match = rcombinators.exec( soFar ) ) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push( {\n\t\t\t\tvalue: matched,\n\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[ 0 ].replace( rtrim, \" \" )\n\t\t\t} );\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||\n\t\t\t\t( match = preFilters[ type ]( match ) ) ) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push( {\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t} );\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n};\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[ i ].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tskip = combinator.next,\n\t\tkey = skip || dir,\n\t\tcheckNonElements = base && key === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, uniqueCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || ( elem[ expando ] = {} );\n\n\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\tuniqueCache = outerCache[ elem.uniqueID ] ||\n\t\t\t\t\t\t\t( outerCache[ elem.uniqueID ] = {} );\n\n\t\t\t\t\t\tif ( skip && skip === elem.nodeName.toLowerCase() ) {\n\t\t\t\t\t\t\telem = elem[ dir ] || elem;\n\t\t\t\t\t\t} else if ( ( oldCache = uniqueCache[ key ] ) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn ( newCache[ 2 ] = oldCache[ 2 ] );\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\tuniqueCache[ key ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[ i ]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[ 0 ];\n}\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[ i ], results );\n\t}\n\treturn results;\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( ( elem = unmatched[ i ] ) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction( function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts(\n\t\t\t\tselector || \"*\",\n\t\t\t\tcontext.nodeType ? [ context ] : context,\n\t\t\t\t[]\n\t\t\t),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( ( elem = temp[ i ] ) ) {\n\t\t\t\t\tmatcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( ( elem = matcherOut[ i ] ) ) {\n\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( ( matcherIn[ i ] = elem ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, ( matcherOut = [] ), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( ( elem = matcherOut[ i ] ) &&\n\t\t\t\t\t\t( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {\n\n\t\t\t\t\t\tseed[ temp ] = !( results[ temp ] = elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t} );\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[ 0 ].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[ \" \" ],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\tvar ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t( checkContext = context ).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\n\t\t\t// Avoid hanging onto element (issue #299)\n\t\t\tcheckContext = null;\n\t\t\treturn ret;\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {\n\t\t\tmatchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[ j ].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\n\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\ttokens\n\t\t\t\t\t\t.slice( 0, i - 1 )\n\t\t\t\t\t\t.concat( { value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" } )\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[ \"TAG\" ]( \"*\", outermost ),\n\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\n\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\toutermostContext = context == document || context || outermost;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\n\t\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\t\tif ( !context && elem.ownerDocument != document ) {\n\t\t\t\t\t\tsetDocument( elem );\n\t\t\t\t\t\txml = !documentIsHTML;\n\t\t\t\t\t}\n\t\t\t\t\twhile ( ( matcher = elementMatchers[ j++ ] ) ) {\n\t\t\t\t\t\tif ( matcher( elem, context || document, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( ( elem = !matcher && elem ) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// `i` is now the count of elements visited above, and adding it to `matchedCount`\n\t\t\t// makes the latter nonnegative.\n\t\t\tmatchedCount += i;\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\t// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`\n\t\t\t// equals `i`), unless we didn't visit _any_ elements in the above loop because we have\n\t\t\t// no element matchers and no seed.\n\t\t\t// Incrementing an initially-string \"0\" `i` allows `i` to remain a string only in that\n\t\t\t// case, which will result in a \"00\" `matchedCount` that differs from `i` but is also\n\t\t\t// numerically zero.\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( ( matcher = setMatchers[ j++ ] ) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !( unmatched[ i ] || setMatched[ i ] ) ) {\n\t\t\t\t\t\t\t\tsetMatched[ i ] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !match ) {\n\t\t\tmatch = tokenize( selector );\n\t\t}\n\t\ti = match.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( match[ i ] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache(\n\t\t\tselector,\n\t\t\tmatcherFromGroupMatchers( elementMatchers, setMatchers )\n\t\t);\n\n\t\t// Save selector and tokenization\n\t\tcached.selector = selector;\n\t}\n\treturn cached;\n};\n\n/**\n * A low-level selection function that works with Sizzle's compiled\n *  selector functions\n * @param {String|Function} selector A selector or a pre-compiled\n *  selector function built with Sizzle.compile\n * @param {Element} context\n * @param {Array} [results]\n * @param {Array} [seed] A set of elements to match against\n */\nselect = Sizzle.select = function( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tcompiled = typeof selector === \"function\" && selector,\n\t\tmatch = !seed && tokenize( ( selector = compiled.selector || selector ) );\n\n\tresults = results || [];\n\n\t// Try to minimize operations if there is only one selector in the list and no seed\n\t// (the latter of which guarantees us context)\n\tif ( match.length === 1 ) {\n\n\t\t// Reduce context if the leading compound selector is an ID\n\t\ttokens = match[ 0 ] = match[ 0 ].slice( 0 );\n\t\tif ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === \"ID\" &&\n\t\t\tcontext.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {\n\n\t\t\tcontext = ( Expr.find[ \"ID\" ]( token.matches[ 0 ]\n\t\t\t\t.replace( runescape, funescape ), context ) || [] )[ 0 ];\n\t\t\tif ( !context ) {\n\t\t\t\treturn results;\n\n\t\t\t// Precompiled matchers will still verify ancestry, so step up a level\n\t\t\t} else if ( compiled ) {\n\t\t\t\tcontext = context.parentNode;\n\t\t\t}\n\n\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t}\n\n\t\t// Fetch a seed set for right-to-left matching\n\t\ti = matchExpr[ \"needsContext\" ].test( selector ) ? 0 : tokens.length;\n\t\twhile ( i-- ) {\n\t\t\ttoken = tokens[ i ];\n\n\t\t\t// Abort if we hit a combinator\n\t\t\tif ( Expr.relative[ ( type = token.type ) ] ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( ( find = Expr.find[ type ] ) ) {\n\n\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\tif ( ( seed = find(\n\t\t\t\t\ttoken.matches[ 0 ].replace( runescape, funescape ),\n\t\t\t\t\trsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||\n\t\t\t\t\t\tcontext\n\t\t\t\t) ) ) {\n\n\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function if one is not provided\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\t( compiled || compile( selector, match ) )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\t!context || rsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n};\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split( \"\" ).sort( sortOrder ).join( \"\" ) === expando;\n\n// Support: Chrome 14-35+\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert( function( el ) {\n\n\t// Should return 1, but returns 4 (following)\n\treturn el.compareDocumentPosition( document.createElement( \"fieldset\" ) ) & 1;\n} );\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert( function( el ) {\n\tel.innerHTML = \"<a href='#'></a>\";\n\treturn el.firstChild.getAttribute( \"href\" ) === \"#\";\n} ) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t} );\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert( function( el ) {\n\tel.innerHTML = \"<input/>\";\n\tel.firstChild.setAttribute( \"value\", \"\" );\n\treturn el.firstChild.getAttribute( \"value\" ) === \"\";\n} ) ) {\n\taddHandle( \"value\", function( elem, _name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t} );\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert( function( el ) {\n\treturn el.getAttribute( \"disabled\" ) == null;\n} ) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t( val = elem.getAttributeNode( name ) ) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\t\tnull;\n\t\t}\n\t} );\n}\n\nreturn Sizzle;\n\n} )( window );\n\n\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\n\n// Deprecated\njQuery.expr[ \":\" ] = jQuery.expr.pseudos;\njQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\njQuery.escapeSelector = Sizzle.escape;\n\n\n\n\nvar dir = function( elem, dir, until ) {\n\tvar matched = [],\n\t\ttruncate = until !== undefined;\n\n\twhile ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {\n\t\tif ( elem.nodeType === 1 ) {\n\t\t\tif ( truncate && jQuery( elem ).is( until ) ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmatched.push( elem );\n\t\t}\n\t}\n\treturn matched;\n};\n\n\nvar siblings = function( n, elem ) {\n\tvar matched = [];\n\n\tfor ( ; n; n = n.nextSibling ) {\n\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\tmatched.push( n );\n\t\t}\n\t}\n\n\treturn matched;\n};\n\n\nvar rneedsContext = jQuery.expr.match.needsContext;\n\n\n\nfunction nodeName( elem, name ) {\n\n  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\n};\nvar rsingleTag = ( /^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i );\n\n\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t} );\n\t}\n\n\t// Single element\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t} );\n\t}\n\n\t// Arraylike of elements (jQuery, arguments, Array)\n\tif ( typeof qualifier !== \"string\" ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( indexOf.call( qualifier, elem ) > -1 ) !== not;\n\t\t} );\n\t}\n\n\t// Filtered directly for both simple and complex selectors\n\treturn jQuery.filter( qualifier, elements, not );\n}\n\njQuery.filter = function( expr, elems, not ) {\n\tvar elem = elems[ 0 ];\n\n\tif ( not ) {\n\t\texpr = \":not(\" + expr + \")\";\n\t}\n\n\tif ( elems.length === 1 && elem.nodeType === 1 ) {\n\t\treturn jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];\n\t}\n\n\treturn jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\treturn elem.nodeType === 1;\n\t} ) );\n};\n\njQuery.fn.extend( {\n\tfind: function( selector ) {\n\t\tvar i, ret,\n\t\t\tlen = this.length,\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter( function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} ) );\n\t\t}\n\n\t\tret = this.pushStack( [] );\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\treturn len > 1 ? jQuery.uniqueSort( ret ) : ret;\n\t},\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow( this, selector || [], false ) );\n\t},\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow( this, selector || [], true ) );\n\t},\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t}\n} );\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\t// Shortcut simple #id case for speed\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/,\n\n\tinit = jQuery.fn.init = function( selector, context, root ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Method init() accepts an alternate rootjQuery\n\t\t// so migrate can support jQuery.sub (gh-2101)\n\t\troot = root || rootjQuery;\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector[ 0 ] === \"<\" &&\n\t\t\t\tselector[ selector.length - 1 ] === \">\" &&\n\t\t\t\tselector.length >= 3 ) {\n\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && ( match[ 1 ] || !context ) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[ 1 ] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[ 0 ] : context;\n\n\t\t\t\t\t// Option to run scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[ 1 ],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[ 2 ] );\n\n\t\t\t\t\tif ( elem ) {\n\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis[ 0 ] = elem;\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || root ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis[ 0 ] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( isFunction( selector ) ) {\n\t\t\treturn root.ready !== undefined ?\n\t\t\t\troot.ready( selector ) :\n\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector( jQuery );\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\nrootjQuery = jQuery( document );\n\n\nvar rparentsprev = /^(?:parents|prev(?:Until|All))/,\n\n\t// Methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.fn.extend( {\n\thas: function( target ) {\n\t\tvar targets = jQuery( target, this ),\n\t\t\tl = targets.length;\n\n\t\treturn this.filter( function() {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[ i ] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tmatched = [],\n\t\t\ttargets = typeof selectors !== \"string\" && jQuery( selectors );\n\n\t\t// Positional selectors never match, since there's no _selection_ context\n\t\tif ( !rneedsContext.test( selectors ) ) {\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tfor ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {\n\n\t\t\t\t\t// Always skip document fragments\n\t\t\t\t\tif ( cur.nodeType < 11 && ( targets ?\n\t\t\t\t\t\ttargets.index( cur ) > -1 :\n\n\t\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\t\tjQuery.find.matchesSelector( cur, selectors ) ) ) {\n\n\t\t\t\t\t\tmatched.push( cur );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );\n\t},\n\n\t// Determine the position of an element within the set\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// Index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn indexOf.call( jQuery( elem ), this[ 0 ] );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call( this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ] : elem\n\t\t);\n\t},\n\n\tadd: function( selector, context ) {\n\t\treturn this.pushStack(\n\t\t\tjQuery.uniqueSort(\n\t\t\t\tjQuery.merge( this.get(), jQuery( selector, context ) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter( selector )\n\t\t);\n\t}\n} );\n\nfunction sibling( cur, dir ) {\n\twhile ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}\n\treturn cur;\n}\n\njQuery.each( {\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, _i, until ) {\n\t\treturn dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, _i, until ) {\n\t\treturn dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, _i, until ) {\n\t\treturn dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn siblings( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn siblings( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\tif ( elem.contentDocument != null &&\n\n\t\t\t// Support: IE 11+\n\t\t\t// <object> elements with no `data` attribute has an object\n\t\t\t// `contentDocument` with a `null` prototype.\n\t\t\tgetProto( elem.contentDocument ) ) {\n\n\t\t\treturn elem.contentDocument;\n\t\t}\n\n\t\t// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only\n\t\t// Treat the template element as a regular one in browsers that\n\t\t// don't support it.\n\t\tif ( nodeName( elem, \"template\" ) ) {\n\t\t\telem = elem.content || elem;\n\t\t}\n\n\t\treturn jQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar matched = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tmatched = jQuery.filter( selector, matched );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tjQuery.uniqueSort( matched );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched );\n\t};\n} );\nvar rnothtmlwhite = ( /[^\\x20\\t\\r\\n\\f]+/g );\n\n\n\n// Convert String-formatted options into Object-formatted ones\nfunction createOptions( options ) {\n\tvar object = {};\n\tjQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t} );\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\tcreateOptions( options ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Flag to know if list is currently firing\n\t\tfiring,\n\n\t\t// Last fire value for non-forgettable lists\n\t\tmemory,\n\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\n\t\t// Flag to prevent firing\n\t\tlocked,\n\n\t\t// Actual callback list\n\t\tlist = [],\n\n\t\t// Queue of execution data for repeatable lists\n\t\tqueue = [],\n\n\t\t// Index of currently firing callback (modified by add/remove as needed)\n\t\tfiringIndex = -1,\n\n\t\t// Fire callbacks\n\t\tfire = function() {\n\n\t\t\t// Enforce single-firing\n\t\t\tlocked = locked || options.once;\n\n\t\t\t// Execute callbacks for all pending executions,\n\t\t\t// respecting firingIndex overrides and runtime changes\n\t\t\tfired = firing = true;\n\t\t\tfor ( ; queue.length; firingIndex = -1 ) {\n\t\t\t\tmemory = queue.shift();\n\t\t\t\twhile ( ++firingIndex < list.length ) {\n\n\t\t\t\t\t// Run callback and check for early termination\n\t\t\t\t\tif ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&\n\t\t\t\t\t\toptions.stopOnFalse ) {\n\n\t\t\t\t\t\t// Jump to end and forget the data so .add doesn't re-fire\n\t\t\t\t\t\tfiringIndex = list.length;\n\t\t\t\t\t\tmemory = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Forget the data if we're done with it\n\t\t\tif ( !options.memory ) {\n\t\t\t\tmemory = false;\n\t\t\t}\n\n\t\t\tfiring = false;\n\n\t\t\t// Clean up if we're done firing for good\n\t\t\tif ( locked ) {\n\n\t\t\t\t// Keep an empty list if we have data for future add calls\n\t\t\t\tif ( memory ) {\n\t\t\t\t\tlist = [];\n\n\t\t\t\t// Otherwise, this object is spent\n\t\t\t\t} else {\n\t\t\t\t\tlist = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Actual Callbacks object\n\t\tself = {\n\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\n\t\t\t\t\t// If we have memory from a past run, we should fire after adding\n\t\t\t\t\tif ( memory && !firing ) {\n\t\t\t\t\t\tfiringIndex = list.length - 1;\n\t\t\t\t\t\tqueue.push( memory );\n\t\t\t\t\t}\n\n\t\t\t\t\t( function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tif ( isFunction( arg ) ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && toType( arg ) !== \"string\" ) {\n\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\t\t\t\t\t} )( arguments );\n\n\t\t\t\t\tif ( memory && !firing ) {\n\t\t\t\t\t\tfire();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\tvar index;\n\t\t\t\t\twhile ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\tlist.splice( index, 1 );\n\n\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ?\n\t\t\t\t\tjQuery.inArray( fn, list ) > -1 :\n\t\t\t\t\tlist.length > 0;\n\t\t\t},\n\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Disable .fire and .add\n\t\t\t// Abort any current/pending executions\n\t\t\t// Clear all callbacks and values\n\t\t\tdisable: function() {\n\t\t\t\tlocked = queue = [];\n\t\t\t\tlist = memory = \"\";\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\n\t\t\t// Disable .fire\n\t\t\t// Also disable .add unless we have memory (since it would have no effect)\n\t\t\t// Abort any pending executions\n\t\t\tlock: function() {\n\t\t\t\tlocked = queue = [];\n\t\t\t\tif ( !memory && !firing ) {\n\t\t\t\t\tlist = memory = \"\";\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\tlocked: function() {\n\t\t\t\treturn !!locked;\n\t\t\t},\n\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( !locked ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tqueue.push( args );\n\t\t\t\t\tif ( !firing ) {\n\t\t\t\t\t\tfire();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\nfunction Identity( v ) {\n\treturn v;\n}\nfunction Thrower( ex ) {\n\tthrow ex;\n}\n\nfunction adoptValue( value, resolve, reject, noValue ) {\n\tvar method;\n\n\ttry {\n\n\t\t// Check for promise aspect first to privilege synchronous behavior\n\t\tif ( value && isFunction( ( method = value.promise ) ) ) {\n\t\t\tmethod.call( value ).done( resolve ).fail( reject );\n\n\t\t// Other thenables\n\t\t} else if ( value && isFunction( ( method = value.then ) ) ) {\n\t\t\tmethod.call( value, resolve, reject );\n\n\t\t// Other non-thenables\n\t\t} else {\n\n\t\t\t// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:\n\t\t\t// * false: [ value ].slice( 0 ) => resolve( value )\n\t\t\t// * true: [ value ].slice( 1 ) => resolve()\n\t\t\tresolve.apply( undefined, [ value ].slice( noValue ) );\n\t\t}\n\n\t// For Promises/A+, convert exceptions into rejections\n\t// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in\n\t// Deferred#then to conditionally suppress rejection.\n\t} catch ( value ) {\n\n\t\t// Support: Android 4.0 only\n\t\t// Strict mode functions invoked without .call/.apply get global-object context\n\t\treject.apply( undefined, [ value ] );\n\t}\n}\n\njQuery.extend( {\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\n\t\t\t\t// action, add listener, callbacks,\n\t\t\t\t// ... .then handlers, argument index, [final state]\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks( \"memory\" ),\n\t\t\t\t\tjQuery.Callbacks( \"memory\" ), 2 ],\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks( \"once memory\" ),\n\t\t\t\t\tjQuery.Callbacks( \"once memory\" ), 0, \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks( \"once memory\" ),\n\t\t\t\t\tjQuery.Callbacks( \"once memory\" ), 1, \"rejected\" ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\t\"catch\": function( fn ) {\n\t\t\t\t\treturn promise.then( null, fn );\n\t\t\t\t},\n\n\t\t\t\t// Keep pipe for back-compat\n\t\t\t\tpipe: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\n\t\t\t\t\treturn jQuery.Deferred( function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( _i, tuple ) {\n\n\t\t\t\t\t\t\t// Map tuples (progress, done, fail) to arguments (done, fail, progress)\n\t\t\t\t\t\t\tvar fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];\n\n\t\t\t\t\t\t\t// deferred.progress(function() { bind to newDefer or newDefer.notify })\n\t\t\t\t\t\t\t// deferred.done(function() { bind to newDefer or newDefer.resolve })\n\t\t\t\t\t\t\t// deferred.fail(function() { bind to newDefer or newDefer.reject })\n\t\t\t\t\t\t\tdeferred[ tuple[ 1 ] ]( function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify )\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + \"With\" ](\n\t\t\t\t\t\t\t\t\t\tthis,\n\t\t\t\t\t\t\t\t\t\tfn ? [ returned ] : arguments\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t} );\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t} ).promise();\n\t\t\t\t},\n\t\t\t\tthen: function( onFulfilled, onRejected, onProgress ) {\n\t\t\t\t\tvar maxDepth = 0;\n\t\t\t\t\tfunction resolve( depth, deferred, handler, special ) {\n\t\t\t\t\t\treturn function() {\n\t\t\t\t\t\t\tvar that = this,\n\t\t\t\t\t\t\t\targs = arguments,\n\t\t\t\t\t\t\t\tmightThrow = function() {\n\t\t\t\t\t\t\t\t\tvar returned, then;\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.3\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-59\n\t\t\t\t\t\t\t\t\t// Ignore double-resolution attempts\n\t\t\t\t\t\t\t\t\tif ( depth < maxDepth ) {\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturned = handler.apply( that, args );\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.1\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-48\n\t\t\t\t\t\t\t\t\tif ( returned === deferred.promise() ) {\n\t\t\t\t\t\t\t\t\t\tthrow new TypeError( \"Thenable self-resolution\" );\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ sections 2.3.3.1, 3.5\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-54\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-75\n\t\t\t\t\t\t\t\t\t// Retrieve `then` only once\n\t\t\t\t\t\t\t\t\tthen = returned &&\n\n\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.4\n\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-64\n\t\t\t\t\t\t\t\t\t\t// Only check objects and functions for thenability\n\t\t\t\t\t\t\t\t\t\t( typeof returned === \"object\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof returned === \"function\" ) &&\n\t\t\t\t\t\t\t\t\t\treturned.then;\n\n\t\t\t\t\t\t\t\t\t// Handle a returned thenable\n\t\t\t\t\t\t\t\t\tif ( isFunction( then ) ) {\n\n\t\t\t\t\t\t\t\t\t\t// Special processors (notify) just wait for resolution\n\t\t\t\t\t\t\t\t\t\tif ( special ) {\n\t\t\t\t\t\t\t\t\t\t\tthen.call(\n\t\t\t\t\t\t\t\t\t\t\t\treturned,\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Identity, special ),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Thrower, special )\n\t\t\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\t\t// Normal processors (resolve) also hook into progress\n\t\t\t\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t\t\t\t// ...and disregard older resolution values\n\t\t\t\t\t\t\t\t\t\t\tmaxDepth++;\n\n\t\t\t\t\t\t\t\t\t\t\tthen.call(\n\t\t\t\t\t\t\t\t\t\t\t\treturned,\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Identity, special ),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Thrower, special ),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve( maxDepth, deferred, Identity,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdeferred.notifyWith )\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Handle all other returned values\n\t\t\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t\t\t// Only substitute handlers pass on context\n\t\t\t\t\t\t\t\t\t\t// and multiple values (non-spec behavior)\n\t\t\t\t\t\t\t\t\t\tif ( handler !== Identity ) {\n\t\t\t\t\t\t\t\t\t\t\tthat = undefined;\n\t\t\t\t\t\t\t\t\t\t\targs = [ returned ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t// Process the value(s)\n\t\t\t\t\t\t\t\t\t\t// Default process is resolve\n\t\t\t\t\t\t\t\t\t\t( special || deferred.resolveWith )( that, args );\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\n\t\t\t\t\t\t\t\t// Only normal processors (resolve) catch and reject exceptions\n\t\t\t\t\t\t\t\tprocess = special ?\n\t\t\t\t\t\t\t\t\tmightThrow :\n\t\t\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tmightThrow();\n\t\t\t\t\t\t\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t\t\t\t\t\t\tif ( jQuery.Deferred.exceptionHook ) {\n\t\t\t\t\t\t\t\t\t\t\t\tjQuery.Deferred.exceptionHook( e,\n\t\t\t\t\t\t\t\t\t\t\t\t\tprocess.stackTrace );\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.4.1\n\t\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-61\n\t\t\t\t\t\t\t\t\t\t\t// Ignore post-resolution exceptions\n\t\t\t\t\t\t\t\t\t\t\tif ( depth + 1 >= maxDepth ) {\n\n\t\t\t\t\t\t\t\t\t\t\t\t// Only substitute handlers pass on context\n\t\t\t\t\t\t\t\t\t\t\t\t// and multiple values (non-spec behavior)\n\t\t\t\t\t\t\t\t\t\t\t\tif ( handler !== Thrower ) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthat = undefined;\n\t\t\t\t\t\t\t\t\t\t\t\t\targs = [ e ];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\tdeferred.rejectWith( that, args );\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.1\n\t\t\t\t\t\t\t// https://promisesaplus.com/#point-57\n\t\t\t\t\t\t\t// Re-resolve promises immediately to dodge false rejection from\n\t\t\t\t\t\t\t// subsequent errors\n\t\t\t\t\t\t\tif ( depth ) {\n\t\t\t\t\t\t\t\tprocess();\n\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t// Call an optional hook to record the stack, in case of exception\n\t\t\t\t\t\t\t\t// since it's otherwise lost when execution goes async\n\t\t\t\t\t\t\t\tif ( jQuery.Deferred.getStackHook ) {\n\t\t\t\t\t\t\t\t\tprocess.stackTrace = jQuery.Deferred.getStackHook();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.setTimeout( process );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn jQuery.Deferred( function( newDefer ) {\n\n\t\t\t\t\t\t// progress_handlers.add( ... )\n\t\t\t\t\t\ttuples[ 0 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction( onProgress ) ?\n\t\t\t\t\t\t\t\t\tonProgress :\n\t\t\t\t\t\t\t\t\tIdentity,\n\t\t\t\t\t\t\t\tnewDefer.notifyWith\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// fulfilled_handlers.add( ... )\n\t\t\t\t\t\ttuples[ 1 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction( onFulfilled ) ?\n\t\t\t\t\t\t\t\t\tonFulfilled :\n\t\t\t\t\t\t\t\t\tIdentity\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// rejected_handlers.add( ... )\n\t\t\t\t\t\ttuples[ 2 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction( onRejected ) ?\n\t\t\t\t\t\t\t\t\tonRejected :\n\t\t\t\t\t\t\t\t\tThrower\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} ).promise();\n\t\t\t\t},\n\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 5 ];\n\n\t\t\t// promise.progress = list.add\n\t\t\t// promise.done = list.add\n\t\t\t// promise.fail = list.add\n\t\t\tpromise[ tuple[ 1 ] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(\n\t\t\t\t\tfunction() {\n\n\t\t\t\t\t\t// state = \"resolved\" (i.e., fulfilled)\n\t\t\t\t\t\t// state = \"rejected\"\n\t\t\t\t\t\tstate = stateString;\n\t\t\t\t\t},\n\n\t\t\t\t\t// rejected_callbacks.disable\n\t\t\t\t\t// fulfilled_callbacks.disable\n\t\t\t\t\ttuples[ 3 - i ][ 2 ].disable,\n\n\t\t\t\t\t// rejected_handlers.disable\n\t\t\t\t\t// fulfilled_handlers.disable\n\t\t\t\t\ttuples[ 3 - i ][ 3 ].disable,\n\n\t\t\t\t\t// progress_callbacks.lock\n\t\t\t\t\ttuples[ 0 ][ 2 ].lock,\n\n\t\t\t\t\t// progress_handlers.lock\n\t\t\t\t\ttuples[ 0 ][ 3 ].lock\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// progress_handlers.fire\n\t\t\t// fulfilled_handlers.fire\n\t\t\t// rejected_handlers.fire\n\t\t\tlist.add( tuple[ 3 ].fire );\n\n\t\t\t// deferred.notify = function() { deferred.notifyWith(...) }\n\t\t\t// deferred.resolve = function() { deferred.resolveWith(...) }\n\t\t\t// deferred.reject = function() { deferred.rejectWith(...) }\n\t\t\tdeferred[ tuple[ 0 ] ] = function() {\n\t\t\t\tdeferred[ tuple[ 0 ] + \"With\" ]( this === deferred ? undefined : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\n\t\t\t// deferred.notifyWith = list.fireWith\n\t\t\t// deferred.resolveWith = list.fireWith\n\t\t\t// deferred.rejectWith = list.fireWith\n\t\t\tdeferred[ tuple[ 0 ] + \"With\" ] = list.fireWith;\n\t\t} );\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( singleValue ) {\n\t\tvar\n\n\t\t\t// count of uncompleted subordinates\n\t\t\tremaining = arguments.length,\n\n\t\t\t// count of unprocessed arguments\n\t\t\ti = remaining,\n\n\t\t\t// subordinate fulfillment data\n\t\t\tresolveContexts = Array( i ),\n\t\t\tresolveValues = slice.call( arguments ),\n\n\t\t\t// the master Deferred\n\t\t\tmaster = jQuery.Deferred(),\n\n\t\t\t// subordinate callback factory\n\t\t\tupdateFunc = function( i ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tresolveContexts[ i ] = this;\n\t\t\t\t\tresolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;\n\t\t\t\t\tif ( !( --remaining ) ) {\n\t\t\t\t\t\tmaster.resolveWith( resolveContexts, resolveValues );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t};\n\n\t\t// Single- and empty arguments are adopted like Promise.resolve\n\t\tif ( remaining <= 1 ) {\n\t\t\tadoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,\n\t\t\t\t!remaining );\n\n\t\t\t// Use .then() to unwrap secondary thenables (cf. gh-3000)\n\t\t\tif ( master.state() === \"pending\" ||\n\t\t\t\tisFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {\n\n\t\t\t\treturn master.then();\n\t\t\t}\n\t\t}\n\n\t\t// Multiple arguments are aggregated like Promise.all array elements\n\t\twhile ( i-- ) {\n\t\t\tadoptValue( resolveValues[ i ], updateFunc( i ), master.reject );\n\t\t}\n\n\t\treturn master.promise();\n\t}\n} );\n\n\n// These usually indicate a programmer mistake during development,\n// warn about them ASAP rather than swallowing them by default.\nvar rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;\n\njQuery.Deferred.exceptionHook = function( error, stack ) {\n\n\t// Support: IE 8 - 9 only\n\t// Console exists when dev tools are open, which can happen at any time\n\tif ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {\n\t\twindow.console.warn( \"jQuery.Deferred exception: \" + error.message, error.stack, stack );\n\t}\n};\n\n\n\n\njQuery.readyException = function( error ) {\n\twindow.setTimeout( function() {\n\t\tthrow error;\n\t} );\n};\n\n\n\n\n// The deferred used on DOM ready\nvar readyList = jQuery.Deferred();\n\njQuery.fn.ready = function( fn ) {\n\n\treadyList\n\t\t.then( fn )\n\n\t\t// Wrap jQuery.readyException in a function so that the lookup\n\t\t// happens at the time of error handling instead of callback\n\t\t// registration.\n\t\t.catch( function( error ) {\n\t\t\tjQuery.readyException( error );\n\t\t} );\n\n\treturn this;\n};\n\njQuery.extend( {\n\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\t}\n} );\n\njQuery.ready.then = readyList.then;\n\n// The ready event handler and self cleanup method\nfunction completed() {\n\tdocument.removeEventListener( \"DOMContentLoaded\", completed );\n\twindow.removeEventListener( \"load\", completed );\n\tjQuery.ready();\n}\n\n// Catch cases where $(document).ready() is called\n// after the browser event has already occurred.\n// Support: IE <=9 - 10 only\n// Older IE sometimes signals \"interactive\" too soon\nif ( document.readyState === \"complete\" ||\n\t( document.readyState !== \"loading\" && !document.documentElement.doScroll ) ) {\n\n\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\twindow.setTimeout( jQuery.ready );\n\n} else {\n\n\t// Use the handy event callback\n\tdocument.addEventListener( \"DOMContentLoaded\", completed );\n\n\t// A fallback to window.onload, that will always work\n\twindow.addEventListener( \"load\", completed );\n}\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\nvar access = function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\tvar i = 0,\n\t\tlen = elems.length,\n\t\tbulk = key == null;\n\n\t// Sets many values\n\tif ( toType( key ) === \"object\" ) {\n\t\tchainable = true;\n\t\tfor ( i in key ) {\n\t\t\taccess( elems, fn, i, key[ i ], true, emptyGet, raw );\n\t\t}\n\n\t// Sets one value\n\t} else if ( value !== undefined ) {\n\t\tchainable = true;\n\n\t\tif ( !isFunction( value ) ) {\n\t\t\traw = true;\n\t\t}\n\n\t\tif ( bulk ) {\n\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif ( raw ) {\n\t\t\t\tfn.call( elems, value );\n\t\t\t\tfn = null;\n\n\t\t\t// ...except when executing function values\n\t\t\t} else {\n\t\t\t\tbulk = fn;\n\t\t\t\tfn = function( elem, _key, value ) {\n\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif ( fn ) {\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\tfn(\n\t\t\t\t\telems[ i ], key, raw ?\n\t\t\t\t\tvalue :\n\t\t\t\t\tvalue.call( elems[ i ], i, fn( elems[ i ], key ) )\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( chainable ) {\n\t\treturn elems;\n\t}\n\n\t// Gets\n\tif ( bulk ) {\n\t\treturn fn.call( elems );\n\t}\n\n\treturn len ? fn( elems[ 0 ], key ) : emptyGet;\n};\n\n\n// Matches dashed string for camelizing\nvar rmsPrefix = /^-ms-/,\n\trdashAlpha = /-([a-z])/g;\n\n// Used by camelCase as callback to replace()\nfunction fcamelCase( _all, letter ) {\n\treturn letter.toUpperCase();\n}\n\n// Convert dashed to camelCase; used by the css and data modules\n// Support: IE <=9 - 11, Edge 12 - 15\n// Microsoft forgot to hump their vendor prefix (#9572)\nfunction camelCase( string ) {\n\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n}\nvar acceptData = function( owner ) {\n\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\treturn owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n};\n\n\n\n\nfunction Data() {\n\tthis.expando = jQuery.expando + Data.uid++;\n}\n\nData.uid = 1;\n\nData.prototype = {\n\n\tcache: function( owner ) {\n\n\t\t// Check if the owner object already has a cache\n\t\tvar value = owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif ( !value ) {\n\t\t\tvalue = {};\n\n\t\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t\t// but we should not, see #8335.\n\t\t\t// Always return an empty object.\n\t\t\tif ( acceptData( owner ) ) {\n\n\t\t\t\t// If it is a node unlikely to be stringify-ed or looped over\n\t\t\t\t// use plain assignment\n\t\t\t\tif ( owner.nodeType ) {\n\t\t\t\t\towner[ this.expando ] = value;\n\n\t\t\t\t// Otherwise secure it in a non-enumerable property\n\t\t\t\t// configurable must be true to allow the property to be\n\t\t\t\t// deleted when data is removed\n\t\t\t\t} else {\n\t\t\t\t\tObject.defineProperty( owner, this.expando, {\n\t\t\t\t\t\tvalue: value,\n\t\t\t\t\t\tconfigurable: true\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn value;\n\t},\n\tset: function( owner, data, value ) {\n\t\tvar prop,\n\t\t\tcache = this.cache( owner );\n\n\t\t// Handle: [ owner, key, value ] args\n\t\t// Always use camelCase key (gh-2257)\n\t\tif ( typeof data === \"string\" ) {\n\t\t\tcache[ camelCase( data ) ] = value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t} else {\n\n\t\t\t// Copy the properties one-by-one to the cache object\n\t\t\tfor ( prop in data ) {\n\t\t\t\tcache[ camelCase( prop ) ] = data[ prop ];\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function( owner, key ) {\n\t\treturn key === undefined ?\n\t\t\tthis.cache( owner ) :\n\n\t\t\t// Always use camelCase key (gh-2257)\n\t\t\towner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];\n\t},\n\taccess: function( owner, key, value ) {\n\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the \"read\" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif ( key === undefined ||\n\t\t\t\t( ( key && typeof key === \"string\" ) && value === undefined ) ) {\n\n\t\t\treturn this.get( owner, key );\n\t\t}\n\n\t\t// When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set( owner, key, value );\n\n\t\t// Since the \"set\" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value !== undefined ? value : key;\n\t},\n\tremove: function( owner, key ) {\n\t\tvar i,\n\t\t\tcache = owner[ this.expando ];\n\n\t\tif ( cache === undefined ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( key !== undefined ) {\n\n\t\t\t// Support array or space separated string of keys\n\t\t\tif ( Array.isArray( key ) ) {\n\n\t\t\t\t// If key is an array of keys...\n\t\t\t\t// We always set camelCase keys, so remove that.\n\t\t\t\tkey = key.map( camelCase );\n\t\t\t} else {\n\t\t\t\tkey = camelCase( key );\n\n\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\tkey = key in cache ?\n\t\t\t\t\t[ key ] :\n\t\t\t\t\t( key.match( rnothtmlwhite ) || [] );\n\t\t\t}\n\n\t\t\ti = key.length;\n\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete cache[ key[ i ] ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if there's no more data\n\t\tif ( key === undefined || jQuery.isEmptyObject( cache ) ) {\n\n\t\t\t// Support: Chrome <=35 - 45\n\t\t\t// Webkit & Blink performance suffers when deleting properties\n\t\t\t// from DOM nodes, so set to undefined instead\n\t\t\t// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)\n\t\t\tif ( owner.nodeType ) {\n\t\t\t\towner[ this.expando ] = undefined;\n\t\t\t} else {\n\t\t\t\tdelete owner[ this.expando ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function( owner ) {\n\t\tvar cache = owner[ this.expando ];\n\t\treturn cache !== undefined && !jQuery.isEmptyObject( cache );\n\t}\n};\nvar dataPriv = new Data();\n\nvar dataUser = new Data();\n\n\n\n//\tImplementation Summary\n//\n//\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n//\t2. Improve the module's maintainability by reducing the storage\n//\t\tpaths to a single mechanism.\n//\t3. Use the same single mechanism to support \"private\" and \"user\" data.\n//\t4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n//\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n//\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n\nvar rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash = /[A-Z]/g;\n\nfunction getData( data ) {\n\tif ( data === \"true\" ) {\n\t\treturn true;\n\t}\n\n\tif ( data === \"false\" ) {\n\t\treturn false;\n\t}\n\n\tif ( data === \"null\" ) {\n\t\treturn null;\n\t}\n\n\t// Only convert to a number if it doesn't change the string\n\tif ( data === +data + \"\" ) {\n\t\treturn +data;\n\t}\n\n\tif ( rbrace.test( data ) ) {\n\t\treturn JSON.parse( data );\n\t}\n\n\treturn data;\n}\n\nfunction dataAttr( elem, key, data ) {\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\t\tname = \"data-\" + key.replace( rmultiDash, \"-$&\" ).toLowerCase();\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = getData( data );\n\t\t\t} catch ( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tdataUser.set( elem, key, data );\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend( {\n\thasData: function( elem ) {\n\t\treturn dataUser.hasData( elem ) || dataPriv.hasData( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn dataUser.access( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\tdataUser.remove( elem, name );\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to dataPriv methods, these can be deprecated.\n\t_data: function( elem, name, data ) {\n\t\treturn dataPriv.access( elem, name, data );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\tdataPriv.remove( elem, name );\n\t}\n} );\n\njQuery.fn.extend( {\n\tdata: function( key, value ) {\n\t\tvar i, name, data,\n\t\t\telem = this[ 0 ],\n\t\t\tattrs = elem && elem.attributes;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = dataUser.get( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !dataPriv.get( elem, \"hasDataAttrs\" ) ) {\n\t\t\t\t\ti = attrs.length;\n\t\t\t\t\twhile ( i-- ) {\n\n\t\t\t\t\t\t// Support: IE 11 only\n\t\t\t\t\t\t// The attrs elements can be null (#14894)\n\t\t\t\t\t\tif ( attrs[ i ] ) {\n\t\t\t\t\t\t\tname = attrs[ i ].name;\n\t\t\t\t\t\t\tif ( name.indexOf( \"data-\" ) === 0 ) {\n\t\t\t\t\t\t\t\tname = camelCase( name.slice( 5 ) );\n\t\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdataPriv.set( elem, \"hasDataAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each( function() {\n\t\t\t\tdataUser.set( this, key );\n\t\t\t} );\n\t\t}\n\n\t\treturn access( this, function( value ) {\n\t\t\tvar data;\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem = this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif ( elem && value === undefined ) {\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// The key will always be camelCased in Data\n\t\t\t\tdata = dataUser.get( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to \"discover\" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata = dataAttr( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn't exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each( function() {\n\n\t\t\t\t// We always store the camelCased key\n\t\t\t\tdataUser.set( this, key, value );\n\t\t\t} );\n\t\t}, null, value, arguments.length > 1, null, true );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each( function() {\n\t\t\tdataUser.remove( this, key );\n\t\t} );\n\t}\n} );\n\n\njQuery.extend( {\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = dataPriv.get( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || Array.isArray( data ) ) {\n\t\t\t\t\tqueue = dataPriv.access( elem, type, jQuery.makeArray( data ) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// Clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// Not public - generate a queueHooks object, or return the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn dataPriv.get( elem, key ) || dataPriv.access( elem, key, {\n\t\t\tempty: jQuery.Callbacks( \"once memory\" ).add( function() {\n\t\t\t\tdataPriv.remove( elem, [ type + \"queue\", key ] );\n\t\t\t} )\n\t\t} );\n\t}\n} );\n\njQuery.fn.extend( {\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[ 0 ], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each( function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// Ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[ 0 ] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t} );\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each( function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t} );\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile ( i-- ) {\n\t\t\ttmp = dataPriv.get( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n} );\nvar pnum = ( /[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/ ).source;\n\nvar rcssNum = new RegExp( \"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\" );\n\n\nvar cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\nvar documentElement = document.documentElement;\n\n\n\n\tvar isAttached = function( elem ) {\n\t\t\treturn jQuery.contains( elem.ownerDocument, elem );\n\t\t},\n\t\tcomposed = { composed: true };\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only\n\t// Check attachment across shadow DOM boundaries when possible (gh-3504)\n\t// Support: iOS 10.0-10.2 only\n\t// Early iOS 10 versions support `attachShadow` but not `getRootNode`,\n\t// leading to errors. We need to check for `getRootNode`.\n\tif ( documentElement.getRootNode ) {\n\t\tisAttached = function( elem ) {\n\t\t\treturn jQuery.contains( elem.ownerDocument, elem ) ||\n\t\t\t\telem.getRootNode( composed ) === elem.ownerDocument;\n\t\t};\n\t}\nvar isHiddenWithinTree = function( elem, el ) {\n\n\t\t// isHiddenWithinTree might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem = el || elem;\n\n\t\t// Inline style trumps all\n\t\treturn elem.style.display === \"none\" ||\n\t\t\telem.style.display === \"\" &&\n\n\t\t\t// Otherwise, check computed style\n\t\t\t// Support: Firefox <=43 - 45\n\t\t\t// Disconnected elements can have computed display: none, so first confirm that elem is\n\t\t\t// in the document.\n\t\t\tisAttached( elem ) &&\n\n\t\t\tjQuery.css( elem, \"display\" ) === \"none\";\n\t};\n\n\n\nfunction adjustCSS( elem, prop, valueParts, tween ) {\n\tvar adjusted, scale,\n\t\tmaxIterations = 20,\n\t\tcurrentValue = tween ?\n\t\t\tfunction() {\n\t\t\t\treturn tween.cur();\n\t\t\t} :\n\t\t\tfunction() {\n\t\t\t\treturn jQuery.css( elem, prop, \"\" );\n\t\t\t},\n\t\tinitial = currentValue(),\n\t\tunit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t// Starting value computation is required for potential unit mismatches\n\t\tinitialInUnit = elem.nodeType &&\n\t\t\t( jQuery.cssNumber[ prop ] || unit !== \"px\" && +initial ) &&\n\t\t\trcssNum.exec( jQuery.css( elem, prop ) );\n\n\tif ( initialInUnit && initialInUnit[ 3 ] !== unit ) {\n\n\t\t// Support: Firefox <=54\n\t\t// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)\n\t\tinitial = initial / 2;\n\n\t\t// Trust units reported by jQuery.css\n\t\tunit = unit || initialInUnit[ 3 ];\n\n\t\t// Iteratively approximate from a nonzero starting point\n\t\tinitialInUnit = +initial || 1;\n\n\t\twhile ( maxIterations-- ) {\n\n\t\t\t// Evaluate and update our best guess (doubling guesses that zero out).\n\t\t\t// Finish if the scale equals or crosses 1 (making the old*new product non-positive).\n\t\t\tjQuery.style( elem, prop, initialInUnit + unit );\n\t\t\tif ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {\n\t\t\t\tmaxIterations = 0;\n\t\t\t}\n\t\t\tinitialInUnit = initialInUnit / scale;\n\n\t\t}\n\n\t\tinitialInUnit = initialInUnit * 2;\n\t\tjQuery.style( elem, prop, initialInUnit + unit );\n\n\t\t// Make sure we update the tween properties later on\n\t\tvalueParts = valueParts || [];\n\t}\n\n\tif ( valueParts ) {\n\t\tinitialInUnit = +initialInUnit || +initial || 0;\n\n\t\t// Apply relative offset (+=/-=) if specified\n\t\tadjusted = valueParts[ 1 ] ?\n\t\t\tinitialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :\n\t\t\t+valueParts[ 2 ];\n\t\tif ( tween ) {\n\t\t\ttween.unit = unit;\n\t\t\ttween.start = initialInUnit;\n\t\t\ttween.end = adjusted;\n\t\t}\n\t}\n\treturn adjusted;\n}\n\n\nvar defaultDisplayMap = {};\n\nfunction getDefaultDisplay( elem ) {\n\tvar temp,\n\t\tdoc = elem.ownerDocument,\n\t\tnodeName = elem.nodeName,\n\t\tdisplay = defaultDisplayMap[ nodeName ];\n\n\tif ( display ) {\n\t\treturn display;\n\t}\n\n\ttemp = doc.body.appendChild( doc.createElement( nodeName ) );\n\tdisplay = jQuery.css( temp, \"display\" );\n\n\ttemp.parentNode.removeChild( temp );\n\n\tif ( display === \"none\" ) {\n\t\tdisplay = \"block\";\n\t}\n\tdefaultDisplayMap[ nodeName ] = display;\n\n\treturn display;\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\t// Determine new display value for elements that need to change\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\n\t\t\t// Since we force visibility upon cascade-hidden elements, an immediate (and slow)\n\t\t\t// check is required in this first loop unless we have a nonempty display value (either\n\t\t\t// inline or about-to-be-restored)\n\t\t\tif ( display === \"none\" ) {\n\t\t\t\tvalues[ index ] = dataPriv.get( elem, \"display\" ) || null;\n\t\t\t\tif ( !values[ index ] ) {\n\t\t\t\t\telem.style.display = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( elem.style.display === \"\" && isHiddenWithinTree( elem ) ) {\n\t\t\t\tvalues[ index ] = getDefaultDisplay( elem );\n\t\t\t}\n\t\t} else {\n\t\t\tif ( display !== \"none\" ) {\n\t\t\t\tvalues[ index ] = \"none\";\n\n\t\t\t\t// Remember what we're overwriting\n\t\t\t\tdataPriv.set( elem, \"display\", display );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of the elements in a second loop to avoid constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\tif ( values[ index ] != null ) {\n\t\t\telements[ index ].style.display = values[ index ];\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.fn.extend( {\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tif ( isHiddenWithinTree( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t} );\n\t}\n} );\nvar rcheckableType = ( /^(?:checkbox|radio)$/i );\n\nvar rtagName = ( /<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i );\n\nvar rscriptType = ( /^$|^module$|\\/(?:java|ecma)script/i );\n\n\n\n( function() {\n\tvar fragment = document.createDocumentFragment(),\n\t\tdiv = fragment.appendChild( document.createElement( \"div\" ) ),\n\t\tinput = document.createElement( \"input\" );\n\n\t// Support: Android 4.0 - 4.3 only\n\t// Check state lost if the name is set (#11217)\n\t// Support: Windows Web Apps (WWA)\n\t// `name` and `type` must use .setAttribute for WWA (#14901)\n\tinput.setAttribute( \"type\", \"radio\" );\n\tinput.setAttribute( \"checked\", \"checked\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tdiv.appendChild( input );\n\n\t// Support: Android <=4.1 only\n\t// Older WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE <=11 only\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\tdiv.innerHTML = \"<textarea>x</textarea>\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n\n\t// Support: IE <=9 only\n\t// IE <=9 replaces <option> tags with their contents when inserted outside of\n\t// the select element.\n\tdiv.innerHTML = \"<option></option>\";\n\tsupport.option = !!div.lastChild;\n} )();\n\n\n// We have to close these tags to support XHTML (#13200)\nvar wrapMap = {\n\n\t// XHTML parsers do not magically insert elements in the\n\t// same way that tag soup parsers do. So we cannot shorten\n\t// this by omitting <tbody> or other required elements.\n\tthead: [ 1, \"<table>\", \"</table>\" ],\n\tcol: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t_default: [ 0, \"\", \"\" ]\n};\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// Support: IE <=9 only\nif ( !support.option ) {\n\twrapMap.optgroup = wrapMap.option = [ 1, \"<select multiple='multiple'>\", \"</select>\" ];\n}\n\n\nfunction getAll( context, tag ) {\n\n\t// Support: IE <=9 - 11 only\n\t// Use typeof to avoid zero-argument method invocation on host objects (#15151)\n\tvar ret;\n\n\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\tret = context.getElementsByTagName( tag || \"*\" );\n\n\t} else if ( typeof context.querySelectorAll !== \"undefined\" ) {\n\t\tret = context.querySelectorAll( tag || \"*\" );\n\n\t} else {\n\t\tret = [];\n\t}\n\n\tif ( tag === undefined || tag && nodeName( context, tag ) ) {\n\t\treturn jQuery.merge( [ context ], ret );\n\t}\n\n\treturn ret;\n}\n\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar i = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\tdataPriv.set(\n\t\t\telems[ i ],\n\t\t\t\"globalEval\",\n\t\t\t!refElements || dataPriv.get( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\n\nvar rhtml = /<|&#?\\w+;/;\n\nfunction buildFragment( elems, context, scripts, selection, ignored ) {\n\tvar elem, tmp, tag, wrap, attached, j,\n\t\tfragment = context.createDocumentFragment(),\n\t\tnodes = [],\n\t\ti = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\telem = elems[ i ];\n\n\t\tif ( elem || elem === 0 ) {\n\n\t\t\t// Add nodes directly\n\t\t\tif ( toType( elem ) === \"object\" ) {\n\n\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t// Convert non-html into a text node\n\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t// Convert html into DOM nodes\n\t\t\t} else {\n\t\t\t\ttmp = tmp || fragment.appendChild( context.createElement( \"div\" ) );\n\n\t\t\t\t// Deserialize a standard representation\n\t\t\t\ttag = ( rtagName.exec( elem ) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\ttmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];\n\n\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\tj = wrap[ 0 ];\n\t\t\t\twhile ( j-- ) {\n\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t}\n\n\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t// Remember the top-level container\n\t\t\t\ttmp = fragment.firstChild;\n\n\t\t\t\t// Ensure the created nodes are orphaned (#12392)\n\t\t\t\ttmp.textContent = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remove wrapper from fragment\n\tfragment.textContent = \"\";\n\n\ti = 0;\n\twhile ( ( elem = nodes[ i++ ] ) ) {\n\n\t\t// Skip elements already in the context collection (trac-4087)\n\t\tif ( selection && jQuery.inArray( elem, selection ) > -1 ) {\n\t\t\tif ( ignored ) {\n\t\t\t\tignored.push( elem );\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tattached = isAttached( elem );\n\n\t\t// Append to fragment\n\t\ttmp = getAll( fragment.appendChild( elem ), \"script\" );\n\n\t\t// Preserve script evaluation history\n\t\tif ( attached ) {\n\t\t\tsetGlobalEval( tmp );\n\t\t}\n\n\t\t// Capture executables\n\t\tif ( scripts ) {\n\t\t\tj = 0;\n\t\t\twhile ( ( elem = tmp[ j++ ] ) ) {\n\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\tscripts.push( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fragment;\n}\n\n\nvar\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\n// Support: IE <=9 - 11+\n// focus() and blur() are asynchronous, except when they are no-op.\n// So expect focus to be synchronous when the element is already active,\n// and blur to be synchronous when the element is not already active.\n// (focus and blur are always synchronous in other supported browsers,\n// this just defines when we can count on it).\nfunction expectSync( elem, type ) {\n\treturn ( elem === safeActiveElement() ) === ( type === \"focus\" );\n}\n\n// Support: IE <=9 only\n// Accessing document.activeElement can throw unexpectedly\n// https://bugs.jquery.com/ticket/13393\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\nfunction on( elem, types, selector, data, fn, one ) {\n\tvar origFn, type;\n\n\t// Types can be a map of types/handlers\n\tif ( typeof types === \"object\" ) {\n\n\t\t// ( types-Object, selector, data )\n\t\tif ( typeof selector !== \"string\" ) {\n\n\t\t\t// ( types-Object, data )\n\t\t\tdata = data || selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tfor ( type in types ) {\n\t\t\ton( elem, type, selector, data, types[ type ], one );\n\t\t}\n\t\treturn elem;\n\t}\n\n\tif ( data == null && fn == null ) {\n\n\t\t// ( types, fn )\n\t\tfn = selector;\n\t\tdata = selector = undefined;\n\t} else if ( fn == null ) {\n\t\tif ( typeof selector === \"string\" ) {\n\n\t\t\t// ( types, selector, fn )\n\t\t\tfn = data;\n\t\t\tdata = undefined;\n\t\t} else {\n\n\t\t\t// ( types, data, fn )\n\t\t\tfn = data;\n\t\t\tdata = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t}\n\tif ( fn === false ) {\n\t\tfn = returnFalse;\n\t} else if ( !fn ) {\n\t\treturn elem;\n\t}\n\n\tif ( one === 1 ) {\n\t\torigFn = fn;\n\t\tfn = function( event ) {\n\n\t\t\t// Can use an empty set, since event contains the info\n\t\t\tjQuery().off( event );\n\t\t\treturn origFn.apply( this, arguments );\n\t\t};\n\n\t\t// Use same guid so caller can remove using origFn\n\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t}\n\treturn elem.each( function() {\n\t\tjQuery.event.add( this, types, fn, data, selector );\n\t} );\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = dataPriv.get( elem );\n\n\t\t// Only attach events to objects that accept data\n\t\tif ( !acceptData( elem ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Ensure that invalid selectors throw exceptions at attach time\n\t\t// Evaluate against documentElement in case elem is a non-element node (e.g., document)\n\t\tif ( selector ) {\n\t\t\tjQuery.find.matchesSelector( documentElement, selector );\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !( events = elemData.events ) ) {\n\t\t\tevents = elemData.events = Object.create( null );\n\t\t}\n\t\tif ( !( eventHandle = elemData.handle ) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== \"undefined\" && jQuery.event.triggered !== e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply( elem, arguments ) : undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnothtmlwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[ t ] ) || [];\n\t\t\ttype = origType = tmp[ 1 ];\n\t\t\tnamespaces = ( tmp[ 2 ] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend( {\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join( \".\" )\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !( handlers = events[ type ] ) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif ( !special.setup ||\n\t\t\t\t\tspecial.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = dataPriv.hasData( elem ) && dataPriv.get( elem );\n\n\t\tif ( !elemData || !( events = elemData.events ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnothtmlwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[ t ] ) || [];\n\t\t\ttype = origType = tmp[ 1 ];\n\t\t\tnamespaces = ( tmp[ 2 ] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[ 2 ] &&\n\t\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join( \"\\\\.(?:.*\\\\.|)\" ) + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector ||\n\t\t\t\t\t\tselector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown ||\n\t\t\t\t\tspecial.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove data and the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdataPriv.remove( elem, \"handle events\" );\n\t\t}\n\t},\n\n\tdispatch: function( nativeEvent ) {\n\n\t\tvar i, j, ret, matched, handleObj, handlerQueue,\n\t\t\targs = new Array( arguments.length ),\n\n\t\t\t// Make a writable jQuery.Event from the native event object\n\t\t\tevent = jQuery.event.fix( nativeEvent ),\n\n\t\t\thandlers = (\n\t\t\t\t\tdataPriv.get( this, \"events\" ) || Object.create( null )\n\t\t\t\t)[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[ 0 ] = event;\n\n\t\tfor ( i = 1; i < arguments.length; i++ ) {\n\t\t\targs[ i ] = arguments[ i ];\n\t\t}\n\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( ( handleObj = matched.handlers[ j++ ] ) &&\n\t\t\t\t!event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// If the event is namespaced, then each handler is only invoked if it is\n\t\t\t\t// specially universal or its namespaces are a superset of the event's.\n\t\t\t\tif ( !event.rnamespace || handleObj.namespace === false ||\n\t\t\t\t\tevent.rnamespace.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||\n\t\t\t\t\t\thandleObj.handler ).apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( ( event.result = ret ) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, handleObj, sel, matchedHandlers, matchedSelectors,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\tif ( delegateCount &&\n\n\t\t\t// Support: IE <=9\n\t\t\t// Black-hole SVG <use> instance trees (trac-13180)\n\t\t\tcur.nodeType &&\n\n\t\t\t// Support: Firefox <=42\n\t\t\t// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)\n\t\t\t// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click\n\t\t\t// Support: IE 11 only\n\t\t\t// ...but not arrow key \"clicks\" of radio inputs, which can have `button` -1 (gh-2343)\n\t\t\t!( event.type === \"click\" && event.button >= 1 ) ) {\n\n\t\t\tfor ( ; cur !== this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't check non-elements (#13208)\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.nodeType === 1 && !( event.type === \"click\" && cur.disabled === true ) ) {\n\t\t\t\t\tmatchedHandlers = [];\n\t\t\t\t\tmatchedSelectors = {};\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matchedSelectors[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatchedSelectors[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) > -1 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matchedSelectors[ sel ] ) {\n\t\t\t\t\t\t\tmatchedHandlers.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matchedHandlers.length ) {\n\t\t\t\t\t\thandlerQueue.push( { elem: cur, handlers: matchedHandlers } );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tcur = this;\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\taddProp: function( name, hook ) {\n\t\tObject.defineProperty( jQuery.Event.prototype, name, {\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\n\t\t\tget: isFunction( hook ) ?\n\t\t\t\tfunction() {\n\t\t\t\t\tif ( this.originalEvent ) {\n\t\t\t\t\t\t\treturn hook( this.originalEvent );\n\t\t\t\t\t}\n\t\t\t\t} :\n\t\t\t\tfunction() {\n\t\t\t\t\tif ( this.originalEvent ) {\n\t\t\t\t\t\t\treturn this.originalEvent[ name ];\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\tset: function( value ) {\n\t\t\t\tObject.defineProperty( this, name, {\n\t\t\t\t\tenumerable: true,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\twritable: true,\n\t\t\t\t\tvalue: value\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\t},\n\n\tfix: function( originalEvent ) {\n\t\treturn originalEvent[ jQuery.expando ] ?\n\t\t\toriginalEvent :\n\t\t\tnew jQuery.Event( originalEvent );\n\t},\n\n\tspecial: {\n\t\tload: {\n\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tclick: {\n\n\t\t\t// Utilize native event to ensure correct state for checkable inputs\n\t\t\tsetup: function( data ) {\n\n\t\t\t\t// For mutual compressibility with _default, replace `this` access with a local var.\n\t\t\t\t// `|| data` is dead code meant only to preserve the variable through minification.\n\t\t\t\tvar el = this || data;\n\n\t\t\t\t// Claim the first handler\n\t\t\t\tif ( rcheckableType.test( el.type ) &&\n\t\t\t\t\tel.click && nodeName( el, \"input\" ) ) {\n\n\t\t\t\t\t// dataPriv.set( el, \"click\", ... )\n\t\t\t\t\tleverageNative( el, \"click\", returnTrue );\n\t\t\t\t}\n\n\t\t\t\t// Return false to allow normal processing in the caller\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\ttrigger: function( data ) {\n\n\t\t\t\t// For mutual compressibility with _default, replace `this` access with a local var.\n\t\t\t\t// `|| data` is dead code meant only to preserve the variable through minification.\n\t\t\t\tvar el = this || data;\n\n\t\t\t\t// Force setup before triggering a click\n\t\t\t\tif ( rcheckableType.test( el.type ) &&\n\t\t\t\t\tel.click && nodeName( el, \"input\" ) ) {\n\n\t\t\t\t\tleverageNative( el, \"click\" );\n\t\t\t\t}\n\n\t\t\t\t// Return non-false to allow normal event-path propagation\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, suppress native .click() on links\n\t\t\t// Also prevent it if we're currently inside a leveraged native-event stack\n\t\t\t_default: function( event ) {\n\t\t\t\tvar target = event.target;\n\t\t\t\treturn rcheckableType.test( target.type ) &&\n\t\t\t\t\ttarget.click && nodeName( target, \"input\" ) &&\n\t\t\t\t\tdataPriv.get( target, \"click\" ) ||\n\t\t\t\t\tnodeName( target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn't alert if the returnValue field is not set.\n\t\t\t\tif ( event.result !== undefined && event.originalEvent ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Ensure the presence of an event listener that handles manually-triggered\n// synthetic events by interrupting progress until reinvoked in response to\n// *native* events that it fires directly, ensuring that state changes have\n// already occurred before other listeners are invoked.\nfunction leverageNative( el, type, expectSync ) {\n\n\t// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add\n\tif ( !expectSync ) {\n\t\tif ( dataPriv.get( el, type ) === undefined ) {\n\t\t\tjQuery.event.add( el, type, returnTrue );\n\t\t}\n\t\treturn;\n\t}\n\n\t// Register the controller as a special universal handler for all event namespaces\n\tdataPriv.set( el, type, false );\n\tjQuery.event.add( el, type, {\n\t\tnamespace: false,\n\t\thandler: function( event ) {\n\t\t\tvar notAsync, result,\n\t\t\t\tsaved = dataPriv.get( this, type );\n\n\t\t\tif ( ( event.isTrigger & 1 ) && this[ type ] ) {\n\n\t\t\t\t// Interrupt processing of the outer synthetic .trigger()ed event\n\t\t\t\t// Saved data should be false in such cases, but might be a leftover capture object\n\t\t\t\t// from an async native handler (gh-4350)\n\t\t\t\tif ( !saved.length ) {\n\n\t\t\t\t\t// Store arguments for use when handling the inner native event\n\t\t\t\t\t// There will always be at least one argument (an event object), so this array\n\t\t\t\t\t// will not be confused with a leftover capture object.\n\t\t\t\t\tsaved = slice.call( arguments );\n\t\t\t\t\tdataPriv.set( this, type, saved );\n\n\t\t\t\t\t// Trigger the native event and capture its result\n\t\t\t\t\t// Support: IE <=9 - 11+\n\t\t\t\t\t// focus() and blur() are asynchronous\n\t\t\t\t\tnotAsync = expectSync( this, type );\n\t\t\t\t\tthis[ type ]();\n\t\t\t\t\tresult = dataPriv.get( this, type );\n\t\t\t\t\tif ( saved !== result || notAsync ) {\n\t\t\t\t\t\tdataPriv.set( this, type, false );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = {};\n\t\t\t\t\t}\n\t\t\t\t\tif ( saved !== result ) {\n\n\t\t\t\t\t\t// Cancel the outer synthetic event\n\t\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\treturn result.value;\n\t\t\t\t\t}\n\n\t\t\t\t// If this is an inner synthetic event for an event with a bubbling surrogate\n\t\t\t\t// (focus or blur), assume that the surrogate already propagated from triggering the\n\t\t\t\t// native event and prevent that from happening again here.\n\t\t\t\t// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the\n\t\t\t\t// bubbling surrogate propagates *after* the non-bubbling base), but that seems\n\t\t\t\t// less bad than duplication.\n\t\t\t\t} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}\n\n\t\t\t// If this is a native event triggered above, everything is now in order\n\t\t\t// Fire an inner synthetic event with the original arguments\n\t\t\t} else if ( saved.length ) {\n\n\t\t\t\t// ...and capture the result\n\t\t\t\tdataPriv.set( this, type, {\n\t\t\t\t\tvalue: jQuery.event.trigger(\n\n\t\t\t\t\t\t// Support: IE <=9 - 11+\n\t\t\t\t\t\t// Extend with the prototype to reset the above stopImmediatePropagation()\n\t\t\t\t\t\tjQuery.extend( saved[ 0 ], jQuery.Event.prototype ),\n\t\t\t\t\t\tsaved.slice( 1 ),\n\t\t\t\t\t\tthis\n\t\t\t\t\t)\n\t\t\t\t} );\n\n\t\t\t\t// Abort handling of the native event\n\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t}\n\t\t}\n\t} );\n}\n\njQuery.removeEvent = function( elem, type, handle ) {\n\n\t// This \"if\" is needed for plain objects\n\tif ( elem.removeEventListener ) {\n\t\telem.removeEventListener( type, handle );\n\t}\n};\n\njQuery.Event = function( src, props ) {\n\n\t// Allow instantiation without the 'new' keyword\n\tif ( !( this instanceof jQuery.Event ) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = src.defaultPrevented ||\n\t\t\t\tsrc.defaultPrevented === undefined &&\n\n\t\t\t\t// Support: Android <=2.3 only\n\t\t\t\tsrc.returnValue === false ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t\t// Create target properties\n\t\t// Support: Safari <=6 - 7 only\n\t\t// Target should not be a text node (#504, #13143)\n\t\tthis.target = ( src.target && src.target.nodeType === 3 ) ?\n\t\t\tsrc.target.parentNode :\n\t\t\tsrc.target;\n\n\t\tthis.currentTarget = src.currentTarget;\n\t\tthis.relatedTarget = src.relatedTarget;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || Date.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tconstructor: jQuery.Event,\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\tisSimulated: false,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tif ( e && !this.isSimulated ) {\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tif ( e && !this.isSimulated ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\n\t\tif ( e && !this.isSimulated ) {\n\t\t\te.stopImmediatePropagation();\n\t\t}\n\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Includes all common event props including KeyEvent and MouseEvent specific props\njQuery.each( {\n\taltKey: true,\n\tbubbles: true,\n\tcancelable: true,\n\tchangedTouches: true,\n\tctrlKey: true,\n\tdetail: true,\n\teventPhase: true,\n\tmetaKey: true,\n\tpageX: true,\n\tpageY: true,\n\tshiftKey: true,\n\tview: true,\n\t\"char\": true,\n\tcode: true,\n\tcharCode: true,\n\tkey: true,\n\tkeyCode: true,\n\tbutton: true,\n\tbuttons: true,\n\tclientX: true,\n\tclientY: true,\n\toffsetX: true,\n\toffsetY: true,\n\tpointerId: true,\n\tpointerType: true,\n\tscreenX: true,\n\tscreenY: true,\n\ttargetTouches: true,\n\ttoElement: true,\n\ttouches: true,\n\n\twhich: function( event ) {\n\t\tvar button = event.button;\n\n\t\t// Add which for key events\n\t\tif ( event.which == null && rkeyEvent.test( event.type ) ) {\n\t\t\treturn event.charCode != null ? event.charCode : event.keyCode;\n\t\t}\n\n\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\tif ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {\n\t\t\tif ( button & 1 ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\tif ( button & 2 ) {\n\t\t\t\treturn 3;\n\t\t\t}\n\n\t\t\tif ( button & 4 ) {\n\t\t\t\treturn 2;\n\t\t\t}\n\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn event.which;\n\t}\n}, jQuery.event.addProp );\n\njQuery.each( { focus: \"focusin\", blur: \"focusout\" }, function( type, delegateType ) {\n\tjQuery.event.special[ type ] = {\n\n\t\t// Utilize native event if possible so blur/focus sequence is correct\n\t\tsetup: function() {\n\n\t\t\t// Claim the first handler\n\t\t\t// dataPriv.set( this, \"focus\", ... )\n\t\t\t// dataPriv.set( this, \"blur\", ... )\n\t\t\tleverageNative( this, type, expectSync );\n\n\t\t\t// Return false to allow normal processing in the caller\n\t\t\treturn false;\n\t\t},\n\t\ttrigger: function() {\n\n\t\t\t// Force setup before trigger\n\t\t\tleverageNative( this, type );\n\n\t\t\t// Return non-false to allow normal event-path propagation\n\t\t\treturn true;\n\t\t},\n\n\t\tdelegateType: delegateType\n\t};\n} );\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// so that event delegation works in jQuery.\n// Do the same for pointerenter/pointerleave and pointerover/pointerout\n//\n// Support: Safari 7 only\n// Safari sends mouseenter too often; see:\n// https://bugs.chromium.org/p/chromium/issues/detail?id=470258\n// for the description of the bug (it existed in older Chrome versions as well).\njQuery.each( {\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\",\n\tpointerenter: \"pointerover\",\n\tpointerleave: \"pointerout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mouseenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n} );\n\njQuery.fn.extend( {\n\n\ton: function( types, selector, data, fn ) {\n\t\treturn on( this, types, selector, data, fn );\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn on( this, types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ?\n\t\t\t\t\thandleObj.origType + \".\" + handleObj.namespace :\n\t\t\t\t\thandleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t} );\n\t}\n} );\n\n\nvar\n\n\t// Support: IE <=10 - 11, Edge 12 - 13 only\n\t// In IE/Edge using regex groups here causes severe slowdowns.\n\t// See https://connect.microsoft.com/IE/feedback/details/1736512/\n\trnoInnerhtml = /<script|<style|<link/i,\n\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g;\n\n// Prefer a tbody over its parent table for containing new rows\nfunction manipulationTarget( elem, content ) {\n\tif ( nodeName( elem, \"table\" ) &&\n\t\tnodeName( content.nodeType !== 11 ? content : content.firstChild, \"tr\" ) ) {\n\n\t\treturn jQuery( elem ).children( \"tbody\" )[ 0 ] || elem;\n\t}\n\n\treturn elem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = ( elem.getAttribute( \"type\" ) !== null ) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tif ( ( elem.type || \"\" ).slice( 0, 5 ) === \"true/\" ) {\n\t\telem.type = elem.type.slice( 5 );\n\t} else {\n\t\telem.removeAttribute( \"type\" );\n\t}\n\n\treturn elem;\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\tvar i, l, type, pdataOld, udataOld, udataCur, events;\n\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif ( dataPriv.hasData( src ) ) {\n\t\tpdataOld = dataPriv.get( src );\n\t\tevents = pdataOld.events;\n\n\t\tif ( events ) {\n\t\t\tdataPriv.remove( dest, \"handle events\" );\n\n\t\t\tfor ( type in events ) {\n\t\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif ( dataUser.hasData( src ) ) {\n\t\tudataOld = dataUser.access( src );\n\t\tudataCur = jQuery.extend( {}, udataOld );\n\n\t\tdataUser.set( dest, udataCur );\n\t}\n}\n\n// Fix IE bugs, see support tests\nfunction fixInput( src, dest ) {\n\tvar nodeName = dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\tdest.checked = src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\nfunction domManip( collection, args, callback, ignored ) {\n\n\t// Flatten any nested arrays\n\targs = flat( args );\n\n\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\ti = 0,\n\t\tl = collection.length,\n\t\tiNoClone = l - 1,\n\t\tvalue = args[ 0 ],\n\t\tvalueIsFunction = isFunction( value );\n\n\t// We can't cloneNode fragments that contain checked, in WebKit\n\tif ( valueIsFunction ||\n\t\t\t( l > 1 && typeof value === \"string\" &&\n\t\t\t\t!support.checkClone && rchecked.test( value ) ) ) {\n\t\treturn collection.each( function( index ) {\n\t\t\tvar self = collection.eq( index );\n\t\t\tif ( valueIsFunction ) {\n\t\t\t\targs[ 0 ] = value.call( this, index, self.html() );\n\t\t\t}\n\t\t\tdomManip( self, args, callback, ignored );\n\t\t} );\n\t}\n\n\tif ( l ) {\n\t\tfragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );\n\t\tfirst = fragment.firstChild;\n\n\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\tfragment = first;\n\t\t}\n\n\t\t// Require either new content or an interest in ignored elements to invoke the callback\n\t\tif ( first || ignored ) {\n\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\thasScripts = scripts.length;\n\n\t\t\t// Use the original fragment for the last item\n\t\t\t// instead of the first because it can end up\n\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tnode = fragment;\n\n\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\tif ( hasScripts ) {\n\n\t\t\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcallback.call( collection[ i ], node, i );\n\t\t\t}\n\n\t\t\tif ( hasScripts ) {\n\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t// Reenable scripts\n\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t!dataPriv.access( node, \"globalEval\" ) &&\n\t\t\t\t\t\tjQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\tif ( node.src && ( node.type || \"\" ).toLowerCase()  !== \"module\" ) {\n\n\t\t\t\t\t\t\t// Optional AJAX dependency, but won't run scripts if not present\n\t\t\t\t\t\t\tif ( jQuery._evalUrl && !node.noModule ) {\n\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src, {\n\t\t\t\t\t\t\t\t\tnonce: node.nonce || node.getAttribute( \"nonce\" )\n\t\t\t\t\t\t\t\t}, doc );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tDOMEval( node.textContent.replace( rcleanScript, \"\" ), node, doc );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn collection;\n}\n\nfunction remove( elem, selector, keepData ) {\n\tvar node,\n\t\tnodes = selector ? jQuery.filter( selector, elem ) : elem,\n\t\ti = 0;\n\n\tfor ( ; ( node = nodes[ i ] ) != null; i++ ) {\n\t\tif ( !keepData && node.nodeType === 1 ) {\n\t\t\tjQuery.cleanData( getAll( node ) );\n\t\t}\n\n\t\tif ( node.parentNode ) {\n\t\t\tif ( keepData && isAttached( node ) ) {\n\t\t\t\tsetGlobalEval( getAll( node, \"script\" ) );\n\t\t\t}\n\t\t\tnode.parentNode.removeChild( node );\n\t\t}\n\t}\n\n\treturn elem;\n}\n\njQuery.extend( {\n\thtmlPrefilter: function( html ) {\n\t\treturn html;\n\t},\n\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone = elem.cloneNode( true ),\n\t\t\tinPage = isAttached( elem );\n\n\t\t// Fix IE cloning issues\n\t\tif ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&\n\t\t\t\t!jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\tfixInput( srcElements[ i ], destElements[ i ] );\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[ i ], destElements[ i ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tcleanData: function( elems ) {\n\t\tvar data, elem, type,\n\t\t\tspecial = jQuery.event.special,\n\t\t\ti = 0;\n\n\t\tfor ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {\n\t\t\tif ( acceptData( elem ) ) {\n\t\t\t\tif ( ( data = elem[ dataPriv.expando ] ) ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: Chrome <=35 - 45+\n\t\t\t\t\t// Assign undefined instead of using delete, see Data#remove\n\t\t\t\t\telem[ dataPriv.expando ] = undefined;\n\t\t\t\t}\n\t\t\t\tif ( elem[ dataUser.expando ] ) {\n\n\t\t\t\t\t// Support: Chrome <=35 - 45+\n\t\t\t\t\t// Assign undefined instead of using delete, see Data#remove\n\t\t\t\t\telem[ dataUser.expando ] = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n} );\n\njQuery.fn.extend( {\n\tdetach: function( selector ) {\n\t\treturn remove( this, selector, true );\n\t},\n\n\tremove: function( selector ) {\n\t\treturn remove( this, selector );\n\t},\n\n\ttext: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().each( function() {\n\t\t\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\t\t\tthis.textContent = value;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t} );\n\t},\n\n\tprepend: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t} );\n\t},\n\n\tbefore: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t} );\n\t},\n\n\tafter: function() {\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t} );\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; ( elem = this[ i ] ) != null; i++ ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map( function() {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t} );\n\t},\n\n\thtml: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\tvar elem = this[ 0 ] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined && elem.nodeType === 1 ) {\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ] ) {\n\n\t\t\t\tvalue = jQuery.htmlPrefilter( value );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\t\telem = this[ i ] || {};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch ( e ) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar ignored = [];\n\n\t\t// Make the changes, replacing each non-ignored context element with the new content\n\t\treturn domManip( this, arguments, function( elem ) {\n\t\t\tvar parent = this.parentNode;\n\n\t\t\tif ( jQuery.inArray( this, ignored ) < 0 ) {\n\t\t\t\tjQuery.cleanData( getAll( this ) );\n\t\t\t\tif ( parent ) {\n\t\t\t\t\tparent.replaceChild( elem, this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Force callback invocation\n\t\t}, ignored );\n\t}\n} );\n\njQuery.each( {\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1,\n\t\t\ti = 0;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone( true );\n\t\t\tjQuery( insert[ i ] )[ original ]( elems );\n\n\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t// .get() because push.apply(_, arraylike) throws on ancient WebKit\n\t\t\tpush.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n} );\nvar rnumnonpx = new RegExp( \"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\" );\n\nvar getStyles = function( elem ) {\n\n\t\t// Support: IE <=11 only, Firefox <=30 (#15098, #14150)\n\t\t// IE throws on elements created in popups\n\t\t// FF meanwhile throws on frame elements through \"defaultView.getComputedStyle\"\n\t\tvar view = elem.ownerDocument.defaultView;\n\n\t\tif ( !view || !view.opener ) {\n\t\t\tview = window;\n\t\t}\n\n\t\treturn view.getComputedStyle( elem );\n\t};\n\nvar swap = function( elem, options, callback ) {\n\tvar ret, name,\n\t\told = {};\n\n\t// Remember the old values, and insert the new ones\n\tfor ( name in options ) {\n\t\told[ name ] = elem.style[ name ];\n\t\telem.style[ name ] = options[ name ];\n\t}\n\n\tret = callback.call( elem );\n\n\t// Revert the old values\n\tfor ( name in options ) {\n\t\telem.style[ name ] = old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar rboxStyle = new RegExp( cssExpand.join( \"|\" ), \"i\" );\n\n\n\n( function() {\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they're executed at the same time to save the second computation.\n\tfunction computeStyleTests() {\n\n\t\t// This is a singleton, we need to execute it only once\n\t\tif ( !div ) {\n\t\t\treturn;\n\t\t}\n\n\t\tcontainer.style.cssText = \"position:absolute;left:-11111px;width:60px;\" +\n\t\t\t\"margin-top:1px;padding:0;border:0\";\n\t\tdiv.style.cssText =\n\t\t\t\"position:relative;display:block;box-sizing:border-box;overflow:scroll;\" +\n\t\t\t\"margin:auto;border:1px;padding:1px;\" +\n\t\t\t\"width:60%;top:1%\";\n\t\tdocumentElement.appendChild( container ).appendChild( div );\n\n\t\tvar divStyle = window.getComputedStyle( div );\n\t\tpixelPositionVal = divStyle.top !== \"1%\";\n\n\t\t// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44\n\t\treliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;\n\n\t\t// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3\n\t\t// Some styles come back with percentage values, even though they shouldn't\n\t\tdiv.style.right = \"60%\";\n\t\tpixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;\n\n\t\t// Support: IE 9 - 11 only\n\t\t// Detect misreporting of content dimensions for box-sizing:border-box elements\n\t\tboxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;\n\n\t\t// Support: IE 9 only\n\t\t// Detect overflow:scroll screwiness (gh-3699)\n\t\t// Support: Chrome <=64\n\t\t// Don't get tricked when zoom affects offsetWidth (gh-4029)\n\t\tdiv.style.position = \"absolute\";\n\t\tscrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;\n\n\t\tdocumentElement.removeChild( container );\n\n\t\t// Nullify the div so it wouldn't be stored in the memory and\n\t\t// it will also be a sign that checks already performed\n\t\tdiv = null;\n\t}\n\n\tfunction roundPixelMeasures( measure ) {\n\t\treturn Math.round( parseFloat( measure ) );\n\t}\n\n\tvar pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,\n\t\treliableTrDimensionsVal, reliableMarginLeftVal,\n\t\tcontainer = document.createElement( \"div\" ),\n\t\tdiv = document.createElement( \"div\" );\n\n\t// Finish early in limited (non-browser) environments\n\tif ( !div.style ) {\n\t\treturn;\n\t}\n\n\t// Support: IE <=9 - 11 only\n\t// Style of cloned element affects source element cloned (#8908)\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\tjQuery.extend( support, {\n\t\tboxSizingReliable: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn boxSizingReliableVal;\n\t\t},\n\t\tpixelBoxStyles: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn pixelBoxStylesVal;\n\t\t},\n\t\tpixelPosition: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn pixelPositionVal;\n\t\t},\n\t\treliableMarginLeft: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn reliableMarginLeftVal;\n\t\t},\n\t\tscrollboxSize: function() {\n\t\t\tcomputeStyleTests();\n\t\t\treturn scrollboxSizeVal;\n\t\t},\n\n\t\t// Support: IE 9 - 11+, Edge 15 - 18+\n\t\t// IE/Edge misreport `getComputedStyle` of table rows with width/height\n\t\t// set in CSS while `offset*` properties report correct values.\n\t\t// Behavior in IE 9 is more subtle than in newer versions & it passes\n\t\t// some versions of this test; make sure not to make it pass there!\n\t\treliableTrDimensions: function() {\n\t\t\tvar table, tr, trChild, trStyle;\n\t\t\tif ( reliableTrDimensionsVal == null ) {\n\t\t\t\ttable = document.createElement( \"table\" );\n\t\t\t\ttr = document.createElement( \"tr\" );\n\t\t\t\ttrChild = document.createElement( \"div\" );\n\n\t\t\t\ttable.style.cssText = \"position:absolute;left:-11111px\";\n\t\t\t\ttr.style.height = \"1px\";\n\t\t\t\ttrChild.style.height = \"9px\";\n\n\t\t\t\tdocumentElement\n\t\t\t\t\t.appendChild( table )\n\t\t\t\t\t.appendChild( tr )\n\t\t\t\t\t.appendChild( trChild );\n\n\t\t\t\ttrStyle = window.getComputedStyle( tr );\n\t\t\t\treliableTrDimensionsVal = parseInt( trStyle.height ) > 3;\n\n\t\t\t\tdocumentElement.removeChild( table );\n\t\t\t}\n\t\t\treturn reliableTrDimensionsVal;\n\t\t}\n\t} );\n} )();\n\n\nfunction curCSS( elem, name, computed ) {\n\tvar width, minWidth, maxWidth, ret,\n\n\t\t// Support: Firefox 51+\n\t\t// Retrieving style before computed somehow\n\t\t// fixes an issue with getting wrong values\n\t\t// on detached elements\n\t\tstyle = elem.style;\n\n\tcomputed = computed || getStyles( elem );\n\n\t// getPropertyValue is needed for:\n\t//   .css('filter') (IE 9 only, #12537)\n\t//   .css('--customProperty) (#3144)\n\tif ( computed ) {\n\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\n\t\tif ( ret === \"\" && !isAttached( elem ) ) {\n\t\t\tret = jQuery.style( elem, name );\n\t\t}\n\n\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t// Android Browser returns percentage for some values,\n\t\t// but width seems to be reliably pixels.\n\t\t// This is against the CSSOM draft spec:\n\t\t// https://drafts.csswg.org/cssom/#resolved-values\n\t\tif ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\twidth = style.width;\n\t\t\tminWidth = style.minWidth;\n\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\tret = computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width = width;\n\t\t\tstyle.minWidth = minWidth;\n\t\t\tstyle.maxWidth = maxWidth;\n\t\t}\n\t}\n\n\treturn ret !== undefined ?\n\n\t\t// Support: IE <=9 - 11 only\n\t\t// IE returns zIndex value as an integer.\n\t\tret + \"\" :\n\t\tret;\n}\n\n\nfunction addGetHookIf( conditionFn, hookFn ) {\n\n\t// Define the hook, we'll check on the first run if it's really needed.\n\treturn {\n\t\tget: function() {\n\t\t\tif ( conditionFn() ) {\n\n\t\t\t\t// Hook not needed (or it's not possible to use it due\n\t\t\t\t// to missing dependency), remove it.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\t\t\treturn ( this.get = hookFn ).apply( this, arguments );\n\t\t}\n\t};\n}\n\n\nvar cssPrefixes = [ \"Webkit\", \"Moz\", \"ms\" ],\n\temptyStyle = document.createElement( \"div\" ).style,\n\tvendorProps = {};\n\n// Return a vendor-prefixed property or undefined\nfunction vendorPropName( name ) {\n\n\t// Check for vendor prefixed names\n\tvar capName = name[ 0 ].toUpperCase() + name.slice( 1 ),\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in emptyStyle ) {\n\t\t\treturn name;\n\t\t}\n\t}\n}\n\n// Return a potentially-mapped jQuery.cssProps or vendor prefixed property\nfunction finalPropName( name ) {\n\tvar final = jQuery.cssProps[ name ] || vendorProps[ name ];\n\n\tif ( final ) {\n\t\treturn final;\n\t}\n\tif ( name in emptyStyle ) {\n\t\treturn name;\n\t}\n\treturn vendorProps[ name ] = vendorPropName( name ) || name;\n}\n\n\nvar\n\n\t// Swappable if display is none or starts with table\n\t// except \"table\", \"table-cell\", or \"table-caption\"\n\t// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trcustomProp = /^--/,\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: \"0\",\n\t\tfontWeight: \"400\"\n\t};\n\nfunction setPositiveNumber( _elem, value, subtract ) {\n\n\t// Any relative (+/-) values have already been\n\t// normalized at this point\n\tvar matches = rcssNum.exec( value );\n\treturn matches ?\n\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {\n\tvar i = dimension === \"width\" ? 1 : 0,\n\t\textra = 0,\n\t\tdelta = 0;\n\n\t// Adjustment may not be necessary\n\tif ( box === ( isBorderBox ? \"border\" : \"content\" ) ) {\n\t\treturn 0;\n\t}\n\n\tfor ( ; i < 4; i += 2 ) {\n\n\t\t// Both box models exclude margin\n\t\tif ( box === \"margin\" ) {\n\t\t\tdelta += jQuery.css( elem, box + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\t// If we get here with a content-box, we're seeking \"padding\" or \"border\" or \"margin\"\n\t\tif ( !isBorderBox ) {\n\n\t\t\t// Add padding\n\t\t\tdelta += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// For \"border\" or \"margin\", add border\n\t\t\tif ( box !== \"padding\" ) {\n\t\t\t\tdelta += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\n\t\t\t// But still keep track of it otherwise\n\t\t\t} else {\n\t\t\t\textra += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\n\t\t// If we get here with a border-box (content + padding + border), we're seeking \"content\" or\n\t\t// \"padding\" or \"margin\"\n\t\t} else {\n\n\t\t\t// For \"content\", subtract padding\n\t\t\tif ( box === \"content\" ) {\n\t\t\t\tdelta -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// For \"content\" or \"padding\", subtract border\n\t\t\tif ( box !== \"margin\" ) {\n\t\t\t\tdelta -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Account for positive content-box scroll gutter when requested by providing computedVal\n\tif ( !isBorderBox && computedVal >= 0 ) {\n\n\t\t// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border\n\t\t// Assuming integer scroll gutter, subtract the rest and round down\n\t\tdelta += Math.max( 0, Math.ceil(\n\t\t\telem[ \"offset\" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -\n\t\t\tcomputedVal -\n\t\t\tdelta -\n\t\t\textra -\n\t\t\t0.5\n\n\t\t// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter\n\t\t// Use an explicit zero to avoid NaN (gh-3964)\n\t\t) ) || 0;\n\t}\n\n\treturn delta;\n}\n\nfunction getWidthOrHeight( elem, dimension, extra ) {\n\n\t// Start with computed style\n\tvar styles = getStyles( elem ),\n\n\t\t// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).\n\t\t// Fake content-box until we know it's needed to know the true value.\n\t\tboxSizingNeeded = !support.boxSizingReliable() || extra,\n\t\tisBorderBox = boxSizingNeeded &&\n\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\tvalueIsBorderBox = isBorderBox,\n\n\t\tval = curCSS( elem, dimension, styles ),\n\t\toffsetProp = \"offset\" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );\n\n\t// Support: Firefox <=54\n\t// Return a confounding non-pixel value or feign ignorance, as appropriate.\n\tif ( rnumnonpx.test( val ) ) {\n\t\tif ( !extra ) {\n\t\t\treturn val;\n\t\t}\n\t\tval = \"auto\";\n\t}\n\n\n\t// Support: IE 9 - 11 only\n\t// Use offsetWidth/offsetHeight for when box sizing is unreliable.\n\t// In those cases, the computed value can be trusted to be border-box.\n\tif ( ( !support.boxSizingReliable() && isBorderBox ||\n\n\t\t// Support: IE 10 - 11+, Edge 15 - 18+\n\t\t// IE/Edge misreport `getComputedStyle` of table rows with width/height\n\t\t// set in CSS while `offset*` properties report correct values.\n\t\t// Interestingly, in some cases IE 9 doesn't suffer from this issue.\n\t\t!support.reliableTrDimensions() && nodeName( elem, \"tr\" ) ||\n\n\t\t// Fall back to offsetWidth/offsetHeight when value is \"auto\"\n\t\t// This happens for inline elements with no explicit setting (gh-3571)\n\t\tval === \"auto\" ||\n\n\t\t// Support: Android <=4.1 - 4.3 only\n\t\t// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)\n\t\t!parseFloat( val ) && jQuery.css( elem, \"display\", false, styles ) === \"inline\" ) &&\n\n\t\t// Make sure the element is visible & connected\n\t\telem.getClientRects().length ) {\n\n\t\tisBorderBox = jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t\t// Where available, offsetWidth/offsetHeight approximate border box dimensions.\n\t\t// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the\n\t\t// retrieved value as a content box dimension.\n\t\tvalueIsBorderBox = offsetProp in elem;\n\t\tif ( valueIsBorderBox ) {\n\t\t\tval = elem[ offsetProp ];\n\t\t}\n\t}\n\n\t// Normalize \"\" and auto\n\tval = parseFloat( val ) || 0;\n\n\t// Adjust for the element's box model\n\treturn ( val +\n\t\tboxModelAdjustment(\n\t\t\telem,\n\t\t\tdimension,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles,\n\n\t\t\t// Provide the current computed size to request scroll gutter calculation (gh-3589)\n\t\t\tval\n\t\t)\n\t) + \"px\";\n}\n\njQuery.extend( {\n\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"animationIterationCount\": true,\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"flexGrow\": true,\n\t\t\"flexShrink\": true,\n\t\t\"fontWeight\": true,\n\t\t\"gridArea\": true,\n\t\t\"gridColumn\": true,\n\t\t\"gridColumnEnd\": true,\n\t\t\"gridColumnStart\": true,\n\t\t\"gridRow\": true,\n\t\t\"gridRowEnd\": true,\n\t\t\"gridRowStart\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = camelCase( name ),\n\t\t\tisCustomProp = rcustomProp.test( name ),\n\t\t\tstyle = elem.style;\n\n\t\t// Make sure that we're working with the right name. We don't\n\t\t// want to query the value if it is a CSS custom property\n\t\t// since they are user-defined.\n\t\tif ( !isCustomProp ) {\n\t\t\tname = finalPropName( origName );\n\t\t}\n\n\t\t// Gets hook for the prefixed version, then unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// Convert \"+=\" or \"-=\" to relative numbers (#7345)\n\t\t\tif ( type === \"string\" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {\n\t\t\t\tvalue = adjustCSS( elem, name, ret );\n\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren't set (#7116)\n\t\t\tif ( value == null || value !== value ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add the unit (except for certain CSS properties)\n\t\t\t// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append\n\t\t\t// \"px\" to a few hardcoded values.\n\t\t\tif ( type === \"number\" && !isCustomProp ) {\n\t\t\t\tvalue += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? \"\" : \"px\" );\n\t\t\t}\n\n\t\t\t// background-* props affect original clone's values\n\t\t\tif ( !support.clearCloneStyle && value === \"\" && name.indexOf( \"background\" ) === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !( \"set\" in hooks ) ||\n\t\t\t\t( value = hooks.set( elem, value, extra ) ) !== undefined ) {\n\n\t\t\t\tif ( isCustomProp ) {\n\t\t\t\t\tstyle.setProperty( name, value );\n\t\t\t\t} else {\n\t\t\t\t\tstyle[ name ] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks &&\n\t\t\t\t( ret = hooks.get( elem, false, extra ) ) !== undefined ) {\n\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = camelCase( name ),\n\t\t\tisCustomProp = rcustomProp.test( name );\n\n\t\t// Make sure that we're working with the right name. We don't\n\t\t// want to modify the value if it is a CSS custom property\n\t\t// since they are user-defined.\n\t\tif ( !isCustomProp ) {\n\t\t\tname = finalPropName( origName );\n\t\t}\n\n\t\t// Try prefixed name followed by the unprefixed name\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t// Convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Make numeric if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || isFinite( num ) ? num || 0 : val;\n\t\t}\n\n\t\treturn val;\n\t}\n} );\n\njQuery.each( [ \"height\", \"width\" ], function( _i, dimension ) {\n\tjQuery.cssHooks[ dimension ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\n\t\t\t\t// Certain elements can have dimension info if we invisibly show them\n\t\t\t\t// but it must have a current display style that would benefit\n\t\t\t\treturn rdisplayswap.test( jQuery.css( elem, \"display\" ) ) &&\n\n\t\t\t\t\t// Support: Safari 8+\n\t\t\t\t\t// Table columns in Safari have non-zero offsetWidth & zero\n\t\t\t\t\t// getBoundingClientRect().width unless display is changed.\n\t\t\t\t\t// Support: IE <=11 only\n\t\t\t\t\t// Running getBoundingClientRect on a disconnected node\n\t\t\t\t\t// in IE throws an error.\n\t\t\t\t\t( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?\n\t\t\t\t\t\tswap( elem, cssShow, function() {\n\t\t\t\t\t\t\treturn getWidthOrHeight( elem, dimension, extra );\n\t\t\t\t\t\t} ) :\n\t\t\t\t\t\tgetWidthOrHeight( elem, dimension, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar matches,\n\t\t\t\tstyles = getStyles( elem ),\n\n\t\t\t\t// Only read styles.position if the test has a chance to fail\n\t\t\t\t// to avoid forcing a reflow.\n\t\t\t\tscrollboxSizeBuggy = !support.scrollboxSize() &&\n\t\t\t\t\tstyles.position === \"absolute\",\n\n\t\t\t\t// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)\n\t\t\t\tboxSizingNeeded = scrollboxSizeBuggy || extra,\n\t\t\t\tisBorderBox = boxSizingNeeded &&\n\t\t\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\tsubtract = extra ?\n\t\t\t\t\tboxModelAdjustment(\n\t\t\t\t\t\telem,\n\t\t\t\t\t\tdimension,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\tisBorderBox,\n\t\t\t\t\t\tstyles\n\t\t\t\t\t) :\n\t\t\t\t\t0;\n\n\t\t\t// Account for unreliable border-box dimensions by comparing offset* to computed and\n\t\t\t// faking a content-box to get border and padding (gh-3699)\n\t\t\tif ( isBorderBox && scrollboxSizeBuggy ) {\n\t\t\t\tsubtract -= Math.ceil(\n\t\t\t\t\telem[ \"offset\" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -\n\t\t\t\t\tparseFloat( styles[ dimension ] ) -\n\t\t\t\t\tboxModelAdjustment( elem, dimension, \"border\", false, styles ) -\n\t\t\t\t\t0.5\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Convert to pixels if value adjustment is needed\n\t\t\tif ( subtract && ( matches = rcssNum.exec( value ) ) &&\n\t\t\t\t( matches[ 3 ] || \"px\" ) !== \"px\" ) {\n\n\t\t\t\telem.style[ dimension ] = value;\n\t\t\t\tvalue = jQuery.css( elem, dimension );\n\t\t\t}\n\n\t\t\treturn setPositiveNumber( elem, value, subtract );\n\t\t}\n\t};\n} );\n\njQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,\n\tfunction( elem, computed ) {\n\t\tif ( computed ) {\n\t\t\treturn ( parseFloat( curCSS( elem, \"marginLeft\" ) ) ||\n\t\t\t\telem.getBoundingClientRect().left -\n\t\t\t\t\tswap( elem, { marginLeft: 0 }, function() {\n\t\t\t\t\t\treturn elem.getBoundingClientRect().left;\n\t\t\t\t\t} )\n\t\t\t\t) + \"px\";\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each( {\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// Assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split( \" \" ) : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( prefix !== \"margin\" ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n} );\n\njQuery.fn.extend( {\n\tcss: function( name, value ) {\n\t\treturn access( this, function( elem, name, value ) {\n\t\t\tvar styles, len,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( Array.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t}\n} );\n\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || jQuery.easing._default;\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\t// Use a property on the element directly when it is not a DOM element,\n\t\t\t// or when there is no matching style property that exists.\n\t\t\tif ( tween.elem.nodeType !== 1 ||\n\t\t\t\ttween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// Passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails.\n\t\t\t// Simple values such as \"10px\" are parsed to Float;\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as-is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\n\t\t\t// Use step hook for back compat.\n\t\t\t// Use cssHook if its there.\n\t\t\t// Use .style if available and use plain properties where available.\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.nodeType === 1 && (\n\t\t\t\t\tjQuery.cssHooks[ tween.prop ] ||\n\t\t\t\t\ttween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE <=9 only\n// Panic based approach to setting things on disconnected nodes\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p * Math.PI ) / 2;\n\t},\n\t_default: \"swing\"\n};\n\njQuery.fx = Tween.prototype.init;\n\n// Back compat <1.8 extension point\njQuery.fx.step = {};\n\n\n\n\nvar\n\tfxNow, inProgress,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trrun = /queueHooks$/;\n\nfunction schedule() {\n\tif ( inProgress ) {\n\t\tif ( document.hidden === false && window.requestAnimationFrame ) {\n\t\t\twindow.requestAnimationFrame( schedule );\n\t\t} else {\n\t\t\twindow.setTimeout( schedule, jQuery.fx.interval );\n\t\t}\n\n\t\tjQuery.fx.tick();\n\t}\n}\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\twindow.setTimeout( function() {\n\t\tfxNow = undefined;\n\t} );\n\treturn ( fxNow = Date.now() );\n}\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\ti = 0,\n\t\tattrs = { height: type };\n\n\t// If we include width, step value is 1 to do all cssExpand values,\n\t// otherwise step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth ? 1 : 0;\n\tfor ( ; i < 4; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {\n\n\t\t\t// We're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter( elem, props, opts ) {\n\tvar prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,\n\t\tisBox = \"width\" in props || \"height\" in props,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHiddenWithinTree( elem ),\n\t\tdataShow = dataPriv.get( elem, \"fxshow\" );\n\n\t// Queue-skipping animations hijack the fx hooks\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always( function() {\n\n\t\t\t// Ensure the complete handler is called before this completes\n\t\t\tanim.always( function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\t}\n\n\t// Detect show/hide animations\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.test( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\n\t\t\t\t// Pretend to be hidden if this is a \"show\" and\n\t\t\t\t// there is still data from a stopped show/hide\n\t\t\t\tif ( value === \"show\" && dataShow && dataShow[ prop ] !== undefined ) {\n\t\t\t\t\thidden = true;\n\n\t\t\t\t// Ignore all other no-op show/hide data\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\t\t}\n\t}\n\n\t// Bail out if this is a no-op like .hide().hide()\n\tpropTween = !jQuery.isEmptyObject( props );\n\tif ( !propTween && jQuery.isEmptyObject( orig ) ) {\n\t\treturn;\n\t}\n\n\t// Restrict \"overflow\" and \"display\" styles during box animations\n\tif ( isBox && elem.nodeType === 1 ) {\n\n\t\t// Support: IE <=9 - 11, Edge 12 - 15\n\t\t// Record all 3 overflow attributes because IE does not infer the shorthand\n\t\t// from identically-valued overflowX and overflowY and Edge just mirrors\n\t\t// the overflowX value there.\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Identify a display type, preferring old show/hide data over the CSS cascade\n\t\trestoreDisplay = dataShow && dataShow.display;\n\t\tif ( restoreDisplay == null ) {\n\t\t\trestoreDisplay = dataPriv.get( elem, \"display\" );\n\t\t}\n\t\tdisplay = jQuery.css( elem, \"display\" );\n\t\tif ( display === \"none\" ) {\n\t\t\tif ( restoreDisplay ) {\n\t\t\t\tdisplay = restoreDisplay;\n\t\t\t} else {\n\n\t\t\t\t// Get nonempty value(s) by temporarily forcing visibility\n\t\t\t\tshowHide( [ elem ], true );\n\t\t\t\trestoreDisplay = elem.style.display || restoreDisplay;\n\t\t\t\tdisplay = jQuery.css( elem, \"display\" );\n\t\t\t\tshowHide( [ elem ] );\n\t\t\t}\n\t\t}\n\n\t\t// Animate inline elements as inline-block\n\t\tif ( display === \"inline\" || display === \"inline-block\" && restoreDisplay != null ) {\n\t\t\tif ( jQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\t\t// Restore the original display value at the end of pure show/hide animations\n\t\t\t\tif ( !propTween ) {\n\t\t\t\t\tanim.done( function() {\n\t\t\t\t\t\tstyle.display = restoreDisplay;\n\t\t\t\t\t} );\n\t\t\t\t\tif ( restoreDisplay == null ) {\n\t\t\t\t\t\tdisplay = style.display;\n\t\t\t\t\t\trestoreDisplay = display === \"none\" ? \"\" : display;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstyle.display = \"inline-block\";\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tanim.always( function() {\n\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t} );\n\t}\n\n\t// Implement show/hide animations\n\tpropTween = false;\n\tfor ( prop in orig ) {\n\n\t\t// General show/hide setup for this element animation\n\t\tif ( !propTween ) {\n\t\t\tif ( dataShow ) {\n\t\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\t\thidden = dataShow.hidden;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdataShow = dataPriv.access( elem, \"fxshow\", { display: restoreDisplay } );\n\t\t\t}\n\n\t\t\t// Store hidden/visible for toggle so `.stop().toggle()` \"reverses\"\n\t\t\tif ( toggle ) {\n\t\t\t\tdataShow.hidden = !hidden;\n\t\t\t}\n\n\t\t\t// Show elements before animating them\n\t\t\tif ( hidden ) {\n\t\t\t\tshowHide( [ elem ], true );\n\t\t\t}\n\n\t\t\t/* eslint-disable no-loop-func */\n\n\t\t\tanim.done( function() {\n\n\t\t\t/* eslint-enable no-loop-func */\n\n\t\t\t\t// The final step of a \"hide\" animation is actually hiding the element\n\t\t\t\tif ( !hidden ) {\n\t\t\t\t\tshowHide( [ elem ] );\n\t\t\t\t}\n\t\t\t\tdataPriv.remove( elem, \"fxshow\" );\n\t\t\t\tfor ( prop in orig ) {\n\t\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\t// Per-property setup\n\t\tpropTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\t\tif ( !( prop in dataShow ) ) {\n\t\t\tdataShow[ prop ] = propTween.start;\n\t\t\tif ( hidden ) {\n\t\t\t\tpropTween.end = propTween.start;\n\t\t\t\tpropTween.start = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( Array.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// Not quite $.extend, this won't overwrite existing keys.\n\t\t\t// Reusing 'index' because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = Animation.prefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\n\t\t\t// Don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t} ),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\n\t\t\t\t// Support: Android 2.3 only\n\t\t\t\t// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ] );\n\n\t\t\t// If there's more to do, yield\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t}\n\n\t\t\t// If this was an empty animation, synthesize a final progress notification\n\t\t\tif ( !length ) {\n\t\t\t\tdeferred.notifyWith( elem, [ animation, 1, 0 ] );\n\t\t\t}\n\n\t\t\t// Resolve the animation and report its conclusion\n\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\treturn false;\n\t\t},\n\t\tanimation = deferred.promise( {\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, {\n\t\t\t\tspecialEasing: {},\n\t\t\t\teasing: jQuery.easing._default\n\t\t\t}, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\n\t\t\t\t\t// If we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// Resolve when we played the last frame; otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.notifyWith( elem, [ animation, 1, 0 ] );\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t} ),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length; index++ ) {\n\t\tresult = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\tif ( isFunction( result.stop ) ) {\n\t\t\t\tjQuery._queueHooks( animation.elem, animation.opts.queue ).stop =\n\t\t\t\t\tresult.stop.bind( result );\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\t// Attach callbacks from options\n\tanimation\n\t\t.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t} )\n\t);\n\n\treturn animation;\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweeners: {\n\t\t\"*\": [ function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value );\n\t\t\tadjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );\n\t\t\treturn tween;\n\t\t} ]\n\t},\n\n\ttweener: function( props, callback ) {\n\t\tif ( isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.match( rnothtmlwhite );\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\tAnimation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];\n\t\t\tAnimation.tweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilters: [ defaultPrefilter ],\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tAnimation.prefilters.unshift( callback );\n\t\t} else {\n\t\t\tAnimation.prefilters.push( callback );\n\t\t}\n\t}\n} );\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tisFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !isFunction( easing ) && easing\n\t};\n\n\t// Go to the end state if fx are off\n\tif ( jQuery.fx.off ) {\n\t\topt.duration = 0;\n\n\t} else {\n\t\tif ( typeof opt.duration !== \"number\" ) {\n\t\t\tif ( opt.duration in jQuery.fx.speeds ) {\n\t\t\t\topt.duration = jQuery.fx.speeds[ opt.duration ];\n\n\t\t\t} else {\n\t\t\t\topt.duration = jQuery.fx.speeds._default;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend( {\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// Show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHiddenWithinTree ).css( \"opacity\", 0 ).show()\n\n\t\t\t// Animate to the value specified\n\t\t\t.end().animate( { opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || dataPriv.get( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = dataPriv.get( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this &&\n\t\t\t\t\t( type == null || timers[ index ].queue === type ) ) {\n\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Start the next in the queue if the last step wasn't forced.\n\t\t\t// Timers currently will call their complete callbacks, which\n\t\t\t// will dequeue but only if they were gotoEnd.\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t} );\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tvar index,\n\t\t\t\tdata = dataPriv.get( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// Enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// Empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// Look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t} );\n\t}\n} );\n\njQuery.each( [ \"toggle\", \"show\", \"hide\" ], function( _i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n} );\n\n// Generate shortcuts for custom animations\njQuery.each( {\n\tslideDown: genFx( \"show\" ),\n\tslideUp: genFx( \"hide\" ),\n\tslideToggle: genFx( \"toggle\" ),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n} );\n\njQuery.timers = [];\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ti = 0,\n\t\ttimers = jQuery.timers;\n\n\tfxNow = Date.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\n\t\t// Run the timer and safely remove it when done (allowing for external removal)\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tjQuery.timers.push( timer );\n\tjQuery.fx.start();\n};\n\njQuery.fx.interval = 13;\njQuery.fx.start = function() {\n\tif ( inProgress ) {\n\t\treturn;\n\t}\n\n\tinProgress = true;\n\tschedule();\n};\n\njQuery.fx.stop = function() {\n\tinProgress = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\n// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/\njQuery.fn.delay = function( time, type ) {\n\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\ttype = type || \"fx\";\n\n\treturn this.queue( type, function( next, hooks ) {\n\t\tvar timeout = window.setTimeout( next, time );\n\t\thooks.stop = function() {\n\t\t\twindow.clearTimeout( timeout );\n\t\t};\n\t} );\n};\n\n\n( function() {\n\tvar input = document.createElement( \"input\" ),\n\t\tselect = document.createElement( \"select\" ),\n\t\topt = select.appendChild( document.createElement( \"option\" ) );\n\n\tinput.type = \"checkbox\";\n\n\t// Support: Android <=4.3 only\n\t// Default value for a checkbox should be \"on\"\n\tsupport.checkOn = input.value !== \"\";\n\n\t// Support: IE <=11 only\n\t// Must access selectedIndex to make default options select\n\tsupport.optSelected = opt.selected;\n\n\t// Support: IE <=11 only\n\t// An input loses its value after becoming a radio\n\tinput = document.createElement( \"input\" );\n\tinput.value = \"t\";\n\tinput.type = \"radio\";\n\tsupport.radioValue = input.value === \"t\";\n} )();\n\n\nvar boolHook,\n\tattrHandle = jQuery.expr.attrHandle;\n\njQuery.fn.extend( {\n\tattr: function( name, value ) {\n\t\treturn access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each( function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t} );\n\t}\n} );\n\njQuery.extend( {\n\tattr: function( elem, name, value ) {\n\t\tvar ret, hooks,\n\t\t\tnType = elem.nodeType;\n\n\t\t// Don't get/set attributes on text, comment and attribute nodes\n\t\tif ( nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === \"undefined\" ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// Attribute hooks are determined by the lowercase version\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\thooks = jQuery.attrHooks[ name.toLowerCase() ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( hooks && \"set\" in hooks &&\n\t\t\t\t( ret = hooks.set( elem, value, name ) ) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\treturn value;\n\t\t}\n\n\t\tif ( hooks && \"get\" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tret = jQuery.find.attr( elem, name );\n\n\t\t// Non-existent attributes return null, we normalize to undefined\n\t\treturn ret == null ? undefined : ret;\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !support.radioValue && value === \"radio\" &&\n\t\t\t\t\tnodeName( elem, \"input\" ) ) {\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name,\n\t\t\ti = 0,\n\n\t\t\t// Attribute names can contain non-HTML whitespace characters\n\t\t\t// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2\n\t\t\tattrNames = value && value.match( rnothtmlwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( ( name = attrNames[ i++ ] ) ) {\n\t\t\t\telem.removeAttribute( name );\n\t\t\t}\n\t\t}\n\t}\n} );\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\telem.setAttribute( name, name );\n\t\t}\n\t\treturn name;\n\t}\n};\n\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( _i, name ) {\n\tvar getter = attrHandle[ name ] || jQuery.find.attr;\n\n\tattrHandle[ name ] = function( elem, name, isXML ) {\n\t\tvar ret, handle,\n\t\t\tlowercaseName = name.toLowerCase();\n\n\t\tif ( !isXML ) {\n\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle = attrHandle[ lowercaseName ];\n\t\t\tattrHandle[ lowercaseName ] = ret;\n\t\t\tret = getter( elem, name, isXML ) != null ?\n\t\t\t\tlowercaseName :\n\t\t\t\tnull;\n\t\t\tattrHandle[ lowercaseName ] = handle;\n\t\t}\n\t\treturn ret;\n\t};\n} );\n\n\n\n\nvar rfocusable = /^(?:input|select|textarea|button)$/i,\n\trclickable = /^(?:a|area)$/i;\n\njQuery.fn.extend( {\n\tprop: function( name, value ) {\n\t\treturn access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\treturn this.each( function() {\n\t\t\tdelete this[ jQuery.propFix[ name ] || name ];\n\t\t} );\n\t}\n} );\n\njQuery.extend( {\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks,\n\t\t\tnType = elem.nodeType;\n\n\t\t// Don't get/set properties on text, comment and attribute nodes\n\t\tif ( nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\tif ( hooks && \"set\" in hooks &&\n\t\t\t\t( ret = hooks.set( elem, value, name ) ) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\treturn ( elem[ name ] = value );\n\t\t}\n\n\t\tif ( hooks && \"get\" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {\n\t\t\treturn ret;\n\t\t}\n\n\t\treturn elem[ name ];\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\n\t\t\t\t// Support: IE <=9 - 11 only\n\t\t\t\t// elem.tabIndex doesn't always return the\n\t\t\t\t// correct value when it hasn't been explicitly set\n\t\t\t\t// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/\n\t\t\t\t// Use proper attribute retrieval(#12072)\n\t\t\t\tvar tabindex = jQuery.find.attr( elem, \"tabindex\" );\n\n\t\t\t\tif ( tabindex ) {\n\t\t\t\t\treturn parseInt( tabindex, 10 );\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\trfocusable.test( elem.nodeName ) ||\n\t\t\t\t\trclickable.test( elem.nodeName ) &&\n\t\t\t\t\telem.href\n\t\t\t\t) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t},\n\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t}\n} );\n\n// Support: IE <=11 only\n// Accessing the selectedIndex property\n// forces the browser to respect setting selected\n// on the option\n// The getter ensures a default option is selected\n// when in an optgroup\n// eslint rule \"no-unused-expressions\" is disabled for this code\n// since it considers such accessions noop\nif ( !support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\n\t\t\t/* eslint no-unused-expressions: \"off\" */\n\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent && parent.parentNode ) {\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t\tset: function( elem ) {\n\n\t\t\t/* eslint no-unused-expressions: \"off\" */\n\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent ) {\n\t\t\t\tparent.selectedIndex;\n\n\t\t\t\tif ( parent.parentNode ) {\n\t\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\njQuery.each( [\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n} );\n\n\n\n\n\t// Strip and collapse whitespace according to HTML spec\n\t// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace\n\tfunction stripAndCollapse( value ) {\n\t\tvar tokens = value.match( rnothtmlwhite ) || [];\n\t\treturn tokens.join( \" \" );\n\t}\n\n\nfunction getClass( elem ) {\n\treturn elem.getAttribute && elem.getAttribute( \"class\" ) || \"\";\n}\n\nfunction classesToArray( value ) {\n\tif ( Array.isArray( value ) ) {\n\t\treturn value;\n\t}\n\tif ( typeof value === \"string\" ) {\n\t\treturn value.match( rnothtmlwhite ) || [];\n\t}\n\treturn [];\n}\n\njQuery.fn.extend( {\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, curValue, clazz, j, finalValue,\n\t\t\ti = 0;\n\n\t\tif ( isFunction( value ) ) {\n\t\t\treturn this.each( function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, getClass( this ) ) );\n\t\t\t} );\n\t\t}\n\n\t\tclasses = classesToArray( value );\n\n\t\tif ( classes.length ) {\n\t\t\twhile ( ( elem = this[ i++ ] ) ) {\n\t\t\t\tcurValue = getClass( elem );\n\t\t\t\tcur = elem.nodeType === 1 && ( \" \" + stripAndCollapse( curValue ) + \" \" );\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( ( clazz = classes[ j++ ] ) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = stripAndCollapse( cur );\n\t\t\t\t\tif ( curValue !== finalValue ) {\n\t\t\t\t\t\telem.setAttribute( \"class\", finalValue );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, curValue, clazz, j, finalValue,\n\t\t\ti = 0;\n\n\t\tif ( isFunction( value ) ) {\n\t\t\treturn this.each( function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );\n\t\t\t} );\n\t\t}\n\n\t\tif ( !arguments.length ) {\n\t\t\treturn this.attr( \"class\", \"\" );\n\t\t}\n\n\t\tclasses = classesToArray( value );\n\n\t\tif ( classes.length ) {\n\t\t\twhile ( ( elem = this[ i++ ] ) ) {\n\t\t\t\tcurValue = getClass( elem );\n\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( \" \" + stripAndCollapse( curValue ) + \" \" );\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( ( clazz = classes[ j++ ] ) ) {\n\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) > -1 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = stripAndCollapse( cur );\n\t\t\t\t\tif ( curValue !== finalValue ) {\n\t\t\t\t\t\telem.setAttribute( \"class\", finalValue );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value,\n\t\t\tisValidValue = type === \"string\" || Array.isArray( value );\n\n\t\tif ( typeof stateVal === \"boolean\" && isValidValue ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tif ( isFunction( value ) ) {\n\t\t\treturn this.each( function( i ) {\n\t\t\t\tjQuery( this ).toggleClass(\n\t\t\t\t\tvalue.call( this, i, getClass( this ), stateVal ),\n\t\t\t\t\tstateVal\n\t\t\t\t);\n\t\t\t} );\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tvar className, i, self, classNames;\n\n\t\t\tif ( isValidValue ) {\n\n\t\t\t\t// Toggle individual class names\n\t\t\t\ti = 0;\n\t\t\t\tself = jQuery( this );\n\t\t\t\tclassNames = classesToArray( value );\n\n\t\t\t\twhile ( ( className = classNames[ i++ ] ) ) {\n\n\t\t\t\t\t// Check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( value === undefined || type === \"boolean\" ) {\n\t\t\t\tclassName = getClass( this );\n\t\t\t\tif ( className ) {\n\n\t\t\t\t\t// Store className if set\n\t\t\t\t\tdataPriv.set( this, \"__className__\", className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed `false`,\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tif ( this.setAttribute ) {\n\t\t\t\t\tthis.setAttribute( \"class\",\n\t\t\t\t\t\tclassName || value === false ?\n\t\t\t\t\t\t\"\" :\n\t\t\t\t\t\tdataPriv.get( this, \"__className__\" ) || \"\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className, elem,\n\t\t\ti = 0;\n\n\t\tclassName = \" \" + selector + \" \";\n\t\twhile ( ( elem = this[ i++ ] ) ) {\n\t\t\tif ( elem.nodeType === 1 &&\n\t\t\t\t( \" \" + stripAndCollapse( getClass( elem ) ) + \" \" ).indexOf( className ) > -1 ) {\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n} );\n\n\n\n\nvar rreturn = /\\r/g;\n\njQuery.fn.extend( {\n\tval: function( value ) {\n\t\tvar hooks, ret, valueIsFunction,\n\t\t\telem = this[ 0 ];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] ||\n\t\t\t\t\tjQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks &&\n\t\t\t\t\t\"get\" in hooks &&\n\t\t\t\t\t( ret = hooks.get( elem, \"value\" ) ) !== undefined\n\t\t\t\t) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\t// Handle most common string cases\n\t\t\t\tif ( typeof ret === \"string\" ) {\n\t\t\t\t\treturn ret.replace( rreturn, \"\" );\n\t\t\t\t}\n\n\t\t\t\t// Handle cases where value is null/undef or number\n\t\t\t\treturn ret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tvalueIsFunction = isFunction( value );\n\n\t\treturn this.each( function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( valueIsFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\n\t\t\t} else if ( Array.isArray( val ) ) {\n\t\t\t\tval = jQuery.map( val, function( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !( \"set\" in hooks ) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t} );\n\t}\n} );\n\njQuery.extend( {\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\n\t\t\t\treturn val != null ?\n\t\t\t\t\tval :\n\n\t\t\t\t\t// Support: IE <=10 - 11 only\n\t\t\t\t\t// option.text throws exceptions (#14686, #14858)\n\t\t\t\t\t// Strip and collapse whitespace\n\t\t\t\t\t// https://html.spec.whatwg.org/#strip-and-collapse-whitespace\n\t\t\t\t\tstripAndCollapse( jQuery.text( elem ) );\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option, i,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\",\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length;\n\n\t\t\t\tif ( index < 0 ) {\n\t\t\t\t\ti = max;\n\n\t\t\t\t} else {\n\t\t\t\t\ti = one ? index : 0;\n\t\t\t\t}\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t// IE8-9 doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t!option.disabled &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled ||\n\t\t\t\t\t\t\t\t!nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t/* eslint-disable no-cond-assign */\n\n\t\t\t\t\tif ( option.selected =\n\t\t\t\t\t\tjQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1\n\t\t\t\t\t) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* eslint-enable no-cond-assign */\n\t\t\t\t}\n\n\t\t\t\t// Force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n} );\n\n// Radios and checkboxes getter/setter\njQuery.each( [ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( Array.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\treturn elem.getAttribute( \"value\" ) === null ? \"on\" : elem.value;\n\t\t};\n\t}\n} );\n\n\n\n\n// Return jQuery for attributes-only inclusion\n\n\nsupport.focusin = \"onfocusin\" in window;\n\n\nvar rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\tstopPropagationCallback = function( e ) {\n\t\te.stopPropagation();\n\t};\n\njQuery.extend( jQuery.event, {\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special, lastElement,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split( \".\" ) : [];\n\n\t\tcur = lastElement = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf( \".\" ) > -1 ) {\n\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split( \".\" );\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf( \":\" ) < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join( \".\" );\n\t\tevent.rnamespace = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join( \"\\\\.(?:.*\\\\.|)\" ) + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === ( elem.ownerDocument || document ) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {\n\t\t\tlastElement = cur;\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = (\n\t\t\t\t\tdataPriv.get( cur, \"events\" ) || Object.create( null )\n\t\t\t\t)[ event.type ] &&\n\t\t\t\tdataPriv.get( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( ( !special._default ||\n\t\t\t\tspecial._default.apply( eventPath.pop(), data ) === false ) &&\n\t\t\t\tacceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name as the event.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\n\t\t\t\t\tif ( event.isPropagationStopped() ) {\n\t\t\t\t\t\tlastElement.addEventListener( type, stopPropagationCallback );\n\t\t\t\t\t}\n\n\t\t\t\t\telem[ type ]();\n\n\t\t\t\t\tif ( event.isPropagationStopped() ) {\n\t\t\t\t\t\tlastElement.removeEventListener( type, stopPropagationCallback );\n\t\t\t\t\t}\n\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\t// Piggyback on a donor event to simulate a different one\n\t// Used only for `focus(in | out)` events\n\tsimulate: function( type, elem, event ) {\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true\n\t\t\t}\n\t\t);\n\n\t\tjQuery.event.trigger( e, null, elem );\n\t}\n\n} );\n\njQuery.fn.extend( {\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t} );\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[ 0 ];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n} );\n\n\n// Support: Firefox <=44\n// Firefox doesn't have focus(in | out) events\n// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787\n//\n// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1\n// focus(in | out) events fire after focus & blur events,\n// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order\n// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857\nif ( !support.focusin ) {\n\tjQuery.each( { focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler on the document while someone wants focusin/focusout\n\t\tvar handler = function( event ) {\n\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );\n\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\n\t\t\t\t// Handle: regular nodes (via `this.ownerDocument`), window\n\t\t\t\t// (via `this.document`) & document (via `this`).\n\t\t\t\tvar doc = this.ownerDocument || this.document || this,\n\t\t\t\t\tattaches = dataPriv.access( doc, fix );\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t\tdataPriv.access( doc, fix, ( attaches || 0 ) + 1 );\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tvar doc = this.ownerDocument || this.document || this,\n\t\t\t\t\tattaches = dataPriv.access( doc, fix ) - 1;\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.removeEventListener( orig, handler, true );\n\t\t\t\t\tdataPriv.remove( doc, fix );\n\n\t\t\t\t} else {\n\t\t\t\t\tdataPriv.access( doc, fix, attaches );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t} );\n}\nvar location = window.location;\n\nvar nonce = { guid: Date.now() };\n\nvar rquery = ( /\\?/ );\n\n\n\n// Cross-browser xml parsing\njQuery.parseXML = function( data ) {\n\tvar xml;\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\n\t// Support: IE 9 - 11 only\n\t// IE throws on parseFromString with invalid input.\n\ttry {\n\t\txml = ( new window.DOMParser() ).parseFromString( data, \"text/xml\" );\n\t} catch ( e ) {\n\t\txml = undefined;\n\t}\n\n\tif ( !xml || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\tjQuery.error( \"Invalid XML: \" + data );\n\t}\n\treturn xml;\n};\n\n\nvar\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( Array.isArray( obj ) ) {\n\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams(\n\t\t\t\t\tprefix + \"[\" + ( typeof v === \"object\" && v != null ? i : \"\" ) + \"]\",\n\t\t\t\t\tv,\n\t\t\t\t\ttraditional,\n\t\t\t\t\tadd\n\t\t\t\t);\n\t\t\t}\n\t\t} );\n\n\t} else if ( !traditional && toType( obj ) === \"object\" ) {\n\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, valueOrFunction ) {\n\n\t\t\t// If value is a function, invoke it and use its return value\n\t\t\tvar value = isFunction( valueOrFunction ) ?\n\t\t\t\tvalueOrFunction() :\n\t\t\t\tvalueOrFunction;\n\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" +\n\t\t\t\tencodeURIComponent( value == null ? \"\" : value );\n\t\t};\n\n\tif ( a == null ) {\n\t\treturn \"\";\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t} );\n\n\t} else {\n\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" );\n};\n\njQuery.fn.extend( {\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map( function() {\n\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t} )\n\t\t.filter( function() {\n\t\t\tvar type = this.type;\n\n\t\t\t// Use .is( \":disabled\" ) so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !rcheckableType.test( type ) );\n\t\t} )\n\t\t.map( function( _i, elem ) {\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\tif ( val == null ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif ( Array.isArray( val ) ) {\n\t\t\t\treturn jQuery.map( val, function( val ) {\n\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t} ).get();\n\t}\n} );\n\n\nvar\n\tr20 = /%20/g,\n\trhash = /#.*$/,\n\trantiCache = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat( \"*\" ),\n\n\t// Anchor tag for parsing the document origin\n\toriginAnchor = document.createElement( \"a\" );\n\toriginAnchor.href = location.href;\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];\n\n\t\tif ( isFunction( func ) ) {\n\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( ( dataType = dataTypes[ i++ ] ) ) {\n\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[ 0 ] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t( structure[ dataType ] = structure[ dataType ] || [] ).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif ( typeof dataTypeOrTransport === \"string\" &&\n\t\t\t\t!seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t} );\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile ( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader( \"Content-Type\" );\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[ 0 ] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s.throws ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tstate: \"parsererror\",\n\t\t\t\t\t\t\t\terror: conv ? e : \"No conversion from \" + prev + \" to \" + current\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n\njQuery.extend( {\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: location.href,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( location.protocol ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /\\bxml\\b/,\n\t\t\thtml: /\\bhtml/,\n\t\t\tjson: /\\bjson\\b/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": JSON.parse,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar transport,\n\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\n\t\t\t// Url cleanup var\n\t\t\turlAnchor,\n\n\t\t\t// Request state (becomes false upon send and true upon completion)\n\t\t\tcompleted,\n\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\n\t\t\t// Loop variable\n\t\t\ti,\n\n\t\t\t// uncached part of the url\n\t\t\tuncached,\n\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context &&\n\t\t\t\t( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\t\tjQuery.event,\n\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks( \"once memory\" ),\n\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( completed ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( ( match = rheaders.exec( responseHeadersString ) ) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[ 1 ].toLowerCase() + \" \" ] =\n\t\t\t\t\t\t\t\t\t( responseHeaders[ match[ 1 ].toLowerCase() + \" \" ] || [] )\n\t\t\t\t\t\t\t\t\t\t.concat( match[ 2 ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() + \" \" ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match.join( \", \" );\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn completed ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tif ( completed == null ) {\n\t\t\t\t\t\tname = requestHeadersNames[ name.toLowerCase() ] =\n\t\t\t\t\t\t\trequestHeadersNames[ name.toLowerCase() ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( completed == null ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( completed ) {\n\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Lazy-add the new callbacks in a way that preserves old ones\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR );\n\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || location.href ) + \"\" )\n\t\t\t.replace( rprotocol, location.protocol + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = ( s.dataType || \"*\" ).toLowerCase().match( rnothtmlwhite ) || [ \"\" ];\n\n\t\t// A cross-domain request is in order when the origin doesn't match the current origin.\n\t\tif ( s.crossDomain == null ) {\n\t\t\turlAnchor = document.createElement( \"a\" );\n\n\t\t\t// Support: IE <=8 - 11, Edge 12 - 15\n\t\t\t// IE throws exception on accessing the href property if url is malformed,\n\t\t\t// e.g. http://example.com:80x/\n\t\t\ttry {\n\t\t\t\turlAnchor.href = s.url;\n\n\t\t\t\t// Support: IE <=8 - 11 only\n\t\t\t\t// Anchor's host property isn't correctly set when s.url is relative\n\t\t\t\turlAnchor.href = urlAnchor.href;\n\t\t\t\ts.crossDomain = originAnchor.protocol + \"//\" + originAnchor.host !==\n\t\t\t\t\turlAnchor.protocol + \"//\" + urlAnchor.host;\n\t\t\t} catch ( e ) {\n\n\t\t\t\t// If there is an error parsing the URL, assume it is crossDomain,\n\t\t\t\t// it can be rejected by the transport if it is invalid\n\t\t\t\ts.crossDomain = true;\n\t\t\t}\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( completed ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\t// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)\n\t\tfireGlobals = jQuery.event && s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger( \"ajaxStart\" );\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\t// Remove hash to simplify url manipulation\n\t\tcacheURL = s.url.replace( rhash, \"\" );\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// Remember the hash so we can put it back\n\t\t\tuncached = s.url.slice( cacheURL.length );\n\n\t\t\t// If data is available and should be processed, append data to url\n\t\t\tif ( s.data && ( s.processData || typeof s.data === \"string\" ) ) {\n\t\t\t\tcacheURL += ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data;\n\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add or update anti-cache param if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\tcacheURL = cacheURL.replace( rantiCache, \"$1\" );\n\t\t\t\tuncached = ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + ( nonce.guid++ ) +\n\t\t\t\t\tuncached;\n\t\t\t}\n\n\t\t\t// Put hash and anti-cache on the URL that will be requested (gh-1732)\n\t\t\ts.url = cacheURL + uncached;\n\n\t\t// Change '%20' to '+' if this is encoded form body content (gh-2658)\n\t\t} else if ( s.data && s.processData &&\n\t\t\t( s.contentType || \"\" ).indexOf( \"application/x-www-form-urlencoded\" ) === 0 ) {\n\t\t\ts.data = s.data.replace( r20, \"+\" );\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[ 0 ] ] +\n\t\t\t\t\t( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend &&\n\t\t\t( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {\n\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// Aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tcompleteDeferred.add( s.complete );\n\t\tjqXHR.done( s.success );\n\t\tjqXHR.fail( s.error );\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\n\t\t\t// If request was aborted inside ajaxSend, stop there\n\t\t\tif ( completed ) {\n\t\t\t\treturn jqXHR;\n\t\t\t}\n\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = window.setTimeout( function() {\n\t\t\t\t\tjqXHR.abort( \"timeout\" );\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tcompleted = false;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\n\t\t\t\t// Rethrow post-completion exceptions\n\t\t\t\tif ( completed ) {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\n\t\t\t\t// Propagate others as results\n\t\t\t\tdone( -1, e );\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Ignore repeat invocations\n\t\t\tif ( completed ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcompleted = true;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\twindow.clearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Use a noop converter for missing script\n\t\t\tif ( !isSuccess && jQuery.inArray( \"script\", s.dataTypes ) > -1 ) {\n\t\t\t\ts.converters[ \"text script\" ] = function() {};\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader( \"Last-Modified\" );\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader( \"etag\" );\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// Extract error from statusText and normalize for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger( \"ajaxStop\" );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n} );\n\njQuery.each( [ \"get\", \"post\" ], function( _i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\n\t\t// Shift arguments if data argument was omitted\n\t\tif ( isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\t// The url can be an options object (which then must have .url)\n\t\treturn jQuery.ajax( jQuery.extend( {\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t}, jQuery.isPlainObject( url ) && url ) );\n\t};\n} );\n\njQuery.ajaxPrefilter( function( s ) {\n\tvar i;\n\tfor ( i in s.headers ) {\n\t\tif ( i.toLowerCase() === \"content-type\" ) {\n\t\t\ts.contentType = s.headers[ i ] || \"\";\n\t\t}\n\t}\n} );\n\n\njQuery._evalUrl = function( url, options, doc ) {\n\treturn jQuery.ajax( {\n\t\turl: url,\n\n\t\t// Make this explicit, since user can override this through ajaxSetup (#11264)\n\t\ttype: \"GET\",\n\t\tdataType: \"script\",\n\t\tcache: true,\n\t\tasync: false,\n\t\tglobal: false,\n\n\t\t// Only evaluate the response if it is successful (gh-4126)\n\t\t// dataFilter is not invoked for failure responses, so using it instead\n\t\t// of the default converter is kludgy but it works.\n\t\tconverters: {\n\t\t\t\"text script\": function() {}\n\t\t},\n\t\tdataFilter: function( response ) {\n\t\t\tjQuery.globalEval( response, options, doc );\n\t\t}\n\t} );\n};\n\n\njQuery.fn.extend( {\n\twrapAll: function( html ) {\n\t\tvar wrap;\n\n\t\tif ( this[ 0 ] ) {\n\t\t\tif ( isFunction( html ) ) {\n\t\t\t\thtml = html.call( this[ 0 ] );\n\t\t\t}\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );\n\n\t\t\tif ( this[ 0 ].parentNode ) {\n\t\t\t\twrap.insertBefore( this[ 0 ] );\n\t\t\t}\n\n\t\t\twrap.map( function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstElementChild ) {\n\t\t\t\t\telem = elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t} ).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( isFunction( html ) ) {\n\t\t\treturn this.each( function( i ) {\n\t\t\t\tjQuery( this ).wrapInner( html.call( this, i ) );\n\t\t\t} );\n\t\t}\n\n\t\treturn this.each( function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t} );\n\t},\n\n\twrap: function( html ) {\n\t\tvar htmlIsFunction = isFunction( html );\n\n\t\treturn this.each( function( i ) {\n\t\t\tjQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );\n\t\t} );\n\t},\n\n\tunwrap: function( selector ) {\n\t\tthis.parent( selector ).not( \"body\" ).each( function() {\n\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t} );\n\t\treturn this;\n\t}\n} );\n\n\njQuery.expr.pseudos.hidden = function( elem ) {\n\treturn !jQuery.expr.pseudos.visible( elem );\n};\njQuery.expr.pseudos.visible = function( elem ) {\n\treturn !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );\n};\n\n\n\n\njQuery.ajaxSettings.xhr = function() {\n\ttry {\n\t\treturn new window.XMLHttpRequest();\n\t} catch ( e ) {}\n};\n\nvar xhrSuccessStatus = {\n\n\t\t// File protocol always yields status code 0, assume 200\n\t\t0: 200,\n\n\t\t// Support: IE <=9 only\n\t\t// #1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported = jQuery.ajaxSettings.xhr();\n\nsupport.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nsupport.ajax = xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport( function( options ) {\n\tvar callback, errorCallback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif ( support.cors || xhrSupported && !options.crossDomain ) {\n\t\treturn {\n\t\t\tsend: function( headers, complete ) {\n\t\t\t\tvar i,\n\t\t\t\t\txhr = options.xhr();\n\n\t\t\t\txhr.open(\n\t\t\t\t\toptions.type,\n\t\t\t\t\toptions.url,\n\t\t\t\t\toptions.async,\n\t\t\t\t\toptions.username,\n\t\t\t\t\toptions.password\n\t\t\t\t);\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif ( options.xhrFields ) {\n\t\t\t\t\tfor ( i in options.xhrFields ) {\n\t\t\t\t\t\txhr[ i ] = options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif ( options.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\txhr.overrideMimeType( options.mimeType );\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\tif ( !options.crossDomain && !headers[ \"X-Requested-With\" ] ) {\n\t\t\t\t\theaders[ \"X-Requested-With\" ] = \"XMLHttpRequest\";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback = function( type ) {\n\t\t\t\t\treturn function() {\n\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\tcallback = errorCallback = xhr.onload =\n\t\t\t\t\t\t\t\txhr.onerror = xhr.onabort = xhr.ontimeout =\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif ( type === \"abort\" ) {\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t} else if ( type === \"error\" ) {\n\n\t\t\t\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t\t\t\t// On a manual native abort, IE9 throws\n\t\t\t\t\t\t\t\t// errors on any property access that is not readyState\n\t\t\t\t\t\t\t\tif ( typeof xhr.status !== \"number\" ) {\n\t\t\t\t\t\t\t\t\tcomplete( 0, \"error\" );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tcomplete(\n\n\t\t\t\t\t\t\t\t\t\t// File: protocol always yields status 0; see #8605, #14207\n\t\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ] || xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\n\t\t\t\t\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t\t\t\t\t// IE9 has no XHR2 but throws on binary (trac-11426)\n\t\t\t\t\t\t\t\t\t// For XHR2 non-text, let the caller handle it (gh-2498)\n\t\t\t\t\t\t\t\t\t( xhr.responseType || \"text\" ) !== \"text\"  ||\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText !== \"string\" ?\n\t\t\t\t\t\t\t\t\t\t{ binary: xhr.response } :\n\t\t\t\t\t\t\t\t\t\t{ text: xhr.responseText },\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload = callback();\n\t\t\t\terrorCallback = xhr.onerror = xhr.ontimeout = callback( \"error\" );\n\n\t\t\t\t// Support: IE 9 only\n\t\t\t\t// Use onreadystatechange to replace onabort\n\t\t\t\t// to handle uncaught aborts\n\t\t\t\tif ( xhr.onabort !== undefined ) {\n\t\t\t\t\txhr.onabort = errorCallback;\n\t\t\t\t} else {\n\t\t\t\t\txhr.onreadystatechange = function() {\n\n\t\t\t\t\t\t// Check readyState before timeout as it changes\n\t\t\t\t\t\tif ( xhr.readyState === 4 ) {\n\n\t\t\t\t\t\t\t// Allow onerror to be called first,\n\t\t\t\t\t\t\t// but that will not handle a native abort\n\t\t\t\t\t\t\t// Also, save errorCallback to a variable\n\t\t\t\t\t\t\t// as xhr.onerror cannot be accessed\n\t\t\t\t\t\t\twindow.setTimeout( function() {\n\t\t\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\t\t\terrorCallback();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback = callback( \"abort\" );\n\n\t\t\t\ttry {\n\n\t\t\t\t\t// Do send the request (this may raise an exception)\n\t\t\t\t\txhr.send( options.hasContent && options.data || null );\n\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t// #14683: Only rethrow if this hasn't been notified as an error yet\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tthrow e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n} );\n\n\n\n\n// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)\njQuery.ajaxPrefilter( function( s ) {\n\tif ( s.crossDomain ) {\n\t\ts.contents.script = false;\n\t}\n} );\n\n// Install script dataType\njQuery.ajaxSetup( {\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, \" +\n\t\t\t\"application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /\\b(?:java|ecma)script\\b/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n} );\n\n// Handle cache's special case and crossDomain\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t}\n} );\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function( s ) {\n\n\t// This transport only deals with cross domain or forced-by-attrs requests\n\tif ( s.crossDomain || s.scriptAttrs ) {\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function( _, complete ) {\n\t\t\t\tscript = jQuery( \"<script>\" )\n\t\t\t\t\t.attr( s.scriptAttrs || {} )\n\t\t\t\t\t.prop( { charset: s.scriptCharset, src: s.url } )\n\t\t\t\t\t.on( \"load error\", callback = function( evt ) {\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback = null;\n\t\t\t\t\t\tif ( evt ) {\n\t\t\t\t\t\t\tcomplete( evt.type === \"error\" ? 404 : 200, evt.type );\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\n\t\t\t\t// Use native DOM manipulation to avoid our domManip AJAX trickery\n\t\t\t\tdocument.head.appendChild( script[ 0 ] );\n\t\t\t},\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n} );\n\n\n\n\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup( {\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce.guid++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n} );\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" &&\n\t\t\t\t( s.contentType || \"\" )\n\t\t\t\t\t.indexOf( \"application/x-www-form-urlencoded\" ) === 0 &&\n\t\t\t\trjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[ \"script json\" ] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// Force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always( function() {\n\n\t\t\t// If previous value didn't exist - remove it\n\t\t\tif ( overwritten === undefined ) {\n\t\t\t\tjQuery( window ).removeProp( callbackName );\n\n\t\t\t// Otherwise restore preexisting value\n\t\t\t} else {\n\t\t\t\twindow[ callbackName ] = overwritten;\n\t\t\t}\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\n\t\t\t\t// Make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// Save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t} );\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n} );\n\n\n\n\n// Support: Safari 8 only\n// In Safari 8 documents created via document.implementation.createHTMLDocument\n// collapse sibling forms: the second one becomes a child of the first one.\n// Because of that, this security measure has to be disabled in Safari 8.\n// https://bugs.webkit.org/show_bug.cgi?id=137337\nsupport.createHTMLDocument = ( function() {\n\tvar body = document.implementation.createHTMLDocument( \"\" ).body;\n\tbody.innerHTML = \"<form></form><form></form>\";\n\treturn body.childNodes.length === 2;\n} )();\n\n\n// Argument \"data\" should be string of html\n// context (optional): If specified, the fragment will be created in this context,\n// defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML = function( data, context, keepScripts ) {\n\tif ( typeof data !== \"string\" ) {\n\t\treturn [];\n\t}\n\tif ( typeof context === \"boolean\" ) {\n\t\tkeepScripts = context;\n\t\tcontext = false;\n\t}\n\n\tvar base, parsed, scripts;\n\n\tif ( !context ) {\n\n\t\t// Stop scripts or inline event handlers from being executed immediately\n\t\t// by using document.implementation\n\t\tif ( support.createHTMLDocument ) {\n\t\t\tcontext = document.implementation.createHTMLDocument( \"\" );\n\n\t\t\t// Set the base href for the created document\n\t\t\t// so any parsed elements with URLs\n\t\t\t// are based on the document's URL (gh-2965)\n\t\t\tbase = context.createElement( \"base\" );\n\t\t\tbase.href = document.location.href;\n\t\t\tcontext.head.appendChild( base );\n\t\t} else {\n\t\t\tcontext = document;\n\t\t}\n\t}\n\n\tparsed = rsingleTag.exec( data );\n\tscripts = !keepScripts && [];\n\n\t// Single tag\n\tif ( parsed ) {\n\t\treturn [ context.createElement( parsed[ 1 ] ) ];\n\t}\n\n\tparsed = buildFragment( [ data ], context, scripts );\n\n\tif ( scripts && scripts.length ) {\n\t\tjQuery( scripts ).remove();\n\t}\n\n\treturn jQuery.merge( [], parsed.childNodes );\n};\n\n\n/**\n * Load a url into a page\n */\njQuery.fn.load = function( url, params, callback ) {\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf( \" \" );\n\n\tif ( off > -1 ) {\n\t\tselector = stripAndCollapse( url.slice( off ) );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax( {\n\t\t\turl: url,\n\n\t\t\t// If \"type\" variable is undefined, then \"GET\" method will be used.\n\t\t\t// Make value of this field explicit since\n\t\t\t// user can override it through ajaxSetup method\n\t\t\ttype: type || \"GET\",\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t} ).done( function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery( \"<div>\" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t// If the request succeeds, this function gets \"data\", \"status\", \"jqXHR\"\n\t\t// but they are ignored because response was set above.\n\t\t// If it fails, this function gets \"jqXHR\", \"status\", \"error\"\n\t\t} ).always( callback && function( jqXHR, status ) {\n\t\t\tself.each( function() {\n\t\t\t\tcallback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t\t} );\n\t\t} );\n\t}\n\n\treturn this;\n};\n\n\n\n\njQuery.expr.pseudos.animated = function( elem ) {\n\treturn jQuery.grep( jQuery.timers, function( fn ) {\n\t\treturn elem === fn.elem;\n\t} ).length;\n};\n\n\n\n\njQuery.offset = {\n\tsetOffset: function( elem, options, i ) {\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition = jQuery.css( elem, \"position\" ),\n\t\t\tcurElem = jQuery( elem ),\n\t\t\tprops = {};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tcurOffset = curElem.offset();\n\t\tcurCSSTop = jQuery.css( elem, \"top\" );\n\t\tcurCSSLeft = jQuery.css( elem, \"left\" );\n\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n\t\t\t( curCSSTop + curCSSLeft ).indexOf( \"auto\" ) > -1;\n\n\t\t// Need to be able to calculate position if either\n\t\t// top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( isFunction( options ) ) {\n\n\t\t\t// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)\n\t\t\toptions = options.call( elem, i, jQuery.extend( {}, curOffset ) );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\n\t\t} else {\n\t\t\tif ( typeof props.top === \"number\" ) {\n\t\t\t\tprops.top += \"px\";\n\t\t\t}\n\t\t\tif ( typeof props.left === \"number\" ) {\n\t\t\t\tprops.left += \"px\";\n\t\t\t}\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\njQuery.fn.extend( {\n\n\t// offset() relates an element's border box to the document origin\n\toffset: function( options ) {\n\n\t\t// Preserve chaining for setter\n\t\tif ( arguments.length ) {\n\t\t\treturn options === undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each( function( i ) {\n\t\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t\t} );\n\t\t}\n\n\t\tvar rect, win,\n\t\t\telem = this[ 0 ];\n\n\t\tif ( !elem ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Return zeros for disconnected and hidden (display: none) elements (gh-2310)\n\t\t// Support: IE <=11 only\n\t\t// Running getBoundingClientRect on a\n\t\t// disconnected node in IE throws an error\n\t\tif ( !elem.getClientRects().length ) {\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t\t// Get document-relative position by adding viewport scroll to viewport-relative gBCR\n\t\trect = elem.getBoundingClientRect();\n\t\twin = elem.ownerDocument.defaultView;\n\t\treturn {\n\t\t\ttop: rect.top + win.pageYOffset,\n\t\t\tleft: rect.left + win.pageXOffset\n\t\t};\n\t},\n\n\t// position() relates an element's margin box to its offset parent's padding box\n\t// This corresponds to the behavior of CSS absolute positioning\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset, doc,\n\t\t\telem = this[ 0 ],\n\t\t\tparentOffset = { top: 0, left: 0 };\n\n\t\t// position:fixed elements are offset from the viewport, which itself always has zero offset\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\n\t\t\t// Assume position:fixed implies availability of getBoundingClientRect\n\t\t\toffset = elem.getBoundingClientRect();\n\n\t\t} else {\n\t\t\toffset = this.offset();\n\n\t\t\t// Account for the *real* offset parent, which can be the document or its root element\n\t\t\t// when a statically positioned element is identified\n\t\t\tdoc = elem.ownerDocument;\n\t\t\toffsetParent = elem.offsetParent || doc.documentElement;\n\t\t\twhile ( offsetParent &&\n\t\t\t\t( offsetParent === doc.body || offsetParent === doc.documentElement ) &&\n\t\t\t\tjQuery.css( offsetParent, \"position\" ) === \"static\" ) {\n\n\t\t\t\toffsetParent = offsetParent.parentNode;\n\t\t\t}\n\t\t\tif ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {\n\n\t\t\t\t// Incorporate borders into its offset, since they are outside its content origin\n\t\t\t\tparentOffset = jQuery( offsetParent ).offset();\n\t\t\t\tparentOffset.top += jQuery.css( offsetParent, \"borderTopWidth\", true );\n\t\t\t\tparentOffset.left += jQuery.css( offsetParent, \"borderLeftWidth\", true );\n\t\t\t}\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true )\n\t\t};\n\t},\n\n\t// This method will return documentElement in the following cases:\n\t// 1) For the element inside the iframe without offsetParent, this method will return\n\t//    documentElement of the parent window\n\t// 2) For the hidden or detached element\n\t// 3) For body or html element, i.e. in case of the html node - it will return itself\n\t//\n\t// but those exceptions were never presented as a real life use-cases\n\t// and might be considered as more preferable results.\n\t//\n\t// This logic, however, is not guaranteed and can change at any point in the future\n\toffsetParent: function() {\n\t\treturn this.map( function() {\n\t\t\tvar offsetParent = this.offsetParent;\n\n\t\t\twhile ( offsetParent && jQuery.css( offsetParent, \"position\" ) === \"static\" ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent || documentElement;\n\t\t} );\n\t}\n} );\n\n// Create scrollLeft and scrollTop methods\njQuery.each( { scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function( method, prop ) {\n\tvar top = \"pageYOffset\" === prop;\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn access( this, function( elem, method, val ) {\n\n\t\t\t// Coalesce documents and windows\n\t\t\tvar win;\n\t\t\tif ( isWindow( elem ) ) {\n\t\t\t\twin = elem;\n\t\t\t} else if ( elem.nodeType === 9 ) {\n\t\t\t\twin = elem.defaultView;\n\t\t\t}\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? win[ prop ] : elem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : win.pageXOffset,\n\t\t\t\t\ttop ? val : win.pageYOffset\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length );\n\t};\n} );\n\n// Support: Safari <=7 - 9.1, Chrome <=37 - 49\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347\n// getComputedStyle returns percent when specified for top/left/bottom/right;\n// rather than make the css module depend on the offset module, just check for it here\njQuery.each( [ \"top\", \"left\" ], function( _i, prop ) {\n\tjQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,\n\t\tfunction( elem, computed ) {\n\t\t\tif ( computed ) {\n\t\t\t\tcomputed = curCSS( elem, prop );\n\n\t\t\t\t// If curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n} );\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name },\n\t\tfunction( defaultExtra, funcName ) {\n\n\t\t// Margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( isWindow( elem ) ) {\n\n\t\t\t\t\t// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)\n\t\t\t\t\treturn funcName.indexOf( \"outer\" ) === 0 ?\n\t\t\t\t\t\telem[ \"inner\" + name ] :\n\t\t\t\t\t\telem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable );\n\t\t};\n\t} );\n} );\n\n\njQuery.each( [\n\t\"ajaxStart\",\n\t\"ajaxStop\",\n\t\"ajaxComplete\",\n\t\"ajaxError\",\n\t\"ajaxSuccess\",\n\t\"ajaxSend\"\n], function( _i, type ) {\n\tjQuery.fn[ type ] = function( fn ) {\n\t\treturn this.on( type, fn );\n\t};\n} );\n\n\n\n\njQuery.fn.extend( {\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ?\n\t\t\tthis.off( selector, \"**\" ) :\n\t\t\tthis.off( types, selector || \"**\", fn );\n\t},\n\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t}\n} );\n\njQuery.each( ( \"blur focus focusin focusout resize scroll click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup contextmenu\" ).split( \" \" ),\n\tfunction( _i, name ) {\n\n\t\t// Handle event binding\n\t\tjQuery.fn[ name ] = function( data, fn ) {\n\t\t\treturn arguments.length > 0 ?\n\t\t\t\tthis.on( name, null, data, fn ) :\n\t\t\t\tthis.trigger( name );\n\t\t};\n\t} );\n\n\n\n\n// Support: Android <=4.0 only\n// Make sure we trim BOM and NBSP\nvar rtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n\n// Bind a function to a context, optionally partially applying any\n// arguments.\n// jQuery.proxy is deprecated to promote standards (specifically Function#bind)\n// However, it is not slated for removal any time soon\njQuery.proxy = function( fn, context ) {\n\tvar tmp, args, proxy;\n\n\tif ( typeof context === \"string\" ) {\n\t\ttmp = fn[ context ];\n\t\tcontext = fn;\n\t\tfn = tmp;\n\t}\n\n\t// Quick check to determine if target is callable, in the spec\n\t// this throws a TypeError, but we will just return undefined.\n\tif ( !isFunction( fn ) ) {\n\t\treturn undefined;\n\t}\n\n\t// Simulated bind\n\targs = slice.call( arguments, 2 );\n\tproxy = function() {\n\t\treturn fn.apply( context || this, args.concat( slice.call( arguments ) ) );\n\t};\n\n\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\treturn proxy;\n};\n\njQuery.holdReady = function( hold ) {\n\tif ( hold ) {\n\t\tjQuery.readyWait++;\n\t} else {\n\t\tjQuery.ready( true );\n\t}\n};\njQuery.isArray = Array.isArray;\njQuery.parseJSON = JSON.parse;\njQuery.nodeName = nodeName;\njQuery.isFunction = isFunction;\njQuery.isWindow = isWindow;\njQuery.camelCase = camelCase;\njQuery.type = toType;\n\njQuery.now = Date.now;\n\njQuery.isNumeric = function( obj ) {\n\n\t// As of jQuery 3.0, isNumeric is limited to\n\t// strings and numbers (primitives or objects)\n\t// that can be coerced to finite numbers (gh-2662)\n\tvar type = jQuery.type( obj );\n\treturn ( type === \"number\" || type === \"string\" ) &&\n\n\t\t// parseFloat NaNs numeric-cast false positives (\"\")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n\t\t// subtraction forces infinities to NaN\n\t\t!isNaN( obj - parseFloat( obj ) );\n};\n\njQuery.trim = function( text ) {\n\treturn text == null ?\n\t\t\"\" :\n\t\t( text + \"\" ).replace( rtrim, \"\" );\n};\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\n\n// Note that for maximum portability, libraries that are not jQuery should\n// declare themselves as anonymous modules, and avoid setting a global if an\n// AMD loader is present. jQuery is a special case. For more information, see\n// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon\n\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( \"jquery\", [], function() {\n\t\treturn jQuery;\n\t} );\n}\n\n\n\n\nvar\n\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$;\n\njQuery.noConflict = function( deep ) {\n\tif ( window.$ === jQuery ) {\n\t\twindow.$ = _$;\n\t}\n\n\tif ( deep && window.jQuery === jQuery ) {\n\t\twindow.jQuery = _jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in AMD\n// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\nif ( typeof noGlobal === \"undefined\" ) {\n\twindow.jQuery = window.$ = jQuery;\n}\n\n\n\n\nreturn jQuery;\n} );\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/jquery-ui.js",
    "content": "/*! jQuery UI - v1.12.1 - 2016-09-14\n* http://jqueryui.com\n* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js\n* Copyright jQuery Foundation and other contributors; Licensed MIT */\n\n(function( factory ) {\n\tif ( typeof define === \"function\" && define.amd ) {\n\n\t\t// AMD. Register as an anonymous module.\n\t\tdefine([ \"jquery\" ], factory );\n\t} else {\n\n\t\t// Browser globals\n\t\tfactory( jQuery );\n\t}\n}(function( $ ) {\n\n$.ui = $.ui || {};\n\nvar version = $.ui.version = \"1.12.1\";\n\n\n/*!\n * jQuery UI Widget 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Widget\n//>>group: Core\n//>>description: Provides a factory for creating stateful widgets with a common API.\n//>>docs: http://api.jqueryui.com/jQuery.widget/\n//>>demos: http://jqueryui.com/widget/\n\n\n\nvar widgetUuid = 0;\nvar widgetSlice = Array.prototype.slice;\n\n$.cleanData = ( function( orig ) {\n\treturn function( elems ) {\n\t\tvar events, elem, i;\n\t\tfor ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {\n\t\t\ttry {\n\n\t\t\t\t// Only trigger remove when necessary to save time\n\t\t\t\tevents = $._data( elem, \"events\" );\n\t\t\t\tif ( events && events.remove ) {\n\t\t\t\t\t$( elem ).triggerHandler( \"remove\" );\n\t\t\t\t}\n\n\t\t\t// Http://bugs.jquery.com/ticket/8235\n\t\t\t} catch ( e ) {}\n\t\t}\n\t\torig( elems );\n\t};\n} )( $.cleanData );\n\n$.widget = function( name, base, prototype ) {\n\tvar existingConstructor, constructor, basePrototype;\n\n\t// ProxiedPrototype allows the provided prototype to remain unmodified\n\t// so that it can be used as a mixin for multiple widgets (#8876)\n\tvar proxiedPrototype = {};\n\n\tvar namespace = name.split( \".\" )[ 0 ];\n\tname = name.split( \".\" )[ 1 ];\n\tvar fullName = namespace + \"-\" + name;\n\n\tif ( !prototype ) {\n\t\tprototype = base;\n\t\tbase = $.Widget;\n\t}\n\n\tif ( $.isArray( prototype ) ) {\n\t\tprototype = $.extend.apply( null, [ {} ].concat( prototype ) );\n\t}\n\n\t// Create selector for plugin\n\t$.expr[ \":\" ][ fullName.toLowerCase() ] = function( elem ) {\n\t\treturn !!$.data( elem, fullName );\n\t};\n\n\t$[ namespace ] = $[ namespace ] || {};\n\texistingConstructor = $[ namespace ][ name ];\n\tconstructor = $[ namespace ][ name ] = function( options, element ) {\n\n\t\t// Allow instantiation without \"new\" keyword\n\t\tif ( !this._createWidget ) {\n\t\t\treturn new constructor( options, element );\n\t\t}\n\n\t\t// Allow instantiation without initializing for simple inheritance\n\t\t// must use \"new\" keyword (the code above always passes args)\n\t\tif ( arguments.length ) {\n\t\t\tthis._createWidget( options, element );\n\t\t}\n\t};\n\n\t// Extend with the existing constructor to carry over any static properties\n\t$.extend( constructor, existingConstructor, {\n\t\tversion: prototype.version,\n\n\t\t// Copy the object used to create the prototype in case we need to\n\t\t// redefine the widget later\n\t\t_proto: $.extend( {}, prototype ),\n\n\t\t// Track widgets that inherit from this widget in case this widget is\n\t\t// redefined after a widget inherits from it\n\t\t_childConstructors: []\n\t} );\n\n\tbasePrototype = new base();\n\n\t// We need to make the options hash a property directly on the new instance\n\t// otherwise we'll modify the options hash on the prototype that we're\n\t// inheriting from\n\tbasePrototype.options = $.widget.extend( {}, basePrototype.options );\n\t$.each( prototype, function( prop, value ) {\n\t\tif ( !$.isFunction( value ) ) {\n\t\t\tproxiedPrototype[ prop ] = value;\n\t\t\treturn;\n\t\t}\n\t\tproxiedPrototype[ prop ] = ( function() {\n\t\t\tfunction _super() {\n\t\t\t\treturn base.prototype[ prop ].apply( this, arguments );\n\t\t\t}\n\n\t\t\tfunction _superApply( args ) {\n\t\t\t\treturn base.prototype[ prop ].apply( this, args );\n\t\t\t}\n\n\t\t\treturn function() {\n\t\t\t\tvar __super = this._super;\n\t\t\t\tvar __superApply = this._superApply;\n\t\t\t\tvar returnValue;\n\n\t\t\t\tthis._super = _super;\n\t\t\t\tthis._superApply = _superApply;\n\n\t\t\t\treturnValue = value.apply( this, arguments );\n\n\t\t\t\tthis._super = __super;\n\t\t\t\tthis._superApply = __superApply;\n\n\t\t\t\treturn returnValue;\n\t\t\t};\n\t\t} )();\n\t} );\n\tconstructor.prototype = $.widget.extend( basePrototype, {\n\n\t\t// TODO: remove support for widgetEventPrefix\n\t\t// always use the name + a colon as the prefix, e.g., draggable:start\n\t\t// don't prefix for widgets that aren't DOM-based\n\t\twidgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name\n\t}, proxiedPrototype, {\n\t\tconstructor: constructor,\n\t\tnamespace: namespace,\n\t\twidgetName: name,\n\t\twidgetFullName: fullName\n\t} );\n\n\t// If this widget is being redefined then we need to find all widgets that\n\t// are inheriting from it and redefine all of them so that they inherit from\n\t// the new version of this widget. We're essentially trying to replace one\n\t// level in the prototype chain.\n\tif ( existingConstructor ) {\n\t\t$.each( existingConstructor._childConstructors, function( i, child ) {\n\t\t\tvar childPrototype = child.prototype;\n\n\t\t\t// Redefine the child widget using the same prototype that was\n\t\t\t// originally used, but inherit from the new version of the base\n\t\t\t$.widget( childPrototype.namespace + \".\" + childPrototype.widgetName, constructor,\n\t\t\t\tchild._proto );\n\t\t} );\n\n\t\t// Remove the list of existing child constructors from the old constructor\n\t\t// so the old child constructors can be garbage collected\n\t\tdelete existingConstructor._childConstructors;\n\t} else {\n\t\tbase._childConstructors.push( constructor );\n\t}\n\n\t$.widget.bridge( name, constructor );\n\n\treturn constructor;\n};\n\n$.widget.extend = function( target ) {\n\tvar input = widgetSlice.call( arguments, 1 );\n\tvar inputIndex = 0;\n\tvar inputLength = input.length;\n\tvar key;\n\tvar value;\n\n\tfor ( ; inputIndex < inputLength; inputIndex++ ) {\n\t\tfor ( key in input[ inputIndex ] ) {\n\t\t\tvalue = input[ inputIndex ][ key ];\n\t\t\tif ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {\n\n\t\t\t\t// Clone objects\n\t\t\t\tif ( $.isPlainObject( value ) ) {\n\t\t\t\t\ttarget[ key ] = $.isPlainObject( target[ key ] ) ?\n\t\t\t\t\t\t$.widget.extend( {}, target[ key ], value ) :\n\n\t\t\t\t\t\t// Don't extend strings, arrays, etc. with objects\n\t\t\t\t\t\t$.widget.extend( {}, value );\n\n\t\t\t\t// Copy everything else by reference\n\t\t\t\t} else {\n\t\t\t\t\ttarget[ key ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn target;\n};\n\n$.widget.bridge = function( name, object ) {\n\tvar fullName = object.prototype.widgetFullName || name;\n\t$.fn[ name ] = function( options ) {\n\t\tvar isMethodCall = typeof options === \"string\";\n\t\tvar args = widgetSlice.call( arguments, 1 );\n\t\tvar returnValue = this;\n\n\t\tif ( isMethodCall ) {\n\n\t\t\t// If this is an empty collection, we need to have the instance method\n\t\t\t// return undefined instead of the jQuery instance\n\t\t\tif ( !this.length && options === \"instance\" ) {\n\t\t\t\treturnValue = undefined;\n\t\t\t} else {\n\t\t\t\tthis.each( function() {\n\t\t\t\t\tvar methodValue;\n\t\t\t\t\tvar instance = $.data( this, fullName );\n\n\t\t\t\t\tif ( options === \"instance\" ) {\n\t\t\t\t\t\treturnValue = instance;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !instance ) {\n\t\t\t\t\t\treturn $.error( \"cannot call methods on \" + name +\n\t\t\t\t\t\t\t\" prior to initialization; \" +\n\t\t\t\t\t\t\t\"attempted to call method '\" + options + \"'\" );\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === \"_\" ) {\n\t\t\t\t\t\treturn $.error( \"no such method '\" + options + \"' for \" + name +\n\t\t\t\t\t\t\t\" widget instance\" );\n\t\t\t\t\t}\n\n\t\t\t\t\tmethodValue = instance[ options ].apply( instance, args );\n\n\t\t\t\t\tif ( methodValue !== instance && methodValue !== undefined ) {\n\t\t\t\t\t\treturnValue = methodValue && methodValue.jquery ?\n\t\t\t\t\t\t\treturnValue.pushStack( methodValue.get() ) :\n\t\t\t\t\t\t\tmethodValue;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t} else {\n\n\t\t\t// Allow multiple hashes to be passed on init\n\t\t\tif ( args.length ) {\n\t\t\t\toptions = $.widget.extend.apply( null, [ options ].concat( args ) );\n\t\t\t}\n\n\t\t\tthis.each( function() {\n\t\t\t\tvar instance = $.data( this, fullName );\n\t\t\t\tif ( instance ) {\n\t\t\t\t\tinstance.option( options || {} );\n\t\t\t\t\tif ( instance._init ) {\n\t\t\t\t\t\tinstance._init();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t$.data( this, fullName, new object( options, this ) );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\treturn returnValue;\n\t};\n};\n\n$.Widget = function( /* options, element */ ) {};\n$.Widget._childConstructors = [];\n\n$.Widget.prototype = {\n\twidgetName: \"widget\",\n\twidgetEventPrefix: \"\",\n\tdefaultElement: \"<div>\",\n\n\toptions: {\n\t\tclasses: {},\n\t\tdisabled: false,\n\n\t\t// Callbacks\n\t\tcreate: null\n\t},\n\n\t_createWidget: function( options, element ) {\n\t\telement = $( element || this.defaultElement || this )[ 0 ];\n\t\tthis.element = $( element );\n\t\tthis.uuid = widgetUuid++;\n\t\tthis.eventNamespace = \".\" + this.widgetName + this.uuid;\n\n\t\tthis.bindings = $();\n\t\tthis.hoverable = $();\n\t\tthis.focusable = $();\n\t\tthis.classesElementLookup = {};\n\n\t\tif ( element !== this ) {\n\t\t\t$.data( element, this.widgetFullName, this );\n\t\t\tthis._on( true, this.element, {\n\t\t\t\tremove: function( event ) {\n\t\t\t\t\tif ( event.target === element ) {\n\t\t\t\t\t\tthis.destroy();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t\tthis.document = $( element.style ?\n\n\t\t\t\t// Element within the document\n\t\t\t\telement.ownerDocument :\n\n\t\t\t\t// Element is window or document\n\t\t\t\telement.document || element );\n\t\t\tthis.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );\n\t\t}\n\n\t\tthis.options = $.widget.extend( {},\n\t\t\tthis.options,\n\t\t\tthis._getCreateOptions(),\n\t\t\toptions );\n\n\t\tthis._create();\n\n\t\tif ( this.options.disabled ) {\n\t\t\tthis._setOptionDisabled( this.options.disabled );\n\t\t}\n\n\t\tthis._trigger( \"create\", null, this._getCreateEventData() );\n\t\tthis._init();\n\t},\n\n\t_getCreateOptions: function() {\n\t\treturn {};\n\t},\n\n\t_getCreateEventData: $.noop,\n\n\t_create: $.noop,\n\n\t_init: $.noop,\n\n\tdestroy: function() {\n\t\tvar that = this;\n\n\t\tthis._destroy();\n\t\t$.each( this.classesElementLookup, function( key, value ) {\n\t\t\tthat._removeClass( value, key );\n\t\t} );\n\n\t\t// We can probably remove the unbind calls in 2.0\n\t\t// all event bindings should go through this._on()\n\t\tthis.element\n\t\t\t.off( this.eventNamespace )\n\t\t\t.removeData( this.widgetFullName );\n\t\tthis.widget()\n\t\t\t.off( this.eventNamespace )\n\t\t\t.removeAttr( \"aria-disabled\" );\n\n\t\t// Clean up events and states\n\t\tthis.bindings.off( this.eventNamespace );\n\t},\n\n\t_destroy: $.noop,\n\n\twidget: function() {\n\t\treturn this.element;\n\t},\n\n\toption: function( key, value ) {\n\t\tvar options = key;\n\t\tvar parts;\n\t\tvar curOption;\n\t\tvar i;\n\n\t\tif ( arguments.length === 0 ) {\n\n\t\t\t// Don't return a reference to the internal hash\n\t\t\treturn $.widget.extend( {}, this.options );\n\t\t}\n\n\t\tif ( typeof key === \"string\" ) {\n\n\t\t\t// Handle nested keys, e.g., \"foo.bar\" => { foo: { bar: ___ } }\n\t\t\toptions = {};\n\t\t\tparts = key.split( \".\" );\n\t\t\tkey = parts.shift();\n\t\t\tif ( parts.length ) {\n\t\t\t\tcurOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );\n\t\t\t\tfor ( i = 0; i < parts.length - 1; i++ ) {\n\t\t\t\t\tcurOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};\n\t\t\t\t\tcurOption = curOption[ parts[ i ] ];\n\t\t\t\t}\n\t\t\t\tkey = parts.pop();\n\t\t\t\tif ( arguments.length === 1 ) {\n\t\t\t\t\treturn curOption[ key ] === undefined ? null : curOption[ key ];\n\t\t\t\t}\n\t\t\t\tcurOption[ key ] = value;\n\t\t\t} else {\n\t\t\t\tif ( arguments.length === 1 ) {\n\t\t\t\t\treturn this.options[ key ] === undefined ? null : this.options[ key ];\n\t\t\t\t}\n\t\t\t\toptions[ key ] = value;\n\t\t\t}\n\t\t}\n\n\t\tthis._setOptions( options );\n\n\t\treturn this;\n\t},\n\n\t_setOptions: function( options ) {\n\t\tvar key;\n\n\t\tfor ( key in options ) {\n\t\t\tthis._setOption( key, options[ key ] );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"classes\" ) {\n\t\t\tthis._setOptionClasses( value );\n\t\t}\n\n\t\tthis.options[ key ] = value;\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._setOptionDisabled( value );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_setOptionClasses: function( value ) {\n\t\tvar classKey, elements, currentElements;\n\n\t\tfor ( classKey in value ) {\n\t\t\tcurrentElements = this.classesElementLookup[ classKey ];\n\t\t\tif ( value[ classKey ] === this.options.classes[ classKey ] ||\n\t\t\t\t\t!currentElements ||\n\t\t\t\t\t!currentElements.length ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// We are doing this to create a new jQuery object because the _removeClass() call\n\t\t\t// on the next line is going to destroy the reference to the current elements being\n\t\t\t// tracked. We need to save a copy of this collection so that we can add the new classes\n\t\t\t// below.\n\t\t\telements = $( currentElements.get() );\n\t\t\tthis._removeClass( currentElements, classKey );\n\n\t\t\t// We don't use _addClass() here, because that uses this.options.classes\n\t\t\t// for generating the string of classes. We want to use the value passed in from\n\t\t\t// _setOption(), this is the new value of the classes option which was passed to\n\t\t\t// _setOption(). We pass this value directly to _classes().\n\t\t\telements.addClass( this._classes( {\n\t\t\t\telement: elements,\n\t\t\t\tkeys: classKey,\n\t\t\t\tclasses: value,\n\t\t\t\tadd: true\n\t\t\t} ) );\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._toggleClass( this.widget(), this.widgetFullName + \"-disabled\", null, !!value );\n\n\t\t// If the widget is becoming disabled, then nothing is interactive\n\t\tif ( value ) {\n\t\t\tthis._removeClass( this.hoverable, null, \"ui-state-hover\" );\n\t\t\tthis._removeClass( this.focusable, null, \"ui-state-focus\" );\n\t\t}\n\t},\n\n\tenable: function() {\n\t\treturn this._setOptions( { disabled: false } );\n\t},\n\n\tdisable: function() {\n\t\treturn this._setOptions( { disabled: true } );\n\t},\n\n\t_classes: function( options ) {\n\t\tvar full = [];\n\t\tvar that = this;\n\n\t\toptions = $.extend( {\n\t\t\telement: this.element,\n\t\t\tclasses: this.options.classes || {}\n\t\t}, options );\n\n\t\tfunction processClassString( classes, checkOption ) {\n\t\t\tvar current, i;\n\t\t\tfor ( i = 0; i < classes.length; i++ ) {\n\t\t\t\tcurrent = that.classesElementLookup[ classes[ i ] ] || $();\n\t\t\t\tif ( options.add ) {\n\t\t\t\t\tcurrent = $( $.unique( current.get().concat( options.element.get() ) ) );\n\t\t\t\t} else {\n\t\t\t\t\tcurrent = $( current.not( options.element ).get() );\n\t\t\t\t}\n\t\t\t\tthat.classesElementLookup[ classes[ i ] ] = current;\n\t\t\t\tfull.push( classes[ i ] );\n\t\t\t\tif ( checkOption && options.classes[ classes[ i ] ] ) {\n\t\t\t\t\tfull.push( options.classes[ classes[ i ] ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._on( options.element, {\n\t\t\t\"remove\": \"_untrackClassesElement\"\n\t\t} );\n\n\t\tif ( options.keys ) {\n\t\t\tprocessClassString( options.keys.match( /\\S+/g ) || [], true );\n\t\t}\n\t\tif ( options.extra ) {\n\t\t\tprocessClassString( options.extra.match( /\\S+/g ) || [] );\n\t\t}\n\n\t\treturn full.join( \" \" );\n\t},\n\n\t_untrackClassesElement: function( event ) {\n\t\tvar that = this;\n\t\t$.each( that.classesElementLookup, function( key, value ) {\n\t\t\tif ( $.inArray( event.target, value ) !== -1 ) {\n\t\t\t\tthat.classesElementLookup[ key ] = $( value.not( event.target ).get() );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_removeClass: function( element, keys, extra ) {\n\t\treturn this._toggleClass( element, keys, extra, false );\n\t},\n\n\t_addClass: function( element, keys, extra ) {\n\t\treturn this._toggleClass( element, keys, extra, true );\n\t},\n\n\t_toggleClass: function( element, keys, extra, add ) {\n\t\tadd = ( typeof add === \"boolean\" ) ? add : extra;\n\t\tvar shift = ( typeof element === \"string\" || element === null ),\n\t\t\toptions = {\n\t\t\t\textra: shift ? keys : extra,\n\t\t\t\tkeys: shift ? element : keys,\n\t\t\t\telement: shift ? this.element : element,\n\t\t\t\tadd: add\n\t\t\t};\n\t\toptions.element.toggleClass( this._classes( options ), add );\n\t\treturn this;\n\t},\n\n\t_on: function( suppressDisabledCheck, element, handlers ) {\n\t\tvar delegateElement;\n\t\tvar instance = this;\n\n\t\t// No suppressDisabledCheck flag, shuffle arguments\n\t\tif ( typeof suppressDisabledCheck !== \"boolean\" ) {\n\t\t\thandlers = element;\n\t\t\telement = suppressDisabledCheck;\n\t\t\tsuppressDisabledCheck = false;\n\t\t}\n\n\t\t// No element argument, shuffle and use this.element\n\t\tif ( !handlers ) {\n\t\t\thandlers = element;\n\t\t\telement = this.element;\n\t\t\tdelegateElement = this.widget();\n\t\t} else {\n\t\t\telement = delegateElement = $( element );\n\t\t\tthis.bindings = this.bindings.add( element );\n\t\t}\n\n\t\t$.each( handlers, function( event, handler ) {\n\t\t\tfunction handlerProxy() {\n\n\t\t\t\t// Allow widgets to customize the disabled handling\n\t\t\t\t// - disabled as an array instead of boolean\n\t\t\t\t// - disabled class as method for disabling individual parts\n\t\t\t\tif ( !suppressDisabledCheck &&\n\t\t\t\t\t\t( instance.options.disabled === true ||\n\t\t\t\t\t\t$( this ).hasClass( \"ui-state-disabled\" ) ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t\t.apply( instance, arguments );\n\t\t\t}\n\n\t\t\t// Copy the guid so direct unbinding works\n\t\t\tif ( typeof handler !== \"string\" ) {\n\t\t\t\thandlerProxy.guid = handler.guid =\n\t\t\t\t\thandler.guid || handlerProxy.guid || $.guid++;\n\t\t\t}\n\n\t\t\tvar match = event.match( /^([\\w:-]*)\\s*(.*)$/ );\n\t\t\tvar eventName = match[ 1 ] + instance.eventNamespace;\n\t\t\tvar selector = match[ 2 ];\n\n\t\t\tif ( selector ) {\n\t\t\t\tdelegateElement.on( eventName, selector, handlerProxy );\n\t\t\t} else {\n\t\t\t\telement.on( eventName, handlerProxy );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_off: function( element, eventName ) {\n\t\teventName = ( eventName || \"\" ).split( \" \" ).join( this.eventNamespace + \" \" ) +\n\t\t\tthis.eventNamespace;\n\t\telement.off( eventName ).off( eventName );\n\n\t\t// Clear the stack to avoid memory leaks (#10056)\n\t\tthis.bindings = $( this.bindings.not( element ).get() );\n\t\tthis.focusable = $( this.focusable.not( element ).get() );\n\t\tthis.hoverable = $( this.hoverable.not( element ).get() );\n\t},\n\n\t_delay: function( handler, delay ) {\n\t\tfunction handlerProxy() {\n\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t.apply( instance, arguments );\n\t\t}\n\t\tvar instance = this;\n\t\treturn setTimeout( handlerProxy, delay || 0 );\n\t},\n\n\t_hoverable: function( element ) {\n\t\tthis.hoverable = this.hoverable.add( element );\n\t\tthis._on( element, {\n\t\t\tmouseenter: function( event ) {\n\t\t\t\tthis._addClass( $( event.currentTarget ), null, \"ui-state-hover\" );\n\t\t\t},\n\t\t\tmouseleave: function( event ) {\n\t\t\t\tthis._removeClass( $( event.currentTarget ), null, \"ui-state-hover\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_focusable: function( element ) {\n\t\tthis.focusable = this.focusable.add( element );\n\t\tthis._on( element, {\n\t\t\tfocusin: function( event ) {\n\t\t\t\tthis._addClass( $( event.currentTarget ), null, \"ui-state-focus\" );\n\t\t\t},\n\t\t\tfocusout: function( event ) {\n\t\t\t\tthis._removeClass( $( event.currentTarget ), null, \"ui-state-focus\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_trigger: function( type, event, data ) {\n\t\tvar prop, orig;\n\t\tvar callback = this.options[ type ];\n\n\t\tdata = data || {};\n\t\tevent = $.Event( event );\n\t\tevent.type = ( type === this.widgetEventPrefix ?\n\t\t\ttype :\n\t\t\tthis.widgetEventPrefix + type ).toLowerCase();\n\n\t\t// The original event may come from any element\n\t\t// so we need to reset the target on the new event\n\t\tevent.target = this.element[ 0 ];\n\n\t\t// Copy original event properties over to the new event\n\t\torig = event.originalEvent;\n\t\tif ( orig ) {\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tif ( !( prop in event ) ) {\n\t\t\t\t\tevent[ prop ] = orig[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.element.trigger( event, data );\n\t\treturn !( $.isFunction( callback ) &&\n\t\t\tcallback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||\n\t\t\tevent.isDefaultPrevented() );\n\t}\n};\n\n$.each( { show: \"fadeIn\", hide: \"fadeOut\" }, function( method, defaultEffect ) {\n\t$.Widget.prototype[ \"_\" + method ] = function( element, options, callback ) {\n\t\tif ( typeof options === \"string\" ) {\n\t\t\toptions = { effect: options };\n\t\t}\n\n\t\tvar hasOptions;\n\t\tvar effectName = !options ?\n\t\t\tmethod :\n\t\t\toptions === true || typeof options === \"number\" ?\n\t\t\t\tdefaultEffect :\n\t\t\t\toptions.effect || defaultEffect;\n\n\t\toptions = options || {};\n\t\tif ( typeof options === \"number\" ) {\n\t\t\toptions = { duration: options };\n\t\t}\n\n\t\thasOptions = !$.isEmptyObject( options );\n\t\toptions.complete = callback;\n\n\t\tif ( options.delay ) {\n\t\t\telement.delay( options.delay );\n\t\t}\n\n\t\tif ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {\n\t\t\telement[ method ]( options );\n\t\t} else if ( effectName !== method && element[ effectName ] ) {\n\t\t\telement[ effectName ]( options.duration, options.easing, callback );\n\t\t} else {\n\t\t\telement.queue( function( next ) {\n\t\t\t\t$( this )[ method ]();\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback.call( element[ 0 ] );\n\t\t\t\t}\n\t\t\t\tnext();\n\t\t\t} );\n\t\t}\n\t};\n} );\n\nvar widget = $.widget;\n\n\n/*!\n * jQuery UI Position 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * http://api.jqueryui.com/position/\n */\n\n//>>label: Position\n//>>group: Core\n//>>description: Positions elements relative to other elements.\n//>>docs: http://api.jqueryui.com/position/\n//>>demos: http://jqueryui.com/position/\n\n\n( function() {\nvar cachedScrollbarWidth,\n\tmax = Math.max,\n\tabs = Math.abs,\n\trhorizontal = /left|center|right/,\n\trvertical = /top|center|bottom/,\n\troffset = /[\\+\\-]\\d+(\\.[\\d]+)?%?/,\n\trposition = /^\\w+/,\n\trpercent = /%$/,\n\t_position = $.fn.position;\n\nfunction getOffsets( offsets, width, height ) {\n\treturn [\n\t\tparseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),\n\t\tparseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )\n\t];\n}\n\nfunction parseCss( element, property ) {\n\treturn parseInt( $.css( element, property ), 10 ) || 0;\n}\n\nfunction getDimensions( elem ) {\n\tvar raw = elem[ 0 ];\n\tif ( raw.nodeType === 9 ) {\n\t\treturn {\n\t\t\twidth: elem.width(),\n\t\t\theight: elem.height(),\n\t\t\toffset: { top: 0, left: 0 }\n\t\t};\n\t}\n\tif ( $.isWindow( raw ) ) {\n\t\treturn {\n\t\t\twidth: elem.width(),\n\t\t\theight: elem.height(),\n\t\t\toffset: { top: elem.scrollTop(), left: elem.scrollLeft() }\n\t\t};\n\t}\n\tif ( raw.preventDefault ) {\n\t\treturn {\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\toffset: { top: raw.pageY, left: raw.pageX }\n\t\t};\n\t}\n\treturn {\n\t\twidth: elem.outerWidth(),\n\t\theight: elem.outerHeight(),\n\t\toffset: elem.offset()\n\t};\n}\n\n$.position = {\n\tscrollbarWidth: function() {\n\t\tif ( cachedScrollbarWidth !== undefined ) {\n\t\t\treturn cachedScrollbarWidth;\n\t\t}\n\t\tvar w1, w2,\n\t\t\tdiv = $( \"<div \" +\n\t\t\t\t\"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>\" +\n\t\t\t\t\"<div style='height:100px;width:auto;'></div></div>\" ),\n\t\t\tinnerDiv = div.children()[ 0 ];\n\n\t\t$( \"body\" ).append( div );\n\t\tw1 = innerDiv.offsetWidth;\n\t\tdiv.css( \"overflow\", \"scroll\" );\n\n\t\tw2 = innerDiv.offsetWidth;\n\n\t\tif ( w1 === w2 ) {\n\t\t\tw2 = div[ 0 ].clientWidth;\n\t\t}\n\n\t\tdiv.remove();\n\n\t\treturn ( cachedScrollbarWidth = w1 - w2 );\n\t},\n\tgetScrollInfo: function( within ) {\n\t\tvar overflowX = within.isWindow || within.isDocument ? \"\" :\n\t\t\t\twithin.element.css( \"overflow-x\" ),\n\t\t\toverflowY = within.isWindow || within.isDocument ? \"\" :\n\t\t\t\twithin.element.css( \"overflow-y\" ),\n\t\t\thasOverflowX = overflowX === \"scroll\" ||\n\t\t\t\t( overflowX === \"auto\" && within.width < within.element[ 0 ].scrollWidth ),\n\t\t\thasOverflowY = overflowY === \"scroll\" ||\n\t\t\t\t( overflowY === \"auto\" && within.height < within.element[ 0 ].scrollHeight );\n\t\treturn {\n\t\t\twidth: hasOverflowY ? $.position.scrollbarWidth() : 0,\n\t\t\theight: hasOverflowX ? $.position.scrollbarWidth() : 0\n\t\t};\n\t},\n\tgetWithinInfo: function( element ) {\n\t\tvar withinElement = $( element || window ),\n\t\t\tisWindow = $.isWindow( withinElement[ 0 ] ),\n\t\t\tisDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,\n\t\t\thasOffset = !isWindow && !isDocument;\n\t\treturn {\n\t\t\telement: withinElement,\n\t\t\tisWindow: isWindow,\n\t\t\tisDocument: isDocument,\n\t\t\toffset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },\n\t\t\tscrollLeft: withinElement.scrollLeft(),\n\t\t\tscrollTop: withinElement.scrollTop(),\n\t\t\twidth: withinElement.outerWidth(),\n\t\t\theight: withinElement.outerHeight()\n\t\t};\n\t}\n};\n\n$.fn.position = function( options ) {\n\tif ( !options || !options.of ) {\n\t\treturn _position.apply( this, arguments );\n\t}\n\n\t// Make a copy, we don't want to modify arguments\n\toptions = $.extend( {}, options );\n\n\tvar atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,\n\t\ttarget = $( options.of ),\n\t\twithin = $.position.getWithinInfo( options.within ),\n\t\tscrollInfo = $.position.getScrollInfo( within ),\n\t\tcollision = ( options.collision || \"flip\" ).split( \" \" ),\n\t\toffsets = {};\n\n\tdimensions = getDimensions( target );\n\tif ( target[ 0 ].preventDefault ) {\n\n\t\t// Force left top to allow flipping\n\t\toptions.at = \"left top\";\n\t}\n\ttargetWidth = dimensions.width;\n\ttargetHeight = dimensions.height;\n\ttargetOffset = dimensions.offset;\n\n\t// Clone to reuse original targetOffset later\n\tbasePosition = $.extend( {}, targetOffset );\n\n\t// Force my and at to have valid horizontal and vertical positions\n\t// if a value is missing or invalid, it will be converted to center\n\t$.each( [ \"my\", \"at\" ], function() {\n\t\tvar pos = ( options[ this ] || \"\" ).split( \" \" ),\n\t\t\thorizontalOffset,\n\t\t\tverticalOffset;\n\n\t\tif ( pos.length === 1 ) {\n\t\t\tpos = rhorizontal.test( pos[ 0 ] ) ?\n\t\t\t\tpos.concat( [ \"center\" ] ) :\n\t\t\t\trvertical.test( pos[ 0 ] ) ?\n\t\t\t\t\t[ \"center\" ].concat( pos ) :\n\t\t\t\t\t[ \"center\", \"center\" ];\n\t\t}\n\t\tpos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : \"center\";\n\t\tpos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : \"center\";\n\n\t\t// Calculate offsets\n\t\thorizontalOffset = roffset.exec( pos[ 0 ] );\n\t\tverticalOffset = roffset.exec( pos[ 1 ] );\n\t\toffsets[ this ] = [\n\t\t\thorizontalOffset ? horizontalOffset[ 0 ] : 0,\n\t\t\tverticalOffset ? verticalOffset[ 0 ] : 0\n\t\t];\n\n\t\t// Reduce to just the positions without the offsets\n\t\toptions[ this ] = [\n\t\t\trposition.exec( pos[ 0 ] )[ 0 ],\n\t\t\trposition.exec( pos[ 1 ] )[ 0 ]\n\t\t];\n\t} );\n\n\t// Normalize collision option\n\tif ( collision.length === 1 ) {\n\t\tcollision[ 1 ] = collision[ 0 ];\n\t}\n\n\tif ( options.at[ 0 ] === \"right\" ) {\n\t\tbasePosition.left += targetWidth;\n\t} else if ( options.at[ 0 ] === \"center\" ) {\n\t\tbasePosition.left += targetWidth / 2;\n\t}\n\n\tif ( options.at[ 1 ] === \"bottom\" ) {\n\t\tbasePosition.top += targetHeight;\n\t} else if ( options.at[ 1 ] === \"center\" ) {\n\t\tbasePosition.top += targetHeight / 2;\n\t}\n\n\tatOffset = getOffsets( offsets.at, targetWidth, targetHeight );\n\tbasePosition.left += atOffset[ 0 ];\n\tbasePosition.top += atOffset[ 1 ];\n\n\treturn this.each( function() {\n\t\tvar collisionPosition, using,\n\t\t\telem = $( this ),\n\t\t\telemWidth = elem.outerWidth(),\n\t\t\telemHeight = elem.outerHeight(),\n\t\t\tmarginLeft = parseCss( this, \"marginLeft\" ),\n\t\t\tmarginTop = parseCss( this, \"marginTop\" ),\n\t\t\tcollisionWidth = elemWidth + marginLeft + parseCss( this, \"marginRight\" ) +\n\t\t\t\tscrollInfo.width,\n\t\t\tcollisionHeight = elemHeight + marginTop + parseCss( this, \"marginBottom\" ) +\n\t\t\t\tscrollInfo.height,\n\t\t\tposition = $.extend( {}, basePosition ),\n\t\t\tmyOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );\n\n\t\tif ( options.my[ 0 ] === \"right\" ) {\n\t\t\tposition.left -= elemWidth;\n\t\t} else if ( options.my[ 0 ] === \"center\" ) {\n\t\t\tposition.left -= elemWidth / 2;\n\t\t}\n\n\t\tif ( options.my[ 1 ] === \"bottom\" ) {\n\t\t\tposition.top -= elemHeight;\n\t\t} else if ( options.my[ 1 ] === \"center\" ) {\n\t\t\tposition.top -= elemHeight / 2;\n\t\t}\n\n\t\tposition.left += myOffset[ 0 ];\n\t\tposition.top += myOffset[ 1 ];\n\n\t\tcollisionPosition = {\n\t\t\tmarginLeft: marginLeft,\n\t\t\tmarginTop: marginTop\n\t\t};\n\n\t\t$.each( [ \"left\", \"top\" ], function( i, dir ) {\n\t\t\tif ( $.ui.position[ collision[ i ] ] ) {\n\t\t\t\t$.ui.position[ collision[ i ] ][ dir ]( position, {\n\t\t\t\t\ttargetWidth: targetWidth,\n\t\t\t\t\ttargetHeight: targetHeight,\n\t\t\t\t\telemWidth: elemWidth,\n\t\t\t\t\telemHeight: elemHeight,\n\t\t\t\t\tcollisionPosition: collisionPosition,\n\t\t\t\t\tcollisionWidth: collisionWidth,\n\t\t\t\t\tcollisionHeight: collisionHeight,\n\t\t\t\t\toffset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],\n\t\t\t\t\tmy: options.my,\n\t\t\t\t\tat: options.at,\n\t\t\t\t\twithin: within,\n\t\t\t\t\telem: elem\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\tif ( options.using ) {\n\n\t\t\t// Adds feedback as second argument to using callback, if present\n\t\t\tusing = function( props ) {\n\t\t\t\tvar left = targetOffset.left - position.left,\n\t\t\t\t\tright = left + targetWidth - elemWidth,\n\t\t\t\t\ttop = targetOffset.top - position.top,\n\t\t\t\t\tbottom = top + targetHeight - elemHeight,\n\t\t\t\t\tfeedback = {\n\t\t\t\t\t\ttarget: {\n\t\t\t\t\t\t\telement: target,\n\t\t\t\t\t\t\tleft: targetOffset.left,\n\t\t\t\t\t\t\ttop: targetOffset.top,\n\t\t\t\t\t\t\twidth: targetWidth,\n\t\t\t\t\t\t\theight: targetHeight\n\t\t\t\t\t\t},\n\t\t\t\t\t\telement: {\n\t\t\t\t\t\t\telement: elem,\n\t\t\t\t\t\t\tleft: position.left,\n\t\t\t\t\t\t\ttop: position.top,\n\t\t\t\t\t\t\twidth: elemWidth,\n\t\t\t\t\t\t\theight: elemHeight\n\t\t\t\t\t\t},\n\t\t\t\t\t\thorizontal: right < 0 ? \"left\" : left > 0 ? \"right\" : \"center\",\n\t\t\t\t\t\tvertical: bottom < 0 ? \"top\" : top > 0 ? \"bottom\" : \"middle\"\n\t\t\t\t\t};\n\t\t\t\tif ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {\n\t\t\t\t\tfeedback.horizontal = \"center\";\n\t\t\t\t}\n\t\t\t\tif ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {\n\t\t\t\t\tfeedback.vertical = \"middle\";\n\t\t\t\t}\n\t\t\t\tif ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {\n\t\t\t\t\tfeedback.important = \"horizontal\";\n\t\t\t\t} else {\n\t\t\t\t\tfeedback.important = \"vertical\";\n\t\t\t\t}\n\t\t\t\toptions.using.call( this, props, feedback );\n\t\t\t};\n\t\t}\n\n\t\telem.offset( $.extend( position, { using: using } ) );\n\t} );\n};\n\n$.ui.position = {\n\tfit: {\n\t\tleft: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.isWindow ? within.scrollLeft : within.offset.left,\n\t\t\t\touterWidth = within.width,\n\t\t\t\tcollisionPosLeft = position.left - data.collisionPosition.marginLeft,\n\t\t\t\toverLeft = withinOffset - collisionPosLeft,\n\t\t\t\toverRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,\n\t\t\t\tnewOverRight;\n\n\t\t\t// Element is wider than within\n\t\t\tif ( data.collisionWidth > outerWidth ) {\n\n\t\t\t\t// Element is initially over the left side of within\n\t\t\t\tif ( overLeft > 0 && overRight <= 0 ) {\n\t\t\t\t\tnewOverRight = position.left + overLeft + data.collisionWidth - outerWidth -\n\t\t\t\t\t\twithinOffset;\n\t\t\t\t\tposition.left += overLeft - newOverRight;\n\n\t\t\t\t// Element is initially over right side of within\n\t\t\t\t} else if ( overRight > 0 && overLeft <= 0 ) {\n\t\t\t\t\tposition.left = withinOffset;\n\n\t\t\t\t// Element is initially over both left and right sides of within\n\t\t\t\t} else {\n\t\t\t\t\tif ( overLeft > overRight ) {\n\t\t\t\t\t\tposition.left = withinOffset + outerWidth - data.collisionWidth;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tposition.left = withinOffset;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Too far left -> align with left edge\n\t\t\t} else if ( overLeft > 0 ) {\n\t\t\t\tposition.left += overLeft;\n\n\t\t\t// Too far right -> align with right edge\n\t\t\t} else if ( overRight > 0 ) {\n\t\t\t\tposition.left -= overRight;\n\n\t\t\t// Adjust based on position and margin\n\t\t\t} else {\n\t\t\t\tposition.left = max( position.left - collisionPosLeft, position.left );\n\t\t\t}\n\t\t},\n\t\ttop: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.isWindow ? within.scrollTop : within.offset.top,\n\t\t\t\touterHeight = data.within.height,\n\t\t\t\tcollisionPosTop = position.top - data.collisionPosition.marginTop,\n\t\t\t\toverTop = withinOffset - collisionPosTop,\n\t\t\t\toverBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,\n\t\t\t\tnewOverBottom;\n\n\t\t\t// Element is taller than within\n\t\t\tif ( data.collisionHeight > outerHeight ) {\n\n\t\t\t\t// Element is initially over the top of within\n\t\t\t\tif ( overTop > 0 && overBottom <= 0 ) {\n\t\t\t\t\tnewOverBottom = position.top + overTop + data.collisionHeight - outerHeight -\n\t\t\t\t\t\twithinOffset;\n\t\t\t\t\tposition.top += overTop - newOverBottom;\n\n\t\t\t\t// Element is initially over bottom of within\n\t\t\t\t} else if ( overBottom > 0 && overTop <= 0 ) {\n\t\t\t\t\tposition.top = withinOffset;\n\n\t\t\t\t// Element is initially over both top and bottom of within\n\t\t\t\t} else {\n\t\t\t\t\tif ( overTop > overBottom ) {\n\t\t\t\t\t\tposition.top = withinOffset + outerHeight - data.collisionHeight;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tposition.top = withinOffset;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Too far up -> align with top\n\t\t\t} else if ( overTop > 0 ) {\n\t\t\t\tposition.top += overTop;\n\n\t\t\t// Too far down -> align with bottom edge\n\t\t\t} else if ( overBottom > 0 ) {\n\t\t\t\tposition.top -= overBottom;\n\n\t\t\t// Adjust based on position and margin\n\t\t\t} else {\n\t\t\t\tposition.top = max( position.top - collisionPosTop, position.top );\n\t\t\t}\n\t\t}\n\t},\n\tflip: {\n\t\tleft: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.offset.left + within.scrollLeft,\n\t\t\t\touterWidth = within.width,\n\t\t\t\toffsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,\n\t\t\t\tcollisionPosLeft = position.left - data.collisionPosition.marginLeft,\n\t\t\t\toverLeft = collisionPosLeft - offsetLeft,\n\t\t\t\toverRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,\n\t\t\t\tmyOffset = data.my[ 0 ] === \"left\" ?\n\t\t\t\t\t-data.elemWidth :\n\t\t\t\t\tdata.my[ 0 ] === \"right\" ?\n\t\t\t\t\t\tdata.elemWidth :\n\t\t\t\t\t\t0,\n\t\t\t\tatOffset = data.at[ 0 ] === \"left\" ?\n\t\t\t\t\tdata.targetWidth :\n\t\t\t\t\tdata.at[ 0 ] === \"right\" ?\n\t\t\t\t\t\t-data.targetWidth :\n\t\t\t\t\t\t0,\n\t\t\t\toffset = -2 * data.offset[ 0 ],\n\t\t\t\tnewOverRight,\n\t\t\t\tnewOverLeft;\n\n\t\t\tif ( overLeft < 0 ) {\n\t\t\t\tnewOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -\n\t\t\t\t\touterWidth - withinOffset;\n\t\t\t\tif ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {\n\t\t\t\t\tposition.left += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t} else if ( overRight > 0 ) {\n\t\t\t\tnewOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +\n\t\t\t\t\tatOffset + offset - offsetLeft;\n\t\t\t\tif ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {\n\t\t\t\t\tposition.left += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\ttop: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.offset.top + within.scrollTop,\n\t\t\t\touterHeight = within.height,\n\t\t\t\toffsetTop = within.isWindow ? within.scrollTop : within.offset.top,\n\t\t\t\tcollisionPosTop = position.top - data.collisionPosition.marginTop,\n\t\t\t\toverTop = collisionPosTop - offsetTop,\n\t\t\t\toverBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,\n\t\t\t\ttop = data.my[ 1 ] === \"top\",\n\t\t\t\tmyOffset = top ?\n\t\t\t\t\t-data.elemHeight :\n\t\t\t\t\tdata.my[ 1 ] === \"bottom\" ?\n\t\t\t\t\t\tdata.elemHeight :\n\t\t\t\t\t\t0,\n\t\t\t\tatOffset = data.at[ 1 ] === \"top\" ?\n\t\t\t\t\tdata.targetHeight :\n\t\t\t\t\tdata.at[ 1 ] === \"bottom\" ?\n\t\t\t\t\t\t-data.targetHeight :\n\t\t\t\t\t\t0,\n\t\t\t\toffset = -2 * data.offset[ 1 ],\n\t\t\t\tnewOverTop,\n\t\t\t\tnewOverBottom;\n\t\t\tif ( overTop < 0 ) {\n\t\t\t\tnewOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -\n\t\t\t\t\touterHeight - withinOffset;\n\t\t\t\tif ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {\n\t\t\t\t\tposition.top += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t} else if ( overBottom > 0 ) {\n\t\t\t\tnewOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +\n\t\t\t\t\toffset - offsetTop;\n\t\t\t\tif ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {\n\t\t\t\t\tposition.top += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\tflipfit: {\n\t\tleft: function() {\n\t\t\t$.ui.position.flip.left.apply( this, arguments );\n\t\t\t$.ui.position.fit.left.apply( this, arguments );\n\t\t},\n\t\ttop: function() {\n\t\t\t$.ui.position.flip.top.apply( this, arguments );\n\t\t\t$.ui.position.fit.top.apply( this, arguments );\n\t\t}\n\t}\n};\n\n} )();\n\nvar position = $.ui.position;\n\n\n/*!\n * jQuery UI :data 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: :data Selector\n//>>group: Core\n//>>description: Selects elements which have data stored under the specified key.\n//>>docs: http://api.jqueryui.com/data-selector/\n\n\nvar data = $.extend( $.expr[ \":\" ], {\n\tdata: $.expr.createPseudo ?\n\t\t$.expr.createPseudo( function( dataName ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn !!$.data( elem, dataName );\n\t\t\t};\n\t\t} ) :\n\n\t\t// Support: jQuery <1.8\n\t\tfunction( elem, i, match ) {\n\t\t\treturn !!$.data( elem, match[ 3 ] );\n\t\t}\n} );\n\n/*!\n * jQuery UI Disable Selection 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: disableSelection\n//>>group: Core\n//>>description: Disable selection of text content within the set of matched elements.\n//>>docs: http://api.jqueryui.com/disableSelection/\n\n// This file is deprecated\n\n\nvar disableSelection = $.fn.extend( {\n\tdisableSelection: ( function() {\n\t\tvar eventType = \"onselectstart\" in document.createElement( \"div\" ) ?\n\t\t\t\"selectstart\" :\n\t\t\t\"mousedown\";\n\n\t\treturn function() {\n\t\t\treturn this.on( eventType + \".ui-disableSelection\", function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t} );\n\t\t};\n\t} )(),\n\n\tenableSelection: function() {\n\t\treturn this.off( \".ui-disableSelection\" );\n\t}\n} );\n\n\n/*!\n * jQuery UI Effects 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Effects Core\n//>>group: Effects\n// jscs:disable maximumLineLength\n//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/category/effects-core/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar dataSpace = \"ui-effects-\",\n\tdataSpaceStyle = \"ui-effects-style\",\n\tdataSpaceAnimated = \"ui-effects-animated\",\n\n\t// Create a local jQuery because jQuery Color relies on it and the\n\t// global may not exist with AMD and a custom build (#10199)\n\tjQuery = $;\n\n$.effects = {\n\teffect: {}\n};\n\n/*!\n * jQuery Color Animations v2.1.2\n * https://github.com/jquery/jquery-color\n *\n * Copyright 2014 jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * Date: Wed Jan 16 08:47:09 2013 -0600\n */\n( function( jQuery, undefined ) {\n\n\tvar stepHooks = \"backgroundColor borderBottomColor borderLeftColor borderRightColor \" +\n\t\t\"borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor\",\n\n\t// Plusequals test for += 100 -= 100\n\trplusequals = /^([\\-+])=\\s*(\\d+\\.?\\d*)/,\n\n\t// A set of RE's that can match strings and generate color tuples.\n\tstringParsers = [ {\n\t\t\tre: /rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*(?:,\\s*(\\d?(?:\\.\\d+)?)\\s*)?\\)/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\texecResult[ 1 ],\n\t\t\t\t\texecResult[ 2 ],\n\t\t\t\t\texecResult[ 3 ],\n\t\t\t\t\texecResult[ 4 ]\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\t\t\tre: /rgba?\\(\\s*(\\d+(?:\\.\\d+)?)\\%\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*(?:,\\s*(\\d?(?:\\.\\d+)?)\\s*)?\\)/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\texecResult[ 1 ] * 2.55,\n\t\t\t\t\texecResult[ 2 ] * 2.55,\n\t\t\t\t\texecResult[ 3 ] * 2.55,\n\t\t\t\t\texecResult[ 4 ]\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\n\t\t\t// This regex ignores A-F because it's compared against an already lowercased string\n\t\t\tre: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt( execResult[ 1 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 2 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 3 ], 16 )\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\n\t\t\t// This regex ignores A-F because it's compared against an already lowercased string\n\t\t\tre: /#([a-f0-9])([a-f0-9])([a-f0-9])/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 3 ] + execResult[ 3 ], 16 )\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\t\t\tre: /hsla?\\(\\s*(\\d+(?:\\.\\d+)?)\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*(?:,\\s*(\\d?(?:\\.\\d+)?)\\s*)?\\)/,\n\t\t\tspace: \"hsla\",\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\texecResult[ 1 ],\n\t\t\t\t\texecResult[ 2 ] / 100,\n\t\t\t\t\texecResult[ 3 ] / 100,\n\t\t\t\t\texecResult[ 4 ]\n\t\t\t\t];\n\t\t\t}\n\t\t} ],\n\n\t// JQuery.Color( )\n\tcolor = jQuery.Color = function( color, green, blue, alpha ) {\n\t\treturn new jQuery.Color.fn.parse( color, green, blue, alpha );\n\t},\n\tspaces = {\n\t\trgba: {\n\t\t\tprops: {\n\t\t\t\tred: {\n\t\t\t\t\tidx: 0,\n\t\t\t\t\ttype: \"byte\"\n\t\t\t\t},\n\t\t\t\tgreen: {\n\t\t\t\t\tidx: 1,\n\t\t\t\t\ttype: \"byte\"\n\t\t\t\t},\n\t\t\t\tblue: {\n\t\t\t\t\tidx: 2,\n\t\t\t\t\ttype: \"byte\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\thsla: {\n\t\t\tprops: {\n\t\t\t\thue: {\n\t\t\t\t\tidx: 0,\n\t\t\t\t\ttype: \"degrees\"\n\t\t\t\t},\n\t\t\t\tsaturation: {\n\t\t\t\t\tidx: 1,\n\t\t\t\t\ttype: \"percent\"\n\t\t\t\t},\n\t\t\t\tlightness: {\n\t\t\t\t\tidx: 2,\n\t\t\t\t\ttype: \"percent\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\tpropTypes = {\n\t\t\"byte\": {\n\t\t\tfloor: true,\n\t\t\tmax: 255\n\t\t},\n\t\t\"percent\": {\n\t\t\tmax: 1\n\t\t},\n\t\t\"degrees\": {\n\t\t\tmod: 360,\n\t\t\tfloor: true\n\t\t}\n\t},\n\tsupport = color.support = {},\n\n\t// Element for support tests\n\tsupportElem = jQuery( \"<p>\" )[ 0 ],\n\n\t// Colors = jQuery.Color.names\n\tcolors,\n\n\t// Local aliases of functions called often\n\teach = jQuery.each;\n\n// Determine rgba support immediately\nsupportElem.style.cssText = \"background-color:rgba(1,1,1,.5)\";\nsupport.rgba = supportElem.style.backgroundColor.indexOf( \"rgba\" ) > -1;\n\n// Define cache name and alpha properties\n// for rgba and hsla spaces\neach( spaces, function( spaceName, space ) {\n\tspace.cache = \"_\" + spaceName;\n\tspace.props.alpha = {\n\t\tidx: 3,\n\t\ttype: \"percent\",\n\t\tdef: 1\n\t};\n} );\n\nfunction clamp( value, prop, allowEmpty ) {\n\tvar type = propTypes[ prop.type ] || {};\n\n\tif ( value == null ) {\n\t\treturn ( allowEmpty || !prop.def ) ? null : prop.def;\n\t}\n\n\t// ~~ is an short way of doing floor for positive numbers\n\tvalue = type.floor ? ~~value : parseFloat( value );\n\n\t// IE will pass in empty strings as value for alpha,\n\t// which will hit this case\n\tif ( isNaN( value ) ) {\n\t\treturn prop.def;\n\t}\n\n\tif ( type.mod ) {\n\n\t\t// We add mod before modding to make sure that negatives values\n\t\t// get converted properly: -10 -> 350\n\t\treturn ( value + type.mod ) % type.mod;\n\t}\n\n\t// For now all property types without mod have min and max\n\treturn 0 > value ? 0 : type.max < value ? type.max : value;\n}\n\nfunction stringParse( string ) {\n\tvar inst = color(),\n\t\trgba = inst._rgba = [];\n\n\tstring = string.toLowerCase();\n\n\teach( stringParsers, function( i, parser ) {\n\t\tvar parsed,\n\t\t\tmatch = parser.re.exec( string ),\n\t\t\tvalues = match && parser.parse( match ),\n\t\t\tspaceName = parser.space || \"rgba\";\n\n\t\tif ( values ) {\n\t\t\tparsed = inst[ spaceName ]( values );\n\n\t\t\t// If this was an rgba parse the assignment might happen twice\n\t\t\t// oh well....\n\t\t\tinst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];\n\t\t\trgba = inst._rgba = parsed._rgba;\n\n\t\t\t// Exit each( stringParsers ) here because we matched\n\t\t\treturn false;\n\t\t}\n\t} );\n\n\t// Found a stringParser that handled it\n\tif ( rgba.length ) {\n\n\t\t// If this came from a parsed string, force \"transparent\" when alpha is 0\n\t\t// chrome, (and maybe others) return \"transparent\" as rgba(0,0,0,0)\n\t\tif ( rgba.join() === \"0,0,0,0\" ) {\n\t\t\tjQuery.extend( rgba, colors.transparent );\n\t\t}\n\t\treturn inst;\n\t}\n\n\t// Named colors\n\treturn colors[ string ];\n}\n\ncolor.fn = jQuery.extend( color.prototype, {\n\tparse: function( red, green, blue, alpha ) {\n\t\tif ( red === undefined ) {\n\t\t\tthis._rgba = [ null, null, null, null ];\n\t\t\treturn this;\n\t\t}\n\t\tif ( red.jquery || red.nodeType ) {\n\t\t\tred = jQuery( red ).css( green );\n\t\t\tgreen = undefined;\n\t\t}\n\n\t\tvar inst = this,\n\t\t\ttype = jQuery.type( red ),\n\t\t\trgba = this._rgba = [];\n\n\t\t// More than 1 argument specified - assume ( red, green, blue, alpha )\n\t\tif ( green !== undefined ) {\n\t\t\tred = [ red, green, blue, alpha ];\n\t\t\ttype = \"array\";\n\t\t}\n\n\t\tif ( type === \"string\" ) {\n\t\t\treturn this.parse( stringParse( red ) || colors._default );\n\t\t}\n\n\t\tif ( type === \"array\" ) {\n\t\t\teach( spaces.rgba.props, function( key, prop ) {\n\t\t\t\trgba[ prop.idx ] = clamp( red[ prop.idx ], prop );\n\t\t\t} );\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( type === \"object\" ) {\n\t\t\tif ( red instanceof color ) {\n\t\t\t\teach( spaces, function( spaceName, space ) {\n\t\t\t\t\tif ( red[ space.cache ] ) {\n\t\t\t\t\t\tinst[ space.cache ] = red[ space.cache ].slice();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\teach( spaces, function( spaceName, space ) {\n\t\t\t\t\tvar cache = space.cache;\n\t\t\t\t\teach( space.props, function( key, prop ) {\n\n\t\t\t\t\t\t// If the cache doesn't exist, and we know how to convert\n\t\t\t\t\t\tif ( !inst[ cache ] && space.to ) {\n\n\t\t\t\t\t\t\t// If the value was null, we don't need to copy it\n\t\t\t\t\t\t\t// if the key was alpha, we don't need to copy it either\n\t\t\t\t\t\t\tif ( key === \"alpha\" || red[ key ] == null ) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tinst[ cache ] = space.to( inst._rgba );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// This is the only case where we allow nulls for ALL properties.\n\t\t\t\t\t\t// call clamp with alwaysAllowEmpty\n\t\t\t\t\t\tinst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );\n\t\t\t\t\t} );\n\n\t\t\t\t\t// Everything defined but alpha?\n\t\t\t\t\tif ( inst[ cache ] &&\n\t\t\t\t\t\t\tjQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {\n\n\t\t\t\t\t\t// Use the default of 1\n\t\t\t\t\t\tinst[ cache ][ 3 ] = 1;\n\t\t\t\t\t\tif ( space.from ) {\n\t\t\t\t\t\t\tinst._rgba = space.from( inst[ cache ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t},\n\tis: function( compare ) {\n\t\tvar is = color( compare ),\n\t\t\tsame = true,\n\t\t\tinst = this;\n\n\t\teach( spaces, function( _, space ) {\n\t\t\tvar localCache,\n\t\t\t\tisCache = is[ space.cache ];\n\t\t\tif ( isCache ) {\n\t\t\t\tlocalCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];\n\t\t\t\teach( space.props, function( _, prop ) {\n\t\t\t\t\tif ( isCache[ prop.idx ] != null ) {\n\t\t\t\t\t\tsame = ( isCache[ prop.idx ] === localCache[ prop.idx ] );\n\t\t\t\t\t\treturn same;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn same;\n\t\t} );\n\t\treturn same;\n\t},\n\t_space: function() {\n\t\tvar used = [],\n\t\t\tinst = this;\n\t\teach( spaces, function( spaceName, space ) {\n\t\t\tif ( inst[ space.cache ] ) {\n\t\t\t\tused.push( spaceName );\n\t\t\t}\n\t\t} );\n\t\treturn used.pop();\n\t},\n\ttransition: function( other, distance ) {\n\t\tvar end = color( other ),\n\t\t\tspaceName = end._space(),\n\t\t\tspace = spaces[ spaceName ],\n\t\t\tstartColor = this.alpha() === 0 ? color( \"transparent\" ) : this,\n\t\t\tstart = startColor[ space.cache ] || space.to( startColor._rgba ),\n\t\t\tresult = start.slice();\n\n\t\tend = end[ space.cache ];\n\t\teach( space.props, function( key, prop ) {\n\t\t\tvar index = prop.idx,\n\t\t\t\tstartValue = start[ index ],\n\t\t\t\tendValue = end[ index ],\n\t\t\t\ttype = propTypes[ prop.type ] || {};\n\n\t\t\t// If null, don't override start value\n\t\t\tif ( endValue === null ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If null - use end\n\t\t\tif ( startValue === null ) {\n\t\t\t\tresult[ index ] = endValue;\n\t\t\t} else {\n\t\t\t\tif ( type.mod ) {\n\t\t\t\t\tif ( endValue - startValue > type.mod / 2 ) {\n\t\t\t\t\t\tstartValue += type.mod;\n\t\t\t\t\t} else if ( startValue - endValue > type.mod / 2 ) {\n\t\t\t\t\t\tstartValue -= type.mod;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresult[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );\n\t\t\t}\n\t\t} );\n\t\treturn this[ spaceName ]( result );\n\t},\n\tblend: function( opaque ) {\n\n\t\t// If we are already opaque - return ourself\n\t\tif ( this._rgba[ 3 ] === 1 ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tvar rgb = this._rgba.slice(),\n\t\t\ta = rgb.pop(),\n\t\t\tblend = color( opaque )._rgba;\n\n\t\treturn color( jQuery.map( rgb, function( v, i ) {\n\t\t\treturn ( 1 - a ) * blend[ i ] + a * v;\n\t\t} ) );\n\t},\n\ttoRgbaString: function() {\n\t\tvar prefix = \"rgba(\",\n\t\t\trgba = jQuery.map( this._rgba, function( v, i ) {\n\t\t\t\treturn v == null ? ( i > 2 ? 1 : 0 ) : v;\n\t\t\t} );\n\n\t\tif ( rgba[ 3 ] === 1 ) {\n\t\t\trgba.pop();\n\t\t\tprefix = \"rgb(\";\n\t\t}\n\n\t\treturn prefix + rgba.join() + \")\";\n\t},\n\ttoHslaString: function() {\n\t\tvar prefix = \"hsla(\",\n\t\t\thsla = jQuery.map( this.hsla(), function( v, i ) {\n\t\t\t\tif ( v == null ) {\n\t\t\t\t\tv = i > 2 ? 1 : 0;\n\t\t\t\t}\n\n\t\t\t\t// Catch 1 and 2\n\t\t\t\tif ( i && i < 3 ) {\n\t\t\t\t\tv = Math.round( v * 100 ) + \"%\";\n\t\t\t\t}\n\t\t\t\treturn v;\n\t\t\t} );\n\n\t\tif ( hsla[ 3 ] === 1 ) {\n\t\t\thsla.pop();\n\t\t\tprefix = \"hsl(\";\n\t\t}\n\t\treturn prefix + hsla.join() + \")\";\n\t},\n\ttoHexString: function( includeAlpha ) {\n\t\tvar rgba = this._rgba.slice(),\n\t\t\talpha = rgba.pop();\n\n\t\tif ( includeAlpha ) {\n\t\t\trgba.push( ~~( alpha * 255 ) );\n\t\t}\n\n\t\treturn \"#\" + jQuery.map( rgba, function( v ) {\n\n\t\t\t// Default to 0 when nulls exist\n\t\t\tv = ( v || 0 ).toString( 16 );\n\t\t\treturn v.length === 1 ? \"0\" + v : v;\n\t\t} ).join( \"\" );\n\t},\n\ttoString: function() {\n\t\treturn this._rgba[ 3 ] === 0 ? \"transparent\" : this.toRgbaString();\n\t}\n} );\ncolor.fn.parse.prototype = color.fn;\n\n// Hsla conversions adapted from:\n// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021\n\nfunction hue2rgb( p, q, h ) {\n\th = ( h + 1 ) % 1;\n\tif ( h * 6 < 1 ) {\n\t\treturn p + ( q - p ) * h * 6;\n\t}\n\tif ( h * 2 < 1 ) {\n\t\treturn q;\n\t}\n\tif ( h * 3 < 2 ) {\n\t\treturn p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6;\n\t}\n\treturn p;\n}\n\nspaces.hsla.to = function( rgba ) {\n\tif ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {\n\t\treturn [ null, null, null, rgba[ 3 ] ];\n\t}\n\tvar r = rgba[ 0 ] / 255,\n\t\tg = rgba[ 1 ] / 255,\n\t\tb = rgba[ 2 ] / 255,\n\t\ta = rgba[ 3 ],\n\t\tmax = Math.max( r, g, b ),\n\t\tmin = Math.min( r, g, b ),\n\t\tdiff = max - min,\n\t\tadd = max + min,\n\t\tl = add * 0.5,\n\t\th, s;\n\n\tif ( min === max ) {\n\t\th = 0;\n\t} else if ( r === max ) {\n\t\th = ( 60 * ( g - b ) / diff ) + 360;\n\t} else if ( g === max ) {\n\t\th = ( 60 * ( b - r ) / diff ) + 120;\n\t} else {\n\t\th = ( 60 * ( r - g ) / diff ) + 240;\n\t}\n\n\t// Chroma (diff) == 0 means greyscale which, by definition, saturation = 0%\n\t// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)\n\tif ( diff === 0 ) {\n\t\ts = 0;\n\t} else if ( l <= 0.5 ) {\n\t\ts = diff / add;\n\t} else {\n\t\ts = diff / ( 2 - add );\n\t}\n\treturn [ Math.round( h ) % 360, s, l, a == null ? 1 : a ];\n};\n\nspaces.hsla.from = function( hsla ) {\n\tif ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {\n\t\treturn [ null, null, null, hsla[ 3 ] ];\n\t}\n\tvar h = hsla[ 0 ] / 360,\n\t\ts = hsla[ 1 ],\n\t\tl = hsla[ 2 ],\n\t\ta = hsla[ 3 ],\n\t\tq = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,\n\t\tp = 2 * l - q;\n\n\treturn [\n\t\tMath.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),\n\t\tMath.round( hue2rgb( p, q, h ) * 255 ),\n\t\tMath.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),\n\t\ta\n\t];\n};\n\neach( spaces, function( spaceName, space ) {\n\tvar props = space.props,\n\t\tcache = space.cache,\n\t\tto = space.to,\n\t\tfrom = space.from;\n\n\t// Makes rgba() and hsla()\n\tcolor.fn[ spaceName ] = function( value ) {\n\n\t\t// Generate a cache for this space if it doesn't exist\n\t\tif ( to && !this[ cache ] ) {\n\t\t\tthis[ cache ] = to( this._rgba );\n\t\t}\n\t\tif ( value === undefined ) {\n\t\t\treturn this[ cache ].slice();\n\t\t}\n\n\t\tvar ret,\n\t\t\ttype = jQuery.type( value ),\n\t\t\tarr = ( type === \"array\" || type === \"object\" ) ? value : arguments,\n\t\t\tlocal = this[ cache ].slice();\n\n\t\teach( props, function( key, prop ) {\n\t\t\tvar val = arr[ type === \"object\" ? key : prop.idx ];\n\t\t\tif ( val == null ) {\n\t\t\t\tval = local[ prop.idx ];\n\t\t\t}\n\t\t\tlocal[ prop.idx ] = clamp( val, prop );\n\t\t} );\n\n\t\tif ( from ) {\n\t\t\tret = color( from( local ) );\n\t\t\tret[ cache ] = local;\n\t\t\treturn ret;\n\t\t} else {\n\t\t\treturn color( local );\n\t\t}\n\t};\n\n\t// Makes red() green() blue() alpha() hue() saturation() lightness()\n\teach( props, function( key, prop ) {\n\n\t\t// Alpha is included in more than one space\n\t\tif ( color.fn[ key ] ) {\n\t\t\treturn;\n\t\t}\n\t\tcolor.fn[ key ] = function( value ) {\n\t\t\tvar vtype = jQuery.type( value ),\n\t\t\t\tfn = ( key === \"alpha\" ? ( this._hsla ? \"hsla\" : \"rgba\" ) : spaceName ),\n\t\t\t\tlocal = this[ fn ](),\n\t\t\t\tcur = local[ prop.idx ],\n\t\t\t\tmatch;\n\n\t\t\tif ( vtype === \"undefined\" ) {\n\t\t\t\treturn cur;\n\t\t\t}\n\n\t\t\tif ( vtype === \"function\" ) {\n\t\t\t\tvalue = value.call( this, cur );\n\t\t\t\tvtype = jQuery.type( value );\n\t\t\t}\n\t\t\tif ( value == null && prop.empty ) {\n\t\t\t\treturn this;\n\t\t\t}\n\t\t\tif ( vtype === \"string\" ) {\n\t\t\t\tmatch = rplusequals.exec( value );\n\t\t\t\tif ( match ) {\n\t\t\t\t\tvalue = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === \"+\" ? 1 : -1 );\n\t\t\t\t}\n\t\t\t}\n\t\t\tlocal[ prop.idx ] = value;\n\t\t\treturn this[ fn ]( local );\n\t\t};\n\t} );\n} );\n\n// Add cssHook and .fx.step function for each named hook.\n// accept a space separated string of properties\ncolor.hook = function( hook ) {\n\tvar hooks = hook.split( \" \" );\n\teach( hooks, function( i, hook ) {\n\t\tjQuery.cssHooks[ hook ] = {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar parsed, curElem,\n\t\t\t\t\tbackgroundColor = \"\";\n\n\t\t\t\tif ( value !== \"transparent\" && ( jQuery.type( value ) !== \"string\" ||\n\t\t\t\t\t\t( parsed = stringParse( value ) ) ) ) {\n\t\t\t\t\tvalue = color( parsed || value );\n\t\t\t\t\tif ( !support.rgba && value._rgba[ 3 ] !== 1 ) {\n\t\t\t\t\t\tcurElem = hook === \"backgroundColor\" ? elem.parentNode : elem;\n\t\t\t\t\t\twhile (\n\t\t\t\t\t\t\t( backgroundColor === \"\" || backgroundColor === \"transparent\" ) &&\n\t\t\t\t\t\t\tcurElem && curElem.style\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tbackgroundColor = jQuery.css( curElem, \"backgroundColor\" );\n\t\t\t\t\t\t\t\tcurElem = curElem.parentNode;\n\t\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvalue = value.blend( backgroundColor && backgroundColor !== \"transparent\" ?\n\t\t\t\t\t\t\tbackgroundColor :\n\t\t\t\t\t\t\t\"_default\" );\n\t\t\t\t\t}\n\n\t\t\t\t\tvalue = value.toRgbaString();\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\telem.style[ hook ] = value;\n\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t// Wrapped to prevent IE from throwing errors on \"invalid\" values like\n\t\t\t\t\t// 'auto' or 'inherit'\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tjQuery.fx.step[ hook ] = function( fx ) {\n\t\t\tif ( !fx.colorInit ) {\n\t\t\t\tfx.start = color( fx.elem, hook );\n\t\t\t\tfx.end = color( fx.end );\n\t\t\t\tfx.colorInit = true;\n\t\t\t}\n\t\t\tjQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );\n\t\t};\n\t} );\n\n};\n\ncolor.hook( stepHooks );\n\njQuery.cssHooks.borderColor = {\n\texpand: function( value ) {\n\t\tvar expanded = {};\n\n\t\teach( [ \"Top\", \"Right\", \"Bottom\", \"Left\" ], function( i, part ) {\n\t\t\texpanded[ \"border\" + part + \"Color\" ] = value;\n\t\t} );\n\t\treturn expanded;\n\t}\n};\n\n// Basic color names only.\n// Usage of any of the other color names requires adding yourself or including\n// jquery.color.svg-names.js.\ncolors = jQuery.Color.names = {\n\n\t// 4.1. Basic color keywords\n\taqua: \"#00ffff\",\n\tblack: \"#000000\",\n\tblue: \"#0000ff\",\n\tfuchsia: \"#ff00ff\",\n\tgray: \"#808080\",\n\tgreen: \"#008000\",\n\tlime: \"#00ff00\",\n\tmaroon: \"#800000\",\n\tnavy: \"#000080\",\n\tolive: \"#808000\",\n\tpurple: \"#800080\",\n\tred: \"#ff0000\",\n\tsilver: \"#c0c0c0\",\n\tteal: \"#008080\",\n\twhite: \"#ffffff\",\n\tyellow: \"#ffff00\",\n\n\t// 4.2.3. \"transparent\" color keyword\n\ttransparent: [ null, null, null, 0 ],\n\n\t_default: \"#ffffff\"\n};\n\n} )( jQuery );\n\n/******************************************************************************/\n/****************************** CLASS ANIMATIONS ******************************/\n/******************************************************************************/\n( function() {\n\nvar classAnimationActions = [ \"add\", \"remove\", \"toggle\" ],\n\tshorthandStyles = {\n\t\tborder: 1,\n\t\tborderBottom: 1,\n\t\tborderColor: 1,\n\t\tborderLeft: 1,\n\t\tborderRight: 1,\n\t\tborderTop: 1,\n\t\tborderWidth: 1,\n\t\tmargin: 1,\n\t\tpadding: 1\n\t};\n\n$.each(\n\t[ \"borderLeftStyle\", \"borderRightStyle\", \"borderBottomStyle\", \"borderTopStyle\" ],\n\tfunction( _, prop ) {\n\t\t$.fx.step[ prop ] = function( fx ) {\n\t\t\tif ( fx.end !== \"none\" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {\n\t\t\t\tjQuery.style( fx.elem, prop, fx.end );\n\t\t\t\tfx.setAttr = true;\n\t\t\t}\n\t\t};\n\t}\n);\n\nfunction getElementStyles( elem ) {\n\tvar key, len,\n\t\tstyle = elem.ownerDocument.defaultView ?\n\t\t\telem.ownerDocument.defaultView.getComputedStyle( elem, null ) :\n\t\t\telem.currentStyle,\n\t\tstyles = {};\n\n\tif ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {\n\t\tlen = style.length;\n\t\twhile ( len-- ) {\n\t\t\tkey = style[ len ];\n\t\t\tif ( typeof style[ key ] === \"string\" ) {\n\t\t\t\tstyles[ $.camelCase( key ) ] = style[ key ];\n\t\t\t}\n\t\t}\n\n\t// Support: Opera, IE <9\n\t} else {\n\t\tfor ( key in style ) {\n\t\t\tif ( typeof style[ key ] === \"string\" ) {\n\t\t\t\tstyles[ key ] = style[ key ];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn styles;\n}\n\nfunction styleDifference( oldStyle, newStyle ) {\n\tvar diff = {},\n\t\tname, value;\n\n\tfor ( name in newStyle ) {\n\t\tvalue = newStyle[ name ];\n\t\tif ( oldStyle[ name ] !== value ) {\n\t\t\tif ( !shorthandStyles[ name ] ) {\n\t\t\t\tif ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {\n\t\t\t\t\tdiff[ name ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn diff;\n}\n\n// Support: jQuery <1.8\nif ( !$.fn.addBack ) {\n\t$.fn.addBack = function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter( selector )\n\t\t);\n\t};\n}\n\n$.effects.animateClass = function( value, duration, easing, callback ) {\n\tvar o = $.speed( duration, easing, callback );\n\n\treturn this.queue( function() {\n\t\tvar animated = $( this ),\n\t\t\tbaseClass = animated.attr( \"class\" ) || \"\",\n\t\t\tapplyClassChange,\n\t\t\tallAnimations = o.children ? animated.find( \"*\" ).addBack() : animated;\n\n\t\t// Map the animated objects to store the original styles.\n\t\tallAnimations = allAnimations.map( function() {\n\t\t\tvar el = $( this );\n\t\t\treturn {\n\t\t\t\tel: el,\n\t\t\t\tstart: getElementStyles( this )\n\t\t\t};\n\t\t} );\n\n\t\t// Apply class change\n\t\tapplyClassChange = function() {\n\t\t\t$.each( classAnimationActions, function( i, action ) {\n\t\t\t\tif ( value[ action ] ) {\n\t\t\t\t\tanimated[ action + \"Class\" ]( value[ action ] );\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\t\tapplyClassChange();\n\n\t\t// Map all animated objects again - calculate new styles and diff\n\t\tallAnimations = allAnimations.map( function() {\n\t\t\tthis.end = getElementStyles( this.el[ 0 ] );\n\t\t\tthis.diff = styleDifference( this.start, this.end );\n\t\t\treturn this;\n\t\t} );\n\n\t\t// Apply original class\n\t\tanimated.attr( \"class\", baseClass );\n\n\t\t// Map all animated objects again - this time collecting a promise\n\t\tallAnimations = allAnimations.map( function() {\n\t\t\tvar styleInfo = this,\n\t\t\t\tdfd = $.Deferred(),\n\t\t\t\topts = $.extend( {}, o, {\n\t\t\t\t\tqueue: false,\n\t\t\t\t\tcomplete: function() {\n\t\t\t\t\t\tdfd.resolve( styleInfo );\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\tthis.el.animate( this.diff, opts );\n\t\t\treturn dfd.promise();\n\t\t} );\n\n\t\t// Once all animations have completed:\n\t\t$.when.apply( $, allAnimations.get() ).done( function() {\n\n\t\t\t// Set the final class\n\t\t\tapplyClassChange();\n\n\t\t\t// For each animated element,\n\t\t\t// clear all css properties that were animated\n\t\t\t$.each( arguments, function() {\n\t\t\t\tvar el = this.el;\n\t\t\t\t$.each( this.diff, function( key ) {\n\t\t\t\t\tel.css( key, \"\" );\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\t// This is guarnteed to be there if you use jQuery.speed()\n\t\t\t// it also handles dequeuing the next anim...\n\t\t\to.complete.call( animated[ 0 ] );\n\t\t} );\n\t} );\n};\n\n$.fn.extend( {\n\taddClass: ( function( orig ) {\n\t\treturn function( classNames, speed, easing, callback ) {\n\t\t\treturn speed ?\n\t\t\t\t$.effects.animateClass.call( this,\n\t\t\t\t\t{ add: classNames }, speed, easing, callback ) :\n\t\t\t\torig.apply( this, arguments );\n\t\t};\n\t} )( $.fn.addClass ),\n\n\tremoveClass: ( function( orig ) {\n\t\treturn function( classNames, speed, easing, callback ) {\n\t\t\treturn arguments.length > 1 ?\n\t\t\t\t$.effects.animateClass.call( this,\n\t\t\t\t\t{ remove: classNames }, speed, easing, callback ) :\n\t\t\t\torig.apply( this, arguments );\n\t\t};\n\t} )( $.fn.removeClass ),\n\n\ttoggleClass: ( function( orig ) {\n\t\treturn function( classNames, force, speed, easing, callback ) {\n\t\t\tif ( typeof force === \"boolean\" || force === undefined ) {\n\t\t\t\tif ( !speed ) {\n\n\t\t\t\t\t// Without speed parameter\n\t\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t\t} else {\n\t\t\t\t\treturn $.effects.animateClass.call( this,\n\t\t\t\t\t\t( force ? { add: classNames } : { remove: classNames } ),\n\t\t\t\t\t\tspeed, easing, callback );\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// Without force parameter\n\t\t\t\treturn $.effects.animateClass.call( this,\n\t\t\t\t\t{ toggle: classNames }, force, speed, easing );\n\t\t\t}\n\t\t};\n\t} )( $.fn.toggleClass ),\n\n\tswitchClass: function( remove, add, speed, easing, callback ) {\n\t\treturn $.effects.animateClass.call( this, {\n\t\t\tadd: add,\n\t\t\tremove: remove\n\t\t}, speed, easing, callback );\n\t}\n} );\n\n} )();\n\n/******************************************************************************/\n/*********************************** EFFECTS **********************************/\n/******************************************************************************/\n\n( function() {\n\nif ( $.expr && $.expr.filters && $.expr.filters.animated ) {\n\t$.expr.filters.animated = ( function( orig ) {\n\t\treturn function( elem ) {\n\t\t\treturn !!$( elem ).data( dataSpaceAnimated ) || orig( elem );\n\t\t};\n\t} )( $.expr.filters.animated );\n}\n\nif ( $.uiBackCompat !== false ) {\n\t$.extend( $.effects, {\n\n\t\t// Saves a set of properties in a data storage\n\t\tsave: function( element, set ) {\n\t\t\tvar i = 0, length = set.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( set[ i ] !== null ) {\n\t\t\t\t\telement.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Restores a set of previously saved properties from a data storage\n\t\trestore: function( element, set ) {\n\t\t\tvar val, i = 0, length = set.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( set[ i ] !== null ) {\n\t\t\t\t\tval = element.data( dataSpace + set[ i ] );\n\t\t\t\t\telement.css( set[ i ], val );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tsetMode: function( el, mode ) {\n\t\t\tif ( mode === \"toggle\" ) {\n\t\t\t\tmode = el.is( \":hidden\" ) ? \"show\" : \"hide\";\n\t\t\t}\n\t\t\treturn mode;\n\t\t},\n\n\t\t// Wraps the element around a wrapper that copies position properties\n\t\tcreateWrapper: function( element ) {\n\n\t\t\t// If the element is already wrapped, return it\n\t\t\tif ( element.parent().is( \".ui-effects-wrapper\" ) ) {\n\t\t\t\treturn element.parent();\n\t\t\t}\n\n\t\t\t// Wrap the element\n\t\t\tvar props = {\n\t\t\t\t\twidth: element.outerWidth( true ),\n\t\t\t\t\theight: element.outerHeight( true ),\n\t\t\t\t\t\"float\": element.css( \"float\" )\n\t\t\t\t},\n\t\t\t\twrapper = $( \"<div></div>\" )\n\t\t\t\t\t.addClass( \"ui-effects-wrapper\" )\n\t\t\t\t\t.css( {\n\t\t\t\t\t\tfontSize: \"100%\",\n\t\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\t\tborder: \"none\",\n\t\t\t\t\t\tmargin: 0,\n\t\t\t\t\t\tpadding: 0\n\t\t\t\t\t} ),\n\n\t\t\t\t// Store the size in case width/height are defined in % - Fixes #5245\n\t\t\t\tsize = {\n\t\t\t\t\twidth: element.width(),\n\t\t\t\t\theight: element.height()\n\t\t\t\t},\n\t\t\t\tactive = document.activeElement;\n\n\t\t\t// Support: Firefox\n\t\t\t// Firefox incorrectly exposes anonymous content\n\t\t\t// https://bugzilla.mozilla.org/show_bug.cgi?id=561664\n\t\t\ttry {\n\t\t\t\tactive.id;\n\t\t\t} catch ( e ) {\n\t\t\t\tactive = document.body;\n\t\t\t}\n\n\t\t\telement.wrap( wrapper );\n\n\t\t\t// Fixes #7595 - Elements lose focus when wrapped.\n\t\t\tif ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {\n\t\t\t\t$( active ).trigger( \"focus\" );\n\t\t\t}\n\n\t\t\t// Hotfix for jQuery 1.4 since some change in wrap() seems to actually\n\t\t\t// lose the reference to the wrapped element\n\t\t\twrapper = element.parent();\n\n\t\t\t// Transfer positioning properties to the wrapper\n\t\t\tif ( element.css( \"position\" ) === \"static\" ) {\n\t\t\t\twrapper.css( { position: \"relative\" } );\n\t\t\t\telement.css( { position: \"relative\" } );\n\t\t\t} else {\n\t\t\t\t$.extend( props, {\n\t\t\t\t\tposition: element.css( \"position\" ),\n\t\t\t\t\tzIndex: element.css( \"z-index\" )\n\t\t\t\t} );\n\t\t\t\t$.each( [ \"top\", \"left\", \"bottom\", \"right\" ], function( i, pos ) {\n\t\t\t\t\tprops[ pos ] = element.css( pos );\n\t\t\t\t\tif ( isNaN( parseInt( props[ pos ], 10 ) ) ) {\n\t\t\t\t\t\tprops[ pos ] = \"auto\";\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\telement.css( {\n\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\ttop: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: \"auto\",\n\t\t\t\t\tbottom: \"auto\"\n\t\t\t\t} );\n\t\t\t}\n\t\t\telement.css( size );\n\n\t\t\treturn wrapper.css( props ).show();\n\t\t},\n\n\t\tremoveWrapper: function( element ) {\n\t\t\tvar active = document.activeElement;\n\n\t\t\tif ( element.parent().is( \".ui-effects-wrapper\" ) ) {\n\t\t\t\telement.parent().replaceWith( element );\n\n\t\t\t\t// Fixes #7595 - Elements lose focus when wrapped.\n\t\t\t\tif ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {\n\t\t\t\t\t$( active ).trigger( \"focus\" );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn element;\n\t\t}\n\t} );\n}\n\n$.extend( $.effects, {\n\tversion: \"1.12.1\",\n\n\tdefine: function( name, mode, effect ) {\n\t\tif ( !effect ) {\n\t\t\teffect = mode;\n\t\t\tmode = \"effect\";\n\t\t}\n\n\t\t$.effects.effect[ name ] = effect;\n\t\t$.effects.effect[ name ].mode = mode;\n\n\t\treturn effect;\n\t},\n\n\tscaledDimensions: function( element, percent, direction ) {\n\t\tif ( percent === 0 ) {\n\t\t\treturn {\n\t\t\t\theight: 0,\n\t\t\t\twidth: 0,\n\t\t\t\touterHeight: 0,\n\t\t\t\touterWidth: 0\n\t\t\t};\n\t\t}\n\n\t\tvar x = direction !== \"horizontal\" ? ( ( percent || 100 ) / 100 ) : 1,\n\t\t\ty = direction !== \"vertical\" ? ( ( percent || 100 ) / 100 ) : 1;\n\n\t\treturn {\n\t\t\theight: element.height() * y,\n\t\t\twidth: element.width() * x,\n\t\t\touterHeight: element.outerHeight() * y,\n\t\t\touterWidth: element.outerWidth() * x\n\t\t};\n\n\t},\n\n\tclipToBox: function( animation ) {\n\t\treturn {\n\t\t\twidth: animation.clip.right - animation.clip.left,\n\t\t\theight: animation.clip.bottom - animation.clip.top,\n\t\t\tleft: animation.clip.left,\n\t\t\ttop: animation.clip.top\n\t\t};\n\t},\n\n\t// Injects recently queued functions to be first in line (after \"inprogress\")\n\tunshift: function( element, queueLength, count ) {\n\t\tvar queue = element.queue();\n\n\t\tif ( queueLength > 1 ) {\n\t\t\tqueue.splice.apply( queue,\n\t\t\t\t[ 1, 0 ].concat( queue.splice( queueLength, count ) ) );\n\t\t}\n\t\telement.dequeue();\n\t},\n\n\tsaveStyle: function( element ) {\n\t\telement.data( dataSpaceStyle, element[ 0 ].style.cssText );\n\t},\n\n\trestoreStyle: function( element ) {\n\t\telement[ 0 ].style.cssText = element.data( dataSpaceStyle ) || \"\";\n\t\telement.removeData( dataSpaceStyle );\n\t},\n\n\tmode: function( element, mode ) {\n\t\tvar hidden = element.is( \":hidden\" );\n\n\t\tif ( mode === \"toggle\" ) {\n\t\t\tmode = hidden ? \"show\" : \"hide\";\n\t\t}\n\t\tif ( hidden ? mode === \"hide\" : mode === \"show\" ) {\n\t\t\tmode = \"none\";\n\t\t}\n\t\treturn mode;\n\t},\n\n\t// Translates a [top,left] array into a baseline value\n\tgetBaseline: function( origin, original ) {\n\t\tvar y, x;\n\n\t\tswitch ( origin[ 0 ] ) {\n\t\tcase \"top\":\n\t\t\ty = 0;\n\t\t\tbreak;\n\t\tcase \"middle\":\n\t\t\ty = 0.5;\n\t\t\tbreak;\n\t\tcase \"bottom\":\n\t\t\ty = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ty = origin[ 0 ] / original.height;\n\t\t}\n\n\t\tswitch ( origin[ 1 ] ) {\n\t\tcase \"left\":\n\t\t\tx = 0;\n\t\t\tbreak;\n\t\tcase \"center\":\n\t\t\tx = 0.5;\n\t\t\tbreak;\n\t\tcase \"right\":\n\t\t\tx = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tx = origin[ 1 ] / original.width;\n\t\t}\n\n\t\treturn {\n\t\t\tx: x,\n\t\t\ty: y\n\t\t};\n\t},\n\n\t// Creates a placeholder element so that the original element can be made absolute\n\tcreatePlaceholder: function( element ) {\n\t\tvar placeholder,\n\t\t\tcssPosition = element.css( \"position\" ),\n\t\t\tposition = element.position();\n\n\t\t// Lock in margins first to account for form elements, which\n\t\t// will change margin if you explicitly set height\n\t\t// see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380\n\t\t// Support: Safari\n\t\telement.css( {\n\t\t\tmarginTop: element.css( \"marginTop\" ),\n\t\t\tmarginBottom: element.css( \"marginBottom\" ),\n\t\t\tmarginLeft: element.css( \"marginLeft\" ),\n\t\t\tmarginRight: element.css( \"marginRight\" )\n\t\t} )\n\t\t.outerWidth( element.outerWidth() )\n\t\t.outerHeight( element.outerHeight() );\n\n\t\tif ( /^(static|relative)/.test( cssPosition ) ) {\n\t\t\tcssPosition = \"absolute\";\n\n\t\t\tplaceholder = $( \"<\" + element[ 0 ].nodeName + \">\" ).insertAfter( element ).css( {\n\n\t\t\t\t// Convert inline to inline block to account for inline elements\n\t\t\t\t// that turn to inline block based on content (like img)\n\t\t\t\tdisplay: /^(inline|ruby)/.test( element.css( \"display\" ) ) ?\n\t\t\t\t\t\"inline-block\" :\n\t\t\t\t\t\"block\",\n\t\t\t\tvisibility: \"hidden\",\n\n\t\t\t\t// Margins need to be set to account for margin collapse\n\t\t\t\tmarginTop: element.css( \"marginTop\" ),\n\t\t\t\tmarginBottom: element.css( \"marginBottom\" ),\n\t\t\t\tmarginLeft: element.css( \"marginLeft\" ),\n\t\t\t\tmarginRight: element.css( \"marginRight\" ),\n\t\t\t\t\"float\": element.css( \"float\" )\n\t\t\t} )\n\t\t\t.outerWidth( element.outerWidth() )\n\t\t\t.outerHeight( element.outerHeight() )\n\t\t\t.addClass( \"ui-effects-placeholder\" );\n\n\t\t\telement.data( dataSpace + \"placeholder\", placeholder );\n\t\t}\n\n\t\telement.css( {\n\t\t\tposition: cssPosition,\n\t\t\tleft: position.left,\n\t\t\ttop: position.top\n\t\t} );\n\n\t\treturn placeholder;\n\t},\n\n\tremovePlaceholder: function( element ) {\n\t\tvar dataKey = dataSpace + \"placeholder\",\n\t\t\t\tplaceholder = element.data( dataKey );\n\n\t\tif ( placeholder ) {\n\t\t\tplaceholder.remove();\n\t\t\telement.removeData( dataKey );\n\t\t}\n\t},\n\n\t// Removes a placeholder if it exists and restores\n\t// properties that were modified during placeholder creation\n\tcleanUp: function( element ) {\n\t\t$.effects.restoreStyle( element );\n\t\t$.effects.removePlaceholder( element );\n\t},\n\n\tsetTransition: function( element, list, factor, value ) {\n\t\tvalue = value || {};\n\t\t$.each( list, function( i, x ) {\n\t\t\tvar unit = element.cssUnit( x );\n\t\t\tif ( unit[ 0 ] > 0 ) {\n\t\t\t\tvalue[ x ] = unit[ 0 ] * factor + unit[ 1 ];\n\t\t\t}\n\t\t} );\n\t\treturn value;\n\t}\n} );\n\n// Return an effect options object for the given parameters:\nfunction _normalizeArguments( effect, options, speed, callback ) {\n\n\t// Allow passing all options as the first parameter\n\tif ( $.isPlainObject( effect ) ) {\n\t\toptions = effect;\n\t\teffect = effect.effect;\n\t}\n\n\t// Convert to an object\n\teffect = { effect: effect };\n\n\t// Catch (effect, null, ...)\n\tif ( options == null ) {\n\t\toptions = {};\n\t}\n\n\t// Catch (effect, callback)\n\tif ( $.isFunction( options ) ) {\n\t\tcallback = options;\n\t\tspeed = null;\n\t\toptions = {};\n\t}\n\n\t// Catch (effect, speed, ?)\n\tif ( typeof options === \"number\" || $.fx.speeds[ options ] ) {\n\t\tcallback = speed;\n\t\tspeed = options;\n\t\toptions = {};\n\t}\n\n\t// Catch (effect, options, callback)\n\tif ( $.isFunction( speed ) ) {\n\t\tcallback = speed;\n\t\tspeed = null;\n\t}\n\n\t// Add options to effect\n\tif ( options ) {\n\t\t$.extend( effect, options );\n\t}\n\n\tspeed = speed || options.duration;\n\teffect.duration = $.fx.off ? 0 :\n\t\ttypeof speed === \"number\" ? speed :\n\t\tspeed in $.fx.speeds ? $.fx.speeds[ speed ] :\n\t\t$.fx.speeds._default;\n\n\teffect.complete = callback || options.complete;\n\n\treturn effect;\n}\n\nfunction standardAnimationOption( option ) {\n\n\t// Valid standard speeds (nothing, number, named speed)\n\tif ( !option || typeof option === \"number\" || $.fx.speeds[ option ] ) {\n\t\treturn true;\n\t}\n\n\t// Invalid strings - treat as \"normal\" speed\n\tif ( typeof option === \"string\" && !$.effects.effect[ option ] ) {\n\t\treturn true;\n\t}\n\n\t// Complete callback\n\tif ( $.isFunction( option ) ) {\n\t\treturn true;\n\t}\n\n\t// Options hash (but not naming an effect)\n\tif ( typeof option === \"object\" && !option.effect ) {\n\t\treturn true;\n\t}\n\n\t// Didn't match any standard API\n\treturn false;\n}\n\n$.fn.extend( {\n\teffect: function( /* effect, options, speed, callback */ ) {\n\t\tvar args = _normalizeArguments.apply( this, arguments ),\n\t\t\teffectMethod = $.effects.effect[ args.effect ],\n\t\t\tdefaultMode = effectMethod.mode,\n\t\t\tqueue = args.queue,\n\t\t\tqueueName = queue || \"fx\",\n\t\t\tcomplete = args.complete,\n\t\t\tmode = args.mode,\n\t\t\tmodes = [],\n\t\t\tprefilter = function( next ) {\n\t\t\t\tvar el = $( this ),\n\t\t\t\t\tnormalizedMode = $.effects.mode( el, mode ) || defaultMode;\n\n\t\t\t\t// Sentinel for duck-punching the :animated psuedo-selector\n\t\t\t\tel.data( dataSpaceAnimated, true );\n\n\t\t\t\t// Save effect mode for later use,\n\t\t\t\t// we can't just call $.effects.mode again later,\n\t\t\t\t// as the .show() below destroys the initial state\n\t\t\t\tmodes.push( normalizedMode );\n\n\t\t\t\t// See $.uiBackCompat inside of run() for removal of defaultMode in 1.13\n\t\t\t\tif ( defaultMode && ( normalizedMode === \"show\" ||\n\t\t\t\t\t\t( normalizedMode === defaultMode && normalizedMode === \"hide\" ) ) ) {\n\t\t\t\t\tel.show();\n\t\t\t\t}\n\n\t\t\t\tif ( !defaultMode || normalizedMode !== \"none\" ) {\n\t\t\t\t\t$.effects.saveStyle( el );\n\t\t\t\t}\n\n\t\t\t\tif ( $.isFunction( next ) ) {\n\t\t\t\t\tnext();\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( $.fx.off || !effectMethod ) {\n\n\t\t\t// Delegate to the original method (e.g., .show()) if possible\n\t\t\tif ( mode ) {\n\t\t\t\treturn this[ mode ]( args.duration, complete );\n\t\t\t} else {\n\t\t\t\treturn this.each( function() {\n\t\t\t\t\tif ( complete ) {\n\t\t\t\t\t\tcomplete.call( this );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tfunction run( next ) {\n\t\t\tvar elem = $( this );\n\n\t\t\tfunction cleanup() {\n\t\t\t\telem.removeData( dataSpaceAnimated );\n\n\t\t\t\t$.effects.cleanUp( elem );\n\n\t\t\t\tif ( args.mode === \"hide\" ) {\n\t\t\t\t\telem.hide();\n\t\t\t\t}\n\n\t\t\t\tdone();\n\t\t\t}\n\n\t\t\tfunction done() {\n\t\t\t\tif ( $.isFunction( complete ) ) {\n\t\t\t\t\tcomplete.call( elem[ 0 ] );\n\t\t\t\t}\n\n\t\t\t\tif ( $.isFunction( next ) ) {\n\t\t\t\t\tnext();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override mode option on a per element basis,\n\t\t\t// as toggle can be either show or hide depending on element state\n\t\t\targs.mode = modes.shift();\n\n\t\t\tif ( $.uiBackCompat !== false && !defaultMode ) {\n\t\t\t\tif ( elem.is( \":hidden\" ) ? mode === \"hide\" : mode === \"show\" ) {\n\n\t\t\t\t\t// Call the core method to track \"olddisplay\" properly\n\t\t\t\t\telem[ mode ]();\n\t\t\t\t\tdone();\n\t\t\t\t} else {\n\t\t\t\t\teffectMethod.call( elem[ 0 ], args, done );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( args.mode === \"none\" ) {\n\n\t\t\t\t\t// Call the core method to track \"olddisplay\" properly\n\t\t\t\t\telem[ mode ]();\n\t\t\t\t\tdone();\n\t\t\t\t} else {\n\t\t\t\t\teffectMethod.call( elem[ 0 ], args, cleanup );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Run prefilter on all elements first to ensure that\n\t\t// any showing or hiding happens before placeholder creation,\n\t\t// which ensures that any layout changes are correctly captured.\n\t\treturn queue === false ?\n\t\t\tthis.each( prefilter ).each( run ) :\n\t\t\tthis.queue( queueName, prefilter ).queue( queueName, run );\n\t},\n\n\tshow: ( function( orig ) {\n\t\treturn function( option ) {\n\t\t\tif ( standardAnimationOption( option ) ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t} else {\n\t\t\t\tvar args = _normalizeArguments.apply( this, arguments );\n\t\t\t\targs.mode = \"show\";\n\t\t\t\treturn this.effect.call( this, args );\n\t\t\t}\n\t\t};\n\t} )( $.fn.show ),\n\n\thide: ( function( orig ) {\n\t\treturn function( option ) {\n\t\t\tif ( standardAnimationOption( option ) ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t} else {\n\t\t\t\tvar args = _normalizeArguments.apply( this, arguments );\n\t\t\t\targs.mode = \"hide\";\n\t\t\t\treturn this.effect.call( this, args );\n\t\t\t}\n\t\t};\n\t} )( $.fn.hide ),\n\n\ttoggle: ( function( orig ) {\n\t\treturn function( option ) {\n\t\t\tif ( standardAnimationOption( option ) || typeof option === \"boolean\" ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t} else {\n\t\t\t\tvar args = _normalizeArguments.apply( this, arguments );\n\t\t\t\targs.mode = \"toggle\";\n\t\t\t\treturn this.effect.call( this, args );\n\t\t\t}\n\t\t};\n\t} )( $.fn.toggle ),\n\n\tcssUnit: function( key ) {\n\t\tvar style = this.css( key ),\n\t\t\tval = [];\n\n\t\t$.each( [ \"em\", \"px\", \"%\", \"pt\" ], function( i, unit ) {\n\t\t\tif ( style.indexOf( unit ) > 0 ) {\n\t\t\t\tval = [ parseFloat( style ), unit ];\n\t\t\t}\n\t\t} );\n\t\treturn val;\n\t},\n\n\tcssClip: function( clipObj ) {\n\t\tif ( clipObj ) {\n\t\t\treturn this.css( \"clip\", \"rect(\" + clipObj.top + \"px \" + clipObj.right + \"px \" +\n\t\t\t\tclipObj.bottom + \"px \" + clipObj.left + \"px)\" );\n\t\t}\n\t\treturn parseClip( this.css( \"clip\" ), this );\n\t},\n\n\ttransfer: function( options, done ) {\n\t\tvar element = $( this ),\n\t\t\ttarget = $( options.to ),\n\t\t\ttargetFixed = target.css( \"position\" ) === \"fixed\",\n\t\t\tbody = $( \"body\" ),\n\t\t\tfixTop = targetFixed ? body.scrollTop() : 0,\n\t\t\tfixLeft = targetFixed ? body.scrollLeft() : 0,\n\t\t\tendPosition = target.offset(),\n\t\t\tanimation = {\n\t\t\t\ttop: endPosition.top - fixTop,\n\t\t\t\tleft: endPosition.left - fixLeft,\n\t\t\t\theight: target.innerHeight(),\n\t\t\t\twidth: target.innerWidth()\n\t\t\t},\n\t\t\tstartPosition = element.offset(),\n\t\t\ttransfer = $( \"<div class='ui-effects-transfer'></div>\" )\n\t\t\t\t.appendTo( \"body\" )\n\t\t\t\t.addClass( options.className )\n\t\t\t\t.css( {\n\t\t\t\t\ttop: startPosition.top - fixTop,\n\t\t\t\t\tleft: startPosition.left - fixLeft,\n\t\t\t\t\theight: element.innerHeight(),\n\t\t\t\t\twidth: element.innerWidth(),\n\t\t\t\t\tposition: targetFixed ? \"fixed\" : \"absolute\"\n\t\t\t\t} )\n\t\t\t\t.animate( animation, options.duration, options.easing, function() {\n\t\t\t\t\ttransfer.remove();\n\t\t\t\t\tif ( $.isFunction( done ) ) {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t}\n} );\n\nfunction parseClip( str, element ) {\n\t\tvar outerWidth = element.outerWidth(),\n\t\t\touterHeight = element.outerHeight(),\n\t\t\tclipRegex = /^rect\\((-?\\d*\\.?\\d*px|-?\\d+%|auto),?\\s*(-?\\d*\\.?\\d*px|-?\\d+%|auto),?\\s*(-?\\d*\\.?\\d*px|-?\\d+%|auto),?\\s*(-?\\d*\\.?\\d*px|-?\\d+%|auto)\\)$/,\n\t\t\tvalues = clipRegex.exec( str ) || [ \"\", 0, outerWidth, outerHeight, 0 ];\n\n\t\treturn {\n\t\t\ttop: parseFloat( values[ 1 ] ) || 0,\n\t\t\tright: values[ 2 ] === \"auto\" ? outerWidth : parseFloat( values[ 2 ] ),\n\t\t\tbottom: values[ 3 ] === \"auto\" ? outerHeight : parseFloat( values[ 3 ] ),\n\t\t\tleft: parseFloat( values[ 4 ] ) || 0\n\t\t};\n}\n\n$.fx.step.clip = function( fx ) {\n\tif ( !fx.clipInit ) {\n\t\tfx.start = $( fx.elem ).cssClip();\n\t\tif ( typeof fx.end === \"string\" ) {\n\t\t\tfx.end = parseClip( fx.end, fx.elem );\n\t\t}\n\t\tfx.clipInit = true;\n\t}\n\n\t$( fx.elem ).cssClip( {\n\t\ttop: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top,\n\t\tright: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right,\n\t\tbottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom,\n\t\tleft: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left\n\t} );\n};\n\n} )();\n\n/******************************************************************************/\n/*********************************** EASING ***********************************/\n/******************************************************************************/\n\n( function() {\n\n// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing)\n\nvar baseEasings = {};\n\n$.each( [ \"Quad\", \"Cubic\", \"Quart\", \"Quint\", \"Expo\" ], function( i, name ) {\n\tbaseEasings[ name ] = function( p ) {\n\t\treturn Math.pow( p, i + 2 );\n\t};\n} );\n\n$.extend( baseEasings, {\n\tSine: function( p ) {\n\t\treturn 1 - Math.cos( p * Math.PI / 2 );\n\t},\n\tCirc: function( p ) {\n\t\treturn 1 - Math.sqrt( 1 - p * p );\n\t},\n\tElastic: function( p ) {\n\t\treturn p === 0 || p === 1 ? p :\n\t\t\t-Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 );\n\t},\n\tBack: function( p ) {\n\t\treturn p * p * ( 3 * p - 2 );\n\t},\n\tBounce: function( p ) {\n\t\tvar pow2,\n\t\t\tbounce = 4;\n\n\t\twhile ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}\n\t\treturn 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );\n\t}\n} );\n\n$.each( baseEasings, function( name, easeIn ) {\n\t$.easing[ \"easeIn\" + name ] = easeIn;\n\t$.easing[ \"easeOut\" + name ] = function( p ) {\n\t\treturn 1 - easeIn( 1 - p );\n\t};\n\t$.easing[ \"easeInOut\" + name ] = function( p ) {\n\t\treturn p < 0.5 ?\n\t\t\teaseIn( p * 2 ) / 2 :\n\t\t\t1 - easeIn( p * -2 + 2 ) / 2;\n\t};\n} );\n\n} )();\n\nvar effect = $.effects;\n\n\n/*!\n * jQuery UI Effects Blind 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Blind Effect\n//>>group: Effects\n//>>description: Blinds the element.\n//>>docs: http://api.jqueryui.com/blind-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectBlind = $.effects.define( \"blind\", \"hide\", function( options, done ) {\n\tvar map = {\n\t\t\tup: [ \"bottom\", \"top\" ],\n\t\t\tvertical: [ \"bottom\", \"top\" ],\n\t\t\tdown: [ \"top\", \"bottom\" ],\n\t\t\tleft: [ \"right\", \"left\" ],\n\t\t\thorizontal: [ \"right\", \"left\" ],\n\t\t\tright: [ \"left\", \"right\" ]\n\t\t},\n\t\telement = $( this ),\n\t\tdirection = options.direction || \"up\",\n\t\tstart = element.cssClip(),\n\t\tanimate = { clip: $.extend( {}, start ) },\n\t\tplaceholder = $.effects.createPlaceholder( element );\n\n\tanimate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ];\n\n\tif ( options.mode === \"show\" ) {\n\t\telement.cssClip( animate.clip );\n\t\tif ( placeholder ) {\n\t\t\tplaceholder.css( $.effects.clipToBox( animate ) );\n\t\t}\n\n\t\tanimate.clip = start;\n\t}\n\n\tif ( placeholder ) {\n\t\tplaceholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing );\n\t}\n\n\telement.animate( animate, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Bounce 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Bounce Effect\n//>>group: Effects\n//>>description: Bounces an element horizontally or vertically n times.\n//>>docs: http://api.jqueryui.com/bounce-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectBounce = $.effects.define( \"bounce\", function( options, done ) {\n\tvar upAnim, downAnim, refValue,\n\t\telement = $( this ),\n\n\t\t// Defaults:\n\t\tmode = options.mode,\n\t\thide = mode === \"hide\",\n\t\tshow = mode === \"show\",\n\t\tdirection = options.direction || \"up\",\n\t\tdistance = options.distance,\n\t\ttimes = options.times || 5,\n\n\t\t// Number of internal animations\n\t\tanims = times * 2 + ( show || hide ? 1 : 0 ),\n\t\tspeed = options.duration / anims,\n\t\teasing = options.easing,\n\n\t\t// Utility:\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tmotion = ( direction === \"up\" || direction === \"left\" ),\n\t\ti = 0,\n\n\t\tqueuelen = element.queue().length;\n\n\t$.effects.createPlaceholder( element );\n\n\trefValue = element.css( ref );\n\n\t// Default distance for the BIGGEST bounce is the outer Distance / 3\n\tif ( !distance ) {\n\t\tdistance = element[ ref === \"top\" ? \"outerHeight\" : \"outerWidth\" ]() / 3;\n\t}\n\n\tif ( show ) {\n\t\tdownAnim = { opacity: 1 };\n\t\tdownAnim[ ref ] = refValue;\n\n\t\t// If we are showing, force opacity 0 and set the initial position\n\t\t// then do the \"first\" animation\n\t\telement\n\t\t\t.css( \"opacity\", 0 )\n\t\t\t.css( ref, motion ? -distance * 2 : distance * 2 )\n\t\t\t.animate( downAnim, speed, easing );\n\t}\n\n\t// Start at the smallest distance if we are hiding\n\tif ( hide ) {\n\t\tdistance = distance / Math.pow( 2, times - 1 );\n\t}\n\n\tdownAnim = {};\n\tdownAnim[ ref ] = refValue;\n\n\t// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here\n\tfor ( ; i < times; i++ ) {\n\t\tupAnim = {};\n\t\tupAnim[ ref ] = ( motion ? \"-=\" : \"+=\" ) + distance;\n\n\t\telement\n\t\t\t.animate( upAnim, speed, easing )\n\t\t\t.animate( downAnim, speed, easing );\n\n\t\tdistance = hide ? distance * 2 : distance / 2;\n\t}\n\n\t// Last Bounce when Hiding\n\tif ( hide ) {\n\t\tupAnim = { opacity: 0 };\n\t\tupAnim[ ref ] = ( motion ? \"-=\" : \"+=\" ) + distance;\n\n\t\telement.animate( upAnim, speed, easing );\n\t}\n\n\telement.queue( done );\n\n\t$.effects.unshift( element, queuelen, anims + 1 );\n} );\n\n\n/*!\n * jQuery UI Effects Clip 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Clip Effect\n//>>group: Effects\n//>>description: Clips the element on and off like an old TV.\n//>>docs: http://api.jqueryui.com/clip-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectClip = $.effects.define( \"clip\", \"hide\", function( options, done ) {\n\tvar start,\n\t\tanimate = {},\n\t\telement = $( this ),\n\t\tdirection = options.direction || \"vertical\",\n\t\tboth = direction === \"both\",\n\t\thorizontal = both || direction === \"horizontal\",\n\t\tvertical = both || direction === \"vertical\";\n\n\tstart = element.cssClip();\n\tanimate.clip = {\n\t\ttop: vertical ? ( start.bottom - start.top ) / 2 : start.top,\n\t\tright: horizontal ? ( start.right - start.left ) / 2 : start.right,\n\t\tbottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom,\n\t\tleft: horizontal ? ( start.right - start.left ) / 2 : start.left\n\t};\n\n\t$.effects.createPlaceholder( element );\n\n\tif ( options.mode === \"show\" ) {\n\t\telement.cssClip( animate.clip );\n\t\tanimate.clip = start;\n\t}\n\n\telement.animate( animate, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n\n} );\n\n\n/*!\n * jQuery UI Effects Drop 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Drop Effect\n//>>group: Effects\n//>>description: Moves an element in one direction and hides it at the same time.\n//>>docs: http://api.jqueryui.com/drop-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectDrop = $.effects.define( \"drop\", \"hide\", function( options, done ) {\n\n\tvar distance,\n\t\telement = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\t\tdirection = options.direction || \"left\",\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tmotion = ( direction === \"up\" || direction === \"left\" ) ? \"-=\" : \"+=\",\n\t\toppositeMotion = ( motion === \"+=\" ) ? \"-=\" : \"+=\",\n\t\tanimation = {\n\t\t\topacity: 0\n\t\t};\n\n\t$.effects.createPlaceholder( element );\n\n\tdistance = options.distance ||\n\t\telement[ ref === \"top\" ? \"outerHeight\" : \"outerWidth\" ]( true ) / 2;\n\n\tanimation[ ref ] = motion + distance;\n\n\tif ( show ) {\n\t\telement.css( animation );\n\n\t\tanimation[ ref ] = oppositeMotion + distance;\n\t\tanimation.opacity = 1;\n\t}\n\n\t// Animate\n\telement.animate( animation, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Explode 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Explode Effect\n//>>group: Effects\n// jscs:disable maximumLineLength\n//>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/explode-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectExplode = $.effects.define( \"explode\", \"hide\", function( options, done ) {\n\n\tvar i, j, left, top, mx, my,\n\t\trows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3,\n\t\tcells = rows,\n\t\telement = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\n\t\t// Show and then visibility:hidden the element before calculating offset\n\t\toffset = element.show().css( \"visibility\", \"hidden\" ).offset(),\n\n\t\t// Width and height of a piece\n\t\twidth = Math.ceil( element.outerWidth() / cells ),\n\t\theight = Math.ceil( element.outerHeight() / rows ),\n\t\tpieces = [];\n\n\t// Children animate complete:\n\tfunction childComplete() {\n\t\tpieces.push( this );\n\t\tif ( pieces.length === rows * cells ) {\n\t\t\tanimComplete();\n\t\t}\n\t}\n\n\t// Clone the element for each row and cell.\n\tfor ( i = 0; i < rows; i++ ) { // ===>\n\t\ttop = offset.top + i * height;\n\t\tmy = i - ( rows - 1 ) / 2;\n\n\t\tfor ( j = 0; j < cells; j++ ) { // |||\n\t\t\tleft = offset.left + j * width;\n\t\t\tmx = j - ( cells - 1 ) / 2;\n\n\t\t\t// Create a clone of the now hidden main element that will be absolute positioned\n\t\t\t// within a wrapper div off the -left and -top equal to size of our pieces\n\t\t\telement\n\t\t\t\t.clone()\n\t\t\t\t.appendTo( \"body\" )\n\t\t\t\t.wrap( \"<div></div>\" )\n\t\t\t\t.css( {\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tvisibility: \"visible\",\n\t\t\t\t\tleft: -j * width,\n\t\t\t\t\ttop: -i * height\n\t\t\t\t} )\n\n\t\t\t\t// Select the wrapper - make it overflow: hidden and absolute positioned based on\n\t\t\t\t// where the original was located +left and +top equal to the size of pieces\n\t\t\t\t.parent()\n\t\t\t\t\t.addClass( \"ui-effects-explode\" )\n\t\t\t\t\t.css( {\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tleft: left + ( show ? mx * width : 0 ),\n\t\t\t\t\t\ttop: top + ( show ? my * height : 0 ),\n\t\t\t\t\t\topacity: show ? 0 : 1\n\t\t\t\t\t} )\n\t\t\t\t\t.animate( {\n\t\t\t\t\t\tleft: left + ( show ? 0 : mx * width ),\n\t\t\t\t\t\ttop: top + ( show ? 0 : my * height ),\n\t\t\t\t\t\topacity: show ? 1 : 0\n\t\t\t\t\t}, options.duration || 500, options.easing, childComplete );\n\t\t}\n\t}\n\n\tfunction animComplete() {\n\t\telement.css( {\n\t\t\tvisibility: \"visible\"\n\t\t} );\n\t\t$( pieces ).remove();\n\t\tdone();\n\t}\n} );\n\n\n/*!\n * jQuery UI Effects Fade 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Fade Effect\n//>>group: Effects\n//>>description: Fades the element.\n//>>docs: http://api.jqueryui.com/fade-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectFade = $.effects.define( \"fade\", \"toggle\", function( options, done ) {\n\tvar show = options.mode === \"show\";\n\n\t$( this )\n\t\t.css( \"opacity\", show ? 0 : 1 )\n\t\t.animate( {\n\t\t\topacity: show ? 1 : 0\n\t\t}, {\n\t\t\tqueue: false,\n\t\t\tduration: options.duration,\n\t\t\teasing: options.easing,\n\t\t\tcomplete: done\n\t\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Fold 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Fold Effect\n//>>group: Effects\n//>>description: Folds an element first horizontally and then vertically.\n//>>docs: http://api.jqueryui.com/fold-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectFold = $.effects.define( \"fold\", \"hide\", function( options, done ) {\n\n\t// Create element\n\tvar element = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\t\thide = mode === \"hide\",\n\t\tsize = options.size || 15,\n\t\tpercent = /([0-9]+)%/.exec( size ),\n\t\thorizFirst = !!options.horizFirst,\n\t\tref = horizFirst ? [ \"right\", \"bottom\" ] : [ \"bottom\", \"right\" ],\n\t\tduration = options.duration / 2,\n\n\t\tplaceholder = $.effects.createPlaceholder( element ),\n\n\t\tstart = element.cssClip(),\n\t\tanimation1 = { clip: $.extend( {}, start ) },\n\t\tanimation2 = { clip: $.extend( {}, start ) },\n\n\t\tdistance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ],\n\n\t\tqueuelen = element.queue().length;\n\n\tif ( percent ) {\n\t\tsize = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];\n\t}\n\tanimation1.clip[ ref[ 0 ] ] = size;\n\tanimation2.clip[ ref[ 0 ] ] = size;\n\tanimation2.clip[ ref[ 1 ] ] = 0;\n\n\tif ( show ) {\n\t\telement.cssClip( animation2.clip );\n\t\tif ( placeholder ) {\n\t\t\tplaceholder.css( $.effects.clipToBox( animation2 ) );\n\t\t}\n\n\t\tanimation2.clip = start;\n\t}\n\n\t// Animate\n\telement\n\t\t.queue( function( next ) {\n\t\t\tif ( placeholder ) {\n\t\t\t\tplaceholder\n\t\t\t\t\t.animate( $.effects.clipToBox( animation1 ), duration, options.easing )\n\t\t\t\t\t.animate( $.effects.clipToBox( animation2 ), duration, options.easing );\n\t\t\t}\n\n\t\t\tnext();\n\t\t} )\n\t\t.animate( animation1, duration, options.easing )\n\t\t.animate( animation2, duration, options.easing )\n\t\t.queue( done );\n\n\t$.effects.unshift( element, queuelen, 4 );\n} );\n\n\n/*!\n * jQuery UI Effects Highlight 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Highlight Effect\n//>>group: Effects\n//>>description: Highlights the background of an element in a defined color for a custom duration.\n//>>docs: http://api.jqueryui.com/highlight-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectHighlight = $.effects.define( \"highlight\", \"show\", function( options, done ) {\n\tvar element = $( this ),\n\t\tanimation = {\n\t\t\tbackgroundColor: element.css( \"backgroundColor\" )\n\t\t};\n\n\tif ( options.mode === \"hide\" ) {\n\t\tanimation.opacity = 0;\n\t}\n\n\t$.effects.saveStyle( element );\n\n\telement\n\t\t.css( {\n\t\t\tbackgroundImage: \"none\",\n\t\t\tbackgroundColor: options.color || \"#ffff99\"\n\t\t} )\n\t\t.animate( animation, {\n\t\t\tqueue: false,\n\t\t\tduration: options.duration,\n\t\t\teasing: options.easing,\n\t\t\tcomplete: done\n\t\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Size 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Size Effect\n//>>group: Effects\n//>>description: Resize an element to a specified width and height.\n//>>docs: http://api.jqueryui.com/size-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectSize = $.effects.define( \"size\", function( options, done ) {\n\n\t// Create element\n\tvar baseline, factor, temp,\n\t\telement = $( this ),\n\n\t\t// Copy for children\n\t\tcProps = [ \"fontSize\" ],\n\t\tvProps = [ \"borderTopWidth\", \"borderBottomWidth\", \"paddingTop\", \"paddingBottom\" ],\n\t\thProps = [ \"borderLeftWidth\", \"borderRightWidth\", \"paddingLeft\", \"paddingRight\" ],\n\n\t\t// Set options\n\t\tmode = options.mode,\n\t\trestore = mode !== \"effect\",\n\t\tscale = options.scale || \"both\",\n\t\torigin = options.origin || [ \"middle\", \"center\" ],\n\t\tposition = element.css( \"position\" ),\n\t\tpos = element.position(),\n\t\toriginal = $.effects.scaledDimensions( element ),\n\t\tfrom = options.from || original,\n\t\tto = options.to || $.effects.scaledDimensions( element, 0 );\n\n\t$.effects.createPlaceholder( element );\n\n\tif ( mode === \"show\" ) {\n\t\ttemp = from;\n\t\tfrom = to;\n\t\tto = temp;\n\t}\n\n\t// Set scaling factor\n\tfactor = {\n\t\tfrom: {\n\t\t\ty: from.height / original.height,\n\t\t\tx: from.width / original.width\n\t\t},\n\t\tto: {\n\t\t\ty: to.height / original.height,\n\t\t\tx: to.width / original.width\n\t\t}\n\t};\n\n\t// Scale the css box\n\tif ( scale === \"box\" || scale === \"both\" ) {\n\n\t\t// Vertical props scaling\n\t\tif ( factor.from.y !== factor.to.y ) {\n\t\t\tfrom = $.effects.setTransition( element, vProps, factor.from.y, from );\n\t\t\tto = $.effects.setTransition( element, vProps, factor.to.y, to );\n\t\t}\n\n\t\t// Horizontal props scaling\n\t\tif ( factor.from.x !== factor.to.x ) {\n\t\t\tfrom = $.effects.setTransition( element, hProps, factor.from.x, from );\n\t\t\tto = $.effects.setTransition( element, hProps, factor.to.x, to );\n\t\t}\n\t}\n\n\t// Scale the content\n\tif ( scale === \"content\" || scale === \"both\" ) {\n\n\t\t// Vertical props scaling\n\t\tif ( factor.from.y !== factor.to.y ) {\n\t\t\tfrom = $.effects.setTransition( element, cProps, factor.from.y, from );\n\t\t\tto = $.effects.setTransition( element, cProps, factor.to.y, to );\n\t\t}\n\t}\n\n\t// Adjust the position properties based on the provided origin points\n\tif ( origin ) {\n\t\tbaseline = $.effects.getBaseline( origin, original );\n\t\tfrom.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top;\n\t\tfrom.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left;\n\t\tto.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top;\n\t\tto.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left;\n\t}\n\telement.css( from );\n\n\t// Animate the children if desired\n\tif ( scale === \"content\" || scale === \"both\" ) {\n\n\t\tvProps = vProps.concat( [ \"marginTop\", \"marginBottom\" ] ).concat( cProps );\n\t\thProps = hProps.concat( [ \"marginLeft\", \"marginRight\" ] );\n\n\t\t// Only animate children with width attributes specified\n\t\t// TODO: is this right? should we include anything with css width specified as well\n\t\telement.find( \"*[width]\" ).each( function() {\n\t\t\tvar child = $( this ),\n\t\t\t\tchildOriginal = $.effects.scaledDimensions( child ),\n\t\t\t\tchildFrom = {\n\t\t\t\t\theight: childOriginal.height * factor.from.y,\n\t\t\t\t\twidth: childOriginal.width * factor.from.x,\n\t\t\t\t\touterHeight: childOriginal.outerHeight * factor.from.y,\n\t\t\t\t\touterWidth: childOriginal.outerWidth * factor.from.x\n\t\t\t\t},\n\t\t\t\tchildTo = {\n\t\t\t\t\theight: childOriginal.height * factor.to.y,\n\t\t\t\t\twidth: childOriginal.width * factor.to.x,\n\t\t\t\t\touterHeight: childOriginal.height * factor.to.y,\n\t\t\t\t\touterWidth: childOriginal.width * factor.to.x\n\t\t\t\t};\n\n\t\t\t// Vertical props scaling\n\t\t\tif ( factor.from.y !== factor.to.y ) {\n\t\t\t\tchildFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom );\n\t\t\t\tchildTo = $.effects.setTransition( child, vProps, factor.to.y, childTo );\n\t\t\t}\n\n\t\t\t// Horizontal props scaling\n\t\t\tif ( factor.from.x !== factor.to.x ) {\n\t\t\t\tchildFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom );\n\t\t\t\tchildTo = $.effects.setTransition( child, hProps, factor.to.x, childTo );\n\t\t\t}\n\n\t\t\tif ( restore ) {\n\t\t\t\t$.effects.saveStyle( child );\n\t\t\t}\n\n\t\t\t// Animate children\n\t\t\tchild.css( childFrom );\n\t\t\tchild.animate( childTo, options.duration, options.easing, function() {\n\n\t\t\t\t// Restore children\n\t\t\t\tif ( restore ) {\n\t\t\t\t\t$.effects.restoreStyle( child );\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\t}\n\n\t// Animate\n\telement.animate( to, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: function() {\n\n\t\t\tvar offset = element.offset();\n\n\t\t\tif ( to.opacity === 0 ) {\n\t\t\t\telement.css( \"opacity\", from.opacity );\n\t\t\t}\n\n\t\t\tif ( !restore ) {\n\t\t\t\telement\n\t\t\t\t\t.css( \"position\", position === \"static\" ? \"relative\" : position )\n\t\t\t\t\t.offset( offset );\n\n\t\t\t\t// Need to save style here so that automatic style restoration\n\t\t\t\t// doesn't restore to the original styles from before the animation.\n\t\t\t\t$.effects.saveStyle( element );\n\t\t\t}\n\n\t\t\tdone();\n\t\t}\n\t} );\n\n} );\n\n\n/*!\n * jQuery UI Effects Scale 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Scale Effect\n//>>group: Effects\n//>>description: Grows or shrinks an element and its content.\n//>>docs: http://api.jqueryui.com/scale-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectScale = $.effects.define( \"scale\", function( options, done ) {\n\n\t// Create element\n\tvar el = $( this ),\n\t\tmode = options.mode,\n\t\tpercent = parseInt( options.percent, 10 ) ||\n\t\t\t( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== \"effect\" ? 0 : 100 ) ),\n\n\t\tnewOptions = $.extend( true, {\n\t\t\tfrom: $.effects.scaledDimensions( el ),\n\t\t\tto: $.effects.scaledDimensions( el, percent, options.direction || \"both\" ),\n\t\t\torigin: options.origin || [ \"middle\", \"center\" ]\n\t\t}, options );\n\n\t// Fade option to support puff\n\tif ( options.fade ) {\n\t\tnewOptions.from.opacity = 1;\n\t\tnewOptions.to.opacity = 0;\n\t}\n\n\t$.effects.effect.size.call( this, newOptions, done );\n} );\n\n\n/*!\n * jQuery UI Effects Puff 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Puff Effect\n//>>group: Effects\n//>>description: Creates a puff effect by scaling the element up and hiding it at the same time.\n//>>docs: http://api.jqueryui.com/puff-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectPuff = $.effects.define( \"puff\", \"hide\", function( options, done ) {\n\tvar newOptions = $.extend( true, {}, options, {\n\t\tfade: true,\n\t\tpercent: parseInt( options.percent, 10 ) || 150\n\t} );\n\n\t$.effects.effect.scale.call( this, newOptions, done );\n} );\n\n\n/*!\n * jQuery UI Effects Pulsate 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Pulsate Effect\n//>>group: Effects\n//>>description: Pulsates an element n times by changing the opacity to zero and back.\n//>>docs: http://api.jqueryui.com/pulsate-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectPulsate = $.effects.define( \"pulsate\", \"show\", function( options, done ) {\n\tvar element = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\t\thide = mode === \"hide\",\n\t\tshowhide = show || hide,\n\n\t\t// Showing or hiding leaves off the \"last\" animation\n\t\tanims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),\n\t\tduration = options.duration / anims,\n\t\tanimateTo = 0,\n\t\ti = 1,\n\t\tqueuelen = element.queue().length;\n\n\tif ( show || !element.is( \":visible\" ) ) {\n\t\telement.css( \"opacity\", 0 ).show();\n\t\tanimateTo = 1;\n\t}\n\n\t// Anims - 1 opacity \"toggles\"\n\tfor ( ; i < anims; i++ ) {\n\t\telement.animate( { opacity: animateTo }, duration, options.easing );\n\t\tanimateTo = 1 - animateTo;\n\t}\n\n\telement.animate( { opacity: animateTo }, duration, options.easing );\n\n\telement.queue( done );\n\n\t$.effects.unshift( element, queuelen, anims + 1 );\n} );\n\n\n/*!\n * jQuery UI Effects Shake 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Shake Effect\n//>>group: Effects\n//>>description: Shakes an element horizontally or vertically n times.\n//>>docs: http://api.jqueryui.com/shake-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectShake = $.effects.define( \"shake\", function( options, done ) {\n\n\tvar i = 1,\n\t\telement = $( this ),\n\t\tdirection = options.direction || \"left\",\n\t\tdistance = options.distance || 20,\n\t\ttimes = options.times || 3,\n\t\tanims = times * 2 + 1,\n\t\tspeed = Math.round( options.duration / anims ),\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tpositiveMotion = ( direction === \"up\" || direction === \"left\" ),\n\t\tanimation = {},\n\t\tanimation1 = {},\n\t\tanimation2 = {},\n\n\t\tqueuelen = element.queue().length;\n\n\t$.effects.createPlaceholder( element );\n\n\t// Animation\n\tanimation[ ref ] = ( positiveMotion ? \"-=\" : \"+=\" ) + distance;\n\tanimation1[ ref ] = ( positiveMotion ? \"+=\" : \"-=\" ) + distance * 2;\n\tanimation2[ ref ] = ( positiveMotion ? \"-=\" : \"+=\" ) + distance * 2;\n\n\t// Animate\n\telement.animate( animation, speed, options.easing );\n\n\t// Shakes\n\tfor ( ; i < times; i++ ) {\n\t\telement\n\t\t\t.animate( animation1, speed, options.easing )\n\t\t\t.animate( animation2, speed, options.easing );\n\t}\n\n\telement\n\t\t.animate( animation1, speed, options.easing )\n\t\t.animate( animation, speed / 2, options.easing )\n\t\t.queue( done );\n\n\t$.effects.unshift( element, queuelen, anims + 1 );\n} );\n\n\n/*!\n * jQuery UI Effects Slide 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Slide Effect\n//>>group: Effects\n//>>description: Slides an element in and out of the viewport.\n//>>docs: http://api.jqueryui.com/slide-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectSlide = $.effects.define( \"slide\", \"show\", function( options, done ) {\n\tvar startClip, startRef,\n\t\telement = $( this ),\n\t\tmap = {\n\t\t\tup: [ \"bottom\", \"top\" ],\n\t\t\tdown: [ \"top\", \"bottom\" ],\n\t\t\tleft: [ \"right\", \"left\" ],\n\t\t\tright: [ \"left\", \"right\" ]\n\t\t},\n\t\tmode = options.mode,\n\t\tdirection = options.direction || \"left\",\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tpositiveMotion = ( direction === \"up\" || direction === \"left\" ),\n\t\tdistance = options.distance ||\n\t\t\telement[ ref === \"top\" ? \"outerHeight\" : \"outerWidth\" ]( true ),\n\t\tanimation = {};\n\n\t$.effects.createPlaceholder( element );\n\n\tstartClip = element.cssClip();\n\tstartRef = element.position()[ ref ];\n\n\t// Define hide animation\n\tanimation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef;\n\tanimation.clip = element.cssClip();\n\tanimation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ];\n\n\t// Reverse the animation if we're showing\n\tif ( mode === \"show\" ) {\n\t\telement.cssClip( animation.clip );\n\t\telement.css( ref, animation[ ref ] );\n\t\tanimation.clip = startClip;\n\t\tanimation[ ref ] = startRef;\n\t}\n\n\t// Actually animate\n\telement.animate( animation, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Transfer 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Transfer Effect\n//>>group: Effects\n//>>description: Displays a transfer effect from one element to another.\n//>>docs: http://api.jqueryui.com/transfer-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effect;\nif ( $.uiBackCompat !== false ) {\n\teffect = $.effects.define( \"transfer\", function( options, done ) {\n\t\t$( this ).transfer( options, done );\n\t} );\n}\nvar effectsEffectTransfer = effect;\n\n\n/*!\n * jQuery UI Focusable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: :focusable Selector\n//>>group: Core\n//>>description: Selects elements which can be focused.\n//>>docs: http://api.jqueryui.com/focusable-selector/\n\n\n\n// Selectors\n$.ui.focusable = function( element, hasTabindex ) {\n\tvar map, mapName, img, focusableIfVisible, fieldset,\n\t\tnodeName = element.nodeName.toLowerCase();\n\n\tif ( \"area\" === nodeName ) {\n\t\tmap = element.parentNode;\n\t\tmapName = map.name;\n\t\tif ( !element.href || !mapName || map.nodeName.toLowerCase() !== \"map\" ) {\n\t\t\treturn false;\n\t\t}\n\t\timg = $( \"img[usemap='#\" + mapName + \"']\" );\n\t\treturn img.length > 0 && img.is( \":visible\" );\n\t}\n\n\tif ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) {\n\t\tfocusableIfVisible = !element.disabled;\n\n\t\tif ( focusableIfVisible ) {\n\n\t\t\t// Form controls within a disabled fieldset are disabled.\n\t\t\t// However, controls within the fieldset's legend do not get disabled.\n\t\t\t// Since controls generally aren't placed inside legends, we skip\n\t\t\t// this portion of the check.\n\t\t\tfieldset = $( element ).closest( \"fieldset\" )[ 0 ];\n\t\t\tif ( fieldset ) {\n\t\t\t\tfocusableIfVisible = !fieldset.disabled;\n\t\t\t}\n\t\t}\n\t} else if ( \"a\" === nodeName ) {\n\t\tfocusableIfVisible = element.href || hasTabindex;\n\t} else {\n\t\tfocusableIfVisible = hasTabindex;\n\t}\n\n\treturn focusableIfVisible && $( element ).is( \":visible\" ) && visible( $( element ) );\n};\n\n// Support: IE 8 only\n// IE 8 doesn't resolve inherit to visible/hidden for computed values\nfunction visible( element ) {\n\tvar visibility = element.css( \"visibility\" );\n\twhile ( visibility === \"inherit\" ) {\n\t\telement = element.parent();\n\t\tvisibility = element.css( \"visibility\" );\n\t}\n\treturn visibility !== \"hidden\";\n}\n\n$.extend( $.expr[ \":\" ], {\n\tfocusable: function( element ) {\n\t\treturn $.ui.focusable( element, $.attr( element, \"tabindex\" ) != null );\n\t}\n} );\n\nvar focusable = $.ui.focusable;\n\n\n\n\n// Support: IE8 Only\n// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop\n// with a string, so we need to find the proper form.\nvar form = $.fn.form = function() {\n\treturn typeof this[ 0 ].form === \"string\" ? this.closest( \"form\" ) : $( this[ 0 ].form );\n};\n\n\n/*!\n * jQuery UI Form Reset Mixin 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Form Reset Mixin\n//>>group: Core\n//>>description: Refresh input widgets when their form is reset\n//>>docs: http://api.jqueryui.com/form-reset-mixin/\n\n\n\nvar formResetMixin = $.ui.formResetMixin = {\n\t_formResetHandler: function() {\n\t\tvar form = $( this );\n\n\t\t// Wait for the form reset to actually happen before refreshing\n\t\tsetTimeout( function() {\n\t\t\tvar instances = form.data( \"ui-form-reset-instances\" );\n\t\t\t$.each( instances, function() {\n\t\t\t\tthis.refresh();\n\t\t\t} );\n\t\t} );\n\t},\n\n\t_bindFormResetHandler: function() {\n\t\tthis.form = this.element.form();\n\t\tif ( !this.form.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar instances = this.form.data( \"ui-form-reset-instances\" ) || [];\n\t\tif ( !instances.length ) {\n\n\t\t\t// We don't use _on() here because we use a single event handler per form\n\t\t\tthis.form.on( \"reset.ui-form-reset\", this._formResetHandler );\n\t\t}\n\t\tinstances.push( this );\n\t\tthis.form.data( \"ui-form-reset-instances\", instances );\n\t},\n\n\t_unbindFormResetHandler: function() {\n\t\tif ( !this.form.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar instances = this.form.data( \"ui-form-reset-instances\" );\n\t\tinstances.splice( $.inArray( this, instances ), 1 );\n\t\tif ( instances.length ) {\n\t\t\tthis.form.data( \"ui-form-reset-instances\", instances );\n\t\t} else {\n\t\t\tthis.form\n\t\t\t\t.removeData( \"ui-form-reset-instances\" )\n\t\t\t\t.off( \"reset.ui-form-reset\" );\n\t\t}\n\t}\n};\n\n\n/*!\n * jQuery UI Support for jQuery core 1.7.x 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n */\n\n//>>label: jQuery 1.7 Support\n//>>group: Core\n//>>description: Support version 1.7.x of jQuery core\n\n\n\n// Support: jQuery 1.7 only\n// Not a great way to check versions, but since we only support 1.7+ and only\n// need to detect <1.8, this is a simple check that should suffice. Checking\n// for \"1.7.\" would be a bit safer, but the version string is 1.7, not 1.7.0\n// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting\n// 1.7 anymore). See #11197 for why we're not using feature detection.\nif ( $.fn.jquery.substring( 0, 3 ) === \"1.7\" ) {\n\n\t// Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight()\n\t// Unlike jQuery Core 1.8+, these only support numeric values to set the\n\t// dimensions in pixels\n\t$.each( [ \"Width\", \"Height\" ], function( i, name ) {\n\t\tvar side = name === \"Width\" ? [ \"Left\", \"Right\" ] : [ \"Top\", \"Bottom\" ],\n\t\t\ttype = name.toLowerCase(),\n\t\t\torig = {\n\t\t\t\tinnerWidth: $.fn.innerWidth,\n\t\t\t\tinnerHeight: $.fn.innerHeight,\n\t\t\t\touterWidth: $.fn.outerWidth,\n\t\t\t\touterHeight: $.fn.outerHeight\n\t\t\t};\n\n\t\tfunction reduce( elem, size, border, margin ) {\n\t\t\t$.each( side, function() {\n\t\t\t\tsize -= parseFloat( $.css( elem, \"padding\" + this ) ) || 0;\n\t\t\t\tif ( border ) {\n\t\t\t\t\tsize -= parseFloat( $.css( elem, \"border\" + this + \"Width\" ) ) || 0;\n\t\t\t\t}\n\t\t\t\tif ( margin ) {\n\t\t\t\t\tsize -= parseFloat( $.css( elem, \"margin\" + this ) ) || 0;\n\t\t\t\t}\n\t\t\t} );\n\t\t\treturn size;\n\t\t}\n\n\t\t$.fn[ \"inner\" + name ] = function( size ) {\n\t\t\tif ( size === undefined ) {\n\t\t\t\treturn orig[ \"inner\" + name ].call( this );\n\t\t\t}\n\n\t\t\treturn this.each( function() {\n\t\t\t\t$( this ).css( type, reduce( this, size ) + \"px\" );\n\t\t\t} );\n\t\t};\n\n\t\t$.fn[ \"outer\" + name ] = function( size, margin ) {\n\t\t\tif ( typeof size !== \"number\" ) {\n\t\t\t\treturn orig[ \"outer\" + name ].call( this, size );\n\t\t\t}\n\n\t\t\treturn this.each( function() {\n\t\t\t\t$( this ).css( type, reduce( this, size, true, margin ) + \"px\" );\n\t\t\t} );\n\t\t};\n\t} );\n\n\t$.fn.addBack = function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter( selector )\n\t\t);\n\t};\n}\n\n;\n/*!\n * jQuery UI Keycode 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Keycode\n//>>group: Core\n//>>description: Provide keycodes as keynames\n//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/\n\n\nvar keycode = $.ui.keyCode = {\n\tBACKSPACE: 8,\n\tCOMMA: 188,\n\tDELETE: 46,\n\tDOWN: 40,\n\tEND: 35,\n\tENTER: 13,\n\tESCAPE: 27,\n\tHOME: 36,\n\tLEFT: 37,\n\tPAGE_DOWN: 34,\n\tPAGE_UP: 33,\n\tPERIOD: 190,\n\tRIGHT: 39,\n\tSPACE: 32,\n\tTAB: 9,\n\tUP: 38\n};\n\n\n\n\n// Internal use only\nvar escapeSelector = $.ui.escapeSelector = ( function() {\n\tvar selectorEscape = /([!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~])/g;\n\treturn function( selector ) {\n\t\treturn selector.replace( selectorEscape, \"\\\\$1\" );\n\t};\n} )();\n\n\n/*!\n * jQuery UI Labels 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: labels\n//>>group: Core\n//>>description: Find all the labels associated with a given input\n//>>docs: http://api.jqueryui.com/labels/\n\n\n\nvar labels = $.fn.labels = function() {\n\tvar ancestor, selector, id, labels, ancestors;\n\n\t// Check control.labels first\n\tif ( this[ 0 ].labels && this[ 0 ].labels.length ) {\n\t\treturn this.pushStack( this[ 0 ].labels );\n\t}\n\n\t// Support: IE <= 11, FF <= 37, Android <= 2.3 only\n\t// Above browsers do not support control.labels. Everything below is to support them\n\t// as well as document fragments. control.labels does not work on document fragments\n\tlabels = this.eq( 0 ).parents( \"label\" );\n\n\t// Look for the label based on the id\n\tid = this.attr( \"id\" );\n\tif ( id ) {\n\n\t\t// We don't search against the document in case the element\n\t\t// is disconnected from the DOM\n\t\tancestor = this.eq( 0 ).parents().last();\n\n\t\t// Get a full set of top level ancestors\n\t\tancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() );\n\n\t\t// Create a selector for the label based on the id\n\t\tselector = \"label[for='\" + $.ui.escapeSelector( id ) + \"']\";\n\n\t\tlabels = labels.add( ancestors.find( selector ).addBack( selector ) );\n\n\t}\n\n\t// Return whatever we have found for labels\n\treturn this.pushStack( labels );\n};\n\n\n/*!\n * jQuery UI Scroll Parent 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: scrollParent\n//>>group: Core\n//>>description: Get the closest ancestor element that is scrollable.\n//>>docs: http://api.jqueryui.com/scrollParent/\n\n\n\nvar scrollParent = $.fn.scrollParent = function( includeHidden ) {\n\tvar position = this.css( \"position\" ),\n\t\texcludeStaticParent = position === \"absolute\",\n\t\toverflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,\n\t\tscrollParent = this.parents().filter( function() {\n\t\t\tvar parent = $( this );\n\t\t\tif ( excludeStaticParent && parent.css( \"position\" ) === \"static\" ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn overflowRegex.test( parent.css( \"overflow\" ) + parent.css( \"overflow-y\" ) +\n\t\t\t\tparent.css( \"overflow-x\" ) );\n\t\t} ).eq( 0 );\n\n\treturn position === \"fixed\" || !scrollParent.length ?\n\t\t$( this[ 0 ].ownerDocument || document ) :\n\t\tscrollParent;\n};\n\n\n/*!\n * jQuery UI Tabbable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: :tabbable Selector\n//>>group: Core\n//>>description: Selects elements which can be tabbed to.\n//>>docs: http://api.jqueryui.com/tabbable-selector/\n\n\n\nvar tabbable = $.extend( $.expr[ \":\" ], {\n\ttabbable: function( element ) {\n\t\tvar tabIndex = $.attr( element, \"tabindex\" ),\n\t\t\thasTabindex = tabIndex != null;\n\t\treturn ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex );\n\t}\n} );\n\n\n/*!\n * jQuery UI Unique ID 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: uniqueId\n//>>group: Core\n//>>description: Functions to generate and remove uniqueId's\n//>>docs: http://api.jqueryui.com/uniqueId/\n\n\n\nvar uniqueId = $.fn.extend( {\n\tuniqueId: ( function() {\n\t\tvar uuid = 0;\n\n\t\treturn function() {\n\t\t\treturn this.each( function() {\n\t\t\t\tif ( !this.id ) {\n\t\t\t\t\tthis.id = \"ui-id-\" + ( ++uuid );\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\t} )(),\n\n\tremoveUniqueId: function() {\n\t\treturn this.each( function() {\n\t\t\tif ( /^ui-id-\\d+$/.test( this.id ) ) {\n\t\t\t\t$( this ).removeAttr( \"id\" );\n\t\t\t}\n\t\t} );\n\t}\n} );\n\n\n/*!\n * jQuery UI Accordion 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Accordion\n//>>group: Widgets\n// jscs:disable maximumLineLength\n//>>description: Displays collapsible content panels for presenting information in a limited amount of space.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/accordion/\n//>>demos: http://jqueryui.com/accordion/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/accordion.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsAccordion = $.widget( \"ui.accordion\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tactive: 0,\n\t\tanimate: {},\n\t\tclasses: {\n\t\t\t\"ui-accordion-header\": \"ui-corner-top\",\n\t\t\t\"ui-accordion-header-collapsed\": \"ui-corner-all\",\n\t\t\t\"ui-accordion-content\": \"ui-corner-bottom\"\n\t\t},\n\t\tcollapsible: false,\n\t\tevent: \"click\",\n\t\theader: \"> li > :first-child, > :not(li):even\",\n\t\theightStyle: \"auto\",\n\t\ticons: {\n\t\t\tactiveHeader: \"ui-icon-triangle-1-s\",\n\t\t\theader: \"ui-icon-triangle-1-e\"\n\t\t},\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tbeforeActivate: null\n\t},\n\n\thideProps: {\n\t\tborderTopWidth: \"hide\",\n\t\tborderBottomWidth: \"hide\",\n\t\tpaddingTop: \"hide\",\n\t\tpaddingBottom: \"hide\",\n\t\theight: \"hide\"\n\t},\n\n\tshowProps: {\n\t\tborderTopWidth: \"show\",\n\t\tborderBottomWidth: \"show\",\n\t\tpaddingTop: \"show\",\n\t\tpaddingBottom: \"show\",\n\t\theight: \"show\"\n\t},\n\n\t_create: function() {\n\t\tvar options = this.options;\n\n\t\tthis.prevShow = this.prevHide = $();\n\t\tthis._addClass( \"ui-accordion\", \"ui-widget ui-helper-reset\" );\n\t\tthis.element.attr( \"role\", \"tablist\" );\n\n\t\t// Don't allow collapsible: false and active: false / null\n\t\tif ( !options.collapsible && ( options.active === false || options.active == null ) ) {\n\t\t\toptions.active = 0;\n\t\t}\n\n\t\tthis._processPanels();\n\n\t\t// handle negative values\n\t\tif ( options.active < 0 ) {\n\t\t\toptions.active += this.headers.length;\n\t\t}\n\t\tthis._refresh();\n\t},\n\n\t_getCreateEventData: function() {\n\t\treturn {\n\t\t\theader: this.active,\n\t\t\tpanel: !this.active.length ? $() : this.active.next()\n\t\t};\n\t},\n\n\t_createIcons: function() {\n\t\tvar icon, children,\n\t\t\ticons = this.options.icons;\n\n\t\tif ( icons ) {\n\t\t\ticon = $( \"<span>\" );\n\t\t\tthis._addClass( icon, \"ui-accordion-header-icon\", \"ui-icon \" + icons.header );\n\t\t\ticon.prependTo( this.headers );\n\t\t\tchildren = this.active.children( \".ui-accordion-header-icon\" );\n\t\t\tthis._removeClass( children, icons.header )\n\t\t\t\t._addClass( children, null, icons.activeHeader )\n\t\t\t\t._addClass( this.headers, \"ui-accordion-icons\" );\n\t\t}\n\t},\n\n\t_destroyIcons: function() {\n\t\tthis._removeClass( this.headers, \"ui-accordion-icons\" );\n\t\tthis.headers.children( \".ui-accordion-header-icon\" ).remove();\n\t},\n\n\t_destroy: function() {\n\t\tvar contents;\n\n\t\t// Clean up main element\n\t\tthis.element.removeAttr( \"role\" );\n\n\t\t// Clean up headers\n\t\tthis.headers\n\t\t\t.removeAttr( \"role aria-expanded aria-selected aria-controls tabIndex\" )\n\t\t\t.removeUniqueId();\n\n\t\tthis._destroyIcons();\n\n\t\t// Clean up content panels\n\t\tcontents = this.headers.next()\n\t\t\t.css( \"display\", \"\" )\n\t\t\t.removeAttr( \"role aria-hidden aria-labelledby\" )\n\t\t\t.removeUniqueId();\n\n\t\tif ( this.options.heightStyle !== \"content\" ) {\n\t\t\tcontents.css( \"height\", \"\" );\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"active\" ) {\n\n\t\t\t// _activate() will handle invalid values and update this.options\n\t\t\tthis._activate( value );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( key === \"event\" ) {\n\t\t\tif ( this.options.event ) {\n\t\t\t\tthis._off( this.headers, this.options.event );\n\t\t\t}\n\t\t\tthis._setupEvents( value );\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\t// Setting collapsible: false while collapsed; open first panel\n\t\tif ( key === \"collapsible\" && !value && this.options.active === false ) {\n\t\t\tthis._activate( 0 );\n\t\t}\n\n\t\tif ( key === \"icons\" ) {\n\t\t\tthis._destroyIcons();\n\t\t\tif ( value ) {\n\t\t\t\tthis._createIcons();\n\t\t\t}\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.element.attr( \"aria-disabled\", value );\n\n\t\t// Support: IE8 Only\n\t\t// #5332 / #6059 - opacity doesn't cascade to positioned elements in IE\n\t\t// so we need to add the disabled class to the headers and panels\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t\tthis._toggleClass( this.headers.add( this.headers.next() ), null, \"ui-state-disabled\",\n\t\t\t!!value );\n\t},\n\n\t_keydown: function( event ) {\n\t\tif ( event.altKey || event.ctrlKey ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar keyCode = $.ui.keyCode,\n\t\t\tlength = this.headers.length,\n\t\t\tcurrentIndex = this.headers.index( event.target ),\n\t\t\ttoFocus = false;\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase keyCode.RIGHT:\n\t\tcase keyCode.DOWN:\n\t\t\ttoFocus = this.headers[ ( currentIndex + 1 ) % length ];\n\t\t\tbreak;\n\t\tcase keyCode.LEFT:\n\t\tcase keyCode.UP:\n\t\t\ttoFocus = this.headers[ ( currentIndex - 1 + length ) % length ];\n\t\t\tbreak;\n\t\tcase keyCode.SPACE:\n\t\tcase keyCode.ENTER:\n\t\t\tthis._eventHandler( event );\n\t\t\tbreak;\n\t\tcase keyCode.HOME:\n\t\t\ttoFocus = this.headers[ 0 ];\n\t\t\tbreak;\n\t\tcase keyCode.END:\n\t\t\ttoFocus = this.headers[ length - 1 ];\n\t\t\tbreak;\n\t\t}\n\n\t\tif ( toFocus ) {\n\t\t\t$( event.target ).attr( \"tabIndex\", -1 );\n\t\t\t$( toFocus ).attr( \"tabIndex\", 0 );\n\t\t\t$( toFocus ).trigger( \"focus\" );\n\t\t\tevent.preventDefault();\n\t\t}\n\t},\n\n\t_panelKeyDown: function( event ) {\n\t\tif ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {\n\t\t\t$( event.currentTarget ).prev().trigger( \"focus\" );\n\t\t}\n\t},\n\n\trefresh: function() {\n\t\tvar options = this.options;\n\t\tthis._processPanels();\n\n\t\t// Was collapsed or no panel\n\t\tif ( ( options.active === false && options.collapsible === true ) ||\n\t\t\t\t!this.headers.length ) {\n\t\t\toptions.active = false;\n\t\t\tthis.active = $();\n\n\t\t// active false only when collapsible is true\n\t\t} else if ( options.active === false ) {\n\t\t\tthis._activate( 0 );\n\n\t\t// was active, but active panel is gone\n\t\t} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {\n\n\t\t\t// all remaining panel are disabled\n\t\t\tif ( this.headers.length === this.headers.find( \".ui-state-disabled\" ).length ) {\n\t\t\t\toptions.active = false;\n\t\t\t\tthis.active = $();\n\n\t\t\t// activate previous panel\n\t\t\t} else {\n\t\t\t\tthis._activate( Math.max( 0, options.active - 1 ) );\n\t\t\t}\n\n\t\t// was active, active panel still exists\n\t\t} else {\n\n\t\t\t// make sure active index is correct\n\t\t\toptions.active = this.headers.index( this.active );\n\t\t}\n\n\t\tthis._destroyIcons();\n\n\t\tthis._refresh();\n\t},\n\n\t_processPanels: function() {\n\t\tvar prevHeaders = this.headers,\n\t\t\tprevPanels = this.panels;\n\n\t\tthis.headers = this.element.find( this.options.header );\n\t\tthis._addClass( this.headers, \"ui-accordion-header ui-accordion-header-collapsed\",\n\t\t\t\"ui-state-default\" );\n\n\t\tthis.panels = this.headers.next().filter( \":not(.ui-accordion-content-active)\" ).hide();\n\t\tthis._addClass( this.panels, \"ui-accordion-content\", \"ui-helper-reset ui-widget-content\" );\n\n\t\t// Avoid memory leaks (#10056)\n\t\tif ( prevPanels ) {\n\t\t\tthis._off( prevHeaders.not( this.headers ) );\n\t\t\tthis._off( prevPanels.not( this.panels ) );\n\t\t}\n\t},\n\n\t_refresh: function() {\n\t\tvar maxHeight,\n\t\t\toptions = this.options,\n\t\t\theightStyle = options.heightStyle,\n\t\t\tparent = this.element.parent();\n\n\t\tthis.active = this._findActive( options.active );\n\t\tthis._addClass( this.active, \"ui-accordion-header-active\", \"ui-state-active\" )\n\t\t\t._removeClass( this.active, \"ui-accordion-header-collapsed\" );\n\t\tthis._addClass( this.active.next(), \"ui-accordion-content-active\" );\n\t\tthis.active.next().show();\n\n\t\tthis.headers\n\t\t\t.attr( \"role\", \"tab\" )\n\t\t\t.each( function() {\n\t\t\t\tvar header = $( this ),\n\t\t\t\t\theaderId = header.uniqueId().attr( \"id\" ),\n\t\t\t\t\tpanel = header.next(),\n\t\t\t\t\tpanelId = panel.uniqueId().attr( \"id\" );\n\t\t\t\theader.attr( \"aria-controls\", panelId );\n\t\t\t\tpanel.attr( \"aria-labelledby\", headerId );\n\t\t\t} )\n\t\t\t.next()\n\t\t\t\t.attr( \"role\", \"tabpanel\" );\n\n\t\tthis.headers\n\t\t\t.not( this.active )\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-selected\": \"false\",\n\t\t\t\t\t\"aria-expanded\": \"false\",\n\t\t\t\t\ttabIndex: -1\n\t\t\t\t} )\n\t\t\t\t.next()\n\t\t\t\t\t.attr( {\n\t\t\t\t\t\t\"aria-hidden\": \"true\"\n\t\t\t\t\t} )\n\t\t\t\t\t.hide();\n\n\t\t// Make sure at least one header is in the tab order\n\t\tif ( !this.active.length ) {\n\t\t\tthis.headers.eq( 0 ).attr( \"tabIndex\", 0 );\n\t\t} else {\n\t\t\tthis.active.attr( {\n\t\t\t\t\"aria-selected\": \"true\",\n\t\t\t\t\"aria-expanded\": \"true\",\n\t\t\t\ttabIndex: 0\n\t\t\t} )\n\t\t\t\t.next()\n\t\t\t\t\t.attr( {\n\t\t\t\t\t\t\"aria-hidden\": \"false\"\n\t\t\t\t\t} );\n\t\t}\n\n\t\tthis._createIcons();\n\n\t\tthis._setupEvents( options.event );\n\n\t\tif ( heightStyle === \"fill\" ) {\n\t\t\tmaxHeight = parent.height();\n\t\t\tthis.element.siblings( \":visible\" ).each( function() {\n\t\t\t\tvar elem = $( this ),\n\t\t\t\t\tposition = elem.css( \"position\" );\n\n\t\t\t\tif ( position === \"absolute\" || position === \"fixed\" ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmaxHeight -= elem.outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.headers.each( function() {\n\t\t\t\tmaxHeight -= $( this ).outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.headers.next()\n\t\t\t\t.each( function() {\n\t\t\t\t\t$( this ).height( Math.max( 0, maxHeight -\n\t\t\t\t\t\t$( this ).innerHeight() + $( this ).height() ) );\n\t\t\t\t} )\n\t\t\t\t.css( \"overflow\", \"auto\" );\n\t\t} else if ( heightStyle === \"auto\" ) {\n\t\t\tmaxHeight = 0;\n\t\t\tthis.headers.next()\n\t\t\t\t.each( function() {\n\t\t\t\t\tvar isVisible = $( this ).is( \":visible\" );\n\t\t\t\t\tif ( !isVisible ) {\n\t\t\t\t\t\t$( this ).show();\n\t\t\t\t\t}\n\t\t\t\t\tmaxHeight = Math.max( maxHeight, $( this ).css( \"height\", \"\" ).height() );\n\t\t\t\t\tif ( !isVisible ) {\n\t\t\t\t\t\t$( this ).hide();\n\t\t\t\t\t}\n\t\t\t\t} )\n\t\t\t\t.height( maxHeight );\n\t\t}\n\t},\n\n\t_activate: function( index ) {\n\t\tvar active = this._findActive( index )[ 0 ];\n\n\t\t// Trying to activate the already active panel\n\t\tif ( active === this.active[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Trying to collapse, simulate a click on the currently active header\n\t\tactive = active || this.active[ 0 ];\n\n\t\tthis._eventHandler( {\n\t\t\ttarget: active,\n\t\t\tcurrentTarget: active,\n\t\t\tpreventDefault: $.noop\n\t\t} );\n\t},\n\n\t_findActive: function( selector ) {\n\t\treturn typeof selector === \"number\" ? this.headers.eq( selector ) : $();\n\t},\n\n\t_setupEvents: function( event ) {\n\t\tvar events = {\n\t\t\tkeydown: \"_keydown\"\n\t\t};\n\t\tif ( event ) {\n\t\t\t$.each( event.split( \" \" ), function( index, eventName ) {\n\t\t\t\tevents[ eventName ] = \"_eventHandler\";\n\t\t\t} );\n\t\t}\n\n\t\tthis._off( this.headers.add( this.headers.next() ) );\n\t\tthis._on( this.headers, events );\n\t\tthis._on( this.headers.next(), { keydown: \"_panelKeyDown\" } );\n\t\tthis._hoverable( this.headers );\n\t\tthis._focusable( this.headers );\n\t},\n\n\t_eventHandler: function( event ) {\n\t\tvar activeChildren, clickedChildren,\n\t\t\toptions = this.options,\n\t\t\tactive = this.active,\n\t\t\tclicked = $( event.currentTarget ),\n\t\t\tclickedIsActive = clicked[ 0 ] === active[ 0 ],\n\t\t\tcollapsing = clickedIsActive && options.collapsible,\n\t\t\ttoShow = collapsing ? $() : clicked.next(),\n\t\t\ttoHide = active.next(),\n\t\t\teventData = {\n\t\t\t\toldHeader: active,\n\t\t\t\toldPanel: toHide,\n\t\t\t\tnewHeader: collapsing ? $() : clicked,\n\t\t\t\tnewPanel: toShow\n\t\t\t};\n\n\t\tevent.preventDefault();\n\n\t\tif (\n\n\t\t\t\t// click on active header, but not collapsible\n\t\t\t\t( clickedIsActive && !options.collapsible ) ||\n\n\t\t\t\t// allow canceling activation\n\t\t\t\t( this._trigger( \"beforeActivate\", event, eventData ) === false ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\toptions.active = collapsing ? false : this.headers.index( clicked );\n\n\t\t// When the call to ._toggle() comes after the class changes\n\t\t// it causes a very odd bug in IE 8 (see #6720)\n\t\tthis.active = clickedIsActive ? $() : clicked;\n\t\tthis._toggle( eventData );\n\n\t\t// Switch classes\n\t\t// corner classes on the previously active header stay after the animation\n\t\tthis._removeClass( active, \"ui-accordion-header-active\", \"ui-state-active\" );\n\t\tif ( options.icons ) {\n\t\t\tactiveChildren = active.children( \".ui-accordion-header-icon\" );\n\t\t\tthis._removeClass( activeChildren, null, options.icons.activeHeader )\n\t\t\t\t._addClass( activeChildren, null, options.icons.header );\n\t\t}\n\n\t\tif ( !clickedIsActive ) {\n\t\t\tthis._removeClass( clicked, \"ui-accordion-header-collapsed\" )\n\t\t\t\t._addClass( clicked, \"ui-accordion-header-active\", \"ui-state-active\" );\n\t\t\tif ( options.icons ) {\n\t\t\t\tclickedChildren = clicked.children( \".ui-accordion-header-icon\" );\n\t\t\t\tthis._removeClass( clickedChildren, null, options.icons.header )\n\t\t\t\t\t._addClass( clickedChildren, null, options.icons.activeHeader );\n\t\t\t}\n\n\t\t\tthis._addClass( clicked.next(), \"ui-accordion-content-active\" );\n\t\t}\n\t},\n\n\t_toggle: function( data ) {\n\t\tvar toShow = data.newPanel,\n\t\t\ttoHide = this.prevShow.length ? this.prevShow : data.oldPanel;\n\n\t\t// Handle activating a panel during the animation for another activation\n\t\tthis.prevShow.add( this.prevHide ).stop( true, true );\n\t\tthis.prevShow = toShow;\n\t\tthis.prevHide = toHide;\n\n\t\tif ( this.options.animate ) {\n\t\t\tthis._animate( toShow, toHide, data );\n\t\t} else {\n\t\t\ttoHide.hide();\n\t\t\ttoShow.show();\n\t\t\tthis._toggleComplete( data );\n\t\t}\n\n\t\ttoHide.attr( {\n\t\t\t\"aria-hidden\": \"true\"\n\t\t} );\n\t\ttoHide.prev().attr( {\n\t\t\t\"aria-selected\": \"false\",\n\t\t\t\"aria-expanded\": \"false\"\n\t\t} );\n\n\t\t// if we're switching panels, remove the old header from the tab order\n\t\t// if we're opening from collapsed state, remove the previous header from the tab order\n\t\t// if we're collapsing, then keep the collapsing header in the tab order\n\t\tif ( toShow.length && toHide.length ) {\n\t\t\ttoHide.prev().attr( {\n\t\t\t\t\"tabIndex\": -1,\n\t\t\t\t\"aria-expanded\": \"false\"\n\t\t\t} );\n\t\t} else if ( toShow.length ) {\n\t\t\tthis.headers.filter( function() {\n\t\t\t\treturn parseInt( $( this ).attr( \"tabIndex\" ), 10 ) === 0;\n\t\t\t} )\n\t\t\t\t.attr( \"tabIndex\", -1 );\n\t\t}\n\n\t\ttoShow\n\t\t\t.attr( \"aria-hidden\", \"false\" )\n\t\t\t.prev()\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-selected\": \"true\",\n\t\t\t\t\t\"aria-expanded\": \"true\",\n\t\t\t\t\ttabIndex: 0\n\t\t\t\t} );\n\t},\n\n\t_animate: function( toShow, toHide, data ) {\n\t\tvar total, easing, duration,\n\t\t\tthat = this,\n\t\t\tadjust = 0,\n\t\t\tboxSizing = toShow.css( \"box-sizing\" ),\n\t\t\tdown = toShow.length &&\n\t\t\t\t( !toHide.length || ( toShow.index() < toHide.index() ) ),\n\t\t\tanimate = this.options.animate || {},\n\t\t\toptions = down && animate.down || animate,\n\t\t\tcomplete = function() {\n\t\t\t\tthat._toggleComplete( data );\n\t\t\t};\n\n\t\tif ( typeof options === \"number\" ) {\n\t\t\tduration = options;\n\t\t}\n\t\tif ( typeof options === \"string\" ) {\n\t\t\teasing = options;\n\t\t}\n\n\t\t// fall back from options to animation in case of partial down settings\n\t\teasing = easing || options.easing || animate.easing;\n\t\tduration = duration || options.duration || animate.duration;\n\n\t\tif ( !toHide.length ) {\n\t\t\treturn toShow.animate( this.showProps, duration, easing, complete );\n\t\t}\n\t\tif ( !toShow.length ) {\n\t\t\treturn toHide.animate( this.hideProps, duration, easing, complete );\n\t\t}\n\n\t\ttotal = toShow.show().outerHeight();\n\t\ttoHide.animate( this.hideProps, {\n\t\t\tduration: duration,\n\t\t\teasing: easing,\n\t\t\tstep: function( now, fx ) {\n\t\t\t\tfx.now = Math.round( now );\n\t\t\t}\n\t\t} );\n\t\ttoShow\n\t\t\t.hide()\n\t\t\t.animate( this.showProps, {\n\t\t\t\tduration: duration,\n\t\t\t\teasing: easing,\n\t\t\t\tcomplete: complete,\n\t\t\t\tstep: function( now, fx ) {\n\t\t\t\t\tfx.now = Math.round( now );\n\t\t\t\t\tif ( fx.prop !== \"height\" ) {\n\t\t\t\t\t\tif ( boxSizing === \"content-box\" ) {\n\t\t\t\t\t\t\tadjust += fx.now;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ( that.options.heightStyle !== \"content\" ) {\n\t\t\t\t\t\tfx.now = Math.round( total - toHide.outerHeight() - adjust );\n\t\t\t\t\t\tadjust = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t},\n\n\t_toggleComplete: function( data ) {\n\t\tvar toHide = data.oldPanel,\n\t\t\tprev = toHide.prev();\n\n\t\tthis._removeClass( toHide, \"ui-accordion-content-active\" );\n\t\tthis._removeClass( prev, \"ui-accordion-header-active\" )\n\t\t\t._addClass( prev, \"ui-accordion-header-collapsed\" );\n\n\t\t// Work around for rendering bug in IE (#5421)\n\t\tif ( toHide.length ) {\n\t\t\ttoHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;\n\t\t}\n\t\tthis._trigger( \"activate\", null, data );\n\t}\n} );\n\n\n\nvar safeActiveElement = $.ui.safeActiveElement = function( document ) {\n\tvar activeElement;\n\n\t// Support: IE 9 only\n\t// IE9 throws an \"Unspecified error\" accessing document.activeElement from an <iframe>\n\ttry {\n\t\tactiveElement = document.activeElement;\n\t} catch ( error ) {\n\t\tactiveElement = document.body;\n\t}\n\n\t// Support: IE 9 - 11 only\n\t// IE may return null instead of an element\n\t// Interestingly, this only seems to occur when NOT in an iframe\n\tif ( !activeElement ) {\n\t\tactiveElement = document.body;\n\t}\n\n\t// Support: IE 11 only\n\t// IE11 returns a seemingly empty object in some cases when accessing\n\t// document.activeElement from an <iframe>\n\tif ( !activeElement.nodeName ) {\n\t\tactiveElement = document.body;\n\t}\n\n\treturn activeElement;\n};\n\n\n/*!\n * jQuery UI Menu 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Menu\n//>>group: Widgets\n//>>description: Creates nestable menus.\n//>>docs: http://api.jqueryui.com/menu/\n//>>demos: http://jqueryui.com/menu/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/menu.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsMenu = $.widget( \"ui.menu\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<ul>\",\n\tdelay: 300,\n\toptions: {\n\t\ticons: {\n\t\t\tsubmenu: \"ui-icon-caret-1-e\"\n\t\t},\n\t\titems: \"> *\",\n\t\tmenus: \"ul\",\n\t\tposition: {\n\t\t\tmy: \"left top\",\n\t\t\tat: \"right top\"\n\t\t},\n\t\trole: \"menu\",\n\n\t\t// Callbacks\n\t\tblur: null,\n\t\tfocus: null,\n\t\tselect: null\n\t},\n\n\t_create: function() {\n\t\tthis.activeMenu = this.element;\n\n\t\t// Flag used to prevent firing of the click handler\n\t\t// as the event bubbles up through nested menus\n\t\tthis.mouseHandled = false;\n\t\tthis.element\n\t\t\t.uniqueId()\n\t\t\t.attr( {\n\t\t\t\trole: this.options.role,\n\t\t\t\ttabIndex: 0\n\t\t\t} );\n\n\t\tthis._addClass( \"ui-menu\", \"ui-widget ui-widget-content\" );\n\t\tthis._on( {\n\n\t\t\t// Prevent focus from sticking to links inside menu after clicking\n\t\t\t// them (focus should always stay on UL during navigation).\n\t\t\t\"mousedown .ui-menu-item\": function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t\"click .ui-menu-item\": function( event ) {\n\t\t\t\tvar target = $( event.target );\n\t\t\t\tvar active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );\n\t\t\t\tif ( !this.mouseHandled && target.not( \".ui-state-disabled\" ).length ) {\n\t\t\t\t\tthis.select( event );\n\n\t\t\t\t\t// Only set the mouseHandled flag if the event will bubble, see #9469.\n\t\t\t\t\tif ( !event.isPropagationStopped() ) {\n\t\t\t\t\t\tthis.mouseHandled = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Open submenu on click\n\t\t\t\t\tif ( target.has( \".ui-menu\" ).length ) {\n\t\t\t\t\t\tthis.expand( event );\n\t\t\t\t\t} else if ( !this.element.is( \":focus\" ) &&\n\t\t\t\t\t\t\tactive.closest( \".ui-menu\" ).length ) {\n\n\t\t\t\t\t\t// Redirect focus to the menu\n\t\t\t\t\t\tthis.element.trigger( \"focus\", [ true ] );\n\n\t\t\t\t\t\t// If the active item is on the top level, let it stay active.\n\t\t\t\t\t\t// Otherwise, blur the active item since it is no longer visible.\n\t\t\t\t\t\tif ( this.active && this.active.parents( \".ui-menu\" ).length === 1 ) {\n\t\t\t\t\t\t\tclearTimeout( this.timer );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"mouseenter .ui-menu-item\": function( event ) {\n\n\t\t\t\t// Ignore mouse events while typeahead is active, see #10458.\n\t\t\t\t// Prevents focusing the wrong item when typeahead causes a scroll while the mouse\n\t\t\t\t// is over an item in the menu\n\t\t\t\tif ( this.previousFilter ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar actualTarget = $( event.target ).closest( \".ui-menu-item\" ),\n\t\t\t\t\ttarget = $( event.currentTarget );\n\n\t\t\t\t// Ignore bubbled events on parent items, see #11641\n\t\t\t\tif ( actualTarget[ 0 ] !== target[ 0 ] ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Remove ui-state-active class from siblings of the newly focused menu item\n\t\t\t\t// to avoid a jump caused by adjacent elements both having a class with a border\n\t\t\t\tthis._removeClass( target.siblings().children( \".ui-state-active\" ),\n\t\t\t\t\tnull, \"ui-state-active\" );\n\t\t\t\tthis.focus( event, target );\n\t\t\t},\n\t\t\tmouseleave: \"collapseAll\",\n\t\t\t\"mouseleave .ui-menu\": \"collapseAll\",\n\t\t\tfocus: function( event, keepActiveItem ) {\n\n\t\t\t\t// If there's already an active item, keep it active\n\t\t\t\t// If not, activate the first item\n\t\t\t\tvar item = this.active || this.element.find( this.options.items ).eq( 0 );\n\n\t\t\t\tif ( !keepActiveItem ) {\n\t\t\t\t\tthis.focus( event, item );\n\t\t\t\t}\n\t\t\t},\n\t\t\tblur: function( event ) {\n\t\t\t\tthis._delay( function() {\n\t\t\t\t\tvar notContained = !$.contains(\n\t\t\t\t\t\tthis.element[ 0 ],\n\t\t\t\t\t\t$.ui.safeActiveElement( this.document[ 0 ] )\n\t\t\t\t\t);\n\t\t\t\t\tif ( notContained ) {\n\t\t\t\t\t\tthis.collapseAll( event );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t},\n\t\t\tkeydown: \"_keydown\"\n\t\t} );\n\n\t\tthis.refresh();\n\n\t\t// Clicks outside of a menu collapse any open menus\n\t\tthis._on( this.document, {\n\t\t\tclick: function( event ) {\n\t\t\t\tif ( this._closeOnDocumentClick( event ) ) {\n\t\t\t\t\tthis.collapseAll( event );\n\t\t\t\t}\n\n\t\t\t\t// Reset the mouseHandled flag\n\t\t\t\tthis.mouseHandled = false;\n\t\t\t}\n\t\t} );\n\t},\n\n\t_destroy: function() {\n\t\tvar items = this.element.find( \".ui-menu-item\" )\n\t\t\t\t.removeAttr( \"role aria-disabled\" ),\n\t\t\tsubmenus = items.children( \".ui-menu-item-wrapper\" )\n\t\t\t\t.removeUniqueId()\n\t\t\t\t.removeAttr( \"tabIndex role aria-haspopup\" );\n\n\t\t// Destroy (sub)menus\n\t\tthis.element\n\t\t\t.removeAttr( \"aria-activedescendant\" )\n\t\t\t.find( \".ui-menu\" ).addBack()\n\t\t\t\t.removeAttr( \"role aria-labelledby aria-expanded aria-hidden aria-disabled \" +\n\t\t\t\t\t\"tabIndex\" )\n\t\t\t\t.removeUniqueId()\n\t\t\t\t.show();\n\n\t\tsubmenus.children().each( function() {\n\t\t\tvar elem = $( this );\n\t\t\tif ( elem.data( \"ui-menu-submenu-caret\" ) ) {\n\t\t\t\telem.remove();\n\t\t\t}\n\t\t} );\n\t},\n\n\t_keydown: function( event ) {\n\t\tvar match, prev, character, skip,\n\t\t\tpreventDefault = true;\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\tthis.previousPage( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\tthis.nextPage( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.HOME:\n\t\t\tthis._move( \"first\", \"first\", event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.END:\n\t\t\tthis._move( \"last\", \"last\", event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.UP:\n\t\t\tthis.previous( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.DOWN:\n\t\t\tthis.next( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.LEFT:\n\t\t\tthis.collapse( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.RIGHT:\n\t\t\tif ( this.active && !this.active.is( \".ui-state-disabled\" ) ) {\n\t\t\t\tthis.expand( event );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.ENTER:\n\t\tcase $.ui.keyCode.SPACE:\n\t\t\tthis._activate( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.ESCAPE:\n\t\t\tthis.collapse( event );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpreventDefault = false;\n\t\t\tprev = this.previousFilter || \"\";\n\t\t\tskip = false;\n\n\t\t\t// Support number pad values\n\t\t\tcharacter = event.keyCode >= 96 && event.keyCode <= 105 ?\n\t\t\t\t( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );\n\n\t\t\tclearTimeout( this.filterTimer );\n\n\t\t\tif ( character === prev ) {\n\t\t\t\tskip = true;\n\t\t\t} else {\n\t\t\t\tcharacter = prev + character;\n\t\t\t}\n\n\t\t\tmatch = this._filterMenuItems( character );\n\t\t\tmatch = skip && match.index( this.active.next() ) !== -1 ?\n\t\t\t\tthis.active.nextAll( \".ui-menu-item\" ) :\n\t\t\t\tmatch;\n\n\t\t\t// If no matches on the current filter, reset to the last character pressed\n\t\t\t// to move down the menu to the first item that starts with that character\n\t\t\tif ( !match.length ) {\n\t\t\t\tcharacter = String.fromCharCode( event.keyCode );\n\t\t\t\tmatch = this._filterMenuItems( character );\n\t\t\t}\n\n\t\t\tif ( match.length ) {\n\t\t\t\tthis.focus( event, match );\n\t\t\t\tthis.previousFilter = character;\n\t\t\t\tthis.filterTimer = this._delay( function() {\n\t\t\t\t\tdelete this.previousFilter;\n\t\t\t\t}, 1000 );\n\t\t\t} else {\n\t\t\t\tdelete this.previousFilter;\n\t\t\t}\n\t\t}\n\n\t\tif ( preventDefault ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t},\n\n\t_activate: function( event ) {\n\t\tif ( this.active && !this.active.is( \".ui-state-disabled\" ) ) {\n\t\t\tif ( this.active.children( \"[aria-haspopup='true']\" ).length ) {\n\t\t\t\tthis.expand( event );\n\t\t\t} else {\n\t\t\t\tthis.select( event );\n\t\t\t}\n\t\t}\n\t},\n\n\trefresh: function() {\n\t\tvar menus, items, newSubmenus, newItems, newWrappers,\n\t\t\tthat = this,\n\t\t\ticon = this.options.icons.submenu,\n\t\t\tsubmenus = this.element.find( this.options.menus );\n\n\t\tthis._toggleClass( \"ui-menu-icons\", null, !!this.element.find( \".ui-icon\" ).length );\n\n\t\t// Initialize nested menus\n\t\tnewSubmenus = submenus.filter( \":not(.ui-menu)\" )\n\t\t\t.hide()\n\t\t\t.attr( {\n\t\t\t\trole: this.options.role,\n\t\t\t\t\"aria-hidden\": \"true\",\n\t\t\t\t\"aria-expanded\": \"false\"\n\t\t\t} )\n\t\t\t.each( function() {\n\t\t\t\tvar menu = $( this ),\n\t\t\t\t\titem = menu.prev(),\n\t\t\t\t\tsubmenuCaret = $( \"<span>\" ).data( \"ui-menu-submenu-caret\", true );\n\n\t\t\t\tthat._addClass( submenuCaret, \"ui-menu-icon\", \"ui-icon \" + icon );\n\t\t\t\titem\n\t\t\t\t\t.attr( \"aria-haspopup\", \"true\" )\n\t\t\t\t\t.prepend( submenuCaret );\n\t\t\t\tmenu.attr( \"aria-labelledby\", item.attr( \"id\" ) );\n\t\t\t} );\n\n\t\tthis._addClass( newSubmenus, \"ui-menu\", \"ui-widget ui-widget-content ui-front\" );\n\n\t\tmenus = submenus.add( this.element );\n\t\titems = menus.find( this.options.items );\n\n\t\t// Initialize menu-items containing spaces and/or dashes only as dividers\n\t\titems.not( \".ui-menu-item\" ).each( function() {\n\t\t\tvar item = $( this );\n\t\t\tif ( that._isDivider( item ) ) {\n\t\t\t\tthat._addClass( item, \"ui-menu-divider\", \"ui-widget-content\" );\n\t\t\t}\n\t\t} );\n\n\t\t// Don't refresh list items that are already adapted\n\t\tnewItems = items.not( \".ui-menu-item, .ui-menu-divider\" );\n\t\tnewWrappers = newItems.children()\n\t\t\t.not( \".ui-menu\" )\n\t\t\t\t.uniqueId()\n\t\t\t\t.attr( {\n\t\t\t\t\ttabIndex: -1,\n\t\t\t\t\trole: this._itemRole()\n\t\t\t\t} );\n\t\tthis._addClass( newItems, \"ui-menu-item\" )\n\t\t\t._addClass( newWrappers, \"ui-menu-item-wrapper\" );\n\n\t\t// Add aria-disabled attribute to any disabled menu item\n\t\titems.filter( \".ui-state-disabled\" ).attr( \"aria-disabled\", \"true\" );\n\n\t\t// If the active item has been removed, blur the menu\n\t\tif ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {\n\t\t\tthis.blur();\n\t\t}\n\t},\n\n\t_itemRole: function() {\n\t\treturn {\n\t\t\tmenu: \"menuitem\",\n\t\t\tlistbox: \"option\"\n\t\t}[ this.options.role ];\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"icons\" ) {\n\t\t\tvar icons = this.element.find( \".ui-menu-icon\" );\n\t\t\tthis._removeClass( icons, null, this.options.icons.submenu )\n\t\t\t\t._addClass( icons, null, value.submenu );\n\t\t}\n\t\tthis._super( key, value );\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.element.attr( \"aria-disabled\", String( value ) );\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t},\n\n\tfocus: function( event, item ) {\n\t\tvar nested, focused, activeParent;\n\t\tthis.blur( event, event && event.type === \"focus\" );\n\n\t\tthis._scrollIntoView( item );\n\n\t\tthis.active = item.first();\n\n\t\tfocused = this.active.children( \".ui-menu-item-wrapper\" );\n\t\tthis._addClass( focused, null, \"ui-state-active\" );\n\n\t\t// Only update aria-activedescendant if there's a role\n\t\t// otherwise we assume focus is managed elsewhere\n\t\tif ( this.options.role ) {\n\t\t\tthis.element.attr( \"aria-activedescendant\", focused.attr( \"id\" ) );\n\t\t}\n\n\t\t// Highlight active parent menu item, if any\n\t\tactiveParent = this.active\n\t\t\t.parent()\n\t\t\t\t.closest( \".ui-menu-item\" )\n\t\t\t\t\t.children( \".ui-menu-item-wrapper\" );\n\t\tthis._addClass( activeParent, null, \"ui-state-active\" );\n\n\t\tif ( event && event.type === \"keydown\" ) {\n\t\t\tthis._close();\n\t\t} else {\n\t\t\tthis.timer = this._delay( function() {\n\t\t\t\tthis._close();\n\t\t\t}, this.delay );\n\t\t}\n\n\t\tnested = item.children( \".ui-menu\" );\n\t\tif ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {\n\t\t\tthis._startOpening( nested );\n\t\t}\n\t\tthis.activeMenu = item.parent();\n\n\t\tthis._trigger( \"focus\", event, { item: item } );\n\t},\n\n\t_scrollIntoView: function( item ) {\n\t\tvar borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;\n\t\tif ( this._hasScroll() ) {\n\t\t\tborderTop = parseFloat( $.css( this.activeMenu[ 0 ], \"borderTopWidth\" ) ) || 0;\n\t\t\tpaddingTop = parseFloat( $.css( this.activeMenu[ 0 ], \"paddingTop\" ) ) || 0;\n\t\t\toffset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;\n\t\t\tscroll = this.activeMenu.scrollTop();\n\t\t\telementHeight = this.activeMenu.height();\n\t\t\titemHeight = item.outerHeight();\n\n\t\t\tif ( offset < 0 ) {\n\t\t\t\tthis.activeMenu.scrollTop( scroll + offset );\n\t\t\t} else if ( offset + itemHeight > elementHeight ) {\n\t\t\t\tthis.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );\n\t\t\t}\n\t\t}\n\t},\n\n\tblur: function( event, fromFocus ) {\n\t\tif ( !fromFocus ) {\n\t\t\tclearTimeout( this.timer );\n\t\t}\n\n\t\tif ( !this.active ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._removeClass( this.active.children( \".ui-menu-item-wrapper\" ),\n\t\t\tnull, \"ui-state-active\" );\n\n\t\tthis._trigger( \"blur\", event, { item: this.active } );\n\t\tthis.active = null;\n\t},\n\n\t_startOpening: function( submenu ) {\n\t\tclearTimeout( this.timer );\n\n\t\t// Don't open if already open fixes a Firefox bug that caused a .5 pixel\n\t\t// shift in the submenu position when mousing over the caret icon\n\t\tif ( submenu.attr( \"aria-hidden\" ) !== \"true\" ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.timer = this._delay( function() {\n\t\t\tthis._close();\n\t\t\tthis._open( submenu );\n\t\t}, this.delay );\n\t},\n\n\t_open: function( submenu ) {\n\t\tvar position = $.extend( {\n\t\t\tof: this.active\n\t\t}, this.options.position );\n\n\t\tclearTimeout( this.timer );\n\t\tthis.element.find( \".ui-menu\" ).not( submenu.parents( \".ui-menu\" ) )\n\t\t\t.hide()\n\t\t\t.attr( \"aria-hidden\", \"true\" );\n\n\t\tsubmenu\n\t\t\t.show()\n\t\t\t.removeAttr( \"aria-hidden\" )\n\t\t\t.attr( \"aria-expanded\", \"true\" )\n\t\t\t.position( position );\n\t},\n\n\tcollapseAll: function( event, all ) {\n\t\tclearTimeout( this.timer );\n\t\tthis.timer = this._delay( function() {\n\n\t\t\t// If we were passed an event, look for the submenu that contains the event\n\t\t\tvar currentMenu = all ? this.element :\n\t\t\t\t$( event && event.target ).closest( this.element.find( \".ui-menu\" ) );\n\n\t\t\t// If we found no valid submenu ancestor, use the main menu to close all\n\t\t\t// sub menus anyway\n\t\t\tif ( !currentMenu.length ) {\n\t\t\t\tcurrentMenu = this.element;\n\t\t\t}\n\n\t\t\tthis._close( currentMenu );\n\n\t\t\tthis.blur( event );\n\n\t\t\t// Work around active item staying active after menu is blurred\n\t\t\tthis._removeClass( currentMenu.find( \".ui-state-active\" ), null, \"ui-state-active\" );\n\n\t\t\tthis.activeMenu = currentMenu;\n\t\t}, this.delay );\n\t},\n\n\t// With no arguments, closes the currently active menu - if nothing is active\n\t// it closes all menus.  If passed an argument, it will search for menus BELOW\n\t_close: function( startMenu ) {\n\t\tif ( !startMenu ) {\n\t\t\tstartMenu = this.active ? this.active.parent() : this.element;\n\t\t}\n\n\t\tstartMenu.find( \".ui-menu\" )\n\t\t\t.hide()\n\t\t\t.attr( \"aria-hidden\", \"true\" )\n\t\t\t.attr( \"aria-expanded\", \"false\" );\n\t},\n\n\t_closeOnDocumentClick: function( event ) {\n\t\treturn !$( event.target ).closest( \".ui-menu\" ).length;\n\t},\n\n\t_isDivider: function( item ) {\n\n\t\t// Match hyphen, em dash, en dash\n\t\treturn !/[^\\-\\u2014\\u2013\\s]/.test( item.text() );\n\t},\n\n\tcollapse: function( event ) {\n\t\tvar newItem = this.active &&\n\t\t\tthis.active.parent().closest( \".ui-menu-item\", this.element );\n\t\tif ( newItem && newItem.length ) {\n\t\t\tthis._close();\n\t\t\tthis.focus( event, newItem );\n\t\t}\n\t},\n\n\texpand: function( event ) {\n\t\tvar newItem = this.active &&\n\t\t\tthis.active\n\t\t\t\t.children( \".ui-menu \" )\n\t\t\t\t\t.find( this.options.items )\n\t\t\t\t\t\t.first();\n\n\t\tif ( newItem && newItem.length ) {\n\t\t\tthis._open( newItem.parent() );\n\n\t\t\t// Delay so Firefox will not hide activedescendant change in expanding submenu from AT\n\t\t\tthis._delay( function() {\n\t\t\t\tthis.focus( event, newItem );\n\t\t\t} );\n\t\t}\n\t},\n\n\tnext: function( event ) {\n\t\tthis._move( \"next\", \"first\", event );\n\t},\n\n\tprevious: function( event ) {\n\t\tthis._move( \"prev\", \"last\", event );\n\t},\n\n\tisFirstItem: function() {\n\t\treturn this.active && !this.active.prevAll( \".ui-menu-item\" ).length;\n\t},\n\n\tisLastItem: function() {\n\t\treturn this.active && !this.active.nextAll( \".ui-menu-item\" ).length;\n\t},\n\n\t_move: function( direction, filter, event ) {\n\t\tvar next;\n\t\tif ( this.active ) {\n\t\t\tif ( direction === \"first\" || direction === \"last\" ) {\n\t\t\t\tnext = this.active\n\t\t\t\t\t[ direction === \"first\" ? \"prevAll\" : \"nextAll\" ]( \".ui-menu-item\" )\n\t\t\t\t\t.eq( -1 );\n\t\t\t} else {\n\t\t\t\tnext = this.active\n\t\t\t\t\t[ direction + \"All\" ]( \".ui-menu-item\" )\n\t\t\t\t\t.eq( 0 );\n\t\t\t}\n\t\t}\n\t\tif ( !next || !next.length || !this.active ) {\n\t\t\tnext = this.activeMenu.find( this.options.items )[ filter ]();\n\t\t}\n\n\t\tthis.focus( event, next );\n\t},\n\n\tnextPage: function( event ) {\n\t\tvar item, base, height;\n\n\t\tif ( !this.active ) {\n\t\t\tthis.next( event );\n\t\t\treturn;\n\t\t}\n\t\tif ( this.isLastItem() ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( this._hasScroll() ) {\n\t\t\tbase = this.active.offset().top;\n\t\t\theight = this.element.height();\n\t\t\tthis.active.nextAll( \".ui-menu-item\" ).each( function() {\n\t\t\t\titem = $( this );\n\t\t\t\treturn item.offset().top - base - height < 0;\n\t\t\t} );\n\n\t\t\tthis.focus( event, item );\n\t\t} else {\n\t\t\tthis.focus( event, this.activeMenu.find( this.options.items )\n\t\t\t\t[ !this.active ? \"first\" : \"last\" ]() );\n\t\t}\n\t},\n\n\tpreviousPage: function( event ) {\n\t\tvar item, base, height;\n\t\tif ( !this.active ) {\n\t\t\tthis.next( event );\n\t\t\treturn;\n\t\t}\n\t\tif ( this.isFirstItem() ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( this._hasScroll() ) {\n\t\t\tbase = this.active.offset().top;\n\t\t\theight = this.element.height();\n\t\t\tthis.active.prevAll( \".ui-menu-item\" ).each( function() {\n\t\t\t\titem = $( this );\n\t\t\t\treturn item.offset().top - base + height > 0;\n\t\t\t} );\n\n\t\t\tthis.focus( event, item );\n\t\t} else {\n\t\t\tthis.focus( event, this.activeMenu.find( this.options.items ).first() );\n\t\t}\n\t},\n\n\t_hasScroll: function() {\n\t\treturn this.element.outerHeight() < this.element.prop( \"scrollHeight\" );\n\t},\n\n\tselect: function( event ) {\n\n\t\t// TODO: It should never be possible to not have an active item at this\n\t\t// point, but the tests don't trigger mouseenter before click.\n\t\tthis.active = this.active || $( event.target ).closest( \".ui-menu-item\" );\n\t\tvar ui = { item: this.active };\n\t\tif ( !this.active.has( \".ui-menu\" ).length ) {\n\t\t\tthis.collapseAll( event, true );\n\t\t}\n\t\tthis._trigger( \"select\", event, ui );\n\t},\n\n\t_filterMenuItems: function( character ) {\n\t\tvar escapedCharacter = character.replace( /[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, \"\\\\$&\" ),\n\t\t\tregex = new RegExp( \"^\" + escapedCharacter, \"i\" );\n\n\t\treturn this.activeMenu\n\t\t\t.find( this.options.items )\n\n\t\t\t\t// Only match on items, not dividers or other content (#10571)\n\t\t\t\t.filter( \".ui-menu-item\" )\n\t\t\t\t\t.filter( function() {\n\t\t\t\t\t\treturn regex.test(\n\t\t\t\t\t\t\t$.trim( $( this ).children( \".ui-menu-item-wrapper\" ).text() ) );\n\t\t\t\t\t} );\n\t}\n} );\n\n\n/*!\n * jQuery UI Autocomplete 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Autocomplete\n//>>group: Widgets\n//>>description: Lists suggested words as the user is typing.\n//>>docs: http://api.jqueryui.com/autocomplete/\n//>>demos: http://jqueryui.com/autocomplete/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/autocomplete.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.autocomplete\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<input>\",\n\toptions: {\n\t\tappendTo: null,\n\t\tautoFocus: false,\n\t\tdelay: 300,\n\t\tminLength: 1,\n\t\tposition: {\n\t\t\tmy: \"left top\",\n\t\t\tat: \"left bottom\",\n\t\t\tcollision: \"none\"\n\t\t},\n\t\tsource: null,\n\n\t\t// Callbacks\n\t\tchange: null,\n\t\tclose: null,\n\t\tfocus: null,\n\t\topen: null,\n\t\tresponse: null,\n\t\tsearch: null,\n\t\tselect: null\n\t},\n\n\trequestIndex: 0,\n\tpending: 0,\n\n\t_create: function() {\n\n\t\t// Some browsers only repeat keydown events, not keypress events,\n\t\t// so we use the suppressKeyPress flag to determine if we've already\n\t\t// handled the keydown event. #7269\n\t\t// Unfortunately the code for & in keypress is the same as the up arrow,\n\t\t// so we use the suppressKeyPressRepeat flag to avoid handling keypress\n\t\t// events when we know the keydown event was used to modify the\n\t\t// search term. #7799\n\t\tvar suppressKeyPress, suppressKeyPressRepeat, suppressInput,\n\t\t\tnodeName = this.element[ 0 ].nodeName.toLowerCase(),\n\t\t\tisTextarea = nodeName === \"textarea\",\n\t\t\tisInput = nodeName === \"input\";\n\n\t\t// Textareas are always multi-line\n\t\t// Inputs are always single-line, even if inside a contentEditable element\n\t\t// IE also treats inputs as contentEditable\n\t\t// All other element types are determined by whether or not they're contentEditable\n\t\tthis.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );\n\n\t\tthis.valueMethod = this.element[ isTextarea || isInput ? \"val\" : \"text\" ];\n\t\tthis.isNewMenu = true;\n\n\t\tthis._addClass( \"ui-autocomplete-input\" );\n\t\tthis.element.attr( \"autocomplete\", \"off\" );\n\n\t\tthis._on( this.element, {\n\t\t\tkeydown: function( event ) {\n\t\t\t\tif ( this.element.prop( \"readOnly\" ) ) {\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tsuppressInput = true;\n\t\t\t\t\tsuppressKeyPressRepeat = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tsuppressKeyPress = false;\n\t\t\t\tsuppressInput = false;\n\t\t\t\tsuppressKeyPressRepeat = false;\n\t\t\t\tvar keyCode = $.ui.keyCode;\n\t\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase keyCode.PAGE_UP:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._move( \"previousPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.PAGE_DOWN:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._move( \"nextPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.UP:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._keyEvent( \"previous\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.DOWN:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._keyEvent( \"next\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.ENTER:\n\n\t\t\t\t\t// when menu is open and has focus\n\t\t\t\t\tif ( this.menu.active ) {\n\n\t\t\t\t\t\t// #6055 - Opera still allows the keypress to occur\n\t\t\t\t\t\t// which causes forms to submit\n\t\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tthis.menu.select( event );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.TAB:\n\t\t\t\t\tif ( this.menu.active ) {\n\t\t\t\t\t\tthis.menu.select( event );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.ESCAPE:\n\t\t\t\t\tif ( this.menu.element.is( \":visible\" ) ) {\n\t\t\t\t\t\tif ( !this.isMultiLine ) {\n\t\t\t\t\t\t\tthis._value( this.term );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.close( event );\n\n\t\t\t\t\t\t// Different browsers have different default behavior for escape\n\t\t\t\t\t\t// Single press can mean undo or clear\n\t\t\t\t\t\t// Double press in IE means clear the whole form\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tsuppressKeyPressRepeat = true;\n\n\t\t\t\t\t// search timeout should be triggered before the input value is changed\n\t\t\t\t\tthis._searchTimeout( event );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tkeypress: function( event ) {\n\t\t\t\tif ( suppressKeyPress ) {\n\t\t\t\t\tsuppressKeyPress = false;\n\t\t\t\t\tif ( !this.isMultiLine || this.menu.element.is( \":visible\" ) ) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif ( suppressKeyPressRepeat ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Replicate some key handlers to allow them to repeat in Firefox and Opera\n\t\t\t\tvar keyCode = $.ui.keyCode;\n\t\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase keyCode.PAGE_UP:\n\t\t\t\t\tthis._move( \"previousPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.PAGE_DOWN:\n\t\t\t\t\tthis._move( \"nextPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.UP:\n\t\t\t\t\tthis._keyEvent( \"previous\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.DOWN:\n\t\t\t\t\tthis._keyEvent( \"next\", event );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tinput: function( event ) {\n\t\t\t\tif ( suppressInput ) {\n\t\t\t\t\tsuppressInput = false;\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._searchTimeout( event );\n\t\t\t},\n\t\t\tfocus: function() {\n\t\t\t\tthis.selectedItem = null;\n\t\t\t\tthis.previous = this._value();\n\t\t\t},\n\t\t\tblur: function( event ) {\n\t\t\t\tif ( this.cancelBlur ) {\n\t\t\t\t\tdelete this.cancelBlur;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tclearTimeout( this.searching );\n\t\t\t\tthis.close( event );\n\t\t\t\tthis._change( event );\n\t\t\t}\n\t\t} );\n\n\t\tthis._initSource();\n\t\tthis.menu = $( \"<ul>\" )\n\t\t\t.appendTo( this._appendTo() )\n\t\t\t.menu( {\n\n\t\t\t\t// disable ARIA support, the live region takes care of that\n\t\t\t\trole: null\n\t\t\t} )\n\t\t\t.hide()\n\t\t\t.menu( \"instance\" );\n\n\t\tthis._addClass( this.menu.element, \"ui-autocomplete\", \"ui-front\" );\n\t\tthis._on( this.menu.element, {\n\t\t\tmousedown: function( event ) {\n\n\t\t\t\t// prevent moving focus out of the text field\n\t\t\t\tevent.preventDefault();\n\n\t\t\t\t// IE doesn't prevent moving focus even with event.preventDefault()\n\t\t\t\t// so we set a flag to know when we should ignore the blur event\n\t\t\t\tthis.cancelBlur = true;\n\t\t\t\tthis._delay( function() {\n\t\t\t\t\tdelete this.cancelBlur;\n\n\t\t\t\t\t// Support: IE 8 only\n\t\t\t\t\t// Right clicking a menu item or selecting text from the menu items will\n\t\t\t\t\t// result in focus moving out of the input. However, we've already received\n\t\t\t\t\t// and ignored the blur event because of the cancelBlur flag set above. So\n\t\t\t\t\t// we restore focus to ensure that the menu closes properly based on the user's\n\t\t\t\t\t// next actions.\n\t\t\t\t\tif ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {\n\t\t\t\t\t\tthis.element.trigger( \"focus\" );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t},\n\t\t\tmenufocus: function( event, ui ) {\n\t\t\t\tvar label, item;\n\n\t\t\t\t// support: Firefox\n\t\t\t\t// Prevent accidental activation of menu items in Firefox (#7024 #9118)\n\t\t\t\tif ( this.isNewMenu ) {\n\t\t\t\t\tthis.isNewMenu = false;\n\t\t\t\t\tif ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {\n\t\t\t\t\t\tthis.menu.blur();\n\n\t\t\t\t\t\tthis.document.one( \"mousemove\", function() {\n\t\t\t\t\t\t\t$( event.target ).trigger( event.originalEvent );\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\titem = ui.item.data( \"ui-autocomplete-item\" );\n\t\t\t\tif ( false !== this._trigger( \"focus\", event, { item: item } ) ) {\n\n\t\t\t\t\t// use value to match what will end up in the input, if it was a key event\n\t\t\t\t\tif ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {\n\t\t\t\t\t\tthis._value( item.value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Announce the value in the liveRegion\n\t\t\t\tlabel = ui.item.attr( \"aria-label\" ) || item.value;\n\t\t\t\tif ( label && $.trim( label ).length ) {\n\t\t\t\t\tthis.liveRegion.children().hide();\n\t\t\t\t\t$( \"<div>\" ).text( label ).appendTo( this.liveRegion );\n\t\t\t\t}\n\t\t\t},\n\t\t\tmenuselect: function( event, ui ) {\n\t\t\t\tvar item = ui.item.data( \"ui-autocomplete-item\" ),\n\t\t\t\t\tprevious = this.previous;\n\n\t\t\t\t// Only trigger when focus was lost (click on menu)\n\t\t\t\tif ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {\n\t\t\t\t\tthis.element.trigger( \"focus\" );\n\t\t\t\t\tthis.previous = previous;\n\n\t\t\t\t\t// #6109 - IE triggers two focus events and the second\n\t\t\t\t\t// is asynchronous, so we need to reset the previous\n\t\t\t\t\t// term synchronously and asynchronously :-(\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tthis.previous = previous;\n\t\t\t\t\t\tthis.selectedItem = item;\n\t\t\t\t\t} );\n\t\t\t\t}\n\n\t\t\t\tif ( false !== this._trigger( \"select\", event, { item: item } ) ) {\n\t\t\t\t\tthis._value( item.value );\n\t\t\t\t}\n\n\t\t\t\t// reset the term after the select event\n\t\t\t\t// this allows custom select handling to work properly\n\t\t\t\tthis.term = this._value();\n\n\t\t\t\tthis.close( event );\n\t\t\t\tthis.selectedItem = item;\n\t\t\t}\n\t\t} );\n\n\t\tthis.liveRegion = $( \"<div>\", {\n\t\t\trole: \"status\",\n\t\t\t\"aria-live\": \"assertive\",\n\t\t\t\"aria-relevant\": \"additions\"\n\t\t} )\n\t\t\t.appendTo( this.document[ 0 ].body );\n\n\t\tthis._addClass( this.liveRegion, null, \"ui-helper-hidden-accessible\" );\n\n\t\t// Turning off autocomplete prevents the browser from remembering the\n\t\t// value when navigating through history, so we re-enable autocomplete\n\t\t// if the page is unloaded before the widget is destroyed. #7790\n\t\tthis._on( this.window, {\n\t\t\tbeforeunload: function() {\n\t\t\t\tthis.element.removeAttr( \"autocomplete\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_destroy: function() {\n\t\tclearTimeout( this.searching );\n\t\tthis.element.removeAttr( \"autocomplete\" );\n\t\tthis.menu.element.remove();\n\t\tthis.liveRegion.remove();\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\t\tif ( key === \"source\" ) {\n\t\t\tthis._initSource();\n\t\t}\n\t\tif ( key === \"appendTo\" ) {\n\t\t\tthis.menu.element.appendTo( this._appendTo() );\n\t\t}\n\t\tif ( key === \"disabled\" && value && this.xhr ) {\n\t\t\tthis.xhr.abort();\n\t\t}\n\t},\n\n\t_isEventTargetInWidget: function( event ) {\n\t\tvar menuElement = this.menu.element[ 0 ];\n\n\t\treturn event.target === this.element[ 0 ] ||\n\t\t\tevent.target === menuElement ||\n\t\t\t$.contains( menuElement, event.target );\n\t},\n\n\t_closeOnClickOutside: function( event ) {\n\t\tif ( !this._isEventTargetInWidget( event ) ) {\n\t\t\tthis.close();\n\t\t}\n\t},\n\n\t_appendTo: function() {\n\t\tvar element = this.options.appendTo;\n\n\t\tif ( element ) {\n\t\t\telement = element.jquery || element.nodeType ?\n\t\t\t\t$( element ) :\n\t\t\t\tthis.document.find( element ).eq( 0 );\n\t\t}\n\n\t\tif ( !element || !element[ 0 ] ) {\n\t\t\telement = this.element.closest( \".ui-front, dialog\" );\n\t\t}\n\n\t\tif ( !element.length ) {\n\t\t\telement = this.document[ 0 ].body;\n\t\t}\n\n\t\treturn element;\n\t},\n\n\t_initSource: function() {\n\t\tvar array, url,\n\t\t\tthat = this;\n\t\tif ( $.isArray( this.options.source ) ) {\n\t\t\tarray = this.options.source;\n\t\t\tthis.source = function( request, response ) {\n\t\t\t\tresponse( $.ui.autocomplete.filter( array, request.term ) );\n\t\t\t};\n\t\t} else if ( typeof this.options.source === \"string\" ) {\n\t\t\turl = this.options.source;\n\t\t\tthis.source = function( request, response ) {\n\t\t\t\tif ( that.xhr ) {\n\t\t\t\t\tthat.xhr.abort();\n\t\t\t\t}\n\t\t\t\tthat.xhr = $.ajax( {\n\t\t\t\t\turl: url,\n\t\t\t\t\tdata: request,\n\t\t\t\t\tdataType: \"json\",\n\t\t\t\t\tsuccess: function( data ) {\n\t\t\t\t\t\tresponse( data );\n\t\t\t\t\t},\n\t\t\t\t\terror: function() {\n\t\t\t\t\t\tresponse( [] );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t};\n\t\t} else {\n\t\t\tthis.source = this.options.source;\n\t\t}\n\t},\n\n\t_searchTimeout: function( event ) {\n\t\tclearTimeout( this.searching );\n\t\tthis.searching = this._delay( function() {\n\n\t\t\t// Search if the value has changed, or if the user retypes the same value (see #7434)\n\t\t\tvar equalValues = this.term === this._value(),\n\t\t\t\tmenuVisible = this.menu.element.is( \":visible\" ),\n\t\t\t\tmodifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\n\n\t\t\tif ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {\n\t\t\t\tthis.selectedItem = null;\n\t\t\t\tthis.search( null, event );\n\t\t\t}\n\t\t}, this.options.delay );\n\t},\n\n\tsearch: function( value, event ) {\n\t\tvalue = value != null ? value : this._value();\n\n\t\t// Always save the actual value, not the one passed as an argument\n\t\tthis.term = this._value();\n\n\t\tif ( value.length < this.options.minLength ) {\n\t\t\treturn this.close( event );\n\t\t}\n\n\t\tif ( this._trigger( \"search\", event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn this._search( value );\n\t},\n\n\t_search: function( value ) {\n\t\tthis.pending++;\n\t\tthis._addClass( \"ui-autocomplete-loading\" );\n\t\tthis.cancelSearch = false;\n\n\t\tthis.source( { term: value }, this._response() );\n\t},\n\n\t_response: function() {\n\t\tvar index = ++this.requestIndex;\n\n\t\treturn $.proxy( function( content ) {\n\t\t\tif ( index === this.requestIndex ) {\n\t\t\t\tthis.__response( content );\n\t\t\t}\n\n\t\t\tthis.pending--;\n\t\t\tif ( !this.pending ) {\n\t\t\t\tthis._removeClass( \"ui-autocomplete-loading\" );\n\t\t\t}\n\t\t}, this );\n\t},\n\n\t__response: function( content ) {\n\t\tif ( content ) {\n\t\t\tcontent = this._normalize( content );\n\t\t}\n\t\tthis._trigger( \"response\", null, { content: content } );\n\t\tif ( !this.options.disabled && content && content.length && !this.cancelSearch ) {\n\t\t\tthis._suggest( content );\n\t\t\tthis._trigger( \"open\" );\n\t\t} else {\n\n\t\t\t// use ._close() instead of .close() so we don't cancel future searches\n\t\t\tthis._close();\n\t\t}\n\t},\n\n\tclose: function( event ) {\n\t\tthis.cancelSearch = true;\n\t\tthis._close( event );\n\t},\n\n\t_close: function( event ) {\n\n\t\t// Remove the handler that closes the menu on outside clicks\n\t\tthis._off( this.document, \"mousedown\" );\n\n\t\tif ( this.menu.element.is( \":visible\" ) ) {\n\t\t\tthis.menu.element.hide();\n\t\t\tthis.menu.blur();\n\t\t\tthis.isNewMenu = true;\n\t\t\tthis._trigger( \"close\", event );\n\t\t}\n\t},\n\n\t_change: function( event ) {\n\t\tif ( this.previous !== this._value() ) {\n\t\t\tthis._trigger( \"change\", event, { item: this.selectedItem } );\n\t\t}\n\t},\n\n\t_normalize: function( items ) {\n\n\t\t// assume all items have the right format when the first item is complete\n\t\tif ( items.length && items[ 0 ].label && items[ 0 ].value ) {\n\t\t\treturn items;\n\t\t}\n\t\treturn $.map( items, function( item ) {\n\t\t\tif ( typeof item === \"string\" ) {\n\t\t\t\treturn {\n\t\t\t\t\tlabel: item,\n\t\t\t\t\tvalue: item\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn $.extend( {}, item, {\n\t\t\t\tlabel: item.label || item.value,\n\t\t\t\tvalue: item.value || item.label\n\t\t\t} );\n\t\t} );\n\t},\n\n\t_suggest: function( items ) {\n\t\tvar ul = this.menu.element.empty();\n\t\tthis._renderMenu( ul, items );\n\t\tthis.isNewMenu = true;\n\t\tthis.menu.refresh();\n\n\t\t// Size and position menu\n\t\tul.show();\n\t\tthis._resizeMenu();\n\t\tul.position( $.extend( {\n\t\t\tof: this.element\n\t\t}, this.options.position ) );\n\n\t\tif ( this.options.autoFocus ) {\n\t\t\tthis.menu.next();\n\t\t}\n\n\t\t// Listen for interactions outside of the widget (#6642)\n\t\tthis._on( this.document, {\n\t\t\tmousedown: \"_closeOnClickOutside\"\n\t\t} );\n\t},\n\n\t_resizeMenu: function() {\n\t\tvar ul = this.menu.element;\n\t\tul.outerWidth( Math.max(\n\n\t\t\t// Firefox wraps long text (possibly a rounding bug)\n\t\t\t// so we add 1px to avoid the wrapping (#7513)\n\t\t\tul.width( \"\" ).outerWidth() + 1,\n\t\t\tthis.element.outerWidth()\n\t\t) );\n\t},\n\n\t_renderMenu: function( ul, items ) {\n\t\tvar that = this;\n\t\t$.each( items, function( index, item ) {\n\t\t\tthat._renderItemData( ul, item );\n\t\t} );\n\t},\n\n\t_renderItemData: function( ul, item ) {\n\t\treturn this._renderItem( ul, item ).data( \"ui-autocomplete-item\", item );\n\t},\n\n\t_renderItem: function( ul, item ) {\n\t\treturn $( \"<li>\" )\n\t\t\t.append( $( \"<div>\" ).text( item.label ) )\n\t\t\t.appendTo( ul );\n\t},\n\n\t_move: function( direction, event ) {\n\t\tif ( !this.menu.element.is( \":visible\" ) ) {\n\t\t\tthis.search( null, event );\n\t\t\treturn;\n\t\t}\n\t\tif ( this.menu.isFirstItem() && /^previous/.test( direction ) ||\n\t\t\t\tthis.menu.isLastItem() && /^next/.test( direction ) ) {\n\n\t\t\tif ( !this.isMultiLine ) {\n\t\t\t\tthis._value( this.term );\n\t\t\t}\n\n\t\t\tthis.menu.blur();\n\t\t\treturn;\n\t\t}\n\t\tthis.menu[ direction ]( event );\n\t},\n\n\twidget: function() {\n\t\treturn this.menu.element;\n\t},\n\n\t_value: function() {\n\t\treturn this.valueMethod.apply( this.element, arguments );\n\t},\n\n\t_keyEvent: function( keyEvent, event ) {\n\t\tif ( !this.isMultiLine || this.menu.element.is( \":visible\" ) ) {\n\t\t\tthis._move( keyEvent, event );\n\n\t\t\t// Prevents moving cursor to beginning/end of the text field in some browsers\n\t\t\tevent.preventDefault();\n\t\t}\n\t},\n\n\t// Support: Chrome <=50\n\t// We should be able to just use this.element.prop( \"isContentEditable\" )\n\t// but hidden elements always report false in Chrome.\n\t// https://code.google.com/p/chromium/issues/detail?id=313082\n\t_isContentEditable: function( element ) {\n\t\tif ( !element.length ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar editable = element.prop( \"contentEditable\" );\n\n\t\tif ( editable === \"inherit\" ) {\n\t\t  return this._isContentEditable( element.parent() );\n\t\t}\n\n\t\treturn editable === \"true\";\n\t}\n} );\n\n$.extend( $.ui.autocomplete, {\n\tescapeRegex: function( value ) {\n\t\treturn value.replace( /[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, \"\\\\$&\" );\n\t},\n\tfilter: function( array, term ) {\n\t\tvar matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), \"i\" );\n\t\treturn $.grep( array, function( value ) {\n\t\t\treturn matcher.test( value.label || value.value || value );\n\t\t} );\n\t}\n} );\n\n// Live region extension, adding a `messages` option\n// NOTE: This is an experimental API. We are still investigating\n// a full solution for string manipulation and internationalization.\n$.widget( \"ui.autocomplete\", $.ui.autocomplete, {\n\toptions: {\n\t\tmessages: {\n\t\t\tnoResults: \"No search results.\",\n\t\t\tresults: function( amount ) {\n\t\t\t\treturn amount + ( amount > 1 ? \" results are\" : \" result is\" ) +\n\t\t\t\t\t\" available, use up and down arrow keys to navigate.\";\n\t\t\t}\n\t\t}\n\t},\n\n\t__response: function( content ) {\n\t\tvar message;\n\t\tthis._superApply( arguments );\n\t\tif ( this.options.disabled || this.cancelSearch ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( content && content.length ) {\n\t\t\tmessage = this.options.messages.results( content.length );\n\t\t} else {\n\t\t\tmessage = this.options.messages.noResults;\n\t\t}\n\t\tthis.liveRegion.children().hide();\n\t\t$( \"<div>\" ).text( message ).appendTo( this.liveRegion );\n\t}\n} );\n\nvar widgetsAutocomplete = $.ui.autocomplete;\n\n\n/*!\n * jQuery UI Controlgroup 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Controlgroup\n//>>group: Widgets\n//>>description: Visually groups form control widgets\n//>>docs: http://api.jqueryui.com/controlgroup/\n//>>demos: http://jqueryui.com/controlgroup/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/controlgroup.css\n//>>css.theme: ../../themes/base/theme.css\n\n\nvar controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g;\n\nvar widgetsControlgroup = $.widget( \"ui.controlgroup\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<div>\",\n\toptions: {\n\t\tdirection: \"horizontal\",\n\t\tdisabled: null,\n\t\tonlyVisible: true,\n\t\titems: {\n\t\t\t\"button\": \"input[type=button], input[type=submit], input[type=reset], button, a\",\n\t\t\t\"controlgroupLabel\": \".ui-controlgroup-label\",\n\t\t\t\"checkboxradio\": \"input[type='checkbox'], input[type='radio']\",\n\t\t\t\"selectmenu\": \"select\",\n\t\t\t\"spinner\": \".ui-spinner-input\"\n\t\t}\n\t},\n\n\t_create: function() {\n\t\tthis._enhance();\n\t},\n\n\t// To support the enhanced option in jQuery Mobile, we isolate DOM manipulation\n\t_enhance: function() {\n\t\tthis.element.attr( \"role\", \"toolbar\" );\n\t\tthis.refresh();\n\t},\n\n\t_destroy: function() {\n\t\tthis._callChildMethod( \"destroy\" );\n\t\tthis.childWidgets.removeData( \"ui-controlgroup-data\" );\n\t\tthis.element.removeAttr( \"role\" );\n\t\tif ( this.options.items.controlgroupLabel ) {\n\t\t\tthis.element\n\t\t\t\t.find( this.options.items.controlgroupLabel )\n\t\t\t\t.find( \".ui-controlgroup-label-contents\" )\n\t\t\t\t.contents().unwrap();\n\t\t}\n\t},\n\n\t_initWidgets: function() {\n\t\tvar that = this,\n\t\t\tchildWidgets = [];\n\n\t\t// First we iterate over each of the items options\n\t\t$.each( this.options.items, function( widget, selector ) {\n\t\t\tvar labels;\n\t\t\tvar options = {};\n\n\t\t\t// Make sure the widget has a selector set\n\t\t\tif ( !selector ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( widget === \"controlgroupLabel\" ) {\n\t\t\t\tlabels = that.element.find( selector );\n\t\t\t\tlabels.each( function() {\n\t\t\t\t\tvar element = $( this );\n\n\t\t\t\t\tif ( element.children( \".ui-controlgroup-label-contents\" ).length ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\telement.contents()\n\t\t\t\t\t\t.wrapAll( \"<span class='ui-controlgroup-label-contents'></span>\" );\n\t\t\t\t} );\n\t\t\t\tthat._addClass( labels, null, \"ui-widget ui-widget-content ui-state-default\" );\n\t\t\t\tchildWidgets = childWidgets.concat( labels.get() );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Make sure the widget actually exists\n\t\t\tif ( !$.fn[ widget ] ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// We assume everything is in the middle to start because we can't determine\n\t\t\t// first / last elements until all enhancments are done.\n\t\t\tif ( that[ \"_\" + widget + \"Options\" ] ) {\n\t\t\t\toptions = that[ \"_\" + widget + \"Options\" ]( \"middle\" );\n\t\t\t} else {\n\t\t\t\toptions = { classes: {} };\n\t\t\t}\n\n\t\t\t// Find instances of this widget inside controlgroup and init them\n\t\t\tthat.element\n\t\t\t\t.find( selector )\n\t\t\t\t.each( function() {\n\t\t\t\t\tvar element = $( this );\n\t\t\t\t\tvar instance = element[ widget ]( \"instance\" );\n\n\t\t\t\t\t// We need to clone the default options for this type of widget to avoid\n\t\t\t\t\t// polluting the variable options which has a wider scope than a single widget.\n\t\t\t\t\tvar instanceOptions = $.widget.extend( {}, options );\n\n\t\t\t\t\t// If the button is the child of a spinner ignore it\n\t\t\t\t\t// TODO: Find a more generic solution\n\t\t\t\t\tif ( widget === \"button\" && element.parent( \".ui-spinner\" ).length ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create the widget if it doesn't exist\n\t\t\t\t\tif ( !instance ) {\n\t\t\t\t\t\tinstance = element[ widget ]()[ widget ]( \"instance\" );\n\t\t\t\t\t}\n\t\t\t\t\tif ( instance ) {\n\t\t\t\t\t\tinstanceOptions.classes =\n\t\t\t\t\t\t\tthat._resolveClassesValues( instanceOptions.classes, instance );\n\t\t\t\t\t}\n\t\t\t\t\telement[ widget ]( instanceOptions );\n\n\t\t\t\t\t// Store an instance of the controlgroup to be able to reference\n\t\t\t\t\t// from the outermost element for changing options and refresh\n\t\t\t\t\tvar widgetElement = element[ widget ]( \"widget\" );\n\t\t\t\t\t$.data( widgetElement[ 0 ], \"ui-controlgroup-data\",\n\t\t\t\t\t\tinstance ? instance : element[ widget ]( \"instance\" ) );\n\n\t\t\t\t\tchildWidgets.push( widgetElement[ 0 ] );\n\t\t\t\t} );\n\t\t} );\n\n\t\tthis.childWidgets = $( $.unique( childWidgets ) );\n\t\tthis._addClass( this.childWidgets, \"ui-controlgroup-item\" );\n\t},\n\n\t_callChildMethod: function( method ) {\n\t\tthis.childWidgets.each( function() {\n\t\t\tvar element = $( this ),\n\t\t\t\tdata = element.data( \"ui-controlgroup-data\" );\n\t\t\tif ( data && data[ method ] ) {\n\t\t\t\tdata[ method ]();\n\t\t\t}\n\t\t} );\n\t},\n\n\t_updateCornerClass: function( element, position ) {\n\t\tvar remove = \"ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all\";\n\t\tvar add = this._buildSimpleOptions( position, \"label\" ).classes.label;\n\n\t\tthis._removeClass( element, null, remove );\n\t\tthis._addClass( element, null, add );\n\t},\n\n\t_buildSimpleOptions: function( position, key ) {\n\t\tvar direction = this.options.direction === \"vertical\";\n\t\tvar result = {\n\t\t\tclasses: {}\n\t\t};\n\t\tresult.classes[ key ] = {\n\t\t\t\"middle\": \"\",\n\t\t\t\"first\": \"ui-corner-\" + ( direction ? \"top\" : \"left\" ),\n\t\t\t\"last\": \"ui-corner-\" + ( direction ? \"bottom\" : \"right\" ),\n\t\t\t\"only\": \"ui-corner-all\"\n\t\t}[ position ];\n\n\t\treturn result;\n\t},\n\n\t_spinnerOptions: function( position ) {\n\t\tvar options = this._buildSimpleOptions( position, \"ui-spinner\" );\n\n\t\toptions.classes[ \"ui-spinner-up\" ] = \"\";\n\t\toptions.classes[ \"ui-spinner-down\" ] = \"\";\n\n\t\treturn options;\n\t},\n\n\t_buttonOptions: function( position ) {\n\t\treturn this._buildSimpleOptions( position, \"ui-button\" );\n\t},\n\n\t_checkboxradioOptions: function( position ) {\n\t\treturn this._buildSimpleOptions( position, \"ui-checkboxradio-label\" );\n\t},\n\n\t_selectmenuOptions: function( position ) {\n\t\tvar direction = this.options.direction === \"vertical\";\n\t\treturn {\n\t\t\twidth: direction ? \"auto\" : false,\n\t\t\tclasses: {\n\t\t\t\tmiddle: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": \"\",\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"\"\n\t\t\t\t},\n\t\t\t\tfirst: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": \"ui-corner-\" + ( direction ? \"top\" : \"tl\" ),\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-\" + ( direction ? \"top\" : \"left\" )\n\t\t\t\t},\n\t\t\t\tlast: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": direction ? \"\" : \"ui-corner-tr\",\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-\" + ( direction ? \"bottom\" : \"right\" )\n\t\t\t\t},\n\t\t\t\tonly: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": \"ui-corner-top\",\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-all\"\n\t\t\t\t}\n\n\t\t\t}[ position ]\n\t\t};\n\t},\n\n\t_resolveClassesValues: function( classes, instance ) {\n\t\tvar result = {};\n\t\t$.each( classes, function( key ) {\n\t\t\tvar current = instance.options.classes[ key ] || \"\";\n\t\t\tcurrent = $.trim( current.replace( controlgroupCornerRegex, \"\" ) );\n\t\t\tresult[ key ] = ( current + \" \" + classes[ key ] ).replace( /\\s+/g, \" \" );\n\t\t} );\n\t\treturn result;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"direction\" ) {\n\t\t\tthis._removeClass( \"ui-controlgroup-\" + this.options.direction );\n\t\t}\n\n\t\tthis._super( key, value );\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._callChildMethod( value ? \"disable\" : \"enable\" );\n\t\t\treturn;\n\t\t}\n\n\t\tthis.refresh();\n\t},\n\n\trefresh: function() {\n\t\tvar children,\n\t\t\tthat = this;\n\n\t\tthis._addClass( \"ui-controlgroup ui-controlgroup-\" + this.options.direction );\n\n\t\tif ( this.options.direction === \"horizontal\" ) {\n\t\t\tthis._addClass( null, \"ui-helper-clearfix\" );\n\t\t}\n\t\tthis._initWidgets();\n\n\t\tchildren = this.childWidgets;\n\n\t\t// We filter here because we need to track all childWidgets not just the visible ones\n\t\tif ( this.options.onlyVisible ) {\n\t\t\tchildren = children.filter( \":visible\" );\n\t\t}\n\n\t\tif ( children.length ) {\n\n\t\t\t// We do this last because we need to make sure all enhancment is done\n\t\t\t// before determining first and last\n\t\t\t$.each( [ \"first\", \"last\" ], function( index, value ) {\n\t\t\t\tvar instance = children[ value ]().data( \"ui-controlgroup-data\" );\n\n\t\t\t\tif ( instance && that[ \"_\" + instance.widgetName + \"Options\" ] ) {\n\t\t\t\t\tvar options = that[ \"_\" + instance.widgetName + \"Options\" ](\n\t\t\t\t\t\tchildren.length === 1 ? \"only\" : value\n\t\t\t\t\t);\n\t\t\t\t\toptions.classes = that._resolveClassesValues( options.classes, instance );\n\t\t\t\t\tinstance.element[ instance.widgetName ]( options );\n\t\t\t\t} else {\n\t\t\t\t\tthat._updateCornerClass( children[ value ](), value );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Finally call the refresh method on each of the child widgets.\n\t\t\tthis._callChildMethod( \"refresh\" );\n\t\t}\n\t}\n} );\n\n/*!\n * jQuery UI Checkboxradio 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Checkboxradio\n//>>group: Widgets\n//>>description: Enhances a form with multiple themeable checkboxes or radio buttons.\n//>>docs: http://api.jqueryui.com/checkboxradio/\n//>>demos: http://jqueryui.com/checkboxradio/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/button.css\n//>>css.structure: ../../themes/base/checkboxradio.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.checkboxradio\", [ $.ui.formResetMixin, {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tdisabled: null,\n\t\tlabel: null,\n\t\ticon: true,\n\t\tclasses: {\n\t\t\t\"ui-checkboxradio-label\": \"ui-corner-all\",\n\t\t\t\"ui-checkboxradio-icon\": \"ui-corner-all\"\n\t\t}\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar disabled, labels;\n\t\tvar that = this;\n\t\tvar options = this._super() || {};\n\n\t\t// We read the type here, because it makes more sense to throw a element type error first,\n\t\t// rather then the error for lack of a label. Often if its the wrong type, it\n\t\t// won't have a label (e.g. calling on a div, btn, etc)\n\t\tthis._readType();\n\n\t\tlabels = this.element.labels();\n\n\t\t// If there are multiple labels, use the last one\n\t\tthis.label = $( labels[ labels.length - 1 ] );\n\t\tif ( !this.label.length ) {\n\t\t\t$.error( \"No label found for checkboxradio widget\" );\n\t\t}\n\n\t\tthis.originalLabel = \"\";\n\n\t\t// We need to get the label text but this may also need to make sure it does not contain the\n\t\t// input itself.\n\t\tthis.label.contents().not( this.element[ 0 ] ).each( function() {\n\n\t\t\t// The label contents could be text, html, or a mix. We concat each element to get a\n\t\t\t// string representation of the label, without the input as part of it.\n\t\t\tthat.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML;\n\t\t} );\n\n\t\t// Set the label option if we found label text\n\t\tif ( this.originalLabel ) {\n\t\t\toptions.label = this.originalLabel;\n\t\t}\n\n\t\tdisabled = this.element[ 0 ].disabled;\n\t\tif ( disabled != null ) {\n\t\t\toptions.disabled = disabled;\n\t\t}\n\t\treturn options;\n\t},\n\n\t_create: function() {\n\t\tvar checked = this.element[ 0 ].checked;\n\n\t\tthis._bindFormResetHandler();\n\n\t\tif ( this.options.disabled == null ) {\n\t\t\tthis.options.disabled = this.element[ 0 ].disabled;\n\t\t}\n\n\t\tthis._setOption( \"disabled\", this.options.disabled );\n\t\tthis._addClass( \"ui-checkboxradio\", \"ui-helper-hidden-accessible\" );\n\t\tthis._addClass( this.label, \"ui-checkboxradio-label\", \"ui-button ui-widget\" );\n\n\t\tif ( this.type === \"radio\" ) {\n\t\t\tthis._addClass( this.label, \"ui-checkboxradio-radio-label\" );\n\t\t}\n\n\t\tif ( this.options.label && this.options.label !== this.originalLabel ) {\n\t\t\tthis._updateLabel();\n\t\t} else if ( this.originalLabel ) {\n\t\t\tthis.options.label = this.originalLabel;\n\t\t}\n\n\t\tthis._enhance();\n\n\t\tif ( checked ) {\n\t\t\tthis._addClass( this.label, \"ui-checkboxradio-checked\", \"ui-state-active\" );\n\t\t\tif ( this.icon ) {\n\t\t\t\tthis._addClass( this.icon, null, \"ui-state-hover\" );\n\t\t\t}\n\t\t}\n\n\t\tthis._on( {\n\t\t\tchange: \"_toggleClasses\",\n\t\t\tfocus: function() {\n\t\t\t\tthis._addClass( this.label, null, \"ui-state-focus ui-visual-focus\" );\n\t\t\t},\n\t\t\tblur: function() {\n\t\t\t\tthis._removeClass( this.label, null, \"ui-state-focus ui-visual-focus\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_readType: function() {\n\t\tvar nodeName = this.element[ 0 ].nodeName.toLowerCase();\n\t\tthis.type = this.element[ 0 ].type;\n\t\tif ( nodeName !== \"input\" || !/radio|checkbox/.test( this.type ) ) {\n\t\t\t$.error( \"Can't create checkboxradio on element.nodeName=\" + nodeName +\n\t\t\t\t\" and element.type=\" + this.type );\n\t\t}\n\t},\n\n\t// Support jQuery Mobile enhanced option\n\t_enhance: function() {\n\t\tthis._updateIcon( this.element[ 0 ].checked );\n\t},\n\n\twidget: function() {\n\t\treturn this.label;\n\t},\n\n\t_getRadioGroup: function() {\n\t\tvar group;\n\t\tvar name = this.element[ 0 ].name;\n\t\tvar nameSelector = \"input[name='\" + $.ui.escapeSelector( name ) + \"']\";\n\n\t\tif ( !name ) {\n\t\t\treturn $( [] );\n\t\t}\n\n\t\tif ( this.form.length ) {\n\t\t\tgroup = $( this.form[ 0 ].elements ).filter( nameSelector );\n\t\t} else {\n\n\t\t\t// Not inside a form, check all inputs that also are not inside a form\n\t\t\tgroup = $( nameSelector ).filter( function() {\n\t\t\t\treturn $( this ).form().length === 0;\n\t\t\t} );\n\t\t}\n\n\t\treturn group.not( this.element );\n\t},\n\n\t_toggleClasses: function() {\n\t\tvar checked = this.element[ 0 ].checked;\n\t\tthis._toggleClass( this.label, \"ui-checkboxradio-checked\", \"ui-state-active\", checked );\n\n\t\tif ( this.options.icon && this.type === \"checkbox\" ) {\n\t\t\tthis._toggleClass( this.icon, null, \"ui-icon-check ui-state-checked\", checked )\n\t\t\t\t._toggleClass( this.icon, null, \"ui-icon-blank\", !checked );\n\t\t}\n\n\t\tif ( this.type === \"radio\" ) {\n\t\t\tthis._getRadioGroup()\n\t\t\t\t.each( function() {\n\t\t\t\t\tvar instance = $( this ).checkboxradio( \"instance\" );\n\n\t\t\t\t\tif ( instance ) {\n\t\t\t\t\t\tinstance._removeClass( instance.label,\n\t\t\t\t\t\t\t\"ui-checkboxradio-checked\", \"ui-state-active\" );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tthis._unbindFormResetHandler();\n\n\t\tif ( this.icon ) {\n\t\t\tthis.icon.remove();\n\t\t\tthis.iconSpace.remove();\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\n\t\t// We don't allow the value to be set to nothing\n\t\tif ( key === \"label\" && !value ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._toggleClass( this.label, null, \"ui-state-disabled\", value );\n\t\t\tthis.element[ 0 ].disabled = value;\n\n\t\t\t// Don't refresh when setting disabled\n\t\t\treturn;\n\t\t}\n\t\tthis.refresh();\n\t},\n\n\t_updateIcon: function( checked ) {\n\t\tvar toAdd = \"ui-icon ui-icon-background \";\n\n\t\tif ( this.options.icon ) {\n\t\t\tif ( !this.icon ) {\n\t\t\t\tthis.icon = $( \"<span>\" );\n\t\t\t\tthis.iconSpace = $( \"<span> </span>\" );\n\t\t\t\tthis._addClass( this.iconSpace, \"ui-checkboxradio-icon-space\" );\n\t\t\t}\n\n\t\t\tif ( this.type === \"checkbox\" ) {\n\t\t\t\ttoAdd += checked ? \"ui-icon-check ui-state-checked\" : \"ui-icon-blank\";\n\t\t\t\tthis._removeClass( this.icon, null, checked ? \"ui-icon-blank\" : \"ui-icon-check\" );\n\t\t\t} else {\n\t\t\t\ttoAdd += \"ui-icon-blank\";\n\t\t\t}\n\t\t\tthis._addClass( this.icon, \"ui-checkboxradio-icon\", toAdd );\n\t\t\tif ( !checked ) {\n\t\t\t\tthis._removeClass( this.icon, null, \"ui-icon-check ui-state-checked\" );\n\t\t\t}\n\t\t\tthis.icon.prependTo( this.label ).after( this.iconSpace );\n\t\t} else if ( this.icon !== undefined ) {\n\t\t\tthis.icon.remove();\n\t\t\tthis.iconSpace.remove();\n\t\t\tdelete this.icon;\n\t\t}\n\t},\n\n\t_updateLabel: function() {\n\n\t\t// Remove the contents of the label ( minus the icon, icon space, and input )\n\t\tvar contents = this.label.contents().not( this.element[ 0 ] );\n\t\tif ( this.icon ) {\n\t\t\tcontents = contents.not( this.icon[ 0 ] );\n\t\t}\n\t\tif ( this.iconSpace ) {\n\t\t\tcontents = contents.not( this.iconSpace[ 0 ] );\n\t\t}\n\t\tcontents.remove();\n\n\t\tthis.label.append( this.options.label );\n\t},\n\n\trefresh: function() {\n\t\tvar checked = this.element[ 0 ].checked,\n\t\t\tisDisabled = this.element[ 0 ].disabled;\n\n\t\tthis._updateIcon( checked );\n\t\tthis._toggleClass( this.label, \"ui-checkboxradio-checked\", \"ui-state-active\", checked );\n\t\tif ( this.options.label !== null ) {\n\t\t\tthis._updateLabel();\n\t\t}\n\n\t\tif ( isDisabled !== this.options.disabled ) {\n\t\t\tthis._setOptions( { \"disabled\": isDisabled } );\n\t\t}\n\t}\n\n} ] );\n\nvar widgetsCheckboxradio = $.ui.checkboxradio;\n\n\n/*!\n * jQuery UI Button 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Button\n//>>group: Widgets\n//>>description: Enhances a form with themeable buttons.\n//>>docs: http://api.jqueryui.com/button/\n//>>demos: http://jqueryui.com/button/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/button.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.button\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<button>\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-button\": \"ui-corner-all\"\n\t\t},\n\t\tdisabled: null,\n\t\ticon: null,\n\t\ticonPosition: \"beginning\",\n\t\tlabel: null,\n\t\tshowLabel: true\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar disabled,\n\n\t\t\t// This is to support cases like in jQuery Mobile where the base widget does have\n\t\t\t// an implementation of _getCreateOptions\n\t\t\toptions = this._super() || {};\n\n\t\tthis.isInput = this.element.is( \"input\" );\n\n\t\tdisabled = this.element[ 0 ].disabled;\n\t\tif ( disabled != null ) {\n\t\t\toptions.disabled = disabled;\n\t\t}\n\n\t\tthis.originalLabel = this.isInput ? this.element.val() : this.element.html();\n\t\tif ( this.originalLabel ) {\n\t\t\toptions.label = this.originalLabel;\n\t\t}\n\n\t\treturn options;\n\t},\n\n\t_create: function() {\n\t\tif ( !this.option.showLabel & !this.options.icon ) {\n\t\t\tthis.options.showLabel = true;\n\t\t}\n\n\t\t// We have to check the option again here even though we did in _getCreateOptions,\n\t\t// because null may have been passed on init which would override what was set in\n\t\t// _getCreateOptions\n\t\tif ( this.options.disabled == null ) {\n\t\t\tthis.options.disabled = this.element[ 0 ].disabled || false;\n\t\t}\n\n\t\tthis.hasTitle = !!this.element.attr( \"title\" );\n\n\t\t// Check to see if the label needs to be set or if its already correct\n\t\tif ( this.options.label && this.options.label !== this.originalLabel ) {\n\t\t\tif ( this.isInput ) {\n\t\t\t\tthis.element.val( this.options.label );\n\t\t\t} else {\n\t\t\t\tthis.element.html( this.options.label );\n\t\t\t}\n\t\t}\n\t\tthis._addClass( \"ui-button\", \"ui-widget\" );\n\t\tthis._setOption( \"disabled\", this.options.disabled );\n\t\tthis._enhance();\n\n\t\tif ( this.element.is( \"a\" ) ) {\n\t\t\tthis._on( {\n\t\t\t\t\"keyup\": function( event ) {\n\t\t\t\t\tif ( event.keyCode === $.ui.keyCode.SPACE ) {\n\t\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\t\t// Support: PhantomJS <= 1.9, IE 8 Only\n\t\t\t\t\t\t// If a native click is available use it so we actually cause navigation\n\t\t\t\t\t\t// otherwise just trigger a click event\n\t\t\t\t\t\tif ( this.element[ 0 ].click ) {\n\t\t\t\t\t\t\tthis.element[ 0 ].click();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.element.trigger( \"click\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\t},\n\n\t_enhance: function() {\n\t\tif ( !this.element.is( \"button\" ) ) {\n\t\t\tthis.element.attr( \"role\", \"button\" );\n\t\t}\n\n\t\tif ( this.options.icon ) {\n\t\t\tthis._updateIcon( \"icon\", this.options.icon );\n\t\t\tthis._updateTooltip();\n\t\t}\n\t},\n\n\t_updateTooltip: function() {\n\t\tthis.title = this.element.attr( \"title\" );\n\n\t\tif ( !this.options.showLabel && !this.title ) {\n\t\t\tthis.element.attr( \"title\", this.options.label );\n\t\t}\n\t},\n\n\t_updateIcon: function( option, value ) {\n\t\tvar icon = option !== \"iconPosition\",\n\t\t\tposition = icon ? this.options.iconPosition : value,\n\t\t\tdisplayBlock = position === \"top\" || position === \"bottom\";\n\n\t\t// Create icon\n\t\tif ( !this.icon ) {\n\t\t\tthis.icon = $( \"<span>\" );\n\n\t\t\tthis._addClass( this.icon, \"ui-button-icon\", \"ui-icon\" );\n\n\t\t\tif ( !this.options.showLabel ) {\n\t\t\t\tthis._addClass( \"ui-button-icon-only\" );\n\t\t\t}\n\t\t} else if ( icon ) {\n\n\t\t\t// If we are updating the icon remove the old icon class\n\t\t\tthis._removeClass( this.icon, null, this.options.icon );\n\t\t}\n\n\t\t// If we are updating the icon add the new icon class\n\t\tif ( icon ) {\n\t\t\tthis._addClass( this.icon, null, value );\n\t\t}\n\n\t\tthis._attachIcon( position );\n\n\t\t// If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove\n\t\t// the iconSpace if there is one.\n\t\tif ( displayBlock ) {\n\t\t\tthis._addClass( this.icon, null, \"ui-widget-icon-block\" );\n\t\t\tif ( this.iconSpace ) {\n\t\t\t\tthis.iconSpace.remove();\n\t\t\t}\n\t\t} else {\n\n\t\t\t// Position is beginning or end so remove the ui-widget-icon-block class and add the\n\t\t\t// space if it does not exist\n\t\t\tif ( !this.iconSpace ) {\n\t\t\t\tthis.iconSpace = $( \"<span> </span>\" );\n\t\t\t\tthis._addClass( this.iconSpace, \"ui-button-icon-space\" );\n\t\t\t}\n\t\t\tthis._removeClass( this.icon, null, \"ui-wiget-icon-block\" );\n\t\t\tthis._attachIconSpace( position );\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tthis.element.removeAttr( \"role\" );\n\n\t\tif ( this.icon ) {\n\t\t\tthis.icon.remove();\n\t\t}\n\t\tif ( this.iconSpace ) {\n\t\t\tthis.iconSpace.remove();\n\t\t}\n\t\tif ( !this.hasTitle ) {\n\t\t\tthis.element.removeAttr( \"title\" );\n\t\t}\n\t},\n\n\t_attachIconSpace: function( iconPosition ) {\n\t\tthis.icon[ /^(?:end|bottom)/.test( iconPosition ) ? \"before\" : \"after\" ]( this.iconSpace );\n\t},\n\n\t_attachIcon: function( iconPosition ) {\n\t\tthis.element[ /^(?:end|bottom)/.test( iconPosition ) ? \"append\" : \"prepend\" ]( this.icon );\n\t},\n\n\t_setOptions: function( options ) {\n\t\tvar newShowLabel = options.showLabel === undefined ?\n\t\t\t\tthis.options.showLabel :\n\t\t\t\toptions.showLabel,\n\t\t\tnewIcon = options.icon === undefined ? this.options.icon : options.icon;\n\n\t\tif ( !newShowLabel && !newIcon ) {\n\t\t\toptions.showLabel = true;\n\t\t}\n\t\tthis._super( options );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"icon\" ) {\n\t\t\tif ( value ) {\n\t\t\t\tthis._updateIcon( key, value );\n\t\t\t} else if ( this.icon ) {\n\t\t\t\tthis.icon.remove();\n\t\t\t\tif ( this.iconSpace ) {\n\t\t\t\t\tthis.iconSpace.remove();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"iconPosition\" ) {\n\t\t\tthis._updateIcon( key, value );\n\t\t}\n\n\t\t// Make sure we can't end up with a button that has neither text nor icon\n\t\tif ( key === \"showLabel\" ) {\n\t\t\t\tthis._toggleClass( \"ui-button-icon-only\", null, !value );\n\t\t\t\tthis._updateTooltip();\n\t\t}\n\n\t\tif ( key === \"label\" ) {\n\t\t\tif ( this.isInput ) {\n\t\t\t\tthis.element.val( value );\n\t\t\t} else {\n\n\t\t\t\t// If there is an icon, append it, else nothing then append the value\n\t\t\t\t// this avoids removal of the icon when setting label text\n\t\t\t\tthis.element.html( value );\n\t\t\t\tif ( this.icon ) {\n\t\t\t\t\tthis._attachIcon( this.options.iconPosition );\n\t\t\t\t\tthis._attachIconSpace( this.options.iconPosition );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._toggleClass( null, \"ui-state-disabled\", value );\n\t\t\tthis.element[ 0 ].disabled = value;\n\t\t\tif ( value ) {\n\t\t\t\tthis.element.blur();\n\t\t\t}\n\t\t}\n\t},\n\n\trefresh: function() {\n\n\t\t// Make sure to only check disabled if its an element that supports this otherwise\n\t\t// check for the disabled class to determine state\n\t\tvar isDisabled = this.element.is( \"input, button\" ) ?\n\t\t\tthis.element[ 0 ].disabled : this.element.hasClass( \"ui-button-disabled\" );\n\n\t\tif ( isDisabled !== this.options.disabled ) {\n\t\t\tthis._setOptions( { disabled: isDisabled } );\n\t\t}\n\n\t\tthis._updateTooltip();\n\t}\n} );\n\n// DEPRECATED\nif ( $.uiBackCompat !== false ) {\n\n\t// Text and Icons options\n\t$.widget( \"ui.button\", $.ui.button, {\n\t\toptions: {\n\t\t\ttext: true,\n\t\t\ticons: {\n\t\t\t\tprimary: null,\n\t\t\t\tsecondary: null\n\t\t\t}\n\t\t},\n\n\t\t_create: function() {\n\t\t\tif ( this.options.showLabel && !this.options.text ) {\n\t\t\t\tthis.options.showLabel = this.options.text;\n\t\t\t}\n\t\t\tif ( !this.options.showLabel && this.options.text ) {\n\t\t\t\tthis.options.text = this.options.showLabel;\n\t\t\t}\n\t\t\tif ( !this.options.icon && ( this.options.icons.primary ||\n\t\t\t\t\tthis.options.icons.secondary ) ) {\n\t\t\t\tif ( this.options.icons.primary ) {\n\t\t\t\t\tthis.options.icon = this.options.icons.primary;\n\t\t\t\t} else {\n\t\t\t\t\tthis.options.icon = this.options.icons.secondary;\n\t\t\t\t\tthis.options.iconPosition = \"end\";\n\t\t\t\t}\n\t\t\t} else if ( this.options.icon ) {\n\t\t\t\tthis.options.icons.primary = this.options.icon;\n\t\t\t}\n\t\t\tthis._super();\n\t\t},\n\n\t\t_setOption: function( key, value ) {\n\t\t\tif ( key === \"text\" ) {\n\t\t\t\tthis._super( \"showLabel\", value );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( key === \"showLabel\" ) {\n\t\t\t\tthis.options.text = value;\n\t\t\t}\n\t\t\tif ( key === \"icon\" ) {\n\t\t\t\tthis.options.icons.primary = value;\n\t\t\t}\n\t\t\tif ( key === \"icons\" ) {\n\t\t\t\tif ( value.primary ) {\n\t\t\t\t\tthis._super( \"icon\", value.primary );\n\t\t\t\t\tthis._super( \"iconPosition\", \"beginning\" );\n\t\t\t\t} else if ( value.secondary ) {\n\t\t\t\t\tthis._super( \"icon\", value.secondary );\n\t\t\t\t\tthis._super( \"iconPosition\", \"end\" );\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._superApply( arguments );\n\t\t}\n\t} );\n\n\t$.fn.button = ( function( orig ) {\n\t\treturn function() {\n\t\t\tif ( !this.length || ( this.length && this[ 0 ].tagName !== \"INPUT\" ) ||\n\t\t\t\t\t( this.length && this[ 0 ].tagName === \"INPUT\" && (\n\t\t\t\t\t\tthis.attr( \"type\" ) !== \"checkbox\" && this.attr( \"type\" ) !== \"radio\"\n\t\t\t\t\t) ) ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t}\n\t\t\tif ( !$.ui.checkboxradio ) {\n\t\t\t\t$.error( \"Checkboxradio widget missing\" );\n\t\t\t}\n\t\t\tif ( arguments.length === 0 ) {\n\t\t\t\treturn this.checkboxradio( {\n\t\t\t\t\t\"icon\": false\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn this.checkboxradio.apply( this, arguments );\n\t\t};\n\t} )( $.fn.button );\n\n\t$.fn.buttonset = function() {\n\t\tif ( !$.ui.controlgroup ) {\n\t\t\t$.error( \"Controlgroup widget missing\" );\n\t\t}\n\t\tif ( arguments[ 0 ] === \"option\" && arguments[ 1 ] === \"items\" && arguments[ 2 ] ) {\n\t\t\treturn this.controlgroup.apply( this,\n\t\t\t\t[ arguments[ 0 ], \"items.button\", arguments[ 2 ] ] );\n\t\t}\n\t\tif ( arguments[ 0 ] === \"option\" && arguments[ 1 ] === \"items\" ) {\n\t\t\treturn this.controlgroup.apply( this, [ arguments[ 0 ], \"items.button\" ] );\n\t\t}\n\t\tif ( typeof arguments[ 0 ] === \"object\" && arguments[ 0 ].items ) {\n\t\t\targuments[ 0 ].items = {\n\t\t\t\tbutton: arguments[ 0 ].items\n\t\t\t};\n\t\t}\n\t\treturn this.controlgroup.apply( this, arguments );\n\t};\n}\n\nvar widgetsButton = $.ui.button;\n\n\n// jscs:disable maximumLineLength\n/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */\n/*!\n * jQuery UI Datepicker 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Datepicker\n//>>group: Widgets\n//>>description: Displays a calendar from an input or inline for selecting dates.\n//>>docs: http://api.jqueryui.com/datepicker/\n//>>demos: http://jqueryui.com/datepicker/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/datepicker.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.extend( $.ui, { datepicker: { version: \"1.12.1\" } } );\n\nvar datepicker_instActive;\n\nfunction datepicker_getZindex( elem ) {\n\tvar position, value;\n\twhile ( elem.length && elem[ 0 ] !== document ) {\n\n\t\t// Ignore z-index if position is set to a value where z-index is ignored by the browser\n\t\t// This makes behavior of this function consistent across browsers\n\t\t// WebKit always returns auto if the element is positioned\n\t\tposition = elem.css( \"position\" );\n\t\tif ( position === \"absolute\" || position === \"relative\" || position === \"fixed\" ) {\n\n\t\t\t// IE returns 0 when zIndex is not specified\n\t\t\t// other browsers return a string\n\t\t\t// we ignore the case of nested elements with an explicit value of 0\n\t\t\t// <div style=\"z-index: -10;\"><div style=\"z-index: 0;\"></div></div>\n\t\t\tvalue = parseInt( elem.css( \"zIndex\" ), 10 );\n\t\t\tif ( !isNaN( value ) && value !== 0 ) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\telem = elem.parent();\n\t}\n\n\treturn 0;\n}\n/* Date picker manager.\n   Use the singleton instance of this class, $.datepicker, to interact with the date picker.\n   Settings for (groups of) date pickers are maintained in an instance object,\n   allowing multiple different settings on the same page. */\n\nfunction Datepicker() {\n\tthis._curInst = null; // The current instance in use\n\tthis._keyEvent = false; // If the last event was a key event\n\tthis._disabledInputs = []; // List of date picker inputs that have been disabled\n\tthis._datepickerShowing = false; // True if the popup picker is showing , false if not\n\tthis._inDialog = false; // True if showing within a \"dialog\", false if not\n\tthis._mainDivId = \"ui-datepicker-div\"; // The ID of the main datepicker division\n\tthis._inlineClass = \"ui-datepicker-inline\"; // The name of the inline marker class\n\tthis._appendClass = \"ui-datepicker-append\"; // The name of the append marker class\n\tthis._triggerClass = \"ui-datepicker-trigger\"; // The name of the trigger marker class\n\tthis._dialogClass = \"ui-datepicker-dialog\"; // The name of the dialog marker class\n\tthis._disableClass = \"ui-datepicker-disabled\"; // The name of the disabled covering marker class\n\tthis._unselectableClass = \"ui-datepicker-unselectable\"; // The name of the unselectable cell marker class\n\tthis._currentClass = \"ui-datepicker-current-day\"; // The name of the current day marker class\n\tthis._dayOverClass = \"ui-datepicker-days-cell-over\"; // The name of the day hover marker class\n\tthis.regional = []; // Available regional settings, indexed by language code\n\tthis.regional[ \"\" ] = { // Default regional settings\n\t\tcloseText: \"Done\", // Display text for close link\n\t\tprevText: \"Prev\", // Display text for previous month link\n\t\tnextText: \"Next\", // Display text for next month link\n\t\tcurrentText: \"Today\", // Display text for current month link\n\t\tmonthNames: [ \"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\n\t\t\t\"July\",\"August\",\"September\",\"October\",\"November\",\"December\" ], // Names of months for drop-down and formatting\n\t\tmonthNamesShort: [ \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\" ], // For formatting\n\t\tdayNames: [ \"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\" ], // For formatting\n\t\tdayNamesShort: [ \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\" ], // For formatting\n\t\tdayNamesMin: [ \"Su\",\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\",\"Sa\" ], // Column headings for days starting at Sunday\n\t\tweekHeader: \"Wk\", // Column header for week of the year\n\t\tdateFormat: \"mm/dd/yy\", // See format options on parseDate\n\t\tfirstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...\n\t\tisRTL: false, // True if right-to-left language, false if left-to-right\n\t\tshowMonthAfterYear: false, // True if the year select precedes month, false for month then year\n\t\tyearSuffix: \"\" // Additional text to append to the year in the month headers\n\t};\n\tthis._defaults = { // Global defaults for all the date picker instances\n\t\tshowOn: \"focus\", // \"focus\" for popup on focus,\n\t\t\t// \"button\" for trigger button, or \"both\" for either\n\t\tshowAnim: \"fadeIn\", // Name of jQuery animation for popup\n\t\tshowOptions: {}, // Options for enhanced animations\n\t\tdefaultDate: null, // Used when field is blank: actual date,\n\t\t\t// +/-number for offset from today, null for today\n\t\tappendText: \"\", // Display text following the input box, e.g. showing the format\n\t\tbuttonText: \"...\", // Text for trigger button\n\t\tbuttonImage: \"\", // URL for trigger button image\n\t\tbuttonImageOnly: false, // True if the image appears alone, false if it appears on a button\n\t\thideIfNoPrevNext: false, // True to hide next/previous month links\n\t\t\t// if not applicable, false to just disable them\n\t\tnavigationAsDateFormat: false, // True if date formatting applied to prev/today/next links\n\t\tgotoCurrent: false, // True if today link goes back to current selection instead\n\t\tchangeMonth: false, // True if month can be selected directly, false if only prev/next\n\t\tchangeYear: false, // True if year can be selected directly, false if only prev/next\n\t\tyearRange: \"c-10:c+10\", // Range of years to display in drop-down,\n\t\t\t// either relative to today's year (-nn:+nn), relative to currently displayed year\n\t\t\t// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)\n\t\tshowOtherMonths: false, // True to show dates in other months, false to leave blank\n\t\tselectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable\n\t\tshowWeek: false, // True to show week of the year, false to not show it\n\t\tcalculateWeek: this.iso8601Week, // How to calculate the week of the year,\n\t\t\t// takes a Date and returns the number of the week for it\n\t\tshortYearCutoff: \"+10\", // Short year values < this are in the current century,\n\t\t\t// > this are in the previous century,\n\t\t\t// string value starting with \"+\" for current year + value\n\t\tminDate: null, // The earliest selectable date, or null for no limit\n\t\tmaxDate: null, // The latest selectable date, or null for no limit\n\t\tduration: \"fast\", // Duration of display/closure\n\t\tbeforeShowDay: null, // Function that takes a date and returns an array with\n\t\t\t// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or \"\",\n\t\t\t// [2] = cell title (optional), e.g. $.datepicker.noWeekends\n\t\tbeforeShow: null, // Function that takes an input field and\n\t\t\t// returns a set of custom settings for the date picker\n\t\tonSelect: null, // Define a callback function when a date is selected\n\t\tonChangeMonthYear: null, // Define a callback function when the month or year is changed\n\t\tonClose: null, // Define a callback function when the datepicker is closed\n\t\tnumberOfMonths: 1, // Number of months to show at a time\n\t\tshowCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)\n\t\tstepMonths: 1, // Number of months to step back/forward\n\t\tstepBigMonths: 12, // Number of months to step back/forward for the big links\n\t\taltField: \"\", // Selector for an alternate field to store selected dates into\n\t\taltFormat: \"\", // The date format to use for the alternate field\n\t\tconstrainInput: true, // The input is constrained by the current date format\n\t\tshowButtonPanel: false, // True to show button panel, false to not show it\n\t\tautoSize: false, // True to size the input for the date format, false to leave as is\n\t\tdisabled: false // The initial disabled state\n\t};\n\t$.extend( this._defaults, this.regional[ \"\" ] );\n\tthis.regional.en = $.extend( true, {}, this.regional[ \"\" ] );\n\tthis.regional[ \"en-US\" ] = $.extend( true, {}, this.regional.en );\n\tthis.dpDiv = datepicker_bindHover( $( \"<div id='\" + this._mainDivId + \"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>\" ) );\n}\n\n$.extend( Datepicker.prototype, {\n\t/* Class name added to elements to indicate already configured with a date picker. */\n\tmarkerClassName: \"hasDatepicker\",\n\n\t//Keep track of the maximum number of rows displayed (see #7043)\n\tmaxRows: 4,\n\n\t// TODO rename to \"widget\" when switching to widget factory\n\t_widgetDatepicker: function() {\n\t\treturn this.dpDiv;\n\t},\n\n\t/* Override the default settings for all instances of the date picker.\n\t * @param  settings  object - the new settings to use as defaults (anonymous object)\n\t * @return the manager object\n\t */\n\tsetDefaults: function( settings ) {\n\t\tdatepicker_extendRemove( this._defaults, settings || {} );\n\t\treturn this;\n\t},\n\n\t/* Attach the date picker to a jQuery selection.\n\t * @param  target\telement - the target input field or division or span\n\t * @param  settings  object - the new settings to use for this date picker instance (anonymous)\n\t */\n\t_attachDatepicker: function( target, settings ) {\n\t\tvar nodeName, inline, inst;\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\tinline = ( nodeName === \"div\" || nodeName === \"span\" );\n\t\tif ( !target.id ) {\n\t\t\tthis.uuid += 1;\n\t\t\ttarget.id = \"dp\" + this.uuid;\n\t\t}\n\t\tinst = this._newInst( $( target ), inline );\n\t\tinst.settings = $.extend( {}, settings || {} );\n\t\tif ( nodeName === \"input\" ) {\n\t\t\tthis._connectDatepicker( target, inst );\n\t\t} else if ( inline ) {\n\t\t\tthis._inlineDatepicker( target, inst );\n\t\t}\n\t},\n\n\t/* Create a new instance object. */\n\t_newInst: function( target, inline ) {\n\t\tvar id = target[ 0 ].id.replace( /([^A-Za-z0-9_\\-])/g, \"\\\\\\\\$1\" ); // escape jQuery meta chars\n\t\treturn { id: id, input: target, // associated target\n\t\t\tselectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection\n\t\t\tdrawMonth: 0, drawYear: 0, // month being drawn\n\t\t\tinline: inline, // is datepicker inline or not\n\t\t\tdpDiv: ( !inline ? this.dpDiv : // presentation div\n\t\t\tdatepicker_bindHover( $( \"<div class='\" + this._inlineClass + \" ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>\" ) ) ) };\n\t},\n\n\t/* Attach the date picker to an input field. */\n\t_connectDatepicker: function( target, inst ) {\n\t\tvar input = $( target );\n\t\tinst.append = $( [] );\n\t\tinst.trigger = $( [] );\n\t\tif ( input.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\t\tthis._attachments( input, inst );\n\t\tinput.addClass( this.markerClassName ).on( \"keydown\", this._doKeyDown ).\n\t\t\ton( \"keypress\", this._doKeyPress ).on( \"keyup\", this._doKeyUp );\n\t\tthis._autoSize( inst );\n\t\t$.data( target, \"datepicker\", inst );\n\n\t\t//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)\n\t\tif ( inst.settings.disabled ) {\n\t\t\tthis._disableDatepicker( target );\n\t\t}\n\t},\n\n\t/* Make attachments based on settings. */\n\t_attachments: function( input, inst ) {\n\t\tvar showOn, buttonText, buttonImage,\n\t\t\tappendText = this._get( inst, \"appendText\" ),\n\t\t\tisRTL = this._get( inst, \"isRTL\" );\n\n\t\tif ( inst.append ) {\n\t\t\tinst.append.remove();\n\t\t}\n\t\tif ( appendText ) {\n\t\t\tinst.append = $( \"<span class='\" + this._appendClass + \"'>\" + appendText + \"</span>\" );\n\t\t\tinput[ isRTL ? \"before\" : \"after\" ]( inst.append );\n\t\t}\n\n\t\tinput.off( \"focus\", this._showDatepicker );\n\n\t\tif ( inst.trigger ) {\n\t\t\tinst.trigger.remove();\n\t\t}\n\n\t\tshowOn = this._get( inst, \"showOn\" );\n\t\tif ( showOn === \"focus\" || showOn === \"both\" ) { // pop-up date picker when in the marked field\n\t\t\tinput.on( \"focus\", this._showDatepicker );\n\t\t}\n\t\tif ( showOn === \"button\" || showOn === \"both\" ) { // pop-up date picker when button clicked\n\t\t\tbuttonText = this._get( inst, \"buttonText\" );\n\t\t\tbuttonImage = this._get( inst, \"buttonImage\" );\n\t\t\tinst.trigger = $( this._get( inst, \"buttonImageOnly\" ) ?\n\t\t\t\t$( \"<img/>\" ).addClass( this._triggerClass ).\n\t\t\t\t\tattr( { src: buttonImage, alt: buttonText, title: buttonText } ) :\n\t\t\t\t$( \"<button type='button'></button>\" ).addClass( this._triggerClass ).\n\t\t\t\t\thtml( !buttonImage ? buttonText : $( \"<img/>\" ).attr(\n\t\t\t\t\t{ src:buttonImage, alt:buttonText, title:buttonText } ) ) );\n\t\t\tinput[ isRTL ? \"before\" : \"after\" ]( inst.trigger );\n\t\t\tinst.trigger.on( \"click\", function() {\n\t\t\t\tif ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) {\n\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t} else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) {\n\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t\t$.datepicker._showDatepicker( input[ 0 ] );\n\t\t\t\t} else {\n\t\t\t\t\t$.datepicker._showDatepicker( input[ 0 ] );\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t} );\n\t\t}\n\t},\n\n\t/* Apply the maximum length for the date format. */\n\t_autoSize: function( inst ) {\n\t\tif ( this._get( inst, \"autoSize\" ) && !inst.inline ) {\n\t\t\tvar findMax, max, maxI, i,\n\t\t\t\tdate = new Date( 2009, 12 - 1, 20 ), // Ensure double digits\n\t\t\t\tdateFormat = this._get( inst, \"dateFormat\" );\n\n\t\t\tif ( dateFormat.match( /[DM]/ ) ) {\n\t\t\t\tfindMax = function( names ) {\n\t\t\t\t\tmax = 0;\n\t\t\t\t\tmaxI = 0;\n\t\t\t\t\tfor ( i = 0; i < names.length; i++ ) {\n\t\t\t\t\t\tif ( names[ i ].length > max ) {\n\t\t\t\t\t\t\tmax = names[ i ].length;\n\t\t\t\t\t\t\tmaxI = i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn maxI;\n\t\t\t\t};\n\t\t\t\tdate.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ?\n\t\t\t\t\t\"monthNames\" : \"monthNamesShort\" ) ) ) );\n\t\t\t\tdate.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ?\n\t\t\t\t\t\"dayNames\" : \"dayNamesShort\" ) ) ) + 20 - date.getDay() );\n\t\t\t}\n\t\t\tinst.input.attr( \"size\", this._formatDate( inst, date ).length );\n\t\t}\n\t},\n\n\t/* Attach an inline date picker to a div. */\n\t_inlineDatepicker: function( target, inst ) {\n\t\tvar divSpan = $( target );\n\t\tif ( divSpan.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\t\tdivSpan.addClass( this.markerClassName ).append( inst.dpDiv );\n\t\t$.data( target, \"datepicker\", inst );\n\t\tthis._setDate( inst, this._getDefaultDate( inst ), true );\n\t\tthis._updateDatepicker( inst );\n\t\tthis._updateAlternate( inst );\n\n\t\t//If disabled option is true, disable the datepicker before showing it (see ticket #5665)\n\t\tif ( inst.settings.disabled ) {\n\t\t\tthis._disableDatepicker( target );\n\t\t}\n\n\t\t// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements\n\t\t// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height\n\t\tinst.dpDiv.css( \"display\", \"block\" );\n\t},\n\n\t/* Pop-up the date picker in a \"dialog\" box.\n\t * @param  input element - ignored\n\t * @param  date\tstring or Date - the initial date to display\n\t * @param  onSelect  function - the function to call when a date is selected\n\t * @param  settings  object - update the dialog date picker instance's settings (anonymous object)\n\t * @param  pos int[2] - coordinates for the dialog's position within the screen or\n\t *\t\t\t\t\tevent - with x/y coordinates or\n\t *\t\t\t\t\tleave empty for default (screen centre)\n\t * @return the manager object\n\t */\n\t_dialogDatepicker: function( input, date, onSelect, settings, pos ) {\n\t\tvar id, browserWidth, browserHeight, scrollX, scrollY,\n\t\t\tinst = this._dialogInst; // internal instance\n\n\t\tif ( !inst ) {\n\t\t\tthis.uuid += 1;\n\t\t\tid = \"dp\" + this.uuid;\n\t\t\tthis._dialogInput = $( \"<input type='text' id='\" + id +\n\t\t\t\t\"' style='position: absolute; top: -100px; width: 0px;'/>\" );\n\t\t\tthis._dialogInput.on( \"keydown\", this._doKeyDown );\n\t\t\t$( \"body\" ).append( this._dialogInput );\n\t\t\tinst = this._dialogInst = this._newInst( this._dialogInput, false );\n\t\t\tinst.settings = {};\n\t\t\t$.data( this._dialogInput[ 0 ], \"datepicker\", inst );\n\t\t}\n\t\tdatepicker_extendRemove( inst.settings, settings || {} );\n\t\tdate = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date );\n\t\tthis._dialogInput.val( date );\n\n\t\tthis._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null );\n\t\tif ( !this._pos ) {\n\t\t\tbrowserWidth = document.documentElement.clientWidth;\n\t\t\tbrowserHeight = document.documentElement.clientHeight;\n\t\t\tscrollX = document.documentElement.scrollLeft || document.body.scrollLeft;\n\t\t\tscrollY = document.documentElement.scrollTop || document.body.scrollTop;\n\t\t\tthis._pos = // should use actual width/height below\n\t\t\t\t[ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ];\n\t\t}\n\n\t\t// Move input on screen for focus, but hidden behind dialog\n\t\tthis._dialogInput.css( \"left\", ( this._pos[ 0 ] + 20 ) + \"px\" ).css( \"top\", this._pos[ 1 ] + \"px\" );\n\t\tinst.settings.onSelect = onSelect;\n\t\tthis._inDialog = true;\n\t\tthis.dpDiv.addClass( this._dialogClass );\n\t\tthis._showDatepicker( this._dialogInput[ 0 ] );\n\t\tif ( $.blockUI ) {\n\t\t\t$.blockUI( this.dpDiv );\n\t\t}\n\t\t$.data( this._dialogInput[ 0 ], \"datepicker\", inst );\n\t\treturn this;\n\t},\n\n\t/* Detach a datepicker from its control.\n\t * @param  target\telement - the target input field or division or span\n\t */\n\t_destroyDatepicker: function( target ) {\n\t\tvar nodeName,\n\t\t\t$target = $( target ),\n\t\t\tinst = $.data( target, \"datepicker\" );\n\n\t\tif ( !$target.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\t$.removeData( target, \"datepicker\" );\n\t\tif ( nodeName === \"input\" ) {\n\t\t\tinst.append.remove();\n\t\t\tinst.trigger.remove();\n\t\t\t$target.removeClass( this.markerClassName ).\n\t\t\t\toff( \"focus\", this._showDatepicker ).\n\t\t\t\toff( \"keydown\", this._doKeyDown ).\n\t\t\t\toff( \"keypress\", this._doKeyPress ).\n\t\t\t\toff( \"keyup\", this._doKeyUp );\n\t\t} else if ( nodeName === \"div\" || nodeName === \"span\" ) {\n\t\t\t$target.removeClass( this.markerClassName ).empty();\n\t\t}\n\n\t\tif ( datepicker_instActive === inst ) {\n\t\t\tdatepicker_instActive = null;\n\t\t}\n\t},\n\n\t/* Enable the date picker to a jQuery selection.\n\t * @param  target\telement - the target input field or division or span\n\t */\n\t_enableDatepicker: function( target ) {\n\t\tvar nodeName, inline,\n\t\t\t$target = $( target ),\n\t\t\tinst = $.data( target, \"datepicker\" );\n\n\t\tif ( !$target.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\tif ( nodeName === \"input\" ) {\n\t\t\ttarget.disabled = false;\n\t\t\tinst.trigger.filter( \"button\" ).\n\t\t\t\teach( function() { this.disabled = false; } ).end().\n\t\t\t\tfilter( \"img\" ).css( { opacity: \"1.0\", cursor: \"\" } );\n\t\t} else if ( nodeName === \"div\" || nodeName === \"span\" ) {\n\t\t\tinline = $target.children( \".\" + this._inlineClass );\n\t\t\tinline.children().removeClass( \"ui-state-disabled\" );\n\t\t\tinline.find( \"select.ui-datepicker-month, select.ui-datepicker-year\" ).\n\t\t\t\tprop( \"disabled\", false );\n\t\t}\n\t\tthis._disabledInputs = $.map( this._disabledInputs,\n\t\t\tfunction( value ) { return ( value === target ? null : value ); } ); // delete entry\n\t},\n\n\t/* Disable the date picker to a jQuery selection.\n\t * @param  target\telement - the target input field or division or span\n\t */\n\t_disableDatepicker: function( target ) {\n\t\tvar nodeName, inline,\n\t\t\t$target = $( target ),\n\t\t\tinst = $.data( target, \"datepicker\" );\n\n\t\tif ( !$target.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\tif ( nodeName === \"input\" ) {\n\t\t\ttarget.disabled = true;\n\t\t\tinst.trigger.filter( \"button\" ).\n\t\t\t\teach( function() { this.disabled = true; } ).end().\n\t\t\t\tfilter( \"img\" ).css( { opacity: \"0.5\", cursor: \"default\" } );\n\t\t} else if ( nodeName === \"div\" || nodeName === \"span\" ) {\n\t\t\tinline = $target.children( \".\" + this._inlineClass );\n\t\t\tinline.children().addClass( \"ui-state-disabled\" );\n\t\t\tinline.find( \"select.ui-datepicker-month, select.ui-datepicker-year\" ).\n\t\t\t\tprop( \"disabled\", true );\n\t\t}\n\t\tthis._disabledInputs = $.map( this._disabledInputs,\n\t\t\tfunction( value ) { return ( value === target ? null : value ); } ); // delete entry\n\t\tthis._disabledInputs[ this._disabledInputs.length ] = target;\n\t},\n\n\t/* Is the first field in a jQuery collection disabled as a datepicker?\n\t * @param  target\telement - the target input field or division or span\n\t * @return boolean - true if disabled, false if enabled\n\t */\n\t_isDisabledDatepicker: function( target ) {\n\t\tif ( !target ) {\n\t\t\treturn false;\n\t\t}\n\t\tfor ( var i = 0; i < this._disabledInputs.length; i++ ) {\n\t\t\tif ( this._disabledInputs[ i ] === target ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t},\n\n\t/* Retrieve the instance data for the target control.\n\t * @param  target  element - the target input field or division or span\n\t * @return  object - the associated instance data\n\t * @throws  error if a jQuery problem getting data\n\t */\n\t_getInst: function( target ) {\n\t\ttry {\n\t\t\treturn $.data( target, \"datepicker\" );\n\t\t}\n\t\tcatch ( err ) {\n\t\t\tthrow \"Missing instance data for this datepicker\";\n\t\t}\n\t},\n\n\t/* Update or retrieve the settings for a date picker attached to an input field or division.\n\t * @param  target  element - the target input field or division or span\n\t * @param  name\tobject - the new settings to update or\n\t *\t\t\t\tstring - the name of the setting to change or retrieve,\n\t *\t\t\t\twhen retrieving also \"all\" for all instance settings or\n\t *\t\t\t\t\"defaults\" for all global defaults\n\t * @param  value   any - the new value for the setting\n\t *\t\t\t\t(omit if above is an object or to retrieve a value)\n\t */\n\t_optionDatepicker: function( target, name, value ) {\n\t\tvar settings, date, minDate, maxDate,\n\t\t\tinst = this._getInst( target );\n\n\t\tif ( arguments.length === 2 && typeof name === \"string\" ) {\n\t\t\treturn ( name === \"defaults\" ? $.extend( {}, $.datepicker._defaults ) :\n\t\t\t\t( inst ? ( name === \"all\" ? $.extend( {}, inst.settings ) :\n\t\t\t\tthis._get( inst, name ) ) : null ) );\n\t\t}\n\n\t\tsettings = name || {};\n\t\tif ( typeof name === \"string\" ) {\n\t\t\tsettings = {};\n\t\t\tsettings[ name ] = value;\n\t\t}\n\n\t\tif ( inst ) {\n\t\t\tif ( this._curInst === inst ) {\n\t\t\t\tthis._hideDatepicker();\n\t\t\t}\n\n\t\t\tdate = this._getDateDatepicker( target, true );\n\t\t\tminDate = this._getMinMaxDate( inst, \"min\" );\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" );\n\t\t\tdatepicker_extendRemove( inst.settings, settings );\n\n\t\t\t// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided\n\t\t\tif ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) {\n\t\t\t\tinst.settings.minDate = this._formatDate( inst, minDate );\n\t\t\t}\n\t\t\tif ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) {\n\t\t\t\tinst.settings.maxDate = this._formatDate( inst, maxDate );\n\t\t\t}\n\t\t\tif ( \"disabled\" in settings ) {\n\t\t\t\tif ( settings.disabled ) {\n\t\t\t\t\tthis._disableDatepicker( target );\n\t\t\t\t} else {\n\t\t\t\t\tthis._enableDatepicker( target );\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._attachments( $( target ), inst );\n\t\t\tthis._autoSize( inst );\n\t\t\tthis._setDate( inst, date );\n\t\t\tthis._updateAlternate( inst );\n\t\t\tthis._updateDatepicker( inst );\n\t\t}\n\t},\n\n\t// Change method deprecated\n\t_changeDatepicker: function( target, name, value ) {\n\t\tthis._optionDatepicker( target, name, value );\n\t},\n\n\t/* Redraw the date picker attached to an input field or division.\n\t * @param  target  element - the target input field or division or span\n\t */\n\t_refreshDatepicker: function( target ) {\n\t\tvar inst = this._getInst( target );\n\t\tif ( inst ) {\n\t\t\tthis._updateDatepicker( inst );\n\t\t}\n\t},\n\n\t/* Set the dates for a jQuery selection.\n\t * @param  target element - the target input field or division or span\n\t * @param  date\tDate - the new date\n\t */\n\t_setDateDatepicker: function( target, date ) {\n\t\tvar inst = this._getInst( target );\n\t\tif ( inst ) {\n\t\t\tthis._setDate( inst, date );\n\t\t\tthis._updateDatepicker( inst );\n\t\t\tthis._updateAlternate( inst );\n\t\t}\n\t},\n\n\t/* Get the date(s) for the first entry in a jQuery selection.\n\t * @param  target element - the target input field or division or span\n\t * @param  noDefault boolean - true if no default date is to be used\n\t * @return Date - the current date\n\t */\n\t_getDateDatepicker: function( target, noDefault ) {\n\t\tvar inst = this._getInst( target );\n\t\tif ( inst && !inst.inline ) {\n\t\t\tthis._setDateFromField( inst, noDefault );\n\t\t}\n\t\treturn ( inst ? this._getDate( inst ) : null );\n\t},\n\n\t/* Handle keystrokes. */\n\t_doKeyDown: function( event ) {\n\t\tvar onSelect, dateStr, sel,\n\t\t\tinst = $.datepicker._getInst( event.target ),\n\t\t\thandled = true,\n\t\t\tisRTL = inst.dpDiv.is( \".ui-datepicker-rtl\" );\n\n\t\tinst._keyEvent = true;\n\t\tif ( $.datepicker._datepickerShowing ) {\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase 9: $.datepicker._hideDatepicker();\n\t\t\t\t\t\thandled = false;\n\t\t\t\t\t\tbreak; // hide on tab out\n\t\t\t\tcase 13: sel = $( \"td.\" + $.datepicker._dayOverClass + \":not(.\" +\n\t\t\t\t\t\t\t\t\t$.datepicker._currentClass + \")\", inst.dpDiv );\n\t\t\t\t\t\tif ( sel[ 0 ] ) {\n\t\t\t\t\t\t\t$.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tonSelect = $.datepicker._get( inst, \"onSelect\" );\n\t\t\t\t\t\tif ( onSelect ) {\n\t\t\t\t\t\t\tdateStr = $.datepicker._formatDate( inst );\n\n\t\t\t\t\t\t\t// Trigger custom callback\n\t\t\t\t\t\t\tonSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false; // don't submit the form\n\t\t\t\tcase 27: $.datepicker._hideDatepicker();\n\t\t\t\t\t\tbreak; // hide on escape\n\t\t\t\tcase 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\tbreak; // previous month/year on page up/+ ctrl\n\t\t\t\tcase 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\tbreak; // next month/year on page down/+ ctrl\n\t\t\t\tcase 35: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._clearDate( event.target );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // clear on ctrl or command +end\n\t\t\t\tcase 36: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._gotoToday( event.target );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // current on ctrl or command +home\n\t\t\t\tcase 37: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\n\t\t\t\t\t\t// -1 day on ctrl or command +left\n\t\t\t\t\t\tif ( event.originalEvent.altKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// next month/year on alt +left on Mac\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase 38: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, -7, \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // -1 week on ctrl or command +up\n\t\t\t\tcase 39: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\n\t\t\t\t\t\t// +1 day on ctrl or command +right\n\t\t\t\t\t\tif ( event.originalEvent.altKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// next month/year on alt +right\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase 40: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, +7, \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // +1 week on ctrl or command +down\n\t\t\t\tdefault: handled = false;\n\t\t\t}\n\t\t} else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home\n\t\t\t$.datepicker._showDatepicker( this );\n\t\t} else {\n\t\t\thandled = false;\n\t\t}\n\n\t\tif ( handled ) {\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\t\t}\n\t},\n\n\t/* Filter entered characters - based on date format. */\n\t_doKeyPress: function( event ) {\n\t\tvar chars, chr,\n\t\t\tinst = $.datepicker._getInst( event.target );\n\n\t\tif ( $.datepicker._get( inst, \"constrainInput\" ) ) {\n\t\t\tchars = $.datepicker._possibleChars( $.datepicker._get( inst, \"dateFormat\" ) );\n\t\t\tchr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode );\n\t\t\treturn event.ctrlKey || event.metaKey || ( chr < \" \" || !chars || chars.indexOf( chr ) > -1 );\n\t\t}\n\t},\n\n\t/* Synchronise manual entry and field/alternate field. */\n\t_doKeyUp: function( event ) {\n\t\tvar date,\n\t\t\tinst = $.datepicker._getInst( event.target );\n\n\t\tif ( inst.input.val() !== inst.lastVal ) {\n\t\t\ttry {\n\t\t\t\tdate = $.datepicker.parseDate( $.datepicker._get( inst, \"dateFormat\" ),\n\t\t\t\t\t( inst.input ? inst.input.val() : null ),\n\t\t\t\t\t$.datepicker._getFormatConfig( inst ) );\n\n\t\t\t\tif ( date ) { // only if valid\n\t\t\t\t\t$.datepicker._setDateFromField( inst );\n\t\t\t\t\t$.datepicker._updateAlternate( inst );\n\t\t\t\t\t$.datepicker._updateDatepicker( inst );\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch ( err ) {\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t},\n\n\t/* Pop-up the date picker for a given input field.\n\t * If false returned from beforeShow event handler do not show.\n\t * @param  input  element - the input field attached to the date picker or\n\t *\t\t\t\t\tevent - if triggered by focus\n\t */\n\t_showDatepicker: function( input ) {\n\t\tinput = input.target || input;\n\t\tif ( input.nodeName.toLowerCase() !== \"input\" ) { // find from button/image trigger\n\t\t\tinput = $( \"input\", input.parentNode )[ 0 ];\n\t\t}\n\n\t\tif ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here\n\t\t\treturn;\n\t\t}\n\n\t\tvar inst, beforeShow, beforeShowSettings, isFixed,\n\t\t\toffset, showAnim, duration;\n\n\t\tinst = $.datepicker._getInst( input );\n\t\tif ( $.datepicker._curInst && $.datepicker._curInst !== inst ) {\n\t\t\t$.datepicker._curInst.dpDiv.stop( true, true );\n\t\t\tif ( inst && $.datepicker._datepickerShowing ) {\n\t\t\t\t$.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] );\n\t\t\t}\n\t\t}\n\n\t\tbeforeShow = $.datepicker._get( inst, \"beforeShow\" );\n\t\tbeforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {};\n\t\tif ( beforeShowSettings === false ) {\n\t\t\treturn;\n\t\t}\n\t\tdatepicker_extendRemove( inst.settings, beforeShowSettings );\n\n\t\tinst.lastVal = null;\n\t\t$.datepicker._lastInput = input;\n\t\t$.datepicker._setDateFromField( inst );\n\n\t\tif ( $.datepicker._inDialog ) { // hide cursor\n\t\t\tinput.value = \"\";\n\t\t}\n\t\tif ( !$.datepicker._pos ) { // position below input\n\t\t\t$.datepicker._pos = $.datepicker._findPos( input );\n\t\t\t$.datepicker._pos[ 1 ] += input.offsetHeight; // add the height\n\t\t}\n\n\t\tisFixed = false;\n\t\t$( input ).parents().each( function() {\n\t\t\tisFixed |= $( this ).css( \"position\" ) === \"fixed\";\n\t\t\treturn !isFixed;\n\t\t} );\n\n\t\toffset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] };\n\t\t$.datepicker._pos = null;\n\n\t\t//to avoid flashes on Firefox\n\t\tinst.dpDiv.empty();\n\n\t\t// determine sizing offscreen\n\t\tinst.dpDiv.css( { position: \"absolute\", display: \"block\", top: \"-1000px\" } );\n\t\t$.datepicker._updateDatepicker( inst );\n\n\t\t// fix width for dynamic number of date pickers\n\t\t// and adjust position before showing\n\t\toffset = $.datepicker._checkOffset( inst, offset, isFixed );\n\t\tinst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ?\n\t\t\t\"static\" : ( isFixed ? \"fixed\" : \"absolute\" ) ), display: \"none\",\n\t\t\tleft: offset.left + \"px\", top: offset.top + \"px\" } );\n\n\t\tif ( !inst.inline ) {\n\t\t\tshowAnim = $.datepicker._get( inst, \"showAnim\" );\n\t\t\tduration = $.datepicker._get( inst, \"duration\" );\n\t\t\tinst.dpDiv.css( \"z-index\", datepicker_getZindex( $( input ) ) + 1 );\n\t\t\t$.datepicker._datepickerShowing = true;\n\n\t\t\tif ( $.effects && $.effects.effect[ showAnim ] ) {\n\t\t\t\tinst.dpDiv.show( showAnim, $.datepicker._get( inst, \"showOptions\" ), duration );\n\t\t\t} else {\n\t\t\t\tinst.dpDiv[ showAnim || \"show\" ]( showAnim ? duration : null );\n\t\t\t}\n\n\t\t\tif ( $.datepicker._shouldFocusInput( inst ) ) {\n\t\t\t\tinst.input.trigger( \"focus\" );\n\t\t\t}\n\n\t\t\t$.datepicker._curInst = inst;\n\t\t}\n\t},\n\n\t/* Generate the date picker content. */\n\t_updateDatepicker: function( inst ) {\n\t\tthis.maxRows = 4; //Reset the max number of rows being displayed (see #7043)\n\t\tdatepicker_instActive = inst; // for delegate hover events\n\t\tinst.dpDiv.empty().append( this._generateHTML( inst ) );\n\t\tthis._attachHandlers( inst );\n\n\t\tvar origyearshtml,\n\t\t\tnumMonths = this._getNumberOfMonths( inst ),\n\t\t\tcols = numMonths[ 1 ],\n\t\t\twidth = 17,\n\t\t\tactiveCell = inst.dpDiv.find( \".\" + this._dayOverClass + \" a\" );\n\n\t\tif ( activeCell.length > 0 ) {\n\t\t\tdatepicker_handleMouseover.apply( activeCell.get( 0 ) );\n\t\t}\n\n\t\tinst.dpDiv.removeClass( \"ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4\" ).width( \"\" );\n\t\tif ( cols > 1 ) {\n\t\t\tinst.dpDiv.addClass( \"ui-datepicker-multi-\" + cols ).css( \"width\", ( width * cols ) + \"em\" );\n\t\t}\n\t\tinst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? \"add\" : \"remove\" ) +\n\t\t\t\"Class\" ]( \"ui-datepicker-multi\" );\n\t\tinst.dpDiv[ ( this._get( inst, \"isRTL\" ) ? \"add\" : \"remove\" ) +\n\t\t\t\"Class\" ]( \"ui-datepicker-rtl\" );\n\n\t\tif ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {\n\t\t\tinst.input.trigger( \"focus\" );\n\t\t}\n\n\t\t// Deffered render of the years select (to avoid flashes on Firefox)\n\t\tif ( inst.yearshtml ) {\n\t\t\torigyearshtml = inst.yearshtml;\n\t\t\tsetTimeout( function() {\n\n\t\t\t\t//assure that inst.yearshtml didn't change.\n\t\t\t\tif ( origyearshtml === inst.yearshtml && inst.yearshtml ) {\n\t\t\t\t\tinst.dpDiv.find( \"select.ui-datepicker-year:first\" ).replaceWith( inst.yearshtml );\n\t\t\t\t}\n\t\t\t\torigyearshtml = inst.yearshtml = null;\n\t\t\t}, 0 );\n\t\t}\n\t},\n\n\t// #6694 - don't focus the input if it's already focused\n\t// this breaks the change event in IE\n\t// Support: IE and jQuery <1.9\n\t_shouldFocusInput: function( inst ) {\n\t\treturn inst.input && inst.input.is( \":visible\" ) && !inst.input.is( \":disabled\" ) && !inst.input.is( \":focus\" );\n\t},\n\n\t/* Check positioning to remain on screen. */\n\t_checkOffset: function( inst, offset, isFixed ) {\n\t\tvar dpWidth = inst.dpDiv.outerWidth(),\n\t\t\tdpHeight = inst.dpDiv.outerHeight(),\n\t\t\tinputWidth = inst.input ? inst.input.outerWidth() : 0,\n\t\t\tinputHeight = inst.input ? inst.input.outerHeight() : 0,\n\t\t\tviewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ),\n\t\t\tviewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() );\n\n\t\toffset.left -= ( this._get( inst, \"isRTL\" ) ? ( dpWidth - inputWidth ) : 0 );\n\t\toffset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0;\n\t\toffset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0;\n\n\t\t// Now check if datepicker is showing outside window viewport - move to a better place if so.\n\t\toffset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ?\n\t\t\tMath.abs( offset.left + dpWidth - viewWidth ) : 0 );\n\t\toffset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ?\n\t\t\tMath.abs( dpHeight + inputHeight ) : 0 );\n\n\t\treturn offset;\n\t},\n\n\t/* Find an object's position on the screen. */\n\t_findPos: function( obj ) {\n\t\tvar position,\n\t\t\tinst = this._getInst( obj ),\n\t\t\tisRTL = this._get( inst, \"isRTL\" );\n\n\t\twhile ( obj && ( obj.type === \"hidden\" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) {\n\t\t\tobj = obj[ isRTL ? \"previousSibling\" : \"nextSibling\" ];\n\t\t}\n\n\t\tposition = $( obj ).offset();\n\t\treturn [ position.left, position.top ];\n\t},\n\n\t/* Hide the date picker from view.\n\t * @param  input  element - the input field attached to the date picker\n\t */\n\t_hideDatepicker: function( input ) {\n\t\tvar showAnim, duration, postProcess, onClose,\n\t\t\tinst = this._curInst;\n\n\t\tif ( !inst || ( input && inst !== $.data( input, \"datepicker\" ) ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this._datepickerShowing ) {\n\t\t\tshowAnim = this._get( inst, \"showAnim\" );\n\t\t\tduration = this._get( inst, \"duration\" );\n\t\t\tpostProcess = function() {\n\t\t\t\t$.datepicker._tidyDialog( inst );\n\t\t\t};\n\n\t\t\t// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed\n\t\t\tif ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {\n\t\t\t\tinst.dpDiv.hide( showAnim, $.datepicker._get( inst, \"showOptions\" ), duration, postProcess );\n\t\t\t} else {\n\t\t\t\tinst.dpDiv[ ( showAnim === \"slideDown\" ? \"slideUp\" :\n\t\t\t\t\t( showAnim === \"fadeIn\" ? \"fadeOut\" : \"hide\" ) ) ]( ( showAnim ? duration : null ), postProcess );\n\t\t\t}\n\n\t\t\tif ( !showAnim ) {\n\t\t\t\tpostProcess();\n\t\t\t}\n\t\t\tthis._datepickerShowing = false;\n\n\t\t\tonClose = this._get( inst, \"onClose\" );\n\t\t\tif ( onClose ) {\n\t\t\t\tonClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : \"\" ), inst ] );\n\t\t\t}\n\n\t\t\tthis._lastInput = null;\n\t\t\tif ( this._inDialog ) {\n\t\t\t\tthis._dialogInput.css( { position: \"absolute\", left: \"0\", top: \"-100px\" } );\n\t\t\t\tif ( $.blockUI ) {\n\t\t\t\t\t$.unblockUI();\n\t\t\t\t\t$( \"body\" ).append( this.dpDiv );\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._inDialog = false;\n\t\t}\n\t},\n\n\t/* Tidy up after a dialog display. */\n\t_tidyDialog: function( inst ) {\n\t\tinst.dpDiv.removeClass( this._dialogClass ).off( \".ui-datepicker-calendar\" );\n\t},\n\n\t/* Close date picker if clicked elsewhere. */\n\t_checkExternalClick: function( event ) {\n\t\tif ( !$.datepicker._curInst ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar $target = $( event.target ),\n\t\t\tinst = $.datepicker._getInst( $target[ 0 ] );\n\n\t\tif ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId &&\n\t\t\t\t$target.parents( \"#\" + $.datepicker._mainDivId ).length === 0 &&\n\t\t\t\t!$target.hasClass( $.datepicker.markerClassName ) &&\n\t\t\t\t!$target.closest( \".\" + $.datepicker._triggerClass ).length &&\n\t\t\t\t$.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) ||\n\t\t\t( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) {\n\t\t\t\t$.datepicker._hideDatepicker();\n\t\t}\n\t},\n\n\t/* Adjust one of the date sub-fields. */\n\t_adjustDate: function( id, offset, period ) {\n\t\tvar target = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tif ( this._isDisabledDatepicker( target[ 0 ] ) ) {\n\t\t\treturn;\n\t\t}\n\t\tthis._adjustInstDate( inst, offset +\n\t\t\t( period === \"M\" ? this._get( inst, \"showCurrentAtPos\" ) : 0 ), // undo positioning\n\t\t\tperiod );\n\t\tthis._updateDatepicker( inst );\n\t},\n\n\t/* Action for current link. */\n\t_gotoToday: function( id ) {\n\t\tvar date,\n\t\t\ttarget = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tif ( this._get( inst, \"gotoCurrent\" ) && inst.currentDay ) {\n\t\t\tinst.selectedDay = inst.currentDay;\n\t\t\tinst.drawMonth = inst.selectedMonth = inst.currentMonth;\n\t\t\tinst.drawYear = inst.selectedYear = inst.currentYear;\n\t\t} else {\n\t\t\tdate = new Date();\n\t\t\tinst.selectedDay = date.getDate();\n\t\t\tinst.drawMonth = inst.selectedMonth = date.getMonth();\n\t\t\tinst.drawYear = inst.selectedYear = date.getFullYear();\n\t\t}\n\t\tthis._notifyChange( inst );\n\t\tthis._adjustDate( target );\n\t},\n\n\t/* Action for selecting a new month/year. */\n\t_selectMonthYear: function( id, select, period ) {\n\t\tvar target = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tinst[ \"selected\" + ( period === \"M\" ? \"Month\" : \"Year\" ) ] =\n\t\tinst[ \"draw\" + ( period === \"M\" ? \"Month\" : \"Year\" ) ] =\n\t\t\tparseInt( select.options[ select.selectedIndex ].value, 10 );\n\n\t\tthis._notifyChange( inst );\n\t\tthis._adjustDate( target );\n\t},\n\n\t/* Action for selecting a day. */\n\t_selectDay: function( id, month, year, td ) {\n\t\tvar inst,\n\t\t\ttarget = $( id );\n\n\t\tif ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tinst = this._getInst( target[ 0 ] );\n\t\tinst.selectedDay = inst.currentDay = $( \"a\", td ).html();\n\t\tinst.selectedMonth = inst.currentMonth = month;\n\t\tinst.selectedYear = inst.currentYear = year;\n\t\tthis._selectDate( id, this._formatDate( inst,\n\t\t\tinst.currentDay, inst.currentMonth, inst.currentYear ) );\n\t},\n\n\t/* Erase the input field and hide the date picker. */\n\t_clearDate: function( id ) {\n\t\tvar target = $( id );\n\t\tthis._selectDate( target, \"\" );\n\t},\n\n\t/* Update the input field with the selected date. */\n\t_selectDate: function( id, dateStr ) {\n\t\tvar onSelect,\n\t\t\ttarget = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tdateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) );\n\t\tif ( inst.input ) {\n\t\t\tinst.input.val( dateStr );\n\t\t}\n\t\tthis._updateAlternate( inst );\n\n\t\tonSelect = this._get( inst, \"onSelect\" );\n\t\tif ( onSelect ) {\n\t\t\tonSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );  // trigger custom callback\n\t\t} else if ( inst.input ) {\n\t\t\tinst.input.trigger( \"change\" ); // fire the change event\n\t\t}\n\n\t\tif ( inst.inline ) {\n\t\t\tthis._updateDatepicker( inst );\n\t\t} else {\n\t\t\tthis._hideDatepicker();\n\t\t\tthis._lastInput = inst.input[ 0 ];\n\t\t\tif ( typeof( inst.input[ 0 ] ) !== \"object\" ) {\n\t\t\t\tinst.input.trigger( \"focus\" ); // restore focus\n\t\t\t}\n\t\t\tthis._lastInput = null;\n\t\t}\n\t},\n\n\t/* Update any alternate field to synchronise with the main field. */\n\t_updateAlternate: function( inst ) {\n\t\tvar altFormat, date, dateStr,\n\t\t\taltField = this._get( inst, \"altField\" );\n\n\t\tif ( altField ) { // update alternate field too\n\t\t\taltFormat = this._get( inst, \"altFormat\" ) || this._get( inst, \"dateFormat\" );\n\t\t\tdate = this._getDate( inst );\n\t\t\tdateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) );\n\t\t\t$( altField ).val( dateStr );\n\t\t}\n\t},\n\n\t/* Set as beforeShowDay function to prevent selection of weekends.\n\t * @param  date  Date - the date to customise\n\t * @return [boolean, string] - is this date selectable?, what is its CSS class?\n\t */\n\tnoWeekends: function( date ) {\n\t\tvar day = date.getDay();\n\t\treturn [ ( day > 0 && day < 6 ), \"\" ];\n\t},\n\n\t/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.\n\t * @param  date  Date - the date to get the week for\n\t * @return  number - the number of the week within the year that contains this date\n\t */\n\tiso8601Week: function( date ) {\n\t\tvar time,\n\t\t\tcheckDate = new Date( date.getTime() );\n\n\t\t// Find Thursday of this week starting on Monday\n\t\tcheckDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) );\n\n\t\ttime = checkDate.getTime();\n\t\tcheckDate.setMonth( 0 ); // Compare with Jan 1\n\t\tcheckDate.setDate( 1 );\n\t\treturn Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1;\n\t},\n\n\t/* Parse a string value into a date object.\n\t * See formatDate below for the possible formats.\n\t *\n\t * @param  format string - the expected format of the date\n\t * @param  value string - the date in the above format\n\t * @param  settings Object - attributes include:\n\t *\t\t\t\t\tshortYearCutoff  number - the cutoff year for determining the century (optional)\n\t *\t\t\t\t\tdayNamesShort\tstring[7] - abbreviated names of the days from Sunday (optional)\n\t *\t\t\t\t\tdayNames\t\tstring[7] - names of the days from Sunday (optional)\n\t *\t\t\t\t\tmonthNamesShort string[12] - abbreviated names of the months (optional)\n\t *\t\t\t\t\tmonthNames\t\tstring[12] - names of the months (optional)\n\t * @return  Date - the extracted date value or null if value is blank\n\t */\n\tparseDate: function( format, value, settings ) {\n\t\tif ( format == null || value == null ) {\n\t\t\tthrow \"Invalid arguments\";\n\t\t}\n\n\t\tvalue = ( typeof value === \"object\" ? value.toString() : value + \"\" );\n\t\tif ( value === \"\" ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar iFormat, dim, extra,\n\t\t\tiValue = 0,\n\t\t\tshortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff,\n\t\t\tshortYearCutoff = ( typeof shortYearCutoffTemp !== \"string\" ? shortYearCutoffTemp :\n\t\t\t\tnew Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ),\n\t\t\tdayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,\n\t\t\tdayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,\n\t\t\tmonthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,\n\t\t\tmonthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,\n\t\t\tyear = -1,\n\t\t\tmonth = -1,\n\t\t\tday = -1,\n\t\t\tdoy = -1,\n\t\t\tliteral = false,\n\t\t\tdate,\n\n\t\t\t// Check whether a format character is doubled\n\t\t\tlookAhead = function( match ) {\n\t\t\t\tvar matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tiFormat++;\n\t\t\t\t}\n\t\t\t\treturn matches;\n\t\t\t},\n\n\t\t\t// Extract a number from the string value\n\t\t\tgetNumber = function( match ) {\n\t\t\t\tvar isDoubled = lookAhead( match ),\n\t\t\t\t\tsize = ( match === \"@\" ? 14 : ( match === \"!\" ? 20 :\n\t\t\t\t\t( match === \"y\" && isDoubled ? 4 : ( match === \"o\" ? 3 : 2 ) ) ) ),\n\t\t\t\t\tminSize = ( match === \"y\" ? size : 1 ),\n\t\t\t\t\tdigits = new RegExp( \"^\\\\d{\" + minSize + \",\" + size + \"}\" ),\n\t\t\t\t\tnum = value.substring( iValue ).match( digits );\n\t\t\t\tif ( !num ) {\n\t\t\t\t\tthrow \"Missing number at position \" + iValue;\n\t\t\t\t}\n\t\t\t\tiValue += num[ 0 ].length;\n\t\t\t\treturn parseInt( num[ 0 ], 10 );\n\t\t\t},\n\n\t\t\t// Extract a name from the string value and convert to an index\n\t\t\tgetName = function( match, shortNames, longNames ) {\n\t\t\t\tvar index = -1,\n\t\t\t\t\tnames = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) {\n\t\t\t\t\t\treturn [ [ k, v ] ];\n\t\t\t\t\t} ).sort( function( a, b ) {\n\t\t\t\t\t\treturn -( a[ 1 ].length - b[ 1 ].length );\n\t\t\t\t\t} );\n\n\t\t\t\t$.each( names, function( i, pair ) {\n\t\t\t\t\tvar name = pair[ 1 ];\n\t\t\t\t\tif ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) {\n\t\t\t\t\t\tindex = pair[ 0 ];\n\t\t\t\t\t\tiValue += name.length;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\tif ( index !== -1 ) {\n\t\t\t\t\treturn index + 1;\n\t\t\t\t} else {\n\t\t\t\t\tthrow \"Unknown name at position \" + iValue;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// Confirm that a literal character matches the string value\n\t\t\tcheckLiteral = function() {\n\t\t\t\tif ( value.charAt( iValue ) !== format.charAt( iFormat ) ) {\n\t\t\t\t\tthrow \"Unexpected literal at position \" + iValue;\n\t\t\t\t}\n\t\t\t\tiValue++;\n\t\t\t};\n\n\t\tfor ( iFormat = 0; iFormat < format.length; iFormat++ ) {\n\t\t\tif ( literal ) {\n\t\t\t\tif ( format.charAt( iFormat ) === \"'\" && !lookAhead( \"'\" ) ) {\n\t\t\t\t\tliteral = false;\n\t\t\t\t} else {\n\t\t\t\t\tcheckLiteral();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch ( format.charAt( iFormat ) ) {\n\t\t\t\t\tcase \"d\":\n\t\t\t\t\t\tday = getNumber( \"d\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"D\":\n\t\t\t\t\t\tgetName( \"D\", dayNamesShort, dayNames );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"o\":\n\t\t\t\t\t\tdoy = getNumber( \"o\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"m\":\n\t\t\t\t\t\tmonth = getNumber( \"m\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"M\":\n\t\t\t\t\t\tmonth = getName( \"M\", monthNamesShort, monthNames );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"y\":\n\t\t\t\t\t\tyear = getNumber( \"y\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"@\":\n\t\t\t\t\t\tdate = new Date( getNumber( \"@\" ) );\n\t\t\t\t\t\tyear = date.getFullYear();\n\t\t\t\t\t\tmonth = date.getMonth() + 1;\n\t\t\t\t\t\tday = date.getDate();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"!\":\n\t\t\t\t\t\tdate = new Date( ( getNumber( \"!\" ) - this._ticksTo1970 ) / 10000 );\n\t\t\t\t\t\tyear = date.getFullYear();\n\t\t\t\t\t\tmonth = date.getMonth() + 1;\n\t\t\t\t\t\tday = date.getDate();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\tif ( lookAhead( \"'\" ) ) {\n\t\t\t\t\t\t\tcheckLiteral();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tliteral = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcheckLiteral();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( iValue < value.length ) {\n\t\t\textra = value.substr( iValue );\n\t\t\tif ( !/^\\s+/.test( extra ) ) {\n\t\t\t\tthrow \"Extra/unparsed characters found in date: \" + extra;\n\t\t\t}\n\t\t}\n\n\t\tif ( year === -1 ) {\n\t\t\tyear = new Date().getFullYear();\n\t\t} else if ( year < 100 ) {\n\t\t\tyear += new Date().getFullYear() - new Date().getFullYear() % 100 +\n\t\t\t\t( year <= shortYearCutoff ? 0 : -100 );\n\t\t}\n\n\t\tif ( doy > -1 ) {\n\t\t\tmonth = 1;\n\t\t\tday = doy;\n\t\t\tdo {\n\t\t\t\tdim = this._getDaysInMonth( year, month - 1 );\n\t\t\t\tif ( day <= dim ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmonth++;\n\t\t\t\tday -= dim;\n\t\t\t} while ( true );\n\t\t}\n\n\t\tdate = this._daylightSavingAdjust( new Date( year, month - 1, day ) );\n\t\tif ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) {\n\t\t\tthrow \"Invalid date\"; // E.g. 31/02/00\n\t\t}\n\t\treturn date;\n\t},\n\n\t/* Standard date formats. */\n\tATOM: \"yy-mm-dd\", // RFC 3339 (ISO 8601)\n\tCOOKIE: \"D, dd M yy\",\n\tISO_8601: \"yy-mm-dd\",\n\tRFC_822: \"D, d M y\",\n\tRFC_850: \"DD, dd-M-y\",\n\tRFC_1036: \"D, d M y\",\n\tRFC_1123: \"D, d M yy\",\n\tRFC_2822: \"D, d M yy\",\n\tRSS: \"D, d M y\", // RFC 822\n\tTICKS: \"!\",\n\tTIMESTAMP: \"@\",\n\tW3C: \"yy-mm-dd\", // ISO 8601\n\n\t_ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) +\n\t\tMath.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ),\n\n\t/* Format a date object into a string value.\n\t * The format can be combinations of the following:\n\t * d  - day of month (no leading zero)\n\t * dd - day of month (two digit)\n\t * o  - day of year (no leading zeros)\n\t * oo - day of year (three digit)\n\t * D  - day name short\n\t * DD - day name long\n\t * m  - month of year (no leading zero)\n\t * mm - month of year (two digit)\n\t * M  - month name short\n\t * MM - month name long\n\t * y  - year (two digit)\n\t * yy - year (four digit)\n\t * @ - Unix timestamp (ms since 01/01/1970)\n\t * ! - Windows ticks (100ns since 01/01/0001)\n\t * \"...\" - literal text\n\t * '' - single quote\n\t *\n\t * @param  format string - the desired format of the date\n\t * @param  date Date - the date value to format\n\t * @param  settings Object - attributes include:\n\t *\t\t\t\t\tdayNamesShort\tstring[7] - abbreviated names of the days from Sunday (optional)\n\t *\t\t\t\t\tdayNames\t\tstring[7] - names of the days from Sunday (optional)\n\t *\t\t\t\t\tmonthNamesShort string[12] - abbreviated names of the months (optional)\n\t *\t\t\t\t\tmonthNames\t\tstring[12] - names of the months (optional)\n\t * @return  string - the date in the above format\n\t */\n\tformatDate: function( format, date, settings ) {\n\t\tif ( !date ) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tvar iFormat,\n\t\t\tdayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,\n\t\t\tdayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,\n\t\t\tmonthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,\n\t\t\tmonthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,\n\n\t\t\t// Check whether a format character is doubled\n\t\t\tlookAhead = function( match ) {\n\t\t\t\tvar matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tiFormat++;\n\t\t\t\t}\n\t\t\t\treturn matches;\n\t\t\t},\n\n\t\t\t// Format a number, with leading zero if necessary\n\t\t\tformatNumber = function( match, value, len ) {\n\t\t\t\tvar num = \"\" + value;\n\t\t\t\tif ( lookAhead( match ) ) {\n\t\t\t\t\twhile ( num.length < len ) {\n\t\t\t\t\t\tnum = \"0\" + num;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn num;\n\t\t\t},\n\n\t\t\t// Format a name, short or long as requested\n\t\t\tformatName = function( match, value, shortNames, longNames ) {\n\t\t\t\treturn ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] );\n\t\t\t},\n\t\t\toutput = \"\",\n\t\t\tliteral = false;\n\n\t\tif ( date ) {\n\t\t\tfor ( iFormat = 0; iFormat < format.length; iFormat++ ) {\n\t\t\t\tif ( literal ) {\n\t\t\t\t\tif ( format.charAt( iFormat ) === \"'\" && !lookAhead( \"'\" ) ) {\n\t\t\t\t\t\tliteral = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\toutput += format.charAt( iFormat );\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tswitch ( format.charAt( iFormat ) ) {\n\t\t\t\t\t\tcase \"d\":\n\t\t\t\t\t\t\toutput += formatNumber( \"d\", date.getDate(), 2 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"D\":\n\t\t\t\t\t\t\toutput += formatName( \"D\", date.getDay(), dayNamesShort, dayNames );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"o\":\n\t\t\t\t\t\t\toutput += formatNumber( \"o\",\n\t\t\t\t\t\t\t\tMath.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"m\":\n\t\t\t\t\t\t\toutput += formatNumber( \"m\", date.getMonth() + 1, 2 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"M\":\n\t\t\t\t\t\t\toutput += formatName( \"M\", date.getMonth(), monthNamesShort, monthNames );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"y\":\n\t\t\t\t\t\t\toutput += ( lookAhead( \"y\" ) ? date.getFullYear() :\n\t\t\t\t\t\t\t\t( date.getFullYear() % 100 < 10 ? \"0\" : \"\" ) + date.getFullYear() % 100 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"@\":\n\t\t\t\t\t\t\toutput += date.getTime();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"!\":\n\t\t\t\t\t\t\toutput += date.getTime() * 10000 + this._ticksTo1970;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\t\tif ( lookAhead( \"'\" ) ) {\n\t\t\t\t\t\t\t\toutput += \"'\";\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tliteral = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\toutput += format.charAt( iFormat );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t},\n\n\t/* Extract all possible characters from the date format. */\n\t_possibleChars: function( format ) {\n\t\tvar iFormat,\n\t\t\tchars = \"\",\n\t\t\tliteral = false,\n\n\t\t\t// Check whether a format character is doubled\n\t\t\tlookAhead = function( match ) {\n\t\t\t\tvar matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tiFormat++;\n\t\t\t\t}\n\t\t\t\treturn matches;\n\t\t\t};\n\n\t\tfor ( iFormat = 0; iFormat < format.length; iFormat++ ) {\n\t\t\tif ( literal ) {\n\t\t\t\tif ( format.charAt( iFormat ) === \"'\" && !lookAhead( \"'\" ) ) {\n\t\t\t\t\tliteral = false;\n\t\t\t\t} else {\n\t\t\t\t\tchars += format.charAt( iFormat );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch ( format.charAt( iFormat ) ) {\n\t\t\t\t\tcase \"d\": case \"m\": case \"y\": case \"@\":\n\t\t\t\t\t\tchars += \"0123456789\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"D\": case \"M\":\n\t\t\t\t\t\treturn null; // Accept anything\n\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\tif ( lookAhead( \"'\" ) ) {\n\t\t\t\t\t\t\tchars += \"'\";\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tliteral = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tchars += format.charAt( iFormat );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn chars;\n\t},\n\n\t/* Get a setting value, defaulting if necessary. */\n\t_get: function( inst, name ) {\n\t\treturn inst.settings[ name ] !== undefined ?\n\t\t\tinst.settings[ name ] : this._defaults[ name ];\n\t},\n\n\t/* Parse existing date and initialise date picker. */\n\t_setDateFromField: function( inst, noDefault ) {\n\t\tif ( inst.input.val() === inst.lastVal ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar dateFormat = this._get( inst, \"dateFormat\" ),\n\t\t\tdates = inst.lastVal = inst.input ? inst.input.val() : null,\n\t\t\tdefaultDate = this._getDefaultDate( inst ),\n\t\t\tdate = defaultDate,\n\t\t\tsettings = this._getFormatConfig( inst );\n\n\t\ttry {\n\t\t\tdate = this.parseDate( dateFormat, dates, settings ) || defaultDate;\n\t\t} catch ( event ) {\n\t\t\tdates = ( noDefault ? \"\" : dates );\n\t\t}\n\t\tinst.selectedDay = date.getDate();\n\t\tinst.drawMonth = inst.selectedMonth = date.getMonth();\n\t\tinst.drawYear = inst.selectedYear = date.getFullYear();\n\t\tinst.currentDay = ( dates ? date.getDate() : 0 );\n\t\tinst.currentMonth = ( dates ? date.getMonth() : 0 );\n\t\tinst.currentYear = ( dates ? date.getFullYear() : 0 );\n\t\tthis._adjustInstDate( inst );\n\t},\n\n\t/* Retrieve the default date shown on opening. */\n\t_getDefaultDate: function( inst ) {\n\t\treturn this._restrictMinMax( inst,\n\t\t\tthis._determineDate( inst, this._get( inst, \"defaultDate\" ), new Date() ) );\n\t},\n\n\t/* A date may be specified as an exact value or a relative one. */\n\t_determineDate: function( inst, date, defaultDate ) {\n\t\tvar offsetNumeric = function( offset ) {\n\t\t\t\tvar date = new Date();\n\t\t\t\tdate.setDate( date.getDate() + offset );\n\t\t\t\treturn date;\n\t\t\t},\n\t\t\toffsetString = function( offset ) {\n\t\t\t\ttry {\n\t\t\t\t\treturn $.datepicker.parseDate( $.datepicker._get( inst, \"dateFormat\" ),\n\t\t\t\t\t\toffset, $.datepicker._getFormatConfig( inst ) );\n\t\t\t\t}\n\t\t\t\tcatch ( e ) {\n\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\n\t\t\t\tvar date = ( offset.toLowerCase().match( /^c/ ) ?\n\t\t\t\t\t$.datepicker._getDate( inst ) : null ) || new Date(),\n\t\t\t\t\tyear = date.getFullYear(),\n\t\t\t\t\tmonth = date.getMonth(),\n\t\t\t\t\tday = date.getDate(),\n\t\t\t\t\tpattern = /([+\\-]?[0-9]+)\\s*(d|D|w|W|m|M|y|Y)?/g,\n\t\t\t\t\tmatches = pattern.exec( offset );\n\n\t\t\t\twhile ( matches ) {\n\t\t\t\t\tswitch ( matches[ 2 ] || \"d\" ) {\n\t\t\t\t\t\tcase \"d\" : case \"D\" :\n\t\t\t\t\t\t\tday += parseInt( matches[ 1 ], 10 ); break;\n\t\t\t\t\t\tcase \"w\" : case \"W\" :\n\t\t\t\t\t\t\tday += parseInt( matches[ 1 ], 10 ) * 7; break;\n\t\t\t\t\t\tcase \"m\" : case \"M\" :\n\t\t\t\t\t\t\tmonth += parseInt( matches[ 1 ], 10 );\n\t\t\t\t\t\t\tday = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"y\": case \"Y\" :\n\t\t\t\t\t\t\tyear += parseInt( matches[ 1 ], 10 );\n\t\t\t\t\t\t\tday = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tmatches = pattern.exec( offset );\n\t\t\t\t}\n\t\t\t\treturn new Date( year, month, day );\n\t\t\t},\n\t\t\tnewDate = ( date == null || date === \"\" ? defaultDate : ( typeof date === \"string\" ? offsetString( date ) :\n\t\t\t\t( typeof date === \"number\" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) );\n\n\t\tnewDate = ( newDate && newDate.toString() === \"Invalid Date\" ? defaultDate : newDate );\n\t\tif ( newDate ) {\n\t\t\tnewDate.setHours( 0 );\n\t\t\tnewDate.setMinutes( 0 );\n\t\t\tnewDate.setSeconds( 0 );\n\t\t\tnewDate.setMilliseconds( 0 );\n\t\t}\n\t\treturn this._daylightSavingAdjust( newDate );\n\t},\n\n\t/* Handle switch to/from daylight saving.\n\t * Hours may be non-zero on daylight saving cut-over:\n\t * > 12 when midnight changeover, but then cannot generate\n\t * midnight datetime, so jump to 1AM, otherwise reset.\n\t * @param  date  (Date) the date to check\n\t * @return  (Date) the corrected date\n\t */\n\t_daylightSavingAdjust: function( date ) {\n\t\tif ( !date ) {\n\t\t\treturn null;\n\t\t}\n\t\tdate.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 );\n\t\treturn date;\n\t},\n\n\t/* Set the date(s) directly. */\n\t_setDate: function( inst, date, noChange ) {\n\t\tvar clear = !date,\n\t\t\torigMonth = inst.selectedMonth,\n\t\t\torigYear = inst.selectedYear,\n\t\t\tnewDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) );\n\n\t\tinst.selectedDay = inst.currentDay = newDate.getDate();\n\t\tinst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();\n\t\tinst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();\n\t\tif ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) {\n\t\t\tthis._notifyChange( inst );\n\t\t}\n\t\tthis._adjustInstDate( inst );\n\t\tif ( inst.input ) {\n\t\t\tinst.input.val( clear ? \"\" : this._formatDate( inst ) );\n\t\t}\n\t},\n\n\t/* Retrieve the date(s) directly. */\n\t_getDate: function( inst ) {\n\t\tvar startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === \"\" ) ? null :\n\t\t\tthis._daylightSavingAdjust( new Date(\n\t\t\tinst.currentYear, inst.currentMonth, inst.currentDay ) ) );\n\t\t\treturn startDate;\n\t},\n\n\t/* Attach the onxxx handlers.  These are declared statically so\n\t * they work with static code transformers like Caja.\n\t */\n\t_attachHandlers: function( inst ) {\n\t\tvar stepMonths = this._get( inst, \"stepMonths\" ),\n\t\t\tid = \"#\" + inst.id.replace( /\\\\\\\\/g, \"\\\\\" );\n\t\tinst.dpDiv.find( \"[data-handler]\" ).map( function() {\n\t\t\tvar handler = {\n\t\t\t\tprev: function() {\n\t\t\t\t\t$.datepicker._adjustDate( id, -stepMonths, \"M\" );\n\t\t\t\t},\n\t\t\t\tnext: function() {\n\t\t\t\t\t$.datepicker._adjustDate( id, +stepMonths, \"M\" );\n\t\t\t\t},\n\t\t\t\thide: function() {\n\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t},\n\t\t\t\ttoday: function() {\n\t\t\t\t\t$.datepicker._gotoToday( id );\n\t\t\t\t},\n\t\t\t\tselectDay: function() {\n\t\t\t\t\t$.datepicker._selectDay( id, +this.getAttribute( \"data-month\" ), +this.getAttribute( \"data-year\" ), this );\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t\tselectMonth: function() {\n\t\t\t\t\t$.datepicker._selectMonthYear( id, this, \"M\" );\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t\tselectYear: function() {\n\t\t\t\t\t$.datepicker._selectMonthYear( id, this, \"Y\" );\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t};\n\t\t\t$( this ).on( this.getAttribute( \"data-event\" ), handler[ this.getAttribute( \"data-handler\" ) ] );\n\t\t} );\n\t},\n\n\t/* Generate the HTML for the current state of the date picker. */\n\t_generateHTML: function( inst ) {\n\t\tvar maxDraw, prevText, prev, nextText, next, currentText, gotoDate,\n\t\t\tcontrols, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,\n\t\t\tmonthNames, monthNamesShort, beforeShowDay, showOtherMonths,\n\t\t\tselectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,\n\t\t\tcornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,\n\t\t\tprintDate, dRow, tbody, daySettings, otherMonth, unselectable,\n\t\t\ttempDate = new Date(),\n\t\t\ttoday = this._daylightSavingAdjust(\n\t\t\t\tnew Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time\n\t\t\tisRTL = this._get( inst, \"isRTL\" ),\n\t\t\tshowButtonPanel = this._get( inst, \"showButtonPanel\" ),\n\t\t\thideIfNoPrevNext = this._get( inst, \"hideIfNoPrevNext\" ),\n\t\t\tnavigationAsDateFormat = this._get( inst, \"navigationAsDateFormat\" ),\n\t\t\tnumMonths = this._getNumberOfMonths( inst ),\n\t\t\tshowCurrentAtPos = this._get( inst, \"showCurrentAtPos\" ),\n\t\t\tstepMonths = this._get( inst, \"stepMonths\" ),\n\t\t\tisMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ),\n\t\t\tcurrentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) :\n\t\t\t\tnew Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ),\n\t\t\tminDate = this._getMinMaxDate( inst, \"min\" ),\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" ),\n\t\t\tdrawMonth = inst.drawMonth - showCurrentAtPos,\n\t\t\tdrawYear = inst.drawYear;\n\n\t\tif ( drawMonth < 0 ) {\n\t\t\tdrawMonth += 12;\n\t\t\tdrawYear--;\n\t\t}\n\t\tif ( maxDate ) {\n\t\t\tmaxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(),\n\t\t\t\tmaxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) );\n\t\t\tmaxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw );\n\t\t\twhile ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) {\n\t\t\t\tdrawMonth--;\n\t\t\t\tif ( drawMonth < 0 ) {\n\t\t\t\t\tdrawMonth = 11;\n\t\t\t\t\tdrawYear--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tinst.drawMonth = drawMonth;\n\t\tinst.drawYear = drawYear;\n\n\t\tprevText = this._get( inst, \"prevText\" );\n\t\tprevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText,\n\t\t\tthis._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ),\n\t\t\tthis._getFormatConfig( inst ) ) );\n\n\t\tprev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ?\n\t\t\t\"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'\" +\n\t\t\t\" title='\" + prevText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"e\" : \"w\" ) + \"'>\" + prevText + \"</span></a>\" :\n\t\t\t( hideIfNoPrevNext ? \"\" : \"<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='\" + prevText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"e\" : \"w\" ) + \"'>\" + prevText + \"</span></a>\" ) );\n\n\t\tnextText = this._get( inst, \"nextText\" );\n\t\tnextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText,\n\t\t\tthis._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ),\n\t\t\tthis._getFormatConfig( inst ) ) );\n\n\t\tnext = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ?\n\t\t\t\"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'\" +\n\t\t\t\" title='\" + nextText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"w\" : \"e\" ) + \"'>\" + nextText + \"</span></a>\" :\n\t\t\t( hideIfNoPrevNext ? \"\" : \"<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='\" + nextText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"w\" : \"e\" ) + \"'>\" + nextText + \"</span></a>\" ) );\n\n\t\tcurrentText = this._get( inst, \"currentText\" );\n\t\tgotoDate = ( this._get( inst, \"gotoCurrent\" ) && inst.currentDay ? currentDate : today );\n\t\tcurrentText = ( !navigationAsDateFormat ? currentText :\n\t\t\tthis.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) );\n\n\t\tcontrols = ( !inst.inline ? \"<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>\" +\n\t\t\tthis._get( inst, \"closeText\" ) + \"</button>\" : \"\" );\n\n\t\tbuttonPanel = ( showButtonPanel ) ? \"<div class='ui-datepicker-buttonpane ui-widget-content'>\" + ( isRTL ? controls : \"\" ) +\n\t\t\t( this._isInRange( inst, gotoDate ) ? \"<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'\" +\n\t\t\t\">\" + currentText + \"</button>\" : \"\" ) + ( isRTL ? \"\" : controls ) + \"</div>\" : \"\";\n\n\t\tfirstDay = parseInt( this._get( inst, \"firstDay\" ), 10 );\n\t\tfirstDay = ( isNaN( firstDay ) ? 0 : firstDay );\n\n\t\tshowWeek = this._get( inst, \"showWeek\" );\n\t\tdayNames = this._get( inst, \"dayNames\" );\n\t\tdayNamesMin = this._get( inst, \"dayNamesMin\" );\n\t\tmonthNames = this._get( inst, \"monthNames\" );\n\t\tmonthNamesShort = this._get( inst, \"monthNamesShort\" );\n\t\tbeforeShowDay = this._get( inst, \"beforeShowDay\" );\n\t\tshowOtherMonths = this._get( inst, \"showOtherMonths\" );\n\t\tselectOtherMonths = this._get( inst, \"selectOtherMonths\" );\n\t\tdefaultDate = this._getDefaultDate( inst );\n\t\thtml = \"\";\n\n\t\tfor ( row = 0; row < numMonths[ 0 ]; row++ ) {\n\t\t\tgroup = \"\";\n\t\t\tthis.maxRows = 4;\n\t\t\tfor ( col = 0; col < numMonths[ 1 ]; col++ ) {\n\t\t\t\tselectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) );\n\t\t\t\tcornerClass = \" ui-corner-all\";\n\t\t\t\tcalender = \"\";\n\t\t\t\tif ( isMultiMonth ) {\n\t\t\t\t\tcalender += \"<div class='ui-datepicker-group\";\n\t\t\t\t\tif ( numMonths[ 1 ] > 1 ) {\n\t\t\t\t\t\tswitch ( col ) {\n\t\t\t\t\t\t\tcase 0: calender += \" ui-datepicker-group-first\";\n\t\t\t\t\t\t\t\tcornerClass = \" ui-corner-\" + ( isRTL ? \"right\" : \"left\" ); break;\n\t\t\t\t\t\t\tcase numMonths[ 1 ] - 1: calender += \" ui-datepicker-group-last\";\n\t\t\t\t\t\t\t\tcornerClass = \" ui-corner-\" + ( isRTL ? \"left\" : \"right\" ); break;\n\t\t\t\t\t\t\tdefault: calender += \" ui-datepicker-group-middle\"; cornerClass = \"\"; break;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcalender += \"'>\";\n\t\t\t\t}\n\t\t\t\tcalender += \"<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix\" + cornerClass + \"'>\" +\n\t\t\t\t\t( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : \"\" ) +\n\t\t\t\t\t( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : \"\" ) +\n\t\t\t\t\tthis._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate,\n\t\t\t\t\trow > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers\n\t\t\t\t\t\"</div><table class='ui-datepicker-calendar'><thead>\" +\n\t\t\t\t\t\"<tr>\";\n\t\t\t\tthead = ( showWeek ? \"<th class='ui-datepicker-week-col'>\" + this._get( inst, \"weekHeader\" ) + \"</th>\" : \"\" );\n\t\t\t\tfor ( dow = 0; dow < 7; dow++ ) { // days of the week\n\t\t\t\t\tday = ( dow + firstDay ) % 7;\n\t\t\t\t\tthead += \"<th scope='col'\" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? \" class='ui-datepicker-week-end'\" : \"\" ) + \">\" +\n\t\t\t\t\t\t\"<span title='\" + dayNames[ day ] + \"'>\" + dayNamesMin[ day ] + \"</span></th>\";\n\t\t\t\t}\n\t\t\t\tcalender += thead + \"</tr></thead><tbody>\";\n\t\t\t\tdaysInMonth = this._getDaysInMonth( drawYear, drawMonth );\n\t\t\t\tif ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) {\n\t\t\t\t\tinst.selectedDay = Math.min( inst.selectedDay, daysInMonth );\n\t\t\t\t}\n\t\t\t\tleadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7;\n\t\t\t\tcurRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate\n\t\t\t\tnumRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043)\n\t\t\t\tthis.maxRows = numRows;\n\t\t\t\tprintDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) );\n\t\t\t\tfor ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows\n\t\t\t\t\tcalender += \"<tr>\";\n\t\t\t\t\ttbody = ( !showWeek ? \"\" : \"<td class='ui-datepicker-week-col'>\" +\n\t\t\t\t\t\tthis._get( inst, \"calculateWeek\" )( printDate ) + \"</td>\" );\n\t\t\t\t\tfor ( dow = 0; dow < 7; dow++ ) { // create date picker days\n\t\t\t\t\t\tdaySettings = ( beforeShowDay ?\n\t\t\t\t\t\t\tbeforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, \"\" ] );\n\t\t\t\t\t\totherMonth = ( printDate.getMonth() !== drawMonth );\n\t\t\t\t\t\tunselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] ||\n\t\t\t\t\t\t\t( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate );\n\t\t\t\t\t\ttbody += \"<td class='\" +\n\t\t\t\t\t\t\t( ( dow + firstDay + 6 ) % 7 >= 5 ? \" ui-datepicker-week-end\" : \"\" ) + // highlight weekends\n\t\t\t\t\t\t\t( otherMonth ? \" ui-datepicker-other-month\" : \"\" ) + // highlight days from other months\n\t\t\t\t\t\t\t( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key\n\t\t\t\t\t\t\t( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ?\n\n\t\t\t\t\t\t\t// or defaultDate is current printedDate and defaultDate is selectedDate\n\t\t\t\t\t\t\t\" \" + this._dayOverClass : \"\" ) + // highlight selected day\n\t\t\t\t\t\t\t( unselectable ? \" \" + this._unselectableClass + \" ui-state-disabled\" : \"\" ) +  // highlight unselectable days\n\t\t\t\t\t\t\t( otherMonth && !showOtherMonths ? \"\" : \" \" + daySettings[ 1 ] + // highlight custom dates\n\t\t\t\t\t\t\t( printDate.getTime() === currentDate.getTime() ? \" \" + this._currentClass : \"\" ) + // highlight selected day\n\t\t\t\t\t\t\t( printDate.getTime() === today.getTime() ? \" ui-datepicker-today\" : \"\" ) ) + \"'\" + // highlight today (if different)\n\t\t\t\t\t\t\t( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? \" title='\" + daySettings[ 2 ].replace( /'/g, \"&#39;\" ) + \"'\" : \"\" ) + // cell title\n\t\t\t\t\t\t\t( unselectable ? \"\" : \" data-handler='selectDay' data-event='click' data-month='\" + printDate.getMonth() + \"' data-year='\" + printDate.getFullYear() + \"'\" ) + \">\" + // actions\n\t\t\t\t\t\t\t( otherMonth && !showOtherMonths ? \"&#xa0;\" : // display for other months\n\t\t\t\t\t\t\t( unselectable ? \"<span class='ui-state-default'>\" + printDate.getDate() + \"</span>\" : \"<a class='ui-state-default\" +\n\t\t\t\t\t\t\t( printDate.getTime() === today.getTime() ? \" ui-state-highlight\" : \"\" ) +\n\t\t\t\t\t\t\t( printDate.getTime() === currentDate.getTime() ? \" ui-state-active\" : \"\" ) + // highlight selected day\n\t\t\t\t\t\t\t( otherMonth ? \" ui-priority-secondary\" : \"\" ) + // distinguish dates from other months\n\t\t\t\t\t\t\t\"' href='#'>\" + printDate.getDate() + \"</a>\" ) ) + \"</td>\"; // display selectable date\n\t\t\t\t\t\tprintDate.setDate( printDate.getDate() + 1 );\n\t\t\t\t\t\tprintDate = this._daylightSavingAdjust( printDate );\n\t\t\t\t\t}\n\t\t\t\t\tcalender += tbody + \"</tr>\";\n\t\t\t\t}\n\t\t\t\tdrawMonth++;\n\t\t\t\tif ( drawMonth > 11 ) {\n\t\t\t\t\tdrawMonth = 0;\n\t\t\t\t\tdrawYear++;\n\t\t\t\t}\n\t\t\t\tcalender += \"</tbody></table>\" + ( isMultiMonth ? \"</div>\" +\n\t\t\t\t\t\t\t( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? \"<div class='ui-datepicker-row-break'></div>\" : \"\" ) : \"\" );\n\t\t\t\tgroup += calender;\n\t\t\t}\n\t\t\thtml += group;\n\t\t}\n\t\thtml += buttonPanel;\n\t\tinst._keyEvent = false;\n\t\treturn html;\n\t},\n\n\t/* Generate the month and year header. */\n\t_generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate,\n\t\t\tsecondary, monthNames, monthNamesShort ) {\n\n\t\tvar inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,\n\t\t\tchangeMonth = this._get( inst, \"changeMonth\" ),\n\t\t\tchangeYear = this._get( inst, \"changeYear\" ),\n\t\t\tshowMonthAfterYear = this._get( inst, \"showMonthAfterYear\" ),\n\t\t\thtml = \"<div class='ui-datepicker-title'>\",\n\t\t\tmonthHtml = \"\";\n\n\t\t// Month selection\n\t\tif ( secondary || !changeMonth ) {\n\t\t\tmonthHtml += \"<span class='ui-datepicker-month'>\" + monthNames[ drawMonth ] + \"</span>\";\n\t\t} else {\n\t\t\tinMinYear = ( minDate && minDate.getFullYear() === drawYear );\n\t\t\tinMaxYear = ( maxDate && maxDate.getFullYear() === drawYear );\n\t\t\tmonthHtml += \"<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>\";\n\t\t\tfor ( month = 0; month < 12; month++ ) {\n\t\t\t\tif ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) {\n\t\t\t\t\tmonthHtml += \"<option value='\" + month + \"'\" +\n\t\t\t\t\t\t( month === drawMonth ? \" selected='selected'\" : \"\" ) +\n\t\t\t\t\t\t\">\" + monthNamesShort[ month ] + \"</option>\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tmonthHtml += \"</select>\";\n\t\t}\n\n\t\tif ( !showMonthAfterYear ) {\n\t\t\thtml += monthHtml + ( secondary || !( changeMonth && changeYear ) ? \"&#xa0;\" : \"\" );\n\t\t}\n\n\t\t// Year selection\n\t\tif ( !inst.yearshtml ) {\n\t\t\tinst.yearshtml = \"\";\n\t\t\tif ( secondary || !changeYear ) {\n\t\t\t\thtml += \"<span class='ui-datepicker-year'>\" + drawYear + \"</span>\";\n\t\t\t} else {\n\n\t\t\t\t// determine range of years to display\n\t\t\t\tyears = this._get( inst, \"yearRange\" ).split( \":\" );\n\t\t\t\tthisYear = new Date().getFullYear();\n\t\t\t\tdetermineYear = function( value ) {\n\t\t\t\t\tvar year = ( value.match( /c[+\\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) :\n\t\t\t\t\t\t( value.match( /[+\\-].*/ ) ? thisYear + parseInt( value, 10 ) :\n\t\t\t\t\t\tparseInt( value, 10 ) ) );\n\t\t\t\t\treturn ( isNaN( year ) ? thisYear : year );\n\t\t\t\t};\n\t\t\t\tyear = determineYear( years[ 0 ] );\n\t\t\t\tendYear = Math.max( year, determineYear( years[ 1 ] || \"\" ) );\n\t\t\t\tyear = ( minDate ? Math.max( year, minDate.getFullYear() ) : year );\n\t\t\t\tendYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear );\n\t\t\t\tinst.yearshtml += \"<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>\";\n\t\t\t\tfor ( ; year <= endYear; year++ ) {\n\t\t\t\t\tinst.yearshtml += \"<option value='\" + year + \"'\" +\n\t\t\t\t\t\t( year === drawYear ? \" selected='selected'\" : \"\" ) +\n\t\t\t\t\t\t\">\" + year + \"</option>\";\n\t\t\t\t}\n\t\t\t\tinst.yearshtml += \"</select>\";\n\n\t\t\t\thtml += inst.yearshtml;\n\t\t\t\tinst.yearshtml = null;\n\t\t\t}\n\t\t}\n\n\t\thtml += this._get( inst, \"yearSuffix\" );\n\t\tif ( showMonthAfterYear ) {\n\t\t\thtml += ( secondary || !( changeMonth && changeYear ) ? \"&#xa0;\" : \"\" ) + monthHtml;\n\t\t}\n\t\thtml += \"</div>\"; // Close datepicker_header\n\t\treturn html;\n\t},\n\n\t/* Adjust one of the date sub-fields. */\n\t_adjustInstDate: function( inst, offset, period ) {\n\t\tvar year = inst.selectedYear + ( period === \"Y\" ? offset : 0 ),\n\t\t\tmonth = inst.selectedMonth + ( period === \"M\" ? offset : 0 ),\n\t\t\tday = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === \"D\" ? offset : 0 ),\n\t\t\tdate = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) );\n\n\t\tinst.selectedDay = date.getDate();\n\t\tinst.drawMonth = inst.selectedMonth = date.getMonth();\n\t\tinst.drawYear = inst.selectedYear = date.getFullYear();\n\t\tif ( period === \"M\" || period === \"Y\" ) {\n\t\t\tthis._notifyChange( inst );\n\t\t}\n\t},\n\n\t/* Ensure a date is within any min/max bounds. */\n\t_restrictMinMax: function( inst, date ) {\n\t\tvar minDate = this._getMinMaxDate( inst, \"min\" ),\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" ),\n\t\t\tnewDate = ( minDate && date < minDate ? minDate : date );\n\t\treturn ( maxDate && newDate > maxDate ? maxDate : newDate );\n\t},\n\n\t/* Notify change of month/year. */\n\t_notifyChange: function( inst ) {\n\t\tvar onChange = this._get( inst, \"onChangeMonthYear\" );\n\t\tif ( onChange ) {\n\t\t\tonChange.apply( ( inst.input ? inst.input[ 0 ] : null ),\n\t\t\t\t[ inst.selectedYear, inst.selectedMonth + 1, inst ] );\n\t\t}\n\t},\n\n\t/* Determine the number of months to show. */\n\t_getNumberOfMonths: function( inst ) {\n\t\tvar numMonths = this._get( inst, \"numberOfMonths\" );\n\t\treturn ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === \"number\" ? [ 1, numMonths ] : numMonths ) );\n\t},\n\n\t/* Determine the current maximum date - ensure no time components are set. */\n\t_getMinMaxDate: function( inst, minMax ) {\n\t\treturn this._determineDate( inst, this._get( inst, minMax + \"Date\" ), null );\n\t},\n\n\t/* Find the number of days in a given month. */\n\t_getDaysInMonth: function( year, month ) {\n\t\treturn 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate();\n\t},\n\n\t/* Find the day of the week of the first of a month. */\n\t_getFirstDayOfMonth: function( year, month ) {\n\t\treturn new Date( year, month, 1 ).getDay();\n\t},\n\n\t/* Determines if we should allow a \"next/prev\" month display change. */\n\t_canAdjustMonth: function( inst, offset, curYear, curMonth ) {\n\t\tvar numMonths = this._getNumberOfMonths( inst ),\n\t\t\tdate = this._daylightSavingAdjust( new Date( curYear,\n\t\t\tcurMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) );\n\n\t\tif ( offset < 0 ) {\n\t\t\tdate.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) );\n\t\t}\n\t\treturn this._isInRange( inst, date );\n\t},\n\n\t/* Is the given date in the accepted range? */\n\t_isInRange: function( inst, date ) {\n\t\tvar yearSplit, currentYear,\n\t\t\tminDate = this._getMinMaxDate( inst, \"min\" ),\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" ),\n\t\t\tminYear = null,\n\t\t\tmaxYear = null,\n\t\t\tyears = this._get( inst, \"yearRange\" );\n\t\t\tif ( years ) {\n\t\t\t\tyearSplit = years.split( \":\" );\n\t\t\t\tcurrentYear = new Date().getFullYear();\n\t\t\t\tminYear = parseInt( yearSplit[ 0 ], 10 );\n\t\t\t\tmaxYear = parseInt( yearSplit[ 1 ], 10 );\n\t\t\t\tif ( yearSplit[ 0 ].match( /[+\\-].*/ ) ) {\n\t\t\t\t\tminYear += currentYear;\n\t\t\t\t}\n\t\t\t\tif ( yearSplit[ 1 ].match( /[+\\-].*/ ) ) {\n\t\t\t\t\tmaxYear += currentYear;\n\t\t\t\t}\n\t\t\t}\n\n\t\treturn ( ( !minDate || date.getTime() >= minDate.getTime() ) &&\n\t\t\t( !maxDate || date.getTime() <= maxDate.getTime() ) &&\n\t\t\t( !minYear || date.getFullYear() >= minYear ) &&\n\t\t\t( !maxYear || date.getFullYear() <= maxYear ) );\n\t},\n\n\t/* Provide the configuration settings for formatting/parsing. */\n\t_getFormatConfig: function( inst ) {\n\t\tvar shortYearCutoff = this._get( inst, \"shortYearCutoff\" );\n\t\tshortYearCutoff = ( typeof shortYearCutoff !== \"string\" ? shortYearCutoff :\n\t\t\tnew Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) );\n\t\treturn { shortYearCutoff: shortYearCutoff,\n\t\t\tdayNamesShort: this._get( inst, \"dayNamesShort\" ), dayNames: this._get( inst, \"dayNames\" ),\n\t\t\tmonthNamesShort: this._get( inst, \"monthNamesShort\" ), monthNames: this._get( inst, \"monthNames\" ) };\n\t},\n\n\t/* Format the given date for display. */\n\t_formatDate: function( inst, day, month, year ) {\n\t\tif ( !day ) {\n\t\t\tinst.currentDay = inst.selectedDay;\n\t\t\tinst.currentMonth = inst.selectedMonth;\n\t\t\tinst.currentYear = inst.selectedYear;\n\t\t}\n\t\tvar date = ( day ? ( typeof day === \"object\" ? day :\n\t\t\tthis._daylightSavingAdjust( new Date( year, month, day ) ) ) :\n\t\t\tthis._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) );\n\t\treturn this.formatDate( this._get( inst, \"dateFormat\" ), date, this._getFormatConfig( inst ) );\n\t}\n} );\n\n/*\n * Bind hover events for datepicker elements.\n * Done via delegate so the binding only occurs once in the lifetime of the parent div.\n * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.\n */\nfunction datepicker_bindHover( dpDiv ) {\n\tvar selector = \"button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a\";\n\treturn dpDiv.on( \"mouseout\", selector, function() {\n\t\t\t$( this ).removeClass( \"ui-state-hover\" );\n\t\t\tif ( this.className.indexOf( \"ui-datepicker-prev\" ) !== -1 ) {\n\t\t\t\t$( this ).removeClass( \"ui-datepicker-prev-hover\" );\n\t\t\t}\n\t\t\tif ( this.className.indexOf( \"ui-datepicker-next\" ) !== -1 ) {\n\t\t\t\t$( this ).removeClass( \"ui-datepicker-next-hover\" );\n\t\t\t}\n\t\t} )\n\t\t.on( \"mouseover\", selector, datepicker_handleMouseover );\n}\n\nfunction datepicker_handleMouseover() {\n\tif ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) {\n\t\t$( this ).parents( \".ui-datepicker-calendar\" ).find( \"a\" ).removeClass( \"ui-state-hover\" );\n\t\t$( this ).addClass( \"ui-state-hover\" );\n\t\tif ( this.className.indexOf( \"ui-datepicker-prev\" ) !== -1 ) {\n\t\t\t$( this ).addClass( \"ui-datepicker-prev-hover\" );\n\t\t}\n\t\tif ( this.className.indexOf( \"ui-datepicker-next\" ) !== -1 ) {\n\t\t\t$( this ).addClass( \"ui-datepicker-next-hover\" );\n\t\t}\n\t}\n}\n\n/* jQuery extend now ignores nulls! */\nfunction datepicker_extendRemove( target, props ) {\n\t$.extend( target, props );\n\tfor ( var name in props ) {\n\t\tif ( props[ name ] == null ) {\n\t\t\ttarget[ name ] = props[ name ];\n\t\t}\n\t}\n\treturn target;\n}\n\n/* Invoke the datepicker functionality.\n   @param  options  string - a command, optionally followed by additional parameters or\n\t\t\t\t\tObject - settings for attaching new datepicker functionality\n   @return  jQuery object */\n$.fn.datepicker = function( options ) {\n\n\t/* Verify an empty collection wasn't passed - Fixes #6976 */\n\tif ( !this.length ) {\n\t\treturn this;\n\t}\n\n\t/* Initialise the date picker. */\n\tif ( !$.datepicker.initialized ) {\n\t\t$( document ).on( \"mousedown\", $.datepicker._checkExternalClick );\n\t\t$.datepicker.initialized = true;\n\t}\n\n\t/* Append datepicker main container to body if not exist. */\n\tif ( $( \"#\" + $.datepicker._mainDivId ).length === 0 ) {\n\t\t$( \"body\" ).append( $.datepicker.dpDiv );\n\t}\n\n\tvar otherArgs = Array.prototype.slice.call( arguments, 1 );\n\tif ( typeof options === \"string\" && ( options === \"isDisabled\" || options === \"getDate\" || options === \"widget\" ) ) {\n\t\treturn $.datepicker[ \"_\" + options + \"Datepicker\" ].\n\t\t\tapply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );\n\t}\n\tif ( options === \"option\" && arguments.length === 2 && typeof arguments[ 1 ] === \"string\" ) {\n\t\treturn $.datepicker[ \"_\" + options + \"Datepicker\" ].\n\t\t\tapply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );\n\t}\n\treturn this.each( function() {\n\t\ttypeof options === \"string\" ?\n\t\t\t$.datepicker[ \"_\" + options + \"Datepicker\" ].\n\t\t\t\tapply( $.datepicker, [ this ].concat( otherArgs ) ) :\n\t\t\t$.datepicker._attachDatepicker( this, options );\n\t} );\n};\n\n$.datepicker = new Datepicker(); // singleton instance\n$.datepicker.initialized = false;\n$.datepicker.uuid = new Date().getTime();\n$.datepicker.version = \"1.12.1\";\n\nvar widgetsDatepicker = $.datepicker;\n\n\n\n\n// This file is deprecated\nvar ie = $.ui.ie = !!/msie [\\w.]+/.exec( navigator.userAgent.toLowerCase() );\n\n/*!\n * jQuery UI Mouse 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Mouse\n//>>group: Widgets\n//>>description: Abstracts mouse-based interactions to assist in creating certain widgets.\n//>>docs: http://api.jqueryui.com/mouse/\n\n\n\nvar mouseHandled = false;\n$( document ).on( \"mouseup\", function() {\n\tmouseHandled = false;\n} );\n\nvar widgetsMouse = $.widget( \"ui.mouse\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tcancel: \"input, textarea, button, select, option\",\n\t\tdistance: 1,\n\t\tdelay: 0\n\t},\n\t_mouseInit: function() {\n\t\tvar that = this;\n\n\t\tthis.element\n\t\t\t.on( \"mousedown.\" + this.widgetName, function( event ) {\n\t\t\t\treturn that._mouseDown( event );\n\t\t\t} )\n\t\t\t.on( \"click.\" + this.widgetName, function( event ) {\n\t\t\t\tif ( true === $.data( event.target, that.widgetName + \".preventClickEvent\" ) ) {\n\t\t\t\t\t$.removeData( event.target, that.widgetName + \".preventClickEvent\" );\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} );\n\n\t\tthis.started = false;\n\t},\n\n\t// TODO: make sure destroying one instance of mouse doesn't mess with\n\t// other instances of mouse\n\t_mouseDestroy: function() {\n\t\tthis.element.off( \".\" + this.widgetName );\n\t\tif ( this._mouseMoveDelegate ) {\n\t\t\tthis.document\n\t\t\t\t.off( \"mousemove.\" + this.widgetName, this._mouseMoveDelegate )\n\t\t\t\t.off( \"mouseup.\" + this.widgetName, this._mouseUpDelegate );\n\t\t}\n\t},\n\n\t_mouseDown: function( event ) {\n\n\t\t// don't let more than one widget handle mouseStart\n\t\tif ( mouseHandled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._mouseMoved = false;\n\n\t\t// We may have missed mouseup (out of window)\n\t\t( this._mouseStarted && this._mouseUp( event ) );\n\n\t\tthis._mouseDownEvent = event;\n\n\t\tvar that = this,\n\t\t\tbtnIsLeft = ( event.which === 1 ),\n\n\t\t\t// event.target.nodeName works around a bug in IE 8 with\n\t\t\t// disabled inputs (#7620)\n\t\t\telIsCancel = ( typeof this.options.cancel === \"string\" && event.target.nodeName ?\n\t\t\t\t$( event.target ).closest( this.options.cancel ).length : false );\n\t\tif ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) {\n\t\t\treturn true;\n\t\t}\n\n\t\tthis.mouseDelayMet = !this.options.delay;\n\t\tif ( !this.mouseDelayMet ) {\n\t\t\tthis._mouseDelayTimer = setTimeout( function() {\n\t\t\t\tthat.mouseDelayMet = true;\n\t\t\t}, this.options.delay );\n\t\t}\n\n\t\tif ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {\n\t\t\tthis._mouseStarted = ( this._mouseStart( event ) !== false );\n\t\t\tif ( !this._mouseStarted ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// Click event may never have fired (Gecko & Opera)\n\t\tif ( true === $.data( event.target, this.widgetName + \".preventClickEvent\" ) ) {\n\t\t\t$.removeData( event.target, this.widgetName + \".preventClickEvent\" );\n\t\t}\n\n\t\t// These delegates are required to keep context\n\t\tthis._mouseMoveDelegate = function( event ) {\n\t\t\treturn that._mouseMove( event );\n\t\t};\n\t\tthis._mouseUpDelegate = function( event ) {\n\t\t\treturn that._mouseUp( event );\n\t\t};\n\n\t\tthis.document\n\t\t\t.on( \"mousemove.\" + this.widgetName, this._mouseMoveDelegate )\n\t\t\t.on( \"mouseup.\" + this.widgetName, this._mouseUpDelegate );\n\n\t\tevent.preventDefault();\n\n\t\tmouseHandled = true;\n\t\treturn true;\n\t},\n\n\t_mouseMove: function( event ) {\n\n\t\t// Only check for mouseups outside the document if you've moved inside the document\n\t\t// at least once. This prevents the firing of mouseup in the case of IE<9, which will\n\t\t// fire a mousemove event if content is placed under the cursor. See #7778\n\t\t// Support: IE <9\n\t\tif ( this._mouseMoved ) {\n\n\t\t\t// IE mouseup check - mouseup happened when mouse was out of window\n\t\t\tif ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) &&\n\t\t\t\t\t!event.button ) {\n\t\t\t\treturn this._mouseUp( event );\n\n\t\t\t// Iframe mouseup check - mouseup occurred in another document\n\t\t\t} else if ( !event.which ) {\n\n\t\t\t\t// Support: Safari <=8 - 9\n\t\t\t\t// Safari sets which to 0 if you press any of the following keys\n\t\t\t\t// during a drag (#14461)\n\t\t\t\tif ( event.originalEvent.altKey || event.originalEvent.ctrlKey ||\n\t\t\t\t\t\tevent.originalEvent.metaKey || event.originalEvent.shiftKey ) {\n\t\t\t\t\tthis.ignoreMissingWhich = true;\n\t\t\t\t} else if ( !this.ignoreMissingWhich ) {\n\t\t\t\t\treturn this._mouseUp( event );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( event.which || event.button ) {\n\t\t\tthis._mouseMoved = true;\n\t\t}\n\n\t\tif ( this._mouseStarted ) {\n\t\t\tthis._mouseDrag( event );\n\t\t\treturn event.preventDefault();\n\t\t}\n\n\t\tif ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {\n\t\t\tthis._mouseStarted =\n\t\t\t\t( this._mouseStart( this._mouseDownEvent, event ) !== false );\n\t\t\t( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) );\n\t\t}\n\n\t\treturn !this._mouseStarted;\n\t},\n\n\t_mouseUp: function( event ) {\n\t\tthis.document\n\t\t\t.off( \"mousemove.\" + this.widgetName, this._mouseMoveDelegate )\n\t\t\t.off( \"mouseup.\" + this.widgetName, this._mouseUpDelegate );\n\n\t\tif ( this._mouseStarted ) {\n\t\t\tthis._mouseStarted = false;\n\n\t\t\tif ( event.target === this._mouseDownEvent.target ) {\n\t\t\t\t$.data( event.target, this.widgetName + \".preventClickEvent\", true );\n\t\t\t}\n\n\t\t\tthis._mouseStop( event );\n\t\t}\n\n\t\tif ( this._mouseDelayTimer ) {\n\t\t\tclearTimeout( this._mouseDelayTimer );\n\t\t\tdelete this._mouseDelayTimer;\n\t\t}\n\n\t\tthis.ignoreMissingWhich = false;\n\t\tmouseHandled = false;\n\t\tevent.preventDefault();\n\t},\n\n\t_mouseDistanceMet: function( event ) {\n\t\treturn ( Math.max(\n\t\t\t\tMath.abs( this._mouseDownEvent.pageX - event.pageX ),\n\t\t\t\tMath.abs( this._mouseDownEvent.pageY - event.pageY )\n\t\t\t) >= this.options.distance\n\t\t);\n\t},\n\n\t_mouseDelayMet: function( /* event */ ) {\n\t\treturn this.mouseDelayMet;\n\t},\n\n\t// These are placeholder methods, to be overriden by extending plugin\n\t_mouseStart: function( /* event */ ) {},\n\t_mouseDrag: function( /* event */ ) {},\n\t_mouseStop: function( /* event */ ) {},\n\t_mouseCapture: function( /* event */ ) { return true; }\n} );\n\n\n\n\n// $.ui.plugin is deprecated. Use $.widget() extensions instead.\nvar plugin = $.ui.plugin = {\n\tadd: function( module, option, set ) {\n\t\tvar i,\n\t\t\tproto = $.ui[ module ].prototype;\n\t\tfor ( i in set ) {\n\t\t\tproto.plugins[ i ] = proto.plugins[ i ] || [];\n\t\t\tproto.plugins[ i ].push( [ option, set[ i ] ] );\n\t\t}\n\t},\n\tcall: function( instance, name, args, allowDisconnected ) {\n\t\tvar i,\n\t\t\tset = instance.plugins[ name ];\n\n\t\tif ( !set ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !allowDisconnected && ( !instance.element[ 0 ].parentNode ||\n\t\t\t\tinstance.element[ 0 ].parentNode.nodeType === 11 ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor ( i = 0; i < set.length; i++ ) {\n\t\t\tif ( instance.options[ set[ i ][ 0 ] ] ) {\n\t\t\t\tset[ i ][ 1 ].apply( instance.element, args );\n\t\t\t}\n\t\t}\n\t}\n};\n\n\n\nvar safeBlur = $.ui.safeBlur = function( element ) {\n\n\t// Support: IE9 - 10 only\n\t// If the <body> is blurred, IE will switch windows, see #9420\n\tif ( element && element.nodeName.toLowerCase() !== \"body\" ) {\n\t\t$( element ).trigger( \"blur\" );\n\t}\n};\n\n\n/*!\n * jQuery UI Draggable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Draggable\n//>>group: Interactions\n//>>description: Enables dragging functionality for any element.\n//>>docs: http://api.jqueryui.com/draggable/\n//>>demos: http://jqueryui.com/draggable/\n//>>css.structure: ../../themes/base/draggable.css\n\n\n\n$.widget( \"ui.draggable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"drag\",\n\toptions: {\n\t\taddClasses: true,\n\t\tappendTo: \"parent\",\n\t\taxis: false,\n\t\tconnectToSortable: false,\n\t\tcontainment: false,\n\t\tcursor: \"auto\",\n\t\tcursorAt: false,\n\t\tgrid: false,\n\t\thandle: false,\n\t\thelper: \"original\",\n\t\tiframeFix: false,\n\t\topacity: false,\n\t\trefreshPositions: false,\n\t\trevert: false,\n\t\trevertDuration: 500,\n\t\tscope: \"default\",\n\t\tscroll: true,\n\t\tscrollSensitivity: 20,\n\t\tscrollSpeed: 20,\n\t\tsnap: false,\n\t\tsnapMode: \"both\",\n\t\tsnapTolerance: 20,\n\t\tstack: false,\n\t\tzIndex: false,\n\n\t\t// Callbacks\n\t\tdrag: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\t_create: function() {\n\n\t\tif ( this.options.helper === \"original\" ) {\n\t\t\tthis._setPositionRelative();\n\t\t}\n\t\tif ( this.options.addClasses ) {\n\t\t\tthis._addClass( \"ui-draggable\" );\n\t\t}\n\t\tthis._setHandleClassName();\n\n\t\tthis._mouseInit();\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\t\tif ( key === \"handle\" ) {\n\t\t\tthis._removeHandleClassName();\n\t\t\tthis._setHandleClassName();\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tif ( ( this.helper || this.element ).is( \".ui-draggable-dragging\" ) ) {\n\t\t\tthis.destroyOnClear = true;\n\t\t\treturn;\n\t\t}\n\t\tthis._removeHandleClassName();\n\t\tthis._mouseDestroy();\n\t},\n\n\t_mouseCapture: function( event ) {\n\t\tvar o = this.options;\n\n\t\t// Among others, prevent a drag on a resizable-handle\n\t\tif ( this.helper || o.disabled ||\n\t\t\t\t$( event.target ).closest( \".ui-resizable-handle\" ).length > 0 ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t//Quit if we're not on a valid handle\n\t\tthis.handle = this._getHandle( event );\n\t\tif ( !this.handle ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._blurActiveElement( event );\n\n\t\tthis._blockFrames( o.iframeFix === true ? \"iframe\" : o.iframeFix );\n\n\t\treturn true;\n\n\t},\n\n\t_blockFrames: function( selector ) {\n\t\tthis.iframeBlocks = this.document.find( selector ).map( function() {\n\t\t\tvar iframe = $( this );\n\n\t\t\treturn $( \"<div>\" )\n\t\t\t\t.css( \"position\", \"absolute\" )\n\t\t\t\t.appendTo( iframe.parent() )\n\t\t\t\t.outerWidth( iframe.outerWidth() )\n\t\t\t\t.outerHeight( iframe.outerHeight() )\n\t\t\t\t.offset( iframe.offset() )[ 0 ];\n\t\t} );\n\t},\n\n\t_unblockFrames: function() {\n\t\tif ( this.iframeBlocks ) {\n\t\t\tthis.iframeBlocks.remove();\n\t\t\tdelete this.iframeBlocks;\n\t\t}\n\t},\n\n\t_blurActiveElement: function( event ) {\n\t\tvar activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),\n\t\t\ttarget = $( event.target );\n\n\t\t// Don't blur if the event occurred on an element that is within\n\t\t// the currently focused element\n\t\t// See #10527, #12472\n\t\tif ( target.closest( activeElement ).length ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Blur any element that currently has focus, see #4261\n\t\t$.ui.safeBlur( activeElement );\n\t},\n\n\t_mouseStart: function( event ) {\n\n\t\tvar o = this.options;\n\n\t\t//Create and append the visible helper\n\t\tthis.helper = this._createHelper( event );\n\n\t\tthis._addClass( this.helper, \"ui-draggable-dragging\" );\n\n\t\t//Cache the helper size\n\t\tthis._cacheHelperProportions();\n\n\t\t//If ddmanager is used for droppables, set the global draggable\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.current = this;\n\t\t}\n\n\t\t/*\n\t\t * - Position generation -\n\t\t * This block generates everything position related - it's the core of draggables.\n\t\t */\n\n\t\t//Cache the margins of the original element\n\t\tthis._cacheMargins();\n\n\t\t//Store the helper's css position\n\t\tthis.cssPosition = this.helper.css( \"position\" );\n\t\tthis.scrollParent = this.helper.scrollParent( true );\n\t\tthis.offsetParent = this.helper.offsetParent();\n\t\tthis.hasFixedAncestor = this.helper.parents().filter( function() {\n\t\t\t\treturn $( this ).css( \"position\" ) === \"fixed\";\n\t\t\t} ).length > 0;\n\n\t\t//The element's absolute position on the page minus margins\n\t\tthis.positionAbs = this.element.offset();\n\t\tthis._refreshOffsets( event );\n\n\t\t//Generate the original position\n\t\tthis.originalPosition = this.position = this._generatePosition( event, false );\n\t\tthis.originalPageX = event.pageX;\n\t\tthis.originalPageY = event.pageY;\n\n\t\t//Adjust the mouse offset relative to the helper if \"cursorAt\" is supplied\n\t\t( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );\n\n\t\t//Set a containment if given in the options\n\t\tthis._setContainment();\n\n\t\t//Trigger event + callbacks\n\t\tif ( this._trigger( \"start\", event ) === false ) {\n\t\t\tthis._clear();\n\t\t\treturn false;\n\t\t}\n\n\t\t//Recache the helper size\n\t\tthis._cacheHelperProportions();\n\n\t\t//Prepare the droppable offsets\n\t\tif ( $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( this, event );\n\t\t}\n\n\t\t// Execute the drag once - this causes the helper not to be visible before getting its\n\t\t// correct position\n\t\tthis._mouseDrag( event, true );\n\n\t\t// If the ddmanager is used for droppables, inform the manager that dragging has started\n\t\t// (see #5003)\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.dragStart( this, event );\n\t\t}\n\n\t\treturn true;\n\t},\n\n\t_refreshOffsets: function( event ) {\n\t\tthis.offset = {\n\t\t\ttop: this.positionAbs.top - this.margins.top,\n\t\t\tleft: this.positionAbs.left - this.margins.left,\n\t\t\tscroll: false,\n\t\t\tparent: this._getParentOffset(),\n\t\t\trelative: this._getRelativeOffset()\n\t\t};\n\n\t\tthis.offset.click = {\n\t\t\tleft: event.pageX - this.offset.left,\n\t\t\ttop: event.pageY - this.offset.top\n\t\t};\n\t},\n\n\t_mouseDrag: function( event, noPropagation ) {\n\n\t\t// reset any necessary cached properties (see #5009)\n\t\tif ( this.hasFixedAncestor ) {\n\t\t\tthis.offset.parent = this._getParentOffset();\n\t\t}\n\n\t\t//Compute the helpers position\n\t\tthis.position = this._generatePosition( event, true );\n\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\n\t\t//Call plugins and callbacks and use the resulting position if something is returned\n\t\tif ( !noPropagation ) {\n\t\t\tvar ui = this._uiHash();\n\t\t\tif ( this._trigger( \"drag\", event, ui ) === false ) {\n\t\t\t\tthis._mouseUp( new $.Event( \"mouseup\", event ) );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.position = ui.position;\n\t\t}\n\n\t\tthis.helper[ 0 ].style.left = this.position.left + \"px\";\n\t\tthis.helper[ 0 ].style.top = this.position.top + \"px\";\n\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.drag( this, event );\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\n\t\t//If we are using droppables, inform the manager about the drop\n\t\tvar that = this,\n\t\t\tdropped = false;\n\t\tif ( $.ui.ddmanager && !this.options.dropBehaviour ) {\n\t\t\tdropped = $.ui.ddmanager.drop( this, event );\n\t\t}\n\n\t\t//if a drop comes from outside (a sortable)\n\t\tif ( this.dropped ) {\n\t\t\tdropped = this.dropped;\n\t\t\tthis.dropped = false;\n\t\t}\n\n\t\tif ( ( this.options.revert === \"invalid\" && !dropped ) ||\n\t\t\t\t( this.options.revert === \"valid\" && dropped ) ||\n\t\t\t\tthis.options.revert === true || ( $.isFunction( this.options.revert ) &&\n\t\t\t\tthis.options.revert.call( this.element, dropped ) )\n\t\t) {\n\t\t\t$( this.helper ).animate(\n\t\t\t\tthis.originalPosition,\n\t\t\t\tparseInt( this.options.revertDuration, 10 ),\n\t\t\t\tfunction() {\n\t\t\t\t\tif ( that._trigger( \"stop\", event ) !== false ) {\n\t\t\t\t\t\tthat._clear();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t} else {\n\t\t\tif ( this._trigger( \"stop\", event ) !== false ) {\n\t\t\t\tthis._clear();\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_mouseUp: function( event ) {\n\t\tthis._unblockFrames();\n\n\t\t// If the ddmanager is used for droppables, inform the manager that dragging has stopped\n\t\t// (see #5003)\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.dragStop( this, event );\n\t\t}\n\n\t\t// Only need to focus if the event occurred on the draggable itself, see #10527\n\t\tif ( this.handleElement.is( event.target ) ) {\n\n\t\t\t// The interaction is over; whether or not the click resulted in a drag,\n\t\t\t// focus the element\n\t\t\tthis.element.trigger( \"focus\" );\n\t\t}\n\n\t\treturn $.ui.mouse.prototype._mouseUp.call( this, event );\n\t},\n\n\tcancel: function() {\n\n\t\tif ( this.helper.is( \".ui-draggable-dragging\" ) ) {\n\t\t\tthis._mouseUp( new $.Event( \"mouseup\", { target: this.element[ 0 ] } ) );\n\t\t} else {\n\t\t\tthis._clear();\n\t\t}\n\n\t\treturn this;\n\n\t},\n\n\t_getHandle: function( event ) {\n\t\treturn this.options.handle ?\n\t\t\t!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :\n\t\t\ttrue;\n\t},\n\n\t_setHandleClassName: function() {\n\t\tthis.handleElement = this.options.handle ?\n\t\t\tthis.element.find( this.options.handle ) : this.element;\n\t\tthis._addClass( this.handleElement, \"ui-draggable-handle\" );\n\t},\n\n\t_removeHandleClassName: function() {\n\t\tthis._removeClass( this.handleElement, \"ui-draggable-handle\" );\n\t},\n\n\t_createHelper: function( event ) {\n\n\t\tvar o = this.options,\n\t\t\thelperIsFunction = $.isFunction( o.helper ),\n\t\t\thelper = helperIsFunction ?\n\t\t\t\t$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :\n\t\t\t\t( o.helper === \"clone\" ?\n\t\t\t\t\tthis.element.clone().removeAttr( \"id\" ) :\n\t\t\t\t\tthis.element );\n\n\t\tif ( !helper.parents( \"body\" ).length ) {\n\t\t\thelper.appendTo( ( o.appendTo === \"parent\" ?\n\t\t\t\tthis.element[ 0 ].parentNode :\n\t\t\t\to.appendTo ) );\n\t\t}\n\n\t\t// Http://bugs.jqueryui.com/ticket/9446\n\t\t// a helper function can return the original element\n\t\t// which wouldn't have been set to relative in _create\n\t\tif ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {\n\t\t\tthis._setPositionRelative();\n\t\t}\n\n\t\tif ( helper[ 0 ] !== this.element[ 0 ] &&\n\t\t\t\t!( /(fixed|absolute)/ ).test( helper.css( \"position\" ) ) ) {\n\t\t\thelper.css( \"position\", \"absolute\" );\n\t\t}\n\n\t\treturn helper;\n\n\t},\n\n\t_setPositionRelative: function() {\n\t\tif ( !( /^(?:r|a|f)/ ).test( this.element.css( \"position\" ) ) ) {\n\t\t\tthis.element[ 0 ].style.position = \"relative\";\n\t\t}\n\t},\n\n\t_adjustOffsetFromHelper: function( obj ) {\n\t\tif ( typeof obj === \"string\" ) {\n\t\t\tobj = obj.split( \" \" );\n\t\t}\n\t\tif ( $.isArray( obj ) ) {\n\t\t\tobj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };\n\t\t}\n\t\tif ( \"left\" in obj ) {\n\t\t\tthis.offset.click.left = obj.left + this.margins.left;\n\t\t}\n\t\tif ( \"right\" in obj ) {\n\t\t\tthis.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;\n\t\t}\n\t\tif ( \"top\" in obj ) {\n\t\t\tthis.offset.click.top = obj.top + this.margins.top;\n\t\t}\n\t\tif ( \"bottom\" in obj ) {\n\t\t\tthis.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;\n\t\t}\n\t},\n\n\t_isRootNode: function( element ) {\n\t\treturn ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];\n\t},\n\n\t_getParentOffset: function() {\n\n\t\t//Get the offsetParent and cache its position\n\t\tvar po = this.offsetParent.offset(),\n\t\t\tdocument = this.document[ 0 ];\n\n\t\t// This is a special case where we need to modify a offset calculated on start, since the\n\t\t// following happened:\n\t\t// 1. The position of the helper is absolute, so it's position is calculated based on the\n\t\t// next positioned parent\n\t\t// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't\n\t\t// the document, which means that the scroll is included in the initial calculation of the\n\t\t// offset of the parent, and never recalculated upon drag\n\t\tif ( this.cssPosition === \"absolute\" && this.scrollParent[ 0 ] !== document &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {\n\t\t\tpo.left += this.scrollParent.scrollLeft();\n\t\t\tpo.top += this.scrollParent.scrollTop();\n\t\t}\n\n\t\tif ( this._isRootNode( this.offsetParent[ 0 ] ) ) {\n\t\t\tpo = { top: 0, left: 0 };\n\t\t}\n\n\t\treturn {\n\t\t\ttop: po.top + ( parseInt( this.offsetParent.css( \"borderTopWidth\" ), 10 ) || 0 ),\n\t\t\tleft: po.left + ( parseInt( this.offsetParent.css( \"borderLeftWidth\" ), 10 ) || 0 )\n\t\t};\n\n\t},\n\n\t_getRelativeOffset: function() {\n\t\tif ( this.cssPosition !== \"relative\" ) {\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t\tvar p = this.element.position(),\n\t\t\tscrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );\n\n\t\treturn {\n\t\t\ttop: p.top - ( parseInt( this.helper.css( \"top\" ), 10 ) || 0 ) +\n\t\t\t\t( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),\n\t\t\tleft: p.left - ( parseInt( this.helper.css( \"left\" ), 10 ) || 0 ) +\n\t\t\t\t( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )\n\t\t};\n\n\t},\n\n\t_cacheMargins: function() {\n\t\tthis.margins = {\n\t\t\tleft: ( parseInt( this.element.css( \"marginLeft\" ), 10 ) || 0 ),\n\t\t\ttop: ( parseInt( this.element.css( \"marginTop\" ), 10 ) || 0 ),\n\t\t\tright: ( parseInt( this.element.css( \"marginRight\" ), 10 ) || 0 ),\n\t\t\tbottom: ( parseInt( this.element.css( \"marginBottom\" ), 10 ) || 0 )\n\t\t};\n\t},\n\n\t_cacheHelperProportions: function() {\n\t\tthis.helperProportions = {\n\t\t\twidth: this.helper.outerWidth(),\n\t\t\theight: this.helper.outerHeight()\n\t\t};\n\t},\n\n\t_setContainment: function() {\n\n\t\tvar isUserScrollable, c, ce,\n\t\t\to = this.options,\n\t\t\tdocument = this.document[ 0 ];\n\n\t\tthis.relativeContainer = null;\n\n\t\tif ( !o.containment ) {\n\t\t\tthis.containment = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment === \"window\" ) {\n\t\t\tthis.containment = [\n\t\t\t\t$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,\n\t\t\t\t$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,\n\t\t\t\t$( window ).scrollLeft() + $( window ).width() -\n\t\t\t\t\tthis.helperProportions.width - this.margins.left,\n\t\t\t\t$( window ).scrollTop() +\n\t\t\t\t\t( $( window ).height() || document.body.parentNode.scrollHeight ) -\n\t\t\t\t\tthis.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment === \"document\" ) {\n\t\t\tthis.containment = [\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t$( document ).width() - this.helperProportions.width - this.margins.left,\n\t\t\t\t( $( document ).height() || document.body.parentNode.scrollHeight ) -\n\t\t\t\t\tthis.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment.constructor === Array ) {\n\t\t\tthis.containment = o.containment;\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment === \"parent\" ) {\n\t\t\to.containment = this.helper[ 0 ].parentNode;\n\t\t}\n\n\t\tc = $( o.containment );\n\t\tce = c[ 0 ];\n\n\t\tif ( !ce ) {\n\t\t\treturn;\n\t\t}\n\n\t\tisUserScrollable = /(scroll|auto)/.test( c.css( \"overflow\" ) );\n\n\t\tthis.containment = [\n\t\t\t( parseInt( c.css( \"borderLeftWidth\" ), 10 ) || 0 ) +\n\t\t\t\t( parseInt( c.css( \"paddingLeft\" ), 10 ) || 0 ),\n\t\t\t( parseInt( c.css( \"borderTopWidth\" ), 10 ) || 0 ) +\n\t\t\t\t( parseInt( c.css( \"paddingTop\" ), 10 ) || 0 ),\n\t\t\t( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -\n\t\t\t\t( parseInt( c.css( \"borderRightWidth\" ), 10 ) || 0 ) -\n\t\t\t\t( parseInt( c.css( \"paddingRight\" ), 10 ) || 0 ) -\n\t\t\t\tthis.helperProportions.width -\n\t\t\t\tthis.margins.left -\n\t\t\t\tthis.margins.right,\n\t\t\t( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -\n\t\t\t\t( parseInt( c.css( \"borderBottomWidth\" ), 10 ) || 0 ) -\n\t\t\t\t( parseInt( c.css( \"paddingBottom\" ), 10 ) || 0 ) -\n\t\t\t\tthis.helperProportions.height -\n\t\t\t\tthis.margins.top -\n\t\t\t\tthis.margins.bottom\n\t\t];\n\t\tthis.relativeContainer = c;\n\t},\n\n\t_convertPositionTo: function( d, pos ) {\n\n\t\tif ( !pos ) {\n\t\t\tpos = this.position;\n\t\t}\n\n\t\tvar mod = d === \"absolute\" ? 1 : -1,\n\t\t\tscrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.top\t+\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top * mod -\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.top :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.left +\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left * mod\t-\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.left :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_generatePosition: function( event, constrainPosition ) {\n\n\t\tvar containment, co, top, left,\n\t\t\to = this.options,\n\t\t\tscrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),\n\t\t\tpageX = event.pageX,\n\t\t\tpageY = event.pageY;\n\n\t\t// Cache the scroll\n\t\tif ( !scrollIsRootNode || !this.offset.scroll ) {\n\t\t\tthis.offset.scroll = {\n\t\t\t\ttop: this.scrollParent.scrollTop(),\n\t\t\t\tleft: this.scrollParent.scrollLeft()\n\t\t\t};\n\t\t}\n\n\t\t/*\n\t\t * - Position constraining -\n\t\t * Constrain the position to a mix of grid, containment.\n\t\t */\n\n\t\t// If we are not dragging yet, we won't check for options\n\t\tif ( constrainPosition ) {\n\t\t\tif ( this.containment ) {\n\t\t\t\tif ( this.relativeContainer ) {\n\t\t\t\t\tco = this.relativeContainer.offset();\n\t\t\t\t\tcontainment = [\n\t\t\t\t\t\tthis.containment[ 0 ] + co.left,\n\t\t\t\t\t\tthis.containment[ 1 ] + co.top,\n\t\t\t\t\t\tthis.containment[ 2 ] + co.left,\n\t\t\t\t\t\tthis.containment[ 3 ] + co.top\n\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\tcontainment = this.containment;\n\t\t\t\t}\n\n\t\t\t\tif ( event.pageX - this.offset.click.left < containment[ 0 ] ) {\n\t\t\t\t\tpageX = containment[ 0 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top < containment[ 1 ] ) {\n\t\t\t\t\tpageY = containment[ 1 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t\tif ( event.pageX - this.offset.click.left > containment[ 2 ] ) {\n\t\t\t\t\tpageX = containment[ 2 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top > containment[ 3 ] ) {\n\t\t\t\t\tpageY = containment[ 3 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( o.grid ) {\n\n\t\t\t\t//Check for grid elements set to 0 to prevent divide by 0 error causing invalid\n\t\t\t\t// argument errors in IE (see ticket #6950)\n\t\t\t\ttop = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -\n\t\t\t\t\tthis.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;\n\t\t\t\tpageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||\n\t\t\t\t\ttop - this.offset.click.top > containment[ 3 ] ) ?\n\t\t\t\t\t\ttop :\n\t\t\t\t\t\t( ( top - this.offset.click.top >= containment[ 1 ] ) ?\n\t\t\t\t\t\t\ttop - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;\n\n\t\t\t\tleft = o.grid[ 0 ] ? this.originalPageX +\n\t\t\t\t\tMath.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :\n\t\t\t\t\tthis.originalPageX;\n\t\t\t\tpageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||\n\t\t\t\t\tleft - this.offset.click.left > containment[ 2 ] ) ?\n\t\t\t\t\t\tleft :\n\t\t\t\t\t\t( ( left - this.offset.click.left >= containment[ 0 ] ) ?\n\t\t\t\t\t\t\tleft - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;\n\t\t\t}\n\n\t\t\tif ( o.axis === \"y\" ) {\n\t\t\t\tpageX = this.originalPageX;\n\t\t\t}\n\n\t\t\tif ( o.axis === \"x\" ) {\n\t\t\t\tpageY = this.originalPageY;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageY -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.top -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top +\n\t\t\t\t( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.top :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.top ) )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageX -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.left -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left +\n\t\t\t\t( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.left :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.left ) )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_clear: function() {\n\t\tthis._removeClass( this.helper, \"ui-draggable-dragging\" );\n\t\tif ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {\n\t\t\tthis.helper.remove();\n\t\t}\n\t\tthis.helper = null;\n\t\tthis.cancelHelperRemoval = false;\n\t\tif ( this.destroyOnClear ) {\n\t\t\tthis.destroy();\n\t\t}\n\t},\n\n\t// From now on bulk stuff - mainly helpers\n\n\t_trigger: function( type, event, ui ) {\n\t\tui = ui || this._uiHash();\n\t\t$.ui.plugin.call( this, type, [ event, ui, this ], true );\n\n\t\t// Absolute position and offset (see #6884 ) have to be recalculated after plugins\n\t\tif ( /^(drag|start|stop)/.test( type ) ) {\n\t\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\t\t\tui.offset = this.positionAbs;\n\t\t}\n\t\treturn $.Widget.prototype._trigger.call( this, type, event, ui );\n\t},\n\n\tplugins: {},\n\n\t_uiHash: function() {\n\t\treturn {\n\t\t\thelper: this.helper,\n\t\t\tposition: this.position,\n\t\t\toriginalPosition: this.originalPosition,\n\t\t\toffset: this.positionAbs\n\t\t};\n\t}\n\n} );\n\n$.ui.plugin.add( \"draggable\", \"connectToSortable\", {\n\tstart: function( event, ui, draggable ) {\n\t\tvar uiSortable = $.extend( {}, ui, {\n\t\t\titem: draggable.element\n\t\t} );\n\n\t\tdraggable.sortables = [];\n\t\t$( draggable.options.connectToSortable ).each( function() {\n\t\t\tvar sortable = $( this ).sortable( \"instance\" );\n\n\t\t\tif ( sortable && !sortable.options.disabled ) {\n\t\t\t\tdraggable.sortables.push( sortable );\n\n\t\t\t\t// RefreshPositions is called at drag start to refresh the containerCache\n\t\t\t\t// which is used in drag. This ensures it's initialized and synchronized\n\t\t\t\t// with any changes that might have happened on the page since initialization.\n\t\t\t\tsortable.refreshPositions();\n\t\t\t\tsortable._trigger( \"activate\", event, uiSortable );\n\t\t\t}\n\t\t} );\n\t},\n\tstop: function( event, ui, draggable ) {\n\t\tvar uiSortable = $.extend( {}, ui, {\n\t\t\titem: draggable.element\n\t\t} );\n\n\t\tdraggable.cancelHelperRemoval = false;\n\n\t\t$.each( draggable.sortables, function() {\n\t\t\tvar sortable = this;\n\n\t\t\tif ( sortable.isOver ) {\n\t\t\t\tsortable.isOver = 0;\n\n\t\t\t\t// Allow this sortable to handle removing the helper\n\t\t\t\tdraggable.cancelHelperRemoval = true;\n\t\t\t\tsortable.cancelHelperRemoval = false;\n\n\t\t\t\t// Use _storedCSS To restore properties in the sortable,\n\t\t\t\t// as this also handles revert (#9675) since the draggable\n\t\t\t\t// may have modified them in unexpected ways (#8809)\n\t\t\t\tsortable._storedCSS = {\n\t\t\t\t\tposition: sortable.placeholder.css( \"position\" ),\n\t\t\t\t\ttop: sortable.placeholder.css( \"top\" ),\n\t\t\t\t\tleft: sortable.placeholder.css( \"left\" )\n\t\t\t\t};\n\n\t\t\t\tsortable._mouseStop( event );\n\n\t\t\t\t// Once drag has ended, the sortable should return to using\n\t\t\t\t// its original helper, not the shared helper from draggable\n\t\t\t\tsortable.options.helper = sortable.options._helper;\n\t\t\t} else {\n\n\t\t\t\t// Prevent this Sortable from removing the helper.\n\t\t\t\t// However, don't set the draggable to remove the helper\n\t\t\t\t// either as another connected Sortable may yet handle the removal.\n\t\t\t\tsortable.cancelHelperRemoval = true;\n\n\t\t\t\tsortable._trigger( \"deactivate\", event, uiSortable );\n\t\t\t}\n\t\t} );\n\t},\n\tdrag: function( event, ui, draggable ) {\n\t\t$.each( draggable.sortables, function() {\n\t\t\tvar innermostIntersecting = false,\n\t\t\t\tsortable = this;\n\n\t\t\t// Copy over variables that sortable's _intersectsWith uses\n\t\t\tsortable.positionAbs = draggable.positionAbs;\n\t\t\tsortable.helperProportions = draggable.helperProportions;\n\t\t\tsortable.offset.click = draggable.offset.click;\n\n\t\t\tif ( sortable._intersectsWith( sortable.containerCache ) ) {\n\t\t\t\tinnermostIntersecting = true;\n\n\t\t\t\t$.each( draggable.sortables, function() {\n\n\t\t\t\t\t// Copy over variables that sortable's _intersectsWith uses\n\t\t\t\t\tthis.positionAbs = draggable.positionAbs;\n\t\t\t\t\tthis.helperProportions = draggable.helperProportions;\n\t\t\t\t\tthis.offset.click = draggable.offset.click;\n\n\t\t\t\t\tif ( this !== sortable &&\n\t\t\t\t\t\t\tthis._intersectsWith( this.containerCache ) &&\n\t\t\t\t\t\t\t$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {\n\t\t\t\t\t\tinnermostIntersecting = false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn innermostIntersecting;\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tif ( innermostIntersecting ) {\n\n\t\t\t\t// If it intersects, we use a little isOver variable and set it once,\n\t\t\t\t// so that the move-in stuff gets fired only once.\n\t\t\t\tif ( !sortable.isOver ) {\n\t\t\t\t\tsortable.isOver = 1;\n\n\t\t\t\t\t// Store draggable's parent in case we need to reappend to it later.\n\t\t\t\t\tdraggable._parent = ui.helper.parent();\n\n\t\t\t\t\tsortable.currentItem = ui.helper\n\t\t\t\t\t\t.appendTo( sortable.element )\n\t\t\t\t\t\t.data( \"ui-sortable-item\", true );\n\n\t\t\t\t\t// Store helper option to later restore it\n\t\t\t\t\tsortable.options._helper = sortable.options.helper;\n\n\t\t\t\t\tsortable.options.helper = function() {\n\t\t\t\t\t\treturn ui.helper[ 0 ];\n\t\t\t\t\t};\n\n\t\t\t\t\t// Fire the start events of the sortable with our passed browser event,\n\t\t\t\t\t// and our own helper (so it doesn't create a new one)\n\t\t\t\t\tevent.target = sortable.currentItem[ 0 ];\n\t\t\t\t\tsortable._mouseCapture( event, true );\n\t\t\t\t\tsortable._mouseStart( event, true, true );\n\n\t\t\t\t\t// Because the browser event is way off the new appended portlet,\n\t\t\t\t\t// modify necessary variables to reflect the changes\n\t\t\t\t\tsortable.offset.click.top = draggable.offset.click.top;\n\t\t\t\t\tsortable.offset.click.left = draggable.offset.click.left;\n\t\t\t\t\tsortable.offset.parent.left -= draggable.offset.parent.left -\n\t\t\t\t\t\tsortable.offset.parent.left;\n\t\t\t\t\tsortable.offset.parent.top -= draggable.offset.parent.top -\n\t\t\t\t\t\tsortable.offset.parent.top;\n\n\t\t\t\t\tdraggable._trigger( \"toSortable\", event );\n\n\t\t\t\t\t// Inform draggable that the helper is in a valid drop zone,\n\t\t\t\t\t// used solely in the revert option to handle \"valid/invalid\".\n\t\t\t\t\tdraggable.dropped = sortable.element;\n\n\t\t\t\t\t// Need to refreshPositions of all sortables in the case that\n\t\t\t\t\t// adding to one sortable changes the location of the other sortables (#9675)\n\t\t\t\t\t$.each( draggable.sortables, function() {\n\t\t\t\t\t\tthis.refreshPositions();\n\t\t\t\t\t} );\n\n\t\t\t\t\t// Hack so receive/update callbacks work (mostly)\n\t\t\t\t\tdraggable.currentItem = draggable.element;\n\t\t\t\t\tsortable.fromOutside = draggable;\n\t\t\t\t}\n\n\t\t\t\tif ( sortable.currentItem ) {\n\t\t\t\t\tsortable._mouseDrag( event );\n\n\t\t\t\t\t// Copy the sortable's position because the draggable's can potentially reflect\n\t\t\t\t\t// a relative position, while sortable is always absolute, which the dragged\n\t\t\t\t\t// element has now become. (#8809)\n\t\t\t\t\tui.position = sortable.position;\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// If it doesn't intersect with the sortable, and it intersected before,\n\t\t\t\t// we fake the drag stop of the sortable, but make sure it doesn't remove\n\t\t\t\t// the helper by using cancelHelperRemoval.\n\t\t\t\tif ( sortable.isOver ) {\n\n\t\t\t\t\tsortable.isOver = 0;\n\t\t\t\t\tsortable.cancelHelperRemoval = true;\n\n\t\t\t\t\t// Calling sortable's mouseStop would trigger a revert,\n\t\t\t\t\t// so revert must be temporarily false until after mouseStop is called.\n\t\t\t\t\tsortable.options._revert = sortable.options.revert;\n\t\t\t\t\tsortable.options.revert = false;\n\n\t\t\t\t\tsortable._trigger( \"out\", event, sortable._uiHash( sortable ) );\n\t\t\t\t\tsortable._mouseStop( event, true );\n\n\t\t\t\t\t// Restore sortable behaviors that were modfied\n\t\t\t\t\t// when the draggable entered the sortable area (#9481)\n\t\t\t\t\tsortable.options.revert = sortable.options._revert;\n\t\t\t\t\tsortable.options.helper = sortable.options._helper;\n\n\t\t\t\t\tif ( sortable.placeholder ) {\n\t\t\t\t\t\tsortable.placeholder.remove();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Restore and recalculate the draggable's offset considering the sortable\n\t\t\t\t\t// may have modified them in unexpected ways. (#8809, #10669)\n\t\t\t\t\tui.helper.appendTo( draggable._parent );\n\t\t\t\t\tdraggable._refreshOffsets( event );\n\t\t\t\t\tui.position = draggable._generatePosition( event, true );\n\n\t\t\t\t\tdraggable._trigger( \"fromSortable\", event );\n\n\t\t\t\t\t// Inform draggable that the helper is no longer in a valid drop zone\n\t\t\t\t\tdraggable.dropped = false;\n\n\t\t\t\t\t// Need to refreshPositions of all sortables just in case removing\n\t\t\t\t\t// from one sortable changes the location of other sortables (#9675)\n\t\t\t\t\t$.each( draggable.sortables, function() {\n\t\t\t\t\t\tthis.refreshPositions();\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"cursor\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar t = $( \"body\" ),\n\t\t\to = instance.options;\n\n\t\tif ( t.css( \"cursor\" ) ) {\n\t\t\to._cursor = t.css( \"cursor\" );\n\t\t}\n\t\tt.css( \"cursor\", o.cursor );\n\t},\n\tstop: function( event, ui, instance ) {\n\t\tvar o = instance.options;\n\t\tif ( o._cursor ) {\n\t\t\t$( \"body\" ).css( \"cursor\", o._cursor );\n\t\t}\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"opacity\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar t = $( ui.helper ),\n\t\t\to = instance.options;\n\t\tif ( t.css( \"opacity\" ) ) {\n\t\t\to._opacity = t.css( \"opacity\" );\n\t\t}\n\t\tt.css( \"opacity\", o.opacity );\n\t},\n\tstop: function( event, ui, instance ) {\n\t\tvar o = instance.options;\n\t\tif ( o._opacity ) {\n\t\t\t$( ui.helper ).css( \"opacity\", o._opacity );\n\t\t}\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"scroll\", {\n\tstart: function( event, ui, i ) {\n\t\tif ( !i.scrollParentNotHidden ) {\n\t\t\ti.scrollParentNotHidden = i.helper.scrollParent( false );\n\t\t}\n\n\t\tif ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&\n\t\t\t\ti.scrollParentNotHidden[ 0 ].tagName !== \"HTML\" ) {\n\t\t\ti.overflowOffset = i.scrollParentNotHidden.offset();\n\t\t}\n\t},\n\tdrag: function( event, ui, i  ) {\n\n\t\tvar o = i.options,\n\t\t\tscrolled = false,\n\t\t\tscrollParent = i.scrollParentNotHidden[ 0 ],\n\t\t\tdocument = i.document[ 0 ];\n\n\t\tif ( scrollParent !== document && scrollParent.tagName !== \"HTML\" ) {\n\t\t\tif ( !o.axis || o.axis !== \"x\" ) {\n\t\t\t\tif ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !o.axis || o.axis !== \"y\" ) {\n\t\t\t\tif ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\n\t\t\tif ( !o.axis || o.axis !== \"x\" ) {\n\t\t\t\tif ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );\n\t\t\t\t} else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !o.axis || o.axis !== \"y\" ) {\n\t\t\t\tif ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollLeft(\n\t\t\t\t\t\t$( document ).scrollLeft() - o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t} else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollLeft(\n\t\t\t\t\t\t$( document ).scrollLeft() + o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( i, event );\n\t\t}\n\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"snap\", {\n\tstart: function( event, ui, i ) {\n\n\t\tvar o = i.options;\n\n\t\ti.snapElements = [];\n\n\t\t$( o.snap.constructor !== String ? ( o.snap.items || \":data(ui-draggable)\" ) : o.snap )\n\t\t\t.each( function() {\n\t\t\t\tvar $t = $( this ),\n\t\t\t\t\t$o = $t.offset();\n\t\t\t\tif ( this !== i.element[ 0 ] ) {\n\t\t\t\t\ti.snapElements.push( {\n\t\t\t\t\t\titem: this,\n\t\t\t\t\t\twidth: $t.outerWidth(), height: $t.outerHeight(),\n\t\t\t\t\t\ttop: $o.top, left: $o.left\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\n\t},\n\tdrag: function( event, ui, inst ) {\n\n\t\tvar ts, bs, ls, rs, l, r, t, b, i, first,\n\t\t\to = inst.options,\n\t\t\td = o.snapTolerance,\n\t\t\tx1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,\n\t\t\ty1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;\n\n\t\tfor ( i = inst.snapElements.length - 1; i >= 0; i-- ) {\n\n\t\t\tl = inst.snapElements[ i ].left - inst.margins.left;\n\t\t\tr = l + inst.snapElements[ i ].width;\n\t\t\tt = inst.snapElements[ i ].top - inst.margins.top;\n\t\t\tb = t + inst.snapElements[ i ].height;\n\n\t\t\tif ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||\n\t\t\t\t\t!$.contains( inst.snapElements[ i ].item.ownerDocument,\n\t\t\t\t\tinst.snapElements[ i ].item ) ) {\n\t\t\t\tif ( inst.snapElements[ i ].snapping ) {\n\t\t\t\t\t( inst.options.snap.release &&\n\t\t\t\t\t\tinst.options.snap.release.call(\n\t\t\t\t\t\t\tinst.element,\n\t\t\t\t\t\t\tevent,\n\t\t\t\t\t\t\t$.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )\n\t\t\t\t\t\t) );\n\t\t\t\t}\n\t\t\t\tinst.snapElements[ i ].snapping = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( o.snapMode !== \"inner\" ) {\n\t\t\t\tts = Math.abs( t - y2 ) <= d;\n\t\t\t\tbs = Math.abs( b - y1 ) <= d;\n\t\t\t\tls = Math.abs( l - x2 ) <= d;\n\t\t\t\trs = Math.abs( r - x1 ) <= d;\n\t\t\t\tif ( ts ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: t - inst.helperProportions.height,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( bs ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: b,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( ls ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: l - inst.helperProportions.width\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t\tif ( rs ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: r\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfirst = ( ts || bs || ls || rs );\n\n\t\t\tif ( o.snapMode !== \"outer\" ) {\n\t\t\t\tts = Math.abs( t - y1 ) <= d;\n\t\t\t\tbs = Math.abs( b - y2 ) <= d;\n\t\t\t\tls = Math.abs( l - x1 ) <= d;\n\t\t\t\trs = Math.abs( r - x2 ) <= d;\n\t\t\t\tif ( ts ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: t,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( bs ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: b - inst.helperProportions.height,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( ls ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: l\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t\tif ( rs ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: r - inst.helperProportions.width\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {\n\t\t\t\t( inst.options.snap.snap &&\n\t\t\t\t\tinst.options.snap.snap.call(\n\t\t\t\t\t\tinst.element,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\t$.extend( inst._uiHash(), {\n\t\t\t\t\t\t\tsnapItem: inst.snapElements[ i ].item\n\t\t\t\t\t\t} ) ) );\n\t\t\t}\n\t\t\tinst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );\n\n\t\t}\n\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"stack\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar min,\n\t\t\to = instance.options,\n\t\t\tgroup = $.makeArray( $( o.stack ) ).sort( function( a, b ) {\n\t\t\t\treturn ( parseInt( $( a ).css( \"zIndex\" ), 10 ) || 0 ) -\n\t\t\t\t\t( parseInt( $( b ).css( \"zIndex\" ), 10 ) || 0 );\n\t\t\t} );\n\n\t\tif ( !group.length ) { return; }\n\n\t\tmin = parseInt( $( group[ 0 ] ).css( \"zIndex\" ), 10 ) || 0;\n\t\t$( group ).each( function( i ) {\n\t\t\t$( this ).css( \"zIndex\", min + i );\n\t\t} );\n\t\tthis.css( \"zIndex\", ( min + group.length ) );\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"zIndex\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar t = $( ui.helper ),\n\t\t\to = instance.options;\n\n\t\tif ( t.css( \"zIndex\" ) ) {\n\t\t\to._zIndex = t.css( \"zIndex\" );\n\t\t}\n\t\tt.css( \"zIndex\", o.zIndex );\n\t},\n\tstop: function( event, ui, instance ) {\n\t\tvar o = instance.options;\n\n\t\tif ( o._zIndex ) {\n\t\t\t$( ui.helper ).css( \"zIndex\", o._zIndex );\n\t\t}\n\t}\n} );\n\nvar widgetsDraggable = $.ui.draggable;\n\n\n/*!\n * jQuery UI Resizable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Resizable\n//>>group: Interactions\n//>>description: Enables resize functionality for any element.\n//>>docs: http://api.jqueryui.com/resizable/\n//>>demos: http://jqueryui.com/resizable/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/resizable.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.resizable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"resize\",\n\toptions: {\n\t\talsoResize: false,\n\t\tanimate: false,\n\t\tanimateDuration: \"slow\",\n\t\tanimateEasing: \"swing\",\n\t\taspectRatio: false,\n\t\tautoHide: false,\n\t\tclasses: {\n\t\t\t\"ui-resizable-se\": \"ui-icon ui-icon-gripsmall-diagonal-se\"\n\t\t},\n\t\tcontainment: false,\n\t\tghost: false,\n\t\tgrid: false,\n\t\thandles: \"e,s,se\",\n\t\thelper: false,\n\t\tmaxHeight: null,\n\t\tmaxWidth: null,\n\t\tminHeight: 10,\n\t\tminWidth: 10,\n\n\t\t// See #7960\n\t\tzIndex: 90,\n\n\t\t// Callbacks\n\t\tresize: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\n\t_num: function( value ) {\n\t\treturn parseFloat( value ) || 0;\n\t},\n\n\t_isNumber: function( value ) {\n\t\treturn !isNaN( parseFloat( value ) );\n\t},\n\n\t_hasScroll: function( el, a ) {\n\n\t\tif ( $( el ).css( \"overflow\" ) === \"hidden\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar scroll = ( a && a === \"left\" ) ? \"scrollLeft\" : \"scrollTop\",\n\t\t\thas = false;\n\n\t\tif ( el[ scroll ] > 0 ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// TODO: determine which cases actually cause this to happen\n\t\t// if the element doesn't have the scroll set, see if it's possible to\n\t\t// set the scroll\n\t\tel[ scroll ] = 1;\n\t\thas = ( el[ scroll ] > 0 );\n\t\tel[ scroll ] = 0;\n\t\treturn has;\n\t},\n\n\t_create: function() {\n\n\t\tvar margins,\n\t\t\to = this.options,\n\t\t\tthat = this;\n\t\tthis._addClass( \"ui-resizable\" );\n\n\t\t$.extend( this, {\n\t\t\t_aspectRatio: !!( o.aspectRatio ),\n\t\t\taspectRatio: o.aspectRatio,\n\t\t\toriginalElement: this.element,\n\t\t\t_proportionallyResizeElements: [],\n\t\t\t_helper: o.helper || o.ghost || o.animate ? o.helper || \"ui-resizable-helper\" : null\n\t\t} );\n\n\t\t// Wrap the element if it cannot hold child nodes\n\t\tif ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) {\n\n\t\t\tthis.element.wrap(\n\t\t\t\t$( \"<div class='ui-wrapper' style='overflow: hidden;'></div>\" ).css( {\n\t\t\t\t\tposition: this.element.css( \"position\" ),\n\t\t\t\t\twidth: this.element.outerWidth(),\n\t\t\t\t\theight: this.element.outerHeight(),\n\t\t\t\t\ttop: this.element.css( \"top\" ),\n\t\t\t\t\tleft: this.element.css( \"left\" )\n\t\t\t\t} )\n\t\t\t);\n\n\t\t\tthis.element = this.element.parent().data(\n\t\t\t\t\"ui-resizable\", this.element.resizable( \"instance\" )\n\t\t\t);\n\n\t\t\tthis.elementIsWrapper = true;\n\n\t\t\tmargins = {\n\t\t\t\tmarginTop: this.originalElement.css( \"marginTop\" ),\n\t\t\t\tmarginRight: this.originalElement.css( \"marginRight\" ),\n\t\t\t\tmarginBottom: this.originalElement.css( \"marginBottom\" ),\n\t\t\t\tmarginLeft: this.originalElement.css( \"marginLeft\" )\n\t\t\t};\n\n\t\t\tthis.element.css( margins );\n\t\t\tthis.originalElement.css( \"margin\", 0 );\n\n\t\t\t// support: Safari\n\t\t\t// Prevent Safari textarea resize\n\t\t\tthis.originalResizeStyle = this.originalElement.css( \"resize\" );\n\t\t\tthis.originalElement.css( \"resize\", \"none\" );\n\n\t\t\tthis._proportionallyResizeElements.push( this.originalElement.css( {\n\t\t\t\tposition: \"static\",\n\t\t\t\tzoom: 1,\n\t\t\t\tdisplay: \"block\"\n\t\t\t} ) );\n\n\t\t\t// Support: IE9\n\t\t\t// avoid IE jump (hard set the margin)\n\t\t\tthis.originalElement.css( margins );\n\n\t\t\tthis._proportionallyResize();\n\t\t}\n\n\t\tthis._setupHandles();\n\n\t\tif ( o.autoHide ) {\n\t\t\t$( this.element )\n\t\t\t\t.on( \"mouseenter\", function() {\n\t\t\t\t\tif ( o.disabled ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthat._removeClass( \"ui-resizable-autohide\" );\n\t\t\t\t\tthat._handles.show();\n\t\t\t\t} )\n\t\t\t\t.on( \"mouseleave\", function() {\n\t\t\t\t\tif ( o.disabled ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif ( !that.resizing ) {\n\t\t\t\t\t\tthat._addClass( \"ui-resizable-autohide\" );\n\t\t\t\t\t\tthat._handles.hide();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t}\n\n\t\tthis._mouseInit();\n\t},\n\n\t_destroy: function() {\n\n\t\tthis._mouseDestroy();\n\n\t\tvar wrapper,\n\t\t\t_destroy = function( exp ) {\n\t\t\t\t$( exp )\n\t\t\t\t\t.removeData( \"resizable\" )\n\t\t\t\t\t.removeData( \"ui-resizable\" )\n\t\t\t\t\t.off( \".resizable\" )\n\t\t\t\t\t.find( \".ui-resizable-handle\" )\n\t\t\t\t\t\t.remove();\n\t\t\t};\n\n\t\t// TODO: Unwrap at same DOM position\n\t\tif ( this.elementIsWrapper ) {\n\t\t\t_destroy( this.element );\n\t\t\twrapper = this.element;\n\t\t\tthis.originalElement.css( {\n\t\t\t\tposition: wrapper.css( \"position\" ),\n\t\t\t\twidth: wrapper.outerWidth(),\n\t\t\t\theight: wrapper.outerHeight(),\n\t\t\t\ttop: wrapper.css( \"top\" ),\n\t\t\t\tleft: wrapper.css( \"left\" )\n\t\t\t} ).insertAfter( wrapper );\n\t\t\twrapper.remove();\n\t\t}\n\n\t\tthis.originalElement.css( \"resize\", this.originalResizeStyle );\n\t\t_destroy( this.originalElement );\n\n\t\treturn this;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\n\t\tswitch ( key ) {\n\t\tcase \"handles\":\n\t\t\tthis._removeHandles();\n\t\t\tthis._setupHandles();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t},\n\n\t_setupHandles: function() {\n\t\tvar o = this.options, handle, i, n, hname, axis, that = this;\n\t\tthis.handles = o.handles ||\n\t\t\t( !$( \".ui-resizable-handle\", this.element ).length ?\n\t\t\t\t\"e,s,se\" : {\n\t\t\t\t\tn: \".ui-resizable-n\",\n\t\t\t\t\te: \".ui-resizable-e\",\n\t\t\t\t\ts: \".ui-resizable-s\",\n\t\t\t\t\tw: \".ui-resizable-w\",\n\t\t\t\t\tse: \".ui-resizable-se\",\n\t\t\t\t\tsw: \".ui-resizable-sw\",\n\t\t\t\t\tne: \".ui-resizable-ne\",\n\t\t\t\t\tnw: \".ui-resizable-nw\"\n\t\t\t\t} );\n\n\t\tthis._handles = $();\n\t\tif ( this.handles.constructor === String ) {\n\n\t\t\tif ( this.handles === \"all\" ) {\n\t\t\t\tthis.handles = \"n,e,s,w,se,sw,ne,nw\";\n\t\t\t}\n\n\t\t\tn = this.handles.split( \",\" );\n\t\t\tthis.handles = {};\n\n\t\t\tfor ( i = 0; i < n.length; i++ ) {\n\n\t\t\t\thandle = $.trim( n[ i ] );\n\t\t\t\thname = \"ui-resizable-\" + handle;\n\t\t\t\taxis = $( \"<div>\" );\n\t\t\t\tthis._addClass( axis, \"ui-resizable-handle \" + hname );\n\n\t\t\t\taxis.css( { zIndex: o.zIndex } );\n\n\t\t\t\tthis.handles[ handle ] = \".ui-resizable-\" + handle;\n\t\t\t\tthis.element.append( axis );\n\t\t\t}\n\n\t\t}\n\n\t\tthis._renderAxis = function( target ) {\n\n\t\t\tvar i, axis, padPos, padWrapper;\n\n\t\t\ttarget = target || this.element;\n\n\t\t\tfor ( i in this.handles ) {\n\n\t\t\t\tif ( this.handles[ i ].constructor === String ) {\n\t\t\t\t\tthis.handles[ i ] = this.element.children( this.handles[ i ] ).first().show();\n\t\t\t\t} else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) {\n\t\t\t\t\tthis.handles[ i ] = $( this.handles[ i ] );\n\t\t\t\t\tthis._on( this.handles[ i ], { \"mousedown\": that._mouseDown } );\n\t\t\t\t}\n\n\t\t\t\tif ( this.elementIsWrapper &&\n\t\t\t\t\t\tthis.originalElement[ 0 ]\n\t\t\t\t\t\t\t.nodeName\n\t\t\t\t\t\t\t.match( /^(textarea|input|select|button)$/i ) ) {\n\t\t\t\t\taxis = $( this.handles[ i ], this.element );\n\n\t\t\t\t\tpadWrapper = /sw|ne|nw|se|n|s/.test( i ) ?\n\t\t\t\t\t\taxis.outerHeight() :\n\t\t\t\t\t\taxis.outerWidth();\n\n\t\t\t\t\tpadPos = [ \"padding\",\n\t\t\t\t\t\t/ne|nw|n/.test( i ) ? \"Top\" :\n\t\t\t\t\t\t/se|sw|s/.test( i ) ? \"Bottom\" :\n\t\t\t\t\t\t/^e$/.test( i ) ? \"Right\" : \"Left\" ].join( \"\" );\n\n\t\t\t\t\ttarget.css( padPos, padWrapper );\n\n\t\t\t\t\tthis._proportionallyResize();\n\t\t\t\t}\n\n\t\t\t\tthis._handles = this._handles.add( this.handles[ i ] );\n\t\t\t}\n\t\t};\n\n\t\t// TODO: make renderAxis a prototype function\n\t\tthis._renderAxis( this.element );\n\n\t\tthis._handles = this._handles.add( this.element.find( \".ui-resizable-handle\" ) );\n\t\tthis._handles.disableSelection();\n\n\t\tthis._handles.on( \"mouseover\", function() {\n\t\t\tif ( !that.resizing ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\taxis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i );\n\t\t\t\t}\n\t\t\t\tthat.axis = axis && axis[ 1 ] ? axis[ 1 ] : \"se\";\n\t\t\t}\n\t\t} );\n\n\t\tif ( o.autoHide ) {\n\t\t\tthis._handles.hide();\n\t\t\tthis._addClass( \"ui-resizable-autohide\" );\n\t\t}\n\t},\n\n\t_removeHandles: function() {\n\t\tthis._handles.remove();\n\t},\n\n\t_mouseCapture: function( event ) {\n\t\tvar i, handle,\n\t\t\tcapture = false;\n\n\t\tfor ( i in this.handles ) {\n\t\t\thandle = $( this.handles[ i ] )[ 0 ];\n\t\t\tif ( handle === event.target || $.contains( handle, event.target ) ) {\n\t\t\t\tcapture = true;\n\t\t\t}\n\t\t}\n\n\t\treturn !this.options.disabled && capture;\n\t},\n\n\t_mouseStart: function( event ) {\n\n\t\tvar curleft, curtop, cursor,\n\t\t\to = this.options,\n\t\t\tel = this.element;\n\n\t\tthis.resizing = true;\n\n\t\tthis._renderProxy();\n\n\t\tcurleft = this._num( this.helper.css( \"left\" ) );\n\t\tcurtop = this._num( this.helper.css( \"top\" ) );\n\n\t\tif ( o.containment ) {\n\t\t\tcurleft += $( o.containment ).scrollLeft() || 0;\n\t\t\tcurtop += $( o.containment ).scrollTop() || 0;\n\t\t}\n\n\t\tthis.offset = this.helper.offset();\n\t\tthis.position = { left: curleft, top: curtop };\n\n\t\tthis.size = this._helper ? {\n\t\t\t\twidth: this.helper.width(),\n\t\t\t\theight: this.helper.height()\n\t\t\t} : {\n\t\t\t\twidth: el.width(),\n\t\t\t\theight: el.height()\n\t\t\t};\n\n\t\tthis.originalSize = this._helper ? {\n\t\t\t\twidth: el.outerWidth(),\n\t\t\t\theight: el.outerHeight()\n\t\t\t} : {\n\t\t\t\twidth: el.width(),\n\t\t\t\theight: el.height()\n\t\t\t};\n\n\t\tthis.sizeDiff = {\n\t\t\twidth: el.outerWidth() - el.width(),\n\t\t\theight: el.outerHeight() - el.height()\n\t\t};\n\n\t\tthis.originalPosition = { left: curleft, top: curtop };\n\t\tthis.originalMousePosition = { left: event.pageX, top: event.pageY };\n\n\t\tthis.aspectRatio = ( typeof o.aspectRatio === \"number\" ) ?\n\t\t\to.aspectRatio :\n\t\t\t( ( this.originalSize.width / this.originalSize.height ) || 1 );\n\n\t\tcursor = $( \".ui-resizable-\" + this.axis ).css( \"cursor\" );\n\t\t$( \"body\" ).css( \"cursor\", cursor === \"auto\" ? this.axis + \"-resize\" : cursor );\n\n\t\tthis._addClass( \"ui-resizable-resizing\" );\n\t\tthis._propagate( \"start\", event );\n\t\treturn true;\n\t},\n\n\t_mouseDrag: function( event ) {\n\n\t\tvar data, props,\n\t\t\tsmp = this.originalMousePosition,\n\t\t\ta = this.axis,\n\t\t\tdx = ( event.pageX - smp.left ) || 0,\n\t\t\tdy = ( event.pageY - smp.top ) || 0,\n\t\t\ttrigger = this._change[ a ];\n\n\t\tthis._updatePrevProperties();\n\n\t\tif ( !trigger ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tdata = trigger.apply( this, [ event, dx, dy ] );\n\n\t\tthis._updateVirtualBoundaries( event.shiftKey );\n\t\tif ( this._aspectRatio || event.shiftKey ) {\n\t\t\tdata = this._updateRatio( data, event );\n\t\t}\n\n\t\tdata = this._respectSize( data, event );\n\n\t\tthis._updateCache( data );\n\n\t\tthis._propagate( \"resize\", event );\n\n\t\tprops = this._applyChanges();\n\n\t\tif ( !this._helper && this._proportionallyResizeElements.length ) {\n\t\t\tthis._proportionallyResize();\n\t\t}\n\n\t\tif ( !$.isEmptyObject( props ) ) {\n\t\t\tthis._updatePrevProperties();\n\t\t\tthis._trigger( \"resize\", event, this.ui() );\n\t\t\tthis._applyChanges();\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\n\t\tthis.resizing = false;\n\t\tvar pr, ista, soffseth, soffsetw, s, left, top,\n\t\t\to = this.options, that = this;\n\n\t\tif ( this._helper ) {\n\n\t\t\tpr = this._proportionallyResizeElements;\n\t\t\tista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName );\n\t\t\tsoffseth = ista && this._hasScroll( pr[ 0 ], \"left\" ) ? 0 : that.sizeDiff.height;\n\t\t\tsoffsetw = ista ? 0 : that.sizeDiff.width;\n\n\t\t\ts = {\n\t\t\t\twidth: ( that.helper.width()  - soffsetw ),\n\t\t\t\theight: ( that.helper.height() - soffseth )\n\t\t\t};\n\t\t\tleft = ( parseFloat( that.element.css( \"left\" ) ) +\n\t\t\t\t( that.position.left - that.originalPosition.left ) ) || null;\n\t\t\ttop = ( parseFloat( that.element.css( \"top\" ) ) +\n\t\t\t\t( that.position.top - that.originalPosition.top ) ) || null;\n\n\t\t\tif ( !o.animate ) {\n\t\t\t\tthis.element.css( $.extend( s, { top: top, left: left } ) );\n\t\t\t}\n\n\t\t\tthat.helper.height( that.size.height );\n\t\t\tthat.helper.width( that.size.width );\n\n\t\t\tif ( this._helper && !o.animate ) {\n\t\t\t\tthis._proportionallyResize();\n\t\t\t}\n\t\t}\n\n\t\t$( \"body\" ).css( \"cursor\", \"auto\" );\n\n\t\tthis._removeClass( \"ui-resizable-resizing\" );\n\n\t\tthis._propagate( \"stop\", event );\n\n\t\tif ( this._helper ) {\n\t\t\tthis.helper.remove();\n\t\t}\n\n\t\treturn false;\n\n\t},\n\n\t_updatePrevProperties: function() {\n\t\tthis.prevPosition = {\n\t\t\ttop: this.position.top,\n\t\t\tleft: this.position.left\n\t\t};\n\t\tthis.prevSize = {\n\t\t\twidth: this.size.width,\n\t\t\theight: this.size.height\n\t\t};\n\t},\n\n\t_applyChanges: function() {\n\t\tvar props = {};\n\n\t\tif ( this.position.top !== this.prevPosition.top ) {\n\t\t\tprops.top = this.position.top + \"px\";\n\t\t}\n\t\tif ( this.position.left !== this.prevPosition.left ) {\n\t\t\tprops.left = this.position.left + \"px\";\n\t\t}\n\t\tif ( this.size.width !== this.prevSize.width ) {\n\t\t\tprops.width = this.size.width + \"px\";\n\t\t}\n\t\tif ( this.size.height !== this.prevSize.height ) {\n\t\t\tprops.height = this.size.height + \"px\";\n\t\t}\n\n\t\tthis.helper.css( props );\n\n\t\treturn props;\n\t},\n\n\t_updateVirtualBoundaries: function( forceAspectRatio ) {\n\t\tvar pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,\n\t\t\to = this.options;\n\n\t\tb = {\n\t\t\tminWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0,\n\t\t\tmaxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity,\n\t\t\tminHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0,\n\t\t\tmaxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity\n\t\t};\n\n\t\tif ( this._aspectRatio || forceAspectRatio ) {\n\t\t\tpMinWidth = b.minHeight * this.aspectRatio;\n\t\t\tpMinHeight = b.minWidth / this.aspectRatio;\n\t\t\tpMaxWidth = b.maxHeight * this.aspectRatio;\n\t\t\tpMaxHeight = b.maxWidth / this.aspectRatio;\n\n\t\t\tif ( pMinWidth > b.minWidth ) {\n\t\t\t\tb.minWidth = pMinWidth;\n\t\t\t}\n\t\t\tif ( pMinHeight > b.minHeight ) {\n\t\t\t\tb.minHeight = pMinHeight;\n\t\t\t}\n\t\t\tif ( pMaxWidth < b.maxWidth ) {\n\t\t\t\tb.maxWidth = pMaxWidth;\n\t\t\t}\n\t\t\tif ( pMaxHeight < b.maxHeight ) {\n\t\t\t\tb.maxHeight = pMaxHeight;\n\t\t\t}\n\t\t}\n\t\tthis._vBoundaries = b;\n\t},\n\n\t_updateCache: function( data ) {\n\t\tthis.offset = this.helper.offset();\n\t\tif ( this._isNumber( data.left ) ) {\n\t\t\tthis.position.left = data.left;\n\t\t}\n\t\tif ( this._isNumber( data.top ) ) {\n\t\t\tthis.position.top = data.top;\n\t\t}\n\t\tif ( this._isNumber( data.height ) ) {\n\t\t\tthis.size.height = data.height;\n\t\t}\n\t\tif ( this._isNumber( data.width ) ) {\n\t\t\tthis.size.width = data.width;\n\t\t}\n\t},\n\n\t_updateRatio: function( data ) {\n\n\t\tvar cpos = this.position,\n\t\t\tcsize = this.size,\n\t\t\ta = this.axis;\n\n\t\tif ( this._isNumber( data.height ) ) {\n\t\t\tdata.width = ( data.height * this.aspectRatio );\n\t\t} else if ( this._isNumber( data.width ) ) {\n\t\t\tdata.height = ( data.width / this.aspectRatio );\n\t\t}\n\n\t\tif ( a === \"sw\" ) {\n\t\t\tdata.left = cpos.left + ( csize.width - data.width );\n\t\t\tdata.top = null;\n\t\t}\n\t\tif ( a === \"nw\" ) {\n\t\t\tdata.top = cpos.top + ( csize.height - data.height );\n\t\t\tdata.left = cpos.left + ( csize.width - data.width );\n\t\t}\n\n\t\treturn data;\n\t},\n\n\t_respectSize: function( data ) {\n\n\t\tvar o = this._vBoundaries,\n\t\t\ta = this.axis,\n\t\t\tismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ),\n\t\t\tismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ),\n\t\t\tisminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ),\n\t\t\tisminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ),\n\t\t\tdw = this.originalPosition.left + this.originalSize.width,\n\t\t\tdh = this.originalPosition.top + this.originalSize.height,\n\t\t\tcw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a );\n\t\tif ( isminw ) {\n\t\t\tdata.width = o.minWidth;\n\t\t}\n\t\tif ( isminh ) {\n\t\t\tdata.height = o.minHeight;\n\t\t}\n\t\tif ( ismaxw ) {\n\t\t\tdata.width = o.maxWidth;\n\t\t}\n\t\tif ( ismaxh ) {\n\t\t\tdata.height = o.maxHeight;\n\t\t}\n\n\t\tif ( isminw && cw ) {\n\t\t\tdata.left = dw - o.minWidth;\n\t\t}\n\t\tif ( ismaxw && cw ) {\n\t\t\tdata.left = dw - o.maxWidth;\n\t\t}\n\t\tif ( isminh && ch ) {\n\t\t\tdata.top = dh - o.minHeight;\n\t\t}\n\t\tif ( ismaxh && ch ) {\n\t\t\tdata.top = dh - o.maxHeight;\n\t\t}\n\n\t\t// Fixing jump error on top/left - bug #2330\n\t\tif ( !data.width && !data.height && !data.left && data.top ) {\n\t\t\tdata.top = null;\n\t\t} else if ( !data.width && !data.height && !data.top && data.left ) {\n\t\t\tdata.left = null;\n\t\t}\n\n\t\treturn data;\n\t},\n\n\t_getPaddingPlusBorderDimensions: function( element ) {\n\t\tvar i = 0,\n\t\t\twidths = [],\n\t\t\tborders = [\n\t\t\t\telement.css( \"borderTopWidth\" ),\n\t\t\t\telement.css( \"borderRightWidth\" ),\n\t\t\t\telement.css( \"borderBottomWidth\" ),\n\t\t\t\telement.css( \"borderLeftWidth\" )\n\t\t\t],\n\t\t\tpaddings = [\n\t\t\t\telement.css( \"paddingTop\" ),\n\t\t\t\telement.css( \"paddingRight\" ),\n\t\t\t\telement.css( \"paddingBottom\" ),\n\t\t\t\telement.css( \"paddingLeft\" )\n\t\t\t];\n\n\t\tfor ( ; i < 4; i++ ) {\n\t\t\twidths[ i ] = ( parseFloat( borders[ i ] ) || 0 );\n\t\t\twidths[ i ] += ( parseFloat( paddings[ i ] ) || 0 );\n\t\t}\n\n\t\treturn {\n\t\t\theight: widths[ 0 ] + widths[ 2 ],\n\t\t\twidth: widths[ 1 ] + widths[ 3 ]\n\t\t};\n\t},\n\n\t_proportionallyResize: function() {\n\n\t\tif ( !this._proportionallyResizeElements.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar prel,\n\t\t\ti = 0,\n\t\t\telement = this.helper || this.element;\n\n\t\tfor ( ; i < this._proportionallyResizeElements.length; i++ ) {\n\n\t\t\tprel = this._proportionallyResizeElements[ i ];\n\n\t\t\t// TODO: Seems like a bug to cache this.outerDimensions\n\t\t\t// considering that we are in a loop.\n\t\t\tif ( !this.outerDimensions ) {\n\t\t\t\tthis.outerDimensions = this._getPaddingPlusBorderDimensions( prel );\n\t\t\t}\n\n\t\t\tprel.css( {\n\t\t\t\theight: ( element.height() - this.outerDimensions.height ) || 0,\n\t\t\t\twidth: ( element.width() - this.outerDimensions.width ) || 0\n\t\t\t} );\n\n\t\t}\n\n\t},\n\n\t_renderProxy: function() {\n\n\t\tvar el = this.element, o = this.options;\n\t\tthis.elementOffset = el.offset();\n\n\t\tif ( this._helper ) {\n\n\t\t\tthis.helper = this.helper || $( \"<div style='overflow:hidden;'></div>\" );\n\n\t\t\tthis._addClass( this.helper, this._helper );\n\t\t\tthis.helper.css( {\n\t\t\t\twidth: this.element.outerWidth(),\n\t\t\t\theight: this.element.outerHeight(),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tleft: this.elementOffset.left + \"px\",\n\t\t\t\ttop: this.elementOffset.top + \"px\",\n\t\t\t\tzIndex: ++o.zIndex //TODO: Don't modify option\n\t\t\t} );\n\n\t\t\tthis.helper\n\t\t\t\t.appendTo( \"body\" )\n\t\t\t\t.disableSelection();\n\n\t\t} else {\n\t\t\tthis.helper = this.element;\n\t\t}\n\n\t},\n\n\t_change: {\n\t\te: function( event, dx ) {\n\t\t\treturn { width: this.originalSize.width + dx };\n\t\t},\n\t\tw: function( event, dx ) {\n\t\t\tvar cs = this.originalSize, sp = this.originalPosition;\n\t\t\treturn { left: sp.left + dx, width: cs.width - dx };\n\t\t},\n\t\tn: function( event, dx, dy ) {\n\t\t\tvar cs = this.originalSize, sp = this.originalPosition;\n\t\t\treturn { top: sp.top + dy, height: cs.height - dy };\n\t\t},\n\t\ts: function( event, dx, dy ) {\n\t\t\treturn { height: this.originalSize.height + dy };\n\t\t},\n\t\tse: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.s.apply( this, arguments ),\n\t\t\t\tthis._change.e.apply( this, [ event, dx, dy ] ) );\n\t\t},\n\t\tsw: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.s.apply( this, arguments ),\n\t\t\t\tthis._change.w.apply( this, [ event, dx, dy ] ) );\n\t\t},\n\t\tne: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.n.apply( this, arguments ),\n\t\t\t\tthis._change.e.apply( this, [ event, dx, dy ] ) );\n\t\t},\n\t\tnw: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.n.apply( this, arguments ),\n\t\t\t\tthis._change.w.apply( this, [ event, dx, dy ] ) );\n\t\t}\n\t},\n\n\t_propagate: function( n, event ) {\n\t\t$.ui.plugin.call( this, n, [ event, this.ui() ] );\n\t\t( n !== \"resize\" && this._trigger( n, event, this.ui() ) );\n\t},\n\n\tplugins: {},\n\n\tui: function() {\n\t\treturn {\n\t\t\toriginalElement: this.originalElement,\n\t\t\telement: this.element,\n\t\t\thelper: this.helper,\n\t\t\tposition: this.position,\n\t\t\tsize: this.size,\n\t\t\toriginalSize: this.originalSize,\n\t\t\toriginalPosition: this.originalPosition\n\t\t};\n\t}\n\n} );\n\n/*\n * Resizable Extensions\n */\n\n$.ui.plugin.add( \"resizable\", \"animate\", {\n\n\tstop: function( event ) {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tpr = that._proportionallyResizeElements,\n\t\t\tista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ),\n\t\t\tsoffseth = ista && that._hasScroll( pr[ 0 ], \"left\" ) ? 0 : that.sizeDiff.height,\n\t\t\tsoffsetw = ista ? 0 : that.sizeDiff.width,\n\t\t\tstyle = {\n\t\t\t\twidth: ( that.size.width - soffsetw ),\n\t\t\t\theight: ( that.size.height - soffseth )\n\t\t\t},\n\t\t\tleft = ( parseFloat( that.element.css( \"left\" ) ) +\n\t\t\t\t( that.position.left - that.originalPosition.left ) ) || null,\n\t\t\ttop = ( parseFloat( that.element.css( \"top\" ) ) +\n\t\t\t\t( that.position.top - that.originalPosition.top ) ) || null;\n\n\t\tthat.element.animate(\n\t\t\t$.extend( style, top && left ? { top: top, left: left } : {} ), {\n\t\t\t\tduration: o.animateDuration,\n\t\t\t\teasing: o.animateEasing,\n\t\t\t\tstep: function() {\n\n\t\t\t\t\tvar data = {\n\t\t\t\t\t\twidth: parseFloat( that.element.css( \"width\" ) ),\n\t\t\t\t\t\theight: parseFloat( that.element.css( \"height\" ) ),\n\t\t\t\t\t\ttop: parseFloat( that.element.css( \"top\" ) ),\n\t\t\t\t\t\tleft: parseFloat( that.element.css( \"left\" ) )\n\t\t\t\t\t};\n\n\t\t\t\t\tif ( pr && pr.length ) {\n\t\t\t\t\t\t$( pr[ 0 ] ).css( { width: data.width, height: data.height } );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Propagating resize, and updating values for each animation step\n\t\t\t\t\tthat._updateCache( data );\n\t\t\t\t\tthat._propagate( \"resize\", event );\n\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n} );\n\n$.ui.plugin.add( \"resizable\", \"containment\", {\n\n\tstart: function() {\n\t\tvar element, p, co, ch, cw, width, height,\n\t\t\tthat = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tel = that.element,\n\t\t\toc = o.containment,\n\t\t\tce = ( oc instanceof $ ) ?\n\t\t\t\toc.get( 0 ) :\n\t\t\t\t( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;\n\n\t\tif ( !ce ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthat.containerElement = $( ce );\n\n\t\tif ( /document/.test( oc ) || oc === document ) {\n\t\t\tthat.containerOffset = {\n\t\t\t\tleft: 0,\n\t\t\t\ttop: 0\n\t\t\t};\n\t\t\tthat.containerPosition = {\n\t\t\t\tleft: 0,\n\t\t\t\ttop: 0\n\t\t\t};\n\n\t\t\tthat.parentData = {\n\t\t\t\telement: $( document ),\n\t\t\t\tleft: 0,\n\t\t\t\ttop: 0,\n\t\t\t\twidth: $( document ).width(),\n\t\t\t\theight: $( document ).height() || document.body.parentNode.scrollHeight\n\t\t\t};\n\t\t} else {\n\t\t\telement = $( ce );\n\t\t\tp = [];\n\t\t\t$( [ \"Top\", \"Right\", \"Left\", \"Bottom\" ] ).each( function( i, name ) {\n\t\t\t\tp[ i ] = that._num( element.css( \"padding\" + name ) );\n\t\t\t} );\n\n\t\t\tthat.containerOffset = element.offset();\n\t\t\tthat.containerPosition = element.position();\n\t\t\tthat.containerSize = {\n\t\t\t\theight: ( element.innerHeight() - p[ 3 ] ),\n\t\t\t\twidth: ( element.innerWidth() - p[ 1 ] )\n\t\t\t};\n\n\t\t\tco = that.containerOffset;\n\t\t\tch = that.containerSize.height;\n\t\t\tcw = that.containerSize.width;\n\t\t\twidth = ( that._hasScroll ( ce, \"left\" ) ? ce.scrollWidth : cw );\n\t\t\theight = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;\n\n\t\t\tthat.parentData = {\n\t\t\t\telement: ce,\n\t\t\t\tleft: co.left,\n\t\t\t\ttop: co.top,\n\t\t\t\twidth: width,\n\t\t\t\theight: height\n\t\t\t};\n\t\t}\n\t},\n\n\tresize: function( event ) {\n\t\tvar woset, hoset, isParent, isOffsetRelative,\n\t\t\tthat = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tco = that.containerOffset,\n\t\t\tcp = that.position,\n\t\t\tpRatio = that._aspectRatio || event.shiftKey,\n\t\t\tcop = {\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0\n\t\t\t},\n\t\t\tce = that.containerElement,\n\t\t\tcontinueResize = true;\n\n\t\tif ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( \"position\" ) ) ) {\n\t\t\tcop = co;\n\t\t}\n\n\t\tif ( cp.left < ( that._helper ? co.left : 0 ) ) {\n\t\t\tthat.size.width = that.size.width +\n\t\t\t\t( that._helper ?\n\t\t\t\t\t( that.position.left - co.left ) :\n\t\t\t\t\t( that.position.left - cop.left ) );\n\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.height = that.size.width / that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t\tthat.position.left = o.helper ? co.left : 0;\n\t\t}\n\n\t\tif ( cp.top < ( that._helper ? co.top : 0 ) ) {\n\t\t\tthat.size.height = that.size.height +\n\t\t\t\t( that._helper ?\n\t\t\t\t\t( that.position.top - co.top ) :\n\t\t\t\t\tthat.position.top );\n\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.width = that.size.height * that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t\tthat.position.top = that._helper ? co.top : 0;\n\t\t}\n\n\t\tisParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );\n\t\tisOffsetRelative = /relative|absolute/.test( that.containerElement.css( \"position\" ) );\n\n\t\tif ( isParent && isOffsetRelative ) {\n\t\t\tthat.offset.left = that.parentData.left + that.position.left;\n\t\t\tthat.offset.top = that.parentData.top + that.position.top;\n\t\t} else {\n\t\t\tthat.offset.left = that.element.offset().left;\n\t\t\tthat.offset.top = that.element.offset().top;\n\t\t}\n\n\t\twoset = Math.abs( that.sizeDiff.width +\n\t\t\t( that._helper ?\n\t\t\t\tthat.offset.left - cop.left :\n\t\t\t\t( that.offset.left - co.left ) ) );\n\n\t\thoset = Math.abs( that.sizeDiff.height +\n\t\t\t( that._helper ?\n\t\t\t\tthat.offset.top - cop.top :\n\t\t\t\t( that.offset.top - co.top ) ) );\n\n\t\tif ( woset + that.size.width >= that.parentData.width ) {\n\t\t\tthat.size.width = that.parentData.width - woset;\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.height = that.size.width / that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t}\n\n\t\tif ( hoset + that.size.height >= that.parentData.height ) {\n\t\t\tthat.size.height = that.parentData.height - hoset;\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.width = that.size.height * that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t}\n\n\t\tif ( !continueResize ) {\n\t\t\tthat.position.left = that.prevPosition.left;\n\t\t\tthat.position.top = that.prevPosition.top;\n\t\t\tthat.size.width = that.prevSize.width;\n\t\t\tthat.size.height = that.prevSize.height;\n\t\t}\n\t},\n\n\tstop: function() {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tco = that.containerOffset,\n\t\t\tcop = that.containerPosition,\n\t\t\tce = that.containerElement,\n\t\t\thelper = $( that.helper ),\n\t\t\tho = helper.offset(),\n\t\t\tw = helper.outerWidth() - that.sizeDiff.width,\n\t\t\th = helper.outerHeight() - that.sizeDiff.height;\n\n\t\tif ( that._helper && !o.animate && ( /relative/ ).test( ce.css( \"position\" ) ) ) {\n\t\t\t$( this ).css( {\n\t\t\t\tleft: ho.left - cop.left - co.left,\n\t\t\t\twidth: w,\n\t\t\t\theight: h\n\t\t\t} );\n\t\t}\n\n\t\tif ( that._helper && !o.animate && ( /static/ ).test( ce.css( \"position\" ) ) ) {\n\t\t\t$( this ).css( {\n\t\t\t\tleft: ho.left - cop.left - co.left,\n\t\t\t\twidth: w,\n\t\t\t\theight: h\n\t\t\t} );\n\t\t}\n\t}\n} );\n\n$.ui.plugin.add( \"resizable\", \"alsoResize\", {\n\n\tstart: function() {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options;\n\n\t\t$( o.alsoResize ).each( function() {\n\t\t\tvar el = $( this );\n\t\t\tel.data( \"ui-resizable-alsoresize\", {\n\t\t\t\twidth: parseFloat( el.width() ), height: parseFloat( el.height() ),\n\t\t\t\tleft: parseFloat( el.css( \"left\" ) ), top: parseFloat( el.css( \"top\" ) )\n\t\t\t} );\n\t\t} );\n\t},\n\n\tresize: function( event, ui ) {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tos = that.originalSize,\n\t\t\top = that.originalPosition,\n\t\t\tdelta = {\n\t\t\t\theight: ( that.size.height - os.height ) || 0,\n\t\t\t\twidth: ( that.size.width - os.width ) || 0,\n\t\t\t\ttop: ( that.position.top - op.top ) || 0,\n\t\t\t\tleft: ( that.position.left - op.left ) || 0\n\t\t\t};\n\n\t\t\t$( o.alsoResize ).each( function() {\n\t\t\t\tvar el = $( this ), start = $( this ).data( \"ui-resizable-alsoresize\" ), style = {},\n\t\t\t\t\tcss = el.parents( ui.originalElement[ 0 ] ).length ?\n\t\t\t\t\t\t\t[ \"width\", \"height\" ] :\n\t\t\t\t\t\t\t[ \"width\", \"height\", \"top\", \"left\" ];\n\n\t\t\t\t$.each( css, function( i, prop ) {\n\t\t\t\t\tvar sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 );\n\t\t\t\t\tif ( sum && sum >= 0 ) {\n\t\t\t\t\t\tstyle[ prop ] = sum || null;\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\t\tel.css( style );\n\t\t\t} );\n\t},\n\n\tstop: function() {\n\t\t$( this ).removeData( \"ui-resizable-alsoresize\" );\n\t}\n} );\n\n$.ui.plugin.add( \"resizable\", \"ghost\", {\n\n\tstart: function() {\n\n\t\tvar that = $( this ).resizable( \"instance\" ), cs = that.size;\n\n\t\tthat.ghost = that.originalElement.clone();\n\t\tthat.ghost.css( {\n\t\t\topacity: 0.25,\n\t\t\tdisplay: \"block\",\n\t\t\tposition: \"relative\",\n\t\t\theight: cs.height,\n\t\t\twidth: cs.width,\n\t\t\tmargin: 0,\n\t\t\tleft: 0,\n\t\t\ttop: 0\n\t\t} );\n\n\t\tthat._addClass( that.ghost, \"ui-resizable-ghost\" );\n\n\t\t// DEPRECATED\n\t\t// TODO: remove after 1.12\n\t\tif ( $.uiBackCompat !== false && typeof that.options.ghost === \"string\" ) {\n\n\t\t\t// Ghost option\n\t\t\tthat.ghost.addClass( this.options.ghost );\n\t\t}\n\n\t\tthat.ghost.appendTo( that.helper );\n\n\t},\n\n\tresize: function() {\n\t\tvar that = $( this ).resizable( \"instance\" );\n\t\tif ( that.ghost ) {\n\t\t\tthat.ghost.css( {\n\t\t\t\tposition: \"relative\",\n\t\t\t\theight: that.size.height,\n\t\t\t\twidth: that.size.width\n\t\t\t} );\n\t\t}\n\t},\n\n\tstop: function() {\n\t\tvar that = $( this ).resizable( \"instance\" );\n\t\tif ( that.ghost && that.helper ) {\n\t\t\tthat.helper.get( 0 ).removeChild( that.ghost.get( 0 ) );\n\t\t}\n\t}\n\n} );\n\n$.ui.plugin.add( \"resizable\", \"grid\", {\n\n\tresize: function() {\n\t\tvar outerDimensions,\n\t\t\tthat = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tcs = that.size,\n\t\t\tos = that.originalSize,\n\t\t\top = that.originalPosition,\n\t\t\ta = that.axis,\n\t\t\tgrid = typeof o.grid === \"number\" ? [ o.grid, o.grid ] : o.grid,\n\t\t\tgridX = ( grid[ 0 ] || 1 ),\n\t\t\tgridY = ( grid[ 1 ] || 1 ),\n\t\t\tox = Math.round( ( cs.width - os.width ) / gridX ) * gridX,\n\t\t\toy = Math.round( ( cs.height - os.height ) / gridY ) * gridY,\n\t\t\tnewWidth = os.width + ox,\n\t\t\tnewHeight = os.height + oy,\n\t\t\tisMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ),\n\t\t\tisMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ),\n\t\t\tisMinWidth = o.minWidth && ( o.minWidth > newWidth ),\n\t\t\tisMinHeight = o.minHeight && ( o.minHeight > newHeight );\n\n\t\to.grid = grid;\n\n\t\tif ( isMinWidth ) {\n\t\t\tnewWidth += gridX;\n\t\t}\n\t\tif ( isMinHeight ) {\n\t\t\tnewHeight += gridY;\n\t\t}\n\t\tif ( isMaxWidth ) {\n\t\t\tnewWidth -= gridX;\n\t\t}\n\t\tif ( isMaxHeight ) {\n\t\t\tnewHeight -= gridY;\n\t\t}\n\n\t\tif ( /^(se|s|e)$/.test( a ) ) {\n\t\t\tthat.size.width = newWidth;\n\t\t\tthat.size.height = newHeight;\n\t\t} else if ( /^(ne)$/.test( a ) ) {\n\t\t\tthat.size.width = newWidth;\n\t\t\tthat.size.height = newHeight;\n\t\t\tthat.position.top = op.top - oy;\n\t\t} else if ( /^(sw)$/.test( a ) ) {\n\t\t\tthat.size.width = newWidth;\n\t\t\tthat.size.height = newHeight;\n\t\t\tthat.position.left = op.left - ox;\n\t\t} else {\n\t\t\tif ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) {\n\t\t\t\touterDimensions = that._getPaddingPlusBorderDimensions( this );\n\t\t\t}\n\n\t\t\tif ( newHeight - gridY > 0 ) {\n\t\t\t\tthat.size.height = newHeight;\n\t\t\t\tthat.position.top = op.top - oy;\n\t\t\t} else {\n\t\t\t\tnewHeight = gridY - outerDimensions.height;\n\t\t\t\tthat.size.height = newHeight;\n\t\t\t\tthat.position.top = op.top + os.height - newHeight;\n\t\t\t}\n\t\t\tif ( newWidth - gridX > 0 ) {\n\t\t\t\tthat.size.width = newWidth;\n\t\t\t\tthat.position.left = op.left - ox;\n\t\t\t} else {\n\t\t\t\tnewWidth = gridX - outerDimensions.width;\n\t\t\t\tthat.size.width = newWidth;\n\t\t\t\tthat.position.left = op.left + os.width - newWidth;\n\t\t\t}\n\t\t}\n\t}\n\n} );\n\nvar widgetsResizable = $.ui.resizable;\n\n\n/*!\n * jQuery UI Dialog 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Dialog\n//>>group: Widgets\n//>>description: Displays customizable dialog windows.\n//>>docs: http://api.jqueryui.com/dialog/\n//>>demos: http://jqueryui.com/dialog/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/dialog.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.dialog\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tappendTo: \"body\",\n\t\tautoOpen: true,\n\t\tbuttons: [],\n\t\tclasses: {\n\t\t\t\"ui-dialog\": \"ui-corner-all\",\n\t\t\t\"ui-dialog-titlebar\": \"ui-corner-all\"\n\t\t},\n\t\tcloseOnEscape: true,\n\t\tcloseText: \"Close\",\n\t\tdraggable: true,\n\t\thide: null,\n\t\theight: \"auto\",\n\t\tmaxHeight: null,\n\t\tmaxWidth: null,\n\t\tminHeight: 150,\n\t\tminWidth: 150,\n\t\tmodal: false,\n\t\tposition: {\n\t\t\tmy: \"center\",\n\t\t\tat: \"center\",\n\t\t\tof: window,\n\t\t\tcollision: \"fit\",\n\n\t\t\t// Ensure the titlebar is always visible\n\t\t\tusing: function( pos ) {\n\t\t\t\tvar topOffset = $( this ).css( pos ).offset().top;\n\t\t\t\tif ( topOffset < 0 ) {\n\t\t\t\t\t$( this ).css( \"top\", pos.top - topOffset );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tresizable: true,\n\t\tshow: null,\n\t\ttitle: null,\n\t\twidth: 300,\n\n\t\t// Callbacks\n\t\tbeforeClose: null,\n\t\tclose: null,\n\t\tdrag: null,\n\t\tdragStart: null,\n\t\tdragStop: null,\n\t\tfocus: null,\n\t\topen: null,\n\t\tresize: null,\n\t\tresizeStart: null,\n\t\tresizeStop: null\n\t},\n\n\tsizeRelatedOptions: {\n\t\tbuttons: true,\n\t\theight: true,\n\t\tmaxHeight: true,\n\t\tmaxWidth: true,\n\t\tminHeight: true,\n\t\tminWidth: true,\n\t\twidth: true\n\t},\n\n\tresizableRelatedOptions: {\n\t\tmaxHeight: true,\n\t\tmaxWidth: true,\n\t\tminHeight: true,\n\t\tminWidth: true\n\t},\n\n\t_create: function() {\n\t\tthis.originalCss = {\n\t\t\tdisplay: this.element[ 0 ].style.display,\n\t\t\twidth: this.element[ 0 ].style.width,\n\t\t\tminHeight: this.element[ 0 ].style.minHeight,\n\t\t\tmaxHeight: this.element[ 0 ].style.maxHeight,\n\t\t\theight: this.element[ 0 ].style.height\n\t\t};\n\t\tthis.originalPosition = {\n\t\t\tparent: this.element.parent(),\n\t\t\tindex: this.element.parent().children().index( this.element )\n\t\t};\n\t\tthis.originalTitle = this.element.attr( \"title\" );\n\t\tif ( this.options.title == null && this.originalTitle != null ) {\n\t\t\tthis.options.title = this.originalTitle;\n\t\t}\n\n\t\t// Dialogs can't be disabled\n\t\tif ( this.options.disabled ) {\n\t\t\tthis.options.disabled = false;\n\t\t}\n\n\t\tthis._createWrapper();\n\n\t\tthis.element\n\t\t\t.show()\n\t\t\t.removeAttr( \"title\" )\n\t\t\t.appendTo( this.uiDialog );\n\n\t\tthis._addClass( \"ui-dialog-content\", \"ui-widget-content\" );\n\n\t\tthis._createTitlebar();\n\t\tthis._createButtonPane();\n\n\t\tif ( this.options.draggable && $.fn.draggable ) {\n\t\t\tthis._makeDraggable();\n\t\t}\n\t\tif ( this.options.resizable && $.fn.resizable ) {\n\t\t\tthis._makeResizable();\n\t\t}\n\n\t\tthis._isOpen = false;\n\n\t\tthis._trackFocus();\n\t},\n\n\t_init: function() {\n\t\tif ( this.options.autoOpen ) {\n\t\t\tthis.open();\n\t\t}\n\t},\n\n\t_appendTo: function() {\n\t\tvar element = this.options.appendTo;\n\t\tif ( element && ( element.jquery || element.nodeType ) ) {\n\t\t\treturn $( element );\n\t\t}\n\t\treturn this.document.find( element || \"body\" ).eq( 0 );\n\t},\n\n\t_destroy: function() {\n\t\tvar next,\n\t\t\toriginalPosition = this.originalPosition;\n\n\t\tthis._untrackInstance();\n\t\tthis._destroyOverlay();\n\n\t\tthis.element\n\t\t\t.removeUniqueId()\n\t\t\t.css( this.originalCss )\n\n\t\t\t// Without detaching first, the following becomes really slow\n\t\t\t.detach();\n\n\t\tthis.uiDialog.remove();\n\n\t\tif ( this.originalTitle ) {\n\t\t\tthis.element.attr( \"title\", this.originalTitle );\n\t\t}\n\n\t\tnext = originalPosition.parent.children().eq( originalPosition.index );\n\n\t\t// Don't try to place the dialog next to itself (#8613)\n\t\tif ( next.length && next[ 0 ] !== this.element[ 0 ] ) {\n\t\t\tnext.before( this.element );\n\t\t} else {\n\t\t\toriginalPosition.parent.append( this.element );\n\t\t}\n\t},\n\n\twidget: function() {\n\t\treturn this.uiDialog;\n\t},\n\n\tdisable: $.noop,\n\tenable: $.noop,\n\n\tclose: function( event ) {\n\t\tvar that = this;\n\n\t\tif ( !this._isOpen || this._trigger( \"beforeClose\", event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._isOpen = false;\n\t\tthis._focusedElement = null;\n\t\tthis._destroyOverlay();\n\t\tthis._untrackInstance();\n\n\t\tif ( !this.opener.filter( \":focusable\" ).trigger( \"focus\" ).length ) {\n\n\t\t\t// Hiding a focused element doesn't trigger blur in WebKit\n\t\t\t// so in case we have nothing to focus on, explicitly blur the active element\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=47182\n\t\t\t$.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) );\n\t\t}\n\n\t\tthis._hide( this.uiDialog, this.options.hide, function() {\n\t\t\tthat._trigger( \"close\", event );\n\t\t} );\n\t},\n\n\tisOpen: function() {\n\t\treturn this._isOpen;\n\t},\n\n\tmoveToTop: function() {\n\t\tthis._moveToTop();\n\t},\n\n\t_moveToTop: function( event, silent ) {\n\t\tvar moved = false,\n\t\t\tzIndices = this.uiDialog.siblings( \".ui-front:visible\" ).map( function() {\n\t\t\t\treturn +$( this ).css( \"z-index\" );\n\t\t\t} ).get(),\n\t\t\tzIndexMax = Math.max.apply( null, zIndices );\n\n\t\tif ( zIndexMax >= +this.uiDialog.css( \"z-index\" ) ) {\n\t\t\tthis.uiDialog.css( \"z-index\", zIndexMax + 1 );\n\t\t\tmoved = true;\n\t\t}\n\n\t\tif ( moved && !silent ) {\n\t\t\tthis._trigger( \"focus\", event );\n\t\t}\n\t\treturn moved;\n\t},\n\n\topen: function() {\n\t\tvar that = this;\n\t\tif ( this._isOpen ) {\n\t\t\tif ( this._moveToTop() ) {\n\t\t\t\tthis._focusTabbable();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis._isOpen = true;\n\t\tthis.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) );\n\n\t\tthis._size();\n\t\tthis._position();\n\t\tthis._createOverlay();\n\t\tthis._moveToTop( null, true );\n\n\t\t// Ensure the overlay is moved to the top with the dialog, but only when\n\t\t// opening. The overlay shouldn't move after the dialog is open so that\n\t\t// modeless dialogs opened after the modal dialog stack properly.\n\t\tif ( this.overlay ) {\n\t\t\tthis.overlay.css( \"z-index\", this.uiDialog.css( \"z-index\" ) - 1 );\n\t\t}\n\n\t\tthis._show( this.uiDialog, this.options.show, function() {\n\t\t\tthat._focusTabbable();\n\t\t\tthat._trigger( \"focus\" );\n\t\t} );\n\n\t\t// Track the dialog immediately upon openening in case a focus event\n\t\t// somehow occurs outside of the dialog before an element inside the\n\t\t// dialog is focused (#10152)\n\t\tthis._makeFocusTarget();\n\n\t\tthis._trigger( \"open\" );\n\t},\n\n\t_focusTabbable: function() {\n\n\t\t// Set focus to the first match:\n\t\t// 1. An element that was focused previously\n\t\t// 2. First element inside the dialog matching [autofocus]\n\t\t// 3. Tabbable element inside the content element\n\t\t// 4. Tabbable element inside the buttonpane\n\t\t// 5. The close button\n\t\t// 6. The dialog itself\n\t\tvar hasFocus = this._focusedElement;\n\t\tif ( !hasFocus ) {\n\t\t\thasFocus = this.element.find( \"[autofocus]\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.element.find( \":tabbable\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.uiDialogButtonPane.find( \":tabbable\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.uiDialogTitlebarClose.filter( \":tabbable\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.uiDialog;\n\t\t}\n\t\thasFocus.eq( 0 ).trigger( \"focus\" );\n\t},\n\n\t_keepFocus: function( event ) {\n\t\tfunction checkFocus() {\n\t\t\tvar activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),\n\t\t\t\tisActive = this.uiDialog[ 0 ] === activeElement ||\n\t\t\t\t\t$.contains( this.uiDialog[ 0 ], activeElement );\n\t\t\tif ( !isActive ) {\n\t\t\t\tthis._focusTabbable();\n\t\t\t}\n\t\t}\n\t\tevent.preventDefault();\n\t\tcheckFocus.call( this );\n\n\t\t// support: IE\n\t\t// IE <= 8 doesn't prevent moving focus even with event.preventDefault()\n\t\t// so we check again later\n\t\tthis._delay( checkFocus );\n\t},\n\n\t_createWrapper: function() {\n\t\tthis.uiDialog = $( \"<div>\" )\n\t\t\t.hide()\n\t\t\t.attr( {\n\n\t\t\t\t// Setting tabIndex makes the div focusable\n\t\t\t\ttabIndex: -1,\n\t\t\t\trole: \"dialog\"\n\t\t\t} )\n\t\t\t.appendTo( this._appendTo() );\n\n\t\tthis._addClass( this.uiDialog, \"ui-dialog\", \"ui-widget ui-widget-content ui-front\" );\n\t\tthis._on( this.uiDialog, {\n\t\t\tkeydown: function( event ) {\n\t\t\t\tif ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&\n\t\t\t\t\t\tevent.keyCode === $.ui.keyCode.ESCAPE ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tthis.close( event );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Prevent tabbing out of dialogs\n\t\t\t\tif ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvar tabbables = this.uiDialog.find( \":tabbable\" ),\n\t\t\t\t\tfirst = tabbables.filter( \":first\" ),\n\t\t\t\t\tlast = tabbables.filter( \":last\" );\n\n\t\t\t\tif ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) &&\n\t\t\t\t\t\t!event.shiftKey ) {\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tfirst.trigger( \"focus\" );\n\t\t\t\t\t} );\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t} else if ( ( event.target === first[ 0 ] ||\n\t\t\t\t\t\tevent.target === this.uiDialog[ 0 ] ) && event.shiftKey ) {\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tlast.trigger( \"focus\" );\n\t\t\t\t\t} );\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t},\n\t\t\tmousedown: function( event ) {\n\t\t\t\tif ( this._moveToTop( event ) ) {\n\t\t\t\t\tthis._focusTabbable();\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\t// We assume that any existing aria-describedby attribute means\n\t\t// that the dialog content is marked up properly\n\t\t// otherwise we brute force the content as the description\n\t\tif ( !this.element.find( \"[aria-describedby]\" ).length ) {\n\t\t\tthis.uiDialog.attr( {\n\t\t\t\t\"aria-describedby\": this.element.uniqueId().attr( \"id\" )\n\t\t\t} );\n\t\t}\n\t},\n\n\t_createTitlebar: function() {\n\t\tvar uiDialogTitle;\n\n\t\tthis.uiDialogTitlebar = $( \"<div>\" );\n\t\tthis._addClass( this.uiDialogTitlebar,\n\t\t\t\"ui-dialog-titlebar\", \"ui-widget-header ui-helper-clearfix\" );\n\t\tthis._on( this.uiDialogTitlebar, {\n\t\t\tmousedown: function( event ) {\n\n\t\t\t\t// Don't prevent click on close button (#8838)\n\t\t\t\t// Focusing a dialog that is partially scrolled out of view\n\t\t\t\t// causes the browser to scroll it into view, preventing the click event\n\t\t\t\tif ( !$( event.target ).closest( \".ui-dialog-titlebar-close\" ) ) {\n\n\t\t\t\t\t// Dialog isn't getting focus when dragging (#8063)\n\t\t\t\t\tthis.uiDialog.trigger( \"focus\" );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\t// Support: IE\n\t\t// Use type=\"button\" to prevent enter keypresses in textboxes from closing the\n\t\t// dialog in IE (#9312)\n\t\tthis.uiDialogTitlebarClose = $( \"<button type='button'></button>\" )\n\t\t\t.button( {\n\t\t\t\tlabel: $( \"<a>\" ).text( this.options.closeText ).html(),\n\t\t\t\ticon: \"ui-icon-closethick\",\n\t\t\t\tshowLabel: false\n\t\t\t} )\n\t\t\t.appendTo( this.uiDialogTitlebar );\n\n\t\tthis._addClass( this.uiDialogTitlebarClose, \"ui-dialog-titlebar-close\" );\n\t\tthis._on( this.uiDialogTitlebarClose, {\n\t\t\tclick: function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.close( event );\n\t\t\t}\n\t\t} );\n\n\t\tuiDialogTitle = $( \"<span>\" ).uniqueId().prependTo( this.uiDialogTitlebar );\n\t\tthis._addClass( uiDialogTitle, \"ui-dialog-title\" );\n\t\tthis._title( uiDialogTitle );\n\n\t\tthis.uiDialogTitlebar.prependTo( this.uiDialog );\n\n\t\tthis.uiDialog.attr( {\n\t\t\t\"aria-labelledby\": uiDialogTitle.attr( \"id\" )\n\t\t} );\n\t},\n\n\t_title: function( title ) {\n\t\tif ( this.options.title ) {\n\t\t\ttitle.text( this.options.title );\n\t\t} else {\n\t\t\ttitle.html( \"&#160;\" );\n\t\t}\n\t},\n\n\t_createButtonPane: function() {\n\t\tthis.uiDialogButtonPane = $( \"<div>\" );\n\t\tthis._addClass( this.uiDialogButtonPane, \"ui-dialog-buttonpane\",\n\t\t\t\"ui-widget-content ui-helper-clearfix\" );\n\n\t\tthis.uiButtonSet = $( \"<div>\" )\n\t\t\t.appendTo( this.uiDialogButtonPane );\n\t\tthis._addClass( this.uiButtonSet, \"ui-dialog-buttonset\" );\n\n\t\tthis._createButtons();\n\t},\n\n\t_createButtons: function() {\n\t\tvar that = this,\n\t\t\tbuttons = this.options.buttons;\n\n\t\t// If we already have a button pane, remove it\n\t\tthis.uiDialogButtonPane.remove();\n\t\tthis.uiButtonSet.empty();\n\n\t\tif ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) {\n\t\t\tthis._removeClass( this.uiDialog, \"ui-dialog-buttons\" );\n\t\t\treturn;\n\t\t}\n\n\t\t$.each( buttons, function( name, props ) {\n\t\t\tvar click, buttonOptions;\n\t\t\tprops = $.isFunction( props ) ?\n\t\t\t\t{ click: props, text: name } :\n\t\t\t\tprops;\n\n\t\t\t// Default to a non-submitting button\n\t\t\tprops = $.extend( { type: \"button\" }, props );\n\n\t\t\t// Change the context for the click callback to be the main element\n\t\t\tclick = props.click;\n\t\t\tbuttonOptions = {\n\t\t\t\ticon: props.icon,\n\t\t\t\ticonPosition: props.iconPosition,\n\t\t\t\tshowLabel: props.showLabel,\n\n\t\t\t\t// Deprecated options\n\t\t\t\ticons: props.icons,\n\t\t\t\ttext: props.text\n\t\t\t};\n\n\t\t\tdelete props.click;\n\t\t\tdelete props.icon;\n\t\t\tdelete props.iconPosition;\n\t\t\tdelete props.showLabel;\n\n\t\t\t// Deprecated options\n\t\t\tdelete props.icons;\n\t\t\tif ( typeof props.text === \"boolean\" ) {\n\t\t\t\tdelete props.text;\n\t\t\t}\n\n\t\t\t$( \"<button></button>\", props )\n\t\t\t\t.button( buttonOptions )\n\t\t\t\t.appendTo( that.uiButtonSet )\n\t\t\t\t.on( \"click\", function() {\n\t\t\t\t\tclick.apply( that.element[ 0 ], arguments );\n\t\t\t\t} );\n\t\t} );\n\t\tthis._addClass( this.uiDialog, \"ui-dialog-buttons\" );\n\t\tthis.uiDialogButtonPane.appendTo( this.uiDialog );\n\t},\n\n\t_makeDraggable: function() {\n\t\tvar that = this,\n\t\t\toptions = this.options;\n\n\t\tfunction filteredUi( ui ) {\n\t\t\treturn {\n\t\t\t\tposition: ui.position,\n\t\t\t\toffset: ui.offset\n\t\t\t};\n\t\t}\n\n\t\tthis.uiDialog.draggable( {\n\t\t\tcancel: \".ui-dialog-content, .ui-dialog-titlebar-close\",\n\t\t\thandle: \".ui-dialog-titlebar\",\n\t\t\tcontainment: \"document\",\n\t\t\tstart: function( event, ui ) {\n\t\t\t\tthat._addClass( $( this ), \"ui-dialog-dragging\" );\n\t\t\t\tthat._blockFrames();\n\t\t\t\tthat._trigger( \"dragStart\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tdrag: function( event, ui ) {\n\t\t\t\tthat._trigger( \"drag\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tstop: function( event, ui ) {\n\t\t\t\tvar left = ui.offset.left - that.document.scrollLeft(),\n\t\t\t\t\ttop = ui.offset.top - that.document.scrollTop();\n\n\t\t\t\toptions.position = {\n\t\t\t\t\tmy: \"left top\",\n\t\t\t\t\tat: \"left\" + ( left >= 0 ? \"+\" : \"\" ) + left + \" \" +\n\t\t\t\t\t\t\"top\" + ( top >= 0 ? \"+\" : \"\" ) + top,\n\t\t\t\t\tof: that.window\n\t\t\t\t};\n\t\t\t\tthat._removeClass( $( this ), \"ui-dialog-dragging\" );\n\t\t\t\tthat._unblockFrames();\n\t\t\t\tthat._trigger( \"dragStop\", event, filteredUi( ui ) );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_makeResizable: function() {\n\t\tvar that = this,\n\t\t\toptions = this.options,\n\t\t\thandles = options.resizable,\n\n\t\t\t// .ui-resizable has position: relative defined in the stylesheet\n\t\t\t// but dialogs have to use absolute or fixed positioning\n\t\t\tposition = this.uiDialog.css( \"position\" ),\n\t\t\tresizeHandles = typeof handles === \"string\" ?\n\t\t\t\thandles :\n\t\t\t\t\"n,e,s,w,se,sw,ne,nw\";\n\n\t\tfunction filteredUi( ui ) {\n\t\t\treturn {\n\t\t\t\toriginalPosition: ui.originalPosition,\n\t\t\t\toriginalSize: ui.originalSize,\n\t\t\t\tposition: ui.position,\n\t\t\t\tsize: ui.size\n\t\t\t};\n\t\t}\n\n\t\tthis.uiDialog.resizable( {\n\t\t\tcancel: \".ui-dialog-content\",\n\t\t\tcontainment: \"document\",\n\t\t\talsoResize: this.element,\n\t\t\tmaxWidth: options.maxWidth,\n\t\t\tmaxHeight: options.maxHeight,\n\t\t\tminWidth: options.minWidth,\n\t\t\tminHeight: this._minHeight(),\n\t\t\thandles: resizeHandles,\n\t\t\tstart: function( event, ui ) {\n\t\t\t\tthat._addClass( $( this ), \"ui-dialog-resizing\" );\n\t\t\t\tthat._blockFrames();\n\t\t\t\tthat._trigger( \"resizeStart\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tresize: function( event, ui ) {\n\t\t\t\tthat._trigger( \"resize\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tstop: function( event, ui ) {\n\t\t\t\tvar offset = that.uiDialog.offset(),\n\t\t\t\t\tleft = offset.left - that.document.scrollLeft(),\n\t\t\t\t\ttop = offset.top - that.document.scrollTop();\n\n\t\t\t\toptions.height = that.uiDialog.height();\n\t\t\t\toptions.width = that.uiDialog.width();\n\t\t\t\toptions.position = {\n\t\t\t\t\tmy: \"left top\",\n\t\t\t\t\tat: \"left\" + ( left >= 0 ? \"+\" : \"\" ) + left + \" \" +\n\t\t\t\t\t\t\"top\" + ( top >= 0 ? \"+\" : \"\" ) + top,\n\t\t\t\t\tof: that.window\n\t\t\t\t};\n\t\t\t\tthat._removeClass( $( this ), \"ui-dialog-resizing\" );\n\t\t\t\tthat._unblockFrames();\n\t\t\t\tthat._trigger( \"resizeStop\", event, filteredUi( ui ) );\n\t\t\t}\n\t\t} )\n\t\t\t.css( \"position\", position );\n\t},\n\n\t_trackFocus: function() {\n\t\tthis._on( this.widget(), {\n\t\t\tfocusin: function( event ) {\n\t\t\t\tthis._makeFocusTarget();\n\t\t\t\tthis._focusedElement = $( event.target );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_makeFocusTarget: function() {\n\t\tthis._untrackInstance();\n\t\tthis._trackingInstances().unshift( this );\n\t},\n\n\t_untrackInstance: function() {\n\t\tvar instances = this._trackingInstances(),\n\t\t\texists = $.inArray( this, instances );\n\t\tif ( exists !== -1 ) {\n\t\t\tinstances.splice( exists, 1 );\n\t\t}\n\t},\n\n\t_trackingInstances: function() {\n\t\tvar instances = this.document.data( \"ui-dialog-instances\" );\n\t\tif ( !instances ) {\n\t\t\tinstances = [];\n\t\t\tthis.document.data( \"ui-dialog-instances\", instances );\n\t\t}\n\t\treturn instances;\n\t},\n\n\t_minHeight: function() {\n\t\tvar options = this.options;\n\n\t\treturn options.height === \"auto\" ?\n\t\t\toptions.minHeight :\n\t\t\tMath.min( options.minHeight, options.height );\n\t},\n\n\t_position: function() {\n\n\t\t// Need to show the dialog to get the actual offset in the position plugin\n\t\tvar isVisible = this.uiDialog.is( \":visible\" );\n\t\tif ( !isVisible ) {\n\t\t\tthis.uiDialog.show();\n\t\t}\n\t\tthis.uiDialog.position( this.options.position );\n\t\tif ( !isVisible ) {\n\t\t\tthis.uiDialog.hide();\n\t\t}\n\t},\n\n\t_setOptions: function( options ) {\n\t\tvar that = this,\n\t\t\tresize = false,\n\t\t\tresizableOptions = {};\n\n\t\t$.each( options, function( key, value ) {\n\t\t\tthat._setOption( key, value );\n\n\t\t\tif ( key in that.sizeRelatedOptions ) {\n\t\t\t\tresize = true;\n\t\t\t}\n\t\t\tif ( key in that.resizableRelatedOptions ) {\n\t\t\t\tresizableOptions[ key ] = value;\n\t\t\t}\n\t\t} );\n\n\t\tif ( resize ) {\n\t\t\tthis._size();\n\t\t\tthis._position();\n\t\t}\n\t\tif ( this.uiDialog.is( \":data(ui-resizable)\" ) ) {\n\t\t\tthis.uiDialog.resizable( \"option\", resizableOptions );\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar isDraggable, isResizable,\n\t\t\tuiDialog = this.uiDialog;\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"appendTo\" ) {\n\t\t\tthis.uiDialog.appendTo( this._appendTo() );\n\t\t}\n\n\t\tif ( key === \"buttons\" ) {\n\t\t\tthis._createButtons();\n\t\t}\n\n\t\tif ( key === \"closeText\" ) {\n\t\t\tthis.uiDialogTitlebarClose.button( {\n\n\t\t\t\t// Ensure that we always pass a string\n\t\t\t\tlabel: $( \"<a>\" ).text( \"\" + this.options.closeText ).html()\n\t\t\t} );\n\t\t}\n\n\t\tif ( key === \"draggable\" ) {\n\t\t\tisDraggable = uiDialog.is( \":data(ui-draggable)\" );\n\t\t\tif ( isDraggable && !value ) {\n\t\t\t\tuiDialog.draggable( \"destroy\" );\n\t\t\t}\n\n\t\t\tif ( !isDraggable && value ) {\n\t\t\t\tthis._makeDraggable();\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"position\" ) {\n\t\t\tthis._position();\n\t\t}\n\n\t\tif ( key === \"resizable\" ) {\n\n\t\t\t// currently resizable, becoming non-resizable\n\t\t\tisResizable = uiDialog.is( \":data(ui-resizable)\" );\n\t\t\tif ( isResizable && !value ) {\n\t\t\t\tuiDialog.resizable( \"destroy\" );\n\t\t\t}\n\n\t\t\t// Currently resizable, changing handles\n\t\t\tif ( isResizable && typeof value === \"string\" ) {\n\t\t\t\tuiDialog.resizable( \"option\", \"handles\", value );\n\t\t\t}\n\n\t\t\t// Currently non-resizable, becoming resizable\n\t\t\tif ( !isResizable && value !== false ) {\n\t\t\t\tthis._makeResizable();\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"title\" ) {\n\t\t\tthis._title( this.uiDialogTitlebar.find( \".ui-dialog-title\" ) );\n\t\t}\n\t},\n\n\t_size: function() {\n\n\t\t// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content\n\t\t// divs will both have width and height set, so we need to reset them\n\t\tvar nonContentHeight, minContentHeight, maxContentHeight,\n\t\t\toptions = this.options;\n\n\t\t// Reset content sizing\n\t\tthis.element.show().css( {\n\t\t\twidth: \"auto\",\n\t\t\tminHeight: 0,\n\t\t\tmaxHeight: \"none\",\n\t\t\theight: 0\n\t\t} );\n\n\t\tif ( options.minWidth > options.width ) {\n\t\t\toptions.width = options.minWidth;\n\t\t}\n\n\t\t// Reset wrapper sizing\n\t\t// determine the height of all the non-content elements\n\t\tnonContentHeight = this.uiDialog.css( {\n\t\t\theight: \"auto\",\n\t\t\twidth: options.width\n\t\t} )\n\t\t\t.outerHeight();\n\t\tminContentHeight = Math.max( 0, options.minHeight - nonContentHeight );\n\t\tmaxContentHeight = typeof options.maxHeight === \"number\" ?\n\t\t\tMath.max( 0, options.maxHeight - nonContentHeight ) :\n\t\t\t\"none\";\n\n\t\tif ( options.height === \"auto\" ) {\n\t\t\tthis.element.css( {\n\t\t\t\tminHeight: minContentHeight,\n\t\t\t\tmaxHeight: maxContentHeight,\n\t\t\t\theight: \"auto\"\n\t\t\t} );\n\t\t} else {\n\t\t\tthis.element.height( Math.max( 0, options.height - nonContentHeight ) );\n\t\t}\n\n\t\tif ( this.uiDialog.is( \":data(ui-resizable)\" ) ) {\n\t\t\tthis.uiDialog.resizable( \"option\", \"minHeight\", this._minHeight() );\n\t\t}\n\t},\n\n\t_blockFrames: function() {\n\t\tthis.iframeBlocks = this.document.find( \"iframe\" ).map( function() {\n\t\t\tvar iframe = $( this );\n\n\t\t\treturn $( \"<div>\" )\n\t\t\t\t.css( {\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\twidth: iframe.outerWidth(),\n\t\t\t\t\theight: iframe.outerHeight()\n\t\t\t\t} )\n\t\t\t\t.appendTo( iframe.parent() )\n\t\t\t\t.offset( iframe.offset() )[ 0 ];\n\t\t} );\n\t},\n\n\t_unblockFrames: function() {\n\t\tif ( this.iframeBlocks ) {\n\t\t\tthis.iframeBlocks.remove();\n\t\t\tdelete this.iframeBlocks;\n\t\t}\n\t},\n\n\t_allowInteraction: function( event ) {\n\t\tif ( $( event.target ).closest( \".ui-dialog\" ).length ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// TODO: Remove hack when datepicker implements\n\t\t// the .ui-front logic (#8989)\n\t\treturn !!$( event.target ).closest( \".ui-datepicker\" ).length;\n\t},\n\n\t_createOverlay: function() {\n\t\tif ( !this.options.modal ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// We use a delay in case the overlay is created from an\n\t\t// event that we're going to be cancelling (#2804)\n\t\tvar isOpening = true;\n\t\tthis._delay( function() {\n\t\t\tisOpening = false;\n\t\t} );\n\n\t\tif ( !this.document.data( \"ui-dialog-overlays\" ) ) {\n\n\t\t\t// Prevent use of anchors and inputs\n\t\t\t// Using _on() for an event handler shared across many instances is\n\t\t\t// safe because the dialogs stack and must be closed in reverse order\n\t\t\tthis._on( this.document, {\n\t\t\t\tfocusin: function( event ) {\n\t\t\t\t\tif ( isOpening ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !this._allowInteraction( event ) ) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tthis._trackingInstances()[ 0 ]._focusTabbable();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tthis.overlay = $( \"<div>\" )\n\t\t\t.appendTo( this._appendTo() );\n\n\t\tthis._addClass( this.overlay, null, \"ui-widget-overlay ui-front\" );\n\t\tthis._on( this.overlay, {\n\t\t\tmousedown: \"_keepFocus\"\n\t\t} );\n\t\tthis.document.data( \"ui-dialog-overlays\",\n\t\t\t( this.document.data( \"ui-dialog-overlays\" ) || 0 ) + 1 );\n\t},\n\n\t_destroyOverlay: function() {\n\t\tif ( !this.options.modal ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this.overlay ) {\n\t\t\tvar overlays = this.document.data( \"ui-dialog-overlays\" ) - 1;\n\n\t\t\tif ( !overlays ) {\n\t\t\t\tthis._off( this.document, \"focusin\" );\n\t\t\t\tthis.document.removeData( \"ui-dialog-overlays\" );\n\t\t\t} else {\n\t\t\t\tthis.document.data( \"ui-dialog-overlays\", overlays );\n\t\t\t}\n\n\t\t\tthis.overlay.remove();\n\t\t\tthis.overlay = null;\n\t\t}\n\t}\n} );\n\n// DEPRECATED\n// TODO: switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for dialogClass option\n\t$.widget( \"ui.dialog\", $.ui.dialog, {\n\t\toptions: {\n\t\t\tdialogClass: \"\"\n\t\t},\n\t\t_createWrapper: function() {\n\t\t\tthis._super();\n\t\t\tthis.uiDialog.addClass( this.options.dialogClass );\n\t\t},\n\t\t_setOption: function( key, value ) {\n\t\t\tif ( key === \"dialogClass\" ) {\n\t\t\t\tthis.uiDialog\n\t\t\t\t\t.removeClass( this.options.dialogClass )\n\t\t\t\t\t.addClass( value );\n\t\t\t}\n\t\t\tthis._superApply( arguments );\n\t\t}\n\t} );\n}\n\nvar widgetsDialog = $.ui.dialog;\n\n\n/*!\n * jQuery UI Droppable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Droppable\n//>>group: Interactions\n//>>description: Enables drop targets for draggable elements.\n//>>docs: http://api.jqueryui.com/droppable/\n//>>demos: http://jqueryui.com/droppable/\n\n\n\n$.widget( \"ui.droppable\", {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"drop\",\n\toptions: {\n\t\taccept: \"*\",\n\t\taddClasses: true,\n\t\tgreedy: false,\n\t\tscope: \"default\",\n\t\ttolerance: \"intersect\",\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tdeactivate: null,\n\t\tdrop: null,\n\t\tout: null,\n\t\tover: null\n\t},\n\t_create: function() {\n\n\t\tvar proportions,\n\t\t\to = this.options,\n\t\t\taccept = o.accept;\n\n\t\tthis.isover = false;\n\t\tthis.isout = true;\n\n\t\tthis.accept = $.isFunction( accept ) ? accept : function( d ) {\n\t\t\treturn d.is( accept );\n\t\t};\n\n\t\tthis.proportions = function( /* valueToWrite */ ) {\n\t\t\tif ( arguments.length ) {\n\n\t\t\t\t// Store the droppable's proportions\n\t\t\t\tproportions = arguments[ 0 ];\n\t\t\t} else {\n\n\t\t\t\t// Retrieve or derive the droppable's proportions\n\t\t\t\treturn proportions ?\n\t\t\t\t\tproportions :\n\t\t\t\t\tproportions = {\n\t\t\t\t\t\twidth: this.element[ 0 ].offsetWidth,\n\t\t\t\t\t\theight: this.element[ 0 ].offsetHeight\n\t\t\t\t\t};\n\t\t\t}\n\t\t};\n\n\t\tthis._addToManager( o.scope );\n\n\t\to.addClasses && this._addClass( \"ui-droppable\" );\n\n\t},\n\n\t_addToManager: function( scope ) {\n\n\t\t// Add the reference and positions to the manager\n\t\t$.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];\n\t\t$.ui.ddmanager.droppables[ scope ].push( this );\n\t},\n\n\t_splice: function( drop ) {\n\t\tvar i = 0;\n\t\tfor ( ; i < drop.length; i++ ) {\n\t\t\tif ( drop[ i ] === this ) {\n\t\t\t\tdrop.splice( i, 1 );\n\t\t\t}\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tvar drop = $.ui.ddmanager.droppables[ this.options.scope ];\n\n\t\tthis._splice( drop );\n\t},\n\n\t_setOption: function( key, value ) {\n\n\t\tif ( key === \"accept\" ) {\n\t\t\tthis.accept = $.isFunction( value ) ? value : function( d ) {\n\t\t\t\treturn d.is( value );\n\t\t\t};\n\t\t} else if ( key === \"scope\" ) {\n\t\t\tvar drop = $.ui.ddmanager.droppables[ this.options.scope ];\n\n\t\t\tthis._splice( drop );\n\t\t\tthis._addToManager( value );\n\t\t}\n\n\t\tthis._super( key, value );\n\t},\n\n\t_activate: function( event ) {\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\tthis._addActiveClass();\n\t\tif ( draggable ) {\n\t\t\tthis._trigger( \"activate\", event, this.ui( draggable ) );\n\t\t}\n\t},\n\n\t_deactivate: function( event ) {\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\tthis._removeActiveClass();\n\t\tif ( draggable ) {\n\t\t\tthis._trigger( \"deactivate\", event, this.ui( draggable ) );\n\t\t}\n\t},\n\n\t_over: function( event ) {\n\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\t// Bail if draggable and droppable are same element\n\t\tif ( !draggable || ( draggable.currentItem ||\n\t\t\t\tdraggable.element )[ 0 ] === this.element[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||\n\t\t\t\tdraggable.element ) ) ) {\n\t\t\tthis._addHoverClass();\n\t\t\tthis._trigger( \"over\", event, this.ui( draggable ) );\n\t\t}\n\n\t},\n\n\t_out: function( event ) {\n\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\t// Bail if draggable and droppable are same element\n\t\tif ( !draggable || ( draggable.currentItem ||\n\t\t\t\tdraggable.element )[ 0 ] === this.element[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||\n\t\t\t\tdraggable.element ) ) ) {\n\t\t\tthis._removeHoverClass();\n\t\t\tthis._trigger( \"out\", event, this.ui( draggable ) );\n\t\t}\n\n\t},\n\n\t_drop: function( event, custom ) {\n\n\t\tvar draggable = custom || $.ui.ddmanager.current,\n\t\t\tchildrenIntersection = false;\n\n\t\t// Bail if draggable and droppable are same element\n\t\tif ( !draggable || ( draggable.currentItem ||\n\t\t\t\tdraggable.element )[ 0 ] === this.element[ 0 ] ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.element\n\t\t\t.find( \":data(ui-droppable)\" )\n\t\t\t.not( \".ui-draggable-dragging\" )\n\t\t\t.each( function() {\n\t\t\t\tvar inst = $( this ).droppable( \"instance\" );\n\t\t\t\tif (\n\t\t\t\t\tinst.options.greedy &&\n\t\t\t\t\t!inst.options.disabled &&\n\t\t\t\t\tinst.options.scope === draggable.options.scope &&\n\t\t\t\t\tinst.accept.call(\n\t\t\t\t\t\tinst.element[ 0 ], ( draggable.currentItem || draggable.element )\n\t\t\t\t\t) &&\n\t\t\t\t\tintersect(\n\t\t\t\t\t\tdraggable,\n\t\t\t\t\t\t$.extend( inst, { offset: inst.element.offset() } ),\n\t\t\t\t\t\tinst.options.tolerance, event\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tchildrenIntersection = true;\n\t\t\t\t\treturn false; }\n\t\t\t} );\n\t\tif ( childrenIntersection ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( this.accept.call( this.element[ 0 ],\n\t\t\t\t( draggable.currentItem || draggable.element ) ) ) {\n\t\t\tthis._removeActiveClass();\n\t\t\tthis._removeHoverClass();\n\n\t\t\tthis._trigger( \"drop\", event, this.ui( draggable ) );\n\t\t\treturn this.element;\n\t\t}\n\n\t\treturn false;\n\n\t},\n\n\tui: function( c ) {\n\t\treturn {\n\t\t\tdraggable: ( c.currentItem || c.element ),\n\t\t\thelper: c.helper,\n\t\t\tposition: c.position,\n\t\t\toffset: c.positionAbs\n\t\t};\n\t},\n\n\t// Extension points just to make backcompat sane and avoid duplicating logic\n\t// TODO: Remove in 1.13 along with call to it below\n\t_addHoverClass: function() {\n\t\tthis._addClass( \"ui-droppable-hover\" );\n\t},\n\n\t_removeHoverClass: function() {\n\t\tthis._removeClass( \"ui-droppable-hover\" );\n\t},\n\n\t_addActiveClass: function() {\n\t\tthis._addClass( \"ui-droppable-active\" );\n\t},\n\n\t_removeActiveClass: function() {\n\t\tthis._removeClass( \"ui-droppable-active\" );\n\t}\n} );\n\nvar intersect = $.ui.intersect = ( function() {\n\tfunction isOverAxis( x, reference, size ) {\n\t\treturn ( x >= reference ) && ( x < ( reference + size ) );\n\t}\n\n\treturn function( draggable, droppable, toleranceMode, event ) {\n\n\t\tif ( !droppable.offset ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar x1 = ( draggable.positionAbs ||\n\t\t\t\tdraggable.position.absolute ).left + draggable.margins.left,\n\t\t\ty1 = ( draggable.positionAbs ||\n\t\t\t\tdraggable.position.absolute ).top + draggable.margins.top,\n\t\t\tx2 = x1 + draggable.helperProportions.width,\n\t\t\ty2 = y1 + draggable.helperProportions.height,\n\t\t\tl = droppable.offset.left,\n\t\t\tt = droppable.offset.top,\n\t\t\tr = l + droppable.proportions().width,\n\t\t\tb = t + droppable.proportions().height;\n\n\t\tswitch ( toleranceMode ) {\n\t\tcase \"fit\":\n\t\t\treturn ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );\n\t\tcase \"intersect\":\n\t\t\treturn ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half\n\t\t\t\tx2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half\n\t\t\t\tt < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half\n\t\t\t\ty2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half\n\t\tcase \"pointer\":\n\t\t\treturn isOverAxis( event.pageY, t, droppable.proportions().height ) &&\n\t\t\t\tisOverAxis( event.pageX, l, droppable.proportions().width );\n\t\tcase \"touch\":\n\t\t\treturn (\n\t\t\t\t( y1 >= t && y1 <= b ) || // Top edge touching\n\t\t\t\t( y2 >= t && y2 <= b ) || // Bottom edge touching\n\t\t\t\t( y1 < t && y2 > b ) // Surrounded vertically\n\t\t\t) && (\n\t\t\t\t( x1 >= l && x1 <= r ) || // Left edge touching\n\t\t\t\t( x2 >= l && x2 <= r ) || // Right edge touching\n\t\t\t\t( x1 < l && x2 > r ) // Surrounded horizontally\n\t\t\t);\n\t\tdefault:\n\t\t\treturn false;\n\t\t}\n\t};\n} )();\n\n/*\n\tThis manager tracks offsets of draggables and droppables\n*/\n$.ui.ddmanager = {\n\tcurrent: null,\n\tdroppables: { \"default\": [] },\n\tprepareOffsets: function( t, event ) {\n\n\t\tvar i, j,\n\t\t\tm = $.ui.ddmanager.droppables[ t.options.scope ] || [],\n\t\t\ttype = event ? event.type : null, // workaround for #2317\n\t\t\tlist = ( t.currentItem || t.element ).find( \":data(ui-droppable)\" ).addBack();\n\n\t\tdroppablesLoop: for ( i = 0; i < m.length; i++ ) {\n\n\t\t\t// No disabled and non-accepted\n\t\t\tif ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ],\n\t\t\t\t\t( t.currentItem || t.element ) ) ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Filter out elements in the current dragged item\n\t\t\tfor ( j = 0; j < list.length; j++ ) {\n\t\t\t\tif ( list[ j ] === m[ i ].element[ 0 ] ) {\n\t\t\t\t\tm[ i ].proportions().height = 0;\n\t\t\t\t\tcontinue droppablesLoop;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm[ i ].visible = m[ i ].element.css( \"display\" ) !== \"none\";\n\t\t\tif ( !m[ i ].visible ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Activate the droppable if used directly from draggables\n\t\t\tif ( type === \"mousedown\" ) {\n\t\t\t\tm[ i ]._activate.call( m[ i ], event );\n\t\t\t}\n\n\t\t\tm[ i ].offset = m[ i ].element.offset();\n\t\t\tm[ i ].proportions( {\n\t\t\t\twidth: m[ i ].element[ 0 ].offsetWidth,\n\t\t\t\theight: m[ i ].element[ 0 ].offsetHeight\n\t\t\t} );\n\n\t\t}\n\n\t},\n\tdrop: function( draggable, event ) {\n\n\t\tvar dropped = false;\n\n\t\t// Create a copy of the droppables in case the list changes during the drop (#9116)\n\t\t$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {\n\n\t\t\tif ( !this.options ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( !this.options.disabled && this.visible &&\n\t\t\t\t\tintersect( draggable, this, this.options.tolerance, event ) ) {\n\t\t\t\tdropped = this._drop.call( this, event ) || dropped;\n\t\t\t}\n\n\t\t\tif ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],\n\t\t\t\t\t( draggable.currentItem || draggable.element ) ) ) {\n\t\t\t\tthis.isout = true;\n\t\t\t\tthis.isover = false;\n\t\t\t\tthis._deactivate.call( this, event );\n\t\t\t}\n\n\t\t} );\n\t\treturn dropped;\n\n\t},\n\tdragStart: function( draggable, event ) {\n\n\t\t// Listen for scrolling so that if the dragging causes scrolling the position of the\n\t\t// droppables can be recalculated (see #5003)\n\t\tdraggable.element.parentsUntil( \"body\" ).on( \"scroll.droppable\", function() {\n\t\t\tif ( !draggable.options.refreshPositions ) {\n\t\t\t\t$.ui.ddmanager.prepareOffsets( draggable, event );\n\t\t\t}\n\t\t} );\n\t},\n\tdrag: function( draggable, event ) {\n\n\t\t// If you have a highly dynamic page, you might try this option. It renders positions\n\t\t// every time you move the mouse.\n\t\tif ( draggable.options.refreshPositions ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( draggable, event );\n\t\t}\n\n\t\t// Run through all droppables and check their positions based on specific tolerance options\n\t\t$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {\n\n\t\t\tif ( this.options.disabled || this.greedyChild || !this.visible ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar parentInstance, scope, parent,\n\t\t\t\tintersects = intersect( draggable, this, this.options.tolerance, event ),\n\t\t\t\tc = !intersects && this.isover ?\n\t\t\t\t\t\"isout\" :\n\t\t\t\t\t( intersects && !this.isover ? \"isover\" : null );\n\t\t\tif ( !c ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( this.options.greedy ) {\n\n\t\t\t\t// find droppable parents with same scope\n\t\t\t\tscope = this.options.scope;\n\t\t\t\tparent = this.element.parents( \":data(ui-droppable)\" ).filter( function() {\n\t\t\t\t\treturn $( this ).droppable( \"instance\" ).options.scope === scope;\n\t\t\t\t} );\n\n\t\t\t\tif ( parent.length ) {\n\t\t\t\t\tparentInstance = $( parent[ 0 ] ).droppable( \"instance\" );\n\t\t\t\t\tparentInstance.greedyChild = ( c === \"isover\" );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We just moved into a greedy child\n\t\t\tif ( parentInstance && c === \"isover\" ) {\n\t\t\t\tparentInstance.isover = false;\n\t\t\t\tparentInstance.isout = true;\n\t\t\t\tparentInstance._out.call( parentInstance, event );\n\t\t\t}\n\n\t\t\tthis[ c ] = true;\n\t\t\tthis[ c === \"isout\" ? \"isover\" : \"isout\" ] = false;\n\t\t\tthis[ c === \"isover\" ? \"_over\" : \"_out\" ].call( this, event );\n\n\t\t\t// We just moved out of a greedy child\n\t\t\tif ( parentInstance && c === \"isout\" ) {\n\t\t\t\tparentInstance.isout = false;\n\t\t\t\tparentInstance.isover = true;\n\t\t\t\tparentInstance._over.call( parentInstance, event );\n\t\t\t}\n\t\t} );\n\n\t},\n\tdragStop: function( draggable, event ) {\n\t\tdraggable.element.parentsUntil( \"body\" ).off( \"scroll.droppable\" );\n\n\t\t// Call prepareOffsets one final time since IE does not fire return scroll events when\n\t\t// overflow was caused by drag (see #5003)\n\t\tif ( !draggable.options.refreshPositions ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( draggable, event );\n\t\t}\n\t}\n};\n\n// DEPRECATED\n// TODO: switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for activeClass and hoverClass options\n\t$.widget( \"ui.droppable\", $.ui.droppable, {\n\t\toptions: {\n\t\t\thoverClass: false,\n\t\t\tactiveClass: false\n\t\t},\n\t\t_addActiveClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.activeClass ) {\n\t\t\t\tthis.element.addClass( this.options.activeClass );\n\t\t\t}\n\t\t},\n\t\t_removeActiveClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.activeClass ) {\n\t\t\t\tthis.element.removeClass( this.options.activeClass );\n\t\t\t}\n\t\t},\n\t\t_addHoverClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.hoverClass ) {\n\t\t\t\tthis.element.addClass( this.options.hoverClass );\n\t\t\t}\n\t\t},\n\t\t_removeHoverClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.hoverClass ) {\n\t\t\t\tthis.element.removeClass( this.options.hoverClass );\n\t\t\t}\n\t\t}\n\t} );\n}\n\nvar widgetsDroppable = $.ui.droppable;\n\n\n/*!\n * jQuery UI Progressbar 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Progressbar\n//>>group: Widgets\n// jscs:disable maximumLineLength\n//>>description: Displays a status indicator for loading state, standard percentage, and other progress indicators.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/progressbar/\n//>>demos: http://jqueryui.com/progressbar/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/progressbar.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsProgressbar = $.widget( \"ui.progressbar\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-progressbar\": \"ui-corner-all\",\n\t\t\t\"ui-progressbar-value\": \"ui-corner-left\",\n\t\t\t\"ui-progressbar-complete\": \"ui-corner-right\"\n\t\t},\n\t\tmax: 100,\n\t\tvalue: 0,\n\n\t\tchange: null,\n\t\tcomplete: null\n\t},\n\n\tmin: 0,\n\n\t_create: function() {\n\n\t\t// Constrain initial value\n\t\tthis.oldValue = this.options.value = this._constrainedValue();\n\n\t\tthis.element.attr( {\n\n\t\t\t// Only set static values; aria-valuenow and aria-valuemax are\n\t\t\t// set inside _refreshValue()\n\t\t\trole: \"progressbar\",\n\t\t\t\"aria-valuemin\": this.min\n\t\t} );\n\t\tthis._addClass( \"ui-progressbar\", \"ui-widget ui-widget-content\" );\n\n\t\tthis.valueDiv = $( \"<div>\" ).appendTo( this.element );\n\t\tthis._addClass( this.valueDiv, \"ui-progressbar-value\", \"ui-widget-header\" );\n\t\tthis._refreshValue();\n\t},\n\n\t_destroy: function() {\n\t\tthis.element.removeAttr( \"role aria-valuemin aria-valuemax aria-valuenow\" );\n\n\t\tthis.valueDiv.remove();\n\t},\n\n\tvalue: function( newValue ) {\n\t\tif ( newValue === undefined ) {\n\t\t\treturn this.options.value;\n\t\t}\n\n\t\tthis.options.value = this._constrainedValue( newValue );\n\t\tthis._refreshValue();\n\t},\n\n\t_constrainedValue: function( newValue ) {\n\t\tif ( newValue === undefined ) {\n\t\t\tnewValue = this.options.value;\n\t\t}\n\n\t\tthis.indeterminate = newValue === false;\n\n\t\t// Sanitize value\n\t\tif ( typeof newValue !== \"number\" ) {\n\t\t\tnewValue = 0;\n\t\t}\n\n\t\treturn this.indeterminate ? false :\n\t\t\tMath.min( this.options.max, Math.max( this.min, newValue ) );\n\t},\n\n\t_setOptions: function( options ) {\n\n\t\t// Ensure \"value\" option is set after other values (like max)\n\t\tvar value = options.value;\n\t\tdelete options.value;\n\n\t\tthis._super( options );\n\n\t\tthis.options.value = this._constrainedValue( value );\n\t\tthis._refreshValue();\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"max\" ) {\n\n\t\t\t// Don't allow a max less than min\n\t\t\tvalue = Math.max( this.min, value );\n\t\t}\n\t\tthis._super( key, value );\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.element.attr( \"aria-disabled\", value );\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t},\n\n\t_percentage: function() {\n\t\treturn this.indeterminate ?\n\t\t\t100 :\n\t\t\t100 * ( this.options.value - this.min ) / ( this.options.max - this.min );\n\t},\n\n\t_refreshValue: function() {\n\t\tvar value = this.options.value,\n\t\t\tpercentage = this._percentage();\n\n\t\tthis.valueDiv\n\t\t\t.toggle( this.indeterminate || value > this.min )\n\t\t\t.width( percentage.toFixed( 0 ) + \"%\" );\n\n\t\tthis\n\t\t\t._toggleClass( this.valueDiv, \"ui-progressbar-complete\", null,\n\t\t\t\tvalue === this.options.max )\n\t\t\t._toggleClass( \"ui-progressbar-indeterminate\", null, this.indeterminate );\n\n\t\tif ( this.indeterminate ) {\n\t\t\tthis.element.removeAttr( \"aria-valuenow\" );\n\t\t\tif ( !this.overlayDiv ) {\n\t\t\t\tthis.overlayDiv = $( \"<div>\" ).appendTo( this.valueDiv );\n\t\t\t\tthis._addClass( this.overlayDiv, \"ui-progressbar-overlay\" );\n\t\t\t}\n\t\t} else {\n\t\t\tthis.element.attr( {\n\t\t\t\t\"aria-valuemax\": this.options.max,\n\t\t\t\t\"aria-valuenow\": value\n\t\t\t} );\n\t\t\tif ( this.overlayDiv ) {\n\t\t\t\tthis.overlayDiv.remove();\n\t\t\t\tthis.overlayDiv = null;\n\t\t\t}\n\t\t}\n\n\t\tif ( this.oldValue !== value ) {\n\t\t\tthis.oldValue = value;\n\t\t\tthis._trigger( \"change\" );\n\t\t}\n\t\tif ( value === this.options.max ) {\n\t\t\tthis._trigger( \"complete\" );\n\t\t}\n\t}\n} );\n\n\n/*!\n * jQuery UI Selectable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Selectable\n//>>group: Interactions\n//>>description: Allows groups of elements to be selected with the mouse.\n//>>docs: http://api.jqueryui.com/selectable/\n//>>demos: http://jqueryui.com/selectable/\n//>>css.structure: ../../themes/base/selectable.css\n\n\n\nvar widgetsSelectable = $.widget( \"ui.selectable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tappendTo: \"body\",\n\t\tautoRefresh: true,\n\t\tdistance: 0,\n\t\tfilter: \"*\",\n\t\ttolerance: \"touch\",\n\n\t\t// Callbacks\n\t\tselected: null,\n\t\tselecting: null,\n\t\tstart: null,\n\t\tstop: null,\n\t\tunselected: null,\n\t\tunselecting: null\n\t},\n\t_create: function() {\n\t\tvar that = this;\n\n\t\tthis._addClass( \"ui-selectable\" );\n\n\t\tthis.dragged = false;\n\n\t\t// Cache selectee children based on filter\n\t\tthis.refresh = function() {\n\t\t\tthat.elementPos = $( that.element[ 0 ] ).offset();\n\t\t\tthat.selectees = $( that.options.filter, that.element[ 0 ] );\n\t\t\tthat._addClass( that.selectees, \"ui-selectee\" );\n\t\t\tthat.selectees.each( function() {\n\t\t\t\tvar $this = $( this ),\n\t\t\t\t\tselecteeOffset = $this.offset(),\n\t\t\t\t\tpos = {\n\t\t\t\t\t\tleft: selecteeOffset.left - that.elementPos.left,\n\t\t\t\t\t\ttop: selecteeOffset.top - that.elementPos.top\n\t\t\t\t\t};\n\t\t\t\t$.data( this, \"selectable-item\", {\n\t\t\t\t\telement: this,\n\t\t\t\t\t$element: $this,\n\t\t\t\t\tleft: pos.left,\n\t\t\t\t\ttop: pos.top,\n\t\t\t\t\tright: pos.left + $this.outerWidth(),\n\t\t\t\t\tbottom: pos.top + $this.outerHeight(),\n\t\t\t\t\tstartselected: false,\n\t\t\t\t\tselected: $this.hasClass( \"ui-selected\" ),\n\t\t\t\t\tselecting: $this.hasClass( \"ui-selecting\" ),\n\t\t\t\t\tunselecting: $this.hasClass( \"ui-unselecting\" )\n\t\t\t\t} );\n\t\t\t} );\n\t\t};\n\t\tthis.refresh();\n\n\t\tthis._mouseInit();\n\n\t\tthis.helper = $( \"<div>\" );\n\t\tthis._addClass( this.helper, \"ui-selectable-helper\" );\n\t},\n\n\t_destroy: function() {\n\t\tthis.selectees.removeData( \"selectable-item\" );\n\t\tthis._mouseDestroy();\n\t},\n\n\t_mouseStart: function( event ) {\n\t\tvar that = this,\n\t\t\toptions = this.options;\n\n\t\tthis.opos = [ event.pageX, event.pageY ];\n\t\tthis.elementPos = $( this.element[ 0 ] ).offset();\n\n\t\tif ( this.options.disabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.selectees = $( options.filter, this.element[ 0 ] );\n\n\t\tthis._trigger( \"start\", event );\n\n\t\t$( options.appendTo ).append( this.helper );\n\n\t\t// position helper (lasso)\n\t\tthis.helper.css( {\n\t\t\t\"left\": event.pageX,\n\t\t\t\"top\": event.pageY,\n\t\t\t\"width\": 0,\n\t\t\t\"height\": 0\n\t\t} );\n\n\t\tif ( options.autoRefresh ) {\n\t\t\tthis.refresh();\n\t\t}\n\n\t\tthis.selectees.filter( \".ui-selected\" ).each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" );\n\t\t\tselectee.startselected = true;\n\t\t\tif ( !event.metaKey && !event.ctrlKey ) {\n\t\t\t\tthat._removeClass( selectee.$element, \"ui-selected\" );\n\t\t\t\tselectee.selected = false;\n\t\t\t\tthat._addClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\tselectee.unselecting = true;\n\n\t\t\t\t// selectable UNSELECTING callback\n\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\t$( event.target ).parents().addBack().each( function() {\n\t\t\tvar doSelect,\n\t\t\t\tselectee = $.data( this, \"selectable-item\" );\n\t\t\tif ( selectee ) {\n\t\t\t\tdoSelect = ( !event.metaKey && !event.ctrlKey ) ||\n\t\t\t\t\t!selectee.$element.hasClass( \"ui-selected\" );\n\t\t\t\tthat._removeClass( selectee.$element, doSelect ? \"ui-unselecting\" : \"ui-selected\" )\n\t\t\t\t\t._addClass( selectee.$element, doSelect ? \"ui-selecting\" : \"ui-unselecting\" );\n\t\t\t\tselectee.unselecting = !doSelect;\n\t\t\t\tselectee.selecting = doSelect;\n\t\t\t\tselectee.selected = doSelect;\n\n\t\t\t\t// selectable (UN)SELECTING callback\n\t\t\t\tif ( doSelect ) {\n\t\t\t\t\tthat._trigger( \"selecting\", event, {\n\t\t\t\t\t\tselecting: selectee.element\n\t\t\t\t\t} );\n\t\t\t\t} else {\n\t\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} );\n\n\t},\n\n\t_mouseDrag: function( event ) {\n\n\t\tthis.dragged = true;\n\n\t\tif ( this.options.disabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tmp,\n\t\t\tthat = this,\n\t\t\toptions = this.options,\n\t\t\tx1 = this.opos[ 0 ],\n\t\t\ty1 = this.opos[ 1 ],\n\t\t\tx2 = event.pageX,\n\t\t\ty2 = event.pageY;\n\n\t\tif ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; }\n\t\tif ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; }\n\t\tthis.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } );\n\n\t\tthis.selectees.each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" ),\n\t\t\t\thit = false,\n\t\t\t\toffset = {};\n\n\t\t\t//prevent helper from being selected if appendTo: selectable\n\t\t\tif ( !selectee || selectee.element === that.element[ 0 ] ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\toffset.left   = selectee.left   + that.elementPos.left;\n\t\t\toffset.right  = selectee.right  + that.elementPos.left;\n\t\t\toffset.top    = selectee.top    + that.elementPos.top;\n\t\t\toffset.bottom = selectee.bottom + that.elementPos.top;\n\n\t\t\tif ( options.tolerance === \"touch\" ) {\n\t\t\t\thit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 ||\n                    offset.bottom < y1 ) );\n\t\t\t} else if ( options.tolerance === \"fit\" ) {\n\t\t\t\thit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 &&\n                    offset.bottom < y2 );\n\t\t\t}\n\n\t\t\tif ( hit ) {\n\n\t\t\t\t// SELECT\n\t\t\t\tif ( selectee.selected ) {\n\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selected\" );\n\t\t\t\t\tselectee.selected = false;\n\t\t\t\t}\n\t\t\t\tif ( selectee.unselecting ) {\n\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\t\tselectee.unselecting = false;\n\t\t\t\t}\n\t\t\t\tif ( !selectee.selecting ) {\n\t\t\t\t\tthat._addClass( selectee.$element, \"ui-selecting\" );\n\t\t\t\t\tselectee.selecting = true;\n\n\t\t\t\t\t// selectable SELECTING callback\n\t\t\t\t\tthat._trigger( \"selecting\", event, {\n\t\t\t\t\t\tselecting: selectee.element\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// UNSELECT\n\t\t\t\tif ( selectee.selecting ) {\n\t\t\t\t\tif ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) {\n\t\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selecting\" );\n\t\t\t\t\t\tselectee.selecting = false;\n\t\t\t\t\t\tthat._addClass( selectee.$element, \"ui-selected\" );\n\t\t\t\t\t\tselectee.selected = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selecting\" );\n\t\t\t\t\t\tselectee.selecting = false;\n\t\t\t\t\t\tif ( selectee.startselected ) {\n\t\t\t\t\t\t\tthat._addClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\t\t\t\tselectee.unselecting = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// selectable UNSELECTING callback\n\t\t\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( selectee.selected ) {\n\t\t\t\t\tif ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) {\n\t\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selected\" );\n\t\t\t\t\t\tselectee.selected = false;\n\n\t\t\t\t\t\tthat._addClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\t\t\tselectee.unselecting = true;\n\n\t\t\t\t\t\t// selectable UNSELECTING callback\n\t\t\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\t\tvar that = this;\n\n\t\tthis.dragged = false;\n\n\t\t$( \".ui-unselecting\", this.element[ 0 ] ).each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" );\n\t\t\tthat._removeClass( selectee.$element, \"ui-unselecting\" );\n\t\t\tselectee.unselecting = false;\n\t\t\tselectee.startselected = false;\n\t\t\tthat._trigger( \"unselected\", event, {\n\t\t\t\tunselected: selectee.element\n\t\t\t} );\n\t\t} );\n\t\t$( \".ui-selecting\", this.element[ 0 ] ).each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" );\n\t\t\tthat._removeClass( selectee.$element, \"ui-selecting\" )\n\t\t\t\t._addClass( selectee.$element, \"ui-selected\" );\n\t\t\tselectee.selecting = false;\n\t\t\tselectee.selected = true;\n\t\t\tselectee.startselected = true;\n\t\t\tthat._trigger( \"selected\", event, {\n\t\t\t\tselected: selectee.element\n\t\t\t} );\n\t\t} );\n\t\tthis._trigger( \"stop\", event );\n\n\t\tthis.helper.remove();\n\n\t\treturn false;\n\t}\n\n} );\n\n\n/*!\n * jQuery UI Selectmenu 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Selectmenu\n//>>group: Widgets\n// jscs:disable maximumLineLength\n//>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/selectmenu/\n//>>demos: http://jqueryui.com/selectmenu/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsSelectmenu = $.widget( \"ui.selectmenu\", [ $.ui.formResetMixin, {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<select>\",\n\toptions: {\n\t\tappendTo: null,\n\t\tclasses: {\n\t\t\t\"ui-selectmenu-button-open\": \"ui-corner-top\",\n\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-all\"\n\t\t},\n\t\tdisabled: null,\n\t\ticons: {\n\t\t\tbutton: \"ui-icon-triangle-1-s\"\n\t\t},\n\t\tposition: {\n\t\t\tmy: \"left top\",\n\t\t\tat: \"left bottom\",\n\t\t\tcollision: \"none\"\n\t\t},\n\t\twidth: false,\n\n\t\t// Callbacks\n\t\tchange: null,\n\t\tclose: null,\n\t\tfocus: null,\n\t\topen: null,\n\t\tselect: null\n\t},\n\n\t_create: function() {\n\t\tvar selectmenuId = this.element.uniqueId().attr( \"id\" );\n\t\tthis.ids = {\n\t\t\telement: selectmenuId,\n\t\t\tbutton: selectmenuId + \"-button\",\n\t\t\tmenu: selectmenuId + \"-menu\"\n\t\t};\n\n\t\tthis._drawButton();\n\t\tthis._drawMenu();\n\t\tthis._bindFormResetHandler();\n\n\t\tthis._rendered = false;\n\t\tthis.menuItems = $();\n\t},\n\n\t_drawButton: function() {\n\t\tvar icon,\n\t\t\tthat = this,\n\t\t\titem = this._parseOption(\n\t\t\t\tthis.element.find( \"option:selected\" ),\n\t\t\t\tthis.element[ 0 ].selectedIndex\n\t\t\t);\n\n\t\t// Associate existing label with the new button\n\t\tthis.labels = this.element.labels().attr( \"for\", this.ids.button );\n\t\tthis._on( this.labels, {\n\t\t\tclick: function( event ) {\n\t\t\t\tthis.button.focus();\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t} );\n\n\t\t// Hide original select element\n\t\tthis.element.hide();\n\n\t\t// Create button\n\t\tthis.button = $( \"<span>\", {\n\t\t\ttabindex: this.options.disabled ? -1 : 0,\n\t\t\tid: this.ids.button,\n\t\t\trole: \"combobox\",\n\t\t\t\"aria-expanded\": \"false\",\n\t\t\t\"aria-autocomplete\": \"list\",\n\t\t\t\"aria-owns\": this.ids.menu,\n\t\t\t\"aria-haspopup\": \"true\",\n\t\t\ttitle: this.element.attr( \"title\" )\n\t\t} )\n\t\t\t.insertAfter( this.element );\n\n\t\tthis._addClass( this.button, \"ui-selectmenu-button ui-selectmenu-button-closed\",\n\t\t\t\"ui-button ui-widget\" );\n\n\t\ticon = $( \"<span>\" ).appendTo( this.button );\n\t\tthis._addClass( icon, \"ui-selectmenu-icon\", \"ui-icon \" + this.options.icons.button );\n\t\tthis.buttonItem = this._renderButtonItem( item )\n\t\t\t.appendTo( this.button );\n\n\t\tif ( this.options.width !== false ) {\n\t\t\tthis._resizeButton();\n\t\t}\n\n\t\tthis._on( this.button, this._buttonEvents );\n\t\tthis.button.one( \"focusin\", function() {\n\n\t\t\t// Delay rendering the menu items until the button receives focus.\n\t\t\t// The menu may have already been rendered via a programmatic open.\n\t\t\tif ( !that._rendered ) {\n\t\t\t\tthat._refreshMenu();\n\t\t\t}\n\t\t} );\n\t},\n\n\t_drawMenu: function() {\n\t\tvar that = this;\n\n\t\t// Create menu\n\t\tthis.menu = $( \"<ul>\", {\n\t\t\t\"aria-hidden\": \"true\",\n\t\t\t\"aria-labelledby\": this.ids.button,\n\t\t\tid: this.ids.menu\n\t\t} );\n\n\t\t// Wrap menu\n\t\tthis.menuWrap = $( \"<div>\" ).append( this.menu );\n\t\tthis._addClass( this.menuWrap, \"ui-selectmenu-menu\", \"ui-front\" );\n\t\tthis.menuWrap.appendTo( this._appendTo() );\n\n\t\t// Initialize menu widget\n\t\tthis.menuInstance = this.menu\n\t\t\t.menu( {\n\t\t\t\tclasses: {\n\t\t\t\t\t\"ui-menu\": \"ui-corner-bottom\"\n\t\t\t\t},\n\t\t\t\trole: \"listbox\",\n\t\t\t\tselect: function( event, ui ) {\n\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\t// Support: IE8\n\t\t\t\t\t// If the item was selected via a click, the text selection\n\t\t\t\t\t// will be destroyed in IE\n\t\t\t\t\tthat._setSelection();\n\n\t\t\t\t\tthat._select( ui.item.data( \"ui-selectmenu-item\" ), event );\n\t\t\t\t},\n\t\t\t\tfocus: function( event, ui ) {\n\t\t\t\t\tvar item = ui.item.data( \"ui-selectmenu-item\" );\n\n\t\t\t\t\t// Prevent inital focus from firing and check if its a newly focused item\n\t\t\t\t\tif ( that.focusIndex != null && item.index !== that.focusIndex ) {\n\t\t\t\t\t\tthat._trigger( \"focus\", event, { item: item } );\n\t\t\t\t\t\tif ( !that.isOpen ) {\n\t\t\t\t\t\t\tthat._select( item, event );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthat.focusIndex = item.index;\n\n\t\t\t\t\tthat.button.attr( \"aria-activedescendant\",\n\t\t\t\t\t\tthat.menuItems.eq( item.index ).attr( \"id\" ) );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.menu( \"instance\" );\n\n\t\t// Don't close the menu on mouseleave\n\t\tthis.menuInstance._off( this.menu, \"mouseleave\" );\n\n\t\t// Cancel the menu's collapseAll on document click\n\t\tthis.menuInstance._closeOnDocumentClick = function() {\n\t\t\treturn false;\n\t\t};\n\n\t\t// Selects often contain empty items, but never contain dividers\n\t\tthis.menuInstance._isDivider = function() {\n\t\t\treturn false;\n\t\t};\n\t},\n\n\trefresh: function() {\n\t\tthis._refreshMenu();\n\t\tthis.buttonItem.replaceWith(\n\t\t\tthis.buttonItem = this._renderButtonItem(\n\n\t\t\t\t// Fall back to an empty object in case there are no options\n\t\t\t\tthis._getSelectedItem().data( \"ui-selectmenu-item\" ) || {}\n\t\t\t)\n\t\t);\n\t\tif ( this.options.width === null ) {\n\t\t\tthis._resizeButton();\n\t\t}\n\t},\n\n\t_refreshMenu: function() {\n\t\tvar item,\n\t\t\toptions = this.element.find( \"option\" );\n\n\t\tthis.menu.empty();\n\n\t\tthis._parseOptions( options );\n\t\tthis._renderMenu( this.menu, this.items );\n\n\t\tthis.menuInstance.refresh();\n\t\tthis.menuItems = this.menu.find( \"li\" )\n\t\t\t.not( \".ui-selectmenu-optgroup\" )\n\t\t\t\t.find( \".ui-menu-item-wrapper\" );\n\n\t\tthis._rendered = true;\n\n\t\tif ( !options.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\titem = this._getSelectedItem();\n\n\t\t// Update the menu to have the correct item focused\n\t\tthis.menuInstance.focus( null, item );\n\t\tthis._setAria( item.data( \"ui-selectmenu-item\" ) );\n\n\t\t// Set disabled state\n\t\tthis._setOption( \"disabled\", this.element.prop( \"disabled\" ) );\n\t},\n\n\topen: function( event ) {\n\t\tif ( this.options.disabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If this is the first time the menu is being opened, render the items\n\t\tif ( !this._rendered ) {\n\t\t\tthis._refreshMenu();\n\t\t} else {\n\n\t\t\t// Menu clears focus on close, reset focus to selected item\n\t\t\tthis._removeClass( this.menu.find( \".ui-state-active\" ), null, \"ui-state-active\" );\n\t\t\tthis.menuInstance.focus( null, this._getSelectedItem() );\n\t\t}\n\n\t\t// If there are no options, don't open the menu\n\t\tif ( !this.menuItems.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isOpen = true;\n\t\tthis._toggleAttr();\n\t\tthis._resizeMenu();\n\t\tthis._position();\n\n\t\tthis._on( this.document, this._documentClick );\n\n\t\tthis._trigger( \"open\", event );\n\t},\n\n\t_position: function() {\n\t\tthis.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );\n\t},\n\n\tclose: function( event ) {\n\t\tif ( !this.isOpen ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isOpen = false;\n\t\tthis._toggleAttr();\n\n\t\tthis.range = null;\n\t\tthis._off( this.document );\n\n\t\tthis._trigger( \"close\", event );\n\t},\n\n\twidget: function() {\n\t\treturn this.button;\n\t},\n\n\tmenuWidget: function() {\n\t\treturn this.menu;\n\t},\n\n\t_renderButtonItem: function( item ) {\n\t\tvar buttonItem = $( \"<span>\" );\n\n\t\tthis._setText( buttonItem, item.label );\n\t\tthis._addClass( buttonItem, \"ui-selectmenu-text\" );\n\n\t\treturn buttonItem;\n\t},\n\n\t_renderMenu: function( ul, items ) {\n\t\tvar that = this,\n\t\t\tcurrentOptgroup = \"\";\n\n\t\t$.each( items, function( index, item ) {\n\t\t\tvar li;\n\n\t\t\tif ( item.optgroup !== currentOptgroup ) {\n\t\t\t\tli = $( \"<li>\", {\n\t\t\t\t\ttext: item.optgroup\n\t\t\t\t} );\n\t\t\t\tthat._addClass( li, \"ui-selectmenu-optgroup\", \"ui-menu-divider\" +\n\t\t\t\t\t( item.element.parent( \"optgroup\" ).prop( \"disabled\" ) ?\n\t\t\t\t\t\t\" ui-state-disabled\" :\n\t\t\t\t\t\t\"\" ) );\n\n\t\t\t\tli.appendTo( ul );\n\n\t\t\t\tcurrentOptgroup = item.optgroup;\n\t\t\t}\n\n\t\t\tthat._renderItemData( ul, item );\n\t\t} );\n\t},\n\n\t_renderItemData: function( ul, item ) {\n\t\treturn this._renderItem( ul, item ).data( \"ui-selectmenu-item\", item );\n\t},\n\n\t_renderItem: function( ul, item ) {\n\t\tvar li = $( \"<li>\" ),\n\t\t\twrapper = $( \"<div>\", {\n\t\t\t\ttitle: item.element.attr( \"title\" )\n\t\t\t} );\n\n\t\tif ( item.disabled ) {\n\t\t\tthis._addClass( li, null, \"ui-state-disabled\" );\n\t\t}\n\t\tthis._setText( wrapper, item.label );\n\n\t\treturn li.append( wrapper ).appendTo( ul );\n\t},\n\n\t_setText: function( element, value ) {\n\t\tif ( value ) {\n\t\t\telement.text( value );\n\t\t} else {\n\t\t\telement.html( \"&#160;\" );\n\t\t}\n\t},\n\n\t_move: function( direction, event ) {\n\t\tvar item, next,\n\t\t\tfilter = \".ui-menu-item\";\n\n\t\tif ( this.isOpen ) {\n\t\t\titem = this.menuItems.eq( this.focusIndex ).parent( \"li\" );\n\t\t} else {\n\t\t\titem = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( \"li\" );\n\t\t\tfilter += \":not(.ui-state-disabled)\";\n\t\t}\n\n\t\tif ( direction === \"first\" || direction === \"last\" ) {\n\t\t\tnext = item[ direction === \"first\" ? \"prevAll\" : \"nextAll\" ]( filter ).eq( -1 );\n\t\t} else {\n\t\t\tnext = item[ direction + \"All\" ]( filter ).eq( 0 );\n\t\t}\n\n\t\tif ( next.length ) {\n\t\t\tthis.menuInstance.focus( event, next );\n\t\t}\n\t},\n\n\t_getSelectedItem: function() {\n\t\treturn this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( \"li\" );\n\t},\n\n\t_toggle: function( event ) {\n\t\tthis[ this.isOpen ? \"close\" : \"open\" ]( event );\n\t},\n\n\t_setSelection: function() {\n\t\tvar selection;\n\n\t\tif ( !this.range ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( window.getSelection ) {\n\t\t\tselection = window.getSelection();\n\t\t\tselection.removeAllRanges();\n\t\t\tselection.addRange( this.range );\n\n\t\t// Support: IE8\n\t\t} else {\n\t\t\tthis.range.select();\n\t\t}\n\n\t\t// Support: IE\n\t\t// Setting the text selection kills the button focus in IE, but\n\t\t// restoring the focus doesn't kill the selection.\n\t\tthis.button.focus();\n\t},\n\n\t_documentClick: {\n\t\tmousedown: function( event ) {\n\t\t\tif ( !this.isOpen ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( !$( event.target ).closest( \".ui-selectmenu-menu, #\" +\n\t\t\t\t\t$.ui.escapeSelector( this.ids.button ) ).length ) {\n\t\t\t\tthis.close( event );\n\t\t\t}\n\t\t}\n\t},\n\n\t_buttonEvents: {\n\n\t\t// Prevent text selection from being reset when interacting with the selectmenu (#10144)\n\t\tmousedown: function() {\n\t\t\tvar selection;\n\n\t\t\tif ( window.getSelection ) {\n\t\t\t\tselection = window.getSelection();\n\t\t\t\tif ( selection.rangeCount ) {\n\t\t\t\t\tthis.range = selection.getRangeAt( 0 );\n\t\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t} else {\n\t\t\t\tthis.range = document.selection.createRange();\n\t\t\t}\n\t\t},\n\n\t\tclick: function( event ) {\n\t\t\tthis._setSelection();\n\t\t\tthis._toggle( event );\n\t\t},\n\n\t\tkeydown: function( event ) {\n\t\t\tvar preventDefault = true;\n\t\t\tswitch ( event.keyCode ) {\n\t\t\tcase $.ui.keyCode.TAB:\n\t\t\tcase $.ui.keyCode.ESCAPE:\n\t\t\t\tthis.close( event );\n\t\t\t\tpreventDefault = false;\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.ENTER:\n\t\t\t\tif ( this.isOpen ) {\n\t\t\t\t\tthis._selectFocusedItem( event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.UP:\n\t\t\t\tif ( event.altKey ) {\n\t\t\t\t\tthis._toggle( event );\n\t\t\t\t} else {\n\t\t\t\t\tthis._move( \"prev\", event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.DOWN:\n\t\t\t\tif ( event.altKey ) {\n\t\t\t\t\tthis._toggle( event );\n\t\t\t\t} else {\n\t\t\t\t\tthis._move( \"next\", event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.SPACE:\n\t\t\t\tif ( this.isOpen ) {\n\t\t\t\t\tthis._selectFocusedItem( event );\n\t\t\t\t} else {\n\t\t\t\t\tthis._toggle( event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.LEFT:\n\t\t\t\tthis._move( \"prev\", event );\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.RIGHT:\n\t\t\t\tthis._move( \"next\", event );\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.HOME:\n\t\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\t\tthis._move( \"first\", event );\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.END:\n\t\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\t\tthis._move( \"last\", event );\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthis.menu.trigger( event );\n\t\t\t\tpreventDefault = false;\n\t\t\t}\n\n\t\t\tif ( preventDefault ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t}\n\t},\n\n\t_selectFocusedItem: function( event ) {\n\t\tvar item = this.menuItems.eq( this.focusIndex ).parent( \"li\" );\n\t\tif ( !item.hasClass( \"ui-state-disabled\" ) ) {\n\t\t\tthis._select( item.data( \"ui-selectmenu-item\" ), event );\n\t\t}\n\t},\n\n\t_select: function( item, event ) {\n\t\tvar oldIndex = this.element[ 0 ].selectedIndex;\n\n\t\t// Change native select element\n\t\tthis.element[ 0 ].selectedIndex = item.index;\n\t\tthis.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );\n\t\tthis._setAria( item );\n\t\tthis._trigger( \"select\", event, { item: item } );\n\n\t\tif ( item.index !== oldIndex ) {\n\t\t\tthis._trigger( \"change\", event, { item: item } );\n\t\t}\n\n\t\tthis.close( event );\n\t},\n\n\t_setAria: function( item ) {\n\t\tvar id = this.menuItems.eq( item.index ).attr( \"id\" );\n\n\t\tthis.button.attr( {\n\t\t\t\"aria-labelledby\": id,\n\t\t\t\"aria-activedescendant\": id\n\t\t} );\n\t\tthis.menu.attr( \"aria-activedescendant\", id );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"icons\" ) {\n\t\t\tvar icon = this.button.find( \"span.ui-icon\" );\n\t\t\tthis._removeClass( icon, null, this.options.icons.button )\n\t\t\t\t._addClass( icon, null, value.button );\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"appendTo\" ) {\n\t\t\tthis.menuWrap.appendTo( this._appendTo() );\n\t\t}\n\n\t\tif ( key === \"width\" ) {\n\t\t\tthis._resizeButton();\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.menuInstance.option( \"disabled\", value );\n\t\tthis.button.attr( \"aria-disabled\", value );\n\t\tthis._toggleClass( this.button, null, \"ui-state-disabled\", value );\n\n\t\tthis.element.prop( \"disabled\", value );\n\t\tif ( value ) {\n\t\t\tthis.button.attr( \"tabindex\", -1 );\n\t\t\tthis.close();\n\t\t} else {\n\t\t\tthis.button.attr( \"tabindex\", 0 );\n\t\t}\n\t},\n\n\t_appendTo: function() {\n\t\tvar element = this.options.appendTo;\n\n\t\tif ( element ) {\n\t\t\telement = element.jquery || element.nodeType ?\n\t\t\t\t$( element ) :\n\t\t\t\tthis.document.find( element ).eq( 0 );\n\t\t}\n\n\t\tif ( !element || !element[ 0 ] ) {\n\t\t\telement = this.element.closest( \".ui-front, dialog\" );\n\t\t}\n\n\t\tif ( !element.length ) {\n\t\t\telement = this.document[ 0 ].body;\n\t\t}\n\n\t\treturn element;\n\t},\n\n\t_toggleAttr: function() {\n\t\tthis.button.attr( \"aria-expanded\", this.isOpen );\n\n\t\t// We can't use two _toggleClass() calls here, because we need to make sure\n\t\t// we always remove classes first and add them second, otherwise if both classes have the\n\t\t// same theme class, it will be removed after we add it.\n\t\tthis._removeClass( this.button, \"ui-selectmenu-button-\" +\n\t\t\t( this.isOpen ? \"closed\" : \"open\" ) )\n\t\t\t._addClass( this.button, \"ui-selectmenu-button-\" +\n\t\t\t\t( this.isOpen ? \"open\" : \"closed\" ) )\n\t\t\t._toggleClass( this.menuWrap, \"ui-selectmenu-open\", null, this.isOpen );\n\n\t\tthis.menu.attr( \"aria-hidden\", !this.isOpen );\n\t},\n\n\t_resizeButton: function() {\n\t\tvar width = this.options.width;\n\n\t\t// For `width: false`, just remove inline style and stop\n\t\tif ( width === false ) {\n\t\t\tthis.button.css( \"width\", \"\" );\n\t\t\treturn;\n\t\t}\n\n\t\t// For `width: null`, match the width of the original element\n\t\tif ( width === null ) {\n\t\t\twidth = this.element.show().outerWidth();\n\t\t\tthis.element.hide();\n\t\t}\n\n\t\tthis.button.outerWidth( width );\n\t},\n\n\t_resizeMenu: function() {\n\t\tthis.menu.outerWidth( Math.max(\n\t\t\tthis.button.outerWidth(),\n\n\t\t\t// Support: IE10\n\t\t\t// IE10 wraps long text (possibly a rounding bug)\n\t\t\t// so we add 1px to avoid the wrapping\n\t\t\tthis.menu.width( \"\" ).outerWidth() + 1\n\t\t) );\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar options = this._super();\n\n\t\toptions.disabled = this.element.prop( \"disabled\" );\n\n\t\treturn options;\n\t},\n\n\t_parseOptions: function( options ) {\n\t\tvar that = this,\n\t\t\tdata = [];\n\t\toptions.each( function( index, item ) {\n\t\t\tdata.push( that._parseOption( $( item ), index ) );\n\t\t} );\n\t\tthis.items = data;\n\t},\n\n\t_parseOption: function( option, index ) {\n\t\tvar optgroup = option.parent( \"optgroup\" );\n\n\t\treturn {\n\t\t\telement: option,\n\t\t\tindex: index,\n\t\t\tvalue: option.val(),\n\t\t\tlabel: option.text(),\n\t\t\toptgroup: optgroup.attr( \"label\" ) || \"\",\n\t\t\tdisabled: optgroup.prop( \"disabled\" ) || option.prop( \"disabled\" )\n\t\t};\n\t},\n\n\t_destroy: function() {\n\t\tthis._unbindFormResetHandler();\n\t\tthis.menuWrap.remove();\n\t\tthis.button.remove();\n\t\tthis.element.show();\n\t\tthis.element.removeUniqueId();\n\t\tthis.labels.attr( \"for\", this.ids.element );\n\t}\n} ] );\n\n\n/*!\n * jQuery UI Slider 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Slider\n//>>group: Widgets\n//>>description: Displays a flexible slider with ranges and accessibility via keyboard.\n//>>docs: http://api.jqueryui.com/slider/\n//>>demos: http://jqueryui.com/slider/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/slider.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsSlider = $.widget( \"ui.slider\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"slide\",\n\n\toptions: {\n\t\tanimate: false,\n\t\tclasses: {\n\t\t\t\"ui-slider\": \"ui-corner-all\",\n\t\t\t\"ui-slider-handle\": \"ui-corner-all\",\n\n\t\t\t// Note: ui-widget-header isn't the most fittingly semantic framework class for this\n\t\t\t// element, but worked best visually with a variety of themes\n\t\t\t\"ui-slider-range\": \"ui-corner-all ui-widget-header\"\n\t\t},\n\t\tdistance: 0,\n\t\tmax: 100,\n\t\tmin: 0,\n\t\torientation: \"horizontal\",\n\t\trange: false,\n\t\tstep: 1,\n\t\tvalue: 0,\n\t\tvalues: null,\n\n\t\t// Callbacks\n\t\tchange: null,\n\t\tslide: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\n\t// Number of pages in a slider\n\t// (how many times can you page up/down to go through the whole range)\n\tnumPages: 5,\n\n\t_create: function() {\n\t\tthis._keySliding = false;\n\t\tthis._mouseSliding = false;\n\t\tthis._animateOff = true;\n\t\tthis._handleIndex = null;\n\t\tthis._detectOrientation();\n\t\tthis._mouseInit();\n\t\tthis._calculateNewMax();\n\n\t\tthis._addClass( \"ui-slider ui-slider-\" + this.orientation,\n\t\t\t\"ui-widget ui-widget-content\" );\n\n\t\tthis._refresh();\n\n\t\tthis._animateOff = false;\n\t},\n\n\t_refresh: function() {\n\t\tthis._createRange();\n\t\tthis._createHandles();\n\t\tthis._setupEvents();\n\t\tthis._refreshValue();\n\t},\n\n\t_createHandles: function() {\n\t\tvar i, handleCount,\n\t\t\toptions = this.options,\n\t\t\texistingHandles = this.element.find( \".ui-slider-handle\" ),\n\t\t\thandle = \"<span tabindex='0'></span>\",\n\t\t\thandles = [];\n\n\t\thandleCount = ( options.values && options.values.length ) || 1;\n\n\t\tif ( existingHandles.length > handleCount ) {\n\t\t\texistingHandles.slice( handleCount ).remove();\n\t\t\texistingHandles = existingHandles.slice( 0, handleCount );\n\t\t}\n\n\t\tfor ( i = existingHandles.length; i < handleCount; i++ ) {\n\t\t\thandles.push( handle );\n\t\t}\n\n\t\tthis.handles = existingHandles.add( $( handles.join( \"\" ) ).appendTo( this.element ) );\n\n\t\tthis._addClass( this.handles, \"ui-slider-handle\", \"ui-state-default\" );\n\n\t\tthis.handle = this.handles.eq( 0 );\n\n\t\tthis.handles.each( function( i ) {\n\t\t\t$( this )\n\t\t\t\t.data( \"ui-slider-handle-index\", i )\n\t\t\t\t.attr( \"tabIndex\", 0 );\n\t\t} );\n\t},\n\n\t_createRange: function() {\n\t\tvar options = this.options;\n\n\t\tif ( options.range ) {\n\t\t\tif ( options.range === true ) {\n\t\t\t\tif ( !options.values ) {\n\t\t\t\t\toptions.values = [ this._valueMin(), this._valueMin() ];\n\t\t\t\t} else if ( options.values.length && options.values.length !== 2 ) {\n\t\t\t\t\toptions.values = [ options.values[ 0 ], options.values[ 0 ] ];\n\t\t\t\t} else if ( $.isArray( options.values ) ) {\n\t\t\t\t\toptions.values = options.values.slice( 0 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !this.range || !this.range.length ) {\n\t\t\t\tthis.range = $( \"<div>\" )\n\t\t\t\t\t.appendTo( this.element );\n\n\t\t\t\tthis._addClass( this.range, \"ui-slider-range\" );\n\t\t\t} else {\n\t\t\t\tthis._removeClass( this.range, \"ui-slider-range-min ui-slider-range-max\" );\n\n\t\t\t\t// Handle range switching from true to min/max\n\t\t\t\tthis.range.css( {\n\t\t\t\t\t\"left\": \"\",\n\t\t\t\t\t\"bottom\": \"\"\n\t\t\t\t} );\n\t\t\t}\n\t\t\tif ( options.range === \"min\" || options.range === \"max\" ) {\n\t\t\t\tthis._addClass( this.range, \"ui-slider-range-\" + options.range );\n\t\t\t}\n\t\t} else {\n\t\t\tif ( this.range ) {\n\t\t\t\tthis.range.remove();\n\t\t\t}\n\t\t\tthis.range = null;\n\t\t}\n\t},\n\n\t_setupEvents: function() {\n\t\tthis._off( this.handles );\n\t\tthis._on( this.handles, this._handleEvents );\n\t\tthis._hoverable( this.handles );\n\t\tthis._focusable( this.handles );\n\t},\n\n\t_destroy: function() {\n\t\tthis.handles.remove();\n\t\tif ( this.range ) {\n\t\t\tthis.range.remove();\n\t\t}\n\n\t\tthis._mouseDestroy();\n\t},\n\n\t_mouseCapture: function( event ) {\n\t\tvar position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,\n\t\t\tthat = this,\n\t\t\to = this.options;\n\n\t\tif ( o.disabled ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.elementSize = {\n\t\t\twidth: this.element.outerWidth(),\n\t\t\theight: this.element.outerHeight()\n\t\t};\n\t\tthis.elementOffset = this.element.offset();\n\n\t\tposition = { x: event.pageX, y: event.pageY };\n\t\tnormValue = this._normValueFromMouse( position );\n\t\tdistance = this._valueMax() - this._valueMin() + 1;\n\t\tthis.handles.each( function( i ) {\n\t\t\tvar thisDistance = Math.abs( normValue - that.values( i ) );\n\t\t\tif ( ( distance > thisDistance ) ||\n\t\t\t\t( distance === thisDistance &&\n\t\t\t\t\t( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) {\n\t\t\t\tdistance = thisDistance;\n\t\t\t\tclosestHandle = $( this );\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t} );\n\n\t\tallowed = this._start( event, index );\n\t\tif ( allowed === false ) {\n\t\t\treturn false;\n\t\t}\n\t\tthis._mouseSliding = true;\n\n\t\tthis._handleIndex = index;\n\n\t\tthis._addClass( closestHandle, null, \"ui-state-active\" );\n\t\tclosestHandle.trigger( \"focus\" );\n\n\t\toffset = closestHandle.offset();\n\t\tmouseOverHandle = !$( event.target ).parents().addBack().is( \".ui-slider-handle\" );\n\t\tthis._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {\n\t\t\tleft: event.pageX - offset.left - ( closestHandle.width() / 2 ),\n\t\t\ttop: event.pageY - offset.top -\n\t\t\t\t( closestHandle.height() / 2 ) -\n\t\t\t\t( parseInt( closestHandle.css( \"borderTopWidth\" ), 10 ) || 0 ) -\n\t\t\t\t( parseInt( closestHandle.css( \"borderBottomWidth\" ), 10 ) || 0 ) +\n\t\t\t\t( parseInt( closestHandle.css( \"marginTop\" ), 10 ) || 0 )\n\t\t};\n\n\t\tif ( !this.handles.hasClass( \"ui-state-hover\" ) ) {\n\t\t\tthis._slide( event, index, normValue );\n\t\t}\n\t\tthis._animateOff = true;\n\t\treturn true;\n\t},\n\n\t_mouseStart: function() {\n\t\treturn true;\n\t},\n\n\t_mouseDrag: function( event ) {\n\t\tvar position = { x: event.pageX, y: event.pageY },\n\t\t\tnormValue = this._normValueFromMouse( position );\n\n\t\tthis._slide( event, this._handleIndex, normValue );\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\t\tthis._removeClass( this.handles, null, \"ui-state-active\" );\n\t\tthis._mouseSliding = false;\n\n\t\tthis._stop( event, this._handleIndex );\n\t\tthis._change( event, this._handleIndex );\n\n\t\tthis._handleIndex = null;\n\t\tthis._clickOffset = null;\n\t\tthis._animateOff = false;\n\n\t\treturn false;\n\t},\n\n\t_detectOrientation: function() {\n\t\tthis.orientation = ( this.options.orientation === \"vertical\" ) ? \"vertical\" : \"horizontal\";\n\t},\n\n\t_normValueFromMouse: function( position ) {\n\t\tvar pixelTotal,\n\t\t\tpixelMouse,\n\t\t\tpercentMouse,\n\t\t\tvalueTotal,\n\t\t\tvalueMouse;\n\n\t\tif ( this.orientation === \"horizontal\" ) {\n\t\t\tpixelTotal = this.elementSize.width;\n\t\t\tpixelMouse = position.x - this.elementOffset.left -\n\t\t\t\t( this._clickOffset ? this._clickOffset.left : 0 );\n\t\t} else {\n\t\t\tpixelTotal = this.elementSize.height;\n\t\t\tpixelMouse = position.y - this.elementOffset.top -\n\t\t\t\t( this._clickOffset ? this._clickOffset.top : 0 );\n\t\t}\n\n\t\tpercentMouse = ( pixelMouse / pixelTotal );\n\t\tif ( percentMouse > 1 ) {\n\t\t\tpercentMouse = 1;\n\t\t}\n\t\tif ( percentMouse < 0 ) {\n\t\t\tpercentMouse = 0;\n\t\t}\n\t\tif ( this.orientation === \"vertical\" ) {\n\t\t\tpercentMouse = 1 - percentMouse;\n\t\t}\n\n\t\tvalueTotal = this._valueMax() - this._valueMin();\n\t\tvalueMouse = this._valueMin() + percentMouse * valueTotal;\n\n\t\treturn this._trimAlignValue( valueMouse );\n\t},\n\n\t_uiHash: function( index, value, values ) {\n\t\tvar uiHash = {\n\t\t\thandle: this.handles[ index ],\n\t\t\thandleIndex: index,\n\t\t\tvalue: value !== undefined ? value : this.value()\n\t\t};\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\tuiHash.value = value !== undefined ? value : this.values( index );\n\t\t\tuiHash.values = values || this.values();\n\t\t}\n\n\t\treturn uiHash;\n\t},\n\n\t_hasMultipleValues: function() {\n\t\treturn this.options.values && this.options.values.length;\n\t},\n\n\t_start: function( event, index ) {\n\t\treturn this._trigger( \"start\", event, this._uiHash( index ) );\n\t},\n\n\t_slide: function( event, index, newVal ) {\n\t\tvar allowed, otherVal,\n\t\t\tcurrentValue = this.value(),\n\t\t\tnewValues = this.values();\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\totherVal = this.values( index ? 0 : 1 );\n\t\t\tcurrentValue = this.values( index );\n\n\t\t\tif ( this.options.values.length === 2 && this.options.range === true ) {\n\t\t\t\tnewVal =  index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal );\n\t\t\t}\n\n\t\t\tnewValues[ index ] = newVal;\n\t\t}\n\n\t\tif ( newVal === currentValue ) {\n\t\t\treturn;\n\t\t}\n\n\t\tallowed = this._trigger( \"slide\", event, this._uiHash( index, newVal, newValues ) );\n\n\t\t// A slide can be canceled by returning false from the slide callback\n\t\tif ( allowed === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\tthis.values( index, newVal );\n\t\t} else {\n\t\t\tthis.value( newVal );\n\t\t}\n\t},\n\n\t_stop: function( event, index ) {\n\t\tthis._trigger( \"stop\", event, this._uiHash( index ) );\n\t},\n\n\t_change: function( event, index ) {\n\t\tif ( !this._keySliding && !this._mouseSliding ) {\n\n\t\t\t//store the last changed value index for reference when handles overlap\n\t\t\tthis._lastChangedValue = index;\n\t\t\tthis._trigger( \"change\", event, this._uiHash( index ) );\n\t\t}\n\t},\n\n\tvalue: function( newValue ) {\n\t\tif ( arguments.length ) {\n\t\t\tthis.options.value = this._trimAlignValue( newValue );\n\t\t\tthis._refreshValue();\n\t\t\tthis._change( null, 0 );\n\t\t\treturn;\n\t\t}\n\n\t\treturn this._value();\n\t},\n\n\tvalues: function( index, newValue ) {\n\t\tvar vals,\n\t\t\tnewValues,\n\t\t\ti;\n\n\t\tif ( arguments.length > 1 ) {\n\t\t\tthis.options.values[ index ] = this._trimAlignValue( newValue );\n\t\t\tthis._refreshValue();\n\t\t\tthis._change( null, index );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( arguments.length ) {\n\t\t\tif ( $.isArray( arguments[ 0 ] ) ) {\n\t\t\t\tvals = this.options.values;\n\t\t\t\tnewValues = arguments[ 0 ];\n\t\t\t\tfor ( i = 0; i < vals.length; i += 1 ) {\n\t\t\t\t\tvals[ i ] = this._trimAlignValue( newValues[ i ] );\n\t\t\t\t\tthis._change( null, i );\n\t\t\t\t}\n\t\t\t\tthis._refreshValue();\n\t\t\t} else {\n\t\t\t\tif ( this._hasMultipleValues() ) {\n\t\t\t\t\treturn this._values( index );\n\t\t\t\t} else {\n\t\t\t\t\treturn this.value();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\treturn this._values();\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar i,\n\t\t\tvalsLength = 0;\n\n\t\tif ( key === \"range\" && this.options.range === true ) {\n\t\t\tif ( value === \"min\" ) {\n\t\t\t\tthis.options.value = this._values( 0 );\n\t\t\t\tthis.options.values = null;\n\t\t\t} else if ( value === \"max\" ) {\n\t\t\t\tthis.options.value = this._values( this.options.values.length - 1 );\n\t\t\t\tthis.options.values = null;\n\t\t\t}\n\t\t}\n\n\t\tif ( $.isArray( this.options.values ) ) {\n\t\t\tvalsLength = this.options.values.length;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tswitch ( key ) {\n\t\t\tcase \"orientation\":\n\t\t\t\tthis._detectOrientation();\n\t\t\t\tthis._removeClass( \"ui-slider-horizontal ui-slider-vertical\" )\n\t\t\t\t\t._addClass( \"ui-slider-\" + this.orientation );\n\t\t\t\tthis._refreshValue();\n\t\t\t\tif ( this.options.range ) {\n\t\t\t\t\tthis._refreshRange( value );\n\t\t\t\t}\n\n\t\t\t\t// Reset positioning from previous orientation\n\t\t\t\tthis.handles.css( value === \"horizontal\" ? \"bottom\" : \"left\", \"\" );\n\t\t\t\tbreak;\n\t\t\tcase \"value\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._refreshValue();\n\t\t\t\tthis._change( null, 0 );\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t\tcase \"values\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._refreshValue();\n\n\t\t\t\t// Start from the last handle to prevent unreachable handles (#9046)\n\t\t\t\tfor ( i = valsLength - 1; i >= 0; i-- ) {\n\t\t\t\t\tthis._change( null, i );\n\t\t\t\t}\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t\tcase \"step\":\n\t\t\tcase \"min\":\n\t\t\tcase \"max\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._calculateNewMax();\n\t\t\t\tthis._refreshValue();\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t\tcase \"range\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._refresh();\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t},\n\n\t//internal value getter\n\t// _value() returns value trimmed by min and max, aligned by step\n\t_value: function() {\n\t\tvar val = this.options.value;\n\t\tval = this._trimAlignValue( val );\n\n\t\treturn val;\n\t},\n\n\t//internal values getter\n\t// _values() returns array of values trimmed by min and max, aligned by step\n\t// _values( index ) returns single value trimmed by min and max, aligned by step\n\t_values: function( index ) {\n\t\tvar val,\n\t\t\tvals,\n\t\t\ti;\n\n\t\tif ( arguments.length ) {\n\t\t\tval = this.options.values[ index ];\n\t\t\tval = this._trimAlignValue( val );\n\n\t\t\treturn val;\n\t\t} else if ( this._hasMultipleValues() ) {\n\n\t\t\t// .slice() creates a copy of the array\n\t\t\t// this copy gets trimmed by min and max and then returned\n\t\t\tvals = this.options.values.slice();\n\t\t\tfor ( i = 0; i < vals.length; i += 1 ) {\n\t\t\t\tvals[ i ] = this._trimAlignValue( vals[ i ] );\n\t\t\t}\n\n\t\t\treturn vals;\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\t},\n\n\t// Returns the step-aligned value that val is closest to, between (inclusive) min and max\n\t_trimAlignValue: function( val ) {\n\t\tif ( val <= this._valueMin() ) {\n\t\t\treturn this._valueMin();\n\t\t}\n\t\tif ( val >= this._valueMax() ) {\n\t\t\treturn this._valueMax();\n\t\t}\n\t\tvar step = ( this.options.step > 0 ) ? this.options.step : 1,\n\t\t\tvalModStep = ( val - this._valueMin() ) % step,\n\t\t\talignValue = val - valModStep;\n\n\t\tif ( Math.abs( valModStep ) * 2 >= step ) {\n\t\t\talignValue += ( valModStep > 0 ) ? step : ( -step );\n\t\t}\n\n\t\t// Since JavaScript has problems with large floats, round\n\t\t// the final value to 5 digits after the decimal point (see #4124)\n\t\treturn parseFloat( alignValue.toFixed( 5 ) );\n\t},\n\n\t_calculateNewMax: function() {\n\t\tvar max = this.options.max,\n\t\t\tmin = this._valueMin(),\n\t\t\tstep = this.options.step,\n\t\t\taboveMin = Math.round( ( max - min ) / step ) * step;\n\t\tmax = aboveMin + min;\n\t\tif ( max > this.options.max ) {\n\n\t\t\t//If max is not divisible by step, rounding off may increase its value\n\t\t\tmax -= step;\n\t\t}\n\t\tthis.max = parseFloat( max.toFixed( this._precision() ) );\n\t},\n\n\t_precision: function() {\n\t\tvar precision = this._precisionOf( this.options.step );\n\t\tif ( this.options.min !== null ) {\n\t\t\tprecision = Math.max( precision, this._precisionOf( this.options.min ) );\n\t\t}\n\t\treturn precision;\n\t},\n\n\t_precisionOf: function( num ) {\n\t\tvar str = num.toString(),\n\t\t\tdecimal = str.indexOf( \".\" );\n\t\treturn decimal === -1 ? 0 : str.length - decimal - 1;\n\t},\n\n\t_valueMin: function() {\n\t\treturn this.options.min;\n\t},\n\n\t_valueMax: function() {\n\t\treturn this.max;\n\t},\n\n\t_refreshRange: function( orientation ) {\n\t\tif ( orientation === \"vertical\" ) {\n\t\t\tthis.range.css( { \"width\": \"\", \"left\": \"\" } );\n\t\t}\n\t\tif ( orientation === \"horizontal\" ) {\n\t\t\tthis.range.css( { \"height\": \"\", \"bottom\": \"\" } );\n\t\t}\n\t},\n\n\t_refreshValue: function() {\n\t\tvar lastValPercent, valPercent, value, valueMin, valueMax,\n\t\t\toRange = this.options.range,\n\t\t\to = this.options,\n\t\t\tthat = this,\n\t\t\tanimate = ( !this._animateOff ) ? o.animate : false,\n\t\t\t_set = {};\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\tthis.handles.each( function( i ) {\n\t\t\t\tvalPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() -\n\t\t\t\t\tthat._valueMin() ) * 100;\n\t\t\t\t_set[ that.orientation === \"horizontal\" ? \"left\" : \"bottom\" ] = valPercent + \"%\";\n\t\t\t\t$( this ).stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( _set, o.animate );\n\t\t\t\tif ( that.options.range === true ) {\n\t\t\t\t\tif ( that.orientation === \"horizontal\" ) {\n\t\t\t\t\t\tif ( i === 0 ) {\n\t\t\t\t\t\t\tthat.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\tleft: valPercent + \"%\"\n\t\t\t\t\t\t\t}, o.animate );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( i === 1 ) {\n\t\t\t\t\t\t\tthat.range[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\twidth: ( valPercent - lastValPercent ) + \"%\"\n\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tqueue: false,\n\t\t\t\t\t\t\t\tduration: o.animate\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif ( i === 0 ) {\n\t\t\t\t\t\t\tthat.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\tbottom: ( valPercent ) + \"%\"\n\t\t\t\t\t\t\t}, o.animate );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( i === 1 ) {\n\t\t\t\t\t\t\tthat.range[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\theight: ( valPercent - lastValPercent ) + \"%\"\n\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tqueue: false,\n\t\t\t\t\t\t\t\tduration: o.animate\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlastValPercent = valPercent;\n\t\t\t} );\n\t\t} else {\n\t\t\tvalue = this.value();\n\t\t\tvalueMin = this._valueMin();\n\t\t\tvalueMax = this._valueMax();\n\t\t\tvalPercent = ( valueMax !== valueMin ) ?\n\t\t\t\t\t( value - valueMin ) / ( valueMax - valueMin ) * 100 :\n\t\t\t\t\t0;\n\t\t\t_set[ this.orientation === \"horizontal\" ? \"left\" : \"bottom\" ] = valPercent + \"%\";\n\t\t\tthis.handle.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( _set, o.animate );\n\n\t\t\tif ( oRange === \"min\" && this.orientation === \"horizontal\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\twidth: valPercent + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t\tif ( oRange === \"max\" && this.orientation === \"horizontal\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\twidth: ( 100 - valPercent ) + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t\tif ( oRange === \"min\" && this.orientation === \"vertical\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\theight: valPercent + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t\tif ( oRange === \"max\" && this.orientation === \"vertical\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\theight: ( 100 - valPercent ) + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t}\n\t},\n\n\t_handleEvents: {\n\t\tkeydown: function( event ) {\n\t\t\tvar allowed, curVal, newVal, step,\n\t\t\t\tindex = $( event.target ).data( \"ui-slider-handle-index\" );\n\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase $.ui.keyCode.HOME:\n\t\t\t\tcase $.ui.keyCode.END:\n\t\t\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\t\tcase $.ui.keyCode.UP:\n\t\t\t\tcase $.ui.keyCode.RIGHT:\n\t\t\t\tcase $.ui.keyCode.DOWN:\n\t\t\t\tcase $.ui.keyCode.LEFT:\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tif ( !this._keySliding ) {\n\t\t\t\t\t\tthis._keySliding = true;\n\t\t\t\t\t\tthis._addClass( $( event.target ), null, \"ui-state-active\" );\n\t\t\t\t\t\tallowed = this._start( event, index );\n\t\t\t\t\t\tif ( allowed === false ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tstep = this.options.step;\n\t\t\tif ( this._hasMultipleValues() ) {\n\t\t\t\tcurVal = newVal = this.values( index );\n\t\t\t} else {\n\t\t\t\tcurVal = newVal = this.value();\n\t\t\t}\n\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase $.ui.keyCode.HOME:\n\t\t\t\t\tnewVal = this._valueMin();\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.END:\n\t\t\t\t\tnewVal = this._valueMax();\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\t\t\tnewVal = this._trimAlignValue(\n\t\t\t\t\t\tcurVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\t\t\tnewVal = this._trimAlignValue(\n\t\t\t\t\t\tcurVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) );\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.UP:\n\t\t\t\tcase $.ui.keyCode.RIGHT:\n\t\t\t\t\tif ( curVal === this._valueMax() ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tnewVal = this._trimAlignValue( curVal + step );\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.DOWN:\n\t\t\t\tcase $.ui.keyCode.LEFT:\n\t\t\t\t\tif ( curVal === this._valueMin() ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tnewVal = this._trimAlignValue( curVal - step );\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tthis._slide( event, index, newVal );\n\t\t},\n\t\tkeyup: function( event ) {\n\t\t\tvar index = $( event.target ).data( \"ui-slider-handle-index\" );\n\n\t\t\tif ( this._keySliding ) {\n\t\t\t\tthis._keySliding = false;\n\t\t\t\tthis._stop( event, index );\n\t\t\t\tthis._change( event, index );\n\t\t\t\tthis._removeClass( $( event.target ), null, \"ui-state-active\" );\n\t\t\t}\n\t\t}\n\t}\n} );\n\n\n/*!\n * jQuery UI Sortable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Sortable\n//>>group: Interactions\n//>>description: Enables items in a list to be sorted using the mouse.\n//>>docs: http://api.jqueryui.com/sortable/\n//>>demos: http://jqueryui.com/sortable/\n//>>css.structure: ../../themes/base/sortable.css\n\n\n\nvar widgetsSortable = $.widget( \"ui.sortable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"sort\",\n\tready: false,\n\toptions: {\n\t\tappendTo: \"parent\",\n\t\taxis: false,\n\t\tconnectWith: false,\n\t\tcontainment: false,\n\t\tcursor: \"auto\",\n\t\tcursorAt: false,\n\t\tdropOnEmpty: true,\n\t\tforcePlaceholderSize: false,\n\t\tforceHelperSize: false,\n\t\tgrid: false,\n\t\thandle: false,\n\t\thelper: \"original\",\n\t\titems: \"> *\",\n\t\topacity: false,\n\t\tplaceholder: false,\n\t\trevert: false,\n\t\tscroll: true,\n\t\tscrollSensitivity: 20,\n\t\tscrollSpeed: 20,\n\t\tscope: \"default\",\n\t\ttolerance: \"intersect\",\n\t\tzIndex: 1000,\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tbeforeStop: null,\n\t\tchange: null,\n\t\tdeactivate: null,\n\t\tout: null,\n\t\tover: null,\n\t\treceive: null,\n\t\tremove: null,\n\t\tsort: null,\n\t\tstart: null,\n\t\tstop: null,\n\t\tupdate: null\n\t},\n\n\t_isOverAxis: function( x, reference, size ) {\n\t\treturn ( x >= reference ) && ( x < ( reference + size ) );\n\t},\n\n\t_isFloating: function( item ) {\n\t\treturn ( /left|right/ ).test( item.css( \"float\" ) ) ||\n\t\t\t( /inline|table-cell/ ).test( item.css( \"display\" ) );\n\t},\n\n\t_create: function() {\n\t\tthis.containerCache = {};\n\t\tthis._addClass( \"ui-sortable\" );\n\n\t\t//Get the items\n\t\tthis.refresh();\n\n\t\t//Let's determine the parent's offset\n\t\tthis.offset = this.element.offset();\n\n\t\t//Initialize mouse events for interaction\n\t\tthis._mouseInit();\n\n\t\tthis._setHandleClassName();\n\n\t\t//We're ready to go\n\t\tthis.ready = true;\n\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"handle\" ) {\n\t\t\tthis._setHandleClassName();\n\t\t}\n\t},\n\n\t_setHandleClassName: function() {\n\t\tvar that = this;\n\t\tthis._removeClass( this.element.find( \".ui-sortable-handle\" ), \"ui-sortable-handle\" );\n\t\t$.each( this.items, function() {\n\t\t\tthat._addClass(\n\t\t\t\tthis.instance.options.handle ?\n\t\t\t\t\tthis.item.find( this.instance.options.handle ) :\n\t\t\t\t\tthis.item,\n\t\t\t\t\"ui-sortable-handle\"\n\t\t\t);\n\t\t} );\n\t},\n\n\t_destroy: function() {\n\t\tthis._mouseDestroy();\n\n\t\tfor ( var i = this.items.length - 1; i >= 0; i-- ) {\n\t\t\tthis.items[ i ].item.removeData( this.widgetName + \"-item\" );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_mouseCapture: function( event, overrideHandle ) {\n\t\tvar currentItem = null,\n\t\t\tvalidHandle = false,\n\t\t\tthat = this;\n\n\t\tif ( this.reverting ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( this.options.disabled || this.options.type === \"static\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t//We have to refresh the items data once first\n\t\tthis._refreshItems( event );\n\n\t\t//Find out if the clicked node (or one of its parents) is a actual item in this.items\n\t\t$( event.target ).parents().each( function() {\n\t\t\tif ( $.data( this, that.widgetName + \"-item\" ) === that ) {\n\t\t\t\tcurrentItem = $( this );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} );\n\t\tif ( $.data( event.target, that.widgetName + \"-item\" ) === that ) {\n\t\t\tcurrentItem = $( event.target );\n\t\t}\n\n\t\tif ( !currentItem ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( this.options.handle && !overrideHandle ) {\n\t\t\t$( this.options.handle, currentItem ).find( \"*\" ).addBack().each( function() {\n\t\t\t\tif ( this === event.target ) {\n\t\t\t\t\tvalidHandle = true;\n\t\t\t\t}\n\t\t\t} );\n\t\t\tif ( !validHandle ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.currentItem = currentItem;\n\t\tthis._removeCurrentsFromItems();\n\t\treturn true;\n\n\t},\n\n\t_mouseStart: function( event, overrideHandle, noActivation ) {\n\n\t\tvar i, body,\n\t\t\to = this.options;\n\n\t\tthis.currentContainer = this;\n\n\t\t//We only need to call refreshPositions, because the refreshItems call has been moved to\n\t\t// mouseCapture\n\t\tthis.refreshPositions();\n\n\t\t//Create and append the visible helper\n\t\tthis.helper = this._createHelper( event );\n\n\t\t//Cache the helper size\n\t\tthis._cacheHelperProportions();\n\n\t\t/*\n\t\t * - Position generation -\n\t\t * This block generates everything position related - it's the core of draggables.\n\t\t */\n\n\t\t//Cache the margins of the original element\n\t\tthis._cacheMargins();\n\n\t\t//Get the next scrolling parent\n\t\tthis.scrollParent = this.helper.scrollParent();\n\n\t\t//The element's absolute position on the page minus margins\n\t\tthis.offset = this.currentItem.offset();\n\t\tthis.offset = {\n\t\t\ttop: this.offset.top - this.margins.top,\n\t\t\tleft: this.offset.left - this.margins.left\n\t\t};\n\n\t\t$.extend( this.offset, {\n\t\t\tclick: { //Where the click happened, relative to the element\n\t\t\t\tleft: event.pageX - this.offset.left,\n\t\t\t\ttop: event.pageY - this.offset.top\n\t\t\t},\n\t\t\tparent: this._getParentOffset(),\n\n\t\t\t// This is a relative to absolute position minus the actual position calculation -\n\t\t\t// only used for relative positioned helper\n\t\t\trelative: this._getRelativeOffset()\n\t\t} );\n\n\t\t// Only after we got the offset, we can change the helper's position to absolute\n\t\t// TODO: Still need to figure out a way to make relative sorting possible\n\t\tthis.helper.css( \"position\", \"absolute\" );\n\t\tthis.cssPosition = this.helper.css( \"position\" );\n\n\t\t//Generate the original position\n\t\tthis.originalPosition = this._generatePosition( event );\n\t\tthis.originalPageX = event.pageX;\n\t\tthis.originalPageY = event.pageY;\n\n\t\t//Adjust the mouse offset relative to the helper if \"cursorAt\" is supplied\n\t\t( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );\n\n\t\t//Cache the former DOM position\n\t\tthis.domPosition = {\n\t\t\tprev: this.currentItem.prev()[ 0 ],\n\t\t\tparent: this.currentItem.parent()[ 0 ]\n\t\t};\n\n\t\t// If the helper is not the original, hide the original so it's not playing any role during\n\t\t// the drag, won't cause anything bad this way\n\t\tif ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {\n\t\t\tthis.currentItem.hide();\n\t\t}\n\n\t\t//Create the placeholder\n\t\tthis._createPlaceholder();\n\n\t\t//Set a containment if given in the options\n\t\tif ( o.containment ) {\n\t\t\tthis._setContainment();\n\t\t}\n\n\t\tif ( o.cursor && o.cursor !== \"auto\" ) { // cursor option\n\t\t\tbody = this.document.find( \"body\" );\n\n\t\t\t// Support: IE\n\t\t\tthis.storedCursor = body.css( \"cursor\" );\n\t\t\tbody.css( \"cursor\", o.cursor );\n\n\t\t\tthis.storedStylesheet =\n\t\t\t\t$( \"<style>*{ cursor: \" + o.cursor + \" !important; }</style>\" ).appendTo( body );\n\t\t}\n\n\t\tif ( o.opacity ) { // opacity option\n\t\t\tif ( this.helper.css( \"opacity\" ) ) {\n\t\t\t\tthis._storedOpacity = this.helper.css( \"opacity\" );\n\t\t\t}\n\t\t\tthis.helper.css( \"opacity\", o.opacity );\n\t\t}\n\n\t\tif ( o.zIndex ) { // zIndex option\n\t\t\tif ( this.helper.css( \"zIndex\" ) ) {\n\t\t\t\tthis._storedZIndex = this.helper.css( \"zIndex\" );\n\t\t\t}\n\t\t\tthis.helper.css( \"zIndex\", o.zIndex );\n\t\t}\n\n\t\t//Prepare scrolling\n\t\tif ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\tthis.scrollParent[ 0 ].tagName !== \"HTML\" ) {\n\t\t\tthis.overflowOffset = this.scrollParent.offset();\n\t\t}\n\n\t\t//Call callbacks\n\t\tthis._trigger( \"start\", event, this._uiHash() );\n\n\t\t//Recache the helper size\n\t\tif ( !this._preserveHelperProportions ) {\n\t\t\tthis._cacheHelperProportions();\n\t\t}\n\n\t\t//Post \"activate\" events to possible containers\n\t\tif ( !noActivation ) {\n\t\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\t\tthis.containers[ i ]._trigger( \"activate\", event, this._uiHash( this ) );\n\t\t\t}\n\t\t}\n\n\t\t//Prepare possible droppables\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.current = this;\n\t\t}\n\n\t\tif ( $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( this, event );\n\t\t}\n\n\t\tthis.dragging = true;\n\n\t\tthis._addClass( this.helper, \"ui-sortable-helper\" );\n\n\t\t// Execute the drag once - this causes the helper not to be visiblebefore getting its\n\t\t// correct position\n\t\tthis._mouseDrag( event );\n\t\treturn true;\n\n\t},\n\n\t_mouseDrag: function( event ) {\n\t\tvar i, item, itemElement, intersection,\n\t\t\to = this.options,\n\t\t\tscrolled = false;\n\n\t\t//Compute the helpers position\n\t\tthis.position = this._generatePosition( event );\n\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\n\t\tif ( !this.lastPositionAbs ) {\n\t\t\tthis.lastPositionAbs = this.positionAbs;\n\t\t}\n\n\t\t//Do scrolling\n\t\tif ( this.options.scroll ) {\n\t\t\tif ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t\tthis.scrollParent[ 0 ].tagName !== \"HTML\" ) {\n\n\t\t\t\tif ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -\n\t\t\t\t\t\tevent.pageY < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollTop =\n\t\t\t\t\t\tscrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollTop =\n\t\t\t\t\t\tscrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;\n\t\t\t\t}\n\n\t\t\t\tif ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -\n\t\t\t\t\t\tevent.pageX < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft = scrolled =\n\t\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft = scrolled =\n\t\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tif ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );\n\t\t\t\t} else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );\n\t\t\t\t}\n\n\t\t\t\tif ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollLeft(\n\t\t\t\t\t\tthis.document.scrollLeft() - o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t} else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollLeft(\n\t\t\t\t\t\tthis.document.scrollLeft() + o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t\t$.ui.ddmanager.prepareOffsets( this, event );\n\t\t\t}\n\t\t}\n\n\t\t//Regenerate the absolute position used for position checks\n\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\n\t\t//Set the helper position\n\t\tif ( !this.options.axis || this.options.axis !== \"y\" ) {\n\t\t\tthis.helper[ 0 ].style.left = this.position.left + \"px\";\n\t\t}\n\t\tif ( !this.options.axis || this.options.axis !== \"x\" ) {\n\t\t\tthis.helper[ 0 ].style.top = this.position.top + \"px\";\n\t\t}\n\n\t\t//Rearrange\n\t\tfor ( i = this.items.length - 1; i >= 0; i-- ) {\n\n\t\t\t//Cache variables and intersection, continue if no intersection\n\t\t\titem = this.items[ i ];\n\t\t\titemElement = item.item[ 0 ];\n\t\t\tintersection = this._intersectsWithPointer( item );\n\t\t\tif ( !intersection ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Only put the placeholder inside the current Container, skip all\n\t\t\t// items from other containers. This works because when moving\n\t\t\t// an item from one container to another the\n\t\t\t// currentContainer is switched before the placeholder is moved.\n\t\t\t//\n\t\t\t// Without this, moving items in \"sub-sortables\" can cause\n\t\t\t// the placeholder to jitter between the outer and inner container.\n\t\t\tif ( item.instance !== this.currentContainer ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Cannot intersect with itself\n\t\t\t// no useless actions that have been done before\n\t\t\t// no action if the item moved is the parent of the item checked\n\t\t\tif ( itemElement !== this.currentItem[ 0 ] &&\n\t\t\t\tthis.placeholder[ intersection === 1 ? \"next\" : \"prev\" ]()[ 0 ] !== itemElement &&\n\t\t\t\t!$.contains( this.placeholder[ 0 ], itemElement ) &&\n\t\t\t\t( this.options.type === \"semi-dynamic\" ?\n\t\t\t\t\t!$.contains( this.element[ 0 ], itemElement ) :\n\t\t\t\t\ttrue\n\t\t\t\t)\n\t\t\t) {\n\n\t\t\t\tthis.direction = intersection === 1 ? \"down\" : \"up\";\n\n\t\t\t\tif ( this.options.tolerance === \"pointer\" || this._intersectsWithSides( item ) ) {\n\t\t\t\t\tthis._rearrange( event, item );\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis._trigger( \"change\", event, this._uiHash() );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t//Post events to containers\n\t\tthis._contactContainers( event );\n\n\t\t//Interconnect with droppables\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.drag( this, event );\n\t\t}\n\n\t\t//Call callbacks\n\t\tthis._trigger( \"sort\", event, this._uiHash() );\n\n\t\tthis.lastPositionAbs = this.positionAbs;\n\t\treturn false;\n\n\t},\n\n\t_mouseStop: function( event, noPropagation ) {\n\n\t\tif ( !event ) {\n\t\t\treturn;\n\t\t}\n\n\t\t//If we are using droppables, inform the manager about the drop\n\t\tif ( $.ui.ddmanager && !this.options.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.drop( this, event );\n\t\t}\n\n\t\tif ( this.options.revert ) {\n\t\t\tvar that = this,\n\t\t\t\tcur = this.placeholder.offset(),\n\t\t\t\taxis = this.options.axis,\n\t\t\t\tanimation = {};\n\n\t\t\tif ( !axis || axis === \"x\" ) {\n\t\t\t\tanimation.left = cur.left - this.offset.parent.left - this.margins.left +\n\t\t\t\t\t( this.offsetParent[ 0 ] === this.document[ 0 ].body ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\tthis.offsetParent[ 0 ].scrollLeft\n\t\t\t\t\t);\n\t\t\t}\n\t\t\tif ( !axis || axis === \"y\" ) {\n\t\t\t\tanimation.top = cur.top - this.offset.parent.top - this.margins.top +\n\t\t\t\t\t( this.offsetParent[ 0 ] === this.document[ 0 ].body ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\tthis.offsetParent[ 0 ].scrollTop\n\t\t\t\t\t);\n\t\t\t}\n\t\t\tthis.reverting = true;\n\t\t\t$( this.helper ).animate(\n\t\t\t\tanimation,\n\t\t\t\tparseInt( this.options.revert, 10 ) || 500,\n\t\t\t\tfunction() {\n\t\t\t\t\tthat._clear( event );\n\t\t\t\t}\n\t\t\t);\n\t\t} else {\n\t\t\tthis._clear( event, noPropagation );\n\t\t}\n\n\t\treturn false;\n\n\t},\n\n\tcancel: function() {\n\n\t\tif ( this.dragging ) {\n\n\t\t\tthis._mouseUp( new $.Event( \"mouseup\", { target: null } ) );\n\n\t\t\tif ( this.options.helper === \"original\" ) {\n\t\t\t\tthis.currentItem.css( this._storedCSS );\n\t\t\t\tthis._removeClass( this.currentItem, \"ui-sortable-helper\" );\n\t\t\t} else {\n\t\t\t\tthis.currentItem.show();\n\t\t\t}\n\n\t\t\t//Post deactivating events to containers\n\t\t\tfor ( var i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\t\tthis.containers[ i ]._trigger( \"deactivate\", null, this._uiHash( this ) );\n\t\t\t\tif ( this.containers[ i ].containerCache.over ) {\n\t\t\t\t\tthis.containers[ i ]._trigger( \"out\", null, this._uiHash( this ) );\n\t\t\t\t\tthis.containers[ i ].containerCache.over = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif ( this.placeholder ) {\n\n\t\t\t//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,\n\t\t\t// it unbinds ALL events from the original node!\n\t\t\tif ( this.placeholder[ 0 ].parentNode ) {\n\t\t\t\tthis.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );\n\t\t\t}\n\t\t\tif ( this.options.helper !== \"original\" && this.helper &&\n\t\t\t\t\tthis.helper[ 0 ].parentNode ) {\n\t\t\t\tthis.helper.remove();\n\t\t\t}\n\n\t\t\t$.extend( this, {\n\t\t\t\thelper: null,\n\t\t\t\tdragging: false,\n\t\t\t\treverting: false,\n\t\t\t\t_noFinalSort: null\n\t\t\t} );\n\n\t\t\tif ( this.domPosition.prev ) {\n\t\t\t\t$( this.domPosition.prev ).after( this.currentItem );\n\t\t\t} else {\n\t\t\t\t$( this.domPosition.parent ).prepend( this.currentItem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\n\t},\n\n\tserialize: function( o ) {\n\n\t\tvar items = this._getItemsAsjQuery( o && o.connected ),\n\t\t\tstr = [];\n\t\to = o || {};\n\n\t\t$( items ).each( function() {\n\t\t\tvar res = ( $( o.item || this ).attr( o.attribute || \"id\" ) || \"\" )\n\t\t\t\t.match( o.expression || ( /(.+)[\\-=_](.+)/ ) );\n\t\t\tif ( res ) {\n\t\t\t\tstr.push(\n\t\t\t\t\t( o.key || res[ 1 ] + \"[]\" ) +\n\t\t\t\t\t\"=\" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) );\n\t\t\t}\n\t\t} );\n\n\t\tif ( !str.length && o.key ) {\n\t\t\tstr.push( o.key + \"=\" );\n\t\t}\n\n\t\treturn str.join( \"&\" );\n\n\t},\n\n\ttoArray: function( o ) {\n\n\t\tvar items = this._getItemsAsjQuery( o && o.connected ),\n\t\t\tret = [];\n\n\t\to = o || {};\n\n\t\titems.each( function() {\n\t\t\tret.push( $( o.item || this ).attr( o.attribute || \"id\" ) || \"\" );\n\t\t} );\n\t\treturn ret;\n\n\t},\n\n\t/* Be careful with the following core functions */\n\t_intersectsWith: function( item ) {\n\n\t\tvar x1 = this.positionAbs.left,\n\t\t\tx2 = x1 + this.helperProportions.width,\n\t\t\ty1 = this.positionAbs.top,\n\t\t\ty2 = y1 + this.helperProportions.height,\n\t\t\tl = item.left,\n\t\t\tr = l + item.width,\n\t\t\tt = item.top,\n\t\t\tb = t + item.height,\n\t\t\tdyClick = this.offset.click.top,\n\t\t\tdxClick = this.offset.click.left,\n\t\t\tisOverElementHeight = ( this.options.axis === \"x\" ) || ( ( y1 + dyClick ) > t &&\n\t\t\t\t( y1 + dyClick ) < b ),\n\t\t\tisOverElementWidth = ( this.options.axis === \"y\" ) || ( ( x1 + dxClick ) > l &&\n\t\t\t\t( x1 + dxClick ) < r ),\n\t\t\tisOverElement = isOverElementHeight && isOverElementWidth;\n\n\t\tif ( this.options.tolerance === \"pointer\" ||\n\t\t\tthis.options.forcePointerForContainers ||\n\t\t\t( this.options.tolerance !== \"pointer\" &&\n\t\t\t\tthis.helperProportions[ this.floating ? \"width\" : \"height\" ] >\n\t\t\t\titem[ this.floating ? \"width\" : \"height\" ] )\n\t\t) {\n\t\t\treturn isOverElement;\n\t\t} else {\n\n\t\t\treturn ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half\n\t\t\t\tx2 - ( this.helperProportions.width / 2 ) < r && // Left Half\n\t\t\t\tt < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half\n\t\t\t\ty2 - ( this.helperProportions.height / 2 ) < b ); // Top Half\n\n\t\t}\n\t},\n\n\t_intersectsWithPointer: function( item ) {\n\t\tvar verticalDirection, horizontalDirection,\n\t\t\tisOverElementHeight = ( this.options.axis === \"x\" ) ||\n\t\t\t\tthis._isOverAxis(\n\t\t\t\t\tthis.positionAbs.top + this.offset.click.top, item.top, item.height ),\n\t\t\tisOverElementWidth = ( this.options.axis === \"y\" ) ||\n\t\t\t\tthis._isOverAxis(\n\t\t\t\t\tthis.positionAbs.left + this.offset.click.left, item.left, item.width ),\n\t\t\tisOverElement = isOverElementHeight && isOverElementWidth;\n\n\t\tif ( !isOverElement ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tverticalDirection = this._getDragVerticalDirection();\n\t\thorizontalDirection = this._getDragHorizontalDirection();\n\n\t\treturn this.floating ?\n\t\t\t( ( horizontalDirection === \"right\" || verticalDirection === \"down\" ) ? 2 : 1 )\n\t\t\t: ( verticalDirection && ( verticalDirection === \"down\" ? 2 : 1 ) );\n\n\t},\n\n\t_intersectsWithSides: function( item ) {\n\n\t\tvar isOverBottomHalf = this._isOverAxis( this.positionAbs.top +\n\t\t\t\tthis.offset.click.top, item.top + ( item.height / 2 ), item.height ),\n\t\t\tisOverRightHalf = this._isOverAxis( this.positionAbs.left +\n\t\t\t\tthis.offset.click.left, item.left + ( item.width / 2 ), item.width ),\n\t\t\tverticalDirection = this._getDragVerticalDirection(),\n\t\t\thorizontalDirection = this._getDragHorizontalDirection();\n\n\t\tif ( this.floating && horizontalDirection ) {\n\t\t\treturn ( ( horizontalDirection === \"right\" && isOverRightHalf ) ||\n\t\t\t\t( horizontalDirection === \"left\" && !isOverRightHalf ) );\n\t\t} else {\n\t\t\treturn verticalDirection && ( ( verticalDirection === \"down\" && isOverBottomHalf ) ||\n\t\t\t\t( verticalDirection === \"up\" && !isOverBottomHalf ) );\n\t\t}\n\n\t},\n\n\t_getDragVerticalDirection: function() {\n\t\tvar delta = this.positionAbs.top - this.lastPositionAbs.top;\n\t\treturn delta !== 0 && ( delta > 0 ? \"down\" : \"up\" );\n\t},\n\n\t_getDragHorizontalDirection: function() {\n\t\tvar delta = this.positionAbs.left - this.lastPositionAbs.left;\n\t\treturn delta !== 0 && ( delta > 0 ? \"right\" : \"left\" );\n\t},\n\n\trefresh: function( event ) {\n\t\tthis._refreshItems( event );\n\t\tthis._setHandleClassName();\n\t\tthis.refreshPositions();\n\t\treturn this;\n\t},\n\n\t_connectWith: function() {\n\t\tvar options = this.options;\n\t\treturn options.connectWith.constructor === String ?\n\t\t\t[ options.connectWith ] :\n\t\t\toptions.connectWith;\n\t},\n\n\t_getItemsAsjQuery: function( connected ) {\n\n\t\tvar i, j, cur, inst,\n\t\t\titems = [],\n\t\t\tqueries = [],\n\t\t\tconnectWith = this._connectWith();\n\n\t\tif ( connectWith && connected ) {\n\t\t\tfor ( i = connectWith.length - 1; i >= 0; i-- ) {\n\t\t\t\tcur = $( connectWith[ i ], this.document[ 0 ] );\n\t\t\t\tfor ( j = cur.length - 1; j >= 0; j-- ) {\n\t\t\t\t\tinst = $.data( cur[ j ], this.widgetFullName );\n\t\t\t\t\tif ( inst && inst !== this && !inst.options.disabled ) {\n\t\t\t\t\t\tqueries.push( [ $.isFunction( inst.options.items ) ?\n\t\t\t\t\t\t\tinst.options.items.call( inst.element ) :\n\t\t\t\t\t\t\t$( inst.options.items, inst.element )\n\t\t\t\t\t\t\t\t.not( \".ui-sortable-helper\" )\n\t\t\t\t\t\t\t\t.not( \".ui-sortable-placeholder\" ), inst ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tqueries.push( [ $.isFunction( this.options.items ) ?\n\t\t\tthis.options.items\n\t\t\t\t.call( this.element, null, { options: this.options, item: this.currentItem } ) :\n\t\t\t$( this.options.items, this.element )\n\t\t\t\t.not( \".ui-sortable-helper\" )\n\t\t\t\t.not( \".ui-sortable-placeholder\" ), this ] );\n\n\t\tfunction addItems() {\n\t\t\titems.push( this );\n\t\t}\n\t\tfor ( i = queries.length - 1; i >= 0; i-- ) {\n\t\t\tqueries[ i ][ 0 ].each( addItems );\n\t\t}\n\n\t\treturn $( items );\n\n\t},\n\n\t_removeCurrentsFromItems: function() {\n\n\t\tvar list = this.currentItem.find( \":data(\" + this.widgetName + \"-item)\" );\n\n\t\tthis.items = $.grep( this.items, function( item ) {\n\t\t\tfor ( var j = 0; j < list.length; j++ ) {\n\t\t\t\tif ( list[ j ] === item.item[ 0 ] ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} );\n\n\t},\n\n\t_refreshItems: function( event ) {\n\n\t\tthis.items = [];\n\t\tthis.containers = [ this ];\n\n\t\tvar i, j, cur, inst, targetData, _queries, item, queriesLength,\n\t\t\titems = this.items,\n\t\t\tqueries = [ [ $.isFunction( this.options.items ) ?\n\t\t\t\tthis.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) :\n\t\t\t\t$( this.options.items, this.element ), this ] ],\n\t\t\tconnectWith = this._connectWith();\n\n\t\t//Shouldn't be run the first time through due to massive slow-down\n\t\tif ( connectWith && this.ready ) {\n\t\t\tfor ( i = connectWith.length - 1; i >= 0; i-- ) {\n\t\t\t\tcur = $( connectWith[ i ], this.document[ 0 ] );\n\t\t\t\tfor ( j = cur.length - 1; j >= 0; j-- ) {\n\t\t\t\t\tinst = $.data( cur[ j ], this.widgetFullName );\n\t\t\t\t\tif ( inst && inst !== this && !inst.options.disabled ) {\n\t\t\t\t\t\tqueries.push( [ $.isFunction( inst.options.items ) ?\n\t\t\t\t\t\t\tinst.options.items\n\t\t\t\t\t\t\t\t.call( inst.element[ 0 ], event, { item: this.currentItem } ) :\n\t\t\t\t\t\t\t$( inst.options.items, inst.element ), inst ] );\n\t\t\t\t\t\tthis.containers.push( inst );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor ( i = queries.length - 1; i >= 0; i-- ) {\n\t\t\ttargetData = queries[ i ][ 1 ];\n\t\t\t_queries = queries[ i ][ 0 ];\n\n\t\t\tfor ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) {\n\t\t\t\titem = $( _queries[ j ] );\n\n\t\t\t\t// Data for target checking (mouse manager)\n\t\t\t\titem.data( this.widgetName + \"-item\", targetData );\n\n\t\t\t\titems.push( {\n\t\t\t\t\titem: item,\n\t\t\t\t\tinstance: targetData,\n\t\t\t\t\twidth: 0, height: 0,\n\t\t\t\t\tleft: 0, top: 0\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t},\n\n\trefreshPositions: function( fast ) {\n\n\t\t// Determine whether items are being displayed horizontally\n\t\tthis.floating = this.items.length ?\n\t\t\tthis.options.axis === \"x\" || this._isFloating( this.items[ 0 ].item ) :\n\t\t\tfalse;\n\n\t\t//This has to be redone because due to the item being moved out/into the offsetParent,\n\t\t// the offsetParent's position will change\n\t\tif ( this.offsetParent && this.helper ) {\n\t\t\tthis.offset.parent = this._getParentOffset();\n\t\t}\n\n\t\tvar i, item, t, p;\n\n\t\tfor ( i = this.items.length - 1; i >= 0; i-- ) {\n\t\t\titem = this.items[ i ];\n\n\t\t\t//We ignore calculating positions of all connected containers when we're not over them\n\t\t\tif ( item.instance !== this.currentContainer && this.currentContainer &&\n\t\t\t\t\titem.item[ 0 ] !== this.currentItem[ 0 ] ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tt = this.options.toleranceElement ?\n\t\t\t\t$( this.options.toleranceElement, item.item ) :\n\t\t\t\titem.item;\n\n\t\t\tif ( !fast ) {\n\t\t\t\titem.width = t.outerWidth();\n\t\t\t\titem.height = t.outerHeight();\n\t\t\t}\n\n\t\t\tp = t.offset();\n\t\t\titem.left = p.left;\n\t\t\titem.top = p.top;\n\t\t}\n\n\t\tif ( this.options.custom && this.options.custom.refreshContainers ) {\n\t\t\tthis.options.custom.refreshContainers.call( this );\n\t\t} else {\n\t\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\t\tp = this.containers[ i ].element.offset();\n\t\t\t\tthis.containers[ i ].containerCache.left = p.left;\n\t\t\t\tthis.containers[ i ].containerCache.top = p.top;\n\t\t\t\tthis.containers[ i ].containerCache.width =\n\t\t\t\t\tthis.containers[ i ].element.outerWidth();\n\t\t\t\tthis.containers[ i ].containerCache.height =\n\t\t\t\t\tthis.containers[ i ].element.outerHeight();\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_createPlaceholder: function( that ) {\n\t\tthat = that || this;\n\t\tvar className,\n\t\t\to = that.options;\n\n\t\tif ( !o.placeholder || o.placeholder.constructor === String ) {\n\t\t\tclassName = o.placeholder;\n\t\t\to.placeholder = {\n\t\t\t\telement: function() {\n\n\t\t\t\t\tvar nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(),\n\t\t\t\t\t\telement = $( \"<\" + nodeName + \">\", that.document[ 0 ] );\n\n\t\t\t\t\t\tthat._addClass( element, \"ui-sortable-placeholder\",\n\t\t\t\t\t\t\t\tclassName || that.currentItem[ 0 ].className )\n\t\t\t\t\t\t\t._removeClass( element, \"ui-sortable-helper\" );\n\n\t\t\t\t\tif ( nodeName === \"tbody\" ) {\n\t\t\t\t\t\tthat._createTrPlaceholder(\n\t\t\t\t\t\t\tthat.currentItem.find( \"tr\" ).eq( 0 ),\n\t\t\t\t\t\t\t$( \"<tr>\", that.document[ 0 ] ).appendTo( element )\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if ( nodeName === \"tr\" ) {\n\t\t\t\t\t\tthat._createTrPlaceholder( that.currentItem, element );\n\t\t\t\t\t} else if ( nodeName === \"img\" ) {\n\t\t\t\t\t\telement.attr( \"src\", that.currentItem.attr( \"src\" ) );\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !className ) {\n\t\t\t\t\t\telement.css( \"visibility\", \"hidden\" );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn element;\n\t\t\t\t},\n\t\t\t\tupdate: function( container, p ) {\n\n\t\t\t\t\t// 1. If a className is set as 'placeholder option, we don't force sizes -\n\t\t\t\t\t// the class is responsible for that\n\t\t\t\t\t// 2. The option 'forcePlaceholderSize can be enabled to force it even if a\n\t\t\t\t\t// class name is specified\n\t\t\t\t\tif ( className && !o.forcePlaceholderSize ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t//If the element doesn't have a actual height by itself (without styles coming\n\t\t\t\t\t// from a stylesheet), it receives the inline height from the dragged item\n\t\t\t\t\tif ( !p.height() ) {\n\t\t\t\t\t\tp.height(\n\t\t\t\t\t\t\tthat.currentItem.innerHeight() -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingTop\" ) || 0, 10 ) -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingBottom\" ) || 0, 10 ) );\n\t\t\t\t\t}\n\t\t\t\t\tif ( !p.width() ) {\n\t\t\t\t\t\tp.width(\n\t\t\t\t\t\t\tthat.currentItem.innerWidth() -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingLeft\" ) || 0, 10 ) -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingRight\" ) || 0, 10 ) );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t//Create the placeholder\n\t\tthat.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) );\n\n\t\t//Append it after the actual current item\n\t\tthat.currentItem.after( that.placeholder );\n\n\t\t//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)\n\t\to.placeholder.update( that, that.placeholder );\n\n\t},\n\n\t_createTrPlaceholder: function( sourceTr, targetTr ) {\n\t\tvar that = this;\n\n\t\tsourceTr.children().each( function() {\n\t\t\t$( \"<td>&#160;</td>\", that.document[ 0 ] )\n\t\t\t\t.attr( \"colspan\", $( this ).attr( \"colspan\" ) || 1 )\n\t\t\t\t.appendTo( targetTr );\n\t\t} );\n\t},\n\n\t_contactContainers: function( event ) {\n\t\tvar i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom,\n\t\t\tfloating, axis,\n\t\t\tinnermostContainer = null,\n\t\t\tinnermostIndex = null;\n\n\t\t// Get innermost container that intersects with item\n\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\n\t\t\t// Never consider a container that's located within the item itself\n\t\t\tif ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( this._intersectsWith( this.containers[ i ].containerCache ) ) {\n\n\t\t\t\t// If we've already found a container and it's more \"inner\" than this, then continue\n\t\t\t\tif ( innermostContainer &&\n\t\t\t\t\t\t$.contains(\n\t\t\t\t\t\t\tthis.containers[ i ].element[ 0 ],\n\t\t\t\t\t\t\tinnermostContainer.element[ 0 ] ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tinnermostContainer = this.containers[ i ];\n\t\t\t\tinnermostIndex = i;\n\n\t\t\t} else {\n\n\t\t\t\t// container doesn't intersect. trigger \"out\" event if necessary\n\t\t\t\tif ( this.containers[ i ].containerCache.over ) {\n\t\t\t\t\tthis.containers[ i ]._trigger( \"out\", event, this._uiHash( this ) );\n\t\t\t\t\tthis.containers[ i ].containerCache.over = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\t// If no intersecting containers found, return\n\t\tif ( !innermostContainer ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Move the item into the container if it's not there already\n\t\tif ( this.containers.length === 1 ) {\n\t\t\tif ( !this.containers[ innermostIndex ].containerCache.over ) {\n\t\t\t\tthis.containers[ innermostIndex ]._trigger( \"over\", event, this._uiHash( this ) );\n\t\t\t\tthis.containers[ innermostIndex ].containerCache.over = 1;\n\t\t\t}\n\t\t} else {\n\n\t\t\t// When entering a new container, we will find the item with the least distance and\n\t\t\t// append our item near it\n\t\t\tdist = 10000;\n\t\t\titemWithLeastDistance = null;\n\t\t\tfloating = innermostContainer.floating || this._isFloating( this.currentItem );\n\t\t\tposProperty = floating ? \"left\" : \"top\";\n\t\t\tsizeProperty = floating ? \"width\" : \"height\";\n\t\t\taxis = floating ? \"pageX\" : \"pageY\";\n\n\t\t\tfor ( j = this.items.length - 1; j >= 0; j-- ) {\n\t\t\t\tif ( !$.contains(\n\t\t\t\t\t\tthis.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] )\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tcur = this.items[ j ].item.offset()[ posProperty ];\n\t\t\t\tnearBottom = false;\n\t\t\t\tif ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {\n\t\t\t\t\tnearBottom = true;\n\t\t\t\t}\n\n\t\t\t\tif ( Math.abs( event[ axis ] - cur ) < dist ) {\n\t\t\t\t\tdist = Math.abs( event[ axis ] - cur );\n\t\t\t\t\titemWithLeastDistance = this.items[ j ];\n\t\t\t\t\tthis.direction = nearBottom ? \"up\" : \"down\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//Check if dropOnEmpty is enabled\n\t\t\tif ( !itemWithLeastDistance && !this.options.dropOnEmpty ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( this.currentContainer === this.containers[ innermostIndex ] ) {\n\t\t\t\tif ( !this.currentContainer.containerCache.over ) {\n\t\t\t\t\tthis.containers[ innermostIndex ]._trigger( \"over\", event, this._uiHash() );\n\t\t\t\t\tthis.currentContainer.containerCache.over = 1;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\titemWithLeastDistance ?\n\t\t\t\tthis._rearrange( event, itemWithLeastDistance, null, true ) :\n\t\t\t\tthis._rearrange( event, null, this.containers[ innermostIndex ].element, true );\n\t\t\tthis._trigger( \"change\", event, this._uiHash() );\n\t\t\tthis.containers[ innermostIndex ]._trigger( \"change\", event, this._uiHash( this ) );\n\t\t\tthis.currentContainer = this.containers[ innermostIndex ];\n\n\t\t\t//Update the placeholder\n\t\t\tthis.options.placeholder.update( this.currentContainer, this.placeholder );\n\n\t\t\tthis.containers[ innermostIndex ]._trigger( \"over\", event, this._uiHash( this ) );\n\t\t\tthis.containers[ innermostIndex ].containerCache.over = 1;\n\t\t}\n\n\t},\n\n\t_createHelper: function( event ) {\n\n\t\tvar o = this.options,\n\t\t\thelper = $.isFunction( o.helper ) ?\n\t\t\t\t$( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) :\n\t\t\t\t( o.helper === \"clone\" ? this.currentItem.clone() : this.currentItem );\n\n\t\t//Add the helper to the DOM if that didn't happen already\n\t\tif ( !helper.parents( \"body\" ).length ) {\n\t\t\t$( o.appendTo !== \"parent\" ?\n\t\t\t\to.appendTo :\n\t\t\t\tthis.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] );\n\t\t}\n\n\t\tif ( helper[ 0 ] === this.currentItem[ 0 ] ) {\n\t\t\tthis._storedCSS = {\n\t\t\t\twidth: this.currentItem[ 0 ].style.width,\n\t\t\t\theight: this.currentItem[ 0 ].style.height,\n\t\t\t\tposition: this.currentItem.css( \"position\" ),\n\t\t\t\ttop: this.currentItem.css( \"top\" ),\n\t\t\t\tleft: this.currentItem.css( \"left\" )\n\t\t\t};\n\t\t}\n\n\t\tif ( !helper[ 0 ].style.width || o.forceHelperSize ) {\n\t\t\thelper.width( this.currentItem.width() );\n\t\t}\n\t\tif ( !helper[ 0 ].style.height || o.forceHelperSize ) {\n\t\t\thelper.height( this.currentItem.height() );\n\t\t}\n\n\t\treturn helper;\n\n\t},\n\n\t_adjustOffsetFromHelper: function( obj ) {\n\t\tif ( typeof obj === \"string\" ) {\n\t\t\tobj = obj.split( \" \" );\n\t\t}\n\t\tif ( $.isArray( obj ) ) {\n\t\t\tobj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };\n\t\t}\n\t\tif ( \"left\" in obj ) {\n\t\t\tthis.offset.click.left = obj.left + this.margins.left;\n\t\t}\n\t\tif ( \"right\" in obj ) {\n\t\t\tthis.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;\n\t\t}\n\t\tif ( \"top\" in obj ) {\n\t\t\tthis.offset.click.top = obj.top + this.margins.top;\n\t\t}\n\t\tif ( \"bottom\" in obj ) {\n\t\t\tthis.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;\n\t\t}\n\t},\n\n\t_getParentOffset: function() {\n\n\t\t//Get the offsetParent and cache its position\n\t\tthis.offsetParent = this.helper.offsetParent();\n\t\tvar po = this.offsetParent.offset();\n\n\t\t// This is a special case where we need to modify a offset calculated on start, since the\n\t\t// following happened:\n\t\t// 1. The position of the helper is absolute, so it's position is calculated based on the\n\t\t// next positioned parent\n\t\t// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't\n\t\t// the document, which means that the scroll is included in the initial calculation of the\n\t\t// offset of the parent, and never recalculated upon drag\n\t\tif ( this.cssPosition === \"absolute\" && this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {\n\t\t\tpo.left += this.scrollParent.scrollLeft();\n\t\t\tpo.top += this.scrollParent.scrollTop();\n\t\t}\n\n\t\t// This needs to be actually done for all browsers, since pageX/pageY includes this\n\t\t// information with an ugly IE fix\n\t\tif ( this.offsetParent[ 0 ] === this.document[ 0 ].body ||\n\t\t\t\t( this.offsetParent[ 0 ].tagName &&\n\t\t\t\tthis.offsetParent[ 0 ].tagName.toLowerCase() === \"html\" && $.ui.ie ) ) {\n\t\t\tpo = { top: 0, left: 0 };\n\t\t}\n\n\t\treturn {\n\t\t\ttop: po.top + ( parseInt( this.offsetParent.css( \"borderTopWidth\" ), 10 ) || 0 ),\n\t\t\tleft: po.left + ( parseInt( this.offsetParent.css( \"borderLeftWidth\" ), 10 ) || 0 )\n\t\t};\n\n\t},\n\n\t_getRelativeOffset: function() {\n\n\t\tif ( this.cssPosition === \"relative\" ) {\n\t\t\tvar p = this.currentItem.position();\n\t\t\treturn {\n\t\t\t\ttop: p.top - ( parseInt( this.helper.css( \"top\" ), 10 ) || 0 ) +\n\t\t\t\t\tthis.scrollParent.scrollTop(),\n\t\t\t\tleft: p.left - ( parseInt( this.helper.css( \"left\" ), 10 ) || 0 ) +\n\t\t\t\t\tthis.scrollParent.scrollLeft()\n\t\t\t};\n\t\t} else {\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t},\n\n\t_cacheMargins: function() {\n\t\tthis.margins = {\n\t\t\tleft: ( parseInt( this.currentItem.css( \"marginLeft\" ), 10 ) || 0 ),\n\t\t\ttop: ( parseInt( this.currentItem.css( \"marginTop\" ), 10 ) || 0 )\n\t\t};\n\t},\n\n\t_cacheHelperProportions: function() {\n\t\tthis.helperProportions = {\n\t\t\twidth: this.helper.outerWidth(),\n\t\t\theight: this.helper.outerHeight()\n\t\t};\n\t},\n\n\t_setContainment: function() {\n\n\t\tvar ce, co, over,\n\t\t\to = this.options;\n\t\tif ( o.containment === \"parent\" ) {\n\t\t\to.containment = this.helper[ 0 ].parentNode;\n\t\t}\n\t\tif ( o.containment === \"document\" || o.containment === \"window\" ) {\n\t\t\tthis.containment = [\n\t\t\t\t0 - this.offset.relative.left - this.offset.parent.left,\n\t\t\t\t0 - this.offset.relative.top - this.offset.parent.top,\n\t\t\t\to.containment === \"document\" ?\n\t\t\t\t\tthis.document.width() :\n\t\t\t\t\tthis.window.width() - this.helperProportions.width - this.margins.left,\n\t\t\t\t( o.containment === \"document\" ?\n\t\t\t\t\t( this.document.height() || document.body.parentNode.scrollHeight ) :\n\t\t\t\t\tthis.window.height() || this.document[ 0 ].body.parentNode.scrollHeight\n\t\t\t\t) - this.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t}\n\n\t\tif ( !( /^(document|window|parent)$/ ).test( o.containment ) ) {\n\t\t\tce = $( o.containment )[ 0 ];\n\t\t\tco = $( o.containment ).offset();\n\t\t\tover = ( $( ce ).css( \"overflow\" ) !== \"hidden\" );\n\n\t\t\tthis.containment = [\n\t\t\t\tco.left + ( parseInt( $( ce ).css( \"borderLeftWidth\" ), 10 ) || 0 ) +\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingLeft\" ), 10 ) || 0 ) - this.margins.left,\n\t\t\t\tco.top + ( parseInt( $( ce ).css( \"borderTopWidth\" ), 10 ) || 0 ) +\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingTop\" ), 10 ) || 0 ) - this.margins.top,\n\t\t\t\tco.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"borderLeftWidth\" ), 10 ) || 0 ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingRight\" ), 10 ) || 0 ) -\n\t\t\t\t\tthis.helperProportions.width - this.margins.left,\n\t\t\t\tco.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"borderTopWidth\" ), 10 ) || 0 ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingBottom\" ), 10 ) || 0 ) -\n\t\t\t\t\tthis.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t}\n\n\t},\n\n\t_convertPositionTo: function( d, pos ) {\n\n\t\tif ( !pos ) {\n\t\t\tpos = this.position;\n\t\t}\n\t\tvar mod = d === \"absolute\" ? 1 : -1,\n\t\t\tscroll = this.cssPosition === \"absolute\" &&\n\t\t\t\t!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?\n\t\t\t\t\tthis.offsetParent :\n\t\t\t\t\tthis.scrollParent,\n\t\t\tscrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.top\t+\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top * mod -\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollTop() :\n\t\t\t\t\t( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.left +\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left * mod\t-\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 :\n\t\t\t\t\tscroll.scrollLeft() ) * mod )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_generatePosition: function( event ) {\n\n\t\tvar top, left,\n\t\t\to = this.options,\n\t\t\tpageX = event.pageX,\n\t\t\tpageY = event.pageY,\n\t\t\tscroll = this.cssPosition === \"absolute\" &&\n\t\t\t\t!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?\n\t\t\t\t\tthis.offsetParent :\n\t\t\t\t\tthis.scrollParent,\n\t\t\t\tscrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );\n\n\t\t// This is another very weird special case that only happens for relative elements:\n\t\t// 1. If the css position is relative\n\t\t// 2. and the scroll parent is the document or similar to the offset parent\n\t\t// we have to refresh the relative offset during the scroll so there are no jumps\n\t\tif ( this.cssPosition === \"relative\" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\tthis.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) {\n\t\t\tthis.offset.relative = this._getRelativeOffset();\n\t\t}\n\n\t\t/*\n\t\t * - Position constraining -\n\t\t * Constrain the position to a mix of grid, containment.\n\t\t */\n\n\t\tif ( this.originalPosition ) { //If we are not dragging yet, we won't check for options\n\n\t\t\tif ( this.containment ) {\n\t\t\t\tif ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) {\n\t\t\t\t\tpageX = this.containment[ 0 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) {\n\t\t\t\t\tpageY = this.containment[ 1 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t\tif ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) {\n\t\t\t\t\tpageX = this.containment[ 2 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) {\n\t\t\t\t\tpageY = this.containment[ 3 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( o.grid ) {\n\t\t\t\ttop = this.originalPageY + Math.round( ( pageY - this.originalPageY ) /\n\t\t\t\t\to.grid[ 1 ] ) * o.grid[ 1 ];\n\t\t\t\tpageY = this.containment ?\n\t\t\t\t\t( ( top - this.offset.click.top >= this.containment[ 1 ] &&\n\t\t\t\t\t\ttop - this.offset.click.top <= this.containment[ 3 ] ) ?\n\t\t\t\t\t\t\ttop :\n\t\t\t\t\t\t\t( ( top - this.offset.click.top >= this.containment[ 1 ] ) ?\n\t\t\t\t\t\t\t\ttop - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) :\n\t\t\t\t\t\t\t\ttop;\n\n\t\t\t\tleft = this.originalPageX + Math.round( ( pageX - this.originalPageX ) /\n\t\t\t\t\to.grid[ 0 ] ) * o.grid[ 0 ];\n\t\t\t\tpageX = this.containment ?\n\t\t\t\t\t( ( left - this.offset.click.left >= this.containment[ 0 ] &&\n\t\t\t\t\t\tleft - this.offset.click.left <= this.containment[ 2 ] ) ?\n\t\t\t\t\t\t\tleft :\n\t\t\t\t\t\t\t( ( left - this.offset.click.left >= this.containment[ 0 ] ) ?\n\t\t\t\t\t\t\t\tleft - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) :\n\t\t\t\t\t\t\t\tleft;\n\t\t\t}\n\n\t\t}\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageY -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.top -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top +\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollTop() :\n\t\t\t\t\t( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageX -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.left -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left +\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollLeft() :\n\t\t\t\t\tscrollIsRootNode ? 0 : scroll.scrollLeft() ) )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_rearrange: function( event, i, a, hardRefresh ) {\n\n\t\ta ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) :\n\t\t\ti.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ],\n\t\t\t\t( this.direction === \"down\" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) );\n\n\t\t//Various things done here to improve the performance:\n\t\t// 1. we create a setTimeout, that calls refreshPositions\n\t\t// 2. on the instance, we have a counter variable, that get's higher after every append\n\t\t// 3. on the local scope, we copy the counter variable, and check in the timeout,\n\t\t// if it's still the same\n\t\t// 4. this lets only the last addition to the timeout stack through\n\t\tthis.counter = this.counter ? ++this.counter : 1;\n\t\tvar counter = this.counter;\n\n\t\tthis._delay( function() {\n\t\t\tif ( counter === this.counter ) {\n\n\t\t\t\t//Precompute after each DOM insertion, NOT on mousemove\n\t\t\t\tthis.refreshPositions( !hardRefresh );\n\t\t\t}\n\t\t} );\n\n\t},\n\n\t_clear: function( event, noPropagation ) {\n\n\t\tthis.reverting = false;\n\n\t\t// We delay all events that have to be triggered to after the point where the placeholder\n\t\t// has been removed and everything else normalized again\n\t\tvar i,\n\t\t\tdelayedTriggers = [];\n\n\t\t// We first have to update the dom position of the actual currentItem\n\t\t// Note: don't do it if the current item is already removed (by a user), or it gets\n\t\t// reappended (see #4088)\n\t\tif ( !this._noFinalSort && this.currentItem.parent().length ) {\n\t\t\tthis.placeholder.before( this.currentItem );\n\t\t}\n\t\tthis._noFinalSort = null;\n\n\t\tif ( this.helper[ 0 ] === this.currentItem[ 0 ] ) {\n\t\t\tfor ( i in this._storedCSS ) {\n\t\t\t\tif ( this._storedCSS[ i ] === \"auto\" || this._storedCSS[ i ] === \"static\" ) {\n\t\t\t\t\tthis._storedCSS[ i ] = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.currentItem.css( this._storedCSS );\n\t\t\tthis._removeClass( this.currentItem, \"ui-sortable-helper\" );\n\t\t} else {\n\t\t\tthis.currentItem.show();\n\t\t}\n\n\t\tif ( this.fromOutside && !noPropagation ) {\n\t\t\tdelayedTriggers.push( function( event ) {\n\t\t\t\tthis._trigger( \"receive\", event, this._uiHash( this.fromOutside ) );\n\t\t\t} );\n\t\t}\n\t\tif ( ( this.fromOutside ||\n\t\t\t\tthis.domPosition.prev !==\n\t\t\t\tthis.currentItem.prev().not( \".ui-sortable-helper\" )[ 0 ] ||\n\t\t\t\tthis.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) {\n\n\t\t\t// Trigger update callback if the DOM position has changed\n\t\t\tdelayedTriggers.push( function( event ) {\n\t\t\t\tthis._trigger( \"update\", event, this._uiHash() );\n\t\t\t} );\n\t\t}\n\n\t\t// Check if the items Container has Changed and trigger appropriate\n\t\t// events.\n\t\tif ( this !== this.currentContainer ) {\n\t\t\tif ( !noPropagation ) {\n\t\t\t\tdelayedTriggers.push( function( event ) {\n\t\t\t\t\tthis._trigger( \"remove\", event, this._uiHash() );\n\t\t\t\t} );\n\t\t\t\tdelayedTriggers.push( ( function( c ) {\n\t\t\t\t\treturn function( event ) {\n\t\t\t\t\t\tc._trigger( \"receive\", event, this._uiHash( this ) );\n\t\t\t\t\t};\n\t\t\t\t} ).call( this, this.currentContainer ) );\n\t\t\t\tdelayedTriggers.push( ( function( c ) {\n\t\t\t\t\treturn function( event ) {\n\t\t\t\t\t\tc._trigger( \"update\", event, this._uiHash( this ) );\n\t\t\t\t\t};\n\t\t\t\t} ).call( this, this.currentContainer ) );\n\t\t\t}\n\t\t}\n\n\t\t//Post events to containers\n\t\tfunction delayEvent( type, instance, container ) {\n\t\t\treturn function( event ) {\n\t\t\t\tcontainer._trigger( type, event, instance._uiHash( instance ) );\n\t\t\t};\n\t\t}\n\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\tif ( !noPropagation ) {\n\t\t\t\tdelayedTriggers.push( delayEvent( \"deactivate\", this, this.containers[ i ] ) );\n\t\t\t}\n\t\t\tif ( this.containers[ i ].containerCache.over ) {\n\t\t\t\tdelayedTriggers.push( delayEvent( \"out\", this, this.containers[ i ] ) );\n\t\t\t\tthis.containers[ i ].containerCache.over = 0;\n\t\t\t}\n\t\t}\n\n\t\t//Do what was originally in plugins\n\t\tif ( this.storedCursor ) {\n\t\t\tthis.document.find( \"body\" ).css( \"cursor\", this.storedCursor );\n\t\t\tthis.storedStylesheet.remove();\n\t\t}\n\t\tif ( this._storedOpacity ) {\n\t\t\tthis.helper.css( \"opacity\", this._storedOpacity );\n\t\t}\n\t\tif ( this._storedZIndex ) {\n\t\t\tthis.helper.css( \"zIndex\", this._storedZIndex === \"auto\" ? \"\" : this._storedZIndex );\n\t\t}\n\n\t\tthis.dragging = false;\n\n\t\tif ( !noPropagation ) {\n\t\t\tthis._trigger( \"beforeStop\", event, this._uiHash() );\n\t\t}\n\n\t\t//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,\n\t\t// it unbinds ALL events from the original node!\n\t\tthis.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );\n\n\t\tif ( !this.cancelHelperRemoval ) {\n\t\t\tif ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {\n\t\t\t\tthis.helper.remove();\n\t\t\t}\n\t\t\tthis.helper = null;\n\t\t}\n\n\t\tif ( !noPropagation ) {\n\t\t\tfor ( i = 0; i < delayedTriggers.length; i++ ) {\n\n\t\t\t\t// Trigger all delayed events\n\t\t\t\tdelayedTriggers[ i ].call( this, event );\n\t\t\t}\n\t\t\tthis._trigger( \"stop\", event, this._uiHash() );\n\t\t}\n\n\t\tthis.fromOutside = false;\n\t\treturn !this.cancelHelperRemoval;\n\n\t},\n\n\t_trigger: function() {\n\t\tif ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) {\n\t\t\tthis.cancel();\n\t\t}\n\t},\n\n\t_uiHash: function( _inst ) {\n\t\tvar inst = _inst || this;\n\t\treturn {\n\t\t\thelper: inst.helper,\n\t\t\tplaceholder: inst.placeholder || $( [] ),\n\t\t\tposition: inst.position,\n\t\t\toriginalPosition: inst.originalPosition,\n\t\t\toffset: inst.positionAbs,\n\t\t\titem: inst.currentItem,\n\t\t\tsender: _inst ? _inst.element : null\n\t\t};\n\t}\n\n} );\n\n\n/*!\n * jQuery UI Spinner 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Spinner\n//>>group: Widgets\n//>>description: Displays buttons to easily input numbers via the keyboard or mouse.\n//>>docs: http://api.jqueryui.com/spinner/\n//>>demos: http://jqueryui.com/spinner/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/spinner.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nfunction spinnerModifer( fn ) {\n\treturn function() {\n\t\tvar previous = this.element.val();\n\t\tfn.apply( this, arguments );\n\t\tthis._refresh();\n\t\tif ( previous !== this.element.val() ) {\n\t\t\tthis._trigger( \"change\" );\n\t\t}\n\t};\n}\n\n$.widget( \"ui.spinner\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<input>\",\n\twidgetEventPrefix: \"spin\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-spinner\": \"ui-corner-all\",\n\t\t\t\"ui-spinner-down\": \"ui-corner-br\",\n\t\t\t\"ui-spinner-up\": \"ui-corner-tr\"\n\t\t},\n\t\tculture: null,\n\t\ticons: {\n\t\t\tdown: \"ui-icon-triangle-1-s\",\n\t\t\tup: \"ui-icon-triangle-1-n\"\n\t\t},\n\t\tincremental: true,\n\t\tmax: null,\n\t\tmin: null,\n\t\tnumberFormat: null,\n\t\tpage: 10,\n\t\tstep: 1,\n\n\t\tchange: null,\n\t\tspin: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\n\t_create: function() {\n\n\t\t// handle string values that need to be parsed\n\t\tthis._setOption( \"max\", this.options.max );\n\t\tthis._setOption( \"min\", this.options.min );\n\t\tthis._setOption( \"step\", this.options.step );\n\n\t\t// Only format if there is a value, prevents the field from being marked\n\t\t// as invalid in Firefox, see #9573.\n\t\tif ( this.value() !== \"\" ) {\n\n\t\t\t// Format the value, but don't constrain.\n\t\t\tthis._value( this.element.val(), true );\n\t\t}\n\n\t\tthis._draw();\n\t\tthis._on( this._events );\n\t\tthis._refresh();\n\n\t\t// Turning off autocomplete prevents the browser from remembering the\n\t\t// value when navigating through history, so we re-enable autocomplete\n\t\t// if the page is unloaded before the widget is destroyed. #7790\n\t\tthis._on( this.window, {\n\t\t\tbeforeunload: function() {\n\t\t\t\tthis.element.removeAttr( \"autocomplete\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar options = this._super();\n\t\tvar element = this.element;\n\n\t\t$.each( [ \"min\", \"max\", \"step\" ], function( i, option ) {\n\t\t\tvar value = element.attr( option );\n\t\t\tif ( value != null && value.length ) {\n\t\t\t\toptions[ option ] = value;\n\t\t\t}\n\t\t} );\n\n\t\treturn options;\n\t},\n\n\t_events: {\n\t\tkeydown: function( event ) {\n\t\t\tif ( this._start( event ) && this._keydown( event ) ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t},\n\t\tkeyup: \"_stop\",\n\t\tfocus: function() {\n\t\t\tthis.previous = this.element.val();\n\t\t},\n\t\tblur: function( event ) {\n\t\t\tif ( this.cancelBlur ) {\n\t\t\t\tdelete this.cancelBlur;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis._stop();\n\t\t\tthis._refresh();\n\t\t\tif ( this.previous !== this.element.val() ) {\n\t\t\t\tthis._trigger( \"change\", event );\n\t\t\t}\n\t\t},\n\t\tmousewheel: function( event, delta ) {\n\t\t\tif ( !delta ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( !this.spinning && !this._start( event ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event );\n\t\t\tclearTimeout( this.mousewheelTimer );\n\t\t\tthis.mousewheelTimer = this._delay( function() {\n\t\t\t\tif ( this.spinning ) {\n\t\t\t\t\tthis._stop( event );\n\t\t\t\t}\n\t\t\t}, 100 );\n\t\t\tevent.preventDefault();\n\t\t},\n\t\t\"mousedown .ui-spinner-button\": function( event ) {\n\t\t\tvar previous;\n\n\t\t\t// We never want the buttons to have focus; whenever the user is\n\t\t\t// interacting with the spinner, the focus should be on the input.\n\t\t\t// If the input is focused then this.previous is properly set from\n\t\t\t// when the input first received focus. If the input is not focused\n\t\t\t// then we need to set this.previous based on the value before spinning.\n\t\t\tprevious = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ?\n\t\t\t\tthis.previous : this.element.val();\n\t\t\tfunction checkFocus() {\n\t\t\t\tvar isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] );\n\t\t\t\tif ( !isActive ) {\n\t\t\t\t\tthis.element.trigger( \"focus\" );\n\t\t\t\t\tthis.previous = previous;\n\n\t\t\t\t\t// support: IE\n\t\t\t\t\t// IE sets focus asynchronously, so we need to check if focus\n\t\t\t\t\t// moved off of the input because the user clicked on the button.\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tthis.previous = previous;\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ensure focus is on (or stays on) the text field\n\t\t\tevent.preventDefault();\n\t\t\tcheckFocus.call( this );\n\n\t\t\t// Support: IE\n\t\t\t// IE doesn't prevent moving focus even with event.preventDefault()\n\t\t\t// so we set a flag to know when we should ignore the blur event\n\t\t\t// and check (again) if focus moved off of the input.\n\t\t\tthis.cancelBlur = true;\n\t\t\tthis._delay( function() {\n\t\t\t\tdelete this.cancelBlur;\n\t\t\t\tcheckFocus.call( this );\n\t\t\t} );\n\n\t\t\tif ( this._start( event ) === false ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis._repeat( null, $( event.currentTarget )\n\t\t\t\t.hasClass( \"ui-spinner-up\" ) ? 1 : -1, event );\n\t\t},\n\t\t\"mouseup .ui-spinner-button\": \"_stop\",\n\t\t\"mouseenter .ui-spinner-button\": function( event ) {\n\n\t\t\t// button will add ui-state-active if mouse was down while mouseleave and kept down\n\t\t\tif ( !$( event.currentTarget ).hasClass( \"ui-state-active\" ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( this._start( event ) === false ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis._repeat( null, $( event.currentTarget )\n\t\t\t\t.hasClass( \"ui-spinner-up\" ) ? 1 : -1, event );\n\t\t},\n\n\t\t// TODO: do we really want to consider this a stop?\n\t\t// shouldn't we just stop the repeater and wait until mouseup before\n\t\t// we trigger the stop event?\n\t\t\"mouseleave .ui-spinner-button\": \"_stop\"\n\t},\n\n\t// Support mobile enhanced option and make backcompat more sane\n\t_enhance: function() {\n\t\tthis.uiSpinner = this.element\n\t\t\t.attr( \"autocomplete\", \"off\" )\n\t\t\t.wrap( \"<span>\" )\n\t\t\t.parent()\n\n\t\t\t\t// Add buttons\n\t\t\t\t.append(\n\t\t\t\t\t\"<a></a><a></a>\"\n\t\t\t\t);\n\t},\n\n\t_draw: function() {\n\t\tthis._enhance();\n\n\t\tthis._addClass( this.uiSpinner, \"ui-spinner\", \"ui-widget ui-widget-content\" );\n\t\tthis._addClass( \"ui-spinner-input\" );\n\n\t\tthis.element.attr( \"role\", \"spinbutton\" );\n\n\t\t// Button bindings\n\t\tthis.buttons = this.uiSpinner.children( \"a\" )\n\t\t\t.attr( \"tabIndex\", -1 )\n\t\t\t.attr( \"aria-hidden\", true )\n\t\t\t.button( {\n\t\t\t\tclasses: {\n\t\t\t\t\t\"ui-button\": \"\"\n\t\t\t\t}\n\t\t\t} );\n\n\t\t// TODO: Right now button does not support classes this is already updated in button PR\n\t\tthis._removeClass( this.buttons, \"ui-corner-all\" );\n\n\t\tthis._addClass( this.buttons.first(), \"ui-spinner-button ui-spinner-up\" );\n\t\tthis._addClass( this.buttons.last(), \"ui-spinner-button ui-spinner-down\" );\n\t\tthis.buttons.first().button( {\n\t\t\t\"icon\": this.options.icons.up,\n\t\t\t\"showLabel\": false\n\t\t} );\n\t\tthis.buttons.last().button( {\n\t\t\t\"icon\": this.options.icons.down,\n\t\t\t\"showLabel\": false\n\t\t} );\n\n\t\t// IE 6 doesn't understand height: 50% for the buttons\n\t\t// unless the wrapper has an explicit height\n\t\tif ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) &&\n\t\t\t\tthis.uiSpinner.height() > 0 ) {\n\t\t\tthis.uiSpinner.height( this.uiSpinner.height() );\n\t\t}\n\t},\n\n\t_keydown: function( event ) {\n\t\tvar options = this.options,\n\t\t\tkeyCode = $.ui.keyCode;\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase keyCode.UP:\n\t\t\tthis._repeat( null, 1, event );\n\t\t\treturn true;\n\t\tcase keyCode.DOWN:\n\t\t\tthis._repeat( null, -1, event );\n\t\t\treturn true;\n\t\tcase keyCode.PAGE_UP:\n\t\t\tthis._repeat( null, options.page, event );\n\t\t\treturn true;\n\t\tcase keyCode.PAGE_DOWN:\n\t\t\tthis._repeat( null, -options.page, event );\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_start: function( event ) {\n\t\tif ( !this.spinning && this._trigger( \"start\", event ) === false ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( !this.counter ) {\n\t\t\tthis.counter = 1;\n\t\t}\n\t\tthis.spinning = true;\n\t\treturn true;\n\t},\n\n\t_repeat: function( i, steps, event ) {\n\t\ti = i || 500;\n\n\t\tclearTimeout( this.timer );\n\t\tthis.timer = this._delay( function() {\n\t\t\tthis._repeat( 40, steps, event );\n\t\t}, i );\n\n\t\tthis._spin( steps * this.options.step, event );\n\t},\n\n\t_spin: function( step, event ) {\n\t\tvar value = this.value() || 0;\n\n\t\tif ( !this.counter ) {\n\t\t\tthis.counter = 1;\n\t\t}\n\n\t\tvalue = this._adjustValue( value + step * this._increment( this.counter ) );\n\n\t\tif ( !this.spinning || this._trigger( \"spin\", event, { value: value } ) !== false ) {\n\t\t\tthis._value( value );\n\t\t\tthis.counter++;\n\t\t}\n\t},\n\n\t_increment: function( i ) {\n\t\tvar incremental = this.options.incremental;\n\n\t\tif ( incremental ) {\n\t\t\treturn $.isFunction( incremental ) ?\n\t\t\t\tincremental( i ) :\n\t\t\t\tMath.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );\n\t\t}\n\n\t\treturn 1;\n\t},\n\n\t_precision: function() {\n\t\tvar precision = this._precisionOf( this.options.step );\n\t\tif ( this.options.min !== null ) {\n\t\t\tprecision = Math.max( precision, this._precisionOf( this.options.min ) );\n\t\t}\n\t\treturn precision;\n\t},\n\n\t_precisionOf: function( num ) {\n\t\tvar str = num.toString(),\n\t\t\tdecimal = str.indexOf( \".\" );\n\t\treturn decimal === -1 ? 0 : str.length - decimal - 1;\n\t},\n\n\t_adjustValue: function( value ) {\n\t\tvar base, aboveMin,\n\t\t\toptions = this.options;\n\n\t\t// Make sure we're at a valid step\n\t\t// - find out where we are relative to the base (min or 0)\n\t\tbase = options.min !== null ? options.min : 0;\n\t\taboveMin = value - base;\n\n\t\t// - round to the nearest step\n\t\taboveMin = Math.round( aboveMin / options.step ) * options.step;\n\n\t\t// - rounding is based on 0, so adjust back to our base\n\t\tvalue = base + aboveMin;\n\n\t\t// Fix precision from bad JS floating point math\n\t\tvalue = parseFloat( value.toFixed( this._precision() ) );\n\n\t\t// Clamp the value\n\t\tif ( options.max !== null && value > options.max ) {\n\t\t\treturn options.max;\n\t\t}\n\t\tif ( options.min !== null && value < options.min ) {\n\t\t\treturn options.min;\n\t\t}\n\n\t\treturn value;\n\t},\n\n\t_stop: function( event ) {\n\t\tif ( !this.spinning ) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout( this.timer );\n\t\tclearTimeout( this.mousewheelTimer );\n\t\tthis.counter = 0;\n\t\tthis.spinning = false;\n\t\tthis._trigger( \"stop\", event );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar prevValue, first, last;\n\n\t\tif ( key === \"culture\" || key === \"numberFormat\" ) {\n\t\t\tprevValue = this._parse( this.element.val() );\n\t\t\tthis.options[ key ] = value;\n\t\t\tthis.element.val( this._format( prevValue ) );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( key === \"max\" || key === \"min\" || key === \"step\" ) {\n\t\t\tif ( typeof value === \"string\" ) {\n\t\t\t\tvalue = this._parse( value );\n\t\t\t}\n\t\t}\n\t\tif ( key === \"icons\" ) {\n\t\t\tfirst = this.buttons.first().find( \".ui-icon\" );\n\t\t\tthis._removeClass( first, null, this.options.icons.up );\n\t\t\tthis._addClass( first, null, value.up );\n\t\t\tlast = this.buttons.last().find( \".ui-icon\" );\n\t\t\tthis._removeClass( last, null, this.options.icons.down );\n\t\t\tthis._addClass( last, null, value.down );\n\t\t}\n\n\t\tthis._super( key, value );\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis._toggleClass( this.uiSpinner, null, \"ui-state-disabled\", !!value );\n\t\tthis.element.prop( \"disabled\", !!value );\n\t\tthis.buttons.button( value ? \"disable\" : \"enable\" );\n\t},\n\n\t_setOptions: spinnerModifer( function( options ) {\n\t\tthis._super( options );\n\t} ),\n\n\t_parse: function( val ) {\n\t\tif ( typeof val === \"string\" && val !== \"\" ) {\n\t\t\tval = window.Globalize && this.options.numberFormat ?\n\t\t\t\tGlobalize.parseFloat( val, 10, this.options.culture ) : +val;\n\t\t}\n\t\treturn val === \"\" || isNaN( val ) ? null : val;\n\t},\n\n\t_format: function( value ) {\n\t\tif ( value === \"\" ) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn window.Globalize && this.options.numberFormat ?\n\t\t\tGlobalize.format( value, this.options.numberFormat, this.options.culture ) :\n\t\t\tvalue;\n\t},\n\n\t_refresh: function() {\n\t\tthis.element.attr( {\n\t\t\t\"aria-valuemin\": this.options.min,\n\t\t\t\"aria-valuemax\": this.options.max,\n\n\t\t\t// TODO: what should we do with values that can't be parsed?\n\t\t\t\"aria-valuenow\": this._parse( this.element.val() )\n\t\t} );\n\t},\n\n\tisValid: function() {\n\t\tvar value = this.value();\n\n\t\t// Null is invalid\n\t\tif ( value === null ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// If value gets adjusted, it's invalid\n\t\treturn value === this._adjustValue( value );\n\t},\n\n\t// Update the value without triggering change\n\t_value: function( value, allowAny ) {\n\t\tvar parsed;\n\t\tif ( value !== \"\" ) {\n\t\t\tparsed = this._parse( value );\n\t\t\tif ( parsed !== null ) {\n\t\t\t\tif ( !allowAny ) {\n\t\t\t\t\tparsed = this._adjustValue( parsed );\n\t\t\t\t}\n\t\t\t\tvalue = this._format( parsed );\n\t\t\t}\n\t\t}\n\t\tthis.element.val( value );\n\t\tthis._refresh();\n\t},\n\n\t_destroy: function() {\n\t\tthis.element\n\t\t\t.prop( \"disabled\", false )\n\t\t\t.removeAttr( \"autocomplete role aria-valuemin aria-valuemax aria-valuenow\" );\n\n\t\tthis.uiSpinner.replaceWith( this.element );\n\t},\n\n\tstepUp: spinnerModifer( function( steps ) {\n\t\tthis._stepUp( steps );\n\t} ),\n\t_stepUp: function( steps ) {\n\t\tif ( this._start() ) {\n\t\t\tthis._spin( ( steps || 1 ) * this.options.step );\n\t\t\tthis._stop();\n\t\t}\n\t},\n\n\tstepDown: spinnerModifer( function( steps ) {\n\t\tthis._stepDown( steps );\n\t} ),\n\t_stepDown: function( steps ) {\n\t\tif ( this._start() ) {\n\t\t\tthis._spin( ( steps || 1 ) * -this.options.step );\n\t\t\tthis._stop();\n\t\t}\n\t},\n\n\tpageUp: spinnerModifer( function( pages ) {\n\t\tthis._stepUp( ( pages || 1 ) * this.options.page );\n\t} ),\n\n\tpageDown: spinnerModifer( function( pages ) {\n\t\tthis._stepDown( ( pages || 1 ) * this.options.page );\n\t} ),\n\n\tvalue: function( newVal ) {\n\t\tif ( !arguments.length ) {\n\t\t\treturn this._parse( this.element.val() );\n\t\t}\n\t\tspinnerModifer( this._value ).call( this, newVal );\n\t},\n\n\twidget: function() {\n\t\treturn this.uiSpinner;\n\t}\n} );\n\n// DEPRECATED\n// TODO: switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for spinner html extension points\n\t$.widget( \"ui.spinner\", $.ui.spinner, {\n\t\t_enhance: function() {\n\t\t\tthis.uiSpinner = this.element\n\t\t\t\t.attr( \"autocomplete\", \"off\" )\n\t\t\t\t.wrap( this._uiSpinnerHtml() )\n\t\t\t\t.parent()\n\n\t\t\t\t\t// Add buttons\n\t\t\t\t\t.append( this._buttonHtml() );\n\t\t},\n\t\t_uiSpinnerHtml: function() {\n\t\t\treturn \"<span>\";\n\t\t},\n\n\t\t_buttonHtml: function() {\n\t\t\treturn \"<a></a><a></a>\";\n\t\t}\n\t} );\n}\n\nvar widgetsSpinner = $.ui.spinner;\n\n\n/*!\n * jQuery UI Tabs 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Tabs\n//>>group: Widgets\n//>>description: Transforms a set of container elements into a tab structure.\n//>>docs: http://api.jqueryui.com/tabs/\n//>>demos: http://jqueryui.com/tabs/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/tabs.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.tabs\", {\n\tversion: \"1.12.1\",\n\tdelay: 300,\n\toptions: {\n\t\tactive: null,\n\t\tclasses: {\n\t\t\t\"ui-tabs\": \"ui-corner-all\",\n\t\t\t\"ui-tabs-nav\": \"ui-corner-all\",\n\t\t\t\"ui-tabs-panel\": \"ui-corner-bottom\",\n\t\t\t\"ui-tabs-tab\": \"ui-corner-top\"\n\t\t},\n\t\tcollapsible: false,\n\t\tevent: \"click\",\n\t\theightStyle: \"content\",\n\t\thide: null,\n\t\tshow: null,\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tbeforeActivate: null,\n\t\tbeforeLoad: null,\n\t\tload: null\n\t},\n\n\t_isLocal: ( function() {\n\t\tvar rhash = /#.*$/;\n\n\t\treturn function( anchor ) {\n\t\t\tvar anchorUrl, locationUrl;\n\n\t\t\tanchorUrl = anchor.href.replace( rhash, \"\" );\n\t\t\tlocationUrl = location.href.replace( rhash, \"\" );\n\n\t\t\t// Decoding may throw an error if the URL isn't UTF-8 (#9518)\n\t\t\ttry {\n\t\t\t\tanchorUrl = decodeURIComponent( anchorUrl );\n\t\t\t} catch ( error ) {}\n\t\t\ttry {\n\t\t\t\tlocationUrl = decodeURIComponent( locationUrl );\n\t\t\t} catch ( error ) {}\n\n\t\t\treturn anchor.hash.length > 1 && anchorUrl === locationUrl;\n\t\t};\n\t} )(),\n\n\t_create: function() {\n\t\tvar that = this,\n\t\t\toptions = this.options;\n\n\t\tthis.running = false;\n\n\t\tthis._addClass( \"ui-tabs\", \"ui-widget ui-widget-content\" );\n\t\tthis._toggleClass( \"ui-tabs-collapsible\", null, options.collapsible );\n\n\t\tthis._processTabs();\n\t\toptions.active = this._initialActive();\n\n\t\t// Take disabling tabs via class attribute from HTML\n\t\t// into account and update option properly.\n\t\tif ( $.isArray( options.disabled ) ) {\n\t\t\toptions.disabled = $.unique( options.disabled.concat(\n\t\t\t\t$.map( this.tabs.filter( \".ui-state-disabled\" ), function( li ) {\n\t\t\t\t\treturn that.tabs.index( li );\n\t\t\t\t} )\n\t\t\t) ).sort();\n\t\t}\n\n\t\t// Check for length avoids error when initializing empty list\n\t\tif ( this.options.active !== false && this.anchors.length ) {\n\t\t\tthis.active = this._findActive( options.active );\n\t\t} else {\n\t\t\tthis.active = $();\n\t\t}\n\n\t\tthis._refresh();\n\n\t\tif ( this.active.length ) {\n\t\t\tthis.load( options.active );\n\t\t}\n\t},\n\n\t_initialActive: function() {\n\t\tvar active = this.options.active,\n\t\t\tcollapsible = this.options.collapsible,\n\t\t\tlocationHash = location.hash.substring( 1 );\n\n\t\tif ( active === null ) {\n\n\t\t\t// check the fragment identifier in the URL\n\t\t\tif ( locationHash ) {\n\t\t\t\tthis.tabs.each( function( i, tab ) {\n\t\t\t\t\tif ( $( tab ).attr( \"aria-controls\" ) === locationHash ) {\n\t\t\t\t\t\tactive = i;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t// Check for a tab marked active via a class\n\t\t\tif ( active === null ) {\n\t\t\t\tactive = this.tabs.index( this.tabs.filter( \".ui-tabs-active\" ) );\n\t\t\t}\n\n\t\t\t// No active tab, set to false\n\t\t\tif ( active === null || active === -1 ) {\n\t\t\t\tactive = this.tabs.length ? 0 : false;\n\t\t\t}\n\t\t}\n\n\t\t// Handle numbers: negative, out of range\n\t\tif ( active !== false ) {\n\t\t\tactive = this.tabs.index( this.tabs.eq( active ) );\n\t\t\tif ( active === -1 ) {\n\t\t\t\tactive = collapsible ? false : 0;\n\t\t\t}\n\t\t}\n\n\t\t// Don't allow collapsible: false and active: false\n\t\tif ( !collapsible && active === false && this.anchors.length ) {\n\t\t\tactive = 0;\n\t\t}\n\n\t\treturn active;\n\t},\n\n\t_getCreateEventData: function() {\n\t\treturn {\n\t\t\ttab: this.active,\n\t\t\tpanel: !this.active.length ? $() : this._getPanelForTab( this.active )\n\t\t};\n\t},\n\n\t_tabKeydown: function( event ) {\n\t\tvar focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( \"li\" ),\n\t\t\tselectedIndex = this.tabs.index( focusedTab ),\n\t\t\tgoingForward = true;\n\n\t\tif ( this._handlePageNav( event ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase $.ui.keyCode.RIGHT:\n\t\tcase $.ui.keyCode.DOWN:\n\t\t\tselectedIndex++;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.UP:\n\t\tcase $.ui.keyCode.LEFT:\n\t\t\tgoingForward = false;\n\t\t\tselectedIndex--;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.END:\n\t\t\tselectedIndex = this.anchors.length - 1;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.HOME:\n\t\t\tselectedIndex = 0;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.SPACE:\n\n\t\t\t// Activate only, no collapsing\n\t\t\tevent.preventDefault();\n\t\t\tclearTimeout( this.activating );\n\t\t\tthis._activate( selectedIndex );\n\t\t\treturn;\n\t\tcase $.ui.keyCode.ENTER:\n\n\t\t\t// Toggle (cancel delayed activation, allow collapsing)\n\t\t\tevent.preventDefault();\n\t\t\tclearTimeout( this.activating );\n\n\t\t\t// Determine if we should collapse or activate\n\t\t\tthis._activate( selectedIndex === this.options.active ? false : selectedIndex );\n\t\t\treturn;\n\t\tdefault:\n\t\t\treturn;\n\t\t}\n\n\t\t// Focus the appropriate tab, based on which key was pressed\n\t\tevent.preventDefault();\n\t\tclearTimeout( this.activating );\n\t\tselectedIndex = this._focusNextTab( selectedIndex, goingForward );\n\n\t\t// Navigating with control/command key will prevent automatic activation\n\t\tif ( !event.ctrlKey && !event.metaKey ) {\n\n\t\t\t// Update aria-selected immediately so that AT think the tab is already selected.\n\t\t\t// Otherwise AT may confuse the user by stating that they need to activate the tab,\n\t\t\t// but the tab will already be activated by the time the announcement finishes.\n\t\t\tfocusedTab.attr( \"aria-selected\", \"false\" );\n\t\t\tthis.tabs.eq( selectedIndex ).attr( \"aria-selected\", \"true\" );\n\n\t\t\tthis.activating = this._delay( function() {\n\t\t\t\tthis.option( \"active\", selectedIndex );\n\t\t\t}, this.delay );\n\t\t}\n\t},\n\n\t_panelKeydown: function( event ) {\n\t\tif ( this._handlePageNav( event ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+up moves focus to the current tab\n\t\tif ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {\n\t\t\tevent.preventDefault();\n\t\t\tthis.active.trigger( \"focus\" );\n\t\t}\n\t},\n\n\t// Alt+page up/down moves focus to the previous/next tab (and activates)\n\t_handlePageNav: function( event ) {\n\t\tif ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {\n\t\t\tthis._activate( this._focusNextTab( this.options.active - 1, false ) );\n\t\t\treturn true;\n\t\t}\n\t\tif ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {\n\t\t\tthis._activate( this._focusNextTab( this.options.active + 1, true ) );\n\t\t\treturn true;\n\t\t}\n\t},\n\n\t_findNextTab: function( index, goingForward ) {\n\t\tvar lastTabIndex = this.tabs.length - 1;\n\n\t\tfunction constrain() {\n\t\t\tif ( index > lastTabIndex ) {\n\t\t\t\tindex = 0;\n\t\t\t}\n\t\t\tif ( index < 0 ) {\n\t\t\t\tindex = lastTabIndex;\n\t\t\t}\n\t\t\treturn index;\n\t\t}\n\n\t\twhile ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {\n\t\t\tindex = goingForward ? index + 1 : index - 1;\n\t\t}\n\n\t\treturn index;\n\t},\n\n\t_focusNextTab: function( index, goingForward ) {\n\t\tindex = this._findNextTab( index, goingForward );\n\t\tthis.tabs.eq( index ).trigger( \"focus\" );\n\t\treturn index;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"active\" ) {\n\n\t\t\t// _activate() will handle invalid values and update this.options\n\t\t\tthis._activate( value );\n\t\t\treturn;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"collapsible\" ) {\n\t\t\tthis._toggleClass( \"ui-tabs-collapsible\", null, value );\n\n\t\t\t// Setting collapsible: false while collapsed; open first panel\n\t\t\tif ( !value && this.options.active === false ) {\n\t\t\t\tthis._activate( 0 );\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"event\" ) {\n\t\t\tthis._setupEvents( value );\n\t\t}\n\n\t\tif ( key === \"heightStyle\" ) {\n\t\t\tthis._setupHeightStyle( value );\n\t\t}\n\t},\n\n\t_sanitizeSelector: function( hash ) {\n\t\treturn hash ? hash.replace( /[!\"$%&'()*+,.\\/:;<=>?@\\[\\]\\^`{|}~]/g, \"\\\\$&\" ) : \"\";\n\t},\n\n\trefresh: function() {\n\t\tvar options = this.options,\n\t\t\tlis = this.tablist.children( \":has(a[href])\" );\n\n\t\t// Get disabled tabs from class attribute from HTML\n\t\t// this will get converted to a boolean if needed in _refresh()\n\t\toptions.disabled = $.map( lis.filter( \".ui-state-disabled\" ), function( tab ) {\n\t\t\treturn lis.index( tab );\n\t\t} );\n\n\t\tthis._processTabs();\n\n\t\t// Was collapsed or no tabs\n\t\tif ( options.active === false || !this.anchors.length ) {\n\t\t\toptions.active = false;\n\t\t\tthis.active = $();\n\n\t\t// was active, but active tab is gone\n\t\t} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {\n\n\t\t\t// all remaining tabs are disabled\n\t\t\tif ( this.tabs.length === options.disabled.length ) {\n\t\t\t\toptions.active = false;\n\t\t\t\tthis.active = $();\n\n\t\t\t// activate previous tab\n\t\t\t} else {\n\t\t\t\tthis._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );\n\t\t\t}\n\n\t\t// was active, active tab still exists\n\t\t} else {\n\n\t\t\t// make sure active index is correct\n\t\t\toptions.active = this.tabs.index( this.active );\n\t\t}\n\n\t\tthis._refresh();\n\t},\n\n\t_refresh: function() {\n\t\tthis._setOptionDisabled( this.options.disabled );\n\t\tthis._setupEvents( this.options.event );\n\t\tthis._setupHeightStyle( this.options.heightStyle );\n\n\t\tthis.tabs.not( this.active ).attr( {\n\t\t\t\"aria-selected\": \"false\",\n\t\t\t\"aria-expanded\": \"false\",\n\t\t\ttabIndex: -1\n\t\t} );\n\t\tthis.panels.not( this._getPanelForTab( this.active ) )\n\t\t\t.hide()\n\t\t\t.attr( {\n\t\t\t\t\"aria-hidden\": \"true\"\n\t\t\t} );\n\n\t\t// Make sure one tab is in the tab order\n\t\tif ( !this.active.length ) {\n\t\t\tthis.tabs.eq( 0 ).attr( \"tabIndex\", 0 );\n\t\t} else {\n\t\t\tthis.active\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-selected\": \"true\",\n\t\t\t\t\t\"aria-expanded\": \"true\",\n\t\t\t\t\ttabIndex: 0\n\t\t\t\t} );\n\t\t\tthis._addClass( this.active, \"ui-tabs-active\", \"ui-state-active\" );\n\t\t\tthis._getPanelForTab( this.active )\n\t\t\t\t.show()\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-hidden\": \"false\"\n\t\t\t\t} );\n\t\t}\n\t},\n\n\t_processTabs: function() {\n\t\tvar that = this,\n\t\t\tprevTabs = this.tabs,\n\t\t\tprevAnchors = this.anchors,\n\t\t\tprevPanels = this.panels;\n\n\t\tthis.tablist = this._getList().attr( \"role\", \"tablist\" );\n\t\tthis._addClass( this.tablist, \"ui-tabs-nav\",\n\t\t\t\"ui-helper-reset ui-helper-clearfix ui-widget-header\" );\n\n\t\t// Prevent users from focusing disabled tabs via click\n\t\tthis.tablist\n\t\t\t.on( \"mousedown\" + this.eventNamespace, \"> li\", function( event ) {\n\t\t\t\tif ( $( this ).is( \".ui-state-disabled\" ) ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t} )\n\n\t\t\t// Support: IE <9\n\t\t\t// Preventing the default action in mousedown doesn't prevent IE\n\t\t\t// from focusing the element, so if the anchor gets focused, blur.\n\t\t\t// We don't have to worry about focusing the previously focused\n\t\t\t// element since clicking on a non-focusable element should focus\n\t\t\t// the body anyway.\n\t\t\t.on( \"focus\" + this.eventNamespace, \".ui-tabs-anchor\", function() {\n\t\t\t\tif ( $( this ).closest( \"li\" ).is( \".ui-state-disabled\" ) ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t}\n\t\t\t} );\n\n\t\tthis.tabs = this.tablist.find( \"> li:has(a[href])\" )\n\t\t\t.attr( {\n\t\t\t\trole: \"tab\",\n\t\t\t\ttabIndex: -1\n\t\t\t} );\n\t\tthis._addClass( this.tabs, \"ui-tabs-tab\", \"ui-state-default\" );\n\n\t\tthis.anchors = this.tabs.map( function() {\n\t\t\treturn $( \"a\", this )[ 0 ];\n\t\t} )\n\t\t\t.attr( {\n\t\t\t\trole: \"presentation\",\n\t\t\t\ttabIndex: -1\n\t\t\t} );\n\t\tthis._addClass( this.anchors, \"ui-tabs-anchor\" );\n\n\t\tthis.panels = $();\n\n\t\tthis.anchors.each( function( i, anchor ) {\n\t\t\tvar selector, panel, panelId,\n\t\t\t\tanchorId = $( anchor ).uniqueId().attr( \"id\" ),\n\t\t\t\ttab = $( anchor ).closest( \"li\" ),\n\t\t\t\toriginalAriaControls = tab.attr( \"aria-controls\" );\n\n\t\t\t// Inline tab\n\t\t\tif ( that._isLocal( anchor ) ) {\n\t\t\t\tselector = anchor.hash;\n\t\t\t\tpanelId = selector.substring( 1 );\n\t\t\t\tpanel = that.element.find( that._sanitizeSelector( selector ) );\n\n\t\t\t// remote tab\n\t\t\t} else {\n\n\t\t\t\t// If the tab doesn't already have aria-controls,\n\t\t\t\t// generate an id by using a throw-away element\n\t\t\t\tpanelId = tab.attr( \"aria-controls\" ) || $( {} ).uniqueId()[ 0 ].id;\n\t\t\t\tselector = \"#\" + panelId;\n\t\t\t\tpanel = that.element.find( selector );\n\t\t\t\tif ( !panel.length ) {\n\t\t\t\t\tpanel = that._createPanel( panelId );\n\t\t\t\t\tpanel.insertAfter( that.panels[ i - 1 ] || that.tablist );\n\t\t\t\t}\n\t\t\t\tpanel.attr( \"aria-live\", \"polite\" );\n\t\t\t}\n\n\t\t\tif ( panel.length ) {\n\t\t\t\tthat.panels = that.panels.add( panel );\n\t\t\t}\n\t\t\tif ( originalAriaControls ) {\n\t\t\t\ttab.data( \"ui-tabs-aria-controls\", originalAriaControls );\n\t\t\t}\n\t\t\ttab.attr( {\n\t\t\t\t\"aria-controls\": panelId,\n\t\t\t\t\"aria-labelledby\": anchorId\n\t\t\t} );\n\t\t\tpanel.attr( \"aria-labelledby\", anchorId );\n\t\t} );\n\n\t\tthis.panels.attr( \"role\", \"tabpanel\" );\n\t\tthis._addClass( this.panels, \"ui-tabs-panel\", \"ui-widget-content\" );\n\n\t\t// Avoid memory leaks (#10056)\n\t\tif ( prevTabs ) {\n\t\t\tthis._off( prevTabs.not( this.tabs ) );\n\t\t\tthis._off( prevAnchors.not( this.anchors ) );\n\t\t\tthis._off( prevPanels.not( this.panels ) );\n\t\t}\n\t},\n\n\t// Allow overriding how to find the list for rare usage scenarios (#7715)\n\t_getList: function() {\n\t\treturn this.tablist || this.element.find( \"ol, ul\" ).eq( 0 );\n\t},\n\n\t_createPanel: function( id ) {\n\t\treturn $( \"<div>\" )\n\t\t\t.attr( \"id\", id )\n\t\t\t.data( \"ui-tabs-destroy\", true );\n\t},\n\n\t_setOptionDisabled: function( disabled ) {\n\t\tvar currentItem, li, i;\n\n\t\tif ( $.isArray( disabled ) ) {\n\t\t\tif ( !disabled.length ) {\n\t\t\t\tdisabled = false;\n\t\t\t} else if ( disabled.length === this.anchors.length ) {\n\t\t\t\tdisabled = true;\n\t\t\t}\n\t\t}\n\n\t\t// Disable tabs\n\t\tfor ( i = 0; ( li = this.tabs[ i ] ); i++ ) {\n\t\t\tcurrentItem = $( li );\n\t\t\tif ( disabled === true || $.inArray( i, disabled ) !== -1 ) {\n\t\t\t\tcurrentItem.attr( \"aria-disabled\", \"true\" );\n\t\t\t\tthis._addClass( currentItem, null, \"ui-state-disabled\" );\n\t\t\t} else {\n\t\t\t\tcurrentItem.removeAttr( \"aria-disabled\" );\n\t\t\t\tthis._removeClass( currentItem, null, \"ui-state-disabled\" );\n\t\t\t}\n\t\t}\n\n\t\tthis.options.disabled = disabled;\n\n\t\tthis._toggleClass( this.widget(), this.widgetFullName + \"-disabled\", null,\n\t\t\tdisabled === true );\n\t},\n\n\t_setupEvents: function( event ) {\n\t\tvar events = {};\n\t\tif ( event ) {\n\t\t\t$.each( event.split( \" \" ), function( index, eventName ) {\n\t\t\t\tevents[ eventName ] = \"_eventHandler\";\n\t\t\t} );\n\t\t}\n\n\t\tthis._off( this.anchors.add( this.tabs ).add( this.panels ) );\n\n\t\t// Always prevent the default action, even when disabled\n\t\tthis._on( true, this.anchors, {\n\t\t\tclick: function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t} );\n\t\tthis._on( this.anchors, events );\n\t\tthis._on( this.tabs, { keydown: \"_tabKeydown\" } );\n\t\tthis._on( this.panels, { keydown: \"_panelKeydown\" } );\n\n\t\tthis._focusable( this.tabs );\n\t\tthis._hoverable( this.tabs );\n\t},\n\n\t_setupHeightStyle: function( heightStyle ) {\n\t\tvar maxHeight,\n\t\t\tparent = this.element.parent();\n\n\t\tif ( heightStyle === \"fill\" ) {\n\t\t\tmaxHeight = parent.height();\n\t\t\tmaxHeight -= this.element.outerHeight() - this.element.height();\n\n\t\t\tthis.element.siblings( \":visible\" ).each( function() {\n\t\t\t\tvar elem = $( this ),\n\t\t\t\t\tposition = elem.css( \"position\" );\n\n\t\t\t\tif ( position === \"absolute\" || position === \"fixed\" ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmaxHeight -= elem.outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.element.children().not( this.panels ).each( function() {\n\t\t\t\tmaxHeight -= $( this ).outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.panels.each( function() {\n\t\t\t\t$( this ).height( Math.max( 0, maxHeight -\n\t\t\t\t\t$( this ).innerHeight() + $( this ).height() ) );\n\t\t\t} )\n\t\t\t\t.css( \"overflow\", \"auto\" );\n\t\t} else if ( heightStyle === \"auto\" ) {\n\t\t\tmaxHeight = 0;\n\t\t\tthis.panels.each( function() {\n\t\t\t\tmaxHeight = Math.max( maxHeight, $( this ).height( \"\" ).height() );\n\t\t\t} ).height( maxHeight );\n\t\t}\n\t},\n\n\t_eventHandler: function( event ) {\n\t\tvar options = this.options,\n\t\t\tactive = this.active,\n\t\t\tanchor = $( event.currentTarget ),\n\t\t\ttab = anchor.closest( \"li\" ),\n\t\t\tclickedIsActive = tab[ 0 ] === active[ 0 ],\n\t\t\tcollapsing = clickedIsActive && options.collapsible,\n\t\t\ttoShow = collapsing ? $() : this._getPanelForTab( tab ),\n\t\t\ttoHide = !active.length ? $() : this._getPanelForTab( active ),\n\t\t\teventData = {\n\t\t\t\toldTab: active,\n\t\t\t\toldPanel: toHide,\n\t\t\t\tnewTab: collapsing ? $() : tab,\n\t\t\t\tnewPanel: toShow\n\t\t\t};\n\n\t\tevent.preventDefault();\n\n\t\tif ( tab.hasClass( \"ui-state-disabled\" ) ||\n\n\t\t\t\t// tab is already loading\n\t\t\t\ttab.hasClass( \"ui-tabs-loading\" ) ||\n\n\t\t\t\t// can't switch durning an animation\n\t\t\t\tthis.running ||\n\n\t\t\t\t// click on active header, but not collapsible\n\t\t\t\t( clickedIsActive && !options.collapsible ) ||\n\n\t\t\t\t// allow canceling activation\n\t\t\t\t( this._trigger( \"beforeActivate\", event, eventData ) === false ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\toptions.active = collapsing ? false : this.tabs.index( tab );\n\n\t\tthis.active = clickedIsActive ? $() : tab;\n\t\tif ( this.xhr ) {\n\t\t\tthis.xhr.abort();\n\t\t}\n\n\t\tif ( !toHide.length && !toShow.length ) {\n\t\t\t$.error( \"jQuery UI Tabs: Mismatching fragment identifier.\" );\n\t\t}\n\n\t\tif ( toShow.length ) {\n\t\t\tthis.load( this.tabs.index( tab ), event );\n\t\t}\n\t\tthis._toggle( event, eventData );\n\t},\n\n\t// Handles show/hide for selecting tabs\n\t_toggle: function( event, eventData ) {\n\t\tvar that = this,\n\t\t\ttoShow = eventData.newPanel,\n\t\t\ttoHide = eventData.oldPanel;\n\n\t\tthis.running = true;\n\n\t\tfunction complete() {\n\t\t\tthat.running = false;\n\t\t\tthat._trigger( \"activate\", event, eventData );\n\t\t}\n\n\t\tfunction show() {\n\t\t\tthat._addClass( eventData.newTab.closest( \"li\" ), \"ui-tabs-active\", \"ui-state-active\" );\n\n\t\t\tif ( toShow.length && that.options.show ) {\n\t\t\t\tthat._show( toShow, that.options.show, complete );\n\t\t\t} else {\n\t\t\t\ttoShow.show();\n\t\t\t\tcomplete();\n\t\t\t}\n\t\t}\n\n\t\t// Start out by hiding, then showing, then completing\n\t\tif ( toHide.length && this.options.hide ) {\n\t\t\tthis._hide( toHide, this.options.hide, function() {\n\t\t\t\tthat._removeClass( eventData.oldTab.closest( \"li\" ),\n\t\t\t\t\t\"ui-tabs-active\", \"ui-state-active\" );\n\t\t\t\tshow();\n\t\t\t} );\n\t\t} else {\n\t\t\tthis._removeClass( eventData.oldTab.closest( \"li\" ),\n\t\t\t\t\"ui-tabs-active\", \"ui-state-active\" );\n\t\t\ttoHide.hide();\n\t\t\tshow();\n\t\t}\n\n\t\ttoHide.attr( \"aria-hidden\", \"true\" );\n\t\teventData.oldTab.attr( {\n\t\t\t\"aria-selected\": \"false\",\n\t\t\t\"aria-expanded\": \"false\"\n\t\t} );\n\n\t\t// If we're switching tabs, remove the old tab from the tab order.\n\t\t// If we're opening from collapsed state, remove the previous tab from the tab order.\n\t\t// If we're collapsing, then keep the collapsing tab in the tab order.\n\t\tif ( toShow.length && toHide.length ) {\n\t\t\teventData.oldTab.attr( \"tabIndex\", -1 );\n\t\t} else if ( toShow.length ) {\n\t\t\tthis.tabs.filter( function() {\n\t\t\t\treturn $( this ).attr( \"tabIndex\" ) === 0;\n\t\t\t} )\n\t\t\t\t.attr( \"tabIndex\", -1 );\n\t\t}\n\n\t\ttoShow.attr( \"aria-hidden\", \"false\" );\n\t\teventData.newTab.attr( {\n\t\t\t\"aria-selected\": \"true\",\n\t\t\t\"aria-expanded\": \"true\",\n\t\t\ttabIndex: 0\n\t\t} );\n\t},\n\n\t_activate: function( index ) {\n\t\tvar anchor,\n\t\t\tactive = this._findActive( index );\n\n\t\t// Trying to activate the already active panel\n\t\tif ( active[ 0 ] === this.active[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Trying to collapse, simulate a click on the current active header\n\t\tif ( !active.length ) {\n\t\t\tactive = this.active;\n\t\t}\n\n\t\tanchor = active.find( \".ui-tabs-anchor\" )[ 0 ];\n\t\tthis._eventHandler( {\n\t\t\ttarget: anchor,\n\t\t\tcurrentTarget: anchor,\n\t\t\tpreventDefault: $.noop\n\t\t} );\n\t},\n\n\t_findActive: function( index ) {\n\t\treturn index === false ? $() : this.tabs.eq( index );\n\t},\n\n\t_getIndex: function( index ) {\n\n\t\t// meta-function to give users option to provide a href string instead of a numerical index.\n\t\tif ( typeof index === \"string\" ) {\n\t\t\tindex = this.anchors.index( this.anchors.filter( \"[href$='\" +\n\t\t\t\t$.ui.escapeSelector( index ) + \"']\" ) );\n\t\t}\n\n\t\treturn index;\n\t},\n\n\t_destroy: function() {\n\t\tif ( this.xhr ) {\n\t\t\tthis.xhr.abort();\n\t\t}\n\n\t\tthis.tablist\n\t\t\t.removeAttr( \"role\" )\n\t\t\t.off( this.eventNamespace );\n\n\t\tthis.anchors\n\t\t\t.removeAttr( \"role tabIndex\" )\n\t\t\t.removeUniqueId();\n\n\t\tthis.tabs.add( this.panels ).each( function() {\n\t\t\tif ( $.data( this, \"ui-tabs-destroy\" ) ) {\n\t\t\t\t$( this ).remove();\n\t\t\t} else {\n\t\t\t\t$( this ).removeAttr( \"role tabIndex \" +\n\t\t\t\t\t\"aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded\" );\n\t\t\t}\n\t\t} );\n\n\t\tthis.tabs.each( function() {\n\t\t\tvar li = $( this ),\n\t\t\t\tprev = li.data( \"ui-tabs-aria-controls\" );\n\t\t\tif ( prev ) {\n\t\t\t\tli\n\t\t\t\t\t.attr( \"aria-controls\", prev )\n\t\t\t\t\t.removeData( \"ui-tabs-aria-controls\" );\n\t\t\t} else {\n\t\t\t\tli.removeAttr( \"aria-controls\" );\n\t\t\t}\n\t\t} );\n\n\t\tthis.panels.show();\n\n\t\tif ( this.options.heightStyle !== \"content\" ) {\n\t\t\tthis.panels.css( \"height\", \"\" );\n\t\t}\n\t},\n\n\tenable: function( index ) {\n\t\tvar disabled = this.options.disabled;\n\t\tif ( disabled === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( index === undefined ) {\n\t\t\tdisabled = false;\n\t\t} else {\n\t\t\tindex = this._getIndex( index );\n\t\t\tif ( $.isArray( disabled ) ) {\n\t\t\t\tdisabled = $.map( disabled, function( num ) {\n\t\t\t\t\treturn num !== index ? num : null;\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tdisabled = $.map( this.tabs, function( li, num ) {\n\t\t\t\t\treturn num !== index ? num : null;\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\t\tthis._setOptionDisabled( disabled );\n\t},\n\n\tdisable: function( index ) {\n\t\tvar disabled = this.options.disabled;\n\t\tif ( disabled === true ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( index === undefined ) {\n\t\t\tdisabled = true;\n\t\t} else {\n\t\t\tindex = this._getIndex( index );\n\t\t\tif ( $.inArray( index, disabled ) !== -1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( $.isArray( disabled ) ) {\n\t\t\t\tdisabled = $.merge( [ index ], disabled ).sort();\n\t\t\t} else {\n\t\t\t\tdisabled = [ index ];\n\t\t\t}\n\t\t}\n\t\tthis._setOptionDisabled( disabled );\n\t},\n\n\tload: function( index, event ) {\n\t\tindex = this._getIndex( index );\n\t\tvar that = this,\n\t\t\ttab = this.tabs.eq( index ),\n\t\t\tanchor = tab.find( \".ui-tabs-anchor\" ),\n\t\t\tpanel = this._getPanelForTab( tab ),\n\t\t\teventData = {\n\t\t\t\ttab: tab,\n\t\t\t\tpanel: panel\n\t\t\t},\n\t\t\tcomplete = function( jqXHR, status ) {\n\t\t\t\tif ( status === \"abort\" ) {\n\t\t\t\t\tthat.panels.stop( false, true );\n\t\t\t\t}\n\n\t\t\t\tthat._removeClass( tab, \"ui-tabs-loading\" );\n\t\t\t\tpanel.removeAttr( \"aria-busy\" );\n\n\t\t\t\tif ( jqXHR === that.xhr ) {\n\t\t\t\t\tdelete that.xhr;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Not remote\n\t\tif ( this._isLocal( anchor[ 0 ] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );\n\n\t\t// Support: jQuery <1.8\n\t\t// jQuery <1.8 returns false if the request is canceled in beforeSend,\n\t\t// but as of 1.8, $.ajax() always returns a jqXHR object.\n\t\tif ( this.xhr && this.xhr.statusText !== \"canceled\" ) {\n\t\t\tthis._addClass( tab, \"ui-tabs-loading\" );\n\t\t\tpanel.attr( \"aria-busy\", \"true\" );\n\n\t\t\tthis.xhr\n\t\t\t\t.done( function( response, status, jqXHR ) {\n\n\t\t\t\t\t// support: jQuery <1.8\n\t\t\t\t\t// http://bugs.jquery.com/ticket/11778\n\t\t\t\t\tsetTimeout( function() {\n\t\t\t\t\t\tpanel.html( response );\n\t\t\t\t\t\tthat._trigger( \"load\", event, eventData );\n\n\t\t\t\t\t\tcomplete( jqXHR, status );\n\t\t\t\t\t}, 1 );\n\t\t\t\t} )\n\t\t\t\t.fail( function( jqXHR, status ) {\n\n\t\t\t\t\t// support: jQuery <1.8\n\t\t\t\t\t// http://bugs.jquery.com/ticket/11778\n\t\t\t\t\tsetTimeout( function() {\n\t\t\t\t\t\tcomplete( jqXHR, status );\n\t\t\t\t\t}, 1 );\n\t\t\t\t} );\n\t\t}\n\t},\n\n\t_ajaxSettings: function( anchor, event, eventData ) {\n\t\tvar that = this;\n\t\treturn {\n\n\t\t\t// Support: IE <11 only\n\t\t\t// Strip any hash that exists to prevent errors with the Ajax request\n\t\t\turl: anchor.attr( \"href\" ).replace( /#.*$/, \"\" ),\n\t\t\tbeforeSend: function( jqXHR, settings ) {\n\t\t\t\treturn that._trigger( \"beforeLoad\", event,\n\t\t\t\t\t$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );\n\t\t\t}\n\t\t};\n\t},\n\n\t_getPanelForTab: function( tab ) {\n\t\tvar id = $( tab ).attr( \"aria-controls\" );\n\t\treturn this.element.find( this._sanitizeSelector( \"#\" + id ) );\n\t}\n} );\n\n// DEPRECATED\n// TODO: Switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for ui-tab class (now ui-tabs-tab)\n\t$.widget( \"ui.tabs\", $.ui.tabs, {\n\t\t_processTabs: function() {\n\t\t\tthis._superApply( arguments );\n\t\t\tthis._addClass( this.tabs, \"ui-tab\" );\n\t\t}\n\t} );\n}\n\nvar widgetsTabs = $.ui.tabs;\n\n\n/*!\n * jQuery UI Tooltip 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Tooltip\n//>>group: Widgets\n//>>description: Shows additional information for any element on hover or focus.\n//>>docs: http://api.jqueryui.com/tooltip/\n//>>demos: http://jqueryui.com/tooltip/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/tooltip.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.tooltip\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-tooltip\": \"ui-corner-all ui-widget-shadow\"\n\t\t},\n\t\tcontent: function() {\n\n\t\t\t// support: IE<9, Opera in jQuery <1.7\n\t\t\t// .text() can't accept undefined, so coerce to a string\n\t\t\tvar title = $( this ).attr( \"title\" ) || \"\";\n\n\t\t\t// Escape title, since we're going from an attribute to raw HTML\n\t\t\treturn $( \"<a>\" ).text( title ).html();\n\t\t},\n\t\thide: true,\n\n\t\t// Disabled elements have inconsistent behavior across browsers (#8661)\n\t\titems: \"[title]:not([disabled])\",\n\t\tposition: {\n\t\t\tmy: \"left top+15\",\n\t\t\tat: \"left bottom\",\n\t\t\tcollision: \"flipfit flip\"\n\t\t},\n\t\tshow: true,\n\t\ttrack: false,\n\n\t\t// Callbacks\n\t\tclose: null,\n\t\topen: null\n\t},\n\n\t_addDescribedBy: function( elem, id ) {\n\t\tvar describedby = ( elem.attr( \"aria-describedby\" ) || \"\" ).split( /\\s+/ );\n\t\tdescribedby.push( id );\n\t\telem\n\t\t\t.data( \"ui-tooltip-id\", id )\n\t\t\t.attr( \"aria-describedby\", $.trim( describedby.join( \" \" ) ) );\n\t},\n\n\t_removeDescribedBy: function( elem ) {\n\t\tvar id = elem.data( \"ui-tooltip-id\" ),\n\t\t\tdescribedby = ( elem.attr( \"aria-describedby\" ) || \"\" ).split( /\\s+/ ),\n\t\t\tindex = $.inArray( id, describedby );\n\n\t\tif ( index !== -1 ) {\n\t\t\tdescribedby.splice( index, 1 );\n\t\t}\n\n\t\telem.removeData( \"ui-tooltip-id\" );\n\t\tdescribedby = $.trim( describedby.join( \" \" ) );\n\t\tif ( describedby ) {\n\t\t\telem.attr( \"aria-describedby\", describedby );\n\t\t} else {\n\t\t\telem.removeAttr( \"aria-describedby\" );\n\t\t}\n\t},\n\n\t_create: function() {\n\t\tthis._on( {\n\t\t\tmouseover: \"open\",\n\t\t\tfocusin: \"open\"\n\t\t} );\n\n\t\t// IDs of generated tooltips, needed for destroy\n\t\tthis.tooltips = {};\n\n\t\t// IDs of parent tooltips where we removed the title attribute\n\t\tthis.parents = {};\n\n\t\t// Append the aria-live region so tooltips announce correctly\n\t\tthis.liveRegion = $( \"<div>\" )\n\t\t\t.attr( {\n\t\t\t\trole: \"log\",\n\t\t\t\t\"aria-live\": \"assertive\",\n\t\t\t\t\"aria-relevant\": \"additions\"\n\t\t\t} )\n\t\t\t.appendTo( this.document[ 0 ].body );\n\t\tthis._addClass( this.liveRegion, null, \"ui-helper-hidden-accessible\" );\n\n\t\tthis.disabledTitles = $( [] );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar that = this;\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"content\" ) {\n\t\t\t$.each( this.tooltips, function( id, tooltipData ) {\n\t\t\t\tthat._updateContent( tooltipData.element );\n\t\t\t} );\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis[ value ? \"_disable\" : \"_enable\" ]();\n\t},\n\n\t_disable: function() {\n\t\tvar that = this;\n\n\t\t// Close open tooltips\n\t\t$.each( this.tooltips, function( id, tooltipData ) {\n\t\t\tvar event = $.Event( \"blur\" );\n\t\t\tevent.target = event.currentTarget = tooltipData.element[ 0 ];\n\t\t\tthat.close( event, true );\n\t\t} );\n\n\t\t// Remove title attributes to prevent native tooltips\n\t\tthis.disabledTitles = this.disabledTitles.add(\n\t\t\tthis.element.find( this.options.items ).addBack()\n\t\t\t\t.filter( function() {\n\t\t\t\t\tvar element = $( this );\n\t\t\t\t\tif ( element.is( \"[title]\" ) ) {\n\t\t\t\t\t\treturn element\n\t\t\t\t\t\t\t.data( \"ui-tooltip-title\", element.attr( \"title\" ) )\n\t\t\t\t\t\t\t.removeAttr( \"title\" );\n\t\t\t\t\t}\n\t\t\t\t} )\n\t\t);\n\t},\n\n\t_enable: function() {\n\n\t\t// restore title attributes\n\t\tthis.disabledTitles.each( function() {\n\t\t\tvar element = $( this );\n\t\t\tif ( element.data( \"ui-tooltip-title\" ) ) {\n\t\t\t\telement.attr( \"title\", element.data( \"ui-tooltip-title\" ) );\n\t\t\t}\n\t\t} );\n\t\tthis.disabledTitles = $( [] );\n\t},\n\n\topen: function( event ) {\n\t\tvar that = this,\n\t\t\ttarget = $( event ? event.target : this.element )\n\n\t\t\t\t// we need closest here due to mouseover bubbling,\n\t\t\t\t// but always pointing at the same event target\n\t\t\t\t.closest( this.options.items );\n\n\t\t// No element to show a tooltip for or the tooltip is already open\n\t\tif ( !target.length || target.data( \"ui-tooltip-id\" ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( target.attr( \"title\" ) ) {\n\t\t\ttarget.data( \"ui-tooltip-title\", target.attr( \"title\" ) );\n\t\t}\n\n\t\ttarget.data( \"ui-tooltip-open\", true );\n\n\t\t// Kill parent tooltips, custom or native, for hover\n\t\tif ( event && event.type === \"mouseover\" ) {\n\t\t\ttarget.parents().each( function() {\n\t\t\t\tvar parent = $( this ),\n\t\t\t\t\tblurEvent;\n\t\t\t\tif ( parent.data( \"ui-tooltip-open\" ) ) {\n\t\t\t\t\tblurEvent = $.Event( \"blur\" );\n\t\t\t\t\tblurEvent.target = blurEvent.currentTarget = this;\n\t\t\t\t\tthat.close( blurEvent, true );\n\t\t\t\t}\n\t\t\t\tif ( parent.attr( \"title\" ) ) {\n\t\t\t\t\tparent.uniqueId();\n\t\t\t\t\tthat.parents[ this.id ] = {\n\t\t\t\t\t\telement: this,\n\t\t\t\t\t\ttitle: parent.attr( \"title\" )\n\t\t\t\t\t};\n\t\t\t\t\tparent.attr( \"title\", \"\" );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tthis._registerCloseHandlers( event, target );\n\t\tthis._updateContent( target, event );\n\t},\n\n\t_updateContent: function( target, event ) {\n\t\tvar content,\n\t\t\tcontentOption = this.options.content,\n\t\t\tthat = this,\n\t\t\teventType = event ? event.type : null;\n\n\t\tif ( typeof contentOption === \"string\" || contentOption.nodeType ||\n\t\t\t\tcontentOption.jquery ) {\n\t\t\treturn this._open( event, target, contentOption );\n\t\t}\n\n\t\tcontent = contentOption.call( target[ 0 ], function( response ) {\n\n\t\t\t// IE may instantly serve a cached response for ajax requests\n\t\t\t// delay this call to _open so the other call to _open runs first\n\t\t\tthat._delay( function() {\n\n\t\t\t\t// Ignore async response if tooltip was closed already\n\t\t\t\tif ( !target.data( \"ui-tooltip-open\" ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// JQuery creates a special event for focusin when it doesn't\n\t\t\t\t// exist natively. To improve performance, the native event\n\t\t\t\t// object is reused and the type is changed. Therefore, we can't\n\t\t\t\t// rely on the type being correct after the event finished\n\t\t\t\t// bubbling, so we set it back to the previous value. (#8740)\n\t\t\t\tif ( event ) {\n\t\t\t\t\tevent.type = eventType;\n\t\t\t\t}\n\t\t\t\tthis._open( event, target, response );\n\t\t\t} );\n\t\t} );\n\t\tif ( content ) {\n\t\t\tthis._open( event, target, content );\n\t\t}\n\t},\n\n\t_open: function( event, target, content ) {\n\t\tvar tooltipData, tooltip, delayedShow, a11yContent,\n\t\t\tpositionOption = $.extend( {}, this.options.position );\n\n\t\tif ( !content ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Content can be updated multiple times. If the tooltip already\n\t\t// exists, then just update the content and bail.\n\t\ttooltipData = this._find( target );\n\t\tif ( tooltipData ) {\n\t\t\ttooltipData.tooltip.find( \".ui-tooltip-content\" ).html( content );\n\t\t\treturn;\n\t\t}\n\n\t\t// If we have a title, clear it to prevent the native tooltip\n\t\t// we have to check first to avoid defining a title if none exists\n\t\t// (we don't want to cause an element to start matching [title])\n\t\t//\n\t\t// We use removeAttr only for key events, to allow IE to export the correct\n\t\t// accessible attributes. For mouse events, set to empty string to avoid\n\t\t// native tooltip showing up (happens only when removing inside mouseover).\n\t\tif ( target.is( \"[title]\" ) ) {\n\t\t\tif ( event && event.type === \"mouseover\" ) {\n\t\t\t\ttarget.attr( \"title\", \"\" );\n\t\t\t} else {\n\t\t\t\ttarget.removeAttr( \"title\" );\n\t\t\t}\n\t\t}\n\n\t\ttooltipData = this._tooltip( target );\n\t\ttooltip = tooltipData.tooltip;\n\t\tthis._addDescribedBy( target, tooltip.attr( \"id\" ) );\n\t\ttooltip.find( \".ui-tooltip-content\" ).html( content );\n\n\t\t// Support: Voiceover on OS X, JAWS on IE <= 9\n\t\t// JAWS announces deletions even when aria-relevant=\"additions\"\n\t\t// Voiceover will sometimes re-read the entire log region's contents from the beginning\n\t\tthis.liveRegion.children().hide();\n\t\ta11yContent = $( \"<div>\" ).html( tooltip.find( \".ui-tooltip-content\" ).html() );\n\t\ta11yContent.removeAttr( \"name\" ).find( \"[name]\" ).removeAttr( \"name\" );\n\t\ta11yContent.removeAttr( \"id\" ).find( \"[id]\" ).removeAttr( \"id\" );\n\t\ta11yContent.appendTo( this.liveRegion );\n\n\t\tfunction position( event ) {\n\t\t\tpositionOption.of = event;\n\t\t\tif ( tooltip.is( \":hidden\" ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttooltip.position( positionOption );\n\t\t}\n\t\tif ( this.options.track && event && /^mouse/.test( event.type ) ) {\n\t\t\tthis._on( this.document, {\n\t\t\t\tmousemove: position\n\t\t\t} );\n\n\t\t\t// trigger once to override element-relative positioning\n\t\t\tposition( event );\n\t\t} else {\n\t\t\ttooltip.position( $.extend( {\n\t\t\t\tof: target\n\t\t\t}, this.options.position ) );\n\t\t}\n\n\t\ttooltip.hide();\n\n\t\tthis._show( tooltip, this.options.show );\n\n\t\t// Handle tracking tooltips that are shown with a delay (#8644). As soon\n\t\t// as the tooltip is visible, position the tooltip using the most recent\n\t\t// event.\n\t\t// Adds the check to add the timers only when both delay and track options are set (#14682)\n\t\tif ( this.options.track && this.options.show && this.options.show.delay ) {\n\t\t\tdelayedShow = this.delayedShow = setInterval( function() {\n\t\t\t\tif ( tooltip.is( \":visible\" ) ) {\n\t\t\t\t\tposition( positionOption.of );\n\t\t\t\t\tclearInterval( delayedShow );\n\t\t\t\t}\n\t\t\t}, $.fx.interval );\n\t\t}\n\n\t\tthis._trigger( \"open\", event, { tooltip: tooltip } );\n\t},\n\n\t_registerCloseHandlers: function( event, target ) {\n\t\tvar events = {\n\t\t\tkeyup: function( event ) {\n\t\t\t\tif ( event.keyCode === $.ui.keyCode.ESCAPE ) {\n\t\t\t\t\tvar fakeEvent = $.Event( event );\n\t\t\t\t\tfakeEvent.currentTarget = target[ 0 ];\n\t\t\t\t\tthis.close( fakeEvent, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t// Only bind remove handler for delegated targets. Non-delegated\n\t\t// tooltips will handle this in destroy.\n\t\tif ( target[ 0 ] !== this.element[ 0 ] ) {\n\t\t\tevents.remove = function() {\n\t\t\t\tthis._removeTooltip( this._find( target ).tooltip );\n\t\t\t};\n\t\t}\n\n\t\tif ( !event || event.type === \"mouseover\" ) {\n\t\t\tevents.mouseleave = \"close\";\n\t\t}\n\t\tif ( !event || event.type === \"focusin\" ) {\n\t\t\tevents.focusout = \"close\";\n\t\t}\n\t\tthis._on( true, target, events );\n\t},\n\n\tclose: function( event ) {\n\t\tvar tooltip,\n\t\t\tthat = this,\n\t\t\ttarget = $( event ? event.currentTarget : this.element ),\n\t\t\ttooltipData = this._find( target );\n\n\t\t// The tooltip may already be closed\n\t\tif ( !tooltipData ) {\n\n\t\t\t// We set ui-tooltip-open immediately upon open (in open()), but only set the\n\t\t\t// additional data once there's actually content to show (in _open()). So even if the\n\t\t\t// tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in\n\t\t\t// the period between open() and _open().\n\t\t\ttarget.removeData( \"ui-tooltip-open\" );\n\t\t\treturn;\n\t\t}\n\n\t\ttooltip = tooltipData.tooltip;\n\n\t\t// Disabling closes the tooltip, so we need to track when we're closing\n\t\t// to avoid an infinite loop in case the tooltip becomes disabled on close\n\t\tif ( tooltipData.closing ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clear the interval for delayed tracking tooltips\n\t\tclearInterval( this.delayedShow );\n\n\t\t// Only set title if we had one before (see comment in _open())\n\t\t// If the title attribute has changed since open(), don't restore\n\t\tif ( target.data( \"ui-tooltip-title\" ) && !target.attr( \"title\" ) ) {\n\t\t\ttarget.attr( \"title\", target.data( \"ui-tooltip-title\" ) );\n\t\t}\n\n\t\tthis._removeDescribedBy( target );\n\n\t\ttooltipData.hiding = true;\n\t\ttooltip.stop( true );\n\t\tthis._hide( tooltip, this.options.hide, function() {\n\t\t\tthat._removeTooltip( $( this ) );\n\t\t} );\n\n\t\ttarget.removeData( \"ui-tooltip-open\" );\n\t\tthis._off( target, \"mouseleave focusout keyup\" );\n\n\t\t// Remove 'remove' binding only on delegated targets\n\t\tif ( target[ 0 ] !== this.element[ 0 ] ) {\n\t\t\tthis._off( target, \"remove\" );\n\t\t}\n\t\tthis._off( this.document, \"mousemove\" );\n\n\t\tif ( event && event.type === \"mouseleave\" ) {\n\t\t\t$.each( this.parents, function( id, parent ) {\n\t\t\t\t$( parent.element ).attr( \"title\", parent.title );\n\t\t\t\tdelete that.parents[ id ];\n\t\t\t} );\n\t\t}\n\n\t\ttooltipData.closing = true;\n\t\tthis._trigger( \"close\", event, { tooltip: tooltip } );\n\t\tif ( !tooltipData.hiding ) {\n\t\t\ttooltipData.closing = false;\n\t\t}\n\t},\n\n\t_tooltip: function( element ) {\n\t\tvar tooltip = $( \"<div>\" ).attr( \"role\", \"tooltip\" ),\n\t\t\tcontent = $( \"<div>\" ).appendTo( tooltip ),\n\t\t\tid = tooltip.uniqueId().attr( \"id\" );\n\n\t\tthis._addClass( content, \"ui-tooltip-content\" );\n\t\tthis._addClass( tooltip, \"ui-tooltip\", \"ui-widget ui-widget-content\" );\n\n\t\ttooltip.appendTo( this._appendTo( element ) );\n\n\t\treturn this.tooltips[ id ] = {\n\t\t\telement: element,\n\t\t\ttooltip: tooltip\n\t\t};\n\t},\n\n\t_find: function( target ) {\n\t\tvar id = target.data( \"ui-tooltip-id\" );\n\t\treturn id ? this.tooltips[ id ] : null;\n\t},\n\n\t_removeTooltip: function( tooltip ) {\n\t\ttooltip.remove();\n\t\tdelete this.tooltips[ tooltip.attr( \"id\" ) ];\n\t},\n\n\t_appendTo: function( target ) {\n\t\tvar element = target.closest( \".ui-front, dialog\" );\n\n\t\tif ( !element.length ) {\n\t\t\telement = this.document[ 0 ].body;\n\t\t}\n\n\t\treturn element;\n\t},\n\n\t_destroy: function() {\n\t\tvar that = this;\n\n\t\t// Close open tooltips\n\t\t$.each( this.tooltips, function( id, tooltipData ) {\n\n\t\t\t// Delegate to close method to handle common cleanup\n\t\t\tvar event = $.Event( \"blur\" ),\n\t\t\t\telement = tooltipData.element;\n\t\t\tevent.target = event.currentTarget = element[ 0 ];\n\t\t\tthat.close( event, true );\n\n\t\t\t// Remove immediately; destroying an open tooltip doesn't use the\n\t\t\t// hide animation\n\t\t\t$( \"#\" + id ).remove();\n\n\t\t\t// Restore the title\n\t\t\tif ( element.data( \"ui-tooltip-title\" ) ) {\n\n\t\t\t\t// If the title attribute has changed since open(), don't restore\n\t\t\t\tif ( !element.attr( \"title\" ) ) {\n\t\t\t\t\telement.attr( \"title\", element.data( \"ui-tooltip-title\" ) );\n\t\t\t\t}\n\t\t\t\telement.removeData( \"ui-tooltip-title\" );\n\t\t\t}\n\t\t} );\n\t\tthis.liveRegion.remove();\n\t}\n} );\n\n// DEPRECATED\n// TODO: Switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for tooltipClass option\n\t$.widget( \"ui.tooltip\", $.ui.tooltip, {\n\t\toptions: {\n\t\t\ttooltipClass: null\n\t\t},\n\t\t_tooltip: function() {\n\t\t\tvar tooltipData = this._superApply( arguments );\n\t\t\tif ( this.options.tooltipClass ) {\n\t\t\t\ttooltipData.tooltip.addClass( this.options.tooltipClass );\n\t\t\t}\n\t\t\treturn tooltipData;\n\t\t}\n\t} );\n}\n\nvar widgetsTooltip = $.ui.tooltip;\n\n\n\n\n}));"
  },
  {
    "path": "src/screenshotbot/js/vendor/metisMenu.js",
    "content": "/*!\n* metismenu https://github.com/onokumus/metismenu#readme\n* A jQuery menu plugin\n* @version 3.0.6\n* @author Osman Nuri Okumus <onokumus@gmail.com> (https://github.com/onokumus)\n* @license: MIT \n*/\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :\n  typeof define === 'function' && define.amd ? define(['jquery'], factory) :\n  (global = global || self, global.metisMenu = factory(global.jQuery));\n}(this, (function ($) { 'use strict';\n\n  $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;\n\n  function _extends() {\n    _extends = Object.assign || function (target) {\n      for (var i = 1; i < arguments.length; i++) {\n        var source = arguments[i];\n\n        for (var key in source) {\n          if (Object.prototype.hasOwnProperty.call(source, key)) {\n            target[key] = source[key];\n          }\n        }\n      }\n\n      return target;\n    };\n\n    return _extends.apply(this, arguments);\n  }\n\n  var Util = function ($) {\n    // eslint-disable-line no-shadow\n    var TRANSITION_END = 'transitionend';\n    var Util = {\n      // eslint-disable-line no-shadow\n      TRANSITION_END: 'mmTransitionEnd',\n      triggerTransitionEnd: function triggerTransitionEnd(element) {\n        $(element).trigger(TRANSITION_END);\n      },\n      supportsTransitionEnd: function supportsTransitionEnd() {\n        return Boolean(TRANSITION_END);\n      }\n    };\n\n    function getSpecialTransitionEndEvent() {\n      return {\n        bindType: TRANSITION_END,\n        delegateType: TRANSITION_END,\n        handle: function handle(event) {\n          if ($(event.target).is(this)) {\n            return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params\n          }\n\n          return undefined;\n        }\n      };\n    }\n\n    function transitionEndEmulator(duration) {\n      var _this = this;\n\n      var called = false;\n      $(this).one(Util.TRANSITION_END, function () {\n        called = true;\n      });\n      setTimeout(function () {\n        if (!called) {\n          Util.triggerTransitionEnd(_this);\n        }\n      }, duration);\n      return this;\n    }\n\n    function setTransitionEndSupport() {\n      $.fn.mmEmulateTransitionEnd = transitionEndEmulator; // eslint-disable-line no-param-reassign\n      // eslint-disable-next-line no-param-reassign\n\n      $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent();\n    }\n\n    setTransitionEndSupport();\n    return Util;\n  }($);\n\n  var NAME = 'metisMenu';\n  var DATA_KEY = 'metisMenu';\n  var EVENT_KEY = \".\" + DATA_KEY;\n  var DATA_API_KEY = '.data-api';\n  var JQUERY_NO_CONFLICT = $.fn[NAME];\n  var TRANSITION_DURATION = 350;\n  var Default = {\n    toggle: true,\n    preventDefault: true,\n    triggerElement: 'a',\n    parentTrigger: 'li',\n    subMenu: 'ul'\n  };\n  var Event = {\n    SHOW: \"show\" + EVENT_KEY,\n    SHOWN: \"shown\" + EVENT_KEY,\n    HIDE: \"hide\" + EVENT_KEY,\n    HIDDEN: \"hidden\" + EVENT_KEY,\n    CLICK_DATA_API: \"click\" + EVENT_KEY + DATA_API_KEY\n  };\n  var ClassName = {\n    METIS: 'metismenu',\n    ACTIVE: 'mm-active',\n    SHOW: 'mm-show',\n    COLLAPSE: 'mm-collapse',\n    COLLAPSING: 'mm-collapsing',\n    COLLAPSED: 'mm-collapsed'\n  };\n\n  var MetisMenu = /*#__PURE__*/function () {\n    // eslint-disable-line no-shadow\n    function MetisMenu(element, config) {\n      this.element = element;\n      this.config = _extends({}, Default, {}, config);\n      this.transitioning = null;\n      this.init();\n    }\n\n    var _proto = MetisMenu.prototype;\n\n    _proto.init = function init() {\n      var self = this;\n      var conf = this.config;\n      var el = $(this.element);\n      el.addClass(ClassName.METIS); // add metismenu class to element\n\n      el.find(conf.parentTrigger + \".\" + ClassName.ACTIVE).children(conf.triggerElement).attr('aria-expanded', 'true'); // add attribute aria-expanded=true the trigger element\n\n      el.find(conf.parentTrigger + \".\" + ClassName.ACTIVE).parents(conf.parentTrigger).addClass(ClassName.ACTIVE);\n      el.find(conf.parentTrigger + \".\" + ClassName.ACTIVE).parents(conf.parentTrigger).children(conf.triggerElement).attr('aria-expanded', 'true'); // add attribute aria-expanded=true the triggers of all parents\n\n      el.find(conf.parentTrigger + \".\" + ClassName.ACTIVE).has(conf.subMenu).children(conf.subMenu).addClass(ClassName.COLLAPSE + \" \" + ClassName.SHOW);\n      el.find(conf.parentTrigger).not(\".\" + ClassName.ACTIVE).has(conf.subMenu).children(conf.subMenu).addClass(ClassName.COLLAPSE);\n      el.find(conf.parentTrigger) // .has(conf.subMenu)\n      .children(conf.triggerElement).on(Event.CLICK_DATA_API, function (e) {\n        // eslint-disable-line func-names\n        var eTar = $(this);\n\n        if (eTar.attr('aria-disabled') === 'true') {\n          return;\n        }\n\n        if (conf.preventDefault && eTar.attr('href') === '#') {\n          e.preventDefault();\n        }\n\n        var paRent = eTar.parent(conf.parentTrigger);\n        var sibLi = paRent.siblings(conf.parentTrigger);\n        var sibTrigger = sibLi.children(conf.triggerElement);\n\n        if (paRent.hasClass(ClassName.ACTIVE)) {\n          eTar.attr('aria-expanded', 'false');\n          self.removeActive(paRent);\n        } else {\n          eTar.attr('aria-expanded', 'true');\n          self.setActive(paRent);\n\n          if (conf.toggle) {\n            self.removeActive(sibLi);\n            sibTrigger.attr('aria-expanded', 'false');\n          }\n        }\n\n        if (conf.onTransitionStart) {\n          conf.onTransitionStart(e);\n        }\n      });\n    };\n\n    _proto.setActive = function setActive(li) {\n      $(li).addClass(ClassName.ACTIVE);\n      var ul = $(li).children(this.config.subMenu);\n\n      if (ul.length > 0 && !ul.hasClass(ClassName.SHOW)) {\n        this.show(ul);\n      }\n    };\n\n    _proto.removeActive = function removeActive(li) {\n      $(li).removeClass(ClassName.ACTIVE);\n      var ul = $(li).children(this.config.subMenu + \".\" + ClassName.SHOW);\n\n      if (ul.length > 0) {\n        this.hide(ul);\n      }\n    };\n\n    _proto.show = function show(element) {\n      var _this = this;\n\n      if (this.transitioning || $(element).hasClass(ClassName.COLLAPSING)) {\n        return;\n      }\n\n      var elem = $(element);\n      var startEvent = $.Event(Event.SHOW);\n      elem.trigger(startEvent);\n\n      if (startEvent.isDefaultPrevented()) {\n        return;\n      }\n\n      elem.parent(this.config.parentTrigger).addClass(ClassName.ACTIVE);\n\n      if (this.config.toggle) {\n        var toggleElem = elem.parent(this.config.parentTrigger).siblings().children(this.config.subMenu + \".\" + ClassName.SHOW);\n        this.hide(toggleElem);\n      }\n\n      elem.removeClass(ClassName.COLLAPSE).addClass(ClassName.COLLAPSING).height(0);\n      this.setTransitioning(true);\n\n      var complete = function complete() {\n        // check if disposed\n        if (!_this.config || !_this.element) {\n          return;\n        }\n\n        elem.removeClass(ClassName.COLLAPSING).addClass(ClassName.COLLAPSE + \" \" + ClassName.SHOW).height('');\n\n        _this.setTransitioning(false);\n\n        elem.trigger(Event.SHOWN);\n      };\n\n      elem.height(element[0].scrollHeight).one(Util.TRANSITION_END, complete).mmEmulateTransitionEnd(TRANSITION_DURATION);\n    };\n\n    _proto.hide = function hide(element) {\n      var _this2 = this;\n\n      if (this.transitioning || !$(element).hasClass(ClassName.SHOW)) {\n        return;\n      }\n\n      var elem = $(element);\n      var startEvent = $.Event(Event.HIDE);\n      elem.trigger(startEvent);\n\n      if (startEvent.isDefaultPrevented()) {\n        return;\n      }\n\n      elem.parent(this.config.parentTrigger).removeClass(ClassName.ACTIVE); // eslint-disable-next-line no-unused-expressions\n\n      elem.height(elem.height())[0].offsetHeight;\n      elem.addClass(ClassName.COLLAPSING).removeClass(ClassName.COLLAPSE).removeClass(ClassName.SHOW);\n      this.setTransitioning(true);\n\n      var complete = function complete() {\n        // check if disposed\n        if (!_this2.config || !_this2.element) {\n          return;\n        }\n\n        if (_this2.transitioning && _this2.config.onTransitionEnd) {\n          _this2.config.onTransitionEnd();\n        }\n\n        _this2.setTransitioning(false);\n\n        elem.trigger(Event.HIDDEN);\n        elem.removeClass(ClassName.COLLAPSING).addClass(ClassName.COLLAPSE);\n      };\n\n      if (elem.height() === 0 || elem.css('display') === 'none') {\n        complete();\n      } else {\n        elem.height(0).one(Util.TRANSITION_END, complete).mmEmulateTransitionEnd(TRANSITION_DURATION);\n      }\n    };\n\n    _proto.setTransitioning = function setTransitioning(isTransitioning) {\n      this.transitioning = isTransitioning;\n    };\n\n    _proto.dispose = function dispose() {\n      $.removeData(this.element, DATA_KEY);\n      $(this.element).find(this.config.parentTrigger) // .has(this.config.subMenu)\n      .children(this.config.triggerElement).off(Event.CLICK_DATA_API);\n      this.transitioning = null;\n      this.config = null;\n      this.element = null;\n    };\n\n    MetisMenu.jQueryInterface = function jQueryInterface(config) {\n      // eslint-disable-next-line func-names\n      return this.each(function () {\n        var $this = $(this);\n        var data = $this.data(DATA_KEY);\n\n        var conf = _extends({}, Default, {}, $this.data(), {}, typeof config === 'object' && config ? config : {});\n\n        if (!data) {\n          data = new MetisMenu(this, conf);\n          $this.data(DATA_KEY, data);\n        }\n\n        if (typeof config === 'string') {\n          if (data[config] === undefined) {\n            throw new Error(\"No method named \\\"\" + config + \"\\\"\");\n          }\n\n          data[config]();\n        }\n      });\n    };\n\n    return MetisMenu;\n  }();\n  /**\n   * ------------------------------------------------------------------------\n   * jQuery\n   * ------------------------------------------------------------------------\n   */\n\n\n  $.fn[NAME] = MetisMenu.jQueryInterface; // eslint-disable-line no-param-reassign\n\n  $.fn[NAME].Constructor = MetisMenu; // eslint-disable-line no-param-reassign\n\n  $.fn[NAME].noConflict = function () {\n    // eslint-disable-line no-param-reassign\n    $.fn[NAME] = JQUERY_NO_CONFLICT; // eslint-disable-line no-param-reassign\n\n    return MetisMenu.jQueryInterface;\n  };\n\n  return MetisMenu;\n\n})));\n//# sourceMappingURL=metisMenu.js.map\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/moment.js",
    "content": "//! moment.js\n//! version : 2.29.1\n//! authors : Tim Wood, Iskren Chernev, Moment.js contributors\n//! license : MIT\n//! momentjs.com\n\n;(function (global, factory) {\n    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n    typeof define === 'function' && define.amd ? define(factory) :\n    global.moment = factory()\n}(this, (function () { 'use strict';\n\n    var hookCallback;\n\n    function hooks() {\n        return hookCallback.apply(null, arguments);\n    }\n\n    // This is done to register the method called with moment()\n    // without creating circular dependencies.\n    function setHookCallback(callback) {\n        hookCallback = callback;\n    }\n\n    function isArray(input) {\n        return (\n            input instanceof Array ||\n            Object.prototype.toString.call(input) === '[object Array]'\n        );\n    }\n\n    function isObject(input) {\n        // IE8 will treat undefined and null as object if it wasn't for\n        // input != null\n        return (\n            input != null &&\n            Object.prototype.toString.call(input) === '[object Object]'\n        );\n    }\n\n    function hasOwnProp(a, b) {\n        return Object.prototype.hasOwnProperty.call(a, b);\n    }\n\n    function isObjectEmpty(obj) {\n        if (Object.getOwnPropertyNames) {\n            return Object.getOwnPropertyNames(obj).length === 0;\n        } else {\n            var k;\n            for (k in obj) {\n                if (hasOwnProp(obj, k)) {\n                    return false;\n                }\n            }\n            return true;\n        }\n    }\n\n    function isUndefined(input) {\n        return input === void 0;\n    }\n\n    function isNumber(input) {\n        return (\n            typeof input === 'number' ||\n            Object.prototype.toString.call(input) === '[object Number]'\n        );\n    }\n\n    function isDate(input) {\n        return (\n            input instanceof Date ||\n            Object.prototype.toString.call(input) === '[object Date]'\n        );\n    }\n\n    function map(arr, fn) {\n        var res = [],\n            i;\n        for (i = 0; i < arr.length; ++i) {\n            res.push(fn(arr[i], i));\n        }\n        return res;\n    }\n\n    function extend(a, b) {\n        for (var i in b) {\n            if (hasOwnProp(b, i)) {\n                a[i] = b[i];\n            }\n        }\n\n        if (hasOwnProp(b, 'toString')) {\n            a.toString = b.toString;\n        }\n\n        if (hasOwnProp(b, 'valueOf')) {\n            a.valueOf = b.valueOf;\n        }\n\n        return a;\n    }\n\n    function createUTC(input, format, locale, strict) {\n        return createLocalOrUTC(input, format, locale, strict, true).utc();\n    }\n\n    function defaultParsingFlags() {\n        // We need to deep clone this object.\n        return {\n            empty: false,\n            unusedTokens: [],\n            unusedInput: [],\n            overflow: -2,\n            charsLeftOver: 0,\n            nullInput: false,\n            invalidEra: null,\n            invalidMonth: null,\n            invalidFormat: false,\n            userInvalidated: false,\n            iso: false,\n            parsedDateParts: [],\n            era: null,\n            meridiem: null,\n            rfc2822: false,\n            weekdayMismatch: false,\n        };\n    }\n\n    function getParsingFlags(m) {\n        if (m._pf == null) {\n            m._pf = defaultParsingFlags();\n        }\n        return m._pf;\n    }\n\n    var some;\n    if (Array.prototype.some) {\n        some = Array.prototype.some;\n    } else {\n        some = function (fun) {\n            var t = Object(this),\n                len = t.length >>> 0,\n                i;\n\n            for (i = 0; i < len; i++) {\n                if (i in t && fun.call(this, t[i], i, t)) {\n                    return true;\n                }\n            }\n\n            return false;\n        };\n    }\n\n    function isValid(m) {\n        if (m._isValid == null) {\n            var flags = getParsingFlags(m),\n                parsedParts = some.call(flags.parsedDateParts, function (i) {\n                    return i != null;\n                }),\n                isNowValid =\n                    !isNaN(m._d.getTime()) &&\n                    flags.overflow < 0 &&\n                    !flags.empty &&\n                    !flags.invalidEra &&\n                    !flags.invalidMonth &&\n                    !flags.invalidWeekday &&\n                    !flags.weekdayMismatch &&\n                    !flags.nullInput &&\n                    !flags.invalidFormat &&\n                    !flags.userInvalidated &&\n                    (!flags.meridiem || (flags.meridiem && parsedParts));\n\n            if (m._strict) {\n                isNowValid =\n                    isNowValid &&\n                    flags.charsLeftOver === 0 &&\n                    flags.unusedTokens.length === 0 &&\n                    flags.bigHour === undefined;\n            }\n\n            if (Object.isFrozen == null || !Object.isFrozen(m)) {\n                m._isValid = isNowValid;\n            } else {\n                return isNowValid;\n            }\n        }\n        return m._isValid;\n    }\n\n    function createInvalid(flags) {\n        var m = createUTC(NaN);\n        if (flags != null) {\n            extend(getParsingFlags(m), flags);\n        } else {\n            getParsingFlags(m).userInvalidated = true;\n        }\n\n        return m;\n    }\n\n    // Plugins that add properties should also add the key here (null value),\n    // so we can properly clone ourselves.\n    var momentProperties = (hooks.momentProperties = []),\n        updateInProgress = false;\n\n    function copyConfig(to, from) {\n        var i, prop, val;\n\n        if (!isUndefined(from._isAMomentObject)) {\n            to._isAMomentObject = from._isAMomentObject;\n        }\n        if (!isUndefined(from._i)) {\n            to._i = from._i;\n        }\n        if (!isUndefined(from._f)) {\n            to._f = from._f;\n        }\n        if (!isUndefined(from._l)) {\n            to._l = from._l;\n        }\n        if (!isUndefined(from._strict)) {\n            to._strict = from._strict;\n        }\n        if (!isUndefined(from._tzm)) {\n            to._tzm = from._tzm;\n        }\n        if (!isUndefined(from._isUTC)) {\n            to._isUTC = from._isUTC;\n        }\n        if (!isUndefined(from._offset)) {\n            to._offset = from._offset;\n        }\n        if (!isUndefined(from._pf)) {\n            to._pf = getParsingFlags(from);\n        }\n        if (!isUndefined(from._locale)) {\n            to._locale = from._locale;\n        }\n\n        if (momentProperties.length > 0) {\n            for (i = 0; i < momentProperties.length; i++) {\n                prop = momentProperties[i];\n                val = from[prop];\n                if (!isUndefined(val)) {\n                    to[prop] = val;\n                }\n            }\n        }\n\n        return to;\n    }\n\n    // Moment prototype object\n    function Moment(config) {\n        copyConfig(this, config);\n        this._d = new Date(config._d != null ? config._d.getTime() : NaN);\n        if (!this.isValid()) {\n            this._d = new Date(NaN);\n        }\n        // Prevent infinite loop in case updateOffset creates new moment\n        // objects.\n        if (updateInProgress === false) {\n            updateInProgress = true;\n            hooks.updateOffset(this);\n            updateInProgress = false;\n        }\n    }\n\n    function isMoment(obj) {\n        return (\n            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)\n        );\n    }\n\n    function warn(msg) {\n        if (\n            hooks.suppressDeprecationWarnings === false &&\n            typeof console !== 'undefined' &&\n            console.warn\n        ) {\n            console.warn('Deprecation warning: ' + msg);\n        }\n    }\n\n    function deprecate(msg, fn) {\n        var firstTime = true;\n\n        return extend(function () {\n            if (hooks.deprecationHandler != null) {\n                hooks.deprecationHandler(null, msg);\n            }\n            if (firstTime) {\n                var args = [],\n                    arg,\n                    i,\n                    key;\n                for (i = 0; i < arguments.length; i++) {\n                    arg = '';\n                    if (typeof arguments[i] === 'object') {\n                        arg += '\\n[' + i + '] ';\n                        for (key in arguments[0]) {\n                            if (hasOwnProp(arguments[0], key)) {\n                                arg += key + ': ' + arguments[0][key] + ', ';\n                            }\n                        }\n                        arg = arg.slice(0, -2); // Remove trailing comma and space\n                    } else {\n                        arg = arguments[i];\n                    }\n                    args.push(arg);\n                }\n                warn(\n                    msg +\n                        '\\nArguments: ' +\n                        Array.prototype.slice.call(args).join('') +\n                        '\\n' +\n                        new Error().stack\n                );\n                firstTime = false;\n            }\n            return fn.apply(this, arguments);\n        }, fn);\n    }\n\n    var deprecations = {};\n\n    function deprecateSimple(name, msg) {\n        if (hooks.deprecationHandler != null) {\n            hooks.deprecationHandler(name, msg);\n        }\n        if (!deprecations[name]) {\n            warn(msg);\n            deprecations[name] = true;\n        }\n    }\n\n    hooks.suppressDeprecationWarnings = false;\n    hooks.deprecationHandler = null;\n\n    function isFunction(input) {\n        return (\n            (typeof Function !== 'undefined' && input instanceof Function) ||\n            Object.prototype.toString.call(input) === '[object Function]'\n        );\n    }\n\n    function set(config) {\n        var prop, i;\n        for (i in config) {\n            if (hasOwnProp(config, i)) {\n                prop = config[i];\n                if (isFunction(prop)) {\n                    this[i] = prop;\n                } else {\n                    this['_' + i] = prop;\n                }\n            }\n        }\n        this._config = config;\n        // Lenient ordinal parsing accepts just a number in addition to\n        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.\n        // TODO: Remove \"ordinalParse\" fallback in next major release.\n        this._dayOfMonthOrdinalParseLenient = new RegExp(\n            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +\n                '|' +\n                /\\d{1,2}/.source\n        );\n    }\n\n    function mergeConfigs(parentConfig, childConfig) {\n        var res = extend({}, parentConfig),\n            prop;\n        for (prop in childConfig) {\n            if (hasOwnProp(childConfig, prop)) {\n                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {\n                    res[prop] = {};\n                    extend(res[prop], parentConfig[prop]);\n                    extend(res[prop], childConfig[prop]);\n                } else if (childConfig[prop] != null) {\n                    res[prop] = childConfig[prop];\n                } else {\n                    delete res[prop];\n                }\n            }\n        }\n        for (prop in parentConfig) {\n            if (\n                hasOwnProp(parentConfig, prop) &&\n                !hasOwnProp(childConfig, prop) &&\n                isObject(parentConfig[prop])\n            ) {\n                // make sure changes to properties don't modify parent config\n                res[prop] = extend({}, res[prop]);\n            }\n        }\n        return res;\n    }\n\n    function Locale(config) {\n        if (config != null) {\n            this.set(config);\n        }\n    }\n\n    var keys;\n\n    if (Object.keys) {\n        keys = Object.keys;\n    } else {\n        keys = function (obj) {\n            var i,\n                res = [];\n            for (i in obj) {\n                if (hasOwnProp(obj, i)) {\n                    res.push(i);\n                }\n            }\n            return res;\n        };\n    }\n\n    var defaultCalendar = {\n        sameDay: '[Today at] LT',\n        nextDay: '[Tomorrow at] LT',\n        nextWeek: 'dddd [at] LT',\n        lastDay: '[Yesterday at] LT',\n        lastWeek: '[Last] dddd [at] LT',\n        sameElse: 'L',\n    };\n\n    function calendar(key, mom, now) {\n        var output = this._calendar[key] || this._calendar['sameElse'];\n        return isFunction(output) ? output.call(mom, now) : output;\n    }\n\n    function zeroFill(number, targetLength, forceSign) {\n        var absNumber = '' + Math.abs(number),\n            zerosToFill = targetLength - absNumber.length,\n            sign = number >= 0;\n        return (\n            (sign ? (forceSign ? '+' : '') : '-') +\n            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +\n            absNumber\n        );\n    }\n\n    var formattingTokens = /(\\[[^\\[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,\n        localFormattingTokens = /(\\[[^\\[]*\\])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g,\n        formatFunctions = {},\n        formatTokenFunctions = {};\n\n    // token:    'M'\n    // padded:   ['MM', 2]\n    // ordinal:  'Mo'\n    // callback: function () { this.month() + 1 }\n    function addFormatToken(token, padded, ordinal, callback) {\n        var func = callback;\n        if (typeof callback === 'string') {\n            func = function () {\n                return this[callback]();\n            };\n        }\n        if (token) {\n            formatTokenFunctions[token] = func;\n        }\n        if (padded) {\n            formatTokenFunctions[padded[0]] = function () {\n                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);\n            };\n        }\n        if (ordinal) {\n            formatTokenFunctions[ordinal] = function () {\n                return this.localeData().ordinal(\n                    func.apply(this, arguments),\n                    token\n                );\n            };\n        }\n    }\n\n    function removeFormattingTokens(input) {\n        if (input.match(/\\[[\\s\\S]/)) {\n            return input.replace(/^\\[|\\]$/g, '');\n        }\n        return input.replace(/\\\\/g, '');\n    }\n\n    function makeFormatFunction(format) {\n        var array = format.match(formattingTokens),\n            i,\n            length;\n\n        for (i = 0, length = array.length; i < length; i++) {\n            if (formatTokenFunctions[array[i]]) {\n                array[i] = formatTokenFunctions[array[i]];\n            } else {\n                array[i] = removeFormattingTokens(array[i]);\n            }\n        }\n\n        return function (mom) {\n            var output = '',\n                i;\n            for (i = 0; i < length; i++) {\n                output += isFunction(array[i])\n                    ? array[i].call(mom, format)\n                    : array[i];\n            }\n            return output;\n        };\n    }\n\n    // format date using native date object\n    function formatMoment(m, format) {\n        if (!m.isValid()) {\n            return m.localeData().invalidDate();\n        }\n\n        format = expandFormat(format, m.localeData());\n        formatFunctions[format] =\n            formatFunctions[format] || makeFormatFunction(format);\n\n        return formatFunctions[format](m);\n    }\n\n    function expandFormat(format, locale) {\n        var i = 5;\n\n        function replaceLongDateFormatTokens(input) {\n            return locale.longDateFormat(input) || input;\n        }\n\n        localFormattingTokens.lastIndex = 0;\n        while (i >= 0 && localFormattingTokens.test(format)) {\n            format = format.replace(\n                localFormattingTokens,\n                replaceLongDateFormatTokens\n            );\n            localFormattingTokens.lastIndex = 0;\n            i -= 1;\n        }\n\n        return format;\n    }\n\n    var defaultLongDateFormat = {\n        LTS: 'h:mm:ss A',\n        LT: 'h:mm A',\n        L: 'MM/DD/YYYY',\n        LL: 'MMMM D, YYYY',\n        LLL: 'MMMM D, YYYY h:mm A',\n        LLLL: 'dddd, MMMM D, YYYY h:mm A',\n    };\n\n    function longDateFormat(key) {\n        var format = this._longDateFormat[key],\n            formatUpper = this._longDateFormat[key.toUpperCase()];\n\n        if (format || !formatUpper) {\n            return format;\n        }\n\n        this._longDateFormat[key] = formatUpper\n            .match(formattingTokens)\n            .map(function (tok) {\n                if (\n                    tok === 'MMMM' ||\n                    tok === 'MM' ||\n                    tok === 'DD' ||\n                    tok === 'dddd'\n                ) {\n                    return tok.slice(1);\n                }\n                return tok;\n            })\n            .join('');\n\n        return this._longDateFormat[key];\n    }\n\n    var defaultInvalidDate = 'Invalid date';\n\n    function invalidDate() {\n        return this._invalidDate;\n    }\n\n    var defaultOrdinal = '%d',\n        defaultDayOfMonthOrdinalParse = /\\d{1,2}/;\n\n    function ordinal(number) {\n        return this._ordinal.replace('%d', number);\n    }\n\n    var defaultRelativeTime = {\n        future: 'in %s',\n        past: '%s ago',\n        s: 'a few seconds',\n        ss: '%d seconds',\n        m: 'a minute',\n        mm: '%d minutes',\n        h: 'an hour',\n        hh: '%d hours',\n        d: 'a day',\n        dd: '%d days',\n        w: 'a week',\n        ww: '%d weeks',\n        M: 'a month',\n        MM: '%d months',\n        y: 'a year',\n        yy: '%d years',\n    };\n\n    function relativeTime(number, withoutSuffix, string, isFuture) {\n        var output = this._relativeTime[string];\n        return isFunction(output)\n            ? output(number, withoutSuffix, string, isFuture)\n            : output.replace(/%d/i, number);\n    }\n\n    function pastFuture(diff, output) {\n        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];\n        return isFunction(format) ? format(output) : format.replace(/%s/i, output);\n    }\n\n    var aliases = {};\n\n    function addUnitAlias(unit, shorthand) {\n        var lowerCase = unit.toLowerCase();\n        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;\n    }\n\n    function normalizeUnits(units) {\n        return typeof units === 'string'\n            ? aliases[units] || aliases[units.toLowerCase()]\n            : undefined;\n    }\n\n    function normalizeObjectUnits(inputObject) {\n        var normalizedInput = {},\n            normalizedProp,\n            prop;\n\n        for (prop in inputObject) {\n            if (hasOwnProp(inputObject, prop)) {\n                normalizedProp = normalizeUnits(prop);\n                if (normalizedProp) {\n                    normalizedInput[normalizedProp] = inputObject[prop];\n                }\n            }\n        }\n\n        return normalizedInput;\n    }\n\n    var priorities = {};\n\n    function addUnitPriority(unit, priority) {\n        priorities[unit] = priority;\n    }\n\n    function getPrioritizedUnits(unitsObj) {\n        var units = [],\n            u;\n        for (u in unitsObj) {\n            if (hasOwnProp(unitsObj, u)) {\n                units.push({ unit: u, priority: priorities[u] });\n            }\n        }\n        units.sort(function (a, b) {\n            return a.priority - b.priority;\n        });\n        return units;\n    }\n\n    function isLeapYear(year) {\n        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n    }\n\n    function absFloor(number) {\n        if (number < 0) {\n            // -0 -> 0\n            return Math.ceil(number) || 0;\n        } else {\n            return Math.floor(number);\n        }\n    }\n\n    function toInt(argumentForCoercion) {\n        var coercedNumber = +argumentForCoercion,\n            value = 0;\n\n        if (coercedNumber !== 0 && isFinite(coercedNumber)) {\n            value = absFloor(coercedNumber);\n        }\n\n        return value;\n    }\n\n    function makeGetSet(unit, keepTime) {\n        return function (value) {\n            if (value != null) {\n                set$1(this, unit, value);\n                hooks.updateOffset(this, keepTime);\n                return this;\n            } else {\n                return get(this, unit);\n            }\n        };\n    }\n\n    function get(mom, unit) {\n        return mom.isValid()\n            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()\n            : NaN;\n    }\n\n    function set$1(mom, unit, value) {\n        if (mom.isValid() && !isNaN(value)) {\n            if (\n                unit === 'FullYear' &&\n                isLeapYear(mom.year()) &&\n                mom.month() === 1 &&\n                mom.date() === 29\n            ) {\n                value = toInt(value);\n                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](\n                    value,\n                    mom.month(),\n                    daysInMonth(value, mom.month())\n                );\n            } else {\n                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);\n            }\n        }\n    }\n\n    // MOMENTS\n\n    function stringGet(units) {\n        units = normalizeUnits(units);\n        if (isFunction(this[units])) {\n            return this[units]();\n        }\n        return this;\n    }\n\n    function stringSet(units, value) {\n        if (typeof units === 'object') {\n            units = normalizeObjectUnits(units);\n            var prioritized = getPrioritizedUnits(units),\n                i;\n            for (i = 0; i < prioritized.length; i++) {\n                this[prioritized[i].unit](units[prioritized[i].unit]);\n            }\n        } else {\n            units = normalizeUnits(units);\n            if (isFunction(this[units])) {\n                return this[units](value);\n            }\n        }\n        return this;\n    }\n\n    var match1 = /\\d/, //       0 - 9\n        match2 = /\\d\\d/, //      00 - 99\n        match3 = /\\d{3}/, //     000 - 999\n        match4 = /\\d{4}/, //    0000 - 9999\n        match6 = /[+-]?\\d{6}/, // -999999 - 999999\n        match1to2 = /\\d\\d?/, //       0 - 99\n        match3to4 = /\\d\\d\\d\\d?/, //     999 - 9999\n        match5to6 = /\\d\\d\\d\\d\\d\\d?/, //   99999 - 999999\n        match1to3 = /\\d{1,3}/, //       0 - 999\n        match1to4 = /\\d{1,4}/, //       0 - 9999\n        match1to6 = /[+-]?\\d{1,6}/, // -999999 - 999999\n        matchUnsigned = /\\d+/, //       0 - inf\n        matchSigned = /[+-]?\\d+/, //    -inf - inf\n        matchOffset = /Z|[+-]\\d\\d:?\\d\\d/gi, // +00:00 -00:00 +0000 -0000 or Z\n        matchShortOffset = /Z|[+-]\\d\\d(?::?\\d\\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z\n        matchTimestamp = /[+-]?\\d+(\\.\\d{1,3})?/, // 123456789 123456789.123\n        // any word (or two) characters or numbers including two/three word month in arabic.\n        // includes scottish gaelic two word and hyphenated months\n        matchWord = /[0-9]{0,256}['a-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFF07\\uFF10-\\uFFEF]{1,256}|[\\u0600-\\u06FF\\/]{1,256}(\\s*?[\\u0600-\\u06FF]{1,256}){1,2}/i,\n        regexes;\n\n    regexes = {};\n\n    function addRegexToken(token, regex, strictRegex) {\n        regexes[token] = isFunction(regex)\n            ? regex\n            : function (isStrict, localeData) {\n                  return isStrict && strictRegex ? strictRegex : regex;\n              };\n    }\n\n    function getParseRegexForToken(token, config) {\n        if (!hasOwnProp(regexes, token)) {\n            return new RegExp(unescapeFormat(token));\n        }\n\n        return regexes[token](config._strict, config._locale);\n    }\n\n    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript\n    function unescapeFormat(s) {\n        return regexEscape(\n            s\n                .replace('\\\\', '')\n                .replace(/\\\\(\\[)|\\\\(\\])|\\[([^\\]\\[]*)\\]|\\\\(.)/g, function (\n                    matched,\n                    p1,\n                    p2,\n                    p3,\n                    p4\n                ) {\n                    return p1 || p2 || p3 || p4;\n                })\n        );\n    }\n\n    function regexEscape(s) {\n        return s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n    }\n\n    var tokens = {};\n\n    function addParseToken(token, callback) {\n        var i,\n            func = callback;\n        if (typeof token === 'string') {\n            token = [token];\n        }\n        if (isNumber(callback)) {\n            func = function (input, array) {\n                array[callback] = toInt(input);\n            };\n        }\n        for (i = 0; i < token.length; i++) {\n            tokens[token[i]] = func;\n        }\n    }\n\n    function addWeekParseToken(token, callback) {\n        addParseToken(token, function (input, array, config, token) {\n            config._w = config._w || {};\n            callback(input, config._w, config, token);\n        });\n    }\n\n    function addTimeToArrayFromToken(token, input, config) {\n        if (input != null && hasOwnProp(tokens, token)) {\n            tokens[token](input, config._a, config, token);\n        }\n    }\n\n    var YEAR = 0,\n        MONTH = 1,\n        DATE = 2,\n        HOUR = 3,\n        MINUTE = 4,\n        SECOND = 5,\n        MILLISECOND = 6,\n        WEEK = 7,\n        WEEKDAY = 8;\n\n    function mod(n, x) {\n        return ((n % x) + x) % x;\n    }\n\n    var indexOf;\n\n    if (Array.prototype.indexOf) {\n        indexOf = Array.prototype.indexOf;\n    } else {\n        indexOf = function (o) {\n            // I know\n            var i;\n            for (i = 0; i < this.length; ++i) {\n                if (this[i] === o) {\n                    return i;\n                }\n            }\n            return -1;\n        };\n    }\n\n    function daysInMonth(year, month) {\n        if (isNaN(year) || isNaN(month)) {\n            return NaN;\n        }\n        var modMonth = mod(month, 12);\n        year += (month - modMonth) / 12;\n        return modMonth === 1\n            ? isLeapYear(year)\n                ? 29\n                : 28\n            : 31 - ((modMonth % 7) % 2);\n    }\n\n    // FORMATTING\n\n    addFormatToken('M', ['MM', 2], 'Mo', function () {\n        return this.month() + 1;\n    });\n\n    addFormatToken('MMM', 0, 0, function (format) {\n        return this.localeData().monthsShort(this, format);\n    });\n\n    addFormatToken('MMMM', 0, 0, function (format) {\n        return this.localeData().months(this, format);\n    });\n\n    // ALIASES\n\n    addUnitAlias('month', 'M');\n\n    // PRIORITY\n\n    addUnitPriority('month', 8);\n\n    // PARSING\n\n    addRegexToken('M', match1to2);\n    addRegexToken('MM', match1to2, match2);\n    addRegexToken('MMM', function (isStrict, locale) {\n        return locale.monthsShortRegex(isStrict);\n    });\n    addRegexToken('MMMM', function (isStrict, locale) {\n        return locale.monthsRegex(isStrict);\n    });\n\n    addParseToken(['M', 'MM'], function (input, array) {\n        array[MONTH] = toInt(input) - 1;\n    });\n\n    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {\n        var month = config._locale.monthsParse(input, token, config._strict);\n        // if we didn't find a month name, mark the date as invalid.\n        if (month != null) {\n            array[MONTH] = month;\n        } else {\n            getParsingFlags(config).invalidMonth = input;\n        }\n    });\n\n    // LOCALES\n\n    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split(\n            '_'\n        ),\n        defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split(\n            '_'\n        ),\n        MONTHS_IN_FORMAT = /D[oD]?(\\[[^\\[\\]]*\\]|\\s)+MMMM?/,\n        defaultMonthsShortRegex = matchWord,\n        defaultMonthsRegex = matchWord;\n\n    function localeMonths(m, format) {\n        if (!m) {\n            return isArray(this._months)\n                ? this._months\n                : this._months['standalone'];\n        }\n        return isArray(this._months)\n            ? this._months[m.month()]\n            : this._months[\n                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)\n                      ? 'format'\n                      : 'standalone'\n              ][m.month()];\n    }\n\n    function localeMonthsShort(m, format) {\n        if (!m) {\n            return isArray(this._monthsShort)\n                ? this._monthsShort\n                : this._monthsShort['standalone'];\n        }\n        return isArray(this._monthsShort)\n            ? this._monthsShort[m.month()]\n            : this._monthsShort[\n                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'\n              ][m.month()];\n    }\n\n    function handleStrictParse(monthName, format, strict) {\n        var i,\n            ii,\n            mom,\n            llc = monthName.toLocaleLowerCase();\n        if (!this._monthsParse) {\n            // this is not used\n            this._monthsParse = [];\n            this._longMonthsParse = [];\n            this._shortMonthsParse = [];\n            for (i = 0; i < 12; ++i) {\n                mom = createUTC([2000, i]);\n                this._shortMonthsParse[i] = this.monthsShort(\n                    mom,\n                    ''\n                ).toLocaleLowerCase();\n                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();\n            }\n        }\n\n        if (strict) {\n            if (format === 'MMM') {\n                ii = indexOf.call(this._shortMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._longMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        } else {\n            if (format === 'MMM') {\n                ii = indexOf.call(this._shortMonthsParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._longMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._longMonthsParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._shortMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        }\n    }\n\n    function localeMonthsParse(monthName, format, strict) {\n        var i, mom, regex;\n\n        if (this._monthsParseExact) {\n            return handleStrictParse.call(this, monthName, format, strict);\n        }\n\n        if (!this._monthsParse) {\n            this._monthsParse = [];\n            this._longMonthsParse = [];\n            this._shortMonthsParse = [];\n        }\n\n        // TODO: add sorting\n        // Sorting makes sure if one month (or abbr) is a prefix of another\n        // see sorting in computeMonthsParse\n        for (i = 0; i < 12; i++) {\n            // make the regex if we don't have it already\n            mom = createUTC([2000, i]);\n            if (strict && !this._longMonthsParse[i]) {\n                this._longMonthsParse[i] = new RegExp(\n                    '^' + this.months(mom, '').replace('.', '') + '$',\n                    'i'\n                );\n                this._shortMonthsParse[i] = new RegExp(\n                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',\n                    'i'\n                );\n            }\n            if (!strict && !this._monthsParse[i]) {\n                regex =\n                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');\n                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');\n            }\n            // test the regex\n            if (\n                strict &&\n                format === 'MMMM' &&\n                this._longMonthsParse[i].test(monthName)\n            ) {\n                return i;\n            } else if (\n                strict &&\n                format === 'MMM' &&\n                this._shortMonthsParse[i].test(monthName)\n            ) {\n                return i;\n            } else if (!strict && this._monthsParse[i].test(monthName)) {\n                return i;\n            }\n        }\n    }\n\n    // MOMENTS\n\n    function setMonth(mom, value) {\n        var dayOfMonth;\n\n        if (!mom.isValid()) {\n            // No op\n            return mom;\n        }\n\n        if (typeof value === 'string') {\n            if (/^\\d+$/.test(value)) {\n                value = toInt(value);\n            } else {\n                value = mom.localeData().monthsParse(value);\n                // TODO: Another silent failure?\n                if (!isNumber(value)) {\n                    return mom;\n                }\n            }\n        }\n\n        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));\n        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);\n        return mom;\n    }\n\n    function getSetMonth(value) {\n        if (value != null) {\n            setMonth(this, value);\n            hooks.updateOffset(this, true);\n            return this;\n        } else {\n            return get(this, 'Month');\n        }\n    }\n\n    function getDaysInMonth() {\n        return daysInMonth(this.year(), this.month());\n    }\n\n    function monthsShortRegex(isStrict) {\n        if (this._monthsParseExact) {\n            if (!hasOwnProp(this, '_monthsRegex')) {\n                computeMonthsParse.call(this);\n            }\n            if (isStrict) {\n                return this._monthsShortStrictRegex;\n            } else {\n                return this._monthsShortRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_monthsShortRegex')) {\n                this._monthsShortRegex = defaultMonthsShortRegex;\n            }\n            return this._monthsShortStrictRegex && isStrict\n                ? this._monthsShortStrictRegex\n                : this._monthsShortRegex;\n        }\n    }\n\n    function monthsRegex(isStrict) {\n        if (this._monthsParseExact) {\n            if (!hasOwnProp(this, '_monthsRegex')) {\n                computeMonthsParse.call(this);\n            }\n            if (isStrict) {\n                return this._monthsStrictRegex;\n            } else {\n                return this._monthsRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_monthsRegex')) {\n                this._monthsRegex = defaultMonthsRegex;\n            }\n            return this._monthsStrictRegex && isStrict\n                ? this._monthsStrictRegex\n                : this._monthsRegex;\n        }\n    }\n\n    function computeMonthsParse() {\n        function cmpLenRev(a, b) {\n            return b.length - a.length;\n        }\n\n        var shortPieces = [],\n            longPieces = [],\n            mixedPieces = [],\n            i,\n            mom;\n        for (i = 0; i < 12; i++) {\n            // make the regex if we don't have it already\n            mom = createUTC([2000, i]);\n            shortPieces.push(this.monthsShort(mom, ''));\n            longPieces.push(this.months(mom, ''));\n            mixedPieces.push(this.months(mom, ''));\n            mixedPieces.push(this.monthsShort(mom, ''));\n        }\n        // Sorting makes sure if one month (or abbr) is a prefix of another it\n        // will match the longer piece.\n        shortPieces.sort(cmpLenRev);\n        longPieces.sort(cmpLenRev);\n        mixedPieces.sort(cmpLenRev);\n        for (i = 0; i < 12; i++) {\n            shortPieces[i] = regexEscape(shortPieces[i]);\n            longPieces[i] = regexEscape(longPieces[i]);\n        }\n        for (i = 0; i < 24; i++) {\n            mixedPieces[i] = regexEscape(mixedPieces[i]);\n        }\n\n        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n        this._monthsShortRegex = this._monthsRegex;\n        this._monthsStrictRegex = new RegExp(\n            '^(' + longPieces.join('|') + ')',\n            'i'\n        );\n        this._monthsShortStrictRegex = new RegExp(\n            '^(' + shortPieces.join('|') + ')',\n            'i'\n        );\n    }\n\n    // FORMATTING\n\n    addFormatToken('Y', 0, 0, function () {\n        var y = this.year();\n        return y <= 9999 ? zeroFill(y, 4) : '+' + y;\n    });\n\n    addFormatToken(0, ['YY', 2], 0, function () {\n        return this.year() % 100;\n    });\n\n    addFormatToken(0, ['YYYY', 4], 0, 'year');\n    addFormatToken(0, ['YYYYY', 5], 0, 'year');\n    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');\n\n    // ALIASES\n\n    addUnitAlias('year', 'y');\n\n    // PRIORITIES\n\n    addUnitPriority('year', 1);\n\n    // PARSING\n\n    addRegexToken('Y', matchSigned);\n    addRegexToken('YY', match1to2, match2);\n    addRegexToken('YYYY', match1to4, match4);\n    addRegexToken('YYYYY', match1to6, match6);\n    addRegexToken('YYYYYY', match1to6, match6);\n\n    addParseToken(['YYYYY', 'YYYYYY'], YEAR);\n    addParseToken('YYYY', function (input, array) {\n        array[YEAR] =\n            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);\n    });\n    addParseToken('YY', function (input, array) {\n        array[YEAR] = hooks.parseTwoDigitYear(input);\n    });\n    addParseToken('Y', function (input, array) {\n        array[YEAR] = parseInt(input, 10);\n    });\n\n    // HELPERS\n\n    function daysInYear(year) {\n        return isLeapYear(year) ? 366 : 365;\n    }\n\n    // HOOKS\n\n    hooks.parseTwoDigitYear = function (input) {\n        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);\n    };\n\n    // MOMENTS\n\n    var getSetYear = makeGetSet('FullYear', true);\n\n    function getIsLeapYear() {\n        return isLeapYear(this.year());\n    }\n\n    function createDate(y, m, d, h, M, s, ms) {\n        // can't just apply() to create a date:\n        // https://stackoverflow.com/q/181348\n        var date;\n        // the date constructor remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            // preserve leap years using a full 400 year cycle, then reset\n            date = new Date(y + 400, m, d, h, M, s, ms);\n            if (isFinite(date.getFullYear())) {\n                date.setFullYear(y);\n            }\n        } else {\n            date = new Date(y, m, d, h, M, s, ms);\n        }\n\n        return date;\n    }\n\n    function createUTCDate(y) {\n        var date, args;\n        // the Date.UTC function remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            args = Array.prototype.slice.call(arguments);\n            // preserve leap years using a full 400 year cycle, then reset\n            args[0] = y + 400;\n            date = new Date(Date.UTC.apply(null, args));\n            if (isFinite(date.getUTCFullYear())) {\n                date.setUTCFullYear(y);\n            }\n        } else {\n            date = new Date(Date.UTC.apply(null, arguments));\n        }\n\n        return date;\n    }\n\n    // start-of-first-week - start-of-year\n    function firstWeekOffset(year, dow, doy) {\n        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)\n            fwd = 7 + dow - doy,\n            // first-week day local weekday -- which local weekday is fwd\n            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;\n\n        return -fwdlw + fwd - 1;\n    }\n\n    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday\n    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {\n        var localWeekday = (7 + weekday - dow) % 7,\n            weekOffset = firstWeekOffset(year, dow, doy),\n            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,\n            resYear,\n            resDayOfYear;\n\n        if (dayOfYear <= 0) {\n            resYear = year - 1;\n            resDayOfYear = daysInYear(resYear) + dayOfYear;\n        } else if (dayOfYear > daysInYear(year)) {\n            resYear = year + 1;\n            resDayOfYear = dayOfYear - daysInYear(year);\n        } else {\n            resYear = year;\n            resDayOfYear = dayOfYear;\n        }\n\n        return {\n            year: resYear,\n            dayOfYear: resDayOfYear,\n        };\n    }\n\n    function weekOfYear(mom, dow, doy) {\n        var weekOffset = firstWeekOffset(mom.year(), dow, doy),\n            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,\n            resWeek,\n            resYear;\n\n        if (week < 1) {\n            resYear = mom.year() - 1;\n            resWeek = week + weeksInYear(resYear, dow, doy);\n        } else if (week > weeksInYear(mom.year(), dow, doy)) {\n            resWeek = week - weeksInYear(mom.year(), dow, doy);\n            resYear = mom.year() + 1;\n        } else {\n            resYear = mom.year();\n            resWeek = week;\n        }\n\n        return {\n            week: resWeek,\n            year: resYear,\n        };\n    }\n\n    function weeksInYear(year, dow, doy) {\n        var weekOffset = firstWeekOffset(year, dow, doy),\n            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);\n        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;\n    }\n\n    // FORMATTING\n\n    addFormatToken('w', ['ww', 2], 'wo', 'week');\n    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');\n\n    // ALIASES\n\n    addUnitAlias('week', 'w');\n    addUnitAlias('isoWeek', 'W');\n\n    // PRIORITIES\n\n    addUnitPriority('week', 5);\n    addUnitPriority('isoWeek', 5);\n\n    // PARSING\n\n    addRegexToken('w', match1to2);\n    addRegexToken('ww', match1to2, match2);\n    addRegexToken('W', match1to2);\n    addRegexToken('WW', match1to2, match2);\n\n    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (\n        input,\n        week,\n        config,\n        token\n    ) {\n        week[token.substr(0, 1)] = toInt(input);\n    });\n\n    // HELPERS\n\n    // LOCALES\n\n    function localeWeek(mom) {\n        return weekOfYear(mom, this._week.dow, this._week.doy).week;\n    }\n\n    var defaultLocaleWeek = {\n        dow: 0, // Sunday is the first day of the week.\n        doy: 6, // The week that contains Jan 6th is the first week of the year.\n    };\n\n    function localeFirstDayOfWeek() {\n        return this._week.dow;\n    }\n\n    function localeFirstDayOfYear() {\n        return this._week.doy;\n    }\n\n    // MOMENTS\n\n    function getSetWeek(input) {\n        var week = this.localeData().week(this);\n        return input == null ? week : this.add((input - week) * 7, 'd');\n    }\n\n    function getSetISOWeek(input) {\n        var week = weekOfYear(this, 1, 4).week;\n        return input == null ? week : this.add((input - week) * 7, 'd');\n    }\n\n    // FORMATTING\n\n    addFormatToken('d', 0, 'do', 'day');\n\n    addFormatToken('dd', 0, 0, function (format) {\n        return this.localeData().weekdaysMin(this, format);\n    });\n\n    addFormatToken('ddd', 0, 0, function (format) {\n        return this.localeData().weekdaysShort(this, format);\n    });\n\n    addFormatToken('dddd', 0, 0, function (format) {\n        return this.localeData().weekdays(this, format);\n    });\n\n    addFormatToken('e', 0, 0, 'weekday');\n    addFormatToken('E', 0, 0, 'isoWeekday');\n\n    // ALIASES\n\n    addUnitAlias('day', 'd');\n    addUnitAlias('weekday', 'e');\n    addUnitAlias('isoWeekday', 'E');\n\n    // PRIORITY\n    addUnitPriority('day', 11);\n    addUnitPriority('weekday', 11);\n    addUnitPriority('isoWeekday', 11);\n\n    // PARSING\n\n    addRegexToken('d', match1to2);\n    addRegexToken('e', match1to2);\n    addRegexToken('E', match1to2);\n    addRegexToken('dd', function (isStrict, locale) {\n        return locale.weekdaysMinRegex(isStrict);\n    });\n    addRegexToken('ddd', function (isStrict, locale) {\n        return locale.weekdaysShortRegex(isStrict);\n    });\n    addRegexToken('dddd', function (isStrict, locale) {\n        return locale.weekdaysRegex(isStrict);\n    });\n\n    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {\n        var weekday = config._locale.weekdaysParse(input, token, config._strict);\n        // if we didn't get a weekday name, mark the date as invalid\n        if (weekday != null) {\n            week.d = weekday;\n        } else {\n            getParsingFlags(config).invalidWeekday = input;\n        }\n    });\n\n    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {\n        week[token] = toInt(input);\n    });\n\n    // HELPERS\n\n    function parseWeekday(input, locale) {\n        if (typeof input !== 'string') {\n            return input;\n        }\n\n        if (!isNaN(input)) {\n            return parseInt(input, 10);\n        }\n\n        input = locale.weekdaysParse(input);\n        if (typeof input === 'number') {\n            return input;\n        }\n\n        return null;\n    }\n\n    function parseIsoWeekday(input, locale) {\n        if (typeof input === 'string') {\n            return locale.weekdaysParse(input) % 7 || 7;\n        }\n        return isNaN(input) ? null : input;\n    }\n\n    // LOCALES\n    function shiftWeekdays(ws, n) {\n        return ws.slice(n, 7).concat(ws.slice(0, n));\n    }\n\n    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split(\n            '_'\n        ),\n        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),\n        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),\n        defaultWeekdaysRegex = matchWord,\n        defaultWeekdaysShortRegex = matchWord,\n        defaultWeekdaysMinRegex = matchWord;\n\n    function localeWeekdays(m, format) {\n        var weekdays = isArray(this._weekdays)\n            ? this._weekdays\n            : this._weekdays[\n                  m && m !== true && this._weekdays.isFormat.test(format)\n                      ? 'format'\n                      : 'standalone'\n              ];\n        return m === true\n            ? shiftWeekdays(weekdays, this._week.dow)\n            : m\n            ? weekdays[m.day()]\n            : weekdays;\n    }\n\n    function localeWeekdaysShort(m) {\n        return m === true\n            ? shiftWeekdays(this._weekdaysShort, this._week.dow)\n            : m\n            ? this._weekdaysShort[m.day()]\n            : this._weekdaysShort;\n    }\n\n    function localeWeekdaysMin(m) {\n        return m === true\n            ? shiftWeekdays(this._weekdaysMin, this._week.dow)\n            : m\n            ? this._weekdaysMin[m.day()]\n            : this._weekdaysMin;\n    }\n\n    function handleStrictParse$1(weekdayName, format, strict) {\n        var i,\n            ii,\n            mom,\n            llc = weekdayName.toLocaleLowerCase();\n        if (!this._weekdaysParse) {\n            this._weekdaysParse = [];\n            this._shortWeekdaysParse = [];\n            this._minWeekdaysParse = [];\n\n            for (i = 0; i < 7; ++i) {\n                mom = createUTC([2000, 1]).day(i);\n                this._minWeekdaysParse[i] = this.weekdaysMin(\n                    mom,\n                    ''\n                ).toLocaleLowerCase();\n                this._shortWeekdaysParse[i] = this.weekdaysShort(\n                    mom,\n                    ''\n                ).toLocaleLowerCase();\n                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();\n            }\n        }\n\n        if (strict) {\n            if (format === 'dddd') {\n                ii = indexOf.call(this._weekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else if (format === 'ddd') {\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        } else {\n            if (format === 'dddd') {\n                ii = indexOf.call(this._weekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else if (format === 'ddd') {\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._weekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._weekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        }\n    }\n\n    function localeWeekdaysParse(weekdayName, format, strict) {\n        var i, mom, regex;\n\n        if (this._weekdaysParseExact) {\n            return handleStrictParse$1.call(this, weekdayName, format, strict);\n        }\n\n        if (!this._weekdaysParse) {\n            this._weekdaysParse = [];\n            this._minWeekdaysParse = [];\n            this._shortWeekdaysParse = [];\n            this._fullWeekdaysParse = [];\n        }\n\n        for (i = 0; i < 7; i++) {\n            // make the regex if we don't have it already\n\n            mom = createUTC([2000, 1]).day(i);\n            if (strict && !this._fullWeekdaysParse[i]) {\n                this._fullWeekdaysParse[i] = new RegExp(\n                    '^' + this.weekdays(mom, '').replace('.', '\\\\.?') + '$',\n                    'i'\n                );\n                this._shortWeekdaysParse[i] = new RegExp(\n                    '^' + this.weekdaysShort(mom, '').replace('.', '\\\\.?') + '$',\n                    'i'\n                );\n                this._minWeekdaysParse[i] = new RegExp(\n                    '^' + this.weekdaysMin(mom, '').replace('.', '\\\\.?') + '$',\n                    'i'\n                );\n            }\n            if (!this._weekdaysParse[i]) {\n                regex =\n                    '^' +\n                    this.weekdays(mom, '') +\n                    '|^' +\n                    this.weekdaysShort(mom, '') +\n                    '|^' +\n                    this.weekdaysMin(mom, '');\n                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');\n            }\n            // test the regex\n            if (\n                strict &&\n                format === 'dddd' &&\n                this._fullWeekdaysParse[i].test(weekdayName)\n            ) {\n                return i;\n            } else if (\n                strict &&\n                format === 'ddd' &&\n                this._shortWeekdaysParse[i].test(weekdayName)\n            ) {\n                return i;\n            } else if (\n                strict &&\n                format === 'dd' &&\n                this._minWeekdaysParse[i].test(weekdayName)\n            ) {\n                return i;\n            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {\n                return i;\n            }\n        }\n    }\n\n    // MOMENTS\n\n    function getSetDayOfWeek(input) {\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();\n        if (input != null) {\n            input = parseWeekday(input, this.localeData());\n            return this.add(input - day, 'd');\n        } else {\n            return day;\n        }\n    }\n\n    function getSetLocaleDayOfWeek(input) {\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;\n        return input == null ? weekday : this.add(input - weekday, 'd');\n    }\n\n    function getSetISODayOfWeek(input) {\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n\n        // behaves the same as moment#day except\n        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)\n        // as a setter, sunday should belong to the previous week.\n\n        if (input != null) {\n            var weekday = parseIsoWeekday(input, this.localeData());\n            return this.day(this.day() % 7 ? weekday : weekday - 7);\n        } else {\n            return this.day() || 7;\n        }\n    }\n\n    function weekdaysRegex(isStrict) {\n        if (this._weekdaysParseExact) {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                computeWeekdaysParse.call(this);\n            }\n            if (isStrict) {\n                return this._weekdaysStrictRegex;\n            } else {\n                return this._weekdaysRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                this._weekdaysRegex = defaultWeekdaysRegex;\n            }\n            return this._weekdaysStrictRegex && isStrict\n                ? this._weekdaysStrictRegex\n                : this._weekdaysRegex;\n        }\n    }\n\n    function weekdaysShortRegex(isStrict) {\n        if (this._weekdaysParseExact) {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                computeWeekdaysParse.call(this);\n            }\n            if (isStrict) {\n                return this._weekdaysShortStrictRegex;\n            } else {\n                return this._weekdaysShortRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_weekdaysShortRegex')) {\n                this._weekdaysShortRegex = defaultWeekdaysShortRegex;\n            }\n            return this._weekdaysShortStrictRegex && isStrict\n                ? this._weekdaysShortStrictRegex\n                : this._weekdaysShortRegex;\n        }\n    }\n\n    function weekdaysMinRegex(isStrict) {\n        if (this._weekdaysParseExact) {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                computeWeekdaysParse.call(this);\n            }\n            if (isStrict) {\n                return this._weekdaysMinStrictRegex;\n            } else {\n                return this._weekdaysMinRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_weekdaysMinRegex')) {\n                this._weekdaysMinRegex = defaultWeekdaysMinRegex;\n            }\n            return this._weekdaysMinStrictRegex && isStrict\n                ? this._weekdaysMinStrictRegex\n                : this._weekdaysMinRegex;\n        }\n    }\n\n    function computeWeekdaysParse() {\n        function cmpLenRev(a, b) {\n            return b.length - a.length;\n        }\n\n        var minPieces = [],\n            shortPieces = [],\n            longPieces = [],\n            mixedPieces = [],\n            i,\n            mom,\n            minp,\n            shortp,\n            longp;\n        for (i = 0; i < 7; i++) {\n            // make the regex if we don't have it already\n            mom = createUTC([2000, 1]).day(i);\n            minp = regexEscape(this.weekdaysMin(mom, ''));\n            shortp = regexEscape(this.weekdaysShort(mom, ''));\n            longp = regexEscape(this.weekdays(mom, ''));\n            minPieces.push(minp);\n            shortPieces.push(shortp);\n            longPieces.push(longp);\n            mixedPieces.push(minp);\n            mixedPieces.push(shortp);\n            mixedPieces.push(longp);\n        }\n        // Sorting makes sure if one weekday (or abbr) is a prefix of another it\n        // will match the longer piece.\n        minPieces.sort(cmpLenRev);\n        shortPieces.sort(cmpLenRev);\n        longPieces.sort(cmpLenRev);\n        mixedPieces.sort(cmpLenRev);\n\n        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n        this._weekdaysShortRegex = this._weekdaysRegex;\n        this._weekdaysMinRegex = this._weekdaysRegex;\n\n        this._weekdaysStrictRegex = new RegExp(\n            '^(' + longPieces.join('|') + ')',\n            'i'\n        );\n        this._weekdaysShortStrictRegex = new RegExp(\n            '^(' + shortPieces.join('|') + ')',\n            'i'\n        );\n        this._weekdaysMinStrictRegex = new RegExp(\n            '^(' + minPieces.join('|') + ')',\n            'i'\n        );\n    }\n\n    // FORMATTING\n\n    function hFormat() {\n        return this.hours() % 12 || 12;\n    }\n\n    function kFormat() {\n        return this.hours() || 24;\n    }\n\n    addFormatToken('H', ['HH', 2], 0, 'hour');\n    addFormatToken('h', ['hh', 2], 0, hFormat);\n    addFormatToken('k', ['kk', 2], 0, kFormat);\n\n    addFormatToken('hmm', 0, 0, function () {\n        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);\n    });\n\n    addFormatToken('hmmss', 0, 0, function () {\n        return (\n            '' +\n            hFormat.apply(this) +\n            zeroFill(this.minutes(), 2) +\n            zeroFill(this.seconds(), 2)\n        );\n    });\n\n    addFormatToken('Hmm', 0, 0, function () {\n        return '' + this.hours() + zeroFill(this.minutes(), 2);\n    });\n\n    addFormatToken('Hmmss', 0, 0, function () {\n        return (\n            '' +\n            this.hours() +\n            zeroFill(this.minutes(), 2) +\n            zeroFill(this.seconds(), 2)\n        );\n    });\n\n    function meridiem(token, lowercase) {\n        addFormatToken(token, 0, 0, function () {\n            return this.localeData().meridiem(\n                this.hours(),\n                this.minutes(),\n                lowercase\n            );\n        });\n    }\n\n    meridiem('a', true);\n    meridiem('A', false);\n\n    // ALIASES\n\n    addUnitAlias('hour', 'h');\n\n    // PRIORITY\n    addUnitPriority('hour', 13);\n\n    // PARSING\n\n    function matchMeridiem(isStrict, locale) {\n        return locale._meridiemParse;\n    }\n\n    addRegexToken('a', matchMeridiem);\n    addRegexToken('A', matchMeridiem);\n    addRegexToken('H', match1to2);\n    addRegexToken('h', match1to2);\n    addRegexToken('k', match1to2);\n    addRegexToken('HH', match1to2, match2);\n    addRegexToken('hh', match1to2, match2);\n    addRegexToken('kk', match1to2, match2);\n\n    addRegexToken('hmm', match3to4);\n    addRegexToken('hmmss', match5to6);\n    addRegexToken('Hmm', match3to4);\n    addRegexToken('Hmmss', match5to6);\n\n    addParseToken(['H', 'HH'], HOUR);\n    addParseToken(['k', 'kk'], function (input, array, config) {\n        var kInput = toInt(input);\n        array[HOUR] = kInput === 24 ? 0 : kInput;\n    });\n    addParseToken(['a', 'A'], function (input, array, config) {\n        config._isPm = config._locale.isPM(input);\n        config._meridiem = input;\n    });\n    addParseToken(['h', 'hh'], function (input, array, config) {\n        array[HOUR] = toInt(input);\n        getParsingFlags(config).bigHour = true;\n    });\n    addParseToken('hmm', function (input, array, config) {\n        var pos = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos));\n        array[MINUTE] = toInt(input.substr(pos));\n        getParsingFlags(config).bigHour = true;\n    });\n    addParseToken('hmmss', function (input, array, config) {\n        var pos1 = input.length - 4,\n            pos2 = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos1));\n        array[MINUTE] = toInt(input.substr(pos1, 2));\n        array[SECOND] = toInt(input.substr(pos2));\n        getParsingFlags(config).bigHour = true;\n    });\n    addParseToken('Hmm', function (input, array, config) {\n        var pos = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos));\n        array[MINUTE] = toInt(input.substr(pos));\n    });\n    addParseToken('Hmmss', function (input, array, config) {\n        var pos1 = input.length - 4,\n            pos2 = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos1));\n        array[MINUTE] = toInt(input.substr(pos1, 2));\n        array[SECOND] = toInt(input.substr(pos2));\n    });\n\n    // LOCALES\n\n    function localeIsPM(input) {\n        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays\n        // Using charAt should be more compatible.\n        return (input + '').toLowerCase().charAt(0) === 'p';\n    }\n\n    var defaultLocaleMeridiemParse = /[ap]\\.?m?\\.?/i,\n        // Setting the hour should keep the time, because the user explicitly\n        // specified which hour they want. So trying to maintain the same hour (in\n        // a new timezone) makes sense. Adding/subtracting hours does not follow\n        // this rule.\n        getSetHour = makeGetSet('Hours', true);\n\n    function localeMeridiem(hours, minutes, isLower) {\n        if (hours > 11) {\n            return isLower ? 'pm' : 'PM';\n        } else {\n            return isLower ? 'am' : 'AM';\n        }\n    }\n\n    var baseConfig = {\n        calendar: defaultCalendar,\n        longDateFormat: defaultLongDateFormat,\n        invalidDate: defaultInvalidDate,\n        ordinal: defaultOrdinal,\n        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,\n        relativeTime: defaultRelativeTime,\n\n        months: defaultLocaleMonths,\n        monthsShort: defaultLocaleMonthsShort,\n\n        week: defaultLocaleWeek,\n\n        weekdays: defaultLocaleWeekdays,\n        weekdaysMin: defaultLocaleWeekdaysMin,\n        weekdaysShort: defaultLocaleWeekdaysShort,\n\n        meridiemParse: defaultLocaleMeridiemParse,\n    };\n\n    // internal storage for locale config files\n    var locales = {},\n        localeFamilies = {},\n        globalLocale;\n\n    function commonPrefix(arr1, arr2) {\n        var i,\n            minl = Math.min(arr1.length, arr2.length);\n        for (i = 0; i < minl; i += 1) {\n            if (arr1[i] !== arr2[i]) {\n                return i;\n            }\n        }\n        return minl;\n    }\n\n    function normalizeLocale(key) {\n        return key ? key.toLowerCase().replace('_', '-') : key;\n    }\n\n    // pick the locale from the array\n    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each\n    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root\n    function chooseLocale(names) {\n        var i = 0,\n            j,\n            next,\n            locale,\n            split;\n\n        while (i < names.length) {\n            split = normalizeLocale(names[i]).split('-');\n            j = split.length;\n            next = normalizeLocale(names[i + 1]);\n            next = next ? next.split('-') : null;\n            while (j > 0) {\n                locale = loadLocale(split.slice(0, j).join('-'));\n                if (locale) {\n                    return locale;\n                }\n                if (\n                    next &&\n                    next.length >= j &&\n                    commonPrefix(split, next) >= j - 1\n                ) {\n                    //the next array item is better than a shallower substring of this one\n                    break;\n                }\n                j--;\n            }\n            i++;\n        }\n        return globalLocale;\n    }\n\n    function loadLocale(name) {\n        var oldLocale = null,\n            aliasedRequire;\n        // TODO: Find a better way to register and load all the locales in Node\n        if (\n            locales[name] === undefined &&\n            typeof module !== 'undefined' &&\n            module &&\n            module.exports\n        ) {\n            try {\n                oldLocale = globalLocale._abbr;\n                aliasedRequire = require;\n                aliasedRequire('./locale/' + name);\n                getSetGlobalLocale(oldLocale);\n            } catch (e) {\n                // mark as not found to avoid repeating expensive file require call causing high CPU\n                // when trying to find en-US, en_US, en-us for every format call\n                locales[name] = null; // null means not found\n            }\n        }\n        return locales[name];\n    }\n\n    // This function will load locale and then set the global locale.  If\n    // no arguments are passed in, it will simply return the current global\n    // locale key.\n    function getSetGlobalLocale(key, values) {\n        var data;\n        if (key) {\n            if (isUndefined(values)) {\n                data = getLocale(key);\n            } else {\n                data = defineLocale(key, values);\n            }\n\n            if (data) {\n                // moment.duration._locale = moment._locale = data;\n                globalLocale = data;\n            } else {\n                if (typeof console !== 'undefined' && console.warn) {\n                    //warn user if arguments are passed but the locale could not be set\n                    console.warn(\n                        'Locale ' + key + ' not found. Did you forget to load it?'\n                    );\n                }\n            }\n        }\n\n        return globalLocale._abbr;\n    }\n\n    function defineLocale(name, config) {\n        if (config !== null) {\n            var locale,\n                parentConfig = baseConfig;\n            config.abbr = name;\n            if (locales[name] != null) {\n                deprecateSimple(\n                    'defineLocaleOverride',\n                    'use moment.updateLocale(localeName, config) to change ' +\n                        'an existing locale. moment.defineLocale(localeName, ' +\n                        'config) should only be used for creating a new locale ' +\n                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'\n                );\n                parentConfig = locales[name]._config;\n            } else if (config.parentLocale != null) {\n                if (locales[config.parentLocale] != null) {\n                    parentConfig = locales[config.parentLocale]._config;\n                } else {\n                    locale = loadLocale(config.parentLocale);\n                    if (locale != null) {\n                        parentConfig = locale._config;\n                    } else {\n                        if (!localeFamilies[config.parentLocale]) {\n                            localeFamilies[config.parentLocale] = [];\n                        }\n                        localeFamilies[config.parentLocale].push({\n                            name: name,\n                            config: config,\n                        });\n                        return null;\n                    }\n                }\n            }\n            locales[name] = new Locale(mergeConfigs(parentConfig, config));\n\n            if (localeFamilies[name]) {\n                localeFamilies[name].forEach(function (x) {\n                    defineLocale(x.name, x.config);\n                });\n            }\n\n            // backwards compat for now: also set the locale\n            // make sure we set the locale AFTER all child locales have been\n            // created, so we won't end up with the child locale set.\n            getSetGlobalLocale(name);\n\n            return locales[name];\n        } else {\n            // useful for testing\n            delete locales[name];\n            return null;\n        }\n    }\n\n    function updateLocale(name, config) {\n        if (config != null) {\n            var locale,\n                tmpLocale,\n                parentConfig = baseConfig;\n\n            if (locales[name] != null && locales[name].parentLocale != null) {\n                // Update existing child locale in-place to avoid memory-leaks\n                locales[name].set(mergeConfigs(locales[name]._config, config));\n            } else {\n                // MERGE\n                tmpLocale = loadLocale(name);\n                if (tmpLocale != null) {\n                    parentConfig = tmpLocale._config;\n                }\n                config = mergeConfigs(parentConfig, config);\n                if (tmpLocale == null) {\n                    // updateLocale is called for creating a new locale\n                    // Set abbr so it will have a name (getters return\n                    // undefined otherwise).\n                    config.abbr = name;\n                }\n                locale = new Locale(config);\n                locale.parentLocale = locales[name];\n                locales[name] = locale;\n            }\n\n            // backwards compat for now: also set the locale\n            getSetGlobalLocale(name);\n        } else {\n            // pass null for config to unupdate, useful for tests\n            if (locales[name] != null) {\n                if (locales[name].parentLocale != null) {\n                    locales[name] = locales[name].parentLocale;\n                    if (name === getSetGlobalLocale()) {\n                        getSetGlobalLocale(name);\n                    }\n                } else if (locales[name] != null) {\n                    delete locales[name];\n                }\n            }\n        }\n        return locales[name];\n    }\n\n    // returns locale data\n    function getLocale(key) {\n        var locale;\n\n        if (key && key._locale && key._locale._abbr) {\n            key = key._locale._abbr;\n        }\n\n        if (!key) {\n            return globalLocale;\n        }\n\n        if (!isArray(key)) {\n            //short-circuit everything else\n            locale = loadLocale(key);\n            if (locale) {\n                return locale;\n            }\n            key = [key];\n        }\n\n        return chooseLocale(key);\n    }\n\n    function listLocales() {\n        return keys(locales);\n    }\n\n    function checkOverflow(m) {\n        var overflow,\n            a = m._a;\n\n        if (a && getParsingFlags(m).overflow === -2) {\n            overflow =\n                a[MONTH] < 0 || a[MONTH] > 11\n                    ? MONTH\n                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])\n                    ? DATE\n                    : a[HOUR] < 0 ||\n                      a[HOUR] > 24 ||\n                      (a[HOUR] === 24 &&\n                          (a[MINUTE] !== 0 ||\n                              a[SECOND] !== 0 ||\n                              a[MILLISECOND] !== 0))\n                    ? HOUR\n                    : a[MINUTE] < 0 || a[MINUTE] > 59\n                    ? MINUTE\n                    : a[SECOND] < 0 || a[SECOND] > 59\n                    ? SECOND\n                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999\n                    ? MILLISECOND\n                    : -1;\n\n            if (\n                getParsingFlags(m)._overflowDayOfYear &&\n                (overflow < YEAR || overflow > DATE)\n            ) {\n                overflow = DATE;\n            }\n            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {\n                overflow = WEEK;\n            }\n            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {\n                overflow = WEEKDAY;\n            }\n\n            getParsingFlags(m).overflow = overflow;\n        }\n\n        return m;\n    }\n\n    // iso 8601 regex\n    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)\n    var extendedIsoRegex = /^\\s*((?:[+-]\\d{6}|\\d{4})-(?:\\d\\d-\\d\\d|W\\d\\d-\\d|W\\d\\d|\\d\\d\\d|\\d\\d))(?:(T| )(\\d\\d(?::\\d\\d(?::\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,\n        basicIsoRegex = /^\\s*((?:[+-]\\d{6}|\\d{4})(?:\\d\\d\\d\\d|W\\d\\d\\d|W\\d\\d|\\d\\d\\d|\\d\\d|))(?:(T| )(\\d\\d(?:\\d\\d(?:\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,\n        tzRegex = /Z|[+-]\\d\\d(?::?\\d\\d)?/,\n        isoDates = [\n            ['YYYYYY-MM-DD', /[+-]\\d{6}-\\d\\d-\\d\\d/],\n            ['YYYY-MM-DD', /\\d{4}-\\d\\d-\\d\\d/],\n            ['GGGG-[W]WW-E', /\\d{4}-W\\d\\d-\\d/],\n            ['GGGG-[W]WW', /\\d{4}-W\\d\\d/, false],\n            ['YYYY-DDD', /\\d{4}-\\d{3}/],\n            ['YYYY-MM', /\\d{4}-\\d\\d/, false],\n            ['YYYYYYMMDD', /[+-]\\d{10}/],\n            ['YYYYMMDD', /\\d{8}/],\n            ['GGGG[W]WWE', /\\d{4}W\\d{3}/],\n            ['GGGG[W]WW', /\\d{4}W\\d{2}/, false],\n            ['YYYYDDD', /\\d{7}/],\n            ['YYYYMM', /\\d{6}/, false],\n            ['YYYY', /\\d{4}/, false],\n        ],\n        // iso time formats and regexes\n        isoTimes = [\n            ['HH:mm:ss.SSSS', /\\d\\d:\\d\\d:\\d\\d\\.\\d+/],\n            ['HH:mm:ss,SSSS', /\\d\\d:\\d\\d:\\d\\d,\\d+/],\n            ['HH:mm:ss', /\\d\\d:\\d\\d:\\d\\d/],\n            ['HH:mm', /\\d\\d:\\d\\d/],\n            ['HHmmss.SSSS', /\\d\\d\\d\\d\\d\\d\\.\\d+/],\n            ['HHmmss,SSSS', /\\d\\d\\d\\d\\d\\d,\\d+/],\n            ['HHmmss', /\\d\\d\\d\\d\\d\\d/],\n            ['HHmm', /\\d\\d\\d\\d/],\n            ['HH', /\\d\\d/],\n        ],\n        aspNetJsonRegex = /^\\/?Date\\((-?\\d+)/i,\n        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3\n        rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\\d{4}))$/,\n        obsOffsets = {\n            UT: 0,\n            GMT: 0,\n            EDT: -4 * 60,\n            EST: -5 * 60,\n            CDT: -5 * 60,\n            CST: -6 * 60,\n            MDT: -6 * 60,\n            MST: -7 * 60,\n            PDT: -7 * 60,\n            PST: -8 * 60,\n        };\n\n    // date from iso format\n    function configFromISO(config) {\n        var i,\n            l,\n            string = config._i,\n            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),\n            allowTime,\n            dateFormat,\n            timeFormat,\n            tzFormat;\n\n        if (match) {\n            getParsingFlags(config).iso = true;\n\n            for (i = 0, l = isoDates.length; i < l; i++) {\n                if (isoDates[i][1].exec(match[1])) {\n                    dateFormat = isoDates[i][0];\n                    allowTime = isoDates[i][2] !== false;\n                    break;\n                }\n            }\n            if (dateFormat == null) {\n                config._isValid = false;\n                return;\n            }\n            if (match[3]) {\n                for (i = 0, l = isoTimes.length; i < l; i++) {\n                    if (isoTimes[i][1].exec(match[3])) {\n                        // match[2] should be 'T' or space\n                        timeFormat = (match[2] || ' ') + isoTimes[i][0];\n                        break;\n                    }\n                }\n                if (timeFormat == null) {\n                    config._isValid = false;\n                    return;\n                }\n            }\n            if (!allowTime && timeFormat != null) {\n                config._isValid = false;\n                return;\n            }\n            if (match[4]) {\n                if (tzRegex.exec(match[4])) {\n                    tzFormat = 'Z';\n                } else {\n                    config._isValid = false;\n                    return;\n                }\n            }\n            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');\n            configFromStringAndFormat(config);\n        } else {\n            config._isValid = false;\n        }\n    }\n\n    function extractFromRFC2822Strings(\n        yearStr,\n        monthStr,\n        dayStr,\n        hourStr,\n        minuteStr,\n        secondStr\n    ) {\n        var result = [\n            untruncateYear(yearStr),\n            defaultLocaleMonthsShort.indexOf(monthStr),\n            parseInt(dayStr, 10),\n            parseInt(hourStr, 10),\n            parseInt(minuteStr, 10),\n        ];\n\n        if (secondStr) {\n            result.push(parseInt(secondStr, 10));\n        }\n\n        return result;\n    }\n\n    function untruncateYear(yearStr) {\n        var year = parseInt(yearStr, 10);\n        if (year <= 49) {\n            return 2000 + year;\n        } else if (year <= 999) {\n            return 1900 + year;\n        }\n        return year;\n    }\n\n    function preprocessRFC2822(s) {\n        // Remove comments and folding whitespace and replace multiple-spaces with a single space\n        return s\n            .replace(/\\([^)]*\\)|[\\n\\t]/g, ' ')\n            .replace(/(\\s\\s+)/g, ' ')\n            .replace(/^\\s\\s*/, '')\n            .replace(/\\s\\s*$/, '');\n    }\n\n    function checkWeekday(weekdayStr, parsedInput, config) {\n        if (weekdayStr) {\n            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.\n            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),\n                weekdayActual = new Date(\n                    parsedInput[0],\n                    parsedInput[1],\n                    parsedInput[2]\n                ).getDay();\n            if (weekdayProvided !== weekdayActual) {\n                getParsingFlags(config).weekdayMismatch = true;\n                config._isValid = false;\n                return false;\n            }\n        }\n        return true;\n    }\n\n    function calculateOffset(obsOffset, militaryOffset, numOffset) {\n        if (obsOffset) {\n            return obsOffsets[obsOffset];\n        } else if (militaryOffset) {\n            // the only allowed military tz is Z\n            return 0;\n        } else {\n            var hm = parseInt(numOffset, 10),\n                m = hm % 100,\n                h = (hm - m) / 100;\n            return h * 60 + m;\n        }\n    }\n\n    // date and time from ref 2822 format\n    function configFromRFC2822(config) {\n        var match = rfc2822.exec(preprocessRFC2822(config._i)),\n            parsedArray;\n        if (match) {\n            parsedArray = extractFromRFC2822Strings(\n                match[4],\n                match[3],\n                match[2],\n                match[5],\n                match[6],\n                match[7]\n            );\n            if (!checkWeekday(match[1], parsedArray, config)) {\n                return;\n            }\n\n            config._a = parsedArray;\n            config._tzm = calculateOffset(match[8], match[9], match[10]);\n\n            config._d = createUTCDate.apply(null, config._a);\n            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);\n\n            getParsingFlags(config).rfc2822 = true;\n        } else {\n            config._isValid = false;\n        }\n    }\n\n    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict\n    function configFromString(config) {\n        var matched = aspNetJsonRegex.exec(config._i);\n        if (matched !== null) {\n            config._d = new Date(+matched[1]);\n            return;\n        }\n\n        configFromISO(config);\n        if (config._isValid === false) {\n            delete config._isValid;\n        } else {\n            return;\n        }\n\n        configFromRFC2822(config);\n        if (config._isValid === false) {\n            delete config._isValid;\n        } else {\n            return;\n        }\n\n        if (config._strict) {\n            config._isValid = false;\n        } else {\n            // Final attempt, use Input Fallback\n            hooks.createFromInputFallback(config);\n        }\n    }\n\n    hooks.createFromInputFallback = deprecate(\n        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +\n            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +\n            'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',\n        function (config) {\n            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));\n        }\n    );\n\n    // Pick the first defined of two or three arguments.\n    function defaults(a, b, c) {\n        if (a != null) {\n            return a;\n        }\n        if (b != null) {\n            return b;\n        }\n        return c;\n    }\n\n    function currentDateArray(config) {\n        // hooks is actually the exported moment object\n        var nowValue = new Date(hooks.now());\n        if (config._useUTC) {\n            return [\n                nowValue.getUTCFullYear(),\n                nowValue.getUTCMonth(),\n                nowValue.getUTCDate(),\n            ];\n        }\n        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];\n    }\n\n    // convert an array to a date.\n    // the array should mirror the parameters below\n    // note: all values past the year are optional and will default to the lowest possible value.\n    // [year, month, day , hour, minute, second, millisecond]\n    function configFromArray(config) {\n        var i,\n            date,\n            input = [],\n            currentDate,\n            expectedWeekday,\n            yearToUse;\n\n        if (config._d) {\n            return;\n        }\n\n        currentDate = currentDateArray(config);\n\n        //compute day of the year from weeks and weekdays\n        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {\n            dayOfYearFromWeekInfo(config);\n        }\n\n        //if the day of the year is set, figure out what it is\n        if (config._dayOfYear != null) {\n            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);\n\n            if (\n                config._dayOfYear > daysInYear(yearToUse) ||\n                config._dayOfYear === 0\n            ) {\n                getParsingFlags(config)._overflowDayOfYear = true;\n            }\n\n            date = createUTCDate(yearToUse, 0, config._dayOfYear);\n            config._a[MONTH] = date.getUTCMonth();\n            config._a[DATE] = date.getUTCDate();\n        }\n\n        // Default to current date.\n        // * if no year, month, day of month are given, default to today\n        // * if day of month is given, default month and year\n        // * if month is given, default only year\n        // * if year is given, don't default anything\n        for (i = 0; i < 3 && config._a[i] == null; ++i) {\n            config._a[i] = input[i] = currentDate[i];\n        }\n\n        // Zero out whatever was not defaulted, including time\n        for (; i < 7; i++) {\n            config._a[i] = input[i] =\n                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];\n        }\n\n        // Check for 24:00:00.000\n        if (\n            config._a[HOUR] === 24 &&\n            config._a[MINUTE] === 0 &&\n            config._a[SECOND] === 0 &&\n            config._a[MILLISECOND] === 0\n        ) {\n            config._nextDay = true;\n            config._a[HOUR] = 0;\n        }\n\n        config._d = (config._useUTC ? createUTCDate : createDate).apply(\n            null,\n            input\n        );\n        expectedWeekday = config._useUTC\n            ? config._d.getUTCDay()\n            : config._d.getDay();\n\n        // Apply timezone offset from input. The actual utcOffset can be changed\n        // with parseZone.\n        if (config._tzm != null) {\n            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);\n        }\n\n        if (config._nextDay) {\n            config._a[HOUR] = 24;\n        }\n\n        // check for mismatching day of week\n        if (\n            config._w &&\n            typeof config._w.d !== 'undefined' &&\n            config._w.d !== expectedWeekday\n        ) {\n            getParsingFlags(config).weekdayMismatch = true;\n        }\n    }\n\n    function dayOfYearFromWeekInfo(config) {\n        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;\n\n        w = config._w;\n        if (w.GG != null || w.W != null || w.E != null) {\n            dow = 1;\n            doy = 4;\n\n            // TODO: We need to take the current isoWeekYear, but that depends on\n            // how we interpret now (local, utc, fixed offset). So create\n            // a now version of current config (take local/utc/offset flags, and\n            // create now).\n            weekYear = defaults(\n                w.GG,\n                config._a[YEAR],\n                weekOfYear(createLocal(), 1, 4).year\n            );\n            week = defaults(w.W, 1);\n            weekday = defaults(w.E, 1);\n            if (weekday < 1 || weekday > 7) {\n                weekdayOverflow = true;\n            }\n        } else {\n            dow = config._locale._week.dow;\n            doy = config._locale._week.doy;\n\n            curWeek = weekOfYear(createLocal(), dow, doy);\n\n            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);\n\n            // Default to current week.\n            week = defaults(w.w, curWeek.week);\n\n            if (w.d != null) {\n                // weekday -- low day numbers are considered next week\n                weekday = w.d;\n                if (weekday < 0 || weekday > 6) {\n                    weekdayOverflow = true;\n                }\n            } else if (w.e != null) {\n                // local weekday -- counting starts from beginning of week\n                weekday = w.e + dow;\n                if (w.e < 0 || w.e > 6) {\n                    weekdayOverflow = true;\n                }\n            } else {\n                // default to beginning of week\n                weekday = dow;\n            }\n        }\n        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {\n            getParsingFlags(config)._overflowWeeks = true;\n        } else if (weekdayOverflow != null) {\n            getParsingFlags(config)._overflowWeekday = true;\n        } else {\n            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);\n            config._a[YEAR] = temp.year;\n            config._dayOfYear = temp.dayOfYear;\n        }\n    }\n\n    // constant that refers to the ISO standard\n    hooks.ISO_8601 = function () {};\n\n    // constant that refers to the RFC 2822 form\n    hooks.RFC_2822 = function () {};\n\n    // date from string and format string\n    function configFromStringAndFormat(config) {\n        // TODO: Move this to another part of the creation flow to prevent circular deps\n        if (config._f === hooks.ISO_8601) {\n            configFromISO(config);\n            return;\n        }\n        if (config._f === hooks.RFC_2822) {\n            configFromRFC2822(config);\n            return;\n        }\n        config._a = [];\n        getParsingFlags(config).empty = true;\n\n        // This array is used to make a Date, either with `new Date` or `Date.UTC`\n        var string = '' + config._i,\n            i,\n            parsedInput,\n            tokens,\n            token,\n            skipped,\n            stringLength = string.length,\n            totalParsedInputLength = 0,\n            era;\n\n        tokens =\n            expandFormat(config._f, config._locale).match(formattingTokens) || [];\n\n        for (i = 0; i < tokens.length; i++) {\n            token = tokens[i];\n            parsedInput = (string.match(getParseRegexForToken(token, config)) ||\n                [])[0];\n            if (parsedInput) {\n                skipped = string.substr(0, string.indexOf(parsedInput));\n                if (skipped.length > 0) {\n                    getParsingFlags(config).unusedInput.push(skipped);\n                }\n                string = string.slice(\n                    string.indexOf(parsedInput) + parsedInput.length\n                );\n                totalParsedInputLength += parsedInput.length;\n            }\n            // don't parse if it's not a known token\n            if (formatTokenFunctions[token]) {\n                if (parsedInput) {\n                    getParsingFlags(config).empty = false;\n                } else {\n                    getParsingFlags(config).unusedTokens.push(token);\n                }\n                addTimeToArrayFromToken(token, parsedInput, config);\n            } else if (config._strict && !parsedInput) {\n                getParsingFlags(config).unusedTokens.push(token);\n            }\n        }\n\n        // add remaining unparsed input length to the string\n        getParsingFlags(config).charsLeftOver =\n            stringLength - totalParsedInputLength;\n        if (string.length > 0) {\n            getParsingFlags(config).unusedInput.push(string);\n        }\n\n        // clear _12h flag if hour is <= 12\n        if (\n            config._a[HOUR] <= 12 &&\n            getParsingFlags(config).bigHour === true &&\n            config._a[HOUR] > 0\n        ) {\n            getParsingFlags(config).bigHour = undefined;\n        }\n\n        getParsingFlags(config).parsedDateParts = config._a.slice(0);\n        getParsingFlags(config).meridiem = config._meridiem;\n        // handle meridiem\n        config._a[HOUR] = meridiemFixWrap(\n            config._locale,\n            config._a[HOUR],\n            config._meridiem\n        );\n\n        // handle era\n        era = getParsingFlags(config).era;\n        if (era !== null) {\n            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);\n        }\n\n        configFromArray(config);\n        checkOverflow(config);\n    }\n\n    function meridiemFixWrap(locale, hour, meridiem) {\n        var isPm;\n\n        if (meridiem == null) {\n            // nothing to do\n            return hour;\n        }\n        if (locale.meridiemHour != null) {\n            return locale.meridiemHour(hour, meridiem);\n        } else if (locale.isPM != null) {\n            // Fallback\n            isPm = locale.isPM(meridiem);\n            if (isPm && hour < 12) {\n                hour += 12;\n            }\n            if (!isPm && hour === 12) {\n                hour = 0;\n            }\n            return hour;\n        } else {\n            // this is not supposed to happen\n            return hour;\n        }\n    }\n\n    // date from string and array of format strings\n    function configFromStringAndArray(config) {\n        var tempConfig,\n            bestMoment,\n            scoreToBeat,\n            i,\n            currentScore,\n            validFormatFound,\n            bestFormatIsValid = false;\n\n        if (config._f.length === 0) {\n            getParsingFlags(config).invalidFormat = true;\n            config._d = new Date(NaN);\n            return;\n        }\n\n        for (i = 0; i < config._f.length; i++) {\n            currentScore = 0;\n            validFormatFound = false;\n            tempConfig = copyConfig({}, config);\n            if (config._useUTC != null) {\n                tempConfig._useUTC = config._useUTC;\n            }\n            tempConfig._f = config._f[i];\n            configFromStringAndFormat(tempConfig);\n\n            if (isValid(tempConfig)) {\n                validFormatFound = true;\n            }\n\n            // if there is any input that was not parsed add a penalty for that format\n            currentScore += getParsingFlags(tempConfig).charsLeftOver;\n\n            //or tokens\n            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;\n\n            getParsingFlags(tempConfig).score = currentScore;\n\n            if (!bestFormatIsValid) {\n                if (\n                    scoreToBeat == null ||\n                    currentScore < scoreToBeat ||\n                    validFormatFound\n                ) {\n                    scoreToBeat = currentScore;\n                    bestMoment = tempConfig;\n                    if (validFormatFound) {\n                        bestFormatIsValid = true;\n                    }\n                }\n            } else {\n                if (currentScore < scoreToBeat) {\n                    scoreToBeat = currentScore;\n                    bestMoment = tempConfig;\n                }\n            }\n        }\n\n        extend(config, bestMoment || tempConfig);\n    }\n\n    function configFromObject(config) {\n        if (config._d) {\n            return;\n        }\n\n        var i = normalizeObjectUnits(config._i),\n            dayOrDate = i.day === undefined ? i.date : i.day;\n        config._a = map(\n            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],\n            function (obj) {\n                return obj && parseInt(obj, 10);\n            }\n        );\n\n        configFromArray(config);\n    }\n\n    function createFromConfig(config) {\n        var res = new Moment(checkOverflow(prepareConfig(config)));\n        if (res._nextDay) {\n            // Adding is smart enough around DST\n            res.add(1, 'd');\n            res._nextDay = undefined;\n        }\n\n        return res;\n    }\n\n    function prepareConfig(config) {\n        var input = config._i,\n            format = config._f;\n\n        config._locale = config._locale || getLocale(config._l);\n\n        if (input === null || (format === undefined && input === '')) {\n            return createInvalid({ nullInput: true });\n        }\n\n        if (typeof input === 'string') {\n            config._i = input = config._locale.preparse(input);\n        }\n\n        if (isMoment(input)) {\n            return new Moment(checkOverflow(input));\n        } else if (isDate(input)) {\n            config._d = input;\n        } else if (isArray(format)) {\n            configFromStringAndArray(config);\n        } else if (format) {\n            configFromStringAndFormat(config);\n        } else {\n            configFromInput(config);\n        }\n\n        if (!isValid(config)) {\n            config._d = null;\n        }\n\n        return config;\n    }\n\n    function configFromInput(config) {\n        var input = config._i;\n        if (isUndefined(input)) {\n            config._d = new Date(hooks.now());\n        } else if (isDate(input)) {\n            config._d = new Date(input.valueOf());\n        } else if (typeof input === 'string') {\n            configFromString(config);\n        } else if (isArray(input)) {\n            config._a = map(input.slice(0), function (obj) {\n                return parseInt(obj, 10);\n            });\n            configFromArray(config);\n        } else if (isObject(input)) {\n            configFromObject(config);\n        } else if (isNumber(input)) {\n            // from milliseconds\n            config._d = new Date(input);\n        } else {\n            hooks.createFromInputFallback(config);\n        }\n    }\n\n    function createLocalOrUTC(input, format, locale, strict, isUTC) {\n        var c = {};\n\n        if (format === true || format === false) {\n            strict = format;\n            format = undefined;\n        }\n\n        if (locale === true || locale === false) {\n            strict = locale;\n            locale = undefined;\n        }\n\n        if (\n            (isObject(input) && isObjectEmpty(input)) ||\n            (isArray(input) && input.length === 0)\n        ) {\n            input = undefined;\n        }\n        // object construction must be done this way.\n        // https://github.com/moment/moment/issues/1423\n        c._isAMomentObject = true;\n        c._useUTC = c._isUTC = isUTC;\n        c._l = locale;\n        c._i = input;\n        c._f = format;\n        c._strict = strict;\n\n        return createFromConfig(c);\n    }\n\n    function createLocal(input, format, locale, strict) {\n        return createLocalOrUTC(input, format, locale, strict, false);\n    }\n\n    var prototypeMin = deprecate(\n            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',\n            function () {\n                var other = createLocal.apply(null, arguments);\n                if (this.isValid() && other.isValid()) {\n                    return other < this ? this : other;\n                } else {\n                    return createInvalid();\n                }\n            }\n        ),\n        prototypeMax = deprecate(\n            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',\n            function () {\n                var other = createLocal.apply(null, arguments);\n                if (this.isValid() && other.isValid()) {\n                    return other > this ? this : other;\n                } else {\n                    return createInvalid();\n                }\n            }\n        );\n\n    // Pick a moment m from moments so that m[fn](other) is true for all\n    // other. This relies on the function fn to be transitive.\n    //\n    // moments should either be an array of moment objects or an array, whose\n    // first element is an array of moment objects.\n    function pickBy(fn, moments) {\n        var res, i;\n        if (moments.length === 1 && isArray(moments[0])) {\n            moments = moments[0];\n        }\n        if (!moments.length) {\n            return createLocal();\n        }\n        res = moments[0];\n        for (i = 1; i < moments.length; ++i) {\n            if (!moments[i].isValid() || moments[i][fn](res)) {\n                res = moments[i];\n            }\n        }\n        return res;\n    }\n\n    // TODO: Use [].sort instead?\n    function min() {\n        var args = [].slice.call(arguments, 0);\n\n        return pickBy('isBefore', args);\n    }\n\n    function max() {\n        var args = [].slice.call(arguments, 0);\n\n        return pickBy('isAfter', args);\n    }\n\n    var now = function () {\n        return Date.now ? Date.now() : +new Date();\n    };\n\n    var ordering = [\n        'year',\n        'quarter',\n        'month',\n        'week',\n        'day',\n        'hour',\n        'minute',\n        'second',\n        'millisecond',\n    ];\n\n    function isDurationValid(m) {\n        var key,\n            unitHasDecimal = false,\n            i;\n        for (key in m) {\n            if (\n                hasOwnProp(m, key) &&\n                !(\n                    indexOf.call(ordering, key) !== -1 &&\n                    (m[key] == null || !isNaN(m[key]))\n                )\n            ) {\n                return false;\n            }\n        }\n\n        for (i = 0; i < ordering.length; ++i) {\n            if (m[ordering[i]]) {\n                if (unitHasDecimal) {\n                    return false; // only allow non-integers for smallest unit\n                }\n                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {\n                    unitHasDecimal = true;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    function isValid$1() {\n        return this._isValid;\n    }\n\n    function createInvalid$1() {\n        return createDuration(NaN);\n    }\n\n    function Duration(duration) {\n        var normalizedInput = normalizeObjectUnits(duration),\n            years = normalizedInput.year || 0,\n            quarters = normalizedInput.quarter || 0,\n            months = normalizedInput.month || 0,\n            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,\n            days = normalizedInput.day || 0,\n            hours = normalizedInput.hour || 0,\n            minutes = normalizedInput.minute || 0,\n            seconds = normalizedInput.second || 0,\n            milliseconds = normalizedInput.millisecond || 0;\n\n        this._isValid = isDurationValid(normalizedInput);\n\n        // representation for dateAddRemove\n        this._milliseconds =\n            +milliseconds +\n            seconds * 1e3 + // 1000\n            minutes * 6e4 + // 1000 * 60\n            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978\n        // Because of dateAddRemove treats 24 hours as different from a\n        // day when working around DST, we need to store them separately\n        this._days = +days + weeks * 7;\n        // It is impossible to translate months into days without knowing\n        // which months you are are talking about, so we have to store\n        // it separately.\n        this._months = +months + quarters * 3 + years * 12;\n\n        this._data = {};\n\n        this._locale = getLocale();\n\n        this._bubble();\n    }\n\n    function isDuration(obj) {\n        return obj instanceof Duration;\n    }\n\n    function absRound(number) {\n        if (number < 0) {\n            return Math.round(-1 * number) * -1;\n        } else {\n            return Math.round(number);\n        }\n    }\n\n    // compare two arrays, return the number of differences\n    function compareArrays(array1, array2, dontConvert) {\n        var len = Math.min(array1.length, array2.length),\n            lengthDiff = Math.abs(array1.length - array2.length),\n            diffs = 0,\n            i;\n        for (i = 0; i < len; i++) {\n            if (\n                (dontConvert && array1[i] !== array2[i]) ||\n                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))\n            ) {\n                diffs++;\n            }\n        }\n        return diffs + lengthDiff;\n    }\n\n    // FORMATTING\n\n    function offset(token, separator) {\n        addFormatToken(token, 0, 0, function () {\n            var offset = this.utcOffset(),\n                sign = '+';\n            if (offset < 0) {\n                offset = -offset;\n                sign = '-';\n            }\n            return (\n                sign +\n                zeroFill(~~(offset / 60), 2) +\n                separator +\n                zeroFill(~~offset % 60, 2)\n            );\n        });\n    }\n\n    offset('Z', ':');\n    offset('ZZ', '');\n\n    // PARSING\n\n    addRegexToken('Z', matchShortOffset);\n    addRegexToken('ZZ', matchShortOffset);\n    addParseToken(['Z', 'ZZ'], function (input, array, config) {\n        config._useUTC = true;\n        config._tzm = offsetFromString(matchShortOffset, input);\n    });\n\n    // HELPERS\n\n    // timezone chunker\n    // '+10:00' > ['10',  '00']\n    // '-1530'  > ['-15', '30']\n    var chunkOffset = /([\\+\\-]|\\d\\d)/gi;\n\n    function offsetFromString(matcher, string) {\n        var matches = (string || '').match(matcher),\n            chunk,\n            parts,\n            minutes;\n\n        if (matches === null) {\n            return null;\n        }\n\n        chunk = matches[matches.length - 1] || [];\n        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];\n        minutes = +(parts[1] * 60) + toInt(parts[2]);\n\n        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;\n    }\n\n    // Return a moment from input, that is local/utc/zone equivalent to model.\n    function cloneWithOffset(input, model) {\n        var res, diff;\n        if (model._isUTC) {\n            res = model.clone();\n            diff =\n                (isMoment(input) || isDate(input)\n                    ? input.valueOf()\n                    : createLocal(input).valueOf()) - res.valueOf();\n            // Use low-level api, because this fn is low-level api.\n            res._d.setTime(res._d.valueOf() + diff);\n            hooks.updateOffset(res, false);\n            return res;\n        } else {\n            return createLocal(input).local();\n        }\n    }\n\n    function getDateOffset(m) {\n        // On Firefox.24 Date#getTimezoneOffset returns a floating point.\n        // https://github.com/moment/moment/pull/1871\n        return -Math.round(m._d.getTimezoneOffset());\n    }\n\n    // HOOKS\n\n    // This function will be called whenever a moment is mutated.\n    // It is intended to keep the offset in sync with the timezone.\n    hooks.updateOffset = function () {};\n\n    // MOMENTS\n\n    // keepLocalTime = true means only change the timezone, without\n    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->\n    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset\n    // +0200, so we adjust the time as needed, to be valid.\n    //\n    // Keeping the time actually adds/subtracts (one hour)\n    // from the actual represented time. That is why we call updateOffset\n    // a second time. In case it wants us to change the offset again\n    // _changeInProgress == true case, then we have to adjust, because\n    // there is no such time in the given timezone.\n    function getSetOffset(input, keepLocalTime, keepMinutes) {\n        var offset = this._offset || 0,\n            localAdjust;\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n        if (input != null) {\n            if (typeof input === 'string') {\n                input = offsetFromString(matchShortOffset, input);\n                if (input === null) {\n                    return this;\n                }\n            } else if (Math.abs(input) < 16 && !keepMinutes) {\n                input = input * 60;\n            }\n            if (!this._isUTC && keepLocalTime) {\n                localAdjust = getDateOffset(this);\n            }\n            this._offset = input;\n            this._isUTC = true;\n            if (localAdjust != null) {\n                this.add(localAdjust, 'm');\n            }\n            if (offset !== input) {\n                if (!keepLocalTime || this._changeInProgress) {\n                    addSubtract(\n                        this,\n                        createDuration(input - offset, 'm'),\n                        1,\n                        false\n                    );\n                } else if (!this._changeInProgress) {\n                    this._changeInProgress = true;\n                    hooks.updateOffset(this, true);\n                    this._changeInProgress = null;\n                }\n            }\n            return this;\n        } else {\n            return this._isUTC ? offset : getDateOffset(this);\n        }\n    }\n\n    function getSetZone(input, keepLocalTime) {\n        if (input != null) {\n            if (typeof input !== 'string') {\n                input = -input;\n            }\n\n            this.utcOffset(input, keepLocalTime);\n\n            return this;\n        } else {\n            return -this.utcOffset();\n        }\n    }\n\n    function setOffsetToUTC(keepLocalTime) {\n        return this.utcOffset(0, keepLocalTime);\n    }\n\n    function setOffsetToLocal(keepLocalTime) {\n        if (this._isUTC) {\n            this.utcOffset(0, keepLocalTime);\n            this._isUTC = false;\n\n            if (keepLocalTime) {\n                this.subtract(getDateOffset(this), 'm');\n            }\n        }\n        return this;\n    }\n\n    function setOffsetToParsedOffset() {\n        if (this._tzm != null) {\n            this.utcOffset(this._tzm, false, true);\n        } else if (typeof this._i === 'string') {\n            var tZone = offsetFromString(matchOffset, this._i);\n            if (tZone != null) {\n                this.utcOffset(tZone);\n            } else {\n                this.utcOffset(0, true);\n            }\n        }\n        return this;\n    }\n\n    function hasAlignedHourOffset(input) {\n        if (!this.isValid()) {\n            return false;\n        }\n        input = input ? createLocal(input).utcOffset() : 0;\n\n        return (this.utcOffset() - input) % 60 === 0;\n    }\n\n    function isDaylightSavingTime() {\n        return (\n            this.utcOffset() > this.clone().month(0).utcOffset() ||\n            this.utcOffset() > this.clone().month(5).utcOffset()\n        );\n    }\n\n    function isDaylightSavingTimeShifted() {\n        if (!isUndefined(this._isDSTShifted)) {\n            return this._isDSTShifted;\n        }\n\n        var c = {},\n            other;\n\n        copyConfig(c, this);\n        c = prepareConfig(c);\n\n        if (c._a) {\n            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);\n            this._isDSTShifted =\n                this.isValid() && compareArrays(c._a, other.toArray()) > 0;\n        } else {\n            this._isDSTShifted = false;\n        }\n\n        return this._isDSTShifted;\n    }\n\n    function isLocal() {\n        return this.isValid() ? !this._isUTC : false;\n    }\n\n    function isUtcOffset() {\n        return this.isValid() ? this._isUTC : false;\n    }\n\n    function isUtc() {\n        return this.isValid() ? this._isUTC && this._offset === 0 : false;\n    }\n\n    // ASP.NET json date format regex\n    var aspNetRegex = /^(-|\\+)?(?:(\\d*)[. ])?(\\d+):(\\d+)(?::(\\d+)(\\.\\d*)?)?$/,\n        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html\n        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere\n        // and further modified to allow for strings containing both week and day\n        isoRegex = /^(-|\\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;\n\n    function createDuration(input, key) {\n        var duration = input,\n            // matching against regexp is expensive, do it on demand\n            match = null,\n            sign,\n            ret,\n            diffRes;\n\n        if (isDuration(input)) {\n            duration = {\n                ms: input._milliseconds,\n                d: input._days,\n                M: input._months,\n            };\n        } else if (isNumber(input) || !isNaN(+input)) {\n            duration = {};\n            if (key) {\n                duration[key] = +input;\n            } else {\n                duration.milliseconds = +input;\n            }\n        } else if ((match = aspNetRegex.exec(input))) {\n            sign = match[1] === '-' ? -1 : 1;\n            duration = {\n                y: 0,\n                d: toInt(match[DATE]) * sign,\n                h: toInt(match[HOUR]) * sign,\n                m: toInt(match[MINUTE]) * sign,\n                s: toInt(match[SECOND]) * sign,\n                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match\n            };\n        } else if ((match = isoRegex.exec(input))) {\n            sign = match[1] === '-' ? -1 : 1;\n            duration = {\n                y: parseIso(match[2], sign),\n                M: parseIso(match[3], sign),\n                w: parseIso(match[4], sign),\n                d: parseIso(match[5], sign),\n                h: parseIso(match[6], sign),\n                m: parseIso(match[7], sign),\n                s: parseIso(match[8], sign),\n            };\n        } else if (duration == null) {\n            // checks for null or undefined\n            duration = {};\n        } else if (\n            typeof duration === 'object' &&\n            ('from' in duration || 'to' in duration)\n        ) {\n            diffRes = momentsDifference(\n                createLocal(duration.from),\n                createLocal(duration.to)\n            );\n\n            duration = {};\n            duration.ms = diffRes.milliseconds;\n            duration.M = diffRes.months;\n        }\n\n        ret = new Duration(duration);\n\n        if (isDuration(input) && hasOwnProp(input, '_locale')) {\n            ret._locale = input._locale;\n        }\n\n        if (isDuration(input) && hasOwnProp(input, '_isValid')) {\n            ret._isValid = input._isValid;\n        }\n\n        return ret;\n    }\n\n    createDuration.fn = Duration.prototype;\n    createDuration.invalid = createInvalid$1;\n\n    function parseIso(inp, sign) {\n        // We'd normally use ~~inp for this, but unfortunately it also\n        // converts floats to ints.\n        // inp may be undefined, so careful calling replace on it.\n        var res = inp && parseFloat(inp.replace(',', '.'));\n        // apply sign while we're at it\n        return (isNaN(res) ? 0 : res) * sign;\n    }\n\n    function positiveMomentsDifference(base, other) {\n        var res = {};\n\n        res.months =\n            other.month() - base.month() + (other.year() - base.year()) * 12;\n        if (base.clone().add(res.months, 'M').isAfter(other)) {\n            --res.months;\n        }\n\n        res.milliseconds = +other - +base.clone().add(res.months, 'M');\n\n        return res;\n    }\n\n    function momentsDifference(base, other) {\n        var res;\n        if (!(base.isValid() && other.isValid())) {\n            return { milliseconds: 0, months: 0 };\n        }\n\n        other = cloneWithOffset(other, base);\n        if (base.isBefore(other)) {\n            res = positiveMomentsDifference(base, other);\n        } else {\n            res = positiveMomentsDifference(other, base);\n            res.milliseconds = -res.milliseconds;\n            res.months = -res.months;\n        }\n\n        return res;\n    }\n\n    // TODO: remove 'name' arg after deprecation is removed\n    function createAdder(direction, name) {\n        return function (val, period) {\n            var dur, tmp;\n            //invert the arguments, but complain about it\n            if (period !== null && !isNaN(+period)) {\n                deprecateSimple(\n                    name,\n                    'moment().' +\n                        name +\n                        '(period, number) is deprecated. Please use moment().' +\n                        name +\n                        '(number, period). ' +\n                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'\n                );\n                tmp = val;\n                val = period;\n                period = tmp;\n            }\n\n            dur = createDuration(val, period);\n            addSubtract(this, dur, direction);\n            return this;\n        };\n    }\n\n    function addSubtract(mom, duration, isAdding, updateOffset) {\n        var milliseconds = duration._milliseconds,\n            days = absRound(duration._days),\n            months = absRound(duration._months);\n\n        if (!mom.isValid()) {\n            // No op\n            return;\n        }\n\n        updateOffset = updateOffset == null ? true : updateOffset;\n\n        if (months) {\n            setMonth(mom, get(mom, 'Month') + months * isAdding);\n        }\n        if (days) {\n            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);\n        }\n        if (milliseconds) {\n            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);\n        }\n        if (updateOffset) {\n            hooks.updateOffset(mom, days || months);\n        }\n    }\n\n    var add = createAdder(1, 'add'),\n        subtract = createAdder(-1, 'subtract');\n\n    function isString(input) {\n        return typeof input === 'string' || input instanceof String;\n    }\n\n    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined\n    function isMomentInput(input) {\n        return (\n            isMoment(input) ||\n            isDate(input) ||\n            isString(input) ||\n            isNumber(input) ||\n            isNumberOrStringArray(input) ||\n            isMomentInputObject(input) ||\n            input === null ||\n            input === undefined\n        );\n    }\n\n    function isMomentInputObject(input) {\n        var objectTest = isObject(input) && !isObjectEmpty(input),\n            propertyTest = false,\n            properties = [\n                'years',\n                'year',\n                'y',\n                'months',\n                'month',\n                'M',\n                'days',\n                'day',\n                'd',\n                'dates',\n                'date',\n                'D',\n                'hours',\n                'hour',\n                'h',\n                'minutes',\n                'minute',\n                'm',\n                'seconds',\n                'second',\n                's',\n                'milliseconds',\n                'millisecond',\n                'ms',\n            ],\n            i,\n            property;\n\n        for (i = 0; i < properties.length; i += 1) {\n            property = properties[i];\n            propertyTest = propertyTest || hasOwnProp(input, property);\n        }\n\n        return objectTest && propertyTest;\n    }\n\n    function isNumberOrStringArray(input) {\n        var arrayTest = isArray(input),\n            dataTypeTest = false;\n        if (arrayTest) {\n            dataTypeTest =\n                input.filter(function (item) {\n                    return !isNumber(item) && isString(input);\n                }).length === 0;\n        }\n        return arrayTest && dataTypeTest;\n    }\n\n    function isCalendarSpec(input) {\n        var objectTest = isObject(input) && !isObjectEmpty(input),\n            propertyTest = false,\n            properties = [\n                'sameDay',\n                'nextDay',\n                'lastDay',\n                'nextWeek',\n                'lastWeek',\n                'sameElse',\n            ],\n            i,\n            property;\n\n        for (i = 0; i < properties.length; i += 1) {\n            property = properties[i];\n            propertyTest = propertyTest || hasOwnProp(input, property);\n        }\n\n        return objectTest && propertyTest;\n    }\n\n    function getCalendarFormat(myMoment, now) {\n        var diff = myMoment.diff(now, 'days', true);\n        return diff < -6\n            ? 'sameElse'\n            : diff < -1\n            ? 'lastWeek'\n            : diff < 0\n            ? 'lastDay'\n            : diff < 1\n            ? 'sameDay'\n            : diff < 2\n            ? 'nextDay'\n            : diff < 7\n            ? 'nextWeek'\n            : 'sameElse';\n    }\n\n    function calendar$1(time, formats) {\n        // Support for single parameter, formats only overload to the calendar function\n        if (arguments.length === 1) {\n            if (!arguments[0]) {\n                time = undefined;\n                formats = undefined;\n            } else if (isMomentInput(arguments[0])) {\n                time = arguments[0];\n                formats = undefined;\n            } else if (isCalendarSpec(arguments[0])) {\n                formats = arguments[0];\n                time = undefined;\n            }\n        }\n        // We want to compare the start of today, vs this.\n        // Getting start-of-today depends on whether we're local/utc/offset or not.\n        var now = time || createLocal(),\n            sod = cloneWithOffset(now, this).startOf('day'),\n            format = hooks.calendarFormat(this, sod) || 'sameElse',\n            output =\n                formats &&\n                (isFunction(formats[format])\n                    ? formats[format].call(this, now)\n                    : formats[format]);\n\n        return this.format(\n            output || this.localeData().calendar(format, this, createLocal(now))\n        );\n    }\n\n    function clone() {\n        return new Moment(this);\n    }\n\n    function isAfter(input, units) {\n        var localInput = isMoment(input) ? input : createLocal(input);\n        if (!(this.isValid() && localInput.isValid())) {\n            return false;\n        }\n        units = normalizeUnits(units) || 'millisecond';\n        if (units === 'millisecond') {\n            return this.valueOf() > localInput.valueOf();\n        } else {\n            return localInput.valueOf() < this.clone().startOf(units).valueOf();\n        }\n    }\n\n    function isBefore(input, units) {\n        var localInput = isMoment(input) ? input : createLocal(input);\n        if (!(this.isValid() && localInput.isValid())) {\n            return false;\n        }\n        units = normalizeUnits(units) || 'millisecond';\n        if (units === 'millisecond') {\n            return this.valueOf() < localInput.valueOf();\n        } else {\n            return this.clone().endOf(units).valueOf() < localInput.valueOf();\n        }\n    }\n\n    function isBetween(from, to, units, inclusivity) {\n        var localFrom = isMoment(from) ? from : createLocal(from),\n            localTo = isMoment(to) ? to : createLocal(to);\n        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {\n            return false;\n        }\n        inclusivity = inclusivity || '()';\n        return (\n            (inclusivity[0] === '('\n                ? this.isAfter(localFrom, units)\n                : !this.isBefore(localFrom, units)) &&\n            (inclusivity[1] === ')'\n                ? this.isBefore(localTo, units)\n                : !this.isAfter(localTo, units))\n        );\n    }\n\n    function isSame(input, units) {\n        var localInput = isMoment(input) ? input : createLocal(input),\n            inputMs;\n        if (!(this.isValid() && localInput.isValid())) {\n            return false;\n        }\n        units = normalizeUnits(units) || 'millisecond';\n        if (units === 'millisecond') {\n            return this.valueOf() === localInput.valueOf();\n        } else {\n            inputMs = localInput.valueOf();\n            return (\n                this.clone().startOf(units).valueOf() <= inputMs &&\n                inputMs <= this.clone().endOf(units).valueOf()\n            );\n        }\n    }\n\n    function isSameOrAfter(input, units) {\n        return this.isSame(input, units) || this.isAfter(input, units);\n    }\n\n    function isSameOrBefore(input, units) {\n        return this.isSame(input, units) || this.isBefore(input, units);\n    }\n\n    function diff(input, units, asFloat) {\n        var that, zoneDelta, output;\n\n        if (!this.isValid()) {\n            return NaN;\n        }\n\n        that = cloneWithOffset(input, this);\n\n        if (!that.isValid()) {\n            return NaN;\n        }\n\n        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;\n\n        units = normalizeUnits(units);\n\n        switch (units) {\n            case 'year':\n                output = monthDiff(this, that) / 12;\n                break;\n            case 'month':\n                output = monthDiff(this, that);\n                break;\n            case 'quarter':\n                output = monthDiff(this, that) / 3;\n                break;\n            case 'second':\n                output = (this - that) / 1e3;\n                break; // 1000\n            case 'minute':\n                output = (this - that) / 6e4;\n                break; // 1000 * 60\n            case 'hour':\n                output = (this - that) / 36e5;\n                break; // 1000 * 60 * 60\n            case 'day':\n                output = (this - that - zoneDelta) / 864e5;\n                break; // 1000 * 60 * 60 * 24, negate dst\n            case 'week':\n                output = (this - that - zoneDelta) / 6048e5;\n                break; // 1000 * 60 * 60 * 24 * 7, negate dst\n            default:\n                output = this - that;\n        }\n\n        return asFloat ? output : absFloor(output);\n    }\n\n    function monthDiff(a, b) {\n        if (a.date() < b.date()) {\n            // end-of-month calculations work correct when the start month has more\n            // days than the end month.\n            return -monthDiff(b, a);\n        }\n        // difference in months\n        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),\n            // b is in (anchor - 1 month, anchor + 1 month)\n            anchor = a.clone().add(wholeMonthDiff, 'months'),\n            anchor2,\n            adjust;\n\n        if (b - anchor < 0) {\n            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');\n            // linear across the month\n            adjust = (b - anchor) / (anchor - anchor2);\n        } else {\n            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');\n            // linear across the month\n            adjust = (b - anchor) / (anchor2 - anchor);\n        }\n\n        //check for negative zero, return zero if negative zero\n        return -(wholeMonthDiff + adjust) || 0;\n    }\n\n    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';\n    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';\n\n    function toString() {\n        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');\n    }\n\n    function toISOString(keepOffset) {\n        if (!this.isValid()) {\n            return null;\n        }\n        var utc = keepOffset !== true,\n            m = utc ? this.clone().utc() : this;\n        if (m.year() < 0 || m.year() > 9999) {\n            return formatMoment(\n                m,\n                utc\n                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'\n                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'\n            );\n        }\n        if (isFunction(Date.prototype.toISOString)) {\n            // native implementation is ~50x faster, use it when we can\n            if (utc) {\n                return this.toDate().toISOString();\n            } else {\n                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)\n                    .toISOString()\n                    .replace('Z', formatMoment(m, 'Z'));\n            }\n        }\n        return formatMoment(\n            m,\n            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'\n        );\n    }\n\n    /**\n     * Return a human readable representation of a moment that can\n     * also be evaluated to get a new moment which is the same\n     *\n     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects\n     */\n    function inspect() {\n        if (!this.isValid()) {\n            return 'moment.invalid(/* ' + this._i + ' */)';\n        }\n        var func = 'moment',\n            zone = '',\n            prefix,\n            year,\n            datetime,\n            suffix;\n        if (!this.isLocal()) {\n            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';\n            zone = 'Z';\n        }\n        prefix = '[' + func + '(\"]';\n        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';\n        datetime = '-MM-DD[T]HH:mm:ss.SSS';\n        suffix = zone + '[\")]';\n\n        return this.format(prefix + year + datetime + suffix);\n    }\n\n    function format(inputString) {\n        if (!inputString) {\n            inputString = this.isUtc()\n                ? hooks.defaultFormatUtc\n                : hooks.defaultFormat;\n        }\n        var output = formatMoment(this, inputString);\n        return this.localeData().postformat(output);\n    }\n\n    function from(time, withoutSuffix) {\n        if (\n            this.isValid() &&\n            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())\n        ) {\n            return createDuration({ to: this, from: time })\n                .locale(this.locale())\n                .humanize(!withoutSuffix);\n        } else {\n            return this.localeData().invalidDate();\n        }\n    }\n\n    function fromNow(withoutSuffix) {\n        return this.from(createLocal(), withoutSuffix);\n    }\n\n    function to(time, withoutSuffix) {\n        if (\n            this.isValid() &&\n            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())\n        ) {\n            return createDuration({ from: this, to: time })\n                .locale(this.locale())\n                .humanize(!withoutSuffix);\n        } else {\n            return this.localeData().invalidDate();\n        }\n    }\n\n    function toNow(withoutSuffix) {\n        return this.to(createLocal(), withoutSuffix);\n    }\n\n    // If passed a locale key, it will set the locale for this\n    // instance.  Otherwise, it will return the locale configuration\n    // variables for this instance.\n    function locale(key) {\n        var newLocaleData;\n\n        if (key === undefined) {\n            return this._locale._abbr;\n        } else {\n            newLocaleData = getLocale(key);\n            if (newLocaleData != null) {\n                this._locale = newLocaleData;\n            }\n            return this;\n        }\n    }\n\n    var lang = deprecate(\n        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',\n        function (key) {\n            if (key === undefined) {\n                return this.localeData();\n            } else {\n                return this.locale(key);\n            }\n        }\n    );\n\n    function localeData() {\n        return this._locale;\n    }\n\n    var MS_PER_SECOND = 1000,\n        MS_PER_MINUTE = 60 * MS_PER_SECOND,\n        MS_PER_HOUR = 60 * MS_PER_MINUTE,\n        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;\n\n    // actual modulo - handles negative numbers (for dates before 1970):\n    function mod$1(dividend, divisor) {\n        return ((dividend % divisor) + divisor) % divisor;\n    }\n\n    function localStartOfDate(y, m, d) {\n        // the date constructor remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            // preserve leap years using a full 400 year cycle, then reset\n            return new Date(y + 400, m, d) - MS_PER_400_YEARS;\n        } else {\n            return new Date(y, m, d).valueOf();\n        }\n    }\n\n    function utcStartOfDate(y, m, d) {\n        // Date.UTC remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            // preserve leap years using a full 400 year cycle, then reset\n            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;\n        } else {\n            return Date.UTC(y, m, d);\n        }\n    }\n\n    function startOf(units) {\n        var time, startOfDate;\n        units = normalizeUnits(units);\n        if (units === undefined || units === 'millisecond' || !this.isValid()) {\n            return this;\n        }\n\n        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;\n\n        switch (units) {\n            case 'year':\n                time = startOfDate(this.year(), 0, 1);\n                break;\n            case 'quarter':\n                time = startOfDate(\n                    this.year(),\n                    this.month() - (this.month() % 3),\n                    1\n                );\n                break;\n            case 'month':\n                time = startOfDate(this.year(), this.month(), 1);\n                break;\n            case 'week':\n                time = startOfDate(\n                    this.year(),\n                    this.month(),\n                    this.date() - this.weekday()\n                );\n                break;\n            case 'isoWeek':\n                time = startOfDate(\n                    this.year(),\n                    this.month(),\n                    this.date() - (this.isoWeekday() - 1)\n                );\n                break;\n            case 'day':\n            case 'date':\n                time = startOfDate(this.year(), this.month(), this.date());\n                break;\n            case 'hour':\n                time = this._d.valueOf();\n                time -= mod$1(\n                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),\n                    MS_PER_HOUR\n                );\n                break;\n            case 'minute':\n                time = this._d.valueOf();\n                time -= mod$1(time, MS_PER_MINUTE);\n                break;\n            case 'second':\n                time = this._d.valueOf();\n                time -= mod$1(time, MS_PER_SECOND);\n                break;\n        }\n\n        this._d.setTime(time);\n        hooks.updateOffset(this, true);\n        return this;\n    }\n\n    function endOf(units) {\n        var time, startOfDate;\n        units = normalizeUnits(units);\n        if (units === undefined || units === 'millisecond' || !this.isValid()) {\n            return this;\n        }\n\n        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;\n\n        switch (units) {\n            case 'year':\n                time = startOfDate(this.year() + 1, 0, 1) - 1;\n                break;\n            case 'quarter':\n                time =\n                    startOfDate(\n                        this.year(),\n                        this.month() - (this.month() % 3) + 3,\n                        1\n                    ) - 1;\n                break;\n            case 'month':\n                time = startOfDate(this.year(), this.month() + 1, 1) - 1;\n                break;\n            case 'week':\n                time =\n                    startOfDate(\n                        this.year(),\n                        this.month(),\n                        this.date() - this.weekday() + 7\n                    ) - 1;\n                break;\n            case 'isoWeek':\n                time =\n                    startOfDate(\n                        this.year(),\n                        this.month(),\n                        this.date() - (this.isoWeekday() - 1) + 7\n                    ) - 1;\n                break;\n            case 'day':\n            case 'date':\n                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;\n                break;\n            case 'hour':\n                time = this._d.valueOf();\n                time +=\n                    MS_PER_HOUR -\n                    mod$1(\n                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),\n                        MS_PER_HOUR\n                    ) -\n                    1;\n                break;\n            case 'minute':\n                time = this._d.valueOf();\n                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;\n                break;\n            case 'second':\n                time = this._d.valueOf();\n                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;\n                break;\n        }\n\n        this._d.setTime(time);\n        hooks.updateOffset(this, true);\n        return this;\n    }\n\n    function valueOf() {\n        return this._d.valueOf() - (this._offset || 0) * 60000;\n    }\n\n    function unix() {\n        return Math.floor(this.valueOf() / 1000);\n    }\n\n    function toDate() {\n        return new Date(this.valueOf());\n    }\n\n    function toArray() {\n        var m = this;\n        return [\n            m.year(),\n            m.month(),\n            m.date(),\n            m.hour(),\n            m.minute(),\n            m.second(),\n            m.millisecond(),\n        ];\n    }\n\n    function toObject() {\n        var m = this;\n        return {\n            years: m.year(),\n            months: m.month(),\n            date: m.date(),\n            hours: m.hours(),\n            minutes: m.minutes(),\n            seconds: m.seconds(),\n            milliseconds: m.milliseconds(),\n        };\n    }\n\n    function toJSON() {\n        // new Date(NaN).toJSON() === null\n        return this.isValid() ? this.toISOString() : null;\n    }\n\n    function isValid$2() {\n        return isValid(this);\n    }\n\n    function parsingFlags() {\n        return extend({}, getParsingFlags(this));\n    }\n\n    function invalidAt() {\n        return getParsingFlags(this).overflow;\n    }\n\n    function creationData() {\n        return {\n            input: this._i,\n            format: this._f,\n            locale: this._locale,\n            isUTC: this._isUTC,\n            strict: this._strict,\n        };\n    }\n\n    addFormatToken('N', 0, 0, 'eraAbbr');\n    addFormatToken('NN', 0, 0, 'eraAbbr');\n    addFormatToken('NNN', 0, 0, 'eraAbbr');\n    addFormatToken('NNNN', 0, 0, 'eraName');\n    addFormatToken('NNNNN', 0, 0, 'eraNarrow');\n\n    addFormatToken('y', ['y', 1], 'yo', 'eraYear');\n    addFormatToken('y', ['yy', 2], 0, 'eraYear');\n    addFormatToken('y', ['yyy', 3], 0, 'eraYear');\n    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');\n\n    addRegexToken('N', matchEraAbbr);\n    addRegexToken('NN', matchEraAbbr);\n    addRegexToken('NNN', matchEraAbbr);\n    addRegexToken('NNNN', matchEraName);\n    addRegexToken('NNNNN', matchEraNarrow);\n\n    addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function (\n        input,\n        array,\n        config,\n        token\n    ) {\n        var era = config._locale.erasParse(input, token, config._strict);\n        if (era) {\n            getParsingFlags(config).era = era;\n        } else {\n            getParsingFlags(config).invalidEra = input;\n        }\n    });\n\n    addRegexToken('y', matchUnsigned);\n    addRegexToken('yy', matchUnsigned);\n    addRegexToken('yyy', matchUnsigned);\n    addRegexToken('yyyy', matchUnsigned);\n    addRegexToken('yo', matchEraYearOrdinal);\n\n    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);\n    addParseToken(['yo'], function (input, array, config, token) {\n        var match;\n        if (config._locale._eraYearOrdinalRegex) {\n            match = input.match(config._locale._eraYearOrdinalRegex);\n        }\n\n        if (config._locale.eraYearOrdinalParse) {\n            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);\n        } else {\n            array[YEAR] = parseInt(input, 10);\n        }\n    });\n\n    function localeEras(m, format) {\n        var i,\n            l,\n            date,\n            eras = this._eras || getLocale('en')._eras;\n        for (i = 0, l = eras.length; i < l; ++i) {\n            switch (typeof eras[i].since) {\n                case 'string':\n                    // truncate time\n                    date = hooks(eras[i].since).startOf('day');\n                    eras[i].since = date.valueOf();\n                    break;\n            }\n\n            switch (typeof eras[i].until) {\n                case 'undefined':\n                    eras[i].until = +Infinity;\n                    break;\n                case 'string':\n                    // truncate time\n                    date = hooks(eras[i].until).startOf('day').valueOf();\n                    eras[i].until = date.valueOf();\n                    break;\n            }\n        }\n        return eras;\n    }\n\n    function localeErasParse(eraName, format, strict) {\n        var i,\n            l,\n            eras = this.eras(),\n            name,\n            abbr,\n            narrow;\n        eraName = eraName.toUpperCase();\n\n        for (i = 0, l = eras.length; i < l; ++i) {\n            name = eras[i].name.toUpperCase();\n            abbr = eras[i].abbr.toUpperCase();\n            narrow = eras[i].narrow.toUpperCase();\n\n            if (strict) {\n                switch (format) {\n                    case 'N':\n                    case 'NN':\n                    case 'NNN':\n                        if (abbr === eraName) {\n                            return eras[i];\n                        }\n                        break;\n\n                    case 'NNNN':\n                        if (name === eraName) {\n                            return eras[i];\n                        }\n                        break;\n\n                    case 'NNNNN':\n                        if (narrow === eraName) {\n                            return eras[i];\n                        }\n                        break;\n                }\n            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {\n                return eras[i];\n            }\n        }\n    }\n\n    function localeErasConvertYear(era, year) {\n        var dir = era.since <= era.until ? +1 : -1;\n        if (year === undefined) {\n            return hooks(era.since).year();\n        } else {\n            return hooks(era.since).year() + (year - era.offset) * dir;\n        }\n    }\n\n    function getEraName() {\n        var i,\n            l,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (eras[i].since <= val && val <= eras[i].until) {\n                return eras[i].name;\n            }\n            if (eras[i].until <= val && val <= eras[i].since) {\n                return eras[i].name;\n            }\n        }\n\n        return '';\n    }\n\n    function getEraNarrow() {\n        var i,\n            l,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (eras[i].since <= val && val <= eras[i].until) {\n                return eras[i].narrow;\n            }\n            if (eras[i].until <= val && val <= eras[i].since) {\n                return eras[i].narrow;\n            }\n        }\n\n        return '';\n    }\n\n    function getEraAbbr() {\n        var i,\n            l,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (eras[i].since <= val && val <= eras[i].until) {\n                return eras[i].abbr;\n            }\n            if (eras[i].until <= val && val <= eras[i].since) {\n                return eras[i].abbr;\n            }\n        }\n\n        return '';\n    }\n\n    function getEraYear() {\n        var i,\n            l,\n            dir,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            dir = eras[i].since <= eras[i].until ? +1 : -1;\n\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (\n                (eras[i].since <= val && val <= eras[i].until) ||\n                (eras[i].until <= val && val <= eras[i].since)\n            ) {\n                return (\n                    (this.year() - hooks(eras[i].since).year()) * dir +\n                    eras[i].offset\n                );\n            }\n        }\n\n        return this.year();\n    }\n\n    function erasNameRegex(isStrict) {\n        if (!hasOwnProp(this, '_erasNameRegex')) {\n            computeErasParse.call(this);\n        }\n        return isStrict ? this._erasNameRegex : this._erasRegex;\n    }\n\n    function erasAbbrRegex(isStrict) {\n        if (!hasOwnProp(this, '_erasAbbrRegex')) {\n            computeErasParse.call(this);\n        }\n        return isStrict ? this._erasAbbrRegex : this._erasRegex;\n    }\n\n    function erasNarrowRegex(isStrict) {\n        if (!hasOwnProp(this, '_erasNarrowRegex')) {\n            computeErasParse.call(this);\n        }\n        return isStrict ? this._erasNarrowRegex : this._erasRegex;\n    }\n\n    function matchEraAbbr(isStrict, locale) {\n        return locale.erasAbbrRegex(isStrict);\n    }\n\n    function matchEraName(isStrict, locale) {\n        return locale.erasNameRegex(isStrict);\n    }\n\n    function matchEraNarrow(isStrict, locale) {\n        return locale.erasNarrowRegex(isStrict);\n    }\n\n    function matchEraYearOrdinal(isStrict, locale) {\n        return locale._eraYearOrdinalRegex || matchUnsigned;\n    }\n\n    function computeErasParse() {\n        var abbrPieces = [],\n            namePieces = [],\n            narrowPieces = [],\n            mixedPieces = [],\n            i,\n            l,\n            eras = this.eras();\n\n        for (i = 0, l = eras.length; i < l; ++i) {\n            namePieces.push(regexEscape(eras[i].name));\n            abbrPieces.push(regexEscape(eras[i].abbr));\n            narrowPieces.push(regexEscape(eras[i].narrow));\n\n            mixedPieces.push(regexEscape(eras[i].name));\n            mixedPieces.push(regexEscape(eras[i].abbr));\n            mixedPieces.push(regexEscape(eras[i].narrow));\n        }\n\n        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');\n        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');\n        this._erasNarrowRegex = new RegExp(\n            '^(' + narrowPieces.join('|') + ')',\n            'i'\n        );\n    }\n\n    // FORMATTING\n\n    addFormatToken(0, ['gg', 2], 0, function () {\n        return this.weekYear() % 100;\n    });\n\n    addFormatToken(0, ['GG', 2], 0, function () {\n        return this.isoWeekYear() % 100;\n    });\n\n    function addWeekYearFormatToken(token, getter) {\n        addFormatToken(0, [token, token.length], 0, getter);\n    }\n\n    addWeekYearFormatToken('gggg', 'weekYear');\n    addWeekYearFormatToken('ggggg', 'weekYear');\n    addWeekYearFormatToken('GGGG', 'isoWeekYear');\n    addWeekYearFormatToken('GGGGG', 'isoWeekYear');\n\n    // ALIASES\n\n    addUnitAlias('weekYear', 'gg');\n    addUnitAlias('isoWeekYear', 'GG');\n\n    // PRIORITY\n\n    addUnitPriority('weekYear', 1);\n    addUnitPriority('isoWeekYear', 1);\n\n    // PARSING\n\n    addRegexToken('G', matchSigned);\n    addRegexToken('g', matchSigned);\n    addRegexToken('GG', match1to2, match2);\n    addRegexToken('gg', match1to2, match2);\n    addRegexToken('GGGG', match1to4, match4);\n    addRegexToken('gggg', match1to4, match4);\n    addRegexToken('GGGGG', match1to6, match6);\n    addRegexToken('ggggg', match1to6, match6);\n\n    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (\n        input,\n        week,\n        config,\n        token\n    ) {\n        week[token.substr(0, 2)] = toInt(input);\n    });\n\n    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {\n        week[token] = hooks.parseTwoDigitYear(input);\n    });\n\n    // MOMENTS\n\n    function getSetWeekYear(input) {\n        return getSetWeekYearHelper.call(\n            this,\n            input,\n            this.week(),\n            this.weekday(),\n            this.localeData()._week.dow,\n            this.localeData()._week.doy\n        );\n    }\n\n    function getSetISOWeekYear(input) {\n        return getSetWeekYearHelper.call(\n            this,\n            input,\n            this.isoWeek(),\n            this.isoWeekday(),\n            1,\n            4\n        );\n    }\n\n    function getISOWeeksInYear() {\n        return weeksInYear(this.year(), 1, 4);\n    }\n\n    function getISOWeeksInISOWeekYear() {\n        return weeksInYear(this.isoWeekYear(), 1, 4);\n    }\n\n    function getWeeksInYear() {\n        var weekInfo = this.localeData()._week;\n        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);\n    }\n\n    function getWeeksInWeekYear() {\n        var weekInfo = this.localeData()._week;\n        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);\n    }\n\n    function getSetWeekYearHelper(input, week, weekday, dow, doy) {\n        var weeksTarget;\n        if (input == null) {\n            return weekOfYear(this, dow, doy).year;\n        } else {\n            weeksTarget = weeksInYear(input, dow, doy);\n            if (week > weeksTarget) {\n                week = weeksTarget;\n            }\n            return setWeekAll.call(this, input, week, weekday, dow, doy);\n        }\n    }\n\n    function setWeekAll(weekYear, week, weekday, dow, doy) {\n        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),\n            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);\n\n        this.year(date.getUTCFullYear());\n        this.month(date.getUTCMonth());\n        this.date(date.getUTCDate());\n        return this;\n    }\n\n    // FORMATTING\n\n    addFormatToken('Q', 0, 'Qo', 'quarter');\n\n    // ALIASES\n\n    addUnitAlias('quarter', 'Q');\n\n    // PRIORITY\n\n    addUnitPriority('quarter', 7);\n\n    // PARSING\n\n    addRegexToken('Q', match1);\n    addParseToken('Q', function (input, array) {\n        array[MONTH] = (toInt(input) - 1) * 3;\n    });\n\n    // MOMENTS\n\n    function getSetQuarter(input) {\n        return input == null\n            ? Math.ceil((this.month() + 1) / 3)\n            : this.month((input - 1) * 3 + (this.month() % 3));\n    }\n\n    // FORMATTING\n\n    addFormatToken('D', ['DD', 2], 'Do', 'date');\n\n    // ALIASES\n\n    addUnitAlias('date', 'D');\n\n    // PRIORITY\n    addUnitPriority('date', 9);\n\n    // PARSING\n\n    addRegexToken('D', match1to2);\n    addRegexToken('DD', match1to2, match2);\n    addRegexToken('Do', function (isStrict, locale) {\n        // TODO: Remove \"ordinalParse\" fallback in next major release.\n        return isStrict\n            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse\n            : locale._dayOfMonthOrdinalParseLenient;\n    });\n\n    addParseToken(['D', 'DD'], DATE);\n    addParseToken('Do', function (input, array) {\n        array[DATE] = toInt(input.match(match1to2)[0]);\n    });\n\n    // MOMENTS\n\n    var getSetDayOfMonth = makeGetSet('Date', true);\n\n    // FORMATTING\n\n    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');\n\n    // ALIASES\n\n    addUnitAlias('dayOfYear', 'DDD');\n\n    // PRIORITY\n    addUnitPriority('dayOfYear', 4);\n\n    // PARSING\n\n    addRegexToken('DDD', match1to3);\n    addRegexToken('DDDD', match3);\n    addParseToken(['DDD', 'DDDD'], function (input, array, config) {\n        config._dayOfYear = toInt(input);\n    });\n\n    // HELPERS\n\n    // MOMENTS\n\n    function getSetDayOfYear(input) {\n        var dayOfYear =\n            Math.round(\n                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5\n            ) + 1;\n        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');\n    }\n\n    // FORMATTING\n\n    addFormatToken('m', ['mm', 2], 0, 'minute');\n\n    // ALIASES\n\n    addUnitAlias('minute', 'm');\n\n    // PRIORITY\n\n    addUnitPriority('minute', 14);\n\n    // PARSING\n\n    addRegexToken('m', match1to2);\n    addRegexToken('mm', match1to2, match2);\n    addParseToken(['m', 'mm'], MINUTE);\n\n    // MOMENTS\n\n    var getSetMinute = makeGetSet('Minutes', false);\n\n    // FORMATTING\n\n    addFormatToken('s', ['ss', 2], 0, 'second');\n\n    // ALIASES\n\n    addUnitAlias('second', 's');\n\n    // PRIORITY\n\n    addUnitPriority('second', 15);\n\n    // PARSING\n\n    addRegexToken('s', match1to2);\n    addRegexToken('ss', match1to2, match2);\n    addParseToken(['s', 'ss'], SECOND);\n\n    // MOMENTS\n\n    var getSetSecond = makeGetSet('Seconds', false);\n\n    // FORMATTING\n\n    addFormatToken('S', 0, 0, function () {\n        return ~~(this.millisecond() / 100);\n    });\n\n    addFormatToken(0, ['SS', 2], 0, function () {\n        return ~~(this.millisecond() / 10);\n    });\n\n    addFormatToken(0, ['SSS', 3], 0, 'millisecond');\n    addFormatToken(0, ['SSSS', 4], 0, function () {\n        return this.millisecond() * 10;\n    });\n    addFormatToken(0, ['SSSSS', 5], 0, function () {\n        return this.millisecond() * 100;\n    });\n    addFormatToken(0, ['SSSSSS', 6], 0, function () {\n        return this.millisecond() * 1000;\n    });\n    addFormatToken(0, ['SSSSSSS', 7], 0, function () {\n        return this.millisecond() * 10000;\n    });\n    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {\n        return this.millisecond() * 100000;\n    });\n    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {\n        return this.millisecond() * 1000000;\n    });\n\n    // ALIASES\n\n    addUnitAlias('millisecond', 'ms');\n\n    // PRIORITY\n\n    addUnitPriority('millisecond', 16);\n\n    // PARSING\n\n    addRegexToken('S', match1to3, match1);\n    addRegexToken('SS', match1to3, match2);\n    addRegexToken('SSS', match1to3, match3);\n\n    var token, getSetMillisecond;\n    for (token = 'SSSS'; token.length <= 9; token += 'S') {\n        addRegexToken(token, matchUnsigned);\n    }\n\n    function parseMs(input, array) {\n        array[MILLISECOND] = toInt(('0.' + input) * 1000);\n    }\n\n    for (token = 'S'; token.length <= 9; token += 'S') {\n        addParseToken(token, parseMs);\n    }\n\n    getSetMillisecond = makeGetSet('Milliseconds', false);\n\n    // FORMATTING\n\n    addFormatToken('z', 0, 0, 'zoneAbbr');\n    addFormatToken('zz', 0, 0, 'zoneName');\n\n    // MOMENTS\n\n    function getZoneAbbr() {\n        return this._isUTC ? 'UTC' : '';\n    }\n\n    function getZoneName() {\n        return this._isUTC ? 'Coordinated Universal Time' : '';\n    }\n\n    var proto = Moment.prototype;\n\n    proto.add = add;\n    proto.calendar = calendar$1;\n    proto.clone = clone;\n    proto.diff = diff;\n    proto.endOf = endOf;\n    proto.format = format;\n    proto.from = from;\n    proto.fromNow = fromNow;\n    proto.to = to;\n    proto.toNow = toNow;\n    proto.get = stringGet;\n    proto.invalidAt = invalidAt;\n    proto.isAfter = isAfter;\n    proto.isBefore = isBefore;\n    proto.isBetween = isBetween;\n    proto.isSame = isSame;\n    proto.isSameOrAfter = isSameOrAfter;\n    proto.isSameOrBefore = isSameOrBefore;\n    proto.isValid = isValid$2;\n    proto.lang = lang;\n    proto.locale = locale;\n    proto.localeData = localeData;\n    proto.max = prototypeMax;\n    proto.min = prototypeMin;\n    proto.parsingFlags = parsingFlags;\n    proto.set = stringSet;\n    proto.startOf = startOf;\n    proto.subtract = subtract;\n    proto.toArray = toArray;\n    proto.toObject = toObject;\n    proto.toDate = toDate;\n    proto.toISOString = toISOString;\n    proto.inspect = inspect;\n    if (typeof Symbol !== 'undefined' && Symbol.for != null) {\n        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {\n            return 'Moment<' + this.format() + '>';\n        };\n    }\n    proto.toJSON = toJSON;\n    proto.toString = toString;\n    proto.unix = unix;\n    proto.valueOf = valueOf;\n    proto.creationData = creationData;\n    proto.eraName = getEraName;\n    proto.eraNarrow = getEraNarrow;\n    proto.eraAbbr = getEraAbbr;\n    proto.eraYear = getEraYear;\n    proto.year = getSetYear;\n    proto.isLeapYear = getIsLeapYear;\n    proto.weekYear = getSetWeekYear;\n    proto.isoWeekYear = getSetISOWeekYear;\n    proto.quarter = proto.quarters = getSetQuarter;\n    proto.month = getSetMonth;\n    proto.daysInMonth = getDaysInMonth;\n    proto.week = proto.weeks = getSetWeek;\n    proto.isoWeek = proto.isoWeeks = getSetISOWeek;\n    proto.weeksInYear = getWeeksInYear;\n    proto.weeksInWeekYear = getWeeksInWeekYear;\n    proto.isoWeeksInYear = getISOWeeksInYear;\n    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;\n    proto.date = getSetDayOfMonth;\n    proto.day = proto.days = getSetDayOfWeek;\n    proto.weekday = getSetLocaleDayOfWeek;\n    proto.isoWeekday = getSetISODayOfWeek;\n    proto.dayOfYear = getSetDayOfYear;\n    proto.hour = proto.hours = getSetHour;\n    proto.minute = proto.minutes = getSetMinute;\n    proto.second = proto.seconds = getSetSecond;\n    proto.millisecond = proto.milliseconds = getSetMillisecond;\n    proto.utcOffset = getSetOffset;\n    proto.utc = setOffsetToUTC;\n    proto.local = setOffsetToLocal;\n    proto.parseZone = setOffsetToParsedOffset;\n    proto.hasAlignedHourOffset = hasAlignedHourOffset;\n    proto.isDST = isDaylightSavingTime;\n    proto.isLocal = isLocal;\n    proto.isUtcOffset = isUtcOffset;\n    proto.isUtc = isUtc;\n    proto.isUTC = isUtc;\n    proto.zoneAbbr = getZoneAbbr;\n    proto.zoneName = getZoneName;\n    proto.dates = deprecate(\n        'dates accessor is deprecated. Use date instead.',\n        getSetDayOfMonth\n    );\n    proto.months = deprecate(\n        'months accessor is deprecated. Use month instead',\n        getSetMonth\n    );\n    proto.years = deprecate(\n        'years accessor is deprecated. Use year instead',\n        getSetYear\n    );\n    proto.zone = deprecate(\n        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',\n        getSetZone\n    );\n    proto.isDSTShifted = deprecate(\n        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',\n        isDaylightSavingTimeShifted\n    );\n\n    function createUnix(input) {\n        return createLocal(input * 1000);\n    }\n\n    function createInZone() {\n        return createLocal.apply(null, arguments).parseZone();\n    }\n\n    function preParsePostFormat(string) {\n        return string;\n    }\n\n    var proto$1 = Locale.prototype;\n\n    proto$1.calendar = calendar;\n    proto$1.longDateFormat = longDateFormat;\n    proto$1.invalidDate = invalidDate;\n    proto$1.ordinal = ordinal;\n    proto$1.preparse = preParsePostFormat;\n    proto$1.postformat = preParsePostFormat;\n    proto$1.relativeTime = relativeTime;\n    proto$1.pastFuture = pastFuture;\n    proto$1.set = set;\n    proto$1.eras = localeEras;\n    proto$1.erasParse = localeErasParse;\n    proto$1.erasConvertYear = localeErasConvertYear;\n    proto$1.erasAbbrRegex = erasAbbrRegex;\n    proto$1.erasNameRegex = erasNameRegex;\n    proto$1.erasNarrowRegex = erasNarrowRegex;\n\n    proto$1.months = localeMonths;\n    proto$1.monthsShort = localeMonthsShort;\n    proto$1.monthsParse = localeMonthsParse;\n    proto$1.monthsRegex = monthsRegex;\n    proto$1.monthsShortRegex = monthsShortRegex;\n    proto$1.week = localeWeek;\n    proto$1.firstDayOfYear = localeFirstDayOfYear;\n    proto$1.firstDayOfWeek = localeFirstDayOfWeek;\n\n    proto$1.weekdays = localeWeekdays;\n    proto$1.weekdaysMin = localeWeekdaysMin;\n    proto$1.weekdaysShort = localeWeekdaysShort;\n    proto$1.weekdaysParse = localeWeekdaysParse;\n\n    proto$1.weekdaysRegex = weekdaysRegex;\n    proto$1.weekdaysShortRegex = weekdaysShortRegex;\n    proto$1.weekdaysMinRegex = weekdaysMinRegex;\n\n    proto$1.isPM = localeIsPM;\n    proto$1.meridiem = localeMeridiem;\n\n    function get$1(format, index, field, setter) {\n        var locale = getLocale(),\n            utc = createUTC().set(setter, index);\n        return locale[field](utc, format);\n    }\n\n    function listMonthsImpl(format, index, field) {\n        if (isNumber(format)) {\n            index = format;\n            format = undefined;\n        }\n\n        format = format || '';\n\n        if (index != null) {\n            return get$1(format, index, field, 'month');\n        }\n\n        var i,\n            out = [];\n        for (i = 0; i < 12; i++) {\n            out[i] = get$1(format, i, field, 'month');\n        }\n        return out;\n    }\n\n    // ()\n    // (5)\n    // (fmt, 5)\n    // (fmt)\n    // (true)\n    // (true, 5)\n    // (true, fmt, 5)\n    // (true, fmt)\n    function listWeekdaysImpl(localeSorted, format, index, field) {\n        if (typeof localeSorted === 'boolean') {\n            if (isNumber(format)) {\n                index = format;\n                format = undefined;\n            }\n\n            format = format || '';\n        } else {\n            format = localeSorted;\n            index = format;\n            localeSorted = false;\n\n            if (isNumber(format)) {\n                index = format;\n                format = undefined;\n            }\n\n            format = format || '';\n        }\n\n        var locale = getLocale(),\n            shift = localeSorted ? locale._week.dow : 0,\n            i,\n            out = [];\n\n        if (index != null) {\n            return get$1(format, (index + shift) % 7, field, 'day');\n        }\n\n        for (i = 0; i < 7; i++) {\n            out[i] = get$1(format, (i + shift) % 7, field, 'day');\n        }\n        return out;\n    }\n\n    function listMonths(format, index) {\n        return listMonthsImpl(format, index, 'months');\n    }\n\n    function listMonthsShort(format, index) {\n        return listMonthsImpl(format, index, 'monthsShort');\n    }\n\n    function listWeekdays(localeSorted, format, index) {\n        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');\n    }\n\n    function listWeekdaysShort(localeSorted, format, index) {\n        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');\n    }\n\n    function listWeekdaysMin(localeSorted, format, index) {\n        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');\n    }\n\n    getSetGlobalLocale('en', {\n        eras: [\n            {\n                since: '0001-01-01',\n                until: +Infinity,\n                offset: 1,\n                name: 'Anno Domini',\n                narrow: 'AD',\n                abbr: 'AD',\n            },\n            {\n                since: '0000-12-31',\n                until: -Infinity,\n                offset: 1,\n                name: 'Before Christ',\n                narrow: 'BC',\n                abbr: 'BC',\n            },\n        ],\n        dayOfMonthOrdinalParse: /\\d{1,2}(th|st|nd|rd)/,\n        ordinal: function (number) {\n            var b = number % 10,\n                output =\n                    toInt((number % 100) / 10) === 1\n                        ? 'th'\n                        : b === 1\n                        ? 'st'\n                        : b === 2\n                        ? 'nd'\n                        : b === 3\n                        ? 'rd'\n                        : 'th';\n            return number + output;\n        },\n    });\n\n    // Side effect imports\n\n    hooks.lang = deprecate(\n        'moment.lang is deprecated. Use moment.locale instead.',\n        getSetGlobalLocale\n    );\n    hooks.langData = deprecate(\n        'moment.langData is deprecated. Use moment.localeData instead.',\n        getLocale\n    );\n\n    var mathAbs = Math.abs;\n\n    function abs() {\n        var data = this._data;\n\n        this._milliseconds = mathAbs(this._milliseconds);\n        this._days = mathAbs(this._days);\n        this._months = mathAbs(this._months);\n\n        data.milliseconds = mathAbs(data.milliseconds);\n        data.seconds = mathAbs(data.seconds);\n        data.minutes = mathAbs(data.minutes);\n        data.hours = mathAbs(data.hours);\n        data.months = mathAbs(data.months);\n        data.years = mathAbs(data.years);\n\n        return this;\n    }\n\n    function addSubtract$1(duration, input, value, direction) {\n        var other = createDuration(input, value);\n\n        duration._milliseconds += direction * other._milliseconds;\n        duration._days += direction * other._days;\n        duration._months += direction * other._months;\n\n        return duration._bubble();\n    }\n\n    // supports only 2.0-style add(1, 's') or add(duration)\n    function add$1(input, value) {\n        return addSubtract$1(this, input, value, 1);\n    }\n\n    // supports only 2.0-style subtract(1, 's') or subtract(duration)\n    function subtract$1(input, value) {\n        return addSubtract$1(this, input, value, -1);\n    }\n\n    function absCeil(number) {\n        if (number < 0) {\n            return Math.floor(number);\n        } else {\n            return Math.ceil(number);\n        }\n    }\n\n    function bubble() {\n        var milliseconds = this._milliseconds,\n            days = this._days,\n            months = this._months,\n            data = this._data,\n            seconds,\n            minutes,\n            hours,\n            years,\n            monthsFromDays;\n\n        // if we have a mix of positive and negative values, bubble down first\n        // check: https://github.com/moment/moment/issues/2166\n        if (\n            !(\n                (milliseconds >= 0 && days >= 0 && months >= 0) ||\n                (milliseconds <= 0 && days <= 0 && months <= 0)\n            )\n        ) {\n            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;\n            days = 0;\n            months = 0;\n        }\n\n        // The following code bubbles up values, see the tests for\n        // examples of what that means.\n        data.milliseconds = milliseconds % 1000;\n\n        seconds = absFloor(milliseconds / 1000);\n        data.seconds = seconds % 60;\n\n        minutes = absFloor(seconds / 60);\n        data.minutes = minutes % 60;\n\n        hours = absFloor(minutes / 60);\n        data.hours = hours % 24;\n\n        days += absFloor(hours / 24);\n\n        // convert days to months\n        monthsFromDays = absFloor(daysToMonths(days));\n        months += monthsFromDays;\n        days -= absCeil(monthsToDays(monthsFromDays));\n\n        // 12 months -> 1 year\n        years = absFloor(months / 12);\n        months %= 12;\n\n        data.days = days;\n        data.months = months;\n        data.years = years;\n\n        return this;\n    }\n\n    function daysToMonths(days) {\n        // 400 years have 146097 days (taking into account leap year rules)\n        // 400 years have 12 months === 4800\n        return (days * 4800) / 146097;\n    }\n\n    function monthsToDays(months) {\n        // the reverse of daysToMonths\n        return (months * 146097) / 4800;\n    }\n\n    function as(units) {\n        if (!this.isValid()) {\n            return NaN;\n        }\n        var days,\n            months,\n            milliseconds = this._milliseconds;\n\n        units = normalizeUnits(units);\n\n        if (units === 'month' || units === 'quarter' || units === 'year') {\n            days = this._days + milliseconds / 864e5;\n            months = this._months + daysToMonths(days);\n            switch (units) {\n                case 'month':\n                    return months;\n                case 'quarter':\n                    return months / 3;\n                case 'year':\n                    return months / 12;\n            }\n        } else {\n            // handle milliseconds separately because of floating point math errors (issue #1867)\n            days = this._days + Math.round(monthsToDays(this._months));\n            switch (units) {\n                case 'week':\n                    return days / 7 + milliseconds / 6048e5;\n                case 'day':\n                    return days + milliseconds / 864e5;\n                case 'hour':\n                    return days * 24 + milliseconds / 36e5;\n                case 'minute':\n                    return days * 1440 + milliseconds / 6e4;\n                case 'second':\n                    return days * 86400 + milliseconds / 1000;\n                // Math.floor prevents floating point math errors here\n                case 'millisecond':\n                    return Math.floor(days * 864e5) + milliseconds;\n                default:\n                    throw new Error('Unknown unit ' + units);\n            }\n        }\n    }\n\n    // TODO: Use this.as('ms')?\n    function valueOf$1() {\n        if (!this.isValid()) {\n            return NaN;\n        }\n        return (\n            this._milliseconds +\n            this._days * 864e5 +\n            (this._months % 12) * 2592e6 +\n            toInt(this._months / 12) * 31536e6\n        );\n    }\n\n    function makeAs(alias) {\n        return function () {\n            return this.as(alias);\n        };\n    }\n\n    var asMilliseconds = makeAs('ms'),\n        asSeconds = makeAs('s'),\n        asMinutes = makeAs('m'),\n        asHours = makeAs('h'),\n        asDays = makeAs('d'),\n        asWeeks = makeAs('w'),\n        asMonths = makeAs('M'),\n        asQuarters = makeAs('Q'),\n        asYears = makeAs('y');\n\n    function clone$1() {\n        return createDuration(this);\n    }\n\n    function get$2(units) {\n        units = normalizeUnits(units);\n        return this.isValid() ? this[units + 's']() : NaN;\n    }\n\n    function makeGetter(name) {\n        return function () {\n            return this.isValid() ? this._data[name] : NaN;\n        };\n    }\n\n    var milliseconds = makeGetter('milliseconds'),\n        seconds = makeGetter('seconds'),\n        minutes = makeGetter('minutes'),\n        hours = makeGetter('hours'),\n        days = makeGetter('days'),\n        months = makeGetter('months'),\n        years = makeGetter('years');\n\n    function weeks() {\n        return absFloor(this.days() / 7);\n    }\n\n    var round = Math.round,\n        thresholds = {\n            ss: 44, // a few seconds to seconds\n            s: 45, // seconds to minute\n            m: 45, // minutes to hour\n            h: 22, // hours to day\n            d: 26, // days to month/week\n            w: null, // weeks to month\n            M: 11, // months to year\n        };\n\n    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize\n    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {\n        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);\n    }\n\n    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {\n        var duration = createDuration(posNegDuration).abs(),\n            seconds = round(duration.as('s')),\n            minutes = round(duration.as('m')),\n            hours = round(duration.as('h')),\n            days = round(duration.as('d')),\n            months = round(duration.as('M')),\n            weeks = round(duration.as('w')),\n            years = round(duration.as('y')),\n            a =\n                (seconds <= thresholds.ss && ['s', seconds]) ||\n                (seconds < thresholds.s && ['ss', seconds]) ||\n                (minutes <= 1 && ['m']) ||\n                (minutes < thresholds.m && ['mm', minutes]) ||\n                (hours <= 1 && ['h']) ||\n                (hours < thresholds.h && ['hh', hours]) ||\n                (days <= 1 && ['d']) ||\n                (days < thresholds.d && ['dd', days]);\n\n        if (thresholds.w != null) {\n            a =\n                a ||\n                (weeks <= 1 && ['w']) ||\n                (weeks < thresholds.w && ['ww', weeks]);\n        }\n        a = a ||\n            (months <= 1 && ['M']) ||\n            (months < thresholds.M && ['MM', months]) ||\n            (years <= 1 && ['y']) || ['yy', years];\n\n        a[2] = withoutSuffix;\n        a[3] = +posNegDuration > 0;\n        a[4] = locale;\n        return substituteTimeAgo.apply(null, a);\n    }\n\n    // This function allows you to set the rounding function for relative time strings\n    function getSetRelativeTimeRounding(roundingFunction) {\n        if (roundingFunction === undefined) {\n            return round;\n        }\n        if (typeof roundingFunction === 'function') {\n            round = roundingFunction;\n            return true;\n        }\n        return false;\n    }\n\n    // This function allows you to set a threshold for relative time strings\n    function getSetRelativeTimeThreshold(threshold, limit) {\n        if (thresholds[threshold] === undefined) {\n            return false;\n        }\n        if (limit === undefined) {\n            return thresholds[threshold];\n        }\n        thresholds[threshold] = limit;\n        if (threshold === 's') {\n            thresholds.ss = limit - 1;\n        }\n        return true;\n    }\n\n    function humanize(argWithSuffix, argThresholds) {\n        if (!this.isValid()) {\n            return this.localeData().invalidDate();\n        }\n\n        var withSuffix = false,\n            th = thresholds,\n            locale,\n            output;\n\n        if (typeof argWithSuffix === 'object') {\n            argThresholds = argWithSuffix;\n            argWithSuffix = false;\n        }\n        if (typeof argWithSuffix === 'boolean') {\n            withSuffix = argWithSuffix;\n        }\n        if (typeof argThresholds === 'object') {\n            th = Object.assign({}, thresholds, argThresholds);\n            if (argThresholds.s != null && argThresholds.ss == null) {\n                th.ss = argThresholds.s - 1;\n            }\n        }\n\n        locale = this.localeData();\n        output = relativeTime$1(this, !withSuffix, th, locale);\n\n        if (withSuffix) {\n            output = locale.pastFuture(+this, output);\n        }\n\n        return locale.postformat(output);\n    }\n\n    var abs$1 = Math.abs;\n\n    function sign(x) {\n        return (x > 0) - (x < 0) || +x;\n    }\n\n    function toISOString$1() {\n        // for ISO strings we do not use the normal bubbling rules:\n        //  * milliseconds bubble up until they become hours\n        //  * days do not bubble at all\n        //  * months bubble up until they become years\n        // This is because there is no context-free conversion between hours and days\n        // (think of clock changes)\n        // and also not between days and months (28-31 days per month)\n        if (!this.isValid()) {\n            return this.localeData().invalidDate();\n        }\n\n        var seconds = abs$1(this._milliseconds) / 1000,\n            days = abs$1(this._days),\n            months = abs$1(this._months),\n            minutes,\n            hours,\n            years,\n            s,\n            total = this.asSeconds(),\n            totalSign,\n            ymSign,\n            daysSign,\n            hmsSign;\n\n        if (!total) {\n            // this is the same as C#'s (Noda) and python (isodate)...\n            // but not other JS (goog.date)\n            return 'P0D';\n        }\n\n        // 3600 seconds -> 60 minutes -> 1 hour\n        minutes = absFloor(seconds / 60);\n        hours = absFloor(minutes / 60);\n        seconds %= 60;\n        minutes %= 60;\n\n        // 12 months -> 1 year\n        years = absFloor(months / 12);\n        months %= 12;\n\n        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js\n        s = seconds ? seconds.toFixed(3).replace(/\\.?0+$/, '') : '';\n\n        totalSign = total < 0 ? '-' : '';\n        ymSign = sign(this._months) !== sign(total) ? '-' : '';\n        daysSign = sign(this._days) !== sign(total) ? '-' : '';\n        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';\n\n        return (\n            totalSign +\n            'P' +\n            (years ? ymSign + years + 'Y' : '') +\n            (months ? ymSign + months + 'M' : '') +\n            (days ? daysSign + days + 'D' : '') +\n            (hours || minutes || seconds ? 'T' : '') +\n            (hours ? hmsSign + hours + 'H' : '') +\n            (minutes ? hmsSign + minutes + 'M' : '') +\n            (seconds ? hmsSign + s + 'S' : '')\n        );\n    }\n\n    var proto$2 = Duration.prototype;\n\n    proto$2.isValid = isValid$1;\n    proto$2.abs = abs;\n    proto$2.add = add$1;\n    proto$2.subtract = subtract$1;\n    proto$2.as = as;\n    proto$2.asMilliseconds = asMilliseconds;\n    proto$2.asSeconds = asSeconds;\n    proto$2.asMinutes = asMinutes;\n    proto$2.asHours = asHours;\n    proto$2.asDays = asDays;\n    proto$2.asWeeks = asWeeks;\n    proto$2.asMonths = asMonths;\n    proto$2.asQuarters = asQuarters;\n    proto$2.asYears = asYears;\n    proto$2.valueOf = valueOf$1;\n    proto$2._bubble = bubble;\n    proto$2.clone = clone$1;\n    proto$2.get = get$2;\n    proto$2.milliseconds = milliseconds;\n    proto$2.seconds = seconds;\n    proto$2.minutes = minutes;\n    proto$2.hours = hours;\n    proto$2.days = days;\n    proto$2.weeks = weeks;\n    proto$2.months = months;\n    proto$2.years = years;\n    proto$2.humanize = humanize;\n    proto$2.toISOString = toISOString$1;\n    proto$2.toString = toISOString$1;\n    proto$2.toJSON = toISOString$1;\n    proto$2.locale = locale;\n    proto$2.localeData = localeData;\n\n    proto$2.toIsoString = deprecate(\n        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',\n        toISOString$1\n    );\n    proto$2.lang = lang;\n\n    // FORMATTING\n\n    addFormatToken('X', 0, 0, 'unix');\n    addFormatToken('x', 0, 0, 'valueOf');\n\n    // PARSING\n\n    addRegexToken('x', matchSigned);\n    addRegexToken('X', matchTimestamp);\n    addParseToken('X', function (input, array, config) {\n        config._d = new Date(parseFloat(input) * 1000);\n    });\n    addParseToken('x', function (input, array, config) {\n        config._d = new Date(toInt(input));\n    });\n\n    //! moment.js\n\n    hooks.version = '2.29.1';\n\n    setHookCallback(createLocal);\n\n    hooks.fn = proto;\n    hooks.min = min;\n    hooks.max = max;\n    hooks.now = now;\n    hooks.utc = createUTC;\n    hooks.unix = createUnix;\n    hooks.months = listMonths;\n    hooks.isDate = isDate;\n    hooks.locale = getSetGlobalLocale;\n    hooks.invalid = createInvalid;\n    hooks.duration = createDuration;\n    hooks.isMoment = isMoment;\n    hooks.weekdays = listWeekdays;\n    hooks.parseZone = createInZone;\n    hooks.localeData = getLocale;\n    hooks.isDuration = isDuration;\n    hooks.monthsShort = listMonthsShort;\n    hooks.weekdaysMin = listWeekdaysMin;\n    hooks.defineLocale = defineLocale;\n    hooks.updateLocale = updateLocale;\n    hooks.locales = listLocales;\n    hooks.weekdaysShort = listWeekdaysShort;\n    hooks.normalizeUnits = normalizeUnits;\n    hooks.relativeTimeRounding = getSetRelativeTimeRounding;\n    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;\n    hooks.calendarFormat = getCalendarFormat;\n    hooks.prototype = proto;\n\n    // currently HTML5 input type only supports 24-hour formats\n    hooks.HTML5_FMT = {\n        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type=\"datetime-local\" />\n        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type=\"datetime-local\" step=\"1\" />\n        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type=\"datetime-local\" step=\"0.001\" />\n        DATE: 'YYYY-MM-DD', // <input type=\"date\" />\n        TIME: 'HH:mm', // <input type=\"time\" />\n        TIME_SECONDS: 'HH:mm:ss', // <input type=\"time\" step=\"1\" />\n        TIME_MS: 'HH:mm:ss.SSS', // <input type=\"time\" step=\"0.001\" />\n        WEEK: 'GGGG-[W]WW', // <input type=\"week\" />\n        MONTH: 'YYYY-MM', // <input type=\"month\" />\n    };\n\n    return hooks;\n\n})));\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/select2.js",
    "content": "/*!\n * Select2 4.0.13\n * https://select2.github.io\n *\n * Released under the MIT license\n * https://github.com/select2/select2/blob/master/LICENSE.md\n */\n;(function (factory) {\n  if (typeof define === 'function' && define.amd) {\n    // AMD. Register as an anonymous module.\n    define(['jquery'], factory);\n  } else if (typeof module === 'object' && module.exports) {\n    // Node/CommonJS\n    module.exports = function (root, jQuery) {\n      if (jQuery === undefined) {\n        // require('jQuery') returns a factory that requires window to\n        // build a jQuery instance, we normalize how we use modules\n        // that require this pattern but the window provided is a noop\n        // if it's defined (how jquery works)\n        if (typeof window !== 'undefined') {\n          jQuery = require('jquery');\n        }\n        else {\n          jQuery = require('jquery')(root);\n        }\n      }\n      factory(jQuery);\n      return jQuery;\n    };\n  } else {\n    // Browser globals\n    factory(jQuery);\n  }\n} (function (jQuery) {\n  // This is needed so we can catch the AMD loader configuration and use it\n  // The inner file should be wrapped (by `banner.start.js`) in a function that\n  // returns the AMD loader references.\n  var S2 =(function () {\n  // Restore the Select2 AMD loader so it can be used\n  // Needed mostly in the language files, where the loader is not inserted\n  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {\n    var S2 = jQuery.fn.select2.amd;\n  }\nvar S2;(function () { if (!S2 || !S2.requirejs) {\nif (!S2) { S2 = {}; } else { require = S2; }\n/**\n * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.\n * Released under MIT license, http://github.com/requirejs/almond/LICENSE\n */\n//Going sloppy to avoid 'use strict' string cost, but strict practices should\n//be followed.\n/*global setTimeout: false */\n\nvar requirejs, require, define;\n(function (undef) {\n    var main, req, makeMap, handlers,\n        defined = {},\n        waiting = {},\n        config = {},\n        defining = {},\n        hasOwn = Object.prototype.hasOwnProperty,\n        aps = [].slice,\n        jsSuffixRegExp = /\\.js$/;\n\n    function hasProp(obj, prop) {\n        return hasOwn.call(obj, prop);\n    }\n\n    /**\n     * Given a relative module name, like ./something, normalize it to\n     * a real name that can be mapped to a path.\n     * @param {String} name the relative name\n     * @param {String} baseName a real name that the name arg is relative\n     * to.\n     * @returns {String} normalized name\n     */\n    function normalize(name, baseName) {\n        var nameParts, nameSegment, mapValue, foundMap, lastIndex,\n            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,\n            baseParts = baseName && baseName.split(\"/\"),\n            map = config.map,\n            starMap = (map && map['*']) || {};\n\n        //Adjust any relative paths.\n        if (name) {\n            name = name.split('/');\n            lastIndex = name.length - 1;\n\n            // If wanting node ID compatibility, strip .js from end\n            // of IDs. Have to do this here, and not in nameToUrl\n            // because node allows either .js or non .js to map\n            // to same file.\n            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {\n                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');\n            }\n\n            // Starts with a '.' so need the baseName\n            if (name[0].charAt(0) === '.' && baseParts) {\n                //Convert baseName to array, and lop off the last part,\n                //so that . matches that 'directory' and not name of the baseName's\n                //module. For instance, baseName of 'one/two/three', maps to\n                //'one/two/three.js', but we want the directory, 'one/two' for\n                //this normalization.\n                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);\n                name = normalizedBaseParts.concat(name);\n            }\n\n            //start trimDots\n            for (i = 0; i < name.length; i++) {\n                part = name[i];\n                if (part === '.') {\n                    name.splice(i, 1);\n                    i -= 1;\n                } else if (part === '..') {\n                    // If at the start, or previous value is still ..,\n                    // keep them so that when converted to a path it may\n                    // still work when converted to a path, even though\n                    // as an ID it is less than ideal. In larger point\n                    // releases, may be better to just kick out an error.\n                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {\n                        continue;\n                    } else if (i > 0) {\n                        name.splice(i - 1, 2);\n                        i -= 2;\n                    }\n                }\n            }\n            //end trimDots\n\n            name = name.join('/');\n        }\n\n        //Apply map config if available.\n        if ((baseParts || starMap) && map) {\n            nameParts = name.split('/');\n\n            for (i = nameParts.length; i > 0; i -= 1) {\n                nameSegment = nameParts.slice(0, i).join(\"/\");\n\n                if (baseParts) {\n                    //Find the longest baseName segment match in the config.\n                    //So, do joins on the biggest to smallest lengths of baseParts.\n                    for (j = baseParts.length; j > 0; j -= 1) {\n                        mapValue = map[baseParts.slice(0, j).join('/')];\n\n                        //baseName segment has  config, find if it has one for\n                        //this name.\n                        if (mapValue) {\n                            mapValue = mapValue[nameSegment];\n                            if (mapValue) {\n                                //Match, update name to the new value.\n                                foundMap = mapValue;\n                                foundI = i;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                if (foundMap) {\n                    break;\n                }\n\n                //Check for a star map match, but just hold on to it,\n                //if there is a shorter segment match later in a matching\n                //config, then favor over this star map.\n                if (!foundStarMap && starMap && starMap[nameSegment]) {\n                    foundStarMap = starMap[nameSegment];\n                    starI = i;\n                }\n            }\n\n            if (!foundMap && foundStarMap) {\n                foundMap = foundStarMap;\n                foundI = starI;\n            }\n\n            if (foundMap) {\n                nameParts.splice(0, foundI, foundMap);\n                name = nameParts.join('/');\n            }\n        }\n\n        return name;\n    }\n\n    function makeRequire(relName, forceSync) {\n        return function () {\n            //A version of a require function that passes a moduleName\n            //value for items that may need to\n            //look up paths relative to the moduleName\n            var args = aps.call(arguments, 0);\n\n            //If first arg is not require('string'), and there is only\n            //one arg, it is the array form without a callback. Insert\n            //a null so that the following concat is correct.\n            if (typeof args[0] !== 'string' && args.length === 1) {\n                args.push(null);\n            }\n            return req.apply(undef, args.concat([relName, forceSync]));\n        };\n    }\n\n    function makeNormalize(relName) {\n        return function (name) {\n            return normalize(name, relName);\n        };\n    }\n\n    function makeLoad(depName) {\n        return function (value) {\n            defined[depName] = value;\n        };\n    }\n\n    function callDep(name) {\n        if (hasProp(waiting, name)) {\n            var args = waiting[name];\n            delete waiting[name];\n            defining[name] = true;\n            main.apply(undef, args);\n        }\n\n        if (!hasProp(defined, name) && !hasProp(defining, name)) {\n            throw new Error('No ' + name);\n        }\n        return defined[name];\n    }\n\n    //Turns a plugin!resource to [plugin, resource]\n    //with the plugin being undefined if the name\n    //did not have a plugin prefix.\n    function splitPrefix(name) {\n        var prefix,\n            index = name ? name.indexOf('!') : -1;\n        if (index > -1) {\n            prefix = name.substring(0, index);\n            name = name.substring(index + 1, name.length);\n        }\n        return [prefix, name];\n    }\n\n    //Creates a parts array for a relName where first part is plugin ID,\n    //second part is resource ID. Assumes relName has already been normalized.\n    function makeRelParts(relName) {\n        return relName ? splitPrefix(relName) : [];\n    }\n\n    /**\n     * Makes a name map, normalizing the name, and using a plugin\n     * for normalization if necessary. Grabs a ref to plugin\n     * too, as an optimization.\n     */\n    makeMap = function (name, relParts) {\n        var plugin,\n            parts = splitPrefix(name),\n            prefix = parts[0],\n            relResourceName = relParts[1];\n\n        name = parts[1];\n\n        if (prefix) {\n            prefix = normalize(prefix, relResourceName);\n            plugin = callDep(prefix);\n        }\n\n        //Normalize according\n        if (prefix) {\n            if (plugin && plugin.normalize) {\n                name = plugin.normalize(name, makeNormalize(relResourceName));\n            } else {\n                name = normalize(name, relResourceName);\n            }\n        } else {\n            name = normalize(name, relResourceName);\n            parts = splitPrefix(name);\n            prefix = parts[0];\n            name = parts[1];\n            if (prefix) {\n                plugin = callDep(prefix);\n            }\n        }\n\n        //Using ridiculous property names for space reasons\n        return {\n            f: prefix ? prefix + '!' + name : name, //fullName\n            n: name,\n            pr: prefix,\n            p: plugin\n        };\n    };\n\n    function makeConfig(name) {\n        return function () {\n            return (config && config.config && config.config[name]) || {};\n        };\n    }\n\n    handlers = {\n        require: function (name) {\n            return makeRequire(name);\n        },\n        exports: function (name) {\n            var e = defined[name];\n            if (typeof e !== 'undefined') {\n                return e;\n            } else {\n                return (defined[name] = {});\n            }\n        },\n        module: function (name) {\n            return {\n                id: name,\n                uri: '',\n                exports: defined[name],\n                config: makeConfig(name)\n            };\n        }\n    };\n\n    main = function (name, deps, callback, relName) {\n        var cjsModule, depName, ret, map, i, relParts,\n            args = [],\n            callbackType = typeof callback,\n            usingExports;\n\n        //Use name if no relName\n        relName = relName || name;\n        relParts = makeRelParts(relName);\n\n        //Call the callback to define the module, if necessary.\n        if (callbackType === 'undefined' || callbackType === 'function') {\n            //Pull out the defined dependencies and pass the ordered\n            //values to the callback.\n            //Default to [require, exports, module] if no deps\n            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;\n            for (i = 0; i < deps.length; i += 1) {\n                map = makeMap(deps[i], relParts);\n                depName = map.f;\n\n                //Fast path CommonJS standard dependencies.\n                if (depName === \"require\") {\n                    args[i] = handlers.require(name);\n                } else if (depName === \"exports\") {\n                    //CommonJS module spec 1.1\n                    args[i] = handlers.exports(name);\n                    usingExports = true;\n                } else if (depName === \"module\") {\n                    //CommonJS module spec 1.1\n                    cjsModule = args[i] = handlers.module(name);\n                } else if (hasProp(defined, depName) ||\n                           hasProp(waiting, depName) ||\n                           hasProp(defining, depName)) {\n                    args[i] = callDep(depName);\n                } else if (map.p) {\n                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});\n                    args[i] = defined[depName];\n                } else {\n                    throw new Error(name + ' missing ' + depName);\n                }\n            }\n\n            ret = callback ? callback.apply(defined[name], args) : undefined;\n\n            if (name) {\n                //If setting exports via \"module\" is in play,\n                //favor that over return value and exports. After that,\n                //favor a non-undefined return value over exports use.\n                if (cjsModule && cjsModule.exports !== undef &&\n                        cjsModule.exports !== defined[name]) {\n                    defined[name] = cjsModule.exports;\n                } else if (ret !== undef || !usingExports) {\n                    //Use the return value from the function.\n                    defined[name] = ret;\n                }\n            }\n        } else if (name) {\n            //May just be an object definition for the module. Only\n            //worry about defining if have a module name.\n            defined[name] = callback;\n        }\n    };\n\n    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {\n        if (typeof deps === \"string\") {\n            if (handlers[deps]) {\n                //callback in this case is really relName\n                return handlers[deps](callback);\n            }\n            //Just return the module wanted. In this scenario, the\n            //deps arg is the module name, and second arg (if passed)\n            //is just the relName.\n            //Normalize module name, if it contains . or ..\n            return callDep(makeMap(deps, makeRelParts(callback)).f);\n        } else if (!deps.splice) {\n            //deps is a config object, not an array.\n            config = deps;\n            if (config.deps) {\n                req(config.deps, config.callback);\n            }\n            if (!callback) {\n                return;\n            }\n\n            if (callback.splice) {\n                //callback is an array, which means it is a dependency list.\n                //Adjust args if there are dependencies\n                deps = callback;\n                callback = relName;\n                relName = null;\n            } else {\n                deps = undef;\n            }\n        }\n\n        //Support require(['a'])\n        callback = callback || function () {};\n\n        //If relName is a function, it is an errback handler,\n        //so remove it.\n        if (typeof relName === 'function') {\n            relName = forceSync;\n            forceSync = alt;\n        }\n\n        //Simulate async callback;\n        if (forceSync) {\n            main(undef, deps, callback, relName);\n        } else {\n            //Using a non-zero value because of concern for what old browsers\n            //do, and latest browsers \"upgrade\" to 4 if lower value is used:\n            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:\n            //If want a value immediately, use require('id') instead -- something\n            //that works in almond on the global level, but not guaranteed and\n            //unlikely to work in other AMD implementations.\n            setTimeout(function () {\n                main(undef, deps, callback, relName);\n            }, 4);\n        }\n\n        return req;\n    };\n\n    /**\n     * Just drops the config on the floor, but returns req in case\n     * the config return value is used.\n     */\n    req.config = function (cfg) {\n        return req(cfg);\n    };\n\n    /**\n     * Expose module registry for debugging and tooling\n     */\n    requirejs._defined = defined;\n\n    define = function (name, deps, callback) {\n        if (typeof name !== 'string') {\n            throw new Error('See almond README: incorrect module build, no module name');\n        }\n\n        //This module may not have dependencies\n        if (!deps.splice) {\n            //deps is not an array, so probably means\n            //an object literal or factory function for\n            //the value. Adjust args.\n            callback = deps;\n            deps = [];\n        }\n\n        if (!hasProp(defined, name) && !hasProp(waiting, name)) {\n            waiting[name] = [name, deps, callback];\n        }\n    };\n\n    define.amd = {\n        jQuery: true\n    };\n}());\n\nS2.requirejs = requirejs;S2.require = require;S2.define = define;\n}\n}());\nS2.define(\"almond\", function(){});\n\n/* global jQuery:false, $:false */\nS2.define('jquery',[],function () {\n  var _$ = jQuery || $;\n\n  if (_$ == null && console && console.error) {\n    console.error(\n      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +\n      'found. Make sure that you are including jQuery before Select2 on your ' +\n      'web page.'\n    );\n  }\n\n  return _$;\n});\n\nS2.define('select2/utils',[\n  'jquery'\n], function ($) {\n  var Utils = {};\n\n  Utils.Extend = function (ChildClass, SuperClass) {\n    var __hasProp = {}.hasOwnProperty;\n\n    function BaseConstructor () {\n      this.constructor = ChildClass;\n    }\n\n    for (var key in SuperClass) {\n      if (__hasProp.call(SuperClass, key)) {\n        ChildClass[key] = SuperClass[key];\n      }\n    }\n\n    BaseConstructor.prototype = SuperClass.prototype;\n    ChildClass.prototype = new BaseConstructor();\n    ChildClass.__super__ = SuperClass.prototype;\n\n    return ChildClass;\n  };\n\n  function getMethods (theClass) {\n    var proto = theClass.prototype;\n\n    var methods = [];\n\n    for (var methodName in proto) {\n      var m = proto[methodName];\n\n      if (typeof m !== 'function') {\n        continue;\n      }\n\n      if (methodName === 'constructor') {\n        continue;\n      }\n\n      methods.push(methodName);\n    }\n\n    return methods;\n  }\n\n  Utils.Decorate = function (SuperClass, DecoratorClass) {\n    var decoratedMethods = getMethods(DecoratorClass);\n    var superMethods = getMethods(SuperClass);\n\n    function DecoratedClass () {\n      var unshift = Array.prototype.unshift;\n\n      var argCount = DecoratorClass.prototype.constructor.length;\n\n      var calledConstructor = SuperClass.prototype.constructor;\n\n      if (argCount > 0) {\n        unshift.call(arguments, SuperClass.prototype.constructor);\n\n        calledConstructor = DecoratorClass.prototype.constructor;\n      }\n\n      calledConstructor.apply(this, arguments);\n    }\n\n    DecoratorClass.displayName = SuperClass.displayName;\n\n    function ctr () {\n      this.constructor = DecoratedClass;\n    }\n\n    DecoratedClass.prototype = new ctr();\n\n    for (var m = 0; m < superMethods.length; m++) {\n      var superMethod = superMethods[m];\n\n      DecoratedClass.prototype[superMethod] =\n        SuperClass.prototype[superMethod];\n    }\n\n    var calledMethod = function (methodName) {\n      // Stub out the original method if it's not decorating an actual method\n      var originalMethod = function () {};\n\n      if (methodName in DecoratedClass.prototype) {\n        originalMethod = DecoratedClass.prototype[methodName];\n      }\n\n      var decoratedMethod = DecoratorClass.prototype[methodName];\n\n      return function () {\n        var unshift = Array.prototype.unshift;\n\n        unshift.call(arguments, originalMethod);\n\n        return decoratedMethod.apply(this, arguments);\n      };\n    };\n\n    for (var d = 0; d < decoratedMethods.length; d++) {\n      var decoratedMethod = decoratedMethods[d];\n\n      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);\n    }\n\n    return DecoratedClass;\n  };\n\n  var Observable = function () {\n    this.listeners = {};\n  };\n\n  Observable.prototype.on = function (event, callback) {\n    this.listeners = this.listeners || {};\n\n    if (event in this.listeners) {\n      this.listeners[event].push(callback);\n    } else {\n      this.listeners[event] = [callback];\n    }\n  };\n\n  Observable.prototype.trigger = function (event) {\n    var slice = Array.prototype.slice;\n    var params = slice.call(arguments, 1);\n\n    this.listeners = this.listeners || {};\n\n    // Params should always come in as an array\n    if (params == null) {\n      params = [];\n    }\n\n    // If there are no arguments to the event, use a temporary object\n    if (params.length === 0) {\n      params.push({});\n    }\n\n    // Set the `_type` of the first object to the event\n    params[0]._type = event;\n\n    if (event in this.listeners) {\n      this.invoke(this.listeners[event], slice.call(arguments, 1));\n    }\n\n    if ('*' in this.listeners) {\n      this.invoke(this.listeners['*'], arguments);\n    }\n  };\n\n  Observable.prototype.invoke = function (listeners, params) {\n    for (var i = 0, len = listeners.length; i < len; i++) {\n      listeners[i].apply(this, params);\n    }\n  };\n\n  Utils.Observable = Observable;\n\n  Utils.generateChars = function (length) {\n    var chars = '';\n\n    for (var i = 0; i < length; i++) {\n      var randomChar = Math.floor(Math.random() * 36);\n      chars += randomChar.toString(36);\n    }\n\n    return chars;\n  };\n\n  Utils.bind = function (func, context) {\n    return function () {\n      func.apply(context, arguments);\n    };\n  };\n\n  Utils._convertData = function (data) {\n    for (var originalKey in data) {\n      var keys = originalKey.split('-');\n\n      var dataLevel = data;\n\n      if (keys.length === 1) {\n        continue;\n      }\n\n      for (var k = 0; k < keys.length; k++) {\n        var key = keys[k];\n\n        // Lowercase the first letter\n        // By default, dash-separated becomes camelCase\n        key = key.substring(0, 1).toLowerCase() + key.substring(1);\n\n        if (!(key in dataLevel)) {\n          dataLevel[key] = {};\n        }\n\n        if (k == keys.length - 1) {\n          dataLevel[key] = data[originalKey];\n        }\n\n        dataLevel = dataLevel[key];\n      }\n\n      delete data[originalKey];\n    }\n\n    return data;\n  };\n\n  Utils.hasScroll = function (index, el) {\n    // Adapted from the function created by @ShadowScripter\n    // and adapted by @BillBarry on the Stack Exchange Code Review website.\n    // The original code can be found at\n    // http://codereview.stackexchange.com/q/13338\n    // and was designed to be used with the Sizzle selector engine.\n\n    var $el = $(el);\n    var overflowX = el.style.overflowX;\n    var overflowY = el.style.overflowY;\n\n    //Check both x and y declarations\n    if (overflowX === overflowY &&\n        (overflowY === 'hidden' || overflowY === 'visible')) {\n      return false;\n    }\n\n    if (overflowX === 'scroll' || overflowY === 'scroll') {\n      return true;\n    }\n\n    return ($el.innerHeight() < el.scrollHeight ||\n      $el.innerWidth() < el.scrollWidth);\n  };\n\n  Utils.escapeMarkup = function (markup) {\n    var replaceMap = {\n      '\\\\': '&#92;',\n      '&': '&amp;',\n      '<': '&lt;',\n      '>': '&gt;',\n      '\"': '&quot;',\n      '\\'': '&#39;',\n      '/': '&#47;'\n    };\n\n    // Do not try to escape the markup if it's not a string\n    if (typeof markup !== 'string') {\n      return markup;\n    }\n\n    return String(markup).replace(/[&<>\"'\\/\\\\]/g, function (match) {\n      return replaceMap[match];\n    });\n  };\n\n  // Append an array of jQuery nodes to a given element.\n  Utils.appendMany = function ($element, $nodes) {\n    // jQuery 1.7.x does not support $.fn.append() with an array\n    // Fall back to a jQuery object collection using $.fn.add()\n    if ($.fn.jquery.substr(0, 3) === '1.7') {\n      var $jqNodes = $();\n\n      $.map($nodes, function (node) {\n        $jqNodes = $jqNodes.add(node);\n      });\n\n      $nodes = $jqNodes;\n    }\n\n    $element.append($nodes);\n  };\n\n  // Cache objects in Utils.__cache instead of $.data (see #4346)\n  Utils.__cache = {};\n\n  var id = 0;\n  Utils.GetUniqueElementId = function (element) {\n    // Get a unique element Id. If element has no id,\n    // creates a new unique number, stores it in the id\n    // attribute and returns the new id.\n    // If an id already exists, it simply returns it.\n\n    var select2Id = element.getAttribute('data-select2-id');\n    if (select2Id == null) {\n      // If element has id, use it.\n      if (element.id) {\n        select2Id = element.id;\n        element.setAttribute('data-select2-id', select2Id);\n      } else {\n        element.setAttribute('data-select2-id', ++id);\n        select2Id = id.toString();\n      }\n    }\n    return select2Id;\n  };\n\n  Utils.StoreData = function (element, name, value) {\n    // Stores an item in the cache for a specified element.\n    // name is the cache key.\n    var id = Utils.GetUniqueElementId(element);\n    if (!Utils.__cache[id]) {\n      Utils.__cache[id] = {};\n    }\n\n    Utils.__cache[id][name] = value;\n  };\n\n  Utils.GetData = function (element, name) {\n    // Retrieves a value from the cache by its key (name)\n    // name is optional. If no name specified, return\n    // all cache items for the specified element.\n    // and for a specified element.\n    var id = Utils.GetUniqueElementId(element);\n    if (name) {\n      if (Utils.__cache[id]) {\n        if (Utils.__cache[id][name] != null) {\n          return Utils.__cache[id][name];\n        }\n        return $(element).data(name); // Fallback to HTML5 data attribs.\n      }\n      return $(element).data(name); // Fallback to HTML5 data attribs.\n    } else {\n      return Utils.__cache[id];\n    }\n  };\n\n  Utils.RemoveData = function (element) {\n    // Removes all cached items for a specified element.\n    var id = Utils.GetUniqueElementId(element);\n    if (Utils.__cache[id] != null) {\n      delete Utils.__cache[id];\n    }\n\n    element.removeAttribute('data-select2-id');\n  };\n\n  return Utils;\n});\n\nS2.define('select2/results',[\n  'jquery',\n  './utils'\n], function ($, Utils) {\n  function Results ($element, options, dataAdapter) {\n    this.$element = $element;\n    this.data = dataAdapter;\n    this.options = options;\n\n    Results.__super__.constructor.call(this);\n  }\n\n  Utils.Extend(Results, Utils.Observable);\n\n  Results.prototype.render = function () {\n    var $results = $(\n      '<ul class=\"select2-results__options\" role=\"listbox\"></ul>'\n    );\n\n    if (this.options.get('multiple')) {\n      $results.attr('aria-multiselectable', 'true');\n    }\n\n    this.$results = $results;\n\n    return $results;\n  };\n\n  Results.prototype.clear = function () {\n    this.$results.empty();\n  };\n\n  Results.prototype.displayMessage = function (params) {\n    var escapeMarkup = this.options.get('escapeMarkup');\n\n    this.clear();\n    this.hideLoading();\n\n    var $message = $(\n      '<li role=\"alert\" aria-live=\"assertive\"' +\n      ' class=\"select2-results__option\"></li>'\n    );\n\n    var message = this.options.get('translations').get(params.message);\n\n    $message.append(\n      escapeMarkup(\n        message(params.args)\n      )\n    );\n\n    $message[0].className += ' select2-results__message';\n\n    this.$results.append($message);\n  };\n\n  Results.prototype.hideMessages = function () {\n    this.$results.find('.select2-results__message').remove();\n  };\n\n  Results.prototype.append = function (data) {\n    this.hideLoading();\n\n    var $options = [];\n\n    if (data.results == null || data.results.length === 0) {\n      if (this.$results.children().length === 0) {\n        this.trigger('results:message', {\n          message: 'noResults'\n        });\n      }\n\n      return;\n    }\n\n    data.results = this.sort(data.results);\n\n    for (var d = 0; d < data.results.length; d++) {\n      var item = data.results[d];\n\n      var $option = this.option(item);\n\n      $options.push($option);\n    }\n\n    this.$results.append($options);\n  };\n\n  Results.prototype.position = function ($results, $dropdown) {\n    var $resultsContainer = $dropdown.find('.select2-results');\n    $resultsContainer.append($results);\n  };\n\n  Results.prototype.sort = function (data) {\n    var sorter = this.options.get('sorter');\n\n    return sorter(data);\n  };\n\n  Results.prototype.highlightFirstItem = function () {\n    var $options = this.$results\n      .find('.select2-results__option[aria-selected]');\n\n    var $selected = $options.filter('[aria-selected=true]');\n\n    // Check if there are any selected options\n    if ($selected.length > 0) {\n      // If there are selected options, highlight the first\n      $selected.first().trigger('mouseenter');\n    } else {\n      // If there are no selected options, highlight the first option\n      // in the dropdown\n      $options.first().trigger('mouseenter');\n    }\n\n    this.ensureHighlightVisible();\n  };\n\n  Results.prototype.setClasses = function () {\n    var self = this;\n\n    this.data.current(function (selected) {\n      var selectedIds = $.map(selected, function (s) {\n        return s.id.toString();\n      });\n\n      var $options = self.$results\n        .find('.select2-results__option[aria-selected]');\n\n      $options.each(function () {\n        var $option = $(this);\n\n        var item = Utils.GetData(this, 'data');\n\n        // id needs to be converted to a string when comparing\n        var id = '' + item.id;\n\n        if ((item.element != null && item.element.selected) ||\n            (item.element == null && $.inArray(id, selectedIds) > -1)) {\n          $option.attr('aria-selected', 'true');\n        } else {\n          $option.attr('aria-selected', 'false');\n        }\n      });\n\n    });\n  };\n\n  Results.prototype.showLoading = function (params) {\n    this.hideLoading();\n\n    var loadingMore = this.options.get('translations').get('searching');\n\n    var loading = {\n      disabled: true,\n      loading: true,\n      text: loadingMore(params)\n    };\n    var $loading = this.option(loading);\n    $loading.className += ' loading-results';\n\n    this.$results.prepend($loading);\n  };\n\n  Results.prototype.hideLoading = function () {\n    this.$results.find('.loading-results').remove();\n  };\n\n  Results.prototype.option = function (data) {\n    var option = document.createElement('li');\n    option.className = 'select2-results__option';\n\n    var attrs = {\n      'role': 'option',\n      'aria-selected': 'false'\n    };\n\n    var matches = window.Element.prototype.matches ||\n      window.Element.prototype.msMatchesSelector ||\n      window.Element.prototype.webkitMatchesSelector;\n\n    if ((data.element != null && matches.call(data.element, ':disabled')) ||\n        (data.element == null && data.disabled)) {\n      delete attrs['aria-selected'];\n      attrs['aria-disabled'] = 'true';\n    }\n\n    if (data.id == null) {\n      delete attrs['aria-selected'];\n    }\n\n    if (data._resultId != null) {\n      option.id = data._resultId;\n    }\n\n    if (data.title) {\n      option.title = data.title;\n    }\n\n    if (data.children) {\n      attrs.role = 'group';\n      attrs['aria-label'] = data.text;\n      delete attrs['aria-selected'];\n    }\n\n    for (var attr in attrs) {\n      var val = attrs[attr];\n\n      option.setAttribute(attr, val);\n    }\n\n    if (data.children) {\n      var $option = $(option);\n\n      var label = document.createElement('strong');\n      label.className = 'select2-results__group';\n\n      var $label = $(label);\n      this.template(data, label);\n\n      var $children = [];\n\n      for (var c = 0; c < data.children.length; c++) {\n        var child = data.children[c];\n\n        var $child = this.option(child);\n\n        $children.push($child);\n      }\n\n      var $childrenContainer = $('<ul></ul>', {\n        'class': 'select2-results__options select2-results__options--nested'\n      });\n\n      $childrenContainer.append($children);\n\n      $option.append(label);\n      $option.append($childrenContainer);\n    } else {\n      this.template(data, option);\n    }\n\n    Utils.StoreData(option, 'data', data);\n\n    return option;\n  };\n\n  Results.prototype.bind = function (container, $container) {\n    var self = this;\n\n    var id = container.id + '-results';\n\n    this.$results.attr('id', id);\n\n    container.on('results:all', function (params) {\n      self.clear();\n      self.append(params.data);\n\n      if (container.isOpen()) {\n        self.setClasses();\n        self.highlightFirstItem();\n      }\n    });\n\n    container.on('results:append', function (params) {\n      self.append(params.data);\n\n      if (container.isOpen()) {\n        self.setClasses();\n      }\n    });\n\n    container.on('query', function (params) {\n      self.hideMessages();\n      self.showLoading(params);\n    });\n\n    container.on('select', function () {\n      if (!container.isOpen()) {\n        return;\n      }\n\n      self.setClasses();\n\n      if (self.options.get('scrollAfterSelect')) {\n        self.highlightFirstItem();\n      }\n    });\n\n    container.on('unselect', function () {\n      if (!container.isOpen()) {\n        return;\n      }\n\n      self.setClasses();\n\n      if (self.options.get('scrollAfterSelect')) {\n        self.highlightFirstItem();\n      }\n    });\n\n    container.on('open', function () {\n      // When the dropdown is open, aria-expended=\"true\"\n      self.$results.attr('aria-expanded', 'true');\n      self.$results.attr('aria-hidden', 'false');\n\n      self.setClasses();\n      self.ensureHighlightVisible();\n    });\n\n    container.on('close', function () {\n      // When the dropdown is closed, aria-expended=\"false\"\n      self.$results.attr('aria-expanded', 'false');\n      self.$results.attr('aria-hidden', 'true');\n      self.$results.removeAttr('aria-activedescendant');\n    });\n\n    container.on('results:toggle', function () {\n      var $highlighted = self.getHighlightedResults();\n\n      if ($highlighted.length === 0) {\n        return;\n      }\n\n      $highlighted.trigger('mouseup');\n    });\n\n    container.on('results:select', function () {\n      var $highlighted = self.getHighlightedResults();\n\n      if ($highlighted.length === 0) {\n        return;\n      }\n\n      var data = Utils.GetData($highlighted[0], 'data');\n\n      if ($highlighted.attr('aria-selected') == 'true') {\n        self.trigger('close', {});\n      } else {\n        self.trigger('select', {\n          data: data\n        });\n      }\n    });\n\n    container.on('results:previous', function () {\n      var $highlighted = self.getHighlightedResults();\n\n      var $options = self.$results.find('[aria-selected]');\n\n      var currentIndex = $options.index($highlighted);\n\n      // If we are already at the top, don't move further\n      // If no options, currentIndex will be -1\n      if (currentIndex <= 0) {\n        return;\n      }\n\n      var nextIndex = currentIndex - 1;\n\n      // If none are highlighted, highlight the first\n      if ($highlighted.length === 0) {\n        nextIndex = 0;\n      }\n\n      var $next = $options.eq(nextIndex);\n\n      $next.trigger('mouseenter');\n\n      var currentOffset = self.$results.offset().top;\n      var nextTop = $next.offset().top;\n      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);\n\n      if (nextIndex === 0) {\n        self.$results.scrollTop(0);\n      } else if (nextTop - currentOffset < 0) {\n        self.$results.scrollTop(nextOffset);\n      }\n    });\n\n    container.on('results:next', function () {\n      var $highlighted = self.getHighlightedResults();\n\n      var $options = self.$results.find('[aria-selected]');\n\n      var currentIndex = $options.index($highlighted);\n\n      var nextIndex = currentIndex + 1;\n\n      // If we are at the last option, stay there\n      if (nextIndex >= $options.length) {\n        return;\n      }\n\n      var $next = $options.eq(nextIndex);\n\n      $next.trigger('mouseenter');\n\n      var currentOffset = self.$results.offset().top +\n        self.$results.outerHeight(false);\n      var nextBottom = $next.offset().top + $next.outerHeight(false);\n      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;\n\n      if (nextIndex === 0) {\n        self.$results.scrollTop(0);\n      } else if (nextBottom > currentOffset) {\n        self.$results.scrollTop(nextOffset);\n      }\n    });\n\n    container.on('results:focus', function (params) {\n      params.element.addClass('select2-results__option--highlighted');\n    });\n\n    container.on('results:message', function (params) {\n      self.displayMessage(params);\n    });\n\n    if ($.fn.mousewheel) {\n      this.$results.on('mousewheel', function (e) {\n        var top = self.$results.scrollTop();\n\n        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;\n\n        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;\n        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();\n\n        if (isAtTop) {\n          self.$results.scrollTop(0);\n\n          e.preventDefault();\n          e.stopPropagation();\n        } else if (isAtBottom) {\n          self.$results.scrollTop(\n            self.$results.get(0).scrollHeight - self.$results.height()\n          );\n\n          e.preventDefault();\n          e.stopPropagation();\n        }\n      });\n    }\n\n    this.$results.on('mouseup', '.select2-results__option[aria-selected]',\n      function (evt) {\n      var $this = $(this);\n\n      var data = Utils.GetData(this, 'data');\n\n      if ($this.attr('aria-selected') === 'true') {\n        if (self.options.get('multiple')) {\n          self.trigger('unselect', {\n            originalEvent: evt,\n            data: data\n          });\n        } else {\n          self.trigger('close', {});\n        }\n\n        return;\n      }\n\n      self.trigger('select', {\n        originalEvent: evt,\n        data: data\n      });\n    });\n\n    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',\n      function (evt) {\n      var data = Utils.GetData(this, 'data');\n\n      self.getHighlightedResults()\n          .removeClass('select2-results__option--highlighted');\n\n      self.trigger('results:focus', {\n        data: data,\n        element: $(this)\n      });\n    });\n  };\n\n  Results.prototype.getHighlightedResults = function () {\n    var $highlighted = this.$results\n    .find('.select2-results__option--highlighted');\n\n    return $highlighted;\n  };\n\n  Results.prototype.destroy = function () {\n    this.$results.remove();\n  };\n\n  Results.prototype.ensureHighlightVisible = function () {\n    var $highlighted = this.getHighlightedResults();\n\n    if ($highlighted.length === 0) {\n      return;\n    }\n\n    var $options = this.$results.find('[aria-selected]');\n\n    var currentIndex = $options.index($highlighted);\n\n    var currentOffset = this.$results.offset().top;\n    var nextTop = $highlighted.offset().top;\n    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);\n\n    var offsetDelta = nextTop - currentOffset;\n    nextOffset -= $highlighted.outerHeight(false) * 2;\n\n    if (currentIndex <= 2) {\n      this.$results.scrollTop(0);\n    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {\n      this.$results.scrollTop(nextOffset);\n    }\n  };\n\n  Results.prototype.template = function (result, container) {\n    var template = this.options.get('templateResult');\n    var escapeMarkup = this.options.get('escapeMarkup');\n\n    var content = template(result, container);\n\n    if (content == null) {\n      container.style.display = 'none';\n    } else if (typeof content === 'string') {\n      container.innerHTML = escapeMarkup(content);\n    } else {\n      $(container).append(content);\n    }\n  };\n\n  return Results;\n});\n\nS2.define('select2/keys',[\n\n], function () {\n  var KEYS = {\n    BACKSPACE: 8,\n    TAB: 9,\n    ENTER: 13,\n    SHIFT: 16,\n    CTRL: 17,\n    ALT: 18,\n    ESC: 27,\n    SPACE: 32,\n    PAGE_UP: 33,\n    PAGE_DOWN: 34,\n    END: 35,\n    HOME: 36,\n    LEFT: 37,\n    UP: 38,\n    RIGHT: 39,\n    DOWN: 40,\n    DELETE: 46\n  };\n\n  return KEYS;\n});\n\nS2.define('select2/selection/base',[\n  'jquery',\n  '../utils',\n  '../keys'\n], function ($, Utils, KEYS) {\n  function BaseSelection ($element, options) {\n    this.$element = $element;\n    this.options = options;\n\n    BaseSelection.__super__.constructor.call(this);\n  }\n\n  Utils.Extend(BaseSelection, Utils.Observable);\n\n  BaseSelection.prototype.render = function () {\n    var $selection = $(\n      '<span class=\"select2-selection\" role=\"combobox\" ' +\n      ' aria-haspopup=\"true\" aria-expanded=\"false\">' +\n      '</span>'\n    );\n\n    this._tabindex = 0;\n\n    if (Utils.GetData(this.$element[0], 'old-tabindex') != null) {\n      this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex');\n    } else if (this.$element.attr('tabindex') != null) {\n      this._tabindex = this.$element.attr('tabindex');\n    }\n\n    $selection.attr('title', this.$element.attr('title'));\n    $selection.attr('tabindex', this._tabindex);\n    $selection.attr('aria-disabled', 'false');\n\n    this.$selection = $selection;\n\n    return $selection;\n  };\n\n  BaseSelection.prototype.bind = function (container, $container) {\n    var self = this;\n\n    var resultsId = container.id + '-results';\n\n    this.container = container;\n\n    this.$selection.on('focus', function (evt) {\n      self.trigger('focus', evt);\n    });\n\n    this.$selection.on('blur', function (evt) {\n      self._handleBlur(evt);\n    });\n\n    this.$selection.on('keydown', function (evt) {\n      self.trigger('keypress', evt);\n\n      if (evt.which === KEYS.SPACE) {\n        evt.preventDefault();\n      }\n    });\n\n    container.on('results:focus', function (params) {\n      self.$selection.attr('aria-activedescendant', params.data._resultId);\n    });\n\n    container.on('selection:update', function (params) {\n      self.update(params.data);\n    });\n\n    container.on('open', function () {\n      // When the dropdown is open, aria-expanded=\"true\"\n      self.$selection.attr('aria-expanded', 'true');\n      self.$selection.attr('aria-owns', resultsId);\n\n      self._attachCloseHandler(container);\n    });\n\n    container.on('close', function () {\n      // When the dropdown is closed, aria-expanded=\"false\"\n      self.$selection.attr('aria-expanded', 'false');\n      self.$selection.removeAttr('aria-activedescendant');\n      self.$selection.removeAttr('aria-owns');\n\n      self.$selection.trigger('focus');\n\n      self._detachCloseHandler(container);\n    });\n\n    container.on('enable', function () {\n      self.$selection.attr('tabindex', self._tabindex);\n      self.$selection.attr('aria-disabled', 'false');\n    });\n\n    container.on('disable', function () {\n      self.$selection.attr('tabindex', '-1');\n      self.$selection.attr('aria-disabled', 'true');\n    });\n  };\n\n  BaseSelection.prototype._handleBlur = function (evt) {\n    var self = this;\n\n    // This needs to be delayed as the active element is the body when the tab\n    // key is pressed, possibly along with others.\n    window.setTimeout(function () {\n      // Don't trigger `blur` if the focus is still in the selection\n      if (\n        (document.activeElement == self.$selection[0]) ||\n        ($.contains(self.$selection[0], document.activeElement))\n      ) {\n        return;\n      }\n\n      self.trigger('blur', evt);\n    }, 1);\n  };\n\n  BaseSelection.prototype._attachCloseHandler = function (container) {\n\n    $(document.body).on('mousedown.select2.' + container.id, function (e) {\n      var $target = $(e.target);\n\n      var $select = $target.closest('.select2');\n\n      var $all = $('.select2.select2-container--open');\n\n      $all.each(function () {\n        if (this == $select[0]) {\n          return;\n        }\n\n        var $element = Utils.GetData(this, 'element');\n\n        $element.select2('close');\n      });\n    });\n  };\n\n  BaseSelection.prototype._detachCloseHandler = function (container) {\n    $(document.body).off('mousedown.select2.' + container.id);\n  };\n\n  BaseSelection.prototype.position = function ($selection, $container) {\n    var $selectionContainer = $container.find('.selection');\n    $selectionContainer.append($selection);\n  };\n\n  BaseSelection.prototype.destroy = function () {\n    this._detachCloseHandler(this.container);\n  };\n\n  BaseSelection.prototype.update = function (data) {\n    throw new Error('The `update` method must be defined in child classes.');\n  };\n\n  /**\n   * Helper method to abstract the \"enabled\" (not \"disabled\") state of this\n   * object.\n   *\n   * @return {true} if the instance is not disabled.\n   * @return {false} if the instance is disabled.\n   */\n  BaseSelection.prototype.isEnabled = function () {\n    return !this.isDisabled();\n  };\n\n  /**\n   * Helper method to abstract the \"disabled\" state of this object.\n   *\n   * @return {true} if the disabled option is true.\n   * @return {false} if the disabled option is false.\n   */\n  BaseSelection.prototype.isDisabled = function () {\n    return this.options.get('disabled');\n  };\n\n  return BaseSelection;\n});\n\nS2.define('select2/selection/single',[\n  'jquery',\n  './base',\n  '../utils',\n  '../keys'\n], function ($, BaseSelection, Utils, KEYS) {\n  function SingleSelection () {\n    SingleSelection.__super__.constructor.apply(this, arguments);\n  }\n\n  Utils.Extend(SingleSelection, BaseSelection);\n\n  SingleSelection.prototype.render = function () {\n    var $selection = SingleSelection.__super__.render.call(this);\n\n    $selection.addClass('select2-selection--single');\n\n    $selection.html(\n      '<span class=\"select2-selection__rendered\"></span>' +\n      '<span class=\"select2-selection__arrow\" role=\"presentation\">' +\n        '<b role=\"presentation\"></b>' +\n      '</span>'\n    );\n\n    return $selection;\n  };\n\n  SingleSelection.prototype.bind = function (container, $container) {\n    var self = this;\n\n    SingleSelection.__super__.bind.apply(this, arguments);\n\n    var id = container.id + '-container';\n\n    this.$selection.find('.select2-selection__rendered')\n      .attr('id', id)\n      .attr('role', 'textbox')\n      .attr('aria-readonly', 'true');\n    this.$selection.attr('aria-labelledby', id);\n\n    this.$selection.on('mousedown', function (evt) {\n      // Only respond to left clicks\n      if (evt.which !== 1) {\n        return;\n      }\n\n      self.trigger('toggle', {\n        originalEvent: evt\n      });\n    });\n\n    this.$selection.on('focus', function (evt) {\n      // User focuses on the container\n    });\n\n    this.$selection.on('blur', function (evt) {\n      // User exits the container\n    });\n\n    container.on('focus', function (evt) {\n      if (!container.isOpen()) {\n        self.$selection.trigger('focus');\n      }\n    });\n  };\n\n  SingleSelection.prototype.clear = function () {\n    var $rendered = this.$selection.find('.select2-selection__rendered');\n    $rendered.empty();\n    $rendered.removeAttr('title'); // clear tooltip on empty\n  };\n\n  SingleSelection.prototype.display = function (data, container) {\n    var template = this.options.get('templateSelection');\n    var escapeMarkup = this.options.get('escapeMarkup');\n\n    return escapeMarkup(template(data, container));\n  };\n\n  SingleSelection.prototype.selectionContainer = function () {\n    return $('<span></span>');\n  };\n\n  SingleSelection.prototype.update = function (data) {\n    if (data.length === 0) {\n      this.clear();\n      return;\n    }\n\n    var selection = data[0];\n\n    var $rendered = this.$selection.find('.select2-selection__rendered');\n    var formatted = this.display(selection, $rendered);\n\n    $rendered.empty().append(formatted);\n\n    var title = selection.title || selection.text;\n\n    if (title) {\n      $rendered.attr('title', title);\n    } else {\n      $rendered.removeAttr('title');\n    }\n  };\n\n  return SingleSelection;\n});\n\nS2.define('select2/selection/multiple',[\n  'jquery',\n  './base',\n  '../utils'\n], function ($, BaseSelection, Utils) {\n  function MultipleSelection ($element, options) {\n    MultipleSelection.__super__.constructor.apply(this, arguments);\n  }\n\n  Utils.Extend(MultipleSelection, BaseSelection);\n\n  MultipleSelection.prototype.render = function () {\n    var $selection = MultipleSelection.__super__.render.call(this);\n\n    $selection.addClass('select2-selection--multiple');\n\n    $selection.html(\n      '<ul class=\"select2-selection__rendered\"></ul>'\n    );\n\n    return $selection;\n  };\n\n  MultipleSelection.prototype.bind = function (container, $container) {\n    var self = this;\n\n    MultipleSelection.__super__.bind.apply(this, arguments);\n\n    this.$selection.on('click', function (evt) {\n      self.trigger('toggle', {\n        originalEvent: evt\n      });\n    });\n\n    this.$selection.on(\n      'click',\n      '.select2-selection__choice__remove',\n      function (evt) {\n        // Ignore the event if it is disabled\n        if (self.isDisabled()) {\n          return;\n        }\n\n        var $remove = $(this);\n        var $selection = $remove.parent();\n\n        var data = Utils.GetData($selection[0], 'data');\n\n        self.trigger('unselect', {\n          originalEvent: evt,\n          data: data\n        });\n      }\n    );\n  };\n\n  MultipleSelection.prototype.clear = function () {\n    var $rendered = this.$selection.find('.select2-selection__rendered');\n    $rendered.empty();\n    $rendered.removeAttr('title');\n  };\n\n  MultipleSelection.prototype.display = function (data, container) {\n    var template = this.options.get('templateSelection');\n    var escapeMarkup = this.options.get('escapeMarkup');\n\n    return escapeMarkup(template(data, container));\n  };\n\n  MultipleSelection.prototype.selectionContainer = function () {\n    var $container = $(\n      '<li class=\"select2-selection__choice\">' +\n        '<span class=\"select2-selection__choice__remove\" role=\"presentation\">' +\n          '&times;' +\n        '</span>' +\n      '</li>'\n    );\n\n    return $container;\n  };\n\n  MultipleSelection.prototype.update = function (data) {\n    this.clear();\n\n    if (data.length === 0) {\n      return;\n    }\n\n    var $selections = [];\n\n    for (var d = 0; d < data.length; d++) {\n      var selection = data[d];\n\n      var $selection = this.selectionContainer();\n      var formatted = this.display(selection, $selection);\n\n      $selection.append(formatted);\n\n      var title = selection.title || selection.text;\n\n      if (title) {\n        $selection.attr('title', title);\n      }\n\n      Utils.StoreData($selection[0], 'data', selection);\n\n      $selections.push($selection);\n    }\n\n    var $rendered = this.$selection.find('.select2-selection__rendered');\n\n    Utils.appendMany($rendered, $selections);\n  };\n\n  return MultipleSelection;\n});\n\nS2.define('select2/selection/placeholder',[\n  '../utils'\n], function (Utils) {\n  function Placeholder (decorated, $element, options) {\n    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));\n\n    decorated.call(this, $element, options);\n  }\n\n  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {\n    if (typeof placeholder === 'string') {\n      placeholder = {\n        id: '',\n        text: placeholder\n      };\n    }\n\n    return placeholder;\n  };\n\n  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {\n    var $placeholder = this.selectionContainer();\n\n    $placeholder.html(this.display(placeholder));\n    $placeholder.addClass('select2-selection__placeholder')\n                .removeClass('select2-selection__choice');\n\n    return $placeholder;\n  };\n\n  Placeholder.prototype.update = function (decorated, data) {\n    var singlePlaceholder = (\n      data.length == 1 && data[0].id != this.placeholder.id\n    );\n    var multipleSelections = data.length > 1;\n\n    if (multipleSelections || singlePlaceholder) {\n      return decorated.call(this, data);\n    }\n\n    this.clear();\n\n    var $placeholder = this.createPlaceholder(this.placeholder);\n\n    this.$selection.find('.select2-selection__rendered').append($placeholder);\n  };\n\n  return Placeholder;\n});\n\nS2.define('select2/selection/allowClear',[\n  'jquery',\n  '../keys',\n  '../utils'\n], function ($, KEYS, Utils) {\n  function AllowClear () { }\n\n  AllowClear.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    decorated.call(this, container, $container);\n\n    if (this.placeholder == null) {\n      if (this.options.get('debug') && window.console && console.error) {\n        console.error(\n          'Select2: The `allowClear` option should be used in combination ' +\n          'with the `placeholder` option.'\n        );\n      }\n    }\n\n    this.$selection.on('mousedown', '.select2-selection__clear',\n      function (evt) {\n        self._handleClear(evt);\n    });\n\n    container.on('keypress', function (evt) {\n      self._handleKeyboardClear(evt, container);\n    });\n  };\n\n  AllowClear.prototype._handleClear = function (_, evt) {\n    // Ignore the event if it is disabled\n    if (this.isDisabled()) {\n      return;\n    }\n\n    var $clear = this.$selection.find('.select2-selection__clear');\n\n    // Ignore the event if nothing has been selected\n    if ($clear.length === 0) {\n      return;\n    }\n\n    evt.stopPropagation();\n\n    var data = Utils.GetData($clear[0], 'data');\n\n    var previousVal = this.$element.val();\n    this.$element.val(this.placeholder.id);\n\n    var unselectData = {\n      data: data\n    };\n    this.trigger('clear', unselectData);\n    if (unselectData.prevented) {\n      this.$element.val(previousVal);\n      return;\n    }\n\n    for (var d = 0; d < data.length; d++) {\n      unselectData = {\n        data: data[d]\n      };\n\n      // Trigger the `unselect` event, so people can prevent it from being\n      // cleared.\n      this.trigger('unselect', unselectData);\n\n      // If the event was prevented, don't clear it out.\n      if (unselectData.prevented) {\n        this.$element.val(previousVal);\n        return;\n      }\n    }\n\n    this.$element.trigger('input').trigger('change');\n\n    this.trigger('toggle', {});\n  };\n\n  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {\n    if (container.isOpen()) {\n      return;\n    }\n\n    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {\n      this._handleClear(evt);\n    }\n  };\n\n  AllowClear.prototype.update = function (decorated, data) {\n    decorated.call(this, data);\n\n    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||\n        data.length === 0) {\n      return;\n    }\n\n    var removeAll = this.options.get('translations').get('removeAllItems');\n\n    var $remove = $(\n      '<span class=\"select2-selection__clear\" title=\"' + removeAll() +'\">' +\n        '&times;' +\n      '</span>'\n    );\n    Utils.StoreData($remove[0], 'data', data);\n\n    this.$selection.find('.select2-selection__rendered').prepend($remove);\n  };\n\n  return AllowClear;\n});\n\nS2.define('select2/selection/search',[\n  'jquery',\n  '../utils',\n  '../keys'\n], function ($, Utils, KEYS) {\n  function Search (decorated, $element, options) {\n    decorated.call(this, $element, options);\n  }\n\n  Search.prototype.render = function (decorated) {\n    var $search = $(\n      '<li class=\"select2-search select2-search--inline\">' +\n        '<input class=\"select2-search__field\" type=\"search\" tabindex=\"-1\"' +\n        ' autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"none\"' +\n        ' spellcheck=\"false\" role=\"searchbox\" aria-autocomplete=\"list\" />' +\n      '</li>'\n    );\n\n    this.$searchContainer = $search;\n    this.$search = $search.find('input');\n\n    var $rendered = decorated.call(this);\n\n    this._transferTabIndex();\n\n    return $rendered;\n  };\n\n  Search.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    var resultsId = container.id + '-results';\n\n    decorated.call(this, container, $container);\n\n    container.on('open', function () {\n      self.$search.attr('aria-controls', resultsId);\n      self.$search.trigger('focus');\n    });\n\n    container.on('close', function () {\n      self.$search.val('');\n      self.$search.removeAttr('aria-controls');\n      self.$search.removeAttr('aria-activedescendant');\n      self.$search.trigger('focus');\n    });\n\n    container.on('enable', function () {\n      self.$search.prop('disabled', false);\n\n      self._transferTabIndex();\n    });\n\n    container.on('disable', function () {\n      self.$search.prop('disabled', true);\n    });\n\n    container.on('focus', function (evt) {\n      self.$search.trigger('focus');\n    });\n\n    container.on('results:focus', function (params) {\n      if (params.data._resultId) {\n        self.$search.attr('aria-activedescendant', params.data._resultId);\n      } else {\n        self.$search.removeAttr('aria-activedescendant');\n      }\n    });\n\n    this.$selection.on('focusin', '.select2-search--inline', function (evt) {\n      self.trigger('focus', evt);\n    });\n\n    this.$selection.on('focusout', '.select2-search--inline', function (evt) {\n      self._handleBlur(evt);\n    });\n\n    this.$selection.on('keydown', '.select2-search--inline', function (evt) {\n      evt.stopPropagation();\n\n      self.trigger('keypress', evt);\n\n      self._keyUpPrevented = evt.isDefaultPrevented();\n\n      var key = evt.which;\n\n      if (key === KEYS.BACKSPACE && self.$search.val() === '') {\n        var $previousChoice = self.$searchContainer\n          .prev('.select2-selection__choice');\n\n        if ($previousChoice.length > 0) {\n          var item = Utils.GetData($previousChoice[0], 'data');\n\n          self.searchRemoveChoice(item);\n\n          evt.preventDefault();\n        }\n      }\n    });\n\n    this.$selection.on('click', '.select2-search--inline', function (evt) {\n      if (self.$search.val()) {\n        evt.stopPropagation();\n      }\n    });\n\n    // Try to detect the IE version should the `documentMode` property that\n    // is stored on the document. This is only implemented in IE and is\n    // slightly cleaner than doing a user agent check.\n    // This property is not available in Edge, but Edge also doesn't have\n    // this bug.\n    var msie = document.documentMode;\n    var disableInputEvents = msie && msie <= 11;\n\n    // Workaround for browsers which do not support the `input` event\n    // This will prevent double-triggering of events for browsers which support\n    // both the `keyup` and `input` events.\n    this.$selection.on(\n      'input.searchcheck',\n      '.select2-search--inline',\n      function (evt) {\n        // IE will trigger the `input` event when a placeholder is used on a\n        // search box. To get around this issue, we are forced to ignore all\n        // `input` events in IE and keep using `keyup`.\n        if (disableInputEvents) {\n          self.$selection.off('input.search input.searchcheck');\n          return;\n        }\n\n        // Unbind the duplicated `keyup` event\n        self.$selection.off('keyup.search');\n      }\n    );\n\n    this.$selection.on(\n      'keyup.search input.search',\n      '.select2-search--inline',\n      function (evt) {\n        // IE will trigger the `input` event when a placeholder is used on a\n        // search box. To get around this issue, we are forced to ignore all\n        // `input` events in IE and keep using `keyup`.\n        if (disableInputEvents && evt.type === 'input') {\n          self.$selection.off('input.search input.searchcheck');\n          return;\n        }\n\n        var key = evt.which;\n\n        // We can freely ignore events from modifier keys\n        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {\n          return;\n        }\n\n        // Tabbing will be handled during the `keydown` phase\n        if (key == KEYS.TAB) {\n          return;\n        }\n\n        self.handleSearch(evt);\n      }\n    );\n  };\n\n  /**\n   * This method will transfer the tabindex attribute from the rendered\n   * selection to the search box. This allows for the search box to be used as\n   * the primary focus instead of the selection container.\n   *\n   * @private\n   */\n  Search.prototype._transferTabIndex = function (decorated) {\n    this.$search.attr('tabindex', this.$selection.attr('tabindex'));\n    this.$selection.attr('tabindex', '-1');\n  };\n\n  Search.prototype.createPlaceholder = function (decorated, placeholder) {\n    this.$search.attr('placeholder', placeholder.text);\n  };\n\n  Search.prototype.update = function (decorated, data) {\n    var searchHadFocus = this.$search[0] == document.activeElement;\n\n    this.$search.attr('placeholder', '');\n\n    decorated.call(this, data);\n\n    this.$selection.find('.select2-selection__rendered')\n                   .append(this.$searchContainer);\n\n    this.resizeSearch();\n    if (searchHadFocus) {\n      this.$search.trigger('focus');\n    }\n  };\n\n  Search.prototype.handleSearch = function () {\n    this.resizeSearch();\n\n    if (!this._keyUpPrevented) {\n      var input = this.$search.val();\n\n      this.trigger('query', {\n        term: input\n      });\n    }\n\n    this._keyUpPrevented = false;\n  };\n\n  Search.prototype.searchRemoveChoice = function (decorated, item) {\n    this.trigger('unselect', {\n      data: item\n    });\n\n    this.$search.val(item.text);\n    this.handleSearch();\n  };\n\n  Search.prototype.resizeSearch = function () {\n    this.$search.css('width', '25px');\n\n    var width = '';\n\n    if (this.$search.attr('placeholder') !== '') {\n      width = this.$selection.find('.select2-selection__rendered').width();\n    } else {\n      var minimumWidth = this.$search.val().length + 1;\n\n      width = (minimumWidth * 0.75) + 'em';\n    }\n\n    this.$search.css('width', width);\n  };\n\n  return Search;\n});\n\nS2.define('select2/selection/eventRelay',[\n  'jquery'\n], function ($) {\n  function EventRelay () { }\n\n  EventRelay.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n    var relayEvents = [\n      'open', 'opening',\n      'close', 'closing',\n      'select', 'selecting',\n      'unselect', 'unselecting',\n      'clear', 'clearing'\n    ];\n\n    var preventableEvents = [\n      'opening', 'closing', 'selecting', 'unselecting', 'clearing'\n    ];\n\n    decorated.call(this, container, $container);\n\n    container.on('*', function (name, params) {\n      // Ignore events that should not be relayed\n      if ($.inArray(name, relayEvents) === -1) {\n        return;\n      }\n\n      // The parameters should always be an object\n      params = params || {};\n\n      // Generate the jQuery event for the Select2 event\n      var evt = $.Event('select2:' + name, {\n        params: params\n      });\n\n      self.$element.trigger(evt);\n\n      // Only handle preventable events if it was one\n      if ($.inArray(name, preventableEvents) === -1) {\n        return;\n      }\n\n      params.prevented = evt.isDefaultPrevented();\n    });\n  };\n\n  return EventRelay;\n});\n\nS2.define('select2/translation',[\n  'jquery',\n  'require'\n], function ($, require) {\n  function Translation (dict) {\n    this.dict = dict || {};\n  }\n\n  Translation.prototype.all = function () {\n    return this.dict;\n  };\n\n  Translation.prototype.get = function (key) {\n    return this.dict[key];\n  };\n\n  Translation.prototype.extend = function (translation) {\n    this.dict = $.extend({}, translation.all(), this.dict);\n  };\n\n  // Static functions\n\n  Translation._cache = {};\n\n  Translation.loadPath = function (path) {\n    if (!(path in Translation._cache)) {\n      var translations = require(path);\n\n      Translation._cache[path] = translations;\n    }\n\n    return new Translation(Translation._cache[path]);\n  };\n\n  return Translation;\n});\n\nS2.define('select2/diacritics',[\n\n], function () {\n  var diacritics = {\n    '\\u24B6': 'A',\n    '\\uFF21': 'A',\n    '\\u00C0': 'A',\n    '\\u00C1': 'A',\n    '\\u00C2': 'A',\n    '\\u1EA6': 'A',\n    '\\u1EA4': 'A',\n    '\\u1EAA': 'A',\n    '\\u1EA8': 'A',\n    '\\u00C3': 'A',\n    '\\u0100': 'A',\n    '\\u0102': 'A',\n    '\\u1EB0': 'A',\n    '\\u1EAE': 'A',\n    '\\u1EB4': 'A',\n    '\\u1EB2': 'A',\n    '\\u0226': 'A',\n    '\\u01E0': 'A',\n    '\\u00C4': 'A',\n    '\\u01DE': 'A',\n    '\\u1EA2': 'A',\n    '\\u00C5': 'A',\n    '\\u01FA': 'A',\n    '\\u01CD': 'A',\n    '\\u0200': 'A',\n    '\\u0202': 'A',\n    '\\u1EA0': 'A',\n    '\\u1EAC': 'A',\n    '\\u1EB6': 'A',\n    '\\u1E00': 'A',\n    '\\u0104': 'A',\n    '\\u023A': 'A',\n    '\\u2C6F': 'A',\n    '\\uA732': 'AA',\n    '\\u00C6': 'AE',\n    '\\u01FC': 'AE',\n    '\\u01E2': 'AE',\n    '\\uA734': 'AO',\n    '\\uA736': 'AU',\n    '\\uA738': 'AV',\n    '\\uA73A': 'AV',\n    '\\uA73C': 'AY',\n    '\\u24B7': 'B',\n    '\\uFF22': 'B',\n    '\\u1E02': 'B',\n    '\\u1E04': 'B',\n    '\\u1E06': 'B',\n    '\\u0243': 'B',\n    '\\u0182': 'B',\n    '\\u0181': 'B',\n    '\\u24B8': 'C',\n    '\\uFF23': 'C',\n    '\\u0106': 'C',\n    '\\u0108': 'C',\n    '\\u010A': 'C',\n    '\\u010C': 'C',\n    '\\u00C7': 'C',\n    '\\u1E08': 'C',\n    '\\u0187': 'C',\n    '\\u023B': 'C',\n    '\\uA73E': 'C',\n    '\\u24B9': 'D',\n    '\\uFF24': 'D',\n    '\\u1E0A': 'D',\n    '\\u010E': 'D',\n    '\\u1E0C': 'D',\n    '\\u1E10': 'D',\n    '\\u1E12': 'D',\n    '\\u1E0E': 'D',\n    '\\u0110': 'D',\n    '\\u018B': 'D',\n    '\\u018A': 'D',\n    '\\u0189': 'D',\n    '\\uA779': 'D',\n    '\\u01F1': 'DZ',\n    '\\u01C4': 'DZ',\n    '\\u01F2': 'Dz',\n    '\\u01C5': 'Dz',\n    '\\u24BA': 'E',\n    '\\uFF25': 'E',\n    '\\u00C8': 'E',\n    '\\u00C9': 'E',\n    '\\u00CA': 'E',\n    '\\u1EC0': 'E',\n    '\\u1EBE': 'E',\n    '\\u1EC4': 'E',\n    '\\u1EC2': 'E',\n    '\\u1EBC': 'E',\n    '\\u0112': 'E',\n    '\\u1E14': 'E',\n    '\\u1E16': 'E',\n    '\\u0114': 'E',\n    '\\u0116': 'E',\n    '\\u00CB': 'E',\n    '\\u1EBA': 'E',\n    '\\u011A': 'E',\n    '\\u0204': 'E',\n    '\\u0206': 'E',\n    '\\u1EB8': 'E',\n    '\\u1EC6': 'E',\n    '\\u0228': 'E',\n    '\\u1E1C': 'E',\n    '\\u0118': 'E',\n    '\\u1E18': 'E',\n    '\\u1E1A': 'E',\n    '\\u0190': 'E',\n    '\\u018E': 'E',\n    '\\u24BB': 'F',\n    '\\uFF26': 'F',\n    '\\u1E1E': 'F',\n    '\\u0191': 'F',\n    '\\uA77B': 'F',\n    '\\u24BC': 'G',\n    '\\uFF27': 'G',\n    '\\u01F4': 'G',\n    '\\u011C': 'G',\n    '\\u1E20': 'G',\n    '\\u011E': 'G',\n    '\\u0120': 'G',\n    '\\u01E6': 'G',\n    '\\u0122': 'G',\n    '\\u01E4': 'G',\n    '\\u0193': 'G',\n    '\\uA7A0': 'G',\n    '\\uA77D': 'G',\n    '\\uA77E': 'G',\n    '\\u24BD': 'H',\n    '\\uFF28': 'H',\n    '\\u0124': 'H',\n    '\\u1E22': 'H',\n    '\\u1E26': 'H',\n    '\\u021E': 'H',\n    '\\u1E24': 'H',\n    '\\u1E28': 'H',\n    '\\u1E2A': 'H',\n    '\\u0126': 'H',\n    '\\u2C67': 'H',\n    '\\u2C75': 'H',\n    '\\uA78D': 'H',\n    '\\u24BE': 'I',\n    '\\uFF29': 'I',\n    '\\u00CC': 'I',\n    '\\u00CD': 'I',\n    '\\u00CE': 'I',\n    '\\u0128': 'I',\n    '\\u012A': 'I',\n    '\\u012C': 'I',\n    '\\u0130': 'I',\n    '\\u00CF': 'I',\n    '\\u1E2E': 'I',\n    '\\u1EC8': 'I',\n    '\\u01CF': 'I',\n    '\\u0208': 'I',\n    '\\u020A': 'I',\n    '\\u1ECA': 'I',\n    '\\u012E': 'I',\n    '\\u1E2C': 'I',\n    '\\u0197': 'I',\n    '\\u24BF': 'J',\n    '\\uFF2A': 'J',\n    '\\u0134': 'J',\n    '\\u0248': 'J',\n    '\\u24C0': 'K',\n    '\\uFF2B': 'K',\n    '\\u1E30': 'K',\n    '\\u01E8': 'K',\n    '\\u1E32': 'K',\n    '\\u0136': 'K',\n    '\\u1E34': 'K',\n    '\\u0198': 'K',\n    '\\u2C69': 'K',\n    '\\uA740': 'K',\n    '\\uA742': 'K',\n    '\\uA744': 'K',\n    '\\uA7A2': 'K',\n    '\\u24C1': 'L',\n    '\\uFF2C': 'L',\n    '\\u013F': 'L',\n    '\\u0139': 'L',\n    '\\u013D': 'L',\n    '\\u1E36': 'L',\n    '\\u1E38': 'L',\n    '\\u013B': 'L',\n    '\\u1E3C': 'L',\n    '\\u1E3A': 'L',\n    '\\u0141': 'L',\n    '\\u023D': 'L',\n    '\\u2C62': 'L',\n    '\\u2C60': 'L',\n    '\\uA748': 'L',\n    '\\uA746': 'L',\n    '\\uA780': 'L',\n    '\\u01C7': 'LJ',\n    '\\u01C8': 'Lj',\n    '\\u24C2': 'M',\n    '\\uFF2D': 'M',\n    '\\u1E3E': 'M',\n    '\\u1E40': 'M',\n    '\\u1E42': 'M',\n    '\\u2C6E': 'M',\n    '\\u019C': 'M',\n    '\\u24C3': 'N',\n    '\\uFF2E': 'N',\n    '\\u01F8': 'N',\n    '\\u0143': 'N',\n    '\\u00D1': 'N',\n    '\\u1E44': 'N',\n    '\\u0147': 'N',\n    '\\u1E46': 'N',\n    '\\u0145': 'N',\n    '\\u1E4A': 'N',\n    '\\u1E48': 'N',\n    '\\u0220': 'N',\n    '\\u019D': 'N',\n    '\\uA790': 'N',\n    '\\uA7A4': 'N',\n    '\\u01CA': 'NJ',\n    '\\u01CB': 'Nj',\n    '\\u24C4': 'O',\n    '\\uFF2F': 'O',\n    '\\u00D2': 'O',\n    '\\u00D3': 'O',\n    '\\u00D4': 'O',\n    '\\u1ED2': 'O',\n    '\\u1ED0': 'O',\n    '\\u1ED6': 'O',\n    '\\u1ED4': 'O',\n    '\\u00D5': 'O',\n    '\\u1E4C': 'O',\n    '\\u022C': 'O',\n    '\\u1E4E': 'O',\n    '\\u014C': 'O',\n    '\\u1E50': 'O',\n    '\\u1E52': 'O',\n    '\\u014E': 'O',\n    '\\u022E': 'O',\n    '\\u0230': 'O',\n    '\\u00D6': 'O',\n    '\\u022A': 'O',\n    '\\u1ECE': 'O',\n    '\\u0150': 'O',\n    '\\u01D1': 'O',\n    '\\u020C': 'O',\n    '\\u020E': 'O',\n    '\\u01A0': 'O',\n    '\\u1EDC': 'O',\n    '\\u1EDA': 'O',\n    '\\u1EE0': 'O',\n    '\\u1EDE': 'O',\n    '\\u1EE2': 'O',\n    '\\u1ECC': 'O',\n    '\\u1ED8': 'O',\n    '\\u01EA': 'O',\n    '\\u01EC': 'O',\n    '\\u00D8': 'O',\n    '\\u01FE': 'O',\n    '\\u0186': 'O',\n    '\\u019F': 'O',\n    '\\uA74A': 'O',\n    '\\uA74C': 'O',\n    '\\u0152': 'OE',\n    '\\u01A2': 'OI',\n    '\\uA74E': 'OO',\n    '\\u0222': 'OU',\n    '\\u24C5': 'P',\n    '\\uFF30': 'P',\n    '\\u1E54': 'P',\n    '\\u1E56': 'P',\n    '\\u01A4': 'P',\n    '\\u2C63': 'P',\n    '\\uA750': 'P',\n    '\\uA752': 'P',\n    '\\uA754': 'P',\n    '\\u24C6': 'Q',\n    '\\uFF31': 'Q',\n    '\\uA756': 'Q',\n    '\\uA758': 'Q',\n    '\\u024A': 'Q',\n    '\\u24C7': 'R',\n    '\\uFF32': 'R',\n    '\\u0154': 'R',\n    '\\u1E58': 'R',\n    '\\u0158': 'R',\n    '\\u0210': 'R',\n    '\\u0212': 'R',\n    '\\u1E5A': 'R',\n    '\\u1E5C': 'R',\n    '\\u0156': 'R',\n    '\\u1E5E': 'R',\n    '\\u024C': 'R',\n    '\\u2C64': 'R',\n    '\\uA75A': 'R',\n    '\\uA7A6': 'R',\n    '\\uA782': 'R',\n    '\\u24C8': 'S',\n    '\\uFF33': 'S',\n    '\\u1E9E': 'S',\n    '\\u015A': 'S',\n    '\\u1E64': 'S',\n    '\\u015C': 'S',\n    '\\u1E60': 'S',\n    '\\u0160': 'S',\n    '\\u1E66': 'S',\n    '\\u1E62': 'S',\n    '\\u1E68': 'S',\n    '\\u0218': 'S',\n    '\\u015E': 'S',\n    '\\u2C7E': 'S',\n    '\\uA7A8': 'S',\n    '\\uA784': 'S',\n    '\\u24C9': 'T',\n    '\\uFF34': 'T',\n    '\\u1E6A': 'T',\n    '\\u0164': 'T',\n    '\\u1E6C': 'T',\n    '\\u021A': 'T',\n    '\\u0162': 'T',\n    '\\u1E70': 'T',\n    '\\u1E6E': 'T',\n    '\\u0166': 'T',\n    '\\u01AC': 'T',\n    '\\u01AE': 'T',\n    '\\u023E': 'T',\n    '\\uA786': 'T',\n    '\\uA728': 'TZ',\n    '\\u24CA': 'U',\n    '\\uFF35': 'U',\n    '\\u00D9': 'U',\n    '\\u00DA': 'U',\n    '\\u00DB': 'U',\n    '\\u0168': 'U',\n    '\\u1E78': 'U',\n    '\\u016A': 'U',\n    '\\u1E7A': 'U',\n    '\\u016C': 'U',\n    '\\u00DC': 'U',\n    '\\u01DB': 'U',\n    '\\u01D7': 'U',\n    '\\u01D5': 'U',\n    '\\u01D9': 'U',\n    '\\u1EE6': 'U',\n    '\\u016E': 'U',\n    '\\u0170': 'U',\n    '\\u01D3': 'U',\n    '\\u0214': 'U',\n    '\\u0216': 'U',\n    '\\u01AF': 'U',\n    '\\u1EEA': 'U',\n    '\\u1EE8': 'U',\n    '\\u1EEE': 'U',\n    '\\u1EEC': 'U',\n    '\\u1EF0': 'U',\n    '\\u1EE4': 'U',\n    '\\u1E72': 'U',\n    '\\u0172': 'U',\n    '\\u1E76': 'U',\n    '\\u1E74': 'U',\n    '\\u0244': 'U',\n    '\\u24CB': 'V',\n    '\\uFF36': 'V',\n    '\\u1E7C': 'V',\n    '\\u1E7E': 'V',\n    '\\u01B2': 'V',\n    '\\uA75E': 'V',\n    '\\u0245': 'V',\n    '\\uA760': 'VY',\n    '\\u24CC': 'W',\n    '\\uFF37': 'W',\n    '\\u1E80': 'W',\n    '\\u1E82': 'W',\n    '\\u0174': 'W',\n    '\\u1E86': 'W',\n    '\\u1E84': 'W',\n    '\\u1E88': 'W',\n    '\\u2C72': 'W',\n    '\\u24CD': 'X',\n    '\\uFF38': 'X',\n    '\\u1E8A': 'X',\n    '\\u1E8C': 'X',\n    '\\u24CE': 'Y',\n    '\\uFF39': 'Y',\n    '\\u1EF2': 'Y',\n    '\\u00DD': 'Y',\n    '\\u0176': 'Y',\n    '\\u1EF8': 'Y',\n    '\\u0232': 'Y',\n    '\\u1E8E': 'Y',\n    '\\u0178': 'Y',\n    '\\u1EF6': 'Y',\n    '\\u1EF4': 'Y',\n    '\\u01B3': 'Y',\n    '\\u024E': 'Y',\n    '\\u1EFE': 'Y',\n    '\\u24CF': 'Z',\n    '\\uFF3A': 'Z',\n    '\\u0179': 'Z',\n    '\\u1E90': 'Z',\n    '\\u017B': 'Z',\n    '\\u017D': 'Z',\n    '\\u1E92': 'Z',\n    '\\u1E94': 'Z',\n    '\\u01B5': 'Z',\n    '\\u0224': 'Z',\n    '\\u2C7F': 'Z',\n    '\\u2C6B': 'Z',\n    '\\uA762': 'Z',\n    '\\u24D0': 'a',\n    '\\uFF41': 'a',\n    '\\u1E9A': 'a',\n    '\\u00E0': 'a',\n    '\\u00E1': 'a',\n    '\\u00E2': 'a',\n    '\\u1EA7': 'a',\n    '\\u1EA5': 'a',\n    '\\u1EAB': 'a',\n    '\\u1EA9': 'a',\n    '\\u00E3': 'a',\n    '\\u0101': 'a',\n    '\\u0103': 'a',\n    '\\u1EB1': 'a',\n    '\\u1EAF': 'a',\n    '\\u1EB5': 'a',\n    '\\u1EB3': 'a',\n    '\\u0227': 'a',\n    '\\u01E1': 'a',\n    '\\u00E4': 'a',\n    '\\u01DF': 'a',\n    '\\u1EA3': 'a',\n    '\\u00E5': 'a',\n    '\\u01FB': 'a',\n    '\\u01CE': 'a',\n    '\\u0201': 'a',\n    '\\u0203': 'a',\n    '\\u1EA1': 'a',\n    '\\u1EAD': 'a',\n    '\\u1EB7': 'a',\n    '\\u1E01': 'a',\n    '\\u0105': 'a',\n    '\\u2C65': 'a',\n    '\\u0250': 'a',\n    '\\uA733': 'aa',\n    '\\u00E6': 'ae',\n    '\\u01FD': 'ae',\n    '\\u01E3': 'ae',\n    '\\uA735': 'ao',\n    '\\uA737': 'au',\n    '\\uA739': 'av',\n    '\\uA73B': 'av',\n    '\\uA73D': 'ay',\n    '\\u24D1': 'b',\n    '\\uFF42': 'b',\n    '\\u1E03': 'b',\n    '\\u1E05': 'b',\n    '\\u1E07': 'b',\n    '\\u0180': 'b',\n    '\\u0183': 'b',\n    '\\u0253': 'b',\n    '\\u24D2': 'c',\n    '\\uFF43': 'c',\n    '\\u0107': 'c',\n    '\\u0109': 'c',\n    '\\u010B': 'c',\n    '\\u010D': 'c',\n    '\\u00E7': 'c',\n    '\\u1E09': 'c',\n    '\\u0188': 'c',\n    '\\u023C': 'c',\n    '\\uA73F': 'c',\n    '\\u2184': 'c',\n    '\\u24D3': 'd',\n    '\\uFF44': 'd',\n    '\\u1E0B': 'd',\n    '\\u010F': 'd',\n    '\\u1E0D': 'd',\n    '\\u1E11': 'd',\n    '\\u1E13': 'd',\n    '\\u1E0F': 'd',\n    '\\u0111': 'd',\n    '\\u018C': 'd',\n    '\\u0256': 'd',\n    '\\u0257': 'd',\n    '\\uA77A': 'd',\n    '\\u01F3': 'dz',\n    '\\u01C6': 'dz',\n    '\\u24D4': 'e',\n    '\\uFF45': 'e',\n    '\\u00E8': 'e',\n    '\\u00E9': 'e',\n    '\\u00EA': 'e',\n    '\\u1EC1': 'e',\n    '\\u1EBF': 'e',\n    '\\u1EC5': 'e',\n    '\\u1EC3': 'e',\n    '\\u1EBD': 'e',\n    '\\u0113': 'e',\n    '\\u1E15': 'e',\n    '\\u1E17': 'e',\n    '\\u0115': 'e',\n    '\\u0117': 'e',\n    '\\u00EB': 'e',\n    '\\u1EBB': 'e',\n    '\\u011B': 'e',\n    '\\u0205': 'e',\n    '\\u0207': 'e',\n    '\\u1EB9': 'e',\n    '\\u1EC7': 'e',\n    '\\u0229': 'e',\n    '\\u1E1D': 'e',\n    '\\u0119': 'e',\n    '\\u1E19': 'e',\n    '\\u1E1B': 'e',\n    '\\u0247': 'e',\n    '\\u025B': 'e',\n    '\\u01DD': 'e',\n    '\\u24D5': 'f',\n    '\\uFF46': 'f',\n    '\\u1E1F': 'f',\n    '\\u0192': 'f',\n    '\\uA77C': 'f',\n    '\\u24D6': 'g',\n    '\\uFF47': 'g',\n    '\\u01F5': 'g',\n    '\\u011D': 'g',\n    '\\u1E21': 'g',\n    '\\u011F': 'g',\n    '\\u0121': 'g',\n    '\\u01E7': 'g',\n    '\\u0123': 'g',\n    '\\u01E5': 'g',\n    '\\u0260': 'g',\n    '\\uA7A1': 'g',\n    '\\u1D79': 'g',\n    '\\uA77F': 'g',\n    '\\u24D7': 'h',\n    '\\uFF48': 'h',\n    '\\u0125': 'h',\n    '\\u1E23': 'h',\n    '\\u1E27': 'h',\n    '\\u021F': 'h',\n    '\\u1E25': 'h',\n    '\\u1E29': 'h',\n    '\\u1E2B': 'h',\n    '\\u1E96': 'h',\n    '\\u0127': 'h',\n    '\\u2C68': 'h',\n    '\\u2C76': 'h',\n    '\\u0265': 'h',\n    '\\u0195': 'hv',\n    '\\u24D8': 'i',\n    '\\uFF49': 'i',\n    '\\u00EC': 'i',\n    '\\u00ED': 'i',\n    '\\u00EE': 'i',\n    '\\u0129': 'i',\n    '\\u012B': 'i',\n    '\\u012D': 'i',\n    '\\u00EF': 'i',\n    '\\u1E2F': 'i',\n    '\\u1EC9': 'i',\n    '\\u01D0': 'i',\n    '\\u0209': 'i',\n    '\\u020B': 'i',\n    '\\u1ECB': 'i',\n    '\\u012F': 'i',\n    '\\u1E2D': 'i',\n    '\\u0268': 'i',\n    '\\u0131': 'i',\n    '\\u24D9': 'j',\n    '\\uFF4A': 'j',\n    '\\u0135': 'j',\n    '\\u01F0': 'j',\n    '\\u0249': 'j',\n    '\\u24DA': 'k',\n    '\\uFF4B': 'k',\n    '\\u1E31': 'k',\n    '\\u01E9': 'k',\n    '\\u1E33': 'k',\n    '\\u0137': 'k',\n    '\\u1E35': 'k',\n    '\\u0199': 'k',\n    '\\u2C6A': 'k',\n    '\\uA741': 'k',\n    '\\uA743': 'k',\n    '\\uA745': 'k',\n    '\\uA7A3': 'k',\n    '\\u24DB': 'l',\n    '\\uFF4C': 'l',\n    '\\u0140': 'l',\n    '\\u013A': 'l',\n    '\\u013E': 'l',\n    '\\u1E37': 'l',\n    '\\u1E39': 'l',\n    '\\u013C': 'l',\n    '\\u1E3D': 'l',\n    '\\u1E3B': 'l',\n    '\\u017F': 'l',\n    '\\u0142': 'l',\n    '\\u019A': 'l',\n    '\\u026B': 'l',\n    '\\u2C61': 'l',\n    '\\uA749': 'l',\n    '\\uA781': 'l',\n    '\\uA747': 'l',\n    '\\u01C9': 'lj',\n    '\\u24DC': 'm',\n    '\\uFF4D': 'm',\n    '\\u1E3F': 'm',\n    '\\u1E41': 'm',\n    '\\u1E43': 'm',\n    '\\u0271': 'm',\n    '\\u026F': 'm',\n    '\\u24DD': 'n',\n    '\\uFF4E': 'n',\n    '\\u01F9': 'n',\n    '\\u0144': 'n',\n    '\\u00F1': 'n',\n    '\\u1E45': 'n',\n    '\\u0148': 'n',\n    '\\u1E47': 'n',\n    '\\u0146': 'n',\n    '\\u1E4B': 'n',\n    '\\u1E49': 'n',\n    '\\u019E': 'n',\n    '\\u0272': 'n',\n    '\\u0149': 'n',\n    '\\uA791': 'n',\n    '\\uA7A5': 'n',\n    '\\u01CC': 'nj',\n    '\\u24DE': 'o',\n    '\\uFF4F': 'o',\n    '\\u00F2': 'o',\n    '\\u00F3': 'o',\n    '\\u00F4': 'o',\n    '\\u1ED3': 'o',\n    '\\u1ED1': 'o',\n    '\\u1ED7': 'o',\n    '\\u1ED5': 'o',\n    '\\u00F5': 'o',\n    '\\u1E4D': 'o',\n    '\\u022D': 'o',\n    '\\u1E4F': 'o',\n    '\\u014D': 'o',\n    '\\u1E51': 'o',\n    '\\u1E53': 'o',\n    '\\u014F': 'o',\n    '\\u022F': 'o',\n    '\\u0231': 'o',\n    '\\u00F6': 'o',\n    '\\u022B': 'o',\n    '\\u1ECF': 'o',\n    '\\u0151': 'o',\n    '\\u01D2': 'o',\n    '\\u020D': 'o',\n    '\\u020F': 'o',\n    '\\u01A1': 'o',\n    '\\u1EDD': 'o',\n    '\\u1EDB': 'o',\n    '\\u1EE1': 'o',\n    '\\u1EDF': 'o',\n    '\\u1EE3': 'o',\n    '\\u1ECD': 'o',\n    '\\u1ED9': 'o',\n    '\\u01EB': 'o',\n    '\\u01ED': 'o',\n    '\\u00F8': 'o',\n    '\\u01FF': 'o',\n    '\\u0254': 'o',\n    '\\uA74B': 'o',\n    '\\uA74D': 'o',\n    '\\u0275': 'o',\n    '\\u0153': 'oe',\n    '\\u01A3': 'oi',\n    '\\u0223': 'ou',\n    '\\uA74F': 'oo',\n    '\\u24DF': 'p',\n    '\\uFF50': 'p',\n    '\\u1E55': 'p',\n    '\\u1E57': 'p',\n    '\\u01A5': 'p',\n    '\\u1D7D': 'p',\n    '\\uA751': 'p',\n    '\\uA753': 'p',\n    '\\uA755': 'p',\n    '\\u24E0': 'q',\n    '\\uFF51': 'q',\n    '\\u024B': 'q',\n    '\\uA757': 'q',\n    '\\uA759': 'q',\n    '\\u24E1': 'r',\n    '\\uFF52': 'r',\n    '\\u0155': 'r',\n    '\\u1E59': 'r',\n    '\\u0159': 'r',\n    '\\u0211': 'r',\n    '\\u0213': 'r',\n    '\\u1E5B': 'r',\n    '\\u1E5D': 'r',\n    '\\u0157': 'r',\n    '\\u1E5F': 'r',\n    '\\u024D': 'r',\n    '\\u027D': 'r',\n    '\\uA75B': 'r',\n    '\\uA7A7': 'r',\n    '\\uA783': 'r',\n    '\\u24E2': 's',\n    '\\uFF53': 's',\n    '\\u00DF': 's',\n    '\\u015B': 's',\n    '\\u1E65': 's',\n    '\\u015D': 's',\n    '\\u1E61': 's',\n    '\\u0161': 's',\n    '\\u1E67': 's',\n    '\\u1E63': 's',\n    '\\u1E69': 's',\n    '\\u0219': 's',\n    '\\u015F': 's',\n    '\\u023F': 's',\n    '\\uA7A9': 's',\n    '\\uA785': 's',\n    '\\u1E9B': 's',\n    '\\u24E3': 't',\n    '\\uFF54': 't',\n    '\\u1E6B': 't',\n    '\\u1E97': 't',\n    '\\u0165': 't',\n    '\\u1E6D': 't',\n    '\\u021B': 't',\n    '\\u0163': 't',\n    '\\u1E71': 't',\n    '\\u1E6F': 't',\n    '\\u0167': 't',\n    '\\u01AD': 't',\n    '\\u0288': 't',\n    '\\u2C66': 't',\n    '\\uA787': 't',\n    '\\uA729': 'tz',\n    '\\u24E4': 'u',\n    '\\uFF55': 'u',\n    '\\u00F9': 'u',\n    '\\u00FA': 'u',\n    '\\u00FB': 'u',\n    '\\u0169': 'u',\n    '\\u1E79': 'u',\n    '\\u016B': 'u',\n    '\\u1E7B': 'u',\n    '\\u016D': 'u',\n    '\\u00FC': 'u',\n    '\\u01DC': 'u',\n    '\\u01D8': 'u',\n    '\\u01D6': 'u',\n    '\\u01DA': 'u',\n    '\\u1EE7': 'u',\n    '\\u016F': 'u',\n    '\\u0171': 'u',\n    '\\u01D4': 'u',\n    '\\u0215': 'u',\n    '\\u0217': 'u',\n    '\\u01B0': 'u',\n    '\\u1EEB': 'u',\n    '\\u1EE9': 'u',\n    '\\u1EEF': 'u',\n    '\\u1EED': 'u',\n    '\\u1EF1': 'u',\n    '\\u1EE5': 'u',\n    '\\u1E73': 'u',\n    '\\u0173': 'u',\n    '\\u1E77': 'u',\n    '\\u1E75': 'u',\n    '\\u0289': 'u',\n    '\\u24E5': 'v',\n    '\\uFF56': 'v',\n    '\\u1E7D': 'v',\n    '\\u1E7F': 'v',\n    '\\u028B': 'v',\n    '\\uA75F': 'v',\n    '\\u028C': 'v',\n    '\\uA761': 'vy',\n    '\\u24E6': 'w',\n    '\\uFF57': 'w',\n    '\\u1E81': 'w',\n    '\\u1E83': 'w',\n    '\\u0175': 'w',\n    '\\u1E87': 'w',\n    '\\u1E85': 'w',\n    '\\u1E98': 'w',\n    '\\u1E89': 'w',\n    '\\u2C73': 'w',\n    '\\u24E7': 'x',\n    '\\uFF58': 'x',\n    '\\u1E8B': 'x',\n    '\\u1E8D': 'x',\n    '\\u24E8': 'y',\n    '\\uFF59': 'y',\n    '\\u1EF3': 'y',\n    '\\u00FD': 'y',\n    '\\u0177': 'y',\n    '\\u1EF9': 'y',\n    '\\u0233': 'y',\n    '\\u1E8F': 'y',\n    '\\u00FF': 'y',\n    '\\u1EF7': 'y',\n    '\\u1E99': 'y',\n    '\\u1EF5': 'y',\n    '\\u01B4': 'y',\n    '\\u024F': 'y',\n    '\\u1EFF': 'y',\n    '\\u24E9': 'z',\n    '\\uFF5A': 'z',\n    '\\u017A': 'z',\n    '\\u1E91': 'z',\n    '\\u017C': 'z',\n    '\\u017E': 'z',\n    '\\u1E93': 'z',\n    '\\u1E95': 'z',\n    '\\u01B6': 'z',\n    '\\u0225': 'z',\n    '\\u0240': 'z',\n    '\\u2C6C': 'z',\n    '\\uA763': 'z',\n    '\\u0386': '\\u0391',\n    '\\u0388': '\\u0395',\n    '\\u0389': '\\u0397',\n    '\\u038A': '\\u0399',\n    '\\u03AA': '\\u0399',\n    '\\u038C': '\\u039F',\n    '\\u038E': '\\u03A5',\n    '\\u03AB': '\\u03A5',\n    '\\u038F': '\\u03A9',\n    '\\u03AC': '\\u03B1',\n    '\\u03AD': '\\u03B5',\n    '\\u03AE': '\\u03B7',\n    '\\u03AF': '\\u03B9',\n    '\\u03CA': '\\u03B9',\n    '\\u0390': '\\u03B9',\n    '\\u03CC': '\\u03BF',\n    '\\u03CD': '\\u03C5',\n    '\\u03CB': '\\u03C5',\n    '\\u03B0': '\\u03C5',\n    '\\u03CE': '\\u03C9',\n    '\\u03C2': '\\u03C3',\n    '\\u2019': '\\''\n  };\n\n  return diacritics;\n});\n\nS2.define('select2/data/base',[\n  '../utils'\n], function (Utils) {\n  function BaseAdapter ($element, options) {\n    BaseAdapter.__super__.constructor.call(this);\n  }\n\n  Utils.Extend(BaseAdapter, Utils.Observable);\n\n  BaseAdapter.prototype.current = function (callback) {\n    throw new Error('The `current` method must be defined in child classes.');\n  };\n\n  BaseAdapter.prototype.query = function (params, callback) {\n    throw new Error('The `query` method must be defined in child classes.');\n  };\n\n  BaseAdapter.prototype.bind = function (container, $container) {\n    // Can be implemented in subclasses\n  };\n\n  BaseAdapter.prototype.destroy = function () {\n    // Can be implemented in subclasses\n  };\n\n  BaseAdapter.prototype.generateResultId = function (container, data) {\n    var id = container.id + '-result-';\n\n    id += Utils.generateChars(4);\n\n    if (data.id != null) {\n      id += '-' + data.id.toString();\n    } else {\n      id += '-' + Utils.generateChars(4);\n    }\n    return id;\n  };\n\n  return BaseAdapter;\n});\n\nS2.define('select2/data/select',[\n  './base',\n  '../utils',\n  'jquery'\n], function (BaseAdapter, Utils, $) {\n  function SelectAdapter ($element, options) {\n    this.$element = $element;\n    this.options = options;\n\n    SelectAdapter.__super__.constructor.call(this);\n  }\n\n  Utils.Extend(SelectAdapter, BaseAdapter);\n\n  SelectAdapter.prototype.current = function (callback) {\n    var data = [];\n    var self = this;\n\n    this.$element.find(':selected').each(function () {\n      var $option = $(this);\n\n      var option = self.item($option);\n\n      data.push(option);\n    });\n\n    callback(data);\n  };\n\n  SelectAdapter.prototype.select = function (data) {\n    var self = this;\n\n    data.selected = true;\n\n    // If data.element is a DOM node, use it instead\n    if ($(data.element).is('option')) {\n      data.element.selected = true;\n\n      this.$element.trigger('input').trigger('change');\n\n      return;\n    }\n\n    if (this.$element.prop('multiple')) {\n      this.current(function (currentData) {\n        var val = [];\n\n        data = [data];\n        data.push.apply(data, currentData);\n\n        for (var d = 0; d < data.length; d++) {\n          var id = data[d].id;\n\n          if ($.inArray(id, val) === -1) {\n            val.push(id);\n          }\n        }\n\n        self.$element.val(val);\n        self.$element.trigger('input').trigger('change');\n      });\n    } else {\n      var val = data.id;\n\n      this.$element.val(val);\n      this.$element.trigger('input').trigger('change');\n    }\n  };\n\n  SelectAdapter.prototype.unselect = function (data) {\n    var self = this;\n\n    if (!this.$element.prop('multiple')) {\n      return;\n    }\n\n    data.selected = false;\n\n    if ($(data.element).is('option')) {\n      data.element.selected = false;\n\n      this.$element.trigger('input').trigger('change');\n\n      return;\n    }\n\n    this.current(function (currentData) {\n      var val = [];\n\n      for (var d = 0; d < currentData.length; d++) {\n        var id = currentData[d].id;\n\n        if (id !== data.id && $.inArray(id, val) === -1) {\n          val.push(id);\n        }\n      }\n\n      self.$element.val(val);\n\n      self.$element.trigger('input').trigger('change');\n    });\n  };\n\n  SelectAdapter.prototype.bind = function (container, $container) {\n    var self = this;\n\n    this.container = container;\n\n    container.on('select', function (params) {\n      self.select(params.data);\n    });\n\n    container.on('unselect', function (params) {\n      self.unselect(params.data);\n    });\n  };\n\n  SelectAdapter.prototype.destroy = function () {\n    // Remove anything added to child elements\n    this.$element.find('*').each(function () {\n      // Remove any custom data set by Select2\n      Utils.RemoveData(this);\n    });\n  };\n\n  SelectAdapter.prototype.query = function (params, callback) {\n    var data = [];\n    var self = this;\n\n    var $options = this.$element.children();\n\n    $options.each(function () {\n      var $option = $(this);\n\n      if (!$option.is('option') && !$option.is('optgroup')) {\n        return;\n      }\n\n      var option = self.item($option);\n\n      var matches = self.matches(params, option);\n\n      if (matches !== null) {\n        data.push(matches);\n      }\n    });\n\n    callback({\n      results: data\n    });\n  };\n\n  SelectAdapter.prototype.addOptions = function ($options) {\n    Utils.appendMany(this.$element, $options);\n  };\n\n  SelectAdapter.prototype.option = function (data) {\n    var option;\n\n    if (data.children) {\n      option = document.createElement('optgroup');\n      option.label = data.text;\n    } else {\n      option = document.createElement('option');\n\n      if (option.textContent !== undefined) {\n        option.textContent = data.text;\n      } else {\n        option.innerText = data.text;\n      }\n    }\n\n    if (data.id !== undefined) {\n      option.value = data.id;\n    }\n\n    if (data.disabled) {\n      option.disabled = true;\n    }\n\n    if (data.selected) {\n      option.selected = true;\n    }\n\n    if (data.title) {\n      option.title = data.title;\n    }\n\n    var $option = $(option);\n\n    var normalizedData = this._normalizeItem(data);\n    normalizedData.element = option;\n\n    // Override the option's data with the combined data\n    Utils.StoreData(option, 'data', normalizedData);\n\n    return $option;\n  };\n\n  SelectAdapter.prototype.item = function ($option) {\n    var data = {};\n\n    data = Utils.GetData($option[0], 'data');\n\n    if (data != null) {\n      return data;\n    }\n\n    if ($option.is('option')) {\n      data = {\n        id: $option.val(),\n        text: $option.text(),\n        disabled: $option.prop('disabled'),\n        selected: $option.prop('selected'),\n        title: $option.prop('title')\n      };\n    } else if ($option.is('optgroup')) {\n      data = {\n        text: $option.prop('label'),\n        children: [],\n        title: $option.prop('title')\n      };\n\n      var $children = $option.children('option');\n      var children = [];\n\n      for (var c = 0; c < $children.length; c++) {\n        var $child = $($children[c]);\n\n        var child = this.item($child);\n\n        children.push(child);\n      }\n\n      data.children = children;\n    }\n\n    data = this._normalizeItem(data);\n    data.element = $option[0];\n\n    Utils.StoreData($option[0], 'data', data);\n\n    return data;\n  };\n\n  SelectAdapter.prototype._normalizeItem = function (item) {\n    if (item !== Object(item)) {\n      item = {\n        id: item,\n        text: item\n      };\n    }\n\n    item = $.extend({}, {\n      text: ''\n    }, item);\n\n    var defaults = {\n      selected: false,\n      disabled: false\n    };\n\n    if (item.id != null) {\n      item.id = item.id.toString();\n    }\n\n    if (item.text != null) {\n      item.text = item.text.toString();\n    }\n\n    if (item._resultId == null && item.id && this.container != null) {\n      item._resultId = this.generateResultId(this.container, item);\n    }\n\n    return $.extend({}, defaults, item);\n  };\n\n  SelectAdapter.prototype.matches = function (params, data) {\n    var matcher = this.options.get('matcher');\n\n    return matcher(params, data);\n  };\n\n  return SelectAdapter;\n});\n\nS2.define('select2/data/array',[\n  './select',\n  '../utils',\n  'jquery'\n], function (SelectAdapter, Utils, $) {\n  function ArrayAdapter ($element, options) {\n    this._dataToConvert = options.get('data') || [];\n\n    ArrayAdapter.__super__.constructor.call(this, $element, options);\n  }\n\n  Utils.Extend(ArrayAdapter, SelectAdapter);\n\n  ArrayAdapter.prototype.bind = function (container, $container) {\n    ArrayAdapter.__super__.bind.call(this, container, $container);\n\n    this.addOptions(this.convertToOptions(this._dataToConvert));\n  };\n\n  ArrayAdapter.prototype.select = function (data) {\n    var $option = this.$element.find('option').filter(function (i, elm) {\n      return elm.value == data.id.toString();\n    });\n\n    if ($option.length === 0) {\n      $option = this.option(data);\n\n      this.addOptions($option);\n    }\n\n    ArrayAdapter.__super__.select.call(this, data);\n  };\n\n  ArrayAdapter.prototype.convertToOptions = function (data) {\n    var self = this;\n\n    var $existing = this.$element.find('option');\n    var existingIds = $existing.map(function () {\n      return self.item($(this)).id;\n    }).get();\n\n    var $options = [];\n\n    // Filter out all items except for the one passed in the argument\n    function onlyItem (item) {\n      return function () {\n        return $(this).val() == item.id;\n      };\n    }\n\n    for (var d = 0; d < data.length; d++) {\n      var item = this._normalizeItem(data[d]);\n\n      // Skip items which were pre-loaded, only merge the data\n      if ($.inArray(item.id, existingIds) >= 0) {\n        var $existingOption = $existing.filter(onlyItem(item));\n\n        var existingData = this.item($existingOption);\n        var newData = $.extend(true, {}, item, existingData);\n\n        var $newOption = this.option(newData);\n\n        $existingOption.replaceWith($newOption);\n\n        continue;\n      }\n\n      var $option = this.option(item);\n\n      if (item.children) {\n        var $children = this.convertToOptions(item.children);\n\n        Utils.appendMany($option, $children);\n      }\n\n      $options.push($option);\n    }\n\n    return $options;\n  };\n\n  return ArrayAdapter;\n});\n\nS2.define('select2/data/ajax',[\n  './array',\n  '../utils',\n  'jquery'\n], function (ArrayAdapter, Utils, $) {\n  function AjaxAdapter ($element, options) {\n    this.ajaxOptions = this._applyDefaults(options.get('ajax'));\n\n    if (this.ajaxOptions.processResults != null) {\n      this.processResults = this.ajaxOptions.processResults;\n    }\n\n    AjaxAdapter.__super__.constructor.call(this, $element, options);\n  }\n\n  Utils.Extend(AjaxAdapter, ArrayAdapter);\n\n  AjaxAdapter.prototype._applyDefaults = function (options) {\n    var defaults = {\n      data: function (params) {\n        return $.extend({}, params, {\n          q: params.term\n        });\n      },\n      transport: function (params, success, failure) {\n        var $request = $.ajax(params);\n\n        $request.then(success);\n        $request.fail(failure);\n\n        return $request;\n      }\n    };\n\n    return $.extend({}, defaults, options, true);\n  };\n\n  AjaxAdapter.prototype.processResults = function (results) {\n    return results;\n  };\n\n  AjaxAdapter.prototype.query = function (params, callback) {\n    var matches = [];\n    var self = this;\n\n    if (this._request != null) {\n      // JSONP requests cannot always be aborted\n      if ($.isFunction(this._request.abort)) {\n        this._request.abort();\n      }\n\n      this._request = null;\n    }\n\n    var options = $.extend({\n      type: 'GET'\n    }, this.ajaxOptions);\n\n    if (typeof options.url === 'function') {\n      options.url = options.url.call(this.$element, params);\n    }\n\n    if (typeof options.data === 'function') {\n      options.data = options.data.call(this.$element, params);\n    }\n\n    function request () {\n      var $request = options.transport(options, function (data) {\n        var results = self.processResults(data, params);\n\n        if (self.options.get('debug') && window.console && console.error) {\n          // Check to make sure that the response included a `results` key.\n          if (!results || !results.results || !$.isArray(results.results)) {\n            console.error(\n              'Select2: The AJAX results did not return an array in the ' +\n              '`results` key of the response.'\n            );\n          }\n        }\n\n        callback(results);\n      }, function () {\n        // Attempt to detect if a request was aborted\n        // Only works if the transport exposes a status property\n        if ('status' in $request &&\n            ($request.status === 0 || $request.status === '0')) {\n          return;\n        }\n\n        self.trigger('results:message', {\n          message: 'errorLoading'\n        });\n      });\n\n      self._request = $request;\n    }\n\n    if (this.ajaxOptions.delay && params.term != null) {\n      if (this._queryTimeout) {\n        window.clearTimeout(this._queryTimeout);\n      }\n\n      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);\n    } else {\n      request();\n    }\n  };\n\n  return AjaxAdapter;\n});\n\nS2.define('select2/data/tags',[\n  'jquery'\n], function ($) {\n  function Tags (decorated, $element, options) {\n    var tags = options.get('tags');\n\n    var createTag = options.get('createTag');\n\n    if (createTag !== undefined) {\n      this.createTag = createTag;\n    }\n\n    var insertTag = options.get('insertTag');\n\n    if (insertTag !== undefined) {\n        this.insertTag = insertTag;\n    }\n\n    decorated.call(this, $element, options);\n\n    if ($.isArray(tags)) {\n      for (var t = 0; t < tags.length; t++) {\n        var tag = tags[t];\n        var item = this._normalizeItem(tag);\n\n        var $option = this.option(item);\n\n        this.$element.append($option);\n      }\n    }\n  }\n\n  Tags.prototype.query = function (decorated, params, callback) {\n    var self = this;\n\n    this._removeOldTags();\n\n    if (params.term == null || params.page != null) {\n      decorated.call(this, params, callback);\n      return;\n    }\n\n    function wrapper (obj, child) {\n      var data = obj.results;\n\n      for (var i = 0; i < data.length; i++) {\n        var option = data[i];\n\n        var checkChildren = (\n          option.children != null &&\n          !wrapper({\n            results: option.children\n          }, true)\n        );\n\n        var optionText = (option.text || '').toUpperCase();\n        var paramsTerm = (params.term || '').toUpperCase();\n\n        var checkText = optionText === paramsTerm;\n\n        if (checkText || checkChildren) {\n          if (child) {\n            return false;\n          }\n\n          obj.data = data;\n          callback(obj);\n\n          return;\n        }\n      }\n\n      if (child) {\n        return true;\n      }\n\n      var tag = self.createTag(params);\n\n      if (tag != null) {\n        var $option = self.option(tag);\n        $option.attr('data-select2-tag', true);\n\n        self.addOptions([$option]);\n\n        self.insertTag(data, tag);\n      }\n\n      obj.results = data;\n\n      callback(obj);\n    }\n\n    decorated.call(this, params, wrapper);\n  };\n\n  Tags.prototype.createTag = function (decorated, params) {\n    var term = $.trim(params.term);\n\n    if (term === '') {\n      return null;\n    }\n\n    return {\n      id: term,\n      text: term\n    };\n  };\n\n  Tags.prototype.insertTag = function (_, data, tag) {\n    data.unshift(tag);\n  };\n\n  Tags.prototype._removeOldTags = function (_) {\n    var $options = this.$element.find('option[data-select2-tag]');\n\n    $options.each(function () {\n      if (this.selected) {\n        return;\n      }\n\n      $(this).remove();\n    });\n  };\n\n  return Tags;\n});\n\nS2.define('select2/data/tokenizer',[\n  'jquery'\n], function ($) {\n  function Tokenizer (decorated, $element, options) {\n    var tokenizer = options.get('tokenizer');\n\n    if (tokenizer !== undefined) {\n      this.tokenizer = tokenizer;\n    }\n\n    decorated.call(this, $element, options);\n  }\n\n  Tokenizer.prototype.bind = function (decorated, container, $container) {\n    decorated.call(this, container, $container);\n\n    this.$search =  container.dropdown.$search || container.selection.$search ||\n      $container.find('.select2-search__field');\n  };\n\n  Tokenizer.prototype.query = function (decorated, params, callback) {\n    var self = this;\n\n    function createAndSelect (data) {\n      // Normalize the data object so we can use it for checks\n      var item = self._normalizeItem(data);\n\n      // Check if the data object already exists as a tag\n      // Select it if it doesn't\n      var $existingOptions = self.$element.find('option').filter(function () {\n        return $(this).val() === item.id;\n      });\n\n      // If an existing option wasn't found for it, create the option\n      if (!$existingOptions.length) {\n        var $option = self.option(item);\n        $option.attr('data-select2-tag', true);\n\n        self._removeOldTags();\n        self.addOptions([$option]);\n      }\n\n      // Select the item, now that we know there is an option for it\n      select(item);\n    }\n\n    function select (data) {\n      self.trigger('select', {\n        data: data\n      });\n    }\n\n    params.term = params.term || '';\n\n    var tokenData = this.tokenizer(params, this.options, createAndSelect);\n\n    if (tokenData.term !== params.term) {\n      // Replace the search term if we have the search box\n      if (this.$search.length) {\n        this.$search.val(tokenData.term);\n        this.$search.trigger('focus');\n      }\n\n      params.term = tokenData.term;\n    }\n\n    decorated.call(this, params, callback);\n  };\n\n  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {\n    var separators = options.get('tokenSeparators') || [];\n    var term = params.term;\n    var i = 0;\n\n    var createTag = this.createTag || function (params) {\n      return {\n        id: params.term,\n        text: params.term\n      };\n    };\n\n    while (i < term.length) {\n      var termChar = term[i];\n\n      if ($.inArray(termChar, separators) === -1) {\n        i++;\n\n        continue;\n      }\n\n      var part = term.substr(0, i);\n      var partParams = $.extend({}, params, {\n        term: part\n      });\n\n      var data = createTag(partParams);\n\n      if (data == null) {\n        i++;\n        continue;\n      }\n\n      callback(data);\n\n      // Reset the term to not include the tokenized portion\n      term = term.substr(i + 1) || '';\n      i = 0;\n    }\n\n    return {\n      term: term\n    };\n  };\n\n  return Tokenizer;\n});\n\nS2.define('select2/data/minimumInputLength',[\n\n], function () {\n  function MinimumInputLength (decorated, $e, options) {\n    this.minimumInputLength = options.get('minimumInputLength');\n\n    decorated.call(this, $e, options);\n  }\n\n  MinimumInputLength.prototype.query = function (decorated, params, callback) {\n    params.term = params.term || '';\n\n    if (params.term.length < this.minimumInputLength) {\n      this.trigger('results:message', {\n        message: 'inputTooShort',\n        args: {\n          minimum: this.minimumInputLength,\n          input: params.term,\n          params: params\n        }\n      });\n\n      return;\n    }\n\n    decorated.call(this, params, callback);\n  };\n\n  return MinimumInputLength;\n});\n\nS2.define('select2/data/maximumInputLength',[\n\n], function () {\n  function MaximumInputLength (decorated, $e, options) {\n    this.maximumInputLength = options.get('maximumInputLength');\n\n    decorated.call(this, $e, options);\n  }\n\n  MaximumInputLength.prototype.query = function (decorated, params, callback) {\n    params.term = params.term || '';\n\n    if (this.maximumInputLength > 0 &&\n        params.term.length > this.maximumInputLength) {\n      this.trigger('results:message', {\n        message: 'inputTooLong',\n        args: {\n          maximum: this.maximumInputLength,\n          input: params.term,\n          params: params\n        }\n      });\n\n      return;\n    }\n\n    decorated.call(this, params, callback);\n  };\n\n  return MaximumInputLength;\n});\n\nS2.define('select2/data/maximumSelectionLength',[\n\n], function (){\n  function MaximumSelectionLength (decorated, $e, options) {\n    this.maximumSelectionLength = options.get('maximumSelectionLength');\n\n    decorated.call(this, $e, options);\n  }\n\n  MaximumSelectionLength.prototype.bind =\n    function (decorated, container, $container) {\n      var self = this;\n\n      decorated.call(this, container, $container);\n\n      container.on('select', function () {\n        self._checkIfMaximumSelected();\n      });\n  };\n\n  MaximumSelectionLength.prototype.query =\n    function (decorated, params, callback) {\n      var self = this;\n\n      this._checkIfMaximumSelected(function () {\n        decorated.call(self, params, callback);\n      });\n  };\n\n  MaximumSelectionLength.prototype._checkIfMaximumSelected =\n    function (_, successCallback) {\n      var self = this;\n\n      this.current(function (currentData) {\n        var count = currentData != null ? currentData.length : 0;\n        if (self.maximumSelectionLength > 0 &&\n          count >= self.maximumSelectionLength) {\n          self.trigger('results:message', {\n            message: 'maximumSelected',\n            args: {\n              maximum: self.maximumSelectionLength\n            }\n          });\n          return;\n        }\n\n        if (successCallback) {\n          successCallback();\n        }\n      });\n  };\n\n  return MaximumSelectionLength;\n});\n\nS2.define('select2/dropdown',[\n  'jquery',\n  './utils'\n], function ($, Utils) {\n  function Dropdown ($element, options) {\n    this.$element = $element;\n    this.options = options;\n\n    Dropdown.__super__.constructor.call(this);\n  }\n\n  Utils.Extend(Dropdown, Utils.Observable);\n\n  Dropdown.prototype.render = function () {\n    var $dropdown = $(\n      '<span class=\"select2-dropdown\">' +\n        '<span class=\"select2-results\"></span>' +\n      '</span>'\n    );\n\n    $dropdown.attr('dir', this.options.get('dir'));\n\n    this.$dropdown = $dropdown;\n\n    return $dropdown;\n  };\n\n  Dropdown.prototype.bind = function () {\n    // Should be implemented in subclasses\n  };\n\n  Dropdown.prototype.position = function ($dropdown, $container) {\n    // Should be implemented in subclasses\n  };\n\n  Dropdown.prototype.destroy = function () {\n    // Remove the dropdown from the DOM\n    this.$dropdown.remove();\n  };\n\n  return Dropdown;\n});\n\nS2.define('select2/dropdown/search',[\n  'jquery',\n  '../utils'\n], function ($, Utils) {\n  function Search () { }\n\n  Search.prototype.render = function (decorated) {\n    var $rendered = decorated.call(this);\n\n    var $search = $(\n      '<span class=\"select2-search select2-search--dropdown\">' +\n        '<input class=\"select2-search__field\" type=\"search\" tabindex=\"-1\"' +\n        ' autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"none\"' +\n        ' spellcheck=\"false\" role=\"searchbox\" aria-autocomplete=\"list\" />' +\n      '</span>'\n    );\n\n    this.$searchContainer = $search;\n    this.$search = $search.find('input');\n\n    $rendered.prepend($search);\n\n    return $rendered;\n  };\n\n  Search.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    var resultsId = container.id + '-results';\n\n    decorated.call(this, container, $container);\n\n    this.$search.on('keydown', function (evt) {\n      self.trigger('keypress', evt);\n\n      self._keyUpPrevented = evt.isDefaultPrevented();\n    });\n\n    // Workaround for browsers which do not support the `input` event\n    // This will prevent double-triggering of events for browsers which support\n    // both the `keyup` and `input` events.\n    this.$search.on('input', function (evt) {\n      // Unbind the duplicated `keyup` event\n      $(this).off('keyup');\n    });\n\n    this.$search.on('keyup input', function (evt) {\n      self.handleSearch(evt);\n    });\n\n    container.on('open', function () {\n      self.$search.attr('tabindex', 0);\n      self.$search.attr('aria-controls', resultsId);\n\n      self.$search.trigger('focus');\n\n      window.setTimeout(function () {\n        self.$search.trigger('focus');\n      }, 0);\n    });\n\n    container.on('close', function () {\n      self.$search.attr('tabindex', -1);\n      self.$search.removeAttr('aria-controls');\n      self.$search.removeAttr('aria-activedescendant');\n\n      self.$search.val('');\n      self.$search.trigger('blur');\n    });\n\n    container.on('focus', function () {\n      if (!container.isOpen()) {\n        self.$search.trigger('focus');\n      }\n    });\n\n    container.on('results:all', function (params) {\n      if (params.query.term == null || params.query.term === '') {\n        var showSearch = self.showSearch(params);\n\n        if (showSearch) {\n          self.$searchContainer.removeClass('select2-search--hide');\n        } else {\n          self.$searchContainer.addClass('select2-search--hide');\n        }\n      }\n    });\n\n    container.on('results:focus', function (params) {\n      if (params.data._resultId) {\n        self.$search.attr('aria-activedescendant', params.data._resultId);\n      } else {\n        self.$search.removeAttr('aria-activedescendant');\n      }\n    });\n  };\n\n  Search.prototype.handleSearch = function (evt) {\n    if (!this._keyUpPrevented) {\n      var input = this.$search.val();\n\n      this.trigger('query', {\n        term: input\n      });\n    }\n\n    this._keyUpPrevented = false;\n  };\n\n  Search.prototype.showSearch = function (_, params) {\n    return true;\n  };\n\n  return Search;\n});\n\nS2.define('select2/dropdown/hidePlaceholder',[\n\n], function () {\n  function HidePlaceholder (decorated, $element, options, dataAdapter) {\n    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));\n\n    decorated.call(this, $element, options, dataAdapter);\n  }\n\n  HidePlaceholder.prototype.append = function (decorated, data) {\n    data.results = this.removePlaceholder(data.results);\n\n    decorated.call(this, data);\n  };\n\n  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {\n    if (typeof placeholder === 'string') {\n      placeholder = {\n        id: '',\n        text: placeholder\n      };\n    }\n\n    return placeholder;\n  };\n\n  HidePlaceholder.prototype.removePlaceholder = function (_, data) {\n    var modifiedData = data.slice(0);\n\n    for (var d = data.length - 1; d >= 0; d--) {\n      var item = data[d];\n\n      if (this.placeholder.id === item.id) {\n        modifiedData.splice(d, 1);\n      }\n    }\n\n    return modifiedData;\n  };\n\n  return HidePlaceholder;\n});\n\nS2.define('select2/dropdown/infiniteScroll',[\n  'jquery'\n], function ($) {\n  function InfiniteScroll (decorated, $element, options, dataAdapter) {\n    this.lastParams = {};\n\n    decorated.call(this, $element, options, dataAdapter);\n\n    this.$loadingMore = this.createLoadingMore();\n    this.loading = false;\n  }\n\n  InfiniteScroll.prototype.append = function (decorated, data) {\n    this.$loadingMore.remove();\n    this.loading = false;\n\n    decorated.call(this, data);\n\n    if (this.showLoadingMore(data)) {\n      this.$results.append(this.$loadingMore);\n      this.loadMoreIfNeeded();\n    }\n  };\n\n  InfiniteScroll.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    decorated.call(this, container, $container);\n\n    container.on('query', function (params) {\n      self.lastParams = params;\n      self.loading = true;\n    });\n\n    container.on('query:append', function (params) {\n      self.lastParams = params;\n      self.loading = true;\n    });\n\n    this.$results.on('scroll', this.loadMoreIfNeeded.bind(this));\n  };\n\n  InfiniteScroll.prototype.loadMoreIfNeeded = function () {\n    var isLoadMoreVisible = $.contains(\n      document.documentElement,\n      this.$loadingMore[0]\n    );\n\n    if (this.loading || !isLoadMoreVisible) {\n      return;\n    }\n\n    var currentOffset = this.$results.offset().top +\n      this.$results.outerHeight(false);\n    var loadingMoreOffset = this.$loadingMore.offset().top +\n      this.$loadingMore.outerHeight(false);\n\n    if (currentOffset + 50 >= loadingMoreOffset) {\n      this.loadMore();\n    }\n  };\n\n  InfiniteScroll.prototype.loadMore = function () {\n    this.loading = true;\n\n    var params = $.extend({}, {page: 1}, this.lastParams);\n\n    params.page++;\n\n    this.trigger('query:append', params);\n  };\n\n  InfiniteScroll.prototype.showLoadingMore = function (_, data) {\n    return data.pagination && data.pagination.more;\n  };\n\n  InfiniteScroll.prototype.createLoadingMore = function () {\n    var $option = $(\n      '<li ' +\n      'class=\"select2-results__option select2-results__option--load-more\"' +\n      'role=\"option\" aria-disabled=\"true\"></li>'\n    );\n\n    var message = this.options.get('translations').get('loadingMore');\n\n    $option.html(message(this.lastParams));\n\n    return $option;\n  };\n\n  return InfiniteScroll;\n});\n\nS2.define('select2/dropdown/attachBody',[\n  'jquery',\n  '../utils'\n], function ($, Utils) {\n  function AttachBody (decorated, $element, options) {\n    this.$dropdownParent = $(options.get('dropdownParent') || document.body);\n\n    decorated.call(this, $element, options);\n  }\n\n  AttachBody.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    decorated.call(this, container, $container);\n\n    container.on('open', function () {\n      self._showDropdown();\n      self._attachPositioningHandler(container);\n\n      // Must bind after the results handlers to ensure correct sizing\n      self._bindContainerResultHandlers(container);\n    });\n\n    container.on('close', function () {\n      self._hideDropdown();\n      self._detachPositioningHandler(container);\n    });\n\n    this.$dropdownContainer.on('mousedown', function (evt) {\n      evt.stopPropagation();\n    });\n  };\n\n  AttachBody.prototype.destroy = function (decorated) {\n    decorated.call(this);\n\n    this.$dropdownContainer.remove();\n  };\n\n  AttachBody.prototype.position = function (decorated, $dropdown, $container) {\n    // Clone all of the container classes\n    $dropdown.attr('class', $container.attr('class'));\n\n    $dropdown.removeClass('select2');\n    $dropdown.addClass('select2-container--open');\n\n    $dropdown.css({\n      position: 'absolute',\n      top: -999999\n    });\n\n    this.$container = $container;\n  };\n\n  AttachBody.prototype.render = function (decorated) {\n    var $container = $('<span></span>');\n\n    var $dropdown = decorated.call(this);\n    $container.append($dropdown);\n\n    this.$dropdownContainer = $container;\n\n    return $container;\n  };\n\n  AttachBody.prototype._hideDropdown = function (decorated) {\n    this.$dropdownContainer.detach();\n  };\n\n  AttachBody.prototype._bindContainerResultHandlers =\n      function (decorated, container) {\n\n    // These should only be bound once\n    if (this._containerResultsHandlersBound) {\n      return;\n    }\n\n    var self = this;\n\n    container.on('results:all', function () {\n      self._positionDropdown();\n      self._resizeDropdown();\n    });\n\n    container.on('results:append', function () {\n      self._positionDropdown();\n      self._resizeDropdown();\n    });\n\n    container.on('results:message', function () {\n      self._positionDropdown();\n      self._resizeDropdown();\n    });\n\n    container.on('select', function () {\n      self._positionDropdown();\n      self._resizeDropdown();\n    });\n\n    container.on('unselect', function () {\n      self._positionDropdown();\n      self._resizeDropdown();\n    });\n\n    this._containerResultsHandlersBound = true;\n  };\n\n  AttachBody.prototype._attachPositioningHandler =\n      function (decorated, container) {\n    var self = this;\n\n    var scrollEvent = 'scroll.select2.' + container.id;\n    var resizeEvent = 'resize.select2.' + container.id;\n    var orientationEvent = 'orientationchange.select2.' + container.id;\n\n    var $watchers = this.$container.parents().filter(Utils.hasScroll);\n    $watchers.each(function () {\n      Utils.StoreData(this, 'select2-scroll-position', {\n        x: $(this).scrollLeft(),\n        y: $(this).scrollTop()\n      });\n    });\n\n    $watchers.on(scrollEvent, function (ev) {\n      var position = Utils.GetData(this, 'select2-scroll-position');\n      $(this).scrollTop(position.y);\n    });\n\n    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,\n      function (e) {\n      self._positionDropdown();\n      self._resizeDropdown();\n    });\n  };\n\n  AttachBody.prototype._detachPositioningHandler =\n      function (decorated, container) {\n    var scrollEvent = 'scroll.select2.' + container.id;\n    var resizeEvent = 'resize.select2.' + container.id;\n    var orientationEvent = 'orientationchange.select2.' + container.id;\n\n    var $watchers = this.$container.parents().filter(Utils.hasScroll);\n    $watchers.off(scrollEvent);\n\n    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);\n  };\n\n  AttachBody.prototype._positionDropdown = function () {\n    var $window = $(window);\n\n    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');\n    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');\n\n    var newDirection = null;\n\n    var offset = this.$container.offset();\n\n    offset.bottom = offset.top + this.$container.outerHeight(false);\n\n    var container = {\n      height: this.$container.outerHeight(false)\n    };\n\n    container.top = offset.top;\n    container.bottom = offset.top + container.height;\n\n    var dropdown = {\n      height: this.$dropdown.outerHeight(false)\n    };\n\n    var viewport = {\n      top: $window.scrollTop(),\n      bottom: $window.scrollTop() + $window.height()\n    };\n\n    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);\n    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);\n\n    var css = {\n      left: offset.left,\n      top: container.bottom\n    };\n\n    // Determine what the parent element is to use for calculating the offset\n    var $offsetParent = this.$dropdownParent;\n\n    // For statically positioned elements, we need to get the element\n    // that is determining the offset\n    if ($offsetParent.css('position') === 'static') {\n      $offsetParent = $offsetParent.offsetParent();\n    }\n\n    var parentOffset = {\n      top: 0,\n      left: 0\n    };\n\n    if (\n      $.contains(document.body, $offsetParent[0]) ||\n      $offsetParent[0].isConnected\n      ) {\n      parentOffset = $offsetParent.offset();\n    }\n\n    css.top -= parentOffset.top;\n    css.left -= parentOffset.left;\n\n    if (!isCurrentlyAbove && !isCurrentlyBelow) {\n      newDirection = 'below';\n    }\n\n    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {\n      newDirection = 'above';\n    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {\n      newDirection = 'below';\n    }\n\n    if (newDirection == 'above' ||\n      (isCurrentlyAbove && newDirection !== 'below')) {\n      css.top = container.top - parentOffset.top - dropdown.height;\n    }\n\n    if (newDirection != null) {\n      this.$dropdown\n        .removeClass('select2-dropdown--below select2-dropdown--above')\n        .addClass('select2-dropdown--' + newDirection);\n      this.$container\n        .removeClass('select2-container--below select2-container--above')\n        .addClass('select2-container--' + newDirection);\n    }\n\n    this.$dropdownContainer.css(css);\n  };\n\n  AttachBody.prototype._resizeDropdown = function () {\n    var css = {\n      width: this.$container.outerWidth(false) + 'px'\n    };\n\n    if (this.options.get('dropdownAutoWidth')) {\n      css.minWidth = css.width;\n      css.position = 'relative';\n      css.width = 'auto';\n    }\n\n    this.$dropdown.css(css);\n  };\n\n  AttachBody.prototype._showDropdown = function (decorated) {\n    this.$dropdownContainer.appendTo(this.$dropdownParent);\n\n    this._positionDropdown();\n    this._resizeDropdown();\n  };\n\n  return AttachBody;\n});\n\nS2.define('select2/dropdown/minimumResultsForSearch',[\n\n], function () {\n  function countResults (data) {\n    var count = 0;\n\n    for (var d = 0; d < data.length; d++) {\n      var item = data[d];\n\n      if (item.children) {\n        count += countResults(item.children);\n      } else {\n        count++;\n      }\n    }\n\n    return count;\n  }\n\n  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {\n    this.minimumResultsForSearch = options.get('minimumResultsForSearch');\n\n    if (this.minimumResultsForSearch < 0) {\n      this.minimumResultsForSearch = Infinity;\n    }\n\n    decorated.call(this, $element, options, dataAdapter);\n  }\n\n  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {\n    if (countResults(params.data.results) < this.minimumResultsForSearch) {\n      return false;\n    }\n\n    return decorated.call(this, params);\n  };\n\n  return MinimumResultsForSearch;\n});\n\nS2.define('select2/dropdown/selectOnClose',[\n  '../utils'\n], function (Utils) {\n  function SelectOnClose () { }\n\n  SelectOnClose.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    decorated.call(this, container, $container);\n\n    container.on('close', function (params) {\n      self._handleSelectOnClose(params);\n    });\n  };\n\n  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {\n    if (params && params.originalSelect2Event != null) {\n      var event = params.originalSelect2Event;\n\n      // Don't select an item if the close event was triggered from a select or\n      // unselect event\n      if (event._type === 'select' || event._type === 'unselect') {\n        return;\n      }\n    }\n\n    var $highlightedResults = this.getHighlightedResults();\n\n    // Only select highlighted results\n    if ($highlightedResults.length < 1) {\n      return;\n    }\n\n    var data = Utils.GetData($highlightedResults[0], 'data');\n\n    // Don't re-select already selected resulte\n    if (\n      (data.element != null && data.element.selected) ||\n      (data.element == null && data.selected)\n    ) {\n      return;\n    }\n\n    this.trigger('select', {\n        data: data\n    });\n  };\n\n  return SelectOnClose;\n});\n\nS2.define('select2/dropdown/closeOnSelect',[\n\n], function () {\n  function CloseOnSelect () { }\n\n  CloseOnSelect.prototype.bind = function (decorated, container, $container) {\n    var self = this;\n\n    decorated.call(this, container, $container);\n\n    container.on('select', function (evt) {\n      self._selectTriggered(evt);\n    });\n\n    container.on('unselect', function (evt) {\n      self._selectTriggered(evt);\n    });\n  };\n\n  CloseOnSelect.prototype._selectTriggered = function (_, evt) {\n    var originalEvent = evt.originalEvent;\n\n    // Don't close if the control key is being held\n    if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) {\n      return;\n    }\n\n    this.trigger('close', {\n      originalEvent: originalEvent,\n      originalSelect2Event: evt\n    });\n  };\n\n  return CloseOnSelect;\n});\n\nS2.define('select2/i18n/en',[],function () {\n  // English\n  return {\n    errorLoading: function () {\n      return 'The results could not be loaded.';\n    },\n    inputTooLong: function (args) {\n      var overChars = args.input.length - args.maximum;\n\n      var message = 'Please delete ' + overChars + ' character';\n\n      if (overChars != 1) {\n        message += 's';\n      }\n\n      return message;\n    },\n    inputTooShort: function (args) {\n      var remainingChars = args.minimum - args.input.length;\n\n      var message = 'Please enter ' + remainingChars + ' or more characters';\n\n      return message;\n    },\n    loadingMore: function () {\n      return 'Loading more results…';\n    },\n    maximumSelected: function (args) {\n      var message = 'You can only select ' + args.maximum + ' item';\n\n      if (args.maximum != 1) {\n        message += 's';\n      }\n\n      return message;\n    },\n    noResults: function () {\n      return 'No results found';\n    },\n    searching: function () {\n      return 'Searching…';\n    },\n    removeAllItems: function () {\n      return 'Remove all items';\n    }\n  };\n});\n\nS2.define('select2/defaults',[\n  'jquery',\n  'require',\n\n  './results',\n\n  './selection/single',\n  './selection/multiple',\n  './selection/placeholder',\n  './selection/allowClear',\n  './selection/search',\n  './selection/eventRelay',\n\n  './utils',\n  './translation',\n  './diacritics',\n\n  './data/select',\n  './data/array',\n  './data/ajax',\n  './data/tags',\n  './data/tokenizer',\n  './data/minimumInputLength',\n  './data/maximumInputLength',\n  './data/maximumSelectionLength',\n\n  './dropdown',\n  './dropdown/search',\n  './dropdown/hidePlaceholder',\n  './dropdown/infiniteScroll',\n  './dropdown/attachBody',\n  './dropdown/minimumResultsForSearch',\n  './dropdown/selectOnClose',\n  './dropdown/closeOnSelect',\n\n  './i18n/en'\n], function ($, require,\n\n             ResultsList,\n\n             SingleSelection, MultipleSelection, Placeholder, AllowClear,\n             SelectionSearch, EventRelay,\n\n             Utils, Translation, DIACRITICS,\n\n             SelectData, ArrayData, AjaxData, Tags, Tokenizer,\n             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,\n\n             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,\n             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,\n\n             EnglishTranslation) {\n  function Defaults () {\n    this.reset();\n  }\n\n  Defaults.prototype.apply = function (options) {\n    options = $.extend(true, {}, this.defaults, options);\n\n    if (options.dataAdapter == null) {\n      if (options.ajax != null) {\n        options.dataAdapter = AjaxData;\n      } else if (options.data != null) {\n        options.dataAdapter = ArrayData;\n      } else {\n        options.dataAdapter = SelectData;\n      }\n\n      if (options.minimumInputLength > 0) {\n        options.dataAdapter = Utils.Decorate(\n          options.dataAdapter,\n          MinimumInputLength\n        );\n      }\n\n      if (options.maximumInputLength > 0) {\n        options.dataAdapter = Utils.Decorate(\n          options.dataAdapter,\n          MaximumInputLength\n        );\n      }\n\n      if (options.maximumSelectionLength > 0) {\n        options.dataAdapter = Utils.Decorate(\n          options.dataAdapter,\n          MaximumSelectionLength\n        );\n      }\n\n      if (options.tags) {\n        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);\n      }\n\n      if (options.tokenSeparators != null || options.tokenizer != null) {\n        options.dataAdapter = Utils.Decorate(\n          options.dataAdapter,\n          Tokenizer\n        );\n      }\n\n      if (options.query != null) {\n        var Query = require(options.amdBase + 'compat/query');\n\n        options.dataAdapter = Utils.Decorate(\n          options.dataAdapter,\n          Query\n        );\n      }\n\n      if (options.initSelection != null) {\n        var InitSelection = require(options.amdBase + 'compat/initSelection');\n\n        options.dataAdapter = Utils.Decorate(\n          options.dataAdapter,\n          InitSelection\n        );\n      }\n    }\n\n    if (options.resultsAdapter == null) {\n      options.resultsAdapter = ResultsList;\n\n      if (options.ajax != null) {\n        options.resultsAdapter = Utils.Decorate(\n          options.resultsAdapter,\n          InfiniteScroll\n        );\n      }\n\n      if (options.placeholder != null) {\n        options.resultsAdapter = Utils.Decorate(\n          options.resultsAdapter,\n          HidePlaceholder\n        );\n      }\n\n      if (options.selectOnClose) {\n        options.resultsAdapter = Utils.Decorate(\n          options.resultsAdapter,\n          SelectOnClose\n        );\n      }\n    }\n\n    if (options.dropdownAdapter == null) {\n      if (options.multiple) {\n        options.dropdownAdapter = Dropdown;\n      } else {\n        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);\n\n        options.dropdownAdapter = SearchableDropdown;\n      }\n\n      if (options.minimumResultsForSearch !== 0) {\n        options.dropdownAdapter = Utils.Decorate(\n          options.dropdownAdapter,\n          MinimumResultsForSearch\n        );\n      }\n\n      if (options.closeOnSelect) {\n        options.dropdownAdapter = Utils.Decorate(\n          options.dropdownAdapter,\n          CloseOnSelect\n        );\n      }\n\n      if (\n        options.dropdownCssClass != null ||\n        options.dropdownCss != null ||\n        options.adaptDropdownCssClass != null\n      ) {\n        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');\n\n        options.dropdownAdapter = Utils.Decorate(\n          options.dropdownAdapter,\n          DropdownCSS\n        );\n      }\n\n      options.dropdownAdapter = Utils.Decorate(\n        options.dropdownAdapter,\n        AttachBody\n      );\n    }\n\n    if (options.selectionAdapter == null) {\n      if (options.multiple) {\n        options.selectionAdapter = MultipleSelection;\n      } else {\n        options.selectionAdapter = SingleSelection;\n      }\n\n      // Add the placeholder mixin if a placeholder was specified\n      if (options.placeholder != null) {\n        options.selectionAdapter = Utils.Decorate(\n          options.selectionAdapter,\n          Placeholder\n        );\n      }\n\n      if (options.allowClear) {\n        options.selectionAdapter = Utils.Decorate(\n          options.selectionAdapter,\n          AllowClear\n        );\n      }\n\n      if (options.multiple) {\n        options.selectionAdapter = Utils.Decorate(\n          options.selectionAdapter,\n          SelectionSearch\n        );\n      }\n\n      if (\n        options.containerCssClass != null ||\n        options.containerCss != null ||\n        options.adaptContainerCssClass != null\n      ) {\n        var ContainerCSS = require(options.amdBase + 'compat/containerCss');\n\n        options.selectionAdapter = Utils.Decorate(\n          options.selectionAdapter,\n          ContainerCSS\n        );\n      }\n\n      options.selectionAdapter = Utils.Decorate(\n        options.selectionAdapter,\n        EventRelay\n      );\n    }\n\n    // If the defaults were not previously applied from an element, it is\n    // possible for the language option to have not been resolved\n    options.language = this._resolveLanguage(options.language);\n\n    // Always fall back to English since it will always be complete\n    options.language.push('en');\n\n    var uniqueLanguages = [];\n\n    for (var l = 0; l < options.language.length; l++) {\n      var language = options.language[l];\n\n      if (uniqueLanguages.indexOf(language) === -1) {\n        uniqueLanguages.push(language);\n      }\n    }\n\n    options.language = uniqueLanguages;\n\n    options.translations = this._processTranslations(\n      options.language,\n      options.debug\n    );\n\n    return options;\n  };\n\n  Defaults.prototype.reset = function () {\n    function stripDiacritics (text) {\n      // Used 'uni range + named function' from http://jsperf.com/diacritics/18\n      function match(a) {\n        return DIACRITICS[a] || a;\n      }\n\n      return text.replace(/[^\\u0000-\\u007E]/g, match);\n    }\n\n    function matcher (params, data) {\n      // Always return the object if there is nothing to compare\n      if ($.trim(params.term) === '') {\n        return data;\n      }\n\n      // Do a recursive check for options with children\n      if (data.children && data.children.length > 0) {\n        // Clone the data object if there are children\n        // This is required as we modify the object to remove any non-matches\n        var match = $.extend(true, {}, data);\n\n        // Check each child of the option\n        for (var c = data.children.length - 1; c >= 0; c--) {\n          var child = data.children[c];\n\n          var matches = matcher(params, child);\n\n          // If there wasn't a match, remove the object in the array\n          if (matches == null) {\n            match.children.splice(c, 1);\n          }\n        }\n\n        // If any children matched, return the new object\n        if (match.children.length > 0) {\n          return match;\n        }\n\n        // If there were no matching children, check just the plain object\n        return matcher(params, match);\n      }\n\n      var original = stripDiacritics(data.text).toUpperCase();\n      var term = stripDiacritics(params.term).toUpperCase();\n\n      // Check if the text contains the term\n      if (original.indexOf(term) > -1) {\n        return data;\n      }\n\n      // If it doesn't contain the term, don't return anything\n      return null;\n    }\n\n    this.defaults = {\n      amdBase: './',\n      amdLanguageBase: './i18n/',\n      closeOnSelect: true,\n      debug: false,\n      dropdownAutoWidth: false,\n      escapeMarkup: Utils.escapeMarkup,\n      language: {},\n      matcher: matcher,\n      minimumInputLength: 0,\n      maximumInputLength: 0,\n      maximumSelectionLength: 0,\n      minimumResultsForSearch: 0,\n      selectOnClose: false,\n      scrollAfterSelect: false,\n      sorter: function (data) {\n        return data;\n      },\n      templateResult: function (result) {\n        return result.text;\n      },\n      templateSelection: function (selection) {\n        return selection.text;\n      },\n      theme: 'default',\n      width: 'resolve'\n    };\n  };\n\n  Defaults.prototype.applyFromElement = function (options, $element) {\n    var optionLanguage = options.language;\n    var defaultLanguage = this.defaults.language;\n    var elementLanguage = $element.prop('lang');\n    var parentLanguage = $element.closest('[lang]').prop('lang');\n\n    var languages = Array.prototype.concat.call(\n      this._resolveLanguage(elementLanguage),\n      this._resolveLanguage(optionLanguage),\n      this._resolveLanguage(defaultLanguage),\n      this._resolveLanguage(parentLanguage)\n    );\n\n    options.language = languages;\n\n    return options;\n  };\n\n  Defaults.prototype._resolveLanguage = function (language) {\n    if (!language) {\n      return [];\n    }\n\n    if ($.isEmptyObject(language)) {\n      return [];\n    }\n\n    if ($.isPlainObject(language)) {\n      return [language];\n    }\n\n    var languages;\n\n    if (!$.isArray(language)) {\n      languages = [language];\n    } else {\n      languages = language;\n    }\n\n    var resolvedLanguages = [];\n\n    for (var l = 0; l < languages.length; l++) {\n      resolvedLanguages.push(languages[l]);\n\n      if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) {\n        // Extract the region information if it is included\n        var languageParts = languages[l].split('-');\n        var baseLanguage = languageParts[0];\n\n        resolvedLanguages.push(baseLanguage);\n      }\n    }\n\n    return resolvedLanguages;\n  };\n\n  Defaults.prototype._processTranslations = function (languages, debug) {\n    var translations = new Translation();\n\n    for (var l = 0; l < languages.length; l++) {\n      var languageData = new Translation();\n\n      var language = languages[l];\n\n      if (typeof language === 'string') {\n        try {\n          // Try to load it with the original name\n          languageData = Translation.loadPath(language);\n        } catch (e) {\n          try {\n            // If we couldn't load it, check if it wasn't the full path\n            language = this.defaults.amdLanguageBase + language;\n            languageData = Translation.loadPath(language);\n          } catch (ex) {\n            // The translation could not be loaded at all. Sometimes this is\n            // because of a configuration problem, other times this can be\n            // because of how Select2 helps load all possible translation files\n            if (debug && window.console && console.warn) {\n              console.warn(\n                'Select2: The language file for \"' + language + '\" could ' +\n                'not be automatically loaded. A fallback will be used instead.'\n              );\n            }\n          }\n        }\n      } else if ($.isPlainObject(language)) {\n        languageData = new Translation(language);\n      } else {\n        languageData = language;\n      }\n\n      translations.extend(languageData);\n    }\n\n    return translations;\n  };\n\n  Defaults.prototype.set = function (key, value) {\n    var camelKey = $.camelCase(key);\n\n    var data = {};\n    data[camelKey] = value;\n\n    var convertedData = Utils._convertData(data);\n\n    $.extend(true, this.defaults, convertedData);\n  };\n\n  var defaults = new Defaults();\n\n  return defaults;\n});\n\nS2.define('select2/options',[\n  'require',\n  'jquery',\n  './defaults',\n  './utils'\n], function (require, $, Defaults, Utils) {\n  function Options (options, $element) {\n    this.options = options;\n\n    if ($element != null) {\n      this.fromElement($element);\n    }\n\n    if ($element != null) {\n      this.options = Defaults.applyFromElement(this.options, $element);\n    }\n\n    this.options = Defaults.apply(this.options);\n\n    if ($element && $element.is('input')) {\n      var InputCompat = require(this.get('amdBase') + 'compat/inputData');\n\n      this.options.dataAdapter = Utils.Decorate(\n        this.options.dataAdapter,\n        InputCompat\n      );\n    }\n  }\n\n  Options.prototype.fromElement = function ($e) {\n    var excludedData = ['select2'];\n\n    if (this.options.multiple == null) {\n      this.options.multiple = $e.prop('multiple');\n    }\n\n    if (this.options.disabled == null) {\n      this.options.disabled = $e.prop('disabled');\n    }\n\n    if (this.options.dir == null) {\n      if ($e.prop('dir')) {\n        this.options.dir = $e.prop('dir');\n      } else if ($e.closest('[dir]').prop('dir')) {\n        this.options.dir = $e.closest('[dir]').prop('dir');\n      } else {\n        this.options.dir = 'ltr';\n      }\n    }\n\n    $e.prop('disabled', this.options.disabled);\n    $e.prop('multiple', this.options.multiple);\n\n    if (Utils.GetData($e[0], 'select2Tags')) {\n      if (this.options.debug && window.console && console.warn) {\n        console.warn(\n          'Select2: The `data-select2-tags` attribute has been changed to ' +\n          'use the `data-data` and `data-tags=\"true\"` attributes and will be ' +\n          'removed in future versions of Select2.'\n        );\n      }\n\n      Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags'));\n      Utils.StoreData($e[0], 'tags', true);\n    }\n\n    if (Utils.GetData($e[0], 'ajaxUrl')) {\n      if (this.options.debug && window.console && console.warn) {\n        console.warn(\n          'Select2: The `data-ajax-url` attribute has been changed to ' +\n          '`data-ajax--url` and support for the old attribute will be removed' +\n          ' in future versions of Select2.'\n        );\n      }\n\n      $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl'));\n      Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl'));\n    }\n\n    var dataset = {};\n\n    function upperCaseLetter(_, letter) {\n      return letter.toUpperCase();\n    }\n\n    // Pre-load all of the attributes which are prefixed with `data-`\n    for (var attr = 0; attr < $e[0].attributes.length; attr++) {\n      var attributeName = $e[0].attributes[attr].name;\n      var prefix = 'data-';\n\n      if (attributeName.substr(0, prefix.length) == prefix) {\n        // Get the contents of the attribute after `data-`\n        var dataName = attributeName.substring(prefix.length);\n\n        // Get the data contents from the consistent source\n        // This is more than likely the jQuery data helper\n        var dataValue = Utils.GetData($e[0], dataName);\n\n        // camelCase the attribute name to match the spec\n        var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter);\n\n        // Store the data attribute contents into the dataset since\n        dataset[camelDataName] = dataValue;\n      }\n    }\n\n    // Prefer the element's `dataset` attribute if it exists\n    // jQuery 1.x does not correctly handle data attributes with multiple dashes\n    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {\n      dataset = $.extend(true, {}, $e[0].dataset, dataset);\n    }\n\n    // Prefer our internal data cache if it exists\n    var data = $.extend(true, {}, Utils.GetData($e[0]), dataset);\n\n    data = Utils._convertData(data);\n\n    for (var key in data) {\n      if ($.inArray(key, excludedData) > -1) {\n        continue;\n      }\n\n      if ($.isPlainObject(this.options[key])) {\n        $.extend(this.options[key], data[key]);\n      } else {\n        this.options[key] = data[key];\n      }\n    }\n\n    return this;\n  };\n\n  Options.prototype.get = function (key) {\n    return this.options[key];\n  };\n\n  Options.prototype.set = function (key, val) {\n    this.options[key] = val;\n  };\n\n  return Options;\n});\n\nS2.define('select2/core',[\n  'jquery',\n  './options',\n  './utils',\n  './keys'\n], function ($, Options, Utils, KEYS) {\n  var Select2 = function ($element, options) {\n    if (Utils.GetData($element[0], 'select2') != null) {\n      Utils.GetData($element[0], 'select2').destroy();\n    }\n\n    this.$element = $element;\n\n    this.id = this._generateId($element);\n\n    options = options || {};\n\n    this.options = new Options(options, $element);\n\n    Select2.__super__.constructor.call(this);\n\n    // Set up the tabindex\n\n    var tabindex = $element.attr('tabindex') || 0;\n    Utils.StoreData($element[0], 'old-tabindex', tabindex);\n    $element.attr('tabindex', '-1');\n\n    // Set up containers and adapters\n\n    var DataAdapter = this.options.get('dataAdapter');\n    this.dataAdapter = new DataAdapter($element, this.options);\n\n    var $container = this.render();\n\n    this._placeContainer($container);\n\n    var SelectionAdapter = this.options.get('selectionAdapter');\n    this.selection = new SelectionAdapter($element, this.options);\n    this.$selection = this.selection.render();\n\n    this.selection.position(this.$selection, $container);\n\n    var DropdownAdapter = this.options.get('dropdownAdapter');\n    this.dropdown = new DropdownAdapter($element, this.options);\n    this.$dropdown = this.dropdown.render();\n\n    this.dropdown.position(this.$dropdown, $container);\n\n    var ResultsAdapter = this.options.get('resultsAdapter');\n    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);\n    this.$results = this.results.render();\n\n    this.results.position(this.$results, this.$dropdown);\n\n    // Bind events\n\n    var self = this;\n\n    // Bind the container to all of the adapters\n    this._bindAdapters();\n\n    // Register any DOM event handlers\n    this._registerDomEvents();\n\n    // Register any internal event handlers\n    this._registerDataEvents();\n    this._registerSelectionEvents();\n    this._registerDropdownEvents();\n    this._registerResultsEvents();\n    this._registerEvents();\n\n    // Set the initial state\n    this.dataAdapter.current(function (initialData) {\n      self.trigger('selection:update', {\n        data: initialData\n      });\n    });\n\n    // Hide the original select\n    $element.addClass('select2-hidden-accessible');\n    $element.attr('aria-hidden', 'true');\n\n    // Synchronize any monitored attributes\n    this._syncAttributes();\n\n    Utils.StoreData($element[0], 'select2', this);\n\n    // Ensure backwards compatibility with $element.data('select2').\n    $element.data('select2', this);\n  };\n\n  Utils.Extend(Select2, Utils.Observable);\n\n  Select2.prototype._generateId = function ($element) {\n    var id = '';\n\n    if ($element.attr('id') != null) {\n      id = $element.attr('id');\n    } else if ($element.attr('name') != null) {\n      id = $element.attr('name') + '-' + Utils.generateChars(2);\n    } else {\n      id = Utils.generateChars(4);\n    }\n\n    id = id.replace(/(:|\\.|\\[|\\]|,)/g, '');\n    id = 'select2-' + id;\n\n    return id;\n  };\n\n  Select2.prototype._placeContainer = function ($container) {\n    $container.insertAfter(this.$element);\n\n    var width = this._resolveWidth(this.$element, this.options.get('width'));\n\n    if (width != null) {\n      $container.css('width', width);\n    }\n  };\n\n  Select2.prototype._resolveWidth = function ($element, method) {\n    var WIDTH = /^width:(([-+]?([0-9]*\\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;\n\n    if (method == 'resolve') {\n      var styleWidth = this._resolveWidth($element, 'style');\n\n      if (styleWidth != null) {\n        return styleWidth;\n      }\n\n      return this._resolveWidth($element, 'element');\n    }\n\n    if (method == 'element') {\n      var elementWidth = $element.outerWidth(false);\n\n      if (elementWidth <= 0) {\n        return 'auto';\n      }\n\n      return elementWidth + 'px';\n    }\n\n    if (method == 'style') {\n      var style = $element.attr('style');\n\n      if (typeof(style) !== 'string') {\n        return null;\n      }\n\n      var attrs = style.split(';');\n\n      for (var i = 0, l = attrs.length; i < l; i = i + 1) {\n        var attr = attrs[i].replace(/\\s/g, '');\n        var matches = attr.match(WIDTH);\n\n        if (matches !== null && matches.length >= 1) {\n          return matches[1];\n        }\n      }\n\n      return null;\n    }\n\n    if (method == 'computedstyle') {\n      var computedStyle = window.getComputedStyle($element[0]);\n\n      return computedStyle.width;\n    }\n\n    return method;\n  };\n\n  Select2.prototype._bindAdapters = function () {\n    this.dataAdapter.bind(this, this.$container);\n    this.selection.bind(this, this.$container);\n\n    this.dropdown.bind(this, this.$container);\n    this.results.bind(this, this.$container);\n  };\n\n  Select2.prototype._registerDomEvents = function () {\n    var self = this;\n\n    this.$element.on('change.select2', function () {\n      self.dataAdapter.current(function (data) {\n        self.trigger('selection:update', {\n          data: data\n        });\n      });\n    });\n\n    this.$element.on('focus.select2', function (evt) {\n      self.trigger('focus', evt);\n    });\n\n    this._syncA = Utils.bind(this._syncAttributes, this);\n    this._syncS = Utils.bind(this._syncSubtree, this);\n\n    if (this.$element[0].attachEvent) {\n      this.$element[0].attachEvent('onpropertychange', this._syncA);\n    }\n\n    var observer = window.MutationObserver ||\n      window.WebKitMutationObserver ||\n      window.MozMutationObserver\n    ;\n\n    if (observer != null) {\n      this._observer = new observer(function (mutations) {\n        self._syncA();\n        self._syncS(null, mutations);\n      });\n      this._observer.observe(this.$element[0], {\n        attributes: true,\n        childList: true,\n        subtree: false\n      });\n    } else if (this.$element[0].addEventListener) {\n      this.$element[0].addEventListener(\n        'DOMAttrModified',\n        self._syncA,\n        false\n      );\n      this.$element[0].addEventListener(\n        'DOMNodeInserted',\n        self._syncS,\n        false\n      );\n      this.$element[0].addEventListener(\n        'DOMNodeRemoved',\n        self._syncS,\n        false\n      );\n    }\n  };\n\n  Select2.prototype._registerDataEvents = function () {\n    var self = this;\n\n    this.dataAdapter.on('*', function (name, params) {\n      self.trigger(name, params);\n    });\n  };\n\n  Select2.prototype._registerSelectionEvents = function () {\n    var self = this;\n    var nonRelayEvents = ['toggle', 'focus'];\n\n    this.selection.on('toggle', function () {\n      self.toggleDropdown();\n    });\n\n    this.selection.on('focus', function (params) {\n      self.focus(params);\n    });\n\n    this.selection.on('*', function (name, params) {\n      if ($.inArray(name, nonRelayEvents) !== -1) {\n        return;\n      }\n\n      self.trigger(name, params);\n    });\n  };\n\n  Select2.prototype._registerDropdownEvents = function () {\n    var self = this;\n\n    this.dropdown.on('*', function (name, params) {\n      self.trigger(name, params);\n    });\n  };\n\n  Select2.prototype._registerResultsEvents = function () {\n    var self = this;\n\n    this.results.on('*', function (name, params) {\n      self.trigger(name, params);\n    });\n  };\n\n  Select2.prototype._registerEvents = function () {\n    var self = this;\n\n    this.on('open', function () {\n      self.$container.addClass('select2-container--open');\n    });\n\n    this.on('close', function () {\n      self.$container.removeClass('select2-container--open');\n    });\n\n    this.on('enable', function () {\n      self.$container.removeClass('select2-container--disabled');\n    });\n\n    this.on('disable', function () {\n      self.$container.addClass('select2-container--disabled');\n    });\n\n    this.on('blur', function () {\n      self.$container.removeClass('select2-container--focus');\n    });\n\n    this.on('query', function (params) {\n      if (!self.isOpen()) {\n        self.trigger('open', {});\n      }\n\n      this.dataAdapter.query(params, function (data) {\n        self.trigger('results:all', {\n          data: data,\n          query: params\n        });\n      });\n    });\n\n    this.on('query:append', function (params) {\n      this.dataAdapter.query(params, function (data) {\n        self.trigger('results:append', {\n          data: data,\n          query: params\n        });\n      });\n    });\n\n    this.on('keypress', function (evt) {\n      var key = evt.which;\n\n      if (self.isOpen()) {\n        if (key === KEYS.ESC || key === KEYS.TAB ||\n            (key === KEYS.UP && evt.altKey)) {\n          self.close(evt);\n\n          evt.preventDefault();\n        } else if (key === KEYS.ENTER) {\n          self.trigger('results:select', {});\n\n          evt.preventDefault();\n        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {\n          self.trigger('results:toggle', {});\n\n          evt.preventDefault();\n        } else if (key === KEYS.UP) {\n          self.trigger('results:previous', {});\n\n          evt.preventDefault();\n        } else if (key === KEYS.DOWN) {\n          self.trigger('results:next', {});\n\n          evt.preventDefault();\n        }\n      } else {\n        if (key === KEYS.ENTER || key === KEYS.SPACE ||\n            (key === KEYS.DOWN && evt.altKey)) {\n          self.open();\n\n          evt.preventDefault();\n        }\n      }\n    });\n  };\n\n  Select2.prototype._syncAttributes = function () {\n    this.options.set('disabled', this.$element.prop('disabled'));\n\n    if (this.isDisabled()) {\n      if (this.isOpen()) {\n        this.close();\n      }\n\n      this.trigger('disable', {});\n    } else {\n      this.trigger('enable', {});\n    }\n  };\n\n  Select2.prototype._isChangeMutation = function (evt, mutations) {\n    var changed = false;\n    var self = this;\n\n    // Ignore any mutation events raised for elements that aren't options or\n    // optgroups. This handles the case when the select element is destroyed\n    if (\n      evt && evt.target && (\n        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'\n      )\n    ) {\n      return;\n    }\n\n    if (!mutations) {\n      // If mutation events aren't supported, then we can only assume that the\n      // change affected the selections\n      changed = true;\n    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {\n      for (var n = 0; n < mutations.addedNodes.length; n++) {\n        var node = mutations.addedNodes[n];\n\n        if (node.selected) {\n          changed = true;\n        }\n      }\n    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {\n      changed = true;\n    } else if ($.isArray(mutations)) {\n      $.each(mutations, function(evt, mutation) {\n        if (self._isChangeMutation(evt, mutation)) {\n          // We've found a change mutation.\n          // Let's escape from the loop and continue\n          changed = true;\n          return false;\n        }\n      });\n    }\n    return changed;\n  };\n\n  Select2.prototype._syncSubtree = function (evt, mutations) {\n    var changed = this._isChangeMutation(evt, mutations);\n    var self = this;\n\n    // Only re-pull the data if we think there is a change\n    if (changed) {\n      this.dataAdapter.current(function (currentData) {\n        self.trigger('selection:update', {\n          data: currentData\n        });\n      });\n    }\n  };\n\n  /**\n   * Override the trigger method to automatically trigger pre-events when\n   * there are events that can be prevented.\n   */\n  Select2.prototype.trigger = function (name, args) {\n    var actualTrigger = Select2.__super__.trigger;\n    var preTriggerMap = {\n      'open': 'opening',\n      'close': 'closing',\n      'select': 'selecting',\n      'unselect': 'unselecting',\n      'clear': 'clearing'\n    };\n\n    if (args === undefined) {\n      args = {};\n    }\n\n    if (name in preTriggerMap) {\n      var preTriggerName = preTriggerMap[name];\n      var preTriggerArgs = {\n        prevented: false,\n        name: name,\n        args: args\n      };\n\n      actualTrigger.call(this, preTriggerName, preTriggerArgs);\n\n      if (preTriggerArgs.prevented) {\n        args.prevented = true;\n\n        return;\n      }\n    }\n\n    actualTrigger.call(this, name, args);\n  };\n\n  Select2.prototype.toggleDropdown = function () {\n    if (this.isDisabled()) {\n      return;\n    }\n\n    if (this.isOpen()) {\n      this.close();\n    } else {\n      this.open();\n    }\n  };\n\n  Select2.prototype.open = function () {\n    if (this.isOpen()) {\n      return;\n    }\n\n    if (this.isDisabled()) {\n      return;\n    }\n\n    this.trigger('query', {});\n  };\n\n  Select2.prototype.close = function (evt) {\n    if (!this.isOpen()) {\n      return;\n    }\n\n    this.trigger('close', { originalEvent : evt });\n  };\n\n  /**\n   * Helper method to abstract the \"enabled\" (not \"disabled\") state of this\n   * object.\n   *\n   * @return {true} if the instance is not disabled.\n   * @return {false} if the instance is disabled.\n   */\n  Select2.prototype.isEnabled = function () {\n    return !this.isDisabled();\n  };\n\n  /**\n   * Helper method to abstract the \"disabled\" state of this object.\n   *\n   * @return {true} if the disabled option is true.\n   * @return {false} if the disabled option is false.\n   */\n  Select2.prototype.isDisabled = function () {\n    return this.options.get('disabled');\n  };\n\n  Select2.prototype.isOpen = function () {\n    return this.$container.hasClass('select2-container--open');\n  };\n\n  Select2.prototype.hasFocus = function () {\n    return this.$container.hasClass('select2-container--focus');\n  };\n\n  Select2.prototype.focus = function (data) {\n    // No need to re-trigger focus events if we are already focused\n    if (this.hasFocus()) {\n      return;\n    }\n\n    this.$container.addClass('select2-container--focus');\n    this.trigger('focus', {});\n  };\n\n  Select2.prototype.enable = function (args) {\n    if (this.options.get('debug') && window.console && console.warn) {\n      console.warn(\n        'Select2: The `select2(\"enable\")` method has been deprecated and will' +\n        ' be removed in later Select2 versions. Use $element.prop(\"disabled\")' +\n        ' instead.'\n      );\n    }\n\n    if (args == null || args.length === 0) {\n      args = [true];\n    }\n\n    var disabled = !args[0];\n\n    this.$element.prop('disabled', disabled);\n  };\n\n  Select2.prototype.data = function () {\n    if (this.options.get('debug') &&\n        arguments.length > 0 && window.console && console.warn) {\n      console.warn(\n        'Select2: Data can no longer be set using `select2(\"data\")`. You ' +\n        'should consider setting the value instead using `$element.val()`.'\n      );\n    }\n\n    var data = [];\n\n    this.dataAdapter.current(function (currentData) {\n      data = currentData;\n    });\n\n    return data;\n  };\n\n  Select2.prototype.val = function (args) {\n    if (this.options.get('debug') && window.console && console.warn) {\n      console.warn(\n        'Select2: The `select2(\"val\")` method has been deprecated and will be' +\n        ' removed in later Select2 versions. Use $element.val() instead.'\n      );\n    }\n\n    if (args == null || args.length === 0) {\n      return this.$element.val();\n    }\n\n    var newVal = args[0];\n\n    if ($.isArray(newVal)) {\n      newVal = $.map(newVal, function (obj) {\n        return obj.toString();\n      });\n    }\n\n    this.$element.val(newVal).trigger('input').trigger('change');\n  };\n\n  Select2.prototype.destroy = function () {\n    this.$container.remove();\n\n    if (this.$element[0].detachEvent) {\n      this.$element[0].detachEvent('onpropertychange', this._syncA);\n    }\n\n    if (this._observer != null) {\n      this._observer.disconnect();\n      this._observer = null;\n    } else if (this.$element[0].removeEventListener) {\n      this.$element[0]\n        .removeEventListener('DOMAttrModified', this._syncA, false);\n      this.$element[0]\n        .removeEventListener('DOMNodeInserted', this._syncS, false);\n      this.$element[0]\n        .removeEventListener('DOMNodeRemoved', this._syncS, false);\n    }\n\n    this._syncA = null;\n    this._syncS = null;\n\n    this.$element.off('.select2');\n    this.$element.attr('tabindex',\n    Utils.GetData(this.$element[0], 'old-tabindex'));\n\n    this.$element.removeClass('select2-hidden-accessible');\n    this.$element.attr('aria-hidden', 'false');\n    Utils.RemoveData(this.$element[0]);\n    this.$element.removeData('select2');\n\n    this.dataAdapter.destroy();\n    this.selection.destroy();\n    this.dropdown.destroy();\n    this.results.destroy();\n\n    this.dataAdapter = null;\n    this.selection = null;\n    this.dropdown = null;\n    this.results = null;\n  };\n\n  Select2.prototype.render = function () {\n    var $container = $(\n      '<span class=\"select2 select2-container\">' +\n        '<span class=\"selection\"></span>' +\n        '<span class=\"dropdown-wrapper\" aria-hidden=\"true\"></span>' +\n      '</span>'\n    );\n\n    $container.attr('dir', this.options.get('dir'));\n\n    this.$container = $container;\n\n    this.$container.addClass('select2-container--' + this.options.get('theme'));\n\n    Utils.StoreData($container[0], 'element', this.$element);\n\n    return $container;\n  };\n\n  return Select2;\n});\n\nS2.define('jquery-mousewheel',[\n  'jquery'\n], function ($) {\n  // Used to shim jQuery.mousewheel for non-full builds.\n  return $;\n});\n\nS2.define('jquery.select2',[\n  'jquery',\n  'jquery-mousewheel',\n\n  './select2/core',\n  './select2/defaults',\n  './select2/utils'\n], function ($, _, Select2, Defaults, Utils) {\n  if ($.fn.select2 == null) {\n    // All methods that should return the element\n    var thisMethods = ['open', 'close', 'destroy'];\n\n    $.fn.select2 = function (options) {\n      options = options || {};\n\n      if (typeof options === 'object') {\n        this.each(function () {\n          var instanceOptions = $.extend(true, {}, options);\n\n          var instance = new Select2($(this), instanceOptions);\n        });\n\n        return this;\n      } else if (typeof options === 'string') {\n        var ret;\n        var args = Array.prototype.slice.call(arguments, 1);\n\n        this.each(function () {\n          var instance = Utils.GetData(this, 'select2');\n\n          if (instance == null && window.console && console.error) {\n            console.error(\n              'The select2(\\'' + options + '\\') method was called on an ' +\n              'element that is not using Select2.'\n            );\n          }\n\n          ret = instance[options].apply(instance, args);\n        });\n\n        // Check if we should be returning `this`\n        if ($.inArray(options, thisMethods) > -1) {\n          return this;\n        }\n\n        return ret;\n      } else {\n        throw new Error('Invalid arguments for Select2: ' + options);\n      }\n    };\n  }\n\n  if ($.fn.select2.defaults == null) {\n    $.fn.select2.defaults = Defaults;\n  }\n\n  return Select2;\n});\n\n  // Return the AMD loader configuration so it can be used outside of this file\n  return {\n    define: S2.define,\n    require: S2.require\n  };\n}());\n\n  // Autoload the jQuery bindings\n  // We know that all of the modules exist above this, so we're safe\n  var select2 = S2.require('jquery.select2');\n\n  // Hold the AMD module references on the jQuery function that was just loaded\n  // This allows Select2 to use the internal loader outside of this file, such\n  // as in the language files.\n  jQuery.fn.select2.amd = S2;\n\n  // Return the Select2 instance for anyone who is importing it.\n  return select2;\n}));\n"
  },
  {
    "path": "src/screenshotbot/js/vendor/sizzle.js",
    "content": "/*!\n * Sizzle CSS Selector Engine v2.3.5\n * https://sizzlejs.com/\n *\n * Copyright JS Foundation and other contributors\n * Released under the MIT license\n * https://js.foundation/\n *\n * Date: 2020-03-14\n */\n( function( window ) {\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\ttokenize,\n\tcompile,\n\tselect,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + 1 * new Date(),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tnonnativeSelectorCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// Instance methods\n\thasOwn = ( {} ).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpushNative = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\n\t// Use a stripped-down indexOf as it's faster than native\n\t// https://jsperf.com/thor-indexof-vs-for/5\n\tindexOf = function( list, elem ) {\n\t\tvar i = 0,\n\t\t\tlen = list.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( list[ i ] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|\" +\n\t\t\"ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\n\t// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram\n\tidentifier = \"(?:\\\\\\\\[\\\\da-fA-F]{1,6}\" + whitespace +\n\t\t\"?|\\\\\\\\[^\\\\r\\\\n\\\\f]|[\\\\w-]|[^\\0-\\\\x7f])+\",\n\n\t// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + identifier + \")(?:\" + whitespace +\n\n\t\t// Operator (capture 2)\n\t\t\"*([*^$|!~]?=)\" + whitespace +\n\n\t\t// \"Attribute values must be CSS identifiers [capture 5]\n\t\t// or strings [capture 3 or capture 4]\"\n\t\t\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\" + identifier + \"))|)\" +\n\t\twhitespace + \"*\\\\]\",\n\n\tpseudos = \":(\" + identifier + \")(?:\\\\((\" +\n\n\t\t// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:\n\t\t// 1. quoted (capture 3; capture 4 or capture 5)\n\t\t\"('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|\" +\n\n\t\t// 2. simple (capture 6)\n\t\t\"((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes + \")*)|\" +\n\n\t\t// 3. anything else (capture 2)\n\t\t\".*\" +\n\t\t\")\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trwhitespace = new RegExp( whitespace + \"+\", \"g\" ),\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" +\n\t\twhitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace +\n\t\t\"*\" ),\n\trdescend = new RegExp( whitespace + \"|>\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + identifier + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + identifier + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + identifier + \"|[*])\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" +\n\t\t\twhitespace + \"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" +\n\t\t\twhitespace + \"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace +\n\t\t\t\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" + whitespace +\n\t\t\t\"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trhtml = /HTML$/i,\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\n\t// CSS escapes\n\t// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\[\\\\da-fA-F]{1,6}\" + whitespace + \"?|\\\\\\\\([^\\\\r\\\\n\\\\f])\", \"g\" ),\n\tfunescape = function( escape, nonHex ) {\n\t\tvar high = \"0x\" + escape.slice( 1 ) - 0x10000;\n\n\t\treturn nonHex ?\n\n\t\t\t// Strip the backslash prefix from a non-hex escape sequence\n\t\t\tnonHex :\n\n\t\t\t// Replace a hexadecimal escape sequence with the encoded Unicode code point\n\t\t\t// Support: IE <=11+\n\t\t\t// For values outside the Basic Multilingual Plane (BMP), manually construct a\n\t\t\t// surrogate pair\n\t\t\thigh < 0 ?\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t},\n\n\t// CSS string/identifier serialization\n\t// https://drafts.csswg.org/cssom/#common-serializing-idioms\n\trcssescape = /([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,\n\tfcssescape = function( ch, asCodePoint ) {\n\t\tif ( asCodePoint ) {\n\n\t\t\t// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER\n\t\t\tif ( ch === \"\\0\" ) {\n\t\t\t\treturn \"\\uFFFD\";\n\t\t\t}\n\n\t\t\t// Control characters and (dependent upon position) numbers get escaped as code points\n\t\t\treturn ch.slice( 0, -1 ) + \"\\\\\" +\n\t\t\t\tch.charCodeAt( ch.length - 1 ).toString( 16 ) + \" \";\n\t\t}\n\n\t\t// Other potentially-special ASCII characters get backslash-escaped\n\t\treturn \"\\\\\" + ch;\n\t},\n\n\t// Used for iframes\n\t// See setDocument()\n\t// Removing the function wrapper causes a \"Permission Denied\"\n\t// error in IE\n\tunloadHandler = function() {\n\t\tsetDocument();\n\t},\n\n\tinDisabledFieldset = addCombinator(\n\t\tfunction( elem ) {\n\t\t\treturn elem.disabled === true && elem.nodeName.toLowerCase() === \"fieldset\";\n\t\t},\n\t\t{ dir: \"parentNode\", next: \"legend\" }\n\t);\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t( arr = slice.call( preferredDoc.childNodes ) ),\n\t\tpreferredDoc.childNodes\n\t);\n\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\t// eslint-disable-next-line no-unused-expressions\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpushNative.apply( target, slice.call( els ) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( ( target[ j++ ] = els[ i++ ] ) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar m, i, elem, nid, match, groups, newSelector,\n\t\tnewContext = context && context.ownerDocument,\n\n\t\t// nodeType defaults to 9, since context defaults to document\n\t\tnodeType = context ? context.nodeType : 9;\n\n\tresults = results || [];\n\n\t// Return early from calls with invalid selector or context\n\tif ( typeof selector !== \"string\" || !selector ||\n\t\tnodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {\n\n\t\treturn results;\n\t}\n\n\t// Try to shortcut find operations (as opposed to filters) in HTML documents\n\tif ( !seed ) {\n\t\tsetDocument( context );\n\t\tcontext = context || document;\n\n\t\tif ( documentIsHTML ) {\n\n\t\t\t// If the selector is sufficiently simple, try using a \"get*By*\" DOM method\n\t\t\t// (excepting DocumentFragment context, where the methods don't exist)\n\t\t\tif ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {\n\n\t\t\t\t// ID selector\n\t\t\t\tif ( ( m = match[ 1 ] ) ) {\n\n\t\t\t\t\t// Document context\n\t\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\t\tif ( ( elem = context.getElementById( m ) ) ) {\n\n\t\t\t\t\t\t\t// Support: IE, Opera, Webkit\n\t\t\t\t\t\t\t// TODO: identify versions\n\t\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// Element context\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t// Support: IE, Opera, Webkit\n\t\t\t\t\t\t// TODO: identify versions\n\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\tif ( newContext && ( elem = newContext.getElementById( m ) ) &&\n\t\t\t\t\t\t\tcontains( context, elem ) &&\n\t\t\t\t\t\t\telem.id === m ) {\n\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t// Type selector\n\t\t\t\t} else if ( match[ 2 ] ) {\n\t\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\t\treturn results;\n\n\t\t\t\t// Class selector\n\t\t\t\t} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&\n\t\t\t\t\tcontext.getElementsByClassName ) {\n\n\t\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Take advantage of querySelectorAll\n\t\t\tif ( support.qsa &&\n\t\t\t\t!nonnativeSelectorCache[ selector + \" \" ] &&\n\t\t\t\t( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&\n\n\t\t\t\t// Support: IE 8 only\n\t\t\t\t// Exclude object elements\n\t\t\t\t( nodeType !== 1 || context.nodeName.toLowerCase() !== \"object\" ) ) {\n\n\t\t\t\tnewSelector = selector;\n\t\t\t\tnewContext = context;\n\n\t\t\t\t// qSA considers elements outside a scoping root when evaluating child or\n\t\t\t\t// descendant combinators, which is not what we want.\n\t\t\t\t// In such cases, we work around the behavior by prefixing every selector in the\n\t\t\t\t// list with an ID selector referencing the scope context.\n\t\t\t\t// The technique has to be used as well when a leading combinator is used\n\t\t\t\t// as such selectors are not recognized by querySelectorAll.\n\t\t\t\t// Thanks to Andrew Dupont for this technique.\n\t\t\t\tif ( nodeType === 1 &&\n\t\t\t\t\t( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {\n\n\t\t\t\t\t// Expand context for sibling selectors\n\t\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) ||\n\t\t\t\t\t\tcontext;\n\n\t\t\t\t\t// We can use :scope instead of the ID hack if the browser\n\t\t\t\t\t// supports it & if we're not changing the context.\n\t\t\t\t\tif ( newContext !== context || !support.scope ) {\n\n\t\t\t\t\t\t// Capture the context ID, setting it first if necessary\n\t\t\t\t\t\tif ( ( nid = context.getAttribute( \"id\" ) ) ) {\n\t\t\t\t\t\t\tnid = nid.replace( rcssescape, fcssescape );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontext.setAttribute( \"id\", ( nid = expando ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prefix every selector in the list\n\t\t\t\t\tgroups = tokenize( selector );\n\t\t\t\t\ti = groups.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tgroups[ i ] = ( nid ? \"#\" + nid : \":scope\" ) + \" \" +\n\t\t\t\t\t\t\ttoSelector( groups[ i ] );\n\t\t\t\t\t}\n\t\t\t\t\tnewSelector = groups.join( \",\" );\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch ( qsaError ) {\n\t\t\t\t\tnonnativeSelectorCache( selector, true );\n\t\t\t\t} finally {\n\t\t\t\t\tif ( nid === expando ) {\n\t\t\t\t\t\tcontext.removeAttribute( \"id\" );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {function(string, object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn ( cache[ key + \" \" ] = value );\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created element and returns a boolean result\n */\nfunction assert( fn ) {\n\tvar el = document.createElement( \"fieldset\" );\n\n\ttry {\n\t\treturn !!fn( el );\n\t} catch ( e ) {\n\t\treturn false;\n\t} finally {\n\n\t\t// Remove from its parent by default\n\t\tif ( el.parentNode ) {\n\t\t\tel.parentNode.removeChild( el );\n\t\t}\n\n\t\t// release memory in IE\n\t\tel = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split( \"|\" ),\n\t\ti = arr.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[ i ] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\ta.sourceIndex - b.sourceIndex;\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( ( cur = cur.nextSibling ) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn ( name === \"input\" || name === \"button\" ) && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for :enabled/:disabled\n * @param {Boolean} disabled true for :disabled; false for :enabled\n */\nfunction createDisabledPseudo( disabled ) {\n\n\t// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable\n\treturn function( elem ) {\n\n\t\t// Only certain elements can match :enabled or :disabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled\n\t\tif ( \"form\" in elem ) {\n\n\t\t\t// Check for inherited disabledness on relevant non-disabled elements:\n\t\t\t// * listed form-associated elements in a disabled fieldset\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#category-listed\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled\n\t\t\t// * option elements in a disabled optgroup\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled\n\t\t\t// All such elements have a \"form\" property.\n\t\t\tif ( elem.parentNode && elem.disabled === false ) {\n\n\t\t\t\t// Option elements defer to a parent optgroup if present\n\t\t\t\tif ( \"label\" in elem ) {\n\t\t\t\t\tif ( \"label\" in elem.parentNode ) {\n\t\t\t\t\t\treturn elem.parentNode.disabled === disabled;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn elem.disabled === disabled;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Support: IE 6 - 11\n\t\t\t\t// Use the isDisabled shortcut property to check for disabled fieldset ancestors\n\t\t\t\treturn elem.isDisabled === disabled ||\n\n\t\t\t\t\t// Where there is no isDisabled, check manually\n\t\t\t\t\t/* jshint -W018 */\n\t\t\t\t\telem.isDisabled !== !disabled &&\n\t\t\t\t\tinDisabledFieldset( elem ) === disabled;\n\t\t\t}\n\n\t\t\treturn elem.disabled === disabled;\n\n\t\t// Try to winnow out elements that can't be disabled before trusting the disabled property.\n\t\t// Some victims get caught in our net (label, legend, menu, track), but it shouldn't\n\t\t// even exist on them, let alone have a boolean value.\n\t\t} else if ( \"label\" in elem ) {\n\t\t\treturn elem.disabled === disabled;\n\t\t}\n\n\t\t// Remaining elements are neither :enabled nor :disabled\n\t\treturn false;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction( function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction( function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ ( j = matchIndexes[ i ] ) ] ) {\n\t\t\t\t\tseed[ j ] = !( matches[ j ] = seed[ j ] );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t} );\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== \"undefined\" && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\tvar namespace = elem.namespaceURI,\n\t\tdocElem = ( elem.ownerDocument || elem ).documentElement;\n\n\t// Support: IE <=8\n\t// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes\n\t// https://bugs.jquery.com/ticket/4833\n\treturn !rhtml.test( namespace || docElem && docElem.nodeName || \"HTML\" );\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare, subWindow,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc;\n\n\t// Return early if doc is invalid or already selected\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Update global variables\n\tdocument = doc;\n\tdocElem = document.documentElement;\n\tdocumentIsHTML = !isXML( document );\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t// Accessing iframe documents after unload throws \"permission denied\" errors (jQuery #13936)\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( preferredDoc != document &&\n\t\t( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {\n\n\t\t// Support: IE 11, Edge\n\t\tif ( subWindow.addEventListener ) {\n\t\t\tsubWindow.addEventListener( \"unload\", unloadHandler, false );\n\n\t\t// Support: IE 9 - 10 only\n\t\t} else if ( subWindow.attachEvent ) {\n\t\t\tsubWindow.attachEvent( \"onunload\", unloadHandler );\n\t\t}\n\t}\n\n\t// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,\n\t// Safari 4 - 5 only, Opera <=11.6 - 12.x only\n\t// IE/Edge & older browsers don't support the :scope pseudo-class.\n\t// Support: Safari 6.0 only\n\t// Safari 6.0 supports :scope but it's an alias of :root there.\n\tsupport.scope = assert( function( el ) {\n\t\tdocElem.appendChild( el ).appendChild( document.createElement( \"div\" ) );\n\t\treturn typeof el.querySelectorAll !== \"undefined\" &&\n\t\t\t!el.querySelectorAll( \":scope fieldset div\" ).length;\n\t} );\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties\n\t// (excepting IE8 booleans)\n\tsupport.attributes = assert( function( el ) {\n\t\tel.className = \"i\";\n\t\treturn !el.getAttribute( \"className\" );\n\t} );\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert( function( el ) {\n\t\tel.appendChild( document.createComment( \"\" ) );\n\t\treturn !el.getElementsByTagName( \"*\" ).length;\n\t} );\n\n\t// Support: IE<9\n\tsupport.getElementsByClassName = rnative.test( document.getElementsByClassName );\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programmatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert( function( el ) {\n\t\tdocElem.appendChild( el ).id = expando;\n\t\treturn !document.getElementsByName || !document.getElementsByName( expando ).length;\n\t} );\n\n\t// ID filter and find\n\tif ( support.getById ) {\n\t\tExpr.filter[ \"ID\" ] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute( \"id\" ) === attrId;\n\t\t\t};\n\t\t};\n\t\tExpr.find[ \"ID\" ] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar elem = context.getElementById( id );\n\t\t\t\treturn elem ? [ elem ] : [];\n\t\t\t}\n\t\t};\n\t} else {\n\t\tExpr.filter[ \"ID\" ] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== \"undefined\" &&\n\t\t\t\t\telem.getAttributeNode( \"id\" );\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\n\t\t// Support: IE 6 - 7 only\n\t\t// getElementById is not reliable as a find shortcut\n\t\tExpr.find[ \"ID\" ] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar node, i, elems,\n\t\t\t\t\telem = context.getElementById( id );\n\n\t\t\t\tif ( elem ) {\n\n\t\t\t\t\t// Verify the id attribute\n\t\t\t\t\tnode = elem.getAttributeNode( \"id\" );\n\t\t\t\t\tif ( node && node.value === id ) {\n\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fall back on getElementsByName\n\t\t\t\t\telems = context.getElementsByName( id );\n\t\t\t\t\ti = 0;\n\t\t\t\t\twhile ( ( elem = elems[ i++ ] ) ) {\n\t\t\t\t\t\tnode = elem.getAttributeNode( \"id\" );\n\t\t\t\t\t\tif ( node && node.value === id ) {\n\t\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn [];\n\t\t\t}\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[ \"TAG\" ] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\n\t\t\t// DocumentFragment nodes don't have gEBTN\n\t\t\t} else if ( support.qsa ) {\n\t\t\t\treturn context.querySelectorAll( tag );\n\t\t\t}\n\t\t} :\n\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\n\t\t\t\t// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( ( elem = results[ i++ ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[ \"CLASS\" ] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== \"undefined\" && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See https://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {\n\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert( function( el ) {\n\n\t\t\tvar input;\n\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// https://bugs.jquery.com/ticket/12359\n\t\t\tdocElem.appendChild( el ).innerHTML = \"<a id='\" + expando + \"'></a>\" +\n\t\t\t\t\"<select id='\" + expando + \"-\\r\\\\' msallowcapture=''>\" +\n\t\t\t\t\"<option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 11-12.16\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\t// The test attribute must be unknown in Opera but \"safe\" for WinRT\n\t\t\t// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section\n\t\t\tif ( el.querySelectorAll( \"[msallowcapture^='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !el.querySelectorAll( \"[selected]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+\n\t\t\tif ( !el.querySelectorAll( \"[id~=\" + expando + \"-]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"~=\" );\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 15 - 18+\n\t\t\t// IE 11/Edge don't find elements on a `[name='']` query in some cases.\n\t\t\t// Adding a temporary attribute to the document before the selection works\n\t\t\t// around the issue.\n\t\t\t// Interestingly, IE 10 & older don't seem to have the issue.\n\t\t\tinput = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"name\", \"\" );\n\t\t\tel.appendChild( input );\n\t\t\tif ( !el.querySelectorAll( \"[name='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*name\" + whitespace + \"*=\" +\n\t\t\t\t\twhitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !el.querySelectorAll( \":checked\" ).length ) {\n\t\t\t\trbuggyQSA.push( \":checked\" );\n\t\t\t}\n\n\t\t\t// Support: Safari 8+, iOS 8+\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t\t// In-page `selector#id sibling-combinator selector` fails\n\t\t\tif ( !el.querySelectorAll( \"a#\" + expando + \"+*\" ).length ) {\n\t\t\t\trbuggyQSA.push( \".#.+[+~]\" );\n\t\t\t}\n\n\t\t\t// Support: Firefox <=3.6 - 5 only\n\t\t\t// Old Firefox doesn't throw on a badly-escaped identifier.\n\t\t\tel.querySelectorAll( \"\\\\\\f\" );\n\t\t\trbuggyQSA.push( \"[\\\\r\\\\n\\\\f]\" );\n\t\t} );\n\n\t\tassert( function( el ) {\n\t\t\tel.innerHTML = \"<a href='' disabled='disabled'></a>\" +\n\t\t\t\t\"<select disabled='disabled'><option/></select>\";\n\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tel.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( el.querySelectorAll( \"[name=d]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( el.querySelectorAll( \":enabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: IE9-11+\n\t\t\t// IE's :disabled selector does not pick up the children of disabled fieldsets\n\t\t\tdocElem.appendChild( el ).disabled = true;\n\t\t\tif ( el.querySelectorAll( \":disabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: Opera 10 - 11 only\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tel.querySelectorAll( \"*,:x\" );\n\t\t\trbuggyQSA.push( \",.*:\" );\n\t\t} );\n\t}\n\n\tif ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||\n\t\tdocElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector ) ) ) ) {\n\n\t\tassert( function( el ) {\n\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( el, \"*\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( el, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t} );\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( \"|\" ) );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( \"|\" ) );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully self-exclusive\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t) );\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( ( b = b.parentNode ) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t// two documents; shallow comparisons work.\n\t\t// eslint-disable-next-line eqeqeq\n\t\tcompare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif ( a == document || a.ownerDocument == preferredDoc &&\n\t\t\t\tcontains( preferredDoc, a ) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif ( b == document || b.ownerDocument == preferredDoc &&\n\t\t\t\tcontains( preferredDoc, b ) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t/* eslint-disable eqeqeq */\n\t\t\treturn a == document ? -1 :\n\t\t\t\tb == document ? 1 :\n\t\t\t\t/* eslint-enable eqeqeq */\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( ( cur = cur.parentNode ) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( ( cur = cur.parentNode ) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[ i ] === bp[ i ] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[ i ], bp[ i ] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t/* eslint-disable eqeqeq */\n\t\t\tap[ i ] == preferredDoc ? -1 :\n\t\t\tbp[ i ] == preferredDoc ? 1 :\n\t\t\t/* eslint-enable eqeqeq */\n\t\t\t0;\n\t};\n\n\treturn document;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\tsetDocument( elem );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t!nonnativeSelectorCache[ expr + \" \" ] &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\n\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t// fragment in IE 9\n\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\tnonnativeSelectorCache( expr, true );\n\t\t}\n\t}\n\n\treturn Sizzle( expr, document, null, [ elem ] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( ( context.ownerDocument || context ) != document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif ( ( elem.ownerDocument || elem ) != document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t( val = elem.getAttributeNode( name ) ) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.escape = function( sel ) {\n\treturn ( sel + \"\" ).replace( rcssescape, fcssescape );\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( ( elem = results[ i++ ] ) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( ( node = elem[ i++ ] ) ) {\n\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[ 1 ] = match[ 1 ].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[ 3 ] = ( match[ 3 ] || match[ 4 ] ||\n\t\t\t\tmatch[ 5 ] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[ 2 ] === \"~=\" ) {\n\t\t\t\tmatch[ 3 ] = \" \" + match[ 3 ] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[ 1 ] = match[ 1 ].toLowerCase();\n\n\t\t\tif ( match[ 1 ].slice( 0, 3 ) === \"nth\" ) {\n\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[ 3 ] ) {\n\t\t\t\t\tSizzle.error( match[ 0 ] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[ 4 ] = +( match[ 4 ] ?\n\t\t\t\t\tmatch[ 5 ] + ( match[ 6 ] || 1 ) :\n\t\t\t\t\t2 * ( match[ 3 ] === \"even\" || match[ 3 ] === \"odd\" ) );\n\t\t\t\tmatch[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === \"odd\" );\n\n\t\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[ 3 ] ) {\n\t\t\t\tSizzle.error( match[ 0 ] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[ 6 ] && match[ 2 ];\n\n\t\t\tif ( matchExpr[ \"CHILD\" ].test( match[ 0 ] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[ 3 ] ) {\n\t\t\t\tmatch[ 2 ] = match[ 4 ] || match[ 5 ] || \"\";\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t( excess = tokenize( unquoted, true ) ) &&\n\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t( excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length ) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[ 0 ] = match[ 0 ].slice( 0, excess );\n\t\t\t\tmatch[ 2 ] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() {\n\t\t\t\t\treturn true;\n\t\t\t\t} :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t( pattern = new RegExp( \"(^|\" + whitespace +\n\t\t\t\t\t\")\" + className + \"(\" + whitespace + \"|$)\" ) ) && classCache(\n\t\t\t\t\t\tclassName, function( elem ) {\n\t\t\t\t\t\t\treturn pattern.test(\n\t\t\t\t\t\t\t\ttypeof elem.className === \"string\" && elem.className ||\n\t\t\t\t\t\t\t\ttypeof elem.getAttribute !== \"undefined\" &&\n\t\t\t\t\t\t\t\t\telem.getAttribute( \"class\" ) ||\n\t\t\t\t\t\t\t\t\"\"\n\t\t\t\t\t\t\t);\n\t\t\t\t} );\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\t/* eslint-disable max-len */\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result.replace( rwhitespace, \" \" ) + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t\t/* eslint-enable max-len */\n\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, _argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, _context, xml ) {\n\t\t\t\t\tvar cache, uniqueCache, outerCache, node, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType,\n\t\t\t\t\t\tdiff = false;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( ( node = node[ dir ] ) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ?\n\t\t\t\t\t\t\t\t\t\tnode.nodeName.toLowerCase() === name :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType === 1 ) {\n\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\n\t\t\t\t\t\t\t// ...in a gzip-friendly way\n\t\t\t\t\t\t\tnode = parent;\n\t\t\t\t\t\t\touterCache = node[ expando ] || ( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\tcache = uniqueCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];\n\t\t\t\t\t\t\tdiff = nodeIndex && cache[ 2 ];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( ( node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t( diff = nodeIndex = 0 ) || start.pop() ) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\tuniqueCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t\tif ( useCache ) {\n\n\t\t\t\t\t\t\t\t// ...in a gzip-friendly way\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\touterCache = node[ expando ] || ( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\t\tcache = uniqueCache[ type ] || [];\n\t\t\t\t\t\t\t\tnodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];\n\t\t\t\t\t\t\t\tdiff = nodeIndex;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// xml :nth-child(...)\n\t\t\t\t\t\t\t// or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t\tif ( diff === false ) {\n\n\t\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\t\twhile ( ( node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t\t( diff = nodeIndex = 0 ) || start.pop() ) ) {\n\n\t\t\t\t\t\t\t\t\tif ( ( ofType ?\n\t\t\t\t\t\t\t\t\t\tnode.nodeName.toLowerCase() === name :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType === 1 ) &&\n\t\t\t\t\t\t\t\t\t\t++diff ) {\n\n\t\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t\touterCache = node[ expando ] ||\n\t\t\t\t\t\t\t\t\t\t\t\t( node[ expando ] = {} );\n\n\t\t\t\t\t\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\t\t\t\t\t\tuniqueCache = outerCache[ node.uniqueID ] ||\n\t\t\t\t\t\t\t\t\t\t\t\t( outerCache[ node.uniqueID ] = {} );\n\n\t\t\t\t\t\t\t\t\t\t\tuniqueCache[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction( function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf( seed, matched[ i ] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t} ) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction( function( selector ) {\n\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction( function( seed, matches, _context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( ( elem = unmatched[ i ] ) ) {\n\t\t\t\t\t\t\tseed[ i ] = !( matches[ i ] = elem );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} ) :\n\t\t\t\tfunction( elem, _context, xml ) {\n\t\t\t\t\tinput[ 0 ] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\n\t\t\t\t\t// Don't keep the element (issue #299)\n\t\t\t\t\tinput[ 0 ] = null;\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t} ),\n\n\t\t\"has\": markFunction( function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t} ),\n\n\t\t\"contains\": markFunction( function( text ) {\n\t\t\ttext = text.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t} ),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test( lang || \"\" ) ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( ( elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute( \"xml:lang\" ) || elem.getAttribute( \"lang\" ) ) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t} ),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement &&\n\t\t\t\t( !document.hasFocus || document.hasFocus() ) &&\n\t\t\t\t!!( elem.type || elem.href || ~elem.tabIndex );\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": createDisabledPseudo( false ),\n\t\t\"disabled\": createDisabledPseudo( true ),\n\n\t\t\"checked\": function( elem ) {\n\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn ( nodeName === \"input\" && !!elem.checked ) ||\n\t\t\t\t( nodeName === \"option\" && !!elem.selected );\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\t// eslint-disable-next-line no-unused-expressions\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[ \"empty\" ]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE<8\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( ( attr = elem.getAttribute( \"type\" ) ) == null ||\n\t\t\t\t\tattr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo( function() {\n\t\t\treturn [ 0 ];\n\t\t} ),\n\n\t\t\"last\": createPositionalPseudo( function( _matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t} ),\n\n\t\t\"eq\": createPositionalPseudo( function( _matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t} ),\n\n\t\t\"even\": createPositionalPseudo( function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"odd\": createPositionalPseudo( function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"lt\": createPositionalPseudo( function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ?\n\t\t\t\targument + length :\n\t\t\t\targument > length ?\n\t\t\t\t\tlength :\n\t\t\t\t\targument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} ),\n\n\t\t\"gt\": createPositionalPseudo( function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t} )\n\t}\n};\n\nExpr.pseudos[ \"nth\" ] = Expr.pseudos[ \"eq\" ];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\ntokenize = Sizzle.tokenize = function( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || ( match = rcomma.exec( soFar ) ) ) {\n\t\t\tif ( match ) {\n\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[ 0 ].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( ( tokens = [] ) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( ( match = rcombinators.exec( soFar ) ) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push( {\n\t\t\t\tvalue: matched,\n\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[ 0 ].replace( rtrim, \" \" )\n\t\t\t} );\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||\n\t\t\t\t( match = preFilters[ type ]( match ) ) ) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push( {\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t} );\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n};\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[ i ].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tskip = combinator.next,\n\t\tkey = skip || dir,\n\t\tcheckNonElements = base && key === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, uniqueCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( ( elem = elem[ dir ] ) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || ( elem[ expando ] = {} );\n\n\t\t\t\t\t\t// Support: IE <9 only\n\t\t\t\t\t\t// Defend against cloned attroperties (jQuery gh-1709)\n\t\t\t\t\t\tuniqueCache = outerCache[ elem.uniqueID ] ||\n\t\t\t\t\t\t\t( outerCache[ elem.uniqueID ] = {} );\n\n\t\t\t\t\t\tif ( skip && skip === elem.nodeName.toLowerCase() ) {\n\t\t\t\t\t\t\telem = elem[ dir ] || elem;\n\t\t\t\t\t\t} else if ( ( oldCache = uniqueCache[ key ] ) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn ( newCache[ 2 ] = oldCache[ 2 ] );\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\tuniqueCache[ key ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[ i ]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[ 0 ];\n}\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[ i ], results );\n\t}\n\treturn results;\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( ( elem = unmatched[ i ] ) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction( function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts(\n\t\t\t\tselector || \"*\",\n\t\t\t\tcontext.nodeType ? [ context ] : context,\n\t\t\t\t[]\n\t\t\t),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( ( elem = temp[ i ] ) ) {\n\t\t\t\t\tmatcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( ( elem = matcherOut[ i ] ) ) {\n\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( ( matcherIn[ i ] = elem ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, ( matcherOut = [] ), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( ( elem = matcherOut[ i ] ) &&\n\t\t\t\t\t\t( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {\n\n\t\t\t\t\t\tseed[ temp ] = !( results[ temp ] = elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t} );\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[ 0 ].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[ \" \" ],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\tvar ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t( checkContext = context ).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\n\t\t\t// Avoid hanging onto element (issue #299)\n\t\t\tcheckContext = null;\n\t\t\treturn ret;\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {\n\t\t\tmatchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[ j ].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\n\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\ttokens\n\t\t\t\t\t\t.slice( 0, i - 1 )\n\t\t\t\t\t\t.concat( { value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" } )\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[ \"TAG\" ]( \"*\", outermost ),\n\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\n\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\toutermostContext = context == document || context || outermost;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\n\t\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t\t// IE/Edge sometimes throw a \"Permission denied\" error when strict-comparing\n\t\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\t\tif ( !context && elem.ownerDocument != document ) {\n\t\t\t\t\t\tsetDocument( elem );\n\t\t\t\t\t\txml = !documentIsHTML;\n\t\t\t\t\t}\n\t\t\t\t\twhile ( ( matcher = elementMatchers[ j++ ] ) ) {\n\t\t\t\t\t\tif ( matcher( elem, context || document, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( ( elem = !matcher && elem ) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// `i` is now the count of elements visited above, and adding it to `matchedCount`\n\t\t\t// makes the latter nonnegative.\n\t\t\tmatchedCount += i;\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\t// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`\n\t\t\t// equals `i`), unless we didn't visit _any_ elements in the above loop because we have\n\t\t\t// no element matchers and no seed.\n\t\t\t// Incrementing an initially-string \"0\" `i` allows `i` to remain a string only in that\n\t\t\t// case, which will result in a \"00\" `matchedCount` that differs from `i` but is also\n\t\t\t// numerically zero.\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( ( matcher = setMatchers[ j++ ] ) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !( unmatched[ i ] || setMatched[ i ] ) ) {\n\t\t\t\t\t\t\t\tsetMatched[ i ] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !match ) {\n\t\t\tmatch = tokenize( selector );\n\t\t}\n\t\ti = match.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( match[ i ] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache(\n\t\t\tselector,\n\t\t\tmatcherFromGroupMatchers( elementMatchers, setMatchers )\n\t\t);\n\n\t\t// Save selector and tokenization\n\t\tcached.selector = selector;\n\t}\n\treturn cached;\n};\n\n/**\n * A low-level selection function that works with Sizzle's compiled\n *  selector functions\n * @param {String|Function} selector A selector or a pre-compiled\n *  selector function built with Sizzle.compile\n * @param {Element} context\n * @param {Array} [results]\n * @param {Array} [seed] A set of elements to match against\n */\nselect = Sizzle.select = function( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tcompiled = typeof selector === \"function\" && selector,\n\t\tmatch = !seed && tokenize( ( selector = compiled.selector || selector ) );\n\n\tresults = results || [];\n\n\t// Try to minimize operations if there is only one selector in the list and no seed\n\t// (the latter of which guarantees us context)\n\tif ( match.length === 1 ) {\n\n\t\t// Reduce context if the leading compound selector is an ID\n\t\ttokens = match[ 0 ] = match[ 0 ].slice( 0 );\n\t\tif ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === \"ID\" &&\n\t\t\tcontext.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {\n\n\t\t\tcontext = ( Expr.find[ \"ID\" ]( token.matches[ 0 ]\n\t\t\t\t.replace( runescape, funescape ), context ) || [] )[ 0 ];\n\t\t\tif ( !context ) {\n\t\t\t\treturn results;\n\n\t\t\t// Precompiled matchers will still verify ancestry, so step up a level\n\t\t\t} else if ( compiled ) {\n\t\t\t\tcontext = context.parentNode;\n\t\t\t}\n\n\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t}\n\n\t\t// Fetch a seed set for right-to-left matching\n\t\ti = matchExpr[ \"needsContext\" ].test( selector ) ? 0 : tokens.length;\n\t\twhile ( i-- ) {\n\t\t\ttoken = tokens[ i ];\n\n\t\t\t// Abort if we hit a combinator\n\t\t\tif ( Expr.relative[ ( type = token.type ) ] ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( ( find = Expr.find[ type ] ) ) {\n\n\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\tif ( ( seed = find(\n\t\t\t\t\ttoken.matches[ 0 ].replace( runescape, funescape ),\n\t\t\t\t\trsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||\n\t\t\t\t\t\tcontext\n\t\t\t\t) ) ) {\n\n\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function if one is not provided\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\t( compiled || compile( selector, match ) )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\t!context || rsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n};\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split( \"\" ).sort( sortOrder ).join( \"\" ) === expando;\n\n// Support: Chrome 14-35+\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert( function( el ) {\n\n\t// Should return 1, but returns 4 (following)\n\treturn el.compareDocumentPosition( document.createElement( \"fieldset\" ) ) & 1;\n} );\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert( function( el ) {\n\tel.innerHTML = \"<a href='#'></a>\";\n\treturn el.firstChild.getAttribute( \"href\" ) === \"#\";\n} ) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t} );\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert( function( el ) {\n\tel.innerHTML = \"<input/>\";\n\tel.firstChild.setAttribute( \"value\", \"\" );\n\treturn el.firstChild.getAttribute( \"value\" ) === \"\";\n} ) ) {\n\taddHandle( \"value\", function( elem, _name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t} );\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert( function( el ) {\n\treturn el.getAttribute( \"disabled\" ) == null;\n} ) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t( val = elem.getAttributeNode( name ) ) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\t\tnull;\n\t\t}\n\t} );\n}\n\n// EXPOSE\nvar _sizzle = window.Sizzle;\n\nSizzle.noConflict = function() {\n\tif ( window.Sizzle === Sizzle ) {\n\t\twindow.Sizzle = _sizzle;\n\t}\n\n\treturn Sizzle;\n};\n\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( function() {\n\t\treturn Sizzle;\n\t} );\n\n// Sizzle requires that there be a global window in Common-JS like environments\n} else if ( typeof module !== \"undefined\" && module.exports ) {\n\tmodule.exports = Sizzle;\n} else {\n\twindow.Sizzle = Sizzle;\n}\n\n// EXPOSE\n\n} )( window );\n"
  },
  {
    "path": "src/screenshotbot/js/websocket-logs.js",
    "content": "\n$(\"[data-websocket-stream]\").each(function () {\n    var $host = $(this);\n    var url = $(this).data(\"websocket-stream\");\n    var initialStreamDone = false;\n\n    var timeout  = -1;\n\n    // Please keep this in sync with +len+ on remote.lisp\n    var maxBufferSize = 1024;\n\n    function append(line) {\n        $host.append(document.createTextNode(line));\n\n        // Minor optimization to avoid super duper aggressive\n        // scrolling.\n        if (line.length < maxBufferSize) {\n            $host.scrollTop($host[0].scrollHeight);\n        }\n    }\n\n    var socket = new WebSocket(url);\n    socket.onerror = function (e) {\n        console.log(\"Error reading socket\", e);\n        append(\"websocket error\");\n    };\n\n    socket.onopen = function () {\n        console.log(\"websocket opened\");\n    }\n\n    socket.onclose = function(e) {\n        console.log(\"got close message\");\n    }\n\n    socket.onmessage = function (e) {\n        append(e.data);\n    }\n});\n"
  },
  {
    "path": "src/screenshotbot/katalon/.gitignore",
    "content": "\n*.so\n*.zaps\n*.dll"
  },
  {
    "path": "src/screenshotbot/katalon/deliver.lisp",
    "content": "(defpackage :screenshotbot/katalon/deliver\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/katalon/deliver)\n\n(defvar *init-hooks* nil)\n\n(defun init-fn ()\n  (format t \"~%~%~%;;; Init screenshotbot library~%~%~%\")\n  (mapc #'funcall *init-hooks*))\n\n(defun deliver-lib (&key debug output)\n  (when debug\n    (ql:quickload :slynk))\n  (push (lambda ()\n          (uiop:symbol-call :slynk :create-server :port 4009))\n        *init-hooks*)\n  (lw-ji:setup-deliver-dynamic-library-for-java\n   :init-java nil)\n  (lw:deliver 'init-fn\n           output\n           (if debug 0 5)\n           :image-type :dll))\n\n(defun safe-prin (x)\n  (let ((*package* (find-package :cl-user)))\n    (prin1-to-string x)))\n\n(defun build (&key debug)\n  (let ((output (asdf:system-relative-pathname :screenshotbot.katalon \"katalon-plugin.so\")))\n   (uiop:run-program\n    (list\n     (lw:lisp-image-name)\n     \"-eval\"\n     (safe-prin\n      `(progn\n         (ql:quickload :screenshotbot.katalon)))\n     \"-eval\"\n     (safe-prin\n      `(progn\n         (deliver-lib :debug ,debug\n                      :output ,output))))\n    :output *standard-output*\n    :error-output *standard-output*)\n    (log:info \"Delivered file size: ~aMB\" (floor (trivial-file-size:file-size-in-octets output) (* 1024 1024)))))\n\n\n;; (build :debug t)\n"
  },
  {
    "path": "src/screenshotbot/katalon/screenshotbot.katalon.asd",
    "content": "(defpackage :screenshotbot.katalon-asdf\n  (:use :cl :asdf))\n(in-package :screenshotbot.katalon-asdf)\n\n(defsystem :screenshotbot.katalon\n    :serial t\n    :depends-on (:util\n                 :log4cl\n                 :trivial-file-size)\n    :components ((:file \"deliver\")))\n"
  },
  {
    "path": "src/screenshotbot/left-side-bar.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/left-side-bar\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/user-api)\n  (:import-from #:screenshotbot/installation\n                #:base-multi-org-feature\n                #:default-oidc-provider\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/template\n                #:left-side-bar\n                #:mdi)\n  (:import-from #:screenshotbot/cdn\n                #:img)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:util\n                #:make-url)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/cdn\n                #:img-with-fallback)\n  (:import-from #:oidc/oidc\n                #:logout-link)\n  (:import-from #:core/ui/left-side-bar\n                #:left-side-bar-container\n                #:left-nav-item))\n(in-package :screenshotbot/left-side-bar)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag bs-icon (&key name)\n  <img src= (format nil \"/assets/images/icons/~a.svg\" name) alt=name />)\n\n(defmethod company-switcher ((installation base-multi-org-feature) &key user)\n  <markup:merge-tag>\n  ,@ (loop for company in (roles:companies-for-user user)\n           collect\n           (let ((company company))\n             <li><a href= (nibble () (company-switch-page company)) class=\"dropdown-item\">\n             Switch to ,(company-name company)\n             </a></li>))\n     <li><a class=\"dropdown-item\" href=\"/organization/new\">New Organization...</a></li>\n\n     <li>\n       <hr class=\"dropdown-divider\" />\n     </li>\n  </markup:merge-tag>)\n\n(defmethod company-switcher ((installation multi-org-feature) &key user)\n  (declare (ignore user))\n  ;; TODO: delete\n  (call-next-method))\n\n(defmethod company-switcher (installation &key user)\n  (declare (ignore user))\n  nil)\n\n(deftag left-side-bar (&key user company script-name)\n  (declare (optimize (speed 0) (debug 3)))\n  <left-side-bar-container\n    logo-small-src=\"/assets/images/logo-small-dark-scaled.webp\"\n    logo-src=\"/assets/images/logo-dark.svg\"\n    logo-alt=\"Screenshotbot logo\" >\n    ,(render-menu-items (installation) :user user :company company :script-name script-name)\n    ,(render-user-menu (installation) :user user :company company)\n  </left-side-bar-container>)\n\n(defmethod web-projects-supported-p (installation)\n  t)\n\n(defmethod billing-supported-p (installation)\n  nil)\n\n(defmethod documentation-url (installation)\n  \"/documentation\")\n\n(defmethod render-menu-items (installation &key user company script-name)\n  <markup:merge-tag>\n    <ul class=\"nav nav-pills flex-column ps-3 pe-3\">\n      <left-nav-item href= \"/runs\" image-class= \"play_arrow\"\n                     script-name=script-name >\n         Recent Runs\n        </left-nav-item>\n\n      ,(when (web-projects-supported-p installation)\n         <left-nav-item href= \"/web-projects\" image-class= \"cloud_queue\"\n                        script-name=script-name >\n           Web Projects\n         </left-nav-item>)\n\n      <left-nav-item href= \"/channels\" image-class= \"book\"\n                     script-name=script-name >\n          Channels\n      </left-nav-item>\n\n      <left-nav-item href= \"/report\" image-class= \"flag\"\n                     script-name=script-name >\n                         Reports\n        </left-nav-item>\n\n      <left-nav-item href= \"/insights\" image-class= \"leaderboard\"\n                     script-name=script-name >\n        Insights\n        </left-nav-item>\n\n    </ul>\n    <hr />\n\n    <ul class=\"nav nav-pills flex-column ps-3 pe-3\">\n\n      <left-nav-item href= \"/team\" image-class= \"group\"\n                     script-name=script-name >\n        Team\n      </left-nav-item>\n\n\n      <left-nav-item href= \"/api-keys\" image-class= \"vpn_key\"\n                     script-name=script-name >\n        API Keys\n      </left-nav-item>\n\n      ,(when (billing-supported-p installation)\n         <left-nav-item href= \"/billing/dashboard\" image-class= \"payment\"\n                        script-name=script-name >\n           Billing\n         </left-nav-item>)\n\n      ,(when user\n         #-screenshotbot-oss\n         <left-nav-item href= \"/ticket/create\" image-class= \"report\"\n                        script-name=script-name >\n                        Report Issue\n         </left-nav-item>)\n    </ul>\n\n    <hr />\n\n    <ul class=\"nav nav-pills flex-column mb-auto ps-3 pe-3\">\n      <left-nav-item href= (documentation-url (installation)) image-class= \"menu_book\" target= \"_blank\"\n                     script-name=script-name >\n        Documentation\n  </left-nav-item>\n\n    </ul>\n\n    <hr class= \"mb-0\" />\n  </markup:merge-tag>)\n\n\n(defmethod render-user-menu (installation &key user company)\n  (when user\n    <div class=\"dropdown p-3\">\n      <a href=\"#\" class=\"d-flex align-items-center text-dark text-decoration-none dropdown-toggle\" id=\"dropdownUser1\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n        <img src= (user-image-url user) alt=\"mdo\" width=\"32\" height=\"32\" class=\"rounded-circle me-2\" />\n        <strong class= \"user-full-name\" > ,(cond\n                                             ((or (not company) (singletonp company) (personalp company))\n                                              (user-full-name user))\n                                             (t\n                                              (company-name company))) </strong>\n      </a>\n      <ul class=\"dropdown-menu dropdown-menu-dark text-small shadow multi-level\" aria-labelledby=\"dropdownUser1\">\n        ,(company-switcher (installation) :user user)\n\n        <li><a class=\"dropdown-item\" href=\"/settings/general\">Settings</a></li>\n        ,(when (adminp user)\n           <li>\n\t         <a href=\"/admin\" class=\"dropdown-item notify-item\">\n\t           <span>Admin</span>\n\t      </a>\n           </li>)\n\n        <li><a class=\"dropdown-item signout-link\" href= (signout-link (installation)) >Sign out</a></li>\n      </ul>\n    </div>))\n\n(defmethod signout-link ((self installation))\n  (symbol-macrolet ((signout-link (auth:session-value :signout-link)))\n   (cond\n     ((default-oidc-provider self)\n      (logout-link (default-oidc-provider self)))\n     ((and\n       (boundp 'auth:*current-session*) ;; for tests\n       signout-link)\n      (let ((old signout-link))\n        (nibble ()\n          (setf signout-link nil)\n          (hex:safe-redirect old))))\n     (t\n      \"/signout\"))))\n\n"
  },
  {
    "path": "src/screenshotbot/login/aws-cognito.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/aws-cognito\n  (:use #:cl)\n  (:import-from #:screenshotbot/login/oidc\n                #:oidc-provider)\n  (:import-from #:oidc/oidc\n                #:after-authentication\n                #:client-id\n                #:authorization-endpoint\n                #:logout-link)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/user-api\n                #:current-user)\n  (:export\n   #:aws-cognito))\n(in-package :screenshotbot/login/aws-cognito)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass aws-cognito (oidc-provider)\n  ())\n\n(defhandler (logout-confirmation :uri \"/cognito/logout-confirmation\") ()\n  (setf (current-user) nil)\n  <html>\n    <body>\n      You are now logged out. <a href= \"/\">Go back here</a>.\n    </body>\n  </html>)\n\n(defmethod after-authentication :after ((self aws-cognito) &key &allow-other-keys)\n  (setf (auth:session-value :signout-link)\n        (logout-link self)))\n\n(defmethod logout-link ((self aws-cognito))\n  \"See https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html.\n\nIn particular this means that /cognito/logout-confirmation must be in your 'Allowed sign-out URLs'\"\n  (let ((url (authorization-endpoint self)))\n    (quri:render-uri\n     (quri:copy-uri\n      (quri:uri url)\n      :path \"/logout\"\n      :query `((\"client_id\" . ,(client-id self))\n               (\"logout_uri\" . ,(hex:make-full-url\n                                   hunchentoot:*request*\n                                   'logout-confirmation)))))))\n"
  },
  {
    "path": "src/screenshotbot/login/github-oauth-ui.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/github-oauth-ui\n  (:use :cl)\n  (:import-from #:screenshotbot/login/common\n                #:oauth-logo-svg)\n  (:import-from #:screenshotbot/login/github-oauth\n                #:github-oauth-provider))\n(in-package :screenshotbot/login/github-oauth-ui)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod oauth-logo-svg ((auth github-oauth-provider))\n  (declare (ignore auth))\n  <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-github\" viewBox=\"0 0 16 16\">\n  <path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z\"/>\n  </svg>)\n"
  },
  {
    "path": "src/screenshotbot/login/github-oauth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/github-oauth\n  (:use :cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:auth\n                #:current-user)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:core/installation/auth-provider\n                #:auth-providers)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:import-from #:hunchentoot-extensions\n                #:make-url)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:oidc/oidc\n                #:access-token-str\n                #:oauth-get-access-token)\n  (:import-from #:screenshotbot/github/access-checks\n                #:github-api-request)\n  (:import-from #:screenshotbot/github/jwt-token\n                #:github-request)\n  (:import-from #:screenshotbot/login/common\n                #:abstract-oauth-provider\n                #:make-oauth-url\n                #:oauth-callback\n                #:oauth-signin-link\n                #:oauth-signup-link)\n  (:import-from #:screenshotbot/login/github\n                #:%find-github-user-by-id\n                #:github-login\n                #:github-user\n                #:known-emails)\n  (:import-from #:screenshotbot/login/oidc\n                #:update-oidc-user)\n  (:import-from #:screenshotbot/model/user\n                #:email)\n  (:import-from #:screenshotbot/user-api\n                #:access-token\n                #:user)\n  (:import-from #:util/events\n                #:push-event)\n  (:export\n   #:github-oauth-provider\n   #:make-gh-oauth-link\n   #:oauth-get-access-token\n   #:prepare-gh-user))\n(in-package :screenshotbot/login/github-oauth)\n\n\n(named-readtables:in-readtable util/java:java-syntax)\n\n\n(defclass github-oauth-provider (abstract-oauth-provider)\n  ((client-id :initarg :client-id\n              :accessor client-id)\n   (client-secret :initarg :client-secret\n                  :accessor client-secret)\n   (redirect-uri :initarg :redirect-uri\n                 :initform nil\n                 :accessor redirect-uri-override\n                 :documentation \"For security reasons we don't want to redirect to\nscreenshotbot.io/oauth-callback directly. Currently, GitHub doesn't really respect\nthe redirect_uri parameter correctly. (i.e. it can be different in the access_token request\nand GitHub will still respond without failure.) Using an intermediate sub-domain for\nredirect reduces the chances that somebody gets access to a sub-domain.\"))\n  (:default-initargs\n   :oauth-name \"GitHub\"))\n\n(defun github-oauth-provider ()\n  (loop for auth-provider in (auth-providers *installation*)\n        if (typep auth-provider 'github-oauth-provider)\n          return auth-provider))\n\n(defun redirect-uri (github-oauth)\n  (declare (ignore github-oauth))\n  (or\n   (redirect-uri-override github-oauth)\n   (quri:render-uri\n    (quri:merge-uris\n     (quri:uri \"/account/oauth-callback\")\n     (quri:uri\n      (installation-domain *installation*))))))\n\n(defun make-gh-oauth-link (github-oauth redirect\n                           &key\n                             authorize-link\n                             ;; We sometimes need to get access tokens\n                             ;; not for login purposes.\n                             (access-token-callback #'process-access-token)\n                             (scope \"user:email\"))\n  (declare (ignore authorize-link)) ;; too lazy to figure out what this was\n  (let* ((callback (lambda (&key code error redirect-uri)\n                            (cond\n                              (code\n                               (%gh-oauth-callback\n                                github-oauth code\n                                redirect\n                                :access-token-callback access-token-callback\n                                :oauth-redirect-uri redirect-uri))\n                              (t\n                               (warn \"GitHub Oauth failed: ~a\" error)\n                               (hex:safe-redirect \"/signin\" :error error)))))\n         (base-url (format nil \"https://github.com~a\"\n                          (make-url \"/login/oauth/authorize\"\n                                    :client_id (client-id github-oauth)\n                                    :scope scope))))\n    (make-oauth-url base-url callback\n                    :redirect-uri (redirect-uri github-oauth))))\n\n(defun handle-github (event auth redirect)\n  (nibble ()\n    (push-event event)\n    (hex:safe-redirect (make-gh-oauth-link auth redirect))))\n\n(defmethod oauth-signin-link ((auth github-oauth-provider) redirect)\n  (handle-github :github-oauth.signin auth redirect))\n\n(defmethod oauth-signup-link ((auth github-oauth-provider) redirect)\n  (handle-github :github-oauth.signup auth redirect))\n\n(defun process-access-token (access-token)\n  (let ((user (get-user-from-gh-access-token access-token)))\n    (setf (current-user) user)))\n\n(auto-restart:with-auto-restart ()\n (defun %gh-oauth-callback (auth-provider code redirect &key (access-token-callback #'process-access-token)\n                                                          oauth-redirect-uri)\n   (let ((access-token (oauth-get-access-token\n                        \"https://github.com/login/oauth/access_token\"\n                        :client_id (client-id auth-provider)\n                        :client_secret (client-secret auth-provider)\n                        :code code\n                        :redirect_uri oauth-redirect-uri)))\n     (funcall access-token-callback access-token)\n     (hex:safe-redirect redirect))))\n\n(defun get-github-emails (access-token-str)\n  (restart-case\n      (let ((ret\n             (github-request  \"/user/emails\"\n                              :installation-token access-token-str\n                              :method :get)))\n        ;; Move the Primary email to the top of the list\n        (loop for x in ret\n              if (assoc-value x :primary)\n                do (setf ret\n                         (cons x (remove x ret))))\n        (loop for x in ret\n              if (assoc-value x :verified)\n                collect (assoc-value x :email)))\n    (retry-get-github-emails ()\n      (get-github-emails access-token-str))))\n\n(defun get-user-from-gh-access-token (access-token)\n  (let ((access-token (access-token-str access-token)))\n    (let* ((response (github-api-request \"/user\"\n                                         :access-token access-token))\n           (emails  (restart-case\n                        (get-github-emails access-token)\n                      (use-bad-email ()\n                        (list (assoc-value response :email))))))\n      (prepare-gh-user\n       :emails emails\n       :user-id (assoc-value response :id)\n       :github-login (assoc-value response :login)\n       :full-name (assoc-value response :name)\n       :avatar (assoc-value response :avatar--url)))))\n\n\n(defun prepare-gh-user (&key emails user-id full-name avatar\n                                    github-login)\n  (assert user-id)\n  (let ((gh-user (or\n                  (%find-github-user-by-id user-id)\n                  (make-instance 'github-user\n                                 :gh-user-id user-id))))\n\n    (let ((ret (update-oidc-user gh-user\n                                 :email (car emails)\n                                 :user-id user-id\n                                 :full-name full-name\n                                 :avatar avatar)))\n      (with-transaction ()\n        (setf (github-login gh-user)\n              github-login))\n      (dolist (email emails)\n        (with-transaction ()\n          (pushnew email (known-emails gh-user)\n                   :test 'equal)))\n      ret)))\n"
  },
  {
    "path": "src/screenshotbot/login/google-oauth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/google-oauth\n  (:use :cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:oidc/oidc\n                #:make-oidc-auth-link\n                #:verify-userinfo)\n  (:import-from #:screenshotbot/login/common\n                #:oauth-logo-svg)\n  (:import-from #:screenshotbot/login/oidc\n                #:identifier\n                #:oidc-provider\n                #:oidc-user)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:auth\n                #:oauth-user-user)\n  (:export\n   #:google-access-token\n   #:google-oauth-provider\n   #:google-oauth-redirect\n   #:google-user\n   #:make-google-oauth-link))\n(in-package :screenshotbot/login/google-oauth)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass google-oauth-provider (oidc-provider)\n  ((domain :initform nil\n           :initarg :domain\n           :reader google-domain\n           :documentation \"Restrict this google oauth to this domain\"))\n  (:default-initargs\n   :issuer \"https://accounts.google.com\"\n   :scope  \"openid email profile\"\n   :identifier  'google\n   :oauth-name \"Google\"))\n\n;; datastore backward compatibility\n(defclass google-user (oidc-user)\n  ((identifier :initform 'google))\n  (:metaclass persistent-class))\n\n(defmethod oauth-logo-svg ((auth google-oauth-provider))\n  (declare (ignore auth))\n  <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-google\" viewBox=\"0 0 16 16\">\n  <path d=\"M15.545 6.558a9.42 9.42 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.689 7.689 0 0 1 5.352 2.082l-2.284 2.284A4.347 4.347 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.304a4.792 4.792 0 0 0 0 3.063h.003c.635 1.893 2.405 3.301 4.492 3.301 1.078 0 2.004-.276 2.722-.764h-.003a3.702 3.702 0 0 0 1.599-2.431H8v-3.08h7.545z\"/>\n  </svg>)\n\n(defmethod make-oidc-auth-link :around ((self google-oauth-provider)\n                                        redirect\n                                        &key &allow-other-keys)\n  (cond\n    ((google-domain self)\n     (quri:render-uri\n      (let ((old (quri:uri (call-next-method))))\n        (quri:copy-uri\n         old\n         :query (list*\n                 `(\"hd\" . ,(google-domain self))\n                 (quri:uri-query-params old))))))\n    (t\n     (call-next-method))))\n\n(defmethod verify-userinfo ((self google-oauth-provider) user-info)\n  (when (google-domain self)\n    (let ((expected-domain (assoc-value user-info :hd))\n          (actual-domain (google-domain self)))\n      (with-extras ((\"expected-domain\" expected-domain)\n                    (\"actual-domain\" actual-domain))\n       (assert (equal expected-domain\n                      actual-domain))))))\n\n(defmethod google-user (user)\n  \"Get the google-user associated with a given user.\"\n  (loop for gu in (bknr.datastore:class-instances 'google-user)\n        if (eql user (ignore-errors (oauth-user-user gu)))\n          return gu))\n"
  },
  {
    "path": "src/screenshotbot/login/microsoft-entra.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/microsoft-entra\n  (:use #:cl)\n  (:import-from #:screenshotbot/login/oidc\n                #:oidc-provider)\n  (:import-from #:auth/login/cached-avatar\n                #:cached-avatar-provider)\n  (:export\n   #:microsoft-entra-provider))\n(in-package :screenshotbot/login/microsoft-entra)\n\n(defclass microsoft-entra-provider (oidc-provider\n                                    cached-avatar-provider)\n  ()\n  (:documentation \"A provider for Microsoft Entra (Azure). In particular the default\nOIDC provider doesn't handle profile pictures correctly for Entra.\"))\n\n\n"
  },
  {
    "path": "src/screenshotbot/login/populate.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/populate\n  (:use :cl)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:bknr.datastore\n                #:store-object-with-id\n                #:with-transaction)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:%put-run\n                #:%recorder-run-post\n                #:run-response-id)\n  (:import-from #:screenshotbot/dashboard/ensure-company\n                #:populate-company)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/login/common\n                #:after-create-user)\n  (:import-from #:screenshotbot/model/company\n                #:add-company-report\n                #:company)\n  (:import-from #:screenshotbot/model/image\n                #:image\n                #:make-image)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:was-promoted-p\n                #:active-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/user\n                #:user-personal-company)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/screenshot-api\n                #:local-image)\n  (:import-from #:screenshotbot/server\n                #:document-root)\n  (:import-from #:screenshotbot/user-api\n                #:recorder-previous-run\n                #:recorder-run-channel\n                #:user)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors)\n  (:local-nicknames (#:dto #:screenshotbot/api/model))\n  (:export\n   #:populate-company))\n(in-package :screenshotbot/login/populate)\n\n(defun md5sum-file (file)\n  (let ((bytes (md5-file file)))\n    (ironclad:byte-array-to-hex-string bytes)))\n\n\n(defmethod populate-company ((company company))\n  (flet ((make-image (path)\n           (make-instance 'local-image\n                          :hash (md5sum-file (path:catfile (document-root)\n                                                               path))\n                          :url (str:concat \"/\" (namestring path))))\n         (create-run (&key image commit)\n           (destructuring-bind (create-run-response run channel)\n               (%put-run\n                company\n                (make-instance 'dto:run\n                               :channel \"demo-project\"\n                               :github-repo nil\n                               :screenshots (list\n                                             (make-instance\n                                              'dto:screenshot\n                                              :name \"image1\"\n                                              :image-id (oid image)))\n                               :commit-hash commit\n                               :main-branch \"master\"\n                               :trunkp t\n                               :cleanp t)\n                :api-key nil)\n             (declare (ignore create-run-response channel))\n             (check-type run recorder-run)\n             run)))\n    (let ((image1 (make-image \"assets/images/example-view-square.svg.png\"))\n          (image2 (make-image \"assets/images/example-view.svg.png\")))\n\n      (let* ((run (create-run :image image1 :commit \"abcdef1\"))\n             (run-2 (create-run :image image2 :commit \"abcdef2\")))\n        (with-transaction ()\n          (setf (recorder-previous-run run-2) run)\n          (setf (was-promoted-p run) t\n                (was-promoted-p run-2) t)\n          (setf (active-run (recorder-run-channel run-2) \"master\") run-2))\n        (let ((report\n               (make-instance 'report\n                              :run run-2\n                              :channel (recorder-run-channel run-2)\n                              :previous-run run\n                              :title \"1 screenshots changed [demo]\")))\n          (add-company-report company report))))))\n\n(defmethod after-create-user ((installation multi-org-feature)\n                              user)\n  ;; With multi-org-mode, each user will have an associated\n  ;; personal company, in that case this might be a good time\n  ;; to populate it with dummy data.\n  (when-let ((company (user-personal-company user)))\n    (ignore-and-log-errors ()\n      (populate-company company))))\n\n\n;; (populate-company (user-personal-company (arnold)))\n\n;; (company-images (user-personal-company (arnold)))\n"
  },
  {
    "path": "src/screenshotbot/login/require-invite-sso-mixin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/require-invite-sso-mixin\n  (:use #:cl)\n  (:import-from #:oidc/oidc\n                #:logout-link\n                #:authentication-error\n                #:after-authentication)\n  (:import-from #:auth/model/invite\n                #:invite-used-p\n                #:invites-with-email)\n  (:import-from #:screenshotbot/model/user\n                #:user-with-email)\n  (:import-from #:screenshotbot/invite\n                #:accept-invite)\n  (:import-from #:nibble\n                #:nibble)\n  (:export\n   #:require-invite-sso-mixin))\n(in-package :screenshotbot/login/require-invite-sso-mixin)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass require-invite-sso-mixin ()\n  ((disallow-domains :initarg :disallow-domains\n                     :reader disallow-domains))\n  (:documentation \"If there's no invite for this SSO, then don't allow it.\"))\n\n(defun no-invite-handler (auth-provider)\n  <html>\n    <body>\n      <p> You must have an invitation to be able to access this site.</p>\n      <!-- at this point, (auth:session-value :signout-link) might not be set, but\n           we still want to be able to log-out of the SSO provider -->\n      <a href= (or (logout-link auth-provider) \"/signout\") >Sign out</a>\n    </body>\n  </html>)\n\n(define-condition disallowed-domain-error (authentication-error)\n  ()\n  (:default-initargs :message\n   \"You may not create an account this email domain. Please use SSO instead.\"))\n\n(define-condition no-invite-error (authentication-error)\n  ()\n  (:default-initargs :message \"You must have an invite to be able to login\"))\n\n(defmethod after-authentication :around ((self require-invite-sso-mixin) &key\n                                                                           email\n                                         &allow-other-keys)\n  (let ((user (user-with-email email)))\n   (cond\n     ((and\n       user\n       (roles:companies-for-user user))\n      (call-next-method))\n     ((not (invites-with-email email))\n      (hex:safe-redirect (nibble () (no-invite-handler self))))\n     (t\n      (loop for domain in (disallow-domains self)\n            if (str:ends-with-p (format nil \"@~a\" domain) email)\n              do (error 'disallowed-domain-error))\n      (prog1\n          (call-next-method)\n        (accept-all-invites (auth:current-user) (invites-with-email email)))))))\n\n(defun accept-all-invites (user invites)\n  (loop for invite in invites do\n    (accept-invite invite :redirect nil :user user)\n    (setf (invite-used-p invite) t)))\n"
  },
  {
    "path": "src/screenshotbot/login/template.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/template\n  (:use :cl)\n  (:import-from #:screenshotbot/cdn\n                #:img)\n  (:import-from #:screenshotbot/template\n                #:landing-head)\n  (:import-from #:screenshotbot/login/common\n                #:auth-template-impl\n                #:auth-template)\n  (:import-from #:screenshotbot/installation\n                #:installation))\n(in-package :screenshotbot/login/template)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod auth-template-impl ((self installation) children &key body-class simple\n                                                              full-width)\n  <html lang= \"en\" >\n    <landing-head simple=simple >\n      ,(progn\n         ;; this is required in the OSS version because of it's using\n         ;; a hacky mix of the pro and OSS dashboard css.\n         #+screenshotbot-oss\n         <style>\n           html {\n           font-size: 10px;\n           }\n         </style>)\n\n    </landing-head>\n\n    ,(cond\n       (simple\n        <body class= \"dashboard login-simple\" >\n                    <div class= \"content-page bg-light-lighten mt-3\">\n            <div class= \"container body-vh-100\" style= (unless full-width \"max-width: 40em\") >\n                          ,@ (progn children)\n            </div>\n          </div>\n\n        </body>)\n       (t\n        <body class= (format nil \"auth-pages ~a\" body-class) >\n\n\n          <div class= \"left-image\">\n            <a class= \"navbar-brand\" href= \"/\">\n              <img src= \"/assets/images/logo-dark.svg\" />\n            </a>\n            <img class= \"botty-image\"  src= \"/assets/images/auth/botty-left.png\" />\n\n            <span class= \"copy\" >\n              &copy; 2018-2025 Modern Interpreters Inc.\n            </span>\n          </div>\n\n          <div class= \"form-container\">\n            <div class= \"home-link\">\n              <a href= \"/\">Home</a>\n            </div>\n            ,@ (progn children)\n          </div>\n        </body>))\n\n  </html>)\n"
  },
  {
    "path": "src/screenshotbot/login/test-common.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-common\n  (:use :cl)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:has-typep\n                #:is-equal-to)\n  (:import-from #:it.bese.fiveam\n                #:is\n                #:signals\n                #:def-fixture\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/login/common\n                #:illegal-oauth-redirect\n                #:server-with-login\n                #:signin-get)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :screenshotbot/login/test-common)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n    (with-test-store ()\n      (with-fake-request ()\n       (auth:with-sessions ()\n         (cl-mock:with-mocks ()\n           (&body)))))))\n\n(test server-with-login-happy-path\n  (with-fixture state ()\n    (let ((last-redirect))\n      (cl-mock:if-called 'signin-get\n                         (lambda (&key alert redirect)\n                           (setf last-redirect redirect)))\n      (server-with-login\n       (lambda () \"foo\")\n       :needs-login t)\n      (assert-that last-redirect\n                   (has-typep 'nibble:nibble)))))\n\n(test server-with-login-with-allowing-url-redirect\n  (with-fixture state ()\n    (let ((last-redirect))\n      (cl-mock:if-called 'signin-get\n                         (lambda (&key alert redirect)\n                           (setf last-redirect redirect)))\n      (with-fake-request (:script-name \"/foo/bar\")\n        (server-with-login\n         (lambda () \"foo\")\n         :allow-url-redirect t\n         :needs-login t))\n      (assert-that last-redirect\n                   (is-equal-to \"/foo/bar\")))))\n\n(test handle-oauth-callback-happy-path\n  (with-fixture state ()\n    (let ((callback-result)\n          (rendered-nibble))\n      (cl-mock:if-called 'nibble:render-nibble\n                         (lambda (self state)\n                           (setf rendered-nibble state)\n                           \"rendered-content\"))\n      (with-fake-request ()\n        (setf callback-result\n              (screenshotbot/login/common::handle-oauth-callback\n               (make-instance 'auth:auth-acceptor-mixin)\n               \"test-code\"\n               \"12345\")))\n      (assert-that callback-result\n                   (is-equal-to \"rendered-content\"))\n      (assert-that rendered-nibble\n                   (is-equal-to \"12345\")))))\n\n\n(test handle-oauth-with-unpermitted-redirect\n  (with-fixture state ()\n    (let ((callback-result)\n          (rendered-nibble))\n      (cl-mock:if-called 'nibble:render-nibble\n                         (lambda (self state)\n                           (setf rendered-nibble state)\n                           \"rendered-content\"))\n      (with-fake-request ()\n        (signals illegal-oauth-redirect\n          (screenshotbot/login/common::handle-oauth-callback\n           (make-instance 'auth:auth-acceptor-mixin)\n           \"test-code\"\n           \"12345,https://attacker.example.com\"))))))\n\n(test handle-oauth-with-permitted-redirect\n  (with-fixture state ()\n    (let ((callback-result)\n          (rendered-nibble))\n      (cl-mock:if-called 'screenshotbot/login/common::allow-oauth-redirect-p\n                         (lambda (installation redirect)\n                           (declare (ignore installation))\n                           (equal redirect \"https://trusted.example.com\")))\n      (with-fake-request ()\n        (catch 'hunchentoot::handler-done\n          (screenshotbot/login/common::handle-oauth-callback\n           (make-instance 'auth:auth-acceptor-mixin)\n           \"test-code\"\n           \"12345,https://trusted.example.com\")\n          (fail \"expected to redirect\"))\n        (is (equal (hunchentoot:header-out :location hunchentoot:*reply*)\n                   \"https://trusted.example.com/account/oauth-callback?state=12345&code=test-code\"))))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-forgot-password.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-forgot-password\n  (:use :cl)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:with-fixture)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/login/common\n                #:standard-auth-provider)\n  (:import-from #:screenshotbot/login/forgot-password\n                #:change-password-request\n                #:forgot-password-page\n                #:reset-password-after-confirmation)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/testing\n                #:screenshot-test)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :screenshotbot/login/test-forgot-password)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture screenshots (&key (providers (list (make-instance 'standard-auth-provider))))\n  (let ((*installation* (make-instance 'installation\n                                       :auth-providers providers)))\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (with-test-store ()\n          (let ((user (make-user)))\n           (&body)))))))\n\n\n(screenshot-test forgot-password-page ()\n  (with-fixture screenshots ()\n    (forgot-password-page)))\n\n(screenshot-test reset-password-when-request-is-used-up ()\n  (with-fixture screenshots ()\n    (reset-password-after-confirmation\n     :user user\n     :req (make-instance 'change-password-request\n                         :used-up-p t))))\n\n(screenshot-test reset-password-when-request-is-active ()\n  (with-fixture screenshots ()\n    (reset-password-after-confirmation\n     :user user\n     :req (make-instance 'change-password-request))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-github-oauth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-github-oauth\n  (:use :cl)\n  (:import-from #:auth\n                #:oauth-user-user\n                #:user-email\n                #:user-full-name)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:is\n                #:is-false\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/login/github\n                #:github-user)\n  (:import-from #:screenshotbot/login/github-oauth\n                #:prepare-gh-user)\n  (:import-from #:screenshotbot/model/company\n                #:prepare-singleton-company)\n  (:import-from #:screenshotbot/user-api\n                #:user\n                #:user-image-url)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:auth/avatar\n                #:actual-public-url))\n(in-package :screenshotbot/login/test-github-oauth)\n\n(util/fiveam:def-suite)\n\n(defvar *test-email* \"arnold+test_github_oauth@example.com\")\n\n(defun %prepare-test-user ()\n  (declare (optimize (debug 3)\n                     (speed 0)))\n  (let ((user (prepare-gh-user\n               :emails (list *test-email*)\n               :user-id 22\n               :full-name \"Arnold Noronha\"\n               :avatar \"https://example.com/doofus.png\")))\n    user))\n\n(def-fixture state ()\n  (with-test-store ()\n   (let ((*installation* (make-instance 'installation)))\n     (prepare-singleton-company)\n     (&body))))\n\n(test slot-access-issue\n  (with-fixture state ()\n    (let ((obj (make-instance 'github-user :gh-user-id \"foo\")))\n      (is (equal (oauth-user-user obj) nil)))))\n\n(test create-new-user\n  (with-fixture state ()\n    (let ((user (%prepare-test-user)))\n      (is (typep user 'user))\n      (is-false (null (github-user user)))\n      (is (equal *test-email* (user-email user)))\n      (is (equal \"Arnold Noronha\" (user-full-name user)))\n      (is (equal \"https://example.com/doofus.png\"\n                 (actual-public-url user))))))\n\n(test second-time-with-the-same-user\n  (with-fixture state ()\n    (let ((user (%prepare-test-user)))\n      (let ((user2 (%prepare-test-user)))\n        (is (eql (store-object-id user) (store-object-id user2)))))))\n\n(test second-time-change-args\n  (with-fixture state ()\n    (let ((user (%prepare-test-user)))\n      (let* ((user (prepare-gh-user\n                    :emails (list \"sam@example.com\")\n                    :user-id 22\n                    :full-name \"Sam Currie\"\n                    :avatar \"https://oddflowerstudio.com/profilepic\")))\n        (is (equal \"sam@example.com\" (user-email user)))\n        (is (equal \"Sam Currie\" (user-full-name user)))\n        (is (equal \"https://oddflowerstudio.com/profilepic\"\n                   (actual-public-url user)))))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-google-oauth.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-google-oauth\n  (:use :cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:auth\n                #:with-sessions)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:finishes\n                #:is\n                #:signals\n                #:test\n                #:with-fixture)\n  (:import-from #:oidc/oidc\n                #:discover\n                #:make-oidc-auth-link\n                #:verify-userinfo)\n  (:import-from #:screenshotbot/login/google-oauth\n                #:google-domain\n                #:google-oauth-provider)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :screenshotbot/login/test-google-oauth)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (&body))\n\n\n(defclass test-google-oauth-provider (google-oauth-provider)\n  ())\n\n(defmethod discover ((self test-google-oauth-provider))\n  `((:ISSUER . \"https://accounts.google.com\") (:AUTHORIZATION--ENDPOINT . \"https://accounts.google.com/o/oauth2/v2/auth\") (:DEVICE--AUTHORIZATION--ENDPOINT . \"https://oauth2.googleapis.com/device/code\") (:TOKEN--ENDPOINT . \"https://oauth2.googleapis.com/token\") (:USERINFO--ENDPOINT . \"https://openidconnect.googleapis.com/v1/userinfo\") (:REVOCATION--ENDPOINT . \"https://oauth2.googleapis.com/revoke\") (:JWKS--URI . \"https://www.googleapis.com/oauth2/v3/certs\") (:RESPONSE--TYPES--SUPPORTED \"code\" \"token\" \"id_token\" \"code token\" \"code id_token\" \"token id_token\" \"code token id_token\" \"none\") (:SUBJECT--TYPES--SUPPORTED \"public\") (:ID--TOKEN--SIGNING--ALG--VALUES--SUPPORTED \"RS256\") (:SCOPES--SUPPORTED \"openid\" \"email\" \"profile\") (:TOKEN--ENDPOINT--AUTH--METHODS--SUPPORTED \"client_secret_post\" \"client_secret_basic\") (:CLAIMS--SUPPORTED \"aud\" \"email\" \"email_verified\" \"exp\" \"family_name\" \"given_name\" \"iat\" \"iss\" \"locale\" \"name\" \"picture\" \"sub\") (:CODE--CHALLENGE--METHODS--SUPPORTED \"plain\" \"S256\") (:GRANT--TYPES--SUPPORTED \"authorization_code\" \"refresh_token\" \"urn:ietf:params:oauth:grant-type:device_code\" \"urn:ietf:params:oauth:grant-type:jwt-bearer\")))\n\n(test make-oidc-auth-link-for-google\n  (with-fixture state ()\n    (let ((provider (make-instance 'test-google-oauth-provider\n                                   :client-id \"foobar\"\n                                   :client-secret \"barbar\"\n                                   :domain \"example.com\")))\n      (with-fake-request ()\n        (with-sessions ()\n          (let ((uri (make-oidc-auth-link provider \"https://foobar.com\")))\n            (is (stringp uri))\n            (let* ((uri (quri:uri uri))\n                   (params (quri:uri-query-params uri)))\n              (is (equal \"foobar\" (assoc-value params \"client_id\" :test #'equal)))\n              (is (equal \"example.com\" (assoc-value params \"hd\" :test #'equal))))))))))\n\n\n(test default-domain-is-nil\n  \"It's very likely we change the default domain for local testing, we\nwant to make sure we don't accidently push that change.\"\n  (with-fixture state ()\n   (let ((provider (make-instance 'google-oauth-provider)))\n     (is (null (google-domain provider))))))\n\n(test verify-userinfo-for-google\n  (with-fixture state ()\n   (let ((provider (make-instance 'google-oauth-provider\n                                  :domain nil)))\n     (finishes\n       (verify-userinfo\n        provider nil)))))\n\n(test verify-userinfo-for-google-with-domain\n  (with-fixture state ()\n   (let ((provider (make-instance 'google-oauth-provider\n                                  :domain \"example.com\")))\n     (finishes\n       (verify-userinfo\n        provider `((:hd . \"example.com\"))))\n     (signals simple-error\n       (verify-userinfo\n        provider `((:hd . \"foo.com\")))))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-login.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-login\n  (:use :cl)\n  (:import-from #:it.bese.fiveam\n                #:test)\n  (:import-from #:screenshotbot/login/common\n                #:standard-auth-provider\n                #:signin-get)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:util/testing\n                #:screenshot-static-page\n                #:with-fake-request)\n  (:import-from #:screenshotbot/login/login\n                #:sign-in-after-email)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/login/test-login)\n\n\n(util/fiveam:def-suite)\n\n(test login-screenshot-test\n  (with-installation ()\n   (with-fake-request ()\n     (auth:with-sessions ()\n       (screenshot-static-page\n        :screenshotbot\n        \"login\"\n        (markup:write-html\n         (signin-get)))))))\n\n(test login-password-test\n  (with-installation ()\n   (with-fake-request ()\n     (auth:with-sessions ()\n       (screenshot-static-page\n        :screenshotbot\n        \"login-password-test\"\n        (markup:write-html\n         (sign-in-after-email\n          (make-instance 'standard-auth-provider)\n          :email \"foo@example.com\")))))))\n\n(test login-error-screen\n  (with-installation ()\n   (with-fake-request ()\n     (auth:with-sessions ()\n       (screenshot-static-page\n        :screenshotbot\n        \"login-error-screen\"\n        (markup:write-html\n         (with-form-errors (:errors `((:email . \"Email doesn't exist\"))\n                            :email \"blah@gmail.com\"\n                            :was-validated t)\n           (signin-get))))))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-oidc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-oidc\n  (:use :cl)\n  (:import-from #:auth\n                #:user-email\n                #:oauth-user-email\n                #:oauth-user-user)\n  (:import-from #:bknr.datastore\n                #:convert-slot-value-while-restoring)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:is\n                #:test\n                #:with-fixture)\n  (:import-from #:oidc/oidc\n                #:after-authentication)\n  (:import-from #:screenshotbot/login/oidc\n                #:update-oidc-user\n                #:%email\n                #:%user\n                #:oidc-provider\n                #:oidc-user)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:screenshotbot/login/signup\n                #:*signup-throttler*)\n  (:import-from #:util/throttler\n                #:keyed-throttler\n                #:throttler))\n(in-package :screenshotbot/login/test-oidc)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key expiration-seconds)\n  (let ((*signup-throttler* (make-instance 'keyed-throttler :tokens 100)))\n    (cl-mock:with-mocks ()\n      (with-installation ()\n        (with-test-store ()\n          (with-fake-request ()\n            (with-test-user (:user user)\n              (auth:with-sessions ()\n                (let ((auth (make-instance 'oidc-provider\n                                           :expiration-seconds expiration-seconds\n                                           :identifier 'foo)))\n                  (&body))))))))))\n\n(test happy-path\n  (with-fixture state ()\n    (after-authentication auth\n                          :user-id (oid user)\n                          :email \"arnold@tdrhq.com\")))\n\n(test expiration-time-is-set\n  (let ((got-expires-in nil))\n   (with-fixture state (:expiration-seconds 30)\n     (if-called '(setf auth:session-value)\n                (lambda (user key &key expires-in)\n                  (setf got-expires-in expires-in)))\n     (after-authentication auth\n                           :user-id (oid user)\n                           :email \"arnold@tdrhq.com\")\n     (is (eql 30 got-expires-in)))))\n\n\n(test email-slot\n  \"These tests are temporary for a migration, you can delete it once the\n%email and email slots are merged.\"\n  (with-fixture state ()\n   (let ((self (make-instance 'oidc-user\n                              :email \"foo\")))\n     (is (equal \"foo\" (oauth-user-email self)))\n     (setf (oauth-user-email self) \"bleh\")\n     (is (equal \"bleh\" (oauth-user-email self)))\n     (is (equal \"bleh\" (slot-value self '%email)))\n     (fiveam:is-false (slot-boundp self 'screenshotbot/login/oidc::email))\n     (convert-slot-value-while-restoring\n      self 'email \"car\")\n     (is (equal \"car\" (oauth-user-email self)))\n     (is (equal \"ack\" (oauth-user-email\n                       (make-instance 'oidc-user\n                                      :old-email-slot \"ack\")))))))\n\n(test user-slot\n  \"These tests are temporary for a migration, you can delete it once the\n%user and user slots are merged.\"\n  (with-fixture state ()\n   (let ((self (make-instance 'oidc-user\n                              :user \"foo\")))\n     (is (equal \"foo\" (oauth-user-user self)))\n     (setf (oauth-user-user self) \"bleh\")\n     (is (equal \"bleh\" (oauth-user-user self)))\n     (is (equal \"bleh\" (slot-value self '%user)))\n     (fiveam:is-false (slot-boundp self 'screenshotbot/login/oidc::user))\n     (convert-slot-value-while-restoring\n      self 'user \"car\")\n     (is (equal \"car\" (oauth-user-user self)))\n     (is (equal \"ack\" (oauth-user-user\n                       (make-instance 'oidc-user\n                                      :old-user-slot \"ack\")))))))\n\n(test we-dont-override-an-existing-user-with-a-given-email\n  (with-fixture state ()\n    (let ((other-user (make-instance 'user :email \"foo@example.com\"))\n          (oidc-user (make-instance 'oidc-user)))\n      (let ((user1 (update-oidc-user\n                    oidc-user\n                    :email \"bar@example.com\"\n                    :user-id \"ignored\"\n                    :full-name \"Foo Bar\"\n                    :avatar \"https://example.com/fake.png\")))\n        (is\n         (eql user1 (update-oidc-user\n                     oidc-user\n                     :email \"foo@example.com\"\n                     :user-id \"ignored\"\n                     :full-name \"Foo Bar\"\n                     :avatar \"https://example.com/fake.png\")))\n        (is (not (eql user1 other-user)))\n        (is (equal \"bar@example.com\" (user-email user1)))\n        (is (equal \"foo@example.com\" (user-email other-user)))\n        (is (equal (list oidc-user)\n                   (auth:oauth-users user1)))\n        (is (equal nil (auth:oauth-users other-user)))))))\n\n(test we-DO-override-an-existing-user-if-theres-no-current-user\n  (with-fixture state ()\n    (let ((other-user (make-instance 'user :email \"foo@example.com\"))\n          (oidc-user (make-instance 'oidc-user)))\n      (let ((user1 (update-oidc-user\n                    oidc-user\n                    :email \"foo@example.com\"\n                    :user-id \"ignored\"\n                    :full-name \"Foo Bar\"\n                    :avatar \"https://example.com/fake.png\")))\n        (is (eql other-user user1))\n        (is (equal (list oidc-user)\n                   (auth:oauth-users other-user)))))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-populate.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-populate\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/login/populate\n                #:populate-company)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/model/report\n                #:report-channel)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:fiveam-matchers/lists\n                #:contains))\n(in-package :screenshotbot/login/test-populate)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((company (make-instance 'company)))\n      (&body))))\n\n(test simple-populate\n  (with-fixture state ()\n    (populate-company company)))\n\n(test ensure-report-channel-is-populated\n  (with-fixture state ()\n    (populate-company company)\n    (let ((reports (bknr.datastore:class-instances 'report)))\n      (assert-that (mapcar #'report-channel reports)\n                   (contains\n                    (has-typep 'channel))))))\n"
  },
  {
    "path": "src/screenshotbot/login/test-signup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-signup\n  (:use :cl)\n  (:import-from #:auth/model/invite\n                #:invite)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:it.bese.fiveam\n                #:is-false\n                #:is-true\n                #:def-fixture\n                #:finishes\n                #:pass\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/login/common\n                #:signup-get\n                #:standard-auth-provider)\n  (:import-from #:screenshotbot/login/github-oauth\n                #:github-oauth-provider)\n  (:import-from #:screenshotbot/login/signup\n                #:allowed-domain-p\n                #:signup-after-email/get\n                #:valid-email-address-p\n                #:confirmation-success\n                #:prepare-and-send-email-confirmation\n                #:process-existing-invites\n                #:render-signup-confirmation\n                #:signup-post)\n  (:import-from #:screenshotbot/model/company\n                #:company\n                #:get-singleton-company\n                #:prepare-singleton-company)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/testing\n                #:screenshot-test\n                #:with-installation)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:screenshot-static-page\n                #:with-fake-request))\n(in-package :screenshotbot/login/test-signup)\n\n(util/fiveam:def-suite)\n\n;; NOTE TO FUTURE SELF: This is a badly written test. This note was\n;; added later. This test doesn't run very well in the repl,\n;; AFAICT. Works fine on `make test-lw`\n(test happy-path\n  (with-test-store ()\n    (with-fake-request (:host \"localhost:80\")\n      (let ((auth::*iterations* 10))\n       (let ((*installation* (make-instance 'installation)))\n         (prepare-singleton-company)\n         (catch 'hunchentoot::handler-done\n           (let* ((company (get-singleton-company *installation*))\n                  (auth-provider (make-instance 'standard-auth-provider)))\n             (unwind-protect\n                  (auth:with-sessions ()\n                    (let ((ret (signup-post auth-provider\n                                            :email \"arnold@tdrhq.com\"\n                                            :password \"foobar23\"\n                                            :full-name \"Arnold Noronha\"\n                                            :accept-terms-p t\n                                            :plan :professional)))\n                      (error \"should not get here: ~s\" ret)))\n               (bknr.datastore:delete-object company))))))\n      (pass))))\n\n(defclass multi-org-install (multi-org-feature\n                             installation)\n  ())\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((company (make-instance 'company)))\n      (with-installation (:installation (make-instance 'multi-org-install))\n        (&body)))))\n\n(def-fixture screenshots (&key (providers (list (make-instance 'standard-auth-provider))))\n  (let ((*installation* (make-instance 'installation\n                                       :auth-providers providers)))\n    (with-fake-request ()\n      (auth:with-sessions ()\n       (&body)))))\n\n(test screenshot-test\n  (with-fixture screenshots ()\n    (screenshot-static-page\n     :screenshotbot\n     \"signup-without-oauth\"\n     (markup:write-html\n      (signup-get)))))\n\n(test screenshot-with-oauth\n  (with-fixture screenshots (:providers\n                             (list\n                              (make-instance 'standard-auth-provider)\n                              (make-instance 'github-oauth-provider\n                                             :client-id \"foo\"\n                                             :client-secret \"bar\")))\n    (screenshot-static-page\n     :screenshotbot\n     \"signup\"\n     (markup:write-html\n      (signup-get)))))\n\n(test screenshot-with-oauth-and-no-login\n  (with-fixture screenshots (:providers\n                             (list\n                              (make-instance 'github-oauth-provider\n                                             :client-id \"foo\"\n                                             :client-secret \"bar\")))\n    (screenshot-static-page\n     :screenshotbot\n     \"signup-with-oauth-and-no-login\"\n     (markup:write-html\n      (signup-get)))))\n\n(test signup-error-screen\n  (with-fixture screenshots ()\n    (screenshot-static-page\n     :screenshotbot\n     \"signup-error-screen\"\n     (markup:write-html\n      (with-form-errors (:errors `((:email . \"Invalid email\"))\n                         :email \"blah@gmail.com\"\n                         :was-validated t)\n        (signup-get))))))\n\n(screenshot-test signup-user-info-page\n  (with-fixture screenshots ()\n    (signup-after-email/get\n     (make-instance 'standard-auth-provider)\n     :email \"foo@example.com\"\n     :redirect \"/\")))\n\n(screenshot-test signup-confirmation-email\n  (with-fixture screenshots ()\n    (render-signup-confirmation\n     \"Arnold\"\n     \"dfdsfs23rsfdsf\"\n     :confirmation-link \"https://example.com\")))\n\n(screenshot-test email-confirmation-complete\n  (with-fixture screenshots ()\n    (confirmation-success)))\n\n(test process-existing-invites\n  (with-fixture state ()\n    (let ((invite (make-instance 'invite :email \"foo@example.com\"\n                                 :company company)))\n      (make-instance 'invite :email \"bar@example.com\"\n                     :company company)\n      (let ((user (make-user :email \"foo@example.com\")))\n        (process-existing-invites\n         user \"foo@example.com\")\n        (assert-that\n         (auth:unaccepted-invites user)\n         (contains invite))))))\n\n(test process-existing-invites-when-signing-up-with-a-different-email\n  (with-fixture state ()\n    (let ((invite (make-instance 'invite :email \"foo@example.com\"\n                                         :company (make-instance 'company)))\n          (invite-2 (make-instance 'invite :email \"bar@example.com\"\n                                           :company company)))\n      (let ((user (make-user :email \"foo@example.com\")))\n        (process-existing-invites\n         user \"foo@example.com\"\n         :current-invite invite-2)\n        (assert-that\n         (auth:unaccepted-invites user)\n         (contains invite-2\n                   invite))))))\n\n(test prepare-and-send-email-confirmation\n  (with-fixture state ()\n    (let ((user (make-user :email \"foo@example.com\"\n                           :full-name \"Arnold Noronha\")))\n      (with-fake-request ()\n       (finishes\n         (prepare-and-send-email-confirmation user))))))\n\n(test valid-email-address-p\n  (is-true (valid-email-address-p \"arnold@example.com\"))\n  (is-false (valid-email-address-p \"foo@foo@example.com\"))\n  (is-true (valid-email-address-p \"reuxxx.norxxxx@proximite.group\")))\n\n(test only-allows-specified-domains\n  (is-true (allowed-domain-p\n            (make-instance 'standard-auth-provider\n                           :allowed-domains :all)\n            \"foo@example.com\"))\n  (is-true (allowed-domain-p\n            (make-instance 'standard-auth-provider\n                           :allowed-domains (list \"example.com\"))\n            \"foo@example.com\"))\n  (is-false (allowed-domain-p\n            (make-instance 'standard-auth-provider\n                           :allowed-domains (list \"example.com\"))\n            \"foo@bar.com\"))\n  (is-false (allowed-domain-p\n             (make-instance 'standard-auth-provider\n                            :allowed-domains (list \"example.com\" \"car.com\"))\n             \"foo@bar.com\"))\n  (is-true (allowed-domain-p\n             (make-instance 'standard-auth-provider\n                            :allowed-domains (list \"example.com\" \"car.com\"))\n             \"foo@car.com\"))\n  (is-true (allowed-domain-p\n            (make-instance 'standard-auth-provider\n                           :allowed-domains (list \"example.com\" \"car.com\"))\n            \"foo@car.com\")))\n"
  },
  {
    "path": "src/screenshotbot/login/test-verify-email.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/login/test-verify-email\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:auth/login/verify-email\n                #:state\n                #:enter-code-screen)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test))\n(in-package :screenshotbot/login/test-verify-email)\n\n(util/fiveam:def-suite)\n\n(screenshot-test verify-email-enter-code-screen\n  (with-installation ()\n   (enter-code-screen\n    (make-instance 'state\n                   :code 123345\n                   :email \"foo@example.com\"))))\n"
  },
  {
    "path": "src/screenshotbot/magick/build.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/build\n  (:use :cl\n   :asdf)\n  (:export #:lib-source-file\n           #:magick-cl-source-file))\n(in-package :screenshotbot/magick/build)\n\n(defclass lib-source-file (c-source-file)\n  ())\n\n(defclass magick-cl-source-file (cl-source-file)\n  ())\n\n(defun default-foreign-library-type ()\n  \"Returns string naming default library type for platform\"\n  #+(or win32 win64 cygwin windows) \"dll\"\n  #+(or macosx darwin ccl-5.0) \"dylib\"\n  #-(or win32 win64 cygwin windows macosx darwin ccl-5.0) \"so\"\n)\n\n(defmethod output-files ((o compile-op) (c lib-source-file))\n  (let ((library-file-type\n          (default-foreign-library-type)))\n    (list (make-pathname :type library-file-type\n                         :name (format nil \"~a~a\"\n                                       (asdf:component-name c)\n                                       (magick-lib-suffix))\n                         :defaults (asdf:component-pathname c)))))\n\n(defmethod perform ((o load-op) (c lib-source-file))\n  t)\n\n(defun guess-mac-magick-location ()\n  (let ((dir \"/opt/homebrew/Cellar/imagemagick/\"))\n    (loop for child in (uiop:subdirectories dir)\n          for name = (car (last (pathname-directory child)))\n          if (eql #\\7 (elt name 0))\n            return child)))\n\n(defun windows-path ()\n   (fad:pathname-directory-pathname\n    (car (directory #P\"C:/Program Files/ImageMagick-*-Q8/configure.xml\"))))\n\n(defun arch ()\n  (or\n   #+arm64 ;; typical Mac M1 Docker\n   \"aarch64\"\n   #+x86-64\n   \"x86_64\"\n   ;; We could just always use * here as a wildcard.\n   \"*\"))\n\n(defun %path ()\n  (destructuring-bind (name sep dir-sep)\n      (if (uiop:os-windows-p)\n          (list \"Path\" \";\" \"\\\\\")\n          (list \"PATH\" \":\" \"/\"))\n   (let* ((parts\n            (str:split\n             sep\n             (uiop:getenv name))))\n     (append\n      (loop for part in parts\n            if (str:ends-with-p\n                dir-sep part)\n              collect part\n            else\n              collect (format nil \"~a~a\" part dir-sep))\n      (list\n       ;; Mac Homebrew default location\n       \"/opt/homebrew/bin/\"\n       ;; By default, this isn't set up in the Path on Mac, but this is where a\n       ;; default install from source of ImageMagick goes\n       \"/usr/local/bin/\")\n      (directory\n       ;; Debian 11 default location. This returns the file itself,\n       ;; but that's okay. The only caller of this will replace the\n       ;; :name part of the pathname.\n       (format nil\n               \"/usr/lib/~a-linux-gnu/ImageMagick-*/bin-q*/MagickWand-config\"\n               (arch)))))))\n\n(defun guess-include-dir ()\n  (path:catdir (windows-path) \"include/\"))\n\n(defun magick-lib-suffix ()\n  #+windows\n  \".Q8\"\n  ;; I don't want to pull in cl-ppcre just for this...\n  #-windows\n  (let* ((haystack (uiop:run-program `(\"pkg-config\"\n                                       \"--libs\"\n                                       \"MagickWand\")\n                                     :output 'string))\n         (needle \"lMagickWand\")\n         (pos\n           (search\n            needle\n            haystack)))\n\n    (unless pos\n      (error \"Could not find lMagickWand in the output of MagickWand-config --libs: ~a\" haystack))\n    (let* ((str (subseq haystack (+ pos (length needle))))\n           (end (search \" \" str)))\n      (subseq str 0 end))))\n\n(defun %string-trim (str)\n  (String-trim '(#\\Space #\\Newline)\n               str))\n\n(defun magick-wand-config (arg)\n  (%string-trim\n   (uiop:run-program `(\"pkg-config\"\n                       ,arg\n                       \"MagickWand\")\n                     :output 'string)))\n\n(defun init-features ()\n  \"If you're using these features, make sure that your files are compiled\n with the right suffix, so that they can coexist in the same\n repository build output. (see the example of magick-lw.lisp below).\"\n  #+windows\n  (pushnew :magick-7 *features*)\n  #-windows\n  (let ((version (%string-trim (uiop:run-program `(\"pkg-config\"\n                                                   \"--modversion\"\n                                                   \"MagickWand\")\n                                                 :output 'string))))\n    (flet ((add-feature (add remove)\n             (setf *features* (remove remove *features*))\n             (pushnew add *features*)))\n     (ecase (elt version 0)\n       (#\\6\n        (add-feature :magick-6 :magick-7))\n       (#\\7\n        (add-feature :magick-7 :magick-6))))))\n\n(init-features)\n\n(defmethod perform ((o compile-op) (c lib-source-file))\n  (uiop:run-program\n   (format nil\"gcc -std=c99 -fPIC -shared ~a ~a -I -Werror -O2 -Wall ~a -o ~a\"\n\n           (uiop:escape-shell-command (namestring\n                                       (component-pathname c)))\n           #-windows\n           (magick-wand-config \"--cflags\")\n           #-windows\n           (magick-wand-config \"--libs\")\n           #+windows\n           (format nil \"-DQUANTUM_DEPTH=8 -DHDRI=0 -I~a\" (uiop:escape-shell-token (namestring (guess-include-dir))))\n           #+windows\n           (format nil \" -lCORE_RL_MagickWand_ -lCORE_RL_MagickCore_ -L~a\" (uiop:escape-shell-token (namestring (windows-path))))\n           (namestring (car (output-files o c))))\n   :output *standard-output*\n   :error-output *error-output*))\n\n(defmethod output-files ((o compile-op) (c magick-cl-source-file))\n  (let ((old (car (call-next-method))))\n    (list\n     (make-pathname\n      :name (format nil \"~a~a\"\n                    (component-name c)\n                    (magick-lib-suffix))\n      :defaults old))))\n"
  },
  {
    "path": "src/screenshotbot/magick/ffi-6.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/ffi-6\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)\n                      #-lispworks\n                      (#-lispworks #:fli #:util/fake-fli)                    )\n  (:export\n   #:alpha-channel-option\n   #:exception-type\n   #:composite-operator\n   #:resource-type\n   #:AreaResource\n   #:DiskResource\n   #:FileResource\n   #:HeightResource\n   #:MapResource\n   #:MemoryResource\n   #:ThreadResource\n   #:ThrottleResource\n   #:TimeResource\n   #:WidthResource\n   #:ListLengthResource\n   #:OnAlphaChannel\n   #:SrcCompositeOp\n   #:SetAlphaChannel\n   #:ResourceLimitWarning\n   #:UndefinedException\n   #:RootMeanSquaredErrorMetric\n   #:metric-type))\n(in-package :screenshotbot/magick/ffi-6)\n\n(fli:define-c-enum resource-type\n  UndefinedResource\n  AreaResource\n  DiskResource\n  FileResource\n  MapResource\n  MemoryResource\n  ThreadResource\n  TimeResource\n  ThrottleResource\n  WidthResource\n  HeightResource\n  ListLengthResource)\n\n(fli:define-c-enum composite-operator\n  UndefinedCompositeOp\n  NoCompositeOp\n  ModulusAddCompositeOp\n  AtopCompositeOp\n  BlendCompositeOp\n  BumpmapCompositeOp\n  ChangeMaskCompositeOp\n  ClearCompositeOp\n  ColorBurnCompositeOp\n  ColorDodgeCompositeOp\n  ColorizeCompositeOp\n  CopyBlackCompositeOp\n  CopyBlueCompositeOp\n  CopyCompositeOp\n  CopyCyanCompositeOp\n  CopyGreenCompositeOp\n  CopyMagentaCompositeOp\n  CopyOpacityCompositeOp\n  CopyRedCompositeOp\n  CopyYellowCompositeOp\n  DarkenCompositeOp\n  DstAtopCompositeOp\n  DstCompositeOp\n  DstInCompositeOp\n  DstOutCompositeOp\n  DstOverCompositeOp\n  DifferenceCompositeOp\n  DisplaceCompositeOp\n  DissolveCompositeOp\n  ExclusionCompositeOp\n  HardLightCompositeOp\n  HueCompositeOp\n  InCompositeOp\n  LightenCompositeOp\n  LinearLightCompositeOp\n  LuminizeCompositeOp\n  MinusDstCompositeOp\n  ModulateCompositeOp\n  MultiplyCompositeOp\n  OutCompositeOp\n  OverCompositeOp\n  OverlayCompositeOp\n  PlusCompositeOp\n  ReplaceCompositeOp\n  SaturateCompositeOp\n  ScreenCompositeOp\n  SoftLightCompositeOp\n  SrcAtopCompositeOp\n  SrcCompositeOp\n  SrcInCompositeOp\n  SrcOutCompositeOp\n  SrcOverCompositeOp\n  ModulusSubtractCompositeOp\n  ThresholdCompositeOp\n  XorCompositeOp\n  DivideDstCompositeOp\n  DistortCompositeOp\n  BlurCompositeOp\n  PegtopLightCompositeOp\n  VividLightCompositeOp\n  PinLightCompositeOp\n  LinearDodgeCompositeOp\n  LinearBurnCompositeOp\n  MathematicsCompositeOp\n  DivideSrcCompositeOp\n  MinusSrcCompositeOp\n  DarkenIntensityCompositeOp\n  LightenIntensityCompositeOp\n  HardMixCompositeOp\n  StereoCompositeOp)\n\n(fli:define-c-enum exception-type\n    UndefinedException\n  (  ResourceLimitWarning 300)\n  (  WarningException 300)\n  (  TypeWarning 305)\n  (  OptionWarning 310)\n  (  DelegateWarning 315)\n  (  MissingDelegateWarning 320)\n  (  CorruptImageWarning 325)\n  (  FileOpenWarning 330)\n  (  BlobWarning 335)\n  (  StreamWarning 340)\n  (  CacheWarning 345)\n  (  CoderWarning 350)\n  (  FilterWarning 352)\n  (  ModuleWarning 355)\n  (  DrawWarning 360)\n  (  ImageWarning 365)\n  (  WandWarning 370)\n  (  RandomWarning 375)\n  (  XServerWarning 380)\n  (  MonitorWarning 385)\n  (  RegistryWarning 390)\n  (  ConfigureWarning 395)\n  (  PolicyWarning 399)\n  (  ErrorException 400)\n  (  ResourceLimitError 400)\n  (  TypeError 405)\n  (  OptionError 410)\n  (  DelegateError 415)\n  (  MissingDelegateError 420)\n  (  CorruptImageError 425)\n  (  FileOpenError 430)\n  (  BlobError 435)\n  (  StreamError 440)\n  (  CacheError 445)\n  (  CoderError 450)\n  (  FilterError 452)\n  (  ModuleError 455)\n  (  DrawError 460)\n  (  ImageError 465)\n  (  WandError 470)\n  (  RandomError 475)\n  (  XServerError 480)\n  (  MonitorError 485)\n  (  RegistryError 490)\n  (  ConfigureError 495)\n  (  PolicyError 499)\n  (  FatalErrorException 700)\n  (  ResourceLimitFatalError 700)\n  (  TypeFatalError 705)\n  (  OptionFatalError 710)\n  (  DelegateFatalError 715)\n  (  MissingDelegateFatalError 720)\n  (  CorruptImageFatalError 725)\n  (  FileOpenFatalError 730)\n  (  BlobFatalError 735)\n  (  StreamFatalError 740)\n  (  CacheFatalError 745)\n  (  CoderFatalError 750)\n  (  FilterFatalError 752)\n  (  ModuleFatalError 755)\n  (  DrawFatalError 760)\n  (  ImageFatalError 765)\n  (  WandFatalError 770)\n  (  RandomFatalError 775)\n  (  XServerFatalError 780)\n  (  MonitorFatalError 785)\n  (  RegistryFatalError 790)\n  (  ConfigureFatalError 795)\n  (  PolicyFatalError 799 ))\n\n(fli:define-c-enum alpha-channel-option\n  UndefinedAlphaChannel\n  ActivateAlphaChannel\n  BackgroundAlphaChannel\n  CopyAlphaChannel\n  DeactivateAlphaChannel\n  ExtractAlphaChannel\n  OpaqueAlphaChannel\n  ResetAlphaChannel\n  SetAlphaChannel\n  ShapeAlphaChannel\n  TransparentAlphaChannel\n  FlattenAlphaChannel\n  RemoveAlphaChannel\n  AssociateAlphaChannel\n  DisassociateAlphaChannel)\n\n(fli:define-c-enum metric-type\n  UndefinedMetric\n  AbsoluteErrorMetric\n  MeanAbsoluteErrorMetric\n  MeanErrorPerPixelMetric\n  MeanSquaredErrorMetric\n  PeakAbsoluteErrorMetric\n  PeakSignalToNoiseRatioMetric\n  RootMeanSquaredErrorMetric\n  NormalizedCrossCorrelationErrorMetric\n  FuzzErrorMetric\n  (UndefinedErrorMetric 0)\n  (PerceptualHashErrorMetric #xff))\n"
  },
  {
    "path": "src/screenshotbot/magick/ffi-7.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/ffi-7\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)\n                      #-lispworks\n                      (#-lispworks #:fli #:util/fake-fli)                    )\n  (:export\n   #:alpha-channel-option\n   #:exception-type\n   #:composite-operator\n   #:resource-type\n   #:AreaResource\n   #:DiskResource\n   #:FileResource\n   #:HeightResource\n   #:MapResource\n   #:MemoryResource\n   #:ThreadResource\n   #:ThrottleResource\n   #:TimeResource\n   #:WidthResource\n   #:ListLengthResource\n   #:OnAlphaChannel\n   #:SrcCompositeOp\n   #:SetAlphaChannel\n   #:MeanSquaredErrorMetric\n   #:RootMeanSquaredErrorMetric\n   #:metric-type\n   #:UndefinedException))\n(in-package :screenshotbot/magick/ffi-7)\n\n(fli:define-c-enum resource-type\n  UndefinedResource\n  AreaResource\n  DiskResource\n  FileResource\n  HeightResource\n  MapResource\n  MemoryResource\n  ThreadResource\n  ThrottleResource\n  TimeResource\n  WidthResource\n  ListLengthResource)\n\n(fli:define-c-enum composite-operator\n    UndefinedCompositeOp\n  AlphaCompositeOp\n  AtopCompositeOp\n  BlendCompositeOp\n  BlurCompositeOp\n  BumpmapCompositeOp\n  ChangeMaskCompositeOp\n  ClearCompositeOp\n  ColorBurnCompositeOp\n  ColorDodgeCompositeOp\n  ColorizeCompositeOp\n  CopyBlackCompositeOp\n  CopyBlueCompositeOp\n  CopyCompositeOp\n  CopyCyanCompositeOp\n  CopyGreenCompositeOp\n  CopyMagentaCompositeOp\n  CopyAlphaCompositeOp\n  CopyRedCompositeOp\n  CopyYellowCompositeOp\n  DarkenCompositeOp\n  DarkenIntensityCompositeOp\n  DifferenceCompositeOp\n  DisplaceCompositeOp\n  DissolveCompositeOp\n  DistortCompositeOp\n  DivideDstCompositeOp\n  DivideSrcCompositeOp\n  DstAtopCompositeOp\n  DstCompositeOp\n  DstInCompositeOp\n  DstOutCompositeOp\n  DstOverCompositeOp\n  ExclusionCompositeOp\n  HardLightCompositeOp\n  HardMixCompositeOp\n  HueCompositeOp\n  InCompositeOp\n  IntensityCompositeOp\n  LightenCompositeOp\n  LightenIntensityCompositeOp\n  LinearBurnCompositeOp\n  LinearDodgeCompositeOp\n  LinearLightCompositeOp\n  LuminizeCompositeOp\n  MathematicsCompositeOp\n  MinusDstCompositeOp\n  MinusSrcCompositeOp\n  ModulateCompositeOp\n  ModulusAddCompositeOp\n  ModulusSubtractCompositeOp\n  MultiplyCompositeOp\n  NoCompositeOp\n  OutCompositeOp\n  OverCompositeOp\n  OverlayCompositeOp\n  PegtopLightCompositeOp\n  PinLightCompositeOp\n  PlusCompositeOp\n  ReplaceCompositeOp\n  SaturateCompositeOp\n  ScreenCompositeOp\n  SoftLightCompositeOp\n  SrcAtopCompositeOp\n  SrcCompositeOp\n  SrcInCompositeOp\n  SrcOutCompositeOp\n  SrcOverCompositeOp\n  ThresholdCompositeOp\n  VividLightCompositeOp\n  XorCompositeOp\n  StereoCompositeOp\n  FreezeCompositeOp\n  InterpolateCompositeOp\n  NegateCompositeOp\n  ReflectCompositeOp\n  SoftBurnCompositeOp\n  SoftDodgeCompositeOp\n  StampCompositeOp\n  RMSECompositeOp\n  SaliencyBlendCompositeOp\n  SeamlessBlendCompositeOp)\n\n(fli:define-c-enum exception-type\n    UndefinedException\n  (  ResourceLimitWarning 300)\n  (  WarningException 300)\n  (  TypeWarning 305)\n  (  OptionWarning 310)\n  (  DelegateWarning 315)\n  (  MissingDelegateWarning 320)\n  (  CorruptImageWarning 325)\n  (  FileOpenWarning 330)\n  (  BlobWarning 335)\n  (  StreamWarning 340)\n  (  CacheWarning 345)\n  (  CoderWarning 350)\n  (  FilterWarning 352)\n  (  ModuleWarning 355)\n  (  DrawWarning 360)\n  (  ImageWarning 365)\n  (  WandWarning 370)\n  (  RandomWarning 375)\n  (  XServerWarning 380)\n  (  MonitorWarning 385)\n  (  RegistryWarning 390)\n  (  ConfigureWarning 395)\n  (  PolicyWarning 399)\n  (  ErrorException 400)\n  (  ResourceLimitError 400)\n  (  TypeError 405)\n  (  OptionError 410)\n  (  DelegateError 415)\n  (  MissingDelegateError 420)\n  (  CorruptImageError 425)\n  (  FileOpenError 430)\n  (  BlobError 435)\n  (  StreamError 440)\n  (  CacheError 445)\n  (  CoderError 450)\n  (  FilterError 452)\n  (  ModuleError 455)\n  (  DrawError 460)\n  (  ImageError 465)\n  (  WandError 470)\n  (  RandomError 475)\n  (  XServerError 480)\n  (  MonitorError 485)\n  (  RegistryError 490)\n  (  ConfigureError 495)\n  (  PolicyError 499)\n  (  FatalErrorException 700)\n  (  ResourceLimitFatalError 700)\n  (  TypeFatalError 705)\n  (  OptionFatalError 710)\n  (  DelegateFatalError 715)\n  (  MissingDelegateFatalError 720)\n  (  CorruptImageFatalError 725)\n  (  FileOpenFatalError 730)\n  (  BlobFatalError 735)\n  (  StreamFatalError 740)\n  (  CacheFatalError 745)\n  (  CoderFatalError 750)\n  (  FilterFatalError 752)\n  (  ModuleFatalError 755)\n  (  DrawFatalError 760)\n  (  ImageFatalError 765)\n  (  WandFatalError 770)\n  (  RandomFatalError 775)\n  (  XServerFatalError 780)\n  (  MonitorFatalError 785)\n  (  RegistryFatalError 790)\n  (  ConfigureFatalError 795)\n  (  PolicyFatalError 799 ))\n\n(fli:define-c-enum alpha-channel-option\n    UndefinedAlphaChannel\n  ActivateAlphaChannel\n  AssociateAlphaChannel\n  BackgroundAlphaChannel\n  CopyAlphaChannel\n  DeactivateAlphaChannel\n  DiscreteAlphaChannel\n  DisassociateAlphaChannel\n  ExtractAlphaChannel\n  OffAlphaChannel\n  OnAlphaChannel\n  OpaqueAlphaChannel\n  RemoveAlphaChannel\n  SetAlphaChannel\n  ShapeAlphaChannel\n  TransparentAlphaChannel)\n\n(fli:define-c-enum metric-type\n    UndefinedErrorMetric\n  AbsoluteErrorMetric\n  FuzzErrorMetric\n  MeanAbsoluteErrorMetric\n  MeanErrorPerPixelErrorMetric\n  MeanSquaredErrorMetric\n  NormalizedCrossCorrelationErrorMetric\n  PeakAbsoluteErrorMetric\n  PeakSignalToNoiseRatioErrorMetric\n  PerceptualHashErrorMetric\n  RootMeanSquaredErrorMetric\n  StructuralSimilarityErrorMetric\n  StructuralDissimilarityErrorMetric)\n"
  },
  {
    "path": "src/screenshotbot/magick/health-checks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/health-checks\n  (:use #:cl)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:screenshotbot/magick/magick\n                #:*magick*)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:ping-image-metadata\n                #:save-wand-to-file\n                #:map-non-alpha-pixels\n                #:magick-write-image\n                #:with-wand)\n  (:import-from #:easy-macros\n                #:def-easy-macro))\n(in-package :screenshotbot/magick/health-checks)\n\n(defun read-file-sequence (file)\n  (with-open-file (stream file :element-type '(unsigned-byte 8))\n    (let ((res (make-array (file-length stream)\n                           :element-type '(unsigned-byte 8))))\n      (read-sequence res stream)\n      res)))\n\n(defvar *rose* (read-file-sequence (asdf:system-relative-pathname\n                                    :screenshotbot \"fixture/rose.webp\")))\n\n(defvar *heic* (read-file-sequence (asdf:system-relative-pathname\n                                    :screenshotbot \"fixture/rose.heic\")))\n\n(def-easy-macro with-rose-wand (&binding wand &key (content *rose*) &fn fn)\n  (uiop:with-temporary-file (:stream webp-s :pathname webp :type \"webp\"\n                              :element-type '(unsigned-byte 8))\n    (write-sequence content webp-s)\n    (finish-output webp-s)\n    (with-wand (wand :file webp)\n      (fn wand))))\n\n(def-health-check load-webp-and-save-png ()\n  (handler-bind ((error (lambda (e)\n                          #+lispworks\n                          (dbg:output-backtrace :brief))))\n    (with-rose-wand (wand)\n      (uiop:with-temporary-file (:pathname png :type \"png\")\n        (save-wand-to-file wand (namestring png))\n        (assert-equal \"PNG\" (%image-format png))))))\n\n#-screenshotbot-oss\n(def-health-check load-heic-and-save-png ()\n  (handler-bind ((error (lambda (e)\n                          #+lispworks\n                          (dbg:output-backtrace :brief))))\n    (with-rose-wand (wand :content *heic*)\n      (uiop:with-temporary-file (:pathname png :type \"png\")\n        (save-wand-to-file wand (namestring png))\n        (assert-equal \"PNG\" (%image-format png))))))\n\n(defun %image-format (file)\n  (third (ping-image-metadata *magick* file)))\n\n(defun assert-equal (one two)\n  (unless (equal one two)\n    (error \"failed: ~s is not equal to ~s\"\n           one two)))\n\n#-screenshotbot-oss\n(def-health-check load-webp-and-save-heic ()\n  (handler-bind ((error (lambda (e)\n                          #+lispworks\n                          (dbg:output-backtrace :brief))))\n    (with-rose-wand (wand)\n      (uiop:with-temporary-file (:pathname png :type \"heic\")\n        (save-wand-to-file wand (namestring png))\n        (assert-equal \"HEIC\" (%image-format png))))))\n\n#-screenshotbot-oss\n(def-health-check load-webp-and-save-jxl ()\n  (handler-bind ((error (lambda (e)\n                          (format t \"error: ~a~%\" e)\n                          #+lispworks\n                          (dbg:output-backtrace :brief))))\n    (with-rose-wand (wand)\n      (uiop:with-temporary-file (:pathname png :type \"jxl\")\n        (save-wand-to-file wand (namestring png))\n        (assert-equal \"JXL\" (%image-format png))))))\n\n(def-health-check map-non-alpha-pixels ()\n  (with-rose-wand (wand)\n    (map-non-alpha-pixels wand (lambda (x y)\n                                 (declare (ignore x y))))))\n"
  },
  {
    "path": "src/screenshotbot/magick/magick-lw.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/magick-lw\n  (:use #:cl\n        #:screenshotbot/magick/magick\n        #+magick-7\n        #:screenshotbot/magick/ffi-7\n        #+magick-6\n        #:screenshotbot/magick/ffi-6\n        #:screenshotbot/mask-rect-api)\n  (:import-from #:screenshotbot/magick\n                #:with-magick-gatekeeper\n                #:*magick*\n                #:abstract-magick)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/magick/build\n                #:magick-lib-suffix)\n  (:import-from #:util/copy-file\n                #:copy-file-fast)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:util/native-module\n                #:make-system-module\n                #:embed-module\n                #:load-module\n                #:make-native-module)\n  (:local-nicknames (#:a #:alexandria)\n                    #-lispworks\n                    (#-lispworks #:fli #:util/fake-fli))\n  (:export\n   #:compare-images\n   #:compare-image-files\n   #:magick-exception\n   #:magick-exception-message\n   #:map-non-alpha-pixels\n   #:get-non-alpha-pixels\n   #:ping-image-metadata\n   #:with-image-comparison\n   #:save-as-webp\n   #:with-pixel-wand\n   #:screenshotbot-set-pixel\n   #:with-pixel\n   #:embed-magick-native\n   #:with-drawing-wand\n   #:magick-draw-image\n   #:get-px-as-string))\n(in-package :screenshotbot/magick/magick-lw)\n\n(defclass magick-native (abstract-magick)\n  ())\n\n(fli:define-c-typedef magick-size-type :uint64)\n(fli:define-c-typedef quantum :uint8)\n\n(defvar *windows-magick-dir*\n  #P\"c:/Program Files/ImageMagick-7.1.0-Q8/\")\n\n(defun win-magick-lib (name)\n  (let ((ret (namestring (pathname (path:catfile *windows-magick-dir* (format nil \"CORE_RL_~a_.dll\" name))))))\n    (assert (path:-e ret))\n    ret))\n\n\n(defvar *path-setp* nil)\n\n(defun magick-so (name)\n  (let ((suffix (magick-lib-suffix)))\n   (lambda ()\n     (cond\n       ((uiop:os-windows-p)\n        ;; TODO: if needed we can discover this from the registry, see the\n        ;; python wand code that does the same\n        (win-magick-lib name))\n       (t (format nil \"lib~a~a.so\" name suffix))))))\n\n(defvar *magick-wand*\n  (make-system-module :magick-wand\n                      :pathname-flag :file-name\n                      :pathname-provider\n                      (magick-so \"MagickWand\")))\n\n(defvar *magick-core*\n  (make-system-module :magick-core\n                      :pathname-flag :file-name\n                      :pathname-provider\n                      (magick-so \"MagickCore\")))\n\n(defvar *libwebp* (make-system-module :libwebp\n                                      :file-name \"libwebp.so\"))\n(defvar *libwebpmux* (make-system-module :libwebpmux\n                                         :file-name \"libwebpmux.so\"))\n(defvar *libwebpdemux* (make-system-module :libwebpdemux\n                                         :file-name \"libwebpdemux.so\"))\n\n(defvar *libpng* (make-system-module :libpng\n                                     :file-name \"libpng.so\"))\n\n(defvar *libgomp* (make-system-module :libgomp\n                                      :file-name \"libgomp.so.1\"))\n\n(defparameter *libs*\n  ;; the order here matter!\n  (list\n   *libwebp*\n   *libpng*\n   *libwebpmux*\n   *libwebpdemux*\n   #+linux\n   *libgomp*))\n\n#+lispworks\n(unless (hcl:delivered-image-p)\n (lw:define-action \"Delivery actions\" \"Embed magick libraries\"\n   (lambda ()\n     (mapc #'embed-module *libs*)\n     (embed-module *magick-wand*)\n     (embed-module *magick-core*)\n     (embed-module *magick-native*))))\n\n(defun register-magick-wand ()\n  (when (uiop:os-windows-p)\n    (unless *path-setp*\n      (setf (uiop:getenv \"Path\") (format nil \"~a;~a\" (namestring *windows-magick-dir*) (uiop:getenv \"Path\")))\n      (setf *path-setp* t)))\n\n  #+lispworks\n  (mapc #'load-module *libs*)\n\n  (load-module *magick-core*)\n  (load-module *magick-wand*)\n  (load-magick-native))\n\n(defun verify-magick ()\n  (let ((ret (screenshotbot-verify-magick\n              'SrcCompositeOp\n              'SetAlphaChannel)))\n    (unless (= ret 1)\n      (error \"The ImageMagick runtime does not match the configuration options that\n Screenshotbot was compiled against. This might happen if you\n recompiled or reinstalled ImageMagick, or switched the compiled\n assets to a different machine. (Got result: ~a)\" ret))))\n\n\n(defvar *magick-native* (make-native-module\n                         :magick-native\n                         :screenshotbot.magick\n                         \"magick-native\"\n                         :verify #'verify-magick))\n\n(defun load-magick-native (&key force)\n  #-linux\n  (when force\n    ;; For some reason reloading magick-native will cause segfaults on\n    ;; mac tests.\n    (warn \"Can't reload magick-native on Mac\"))\n  (load-module *magick-native* :force force))\n\n;; (load-magick-native :force t)\n\n(fli:define-c-struct wand\n    (dummy :int))\n\n(fli:define-c-struct pixel-wand\n    (dummy :int))\n\n(fli:define-c-struct drawing-wand\n    (dummy :int))\n\n(fli:define-c-struct pixel-iterator\n  (dummy :int))\n\n(fli:define-foreign-function (magick-wand-genesis \"MagickWandGenesis\")\n    ()\n  :result-type :void)\n\n(fli:define-foreign-function (magick-wand-terminus \"MagickWandTerminus\")\n    ()\n  :result-type :void)\n\n(fli:define-foreign-function (new-magick-wand \"NewMagickWand\")\n    ()\n  :result-type (:pointer wand))\n\n(fli:define-foreign-function (destroy-magick-wand \"DestroyMagickWand\")\n    ((wandp :pointer))\n  :result-type (:pointer wand))\n\n(fli:define-foreign-function (magick-read-image \"MagickReadImage\")\n    ((wand :pointer)\n     (filename (:reference-pass :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-set-size \"MagickSetSize\")\n    ((wand :pointer)\n     (cols :size-t)\n     (rows :size-t))\n  :result-type :boolean\n  :documentation \"Only used for tests\")\n\n(fli:define-foreign-function (magick-crop-image \"MagickCropImage\")\n    ((Wand :pointer)\n     (width :size-t)\n     (height :size-t)\n     (x :size-t)\n     (y :size-t))\n  :result-type :boolean)\n\n(fli:define-foreign-function (new-pixel-iterator \"NewPixelIterator\")\n    ((wand (:pointer wand)))\n  :result-type (:pointer pixel-iterator))\n\n(fli:define-foreign-function (destroy-pixel-iterator \"DestroyPixelIterator\")\n    ((pix (:pointer pixel-iterator)))\n  ;; I think the return value should always be NULL\n  :result-type (:pointer pixel-iterator))\n\n(fli:define-foreign-function (magick-new-image \"MagickNewImage\")\n    ((wand (:pointer wand))\n     (columns :size-t)\n     (rows :size-t)\n     (pixel-wand (:pointer pixel-wand)))\n  :result-type :boolean)\n\n\n(fli:define-foreign-function (new-pixel-wand \"NewPixelWand\")\n    ()\n  :result-type (:pointer pixel-wand))\n\n(fli:define-foreign-function (pixel-set-alpha \"PixelSetAlpha\")\n    ((pwand (:pointer pixel-wand))\n     (alpha :double))\n  :result-type :void)\n\n(fli:define-foreign-function (pixel-set-color \"PixelSetColor\")\n    ((pwand (:pointer pixel-wand))\n     (color (:reference-pass :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (clear-pixel-wand \"ClearPixelWand\")\n    ((pixel-wand (:pointer pixel-wand)))\n  :result-type :void)\n\n(fli:define-foreign-function (magick-ping-image \"MagickPingImage\")\n    ((wand (:pointer wand))\n     (filename (:reference-pass :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-compare-images \"MagickCompareImages\" )\n  ((wand (:pointer wand))\n   (reference-wand (:pointer wand))\n   (metric metric-type)\n   (onutput (:reference-return :double)))\n  :result-type (:pointer wand))\n\n(fli:define-foreign-function (%magick-identify-image \"MagickIdentifyImage\")\n    ((wand (:pointer wand)))\n  :result-type (:pointer :char))\n\n(fli:define-foreign-function (magick-set-option \"MagickSetOption\")\n    ((wand (:pointer wand))\n     (name (:reference :ef-mb-string))\n     (value (:reference :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-set-image-artifact \"MagickSetImageArtifact\")\n    ((wand (:pointer wand))\n     (artifact (:reference :ef-mb-string))\n     (value (:reference :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-write-image \"MagickWriteImage\")\n    ((wand (:pointer wand))\n     (file (:reference :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (draw-annotation \"DrawAnnotation\")\n    ((wand (:pointer drawing-wand))\n     (x :double)\n     (y :double)\n     (text (:reference :ef-mb-string)))\n  :result-type :void)\n\n(fli:define-foreign-function (draw-set-stroke-color \"DrawSetStrokeColor\")\n    ((dwand (:pointer drawing-wand))\n     (px (:pointer pixel-wand)))\n  :result-type :void)\n\n(fli:define-foreign-function (draw-set-fill-color \"DrawSetFillColor\")\n    ((dwand (:pointer drawing-wand))\n     (px (:pointer pixel-wand)))\n  :result-type :void)\n\n(fli:define-foreign-function (draw-set-font \"DrawSetFont\")\n    ((wand (:pointer drawing-wand))\n     (font-name (:reference :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (new-drawing-wand \"NewDrawingWand\")\n    ()\n  :result-type (:pointer drawing-wand))\n\n(fli:define-foreign-function (magick-draw-image \"MagickDrawImage\")\n    ((wand (:pointer wand))\n     (dwand (:pointer drawing-wand)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (destroy-drawing-wand \"DestroyDrawingWand\")\n    ((dwand (:pointer drawing-wand)))\n  ;; The function signature actually returns a pointer :/\n  :result-type (:pointer drawing-wand))\n\n\n(fli:define-foreign-function (magick-clear-exception \"MagickClearException\")\n    ((wand (:pointer wand)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (%get-policy-value \"GetPolicyValue\")\n    ((name (:reference-pass :ef-mb-string)))\n  :result-type (:pointer :char)\n  :documentation \"For debugging only\")\n\n(fli:define-foreign-function (get-max-memory-request \"GetMaxMemoryRequest\")\n    ()\n  :result-type :size-t\n  :documentation \"For debugging only\")\n\n(defun get-policy-value (name)\n  (let ((ret (%get-policy-value name)))\n    (unless (fli:null-pointer-p ret)\n     (unwind-protect\n          (fli:convert-from-foreign-string ret)\n       (magick-relinquish-memory ret)))))\n\n(fli:define-foreign-function (magick-set-image-compose \"MagickSetImageCompose\")\n    ((wand (:pointer wand))\n     (compose composite-operator))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-get-exception \"MagickGetException\")\n    ((wand (:pointer wand))\n     (exception-type (:reference-return exception-type)))\n  :result-type (:pointer :char))\n\n(fli:define-foreign-function (magick-relinquish-memory \"MagickRelinquishMemory\")\n  ((resource (:pointer :void)))\n  :result-type (:pointer :void))\n\n(fli:define-foreign-function (magick-get-resource-limit \"MagickGetResourceLimit\")\n    ((op resource-type))\n  :result-type magick-size-type)\n\n(fli:define-foreign-function (magick-get-resource \"MagickGetResource\")\n    ((op resource-type))\n  :result-type magick-size-type)\n\n(fli:define-foreign-function (magick-set-resource-limit \"MagickSetResourceLimit\")\n    ((op resource-type)\n     (limit magick-size-type))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-get-image-height \"MagickGetImageHeight\")\n    ((wand (:pointer wand)))\n  :result-type :size-t)\n\n(fli:define-foreign-function (magick-get-image-width \"MagickGetImageWidth\")\n    ((wand (:pointer wand)))\n  :result-type :size-t)\n\n(fli:define-foreign-function (magick-get-image-format \"MagickGetImageFormat\")\n    ((wand (:pointer wand)))\n  :result-type (:pointer :char))\n\n(defvar +area-resource+ 1)\n\n(fli:define-foreign-function (magick-strip-image \"MagickStripImage\")\n    ((wand (:pointer wand)))\n    :result-type :boolean)\n\n(fli:define-foreign-function (magick-get-image-alpha-channel \"MagickGetImageAlphaChannel\")\n    ((wand (:pointer wand)))\n    :result-type :boolean)\n\n(fli:define-foreign-function (magick-set-image-alpha-channel \"MagickSetImageAlphaChannel\")\n                             ((wand (:pointer wand))\n                              (alpha-channel-option alpha-channel-option))\n  :result-type :boolean)\n\n(fli:define-foreign-function (magick-get-version \"MagickGetVersion\")\n    ((version (:reference-return #-lispworks :uint64 #+lispworks :size-t)))\n  :result-type (:pointer :char))\n\n(fli:define-foreign-function (magick-crop-image \"MagickCropImage\")\n    ((wand (:pointer wand))\n     (width :size-t)\n     (height :size-t)\n     (x :size-t)\n     (y :size-t))\n  :result-type :boolean)\n\n(defvar *magick-wand-inited* nil)\n\n(defun mb (x)\n  (format nil \"~a\" (* x 1024 1024)))\n\n(defun init-magick-wand ()\n  (unless *magick-wand-inited*\n    (register-magick-wand)\n    (magick-wand-genesis)\n    (update-resource-limits)\n    (setf *magick-wand-inited* t)))\n\n(defun end-magick-wand ()\n  (progn\n    (magick-wand-terminus)\n    (setf *magick-wand-inited* nil)))\n\n(define-condition magick-exception (error)\n  ((expression :initarg :expression)\n   (message :initarg :message\n            :reader magick-exception-message)))\n\n(defmethod print-object ((self magick-exception) out)\n  (with-slots (message) self\n   (format out \"Magick exception: ~a\" message)))\n\n(defmacro check-boolean (x wand)\n  (let ((wand-sym (gensym \"wand\")))\n    `(let ((,wand-sym ,wand))\n       (unless ,x\n         (cond\n           (,wand-sym\n            (raise-magick-exception ,wand-sym ',x))\n           (t\n            (error \"expression failed\")))))))\n\n(defun raise-magick-exception (wand &optional expression)\n  (declare (optimize (debug 3) (speed 0)))\n  (multiple-value-bind (message type)\n      (magick-get-exception wand 'UndefinedException)\n    (declare (ignore type))\n    (magick-clear-exception wand)\n    (unwind-protect\n         (error 'magick-exception\n                 :expression expression\n                 :message (fli:convert-from-foreign-string message))\n      (magick-relinquish-memory message))))\n\n(defun get-disk-resource ()\n  ;; TODO: maybe compute this dynamically?\n  (* 3 1024 1024 1024))\n\n(defun update-resource-limits ()\n  (loop for (name value) in `((AreaResource 3000000) ;; unclear if this being used (T1709)\n                              (DiskResource ,(get-disk-resource))\n                              (WidthResource 20000)\n                              (HeightResource 40000)\n                              (ListLengthResource ,(* 10000 1024  1024))\n                              (MemoryResource ,(* 2000 1024 1024)))\n        do\n           (check-boolean (magick-set-resource-limit name value)\n                          nil)))\n\n(def-easy-macro with-wand (&binding wand &key file from (alpha t) &fn fn)\n  (init-magick-wand)\n  (push-event :magick.with-wand)\n  (let ((wand (or from (new-magick-wand))))\n    (unwind-protect\n         (progn\n             (when file\n               (check-boolean (magick-read-image wand (namestring file)) wand)\n               (when alpha\n                 (set-wand-alpha-channel wand)))\n           (funcall fn wand))\n      (destroy-magick-wand wand))))\n\n\n(fli:define-foreign-function screenshotbot-inplace-compare-v2\n    ((dest (:pointer wand))\n     (src (:pointer wand))\n     (dist :int))\n  :result-type :int)\n\n(defun screenshotbot-inplace-compare (dest src &key (pixel-tolerance 0))\n  (screenshotbot-inplace-compare-v2 dest src pixel-tolerance))\n\n(defun set-wand-alpha-channel (wand)\n  (check-boolean (magick-set-image-alpha-channel wand 'SetAlphaChannel)\n                 wand))\n\n\n(def-easy-macro with-image-comparison (wand1 wand2 &key &binding result\n                                             &binding same-p\n                                             in-place-p\n                                             (highlight-color \"red\")\n                                             (lowlight-color \"none\")\n                                             (pixel-tolerance 0)\n                                             &fn fn)\n  (assert (not (fli:null-pointer-p wand1)))\n  (assert (not (fli:null-pointer-p wand2)))\n\n  (cond\n    (in-place-p\n     (log:info \"Using in-place image comparison\")\n\n     (flet ((inner (wand1 wand2)\n              (set-wand-alpha-channel wand1)\n              (let ((res (screenshotbot-inplace-compare wand1 wand2 :pixel-tolerance pixel-tolerance)))\n                (when (< res 0)\n                  (error \"Error calling inplace-compare: ~a\" res))\n                (funcall fn wand1 (= res 0)))))\n       (cond\n         ((and\n           (<=\n            (magick-get-image-height wand1)\n            (magick-get-image-height wand2))\n           (<=\n            (magick-get-image-width wand1)\n            (magick-get-image-width wand2)))\n          ;; if both the dimensions of wand1 are smaller than wand2, then\n          ;; we swap them. Otherwise we might have a situation where a\n          ;; larger image in both dimensions won't detect a change.\n          (inner wand2 wand1))\n         (t\n          (inner wand1 wand2)))))\n    (t\n     (check-boolean (magick-set-image-artifact wand1 \"compare:lowlight-color\" lowlight-color) wand1)\n     (check-boolean (magick-set-image-artifact wand1 \"compare:highlight-color\" highlight-color) wand1)\n     (check-boolean (magick-set-image-compose wand1  'SrcCompositeOp) wand1)\n     (multiple-value-bind (output difference)\n         (magick-compare-images\n          wand1\n          wand2\n          'RootMeanSquaredErrorMetric\n          0.0d0)\n       (unwind-protect\n            (let ((same-p (= difference 0.0d0)))\n              (funcall fn output same-p))\n         (unless (fli:null-pointer-p output)\n           (destroy-magick-wand output)))))))\n\n(defun compare-images (wand1 wand2)\n  (with-image-comparison (wand1 wand2 :same-p same-p)\n    same-p))\n\n(defmethod compare-image-files ((magick magick-native) file1 file2)\n  (push-event :magick.compare-images-files)\n  (with-wand (wand1 :file file1)\n    (with-wand (wand2 :file file2)\n      (compare-images wand1 wand2))))\n\n(defun save-wand-to-file (wand output)\n  \"Similar to save as webp\"\n  (check-boolean (magick-write-image wand (namestring output)) wand))\n\n(defun save-as-webp (wand output &key (lossless t))\n  (when lossless\n   (check-boolean (magick-set-option wand \"webp:lossless\" \"true\") wand))\n  (check-boolean (magick-strip-image wand) wand)\n  (save-wand-to-file wand output))\n\n(defmethod convert-to-lossless-webp ((self magick-native) input output)\n  (push-event :magick.convert-to-lossless-webp)\n  (with-wand (wand :file input)\n    (save-as-webp wand output)))\n\n\n(setf *magick* (make-instance 'magick-native))\n\n\n(defmethod ping-image-dimensions ((magick magick-native) file)\n  (destructuring-bind (width height format)\n      (ping-image-metadata magick file)\n    (declare (ignore format))\n    (list width height)))\n\n(defmethod ping-image-metadata ((magick magick-native) file)\n  (push-event :magick.ping-image-metadata)\n  (with-wand (wand)\n    (check-boolean (magick-ping-image wand (namestring file))\n                   wand)\n    (list\n     (magick-get-image-width wand)\n     (magick-get-image-height wand)\n     (get-image-format wand))))\n\n\n(defun from-magick-string (format)\n  (unwind-protect\n       (fli:convert-from-foreign-string format)\n    (magick-relinquish-memory format)))\n\n(defun get-image-format (wand)\n  (from-magick-string (magick-get-image-format wand)))\n\n(fli:define-c-struct pixel\n    (x :size-t)\n  (y :size-t))\n\n(def-easy-macro with-pixel (&binding pixel x y &fn fn)\n  (fli:with-dynamic-foreign-objects\n      ((pixel pixel))\n    (setf (fli:foreign-slot-value pixel 'x) x)\n    (setf (fli:foreign-slot-value pixel 'y) y)\n    (funcall fn pixel)))\n\n(fli:define-c-struct native-mask\n    (x :size-t)\n  (y :size-t)\n  (width :size-t)\n  (height :size-t))\n\n(fli:define-foreign-function screenshotbot-set-pixel\n    ((wand (:pointer wand))\n     (pixel (:pointer pixel))\n     (color (:reference-pass :ef-mb-string)))\n  :result-type :boolean)\n\n(fli:define-foreign-function screenshotbot-find-non-transparent-pixels-with-masks\n    ((wand (:pointer wand))\n     (masks (:pointer native-mask))\n     (num-masks :size-t)\n     (output (:pointer pixel))\n     (max :size-t))\n  :result-type :size-t)\n\n(fli:define-foreign-function screenshotbot-verify-magick\n    ((src-composite-op composite-operator)\n     (on-alpha-channel alpha-channel-option))\n  :result-type :int)\n\n(fli:define-foreign-function (magick-get-image-pixel-color \"MagickGetImagePixelColor\")\n    ((wand (:pointer wand))\n     (x :ssize-t)\n     (y :ssize-t)\n     (color (:pointer pixel-wand)))\n  :result-type :boolean)\n\n(fli:define-foreign-function (pixel-get-color-as-string \"PixelGetColorAsString\")\n    ((wand (:pointer pixel-wand)))\n  :result-type (:pointer :char))\n\n\n(defun map-non-alpha-pixels (wand fn &key (limit 1000))\n  ;; for each pixel in wand that is not 100% transparent, call the\n  ;; function, upto LIMIT times.\n  (push-event :magick.map-non-alpha)\n  (let ((pxs (get-non-alpha-pixels wand :limit limit)))\n    (loop for i below (car (array-dimensions pxs))\n          do (funcall fn (aref pxs i 0) (aref pxs i 1)))))\n\n(def-easy-macro with-native-masks (&binding native-masks masks &fn fn)\n  (fli:with-dynamic-foreign-objects ((native-masks native-mask :nelems (1+ (length masks))))\n    (let ((ptr (fli:copy-pointer native-masks)))\n      (loop for mask in masks\n            do\n               (progn\n                 ;; Clamp negative coordinates to 0 (to avoid unsigned integer overflow)\n                 ;; and adjust width/height to maintain the same coverage\n                 (let* ((left (mask-rect-left mask))\n                        (top (mask-rect-top mask))\n                        (width (mask-rect-width mask))\n                        (height (mask-rect-height mask))\n                        ;; Clamp to 0 if negative\n                        (clamped-x (max 0 (ceiling left)))\n                        (clamped-y (max 0 (ceiling top)))\n                        ;; Adjust width/height to maintain right/bottom edges\n                        (adjusted-width (floor (+ left width (- clamped-x))))\n                        (adjusted-height (floor (+ top height (- clamped-y)))))\n                   (setf (fli:foreign-slot-value ptr 'x) clamped-x)\n                   (setf (fli:foreign-slot-value ptr 'y) clamped-y)\n                   (setf (fli:foreign-slot-value ptr 'width) (max 0 adjusted-width))\n                   (setf (fli:foreign-slot-value ptr 'height) (max 0 adjusted-height)))\n                 (fli:incf-pointer ptr))))\n    (fn native-masks)))\n\n(defun get-non-alpha-pixels (wand &key (limit 1000)\n                                    masks)\n  (declare (optimize (Debug 3) (speed 0)))\n  (log:debug \"Got masks: ~S\" (loop for mask in masks\n                                   collect (list\n                                            (mask-rect-left mask)\n                                            (mask-rect-top mask)\n                                            (mask-rect-width mask)\n                                            (mask-rect-height mask))))\n  (with-magick-gatekeeper ()\n    (when (magick-get-image-alpha-channel wand)\n      (with-native-masks (native-masks masks)\n       (fli:with-dynamic-foreign-objects\n           ((output pixel :nelems limit))\n         (let ((size (screenshotbot-find-non-transparent-pixels-with-masks\n                      wand\n                      native-masks\n                      (length masks)\n                      output limit)))\n           (let ((ret (make-array (list size 2))))\n             (loop for i below size\n                   do\n                      (setf (aref ret i 0) (fli:foreign-slot-value output 'x))\n                      (setf (aref ret i 1) (fli:foreign-slot-value output 'y))\n                      (fli:incf-pointer output))\n             ret)))))))\n\n(defun limit-size-for-webp (wand)\n  (let ((+max-dim+ 16383))\n    (let ((width (magick-get-image-width wand))\n          (height (magick-get-image-height wand)))\n      (when (or\n             (> width +max-dim+)\n             (> height +max-dim+))\n        (let ((width (min width +max-dim+))\n              (height (min height +max-dim+)))\n          (check-boolean\n           (magick-crop-image wand\n                              width\n                              height\n                              0\n                              0)\n           wand))))))\n\n(defun compare-wands (before after p &key in-place-p)\n  ;; Limit the size of the before and after wands before doing the\n  ;; comparison\n  (limit-size-for-webp before)\n  (limit-size-for-webp after)\n  (with-image-comparison (before after\n                          :result result\n                          :same-p same-p\n                          :in-place-p in-place-p\n                          :highlight-color \"red\"\n                          :lowlight-color \"none\")\n    (save-as-webp result p)\n    same-p))\n\n(def-easy-macro with-pixel-wand (&binding pixel-wand &fn fn)\n  (let ((pwand (new-pixel-wand)))\n    (unwind-protect\n         (funcall fn pwand)\n      (clear-pixel-wand pwand))))\n\n(def-easy-macro with-drawing-wand (&binding drawing-wand &fn fn)\n  (let ((dwand (new-drawing-wand)))\n    (unwind-protect\n         (funcall fn dwand)\n      (destroy-drawing-wand dwand))))\n\n(fli:define-foreign-function screenshotbot-resize\n    ((wand (:pointer wand))\n     (width :size-t)\n     (height :size-t))\n  :result-type :boolean)\n\n(fli:define-foreign-function screenshotbot-calculate-row-rmse-square\n    ((one (:pointer pixel-iterator))\n     (two (:pointer pixel-iterator)))\n  :result-type :double)\n\n(defun resize-image (input &key output size)\n  (destructuring-bind (width height)\n      (cond\n        ((stringp size)\n         (mapcar 'parse-integer (str:split \"x\" size)))\n        (t size))\n    (with-wand (wand :file input)\n      (let ((old-width (magick-get-image-width wand))\n            (old-height (magick-get-image-height wand)))\n       (cond\n         ((and\n           (string-equal (get-image-format wand) \"webp\")\n           (> width old-width)\n           (> height old-height))\n          ;; The image is already an appropriate size and format, just\n          ;; hardlink it\n          (if (path:-e output)\n              (delete-file output))\n          (copy-file-fast input output))\n         (t\n          ;; We really have to do the resize...\n          (let ((scale (min (/ width old-width)\n                            (/ height old-height)\n                            1)))\n           (check-boolean\n            (screenshotbot-resize\n             wand\n             (ceiling (* scale old-width))\n             (ceiling (* scale old-height)))\n            wand))\n          (save-as-webp wand output\n                        :lossless nil)))))))\n\n\n(defun magick-identify-image (wand)\n  (let ((ptr (%magick-identify-image wand)))\n    (unwind-protect\n         (fli:convert-from-foreign-string ptr)\n      (magick-relinquish-memory ptr))))\n\n(defun magick-bad-exif-data (wand)\n  \"Returns a map of `bad` exif data. This is exif data that might be\ntemporary and change over time. At time of writing we only expect this\nto be encoded timestamps.\"\n  (let ((identification (magick-identify-image wand))\n        (keys (list \"png:tIME\")))\n    (log:info \"Got id: ~a\" identification)\n    (reduce\n     (lambda (map line)\n       (destructuring-bind (key &optional val) (mapcar #'str:trim (str:split \": \" (str:trim line) :limit 2))\n         (log:info \"Got key: ~a\" key)\n         (cond\n           ((str:s-member keys key)\n            (fset:with map key val))\n           (t\n            map))))\n     (str:lines identification)\n     :initial-value (fset:empty-map))))\n\n(defun get-px-as-string (wand x y)\n  (with-pixel-wand (px)\n    (magick-get-image-pixel-color\n     wand x y\n     px)\n    (from-magick-string (pixel-get-color-as-string px))))\n\n(def-easy-macro with-pixel-iterator (&binding pixel-iterator wand &fn fn)\n  (let ((pix-it (new-pixel-iterator wand)))\n    (unwind-protect\n         (fn pix-it)\n      (destroy-pixel-iterator pix-it))))\n\n(defun calculate-difference-rmse (wand1 wand2)\n  \"Compute the difference between the two wands in terms of RMSE\"\n  (cond\n    ;; TODO: this null-pointer check is for backward compatibility in\n    ;; case we accidently ship this\n    #+lispworks\n    ((fli:null-pointer-p (fli:make-pointer :address 'screenshotbot-calculate-row-rmse-square))\n     (warn \"screenshotbot_calculate_row_rmse_square not present in magick-native\")\n     0.0)\n    ;; If heights or widths are different then return 1.0.\n    ((not (apply #'= (mapcar #'magick-get-image-width (list wand1 wand2))))\n     1.0d0)\n    ((not (apply #'= (mapcar #'magick-get-image-height (list wand1 wand2))))\n     1.0d0)    \n    (t\n     ;; At this point we know the dimensions are identical\n     (with-pixel-iterator (pix1 wand1)\n       (with-pixel-iterator (pix2 wand2)\n         (let ((result 0)\n               (rows (magick-get-image-height wand1)))\n           (dotimes (i rows)\n             (incf result (screenshotbot-calculate-row-rmse-square pix1 pix2)))\n           (sqrt (/ result rows))))))))\n"
  },
  {
    "path": "src/screenshotbot/magick/magick-native.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include <stdbool.h>\n\n#if __has_include(\"MagickWand/MagickWand.h\")\n# include <MagickWand/MagickWand.h>\n#else\n# include <wand/MagickWand.h>\n#endif\n\ntypedef struct _pixel {\n        size_t x;\n        size_t y;\n} pixel;\n\n#if MagickLibVersion >= 0x700\n#define IF7(v1,v2) v1\n#else\n#define IF7(v1,v2) v2\n#endif\n\n\n#define MAX_QUANTUM ((1 << MAGICKCORE_QUANTUM_DEPTH) - 1)\n\nextern int screenshotbot_verify_magick(CompositeOperator srcCompositeOp,\n                                       IF7(AlphaChannelOption,AlphaChannelType) onAlphaChannel) {\n        size_t depth;\n        GetMagickQuantumDepth(&depth);\n        if (MAGICKCORE_QUANTUM_DEPTH != depth) {\n                return -1;\n        }\n\n        if (srcCompositeOp != SrcCompositeOp) {\n                return -2;\n        }\n\n        if (onAlphaChannel != SetAlphaChannel) {\n                return -3;\n        }\n\n        const char* features = GetMagickFeatures();\n        char* feat = strstr(features, \"HDRI\");\n\n\n#if MAGICKCORE_HDRI_ENABLE\n        int hdriMatches = (feat != NULL);\n#else\n        int hdriMatches = (feat == NULL);\n#endif\n        if (!hdriMatches) {\n                printf(\"HDRI setting does not match, original: %d. Features `%s`\",\n                       MAGICKCORE_HDRI_ENABLE,\n                       features);\n                return -4;\n        }\n\n        return 1;\n}\n\nstruct mask {\n        size_t x;\n        size_t y;\n        size_t width;\n        size_t height;\n};\n\n/*\n * Set a pixel color. This is only really useful for tests.\n */\nextern MagickBooleanType\nscreenshotbot_set_pixel(\n        MagickWand *wand,\n        const pixel *ppixel, // pointer just for FLI simplicity\n        const char* color) {\n        PixelIterator *iterator = NewPixelIterator(wand);\n        MagickBooleanType ret = MagickFalse;\n        size_t height = MagickGetImageHeight(wand);\n        pixel pixel = *ppixel;\n\n        if (pixel.y >= height) {\n                printf(\"Invalid height: %zu\\n\", pixel.y);\n                goto cleanup;\n        }\n\n        size_t width;\n        PixelWand** row;\n        for (int y = 0; y <= pixel.y; y++) {\n                row = PixelGetNextIteratorRow(iterator, &width);\n        }\n\n        if (pixel.x >= width) {\n                printf(\"Invalid width: %zu out of %zu\\n\", pixel.x, width);\n                goto cleanup;\n        }\n\n        ret = PixelSetColor(row[pixel.x], color);\n        if (!ret) {\n                printf(\"PixelSetColor failed\\n\");\n                goto cleanup;\n        }\n\n        ret = PixelSyncIterator(iterator);\ncleanup:\n        DestroyPixelIterator(iterator);\n        return ret;\n}\n\nstatic int _mask_cmp(const void* p1, const void* p2) {\n        const struct mask* m1 = (struct mask*) p1;\n        const struct mask* m2 = (struct mask*) p2;\n\n        return m1->x - m2->x;\n}\n\nstatic int in_range(int x, int start, int width) {\n        return start <= x && x < (start + width);\n}\n\n/*\n * Returns a sorted list of masks that that intersect in the y\n * positions. The number that is returned is number of elements in output.\n */\nstatic int filter_masks(struct mask* masks, struct mask* output, int numMasks, int y) {\n        int ret = 0;\n        for (int i = 0; i < numMasks; i++) {\n                struct mask mask = masks[i];\n                if (in_range(y, mask.y, mask.height)) {\n                        output[ret++] = mask;\n                }\n        }\n\n        qsort(output, ret, sizeof(struct mask), &_mask_cmp);\n        return ret;\n}\n\ninline int _max(int a, int b) {\n        return (a > b ? a : b);\n}\n\ninline int _min(int a, int b) {\n        return (a < b ? a : b);\n}\n\nextern size_t\nscreenshotbot_find_non_transparent_pixels_with_masks\n(MagickWand* wand, struct mask* masks, size_t numMasks, pixel* output, size_t max) {\n        max--;\n        PixelIterator* iterator = NewPixelIterator(wand);\n        struct mask *tmp = malloc(numMasks * sizeof(struct mask));\n\n        //printf(\"The top mask is: %zu, %zu\\n\", masks[0].y, masks[0].height);\n\n        size_t ret = 0;\n        size_t height = MagickGetImageHeight(wand);\n        for (int y = 0; y < height; ++y) {\n                int numLocalMasks = filter_masks(masks, tmp, numMasks, y);\n                //printf(\"For %d, got %d local masks out of %zu\\n\", y, numLocalMasks, numMasks);\n                int nextMask = 0;\n\n                size_t width = 0;\n                PixelWand** row = PixelGetNextIteratorRow(iterator, &width);\n\n                for (int x = 0; x < width; x++) {\n\n                        if (nextMask < numLocalMasks) {\n                                if (x >= tmp[nextMask].x) {\n                                        x = _max(x, tmp[nextMask].x + tmp[nextMask].width) - 1;\n                                        nextMask++;\n                                        //printf(\"Set x to %d\\n\", x);\n                                        continue;\n                                }\n                        }\n\n                        Quantum px = PixelGetAlphaQuantum(row[x]);\n\n                        /*\n                         * Currently the yellow that's drawn as part\n                         * of the mask is ~204.  However, we also need\n                         * to deal with a situation that the yellow is\n                         * overlaid on the red pixel. So even though\n                         * this function is named \"get_non_alpha\",\n                         * it's actually \"get_non_alpha\" where green\n                         * is 0.\n                         */\n                        if (px == MAX_QUANTUM && PixelGetGreenQuantum(row[x]) == 0) {\n                                if (output) {\n                                        output[ret].x = x;\n                                        output[ret].y = y;\n                                }\n                                ret++;\n\n                                if (ret >= max) {\n                                        goto cleanup;\n                                }\n                        }\n                }\n        }\n\ncleanup:\n        free(tmp);\n        DestroyPixelIterator(iterator);\n        return ret;\n}\n\nstatic void draw_red(PixelWand* wand) {\n        PixelSetAlphaQuantum(wand, QuantumRange);\n        PixelSetRedQuantum(wand, QuantumRange);\n        PixelSetGreenQuantum(wand, 0);\n        PixelSetBlueQuantum(wand, 0);\n}\n\nstatic void draw_transparent(PixelWand* wand) {\n        PixelSetAlphaQuantum(wand, 0);\n        PixelSetRedQuantum(wand, 0);\n        PixelSetGreenQuantum(wand, 0);\n        PixelSetBlueQuantum(wand, 0);\n}\n\n\nstatic bool fill_row_with_red(int start, PixelWand** drow, size_t dwidth) {\n        for (int i = start; i < dwidth; i++) {\n                draw_red(drow[i]);\n        }\n        return start < dwidth;\n}\n\n#define DIST(name,i) (((long)name(drow)) - ((long)name(srow)))\ninline long get_square_dist(PixelWand* srow, PixelWand* drow) {\n        long a = DIST(PixelGetAlphaQuantum, x);\n        long r = DIST(PixelGetRedQuantum, x);\n        long g = DIST(PixelGetGreenQuantum, x);\n        long b = DIST(PixelGetBlueQuantum, x);\n        return (a*a + r*r + g*g + b*b);\n\n}\n\nstatic bool inplace_compare_rows_v2(PixelWand** drow, PixelWand** srow,\n                                    size_t dwidth,\n                                    size_t swidth,\n                                    int dist) {\n        int x;\n        bool changed = false;\n        long sq_dist = 4 * dist;\n\n#if MAGICKCORE_QUANTUM_DEPTH == 16\n        sq_dist *= 256 * 256;\n#endif\n\n#define CQ(name,i) (name(drow[i]) == name(srow[i]))\n\n        for (x = 0; x < _min(dwidth, swidth); x++) {\n                bool same;\n\n                if (sq_dist == 0) {\n                        same = CQ(PixelGetAlphaQuantum, x) &&\n                                CQ(PixelGetRedQuantum, x) &&\n                                CQ(PixelGetBlueQuantum, x) &&\n                                CQ(PixelGetGreenQuantum, x);\n                } else {\n                        same = (get_square_dist(srow[x], drow[x]) <= sq_dist);\n                }\n                if (same) {\n                        draw_transparent(drow[x]);\n                } else {\n                        draw_red(drow[x]);\n                        changed = true;\n                }\n        }\n\n        if (fill_row_with_red(x, drow, dwidth)) {\n                changed = true;\n        }\n\n        return changed;\n\n}\n\n/*\n * Compares dest to src, while modifying dest.  returns 0 if no\n * changes were detected. -1 on error, and 1 on success.\n *\n * dist: the distance between two pixels to be considered different.\n *\n * v1 of this function did not take a dist, but that is now in Lisp\n * code.\n */\nextern int screenshotbot_inplace_compare_v2(MagickWand* dest,\n                                            MagickWand* src,\n                                            int dist) {\n        PixelIterator* diter = NULL;\n        PixelIterator* siter = NULL;\n        int changed = 0;\n\n\n        diter = NewPixelIterator(dest);\n        if (!diter) {\n                changed = -1;\n                goto cleanup;\n        }\n\n        siter = NewPixelIterator(src);\n        if (!siter) {\n                changed = -1;\n                goto cleanup;\n        }\n\n        int processedRows = 0;\n        //fprintf(stderr, \"iterating through the inplace rows\\n\");\n        while (true) {\n                processedRows++;\n                //fprintf(stderr, \"Processing row: %d\\n\", processedRows);\n                size_t dwidth = 0;\n                PixelWand** drow = PixelGetNextIteratorRow(diter, &dwidth);\n\n                size_t swidth = 0;\n                PixelWand** srow = PixelGetNextIteratorRow(siter, &swidth);\n\n                if (!drow) {\n                        //fprintf(stderr, \"Reached end at %d\\n\", processedRows);\n                        break;\n                } else if (srow) {\n                        if (inplace_compare_rows_v2(drow, srow, dwidth, swidth, dist)) {\n                                changed = 1;\n                        }\n                } else {\n                        if (fill_row_with_red(0, drow, dwidth)) {\n                                changed = 1;\n                        }\n                }\n\n                // We always have to sync, since we're writing\n                // transparent pixels too.\n                if (!PixelSyncIterator(diter)) {\n                        //fprintf(stderr, \"Failed to sync iterator\\n\");\n                        changed = -1;\n                        goto cleanup;\n                }\n        }\n\n\n        MagickSetImageColorspace(dest, RGBColorspace);\n\n        if (MagickGetImageHeight(dest) < MagickGetImageHeight(src)) {\n                changed = 1;\n        }\ncleanup:\n        if (diter) DestroyPixelIterator(diter);\n        if (siter) DestroyPixelIterator(siter);\n\n        // If the destination is shorter, then we're not going to\n        // process rest of the rows, so we should check that here.\n        return changed;\n        //return -processedRows;\n}\n\nextern MagickBooleanType\nscreenshotbot_resize(MagickWand *wand,\n                     size_t width,\n                     size_t height) {\n        return MagickResizeImage(\n                wand,\n                width,\n                height,\n                /* this is the only reason we're compiling this */\n                LanczosFilter\n#if MagickLibVersion < 0x700\n                ,1.0\n#endif\n         );\n}\n\n/*\n * Given the PixelIterators for two rows of same length, calculate the square of the RMSE. i.e.\n * it calculates (dr^2 + dg^2  + db^2 + da^2) / 4*len\n */\nextern double screenshotbot_calculate_row_rmse_square(PixelIterator* one, PixelIterator* two) {\n        size_t width1 = 0;\n        size_t width2 = 0;\n\n        PixelWand** row1 = PixelGetNextIteratorRow(one, &width1);\n        PixelWand** row2 = PixelGetNextIteratorRow(two, &width2);\n\n        if (width1 != width2) {\n                return 1.0;\n        }\n\n        long res = 0;\n\n        for (size_t i = 0; i < width1; i++) {\n                res += get_square_dist(row1[i], row2[i]);\n        }\n\n        return ((double)res) / MAX_QUANTUM / MAX_QUANTUM / width1 / 4 ;\n}\n"
  },
  {
    "path": "src/screenshotbot/magick/magick.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/magick\n  (:nicknames :screenshotbot/magick)\n  (:use #:cl)\n  (:import-from #:screenshotbot/magick/build\n                #:magick-wand-config)\n  (:import-from #:util/misc\n                #:or-setf)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:run-magick\n   #:compare-image-files\n   #:magick\n   #:*magick*\n   #:convert-to-lossless-webp\n   #:ping-image-dimensions\n   #:with-magick-gatekeeper))\n(in-package :screenshotbot/magick/magick)\n\n(defclass abstract-magick ()\n  ())\n\n(defclass magick-cli (abstract-magick)\n  ())\n\n(defvar *magick* (make-instance 'magick-cli))\n\n(defvar *prefix* nil)\n\n(defun prefix ()\n  (util/misc:or-setf\n   *prefix*\n   (format nil \"~a/\"\n           (magick-wand-config \"--prefix\"))))\n\n(defun magick ()\n  *magick*)\n\n(defvar *semaphore* (bt:make-semaphore\n                     :name \"magick\"\n                     :count (serapeum:count-cpus)))\n\n(defun call-with-semaphore (sem fn)\n  (unwind-protect\n       (progn\n         (bt:wait-on-semaphore sem :timeout 60)\n         (funcall fn))\n    (bt:signal-semaphore sem)))\n\n(defun magick-prefix-uncached ()\n  (cond\n    ((uiop:os-unix-p)\n     (let ((magick (path:catfile (prefix) \"bin/magick\")))\n      (cond\n        ((uiop:file-exists-p magick)\n         (list (namestring magick)))\n        (t\n         nil))))\n   ((uiop:os-windows-p)\n    (list \"C:\\\\Program Files\\\\ImageMagick-7.1.0-Q8\\\\magick\"))\n   (t\n    (error \"Unsupported impl\"))))\n\n(let (cache)\n (defun magick-prefix ()\n   \"ImageMagick 7 comes with a binary called `magick`. We can still\n  call `convert` etc directly. But `convert` *might* point to a 6.9\n  version of ImageMagick. The 6.9 version is buggy.\n\n  We could download the magick binary at runtime, however, the magick\n  binary uses AppImage, and doesn't work seamlessly on docker. In\n  production, we'll ensure the magick binary is available. On OSS,\n  it's harder to enforce, so for now, if it's not available then we'll\n  just revert to calling convert directly.\"\n\n   (cdr ;; remove :dummy\n    (or-setf\n     cache\n     (cons :dummy\n           (magick-prefix-uncached))))))\n\n(defun default-limits ()\n  (list\n   \"-limit\" \"memory\" \"3MB\"\n   \"-limit\" \"map\" \"3MB\"\n   \"-limit\" \"disk\" \"1000MB\"))\n\n(defun run-magick (command &rest args &key (error-output t) (output t)\n                                        (ignore-error-status nil)\n                                        (async nil)\n                                        (lock t))\n  \"Wrapper for magick commands, in the future we might run this in-process\"\n  (restart-case\n      (let* ((command (loop for str in command\n                            if (pathnamep str)\n                              collect (namestring str)\n                            else\n                              collect str))\n             (command `(,(car command)\n                        ,@(default-limits)\n                        ,@(cdr command)))\n             (command (append (magick-prefix) command)))\n\n        (labels ((actually-run ()\n                   (uiop:run-program\n                    command\n                    :output output\n                    :error-output error-output\n                    :ignore-error-status ignore-error-status))\n                 (run ()\n                   (cond\n                     (lock\n                      (call-with-semaphore\n                       *semaphore*\n                       #'actually-run))\n                     (t\n                      (actually-run)))))\n          (cond\n            (async\n             ;; this is the streaming functionality. In this case we\n             ;; return a stream that's the output. This only works on\n             ;; Lispworks\n             #+lispworks\n             (system:open-pipe command\n                               :element-type '(unsigned-byte 8)\n                               :direction :input)\n\n             ;; Outside of lispworks, let's not try to complicate\n             ;; things. Let's just save to a local temporary file, and\n             ;; return that stream.\n             #-lispworks\n             (uiop:with-temporary-file (:pathname p)\n               (setf output p)\n               (run)\n               (open p :direction :input :element-type '(unsigned-byte 8))))\n            (t\n             (run)))))\n    (retry-run-magick ()\n      (apply #'run-magick command args))))\n\n\n(defmethod compare-image-files ((magick magick-cli) file1 file2)\n  (multiple-value-bind (out err ret)\n      (run-magick\n       (list \"compare\" \"-metric\" \"RMSE\" file1 file2 \"null:\")\n       :error-output 'string\n       :ignore-error-status t\n       :lock nil)\n    (declare (ignore out))\n    (and (= 0 ret)\n         (string= \"0 (0)\" (str:trim err)))))\n\n(defmacro with-magick-gatekeeper (()  &body body)\n  `(call-with-semaphore\n    *semaphore*\n    (lambda ()\n      ,@body)))\n\n(defmethod compare-image-files :around ((magick abstract-magick) file1 file2)\n  (with-magick-gatekeeper ()\n    (call-next-method)))\n\n\n(defmethod convert-to-lossless-webp ((magick magick-cli) file1 output)\n  (run-magick\n   (list\n    \"convert\" file1 \"-strip\" \"-define\" \"webp:lossless=true\" output)\n   :lock nil))\n\n(defmethod convert-to-lossless-webp ((magick abstract-magick) file1 output)\n  (assert (equal \"webp\" (pathname-type output)))\n  (with-magick-gatekeeper ()\n    (call-next-method)))\n\n(defmethod ping-image-dimensions ((magick magick-cli) file)\n  (let ((res (run-magick\n              (list \"identify\" \"-format\" \"%w %h\" file)\n              :output 'string)))\n    (mapcar #'parse-integer\n              (str:split \" \" res))))\n"
  },
  {
    "path": "src/screenshotbot/magick/screenshotbot.magick.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :screenshotbot.magick\n  :serial t\n  :defsystem-depends-on (:screenshotbot.magick.build)\n  :depends-on (#-lispworks #:util/fake-fli\n               #:easy-macros\n               #:util/events\n               #:util/posix\n               #:trivial-features\n               #:screenshotbot/mask-rect-api\n               #:util/native-module\n               #:util/health-check\n               #:screenshotbot.magick.build\n               #:serapeum\n               #:fset\n               #:util/copy-file\n               #:alexandria)\n  :components ((:file \"magick\")\n               (\"screenshotbot/magick/build:lib-source-file\"\n                \"magick-native\")\n               (\"screenshotbot/magick/build:magick-cl-source-file\" \"ffi-7\")\n               (\"screenshotbot/magick/build:magick-cl-source-file\" \"ffi-6\")\n               (\"screenshotbot/magick/build:magick-cl-source-file\"\n                \"magick-lw\")\n               (:file \"health-checks\")))\n\n(defsystem :screenshotbot.magick/tests\n  :serial t\n  :defsystem-depends-on (:screenshotbot.magick.build)\n  :depends-on (:util/fiveam\n               :tmpdir\n               :fiveam-matchers\n               :util/digests\n               :screenshotbot.magick)\n  :components ((\"screenshotbot/magick/build:magick-cl-source-file\" \"test-magick-lw\")))\n"
  },
  {
    "path": "src/screenshotbot/magick/screenshotbot.magick.build.asd",
    "content": "(defsystem :screenshotbot.magick.build\n  :serial t\n  :depends-on (:str\n\t       :cl-fad\n\t       :trivial-features)\n  :components ((:file \"build\")))\n"
  },
  {
    "path": "src/screenshotbot/magick/test-magick-lw.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/magick/test-magick-lw\n  (:use #:cl\n        #:fiveam\n        #:fiveam-matchers\n        #:screenshotbot/mask-rect-api)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:calculate-difference-rmse\n                #:screenshotbot-inplace-compare-v2\n                #:get-px-as-string\n                #:magick-bad-exif-data\n                #:magick-identify-image\n                #:with-pixel\n                #:magick-get-image-width\n                #:magick-get-image-height\n                #:resize-image\n                #:save-as-webp\n                #:pixel-set-color\n                #:pixel-set-alpha\n                #:set-wand-alpha-channel\n                #:get-non-alpha-pixels\n                #:y\n                #:pixel\n                #:x\n                #:screenshotbot-set-pixel\n                #:magick-new-image\n                #:with-pixel-wand\n                #:compare-wands\n                #:magick-set-size\n                #:verify-magick\n                #:load-magick-native\n                #:screenshotbot-verify-magick\n                #:with-image-comparison\n                #:ping-image-metadata\n                #:map-non-alpha-pixels\n                #:magick-exception-message\n                #:magick-read-image\n                #:new-magick-wand\n                #:magick-exception\n                #:with-wand\n                #:check-boolean\n                #:compare-images\n                #:magick-native)\n  (:import-from #:screenshotbot/magick/magick\n                #:convert-to-lossless-webp)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:local-nicknames (#:a #:alexandria)\n                    #-lispworks\n                    (#:fli #:util/fake-fli)))\n(in-package :screenshotbot/magick/test-magick-lw)\n\n\n(util/fiveam:def-suite)\n\n(eval-when (:compile-toplevel :execute)\n  (defun fixture (name)\n    (asdf:system-relative-pathname :screenshotbot.magick\n                                   (format nil \"../fixture/~a\" name))))\n\n(def-fixture state ()\n  (tmpdir:with-tmpdir (tmpdir)\n    (let ((rose #.(fixture \"rose.png\"))\n          (rose-webp #.(fixture \"rose.webp\"))\n          (wizard #.(fixture \"wizard.png\"))\n          (transparent #.(fixture \"point.png\")))\n      (&body))))\n\n(test simple-file-load-save\n  (with-fixture state ()\n   (with-wand (wand :file rose)\n     (pass))))\n\n(test compare-nil\n  (with-fixture state ()\n    (with-wand (wand1 :file rose)\n      (with-wand (wand2 :file wizard)\n        (is-false (compare-images wand1 wand2))))))\n\n(test compare-is-true\n  (with-fixture state ()\n    (with-wand (wand1 :file rose)\n      (with-wand (wand2 :file rose-webp)\n        (is-true (compare-images wand1 wand2))))))\n\n(test convert-to-webp\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname out :type \"webp\")\n      (convert-to-lossless-webp\n       (make-instance 'magick-native)\n       rose out)\n      (with-wand (rose1 :file rose)\n        (with-wand (out1 :file out)\n          (is-true (compare-images rose1 out1)))))))\n\n(test ensure-convert-to-webp-is-deterministic\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname out :type \"webp\")\n      (convert-to-lossless-webp (make-instance 'magick-native)\n                                rose out)\n      (uiop:with-temporary-file (:pathname out2 :type \"webp\")\n        (convert-to-lossless-webp (make-instance 'magick-native)\n                                  rose out2)\n        (is (equalp (md5-file out)\n                    (md5-file out2)))))))\n\n(test raises-magick-exception\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname p)\n      (with-wand (wand)\n        (handler-case\n            (let ((code (magick-read-image wand (namestring p))))\n              (check-boolean code wand)\n              (fail \"Excepted exception\"))\n          (magick-exception (e)\n            (is (str:containsp \"decode delegate for\"\n                               (magick-exception-message e)))))))))\n\n\n(test raises-magick-exception-message-is-not-corrupted\n  ;; Just ensuring that magick-relinquish-memory is the right thing to\n  ;; do.\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname p)\n      (with-wand (wand)\n        (let ((messages\n                (loop for x from 0 to 100\n                      collect\n                      (handler-case\n                          (let ((code (magick-read-image wand (namestring p))))\n                            (check-boolean code wand)\n                            (fail \"Excepted exception\"))\n                        (magick-exception (e)\n                          (magick-exception-message e))))))\n          (assert-that messages\n                       (every-item\n                        (starts-with \"no decode delegate for\"))))))))\n\n(test find-first-non-transparent\n  (handler-bind ((error (lambda (E)\n                          (trivial-backtrace:print-backtrace e))))\n   (with-fixture state ()\n     (let ((transparent-pixel-for-null\n             (block top\n               (with-wand (wand :file transparent)\n                 (map-non-alpha-pixels wand\n                                       (lambda (i j)\n                                         (return-from top (cons i j))))))))\n       (is\n        (equal (cons 20 30)\n               transparent-pixel-for-null)))\n     (is\n      ;; Is this going to be constant? I hope so. If this changes with\n      ;; ImageMagick version, then just replace this, okay?\n      (equal (cons 244 38)\n             (block top\n               (with-wand (wand :file \"logo:\")\n                 (map-non-alpha-pixels wand\n                                       (lambda (i j)\n                                         (return-from top (cons i j)))))))))))\n(test ping-image-metadata\n  (with-fixture state ()\n    (is (equal '(70 46 \"PNG\")\n               (ping-image-metadata (make-instance 'magick-native)\n                                    rose)))\n    (is (equal '(70 46 \"WEBP\")\n               (ping-image-metadata (make-instance 'magick-native)\n                                    rose-webp)))))\n\n(test no-background-in-compare\n  (with-fixture state ()\n    (with-wand (one :file rose)\n      (with-wand (two :file rose)\n        (with-image-comparison (one two :result result :same-p same-p)\n          (is-true same-p)\n          (let ((non-alphas 0))\n            (map-non-alpha-pixels result\n                                  (lambda (x y)\n                                    (incf non-alphas)))\n            (assert-that non-alphas\n                         (described-as \"We shouldn't have a background image in the comparison\"\n                           (equal-to 0)))))))))\n\n(test verify-magick-native\n  (load-magick-native)\n  (with-fixture state ()\n    (finishes\n      (verify-magick))))\n\n(test force-reload-magick-native\n  (load-magick-native)\n  (finishes\n   (load-magick-native :force t)))\n\n;; Dummy test to look at the test output in builds to see which\n;; version of magick is being used.\n(test #+magick-6 using-magick-6 #+magick-7 using-magick7\n  (pass))\n\n(def-easy-macro with-large-wand (&binding wand &key (height 16385)  &fn fn)\n  (with-wand (wand)\n    (with-pixel-wand (pwand)\n      (check-boolean\n       (magick-new-image wand\n                         1 height\n                         pwand)\n       wand))\n    (funcall fn wand)))\n\n#-screenshotbot-oss\n(test large-image\n  \"If this test fails, definitely look at the global policy.xml for the\n given ImageMagick version. Currently, we can't override the\n resource:height policy, which by default is set to 16KP, at least on\n ImageMagick 6. On IM7, it appears to be set to 300KP, which I'm not\n sure if I changed myself in my local instance. On the docker\n instances it doesn't look like the height policy is set, and I'm too\n lazy to figure out what the default is. \"\n  (with-large-wand (before)\n    (with-large-wand (after)\n      (uiop:with-temporary-file (:pathname output :type \"webp\")\n        ;; This does not work in Magick-6. I don't know why. Ideally\n        ;; I'd like to make it work, but for now I'll just disable\n        ;; this test. Essentially, expect compare-wands to fail on\n        ;; large images in magic-6.\n        (finishes\n          (compare-wands before after output))))))\n\n(test small-image-comparison-happy-path\n  (with-large-wand (before :height 20)\n    (with-large-wand (after :height 20)\n      (uiop:with-temporary-file (:pathname output :type \"webp\")\n        (finishes\n          (compare-wands before after output))))))\n\n(defun  mark-pixel (wand x y &key (color \"rgba(10,0,0,1.0)\"))\n  (with-pixel (pixel x y)\n    (check-boolean\n     (screenshotbot-set-pixel\n      wand\n      pixel\n      color)\n     wand)))\n\n(def-easy-macro with-single-pixel-image (&key &binding wand\n                                              width\n                                              height\n                                              default-mark\n                                              (default-color \"none\")\n                                              &fn fn)\n  (with-wand (wand)\n    (with-pixel-wand (pwand)\n      (pixel-set-color pwand default-color)\n      (check-boolean\n       (magick-new-image wand\n                         width height\n                         pwand)\n       wand)\n      (set-wand-alpha-channel wand))\n    (when default-mark\n      (mark-pixel wand 10 19))\n    (fn wand)))\n\n(def-fixture get-non-alpha (&key (width 40) (height 30) (default-mark t))\n  #+nil\n  (progn\n    (asdf:perform\n     'asdf:load-op\n     (asdf:find-component :screenshotbot.magick \"magick-native\"))\n    (load-magick-native :force t))\n  (with-single-pixel-image (:wand wand :height height :width width\n                            :default-mark t)\n    (&body)))\n\n(test get-non-alpha-pixels ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand)))\n      (is (equalp (list 1 2)\n                  (array-dimensions res)))\n      (is\n       (equalp #2A((10 19))\n               res)))))\n\n(defclass simple-mask ()\n  ((left :initarg :left\n         :reader mask-rect-left)\n   (top :initarg :top\n        :reader mask-rect-top)\n   (width :initarg :width\n          :reader mask-rect-width)\n   (height :initarg :height\n           :reader mask-rect-height)))\n\n(test get-non-alpha-with-masks ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 0\n                                                          :height 3\n                                                          :width 3)))))\n      (is (equalp (list 1 2)\n                  (array-dimensions res)))\n      (is\n       (equalp #2A((10 19))\n               res)))))\n\n\n(test get-non-alpha-with-masks-covering-change ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list (make-instance 'simple-mask\n                                                          :left 9\n                                                          :top 18\n                                                          :height 6\n                                                          :width 6)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res)))\n      (pass))))\n\n(test get-non-alpha-with-two-masks ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 0\n                                                          :height 3\n                                                          :width 3)\n                                      (make-instance 'simple-mask\n                                                          :left 9\n                                                          :top 18\n                                                          :height 6\n                                                          :width 6)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))))\n\n(test get-non-alpha-with-masks-that-extend-the-space ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 0\n                                                          :height 300\n                                                          :width 300)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))))\n\n(test get-non-alpha-with-masks-left-border-is-included ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 10\n                                                          :top 0\n                                                          :height 300\n                                                          :width 300)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                     :left 11\n                                                     :top 0\n                                                     :height 300\n                                                     :width 300)))))\n      (is (equalp (list 1 2)\n                  (array-dimensions res))))))\n\n(test get-non-alpha-with-masks-top-border-is-included ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 19\n                                                          :height 300\n                                                          :width 300)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))\n\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                     :left 0\n                                                     :top 20\n                                                     :height 300\n                                                     :width 300)))))\n      (is (equalp (list 1 2)\n                  (array-dimensions res))))))\n\n(test get-non-alpha-with-masks-right-border-is-exclusive ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 0\n                                                          :width 11\n                                                          :height 30)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))\n\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                     :left 0\n                                                     :top 0\n                                                     :width 10\n                                                     :height 100)))))\n      (is (equalp (list 1 2)\n                  (array-dimensions res))))))\n\n\n(test get-non-alpha-with-masks-right-border-is-exclusive-even-with-overlaps ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 0\n                                                          :width 11\n                                                          :height 30)\n                                      (make-instance 'simple-mask\n                                                     :left 3\n                                                     :top 0\n                                                     :width 5\n                                                     :height 30)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))\n\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                     :left 3\n                                                     :top 0\n                                                     :width 5\n                                                     :height 30)\n                                      (make-instance 'simple-mask\n                                                     :left 0\n                                                     :top 0\n                                                     :width 10\n                                                     :height 100)))))\n      (is (equalp (list 1 2)\n                  (array-dimensions res))))))\n\n\n(test get-non-alpha-with-masks-bottom-border-is-exclusive ()\n  (with-fixture get-non-alpha ()\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                          :left 0\n                                                          :top 0\n                                                          :width 11\n                                                          :height 20)))))\n      (is (equalp (list 0 2)\n                  (array-dimensions res))))\n\n    (let ((res (get-non-alpha-pixels wand\n                                     :masks\n                                     (list\n                                      (make-instance 'simple-mask\n                                                     :left 0\n                                                     :top 0\n                                                     :width 11\n                                                     :height 19)))))\n      (is (equalp (list 1 2)\n                  (array-dimensions res))))))\n\n\n\n\n(test complex-case-from-prod\n  (with-fixture get-non-alpha (:width 1500 :height 55)\n    (let ((masks '((1243 22 9 26)\n                   (1244 45 39 10)\n                   (1280 18 8 31)\n                   (1432 20 5 23)\n                   (1479 21 8 21)\n                   (1316 41 28 14)\n                   (1437 52 46 10))))\n      (mark-pixel wand 1261 50)\n      (let ((res (get-non-alpha-pixels wand\n                                       :masks\n                                       (loop for mask in masks\n                                             collect\n                                             (make-instance 'simple-mask\n                                                            :left (first mask)\n                                                            :top (second mask)\n                                                            :width (third mask)\n                                                            :height (fourth mask))))))\n        (is (equalp #2A((10 19))\n                    res))))))\n\n(test in-place-comparison\n  (with-single-pixel-image (:wand wand :height 30 :width 30)\n    (with-single-pixel-image (:wand wand2 :height 50 :width 50)\n      (with-image-comparison (wand wand2 :in-place-p t\n                                         :same-p same-p)\n        (is-false same-p)))))\n\n(test in-place-comparison-reverse-order\n  (with-single-pixel-image (:wand wand2 :height 30 :width 30)\n    (with-single-pixel-image (:wand wand :height 50 :width 50)\n      (with-image-comparison (wand wand2 :in-place-p t\n                                         :same-p same-p)\n        (is-false same-p)))))\n\n(test resize-never-upsizes-webp\n  (with-single-pixel-image (:wand wand :height 10 :width 10)\n    (uiop:with-temporary-file (:pathname input :type \"webp\")\n      (save-as-webp wand input)\n      (uiop:with-temporary-file (:pathname dest :type \"webp\")\n        (resize-image input :output dest :size \"30x20\")\n        (with-wand (res :file dest)\n          (is (eql 10 (magick-get-image-height res)))\n          (is (eql 10 (magick-get-image-width res))))))))\n\n\n(test resize-never-upsizes-png\n  (with-single-pixel-image (:wand wand :height 10 :width 10)\n    (uiop:with-temporary-file (:pathname input :type \"png\")\n      (save-as-webp wand input)\n      (uiop:with-temporary-file (:pathname dest :type \"webp\")\n        (resize-image input :output dest :size \"30x20\")\n        (with-wand (res :file dest)\n          (is (eql 10 (magick-get-image-height res)))\n          (is (eql 10 (magick-get-image-width res))))))))\n\n(test scales-both-dimensions\n  (with-single-pixel-image (:wand wand :height 10 :width 30)\n    (uiop:with-temporary-file (:pathname input :type \"webp\")\n      (save-as-webp wand input)\n      (uiop:with-temporary-file (:pathname dest :type \"webp\")\n        (resize-image input :output dest :size \"5x5\")\n        (with-wand (res :file dest)\n          (is (eql 2 (magick-get-image-height res)))\n          (is (eql 5 (magick-get-image-width res))))))))\n\n(test identify-image\n  (with-fixture state ()\n    (with-wand (wand :file rose)\n      (assert-that (magick-identify-image wand)\n                   (matches-regex \"png:IHDR.bit_depth: 8\")))))\n\n(test magick-bad-exif-data ()\n  (with-fixture state ()\n    (with-wand (wand :file rose)\n      (is (fset:equal? (fset:empty-map)\n                       (magick-bad-exif-data wand))))\n    (with-wand (wand :file #. (fixture \"image-with-timestamp.png\"))\n      (is (fset:equal?\n           (fset:with\n            (fset:empty-map)\n            \"png:tIME\" \"2023-06-06T14:13:20Z\")\n           (magick-bad-exif-data wand))))))\n\n(test get-px-as-string\n  (with-single-pixel-image (:wand wand :height 20 :width 30 :default-mark t)\n    (is (equal \"srgb(10,0,0)\" (get-px-as-string wand 10 19)))\n    (is (equal \"srgba(0,0,0,0)\" (get-px-as-string wand 3 3)))))\n\n(test comparison-using-dist-preconditions\n  (with-single-pixel-image (:wand wand1 :height 20 :width 30)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 30)\n      (is (eql 0 (screenshotbot-inplace-compare-v2 wand1 wand2\n                                                   1))))))\n\n(test comparison-using-dist\n  (with-single-pixel-image (:wand wand1 :height 20 :width 30)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 30)\n      (mark-pixel wand1 3 4 :color \"srgb(30,10,12)\")\n      (mark-pixel wand2 3 4 :color \"srgb(31,9,13)\")\n      (is (eql 0 (screenshotbot-inplace-compare-v2 wand1 wand2\n                                                   #+screenshotbot-oss\n                                                   4\n                                                   #-screenshotbot-oss\n                                                   1))))))\n\n(test comparison-using-dist-but-with-zero-dist\n  (with-single-pixel-image (:wand wand1 :height 20 :width 30)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 30)\n      (mark-pixel wand1 3 4 :color \"srgb(30,10,12)\")\n      (mark-pixel wand2 3 4 :color \"srgb(31,9,13)\")\n      (is (eql 1 (screenshotbot-inplace-compare-v2 wand1 wand2\n                                                   0))))))\n\n(test comparison-using-dist-but-difference-size\n  (with-single-pixel-image (:wand wand1 :height 21 :width 30)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 30)\n      (mark-pixel wand1 3 4 :color \"srgb(30,10,12)\")\n      (mark-pixel wand2 3 4 :color \"srgb(31,9,13)\")\n      (is (eql 1 (screenshotbot-inplace-compare-v2 wand1 wand2\n                                                   1))))))\n\n(test comparison-using-dist-but-difference-size-reverse\n  (with-single-pixel-image (:wand wand1 :height 21 :width 30)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 30)\n      (mark-pixel wand1 3 4 :color \"srgb(30,10,12)\")\n      (mark-pixel wand2 3 4 :color \"srgb(31,9,13)\")\n      (is (eql 1 (screenshotbot-inplace-compare-v2 wand2 wand1\n                                                   1))))))\n\n(test comparison-using-dist-but-difference-width\n  (with-single-pixel-image (:wand wand1 :height 20 :width 31)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 30)\n      (mark-pixel wand1 3 4 :color \"srgb(30,10,12)\")\n      (mark-pixel wand2 3 4 :color \"srgb(31,9,13)\")\n      (is (eql 1 (screenshotbot-inplace-compare-v2 wand1 wand2\n                                                   1))))))\n\n(test compute-difference-rmse-simple-test\n  (with-single-pixel-image (:wand wand1 :height 20\n                            :default-color \"black\"\n                            :width 20)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 20\n                              :default-color \"black\"\n                              :default-mark t)\n      (let ((rmse (calculate-difference-rmse wand1 wand2)))\n        (is (<\n             (abs (- rmse (sqrt (/  (/ 100 (* 255 255)) (* 4 20 20)))))\n             0.0001))))))\n\n(test compute-difference-rmse-if-widths-are-difference\n  (with-single-pixel-image (:wand wand1 :height 20\n                            :width 21)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 20\n                              :default-mark t)\n      (let ((rmse (calculate-difference-rmse wand1 wand2)))\n        (is (< 0.999d0  rmse))))))\n\n(test compute-difference-rmse-if-heights-are-difference\n  (with-single-pixel-image (:wand wand1 :height 21\n                            :width 20)\n    (with-single-pixel-image (:wand wand2 :height 20 :width 20\n                              :default-mark t)\n      (let ((rmse (calculate-difference-rmse wand1 wand2)))\n        (is (< 0.999d0  rmse))))))\n"
  },
  {
    "path": "src/screenshotbot/mask-rect-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/mask-rect-api\n  (:use #:cl)\n  (:export\n   #:mask-rect-left\n   #:mask-rect-top\n   #:mask-rect-width\n   #:mask-rect-height))\n(in-package :screenshotbot/mask-rect-api)\n\n(defgeneric mask-rect-left (mask-rect))\n\n(defgeneric mask-rect-top (mask-rect))\n\n(defgeneric mask-rect-width (mask-rect))\n\n(defgeneric mask-rect-height (mask-rect))\n"
  },
  {
    "path": "src/screenshotbot/mcp/mcp.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/mcp/mcp\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:json\n                #:encode-json-to-string))\n(in-package :screenshotbot/mcp/mcp)\n\n(defun list-tools (id)\n  (encode-json-to-string\n   `((:jsonrpc . \"2.0\")\n     (:id . ,id)\n     (:result . ((:tools . (((:name . \"list_channels\")\n                             (:description . \"List all channels (projects) in Screenshotbot\")\n                             (:inputSchema . ((:type . \"object\")\n                                              (:properties . ())\n                                              (:required . ())))))))))))\n\n(defun list-resources (id)\n  (encode-json-to-string\n   `((:jsonrpc . \"2.0\")\n     (:id . ,id)\n     (:result . ((:resources . (((:uri . \"channel://list\")\n                                 (:name . \"channels\")\n                                 (:description . \"List of all channels (projects) in Screenshotbot\")\n                                 (:mimeType . \"application/json\")))))))))\n\n(defhandler (nil :uri \"/mcp\" :method :post) ()\n  (setf (hunchentoot:header-out :content-type) \"application/json\")\n  (let* ((request-body (hunchentoot:raw-post-data :force-text t))\n         (request-json (when request-body\n                         (cl-json:decode-json-from-string request-body)))\n         (method (cdr (assoc :method request-json)))\n         (id (cdr (assoc :id request-json))))\n    (log:info \"Got body: ~a\" request-body)\n    (cond\n      ((string= method \"initialize\")\n       (encode-json-to-string\n         `((:jsonrpc . \"2.0\")\n           (:id . ,id)\n           (:result . \n             ((:protocolVersion . \"2024-11-05\")\n              (:capabilities . \n                ((:tools . ((:listChanged . nil)))\n                 (:resources . ((:subscribe . nil)\n                               (:listChanged . nil)))\n                 (:prompts . ((:listChanged . nil)))\n                 (:logging . nil)))\n              (:serverInfo . \n                ((:name . \"Screenshotbot MCP Server\")\n                 (:version . \"1.0.0\"))))))))\n      \n      ((string= method \"tools/list\")\n       (list-tools id))\n      \n      ((string= method \"resources/list\")\n       (list-resources id))\n      \n      (t\n       (encode-json-to-string\n         `((:jsonrpc . \"2.0\")\n           (:id . ,id)\n           (:error . ((:code . -32601)\n                     (:message . \"Method not found\")))))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/mcp/screenshotbot.mcp.asd",
    "content": "(defsystem \"screenshotbot.mcp\"\n  :description \"Model Context Protocol integration for Screenshotbot\"\n  :author \"Arnold Noronha <arnold@screenshotbot.io>\"\n  :license \"Apache-2.0\"\n  :version \"0.1.0\"\n  :serial t\n  :depends-on (:screenshotbot\n               :alexandria\n               :cl-json\n               :dexador\n               :log4cl\n               :str)\n  :components ((:file \"mcp\")))\n\n(defsystem \"screenshotbot.mcp/tests\"\n  :description \"Tests for screenshotbot.mcp\"\n  :depends-on (:screenshotbot.mcp\n               :fiveam)\n  :components ())\n\n"
  },
  {
    "path": "src/screenshotbot/metrics.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/metrics\n  (:use #:cl\n        #:screenshotbot/model/company\n        #:screenshotbot/model/user\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/report)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:do-runs-for-company)\n  (:import-from #:serapeum/iter\n                #:collecting)\n  (:local-nicknames (#:roles #:auth/model/roles)))\n(in-package :screenshotbot/metrics)\n\n(defvar *company* #+nil(company-with-name \"\"))\n\n(defun number-of-members ()\n  (let ((results nil))\n    (loop for interval in (list 30 90) do\n      (flet ((metric (name val)\n               (setf (alexandria:assoc-value\n                      results\n                      (format nil \"~a-~a\" name interval) )\n                     val)))\n        (metric :num-users\n                (length (roles:users-for-company *company*)))\n        (let ((runs (collecting\n                      (do-runs-for-company (run *company*)\n                        (when (local-time:timestamp> (screenshotbot/user-api:created-at run) (local-time:timestamp- (local-time:now) interval :day))\n                          (collect run))))))\n          (metric :num-runs (length runs))\n\n          (let ((channels (remove-duplicates\n                           (mapcar #'recorder-run-channel runs))))\n            (metric :active-channels\n                    (length channels))\n            (metric :active-screenshots\n             (loop for channel in channels\n                   summing\n                   (length\n                    (remove-duplicates\n                     (loop for (branch . run) in (screenshotbot/model/channel::all-active-runs channel)\n                           appending\n                           (mapcar #'screenshotbot/model/screenshot:screenshot-name\n                                   (recorder-run-screenshots run))))))))\n          (let ((pr-runs (loop for run in runs\n                               if (pull-request-url run)\n                                 collect run)))\n            (metric :num-runs-with-pr\n                    (length pr-runs))\n            (let ((reports (loop for run in pr-runs\n                                 appending (reports-for-run run))))\n              (metric :reports (length reports))\n              (let ((reviewed-reports\n                      (loop for report in reports\n                            if (acceptable-state (report-acceptable report))\n                              collect report)))\n                (metric :reviewed-reports (length reviewed-reports))\n                (let ((reviewers\n                        (loop for report in reviewed-reports\n                              collect (acceptable-reviewer (report-acceptable report)))))\n                  (metric :active-reviewers (length (remove-duplicates reviewers)))\n\n                  (metric :top-reviewers (top-5 reviewers)))))))))\n    results))\n\n(defun top-5 (items)\n  (let ((count 100))\n   (let ((ht (make-hash-table)))\n     (dolist (item items)\n       (incf (gethash item ht 0)))\n     (let ((items (loop for key being the hash-keys of ht\n                        for count being the hash-values of ht\n                        collect (list key count))))\n       (setf items (sort items #'> :key #'second))\n       (cond\n         ((< (length items) count)\n          items)\n         (t\n          (subseq items 0 (1+ count))))))))\n"
  },
  {
    "path": "src/screenshotbot/microsoft-teams/audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/microsoft-teams/audit-log\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:screenshotbot/audit-log\n                #:base-audit-log\n                #:audit-log-error\n                #:audit-logs-for-company)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at)\n  (:export\n   #:teams-audit-log\n   #:post-to-workflow-audit-log\n   #:workflow-name\n   #:teams-audit-logs-for-company))\n(in-package :screenshotbot/microsoft-teams/audit-log)\n\n(defclass teams-audit-log (base-audit-log)\n  ()\n  (:metaclass persistent-class))\n\n(defclass post-to-workflow-audit-log (teams-audit-log)\n  ((workflow-name :initarg :workflow-name\n                  :reader workflow-name)\n   (text :initarg :text\n         :reader audit-log-text))\n  (:metaclass persistent-class))\n\n(defun teams-audit-logs-for-company (company)\n  (audit-logs-for-company company 'teams-audit-log))\n"
  },
  {
    "path": "src/screenshotbot/microsoft-teams/channel-card.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/microsoft-teams/channel-card\n  (:use #:cl)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page\n                #:confirmation-page)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name)\n  (:import-from #:screenshotbot/microsoft-teams/model\n                #:teams-workflow\n                #:teams-workflows-for-channel\n                #:workflow-name)\n  (:import-from #:screenshotbot/dashboard/channels\n                #:microsoft-teams-card)\n  (:import-from #:util/form-errors\n                #:with-error-builder)\n  (:import-from #:core/ui/mdi\n                #:mdi))\n(in-package :screenshotbot/microsoft-teams/channel-card)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(defmethod microsoft-teams-card (channel)\n  <div class= \"card mt-3\">\n    <div class= \"card-body\">\n      <h4 class= \"card-title mb-3\">\n        Microsoft Teams workflows\n      </h4>\n\n      ,(let ((workflows (fset:convert 'list (teams-workflows-for-channel channel))))\n         (cond\n           (workflows\n            <div>\n              <ul class= \"list-group mb-3\">\n                ,@(loop for workflow in workflows\n                        collect\n                        <li class= \"list-group-item d-flex justify-content-between align-items-center\">\n                          <span>,(workflow-name workflow)</span>\n                          <a href= (nibble () (delete-workflow channel workflow))\n                             class= \"btn btn-sm btn-danger\"\n                             title= \"Delete workflow\">\n                            <mdi name= \"delete\" /> Remove\n                          </a>\n                        </li>)\n              </ul>\n            </div>)\n           (t\n            <p class= \"text-muted\">No Teams workflows configured for this channel.</p>)))\n\n      <div class= \"d-flex gap-2\">\n        <a href= (nibble () (add-workflow channel)) class= \"btn btn-primary\">Add Workflow Webhook</a>\n      </div>\n    </div>\n  </div>)\n\n(defun add-workflow (channel)\n  <simple-card-page title= (format nil \"Add Workflow webhook for ~a\" (channel-name channel))\n                    form-action= (nibble (name url)\n                                    (add-workflow/post channel :name name :url url))\n                    >\n    <div class= \"card-header\">\n      <h5 class= \"card-title mb-0\">Add Workflow webhook for <tt>,(channel-name channel)</tt></h5>\n    </div>\n    <div class= \"\">\n      <div class= \"mb-3\">\n        <label for= \"name\" class= \"form-label\">Name</label>\n        <input type= \"text\" class= \"form-control\" id= \"name\" name= \"name\" required= \"true\" />\n      </div>\n      \n      <div class= \"form-group mb-3\">\n        <label for= \"url\" class= \"form-label\">Webhook URL</label>\n        <input type= \"url\" class= \"form-control\" id= \"url\" name= \"url\" required= \"true\" />\n      </div>\n    </div>\n    <div class= \"card-footer\">\n      <input class= \"btn btn-primary\" type= \"submit\" value= \"Add\" />\n    </div>\n  </simple-card-page>)\n\n(defun add-workflow/post-validated (channel &key name url)\n  (make-instance 'teams-workflow\n                 :name name\n                 :channel channel\n                 :webhook-url url)\n  (hex:safe-redirect\n   \"/channels/:id\" :id (bknr.datastore:store-object-id channel)))\n\n(defun add-workflow/post (channel &key name url)\n  (with-error-builder (:check check\n                       :form-builder (add-workflow channel)\n                       :form-args (:name name :url url)\n                       :errors errors\n                       :success (add-workflow/post-validated channel :name name :url url))\n    (check :name (>= (length name) 0)\n           \"Name can't be blank\")\n    (check :url (ignore-errors (quri:uri url))\n           \"Invalid URL\")\n    (check :url (str:ends-with-p \"powerplatform.com\" (quri:uri-host (quri:uri url)))\n           \"This doesn't look like a Microsoft teams URI\")))\n\n(defun delete-workflow (channel workflow)\n  (confirmation-page\n   :yes (nibble ()\n          (bknr.datastore:delete-object workflow)\n          (hex:safe-redirect\n           \"/channels/:id\" :id (bknr.datastore:store-object-id channel)))\n   :no (format nil \"/channels/~a\" (bknr.datastore:store-object-id channel))\n   <div>\n     <p>Are you sure you want to delete the workflow <strong>,(workflow-name workflow)</strong>?</p>\n   </div>))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/microsoft-teams/model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/microsoft-teams/model\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:export\n   #:teams-workflows-for-channel\n   #:workflow-name\n   #:webhook-url))\n(in-package :screenshotbot/microsoft-teams/model)\n\n\n(defindex +channel-index+\n  'fset-set-index\n  :slot-name '%channel)\n\n(with-class-validation\n  (defclass teams-workflow (store-object)\n    ((%name :initarg :name\n            :reader workflow-name)\n     (%webhook-url :initarg :webhook-url\n                   :reader webhook-url)\n     (%channel :initarg :channel\n               :index +channel-index+\n               :index-reader teams-workflows-for-channel))\n    (:metaclass persistent-class)))\n"
  },
  {
    "path": "src/screenshotbot/microsoft-teams/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/microsoft-teams/settings\n  (:use #:cl)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:screenshotbot/settings-api\n                #:defsettings\n                #:settings-template)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:render-audit-logs\n                #:describe-audit-log)\n  (:import-from #:screenshotbot/microsoft-teams/audit-log\n                #:teams-audit-log\n                #:post-to-workflow-audit-log\n                #:workflow-name))\n(in-package :screenshotbot/microsoft-teams/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun get-settings-teams ()\n  <settings-template>\n    <div class= \"card mt-3\">\n      <div class= \"card-header\">\n        <h3>Microsoft Teams Integration</h3>\n      </div>\n      <div class= \"card-body\">\n        <p class= \"text-muted\">\n          Microsoft Teams notifications are configured per channel. Visit any channel's settings page to add Teams workflow webhooks.\n        </p>\n        <p>\n          <a href= \"/channels\" class= \"btn btn-primary\">Go to Channels</a>\n        </p>\n      </div>\n    </div>\n\n    <audit-logs />\n  </settings-template>)\n\n(deftag audit-logs ()\n  (render-audit-logs\n   :type 'teams-audit-log\n   :company (current-company)\n   :subtitle \"All webhook calls to Microsoft Teams made by Screenshotbot in the last 30 days will be listed here\"))\n\n(defmethod describe-audit-log ((self post-to-workflow-audit-log))\n  (let ((err (screenshotbot/audit-log:audit-log-error self)))\n    (cond\n      (err\n       <span>Failed to post to workflow <tt>,(workflow-name self)</tt>: ,(progn err)</span>)\n      (t\n       <span>Posted to workflow <tt>,(workflow-name self)</tt></span>))))\n\n(defsettings teams\n  :name \"teams\"\n  :title \"Microsoft Teams\"\n  :section :tasks\n  :handler 'get-settings-teams)\n"
  },
  {
    "path": "src/screenshotbot/microsoft-teams/task-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/microsoft-teams/task-integration\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/task-integration-api\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/report\n        #:screenshotbot/model/company\n        #:screenshotbot/model/recorder-run\n        #:anaphora)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:report-link)\n  (:import-from #:screenshotbot/microsoft-teams/teams-api\n                #:make-adaptive-card\n                #:teams-post-adaptive-card\n                #:teams-error)\n  (:import-from #:screenshotbot/microsoft-teams/model\n                #:teams-workflows-for-channel\n                #:webhook-url\n                #:workflow-name\n                #:teams-workflow)\n  (:import-from #:screenshotbot/microsoft-teams/audit-log\n                #:post-to-workflow-audit-log)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-teams-workflows)\n  (:import-from #:screenshotbot/model/company\n                #:default-teams-config)\n  (:import-from #:screenshotbot/model/report\n                #:report-channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-tags\n                #:recorder-run-work-branch)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/audit-log\n                #:with-audit-log))\n(in-package :screenshotbot/microsoft-teams/task-integration)\n\n(defclass teams-task-integration (task-integration)\n  ())\n\n(register-task-integration 'teams-task-integration)\n\n(defmethod enabledp ((inst teams-task-integration))\n  ;; Always enabled since there's no global configuration required\n  t)\n\n(defun render-tags (report)\n  (let ((tags (recorder-run-tags (report-run report))))\n   (cond\n     (tags\n      (format nil \" (from run with tags ~a)\" (str:join \", \" tags)))\n     (t\n      \"\"))))\n\n(defun render-text (report)\n  \"Render the notification text for a Teams message, similar to Slack format.\"\n  (let ((run (report-run report))\n        (first-line (format nil \"Screenshots changed in **~a**~a\"\n                           (channel-name (report-channel report))\n                           (render-tags report)))\n        (second-line (format nil \"[~a](~a)\"\n                             (report-title report)\n                             (report-link report))))\n    (format nil \"~a~%~a\"\n            (cond\n              ((?. recorder-run-work-branch run)\n               (format nil \"~a on **~a**\" first-line (recorder-run-work-branch run)))\n              (t\n               first-line))\n            second-line)))\n\n(defmethod send-task ((inst teams-task-integration) report)\n  (let ((company (task-integration-company inst)))\n    (assert (enabledp inst))\n    (let ((card-payload (make-adaptive-card :text (render-text report)))\n          (text (render-text report)))\n      (let ((workflows (teams-workflows-for-channel (report-channel report))))\n        (fset:do-set (workflow workflows)\n          (with-audit-log (audit-log (make-instance 'post-to-workflow-audit-log\n                                                    :company company\n                                                    :workflow-name (workflow-name workflow)\n                                                    :text text))\n            (declare (ignore audit-log))\n            (teams-post-adaptive-card\n             :webhook-url (webhook-url workflow)\n             :card-payload card-payload)))))))\n"
  },
  {
    "path": "src/screenshotbot/microsoft-teams/teams-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/microsoft-teams/teams-api\n  (:use #:cl\n        #:alexandria)\n  (:export\n   #:teams-post-adaptive-card\n   #:make-adaptive-card\n   #:teams-error\n   #:teams-error-response))\n(in-package :screenshotbot/microsoft-teams/teams-api)\n\n(define-condition teams-error (error)\n  ((response :initarg :response\n             :reader teams-error-response))\n  (:report (lambda (condition stream)\n             (format stream \"Teams API error: ~a\" (teams-error-response condition)))))\n\n(defun make-adaptive-card (&key text)\n  \"Create a simple Adaptive Card structure with the given text.\"\n  (list\n   (cons :type \"message\")\n   (cons :attachments\n         (vector\n          (alexandria:alist-hash-table\n           `((:content-type . \"application/vnd.microsoft.card.adaptive\")\n             (:content . ((:$schema . \"http://adaptivecards.io/schemas/adaptive-card.json\")\n                          (:type . \"AdaptiveCard\")\n                          (:version . \"1.4\")\n                          (:body . ,(vector\n                                     (alexandria:alist-hash-table\n                                      `((:type . \"TextBlock\")\n                                        (:text . ,text)\n                                        (:wrap . t)))))))))))))\n\n(defun teams-post-adaptive-card (&key webhook-url card-payload)\n  \"Post an Adaptive Card to a Microsoft Teams channel via webhook.\n\n   WEBHOOK-URL should be the full Power Automate workflow webhook URL\n   (including the sig parameter).\n\n   CARD-PAYLOAD should be an alist representing the Adaptive Card structure.\n   You can use MAKE-ADAPTIVE-CARD for simple cards, or construct your own.\"\n  (let ((content (json:encode-json-to-string card-payload)))\n   (handler-case\n       (multiple-value-bind (body status-code)\n           (util/request:http-request\n            webhook-url\n            :method :post\n            :content-type \"application/json\"\n            :content content\n            :want-string t\n            :preserve-uri t)\n         (unless (member status-code '(200 202))\n           (error 'teams-error :response (format nil \"HTTP ~a: ~a\" status-code body)))\n         (values body status-code))\n     (error (e)\n       (error 'teams-error :response (format nil \"~a\" e))))))\n"
  },
  {
    "path": "src/screenshotbot/migrations/report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/migrations/report\n  (:use #:cl)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:bknr.datastore\n                #:with-transaction\n                #:class-instances)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:company-reports)\n  (:import-from #:screenshotbot/model/report\n                #:%report-company\n                #:promotion-report-p)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:screenshotbot/report-api\n                #:report-run\n                #:report)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-company\n                #:recorder-run))\n(in-package :screenshotbot/migrations/report)\n\n(def-store-migration (\"Update promotion-report-p\" :version 3)\n  (ensure-slot-boundp 'report 'promotion-report-p)\n  (dolist (company (class-instances 'company))\n    (log:info \"Updating reports for ~s\" company)\n    (dolist (report (company-reports company))\n      (with-transaction ()\n        (setf (slot-value report 'promotion-report-p) t)))))\n\n(def-store-migration (\"Add company slot to reports\" :version 4)\n  (ensure-slot-boundp 'recorder-run 'screenshotbot/model/recorder-run::company)\n  (dolist (report (class-instances 'report))\n    (log:info \"Fixing report: ~a\" report)\n    (with-transaction ()\n      (setf\n       ;; TODO: replace this with report-company\n       (%report-company report)\n       (recorder-run-company\n        (report-run report))))))\n"
  },
  {
    "path": "src/screenshotbot/migrations/screenshotbot.migrations.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :screenshotbot.migrations\n  :serial t\n  :depends-on (:screenshotbot\n               :util.store\n               :log4cl)\n  :components ((:file \"report\")))\n"
  },
  {
    "path": "src/screenshotbot/model/archived-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/archived-run\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:bknr.indices\n                #:base-indexed-object)\n  (:import-from #:screenshotbot/api/model\n                #:metadata-key\n                #:metadata-value\n                #:metadata\n                #:encode-json\n                #:decode-json)\n  (:import-from #:util/store/object-id\n                #:find-by-oid\n                #:oid\n                #:oid-array)\n  (:import-from #:util/store\n                #:location-for-oid)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:compare-tolerance\n                #:recorder-run-metadata\n                #:recorder-run-batch\n                #:compare-threshold\n                #:periodic-job-p\n                #:trunkp\n                #:run-build-url\n                #:gitlab-merge-request-iid\n                #:phabricator-diff-id\n                #:recorder-run-merge-base\n                #:recorder-run-author\n                #:recorder-run-work-branch\n                #:override-commit-hash\n                #:github-repo\n                #:recorder-run-branch\n                #:recorder-run-warnings\n                #:recorder-run-tags\n                #:run-screenshot-map\n                #:bknr-or-archived-run-mixin\n                #:recorder-run-company)\n  (:import-from #:screenshotbot/user-api\n                #:activep\n                #:pull-request-url\n                #:recorder-run-commit\n                #:%created-at\n                #:recorder-run-channel)\n  (:import-from #:bknr.datastore\n                #:store-object-with-id)\n  (:export\n   #:archived-run\n   #:archived-run-channel\n   #:archived-run-company\n   #:archived-run-commit\n   #:archived-run-build-url\n   #:archived-run-github-repo\n   #:archived-run-cleanp\n   #:archived-run-activep\n   #:archived-run-branch\n   #:archived-run-work-branch\n   #:archived-run-release-branch-p\n   #:archived-run-branch-hash\n   #:archived-run-merge-base\n   #:archived-run-pull-request-url\n   #:archived-run-gitlab-merge-request-iid\n   #:archived-run-phabricator-diff-id\n   #:archived-run-previous-run\n   #:archived-run-create-github-issue-p\n   #:archived-run-trunkp\n   #:archived-run-periodic-job-p\n   #:archived-run-screenshots\n   #:archived-run-promotion-complete-p\n   #:archived-run-override-commit-hash\n   #:archived-run-compare-threshold\n   #:archived-run-compare-tolerance\n   #:archived-run-created-at\n   #:archived-run-tags\n   #:archived-run-batch\n   #:archived-run-group-separator\n   #:archived-run-was-promoted-p\n   #:archived-run-author\n   #:archived-run-metadata\n   #:save-archived-run\n   #:load-archived-run))\n(in-package :screenshotbot/model/archived-run)\n\n\n(defclass archived-run (base-indexed-object\n                        bknr-or-archived-run-mixin)\n  ((channel\n    :initarg :channel\n    :initform nil\n    :json-key \"channel\"\n    :json-type (or null :number)\n    :accessor archived-run-channel\n    :documentation \"The channel this run belongs to\")\n   (oid\n    :initarg :oid\n    :initform nil\n    :json-key \"oid\"\n    :json-type (or null :string)\n    :accessor %oid)\n   (bknr-id\n    :initarg :bknr-id\n    :initform nil\n    :json-key \"bknrId\"\n    :json-type (or null :number)\n    :documentation \"The original bknr datastore id for this object\")\n   (company\n    :initarg :company\n    :initform nil\n    :json-key \"company\"\n    :json-type (or null :string)\n    :accessor archived-run-company\n    :documentation \"The company this run belongs to\")\n   (commit-hash\n    :initarg :commit-hash\n    :initform nil\n    :json-key \"commitHash\"\n    :json-type (or null :string)\n    :accessor archived-run-commit\n    :reader recorder-run-commit)\n   (build-url\n    :initform nil\n    :initarg :build-url\n    :json-key \"buildUrl\"\n    :json-type (or null :string)\n    :accessor archived-run-build-url\n    :reader run-build-url)\n   (github-repo\n    :initform nil\n    :initarg :github-repo\n    :json-key \"githubRepo\"\n    :json-type (or null :string)\n    :accessor archived-run-github-repo\n    :reader github-repo)\n   (cleanp\n    :initarg :cleanp\n    :initform nil\n    :json-key \"cleanp\"\n    :json-type (or null :bool)\n    :accessor archived-run-cleanp)\n   (activep\n    :initarg :activep\n    :initform nil\n    :json-key \"activep\"\n    :json-type (or null :bool)\n    :reader activep\n    :accessor archived-run-activep)\n   (branch\n    :initarg :branch\n    :initform nil\n    :json-key \"branch\"\n    :json-type (or null :string)\n    :accessor archived-run-branch\n    :reader recorder-run-branch\n    :documentation \"The main branch. This is not the branch associated with the\n    current pull request!\")\n   (work-branch\n    :initarg :work-branch\n    :initform nil\n    :json-key \"workBranch\"\n    :json-type (or null :string)\n    :accessor archived-run-work-branch\n    :reader recorder-run-work-branch\n    :documentation \"The branch we're currently working on, which might\n    be the same as the main branch, or it might be the current pull\n    request branch\")\n   (release-branch-p\n    :initarg :release-branch-p\n    :initform nil\n    :json-key \"releaseBranchP\"\n    :json-type (or null :bool)\n    :accessor archived-run-release-branch-p\n    :documentation \"Whether the work-branch is a release branch\")\n   (branch-hash\n    :initarg :branch-hash\n    :initform nil\n    :json-key \"branchHash\"\n    :json-type (or null :string)\n    :accessor archived-run-branch-hash\n    :documentation \"If a --branch is provided, this is the sha of the\n    specified branch at the time of run. This might be different from\n    the COMMIT-HASH, because the COMMIT-HASH might on, say a Pull\n    Request (tied to the branch) or an ancestor of the branch.\")\n   (merge-base-hash\n    :initform nil\n    :initarg :merge-base\n    :json-key \"mergeBase\"\n    :json-type (or null :string)\n    :accessor archived-run-merge-base\n    :reader recorder-run-merge-base\n    :documentation \"The merge base between branch-hash and commit-hash\")\n   (pull-request\n    :initarg :pull-request\n    :initform nil\n    :json-key \"pullRequest\"\n    :json-type (or null :string)\n    :reader pull-request-url\n    :accessor archived-run-pull-request-url)\n   (gitlab-merge-request-iid\n    :initarg :gitlab-merge-request-iid\n    :initform nil\n    :json-key \"gitlabMergeRequestIid\"\n    :json-type (or null :string)\n    :accessor archived-run-gitlab-merge-request-iid\n    :reader gitlab-merge-request-iid)\n   (phabricator-diff-id\n    :initarg :phabricator-diff-id\n    :initform nil\n    :json-key \"phabricatorDiffId\"\n    :json-type (or null :string)\n    :reader phabricator-diff-id\n    :accessor archived-run-phabricator-diff-id)\n   (previous-run\n    :initform nil\n    :initarg :previous-run\n    :json-key \"previousRun\"\n    :json-type (or null :string)\n    :accessor archived-run-previous-run\n    :documentation \"The previous *ACTIVE* run. Unpromoted runs aren't tracked on the channel, because often the reason it's unpromoted means that we don't understand if it belongs to the channel. If there are multile previous-runs for different branches, this will always point to the previous run on master branch.\")\n   (create-github-issue-p\n    :initform nil\n    :initarg :create-github-issue-p\n    :json-key \"createGithubIssueP\"\n    :json-type (or null :bool)\n    :accessor archived-run-create-github-issue-p)\n   (trunkp\n    :initarg :trunkp\n    :initform nil\n    :json-key \"trunkp\"\n    :json-type (or null :bool)\n    :accessor archived-run-trunkp\n    :reader trunkp\n    :documentation \"whether this is tracking a production branch (as opposed to dev)\")\n   (periodic-job-p\n    :initarg :periodic-job-p\n    :initform nil\n    :json-key \"periodicJobP\"\n    :json-type (or null :bool)\n    :accessor archived-run-periodic-job-p\n    :reader periodic-job-p\n    :documentation \"Jobs that are done periodically, as opposed to for\n    each commit. We will attempt to promote each run. This is mostly\n    for taking screenshots of public websites.\")\n   (screenshot-map\n    :initarg :screenshots\n    :initform nil\n    :json-key \"screenshotMap\"\n    :json-type (or null :number)\n    :accessor archived-run-screenshots\n    :documentation \"The list of screenshot names\")\n   (promotion-complete-p\n    :initform nil\n    :json-key \"promotionCompleteP\"\n    :json-type (or null :bool)\n    :accessor archived-run-promotion-complete-p)\n   (override-commit-hash\n    :initform nil\n    :initarg :override-commit-hash\n    :json-key \"overrideCommitHash\"\n    :json-type (or null :string)\n    :accessor archived-run-override-commit-hash\n    :reader override-commit-hash\n    :documentation \"Override the pull request commit hash that will be\n    used to update the Pull Request (either GitHub or Bitbucket)\")\n   (compare-threshold\n    :initform nil\n    :initarg :compare-threshold\n    :json-key \"compareThreshold\"\n    :json-type (or null :number)\n    :accessor archived-run-compare-threshold\n    :reader compare-threshold\n    :documentation \"The comparison threshold in terms of fraction of pixels changed. If\nNIL or 0, this will use exact pixel comparisons.\")\n   (compare-tolerance\n    :initarg :compare-tolerance\n    :initform nil\n    :json-key \"compareTolerance\"\n    :json-type (or null :number)\n    :accessor compare-tolerance\n    :accessor archived-run-compare-tolerance)\n   (created-at\n    :initform nil\n    :initarg :created-at\n    :json-key \"createdAt\"\n    :json-type (or null :number)\n    :accessor archived-run-created-at\n    :reader %created-at)\n   (tags\n    :initarg :tags\n    :initform nil\n    :json-key \"tags\"\n    :json-type (or null :string)\n    :accessor archived-run-tags\n    :reader recorder-run-tags)\n   (batch\n    :initform nil\n    :initarg :batch\n    :json-key \"batch\"\n    :json-type (or null :string)\n    :accessor archived-run-batch\n    :reader recorder-run-batch\n    :documentation \"The batch object associated with this run\")\n   (group-separator\n    :initarg :group-separator\n    :initform nil\n    :json-key \"groupSeparator\"\n    :json-type (or null :string)\n    :accessor archived-run-group-separator)\n   (was-promoted-p\n    :initarg :was-promoted-p\n    :initform nil\n    :json-key \"wasPromotedP\"\n    :json-type (or null :bool)\n    :accessor archived-run-was-promoted-p\n    :documentation \"Whether this was ever set as an active run for the run's channel,branch.\")\n   (author\n    :initarg :author\n    :initform nil\n    :json-key \"author\"\n    :json-type (or null :string)\n    :accessor archived-run-author\n    :reader recorder-run-author\n    :documentation \"The author, or owner of this run. This will be used for logic to ensure that authors cannot review their own runs. See T1056.\")\n   (metadata\n    :initarg :metadata\n    :initform nil\n    :json-key \"metadata\"\n    :json-type (:list metadata)\n    :accessor archived-run-metadata\n    :documentation \"A list of metadata objects with key-value pairs.\"))\n  (:metaclass ext-json-serializable-class)\n  (:documentation \"An archived recorder run with minimal information, stored as JSON.\n  This represents an old, finalized run that will be rarely directly referenced.\"))\n\n(defmethod oid ((run archived-run) &key (stringp t))\n  \"Returns the oid of the archived-run. If :STRINGP is T, then we return\nthe oid as a string. Otherwise we return the oid as stored.\"\n  (let ((oid (%oid run)))\n    (cond\n      ((and stringp oid)\n       oid)\n      (t\n       oid))))\n\n(defun archived-run-location-for-oid (oid)\n  \"Figure out the local location for the given OID for an archived run\"\n  (location-for-oid\n   #P\"archived-runs/\"\n   oid\n   :type \"json\"))\n\n(defun save-archived-run (run)\n  \"Save an archived-run to disk as JSON using its OID to determine the location.\"\n  (let* ((oid-str (oid run :stringp t))\n         (oid-bytes (mongoid:oid oid-str))\n         (file-path (archived-run-location-for-oid oid-bytes))\n         (json-string (encode-json run)))\n    (with-open-file (stream file-path\n                            :direction :output\n                            :if-exists :supersede\n                            :if-does-not-exist :create)\n      (write-string json-string stream))\n    file-path))\n\n(defun load-archived-run (oid)\n  \"Load an archived-run from disk using its OID.\n   OID can be a string, array, or OID struct.\"\n  (let* ((oid-bytes (etypecase oid\n                      (string (mongoid:oid oid))\n                      ((vector (unsigned-byte 8)) oid)\n                      (util/store/object-id::oid\n                       (util/store/object-id::oid-arr oid))))\n         (file-path (archived-run-location-for-oid oid-bytes)))\n    (when (probe-file file-path)\n     (with-open-file (stream file-path\n                             :direction :input)\n       (let ((json-string (with-output-to-string (out)\n                            (loop for line = (read-line stream nil nil)\n                                  while line\n                                  do (write-line line out)))))\n         (decode-json json-string 'archived-run))))))\n\n(defmethod recorder-run-company ((self archived-run))\n  (find-by-oid (archived-run-company self) '%c:company))\n\n(defmethod recorder-run-channel ((self archived-run))\n  (store-object-with-id (archived-run-channel self)))\n\n(defmethod run-screenshot-map ((self archived-run))\n  (store-object-with-id (archived-run-screenshots self)))\n\n\n(defmethod recorder-run-warnings ((self archived-run))\n  nil)\n\n(defmethod recorder-run-metadata ((self archived-run))\n  (loop for entry in (archived-run-metadata self)\n        collect (cons\n                 (metadata-key entry)\n                 (metadata-value entry))))\n"
  },
  {
    "path": "src/screenshotbot/model/auto-cleanup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/auto-cleanup\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:register-auto-cleanup))\n(in-package :screenshotbot/model/auto-cleanup)\n\n(defvar *cleanups* nil)\n\n(defclass cleanup ()\n  ((type :initarg :type\n         :reader cleanup-type)\n   (timestamp :initarg :timestamp\n              :reader cleanup-timestamp)\n   (age :initarg :age\n        :reader cleanup-age)))\n\n(defun register-auto-cleanup (obj &key (timestamp (error \"must provide timestamp function\"))\n                                    (age 30))\n  (setf (a:assoc-value *cleanups* obj)\n        (make-instance 'cleanup\n                       :type obj\n                       :timestamp timestamp\n                       :age age)))\n\n(defun process-cleanup (cleanup)\n  (let ((threshold (- (get-universal-time) (* 24 3600 (cleanup-age cleanup))))\n        (objs (class-instances (cleanup-type cleanup))))\n    (dolist (obj objs)\n      (when (< (funcall (cleanup-timestamp cleanup) obj)\n               threshold)\n        (log:info \"Auto-deleting: ~s\" obj)\n        (delete-object obj)))))\n\n(defun dispatch-cleanups ()\n  (loop for (nil . cleanup) in *cleanups* do\n    (process-cleanup cleanup)))\n\n(def-cron dispatch-cleanups (:minute 23 :hour 1)\n  (dispatch-cleanups))\n"
  },
  {
    "path": "src/screenshotbot/model/batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/batch\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/store\n                #:nsort-store-objects\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index\n                #:fset-unique-index)\n  (:import-from #:bknr.indices\n                #:should-index-objects-for-class-p\n                #:hash-index\n                #:index-get)\n  (:import-from #:util/store/object-id\n                #:oid-array\n                #:find-by-oid\n                #:object-with-oid)\n  (:import-from #:screenshotbot/user-api\n                #:can-view)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:transient-promotion-log\n                #:promotion-log\n                #:recorder-run-repo-url\n                #:phabricator-diff-id\n                #:pull-request-url\n                #:github-repo\n                #:recorder-run-company)\n  (:import-from #:auth/viewer-context\n                #:logged-in-viewer-context)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:util/store/simple-object-snapshot\n                #:simple-object-snapshot)\n  (:import-from #:util/store/store-version\n                #:*snapshot-store-version*)\n  (:export\n   #:find-or-create-batch\n   #:batch-items\n   #:batch-item-channel\n   #:batch-name\n   #:batch-item-run\n   #:batch-item-report\n   #:batch-commit\n   #:batch-item-status\n   #:batch-item-title\n   #:lock\n   #:push-lock\n   #:state-invalidated-p\n   #:finalize-batch\n   #:batch))\n(in-package :screenshotbot/model/batch)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defun accessors ()\n    '(company repo name phabricator-diff-id\n      pull-request-url)))\n\n\n(defindex +lookup-index-v4+\n  'fset-set-index\n  :slot-name '%commit)\n\n;; Note that for most of the fields, we create an alias for the slot\n;; readers to use the same slots as recorder-run as a convenience.\n(with-class-validation\n  (defclass batch (object-with-oid)\n    ((%company :initarg :company\n               :reader company\n               :reader recorder-run-company)\n     (%repo :initarg :repo\n            :reader repo\n            :reader github-repo\n            :reader recorder-run-repo-url)\n     (%commit :initarg :commit\n              :index +lookup-index-v4+\n              :index-reader batches-for-commit\n              ;; Note: we don't need recorder-run-commit here because\n              ;; a promoter should not be using that anyway. Promoters\n              ;; should instead use CHECK-SHA.\n              :reader batch-commit\n              :reader commit)\n     (%name :initarg :name\n            :reader batch-name)\n     (%phabricator-diff-id :initarg :phabricator-diff-id\n                           :initform nil\n                           :reader phabricator-diff-id)\n     (%pull-request-url :initarg :pull-request-url\n                        :initform nil\n                        :reader pull-request-url)\n     (lock :initform (bt:make-lock \"batch-lock\")\n           :transient t\n           :reader lock\n           :documentation \"A lock to make sure that \")\n     (push-lock :initform (bt:make-lock \"batch-push-lock\")\n                :transient t\n                :reader push-lock\n                :documentation \"A lock to make sure only one promoter is updating the PR.\")\n     (state-invalidated-p :initform t\n                          :transient t\n                          :accessor state-invalidated-p\n                          :documentation \"The current state was invalidated since the last push to remote (GitHub/Bitbucket etc.).\"))\n    (:metaclass persistent-class)))\n\n(defmethod name (batch)\n  ;; just convenience for some of the macro-expansions\n  (batch-name batch))\n\n(defindex +batch-item-index+\n  'hash-index\n  :slot-name 'batch)\n\n(with-class-validation\n  (defclass batch-item (store-object)\n    ((batch :initarg :batch\n            :reader batch\n            :index +batch-item-index+\n            :index-reader %batch-items)\n     (%status :initarg :status\n              :initform nil\n              :accessor batch-item-status)\n     (%channel :initarg :channel\n               :initform nil\n               :accessor batch-item-channel)\n     (%acceptable :initarg :acceptable\n                  :initform nil\n                  :accessor acceptable\n                  :documentation \"This slot isn't actually being used (AFAICT)\")\n     (%title :initarg :title\n             :initform \"\"\n             :accessor batch-item-title)\n     (%run :initarg :run\n           :initform nil\n           :accessor batch-item-run)\n     (%report :initarg :report\n              :initform nil\n              :accessor batch-item-report)\n     ;; Oops, accident. Leaving it around to be safe. Can be cleared\n     ;; in the future before a restart.\n     (lock :transient t)\n     (push-lock :transient t))\n    (:metaclass persistent-class)))\n\n(defun batch-items (batch)\n  \"In the previous version of the index, we used an fset-index.\n\nWhen reloading this code, the index isn't recreated. When rebooting,\nit will recreate with with a hash-index.\n\nThis is for compatibility with the rest of the\ncode.\"\n  (fset:convert 'fset:set\n                (%batch-items batch)))\n\n(defmethod bknr.datastore:make-object-snapshot ((self batch-item))\n  (when (>= *snapshot-store-version* 23)\n    (make-instance 'simple-object-snapshot\n                   :object self\n                   :except-slots '(%status\n                                   %title\n                                   %run\n                                   %report))))\n\n(defvar *lock* (bt:make-lock))\n\n(defun find-existing-batch #.`(&key commit\n                                    ,@(accessors))\n  (fset:do-set (batch (batches-for-commit commit))\n    (when #.`(and\n              ,@(loop for key in (accessors)\n                      collect `(equal ,key (,key batch))))\n          (return batch))))\n\n(defun find-batches-for-commit (&key commit company)\n  (fset:convert\n   'list\n   (fset:filter\n    (lambda (batch)\n      (eql (company batch) company))\n    (batches-for-commit commit))))\n\n(defun find-or-create-batch (&rest args\n                             &key &allow-other-keys)\n  (bt:with-lock-held (*lock*)\n    (or\n     (apply #'find-existing-batch args)\n     (apply #'make-instance 'batch\n            args))))\n\n\n(defun find-batch-item (batch &key channel)\n  (fset:do-set (item (batch-items batch))\n    (when (eql (batch-item-channel item) channel)\n      (return item))))\n\n(defmethod auth:can-view ((self batch) user)\n  (auth:can-view-with-normal-viewer-context\n   user self))\n\n(defmethod auth:can-viewer-view ((vc logged-in-viewer-context)\n                                 (self batch))\n  (auth:can-viewer-view vc (company self)))\n\n(defgeneric finalize-batch (batch)\n  (:documentation \"Called when the batch is finalized, typically because the commit is finalized. See implementation in batch-promoter\"))\n\n(defmethod promotion-log ((batch batch))\n  (make-instance 'transient-promotion-log\n                 :oid-array (oid-array batch)))\n\n(def-store-migration (\"Ensure some slots are bound\" :version 23)\n  (ensure-slot-boundp 'batch-item '%status)\n  (ensure-slot-boundp 'batch-item '%run))\n\n(defmethod should-index-objects-for-class-p ((self batch-item))\n  nil)\n\n(defmethod class-instances ((class (eql (find-class 'batch-item))))\n  (nsort-store-objects\n   (loop for obj in (bknr.datastore:all-store-objects)\n         if (typep obj 'batch-item)\n           collect obj)))\n"
  },
  {
    "path": "src/screenshotbot/model/build-info.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/build-info\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:with-class-validation\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index\n                #:fset-set-index)\n  (:import-from #:bknr.indices\n                #:index-get))\n(in-package :screenshotbot/model/build-info)\n\n\n(defindex +build-url-index-v2+\n  'fset-unique-index\n  :slots '(company build-url))\n\n(unintern '+build-url-index+)\n\n(defvar *lock* (bt:make-lock))\n\n\n(with-class-validation\n  (defclass build-info (store-object)\n    ((build-url :initarg :build-url\n               :reader build-url)\n     (company :initarg :company)\n     (repo-url :initarg :repo-url\n               :accessor build-info-repo-url))\n    (:metaclass persistent-class)\n    (:class-indices (company-build-url-index\n                     :index +build-url-index-v2+))\n    (:documentation \"On some CI systems (Xcode cloud!) we might need to store some\ninformation across steps, because it might not be available in a\nfuture step. This is just a way of storing that information.\")))\n\n(defun find-build-info (company build-url)\n  (index-get +build-url-index-v2+ (list company build-url)))\n\n(defun find-or-create-build-info (company build-url)\n  (bt:with-lock-held (*lock*)\n    (or\n     (find-build-info company build-url)\n     (make-instance 'build-info :build-url build-url :company company))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/model/channel.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/channel\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/core\n        #:screenshotbot/user-api\n        #:screenshotbot/model/view)\n  (:import-from #:screenshotbot/git-repo\n                #:null-repo\n                #:generic-git-repo)\n  (:import-from #:screenshotbot/plugin\n                #:plugin-parse-repo)\n  (:import-from #:screenshotbot/installation\n                #:plugins\n                #:installation)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object\n                #:store-objects-with-class\n                #:store-object-id\n                #:hash-index\n                #:deftransaction)\n  (:import-from #:screenshotbot/model/image\n                #:masks)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:trunkp\n                #:was-promoted-p\n                #:unchanged-run-other-commit\n                #:unchanged-run-for-commit\n                #:unchanged-runs-for-commit\n                #:recorder-run\n                #:recorder-run-commit\n                #:master-branch\n                #:active-run\n                #:recorder-previous-run\n                #:channel-runs\n                #:github-repo\n                #:activep\n                #:publicp\n                #:recorder-run-channel)\n  (:import-from #:screenshotbot/model/company\n                #:company\n                #:company-runs)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/model/review-policy\n                #:disallow-author-review-policy\n                #:anyone-can-review)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:auth/viewer-context\n                #:viewer-context-api-key\n                #:api-viewer-context)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key-company)\n  (:import-from #:util/store/slot-subsystem\n                #:externalized-slot-value)\n  (:export\n   #:channel\n   #:set-channel-screenshot-mask\n   #:channel-branch\n   #:channel-promoted-runs\n   #:channel-name\n   #:channel-runs\n   #:channel-repo\n   #:channel-promotion-lock\n   #:production-run-for\n   #:*channel-repo-overrides*\n   #:masks\n   #:channel-cv\n   #:channel-lock\n   #:all-active-runs\n   #:channel-subscribers\n   #:commit-to-run-map\n   #:channel-slack-channels\n   #:review-policy-name\n   #:channel-deleted-p\n   #:shortened-channel-name)\n\n  ;; forward decls\n  (:export\n   #:make-gitlab-repo\n   #:github-get-canonical-repo)\n  (:local-nicknames (#:recorder-run #:screenshotbot/model/recorder-run)))\n(in-package :screenshotbot/model/channel)\n\n\n;; hack. While promoting we want to temporarily give the channel a new\n;; repo link\n(defvar *channel-repo-overrides* nil)\n\n(with-class-validation\n (defclass channel (store-object)\n   ((name :initarg :name\n          :reader channel-name)\n    (company\n     :initarg :company\n     :initform nil\n     :accessor company\n     :documentation \"This slot might be NIL if the channel was deleted, which can indirectly lead to background promotion threads crashing, but in general will be safe.\")\n    (active-runs\n     :initform nil\n     :accessor all-active-runs\n     :relaxed-object-reference t\n     :documentation \"Mapping from branch name to active run as an alist\")\n    (branch :initarg :branch\n            :accessor channel-branch)\n    (github-repo :initarg :github-repo\n                 :initform nil\n                 :writer (setf github-repo)\n                 :reader %%github-repo)\n    (github-repo-full-name :accessor github-repo-full-name\n                           :index-type hash-index\n                           :index-initargs (:test 'equal)\n                           :index-reader channel-with-github-repo-full-name)\n    (master-branch :accessor master-branch\n                   :initform \"master\")\n    (masks :initform nil\n           :reader masks\n           :accessor channel-masks\n           :documentation \"assoc value from screenshot name to list of MASK-RECTs\")\n    (publicp :initform nil\n             :initarg :publicp\n             :accessor publicp)\n    (repo-cache\n     :initform nil\n     :transient t)\n    (channel-runs\n     :initform nil\n     :transient t\n     :documentation \"Deprecated: do not use. See runs-for-channel instead\")\n    (%commit-to-run-map\n     :initform (fset:empty-map)\n     :transient t\n     :documentation \"Deprecated: do not use\")\n    (lock\n     :initform (bt:make-lock)\n     :transient t\n     :reader channel-lock)\n    (promotion-lock\n     :initform (bt:make-lock)\n     :transient t\n     :reader channel-promotion-lock)\n    (%subscribers\n     :initform nil\n     :accessor channel-subscribers)\n    (cv\n     :initform (bt:make-condition-variable :name \"channel-cv\")\n     :transient t\n     :reader channel-cv)\n    (created-at\n     :initform (local-time:timestamp-to-universal (local-time:now))\n     :accessor %created-at)\n    (%slack-channels\n     :initform nil\n     :accessor channel-slack-channels)\n    (%review-policy\n     :initarg :review-policy\n     :accessor review-policy-name)\n    (%allow-public-badge-p\n     :initarg :allow-public-badge-p\n     :accessor allow-public-badge-p))\n   (:metaclass persistent-class)\n   (:default-initargs :review-policy :allow-author\n                      :allow-public-badge-p nil)))\n\n\n(defmethod channels-for-company (company)\n  (loop for channel in (bknr.datastore:class-instances 'channel)\n        if (eql company (ignore-errors\n                         (company channel)))\n          collect channel))\n\n(defmethod print-object ((self channel) stream)\n  (format stream \"#<CHANNEL ~a>\" (ignore-errors (channel-name self))))\n\n(defmethod review-policy-name :around ((self channel))\n  (or\n   (ignore-errors (call-next-method))\n   :allow-author))\n\n(defmethod review-policy ((self channel))\n  (ecase (review-policy-name self)\n    (:allow-author\n     (make-instance 'anyone-can-review))\n    (:disallow-author\n     (make-instance 'disallow-author-review-policy))))\n\n(defmethod channel-company ((channel channel))\n  (company channel))\n\n(defun all-channels ()\n  (store-objects-with-class 'channel))\n\n(defmethod github-repo ((channel channel))\n  (or\n   (assoc-value *channel-repo-overrides* channel)\n   (ignore-errors\n    (%%github-repo channel))))\n\n#-lispworks\n(defvar *updatef-lock* (bt:make-lock))\n\n(defmacro updatef (map key fn)\n  (let ()\n    `(let ((k ,key)\n           (ff ,fn))\n\n       (flet ((update (m)\n                (fset:with m\n                           k\n                           (funcall ff (fset:lookup m k)))))\n         #+lispworks\n         (atomics:atomic-update\n          ,map #'update)\n         #-lispworks\n         (bt:with-lock-held (*updatef-lock*)\n           (setf\n            ,map (update ,map)))))))\n\n(defmacro with-channel-lock ((channel) &body body)\n  `(flet ((body () ,@body))\n     (with-slots (lock) ,channel\n       (bt:with-lock-held (lock)\n         (body)))))\n\n(defmethod (setf github-repo) :after (val (channel channel))\n  (setf (github-repo-full-name channel)\n        (get-full-repo-from-repo val)))\n\n(defun get-full-repo-from-repo (repo)\n  (when repo\n   (multiple-value-bind (full parts)\n       (cl-ppcre:scan-to-strings\n        \"^https://github.com/(.*/.*)$\"\n        (github-get-canonical-repo repo))\n     (when full\n       (elt parts 0)))))\n\n\n(deftransaction\n    %set-active-run (channel branch run)\n    (check-type run recorder-run)\n    (check-type channel channel)\n    (check-type branch string)\n    (setf\n     (assoc-value (all-active-runs channel)\n                  branch\n                  :test 'equal)\n     run))\n\n(deftransaction\n    set-channel-screenshot-mask (channel name mask)\n    (check-type channel channel)\n    (check-type name string)\n    (check-type mask list)\n    (setf (assoc-value (slot-value channel 'masks) name :test 'equal)\n          mask))\n\n(defmethod (setf active-run) ((run recorder-run) (channel channel) (branch string))\n  (setf (was-promoted-p run) t)\n  (%set-active-run channel branch run))\n\n(defmethod (setf active-run) ((run recorder-run) (channel channel) (branch null))\n  (values))\n\n(defmethod active-run ((channel channel) branch)\n  (assoc-value (all-active-runs channel)\n               branch :test 'equal))\n\n(defmethod clear-caches ((channel channel))\n  (with-slots (repo-cache) channel\n    (setf repo-cache nil)))\n\n(defmethod channel-repo ((channel channel))\n  (cond\n    ((null (github-repo channel))\n     (make-instance 'null-repo\n                    :company (channel-company channel)))\n    (t\n     (loop for plugin in (plugins (installation))\n           for repo = (plugin-parse-repo plugin\n                                         (channel-company channel)\n                                         (github-repo channel))\n           if repo\n             return repo\n           finally\n              (return (make-instance 'generic-git-repo :link (github-repo channel)\n                                                       :company (channel-company channel)))))))\n\n(defmethod created-at (x)\n  (local-time:universal-to-timestamp (%created-at x)))\n\n(defmethod can-view ((channel channel) user)\n  (auth:can-view-with-normal-viewer-context\n   user channel))\n\n(defmethod auth:can-viewer-view (vc (channel channel))\n  (or\n   (publicp channel)\n   (auth:can-viewer-view vc (company channel))))\n\n(defmethod auth:can-viewer-view ((vc api-viewer-context)\n                                 (channel channel))\n  (or\n   (publicp channel)\n   (eql (api-key-company (viewer-context-api-key vc))\n        (company channel))))\n\n(defmethod production-run-for ((channel channel)\n                               &key commit\n                                 (seen (fset:empty-set)))\n  ;; currently returns the oldest run for a given commit\n  (or\n   (%find-direct-production-run channel :commit commit)\n   (let ((unchanged-run (unchanged-run-for-commit\n                         channel commit)))\n     (cond\n       ((fset:contains? seen commit)\n        nil)\n       (unchanged-run\n        (production-run-for channel\n                            :commit (unchanged-run-other-commit unchanged-run)\n                            :seen (fset:with seen commit)))))))\n\n(defmethod %runs-for-commit ((channel channel) commit)\n  (let ((new-version\n          (reverse\n           (fset:convert\n            'list\n            (fset:lookup\n             (or\n              (externalized-slot-value channel 'recorder-run:commit-map)\n              (fset:empty-map))\n             commit)))))\n    new-version))\n\n(defun %find-direct-production-run (channel &key commit)\n  (let ((large-int most-positive-fixnum))\n    (reduce\n     (lambda (x y)\n       (if (< (if x (store-object-id x) large-int)\n              (if y (store-object-id y) large-int))\n           x\n           y))\n     (fset:filter\n      #'trunkp\n      (%runs-for-commit channel commit))\n     :initial-value nil)))\n\n(defmethod channel-active-run ((channel channel))\n  (loop for run in (company-runs (channel-company channel))\n        if (and\n            (eql channel (recorder-run-channel run))\n            (activep run))\n          return run))\n\n(defun channel-promoted-runs (channel &key branch\n                                        (iterator nil))\n  \"Get the list of promoted-runs. If iterator is t, it returns a function\n that always returns two values: the next promoted-run, and whether\n this is an eof, or end of list. It is safe to call the iterator\n multiple times at the end.\"\n  (let ((branch (or branch (master-branch channel))))\n    (let ((run (active-run channel branch)))\n      (flet ((iterator ()\n               (when run\n                (let ((next run)\n                      (previous-run (recorder-previous-run run)))\n                  (setf run previous-run)\n                  (values next (not (null next)))))))\n\n        (cond\n          (iterator\n           #'iterator)\n          (t\n           (loop for run = (iterator)\n                 for i from 0 to 1000\n                 while run\n                 collect run)))))))\n\n(def-store-migration (\"Ensure allow-public-badge-p is bound\" :version 20)\n  (ensure-slot-boundp 'channel '%allow-public-badge-p))\n\n(defmethod channel-deleted-p ((channel channel))\n  (not (company channel)))\n\n(defmethod repos-for-company (company)\n  (let ((channels (channels-for-company company)))\n    (fset:convert\n     'list\n     (fset:convert\n      'fset:set\n      (mapcar #'github-repo channels)))))\n\n(defmethod channel-active-commits (channel)\n  (remove-duplicates\n   (remove-if\n    #'str:emptyp\n    (loop for (branch . run) in (all-active-runs channel)\n          collect (recorder-run-commit run)))\n   :test #'equal))\n\n\n(defun rshorten (count str)\n  (reverse\n   (str:shorten count (reverse str))))\n\n(defun %guess-separator (name)\n  (cond\n    ((< (count #\\/ name)\n        (count #\\: name))\n     \":\")\n    (t\n     \"/\")))\n\n(defun shortened-channel-name (name &key (length 30))\n  \"Get a short version of a channel name, appropriate to use in UI where\na long name might be awkward.\"\n\n  (let ((sep (%guess-separator name)))\n    (let* ((part (str:split sep name))\n           (first (first part))\n           (last (car (last part))))\n      (block nil\n        (cond\n          ((<= (length part) 2)\n           ;; We don't have any shortening behavior for now\n           (rshorten length name))\n          ((<= (+ 2 (length first) (length last)) length)\n           (let ()\n             ;; / + first + / + ... + / + last is less than length?\n             (let* ((right (second (str:split sep name :limit 2)))\n                    (center (first (str:rsplit sep right :limit 2))))\n               (format nil\n                       \"~a~a~a~a~a\"\n                       first\n                       sep\n                       (rshorten (- length 2 (length first) (length last)) center)\n                       sep\n                       last))))\n          (t\n           (rshorten length name)))))))\n"
  },
  {
    "path": "src/screenshotbot/model/commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/commit-graph\n  (:use #:cl #:alexandria)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:class-instances\n                #:deftransaction\n                #:store-object-id\n                #:store-objects-with-class\n                #:blob-pathname\n                #:persistent-class)\n  (:import-from #:screenshotbot/git-repo\n                #:commit-graph-dag\n                #:find-or-create-commit-graph\n                #:commit-graph)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:util/events\n                #:with-tracing)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:util/store/simple-object-snapshot\n                #:simple-object-snapshot\n                #:snapshot-slot-value)\n  (:import-from #:util/misc/lists\n                #:with-batches)\n  (:import-from #:util/store/store-version\n                #:*store-version*)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:util/misc\n                #:?.)\n  (:export\n   #:commit-graph\n   #:repo-url\n   #:lock\n   #:find-commit-graph\n   #:find-or-create-commit-graph\n   #:commit-graph-dag))\n(in-package :screenshotbot/model/commit-graph)\n\n(defvar *flush-lock* (bt:make-lock \"dag-flush-lock\"))\n\n(defvar *lock* (bt:make-lock))\n\n(defun transient-dag-p ()\n  \"In older versions we used to read the DAG from a transient slot. In\nthis new version, we no longer use the transient slot. Eventually you\ncan delete this code. T1739\"\n  (< *store-version* 32))\n\n(defindex +normalized-repo-url-index+\n  'fset-set-index\n  :slot-name '%normalized-repo-url)\n\n(with-class-validation\n  (defclass commit-graph (bknr.datastore:blob)\n    ((url :type string\n          :initform nil\n          :accessor repo-url\n          :initarg :url)\n     (%normalized-repo-url :type string\n                           :accessor normalized-repo-url\n                           :index +normalized-repo-url-index+\n                           :index-reader find-by-normalized-url\n                           :initarg :normalized-url)\n     (company :initarg :company\n              :accessor company\n              :initform nil)\n     (lock :initform (bt:make-recursive-lock)\n           :transient t\n           :accessor lock)\n     (dag :initform nil\n          :transient t\n          :accessor %commit-graph-dag)\n     (refs :initform nil \n           :initarg :refs\n           :accessor %commit-graph-refs\n           :writer (setf commit-graph-refs)\n           :documentation \"An fset:map, with the last known refs. These refs\nare force updated, so it might move `backwards`! The main purpose of these\nrefs is just to allow incremental updates, and for potential garbage\ncollection. For now, if you need a source of truth of refs, you might want to\nuse the refs in promoted runs.\")\n     (dag-v2 :accessor %persisted-dag\n             :initarg :dag\n             :documentation \"Move from DAG from DAG-V2 to keep the DAG persisted.\")\n     (needs-flush-p :initform nil\n                    :transient t\n                    :accessor needs-flush-p))\n    (:metaclass persistent-class)\n    (:default-initargs :dag nil)))\n\n(defmethod commit-graph-refs ((self commit-graph))\n  (let ((refs (%commit-graph-refs self)))\n    (when (listp refs) #| or nil |#\n      (setf refs\n            (setf (%commit-graph-refs self)\n                  (fset:convert 'fset:map refs))))\n    refs))\n\n(defindex +normalization-override-from+\n  'unique-index\n  :test #'equal\n  :slot-name '%from)\n\n(defclass normalization-override (store-object)\n  ((%from :initarg :from\n          :index +normalization-override-from+\n          :index-reader find-normalization-override)\n   (%to :initarg :to\n        :reader normalization-override-to))\n  (:metaclass persistent-class)\n  (:documentation \"See T1748, specific issues with certain customers\"))\n\n(defmethod bknr.datastore:make-object-snapshot ((self commit-graph))\n  (make-instance 'simple-object-snapshot\n                 :object self\n                 :except-slots '(dag-v2)))\n\n(defmethod snapshot-slot-value ((self commit-graph) (slot (eql 'dag-v2)))\n  (when-let ((dag (slot-value self 'dag-v2)))\n    (dag:clone-dag dag)))\n\n(defun %remove-auth (url)\n  (cond\n    ((str:starts-with-p \"http\" url)\n     (let ((uri (quri:uri url)))\n       (setf (quri:uri-userinfo uri) nil)\n       (quri:render-uri uri)))\n    (t\n     url)))\n\n(defun normalize-url (repo-url)\n  \"Normalizing is relatively safe. Even if two distinct repos normalize\nto the same repo, the graph will still be the same.\"\n  (let ((repo-url (%remove-auth repo-url)))\n   (or\n    (?. normalization-override-to (find-normalization-override repo-url))\n    (let ((repo-url (str:downcase repo-url)))\n      (flet ((remove-prefixes (repo-url)\n               (let ((git-prefix \"^(ssh[:/]//)?git@\"))\n                 (cond\n                   ((cl-ppcre:scan git-prefix repo-url)\n                    (cl-ppcre:regex-replace-all\n                     git-prefix (str:replace-all \":\" \"/\" repo-url) \"https://\"))\n                   (t repo-url)))))\n        (let ((suffixes (list \"/\" \".git\")))\n          (loop for suffix in suffixes\n                if (str:ends-with-p suffix repo-url)\n                  return (normalize-url (str:substring 0 (- (length repo-url)\n                                                            (length suffix))\n                                                       repo-url))\n                finally (return (remove-prefixes repo-url)))))))))\n\n\n(defmethod find-commit-graph ((company company) (url string))\n  (log:info \"Finding commit graph for company ~a and repo ~a\" company url)\n  (let ((result (or\n                 (fset:do-set (cg (find-by-normalized-url (normalize-url url)))\n                   (when (eql company (company cg))\n                     (return cg)))\n                 (%find-by-unnormalized-url company url))))\n    (log:info \"Using commit graph: ~a\" result)\n    result))\n\n(defun %find-by-unnormalized-url (company url)\n  (loop for commit-graph in (store-objects-with-class 'commit-graph)\n        if (and\n            (eql (company commit-graph) company)\n            (equal (repo-url commit-graph) url))\n          do (return commit-graph)))\n\n(let ((lock (bt:make-lock)))\n  (defmethod find-or-create-commit-graph ((company company) (url string))\n    (bt:with-lock-held (lock)\n      (or\n       (find-commit-graph company url)\n       (make-instance 'commit-graph\n                      :url url\n                      :normalized-url (normalize-url url)\n                      :company company)))))\n\n(defmethod commit-graph-dag ((obj commit-graph))\n  (cond\n    ((transient-dag-p)\n     (util:or-setf\n      (%commit-graph-dag obj)\n      (bt:with-recursive-lock-held ((lock obj))\n        (cond\n          ((not (path:-e (blob-pathname obj)))\n           (make-instance 'dag:dag))\n          (t\n           (with-open-file (s (blob-pathname obj) :direction :input)\n             (dag:read-from-stream s)))))))\n    (t\n     (commit-graph-persisted-dag obj))))\n\n(defmethod commit-graph-persisted-dag ((obj commit-graph))\n  \"Eventually commit-graph-dag should point here.\"\n  (util:or-setf\n   (%persisted-dag obj)\n   (make-instance 'dag:dag)\n   :thread-safe t\n   :lock *lock*))\n\n(defmethod (setf commit-graph-dag) (dag (obj commit-graph))\n  (cond\n    ((transient-dag-p)\n     (setf (%commit-graph-dag obj) dag)\n     (setf (needs-flush-p obj) t)))\n    (t\n     (error \"unimpl\")))\n\n(defmethod flush-dag ((obj commit-graph))\n  \"This code is no longer being called for most dags and could probably be removed\"\n  (when (transient-dag-p)\n    (warn \"Expected this code to not be running in prod.\")\n    (bt:with-recursive-lock-held ((lock obj))\n      (with-tracing (:flush-dag :id (store-object-id obj))\n        (let ((dag (%commit-graph-dag obj)))\n          (with-open-file (s (blob-pathname obj) :if-does-not-exist :create\n                                                 :direction :output\n                                                 :if-exists :supersede)\n            (dag:write-to-stream dag s)\n            (finish-output s)\n            (log:info \"Updated commit graph in ~s\" (blob-pathname obj)))))))\n  (setf (needs-flush-p obj) nil))\n\n(defun flush-dags ()\n  (bt:with-lock-held (*flush-lock*)\n    (loop for commit-graph in (bknr.datastore:class-instances 'commit-graph)\n          if (needs-flush-p commit-graph)\n            do (flush-dag commit-graph))))\n\n(def-cron flush-dags (:step-min 30)\n  (flush-dags))\n\n(def-store-migration (\"Add normalized repos\" :version 9)\n  (loop for cg in (bknr.datastore:class-instances 'commit-graph)\n        if (repo-url cg)\n          do (setf (normalized-repo-url cg) (normalize-url (repo-url cg)))))\n\n(deftransaction tx-add-commits (commit-graph commits)\n  (let ((dag (commit-graph-persisted-dag commit-graph)))\n    (loop for commit in commits\n          do\n             (dag:add-commit dag commit))))\n\n(defmethod merge-dag-into-commit-graph (commit-graph new-dag)\n  (bt:with-recursive-lock-held ((lock commit-graph))\n    (when (transient-dag-p)\n      (let ((dag (commit-graph-dag commit-graph)))\n        (dag:merge-dag dag new-dag)\n        (setf (commit-graph-dag commit-graph)\n              dag)))\n    (let ((dag (commit-graph-persisted-dag commit-graph)))\n      (let ((difference (dag:all-commits\n                         (dag:dag-difference new-dag dag))))\n        (with-batches (commits difference :batch-size 100)\n          (tx-add-commits commit-graph commits))))))\n\n(def-store-migration (\"Add dag-v2 slot\" :version 31)\n  (ensure-slot-boundp 'commit-graph 'dag-v2))\n\n(def-store-migration (\"Import dag to dag-v2\" :version 32)\n  (loop for cg in (class-instances 'commit-graph)\n        do (merge-dag-into-commit-graph cg (commit-graph-dag cg))))\n\n(deftransaction tx-commit-graph-set-ref (commit-graph name sha)\n  (setf\n   (commit-graph-refs commit-graph)\n   (fset:with\n    (commit-graph-refs commit-graph)\n    name sha)))\n\n(defmethod commit-graph-set-ref ((commit-graph commit-graph)\n                                 (name string)\n                                 (sha string))\n  (unless (equal sha (fset:lookup (commit-graph-refs commit-graph) name))\n    (tx-commit-graph-set-ref commit-graph name sha)))\n"
  },
  {
    "path": "src/screenshotbot/model/company-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/company-graph\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:location-for-oid\n                #:object-neighbors\n                #:find-any-refs)\n  (:import-from #:screenshotbot/model/user\n                #:user-with-email\n                #:adminp)\n  (:import-from #:screenshotbot/model/note\n                #:note)\n  (:import-from #:screenshotbot/model/company\n                #:company-with-name\n                #:company)\n  (:import-from #:bknr.datastore\n                #:blob\n                #:store-object-id\n                #:blob-pathname\n                #:*store*\n                #:store-directory\n                #:class-instances\n                #:store-object-subsystem\n                #:snapshot-subsystem-helper)\n  (:import-from #:screenshotbot/model/image\n                #:image-filesystem-pathname\n                #:image)\n  (:import-from #:util/copy-file\n                #:copy-file-fast)\n  (:import-from #:util/store/object-id\n                #:oid\n                #:oid-array)\n  (:import-from #:util/misc\n                #:?.\n                #:safe-ensure-directories-exist)\n  (:import-from #:screenshotbot/model/constant-string\n                #:constant-string)\n  (:export\n   #:company-graph\n   #:company-full-graph)\n  (:local-nicknames (#:image-comparison #:screenshotbot/model/image-comparison)))\n(in-package :screenshotbot/model/company-graph)\n\n(defvar *lparallelp* t)\n\n(defvar *root-company* nil\n  \"The root company on which we are doing the current graph work. For\nconvenience.\")\n\n(defun pmapc (fn objs)\n  (funcall (if *lparallelp* #'lparallel:pmapc #'mapc)\n           fn objs))\n\n(defun ignorable-atom (obj)\n  \"We know these objects can't eventually refer any other objects, so we\nremove these from the graph.\"\n  (or\n   (stringp obj)\n   (numberp obj)\n   (symbolp obj)))\n\n(defmethod object-neighbors-for-graph (x)\n  (object-neighbors x))\n\n(defmethod object-neighbors-for-graph ((note note))\n  (unless (?. adminp (screenshotbot/model/note::user note))\n    (call-next-method)))\n\n(defmethod object-neighbors-for-graph ((map fset:map))\n  (let ((result))\n    (fset:do-map (key val map)\n      (push key result)\n      (push val result))\n    result))\n\n(defmethod object-neighbors-for-graph ((map hash-table))\n  (let ((result))\n    (loop for key being the hash-keys of map\n          using (hash-value val)\n          do\n             (push key result)\n             (push val result))\n    result))\n\n(defmethod object-neighbors-for-graph ((self screenshotbot/model/screenshot::lite-screenshot))\n  (list*\n   (?. screenshotbot/model/image:find-image-by-oid (screenshotbot/model/screenshot::image-oid self))\n   (call-next-method)))\n\n(defun all-starting-store-objects ()\n  (let ((all (bknr.datastore:all-store-objects)))\n    (loop for obj in all\n          if (not\n              (or\n               (typep obj 'gatekeeper/gatekeeper::gatekeeper)\n               (typep obj 'gatekeeper/gatekeeper::access-control)))\n            collect obj)))\n\n(defun warn-about-unrecognized-object-types (neighbor)\n  (unless (or\n           (typep neighbor 'bknr.datastore:store-object)\n           (listp neighbor)\n           (arrayp neighbor)\n           (typep neighbor 'util/store/object-id:oid)\n           (typep neighbor 'dag:dag)\n           (typep neighbor 'dag:commit)           \n           (typep neighbor 'fset:map)\n           (hash-table-p neighbor)\n           (typep neighbor 'screenshotbot/model/screenshot::lite-screenshot))\n    (log:warn \"found an object of weird type: ~a\" neighbor)))\n\n(defun reverse-graph (&key (undirected nil))\n  \"Creates the graph as a hash-table.\"\n  (let  ((graph (make-hash-table)))\n    (let ((seen (make-hash-table))\n          (queue (make-array 0 :adjustable t :fill-pointer t))\n          (start 0))\n      (loop for obj in (all-starting-store-objects) do\n        (vector-push-extend obj queue))\n      (loop while (< start (length queue))\n            for obj = (aref queue (1- (incf start)))\n            do\n               (unless (gethash obj seen)\n                 (setf (gethash obj seen) t)\n                 (loop for neighbor in (object-neighbors-for-graph obj)\n                       if (not (ignorable-atom neighbor))\n                         do\n                            (warn-about-unrecognized-object-types neighbor)\n                            (push obj (gethash neighbor graph))\n                            (when undirected\n                              (push neighbor (gethash obj graph)))\n                            (vector-push-extend neighbor queue)))))\n    graph))\n\n(defmethod company-graph ((self company))\n  (call-next-method))\n\n(defmethod should-continue-traversing-p (obj neighbor)\n  \"Should we keep traversing when we're at this object?\"\n  t)\n\n(defmethod should-continue-traversing-p ((x screenshotbot/model/screenshot-key::screenshot-key)\n                                         neighbor)\n  (and\n   (listp neighbor)\n   (every (alexandria:rcurry #'typep 'screenshotbot/model/image::mask-rect)\n          neighbor)))\n\n(defmethod should-continue-traversing-p ((obj constant-string) neighbor)\n  \"Don't traverse from constant-strings to their neighbors, as constant-strings\ncan be shared between unrelated companies and would create false connections.\"\n  nil)\n\n(defun find-reachable-store-objects (graph self)\n  (let ((seen (make-hash-table))\n        (queue (make-array 0 :adjustable t :fill-pointer t))\n        (from (make-hash-table)) ;; where we came from, to generate a path\n        (start 0))\n    (vector-push-extend self queue)\n    (loop while (< start (length queue))\n          for obj = (aref queue (1- (incf start)))\n          do\n             (unless (gethash obj seen)\n               (setf (gethash obj seen) t)\n               (loop for neighbor in (gethash obj graph)\n                     if (should-continue-traversing-p obj neighbor)\n                       do\n                          (unless (gethash neighbor from)\n                            (setf (gethash neighbor from) obj))\n                          (vector-push-extend neighbor queue))))\n    (values\n     (loop for obj being the hash-keys of seen\n           if (typep obj 'bknr.datastore:store-object)\n             collect obj)\n     from)))\n\n(defmethod company-graph (self)\n  \"Get all objects belonging to an object, even though we call it company-graph\"\n  (let ((graph (reverse-graph)))\n    (find-reachable-store-objects graph self)))\n\n(defmethod company-full-graph (self)\n  \"Get all the objects that refer or are referenced by, directly or\nindirectly to a company. This is useful for copying a company and its\nobjects to a new instances. To see why this is implemented this way,\nsee the full-graph-finds-everything test.\"\n  (let ((*root-company* self))\n   (let ((graph (reverse-graph :undirected t)))\n     (find-reachable-store-objects graph self))))\n\n(defun find-a-path (src dest &key (undirected t))\n  \"Find a path from src to dest in the undirected full graph. For debugging\"\n  (let ((graph (reverse-graph :undirected undirected)))\n    (multiple-value-bind (reach backlinks)\n        (find-reachable-store-objects graph src)\n      (declare (ignore reach))\n      (loop for x = (gethash dest backlinks)\n            while (and x (not (eql src x)))\n            do\n               (setf dest x)\n               (log:info \"<-- ~a\" x)))))\n\n(defun save-graph (company file)\n  \"Save all the objects related to a company to a snapshot. Useful for\nmoving a company to a new instance.\"\n  (let ((objects (company-full-graph company)))\n    (save-objects objects file)))\n\n(defun save-objects (objects file)\n  (with-open-file (s file\n                     :direction :output\n                     :element-type '(unsigned-byte 8))\n    (funcall\n     (snapshot-subsystem-helper\n      (loop for subsystem in (bknr.datastore::store-subsystems bknr.datastore:*store*)\n            if (typep subsystem 'store-object-subsystem)\n              return subsystem)\n      s\n      :map-store-objects (lambda (fn)\n                           (mapc fn objects))))))\n\n(defun save-images (objects &key output)\n  (let ((images (loop for obj in objects\n                      if (typep obj 'image)\n                        collect obj))\n        (image-blobs (ensure-directories-exist (path:catdir output \"image-blobs/\"))))\n    (pmapc\n     (lambda (img)\n       (when (path:-e (image-filesystem-pathname img))\n         (copy-file-fast (image-filesystem-pathname img)\n                         (location-for-oid\n                          image-blobs\n                          (oid-array img))))\n       (copy-image-cache img :output output))\n     images)))\n\n(defun copy-image-cache (img &key output)\n  (dolist (size '(\"300x300\" \"600x600\" \"2000x2000\"))\n    (let ((args (list (oid img :stringp nil) :suffix size :type \"webp\")))\n     (let ((src (apply #'location-for-oid #P \"image-cache/\" args))\n           (dest (apply #'location-for-oid (path:catdir output \"image-cache/\") args)))\n       (when (path:-e src)\n         (copy-file-fast\n          src\n          (safe-ensure-directories-exist dest)))))))\n\n(defun check-graph (objects)\n  #+nil\n  (loop for object in objects\n        if (string-equal :log-file (type-of object))\n          do (error \"bad object: ~a\" object)))\n\n(auto-restart:with-auto-restart ()\n (defun copy-blobs (objects &key output)\n   \"As of now, this is mostly needed for commit-graphs\"\n   (check-graph objects)\n   (let ((blob-root (ensure-directories-exist (path:catdir output \"blob-root/\"))))\n     (pmapc\n      (lambda (obj)\n        (when (path:-e (blob-pathname obj))\n          (log:info \"Copying blob: ~a\" obj)\n          (uiop:copy-file\n           (blob-pathname obj)\n           (path:catfile blob-root (format nil \"~a\" (store-object-id obj))))))\n      (loop for object in objects\n            if (typep object 'blob)\n              collect object)))))\n\n(defun copy-keys (output)\n  (dolist (key '(\"aes-128-key.txt\" \"blowfish-key.txt\"))\n    (uiop:copy-file\n     (path:catfile (store-directory *store*) key)\n     (path:catfile output key))))\n\n(defun copy-other-snapshot-files (output)\n  #+bknr.cluster\n  (let ((snapshot-files\n          (directory\n           (path:catfile\n            (bknr.cluster/server::data-path *store*)\n            \"snapshot/snapshot_*/\"))))\n   (loop for name in (list\n                       \"random-state\"\n                       \"version-subsystem-snapshot\")\n         do\n            (loop for src in snapshot-files\n                  if (equal (pathname-name src) name)\n                    do\n                       (log:info \"Copying ~a\" src)\n                       (uiop:copy-file src (path:catfile output \"current/\" name))))))\n\n(defun save-image-comparison-snapshot (objects output)\n  (let ((ht (make-hash-table)))\n    (dolist (obj objects)\n      (setf (gethash obj ht) t))\n    (let ((res (fset:empty-set))j)\n      (fset:do-set (imc image-comparison::*stored-cache*)\n        (flet ((has? (x) (or\n                          (not x)\n                          (gethash x ht))))\n          (when (and\n                 (has? (image-comparison::image-comparison-before imc))\n                 (has? (image-comparison::image-comparison-after imc))\n                 ;; This is breaking thigs. But for now it ends up\n                 ;; posting an empty image-comparison snapshot. Which\n                 ;; is okay. In the future, it might be that the\n                 ;; company graph is not going through these objects.\n                 (has? (image-comparison::image-comparison-result imc)))\n            (log:info \"Copying: ~a\" imc)\n            (setf\n             res (fset:with res imc)))))\n      (log:info \"Writing: ~a image-comparisons\" (fset:size res))\n      (image-comparison::write-snapshot output res))))\n\n(defun save-graph-and-blobs (company &key output)\n  (let ((company (typecase company\n                   (string\n                    (company-with-name company))\n                   (t company))))\n    (let ((objects (company-full-graph company)))\n      (check-graph objects)\n      (save-objects (copy-seq objects) (ensure-directories-exist\n                               (path:catfile output \"current/store-object-subsystem-snapshot\")))\n      (save-image-comparison-snapshot\n       (copy-seq objects)\n       (path:catfile output \"current/image-comparison-subsystem-snapshot\"))\n      (log:info \"Saving images\")\n      (save-images (copy-seq objects) :output output)\n      (log:info \"Saving blobs\")\n      (copy-blobs objects :output output)\n      (copy-keys output)\n      (copy-other-snapshot-files output))))\n\n\n(defun parse-inconsistencies (msg)\n  (let ((lines (str:lines msg)))\n    (iter:iterate (iter:for line iter:in lines)\n      (multiple-value-bind (match parts)\n          (cl-ppcre:scan-to-strings\n           \"Reference to inexistent object with id (.*) from\"\n           line)\n        (when match\n          (iter:collect (bknr.datastore:store-object-with-id (parse-integer (elt parts 0)))))))))\n"
  },
  {
    "path": "src/screenshotbot/model/company.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/company\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/user-api\n        #:screenshotbot/screenshot-api)\n  (:nicknames #:%c)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:unique-index\n                #:with-transaction\n                #:store-object)\n  (:import-from #:screenshotbot/task-integration-api\n                #:enabledp)\n  (:import-from #:util\n                #:find-by-oid\n                #:object-with-oid)\n  (:import-from #:bknr.datastore\n                #:deftransaction)\n  (:import-from #:screenshotbot/installation\n                #:oss-installation\n                #:multi-org-feature\n                #:installation)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index\n                #:fset-unique-index)\n  (:import-from #:core/installation/auth\n                #:company-for-request)\n  (:import-from #:core/installation/auth-provider\n                #:company-sso-auth-provider)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:import-from #:auth/login/roles-auth-provider\n                #:get-company-for-auth-provider)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context\n                #:viewer-context-user\n                #:logged-in-viewer-context)\n  (:import-from #:screenshotbot/server\n                #:needs-sso-condition)\n  (:import-from #:auth/login/sso\n                #:cant-view-company-condition)\n  (:export\n   #:company\n   #:company-reports\n   #:github-config\n   #:access-token\n   #:company-channels\n   #:jira-config-url\n   #:jira-config-username\n   #:slack-config-channel\n   #:jira-config-password\n   #:enabledp\n   #:company\n   #:find-image-by-id\n   #:company-with-name\n   #:find-or-create-channel\n   #:verified-p\n   #:jira-config-project-id\n   #:github-config\n   #:installation-id\n   #:company-admins\n   #:jira-config\n   #:phabricator-config\n   #:slack-config\n   #:phabricator-config-for-company\n   #:company-with-singletonp\n   #:singletonp\n   #:phabricator-url\n   #:conduit-api-key\n   #:default-slack-config\n   #:jira-config\n   #:add-company-run\n   #:company-owner\n   #:add-company-report\n   #:maybe-redirect-for-company\n   #:emails-enabled-by-default-p\n   #:has-root-company-p)\n  (:local-nicknames (#:roles #:auth/model/roles)))\n(in-package :screenshotbot/model/company)\n\n(defindex +singleton-index+\n  'fset-unique-index\n  :slot-name 'singletonp)\n\n(defindex +slug-index+\n  'fset-unique-index\n  :slot-name '%slug)\n\n(with-class-validation\n  (defclass company (object-with-oid)\n    ((name\n      :initarg :name\n      :accessor %company-name\n      :writer (setf company-name)\n      :index-initargs (:test 'equal)\n      :index-type unique-index\n      :index-reader company-with-name\n      :index-values all-named-companies)\n     (personalp\n      :initarg :personalp\n      :accessor personalp\n      :initform nil)\n     (owner\n      :reader %company-owner\n      :initform :roles\n      :documentation \"It used to be the actual company owner, but if it's :roles, then\n the owner will be pulled from user-roles.\")\n     (admins\n      :initarg :admins\n      :initform nil\n      :reader company-admins)\n     (runs\n      :initarg :runs\n      :initform nil\n      :accessor company-runs)\n     (reports\n      :initarg :reports\n      :initform nil\n      :accessor company-reports)\n     (invites\n      :initform nil\n      :documentation \"DEPRECATED\")\n     (channels\n      :initarg :channels\n      :initform nil\n      :accessor company-channels)\n     (default-slack-config\n      :initform nil\n      :initarg :default-slack-config\n      :accessor default-slack-config)\n     (github-config\n      :initform nil)\n     (jira-config\n      :initform nil\n      :reader %jira-config\n      :writer (setf jira-config))\n     (demo-filled-p\n      :initform nil)\n     (singletonp\n      :initarg :singletonp\n      :index +singleton-index+\n      :index-reader company-with-singletonp)\n     (sso-auth-provider\n      :initform nil\n      :accessor company-sso-auth-provider)\n     (images\n      :initarg :company-images\n      :initform nil\n      :documentation \"deprecated list of images. do not use.\")\n     (%redirect-url\n      :initarg :redirect-url\n      :accessor redirect-url\n      :documentation \"Redirect this company to this URL. This is used if we want to migrate a company to a different domain.\")\n     (%emails-enabled-by-default-p\n      :initarg :emails-enabled-by-default-p\n      :accessor emails-enabled-by-default-p)\n     (invitation-role\n      :initarg :invitation-role\n      :accessor company-invitation-role)\n     (%slug\n      :initarg :company-slug\n      :index +slug-index+\n      :index-reader company-for-slug\n      :accessor company-slug))\n    (:metaclass persistent-class)\n    (:default-initargs :redirect-url nil\n                       :company-slug nil\n                       :invitation-role 'roles:standard-member\n                       :emails-enabled-by-default-p t)))\n\n(defmethod initialize-instance :after ((self company) &rest args &key owner)\n  (when (and owner (not (eql :roles owner)))\n    (roles:ensure-has-role self owner 'roles:owner)))\n\n(defindex +parent-index+\n  'fset-set-index\n  :slot-name 'parent)\n\n(with-class-validation\n  (defclass sub-company (company)\n    ((parent :initarg :parent\n             :initform nil\n             :index +parent-index+\n             :index-reader %sub-companies-of\n             :reader company-parent))\n    (:metaclass persistent-class)\n    (:documentation \"A child organization, by default has all the permissions of the\nparent organization.\")))\n\n(defmethod sub-companies-of (company)\n  (%sub-companies-of company))\n\n(defmethod emails-enabled-by-default-p ((self sub-company))\n  (emails-enabled-by-default-p (company-parent self)))\n\n(defmethod print-object ((self company) out)\n  (format out \"#<COMPANY ~a>\" (ignore-errors (company-name self))))\n\n(let ((lock (bt:make-lock \"jira-config\")))\n  (defmethod jira-config ((company company))\n    \"For historical reasons, our company links to the jira config. We'll get rid of this in a future version\"\n    (bt:with-lock-held (lock)\n      (or\n       (%jira-config company)\n       (with-transaction ()\n        (setf\n         (jira-config company)\n         (make-instance 'jira-config :enabledp nil)))))))\n\n(defmethod singletonp ((company company))\n  (slot-boundp company 'singletonp))\n\n(with-class-validation\n (defclass github-config (store-object)\n   ((installation-id\n     :initform nil\n     :accessor installation-id))\n   (:metaclass persistent-class)))\n\n(with-class-validation\n  (defclass phabricator-config (store-object)\n    ((company :initarg :company\n              :index-initargs (:test 'eql)\n              :index-type unique-index\n              :index-reader %phabricator-config-for-company)\n     (url :initarg :url\n          :initform nil\n          :accessor phabricator-url)\n     (api-key :initarg :api-key\n              :initform nil\n              :accessor conduit-api-key))\n    (:metaclass persistent-class)))\n\n(let ((lock (bt:make-lock)))\n (defun phabricator-config-for-company (company)\n   (bt:with-lock-held (lock)\n     (or\n      (%phabricator-config-for-company company)\n      (make-instance 'phabricator-config :company company)))))\n\n\n(defmethod github-config ((company company))\n  (with-slots (github-config) company\n   (or\n    github-config\n    (with-transaction ()\n      (or\n       github-config\n       (setf github-config (make-instance 'github-config)))))))\n\n(with-class-validation\n  (defclass slack-config (store-object)\n    ((access-token\n      :initarg :access-token\n      :initform nil\n      :relaxed-object-reference t\n      :accessor access-token)\n     (channel\n      :initarg :channel\n      :accessor slack-config-channel)\n     (enabledp\n      :initarg :enabledp\n      :initform nil\n      :accessor enabledp))\n    (:metaclass persistent-class)))\n\n(with-class-validation\n  (defclass jira-config (store-object)\n    ((url :initarg :url\n          :initform nil\n          :accessor jira-config-url)\n     (username :initarg :username\n               :initform nil\n               :accessor jira-config-username)\n     (password :initarg :password\n               :initform nil\n               :accessor jira-config-password)\n     (project-id :initarg :project-id\n                 :initform nil\n                 :accessor jira-config-project-id)\n     (enabledp\n      :initarg :enabledp\n      :initform nil\n      :accessor enabledp))\n    (:metaclass persistent-class)))\n\n(defmethod (setf company-admins) (val (company company))\n  ;; always have at least one admin\n  (assert (> (length val) 0))\n  (setf (slot-value company 'admins) val))\n\n\n(defun company-name (company)\n  (cond\n    ((personalp company)\n     ;; there should be just one admin\n     (let ((name (anaphora:awhen (car (company-admins company))\n                   (user-full-name anaphora:it))))\n       (cond\n         ((str:emptyp name)\n          \"Default\")\n         (t\n          name))))\n    ((and\n      (singletonp company)\n      (or\n       (not (slot-boundp company 'name))\n       (not (slot-value company 'name))))\n     ;; TODO: we probably don't need this. We could probably fix this\n     ;; in a migration in the future to make sure that every singleton\n     ;; company has a name.\n     \"Singleton Company\")\n    (t\n     (let ((name (slot-value company 'name)))\n       name))))\n\n\n(defmethod find-image ((company company) hash)\n  (error \"Old function: call model/image:find-image instead\"))\n\n(defgeneric find-image-by-id (company id))\n\n(defmethod find-channel ((company company) name)\n  (loop for channel in (company-channels company)\n        if (equal (channel-name channel) name)\n          return channel))\n\n(defmethod find-or-create-channel ((company company) name)\n  (or\n   (find-channel company name)\n   (let ((channel (make-instance 'channel :name name :company company)))\n     (with-transaction ()\n      (pushnew channel (company-channels company)))\n     channel)))\n\n(defmethod can-view ((company company) user)\n  (auth:can-view-with-normal-viewer-context\n   user company))\n\n(defmethod auth:can-viewer-view ((vc logged-in-viewer-context)\n                                 (company company))\n  (and\n   (roles:has-role-p company\n                     (viewer-context-user vc)\n                     'roles:read-only)\n   (company-domain-matches-installation-p company)))\n\n(defun company-domain-matches-installation-p (company)\n  (let ((redirect-url (redirect-url company)))\n    (cond\n      ((null redirect-url)\n       t)\n      (t\n       (equal\n        (installation-domain *installation*)\n        redirect-url)))))\n\n(defmethod auth:can-view ((company sub-company) user)\n  (auth:can-view-with-normal-viewer-context\n   user company))\n\n(defmethod auth:can-viewer-view ((vc logged-in-viewer-context) (company sub-company))\n  (or\n   (call-next-method)\n   (auth:can-viewer-view vc (company-parent company))))\n\n(defmethod auth:can-viewer-view ((vc normal-viewer-context)\n                                 (company company))\n  \"This is not an SSO viewer context. We must make sure the company is\nnot set up for SSO.\"\n  (cond\n    ((company-sso-auth-provider company)\n     (let ((res (and\n                 (roles:has-role-p company (viewer-context-user vc) 'roles:admin)\n                 (call-next-method))))\n       (unless res\n         (signal 'needs-sso-condition :company company))\n       res))\n    (t\n     (let ((res (call-next-method)))\n       (unless res\n         (signal 'cant-view-company-condition :company company))\n       res))))\n\n(defgeneric company (obj)\n  (:documentation \"For a given obj, figure out which company it belongs to\"))\n\n(defmethod prepare-singleton-company-for-installation ((installation installation))\n  \"Get a singleton persistent company. If no singleton company exists,\n  it is created. Otherwise the existing singleton company is\n  returned. Singleton companies are mostly used in the OSS version,\n  even though you can customize it to use multiple companies.\"\n  (unless (company-with-singletonp t)\n    (make-instance 'company :singletonp t)))\n\n(defmethod prepare-singleton-company-for-installation ((installation multi-org-feature))\n  \"We never create a singleton company for multi-orgs\"\n  (values))\n\n(defun prepare-singleton-company ()\n  (when-let (installation (ignore-errors (installation)))\n    (prepare-singleton-company-for-installation installation)))\n\n(defvar *singleton-lock* (bt:make-lock))\n\n(defmethod get-singleton-company ((installation installation))\n  (or\n   (company-with-singletonp t)\n   (bt:with-lock-held (*singleton-lock*)\n     (or\n      (company-with-singletonp t)\n      (progn\n        (prepare-singleton-company)\n        (company-with-singletonp t))))))\n\n(defmethod get-singleton-company ((installation multi-org-feature))\n  (error \"singleton company doesn't make sense in a multi-org mode\"))\n\n(deftransaction\n    add-company-run (company run)\n    (check-type company company)\n    (check-type run store-object)\n    (push run (company-runs company)))\n\n(defmethod company-admin-p ((company company) user)\n  (roles:has-role-p company user 'roles:admin))\n\n(deftransaction\n    add-company-report (company report)\n    (check-type company company)\n    (check-type report store-object)\n    (push report (company-reports company)))\n\n(defmethod company-for-request ((installation installation) request)\n  (get-singleton-company installation))\n\n(def-store-migration (\"Add :redirect-url to companies\" :version 10)\n  (ensure-slot-boundp 'company '%redirect-url))\n\n\n(defun maybe-redirect-for-company (company)\n  \"When called within a hunchentoot requests, redirects to the redirect\nURL for the company, if there is one.\"\n  (when-let  ((redirect-url (redirect-url company)))\n    (hunchentoot:redirect\n     (quri:render-uri\n      (quri:merge-uris\n       (quri:uri (hunchentoot:request-uri*))\n       redirect-url)))))\n\n(def-store-migration (\"Add default :invitation-role\" :version 12)\n  (ensure-slot-boundp 'company 'invitation-role :value 'roles:standard-member))\n\n(defun ensure-company-using-roles (company)\n  \"This is only used for a migration\"\n  (when-let ((owner (slot-value company 'owner)))\n    (unless (eql :roles owner)\n      (setf (slot-value company 'owner) :roles)\n      (roles:ensure-has-role company owner 'roles:owner))))\n\n(def-store-migration (\"Ensure company owner uses roles\" :version 13)\n  (ensure-slot-boundp 'company 'owner)\n  (mapc #'ensure-company-using-roles\n        (bknr.datastore:class-instances 'company)))\n\n(defmethod company-owner ((self company))\n  (let ((owner (%company-owner self)))\n    (cond\n      ((eql :roles owner)\n       (roles:company-owner self))\n      (t\n       (warn \"Using old company-owner schema for ~a\" self)\n       owner))))\n\n(def-store-migration (\"Ensure company-admins is using roles\" :version 14)\n  (ensure-slot-boundp 'company 'admins)\n  (dolist (company (bknr.datastore:class-instances 'company))\n    (dolist (user (slot-value company 'admins))\n      (roles:ensure-has-role company user 'roles:admin))))\n\n(def-store-migration (\"Clear company-admins slot\" :version 15)\n  ;; We accidentally forgot to do this in the previous migration\n  (dolist (company (bknr.datastore:class-instances 'company))\n    (setf (slot-value company 'admins) nil)))\n\n(defmethod get-company-for-auth-provider ((installation oss-installation)\n                                          auth-provider)\n  (get-singleton-company installation))\n\n(defmethod roles:users-for-company ((company sub-company))\n  (union\n   (call-next-method)\n   (roles:users-for-company (company-parent company))))\n\n(defmethod roles:user-role ((company sub-company) user)\n  (or\n   (call-next-method)\n   (roles:user-role (company-parent company) user)))\n\n(def-store-migration (\"Add default value for emails-enabled-by-default-p\" :version 21)\n  (let ((default-value\n          (funcall\n           (or\n            (symbol-function (read-from-string \"screenshotbot/email-tasks/settings::emails-enabled-by-default-p\"))\n            (lambda (installation)\n              (declare (ignore installation))\n              t))\n           (installation))))\n    (ensure-slot-boundp 'company '%emails-enabled-by-default-p :value default-value)))\n\n(def-store-migration (\"Set invites slot to nil\" :version 22)\n  (loop for company in (bknr.datastore:class-instances 'company)\n        do (setf (slot-value company 'invites) nil)))\n\n\n(defmethod has-root-company-p ((a sub-company) b)\n  \"Check if B is an ancestor of A. Yeah the name is currently incorrect\nand needs to be tidied up.\"\n  (or\n   (eql a b)\n   (eql (company-parent a) b)))\n\n(defmethod has-root-company-p (a b)\n  (eql a b))\n"
  },
  {
    "path": "src/screenshotbot/model/constant-string.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/constant-string\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:deftransaction\n                #:persistent-class\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/misc/lists\n                #:with-batches))\n(in-package :screenshotbot/model/constant-string)\n\n(defvar *lock* (bt:make-lock))\n\n(defindex +string-index+\n  'unique-index\n  :slot-name '%str\n  :test #'equal)\n\n;; WARNING: It is unsafe to use CONSTANT-STRING as a key in an\n;; fset-index.  When loading from disk, the object will not be ready\n;; and so the index will be invalid. Perhaps we can make it sort by\n;; index. ins\n\n(with-class-validation\n  (defclass constant-string (store-object)\n    ((%str :type string\n           :initarg :str\n           :index-initargs (:test #'equal)\n           :index +string-index+\n           :index-reader constant-string-with-str\n           :reader constant-string-string))\n    (:metaclass persistent-class)))\n\n(defmethod constant-string ((str string))\n  (or\n   (constant-string-with-str str)\n   (bt:with-lock-held (*lock*)\n     (%constant-string-without-lock str))))\n\n(defun %constant-string-without-lock (str)\n  (or\n   (constant-string-with-str str)\n   (make-instance 'constant-string :str str)))\n\n(defmethod constant-string ((str constant-string))\n  str)\n\n(defmethod constant-string ((str null))\n  nil)\n\n(defmethod constant-string-string ((str string))\n  \"Convenience during migrations.\"\n  str)\n\n(defmethod constant-string-string ((str null))\n  str)\n\n(defmethod print-object ((str constant-string)\n                         stream)\n  (print-object (constant-string-string str)\n                stream))\n\n\n(defmethod fset:compare ((self constant-string) two)\n  (fset:compare\n   (constant-string-string self)\n   two))\n\n(defmethod fset:compare ((one string) (two constant-string))\n  (fset:compare\n   one\n   (constant-string-string two)))\n\n(defmethod fset:compare ((one constant-string) (two constant-string))\n  (fset:compare\n   (constant-string-string one)\n   (constant-string-string two)))\n\n(defmethod fset:compare ((self constant-string) (two store-object))\n  :unequal)\n\n(defmethod fset:compare ((two store-object) (self constant-string))\n  :unequal)\n\n(deftransaction tx-ensure-slot-constant-string (objs slot-name)\n  (dolist (obj objs)\n    (let ((val (slot-value obj slot-name)))\n      (when (typep val 'string)\n        (setf (slot-value obj slot-name)\n              (%constant-string-without-lock val))))))\n\n(defun ensure-slot-constant-string (objs slot-name)\n  (with-batches (objs objs :batch-size 1000)\n    (bt:with-lock-held (*lock*)\n      (tx-ensure-slot-constant-string objs slot-name))))\n\n"
  },
  {
    "path": "src/screenshotbot/model/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/core\n  (:use #:cl #:alexandria #:util)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:with-transaction\n                #:store-object\n                #:deftransaction)\n  (:import-from #:screenshotbot/model/api-key\n                #:generate-api-key\n                #:generate-api-secret)\n  (:import-from #:util/misc/lists\n                #:with-batches)\n  (:export\n   #:has-created-at\n   #:ensure-slot-boundp\n   #:generate-api-key\n   #:generate-api-secret\n   #:%created-at\n   #:non-root-object))\n(in-package :screenshotbot/model/core)\n\n(defclass has-created-at (persistent-class)\n  ())\n\n(deftransaction\n    set-created-at (obj val)\n  (check-type obj store-object)\n  (check-type val number)\n  (setf (%created-at obj) val))\n\n(defmethod make-instance :around ((sym has-created-at) &rest args)\n  (let ((res (call-next-method)))\n    (set-created-at res (get-universal-time))\n    res))\n\n(deftransaction tx-ensure-slot-boundp (items slot value)\n  (loop for item in items do\n      (unless (slot-boundp item slot)\n        (setf (slot-value item slot) value))))\n\n(defun ensure-slot-boundp (item slot &key value)\n  (let ((items (cond\n                 ((listp item)\n                  item)\n                 ((symbolp item)\n                  (bknr.datastore:class-instances item))\n                 (t\n                  (list item)))))\n    (with-batches (items items :batch-size 1000)\n      (tx-ensure-slot-boundp items slot value))))\n\n\n\n(defun print-type-histogram ()\n  (let* ((types (mapcar 'type-of (bknr.datastore:all-store-objects)))\n         (names (remove-duplicates types)))\n    (loop for name in names do\n      (format t \"~a: ~a~%\" name (count name types)))))\n\n(defclass non-root-object (store-object)\n  ()\n  (:metaclass persistent-class)\n  (:documentation \"An object that is safe to garbage collect if it's no longer reachable\"))\n"
  },
  {
    "path": "src/screenshotbot/model/counter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/counter\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:bknr.datastore\n                #:deftransaction\n                #:store-object\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:bknr.indices\n                #:index-get\n                #:hash-index\n                #:unique-index)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index))\n(in-package :screenshotbot/model/counter)\n\n(defindex +counter-index+\n  'fset-unique-index\n  :slots '(%company %name))\n\n\n(defclass counter (store-object)\n  ((%company :initarg :company\n             :reader counter-company)\n   (%name :initarg :name\n          :reader counter-name)\n   (next-value :initarg :next-value\n               :initform 1))\n  (:class-indices (counter-index\n                   :index +counter-index+))\n  (:metaclass persistent-class))\n\n(deftransaction tx-next-counter (company name)\n  (let ((counter (or\n                  ())))))\n\n(defmacro defcounter (name ())\n  (let* ((next-counter (intern (format nil \"NEXT-~a\" name)))\n         (tx-next-counter (intern (format nil \"%TX-~a\" next-counter)))\n         (index (intern (format nil \"+~a-INDEX+\" name))))\n    `(progn\n       (defindex ,index\n         'unique-index\n         :slot-name '%company)\n       (defclass ,name (store-object)\n         ((%company :initarg :company\n                    :index ,index)\n          (%next-value :initform 1\n                       :accessor next-value))\n         (:metaclass persistent-class))\n\n       (deftransaction ,tx-next-counter (company)\n         (tx-next-counter-impl ,index ',name company))\n\n       (defun ,next-counter (company)\n         (,tx-next-counter company)))))\n\n(defun tx-next-counter-impl (index class-name company)\n  (let ((counter (or\n                  (index-get index company)\n                  (make-instance class-name :company company))))\n    (prog1\n        (next-value counter)\n      (incf (next-value counter)))))\n\n(defmethod next-counter ((self company) name)\n  \"Get the next auto-increment counter for the company and the given name\"\n  (tx-next-counter self name))\n\n"
  },
  {
    "path": "src/screenshotbot/model/deprecated.lisp",
    "content": "\n"
  },
  {
    "path": "src/screenshotbot/model/downloadable-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/downloadable-run\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:blob-pathname\n                #:persistent-class\n                #:blob)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:import-from #:screenshotbot/user-api\n                #:screenshot-name\n                #:recorder-run-screenshots)\n  (:import-from #:screenshotbot/model/image\n                #:with-local-image)\n  (:import-from #:util/threading\n                #:make-thread)\n  (:import-from #:util/object-id\n                #:oid)\n  (:export\n   #:schedule\n   #:downloadable-run-state\n   #:downloadable-run\n   #:find-or-create-downloadable-run\n   #:find-downloadable-run))\n(in-package :screenshotbot/model/downloadable-run)\n\n(defindex +run-index+\n  'fset-unique-index\n  :slot-name '%run)\n\n(defvar *lock* (bt:make-lock))\n\n(with-class-validation\n  (defclass downloadable-run (blob)\n    ((%run :initarg :run\n           :index +run-index+\n           :index-reader find-downloadable-run\n           :reader %run)\n     (%created-at\n      :initarg :created-at ;; for tests\n      :reader created-at)\n     (state :initform :pending\n            :accessor downloadable-run-state))\n    (:metaclass persistent-class)\n    (:default-initargs :created-at (get-universal-time))\n    (:documentation \"Represents a downloadable artifact, a zip/tar of all of the\nscreenshots in a run.\")))\n\n(defun find-or-create-downloadable-run (run &key (prepare nil))\n  (bt:with-lock-held (*lock*)\n    (or (find-downloadable-run run)\n        (let ((res (make-instance 'downloadable-run :run run)))\n          (when prepare\n            (schedule res))\n          res))))\n\n(defmethod build-archive ((self downloadable-run))\n  (zip:with-output-to-zipfile (zip-writer (blob-pathname self) :if-exists :supersede)\n    (dolist (screenshot (recorder-run-screenshots (%run self)))\n      (with-local-image (file screenshot)\n        (with-open-file (data file :direction :input :element-type '(unsigned-byte 8))\n          (zip:write-zipentry zip-writer\n                              (format nil \"~a/~a.png\"\n                                      (oid (%run self))\n                                      (screenshot-name screenshot))\n                              data))))))\n\n(defmethod schedule ((self downloadable-run))\n  (make-thread\n   (lambda ()\n     (unwind-protect\n          (build-archive self)\n       (setf (downloadable-run-state self) :done)))))\n\n(defun delete-old-runs ()\n  (let ((boundary (- (get-universal-time) (* 24 3600 7))))\n    (loop for dr in (bknr.datastore:class-instances 'downloadable-run)\n          do\n             (bt:with-lock-held (*lock*)\n               (when (< (created-at dr) boundary)\n                 (when (path:-e (blob-pathname dr))\n                   (delete-file (blob-pathname dr)))\n                 (bknr.datastore:delete-object dr))))))\n"
  },
  {
    "path": "src/screenshotbot/model/enterprise.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/enterprise\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:export #:enterprise-install\n           #:enterprise-install-domain\n           #:enterprise-install-by-domain\n           #:all-enterprise-installs))\n(in-package :screenshotbot/model/enterprise)\n\n(defindex +domain-index+\n  'fset-unique-index\n  :slot-name 'domain)\n\n(defclass enterprise-install (store-object)\n  ((domain :initarg :domain\n           :index +domain-index+\n           :index-reader enterprise-install-by-domain\n           :index-values all-enterprise-installs\n           :accessor enterprise-install-domain))\n  (:metaclass persistent-class))\n\n"
  },
  {
    "path": "src/screenshotbot/model/failed-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/failed-run\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:local-nicknames (#:channel #:screenshotbot/model/channel))\n  (:export\n   #:run-failed-on-commit-p))\n(in-package :screenshotbot/model/failed-run)\n\n(with-class-validation\n (defclass failed-run (store-object)\n   ((channel :initarg :channel\n             :reader failed-run-channel\n             :index-type hash-index\n             :index-reader failed-runs-for-channel)\n    (%company :initarg :company\n              :reader failed-run-company\n              :index-type hash-index\n              :index-reader failed-runs-for-company)\n    (commit :initarg :commit\n            :reader failed-run-commit)\n    (%ts :initarg :created-at\n         :reader created-at))\n   (:default-initargs :created-at (get-universal-time))\n   (:metaclass persistent-class)))\n\n(defmethod run-failed-on-commit-p ((channel channel:channel)\n                                   (commit string))\n  (loop for failed-run in (failed-runs-for-channel channel)\n        if (equal (failed-run-commit failed-run)\n                  commit)\n          return t))\n"
  },
  {
    "path": "src/screenshotbot/model/figma.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/figma\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:store-object\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:import-from #:bknr.indices\n                #:index-get)\n  (:import-from #:alexandria\n                #:when-let)\n  (:export\n   #:figma-link-channel\n   #:figma-link-screenshot-name\n   #:figma-link-image\n   #:figma-link\n   #:figma-link-url))\n(in-package :screenshotbot/model/figma)\n\n(defindex +channel-screenshot-index+\n  'fset-unique-index\n  :slots '(channel screenshot-name))\n\n\n(defclass figma-link (store-object)\n  ((channel :initarg :channel\n            :reader figma-link-channel)\n   (screenshot-name :initarg :screenshot-name\n                    :reader figma-link-screenshot-name)\n   (image :initarg :image\n          :reader figma-link-image)\n   (url :initarg :url\n        :reader figma-link-url))\n  (:class-indices (channel-screenshot-name-index\n                   :index +channel-screenshot-index+\n                   :slots (channel screenshot-name)))\n  (:metaclass persistent-class))\n\n\n(defun find-existing-figma-link (&key channel screenshot-name)\n  (index-get +channel-screenshot-index+\n             (list channel screenshot-name)))\n\n(defvar *lock* (bt:make-lock))\n\n(defun update-figma-link (&rest args &key channel screenshot-name &allow-other-keys)\n  (bt:with-lock-held (*lock*)\n    (when-let ((existing (find-existing-figma-link :channel channel :screenshot-name screenshot-name)))\n      (delete-object existing))\n    (apply #'make-instance\n           'figma-link\n           args)))\n"
  },
  {
    "path": "src/screenshotbot/model/finalized-commit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/finalized-commit\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:export\n   #:finalized-commit-company\n   #:finalized-commit\n   #:finalized-commit-hash\n   #:find-or-create-finalized-commit))\n(in-package :screenshotbot/model/finalized-commit)\n\n(defindex +commit-index+\n  'fset-set-index\n  :slot-name '%commit)\n\n(with-class-validation\n (defclass finalized-commit (store-object)\n   ((%company :initarg :company\n              :reader finalized-commit-company)\n    (%commit :initarg :commit\n             :index +commit-index+\n             :reader finalized-commit-hash\n             :index-reader %finalized-commits-for-commit))\n   (:metaclass persistent-class)))\n\n(defun find-finalized-commit (company commit)\n  \"Find an existing finalized-commit for the given company and commit SHA.\"\n  (fset:do-set (fc (%finalized-commits-for-commit commit))\n    (when (eql company (finalized-commit-company fc))\n      (return fc))))\n\n(defun commit-finalized-p (company commit)\n  (not (null (find-finalized-commit company commit))))\n\n(defun find-or-create-finalized-commit (company commit)\n  \"Find or create a finalized-commit for the given company and commit SHA.\n   Returns the existing finalized-commit if one exists, or creates a new one.\"\n  (or (find-finalized-commit company commit)\n      (make-instance 'finalized-commit\n                     :company company\n                     :commit commit)))\n"
  },
  {
    "path": "src/screenshotbot/model/image-comparer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/image-comparer\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/image\n                #:dimension=\n                #:image-dimensions\n                #:with-local-image\n                #:image=\n                #:base-image-comparer)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:get-non-alpha-pixels\n                #:magick-get-image-width\n                #:magick-get-image-height\n                #:with-wand\n                #:with-image-comparison)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/async\n                #:magick-future)\n  (:import-from #:lparallel\n                #:future\n                #:force)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:with-class-validation\n                #:defindex)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:local-nicknames (#:recorder-run #:screenshotbot/model/recorder-run))\n  (:export\n   #:make-image-comparer))\n(in-package :screenshotbot/model/image-comparer)\n\n(defclass cached-comparer ()\n  ())\n\n(defclass threshold-comparer (cached-comparer\n                              base-image-comparer)\n  ((threshold :initarg :threshold\n              :reader compare-threshold)\n   (tolerance :initarg :tolerance\n              :initform 0\n              :reader compare-tolerance)))\n\n(defmethod make-image-comparer (run)\n  (flet ((gt-0 (x)\n           \"Check if x is greater than zero, and is a number.\"\n           (and\n            (numberp x)\n            (> x 0))))\n   (let ((threshold (recorder-run:compare-threshold run))\n         (tolerance (recorder-run:compare-tolerance run)))\n     (cond\n       ((or\n         (gt-0 threshold)\n         (gt-0 tolerance))\n        (make-instance 'threshold-comparer\n                       :threshold (or threshold 0)\n                       :tolerance (or tolerance 0)))\n       (t\n        (make-instance 'base-image-comparer))))))\n\n\n(defmethod image= ((self threshold-comparer)\n                   image1\n                   image2\n                   masks)\n  (or\n   (call-next-method)\n\n   (and\n    (dimension=\n     (image-dimensions image1)\n     (image-dimensions image2))\n    (compare-with-threshold\n     self\n     image1\n     image2\n     masks))))\n\n(defmethod compare-with-threshold ((self threshold-comparer)\n                                   image1\n                                   image2\n                                   masks)\n  (with-local-image (file1 image1)\n    (with-local-image (file2 image2)\n      (force\n       (magick-future ()\n         (with-wand (before :file file1)\n           (let ((limit (floor (* (min 1.0 (compare-threshold self))\n                                  (magick-get-image-height before)\n                                  (magick-get-image-width before)))))\n             (with-wand (after :file file2)\n               (check-type (compare-tolerance self) number)\n               (with-image-comparison (before after\n                                       :result result\n                                       :pixel-tolerance (compare-tolerance self)\n                                       :in-place-p t)\n                 (let ((bad-pixels (get-non-alpha-pixels\n                                    result\n                                    ;; Why +2 instead of +1? I think it might\n                                    ;; be a bug somewhere in\n                                    ;; get-non-alpha-pixels, but +2 works for\n                                    ;; now.\n                                    :limit (+ 2 limit)\n                                    :masks masks)))\n                   (let ((bad-pixel-count (first (array-dimensions bad-pixels))))\n                     (<= bad-pixel-count limit))))))))))))\n\n(defindex *image-equal-cache*\n  'unique-index\n  :test #'equal\n  :slot-name '%key)\n\n\n(with-class-validation\n (defclass image-equal-cache (store-object)\n   ((%key :initarg :key\n          :index *image-equal-cache*\n          :index-reader image-equal-cache-for-key)\n    (%result :initarg :result\n             :reader image-equal-cache-result))\n   (:metaclass persistent-class)))\n\n\n(defvar *lock* (bt:make-lock))\n\n(defmethod image= :around ((self cached-comparer)\n                           image1\n                           image2\n                           masks)\n  (let* ((key (list\n               (type-of self)\n               (compare-threshold self)\n               (ignore-errors (compare-tolerance self))\n               image1\n               image2\n               masks))\n         (cache (image-equal-cache-for-key key)))\n    (cond\n      ((eql image1 image2)\n       ;; We could most likely just return T here, but this might\n       ;; be less error prone\n       (call-next-method))\n      (cache\n       (image-equal-cache-result cache))\n      (t\n       (let ((res (call-next-method)))\n         (bt:with-lock-held (*lock*)\n           (or\n            (image-equal-cache-for-key key)\n            (make-instance 'image-equal-cache\n                           :key key\n                           :result res)))\n         res)))))\n"
  },
  {
    "path": "src/screenshotbot/model/image-comparison.lisp",
    "content": "(defpackage :screenshotbot/model/image-comparison\n  (:use #:cl)\n  (:shadow #:find)\n  (:import-from #:bknr.datastore\n                #:store-object-id\n                #:snapshot-subsystem-async\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/transient-object\n                #:make-transient-clone\n                #:with-transient-copy)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:screenshotbot/model/image\n                #:find-image-by-oid\n                #:mask-rect-height\n                #:mask-rect-width\n                #:mask-rect-top\n                #:mask-rect-left\n                #:with-tmp-image-file\n                #:mask=\n                #:with-local-image\n                #:make-image\n                #:image)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:util/object-id\n                #:oid\n                #:oid-array)\n  (:import-from #:util/store\n                #:defsubsystem\n                #:add-datastore-cleanup-hook\n                #:object-store\n                #:location-for-oid)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:bknr.datastore\n                #:store-objects-with-class)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:calculate-difference-rmse\n                #:compare-wands\n                #:with-image-comparison\n                #:with-wand)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:bknr.datastore\n                #:deftransaction)\n  (:import-from #:bknr.datastore\n                #:store-subsystem-snapshot-pathname)\n  (:import-from #:bknr.datastore\n                #:encode-object)\n  (:import-from #:bknr.datastore\n                #:decode-object)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:bknr.datastore\n                #:restore-subsystem)\n  (:import-from #:bknr.datastore\n                #:close-subsystem)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:util/store/store\n                #:all-subsystem-objects)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:util/events\n                #:with-tracing)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:image-comparison\n   #:%image-comparisons-for-before\n   #:image-comparison-after\n   #:image-comparison-result\n   #:identical-p\n   #:image-comparison-difference-value))\n(in-package :screenshotbot/model/image-comparison)\n\n(with-transient-copy (transient-image-comparison abstract-image-comparison)\n  (defclass image-comparison (store-object)\n    ((before :initarg :before\n             :reader image-comparison-before\n             :index-type hash-index\n             :index-reader %image-comparisons-for-before\n             :relaxed-object-reference t)\n     (after :initarg :after\n            :reader image-comparison-after\n            :relaxed-object-reference t)\n     (masks :initarg :masks\n            :initform nil\n            :documentation \"DEPRECATED: do not use.\")\n     (difference-value :initarg :difference-value\n                       :initform nil\n                       :accessor image-comparison-difference-value\n                       :documentation \"The difference as computed as a value, currently only RMSE metric. Will be a DOUBLE or NIL for older image-comparisons where this was not computed.\")\n     (identical-p :initform nil\n                  :accessor identical-p\n                  :initarg :identical-p\n                  :documentation \"A result inducating that the images differ only in exif data\")\n     (result :initarg :result\n             :accessor image-comparison-result\n             :documentation \"The final image object\"))\n    (:metaclass persistent-class)))\n\n(defmethod fset:compare ((a transient-image-comparison)\n                         (b transient-image-comparison))\n  (fset:compare-slots a b\n                      #'image-comparison-before\n                      #'image-comparison-after))\n\n(defvar *stored-cache*\n  (fset:empty-set)\n  \"A cache of empty image-comparison\")\n\n(deftransaction %make-image-comparison (args)\n  (let ((imc (apply #'make-instance\n                    'transient-image-comparison\n                    args)))\n    (setf *stored-cache*\n          (fset:with *stored-cache* imc))\n    imc))\n\n(defun find-image-comparison-from-cache (&key before after)\n  (multiple-value-bind (exists-p value)\n      (fset:lookup\n       *stored-cache*\n       (make-instance 'transient-image-comparison\n                      :before before\n                      :after after))\n    (declare (ignore exists-p))\n    value))\n\n(defun make-image-comparison (&rest args)\n  (%make-image-comparison args))\n\n(deftransaction tx-remove-image-comparison (before after)\n  \"Deletes the image-comparison object with BEFORE and AFTER.\"\n  (setf *stored-cache*\n        (fset:less *stored-cache* (make-instance 'transient-image-comparison\n                                                 :before before\n                                                 :after after))))\n\n(defun remove-image-comparison (before after)\n  (tx-remove-image-comparison before after))\n\n(defun do-image-comparison (before-image\n                            after-image\n                            p)\n  \"Compares before-screenshot and after-screenshot, and saves the result image to P.\n\nReturns two values. If the images are identical, we return T for the\nprimary value, else we return NIL.\n\nSecond second value will be the RMSE difference between the two\nimages. If the first value was T, then this will always be 0.0\"\n  (with-tracing (:image-comparison)\n    (with-local-image (before-file before-image)\n      (with-local-image (after-file after-image)\n        (with-wand (before :file before-file)\n          (with-wand (after :file after-file)\n            (let ((rmse (calculate-difference-rmse before after)))\n             (let ((same-p (compare-wands before after p\n                                          :in-place-p t)))\n               (values same-p rmse)))))))))\n\n(defmethod find-image-comparison-on-images ((before image)\n                                            (after image)\n                                            &key only-cached-p)\n  \"Finds an existing image comparison for before and after, if it\n  doesn't exist calls creator with a temporary file. The creator\n  should create the image in the file provided. The creator should\n  returns true if the images are completely identical, or nil\n  otherwise\n\n  If CACHE-ONLY is T, we won't create the image-comparison, and should\n  respond quickly.\"\n  \n  (when (> (store-object-id before)\n           (store-object-id after))\n    (rotatef before after))\n\n  (unless (eql (company before)\n               (company after))\n    (error \"Trying to compare images from different companies\"))\n\n  (flet ((find ()\n           (let ((res (find-image-comparison-from-cache :before before :after after)))\n             (when (and res (not only-cached-p))\n              (maybe-populate-difference-value res))\n             res)))\n    (or\n     (find)\n     (unless only-cached-p\n       (with-tmp-image-file (:pathname p :type \"webp\" :prefix \"comparison\")\n         (multiple-value-bind (identical-p\n                               difference-value)\n             (do-image-comparison before after p)\n           (let* ((image (make-image :pathname p\n                                     :company (company after))))\n             (make-image-comparison\n              :before before\n              :after after\n              :identical-p identical-p\n              :difference-value difference-value\n              :result image))))))))\n\n(defun maybe-populate-difference-value (image-comparison)\n  \"As a migration strategy for T1547, if the difference-value is not\nalready present, let's recompute it. We expect this logic to not be\nhit too much in production.\"\n  (cond\n    ((image-comparison-difference-value image-comparison)\n     (values))\n    (t\n     (with-tmp-image-file (:pathname output-image :type \"webp\")\n       (multiple-value-bind (identical-p difference-value)\n           (do-image-comparison (image-comparison-before image-comparison)\n             (image-comparison-after image-comparison)\n             output-image)\n         (declare (ignore identical-p))\n         (setf (image-comparison-difference-value image-comparison)\n               difference-value))))))\n\n\n(defclass image-comparison-subsystem ()\n  ())\n\n(defconstant +snapshot-version+ 1)\n\n(defun gc-comparisons ()\n  (fset:do-set (var *stored-cache*)\n    (flet ((destroyed? (obj)\n             (or (not obj)\n                 (and\n                  (typep obj 'store-object)\n                  (object-destroyed-p obj)))))\n      (when (or (destroyed? (image-comparison-before var))\n                (destroyed? (image-comparison-after var))\n                (destroyed? (image-comparison-result var)))\n        (setf *stored-cache*\n              (fset:less *stored-cache* var))))))\n\n(defun write-snapshot (pathname stored-cache)\n  (log:info \"Snapshotting image-comparison subsystem\")\n  (with-open-file (output pathname\n                          :direction :output\n                          :element-type '(unsigned-byte 8)\n                          :if-does-not-exist :create\n                          :if-exists :supersede)\n    (encode +snapshot-version+ output)\n    ;; write the number of objects too\n    (encode (fset:size stored-cache) output)\n    (labels ((write-single (obj)\n               (encode (image-comparison-before obj) output)\n               (encode (image-comparison-after obj) output)\n               (encode (image-comparison-result obj) output)\n               (encode\n                (if (identical-p obj) 1 0)\n                output)))\n      (fset:do-set (next stored-cache)\n        (write-single next)))))\n\n(defmethod snapshot-subsystem-async ((store bknr.datastore:store) (self image-comparison-subsystem))\n  (gc-comparisons)\n  (let ((pathname (store-subsystem-snapshot-pathname store self))\n        (stored-cache *stored-cache*))\n    (lambda ()\n      (Write-snapshot pathname stored-cache))))\n\n(defmethod restore-subsystem ((store bknr.datastore:store) (self image-comparison-subsystem) &key until)\n  (declare (ignore until) (optimize (debug 3)))\n  (log:info \"Restoring image-comparison subsystem\")\n  (let ((snapshot-file (store-subsystem-snapshot-pathname store self)))\n    (when (probe-file snapshot-file)\n      (with-open-file (stream snapshot-file\n                              :direction :input\n                              :element-type '(unsigned-byte 8))\n        (let ((version (decode stream)))\n          (unless (<= version +snapshot-version+)\n            (error \"Unsupported version for image comparisons snapshot: ~a\" version))\n          (let ((size (decode stream)))\n            (flet ((read-single ()\n                     (make-instance 'transient-image-comparison\n                                    :before (decode stream)\n                                    :after (decode stream)\n                                    :result (decode stream)\n                                    :identical-p (ecase (decode stream)\n                                                   (1 t)\n                                                   (0 nil)))))\n              (let ((objs (loop for i from 0 below size\n                                for obj = (read-single)\n                                ;; When we copy this snapshot to\n                                ;; different server, some images may\n                                ;; not be available.\n                                #+lispworks #+lispworks\n                                if (and\n                                    (image-comparison-before obj)\n                                    (image-comparison-after obj))\n                                  collect obj)))\n                (setf *stored-cache*\n                      (fset:convert 'fset:set objs)))))))))\n  (log:info \"Loaded ~a image-comparison objects\" (fset:size *stored-cache*)))\n\n(defmethod all-subsystem-objects ((self image-comparison-subsystem))\n  (fset:convert 'list *stored-cache*))\n\n(defmethod close-subsystem ((store bknr.datastore:store) (self image-comparison-subsystem))\n  (setf *stored-cache* (fset:empty-set)))\n\n(defsubsystem image-comparison-subsystem :priority 20)\n\n(def-store-migration (\"Add company slot to previous results\" :version 11)\n  (fset:do-set (imc *stored-cache*)\n    (alexandria:when-let ((after (image-comparison-after imc))\n                          (result (image-comparison-result imc)))\n      (unless (company result)\n        (setf (company result) (company after))))))\n"
  },
  {
    "path": "src/screenshotbot/model/image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/image\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/view\n        #:screenshotbot/magick\n        #:screenshotbot/model/core\n        #:screenshotbot/mask-rect-api\n        #:screenshotbot/screenshot-api)\n  (:import-from #:util\n                #:make-url\n                #:oid-bytes\n                #:oid\n                #:object-with-oid)\n  (:import-from #:screenshotbot/server\n                #:document-root\n                #:*root*)\n  (:import-from #:screenshotbot/screenshot-api\n                #:local-image)\n  (:import-from #:screenshotbot/model/company\n                #:find-image-by-id\n                #:company\n                #:verified-p ;; todo: remove, will cause conflict\n                #:image-oid-cache)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:with-transaction\n                #:store-object\n                #:persistent-class)\n  (:import-from #:screenshotbot/magick\n                #:ping-image-dimensions\n                #:magick)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:util/hash-lock\n                #:with-hash-lock-held\n                #:hash-lock)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:magick-exception\n                #:get-non-alpha-pixels\n                #:with-image-comparison\n                #:ping-image-metadata\n                #:with-wand)\n  (:import-from #:util/object-id\n                #:make-oid\n                #:oid\n                #:oid-arr\n                #:oid-p\n                #:%make-oid\n                #:oid-array)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:util/store\n                #:defindex\n                #:def-store-local\n                #:location-for-oid\n                #:with-class-validation)\n  (:import-from #:screenshotbot/cdn\n                #:make-image-cdn-url)\n  (:import-from #:screenshotbot/model/transient-object\n                #:cannot-make-transient\n                #:make-transient-clone\n                #:with-transient-copy)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:util/copy-file\n                #:copy-file-fast)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:util/store/simple-object-snapshot\n                #:simple-object-snapshot)\n  (:import-from #:util/store/store\n                #:defindex\n                #:fast-ensure-directories-exist)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:util/cron\n                #:def-cron)\n  ;; classes\n  (:export\n   #:image\n   #:image-blob\n   #:mask-rect\n   #:local-image)\n  ;;methods\n  (:export\n   #:with-local-image\n   #:image=\n   #:image-public-url\n   #:image-hash\n   #:image-blob-get\n   #:image-blob\n   #:verified-p\n   #:mask-rect-left\n   #:rect-as-list\n   #:mask-rect-width\n   #:mask-rect-top\n   #:mask-rect-height)\n  (:export\n   #:image-dimensions\n   #:dimension\n   #:dimension-height\n   #:dimension-width\n   #:image-format\n   #:ping-image-dimensions\n   #:find-image\n   #:make-image\n   #:image-filesystem-pathname\n   #:update-image\n   #:mask=\n   #:image-metadata\n   #:find-image-by-oid\n   #:base-image-comparer\n   #:dimension=\n   #:image-size\n   #:invalid-image))\n(in-package :screenshotbot/model/image)\n\n(hex:declare-handler 'image-blob-get)\n\n(defvar *image-creation-hooks*\n  nil)\n\n(define-condition invalid-image (error)\n  ()\n  (:documentation \"The image we're looking at is invalid\"))\n\n#+screenshotbot-oss\n(with-class-validation\n  (defclass image-blob (bknr.datastore:blob)\n    ()\n    (:metaclass persistent-class)))\n\n(defindex +image-oid-index+ 'unique-index\n  :test 'equalp\n  :slot-name 'oid)\n\n(defindex +image-hash-index+ 'hash-index\n  :test #'equalp\n  :slot-name 'hash)\n\n(defparameter +image-state-filesystem+ 1\n  \"Image is saved on the local filesystem\")\n\n;; Some of these slots are limited to screenshotbot-oss. This is for\n;; backward compatibility in the OSS version, where we don't have a\n;; schema migration process.\n(with-transient-copy (transient-image abstract-image\n                      :extra-transient-slots (#-screenshotbot-oss blob))\n  (defclass image (store-object)\n    (#+screenshotbot-oss\n     (link :initarg :link)\n     (oid :accessor util/object-id:oid-struct-or-array\n          :initarg :oid\n          :reader image-oid\n          :index +image-oid-index+\n          :index-reader %find-image-by-oid)\n     (hash :initarg :hash\n           :reader image-hash ;; NOTE: Returns a vector!\n           :index +image-hash-index+\n           :index-reader images-for-original-hash)\n     (state :initarg :state\n            :initform nil\n            :accessor %image-state\n            :documentation \"The state of the image. We use integers\n           because they're cheaper to parse in bknr.datastore, and\n           image objects are the largest number of objects in the\n           store.\")\n     #+screenshotbot-oss\n     (blob\n      :initarg :blob\n      :relaxed-object-reference t\n      :accessor %image-blob ;; don't access directly!\n      :initform nil)\n     (company\n      :initarg :company\n      :accessor company\n      :initform nil)\n     (verified-p\n      :accessor verified-p\n      :initform nil\n      :initarg :verified-p\n      :documentation \"If we have verified that this image was uploaded\")\n     (%size\n      :initarg :size\n      :accessor %image-size\n      :documentation \"The size of the image in bytes\")\n     #+screenshotbot-oss\n     (content-type :initarg :content-type\n                   :reader image-content-type))\n    (:metaclass persistent-class)\n    (:default-initargs :oid (%make-oid)\n                       :size 0)))\n\n(defun imagep (image)\n  (typep image 'abstract-image))\n\n(defun check-imagep (image)\n  (assert (imagep image))\n  image)\n\n(defmethod find-image-by-oid ((oid string))\n  (find-image-by-oid\n   ;; Convert to an array\n   (mongoid:oid oid)))\n\n(defmethod find-image-by-oid ((oid array))\n  (find-image-by-oid\n   ;; Convert to an OID object\n   (make-oid :arr oid)))\n\n(defmethod find-image-by-oid ((oid oid))\n  (%find-image-by-oid oid))\n\n(defmethod find-image-by-id ((company company) id)\n  (let ((obj (find-image-by-oid id)))\n    (typecase obj\n      (local-image\n       obj)\n      (t\n       (assert (eql company (company obj)))\n       obj))))\n\n\n(defmethod make-transient-clone ((image image))\n  (make-instance 'transient-image\n                 :oid (oid-array image)\n                 :hash (image-hash image)\n                 :state (%image-state image)\n                 :company (?. oid (company image))\n                 :verified-p (verified-p image)))\n\n(defmethod find-image ((company company) (hash string))\n  (loop for image in (append\n                      (images-for-original-hash hash)\n                      (images-for-original-hash (ironclad:hex-string-to-byte-array hash)))\n        if (and\n            (eql (company image) company)\n            (verified-p image))\n          return image))\n\n(defmethod find-image ((company company) (hash array))\n  (find-image company\n              (ironclad:byte-array-to-hex-string hash)))\n\n(defmethod print-object ((self image) stream)\n  (format stream \"#<IMAGE ~a>\" (store-object-id self)))\n\n(with-transient-copy (transient-mask-rect abstract-mask-rect)\n  (defclass mask-rect (store-object)\n    ((top :initarg :top\n          :accessor mask-rect-top)\n     (left :initarg :left\n           :accessor mask-rect-left)\n     (height :initarg :height\n             :accessor %mask-rect-height)\n     (width :initarg :width\n            :accessor %mask-rect-width))\n    (:metaclass persistent-class)))\n\n(defmethod mask-rect-top :around ((mask abstract-mask-rect))\n  (let ((top (call-next-method)))\n   (min\n    top\n    (+ top (%mask-rect-height mask)))))\n\n(defmethod mask-rect-left :around ((mask abstract-mask-rect))\n  (let ((left (call-next-method)))\n    (min\n     left\n     (+ left (%mask-rect-width mask)))))\n\n(defmethod mask-rect-height ((mask abstract-mask-rect))\n  (abs (%mask-rect-height mask)))\n\n(defmethod mask-rect-width ((mask abstract-mask-rect))\n  (abs (%mask-rect-width mask)))\n\n(defmethod mask= ((a abstract-mask-rect) (b abstract-mask-rect))\n  (or\n   (eql a b)\n   (every #'identity\n    (loop for fn in (list #'mask-rect-top\n                          #'mask-rect-left\n                          #'mask-rect-height\n                          #'mask-rect-width)\n          collect\n          (eql\n           (funcall fn a)\n           (funcall fn b))))))\n\n(defmethod make-transient-clone ((self mask-rect))\n  (make-instance 'transient-mask-rect\n                 :top (mask-rect-top self)\n                 :left (mask-rect-left self)\n                 :height (mask-rect-height self)\n                 :width (mask-rect-width self)))\n\n(defmethod rect-as-list ((rect mask-rect))\n  (with-slots (top left height width) rect\n    (list top left height width)))\n\n(defmethod image-filesystem-pathname ((image abstract-image))\n  \"If the image is stored on the current file system, return the\n  pathname to the image. If it's stored remotely, raise an error!\"\n  (cond\n    ((eql +image-state-filesystem+ (%image-state image))\n     (local-location-for-oid (oid-array image)))\n    #+screenshotbot-oss\n    ((%image-blob image)\n     (bknr.datastore:blob-pathname (%image-blob image)))\n    (t\n     ;; the file most likely does not exist at this point, but this is\n     ;; what you're asking for!\n     (local-location-for-oid (oid-array image)))))\n\n(defmethod image-not-uploaded-yet-p ((image image))\n  (and\n   (eql nil (%image-state image))))\n\n(defmethod image-not-uploaded-yet-p ((image transient-image))\n  nil)\n\n(defclass local-image (image)\n  ((url :initarg :url\n        :accessor local-image-url))\n  (:metaclass persistent-class)\n  (:default-initargs :oid (%make-oid))\n  (:documentation \"An IMAGE, that's used only for testing purposes locally\"))\n\n(define-condition image-error (error)\n  ((image :initarg :image)))\n\n(define-condition no-image-uploaded-yet (image-error)\n  ()\n  (:report (lambda (self stream)\n             (format stream \"No image uploaded for ~a\" (slot-value self 'image)))))\n\n(define-condition image-file-deleted (image-error)\n  ()\n  (:report (lambda (self stream)\n             (format stream \"Image file deleted for ~a\" (slot-value self 'image)))))\n\n(defmethod %with-local-image ((image abstract-image) fn)\n  (cond\n    ((image-not-uploaded-yet-p image)\n     (error 'no-image-uploaded-yet :image image))\n    (t\n     (multiple-value-bind (file) (image-filesystem-pathname image)\n       (unless (path:-e file)\n         (error 'image-file-deleted :image image))\n       (funcall fn file)))))\n\n;; todo: remove\n(defmethod %with-local-image ((image image) fn)\n  (call-next-method))\n\n(defmethod delete-image ((image image))\n  \"Delete the image and the image files associated with it.\"\n  (let ((file (image-filesystem-pathname image)))\n    (bknr.datastore:delete-object image)\n    (when (path:-e file)\n      (delete-file file))))\n\n(defmethod %with-local-image ((image local-image) fn)\n  ;; this could be bad if users have a way of creating local-images,\n  ;; but they don't. It's always created in code for demo\n  ;; purposes. (TODO: We should remove that logic, and use real images\n  ;; instead).\n  (funcall fn (asdf:system-relative-pathname\n               :screenshotbot\n               (format nil \"static~a\" (local-image-url image)))))\n\n(defmacro with-local-image ((file screenshot) &body body)\n  `(flet ((body (,file) ,@body))\n     (%with-local-image ,screenshot #'body)))\n\n(defun px-in-mask-p (i j mask)\n  (declare (optimize (speed 3) (safety 0))\n           (type fixnum i j))\n  (and\n   (<= (mask-rect-top mask)\n       i\n       (+ (mask-rect-top mask) (mask-rect-height mask) -1))\n   (<= (mask-rect-left mask)\n       j\n       (+ (mask-rect-left mask) (mask-rect-width mask) -1))))\n\n\n(defun local-location-for-oid (oid)\n  \"Figure out the local location for the given OID\"\n  (location-for-oid\n   #P\"image-blobs/\"\n   oid))\n\n(defun metadata-location-for-oid (oid)\n  (location-for-oid\n   #P \"cl-store/image-metadata/\"\n   oid))\n\n(defun make-image (&rest args &key hash blob pathname\n                                oid ;; for test convenience\n                                for-tests &allow-other-keys)\n  (when blob\n    (error \"don't specify blob\"))\n  (unless (or hash pathname)\n    (error \"Must provide at least one of hash or pathname\"))\n  (let* ((args (alexandria:remove-from-plist args :pathname :for-tests))\n         (oid (or oid (%make-oid)))\n         (hash (cond\n                 ((stringp hash)\n                  (ironclad:hex-string-to-byte-array hash))\n                 (t\n                  hash))))\n    (multiple-value-bind (image-file) (local-location-for-oid oid)\n      ;; TODO: copy-overwriting-target could be a lot more efficient in\n      ;; many cases.\n      (when pathname\n        (assert (path:-e pathname))\n        (copy-file-fast pathname image-file))\n\n      (apply #'make-instance 'image\n               :oid oid\n               :state (cond\n                        (pathname\n                         +image-state-filesystem+))\n               :size (when pathname\n                       (trivial-file-size:file-size-in-octets pathname))\n               :hash (cond\n                       (hash hash)\n                       (pathname\n                        (md5-file image-file))\n                       (t (error \"must provide hash or pathname\")))\n               args))))\n\n(define-condition image-reuploaded-warning (warning)\n  ()\n  (:report \"An image was reuploaded\"))\n\n(defvar *image-upload-hash-lock* (make-instance 'hash-lock))\n\n(defmethod update-image ((image image) &key pathname)\n  (assert pathname)\n  (with-transaction ()\n    (setf (%image-state image)\n          +image-state-filesystem+))\n  ;; The UIOP:WITH-STAGING-PATHNAME only protects against program crashes,\n  ;; the lock protects against concurrent attempts.\n  (with-hash-lock-held (image *image-upload-hash-lock*)\n    (multiple-value-bind (dest) (image-filesystem-pathname image)\n      (when (path:-e dest)\n        (warn 'image-reuploaded-warning))\n      (uiop:with-staging-pathname (dest dest)\n        (uiop:copy-file pathname dest))\n      (setf\n       (%image-size image)\n       (trivial-file-size:file-size-in-octets pathname))\n      dest)))\n\n(defindex +image-1-index+\n  'hash-index\n  :slot-name 'image-1\n  :test #'equalp)\n\n(with-class-validation\n  (defclass content-equal-result (store-object)\n    ((image-1 :initarg :image-1\n              :index +image-1-index+\n              :index-reader content-equal-results-for-image-1)\n     (image-2 :initarg :image-2\n              :reader image-2)\n     (masks :initarg :masks\n            :reader masks)\n     (result :initarg :result\n             :reader result))\n    (:metaclass persistent-class)\n    (:documentation \"Comparing two images by content can be slow. This\n  caches the result of such comparisons.\")))\n\n(defun clear-content-equal-results ()\n  (mapc #'bknr.datastore:delete-object\n          (bknr.datastore:store-objects-with-class 'content-equal-result)))\n\n(defvar *content-equal-hash-lock* (make-instance 'hash-lock))\n\n(define-condition slow-image-comparison ()\n  ())\n\n(defun images-equal-by-magick (img1 img2)\n  \"Use ImageMagick to check if the two images have identical contents\"\n  (log:info \"Comparing images with magick: ~a ~a\" img1 img2)\n  (with-local-image (file1 img1)\n    (with-local-image (file2 img2)\n      (compare-image-files (magick) file1 file2))))\n\n(defun images-equal-by-content-p (img1 img2 &key masks)\n  (push-event :images-equal-by-content\n              :img1 (oid img1))\n  (with-hash-lock-held (img1 *content-equal-hash-lock*)\n    (let ((existing-results (content-equal-results-for-image-1 (oid img1 :stringp nil))))\n      (log:info \"existing results: ~S\" existing-results)\n      (loop for result in existing-results\n            if (and (equalp\n                     (oid img2 :stringp nil)\n                     (image-2 result))\n                    (equal masks (masks result)))\n              do (return (result result))\n            finally\n               (return\n                 (flet ((save-result (result)\n                          (make-instance 'content-equal-result\n                                          :image-1 (oid img1 :stringp nil)\n                                          :image-2 (oid img2 :stringp nil)\n                                          :masks masks\n                                          :result result)\n                          result))\n                   (log:info \"[slow-path] checking images ~s, ~s with masks ~s\" img1 img2 masks)\n                   (signal 'slow-image-comparison)\n                   (save-result\n                    (with-local-image (file1 img1)\n                      (with-local-image (file2 img2)\n                        (with-wand (wand1 :file file1)\n                          (with-wand (wand2 :file file2)\n                            (with-image-comparison (wand1 wand2\n                                                    :in-place-p t\n                                                    :result result)\n                              (eql\n                               0\n                               (first (array-dimensions (get-non-alpha-pixels\n                                                         result\n                                                         :limit 1\n                                                         :masks masks))))))))))))))))\n\n(defclass base-image-comparer ()\n  ())\n\n(defmethod image= ((self base-image-comparer) img1 img2 masks)\n  \"Check if the two images have the same contents. Looks at both file\n  hash and image contents\"\n  (assert (image-hash img1))\n  (assert (image-hash img2))\n  (or\n   (equalp (image-hash img1)\n           (image-hash img2))\n   ;; if the hash's don't match, check the perceptual hash. This is\n   ;; slow, so make sure we're only doing this if absolutely required\n   (when (or masks\n             ;; A clever hack for allowing us to migrate from PNG to\n             ;; WEBP without the user ever noticing. As long as the\n             ;; PNG and the WEBP images are both lossless, they should\n             ;; result in identical images.\n             #+nil\n             (not (string= (image-format img1)\n                           (image-format img2))))\n     (images-equal-by-content-p img1 img2 :masks masks))))\n\n(defmethod image-hash ((image local-image))\n  ;; this is probably only used for tests... hopefully doesn't hit in\n  ;; prod.\n  (with-local-image (im image)\n    (md5-file im)))\n\n(defgeneric image-public-url (image &key size type originalp)\n  (:documentation \"Note that the main implementation of this is in dashboard/image\"))\n\n(defmethod image-public-url ((image image) &key size type originalp)\n  (call-next-method))\n\n(defmethod image-local-url ((image image))\n  (image-public-url image))\n\n(defmethod image-public-url ((image local-image) &key &allow-other-keys)\n  (local-image-url image))\n\n(defmethod auth:can-view ((image image) user)\n  (auth:can-view-with-normal-viewer-context\n   user image))\n\n(defmethod auth:can-viewer-view (vc (image image))\n  (auth:can-viewer-view vc (company image)))\n\n(defmethod auth:can-viewer-view (vc (image local-image))\n  ;; Currently local-images don't have :company attached to it. We\n  ;; also don't use local-images for much except the demo image, so we\n  ;; might as well allow anyone to view it. See T1260\n  t)\n\n(defclass metadata ()\n  ((image-format :initarg :image-format\n                 :reader metadata-image-format)\n   (dimensions :initarg :dimensions\n               :reader metadata-image-dimensions)))\n\n(defclass dimension ()\n  ((height :initarg :height\n           :reader dimension-height)\n   (width :initarg :width\n          :reader dimension-width)))\n\n(defmethod dimension= (dim1 dim2)\n  (and\n   (eql (dimension-height dim1)\n        (dimension-height dim2))\n   (eql (dimension-width dim1)\n        (dimension-width dim2))))\n\n(def-store-local *metadata-cache* (make-hash-table))\n\n(def-easy-macro with-invalid-image-handling (&fn fn)\n  (handler-bind ((magick-exception (lambda (e)\n                                     ;; in the crash in question, this was \"NoDecodeDelegateForThisImageFormat\"\n                                     ;; on thecharmer, this is \"no decode delegate for this image format\"\n                                     ;; I should try to confirm out why there's this disparity.\n                                     (when (cl-ppcre:scan-to-strings \".*no.*decode.*delegate.*\"\n                                                                     (str:downcase (princ-to-string e)))\n                                       (error 'invalid-image)))))\n    (fn)))\n\n(defmethod image-metadata ((image abstract-image))\n  (with-invalid-image-handling ()\n   (with-extras ((\"image\" image))\n     (util:or-setf\n      (gethash image *metadata-cache*)\n      (with-local-image (file image)\n        (destructuring-bind (width height type)\n            (ping-image-metadata (magick) file)\n          (assert (member type '(\"WEBP\" \"PNG\" \"JPEG\"\n                                 \"JXL\" \"HEIC\")\n                          :test #'string=))\n          (make-instance 'metadata\n                         :image-format (intern type \"KEYWORD\")\n                         :dimensions\n                         (make-instance 'dimension\n                                        :height height\n                                        :width width))))))))\n\n(defmethod image-dimensions (image)\n  (metadata-image-dimensions (image-metadata image)))\n\n(defun image-file-dimensions (file)\n  (destructuring-bind (width height) (ping-image-dimensions (magick) file)\n      (make-instance 'dimension\n                      :width width\n                      :height height)))\n\n(defun image-format (image)\n  \"Get the image format of the file. This looks at the magic in the\nfile content to determine the file type, not the pathname's\ntype. Example output could be either :PNG or :WEBP. If we can't\nrecognized the file, we'll return nil.\"\n  (metadata-image-format (image-metadata image)))\n\n\n(defun ensure-images-have-hash ()\n  \"Used as a migration to fix an issue with images having no hash\"\n  (let ((images (reverse\n                 (loop for image in (bknr.datastore:store-objects-with-class 'image)\n                       unless (image-hash image)\n                         collect image))))\n    (loop for image in images\n          do\n             (log:info \"looking at: ~a\" image)\n             (with-local-image (file image)\n               (let ((hash (md5-file file)))\n                 (log:info \"Got hash: ~a\" hash)\n                 (with-transaction ()\n                   (setf (slot-value image 'hash) hash))\n                 hash)))))\n\n(defun delete-image-blob (oid))\n\n(defun touch (file)\n  (ensure-directories-exist file)\n  (with-open-file (stream file :direction :output)\n    (declare (ignore stream))))\n\n(defun img-tmp-dir ()\n  (let* ((dir (path:catdir\n               (bknr.datastore::store-directory\n                bknr.datastore:*store*)\n               \"screenshotbot-tmp/\"))\n         (keep-file (path:catfile dir \".keep\")))\n    (cond\n      ((path:-e keep-file)\n       dir)\n      (t\n       ;; Avoid leaving an empty directory around, otherwise we might\n       ;; delete it during migrations etc.\n       (touch keep-file)\n       dir))))\n\n(defmacro with-tmp-image-file (args &body body)\n  \"Just like uiop:with-temporary-file, but uses a directory which is\n suitable for use with make-image. i.e. it will be on the same\n filesystem so we can hardlink instead of copying the image over if\n needed.\"\n  `(uiop:with-temporary-file (:directory (img-tmp-dir) ,@args)\n     ,@body))\n\n(defun soft-expiration-time (image &key (months 3))\n  (let* ((oid (cond\n                ((oid-p image)\n                 image)\n                (t\n                 (oid image :stringp nil))))\n         (delta (- (mod (aref (oid-arr oid) 11) 64) 32))\n         (oid-time (local-time:unix-to-timestamp\n                    (mongoid:get-timestamp (oid-arr oid))))\n         (midpoint (local-time:timestamp+\n                     oid-time (* months 30) :day)))\n    (local-time:timestamp+ midpoint delta :day)))\n\n\n(defun all-soft-expired-images (&key (months 3))\n  (let ((now (local-time:now)))\n    (loop for im in (class-instances 'image)\n          if (local-time:timestamp<\n              (soft-expiration-time im :months months)\n              now)\n            collect im)))\n\n(defmethod image-size (image)\n  (or\n   (%image-size image)\n   0))\n\n;;(length (all-soft-expired-images))\n\n(defmethod bknr.datastore:make-object-snapshot ((self image))\n  (make-instance 'simple-object-snapshot\n                 :object self))\n\n(auto-restart:with-auto-restart ()\n  (defun %fix-one-image-v33 (image)\n    (when (eql 0 (random 1000))\n     (log:info \"Doing: ~a\" image))\n    (unless (%image-size image)\n      (setf\n       (%image-size image)\n       (handler-case\n           (with-local-image (file image)\n             (trivial-file-size:file-size-in-octets file))\n         (error (e)\n           (log:info \"Error fixing: ~a, ~a\" image e)))))))\n\n(def-store-migration (\"populate image sizes\" :version 33)\n  (ensure-slot-boundp 'image '%size)\n  (loop for image in (bknr.datastore:class-instances 'image)\n        do\n           (%fix-one-image-v33 image)))\n\n(defvar *company-image-size* (make-hash-table))\n\n(defun update-company-image-size ()\n  (let ((ht (make-hash-table)))\n    (loop for image in (bknr.datastore:class-instances 'image)\n          do\n             (incf (gethash (company image) ht 0)\n                   (image-size image)))\n    (setf *company-image-size* ht)))\n\n(defun get-company-image-size (company)\n  (gethash company *company-image-size* 0))\n\n(def-cron update-company-image-size (:minute 13 :step-hour 3)\n  (update-company-image-size))\n\n(defun make-image-from-fixture (&key company fixture)\n  \"Convenience function to make an image from the fixture/ directory,\nonly for testing purposes.\"\n  (let ((dir #.(asdf:system-relative-pathname :screenshotbot \"fixture/\")))\n    (make-image\n     :company company\n     :pathname (path:catfile dir fixture))))\n"
  },
  {
    "path": "src/screenshotbot/model/note.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/note\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:note\n   #:user\n   #:message\n   #:note-for\n   #:find-notes-for))\n(in-package :screenshotbot/model/note)\n\n(with-class-validation\n  (defclass note (store-object)\n    ((user :initarg :user\n           :reader user)\n     (message :initarg :message\n              :reader message)\n     (for :initarg :For\n          :reader note-for\n          :index-type hash-index\n          :index-reader find-notes-for)\n     (created-at :initform 0\n                 :initarg :created-at))\n    (:metaclass persistent-class)\n    (:default-initargs :created-at (get-universal-time))))\n"
  },
  {
    "path": "src/screenshotbot/model/pr-rollout-rule.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/pr-rollout-rule\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:with-class-validation\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-author\n                #:recorder-run-work-branch)\n  (:import-from #:alexandria\n                #:when-let)\n  (:export\n   #:disable-pull-request-checks-p))\n(in-package :screenshotbot/model/pr-rollout-rule)\n\n(defindex +company-index+\n  'fset-unique-index\n  :slot-name '%company)\n\n(with-class-validation\n (defclass pr-rollout-rule (store-object)\n   ((%company :initarg :company\n              :index +company-index+\n              :index-reader pr-rollout-rule-for-company))\n   (:metaclass persistent-class)))\n\n(with-class-validation\n  (defclass whitelist-rule (pr-rollout-rule)\n    ((emails :initarg :emails\n             :initform nil\n             :accessor whitelist-rule-emails))\n    (:metaclass persistent-class)))\n\n(defmethod disable-pull-request-checks-p (self\n                                          run)\n  nil)\n\n(defmethod disable-pull-request-checks-p ((self whitelist-rule)\n                                          run)\n  (not\n   (or\n    (str:containsp \"screenshotbot\" (recorder-run-work-branch run)\n                   :ignore-case t)\n    (when-let ((author (recorder-run-author run)))\n     (str:s-member (whitelist-rule-emails self)\n                   author)))))\n\n"
  },
  {
    "path": "src/screenshotbot/model/recorder-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/recorder-run\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/core\n        #:screenshotbot/model/view)\n  (:nicknames #:%r)\n  (:import-from #:bknr.datastore\n                #:deftransaction\n                #:class-instances\n                #:persistent-class\n                #:with-transaction\n                #:store-object-id\n                #:store-object\n                #:store-objects-with-class)\n  (:import-from #:util\n                #:object-with-oid)\n  (:import-from #:screenshotbot/user-api\n                #:user\n                #:recorder-run-screenshots\n                #:recorder-run-channel\n                #:recorder-previous-run\n                #:pull-request-url\n                #:recorder-run-commit\n                #:activep)\n  (:import-from #:util/store\n                #:location-for-oid\n                #:with-class-validation)\n  (:import-from #:util/object-id\n                #:oid\n                #:oid-array)\n  (:import-from #:screenshotbot/model/view\n                #:can-edit)\n  (:import-from #:alexandria\n                #:remove-from-plist\n                #:when-let)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp\n                #:non-root-object)\n  (:import-from #:util/store/store\n                #:nsort-store-objects\n                #:validate-index-values\n                #:defindex)\n  (:import-from #:util/store/fset-index\n                #:fset-unique-index\n                #:fset-many-to-many-index\n                #:fset-set-index)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:util/events\n                #:push-event\n                #:with-tracing)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:auth/viewer-context\n                #:viewer-context-api-key\n                #:api-viewer-context\n                #:viewer-context-user\n                #:normal-viewer-context\n                #:logged-in-viewer-context\n                #:viewer-context)\n  (:import-from #:util/store/simple-object-snapshot\n                #:simple-object-snapshot)\n  (:import-from #:core/api/model/api-key\n                #:api-key-user\n                #:cli-api-key\n                #:api-key-permissions)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:screenshotbot/model/constant-string\n                #:ensure-slot-constant-string\n                #:constant-string\n                #:constant-string-string)\n  (:import-from #:bknr.indices\n                #:should-index-objects-for-class-p\n                #:index-get\n                #:hash-index)\n  (:import-from #:screenshotbot/model/counter\n                #:defcounter)\n  (:import-from #:util/misc/lists\n                #:with-batches)\n  (:import-from #:util/store/slot-subsystem\n                #:externalized-slot-value)\n  (:import-from #:fset\n                #:@)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  ;; classes\n  (:export #:promotion-log\n           #:recorder-run\n           #:unchanged-run)\n\n  ;; methods\n  (:export #:recorder-run-company\n           #:recorder-run-merge-base\n           #:phabricator-diff-id\n           #:recorder-run-channel\n           #:recorder-run-commit\n           #:run-build-url\n           #:github-repo\n           #:activep\n           #:screenshot-get-canonical ;; forward declaration\n           #:cleanp\n           #:promotion-complete-p\n           #:recorder-previous-run\n           #:finalize-promotion\n           #:trunkp\n           #:channel-runs\n           #:promotion-log\n           #:publicp\n           #:active-run\n           #:gitlab-merge-request-iid\n           #:pull-request-url\n           #:recorder-run-branch\n           #:recorder-run-branch-hash\n           #:recorder-run-commit\n           #:create-github-issue-p\n           #:recorder-run-screenshots\n           #:master-branch)\n  (:export\n   #:periodic-job-p\n   #:override-commit-hash\n   #:unpromote-run\n   #:pull-request-id\n   #:compared-against\n   #:compare-threshold\n   #:compare-tolerance\n   #:not-fast-forward-promotion-warning\n   #:run-screenshot-map\n   #:make-recorder-run\n   #:runs-for-company\n   #:recorder-run-work-branch\n   #:recorder-run-batch\n   #:recorder-run-repo-url\n   #:group-separator\n   #:recorder-run-author\n   #:abstract-run\n   #:delete-run\n   #:was-promoted-p\n   #:find-shards\n   #:shard-number\n   #:shard-count\n   #:shard-screenshots\n   #:shard-key\n   #:recorder-run-metadata\n   #:recorder-run-uname\n   #:find-run-by-run-id\n   #:commit-map\n   #:do-runs-for-company)\n  (:local-nicknames (#:screenshot-map #:screenshotbot/model/screenshot-map)))\n(in-package :screenshotbot/model/recorder-run)\n\n(with-class-validation\n (defclass promotion-log (bknr.datastore:blob)\n   ()\n   (:metaclass persistent-class)))\n\n(defclass transient-promotion-log ()\n  ((oid-array :initarg :oid-array\n              :reader oid-array)))\n\n(defvar *lock* (bt:make-lock))\n\n(defvar *warning-lock* (bt:make-lock))\n\n(defindex +tags-index-v2+\n  'fset-many-to-many-index\n  :slot-name 'tags)\n\n(defindex +run-channel-index+\n  'fset-set-index\n  :slot-name 'channel)\n\n(defclass bknr-or-archived-run-mixin ()\n  ()\n  (:documentation \"A class that's mixed into both RECORDER-RUN and ARCHIVED-RUN, so that\nwe can write methods that are generic to both.\"))\n\n(defclass abstract-run (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(defmethod should-index-objects-for-class-p ((class (eql (find-class 'abstract-run))))\n  nil)\n\n(defindex +shard-key-index+\n  'fset-set-index\n  :slot-name '%key)\n\n(with-class-validation\n  (defclass shard (store-object)\n    ((%screenshots :initarg :screenshots\n                   :reader shard-screenshots)\n     (%company :initarg :company\n               :reader shard-company)\n     (%key :initarg :key\n           :reader shard-key\n           :index +shard-key-index+\n           :index-reader %shards-for-key)\n     (%number :initarg :number\n              :reader shard-number)\n     (%count :initarg :count\n             :reader shard-count)\n     (%channel :initarg :channel\n               :reader shard-channel)\n     (%ts :initarg :ts\n          :reader shard-ts))\n    (:metaclass persistent-class)\n    (:default-initargs :ts (get-universal-time)\n                       :channel nil)))\n\n(defun find-shards (channel key)\n  (reverse ;; most recent first\n   (loop for shard in (fset:convert 'list (%shards-for-key key))\n         if (and\n             (eql channel (shard-channel shard)))\n           collect shard)))\n\n(defcounter recorder-run-counter ())\n\n(with-class-validation\n  (defclass recorder-run (object-with-oid abstract-run\n                          bknr-or-archived-run-mixin)\n    ((%run-id :initarg :run-id\n              :reader recorder-run-id)\n     (channel\n      :initarg :channel\n      :initform nil\n      :relaxed-object-reference t\n      :index +run-channel-index+\n      :index-reader runs-for-channel\n      :accessor recorder-run-channel)\n     (company\n      :initarg :company\n      :initform nil\n      :accessor recorder-run-company\n      :documentation \"Heads up! This can be NIL for deleted runs, which might be referenced from non-deleted reports (T1876)\")\n     (commit-hash\n      :initarg :commit-hash\n      :initform nil\n      :accessor %recorder-run-commit)\n     #+screenshotbot-oss\n     (promotion-log\n      :accessor %promotion-log)\n     (build-url\n      :initform nil\n      :initarg :build-url\n      :accessor %run-build-url)\n     (github-repo\n      :initform nil\n      :initarg :github-repo\n      :accessor github-repo\n      :accessor recorder-run-repo-url)\n     (cleanp\n      :initarg :cleanp)\n     (activep\n      :initarg :activep\n      :initform nil)\n     (branch\n      :initarg :branch\n      :initform nil\n      :accessor recorder-run-branch\n      :documentation \"The main branch. This is not the branch associated with the\n    current pull request!\")\n     (work-branch\n      :initarg :work-branch\n      :initform nil\n      :accessor recorder-run-work-branch\n      :documentation \"The branch we're currently working on, which might\n    be the same as the main branch, or it might be the current pull\n    request branch\")\n     (release-branch-p\n      :initarg :release-branch-p\n      :accessor release-branch-p\n      :documentation \"Whether the work-branch is a release branch\")\n     (branch-hash\n      :initarg :branch-hash\n      :initform nil\n      :accessor %recorder-run-branch-hash\n      :documentation \"If a --branch is provided, this is the sha of the\n    specified branch at the time of run. This might be different from\n    the COMMIT-HASH, because the COMMIT-HASH might on, say a Pull\n    Request (tied to the branch) or an ancestor of the branch.\")\n     (merge-base-hash\n      :initform nil\n      :initarg :merge-base\n      :accessor %recorder-run-merge-base\n      :documentation \"The merge base between branch-hash and commit-hash\")\n     (pull-request\n      :initarg :pull-request\n      :initform nil\n      :accessor %pull-request-url)\n     (gitlab-merge-request-iid\n      :initarg :gitlab-merge-request-iid\n      :initform nil\n      :reader gitlab-merge-request-iid)\n     (phabricator-diff-id\n      :initarg :phabricator-diff-id\n      :initform nil\n      :reader phabricator-diff-id)\n     (previous-run\n      :initform nil\n      :initarg :previous-run\n      :accessor recorder-previous-run\n      :documentation \"The previous *ACTIVE* run. Unpromoted runs aren't tracked on the channel, because often the reason it's unpromoted means that we don't understand if it belongs to the channel. If there are multile previous-runs for different branches, this will always point to the previous run on master branch.\")\n     (create-github-issue-p\n      :initform nil\n      :initarg :create-github-issue-p\n      :accessor create-github-issue-p)\n     (trunkp\n      :initarg :trunkp\n      :accessor trunkp\n      :initform nil\n      :documentation \"whether this is tracking a production branch (as opposed to dev)\")\n     (periodic-job-p\n      :initarg :periodic-job-p\n      :initform nil\n      :accessor periodic-job-p\n      :documentation \"Jobs that are done periodically, as opposed to for\n    each commit. We will attempt to promote each run. This is mostly\n    for taking screenshots of public websites.\")\n     (Screenshots\n      :initarg :screenshots\n      :initform nil\n      :documentation \"The old list of screenshots, these days we generate them from the map\")\n     (%screenshot-map\n      :initarg :screenshot-map\n      :accessor run-screenshot-map)\n     (promotion-complete-p\n      :initform nil\n      :accessor promotion-complete-p)\n     (override-commit-hash\n      :initform nil\n      :initarg :override-commit-hash\n      :accessor %override-commit-hash\n      :documentation \"Override the pull request commit hash that will be\n    used to update the Pull Request (either GitHub or Bitbucket)\")\n     (%compare-threshold\n      :initform nil\n      :initarg :compare-threshold\n      :accessor compare-threshold\n      :documentation \"The comparison threshold in terms of fraction of pixels changed. If\nNIL or 0, this will use exact pixel comparisons.\")\n     (%compare-tolerance\n      :initarg :compare-tolerance\n      :accessor compare-tolerance)\n     (%warnings\n      :initform nil\n      :accessor recorder-run-warnings\n      :documentation \"A list of warning objects that will be rendered whenever the run or an\nassociated report is rendered.\")\n     (created-at\n      :initform nil\n      :accessor %created-at)\n     (tags\n      :initarg :tags\n      :accessor recorder-run-tags\n      :index +tags-index-v2+\n      :index-reader %runs-for-tag)\n     (batch :initform nil\n            :initarg :batch\n            :accessor recorder-run-batch\n            :documentation \"The batch object associated with this run\")\n     (%group-separator :initarg :group-separator\n                       :reader group-separator)\n     (%was-promoted-p :initarg :was-promoted-p\n                      :accessor was-promoted-p\n                      :documentation \"Whether this was ever set as an active run for the run's channel,branch.\")\n     (%author :initarg :author\n              :reader recorder-run-author\n              :documentation \"The author, or owner of this run. This will be used for logic to ensure that authors cannot review their own runs. See T1056.\")\n     (%metadata :initarg :metadata\n                :reader recorder-run-metadata\n                :documentation \"An alist of metadata. The keys and values are both strings.\"))\n    (:metaclass has-created-at)\n    (:default-initargs :screenshot-map (error \"need screenshot-map\")\n                       :compare-tolerance nil\n                       :release-branch-p nil\n                       :author nil\n                       :metadata nil\n                       :tags nil\n                       :was-promoted-p nil)))\n\n(defmethod should-index-objects-for-class-p ((class (eql (find-class 'recorder-run))))\n  nil)\n\n(defun recorder-run-uname (run)\n  (assoc-value (recorder-run-metadata run)\n               \"uname\" :test #'equal))\n\n(defmethod pull-request-url ((self recorder-run))\n  (constant-string-string (%pull-request-url self)))\n\n(defmethod run-build-url ((self recorder-run))\n  (constant-string-string (%run-build-url self)))\n\n(defmethod recorder-run-commit ((self recorder-run))\n  (constant-string-string (%recorder-run-commit self)))\n\n(defmethod (setf recorder-run-commit) (val (self recorder-run))\n  (setf (%recorder-run-commit self)\n        (constant-string val)))\n\n(defmethod recorder-run-branch-hash ((self recorder-run))\n  (constant-string-string (%recorder-run-branch-hash self)))\n\n(defmethod recorder-run-merge-base ((self recorder-run))\n  (constant-string-string (%recorder-run-merge-base self)))\n\n(defmethod override-commit-hash ((self recorder-run))\n  (constant-string-string (%override-commit-hash self)))\n\n(defmethod bknr.datastore:make-object-snapshot ((self recorder-run))\n  (make-instance 'simple-object-snapshot\n                 :object self\n                 :except-slots '(promotion-complete-p\n                                 %warnings)))\n\n(defmethod recorder-run-author :around ((run recorder-run))\n  (handler-case\n      (call-next-method)\n    (unbound-slot (e)\n      nil)))\n\n(defun assert-no-loops (recorder-run)\n  (flet ((next (run)\n           (when run\n            (recorder-previous-run run))))\n   (when recorder-run\n     (labels ((work (slow fast)\n                (cond\n                  ((not fast)\n                   ;; there's no loop here.\n                   t)\n                  ((eql slow fast)\n                   (error \"There's a loop in this run history at ~a\" slow))\n                  (t\n                   (work (next slow)\n                         (next (next fast)))))))\n       (work recorder-run (next recorder-run))))))\n\n(defindex +unchanged-run-index+\n  'hash-index\n  :test #'equal\n  :slot-name 'commit)\n\n(defmethod recorder-run-commit :around (run)\n  (or\n   (call-next-method)\n   (override-commit-hash run)))\n\n(with-class-validation\n (defclass unchanged-run (abstract-run)\n   ((commit :initarg :commit\n            :reader unchanged-run-commit\n            :index +unchanged-run-index+\n            :index-reader %unchanged-runs-for-commit\n            :reader recorder-run-commit)\n    (other-commit :initarg :other-commit\n                  :reader unchanged-run-other-commit\n                  :documentation \"The commit that this is going to be a copy of\")\n    (channel :initarg :channel\n             :initform nil\n             :reader unchanged-run-channel\n             :reader recorder-run-channel)\n    (%merge-base :initarg :merge-base\n                 :reader recorder-run-merge-base)\n    (%override-commit-hash :initarg :override-commit-hash\n                           :reader override-commit-hash)\n    (%work-branch :initarg :work-branch\n                  :reader recorder-run-work-branch)\n    (%batch :initarg :batch\n            :accessor recorder-run-batch\n            :documentation \"The batch object associated with this run\")\n    (%%created-at :initarg :created-at\n                 :accessor %created-at)\n    (promotion-complete-p\n     :initform nil\n     :accessor promotion-complete-p))\n   (:metaclass persistent-class)\n   (:default-initargs :batch nil\n                      :override-commit-hash nil\n                      :created-at (get-universal-time)\n                      :merge-base nil)\n   (:documentation \"Annotates that this commit should have identical screenshots to the other commit\")))\n\n(defmethod initialize-instance :after ((self recorder-run) &key &allow-other-keys)\n  (%update-commit-map self)\n  (%update-run-id-map self))\n\n(defmethod %update-commit-map ((self recorder-run) &key (removep nil))\n  \"Update the commit map for run.\n\nREMOVEP is the opposite, it's just a conveience for deleting the run\nfrom the map without too much code duplication\"\n  (let ((commit-hash (constant-string-string (recorder-run-commit self)))\n        (channel (recorder-run-channel self)))\n    (when (and channel\n               (not (str:emptyp commit-hash)))\n      (let ((existing-map (or\n                           (externalized-slot-value channel 'commit-map)\n                           (fset:empty-map))))\n        (let ((old-set (or\n                        (@ existing-map commit-hash)\n                        (fset:empty-set))))\n          (setf\n           (externalized-slot-value channel 'commit-map)\n           (fset:with existing-map\n                      commit-hash\n                      (funcall (if removep #'fset:less #'fset:with) old-set self))))))))\n\n(defmethod %update-run-id-map ((run recorder-run) &key (removep nil))\n  (when-let ((company (recorder-run-company run))\n             (run-id (handler-case\n                         (recorder-run-id run)\n                       (unbound-slot ()\n                         nil))))\n    (symbol-macrolet ((place (externalized-slot-value company 'run-id-map (fset:empty-map))))\n     (cond\n       (removep\n        (fset:excludef\n         place\n         run-id))\n       (t\n        (fset:includef\n         place\n         run-id\n         run))))))\n\n\n(defmethod should-index-objects-for-class-p ((class (eql (find-class 'unchanged-run))))\n  nil)\n\n(defun unchanged-run-for-commit (channel commit)\n  (let ((runs\n          ;; Backward compatibility for a migration from\n          ;; fset-set-index to hash-index.\n          (fset:convert 'fset:set\n                        (%unchanged-runs-for-commit commit))))\n    (fset:do-set (ur runs)\n      (when (eql channel (unchanged-run-channel ur))\n        (return-from unchanged-run-for-commit ur))))\n  nil)\n\n(defmethod bknr.datastore:make-object-snapshot ((self unchanged-run))\n  (make-instance 'simple-object-snapshot\n                 :object self\n                 :except-slots '(promotion-complete-p)))\n\n(defun make-recorder-run (&rest args &key screenshots channel\n                                       pull-request\n                                       build-url\n                                       commit-hash\n                                       company\n                                       branch-hash\n                                       merge-base\n                                       override-commit-hash\n                          &allow-other-keys)\n  (let ((args (cond\n                (company\n                 (list*\n                  :run-id (next-recorder-run-counter company)\n                  args))\n                (t\n                 args))))\n   (apply #'make-instance 'recorder-run\n          :pull-request (constant-string pull-request)\n          :build-url (constant-string build-url)\n          :commit-hash (constant-string commit-hash)\n          :branch-hash (constant-string branch-hash)\n          :merge-base (constant-string merge-base)\n          :override-commit-hash (constant-string override-commit-hash)\n          :screenshot-map (screenshot-map:make-screenshot-map channel screenshots)\n          (remove-from-plist args :screenshots))))\n\n(defmethod print-object ((o recorder-run) stream)\n  (format stream \"#<RECORDER-RUN ~a>\" (ignore-errors (oid o))))\n\n(defmethod pull-request-id (run)\n  (when-let ((url (pull-request-url run)))\n    (when-let ((part (last (str:split \"/\" url))))\n      (ignore-errors\n       (parse-integer (car part))))))\n\n(defun unpromote-run (run)\n  (let ((previous-run (recorder-previous-run run))\n        (channel (recorder-run-channel run))\n        (branch (recorder-run-branch run)))\n    (assert (eql run (active-run channel branch)))\n    (with-transaction ()\n     (setf (recorder-previous-run run) nil))\n    (setf (active-run channel branch) previous-run)))\n\n(defmethod promotion-log ((run recorder-run))\n  (make-instance 'transient-promotion-log\n                 :oid-array (oid-array run)))\n\n;; TODO: delete\n(defmethod bknr.datastore:initialize-transient-instance :after ((run recorder-run)))\n\n(defmethod bknr.datastore::destroy-object :before ((run recorder-run))\n  (when-let ((channel (recorder-run-channel run)))\n    (unless (bknr.datastore::object-destroyed-p channel)\n      (%update-commit-map run :removep t)))\n  (%update-run-id-map run :removep t))\n\n(defmethod auth:can-view ((run bknr-or-archived-run-mixin) user)\n  (auth:can-view-with-normal-viewer-context\n   user run))\n\n(defmethod auth:can-view ((run recorder-run) user)\n  ;; TODO: delete\n  (call-next-method))\n\n(defmethod auth:can-viewer-view (vc (run bknr-or-archived-run-mixin))\n  (or\n   (publicp (recorder-run-channel run))\n   (auth:can-viewer-view vc (recorder-run-channel run))))\n\n(defmethod auth:can-viewer-view (vc (run recorder-run))\n  ;; TODO: delete\n  (call-next-method))\n\n(defmethod can-api-key-view-run-p (api-key run)\n  (member :full (api-key-permissions api-key)))\n\n(defmethod can-api-key-view-run-p ((api-key cli-api-key) run)\n  \"from a cli-api-key, we need to be able to access our runs... but we\nmight also want to build a feature to check against our current main\nbranch runs.\"\n  t)\n\n(defmethod auth:can-viewer-view :around ((vc api-viewer-context) (run recorder-run))\n  (and\n   (or\n    (not (gk:check :api-key-roles (recorder-run-company run)))\n    (can-api-key-view-run-p (viewer-context-api-key vc) run))\n   (call-next-method)))\n\n(defmethod can-public-view ((run recorder-run))\n  (publicp (recorder-run-channel run)))\n\n(defmethod auth:can-edit ((run recorder-run) (user user))\n  (auth:can-edit-with-normal-viewer-context\n   user run))\n\n(defmethod auth:can-viewer-edit ((vc normal-viewer-context) (run recorder-run))\n  (roles:has-role-p\n   (recorder-run-company run)\n   (viewer-context-user vc)\n   'roles:standard-member))\n\n(defmethod activep ((run recorder-run))\n  (let ((channel (recorder-run-channel run)))\n   (eql run\n        (active-run channel\n                    (master-branch channel)))))\n\n(defmethod (setf activep) (val (run recorder-run))\n  (error \"Old method, set active-run on channel directly\"))\n\n(defmethod recorder-run-screenshots ((run bknr-or-archived-run-mixin))\n  (screenshot-map:to-list\n   (run-screenshot-map run)))\n\n\n(defmethod bknr.datastore:blob-pathname ((self transient-promotion-log))\n  (ensure-directories-exist\n   (location-for-oid #P\"promotion-logs/\"\n                     (oid-array self))))\n\n(defmethod ensure-screenshot-map ((run recorder-run))\n  (restart-case\n      (when-let ((channel (recorder-run-channel run)))\n        (unless (run-screenshot-map run)\n          (let ((screenshots (recorder-run-screenshots run)))\n            (when (listp screenshots)\n             (let ((map\n                     (screenshot-map:make-screenshot-map channel\n                                                         screenshots)))\n               (log:info \"Updating ~a\" (bknr.datastore:store-object-id run))\n               (with-transaction ()\n                 (setf (run-screenshot-map run) map)))))))\n    (ignore-this-run ()\n      (values))))\n\n(defmethod verify-screenshot-map (run)\n  (log:info \"Verifying ~a\" run)\n  (when (recorder-run-channel run)\n   (let ((screenshots (recorder-run-screenshots run))\n         (map (run-screenshot-map run)))\n     (when (or screenshots map)\n       (let ((screenshot-set (screenshot-map:make-set screenshots)))\n         (assert (fset:equal?\n                  screenshot-set\n                  (screenshot-map:to-map map)))\n         (loop for x in (fset:convert 'list screenshot-set)\n               for y in (fset:convert 'list (screenshot-map:to-map map))\n               do (assert (eql (car x) (car y)))))))))\n\n;; (mapc #'verify-screenshot-map (bknr.datastore:class-instances 'recorder-run))\n\n(defun ensure-all-screenshot-maps ()\n  (ensure-slot-boundp 'recorder-run '%screenshot-map)\n  (time\n   (mapc #'ensure-screenshot-map\n         (bknr.datastore:class-instances 'recorder-run))))\n\n;; Migration\n#+screenshotbot-oss\n(defun convert-old-promotion-logs ()\n  (loop for run in (bknr.datastore:store-objects-with-class 'recorder-run)\n        for promotion-log = (%promotion-log run)\n        if (typep promotion-log 'promotion-log)\n          do\n             (let ((pathname (bknr.datastore:blob-pathname promotion-log)))\n              (restart-case\n                  (progn\n                    (with-transaction ()\n                      (setf (%promotion-log run) nil))\n                    (when (path:-e pathname)\n                      (rename-file\n                       pathname\n                       (bknr.datastore:blob-pathname (promotion-log run))))\n                    (bknr.datastore:delete-object promotion-log))\n                (ignore-this-blob ()\n                  (values))))))\n\n(defindex +run-warning-run-index+\n  'hash-index\n  :slot-name '%run)\n\n(defclass base-run-warning (store-object)\n  ((%run :initarg :run\n         :index +run-warning-run-index+\n         :index-reader warnings-for-run))\n  (:metaclass persistent-class))\n\n(with-class-validation\n (defclass merge-base-failed-warning (base-run-warning)\n   ((%compared-against :initarg :compared-against\n                       :reader compared-against))\n   (:metaclass persistent-class)\n   (:documentation \"A warning that when making a pull request comparison, we didn't\ncompare against the actual merge base.\")))\n\n(with-class-validation\n  (defclass not-fast-forward-promotion-warning (base-run-warning)\n    ()\n    (:metaclass persistent-class)\n    (:documentation \"A warning that this was not a fast-forward when the run was promoted\")))\n\n(def-store-migration (\"Add compare tolerance\" :version 7)\n  (ensure-slot-boundp 'recorder-run '%compare-tolerance))\n\n(def-store-migration (\"Add tags to model\" :version 8)\n  (ensure-slot-boundp 'recorder-run 'tags))\n\n(defmethod runs-for-tag (company tag)\n  (let ((runs (fset:convert 'list (%runs-for-tag tag))))\n    (loop for run in runs\n          if (eql company (recorder-run-company run))\n            collect run)))\n\n(defmethod group-separator :around ((run recorder-run))\n  (or (ignore-errors (call-next-method))\n      \"--\"))\n\n(defmethod recorder-run-company ((self unchanged-run))\n  (screenshotbot/model/company:company (unchanged-run-channel self)))\n\n(defmethod (setf recorder-run-company) :before ((val null) (self recorder-run))\n  (%update-run-id-map self :removep t))\n\n(defmethod recorder-run-repo-url ((self unchanged-run))\n  (github-repo (unchanged-run-channel self)))\n\n(def-store-migration (\"Ensure :company slot is set to the channel company\" :version 17)\n  (ensure-slot-boundp 'recorder-run 'company)\n  (dolist (run (bknr.datastore:class-instances 'recorder-run))\n    (when (and\n           (not (recorder-run-company run))\n           (recorder-run-channel run)\n           (ignore-errors\n            (screenshotbot/model/company:company (recorder-run-channel run))))\n      (format t \"Fixing run: ~a~%\" run)\n      (setf (recorder-run-company run)\n            (screenshotbot/model/company:company (recorder-run-channel run) )))))\n\n(defmethod delete-run (run)\n  \"Not exactly deleting, but currently just detaching it from the\ncompany as a way of deleting.\"\n  (push-event :delete-run :run (oid run) :company (?. oid (recorder-run-company run)))\n  (setf (recorder-run-company run) nil))\n\n(def-store-migration (\"Ensure :was-promoted-p is bound\" :Version 25)\n  (ensure-slot-boundp 'recorder-run '%was-promoted-p))\n\n(defun clean-up-old-shards ()\n  (let ((cut-off (- (get-universal-time) (* 24 3600 2))))\n    (loop for shard in (class-instances 'shard)\n          if (< (shard-ts shard) cut-off)\n          do (bknr.datastore:delete-object shard))))\n\n(def-cron clean-up-old-shards (:minute 0 :hour 1)\n  (clean-up-old-shards))\n\n(def-store-migration (\"Use constant-string for pull-request\" :version 26)\n  (ensure-slot-constant-string\n   (bknr.datastore:class-instances 'recorder-run)\n   'pull-request))\n\n(def-store-migration (\"Use constant-string for build-url\" :version 27)\n  (ensure-slot-constant-string\n   (bknr.datastore:class-instances 'recorder-run)\n   'build-url))\n\n(def-store-migration (\"Use constant-string for commits on run\" :version 28)\n  (loop for slot in '(commit-hash\n                      branch-hash\n                      merge-base-hash\n                      override-commit-hash)\n        do\n           (ensure-slot-boundp 'recorder-run slot)\n           (ensure-slot-constant-string\n            (bknr.datastore:class-instances 'recorder-run)\n            slot)))\n\n(def-store-migration (\"Add new slot for metadata\" :version 34)\n  \"This migration was supposed to be for version 29, but because of a\ntypo it wasn't being run. I've updated the version.\"\n  (ensure-slot-boundp 'recorder-run '%metadata))\n\n(def-store-migration (\"Add new slot for release-branch-p\" :version 30)\n  (ensure-slot-boundp 'recorder-run 'release-branch-p))\n\n(defun push-run-warning (run type &rest args)\n  \"Create a run warning of type TYPE with args ARGS, and push it to the\nlist of warnings for RUN.\"\n  (let ((warning (apply #'make-instance type\n                        :run run\n                        args)))\n    (bt:with-lock-held (*warning-lock*)\n      (push warning (recorder-run-warnings run)))))\n\n(defun delete-old-unchanged-runs (&key (now (get-universal-time)))\n  (let ((cutoff (- now (* 3600 24 180))))\n    (loop for unchanged-run in (bknr.datastore:class-instances 'unchanged-run)\n          if (<\n              (handler-case\n                  (%created-at unchanged-run)\n                (unbound-slot ()\n                  0))\n              cutoff)\n            do\n               (bknr.datastore:delete-object unchanged-run))))\n\n(def-cron delete-old-unchanged-runs (:minute 22 :hour 4)\n  (delete-old-unchanged-runs))\n\n(def-store-migration (\"Backfill run slot on warnings\" :version 37)\n  (dolist (run (bknr.datastore:class-instances 'recorder-run))\n    (dolist (warning (recorder-run-warnings run))\n      (when (typep warning 'base-run-warning)\n        (unless (slot-boundp warning '%run)\n          (setf (slot-value warning '%run) run))))))\n\n(defun run-id-to-run-map (company)\n  (externalized-slot-value company 'run-id-map (fset:empty-map)))\n\n(defun %count-screenshots (company &key (num-days 30))\n  \"Used for collecting data for our Pay-as-you-go migration plan. This is\nonly run on a REPL.\"\n  (let ((time (- (get-universal-time) (* num-days 24 2600))))\n   (loop for run in (fset:convert 'list (runs-for-company company))\n         if (> (%created-at run) time)\n           summing (length (recorder-run-screenshots run)))))\n\n(defun find-run-by-run-id (company run-id)\n  (fset:lookup (run-id-to-run-map company)\n               run-id))\n\n(deftransaction tx-populate-run-id (runs)\n  (loop for run in runs\n        if (recorder-run-company run)\n          unless (slot-boundp run '%run-id)\n            do\n               (setf (slot-value run '%run-id)\n                     (next-recorder-run-counter (recorder-run-company run)))))\n\n(defun populate-run-id ()\n  (with-batches (runs (bknr.datastore:class-instances 'recorder-run) :batch-size 100)\n    (when-let ((runs (loop for run in runs\n                           unless (slot-boundp run '%run-id)\n                             collect run)))\n      (tx-populate-run-id runs))))\n\n(def-store-migration (\"Backfill run-id, step 1\" :version 38)\n  (populate-run-id))\n\n(def-store-migration (\"Backfill run-id, step 2\" :version 41)\n  \"Intentionally running this a second time to fill any missed runs\"\n  (populate-run-id))\n\n(defmethod validate-index-values ((self fset-unique-index) all-elts (slot-name (eql '%run-id)))\n  \"See T2282\"\n  nil)\n\n(defmethod validate-index-values ((self fset-set-index) all-elts (slot-name (eql 'company)))\n  \"See T2282\"\n  nil)\n\n(defmethod bknr.datastore:class-instances ((name (eql 'recorder-run)))\n  (nsort-store-objects\n   (loop for obj in (bknr.datastore:all-store-objects)\n         if (typep obj 'recorder-run)\n           collect obj)))\n\n(defmethod bknr.datastore:class-instances ((name (eql 'unchanged-run)))\n  (nsort-store-objects\n   (loop for obj in (bknr.datastore:all-store-objects)\n         if (typep obj 'unchanged-run)\n           collect obj)))\n\n(deftransaction %bulk-add-to-persistent-commit-map-index (runs)\n  (mapc #'%update-commit-map runs))\n\n(def-store-migration (\"Add to persistent commit-map index\" :version 40)\n  (with-batches (runs (bknr.datastore:class-instances 'recorder-run)\n                 :batch-size 1000)\n    (%bulk-add-to-persistent-commit-map-index runs)))\n\n(deftransaction %bulk-add-to-run-id-map (runs)\n  (mapc #'%update-run-id-map runs))\n\n(def-store-migration (\"Add to persistent run-id map\" :version 42)\n  (with-batches (runs (bknr.datastore:class-instances 'recorder-run)\n                 :batch-size 1000)\n    (%bulk-add-to-run-id-map runs)))\n\n(def-easy-macro do-runs-for-company (&binding run company &fn fn)\n  (fset:do-map (run-id run (run-id-to-run-map company))\n    (declare (ignore run-id))\n    (fn run)))\n\n(defun runs-for-company (company)\n  (warn \"runs-for-company being called, this is slow and should not be used in production\")\n  (fset:range (run-id-to-run-map company)))\n"
  },
  {
    "path": "src/screenshotbot/model/report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/report\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/core\n        #:screenshotbot/report-api\n        #:screenshotbot/model/view)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name\n                #:report-num-changes)\n  (:import-from #:util #:object-with-oid)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:pull-request-id\n                #:recorder-run-company\n                #:recorder-run-channel\n                #:publicp)\n  (:import-from #:bknr.datastore\n                #:deftransaction\n                #:persistent-class\n                #:store-object\n                #:with-transaction)\n  (:import-from #:bknr.indices\n                #:index-get\n                #:index-add\n                #:hash-index)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:util/store/fset-index\n                #:index-object-key\n                #:fset-set-index)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:screenshotbot/model/view\n                #:can-edit)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context\n                #:logged-in-viewer-context)\n  (:export\n   #:report\n   #:report-run\n   #:report-title\n   #:base-acceptable\n   #:acceptable-state\n   #:report-previous-run\n   #:report-channel\n   #:report-acceptable\n   #:github-task\n   #:report-num-changes\n   #:reports-for-run\n   #:report-company\n   #:acceptable-report\n   #:acceptable-reviewer\n   #:company-promotion-reports\n   #:report-to-dto\n   #:acceptable-history-item)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/model/report)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defclass report-company-index (fset-set-index)\n    ()))\n\n(defindex +report-company-index+\n  'report-company-index\n  :slots '(%company promotion-report-p))\n\n(defindex +report-run-index+\n  'fset-set-index\n  :slot-name 'run)\n\n(with-class-validation\n  (defclass report (object-with-oid)\n    ((run\n      :initarg :run\n      :accessor report-run\n      :index +report-run-index+\n      :index-reader %reports-for-run)\n     (%company\n      :initform nil\n      :accessor %report-company)\n     (title\n      :type string\n      :initarg :title\n      :accessor report-title)\n     (previous-run\n      :initarg :previous-run\n      :accessor report-previous-run)\n     (channel\n      :initarg :channel\n      :initform nil\n      :accessor report-channel)\n     (acceptable\n      :initarg :acceptable\n      :initform nil\n      :accessor report-acceptable)\n     (github-task\n      :initform nil\n      :initarg :github-task\n      :accessor github-task)\n     (num-changes ;; see report-num-changes\n      :initform 0\n      :initarg :num-changes\n      :accessor report-num-changes)\n     (promotion-report-p\n      :initform nil\n      :initarg :promotion-report-p\n      :reader promotion-report-p)\n     (created-at\n      :accessor %created-at))\n    (:metaclass has-created-at)\n    (:class-indices (company-index\n                     :index +report-company-index+\n                     :slots (%company promotion-report-p)))\n    (:default-initargs\n     :run nil\n     :title nil\n     :previous-run nil)))\n\n(defun reports-for-run (run)\n  (fset:convert 'list\n                (%reports-for-run run)))\n\n(defmethod index-add ((index report-company-index) obj)\n  (when (promotion-report-p obj)\n    (call-next-method)))\n\n(defmethod index-object-key ((index report-company-index) report)\n  (%report-company report))\n\n(defmethod initialize-instance :after ((report report) &key run)\n  (when run\n    (setf (%report-company report)\n          (recorder-run-company run))))\n\n(defmethod can-view ((report report) user)\n  (auth:can-view-with-normal-viewer-context\n   user report))\n\n(defmethod auth:can-viewer-view ((vc logged-in-viewer-context)\n                                 (report report))\n  (and\n   (auth:can-viewer-view vc (report-run report))\n   (if (report-previous-run report)\n       (auth:can-viewer-view vc (report-previous-run report))\n       t)))\n\n(defun company-promotion-reports (company)\n  (index-get +report-company-index+ company))\n\n\n(defmethod report-company ((report report))\n  (recorder-run-company (report-run report)))\n\n(defmethod can-public-view ((report report))\n  ;; The public can view any report associated with a public github\n  ;; repo\n  (publicp (recorder-run-channel (report-run report))))\n\n(with-class-validation\n  (defclass base-acceptable (store-object)\n    ((state :initform nil\n            :initarg :state\n            :documentation \"One of NIL, :ACCEPTED, :REJECTED\"\n            :reader acceptable-state)\n     (report :initarg :report\n             :reader acceptable-report\n             :documentation \"Keep track of the report, mostly for audit-log\n             purposes, which needs a reference to the company.\")\n     (%user :initarg :user\n            :initform nil\n            :accessor acceptable-reviewer\n            :documentation \"The reviewer who last updated the state\")\n     (history :initform nil\n              :initarg :history\n              :accessor acceptable-history\n              :documentation \"A list of history items\"))\n    (:metaclass persistent-class)))\n\n\n(with-class-validation\n  (defclass acceptable-history-item (store-object)\n    ((state :initarg :state\n            :reader acceptable-history-item-state)\n     (%user :initarg :user\n            :reader accepable-history-item-user ;; TODO: remove\n            :reader acceptable-history-item-user)\n     (ts :accessor acceptable-history-item-ts\n         :initarg :ts))\n    (:metaclass persistent-class)\n    (:default-initargs :ts (get-universal-time))))\n\n(defmethod can-view ((self base-acceptable) user)\n  (auth:can-view-with-normal-viewer-context\n   user self))\n\n(defmethod auth:can-viewer-view ((vc normal-viewer-context)\n                                 (self base-acceptable))\n  (and\n   (slot-boundp self 'report)\n   (auth:can-viewer-view vc (acceptable-report self))))\n\n(defmethod can-edit ((self base-acceptable) user)\n  (auth:can-edit-with-normal-viewer-context\n   user\n   self))\n\n(defmethod auth:can-viewer-edit ((vc normal-viewer-context)\n                                 (self base-acceptable))\n  (and\n   (auth:can-viewer-view vc self)\n   (when-let* ((report (acceptable-report self))\n               (run (report-run report)))\n     (auth:can-viewer-edit vc run))))\n\n\n(defmethod find-acceptables (channel\n                             &key (pull-request-id nil pull-request-provided-p))\n  (loop for acc in (class-instances 'base-acceptable)\n        for report = (acceptable-report acc)\n        for this-channel = (report-channel report)\n        if (and\n            (eql this-channel channel)\n            (or (not pull-request-provided-p)\n                (equal pull-request-id\n                       (?. pull-request-id (report-run report)))))\n          collect acc))\n\n(defmethod (setf acceptable-state) (state (acceptable base-acceptable) &key user)\n  (assert (member state (list nil :accepted :rejected)))\n  (with-transaction ()\n    (setf (slot-value acceptable 'state)\n          state)\n    (setf (acceptable-reviewer acceptable) user)\n    (push\n     (make-instance 'acceptable-history-item\n                    :state state\n                    :user user)\n       (acceptable-history acceptable))))\n\n(defun report-to-dto (report)\n  (let ((acceptable (report-acceptable report)))\n    (make-instance 'dto:report\n                   :id (oid report)\n                   :run (?. oid (report-run report))\n                   :previous-run (?. oid (report-previous-run report))\n                   :channel (?. channel-name (report-channel report))\n                   :title (report-title report)\n                   :acceptable-state\n                   (or\n                    (when acceptable\n                      (string-downcase\n                       (or\n                        (acceptable-state acceptable)\n                        \"none\")))\n                    \"na\")\n                   :reviewer-name\n                   (?. auth:user-full-name (?. acceptable-reviewer acceptable)))))\n"
  },
  {
    "path": "src/screenshotbot/model/review-policy.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/review-policy\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-author\n                #:recorder-run)\n  (:import-from #:screenshotbot/report-api\n                #:report-run\n                #:report)\n  (:export\n   #:can-review?\n   #:anyone-can-review))\n(in-package :screenshotbot/model/review-policy)\n\n(defclass anyone-can-review ()\n  ())\n\n(defmethod can-review? ((self anyone-can-review) (run recorder-run) user)\n  t)\n\n(defmethod can-review? (policy (report report) user)\n  (can-review? policy (report-run report) user))\n\n(defclass disallow-author-review-policy ()\n  ())\n\n(defun parse-email (email)\n  (when email\n   (multiple-value-bind (res parts)\n       (cl-ppcre:scan-to-strings \".* <(.*)>\" (str:trim email))\n     (cond\n       (res\n        (elt parts 0))\n       (t\n        email)))))\n\n(defmethod can-review? ((self disallow-author-review-policy) (run recorder-run) user)\n  (let ((author (parse-email (recorder-run-author run)))\n        (email (auth:user-email user)))\n    (cond\n      (t\n       (not (string-equal (str:trim email) (str:trim author)))))))\n"
  },
  {
    "path": "src/screenshotbot/model/run-commit-lookup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;;;; Logic to lookup runs by commit. Effectively needs to be aware of\n;;;; the commit graph and information from the runs (e.g. to figure\n;;;; the SHA for a specific branch.\n\n(defpackage :screenshotbot/model/run-commit-lookup\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:do-runs-for-company\n                #:github-repo\n                #:runs-for-company\n                #:recorder-run)\n  (:import-from #:screenshotbot/user-api\n                #:recorder-run-channel\n                #:recorder-run-commit)\n  (:import-from #:util/misc\n                #:or-setf)\n  (:import-from #:serapeum/iter\n                #:collecting))\n(in-package :screenshotbot/model/run-commit-lookup)\n\n(defvar *cache* (make-hash-table :test #'equal\n                                 #+lispworks #+lispworks\n                                 :weak-kind :key))\n\n(defun find-runs-by-commit (commit &key (company (error \"must provide company (or :all)\"))\n                                     repo)\n  \"Find runs for a given commit prefix and given company.\n\nTo search for all companies (i.e. only for internal dashboards),\nprovide a :company of :all.\n\"\n  (when repo\n    (check-type repo string))\n  (when (and (not (eql :all company)) (not repo))\n    (error \"If you provide :company, you must also provide :repo\"))\n  (or-setf\n   (gethash (list commit company repo) *cache*)\n   (let ((runs (cond\n                 ((eql :all company)\n                  (bknr.datastore:class-instances 'recorder-run))\n                 (company\n                  (collecting\n                    (do-runs-for-company (run company)\n                      (when (equal repo (github-repo (recorder-run-channel run)))\n                        (collect run))))))))\n     (loop for run in runs\n           if (str:starts-with-p commit (recorder-run-commit run))\n             collect run))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/model/screenshot-key.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/screenshot-key\n  (:use #:cl)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/image\n                #:mask=)\n  (:import-from #:screenshotbot/user-api\n                #:screenshot-name)\n  (:import-from #:screenshotbot/report-api\n                #:screenshot-device\n                #:screenshot-lang)\n  (:import-from #:util/hash-lock\n                #:with-hash-lock-held\n                #:hash-lock)\n  (:export\n   #:screenshot-masks))\n(in-package :screenshotbot/model/screenshot-key)\n\n(defvar *hash-lock* (make-instance 'hash-lock :test #'equal)\n  \"Hash lock on the screenshot name. If we use a global lock, then we'll\nlimit the throughput of creating new screenshot-keys to about\n200/s. Under load it probably would be worse.\")\n\n(defclass abstract-screenshot-key ()\n  ())\n\n(with-class-validation\n  (defclass screenshot-key (abstract-screenshot-key store-object)\n    ((%name :type string\n           :initarg :name\n           :accessor screenshot-name\n           :index-type hash-index\n           :index-initargs (:test #'equal)\n           :index-reader screenshot-keys-for-name)\n     (%lang\n      :initarg :lang\n      :initform nil\n      :accessor screenshot-lang)\n     (%device\n      :initarg :device\n      :initform nil\n      :accessor screenshot-device)\n     (%masks\n      :initarg :masks\n      :initform nil\n      :accessor screenshot-masks))\n    (:metaclass persistent-class)))\n\n(defclass fake-screenshot-key (abstract-screenshot-key)\n  ((name :initarg :name\n         :initform nil\n         :accessor screenshot-name)\n   (lang :initarg :lang\n         :initform nil\n         :accessor screenshot-lang)\n   (device :initarg :device\n           :initform nil\n           :accessor screenshot-device)\n   (masks\n    :initarg :masks\n    :initform nil\n    :accessor screenshot-masks)))\n\n(defmethod print-object ((self screenshot-key) out)\n  (format out \"#<KEY ~a>\" (screenshot-name self)))\n\n(defmethod fset:compare ((a abstract-screenshot-key)\n                         (b abstract-screenshot-key))\n  (let ((a-name (screenshot-name a))\n        (b-name (screenshot-name b)))\n    (cond\n      ((eql a b)\n       :equal)\n      ((string< a-name b-name)\n       :less)\n      ((string> a-name b-name)\n       :greater)\n      (t\n       (fset:compare-slots\n        a b\n        #'screenshot-lang\n        #'screenshot-device\n        #'screenshot-masks)))))\n\n(defun ensure-screenshot-key (&key name lang device masks)\n  (flet ((find-existing ()\n           (loop for key in (screenshot-keys-for-name name)\n                 if (and\n                     (equal (screenshot-lang key) lang)\n                     (equal (screenshot-device key) device)\n                     (eql (length masks)\n                          (length (screenshot-masks key)))\n                     (eql 0\n                          (loop for m1 in masks\n                                for m2 in (screenshot-masks key)\n                                unless (mask= m1 m2)\n                                  sum 1)))\n            return key)))\n   (or\n    (find-existing)\n    (with-hash-lock-held (name *hash-lock*)\n      (find-existing)\n      (make-instance 'screenshot-key\n                     :name name\n                     :lang lang\n                     :device device\n                     :masks masks)))))\n"
  },
  {
    "path": "src/screenshotbot/model/screenshot-map.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;;;; Maintains a persistent mapping from screenshot-keys to images.\n\n(defpackage :screenshotbot/model/screenshot-map\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot\n                #:screenshot-image)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:screenshot-key)\n  (:import-from #:util/misc/lists\n                #:head)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:screenshotbot/user-api\n                #:company-name)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/events\n                #:with-tracing)\n  (:import-from #:util/store/simple-object-snapshot\n                #:simple-object-snapshot)\n  (:export\n   #:screenshot-map\n   #:screenshot-map-as-list\n   #:make-set\n   #:to-map\n   #:to-list\n   #:make-screenshot-map))\n\n(in-package :screenshotbot/model/screenshot-map)\n\n(defparameter *lookback-count* 5\n  \"The number of recent screenshot-maps to look into to determine the\n  best parent for a new map. TODO: We could use a precomputed\n  _filtered_ map to actually do the comparisons, which would avoid the\n  cost if the lookback is very large.\")\n\n(defparameter *max-chain-cost-factor* 50\n  \"Let's call this f. If adding a map to a chain, causes the chain cost\nof the map to go beyond f*N, where N is the size of the map, then we\nwill not consider it as a possibility.\")\n\n(defvar *to-list-cache* (trivial-garbage:make-weak-hash-table\n                         :weakness :value\n                         #+sbcl\n                         :synchronized #+sbcl t))\n\n(defconstant +inf+ 1000000)\n\n(with-class-validation\n  (defclass screenshot-map (store-object)\n    ((%channel :initarg :channel\n               :reader screenshot-map-channel\n               :index-type hash-index\n               :index-reader screenshot-maps-for-channel)\n     (screenshots :initarg :screenshots\n                  :reader screenshots\n                  :documentation \"The list of screenhots that are added on top of the previous\")\n     (deleted :initarg :deleted\n              :reader deleted\n              :initform nil\n              :documentation \"The list of screenshot-keys that will be deleted from the previous.\")\n     (previous :initarg :previous\n               :initform nil\n               :reader previous)\n     (map :transient t\n          :initform nil)\n     (chain-cost :transient t\n                 :initform nil))\n    (:metaclass persistent-class)))\n\n(defmethod screenshot-map-to-list ((self screenshot-map))\n  (slot-value self 'screenshots))\n\n(defun memoized-reduce (fn map initial-value slot)\n  (let ((stack nil))\n    (labels ((walk-back (value)\n               \"Once we reach the end start walking back the stack\"\n               (loop while stack\n                     do\n                        (let ((map (pop stack)))\n                          (let ((next-value (funcall fn map value)))\n                            (setf (slot-value map slot) next-value)\n                            (setf value next-value))))\n               value))\n     (loop while t do\n       (cond\n         ((null map)\n          (return (walk-back initial-value)))\n         (t\n          (cond\n            ((slot-value map slot)\n             (return (walk-back (slot-value map slot))))\n            (t\n             (push map stack)\n             (setf map (previous map))))))))))\n\n(defun chain-cost (map)\n  (memoized-reduce (lambda (this cost)\n                     (+ (length (screenshots this))\n                        (length (deleted this))\n                        cost))\n                   map\n                   0\n                   'chain-cost))\n\n(defun make-set (list &optional (map (fset:empty-map)))\n  (cond\n    ((null list)\n     map)\n    (t\n     (make-set (cdr list)\n               (fset:with map (screenshot-key (first list))\n                          (screenshot-image (first list)))))))\n\n(defmethod to-map ((self screenshot-map))\n  (memoized-reduce (lambda (self previous-set)\n                     (let ((previous-set (fset:restrict-not\n                                          previous-set\n                                          (fset:convert 'fset:set\n                                                        (deleted self)))))\n                       (fset:map-union\n                        previous-set\n                        (make-set\n                         (screenshots self)))))\n                   self\n                   (fset:empty-map)\n                   'map))\n\n(defmethod to-list ((self screenshot-map))\n  (util:or-setf\n   (gethash self *to-list-cache*)\n   (loop for (key . image) in (fset:convert 'list (to-map self))\n         collect (make-screenshot :key key :image image))))\n\n(defmethod make-from-previous (screenshots (previous screenshot-map)\n                               channel)\n  (let* ((map (make-set screenshots))\n         (old-map (to-map previous))\n         (added (fset:map-difference-2\n                 map old-map))\n         (deleted (fset:set-difference\n                   (fset:domain old-map)\n                   (fset:domain map))))\n    (make-instance 'screenshot-map\n                   :channel channel\n                   :screenshots\n                   (fset:reduce (lambda (list key val)\n                                  (list*\n                                   (make-screenshot\n                                    :key key\n                                    :image val)\n                                   list))\n                                added\n                                :initial-value nil)\n                   :deleted (fset:reduce (lambda (list key)\n                                           (list* key list))\n                                         deleted :initial-value nil)\n                   :previous previous)))\n\n(defmethod make-from-previous (screenshots (previous null)\n                               channel)\n  (make-instance 'screenshot-map\n                 :channel channel\n                 :screenshots screenshots))\n\n(defun compute-cost (this-map prev-map)\n  ;; This next step is computing the cost of the delta needed to\n  ;; represent the difference between this-map and prev-map. This is\n  ;; |A-B| + |D(B) - D(A)|, where D(X) is the domain of\n  ;; X. fset:map-difference returns two maps\n  (multiple-value-bind (diff-1 diff-2)\n      (fset:map-difference-2 this-map prev-map)\n    (+ (fset:size diff-1)\n       (fset:size diff-2)\n       ;; If the value for a given key is changed, then it will be\n       ;; present in both |A-B| and |B-A|, so we need to account for\n       ;; that.\n       (-\n        (fset:size (fset:intersection\n                    (fset:domain diff-1)\n                    (fset:domain diff-2)))))))\n\n(defun pick-best-existing-map (channel screenshots)\n  \"Returns two values: the best previous map, or NIL if none, and the\n  size of the delta between the previous map and this new map.\"\n  (let ((this-map (make-set screenshots)))\n    (labels ((find-best-in (potentials)\n               (cond\n                 ((null potentials)\n                  (values nil +inf+))\n                 (t\n                  (destructuring-bind (this . rest) potentials\n                    (let ((cost (compute-cost this-map (to-map this))))\n                      (cond\n                        ((zerop cost)\n                         ;; Short circuit, we don't need to find a better map\n                         (values this cost))\n                        (t\n                         (multiple-value-bind (next-best next-best-cost)\n                             (find-best-in rest)\n                           (cond\n                             ((and\n                               (< cost next-best-cost)\n                               (< cost (fset:size this-map))\n                               (<= (+ cost (chain-cost this))\n                                   (* *max-chain-cost-factor* (fset:size this-map))))\n                              (values this cost))\n                             (t\n                              (values next-best next-best-cost))))))))))))\n      (find-best-in (head (screenshot-maps-for-channel channel) *lookback-count*)))))\n\n(defun make-screenshot-map (channel screenshots)\n  (with-tracing (\"make-screenshot-map\")\n    (multiple-value-bind (prev delta-size)\n        (pick-best-existing-map channel screenshots)\n      (cond\n        ((and prev (zerop delta-size))\n         prev)\n        (prev\n         (make-from-previous screenshots\n                             prev\n                             channel))\n        (t\n         (make-instance 'screenshot-map\n                        :channel channel\n                        :screenshots screenshots))))))\n\n(defun build-usage-map ()\n  \"Builds a map from companies to their total screenshot-map usage counts.\n  \n  Iterates through all screenshot-map instances and aggregates the usage\n  (screenshots + deleted items) per company. Returns an fset map where\n  keys are company objects and values are total usage counts.\n  \n  Returns: An fset map mapping company -> total usage count\"\n  (labels ((build (rest result)\n             \"Recursively processes the list of screenshot-maps, accumulating usage by company\"\n             (cond\n               ((not rest)\n                result)\n               (t\n                (let* ((this (car rest))\n                       (count (+ (length (screenshots this))\n                                 (length (deleted this))))\n                       (company (ignore-errors (company (screenshot-map-channel this)))))\n                  (cond\n                    (company\n                     (build (cdr rest)\n                            (fset:with\n                             result\n                             company\n                             (+ count (fset:lookup result company)))))\n                    (t\n                     (build (cdr rest) result))))))))\n    (build (class-instances 'screenshot-map)\n           (fset:empty-map 0))))\n\n;; (build-usage-map)\n\n(defun push-usage-map ()\n  (fset:do-map (key val (build-usage-map))\n    (when (> val 1000)\n      (push-event :screenshot-map.usage\n                  :company (company-name key)\n                  :cost val))))\n\n(def-cron push-usage-map (:minute 0 :step-hour 1)\n  (push-usage-map))\n\n(defmethod bknr.datastore:make-object-snapshot ((self screenshot-map))\n  (make-instance 'simple-object-snapshot\n                 :object self))\n"
  },
  {
    "path": "src/screenshotbot/model/screenshot.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/screenshot\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/screenshot-api)\n  (:import-from #:bknr.datastore\n                #:with-transaction\n                #:decode\n                #:decode-object\n                #:encode\n                #:encode-object\n                #:persistent-class\n                #:store-object\n                #:delete-object\n                #:unique-index)\n  (:import-from #:screenshotbot/user-api\n                #:recorder-previous-run\n                #:screenshot-name)\n  (:import-from #:screenshotbot/report-api\n                #:screenshot-lang\n                #:screenshot-device)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-promoted-runs)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:assert-no-loops\n                #:run-screenshot-map\n                #:active-run\n                #:master-branch\n                #:recorder-run\n                #:screenshot-get-canonical\n                #:recorder-run-screenshots)\n  (:import-from #:screenshotbot/model/image\n                #:base-image-comparer\n                #:find-image-by-oid\n                #:image=\n                #:image-metadata\n                #:%with-local-image\n                #:rect-as-list)\n  (:import-from #:util/store\n                #:with-class-validation\n                #:def-store-local)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/object-id\n                #:oid\n                #:oid-p)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:fake-screenshot-key\n                #:screenshot-masks\n                #:screenshot-key\n                #:ensure-screenshot-key)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/model/image-comparer\n                #:make-image-comparer)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:to-map)\n  (:export\n   #:constant-string\n   #:get-constant\n   #:screenshot-masks\n   #:screenshot-name\n   #:screenshot-device\n   #:screenshot-image\n   #:screenshot-lang\n   #:make-screenshot\n   #:screenshot\n   #:get-screenshot-history\n   #:abstract-screenshot))\n(in-package :screenshotbot/model/screenshot)\n\n(with-class-validation\n  (defclass constant-string (store-object)\n    ((str))\n    (:metaclass persistent-class)))\n\n(def-store-local *screenshot-cache-v2* (make-hash-table :test 'equal))\n\n(defclass abstract-screenshot ()\n  ())\n\n(with-class-validation\n  (defclass screenshot (store-object abstract-screenshot)\n    ((name :type string\n           :initarg :name\n           :accessor %screenshot-name)\n     (recorder-run\n      :initarg :run\n      :accessor screenshot-run\n      :documentation \"DEPRECATED\")\n     (lang\n      :initarg :lang\n      :initform nil\n      :accessor screenshot-lang)\n     (device\n      :initarg :device\n      :initform nil\n      :accessor screenshot-device)\n     (image\n      :initarg :image\n      :initform nil\n      :reader screenshot-image)\n     (masks\n      :initarg :masks\n      :initform nil\n      :accessor screenshot-masks))\n    (:metaclass persistent-class)))\n\n(defclass lite-screenshot (abstract-screenshot)\n  ((%screenshot-key :initarg :screenshot-key\n                   :reader screenshot-key)\n   (%image-oid :initarg :image-oid\n               :reader image-oid))\n  (:documentation \"A lightweight screenshot\"))\n\n(defmethod screenshot-key ((screenshot screenshot))\n  (ensure-screenshot-key :name (screenshot-name screenshot)\n                         :lang (screenshot-lang screenshot)\n                         :device (screenshot-device screenshot)\n                         :masks (screenshot-masks screenshot)))\n\n(defmethod fset:compare ((one lite-screenshot) (two lite-screenshot))\n  (fset:compare-slots\n   one two\n   #'screenshot-key\n   #'image-oid))\n\n(defmethod screenshot-name ((self lite-screenshot))\n  (screenshot-name  (screenshot-key self)))\n\n(defmethod screenshot-lang ((self lite-screenshot))\n  (screenshot-lang (screenshot-key self)))\n\n(defmethod screenshot-device ((self lite-screenshot))\n  (screenshot-device (screenshot-key self)))\n\n(defmethod screenshot-masks ((self lite-screenshot))\n  (screenshot-masks (screenshot-key self)))\n\n(define-condition image-no-longer-exists-for-screenshot (error)\n  ())\n\n(defmethod screenshot-image ((self lite-screenshot))\n  (when-let ((oid (image-oid self)))\n    (let ((image (find-image-by-oid oid)))\n      (cond\n        (image image)\n        (t\n         (error 'image-no-longer-exists-for-screenshot))))))\n\n(defmethod encode-object ((self lite-screenshot) stream)\n  (bknr.datastore::%write-tag #\\S stream)\n  (encode (screenshot-key self) stream)\n  (encode (image-oid self) stream))\n\n(defmethod decode-object ((tag (eql #\\S)) stream)\n  (let ((key (decode stream))\n        (oid (decode stream)))\n   (make-instance 'lite-screenshot\n                  :screenshot-key key\n                  :image-oid oid)))\n\n(defmethod screenshot-image :around ((self screenshot))\n  (let ((obj (call-next-method self)))\n    (cond\n      ((oid-p obj)\n       (find-image-by-oid obj))\n      (t\n       obj))))\n\n(defmethod company ((self screenshot))\n  (company (screenshot-image self)))\n\n(defclass fake-screenshot (abstract-screenshot)\n  ((name :initarg :name\n         :accessor %screenshot-name)\n   (recorder-run\n    :initarg :run\n    :accessor screenshot-run)\n   (lang\n    :initarg :lang\n    :initform nil\n    :accessor screenshot-lang)\n   (device\n    :initarg :device\n    :initform nil\n    :accessor screenshot-device)\n   (image\n    :initarg :image\n    :initform nil\n    :accessor screenshot-image)\n   (masks\n    :initarg :masks\n    :initform nil\n    :accessor screenshot-masks))\n  (:documentation \"in memory screenshot object, used for canonical computation only\"))\n\n(defun screenshot-cache-key (screenshot)\n  (cons\n   (loop for x in (screenshot-masks screenshot)\n         appending\n         (rect-as-list x))\n   (mapcar (lambda (x) (funcall x screenshot))\n           (list 'screenshot-name\n                  'screenshot-lang\n                  'screenshot-device\n                  'screenshot-image))))\n\n;;(ensure-slot-boundp (store-objects-with-class 'screenshot) 'image)\n\n(let ((lock (bt:make-lock)))\n  (symbol-macrolet ((place (gethash (screenshot-cache-key screenshot) *screenshot-cache-v2*)))\n    (defun screenshot-add-to-cache (screenshot)\n      (check-type screenshot screenshot)\n      (bt:with-lock-held (lock)\n        (or place\n            (setf place\n                  screenshot))))\n    (defmethod bknr.datastore::destroy-object ((screenshot screenshot))\n      (bt:with-lock-held (lock)\n        (when (eql place screenshot)\n          (setf place nil)))\n      (call-next-method))\n\n    (defun screenshot-get-canonical (screenshot)\n      place)))\n\n\n(defmethod bknr.datastore:initialize-transient-instance :after ((screenshot screenshot))\n  (screenshot-add-to-cache screenshot))\n\n;; (mapc 'screenshot-add-to-cache (bknr.datastore:store-objects-with-class 'screenshot))\n\n\n(defmethod screenshot-name ((screenshot abstract-screenshot))\n  (%screenshot-name screenshot))\n\n(defun make-screenshot (&rest args &key image key &allow-other-keys)\n  (let ((screenshot-key\n          (cond\n            (key key)\n            (t\n             (apply\n              #'ensure-screenshot-key\n              (remove-from-plist args :image))))))\n    (make-instance 'lite-screenshot\n                   :screenshot-key screenshot-key\n                   :image-oid\n                   (when image\n                    (oid image :stringp nil)))))\n\n\n(defmethod can-view ((screenshot screenshot) user)\n  (can-view (screenshot-image screenshot) user))\n\n(defmethod %with-local-image ((screenshot abstract-screenshot) fn)\n  (%with-local-image (screenshot-image screenshot) fn))\n\n(defun find-in-run (run screenshot-name)\n  (when run\n    (let ((map (to-map (run-screenshot-map run))))\n      (multiple-value-bind (rank containsp)\n          (fset:rank map (make-instance 'fake-screenshot-key\n                                        :name screenshot-name))\n        (flet ((from-rank (rank)\n                 (when (< rank (fset:size map))\n                   (multiple-value-bind (key value)\n                       (fset:at-rank map rank)\n                     (when (equal (screenshot-name key) screenshot-name)\n                       (make-screenshot\n                        :key key\n                        :image value))))))\n         (cond\n           (containsp\n            (from-rank rank))\n           (t\n            (from-rank (1+ rank)))))))))\n\n(defun get-screenshot-history (channel screenshot-name &key (iterator nil)\n                                                         branch)\n  (let* ((branch (or branch (master-branch channel)))\n         (run (active-run channel branch)))\n    (assert-no-loops run)\n    (flet ((find-by-image (run screenshot)\n             (when run\n               (loop for s in (recorder-run-screenshots run)\n                     if (image=\n                         ;; We are doing the fast comparison here,\n                         ;; since this is only for history purposes.\n                         (make-instance 'base-image-comparer)\n                         (screenshot-image s)\n                         (screenshot-image screenshot)\n                         nil)\n                       return s))))\n      (let ((screenshot (find-in-run run screenshot-name)))\n        (labels ((iterator (run screenshot)\n                   (when run\n                     (let* ((prev-run (recorder-previous-run run))\n                            (image-comparer\n                              (make-image-comparer run))\n                            (prev-screenshot\n                              (when screenshot\n                                (or\n                                 (find-in-run prev-run\n                                              (screenshot-name screenshot))\n                                 (find-by-image prev-run screenshot)))))\n                       (flet ((respond (prev-screenshot)\n                                (let ((run run)\n                                      (screenshot screenshot))\n                                  (values (list screenshot run prev-screenshot)\n                                          (lambda ()\n                                            (iterator prev-run prev-screenshot))))))\n                         (cond\n                           ((not screenshot)\n                            ;; The screenshot didn't exist when the\n                            ;; channel was originally created, so we\n                            ;; reached the end of its history.\n                            (values nil nil))\n                           ((not prev-screenshot)\n                            (respond nil))\n                           ((and\n                             (string= (screenshot-name screenshot)\n                                      (screenshot-name prev-screenshot))\n                             (image=\n                              image-comparer\n                              (screenshot-image screenshot)\n                              (screenshot-image prev-screenshot)\n                              ;; TODO: should we use masks here?\n                              ;; Probably not.\n                              nil))\n                            ;; Tail call optimize the next call\n                            (iterator prev-run prev-screenshot))\n                           (t\n                            ;; in this, we found a difference between\n                            ;; the current and the next screenshot.\n                            (respond prev-screenshot))))))))\n\n          (cond\n            (iterator\n             (lambda ()\n              (iterator run screenshot)))\n            (t\n             (let ((iter (lambda () (iterator run screenshot))))\n               (labels ((to-list (iter &optional res)\n                          (cond\n                            (iter\n                             (multiple-value-bind (next next-iter) (funcall iter)\n                               (to-list next-iter (list* next res))))\n                            (t\n                             (remove-if #'null\n                              (mapcar #'first\n                                      (reverse res)))))))\n                 (to-list iter))))))))))\n\n(defmethod image-metadata ((self abstract-screenshot))\n  (image-metadata (screenshot-image self)))\n\n\n(defun make-screenshot-from-key (key image)\n  (make-instance 'fake-screenshot\n                 :name (screenshot-name key)\n                 :lang (screenshot-lang key)\n                 :device (screenshot-device key)\n                 :masks (screenshot-masks key)\n                 :image image))\n\n(defun make-key-from-screenshot (screenshot)\n  (ensure-screenshot-key\n   :name (screenshot-name screenshot)\n   :lang (screenshot-lang screenshot)\n   :device (screenshot-device screenshot)\n   :masks (screenshot-masks screenshot)))\n\n(defun remake-screenshot (screenshot)\n  (make-screenshot\n   :name (screenshot-name screenshot)\n   :lang (screenshot-lang screenshot)\n   :device (screenshot-device screenshot)\n   :masks (screenshot-masks screenshot)\n   :image (screenshot-image screenshot)))\n\n(defmethod auth:can-viewer-view (vc (self abstract-screenshot))\n  (auth:can-viewer-view vc (screenshot-image self)))\n"
  },
  {
    "path": "src/screenshotbot/model/sharing.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/sharing\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/object-id\n                #:object-with-oid)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:shares-for-company\n   #:share-creator\n   #:expiry-date))\n(in-package :screenshotbot/model/sharing)\n\n(with-class-validation\n (defclass share (object-with-oid)\n   ((object :initarg :object\n            :reader share-object\n            :index-type hash-index\n            :index-reader shares-for-object)\n    (creator :initarg :creator\n             :reader share-creator)\n    (%company :initarg :company\n              :reader company\n              :index-type hash-index\n              :index-reader shares-for-company)\n    (expiry-date :initarg :expiry-date\n                 :reader expiry-date\n                 :type (or null string)\n                 :documentation \"expiration date in yyyy-mm-dd format\")\n    (revoked-p :initform nil\n             :accessor share-revoked-p))\n   (:metaclass persistent-class)))\n\n(defmethod share-expired-p ((share share))\n  (or\n   (share-revoked-p share)\n   (unless (str:emptyp (expiry-date share))\n    (local-time:timestamp>=\n     (local-time:now)\n     (local-time:parse-timestring (expiry-date share))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-acceptable.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-acceptable\n  (:use #:cl\n        #:alexandria\n        #:fiveam\n        #:screenshotbot/model/report)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/channel\n                #:channel)\n  (:import-from #:screenshotbot/model/report\n                #:find-acceptables))\n(in-package :screenshotbot/model/test-acceptable)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run\n                 :channel channel\n                 :pull-request \"https://github.com/tdrhq/fast-example/pulls/20\"))\n           (report (make-instance 'report :run run\n                                  :channel channel))\n           (channel2 (make-instance 'channel))\n           (run2 (make-recorder-run\n                  :channel channel2\n                  :pull-request \"https://github.com/tdrhq/blah/pulls/20\"))\n           (report2 (make-instance 'report :run run\n                                           :channel channel2))\n           (run3 (make-recorder-run\n                  :channel channel\n                  :pull-request \"https://github.com/tdrhq/fast-example/pulls/21\"))\n           (report3 (make-instance 'report :run run3\n                                   :channel channel)))\n     (&body))))\n\n(test simple-acceptable-set-get\n  (with-fixture state ()\n   (let ((a (make-instance 'base-acceptable)))\n     (is (equal nil (acceptable-state a)))\n     (setf (acceptable-state a) :accepted)\n     (is (equal :accepted (acceptable-state a)))\n     (signals error\n       (setf (acceptable-state a) :foo)))))\n\n(test find-acceptables\n  (with-fixture state ()\n    (let ((acc (make-instance 'base-acceptable\n                            :report report)))\n      (is (equal\n           (list acc)\n           (find-acceptables channel\n                             :pull-request-id 20)))\n      (make-instance 'base-acceptable\n                     :report report2)\n      (is (equal\n           (list acc)\n           (find-acceptables channel\n                             :pull-request-id 20)))\n      (make-instance 'base-acceptable\n                     :report report3)\n\n      (is (equal\n           (list acc)\n           (find-acceptables channel\n                             :pull-request-id 20))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-archived-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-archived-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/archived-run\n                #:archived-run\n                #:archived-run-commit\n                #:archived-run-branch\n                #:archived-run-author\n                #:archived-run-cleanp\n                #:archived-run-created-at\n                #:archived-run-compare-threshold\n                #:archived-run-compare-tolerance\n                #:archived-run-channel\n                #:archived-run-company\n                #:archived-run-metadata\n                #:archived-run-tags\n                #:archived-run-screenshots\n                #:save-archived-run\n                #:load-archived-run\n                #:oid)\n  (:import-from #:screenshotbot/api/model\n                #:metadata\n                #:metadata-key\n                #:metadata-value)\n  (:import-from #:json-mop\n                #:json-to-clos)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-channel\n                #:run-screenshot-map\n                #:recorder-run-warnings\n                #:recorder-run-branch\n                #:recorder-run-tags)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at\n                #:channel\n                #:user)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:screenshot-map)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key)\n  (:import-from #:auth/viewer-context\n                #:api-viewer-context)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/model/test-archived-run)\n\n(util/fiveam:def-suite)\n\n(test write-and-read-archived-run\n  \"Test writing an archived-run to JSON and reading it back\"\n  (let* ((run (make-instance 'archived-run\n                             :commit-hash \"abc123def456\"\n                             :branch \"main\"\n                             :author \"test-author\"\n                             :cleanp t\n                             :created-at 1234567890\n                             :compare-threshold 0.05\n                             :compare-tolerance 10\n                             :channel 12345\n                             :company \"test-company\"))\n         (json-output (with-output-to-string (out)\n                        (json-mop:encode run out)))\n         (read-run (json-to-clos json-output 'archived-run)))\n\n    ;; Verify the data matches\n    (is (equal \"abc123def456\" (archived-run-commit read-run)))\n    (is (equal \"main\" (archived-run-branch read-run)))\n    (is (equal \"test-author\" (archived-run-author read-run)))\n    (is (equal 12345 (archived-run-channel read-run)))\n    (is (equal \"test-company\" (archived-run-company read-run)))\n\n    ;; Verify boolean is parsed correctly\n    (is (equal t (archived-run-cleanp read-run)))\n    (is (typep (archived-run-cleanp read-run) 'boolean))\n\n    ;; Verify integers are parsed correctly\n    (is (equal 1234567890 (archived-run-created-at read-run)))\n    (is (typep (archived-run-created-at read-run) 'integer))\n\n    ;; Verify numbers are parsed correctly\n    (is (< (abs (- 0.05 (archived-run-compare-threshold read-run))) 0.001))\n    (is (typep (archived-run-compare-threshold read-run) 'number))\n    (is (equal 10 (archived-run-compare-tolerance read-run)))\n    (is (typep (archived-run-compare-tolerance read-run) 'number))))\n\n(test write-multiple-archived-runs\n  \"Test encoding multiple archived-runs to JSON\"\n  (let* ((run1 (make-instance 'archived-run\n                              :commit-hash \"commit1\"\n                              :branch \"main\"\n                              :cleanp t))\n         (run2 (make-instance 'archived-run\n                              :commit-hash \"commit2\"\n                              :branch \"develop\"\n                              :cleanp nil))\n         (json1 (with-output-to-string (out)\n                  (json-mop:encode run1 out)))\n         (json2 (with-output-to-string (out)\n                  (json-mop:encode run2 out)))\n         (read-run1 (json-to-clos json1 'archived-run))\n         (read-run2 (json-to-clos json2 'archived-run)))\n\n    ;; Verify the data\n    (is (equal \"commit1\" (archived-run-commit read-run1)))\n    (is (equal \"commit2\" (archived-run-commit read-run2)))\n\n    ;; Verify booleans work with both true and false\n    (is (equal t (archived-run-cleanp read-run1)))\n    (is (typep (archived-run-cleanp read-run1) 'boolean))\n    (is (equal nil (archived-run-cleanp read-run2)))\n    (is (typep (archived-run-cleanp read-run2) 'boolean))))\n\n(test archived-run-with-metadata\n  \"Test that archived-run properly serializes and deserializes metadata objects\"\n  (let* ((meta1 (make-instance 'metadata\n                               :key \"build-id\"\n                               :value \"12345\"))\n         (meta2 (make-instance 'metadata\n                               :key \"environment\"\n                               :value \"production\"))\n         (run (make-instance 'archived-run\n                             :commit-hash \"test-commit\"\n                             :branch \"main\"\n                             :metadata (list meta1 meta2)))\n         (json-output (with-output-to-string (out)\n                        (json-mop:encode run out)))\n         (read-run (json-to-clos json-output 'archived-run)))\n\n    ;; Verify basic fields\n    (is (equal \"test-commit\" (archived-run-commit read-run)))\n    (is (equal \"main\" (archived-run-branch read-run)))\n\n    ;; Verify metadata is a list\n    (is (listp (archived-run-metadata read-run)))\n    (is (= 2 (length (archived-run-metadata read-run))))\n\n    ;; Verify first metadata object\n    (let ((read-meta1 (first (archived-run-metadata read-run))))\n      (is (typep read-meta1 'metadata))\n      (is (equal \"build-id\" (metadata-key read-meta1)))\n      (is (equal \"12345\" (metadata-value read-meta1))))\n\n    ;; Verify second metadata object\n    (let ((read-meta2 (second (archived-run-metadata read-run))))\n      (is (typep read-meta2 'metadata))\n      (is (equal \"environment\" (metadata-key read-meta2)))\n      (is (equal \"production\" (metadata-value read-meta2))))))\n\n(test save-and-load-archived-run\n  \"Test saving and loading an archived-run to/from disk\"\n  (with-test-store ()\n   (uiop:with-temporary-file (:pathname temp-dir :type :directory)\n     (let* ((run (make-instance 'archived-run\n                                :oid \"507f1f77bcf86cd799439011\"\n                                :commit-hash \"abc123def456\"\n                                :branch \"main\"\n                                :author \"test-author\"\n                                :cleanp t\n                                :created-at 1234567890\n                                :compare-threshold 0.05\n                                :compare-tolerance 10\n                                :channel 12345\n                                :company \"test-company\"))\n            (oid-str (oid run :stringp t)))\n\n       ;; Save the run\n       (let ((file-path (save-archived-run run)))\n         (assert-that\n          (namestring file-path)\n          (matches-regex \".*/50/7f/1f77bcf86cd799439011\" ))\n         (is (not (null file-path)))\n         (is (probe-file file-path)))\n\n       ;; Load the run back\n       (let ((loaded-run (load-archived-run oid-str)))\n         ;; Verify the data matches\n         (is (equal \"abc123def456\" (archived-run-commit loaded-run)))\n         (is (equal \"main\" (archived-run-branch loaded-run)))\n         (is (equal \"test-author\" (archived-run-author loaded-run)))\n         (is (equal 12345 (archived-run-channel loaded-run)))\n         (is (equal \"test-company\" (archived-run-company loaded-run)))\n         (is (equal t (archived-run-cleanp loaded-run)))\n         (is (equal 1234567890 (archived-run-created-at loaded-run)))\n         (is (< (abs (- 0.05 (archived-run-compare-threshold loaded-run))) 0.001))\n         (is (equal 10 (archived-run-compare-tolerance loaded-run))))))))\n\n(test non-existent-archived-run\n  (with-test-store ()\n    (is (eql nil\n             (load-archived-run \"507f1f77bcf86cd799439011\")))))\n\n(test archived-run-recorder-run-channel\n  \"Test that recorder-run-channel works with archived-run\"\n  (with-test-store ()\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel :company company))\n           (channel-id (store-object-id channel))\n           (run (make-instance 'archived-run\n                               :channel channel-id\n                               :company (store-object-id company))))\n      ;; Verify that recorder-run-channel returns the channel object\n      (is (eql channel (recorder-run-channel run))))))\n\n(test archived-run-run-screenshot-map\n  \"Test that run-screenshot-map works with archived-run\"\n  (with-test-store ()\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel :company company))\n           (screenshot-map (make-instance 'screenshot-map :channel channel))\n           (screenshot-map-id (store-object-id screenshot-map))\n           (run (make-instance 'archived-run\n                               :screenshots screenshot-map-id\n                               :channel (store-object-id channel)\n                               :company (store-object-id company))))\n      ;; Verify that run-screenshot-map returns the screenshot-map object\n      (is (eql screenshot-map (run-screenshot-map run))))))\n\n(test archived-run-recorder-run-warnings\n  \"Test that recorder-run-warnings returns nil for archived-run\"\n  (with-test-store ()\n    (let* ((run (make-instance 'archived-run\n                               :commit-hash \"test-commit\")))\n      ;; Verify that recorder-run-warnings returns nil\n      (is (eql nil (recorder-run-warnings run))))))\n\n(test archived-run-recorder-run-branch-reader\n  \"Test that recorder-run-branch reader works with archived-run\"\n  (let* ((run (make-instance 'archived-run\n                             :branch \"feature-branch\")))\n    ;; Verify that recorder-run-branch returns the branch\n    (is (equal \"feature-branch\" (recorder-run-branch run)))))\n\n(test archived-run-created-at-reader\n  \"Test that %created-at reader works with archived-run\"\n  (let* ((timestamp 1234567890)\n         (run (make-instance 'archived-run\n                             :created-at timestamp)))\n    ;; Verify that %created-at returns the timestamp\n    (is (equal timestamp (%created-at run)))))\n\n(test archived-run-recorder-run-tags-reader\n  \"Test that recorder-run-tags reader works with archived-run\"\n  (let* ((run (make-instance 'archived-run\n                             :tags \"v1.0.0\")))\n    ;; Verify that recorder-run-tags returns the tags\n    (is (equal \"v1.0.0\" (recorder-run-tags run)))))\n\n(test archived-run-can-view\n  \"Test that auth:can-view works with archived-run\"\n  (with-test-store ()\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel :company company))\n           (user (make-instance 'user))\n           (run (make-instance 'archived-run\n                               :channel (store-object-id channel)\n                               :company (store-object-id company))))\n      ;; Give the user access to the company\n      (roles:ensure-has-role company user 'roles:standard-member)\n      ;; Verify that the user can view the run\n      (is (equal t (auth:can-view run user))))))\n\n(test archived-run-can-viewer-view\n  \"Test that auth:can-viewer-view works with archived-run\"\n  (with-test-store ()\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel :company company))\n           (user (make-instance 'user))\n           (api-key (make-instance 'api-key\n                                   :user user\n                                   :company company\n                                   :permissions '(:full)))\n           (run (make-instance 'archived-run\n                               :channel (store-object-id channel)\n                               :company (store-object-id company))))\n      ;; Give the user access to the company\n      (roles:ensure-has-role company user 'roles:standard-member)\n      ;; Verify that the viewer can view the run\n      (assert-that\n       (auth:can-viewer-view\n        (make-instance 'api-viewer-context\n                       :user user\n                       :api-key api-key)\n        run)\n       (is-equal-to t)))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-auto-cleanup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-auto-cleanup\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/auto-cleanup\n                #:dispatch-cleanups\n                #:register-auto-cleanup\n                #:*cleanups*)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:all-store-objects)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/model/test-auto-cleanup)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((*cleanups* nil))\n      (register-auto-cleanup\n       'simple-obj\n       :timestamp #'ts)\n      (&body))))\n\n(defclass simple-obj (store-object)\n  ((ts :initarg :ts\n       :reader ts))\n  (:default-initargs :ts (get-universal-time))\n  (:metaclass persistent-class))\n\n(defclass simple-obj-2 (store-object)\n  ((ts :initarg :ts\n       :reader ts))\n  (:default-initargs :ts (get-universal-time))\n  (:metaclass persistent-class))\n\n(test simple-cleanup\n  (with-fixture state ()\n    (is (= 1 (length *cleanups*)))\n    (register-auto-cleanup\n     'simple-obj\n     :timestamp #'ts)\n    (is (= 1 (length *cleanups*)))))\n\n(test attempt-cleanup\n  (with-fixture state ()\n    (make-instance 'simple-obj)\n    (dispatch-cleanups)\n    (is (eql 1 (length (class-instances 'simple-obj))))))\n\n(test attempt-cleanup-with-old-objects\n  (with-fixture state ()\n    (let ((obj1 (make-instance 'simple-obj))\n          (obj2 (make-instance 'simple-obj :ts (- (get-universal-time)\n                                                  (* 24 3600 60)))))\n      (dispatch-cleanups)\n      (is (equal (list obj1)\n                 (class-instances 'simple-obj))))))\n\n(test only-delete-the-right-objects\n  (with-fixture state ()\n    (let ((ts (- (get-universal-time)\n                 (* 24 3600 60))))\n     (let ((obj1 (make-instance 'simple-obj-2 :ts ts))\n           (obj2 (make-instance 'simple-obj :ts ts)))\n       (dispatch-cleanups)\n       (is (equal (list obj1)\n                  (all-store-objects)))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-batch\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:company)\n  (:import-from #:screenshotbot/model/batch\n                #:find-batches-for-commit\n                #:batch-item\n                #:find-batch-item\n                #:find-or-create-batch)\n  (:import-from #:screenshotbot/user-api\n                #:can-view\n                #:user)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:promotion-log)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context))\n(in-package :screenshotbot/model/test-batch)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-installation ()\n     (let* ((company (make-instance 'company))\n            (user (make-instance 'user))\n            (channel (find-or-create-channel company \"test-channel\")))\n       (&body)))))\n\n(test find-instead-of-create\n  (with-fixture state ()\n   (is (eql\n        (find-or-create-batch\n         :company company\n         :repo \"http://foo.git\"\n         :commit \"abcd\"\n         :name \"foobar\")\n        (find-or-create-batch\n         :company company\n         :repo \"http://foo.git\"\n         :commit \"abcd\"\n         :name \"foobar\")))))\n\n(test find-batch-item\n  (with-fixture state ()\n    (let ((batch (find-or-create-batch\n                  :company company\n                  :repo \"http://foo.git\"\n                  :commit \"abcd\"\n                  :name \"foobar\")))\n      (let ((case1 (make-instance 'batch-item\n                     :batch batch\n                     :channel :foo))\n            (res (make-instance 'batch-item\n                                :batch batch\n                                :channel channel)))\n        (is (eql res (find-batch-item batch :channel channel)))\n        (is (eql case1 (find-batch-item batch :channel :foo)))))))\n\n\n(test can-view\n  (with-fixture state ()\n    (let ((batch (find-or-create-batch\n                  :company company\n                  :repo \"http://foo.git\"\n                  :commit \"abcd\"\n                  :name \"foobar\")))\n      (is-false (auth:can-viewer-view\n                 (make-instance 'normal-viewer-context\n                                :user user)\n                 batch)))))\n\n(test find-batches-for-company ()\n  (with-fixture state ()\n    (let ((other-company (make-instance 'company)))\n      (let ((batch-1 (find-or-create-batch\n                      :repo \"https://foo.git\"\n                      :company company\n                      :commit \"abcd\"\n                      :name \"foobar\"))\n            (batch-2 (find-or-create-batch\n                      :repo \"https://foo.git\"\n                      :company company\n                      :commit \"abcd\"\n                      :name \"foobar-another\"))\n            (batch-3 (find-or-create-batch\n                      :repo \"https://foo.git\"\n                      :company other-company\n                      :commit \"abcd\"))\n            (batch-4 (find-or-create-batch\n                      :repo \"https://foo.git\"\n                      :company company\n                      :commit \"a000\")))\n        (is (not (eql batch-2 batch-1)))\n        (assert-that (find-batches-for-commit :commit \"abcd\"\n                                              :company company)\n                     (contains\n                      batch-1 batch-2))))))\n\n\n(test promotion-log-for-batch\n  (with-fixture state ()\n    (let ((batch (find-or-create-batch\n                  :repo \"dfdf\"\n                  :company company\n                  :commit \"abcd\")))\n      (is (pathnamep\n           (blob-pathname\n            (promotion-log batch)))))))\n\n\n(test saving-batch-items\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (make-instance 'batch-item :batch \"foo\"\n                                 :title \"bar\")\n      (finishes (bknr.datastore:snapshot)))\n    (with-test-store (:dir dir)\n      (pass))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-build-info.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-build-info\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/build-info\n                #:build-url\n                #:find-or-create-build-info)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user))\n(in-package :screenshotbot/model/test-build-info)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-test-user (:company company)\n     (&body))))\n\n(test find-or-create-build-info-test\n  (with-fixture state ()\n   (let ((build-url \"https://example.com/build/123\"))\n     ;; Test creating new build-info\n     (let ((build-info-1 (find-or-create-build-info company build-url)))\n       (is (not (null build-info-1)))\n       (is (string= build-url (build-url build-info-1)))\n      \n       ;; Test finding existing build-info\n       (let ((build-info-2 (find-or-create-build-info company build-url)))\n         (is (eq build-info-1 build-info-2)))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/model/test-channel.lisp",
    "content": ";; -*- coding: utf-8 -*-\n;;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/model/test-channel\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/company\n        #:fiveam)\n  (:import-from #:screenshotbot/model/channel\n                #:shortened-channel-name\n                #:channel-active-commits\n                #:repos-for-company\n                #:channels-for-company\n                #:review-policy-name\n                #:review-policy\n                #:%review-policy\n                #:get-full-repo-from-repo)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:runs-for-channel\n                #:unchanged-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/model/review-policy\n                #:disallow-author-review-policy\n                #:anyone-can-review)\n  (:import-from #:fiveam-matchers/lists\n                #:contains\n                #:contains-in-any-order))\n\n(util/fiveam:def-suite)\n\n(defclass unserializable () ())\n\n(def-fixture state ()\n  (with-test-store ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run\n                 :channel channel)))\n      (&body))))\n\n(test active-runs\n  (with-fixture state ()\n   (is (equal nil (active-run channel \"foo\")))\n    (setf (active-run channel \"foo\") run)\n    (is (eql run (active-run channel \"foo\")))))\n\n(test production-run-for\n  (with-fixture state ()\n   (let* ((company (make-instance 'company))\n          (channel (make-instance 'channel\n                                   :company company)))\n     (is (fset:empty? (runs-for-channel channel)))\n     (let ((run (make-recorder-run :channel channel\n                                   :company company)))\n       (is (equal (list run)\n                  (fset:convert 'list\n                   (runs-for-channel channel))))))))\n\n(test find-production-run-for-commit\n  (with-fixture state ()\n   (let* ((channel (make-instance 'channel))\n          (foo-run (make-recorder-run :channel channel\n                                      :commit-hash \"foo\"\n                                      :trunkp t))\n          (bar-run (make-recorder-run :channel channel\n                                      :commit-hash \"bar\"\n                                      :trunkp t)))\n     (is (eql foo-run (production-run-for channel :commit \"foo\")))\n     (is (eql bar-run (production-run-for channel :commit \"bar\")))\n     (let ((another-bar-run (make-recorder-run\n                             :channel channel\n                             :commit-hash \"bar\")))\n       (is (eql bar-run (production-run-for channel :commit \"bar\")))))))\n\n(test find-production-run-for-commit-does-not-pick-non-production-runs\n  (with-fixture state ()\n   (let* ((channel (make-instance 'channel))\n          (foo-run (make-recorder-run :channel channel\n                                      :commit-hash \"foo\"\n                                      :trunkp nil))\n          (bar-run (make-recorder-run :channel channel\n                                      :commit-hash \"bar\"\n                                      :trunkp nil)))\n     (is (eql nil (production-run-for channel :commit \"foo\")))\n     (is (eql nil (production-run-for channel :commit \"bar\")))\n     (let ((another-bar-run (make-recorder-run\n                             :channel channel\n                             :commit-hash \"bar\"\n                             :trunkp t)))\n       (is (eql another-bar-run (production-run-for channel :commit \"bar\")))))))\n\n(test find-production-run-unchanged-run\n  (with-fixture state ()\n   (let* ((channel (make-instance 'channel))\n          (unchanged-run (make-instance 'unchanged-run\n                                  :channel channel\n                                  :commit \"foo\"\n                                  :other-commit \"bar\"))\n          (bar-run (make-recorder-run :channel channel\n                                      :commit-hash \"bar\"\n                                      :trunkp t)))\n     (is (eql bar-run (production-run-for channel :commit \"bar\")))\n     (is (eql bar-run (production-run-for channel :commit \"foo\")))\n\n     (let ((another-bar-run (make-recorder-run\n                             :channel channel\n                             :commit-hash \"bar\")))\n       (is (eql bar-run (production-run-for channel :commit \"foo\")))\n       (is (eql bar-run (production-run-for channel :commit \"bar\")))))))\n\n(test finds-production-run-if-theres-also-an-unchanged-run-for-that-commit\n  (with-fixture state ()\n   (let* ((channel (make-instance 'channel))\n          (unchanged-run (make-instance 'unchanged-run\n                                  :channel channel\n                                  :commit \"bar\"\n                                  :other-commit \"bar\"))\n          (bar-run (make-recorder-run :channel channel\n                                      :commit-hash \"bar\"\n                                      :trunkp t)))\n     (is (eql bar-run (production-run-for channel :commit \"bar\")))\n     (is (eql nil (production-run-for channel :commit \"foo\"))))))\n\n(test detect-infinite-loops-in-unchanged-run\n  (with-fixture state ()\n   (let* ((channel (make-instance 'channel))\n          (foo-run (make-instance 'unchanged-run\n                                  :channel channel\n                                  :commit \"foo\"\n                                  :other-commit \"bar\"))\n          (bar-run (make-instance 'unchanged-run\n                                  :channel channel\n                                  :commit \"bar\"\n                                  :other-commit \"foo\")))\n     (is (eql nil (production-run-for channel :commit \"bar\"))))))\n\n(test get-full-repo-from-repo\n  (is (equal \"foo/bar\"\n             (get-full-repo-from-repo \"git@github.com:foo/bar.git\"))))\n\n\n(test channel-print-object\n  (with-fixture state ()\n    (is (equal \"#<CHANNEL foobar>\"\n               (prin1-to-string\n                (make-instance 'channel\n                               :name \"foobar\"))))\n    (assert-that (prin1-to-string (make-instance 'channel))\n                 (contains-string \"CHANNEL\"))))\n\n(test review-policy-happy-path\n  (with-fixture state ()\n    (let ((channel (make-instance 'channel :name \"foobar\")))\n      (assert-that (review-policy channel)\n                   (has-typep 'anyone-can-review))\n      (slot-makunbound channel '%review-policy)\n      (assert-that (review-policy channel)\n                   (has-typep 'anyone-can-review))\n      (setf (review-policy-name channel) :disallow-author)\n      (assert-that (review-policy channel)\n                   (has-typep 'disallow-author-review-policy)))))\n\n(test sets-was-promoted-p\n  (with-fixture state ()\n    (is-false (was-promoted-p run))\n    (setf (active-run channel \"master\") run)\n    (is-true (was-promoted-p run))\n    (setf (active-run channel \"master\") (make-recorder-run :channel channel\n                                                           :screenshots nil))\n    (is-true (was-promoted-p run))))\n\n(test channels-for-company\n  (with-fixture state ()\n    (let ((channel-1\n            (make-instance 'channel\n                           :company :company-1))\n          (channel-2\n            (make-instance 'channel\n                           :company :company-1))\n          (channel-3\n            (make-instance 'channel\n                           :company :company-3)))\n      (assert-that\n       (channels-for-company :company-1)\n       (contains-in-any-order channel-1 channel-2))\n      (assert-that\n       (channels-for-company :company-3)\n       (contains channel-3)))))\n\n(test repos-for-company\n  (with-fixture state ()\n   (let ((channel-1\n           (make-instance 'channel\n                          :company :company-1\n                          :github-repo \"https://github.com/tdrhq/fast-example.git\"))\n         (channel-2\n           (make-instance 'channel\n                          :company :company-1\n                          :github-repo \"https://gitlab.com/foo/bar.git\"))\n         (channel-3\n           (make-instance 'channel\n                          :company :company-3\n                          :github-repo \"https://github.com/tdrhq/bleh.git\")))\n     (assert-that\n      (repos-for-company :company-1)\n      (contains-in-any-order \"https://github.com/tdrhq/fast-example.git\"\n                             \"https://gitlab.com/foo/bar.git\")))))\n\n(test repos-for-company-removes-duplicates\n  (with-fixture state ()\n   (let ((channel-1\n           (make-instance 'channel\n                          :company :company-1\n                          :github-repo \"https://github.com/tdrhq/fast-example.git\"))\n         (channel-2\n           (make-instance 'channel\n                          :company :company-1\n                          :github-repo \"https://github.com/tdrhq/fast-example.git\"))\n         (channel-3\n           (make-instance 'channel\n                          :company :company-3\n                          :github-repo \"https://github.com/tdrhq/bleh.git\")))\n     (assert-that\n      (repos-for-company :company-1)\n      (contains-in-any-order \"https://github.com/tdrhq/fast-example.git\")))))\n\n(test active-commits\n  (with-fixture state ()\n    (is (equal nil (channel-active-commits channel)))\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"foobar\"\n                :commit-hash \"abcd\")))\n      (setf (active-run channel \"foobar\") run)\n      (is (equal (list \"abcd\") (channel-active-commits channel))))\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"car\"\n                :commit-hash \"aaaa\")))\n      (setf (active-run channel \"car\") run)\n      (assert-that (channel-active-commits channel)\n                   (contains-in-any-order \"aaaa\" \"abcd\" )))\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"car\"\n                :commit-hash nil)))\n      (setf (active-run channel \"dar\") run)\n      (assert-that (channel-active-commits channel)\n                   (contains-in-any-order \"aaaa\" \"abcd\" )))\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"car\"\n                :commit-hash \"\")))\n      (setf (active-run channel \"dar\") run)\n      (assert-that (channel-active-commits channel)\n                   (contains-in-any-order \"aaaa\" \"abcd\" )))))\n\n(test active-commits-removes-duplicates\n  (with-fixture state ()\n    (is (equal nil (channel-active-commits channel)))\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"foobar\"\n                :commit-hash \"abcd\")))\n      (setf (active-run channel \"foobar\") run)\n      (is (equal (list \"abcd\") (channel-active-commits channel))))\n    (let ((run (make-recorder-run\n                :channel channel\n                :work-branch \"car\"\n                :commit-hash \"abcd\")))\n      (setf (active-run channel \"car\") run)\n      (assert-that (channel-active-commits channel)\n                   (contains-in-any-order \"abcd\" )))))\n\n(test shortened-channel-name\n  (is (equal \"foobar\" (shortened-channel-name \"foobar\")))\n  (is (equal \"foo/bar\" (shortened-channel-name \"foo/bar\")))\n  (is (equal \"foo/.../la/car\"\n             (shortened-channel-name\n              \"foo/bar/dar/blah/la/car\"\n              :length 14)))\n  (is (equal \"foo/.../la/c:ar\"\n             (shortened-channel-name\n              \"foo/bar/dar/blah/la/c:ar\"\n              :length 15)))\n  (is (equal \"foo/...ah/la/car\"\n             (shortened-channel-name\n              \"foo/bar/dar/blah/la/car\"\n              :length 16)))\n  (is (equal \"...ar\"\n             (shortened-channel-name \"foobar\" :length 5)))\n  (is (equal \"...la/car\"\n             (shortened-channel-name\n              \"foobar/bar/dar/blah/la/car\"\n              :length 9))))\n\n(test shortened-channel-name-for-gradle\n  (is (equal \"foobar\" (shortened-channel-name \"foobar\")))\n  (is (equal \"foo:bar\" (shortened-channel-name \"foo:bar\")))\n  (is (equal \"foo:...:la:car\"\n             (shortened-channel-name\n              \"foo:bar:dar:blah:la:car\"\n              :length 14)))\n  (is (equal \"foo:...:la:c/ar\"\n             (shortened-channel-name\n              \"foo:bar:dar:blah:la:c/ar\"\n              :length 15)))\n  (is (equal \"foo:...ah:la:car\"\n             (shortened-channel-name\n              \"foo:bar:dar:blah:la:car\"\n              :length 16)))\n  (is (equal \"...ar\"\n             (shortened-channel-name \"foobar\" :length 5)))\n  (is (equal \"...la:car\"\n             (shortened-channel-name\n              \"foobar:bar:dar:blah:la:car\"\n              :length 9))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-commit-graph\n  (:use #:cl\n        #:fiveam\n        #:screenshotbot/model/commit-graph\n        #:screenshotbot/model/company)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:%commit-graph-refs\n                #:commit-graph-refs\n                #:normalization-override\n                #:merge-dag-into-commit-graph\n                #:dag-v2\n                #:%persisted-dag\n                #:normalize-url\n                #:needs-flush-p\n                #:flush-dags)\n  (:import-from #:cl-mock\n                #:answer\n                #:if-called)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:has-typep\n                #:assert-that\n                #:equal-to\n                #:is-not)\n  (:import-from #:fiveam-matchers/misc\n                #:is-not-null)\n  (:import-from #:util/store/simple-object-snapshot\n                #:snapshot-slot-value)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :screenshotbot/model/test-commit-graph)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key dir)\n  (with-test-store (:dir dir)\n    (let ((company (make-instance 'company))\n          (other-company (make-instance 'company)))\n      (let ((dag (make-instance 'dag:dag)))\n        (dag:add-commit dag (make-instance 'dag:commit\n                                       :sha \"aa\") )\n       (&body)))))\n\n(test simple-find-or-create\n  (with-fixture state ()\n    (is (eql\n         (find-or-create-commit-graph company \"foo\")\n         (find-or-create-commit-graph company \"foo\")))))\n\n(test set-and-get-dag\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"Foo\")))\n      (merge-dag-into-commit-graph cg dag)\n      (is-false (needs-flush-p cg)))))\n\n(test simple-snapshot-and-restore\n  (tmpdir:with-tmpdir (dir)\n    (with-fixture state (:dir dir)\n      (let ((cg (find-or-create-commit-graph company \"Foo\")))\n        (merge-dag-into-commit-graph cg dag)\n        (util:safe-snapshot \"..\" t)))\n    (with-test-store (:dir dir))))\n\n(test flush-dags\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"Foo\")))\n      (merge-dag-into-commit-graph cg dag)\n      (finishes\n        (flush-dags))\n      (is-false (needs-flush-p cg)))))\n\n(test normalize-url\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"https://github.com/tdrhq/fast-example.git\")))\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"https://github.com/tdrhq/fast-example.git/\")))\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"https://github.com/tdrhq/fast-example/\")))\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"https://github.com/tdrhq/Fast-Example/\")))\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"git@github.com/tdrhq/fast-example/\")))\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"git@github.com:tdrhq/fast-example/\")))\n  (is (equal \"https://github.com/tdrhq/fast-example\"\n             (normalize-url \"ssh://git@github.com:tdrhq/fast-example/\"))))\n\n(test normalize-url-removes-auth-info\n  (is (equal\n       \"https://gitlab.com/abc/def/g/hij/klm\"\n       (normalize-url \"https://gitlab-ci-token:blahblah@gitlab.com/abc/def/g/hij/klm\"))))\n\n(test normalization-overrides\n  (with-fixture state ()\n    (make-instance 'normalization-override\n                   :from \"foobar\"\n                   :to \"bar\")\n    (is (equal \"bar\" (normalize-url \"foobar\")))\n    (is (equal \"https://github.com/tdrhq/fast-example\"\n               (normalize-url \"git@github.com/tdrhq/fast-example/\")))))\n\n(test find-or-create-normalized-commit-graph\n  (with-fixture state ()\n    (is (eql\n         (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example\")\n         (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example.git\")))))\n\n(test if-normalization-algorithm-changes-we-still-use-url\n  (with-fixture state ()\n    (let ((original (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example\")))\n      (cl-mock:with-mocks ()\n        (answer (normalize-url \"https://github.com/tdrhq/fast-example\") \"foo\")\n        (is (eql original\n                 (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example\")))))))\n\n(test we-only-return-a-commit-graph-for-the-right-company\n  (with-fixture state ()\n    (let ((first-version (find-or-create-commit-graph other-company \"https://github.com/tdrhq/fast-example.git\")))\n      (assert-that\n       (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example\")\n       (is-not\n        (equal-to\n         first-version))\n       (is-not-null)))))\n\n(test pulls-the-graph-for-the-right-company\n  (with-fixture state ()\n    (let ((other-cg (find-or-create-commit-graph other-company \"https://github.com/tdrhq/fast-example.git\"))\n          (cg (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example.git\")))\n      (assert-that\n       (find-or-create-commit-graph company \"https://github.com/tdrhq/fast-example\")\n       (equal-to cg))\n      (assert-that\n       (find-or-create-commit-graph other-company \"https://github.com/tdrhq/fast-example\")\n       (equal-to other-cg)))))\n\n(test snapshot-slot-value\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"foo\")))\n      (setf (%persisted-dag cg) nil #| probably already nil though |#)\n      (is (eql nil (snapshot-slot-value cg 'dag-v2)))\n      (setf (%persisted-dag cg) dag)\n      (assert-that (snapshot-slot-value cg 'dag-v2)\n                   (has-typep 'dag:dag)\n                   (is-not (is-equal-to dag))))))\n\n(test merging-into-graph\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"foo\")))\n      (merge-dag-into-commit-graph cg dag)\n      (is-true (%persisted-dag cg))\n      (is-true (dag:get-commit (%persisted-dag cg) \"aa\"))\n      (is-false (dag:get-commit (%persisted-dag cg) \"bb\"))\n      (merge-dag-into-commit-graph cg dag)\n      (let ((dag (make-instance 'dag:dag)))\n        (dag:add-commit dag (make-instance 'dag:commit\n                                           :sha \"bb\"))\n        (merge-dag-into-commit-graph cg dag)\n        (is-true (dag:get-commit (%persisted-dag cg) \"aa\"))\n        (is-true (dag:get-commit (%persisted-dag cg) \"bb\"))))))\n\n(test merging-a-non-shallow-commit-into-a-shallow-commit\n  \"There are probably old versions of the CLIs still sending shallow\ncommits every now and then and we need to handle it.\"\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"foo\")))\n      (merge-dag-into-commit-graph cg dag)\n      (is-true (%persisted-dag cg))\n      (is-true (dag:get-commit (%persisted-dag cg) \"aa\"))\n      (assert-that (dag:parents (dag:get-commit (%persisted-dag cg) \"aa\"))\n                   (has-length 0))\n\n      (let ((dag (make-instance 'dag:dag)))\n        (dag:add-commit dag (make-instance 'dag:commit\n                                           :sha \"aa\"\n                                           :parents (list \"cc\")))\n        (merge-dag-into-commit-graph cg dag)\n        (is-true (dag:get-commit (%persisted-dag cg) \"aa\"))\n        (assert-that (dag:parents (dag:get-commit (%persisted-dag cg) \"aa\"))\n                     (has-length 1))\n        (is-false (dag:get-commit (%persisted-dag cg) \"cc\"))))))\n\n\n(test commit-graph-refs-when-refs-is-nil\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"foo\")))\n      (is (fset:equal?\n           (fset:empty-map)\n           (commit-graph-refs cg))))))\n\n(test commit-graph-refs-when-refs-from-old-model\n  (with-fixture state ()\n    (let ((cg (find-or-create-commit-graph company \"foo\")))\n      (setf (%commit-graph-refs cg)\n            `((\"foo\" . \"bar\")\n              (\"car\" . \"dar\")))\n      (is (fset:equal?\n           (fset:with\n            (fset:with\n             (fset:empty-map)\n             \"foo\" \"bar\")\n            \"car\" \"dar\")\n           (commit-graph-refs cg))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-company-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-company-graph\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:multi-org-test-installation\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:screenshotbot/model/company-graph\n                #:copy-keys\n                #:copy-other-snapshot-files\n                #:*lparallelp*\n                #:save-images\n                #:company-full-graph\n                #:company-graph)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:is-not\n                #:assert-that)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:screenshotbot/model/constant-string\n                #:constant-string)\n  (:local-nicknames (#:roles #:auth/model/roles)))\n(in-package :screenshotbot/model/test-company-graph)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*lparallelp* nil))\n   (with-test-store ()\n     (with-installation (:installation (make-instance 'multi-org-test-installation))\n       (&body)))))\n\n(test happy-path-graph\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (assert-that (company-graph company)\n                   (has-item user))\n      (with-test-user (:user other-user :company company2 :company-name \"other company\")\n        (assert-that (company-graph company)\n                     (is-not (has-item other-user)))))))\n\n(test full-graph-without-connecting-the-companies\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (assert-that (company-full-graph company)\n                   (has-item user))\n      (with-test-user (:user other-user :company company2 :company-name \"other company\")\n        (assert-that (company-full-graph company)\n                     (is-not (has-item other-user)))))))\n\n(defclass dummy-class (store-object)\n  ((one :initarg :one)\n   (two :initarg :two\n        :reader two))\n  (:metaclass persistent-class)\n  (:documentation \"Sometimes you might connect two objects with something like this.\"))\n\n(test full-graph-finds-everything\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (with-test-user (:user other-user :company company2 :company-name \"other company\")\n        (make-instance 'dummy-class\n                       :one user\n                       :two other-user)\n        (is (not (equal company2 company)))\n        (assert-that (company-full-graph company)\n                     (has-item other-user)\n                     (has-item user)\n                     (has-item company2)\n                     (has-item company))))))\n\n(test full-graph-doesnt-go-via-strings\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let ((str \"abcde\"))\n       (with-test-user (:user other-user :company company2 :company-name \"other company\")\n         (let ((obj1 (make-instance 'dummy-class\n                                    :one company\n                                    :two str))\n               (obj2 (make-instance 'dummy-class\n                                    :one company2\n                                    :two str)))\n           (is (eql (two obj1)\n                    (two obj2))))\n         (is (not (equal company2 company)))\n         (assert-that (company-full-graph company)\n                      (has-item company)\n                      (does-not (has-item company2))))))))\n\n\n(test full-graph-doesnt-go-via-constant-strings\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let ((str \"abcde\"))\n       (with-test-user (:user other-user :company company2 :company-name \"other company\")\n         (let ((obj1 (make-instance 'dummy-class\n                                    :one company\n                                    :two (constant-string str)))\n               (obj2 (make-instance 'dummy-class\n                                    :one company2\n                                    :two (constant-string str))))\n           (is (eql (two obj1)\n                    (two obj2))))\n         (is (not (equal company2 company)))\n         (assert-that (company-full-graph company)\n                      (has-item company)\n                      (has-item (constant-string \"abcde\"))\n                      (does-not (has-item company2))))))))\n\n\n(test copies-images\n  (with-fixture state ()\n    (with-test-user (:company company)\n      (let ((img (make-image :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\")\n                             :oid #(1 2 3 4 1 2 3 4 1 2 3 4)\n                             :company company)))\n        (is (equal \"010203040102030401020304\" (oid img)))\n        (tmpdir:with-tmpdir (dir)\n          (finishes\n            (save-images (company-full-graph company) :output dir))\n          (is-true\n           (path:-d (path:catdir dir \"image-blobs/01/\")))\n          (is-true\n           (path:-e (path:catfile dir \"image-blobs/01/02/03040102030401020304\"))))))))\n\n(test test-reverse-graph-basic-structure\n  ;; Tests that reverse-graph creates the correct inverse relationships\n  ;; This ensures the graph structure is properly inverted for traversal\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let ((graph (screenshotbot/model/company-graph::reverse-graph :undirected t)))\n        (is (hash-table-p graph))\n        ;; Verify that the user-roles object connects user and company\n        (let ((user-roles (find-if (lambda (obj)\n                                     (and (typep obj 'roles::user-roles)\n                                          (eq (roles:role-user obj) user)\n                                          (eq (roles:role-company obj) company)))\n                                   (bknr.datastore:store-objects-with-class 'roles::user-roles))))\n          (assert-that (gethash user-roles graph)\n                       (has-item company)\n                       (has-item user)))))))\n\n(test test-reverse-graph-ignores-atoms\n  ;; Tests that reverse-graph properly excludes atomic values like strings and numbers\n  ;; This prevents false connections through shared primitive values\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let ((obj1 (make-instance 'dummy-class\n                                 :one company\n                                 :two \"shared-string\"))\n            (obj2 (make-instance 'dummy-class\n                                 :one user\n                                 :two 42)))\n        (let ((graph (screenshotbot/model/company-graph::reverse-graph)))\n          ;; Strings and numbers should not appear as keys in the reverse graph\n          (is (null (gethash \"shared-string\" graph)))\n          (is (null (gethash 42 graph))))))))\n\n\n(test fset-map\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let* ((key1 user)\n             (val1 company)\n             (key2 \"string-key\")\n             (val2 42)\n             (test-map (fset:map (key1 val1) (key2 val2))))\n        (let ((neighbors (screenshotbot/model/company-graph::object-neighbors-for-graph test-map)))\n          (assert-that neighbors\n                       (has-item key1)\n                       (has-item val1)\n                       (has-item key2)\n                       (has-item val2))\n          ;; Verify we get exactly 4 items (2 keys + 2 values)\n          (is (= 4 (length neighbors))))))))\n\n\n\n(test hash-table-object-neighbors-for-graph\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (let ((test-hash (make-hash-table :test 'equal)))\n        (setf (gethash user test-hash) company)\n        (setf (gethash \"key1\" test-hash) \"value1\")\n        (setf (gethash 42 test-hash) user)\n        (let ((neighbors (screenshotbot/model/company-graph::object-neighbors-for-graph test-hash)))\n          (assert-that neighbors\n                       (has-item user)\n                       (has-item company)\n                       (has-item \"key1\")\n                       (has-item \"value1\")\n                       (has-item 42))\n          ;; Verify we get exactly 6 items (3 keys + 3 values)\n          (is (= 6 (length neighbors))))))))\n\n(test save-and-load-graph-with-blobs\n  (let (obj1-id obj2-id)\n   (cl-mock:with-mocks ()\n     (tmpdir:with-tmpdir (dir)\n       ;; Create and save objects in the first store\n       (with-fixture state ()\n         (with-test-user (:user user :company company)\n           (cl-mock:if-called 'copy-keys\n                              (lambda (output)\n                                (declare (ignore output))))\n           (cl-mock:if-called 'copy-other-snapshot-files\n                              (lambda (output)\n                                (declare (ignore output))))\n\n           ;; Create two dummy objects with slot values\n           (let ((obj1 (make-instance 'dummy-class\n                                      :one user\n                                      :two \"second-value\"))\n                 (obj2 (make-instance 'dummy-class\n                                      :one user\n                                      :two company)))\n             (setf obj1-id (bknr.datastore:store-object-id obj1))\n             (setf obj2-id (bknr.datastore:store-object-id obj2))\n             ;; Save the company graph to the tmpdir\n             (finishes\n               (screenshotbot/model/company-graph::save-graph-and-blobs company :output dir)))))\n\n       ;; Load the store from the saved directory\n       (let ((*lparallelp* nil))\n         (with-test-store (:dir dir)\n           (with-installation (:installation (make-instance 'multi-org-test-installation))\n             ;; Find the loaded objects by iterating through store objects\n             (let ((loaded-objects (bknr.datastore:class-instances 'dummy-class)))\n               ;; Verify we have exactly 2 dummy-class objects\n               (is (= 2 (length loaded-objects)))\n\n               ;; Find the objects by their slot values\n               (let ((loaded-obj1 (bknr.datastore:store-object-with-id obj1-id))\n                     (loaded-obj2 (bknr.datastore:store-object-with-id obj2-id)))\n                 ;; Verify obj1 was loaded with correct slot values\n                 (is-true loaded-obj1)\n                 (is (not (null (slot-value loaded-obj1 'one))))\n                 (is (equal \"second-value\" (two loaded-obj1)))\n\n                 ;; Verify obj2 was loaded with correct slot values\n                 (is-true loaded-obj2)\n                 (is (typep (slot-value loaded-obj2 'one) 'bknr.datastore:store-object))\n                 (is (typep (two loaded-obj2) 'bknr.datastore:store-object)))))))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/model/test-company.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/test-company\n    (:use #:cl\n          #:fiveam\n          #:screenshotbot/model/company)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:screenshotbot/user-api\n                #:user\n                #:company-name)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/model/company\n                #:needs-sso-condition\n                #:emails-enabled-by-default-p\n                #:ensure-company-using-roles\n                #:company-owner\n                #:sub-company)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context\n                #:logged-in-viewer-context)\n  (:import-from #:core/installation/auth-provider\n                #:company-sso-auth-provider)\n  (:import-from #:screenshotbot/server\n                #:needs-sso-condition-company\n                #:needs-sso-condition)\n  (:import-from #:auth/login/sso\n                #:cant-view-company-condition-company\n                #:cant-view-company-condition)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:local-nicknames (#:roles #:auth/model/roles)))\n(in-package :screenshotbot/model/test-company)\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-installation (:installation (make-instance 'my-installation))\n      (let ((company (make-instance 'company))\n            (user (make-instance 'user)))\n        (&body)))))\n\n(test jira-config\n  (with-fixture state ()\n   (let ((company (make-instance 'company)))\n     (is-true (jira-config company))\n     (is (eql (jira-config company)\n              (jira-config company)))\n     (delete-object company))))\n\n(test company-name\n  (with-fixture state ()\n   (let ((company (make-instance 'company\n                                  :personalp t\n                                  )))\n     (unwind-protect\n          (progn\n            (is (equal \"Default\" (company-name\n                                  company))))\n       (delete-object company)))))\n\n(test sub-company-and-such\n  (with-fixture state ()\n    (let* ((company (make-instance 'company))\n           (child (make-instance 'sub-company\n                                :parent company)))\n      (let* ((user (make-user :companies (list company)))\n            (vc (make-instance 'logged-in-viewer-context\n                               :user user)))\n        (is-true (auth:can-viewer-view vc company))\n        (is-true (auth:can-viewer-view vc child)))\n\n      (let* ((user (make-user :companies (list child)))\n             (vc (make-instance 'logged-in-viewer-context\n                                :user user)))\n        (is-false (auth:can-viewer-view vc company))\n        (is-true (auth:can-viewer-view vc child))))))\n\n\n(test can-view-respects-redirect-url\n  (with-fixture state ()\n    (let* ((company (make-instance 'company))\n           (company-with-redirect (make-instance 'company :redirect-url \"https://one.example.com\"))\n           (company-with-redirect-2 (make-instance 'company :redirect-url \"https://example.com\")))\n      (let* ((user (make-user :companies (list company-with-redirect company company-with-redirect-2)))\n             (vc (make-instance 'logged-in-viewer-context :user user)))\n        (is-true (auth:can-viewer-view vc company))\n        (is-false (auth:can-viewer-view vc company-with-redirect))\n        (is-true (auth:can-viewer-view vc company-with-redirect-2))\n        (let ((*installation* (make-instance 'my-installation :domain \"https://one.example.com\")))\n          (is-true (auth:can-viewer-view vc company-with-redirect)))))))\n\n(test company-owner\n  (with-fixture state ()\n    (let* ((user (make-user))\n           (company (make-instance 'company)))\n      (setf (slot-value company 'screenshotbot/model/company::owner) user)\n      (is (eql user (company-owner company))))))\n\n(test company-owner-when-owner-is-role\n  (with-fixture state ()\n    (let* ((user (make-user))\n           (company (make-instance 'company :owner :roles)))\n      (make-instance 'roles::user-roles :user user\n                                        :company company\n                                        :role 'roles:owner)\n      (is (eql user (company-owner company))))))\n\n(test company-owner-when-no-owner-exists\n  (with-fixture state ()\n    (let* ((user (make-user))\n           (company (make-instance 'company :owner :roles)))\n      (make-instance 'roles::user-roles :user user\n                                        :company company\n                                        :role 'roles:standard-member)\n      (is (eql nil (company-owner company))))))\n\n(test make-instance-doesnt-accept-invalid-args\n  (with-fixture state ()\n    (signals #+lispworks conditions:unknown-keyword-error #-lispworks error\n      (make-instance 'company :dfdfdfd 2))))\n\n(test ensure-company-using-roles\n  (with-fixture state ()\n    (let* ((user (make-user))\n           (company (make-instance 'company)))\n      (setf (slot-value company 'screenshotbot/model/company::owner) user)\n      (ensure-company-using-roles company)\n      (is (eql :roles (slot-value company 'screenshotbot/model/company::owner)))\n      (is-true\n       (roles:has-role-p company user 'roles:owner))\n\n      ;; what if they're already migrated?\n      (ensure-company-using-roles company)\n      (is-false\n       (roles:has-role-p company :roles 'roles:owner)))))\n\n\n(test emails-enabled-by-default-for-sub-company\n  (with-fixture state ()\n    (let* ((company (make-instance 'company\n                                   :emails-enabled-by-default-p :foobar))\n           (sub-company (make-instance 'sub-company\n                                       :parent company)))\n      (is (eql :foobar (emails-enabled-by-default-p company))))))\n\n(def-fixture user-company-with-sso (&key (role 'roles:admin)\n                                    (sso-p t))\n  (let ((user (make-user))\n        (company (make-instance 'company)))\n    (when sso-p\n     (setf (company-sso-auth-provider company) :foobar))\n    (roles:ensure-has-role company user role)\n    (&body)))\n\n(test normal-viewer-context-can-view-sso-company-if-the-user-is-an-admin\n  (with-fixture state ()\n    (with-fixture user-company-with-sso (:role 'roles:admin)\n      (let ((vc (make-instance 'normal-viewer-context :user user)))\n        (is-true (auth:can-viewer-view vc company))))))\n\n(test normal-viewer-context-cant-view-sso-company\n  (with-fixture state ()\n    (with-fixture user-company-with-sso (:role 'roles:standard-member)\n      (let ((vc (make-instance 'normal-viewer-context :user user)))\n        (is-false (auth:can-viewer-view vc company))))))\n\n(test normal-viewer-context-cant-view-sso-company-and-signals-condition\n  (with-fixture state ()\n    (with-fixture user-company-with-sso (:role 'roles:standard-member)\n      (let ((vc (make-instance 'normal-viewer-context :user user))\n            (actual-condition))\n        (handler-bind ((needs-sso-condition (lambda (c)\n                                              (setf actual-condition c))))\n          (auth:can-viewer-view vc company))\n        (is-true actual-condition)\n        (is (eql company (needs-sso-condition-company actual-condition)))))))\n\n(test normal-viewer-context-can-view-non-sso-company\n  (with-fixture state ()\n    (with-fixture user-company-with-sso (:role 'roles:standard-member :sso-p nil)\n      (let ((vc (make-instance 'normal-viewer-context :user user)))\n        (is-true (auth:can-viewer-view vc company))))))\n\n(test cant-view-company-condition-not-triggered\n  (with-fixture state ()\n    (roles:ensure-has-role company user 'roles:standard-member)\n    (let ((vc (make-instance 'normal-viewer-context :user user)))\n      (is-true (auth:can-viewer-view vc company))\n      (handler-case\n          (auth:can-viewer-view vc company)\n        (cant-view-company-condition (c)\n          (fail \"not expected to see this condition\"))))))\n\n(test cant-view-company-condition-is-triggered\n  (with-fixture state ()\n    (let ((vc (make-instance 'normal-viewer-context :user user)))\n      (is-false (auth:can-viewer-view vc company))\n      (let ((condition nil))\n        (finishes\n          (handler-bind ((cant-view-company-condition (lambda (c)\n                                                        (setf condition c))))\n            (auth:can-viewer-view vc company)))\n        (assert-that condition\n                     (described-as \"cant-view-company-condition should've been signalled\"\n                       (has-typep 'cant-view-company-condition)))\n        (assert-that\n         (cant-view-company-condition-company condition)\n         (is-equal-to company))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-constant-string.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-constant-string\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/constant-string\n                #:ensure-slot-constant-string\n                #:constant-string-string\n                #:constant-string)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object))\n(in-package :screenshotbot/model/test-constant-string)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test simple-constant-string\n  (with-fixture state ()\n    (is (eql nil (constant-string nil)))\n    (is (typep (constant-string \"foobar\") 'constant-string))\n    (is (eql\n         (constant-string \"foobar\")\n         (constant-string \"foobar\")))\n    (is (not\n         (eql\n          (constant-string \"foobar\")\n          (constant-string \"carbar\"))))))\n\n(test coerce-back-into-string\n  (with-fixture state ()\n    (is (equal \"foobar\" (constant-string-string (constant-string \"foobar\"))))))\n\n(test print-object\n  (with-fixture state ()\n    (is (equal \"foobar\" (format nil \"~a\" (constant-string \"foobar\"))))))\n\n(test comparison\n  (with-fixture state ()\n    (is (eql\n         :less\n         (fset:compare \"abc\"\n                       \"bar\")))\n    (is (eql\n         :less\n         (fset:compare (constant-string \"abc\")\n                       \"bar\")))\n    (is (eql\n         :less\n         (fset:compare (constant-string \"abc\")\n                       (constant-string \"bar\"))))\n    (is (eql\n         :greater\n         (fset:compare (constant-string \"bar\")\n                       (constant-string \"abc\"))))\n    (is (fset:equal?\n         (constant-string \"car\")\n         \"car\"))))\n\n(defclass foo (store-object)\n  ((bar :initarg :bar))\n  (:metaclass persistent-class))\n\n(test ensure-slot-constant-string\n  (with-fixture state ()\n   (let ((obj (make-instance 'foo\n                             :bar \"car\")))\n     (ensure-slot-constant-string\n      (list obj)\n      'bar)\n     (is (typep (slot-value obj 'bar)\n                'constant-string)))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-core\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp\n                #:generate-api-key)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:has-all\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/model/test-core)\n\n(util/fiveam:def-suite)\n\n(defclass foo (store-object)\n  ((slot1 :initarg :slot1))\n  (:metaclass persistent-class))\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test ensure-slot-boundp\n  (with-fixture state ()\n    (loop for i from 0 to 2000\n          do (make-instance 'foo))\n    (is (not (slot-boundp (car (class-instances 'foo)) 'slot1)))\n    (ensure-slot-boundp 'foo 'slot1)\n    (is (slot-boundp (car (class-instances 'foo)) 'slot1))\n    (assert-that\n     (loop for obj in (class-instances 'foo)\n           collect\n           (slot-boundp obj 'slot1))\n     (does-not\n      (contains nil)))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-counter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-counter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/counter\n                #:defcounter\n                #:next-counter)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store/store\n                #:with-test-store))\n(in-package :screenshotbot/model/test-counter)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((company (make-instance 'company))\n          (other-company (make-instance 'company)))\n      (&body))))\n\n(defcounter my-counter ())\n\n(test simple-counter-invocation\n  (with-fixture state ()\n    (is (eql 1 (next-my-counter company)))\n    (is (eql 2 (next-my-counter company)))\n    (is (eql 3 (next-my-counter company)))))\n\n(test each-company-get-its-own\n  (with-fixture state ()\n    (is (eql 1 (next-my-counter company)))\n    (is (eql 1 (next-my-counter other-company)))\n    (is (eql 2 (next-my-counter company)))\n    (is (eql 3 (next-my-counter company)))\n    (is (eql 2 (next-my-counter other-company)))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-downloadable-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-downloadable-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/downloadable-run\n                #:build-archive\n                #:delete-old-runs\n                #:downloadable-run\n                #:find-or-create-downloadable-run)\n  (:import-from #:bknr.datastore\n                #:blob-pathname\n                #:class-instances)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex))\n(in-package :screenshotbot/model/test-downloadable-run)\n\n(util/fiveam:def-suite)\n\n(defvar *image* (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\"))\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (let* ((company (make-instance 'company))\n            (img (make-image :pathname *image*))\n            (run (make-recorder-run\n                  :screenshots (list\n                                (make-screenshot\n                                 :image img\n                                 :name \"foobar\")))))\n       (&body)))))\n\n(test find-or-create\n  (with-fixture state ()\n    (is (eql\n         (find-or-create-downloadable-run run)\n         (find-or-create-downloadable-run run)))\n    (is-true (find-or-create-downloadable-run run))))\n\n(test delete-old-runs\n  (with-fixture state ()\n    (let* ((d1 (find-or-create-downloadable-run run))\n           (d2 (make-instance 'downloadable-run\n                              :created-at 10\n                              :run (make-recorder-run)))\n           (pathname (bknr.datastore:blob-pathname d2)))\n      (with-open-file (file pathname :direction :output))\n      (assert-that (class-instances 'downloadable-run)\n                   (has-length 2))\n      (is (path:-e pathname))\n      (delete-old-runs)\n      (assert-that (class-instances 'downloadable-run)\n                   (contains d1))\n      (is (not (path:-e pathname))))))\n\n\n(test delete-old-runs-when-the-file-does-not-exist\n  (with-fixture state ()\n    (let* ((d1 (find-or-create-downloadable-run run))\n           (d2 (make-instance 'downloadable-run\n                              :created-at 10\n                              :run (make-recorder-run)))\n           (pathname (bknr.datastore:blob-pathname d2)))\n      (is (not (path:-e pathname)))\n      (delete-old-runs)\n      (assert-that (class-instances 'downloadable-run)\n                   (contains d1)))))\n\n(test build-archive\n  (with-fixture state ()\n    (let ((d1 (find-or-create-downloadable-run run)))\n      (finishes\n        (build-archive d1))\n      (zip:with-zipfile (reader (blob-pathname d1))\n        (assert-that (alexandria:hash-table-keys (zip:zipfile-entries reader))\n                     (contains\n                      (matches-regex \".*/foobar.png\")))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-figma.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-figma\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/figma\n                #:update-figma-link\n                #:figma-link-url\n                #:find-existing-figma-link\n                #:figma-link)\n  (:import-from #:util/store/store\n                #:with-test-store))\n(in-package :screenshotbot/model/test-figma)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n   (let* ((channel (make-instance 'screenshotbot/model/channel:channel\n                                  :name \"test-channel\"))\n          (figma-link (make-instance 'figma-link\n                                     :channel channel\n                                     :screenshot-name \"test-screenshot\"\n                                     :url \"https://www.google.com\"\n                                     :image (make-instance 'screenshotbot/model/image:image))))\n     (&body))))\n\n(test preconditions\n  (with-fixture state ()\n    (pass)))\n\n(test find-figma-link\n  (with-fixture state ()\n    (is (equal figma-link\n               (find-existing-figma-link\n                :channel channel\n                :screenshot-name \"test-screenshot\")))))\n\n(test update-figma-link\n  (with-fixture state ()\n    (is (equal \"https://www.google.com\"\n               (figma-link-url\n                (find-existing-figma-link\n                 :channel channel\n                 :screenshot-name \"test-screenshot\"))))\n    (update-figma-link :channel channel :screenshot-name \"test-screenshot\"\n                       :url \"https://example.com\")\n    (is (equal \"https://example.com\"\n               (figma-link-url\n                (find-existing-figma-link\n                 :channel channel\n                 :screenshot-name \"test-screenshot\"))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-finalized-commit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-finalized-commit\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/finalized-commit\n                #:finalized-commit\n                #:finalized-commit-company\n                #:finalized-commit-hash\n                #:commit-finalized-p\n                #:find-finalized-commit\n                #:find-or-create-finalized-commit))\n(in-package :screenshotbot/model/test-finalized-commit)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((company (make-instance 'company)))\n     (&body))))\n\n(test simple-test\n  (with-fixture state ()\n    (is (eql nil (commit-finalized-p company \"foo\")))\n    (make-instance 'finalized-commit\n                   :company company\n                   :commit \"foo\")\n    (is (eql t (commit-finalized-p company \"foo\")))))\n\n(test restricted-to-company\n  (with-fixture state ()\n    (let ((company-2 (make-instance 'company)))\n      (make-instance 'finalized-commit\n                     :company company\n                     :commit \"foo\")\n      (is (eql t (commit-finalized-p company \"foo\"))))))\n\n(test find-finalized-commit-returns-nil-when-not-found\n  (with-fixture state ()\n    (is (eql nil (find-finalized-commit company \"nonexistent\")))))\n\n(test find-finalized-commit-returns-object-when-found\n  (with-fixture state ()\n    (let ((fc (make-instance 'finalized-commit\n                             :company company\n                             :commit \"abc123\")))\n      (is (eql fc (find-finalized-commit company \"abc123\"))))))\n\n(test find-finalized-commit-restricted-to-company\n  (with-fixture state ()\n    (let ((company-2 (make-instance 'company)))\n      (let ((fc1 (make-instance 'finalized-commit\n                                :company company\n                                :commit \"shared-commit\"))\n            (fc2 (make-instance 'finalized-commit\n                                :company company-2\n                                :commit \"shared-commit\")))\n        (is (eql fc1 (find-finalized-commit company \"shared-commit\")))\n        (is (eql fc2 (find-finalized-commit company-2 \"shared-commit\")))\n        (is (not (eql fc1 fc2)))))))\n\n(test find-or-create-finalized-commit-creates-when-not-found\n  (with-fixture state ()\n    (is (eql nil (find-finalized-commit company \"new-commit\")))\n    (let ((fc (find-or-create-finalized-commit company \"new-commit\")))\n      (is (not (null fc)))\n      (is (typep fc 'finalized-commit))\n      (is (eql company (finalized-commit-company fc)))\n      (is (equal \"new-commit\" (finalized-commit-hash fc))))))\n\n(test find-or-create-finalized-commit-returns-existing\n  (with-fixture state ()\n    (let ((fc1 (make-instance 'finalized-commit\n                              :company company\n                              :commit \"existing\")))\n      (let ((fc2 (find-or-create-finalized-commit company \"existing\")))\n        (is (eql fc1 fc2))\n        (is (eql 1 (length (bknr.datastore:class-instances 'finalized-commit))))))))\n\n(test find-or-create-finalized-commit-respects-company-isolation\n  (with-fixture state ()\n    (let ((company-2 (make-instance 'company)))\n      (let ((fc1 (find-or-create-finalized-commit company \"commit-sha\"))\n            (fc2 (find-or-create-finalized-commit company-2 \"commit-sha\")))\n        (is (not (eql fc1 fc2)))\n        (is (eql company (finalized-commit-company fc1)))\n        (is (eql company-2 (finalized-commit-company fc2)))\n        (is (eql 2 (length (bknr.datastore:class-instances 'finalized-commit))))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-image-comparer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-image-comparer\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:get-non-alpha-pixels)\n  (:import-from #:screenshotbot/model/image\n                #:mask-rect\n                #:image-hash\n                #:base-image-comparer\n                #:make-image\n                #:image=)\n  (:import-from #:screenshotbot/model/image-comparer\n                #:image-equal-cache\n                #:compare-threshold\n                #:make-image-comparer\n                #:threshold-comparer)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/model/testing\n                #:with-test-image))\n(in-package :screenshotbot/model/test-image-comparer)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key (threshold 0))\n  (with-test-store ()\n    (with-installation ()\n      (let ((comparer (make-instance 'threshold-comparer :threshold threshold)))\n        (&body)))))\n\n(test test-with-test-image\n  (with-fixture state ()\n    (with-test-image (name)\n      (pass))))\n\n(test compare-two-identical-images\n  (with-fixture state ()\n    (with-test-image (f1 :pixels '((1 2)))\n      (with-test-image (f2 :pixels '((1 2)))\n        (is-true\n         (image=\n          comparer\n          (make-image :pathname f1)\n          (make-image :pathname f2)\n          nil))))))\n\n(test compare-two-unidentical-images\n  (with-fixture state (:threshold 0)\n    (with-test-image (f1 :pixels '((1 2)))\n      (with-test-image (f2)\n        (is-false\n         (image=\n          comparer\n          (make-image :pathname f1)\n          (make-image :pathname f2)\n          nil))))))\n\n(test with-comparison-threshold\n  (with-fixture state (:threshold 1/100)\n    (with-test-image (f1 :pixels '((1 2)))\n      (with-test-image (f2)\n        (is-true\n         (image=\n          comparer\n          (make-image :pathname f1)\n          (make-image :pathname f2)\n          nil))))))\n\n(test with-ridiculously-low-threshold\n  (with-fixture state (:threshold 1/10000)\n    (with-test-image (f1 :pixels '((1 2)))\n      (with-test-image (f2)\n        (is-false\n         (image=\n          comparer\n          (make-image :pathname f1)\n          (make-image :pathname f2)\n          nil))))))\n\n(test with-comparison-threshold-but-more-failures\n  (with-fixture state (:threshold 1/100)\n    (with-test-image (f1 :pixels '((1 2)))\n      (with-test-image (f2 :pixels '((3 3)))\n        (is-false\n         (image=\n          comparer\n          (make-image :pathname f1)\n          (make-image :pathname f2)\n          nil))))))\n\n(test different-dimensions-is-always-false\n  (with-fixture state (:threshold 1/2)\n    (with-test-image (f1 :height 9)\n      (with-test-image (f2)\n        (is-false\n         (image=\n          comparer\n          (make-image :pathname f1)\n          (make-image :pathname f2)\n          nil))))))\n\n(test make-image-comparer-when-no-threshold\n  (with-fixture state ()\n    (assert-that\n     (make-image-comparer (make-recorder-run))\n     (has-typep 'base-image-comparer))))\n\n(test make-image-comparer-when-zero-threshold\n  (with-fixture state ()\n    (assert-that\n     (make-image-comparer (make-recorder-run\n                           :compare-threshold 0.0))\n     (has-typep 'base-image-comparer))))\n\n(test make-image-comparer-when-non-zero-threshold\n  (with-fixture state ()\n    (let ((comparer (make-image-comparer (make-recorder-run\n                                         :compare-threshold 0.001))))\n      (assert-that\n       comparer\n       (has-typep 'threshold-comparer))\n      (assert-that\n       (compare-threshold comparer)\n       (is-equal-to 0.001)))))\n\n(test threshold-image-comparison-is-cached\n  (with-fixture state ()\n    (with-test-image (file1)\n      (with-test-image (file2 :pixels '((5 5)))\n        (let ((im1 (make-image :pathname file1))\n              (im2 (make-image :pathname file2))\n              (comparer (make-image-comparer (make-recorder-run\n                                              :compare-threshold 0.001)))\n              (counter 0))\n          (is (not (eql im1 im2)))\n          (is (not (equalp (image-hash im1) (image-hash im2))))\n          (is-false (image= (make-instance 'base-image-comparer)\n                            im1 im2 nil))\n          (cl-mock:with-mocks ()\n            (cl-mock:if-called 'get-non-alpha-pixels\n                               (lambda (&rest args)\n                                 (incf counter)\n                                 (make-array '(2 2))))\n            (is-false (image= comparer im1 im2 nil))\n            (is (eql 1 counter))\n            (is-false (image= comparer im1 im2 nil))\n            (assert-that\n             counter\n             (described-as \"The image processing must be cached the second time\"\n               (is-equal-to 1)))\n            (assert-that\n             (class-instances 'image-equal-cache)\n             (has-length 1))))))))\n\n(test threshold-image-comparison-is-cached-when-same-too\n  (with-fixture state ()\n    (with-test-image (file1)\n      (with-test-image (file2 :pixels '((5 5)))\n        (let ((im1 (make-image :pathname file1))\n              (im2 (make-image :pathname file2))\n              (comparer (make-image-comparer (make-recorder-run\n                                              :compare-threshold 0.1)))\n              (counter 0))\n          (is (not (eql im1 im2)))\n          (is (not (equalp (image-hash im1) (image-hash im2))))\n          (is-false (image= (make-instance 'base-image-comparer)\n                            im1 im2 nil))\n          (cl-mock:with-mocks ()\n            (cl-mock:if-called 'get-non-alpha-pixels\n                               (lambda (&rest args)\n                                 (incf counter)\n                                 (make-array '(2 2))))\n            (is-true (image= comparer im1 im2 nil))\n            (is (eql 1 counter))\n            (is-true (image= comparer im1 im2 nil))\n            (assert-that\n             counter\n             (described-as \"The image processing must be cached the second time\"\n               (is-equal-to 1)))\n            (assert-that\n             (class-instances 'image-equal-cache)\n             (has-length 1))))))))\n\n(test images-that-are-same-are-not-cached\n  (with-fixture state ()\n    (with-test-image (file1)\n      (let ((im1 (make-image :pathname file1))\n            (comparer (make-image-comparer (make-recorder-run\n                                            :compare-threshold 0.1))))\n        (is-true (image= comparer im1 im1 nil))\n        (assert-that\n         (class-instances 'image-equal-cache)\n         (has-length 0))))))\n\n(test negative-mask-coordinates-bug\n  \"Demonstrates bug with negative mask coordinates being treated as unsigned.\n   Two images differ at pixel (135,416) but the mask with negative coordinates\n   should cover the entire image, making them equal.\"\n  (with-fixture state ()\n    (with-test-image (f1 :width 2118 :height 2532)\n      (with-test-image (f2 :width 2118 :height 2532\n                           :pixels '((135 416)))\n        (let* ((im1 (make-image :pathname f1))\n               (im2 (make-image :pathname f2))\n               ;; Mask that should cover entire image (from user's bug report)\n               (mask (make-instance 'mask-rect\n                                    :left -388.29245\n                                    :top -81.14493\n                                    :width 2703.924\n                                    :height 2707.5926))\n               (masks (list mask)))\n          ;; The mask covers from (-388.29, -81.14) to (2315.63, 2626.45)\n          ;; which should fully cover the 2118x2532 image\n          ;; Therefore the single pixel difference at (135, 416) is masked\n          ;; and the images should be considered equal\n          (is-true\n           (image=\n            comparer\n            im1\n            im2\n            masks)\n           \"Images with masked difference should be equal, but negative mask coordinates are treated as unsigned size_t, causing them to wrap to huge positive values\"))))))\n\n(test negative-mask-coordinates-bug--ensure-0-0-is-still-under-mask\n  \"Demonstrates bug with negative mask coordinates being treated as unsigned.\n   Two images differ at pixel (135,416) but the mask with negative coordinates\n   should cover the entire image, making them equal.\"\n  (with-fixture state ()\n    (with-test-image (f1 :width 2118 :height 2532)\n      (with-test-image (f2 :width 2118 :height 2532\n                           :pixels '((0 0)))\n        (let* ((im1 (make-image :pathname f1))\n               (im2 (make-image :pathname f2))\n               ;; Mask that should cover entire image (from user's bug report)\n               (mask (make-instance 'mask-rect\n                                    :left -388.29245\n                                    :top -81.14493\n                                    :width 2703.924\n                                    :height 2707.5926))\n               (masks (list mask)))\n          ;; The mask covers from (-388.29, -81.14) to (2315.63, 2626.45)\n          ;; which should fully cover the 2118x2532 image\n          ;; Therefore the single pixel difference at (135, 416) is masked\n          ;; and the images should be considered equal\n          (is-true\n           (image=\n            comparer\n            im1\n            im2\n            masks)\n           \"Images with masked difference should be equal, but negative mask coordinates are treated as unsigned size_t, causing them to wrap to huge positive values\"))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-image-comparison.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-image-comparison\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/model/image-comparison\n                #:remove-image-comparison\n                #:identical-p\n                #:image-comparison-difference-value\n                #:image-comparison-after\n                #:image-comparison-result\n                #:find-image-comparison-from-cache\n                #:image-comparison-before\n                #:make-image-comparison\n                #:*stored-cache*\n                #:image-comparison\n                #:do-image-comparison\n                #:find-image-comparison-on-images)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:magick-get-image-height\n                #:magick-get-image-width\n                #:with-wand)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/testing\n                #:with-test-image)\n  (:local-nicknames (#:a #:alexandria)\n                    #-lispworks\n                    (#:fli #:util/fake-fli)))\n(in-package :screenshotbot/model/test-image-comparison)\n\n(util/fiveam:def-suite)\n\n;; TODO: im1 and im2 seem like they're identical, and that might've been a typo from the past\n(defvar im1 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\"))\n(defvar im2 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-2.png\"))\n(defvar im3 #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image-3.png\"))\n\n(def-fixture state (&key dir)\n  (let ((*installation* (make-instance 'installation)))\n    (with-test-store ()\n      (flet ((inner (dir)\n               (with-fake-request ()\n                 (let (objs)\n                   (labels ((make-screenshot (img)\n                              (let* ((image (make-image :pathname img)))\n                                (make-instance 'screenshot\n                                               :name \"foobar\"\n                                               :image image))))\n                     (&body))))))\n        (cond\n          (dir\n           (inner dir))\n          (t\n           (tmpdir:with-tmpdir (dir2)\n             (inner dir2))))))))\n\n(test do-image-comparison\n  (with-fixture state ()\n    (is-true (uiop:file-exists-p im1))\n    (is-true (uiop:file-exists-p im2))\n    (uiop:with-temporary-file (:pathname out :type \"png\")\n      (is-false\n       (do-image-comparison (make-screenshot im1) (make-screenshot im3) out)))\n    (uiop:with-temporary-file (:pathname out :type \"png\")\n      (is-true\n       (do-image-comparison (make-screenshot im1) (make-screenshot im2) out)))))\n\n(test do-image-comparison-for-webp\n  (with-fixture state ()\n    (is-true (uiop:file-exists-p im1))\n    (is-true (uiop:file-exists-p im2))\n    (uiop:with-temporary-file (:pathname out :type \"webp\")\n      (finishes\n        (do-image-comparison (make-screenshot im1) (make-screenshot im3) out)))))\n\n(test find-image-comparison-on-images\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1))\n          (after (make-image :pathname im3)))\n      ;; this will create a new image-comparison\n      (let ((result (find-image-comparison-on-images before after)))\n        (is-true result)\n        ;; We used to test that this object is the same as before, but\n        ;; with sqlite that might no longer be the case.\n        (is-true (find-image-comparison-on-images before after))\n        (is-false (identical-p result))\n        (is (< 0 (image-comparison-difference-value result)))))))\n\n(test difference-value-is-populated-if-it-doesnt-exist\n  \"This is a migration strategy, to avoid having to recompute everything\nfrom beginning of time. It might be reasonable to delete in the future.\"\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1))\n          (after (make-image :pathname im3)))\n      ;; this will create a new image-comparison\n      (let ((result (find-image-comparison-on-images before after)))\n        (setf (image-comparison-difference-value result) nil)\n\n        (is-true (find-image-comparison-on-images before after))\n        (is-false (identical-p result))\n\n        (is-true (image-comparison-difference-value result))\n        (is (< 0 (image-comparison-difference-value result)))))))\n\n(test difference-value-is-not-populated-if-we-request-only-cached-values\n  \"Same as the previous test, if if we have :only-cached-p, then we don't expect\nto be doing image processing during the request.\"\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1))\n          (after (make-image :pathname im3)))\n      ;; this will create a new image-comparison\n      (let ((result (find-image-comparison-on-images before after)))\n        (setf (image-comparison-difference-value result) nil)\n\n        (is-true (find-image-comparison-on-images before after :only-cached-p t))\n        (is-false (identical-p result))\n\n        (is-false (image-comparison-difference-value result))))))\n\n(test order-doesnt-matter\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1))\n          (after (make-image :pathname im3)))\n      ;; this will create a new image-comparison\n      (let ((result (find-image-comparison-on-images before after)))\n        (is-true result)\n        (is (eql result (find-image-comparison-on-images after before)))))))\n\n(test only-cached-p-should-not-create-a-new-one\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1))\n          (after (make-image :pathname im3)))\n      ;; this will create a new image-comparison\n      (is (eql nil\n               (find-image-comparison-on-images before after\n                                                :only-cached-p t))))))\n\n(test propagates-company\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1 :company :company-2))\n          (after (make-image :pathname im2 :company :company-2)))\n      (let ((result (find-image-comparison-on-images before after)))\n        (is\n         (eql\n          :company-2\n          (company (image-comparison-result result))))))))\n\n(test both-images-have-to-be-same-company\n  (with-fixture state ()\n    (let ((before (make-image :pathname im1 :company :company-1))\n          (after (make-image :pathname im2 :company :company-2)))\n      (signals simple-error\n       (find-image-comparison-on-images after before #| here |#)))))\n\n\n(def-easy-macro with-file-copy (&binding result file &fn fn)\n  (uiop:with-temporary-file (:pathname res :stream s)\n    (close s)\n    (delete-file res)\n    (fad:copy-file file res)\n    (funcall fn (namestring res))))\n\n(def-fixture stored-cache ()\n  (unwind-protect\n       (&body)\n    (setf *stored-cache* (fset:empty-set))))\n\n(test saving-and-restoring-subsystem\n  (with-fixture stored-cache ()\n    (is (fset:equal? *stored-cache* (fset:empty-set)))\n    (tmpdir:with-tmpdir (dir)\n      (with-test-store (:dir dir)\n        (let ((im1 (make-image-comparison :before \"foo\" :after \"bar\"\n                                          :result \"car\"))\n              (im2 (make-image-comparison :before \"foo1\" :after \"bar1\"\n                                          :result \"car1\")))\n          (util:safe-snapshot)\n          (is (eql 2 (fset:size *stored-cache*)))))\n      (is (fset:equal? *stored-cache* (fset:empty-set)))\n      (with-test-store (:dir dir)\n        (is (eql 2 (fset:size *stored-cache*)))\n        (is (equal \"foo\"\n                   (image-comparison-before (fset:least *stored-cache*))))\n        (is (equal \"foo1\"\n                   (image-comparison-before (fset:greatest *stored-cache*))))))))\n\n(defun check-for-bad-state ()\n  #+lispworks ;; For reasons I don't yet understand, this failed on SBCL\n  (fset:do-set (imc *stored-cache*)\n    (is-true (image-comparison-before imc))\n    (is-true (image-comparison-after imc))\n    (is-true (image-comparison-result imc))))\n\n(test saving-and-restoring-actual-image-objects\n  (with-fixture stored-cache ()\n    (let ((*installation* (make-instance 'installation)))\n     (tmpdir:with-tmpdir (dir)\n       (with-test-store (:dir dir)\n         (let ((s1 (make-image :pathname im1))\n               (s2 (make-image :pathname im2)))\n           (make-image-comparison :before s1 :after s2 :result s2)\n           (is (eql 1 (fset:size *stored-cache*)))\n           (check-for-bad-state)\n           (util:safe-snapshot)))\n       (is (eql 0 (fset:size *stored-cache*)))\n       (with-test-store (:dir dir)\n         (is (eql 1 (fset:size *stored-cache*)))\n         (check-for-bad-state))))))\n\n(test finding-image-comparisons\n  (with-fixture stored-cache ()\n    (let ((*installation* (make-instance 'installation)))\n     (tmpdir:with-tmpdir (dir)\n       (with-test-store (:dir dir)\n         (let ((s1 (make-image :pathname im1))\n               (s2 (make-image :pathname im2)))\n           (make-image-comparison :before s1 :after s2 :result \"bleh\")\n           (let ((imc\n                   (find-image-comparison-from-cache\n                    :before s1 :after s2)))\n             (is (equal \"bleh\" (image-comparison-result imc))))))))))\n\n(test saving-deleted-objects\n  (with-fixture stored-cache ()\n    (let ((*installation* (make-instance 'installation)))\n     (tmpdir:with-tmpdir (dir)\n       (with-test-store (:dir dir)\n         (let ((s1 (make-image :pathname im1))\n               (s2 (make-image :pathname im2)))\n           (make-image-comparison :before s1 :after s2 :result s2)\n           (make-image-comparison :before s2 :after s2 :result s2)\n           (is (eql 2 (fset:size *stored-cache*)))\n           (check-for-bad-state)\n           (delete-object s1)\n           (util:safe-snapshot)))\n       (is (eql 0 (fset:size *stored-cache*)))\n       (with-test-store (:dir dir)\n         (is (eql 1 (fset:size *stored-cache*)))\n         (check-for-bad-state))))))\n\n(defun image-dimensions-for-pathname (pathname)\n  \"Returns a list of (width height) for the image at pathname\"\n  (with-wand (wand :file pathname)\n    (list (magick-get-image-width wand)\n          (magick-get-image-height wand))))\n\n\n\n(test different-sized-images-should-not-be-identical\n  \"Regression test: images of different sizes should never be considered identical,\n   even if all overlapping pixels match\"\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname out :type \"png\")\n      ;; Test case 1: second image larger than first\n      (with-test-image (small-img :width 10 :height 10 :color \"red\")\n        (with-test-image (large-img :width 20 :height 20 :color \"red\")\n          (is-false (do-image-comparison \n                      (make-screenshot small-img) \n                      (make-screenshot large-img) \n                      out))\n          (is (equal\n               (list 20 20)\n               (image-dimensions-for-pathname out)))))\n      \n      ;; Test case 2: first image larger than second  \n      (with-test-image (large-img :width 20 :height 20 :color \"blue\")\n        (with-test-image (small-img :width 10 :height 10 :color \"blue\")\n          (is-false (do-image-comparison \n                      (make-screenshot large-img) \n                      (make-screenshot small-img) \n                      out))\n          (is (equal\n               (list 20 20)\n               (image-dimensions-for-pathname out))))))))\n\n(test different-sized-images-should-not-be-identical--with-only-height-change\n  \"Regression test: images of different sizes should never be considered identical,\n   even if all overlapping pixels match\"\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname out :type \"png\")\n      ;; Test case 1: second image larger than first\n      (with-test-image (small-img :width 10 :height 10 :color \"red\")\n        (with-test-image (large-img :width 10 :height 20 :color \"red\")\n          (is-false (do-image-comparison \n                      (make-screenshot small-img) \n                      (make-screenshot large-img) \n                      out))\n          (is (equal\n               (list 10 20)\n               (image-dimensions-for-pathname out)))))\n      \n      ;; Test case 2: first image larger than second  \n      (with-test-image (large-img :width 10 :height 20 :color \"blue\")\n        (with-test-image (small-img :width 10 :height 10 :color \"blue\")\n          (is-false (do-image-comparison \n                      (make-screenshot large-img) \n                      (make-screenshot small-img) \n                      out))\n          (is (equal\n               (list 10 20)\n               (image-dimensions-for-pathname out))))))))\n\n(test remove-image-comparison\n  \"Test that we can create and remove image-comparison objects from the cache\"\n  (with-fixture stored-cache ()\n    (let ((*installation* (make-instance 'installation)))\n      (tmpdir:with-tmpdir (dir)\n        (with-test-store (:dir dir)\n          (let ((s1 (make-image :pathname im1))\n                (s2 (make-image :pathname im2))\n                (s3 (make-image :pathname im3)))\n            ;; Create two image comparisons\n            (let ((imc1 (make-image-comparison :before s1 :after s2 :result s3))\n                  (imc2 (make-image-comparison :before s2 :after s3 :result s1)))\n              ;; Verify both are in the cache\n              (is (eql 2 (fset:size *stored-cache*)))\n              ;; Remove one of them\n              (remove-image-comparison s1 s2)\n              ;; Verify only one remains\n              (is (eql 1 (fset:size *stored-cache*)))\n              ;; Verify the correct one remains\n              (is (eql imc2 (find-image-comparison-from-cache :before s2 :after s3)))\n              ;; Verify the removed one is gone\n              (is (eql nil (find-image-comparison-from-cache :before s1 :after s2))))))))))\n\n"
  },
  {
    "path": "src/screenshotbot/model/test-image.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/model/test-image\n  (:use #:cl\n        #:fiveam\n        #:screenshotbot/model/image)\n  (:import-from #:screenshotbot/model/image\n                #:invalid-image\n                #:delete-image\n                #:image-file-deleted\n                #:update-company-image-size\n                #:get-company-image-size\n                #:img-tmp-dir\n                #:%image-size\n                #:image-reuploaded-warning\n                #:no-image-uploaded-yet\n                #:base-image-comparer\n                #:all-soft-expired-images\n                #:soft-expiration-time\n                #:image-filesystem-pathname\n                #:image\n                #:local-image\n                #:image-blob\n                #:%perceptual-hash\n                #:image-blob-get\n                #:image=)\n  (:import-from #:util\n                #:oid)\n  (:import-from #:screenshotbot/model/image\n                #:%image-state\n                #:+image-state-filesystem+\n                #:with-local-image\n                #:update-image\n                #:local-location-for-oid\n                #:slow-image-comparison\n                #:image-format\n                #:dimension-width\n                #:dimension-height\n                #:dimension\n                #:image-dimensions\n                #:map-unequal-pixels-on-file\n                #:map-unequal-pixels-arr\n                #:%map-unequal-pixels\n                #:transient-mask-rect\n                #:map-unequal-pixels)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/object-id\n                #:make-oid\n                #:%make-oid\n                #:oid-array\n                #:oid-arr)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:with-wand\n                #:save-wand-to-file)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:export))\n\n(util/fiveam:def-suite)\n\n(defun %%image= (&rest args)\n  \"For all of these tests, we're only concerned with the version that\nuses the base-image-comparer.\"\n  (apply #'image=\n         (make-instance 'base-image-comparer)\n         args))\n\n(defun static-asset (file)\n  (path:catfile\n     #.(asdf:system-relative-pathname :screenshotbot\n                                      \"static/\")\n     file))\n\n\n(def-easy-macro with-base-fixture (&fn fn)\n  (let ((*installation* (make-instance 'installation)))\n    (funcall fn)))\n\n(def-fixture state (&key dir)\n  (with-base-fixture ()\n   (let ((file #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\")))\n     (with-test-store (:dir dir)\n       (let* ((img (make-image\n                    :pathname (static-asset \"assets/images/old-example-view-right.png\")))\n              (img2 (make-image\n                     :pathname (static-asset \"assets/images/old-example-view-left.png\")))\n              (img-copy (make-image\n                         :pathname (static-asset \"assets/images/old-example-view-right.png\")))\n              (rect (make-instance 'mask-rect :left 7 :top 10\n                                              :height 100 :width 104)))\n         (&body))))))\n\n(test image-comparison-is-cached ()\n  (with-fixture state ()\n    (let ((got-signal nil))\n     (handler-bind ((slow-image-comparison\n                      (lambda (e)\n                        (setf got-signal t))))\n       (is-true (%%image=\n                 img img2 (list rect)))\n       (is-true got-signal)))\n    (handler-bind ((slow-image-comparison\n                    (lambda (e)\n                      (fail \"Should not get slow-image-comparison\"))))\n      (is-true (%%image=\n                img img2 (list rect))))))\n\n(test image-comparison-is-cached-for-unequal ()\n  (with-fixture state ()\n    (let ((img (make-magick-test-image \"rose:\"))\n          (img2 (make-magick-test-image \"wizard:\")))\n      (let ((got-signal nil))\n        (handler-bind ((slow-image-comparison\n                         (lambda (e)\n                           (setf got-signal t))))\n          (is-false (%%image=\n                     img img2 (list rect)))\n          (is-true got-signal)))\n      (handler-bind ((slow-image-comparison\n                       (lambda (e)\n                         (fail \"Should not get slow-image-comparison\"))))\n        (is-false (%%image=\n                   img img2 (list rect)))))))\n\n\n(test simple-compare ()\n  (with-fixture state ()\n    (is-true (%%image=\n              img img nil))\n    (is-true (%%image= img img-copy nil))\n    (is-false (%%image= img img2 nil))\n    (is-true (%%image= img img2 (list rect)))))\n\n(defun make-magick-test-image (name)\n  (uiop:with-temporary-file (:pathname p :type \"webp\")\n    (with-wand (wand :file name)\n      (save-wand-to-file wand p))\n    (make-test-image p)))\n\n(defun make-test-image (pathname)\n  (let* ((image (make-image\n                 :pathname pathname)))\n    image))\n\n(test image-dimensions\n  (with-fixture state ()\n    (let* ((file #.(asdf:system-relative-pathname :screenshotbot \"dashboard/fixture/image.png\"))\n           (image (make-image :pathname file)))\n      (let ((dimension (image-dimensions image)))\n        (is (typep dimension 'dimension))\n        (is (eql 360 (dimension-height dimension)))\n        (is (eql 360 (dimension-width  dimension)))))))\n\n(test image-format\n  (with-fixture state ()\n    (is (eql :png (image-format img)))\n    (uiop:with-temporary-file (:pathname webp :type \"webp\")\n      (with-wand (wand :file \"rose:\")\n        (save-wand-to-file wand webp))\n      (let* ((image (make-image :pathname webp)))\n        (is (eql :webp (image-format image)))))))\n\n(test make-image-with-filename\n  (with-fixture state ()\n   (let ((image (make-image :pathname file)))\n     (is (eql +image-state-filesystem+ (%image-state image)))\n     (is (path:-e (image-filesystem-pathname image)))\n     (is (equalp #(145 184 144 188 213 44 215 112 157 4 202 64 212 94 93 133)\n                 (util/digests:md5-file (image-filesystem-pathname image)))))))\n\n(test local-location-for-oid--ensure-directory-is-writeable\n  (with-fixture state ()\n    (with-open-file (file (local-location-for-oid (mongoid:oid))\n                          :direction :output)\n      (write-string \"hello\" file))))\n\n(test update-image\n  (with-fixture state ()\n    (let ((hash #(145 184 144 188 213 44 215 112 157 4 202 64 212 94 93 133)))\n     (let ((image (make-image :hash hash)))\n       (update-image image :pathname file)\n       (with-local-image (file image)\n         (is (equalp hash\n                     (md5-file file))))))))\n\n(test overwriting-existing-image-warns\n  (with-fixture state ()\n    (let ((hash #(145 184 144 188 213 44 215 112 157 4 202 64 212 94 93 133)))\n      (let ((image (make-image :hash hash)))\n        (handler-case\n            (update-image image :pathname file)\n          (image-reuploaded-warning ()\n            (fail \"Did not expect to see image-reuploaded-warning\")))\n\n        (signals image-reuploaded-warning\n          (update-image image :pathname file))\n        \n        (with-local-image (file image)\n          (is (equalp hash\n                      (md5-file file))))))))\n\n(test find-image-by-oid\n  (with-fixture state ()\n    (let ((img (make-image :pathname file)))\n      (is (not (null (oid img))))\n      (is (eql img (find-image-by-oid (oid img))))\n      (is (eql 12 (length (oid-array img))))\n      (is (eql img (find-image-by-oid (oid-array img)))))))\n\n(test negative-width-for-mask-rect\n  (with-fixture state ()\n   (let ((mask-rect (make-instance 'mask-rect\n                                   :top 0\n                                   :left 10\n                                   :width -5\n                                   :height 5)))\n     (is (eql 0 (mask-rect-top mask-rect)))\n     (is (eql 5 (mask-rect-left mask-rect)))\n     (is (eql 5 (mask-rect-width mask-rect)))\n     (is (eql 5 (mask-rect-height mask-rect))))))\n\n(test negative-height-for-mask-rect\n  (with-fixture state ()\n   (let ((mask-rect (make-instance 'mask-rect\n                                   :top 0\n                                   :left 10\n                                   :width 5\n                                   :height -5)))\n     (is (eql -5 (mask-rect-top mask-rect)))\n     (is (eql 10 (mask-rect-left mask-rect)))\n     (is (eql 5 (mask-rect-width mask-rect)))\n     (is (eql 5 (mask-rect-height mask-rect))))))\n\n\n(test soft-expiration-time\n  ;; The date this oid was generated Jan 18th, 2023\n  (let ((today (local-time:parse-timestring \"2023-01-18T00:00:00Z\"))\n        (oid (make-oid :arr (mongoid:oid \"63c82a1bf6fe3c0c419f630a\"))))\n    (let ((expiry (soft-expiration-time oid)))\n      (is (local-time:timestamp<\n               expiry\n               (local-time:timestamp+ today 124 :day)))\n      (is (local-time:timestamp>\n           expiry\n           (local-time:timestamp+ today 58 :day))))))\n\n(test all-soft-expired-images-happy-path\n  (with-fixture state ()\n    (is-false (all-soft-expired-images))))\n\n(test no-image-uploaded-yet\n  (with-fixture state ()\n    (setf (%image-state img) nil)\n    (signals no-image-uploaded-yet\n      (with-local-image (file img)))))\n\n\n(test simple-image-snapshots\n  (tmpdir:with-tmpdir (dir)\n   (with-fixture state (:dir dir)\n     (make-image :pathname file)\n     (let ((other-image (make-image :pathname file)))\n       (slot-makunbound other-image 'company))\n     (finishes\n       (bknr.datastore:snapshot)))\n    (with-fixture state (:dir dir)\n      (pass))))\n\n(test store-images-size\n  (with-fixture state ()\n    (let ((image (make-image :pathname file)))\n      (is (eql 1085 (%image-size image))))))\n\n(test store-images-size-on-update\n  (with-fixture state ()\n    (let ((image (make-image :hash \"343431\")))\n      (update-image image :pathname file)\n      (is (eql 1085 (%image-size image))))))\n\n(test img-tmp-dir-exists\n  (with-fixture state ()\n    (path:-d (img-tmp-dir))))\n\n(test company-image-size\n  (with-fixture state ()\n    (let ((company (make-instance 'company))\n          (total-size 0))\n      (let ((image1 (make-image :pathname file :company company))\n            (image2 (make-image :pathname file :company company)))\n        (update-company-image-size)\n        (is (eql (* 2 1085) (get-company-image-size company)))))))\n\n(test company-image-size-when-the-slot-is-NIL\n  (with-fixture state ()\n    (let ((company (make-instance 'company))\n          (total-size 0))\n      (let ((image1 (make-image :pathname file :company company))\n            (image2 (make-image :pathname file :company company)))\n        (setf (%image-size image1) nil)\n        (update-company-image-size)\n        (is (eql 1085 (get-company-image-size company)))))))\n\n(test company-image-size-with-two-companies\n  (with-fixture state ()\n    (let ((company1 (make-instance 'company))\n          (company2 (make-instance 'company)))\n      (let ((image1 (make-image :pathname file :company company1))\n            (image2 (make-image :pathname file :company company1))\n            (image3 (make-image :pathname file :company company2)))\n        (update-company-image-size)\n        (is (eql (* 2 1085) (get-company-image-size company1)))\n        (is (eql 1085 (get-company-image-size company2)))))))\n\n(test company-image-size-with-no-images\n  (with-fixture state ()\n    (let ((company (make-instance 'company))\n          (total-size 0))\n      (update-company-image-size)\n      (is (eql 0 (get-company-image-size company))))))\n\n(test with-local-image-raises-image-file-deleted\n  (with-fixture state ()\n    (let ((image (make-image :pathname file)))\n      (let ((local-file (image-filesystem-pathname image)))\n        (is (path:-e local-file))\n        (delete-file local-file)\n        (signals image-file-deleted\n          (with-local-image (file image)\n            (fail \"Should not reach this point\")))))))\n\n(test delete-image-deletes-associated-file\n  (with-fixture state ()\n    (let ((image (make-image :pathname file)))\n      (let ((image-file (image-filesystem-pathname image)))\n        (is (path:-e image-file))\n        (delete-image image)\n        (is-false (path:-e image-file))))))\n\n(test delete-image-when-file-already-deleted\n  (with-fixture state ()\n    (let ((image (make-image :pathname file)))\n      (let ((image-file (image-filesystem-pathname image)))\n        (is (path:-e image-file))\n        (delete-file image-file)\n        (is-false (path:-e image-file))\n        (finishes\n          (delete-image image))))))\n\n(test invalid-image-dimensions\n  (with-fixture state ()\n    (let* ((filename #. (asdf:system-relative-pathname :screenshotbot \"fixture/invalid-image.png\"))\n           (image (make-image :pathname filename)))\n      (signals invalid-image\n        (image-dimensions image)))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-object.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/model/test-object\n  (:use #:cl #:alexandria)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:export #:test-object))\n(in-package :screenshotbot/model/test-object)\n\n(with-class-validation\n  (defclass test-object (store-object)\n    ()\n    (:metaclass persistent-class)))\n"
  },
  {
    "path": "src/screenshotbot/model/test-pr-rollout-rule.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-pr-rollout-rule\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/pr-rollout-rule\n                #:disable-pull-request-checks-p\n                #:whitelist-rule)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run))\n(in-package :screenshotbot/model/test-pr-rollout-rule)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((rule (make-instance 'whitelist-rule\n                               :emails '(\"foo@gmail.com\" \"bar@gmail.com\"))))\n      (&body))))\n\n\n(test basic-run-without-info-is-blacklisted\n  (with-fixture state ()\n    (is-true\n     (disable-pull-request-checks-p\n      rule (make-recorder-run :screenshots nil)))\n    (is-true\n     (disable-pull-request-checks-p\n      rule (make-recorder-run :screenshots nil\n                              :author \"arnold@example.com\"\n                              :work-branch \"foo-bar\")))))\n\n(test with-branch-name-contains-screenshotbot\n  (with-fixture state ()\n    (is-false\n     (disable-pull-request-checks-p\n      rule (make-recorder-run :screenshots nil\n                              :work-branch \"foo-screenshotbot-bar\")))))\n\n(test with-branch-has-proper-author\n  (with-fixture state ()\n    (is-false\n     (disable-pull-request-checks-p\n      rule (make-recorder-run :screenshots nil\n                              :author \"foo@gmail.com\"\n                              :work-branch \"foo-bar\")))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-recorder-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-recorder-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-company\n                #:tx-populate-run-id\n                #:recorder-run-id\n                #:find-run-by-run-id\n                #:recorder-run-uname\n                #:delete-old-unchanged-runs\n                #:%run-build-url\n                #:run-build-url\n                #:clean-up-old-shards\n                #:find-shards\n                #:shard\n                #:unchanged-run-other-commit\n                #:unchanged-run\n                #:runs-for-channel\n                #:delete-run\n                #:runs-for-company\n                #:recorder-run-author\n                #:%author\n                #:assert-no-loops\n                #:runs-for-tag\n                #:make-recorder-run\n                #:pull-request-id\n                #:transient-promotion-log\n                #:promotion-log\n                #:recorder-run\n                #:push-run-warning\n                #:recorder-run-warnings\n                #:not-fast-forward-promotion-warning\n                #:%run)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:equal-to\n                #:has-typep\n                #:assert-that)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:class-instances\n                #:with-transaction)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:has-item\n                #:contains)\n  (:import-from #:screenshotbot/user-api\n                #:user\n                #:recorder-previous-run\n                #:recorder-run-commit\n                #:channel)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/model/channel\n                #:production-run-for)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:company)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key)\n  (:import-from #:auth/viewer-context\n                #:api-viewer-context)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:core/api/model/api-key\n                #:cli-api-key)\n  (:import-from #:screenshotbot/model/constant-string\n                #:constant-string)\n  (:import-from #:util/store/slot-subsystem\n                #:externalized-slot-value)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/model/test-recorder-run)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key (api-key-roles :both))\n  (dolist (api-key-roles\n           (ecase api-key-roles\n             (:both\n              '(gk:enable gk:disable))\n             (:disable\n              '(gk:disable))\n             (:enable\n              '(gk:enable))))\n    (with-test-store ()\n      (gk:create :api-key-roles)\n      (funcall api-key-roles :api-key-roles)\n      (let* ((run (make-recorder-run))\n             (company (make-instance 'company))\n             (promotion-log (make-instance 'promotion-log)))\n        (&body)))))\n\n\n(test promotion-log-for-new\n  (with-fixture state ()\n    (assert-that (promotion-log run)\n                 (has-typep 'transient-promotion-log))\n    (is (pathnamep\n         (blob-pathname (promotion-log run))))))\n\n(test pull-request-id\n  (with-fixture state ()\n    (let ((run2 (make-recorder-run\n                 :pull-request \"https://foo/bar/20\")))\n      (is (eql nil (pull-request-id run)))\n      (is (eql 20 (pull-request-id run2))))))\n\n(test maintains-channel-runs\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run\n                 :commit-hash \"bleh2\"\n                 :channel channel\n                 :trunkp t)))\n      (assert-that (production-run-for channel :commit \"car\")\n                   (has-length 0))\n      (assert-that (fset:convert 'list (runs-for-channel channel))\n                   (contains run))\n      (assert-that (production-run-for channel :commit \"bleh2\")\n                   (is-equal-to run))\n      (bknr.datastore:delete-object run)\n      (assert-that (fset:convert 'list (runs-for-channel channel))\n                   (has-length 0))\n      (assert-that (production-run-for channel :commit \"bleh2\")\n                   (has-length 0)))))\n\n(test if-commit-is-not-present-try-override-commit\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run\n                 :company company\n                 :override-commit-hash \"bleh\")))\n      (is (equal \"bleh\" (recorder-run-commit run))))))\n\n(test if-both-commit-and-override-commit-is-not-present\n  \"Then we return nil without failing\"\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run (make-recorder-run\n                 :company company)))\n      (is (equal nil (recorder-run-commit run))))))\n\n(test finding-runs-by-tags\n  (with-fixture state ()\n    (let* ((company2 (make-instance 'company))\n           (run1 (make-recorder-run\n                     :company company\n                     :tags (list \"foo\" \"bar\")))\n           (run2 (make-recorder-run\n                  :company company\n                  :tags (list \"bar\")))\n           (run3 (make-recorder-run\n                  :company company))\n           (run4 (make-recorder-run\n                  :company company2\n                  :tags (list \"foo\"))))\n      (assert-that (runs-for-tag company \"foo\")\n                   (contains run1))\n      (assert-that (runs-for-tag company \"bar\")\n                   (has-item run1)\n                   (has-item run2)))))\n\n(test assert-no-loops\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :previous-run (make-recorder-run\n                               :previous-run (make-recorder-run\n                                              :previous-run nil)))))\n      (finishes (assert-no-loops run))\n      (setf (recorder-previous-run (recorder-previous-run (recorder-previous-run run)))\n            run)\n      (signals simple-error\n        (assert-no-loops run)))\n    (let ((run (make-recorder-run\n                :previous-run (make-recorder-run\n                               :previous-run nil))))\n      (finishes (assert-no-loops run)))))\n\n(test unbound-slot-for-author\n  (with-fixture state ()\n    (let ((run (make-recorder-run)))\n      (slot-makunbound run '%author)\n      (is (eql nil (recorder-run-author run))))))\n\n\n(test delete-run-happy-path\n  (with-fixture state ()\n    (let ((run (make-recorder-run :company company)))\n      (assert-that (fset:convert 'list (runs-for-company company))\n                   (contains run))\n      (delete-run run)\n      (assert-that (fset:convert 'list (runs-for-company company))\n                   (contains))\n      (finishes\n        (delete-run run)))))\n\n(test runs-for-company-happy-path\n  (with-fixture state ()\n    (is (fset:equal? (fset:empty-set)\n                     (runs-for-company company)))\n    (let ((run (make-recorder-run :company company)))\n      (is (fset:equal? (fset:with (fset:empty-set) run)\n                       (runs-for-company company))))))\n\n\n(test can-save-and-restore-unchanged-runs\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (make-instance 'unchanged-run\n                     :commit \"foo\"\n                     :other-commit \"bar\"\n                     :channel (find-or-create-channel (make-instance 'company) \"bleh\" ))\n      (bknr.datastore:snapshot))\n    (with-test-store (:dir dir)\n      (is (equal \"bar\"\n                 (unchanged-run-other-commit (car\n                                              (class-instances 'unchanged-run))))))))\n\n\n(test can-save-and-restore-recorder-runs\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (make-recorder-run\n       :commit-hash \"foo\"\n       :channel (find-or-create-channel (make-instance 'company) \"bleh\"))\n      (bknr.datastore:snapshot))\n    (with-test-store (:dir dir)\n      (is (equal \"foo\"\n                 (recorder-run-commit (car\n                                       (class-instances 'recorder-run))))))))\n\n(test api-viewer-context-can-only-see-runs-in-the-company\n  (with-fixture state ()\n    (let* ((other-company (make-instance 'company))\n           (user (make-instance 'user))\n           (api-key (make-instance 'api-key\n                                   :user user\n                                   :permissions '(:full)\n                                   :company other-company))\n           (other-channel (make-instance 'channel\n                                         :company other-company))\n           (other-run (make-recorder-run\n                       :channel other-channel\n                       :company other-company))\n           (channel (make-instance 'channel\n                                         :company company))\n           (run (make-recorder-run\n                       :channel channel\n                       :company company)))\n      (roles:ensure-has-role company user 'roles:standard-member)\n      (roles:ensure-has-role other-company user 'roles:standard-member)\n      (assert-that\n       (auth:can-viewer-view\n        (make-instance\n         'api-viewer-context\n         :user user\n         :api-key (make-instance 'api-key\n                                 :user user\n                                 :permissions '(:full)\n                                 :company other-company))\n        other-run)\n       (described-as \"Can view runs in the same company\"\n         (is-equal-to t)))\n      (assert-that\n       (auth:can-viewer-view\n        (make-instance\n         'api-viewer-context\n         :user user\n         :api-key api-key)\n        run)\n       (described-as \"Cannot view runs in the other company\"\n         (is-equal-to nil))))))\n\n(def-fixture can-viewer-view-fixture (&key (permissions '(:ci)))\n  (let* ((channel (make-instance 'channel :company company))\n           (user (make-instance 'user))\n           (run (make-recorder-run\n                 :channel channel\n                 :company company))\n           (api-key (make-instance 'api-key\n                                   :permissions permissions\n                                   :user user\n                                   :company company)))\n      (roles:ensure-has-role company user 'roles:standard-member)\n    (&body)))\n\n(test api-viewer-context-cant-view-run-if-only-ci-permissions\n  (with-fixture state (:api-key-roles :enable)\n    (with-fixture can-viewer-view-fixture ()\n      (assert-that\n       (auth:can-viewer-view\n        (make-instance\n         'api-viewer-context\n         :user user\n         :api-key api-key)\n        run)\n       (is-equal-to nil)))))\n\n(test api-viewer-context-can-view-if-full-permissions\n  (with-fixture state (:api-key-roles :enable)\n    (with-fixture can-viewer-view-fixture (:permissions '(:full))\n      (assert-that\n       (auth:can-viewer-view\n        (make-instance\n         'api-viewer-context\n         :user user\n         :api-key api-key)\n        run)\n       (is-equal-to t)))))\n\n\n(test api-viewer-context-can-view-if-gk-is-disabled\n  (with-fixture state (:api-key-roles :disable)\n    (with-fixture can-viewer-view-fixture ()\n      (assert-that\n       (auth:can-viewer-view\n        (make-instance\n         'api-viewer-context\n         :user user\n         :api-key api-key)\n        run)\n       (is-equal-to t)))))\n\n(test api-viewer-context-can-view-runs-from-cli-api-key\n  (with-fixture state (:api-key-roles :enable)\n    (with-fixture can-viewer-view-fixture ()\n      (let ((api-key (make-instance 'cli-api-key\n                                    :user user\n                                    :company company)))\n        (assert-that\n         (auth:can-viewer-view\n          (make-instance\n           'api-viewer-context\n           :user user\n           :api-key api-key)\n          run)\n         (is-equal-to t))))))\n\n(test lookup-shards\n  (with-fixture state ()\n    (let* ((company-2 (make-instance 'company))\n           (channel (make-instance 'channel\n                                   :company company))\n           (channel-2 (make-instance 'channel\n                                     :company company-2)))\n      (make-instance 'shard\n                     :channel channel-2\n                     :key \"foo\")\n      (make-instance 'shard\n                     :channel channel\n                     :key \"foo\")\n      (make-instance 'shard\n                     :channel channel\n                     :key \"foo\")\n      (make-instance 'shard\n                     :channel channel                     \n                     :key \"bar\")\n      (assert-that (find-shards channel \"foo\")\n                   (has-length 2)))))\n\n(test clean-up-old-shards\n  (with-fixture state ()\n   (let ((shard1 (make-instance 'shard\n                                :ts 100))\n         (shard2 (make-instance 'shard)))\n     (clean-up-old-shards)\n     (assert-that (class-instances 'shard)\n                  (contains shard2)))))\n\n\n(test build-url-for-recorder-run\n  (with-fixture state ()\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel\n                                   :company company))\n           (run (make-recorder-run\n                 :channel channel\n                 :screenshots nil\n                 :build-url \"foobar\")))\n      (is (equal \"foobar\" (run-build-url run)))\n      (is (typep (%run-build-url run) 'constant-string)))))\n\n(test delete-old-unchanged-runs\n  (with-fixture state ()\n    (let* ((now 1000000000)\n           (one (make-instance 'unchanged-run :created-at 1000000))\n           (two (make-instance 'unchanged-run :created-at 2000000))\n           (three (make-instance 'unchanged-run :created-at 999999000))\n           (four (make-instance 'unchanged-run :created-at 999999999)))\n      (delete-old-unchanged-runs :now now)\n      (assert-that (bknr.datastore:class-instances 'unchanged-run)\n                   (contains-in-any-order\n                    three four)))))\n\n(test uname\n  (with-fixture state ()\n    (let ((one (make-recorder-run\n                :metadata `((\"foo\" . \"bar\")\n                            (\"uname\" . \"carbar\")))))\n      (is (equal \"carbar\"\n                 (recorder-run-uname one))))))\n\n(test push-run-warning-sets-run-slot\n  (with-fixture state ()\n    (push-run-warning run 'not-fast-forward-promotion-warning)\n    (let ((warning (first (recorder-run-warnings run))))\n      (is (not (null warning)))\n      (is (eq run (slot-value warning '%run))))))\n\n(test find-run-by-run-id\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :company company)))\n      (is (eql run (find-run-by-run-id\n                    company\n                    1 #| since this is the first run |#))))))\n\n(test tx-populate-run-id\n  (with-fixture state ()\n    (let ((run1 (make-recorder-run\n                 :company company))\n          (run2 (make-recorder-run\n                 :run-id 22\n                 :company company))\n          (run3 (make-recorder-run)))\n      (tx-populate-run-id (list run1 run2 run3))\n      (is (eql 1 (recorder-run-id run1))))))\n\n\n(test commit-map-serialized-index\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run1 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"abcd\"\n                  :screenshots nil)))\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((\"abcd\" . ,(fset:convert 'fset:set (list run1)))))\n           (externalized-slot-value channel '%r::commit-map))))))\n\n(test commit-map-serialized-index-multiple-runs\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run1 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"abcd\"\n                  :screenshots nil))\n           (run2 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"carbar\"\n                  :screenshots nil)))\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((\"abcd\" . ,(fset:convert 'fset:set (list run1)))\n                           (\"carbar\" . ,(fset:convert 'fset:set (list run2)))))\n           (externalized-slot-value channel '%r::commit-map))))))\n\n(test commit-map-serialized-index-always-picks-everything\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run1 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"abcd\"\n                  :screenshots nil))\n           (run2 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"abcd\"\n                  :screenshots nil)))\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((\"abcd\" . ,(fset:convert 'fset:set (list run1 run2)))))\n           (externalized-slot-value channel '%r::commit-map))))))\n\n(test delete-run-removes-from-commit-map\n  (with-fixture state ()\n    (let* ((channel (make-instance 'channel))\n           (run1 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"abcd\"\n                  :screenshots nil))\n           (run2 (make-recorder-run\n                  :channel channel\n                  :commit-hash \"abcd\"\n                  :screenshots nil)))\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((\"abcd\" . ,(fset:convert 'fset:set (list run1 run2)))))\n           (externalized-slot-value channel '%r::commit-map)))\n      (bknr.datastore:delete-object run2)\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((\"abcd\" . ,(fset:convert 'fset:set (list run1)))))\n           (externalized-slot-value channel '%r::commit-map))))))\n\n(test maintains-run-id-map\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :company company\n                  :screenshots nil)))\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((1 . ,run1)))\n           (externalized-slot-value company '%r::run-id-map))))))\n\n(test maintains-run-id-map-for-two-runs\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :company company\n                  :screenshots nil))\n           (run2 (make-recorder-run\n                  :company company\n                  :screenshots nil))\n           (run3 (make-recorder-run\n                  :company (make-instance 'company)\n                  :screenshots nil)))\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((1 . ,run1)\n                           (2 . ,run2)))\n           (externalized-slot-value company '%r::run-id-map))))))\n\n(test deletes-from-run-id-map\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :company company\n                  :screenshots nil))\n           (run2 (make-recorder-run\n                  :company company\n                  :screenshots nil))\n           (run3 (make-recorder-run\n                  :company (make-instance 'company)\n                  :screenshots nil)))\n      (delete-object run1)\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((2 . ,run2)))\n           (externalized-slot-value company '%r::run-id-map))))))\n\n(test deletes-from-run-id-map-when-company-is-set-to-NIL\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :company company\n                  :screenshots nil))\n           (run2 (make-recorder-run\n                  :company company\n                  :screenshots nil))\n           (run3 (make-recorder-run\n                  :company (make-instance 'company)\n                  :screenshots nil)))\n      (setf (recorder-run-company run1) nil)\n      (is (fset:equal?\n           (fset:convert 'fset:map\n                         `((2 . ,run2)))\n           (externalized-slot-value company '%r::run-id-map))))))\n\n"
  },
  {
    "path": "src/screenshotbot/model/test-report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-report\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/report\n                #:base-acceptable\n                #:report-to-dto\n                #:company-promotion-reports\n                #:%report-company)\n  (:import-from #:screenshotbot/api/model\n                #:encode-json)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:screenshotbot/user-api\n                #:channel\n                #:can-view)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/model/test-report)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let* ((company (make-instance 'company))\n           (run (make-instance 'recorder-run\n                               :company company\n                               :screenshot-map nil)))\n      (&body))))\n\n(test simple-creation ()\n  (with-fixture state ()\n    (finishes\n      (make-instance 'report :acceptable nil))))\n\n(test crashes-on-bad-args ()\n  (with-fixture state ()\n    (signals #+lispworks conditions:unknown-keyword-error\n      #-lispworks error\n      (make-instance 'report :does-not-exist-arg t))))\n\n(test sets-company ()\n  (with-fixture state ()\n    (let ((report (make-instance 'report :run run)))\n      (is (eql (%report-company report)\n               company)))))\n\n(test company-promoted-index\n  (with-fixture state ()\n    (let ((report-1 (make-instance 'report :run run\n                                           :promotion-report-p t))\n          (report-2 (make-instance 'report :run run)))\n      (is\n       (fset:equal?\n        (fset:with (fset:empty-set) report-1)\n        (company-promotion-reports company))))))\n\n(test dto-is-serializable\n  (with-fixture state ()\n    (let ((report (make-instance 'report\n                                 :run run\n                                 :previous-run run)))\n      (finishes\n        (encode-json (report-to-dto report))))))\n\n(defclass multi-install (multi-org-feature\n                         installation)\n  ())\n\n(test can-view-on-report\n  (with-fixture state ()\n    (with-installation (:installation (make-instance 'multi-install))\n     (with-test-user (:user user :company company)\n       (let* ((channel (make-instance 'channel :company company))\n              (run1 (make-recorder-run :company company\n                                       :channel channel))\n              (run2 (make-recorder-run :company company\n                                       :channel channel))\n              (report (make-instance 'report\n                                     :run run1\n                                     :previous-run run2)))\n         (assert-that (roles:companies-for-user user)\n                      (has-item company))\n         (is-true user)\n         (is-true (auth:can-viewer-view\n                   (make-instance 'normal-viewer-context\n                                  :user user)\n                   report)))))))\n\n(test can-view-on-report-with-nil-previous\n  (with-fixture state ()\n    (with-installation (:installation (make-instance 'multi-install))\n     (with-test-user (:user user :company company)\n       (let* ((channel (make-instance 'channel :company company))\n              (run1 (make-recorder-run :company company\n                                       :channel channel))\n              (run2 nil)\n              (report (make-instance 'report\n                                     :run run1\n                                     :previous-run run2)))\n         (assert-that (roles:companies-for-user user)\n                      (has-item company))\n         (is-true user)\n         (is-true (auth:can-viewer-view\n                   (make-instance 'normal-viewer-context\n                                  :user user)\n                   report)))))))\n\n\n(test review-state-encoding-without-acceptable\n  (with-fixture state ()\n    (let ((report (make-instance 'report :acceptable nil)))\n      (is (equal \"na\" (dto:report-acceptable-state\n                       (report-to-dto report)))))))\n\n(test review-state-encoding-with-acceptable\n  (with-fixture state ()\n    (let* ((acceptable (make-instance 'base-acceptable))\n           (report (make-instance 'report :acceptable acceptable)))\n      (is (equal \"none\" (dto:report-acceptable-state\n                         (report-to-dto report)))))))\n\n(test review-state-encoding-with-acceptable\n  (with-fixture state ()\n    (let* ((acceptable (make-instance 'base-acceptable\n                                      :state :accepted))\n           (report (make-instance 'report :acceptable acceptable)))\n      (is (equal \"accepted\" (dto:report-acceptable-state\n                         (report-to-dto report)))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-review-policy.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-review-policy\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:with-mocks)\n  (:import-from #:screenshotbot/model/channel\n                #:review-policy)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/review-policy\n                #:parse-email\n                #:can-review?\n                #:disallow-author-review-policy)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run))\n(in-package :screenshotbot/model/test-review-policy)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n   (with-mocks ()\n     (with-test-store ()\n       (let ((disallow-author (make-instance 'disallow-author-review-policy))\n             (user (make-user :email \"foo@example.com\")))\n         (&body))))))\n\n(test simple-can-review\n  (with-fixture state ()\n    (is-true\n     (can-review? disallow-author\n                  (make-recorder-run :author nil)\n                  user))\n    (is-true\n     (can-review? disallow-author\n                  (make-recorder-run :author \"bar@example.com\")\n                  user))\n    (is-false\n     (can-review? disallow-author\n                  (make-recorder-run :author \"foo@example.com\")\n                  user))))\n\n(test parses-out-email\n  (with-fixture state ()\n    (is-false\n     (can-review? disallow-author\n                  (make-recorder-run :author \"Foo Bar <foo@example.com>\")\n                  user))))\n\n(test parse-email\n  (with-fixture state ()\n    (is (equal \"foo@example.com\" (parse-email \"Foo bar <foo@example.com>\")))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-run-commit-lookup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-run-commit-lookup\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:screenshotbot/model/run-commit-lookup\n                #:*cache*\n                #:find-runs-by-commit)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:contains)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:channel))\n(in-package :screenshotbot/model/test-run-commit-lookup)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (clrhash *cache*)\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel\n                                   :github-repo \"foo\"))\n           (run1 (make-recorder-run\n                  :screenshots nil\n                  :channel channel\n                  :commit-hash \"abcd1234\"))\n           (run2 (make-recorder-run\n                  :screenshots nil\n                  :channel channel\n                  :commit-hash \"ab1234ab\")))\n      (&body))))\n\n(test simple-lookup\n  (with-fixture state ()\n    (assert-that (find-runs-by-commit \"abcd\" :company :all)\n                 (contains run1))\n    (assert-that (find-runs-by-commit \"ab\" :company :all)\n                 (contains-in-any-order run1 run2))\n    (assert-that (find-runs-by-commit \"abef\" :company :all)\n                 (contains))))\n\n(test caching\n  (with-fixture state ()\n    (let ((key \"abcd\"))\n      (is (eql\n           (find-runs-by-commit key :company :all)\n           (find-runs-by-commit key :company :all))))))\n\n(test lookup-by-company-being-nil\n  (with-fixture state ()\n    (assert-that (find-runs-by-commit \"abcd\" :company nil\n                                             :repo \"foo\")\n                 (contains))))\n\n(test lookup-by-specific-company\n  (with-fixture state ()\n    (let ((run3 (make-recorder-run\n                 :screenshots nil\n                 :company company\n                 :channel channel\n                 :commit-hash \"abcd1234\")))\n      (assert-that (find-runs-by-commit \"abcd\" :company company :repo \"foo\")\n                   (contains run3))\n      (assert-that (find-runs-by-commit \"abcd\" :company :all)\n                   (contains-in-any-order run1 run3)))))\n\n(test filters-by-right-channel\n  (with-fixture state ()\n    (let* ((channel-2 (make-instance 'channel\n                                     :github-repo \"bar\"))\n           (run3 (make-recorder-run\n                  :screenshots nil\n                  :company company\n                  :channel channel\n                  :commit-hash \"abcd1234\"))\n           (run4 (make-recorder-run\n                  :screenshots nil\n                  :company company\n                  :channel channel-2\n                  :commit-hash \"abcd1234\")))\n      (assert-that (find-runs-by-commit \"abcd\" :company company :repo \"foo\")\n                   (contains run3))\n      (assert-that (find-runs-by-commit \"abcd\" :company company :repo \"bar\")\n                   (contains run4)))))\n\n"
  },
  {
    "path": "src/screenshotbot/model/test-screenshot-key.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-screenshot-key\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/screenshot\n                #:make-screenshot\n                #:make-key-from-screenshot\n                #:make-screenshot-from-key)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:ensure-screenshot-key\n                #:screenshot-key)\n  (:import-from #:screenshotbot/user-api\n                #:screenshot-name)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/screenshot-api\n                #:screenshot-image))\n(in-package :screenshotbot/model/test-screenshot-key)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test simple-conversion\n  (with-fixture state ()\n    (let ((screenshot (make-screenshot :name \"foobar2\")))\n      (is (eql\n           (make-key-from-screenshot screenshot)\n           (make-key-from-screenshot screenshot)))\n      (is (equal \"foobar2\"\n                 (screenshot-name\n                  (make-key-from-screenshot screenshot))))\n      (assert-that (make-key-from-screenshot screenshot)\n                   (has-typep 'screenshot-key)))))\n\n(test reverse-conversion\n  (with-fixture state ()\n    (let ((key (ensure-screenshot-key :name \"bleh\")))\n      (is (eql key (ensure-screenshot-key :name \"bleh\")))\n      (is (not (eql key (ensure-screenshot-key :name \"bleh\"\n                                               :lang \"blah\"))))\n      (let ((screenshot (make-screenshot-from-key key :image)))\n        (is (equal \"bleh\" (screenshot-name screenshot)))\n        (is (eql :image (screenshot-image screenshot)))))))\n\n(defun to-alist (map)\n  (let ((ret))\n   (fset:do-map (key value map (reverse ret))\n     (push (cons key value) ret))))\n\n(test can-make-map\n  (with-fixture state ()\n    (let ((s1 (ensure-screenshot-key :name \"bleh\"))\n          (s2 (ensure-screenshot-key :name \"car\")))\n      (is (eql :less\n               (fset:compare s1 s2)))\n      (let ((map\n              (fset:with\n               (fset:with\n                (fset:empty-map)\n                s1 \"foo\")\n               s2 \"bar\")))\n\n        (is (equal \"foo\" (fset:lookup map s1)))\n        (is (equal \"bar\" (fset:lookup map s2)))\n        (is (equal (list\n                    (cons s1 \"foo\")\n                    (cons s2 \"bar\"))\n                   (to-alist map)))))))\n\n\n(test can-make-with-equivalent-keys\n  (with-fixture state ()\n    (let ((s1 (ensure-screenshot-key :name \"bleh\"))\n          (s2 (ensure-screenshot-key :name \"bleh\")))\n      (is (eql :equal\n               (fset:compare s1 s2)))\n      (let ((map\n              (fset:with\n               (fset:with\n                (fset:empty-map)\n                s1 \"foo\")\n               s2 \"bar\")))\n\n        (is (equal \"bar\" (fset:lookup map s1)))\n        (is (equal \"bar\" (fset:lookup map s2)))\n        (is (equal (list\n                    (cons s2 \"bar\"))\n                   (to-alist map)))))))\n\n\n(test large-set\n  (with-fixture state ()\n    (let* ((rstate (make-random-state) #|for determinism|#)\n           (map (fset:empty-map))\n           (keys (loop for i from 0 to 1000\n                       collect\n                       (ensure-screenshot-key :name\n                                              (format nil \"~a\"\n                                                      (random 1000000000\n                                                              rstate))))))\n\n      (loop for key in keys\n            do (setf map (fset:with map key \"foo\")))\n      (is\n       (equal\n        (mapcar #'car (to-alist map))\n        (sort (copy-list keys) #'string< :key #'screenshot-name))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-screenshot-map.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-screenshot-map\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/screenshot\n                #:screenshot\n                #:lite-screenshot)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:screenshot-key)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/model/screenshot-map\n                #:memoized-reduce\n                #:screenshot-map\n                #:to-list\n                #:previous\n                #:chain-cost\n                #:compute-cost\n                #:*lookback-count*\n                #:pick-best-existing-map\n                #:screenshots\n                #:make-from-previous\n                #:deleted\n                #:to-map\n                #:screenshot-map-to-list\n                #:make-screenshot-map)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that\n                #:matchesp\n                #:matcher)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:ensure-screenshot-key\n                #:screenshot-key)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/screenshot-api\n                #:screenshot-image\n                #:make-screenshot)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:fiveam-matchers/satisfying\n                #:satisfying)\n  (:import-from #:fiveam-matchers/misc\n                #:is-null)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:deftransaction)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :screenshotbot/model/test-screenshot-map)\n\n\n(util/fiveam:def-suite)\n\n(defun image-file (name)\n  (path:catfile\n   #.(asdf:system-relative-pathname\n      :screenshotbot\n      \"fixture/\")\n   name))\n\n(def-fixture state (&key dir)\n  (with-installation ()\n   (with-test-store (:dir dir)\n     (let* ((channel (make-instance 'channel))\n            (im-1 (make-image :pathname (image-file \"wizard.png\")))\n            (im-2 (make-image :pathname (image-file \"rose.png\")))\n            (screenshot-1 (make-screenshot :image im-1 :name \"one\"))\n            (screenshot-2 (make-screenshot :image im-2 :name \"two\"))\n            (screenshot-3 (make-screenshot :image im-2 :name \"three\"))\n            (screenshot-4 (make-screenshot :image im-2 :name \"four\"))\n            (screenshot-5 (make-screenshot :image im-2 :name \"five\"))\n            (screenshot-key-1-copy\n              (make-instance 'screenshot-key :name \"one\"))\n            (screenshot-key-2-copy\n              (make-instance 'screenshot-key :name \"two\"))\n            (screenshot-key-3\n              (make-instance 'screenshot-key :name \"three\")))\n       (&body)))))\n\n(defun screenshot= (s1 s2)\n  (and\n   (eql :equal\n        (fset:compare (screenshot-key s1)\n                      (screenshot-key s2)))\n   (eql (screenshot-image s1)\n        (screenshot-image s2))))\n\n(test preconditions ()\n  (with-fixture state ()\n    (pass)))\n\n(defclass has-screenshot-matcher (matcher)\n  ((key :initarg :key\n        :reader matcher-key)))\n\n(defun has-screenshot (screenshot)\n  (make-instance 'has-screenshot-matcher\n                 :key (screenshot-key screenshot)))\n\n(defmethod matchesp ((self has-screenshot-matcher)\n                     value)\n  (eql :equal (fset:compare (matcher-key self)\n                            (screenshot-key value))))\n\n(test make-a-simple-list\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2))))\n      (assert-that (screenshot-map-to-list m1)\n                   (contains\n                    (has-screenshot screenshot-1)\n                    (has-screenshot screenshot-2))))))\n\n(test same-list-twice-in-a-row-gives-same-value\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2)))\n          (m2 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2))))\n      (is (eql m1 m2)))))\n\n(test second-time-is-a-different-set\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2)))\n          (m2 (make-screenshot-map\n               channel\n               (list screenshot-1))))\n      (is (not (eql m1 m2))))))\n\n(test image-matter-too\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2)))\n          (m2 (make-screenshot-map\n               channel\n               (list (make-screenshot :image im-2 :name \"one\")\n                     (make-screenshot :image im-1 :name \"two\")))))\n      (is (not (eql m1 m2))))))\n\n(test recreated-screenshot\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2)))\n          (m2 (make-screenshot-map\n               channel\n               (list (make-screenshot :image im-1 :name \"one\")\n                     (make-screenshot :image im-2 :name \"two\")))))\n      (is (eql m1 m2)))))\n\n(test to-map-is-cached\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2))))\n      (is\n       (eql (to-map m1)\n            (to-map m1))))))\n\n\n(defun set-to-list (set &optional res)\n  (cond\n    ((fset:empty? set)\n     res)\n    (t\n     (let ((least (fset:least set)))\n       (set-to-list\n        (fset:less set least)\n        (list*\n         least\n         res))))))\n\n(test recreated-screenshot-with-uneql-objects\n  (with-fixture state ()\n    (let ((m1 (make-screenshot-map\n               channel\n               (list screenshot-1\n                     screenshot-2)))\n          (m2 (make-screenshot-map\n               channel\n               (list (make-instance 'lite-screenshot\n                                    :screenshot-key screenshot-key-1-copy\n                                    :image-oid (oid im-1 :stringp nil))\n                     (make-instance 'lite-screenshot\n                                    :screenshot-key screenshot-key-2-copy\n                                    :image-oid (oid im-2 :stringp nil))\n                     (make-instance 'lite-screenshot\n                                    :screenshot-key screenshot-key-3\n                                    :image-oid (oid im-2 :stringp nil))))))\n      (is (not (eql m2 m1)))\n      (let ((map-2 (to-map m2))\n            (map-1 (to-map m1)))\n        (is (fset:equal?\n             map-1\n             (fset:map-intersection map-2 map-1)))\n        (let ((diff (fset:map-difference-2 map-2 map-1)))\n          (is (eql 1 (fset:size diff))))\n\n        ;; All of the above tests should go through even if we don't use\n        ;; the same map core. So now, we ensure that we're actually\n        ;; using the same core. The two maps have different\n        ;; screenshot-keys, so we want to make sure the new map has the\n        ;; same screenshot-key's as the first.\n        (let ((keys-1 (set-to-list (fset:domain map-1)))\n              (keys-2 (set-to-list (fset:domain map-2))))\n\n          ;; the sorting order is two, three, one (reverse sorted)\n          (is (eql screenshot-key-3 (elt keys-2 1)))\n\n          (assert-that keys-1\n                       (contains\n                        (screenshot-key screenshot-2)\n                        (screenshot-key screenshot-1)))\n          (assert-that keys-2\n                       (contains\n                        (screenshot-key screenshot-2)\n                        screenshot-key-3\n                        (screenshot-key screenshot-1))))))))\n\n\n(test recreated-screenshot-with-deleted-uneql-objects\n  (with-fixture state ()\n    (let* ((screenshot-3 (make-screenshot\n                          :key (ensure-screenshot-key :name \"foobar\")\n                          :image im-1))\n           (m1 (make-screenshot-map\n                channel\n                (list screenshot-1\n                      screenshot-2\n                      screenshot-3\n                      screenshot-4\n                      screenshot-5)))\n           (m2 (make-screenshot-map\n                channel\n                (list\n                 screenshot-4\n                 screenshot-5\n                 (make-instance 'lite-screenshot\n                                     :screenshot-key screenshot-key-1-copy\n                                     :image-oid (oid im-1 :stringp nil))))))\n      (is (not (eql m2 m1)))\n      (let ((map-2 (to-map m2))\n            (map-1 (to-map m1)))\n        (is (fset:equal?\n             map-2\n             (fset:map-intersection map-2 map-1)))\n        (let ((diff (fset:map-difference-2 map-2 map-1)))\n          (is (eql 0 (fset:size diff))))\n        (let ((diff (fset:map-difference-2 map-1 map-2)))\n          (is (eql 2 (fset:size diff))))\n\n        ;; All of the above tests should go through even if we don't use\n        ;; the same map core. So now, we ensure that we're actually\n        ;; using the same core. The two maps have different\n        ;; screenshot-keys, so we want to make sure the new map has the\n        ;; same screenshot-key's as the first.\n        (let ((keys-1 (set-to-list (fset:domain map-1)))\n              (keys-2 (set-to-list (fset:domain map-2))))\n\n          (assert-that (deleted m2)\n                       (described-as\n                           \"We expect the deleted slot to be set\"\n                         (contains\n                          (screenshot-key screenshot-2)\n                          (screenshot-key screenshot-3))))\n\n          (assert-that keys-1\n                       (contains\n                        (screenshot-key screenshot-2)\n                        (screenshot-key screenshot-1)\n                        (screenshot-key screenshot-4)\n                        (screenshot-key screenshot-3)\n                        (screenshot-key screenshot-5)))\n          (assert-that keys-2\n                       (contains\n                        (screenshot-key screenshot-1)\n                        (screenshot-key screenshot-4)\n                        (screenshot-key screenshot-5))))))))\n\n(test make-from-previous\n  (with-fixture state ()\n    (let* ((m1 (make-from-previous (list screenshot-1)\n                                   nil channel))\n           (m2 (make-from-previous (list screenshot-1 screenshot-2)\n                                   m1 channel)))\n      (assert-that (screenshots m1)\n                   (contains\n                    (satisfying (screenshot= screenshot-1 *))))\n      (assert-that (screenshots m2)\n                   (contains\n                    (satisfying (screenshot= screenshot-2 *)))))))\n\n(test make-from-previous-with-deleted-items\n  (with-fixture state ()\n    (let* ((m1 (make-from-previous (list screenshot-1)\n                                   nil channel))\n           (m2 (make-from-previous (list screenshot-2)\n                                   m1 channel)))\n      (assert-that (screenshots m1)\n                   (contains\n                    (satisfying (screenshot= screenshot-1 *))))\n      (assert-that (screenshots m2)\n                   (contains\n                    (satisfying (screenshot= screenshot-2 *))))\n      (assert-that (deleted m2)\n                   (contains\n                    (is-equal-to\n                     (screenshot-key screenshot-1)))))))\n\n(test pick-best-existing-map\n  (with-fixture state ()\n    (let* ((m1 (make-from-previous (list screenshot-3 screenshot-4 screenshot-1) nil channel))\n           (m2 (make-from-previous (list screenshot-3 screenshot-4 screenshot-2) nil channel)))\n      (assert-that (pick-best-existing-map channel\n                                           (list screenshot-3 screenshot-4 screenshot-1))\n                   (is-equal-to\n                    m1))\n      (assert-that (pick-best-existing-map channel\n                                           (list screenshot-3 screenshot-4 screenshot-2))\n                   (is-equal-to\n                    m2))\n      (assert-that\n       (let ((*lookback-count* 1))\n         (pick-best-existing-map channel\n                                 (list screenshot-3 screenshot-4 screenshot-1)))\n       (is-equal-to\n        m2)))))\n\n(test compute-cost\n  (with-fixture state ()\n    (let* ((one (fset:with\n                 (fset:empty-map)\n                 (ensure-screenshot-key :name \"foo\")\n                 :one))\n           (two (fset:with\n                 (fset:empty-map)\n                 (ensure-screenshot-key :name \"bar\")\n                 :two))\n           (three (fset:with\n                   (fset:empty-map)\n                   (ensure-screenshot-key :name \"foo\")\n                   :two))\n           (four (fset:with\n                  three\n                  (ensure-screenshot-key :name \"bar\")\n                  :two)))\n      (is (eql 2 (compute-cost one two)))\n      (is (eql 1 (compute-cost one three)))\n      (is (eql 2 (compute-cost two one)))\n      (is (eql 1 (compute-cost three one)))\n      (is (eql 2 (compute-cost one four)))\n      (is (eql 2 (compute-cost four one)))\n      (is (eql 1 (compute-cost two four)))\n      (is (eql 1 (compute-cost four two))))))\n\n(def-fixture chain-costs ()\n  (let* ((one (make-from-previous (list screenshot-1 screenshot-2)\n                                    nil channel))\n           (two (make-from-previous (list screenshot-1)\n                                    one channel))\n           (three (make-from-previous (list screenshot-2)\n                                      two channel)))\n      (&body)))\n\n(test chain-cost\n  (with-fixture state ()\n    (with-fixture chain-costs ()\n      (is (eql 0 (chain-cost nil)))\n      (is (eql 2 (chain-cost one)))\n      (is (eql 3 (chain-cost two)))\n      (is (eql 5 (chain-cost three))))))\n\n(test chain-cost-uncached\n  (with-fixture state ()\n    (with-fixture chain-costs ()\n      (is (eql 5 (chain-cost three)))\n      (is (eql 2 (slot-value one 'chain-cost)))\n      (is (eql 3 (slot-value two 'chain-cost)))\n      (is (eql 5 (slot-value three 'chain-cost))))))\n\n(test non-lite-screenshots-are-supported\n  \"this is just a convenience to make migrations easy, if we need to\ndelete this test in the future, it might be okay.\"\n  (with-fixture state ()\n    (let* ((screenshot-1-old-style (make-instance 'screenshot\n                                                  :name \"one\"\n                                                  :image im-1))\n           (one (make-screenshot-map channel\n                                     (list\n                                      screenshot-1-old-style)))\n           (two (make-screenshot-map channel\n                                     (list\n                                      screenshot-1))))\n      (is (eql :equal (fset:compare (screenshot-key screenshot-1-old-style)\n                                    (screenshot-key screenshot-1))))\n      (is (eql one two)))))\n\n(test we-dont-use-a-large-cost-as-parent\n  (with-fixture state ()\n    (let ((one (make-screenshot-map channel\n                                    (list screenshot-1\n                                          screenshot-2))))\n      (setf (slot-value one 'chain-cost) 100000000000)\n      (let ((two (make-screenshot-map channel\n                                      (list screenshot-1))))\n        (is (null (previous two)))))))\n\n(test if-the-cost-is-too-much-dont-use-parent\n  (with-fixture state ()\n    (let ((one (make-screenshot-map channel\n                                    (list screenshot-1))))\n      (let ((two (make-screenshot-map channel\n                                      (list screenshot-2))))\n        (is (null (previous two)))))))\n\n(test to-list\n  (with-fixture state ()\n    (let ((map (make-screenshot-map channel\n                                    (list screenshot-1 screenshot-2))))\n      (let ((one (to-list map))\n            (two (to-list map)))\n        (is (eq one two))\n        (assert-that one\n                     (contains\n                      (has-screenshot screenshot-1)\n                      (has-screenshot screenshot-2)))))))\n\n\n(test make-empty-map\n  \"Unlikely to see this in prod, but let's make sure we're handling this\"\n  (with-fixture state ()\n    (let ((map (make-screenshot-map channel nil))\n          (map-2 (make-screenshot-map channel nil)))\n      (is (eql map map-2)))))\n\n(deftransaction construct-long-list (count)\n  (let ((items (loop for i from 0 below count\n                     collect (make-instance 'screenshot-map\n                                            :screenshots nil))))\n    (loop for item on items\n          if (second item)\n            do\n               (setf (slot-value (first item) 'previous)\n                     (second item)))\n    items))\n\n(test memoized-map-is-tail-call-optimized\n  (with-fixture state ()\n    (let ((count 40000))\n      (let ((items (construct-long-list count)))\n        (is\n         (equal count\n                (memoized-reduce\n                 (lambda (this length)\n                   (+ 1 length))\n                 (car items)\n                 0\n                 'chain-cost)))))))\n\n(test save-and-restore-screenshot-map\n  (tmpdir:with-tmpdir (dir)\n    (with-fixture state (:dir dir)\n      (let ((map (make-screenshot-map channel\n                                      (list screenshot-1 screenshot-2)))))\n      (bknr.datastore:snapshot))\n    (with-fixture state (:dir dir)\n      (assert-that (class-instances 'screenshot-map)\n                   (has-length 1)))))\n\n(def-fixture small-chain ()\n  (let* ((one (make-instance 'screenshot-map))\n         (two (make-instance 'screenshot-map :previous one))\n         (three (make-instance 'screenshot-map :previous two))\n         (four (make-instance 'screenshot-map :previous three)))\n    (&body)))\n\n(test memoized-reduce-on-simple-chain\n  (with-fixture state ()\n    (with-fixture small-chain ()\n      (is\n       (equal\n        (list four three two one)\n        (memoized-reduce #'list* four nil 'chain-cost)))\n      (is\n       (equal\n        (list two one)\n        (slot-value two 'chain-cost))))))\n\n(test memoized-reduce-on-null\n  (with-fixture state ()\n    (with-fixture small-chain ()\n      (is\n       (equal\n        :foobar\n        (memoized-reduce #'list* nil :foobar 'chain-cost))))))\n\n(test memoized-reduce-is-actually-cached\n  (with-fixture state ()\n    (with-fixture small-chain ()\n      (is\n       (equal\n        (list four three two one)\n        (memoized-reduce #'list* four nil 'chain-cost)))\n      (is\n       (equal\n        (list two one)\n        (slot-value two 'chain-cost)))\n      (is\n       (equal\n        (list four three two one)\n        (memoized-reduce #'list* four :will-cause-a-crash-if-used 'chain-cost)))\n      (setf (slot-value two 'chain-cost)\n            :will-cause-a-crash-if-used)\n      (is\n       (equal\n        (list four three two one)\n        (memoized-reduce #'list* four :will-cause-a-crash-if-used 'chain-cost))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-screenshot.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/model/test-screenshot\n  (:use #:cl\n        #:alexandria\n        #:bknr.datastore\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/model/image\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/company\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/model/image\n                #:delete-image\n                #:image=)\n  (:import-from #:util/object-id\n                #:oid)\n  (:import-from #:screenshotbot/model/screenshot\n                #:image-no-longer-exists-for-screenshot\n                #:find-in-run\n                #:screenshot-key\n                #:lite-screenshot)\n  (:import-from #:screenshotbot/model/screenshot-key\n                #:ensure-screenshot-key)\n  (:import-from #:bknr.datastore\n                #:decode\n                #:encode)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run))\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'installation)))\n   (with-test-store ()\n     (let* ((channel (make-instance 'channel)))\n       (&body)))))\n\n(test no-history\n  (with-fixture state ()\n    (is (equal nil (get-screenshot-history channel \"foo\")))))\n\n(def-fixture history-fixture ()\n  (let* ((im1 (make-image :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\")))\n         (im2 (make-image :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/wizard.png\"))))\n    (&body)))\n\n(defun screenshots= (list1 list2)\n  (and\n   (eql (length list1) (length list2))\n   (loop for x in list1\n         for y in list2\n         if (not\n             (and\n              (fset:equal? (screenshot-key x) (screenshot-key y))\n              (eql (screenshot-image x) (screenshot-image y))))\n           return nil\n         finally\n         (return t))))\n\n(test theres-runs-but-none-of-this-name\n  (with-fixture state ()\n    (with-fixture history-fixture ()\n      (let* ((run1 (make-recorder-run\n                    :screenshots (list\n                                  (make-instance 'screenshot\n                                                 :name \"foo\"\n                                                 :image im1))\n                    :channel channel))\n             (run2 (make-recorder-run\n                    :screenshots (list\n                                  (make-instance 'screenshot\n                                                 :name \"foo\"\n                                                 :image im2))\n                    :previous-run run1\n                    :channel channel)))\n        (is-true (recorder-run-screenshots run1))\n        (setf (active-run channel \"master\") run2)\n        (is-true channel)\n        (is (equal nil (get-screenshot-history channel \"blah\")))\n        (is (equal (list run2 run1) (channel-promoted-runs channel)))\n        (is (screenshots= (list\n                           (car (recorder-run-screenshots run2))\n                           (car (recorder-run-screenshots run1)))\n                          (get-screenshot-history channel \"foo\")))))))\n\n(test in-history-we-also-pull-renamed-screenshots\n  (with-fixture state ()\n    (with-fixture history-fixture ()\n      (let* ((run1 (make-recorder-run\n                    :screenshots (list\n                                  (make-instance 'screenshot\n                                                 :name \"bar\"\n                                                 :image im1))\n                    :channel channel))\n             (run2 (make-recorder-run\n                    :screenshots (list\n                                  (make-instance 'screenshot\n                                                 :name \"foo\"\n                                                 :image im1))\n                    :previous-run run1\n                    :channel channel)))\n        (setf (active-run channel \"master\") run2)\n        (is (screenshots= (list\n                           (car (recorder-run-screenshots run2))\n                           (car (recorder-run-screenshots run1)))\n                          (get-screenshot-history channel \"foo\")))))))\n\n\n(test make-screenshot-uniqueness ()\n  (with-test-store ()\n   (let ((args (list :name \"foo4\" :lang \"bar\")))\n     (let ((screenshot (apply 'make-screenshot args)))\n       (is (eql (screenshot-key screenshot) (screenshot-key (apply 'make-screenshot args))))))))\n\n(test make-screenshot-uniqueness-with-masks ()\n  (with-test-store ()\n   (let* ((mask1 (list (make-instance 'mask-rect :left 1 :top 2 :width 3 :height 4)))\n          (mask2 (list (make-instance 'mask-rect :left 1 :top 2 :width 3 :height 5)))\n          (mask3 (list (make-instance 'mask-rect :left 1 :top 2 :width 3 :height 4))))\n     (is (eql (screenshot-key (make-screenshot :name \"foo\" :masks mask1))\n              (screenshot-key (make-screenshot :name \"foo\" :masks mask3))))\n     (is (not (eql (screenshot-key (make-screenshot :name \"foo\" :masks mask1))\n                   (screenshot-key (make-screenshot :name \"foo\" :masks mask2))))))))\n\n\n(test image-slot-can-be-oid-or-not\n  (with-test-store ()\n    (let ((img (make-instance 'image)))\n      (let ((s1 (make-instance 'screenshot\n                               :name \"bleh\"\n                               :image img)))\n        (is (eql img (screenshot-image s1))))\n      (let ((s1 (make-instance 'screenshot\n                               :name \"bleh\"\n                               :image (oid img :stringp nil))))\n        (is (eql img (screenshot-image s1)))))))\n\n(test encode-lite-screenshot\n  (with-test-store ()\n    (let ((stream (flex:make-in-memory-output-stream))\n          (img (make-instance 'image)))\n     (let ((screenshot (make-instance 'lite-screenshot\n                                      :screenshot-key (ensure-screenshot-key\n                                                       :name \"foobar\")\n                                      :image-oid (oid img :stringp nil))))\n       (encode screenshot stream)\n       (let ((decoded (decode (flex:make-in-memory-input-stream\n                               (flex:get-output-stream-sequence  stream))))))))))\n\n(test find-in-run\n  (with-fixture state ()\n    (with-fixture history-fixture ()\n      (let* ((s1 (make-screenshot\n                  :name \"bleh\"\n                  :image im1))\n             (s2 (make-screenshot\n                  :name \"foo\"\n                  :image im2))\n             (run (make-recorder-run\n                  :screenshots (list\n                                s1\n                                s2))))\n        (is (equal \"bleh\" (screenshot-name (find-in-run run \"bleh\"))))\n        (is (equal \"foo\" (screenshot-name (find-in-run run \"foo\"))))))))\n\n(test screenshot-with-deleted-image-throws-error\n  (with-fixture state ()\n    (with-fixture history-fixture ()\n      (let* ((screenshot (make-screenshot\n                          :name \"test-screenshot\"\n                          :image im1)))\n        (delete-image im1)\n        (signals image-no-longer-exists-for-screenshot\n          (screenshot-image screenshot))))))\n\n"
  },
  {
    "path": "src/screenshotbot/model/test-transient-object.lisp",
    "content": ";;;; -*- coding: utf-8 -*-\n;;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-transient-object\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/model/transient-object\n                #:with-transient-copy)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:util/object-id\n                #:oid-array\n                #:object-with-oid)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:does-not-have-item\n                #:has-item)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/model/test-transient-object)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(with-transient-copy (mem-obj abstract-obj)\n  (defclass obj (store-object)\n    ((value :initarg :value\n            :accessor obj-value))\n    (:metaclass persistent-class)))\n\n(test preconditions\n  (with-fixture state ()\n    (make-instance 'obj)\n    (make-instance 'mem-obj)\n    (let ((obj (make-instance 'mem-obj)))\n      (is (typep obj 'abstract-obj))\n      (setf (obj-value obj) :arg)\n      (is (eql :arg (obj-value obj))))\n    (let ((obj (make-instance 'obj)))\n      (is (typep obj 'abstract-obj))\n      (setf (obj-value obj) :arg)\n      (is (eql :arg (obj-value obj)))\n      (with-transaction ()\n        (setf (obj-value obj) :arg2))\n      (is (eql :arg2 (obj-value obj))))))\n\n(test transactions-are-okay-even-for-transient-objects\n  (with-fixture state ()\n    (let ((obj (make-instance 'mem-obj)))\n      (with-transaction ()\n        (setf (obj-value obj) :arg2))\n      (pass))))\n\n(with-transient-copy (mem-obj-id abstract-obj-id)\n  (defclass obj-id (object-with-oid)\n    ((value :initarg :value\n            :accessor obj-value))\n    (:metaclass persistent-class)))\n\n(test object-with-oid\n  (with-fixture state ()\n    (let ((obj (make-instance 'mem-obj-id :oid #(22 22))))\n      (is (equalp #(22 22) (oid-array obj))))))\n\n(with-transient-copy (mem-obj-2 abstract-obj-2\n                                :extra-transient-slots (some-slot))\n  (defclass obj-2 (store-object)\n    ((value :initarg :value\n            :accessor obj-value))\n    (:metaclass persistent-class)))\n\n(test transient-slots\n  (with-fixture state ()\n    (assert-that (mapcar #'closer-mop:slot-definition-name\n                         (closer-mop:class-slots\n                          (closer-mop:ensure-finalized\n                           (find-class 'mem-obj-2))))\n                 (has-item 'some-slot))\n    (assert-that (mapcar #'closer-mop:slot-definition-name\n                         (closer-mop:class-slots\n                          (closer-mop:ensure-finalized\n                           (find-class 'abstract-obj-2))))\n                 (does-not-have-item 'some-slot))\n\n    (let ((obj (make-instance 'mem-obj-2)))\n      (finishes\n        (setf (slot-value obj 'some-slot) 2))\n      (is (eql 2 (slot-value obj 'some-slot))))))\n"
  },
  {
    "path": "src/screenshotbot/model/test-user.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/test-user\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:screenshotbot/user-api\n                #:user-email\n                #:user\n                #:user-companies)\n  (:import-from #:screenshotbot/model/company\n                #:sub-company\n                #:company-admin-p\n                #:get-singleton-company\n                #:prepare-singleton-company\n                #:personalp\n                #:company\n                #:company-owner)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation\n                #:*installation*)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p)\n  (:import-from #:screenshotbot/model/user\n                #:user-email-exists\n                #:*lowercase-email-map*\n                #:user-with-email\n                #:make-user)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:fiveam-matchers/misc\n                #:is-not-null)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:has-typep\n                #:assert-that\n                #:is-equal-to)\n  (:import-from #:auth/model/roles\n                #:owner\n                #:admin\n                #:standard-member\n                #:user-role)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order\n                #:has-item\n                #:contains)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/model/test-user)\n\n\n(util/fiveam:def-suite)\n\n(defclass pro-installation (installation multi-org-feature)\n  ())\n\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((*installation* (make-instance 'pro-installation)))\n      (&body))))\n\n(test make-user\n  (with-fixture state ()\n   (let ((user (make-user)))\n     (unwind-protect\n          (let ((companies (roles:companies-for-user user)))\n            (is (equal 1 (length companies)))\n            (let ((company (car companies)))\n              (is-true (personalp company))\n              (is-true (roles:has-role-p company user 'roles:admin))))\n       (let ((companies (roles:companies-for-user user)))\n         (delete-object user)\n         (loop for company in companies\n               do (delete-object company)))))))\n\n(test but-with-regular-installation-singleton-company-is-not-deleted\n  (with-test-store ()\n   (let ((*installation* (make-instance 'installation)))\n     (prepare-singleton-company)\n     (let* ((user (make-user)))\n       (loop for company in (bknr.datastore:store-objects-with-class 'company)\n             do\n                (is-false (roles:has-role-p company user 'roles:admin))\n                (is-false (roles:has-role-p company user 'roles:owner)))\n       (delete-object user)\n       (pass)))))\n\n(test user-with-email-is-case-insensitive\n  (with-fixture state ()\n    (let ((user (make-user :email \"IT@example.com\")))\n      (is (eql user (user-with-email \"IT@example.com\")))\n      (is (eql user (user-with-email \"it@example.com\"))))))\n\n(test user-with-email-is-case-insensitive-the-other-way-around\n  (with-fixture state ()\n    (let ((user (make-user :email \"it@example.com\")))\n      (is (eql user (user-with-email \"IT@example.com\")))\n      (is (eql user (user-with-email \"it@example.com\"))))))\n\n(test user-with-email-is-case-insentivie-even-after-setting-email\n  (with-fixture state ()\n    (let ((user (make-user :email \"foo@example.com\")))\n      (is (eql user (user-with-email \"foo@example.com\")))\n      (with-transaction ()\n        (setf (user-email user) \"IT@example.com\"))\n      (is (equal user (user-with-email \"it@example.com\")))\n      (is (equal nil (user-with-email \"foo@example.com\"))))))\n\n(test |don't allow me to add a new user with same email|\n  (with-fixture state ()\n    (make-user :email \"IT@example.com\")\n    (signals user-email-exists\n      (make-user :email \"it@example.com\"))\n    ;; check that our store is still valid though\n    (make-user :email \"foo@example.com\")\n    (signals user-email-exists\n      (make-user :email \"IT@example.com\"))))\n\n(test simple-find-or-create-user\n  (with-fixture state ()\n    (assert-that\n     (auth:find-or-create-user *installation* :email \"foo@example.com\")\n     (is-not-null))\n    (assert-that\n     (auth:find-or-create-user *installation* :email \"foo@example.com\")\n     (is-equal-to (first (bknr.datastore:class-instances 'user))))))\n\n(test user-roles-from-old-model\n  (with-fixture state ()\n    (let* ((company (make-instance 'company))\n           (user (make-user :email \"foo@example.com\"\n                            :companies (list company))))\n      (assert-that (user-role\n                    company user)\n                   (has-typep 'standard-member))\n      (is-false (company-admin-p company user)))))\n\n(test user-with-email-shouldnt-find-a-deleted-user\n  (with-fixture state ()\n    (let ((user (make-user :email \"foo@example.com\")))\n      (is (eql user (user-with-email \"Foo@example.com\")))\n      (bknr.datastore:delete-object user)\n      (is-false (user-with-email \"foo@example.com\"))\n      (is\n       (not (eql user (make-user :email \"foo@example.com\")))))))\n\n(test roles-shows-sub-companies-too\n  (with-fixture state ()\n    (let* ((company (make-instance 'company))\n           (user (make-user :companies (list company)))\n           (company-2 (make-instance 'sub-company :parent company :name \"second\")))\n      (assert-that (roles:companies-for-user user)\n                   ;; order matters!\n                   (contains company company-2)))))\n\n(test companies-dont-show-up-twice\n  (with-fixture state ()\n    (let* ((company (make-instance 'company))\n           (company-2 (make-instance 'sub-company :parent company :name \"second\"))\n           (user (make-user :companies (list company company-2))))\n      (assert-that (roles:companies-for-user user)\n                   (contains-in-any-order company company-2)))))\n"
  },
  {
    "path": "src/screenshotbot/model/testing.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/testing\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/magick/magick-lw\n                #:save-as-webp\n                #:magick-new-image\n                #:pixel-set-color\n                #:new-pixel-wand\n                #:with-wand\n                #:with-pixel\n                #:screenshotbot-set-pixel))\n(in-package :screenshotbot/model/testing)\n\n(def-easy-macro with-test-image (&binding name &key pixels (color \"red\")\n                                          (height 10)\n                                          (width 10)\n                                          &fn fn)\n  \"Creates a temporary WebP test image with specified pixels and dimensions.\n   PIXELS is a list of (x y) coordinate pairs to set to COLOR.\n   Binds NAME to the pathname of the temporary image file.\"\n  (uiop:with-temporary-file (:pathname p :type \"webp\")\n    (with-wand (wand)\n     (let ((default-pixel (new-pixel-wand)))\n       (pixel-set-color default-pixel \"none\")\n       (magick-new-image wand width height default-pixel)\n       (loop for (x y) in pixels\n             do\n                (with-pixel (pixel x y)\n                  (screenshotbot-set-pixel wand pixel color)))\n       (save-as-webp wand p)\n       (fn p)))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/model/transient-object.lisp",
    "content": "(defpackage :screenshotbot/model/transient-object\n  (:use #:cl)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:util/object-id\n                #:oid-array\n                #:oid\n                #:object-with-oid)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:with-transient-copy\n   #:make-transient-clone\n   #:cannot-make-transient))\n(in-package :screenshotbot/model/transient-object)\n\n(defun remove-index-args (slot-def)\n  (let ((args (cdr slot-def)))\n    (list*\n     (car slot-def)\n     (a:remove-from-plist args\n                          :index\n                          :index-type\n                          :index-initargs\n                          :index-reader\n                          :index-values\n                          :relaxed-object-reference))))\n\n(defmacro with-transient-copy ((transient-class parent-class &key extra-transient-slots)\n                               &body (class-def . class-def-rest))\n  (assert (not class-def-rest))\n  (destructuring-bind (keyword class-name parent-classes slot-defs &rest options)\n      class-def\n   `(progn\n      (defclass ,parent-class ()\n        ())\n\n      (defclass ,transient-class (,parent-class)\n        (,@ (when (member 'object-with-oid parent-classes)\n              `((oid :initarg :oid\n                     :accessor oid-array)))\n         (id :initarg :id\n             :accessor transient-object-original-id\n             :documentation \"The original id, for debugging purposes\")\n         ,@(mapcar #'remove-index-args slot-defs)\n         ,@ extra-transient-slots))\n\n      (with-class-validation\n        (,keyword ,class-name (,@parent-classes ,parent-class)\n                  ,slot-defs\n                  ,@options)))))\n\n(define-condition cannot-make-transient (error)\n  ((obj :initarg :obj)))\n\n(defgeneric make-transient-clone (obj)\n  (:documentation \"Make a transient clone of a given object. The rules of creating the\n clone might vary depending on the object and business logic, so be\n sure to look at the implementation details for the object that you're\n interested in.\"))\n\n(defmethod make-transient-clone :around ((self store-object))\n  (let ((ret (call-next-method)))\n    (setf (transient-object-original-id ret) (bknr.datastore:store-object-id self))\n    ret))\n"
  },
  {
    "path": "src/screenshotbot/model/user.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/user\n  (:use :cl)\n  (:nicknames #:%u)\n  (:import-from #:auth\n                #:current-user\n                #:oauth-user-avatar\n                #:oauth-user-email\n                #:oauth-user-full-name\n                #:user-first-name\n                #:oauth-user-user\n                #:password-hash\n                #:user-email)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:persistent-class\n                #:store-objects-with-class\n                #:with-transaction)\n  (:import-from #:bknr.indices\n                #:destroy-object\n                #:unique-index)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature\n                #:one-owned-company-per-user)\n  (:import-from #:screenshotbot/login/github\n                #:github-user)\n  (:import-from #:screenshotbot/model/company\n                #:sub-companies-of\n                #:company-admin-p\n                #:company\n                #:company-owner\n                #:get-singleton-company)\n  (:import-from #:screenshotbot/notice-api\n                #:notice-summary\n                #:notice-title)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:all-users\n                #:personalp\n                #:unaccepted-invites\n                #:user\n                #:user-companies\n                #:user-full-name\n                #:user-image-url\n                #:user-notices)\n  (:import-from #:util\n                #:make-secret-code)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:util/store/object-id\n                #:object-with-oid)\n  (:import-from #:util/store/store\n                #:def-store-local\n                #:with-class-validation)\n  (:import-from #:auth/model/email-confirmation\n                #:user-email-confirmed-p\n                #:secret-code\n                #:finish-confirmation)\n  (:import-from #:auth/model/roles\n                #:owner\n                #:admin\n                #:user-role\n                #:standard-member)\n  (:import-from #:util/store/store-migrations\n                #:def-store-migration)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:auth/model/invite\n                #:set-user-has-seen-invite)\n  (:import-from #:util.cdn\n                #:make-cdn)\n  (:import-from #:encrypt/hmac\n                #:sign-hmac)\n  (:local-nicknames (#:roles #:auth/model/roles))\n  (:export\n   #:adminp\n   #:arnold\n   #:companies\n   #:confirmation-confirmed-p\n   #:confirmed-p\n   #:email\n   #:email-confirmation-code\n   #:email-confirmations\n   #:finish-confirmation\n   #:github-user\n   #:make-user\n   #:notices\n   #:oauth-user-avatar\n   #:oauth-user-email\n   #:oauth-user-full-name\n   #:oauth-user-user\n   #:oauth-users\n   #:password-hash\n   #:personalp\n   #:professionalp\n   #:secret-code\n   #:unaccepted-invites\n   #:user\n   #:user-companies\n   #:user-email\n   #:user-first-name\n   #:user-full-name\n   #:user-image-url\n   #:user-notice\n   #:user-notices\n   #:user-personal-company\n   #:user-with-email\n   #:users-for-company\n   #:with-user-lock))\n(in-package :screenshotbot/model/user)\n\n(defvar *lock* (bt:make-lock))\n\n(def-store-local *lowercase-email-map*\n    (make-hash-table :test #'equal)\n  \"A map from lowercase emails to the user. There might be stale\n  mappings, so please use user-with-email to access this map, and\n  don't directly use this map.\")\n\n(defun arnold ()\n  (user-with-email \"arnold@tdrhq.com\"))\n\n(with-class-validation\n  (defclass user (util:object-with-oid)\n    ((full-name :type (or null string)\n                :initarg :full-name\n                :initform nil\n                :reader %user-full-name\n                :writer (setf user-full-name))\n     (email :type (or null string)\n            :initarg :email\n            :initform nil\n            :reader user-email\n            :index-initargs (:test #'equal)\n            :index-type unique-index\n            :index-reader %user-with-email\n            :writer (setf user-email))\n     (password-hash :type (or null string)\n                    :initform nil\n                    :accessor auth:password-hash)\n     (confirmed-p :type boolean\n                  :initarg :confirmed-p\n                  :writer (setf confirmed-p)\n                  :reader %confirmed-p\n                  :documentation \"Don't think we're actually reading this from anywhere. Look at user-email-confirmed-p instead.\")\n     (professionalp\n      :type boolean\n      :initarg :professionalp\n      :initform nil\n      :accessor professionalp)\n     (notices\n      :initform nil\n      :accessor user-notices)\n     (unaccepted-invites\n      :initform nil\n      :accessor unaccepted-invites\n      :accessor auth:unaccepted-invites)\n     (lock\n      :transient t\n      :initform (bt:make-lock))\n     (adminp\n      :initform nil\n      :accessor adminp)\n     (email-confirmations\n      :initarg :email-confirmations\n      :initform nil\n      :accessor email-confirmations)\n     (oauth-users\n      :initform nil\n      :accessor auth:oauth-users)\n     (companies\n      :initform nil\n      :accessor %user-companies\n      :documentation \"This companies slot is only use in a multi-org\n    set-up. A default installation of Screenshotbot OSS, would be a\n    single org set up.\")\n     (default-company\n      :initarg :default-company\n      :initform nil\n      :documentation \"The default company when this user logs in. We'll\n    change this when the user 'switches' companies in the UI. If no\n    default company is provided it should default to the personal\n    company.\"))\n    (:metaclass persistent-class)))\n\n(define-condition user-email-exists (error)\n  ((email :initarg :email)))\n\n(defmethod print-object ((e user-email-exists) out)\n  (format out \"The user already exists with email: ~a\"\n          (slot-value e 'email)))\n\n(defmethod update-lowercase-email-map ((user user))\n  \"Update the lowercase email index. This might be called inside the transaction to make-user, in which case it should correctly error and not update the state\"\n  (when (slot-boundp user 'email)\n    (let ((email (str:downcase (user-email user))))\n      (when email ;; mostly for tests\n        (symbol-macrolet ((place (gethash email *lowercase-email-map*)))\n          (let ((prev-user place))\n            (cond\n              ((or (null prev-user)\n                   (bknr.datastore::object-destroyed-p prev-user))\n               (setf place user))\n              ((not (eql prev-user user))\n               (error 'user-email-exists :email (user-email user))))))))))\n\n(defmethod bknr.datastore:initialize-transient-instance :after ((user user))\n  (update-lowercase-email-map user))\n\n;; (mapc #'update-lowercase-email-map (all-users))\n\n(defun all-users ()\n  (store-objects-with-class 'user))\n\n(defun user-with-email (email)\n  (let ((old-val (%user-with-email email)))\n   (let ((user\n           (gethash (str:downcase email) *lowercase-email-map*)))\n     (cond\n       ((and user\n             (not (bknr.datastore::object-destroyed-p user))\n             (string-equal email (user-email user)))\n        user)\n       (t\n        (when old-val\n          ;; Safety measure. Once we verify this is not happening, we\n          ;; can safely delete the use of old-val and just always\n          ;; return nil\n          (log:warn \"Old user: ~a, new user: ~a\" old-val user)\n          (warn \"The new index did not match the new-index for ~a\" email))\n        old-val)))))\n\n(defun make-user (&rest args &key companies  &allow-other-keys)\n  (let ((user (apply #'make-instance 'user (alexandria:remove-from-plist\n                                            args\n                                            :companies))))\n    (cond\n      (companies ;; probably only in tests?\n       ;; TODO: remove. Only task-integration-tests needs it.\n       (setf (slot-value user 'companies) companies)\n       (dolist (company companies)\n         (roles:ensure-has-role company user 'roles:standard-member)))\n      (t\n       (initialize-companies-for-user user (installation))))\n    (push-event :user.created)\n    user))\n\n(defmethod initialize-companies-for-user (user installation)\n  (values))\n\n(defmethod initialize-companies-for-user (user (installation multi-org-feature))\n  ;; Heads up, in pro-installation, we also mixin\n  ;; one-owned-company-per-user. So as of writing, this code probably\n  ;; doesn't get hit in prod, only in tests.\n  (let ((company (make-instance 'company\n                                :personalp t\n                                :admins (list user)\n                                :owner user)))\n    (roles:ensure-has-role company user 'roles:standard-member)))\n\n(defmethod initialize-companies-for-user (user (installation\n                                                one-owned-company-per-user))\n  (values))\n\n(defmethod destroy-object :before ((user user))\n  \"TODO: 5/13/24: delete\")\n\n(defmethod users-for-company ((company company))\n  ;; This is currently only used by company/members.lisp. We can\n  ;; replace it with roles:users-for-company once we migrate all the\n  ;; roles. (T1156)\n  (loop for user in (all-users)\n        if (roles:has-role-p company user t)\n          collect user))\n\n(defmethod user-companies ((user user))\n  (error \"unimplemented\"))\n\n(defmethod (setf user-companies) (companies (user user))\n  (error \"unimplemented\"))\n\n(with-class-validation\n  (defclass user-notice (util:object-with-unindexed-oid)\n    ((title :initarg :title\n            :accessor notice-title)\n     (summary :initarg :summary\n              :accessor notice-summary))\n    (:metaclass persistent-class)))\n\n(defmacro with-user-lock ((&optional (user `(current-user))) &body body)\n  `(flet ((body () ,@body))\n     (with-slots (lock) ,user\n       (bt:with-lock-held (lock)\n         (body)))))\n\n(defmethod print-object ((user user) out)\n  (format out \"#<USER ~a>\" (user-email user)))\n\n(defmethod user-full-name ((user user))\n  (or (%user-full-name user)\n      (when (auth:oauth-users user)\n        (assert (car (auth:oauth-users user)))\n        (oauth-user-full-name (car (auth:oauth-users user))))))\n\n(defun user-personal-company (user)\n  (loop for company in (roles:companies-for-user user)\n        if (personalp company)\n          do (return company)))\n\n\n(with-class-validation\n  (defclass email-confirmation-code (object-with-oid)\n    ((code :initform (make-secret-code)\n           :type string\n           :reader secret-code)\n     (email :type (or null string)\n            :initform nil\n            :initarg :email\n            :reader confirmation-code-email)\n     (confirmed-p\n      :type boolean\n      :accessor confirmation-confirmed-p)\n     (user\n      :initarg :user\n      :accessor confirmation-user))\n    (:metaclass persistent-class)))\n\n(defmethod finish-confirmation ((confirmation email-confirmation-code))\n  (let ((user (confirmation-user confirmation)))\n    (with-transaction ()\n      (push confirmation (email-confirmations user))\n      (setf (confirmation-confirmed-p confirmation) t)\n      (setf (confirmed-p user) t))))\n\n(defmethod user-image-url (user &rest args)\n  (let ((id (bknr.datastore:store-object-id user)))\n   (make-cdn\n    (hex:make-url\n     \"/account/avatar\"\n     :id id\n     :signature (ironclad:byte-array-to-hex-string\n                 (sign-hmac (format nil \"avatar.~a\" id)))))))\n\n(defmethod default-company ((user user))\n  (error \"deprecated\"))\n\n(defmethod (setf user-email) :after (email (user user))\n  (update-lowercase-email-map user))\n\n(defmethod auth:find-user ((self installation) &key email)\n  (user-with-email email))\n\n(defmethod auth:find-or-create-user ((self installation) &key email)\n  (bt:with-lock-held (*lock*)\n   (or\n    (values (user-with-email email) nil)\n    (values (make-user :email email) t))))\n\n(defmethod auth:make-user ((self installation) &rest args &key &allow-other-keys)\n  (apply #'make-user args))\n\n(defmethod user-email-confirmed-p ((user user))\n  (or\n   (call-next-method)\n   (%confirmed-p user)))\n\n(defmethod user-role ((company company) (user user))\n  \"To support transitioning to the new role based access control, we\noverride user-role.\"\n  (call-next-method))\n\n;; TODO: remove these two\n(defmethod (setf roles:user-role) :after (value (company company) (user user)))\n(defmethod (setf roles:user-role) :after ((value null) (company company) (user user)))\n\n(defmethod roles:companies-for-user :around ((user user))\n  (call-next-method))\n\n\n(def-store-migration (\"Migrate user-companies to roles\" :version 16)\n  \"Note we don't clean up the 'companies slot. That can be a future\nmigration.\"\n  (ensure-slot-boundp 'user 'companies)\n  (dolist (user (class-instances 'user))\n    (dolist (company (user-companies user))\n      (format t \"Adding ~a to ~a~%\" user company)\n      (roles:ensure-has-role company user 'roles:standard-member))))\n\n(def-store-migration (\"migrated unaccepted-invitations\" :version 18)\n  (ensure-slot-boundp 'user 'unaccepted-invites)\n  (dolist (user (class-instances 'user))\n    (dolist (invite (unaccepted-invites user))\n      (set-user-has-seen-invite user invite))))\n\n(defmethod roles:companies-for-user :around ((user user))\n  (let ((stack (call-next-method))\n        (result nil)\n        (seen (make-hash-table)))\n    (loop while stack do\n      (let ((next (pop stack)))\n        (unless (gethash next seen)\n          (setf (gethash next seen) t)\n          (push next result)\n          (fset:do-set (sub-company (sub-companies-of next))\n            (push sub-company stack)))))\n    (reverse result)))\n\n(defmethod auth:can-viewer-view (obj (user user))\n  (error \"You got the order wrong, user must come before obj in can-viewer-view!\"))\n"
  },
  {
    "path": "src/screenshotbot/model/view.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/model/view\n  (:use :cl)\n  (:import-from #:auth\n                #:current-user\n                #:no-access-error\n                #:can-view\n                #:can-edit\n                #:can-edit!\n                #:can-view!)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:can-public-view\n                #:user)\n  (:import-from #:auth/view\n                #:can-viewer-view)\n  (:import-from #:auth/viewer-context\n                #:site-admin-viewer-context)\n  (:export\n   #:can-edit\n   #:can-edit!\n   #:can-public-view\n   #:can-view\n   #:can-view!))\n(in-package :screenshotbot/model/view)\n\n;; This file adds logic to check if a specific object can be viewed by\n;; the given user\n\n(defmethod can-view :around (obj (user user))\n  ;; TODO: remove\n  (call-next-method))\n\n(defmethod can-viewer-view :around ((vc site-admin-viewer-context)\n                                    obj)\n  (or\n   ;; always call next method to make sure that code gets tested.\n   (call-next-method)\n   t))\n"
  },
  {
    "path": "src/screenshotbot/notice-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :screenshotbot/notice-api\n  (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/model/invite\n                #:invite-company)\n  (:export\n   #:invite-company\n   #:notice-title\n   #:notice-summary))\n(in-package :screenshotbot/notice-api)\n"
  },
  {
    "path": "src/screenshotbot/phabricator/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/phabricator\n    (:use #:cl\n          #:alexandria)\n  (:use-reexport #:screenshotbot/phabricator/plugin))\n"
  },
  {
    "path": "src/screenshotbot/phabricator/builds.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/phabricator/builds\n  (:use #:cl)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:util/phabricator/conduit\n                #:phab-instance\n                #:call-conduit)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:bknr.indices\n                #:unique-index)\n  (:import-from #:bknr.datastore\n                #:initialize-transient-instance)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:lparallel.promise\n                #:future)\n  (:import-from #:screenshotbot/phabricator/plugin\n                #:phab-instance-for-company)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/phabricator/builds)\n\n(defvar *build-info-index* (make-mp-hash-table :test #'equal))\n\n(defvar *lock* (bt:make-lock))\n\n(with-class-validation\n (defclass build-info (store-object)\n   ((diff :initarg :diff\n          :reader build-info-diff)\n    (revision :initarg :revision\n              :initform nil\n              :accessor build-info-revision)\n    (target-phid :initarg :target-phid\n                 :accessor target-phid)\n    (build-phid :initarg :build-phid\n                :accessor build-phid)\n    (company :initarg :company\n             :reader company)\n    (status :initarg :status\n            :initform nil\n            :documentation \"DEPRECATED\")\n    (details :initarg :details\n             :initform nil\n             :documentation \"DEPRECATED\")\n    (status-map :initarg status-map\n                :initform nil\n                :accessor build-info-status-map\n                :documentation \"A list of lists: each item having three values: the channel name,\nthe status, and the details\")\n    (needs-sync-p :initform nil\n                  :accessor needs-sync-p\n                  :documentation \"The status has been updated, but we've not synced to Phabricator yet,\n because we haven't gotten a callback, or because we've sent out a\n restart command and awaiting its response \")\n    (ts :initarg :ts\n        :reader ts))\n   (:metaclass persistent-class)\n   (:default-initargs :ts (get-universal-time))))\n\n(defmethod initialize-transient-instance :after ((self build-info))\n  (when (and\n         (slot-boundp self 'company)\n         (slot-boundp self 'diff))\n   (setf (gethash (cons (company self)\n                        (build-info-diff self))\n                  *build-info-index*)\n         self)))\n\n(defmethod find-build-info (company diff)\n  (gethash (cons company diff) *build-info-index*))\n\n(defmethod find-build-info (company (diff string))\n  (find-build-info company (parse-integer diff)))\n\n(defvar *pool* (make-instance 'max-pool\n                              :max 30))\n\n(def-easy-macro in-future (&fn fn)\n  (make-thread\n   (lambda ()\n     (fn))\n   :name \"phabricator/builds.lisp\"))\n\n(defmacro if-setf (place expr)\n  `(let ((val ,expr))\n     (when val\n       (setf ,place val))))\n\n(defmethod make-or-update-build-info (company (diff number)\n                                      &key )\n  (let ((build-info (find-build-info company diff)))\n    (cond\n      (build-info\n       (with-transaction ())\n       build-info)\n      (t\n       (make-instance 'build-info\n                      :diff diff\n                      :company company)))))\n\n(defapi (%update-build :uri \"/phabricator/update-build\" :method :post)\n        ((diff :parameter-type 'integer) (revision :parameter-type 'integer) target-phid\n         build-phid)\n  (assert diff)\n  (assert revision)\n  (assert target-phid)\n  (assert build-phid)\n  (let* ((company (current-company)))\n    (assert company)\n    (in-future ()\n      (bt:with-lock-held (*lock*)\n        (let ((build-info (make-or-update-build-info\n                           company diff)))\n          (with-transaction ()\n            (setf (build-info-revision build-info) revision)\n            (setf (target-phid build-info) target-phid)\n            (setf (build-phid build-info) build-phid))\n          (when (needs-sync-p build-info)\n            (let ((phab-instance (phab-instance-for-company company))\n                  (status-map (build-info-status-map build-info)))\n              (%actually-update-status\n               phab-instance\n               build-info\n               :status-map status-map))\n            (with-transaction ()\n              (setf (needs-sync-p build-info) nil)))))))\n  \"OK\")\n\n(defmethod %send-message ((phab phab-instance)\n                          phid\n                          type &key unit)\n  ;;(check-type unit (or null string))\n  (let ((type (str:downcase type)))\n    (let ((args `(,@ (when unit\n                       `((\"unit\" . ,unit)))\n                     (\"receiver\" . ,phid)\n                     (\"type\" . ,type))))\n      (call-conduit\n       phab\n       \"harbormaster.sendmessage\"\n       args))))\n\n(defun %actually-update-status (phab  self\n                                &key status-map)\n  \"Immediately send the Harbormaster message\"\n  (log:info \"Updating: D~a ~S\" (build-info-revision self) status-map)\n  (let ((types (mapcar #'cadr status-map)))\n    (let ((fail-count (count \"fail\" types :test #'equal))\n          (work-count (count \"work\" types :test #'equal)))\n     (%send-message phab\n                    (target-phid self)\n                    (cond\n                      ((not (zerop fail-count))\n                       \"fail\")\n                      ((not (zerop work-count))\n                       \"work\")\n                      (t\n                       \"pass\"))\n                    :unit\n                    (loop for (name type details) in status-map\n                          collect\n                          (a:alist-hash-table\n                           `((\"name\" . ,name)\n                             (\"result\" . ,(str:downcase type))\n                             (\"details\" . ,(or details \"dummy tdetails\"))\n                             (\"format\" . \"remarkup\"))))))))\n\n(defun got-initial-callback-p (build-info)\n  (slot-boundp build-info 'build-phid))\n\n(defmethod update-diff-status (company (diff string) status &key details (name \"Screenshot Tests\"))\n  (update-diff-status\n   company\n   (parse-integer diff)\n   status\n   :details details\n   :name name))\n\n(defmethod update-diff-status (company (diff number)\n                               status &key details (name \"Screenshot Tests\"))\n  (bt:with-lock-held (*lock*)\n    (let ((build-info\n            (make-or-update-build-info\n             company diff))\n          (phab (phab-instance-for-company company)))\n      (let ((original-status-map (build-info-status-map build-info)))\n        (with-transaction ()\n          (setf (assoc-value (build-info-status-map build-info) name\n                             :test #'equal)\n                (list status details)))\n        (flet ((update-needs-sync ()\n                 (with-transaction ()\n                   (setf (needs-sync-p build-info) t))))\n          (cond\n            ((needs-sync-p build-info)\n             ;; We don't need to do anything right now, we're still\n             ;; waiting a callback\n             (values))\n            ((and (not original-status-map)\n                  (got-initial-callback-p build-info))\n             ;; First time we're sending a message, but we already\n             ;; have a callback, so we can do this immediately.\n             (%actually-update-status\n              phab build-info\n              :status-map (build-info-status-map build-info)))\n            ((got-initial-callback-p build-info)\n             ;; So needs-sync is nil, and we got an initial callback, so\n             ;; we can immediately send the message.\n             (send-restart phab build-info)\n             (update-needs-sync))\n            (t\n             ;; We haven't got even our first callback at this point,\n             ;; so we just update the needs-sync.\n             (update-needs-sync))))))))\n\n(defmethod send-restart ((phab phab-instance) (self build-info))\n  (%send-message phab (build-phid self) :restart))\n\n#|\n(update-status *phab*\n(car (reverse (bknr.datastore:class-instances 'build-info)))\n\n:pass\n:details \"https://www.google.com\"\n)\n|#\n"
  },
  {
    "path": "src/screenshotbot/phabricator/diff-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/phabricator/diff-promoter\n  (:use #:cl\n        #:alexandria\n        #:util/phabricator/conduit\n        #:screenshotbot/model/company\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/promote-api)\n  (:import-from #:screenshotbot/phabricator/plugin\n                #:phab-instance-for-company\n                #:phabricator-git-repo\n                #:phabricator-plugin)\n  (:import-from #:util/phabricator/conduit\n                #:make-phab-instance-from-arcrc)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:check-key\n                #:promoter-pull-id\n                #:check-title\n                #:make-promoter-for-acceptable\n                #:details-url\n                #:check-status\n                #:push-remote-check\n                #:plugin-installed?\n                #:make-acceptable\n                #:abstract-pr-acceptable\n                #:valid-repo?\n                #:abstract-pr-promoter)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/phabricator/builds\n                #:build-info-revision\n                #:update-diff-status\n                #:target-phid\n                #:build-phid\n                #:find-build-info)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:phabricator-diff-id\n                #:recorder-run-company)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/logger\n                #:format-log)\n  (:export #:phabricator-promoter))\n(in-package :screenshotbot/phabricator/diff-promoter)\n\n\n(defmethod diff-to-revision ((phab phab-instance) diff-id)\n  (parse-integer\n   (assoc-value\n    (cdar\n     (cdar\n      (call-conduit phab \"differential.querydiffs\"\n                    `((\"ids\" . ,(vector diff-id))))))\n    :revision-+id+)))\n\n(defmethod create-comment ((phab phab-instance) revision-id message)\n  (call-conduit phab \"differential.createcomment\"\n                `((\"revision_id\" . ,revision-id)\n                  (\"message\" . ,message))))\n\n#+nil\n(diff-to-revision (make-phab-instance-from-arcrc \"https://phabricator.tdrhq.com/\") 7821)\n#+nil\n(create-comment *phab* 3498 \"hello world\")\n\n(defclass phabricator-promoter (abstract-pr-promoter)\n  ())\n\n(defclass diff-acceptable (abstract-pr-acceptable)\n  ()\n  (:metaclass persistent-class))\n\n(defmethod maybe-promote ((promoter phabricator-promoter) run)\n  (call-next-method))\n\n(defmethod valid-repo? ((promoter phabricator-promoter) (repo phabricator-git-repo))\n  t)\n\n(defmethod valid-repo? ((promoter phabricator-promoter) repo)\n  nil)\n\n(defmethod make-acceptable ((promoter phabricator-promoter) report\n                            &rest args)\n  (apply #'make-instance 'diff-acceptable\n         :report report\n         args))\n\n(defmethod plugin-installed? ((promoter phabricator-promoter) company repo-url)\n  (let ((ret (phabricator-config-for-company company)))\n    ret))\n\n(defmethod push-remote-check ((promoter phabricator-promoter) run check)\n  (cond\n    ((not (phabricator-diff-id run))\n     (format-log run :warn \"No diff-id, please provide a --phabricator-diff-id\"))\n    (t\n     (update-diff-status\n      (recorder-run-company run)\n      (phabricator-diff-id run)\n      (ecase (check-status check)\n        (:accepted \"pass\")\n        (:rejected \"fail\")\n        (:success \"pass\")\n        (:failed \"fail\")\n        (:failure \"fail\")\n        (:pending \"work\")\n        (:action-required \"fail\"))\n      :name (check-key check)\n      :details\n      (format nil \"~a~%~a\"\n              (check-title check)\n              (details-url check))))))\n\n(defmethod make-promoter-for-acceptable ((self diff-acceptable))\n  (make-instance 'phabricator-promoter))\n\n(defmethod maybe-send-tasks ((promoter phabricator-promoter) run)\n  (values))\n\n;; TODO: also add a `previous-reviews :before` to wait until the run\n;; gets a revision ID.\n(defmethod promoter-pull-id ((promoter phabricator-promoter) run)\n  (when-let ((build-info (find-build-info\n                          (recorder-run-company run)\n                          (phabricator-diff-id run))))\n    (build-info-revision build-info)))\n\n(defmethod add-comment ((promoter phabricator-promoter) comment)\n  (error \"TODO: Obsolete, delete\"))\n\n(unregister-promoter 'phabricator-promoter)\n\n(defmethod plugin-promoter ((plugin phabricator-plugin))\n  (make-instance 'phabricator-promoter))\n"
  },
  {
    "path": "src/screenshotbot/phabricator/plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/phabricator/plugin\n  (:use #:cl\n        #:alexandria\n        #:util/phabricator/conduit)\n  (:import-from #:screenshotbot/plugin\n                #:plugin\n                #:plugin-parse-repo)\n  (:import-from #:screenshotbot/git-repo\n                #:company\n                #:repo-link\n                #:commit-link\n                #:generic-git-repo)\n  (:import-from #:screenshotbot/model/company\n                #:phabricator-config-for-company\n                #:phabricator-url\n                #:conduit-api-key)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:screenshotbot/dashboard/review-link\n                #:review-link-impl)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run\n                #:phabricator-diff-id)\n  (:import-from #:util/misc\n                #:?.)\n  (:export #:phabricator-plugin\n           #:phab-instance-for-company)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/phabricator/plugin)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass phabricator-plugin (plugin)\n  ())\n\n(defclass phabricator-git-repo (generic-git-repo)\n  ())\n\n(defmethod plugin-parse-repo ((plugin phabricator-plugin)\n                              company\n                              repo-str)\n  (when (str:containsp \"phabricator\" repo-str)\n    (make-instance 'phabricator-git-repo :link repo-str\n                                         :company company)))\n\n(defparameter *cache* nil)\n(defvar *lock* (bt:make-lock))\n\n(defmethod get-repos (company (phab phab-instance))\n  \"Get the parsed JSON information about all the repos with\ndiffusion.repository.search.\"\n  ;; todo: paging. Currently this is limited to phabricator instances\n  ;; with less than 100 repos.\n  (bt:with-lock-held (*lock*)\n    (let ((assoc (assoc company *cache*)))\n      (if assoc\n          (cdr assoc)\n          (setf (assoc-value *cache* company)\n                ;; Ignore errors, because the Phabricator instance\n                ;; might be down. TODO: if this happens we should\n                ;; reset the cache at some point\n                (ignore-errors\n                 (assoc-value\n                  (assoc-value\n                   (call-conduit  phab\n                                  \"diffusion.repository.search\"\n                                  `())\n                   :result)\n                  :data)))))))\n\n(def-cron clean-phabricator-cache (:step-min 30)\n  (bt:with-lock-held (*lock*)\n    (setf *cache* nil)))\n\n(defmethod find-repo-by-name (repos (name string))\n  \"Find the repo json from diffusion.repository.search that\ncorresponds to this name. name could be shortName or full-name.\"\n  (loop for x in repos\n        for fields = (assoc-value x :fields)\n        if (or\n            (equal name (assoc-value fields :name))\n            (equal name (assoc-value fields :short-name)))\n          return x))\n\n(defmethod get-callsign (repos name)\n  \"Given the repo name, and the list of repos (from get-repos), figure\nout the callsign of the repo.\"\n  (assoc-value\n   (assoc-value\n    (find-repo-by-name repos name)\n    :fields)\n   :callsign))\n\n(defun phab-instance-for-company (company)\n  (let* ((config (phabricator-config-for-company company))\n         (phabricator-url (?. phabricator-url config))\n         (conduit-api-key (?. conduit-api-key config)))\n    (when (and phabricator-url\n               conduit-api-key)\n     (make-instance 'phab-instance\n                    :url phabricator-url\n                    :api-key conduit-api-key))))\n\n(defmethod commit-link ((repo phabricator-git-repo) hash)\n  (let* ((company (company repo))\n         (phabricator-url (phabricator-url\n                           (phabricator-config-for-company company))))\n    (a:when-let* ((phab-instance (phab-instance-for-company company))\n                  (repos (get-repos company phab-instance)))\n      (multiple-value-bind (res parts)\n          (cl-ppcre:scan-to-strings \"/([^/]*)[.]git$\" (repo-link repo))\n        (declare (ignore res))\n        (cond\n          (res\n           (let ((callsign (get-callsign repos (elt parts 0))))\n             (if callsign\n                 (format nil \"~a/r~a~a\" phabricator-url callsign hash)\n                 \"#\")))\n          (t \"#\"))))))\n\n\n(defmethod review-link-impl ((repo phabricator-git-repo) (run recorder-run))\n  (let* ((company (company repo))\n         (config (phabricator-config-for-company company))\n         (phabricator-url (phabricator-url config)))\n    <a href= (format nil \"~a/differential/diff/~a\" phabricator-url\n              (phabricator-diff-id run)) >\n      Revision\n    </a>))\n"
  },
  {
    "path": "src/screenshotbot/phabricator/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/phabricator/settings\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:nibble\n        #:util/form-errors\n        #:screenshotbot/model/company\n        #:screenshotbot/user-api\n        #:screenshotbot/settings-api)\n  (:import-from #:screenshotbot/phabricator/plugin\n                #:phabricator-plugin)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/model/company\n                #:conduit-api-key))\n(in-package :screenshotbot/phabricator/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar +unchanged+ \"unchanged\")\n\n(deftag phabricator-settings-template ()\n  (let* ((config (phabricator-config-for-company (current-company)))\n         (current-api-key (?. conduit-api-key config))\n         (submit (nibble (url api-key)\n                   (let ((api-key (cond\n                                    ((equal api-key +unchanged+)\n                                     current-api-key)\n                                    (t\n                                     api-key))))\n                    (let ((errors))\n                      (flet ((check (field test message)\n                               (unless test\n                                 (push (cons field message)\n                                       errors))))\n                        (check :url (not (str:emptyp url))\n                               \"Please provide a URL for phabricator\")\n                        (check :api-key (not (str:emptyp api-key))\n                               \"Please provide an API Key for Conduit\")\n                        (cond\n                          (errors\n                           (with-form-errors (:errors errors :url url :api-key api-key\n                                              :was-validated t)\n                             (phabricator-settings-template)))\n                          (t\n                           (with-transaction ()\n                             (setf (phabricator-url config) url)\n                             (setf (conduit-api-key config) api-key))\n                           (phabricator-settings-template)))))))))\n    <settings-template>\n      <form action= submit method= \"POST\" >\n        <div class= \"card mt-3\">\n          <div class= \"card-header\">\n            <h3>Setup Phabricator Comments</h3>\n          </div>\n\n          <div class= \"card-body\">\n\n            <div class= \"mb-3\">\n              <label for= \"url\" class= \"form-label\">Phabricator Url</label>\n              <input type= \"text\" class= \"form-control\" name= \"url\" placeholder= \"https://example.phabricator.com\" value= (phabricator-url config) />\n            </div>\n\n            <div class= \"mb-3\">\n              <label for= \"api-key\" class= \"form-label\">Conduit API Key</label>\n              <input type= \"password\" class= \"form-control\" name= \"api-key\" placeholder= \"*****\" value= (when (conduit-api-key config) +unchanged+) />\n            </div>\n\n          </div>\n\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Done\" />\n          </div>\n        </div>\n      </form>\n    </settings-template>))\n\n(defsettings phabricator-settings\n  :name \"phabricator\"\n  :title \"Phabricator\"\n  :section :vcs\n  :plugin 'phabricator-plugin\n  :handler 'phabricator-settings-template)\n"
  },
  {
    "path": "src/screenshotbot/phabricator/test-builds.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/phabricator/test-builds\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/phabricator/builds\n                #:send-restart\n                #:build-phid\n                #:target-phid\n                #:%actually-update-status\n                #:needs-sync-p\n                #:update-diff-status\n                #:%send-message\n                #:call-in-future\n                #:find-build-info\n                #:build-info\n                #:%update-build)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/phabricator/plugin\n                #:phab-instance-for-company)\n  (:import-from #:util/phabricator/conduit\n                #:phab-instance)\n  (:import-from #:hunchentoot\n                #:*request*)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/phabricator/test-builds)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (cl-mock:if-called 'call-in-future\n                       (lambda (fn)\n                         (funcall fn)))\n    (with-test-store ()\n      (let ((company (make-instance 'company)))\n\n        (cl-mock:if-called 'phab-instance-for-company\n                           (lambda (company)\n                             (make-instance 'phab-instance)))\n        (with-fake-request ()\n          (setf (auth:request-account *request*) company)\n          (&body))))))\n\n(test first-update\n  (with-fixture state ()\n    (cl-mock:if-called '%send-message\n                       (lambda (&rest args)\n                         (error \"should not be called\")))\n    (%update-build\n     :diff 123\n     :revision 321\n     :target-phid \"foobar\"\n     :build-phid \"carbar\")\n    (let ((info (car (class-instances 'build-info))))\n      (is (eql company\n               (company info)))\n      (is (eql info\n               (find-build-info company 123)))\n\n      (%update-build\n       :diff 123\n       :revision 321\n       :target-phid \"new-foo\"\n       :build-phid \"new-car\")\n      (is (equal (list info) (class-instances 'build-info))))))\n\n(defun get-only-build-info ()\n  (car (class-instances 'build-info)))\n\n(test first-status-then-callback\n  (with-fixture state ()\n\n    (cl-mock:if-called '%actually-update-status\n                       (lambda (phab self &key status-map)\n                         (error \"Should not be called\")))\n\n    (update-diff-status company 123\n                        :pass)\n\n    (is (eql t (needs-sync-p (get-only-build-info))))\n\n    (let ((res))\n      (cl-mock:if-called '%actually-update-status\n                         (lambda (phab self &key status-map)\n                           (is (equal \"foobar\"\n                                      (target-phid self)))\n                           (is (equal \"carbar\"\n                                      (build-phid self)))\n                           (push (second (first status-map)) res))\n                         :at-start t)\n      (%update-build\n       :diff 123\n       :revision 321\n       :target-phid \"foobar\"\n       :build-phid \"carbar\")\n      (is (equal '(:pass) res)))))\n\n(test update-status-after-callback\n  (with-fixture state ()\n    (let ((res nil))\n      (cl-mock:if-called '%actually-update-status\n                         (lambda (phab self &key status-map)\n                           (push (second (car status-map)) res)))\n      (cl-mock:if-called 'send-restart\n                         (lambda (&rest args)\n                           (error \"send-restart should not be called\")))\n      (%update-build\n       :diff 123\n       :revision 321\n       :target-phid \"foobar\"\n       :build-phid \"carbar\")\n\n\n      ;; We'll immediately send the message in this case\n      (update-diff-status company 123 :fail)\n\n      (is (equal '(:fail) res)))))\n\n(test update-status-twice\n  (with-fixture state ()\n    (let ((res nil))\n      (cl-mock:if-called '%actually-update-status\n                         (lambda (phab self &key status-map)\n                           (push (second (car status-map)) res)))\n      (cl-mock:if-called 'send-restart\n                         (lambda (&rest args)\n                           (error \"send-restart should not be called\")))\n      (%update-build\n       :diff 123\n       :revision 321\n       :target-phid \"foobar\"\n       :build-phid \"carbar\")\n\n\n      ;; We'll immediately send the message in this case\n      (update-diff-status company 123 :fail)\n      (is (equal '(:fail) res))\n\n      (cl-mock:if-called '%actually-update-status\n                         (lambda (phab self &key status-map)\n                           (error \"should not be called\"))\n                         :at-start t)\n      (let ((res))\n        (cl-mock:if-called 'send-restart\n                           (lambda (&rest args)\n                             (push t res))\n                           :at-start t)\n\n        (update-diff-status company 123 :pass)\n        (is (equal '(t) res))\n        (is-true (needs-sync-p (get-only-build-info))))\n\n      (setf res nil)\n\n      (cl-mock:if-called '%actually-update-status\n                         (lambda (phab self &key status-map)\n                           (push (second (first status-map)) res))\n                         :at-start t)\n      (cl-mock:if-called 'send-restart\n                         (lambda (&rest args)\n                           (error \"send-restart should not be called\"))\n                         :at-start t)\n      (%update-build\n       :diff 123\n       :revision 321\n       :target-phid \"foobar2\"\n       :build-phid \"carbar2\")\n      (is (equal '(:pass) res))\n      (is-false (needs-sync-p (get-only-build-info)))\n      (is (equal \"foobar2\" (target-phid (get-only-build-info))))\n      (is (equal \"carbar2\" (build-phid (get-only-build-info)))))))\n"
  },
  {
    "path": "src/screenshotbot/phabricator/test-diff-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/phabricator/test-diff-promoter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/promote-api\n                #:plugin-promoter)\n  (:import-from #:screenshotbot/phabricator/plugin\n                #:phabricator-plugin)\n  (:import-from #:screenshotbot/phabricator/diff-promoter\n                #:phabricator-promoter)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/phabricator/builds\n                #:*build-info-index*\n                #:build-info)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:push-remote-check\n                #:promoter-pull-id)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/phabricator/test-diff-promoter)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*build-info-index* (make-hash-table :test #'equal)))\n   (with-test-store ()\n     (let ((promoter (make-instance 'phabricator-promoter))\n           (company (make-instance 'company)))\n       (&body)))))\n\n(test plugin-promoter-happy-path\n  (with-fixture state ()\n   (assert-that\n    (plugin-promoter (make-instance 'phabricator-plugin))\n    (has-typep 'phabricator-promoter))))\n\n(test promoter-pull-id-for-phabricator\n  (with-fixture state ()\n    (make-instance 'build-info\n                   :company company\n                   :diff 20\n                   :revision 5)\n    (let ((run (make-recorder-run\n                :company company\n                :phabricator-diff-id \"20\")))\n      (is (eql 5 (promoter-pull-id promoter run))))))\n\n(test push-remote-check-succeeds-quietly-if-no-diff-id-is-presnt\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :commit-hash \"abcd\"\n                :company company\n                :phabricator-diff-id nil))\n          (check (make-instance 'screenshotbot/abstract-pr-promoter::check\n                                :sha \"abcd\"\n                                :status :accepted\n                                :key \"test-check\"\n                                :title \"Test Check\")))\n      (finishes\n        (push-remote-check promoter run check)))))\n"
  },
  {
    "path": "src/screenshotbot/plan.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/plan\n  (:use #:cl)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:current-user\n                #:user)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:plan\n   #:company-plan))\n(in-package :screenshotbot/plan)\n\n(defclass plan ()\n  ()\n  (:documentation \"base class for all plans. Default OSS plan\"))\n\n(defvar *plan* (make-instance 'plan))\n\n(defmethod company-plan (company installation)\n  \"Get the plan for the current user\"\n  *plan*)\n\n(defun plan (&optional (company (current-company)))\n  (company-plan\n   company\n   (installation)))\n"
  },
  {
    "path": "src/screenshotbot/plugin/.gitignore",
    "content": "build\n.gradle"
  },
  {
    "path": "src/screenshotbot/plugin/build.gradle",
    "content": "buildscript {\n    repositories {\n        jcenter()\n        google()\n        mavenLocal()\n    }\n}\n\nplugins {\n    id 'java'\n    id 'signing'\n}\n\nrepositories {\n    google()\n}\n\napply plugin: \"java-gradle-plugin\"\napply plugin: 'maven-publish'\n\n\ngroup 'io.screenshotbot'\nversion '0.1.1'\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation \"com.android.tools.build:gradle:7.1.0\"\n    implementation files('libs/lispcalls.jar')\n}\n\nsigning {\n    useGpgCmd()\n}\n\njar {\n    from {\n        zipTree(\"libs/lispcalls.jar\")\n    }\n}\n\ngradlePlugin {\n    plugins {\n        simplePlugin {\n            id = 'io.screenshotbot.plugin'\n            implementationClass = \"io.screenshotbot.plugin.ScreenshotbotPlugin\"\n        }\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/plugin/gradle/gradle-mvn-push.gradle",
    "content": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\napply plugin: 'maven'\napply plugin: 'signing'\n\ndef isReleaseBuild() {\n  return !VERSION_NAME.contains(\"SNAPSHOT\")\n}\n\ndef getRepositoryUsername() {\n  return findProperty('repositoryUsername') ? property('repositoryUsername') : \"\"\n}\n\ndef getRepositoryPassword() {\n  return findProperty('repositoryPassword') ? property('repositoryPassword') : \"\"\n}\n\ndef configurePom(pom) {\n  pom.groupId = GROUP\n  pom.artifactId = POM_ARTIFACT_ID\n  pom.version = VERSION_NAME\n\n  pom.project {\n    name POM_NAME\n    packaging POM_PACKAGING\n    description POM_DESCRIPTION\n    url POM_URL\n\n    scm {\n      url POM_SCM_URL\n      connection POM_SCM_CONNECTION\n      developerConnection POM_SCM_DEV_CONNECTION\n    }\n\n    licenses {\n      license {\n        name POM_LICENCE_NAME\n        url POM_LICENCE_URL\n        distribution POM_LICENCE_DIST\n      }\n    }\n\n    developers {\n      developer {\n        id POM_DEVELOPER_ID\n        name POM_DEVELOPER_NAME\n      }\n    }\n  }\n}\n\nafterEvaluate { project ->\n  uploadArchives {\n    repositories {\n      mavenDeployer {\n        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }\n\n        repository(url: \"https://oss.sonatype.org/service/local/staging/deploy/maven2/\") {\n          authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n        }\n        snapshotRepository(url: \"https://oss.sonatype.org/content/repositories/snapshots/\") {\n          authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n        }\n\n        configurePom(pom)\n      }\n    }\n  }\n\n  task installArchives(type: Upload) {\n    configuration = configurations.archives\n    repositories {\n      mavenDeployer {\n        repository url: \"file://${System.properties['user.home']}/.m2/repository\"\n        configurePom(pom)\n      }\n    }\n  }\n\n  signing {\n    required { isReleaseBuild() && gradle.taskGraph.hasTask(\"uploadArchives\") }\n    sign configurations.archives\n  }\n\n  if (property('POM_PACKAGING') == 'aar') {\n    task androidJavadocs(type: Javadoc) {\n      source = android.sourceSets.main.java.srcDirs\n      classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n      exclude \"**/BUCK\"\n      if (JavaVersion.current().isJava8Compatible()) {\n        options.addStringOption('Xdoclint:none', '-quiet')\n      }\n    }\n\n    task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {\n      classifier = 'javadoc'\n      from androidJavadocs.destinationDir\n    }\n\n    task androidSourcesJar(type: Jar) {\n      classifier = 'sources'\n      from android.sourceSets.main.java.sourceFiles\n    }\n\n    artifacts {\n      archives androidSourcesJar\n      archives androidJavadocsJar\n    }\n  } else {\n    task sourcesJar(type: Jar, dependsOn: classes) {\n      classifier = 'sources'\n      from sourceSets.main.allSource\n    }\n\n    task javadocJar(type: Jar, dependsOn: javadoc) {\n      classifier = 'javadoc'\n      from javadoc.destinationDir\n    }\n\n    artifacts {\n      archives sourcesJar\n      archives javadocJar\n    }\n  }\n}\n"
  },
  {
    "path": "src/screenshotbot/plugin/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.3.3-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "src/screenshotbot/plugin/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "src/screenshotbot/plugin/src/main/java/io/screenshotbot/plugin/RecordFacebookTask.java",
    "content": "package io.screenshotbot.plugin;\n\nimport com.android.build.gradle.api.TestVariant;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.TaskAction;\nimport com.android.build.gradle.api.*;\nimport com.android.build.gradle.*;\nimport org.gradle.api.*;\nimport org.gradle.process.*;\nimport javax.inject.Inject;\n\npublic class RecordFacebookTask extends DefaultTask {\n\n    private TestedExtension androidExtension;\n\n    private ExecOperations execOperations;\n    private TestVariant variant;\n    private String testApplicationId;\n    private ScreenshotbotPlugin.Extension extension;\n\n    @Inject\n    public RecordFacebookTask(ExecOperations execOperations) {\n        this.execOperations = execOperations;\n        setGroup(\"Screenshotbot Tasks\");\n    }\n\n    public void init(TestVariant variant,\n                     TestedExtension androidExtension,\n                     ScreenshotbotPlugin.Extension extension) {\n        //dependsOn(variant.getConnectedInstrumentTestProvider());\n        this.androidExtension = androidExtension;\n        this.variant = variant;\n        this.extension = extension;\n    }\n\n    public String ensureString(String s) {\n        if (s == null) {\n            return \"\";\n        }\n        return s;\n    }\n\n    @TaskAction\n    public void record() {\n        /*\n          Might be useful in the future, but it gives null for the moment:\n          androidExtension.getAdbOptions().getInstallOptions()\n         */\n\n        String channel = extension.getChannel();\n        if (channel == null) {\n            channel = variant.getApplicationId();\n        }\n\n        SbNative.callFn(execOperations, \"record-facebook-task\",\n                        androidExtension.getAdbExe().toString(),\n                        variant.getApplicationId(),\n                        channel,\n                        ensureString(extension.getApiKey()),\n                        ensureString(extension.getApiSecret()));\n        ScreenshotbotPlugin.println(\"hello world: \" +\n                                    androidExtension.getAdbExe().toString()\n                                    );\n\n\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/plugin/src/main/java/io/screenshotbot/plugin/SbNative.java",
    "content": "package io.screenshotbot.plugin;\n\nimport com.lispworks.LispCalls;\nimport org.gradle.process.*;\n\npublic class SbNative {\n\n    public static String getExe() {\n        return \"/home/arnold/builds/web/build/asdf-cache/lw-8.0.1-linux-x64/src/screenshotbot/sdk/java\";\n    }\n\n    public static void callFn(ExecOperations execOperations, Object... args) {\n        var ret = execOperations.exec((execSpec) -> {\n                execSpec.setExecutable(getExe());\n                execSpec.args(args);\n                execSpec.setStandardOutput(System.out);\n            });\n        ret.assertNormalExitValue();\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/plugin/src/main/java/io/screenshotbot/plugin/ScreenshotbotPlugin.java",
    "content": "package io.screenshotbot.plugin;\n\nimport org.gradle.api.*;\nimport com.android.build.gradle.api.*;\nimport com.android.build.gradle.*;\nimport com.lispworks.LispCalls;\n\npublic class ScreenshotbotPlugin implements Plugin<Project>{\n\n    public static class Extension {\n        private String channel;\n        public void setChannel(String channel) {\n            this.channel = channel;\n        }\n\n        public String getChannel() {\n            return channel;\n        }\n\n        private String mApiKey;\n\n        public void setApiKey(String apiKey) {\n            mApiKey = apiKey;\n        }\n\n        public String getApiKey() {\n            return mApiKey;\n        }\n\n        private String mApiSecret;\n\n        public void setApiSecret(String apiSecret) {\n            mApiSecret = apiSecret;\n        }\n\n        public String getApiSecret() {\n            return mApiSecret;\n        }\n\n    }\n\n    private Extension screenshotbotExtension;\n\n\n    public static void println(String str) {\n        System.out.println(str);\n    }\n\n    @Override\n    public void apply(Project project) {\n        var extensions = project.getExtensions();\n\n        screenshotbotExtension = extensions.create(\"screenshotbot\",\n                                                   Extension.class);\n\n        var plugins = project.getPlugins();\n        if (plugins.hasPlugin(\"com.facebook.testing.screenshot\")) {\n            extensions.configure(TestedExtension.class,\n                                 (androidExtension) -> {\n                                     generateFacebookTasks(project, androidExtension);\n                                 });\n        }\n    }\n\n    private TestedExtension getProjectExtension(Project project) {\n        var extensions = project.getExtensions();\n        var plugins = project.getPlugins();\n        if (plugins.hasPlugin(\"com.android.application\")) {\n            return extensions.findByType(AppExtension.class);\n        } else if (plugins.hasPlugin(\"com.android.library\")) {\n            return extensions.findByType(LibraryExtension.class);\n        } else {\n            throw new IllegalArgumentException(\"Screenshotbot plugin requires either an Android Application or Android Library plugin\");\n        }\n    }\n\n    private String capitalize(String input) {\n        return input.substring(0, 1).toUpperCase() +\n            input.substring(1);\n    }\n\n    public void generateFacebookTasks(Project project, TestedExtension androidExtension) {\n        androidExtension.getTestVariants().all((variant) -> {\n                // Create a screenshotbot task\n                var name = \"recordScreenshotbot\" + capitalize(variant.getName());\n                println(\"creating task: \" + name);\n                project.getTasks().register(name,\n                                            RecordFacebookTask.class)\n                    .configure((it) -> {\n                            it.init(variant,\n                                    androidExtension,\n                                    screenshotbotExtension);\n                        });\n            });\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/plugin.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :screenshotbot/plugin\n    (:use #:cl #:alexandria)\n  (:export #:plugin\n           #:plugin-parse-repo))\n(in-package :screenshotbot/plugin)\n\n(defclass plugin ()\n  ())\n\n(defgeneric plugin-parse-repo (plugin company repo-str)\n  (:documentation \"Parses a repo string and generates a corresponding repo\n  object. Return NIL if it doesn't look like a repo that this plugin\n  can parse.\"))\n\n(defmethod plugin-parse-repo (plugin company repo-str)\n  nil)\n"
  },
  {
    "path": "src/screenshotbot/promote-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/promote-api\n    (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:plugin\n                #:plugins\n                #:promoters)\n  (:export\n   #:maybe-send-tasks\n   #:maybe-promote\n   #:promoter\n   #:register-promoter\n   #:plugin-promoter\n   #:list-promoters\n   #:start-promotion-thread\n   #:unregister-promoter))\n(in-package :screenshotbot/promote-api)\n\n(defclass promoter ()\n  ())\n\n(defmethod print-object ((promoter promoter)\n                         out)\n  (format out \"#<~a>\"\n          (string (type-of promoter))))\n\n(defgeneric maybe-send-tasks (promoter run))\n\n(defgeneric maybe-promote (promoter run))\n\n(defvar *promoters* nil)\n\n(defmethod plugin-promoter ((plugin plugin))\n  nil)\n\n(defun register-promoter (name)\n  \"Register a promoter. NAME is the class name for a promoter. Note that\n if you need a custom function to create the promoter, consider using\n PLUGIN-PROMOTER instead.\"\n  (assert (symbolp name))\n  (pushnew name *promoters*))\n\n(defun unregister-promoter (name)\n  \"Unregister a promoter. This is only useful for updating an existing\n promoter in a live image.\"\n  (removef *promoters* name))\n\n(defun list-promoters ()\n  (restart-case\n      (remove-if #'null\n                   (append\n                    (loop for plugin in (plugins (installation))\n                          collect (plugin-promoter plugin))\n                    (mapcar #'make-instance\n                              *promoters*)))\n    (recreate-promoter-list ()\n      (list-promoters))))\n"
  },
  {
    "path": "src/screenshotbot/raft-redirect.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/raft-redirect\n    (:use :cl)\n  #+ (and lispworks linux)\n  (:import-from #:bknr.cluster/server\n                #:wait-for-leader\n                #:leader-id\n                #:leaderp)\n  (:import-from #:hunchentoot-extensions\n                #:forward-request)\n  (:import-from #:core/rpc/rpc\n                #:id-to-ip)\n  (:export\n   #:maybe-redirect-to-leader)\n  (:local-nicknames (#:hex #:hunchentoot-extensions)))\n(in-package :screenshotbot/raft-redirect)\n\n(defun assert-not-in-redirect-loop (request)\n  (when (hunchentoot:header-in :x-raft-forwarded request)\n    (log:error \"Request forwarding loop detected\")\n    (setf (hunchentoot:return-code*) 502)\n    (hunchentoot:abort-request-handler)))\n\n(defun exclude-from-raft-redirect-p (request)\n  \"Returns T if the request path should not be forwarded to the leader.\n   Paths that handle their own routing or need direct node access should be excluded.\"\n  (let ((path (hunchentoot:script-name request)))\n    (or (str:starts-with-p \"/raft-state\" path)\n        (str:starts-with-p \"/intern/rpc\" path))))\n\n(defun maybe-redirect-to-leader (request)\n  #+bknr.cluster\n  (when (and\n         (boundp 'bknr.datastore:*store*)\n         (not (leaderp bknr.datastore:*store*))\n         (not (exclude-from-raft-redirect-p request)))\n    \n    (cond\n      ((not (wait-for-leader bknr.datastore:*store* :timeout 10))\n       (error \"Did not get a leader in time, we're probably in a read-only state\"))\n      ((leaderp bknr.datastore:*store*)\n       ;; If we've become the leader while we were waiting then just\n       ;; continue.\n       (values))\n      (t\n       (assert-not-in-redirect-loop request)\n\n       (let* ((leader-id (leader-id bknr.datastore:*store*))\n              (leader-ip (id-to-ip leader-id))\n              (port (hunchentoot:acceptor-port hunchentoot:*acceptor*))\n              (url (format nil \"http://~a:~a\" leader-ip port)))\n         (log:debug \"About to forward ~a ~a to ~a\"\n                   (hunchentoot:request-method hunchentoot:*request*)\n                   (hunchentoot:script-name hunchentoot:*request*)\n                   url)\n         (hex:forward-request url\n                              :request request\n                              :keep-current-host t\n                              :extra-headers '((:x-raft-forwarded . \"1\"))))))))\n"
  },
  {
    "path": "src/screenshotbot/replay/AWS-SELENIUM-IAM-POLICY.md",
    "content": "# IAM Policy for AWS Selenium Provider\n\n## Overview\n\nThis document describes the IAM policy required for the `aws-selenium-provider` to securely manage EC2 instances for Selenium testing. The policy uses **tag-based access control** to restrict permissions to only instances created by the selenium provider.\n\n## Security Model\n\nAll EC2 instances created by the `aws-selenium-provider` are tagged with:\n- `ManagedBy=aws-selenium-provider`\n- `Temporary=true`\n\nThe IAM policy restricts `ec2:TerminateInstances` permission to ONLY instances with these tags, preventing accidental termination of permanent infrastructure.\n\n## IAM Policy\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"AllowRunInstancesWithTags\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"ec2:RunInstances\",\n      \"Resource\": [\n        \"arn:aws:ec2:*:*:instance/*\",\n        \"arn:aws:ec2:*:*:volume/*\"\n      ],\n      \"Condition\": {\n        \"StringEquals\": {\n          \"aws:RequestTag/ManagedBy\": \"aws-selenium-provider\",\n          \"aws:RequestTag/Temporary\": \"true\"\n        }\n      }\n    },\n    {\n      \"Sid\": \"AllowRunInstancesOnOtherResources\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"ec2:RunInstances\",\n      \"Resource\": [\n        \"arn:aws:ec2:*:*:subnet/*\",\n        \"arn:aws:ec2:*:*:network-interface/*\",\n        \"arn:aws:ec2:*:*:security-group/*\",\n        \"arn:aws:ec2:*:*:key-pair/*\",\n        \"arn:aws:ec2:*::image/*\"\n      ]\n    },\n    {\n      \"Sid\": \"AllowTerminateOnlyManagedInstances\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"ec2:TerminateInstances\",\n      \"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"ec2:ResourceTag/ManagedBy\": \"aws-selenium-provider\",\n          \"ec2:ResourceTag/Temporary\": \"true\"\n        }\n      }\n    },\n    {\n      \"Sid\": \"AllowDescribeInstances\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ec2:DescribeInstances\",\n        \"ec2:DescribeInstanceStatus\",\n        \"ec2:DescribeTags\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"AllowCreateTagsOnLaunch\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"ec2:CreateTags\",\n      \"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"ec2:CreateAction\": \"RunInstances\"\n        }\n      }\n    },\n    {\n      \"Sid\": \"AllowPassRoleForInstanceProfile\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"iam:PassRole\",\n      \"Resource\": \"arn:aws:iam::ACCOUNT-ID:role/YOUR-SELENIUM-INSTANCE-ROLE-NAME\"\n    }\n  ]\n}\n```\n\n## Configuration Steps\n\n### 1. Replace Placeholders\n\nIn the policy above, replace:\n- `ACCOUNT-ID` - Your AWS account ID\n- `YOUR-SELENIUM-INSTANCE-ROLE-NAME` - The name of the IAM role that will be attached to the created EC2 instances\n\n### 2. Create IAM Role for the Application\n\nIf running on EC2 (recommended):\n\n```bash\n# Create trust policy\ncat > trust-policy.json <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [{\n    \"Effect\": \"Allow\",\n    \"Principal\": {\n      \"Service\": \"ec2.amazonaws.com\"\n    },\n    \"Action\": \"sts:AssumeRole\"\n  }]\n}\nEOF\n\n# Create IAM role\naws iam create-role \\\n  --role-name SeleniumProviderRole \\\n  --assume-role-policy-document file://trust-policy.json\n\n# Create and attach the policy\naws iam put-role-policy \\\n  --role-name SeleniumProviderRole \\\n  --policy-name SeleniumProviderPolicy \\\n  --policy-document file://selenium-provider-policy.json\n\n# Create instance profile\naws iam create-instance-profile \\\n  --instance-profile-name SeleniumProviderInstanceProfile\n\n# Add role to instance profile\naws iam add-role-to-instance-profile \\\n  --instance-profile-name SeleniumProviderInstanceProfile \\\n  --role-name SeleniumProviderRole\n\n# Attach to your EC2 instance\naws ec2 associate-iam-instance-profile \\\n  --instance-id i-your-instance-id \\\n  --iam-instance-profile Name=SeleniumProviderInstanceProfile\n```\n\n### 3. Verify the Policy\n\nTest that the policy works correctly:\n\n```bash\n# Should succeed - terminate a managed instance\naws ec2 terminate-instances --instance-ids i-managed-instance-id\n\n# Should fail with AccessDenied - terminate a permanent instance\naws ec2 terminate-instances --instance-ids i-permanent-instance-id\n```\n\n## Policy Breakdown\n\n### RunInstances Restrictions\n\nThe policy enforces that all instances created MUST have the required tags. This prevents privilege escalation where someone could create an instance without tags and then remove the tag-based restrictions.\n\n```json\n\"Condition\": {\n  \"StringEquals\": {\n    \"aws:RequestTag/ManagedBy\": \"aws-selenium-provider\",\n    \"aws:RequestTag/Temporary\": \"true\"\n  }\n}\n```\n\n### TerminateInstances Restrictions\n\nThe critical security control - only instances with BOTH tags can be terminated:\n\n```json\n\"Condition\": {\n  \"StringEquals\": {\n    \"ec2:ResourceTag/ManagedBy\": \"aws-selenium-provider\",\n    \"ec2:ResourceTag/Temporary\": \"true\"\n  }\n}\n```\n\n### DescribeInstances\n\nRead-only permissions have no resource restrictions as they cannot cause harm:\n\n```json\n\"Action\": [\n  \"ec2:DescribeInstances\",\n  \"ec2:DescribeInstanceStatus\",\n  \"ec2:DescribeTags\"\n],\n\"Resource\": \"*\"\n```\n\n## Additional Hardening (Optional)\n\nFor even stricter security, you can add additional conditions:\n\n### Restrict to Specific Subnet\n\n```json\n{\n  \"Condition\": {\n    \"StringEquals\": {\n      \"ec2:Subnet\": \"arn:aws:ec2:us-east-1:123456789012:subnet/subnet-xxxxx\"\n    }\n  }\n}\n```\n\n### Restrict to Specific VPC\n\n```json\n{\n  \"Condition\": {\n    \"StringEquals\": {\n      \"ec2:Vpc\": \"arn:aws:ec2:us-east-1:123456789012:vpc/vpc-xxxxx\"\n    }\n  }\n}\n```\n\n### Restrict Instance Types\n\n```json\n{\n  \"Condition\": {\n    \"StringEquals\": {\n      \"ec2:InstanceType\": [\"m7a.medium\", \"m7a.large\"]\n    }\n  }\n}\n```\n\n## Troubleshooting\n\n### Error: \"You are not authorized to perform this operation\"\n\nThis usually means:\n1. The IAM role is not attached to your EC2 instance\n2. The instance you're trying to terminate doesn't have the required tags\n3. The IAM policy has a typo in the ARN or condition\n\n### Instances Created Without Tags\n\nIf instances are being created without tags:\n1. Check that the `--tag-specifications` parameter is correct in the code\n2. Verify the IAM policy is enforcing the `aws:RequestTag` condition\n\n### Cannot Create Instances\n\nIf you get permission denied when creating instances:\n1. Ensure all resources (subnet, security groups, AMI, key pair) exist\n2. Check that the IAM role specified in `iam-profile` exists and the policy allows `iam:PassRole`\n3. Verify the `AllowRunInstancesOnOtherResources` statement includes all necessary resource types\n\n## References\n\n- [AWS IAM Policy Elements: Condition](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html)\n- [Controlling Access to EC2 Resources Using Tags](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/control-access-with-tags.html)\n- [IAM Roles for Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)\n"
  },
  {
    "path": "src/screenshotbot/replay/aws-selenium-provider.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n;;;;\n;;;; AWS Selenium Provider - Creates temporary EC2 instances for Selenium testing\n;;;;\n;;;; IMPORTANT: For production deployment, this requires proper IAM permissions.\n;;;; See AWS-SELENIUM-IAM-POLICY.md in this directory for the complete IAM policy\n;;;; that uses tag-based access control to restrict instance termination.\n\n(defpackage :screenshotbot/replay/aws-selenium-provider\n  (:use #:cl)\n  (:import-from #:screenshotbot/replay/services\n                #:selenium-server\n                #:call-with-selenium-server)\n  (:import-from #:util/request\n                #:http-request))\n(in-package :screenshotbot/replay/aws-selenium-provider)\n\n(defclass aws-selenium-provider ()\n  ((security-groups :initarg :security-groups\n                    :reader security-groups\n                    :documentation \"List of security group IDs\")\n   (subnet-id :initarg :subnet-id\n              :reader subnet-id)\n   (iam-profile :initarg :iam-profile\n                :reader iam-profile)\n   (proxy-binary :initarg :proxy-binary\n                 :reader proxy-binary)\n   (ssh-key-name :initarg :ssh-key-name\n                 :reader ssh-key-name)\n   (ami :initarg :ami\n        :initform \"ami-0030d3967006e88b5\"\n        :reader ami)))\n\n(auto-restart:with-auto-restart ()\n (defmethod get-instance-ipv6 (instance-id)\n   \"Get the IPv6 address of an AWS EC2 instance\"\n   (let* ((result (uiop:run-program\n                   (list \"aws\" \"ec2\" \"describe-instances\"\n                         \"--instance-ids\" instance-id\n                         \"--output\" \"json\")\n                   :output :string))\n          (response (yason:parse result))\n          (reservations (gethash \"Reservations\" response))\n          (instances (gethash \"Instances\" (first reservations)))\n          (instance (first instances))\n          (network-interfaces (gethash \"NetworkInterfaces\" instance)))\n     (loop for interface in network-interfaces\n           for ipv6-addresses = (gethash \"Ipv6Addresses\" interface)\n           when (and ipv6-addresses (> (length ipv6-addresses) 0))\n             return (gethash \"Ipv6Address\" (elt ipv6-addresses 0))\n           finally (error \"No IPv6 address found for instance ~A\" instance-id)))))\n\n(defun get-status (ipv6)\n  (let* ((url (format nil \"http://[~a]:5004/status\" ipv6))\n         (response\n           (handler-case\n               (http-request\n                url)\n             (error (e)\n               (log:warn \"Got error: ~a\" e)\n               nil))))\n    (log:info \"Made request to ~a\" url)\n    response))\n\n(auto-restart:with-auto-restart ()\n  (defmethod after-machine-is-up ((self aws-selenium-provider)\n                                  fn &key type ipv6)\n    \n    (funcall fn\n             (make-instance 'selenium-server\n                            :host ipv6\n                            :port 4444\n                            :type type\n                            :squid-proxy (format nil \"squid:3128\")\n                            \n                            ;; Not providing :selenium-hub\n                            ))))\n\n(defmethod call-with-selenium-server ((self aws-selenium-provider)\n                                      fn &key type)\n  (let ((instance-id (aws-new-instance self)))\n    (log:info \"Started new instance: ~a\" instance-id)\n    (unwind-protect\n         (let ((ipv6 (get-instance-ipv6 instance-id)))\n           (block wait\n            (loop for i from 0 to 600\n                  do\n                     (let ((response (get-status ipv6)))\n                       (log:info \"Got response: ~a\" response)\n                       (when (equal  (str:trim response) \"OK\")\n                         (return-from wait nil))\n                       (sleep 1))))\n           (after-machine-is-up self fn :type type :ipv6 ipv6))\n      \n      (aws-terminate-instance instance-id))))\n\n\n(defmethod aws-new-instance ((self aws-selenium-provider))\n  \"Create a new AWS EC2 instance for selenium testing\"\n  (let* ((image-id (ami self)) \n         (instance-type \"m7a.medium\")\n         (subnet-id (subnet-id self))\n         (user-data (format nil \"#!/bin/bash\naws s3 cp ~a ./proxy.gz\ngunzip ./proxy.gz\nchmod a+x ./proxy\n./proxy --address \\\"::\\\" --prepare-machine > /tmp/output &\n\" (proxy-binary self)))\n         (user-data-file (format nil \"/tmp/user-data-~A.sh\" (get-universal-time))))\n    ;; Write user-data to temporary file\n    (with-open-file (stream user-data-file :direction :output :if-exists :supersede)\n      (write-string user-data stream))\n    (unwind-protect\n        (let* ((run-result (uiop:run-program\n                            (append\n                             (list \"aws\" \"ec2\" \"run-instances\"\n                                   \"--image-id\" image-id\n                                   \"--count\" \"1\"\n                                   \"--instance-type\" instance-type\n                                   \"--key-name\" (ssh-key-name self)\n                                   \"--security-group-ids\")\n                             (security-groups self)\n                             (list \"--subnet-id\" subnet-id\n                                   \"--iam-instance-profile\" (format nil \"Name=~A\" (iam-profile self))\n                                   \"--tag-specifications\" \"ResourceType=instance,Tags=[{Key=ManagedBy,Value=aws-selenium-provider},{Key=Temporary,Value=true}]\"\n                                   \"--block-device-mappings\" \"[{\\\"DeviceName\\\":\\\"/dev/xvda\\\",\\\"Ebs\\\":{\\\"VolumeSize\\\":24,\\\"VolumeType\\\":\\\"gp3\\\"}}]\"\n                                   \"--user-data\" (format nil \"file://~A\" user-data-file)\n                                   \"--output\" \"json\"))\n                           :error-output t\n                           :output :string))\n               (response (yason:parse run-result))\n               (instances (gethash \"Instances\" response))\n               (instance-id (gethash \"InstanceId\" (first instances))))\n          (aws-wait-for-instance-running self instance-id)\n          instance-id)\n      ;; Cleanup temporary file\n      (when (probe-file user-data-file)\n        (delete-file user-data-file)))))\n\n(auto-restart:with-auto-restart ()\n (defmethod aws-wait-for-instance-running ((self aws-selenium-provider) instance-id)\n   \"Wait for the EC2 instance to be in running state\"\n   (loop with max-attempts = 30\n         with attempt = 0\n         do (let* ((result (uiop:run-program\n                            (list \"aws\" \"ec2\" \"describe-instances\"\n                                  \"--instance-ids\" instance-id\n                                  \"--output\" \"json\")\n                            :output :string))\n                   (response (yason:parse result))\n                   (reservations (gethash \"Reservations\" response))\n                   (instances (gethash \"Instances\" (first reservations)))\n                   (instance (first instances))\n                   (state (gethash \"Name\" (gethash \"State\" instance))))\n              (when (string= state \"running\")\n                (return t))\n              (when (>= (incf attempt) max-attempts)\n                (error \"Instance ~A failed to start within timeout\" instance-id))\n              (sleep 10)))))\n\n\n(defmethod aws-terminate-instance (instance-id)\n  \"Terminate an AWS EC2 instance\"\n  (log:info \"Terminating instance: ~a\" instance-id)\n  (let ((result (uiop:run-program\n                 (list \"aws\" \"ec2\" \"terminate-instances\"\n                       \"--instance-ids\" instance-id\n                       \"--output\" \"json\")\n                 :error-output t\n                 :output :string)))\n    (let* ((response (yason:parse result))\n           (terminating-instances (gethash \"TerminatingInstances\" response))\n           (instance (first terminating-instances))\n           (current-state (gethash \"Name\" (gethash \"CurrentState\" instance))))\n      (log:info \"Instance ~a state: ~a\" instance-id current-state)\n      current-state)))\n\n"
  },
  {
    "path": "src/screenshotbot/replay/browser-config.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/browser-config\n  (:use #:cl)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:dimensions\n   #:height\n   #:width\n   #:browser-config\n   #:browser-config-name\n   #:browser-type\n   #:mobile-emulation))\n(in-package :screenshotbot/replay/browser-config)\n\n(defclass dimensions ()\n  ((height :initarg :height\n           :json-type :number\n           :json-key \"height\"\n           :reader height)\n   (width :initarg :width\n          :json-type :number\n          :json-key \"width\"\n          :reader width))\n  (:metaclass ext-json-serializable-class))\n\n(defclass browser-config ()\n  ((name :initarg :name\n         :json-type :string\n         :json-key \"name\"\n         :reader browser-config-name)\n   (type :initarg :type\n         :json-type :string\n         :json-key \"type\"\n         :reader browser-type)\n   (dimensions :initarg :dimensions\n               :json-type (or null dimensions)\n               :json-key  \"dimensions\"\n               :initform nil\n               :reader dimensions)\n   (mobile-emulation :initarg :mobile-emulation\n                     :json-type (or null :string)\n                     :json-key \"mobileEmulation\"\n                     :initform nil\n                     :reader mobile-emulation))\n  (:metaclass ext-json-serializable-class))\n"
  },
  {
    "path": "src/screenshotbot/replay/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/core\n  (:use #:cl)\n  (:import-from #:json-mop\n                #:json-serializable\n                #:json-serializable-class)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:util/misc\n                #:not-null!\n                #:or-setf)\n  (:import-from #:util/threading\n                #:with-extras\n                #:safe-interrupt-checkpoint)\n  (:import-from #:util/digests\n                #:sha256-file)\n  (:import-from #:util/lru-cache\n                #:with-cache-file)\n  (:import-from #:util/http-cache\n                #:parse-max-age)\n  (:import-from #:alexandria\n                #:when-let\n                #:assoc-value)\n  (:import-from #:util/request\n                #:proxy-engine\n                #:http-success-response?\n                #:http-request-impl\n                #:engine)\n  (:import-from #:util/engines\n                #:handle-misbehaving-engine)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:screenshotbot/replay/browser-config\n                #:browser-config)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:screenshotbot/replay/css-tokenizer\n                #:consume-string-token\n                #:token-value\n                #:function-token\n                #:url-token\n                #:consume-ident-like-token)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:dns-client #:org.shirakumo.dns-client))\n  (:export\n   #:rewrite-css-urls\n   #:*replay-logs*\n   #:tmpdir\n   #:asset-file\n   #:asset-file\n   #:asset-response-headers\n   #:asset-status\n   #:load-url\n   #:snapshot\n   #:load-url-into\n   #:snapshot-asset-file\n   #:uuid\n   #:assets\n   #:url\n   #:http-header-name\n   #:http-header-value\n   #:snapshot-request\n   #:browser-configs\n   #:channel-name\n   #:root-assets\n   #:pull-request\n   #:main-branch\n   #:repo-url\n   #:merge-base\n   #:commit\n   #:branch-hash\n   #:request-counter\n   #:call-with-request-counter\n   #:write-replay-log\n   #:context\n   #:asset-file-name\n   #:parse-max-age\n   #:request-engine))\n(in-package :screenshotbot/replay/core)\n\n(defconstant +empty-headers+ nil)\n\n(defvar *replay-logs* *terminal-io*)\n\n(defvar *timeout* 15)\n\n(defparameter *fetch-sleep-time* 0.1\n  \"Time to sleep between actual requests, to throttle it.\")\n\n(defvar *stack* nil)\n\n(defclass snapshot-request ()\n  ((snapshot :initarg :snapshot\n             :json-key \"snapshot\"\n             :json-type snapshot\n             :reader snapshot)\n   (browser-configs :initarg :browser-configs\n                    :json-key \"browserConfigs\"\n                    :json-type (:list browser-config)\n                    :reader browser-configs)\n   (channel-name :initarg :channel-name\n                 :json-key \"channel\"\n                 :json-type :string\n                 :reader channel-name)\n   (pull-request :initarg :pull-request\n                 :json-key \"pullRequestUrl\"\n                 :json-type (or null :string)\n                 :initform nil\n                 :reader pull-request)\n   (main-branch :initarg :main-branch\n                :json-key \"mainBranch\"\n                :json-type (or null :string)\n                :initform nil\n                :reader main-branch)\n   (repo-url :initarg :repo-url\n             :json-key \"repoUrl\"\n             :json-type (or null :string)\n             :initform nil\n             :reader repo-url)\n   (branch-hash :initarg :branch-hash\n                :json-key \"branchHash\"\n                :json-type (or null :string)\n                :initform nil\n                :reader branch-hash)\n   (commit :initarg :commit\n           :json-key \"commit\"\n           :json-type (or null :string)\n           :initform nil\n           :reader commit)\n   (sdk-flags :initform nil\n              :json-key \"sdkFlags\"\n              :json-type (or null :string)\n              :initarg :sdk-flags\n              :reader snapshot-request-sdk-flags)\n   (merge-base :initarg :merge-base\n               :json-key \"mergeBase\"\n               :json-type (or null :string)\n               :initform nil\n               :reader merge-base))\n  (:metaclass ext-json-serializable-class))\n\n(defclass context ()\n  ((seen :initform (make-hash-table :test #'equal)\n         :reader context-seen)\n   (custom-css :initarg :custom-css\n               :initform nil\n               :reader custom-css))\n  (:documentation \"A crawler context. Keeps track of all DFS nodes\n  we're looking at, and also allows us to customize the node\n  processing for specific websites\"))\n\n(defclass http-header ()\n  ((name :initarg :name\n         :json-type :string\n         :json-key \"name\"\n         :reader http-header-name)\n   (value :initarg :value\n          :json-type :string\n          :json-key \"value\"\n          :reader http-header-value))\n  (:metaclass ext-json-serializable-class))\n\n(defmethod initialize-instance :after ((self http-header) &key name)\n  (when (symbolp name)\n    (setf (slot-value self 'name) (string-downcase (string name)))))\n\n(defclass asset ()\n  ((file :initarg :file\n         :json-type :string\n         :json-key \"file\"\n         :reader asset-file)\n   (url :initarg :url\n        :json-type :string\n        :json-key \"url\"\n        :reader url)\n   (status :initarg :status\n           :json-type :number\n           :json-key \"status\"\n           :reader asset-status)\n   (stylesheetp :initarg :stylesheetp\n                :json-type :bool\n                :json-key \"isStylesheet\"\n                :initform nil\n                :reader stylesheetp)\n   (response-headers :initarg :response-headers\n                     :json-type (:list http-header)\n                     :json-key \"responseHeaders\"\n                     :reader asset-response-headers))\n  (:metaclass ext-json-serializable-class))\n\n(defmethod asset-file-name ((asset asset))\n  (car (last (str:split \"/\" (asset-file asset)))))\n\n(defmethod print-object ((asset asset) out)\n  (format out \"#<ASSET ~a ~a>\" (asset-file asset) (url asset)))\n\n(defmethod initialize-instance :after ((self asset) &key &allow-other-keys)\n  )\n\n(defmethod class-persistent-slots ((self asset))\n  '(file status stylesheetp response-headers))\n\n(defclass snapshot ()\n  ((assets :initform nil\n           :initarg :assets\n           :json-type (:list asset)\n           :json-key \"assets\"\n           :accessor assets)\n   (uuid :initform (format nil \"~a\" (uuid:make-v4-uuid))\n         :json-type :string\n         :json-key \"uuid\"\n         :reader uuid)\n   (tmpdir :initarg :tmpdir\n           :accessor tmpdir)\n   (root-files :initarg :root-files\n               :initform nil\n               :documentation \"DEPRECATED: See T621, as we're migrating this away to root-urls\")\n   (root-urls :accessor root-urls\n              :json-type (:list :string)\n              :json-key \"rootUrls\"\n              :initarg :root-urls\n              :initform nil\n              :documentation \"The URLs that were used for the root\")\n   (request-counter :accessor request-counter\n                    :initform 0\n                    :documentation \"When the snapshot is being served,\n                    this keeps track of the total number of current\n                    requests. This allows us to wait until all assets\n                    are served before taking screenshots.\"))\n  (:metaclass ext-json-serializable-class))\n\n(defvar *request-counter-lock* (bt:make-lock \"request-counter-lock\"))\n\n(defclass request-engine (handle-misbehaving-engine\n                          proxy-engine\n                          engine)\n  ())\n\n\n(defgeneric replay-external-request-engine (installation)\n  (:method (installation)\n    (make-instance 'request-engine))\n  (:documentation \"The engine to use for making requests to external websites. In\nparticular, we plan to use a Squid proxy for prod. (and is probably\nshipped by the time you're reading this.)\"))\n\n(defmethod call-with-request-counter ((snapshot snapshot) fn)\n  (unwind-protect\n       (progn\n         (bt:with-lock-held (*request-counter-lock*)\n           (incf (request-counter snapshot)))\n         (funcall fn))\n    (bt:with-lock-held (*request-counter-lock*)\n      (decf (request-counter snapshot)))))\n\n(defun root-assets (snapshot)\n  (let ((url-to-asset-map\n          (reduce\n           (lambda (map asset)\n             (declare (type asset asset)\n                      (type string url))\n             (fset:with map (url asset) asset))\n           (assets snapshot)\n           :initial-value (fset:empty-map))))\n    (reverse\n     (loop for url in (root-urls snapshot)\n           collect\n           (not-null! (fset:lookup url-to-asset-map url))))))\n\n(defmethod process-node (context node snapshot url)\n  (values))\n\n(defun fix-asset-headers (headers)\n  \"Fix some bad headers\"\n  (loop for (k . v) in headers\n        if (string-equal \"Access-Control-Allow-Origin\" k)\n          collect (cons k \"*\")\n        else\n          collect (cons k v)))\n\n(define-condition recursive-asset-error (error)\n  ((url :initarg :url)))\n\n(defmethod call-with-fetch-asset ((context context)\n                                  fn type tmpdir &key url\n                                                   (snapshot (error \"must provide snapshot\")))\n  (when (member url *stack* :test #'equal)\n    (error 'recursive-asset-error\n            :url url))\n  (let ((*stack* (cons url *stack*)))\n   (multiple-value-bind (remote-stream status response-headers)\n       (funcall fn)\n     ;; dex:get should handle redirects, but just in case:\n     (with-open-stream (input remote-stream)\n       (assert (not (member status `(301 302))))\n       (fix-asset-headers response-headers)\n\n       (uiop:with-temporary-file (:pathname p :stream out :directory tmpdir :keep t\n                                  :element-type '(unsigned-byte 8)\n                                  :direction :io)\n         (uiop:copy-stream-to-stream input out :element-type '(unsigned-byte 8))\n         (file-position out 0)\n         (finish-output out)\n         (close out)\n\n         (write-asset p type\n                      :tmpdir tmpdir\n                      :url url\n                      :snapshot snapshot\n                      :response-headers response-headers\n                      :status status))))))\n\n\n(defun write-replay-log (message &rest args)\n  (unless (eql *terminal-io* *replay-logs*)\n    (local-time:format-timestring\n     *replay-logs*\n     (local-time:now) :format `(\"[\" (:hour 2) \":\" (:min 2) \":\" (:sec 2) \"] \"))\n    (apply #'format *replay-logs* message args)\n    (format *replay-logs* \"~%\")\n    (finish-output *replay-logs*)))\n\n(defun hash-file (file)\n  (sha256-file file))\n\n(defun write-asset (p type &key tmpdir\n                             url\n                             (snapshot (error \"must provide snapshot\"))\n                             response-headers\n                             stylesheetp\n                             status)\n  (let ((hash (ironclad:byte-array-to-hex-string (hash-file p))))\n    (uiop:rename-file-overwriting-target\n     p (make-pathname\n        :name hash\n        :type type\n        :defaults tmpdir))\n    (make-instance\n     'asset\n     :file (format nil\n                   \"/snapshot/~a/assets/~a\"\n                   (uuid snapshot)\n                   (make-pathname :name hash :type type\n                                  :directory `(:relative)))\n     :url (cond\n            ((stringp url)\n             url)\n            (t\n             (quri:render-uri url)))\n     :response-headers (loop for (k . v) in response-headers\n                             collect\n                             (make-instance 'http-header\n                                            :name k\n                                            :value v))\n     :stylesheetp stylesheetp\n     :status status)))\n\n(defun fix-malformed-url (url)\n  (let* ((url (quri:render-uri (quri:uri url)))\n         (url (str:replace-all \" \" \"%20\" url))\n         (url (str:replace-all \"[\" \"%5B\" url))\n         (url (str:replace-all \"]\" \"%5D\" url)))\n    url))\n\n(defun remove-unwanted-headers (response-headers)\n  (remove-if\n   (lambda (key)\n     (or\n      (member key '(:content-security-policy\n                    ;; The next two are just not\n                    ;; needed, so we'll let them be\n                    ;; GC-ed earlier\n                    :connection\n                    :alt-svc))\n      (char-equal #\\x (elt (string key) 0))))\n   response-headers\n   :key #'car))\n\n(defun blacklisted-domain-p (domain)\n  (and\n   (not (equal \"cdn.screenshotbot.io\" domain))\n   (or\n    (cl-ppcre:scan \"\\\\d*\\\\.\\\\d*\\\\.\\\\d*\\\\d*\" domain)\n    (cl-ppcre:scan \".*\\\\.screenshotbot.io\" domain)\n    (cl-ppcre:scan \".*localhost.*\" domain))))\n\n(define-condition blacklisted-domain (error)\n  ())\n\n(define-condition blacklisted-ip (error)\n  ())\n\n(defun blacklisted-ip-p (host)\n  #+nil\n  (block nil\n    (let ((ip (dns-client:resolve host :type :a)))\n      (when (str:starts-with-p \"127.\" ip)\n        (return t))\n      ;; This is hard-coded for now, but this is our internal IP\n      ;; range.\n      (when (str:starts-with-p \"172.30.\" ip)\n        (return t)))\n    (let ((ip (dns-client:resolve host :type :aaaa)))\n      (when ip\n        (return\n          (or\n           (equal \"::1\" ip)))))))\n\n(defun running-in-sdk-p ()\n  (not (boundp '*installation*)))\n\n(defmethod validate-url ((url puri:uri) &key for-redirect)\n  (validate-url\n   (quri:uri (puri:render-uri url nil))\n   :for-redirect for-redirect))\n\n(defmethod validate-url ((url quri:uri) &key for-redirect)\n  (let* ((scheme (quri:uri-scheme url)))\n    (cond\n      ((equal \"file\" scheme)\n       (error \"file:// urls are not supported\"))\n      ((and\n        (not (running-in-sdk-p))\n        (blacklisted-domain-p (quri:uri-host url)))\n       (error 'blacklisted-domain))\n      ((blacklisted-ip-p (quri:uri-host url))\n       (error 'blacklisted-ip))\n      ((and\n        (not (running-in-sdk-p))\n        (not (member (quri:uri-port url)\n                     '(80 443 nil))))\n       (error 'blacklisted-domain))\n      ((and\n        for-redirect\n        (not (str:s-member '(\"http\" \"https\") scheme)))\n       (error \"Unsupported schemed ~a during redirect\" scheme)))))\n\n(defmethod http-request-impl ((engine request-engine)\n                              url &rest args)\n  (let* ((url (fix-malformed-url url))\n         (url (quri:uri url))\n         (scheme (quri:uri-scheme url)))\n    (validate-url url)\n    (cond\n      ((member scheme (list \"chrome-extension\") :test #'string-equal)\n       ;; respond with an empty file\n       (values\n        (make-string-input-stream \"\")\n        200\n        +empty-headers+))\n      ((or (equal \"https\" scheme)\n           (equal \"http\" scheme))\n\n       (maybe-sleep-before-fetch url)\n\n       (log:info \"Fetching: ~a\" url)\n       (write-replay-log \"Fetching: ~a\" url)\n\n       (handler-bind (#-screenshotbot-oss\n                      (drakma::http-redirecting (lambda (condition)\n                                                  (validate-url\n                                                   (drakma::http-redirect-location condition)\n                                                   :for-redirect t))))\n        (multiple-value-bind (remote-stream status response-headers)\n            (call-next-method)\n          (unless (http-success-response? status)\n            (write-replay-log \"Warning: request failed with ~a\" status))\n          (let ((response-headers\n                  (remove-unwanted-headers response-headers)))\n            (push `(:x-original-url . ,(quri:render-uri url)) response-headers)\n            (values remote-stream status response-headers)))))\n      (t\n       (error \"unsupported scheme: ~a\" scheme)))))\n\n(defun maybe-sleep-before-fetch (url)\n  (sleep *fetch-sleep-time*))\n\n(defun http-get-without-cache (url &key (force-binary t)\n                                     (force-string nil))\n  (declare (ignore force-string))\n\n  (util/request:http-request (format nil \"~a\" url)\n                             :want-stream t :force-binary force-binary\n                             :read-timeout *timeout*\n                             :accept \"image/webp,*/*\"\n                             :connection-timeout *timeout*\n                             :engine (replay-external-request-engine\n                                      ;; See test http-get-works-without-installation\n                                      (ignore-errors *installation*))))\n\n(defvar *cache* nil)\n\n(defun http-cache-dir ()\n  (let ((object-store-sym (find-symbol \"*OBJECT-STORE*\" \"UTIL\")))\n    (unless object-store-sym\n      (error \"HTTP-CACHE-DIR not supported in SDK, most likely a bug: please reach out to arnold@screenshotbot.io\"))\n    (let ((store-dir (symbol-value object-store-sym)))\n      (let ((dir (path:catdir\n                  store-dir\n                  \"cache/replay-http-cache/\")))\n        (ensure-directories-exist dir)\n        (log:debug \"Using cache dir for http: ~A\" dir)\n        dir))))\n\n(defun %lru-cache ()\n  (util/misc:or-setf\n   *cache*\n   (make-instance 'util/lru-cache:lru-cache\n                  :dir (http-cache-dir)\n                  :max-size \"2GB\")))\n\n(defclass remote-response ()\n  ((status :initarg :status\n           :reader remote-response-status)\n   (request-time :initarg :request-time\n                 :initform 0\n                 :reader request-time)\n   (headers :initarg :headers\n            :reader remote-response-headers)))\n\n(defmethod max-age ((self remote-response))\n  (let ((cache-control (assoc-value (remote-response-headers self) :cache-control)))\n    (log:debug \"Got cache-control: ~a\" cache-control)\n    (cond\n      (cache-control\n       (parse-max-age cache-control))\n      (t\n       -1))))\n\n(defun remove-quotes (str)\n  (cond\n    ((member (elt str 0)\n             (list #\\' #\\\"))\n     (str:substring 1 -1 str))\n    (t\n     str)))\n\n(defmethod guess-external-format ((info remote-response)\n                                  file-name)\n  (or\n   (a:when-let ((content-type (assoc-value (remote-response-headers info) :content-type)))\n     (loop for part in (str:split \";\" content-type)\n           do\n              (destructuring-bind (key &optional value) (str:split \"=\" (str:trim part))\n                (when (string-equal (str:trim key) \"charset\")\n                  (let* ((charset (remove-quotes (str:downcase (str:trim value)))))\n                    (return\n                      (cond\n                        ((or\n                          (string= charset \"utf-8\")\n                          (string= charset \"utf8\"))\n                         :utf-8)\n                        ((or\n                          (string= charset \"iso-8859-1\")\n                          (string= charset \"latin-1\"))\n                         :latin-1)\n                        (t\n                         (error \"unspported charset ~a\" charset)))))))))\n   (ignore-errors\n    (guess-content-type-from-file file-name))\n   :latin-1))\n\n(defun guess-content-type-from-file (file)\n  (with-open-file (stream file :direction :input)\n    (let ((html (plump:parse stream)))\n      (labels ((charset (tag)\n                 (plump:attribute tag \"charset\"))\n               (find-meta (tag)\n                 (cond\n                   ((and\n                     (typep tag 'plump:element)\n                     (string-equal \"meta\" (plump:tag-name tag))\n                     (charset tag))\n                    (cond\n                      ((string-equal \"utf-8\" (charset tag))\n                       :utf-8)))\n                   ((typep tag 'plump:nesting-node)\n                    (loop for child across (plump:children tag)\n                          for charset = (find-meta child)\n                          if charset\n                            return charset)))))\n        (find-meta html)))))\n\n(defun http-get (url &key (force-binary t)\n                       (force-string nil)\n                       (cache t))\n  (with-extras ((\"fetch-url\" url))\n    (let* ((url (quri:uri url))\n           (cache-key (format\n                       nil \"~a-~a-v6\"\n                       (ironclad:byte-array-to-hex-string (ironclad:digest-sequence :sha256 (flexi-streams:string-to-octets  (quri:render-uri url))))\n                       force-binary)))\n      (with-cache-file (output (%lru-cache) (make-pathname :name cache-key :type \"data\"))\n        (with-cache-file (info (%lru-cache) (make-pathname :name cache-key :type \"info-v3\"))\n          (flet ((read-cached ()\n                   (let ((info (cl-store:restore info)))\n                     (log:debug \"Opening from cache: ~a\" output)\n                     (values\n                      (open output :element-type (cond\n                                                   (force-binary\n                                                    '(unsigned-byte 8))\n                                                   (force-string\n                                                    'character)\n                                                   (t\n                                                    'character))\n                                   :external-format (or\n                                                     (unless force-binary\n                                                       (guess-external-format info output))\n                                                     :default))\n                      (remote-response-status info)\n                      (remote-response-headers info))))\n                 (good-cache-p (file)\n                   (uiop:file-exists-p file)))\n            (cond\n              ((and cache (every #'good-cache-p (list info output))\n                    (let ((info (cl-store:restore info)))\n                      (and\n                       (slot-boundp info 'request-time) #| Compatibility with old cache|#\n                       (max-age info)\n                       (< (get-universal-time)\n                          (+ (max-age info) (request-time info))))))\n               (write-replay-log \"Using cached asset for ~a\" url)\n               (read-cached))\n              (t\n               ;; we're not cached yet\n               (multiple-value-bind (stream %status %headers)\n                   (handler-case\n                       (http-get-without-cache url :force-binary t)\n                     (puri:uri-parse-error (e)\n                       (write-replay-log \"Invalid URL: ~a, will treat as 500\" url)\n                       (values\n                        (make-string-input-stream \"\")\n                        500\n                        +empty-headers+)))\n                 (with-open-file (output output :element-type '(unsigned-byte 8)\n                                                :if-exists :supersede\n                                                :direction :output)\n                   (uiop:copy-stream-to-stream stream output :element-type '(unsigned-byte 8)))\n                 (cl-store:store (make-instance 'remote-response\n                                                :status %status\n                                                :request-time (get-universal-time)\n                                                :headers %headers)\n                                 info))\n               (log:debug \"Caching for the first time ~a\" output)\n               (read-cached)))))))))\n\n(with-auto-restart ()\n (defmethod fetch-asset ((context context) url tmpdir (snapshot snapshot))\n   \"Fetches the url into a file <hash>.<file-type> in the tmpdir.\"\n   (let ((pathname (ignore-errors (quri:uri-file-pathname url))))\n     (call-with-fetch-asset\n      context\n      (lambda ()\n        (log:debug \"v1 of fetch-asset\")\n        (http-get url))\n      (cond\n        (pathname\n         (pathname-type pathname))\n        (t\n         nil))\n      tmpdir\n      :url url\n      :snapshot snapshot))))\n\n(defparameter *regexs*\n  (uiop:read-file-lines\n   (asdf:system-relative-pathname :screenshotbot \"replay/replay-regex.txt\")))\n\n(defun should-rewrite-url-p (url)\n  (let ((untouched-schemes (list \"data:\" \"moz-extension:\")))\n    (loop for scheme in untouched-schemes\n            always\n            (not (str:starts-with-p scheme url)))))\n\n(defun rewrite-css-urls (css fn)\n  (destructuring-bind (property-matcher url-matcher) *regexs*\n    (declare (ignore property-matcher))\n    (let ((url-scanner (cl-ppcre:create-scanner url-matcher)))\n      (let ((output (make-string-output-stream))\n            (last-appended 0))\n\n        (cl-ppcre:do-scans (match-start match-end\n                            reg-starts reg-ends\n                            url-scanner\n                            css)\n          (declare (ignore reg-starts reg-ends))\n          (let ((stream (make-string-input-stream css match-start)))\n            (let ((token (consume-ident-like-token stream)))\n              (flet ((move-forward (new-last-appended)\n                       (write-string css output :start last-appended :end new-last-appended)\n                       (setf last-appended new-last-appended)))\n               (etypecase token\n                 (url-token\n                  ;; When we see a url(foobar) [no quotes], we're\n                  ;; replacing the whole expression.\n                  (let ((url (token-value token)))\n                    (when (should-rewrite-url-p url)\n                      (move-forward match-start)\n                      (format output \"url(~a)\" (funcall fn url))\n                      (incf last-appended (file-position stream)))))\n                 (function-token\n                  ;; When we see a url(\"foobar\"), we're only replacing\n                  ;; the \"foobar\" with the new URL\n                  (assert (equal \"url\" (token-value token)))\n                  (let* ((start (file-position stream))\n                         (starting-code-point (read-char stream))\n                         (string-token (consume-string-token stream starting-code-point))\n                         (url (token-value string-token)))\n                    (when (should-rewrite-url-p url)\n                      (move-forward (+ match-start start))\n                      (format output \"~a\" (funcall fn url))\n                      (incf last-appended (- (file-position stream) start))))))))))\n\n        (write-string css output :start last-appended :end (length css))\n        (get-output-stream-string output))\n\n      #+nil\n      (cl-ppcre:regex-replace-all\n       url-scanner\n       css\n       (lambda (match start end match-start match-end reg-starts reg-ends)\n         (declare (ignore start end match-start match-end))\n         (let ((url (subseq match (elt reg-starts 0)\n                            (elt reg-ends 0))))\n           (cond\n             ((not (should-rewrite-url-p url))\n              ;; we never wan't to rewrite data urls\n              url)\n             (t\n              (format nil \"url(~a)\" (funcall fn url))))))))))\n\n(defmethod rewrite-css ((context context) (snapshot snapshot) url css)\n  (flet ((rewrite-url (this-url)\n           (let ((full-url (quri:merge-uris (quri:uri this-url) url)))\n             (asset-file-name\n              (push-asset context snapshot full-url nil)))))\n    (let* ((css (rewrite-css-urls css #'rewrite-url)))\n      css)))\n\n\n(defmethod fetch-css-asset ((context context) (snapshot snapshot) url tmpdir)\n  (log:debug \"calling from fetch-css-asset\")\n  (multiple-value-bind (remote-stream status response-headers) (http-get url :force-binary nil)\n    (with-open-stream (remote-stream remote-stream)\n     (uiop:with-temporary-file (:stream out :pathname p :type \"css\"\n                                :directory (tmpdir snapshot))\n       (let* ((css (uiop:slurp-stream-string remote-stream))\n              (css (rewrite-css context snapshot url css)))\n         (write-string css out)\n         (finish-output out))\n       (close out) ;; required for windows\n       (write-asset p \"css\"\n                    :tmpdir (tmpdir snapshot)\n                    :url url\n                    :status status\n                    :snapshot snapshot\n                    :stylesheetp t\n                    :response-headers response-headers)))))\n\n(defmethod push-asset ((context context) (snapshot snapshot) url stylesheetp)\n  (let ((rendered-uri (quri:render-uri url)))\n   (let ((stylesheetp (or stylesheetp (str:ends-with-p \".css\" rendered-uri))))\n     (loop for asset in (assets snapshot)\n           for this-url = (url asset)\n           do (check-type this-url string))\n     (loop for asset in (assets snapshot)\n           if (string= (url asset) rendered-uri)\n             do\n                (return asset)\n           finally\n              (let ((asset (cond\n                             (stylesheetp\n                              (fetch-css-asset context snapshot url (tmpdir snapshot)))\n                             (t\n                              (fetch-asset context url (tmpdir snapshot) snapshot)))))\n                (push asset (assets snapshot))\n                (return asset))))))\n\n(defun read-srcset (srcset)\n  (flet ((read-spaces (stream)\n           (loop while (let ((ch (peek-char nil stream nil)))\n                         (and ch\n                              (str:blankp ch)))\n                     do (read-char stream nil))))\n   (let ((stream (make-string-input-stream srcset)))\n     (read-spaces stream)\n     (loop while (peek-char nil stream nil)\n           collect\n           (cons\n            (str:trim\n             (with-output-to-string (url)\n               (loop for ch = (read-char stream nil)\n                     while (and ch\n                                (not (str:blankp ch)))\n                     do\n                        (write-char ch url))\n               (read-spaces stream)))\n            (str:trim\n             (with-output-to-string (width)\n               (loop for ch = (read-char stream nil)\n                     while (and ch\n                                (not (eql #\\, ch)))\n                     do\n                        (write-char ch width))\n               (read-spaces stream))))))))\n\n(defmethod process-node ((context context)\n                         (node plump:element) snapshot root-url)\n  (let ((name (plump:tag-name node)))\n    (labels ((? (test-name)\n               (string-equal name test-name))\n             (safe-replace-attr (name fn)\n               \"Replace the attribute if it exists with the output from the function\"\n               (let ((val (plump:attribute node name)))\n                 (when (and val\n                            (should-rewrite-url-p val))\n                   (let* ((ref-uri val)\n                          (uri (quri:merge-uris\n                                (quri:uri ref-uri)\n                                (quri:uri root-url))))\n                     (setf (plump:attribute node name) (funcall fn uri))))))\n             (replace-attr (name &optional stylesheetp)\n               (safe-replace-attr\n                name\n                (lambda (uri)\n                  (let* ((asset (push-asset context snapshot uri stylesheetp))\n                         (res (asset-file-name asset)))\n                    res))))\n             (parse-intrinsic (x)\n               (parse-integer (str:replace-all \"w\" \"\" x)))\n             (replace-srcset (name)\n               (ignore-errors\n                (let ((srcset (plump:attribute node name)))\n                  (when srcset\n                    (let* ((data (read-srcset srcset))\n                           (smallest-width\n                             (loop for (nil . width) in data\n                                   minimizing (parse-intrinsic width)))\n                           (max-width (max 500 smallest-width)))\n\n                      (let ((final-attr\n                              (str:join\n                               \",\"\n                               (loop for (url . width) in  data\n                                     for uri = (quri:merge-uris (quri:uri url) (quri:uri root-url))\n                                     if (<= (parse-intrinsic width) max-width)\n                                       collect\n                                       (let ((asset (push-asset context snapshot uri nil)))\n                                         (str:join \" \"\n                                                   (list\n                                                    (asset-file-name asset)\n                                                    width)))))))\n                        (setf (plump:attribute node name)\n                              final-attr))))))))\n      (cond\n       ((or (? \"img\")\n            (? \"source\"))\n        (replace-attr \"src\")\n        (plump:remove-attribute node \"srcset\")\n        ;;(replace-srcset \"srcset\")\n        ;; webpack? Maybe?\n        ;;(replace-attr \"data-src\")\n        ;;(replace-srcset \"data-srcset\")\n        (plump:remove-attribute node \"decoding\")\n        (setf (plump:attribute node \"loading\") \"eager\")\n        (plump:remove-attribute node \"data-gatsby-image-ssr\"))\n       ((? \"picture\")\n        (replace-srcset \"srcset\"))\n       ((? \"iframe\")\n        (setf (plump:attribute node \"src\")\n              \"/iframe-not-supported\"))\n       ((? \"style\")\n        (loop for child across (plump:children node)\n              do (setf (plump:text child)\n                       (rewrite-css context\n                                    snapshot\n                                    root-url\n                                    (plump:text child)))))\n       ((? \"video\")\n        ;; autoplay videos mess up screenshots\n        (plump:remove-attribute node \"autoplay\"))\n       ((? \"link\")\n        (let ((rel (plump:attribute node \"rel\")))\n         (cond\n           ((string-equal \"canonical\" rel)\n            ;; do nothing\n            (values))\n           ((string-equal \"dns-prefetch\" rel)\n            (values))\n           (t\n            (replace-attr \"href\" (string-equal \"stylesheet\" rel)))))\n        (plump:remove-attribute node \"integrity\"))\n       ((? \"script\")\n        (replace-attr \"src\"))\n       ((? \"input\")\n        (plump:remove-attribute node \"autofocus\"))\n       ((? \"body\")\n        (let ((old-class (or (plump:attribute node \"class\") \"\")))\n          (setf (plump:attribute node \"class\") (format nil \"~a screenshotbot\" old-class)))))))\n\n  ;; Fix any style attributes\n  (when-let ((style (plump:attribute node \"style\")))\n    (setf (plump:attribute node \"style\")\n          (rewrite-css context snapshot root-url style)))\n\n  (call-next-method))\n\n\n(defmethod process-node ((context context) (node plump:nesting-node) snapshot root-url)\n  (loop for child across (plump:children node)\n        do (process-node context child snapshot root-url))\n  (call-next-method))\n\n(defmethod process-node :before ((context context) node snapshot root-url)\n  ;;(log:info \"Looking at: ~S\" node)\n  )\n\n(defmethod add-css ((context context) html)\n  \"Add the replay css overrides to the html\"\n  (a:when-let (head (ignore-errors (elt (lquery:$ html \"head\") 0)))\n    (let ((link (plump:make-element\n                        head \"link\")))\n      (setf (plump:attribute link \"href\") \"/css/replay.css\")\n      (setf (plump:attribute link \"rel\") \"stylesheet\"))\n    (when (custom-css context)\n      (let ((style (plump:make-element head \"style\")))\n        (plump:make-text-node style (custom-css context))))))\n\n(defun safe-plump-serialize (&rest args)\n  (flet ((ignore-handler (e)\n           (declare (ignore e))\n           (invoke-restart 'cl:continue)))\n    (handler-bind ((plump-dom:invalid-xml-character #'ignore-handler)\n                   (plump-dom:discouraged-xml-character #'ignore-handler))\n      (apply #'plump:serialize args))))\n\n(with-auto-restart (:retries 0 :sleep 10)\n  (defmethod load-url-into ((context context) snapshot url tmpdir\n                            &rest args\n                            &key\n                              &allow-other-keys)\n    (safe-interrupt-checkpoint)\n    (with-open-stream (content (http-get url :force-string t\n                                             :force-binary nil))\n\n      (apply\n       #'parse-after-fetching context\n       snapshot\n       url\n       tmpdir\n       :content content\n       args))))\n\n(with-auto-restart ()\n  (defmethod parse-after-fetching ((context context)\n                                   snapshot\n                                   url\n                                   tmpdir\n                                   &key actual-url\n                                     content)\n    (file-position content 0) ;; in case of a restart\n    (let* ((url (quri:uri url))\n           (html (plump:parse content)))\n      (process-node context html snapshot\n                    ;; If we pass an actual-url, then we should use the\n                    ;; actual URL to figure out where to fetch assets\n                    ;; from\n                    (or actual-url url))\n      (add-css context html)\n\n      #+nil\n      (error \"got html: ~a\"\n             (with-output-to-string (s)\n               (plump:serialize html s)))\n      (uiop:with-temporary-file (:direction :io :stream tmp :element-type '(unsigned-byte 8))\n        (let ((root-asset (call-with-fetch-asset\n                           context\n                           (lambda ()\n                             (log:debug \"v2 of fetch asset\")\n                             (safe-plump-serialize html (flexi-streams:make-flexi-stream tmp :element-type 'character :external-format :utf-8))\n                             (file-position tmp 0)\n                             (let ((headers `((:content-type . \"text/html; charset=UTF-8\")\n                                              (:cache-control . \"no-cache\"))))\n                               (values tmp 200 headers)))\n                           \"html\"\n                           tmpdir\n                           :url url\n                           :snapshot snapshot)))\n          (push\n           (quri:render-uri url)\n           (root-urls snapshot))\n          (push root-asset\n                (assets snapshot))))\n      snapshot)))\n\n(defmethod snapshot-asset-file ((snapshot snapshot)\n                                (asset asset))\n  (let* ((script-name (asset-file asset))\n         (input-file (make-pathname\n                      :name (pathname-name script-name)\n                      :type (pathname-type script-name)\n                      :defaults (tmpdir snapshot))))\n    input-file))\n\n(defun load-url (url tmpdir)\n  (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n    (load-url-into (make-instance 'context) snapshot url tmpdir)))\n"
  },
  {
    "path": "src/screenshotbot/replay/css-tokenizer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/css-tokenizer\n  (:use #:cl)\n  (:export\n   #:token\n   #:token-value\n   #:url-token\n   #:bad-url-token\n   #:string-token\n   #:bad-string-token\n   #:function-token\n   #:consume-url-token\n   #:consume-string-token\n   #:consume-ident-like-token\n   #:consume-ident-sequence\n   #:whitespacep\n   #:non-printable-p\n   #:newline-p\n   #:hex-digit-p\n   #:letter-p\n   #:non-ascii-p\n   #:ident-start-p\n   #:digit-p\n   #:ident-code-point-p\n   #:consume-escaped-code-point\n   #:valid-escape-p))\n(in-package :screenshotbot/replay/css-tokenizer)\n\n;;;; As of writing this is an incomplete tokenizer, but based off of\n;;;; https://www.w3.org/TR/css-syntax-3/. Good news for us, the CSS\n;;;; tokenizer is quite simple and doesn't need a CFG.\n\n(defclass token ()\n  ((value :initarg :value\n          :reader token-value)))\n\n(defclass url-token (token)\n  ())\n\n(defclass bad-url-token (token)\n  ())\n\n(defclass string-token (token)\n  ())\n\n(defclass bad-string-token (token)\n  ())\n\n(defclass function-token (token)\n  ())\n\n(defun whitespacep (ch)\n  #+lispworks\n  (lw:whitespace-char-p ch)\n  #-lispworks\n  (str:blankp (string ch)))\n\n(defun consume-whitespace (stream)\n  (loop while (whitespacep (peek-char nil stream nil))\n        do (read-char stream)))\n\n(defun newline-p (ch)\n  \"Check if character is a newline (U+000A LINE FEED, U+000D CARRIAGE RETURN, or U+000C FORM FEED)\"\n  (or (char= ch #\\Newline)\n      (char= ch #\\Return)\n      (char= ch #\\Page)))\n\n(defun non-printable-p (ch)\n  \"Check if character is non-printable per CSS spec.\nNon-printable: U+0000-U+0008, U+000B, U+000E-U+001F, U+007F\"\n  (let ((code (char-code ch)))\n    (or (<= 0 code 8)\n        (= code 11)\n        (<= 14 code 31)\n        (= code 127))))\n\n(defun hex-digit-p (ch)\n  \"Check if character is a hexadecimal digit\"\n  (or (char<= #\\0 ch #\\9)\n      (char<= #\\A ch #\\F)\n      (char<= #\\a ch #\\f)))\n\n(defun letter-p (ch)\n  \"Check if character is a letter (A-Z or a-z)\"\n  (or (char<= #\\A ch #\\Z)\n      (char<= #\\a ch #\\z)))\n\n(defun non-ascii-p (ch)\n  \"Check if character is non-ASCII (code point >= U+0080)\"\n  (>= (char-code ch) #x80))\n\n(defun ident-start-p (ch)\n  \"Check if character is an ident-start code point.\nAn ident-start code point is: a letter, a non-ASCII code point, or underscore (_)\"\n  (or (letter-p ch)\n      (non-ascii-p ch)\n      (char= ch #\\_)))\n\n(defun digit-p (ch)\n  \"Check if character is a digit (0-9)\"\n  (char<= #\\0 ch #\\9))\n\n(defun ident-code-point-p (ch)\n  \"Check if character is an ident code point.\nAn ident code point is: an ident-start code point, a digit, or hyphen (-)\"\n  (or (ident-start-p ch)\n      (digit-p ch)\n      (char= ch #\\-)))\n\n(defun valid-escape-p (stream)\n  \"Check if the next two code points form a valid escape.\nA valid escape is backslash followed by anything except newline.\"\n  (let ((ch1 (peek-char nil stream nil))\n        (ch2 nil))\n    (when (and ch1 (char= ch1 #\\\\))\n      ;; Read the backslash and peek at next char\n      (read-char stream)\n      (setf ch2 (peek-char nil stream nil))\n      ;; Put the backslash back\n      (unread-char ch1 stream)\n      (and ch2 (not (newline-p ch2))))))\n\n(defun consume-escaped-code-point (stream)\n  \"Consume an escaped code point. Assumes the backslash has already been consumed.\"\n  (let ((ch (read-char stream nil)))\n    (cond\n      ((null ch)\n       ;; EOF - return replacement character\n       (code-char #xFFFD))\n\n      ((hex-digit-p ch)\n       ;; Hex escape: consume up to 5 more hex digits\n       (let ((hex-string (string ch)))\n         (loop repeat 5\n               for next-ch = (peek-char nil stream nil)\n               while (and next-ch (hex-digit-p next-ch))\n               do (progn\n                    (setf hex-string (concatenate 'string hex-string (string (read-char stream))))))\n         ;; Consume optional whitespace after hex\n         (let ((next-ch (peek-char nil stream nil)))\n           (when (and next-ch (whitespacep next-ch))\n             (read-char stream)))\n         ;; Parse hex value\n         (let ((code-point (parse-integer hex-string :radix 16)))\n           (if (or (= code-point 0)\n                   (<= #xD800 code-point #xDFFF)\n                   (> code-point #x10FFFF))\n               (code-char #xFFFD)\n               (code-char code-point)))))\n\n      (t\n       ;; Any other character - return as-is\n       ch))))\n\n(defun consume-bad-url-remnants (stream)\n  \"Consume the remnants of a bad URL. Called after detecting a parse error.\"\n  (loop for ch = (read-char stream nil)\n        while ch\n        do (cond\n             ((char= ch #\\))\n              (return))\n             ((char= ch #\\\\)\n              ;; Check for valid escape\n              (let ((next-ch (peek-char nil stream nil)))\n                (when (and next-ch (not (newline-p next-ch)))\n                  ;; Valid escape - consume it\n                  (consume-escaped-code-point stream)))))))\n\n(defun consume-ident-sequence (stream)\n  \"Consume an ident sequence from the stream.\nSection 4.3.11 of the standard\"\n  (let ((result (make-array 0 :element-type 'character :adjustable t :fill-pointer 0)))\n    (loop\n      (let ((ch (peek-char nil stream nil)))\n        (cond\n          ;; Ident code point - consume and append\n          ((and ch (ident-code-point-p ch))\n           (read-char stream)\n           (vector-push-extend ch result))\n\n          ;; Valid escape sequence - consume escaped code point\n          ((and ch (char= ch #\\\\))\n           (read-char stream) ; consume backslash\n           (let ((next-ch (peek-char nil stream nil)))\n             (cond\n               ;; EOF after backslash - put it back and stop\n               ((null next-ch)\n                (unread-char #\\\\ stream)\n                (return result))\n               ;; Newline after backslash - not valid escape, put it back and stop\n               ((newline-p next-ch)\n                (unread-char #\\\\ stream)\n                (return result))\n               ;; Valid escape - consume it\n               (t\n                (vector-push-extend (consume-escaped-code-point stream) result)))))\n\n          ;; Anything else - stop\n          (t\n           (return result)))))\n    result))\n\n(defun consume-ident-like-token (stream)\n  \"Consume an ident-like token (identifier, function, or url).\nSection 4.3.4 of the standard.\nImplemented:\n  - url(...) with unquoted URLs -> url-token\n  - url(...) with quoted URLs -> function-token\nUnimplemented (error):\n  - Other function tokens\n  - Identifier tokens\"\n  (let ((string (consume-ident-sequence stream)))\n    ;; Check for url function\n    (when (and (string-equal string \"url\")\n               (eql (peek-char nil stream nil) #\\())\n      ;; Consume the left parenthesis\n      (read-char stream)\n      ;; Consume whitespace\n      (consume-whitespace stream)\n      ;; Check the next character(s)\n      (let ((next-ch (peek-char nil stream nil)))\n        (cond\n          ;; If it's a quote, it's a function token\n          ;; The opening paren has been consumed, but the quoted string remains in the stream\n          ((or (eql next-ch #\\\")\n               (eql next-ch #\\'))\n           (return-from consume-ident-like-token\n             (make-instance 'function-token :value string)))\n\n          ;; Otherwise, consume as URL token\n          (t\n           (return-from consume-ident-like-token (consume-url-token stream))))))\n\n    ;; Check for regular function\n    (when (eql (peek-char nil stream nil) #\\()\n      (error \"Unimplemented: function token\"))\n\n    ;; Otherwise it's an identifier\n    (error \"Unimplemented: identifier token\")))\n\n(defun consume-url-token (stream)\n  \"This assumes that the initial `url(` has already been consumed.\nSection 4.3.6 of the standard\"\n  (let ((result (make-instance 'url-token :value \"\"))\n        (url-string (make-array 0 :element-type 'character :adjustable t :fill-pointer 0)))\n    (consume-whitespace stream)\n\n    (loop\n      (let ((ch (read-char stream nil)))\n        (cond\n          ;; Right parenthesis - done\n          ((and ch (char= ch #\\)))\n           (setf (slot-value result 'value) url-string)\n           (return result))\n\n          ;; EOF - parse error but return token\n          ((null ch)\n           (setf (slot-value result 'value) url-string)\n           (return result))\n\n          ;; Whitespace\n          ((whitespacep ch)\n           (consume-whitespace stream)\n           (let ((next-ch (peek-char nil stream nil)))\n             (cond\n               ;; Followed by ) or EOF - ok\n               ((or (null next-ch) (char= next-ch #\\)))\n                (when next-ch (read-char stream)) ; consume the )\n                (setf (slot-value result 'value) url-string)\n                (return result))\n               ;; Otherwise - bad URL\n               (t\n                (consume-bad-url-remnants stream)\n                (return (make-instance 'bad-url-token :value \"\"))))))\n\n          ;; Quote or left paren or non-printable - bad URL\n          ((or (char= ch #\\\")\n               (char= ch #\\')\n               (char= ch #\\()\n               (non-printable-p ch))\n           (consume-bad-url-remnants stream)\n           (return (make-instance 'bad-url-token :value \"\")))\n\n          ;; Backslash - check for valid escape\n          ((char= ch #\\\\)\n           (let ((next-ch (peek-char nil stream nil)))\n             (if (and next-ch (not (newline-p next-ch)))\n                 ;; Valid escape - consume it\n                 (vector-push-extend (consume-escaped-code-point stream) url-string)\n                 ;; Invalid escape - bad URL\n                 (progn\n                   (consume-bad-url-remnants stream)\n                   (return (make-instance 'bad-url-token :value \"\"))))))\n\n          ;; Any other character - append to URL\n          (t\n           (vector-push-extend ch url-string)))))))\n\n(defun consume-string-token (stream ending-code-point)\n  \"Consume a string token. The ending-code-point is the quote character that started the string.\nSection 4.3.5 of the standard\"\n  (let ((result (make-instance 'string-token :value \"\"))\n        (string-value (make-array 0 :element-type 'character :adjustable t :fill-pointer 0)))\n    (loop\n      (let ((ch (read-char stream nil)))\n        (cond\n          ;; Ending code point (matching quote) - done\n          ((and ch (char= ch ending-code-point))\n           (setf (slot-value result 'value) string-value)\n           (return result))\n\n          ;; EOF - parse error but return token\n          ((null ch)\n           (setf (slot-value result 'value) string-value)\n           (return result))\n\n          ;; Newline - parse error, reconsume and return bad-string-token\n          ((newline-p ch)\n           (unread-char ch stream)\n           (return (make-instance 'bad-string-token :value \"\")))\n\n          ;; Backslash - escape sequence\n          ((char= ch #\\\\)\n           (let ((next-ch (peek-char nil stream nil)))\n             (cond\n               ;; EOF after backslash - do nothing\n               ((null next-ch)\n                nil)\n\n               ;; Newline after backslash - consume it (escaped newline)\n               ((newline-p next-ch)\n                (read-char stream))\n\n               ;; Valid escape - consume escaped code point\n               (t\n                (vector-push-extend (consume-escaped-code-point stream) string-value)))))\n\n          ;; Any other character - append to string\n          (t\n           (vector-push-extend ch string-value)))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/replay/fixtures/sitemap-0.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"><url><loc>https://www.rollins.edu/academics/master-of-applied-behavior-analysis-and-clinical-science</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/biochemistry-molecular-biology</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/latin-american-caribbean-studies</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/asian-studies</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/master-of-liberal-studies</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/theatre-arts</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/social-entrepreneurship</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/religious-studies</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/physics</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/international-relations</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/history</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/english</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/education</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/biology</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/art-history</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/american-studies</loc><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://www.rollins.edu/academics/pre-health</loc><changefreq>daily</changefreq><priority>0.7</priority></url></urlset>\n"
  },
  {
    "path": "src/screenshotbot/replay/fixtures/sitemap-index.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"><sitemap><loc>https://www.rollins.edu/sitemap-0.xml</loc></sitemap></sitemapindex>\n"
  },
  {
    "path": "src/screenshotbot/replay/frontend.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/frontend\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/replay/frontend)\n  (:shadow #:log)\n  (:import-from #:screenshotbot/replay/browser-config\n                #:browser-config\n                #:dimensions\n                #:height\n                #:width\n                #:browser-type\n                #:mobile-emulation\n                #:browser-config-name)\n  (:import-from #:markup\n                #:write-html\n                #:deftag)\n  (:import-from #:screenshotbot/replay/core\n                #:uuid\n                #:*replay-logs*\n                #:load-url)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/webdriver\n                #:chrome\n                #:firefox\n                #:take-screenshot\n                #:with-webdriver)\n  (:import-from #:webdriver-client\n                #:http-post-check)\n  (:import-from #:screenshotbot/webdriver/impl\n                #:call-with-webdriver)\n  (:import-from #:screenshotbot/webdriver/screenshot\n                #:full-page-screenshot)\n  (:import-from #:screenshotbot/pro/replay/services\n                #:selenium-server\n                #:selenium-server-url)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:browser-config\n   #:dimensions\n   #:height\n   #:width\n   #:browser-type\n   #:mobile-emulation\n   #:browser-config-name\n   #:type))\n(in-package :screenshotbot/replay/frontend)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass screenshot ()\n  ((key :initarg :key\n        :reader screenshot-file-key)\n   (title :initarg :title\n          :reader screenshot-title)))\n\n(defparameter *default-browser-configs*\n  (list\n   (make-instance 'browser-config\n                  :name \"Firefox\"\n                  :type 'firefox)\n   (make-instance 'browser-config\n                  :name \"Chrome\"\n                  :dimensions (make-instance 'dimensions :width 1280 :height 800)\n                  :type 'chrome)\n   (make-instance 'browser-config\n                  :name \"Chrome Nexus 5 emulation\"\n                  :type 'chrome\n                  :mobile-emulation \"Nexus 6P\")\n   (make-instance 'browser-config\n                  :name \"Chrome Pixel 2 emulation\"\n                  :type 'chrome\n                  :mobile-emulation \"Pixel 2\")))\n\n(defclass job ()\n  ((logs :initarg :logs\n         :reader job-logs)\n   (url :initarg :url\n        :reader url)\n   (sleep :initarg :sleep\n          :initform 5\n          :reader sleep-time)\n   (screenshots :initform nil\n                :accessor screenshots)\n   (browser-configs :initarg :browser-configs\n                    :initform *default-browser-configs*\n                    :reader browser-configs)\n   (donep :initform nil\n          :accessor donep)))\n\n\n(defun arr (x)\n  (make-array (length x)\n              :initial-contents x))\n"
  },
  {
    "path": "src/screenshotbot/replay/integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/integration\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/replay/integration)\n  (:import-from #:screenshotbot/replay/frontend\n                #:width\n                #:dimensions\n                #:browser-type\n                #:*default-browser-configs*\n                #:screenshot-file-key\n                #:screenshot-title\n                #:screenshots\n                #:job)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:upload-image-directory)\n  (:import-from #:screenshotbot/sdk/git\n                #:null-repo)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:local-image\n                #:list-images)\n  (:import-from #:screenshotbot/sdk/git\n                #:make-instance)\n  (:import-from #:util\n                #:or-setf)\n  (:import-from #:screenshotbot/replay/sitemap\n                #:parse-sitemap)\n  (:import-from #:screenshotbot/replay/core\n                #:*replay-logs*\n                #:snapshot-request-sdk-flags\n                #:asset-file-name\n                #:context\n                #:write-replay-log\n                #:uuid\n                #:asset-file\n                #:load-url-into\n                #:snapshot)\n  (:import-from #:screenshotbot/replay/replay-acceptor\n                #:with-hosted-snapshot\n                #:call-with-hosted-snapshot)\n  (:import-from #:screenshotbot/webdriver/impl\n                #:with-webdriver\n                #:call-with-webdriver)\n  (:import-from #:screenshotbot/webdriver/screenshot\n                #:full-page-screenshot)\n  (:import-from #:webdriver-client\n                #:window-resize)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key-secret\n                #:make-transient-key)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key-secret-key\n                #:api-key-key)\n  (:import-from #:screenshotbot/replay/services\n                #:with-selenium-server\n                #:squid-proxy\n                #:selenium-port\n                #:selenium-host\n                #:selenium-server\n                #:selenium-server-url)\n  (:import-from #:screenshotbot/replay/run-builder\n                #:record-screenshot\n                #:all-screenshots)\n  (:import-from #:util/threading\n                #:make-thread\n                #:safe-interrupt\n                #:with-extras\n                #:safe-interrupt-checkpoint)\n  (:import-from #:screenshotbot/replay/proxy\n                #:ensure-proxy)\n  (:import-from #:util/object-id\n                #:oid-array)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:*sdk-flags*)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:util/misc/lists\n                #:make-batches\n                #:with-batches)\n  (:import-from #:screenshotbot/plan\n                #:company-plan)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:util/reused-ssl\n                #:with-reused-ssl)\n  (:import-from #:util/request\n                #:*engine*\n                #:engine)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:frontend #:screenshotbot/replay/frontend)\n                    (#:integration #:screenshotbot/replay/integration)\n                    (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:replay #:screenshotbot/replay/core)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:flags #:screenshotbot/sdk/flags))\n  (:export\n   #:sitemap\n   #:run\n   #:original-request\n   #:replay-concurrency))\n(in-package :screenshotbot/replay/integration)\n\n(with-auto-restart ()\n (defun get-local-addr (host port)\n   (let ((socket (usocket:socket-connect host port)))\n     (unwind-protect\n          (progn\n            #-lispworks\n            (let ((local-name (usocket:get-local-name socket)))\n              (str:join \".\" (loop for i across local-name\n                                  collect (format nil \"~d\" i))))\n            #+lispworks\n            (let ((local-name (comm:get-socket-address (usocket:socket socket))))\n              (comm:ip-address-string local-name)))\n       (usocket:socket-close socket)))))\n\n#+nil\n(defun get-local-name (service)\n  \"Not currently in use. Mostly because we're hardcoding names in the\nsquid config, so if we do this we'd have to dynamically update squid\ntoo.\"\n  (typecase service\n    (local-client\n     \"localhost\")\n    #+lispworks\n    (t\n     (get-local-addr (host service) (port service)))))\n\n(defclass run ()\n  ((company :initarg :company\n            :reader company)\n   (user :initarg :user\n         :reader user)\n   (sitemap :initarg :sitemap\n            :initform nil\n            :reader sitemap\n            :documentation \"URL to a sitemap. If Provided the list of URLs are picked from the sitemap\")\n   (exclusions :initarg :exclusions\n               :initform nil\n               :reader exclusions\n               :documentation \"List of regular expressions as string\")\n   (sampling :initarg :sampling\n             :initform 1\n             :reader sampling\n             :documentation \"Sampling rate from the list of URLs. Works when providing sitemap too.\")\n   (channel :initarg :channel\n            :reader channel)\n   (max-width :initarg :max-width\n              :reader max-width\n              :initform 640)\n   (host :initarg :host\n         :initform \"https://api.screenshotbot.io\"\n         :reader host)\n   (browser-configs :initarg :browser-configs\n                    :initform *default-browser-configs*\n                    :reader browser-configs)\n\n   (request :initarg :request\n            :initform nil\n            :reader original-request)\n   (urls :initarg :urls\n         :initform nil\n         :accessor %urls)\n   (custom-css :initarg :custom-css\n               :initform nil\n               :accessor custom-css)\n   (sleep :initarg :sleep\n          :initform 0.5\n          :reader sleep-time)))\n\n(define-condition config-error (simple-error)\n  ())\n\n(defmethod initialize-instance :after ((self run) &key urls sitemap &allow-other-keys)\n  (when (and urls sitemap)\n    (error \"Can't provide both urls and sitemap\")))\n\n(defmethod remove-base-url ((url string))\n  (let ((uri (quri:uri url)))\n    (quri:render-uri\n     (quri:copy-uri uri\n                    :scheme nil\n                    :host nil))))\n\n(defmethod urls ((run run))\n  (or-setf\n   (%urls run)\n   (let ((exclusions (mapcar #'cl-ppcre:create-scanner\n                             (exclusions run))))\n    (and (sitemap run)\n         (loop for url in (parse-sitemap (sitemap run))\n               unless (loop for exclusion in exclusions\n                            if (cl-ppcre:scan exclusion url)\n                              return t)\n               collect\n               (cons\n                (remove-base-url url)\n                url))))))\n\n(defmethod sampled-urls ((run run))\n  \"Determistically sample the list of URLS from run. Any\nscreenshotting operation should use this method instead of directly\naccessing the urls or sitemap slot.\"\n  (let ((urls (urls run))\n        (sampling (* 256 (sampling run))))\n    (loop for (name . url) in urls\n          for hash = (elt (md5:md5sum-string url) 0)\n          if (<= hash sampling)\n            collect (cons name url))))\n\n(def-easy-macro with-sdk-flags (&key flags &fn fn)\n  (loop for (key . value) in flags\n        for sym = (gethash key *sdk-flags*)\n        if sym ;; in case the SDK is not in sync\n          collect sym into symbols\n        if sym\n          collect value into values\n        finally (progv symbols values\n                  (funcall fn))))\n\n(defclass replay-run-context (run-context:default-flags-run-context\n                              run-context:run-context)\n  ())\n\n\n(defun process-results (run results\n                        &key (engine *engine*))\n  ;; The SDK has an ugly API when used from a non-SDK world\n\n  (restart-case\n      (let* ((api-key (make-transient-key :user (user run)\n                                          :company (company run)))\n             (request (integration:original-request run)))\n        ;; There are two situations we could be here: we could be\n        ;; here from the static website code from the SDK, or we\n        ;; could be here from the web based replay jobs. We need\n        ;; to set the flags manually for the web-based replay\n        ;; jobs, but for the static website code, the\n        ;; `with-sdk-flags` will propagate the flags.\n        (cond\n          ((gk:check :fixed-run-context (company run))\n           (error \"unimplemented\"))\n          (t\n           (let ((flags:*pull-request* (?. replay:pull-request request))\n                 (flags:*main-branch* (?. replay:main-branch request))\n                 (flags:*repo-url* (?. replay:repo-url request)))\n             (with-sdk-flags (:flags (?. snapshot-request-sdk-flags request))\n               (let ((api-context (make-instance 'api-context\n                                                 :key (api-key-key api-key)\n                                                 :secret (api-key-secret-key api-key)\n                                                 :hostname (host run)\n                                                 :engine engine)))\n                 (with-reused-ssl ((engine api-context))\n                   (let ((images\n                           ;; In this case it's not really uploading, we\n                           ;; should not depend on this method and just\n                           ;; construct the dto objects directly.\n                           (sdk:upload-image-directory api-context results)))\n                     (log:debug \"Creating run\")\n                     ;; TODO: replace with put-run-with-run-context instead.\n                     (let ((run-context (make-instance 'replay-run-context\n                                           :repo-url (when request (replay:repo-url request))\n                                           :channel (channel run)\n                                           :productionp t\n                                           :main-branch \"master\"\n                                           :repo-clean-p t\n                                           :commit-hash (when request (replay:commit request))\n                                           :merge-base (when request (replay:merge-base request))\n                                           :main-branch-hash (when request (replay:branch-hash request)))))\n                       (sdk:make-run\n                        api-context\n                        images\n                        :run-context run-context\n                        :repo (make-instance 'null-repo)\n                        :periodic-job-p (or (not request) (str:emptyp (replay:commit request)))))))))))))\n    (retry-process-results ()\n      (process-results run results))))\n\n(defun run-replay-on-urls (&key (snapshot (error \"provide snapshot\"))\n                                (replay-proxy (error \"provide replay proxy\"))\n                             (urls (error \"provide urls\"))\n                             (logger (lambda (url actual-url) (declare (ignore url actual-url))))\n                             (hosted-url (error \"provide hosted-url\"))\n                             (driver (error \"provide driver\"))\n                             (config (error \"provide config\"))\n                             (run (error \"provide run\"))\n                             (tmpdir (error \"provide tmpdir\"))\n                             (results (error \"provide results\")))\n  (declare (ignore tmpdir)) ;; we used to use this to store images, no longer.\n  (loop for (title . url) in urls\n        for i from 0 do\n          (restart-case\n              (progn\n                (safe-interrupt-checkpoint)\n                (loop for root-asset in (replay:assets snapshot)\n                      until (string= (replay:url root-asset) url)\n                      finally\n                         (let ((actual-url (quri:render-uri\n                                            (quri:merge-uris\n                                             (quri:uri\n                                              (format nil \"/company/~a/assets/~a\"\n                                                      (encrypt:encrypt-mongoid\n                                                       (oid-array (company run)))\n                                                      (asset-file-name root-asset)))\n                                             (quri:uri\n                                              hosted-url)))))\n\n                           (funcall logger url actual-url)\n\n                           (a:when-let (dimension (frontend:dimensions config))\n                             (window-resize :width (frontend:width dimension)\n                                            :height (frontend:height dimension)))\n                           (setf (webdriver-client:url)\n                                 actual-url)))\n\n                ;; a temporary screenshot, I think this\n                ;; will prime the browser to start loading\n                ;; any assets that might be missing\n                ;;(full-page-screenshot driver nil)\n\n\n                ;; TODO: This sleep-time used to be part of\n                ;; wait-for-zero-requests. Currently, because of\n                ;; aggressive proxy caching, there's no way of waiting for\n                ;; zero requests. In the previous logic we would *at\n                ;; least* sleep for this much time, but might be more\n                ;; while requests are pending. We might be able to reduce\n                ;; this in the future, but it's not the bottleneck at time\n                ;; of writing.\n                (sleep (sleep-time run))\n\n                (process-full-page-screenshot\n                 driver\n                 replay-proxy\n                 :results results\n                 :title (format nil \"~a--~a\"\n                                title (frontend:browser-config-name config))))\n            (ignore-this-url ()\n              nil))))\n\n(defmethod fetch-full-page-screenshot-handle (driver proxy)\n  \"Creates a full-page-screenshot, and returns two values: the handle\n  for the screenshot and the md5sum of the screenshot\"\n  (let* ((url (format nil \"~a/full-page-screenshot\"\n                      proxy))\n         (resp (uiop:slurp-input-stream\n                'string\n                (util/request:http-request\n                 url\n                 :method :post\n                 :parameters `((\"session\" . ,(webdriver-client::session-id webdriver-client::*session*))\n                               (\"browser\" . ,(string-downcase (type-of driver)))\n                               (\"uri\" . ,webdriver-client::*uri*))\n                 :want-stream t\n                 :connection-timeout 15\n                 ;; this request can be slow! Also give it some time\n                 ;; in case we're in the debugger.\n                 :read-timeout 1200)))\n         (json-response (json:decode-json-from-string resp))\n         (oid (a:assoc-value json-response :oid))\n         (md5 (a:assoc-value json-response :md-5)))\n    (when (str:emptyp oid)\n      (error \"full-page-screenshot failed with response: ~a\" resp))\n    (log:info \"Got response: ~a\" resp)\n    (values oid md5)))\n\n(defmethod write-full-page-screenshot-from-handle (driver proxy oid dest)\n  (with-open-stream (stream (util/request:http-request\n                             (format nil \"~a/download?oid=~a\" proxy oid)\n                             :force-binary t\n                             :want-stream t))\n    (with-open-file (dest dest :direction :output\n                               :if-exists :supersede\n                               :element-type '(unsigned-byte 8)\n                               :if-does-not-exist :create)\n      (uiop:copy-stream-to-stream\n       stream dest\n       :element-type '(unsigned-byte 8)))))\n\n(auto-restart:with-auto-restart ()\n (defun process-full-page-screenshot (driver\n                                      proxy\n                                      &key title\n                                        results)\n   (multiple-value-bind (oid md5)\n       (fetch-full-page-screenshot-handle driver proxy)\n     (assert oid)\n     (assert md5)\n     (record-screenshot\n      results\n      :md5 md5\n      :fetch (lambda (dest)\n               (log:info \"Fetching screenshot to ~a\" dest)\n               (write-full-page-screenshot-from-handle\n                driver\n                proxy\n                oid\n                dest))\n      :title title))))\n\n(with-auto-restart (:retries 2 :sleep 60)\n  (defun run-batch (&key urls config selenium-server\n                      idx\n                      snapshot\n                      hosted-url\n                      run\n                      tmpdir\n                      results\n                      url-count)\n    (log:info \"Running batch from index ~a\" idx)\n    (with-webdriver (driver\n                     :proxy (squid-proxy selenium-server)\n                     :browser (frontend:browser-type config)\n                     :dimensions (when (frontend:dimensions config)\n                                   (cons\n                                    (frontend:width (dimensions config))\n                                    (frontend:height (dimensions config))))\n                     :mobile-emulation (frontend:mobile-emulation config))\n      ;; We have our browser and our hosted snapshots, let's go through this\n      (write-replay-log \"Selenium worker is ready\")\n      (run-replay-on-urls\n       :snapshot snapshot\n       :replay-proxy (ensure-proxy selenium-server)\n       :urls urls\n       :hosted-url hosted-url\n       :driver driver\n       :logger (lambda (url actual-url)\n                 ;; We used to log the actual-url here, but we need to\n                 ;; remove it for now to solve T902. We should have a\n                 ;; separate internal log where we can put this though.\n                 (write-replay-log \"[~a/~a] Running url: ~a\"\n                                   (incf idx)\n                                   url-count  url))\n       :config config\n       :run run\n       :tmpdir tmpdir\n       :results results)\n      idx)))\n\n(defgeneric replay-concurrency (company plan)\n  (:method (company plan)\n    ;; Keep things simple...\n    1))\n\n(defun run-in-parallel (urls &rest rest-args &key selenium-server\n                                               run\n                        &allow-other-keys)\n  (let* ((company (company run))\n         (plan (company-plan company (installation)))\n         (seen-failure-p nil)\n         (replay-logs *replay-logs*)\n         (batch-size 10)\n         (next-start 0)\n         (lock (bt:make-lock))\n         (num-threads (replay-concurrency company plan)))\n    (let ((thread-batches (make-batches urls :batch-size (ceiling (length urls) num-threads))))\n      (log:info \"thread batches: ~a\" thread-batches)\n      (flet ((run-thread-batch (urls)\n               (let ((batches (make-batches urls :batch-size batch-size)))\n                 (mapc\n                  (lambda (urls)\n                    (log:info \"Processing urls on thread ~a: ~S\"\n                              (bt:current-thread)\n                              urls)\n                    (unless seen-failure-p\n                      (flet ((cleanup (e)\n                               (declare (ignore e))\n                               (setf seen-failure-p t)))\n                        (handler-bind ((error #'cleanup)\n                                       (safe-interrupt #'cleanup))\n                          (safe-interrupt-checkpoint)\n                          (let ((webdriver-client::*uri*\n                                  (selenium-server-url selenium-server))\n                                (*replay-logs* replay-logs))\n                            (apply #'run-batch :urls urls\n                                               :idx (bt:with-lock-held (lock)\n                                                      (let ((next next-start))\n                                                        (incf next-start (length urls))\n                                                        next))\n                                               rest-args))))))\n                  batches))))\n       (let ((worker-threads\n               (loop for urls in (cdr thread-batches)\n                     collect\n                     (let ((urls urls))\n                       (make-thread\n                        (lambda ()\n                          (run-thread-batch urls))\n                        :name (format nil \"replay-worker-thread-for-~a\" (bt:current-thread)))))))\n\n         ;; Now let's use the current thread as one more batch\n         (unwind-protect\n              (run-thread-batch (car thread-batches))\n           (loop for thread in worker-threads\n                 do\n                    (write-replay-log \"Waiting for worker thread: ~a\" thread)\n                    (bt:join-thread thread))))))))\n\n(with-auto-restart ()\n  (defun replay-job-from-snapshot (&key (snapshot (error \"must provide snapshot\"))\n                                     urls run tmpdir)\n   (let* ((results (make-instance 'all-screenshots\n                                   :company (company run)))\n          (url-count  (length urls)))\n     (prog1 results\n       (let ((configs (browser-configs run)))\n         (assert configs)\n         (dolist (config configs)\n           (with-selenium-server (selenium-server :type (browser-type config))\n             (with-hosted-snapshot (hosted-url (company run) snapshot\n                                    :hostname (get-local-addr\n                                               (selenium-host selenium-server)\n                                               (selenium-port selenium-server)))\n               (write-replay-log \"Waiting for Selenium worker of type ~a\" (browser-type config))\n               (run-in-parallel urls\n                                :config config\n                                :selenium-server selenium-server\n                                :snapshot snapshot\n                                :hosted-url hosted-url\n                                :run run\n                                :tmpdir tmpdir\n                                :results results\n                                :url-count url-count)))))))))\n\n(defun best-image-type (config)\n  (cond\n    ((string-equal \"firefox\" (browser-type config))\n     \"png\")\n    (t\n     \"png\")))\n\n(with-auto-restart ()\n  (defun schedule-replay-job (run)\n    (with-extras ((\"run\" run)\n                  (\"company\" (company run)))\n     (tmpdir:with-tmpdir (tmpdir)\n       (handler-bind ((dex:http-request-failed\n                        (lambda (e)\n                          (write-replay-log \"HTTP request failed: ~a~%\" (type-of e))))\n                      (cl+ssl::hostname-verification-error\n                        (lambda (e)\n                          (write-replay-log \"SSL error: ~S~%\" e))))\n         (let* ((urls (sampled-urls run))\n                (snapshot (make-instance 'snapshot :tmpdir tmpdir))\n                (context (make-instance 'context\n                                        :custom-css (custom-css run)))\n                (count (length urls)))\n           (loop for (nil . url) in urls\n                 for i from 1\n                 do\n                    (restart-case\n                        (progn\n                          (log:info \"Loading ~a/~a\" i count)\n                          (load-url-into context snapshot url tmpdir))\n                      (ignore-this-url ()\n                        (values))))\n           (let ((results (replay-job-from-snapshot\n                           :snapshot snapshot\n                           :urls urls\n                           :tmpdir tmpdir\n                           :run run)))\n             (process-results run results))))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/replay/proxy-main.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/proxy-main\n  (:use #:cl)\n  (:import-from #:screenshotbot/replay/proxy\n                #:replay-proxy\n                #:*proxy-port*)\n  (:import-from #:clingon\n                #:make-option)\n  (:export\n   #:proxy-main\n   #:proxy-main/command))\n(in-package :screenshotbot/replay/proxy-main)\n\n(defun setup-squid ()\n  \"Configure and start Squid proxy\"\n  (log:info \"Configuring Squid\")\n\n  ;; Create allowed_sites file\n  (with-open-file (stream \"/etc/squid/allowed_sites\"\n                          :direction :output\n                          :if-exists :supersede\n                          :if-does-not-exist :create)\n    (format stream \"staging~%\")\n    (format stream \"replay~%\")\n    (format stream \"~%\")\n    (format stream \"# static IP for staging~%\")\n    (format stream \"172.29.1.15~%\")\n    (format stream \"~%\")\n    (format stream \"# second static IP for staging~%\")\n    (format stream \"172.20.0.6~%\")\n    (format stream \"~%\")\n    (format stream \"# static IP for replay~%\")\n    (format stream \"172.29.1.14~%\")\n    (format stream \"~%\")\n    (format stream \"# production over OpenVPN~%\")\n    (format stream \"10.9.8.1~%\")\n    (format stream \"~%\")\n    (format stream \"# debian-us-east-001~%\")\n    (format stream \"45.33.86.76~%\"))\n\n  ;; Create squid.conf\n  (with-open-file (stream \"/etc/squid/squid.conf\"\n                          :direction :output\n                          :if-exists :supersede\n                          :if-does-not-exist :create)\n    (format stream \"http_port 3128~%\")\n    (format stream \"cache allow all~%\")\n    (format stream \"cache_dir aufs /var/spool/squid 50000 16 256~%\")\n    (format stream \"acl whitelist dstdomain \\\"/etc/squid/allowed_sites\\\"~%\")\n    (format stream \"http_access deny !whitelist~%\"))\n\n  ;; Initialize cache directories\n  (log:info \"Initializing Squid cache directories\")\n  (uiop:run-program '(\"squid\" \"-z\")\n                    :output :interactive\n                    :error-output :interactive\n                    :ignore-error-status t)\n\n  ;; Start Squid\n  (log:info \"Starting Squid service\")\n  (uiop:run-program '(\"systemctl\" \"start\" \"squid\")\n                    :output :interactive\n                    :error-output :interactive)\n  (uiop:run-program '(\"systemctl\" \"enable\" \"squid\")\n                    :output :interactive\n                    :error-output :interactive))\n\n(defun prepare-machine ()\n  \"Prepare the machine by updating packages and installing Docker and Squid\"\n  (log:info \"Preparing machine: running apt-get update\")\n  (uiop:run-program '(\"apt-get\" \"update\")\n                    :output :interactive\n                    :error-output :interactive)\n\n  (log:info \"Installing Docker and Squid\")\n  (uiop:run-program '(\"apt-get\" \"install\" \"-y\" \"docker.io\" \"squid\")\n                    :output :interactive\n                    :error-output :interactive)\n\n  (setup-squid)\n\n  (log:info \"Machine preparation complete\"))\n\n(defun proxy-main/handler (cmd)\n  (let ((port (clingon:getopt cmd :port))\n        (address (clingon:getopt cmd :address))\n        (listen-backlog (clingon:getopt cmd :listen-backlog))\n        (prepare-machine-p (clingon:getopt cmd :prepare-machine)))\n    (when prepare-machine-p\n      (prepare-machine))\n    (server:main :acceptor (make-instance 'replay-proxy\n                                          :port port\n                                          :address address\n                                          :listen-backlog listen-backlog)\n                 :enable-store nil\n                 :jvm nil)))\n\n(defun proxy-main/command ()\n  (clingon:make-command\n   :name \"proxy\"\n   :description \"Screenshotbot Selenium Proxy Server\"\n   :handler #'proxy-main/handler\n   :options (list\n             (make-option\n              :integer\n              :description \"HTTP access port\"\n              :long-name \"port\"\n              :initial-value 5004\n              :key :port)\n             (make-option\n              :string\n              :description \"Address to bind to\"\n              :long-name \"address\"\n              :initial-value \"0.0.0.0\"\n              :key :address)\n             (make-option\n              :integer\n              :description \"Listen backlog size\"\n              :long-name \"listen-backlog\"\n              :initial-value 500\n              :key :listen-backlog)\n             (make-option\n              :flag\n              :description \"Prepare machine by running apt-get update and installing Docker\"\n              :long-name \"prepare-machine\"\n              :initial-value nil\n              :key :prepare-machine))))\n\n(defun proxy-main ()\n  (let ((args (cdr (uiop:raw-command-line-arguments))))\n    (clingon:run (proxy-main/command) args)))\n"
  },
  {
    "path": "src/screenshotbot/replay/proxy.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;; Proxy for getting screenshots, to avoid network bandwidth usage.\n;; Not to be confused with the Squid proxy used for browsers to get\n;; images.\n\n(defpackage :screenshotbot/replay/proxy\n  (:use #:cl)\n  (:import-from #:screenshotbot/webdriver/impl\n                #:make-driver)\n  (:import-from #:screenshotbot/webdriver/screenshot\n                #:full-page-screenshot)\n  (:import-from #:hunchentoot\n                #:*acceptor*)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:screenshotbot/hub/server\n                #:direct-selenium-url\n                #:relay-session-request\n                #:request-session-and-respond\n                #:hub)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:http-proxy/server\n                #:allowedp\n                #:http-proxy)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:*proxy-port*\n   #:ensure-proxy\n   #:selenium-host\n   #:selenium-port))\n(in-package :screenshotbot/replay/proxy)\n\n(defvar *screenshot-proxy-cache*\n  \"~/screenshot-proxy-cache/\")\n\n(defvar *lock* (bt:make-lock))\n\n(defclass custom-http-proxy (http-proxy)\n  ((allowed-hosts :initform nil\n                  :accessor allowed-hosts)))\n\n(defmethod allowedp ((self custom-http-proxy)\n                     host)\n  (let ((host (car (str:split \":\" host))))\n    (bt:with-lock-held (*lock*)\n     (str:s-member (allowed-hosts self) host))))\n\n(defclass replay-proxy (hunchentoot:easy-acceptor)\n  ((cache-dir :initarg :cache-dir\n              :reader cache-dir\n              :initform (pathname *screenshot-proxy-cache*))\n   (hub :initarg :hub\n        :initform (hub)\n        :reader replay-proxy-hub)\n   (http-proxy :initarg :http-proxy\n               :initform (make-instance 'custom-http-proxy\n                                        :port 3129)\n               :reader http-proxy))\n  (:default-initargs :name 'replay-proxy)\n  (:documentation \"Even though this is called 'replay' it refers to a Selenium proxy,\n with some added functinality for screenshots.\"))\n\n(defmethod hunchentoot:start :before ((self replay-proxy))\n  (hunchentoot:start (http-proxy self)))\n\n(defmethod hunchentoot:stop :after ((self replay-proxy) &key &allow-other-keys)\n  (hunchentoot:stop (http-proxy self)))\n\n(defun linode? ()\n  #-screenshotbot-oss\n  (or\n   (equal \"localhost\" (uiop:hostname))\n   (equal \"prod1.screenshotbot.io\" (uiop:hostname))\n   (equal \"prod1.intern.screenshotbot.io\" (uiop:hostname))))\n\n(defvar *replay-proxy* nil)\n\n(defvar *proxy-port* 5003)\n\n(defun ensure-local-proxy ()\n  (util/misc:or-setf\n   *replay-proxy*\n   (let ((proxy (make-instance 'replay-proxy :port *proxy-port*)))\n     (hunchentoot:start proxy)\n     proxy)\n   :thread-safe t)\n  (format nil \"http://localhost:~a\" (hunchentoot:acceptor-port *replay-proxy*)))\n\n;; If you change this behaves, make sure it looks reasonable in launch-proxy.lisp\n(defun ensure-proxy (selenium-service)\n  \"Ensure the proxy is running and return the URL to the proxy\"\n  (cond\n    ((and selenium-service\n          (selenium-host selenium-service)\n          (find #\\: (selenium-host selenium-service)))\n     ;; IPv6 selenium server - proxy is running on the same host\n     (format nil \"http://[~a]:5004\"\n             (selenium-host selenium-service)))\n    ((linode?)\n     ;; todo: error handling: if this endpoint goes down then just a local\n     ;; proxy.\n     (format nil \"http://~a:~d\"\n             (selenium-host selenium-service)\n             (selenium-port selenium-service)))\n    #+screenshotbot-oss\n    (t\n     (ensure-local-proxy))\n    (t\n     ;; This is the docker host. The proxy will run on the host\n     ;; container since we need it to create docker containers.\n     \"http://172.17.0.1:5004\")))\n\n(def-easy-macro with-error-handling (&fn fn)\n  (handler-bind ((error (lambda (e)\n                          (setf (hunchentoot:return-code*) 500)\n                          (return-from call-with-error-handling\n                            (json:encode-json-to-string\n                             `((\"value\" . ((\"error\" . ,(%print-condition e))\n                                           (\"message\" . \"Error forwarding request\")\n                                           #+lispworks\n                                           (\"stacktrace\" .\n                                                         ,(with-output-to-string (out)\n                                                            (dbg:output-backtrace :debug :stream out)))))))))))\n    (funcall fn)))\n\n(defun %handler-wrap (fn)\n  (with-error-handling ()\n    (funcall fn)))\n\n(defmacro def-proxy-handler ((name &key uri method) args &body body)\n  `(hex:better-easy-handler (,name :uri ,uri :acceptor-names '(replay-proxy) :method ,method) ,args\n     (%handler-wrap\n      (lambda ()\n        ,@body))))\n\n\n(defun oid-pathname (oid)\n  (path:catfile (cache-dir *acceptor*) (format nil \"~a.png\" oid)))\n\n(defun %print-condition (e)\n  (let ((*print-escape* nil))\n    (format nil \"~a\" e)))\n\n\n(def-proxy-handler (%full-page-screenshot :uri \"/full-page-screenshot\") (session browser)\n  (let* ((oid (mongoid:oid-str (mongoid:oid)))\n         (driver (make-driver browser :proxy nil))\n         (path (oid-pathname oid))\n         (webdriver-client::*uri* (direct-selenium-url\n                                   (replay-proxy-hub hunchentoot:*acceptor*)\n                                   session)))\n    (let ((webdriver-client::*session*\n            (make-instance 'webdriver-client::session\n                            :id session)))\n      (ensure-directories-exist path)\n      (full-page-screenshot driver path)\n      (setf (hunchentoot:content-type*) \"application/json\")\n      (json:encode-json-to-string\n       `((:oid . ,oid)\n         (:md5 . ,(ironclad:byte-array-to-hex-string (md5-file path))))))))\n\n(def-proxy-handler (nil :uri \"/wd/hub\") ()\n  (error \"Unimpl\"))\n\n(defun add-remote-to-proxy-acl ()\n  ;; Note that we're using remote-addr, and not real-remote-addr. If\n  ;; the incoming request was being forwarded from somewhere, then you\n  ;; need to specify that directly.\n  (let ((host (hunchentoot:remote-addr hunchentoot:*request*)))\n    (bt:with-lock-held (*lock*)\n      (pushnew host\n               (allowed-hosts (http-proxy hunchentoot:*acceptor*))))))\n\n(def-proxy-handler (nil :uri \"/wd/hub/session\" :method :post) ()\n  (add-remote-to-proxy-acl)\n  (let ((content (hunchentoot:raw-post-data\n                  :force-text t)))\n    (let ((hub (replay-proxy-hub hunchentoot:*acceptor*)))\n      (request-session-and-respond\n       hub\n       content))))\n\n(def-proxy-handler (nil :uri (lambda (request)\n                               (let ((script-name (hunchentoot:script-name request)))\n                                 (let ((prefix \"/wd/hub/session/\"))\n                                  (and\n                                   (str:starts-with-p prefix script-name)\n                                   (> (length script-name) (length prefix)))))))\n                   ()\n  (let ((hub (replay-proxy-hub hunchentoot:*acceptor*)))\n    (relay-session-request hub\n                           :method (hunchentoot:request-method*)\n                           :content (hunchentoot:raw-post-data :force-text t)\n                           :content-type (hunchentoot:content-type*)\n                           :script-name (hunchentoot:script-name*))))\n\n(def-proxy-handler (%download :uri \"/download\") (oid)\n  (assert (ironclad:hex-string-to-byte-array oid))\n  (hunchentoot:handle-static-file (oid-pathname oid)))\n\n(def-proxy-handler (%status :uri \"/status\") ()\n  \"OK\")\n\n(defun clean-old-screenshots ()\n  (let ((cut-off (- (get-universal-time)\n                    (* 10 60))))\n   (loop for file in  (fad:list-directory *screenshot-proxy-cache*)\n         for write-date = (file-write-date file)\n         if (< write-date cut-off)\n           do\n              (log:info \"Deleting file ~a\" file)\n              (delete-file file))))\n\n;; We can't use def-cron here because there's no datastore\n(cl-cron:make-cron-job\n (lambda ()\n   (ignore-and-log-errors ()\n     (clean-old-screenshots)))\n :hash-key 'clean-old-screenshots\n :step-min 5)\n"
  },
  {
    "path": "src/screenshotbot/replay/remote.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/remote\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/replay/remote)\n  (:import-from #:screenshotbot/replay/integration\n                #:schedule-replay-job\n                #:run)\n  (:import-from #:util/object-id\n                #:find-by-oid\n                #:oid\n                #:object-with-oid)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:blob)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:screenshotbot/user-api\n                #:company-name\n                #:user\n                #:can-view\n                #:%created-at)\n  (:import-from #:screenshotbot/replay/core\n                #:write-replay-log\n                #:*replay-logs*)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:hunchensocket\n                #:websocket-resource)\n  (:import-from #:screenshotbot/server\n                #:make-thread\n                #:staging-p)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store\n                #:with-class-validation\n                #:defindex)\n  (:import-from #:util/hash-lock\n                #:with-hash-lock-held\n                #:hash-lock)\n  (:import-from #:util/threading\n                #:with-safe-interruptable)\n  (:import-from #:screenshotbot/events\n                #:with-event)\n  (:import-from #:util/store/fset-index\n                #:fset-set-compat-index\n                #:fset-set-index)\n  (:import-from #:util/misc\n                #:safe-with-open-file)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:frontend #:screenshotbot/replay/frontend)\n                    (#:browser-config #:screenshotbot/replay/browser-config))\n  (:export\n   #:send-remote-run\n   #:remote-runs-for-company))\n(in-package :screenshotbot/replay/remote)\n\n(defvar *threads* (trivial-garbage:make-weak-hash-table\n                   :weakness :value))\n\n(defparameter *timeout* (* 4 3600))\n(defvar *hash-lock* (make-instance 'hash-lock))\n\n(with-auto-restart ()\n  (defun send-remote-run (run &key (company (error \"Must provide company\"))\n                                (cleanup (lambda ())))\n   (let* ((log-file (make-instance 'log-file))\n          (remote-run (make-instance 'remote-run\n                                      :company company\n                                      :log-file log-file)))\n\n     ;; If the block in with-open-file signals, then the log file will\n     ;; be deleted. This prevents that.\n     (open (blob-pathname log-file) :direction :probe\n          :if-does-not-exist :create)\n\n     (let ((thread\n             (make-thread\n              (lambda ()\n                (with-safe-interruptable (:on-quit\n                                          (lambda ()\n                                            (with-transaction ()\n                                              (write-replay-log \"Aborted by user\")\n                                              (setf (remote-run-status remote-run)\n                                                    :user-aborted))))\n                  (with-transaction ()\n                    (setf (remote-run-status remote-run) :queued))\n                  (with-event (:replay-job :company (company-name company))\n                   (safe-with-open-file (stream (bknr.datastore:blob-pathname log-file) :direction :output)\n                     (handler-bind ((error (lambda (e)\n                                             (write-replay-log \"Got condition: ~a\" e))))\n                       (with-hash-lock-held ((remote-run-company remote-run) *hash-lock*)\n                         (unless (eql :cancelled (remote-run-status remote-run))\n                           (with-transaction ()\n                             (setf (remote-run-status remote-run) :running))\n                           ;; Useful for testing cancellation flow:\n                           ;;(sleep 40)\n                           (let* ((ret (actually-run run stream)))\n                             (log:info \"remote run done: ~a\" ret))))\n\n                       ;; todo: move to unwind-protect\n                       (funcall cleanup))))\n                  (with-transaction ()\n                    (unless (eql :cancelled (remote-run-status remote-run))\n                      (setf (remote-run-status remote-run) :success)))))\n              :name (format nil \"remote-replay-management-threads for ~a\" (bknr.datastore:store-object-id remote-run)))))\n       (setf (gethash remote-run *threads*)\n             thread))\n     remote-run)))\n\n(defun actually-run-serialized (serialized-run &rest args)\n  (let ((stream (flexi-streams:make-in-memory-input-stream serialized-run)))\n    (let ((run (cl-store:restore stream)))\n      (unwind-protect\n           (apply #'actually-run run args))\n      \"done\")))\n\n(defun update-remote-run (run)\n  (assert (not (donep run)))\n  (assert (not (str:emptyp (remote-oid run))))\n  (handler-case\n      (let ((json (json:decode-json-from-string\n                   (dex:get\n                    (format nil \"~a/get-job-status?oid=~a\"\n                            (remote-url run)\n                            (remote-oid run))))))\n        (a:when-let (donep (a:assoc-value json :donep))\n          (with-transaction ()\n            (setf (donep run) t)\n            (setf (remote-run-status run) :success))))\n    (dex:http-request-failed ()\n      (with-transaction ()\n        (setf (donep run) t)))\n    (usocket:timeout-error (e))))\n;; (log:info \"~a\" (encode-run (make-testing-run)))\n\n;; (send-remote-run (make-testing-run))\n\n(defindex +company-index+ 'fset-set-compat-index\n  :slot-name 'company)\n\n(with-class-validation\n (defclass remote-run (object-with-oid)\n   ((remote-url :initarg :remote-url\n                :reader remote-url\n                :initform nil)\n    (company :initarg :company\n             :initform nil\n             :index +company-index+\n             :index-reader remote-runs-for-company\n             :reader remote-run-company)\n    (log-file :initarg :log-file\n              :accessor log-file\n              :initform nil\n              :documentation \"We've noticed a case where this log-file object was being deleted on staging. Was that a temporary bug during development, or is there a bigger bug? On staging, we fixed it by just setting log-file to NIL for all the destroyed objects. e.g. T1718.\")\n    (oid :initarg :remote-oid\n         :initform nil\n         :reader remote-oid)\n    (donep :initarg :donep\n           :initform nil\n           :writer (setf donep))\n    (status :initarg :status\n            :initform :unknown\n            :accessor remote-run-status)\n    (%finished-at :initform 0\n                  :accessor finished-at)\n    (%started-at :initform 0\n                 :accessor started-at)\n    (%created-at :initform 0\n                 :initarg :created-at\n                 :reader %created-at))\n   (:metaclass persistent-class)\n   (:default-initargs :created-at (get-universal-time))))\n\n(defmethod auth:can-view ((self remote-run) (user user))\n  (auth:can-view-with-normal-viewer-context\n   user self))\n\n(defmethod auth:can-viewer-view (vc (self remote-run))\n  (let ((company (remote-run-company self)))\n    (assert company)\n    (check-type company company)\n    (auth:can-viewer-view vc (remote-run-company self))))\n\n(defmethod donep ((run remote-run))\n  (let ((thread (gethash run *threads*)))\n    (not\n     (and thread\n          (bt:thread-alive-p thread)))))\n\n(defmethod (setf remote-run-status) :after ((val (eql :running)) (run remote-run))\n  (setf (started-at run) (get-universal-time)))\n\n(defmethod (setf remote-run-status) :after ((val (eql :success)) (run remote-run))\n  (setf (finished-at run) (get-universal-time)))\n\n(defmethod run-thread-id ((run remote-run))\n  (unless (donep run)\n    (let ((thread (gethash run *threads*)))\n      (values\n       (bt:thread-name thread)\n       thread))))\n\n(defclass local-run (object-with-oid)\n  ((donep :initform nil\n          :accessor donep)\n   (log-file :initarg :log-file\n             :initform nil\n             :accessor log-file))\n  (:metaclass persistent-class))\n\n(defclass log-file (blob)\n  ()\n  (:metaclass persistent-class))\n\n(defclass local-run-log-resource (hunchensocket:websocket-resource)\n  ((local-run :initarg :local-run\n              :reader local-run))\n  (:default-initargs :client-class 'local-run-log-client))\n\n(defclass local-run-log-client (hunchensocket:websocket-client)\n  ())\n\n(defun find-ws-resource (request)\n  (let ((script-name (hunchentoot:script-name request)))\n    (log:info \"finding resource with: ~a\" script-name)\n    (when (str:starts-with-p \"/wsapp/replay/logs/\" script-name)\n      (let* ((oid (car (last (str:split \"/\" script-name))))\n             (run (find-by-oid oid)))\n        (check-type run (or local-run\n                            remote-run))\n        (make-instance 'local-run-log-resource\n                        :local-run run)))))\n\n(defvar +sleep-time-for-websocket+ 2\n  \"If the requests are getting heavy, we can increase this to reduce\n  how frequently we're hitting our resources\")\n\n(defmacro handle-websocket-gracefully (() &body body)\n  `(handler-case\n       (progn ,@body)\n     #+lispworks\n     (conditions:stream-closed-error ()\n       (log:info \"Websocket closed\"))))\n\n(defmethod hunchensocket:client-connected ((resource websocket-resource)\n                                           client)\n  (log:info \"client connected!\")\n  (util:make-thread\n   (lambda ()\n     (let* ((+len+ 1024) ;; Update this in websocket-logs.js too\n            (data (make-array +len+ :element-type 'character\n                                    :adjustable t))\n            (local-run (local-run resource))\n            (log-file (blob-pathname (log-file local-run))))\n       (unwind-protect\n            (handle-websocket-gracefully ()\n              (when (and\n                     (staging-p)\n                     (equal \"thecharmer\" (uiop:hostname)))\n               (hunchensocket:send-text-message client\n                                                (format nil \"Log file is: ~a~%\" log-file)))\n              (handler-case\n                  (safe-with-open-file (stream log-file :direction :input)\n                    (labels ((send-remaining ()\n                               (when (< (file-position stream)\n                                        (file-length stream))\n                                 (loop\n                                   for pos = (read-sequence data stream)\n                                   do\n                                      (when (< pos +len+)\n                                        (adjust-array data pos))\n                                      (hunchensocket:send-text-message client data)\n                                      (when (< pos +len+)\n                                        (adjust-array data +len+)\n                                        (return)))\n                                 :data-was-sent)))\n\n                      (send-remaining)\n                      (loop while (not (donep local-run)) do\n                        (sleep +sleep-time-for-websocket+)\n                        (unless (send-remaining)\n                          (hunchensocket:send-ping client)))\n                      (send-remaining)\n                      (hunchensocket:send-text-message client \"DONE\")))\n                (file-error (e)\n                  (hunchensocket:send-text-message\n                   client\n                   (format nil \"Error opening log file: ~a\" e)))))\n         (handle-websocket-gracefully ()\n          (hunchensocket:close-connection client)))))\n   :name (format nil \"websocket-log-tail started at ~a\" (local-time:now)))\n)\n\n(pushnew 'find-ws-resource hunchensocket:*websocket-dispatch-table*)\n\n(defun log-dir ()\n  (ensure-directories-exist\n   (path:catdir util/store:*object-store* \"replay-logs-dir/\")))\n\n(defun actually-run (run log-stream)\n  (let ((*replay-logs* log-stream))\n    (schedule-replay-job run)))\n\n\n(defun get-run-status (local-run)\n  (setf (hunchentoot:content-type*) \"application/json\")\n  (json:encode-json-to-string\n   `((:donep . ,(donep local-run))\n     (:oid . ,(oid local-run)))))\n"
  },
  {
    "path": "src/screenshotbot/replay/replay-acceptor.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:screenshotbot/replay/replay-acceptor\n  (:use #:cl)\n  (:nicknames #:screenshotbot/pro/replay/replay-acceptor)\n  (:import-from #:hunchentoot\n                #:*acceptor*\n                #:define-easy-handler)\n  (:import-from #:screenshotbot/replay/core\n                #:asset-response-headers\n                #:http-header-value\n                #:http-header-name\n                #:asset-file-name\n                #:asset-file\n                #:assets\n                #:snapshot\n                #:request-counter\n                #:call-with-request-counter)\n  (:import-from #:local-time\n                #:timestamp+\n                #:now\n                #:timestamp>=)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:screenshotbot/server\n                #:register-init-hook\n                #:*init-hooks*)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/object-id\n                #:find-by-oid)\n  (:import-from #:util/http-cache\n                #:parse-max-age)\n  (:import-from #:util/misc\n                #:not-null!\n                #:make-mp-hash-table)\n  #+lispworks\n  (:import-from #:server/acceptor-override\n                #:ipv6-acceptor)\n  (:export\n   #:call-with-hosted-snapshot\n   #:render-acceptor\n   #:push-snapshot\n   #:with-hosted-snapshot\n   #:pop-snapshot)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:replay #:screenshotbot/replay/core)))\n(in-package #:screenshotbot/replay/replay-acceptor)\n\n(defun document-root ()\n  (asdf:system-relative-pathname :screenshotbot.pro \"replay/static/\"))\n\n\n(defclass cached-404-acceptor (hunchentoot:acceptor)\n  ())\n\n(defclass render-acceptor (#+lispworks\n                           ipv6-acceptor\n                           hunchentoot:easy-acceptor\n                           cached-404-acceptor)\n  ((snapshots :reader acceptor-snapshots\n              :initform (make-mp-hash-table :test #'equal))\n   (asset-maps :reader asset-maps\n               :initform (make-mp-hash-table)\n               :documentation \"For each snapshot, a map from filename to asset\")\n   (snapshots-company\n    :initform nil\n    :accessor snapshots-company\n    :documentation \"A list of company and snapshot pairs\"))\n  (:default-initargs :name 'replay\n                     :port 5002\n                     :access-log-destination nil\n                     :message-log-destination nil))\n\n\n(defvar *default-render-acceptor* nil)\n\n\n(defmethod hunchentoot:acceptor-dispatch-request ((acceptor cached-404-acceptor)\n                                                  request)\n  (send-404 (hunchentoot:script-name request) 3600))\n\n(defun default-render-acceptor ()\n  (util:or-setf\n   *default-render-acceptor*\n   (let ((acceptor (make-instance 'render-acceptor)))\n     (hunchentoot:start acceptor)\n     acceptor)\n   :thread-safe t))\n\n(defmethod initialize-instance :after ((acceptor render-acceptor) &key snapshot\n                                       &allow-other-keys)\n  (when snapshot\n    (error \"OBSOLETE: passing snapshot as initarg\")))\n\n(defmethod push-snapshot ((acceptor render-acceptor)\n                          (company company)\n                          (snapshot replay:snapshot))\n  (setf (gethash (format nil \"~a\" (replay:uuid snapshot)) (acceptor-snapshots acceptor))\n        snapshot)\n  (setf (snapshots-company acceptor)\n        (acons\n         snapshot company\n         (snapshots-company acceptor)))\n  (let ((asset-map (make-mp-hash-table :test #'equal)))\n    (dolist (asset (assets snapshot))\n      (setf (gethash (asset-file-name asset) asset-map) asset))\n    (setf (gethash snapshot (asset-maps acceptor))\n          asset-map)))\n\n(defmethod pop-snapshot ((acceptor render-acceptor)\n                         (snapshot replay:snapshot))\n  (a:deletef (snapshots-company acceptor)\n             snapshot :key #'car)\n  (remhash (format nil \"~a\" (replay:uuid snapshot))  (acceptor-snapshots acceptor))\n  (remhash snapshot (Asset-maps acceptor)))\n\n\n(define-easy-handler (root :uri \"/root\" :acceptor-names '(replay)) ()\n  (let ((snapshot (car (loop for snapshot being the hash-values of (acceptor-snapshots *acceptor*)\n                             collect snapshot))))\n   (handle-asset\n    snapshot\n    (car (replay:root-assets snapshot)))))\n\n(define-easy-handler (debug-replay :uri \"/debug\" :acceptor-names '(replay)) ()\n  (format nil \"snapshots: ~S\"\n          (loop for key being the hash-keys of  (acceptor-snapshots hunchentoot:*acceptor*)\n                collect key)))\n\n(define-easy-handler (iframe-not-support\n                      :uri \"/iframe-not-supported\"\n                      :acceptor-names '(replay)) ()\n  \"<h1>iframe removed by Screenshotbot</h1>\")\n\n(define-easy-handler (replay.css :uri \"/css/replay.css\" :acceptor-names '(replay)) ()\n  (let ((file (path:catfile (document-root) \"css/replay.css\")))\n   (hunchentoot:handle-static-file\n    file)))\n\n(defun set-cache-control (seconds)\n  (setf (hunchentoot:header-out \"cache-control\" hunchentoot:*reply*)\n        (format nil \"max-age=~d\" seconds)))\n\n(with-auto-restart ()\n (defun handle-asset (snapshot asset)\n   (log:debug \"Starting with ~a\" asset)\n   (let ((if-modified-since (hunchentoot:header-in* :if-modified-since))\n         (last-modified (loop for header in (asset-response-headers asset)\n                              if (string-equal (http-header-name header)\n                                               \"last-modified\")\n                                return (http-header-value header))))\n     (cond\n       ((and if-modified-since\n             (string-equal if-modified-since last-modified))\n        (handle-asset-not-modified asset))\n       (t\n        (handle-asset-modified snapshot asset))))))\n\n(defun handle-asset-not-modified (asset)\n  (send-asset-headers asset :content-length nil)\n  (setf (hunchentoot:return-code*) hunchentoot:+http-not-modified+)\n  (hunchentoot:abort-request-handler))\n\n(defun handle-asset-modified (snapshot asset)\n  (flet ((fix-input-file (f)\n           (ensure-directories-exist\n            (cond\n              ((uiop:file-exists-p f)\n               f)\n              (t\n               ;; hack: please remove\n               (make-pathname :type \"tmp\"\n                              :defaults f)))))\n         (set-minimum-cache ()\n           (set-cache-control 300)))\n    (set-minimum-cache)\n    (let ((input-file (fix-input-file (replay:snapshot-asset-file snapshot asset))))\n      (setf (hunchentoot:return-code*)\n            (replay:asset-status asset))\n      (send-asset-headers asset\n                          :content-length\n                          ;; If the file does not exist (i.e. if the headers sent back 404,\n                          ;; we want to gracefully return a 404\n                          (cond\n                            ((path:-e input-file)\n                             (with-open-file (input input-file)\n                               (file-length input)))\n                            (t 0)))\n\n      (handler-case\n          (let ((out (hunchentoot:send-headers)))\n            (ecase (hunchentoot:request-method*)\n              (:head)\n              (:get\n               (when (path:-e input-file)\n                (send-file-to-stream input-file out))))\n            (finish-output out))\n        #+lispworks\n        (comm:socket-io-error ()))\n      (log:debug \"Done with ~a\" asset))))\n\n(defun send-asset-headers (asset &key content-length)\n  (loop for header in (replay:asset-response-headers asset)\n        for key = (replay:http-header-name header)\n        for val = (replay:http-header-value header)\n        do\n           (cond\n             ((member key (list \"transfer-encoding\") :test #'string-equal)\n              ;; do nothing\n              nil)\n             ((and\n               (string-equal \"cache-control\" key)\n               (< (parse-max-age val) 300))\n              (set-cache-control 300))\n             (t\n              (flet ((send-header (val)\n                       (setf (hunchentoot:header-out key hunchentoot:*reply*) val)))\n               (cond\n                 ((string-equal \"content-length\" key)\n                  ;; hunchentoot has special handling for\n                  ;; content-length. But also, we might have\n                  ;; modified the file since we downloaded it, so\n                  ;; we should use the updated length.\n                  (when content-length\n                    (send-header content-length)))\n                 (t\n                  (send-header val))))))))\n\n(defun send-file-to-stream (input-file out)\n  (assert (uiop:file-exists-p input-file))\n  (when (uiop:file-exists-p input-file)\n    (with-open-file (input input-file\n                           :element-type '(unsigned-byte 8))\n      (uiop:copy-stream-to-stream input out :element-type '(unsigned-byte 8)))))\n\n(defvar *lock* (bt:make-lock))\n(define-easy-handler (asset :uri (lambda (request)\n                                   (let ((script-name (hunchentoot:script-name request)))\n                                    (and\n                                     (str:starts-with-p \"/snapshot/\" script-name)\n                                     (str:containsp \"/assets/\" script-name))))\n                            :acceptor-names '(replay))\n    ()\n  (let* ((script-name (hunchentoot:script-name hunchentoot:*request*))\n         (parts (str:split \"/\" script-name))\n         (uuid (elt parts 2))\n         (asset-file-name (elt parts 4))\n         (snapshot (gethash uuid (acceptor-snapshots hunchentoot:*acceptor*))))\n    (unless snapshot\n      (error \"Could not find snapshot for uuid `~a`\" uuid))\n    (call-with-request-counter\n     snapshot\n     (lambda ()\n       (let* ((asset-map (gethash snapshot (asset-maps hunchentoot:*acceptor*)))\n              (asset (gethash asset-file-name asset-map)))\n         (cond\n           (asset\n            (handle-asset snapshot asset))\n           (t\n            (send-404 script-name))))))))\n\n(defun send-404 (script-name &optional (cache-time 60))\n  (log:debug \"No such asset: ~a\" script-name)\n  (setf (hunchentoot:return-code*) 404)\n  (set-cache-control cache-time)\n  \"No such asset\")\n\n(define-easy-handler (asset-from-company\n                      :uri (lambda (request)\n                             (let ((script-name (hunchentoot:script-name request)))\n                               (and\n                                (str:starts-with-p \"/company/\" script-name)\n                                (str:containsp \"/assets/\" script-name))))\n                            :acceptor-names '(replay))\n    ()\n  (let* ((script-name (hunchentoot:script-name hunchentoot:*request*))\n         (parts (str:split \"/\" script-name))\n         (company-oid (encrypt:decrypt-mongoid (elt parts 2)))\n         (asset-file-name (elt parts 4))\n         (company (find-by-oid company-oid)))\n    (check-type company company)\n    (handle-asset-from-company\n     hunchentoot:*acceptor*\n     company\n     asset-file-name)))\n\n(defun handle-asset-from-company (acceptor company asset-file-name)\n  (loop for (snapshot . check-company) in (snapshots-company acceptor)\n        for asset-map = (not-null! (gethash snapshot\n                                            (not-null!\n                                             (asset-maps acceptor))))\n        for asset = (gethash asset-file-name asset-map)\n        if (and (eql company check-company) asset)\n          do\n             (return\n               (handle-asset snapshot asset))\n        finally\n        (send-404 asset-file-name)))\n\n\n(defun hostname ()\n  (cond\n    ((and\n      (uiop:file-exists-p \"/.dockerenv\")\n      (equal \"thecharmer\" (uiop:hostname)))\n     \"staging\")\n    (t\n     \"replay\")))\n\n(defmacro with-hosted-snapshot ((hosted-url company snapshot &key (hostname '(hostname)))\n                                &body body)\n  `(call-with-hosted-snapshot\n    ,company\n    ,snapshot\n    (lambda (,hosted-url)\n      ,@body)\n    :hostname ,hostname))\n\n(defmethod call-with-hosted-snapshot ((company company)\n                                      (snapshot snapshot)\n                                      fn &key (hostname (hostname)))\n  (assert (functionp fn))\n  (push-snapshot (default-render-acceptor) company snapshot)\n  (unwind-protect\n       (let ((acceptor (default-render-acceptor))\n             (root-asset (car (replay:root-assets snapshot))))\n         (progn\n           (funcall fn (format nil \"http://~a:~a~a\"\n                               ;; IPv6 addresses contain colons and must be wrapped in brackets\n                               (if (find #\\: hostname)\n                                   (format nil \"[~a]\" hostname)\n                                   hostname)\n                               (hunchentoot:acceptor-port acceptor)\n                               (replay:asset-file root-asset)))))\n    (pop-snapshot (default-render-acceptor) snapshot)))\n"
  },
  {
    "path": "src/screenshotbot/replay/replay-regex.txt",
    "content": "@import[^;]*|[;\\s{]?\\*?[a-zA-Z\\-]+\\s*:#?[\\s\\S]*url\\(\\s*['\"]?[^'\"\\)\\s]+['\"]?\\s*\\)[^;}]*\nurl\\(\\s*['\"]?([^)'\"]+)['\"]?\\s*\\)\n"
  },
  {
    "path": "src/screenshotbot/replay/run-builder.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/run-builder\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/replay/run-builder)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:local-image\n                #:list-images)\n  (:import-from #:screenshotbot/model/image\n                #:with-tmp-image-file\n                #:image-blob\n                #:make-image\n                #:find-image)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:upload-image-directory)\n  (:import-from #:util/object-id\n                #:oid)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:dto #:screenshotbot/api/model))\n  (:export\n   #:recorder-run-builder\n   #:all-screenshots\n   #:record-screenshot))\n(in-package :screenshotbot/replay/run-builder)\n\n(defclass screenshot ()\n  ((title :initarg :title\n          :reader screenshot-title)\n   (image :initarg :image\n          :reader screenshot-image)))\n\n(defclass all-screenshots ()\n  ((screenshots :initform nil\n                :accessor screenshots)\n   (company :initarg :company\n            :accessor company)))\n\n(defmethod record-screenshot ((self all-screenshots)\n                              &key title md5 fetch)\n  (let ((hash md5))\n    (let ((image (or\n                  (find-image (company self) hash)\n                  (with-tmp-image-file (:pathname tmpfile)\n                    (delete-file tmpfile)\n                    (funcall fetch tmpfile)\n                    (make-image :company (company self)\n                                :pathname tmpfile\n                                :hash hash\n                                :verified-p t)))))\n      (atomics:atomic-push\n       (make-instance\n        'screenshot\n        :title title\n        :image image)\n       (slot-value self 'screenshots)))))\n\n\n(defmethod list-all-screenshots ((self all-screenshots))\n  (error \"unimplemented\"))\n\n\n(defmethod list-images ((self all-screenshots))\n  (error \"unimplemented\"))\n\n(defmethod upload-image-directory (api-context (self all-screenshots))\n  (declare (ignore api-context))\n  (loop for im in (remove-duplicates (screenshots self) :test #'equal :key #'screenshot-title)\n        collect\n        (make-instance 'dto:screenshot\n                       :image-id (oid (screenshot-image im))\n                       :name (screenshot-title im))))\n"
  },
  {
    "path": "src/screenshotbot/replay/safe-interrupt.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;; Safely interrupt a thread\n\n(defpackage :screenshotbot/replay/safe-interrupt\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/replay/safe-interrupt)\n\n(defun safe-interrupt-process (process)\n  )\n"
  },
  {
    "path": "src/screenshotbot/replay/services.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/services\n  (:use #:cl)\n  (:import-from #:screenshotbot/replay/proxy\n                #:selenium-host\n                #:selenium-port)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:replay-password)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:nicknames :screenshotbot/pro/replay/services)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:selenium-port\n   #:selenium-host\n   #:selenium-type\n   #:squid-proxy\n   #:linode?))\n(in-package :screenshotbot/replay/services)\n\n(named-readtables:in-readtable :interpol-syntax)\n\n(defclass selenium-server ()\n  ((host :initarg :host\n         :reader selenium-host)\n   (port :initarg :port\n         :reader selenium-port)\n   (type :initarg :type\n         :reader selenium-type)\n   (squid-proxy :initarg :squid-proxy\n                :reader squid-proxy)\n   (selenium-hub :initarg :selenium-hub)))\n\n(defmethod selenium-server-url ((self selenium-server))\n  (let ((host (selenium-host self)))\n    (format nil \"http://~a:~a\"\n            ;; IPv6 addresses contain colons and must be wrapped in brackets\n            (if (find #\\: host)\n                (format nil \"[~a]\" host)\n                host)\n            (selenium-port self))))\n\n(defun selenium-server (&key (type (error \"specify type\")))\n  (assert (member type '(\"firefox\" \"chrome\") :test #'equal))\n  (make-instance 'selenium-server\n                 ;; This is the IP of the replay server.\n                 :host \"172.30.1.180\"\n                 ;; This is a docker instance that is running on the the replay server.\n                 :squid-proxy \"squid:3128\"\n                 :port 5004\n                 :type nil))\n\n\n(defclass static-selenium-provider ()\n  ()\n  (:documentation \"An older selenium provider\"))\n\n(defmethod selenium-provider (installation)\n  (make-instance 'static-selenium-provider))\n\n(defmethod call-with-selenium-server ((self static-selenium-provider)\n                                      fn &key type)\n  (funcall fn\n           (selenium-server :type type)))\n\n(defmacro with-selenium-server ((var &rest args) &body body)\n  `(call-with-selenium-server\n    (selenium-provider *installation*)\n    (lambda (,var) ,@body)\n    ,@args))\n"
  },
  {
    "path": "src/screenshotbot/replay/sitemap.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/sitemap\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/replay/sitemap)\n  (:import-from #:screenshotbot/replay/core\n                #:write-replay-log)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:parse-sitemap))\n(in-package :screenshotbot/replay/sitemap)\n\n(defun read-loc (url)\n  (loop for attr in (xmls:node-children url)\n        if (string-equal \"loc\" (xmls:node-name attr))\n          return (car (xmls:node-children attr))))\n\n(defun parse-sitemap (url)\n  \"Gets the list of all URLS in a given sitemap file\"\n  (write-replay-log \"Fetching sitemap: ~a\" url)\n  (restart-case\n      (remove-duplicates\n       (let* ((content (dex:get url))\n              (root (xmls:parse content))\n              (urls (xmls:node-children root)))\n         (remove-if\n          #'null\n            (loop for url in urls\n                  if (equal \"url\" (xmls:node-name url))\n                    collect (read-loc url)\n                  if (equal \"sitemap\" (xmls:node-name url))\n                    append (parse-sitemap (read-loc url)))))\n       :test #'equal)\n    (retry-parse-sitemap ()\n      (parse-sitemap url))))\n"
  },
  {
    "path": "src/screenshotbot/replay/squid.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/squid\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/replay/squid)\n\n(def-easy-macro with-squid (&binding host &fn fn)\n  (let ((random-port (util/random-port:random-port)))\n   (tmpdir:with-tmpdir (cache-dir)\n     (tmpdir:with-tmpdir (log-dir)\n      (uiop:with-temporary-file (:pathname squid.conf :type \"conf\" :prefix \"squid\"\n                                 :stream squid.conf-stream)\n        (uiop:with-temporary-file (:pathname allowed-sites :prefix \"allowed_sites\"\n                                   :stream allowed-sites-stream)\n          (write-squid.conf squid.conf-stream\n                            :random-port random-port\n                            :cache-dir cache-dir\n                            :log-dir log-dir\n                            :allowed-sites allowed-sites)\n\n          (write-allowed-sites\n           allowed-sites-stream)\n\n          (with-open-file (squid-logs \"/tmp/squid-logs\"\n                                      :if-exists :append\n                                      :if-does-not-exist :create\n                                      :direction :output)\n            (let ((common-args (list\n                                \"-NYC\"\n                                \"-d\" \"3\"\n                                \"-n\" \"localsquid\"\n                                \"-f\" (namestring squid.conf))))\n              (uiop:run-program (list* \"/sbin/squid\"\n                                       \"-z\" common-args))\n              (let ((process-info (uiop:launch-program\n                                   (list* \"/sbin/squid\"\n                                          common-args)\n                                   :output :interactive\n                                   :error-output :interactive)))\n                (wait-for-port random-port)\n                (unwind-protect\n                     (let ((squid (format nil \"~a:~d\"\n                                          \"localhost\"\n                                          random-port)))\n                       (log:info \"Squid launched on ~S\" squid)\n                       (funcall fn squid))\n                  (uiop:terminate-process process-info)\n                  (log:info \"Waiting for squid process to terminate\")\n                  (uiop:wait-process process-info)\n                  (log:info \"Squid process terminated\")))))))))))\n\n(defun write-squid.conf (stream &key\n                                  random-port\n                                  log-dir\n                                  cache-dir\n                                  allowed-sites)\n  (format stream\n          \"http_port ~a\ncache allow all\ncache_log ~a\npid_filename ~a\ncache_effective_user proxy\nmemory_cache_shared off\ncache_dir aufs ~a 50000 16 256\nacl whitelist dstdomain \\\"~a\\\"\nhttp_access deny !whitelist\"\n          random-port\n          (path:catfile log-dir \"cache.log\")\n          (path:catfile log-dir \"pid-file\")\n          (namestring cache-dir)\n          (namestring allowed-sites))\n  (finish-output stream))\n\n(defun write-allowed-sites (stream)\n  ;; TODO: we can do better. Since we're generating this dynamically,\n  ;; we know exactly which IP address we need access to. But it'll\n  ;; have to passed in as an argument.\n  (format stream \"\nstaging\nreplay\n\n# static IP for staging\n172.29.1.15\n\n# second static IP for staging. It's unclear which network is used to\n# connect from staging to the squid proxy since both are two networks.\n172.20.0.6\n\n\n# static IP for replay\n172.29.1.14\n\n# production over OpenVPN\n10.9.8.1\n\n# debian-us-east-001\n45.33.86.76\")\n  (finish-output stream))\n\n(defun wait-for-port (port)\n  (loop for i from 0 to 50\n        do\n           (handler-case\n               (let ((socket (usocket:socket-connect \"127.0.0.1\" port)))\n                 (log:info \"Got socket connection\")\n                 (usocket:socket-close socket)\n                 (return))\n             (usocket:socket-error (e)\n               (log:info \"Socket not ready yet: ~a\" e)\n               (sleep 0.1)))\n        finally (error \"Squid did not launch\")))\n\n#+nil\n(with-squid (host)\n  (log:info \"Inside!\"))\n"
  },
  {
    "path": "src/screenshotbot/replay/test-core.lisp",
    "content": ";;;; -*- coding: utf-8 -*-\n;;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-core\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/replay/core\n                #:validate-url\n                #:*fetch-sleep-time*\n                #:replay-external-request-engine\n                #:blacklisted-ip\n                #:blacklisted-domain\n                #:blacklisted-domain-p\n                #:snapshot-request\n                #:http-header-value\n                #:http-header-name\n                #:asset-response-headers\n                #:request-counter\n                #:root-urls\n                #:root-assets\n                #:remove-unwanted-headers\n                #:+empty-headers+\n                #:%lru-cache\n                #:*cache*\n                #:fix-malformed-url\n                #:process-node\n                #:http-cache-dir\n                #:context\n                #:remote-response\n                #:guess-external-format\n                #:load-url-into\n                #:url\n                #:assets\n                #:snapshot\n                #:should-rewrite-url-p\n                #:read-srcset\n                #:push-asset\n                #:rewrite-css-urls\n                #:http-get)\n  (:import-from #:util/lru-cache\n                #:lru-cache)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json\n                #:encode-json)\n  (:import-from #:screenshotbot/replay/browser-config\n                #:dimensions\n                #:browser-config)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:dns-client #:org.shirakumo.dns-client)))\n(in-package :screenshotbot/replay/test-core)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key (setup-installation-p t))\n  (let ((*fetch-sleep-time* 0))\n   (flet ((body ()\n            (tmpdir:with-tmpdir (tmpdir)\n              (let ((*cache* (make-instance 'lru-cache\n                                            :dir tmpdir))\n                    (context (make-instance 'context)))\n                (cl-mock:with-mocks ()\n                  (&body))))))\n     (cond\n       (setup-installation-p\n        (with-installation ()\n          (body)))\n       (t\n        (body))))))\n\n\n(test url-rewriting\n  (let ((css \"foo {\nbackground: url(https://google.com)\n}\"))\n    (is\n     (equal\n      \"foo {\nbackground: url(shttps://google.com?f=1)\n}\"\n      (rewrite-css-urls css (lambda (url)\n                              (format nil \"s~a?f=1\" url)))))))\n\n(test url-rewriting-with-space\n  (let ((css \"foo {\nbackground: url(   https://google.com   ),\nbar: car\n}\"))\n    (is\n     (equal\n      \"foo {\nbackground: url(shttps://google.com?f=1),\nbar: car\n}\"\n      (rewrite-css-urls css (lambda (url)\n                              (format nil \"s~a?f=1\" url)))))))\n\n(test url-rewriting-with-quotes\n  (let ((css \"foo {\nbackground: url('https://google.com')\n}\"))\n    (is\n     (equal\n      \"foo {\nbackground: url(shttps://google.com?f=1)\n}\"\n      (rewrite-css-urls css (lambda (url)\n                              (format nil \"s~a?f=1\" url)))))))\n\n(test parenthesis-in-url-t2196\n  (let ((css \"foo {\nbackground-image: url(\\\"https://cdn.prod.website-files.com/640f69143ec11b21d42015c6/670fe1025fa272f3cda39a34_Trial%20CTA%20Footer_noise%20(1).png\\\");\n}\"))\n    (is\n     (equal\n      \"foo {\nbackground-image: url(https://example.com);\n}\"\n      (rewrite-css-urls css (lambda (url)\n                              (format nil \"https://example.com\" url)))))))\n\n(test url-rewriting-with-quotes-and-whitespace\n  (let ((css \"foo {\nbackground: url(   'https://google.com'   )\n}\"))\n    (is\n     (equal\n      \"foo {\nbackground: url(   shttps://google.com?f=1   )\n}\"\n      (rewrite-css-urls css (lambda (url)\n                              (format nil \"s~a?f=1\" url)))))))\n\n(test read-srcset\n  (is (eql nil (read-srcset \" \")))\n  (is (equal `((\"foo\" . \"20w\"))\n             (read-srcset \"foo 20w\")))\n  (is (equal `((\"foo\" . \"20w\"))\n             (read-srcset \"  foo    20w   \")))\n  (is (equal `((\"foo\" . \"20w\")\n               (\"bar\" . \"30w\"))\n             (read-srcset \"  foo    20w  ,bar 30w \")))\n  (is (equal `((\"foo\" . \"20w\")\n               (\"bar,0\" . \"30w\"))\n             (read-srcset \"  foo    20w  ,bar,0 30w \"))))\n\n(test should-rewrite-url-p\n  (is-true (should-rewrite-url-p \"https://foobar.com/foo\"))\n  (is-false (should-rewrite-url-p \"moz-extension://foobar.com/foo\")))\n\n\n(test push-asset-is-correctly-cached\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            #())\n                           200\n                           +empty-headers+)))\n\n     (let* ((snapshot (make-instance 'snapshot :tmpdir tmpdir))\n            (rand (random 10000000000))\n            (font (format nil \"https://screenshotbot.io/assets/fonts/metropolis/Metropolis-Bold-arnold.otf?id=~a\" rand))\n            (html (format nil \"https://screenshotbot.io/?id=~a\" rand)))\n\n       (push-asset context snapshot (quri:uri html) nil)\n       (is (equal 1 (length (assets snapshot))))\n       (push-asset context snapshot (quri:uri font)  t)\n       (is (equal 2 (length (assets snapshot))))\n       (is (equal font\n                  (url (car (Assets snapshot)))))\n       (push-asset context snapshot (quri:uri font)  t)\n\n       (is (equal 2 (length (assets snapshot))))\n       (push-asset context snapshot (quri:uri html) nil)\n       (is (equal 2 (length (assets snapshot))))\n       (push-asset context snapshot (quri:uri font)  t)\n       (is (equal 2 (length (assets snapshot))))))))\n\n(test happy-path-fetch-toplevel-only-once\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body></body></html>\"))\n                           200\n                           +empty-headers+)))\n\n     (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n       ;; Just verifying that on Windows, we don't keep any stale file descriptors around\n       (finishes (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/\") tmpdir))\n       (is (eql 1 (length (root-urls snapshot))))\n       (assert-that (car (root-urls snapshot))\n                    (is-equal-to\n                     \"https://screenshotbot.io/\"))\n       (assert-that (root-assets snapshot)\n                    (has-length 1))))))\n\n(test can-encode-snapshot\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body></body></html>\"))\n                           200\n                           +empty-headers+)))\n     (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n       (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/\") tmpdir)\n       (assert-that (assets snapshot)\n                    (has-length 1))\n       (flet ((check-snapshot (snapshot)\n                (is (eql 0 (request-counter snapshot)))\n                (let* ((asset (car (assets snapshot)))\n                       (header (find\n                                \"content-type\"\n                                (asset-response-headers asset)\n                                :key #'http-header-name\n                                :test #'string-equal)))\n                  (assert-that (mapcar #'http-header-name\n                                       (asset-response-headers asset))\n                               (has-item \"content-type\"))\n                  (is-true header)\n                  (is (equal \"text/html; charset=UTF-8\" (http-header-value header))))))\n         (check-snapshot snapshot)\n         (let* ((encoded\n                  (encode-json snapshot))\n                (snapshot (decode-json encoded 'snapshot)))\n           #+nil\n           (check-snapshot snapshot)))))))\n\n(test two-urls-with-same-content\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body></body></html>\"))\n                           200\n                           `((\"x-foobar\" . 1)))))\n\n     (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n       ;; Just verifying that on Windows, we don't keep any stale file descriptors around\n       (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/one\") tmpdir)\n       (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/two\") tmpdir)\n\n       (assert-that (root-assets snapshot)\n                    (has-length 2))\n       (assert-that (root-urls snapshot)\n                    (has-length 2))\n       (assert-that (sort (mapcar #'url (root-assets snapshot)) #'string<)\n                    (contains\n                     \"https://screenshotbot.io/one\"\n                     \"https://screenshotbot.io/two\"))))))\n\n(test identical-content-on-two-pages\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body></body></html>\"))\n                           200\n                           +empty-headers+)))\n\n     (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n       (finishes (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/\") tmpdir))\n       (finishes (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/foobar\") tmpdir))\n       (is (eql 2 (length (root-urls snapshot))))))))\n\n(test identical-content-on-two-pages-with-different-actual-url\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body></body></html>\"))\n                           200\n                           +empty-headers+)))\n\n     (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n       (finishes (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/deadbeaf/\") tmpdir :actual-url \"foobar-1\"))\n       (finishes (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/deadbeef/\") tmpdir\n                                :actual-url \"foobar-2\"))\n       (is (eql 2 (length (root-urls snapshot))))))))\n\n(test happy-path-fetch-toplevel\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body></body></html>\"))\n                           200\n                           +empty-headers+)))\n\n     (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n       (load-url-into context snapshot (quri:uri \"https://screenshotbot.io/\") tmpdir))\n          (let ((snapshot (make-instance 'snapshot :tmpdir tmpdir)))\n            (load-url-into context snapshot \"https://screenshotbot.io/\" tmpdir)\n            (pass)))))\n\n(test adds-screenshotbot-css\n  (with-fixture state ()\n    (let ((html (plump:parse \"<html><body>hello</body></html>\")))\n      (process-node (make-instance 'context)\n                    html\n                    (make-instance 'snapshot)\n                    \"https://www.google.com\")\n      (is (equal \"<html><body class=\\\" screenshotbot\\\">hello</body></html>\"\n                 (with-output-to-string (s)\n                  (plump:serialize html s))))\n      (pass))))\n\n\n(test utf-8\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body>©</body></html>\"\n                             :external-format :utf-8))\n                           200\n                           `((:content-type . \"text/html; charset=utf-8\")))))\n\n     (with-open-stream (content (http-get \"https://example.com\" :force-string t\n                                                                :force-binary nil))\n       (is (equal \"<html><body>©</body></html>\" (uiop:slurp-input-stream :string content))))\n     (with-open-stream (content (http-get \"https://example.com\" :force-string t\n                                                                :force-binary nil))\n       (is (equal \"<html><body>©</body></html>\" (uiop:slurp-input-stream :string content)))))))\n\n(test http-get-works-without-installation\n  \"In particular, the SDK will call this without an installation.\"\n  (with-fixture state (:setup-installation-p nil)\n   (tmpdir:with-tmpdir (tmpdir)\n     (cl-mock:if-called 'util/request:http-request\n                        (lambda (url &rest args)\n                          (values\n                           (flexi-streams:make-in-memory-input-stream\n                            (flexi-streams:string-to-octets\n                             \"<html><body>foo</body></html>\"\n                             :external-format :utf-8))\n                           200\n                           `((:content-type . \"text/html; charset=utf-8\")))))\n\n     (with-open-stream (content (http-get \"https://example.com\" :force-string t\n                                                                :force-binary nil))\n       (is (equal \"<html><body>foo</body></html>\" (uiop:slurp-input-stream :string content)))))))\n\n(test guess-external-format\n  (uiop:with-temporary-file (:pathname p)\n   (flet ((make-info (content-type)\n            (let ((map `((:content-type . ,content-type))))\n              (make-instance 'remote-response\n                             :headers map))))\n     (is (equal :utf-8\n                (guess-external-format (make-info \"text/html; charset=utf-8\") p)))\n     (is (equal :utf-8\n                (guess-external-format (make-info \"text/html; charset=UTF-8\") p)))\n     (is (equal :utf-8\n                (guess-external-format (make-info \"text/html; charset='utf-8' \") p))))))\n\n(test guess-external-format-from-content-utf-8\n  (uiop:with-temporary-file (:pathname p :stream s)\n   (flet ((make-info (content-type)\n            (let ((map `((:content-type . ,content-type))))\n              (make-instance 'remote-response\n                             :headers map))))\n     (write-string \"<html><head><meta charset='utf-8'></head></html>\"\n                   s)\n     (finish-output s)\n     (is (equal :utf-8\n                (guess-external-format (make-info \"text/html\") p))))))\n\n(test guess-external-format-from-content-latin-1\n  (uiop:with-temporary-file (:pathname p :stream s)\n   (flet ((make-info (content-type)\n            (let ((map `((:content-type . ,content-type))))\n              (make-instance 'remote-response\n                             :headers map))))\n     (write-string \"<html><head></head></html>\"\n                   s)\n     (finish-output s)\n     (is (equal :latin-1\n                (guess-external-format (make-info \"text/html\") p))))))\n\n(test http-cache-dir\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (util:*object-store*)\n      (let ((*cache* nil))\n        (is (path:-d (path:catdir (util/lru-cache::dir (%lru-cache)))))))))\n\n(test fix-malformed-url\n  (is (equal\n       \"https://www.rollins.edu/academic-advising/images/Tres%20Loch.jpg\"\n       (fix-malformed-url\n        \"https://www.rollins.edu/academic-advising/images/Tres Loch.jpg\"))))\n\n(test http-get-ignores-invalid-url\n  (with-fixture state ()\n    (multiple-value-bind (stream ret) (http-get \"http://example.com/????invalid\")\n      (close stream)\n      (is (equal\n           500\n           ret)))))\n\n(test remove-unwanted-headers\n  (is (equal\n       `((:foo . \"bar\"))\n       (remove-unwanted-headers\n        `((:x-foo-bar . \"bleh\")\n          (:foo . \"bar\")))))\n  (is (equal\n       `((:foo . \"bar\"))\n       (remove-unwanted-headers\n        `((:alt-svc . \"bleh\")\n          (:foo . \"bar\")))))\n    (is (equal\n       `((:foo . \"bar\"))\n       (remove-unwanted-headers\n        `((:content-security-policy . \"bleh\")\n          (:foo . \"bar\"))))))\n\n(test encode-decode-snapshot-request\n  (let ((snapshot-request (make-instance 'snapshot-request\n                                         :browser-configs\n                                         (list\n                                          (make-instance 'browser-config\n                                                         :name \"foobar\"\n                                                         :type \"chrome\"\n                                                         :dimensions (make-instance 'dimensions\n                                                                                    :width 1024\n                                                                                    :height 800)))\n                                         :channel-name \"bleh\")))\n    (finishes (encode-json snapshot-request)))\n  (let ((snapshot-request (make-instance 'snapshot-request\n                                         :browser-configs\n                                         (list\n                                          (make-instance 'browser-config\n                                                         :name \"foobar\"\n                                                         :type \"chrome\"\n                                                         :mobile-emulation \"foobar\"))\n                                         :channel-name \"bleh\")))\n    (finishes (encode-json snapshot-request))))\n\n\n;; TODO: these tests all hit the network!\n\n(test blacklisted-domain-p\n  (is-false (blacklisted-domain-p \"example.com\"))\n  (is-true (blacklisted-domain-p \"169.254.169.254\"))\n  (is-true (blacklisted-domain-p \"foo1.screenshotbot.io\"))\n  ;; To maintain current behavior, since we use this logic to test\n  ;; Screenshotbot itself.\n  (is-false (blacklisted-domain-p \"screenshotbot.io\")))\n\n(def-fixture blacklisting ()\n  (with-installation ()\n    (&body)))\n\n(test domain-is-actually-blacklisted\n  (with-fixture blacklisting ()\n    (signals blacklisted-domain\n      (util/request:http-request \"http://foo1.screenshotbot.io\"\n                                 :engine (replay-external-request-engine :installation)))))\n\n(test ports-are-blacklisted\n  (with-fixture blacklisting ()\n    (signals blacklisted-domain\n      (util/request:http-request \"http://example.com:34\"\n                                :engine (replay-external-request-engine :installation)))))\n\n(test bad-redirects\n  (with-fixture blacklisting ()\n    (signals blacklisted-domain\n      (util/request:http-request \"http://localhost\"\n                                 :engine (replay-external-request-engine :installation)))))\n\n#+nil\n(test bad-ip\n  (signals blacklisted-ip\n    (util/request:http-request \"http://lcl.tdrhq.com\"\n                               :engine *request-engine*)))\n\n(test validate-url-happy-path\n  (validate-url (quri:uri \"https://example.com\"))\n  (validate-url (puri:uri \"https://example.com\")))\n\n(test url-unescaped\n  (let ((found nil))\n    (is\n     (equal\n      \"background-image:url('foobar');\"\n      (rewrite-css-urls \"background-image:url(/assets/img/img_ynab_most_important_money_video.a35777e6.png);\"\n                        (lambda (url)\n                          (setf found url)\n                          \"'foobar'\"))))\n    (is\n     (equal \"/assets/img/img_ynab_most_important_money_video.a35777e6.png\"\n            found))))\n\n(test style-attributes-are-getting-parsed\n  (with-fixture state ()\n    (cl-mock:if-called 'util/request:http-request\n                       (lambda (url &rest args)\n                         (values\n                          (flexi-streams:make-in-memory-input-stream\n                           (flexi-streams:string-to-octets\n                            \"fake-image\"\n                            :external-format :utf-8))\n                          200\n                          `((:content-type . \"image/png\")))))\n    (let ((html (plump:parse \"<html><body><div style=\\\"background-image:url(/assets/img/img_ynab_most_important_money_video.a35777e6.png);\\\" >hello</div></body></html>\")))\n      (process-node (make-instance 'context)\n                    html\n                    (make-instance 'snapshot :tmpdir tmpdir)\n                    \"https://www.google.com\")\n      (assert-that\n       (with-output-to-string (s)\n         (plump:serialize html s))\n       (does-not\n        (matches-regex\n         \"<html><body class=\\\" screenshotbot\\\"><div style=\\\"background-image:url(.*ynab.*)\\\">hello</div></body></html>\"))))))\n\n(test bad-url-token\n  (with-fixture state ()\n    (cl-mock:if-called 'util/request:http-request\n                       (lambda (url &rest args)\n                         (values\n                          (flexi-streams:make-in-memory-input-stream\n                           (flexi-streams:string-to-octets\n                            \"fake-image\"\n                            :external-format :utf-8))\n                          200\n                          `((:content-type . \"image/png\")))))\n    (let ((html (plump:parse \"<html><body><div style=\\\"background-image:url(/assets/img/img_ynab_most_important_money_video.a35777e6.png);\\\" >hello</div></body></html>\")))\n      (process-node (make-instance 'context)\n                    html\n                    (make-instance 'snapshot :tmpdir tmpdir)\n                    \"https://www.google.com\")\n      (assert-that\n       (with-output-to-string (s)\n         (plump:serialize html s))\n       (does-not\n        (matches-regex\n         \"<html><body class=\\\" screenshotbot\\\"><div style=\\\"background-image:url(.*ynab.*)\\\">hello</div></body></html>\"))))))\n\n(test style-attributes-are-unchanged-in-the-good-case\n  (with-fixture state ()\n    (let ((html (plump:parse \"<html><body><div style=\\\"background-image:none;\\\" >hello</div></body></html>\")))\n      (process-node (make-instance 'context)\n                    html\n                    (make-instance 'snapshot :tmpdir tmpdir)\n                    \"https://www.google.com\")\n      (assert-that\n       (with-output-to-string (s)\n         (plump:serialize html s))\n       (matches-regex\n        \"<html><body class=\\\" screenshotbot\\\"><div style=\\\"background-image:none;\\\">hello</div></body></html>\")))))\n\n(test link-integrity-tags-are-removed\n  (with-fixture state ()\n    (let ((html (plump:parse \"<html><head><link rel=\\\"stylesheet\\\" href=\\\"https://example.com/style.css\\\" integrity=\\\"sha384-fake-hash-value\\\"></head><body>hello</body></html>\")))\n      (cl-mock:if-called 'drakma:http-request\n                         (lambda (&rest args)\n                           (values (make-string-input-stream \"\") 200 nil)))\n      (process-node (make-instance 'context)\n                    html\n                    (make-instance 'snapshot :tmpdir tmpdir)\n                    \"https://www.google.com\")\n      (assert-that\n       (with-output-to-string (s)\n         (plump:serialize html s))\n       (matches-regex \"link \")\n       (does-not\n        (matches-regex \"integrity=\"))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/replay/test-css-tokenizer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-css-tokenizer\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/replay/css-tokenizer\n                #:token-value\n                #:url-token\n                #:bad-url-token\n                #:string-token\n                #:bad-string-token\n                #:function-token\n                #:consume-url-token\n                #:consume-string-token\n                #:consume-ident-like-token\n                #:consume-ident-sequence\n                #:whitespacep\n                #:non-printable-p\n                #:newline-p\n                #:hex-digit-p\n                #:letter-p\n                #:non-ascii-p\n                #:ident-start-p\n                #:digit-p\n                #:ident-code-point-p)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that))\n(in-package :screenshotbot/replay/test-css-tokenizer)\n\n\n(util/fiveam:def-suite)\n\n\n(test whitespacep\n  (is-false (whitespacep #\\a))\n  (is-true (whitespacep #\\Return)))\n\n\n(test non-printable-p\n  (is-true (non-printable-p (code-char 0)))\n  (is-true (non-printable-p (code-char 8)))\n  (is-true (non-printable-p (code-char 11)))\n  (is-true (non-printable-p (code-char 31)))\n  (is-true (non-printable-p (code-char 127)))\n  (is-false (non-printable-p #\\a))\n  (is-false (non-printable-p #\\Space)))\n\n(test newline-p\n  (is-true (newline-p #\\Newline))\n  (is-true (newline-p #\\Return))\n  (is-true (newline-p #\\Page))\n  (is-false (newline-p #\\Space))\n  (is-false (newline-p #\\a)))\n\n(test hex-digit-p\n  (is-true (hex-digit-p #\\0))\n  (is-true (hex-digit-p #\\9))\n  (is-true (hex-digit-p #\\A))\n  (is-true (hex-digit-p #\\F))\n  (is-true (hex-digit-p #\\a))\n  (is-true (hex-digit-p #\\f))\n  (is-false (hex-digit-p #\\G))\n  (is-false (hex-digit-p #\\g))\n  (is-false (hex-digit-p #\\Space)))\n\n(test consume-url-token-basic\n  \"Test basic URL consumption\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://google.com)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://google.com\" (token-value token)))))\n\n(test consume-url-token-with-leading-whitespace\n  \"Test URL with leading whitespace\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"  https://example.com)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-url-token-with-trailing-whitespace\n  \"Test URL with trailing whitespace before closing paren\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://example.com  )\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-url-token-eof\n  \"Test URL without closing paren (EOF)\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://example.com\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-url-token-with-escaped-char\n  \"Test URL with escaped character\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://example.com/\\\\(test\\\\))\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com/(test)\" (token-value token)))))\n\n(test consume-url-token-with-hex-escape\n  \"Test URL with hex escape sequence\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://example.com/\\\\20test)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com/ test\" (token-value token)))))\n\n(test consume-url-token-bad-url-quote\n  \"Test bad URL with quote character\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://\\\"bad.com)\"))))\n    (assert-that token (has-typep 'bad-url-token))))\n\n(test consume-url-token-bad-url-single-quote\n  \"Test bad URL with single quote character\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://'bad.com)\"))))\n    (assert-that token (has-typep 'bad-url-token))))\n\n(test consume-url-token-bad-url-paren\n  \"Test bad URL with unescaped left parenthesis\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://(bad.com)\"))))\n    (assert-that token (has-typep 'bad-url-token))))\n\n(test consume-url-token-bad-url-non-printable\n  \"Test bad URL with non-printable character\"\n  (let ((token (consume-url-token\n                (make-string-input-stream\n                 (format nil \"https://bad~Ccom)\" (code-char 0))))))\n    (assert-that token (has-typep 'bad-url-token))))\n\n(test consume-url-token-bad-url-invalid-escape\n  \"Test bad URL with invalid escape (backslash before newline)\"\n  (let ((token (consume-url-token\n                (make-string-input-stream\n                 (format nil \"https://bad\\\\~Acom)\" #\\Newline)))))\n    (assert-that token (has-typep 'bad-url-token))))\n\n(test consume-url-token-bad-url-whitespace-not-followed-by-paren\n  \"Test bad URL with whitespace not followed by closing paren\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://bad.com more)\"))))\n    (assert-that token (has-typep 'bad-url-token))))\n\n(test consume-url-token-empty-url\n  \"Test empty URL\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \")\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"\" (token-value token)))))\n\n(test consume-url-token-with-path\n  \"Test URL with path and query\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://example.com/path?query=value)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com/path?query=value\" (token-value token)))))\n\n(test consume-url-token-with-fragment\n  \"Test URL with fragment\"\n  (let ((token (consume-url-token\n                (make-string-input-stream \"https://example.com#fragment)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com#fragment\" (token-value token)))))\n\n;;; consume-string-token tests\n\n(test consume-string-token-basic-double-quote\n  \"Test basic string with double quotes\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello world\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello world\" (token-value token)))))\n\n(test consume-string-token-basic-single-quote\n  \"Test basic string with single quotes\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello world'\")\n                #\\')))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello world\" (token-value token)))))\n\n(test consume-string-token-empty\n  \"Test empty string\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"\" (token-value token)))))\n\n(test consume-string-token-eof\n  \"Test string ending with EOF (parse error but returns token)\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello world\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello world\" (token-value token)))))\n\n(test consume-string-token-with-escaped-quote\n  \"Test string with escaped quote\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello \\\\\\\"world\\\\\\\"\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello \\\"world\\\"\" (token-value token)))))\n\n(test consume-string-token-with-escaped-single-quote\n  \"Test string with escaped single quote\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello \\\\'world\\\\'\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello 'world'\" (token-value token)))))\n\n(test consume-string-token-with-hex-escape\n  \"Test string with hex escape sequence\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello\\\\20world\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello world\" (token-value token)))))\n\n(test consume-string-token-with-escaped-backslash\n  \"Test string with escaped backslash\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello\\\\\\\\world\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello\\\\world\" (token-value token)))))\n\n(test consume-string-token-with-escaped-newline\n  \"Test string with escaped newline (backslash followed by newline)\"\n  (let ((token (consume-string-token\n                (make-string-input-stream\n                 (format nil \"hello\\\\~Aworld\\\"\" #\\Newline))\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"helloworld\" (token-value token)))))\n\n(test consume-string-token-bad-string-unescaped-newline\n  \"Test bad string with unescaped newline\"\n  (let ((token (consume-string-token\n                (make-string-input-stream\n                 (format nil \"hello~Aworld\\\"\" #\\Newline))\n                #\\\")))\n    (assert-that token (has-typep 'bad-string-token))))\n\n(test consume-string-token-bad-string-unescaped-return\n  \"Test bad string with unescaped carriage return\"\n  (let ((token (consume-string-token\n                (make-string-input-stream\n                 (format nil \"hello~Aworld\\\"\" #\\Return))\n                #\\\")))\n    (assert-that token (has-typep 'bad-string-token))))\n\n(test consume-string-token-bad-string-unescaped-page\n  \"Test bad string with unescaped form feed\"\n  (let ((token (consume-string-token\n                (make-string-input-stream\n                 (format nil \"hello~Aworld\\\"\" #\\Page))\n                #\\\")))\n    (assert-that token (has-typep 'bad-string-token))))\n\n(test consume-string-token-with-multiple-escapes\n  \"Test string with multiple escape sequences\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"\\\\22hello\\\\20world\\\\22\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"\\\"hello world\\\"\" (token-value token)))))\n\n(test consume-string-token-backslash-at-eof\n  \"Test string ending with backslash at EOF\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello\\\\\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello\" (token-value token)))))\n\n(test consume-string-token-with-special-chars\n  \"Test string with special characters\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"hello!@#$%^&*()_+-={}[]|:;<>?,./\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    (is (string= \"hello!@#$%^&*()_+-={}[]|:;<>?,./\" (token-value token)))))\n\n(test consume-string-token-unicode-escape\n  \"Test string with unicode escape\"\n  (let ((token (consume-string-token\n                (make-string-input-stream \"\\\\1F600\\\"\")\n                #\\\")))\n    (assert-that token (has-typep 'string-token))\n    ;; U+1F600 is the grinning face emoji\n    (is (string= (string (code-char #x1F600)) (token-value token)))))\n\n;;; Identifier helper function tests\n\n(test letter-p-test\n  (is-true (letter-p #\\a))\n  (is-true (letter-p #\\Z))\n  (is-true (letter-p #\\m))\n  (is-false (letter-p #\\0))\n  (is-false (letter-p #\\_))\n  (is-false (letter-p #\\-)))\n\n(test digit-p-test\n  (is-true (digit-p #\\0))\n  (is-true (digit-p #\\9))\n  (is-true (digit-p #\\5))\n  (is-false (digit-p #\\a))\n  (is-false (digit-p #\\A)))\n\n(test non-ascii-p-test\n  (is-true (non-ascii-p (code-char #x80)))\n  (is-true (non-ascii-p (code-char #x1F600)))\n  (is-false (non-ascii-p #\\a))\n  (is-false (non-ascii-p #\\0)))\n\n(test ident-start-p-test\n  (is-true (ident-start-p #\\a))\n  (is-true (ident-start-p #\\Z))\n  (is-true (ident-start-p #\\_))\n  (is-true (ident-start-p (code-char #x80)))\n  (is-false (ident-start-p #\\0))\n  (is-false (ident-start-p #\\-)))\n\n(test ident-code-point-p-test\n  (is-true (ident-code-point-p #\\a))\n  (is-true (ident-code-point-p #\\Z))\n  (is-true (ident-code-point-p #\\_))\n  (is-true (ident-code-point-p #\\0))\n  (is-true (ident-code-point-p #\\-))\n  (is-true (ident-code-point-p (code-char #x80)))\n  (is-false (ident-code-point-p #\\Space))\n  (is-false (ident-code-point-p #\\()))\n\n;;; consume-ident-sequence tests\n\n(test consume-ident-sequence-simple\n  \"Test simple identifier\"\n  (let ((ident (consume-ident-sequence\n                (make-string-input-stream \"hello\"))))\n    (is (string= \"hello\" ident))))\n\n(test consume-ident-sequence-with-digits\n  \"Test identifier with digits\"\n  (let ((ident (consume-ident-sequence\n                (make-string-input-stream \"test123\"))))\n    (is (string= \"test123\" ident))))\n\n(test consume-ident-sequence-with-hyphen\n  \"Test identifier with hyphen\"\n  (let ((ident (consume-ident-sequence\n                (make-string-input-stream \"test-name\"))))\n    (is (string= \"test-name\" ident))))\n\n(test consume-ident-sequence-with-underscore\n  \"Test identifier with underscore\"\n  (let ((ident (consume-ident-sequence\n                (make-string-input-stream \"_private\"))))\n    (is (string= \"_private\" ident))))\n\n(test consume-ident-sequence-stops-at-space\n  \"Test identifier stops at space\"\n  (let ((stream (make-string-input-stream \"hello world\")))\n    (let ((ident (consume-ident-sequence stream)))\n      (is (string= \"hello\" ident))\n      ;; Space should still be in stream\n      (is (char= #\\Space (read-char stream))))))\n\n(test consume-ident-sequence-stops-at-paren\n  \"Test identifier stops at parenthesis\"\n  (let ((stream (make-string-input-stream \"url(\")))\n    (let ((ident (consume-ident-sequence stream)))\n      (is (string= \"url\" ident))\n      ;; Paren should still be in stream\n      (is (char= #\\( (read-char stream))))))\n\n(test consume-ident-sequence-with-escape\n  \"Test identifier with escape sequence\"\n  (let ((ident (consume-ident-sequence\n                (make-string-input-stream \"hello\\\\20world\"))))\n    (is (string= \"hello world\" ident))))\n\n;;; consume-ident-like-token tests\n\n(test consume-ident-like-token-url-unquoted\n  \"Test url(...) with unquoted URL\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"url(https://example.com)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-ident-like-token-url-with-whitespace\n  \"Test url(...) with whitespace before URL\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"url(  https://example.com  )\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-ident-like-token-url-case-insensitive\n  \"Test URL is case-insensitive\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"URL(https://example.com)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-ident-like-token-url-mixed-case\n  \"Test url with mixed case\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"Url(https://example.com)\"))))\n    (assert-that token (has-typep 'url-token))\n    (is (string= \"https://example.com\" (token-value token)))))\n\n(test consume-ident-like-token-url-quoted-double\n  \"Test url(...) with double-quoted string returns function token\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"url(\\\"https://example.com\\\")\"))))\n    (assert-that token (has-typep 'function-token))\n    (is (string= \"url\" (token-value token)))))\n\n(test consume-ident-like-token-url-quoted-single\n  \"Test url(...) with single-quoted string returns function token\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"url('https://example.com')\"))))\n    (assert-that token (has-typep 'function-token))\n    (is (string= \"url\" (token-value token)))))\n\n(test consume-ident-like-token-url-quoted-with-whitespace\n  \"Test url(...) with quoted string and whitespace\"\n  (let ((token (consume-ident-like-token\n                (make-string-input-stream \"url(  \\\"https://example.com\\\"  )\"))))\n    (assert-that token (has-typep 'function-token))\n    (is (string= \"url\" (token-value token)))))\n\n(test consume-ident-like-token-url-quoted-stream-position\n  \"Test that stream is positioned at the quote after consuming url(\"\n  (let ((stream (make-string-input-stream \"url(\\\"test\\\")\")))\n    (let ((token (consume-ident-like-token stream)))\n      (assert-that token (has-typep 'function-token))\n      (is (string= \"url\" (token-value token)))\n      ;; The next character should be the opening quote\n      (is (char= #\\\" (peek-char nil stream))))))\n\n(test consume-ident-like-token-function-errors\n  \"Test regular function throws error (unimplemented)\"\n  (signals error\n    (consume-ident-like-token\n     (make-string-input-stream \"rgb(255, 0, 0)\"))))\n\n(test consume-ident-like-token-identifier-errors\n  \"Test identifier throws error (unimplemented)\"\n  (signals error\n    (consume-ident-like-token\n     (make-string-input-stream \"color\"))))\n"
  },
  {
    "path": "src/screenshotbot/replay/test-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-integration\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/replay/integration\n                #:with-sdk-flags\n                #:all-screenshots\n                #:process-results\n                #:replay-job-from-snapshot\n                #:remove-base-url\n                #:get-local-addr)\n  (:import-from #:screenshotbot/replay/core\n                #:asset\n                #:snapshot)\n  (:import-from #:screenshotbot/replay/browser-config\n                #:browser-config)\n  (:import-from #:screenshotbot/webdriver/impl\n                #:make-driver\n                #:call-with-webdriver)\n  (:import-from #:screenshotbot/webdriver/screenshot\n                #:full-page-screenshot)\n  (:import-from #:screenshotbot/replay/replay-acceptor\n                #:call-with-hosted-snapshot)\n  (:import-from #:util/store\n                #:*object-store*\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:prepare-singleton-company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:util/random-port\n                #:random-port)\n  (:import-from #:screenshotbot/server\n                #:acceptor)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:*synchronous-promotion*)\n  (:import-from #:bknr.datastore\n                #:blob-pathname)\n  (:import-from #:screenshotbot/model/image\n                #:with-local-image\n                #:image\n                #:image-blob)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/task-integration-api\n                #:send-task)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-send-tasks)\n  (:import-from #:screenshotbot/replay/proxy\n                #:ensure-proxy\n                #:*replay-proxy*\n                #:*proxy-port*)\n  (:import-from #:screenshotbot/replay/integration\n                #:write-full-page-screenshot-from-handle\n                #:fetch-full-page-screenshot-handle)\n  (:import-from #:hunchentoot\n                #:single-threaded-taskmaster)\n  (:import-from #:util/testing\n                #:with-global-binding)\n  (:import-from #:screenshotbot/replay/services\n                #:selenium-server\n                #:call-with-selenium-server)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains\n                #:has-item)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:util/request\n                #:*engine*)\n  (:import-from #:util/hunchentoot-engine\n                #:hunchentoot-engine)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:integration #:screenshotbot/replay/integration)\n                    (#:run-builder #:screenshotbot/replay/run-builder)))\n(in-package :screenshotbot/replay/test-integration)\n\n\n(util/fiveam:def-suite)\n\n(test script-name\n  (is (equal \"/foo\" (remove-base-url \"https://google.com/foo\")))\n  (is (equal \"/foo?bar=car\" (remove-base-url \"https://google.com/foo?bar=car\"))))\n\n(defclass fake-driver ()\n  ())\n\n(defmethod full-page-screenshot ((driver fake-driver) file)\n  (error \"this method should not be directly called\"))\n\n(defmethod fetch-full-page-screenshot-handle ((driver fake-driver) proxy)\n  (values \"123\" (md5:md5sum-string \"foobar\")))\n\n(defmethod write-full-page-screenshot-from-handle ((driver fake-driver)\n                                                   proxy\n                                                   oid file)\n  (with-open-file (stream file :direction :output)\n    (write-string \"foobar\" stream)))\n\n(defclass test-installation (multi-org-feature\n                             installation)\n  ())\n\n(defmacro with-test-globals (&body body)\n  `(let ((auto-restart:*global-enable-auto-retries-p* nil))\n     (tmpdir:with-tmpdir (tmp-store-dir)\n       (with-global-binding ((*installation* (make-instance 'test-installation :plugins nil))\n                             (*object-store* (namestring tmp-store-dir))\n                             (*synchronous-promotion* t))\n         ,@body))))\n\n(defmacro with-proxy (&body body)\n  `(let ((*proxy-port* (util/random-port:random-port))\n         (*replay-proxy* nil))\n     (unwind-protect\n          (progn ,@body)\n       (when *replay-proxy*\n         (hunchentoot:stop *replay-proxy*)))))\n\n(def-fixture state (&key host)\n  (with-installation ()\n   (with-global-binding ((lparallel:*kernel* nil))\n     (with-test-store (:globally t)\n       (with-test-globals\n         (tmpdir:with-tmpdir (tmpdir)\n           (cl-mock:with-mocks ()\n             (with-open-file (dummy (path:catfile tmpdir \"foo.html\") :direction :output)\n               (write-string \"foo\" dummy))\n             (let* ((assets (list (make-instance 'asset\n                                                 :url \"https://www.google.com\"\n                                                 :status 200\n                                                 :response-headers nil\n                                                 :file \"/foo.html\")))\n                    (snapshot (make-instance 'snapshot :tmpdir tmpdir\n                                                       :root-urls (list\n                                                                    \"https://www.google.com\" )\n                                                       :assets assets))\n                    (company (make-instance 'company))\n                    (user (make-user\n                           :companies (list company)))\n                    (run (make-instance 'integration:run\n                                        :company company\n                                        :user user\n                                        :host host\n                                        :channel \"test-channel\"\n                                        :browser-configs\n                                        (list\n                                         (make-instance 'browser-config\n                                                        :name \"firefox\"\n                                                        :type \"firefox\"))))\n                    (fake-driver (make-instance 'fake-driver)))\n               (cl-mock:if-called 'call-with-selenium-server\n                                  (lambda (self fn &key type)\n                                    (funcall fn\n                                             (make-instance 'selenium-server\n                                                            :host \"foo.bar\"\n                                                            :squid-proxy \"foo.bar:3128\"\n                                                            :port 9093\n                                                            :type nil))))\n               (cl-mock:if-called 'get-local-addr\n                                  (lambda (&rest args)\n                                    \"de.ad.be.ef\"))\n\n               (cl-mock:if-called 'call-with-webdriver\n                                  (lambda (fn &rest args)\n                                    (let ((webdriver-client::*session*\n                                            (make-instance 'webdriver-client::session\n                                                           :id \"dummy-test-session\")))\n                                      (funcall fn fake-driver))))\n               (cl-mock:if-called '(setf webdriver-client:url)\n                                  (lambda (url)\n                                    nil))\n               (cl-mock:if-called 'call-with-hosted-snapshot\n                                  (lambda (company snapshot fn &rest args)\n                                    (funcall fn \"http://fake-url/\")))\n\n               (cl-mock:if-called 'ensure-proxy\n                                  (lambda (selenium-service)\n                                    \"http://fakehost:5003\"))\n               (&body)))))))))\n\n(test replay-job-from-snapshot\n  (with-fixture state ()\n    (is (typep\n         (replay-job-from-snapshot\n          :snapshot snapshot\n          :urls (list\n                 (cons \"/\" \"https://www.google.com\"))\n          :run run\n          :tmpdir tmpdir)\n         'all-screenshots))))\n\n(test process-results\n  (with-installation (:globally t)\n    (let ((engine (make-instance 'hunchentoot-engine\n                                   :acceptor (make-instance 'acceptor)))\n          (auto-restart:*global-enable-auto-retries-p* nil))\n     (with-fixture state (:host \"localhost:9098\")\n       (unwind-protect\n            (let ((all-screenshots (make-instance 'all-screenshots\n                                                  :company company)))\n              (let ((pathname #.(asdf:system-relative-pathname\n                                 :screenshotbot\n                                 \"fixture/rose.png\")))\n                (run-builder:record-screenshot\n                 all-screenshots\n                 :title \"rose\"\n                 :md5 (md5:md5sum-file pathname)\n                 :fetch (lambda (dest)\n                          (fad:copy-file pathname dest))))\n              (finishes\n                (process-results\n                 run\n                 all-screenshots\n                 :engine engine))))))))\n\n(test if-old-image-is-rewritten-on-disk-we-still-dont-reupload\n  (with-installation (:globally t)\n    (let ((engine (make-instance 'hunchentoot-engine\n                                 :acceptor (make-instance 'acceptor)))\n          (auto-restart:*global-enable-auto-retries-p* nil))\n      (with-fixture state (:host \"localhost:9098\")\n        (unwind-protect\n             (let ((all-screenshots (make-instance 'all-screenshots\n                                                   :company company)))\n               (let ((pathname #.(asdf:system-relative-pathname\n                                  :screenshotbot\n                                  \"fixture/rose.png\")))\n                 (run-builder:record-screenshot\n                  all-screenshots\n                  :title \"rose\"\n                  :md5 (md5:md5sum-file pathname)\n                  :fetch (lambda (dest)\n                           (fad:copy-file pathname dest))))\n               (finishes\n                 (process-results\n                  run\n                  all-screenshots\n                  :engine engine))\n               (let ((objs (bknr.datastore:store-objects-with-class 'image)))\n                 (is (eql 1 (length objs))))\n               (loop for im in (bknr.datastore:store-objects-with-class 'image)\n                     do\n                        (with-local-image (pathname im)\n                          (fad:copy-file\n                           #.(asdf:system-relative-pathname\n                              :screenshotbot\n                              \"fixture/rose.webp\")\n                           pathname\n                           :overwrite t)))\n\n               (process-results\n                run\n                all-screenshots\n                :engine engine)\n\n               (is (eql 1 (length (bknr.datastore:store-objects-with-class 'image))))))))))\n\n(def-fixture state-2 ()\n  (with-installation ()\n    (with-global-binding ((lparallel:*kernel* nil))\n     (with-test-store ()\n       (tmpdir:with-tmpdir (tmpdir)\n         (cl-mock:with-mocks ()\n           (cl-mock:if-called 'get-local-addr\n                              (lambda (&rest args)\n                                \"9.9.9.9\"))\n           (cl-mock:if-called 'hunchentoot:start\n                              (lambda (acceptor) (declare (ignore acceptor))))\n           (let* ((company (make-instance 'company))\n                  (*default-render-acceptor* nil)\n                  (asset (make-instance 'asset\n                                        :url \"https://foo.com\"\n                                        :file \"foo\"))\n                  (snapshot (make-instance 'snapshot\n                                           :assets (list asset)\n                                           :root-urls (list \"https://foo.com\")))\n                  (run (make-instance 'integration:run\n                                      :company company\n                                      :browser-configs\n                                      (list (make-instance 'browser-config\n                                                           :type \"chrome\")))))\n             (&body))))))))\n\n(test replay-job-from-snapshot-2\n  (with-fixture state-2 ()\n    (cl-mock:if-called 'call-with-webdriver\n                        (lambda (&rest args)\n                          (declare (ignore args))))\n    (replay-job-from-snapshot\n     :run run\n     :snapshot snapshot\n     :urls (list \"https://www.google.com\")\n     :tmpdir tmpdir)\n    (pass)))\n\n\n(def-fixture fake-acceptor (&rest options)\n  ;; We're using a hunchentoot server here since it's the most common\n  ;; use case. It also correctly works on both IPv6 and IPv4.\n  (let* ((port (random-port))\n         (acceptor (apply #'make-instance 'hunchentoot:acceptor :port\n                          port\n                          options)))\n    (unwind-protect\n         (progn\n           (hunchentoot:start acceptor)\n           (&body))\n      (hunchentoot:stop acceptor))))\n\n(test get-local-addr\n  (with-fixture fake-acceptor ()\n    (is (equal \"127.0.0.1\"\n               (get-local-addr \"127.0.0.1\" port)))))\n\n#+lispworks ;; currently breaking badly on SBCL and CCL\n(test get-local-addr-on-ipv6\n  (with-fixture fake-acceptor (:address \"::1\")\n    (is (equal \"::1\"\n               (get-local-addr \"::1\" port)))))\n\n(test exclusions-on-sitemap\n  (with-fixture state ()\n    (cl-mock:if-called 'integration::parse-sitemap\n                       (lambda (url)\n                         (list\n                          \"https://example.com/google\"\n                          \"https://example.com/facebook\")))\n    (let ((run (make-instance 'integration:run\n                              :sitemap \"https://www.google.com/sitemap.xml\"\n                              :exclusions (list \".*face.*\"))))\n      (assert-that (mapcar #'car (integration::urls run))\n                   (is-not (has-item \"/facebook\"))\n                   (has-item \"/google\")))))\n\n(test with-sdk-flags-happy-path\n  (let (result)\n    (with-sdk-flags (:flags `((:metadata . \"foobar\")))\n      (setf result :pass))\n    (is (eql :pass result))))\n\n(test with-sdk-flags-unknown-flag\n  (let (result)\n    (with-sdk-flags (:flags `((:unknown-flag . \"foobar\")\n                              (:metadata . \"bleh\")))\n      (is (equal \"bleh\" screenshotbot/sdk/flags:*metadata*))\n      (setf result :pass))\n    (is (eql :pass result))))\n"
  },
  {
    "path": "src/screenshotbot/replay/test-remote.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-remote\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/replay/remote\n                #:started-at\n                #:finished-at\n                #:remote-run-status\n                #:remote-run)\n  (:import-from #:bknr.datastore\n                #:with-transaction))\n(in-package :screenshotbot/replay/test-remote)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test setf-remote-run-status\n  (with-fixture state ()\n    (let ((run (make-instance 'remote-run)))\n      (with-transaction ()\n        (setf (remote-run-status run) :success))\n      (is (> (finished-at run) 0)))))\n\n(test setf-remote-run-status-running\n  (with-fixture state ()\n    (let ((run (make-instance 'remote-run)))\n      (with-transaction ()\n        (setf (remote-run-status run) :running))\n      (is (> (started-at run) 0)))))\n"
  },
  {
    "path": "src/screenshotbot/replay/test-replay-acceptor.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-replay-acceptor\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/replay/replay-acceptor\n                #:handle-asset-modified\n                #:acceptor-snapshots\n                #:asset-maps\n                #:pop-snapshot\n                #:snapshots-company\n                #:handle-asset\n                #:handle-asset-from-company\n                #:push-snapshot\n                #:render-acceptor\n                #:default-render-acceptor\n                #:*default-render-acceptor*)\n  (:import-from #:util/testing\n                #:with-fake-request\n                #:with-local-acceptor)\n  (:import-from #:screenshotbot/replay/core\n                #:http-header\n                #:assets\n                #:uuid\n                #:snapshot\n                #:asset)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/object-id\n                #:oid-array)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:flexi-streams\n                #:make-in-memory-output-stream)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/replay/test-replay-acceptor)\n\n(util/fiveam:def-suite)\n\n(defparameter *fixture* (asdf:system-relative-pathname\n                         :screenshotbot\n                         \"fixture/rose.png\"))\n\n(def-fixture state (&key response-headers\n                    (status 200))\n  (with-test-store (:globally t)\n    (tmpdir:with-tmpdir (tmpdir)\n     (with-local-acceptor (host :acceptor acceptor)\n         ('render-acceptor)\n       (unless (eql status 404)\n         (fad:copy-file *fixture* (path:catfile tmpdir \"abcd00.png\")))\n       (with-global-binding ((*default-render-acceptor* acceptor))\n        (let* ((company (make-instance 'company))\n               (snapshot (make-instance 'snapshot\n                                        :tmpdir tmpdir))\n               (asset (make-instance\n                       'asset\n                       :status status\n                       :url \"https://www.example.com\"\n                       :file (format nil \"/snapshot/~a/assets/abcd00.png\"\n                                     (uuid snapshot))\n                       :response-headers\n                       (list* (make-instance 'http-header\n                                             :name \"Content-type\"\n                                             :value \"image/png\")\n                              response-headers))))\n          (setf (assets snapshot)\n                (list asset))\n          (&body)))))))\n\n(test simple-loading\n  (with-fixture state ()\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/snapshot/~a/assets/abcd00.png\"\n                       host\n                       (uuid snapshot))))\n      (multiple-value-bind (stream ret)\n          (util/request:http-request url\n                                     :force-binary t\n                                     :want-stream t)\n        (with-open-stream (stream stream)\n          (is (equal 200 ret))\n          (is (equalp (md5:md5sum-file *fixture*)\n                      (ironclad:digest-stream :md5 stream))))))))\n\n\n(test expect-404-for-non-existent-assets\n  (with-fixture state ()\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/snapshot/~a/assets/abcd01.png\"\n                       host\n                       (uuid snapshot))))\n      (signals dex:http-request-not-found\n        (dex:get url)))))\n\n(test loading-by-company\n  (with-fixture state ()\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (multiple-value-bind (stream ret headers)\n          (util/request:http-request url\n                                     :force-binary t\n                                     :headers-as-hash-table t\n                                     :want-stream t)\n        (with-open-stream (stream stream)\n          (is (equal 200 ret))\n          (is (equalp (md5:md5sum-file *fixture*)\n                      (ironclad:digest-stream :md5 stream))))\n\n        ;; verify minimum caching\n        (is (equal \"max-age=300\" (gethash \"cache-control\" headers)))))))\n\n(test http-revalidate-by-HEAD\n  (with-fixture state ()\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (multiple-value-bind (data ret headers)\n          (dex:head url)\n        (is (equalp #() data))\n        (is (equal 200 ret))\n\n        ;; verify minimum caching\n        (is (equal \"max-age=300\" (gethash \"cache-control\" headers)))))))\n\n(test dont-override-max-age-for-large\n  (with-fixture state (:response-headers (list (make-instance 'http-header\n                                                               :name \"Cache-Control\"\n                                                               :value \"max-age=360000\")))\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (multiple-value-bind (stream ret headers)\n          (util/request:http-request url\n                                     :force-binary t\n                                     :headers-as-hash-table t\n                                     :want-stream t)\n        (with-open-stream (stream stream)\n          (is (equal 200 ret))\n          (is (equalp (md5:md5sum-file *fixture*)\n                      (ironclad:digest-stream :md5 stream))))\n\n        ;; max-age should not be overwritten\n        (is (equal \"max-age=360000\" (gethash \"cache-control\" headers)))))))\n\n(test small-max-ages-are-overwritten\n  (with-fixture state (:response-headers (list (make-instance 'http-header\n                                                               :name \"Cache-Control\"\n                                                               :value \"max-age=2\")))\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (multiple-value-bind (stream ret headers)\n          (util/request:http-request url\n                                     :force-binary t\n                                     :headers-as-hash-table t\n                                     :want-stream t)\n        (with-open-stream (stream stream)\n          (is (equal 200 ret))\n          (is (equalp (md5:md5sum-file *fixture*)\n                      (ironclad:digest-stream :md5 stream))))\n\n        ;; max-age should not be overwritten\n        (is (equal \"max-age=300\" (gethash \"cache-control\" headers)))))))\n\n(test expect-404-for-non-existent-assets-by-company\n  (with-fixture state ()\n    (let ((url (format nil \"~a/company/~a/assets/abcd01.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (handler-case\n          (progn\n            (dex:get url)\n            (fail \"expected error\"))\n        (dex:http-request-not-found (e)\n          (is (equal \"max-age=60\" (gethash \"cache-control\" (dex:response-headers e)))))))))\n\n(test expect-404-for-non-existent-file\n  (with-fixture state ()\n    (let ((url (format nil \"~a/foo/bar.png\"\n                       host)))\n      (handler-case\n          (progn\n            (dex:get url)\n            (fail \"expected error\"))\n        (dex:http-request-not-found (e)\n          (is (equal \"max-age=3600\" (gethash \"cache-control\" (dex:response-headers e)))))))))\n\n\n(test handle-asset-from-company\n  (with-fixture state ()\n    (push-snapshot acceptor company snapshot)\n    (cl-mock:with-mocks ()\n     (let ((called-args nil))\n       (cl-mock:if-called 'handle-asset\n                           (lambda (snapshot asset)\n                             (setf called-args (list snapshot asset))))\n       (handle-asset-from-company acceptor company \"abcd00.png\")\n       (is (equal (list snapshot asset)\n                  called-args))))))\n\n(test handle-asset-not-modified\n  (with-fixture state (:response-headers (list\n                                          (make-instance 'http-header\n                                                          :name \"Last-Modified\"\n                                                          :value \"Wed, 21 Oct 2015 07:28:00 GMT\")))\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (handler-case\n          (multiple-value-bind (data ret headers)\n              (dex:get url\n                       :headers `((\"If-modified-since\" . \"Wed, 21 Oct 2015 07:28:00 GMT\")))\n            (is (equalp #() data))\n            (is (eql 304 ret)))))))\n\n(test handle-asset-not-modified-with-content-length\n  (with-fixture state (:response-headers (list\n                                          (make-instance 'http-header\n                                                          :name \"content-length\"\n                                                          :value 20)\n                                          (make-instance 'http-header\n                                                          :name \"Last-Modified\"\n                                                          :value \"Wed, 21 Oct 2015 07:28:00 GMT\")))\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (handler-case\n          (multiple-value-bind (data ret headers)\n              (dex:get url\n                       :headers `((\"If-modified-since\" . \"Wed, 21 Oct 2015 07:28:00 GMT\")))\n            (is (equalp #() data))\n            (is (eql 304 ret)))))))\n\n(test 404-without-asset-file\n  (with-fixture state (:status 404)\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (signals dexador.error:http-request-not-found\n        (dex:get url)))))\n\n(test handle-asset-modified-on-404\n  (with-fixture state (:status 404)\n    (push-snapshot acceptor company snapshot)\n    (let ((url (format nil \"~a/company/~a/assets/abcd00.png\"\n                       host\n                       (encrypt:encrypt-mongoid (oid-array company)))))\n      (with-fake-request ()\n        (let ((hunchentoot::*hunchentoot-stream*\n                (make-in-memory-output-stream)))\n         (finishes\n           (handle-asset-modified snapshot asset)))))))\n\n(test cleanup-pop-snapshot\n  (with-fixture state ()\n    (is (eql 0 (length (snapshots-company acceptor))))\n    (push-snapshot acceptor company snapshot)\n    (is (eql 1 (length (snapshots-company acceptor))))\n    (pop-snapshot acceptor snapshot)\n    (is (eql 0 (length (snapshots-company acceptor))))\n    (is (eql '()\n              (a:hash-table-keys (asset-maps acceptor))))\n    (is (eql '()\n             (a:hash-table-keys (acceptor-snapshots acceptor))))))\n"
  },
  {
    "path": "src/screenshotbot/replay/test-run-builder.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-run-builder\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/replay/run-builder\n                #:screenshots\n                #:record-screenshot\n                #:all-screenshots)\n  (:import-from #:util/copy-file\n                #:copy-file-fast)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/testing\n                #:with-installation))\n(in-package :screenshotbot/replay/test-run-builder)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-installation ()\n     (let ((company (make-instance 'company)))\n       (&body)))))\n\n(test simple-record-screenshot\n  (with-fixture state ()\n   (let ((all-screenshots (make-instance 'all-screenshots\n                                         :company company)))\n     (record-screenshot\n      all-screenshots\n      :title \"foobar\"\n      :md5 \"aaaa\"\n      :fetch (lambda (tmpfile)\n               (copy-file-fast #.(asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\")\n                               tmpfile)))\n     (is (eql 1 (length (screenshots all-screenshots)))))))\n"
  },
  {
    "path": "src/screenshotbot/replay/test-sitemap.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/replay/test-sitemap\n  (:use #:cl\n        #:fiveam\n        #:screenshotbot/replay/sitemap)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/replay/test-sitemap)\n\n\n(util/fiveam:def-suite)\n(def-fixture mocked-network ()\n  (cl-mock:with-mocks ()\n    (cl-mock:answer dex:get\n      (uiop:read-file-string #.(asdf:system-relative-pathname :screenshotbot \"replay/fixtures/sitemap-index.xml\"))\n      (uiop:read-file-string #.(asdf:system-relative-pathname :screenshotbot \"replay/fixtures/sitemap-0.xml\")))\n    (&body)))\n\n(test sitemap\n  (with-fixture mocked-network ()\n   (is\n    (str:s-member (parse-sitemap \"https://www.rollins.edu/sitemap-index.xml\")\n                  \"https://www.rollins.edu/academics/latin-american-caribbean-studies\"))))\n"
  },
  {
    "path": "src/screenshotbot/report-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :screenshotbot/report-api\n    (:use #:cl #:alexandria)\n  (:export\n   #:report-title\n   #:report-run\n   #:report\n   #:report-previous-run\n   #:screenshot-lang\n   #:screenshot-device\n   #:report-acceptable\n   #:row-filter\n   #:render-diff-report))\n(in-package :screenshotbot/report-api)\n"
  },
  {
    "path": "src/screenshotbot/run-prod",
    "content": "#!/bin/sh\n"
  },
  {
    "path": "src/screenshotbot/s3/backup.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/s3/backup\n  (:use #:cl)\n  (:import-from #:screenshotbot/s3/core\n                #:upload-file))\n(in-package :screenshotbot/s3/backup)\n\n(defmethod backup-directory (store\n                             directory\n                             key)\n  #+(and linux lispworks)\n  (uiop:with-temporary-file (:pathname p :direction :output\n                                       :type \"tar.gz\")\n    (sys:run-shell-command\n     (list\n      \"/bin/tar\"\n      \"cz\"\n      \"-C\" (namestring directory)\n      \"./\")\n     :output p\n     :if-output-exists :supersede)\n    (log:info \"Uploading archive to ~a on s3\" key)\n    (upload-file store p key)\n    (log:info \"Archive upload done\")))\n"
  },
  {
    "path": "src/screenshotbot/s3/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/s3/core\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:zs3\n                #:secret-key\n                #:access-key\n                #:*credentials*\n                #:*s3-endpoint*)\n  (:import-from #:screenshotbot/installation\n                #:null-s3-store)\n  (:import-from #:util/misc\n                #:safe-ensure-directories-exist)\n  (:import-from #:util/threading\n                #:with-extras\n                #:make-thread\n                #:max-pool\n                #:ignore-and-log-errors)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:null-store\n   #:s3-store-update-remote))\n(in-package :screenshotbot/s3/core)\n\n(defclass base-store ()\n  ())\n\n(defclass s3-store (base-store)\n  ((endpoint :initarg :endpoint\n             :reader endpoint)\n   (bucket :initarg :bucket\n           :reader bucket)\n   (access-key :initarg :access-key\n               :reader access-key)\n   (secret-access-key :initarg :secret-access-key\n                      :reader secret-key))\n  (:documentation \"An S3 copy of the object-store\"))\n\n(defvar *upload-pool*\n  (make-instance 'max-pool :max 40))\n\n(def-easy-macro with-store (store &fn fn)\n  (let ((*s3-endpoint* (endpoint store))\n        (*credentials* store))\n    (funcall fn)))\n\n(defmethod upload-file :around (store file key)\n  (with-extras ((\"s3-key\" key))\n    (call-next-method)))\n\n(auto-restart:with-auto-restart (:retries 3 :sleep 5)\n  (defmethod upload-file ((store s3-store) file key)\n    (with-store (store)\n      (zs3:put-file\n       file\n       (bucket store)\n       key))))\n\n(defmethod upload-file ((store null-s3-store) file key)\n  nil)\n\n(defmethod s3-store-update-remote ((store s3-store) file key)\n  (make-thread\n   (lambda ()\n     (upload-file store file key))\n   :pool *upload-pool*\n   :name \"S3 upload\"))\n\n(defmethod s3-store-update-remote ((store null-s3-store) file key)\n  nil)\n\n(defmethod s3-store-fetch-remote :before ((store base-store) file key)\n  (safe-ensure-directories-exist file))\n\n(defmethod s3-store-fetch-remote ((store null-s3-store) file key)\n  (error \"Recovering images not supported: Can't fetch remote file from null s3 store\"))\n\n(auto-restart:with-auto-restart (:retries 3)\n  (defmethod s3-store-fetch-remote ((store s3-store) file key)\n    (with-store (store)\n      (zs3:get-file (bucket store) key file))))\n\n#|\n(upload-file *store* \"/home/arnold/builds/web/backup.sh\"\n\n\"test-backup.sh\")\n|#\n"
  },
  {
    "path": "src/screenshotbot/s3-policy.json",
    "content": "{\n   \"Version\":\"2012-10-17\",\n   \"Statement\":[\n      {\n         \"Effect\":\"Allow\",\n         \"Action\":[\n            \"s3:PutObject\",\n            \"s3:PutObjectAcl\"\n          ],\n         \"Resource\":\"arn:aws:s3:::silkwrm/*\"\n      }\n   ]\n}\n"
  },
  {
    "path": "src/screenshotbot/screenshot-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/screenshot-api\n    (:use #:cl #:alexandria)\n  (:export\n   #:image-public-url\n   #:screenshot-image\n   #:local-image\n   #:history-page\n   #:make-screenshot\n   #:get-screenshot-history))\n(in-package :screenshotbot/screenshot-api)\n\n(hex:declare-handler 'history-page)\n"
  },
  {
    "path": "src/screenshotbot/screenshotbot.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot-system\n  (:use :cl\n   :asdf))\n(in-package :screenshotbot-system)\n\n\n(defsystem :screenshotbot/mask-rect-api\n  :serial t\n  :depends-on ()\n  :components ((:file \"mask-rect-api\")))\n\n(defsystem :screenshotbot\n  :serial t\n  :author \"Arnold Noronha <arnold@screenshotbot.io>\"\n  :license \"Mozilla Public License, v 2.0\"\n  :defsystem-depends-on (:trivial-features)\n  :depends-on (:util\n               :auth.login\n               :markup\n               :gravatar\n               :parse-float\n               :core.rpc\n               :util.store/sync\n               :trivial-garbage\n               :core.active-users\n               :util/posix\n               :util/throttler\n               :core.api\n               :bknr.impex\n               :util/logger\n               #+ (and bknr.cluster (not :screenshotbot-oss))\n               :cluster\n               :core.config/api\n               :util.store/raft-state\n               :lparallel\n               :zip\n               :util/lparallel\n               :cl-store\n               :core.installation\n               :sentry-client\n               :fset\n               :trivia\n               :json-mop\n               :encrypt\n               :util/timeago\n               :iterate\n               :pkg\n               #-lispworks\n               :util/fake-fli\n               :auth\n               :jvm\n               #-screenshotbot-oss\n               :sentry\n               :server\n               :core.ui\n               :auto-restart\n               :scheduled-jobs\n               :serapeum\n               #+(or ccl lispworks)\n               :java.libs\n               :util/events\n               :util/form-state\n               :util/hash-lock\n               :util/request\n               :jose\n               :util/digests\n               :trivial-file-size\n               :remark\n               :screenshotbot.js-assets\n               :oidc\n               :cl-isaac\n               :screenshotbot.css-assets\n               :screenshotbot/secrets\n               :util.java\n               :util/phabricator\n               :util/copy-file\n               :hunchensocket\n               :drakma\n               :anaphora\n               :dag\n               #-screenshotbot-oss\n               :sentry-client\n               :quri\n               :clavier\n               :easy-macros\n               :cl-cron\n               :cl-interpol\n               :dns-client\n               :util/fset\n               :gzip-stream\n               :zs3\n               :random-sample\n               :gatekeeper\n               :pem\n               ;;:cljwt-custom ;; custom comes from clath, for rs-256\n               :do-urlencode\n               :screenshotbot.magick\n               :screenshotbot/api-model\n               :nibble\n               :cl-json)\n  :components\n  ((:static-file \"dtd/api\" :type \"dtd\")\n   (:file \"ignore-and-log-errors\")\n   (:file \"analytics\" :depends-on (\"ignore-and-log-errors\"))\n   (:file \"plugin\")\n   (:file \"async\")\n   (:file \"installation\")\n   (:file \"billing-meter\")\n   (:file \"default-oidc-provider\")\n   (:file \"throttler\")\n   (:file \"raft-redirect\")\n   (:file \"server\" :depends-on (\"analytics\" \"raft-redirect\"))\n   (:file \"debugging\")\n   (:module \"s3\"\n    :serial t\n    :components ((:file \"core\")\n                 (:file \"backup\")))\n   (:file \"cdn\")\n   (:file \"user-api\")\n   (:file \"notice-api\")\n   (:file \"report-api\")\n   (:file \"promote-api\")\n   (:file \"email-template\")\n   (:file \"screenshot-api\")\n   (:file \"settings-api\")\n   (:file \"task-integration-api\")\n   (:file \"plan\")\n   (:file \"template\")\n   (:file \"left-side-bar\")\n   (:module \"ui\"\n    :components ((:file \"core\")\n                 (:file \"all\")))\n   (:file \"artifacts\")\n   (:file \"assets\")\n   (:file \"git-repo\")\n   (:file \"uname\")\n   (:module \"model\"\n    :serial t\n    :components ((:file \"core\")\n                 (:file \"constant-string\")\n                 (:file \"transient-object\")\n                 (:file \"auto-cleanup\")\n                 (:file \"company\")\n                 (:file \"counter\")\n                 (:file \"build-info\")\n                 (:file \"enterprise\")\n                 (:file \"sharing\")\n                 (:file \"user\")\n                 (:file \"view\")\n                 (:file \"image\")\n                 (:file \"screenshot-key\")\n                 (:file \"screenshot-map\")\n                 (:file \"recorder-run\")\n                 (:file \"archived-run\")\n                 (:file \"pr-rollout-rule\")\n                 (:file \"downloadable-run\")\n                 (:file \"figma\")\n                 (:file \"batch\")\n                 (:file \"report\" :depends-on (\"recorder-run\"))\n                 (:file \"image-comparison\")\n                 (:file \"review-policy\")\n                 (:file \"channel\")\n                 (:file \"failed-run\")\n                 (:file \"image-comparer\")\n                 (:file \"finalized-commit\")\n                 (:file \"screenshot\")\n                 (:file \"commit-graph\")\n                 (:file \"run-commit-lookup\")\n                 (:file \"test-object\")\n                 (:file \"note\")\n                 (:file \"company-graph\")))\n   (:module \"thresholds\"\n    :components ((:file \"dsl\")))\n   (:file \"audit-log\")\n   (:file \"site-admin\")\n   (:file \"diff-report\")\n   (:file \"invite\")\n   (:module \"figma\"\n    :components ((:file \"figma\")\n                 (:file \"view\")\n                 (:file \"dropdown\")))\n   (:module \"dashboard\"\n    :serial t\n    :components ((:file \"explain\")\n                 (:file \"home\")\n                 (:file \"ensure-company\")\n                 (:file \"review-link\")\n                 (:file \"commit-graph\")\n                 (:file \"run-page\")\n                 (:file \"dashboard\")\n                 (:file \"image\")\n                 (:file \"compare\")\n                 (:file \"compare-branches\")\n                 (:file \"notes\")\n                 (:file \"recent-runs\")\n                 (:file \"notices\")\n                 (:file \"flaky-screenshots\")\n                 (:file \"api-key-impl\")\n                 (:file \"channels\")\n                 (:file \"reports\")\n                 (:file \"bisect\")\n                 (:file \"history\")\n                 (:file \"batch\")\n                 (:file \"mask-builder\")\n                 (:file \"site-admin\")\n                 (:file \"audit-log\")))\n   (:file \"image-comparison\")\n   (:module \"webhook\"\n    :serial t\n    :components ((:file \"model\")\n                 (:file \"webhook\")\n                 (:file \"settings\")))\n   (:file \"abstract-pr-promoter\")\n   (:file \"batch-promoter\")\n   (:module \"github\"\n    :serial t\n    :components ((:file \"plugin\")\n                 (:file \"github-installation\")\n                 (:file \"audit-log\")\n                 (:file \"marketplace\")\n                 (:file \"webhook\")\n                 (:file \"repo-push-webhook\")\n                 (:file \"jwt-token\")\n                 (:file \"access-checks\")\n                 (:file \"review-link\")\n                 (:file \"app-installation\")\n                 (:file \"pr-checks\" :depends-on (\"access-checks\"))\n                 (:file \"read-repos\")\n                 (:file \"settings\")\n                 (:file \"pull-request-promoter\")\n                 (:file \"task-integration\")\n                 (:file \"all\")))\n   (:module \"azure\"\n    :serial t\n    :components ((:file \"audit-log\")\n                 (:file \"request\")\n                 (:file \"run-warnings\")\n                 (:file \"plugin\")\n                 (:file \"settings\")\n                 (:file \"promoter\")))\n   (:module \"bitbucket\"\n    :components ((:file \"core\")\n                 (:file \"plugin\")\n                 (:file \"review-link\")\n                 (:file \"audit-log\")\n                 (:file \"settings\")\n                 (:file \"promoter\")))\n   (:module \"gitlab\"\n    :serial t\n    :components ((:file \"repo\")\n                 (:file \"audit-logs\")\n                 (:file \"plugin\")\n                 (:file \"review-link\")\n                 (:file \"settings\")\n                 (:file \"webhook\")\n                 (:file \"merge-request-promoter\")\n                 (:file \"all\")))\n   (:module \"api\"\n    :serial t\n    :components ((:file \"doc\")\n                 (:file \"core\")\n                 (:file \"version\")\n                 (:file \"analytics-event\")\n                 (:file \"build-info\")\n                 (:file \"failed-run\")\n                 (:file \"cli-log\")\n                 (:file \"finalized-commit\")\n                 (:file \"batch\")\n                 (:file \"image\")\n                 (:file \"api-key\")\n                 (:file \"promote\")\n                 (:file \"recorder-run\" :depends-on (\"promote\"))\n                 (:file \"report\")\n                 (:file \"active-runs\")\n                 (:file \"unchanged-run\")\n                 (:file \"compare\")\n                 (:file \"commit-graph\")\n                 (:module \"docs\"\n                  :components ((:file \"recorder-run\")))))\n   (:module \"phabricator\"\n    :serial t\n    :components ((:file \"plugin\")\n                 (:file \"builds\")\n                 (:file \"diff-promoter\")\n                 (:file \"settings\")\n                 (:file \"all\")))\n   (:module \"sso\"\n    :serial t\n    :components ((:file \"model\")\n                 (:file \"fake\")\n                 (:file \"settings\")\n                 (:file \"redirect\")))\n   (:module \"login\"\n    :serial t\n    :components ((:file \"github-oauth\")\n                 (:file \"github-oauth-ui\")\n                 (:file \"google-oauth\")\n                 (:file \"populate\")\n                 (:file \"microsoft-entra\")\n                 (:file \"aws-cognito\")\n                 (:file \"require-invite-sso-mixin\")\n                 (:file \"template\")))\n   (:module \"company\"\n    :serial t\n    :components ((:file \"new\")\n                 (:file \"members\")\n                 (:file \"request\")\n                 (:file \"rename\")))\n   (:module \"microsoft-teams\"\n    :serial t\n    :components ((:file \"model\")\n                 (:file \"teams-api\")\n                 (:file \"audit-log\")\n                 (:file \"channel-card\")\n                 (:file \"task-integration\")\n                 (:file \"settings\")))\n   (:module \"slack\"\n    :serial t\n    :components ((:file \"plugin\")\n                 (:file \"core\")\n                 (:file \"rules\")\n                 (:file \"task-integration\")\n                 (:file \"rules-card\")\n                 (:file \"settings\")\n                 (:file \"all\")))\n   (:module \"email-tasks\"\n    :components ((:file \"settings\")\n                 (:file \"task-integration\")))\n   (:module \"settings\"\n    :serial t\n    :components ((:file \"settings-template\")\n                 (:file \"general\")\n                 (:file \"security\")\n                 (:file \"shares\")))\n   (:module \"admin\"\n    :serial t\n    :components ((:file \"core\")\n                 (:file \"index\")\n                 (:file \"test-writes\")))\n   (:module \"tasks\"\n    :serial t\n    :components ((:file \"common\")))\n   (:file \"config\")\n   (:file \"cleanup\")\n   (:file \"metrics\")))\n\n(defsystem :screenshotbot/testing-lib\n  :serial t\n  :depends-on (:screenshotbot\n               :util/fiveam\n               :util.testing\n               :alexandria\n               :fiveam-matchers)\n  :components ((:file \"testing\")))\n\n(defsystem :screenshotbot/tests\n  :serial t\n  :depends-on (:fiveam\n               :util\n               :core.config\n               :util/fiveam\n               :fiveam-matchers\n               :util/random-port\n               :trivia\n               :screenshotbot/replay-core\n               :screenshotbot/webdriver\n               :screenshotbot/replay\n               :screenshotbot/testing-lib\n               :util/events\n               :tmpdir\n               :cl-mock\n               :screenshotbot)\n  :components ((:file \"test-server\")\n               (:file \"test-throttler\")\n               (:file \"test-testing\")\n               (:module \"figma\"\n                :components ((:file \"test-view\")))\n               (:file \"test-artifacts\")\n               (:file \"test-email-template\")\n               (:file \"test-git-repo\")\n               (:file \"test-billing-meter\")\n               (:file \"test-promote-api\")\n               (:file \"test-diff-report\")\n               (:file \"test-secret\")\n               (:file \"test-async\")\n               (:file \"test-settings-api\")\n               (:file \"test-config\")\n               (:file \"test-installation\")\n               (:file \"test-assets\")\n               (:file \"test-template\")\n               (:file \"test-audit-log\")\n               (:file \"test-abstract-pr-promoter\")\n               (:file \"test-batch-promoter\")\n               (:module \"thresholds\"\n                :serial t\n                :components ((:file \"test-dsl\")))\n               (:module \"tasks\"\n                :components ((:file \"test-common\")))\n               (:module \"company\"\n                :components ((:file \"test-request\")\n                             (:file \"test-rename\")))\n               (:module \"webhook\"\n                :components ((:file \"test-model\")\n                             (:file \"test-webhook\")\n                             (:file \"test-settings\")))\n               (:module \"bitbucket\"\n                :components ((:static-file \"error-response-1\" :type \"json\")\n                             (:file \"test-plugin\")\n                             (:file \"test-audit-log\")\n                             (:file \"test-review-link\")\n                             (:file \"test-settings\")\n                             (:file \"test-promoter\")))\n               (:module \"login\"\n                :components ((:file \"test-github-oauth\")\n                             (:file \"test-forgot-password\")\n                             (:file \"test-google-oauth\")\n                             (:file \"test-populate\")\n                             (:file \"test-verify-email\")\n                             (:file \"test-oidc\")\n                             (:file \"test-login\")\n                             (:file \"test-signup\")\n                             (:file \"test-common\")))\n               (:file \"test-invite\")\n               (:file \"test-uname\")\n               (:module \"model\"\n                :components ((:file \"testing\")\n                             (:file \"test-core\")\n                             (:file \"test-figma\")\n                             (:file \"test-constant-string\")\n                             (:file \"test-build-info\")\n                             (:file \"test-auto-cleanup\")\n                             (:file \"test-counter\")\n                             (:file \"test-archived-run\")\n                             (:file \"test-transient-object\")\n                             (:file \"test-recorder-run\")\n                             (:file \"test-downloadable-run\")\n                             (:file \"test-pr-rollout-rule\")\n                             (:file \"test-run-commit-lookup\")\n                             (:file \"test-batch\")\n                             (:file \"test-finalized-commit\")\n                             (:file \"test-screenshot\")\n                             (:file \"test-screenshot-key\")\n                             (:file \"test-screenshot-map\")\n                             (:file \"test-image-comparer\")\n                             (:file \"test-report\")\n                             (:file \"test-user\")\n                             (:file \"test-channel\")\n                             (:file \"test-review-policy\")\n                             (:file \"test-company\")\n                             (:file \"test-company-graph\")\n                             (:file \"test-image\")\n                             (:file \"test-image-comparison\")\n                             (:file \"test-commit-graph\")\n                             (:file \"test-acceptable\")))\n               (:module \"dashboard\"\n                :components ((:file \"test-recent-runs\")\n                             (:file \"test-ensure-company\")\n                             (:file \"test-review-link\")\n                             (:file \"test-compare-branches\")\n                             (:file \"test-run-page\")\n                             (:file \"test-compare\")\n                             (:file \"test-reports\")\n                             (:file \"test-api-keys\")\n                             (:file \"test-batch\")\n                             (:file \"test-image\")\n                             (:file \"test-channels\")\n                             (:file \"test-flaky-screenshots\")\n                             (:file \"test-history\")\n                             (:file \"test-bisect\")\n                             (:file \"test-notices\")\n                             (:file \"test-dashboard\")))\n               (:module \"webdriver\"\n                :components ((:file \"test-screenshot\")))\n               (:module \"github\"\n                :components ((:file \"test-jwt-token\")\n                             (:file \"test-app-installation\")\n                             (:file \"test-repo-push-webhook\")\n                             (:file \"test-read-repos\")\n                             (:file \"test-plugin\")\n                             (:file \"test-access-checks\")\n                             (:file \"test-settings\")\n                             (:file \"test-pull-request-promoter\")\n                             (:file \"test-review-link\")\n                             (:file \"test-webhook\")))\n               (:module \"gitlab\"\n                :components ((:file \"test-settings\")\n                             (:file \"test-review-link\")\n                             (:file \"test-merge-request-promoter\")))\n               (:module \"azure\"\n                :components ((:file \"test-plugin\")\n                             (:file \"test-request\")\n                             (:file \"test-run-warnings\")\n                             (:file \"test-settings\")\n                             (:file \"test-promoter\")))\n               (:module \"phabricator\"\n                :components ((:file \"test-builds\")\n                             (:file \"test-diff-promoter\")))\n               (:module \"replay\"\n                :components ((:file \"test-core\")\n                             (:file \"test-sitemap\")\n                             (:file \"test-css-tokenizer\")\n                             (:file \"test-remote\")\n                             (:file \"test-run-builder\")\n                             (:file \"test-integration\")\n                             (:file \"test-replay-acceptor\")))\n               (:module \"slack\"\n                :components ((:file \"test-settings\")\n                             (:file \"test-core\")\n                             (:file \"test-rules\")\n                             (:file \"test-task-integration\")))\n               (:file \"test-analytics\" :if-feature (:not :windows))\n               (:module \"email-tasks\"\n                :components ((:file \"test-task-integration\")))\n               (:module \"settings\"\n                :components ((:file \"test-security\")))\n               (:module \"api\"\n                :components ((:file \"test-core\")\n                             (:file \"test-model\")\n                             (:file \"test-active-runs\")\n                             (:file \"test-failed-run\")\n                             (:file \"test-build-info\")\n                             (:file \"test-unchanged-run\")\n                             (:file \"test-report\")\n                             (:file \"test-finalized-commit\")\n                             (:file \"test-commit-graph\")\n                             (:file \"test-analytics-event\")\n                             (:file \"test-compare\")\n                             (:file \"test-batch\")\n                             (:file \"test-image\")\n                             (:file \"test-promote\")\n                             (:file \"test-send-tasks\")\n                             (:file \"test-recorder-runs\")\n                             (:file \"test-version\")))\n               (:module \"web-build\"\n                :components ((:file \"test-scheduler\")\n                             (:file \"test-project\")))\n               (:file \"test-cdn\")\n               (:file \"test-raft-redirect\")))\n\n(defsystem :screenshotbot/secrets\n  :serial t\n  :depends-on (:alexandria\n               :pkg\n               :util/digests\n               :bknr.impex)\n  :components ((:static-file \"dtd/secret\" :type \"dtd\")\n               (:file \"secret\")))\n\n\n(defsystem :screenshotbot/store-tests\n  :serial t\n  :depends-on (:screenshotbot\n               :util.testing\n               :fiveam)\n  :components ((:file \"test-store\")))\n\n\n\n(asdf:defsystem :screenshotbot/replay-core\n  :serial t\n  :depends-on (:plump\n               :lquery\n               :fset\n               :uuid\n               :easy-macros\n               :util/hunchentoot-engine\n               :cl-store\n               :util.threading\n               :util.misc\n               :util/request\n               :util/lru-cache\n               :util/json-mop\n               :hunchentoot-extensions\n               :util/digests\n               :util/cron\n               :dns-client\n               :screenshotbot/hub\n               :http-proxy\n               :auto-restart\n               :drakma\n               :json-mop\n               :core.installation\n               :alexandria)\n  :components ((:module \"replay\"\n                :serial t\n                :components ((static-file \"replay-regex\" :type \"txt\")\n                             (:file \"browser-config\")\n                             (:file \"css-tokenizer\")\n                             (:file \"core\")\n                             (:file \"squid\")))))\n\n\n\n(asdf:defsystem :screenshotbot/replay\n  :serial t\n  :depends-on (:dexador\n               :cl+ssl\n               :cl-mongo-id\n               :cl-webdriver-client\n               :clingon\n               :hunchentoot\n               :hunchensocket\n               :screenshotbot\n               :tmpdir\n               :quri\n               :screenshotbot/replay-core\n               :ironclad/core\n               :cl-json\n               :ironclad/digests\n               :screenshotbot.sdk\n               :str\n               :flexi-streams\n               :scheduled-jobs\n               :xmls\n               :lquery\n               :drakma\n               :markup\n               :screenshotbot/webdriver\n               :screenshotbot/hub\n               :closer-mop\n               :plump)\n  :components ((:module \"replay\"\n                :serial t\n                :components ((:file \"proxy\")\n                             (:file \"proxy-main\")\n                             (:file \"replay-acceptor\")\n                             (:file \"services\")\n                             (:file \"aws-selenium-provider\")\n                             (:file \"frontend\")\n                             (:file \"sitemap\")\n                             (:file \"run-builder\")\n                             (:file \"integration\")\n                             (:file \"remote\")))\n               (:module \"web-build\"\n                :serial t\n                :components ((:file \"project\")\n                             (:file \"scheduler\")\n                             (:file \"device-list\")\n                             (:file \"browsers\")))))\n\n(defsystem :screenshotbot/hub\n  :serial t\n  :depends-on (:util/request\n               :util.misc\n               :hunchentoot\n               :auto-restart\n               :cl-json)\n  :components ((:module \"hub\"\n                :components ((:file \"server\")\n                             (:file \"container\")))))\n\n(defsystem :screenshotbot/api-model\n  :serial t\n  :depends-on (:json-mop\n               :util/json-mop)\n  :components ((:module \"api\"\n                :components ((:file \"model\")))))\n\n(defsystem :screenshotbot/webdriver\n  :serial t\n  :depends-on (:flexi-streams\n               :trivial-file-size\n               :auto-restart\n               #-lispworks\n               :util/fake-fli\n               :cl-webdriver-client)\n  :components ((:module \"webdriver\"\n                :components ((:file \"impl\")\n                             (:file \"screenshot\")\n                             (:file \"all\")))))\n\n(defsystem :screenshotbot/all\n  :serial t\n  :depends-on (:screenshotbot\n               :screenshotbot/replay\n               :screenshotbot.migrations))\n"
  },
  {
    "path": "src/screenshotbot/sdk/.gitattributes",
    "content": "*.png binary\n"
  },
  {
    "path": "src/screenshotbot/sdk/.gitignore",
    "content": "template.*"
  },
  {
    "path": "src/screenshotbot/sdk/CHANGELOG.md",
    "content": "# Changelog\n\n## 2.18.10 - 2026-04-08\n\n- Enables the git-fetch by default, behind a GK, because of\n  side-effects from 2.18.8. T2274\n\n### Changed\n\n## 2.18.9 - 2026-04-05\n\n### Changed\n\n- In 2.28.7, we thought we removed the `git status` but that was\n  incorrect and is fixed correctly here.\n- Added the ability to control the --verbose flag from the server side\n\n## 2.18.8 - 2026-04-03\n\n### Changed\n\n- Avoid git-fetch when using the new commit graph flow\n- Add more warning logs for slow parts of the code (such as `git status`)\n- NOTE: Slightly buggy release, see T2274. Fixed in 2.18.10 behind a GK.\n\n## 2.18.7 - 2026-04-01\n\n### Changed\n\n- Avoid doing a `git status` when running in CI. On larger repos this\n  can be quite slow\n\n## 2.18.6 - 2026-03-18\n\n### Changed\n\n- Removed noisy warning about being unable to compute merge-base\n\n## 2.18.5 - 2026-03-04\n\n### Added\n\n- Added `pull commit` subcommand to pull all runs associated with a commit\n\n### Changed\n\n- Added `pull run` subcommand, and deprecated the `download-run`\n  subcommand.\n\n\n## 2.18.4 - 2026-02-27\n\n### Changed\n\n- Fixed build URL for BuildKite\n- This will be the first release where we'll also publish the binary\n  artifacts directly (not just the installer). At the time of writing\n  this script isn't ready yet.\n\n## 2.18.3 - 2026-02-18\n\n- This should be a no-op, or at least no intentional changes. Testing\n  a deployment script.\n\n\n## 2.18.2 - 2026-02-17\n\n### Added\n\n- Added --html-list argument for static-websites\n\n## 2.18.1 - 2026-02-12\n\n### Changed\n\n- Use POST for check-wants, since the shas parameter can be quite long\n\n## 2.18.0 - 2026-02-05\n\n### Added\n\n- Added support for CodeMagic\n\n## 2.17.7 - 2026-02-04\n\n### Changed\n\n- Fixes crash in 2.17.6\n- Also fixes a general hanging when using SSH based repos in certain\n  situations.\n- Note added later: this is the first release with the git-upload-pack\n  flow being very stable.\n\n## 2.17.6 - 2026-02-03\n\n### Changed\n\n- BAD RELEASE! DO NOT USE!\n- Now uses both local and remote commits during the upload-pack flow\n\n## 2.17.5 - 2026-02-01\n\n### Changed\n\n- Bring back multi_ack again\n\n\n## 2.17.4 - 2026-01-31\n\n### Changed\n\n- For the commit-graph protocol, prefer the use of repo-link instead\n  of the remote-url from the git repo.\n\n## 2.17.3 - 2026-01-30\n\n### Changed\n\n- Don't use both extraheaders and auth\n\n## 2.17.2 - 2026-01-29\n\n### Changed\n\n- Removed ignore-errors around credential-fill for better debugging\n- Remove --local, so we can also see global extraheaders\n- Add a warning for when both extraheaders and a credential is available\n\n## 2.17.1 - 2026-01-29\n\n### Added\n\n- Added support for credential-helper when interacting with Git via\n  HTTP\n\n## 2.17.0 - 2026-01-29\n\n### Changed\n\n- Lispworks upgraded to 8.1.2, likely we pulled in Quicklisp changes\n  too. But no intentional changes otherwise.\n\n## 2.16.24 - 2025-11-18\n\n### Changed\n\n- Fixed an issue with the upload-pack protocol hanging when an ERR is\n  sent during read-shallow-lines. (T2130)\n\n\n## 2.16.23 - 2025-11-10\n\n### Changed\n\n- Added more Sentry extras\n\n## 2.16.22 - 2025-11-10\n\n### Changed\n\n- Added a bunch of Sentry extras to track run-time when a crash happens\n\n## 2.16.21 - 2025-11-10\n\n### Changed\n\n- Reverted multi-ack capability changes from 2.16.20\n- Fixed logging for stack overflow errors to improve error reporting (T2113)\n- Fixed stack overflow issue in git-pack protocol by avoiding reliance on tail call optimization (T2115)\n\n### Note\n\n- Version 2.16.20 should not be used due to issues with the multi-ack implementation\n\n## 2.16.20 - 2025-11-07\n\n### Changed\n\n- Added retry logic for upload-pack operations to improve reliability (T2111)\n- Enabled multi-ack capability in git-pack protocol by default\n\n**WARNING: This version has known issues and should not be used. Please upgrade to 2.16.21 or later.**\n\n## 2.16.19 - 2025-11-06\n\n### Changed\n\n- Fixed git protocol ACK/NAK handling when sending multiple \"have\" lines\n  without multi_ack capability. The server sends ACK responses interleaved\n  with the request stream, so we now read until finding the PACK signature\n  (T2108)\n\n## 2.16.18 - 2025-10-31\n\n### Changed\n\n- Automatically detect API hostname from API secret when --api-hostname is not provided\n\n## 2.16.17 - 2025-10-28\n\n### Changed\n \n- Fix an issue with duplicate hashes in the `haves` during the\n  git-pack protocol. This leads to undocumented behavior on\n  upload-pack servers (T1907)\n\n## 2.16.16 - 2025-10-28\n\nNo expected changes, only refactoring\n\n## 2.16.15 - 2025-10-24\n\n### Changed\n\n- Advertise \"shallow\" feature in the git-pack protocol (potential fix\n  for T2093)\n\n## 2.16.14 - 2025-10-24\n\n### Changed\n\n- Added timestamps to the server-cli-logs\n\n\n## 2.16.13 - 2025-10-23\n\n### Added\n\n- Added support for using Authorization header from .git/config when\n  making HTTP requests. (T2082)\n\n\n## 2.16.12 - 2025-10-22\n\n### Changed\n\n- We were sending the wrong data format to /api/commit-graph in the\n  new flow (T2092)\n\n\n## 2.16.11 - 2025-10-22\n\n### Changed\n\n- Fixed a typo that prevented `allow-tip-sha1-in-want` to actually be\n  enabled, probably causing T2091\n\n## 2.16.10 - 2025-10-22\n\n### Changed\n\n- Use the new commit-graph flow even for what we were calling\n  \"locally-rebased\". In reality, it wasn't being locally rebased, it\n  was Bitrise using the `refs/pull/*merge` commit, which should be\n  part of the remote repository.\n\n## 2.16.9 - 2025-10-21\n\n### Added\n\n- Added X-Cli-session-id\n\n### Changed\n\n- Fixed server-cli-logs\n\n## 2.16.8 - 2025-10-21\n\n### Changed\n\n- Fixed `dev install` command (T2086)\n\n\n## 2.16.7 - 2025-10-15\n\n### Changed\n\n- Added the ability to store logs, to make it easier to debug CI-side issues\n\n\n## 2.16.6 - 2025-10-14\n\n### Changed\n\n- Nothing significant should've changed, accidental no-op\n\n## 2.16.5 - 2025-10-14\n\n### Changed\n\n- Fixed crash when using default API hostname (T2077, T2076, T2075, T2074)\n\n## 2.16.4 - 2025-10-13\n\n### Added\n\n- Server side flag to control the new commit-graph workflow that was\n  last disabled in 2.15.4. The plan is to slowly roll out.\n  \n### Changed\n\n- /api/version is now authenticated\n- Updgraded to Lispworks 8.1.1\n\n## 2.16.3 - 2025-09-12\n\n### Added\n\n- Added `--pixel-tolerance` flag to help with flakiness to some\n  degree.\n\n## 2.16.2 - 2025-08-29\n\n### Changed\n\n- Fixed issue with re-using SSL connections (blames to 2.16.0, from\n  changes in dependencies.)\n\n## 2.16.1 - 2025-08-28\n\n### Changed\n\n- Fixed `ci upload-commit-graph` subcommand. This was crashing.\n\n## 2.16.0 - 2025-08-28 \n\n### Added\n\n- Support for Xcode cloud Environment variables\n- Support for parsing images from .xcresults\n\n### Changed\n\n- We're not using OpenSSL/LibreSSL anymore for hash functions. This\n  might be a minor performance regression (by a few seconds at\n  most). But it's more reliable.\n\n## 2.15.17 - 2025-06-02\n\n### Changed\n\n- Fixed the innocuous warning about `--pull-request argument` being\n  invalid, which typically only happened on Bitrise.\n\n## 2.15.16 - 2025-05-30\n\n### Changed\n\n- Correctly parse partial --override-commit-hash arguments. (Context:\n  Some older customers tend to pass this arguments, but for most cases\n  you don't need to pass this and we can detect it from the\n  environment. For customers who were using this argument, some were\n  passing an incomplete SHA prefix. Around version 2.15.14, we started\n  trying to complete this prefix, but the code was buggy.)\n\n## 2.15.15 - 2025-05-22\n\n### Changed\n\n- Fixed bug in the old commit-graph flow where shallow commits weren't\n  sending parent commits, thus messing up the commit graph.\n\n## 2.15.14 - 2025-05-21\n\n### Changed\n\n- Disabled the new commit-graph flow again. We've been running an\n  older version (2.15.1), but we're pushing this new version so that\n  customers can update to this instead of running buggy versions > 2.15.1 and < 2.15.14.\n\n## 2.15.12 - 2025-05-11\n\n### Changed\n\n- Fixed incomplete commit prefixes passed to\n  `--override-commit-hash`. \n\n## 2.15.11 - 2025-05-11\n\n### Changed\n\n- Fix couple of edge cases with the new commit graph flow\n\n## 2.15.8 - 2025-05-11\n\n### Changed\n\n- Fixed netrc parsing in certain case\n- Fix Git HTTP URL parsing when authentication is part of the Git URL\n- Add a retry if the Git HTTP request fails\n\n\n## 2.15.7 - 2025-05-10\n\n### Changed\n\n- Bug fix in the HTTP upload-pack protocol\n\n## 2.15.6 - 2025-05-10\n\n### Changed\n\n- Enable using upload-pack via HTTP protocol too\n\n## 2.15.5 - 2025-05-10\n\n### Changed\n\n- Added some additional logging, no behavioral changes\n\n## 2.15.4 - 2025-05-09\n\n### Changed\n\n- Fixed a bug in URL encoding of parameter of new commit graph API\n\n## 2.15.3 - 2025-05-09\n\n### Added\n\n- Re-enabled the new commit graph api\n\n\n## 2.15.1 - 2025-05-08\n\n### Removed\n\n- Disabled the new commit graph api\n\n\n## 2.15.0 - 2025-05-08\n\n### Added\n\n- It turns out we weren't really deploying the older versions because\n  of a bug in 2.12.1. So this includes all the changes from 2.12.1\n  onward to 2.14.1. Sorry about this.\n\n## 2.14.1 - 2025-05-08\n\n### Changed\n\n- Fixed bug in 2.14.0 that made the commit-graph always be uploaded\n  twice, even when the first one succeeded.\n\n## 2.14.0 - 2025-05-08\n\nThis release was not deployed because of a bug\n\n### Added\n\n- Use the `git-upload-pack` protocol to support shallow clones\n\n## 2.13.1 - 2025-04-17\n\n### Changed\n\n- Avoid crash when `git merge-base` fails, warn instead. This is\n  technically not being used by the server anymore.\n\n\n## 2.13.0 - 2025-04-07\n\n### Added\n\n- `dev verify-against-ci` sub-command, currently in beta testing. At\n  the moment, we require you to know the exact commit you want to\n  compare against, in a future update we'll try to automate that\n  process based on feedback.\n\n## 2.12.1 - 2025-03-29\n\n### Changed\n\n- Upgraded Lispworks to 8.1 \n\n## 2.12.0 - 2025-03-06\n\n### Added\n\n- Started this CHANGELOG\n- Added subcommand `batch reports` to list all reports under a batch\n- Added subcommand `batch accept-all` to accept all reports under a batch\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/UTF-8-demo.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n    <head><title>UTF-8 test file</title></head>\n    <body>\n    <p>Original by Markus Kuhn, adapted for HTML by Martin D&uuml;rst.</p>\n<pre>\nUTF-8 encoded sample plain-text file\n‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nMarkus Kuhn [ˈmaʳkʊs kuːn] &lt;mkuhn@acm.org> — 1999-08-20\n\n\nThe ASCII compatible UTF-8 encoding of ISO 10646 and Unicode\nplain-text files is defined in RFC 2279 and in ISO 10646-1 Annex R.\n\n\nUsing Unicode/UTF-8, you can write in emails and source code things such as\n\nMathematics and Sciences:\n\n  ∮ E⋅da = Q,  n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),\n\n  ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ &lt; a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B),\n\n  2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm\n\nLinguistics and dictionaries:\n\n  ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn\n  Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ]\n\nAPL:\n\n  ((V⍳V)=⍳⍴V)/V←,V    ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈\n\nNicer typography in plain text files:\n\n  ╔══════════════════════════════════════════╗\n  ║                                          ║\n  ║   • ‘single’ and “double” quotes         ║\n  ║                                          ║\n  ║   • Curly apostrophes: “We’ve been here” ║\n  ║                                          ║\n  ║   • Latin-1 apostrophe and accents: '´`  ║\n  ║                                          ║\n  ║   • ‚deutsche‘ „Anführungszeichen“       ║\n  ║                                          ║\n  ║   • †, ‡, ‰, •, 3–4, —, −5/+5, ™, …      ║\n  ║                                          ║\n  ║   • ASCII safety test: 1lI|, 0OD, 8B     ║\n  ║                      ╭─────────╮         ║\n  ║   • the euro symbol: │ 14.95 € │         ║\n  ║                      ╰─────────╯         ║\n  ╚══════════════════════════════════════════╝\n\nGreek (in Polytonic):\n\n  The Greek anthem:\n\n  Σὲ γνωρίζω ἀπὸ τὴν κόψη\n  τοῦ σπαθιοῦ τὴν τρομερή,\n  σὲ γνωρίζω ἀπὸ τὴν ὄψη\n  ποὺ μὲ βία μετράει τὴ γῆ.\n\n  ᾿Απ᾿ τὰ κόκκαλα βγαλμένη\n  τῶν ῾Ελλήνων τὰ ἱερά\n  καὶ σὰν πρῶτα ἀνδρειωμένη\n  χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!\n\n  From a speech of Demosthenes in the 4th century BC:\n\n  Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι,\n  ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς\n  λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ\n  τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿ \n  εἰς τοῦτο προήκοντα,  ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ\n  πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν\n  οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι,\n  οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν\n  ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον\n  τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι\n  γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν\n  προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους\n  σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ\n  τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ\n  τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς\n  τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον.\n\n  Δημοσθένους, Γ´ ᾿Ολυνθιακὸς\n\nGeorgian:\n\n  From a Unicode conference invitation:\n\n  გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო\n  კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს,\n  ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს\n  ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი,\n  ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება\n  ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში,\n  ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში.\n\nRussian:\n\n  From a Unicode conference invitation:\n\n  Зарегистрируйтесь сейчас на Десятую Международную Конференцию по\n  Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии.\n  Конференция соберет широкий круг экспертов по  вопросам глобального\n  Интернета и Unicode, локализации и интернационализации, воплощению и\n  применению Unicode в различных операционных системах и программных\n  приложениях, шрифтах, верстке и многоязычных компьютерных системах.\n\nThai (UCS Level 2):\n\n  Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese\n  classic 'San Gua'):\n\n  [----------------------------|------------------------]\n    ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช  พระปกเกศกองบู๊กู้ขึ้นใหม่\n  สิบสองกษัตริย์ก่อนหน้าแลถัดไป       สององค์ไซร้โง่เขลาเบาปัญญา\n    ทรงนับถือขันทีเป็นที่พึ่ง           บ้านเมืองจึงวิปริตเป็นนักหนา\n  โฮจิ๋นเรียกทัพทั่วหัวเมืองมา         หมายจะฆ่ามดชั่วตัวสำคัญ\n    เหมือนขับไสไล่เสือจากเคหา      รับหมาป่าเข้ามาเลยอาสัญ\n  ฝ่ายอ้องอุ้นยุแยกให้แตกกัน          ใช้สาวนั้นเป็นชนวนชื่นชวนใจ\n    พลันลิฉุยกุยกีกลับก่อเหตุ          ช่างอาเพศจริงหนาฟ้าร้องไห้\n  ต้องรบราฆ่าฟันจนบรรลัย           ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ\n\n  (The above is a two-column text. If combining characters are handled\n  correctly, the lines of the second column should be aligned with the\n  | character above.)\n\nEthiopian:\n\n  Proverbs in the Amharic language:\n\n  ሰማይ አይታረስ ንጉሥ አይከሰስ።\n  ብላ ካለኝ እንደአባቴ በቆመጠኝ።\n  ጌጥ ያለቤቱ ቁምጥና ነው።\n  ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው።\n  የአፍ ወለምታ በቅቤ አይታሽም።\n  አይጥ በበላ ዳዋ ተመታ።\n  ሲተረጉሙ ይደረግሙ።\n  ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል።\n  ድር ቢያብር አንበሳ ያስር።\n  ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም።\n  እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም።\n  የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ።\n  ሥራ ከመፍታት ልጄን ላፋታት።\n  ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።\n  የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።\n  ተንጋሎ ቢተፉ ተመልሶ ባፉ።\n  ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው።\n  እግርህን በፍራሽህ ልክ ዘርጋ።\n\nRunes:\n\n  ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ\n\n  (Old English, which transcribed into Latin reads 'He cwaeth that he\n  bude thaem lande northweardum with tha Westsae.' and means 'He said\n  that he lived in the northern land near the Western Sea.')\n\nBraille:\n\n  ⡌⠁⠧⠑ ⠼⠁⠒  ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌\n\n  ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞\n  ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎\n  ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂\n  ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙\n  ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑ \n  ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲\n\n  ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲\n\n  ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹\n  ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞\n  ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕\n  ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹ \n  ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎ \n  ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎\n  ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳\n  ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞\n  ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲\n\n  (The first couple of paragraphs of \"A Christmas Carol\" by Dickens)\n\nCompact font selection example text:\n\n  ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n  abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n  –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n  ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ ﬁ�⑀₂ἠḂӥẄɐː⍎אԱა\n\nGreetings in various languages:\n\n  Hello world, Καλημέρα κόσμε, コンニチハ\n\nBox drawing alignment tests:                                          █\n                                                                      ▉\n  ╔══╦══╗  ┌──┬──┐  ╭──┬──╮  ╭──┬──╮  ┏━━┳━━┓  ┎┒┏┑   ╷  ╻ ┏┯┓ ┌┰┐    ▊ ╱╲╱╲╳╳╳\n  ║┌─╨─┐║  │╔═╧═╗│  │╒═╪═╕│  │╓─╁─╖│  ┃┌─╂─┐┃  ┗╃╄┙  ╶┼╴╺╋╸┠┼┨ ┝╋┥    ▋ ╲╱╲╱╳╳╳\n  ║│╲ ╱│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╿ │┃  ┍╅╆┓   ╵  ╹ ┗┷┛ └┸┘    ▌ ╱╲╱╲╳╳╳\n  ╠╡ ╳ ╞╣  ├╢   ╟┤  ├┼─┼─┼┤  ├╫─╂─╫┤  ┣┿╾┼╼┿┫  ┕┛┖┚     ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n  ║│╱ ╲│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╽ │┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▎\n  ║└─╥─┘║  │╚═╤═╝│  │╘═╪═╛│  │╙─╀─╜│  ┃└─╂─┘┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▏\n  ╚══╩══╝  └──┴──┘  ╰──┴──╯  ╰──┴──╯  ┗━━┻━━┛           └╌╌┘ ╎ ┗╍╍┛ ┋  ▁▂▃▄▅▆▇█\n\n</pre>\n</body>\n</html>\n"
  },
  {
    "path": "src/screenshotbot/sdk/active-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/active-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:request)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/active-run)\n\n(defun %get-active-runs (api-context &key channel)\n  (multiple-value-bind (body code)\n      (request api-context\n               (format nil \"/api/runs/active?channel=~a\" channel)\n               :method :get\n               :decode-response nil)\n    (unless (= 200 code)\n      (error \"Request failed with: ~a\" body))\n    (decode-json body '(:list dto:run))))\n\n(defun find-active-run (api-context\n                        &key channel\n                          branch)\n  (let ((runs (%get-active-runs api-context :channel channel)))\n    (log:debug \"Branches are: ~S\"\n               (mapcar #'dto:main-branch runs))\n    (cond\n      ((and (str:emptyp branch)\n            (= 1 (length runs)))\n       (car runs))\n      ((not (str:emptyp branch))\n       (loop for run in runs\n             if (equal (dto:main-branch run) branch)\n               return run))\n      (t\n       (error \"Could not disambiguate branch name, pass the --branch argument\")))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/adb-puller.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/adb-puller\n  (:use #:cl)\n  (:export\n   #:external-data-dir\n   #:pull-file\n   #:remote-file-exists-p\n   #:pull-directory))\n(in-package :screenshotbot/sdk/adb-puller)\n\n(defclass adb-puller ()\n  ((exec :initarg :exec\n         :reader exec\n         :documentation \"Path to the adb executable\")))\n\n\n(defmethod exec-adb ((self adb-puller)\n                     args\n                     &key ignore-error-status\n                       (output  *standard-output*))\n  (uiop:run-program\n   (list*\n    (namestring (exec self))\n    args)\n   :output output\n   :error-output *standard-output*\n   :ignore-error-status ignore-error-status))\n\n(defun android-namestring (remote)\n  (let* ((pathname (pathname remote))\n         (directory (pathname-directory pathname)))\n    (assert (eql :absolute (car directory)))\n    (str:concat\n     \"/\"\n     (str:join \"/\" (cdr directory))\n     \"/\"\n     (namestring\n      (make-pathname\n       :directory nil\n       :defaults pathname)))))\n\n(defmethod remote-file-exists-p ((self adb-puller)\n                                 src)\n  (let ((output (exec-adb self\n                          (list\n                           \"shell\"\n                           (format nil\n                                   \"ls ~a && echo EXISTS || echo DOES_NOT_EXIST\"\n                                   (android-namestring src)))\n                          :output 'string)))\n    (str:containsp \"EXISTS\" output)))\n\n(defmethod pull-file ((self adb-puller)\n                      remote\n                      local)\n  (let ((output (exec-adb self\n                          (list\n                           \"pull\"\n                           (android-namestring remote)\n                           (namestring local)))))))\n\n(defmethod pull-directory ((self adb-puller)\n                           remote\n                           local)\n  (let ((remote-tar (cl-ppcre:regex-replace-all\n                     \"/$\"\n                     (namestring remote)\n                     \".zip\" )))\n    (exec-adb self\n              (list\n               \"shell\"\n               \"zip\"\n               \"-r\"\n               remote-tar\n               (namestring remote))\n              :output 'string\n              :error-output 'string)\n    (uiop:with-temporary-file (:pathname p :type \"zip\")\n      (pull-file self\n                 remote-tar\n                 p)\n      (log:info \"Untarring the archive\")\n      (let ((zipfile (zip:open-zipfile p)))\n        (zip:do-zipfile-entries (name entry zipfile)\n          (with-open-file (output (ensure-directories-exist (path:catfile local name))\n                                  :direction :output\n                                  :element-type 'flex:octet)\n            (zip:zipfile-entry-contents entry output))))\n      (log:info \"Cleaning up temporary tar file\"))\n    (exec-adb self\n              (list \"shell\" \"rm\" remote-tar))))\n\n(defmethod external-data-dir ((self adb-puller))\n  (format nil \"~a/\"\n   (str:trim\n    (exec-adb self\n              (list \"shell\" \"echo\" \"$EXTERNAL_STORAGE\")\n              :output 'string))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/android.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/sdk/android\n  (:use #:cl\n        #:screenshotbot/sdk/flags\n        #:alexandria)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:image-directory\n                #:streamed-image\n                #:list-images)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:directory-image-bundle\n   #:make-image-bundle))\n(in-package :screenshotbot/sdk/android)\n\n(defun child-by-name (item name)\n  (loop for child across (dom:child-nodes item)\n        if (string= name (dom:node-name child))\n          return child))\n\n(Defun node-value (item)\n  (dom:node-value (elt (dom:child-nodes item) 0)))\n\n(defun node-integer-value (item)\n  (declare (optimize (speed 0) (debug 3)))\n  (parse-integer (node-value item)))\n\n(defclass image-bundle ()\n  ((metadata :initarg :metadata\n             :accessor metadata-file)))\n\n(defclass multi-metadata-bundle ()\n  ((image-bundles :initarg :image-bundles\n                  :reader image-bundles)))\n\n(defclass directory-image-bundle (image-bundle)\n  ((directory :initarg :directory)))\n\n(defclass zip-image-bundle (image-bundle)\n  ((zip :initarg :zip)\n   (zipfile)))\n\n(defmethod initialize-instance :after ((inst zip-image-bundle) &key zip &allow-other-keys)\n  (with-slots (zipfile) inst\n    (setf zipfile (zip:open-zipfile zip))\n    (let ((zipfile zipfile))\n      (trivial-garbage:finalize inst\n                                (lambda ()\n                                  (zip:close-zipfile zipfile))))))\n\n\n(defmethod read-image ((bundle directory-image-bundle) name)\n  (with-slots (directory) bundle\n   (imago:read-image\n    (path:catfile directory\n                  (format nil \"~a.png\" name)))))\n\n(defmethod read-image ((bundle zip-image-bundle) name)\n  (with-slots (zipfile) bundle\n    (uiop:with-temporary-file (:pathname p :stream s :direction :output :type \"png\"\n                               :element-type 'flexi-streams:octet)\n     (let ((entry (zip:get-zipfile-entry (format nil \"~a.png\" name) zipfile)))\n       (zip:zipfile-entry-contents entry s)\n       (finish-output s)\n       (imago:read-image p)))))\n\n(defun merge-tiles (tiles)\n  (destructuring-bind (w h)\n      (array-dimensions tiles)\n    (let ((full-width (loop for i from 0 below w\n                             summing (imago:image-width (aref tiles i 0)\n)))\n          (full-height (loop for i from 0 below h\n                            summing (imago:image-height (aref tiles 0 i))))\n          (single-tile-width (imago:image-width (aref tiles 0 0)))\n          (single-tile-height (imago:image-height (aref tiles 0 0))))\n\n      (let ((dest (make-instance 'imago:rgb-image\n                                 :width full-width\n                                 :height full-height)))\n        (let ((x 0))\n          (dotimes (ww w)\n            (let ((y 0))\n              (dotimes (hh h)\n                (log:trace \"Writing tile: (~d,~d) to (~d, ~d) \"\n                          ww hh\n                          x y)\n                (let ((src (aref tiles ww hh)))\n                  (imago:copy dest src\n                              :height (imago:image-height src)\n                              :width (imago:image-width src)\n                              :dest-y y\n                              :dest-x x))\n                (incf y single-tile-height)))\n            (incf x single-tile-width)))\n        dest))))\n\n(defmethod read-screenshot-tiles (screenshot (bundle image-bundle))\n  (let* ((name (a:assoc-value screenshot :name))\n         (tile-height (a:assoc-value screenshot :tile-height))\n         (tile-width (a:assoc-value screenshot :tile-width)))\n    (let ((arr (make-array (list tile-width tile-height))))\n      (dotimes (w tile-width)\n        (dotimes (h tile-height)\n          (let ((name (cond\n                        ((and (eql 0 h) (eql 0 w))\n                         name)\n                        (t\n                         (format nil \"~a_~d_~d\" name w h)))))\n            (setf (aref arr w h) (read-image bundle name)))))\n      (cons name (merge-tiles arr)))))\n\n\n(defmethod decode-metadata (metadata-file image-bundle)\n  (json:decode-json metadata-file))\n\n(defun read-android-metadata (metadata-file image-bundle)\n  (with-open-file (metadata-file metadata-file)\n   (let ((screenshots (decode-metadata metadata-file image-bundle)))\n     (loop for screenshot in screenshots\n           collect (read-screenshot-tiles screenshot image-bundle)))))\n\n(defun old-version! ()\n  (error \"It looks like you are using an older version of\n screenshot-tests-for-android or Shot.\n\nThese older versions use a different metadata format and are currently\n unsupported by this CLI tool. Please upgrade to either\n screenshot-tests-for-android 0.14.0, or Shot 5.13.0. If you need to\n use an older version of these libraries, please contact\n support@screenshotbot.io\"))\n\n(defun make-image-bundle (&key metadata)\n  (make-instance 'multi-metadata-bundle\n                  :image-bundles\n                  (loop for metadata in (cond\n                                          ((listp metadata)\n                                           metadata)\n                                          (t\n                                           ;; We're allowing a list of metadata files\n                                           ;; instead of just one metadata file, in\n                                           ;; order to support Shot's metadata_compose.json\n                                           (list metadata)))\n                        collect\n                        (progn\n                            (when (string-equal \"xml\" (pathname-type metadata))\n                              (old-version!))\n\n                            (cond\n                              ((equal \"metadata_compose\"\n                                      (pathname-name metadata))\n                               (make-instance\n                                'image-directory\n                                 :directory (fad:pathname-directory-pathname\n                                             metadata)))\n                              (t\n                               (make-instance\n                                'directory-image-bundle\n                                 :directory (fad:pathname-directory-pathname metadata)\n                                 :metadata metadata)))))))\n\n\n(defmethod list-images ((bundle image-bundle))\n  (let ((files (read-android-metadata\n                (metadata-file bundle)\n                bundle)))\n    (loop for (name . im) in files\n          collect\n          (progn\n            (uiop:with-temporary-file (:pathname p :type \"png\"\n                                       :direction :output\n                                       :element-type 'flexi-streams:octet)\n              (imago:write-png im p)\n              (make-bundle-image bundle name p))))))\n\n(defmethod make-bundle-image ((bundle image-bundle) name p)\n  \"Create an image object for the given name and pathname.\"\n  (make-instance 'streamed-image\n                 :name name\n                 :stream (open p :direction :input\n                                 :element-type 'flexi-streams:octet)))\n\n(defmethod list-images ((bundle multi-metadata-bundle))\n  (loop for image-bundle in (image-bundles bundle)\n        appending (list-images image-bundle)))\n\n#+nil\n(make-regular-dir (path:catfile (asdf:system-source-directory :screenshotbot.sdk)\n                                \"example/metadata.xml\")\n                  #P \"/tmp/foog/\")\n"
  },
  {
    "path": "src/screenshotbot/sdk/api-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/api-context\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:util/request\n                #:http-request-impl\n                #:http-request\n                #:engine\n                #:*engine*)\n  (:import-from #:util/reused-ssl\n                #:reused-ssl-mixin)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:format-api-url\n                #:api-hostname)\n  (:export\n   #:api-context\n   #:key\n   #:secret\n   #:hostname\n   #:remote-version\n   #:fetch-version\n   #:engine\n   #:api-feature-enabled-p\n   #:session-id\n   #:extract-hostname-from-secret)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/api-context)\n\n(defgeneric fetch-version (api-context)\n  (:documentation \"Actually fetch the api version, without any caching. Don't call this directly, instead call REMOTE-VERSION on the api-context.\"))\n\n(defclass base-api-context ()\n  ((remote-version :initform nil\n                   :initarg :remote-version\n                   :accessor remote-version)\n   (features :initarg :features\n             :initform nil\n             :initarg api-features\n             :accessor api-features)\n   (session-id :initform (format nil \"~a-~a-~a\"\n                                 (uiop:hostname)\n                                 (local-time:now)\n                                 (random 1000000))\n               :reader session-id)))\n\n(defmethod api-context-prepared-p ((self base-api-context))\n  (slot-value self 'remote-version))\n\n(defmethod fetch-remote-information ((self base-api-context))\n  (let ((prev (slot-value self 'remote-version)))\n    (cond\n      (prev  prev)\n      (t\n       (multiple-value-bind (version-num version-obj)\n           (fetch-version self)\n         (setf (remote-version self) version-num)\n         (setf (api-features self) (?. dto:api-features version-obj)))))))\n\n(defmethod remote-version :before ((self base-api-context))\n  (fetch-remote-information self))\n\n(defmethod api-features :before ((self base-api-context))\n  (fetch-remote-information self))\n\n(defmethod api-feature-enabled-p ((self base-api-context)\n                                  feature)\n  (str:s-member\n   (api-features self)\n   (str:downcase feature)))\n\n(defclass api-engine (reused-ssl-mixin engine)\n  ((session-id :initarg :session-id\n               :reader session-id)))\n\n(defmethod http-request-impl ((self api-engine) url &rest args &key additional-headers)\n  (apply #'call-next-method\n         self\n         url\n         :additional-headers\n         (list*\n          (cons :x-cli-session-id (session-id self))\n          additional-headers)\n         args))\n\n(defvar *api-engine* (make-instance 'api-engine))\n\n(defclass api-context (base-api-context)\n  ((key :initarg :key\n        :initform nil\n        :reader key\n        :writer (setf %key))\n   (secret :initarg :secret\n           :initform nil\n           :reader secret\n           :writer (setf %secret))\n   (hostname :initarg :hostname\n             :reader hostname\n             :writer (setf %hostname)\n             :documentation \"A URL like https://screenshotbot.io\")\n   (engine :accessor engine\n           :initform nil\n           :initarg :engine)))\n\n\n\n(defun %fix-hostname (found-hostname)\n  (cond\n    ((str:emptyp found-hostname)\n     nil)\n    (t\n     (api-hostname\n      :hostname found-hostname))))\n\n(defun extract-info-from-secret (api-secret)\n  \"Extract the api-key, secret, and hostname from an encoded API secret.\nReturns three values: (api-key actual-secret hostname), or NIL for all if decoding fails.\"\n  (when (and api-secret (> (length api-secret) 40))\n    (handler-case\n        (let* ((secret-start (- (length api-secret) 40))\n               (encoded (str:substring 0 secret-start api-secret))\n               (actual-secret (str:substring secret-start nil api-secret))\n               (decoded (base64:base64-string-to-string encoded))\n               (parts (str:split \",\" decoded)))\n          (when (>= (length parts) 3)\n            (values (first parts) actual-secret (third parts))))\n      (error (e)\n        ;; If decoding fails, log a warning since the secret is longer than expected\n        (log:warn \"Failed to decode API secret (length: ~a): ~a. The API secret might be invalid, did you copy it in full?\"\n                  (length api-secret) e)\n        (values nil nil nil)))))\n\n(defun extract-hostname-from-secret (api-secret)\n  \"Extract the hostname from an encoded API secret if possible.\nReturns NIL if the secret doesn't contain hostname information.\"\n  (multiple-value-bind (key secret hostname)\n      (extract-info-from-secret api-secret)\n    (declare (ignore key secret))\n    hostname))\n\n(defun format-api-url (api-context api)\n  (quri:render-uri\n   (quri:merge-uris\n    api\n    (api-hostname :hostname (hostname api-context)))))\n\n(defmethod initialize-instance :after ((self api-context) &rest args &key hostname)\n  ;; Try to extract api-key from the provided secret if key is not provided\n  (when (str:emptyp (key self))\n    (multiple-value-bind (extracted-key extracted-secret extracted-hostname)\n        (extract-info-from-secret (secret self))\n      (declare (ignore extracted-secret extracted-hostname))\n      (when extracted-key\n        (log:debug \"Extracted API key from API secret\")\n        (setf (%key self) extracted-key))))\n\n  (cond\n    ((str:emptyp hostname)\n     ;; Try to extract hostname from the secret first\n     (let ((extracted-hostname (extract-hostname-from-secret (secret self))))\n       (cond\n         ((and extracted-hostname (not (str:emptyp extracted-hostname)))\n          (log:debug \"Extracted hostname from API secret: ~a\" extracted-hostname)\n          (setf (%hostname self) (%fix-hostname extracted-hostname)))\n         (t\n          (setf (%hostname self) \"https://api.screenshotbot.io\")))))\n    (t\n     (setf (%hostname self)\n           (%fix-hostname (hostname self)))))\n  (log:debug \"Using hostname: ~a\" (hostname self))\n  (unless (engine self)\n    (setf (engine self)\n          (make-instance 'api-engine\n                         :session-id (session-id self)))))\n\n(defclass json-api-context (api-context)\n  ((hostname :initarg :hostname\n             :json-key \"hostname\"\n             :json-type :string\n             :reader hostname)\n   (key :initarg :key\n            :json-key \"apiKey\"\n            :json-type :string\n            :reader key)\n   (secret :initarg :secret\n           :json-key \"apiSecretKey\"\n           :json-type :string\n           :reader secret))\n  (:metaclass ext-json-serializable-class)\n  (:documentation \"An API context that can be serialized\"))\n"
  },
  {
    "path": "src/screenshotbot/sdk/backoff.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/backoff\n  (:use #:cl))\n(in-package :screenshotbot/sdk/backoff)\n\n(defun backoff (num)\n  \"A backoff for auto-restart\"\n  (let ((ret (car (nthcdr num (list 5 10 60)))))\n    (warn \"Will retry in ~a seconds\" ret)\n    ret))\n\n(defun add-jitter (num)\n  (* num (+ 0.5 (random 1.0))))\n\n(define-condition too-many-requests (error)\n  ()\n  (:report \"We're making too many requests\"))\n\n(define-condition server-unavailable (error)\n  ()\n  (:report \"The server is unavailable\"))\n\n(defun should-retry-p (attempt)\n  (and\n   (< attempt 5)\n   auto-restart:*global-enable-auto-retries-p*))\n\n(defun maybe-retry-request (response-code &key\n                                            (attempt (error \"must provide :attempt\"))\n                                            (restart (error \"must provide :restart\"))\n                                            (errorp t)\n                                            (backoff 2))\n\n  \"If ERRORP is true, then we'll raise an error when we don't call a restart. (Typically this will be set to NIL fin a signal handler to continue the signal propagation)\"\n  (assert (find-restart restart))\n  (when (member response-code '(408 ;; Typically client side\n                                429\n                                502\n                                503))\n    (cond\n      ((should-retry-p attempt)\n       (let ((timeout (add-jitter (expt backoff (1+ attempt)))))\n         (flet ((%warn (message)\n                  (log:warn \"~a, backing off for ~ds\" message (ceiling timeout))))\n           (cond\n             ((member response-code '(429 503))\n              (%warn \"We're making too many requests\"))\n             ((eql 408 response-code)\n              (%warn \"Request timed out\"))\n             (t\n              (%warn \"The server is unavailable\"))))\n         (sleep timeout)\n         (invoke-restart restart)))\n      (t\n       (when errorp\n        (cond\n          ((member response-code '(429 503))\n           (error 'too-many-requests))\n          (t\n           (error 'server-unavailable))))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/batch.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/batch\n  (:use #:cl)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:with-clingon-api-context)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:local-nicknames (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/batch)\n\n(defun list-reports (api-context &key batch-id)\n  (when (str:emptyp batch-id)\n    (error \"Please provide a --batch-id\"))\n  (multiple-value-bind (result code)\n      (sdk:request api-context\n                   (format nil \"/api/batch/~a/reports\" batch-id)\n                   :decode-response nil\n                   :method :get)\n    (unless (eql 200 code)\n      (error \"API request failed: ~a\" result))\n    (decode-json\n     result\n     `(:list dto:report))))\n\n(defun print-reports (api-context &key batch-id)\n  (loop for report in (list-reports api-context :batch-id batch-id)\n        do (format t \"~a~%\" (dto:report-id report))))\n\n(defun reports/command ()\n  (clingon:make-command\n   :name \"reports\"\n   :description \"Show all the reports that are part of the given batch\"\n   :handler (lambda (cmd)\n              (with-clingon-api-context (api-context cmd)\n               (print-reports api-context :batch-id (getopt cmd :batch-id))))\n   :options (list\n             (make-option\n              :string\n              :long-name \"batch-id\"\n              :key :batch-id\n              :description \"The Batch ID. Typically, if the URL that is linked to it will be /batch/<id>.\"))))\n\n(defun accept-report (api-context report)\n  (log:info \"Accepting ~a\" (dto:report-id report))\n  (multiple-value-bind (result code)\n      (sdk:request api-context\n                   (format nil \"/api/report/~a/review/accept\" (dto:report-id report))\n                   :method :post\n                   :decode-response nil)\n    (unless (eql 200 code)\n      (error \"Could not accept report ~a, ~a\" (dto:report-id report)\n             result))))\n\n(defun %accept-all (api-context &key batch-id)\n  (loop for report in (list-reports api-context :batch-id batch-id)\n        if (not (equal \"accepted\" (dto:report-acceptable-state report)))\n          do\n             (accept-report api-context report)\n        else\n          do\n        (log:info \"Report ~a is already accepted\" (dto:report-id report))))\n\n(defun accept-all/command ()\n  (clingon:make-command\n   :name \"accept-all\"\n   :description \"Accept all reports under this batch\"\n   :handler (lambda (cmd)\n              (with-clingon-api-context (api-context cmd)\n                (%accept-all api-context :batch-id (getopt cmd :batch-id))))\n   :options (list\n             (make-option\n              :string\n              :long-name \"batch-id\"\n              :key :batch-id\n              :description \"The Batch ID. Typically, if the URL that is linked to it will be /batch/<id>.\"))))\n\n(defun batch/command ()\n  (clingon:make-command\n   :name \"batch\"\n   :description \"Commands operating on Batch objects\"\n   :sub-commands (list\n                  (reports/command)\n                  (accept-all/command))))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/bundle.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/sdk/bundle\n  (:use #:cl\n        #:alexandria)\n  (:export\n   #:image-stream\n   #:close-bundle))\n(in-package :screenshotbot/sdk/bundle)\n\n\n(defclass abstract-image ()\n  ((cached-md5 :initform nil\n              :accessor cached-md5)\n   (lock :initform (bt:make-lock))\n   (cv :initform (bt:make-condition-variable))))\n\n(defclass local-image (abstract-image)\n  ((name :initarg :name\n         :accessor image-name)\n   (pathname :initarg :pathname\n             :accessor image-pathname)))\n\n(defclass streamed-image (abstract-image)\n  ((name :initarg :name\n         :accessor image-name)\n   (stream :initarg :stream\n           :accessor image-stream)))\n\n(defmethod close-bundle (bundle)\n  (values))\n\n\n(defmethod close-image ((local-image local-image)))\n\n(defmethod close-image ((image streamed-image))\n  (close (image-stream image)))\n\n(defmethod %md5-sum (image)\n  (let ((stream (image-stream image)))\n    (unwind-protect\n        (ironclad:byte-array-to-hex-string (md5:md5sum-stream stream))\n      (etypecase image\n        (local-image\n         (close stream))\n        (t\n         (file-position stream 0))))))\n\n(defun md5-sum (image)\n  (or (cached-md5 image)\n      (setf (cached-md5 image) (%md5-sum image))))\n\n(defclass image-directory ()\n  ((directory :initarg :directory\n              :accessor bundle-directory)\n   (recursivep :initarg :recursivep\n               :initform nil\n               :reader recursivep)\n   (file-types :initarg :file-types\n               :initform (list \"png\")\n               :reader file-types)))\n\n(defmethod initialize-instance :after ((self image-directory) &key file-types &allow-other-keys)\n  (assert (listp file-types))\n  (loop for type in file-types\n        if  (not (str:s-member '(\"png\" \"jpg\" \"jpeg\" \"heic\" \"jxl\" \"webp\")\n                               type :ignore-case t))\n          do\n             (error \"Invalid image file type: '~a'\" type)))\n\n(defmethod override-image-pathname ((bundle image-directory)\n                                    key pathname)\n  pathname)\n\n(defmethod list-images ((bundle image-directory))\n  (labels ((parse-directory (dir prefix)\n             (loop for im in (fad:list-directory dir)\n                   if (and (recursivep bundle)\n                           (fad:directory-pathname-p im))\n                     append (parse-directory\n                             im\n                             (str:concat prefix (car (last (pathname-directory im))) \"/\"))\n                   if (str:s-member (file-types bundle)\n                                    (pathname-type im)\n                                    :ignore-case t)\n                     collect\n                     (let ((key (str:concat prefix (pathname-name im))))\n                       (let ((image (make-instance 'local-image\n                                                   :name key\n                                                   :pathname (override-image-pathname\n                                                              bundle\n                                                              key\n                                                              im))))\n                         image)))))\n    (log:info \"Parsing directory ~a\" (bundle-directory bundle))\n    (unwind-protect\n         (parse-directory (bundle-directory bundle) \"\")\n      (log:debug \"Done parsing directory\"))))\n\n\n(defmethod image-stream ((im local-image))\n  (open (image-pathname im) :direction :input\n                            :element-type 'flexi-streams:octet))\n\n(defclass image-directory-with-diff-dir (image-directory)\n  ((diff-dir :initarg :diff-dir)))\n\n(defmethod override-image-pathname ((bundle image-directory-with-diff-dir)\n                                    key\n                                    pathname)\n  (with-slots (diff-dir) bundle\n   (let ((potential-file (path:catfile diff-dir\n                                       (format nil \"failed_~a.png\" key))))\n     (cond\n       ((path:-e potential-file)\n        potential-file)\n       (t\n        pathname)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/cli-common.lisp",
    "content": "(defpackage :screenshotbot/sdk/cli-common\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:api-hostname)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:json-api-context\n                #:api-context)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/health-check\n                #:run-health-checks)\n  (:import-from #:screenshotbot/sdk/install\n                #:credential-file)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:import-from #:screenshotbot/sdk/fetch-run\n                #:save-run)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:with-clingon-api-context)\n  (:import-from #:screenshotbot/sdk/upload-commit-graph\n                #:upload-commit-graph/command)\n  (:import-from #:screenshotbot/sdk/batch\n                #:batch/command)\n  (:import-from #:screenshotbot/sdk/pull\n                #:pull/command\n                #:download-run/command)\n  (:export\n   #:with-clingon-api-context\n   #:common-run-options\n   #:register-root-command\n   #:dev/command))\n(in-package :screenshotbot/sdk/cli-common)\n\n(declaim (ftype (function) dev/command))\n\n(defvar *root-commands* nil)\n\n(defun root/handler (cmd)\n  (clingon:print-usage-and-exit cmd t))\n\n(defclass root-command (clingon:command)\n  ())\n\n(defmethod clingon:command-usage-string ((self root-command))\n  \"recorder ci record --directory /path/to/screenshots --channel channel-name\n  recorder dev [record|verify] <options>\")\n\n(defun root-options ()\n  (list\n   (make-option\n    :string\n    :long-name \"api-key\"\n    :initial-value nil\n    :description \"The Screenshotbot API Key\"\n    :env-vars (list \"SCREENSHOTBOT_API_KEY\")\n    :key :api-key)\n   (make-option\n    :string\n    :long-name \"api-secret\"\n    :initial-value nil\n    :key :api-secret\n    :env-vars (list \"SCREENSHOTBOT_API_SECRET\")\n    :description \"The Screenshotbot API Secret\")\n   (make-option\n    :string\n    :key :hostname\n    :long-name \"api-hostname\"\n    :initial-value \"https://api.screenshotbot.io\"\n    :env-vars (list \"SCREENSHOTBOT_API_HOSTNAME\")\n    :description \"The API hostname used as an endpoint. You will need to supply this if using this in the OSS version of Screenshotbot, or if you're an Enterprise customer with a dedicated installation.\")\n   (make-option\n    :boolean\n    :key :verbose\n    :long-name \"verbose\"\n    :description \"Verbose logging\")))\n\n(defun root/command ()\n  (make-instance\n   'root-command\n   :name \"recorder\"\n   :handler #'root/handler\n   :description \"Use this script from your CI pipelines or locally to\nupload screenshots and generate reports with Screenshotbot.\n\nThis is the documentation for the experimental V2 of the CLI interface. To\nview the documentation for the stable interface, run `recorder --help`\nas opposed to `recorder help`.\"\n   :options (root-options)\n   :sub-commands\n   (list\n    (self-test/command)\n    (ci/command)\n    (dev/command)\n    (download-run/command)\n    (pull/command)\n    (batch/command))))\n\n\n(defun ci/command ()\n  (clingon:make-command\n   :name \"ci\"\n   :handler (lambda (cmd)\n              (clingon:print-usage-and-exit cmd t))\n   :description \"Collection of commands that are typically run during CI jobs. In particular, `ci record` might be what you're looking for.\"\n   :sub-commands (list*\n                  (upload-commit-graph/command)\n                  (mapcar #'funcall (mapcar #'cdr *root-commands*)))))\n\n(defun common-run-options ()\n  \"A list of run options that are common between directory runs and static-website runs.\"\n  (list\n   (make-option\n    :flag\n    :long-name \"production\"\n    :initial-value :true\n    :description \"Treat this as a production run on CI. (As opposed to a developer running a local run. This avoids pollution of the main history.)\"\n    :key :production)\n   (make-option\n    :string\n    :long-name \"repo-url\"\n    :initial-value nil\n    :description \"The URL of the repository (e.g. 'https://github.com/foo/bar')\"\n    :key :repo-url)\n   (make-option\n    :string\n    :long-name \"channel\"\n    :initial-value \"unnamed-channel\"\n    :description \"The channel name for this run\"\n    :key :channel)\n   (make-option\n    :string\n    :long-name \"main-branch\"\n    :initial-value nil\n    :description \"The main branch to compare this run with. e.g. `main`, `master`, or `trunk`. The default is to first try `main`, and then `master` and pick the first such that origin/<name> exists.\"\n    :key :main-branch)\n   (make-option\n    :string\n    :long-name \"pull-request\"\n    :initial-value nil\n    :description \"The pull request URL.\"\n    :key :pull-request)))\n\n(defmacro register-root-command (fn)\n  `(setf (assoc-value *root-commands* ,fn) ,fn))\n\n\n(defun self-test/command ()\n  (clingon:make-command\n   :name \"self-test\"\n   :description \"Run a few diagnostic self-tests. This can be useful to figure out why this tool is failing in your environment.\"\n   :handler (lambda (cmd)\n              (uiop:quit (if (run-health-checks) 0 1)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/clingon-api-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/clingon-api-context\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:api-hostname)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:json-api-context\n                #:api-context)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/health-check\n                #:run-health-checks)\n  (:import-from #:screenshotbot/sdk/install\n                #:credential-file)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:import-from #:screenshotbot/sdk/fetch-run\n                #:save-run)\n  (:import-from #:util/request\n                #:engine)\n  (:import-from #:util/reused-ssl\n                #:with-reused-ssl)\n  (:export\n   #:with-clingon-api-context))\n(in-package :screenshotbot/sdk/clingon-api-context)\n\n(defun make-api-context (&key api-key\n                           api-secret\n                           hostname)\n  (cond\n    ((and (not (str:emptyp api-key))\n          (not (str:emptyp api-secret)))\n     (let ((key api-key)\n           (secret api-secret))\n       (when (str:emptyp key)\n         (error \"No --api-key provided\"))\n       (when(str:emptyp secret)\n         (error \"No --api-secret provided\"))\n       (let ((hostname (api-hostname\n                        :hostname hostname)))\n         (log:debug \"Using hostname: ~a\" hostname)\n         (make-instance 'api-context\n                        :key key\n                        :secret secret\n                        :hostname hostname))))\n    ((path:-e (credential-file))\n     (decode-json (uiop:read-file-string (credential-file))\n                  'json-api-context))\n    (t\n     (error \"You must provide a --api-key and --api-secret. (Alternatively, run `~~screenshotbot/recorder dev install` and follow the instructions to install a key.\"))))\n\n(def-easy-macro with-clingon-api-context (&binding api-context cmd &fn fn)\n  (let ((api-context (apply #'make-api-context\n                            (loop for key in '(:api-key :api-secret :hostname)\n                                  append `(,key ,(getopt cmd key))))))\n    (with-reused-ssl ((engine api-context))\n     (funcall fn api-context))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/commit-graph\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/request\n                #:request)\n  (:import-from #:screenshotbot/sdk/git\n                #:null-repo\n                #:read-graph\n                #:fetch-remote-branch\n                #:repo-link)\n  #+lispworks\n  (:import-from #:screenshotbot/sdk/git-pack\n                #:http-upload-pack\n                #:local-upload-pack\n                #:abstract-upload-pack\n                #:remote-ref-equals\n                #:read-commits\n                #:make-remote-upload-pack\n                #:upload-pack\n                #:supported-remote-repo-p)\n  (:import-from #:util/misc\n                #:with-slow-logging\n                #:?.)\n  (:import-from #:util/threading\n                #:with-extras\n                #:ignore-and-log-errors)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-feature-enabled-p\n                #:remote-version)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)\n                    (#:git #:screenshotbot/sdk/git)))\n(in-package :screenshotbot/sdk/commit-graph)\n\n(defclass commit-graph-updater ()\n  ((api-context :initarg :api-context\n                :reader api-context)\n   (all-remote-refs :initform (make-hash-table :test #'equal)\n                    :reader all-remote-refs\n                    :documentation \"A store of all the remote refs sent from the server\")\n   (refs-to-update :initform nil\n                   :accessor refs-to-update\n                   :documentation \"A list of git-refs that will be sent to the server\"))\n  (:documentation \"The commit graph updater will eventually be stateful, in particular\nto cache the refs.\"))\n\n(defmethod get-commit-graph-refs ((self commit-graph-updater)\n                                  repo)\n  (dto:decode-json\n   (request\n    (api-context self)\n    \"/api/commit-graph/refs\"\n    :method :get\n    :decode-response nil\n    :parameters (list\n                 (cons \"repo-url\" repo)))\n   '(:list dto:git-ref)))\n\n(defun new-flow-enabled-p (commit-graph-updater repo)\n  #-lispworks\n  (declare (ignorable repo))\n  #+lispworks\n  (and\n   (>= *api-version* 19)\n   (?. supported-remote-repo-p (git:get-remote-url repo))\n   (api-feature-enabled-p\n    (api-context commit-graph-updater)\n    :cli-shallow-clones))\n  #-lispworks\n  nil)\n\n(defmethod update-commit-graph-old-style ((self commit-graph-updater)\n                                          (repo null-repo)\n                                          branch)\n  (log:info \"No repo to update commit graph\"))\n\n(defmethod update-commit-graph-old-style ((self commit-graph-updater) repo branch)\n  \"Update commit-graph by pulling, and then always push the top 1000 or\nso changes.\"\n  (log:info \"Updating commit graph [old flow]\")\n  (with-slow-logging (\"git fetch\")\n    (fetch-remote-branch repo branch))\n\n  (let* ((dag (read-graph repo))\n         (json (with-output-to-string (s)\n                 (dag:write-to-stream dag s))))\n    (request\n     (api-context self)\n     \"/api/commit-graph\"\n     :method :post\n     :parameters (list\n                  (cons \"repo-url\" (repo-link repo))\n                  (cons \"branch\" branch)\n                  (cons \"graph-json\" json)))))\n\n(defmethod filter-wanted-commits (api-context repo-url commits)\n  \"Check if the server needs these commits, and returns the list of\ncommits that are needed.\"\n  ;; TODO: only make this call if COMMITS is NIL. For now we're\n  ;; avoiding code branches to keep it easier to test, but this should\n  ;; be an easy optimization.\n  (let ((commits (remove-duplicates commits :test #'equal)))\n    (json:decode-json-from-string\n     (request\n      api-context\n      \"/api/commit-graph/check-wants\"\n      :decode-response nil\n      ;; Use POST for API version >= 21 to avoid URL length limits (T2237)\n      :method (if (>= (remote-version api-context) 21) :post :get)\n      :parameters `((\"repo-url\" . ,repo-url)\n                    (\"shas\" . ,(json:encode-json-to-string commits)))))))\n\n(defun find-missing-commits (commits)\n  (fset:convert\n   'list\n   (fset:set-difference\n    (fset:convert 'fset:set\n                  (loop for (nil . parents) in  commits\n                        appending parents))\n    (fset:convert 'fset:set\n                  (loop for (commit . nil) in commits\n                        collect commit)))))\n\n(defun get-local-commits (repo)\n  (let ((dag (git::read-graph repo)))\n    (loop for commit in (dag:all-commits dag)\n          for parents = (dag:parents commit)\n          collect\n          (progn\n            (list* (dag:sha commit)\n                   parents)))))\n\n#+lispworks\n(defun update-commit-graph-new-style (api-context repo branch)\n  (let ((remote-url (git:get-remote-url repo)))\n    (with-extras ((\"remote-git-url\" remote-url)\n                  (\"git-config-anonymized\" (or (git:debug-git-config repo)\n                                               \"failed\")))\n      (let ((local-commits (get-local-commits repo)))\n        (let ((upload-pack (make-remote-upload-pack repo)))\n          (update-from-pack\n           api-context\n           upload-pack\n           (or (repo-link repo)\n               remote-url)\n           (list branch\n                 (git:current-branch repo))\n           :local-commits local-commits))))))\n\n(defun ref-in-sync-p (known-refs sha ref)\n  \"At this point, we know we're interested in this ref. What we need to know is if this is in sync with the server\"\n  (declare (ignore ref))\n  (loop for known-ref in known-refs\n        ;; Weird, note that we actually don't care about ref here,\n        ;; since if this commit was seen on the server, then that's\n        ;; good enough.\n        if (equal (dto:git-ref-sha known-ref) sha)\n          return t))\n\n#+lispworks\n(defun want-remote-ref (known-refs branches sha ref)\n  (check-type known-refs list)\n  (loop for branch in branches\n        if (remote-ref-equals branch ref)\n          return (not (ref-in-sync-p known-refs sha ref))))\n\n(defun save-refs (self refs)\n  \"Save the refs for reading in the future\"\n  (loop for  (sha . ref) in refs\n        do (setf (gethash ref (all-remote-refs self)) sha)))\n\n(defmethod maybe-push-ref-to-update ((self commit-graph-updater) sha ref)\n  (when (str:starts-with-p \"refs/heads/\" ref)\n    (push (make-instance 'dto:git-ref\n                         :name (str:substring (length \"refs/heads/\") nil ref)\n                         :sha sha)\n          (refs-to-update self))))\n\n#+lispworks\n(auto-restart:with-auto-restart (:retries 3)\n  (defmethod read-commits-with-retries ((upload-pack http-upload-pack) &rest args)\n    \"GitLab's https endpoint is buggy, and occassionally returns 401. See T2111.\"\n    (apply #'read-commits upload-pack args)))\n\n(defmethod read-commits-with-retries (upload-pack &rest args)\n  (apply #'read-commits upload-pack args))\n\n(defun remove-duplicate-commits (commits)\n  \"Remove duplicate commits by SHA, keeping the first occurrence.\"\n  (let ((seen (make-hash-table :test #'equal)))\n    (loop for commit in commits\n          unless (gethash (car commit) seen)\n            do (setf (gethash (car commit) seen) t)\n            and collect commit)))\n\n#+lispworks\n(defmethod update-from-pack ((self commit-graph-updater)\n                             (upload-pack abstract-upload-pack)\n                             (repo-url string)\n                             branches\n                             &key local-commits)\n  (log:info \"Getting known refs from Screenshotbot server\")\n  (let ((known-refs\n          ;; Technically we could combine this with the /check-wants\n          ;; if we do it carefully\n          (get-commit-graph-refs self repo-url)))\n    (check-type known-refs list)\n    (log:info \"Getting git graph via git-upload-pack\")\n    (let* ((missing-local-commits (find-missing-commits local-commits))\n           (commits (read-commits-with-retries\n                     upload-pack\n                     ;; TODO: also do release branches, but that will need a regex here\n                     :wants (lambda (list)\n                              (save-refs self list)\n                              ;; This might make a network call\n                              (let ((wants (build-wants self\n                                                        :missing-local-commits missing-local-commits\n                                                        :known-refs known-refs\n                                                        :branches branches\n                                                        :repo-url repo-url)))\n                               (log:debug \"Wanting: ~a\" wants)\n                               wants))\n                    :haves (loop for known-ref in known-refs\n                                 collect (dto:git-ref-sha known-ref))\n                    :parse-parents t)))\n      (%finish-update-commit-graph self\n                                   :commits (remove-duplicate-commits\n                                             (append local-commits commits))\n                                   :repo-url repo-url\n                                   :refs (refs-to-update self)))))\n\n(defmethod build-wants ((self commit-graph-updater) &key missing-local-commits\n                                                      known-refs\n                                                      branches\n                                                      repo-url)\n  \"Build a list of commit SHAs that the server wants by filtering remote refs\nagainst known refs and branches, then checking with the server which commits\nare actually needed.\n\nThis function does have a side effect: it stores the the refs need to\nbe updated in the REFS-TO-UPDATE slot.\n\nReturns a list of commit SHAs that should be included in the commit graph update.\n\nLOCAL-COMMITS are of the form (list (commit . parents)*)\"\n  (let* ((shas (remove-if\n                #'null\n                (append\n                 missing-local-commits\n                 (loop for ref being the hash-keys of (all-remote-refs self)\n                         using (hash-value sha)\n                       if (want-remote-ref known-refs branches\n                                           sha ref)\n                         collect (progn\n                                   (maybe-push-ref-to-update self sha ref)\n                                   sha))))))\n    ;; This will make a network call\n    (filter-wanted-commits\n     (api-context self)\n     repo-url\n     shas)))\n\n(defmethod %finish-update-commit-graph ((self commit-graph-updater)\n                                        &key commits\n                                          repo-url\n                                          refs)\n  (let ((commits (loop for (sha . parents) in commits\n                       collect (make-instance 'dto:commit\n                                              :sha sha\n                                              :parents parents))))\n    (log:info \"Updating git commit-graph\")\n    (request\n     (api-context self)\n     \"/api/commit-graph\"\n     :method :post\n     :parameters (list\n                  (cons \"repo-url\" repo-url)\n                  (cons \"graph-json\" (dto:encode-json\n                                      (make-instance 'dto:commit-graph\n                                                     :commits commits)))\n                  (cons \"refs\" (dto:encode-json (or refs #())))))))\n\n\n(defmethod update-commit-graph ((self commit-graph-updater) repo branch &key)\n  (log:info \"Updating commit graph\")\n  (when (api-feature-enabled-p (api-context self) :always-git-fetch)\n    (log:info \"The always-git-fetch feature is enabled, so we'll run a git fetch now\")\n    (fetch-remote-branch repo branch))\n  (cond\n    ((new-flow-enabled-p self repo)\n     (or\n      (ignore-and-log-errors ()\n        (trivial-timeout:with-timeout (60)\n          (update-commit-graph-new-style self repo branch))\n        (log:debug \"New commit graph uploaded successfully\")\n        t)\n      (progn\n        (warn \"Reverting to old commit-graph flow\")\n        (update-commit-graph-old-style self repo branch))))\n    (t\n     (log:info \"Using old flow for commit-graph\")\n     #+nil\n     (warn \"Using the old commit-graph flow for ~a\" (git:get-remote-url repo))\n     (update-commit-graph-old-style self repo branch))))\n\n(defmethod update-commit-graph (self (repo null-repo) branch &key &allow-other-keys)\n  (log:info \"Not updating the commit graph, since there's no repo\"))\n"
  },
  {
    "path": "src/screenshotbot/sdk/common-flags.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/common-flags\n  (:use #:cl\n        #:com.google.flag)\n  (:shadow #:define-flag)\n  (:export\n   #:*verbose*\n   #:*help*\n   #:*sdk-flags*\n   #:define-flag\n   #:obsolete?))\n(in-package :screenshotbot/sdk/common-flags)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *sdk-flags* (make-hash-table)))\n\n;; For easy reloading, this can be deleted in the future, but might\n;; require a restart of the server.\n#+lispworks\n'(*hostname*\n *api-key*\n *api-secret*)\n\n(defmacro define-flag (name &rest args\n     &key selector &allow-other-keys)\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (setf (gethash ,(intern (str:upcase selector) \"KEYWORD\") *sdk-flags*)\n           ',name)\n     (com.google.flag:define-flag ,name\n       ,@args)))\n\n(define-flag *verbose*\n  :default-value nil\n  :selector \"verbose\"\n  :type boolean\n  :help \"Verbose logs\")\n\n(define-flag *help*\n  :selector \"help\"\n  :default-value nil\n  :type boolean)\n\n(defun obsolete? (flag)\n  (str:containsp \"[OBSOLETE]\" (com.google.flag::help flag)))\n"
  },
  {
    "path": "src/screenshotbot/sdk/deliver-java.lisp",
    "content": "(in-package :cl-user)\n\n(ql:quickload :screenshotbot.sdk/gradle)\n\n(defun output-file ()\n  (car (asdf:output-files\n        'asdf:compile-op\n         (asdf:find-component :screenshotbot.sdk/deliver-java \"deliver-java\"))))\n\n(defun deliver-main ()\n  (let ((output-file (output-file)))\n    (ensure-directories-exist output-file)\n\n    (deliver 'screenshotbot/sdk/gradle:main\n              output-file\n              5\n              #+mswindows :console #+mswindows :init\n              #+mswindows :startup-bitmap-file #+mswindows nil\n              :packages-to-keep-symbol-names :all\n              :keep-clos-object-printing t\n              :keep-symbols `(system:pipe-exit-status\n                              ironclad:sha1\n                              simple-test)\n              :multiprocessing t)))\n\n(deliver-main)\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/deliver-sdk.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :cl-user)\n\n(ql:quickload :screenshotbot.sdk)\n(ql:quickload :core.cli/deliver)\n\n(defun output-file ()\n  (car (asdf:output-files 'asdf:compile-op (asdf:find-component :screenshotbot.sdk/deliver \"deliver-sdk\"))))\n\n(defun template-builder ()\n  (tmpdir:with-tmpdir (dir)\n    (unwind-protect\n         (let ((git-repo (make-instance 'screenshotbot/sdk/git::git-repo\n                                        :dir dir\n                                        :link \"https://github.com/tdrhq/fast-example\")))\n           (screenshotbot/sdk/git::$\n             (list \"git\" \"clone\" \"https://github.com/tdrhq/fast-example\"\n                   dir))\n           (handler-case\n               (screenshotbot/sdk/sdk::update-commit-graph\n                (make-instance 'screenshotbot/sdk/api-context:api-context\n                               :key \"deliver-sdk\"\n                               :secret \"xxx\"\n                               :hostname \"https://screenshotbot.io\")\n                git-repo \"bar\")\n             (screenshotbot/sdk/sdk::api-error ()\n               nil)))\n      #+mswindows\n      (screenshotbot/sdk/git::$\n        (list \"attrib\" \"-r\" (format nil \"~a\\*.*\" (namestring dir)) \"/s\")))))\n\n(compile 'template-builder)\n\n(core/cli/deliver:deliver-end-user-cli\n :restart-fn 'screenshotbot/sdk/main:main\n :deliver-script *load-truename*\n :output-file (output-file)\n :template-builder-fn #'template-builder)\n"
  },
  {
    "path": "src/screenshotbot/sdk/dev/commands.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/dev/commands\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:dev/command)\n  (:import-from #:screenshotbot/sdk/dev/record-verify\n                #:verify/command\n                #:record/command)\n  (:import-from #:screenshotbot/sdk/install\n                #:install/command)\n  (:import-from #:screenshotbot/sdk/dev/verify-against-ci\n                #:verify-against-ci/command))\n(in-package :screenshotbot/sdk/dev/commands)\n\n(defun dev/command ()\n  (clingon:make-command\n   :name \"dev\"\n   :description \"Tools that are run from a developer device (as opposed to CI)\"\n   :handler (lambda (cmd)\n              (clingon:print-usage-and-exit cmd t))\n   :sub-commands\n   (list\n    (record/command)\n    (verify/command)\n    (install/command)\n    (verify-against-ci/command))))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/dev/record-verify.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/dev/record-verify\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:with-flags-from-run-context\n                #:productionp\n                #:env-reader-run-context\n                #:run-context)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:put-run-with-run-context\n                #:upload-image-directory\n                #:request)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:with-clingon-api-context\n                #:dev/command)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/env\n                #:env-reader)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:image-directory)\n  (:import-from #:screenshotbot/sdk/sentry\n                #:with-sentry)\n  (:import-from #:screenshotbot/sdk/git\n                #:null-repo)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json\n                #:encode-json)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:screenshotbot/sdk/install\n                #:install/command\n                #:install-credentials)\n  (:export\n   #:dev/command)\n  (:local-nicknames (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/dev/record-verify)\n\n(defclass dev-run-context (run-context\n                           env-reader-run-context)\n  ()\n  (:default-initargs :env (make-instance 'env-reader)))\n\n(defun default-options ()\n  ;; TODO: recursive\n  (list\n   (make-option\n    :string\n    :long-name \"channel\"\n    :initial-value \"default-dev-channel\"\n    :description \"The channel name to use. We save recordings on a per channel basis\"\n    :key :channel)\n   (make-option\n    :string\n    :long-name \"directory\"\n    :short-name #\\d\n    :description \"The directory with screenshots to record\"\n    :initial-value nil\n    :required t\n    :key :directory)\n   (make-option\n    :flag\n    :long-name \"recursive\"\n    :short-name #\\r\n    :description \"Whether to recursively parse the directory for screenshots\"\n    :initial-value nil\n    :key :recursivep)\n   (make-option\n    :string\n    :long-name \"image-file-types\"\n    :key :image-file-types\n    :initial-value \"png\"\n    :description \"When scanning a directory, this is the list of file extensions\nwe consider as images. This defaults to `png`, but we support PNG, WEBP, HEIC, JXL, JPG. We do not\nrecommend JPG or any other lossy formats. You can separate multiple\nextensions with a comma.\")))\n\n(defmethod productionp ((self dev-run-context))\n  nil)\n\n(defun %make-run-and-get-id (api-ctx &key directory channel recursivep\n                                       compare-threshold\n                                       (file-types (list \"png\")))\n  (let ((run-ctx (make-instance 'dev-run-context\n                            :productionp nil\n                            :channel channel\n                            :compare-threshold compare-threshold\n                            :main-branch \"main\")))\n    (let* ((image-directory (make-instance 'image-directory\n                                           :directory directory\n                                           :recursivep recursivep\n                                           :file-types file-types))\n           (screenshots (upload-image-directory api-ctx image-directory)))\n      (put-run-with-run-context\n       api-ctx\n       run-ctx\n       screenshots))))\n\n(defun make-run-and-get-id (cmd)\n  (when (getopt cmd :verbose)\n    (log:config :debug))\n  (with-clingon-api-context (api-ctx cmd)\n    (%make-run-and-get-id\n     api-ctx\n     :channel (getopt cmd :channel)\n     :directory (getopt cmd :directory)\n     :recursivep (getopt cmd :recursivep)\n     :file-types (str:split \",\" (getopt cmd :image-file-types)))))\n\n(defun homedir ()\n  (path:-d (uiop:getenv \"HOME\")))\n\n(defun recording-file (channel)\n  (ensure-directories-exist\n   (path:catfile\n    (homedir)\n    \".config/screenshotbot/recordings/\"\n    (format nil \"~a.json\" channel))))\n\n(defun record-run (run channel)\n  (let ((recording (recording-file channel)))\n    (uiop:with-staging-pathname (recording)\n      (with-open-file (stream recording :direction :output\n                                        :if-exists :supersede)\n        (write-string (encode-json run) stream)))))\n\n(defun record/command ()\n  (clingon:make-command\n   :name \"record\"\n   :description \"Record a user run\"\n   :options (default-options)\n   :handler (lambda (cmd)\n              (with-sentry ()\n                (let ((run (make-run-and-get-id cmd)))\n                  (record-run run (getopt cmd :channel)))))))\n\n(defun read-recorded-run (channel)\n  (let ((file (recording-file channel)))\n    (cond\n      ((path:-e file)\n       (decode-json (uiop:read-file-string file)\n                    'dto:run))\n      (t\n       (error \"No recording for channel ~a\" channel)))))\n\n(defun compare-runs (api-context run1 run2)\n  (let ((body\n          (request api-context (format nil \"/api/run/~a/compare/~a\"\n                                       (dto:run-id run1)\n                                       (dto:run-id run2)))))\n    (make-instance 'dto:comparison\n                   :samep (assoc-value body :samep)\n                   :title (assoc-value body :title)\n                   :url (assoc-value body :url))))\n\n(defun compare-and-log (api-ctx run recorded-run)\n  (let ((comparison (compare-runs api-ctx run recorded-run)))\n    (cond\n      ((dto:comparison-samep comparison)\n       (log:info \"Nothing's changed.\")\n       (uiop:quit 0))\n      (t\n       (log:info \"~a\" (dto:comparison-title comparison))\n       (log:info \"See changes at ~a\" (dto:comparison-url comparison))\n       (uiop:quit 1)))))\n\n\n(defun verify/command ()\n  (clingon:make-command\n   :name \"verify\"\n   :description \"Verify a run against the last recorded run\"\n   :options (default-options)\n   :handler (lambda (cmd)\n              (with-sentry ()\n                (let ((recorded-run (read-recorded-run (getopt cmd :channel))))\n                  (with-clingon-api-context (api-ctx cmd)\n                   (let* ((run (make-run-and-get-id cmd)))\n                     (compare-and-log api-ctx run recorded-run))))))))\n\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/dev/test-record-verify.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/dev/test-record-verify\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/dev/record-verify\n                #:record-run\n                #:homedir\n                #:%make-run-and-get-id\n                #:record/command)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:put-run\n                #:request)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:remote-version)\n  (:import-from #:screenshotbot/sdk/integration-fixture\n                #:with-sdk-integration)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:compare-threshold\n                #:trunkp\n                #:recorder-run)\n  (:import-from #:fiveam-matchers/numbers\n                #:is-number-close-to)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/dev/test-record-verify)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-sdk-integration (api-context)\n   (cl-mock:with-mocks ()\n     (if-called 'uiop:quit (lambda (code)\n                             (error \"exiting with: ~a\" code)))\n     (tmpdir:with-tmpdir (%homedir)\n       (if-called 'homedir (lambda ()\n                             %homedir))\n       (tmpdir:with-tmpdir (dir)\n         (uiop:copy-file\n          #.(asdf:system-relative-pathname\n             :screenshotbot.sdk\n             \"fixture/dir1/rose.png\")\n          (path:catfile dir \"foo.png\"))\n         (&body))))))\n\n(test record-happy-path\n  (with-fixture state ()\n    (finishes\n      (record-run\n       (%make-run-and-get-id\n        api-context\n        :directory dir\n        :channel \"foo\")\n       \"foo\"))\n    (is (path:-e (path:catfile %homedir \".config/screenshotbot/recordings/foo.json\")))))\n\n(test run-has-productionp-as-nil\n  \"This is a critical test, because we don't want these to pollute the production runs\"\n  (with-fixture state ()\n    (finishes\n      (record-run\n       (%make-run-and-get-id\n        api-context\n        :directory dir\n        :channel \"foo\")\n       \"foo\"))\n    (let ((run (first (bknr.datastore:class-instances 'recorder-run))))\n      (is-false (trunkp run)))))\n\n(test run-has-theshold\n  \"This is a critical test, because we don't want these to pollute the production runs\"\n  (with-fixture state ()\n    (finishes\n      (record-run\n       (%make-run-and-get-id\n        api-context\n        :compare-threshold 0.77\n        :directory dir\n        :channel \"foo\")\n       \"foo\"))\n    (let ((run (first (bknr.datastore:class-instances 'recorder-run))))\n      (assert-that\n       (compare-threshold run)\n       (is-number-close-to 0.77)))))\n\n(test 0-threshold-is-always-zero\n  \"This is a critical test, because we don't want these to pollute the production runs\"\n  (with-fixture state ()\n    (finishes\n      (record-run\n       (%make-run-and-get-id\n        api-context\n        :compare-threshold 0.0\n        :directory dir\n        :channel \"foo\")\n       \"foo\"))\n    (let ((run (first (bknr.datastore:class-instances 'recorder-run))))\n      (is (eql 0.0D0\n               (compare-threshold run))))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/dev/test-verify-against-ci.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/dev/test-verify-against-ci\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/integration-fixture\n                #:with-sdk-integration)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:util/fake-clingon\n                #:make-fake-clingon)\n  (:import-from #:screenshotbot/sdk/dev/verify-against-ci\n                #:parse-threshold\n                #:find-base-run\n                #:%verify-against-ci\n                #:%options)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:auto-restart\n                #:*global-enable-auto-retries-p*)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:*synchronous-promotion*)\n  (:local-nicknames (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/dev/test-verify-against-ci)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*global-enable-auto-retries-p* nil))\n   (cl-mock:with-mocks ()\n     (cl-mock:if-called 'uiop:quit (lambda (err)))\n     (with-sdk-integration (api-context :company company)\n       \n       (let* ((*synchronous-promotion* t)\n              (channel (find-or-create-channel company \"foobar\"))\n              (run (make-recorder-run\n                    :company company\n                    :commit-hash \"abcd\"\n                    :channel channel\n                    :trunkp t\n                    :screenshots nil)))\n         (&body))))))\n\n(test find-base-run-test\n  (with-fixture state ()\n    (let ((run (find-base-run api-context \"foobar\" \"abcd\")))\n      (assert-that run\n                   (has-typep 'dto:run)))))\n\n(test verify-against-ci\n  (with-fixture state ()\n    (let ((cmd (make-fake-clingon (%options)\n                                  :directory (namestring\n                                              (asdf:system-relative-pathname\n                                               :screenshotbot.sdk\n                                               \"fixture/dir1/\"))\n                                  :image-file-types \"png\"\n                                  :recursivep nil\n                                  :channel \"foobar\"\n                                  :base-commit-hash \"abcd\")))\n      (%verify-against-ci api-context\n                          cmd))))\n\n(test verify-against-ci-when-base-commit-doesnt-exist\n  (with-fixture state ()\n    (let ((cmd (make-fake-clingon (%options)\n                                  :directory (namestring\n                                              (asdf:system-relative-pathname\n                                               :screenshotbot.sdk\n                                               \"fixture/dir1/\"))\n                                  :image-file-types \"png\"\n                                  :recursivep nil\n                                  :channel \"foobar\"\n                                  :base-commit-hash \"dcba\")))\n      (signals-error-matching ()\n          (%verify-against-ci api-context\n                              cmd)\n          (error-with-string-matching\n           (matches-regex \".*Could not find run for commit.*\"))))))\n\n(test parse-threshold\n  (eql 0.111 (parse-threshold \"0.111\"))\n  (eql 1 (parse-threshold \"1\"))\n  (eql nil (parse-threshold nil))\n  (eql nil (parse-threshold \"\"))  \n  (signals-error-matching ()\n                          (parse-threshold \"sdfdsfds\")\n                          (error-with-string-matching\n                           (matches-regex \".*Threshold must be a floating point number between 0 and 1: got \")))\n  (signals-error-matching ()\n                          (parse-threshold \"2.0\")\n                          (error-with-string-matching\n                           (matches-regex \".*Threshold must be a floating point number between 0 and 1: got \"))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/dev/verify-against-ci.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/dev/verify-against-ci\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/dev/record-verify\n                #:compare-and-log\n                #:%make-run-and-get-id\n                #:default-options)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:with-clingon-api-context)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:request)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:import-from #:util/misc\n                #:not-null!)\n  (:import-from #:parse-float\n                #:parse-float)\n  (:local-nicknames (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/dev/verify-against-ci)\n\n(defun find-base-run (api-ctx channel commit)\n  (let ((res (request api-ctx\n                      (format nil \"/api/find-base-run?channel=~a&commit=~a\"\n                              channel commit)\n                      :method :get\n                      :decode-response nil)))\n    (decode-json\n     res\n     '(or null dto:run))))\n\n(defun %verify-against-ci (api-ctx cmd)\n  (let* ((channel (getopt cmd :channel))\n         (commit (getopt cmd :base-commit-hash))\n         (base-run (find-base-run api-ctx channel commit))\n         (run (%make-run-and-get-id\n               api-ctx\n               :compare-threshold (parse-threshold (getopt cmd :threshold))\n               :channel channel\n               :directory (getopt cmd :directory)\n               :recursivep (getopt cmd :recursivep)\n               :file-types (str:split \",\" (getopt cmd :image-file-types)))))\n    (unless base-run\n      (error \"Could not find run for commit: ~a\" commit))\n    (compare-and-log api-ctx run base-run)))\n\n(defun %options ()\n  (list*\n   (make-option\n    :string\n    :long-name \"threshold\"\n    :initial-value nil\n    :description \"The threshold to use to compare\"\n    :key :threshold)\n   (make-option\n    :string\n    :long-name \"base-commit\"\n    :description \"The base commit-SHA to compare against\"\n    :key :base-commit-hash\n    :required t)\n   (default-options)))\n\n(defun parse-threshold (str)\n  (when (str:non-empty-string-p str)\n   (flet ((threshold-error ()\n            (error \"Threshold must be a floating point number between 0 and 1: got ~a\" str)))\n     (let ((res (handler-case\n                    (parse-float str)\n                  (alexandria:simple-parse-error (e)\n                    (threshold-error)))))\n       (unless (<= 0 res 1)\n         (threshold-error))\n       res))))\n\n\n(defun verify-against-ci/command ()\n  (clingon:make-command\n   :name \"verify-against-ci\"\n   :description \"Verify your local screenshots against a CI version\"\n   :options (%options)\n   :handler (lambda (cmd)\n              (with-clingon-api-context (api-ctx cmd)\n                (%verify-against-ci\n                 api-ctx\n                 cmd)))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/env.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/env\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:assoc-value\n                #:when-let\n                #:if-let)\n  (:import-from #:screenshotbot/sdk/git\n                #:git-message\n                #:git-repo)\n  (:shadow #:getenv)\n  (:export\n   #:make-env-reader\n   #:api-key\n   #:api-secret\n   #:api-hostname\n   #:pull-request-url\n   #:sha1\n   #:build-url\n   #:repo-url\n   #:guess-channel-name\n   #:work-branch\n   #:pull-request-base-branch))\n(in-package :screenshotbot/sdk/env)\n\n(define-condition unsupported-variable (condition)\n  ())\n\n(defclass base-env-reader ()\n  ((overrides :initarg :overrides\n              :reader overrides))\n  (:documentation \"Reads the environment to get some information about the current build\"))\n\n(defgeneric work-branch (env)\n  (:documentation \"This isn't being used, and can be removed if required. Currently we\nget this from the Git repository directly.\"))\n\n(defclass env-reader (base-env-reader)\n  ())\n\n(defmethod getenv ((self base-env-reader) name)\n  (if (slot-boundp self 'overrides)\n   (assoc-value (overrides self)\n                name :test #'string=)\n   (uiop:getenv name)))\n\n(defmethod api-key ((self base-env-reader))\n  (getenv self \"SCREENSHOTBOT_API_KEY\"))\n\n(defmethod api-secret ((self base-env-reader))\n  (getenv self \"SCREENSHOTBOT_API_SECRET\"))\n\n(defmethod api-hostname ((self base-env-reader))\n  (getenv self \"SCREENSHOTBOT_API_HOSTNAME\"))\n\n(defmethod pull-request-url ((self env-reader))\n  nil)\n\n(defgeneric pull-request-base-branch (env-reader)\n  (:documentation \"This isn't being used, at least not for any significant logic.\n\nIt isn't supported by all CI environments, in particular CircleCI\ndoesn't provide it.\n\nThe bigger problem is that the build might run before this PR\ninformation is available, so we can't have complex logic rely on it.\"))\n\n(defmethod pull-request-base-branch ((self env-reader))\n  nil)\n\n(defmethod guess-channel-name ((self base-env-reader))\n  (when-let ((repo-url (repo-url self)))\n    (let ((ret (remove-.git (car (last (remove-if #'str:emptyp (str:split \"/\" repo-url)))))))\n      (unless (str:emptyp ret)\n        ret))))\n\n(defmethod work-branch ((self env-reader))\n  nil)\n\n(defmethod build-url ((self env-reader))\n  ;; For jenkins\n  (getenv self \"BUILD_URL\"))\n\n(defmethod repo-url ((self env-reader))\n  nil)\n\n(defmethod sha1 ((self env-reader))\n  nil)\n\n(defgeneric validp (env-reader)\n  (:documentation \"Is the current environment reader valid for this situation\"))\n\n(defclass circleci-env-reader (base-env-reader)\n  ())\n\n(defmethod build-url ((self circleci-env-reader))\n  (getenv self \"CIRCLE_BUILD_URL\"))\n\n(defmethod repo-url ((self circleci-env-reader))\n  (getenv self \"CIRCLE_REPOSITORY_URL\"))\n\n(defmethod validp ((self circleci-env-reader))\n  (or\n   (sha1 self)\n   (pull-request-url self)))\n\n(defmethod pull-request-url ((self circleci-env-reader))\n  (getenv self \"CIRCLE_PULL_REQUEST\"))\n\n(defmethod pull-request-base-branch ((self circleci-env-reader))\n  (signal 'unsupported-variable)\n  nil)\n\n(defmethod sha1 ((self circleci-env-reader))\n  (getenv self \"CIRCLE_SHA1\"))\n\n(defmethod work-branch ((self circleci-env-reader))\n  (getenv self \"CIRCLE_BRANCH\"))\n\n(defclass bitrise-env-reader (base-env-reader)\n  ())\n\n(defmethod validp ((self bitrise-env-reader))\n  (or\n   (pull-request-url self)\n   (sha1 self)))\n\n(defmethod build-url ((self bitrise-env-reader))\n  (getenv self \"BITRISE_BUILD_URL\"))\n\n(defmethod pull-request-url ((self bitrise-env-reader))\n  (if-let ((repo-url (repo-url self))\n           (pull-id (getenv self \"BITRISE_PULL_REQUEST\")))\n    (link-to-github-pull-request repo-url pull-id)))\n\n(defmethod pull-request-base-branch ((self bitrise-env-reader))\n  (getenv self \"BITRISEIO_GIT_BRANCH_DEST\"))\n\n(defmethod repo-url ((Self bitrise-env-reader))\n  (or\n   ;; TODO: this one is probably incorrect.\n   (getenv self \"BITRISEIO_PULL_REQUEST_REPOSITORY_URL\")\n   (getenv self \"GIT_REPOSITORY_URL\")))\n\n(defmethod work-branch ((self bitrise-env-reader))\n  (getenv self \"BITRISE_GIT_BRANCH\"))\n\n(defun link-to-github-pull-request (repo-url pull-id)\n  (let ((key (cond\n               ((str:containsp \"bitbucket\" repo-url)\n                \"pull-requests\")\n               (t\n                \"pulls\"))))\n   (format nil \"~a/~a/~a\"\n           repo-url\n           key\n           pull-id)))\n\n(defclass netlify-env-reader (base-env-reader)\n  ())\n\n(defmethod pull-request-url ((self netlify-env-reader))\n  (let ((pull-request-p (equal \"true\" (getenv self \"PULL_REQUEST\"))))\n    (when pull-request-p\n      (when-let ((review-id (getenv self \"REVIEW_ID\")))\n        (link-to-github-pull-request\n         (repo-url self)\n         review-id)))))\n\n(defmethod pull-request-base-branch ((self netlify-env-reader))\n  (signal 'unsupported-variable)\n  nil)\n\n(defmethod build-url ((self netlify-env-reader))\n  (let ((build-id (getenv self \"BUILD_ID\"))\n        (site-name (getenv self \"SITE_NAME\")))\n    (format nil \"https://app.netlify.com/sites/~a/deploys/~a\"\n            site-name\n            build-id)))\n\n(defmethod repo-url ((self netlify-env-reader))\n  (getenv self \"REPOSITORY_URL\"))\n\n(defmethod guess-channel-name ((self netlify-env-reader))\n  (getenv self \"SITE_NAME\"))\n\n(defmethod sha1 ((self netlify-env-reader))\n  (getenv self \"COMMIT_REF\"))\n\n(defun remove-.git (name)\n  (cl-ppcre:regex-replace \"[.]git$\" name \"\"))\n\n(defmethod validp ((self netlify-env-reader))\n  (equal \"true\" (getenv self \"NETLIFY\")))\n\n(defmethod validp ((self env-reader))\n  t)\n\n(defmethod work-branch ((self netlify-env-reader))\n  (getenv self \"BRANCH\"))\n\n(defmethod sha1 ((self bitrise-env-reader))\n  (getenv self \"BITRISE_GIT_COMMIT\"))\n\n(defclass azure-env-reader (base-env-reader)\n  ())\n\n(defmethod getenv ((self azure-env-reader) name)\n  \"Make it convenient to copy paste environment variables from Azure docs\"\n  (call-next-method\n   self\n   (str:upcase (str:replace-all \".\" \"_\" name))))\n\n(defmethod validp ((self azure-env-reader))\n  (getenv self \"Build.BuildId\"))\n\n(defmethod pull-request-url ((self azure-env-reader))\n  (when-let ((repo-url (getenv self \"System.PullRequest.SourceRepositoryURI\"))\n             (pull-id (getenv self \"System.PullRequest.PullRequestId\")))\n   (format nil \"~a/pullrequest/~a\"\n           repo-url\n           pull-id)))\n\n(defmethod pull-request-base-branch ((self azure-env-reader))\n  (getenv self \"System.PullRequest.targetBranchName\"))\n\n(defmethod sha1 ((self azure-env-reader))\n  (getenv self \"Build.SourceVersion\"))\n\n\n(defmethod build-url ((self azure-env-reader))\n  ;; This does not return an HTTPS url sadly...\n  (getenv self \"Build.BuildUri\"))\n\n(defmethod repo-url ((self azure-env-reader))\n  (getenv self \"Build.Repository.Uri\"))\n\n(defmethod work-branch ((self azure-env-reader))\n  (getenv self \"Build.SourceBranchName\"))\n\n;; https://buildkite.com/docs/pipelines/environment-variables\n(defclass buildkite-env-reader (base-env-reader)\n  ())\n\n(defmethod validp ((self buildkite-env-reader))\n  (getenv self \"BUILDKITE_BUILD_ID\"))\n\n(defmethod make-pr-url (self repo pr-id)\n  (when-let ((repo-url (getenv self repo))\n             (pull-id (getenv self pr-id)))\n    (format nil \"~a/pull/~a\"\n            repo-url\n            pull-id)))\n\n(defmethod pull-request-url ((self  buildkite-env-reader))\n  (let ((pr \"BUILDKITE_PULL_REQUEST\"))\n   (unless (string-equal \"false\" (getenv self pr))\n     (make-pr-url self\n                  \"BUILDKITE_REPO\"\n                  pr))))\n\n(defmethod pull-request-base-branch ((self buildkite-env-reader))\n  (getenv self \"BUILDKITE_PULL_REQUEST_BASE_BRANCH\"))\n\n(defmethod sha1 ((self buildkite-env-reader))\n  (getenv self \"BUILDKITE_COMMIT\"))\n\n(defmethod build-url ((self buildkite-env-reader))\n  (format nil\n          \"~a#~a\"\n          (getenv self \"BUILDKITE_BUILD_URL\")\n          (getenv self \"BUILDKITE_JOB_ID\")))\n\n(defmethod repo-url ((Self buildkite-env-reader))\n  (getenv self \"BUILDKITE_REPO\"))\n\n(defmethod work-branch ((self buildkite-env-reader))\n  (getenv self \"BUILDKITE_BRANCH\"))\n\n(defclass bitbucket-pipeline-env-reader (base-env-reader)\n  ())\n\n(defmethod validp ((self bitbucket-pipeline-env-reader))\n  (getenv self \"BITBUCKET_BUILD_NUMBER\"))\n\n(defmethod pull-request-url ((self bitbucket-pipeline-env-reader))\n  (make-pr-url\n   self\n   \"BITBUCKET_GIT_HTTP_ORIGIN\"\n   \"BITBUCKET_PR_ID\"))\n\n(defmethod pull-request-base-branch ((self bitbucket-pipeline-env-reader))\n  (getenv self \"BITBUCKET_PR_DESTINATION_BRANCH\"))\n\n(defmethod sha1 ((self bitbucket-pipeline-env-reader))\n  (getenv self \"BITBUCKET_COMMIT\"))\n\n(defmethod build-url ((self bitbucket-pipeline-env-reader))\n  (format nil \"~a/addon/pipelines/home#!/results/~a\"\n          (getenv self \"BITBUCKET_GIT_HTTP_ORIGIN\")\n          (getenv self \"BITBUCKET_BUILD_NUMBER\")))\n\n(defmethod repo-url ((self bitbucket-pipeline-env-reader))\n  (getenv self \"BITBUCKET_GIT_HTTP_ORIGIN\"))\n\n(defmethod work-branch ((self bitbucket-pipeline-env-reader))\n  (getenv self \"BITBUCKET_BRANCH\"))\n\n(defclass gitlab-ci-env-reader (base-env-reader)\n  ())\n\n(defmethod validp ((self gitlab-ci-env-reader))\n  (getenv self \"GITLAB_CI\"))\n\n(defmethod pull-request-url ((self gitlab-ci-env-reader))\n  (when-let ((url (getenv self \"CI_MERGE_REQUEST_PROJECT_URL\"))\n             (iid (getenv self \"CI_MERGE_REQUEST_IID\")))\n    (format nil \"~a/-/merge_requests/~a\"\n            url\n            iid)))\n\n(defmethod pull-request-base-branch ((self gitlab-ci-env-reader))\n  (getenv self \"CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME\"))\n\n(defmethod sha1 ((self gitlab-ci-env-reader))\n  (getenv self \"CI_COMMIT_SHA\"))\n\n(defmethod build-url ((self gitlab-ci-env-reader))\n  (getenv self \"CI_JOB_URL\"))\n\n(defmethod repo-url ((self gitlab-ci-env-reader))\n  (getenv self \"CI_PROJECT_URL\"))\n\n(defmethod work-branch ((self gitlab-ci-env-reader))\n  (getenv self \"CI_COMMIT_REF_NAME\"))\n\n(defclass github-actions-env-reader (base-env-reader)\n  ())\n\n(defmethod validp ((self github-actions-env-reader))\n  (getenv self \"GITHUB_ACTION\"))\n\n(defmethod pull-request-action-p ((self github-actions-env-reader))\n  (equal \"pull_request\" (getenv self \"GITHUB_EVENT_NAME\")))\n\n(defmethod pull-request-url ((self github-actions-env-reader))\n  (let ((ref (getenv self \"GITHUB_REF\")))\n   (when (pull-request-action-p self)\n     (multiple-value-bind (res parts)\n         (cl-ppcre:scan-to-strings\n          \"refs/pull/(.*)/merge\"\n          ref)\n       (cond\n         (res\n          (format nil \"~a/pull/~a\"\n                  (repo-url self)\n                  (elt parts 0)))\n         (t\n          (warn \"could not parse pull request id from ~a\" ref)\n          nil))))))\n\n(defmethod pull-request-base-branch ((self github-actions-env-reader))\n  (getenv self \"GITHUB_BASE_REF\"))\n\n(defmethod sha1 ((self github-actions-env-reader))\n  (let ((sha (getenv self \"GITHUB_SHA\")))\n    (cond\n      ((pull-request-action-p self)\n       (log:info \"Parsing commit request from GitHub Actions merge commit\")\n       (let* ((repo (make-instance 'git-repo))\n              (message (git-message repo)))\n         (multiple-value-bind (res parts)\n             (cl-ppcre:scan-to-strings\n              \".*Merge (.*) into .*\"\n              message)\n           (cond\n             (res\n              (elt parts 0))\n             (t\n              (warn \"Could not parse ~a\" message)\n              sha)))))\n      (t\n       sha))))\n\n(defmethod github-server-url ((self github-actions-env-reader))\n  (getenv self \"GITHUB_SERVER_URL\"))\n\n(defmethod github-repo ((self github-actions-env-reader))\n  (getenv self \"GITHUB_REPOSITORY\"))\n\n(defmethod build-url ((self github-actions-env-reader))\n  (format nil \"~a/~a/actions/runs/~a\"\n          (github-server-url self)\n          (github-repo self)\n          (getenv self \"GITHUB_RUN_ID\")))\n\n(defmethod repo-url ((self github-actions-env-reader))\n  (format nil\n          \"~a/~a\"\n          (github-server-url self)\n          (github-repo self)))\n\n(defmethod work-branch ((self github-actions-env-reader))\n  (getenv self \"GITHUB_HEAD_REF\"))\n\n(defclass teamcity-env-reader (base-env-reader)\n  ()\n  (:documentation \"https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html. But\nnot everything is documented here.\n\nSee also:\nhttps://phabricator.tdrhq.com/w/teamcity_environment_variables/ for\nsample output that we generated from a local instance of TeamCity\nrunning in Docker.\"))\n\n(defmethod validp ((self teamcity-env-reader))\n  (getenv self \"TEAMCITY_BUILD_PROPERTIES_FILE\"))\n\n(defun unescape-property-value (input)\n  (let ((output (make-string-output-stream))\n        (escaping nil))\n    (loop for ch across input\n          do\n             (cond\n               (escaping\n                (setf escaping nil)\n                (write-char ch output))\n               ((eql #\\\\ ch)\n                (setf escaping t))\n               (t\n                (write-char ch output))))\n    (get-output-stream-string output)))\n\n(defun read-java-property (file key)\n  (when file\n    (with-open-file (s file :direction :input)\n      (loop for line = (read-line s nil nil)\n            while line\n            do\n               (let ((line (str:trim line)))\n                 (cond\n                   ((str:starts-with-p \"#\" line)\n                    (values))\n                   (t\n                    (destructuring-bind (this-key &optional value)\n                        (str:split \"=\" line :limit 2)\n                      (when (and value\n                                 (string-equal (str:trim this-key) key))\n                        (return-from read-java-property\n                          (unescape-property-value (str:trim value))))))))))))\n\n(defmethod teamcity-build-prop ((self teamcity-env-reader) key)\n  (read-java-property\n   (getenv self \"TEAMCITY_BUILD_PROPERTIES_FILE\")\n   key))\n\n(defmethod teamcity-config-prop ((self teamcity-env-reader) key)\n  (read-java-property\n   (teamcity-build-prop self \"teamcity.configuration.properties.file\")\n   key))\n\n(defmethod pull-request-url ((self teamcity-env-reader))\n  (let ((pull-id (teamcity-build-prop self \"teamcity.pullRequest.number\")))\n    (unless (str:emptyp pull-id)\n     (link-to-github-pull-request\n      (repo-url self)\n      pull-id))))\n\n(defmethod pull-request-base-branch ((self teamcity-env-reader))\n  (teamcity-build-prop self \"teamcity.pullRequest.number\"))\n\n(defmethod sha1 ((self teamcity-env-reader))\n  (teamcity-build-prop self \"build.vcs.number\"))\n\n(defmethod build-url ((self teamcity-env-reader))\n  (getenv self \"BUILD_URL\"))\n\n(defmethod repo-url ((self teamcity-env-reader))\n  (let ((result (teamcity-config-prop self \"vcsroot.url\")))\n    (when (str:emptyp result)\n      (log:warn \"Could not find vcsroot.url, this might be because of multiple VCS roots. Pass the --repo-url to disambiguate\"))\n    result))\n\n(defmethod work-branch ((self teamcity-env-reader))\n  (teamcity-config-prop self \"teamcity.build.branch\"))\n\n(defclass xcode-cloud-env-reader (base-env-reader)\n  ()\n  (:documentation \"https://developer.apple.com/documentation/xcode/environment-variable-reference\"))\n\n(defmethod validp ((self xcode-cloud-env-reader))\n  (not (null (getenv self \"CI_XCODE_CLOUD\"))))\n\n(defmethod pull-request-url ((self xcode-cloud-env-reader))\n  (getenv self \"CI_PULL_REQUEST_HTML_URL\"))\n\n(defmethod pull-request-base-branch ((self xcode-cloud-env-reader))\n  (getenv self \"CI_PULL_REQUEST_TARGET_BRANCH\"))\n\n(defmethod sha1 ((self xcode-cloud-env-reader))\n  (getenv self \"CI_COMMIT\"))\n\n(defmethod build-url ((self xcode-cloud-env-reader))\n  (getenv self \"CI_BUILD_URL\"))\n\n(defmethod repo-url ((self xcode-cloud-env-reader))\n  ;; Hopefully we can figure this out from the git repo directly\n  nil)\n\n(defmethod work-branch ((self xcode-cloud-env-reader))\n  (getenv self \"CI_BRANCH\"))\n\n\n(defclass codemagic-env-reader (base-env-reader)\n  ()\n  (:documentation \"https://docs.codemagic.io/yaml-basic-configuration/environment-variables/\"))\n\n(defmethod validp ((self codemagic-env-reader))\n  (not (null (getenv self \"CM_BUILD_ID\"))))\n\n(defmethod pull-request-url ((self codemagic-env-reader))\n  ;; Codemagic doesn't provide a repo URL, so we use a synthetic URL.\n  ;; The server only parses pulls/<id> from it.\n  (when-let ((pull-id (getenv self \"CM_PULL_REQUEST_NUMBER\")))\n    (format nil \"https://codemagic.io/pull/~a\" pull-id)))\n\n(defmethod pull-request-base-branch ((self codemagic-env-reader))\n  (getenv self \"CM_PULL_REQUEST_DEST\"))\n\n(defmethod sha1 ((self codemagic-env-reader))\n  (getenv self \"CM_COMMIT\"))\n\n(defmethod build-url ((self codemagic-env-reader))\n  (when-let ((project-id (getenv self \"CM_PROJECT_ID\"))\n             (build-id (getenv self \"CM_BUILD_ID\")))\n    (format nil \"https://codemagic.io/app/~a/build/~a\"\n            project-id\n            build-id)))\n\n(defmethod repo-url ((self codemagic-env-reader))\n  ;; Codemagic only provides CM_REPO_SLUG (owner/repo), not a full URL\n  nil)\n\n(defmethod work-branch ((self codemagic-env-reader))\n  (getenv self \"CM_BRANCH\"))\n\n(defparameter *all-readers*\n  '(circleci-env-reader\n    bitrise-env-reader\n    netlify-env-reader\n    azure-env-reader\n    bitbucket-pipeline-env-reader\n    buildkite-env-reader\n    gitlab-ci-env-reader\n    github-actions-env-reader\n    teamcity-env-reader\n    xcode-cloud-env-reader\n    codemagic-env-reader))\n\n\n(defun make-env-reader (&key overrides)\n  (loop for option in *all-readers*\n        for env = (make-instance option)\n        if (validp env)\n          return env\n        finally\n           (return (make-instance 'env-reader))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 795,\n    \"height\": 7479,\n    \"children\": [\n      {\n        \"class\": \"com.facebook.litho.Column\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 795,\n        \"height\": 7479,\n        \"children\": [\n          {\n            \"class\": \"com.facebook.testing.screenshot.sample.ImageRow\",\n            \"left\": 63,\n            \"top\": 63,\n            \"width\": 669,\n            \"height\": 336,\n            \"children\": [\n              {\n                \"class\": \"com.facebook.litho.Row\",\n                \"left\": 63,\n                \"top\": 63,\n                \"width\": 669,\n                \"height\": 336,\n                \"children\": [\n                  {\n                    \"class\": \"com.facebook.litho.widget.Image\",\n                    \"left\": 63,\n                    \"top\": 63,\n                    \"width\": 168,\n                    \"height\": 168,\n                    \"children\": []\n                  },\n                  {\n                    \"class\": \"com.facebook.litho.widget.Image\",\n                    \"left\": 231,\n                    \"top\": 63,\n                    \"width\": 336,\n                    \"height\": 336,\n                    \"children\": []\n                  },\n                  {\n                    \"class\": \"com.facebook.litho.widget.Image\",\n                    \"left\": 567,\n                    \"top\": 63,\n                    \"width\": 84,\n                    \"height\": 84,\n                    \"children\": []\n                  }\n                ]\n              }\n            ]\n          },\n          {\n            \"class\": \"com.facebook.litho.widget.Text\",\n            \"left\": 63,\n            \"top\": 399,\n            \"width\": 669,\n            \"height\": 7017,\n            \"children\": []\n          }\n        ]\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": null\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 787,\n    \"height\": 336,\n    \"children\": [\n      {\n        \"class\": \"com.facebook.litho.Row\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 787,\n        \"height\": 336,\n        \"children\": [\n          {\n            \"class\": \"com.facebook.litho.widget.Image\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 168,\n            \"height\": 168,\n            \"children\": []\n          },\n          {\n            \"class\": \"com.facebook.litho.widget.Image\",\n            \"left\": 168,\n            \"top\": 0,\n            \"width\": 336,\n            \"height\": 336,\n            \"children\": []\n          },\n          {\n            \"class\": \"com.facebook.litho.widget.Image\",\n            \"left\": 504,\n            \"top\": 0,\n            \"width\": 84,\n            \"height\": 84,\n            \"children\": []\n          }\n        ]\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": null\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1794,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1794,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1794,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1794,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1794,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 63,\n                            \"width\": 1080,\n                            \"height\": 147,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 63,\n                                \"width\": 1080,\n                                \"height\": 147,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 42,\n                                    \"top\": 101,\n                                    \"width\": 882,\n                                    \"height\": 71\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 975,\n                                    \"top\": 63,\n                                    \"width\": 105,\n                                    \"height\": 147,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 975,\n                                        \"top\": 73,\n                                        \"width\": 105,\n                                        \"height\": 126\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 210,\n                            \"width\": 1080,\n                            \"height\": 1584,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 210,\n                                \"width\": 1080,\n                                \"height\": 1584\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 891,\n                            \"top\": 1605,\n                            \"width\": 147,\n                            \"height\": 147\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1794,\n        \"width\": 1080,\n        \"height\": 126\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 63\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 63,\n                              \"bottom\": 210\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 147\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 63,\n                                  \"bottom\": 210\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 882,\n                                      \"top\": 0,\n                                      \"bottom\": 71\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 42,\n                                      \"right\": 924,\n                                      \"top\": 101,\n                                      \"bottom\": 172\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 105,\n                                      \"top\": 0,\n                                      \"bottom\": 147\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 975,\n                                      \"right\": 1080,\n                                      \"top\": 63,\n                                      \"bottom\": 210\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          16908356,\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 105,\n                                          \"top\": 0,\n                                          \"bottom\": 126\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 975,\n                                          \"right\": 1080,\n                                          \"top\": 73,\n                                          \"bottom\": 199\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"More options\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1584\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 210,\n                              \"bottom\": 1794\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1584\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 210,\n                                  \"bottom\": 1794\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is ERROR\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 147,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 891,\n                              \"right\": 1038,\n                              \"top\": 1605,\n                              \"bottom\": 1752\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 126\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1794,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 42,\n            \"top\": 38,\n            \"width\": 882,\n            \"height\": 71\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 10,\n            \"width\": 105,\n            \"height\": 126\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1584\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 891,\n            \"top\": 1605,\n            \"width\": 147,\n            \"height\": 147\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1794,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1794,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1794,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1794,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1794,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 63,\n                            \"width\": 1080,\n                            \"height\": 147,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 63,\n                                \"width\": 1080,\n                                \"height\": 147,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 42,\n                                    \"top\": 101,\n                                    \"width\": 882,\n                                    \"height\": 71\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 975,\n                                    \"top\": 63,\n                                    \"width\": 105,\n                                    \"height\": 147,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 975,\n                                        \"top\": 73,\n                                        \"width\": 105,\n                                        \"height\": 126\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 210,\n                            \"width\": 1080,\n                            \"height\": 1584,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 210,\n                                \"width\": 1080,\n                                \"height\": 1584\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 891,\n                            \"top\": 1480,\n                            \"width\": 147,\n                            \"height\": 147\n                          },\n                          {\n                            \"class\": \"com.google.android.material.snackbar.Snackbar.SnackbarLayout\",\n                            \"left\": 0,\n                            \"top\": 1669,\n                            \"width\": 1080,\n                            \"height\": 125,\n                            \"children\": [\n                              {\n                                \"class\": \"com.google.android.material.snackbar.SnackbarContentLayout\",\n                                \"left\": 32,\n                                \"top\": 1669,\n                                \"width\": 1016,\n                                \"height\": 125,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 32,\n                                    \"top\": 1669,\n                                    \"width\": 1016,\n                                    \"height\": 125\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatButton\",\n                                    \"left\": 32,\n                                    \"top\": 1669,\n                                    \"width\": 0,\n                                    \"height\": 0\n                                  }\n                                ]\n                              }\n                            ]\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1794,\n        \"width\": 1080,\n        \"height\": 126\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 63\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 5,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 63,\n                              \"bottom\": 210\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 147\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 63,\n                                  \"bottom\": 210\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 882,\n                                      \"top\": 0,\n                                      \"bottom\": 71\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 42,\n                                      \"right\": 924,\n                                      \"top\": 101,\n                                      \"bottom\": 172\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 105,\n                                      \"top\": 0,\n                                      \"bottom\": 147\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 975,\n                                      \"right\": 1080,\n                                      \"top\": 63,\n                                      \"bottom\": 210\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          16908356,\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 105,\n                                          \"top\": 0,\n                                          \"bottom\": 126\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 975,\n                                          \"right\": 1080,\n                                          \"top\": 73,\n                                          \"bottom\": 199\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"More options\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1584\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 210,\n                              \"bottom\": 1794\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1584\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 210,\n                                  \"bottom\": 1794\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is OK\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              1,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 147,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 891,\n                              \"right\": 1038,\n                              \"top\": 1480,\n                              \"bottom\": 1627\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          },\n                          {\n                            \"class\": \"com.google.android.material.snackbar.Snackbar$SnackbarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              1,\n                              64,\n                              16908342,\n                              1048576\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 125\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 1669,\n                              \"bottom\": 1794\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.FrameLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": true,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 1,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"com.google.android.material.snackbar.SnackbarContentLayout\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1016,\n                                  \"top\": 0,\n                                  \"bottom\": 125\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 32,\n                                  \"right\": 1048,\n                                  \"top\": 1669,\n                                  \"bottom\": 1794\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 1,\n                                \"className\": \"android.widget.LinearLayout\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 1016,\n                                      \"top\": 0,\n                                      \"bottom\": 125\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 32,\n                                      \"right\": 1048,\n                                      \"top\": 1669,\n                                      \"bottom\": 1794\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": true,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"This is a snackbar\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatButton\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      1,\n                                      64,\n                                      16,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 0,\n                                      \"top\": 0,\n                                      \"bottom\": 0\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 32,\n                                      \"right\": 32,\n                                      \"top\": 1669,\n                                      \"bottom\": 1669\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.Button\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": true,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": true,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": true,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": false,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": null\n                                  }\n                                ]\n                              }\n                            ]\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 126\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1794,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 42,\n            \"top\": 38,\n            \"width\": 882,\n            \"height\": 71\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 10,\n            \"width\": 105,\n            \"height\": 126\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1584\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 891,\n            \"top\": 1480,\n            \"width\": 147,\n            \"height\": 147\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1794,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1794,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1794,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1794,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1794,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 63,\n                            \"width\": 1080,\n                            \"height\": 147,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 63,\n                                \"width\": 1080,\n                                \"height\": 147,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 42,\n                                    \"top\": 101,\n                                    \"width\": 882,\n                                    \"height\": 71\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 975,\n                                    \"top\": 63,\n                                    \"width\": 105,\n                                    \"height\": 147,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 975,\n                                        \"top\": 73,\n                                        \"width\": 105,\n                                        \"height\": 126\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 210,\n                            \"width\": 1080,\n                            \"height\": 1584,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 210,\n                                \"width\": 1080,\n                                \"height\": 1584\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 891,\n                            \"top\": 1605,\n                            \"width\": 147,\n                            \"height\": 147\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1794,\n        \"width\": 1080,\n        \"height\": 126\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 63\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 63,\n                              \"bottom\": 210\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 147\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 63,\n                                  \"bottom\": 210\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 882,\n                                      \"top\": 0,\n                                      \"bottom\": 71\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 42,\n                                      \"right\": 924,\n                                      \"top\": 101,\n                                      \"bottom\": 172\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 105,\n                                      \"top\": 0,\n                                      \"bottom\": 147\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 975,\n                                      \"right\": 1080,\n                                      \"top\": 63,\n                                      \"bottom\": 210\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          16908356,\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 105,\n                                          \"top\": 0,\n                                          \"bottom\": 126\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 975,\n                                          \"right\": 1080,\n                                          \"top\": 73,\n                                          \"bottom\": 199\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"More options\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1584\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 210,\n                              \"bottom\": 1794\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1584\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 210,\n                                  \"bottom\": 1794\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is OK\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 147,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 891,\n                              \"right\": 1038,\n                              \"top\": 1605,\n                              \"bottom\": 1752\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 126\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1794,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 42,\n            \"top\": 38,\n            \"width\": 882,\n            \"height\": 71\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 10,\n            \"width\": 105,\n            \"height\": 126\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1584\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 891,\n            \"top\": 1605,\n            \"width\": 147,\n            \"height\": 147\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1794,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1794,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1794,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1794,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1794,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 63,\n                            \"width\": 1080,\n                            \"height\": 147,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 63,\n                                \"width\": 1080,\n                                \"height\": 147,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 42,\n                                    \"top\": 101,\n                                    \"width\": 882,\n                                    \"height\": 71\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 975,\n                                    \"top\": 63,\n                                    \"width\": 105,\n                                    \"height\": 147,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 975,\n                                        \"top\": 73,\n                                        \"width\": 105,\n                                        \"height\": 126\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 210,\n                            \"width\": 1080,\n                            \"height\": 1584,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 210,\n                                \"width\": 1080,\n                                \"height\": 1584\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 891,\n                            \"top\": 1605,\n                            \"width\": 147,\n                            \"height\": 147\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1794,\n        \"width\": 1080,\n        \"height\": 126\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 63\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {}\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1794,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1794,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1794,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1794,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1794,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 63,\n                            \"width\": 1080,\n                            \"height\": 147,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 63,\n                                \"width\": 1080,\n                                \"height\": 147,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 42,\n                                    \"top\": 101,\n                                    \"width\": 882,\n                                    \"height\": 71\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 975,\n                                    \"top\": 63,\n                                    \"width\": 105,\n                                    \"height\": 147,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 975,\n                                        \"top\": 73,\n                                        \"width\": 105,\n                                        \"height\": 126\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 210,\n                            \"width\": 1080,\n                            \"height\": 1584,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 210,\n                                \"width\": 1080,\n                                \"height\": 1584\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 891,\n                            \"top\": 1605,\n                            \"width\": 147,\n                            \"height\": 147\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1794,\n        \"width\": 1080,\n        \"height\": 126\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 63\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 63,\n                              \"bottom\": 210\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 147\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 63,\n                                  \"bottom\": 210\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 882,\n                                      \"top\": 0,\n                                      \"bottom\": 71\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 42,\n                                      \"right\": 924,\n                                      \"top\": 101,\n                                      \"bottom\": 172\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 105,\n                                      \"top\": 0,\n                                      \"bottom\": 147\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 975,\n                                      \"right\": 1080,\n                                      \"top\": 63,\n                                      \"bottom\": 210\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          16908356,\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 105,\n                                          \"top\": 0,\n                                          \"bottom\": 126\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 975,\n                                          \"right\": 1080,\n                                          \"top\": 73,\n                                          \"bottom\": 199\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"More options\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1584\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 210,\n                              \"bottom\": 1794\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1584\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 210,\n                                  \"bottom\": 1794\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is OK\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 147,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 891,\n                              \"right\": 1038,\n                              \"top\": 1605,\n                              \"bottom\": 1752\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 126\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1794,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 42,\n            \"top\": 38,\n            \"width\": 882,\n            \"height\": 71\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 10,\n            \"width\": 105,\n            \"height\": 126\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1584\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 891,\n            \"top\": 1605,\n            \"width\": 147,\n            \"height\": 147\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1794,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1794,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1794,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1794,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1794,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 63,\n                            \"width\": 1080,\n                            \"height\": 147,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 63,\n                                \"width\": 1080,\n                                \"height\": 147,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 42,\n                                    \"top\": 101,\n                                    \"width\": 882,\n                                    \"height\": 71\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 975,\n                                    \"top\": 63,\n                                    \"width\": 105,\n                                    \"height\": 147,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 975,\n                                        \"top\": 73,\n                                        \"width\": 105,\n                                        \"height\": 126\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 210,\n                            \"width\": 1080,\n                            \"height\": 1584,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 210,\n                                \"width\": 1080,\n                                \"height\": 1584\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 891,\n                            \"top\": 1605,\n                            \"width\": 147,\n                            \"height\": 147\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1794,\n        \"width\": 1080,\n        \"height\": 126\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 63\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1794\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1794\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1794\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1794\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1794\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 63,\n                              \"bottom\": 210\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 147\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 63,\n                                  \"bottom\": 210\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 882,\n                                      \"top\": 0,\n                                      \"bottom\": 71\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 42,\n                                      \"right\": 924,\n                                      \"top\": 101,\n                                      \"bottom\": 172\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 105,\n                                      \"top\": 0,\n                                      \"bottom\": 147\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 975,\n                                      \"right\": 1080,\n                                      \"top\": 63,\n                                      \"bottom\": 210\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          16908356,\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 105,\n                                          \"top\": 0,\n                                          \"bottom\": 126\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 975,\n                                          \"right\": 1080,\n                                          \"top\": 73,\n                                          \"bottom\": 199\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"More options\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1584\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 210,\n                              \"bottom\": 1794\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1584\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 210,\n                                  \"bottom\": 1794\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is WARNING\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 147,\n                              \"top\": 0,\n                              \"bottom\": 147\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 891,\n                              \"right\": 1038,\n                              \"top\": 1605,\n                              \"bottom\": 1752\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 126\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1794,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 63\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 42,\n            \"top\": 38,\n            \"width\": 882,\n            \"height\": 71\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 10,\n            \"width\": 105,\n            \"height\": 126\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1584\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 891,\n            \"top\": 1605,\n            \"width\": 147,\n            \"height\": 147\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 787,\n    \"height\": 126,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"left\": 0,\n        \"top\": 1,\n        \"width\": 556,\n        \"height\": 118\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"left\": 556,\n        \"top\": 0,\n        \"width\": 231,\n        \"height\": 126\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"actionList\": [\n          2097152\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 1,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": true,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": \"搜索世界\",\n        \"children\": null\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"actionList\": [\n          256,\n          512,\n          131072\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": true,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"搜\",\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 787,\n    \"height\": 126,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"left\": 0,\n        \"top\": 1,\n        \"width\": 373,\n        \"height\": 118\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"left\": 373,\n        \"top\": 0,\n        \"width\": 414,\n        \"height\": 126\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"actionList\": [\n          256,\n          512,\n          131072,\n          2097152\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 1,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": true,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"This is a really long text and should overflow\",\n        \"children\": null\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"actionList\": [\n          256,\n          512,\n          131072\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": true,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"Search!!!!!!!!!....%%%%\",\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 787,\n    \"height\": 126,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"left\": 0,\n        \"top\": 1,\n        \"width\": 373,\n        \"height\": 118\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"left\": 373,\n        \"top\": 0,\n        \"width\": 414,\n        \"height\": 126\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"actionList\": [\n          2097152\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 1,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": true,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": \"Search the world!\",\n        \"children\": null\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"actionList\": [\n          256,\n          512,\n          131072\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": true,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"Search!!!!!!!!!....%%%%\",\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/default.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nbody {\n    background: #fafafa;\n    font-family: 'Arial';\n}\n\nimg {\n    display:block;\n}\n\n.flex-wrapper {\n    display: flex;\n    flex-direction: row;\n}\n\n.img-block {\n    border-right: 1px solid #aaaaaa;\n    margin-right: 8px;\n    margin-top: 10px;\n    padding-right: 8px;\n}\n\n.img-wrapper {\n    background-image: url(\"background.png\");\n    margin-top: 5px;\n    position: relative;\n}\n\n.img-wrapper.dark {\n    background-image: url(\"background_dark.png\");\n}\n\n.hierarchy-overlay {\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    display: none;\n}\n\n.hierarchy-node {\n    position: absolute;\n    display: block;\n    border: 2px solid #000;\n}\n\n.hierarchy-node.highlight {\n    background-color: rgba(20, 20, 128, 0.5);\n    border-color: #00f;\n}\n\n.command-wrapper {\n    margin-top: 16px;\n    flex-grow: 1;\n    overflow: auto;\n}\n\n.view-hierarchy {\n    background: #ddd;\n    padding: 16px;\n    max-height: 750px;\n    overflow: auto;\n    border-radius: 3px;\n}\n\ndiv.screenshot {\n    padding: 10px;\n}\n\ndiv.alternate {\n\n}\n\ndiv.screenshot_error {\n    color: red;\n}\n\ntable {\n    border-collapse: collapse;\n}\n\ntable, th, tr, td, img{\n    padding: 0;\n    margin: 0;\n    border: 0;\n}\n\ntable {\n    border-spacing: 0;\n    border-collapse: collapse;\n}\n\n.screenshot_name {\n    font-size: 24px;\n}\n\n.screenshot_name .demphasize {\n    color: #999;\n}\n\n.screenshot_description {\n    color: #999;\n    font-style: italic;\n}\n\nbutton {\n    border: 1px solid #000;\n    border-radius: 3px;\n    background-color: #fff;\n    padding: 16px;\n    font-size: 18px;\n    margin-right: 8px;\n}\n\nbutton:hover {\n    background-color: #000;\n    color: #fff;\n    cursor: pointer;\n}\n\n.clearfix {\n    display: block;\n    clear: both;\n    content: \"\";\n}\n\nhr {\n    border: 0;\n    height: 1px;\n    background-color: #000;\n    margin: 16px 0;\n}\n\nh3 {\n    font-weight: normal;\n}\n\ndetails {\n    margin-left: 24px;\n}\n\nsummary {\n    font-size: 16px;\n}\n\nul {\n    list-style: none;\n    margin: 0;\n    margin-bottom: 8px;\n}\n"
  },
  {
    "path": "src/screenshotbot/sdk/example/default.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n$(function () {\n    $(\".extra\").click(function () {\n            var str = $(this).attr('data');\n            $('<pre></pre>').dialog({\n                modal: true,\n                title: \"Extra Info\",\n                open: function () {\n                    $(this).html(str);\n                },\n                buttons: {\n                    Ok: function () {\n                        $(this).dialog(\"close\");\n                    }\n                },\n                width:'1000px',\n                position: {\n                    at: 'top',\n                },\n            });\n        });\n\n    $(\".toggle_dark\").click(function() {\n        $(this).closest(\".screenshot\").find(\".img-wrapper\").toggleClass(\"dark\");\n    })\n\n    $(\".toggle_hierarchy\").click(function() {\n        $(this).closest(\".screenshot\").find(\".hierarchy-overlay\").toggle();\n    })\n\n    $(\".view-hierarchy\")\n        .mousemove(\n            function(e) {\n                $(\".hierarchy-node\").removeClass('highlight');\n                $($(e.target).closest(\"details\").attr('target')).addClass('highlight');\n            })\n        .mouseout(\n            function() {\n                $(\".hierarchy-node\").removeClass('highlight');\n            });\n});\n"
  },
  {
    "path": "src/screenshotbot/sdk/example/fab_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 147,\n    \"height\": 147\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n    \"actionList\": [\n      4,\n      8,\n      2,\n      64,\n      16,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 147,\n      \"top\": 0,\n      \"bottom\": 147\n    },\n    \"boundsInScreen\": {\n      \"left\": 891,\n      \"right\": 1038,\n      \"top\": 1605,\n      \"bottom\": 1752\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"className\": \"android.widget.ImageButton\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": true,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": true,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": null\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/fab_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 891,\n            \"top\": 1605,\n            \"width\": 147,\n            \"height\": 147\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example/index.html",
    "content": "<!DOCTYPE html><html><head><title>Screenshot Test Results</title><script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js\"></script><script src=\"https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js\"></script><script src=\"default.js\"></script><link rel=\"stylesheet\" href=\"https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css\" /><link rel=\"stylesheet\" href=\"default.css\"></head><body><div class=\"screenshot alternate\"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>ExampleScreenshotTest_testDefault</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_3.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_4.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_4.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_5.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_5.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_6.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_6.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_7.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_7.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_8.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_8.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_9.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_9.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_10.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_10.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_11.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_11.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_12.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_12.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_13.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_13.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_14.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_14.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:791px;height:7475px;\"\n          id=\"1-node-com-facebook-litho-LithoView-0-0-795-7479\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:791px;height:7475px;\"\n          id=\"1-node-com-facebook-litho-Column-0-0-795-7479\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:63px;top:63px;width:665px;height:332px;\"\n          id=\"1-node-com-facebook-testing-screenshot-sample-ImageRow-63-63-669-336\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:63px;top:399px;width:665px;height:7013px;\"\n          id=\"1-node-com-facebook-litho-widget-Text-63-399-669-7017\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:63px;top:63px;width:665px;height:332px;\"\n          id=\"1-node-com-facebook-litho-Row-63-63-669-336\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:63px;top:63px;width:164px;height:164px;\"\n          id=\"1-node-com-facebook-litho-widget-Image-63-63-168-168\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:231px;top:63px;width:332px;height:332px;\"\n          id=\"1-node-com-facebook-litho-widget-Image-231-63-336-336\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:567px;top:63px;width:80px;height:80px;\"\n          id=\"1-node-com-facebook-litho-widget-Image-567-63-84-84\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#1-node-com-facebook-litho-LithoView-0-0-795-7479\"><summary>com.facebook.litho.LithoView</summary><ul><li><strong>height:</strong> 7479</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 795</li></ul><details target=\"#1-node-com-facebook-litho-Column-0-0-795-7479\"><summary>com.facebook.litho.Column</summary><ul><li><strong>height:</strong> 7479</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 795</li></ul><details target=\"#1-node-com-facebook-testing-screenshot-sample-ImageRow-63-63-669-336\"><summary>com.facebook.testing.screenshot.sample.ImageRow</summary><ul><li><strong>height:</strong> 336</li><li><strong>left:</strong> 63</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 669</li></ul><details target=\"#1-node-com-facebook-litho-Row-63-63-669-336\"><summary>com.facebook.litho.Row</summary><ul><li><strong>height:</strong> 336</li><li><strong>left:</strong> 63</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 669</li></ul><details target=\"#1-node-com-facebook-litho-widget-Image-63-63-168-168\"><summary>com.facebook.litho.widget.Image</summary><ul><li><strong>height:</strong> 168</li><li><strong>left:</strong> 63</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 168</li></ul></details><details target=\"#1-node-com-facebook-litho-widget-Image-231-63-336-336\"><summary>com.facebook.litho.widget.Image</summary><ul><li><strong>height:</strong> 336</li><li><strong>left:</strong> 231</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 336</li></ul></details><details target=\"#1-node-com-facebook-litho-widget-Image-567-63-84-84\"><summary>com.facebook.litho.widget.Image</summary><ul><li><strong>height:</strong> 84</li><li><strong>left:</strong> 567</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 84</li></ul></details></details></details><details target=\"#1-node-com-facebook-litho-widget-Text-63-399-669-7017\"><summary>com.facebook.litho.widget.Text</summary><ul><li><strong>height:</strong> 7017</li><li><strong>left:</strong> 63</li><li><strong>top:</strong> 399</li><li><strong>width:</strong> 669</li></ul></details></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.facebook.litho.LithoView</summary><ul><li><strong>actionList:</strong> None</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot \"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>ImageRowScreenshotTest_testDefault2</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_1_0.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:783px;height:332px;\"\n          id=\"2-node-com-facebook-litho-LithoView-0-0-787-336\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:783px;height:332px;\"\n          id=\"2-node-com-facebook-litho-Row-0-0-787-336\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:164px;height:164px;\"\n          id=\"2-node-com-facebook-litho-widget-Image-0-0-168-168\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:168px;top:0px;width:332px;height:332px;\"\n          id=\"2-node-com-facebook-litho-widget-Image-168-0-336-336\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:504px;top:0px;width:80px;height:80px;\"\n          id=\"2-node-com-facebook-litho-widget-Image-504-0-84-84\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#2-node-com-facebook-litho-LithoView-0-0-787-336\"><summary>com.facebook.litho.LithoView</summary><ul><li><strong>height:</strong> 336</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 787</li></ul><details target=\"#2-node-com-facebook-litho-Row-0-0-787-336\"><summary>com.facebook.litho.Row</summary><ul><li><strong>height:</strong> 336</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 787</li></ul><details target=\"#2-node-com-facebook-litho-widget-Image-0-0-168-168\"><summary>com.facebook.litho.widget.Image</summary><ul><li><strong>height:</strong> 168</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 168</li></ul></details><details target=\"#2-node-com-facebook-litho-widget-Image-168-0-336-336\"><summary>com.facebook.litho.widget.Image</summary><ul><li><strong>height:</strong> 336</li><li><strong>left:</strong> 168</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 336</li></ul></details><details target=\"#2-node-com-facebook-litho-widget-Image-504-0-84-84\"><summary>com.facebook.litho.widget.Image</summary><ul><li><strong>height:</strong> 84</li><li><strong>left:</strong> 504</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 84</li></ul></details></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.facebook.litho.LithoView</summary><ul><li><strong>actionList:</strong> None</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot alternate\"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>MainActivityTest_errorTextShouldBeRed</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1916px;\"\n          id=\"3-node-com-android-internal-policy-DecorView-0-0-1080-1920\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"3-node-android-widget-LinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1794px;width:1076px;height:122px;\"\n          id=\"3-node-android-view-View-0-1794-1080-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:59px;\"\n          id=\"3-node-android-view-View-0-0-1080-63\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"3-node-android-view-ViewStub-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"3-node-android-widget-FrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"3-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"3-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"3-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"3-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"3-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"3-node-android-widget-LinearLayout-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:891px;top:1605px;width:143px;height:143px;\"\n          id=\"3-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"3-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"3-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:42px;top:101px;width:878px;height:67px;\"\n          id=\"3-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:63px;width:101px;height:143px;\"\n          id=\"3-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:73px;width:101px;height:122px;\"\n          id=\"3-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#3-node-com-android-internal-policy-DecorView-0-0-1080-1920\"><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>height:</strong> 1920</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-android-widget-LinearLayout-0-0-1080-1794\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-android-view-ViewStub-0-0-0-0\"><summary>android.view.ViewStub</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#3-node-android-widget-FrameLayout-0-0-1080-1794\"><summary>android.widget.FrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#3-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 71</li><li><strong>left:</strong> 42</li><li><strong>top:</strong> 101</li><li><strong>width:</strong> 882</li></ul></details><details target=\"#3-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 105</li></ul><details target=\"#3-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"><summary>androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 73</li><li><strong>width:</strong> 105</li></ul></details></details></details></details><details target=\"#3-node-android-widget-LinearLayout-0-210-1080-1584\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul><details target=\"#3-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul></details></details><details target=\"#3-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 891</li><li><strong>top:</strong> 1605</li><li><strong>width:</strong> 147</li></ul></details></details></details></details></details></details><details target=\"#3-node-android-view-View-0-1794-1080-126\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1794</li><li><strong>width:</strong> 1080</li></ul></details><details target=\"#3-node-android-view-View-0-0-1080-63\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 63</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.view.ViewStub</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.widget.FrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 4</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 882, u'bottom': 71, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 101, u'right': 924, u'bottom': 172, u'left': 42}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Screenshot Tests for Android Sample</li></ul></details><details><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> androidx.appcompat.widget.LinearLayoutCompat</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton</summary><ul><li><strong>actionList:</strong> [16908356, 4, 8, 1, 64, 16, 32, 131072, 256, 512, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 73, u'right': 1080, u'bottom': 199, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> More options</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> True</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 11</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Status is ERROR</li></ul></details></details><details><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>actionList:</strong> [4, 8, 2, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 147, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1605, u'right': 1038, u'bottom': 1752, u'left': 891}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageButton</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details></details></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1794, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot \"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>MainActivityTest_mainActivityTestSettingsOpen</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1916px;\"\n          id=\"4-node-com-android-internal-policy-DecorView-0-0-1080-1920\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"4-node-android-widget-LinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1794px;width:1076px;height:122px;\"\n          id=\"4-node-android-view-View-0-1794-1080-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:59px;\"\n          id=\"4-node-android-view-View-0-0-1080-63\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"4-node-android-view-ViewStub-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"4-node-android-widget-FrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"4-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"4-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"4-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"4-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"4-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"4-node-android-widget-LinearLayout-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:891px;top:1480px;width:143px;height:143px;\"\n          id=\"4-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1480-147-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1669px;width:1076px;height:121px;\"\n          id=\"4-node-com-google-android-material-snackbar-Snackbar-SnackbarLayout-0-1669-1080-125\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"4-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"4-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:32px;top:1669px;width:1012px;height:121px;\"\n          id=\"4-node-com-google-android-material-snackbar-SnackbarContentLayout-32-1669-1016-125\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:42px;top:101px;width:878px;height:67px;\"\n          id=\"4-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:63px;width:101px;height:143px;\"\n          id=\"4-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:32px;top:1669px;width:1012px;height:121px;\"\n          id=\"4-node-androidx-appcompat-widget-AppCompatTextView-32-1669-1016-125\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:32px;top:1669px;width:-4px;height:-4px;\"\n          id=\"4-node-androidx-appcompat-widget-AppCompatButton-32-1669-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:73px;width:101px;height:122px;\"\n          id=\"4-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#4-node-com-android-internal-policy-DecorView-0-0-1080-1920\"><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>height:</strong> 1920</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-android-widget-LinearLayout-0-0-1080-1794\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-android-view-ViewStub-0-0-0-0\"><summary>android.view.ViewStub</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#4-node-android-widget-FrameLayout-0-0-1080-1794\"><summary>android.widget.FrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#4-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 71</li><li><strong>left:</strong> 42</li><li><strong>top:</strong> 101</li><li><strong>width:</strong> 882</li></ul></details><details target=\"#4-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 105</li></ul><details target=\"#4-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"><summary>androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 73</li><li><strong>width:</strong> 105</li></ul></details></details></details></details><details target=\"#4-node-android-widget-LinearLayout-0-210-1080-1584\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul></details></details><details target=\"#4-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1480-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 891</li><li><strong>top:</strong> 1480</li><li><strong>width:</strong> 147</li></ul></details><details target=\"#4-node-com-google-android-material-snackbar-Snackbar-SnackbarLayout-0-1669-1080-125\"><summary>com.google.android.material.snackbar.Snackbar.SnackbarLayout</summary><ul><li><strong>height:</strong> 125</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1669</li><li><strong>width:</strong> 1080</li></ul><details target=\"#4-node-com-google-android-material-snackbar-SnackbarContentLayout-32-1669-1016-125\"><summary>com.google.android.material.snackbar.SnackbarContentLayout</summary><ul><li><strong>height:</strong> 125</li><li><strong>left:</strong> 32</li><li><strong>top:</strong> 1669</li><li><strong>width:</strong> 1016</li></ul><details target=\"#4-node-androidx-appcompat-widget-AppCompatTextView-32-1669-1016-125\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 125</li><li><strong>left:</strong> 32</li><li><strong>top:</strong> 1669</li><li><strong>width:</strong> 1016</li></ul></details><details target=\"#4-node-androidx-appcompat-widget-AppCompatButton-32-1669-0-0\"><summary>androidx.appcompat.widget.AppCompatButton</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 32</li><li><strong>top:</strong> 1669</li><li><strong>width:</strong> 0</li></ul></details></details></details></details></details></details></details></details><details target=\"#4-node-android-view-View-0-1794-1080-126\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1794</li><li><strong>width:</strong> 1080</li></ul></details><details target=\"#4-node-android-view-View-0-0-1080-63\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 63</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.view.ViewStub</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.widget.FrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 5</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 882, u'bottom': 71, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 101, u'right': 924, u'bottom': 172, u'left': 42}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Screenshot Tests for Android Sample</li></ul></details><details><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> androidx.appcompat.widget.LinearLayoutCompat</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton</summary><ul><li><strong>actionList:</strong> [16908356, 4, 8, 1, 64, 16, 32, 131072, 256, 512, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 73, u'right': 1080, u'bottom': 199, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> More options</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> True</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 11</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Status is OK</li></ul></details></details><details><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>actionList:</strong> [4, 8, 1, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 147, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1480, u'right': 1038, u'bottom': 1627, u'left': 891}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageButton</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>com.google.android.material.snackbar.Snackbar$SnackbarLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 1, 64, 16908342, 1048576]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 125, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1669, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> True</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 1</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>com.google.android.material.snackbar.SnackbarContentLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1016, u'bottom': 125, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1669, u'right': 1048, u'bottom': 1794, u'left': 32}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1016, u'bottom': 125, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1669, u'right': 1048, u'bottom': 1794, u'left': 32}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> This is a snackbar</li></ul></details><details><summary>androidx.appcompat.widget.AppCompatButton</summary><ul><li><strong>actionList:</strong> [4, 8, 1, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1669, u'right': 32, u'bottom': 1669, u'left': 32}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.Button</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details></details></details></details></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1794, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot alternate\"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>MainActivityTest_okTextShouldBeGreen</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1916px;\"\n          id=\"5-node-com-android-internal-policy-DecorView-0-0-1080-1920\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"5-node-android-widget-LinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1794px;width:1076px;height:122px;\"\n          id=\"5-node-android-view-View-0-1794-1080-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:59px;\"\n          id=\"5-node-android-view-View-0-0-1080-63\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"5-node-android-view-ViewStub-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"5-node-android-widget-FrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"5-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"5-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"5-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"5-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"5-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"5-node-android-widget-LinearLayout-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:891px;top:1605px;width:143px;height:143px;\"\n          id=\"5-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"5-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"5-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:42px;top:101px;width:878px;height:67px;\"\n          id=\"5-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:63px;width:101px;height:143px;\"\n          id=\"5-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:73px;width:101px;height:122px;\"\n          id=\"5-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#5-node-com-android-internal-policy-DecorView-0-0-1080-1920\"><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>height:</strong> 1920</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-android-widget-LinearLayout-0-0-1080-1794\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-android-view-ViewStub-0-0-0-0\"><summary>android.view.ViewStub</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#5-node-android-widget-FrameLayout-0-0-1080-1794\"><summary>android.widget.FrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#5-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 71</li><li><strong>left:</strong> 42</li><li><strong>top:</strong> 101</li><li><strong>width:</strong> 882</li></ul></details><details target=\"#5-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 105</li></ul><details target=\"#5-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"><summary>androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 73</li><li><strong>width:</strong> 105</li></ul></details></details></details></details><details target=\"#5-node-android-widget-LinearLayout-0-210-1080-1584\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul><details target=\"#5-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul></details></details><details target=\"#5-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 891</li><li><strong>top:</strong> 1605</li><li><strong>width:</strong> 147</li></ul></details></details></details></details></details></details><details target=\"#5-node-android-view-View-0-1794-1080-126\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1794</li><li><strong>width:</strong> 1080</li></ul></details><details target=\"#5-node-android-view-View-0-0-1080-63\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 63</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.view.ViewStub</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.widget.FrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 4</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 882, u'bottom': 71, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 101, u'right': 924, u'bottom': 172, u'left': 42}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Screenshot Tests for Android Sample</li></ul></details><details><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> androidx.appcompat.widget.LinearLayoutCompat</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton</summary><ul><li><strong>actionList:</strong> [16908356, 4, 8, 1, 64, 16, 32, 131072, 256, 512, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 73, u'right': 1080, u'bottom': 199, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> More options</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> True</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 11</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Status is OK</li></ul></details></details><details><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>actionList:</strong> [4, 8, 2, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 147, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1605, u'right': 1038, u'bottom': 1752, u'left': 891}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageButton</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details></details></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1794, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot \"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>MainActivityTest_testScreenshotEntireActivity</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1916px;\"\n          id=\"6-node-com-android-internal-policy-DecorView-0-0-1080-1920\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"6-node-android-widget-LinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1794px;width:1076px;height:122px;\"\n          id=\"6-node-android-view-View-0-1794-1080-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:59px;\"\n          id=\"6-node-android-view-View-0-0-1080-63\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"6-node-android-view-ViewStub-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"6-node-android-widget-FrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"6-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"6-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"6-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"6-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"6-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"6-node-android-widget-LinearLayout-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:891px;top:1605px;width:143px;height:143px;\"\n          id=\"6-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"6-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"6-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:42px;top:101px;width:878px;height:67px;\"\n          id=\"6-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:63px;width:101px;height:143px;\"\n          id=\"6-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:73px;width:101px;height:122px;\"\n          id=\"6-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#6-node-com-android-internal-policy-DecorView-0-0-1080-1920\"><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>height:</strong> 1920</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-android-widget-LinearLayout-0-0-1080-1794\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-android-view-ViewStub-0-0-0-0\"><summary>android.view.ViewStub</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#6-node-android-widget-FrameLayout-0-0-1080-1794\"><summary>android.widget.FrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#6-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 71</li><li><strong>left:</strong> 42</li><li><strong>top:</strong> 101</li><li><strong>width:</strong> 882</li></ul></details><details target=\"#6-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 105</li></ul><details target=\"#6-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"><summary>androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 73</li><li><strong>width:</strong> 105</li></ul></details></details></details></details><details target=\"#6-node-android-widget-LinearLayout-0-210-1080-1584\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul><details target=\"#6-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul></details></details><details target=\"#6-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 891</li><li><strong>top:</strong> 1605</li><li><strong>width:</strong> 147</li></ul></details></details></details></details></details></details><details target=\"#6-node-android-view-View-0-1794-1080-126\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1794</li><li><strong>width:</strong> 1080</li></ul></details><details target=\"#6-node-android-view-View-0-0-1080-63\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 63</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.view.ViewStub</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.widget.FrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 4</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 882, u'bottom': 71, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 101, u'right': 924, u'bottom': 172, u'left': 42}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Screenshot Tests for Android Sample</li></ul></details><details><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> androidx.appcompat.widget.LinearLayoutCompat</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton</summary><ul><li><strong>actionList:</strong> [16908356, 4, 8, 1, 64, 16, 32, 131072, 256, 512, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 73, u'right': 1080, u'bottom': 199, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> More options</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> True</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 11</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Status is OK</li></ul></details></details><details><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>actionList:</strong> [4, 8, 2, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 147, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1605, u'right': 1038, u'bottom': 1752, u'left': 891}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageButton</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details></details></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1794, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot alternate\"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1916px;\"\n          id=\"7-node-com-android-internal-policy-DecorView-0-0-1080-1920\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"7-node-android-widget-LinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1794px;width:1076px;height:122px;\"\n          id=\"7-node-android-view-View-0-1794-1080-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:59px;\"\n          id=\"7-node-android-view-View-0-0-1080-63\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"7-node-android-view-ViewStub-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"7-node-android-widget-FrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"7-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"7-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"7-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"7-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"7-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"7-node-android-widget-LinearLayout-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:891px;top:1605px;width:143px;height:143px;\"\n          id=\"7-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"7-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"7-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:42px;top:101px;width:878px;height:67px;\"\n          id=\"7-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:63px;width:101px;height:143px;\"\n          id=\"7-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:73px;width:101px;height:122px;\"\n          id=\"7-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#7-node-com-android-internal-policy-DecorView-0-0-1080-1920\"><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>height:</strong> 1920</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-android-widget-LinearLayout-0-0-1080-1794\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-android-view-ViewStub-0-0-0-0\"><summary>android.view.ViewStub</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#7-node-android-widget-FrameLayout-0-0-1080-1794\"><summary>android.widget.FrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#7-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 71</li><li><strong>left:</strong> 42</li><li><strong>top:</strong> 101</li><li><strong>width:</strong> 882</li></ul></details><details target=\"#7-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 105</li></ul><details target=\"#7-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"><summary>androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 73</li><li><strong>width:</strong> 105</li></ul></details></details></details></details><details target=\"#7-node-android-widget-LinearLayout-0-210-1080-1584\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul><details target=\"#7-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul></details></details><details target=\"#7-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 891</li><li><strong>top:</strong> 1605</li><li><strong>width:</strong> 147</li></ul></details></details></details></details></details></details><details target=\"#7-node-android-view-View-0-1794-1080-126\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1794</li><li><strong>width:</strong> 1080</li></ul></details><details target=\"#7-node-android-view-View-0-0-1080-63\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 63</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot \"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>MainActivityTest_warningTextShouldBeYellow</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2.png\" /></td></tr><tr><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1916px;\"\n          id=\"8-node-com-android-internal-policy-DecorView-0-0-1080-1920\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"8-node-android-widget-LinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1794px;width:1076px;height:122px;\"\n          id=\"8-node-android-view-View-0-1794-1080-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:59px;\"\n          id=\"8-node-android-view-View-0-0-1080-63\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"8-node-android-view-ViewStub-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"8-node-android-widget-FrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"8-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:-4px;height:-4px;\"\n          id=\"8-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"8-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:1076px;height:1790px;\"\n          id=\"8-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"8-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"8-node-android-widget-LinearLayout-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:891px;top:1605px;width:143px;height:143px;\"\n          id=\"8-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:63px;width:1076px;height:143px;\"\n          id=\"8-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:210px;width:1076px;height:1580px;\"\n          id=\"8-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:42px;top:101px;width:878px;height:67px;\"\n          id=\"8-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:63px;width:101px;height:143px;\"\n          id=\"8-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:975px;top:73px;width:101px;height:122px;\"\n          id=\"8-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#8-node-com-android-internal-policy-DecorView-0-0-1080-1920\"><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>height:</strong> 1920</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-android-widget-LinearLayout-0-0-1080-1794\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-android-view-ViewStub-0-0-0-0\"><summary>android.view.ViewStub</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#8-node-android-widget-FrameLayout-0-0-1080-1794\"><summary>android.widget.FrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-androidx-appcompat-widget-FitWindowsLinearLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-androidx-appcompat-widget-ViewStubCompat-0-0-0-0\"><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>height:</strong> 0</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 0</li></ul></details><details target=\"#8-node-androidx-appcompat-widget-ContentFrameLayout-0-0-1080-1794\"><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-androidx-coordinatorlayout-widget-CoordinatorLayout-0-0-1080-1794\"><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>height:</strong> 1794</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-com-google-android-material-appbar-AppBarLayout-0-63-1080-147\"><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-androidx-appcompat-widget-Toolbar-0-63-1080-147\"><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-androidx-appcompat-widget-AppCompatTextView-42-101-882-71\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 71</li><li><strong>left:</strong> 42</li><li><strong>top:</strong> 101</li><li><strong>width:</strong> 882</li></ul></details><details target=\"#8-node-androidx-appcompat-widget-ActionMenuView-975-63-105-147\"><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 63</li><li><strong>width:</strong> 105</li></ul><details target=\"#8-node-androidx-appcompat-widget-ActionMenuPresenter-OverflowMenuButton-975-73-105-126\"><summary>androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 975</li><li><strong>top:</strong> 73</li><li><strong>width:</strong> 105</li></ul></details></details></details></details><details target=\"#8-node-android-widget-LinearLayout-0-210-1080-1584\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul><details target=\"#8-node-androidx-appcompat-widget-AppCompatTextView-0-210-1080-1584\"><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>height:</strong> 1584</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 210</li><li><strong>width:</strong> 1080</li></ul></details></details><details target=\"#8-node-com-google-android-material-floatingactionbutton-FloatingActionButton-891-1605-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 891</li><li><strong>top:</strong> 1605</li><li><strong>width:</strong> 147</li></ul></details></details></details></details></details></details><details target=\"#8-node-android-view-View-0-1794-1080-126\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1794</li><li><strong>width:</strong> 1080</li></ul></details><details target=\"#8-node-android-view-View-0-0-1080-63\"><summary>android.view.View</summary><ul><li><strong>height:</strong> 63</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 1080</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.android.internal.policy.DecorView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.view.ViewStub</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.widget.FrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.FitWindowsLinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ViewStubCompat</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>androidx.appcompat.widget.ContentFrameLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.FrameLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.coordinatorlayout.widget.CoordinatorLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 4</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>com.google.android.material.appbar.AppBarLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.Toolbar</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 2</li><li><strong>className:</strong> android.view.ViewGroup</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 882, u'bottom': 71, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 101, u'right': 924, u'bottom': 172, u'left': 42}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Screenshot Tests for Android Sample</li></ul></details><details><summary>androidx.appcompat.widget.ActionMenuView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 63, u'right': 1080, u'bottom': 210, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> androidx.appcompat.widget.LinearLayoutCompat</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton</summary><ul><li><strong>actionList:</strong> [16908356, 4, 8, 1, 64, 16, 32, 131072, 256, 512, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 105, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 73, u'right': 1080, u'bottom': 199, u'left': 975}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> More options</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> True</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 11</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 1</li><li><strong>className:</strong> android.widget.LinearLayout</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>androidx.appcompat.widget.AppCompatTextView</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342, 256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 1584, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 210, u'right': 1080, u'bottom': 1794, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.TextView</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Status is WARNING</li></ul></details></details><details><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>actionList:</strong> [4, 8, 2, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 147, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1605, u'right': 1038, u'bottom': 1752, u'left': 891}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageButton</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></details></details></details></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 126, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1794, u'right': 1080, u'bottom': 1920, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details><details><summary>android.view.View</summary><ul><li><strong>actionList:</strong> [4, 8, 64, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 1080, u'bottom': 63, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.view.View</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot alternate\"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>StandardAndroidViewTest_testChinese</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:783px;height:122px;\"\n          id=\"9-node-android-widget-LinearLayout-0-0-787-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1px;width:552px;height:114px;\"\n          id=\"9-node-android-widget-EditText-0-1-556-118\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:556px;top:0px;width:227px;height:122px;\"\n          id=\"9-node-android-widget-Button-556-0-231-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#9-node-android-widget-LinearLayout-0-0-787-126\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 787</li></ul><details target=\"#9-node-android-widget-EditText-0-1-556-118\"><summary>android.widget.EditText</summary><ul><li><strong>height:</strong> 118</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1</li><li><strong>width:</strong> 556</li></ul></details><details target=\"#9-node-android-widget-Button-556-0-231-126\"><summary>android.widget.Button</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 556</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 231</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> None</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.EditText</summary><ul><li><strong>actionList:</strong> [2097152]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 1</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> True</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> 搜索世界</li></ul></details><details><summary>android.widget.Button</summary><ul><li><strong>actionList:</strong> [256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> 搜</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot \"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>StandardAndroidViewTest_testLongText</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:783px;height:122px;\"\n          id=\"10-node-android-widget-LinearLayout-0-0-787-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1px;width:369px;height:114px;\"\n          id=\"10-node-android-widget-EditText-0-1-373-118\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:373px;top:0px;width:410px;height:122px;\"\n          id=\"10-node-android-widget-Button-373-0-414-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#10-node-android-widget-LinearLayout-0-0-787-126\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 787</li></ul><details target=\"#10-node-android-widget-EditText-0-1-373-118\"><summary>android.widget.EditText</summary><ul><li><strong>height:</strong> 118</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1</li><li><strong>width:</strong> 373</li></ul></details><details target=\"#10-node-android-widget-Button-373-0-414-126\"><summary>android.widget.Button</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 373</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 414</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> None</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.EditText</summary><ul><li><strong>actionList:</strong> [256, 512, 131072, 2097152]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 1</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> True</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> This is a really long text and should overflow</li></ul></details><details><summary>android.widget.Button</summary><ul><li><strong>actionList:</strong> [256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Search!!!!!!!!!....%%%%</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot alternate\"><div class=\"screenshot_name\"><span class=\"demphasize\">com.facebook.testing.screenshot.sample.</span>StandardAndroidViewTest_testRendering</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering.png\" /></td><td><img src=\"./com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:783px;height:122px;\"\n          id=\"11-node-android-widget-LinearLayout-0-0-787-126\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:1px;width:369px;height:114px;\"\n          id=\"11-node-android-widget-EditText-0-1-373-118\"></div>\n        \n        <div\n          class=\"hierarchy-node\"\n          style=\"left:373px;top:0px;width:410px;height:122px;\"\n          id=\"11-node-android-widget-Button-373-0-414-126\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#11-node-android-widget-LinearLayout-0-0-787-126\"><summary>android.widget.LinearLayout</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 787</li></ul><details target=\"#11-node-android-widget-EditText-0-1-373-118\"><summary>android.widget.EditText</summary><ul><li><strong>height:</strong> 118</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 1</li><li><strong>width:</strong> 373</li></ul></details><details target=\"#11-node-android-widget-Button-373-0-414-126\"><summary>android.widget.Button</summary><ul><li><strong>height:</strong> 126</li><li><strong>left:</strong> 373</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 414</li></ul></details></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>android.widget.LinearLayout</summary><ul><li><strong>actionList:</strong> None</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul><details><summary>android.widget.EditText</summary><ul><li><strong>actionList:</strong> [2097152]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 1</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> True</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Search the world!</li></ul></details><details><summary>android.widget.Button</summary><ul><li><strong>actionList:</strong> [256, 512, 131072]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 0, u'right': 0, u'bottom': 0, u'left': 0}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> False</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> False</li><li><strong>isFocusable:</strong> False</li><li><strong>isImportantForAccessibility:</strong> False</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> True</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> False</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 31</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> Search!!!!!!!!!....%%%%</li></ul></details></details></div></div></div></div><div class=\"clearfix\"></div><hr/><div class=\"screenshot \"><div class=\"screenshot_name\"><span class=\"demphasize\"></span>fab</div><div class=\"screenshot_description\">None</div><div class=\"flex-wrapper\"><div class=\"img-block\"><div class=\"img-wrapper\"><table><tr><td><img src=\"./fab.png\" /></td></tr></table><div class=\"hierarchy-overlay\">\n        <div\n          class=\"hierarchy-node\"\n          style=\"left:0px;top:0px;width:143px;height:143px;\"\n          id=\"12-node-com-google-android-material-floatingactionbutton-FloatingActionButton-0-0-147-147\"></div>\n        </div></div></div><div class=\"command-wrapper\"><button class=\"toggle_dark\">Toggle Dark Background</button><button class=\"toggle_hierarchy\">Toggle View Hierarchy Overlay</button><h3>View Hierarchy</h3><div class=\"view-hierarchy\"><details target=\"#12-node-com-google-android-material-floatingactionbutton-FloatingActionButton-0-0-147-147\"><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>height:</strong> 147</li><li><strong>left:</strong> 0</li><li><strong>top:</strong> 0</li><li><strong>width:</strong> 147</li></ul></details></div><h3>Accessibility Hierarchy</h3><div class=\"view-hierarchy\"><details><summary>com.google.android.material.floatingactionbutton.FloatingActionButton</summary><ul><li><strong>actionList:</strong> [4, 8, 2, 64, 16, 16908342]</li><li><strong>boundsInParent:</strong> {u'top': 0, u'right': 147, u'bottom': 147, u'left': 0}</li><li><strong>boundsInScreen:</strong> {u'top': 1605, u'right': 1038, u'bottom': 1752, u'left': 891}</li><li><strong>canOpenPopup:</strong> False</li><li><strong>childCount:</strong> 0</li><li><strong>className:</strong> android.widget.ImageButton</li><li><strong>collectionInfo:</strong> None</li><li><strong>collectionItemInfo:</strong> None</li><li><strong>contentDescription:</strong> None</li><li><strong>error:</strong> None</li><li><strong>extras:</strong> Bundle[{}]</li><li><strong>inputType:</strong> 0</li><li><strong>isCheckable:</strong> False</li><li><strong>isChecked:</strong> False</li><li><strong>isClickable:</strong> True</li><li><strong>isContentInvalid:</strong> False</li><li><strong>isDismissable:</strong> False</li><li><strong>isEditable:</strong> False</li><li><strong>isEnabled:</strong> True</li><li><strong>isFocusable:</strong> True</li><li><strong>isImportantForAccessibility:</strong> True</li><li><strong>isLongClickable:</strong> False</li><li><strong>isMultiLine:</strong> False</li><li><strong>isPassword:</strong> False</li><li><strong>isScrollable:</strong> False</li><li><strong>isSelected:</strong> False</li><li><strong>isVisibleToUser:</strong> True</li><li><strong>liveRegion:</strong> 0</li><li><strong>maxTextLength:</strong> -1</li><li><strong>movementGranularities:</strong> 0</li><li><strong>rangeInfo:</strong> None</li><li><strong>text:</strong> None</li></ul></details></div></div></div></div><div class=\"clearfix\"></div><hr/></body></html>"
  },
  {
    "path": "src/screenshotbot/sdk/example/metadata.xml",
    "content": "<screenshots><screenshot><description /><name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault</name><test_class>com.facebook.testing.screenshot.sample.ExampleScreenshotTest</test_class><test_name>testDefault</test_name><tile_width>2</tile_width><tile_height>15</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_4</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_4</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_5</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_5</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_6</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_6</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_7</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_7</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_8</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_8</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_9</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_9</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_10</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_10</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_11</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_11</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_12</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_12</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_13</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_13</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_14</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_14</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_4</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_4</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_5</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_5</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_6</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_6</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_7</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_7</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_8</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_8</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_9</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_9</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_10</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_10</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_11</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_11</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_12</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_12</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_13</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_13</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_14</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_14</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2</name><test_class>com.facebook.testing.screenshot.sample.ImageRowScreenshotTest</test_class><test_name>testDefault2</test_name><tile_width>2</tile_width><tile_height>1</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault2_1_0</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata</name><test_class>com.facebook.testing.screenshot.sample.MainActivityTest</test_class><test_name>testScreenshotEntireActivityWithoutAccessibilityMetadata</test_name><tile_width>3</tile_width><tile_height>4</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow</name><test_class>com.facebook.testing.screenshot.sample.MainActivityTest</test_class><test_name>warningTextShouldBeYellow</test_name><tile_width>3</tile_width><tile_height>4</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3</relative_file_name></screenshot><screenshot><description /><name>fab</name><test_class>unknown</test_class><test_name>unknown</test_name><tile_width>1</tile_width><tile_height>1</tile_height><view_hierarchy>fab_dump.json</view_hierarchy><ax_issues>fab_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/fab</absolute_file_name><relative_file_name>fab</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity</name><test_class>com.facebook.testing.screenshot.sample.MainActivityTest</test_class><test_name>testScreenshotEntireActivity</test_name><tile_width>3</tile_width><tile_height>4</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed</name><test_class>com.facebook.testing.screenshot.sample.MainActivityTest</test_class><test_name>errorTextShouldBeRed</test_name><tile_width>3</tile_width><tile_height>4</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen</name><test_class>com.facebook.testing.screenshot.sample.MainActivityTest</test_class><test_name>mainActivityTestSettingsOpen</test_name><tile_width>3</tile_width><tile_height>4</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen</name><test_class>com.facebook.testing.screenshot.sample.MainActivityTest</test_class><test_name>okTextShouldBeGreen</test_name><tile_width>3</tile_width><tile_height>4</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering</name><test_class>com.facebook.testing.screenshot.sample.StandardAndroidViewTest</test_class><test_name>testRendering</test_name><tile_width>2</tile_width><tile_height>1</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText</name><test_class>com.facebook.testing.screenshot.sample.StandardAndroidViewTest</test_class><test_name>testLongText</test_name><tile_width>2</tile_width><tile_height>1</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0</relative_file_name></screenshot><screenshot><description /><name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese</name><test_class>com.facebook.testing.screenshot.sample.StandardAndroidViewTest</test_class><test_name>testChinese</test_name><tile_width>2</tile_width><tile_height>1</tile_height><view_hierarchy>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_dump.json</view_hierarchy><ax_issues>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_issues.json</ax_issues><extras /><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese</relative_file_name><absolute_file_name>/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0</absolute_file_name><relative_file_name>com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0</relative_file_name></screenshot></screenshots>"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 826,\n    \"height\": 826,\n    \"children\": [\n      {\n        \"class\": \"com.facebook.litho.Column\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 826,\n        \"height\": 826,\n        \"children\": [\n          {\n            \"class\": \"com.facebook.testing.screenshot.sample.ImageRow\",\n            \"left\": 66,\n            \"top\": 66,\n            \"width\": 694,\n            \"height\": 234,\n            \"children\": [\n              {\n                \"class\": \"com.facebook.litho.Row\",\n                \"left\": 66,\n                \"top\": 66,\n                \"width\": 694,\n                \"height\": 234,\n                \"children\": [\n                  {\n                    \"class\": \"com.facebook.litho.widget.Image\",\n                    \"left\": 66,\n                    \"top\": 66,\n                    \"width\": 176,\n                    \"height\": 176,\n                    \"children\": []\n                  },\n                  {\n                    \"class\": \"com.facebook.litho.widget.Image\",\n                    \"left\": 242,\n                    \"top\": 66,\n                    \"width\": 353,\n                    \"height\": 353,\n                    \"children\": []\n                  },\n                  {\n                    \"class\": \"com.facebook.litho.widget.Image\",\n                    \"left\": 595,\n                    \"top\": 66,\n                    \"width\": 88,\n                    \"height\": 88,\n                    \"children\": []\n                  }\n                ]\n              }\n            ]\n          },\n          {\n            \"class\": \"com.facebook.litho.widget.Text\",\n            \"left\": 66,\n            \"top\": 299,\n            \"width\": 694,\n            \"height\": 461,\n            \"children\": []\n          }\n        ]\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": null\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 826,\n    \"height\": 353,\n    \"children\": [\n      {\n        \"class\": \"com.facebook.litho.Row\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 826,\n        \"height\": 353,\n        \"children\": [\n          {\n            \"class\": \"com.facebook.litho.widget.Image\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 176,\n            \"height\": 176,\n            \"children\": []\n          },\n          {\n            \"class\": \"com.facebook.litho.widget.Image\",\n            \"left\": 176,\n            \"top\": 0,\n            \"width\": 353,\n            \"height\": 353,\n            \"children\": []\n          },\n          {\n            \"class\": \"com.facebook.litho.widget.Image\",\n            \"left\": 529,\n            \"top\": 0,\n            \"width\": 88,\n            \"height\": 88,\n            \"children\": []\n          }\n        ]\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.facebook.litho.LithoView\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": null\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1788,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1788,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1788,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1788,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 1080,\n                            \"height\": 154,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 66,\n                                \"width\": 1080,\n                                \"height\": 154,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 44,\n                                    \"top\": 106,\n                                    \"width\": 903,\n                                    \"height\": 74\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 969,\n                                    \"top\": 66,\n                                    \"width\": 111,\n                                    \"height\": 154,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 969,\n                                        \"top\": 77,\n                                        \"width\": 111,\n                                        \"height\": 132\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 220,\n                            \"width\": 1080,\n                            \"height\": 1568,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 220,\n                                \"width\": 1080,\n                                \"height\": 1568\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 882,\n                            \"top\": 1590,\n                            \"width\": 154,\n                            \"height\": 154\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 66,\n                              \"bottom\": 220\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 154\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 66,\n                                  \"bottom\": 220\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 903,\n                                      \"top\": 0,\n                                      \"bottom\": 74\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 44,\n                                      \"right\": 947,\n                                      \"top\": 106,\n                                      \"bottom\": 180\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 111,\n                                      \"top\": 0,\n                                      \"bottom\": 154\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 969,\n                                      \"right\": 1080,\n                                      \"top\": 66,\n                                      \"bottom\": 220\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 111,\n                                          \"top\": 0,\n                                          \"bottom\": 132\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 969,\n                                          \"right\": 1080,\n                                          \"top\": 77,\n                                          \"bottom\": 209\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎More options‎‏‎‎‏‎\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1568\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 220,\n                              \"bottom\": 1788\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1568\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 220,\n                                  \"bottom\": 1788\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is ERROR\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 154,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 882,\n                              \"right\": 1036,\n                              \"top\": 1590,\n                              \"bottom\": 1744\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 132\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1788,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 44,\n            \"top\": 40,\n            \"width\": 903,\n            \"height\": 74\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 11,\n            \"width\": 111,\n            \"height\": 132\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1568\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 882,\n            \"top\": 1590,\n            \"width\": 154,\n            \"height\": 154\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1788,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1788,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1788,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1788,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 1080,\n                            \"height\": 154,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 66,\n                                \"width\": 1080,\n                                \"height\": 154,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 44,\n                                    \"top\": 106,\n                                    \"width\": 903,\n                                    \"height\": 74\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 969,\n                                    \"top\": 66,\n                                    \"width\": 111,\n                                    \"height\": 154,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 969,\n                                        \"top\": 77,\n                                        \"width\": 111,\n                                        \"height\": 132\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 220,\n                            \"width\": 1080,\n                            \"height\": 1568,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 220,\n                                \"width\": 1080,\n                                \"height\": 1568\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 882,\n                            \"top\": 1459,\n                            \"width\": 154,\n                            \"height\": 154\n                          },\n                          {\n                            \"class\": \"com.google.android.material.snackbar.Snackbar.SnackbarLayout\",\n                            \"left\": 0,\n                            \"top\": 1657,\n                            \"width\": 1080,\n                            \"height\": 131,\n                            \"children\": [\n                              {\n                                \"class\": \"com.google.android.material.snackbar.SnackbarContentLayout\",\n                                \"left\": 33,\n                                \"top\": 1657,\n                                \"width\": 1014,\n                                \"height\": 131,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 33,\n                                    \"top\": 1657,\n                                    \"width\": 1014,\n                                    \"height\": 131\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatButton\",\n                                    \"left\": 33,\n                                    \"top\": 1657,\n                                    \"width\": 0,\n                                    \"height\": 0\n                                  }\n                                ]\n                              }\n                            ]\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 5,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 66,\n                              \"bottom\": 220\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 154\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 66,\n                                  \"bottom\": 220\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 903,\n                                      \"top\": 0,\n                                      \"bottom\": 74\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 44,\n                                      \"right\": 947,\n                                      \"top\": 106,\n                                      \"bottom\": 180\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 111,\n                                      \"top\": 0,\n                                      \"bottom\": 154\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 969,\n                                      \"right\": 1080,\n                                      \"top\": 66,\n                                      \"bottom\": 220\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 111,\n                                          \"top\": 0,\n                                          \"bottom\": 132\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 969,\n                                          \"right\": 1080,\n                                          \"top\": 77,\n                                          \"bottom\": 209\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎More options‎‏‎‎‏‎\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1568\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 220,\n                              \"bottom\": 1788\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1568\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 220,\n                                  \"bottom\": 1788\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is OK\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              1,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 154,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 882,\n                              \"right\": 1036,\n                              \"top\": 1459,\n                              \"bottom\": 1613\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          },\n                          {\n                            \"class\": \"com.google.android.material.snackbar.Snackbar$SnackbarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              1,\n                              64,\n                              16908342,\n                              1048576\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 131\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 1657,\n                              \"bottom\": 1788\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.FrameLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.PANE_TITLE_KEY=null, androidx.view.accessibility.AccessibilityNodeInfoCompat.BOOLEAN_PROPERTY_KEY=0}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": true,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 1,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"com.google.android.material.snackbar.SnackbarContentLayout\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1014,\n                                  \"top\": 0,\n                                  \"bottom\": 131\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 33,\n                                  \"right\": 1047,\n                                  \"top\": 1657,\n                                  \"bottom\": 1788\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 1,\n                                \"className\": \"android.widget.LinearLayout\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 1014,\n                                      \"top\": 0,\n                                      \"bottom\": 131\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 33,\n                                      \"right\": 1047,\n                                      \"top\": 1657,\n                                      \"bottom\": 1788\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": true,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"This is a snackbar\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatButton\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      1,\n                                      64,\n                                      16,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 0,\n                                      \"top\": 0,\n                                      \"bottom\": 0\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 33,\n                                      \"right\": 33,\n                                      \"top\": 1657,\n                                      \"bottom\": 1657\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.Button\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": true,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": true,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": true,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": false,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": null\n                                  }\n                                ]\n                              }\n                            ]\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 132\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1788,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 44,\n            \"top\": 40,\n            \"width\": 903,\n            \"height\": 74\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 11,\n            \"width\": 111,\n            \"height\": 132\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1568\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 882,\n            \"top\": 1459,\n            \"width\": 154,\n            \"height\": 154\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1788,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1788,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1788,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1788,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 1080,\n                            \"height\": 154,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 66,\n                                \"width\": 1080,\n                                \"height\": 154,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 44,\n                                    \"top\": 106,\n                                    \"width\": 903,\n                                    \"height\": 74\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 969,\n                                    \"top\": 66,\n                                    \"width\": 111,\n                                    \"height\": 154,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 969,\n                                        \"top\": 77,\n                                        \"width\": 111,\n                                        \"height\": 132\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 220,\n                            \"width\": 1080,\n                            \"height\": 1568,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 220,\n                                \"width\": 1080,\n                                \"height\": 1568\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 882,\n                            \"top\": 1590,\n                            \"width\": 154,\n                            \"height\": 154\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 66,\n                              \"bottom\": 220\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 154\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 66,\n                                  \"bottom\": 220\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 903,\n                                      \"top\": 0,\n                                      \"bottom\": 74\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 44,\n                                      \"right\": 947,\n                                      \"top\": 106,\n                                      \"bottom\": 180\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 111,\n                                      \"top\": 0,\n                                      \"bottom\": 154\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 969,\n                                      \"right\": 1080,\n                                      \"top\": 66,\n                                      \"bottom\": 220\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 111,\n                                          \"top\": 0,\n                                          \"bottom\": 132\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 969,\n                                          \"right\": 1080,\n                                          \"top\": 77,\n                                          \"bottom\": 209\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎More options‎‏‎‎‏‎\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1568\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 220,\n                              \"bottom\": 1788\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1568\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 220,\n                                  \"bottom\": 1788\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is OK\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 154,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 882,\n                              \"right\": 1036,\n                              \"top\": 1590,\n                              \"bottom\": 1744\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 132\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1788,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 44,\n            \"top\": 40,\n            \"width\": 903,\n            \"height\": 74\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 11,\n            \"width\": 111,\n            \"height\": 132\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1568\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 882,\n            \"top\": 1590,\n            \"width\": 154,\n            \"height\": 154\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1788,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1788,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1788,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1788,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 1080,\n                            \"height\": 154,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 66,\n                                \"width\": 1080,\n                                \"height\": 154,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 44,\n                                    \"top\": 106,\n                                    \"width\": 903,\n                                    \"height\": 74\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 969,\n                                    \"top\": 66,\n                                    \"width\": 111,\n                                    \"height\": 154,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 969,\n                                        \"top\": 77,\n                                        \"width\": 111,\n                                        \"height\": 132\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 220,\n                            \"width\": 1080,\n                            \"height\": 1568,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 220,\n                                \"width\": 1080,\n                                \"height\": 1568\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 882,\n                            \"top\": 1590,\n                            \"width\": 154,\n                            \"height\": 154\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {}\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1788,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1788,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1788,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1788,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 1080,\n                            \"height\": 154,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 66,\n                                \"width\": 1080,\n                                \"height\": 154,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 44,\n                                    \"top\": 106,\n                                    \"width\": 903,\n                                    \"height\": 74\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 969,\n                                    \"top\": 66,\n                                    \"width\": 111,\n                                    \"height\": 154,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 969,\n                                        \"top\": 77,\n                                        \"width\": 111,\n                                        \"height\": 132\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 220,\n                            \"width\": 1080,\n                            \"height\": 1568,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 220,\n                                \"width\": 1080,\n                                \"height\": 1568\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 882,\n                            \"top\": 1590,\n                            \"width\": 154,\n                            \"height\": 154\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 66,\n                              \"bottom\": 220\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 154\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 66,\n                                  \"bottom\": 220\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 903,\n                                      \"top\": 0,\n                                      \"bottom\": 74\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 44,\n                                      \"right\": 947,\n                                      \"top\": 106,\n                                      \"bottom\": 180\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 111,\n                                      \"top\": 0,\n                                      \"bottom\": 154\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 969,\n                                      \"right\": 1080,\n                                      \"top\": 66,\n                                      \"bottom\": 220\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 111,\n                                          \"top\": 0,\n                                          \"bottom\": 132\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 969,\n                                          \"right\": 1080,\n                                          \"top\": 77,\n                                          \"bottom\": 209\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎More options‎‏‎‎‏‎\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1568\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 220,\n                              \"bottom\": 1788\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1568\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 220,\n                                  \"bottom\": 1788\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is OK\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 154,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 882,\n                              \"right\": 1036,\n                              \"top\": 1590,\n                              \"bottom\": 1744\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 132\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1788,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 44,\n            \"top\": 40,\n            \"width\": 903,\n            \"height\": 74\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 11,\n            \"width\": 111,\n            \"height\": 132\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1568\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 882,\n            \"top\": 1590,\n            \"width\": 154,\n            \"height\": 154\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1788,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 0,\n                \"width\": 1080,\n                \"height\": 1788,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 0,\n                    \"width\": 1080,\n                    \"height\": 1788,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"left\": 0,\n                        \"top\": 0,\n                        \"width\": 1080,\n                        \"height\": 1788,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 1080,\n                            \"height\": 154,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"left\": 0,\n                                \"top\": 66,\n                                \"width\": 1080,\n                                \"height\": 154,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"left\": 44,\n                                    \"top\": 106,\n                                    \"width\": 903,\n                                    \"height\": 74\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"left\": 969,\n                                    \"top\": 66,\n                                    \"width\": 111,\n                                    \"height\": 154,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter.OverflowMenuButton\",\n                                        \"left\": 969,\n                                        \"top\": 77,\n                                        \"width\": 111,\n                                        \"height\": 132\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"left\": 0,\n                            \"top\": 220,\n                            \"width\": 1080,\n                            \"height\": 1568,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"left\": 0,\n                                \"top\": 220,\n                                \"width\": 1080,\n                                \"height\": 1568\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"left\": 882,\n                            \"top\": 1590,\n                            \"width\": 154,\n                            \"height\": 154\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"actionList\": [\n      4,\n      8,\n      64,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 1080,\n      \"top\": 0,\n      \"bottom\": 1920\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 1,\n    \"className\": \"android.widget.FrameLayout\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 1788\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 1,\n        \"className\": \"android.widget.LinearLayout\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 0,\n              \"top\": 0,\n              \"bottom\": 0\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 0,\n            \"className\": \"android.view.View\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": false,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": null\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"actionList\": [\n              4,\n              8,\n              64,\n              16908342\n            ],\n            \"boundsInParent\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"boundsInScreen\": {\n              \"left\": 0,\n              \"right\": 1080,\n              \"top\": 0,\n              \"bottom\": 1788\n            },\n            \"canOpenPopup\": false,\n            \"childCount\": 1,\n            \"className\": \"android.widget.FrameLayout\",\n            \"collectionInfo\": null,\n            \"collectionItemInfo\": null,\n            \"contentDescription\": null,\n            \"error\": null,\n            \"extras\": \"Bundle[{}]\",\n            \"inputType\": 0,\n            \"isCheckable\": false,\n            \"isChecked\": false,\n            \"isClickable\": false,\n            \"isContentInvalid\": false,\n            \"isDismissable\": false,\n            \"isEditable\": false,\n            \"isEnabled\": true,\n            \"isFocusable\": false,\n            \"isImportantForAccessibility\": false,\n            \"isLongClickable\": false,\n            \"isMultiLine\": false,\n            \"isPassword\": false,\n            \"isScrollable\": false,\n            \"isSelected\": false,\n            \"isVisibleToUser\": true,\n            \"liveRegion\": 0,\n            \"maxTextLength\": -1,\n            \"movementGranularities\": 0,\n            \"rangeInfo\": null,\n            \"text\": null,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"actionList\": [\n                  4,\n                  8,\n                  64,\n                  16908342\n                ],\n                \"boundsInParent\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"boundsInScreen\": {\n                  \"left\": 0,\n                  \"right\": 1080,\n                  \"top\": 0,\n                  \"bottom\": 1788\n                },\n                \"canOpenPopup\": false,\n                \"childCount\": 1,\n                \"className\": \"android.widget.LinearLayout\",\n                \"collectionInfo\": null,\n                \"collectionItemInfo\": null,\n                \"contentDescription\": null,\n                \"error\": null,\n                \"extras\": \"Bundle[{}]\",\n                \"inputType\": 0,\n                \"isCheckable\": false,\n                \"isChecked\": false,\n                \"isClickable\": false,\n                \"isContentInvalid\": false,\n                \"isDismissable\": false,\n                \"isEditable\": false,\n                \"isEnabled\": true,\n                \"isFocusable\": false,\n                \"isImportantForAccessibility\": false,\n                \"isLongClickable\": false,\n                \"isMultiLine\": false,\n                \"isPassword\": false,\n                \"isScrollable\": false,\n                \"isSelected\": false,\n                \"isVisibleToUser\": true,\n                \"liveRegion\": 0,\n                \"maxTextLength\": -1,\n                \"movementGranularities\": 0,\n                \"rangeInfo\": null,\n                \"text\": null,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 0,\n                      \"top\": 0,\n                      \"bottom\": 0\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 0,\n                    \"className\": \"android.view.View\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": false,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": null\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"actionList\": [\n                      4,\n                      8,\n                      64,\n                      16908342\n                    ],\n                    \"boundsInParent\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"boundsInScreen\": {\n                      \"left\": 0,\n                      \"right\": 1080,\n                      \"top\": 0,\n                      \"bottom\": 1788\n                    },\n                    \"canOpenPopup\": false,\n                    \"childCount\": 1,\n                    \"className\": \"android.widget.FrameLayout\",\n                    \"collectionInfo\": null,\n                    \"collectionItemInfo\": null,\n                    \"contentDescription\": null,\n                    \"error\": null,\n                    \"extras\": \"Bundle[{}]\",\n                    \"inputType\": 0,\n                    \"isCheckable\": false,\n                    \"isChecked\": false,\n                    \"isClickable\": false,\n                    \"isContentInvalid\": false,\n                    \"isDismissable\": false,\n                    \"isEditable\": false,\n                    \"isEnabled\": true,\n                    \"isFocusable\": false,\n                    \"isImportantForAccessibility\": false,\n                    \"isLongClickable\": false,\n                    \"isMultiLine\": false,\n                    \"isPassword\": false,\n                    \"isScrollable\": false,\n                    \"isSelected\": false,\n                    \"isVisibleToUser\": true,\n                    \"liveRegion\": 0,\n                    \"maxTextLength\": -1,\n                    \"movementGranularities\": 0,\n                    \"rangeInfo\": null,\n                    \"text\": null,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.coordinatorlayout.widget.CoordinatorLayout\",\n                        \"actionList\": [\n                          4,\n                          8,\n                          64,\n                          16908342\n                        ],\n                        \"boundsInParent\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"boundsInScreen\": {\n                          \"left\": 0,\n                          \"right\": 1080,\n                          \"top\": 0,\n                          \"bottom\": 1788\n                        },\n                        \"canOpenPopup\": false,\n                        \"childCount\": 4,\n                        \"className\": \"android.view.ViewGroup\",\n                        \"collectionInfo\": null,\n                        \"collectionItemInfo\": null,\n                        \"contentDescription\": null,\n                        \"error\": null,\n                        \"extras\": \"Bundle[{}]\",\n                        \"inputType\": 0,\n                        \"isCheckable\": false,\n                        \"isChecked\": false,\n                        \"isClickable\": false,\n                        \"isContentInvalid\": false,\n                        \"isDismissable\": false,\n                        \"isEditable\": false,\n                        \"isEnabled\": true,\n                        \"isFocusable\": false,\n                        \"isImportantForAccessibility\": true,\n                        \"isLongClickable\": false,\n                        \"isMultiLine\": false,\n                        \"isPassword\": false,\n                        \"isScrollable\": false,\n                        \"isSelected\": false,\n                        \"isVisibleToUser\": true,\n                        \"liveRegion\": 0,\n                        \"maxTextLength\": -1,\n                        \"movementGranularities\": 0,\n                        \"rangeInfo\": null,\n                        \"text\": null,\n                        \"children\": [\n                          {\n                            \"class\": \"com.google.android.material.appbar.AppBarLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 66,\n                              \"bottom\": 220\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 2,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.Toolbar\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 154\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 66,\n                                  \"bottom\": 220\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 2,\n                                \"className\": \"android.view.ViewGroup\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": false,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": false,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 0,\n                                \"rangeInfo\": null,\n                                \"text\": null,\n                                \"children\": [\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342,\n                                      256,\n                                      512,\n                                      131072\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 903,\n                                      \"top\": 0,\n                                      \"bottom\": 74\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 44,\n                                      \"right\": 947,\n                                      \"top\": 106,\n                                      \"bottom\": 180\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 0,\n                                    \"className\": \"android.widget.TextView\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": true,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 31,\n                                    \"rangeInfo\": null,\n                                    \"text\": \"Screenshot Tests for Android Sample\",\n                                    \"children\": null\n                                  },\n                                  {\n                                    \"class\": \"androidx.appcompat.widget.ActionMenuView\",\n                                    \"actionList\": [\n                                      4,\n                                      8,\n                                      64,\n                                      16908342\n                                    ],\n                                    \"boundsInParent\": {\n                                      \"left\": 0,\n                                      \"right\": 111,\n                                      \"top\": 0,\n                                      \"bottom\": 154\n                                    },\n                                    \"boundsInScreen\": {\n                                      \"left\": 969,\n                                      \"right\": 1080,\n                                      \"top\": 66,\n                                      \"bottom\": 220\n                                    },\n                                    \"canOpenPopup\": false,\n                                    \"childCount\": 1,\n                                    \"className\": \"androidx.appcompat.widget.LinearLayoutCompat\",\n                                    \"collectionInfo\": null,\n                                    \"collectionItemInfo\": null,\n                                    \"contentDescription\": null,\n                                    \"error\": null,\n                                    \"extras\": \"Bundle[{}]\",\n                                    \"inputType\": 0,\n                                    \"isCheckable\": false,\n                                    \"isChecked\": false,\n                                    \"isClickable\": false,\n                                    \"isContentInvalid\": false,\n                                    \"isDismissable\": false,\n                                    \"isEditable\": false,\n                                    \"isEnabled\": true,\n                                    \"isFocusable\": false,\n                                    \"isImportantForAccessibility\": false,\n                                    \"isLongClickable\": false,\n                                    \"isMultiLine\": false,\n                                    \"isPassword\": false,\n                                    \"isScrollable\": false,\n                                    \"isSelected\": false,\n                                    \"isVisibleToUser\": true,\n                                    \"liveRegion\": 0,\n                                    \"maxTextLength\": -1,\n                                    \"movementGranularities\": 0,\n                                    \"rangeInfo\": null,\n                                    \"text\": null,\n                                    \"children\": [\n                                      {\n                                        \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n                                        \"actionList\": [\n                                          4,\n                                          8,\n                                          1,\n                                          64,\n                                          16,\n                                          32,\n                                          131072,\n                                          256,\n                                          512,\n                                          16908342\n                                        ],\n                                        \"boundsInParent\": {\n                                          \"left\": 0,\n                                          \"right\": 111,\n                                          \"top\": 0,\n                                          \"bottom\": 132\n                                        },\n                                        \"boundsInScreen\": {\n                                          \"left\": 969,\n                                          \"right\": 1080,\n                                          \"top\": 77,\n                                          \"bottom\": 209\n                                        },\n                                        \"canOpenPopup\": false,\n                                        \"childCount\": 0,\n                                        \"className\": \"android.widget.ImageView\",\n                                        \"collectionInfo\": null,\n                                        \"collectionItemInfo\": null,\n                                        \"contentDescription\": \"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎More options‎‏‎‎‏‎\",\n                                        \"error\": null,\n                                        \"extras\": \"Bundle[{}]\",\n                                        \"inputType\": 0,\n                                        \"isCheckable\": false,\n                                        \"isChecked\": false,\n                                        \"isClickable\": true,\n                                        \"isContentInvalid\": false,\n                                        \"isDismissable\": false,\n                                        \"isEditable\": false,\n                                        \"isEnabled\": true,\n                                        \"isFocusable\": true,\n                                        \"isImportantForAccessibility\": true,\n                                        \"isLongClickable\": true,\n                                        \"isMultiLine\": false,\n                                        \"isPassword\": false,\n                                        \"isScrollable\": false,\n                                        \"isSelected\": false,\n                                        \"isVisibleToUser\": true,\n                                        \"liveRegion\": 0,\n                                        \"maxTextLength\": -1,\n                                        \"movementGranularities\": 11,\n                                        \"rangeInfo\": null,\n                                        \"text\": null,\n                                        \"children\": null\n                                      }\n                                    ]\n                                  }\n                                ]\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"android.widget.LinearLayout\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              64,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 0,\n                              \"bottom\": 1568\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 0,\n                              \"right\": 1080,\n                              \"top\": 220,\n                              \"bottom\": 1788\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 1,\n                            \"className\": \"android.widget.LinearLayout\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": false,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": false,\n                            \"isImportantForAccessibility\": false,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": [\n                              {\n                                \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n                                \"actionList\": [\n                                  4,\n                                  8,\n                                  64,\n                                  16908342,\n                                  256,\n                                  512,\n                                  131072\n                                ],\n                                \"boundsInParent\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 0,\n                                  \"bottom\": 1568\n                                },\n                                \"boundsInScreen\": {\n                                  \"left\": 0,\n                                  \"right\": 1080,\n                                  \"top\": 220,\n                                  \"bottom\": 1788\n                                },\n                                \"canOpenPopup\": false,\n                                \"childCount\": 0,\n                                \"className\": \"android.widget.TextView\",\n                                \"collectionInfo\": null,\n                                \"collectionItemInfo\": null,\n                                \"contentDescription\": null,\n                                \"error\": null,\n                                \"extras\": \"Bundle[{}]\",\n                                \"inputType\": 0,\n                                \"isCheckable\": false,\n                                \"isChecked\": false,\n                                \"isClickable\": false,\n                                \"isContentInvalid\": false,\n                                \"isDismissable\": false,\n                                \"isEditable\": false,\n                                \"isEnabled\": true,\n                                \"isFocusable\": false,\n                                \"isImportantForAccessibility\": true,\n                                \"isLongClickable\": false,\n                                \"isMultiLine\": true,\n                                \"isPassword\": false,\n                                \"isScrollable\": false,\n                                \"isSelected\": false,\n                                \"isVisibleToUser\": true,\n                                \"liveRegion\": 0,\n                                \"maxTextLength\": -1,\n                                \"movementGranularities\": 31,\n                                \"rangeInfo\": null,\n                                \"text\": \"Status is WARNING\",\n                                \"children\": null\n                              }\n                            ]\n                          },\n                          {\n                            \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n                            \"actionList\": [\n                              4,\n                              8,\n                              2,\n                              64,\n                              16,\n                              16908342\n                            ],\n                            \"boundsInParent\": {\n                              \"left\": 0,\n                              \"right\": 154,\n                              \"top\": 0,\n                              \"bottom\": 154\n                            },\n                            \"boundsInScreen\": {\n                              \"left\": 882,\n                              \"right\": 1036,\n                              \"top\": 1590,\n                              \"bottom\": 1744\n                            },\n                            \"canOpenPopup\": false,\n                            \"childCount\": 0,\n                            \"className\": \"android.widget.ImageButton\",\n                            \"collectionInfo\": null,\n                            \"collectionItemInfo\": null,\n                            \"contentDescription\": null,\n                            \"error\": null,\n                            \"extras\": \"Bundle[{}]\",\n                            \"inputType\": 0,\n                            \"isCheckable\": false,\n                            \"isChecked\": false,\n                            \"isClickable\": true,\n                            \"isContentInvalid\": false,\n                            \"isDismissable\": false,\n                            \"isEditable\": false,\n                            \"isEnabled\": true,\n                            \"isFocusable\": true,\n                            \"isImportantForAccessibility\": true,\n                            \"isLongClickable\": false,\n                            \"isMultiLine\": false,\n                            \"isPassword\": false,\n                            \"isScrollable\": false,\n                            \"isSelected\": false,\n                            \"isVisibleToUser\": true,\n                            \"liveRegion\": 0,\n                            \"maxTextLength\": -1,\n                            \"movementGranularities\": 0,\n                            \"rangeInfo\": null,\n                            \"text\": null,\n                            \"children\": null\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 132\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 1788,\n          \"bottom\": 1920\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"actionList\": [\n          4,\n          8,\n          64,\n          16908342\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 1080,\n          \"top\": 0,\n          \"bottom\": 66\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"className\": \"android.view.View\",\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": true,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": true,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": null,\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 44,\n            \"top\": 40,\n            \"width\": 903,\n            \"height\": 74\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"OverflowMenuButton\",\n          \"class\": \"androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 11,\n            \"width\": 111,\n            \"height\": 132\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"AppCompatTextView\",\n          \"class\": \"androidx.appcompat.widget.AppCompatTextView\",\n          \"position\": {\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 1080,\n            \"height\": 1568\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        },\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 882,\n            \"top\": 1590,\n            \"width\": 154,\n            \"height\": 154\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 826,\n    \"height\": 132,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"left\": 0,\n        \"top\": 1,\n        \"width\": 583,\n        \"height\": 124\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"left\": 583,\n        \"top\": 0,\n        \"width\": 243,\n        \"height\": 132\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"actionList\": [\n          2097152\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 1,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": true,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": \"搜索世界\",\n        \"children\": null\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"actionList\": [\n          256,\n          512,\n          131072\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": true,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"搜\",\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 826,\n    \"height\": 132,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"left\": 0,\n        \"top\": 1,\n        \"width\": 583,\n        \"height\": 124\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"left\": 583,\n        \"top\": 0,\n        \"width\": 243,\n        \"height\": 132\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"actionList\": [\n          256,\n          512,\n          131072,\n          2097152\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 1,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": true,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"This is a really long text and should overflow\",\n        \"children\": null\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"actionList\": [\n          256,\n          512,\n          131072\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": true,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"SEARCH!\",\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 826,\n    \"height\": 132,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"left\": 0,\n        \"top\": 1,\n        \"width\": 583,\n        \"height\": 124\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"left\": 583,\n        \"top\": 0,\n        \"width\": 243,\n        \"height\": 132\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"android.widget.LinearLayout\",\n    \"actionList\": null,\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"boundsInScreen\": {\n      \"left\": 0,\n      \"right\": 0,\n      \"top\": 0,\n      \"bottom\": 0\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": false,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": false,\n    \"isFocusable\": false,\n    \"isImportantForAccessibility\": false,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": false,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": [\n      {\n        \"class\": \"android.widget.EditText\",\n        \"actionList\": [\n          2097152\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{}]\",\n        \"inputType\": 1,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": true,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": false,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 0,\n        \"rangeInfo\": null,\n        \"text\": \"Search the world\",\n        \"children\": null\n      },\n      {\n        \"class\": \"android.widget.Button\",\n        \"actionList\": [\n          256,\n          512,\n          131072\n        ],\n        \"boundsInParent\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"boundsInScreen\": {\n          \"left\": 0,\n          \"right\": 0,\n          \"top\": 0,\n          \"bottom\": 0\n        },\n        \"canOpenPopup\": false,\n        \"childCount\": 0,\n        \"collectionInfo\": null,\n        \"collectionItemInfo\": null,\n        \"contentDescription\": null,\n        \"error\": null,\n        \"extras\": \"Bundle[{androidx.view.accessibility.AccessibilityNodeInfoCompat.SPANS_START_KEY=[]}]\",\n        \"inputType\": 0,\n        \"isCheckable\": false,\n        \"isChecked\": false,\n        \"isClickable\": false,\n        \"isContentInvalid\": false,\n        \"isDismissable\": false,\n        \"isEditable\": false,\n        \"isEnabled\": false,\n        \"isFocusable\": false,\n        \"isImportantForAccessibility\": false,\n        \"isLongClickable\": false,\n        \"isMultiLine\": true,\n        \"isPassword\": false,\n        \"isScrollable\": false,\n        \"isSelected\": false,\n        \"isVisibleToUser\": false,\n        \"liveRegion\": 0,\n        \"maxTextLength\": -1,\n        \"movementGranularities\": 31,\n        \"rangeInfo\": null,\n        \"text\": \"SEARCH!\",\n        \"children\": null\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_issues.json",
    "content": "{\n  \"axIssues\": []\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/fab_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 154,\n    \"height\": 154\n  },\n  \"version\": 1,\n  \"axHierarchy\": {\n    \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n    \"actionList\": [\n      4,\n      8,\n      2,\n      64,\n      16,\n      16908342\n    ],\n    \"boundsInParent\": {\n      \"left\": 0,\n      \"right\": 154,\n      \"top\": 0,\n      \"bottom\": 154\n    },\n    \"boundsInScreen\": {\n      \"left\": 882,\n      \"right\": 1036,\n      \"top\": 1590,\n      \"bottom\": 1744\n    },\n    \"canOpenPopup\": false,\n    \"childCount\": 0,\n    \"className\": \"android.widget.ImageButton\",\n    \"collectionInfo\": null,\n    \"collectionItemInfo\": null,\n    \"contentDescription\": null,\n    \"error\": null,\n    \"extras\": \"Bundle[{}]\",\n    \"inputType\": 0,\n    \"isCheckable\": false,\n    \"isChecked\": false,\n    \"isClickable\": true,\n    \"isContentInvalid\": false,\n    \"isDismissable\": false,\n    \"isEditable\": false,\n    \"isEnabled\": true,\n    \"isFocusable\": true,\n    \"isImportantForAccessibility\": true,\n    \"isLongClickable\": false,\n    \"isMultiLine\": false,\n    \"isPassword\": false,\n    \"isScrollable\": false,\n    \"isSelected\": false,\n    \"isVisibleToUser\": true,\n    \"liveRegion\": 0,\n    \"maxTextLength\": -1,\n    \"movementGranularities\": 0,\n    \"rangeInfo\": null,\n    \"text\": null,\n    \"children\": null\n  }\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/fab_issues.json",
    "content": "{\n  \"axIssues\": [\n    {\n      \"id\": \"talkback_focusable_element_without_spoken_feedback\",\n      \"name\": \"Focusable Element Without Spoken Feedback\",\n      \"description\": \"The element is focusable by screen readers such as Talkback, but has no text to announce.\",\n      \"elements\": [\n        {\n          \"name\": \"FloatingActionButton\",\n          \"class\": \"com.google.android.material.floatingactionbutton.FloatingActionButton\",\n          \"position\": {\n            \"left\": 882,\n            \"top\": 1590,\n            \"width\": 154,\n            \"height\": 154\n          },\n          \"suggestions\": [\n            \"Add a contentDescription to the element.\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/metadata.json",
    "content": "[{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault\",\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1\",\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0\",\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1\"],\"testClass\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest\",\"testName\":\"testDefault\",\"tileHeight\":2,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault\",\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest\",\"testName\":\"testDefault\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"warningTextShouldBeYellow\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/fab\"],\"axIssues\":\"fab_issues.json\",\"extras\":{},\"name\":\"fab\",\"relativeFileNames\":[\"fab\"],\"testClass\":\"unknown\",\"testName\":\"unknown\",\"tileHeight\":1,\"tileWidth\":1,\"viewHierarchy\":\"fab_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"testScreenshotEntireActivity\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"errorTextShouldBeRed\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"mainActivityTestSettingsOpen\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"okTextShouldBeGreen\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering\",\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest\",\"testName\":\"testRendering\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText\",\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest\",\"testName\":\"testLongText\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese\",\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest\",\"testName\":\"testChinese\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_dump.json\"}]"
  },
  {
    "path": "src/screenshotbot/sdk/example-firebase-artifacts/artifacts/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/tests_run_id",
    "content": ""
  },
  {
    "path": "src/screenshotbot/sdk/example-shot-artifacts/com.karumi.shotconsumercompose.staging.debug.test/screenshots-compose-default/metadata_compose.json",
    "content": "{\"screenshots\":[{\"name\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest_rendersAGreetingWithAnEmptyText\",\"testClassName\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest\",\"testName\":\"rendersAGreetingWithAnEmptyText\"},{\"name\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest_rendersAGreetingWithALongText\",\"testClassName\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest\",\"testName\":\"rendersAGreetingWithALongText\"},{\"name\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest_rendesAnyComponentUsingABitmapInsteadOfANode\",\"testClassName\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest\",\"testName\":\"rendesAnyComponentUsingABitmapInsteadOfANode\"},{\"name\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest_rendersAGreetingWithATextFullOfWhitespaces\",\"testClassName\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest\",\"testName\":\"rendersAGreetingWithATextFullOfWhitespaces\"},{\"name\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest_rendersTheDefaultComponent\",\"testClassName\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest\",\"testName\":\"rendersTheDefaultComponent\"},{\"name\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest_rendersAGreetingWithAShortText\",\"testClassName\":\"com.karumi.shotconsumercompose.GreetingScreenshotTest\",\"testName\":\"rendersAGreetingWithAShortText\"},{\"name\":\"com.karumi.shotconsumercompose.RoundedCornersBoxScreenshotTest_rendersTheDefaultComponent\",\"testClassName\":\"com.karumi.shotconsumercompose.RoundedCornersBoxScreenshotTest\",\"testName\":\"rendersTheDefaultComponent\"}]}"
  },
  {
    "path": "src/screenshotbot/sdk/example-shot-artifacts/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_dump.json",
    "content": "{\n  \"viewHierarchy\": {\n    \"class\": \"com.android.internal.policy.DecorView\",\n    \"left\": 0,\n    \"top\": 0,\n    \"width\": 1080,\n    \"height\": 1920,\n    \"children\": [\n      {\n        \"class\": \"android.widget.LinearLayout\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 1788,\n        \"children\": [\n          {\n            \"class\": \"android.view.ViewStub\",\n            \"left\": 0,\n            \"top\": 0,\n            \"width\": 0,\n            \"height\": 0\n          },\n          {\n            \"class\": \"android.widget.FrameLayout\",\n            \"left\": 0,\n            \"top\": 66,\n            \"width\": 1080,\n            \"height\": 1722,\n            \"children\": [\n              {\n                \"class\": \"androidx.appcompat.widget.FitWindowsLinearLayout\",\n                \"left\": 0,\n                \"top\": 66,\n                \"width\": 1080,\n                \"height\": 1722,\n                \"children\": [\n                  {\n                    \"class\": \"androidx.appcompat.widget.ViewStubCompat\",\n                    \"left\": 0,\n                    \"top\": 66,\n                    \"width\": 0,\n                    \"height\": 0\n                  },\n                  {\n                    \"class\": \"androidx.appcompat.widget.ContentFrameLayout\",\n                    \"left\": 0,\n                    \"top\": 66,\n                    \"width\": 1080,\n                    \"height\": 1722,\n                    \"children\": [\n                      {\n                        \"class\": \"androidx.compose.ui.platform.ComposeView\",\n                        \"left\": 0,\n                        \"top\": 66,\n                        \"width\": 277,\n                        \"height\": 335,\n                        \"children\": [\n                          {\n                            \"class\": \"androidx.compose.ui.platform.AndroidComposeView\",\n                            \"left\": 0,\n                            \"top\": 66,\n                            \"width\": 277,\n                            \"height\": 335,\n                            \"children\": []\n                          }\n                        ]\n                      }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 1788,\n        \"width\": 1080,\n        \"height\": 132\n      },\n      {\n        \"class\": \"android.view.View\",\n        \"left\": 0,\n        \"top\": 0,\n        \"width\": 1080,\n        \"height\": 66\n      }\n    ]\n  },\n  \"version\": 1,\n  \"axHierarchy\": {}\n}"
  },
  {
    "path": "src/screenshotbot/sdk/example-shot-artifacts/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/metadata.json",
    "content": "[{\"absoluteFilesNames\":[\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_0_1\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_0_2\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_0_3\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_0\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_1\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_2\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_3\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_0\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_1\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_2\",\"/storage/emulated/0/Download/screenshots/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_3\"],\"axIssues\":\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_issues.json\",\"extras\":{},\"name\":\"com.karumi.shotconsumercompose.MainActivityTest_activityTest\",\"relativeFileNames\":[\"com.karumi.shotconsumercompose.MainActivityTest_activityTest\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_0_1\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_0_2\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_0_3\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_0\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_1\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_2\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_1_3\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_0\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_1\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_2\",\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_2_3\"],\"testClass\":\"com.karumi.shotconsumercompose.MainActivityTest\",\"testName\":\"activityTest\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.karumi.shotconsumercompose.MainActivityTest_activityTest_dump.json\"}]"
  },
  {
    "path": "src/screenshotbot/sdk/example-shot-artifacts/com.karumi.shotconsumercompose.staging.debug.test/screenshots-default/tests_run_id",
    "content": ""
  },
  {
    "path": "src/screenshotbot/sdk/failed-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/failed-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/git\n                #:current-commit\n                #:git-repo)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:remote-version)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:version-check #:screenshotbot/sdk/version-check)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/failed-run)\n\n(defun mark-failed (api-context)\n  (when (< (remote-version api-context) 3)\n    (error \"The remote Screenshotbot server does not support --mark-failed\"))\n\n  (when (equal flags:*channel* \"unnamed-channel\")\n    (error \"You must provide a --channel to use --mark-failed\"))\n\n  (let ((repo (git-repo)))\n    (sdk:request api-context\n                 \"/api/failed-run\" :method :put\n                                   :content (make-instance 'dto:failed-run\n                                                           :commit (current-commit repo)\n                                                           :channel flags:*channel*))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/fetch-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/fetch-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:request)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:util/request\n                #:engine\n                #:http-request)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool)\n  (:import-from #:util/misc/lists\n                #:make-batches\n                #:with-batches)\n  (:import-from #:screenshotbot/sdk/active-run\n                #:find-active-run)\n  (:import-from #:util/reused-ssl\n                #:reused-ssl-mixin\n                #:with-reused-ssl)\n  (:import-from #:screenshotbot/sdk/request\n                #:request)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/fetch-run)\n\n(defclass download-engine (reused-ssl-mixin\n                           engine)\n  ())\n\n(defvar *download-engine*\n  (make-instance 'download-engine))\n\n\n(defun get-run (api-context oid)\n  (multiple-value-bind (body code)\n      (request api-context\n               (format nil \"/api/run/~a\" oid)\n               :method :get\n               :decode-response nil)\n    (log:debug \"Got /api/run/... result: ~s\" body)\n    ;; TODO: sync this with ENSURE-API-SUCCESS.\n    (unless (eql code 200)\n      (error \"Could not fetch run: ~a, ~a\" code body))\n    (json-mop:json-to-clos\n     body\n     'dto:run)))\n\n(defvar *lock* (bt:make-lock))\n\n(define-condition unsafe-screenshot-name (error)\n  ())\n\n(defun safe-name-p (screenshot-name)\n  (let ((path (pathname screenshot-name)))\n    (and\n     (not (eql :absolute (car (pathname-directory path))))\n     (loop for directory in (pathname-directory path)\n           if (or (equal \"..\" directory) ;; it's actually just :up, but keeping it here in case\n                  (equal :up directory))\n             return nil\n           finally\n              (return t)))))\n\n(defun save-run (api-context oid &key output channel branch)\n  (cond\n    (oid\n     (let ((run (get-run api-context oid)))\n       (%save-run run :output output)))\n    (t\n     (log:debug \"Looking up active run for ~a, ~a\" channel branch)\n     (let ((run (find-active-run api-context :channel channel :branch branch)))\n       (unless run\n         (error \"Could not find active run for that combination of channel and branch\"))\n       (log:info \"Figured out the run as ~a\" (dto:run-id run))\n       (save-run api-context (dto:run-id run) :output output)))))\n\n#+lispworks\n(defvar *semaphore* (mp:make-semaphore :count 2)\n  \"We don't want all three threads being blocked on latency at the same time.\")\n\n(defun download-url (url file &key engine)\n  (with-open-file (output (bt:with-lock-held (*lock*)\n                            (ensure-directories-exist file))\n                          :direction :output\n                          :element-type '(unsigned-byte 8))\n    #+lispworks\n    (mp:semaphore-acquire *semaphore*)\n\n    (with-open-stream (input (unwind-protect\n                                  (http-request\n                                   url\n                                   :want-stream t\n                                   :engine engine)\n                               #+lispworks\n                               (mp:semaphore-release *semaphore*)))\n      (uiop:copy-stream-to-stream\n       input\n       output\n       :element-type '(unsigned-byte 8)))))\n\n(defun trim-/ (name)\n  (cl-ppcre:regex-replace \"^/*\" name \"\"))\n\n(defun download-batch-of-screenshots (screenshots &key output engine)\n  (loop for screenshot in screenshots\n        do\n           (let ((screenshot-name (trim-/ (dto:screenshot-name screenshot))))\n             (unless (safe-name-p screenshot-name)\n               (error 'unsafe-screenshot-name))\n             (let ((output\n                     (format nil \"~a.png\"\n                             (path:catfile output screenshot-name))))\n               (log:info \"Saving: ~a\" output)\n               (let ((url (dto:screenshot-url screenshot)))\n                 (unless (str:emptyp (uiop:getenv \"SCREENSHOTBOT_DEBUG_IGNORE_CDN\"))\n                   (log:warn \"Ignoring CDN!\")\n                   (setf url (str:replace-all \"cdn.screenshotbot.io\" \"screenshotbot.io\" url)))\n                 (log:debug \"URL is: ~a\" url)\n                 (download-url\n                  url\n                  output\n                  :engine engine))))))\n\n(defun %save-run (run &key output)\n  (let ((screenshots (dto:run-screenshots run)))\n    (let* ((batches (make-batches screenshots :batch-size (ceiling (length screenshots) 3)))\n           (engine *download-engine*)\n           (finish-count 0)\n           (e nil)\n           (threads (loop for batch in batches\n                          collect\n                          (let ((batch batch))\n                            (make-thread\n                             (lambda ()\n                               (handler-bind ((error (lambda (%e)\n                                                       (setf e %e))))\n                                 (with-reused-ssl (engine)\n                                  (download-batch-of-screenshots batch :output output\n                                                                       :engine engine)))\n                               (bt:with-lock-held (*lock*)\n                                 (incf finish-count))))))))\n     (mapc #'bt:join-thread threads)\n      (unless (eql finish-count (length threads))\n        (cond\n          (e\n           (error e))\n          (t\n           (error \"Some threads failed to complete and we don't know why\")))))))\n\n(defun runs-for-commit (api-context commit)\n  (decode-json\n   (request\n    api-context\n    (format nil \"/api/run?commit=~a\" commit)\n    :method :get\n    :decode-response nil\n    :parameters `((\"commit\" . ,commit)))\n   `(:list dto:run)))\n\n(defun save-runs-from-commit (api-context commit &key output)\n  (let ((seen (make-hash-table :test #'equal)))\n    (let ((runs (runs-for-commit api-context commit)))\n      (loop for run in runs\n            if (not (gethash (dto:run-channel run) seen))\n              do\n                 (setf (gethash (dto:run-channel run) seen) t)\n                 (save-run\n                  api-context\n                  (dto:run-id run) ;; The run doesn't have screenshots, so we need to refetch it.\n                  :output (path:catdir output (str:ensure-suffix \"/\" (dto:run-channel run))))))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/finalized-commit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/finalized-commit\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/git\n                #:current-commit\n                #:git-repo)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:remote-version)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:register-root-command\n                #:*root-commands*\n                #:with-clingon-api-context)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:version-check #:screenshotbot/sdk/version-check)\n                    (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/finalized-commit)\n\n(defun finalize-commit (api-context &key commit)\n  (when (< (remote-version api-context) 7)\n    (error \"The remote Screenshotbot server does not support --mark-unchanged-from\"))\n\n  (let* ((repo (git-repo))\n         (commit (or\n                  commit\n                  (current-commit repo))))\n    (sdk:request api-context\n                 \"/api/finalized-commit\"\n                 :method :post\n                 :content\n                 (make-instance 'dto:finalized-commit\n                                :commit commit))\n    (log:info \"Finalized runs for commit ~a\" commit)))\n\n(defun finalize-commit/command ()\n  (clingon:make-command\n   :name \"finalize\"\n   :description \"'Finalize' a commit. This tells Screenshotbot that there will no more runs associated with this commit (even if the same commit exists in multiple forked repositories in the organization\"\n   :options (list\n             (make-option\n              :string\n              :long-name \"commit\"\n              :initial-value nil\n              :description \"The commit to finalize, but will default to the current commit on the current repository\"\n              :key :commit))\n   :handler (lambda (cmd)\n              (with-clingon-api-context (api-context cmd)\n                (finalize-commit api-context\n                                 :commit (getopt cmd :commit))))))\n\n(register-root-command 'finalize-commit/command)\n"
  },
  {
    "path": "src/screenshotbot/sdk/firebase.lisp",
    "content": ";; -*- encoding: utf-8 -*-\n(defpackage :screenshotbot/sdk/firebase\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags #:screenshotbot/sdk/flags))\n  (:export\n   #:parse-firebase-output\n   #:with-firebase-output))\n(in-package :screenshotbot/sdk/firebase)\n\n(defclass firebase-output ()\n  ((bucket\n    :initarg :bucket\n    :reader firebase-output-bucket)\n   (location :initarg :location\n             :reader firebase-output-location)\n   (test-axis :initarg :test-axis\n              :initform nil\n              :reader firebase-output-test-axis)))\n\n(defun %parse-test-axis-line (line)\n  ;; This is not the same as |, this is a different UTF character! To\n  ;; be safe, we're pulling the character directly from the string, in\n  ;; case it changes based on how things are encoded.\n  (let ((parts (str:split (elt line 0) line)))\n    (unless (eql 5 (length parts))\n      (error \"Could not figure out test axis in: `~a`. It's possible the FTL output has changed, please reach out to support@screenshotbot.io\" line))\n    (str:trim (elt parts 2))))\n\n(defun %parse-test-axis (output)\n  (loop for (line . rest) on (str:lines output)\n        if (and\n            (str:containsp \"OUTCOME\" line)\n            (str:containsp \"TEST_AXIS_VALUE\" line))\n          return\n             (%parse-test-axis-line (cadr rest))))\n\n\n(defun parse-firebase-output (output)\n  (flet ((oops ()\n           (error \"Could not parse the firebase output file! Please reach out to support@screenshotbot.io\")))\n   (let ((lines (str:lines output)))\n     (loop for line in lines\n           if (str:containsp \"Raw results will be stored in\" line)\n             return\n             (multiple-value-bind (full parts)\n                 (cl-ppcre:scan-to-strings\n                  \"\\\\[https://console.developers.google.com/storage/browser/(.*)/(.*)/\\\\]\"\n                  line)\n               (unless full\n                 (oops))\n               (make-instance 'firebase-output\n                               :bucket (elt parts 0)\n                               :location (elt parts 1)\n                               :test-axis (%parse-test-axis output)))\n           finally\n              (oops)))))\n\n(defun find-file (dir file-name)\n  (cond\n    ((and\n      (equal (pathname-name file-name) (pathname-name dir))\n      (equal (pathname-type file-name) (pathname-type dir)))\n     dir)\n    (t\n     (loop for child in (fad:list-directory dir)\n           for artifact = (find-file child file-name)\n           if artifact\n             return artifact))))\n\n(auto-restart:with-auto-restart (:retries 3)\n  (defun gcloud-storage-cp (src dest)\n    (uiop:run-program\n     (list \"gcloud\" \"storage\" \"cp\"\n           ;; Hopefully the --no-clobber allows us to retry the download safely if\n           ;; some objects fail. See T1635\n           \"--no-clobber\"\n           \"-r\"\n           src\n           (namestring dest))\n     :output *standard-output*\n     :error-output *standard-output*)))\n\n\n(def-easy-macro with-firebase-output (filename &fn fn)\n  (let ((firebase-output (parse-firebase-output (uiop:read-file-string filename))))\n    (tmpdir:with-tmpdir (dir)\n      (let ((cloud-location (format nil \"gs://~a/~a/~a/artifacts/\"\n                                    (firebase-output-bucket firebase-output)\n                                    (firebase-output-location firebase-output)\n                                    (firebase-output-test-axis firebase-output))))\n        (log:info \"Downloading screenshots from Google Cloud: ~a\" cloud-location)\n        (gcloud-storage-cp cloud-location (namestring dir))\n        (log:info \"Cleaning up the Google cloud directory before we continue\")\n        #+nil\n        (uiop:run-program\n         (list \"gcloud\" \"alpha\" \"storage\" \"rm\" \"-r\"\n               cloud-location)\n         :output *standard-output*\n         :error-output *standard-output*))\n\n      (log:info \"Downloaded screenshots, processing it locally\")\n      (let ((flags:*metadata*\n              (remove-if #'null\n               (list\n                (find-file dir \"metadata.json\")\n                (find-file dir \"metadata_compose.json\")))))\n        (funcall fn)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/braft-expected.log",
    "content": "ab0017f0b98d429138d83a04d3ed351197d671a9 9d9a17db9cc4b35d9493e5ac3f7ebb81c0e8daa8 1bd6c491a8cb115147fd1ff12120f56e7dc54094\n9d9a17db9cc4b35d9493e5ac3f7ebb81c0e8daa8 921b00c0e0cbc96926b1d0230aedfad801f99b69 2c78931c5874ca69246e0a42eb45541d652942a0\n921b00c0e0cbc96926b1d0230aedfad801f99b69 bc9fb052868560322a9b833c8179bb4d1a1f7dc5 b9e12939a03506652180d5541893d7c296b6cb46\n1bd6c491a8cb115147fd1ff12120f56e7dc54094 bc9fb052868560322a9b833c8179bb4d1a1f7dc5\n2c78931c5874ca69246e0a42eb45541d652942a0 bc9fb052868560322a9b833c8179bb4d1a1f7dc5\nb9e12939a03506652180d5541893d7c296b6cb46 bc9fb052868560322a9b833c8179bb4d1a1f7dc5\nbc9fb052868560322a9b833c8179bb4d1a1f7dc5 b37c610039aa34d6df2e5bda12f830003561b08b c5a306817f3976f64da9a0a9d5112164392e028d\nc5a306817f3976f64da9a0a9d5112164392e028d b37c610039aa34d6df2e5bda12f830003561b08b\nb37c610039aa34d6df2e5bda12f830003561b08b ae887a0fcb82c664825331151c096403f49bf3f0 7fd7b80738014d1a5cf97486be7e6b90dbb2e0c3\nae887a0fcb82c664825331151c096403f49bf3f0 080689bd54c3231921d83351e23012c2e9f44b0e bd2387a7deba42a7a5d039f833a4e70f42015ea1\n"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/braft.log",
    "content": "commit ab0017f0b98d429138d83a04d3ed351197d671a9\ntree d94321f16c9b744e12af0dce52bf353a43177e6f\nparent 9d9a17db9cc4b35d9493e5ac3f7ebb81c0e8daa8\nparent 1bd6c491a8cb115147fd1ff12120f56e7dc54094\nauthor doc001 <wenffie@gmail.com> 1729840929 +0800\ncommitter GitHub <noreply@github.com> 1729840929 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJnG0chCRC1aQ7uu5UhlAAALl4QAGP53RvP5XzrhxrLxhS7SvCK\n iGLn7DTp60dvYjpvwf3/xF/EJ16dFgaJkcFwVy6nzOUMuES1+/LM6Lgpzoxu+bWL\n Oi/pcwaKJ989ettgBbtRatlW77fsq7DNjR/BW23C1x/dCFQGs8Ht2xfkUBnL1fby\n ISxBWn1KWQPvS2kUZka+3zTCXLGwxymCYFiYv7zbQ+sZiq16uDzT8UZDNngRSzPL\n cWcjuOKKPKx2xDp+qGoJKRKn6ci8MHOFoZdkr/8RX36w708Z3CFIipNi6VtmM8y4\n iPoX8DoErQGQUX/umj3JL/Z0uC3znODqJuCOOvTlwWHpz6zXPdglvX3VtIUcVsT8\n 7Jc4suRS4IyXlOFa6mWqccormWAQFXqf8cBJHETs/UFiV1htjwb9w3GQjYmwL9xb\n eQwajRGIXnXaac+TCt5OMZ25OcNr8qB/uLAxEw3TdRpd9SDlXLSj8indIj9V3rMg\n DQh6sx+IPYNu0QDFBj0mT+2uAJJYzXOzlB2Jkbnt5HJlOq3/IDlwrM/6BxEz1C/j\n hhKBXtSv8SY8RQNlpbjIFyYjaDmIwetj9lZBVioNR2vPN5U85eCouzY5NtlM06ps\n vKApnYw7xJkM8BChCWrdIt35s1R8lkHRFefUCEme0dniVlPij9Xpio+CXLBirx/x\n 6vyEL5EtzNYyN0hLa0Nj\n =iH1D\n -----END PGP SIGNATURE-----\n \n\n    Merge pull request #475 from thweetkomputer/master\n    \n    fix typo in route_table\n\ncommit 9d9a17db9cc4b35d9493e5ac3f7ebb81c0e8daa8\ntree 4e24078e1a119d4938188a98f39aa8fddc172210\nparent 921b00c0e0cbc96926b1d0230aedfad801f99b69\nparent 2c78931c5874ca69246e0a42eb45541d652942a0\nauthor doc001 <wenffie@gmail.com> 1729839361 +0800\ncommitter GitHub <noreply@github.com> 1729839361 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJnG0EBCRC1aQ7uu5UhlAAADFoQABty+O/W6pxTuGwTw4LxDlmc\n c3EBdGL1a72xeJsvlpipI3xcM2XzvXzcvvku7PQ8XNn0oFgo6Nbw0Ud7I/j87MGv\n aBqsMXPjem6ZlnkaVXTrvJYqAxxfr36pbDW5D29mqBOH9tt/KbzSzpwJk0WkFeGU\n ip5Yxzj+3c/4xcPIrp/4n5YtqNDcxvVEwjjSAiXzXhj4WKcqPEmRt9me9khUWlKq\n qManjkHnYIoBoXqgqZ6bq2VZjUT1BSQ7v9NFrUA153WNKmwW+/rKhrFydD6v6k4T\n wzLyd6wUde7wpn0fxQ5dI8p9s56XexVu2wokw1/6akdIpFfOe6fE0dDvuQudkel0\n 1d39LoEqLEj387I4J+mEKLpIyX/TWOcCgJROcktjdJvbQ7QtBZ4kD6iJx7sYvsY7\n LWswqVZNqw9rYZ1Os+lDAXvPVEdaaeA3Tf5Y3lxZgxJ4otxPl+Ma7mAz2nz40R0k\n vtDSSTyE51rKvSs/twyjV9vWw19+Bp7aRdcjzAyG3wHMLgyiYlgoejYHKE9+6euQ\n zZ1f0vWxKwTJ+oTNzjAlsI9Qqfa7iH/SLhr6/sH9fFto/B1WHg6falPicQOgZnRG\n 76Fhl/Ikqd6WJ7zG1ZX1eBWjCAtMMsY8ydQ1tw8qJKufTwE7oG2UEVBes5BRm+N0\n VY+DwrPmh45RZa29/Rso\n =YZYM\n -----END PGP SIGNATURE-----\n \n\n    Merge pull request #472 from ideal/master\n    \n    fix typo in example\n\ncommit 921b00c0e0cbc96926b1d0230aedfad801f99b69\ntree 8e2f85ec5652b58695b2702f61f10fc66fab7fee\nparent bc9fb052868560322a9b833c8179bb4d1a1f7dc5\nparent b9e12939a03506652180d5541893d7c296b6cb46\nauthor doc001 <wenffie@gmail.com> 1729839328 +0800\ncommitter GitHub <noreply@github.com> 1729839328 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJnG0DgCRC1aQ7uu5UhlAAA7DcQACAD4FmYEXzK+CHyfR5GOJHI\n KKIhUpgFun2afrGiB7orHw0jjZCadcYzHT3ZgxTGXgh2lhnoFBBHY0xHn6gOtMzV\n HWqt+vTSk34GWnYvJFgeKpvFwNyCx7i29bRclUpkqmXyLttiF2u2rvAPW+Ba15vq\n mzDYi7Ql0sJj6HgUuOTuFwNCCjV0qphMGD2ulpWlqsfEyUdcqzI+kcHGjkEDr4nD\n fGJVSN7cMolpBxEMoPwCjpEio9cZg8DuuWsqpItHfCEvUdfhFIkWH0fbmMT+0p57\n Jc+VuRgzgEHK6PuPKUEwTPbIxaLWogWPI4eWTKjj+0xbgDWeiFOyIhZ686xcHj0P\n 8PGQ6PhWHLasuFSqj2m8U0V1QsHxegbkKvz+UPTyo/3wfusc4zmNJpqClZ2P8rjb\n 5lQiRNb50Mlp5jCG/WjVLIdTKhj6vBu9NwhD6V6/ON7de6Wpu9iuNoMk3e9q9HhT\n Cif3w8n3kjEZ0AFgsxALuR6gYAa7YUbsfaYHeO8n61qQP+UD3NOPIRu6ocrvKE2w\n 2VzfsWC+FW29u1ShOoeI30A8gU+a4H2MJ3OOAUx3IhVwT/XfOYi+jaqTT8w9hbhw\n fk30hCfhJ/VjqOLIOSIQVR5+b+iBFLdnGJbgqO2X+eqr4sczralsdVouBYDuvo2x\n DR+fMAh50pESCPqW23j7\n =5Le6\n -----END PGP SIGNATURE-----\n \n\n    Merge pull request #471 from BusyJay/patch-1\n    \n    fix use after free in handle_timeout_now_request\n\ncommit 1bd6c491a8cb115147fd1ff12120f56e7dc54094\ntree 95107a58dea5547328767fc4d6edc231cd1f904f\nparent bc9fb052868560322a9b833c8179bb4d1a1f7dc5\nauthor Chen Zhao <ch3nzhao@gmail.com> 1729578461 +1100\ncommitter Chen Zhao <ch3nzhao@gmail.com> 1729578461 +1100\n\n    fix typo in route_table.cpp\n\ncommit 2c78931c5874ca69246e0a42eb45541d652942a0\ntree 6846b0a69d7095c1463b08016f98e91cfc1392bf\nparent bc9fb052868560322a9b833c8179bb4d1a1f7dc5\nauthor ideal <idealities@gmail.com> 1725332794 +0800\ncommitter ideal <idealities@gmail.com> 1725332794 +0800\n\n    Fix typo\n\ncommit b9e12939a03506652180d5541893d7c296b6cb46\ntree 8e2f85ec5652b58695b2702f61f10fc66fab7fee\nparent bc9fb052868560322a9b833c8179bb4d1a1f7dc5\nauthor Jay <BusyJay@users.noreply.github.com> 1724143389 +0800\ncommitter GitHub <noreply@github.com> 1724143389 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJmxFcdCRC1aQ7uu5UhlAAAko4QAEnYn9l0l41JcH/jxGC98OPq\n +UG2FqfDNlVYxelEu8DY6uN4VUiaFr+7YzzyWLztc4UnCvvMwDPCwwrKdEtp8y9U\n X2jSV3E5LViJBYiB+em0wN0z9CEpKDJo5TsCGZL6g+5//HdQsdmBcLfUPb73YHR7\n QHWqyqURF+uqBsTxKxpAcwcJ0c/ykbeLesEJALkz4PzVNW3PKppK0maSlTg4/WnU\n x22MP2GNQd5TaeFI74NEf0gCVMSHc+TYYk+ScYBMKfAHFDsAoNjsU2jWHorLDM91\n 7slV2uCwzq6gUMAJKZj7lJfnemXhoe0rCYb3zVeY1YNp/nIJowDbp09ayBq9qzbT\n YpSF27TF+a5L6PjS5Mp7WYOLTK2TLfdDwtWe0anyp1G7siDIYKH+OU2OgQaHksAp\n 43xZq86yeUVpvEFg0QbrJx5Y0qzRT1E57s/HAIjUuBbwiwyx//X6LmQjzqi+H0fw\n ymBTNGDr7RvLCfbV6QfvH0nwJn5bhKX9G0CZTVNuVzVRjMZu/mWIINjKxCP3f+Mt\n Cle8q4bujKlyxl7z9raIXlG73Yfo0Kg2C5QnoC7N+kfx6jC4bLRkmVwUi7fRQoMb\n qO99VLgQQIRaWrAuwu+PihzrfhTfBxb5iRppNdMEIi5hacq8x70E37kOKCN9x+p1\n qhfKYONMk99njCSMyV9A\n =RDOY\n -----END PGP SIGNATURE-----\n \n\n    fix use after free in handle_timeout_now_request\n    \n    Closure maybe invoked before `elect_self`, which will free request and cause UAF.\n\ncommit bc9fb052868560322a9b833c8179bb4d1a1f7dc5\ntree 92e05e8c9a851b89c095e908cc57bb5ed7dd9a38\nparent b37c610039aa34d6df2e5bda12f830003561b08b\nparent c5a306817f3976f64da9a0a9d5112164392e028d\nauthor doc001 <wenffie@gmail.com> 1718982014 +0800\ncommitter GitHub <noreply@github.com> 1718982014 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJmdZV+CRC1aQ7uu5UhlAAAkXsQABD3S2nSmBc5K9RCnq2dxh+U\n UktHC+XozyBTM8PrDpi7QGRnwUB/EtdSPL/CY9rgKAj4CqDFcS+93K1tdndoMRT1\n MU8JXSgQJHGKZw3CD6HjUQEm/hb/QwyVxVoX9tgajPGOkVGit9oEUvcFzc9I+832\n NgI65cyRwbDU8dD5hsORAJvYILpnduZk72lAwl9xxdI8WgJMiQ4OKjV1asMAkTQM\n +RrliIWtSMAOJd1AguEwf6U5IyXLPS8rdF995pl7GhbYh28Q9T0cbOhm1crSVs4Z\n KcB7yfdb+/RxxQAGqOxxhrzhAVOf/V9npOm34lfq45FgmJCZWs1lw0Q2Pyl7Seuo\n 1AYChlny92ZiJW84NBNqSCURcurn5bpdUFb/wLxuFQEXU/4JE0hxggwlCMIwlgXZ\n i0A4Nu2R2y2LDBqqNxyG8XQ4hZQD6DaQ3IXJqyqdS7Lwf+GvN389pbwlGydkmh+R\n f9Wkbgk1Ut6j8KbF9LAZ5Ja9x3aOsS+3TAqVJJf6HEbHCmO8ZbSfUvZ1LmdlaJqe\n +UoOZXKvKkBsFqwFe/opD3HntL2GRNkORoV6ZAEUYyOyQRibFNhp+hkV3YiveZdn\n OjR55VhIHd/tr/oG2q+PNJR3+60wimXdUEpEX0xjtfWfogUBG0cKJGbk4kRhmUFs\n 94B3b8ETdbtdSZ+Iknf3\n =2bxP\n -----END PGP SIGNATURE-----\n \n\n    Merge pull request #458 from walterzhaoJR/fix-mem-leak-issues-454\n    \n    fix mem leak in Replicator::_prepare_entry\n\ncommit c5a306817f3976f64da9a0a9d5112164392e028d\ntree 92e05e8c9a851b89c095e908cc57bb5ed7dd9a38\nparent b37c610039aa34d6df2e5bda12f830003561b08b\nauthor walterzhaoJR <519362600@qq.com> 1718964713 +0800\ncommitter walterzhaoJR <519362600@qq.com> 1718964713 +0800\n\n    fix mem leak in Replicator::_prepare_entry\n    detail:https://github.com/baidu/braft/issues/454\n\ncommit b37c610039aa34d6df2e5bda12f830003561b08b\ntree e85bcb194795f98ff30c437b48f0a31ac3eef4e3\nparent ae887a0fcb82c664825331151c096403f49bf3f0\nparent 7fd7b80738014d1a5cf97486be7e6b90dbb2e0c3\nauthor doc001 <wenffie@gmail.com> 1708504591 +0800\ncommitter GitHub <noreply@github.com> 1708504591 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJl1bYPCRC1aQ7uu5UhlAAA2MQQAHpIDnVbg9Vpp1iyXstgobsL\n B08Fa4FuQ+rcPuAz1VuVGja5feLd+lniIEM8rqUIfSj6GPjDCVgiJvi+tbV9T5PQ\n aXxifzQEPXse+HHSE34rtCanwELC238KUW2L/2KIQe/oMDGX4Ny/XDYqzb1Fgccu\n QlShMsReY+gThRwi80T6GLSS+uDz1KK1Y06HzIW+KTp9GfH0m2CefQdxYwiod8+9\n EWU3Z1R6pvPn5h9yUjcp/hjvQs4fZfqukNz+cBclHDpbzGtyyF/sPGLgXoT0P6tc\n 7DhhP7nOLRZBPut5eRm0linUCuuDzOzi9P+zNIIXSJI4Zpn9EhrbPMQ53rk+v4vh\n VQrQSAe8Bh7YH72X7IH7fM//UKbyzN+TdXuXT2TC2KMcQt1nw2149QDht9POLxsh\n cqV2Fg+oAbPdDhO9UOXkKhaJC1BRksYQMifulkTepL7qHKbI2TAn8v8kaVFw+98/\n 312S9wUue4yoaa1fBvav8b0JsaYtmhWZ24pwDAbUOIo2kJBxqSF1UC5BRHSYFXkH\n 1iU8VZf6zksP777YXz21fgt9xZ2AjfCBbPRP+km/u9ibQ0bjkNXQcfAEPPWZqezP\n QaDDAuPPE5q3cI+Gr3v3NrTIuK2Z07SUaR6/FmQROXvO94L5lvedTRJSIed5jg+/\n 1VJ/e5JOdXlnBD1x2vOi\n =o6/z\n -----END PGP SIGNATURE-----\n \n\n    Merge pull request #436 from lhsoft/recover_log_from_corrupt\n    \n    recover log from data corrupt\n\ncommit ae887a0fcb82c664825331151c096403f49bf3f0\ntree ceb98aa5b8a762daf183a2211169f94a8d18a5e6\nparent 080689bd54c3231921d83351e23012c2e9f44b0e\nparent bd2387a7deba42a7a5d039f833a4e70f42015ea1\nauthor doc001 <wenffie@gmail.com> 1708504513 +0800\ncommitter GitHub <noreply@github.com> 1708504513 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsFcBAABCAAQBQJl1bXBCRC1aQ7uu5UhlAAArVEQAGM/U6mzq7P8g5Rd1olgF72m\n oJ2DK/LIXdjG21Vvs5t0VS1aiHQ5rkgNXgWivQV/UybziDEDGEhLwoabRikXzYBc\n R11ysPLqAE+9pnvm/lPQLeevKnxhOUBVHL//zxyPy8DcGxkDvKJWeN1KgVzarBu9\n p8lIfSi6QtDbh/5pRQ0+1q1ls8mHFF57HzP1nnkg6XxEbUZPt5nYHFqAbfIFiKQC\n GWuRBSz+hFBtx+JIi3U7gUPIOH23NEZBBMdACQJeXsqJYOl3iC7I6irIchjXucEz\n 2xzW26hro0g9DqIgQoMfQfGkdXnR/oYwkF8XLLnSWRLKX+9TIE1zffiF5FhIwJK0\n cc22gzqp12qfJfGd5Ip8AJJh/JMtmjnbma8uJi05pULzpNdQ+cckPEh8jzgTeD7g\n sxrDDpDb2vF4I4Xb5W50e+E2ue3xqpRtjpQPbnSGo35AnvP6lJJYkctGV3/HmUXM\n 66BJr5t7MR2+m31g9GRRUJNMdXzErVmpQgw5wwj8TH5SRXdzMfzRXpr/dNlKpT16\n QGZfJbanpjUc9Ua+KSeEGH+Xthpn0R2TcNNqDOELretNT0Cmq1Ptcq3Jw5ej2Zqm\n v9i6Cpb4op3zgTAY+M0Pn9TI2v+MOSUlYG4drvZHpRKwlhDGlHhSJw5mmBDXSV6m\n KgsptQin73X53OVEUGV2\n =3TBC\n -----END PGP SIGNATURE-----\n \n\n    Merge pull request #437 from lhsoft/sync_conf_log\n    \n    sync log immediately when there is configuration\n"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/result12.xcresult/Data/data.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw==",
    "content": "[]"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/result12.xcresult/Data/data.0~cGShflEJBARoixS1GsvQECcSXDe_RToOUASg4dt2evSJUrPo4DGe9wi_KSE_LITnN49kLhLYwqADoFxwdO4IkQ==",
    "content": "[{\"name\":\"testmanagerd.log\",\"type\":1}]"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/result12.xcresult/Data/data.0~kFpRFrgGEeBh9uvA-3MuJgNTSwW13Ow2V_3URBYMSH-0k4ZJ_Ox5K-g1rDuSOJ-gyMo87MiandTZsPmy75OOjA==",
    "content": "[{\"name\":\"testmanagerd.log\",\"type\":1}]"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/result12.xcresult/Data/data.0~nqtjyxgscHiZYdt4Y8ICuh3SgKydPuah7I-GxreD8fGSkhEUXa0St_rRnKDM_k7wTDpMNkr5aAZxz0YpyZaoVA==",
    "content": "[{\"name\":\"testmanagerd.log\",\"type\":1}]"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/result12.xcresult/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>dateCreated</key>\n\t<date>2025-08-28T22:47:30Z</date>\n\t<key>externalLocations</key>\n\t<array/>\n\t<key>rootId</key>\n\t<dict>\n\t\t<key>hash</key>\n\t\t<string>0~N2rVyxSAcn-Jy_KSWQLN2uvz4gRzBEedrqckuRVRMEnKnVoMSwJikfRp8si5LOPil5bxE0dBeX2wEKQXubsUMw==</string>\n\t</dict>\n\t<key>storage</key>\n\t<dict>\n\t\t<key>backend</key>\n\t\t<string>fileBacked2</string>\n\t\t<key>compression</key>\n\t\t<string>standard</string>\n\t</dict>\n\t<key>version</key>\n\t<dict>\n\t\t<key>major</key>\n\t\t<integer>3</integer>\n\t\t<key>minor</key>\n\t\t<integer>53</integer>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/teamcity/teamcity.build.parameters",
    "content": "#TeamCity build properties without 'system.' prefix\n#Sun Jul 14 19:39:34 BST 2024\n# Arnold: I trimmed some values\nagent.home.dir=/opt/buildagent\nagent.name=ip_172.17.0.1\nagent.ownPort=9090\nagent.work.dir=/opt/buildagent/work\nbuild.number=9\nbuild.vcs.number=0988f3e38dc92a2689d4d9e93e54e97cac7e701e\nbuild.vcs.number.1=0988f3e38dc92a2689d4d9e93e54e97cac7e701e\nbuild.vcs.number.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster=0988f3e38dc92a2689d4d9e93e54e97cac7e701e\njava.io.tmpdir=/opt/buildagent/temp/buildTmp\nteamcity.agent.cpuBenchmark=1480\nteamcity.agent.dotnet.agent_url=http\\://localhost\\:9090/RPC2\nteamcity.agent.dotnet.build_id=9\nteamcity.auth.password=xxxxxxxx\nteamcity.auth.userId=TeamCityBuildId\\=9\nteamcity.build.changedFiles.file=/opt/buildagent/temp/buildTmp/teamcity.changedFiles.txt\nteamcity.build.checkoutDir=/opt/buildagent/work/e4d122abb5d071c7\nteamcity.build.id=9\nteamcity.build.properties.file=/opt/buildagent/temp/buildTmp/teamcity.build.parameters\nteamcity.build.properties.file.checksum=/opt/buildagent/temp/buildTmp/teamcity.build.parameters.checksum\nteamcity.build.tempDir=/opt/buildagent/temp/buildTmp\nteamcity.build.workingDir=/opt/buildagent/work/e4d122abb5d071c7\nteamcity.buildConfName=Build\nteamcity.buildType.id=FastExample_Build\nteamcity.configuration.properties.file=/opt/buildagent/temp/buildTmp/teamcity.config.parameters\nteamcity.projectName=Fast Example\nteamcity.runner.properties.file=/opt/buildagent/temp/buildTmp/teamcity.runner.parameters\nteamcity.tests.recentlyFailedTests.file=/opt/buildagent/temp/buildTmp/teamcity.testsToRunFirst.txt\nteamcity.version=2024.03.3 (build 156364)\n"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/teamcity/teamcity.config.parameters",
    "content": "#TeamCity configuration parameters for build with id 9\n#Sun Jul 14 19:39:34 BST 2024\n # Arnold: I trimmed some values\nbuild.counter=9\n\n# newlines on purpose:\n\n\nbuild.number=9\nbuild.vcs.number=0988f3e38dc92a2689d4d9e93e54e97cac7e701e\nbuild.vcs.number.1=0988f3e38dc92a2689d4d9e93e54e97cac7e701e\nbuild.vcs.number.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster=0988f3e38dc92a2689d4d9e93e54e97cac7e701e\ndocker.version=24.0.9\nDotNetCLI=6.0.413\nDotNetCLI_Path=/usr/share/dotnet/dotnet\nDotNetCoreRuntime6.0.21_Path=/usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.21\nDotNetCoreRuntime6.0_Path=/usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.21\nDotNetCoreSDK6.0.413_Path=/usr/share/dotnet/sdk/6.0.413\nDotNetCoreSDK6.0_Path=/usr/share/dotnet/sdk/6.0.413\nDotNetCredentialProvider1.0.0_Path=/opt/buildagent/plugins/nuget-agent/bin/credential-plugin/netcoreapp1.0/CredentialProvider.TeamCity.dll\nDotNetCredentialProvider2.0.0_Path=/opt/buildagent/plugins/nuget-agent/bin/credential-plugin/netcoreapp2.0/CredentialProvider.TeamCity.dll\nDotNetCredentialProvider3.0.0_Path=/opt/buildagent/plugins/nuget-agent/bin/credential-plugin/netcoreapp3.0/CredentialProvider.TeamCity.dll\nDotNetCredentialProvider4.0.0_Path=/opt/buildagent/plugins/nuget-agent/bin/credential-plugin/net46/CredentialProvider.TeamCity.exe\nDotNetCredentialProvider5.0.0_Path=/opt/buildagent/plugins/nuget-agent/bin/credential-plugin/net5.0/CredentialProvider.TeamCity.dll\nDotNetCredentialProvider6.0.0_Path=/opt/buildagent/plugins/nuget-agent/bin/credential-plugin/net6.0/CredentialProvider.TeamCity.dll\npython2.executable=/usr/bin/python2\npython3.executable=/usr/bin/python3\npythonAny.executable=/usr/bin/python3\nteamcity.agent.hardware.cpuCount=32\nteamcity.agent.hardware.memorySizeMb=63440\nteamcity.agent.home.dir=/opt/buildagent\nteamcity.agent.hostname=c89aeee624a9\nteamcity.agent.internal.passwords.values=xxxxxx\nteamcity.agent.jvm.file.encoding=UTF-8\nteamcity.agent.jvm.file.separator=/\nteamcity.agent.jvm.java.home=/opt/java/openjdk\nteamcity.agent.jvm.os.arch=amd64\nteamcity.agent.jvm.os.name=Linux\nteamcity.agent.jvm.os.version=6.1.0-21-amd64\nteamcity.agent.jvm.path.separator=\\:\nteamcity.agent.jvm.specification=17\nteamcity.agent.jvm.user.country=US\nteamcity.agent.jvm.user.home=/home/buildagent\nteamcity.agent.jvm.user.language=en\nteamcity.agent.jvm.user.name=buildagent\nteamcity.agent.jvm.user.timezone=Europe/London\nteamcity.agent.jvm.vendor=Amazon.com Inc.\nteamcity.agent.jvm.version=17.0.7\nteamcity.agent.launcher.version=2024.03-156364\nteamcity.agent.name=ip_172.17.0.1\nteamcity.agent.os.arch.bits=64\nteamcity.agent.ownPort=9090\nteamcity.agent.protocol=polling\nteamcity.agent.tools.dir=/opt/buildagent/tools\nteamcity.agent.work.dir=/opt/buildagent/work\nteamcity.agent.work.dir.freeSpaceMb=32106\nteamcity.build.branch=master\nteamcity.build.branch.is_default=true\nteamcity.build.checkoutDir=/opt/buildagent/work/e4d122abb5d071c7\nteamcity.build.default.checkoutDir=e4d122abb5d071c7\nteamcity.build.id=9\nteamcity.build.triggeredBy=arnold\nteamcity.build.triggeredBy.username=arnold\nteamcity.build.vcs.branch.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster=refs/heads/master\nteamcity.dotCover.home=%teamcity.tool.JetBrains.dotCover.CommandLineTools.bundled%\nteamcity.git.build.vcs.branch.https___github_com_tdrhq_fast_example_git_refs_heads_master=refs/heads/master\nteamcity.git.ssh.version=OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\nteamcity.gitLfs.version=2.9.2\nteamcity.hg.agent.path=hg\nteamcity.hg.version=5.3.1\nteamcity.internal.npm.registry.count=0\nteamcity.internal.perfmon.runtime.enabled=true\nteamcity.nuget.feed.api.key=xxxxxx\nteamcity.perfmon.feature.enabled=true\nteamcity.project.id=FastExample\nteamcity.serverUrl=xxxxxxx\nteamcity.tool.ant-net-tasks=/opt/buildagent/tools/ant-net-tasks\nteamcity.tool.dotCover=%teamcity.tool.JetBrains.dotCover.CommandLineTools.bundled%\nteamcity.tool.gant=/opt/buildagent/tools/gant\nteamcity.tool.intellij.DEFAULT=%teamcity.tool.idea%\nteamcity.tool.jacoco.DEFAULT=%teamcity.tool.jacoco.0.7.5%\nteamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT=%teamcity.tool.JetBrains.dotCover.CommandLineTools.bundled%\nteamcity.tool.jetbrains.resharper-clt.DEFAULT=%teamcity.tool.jetbrains.resharper-clt.bundled%\nteamcity.tool.jps=/opt/buildagent/tools/jps\nteamcity.tool.kotlin.compiler.DEFAULT=%teamcity.tool.kotlin.compiler.bundled%\nteamcity.tool.maven.AUTO=?maven.AUTO\nteamcity.tool.maven.DEFAULT=%teamcity.tool.maven3_9%\nteamcity.vault.supported=true\nvcsroot.agentCleanFilesPolicy=ALL_UNTRACKED\nvcsroot.agentCleanPolicy=ON_BRANCH_CHANGE\nvcsroot.authMethod=ANONYMOUS\nvcsroot.branch=refs/heads/master\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.agentCleanFilesPolicy=ALL_UNTRACKED\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.agentCleanPolicy=ON_BRANCH_CHANGE\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.authMethod=ANONYMOUS\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.branch=refs/heads/master\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.ignoreKnownHosts=true\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.submoduleCheckout=CHECKOUT\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.teamcity\\:branchSpec=refs/heads/*\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.url=https\\://github.com/tdrhq/fast-example.git\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.useAlternates=AUTO\nvcsroot.FastExample_HttpsGithubComTdrhqFastExampleGitRefsHeadsMaster.usernameStyle=USERID\nvcsroot.ignoreKnownHosts=true\nvcsroot.submoduleCheckout=CHECKOUT\nvcsroot.teamcity\\:branchSpec=refs/heads/*\nvcsroot.url=https\\://github.com/tdrhq/fast-example.git\nvcsroot.useAlternates=AUTO\nvcsroot.usernameStyle=USERID\n"
  },
  {
    "path": "src/screenshotbot/sdk/fixture/xcresults-attachments/manifest.json",
    "content": "[\n  {\n    \"attachments\" : [\n      {\n        \"configurationName\" : \"Test Scheme Action\",\n        \"deviceId\" : \"CE4A7935-27C3-4F4E-8F6C-769DBDCF4B12\",\n        \"deviceName\" : \"iPhone 16\",\n        \"exportedFileName\" : \"BF65B0E7-0E2B-4C2C-B067-4F3DE913F10E.png\",\n        \"isAssociatedWithFailure\" : false,\n        \"suggestedHumanReadableName\" : \"SnapshotTest_testExample.1_0_400D40FA-F90A-4990-85C2-F59E138AB122.png\",\n        \"timestamp\" : 1756421363.48\n      }\n    ],\n    \"testIdentifier\" : \"SimpleProjectTests/testExample()\",\n    \"testIdentifierURL\" : \"test://com.apple.xcode/SimpleProject/SimpleProjectTests/SimpleProjectTests/testExample\"\n  },\n  {\n    \"attachments\" : [\n      {\n        \"configurationName\" : \"Test Scheme Action\",\n        \"deviceId\" : \"CE4A7935-27C3-4F4E-8F6C-769DBDCF4B12\",\n        \"deviceName\" : \"iPhone 16\",\n        \"exportedFileName\" : \"CE85A51E-5309-4968-BB1A-333BA6E9E9C2.png\",\n        \"isAssociatedWithFailure\" : false,\n        \"suggestedHumanReadableName\" : \"SnapshotTest_testLoginViewSnapshot.1_0_9AFD3FA0-250A-4D8A-B601-32B2E46F6011.png\",\n        \"timestamp\" : 1756421364.301\n      }\n    ],\n    \"testIdentifier\" : \"SimpleProjectTests/testLoginViewSnapshot()\",\n    \"testIdentifierURL\" : \"test://com.apple.xcode/SimpleProject/SimpleProjectTests/SimpleProjectTests/testLoginViewSnapshot\"\n  },\n  {\n    \"attachments\" : [\n      {\n        \"arguments\" : [\n          \"1\",\n          \"1\"\n        ],\n        \"configurationName\" : \"Test Scheme Action\",\n        \"deviceId\" : \"CE4A7935-27C3-4F4E-8F6C-769DBDCF4B12\",\n        \"deviceName\" : \"iPhone 16\",\n        \"exportedFileName\" : \"407D5DE6-2912-4CD7-893A-1D201CB84CFD.png\",\n        \"isAssociatedWithFailure\" : false,\n        \"suggestedHumanReadableName\" : \"Launch Screen_0_975448AB-2D84-4E03-97C8-3B4F33383DC5.png\",\n        \"timestamp\" : 1756421409.515\n      },\n      {\n        \"arguments\" : [\n          \"4\",\n          \"1\"\n        ],\n        \"configurationName\" : \"Test Scheme Action\",\n        \"deviceId\" : \"CE4A7935-27C3-4F4E-8F6C-769DBDCF4B12\",\n        \"deviceName\" : \"iPhone 16\",\n        \"exportedFileName\" : \"FCE05EAF-FC0D-4355-94A7-30C6F1EDD229.png\",\n        \"isAssociatedWithFailure\" : false,\n        \"suggestedHumanReadableName\" : \"Launch Screen_0_11EEFAA4-B256-42AC-BD2C-4D1DDD957E82.png\",\n        \"timestamp\" : 1756421420.387\n      },\n      {\n        \"arguments\" : [\n          \"1\",\n          \"2\"\n        ],\n        \"configurationName\" : \"Test Scheme Action\",\n        \"deviceId\" : \"CE4A7935-27C3-4F4E-8F6C-769DBDCF4B12\",\n        \"deviceName\" : \"iPhone 16\",\n        \"exportedFileName\" : \"37DC35ED-9993-49AB-97B9-F21AE046D54C.png\",\n        \"isAssociatedWithFailure\" : false,\n        \"suggestedHumanReadableName\" : \"Launch Screen_0_A5E062EA-661E-4FEF-AC25-BF78568EB190.png\",\n        \"timestamp\" : 1756421431.197\n      },\n      {\n        \"arguments\" : [\n          \"2\",\n          \"4\"\n        ],\n        \"configurationName\" : \"Test Scheme Action\",\n        \"deviceId\" : \"CE4A7935-27C3-4F4E-8F6C-769DBDCF4B12\",\n        \"deviceName\" : \"iPhone 16\",\n        \"exportedFileName\" : \"64991D8E-D140-4DCD-9E8F-BB937EF0AFFC.png\",\n        \"isAssociatedWithFailure\" : false,\n        \"suggestedHumanReadableName\" : \"Launch Screen_0_A5026E8B-F398-444E-806A-F42C4D9C2B20.png\",\n        \"timestamp\" : 1756421439.224\n      }\n    ],\n    \"testIdentifier\" : \"SimpleProjectUITestsLaunchTests/testLaunch\",\n    \"testIdentifierURL\" : \"test://com.apple.xcode/SimpleProject/SimpleProjectUITests/SimpleProjectUITestsLaunchTests/testLaunch\"\n  }\n]"
  },
  {
    "path": "src/screenshotbot/sdk/flags.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/sdk/flags\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:com.google.flag\n                #:parse-string)\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:define-flag)\n  (:use-reexport\n   #:screenshotbot/sdk/common-flags)\n  (:export\n   #:*directory*\n   #:*verbose*\n   #:*org-defaults*\n   #:*create-github-issue*\n   #:*gitlab-merge-request-iid*\n   #:*channel*\n   #:*pull-request*\n   #:*branch*\n   #:*main-branch*\n   #:*repo-url*\n   #:*phabricator-diff-id*\n   #:*build-url*\n   #:*production*\n   #:*help*\n   #:*lang-regex*\n   #:*device-regex*\n   #:*ios-multi-dir*\n   #:*ios-diff-dir*\n   #:*metadata*\n   #:*static-website*\n   #:*browser-configs*\n   #:*static-website-assets-root*\n   #:*commit-hash*\n   #:*override-commit-hash*\n   #:*selenium-hub*\n   #:*selenium-hub-port*\n   #:*firebase-output*\n   #:*self-test*\n   #:*commit-limit*\n   #:*mark-failed*\n   #:*versionp*\n   #:*compare-threshold*\n   #:*recursive*\n   #:*unchanged-from*\n   #:*finalize*\n   #:*batch*\n   #:*work-branch*\n   #:*main-branch-commit-hash*\n   #:*tags*\n   #:*author*\n   #:*merge-base-commit-hash*\n   #:*shard*\n   #:*image-file-types*\n   #:*release-branch-regex*\n   #:*pixel-tolerance*\n   #:*xcresult*))\n\n(in-package :screenshotbot/sdk/flags)\n\n(define-flag *directory*\n  :default-value nil\n  :selector \"directory\"\n  :type (or null string)\n  :help \"Directory with the images.\n\nBy default we don't scan recursively, see the `--recursive` flag for that.\")\n\n(define-flag *recursive*\n  :default-value nil\n  :selector \"recursive\"\n  :type boolean\n  :help \"Whether to scan the directory recursively\")\n\n(define-flag *versionp*\n  :default-value nil\n  :selector \"version\"\n  :type boolean\n  :help \"Show the version of this SDK and exit\")\n\n(define-flag *org-defaults*\n  :default-value nil\n  :selector \"defaults\"\n  :type (or null string)\n  :help \"[OBSOLETE]\")\n\n(define-flag *create-github-issue*\n  :selector \"create-github-issue\"\n  :default-value nil\n  :type boolean\n  :help \"[OBSOLETE] If you need to use this, please contact support.\")\n\n\n(define-flag *gitlab-merge-request-iid*\n  :selector \"gitlab-merge-request-iid\"\n  :default-value nil\n  :type (or null string)\n  :help \"GitLab merge request IID\")\n\n\n(define-flag *channel*\n  :selector \"channel\"\n  :default-value \"unnamed-channel\"\n  :type string\n  :help \"Channel name for screenshot tracking. Defaults to `unnamed-channel`.\")\n\n(define-flag *pull-request*\n  :selector \"pull-request\"\n  :default-value nil\n  :type (or null string)\n  :help \"Pull request URL. Automatically detected on CircleCI,\n  Bitrise, Netlify.\")\n\n(define-flag *branch*\n  :selector \"branch\"\n  :default-value nil\n  :type (or null string)\n  :help \"[OBSOLETE]\")\n\n(define-flag *main-branch*\n  :selector \"main-branch\"\n  :default-value nil\n  :type (or null string)\n  :help \"Git Branch of the main branch being tracked. We try first\n  `main` and then `master`, by checking for origin/<branch-name>\")\n\n(define-flag *release-branch-regex*\n  :selector \"release-branch-regex\"\n  :default-value nil\n  :type (or null string)\n  :help \"A release branch is branched off the main branch, but is tracked\nindependently with its own notifications. Release branches are treated\ndifferently than Pull Requests, since authors are not expected to review\neach change. A work-branch that matches this regex is treated as a release branch.\n\nIt is safe to use this regex to identify long-running feature branches too.\n\nThe regex is matched to the whole branch name, not just partially.\n\ne.g. --release-branch-regex 'release/.*'\n\nor\n\n--release-branch-regex '(release|long-feature)/.*'\n\n\")\n\n(define-flag *main-branch-commit-hash*\n  :selector \"main-branch-commit-hash\"\n  :default-value nil\n  :type (or null string)\n  :help \"The commit of the main branch at time of running. In most case, you\nDO NOT need to provide this, and we'll detect this from the Git repository\ndirectly. Please contact support@screenshotbot.io if you plan on using this.\")\n\n(define-flag *work-branch*\n  :selector \"work-branch\"\n  :default-value nil\n  :type (or null string)\n  :help \"Your current branch, preferably as provided by your CI environment\nas opposed to reading from git. Some CI environments may not update the\nlocal branch name, and instead provides the branch name as an environment\nvariable. We can automatically figure this out on CircleCI, Bitrise,\nNetlify, Azure DevOps, BuildKits, and Bitbucket Pipelines.\")\n\n(define-flag *repo-url*\n  :selector \"repo-url\"\n  :default-value nil\n  :type (or null string)\n  :help \"Repository URL (e.g. https://github.com/foo/bar)\")\n\n(define-flag *phabricator-diff-id*\n  :selector \"phabricator-diff-id\"\n  :default-value nil\n  :type (or null string)\n  :help \"Phabricator Diff ID\")\n\n(define-flag *build-url*\n  :selector \"build-url\"\n  :default-value nil\n  :type (or null string)\n  :help \"Build URL to easily identify build that generated this run\")\n\n(define-flag *production*\n  :selector \"production\"\n  :default-value t\n  :type boolean\n  :help \"Whether this is a run on your CI. For local runs, we\n  suggest using `--production=false`. This avoids polluting your runs\n  in production.\")\n\n(define-flag *lang-regex*\n  :selector \"lang-regex\"\n  :default-value nil\n  :type (or null string)\n  :help \"[OBSOLETE]\")\n\n(define-flag *device-regex*\n  :selector \"device-regex\"\n  :default-value nil\n  :type (or null string)\n  :help \"[OBSOLETE]\")\n\n(define-flag *ios-multi-dir*\n  :selector \"ios-multi-dir\"\n  :default-value nil\n  :help \"[OBSOLETE]\"\n  :type boolean)\n\n(define-flag *ios-diff-dir*\n  :selector \"ios-diff-dir\"\n  :default-value nil\n  :type (or null string)\n  :help \"[OBSOLETE]\")\n\n(define-flag *metadata*\n  :selector \"metadata\"\n  :default-value nil\n  :type (or null string list)\n  :parser parse-string\n  :help \"A metadata.xml file (Android only)\")\n\n(define-flag *static-website*\n  :selector \"static-website\"\n  :default-value nil\n  :type (or null string)\n  :help \"Use to generate screenshots of a static website\")\n\n(define-flag *static-website-assets-root*\n  :selector \"static-website-assets-root\"\n  :default-value nil\n  :type (or null string)\n  :help \"When parsing the website directory at --static-website, the\n  asset root is used to determine where to fetch JS, CSS and image\n  from. If not specified, we assume the assets are all stored in the\n  same directory.\")\n\n(define-flag *browser-configs*\n  :selector \"browser-configs\"\n  :default-value nil\n  :type (or null string)\n  :help \"A YAML file that specifies the configuration of the\n  browsers. Please see documentation for details.\")\n\n(define-flag *override-commit-hash*\n  :selector \"override-commit-hash\"\n  :default-value nil\n  :type (or null string)\n  :help \"Override the commit hash detected by git. In most cases you\n  don't need this, and this is only relevant for Pull Requests, and\n  only if you rebase your changes as part of your CI run. This hash\n  must be the full hash, partial hashes or tag names are not\n  suitable.\n  Automatically detected on: CircleCI, Bitrise\")\n\n(define-flag *merge-base-commit-hash*\n  :selector \"merge-base-commit-hash\"\n  :default-value nil\n  :type (or null string)\n  :help \"Override the merge base commit. In most cases, you do not\nneed to provide this, and it will be computed automatically (as\n`git merge-base main this-branch`).The merge base is only use on PR\ncommits, to figure out which commit to compare screenshots against.\n\nThis might be useful to override if you don't have access to the\nrepository when calling this CLI, or implementing custom behavior with\nstacked Pull Requests.\")\n\n(define-flag *firebase-output*\n  :selector \"firebase-output\"\n  :default-value nil\n  :type (or null string)\n  :help \"When running Android tests in Firebase Test Lab, pass the output of\n the `gcloud firebase test android run` as a file to this option. We'll\n automatically fetch the required files from Google Cloud, and clean\n up  the files from Google Cloud when we're done. We use the `gcloud`\n command line tool so you must have already called\n activate-service-account before this step.\")\n\n(define-flag *batch*\n  :selector \"batch\"\n  :default-value nil\n  :type (or null string)\n  :help \"Batch multiple channels into one single build status named by this\nargument.\")\n\n(define-flag *self-test*\n  :selector \"self-test\"\n  :default-value nil\n  :type boolean\n  :help \"Run self-diagnostic tools to ensure that this CLI can work on your\nmachine.\")\n\n(define-flag *commit-limit*\n  :selector \"commit-limit\"\n  :default-value 1000\n  :type (or null integer)\n  :help \"Limit the number of commits for which we upload commit hashes. This is\nuseful for large repositories (about > 10000 commits). For large\nrepositories, sending all the commit hashes will time out. The\nuploaded graph is merged with the graph that the server already knows\nof.\n\nYou should set this high enough so that Screenshotbot always has the\nentire commit graph from the previous promoted run to the new run. A\nvalue of about 1000 should be safe and relatively fast for most\npeople.\")\n\n(define-flag *mark-failed*\n  :selector \"mark-failed\"\n  :default-value nil\n  :type boolean\n  :help \"Mark this run as failed. This might happen if the build step that\ngenerates the screenshots failed.\n\nYou don't need to call this, but if you do we can use the information\nto show more appropriate information on Pull Requests. For instance,\nif you have a Pull Request based off of a failing commit, we can find\nthe last green commit to make our screenshot report.\")\n\n(define-flag *unchanged-from*\n  :selector \"mark-unchanged-from\"\n  :default-value nil\n  :type (or null string)\n  :help \"Notify Screenshotbot that the run for this commit will be identical\nto the run from the commit provided\")\n\n(define-flag *shard*\n  :selector \"shard\"\n  :default-value nil\n  :type (or null string)\n\n;; TODO: \n;; On CircleCI, you can also use the shard specifier as `auto`, which determines a suitable\n;; shard specifier based on CircleCI's test splits.\n  \n  :help \"If the screenshots for a channel are being generated from multiple shards\n(or \\\"test splits\\\"), you can pass a shard specifier here. When provided, Screenshotbot will\nonly create the screenshots for the last shard is uploaded.\n\nA shard specifier looks like <buildId>:<shardNum>:<shardCount>. Build ID can be any\narbitrary identifier, but must be the same for the all the invocations. For example, it could\nbe a build ID of the root CI job.\")\n\n\n(define-flag *compare-threshold*\n  :selector \"compare-threshold\"\n  :default-value nil\n  :type (or null double-float)\n  :help \"Fraction of pixels that can be different for Screenshotbot to consider\nthe screenshots to be the same. (between 0.0 and 1.0)\n\nWe don't recommend using this unless absolutely required. Flaky\nscreenshots are harder to maintain long term. Using this argument can\nalso slow down the processig of your reports significantly.\n\nIf not specified, or if specified by 0.0, we'll consider screenshots\nequal only if they are identical by file content. (e.g. any changes in\nencoding, or EXIF data will cause a screenshot change.)\n\nKeep in mind that this value will typically be very low. e.g., a\n1000x1000 image, using 0.001 threshold would still allow for 1000\npixel changes which might be too high for most practical uses. You\nprobably want to choose this so that no more than 10-20 pixels are\nallowed to be different at a time.\")\n\n(define-flag *pixel-tolerance*\n  :selector \"pixel-tolerance\"\n  :default-value 0\n  :type (or null integer)\n  :help \"The square of the Euclidean distance between two pixels when\nconsidering them different or not. This is different from --compare-threshold\nwhich specifies the fraction of pixels that can be different.\n\nThis value controls the sensitivity of individual pixel comparison. When\ncomparing two pixels, if the square of the Euclidean distance in RGB color\nspace between them is less than or equal to this value, they are considered\nthe same.\n\nIn most cases --pixel-tolerance=1 will be good enough for your needs\nif you are not able to achieve pixel-perfect images.\")\n\n(define-flag *finalize*\n  :selector \"finalize\"\n  :default-value nil\n  :type boolean\n  :help \"Notify Screenshotbot that all builds on all channels on this\ncommit are complete. This is not required to be called, but if\nused provides a better developer experience when later builds\n are waiting on this commit.\")\n\n(define-flag *image-file-types*\n  :selector \"image-file-types\"\n  :default-value \"png\"\n  :type string\n  :help \"When scanning a directory, this is the list of file extensions\nwe consider as images. This defaults to `png`, but we support\nPNG, WEBP, HEIC, JXL, JPG. We do not\nrecommend JPG or any other lossy formats. You can separate multiple\nextensions with a comma.\")\n\n(define-flag *tags*\n  :selector \"tags\"\n  :default-value nil\n  :type (or null string)\n  :help \"A comma separated list of tags to associate with these runs. Tags are arbitrary and will be shown next to the run names, and let's you filter runs by tags.\")\n\n(define-flag *author*\n  :selector \"author\"\n  :default-value nil\n  :type (or null string)\n  :help \"Author for this run. Either in the form of \\\"Full Name <email@example.com>\\\" or just \\\"email@example.com\\\". The email is used to enforce review rules. \")\n\n(define-flag *xcresult*\n  :selector \"xcresult\"\n  :default-value nil\n  :type (or null string)\n  :help \"Path to an xcresult file to extract screenshots from. When provided, screenshots will be extracted from the xcresult bundle instead of scanning a directory.\")\n"
  },
  {
    "path": "src/screenshotbot/sdk/git-pack.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/git-pack\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:assoc-value\n                #:when-let)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/misc\n                #:not-null!\n                #:?.)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:util/request\n                #:http-request)\n  (:local-nicknames (#:git #:screenshotbot/sdk/git)))\n(in-package :screenshotbot/sdk/git-pack)\n\n(defmacro assert-equal (one two)\n  `(let ((one ,one)\n         (two ,two))\n     (unless (equal one two)\n       (error \"Expected ~a to be equal to ~a, but got ~a\"\n              ',two\n              one\n              two))))\n\n(defclass abstract-upload-pack ()\n  ((multi-ack-p :initarg :multi-ack-p\n                :initform t\n                :reader multi-ack-p\n                :documentation \"Whether the server supports multi_ack capability\")))\n\n(defclass upload-pack (abstract-upload-pack)\n  ((stream :initarg :stream\n           :reader %stream)))\n\n(defclass http-upload-pack (abstract-upload-pack)\n  ((repo :initarg :repo\n         :reader http-url)\n   (extra-headers :initarg :extra-headers\n                  :initform nil\n                  :reader extra-headers)\n   (stream :initarg :stream\n           :accessor %stream)))\n\n(defparameter +git@-regex+ \"^([a-zA-Z0-9]*)@([a-zA-Z0-9.]*):(.*)$\")\n(defparameter +ssh//-regex+ \"^ssh://([a-zA-Z0-9]*)@([a-zA-Z0-9.]*)(:([0-9]+))?(/.*)$\")\n\n(defun supported-remote-repo-p (repo)\n  (and\n   (or\n    (cl-ppcre:scan-to-strings +git@-regex+ repo)\n    (cl-ppcre:scan-to-strings +ssh//-regex+ repo)\n    (str:starts-with-p \"https:\" repo)\n    (str:starts-with-p \"http:\" repo))))\n\n(defun get-ssh-parts (url)\n  \"Returns four arguments: the user, the host, the directory and finally the port\"\n  (multiple-value-bind (match parts)\n      (cl-ppcre:scan-to-strings +git@-regex+ url)\n    (when match\n      (return-from get-ssh-parts (values\n                                  (elt parts 0)\n                                  (elt parts 1)\n                                  (elt parts 2)))))\n  (multiple-value-bind (match parts)\n      (cl-ppcre:scan-to-strings +ssh//-regex+ url)\n    (when match\n      (return-from get-ssh-parts\n        (values\n         (elt parts 0)\n         (elt parts 1)\n         (elt parts 4)\n         (elt parts 3))))))\n\n(defmethod make-upload-pack-command ((repo string))\n  (multiple-value-bind (user host directory port)\n      (get-ssh-parts repo)\n   (cond\n     ((and user host)\n      (remove-if #'null\n                 (list\n                  \"/usr/bin/env\"\n                  \"ssh\"\n                  (format nil \"~a@~a\" user host)\n                  (when port \"-p\")\n                  (when port port)\n                  \"git-upload-pack\"\n                  (format nil \"'~a'\" directory))))\n     (t\n      (list\n       \"/usr/bin/env\"\n       \"git-upload-pack\"\n       repo)))))\n\n(defmethod make-upload-pack-for-local-repo-only ((repo git:git-repo))\n  (make-upload-pack-from-cmd\n   (list\n    \"/usr/bin/env\"\n    \"git-upload-pack\"\n    (namestring (git:repo-dir repo)))))\n\n(defmethod make-upload-pack-command ((repo git:git-repo))\n  (make-upload-pack-command (namestring (git:repo-dir repo))))\n\n(defun local-upload-pack (repo)\n  \"TODO: badly named function. See also make-upload-pack-for-local-repo-only.\"\n  (let ((cmd (make-upload-pack-command repo)))\n    (log:debug \"Upload pack command is: ~a\" cmd)\n    (make-upload-pack-from-cmd cmd)))\n\n(defun make-upload-pack-from-cmd (cmd)\n  (multiple-value-bind (stream)\n      (sys:run-shell-command\n       cmd\n       :output :stream\n       :input :stream\n       :wait nil\n       :element-type '(unsigned-byte 8))\n    (assert stream)\n      \n    (make-instance 'upload-pack\n                   :stream stream)))\n\n(defmethod make-remote-upload-pack ((repo git:git-repo))\n  (let ((repo-url (git:get-remote-url repo)))\n    (cond\n     ((or\n       (str:starts-with-p \"http://\" repo-url)\n       (str:starts-with-p \"https://\" repo-url))\n      (make-instance 'http-upload-pack\n                     :extra-headers (git:extra-header repo)\n                     :repo repo-url))\n     (t\n      (local-upload-pack\n       (not-null! repo-url))))))\n\n(defmethod close-upload-pack (self)\n  (close (%stream self)))\n\n(defun read-length (self)\n  (let ((len (make-array 4 :element-type '(unsigned-byte 8))))\n    (let ((num-bytes (read-sequence len (%stream self))))\n     (unless (= 4 num-bytes)\n       (error \"Could not read length, got ~a bytes\" num-bytes)))\n\n    (when (equalp len #. (flex:string-to-octets \"PACK\"))\n      (error 'pack-header-encountered))\n    \n    (log:trace \"Got length: ~a\" len)\n    (parse-integer (flex:octets-to-string len)\n                   :radix 16)))\n\n(defun write-length (self len)\n  (let ((len (str:downcase (format nil \"~4,'0x\" len))))\n    (write-sequence (flex:string-to-octets len) (%stream self))))\n\n(define-condition pack-header-encountered (error)\n  ())\n\n(define-condition upload-pack-error (error)\n  ((message :initarg :message\n            :reader upload-pack-error-message))\n  (:report (lambda (condition stream)\n             (format stream \"Git upload-pack error: ~a\"\n                     (upload-pack-error-message condition)))))\n\n(defmethod read-protocol-line ((self abstract-upload-pack))\n  (let ((len (read-length self)))\n    (cond\n      ((eql 0 len)\n       nil)\n      (t\n       (let ((content (make-array (- len 4) :element-type '(unsigned-byte 8))))\n         (read-sequence content (%stream self))\n         (let ((result\n                 (flex:octets-to-string content)))\n           (log:trace \"Read protocol line: ~a\" result)\n           ;; Check for ERR packets sent by git\n           (when (str:starts-with-p \"ERR \" result)\n             (error 'upload-pack-error\n                    :message (str:substring 4 nil result)))\n           result))))))\n\n(defmethod read-headers (self)\n  ;;  () - Got line:829e9b346306604777c9dcc2c09b61aa30511446 HEAD ... multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed symref=HEAD:refs/heads/new-pr filter object-format=sha1 agent=git/2.39.5\n  ;; In particular we care about the filter capability in the future.\n\n  (let ((features))\n    (let ((refs\n           (loop for line = (read-protocol-line self)\n                 while line\n                 collect\n                 (destructuring-bind (ref rest)\n                     (str:split \" \" (str:trim line) :limit 2)\n                   (cond\n                     (features\n                      (list ref rest))\n                     (t\n                      (let ((parts (str:split #\\Null rest)))\n                        (setf features (str:split \" \" (second parts)))\n                        (list ref (first parts)))))))))\n      (values refs features))))\n\n\n(defmethod write-packet (self fmt &rest content)\n  (log:trace \"Writing packet ~a\" (apply #'format nil fmt content))\n  (let ((bytes (flex:string-to-octets (apply #'format nil fmt content))))\n    (let ((len (+ 5 (length bytes))))\n      (write-length self len)\n      (write-sequence bytes (%stream self))\n      (write-byte 10 (%stream self)))\n    (force-output (%stream self))))\n\n(defmethod want (self commit)\n  (write-packet self \"want ~a\" commit))\n\n(defmethod write-flush (self)\n  (write-length self 0)\n  (force-output (%stream self)))\n\n(defun decode-uint32 (stream)\n  \"Taken from bknr.datastore\"\n  (logior (ash (read-byte stream) 24)\n          (ash (read-byte stream) 16)\n          (ash (read-byte stream) 08)\n          (read-byte stream)))\n\n(defun read-packfile-header (stream &key (read-magic-p t))\n  \"Reads the header and returns the number of entries\n\nIf READ-MAGIC-P is true, we'll read the first four bytes of PACK magic.\"\n  ;; https://github.com/git/git/blob/master/Documentation/gitformat-pack.adoc\n  (when read-magic-p\n    (let ((magic (make-array 4 :element-type '(unsigned-byte 8))))\n      (assert-equal 4 (read-sequence magic stream))\n      (assert-equal \"PACK\" (flex:octets-to-string magic))))\n\n  (let ((version (make-array 4 :element-type '(unsigned-byte 8))))\n    (assert (= 4 (read-sequence version stream)))\n    (assert (equalp #(0 0 0 2) version)))\n\n  (decode-uint32 stream))\n\n(defun p (x)\n  (log:trace \"Got value: ~a\" x)\n  x)\n\n(defun read-entry-header (stream)\n  \"Returns two values, the type and length\"\n  (let ((byte (read-byte stream))\n        (size 0)\n        (type))\n    ;; https://github.com/git/git/blob/master/packfile.c#L111-header3\n    (log:trace \"First byte: ~a\" byte)\n    (setf type (logand 7 (ash byte -4)))\n    (setf size (logand byte #b1111))\n\n    (let ((mult 4))\n     (loop while (> (logand byte 128) 0)\n           do\n              (setf byte (read-byte stream))\n              (log:trace \"Read another byte: \" byte)\n              (incf size (p (ash (p (logand (1- #x80) byte)) mult)))\n              (incf mult 7)))\n\n    (log:trace \"final size: ~a\" size)\n    (values type size)))\n\n(defun simulate ()\n\n  ;; https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.adoc\n  (setf *p* (local-upload-pack \"/home/arnold/builds/web/.git/\"))\n  (read-headers *p*)\n  (want *p* \"ff6dbd2b1e3db2d5a89b30fe8d5eacc2a1874de3 filter\")\n  ;; This requires something like git config --global --add  uploadpack.allowFilter 1\n  ;; Unclear if this is always available.\n  ;; https://github.com/git/git/blob/master/Documentation/gitprotocol-capabilities.adoc#filter\n  ;; We should check if the capability is available before attempting it.\n  ;; (write-packet *p* \"filter object:type=commit\")\n  (write-flush *p*)\n  (write-packet *p* \"done\")\n  (finish-output (%stream *p*))\n  (read-protocol-line *p*)\n  ;; From here on the rest is the packfile\n  )\n\n(defconstant +obj-ref-delta+ 7)\n(defconstant +obj-obs-delta+ 6)\n\n(defun read-packfile-entry (packfile)\n  \"Returns type and the contents of the entry\"\n  ;; https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.adoc\n  (log:trace \"Reading packfile entry\")\n  (let ((stream (%stream packfile)))\n    (multiple-value-bind (type length) (read-entry-header stream)\n      (log:trace \"Got type: ~a\" type)\n\n      (when (eql +obj-ref-delta+ type)\n        (let ((arr (make-array 20 :element-type '(unsigned-byte 8))))\n          ;; Read the name of the base object\n          (read-sequence arr stream)))\n\n      (when (eql +obj-obs-delta+ type)\n        ;; I haven't encountered this in testing. It looks like this\n        ;; is going to be a variable length integer. For my purposes,\n        ;; I don't care what the actual value of this integer is. In\n        ;; theory read-packfile-header reads the exact number of\n        ;; bytes, so I'll just use that here to ignore the bytes.\n        (warn \"Untested code for obj-obs-delta\")\n        (read-entry-header (%stream packfile)))\n\n      (let ((body (semz.decompress:decompress :zlib stream\n                                              :allow-overreads-p nil\n                                              :output-size length)))\n\n        (values type\n                body\n                length)))))\n\n(defun packfile-test ()\n  (simulate)\n  (setf *arr* (make-array 4 :element-type '(unsigned-byte 8)))\n  (let ((num (read-packfile-header (%stream *p*))))\n    (loop for i below num\n          collect\n             (multiple-value-list\n              (read-packfile-entry *p*))))\n\n  \n  (close-upload-pack *p*))\n\n(defun compute-sha1-from-commit (body)\n  (let ((digest (ironclad:make-digest 'ironclad:sha1)))\n    (ironclad:digest-sequence\n     digest\n     (flex:string-to-octets (format nil \"commit ~a~a\" (length body) #\\Null)))\n    (ironclad:byte-array-to-hex-string\n     (ironclad:digest-sequence\n      digest\n      body))))\n\n(defun parse-parents (commit)\n  (let ((stream (make-string-input-stream commit)))\n    (loop for line = (read-line stream)\n          until (str:emptyp (str:trim line))\n          for parts = (str:split \" \" line)\n          if (equal \"parent\" (first parts))\n            collect (second parts))))\n\n(defun remote-ref-equals (local-ref ref)\n  (let ((parts (str:split \"/\" ref :limit 3)))\n    (and\n     (eql 3 (length parts))\n     (equal local-ref (elt parts 2)))))\n\n(def-easy-macro with-opened-upload-pack (upload-pack &key &binding headers &binding features &fn fn)\n  (multiple-value-bind (headers features)\n      (read-headers upload-pack)\n    (unwind-protect\n         (fn headers features)\n      #+nil\n      (close-upload-pack upload-pack))))\n\n(defun assert-content-type (http-headers expected-content-type)\n  (let ((content-type (assoc-value http-headers :content-type)))\n    (unless (equal expected-content-type content-type)\n      (error \"Got bad content-type: ~a\" content-type))))\n\n(defun remove-auth-from-uri (uri)\n  (let* ((uri (quri:uri uri))\n         (user (quri:uri-userinfo uri)))\n    (setf (quri:uri-userinfo uri) nil)\n    (values\n     (quri:render-uri uri)\n     (when user\n       (str:split \":\" user :limit 2)))))\n\n(defmethod %get-auth-ignoring-extra-headers ((p http-upload-pack))\n  (multiple-value-bind (git-url auth)\n      (remove-auth-from-uri (http-url p))\n    (declare (ignore git-url))\n    (or\n     auth\n     (read-netrc\n      (quri:uri-host\n       (quri:uri (http-url p))))\n     (git:credential-fill (http-url p)))))\n\n(defmethod %get-auth ((p http-upload-pack))\n  (let ((auth (unless (has-authorization-header-p (extra-headers p))\n                (%get-auth-ignoring-extra-headers p))))\n    (unless (or auth (has-authorization-header-p (extra-headers p)))\n      (log:info \"Could not find authentication information for Git http, assuming this is a public repo\"))\n    auth))\n\n(auto-restart:with-auto-restart (:retries 2)\n (defmethod git-http-request ((p http-upload-pack)\n                              url &rest args)\n   (let ((git-url (remove-auth-from-uri (http-url p))))\n     (let ((auth (%get-auth p)))\n       (apply #'http-request (format nil \"~a~a\"\n                                     (str:ensure-suffix \"/\" git-url)\n                                     url)\n              :basic-authorization auth\n              :additional-headers (extra-headers p)\n              :want-stream t\n              :ensure-success t\n              :read-timeout 45\n              :connection-timeout 15\n              :force-binary t\n              args)))))\n\n(defun has-authorization-header-p (extra-headers)\n  (loop for (key . value) in extra-headers\n        if (string-equal key \"authorization\")\n          return t))\n\n(auto-restart:with-auto-restart ()\n  (defmethod read-commits ((p http-upload-pack)\n                           &key (filter-blobs t)\n                             (depth 1000)\n                             wants\n                             (haves nil)\n                             (parse-parents nil))\n    (multiple-value-bind (stream code http-headers)\n        (git-http-request\n         p\n         \"info/refs?service=git-upload-pack\"\n         :method :get)\n      (declare (ignore code))\n      (assert-content-type http-headers \"application/x-git-upload-pack-advertisement\")\n      (setf (%stream p) stream)\n      (let ((service-headers (read-headers p)))\n        ;; This is typically a single line header ... but I'm mostly\n        ;; just discarding this for now.\n        (log:debug \"Got service headers: ~a\" service-headers))\n      (multiple-value-bind (headers features)\n          (read-headers p)\n        (close (%stream p))\n        (let ((wants (calculate-wants headers features wants)))\n          (cond\n            ((not wants)\n             ;; We're done\n             nil)\n            (t\n             (log:debug \"Wanting: ~s\" wants)\n             (let ((body-builder (flex:make-in-memory-output-stream)))\n               (setf (%stream p) body-builder)\n               (write-wants-and-haves\n                p\n                wants\n                haves :depth depth :filter-blobs filter-blobs :features features\n                      :http? t)\n               (let ((bytes (flex:get-output-stream-sequence body-builder)))\n                (multiple-value-bind (stream code http-headers)\n                    (git-http-request p\n                                      \"git-upload-pack\"\n                                      :content-type \"application/x-git-upload-pack-request\"\n                                      :method :post\n                                      :content bytes)\n                  (assert-content-type http-headers \"application/x-git-upload-pack-result\")\n                  (setf (%stream p) stream)\n                  (when depth\n                    (read-shallow-lines p))\n                  (read-response-and-packfile p :parse-parents parse-parents)))))))))))\n\n(defmethod read-commits :around ((p abstract-upload-pack)\n                                 &rest args\n                                 &key wants\n                                 &allow-other-keys)\n  (let ((wants\n          (cond\n            ((listp wants)\n             (lambda (list)\n               (loop for (sha . remote-ref) in list\n                     if (loop for local-ref in wants\n                              if (remote-ref-equals local-ref remote-ref)\n                                return t)\n                       collect sha)))\n            (t\n             wants))))\n    (apply #'call-next-method p :wants wants args)))\n\n(defun calculate-wants (headers features wants)\n  (log:debug \"Got features: ~a\" features)\n  (unless (str:s-member features \"allow-tip-sha1-in-want\")\n    (warn \"Git server doesn't support allow-tip-sha1-in-want, features: ~s\" features))\n\n  (unless (or\n           (str:s-member features \"allow-tip-sha1-in-want\")\n           (str:s-member features \"allow-reachable-sha1-in-want\"))\n    (error \"This git server doesn't support allow-{tip|reachable}-sha1-in-want\"))\n  (funcall\n   wants\n   (loop for this-branch in headers\n         collect (cons\n                  (first this-branch) (second this-branch)))))\n\n(defun supports-multi-ack-p (features)\n  \"Check if the server supports multi_ack or multi_ack_detailed capability\"\n  (or (str:s-member features \"multi_ack\")\n      (str:s-member features \"multi_ack_detailed\")))\n\n(defun read-shallow-lines (p)\n  \"In SSH this happens after wants/deepen and before haves. In HTTP this happens after wants/deepen/haves.\"\n  (log:debug \"Reading shallow lines\")\n  (loop for line = (read-protocol-line p)\n        while line\n        do (log:debug \"Ignoring: ~a\" line)))\n\n(defun write-wants-and-haves (p wants haves &key depth filter-blobs features http?)\n  (let* ((multi-ack-p (supports-multi-ack-p features))\n         (capabilities (format nil \"~a filter allow-tip-sha1-in-want allow-reachable-sha1-in-want shallow~a agent=screenshotbot-cli\"\n                               (car wants)\n                               (if multi-ack-p \" multi_ack\" \"\"))))\n    (want p capabilities))\n  (dolist (want (cdr wants))\n    (want p want))\n\n  (when depth\n    (write-packet p \"deepen ~a\" depth))\n\n  (cond\n    ((and\n      filter-blobs\n      (str:s-member features \"filter\"))\n     (log:debug \"filter is enabled, sending filter\")\n     (write-packet p \"filter blob:none\"))\n    (t\n     (warn \"Filter is not supported\")))\n  (write-flush p)\n\n  (when (and depth (not http?))\n    ;; These should be \"shallow ~a\" or \"unshallow ~a\" lines\n    ;; We don't care for them really\n    (read-shallow-lines p))\n\n  ;; We must remove duplicate haves, otherwise upload-pack misbehaves\n  ;; in the response\n  (fset:do-set (have (fset:convert 'fset:set haves))\n    (write-packet p \"have ~a\" have))\n  (when (and haves (not http?))\n    (write-flush p))\n\n         \n  (write-packet p \"done\")\n  (finish-output (%stream p)))\n\n(defmethod read-commits ((p upload-pack)\n                         &key (filter-blobs t)\n                           (depth 1000)\n                           wants\n                           (haves nil)\n                           (parse-parents nil))\n  \"WANTS is a function that take an alist of (sha -> ref-name) and\nreturns all the SHAs it wants. If this function is called, we can\nassume that the server has allow-{tip|reachable}-sha1-in-want enabled.\n\nReturns two values: An alist of commit hashes to commit bodys, and as\na second value the headers that were initially provided (sha and refs)\n\"\n  (with-opened-upload-pack (p :headers headers :features features)\n    ;;(log:trace \"Got headers: ~S\" headers)\n    (let ((wants\n            (calculate-wants headers features wants)))\n      (cond\n        ((not wants)\n         (write-flush p))\n        (t\n         (write-wants-and-haves\n          p\n          wants\n          haves :depth depth :filter-blobs filter-blobs :features features)\n\n         (log:debug \"Waiting for response\")\n         (read-response-and-packfile\n          p :parse-parents parse-parents))))))\n\n(defun process-ack (p)\n  \"See process_ack() in fetch-pack.c\"\n  (loop\n    (let ((line (read-protocol-line p)))\n      (log:debug \"Got ACK/NAK: ~a\" line)\n      (cond\n        ((equal \"NAK\" (str:trim line)))\n        ((str:starts-with-p \"ACK \" line))\n        ((equal \"ready\" (str:trim line)))\n        (t\n         (error \"Didn't get an ACK or NAK or recognized line: ~a\" line))))))\n\n(defun read-response-and-packfile (p &key parse-parents)\n  ;; This should either be a NAK (nothing common found), or ACK\n  ;; <commit> signalling that that was a common commit.\n  (handler-case\n      (progn\n        (process-ack p)\n        (error \"We should never be here\"))\n    (pack-header-encountered (e)))\n  \n  (log:debug \"reading packfile\")        \n  ;; Now we get the packfile\n  (let ((num (read-packfile-header (%stream p)\n                                   ;; We've already read the magic in process-ack\n                                   :read-magic-p nil)))\n    (log:debug \"Packfile entries: ~a\" num)\n    (serapeum:collecting\n      (loop for i below num\n            do\n               (multiple-value-bind (type body) (read-packfile-entry p)\n                 (when (eql 1 type)\n                   (collect\n                       (cons\n                        (compute-sha1-from-commit body)\n                        (cond\n                          (parse-parents\n                           (parse-parents (flex:octets-to-string body)))\n                          (t\n                           (flex:octets-to-string body)))))))))))\n\n(defun read-netrc-line (stream)\n  (when-let ((line (read-line stream nil nil)))\n   (let ((line (str:trim line)))\n     (cond\n       ((str:starts-with-p \"#\" line)\n        ;; Technically this might not really be needed\n        (read-netrc-line stream))\n       (t\n        (let ((parts (str:split \" \" line)))\n          (values\n           (str:trim (first parts))\n           (str:trim (car (last parts))))))))))\n\n(defun read-netrc (host &key (pathname \"~/.netrc\"))\n  (when (path:-e pathname)\n   (let (login\n         password\n         found)\n     (block nil\n       (flet ((maybe-return ()\n                (when (and found login password)\n                  (return-from read-netrc (list login password)))))\n         (with-open-file (s pathname :direction :input)\n           (dotimes (i 10000) ;; avoid bugs\n             (multiple-value-bind (key value) (read-netrc-line s)\n               (cond\n                 ((not key)\n                  (return nil))\n                 ((and\n                   (not found)\n                   (equal \"machine\" key)\n                   (equal host value))\n                  (setf found t))\n                 ((and\n                   found\n                   (equal \"machine\" key))\n                  (return nil))\n                 ((and\n                   found\n                   (equal \"login\" key))\n                  (setf login value)\n                  (maybe-return))\n                 ((and\n                   found\n                   (equal \"password\" key))\n                  (setf password value)\n                  (maybe-return)))))))))))\n\n\n(defmethod read-commits ((repo string) &rest args &key &allow-other-keys)\n  (cond\n    ((or\n      (str:starts-with-p \"http:\" repo)\n      (str:starts-with-p \"https://\" repo))\n     (apply #'read-commits (make-instance 'http-upload-pack\n                                          :repo repo)\n            args))\n    (t\n     (let* ((p (local-upload-pack repo)))\n       (apply #'read-commits p args)))))\n\n;; (log:config :warn)\n;; (read-commits \"/home/arnold/builds/fast-example/.git\" :branch \"refs/heads/master\")\n;; (length (read-commits \"git@github.com:tdrhq/fast-example.git\" :branch \"refs/heads/master\" :haves (list \"3c6fcd29ecdf37a2d1a36f46309787d32e11e69b\")))\n;; (length (read-commits \"git@github.com:tdrhq/fast-example.git\" :wants (list \"master\") :depth 30))\n;; (read-commits \"https://github.com/tdrhq/fast-example.git\" :wants (list \"master\") :haves (list \"3c6fcd29ecdf37a2d1a36f46309787d32e11e69b\") :depth 300)\n;; (read-commits \"https://github.com/tdrhq/alisp.git\" :wants (list \"master\") :depth nil)\n;; (read-commits \"git@gitlab.com:tdrhq/fast-example.git\" :wants (list \"master\") :depth 30 :haves (list \"3c6fcd29ecdf37a2d1a36f46309787d32e11e69b\" \"3c6fcd29ecdf37a2d1a36f46309787d32e11e69b\"))\n;; (read-commits \"git@github.com:tdrhq/fast-example.git\" :wants (list \"master\") :depth 30 :haves (list \"3c6fcd29ecdf37a2d1a36f46309787d32e11e69b\"  \"3be209da6b797a6821d8c43b13c8b2ce64cf4a5b\"))\n;; (read-commits \"git@bitbucket.org:tdrhq/fast-example.git\" :wants (list \"master\") :depth 30)\n;; (read-commits (make-remote-upload-pack (make-instance 'git:git-repo :dir \"/tmp/alisp/\")) :wants (list \"master\"))\n\n;; Azure features: ( multi_ack thin-pack side-band side-band-64k no-progress multi_ack_detailed no-done shallow allow-tip-sha1-in-want filter symref=HEAD:refs/heads/master)\n;; Azure also requires clients to support multi-ack :/\n;; (read-commits \"git@ssh.dev.azure.com:v3/testsbot/fast-example/fast-example\" :wants (list \"master\") :depth 30)\n;; (length (read-commits \"git@github.com:tdrhq/fast-example.git\" :wants (list \"master\") :depth 30 :parse-parents t))\n;; (length (read-commits \"git@github.com:tdrhq/braft.git\" :branch \"master\" :parse-parents t))\n;; (read-commits \"ssh://git@phabricator.tdrhq.com:2222/source/web.git\" :wants (list \"master\") :extra-wants (list \"4ec592fc02c8bf200e4e2b9902e9a71fd8de3aec\") :depth 100)\n;; (read-commits \"/home/arnold/builds/web/.git\" :wants (list \"master\") :depth 2)\n;; (read-commits \"https://github.com/tdrhq/alisp.git\" :git-config \"/tmp/alisp/.git/config\" :wants (list \"master\") :depth nil)\n\n;; Reproduce \"fatal: git upload-pack: not our ref\" error:\n;; This happens when you request a SHA that doesn't exist in the repository,\n;; typically because it was removed via force push or history rewrite.\n;; (read-commits \"git@bitbucket.org:tdrhq/fast-example.git\" :wants (lambda (refs) (list \"458a81c522f6d7325c9d893624c926eaa8ed2cc0\")) :depth 30)\n\n(def-health-check can-compute-sha1 ()\n  \"The first time we tried to do this, sha1 was removed out of the delivered image\"\n  (assert (equal \"e502bf251eaf300073dde00c0a39bd1061fb04de\" (compute-sha1-from-commit\n                                                             (flex:string-to-octets \"hello world\")))))\n\n\n\n;; (git:extra-header (make-instance 'git:git-repo :dir \"/tmp/alisp/\"))\n"
  },
  {
    "path": "src/screenshotbot/sdk/git.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/sdk/git\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:dag\n                #:ordered-commits)\n  (:export\n   #:null-repo\n   #:git-repo\n   #:rev-parse\n   #:merge-base\n   #:current-commit\n   #:current-branch\n   #:cleanp\n   #:git-message\n   #:fetch-remote-branch\n   #:author\n   #:get-remote-url\n   #:repo-dir\n   #:rev-parse-local\n   #:$\n   #:extra-header\n   #:debug-git-config\n   #:credential-fill)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)))\n(in-package :screenshotbot/sdk/git)\n\n(defun git-root (&key (directory (uiop:getcwd)) (errorp t))\n  (declare (optimize (speed 0) (debug 3)))\n  (cond\n    ((path:-d (path:catdir directory \".git/\"))\n     directory)\n    ((>= 1 (length (pathname-directory directory)))\n     (if errorp\n         (error \"Could not find git root\")\n         nil))\n    (t\n     (git-root\n      :directory (make-pathname\n                  :directory (butlast (pathname-directory directory))\n                  :defaults directory)\n      :errorp errorp))))\n\n(defclass git-repo ()\n  ((link :initarg :link\n         :accessor repo-link)\n   (dir :initarg :dir\n        :accessor repo-dir\n        :initform (git-root))))\n\n(defclass null-repo ()\n  ((link :initarg :link\n         :initform nil\n         :accessor repo-link)))\n\n(defmethod cleanp ((repo null-repo))\n  t)\n\n(defmethod repo-git-dir ((repo git-repo))\n  (path:catdir (repo-dir repo) \".git/\"))\n\n(defmethod git-command ((repo git-repo))\n  (list\n   \"git\"\n   \"--git-dir\"\n   (namestring (repo-git-dir repo))\n   \"--work-tree\"\n   (namestring (repo-dir repo))))\n\n(defmethod %git-status-porcelain ((repo git-repo))\n  ($ (git-command repo) \"status\" \"--porcelain\"))\n\n(defmethod cleanp ((repo git-repo))\n  (str:emptyp (%git-status-porcelain repo)))\n\n(defmethod git-message ((repo git-repo) &key (ref \"HEAD\"))\n  \"Shows the message of the given ref, which defaults to HEAD\"\n  ($ (git-command repo) \"log\" \"-1\" \"--pretty=%B\" \"HEAD\"))\n\n\n(defmethod current-commit ((repo git-repo))\n  ($ (git-command repo) \"rev-parse\" \"HEAD\"))\n\n(defmethod current-commit ((repo null-repo))\n  nil)\n\n(defun $ (&rest args)\n  (log:debug \"Running command with args: ~S\" args)\n  (let* ((args (flatten args))\n         (args (loop for arg in args\n                    if (pathnamep arg)\n                      collect (namestring arg)\n                    else\n                      collect arg))\n         (out\n           (uiop:run-program args\n                             :output 'string\n                             :error-output *error-output*)))\n    (str:trim out)))\n\n(defmethod current-branch ((repo git-repo))\n  ($ (git-command repo) \"rev-parse\" \"--abbrev-ref\" \"HEAD\"))\n\n(defmethod current-branch ((repo null-repo))\n  nil)\n\n(defun parse-raw-git-log (log)\n  (let ((input (make-string-input-stream log))\n        (output (make-string-output-stream)))\n    (loop for line = (str:trim (read-line input nil nil))\n          while line\n          do\n             (destructuring-bind (key &optional sha)\n                 (str:split \" \" line :limit 2)\n               (when (and\n                      sha\n                      (= (length sha) 40)\n                      (cl-ppcre:scan \"[a-f0-9]*\" sha))\n                 (cond\n                   ((Equal \"commit\" key)\n                    (format output \"~%~a\" sha))\n                   ((equal \"parent\" key)\n                    (format output \" ~a\" sha))))))\n    (format nil \"~a~%\" (str:trim (get-output-stream-string output)))))\n\n(defmethod read-graph ((repo git-repo))\n  (dag:read-from-stream\n   (make-string-input-stream\n    (parse-raw-git-log\n     ($ (git-command repo)\n       \"log\" \"--all\"\n       (when flags:*commit-limit*\n         (format nil \"--max-count=~a\" flags:*commit-limit*))\n       \"--pretty=raw\" \"--no-color\")))\n   :format :text))\n\n(defmethod merge-base ((repo git-repo) master-sha commit-sha)\n  (unless master-sha\n    (error \"Main branch SHA missing\"))\n  (unless commit-sha\n    (error \"Commit SHA missing\"))\n\n  ($ (git-command repo) \"merge-base\" master-sha commit-sha))\n\n(defmethod merge-base ((repo null-repo) master-sha commit-sha)\n  nil)\n\n(defmethod rev-parse ((repo git-repo) branch)\n  (rev-parse-local\n   repo\n   (format nil \"origin/~a\" branch)))\n\n(defmethod rev-parse-local ((repo git-repo) rev)\n  \"Technically should be called rev-parse, but for historical reasons\nrev-parse compares against the remote branch :/\"\n  (handler-case\n      ($ (git-command repo) \"rev-parse\" \"-q\" \"--verify\" rev)\n    (error\n      nil)))\n\n(auto-restart:with-auto-restart (:retries 2)\n  (defmethod %fetch-remote-branch ((repo git-repo) branch)\n    (log:info \"Running: git fetch origin ~a\" branch)\n    ($ (git-command repo) \"fetch\" \"origin\" branch)))\n\n(defmethod fetch-remote-branch ((repo git-repo) branch)\n  (unless (str:emptyp branch)\n    (handler-case\n        (%fetch-remote-branch repo branch)\n      (error (e)\n        (warn \"Git fetch failed with ~a\" e)))))\n\n(defmethod origin (repo)\n  \"origin\")\n\n(defmethod fetch-remote-branch ((repo null-repo) branch)\n  (values))\n\n(defmethod rev-parse ((repo null-repo) branch)\n  nil)\n\n(defmethod author ((repo git-repo))\n  ($ (git-command repo) \"log\" \"--format=%ae\" \"-n\" \"1\"))\n\n(defmethod author ((repo null-repo))\n  nil)\n\n(defmethod get-remote-url ((repo null-repo))\n  nil)\n\n(defmethod get-remote-url ((repo git-repo))\n  (handler-case\n      ($ (git-command repo) \"remote\" \"get-url\" (origin repo))\n    (uiop:subprocess-error ()\n      (warn \"git remote get-url failed for ~a\" (origin repo))\n      nil)))\n\n(def-health-check verify-git-is-present ()\n  (uiop:run-program (list \"git\" \"--help\")))\n\n(def-health-check verify-can-read-a-git-commit-graph ()\n  (tmpdir:with-tmpdir (dir)\n    (let ((*error-output* (make-string-output-stream)))\n      ($ \"git\" \"clone\" \"https://github.com/tdrhq/tiny-test-repo\" dir))\n    (let ((repo (make-instance 'git-repo :dir dir)))\n      (let ((dag (read-graph repo)))\n        (assert (> (length (dag:ordered-commits dag)) 0))))\n\n    ;; On Windows, some Git files are read-only, which will make this test\n    ;; fail\n    #+windows\n    (uiop:run-program (list \"attrib\" \"-r\" (format nil \"~a\\\\*.*\" (namestring dir)) \"/s\"))))\n\n(defmethod debug-git-config ((repo git-repo))\n  \"Generates a string with enough information about what's in the\n.git/config without revealing any secrets\"\n  (let ((config (path:catfile (repo-git-dir repo) \"config\")))\n    (let ((lines (str:lines (uiop:read-file-string config))))\n      (str:join #\\Newline\n       (loop for line in lines\n             collect (str:substring 0 8 line))))))\n\n\n(defmethod extra-header ((repo git-repo))\n  (ignore-errors\n   (loop for line in (str:lines\n                      ($ (git-command repo)\n                        \"config\" \"--get-urlmatch\"\n                        \"http.extraheader\"\n                        (get-remote-url repo)))\n         collect\n         (destructuring-bind (key value)\n             (mapcar #'str:trim\n                     (str:split \":\" line))\n           (cons (str:downcase key) value)))))\n\n(defmethod credential-fill (url)\n  \"Call git credential fill to retrieve credentials for the given URL.\nReturns a list of (username password) or NIL if no credentials found.\"\n  (let* ((input (format nil \"url=~a\"\n                        url)))\n    (multiple-value-bind (output error-output exit-code)\n        (uiop:run-program (list \"git\" \"credential\" \"fill\")\n                          :input (make-string-input-stream input)\n                          :output :string\n                          :error-output :string\n                          :ignore-error-status t)\n      (declare (ignore error-output))\n      (when (zerop exit-code)\n        (let ((lines (str:lines output))\n              username\n              password)\n          (dolist (line lines)\n            (let ((parts (str:split \"=\" line :limit 2)))\n              (when (= 2 (length parts))\n                (cond\n                  ((equal \"username\" (first parts))\n                   (setf username (second parts)))\n                  ((equal \"password\" (first parts))\n                   (setf password (second parts)))))))\n          (when (and username password)\n            (list username password)))))))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/gradle.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/gradle\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/adb-puller\n                #:pull-file\n                #:external-data-dir\n                #:adb-puller)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:streamed-image\n                #:abstract-image)\n  (:import-from #:screenshotbot/sdk/android\n                #:read-image\n                #:image-bundle)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:single-directory-run)\n  (:local-nicknames (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:adb-puller #:screenshotbot/sdk/adb-puller))\n  (:export\n   #:main))\n(in-package :screenshotbot/sdk/gradle)\n\n(defclass adb-image (abstract-image)\n  ((pathname :initarg :pathname\n             :accessor remote-pathname\n             :documentation \"The remote pathname of the device\"))\n  (:documentation \"An image that is by default pulled from the remote device via adb.\"))\n\n(defclass facebook-bundle (image-bundle)\n  ((adb :initarg :adb\n        :reader adb)\n   (remote-metadata-pathname :initarg :remote-metadata-pathname\n                             :reader remote-metadata-pathname)\n   (tests-run-id :initarg :tests-run-id\n                 :reader tests-run-id\n                 :documentation \"There's a feature in the library, where they try to store images in\nthe same directory across multiple instrumentation test runs. In that\nformat, all the files are in the $DIR/$test-run-id/name.png.\n\nThis doesn't happen in Firebase, probably because\nSCREENSHOT_TESTS_RUN_ID is not set. So we should probably try handling\nthe situation that this is empty.\")\n   (tmpdir :initarg :tmpdir\n           :reader tmpdir)))\n\n(defmethod read-image ((bundle facebook-bundle)\n                       name)\n  (let* ((name (make-pathname :type \"png\"\n                              :name name))\n         (cache-file (path:catfile (tmpdir bundle) name)))\n    (flet ((read-cache ()\n             (imago:read-image cache-file)))\n      (cond\n        ((path:-e cache-file)\n         (read-cache))\n        (t\n         (adb-puller:pull-file\n          (adb bundle)\n          (path:catfile (remote-metadata-pathname bundle)\n                        (if (tests-run-id bundle)\n                            (format nil \"~a/\" (tests-run-id bundle))\n                            \"\")\n                        name)\n          cache-file)\n         (read-cache))))))\n\n(defun prepare-image ()\n  (log:config :sane :immediate-flush t)\n  (log:config :info))\n\n(defmacro def-ext-fun (name args &body body)\n  `(progn\n     (defun ,name ,args\n       ,@body\n       1)\n     (lw:deliver-keep-symbol-names ',name)\n     (lw:deliver-keep-symbols ',name)))\n\n(def-ext-fun record-facebook-task (adb package channel\n                                       api-key\n                                       api-secret)\n  (let ((adb (make-instance 'adb-puller :exec adb)))\n    (let* ((sdcard (external-data-dir adb))\n           (metadata (path:catfile\n                      sdcard \"screenshots/\"\n                      (format nil \"~a/\" package)\n                      \"screenshots-default/metadata.json\"))\n           (test-run-id-remote (path:catfile metadata \"tests_run_id\")))\n      (uiop:with-temporary-file (:prefix \"metadata\" :type \"xml\"\n                                 :pathname p)\n        (pull-file\n         adb\n         metadata\n         p)\n        (log:info \"Retrieved metadata.json\")\n        (uiop:with-temporary-file (:pathname test-run-id)\n          (pull-file adb test-run-id-remote test-run-id)\n          (tmpdir:with-tmpdir (tmpdir)\n            (let ((bundle (make-instance 'facebook-bundle\n                                         :metadata p\n                                         :remote-metadata-pathname metadata\n                                         :tests-run-id (str:trim (uiop:read-file-string test-run-id))\n                                         :adb adb\n                                         :tmpdir tmpdir)))\n              (let ((flags:*main-branch* \"master\")\n                    (flags:*api-key* api-key)\n                    (flags:*api-secret* api-secret))\n                (sdk:parse-org-defaults)\n                (single-directory-run bundle :channel channel)))))))))\n\n(defun read-sym (a)\n  (find-symbol\n   (str:upcase a)\n   #.(find-package :screenshotbot/sdk/gradle)))\n\n(defun main ()\n  (uiop:setup-command-line-arguments)\n  (prepare-image)\n\n  (let ((args (cdr\n               #+lispworks system:*line-arguments-list*\n               #-lispworks (uiop:command-line-arguments))))\n    (format t \"Got args2: ~S~%\" args)\n    (apply (read-sym (car args))\n           (cdr args)))\n  (uiop:quit 0))\n"
  },
  {
    "path": "src/screenshotbot/sdk/health-checks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/health-checks\n  (:use #:cl)\n  (:import-from #:util/health-check\n                #:def-health-check))\n(in-package :screenshotbot/sdk/health-checks)\n\n(defvar *crlf* (format nil \"~a~a\" #\\Return #\\Linefeed))\n\n(def-health-check upload-large-file-multiple-times ()\n  ;; See T1252\n  #+(and lispworks darwin) ;; Avoid this health check on Prod\n  (let ((content (make-array 333333 :element-type '(unsigned-byte 8) :initial-element 0))\n        (hostname\n          \"screenshotbot.io\"))\n    (flet ((make-request (&key read-timeout)\n             (log:debug \"Making request\")\n             (let ((conn (comm:open-tcp-stream hostname 443\n                                               :ssl-ctx drakma::*verifying-context*\n                                               :read-timeout read-timeout)))\n               (assert conn)\n               (write-string \"PUT /api/upload-test HTTP/1.1\" conn)\n               (write-string *crlf* conn)\n               (write-string \"Content-Length: 333333\" conn)\n               (Write-string *crlf* conn)\n               (format conn \"Host: ~a\" hostname)\n               (write-string *crlf* conn)\n               (write-string \"Content-type: application/octet-stream\" conn)\n               (write-string *crlf* conn)\n               (write-string *crlf* conn)\n               (write-sequence content conn)\n               (finish-output conn)\n\n               (read-line conn)\n               (close conn))))\n      (dotimes (i 20)\n        (let ((start-time (get-internal-real-time)))\n          (make-request :read-timeout 30)\n          (let ((end-time (get-internal-real-time)))\n            (when (> (- end-time start-time) 15000)\n              (error \"Upload took too long, probably a bug\"))))))))\n\n#+lispworks\n(def-health-check ssl-hard-eof-T1797 ()\n  \"See T1797\"\n  (let ((conn (comm:open-tcp-stream \"api.screenshotbot.io\" 443\n                                    :ssl-ctx t\n                                    :read-timeout 10)))\n    (write-string \"PUT /api/version HTTP/1.1\" conn)\n    (write-string *crlf* conn)\n    (write-string \"Host: api.screenshotbot.io\" conn)\n    (write-string *crlf* conn)\n    (write-string \"Connection: close\" conn)\n    (write-string *crlf* conn)\n    (write-string \"Content-length: 6\" conn)\n    (write-string *crlf* conn)\n    (write-string *crlf* conn)\n    (write-string \"foobar\" conn)\n    (finish-output conn)\n    (uiop:slurp-stream-string conn)\n    (close conn)))\n"
  },
  {
    "path": "src/screenshotbot/sdk/help.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/sdk/help\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:obsolete?)\n  (:export\n   #:help))\n(in-package :screenshotbot/sdk/help)\n\n\n(defun help ()\n  (format t \"Screenshotbot Recorder script~%~%\")\n  (format t \"Usage: recorder [options]~%~%\")\n  (format t \"Use this script from your CI pipelines or locally to\nupload screenshots and generate reports~%~%\")\n  (format t \"We are currently migrating the commands from a single top-level\ncommand to multiple subcommands. Use `recorder help` to get documentation\nfor subcommands.~%~%\")\n  (format t \"Options:~%~%\")\n  (loop for (name . flag) in (sort (copy-list com.google.flag::*registered-flags*)\n                                   #'string< :key #'car) do\n    (let* ((lines (mapcar #'str:trim (str:lines (com.google.flag::help flag))))\n           (lines (loop for line in lines\n                        for start from 0\n                        if (= start 0)\n                          collect line\n                        else\n                          collect (str:concat \" \" line)))\n           (lines (cond\n                    ((< (length name) 22)\n                     lines)\n                    (t\n                     (list* \"\" lines)))))\n      (unless (obsolete? flag)\n        (format t \" --~22A~40A~%\" name\n                (or (car lines) \"\"))\n        (loop for l in (cdr lines) do\n          (format t \"~25A~A~%\" \" \" l)))))\n  (format t \"~%Copyright 2020-2022 Modern Interpreters Inc.~%\")\n  (format t \"Please reach out to support@screenshotbot.io for any questions~%\"))\n"
  },
  {
    "path": "src/screenshotbot/sdk/hostname.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/hostname\n  (:use #:cl)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags))\n  (:export\n   #:api-hostname\n   #:format-api-url))\n(in-package :screenshotbot/sdk/hostname)\n\n(defun api-hostname (&key (hostname (error \"must provide :hostname\")))\n  (assert (str:non-empty-string-p hostname))\n  (cond\n    ((not (str:containsp \"/\" hostname))\n     (format nil \"https://~a\" hostname))\n    (t\n     hostname)))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/install.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/install\n  (:use #:cl)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:screenshotbot/api/model\n                #:installation-url\n                #:encode-json)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:json-api-context\n                #:api-context\n                #:fetch-version)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:request)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/sentry\n                #:with-sentry)\n  (:export\n   #:install-credentials\n   #:install/command))\n(in-package :screenshotbot/sdk/install)\n\n(defun fix-hostname (hostname)\n  (multiple-value-bind (number version)\n      (fetch-version (make-instance 'api-context :hostname hostname))\n    (declare (ignore number))\n    (installation-url version)))\n\n(defun install-credentials (hostname &key no-stdin token)\n  (let ((hostname (fix-hostname hostname)))\n    (cond\n      ((not (str:emptyp token))\n       (install-pasted hostname token))\n      (t\n       (format t \"LOGIN\nOpen this page in your browser and log in if necessary:\n\n~a\n\nThen paste the Token on that page below.\" (quri:merge-uris \"/api-keys/cli\" hostname))\n       (finish-output t)\n\n       (unless no-stdin\n         (flet ((prompt ()\n                  (format t \"\n\n    Paste Token from that page: \")\n                  (finish-output t)))\n           (prompt)\n           (loop for line = (str:trim (read-line))\n                 while (str:emptyp line)\n                 do (prompt)\n                 finally\n                    (install-pasted hostname line)\n                    (format t \"~%Your credentials are now installed~%~%\"))))))))\n\n\n(defun credential-file ()\n  (ensure-directories-exist\n   (pathname \"~/.config/screenshotbot/credentials\")))\n\n(defun install-pasted (hostname line)\n  (let ((file (credential-file)))\n    (with-open-file (stream file :direction :output :if-exists :supersede)\n      (destructuring-bind (key secret) (str:split \":\" (str:trim line))\n        (let ((api-ctx (make-instance 'json-api-context\n                         :hostname hostname\n                         :key key\n                         :secret secret)))\n          (request\n           api-ctx\n           \"/api/finalize-cli\"\n           :method :post\n           :parameters `((\"hostname\" . ,(uiop:hostname))))\n          (write-string\n           (encode-json\n            api-ctx)\n           stream))))))\n\n\n(defun install/command ()\n  (clingon:make-command\n   :name \"install\"\n   :description \"Installs API keys to connect to your Screenshotbot server\"\n   :options (list\n             (make-option\n              :string\n              :long-name \"token\"\n              :initial-value nil\n              :description \"A token to install non-interactively\"\n              :key :token)\n             (make-option\n              :flag\n              :long-name \"no-stdin\"\n              :initial-value nil\n              :description \"[internal use only]\"\n              :key :no-stdin))\n   :handler (lambda (cmd)\n              (with-sentry ()\n                (install-credentials (getopt cmd :hostname)\n                                     :no-stdin (getopt cmd :no-stdin)\n                                     :token (getopt cmd :token))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/installer.sh",
    "content": "#!/bin/sh\n\nset +e\n\nif [ \"$SCREENSHOTBOT_DIR\" = \"\" ] ; then\n    SCREENSHOTBOT_DIR=~/screenshotbot\nfi\n\nRECORDER=$SCREENSHOTBOT_DIR/recorder\nmkdir $SCREENSHOTBOT_DIR 2>/dev/null || true\nrm -f $RECORDER\ncp sdk $RECORDER\nchmod a+x $RECORDER\n\n\nif [ -e sdk.lwheap ] ; then\n    rm -f $RECORDER.lwheap\n    cp sdk.lwheap $RECORDER.lwheap\nfi\n\necho \"Installed $RECORDER\"\n"
  },
  {
    "path": "src/screenshotbot/sdk/integration-fixture.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/integration-fixture\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/server\n                #:*acceptor*)\n  (:import-from #:util/hunchentoot-engine\n                #:hunchentoot-engine)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/api/core\n                #:*wrap-internal-errors*)\n  (:import-from #:screenshotbot/sdk/backoff\n                #:too-many-requests)\n  (:import-from #:util/throttler\n                #:ignore-throttling)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:a #:alexandria)\n                    (#:dto #:screenshotbot/api/model)\n                    (#:api-key #:core/api/model/api-key)))\n(in-package :screenshotbot/sdk/integration-fixture)\n\n(defclass fake-api-context (api-context)\n  ()\n  (:default-initargs :engine (make-instance 'hunchentoot-engine\n                                            :acceptor *acceptor*)))\n\n\n(def-easy-macro with-sdk-integration (&binding api-context &key &binding company &fn fn)\n  (with-installation ()\n    (with-test-store (:globally t)\n      (let ((auto-restart:*global-enable-auto-retries-p* nil))\n        (let* ((company (make-instance 'company))\n               (user (make-instance 'user))\n               (api-key (make-instance 'api-key:api-key\n                                       :user user\n                                       :company company))\n               (api-context (make-instance 'fake-api-context\n                                           :key (api-key:api-key-key api-key)\n                                           :hostname \"localhost\"\n                                           :secret (api-key:api-key-secret-key api-key))))\n          (let ((*wrap-internal-errors* nil))\n            (fn api-context company)))))))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/integration-tests.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/integration-tests\n  (:use #:cl\n        #:fiveam)\n  (:shadow #:run)\n  (:import-from #:util/testing\n                #:with-local-acceptor)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key-secret-key\n                #:api-key-key)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key-secret)\n  (:import-from #:screenshotbot/user-api\n                #:can-view\n                #:can-view!))\n(in-package :screenshotbot/sdk/integration-tests)\n\n(def-suite* :integration-tests)\n\n(defmacro with-repo (&body body)\n  `(tmpdir:with-tmpdir (dir)\n     (run (list \"git\" \"clone\" \"https://github.com/tdrhq/fast-example\" dir))\n     (unwind-protect\n          (let ((*default-cmd-dir* dir))\n            ,@body)\n\n       ;; On Windows, for some reason I'm unable to delete the .git\n       ;; directory. Well, not *some* reason. It's just because the\n       ;; directories are all marked as read-only. I don't know how\n       ;; to clear that up at the moment, so I'm using the nuclear\n       ;; option.\n       #+(or mswindows win32)\n       (run (list \"attrib\" \"-r\" (format nil \"~a\\\\.git\\\\*.*\" (namestring dir)) \"/s\")))))\n\n(defvar *sdk* (car (asdf:output-files 'asdf:compile-op\n                                       (asdf:find-component :screenshotbot.sdk/deliver\n                                                            \"deliver-sdk\"))))\n(defvar *default-cmd-dir* \".\")\n\n(defun run (cmd &rest args &key (directory *default-cmd-dir*) &allow-other-keys)\n  (let* ((cmd (loop for x in cmd\n                    if (pathnamep x)\n                      collect (namestring x)\n                    else collect x))\n         (cmd (loop for x in cmd\n                    collect (uiop:escape-shell-command x)))\n         (cmd (str:join \" \" cmd))\n         (cmd (format nil \"cd ~a && ~a\"\n                      (uiop:escape-shell-command\n                       (cond\n                         ((pathnamep directory)\n                          (namestring directory))\n                         (t\n                          directory)))\n                      cmd)))\n    (log:info \"running: ~s\" cmd)\n    (apply #'uiop:run-program\n           (append\n            (cond\n              ((uiop:os-windows-p)\n               (list \"cmd\" \"/c\"))\n              (t\n               (list \"bash\" \"-c\")))\n            (list\n             cmd))\n           (append\n            (remove-from-plist args :directory)\n            (list :output t\n                  :error-output t)))))\n\n(def-easy-macro with-env (name value &fn fn)\n  (setf (uiop:getenv name) value)\n  (log:info \"Setting env ~a to ~a\" name value)\n  (unwind-protect\n       (fn)\n    (setf (uiop:getenv name) nil)))\n\n(def-fixture state ()\n  (setf (uiop:getenv \"TDRHQ_IGNORE_SENTRY\") \"true\")\n  (with-installation (:globally t)\n   (with-test-store (:globally t)\n     (with-local-acceptor (host) ('screenshotbot/server:acceptor)\n       (with-env (\"SCREENSHOTBOT_API_KEY\" \"\")\n         (with-env (\"SCREENSHOTBOT_API_SECRET\" \"\")\n           (with-env (\"SCREENSHOTBOT_API_HOSTNAME\" host)\n             (with-test-user (:user user :company company :api-key api-key)\n               (assert (can-view company user))\n               (with-env (\"SCREENSHOTBOT_API_KEY\" (api-key-key api-key))\n                 (with-env (\"SCREENSHOTBOT_API_SECRET\" (api-key-secret-key api-key))\n                   (&body)))))))))))\n\n(test preconditions\n  (with-fixture state ()\n    (pass)))\n\n(def-easy-macro with-v2 (&fn fn)\n  (with-env (\"SCREENSHOTBOT_CLI_V2\" \"true\")\n    (fn)))\n\n(test mark-failed\n  (with-fixture state ()\n    (with-repo\n      (finishes\n        (run (list *sdk*\n                   \"--mark-failed\"\n                   \"--channel=foo\"))))))\n\n(test record-and-verify-unchanged\n  (with-fixture state ()\n    (with-v2 ()\n      (with-repo\n        (run (list \"./gen.sh\"))\n        (finishes\n          (run (list *sdk* \"dev\" \"record\" \"--directory\" \"screenshots\" \"--channel\" \"bleh\")))\n        (finishes\n          (run (list *sdk* \"dev\" \"verify\" \"--directory\" \"screenshots\" \"--channel\" \"bleh\")))))))\n\n(test record-and-verify-changed\n  (with-fixture state ()\n    (with-v2 ()\n      (with-repo\n        (run (list \"./gen.sh\"))\n        (finishes\n          (run (list *sdk* \"dev\" \"record\" \"--directory\" \"screenshots\" \"--channel\" \"bleh\")))\n\n        (with-open-file (stream (path:catfile *default-cmd-dir*\n                                              \"ascii.txt\")\n                                :direction :output :if-exists :supersede)\n          (write-string \"text 15,15 \\\"duh\\\" \" stream))\n        (run (list \"./gen.sh\"))\n        (multiple-value-bind (stdout stderr ret)\n            (run (list *sdk* \"dev\" \"verify\" \"--directory\" \"screenshots\" \"--channel\" \"bleh\")\n                 :ignore-error-status t)\n          (is (eql 1 ret)))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/main.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/main\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/help\n                #:help)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:chdir-for-bin)\n  (:import-from #:util/threading\n                #:with-tags\n                #:maybe-log-sentry\n                #:*warning-count*\n                #:with-extras\n                #:*extras*\n                #:funcall-with-sentry-logs)\n  (:import-from #:screenshotbot/sdk/version-check\n                #:*client-version*)\n  (:import-from #:util/health-check\n                #:def-health-check\n                #:run-health-checks)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/sdk/failed-run\n                #:mark-failed)\n  (:import-from #:screenshotbot/sdk/unchanged-run\n                #:mark-unchanged-run)\n  (:import-from #:screenshotbot/sdk/finalized-commit\n                #:finalize-commit)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-feature-enabled-p\n                #:remote-version\n                #:api-context)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:api-hostname)\n  (:import-from #:screenshotbot/sdk/env\n                #:make-env-reader)\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:define-flag)\n  (:import-from #:com.google.flag\n                #:parse-command-line)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:api-context #:screenshotbot/sdk/api-context)                    \n                    (#:e #:screenshotbot/sdk/env)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:static #:screenshotbot/sdk/static)\n                    (#:firebase #:screenshotbot/sdk/firebase))\n  ;; TODO: delete\n  #+lispworks\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:*api-key*\n                #:*hostname*\n                #:*api-secret*)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:root/command)\n  (:import-from #:screenshotbot/sdk/sentry\n                #:with-sentry)\n  (:import-from #:util/request\n                #:engine)\n  (:import-from #:util/reused-ssl\n                #:with-reused-ssl)\n  (:import-from #:screenshotbot/sdk/xcresult\n                #:make-xcresults-bundle\n                #:xcresults-attachment-bundle)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:close-bundle)\n  (:import-from #:screenshotbot/sdk/server-log-appender\n                #:flush-server-appender\n                #:make-server-log-appender)\n  (:export\n   #:main))\n\n(in-package :screenshotbot/sdk/main)\n\n(define-flag *api-key*\n  :selector \"api-key\"\n  :default-value nil\n  :type (or null string)\n  :help \"Screenshotbot API Key. Defaults to $SCREENSHOTBOT_API_KEY.\")\n\n(define-flag *api-secret*\n  :selector \"api-secret\"\n  :default-value nil\n  :type (or null string)\n  :help \"Screenshotbot API Secret. Defaults to $SCREENSHOTBOT_API_SECRET\")\n\n(define-flag *hostname*\n  :selector \"api-hostname\"\n  :default-value \"\"\n  :type string\n  :help \"The API endpoint we connect to. This is not required for most users,\nsince the hostname is encoded in the API secret. If your Screenshotbot\ndomain was moved, then this might be useful. We also read\nSCREENSHOTBOT_API_HOSTNAME environment variable.\")\n\n\n(def-easy-macro with-defaults (&binding api-context &fn fn)\n  (sdk:parse-org-defaults)\n  (let ((api-context (make-api-context)))\n    (with-extras ((\"cli-session-id\" (api-context:session-id api-context)))\n      (with-reused-ssl ((engine api-context))\n        (let ((version (remote-version api-context)))\n          (log:debug \"Remote version is ~a\" version))\n        (let ((server-appender (make-server-log-appender api-context)))\n          (when (api-feature-enabled-p api-context :server-cli-logs)\n            (log4cl:add-appender log4cl:*root-logger*\n                                 server-appender))\n          (when (api-feature-enabled-p api-context :debug-cli-logs)\n            (log:config :debug))\n          (unwind-protect\n               (funcall fn api-context)\n            (when (api-feature-enabled-p api-context :server-cli-logs)\n              (flush-server-appender server-appender))))))))\n\n\n(defun emptify (s)\n  \"If the string is empty, return nil\"\n  (if (str:emptyp s) nil s))\n\n(defvar *api-key-info* \"You can also use $SCREENSHOTBOT_API_KEY and $SCREENSHOTBOT_API_SECRET environment variable in CI.\")\n\n(defun make-api-context (&key (env (make-env-reader)))\n  (let ((key (or (emptify *api-key*)\n                 (e:api-key env)))\n        (secret (or (emptify *api-secret*)\n                    (e:api-secret env))))\n    (when (str:emptyp key)\n      (error \"No --api-key provided. ~a\" *api-key-info* ))\n    (when(str:emptyp secret)\n      (error \"No --api-secret provided. ~a\" *api-key-info*))\n    (make-instance 'api-context\n                   :key key\n                   :secret secret\n                   :hostname (or (emptify *hostname*)\n                                 (emptify (e:api-hostname env))))))\n\n(defun try-clingon (argv)\n  (clingon:run (root/command) (cdr argv)))\n\n(defun warn-when-obsolete-flags ()\n  (macrolet ((test (flag)\n               `(when ,flag\n                  (warn \"obsolete flag used: ~a ~a\"\n                         ',flag ,flag))))\n    (test flags:*ios-multi-dir*)\n    (test flags:*branch*)\n    (test flags:*lang-regex*)\n    (test flags:*device-regex*)\n    (test flags:*ios-diff-dir*)))\n\n(defun maybe-setup-ssl ()\n  #+ (and lispworks linux)\n  (let* ((potential-paths (list \"/usr/lib/libssl.so.3\"\n                                \"/usr/lib/libssl.so.1.1\"))\n         (path (loop for path in potential-paths\n                     if (path:-e path)\n                       return path)))\n    (when path\n      (handler-case\n          (comm:ensure-ssl)\n        (error ()\n          (log:warn \"Default libssl paths failed, trying ~a\" path)\n          (comm:ensure-ssl :library-path path))))))\n\n(auto-restart:with-auto-restart ()\n  (defun maybe-test-stack-overflow! ()\n    (when (str:non-empty-string-p (uiop:getenv \"SCREENSHOTBOT_TEST_STACK_OVERFLOW\"))\n      (setf drakma:*header-stream* *standard-output*)\n      (maybe-test-stack-overflow!))))\n\n(defun %main (&optional (argv #+lispworks system:*line-arguments-list*\n                              #-lispworks (uiop:raw-command-line-arguments)))\n  ;; We used to do :immediate-flush, but it looks like that might be\n  ;; causing issues with Gradle. See T1514.\n  (log:config :sane :pattern \"[%D{%H:%M:%S}] %5p: %m%n\")\n  (log:config :info)\n\n  (log:info \"Screenshotbot SDK v~a\" *client-version*)\n\n  (maybe-setup-ssl)\n\n  (maybe-test-stack-overflow!)\n\n  (let ((unrecognized  (parse-command-line (cdr argv))))\n    (when flags:*verbose*\n      (log:config :debug :oneline))\n    (log:debug \"Run this in interactive shell: ~S\"\n               `(progn\n                  (chdir-for-bin ,(uiop:getcwd))\n                  (%main ',argv)))\n\n    (warn-when-obsolete-flags)\n    (cond\n      (unrecognized\n       (cond\n         ((or\n           (not (str:emptyp (uiop:getenv \"SCREENSHOTBOT_CLI_V2\")))\n           (str:s-member argv \"dev\")\n           (str:s-member argv \"ci\")\n           (str:s-member argv \"batch\")\n           (str:s-member argv \"download-run\")\n           (str:s-member argv \"pull\")\n           #+nil ;; References \"ci record\" which doesn't exit\n           (str:s-member argv \"help\"))\n          (log:debug \"Enabling V2 of Screenshotbot CLI interface\")\n          (try-clingon argv))\n         (t\n           (format t \"Unrecognized arguments: ~a~%\" (Str:join \" \" unrecognized))\n           (help)\n           (uiop:quit 1))))\n      (flags:*help*\n       (help))\n      (flags:*versionp*\n       ;; We've already printed the version by this point\n       nil)\n      (flags:*self-test*\n       (uiop:quit (if (run-health-checks) 0 1)))\n      (flags:*mark-failed*\n       (with-defaults (api-context)\n         (mark-failed api-context)))\n      (flags:*unchanged-from*\n       (with-defaults (api-context)\n         (mark-unchanged-run api-context)))\n      (flags:*static-website*\n       (with-defaults (api-context)\n         (static:record-static-website api-context flags:*static-website*\n                                       :production flags:*production*\n                                       :channel flags:*channel*\n                                       :repo-url flags:*repo-url*\n                                       :browser-configs flags:*browser-configs*\n                                       :main-branch flags:*main-branch*\n                                       :assets-root flags:*static-website-assets-root*)))\n      (flags:*firebase-output*\n       (firebase:with-firebase-output (flags:*firebase-output*)\n         (with-defaults (api-context)\n           (sdk:run-prepare-directory-toplevel api-context))))\n      (flags:*finalize*\n       (with-defaults (api-context)\n         (finalize-commit api-context)))\n      (flags:*xcresult*\n       (when flags:*directory*\n         (log:warn \"Ignoring --directory since xcresult is provided\"))\n       (with-defaults (api-context)\n         (let ((directory (make-xcresults-bundle flags:*xcresult*)))\n           (prog1\n               (sdk:single-directory-run api-context directory :channel flags:*channel*)\n             (close-bundle directory)))))\n      (flags:*directory*\n       (with-defaults (api-context)\n         (sdk:run-prepare-directory-toplevel api-context)))\n      (t\n       (help)\n       (warn \"No --directory provided\")\n       (uiop:quit 1)))))\n\n(defun main (&rest args)\n  (uiop:setup-command-line-arguments)\n\n  #-screenshotbot-oss\n  (sentry-client:initialize-sentry-client\n   \"https://b0c00f1b8a0e499fbdba1de5013fc97b:0632f4ab8db64d55accd4b81c93e7b79@sentry.io/1460546\"\n   :client-class 'sentry:delivered-client)\n\n  (with-sentry ()\n    (apply '%main args))\n\n  #-sbcl\n  (log4cl::exit-hook)\n  (uiop:quit 0))\n\n#-screenshotbot-oss\n(def-health-check sentry-logger ()\n  (let ((out-stream (make-string-output-stream)))\n    (restart-case\n        (with-sentry (:on-error (lambda ()\n                                  (invoke-restart 'expected))\n                      :stream out-stream\n                      :dry-run t)\n          (error \"health-check for sdk\"))\n     (expected ()\n       nil))\n    (assert (cl-ppcre:scan \".*RUN-HEALTH-CHECKS.*\"\n                           (get-output-stream-string out-stream)))))\n\n#+nil ;; too noisy, and less important\n(def-health-check sentry-logger-for-warnings ()\n  (let ((out-stream (make-string-output-stream)))\n    (with-sentry (:dry-run t)\n      (warn \"warning health-check for sdk\"))))\n\n#+lispworks\n(lw:defadvice (clingon.utils:exit clingon-exit-warning :before) (&optional (code 0))\n  (unless (= code 0)\n    (warn \"Exiting with code: ~a~%\" code))\n  (finish-output *error-output*)\n\n  ;; The actual clingon implementation will not exit, because it will\n  ;; check for SLY. :/ So we're hardcoding an exit for now.\n  (uiop:quit code))\n"
  },
  {
    "path": "src/screenshotbot/sdk/metadata.json",
    "content": "[{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault\",\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_0_1\",\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_0\",\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_1_1\"],\"testClass\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest\",\"testName\":\"testDefault\",\"tileHeight\":2,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.ExampleScreenshotTest_testDefault_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault\",\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest\",\"testName\":\"testDefault\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.ImageRowScreenshotTest_testDefault_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"testScreenshotEntireActivityWithoutAccessibilityMetadata\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivityWithoutAccessibilityMetadata_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"warningTextShouldBeYellow\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_warningTextShouldBeYellow_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/fab\"],\"axIssues\":\"fab_issues.json\",\"extras\":{},\"name\":\"fab\",\"relativeFileNames\":[\"fab\"],\"testClass\":\"unknown\",\"testName\":\"unknown\",\"tileHeight\":1,\"tileWidth\":1,\"viewHierarchy\":\"fab_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"testScreenshotEntireActivity\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_testScreenshotEntireActivity_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"errorTextShouldBeRed\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_errorTextShouldBeRed_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"mainActivityTestSettingsOpen\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_mainActivityTestSettingsOpen_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_0_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_1_3\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_0\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_1\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_2\",\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_2_3\"],\"testClass\":\"com.facebook.testing.screenshot.sample.MainActivityTest\",\"testName\":\"okTextShouldBeGreen\",\"tileHeight\":4,\"tileWidth\":3,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.MainActivityTest_okTextShouldBeGreen_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering\",\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest\",\"testName\":\"testRendering\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testRendering_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText\",\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest\",\"testName\":\"testLongText\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testLongText_dump.json\"},{\"absoluteFilesNames\":[\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese\",\"/sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0\"],\"axIssues\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_issues.json\",\"extras\":{},\"name\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese\",\"relativeFileNames\":[\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese\",\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_1_0\"],\"testClass\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest\",\"testName\":\"testChinese\",\"tileHeight\":1,\"tileWidth\":2,\"viewHierarchy\":\"com.facebook.testing.screenshot.sample.StandardAndroidViewTest_testChinese_dump.json\"}]"
  },
  {
    "path": "src/screenshotbot/sdk/package.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n#+nil\n(defpackage :screenshotbot-sdk\n  (:export #:main))\n"
  },
  {
    "path": "src/screenshotbot/sdk/pull.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/pull\n  (:use #:cl)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:with-clingon-api-context)\n  (:import-from #:screenshotbot/sdk/fetch-run\n                #:save-runs-from-commit\n                #:save-run)\n  (:export\n   #:pull/command))\n(in-package :screenshotbot/sdk/pull)\n\n(defun download-run/command (&key\n                               (name \"download-run\")\n                               (description \"[DEPRECATED] Use `pull run` instead.\"))\n  (clingon:make-command\n   :name name\n   :handler (lambda (cmd)\n              (with-clingon-api-context (api-context cmd)\n                (save-run\n                 api-context\n                 (getopt cmd :run-id)\n                 :output\n                 (format nil \"~a/\" (or\n                                    (getopt cmd :output)\n                                    (format nil \"./~a\" (getopt cmd :run-id))))\n                 :channel (getopt cmd :channel)\n                 :branch (getopt cmd :branch))))\n   :description description\n   :options (list\n             (make-option\n              :string\n              :long-name \"id\"\n              :initial-value nil\n              :description \"The ID of the run, this is the ID you see in https://screenshotbot.io/runs/<ID>. Be aware that you cannot use a report ID here.\"\n              :key :run-id)\n             (make-option\n              :string\n              :long-name \"channel\"\n              :initial-value nil\n              :key :channel\n              :description \"A channel name, to provide instead of the --id, which will be used to download the active channel\")\n             (make-option\n              :string\n              :long-name \"branch\"\n              :initial-value nil\n              :key :branch\n              :description \"The branch to disambiguate active runs on the channel. By default, we'll pick a branch that matches `main` or `master`.\")\n             (make-option\n              :string\n              :long-name \"output\"\n              :initial-value nil\n              :description \"The output directory to save the images. If not present it will default to  ./<id>\"\n              :key :output))))\n\n(defun run/command ()\n  (download-run/command\n   :name \"run\"\n   :description \"Use this to download a run and all of its images locally.\"))\n\n(defun commit/command ()\n  (clingon:make-command\n   :name \"commit\"\n   :handler #'commit/handler\n   :options (list\n             (make-option\n              :string\n              :long-name \"output\"\n              :initial-value nil\n              :key :output\n              :description \"The output directory to save the images. If not present it will default to ./<commit>\"))\n   :description \"Pull all screenshots for a given commit\"))\n\n(defun commit/handler (cmd)\n  (with-clingon-api-context (api-context cmd)\n   (let ((args (clingon:command-arguments cmd)))\n     (unless (equal 1 (length args))\n       (error \"You must provide one argument, which is the commit SHA\"))\n     (let ((sha (elt args 0)))\n       (pull-commit api-context sha :output (clingon:getopt cmd :output))))))\n\n(defun pull-commit (api-context sha &key output)\n  (save-runs-from-commit api-context sha\n                         :output (or output (format nil \"./~a/\" sha))))\n\n(defun pull/command ()\n  (clingon:make-command\n   :name \"pull\"\n   :handler (lambda (cmd)\n              (clingon:print-usage-and-exit cmd t))\n   :description \"Commands to existing screenshots from Screenshotbot\"\n   :sub-commands (list\n                  (run/command)\n                  (commit/command))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/request\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*\n                #:encode-json)\n  (:import-from #:util/request\n                #:engine\n                #:http-request)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:format-api-url)\n  (:import-from #:screenshotbot/sdk/version-check\n                #:*client-version*\n                #:remote-supports-basic-auth-p)\n  (:import-from #:screenshotbot/sdk/backoff\n                #:maybe-retry-request)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class\n                #:json-mop-to-string)\n  (:import-from #:anaphora\n                #:it\n                #:awhen)\n  (:import-from #:alexandria\n                #:assoc-value\n                #:when-let)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:dto #:screenshotbot/api/model)\n                    (#:e #:screenshotbot/sdk/env)\n                    (#:api-context #:screenshotbot/sdk/api-context)\n                    (#:android   #:screenshotbot/sdk/android)\n                    (#:run-context #:screenshotbot/sdk/run-context))\n  (:export\n   #:api-error\n   #:ensure-api-success\n   #:request))\n(in-package :screenshotbot/sdk/request)\n\n(define-condition api-error (error)\n  ((message :initarg :message)))\n\n(defmethod print-object ((e api-error) stream)\n  (with-slots (message) e\n   (format stream \"#<API-ERROR ~a>\" message)))\n\n\n(defun ensure-api-success (result)\n  (let ((indent \"    \"))\n   (awhen (assoc-value result :error)\n     (log:error \"API error: ~a\" it)\n     (when-let ((stacktrace (assoc-value result :stacktrace)))\n      (log:error \"Server stack trace: ~%~a~a\"\n                 indent\n                 (str:join (format nil \"~%~a\" indent)\n                           (str:lines stacktrace))))\n     (error 'api-error :message it)))\n  (assoc-value result :response))\n\n\n\n\n(defmethod %make-basic-auth (api-context)\n  (list\n   (api-context:key api-context)\n   (api-context:secret api-context)))\n\n\n(auto-restart:with-auto-restart (:attempt attempt)\n  (defun %request (api-context\n                   api &key (method :post)\n                         parameters\n                         content\n                         (auto-retry t)\n                         (backoff 2))\n    ;; TODO: we're losing the response code here, we need to do\n    ;; something with it.\n\n    (when (and auto-retry\n               (streamp content))\n      (warn \"auto-retry won't work for stream content\"))\n\n    (multiple-value-bind (stream response-code)\n        (handler-bind ((error (lambda (e)\n                                (warn \"Retrying request because of error, is this a timeout? ~a\" e)\n                                (when auto-retry\n                                  (maybe-retry-request\n                                   408 ;; This is most likely the case this low down\n                                   :attempt attempt\n                                   :restart 'retry-%request\n                                   :errorp nil\n                                   :backoff backoff)))))\n          (http-request\n           (format-api-url api-context api)\n           :method method\n           :want-stream t\n           :method method\n           :basic-authorization (when (remote-supports-basic-auth-p api-context)\n                                  (%make-basic-auth api-context))\n           :additional-headers `((\"X-client-version\" . ,*client-version*)\n                                 (\"X-client-api-version\" . ,*api-version*))\n           :content content\n           :external-format-out :utf-8\n           :read-timeout 45\n           :engine (api-context:engine api-context)\n           :parameters (cond\n                         ((remote-supports-basic-auth-p api-context)\n                          parameters)\n                         (t (list*\n                             (cons \"api-key\" (api-context:key api-context))\n                             (cons \"api-secret-key\" (api-context:secret api-context))\n                             parameters)))))\n      (when auto-retry\n        (maybe-retry-request\n         response-code\n         :attempt attempt\n         :restart 'retry-%request\n         :backoff backoff))\n\n      (with-open-stream (stream stream)\n        (values\n         (uiop:slurp-input-stream 'string stream)\n         response-code)))))\n\n\n\n(defmethod request ((api-context api-context:api-context)\n                    api &key (method :post)\n                          parameters\n                          (decode-response t)\n                          content)\n  (log:debug \"Making API request: ~S\" api)\n  (multiple-value-bind (json code)\n      (%request api-context\n                api :method method\n                :parameters parameters\n                :content (cond\n                           ((or\n                             (eql :put method)\n                             (typep (class-of content)\n                                    'ext-json-serializable-class))\n                            (json-mop-to-string\n                             content))\n                           ((and (eql method :post)\n                                 content)\n                            content)))\n    (cond\n      (decode-response\n       (handler-case\n           (let ((result (json:decode-json-from-string json)))\n             (ensure-api-success result))\n         (json:json-syntax-error (e)\n           (error \"Could not parse json:\"\n                  json))))\n      (t\n       (values json code)))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/run-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/run-context\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/git\n                #:git-root\n                #:current-commit\n                #:rev-parse)\n  (:import-from #:alexandria\n                #:remove-from-plist\n                #:when-let)\n  (:import-from #:util/misc\n                #:with-slow-logging\n                #:?.)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:export\n   #:run-context\n   #:flags-run-context\n   #:main-branch\n   #:pull-request-url\n   #:productionp\n   #:repo-url\n   #:git-repo\n   #:env-reader-run-context\n   #:override-commit-hash\n   #:channel\n   #:main-branch-hash\n   #:commit-hash\n   #:merge-base\n   #:repo-clean-p\n   #:work-branch\n   #:build-url\n   #:compare-threshold\n   #:pixel-tolerance\n   #:batch\n   #:gitlab-merge-request-iid\n   #:phabricator-diff-id\n   #:with-flags-from-run-context\n   #:tags\n   #:author\n   #:default-flags-run-context\n   #:shard-spec\n   #:run-context-metadata\n   #:work-branch-is-release-branch-p\n   #:main-branch-hash-internal\n   #:valid-pull-request-url-p\n   #:warn-invalid-pull-request\n   #:warn-invalid-pull-request-url)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:e #:screenshotbot/sdk/env)\n                    (#:dto #:screenshotbot/api/model)                    \n                    (#:git #:screenshotbot/sdk/git)))\n(in-package :screenshotbot/sdk/run-context)\n\n(defmacro wrap-dto ((def klass empty slots))\n  (declare (ignore def empty))\n  `(progn\n     (defclass ,klass ()\n       ,(loop for slot in slots\n              collect\n              (list*\n               (car slot)\n               (remove-from-plist (cdr slot) :json-type :json-key))))\n     (defclass run-context-dto (,klass)\n       ,slots\n       (:metaclass ext-json-serializable-class))\n\n     (defmethod run-context-to-dto (run-context)\n       (make-instance 'run-context-dto\n                      ,@ (loop for slot in slots\n                               for slot-args = (cdr slot)\n                               appending\n                               `(,(getf slot-args :initarg)\n                                 (,(getf slot-args :reader)\n                                  run-context)))))))\n\n(wrap-dto\n (defclass run-context ()\n   ((main-branch :initarg :main-branch\n                 :initform nil\n                 :reader main-branch\n                 :json-type (or null :string)\n                 :json-key \"mainBranch\")\n    (main-branch-hash :initarg :main-branch-hash\n                      :initform nil\n                      :reader main-branch-hash\n                      ;; We need to be able to set and get the default\n                      ;; value of the main-branch-hash, that is\n                      ;; without the overriden part of it. It's not\n                      ;; ideal, and is stateful but that's what we\n                      ;; gotta do for now.\n                      :accessor main-branch-hash-internal\n                      :json-type (or null :string)\n                      :json-key \"mainBranchCommit\")\n    (release-branch-regex :initarg :release-branch-regex\n                          :initform nil\n                          :reader release-branch-regex\n                          :json-type (or null :string)\n                          :json-key \"releaseBranchRegex\")\n    (pull-request-url :initarg :pull-request-url\n                      :initform nil\n                      :reader pull-request-url\n                      :json-type (or null :string)\n                      :json-key \"pullRequestUrl\")\n    (repo-url :initarg :repo-url\n              :initform nil\n              :reader repo-url\n              :json-type (or null :string)\n              :json-key \"repo\")\n    (author :initarg :author\n            :initform nil\n            :reader author\n            :json-type (or null :string)\n            :json-key \"author\")\n    (productionp :initarg :productionp\n                 :initform nil\n                 :reader productionp\n                 :json-type (or null :string)\n                 :json-key \"isTrunk\")\n    (work-branch :initarg :work-branch\n                 :initform nil\n                 :reader work-branch\n                 :json-type (or null :string)\n                 :json-key \"workBranch\")\n    (build-url :initarg :build-url\n               :initform nil\n               :reader build-url\n               :json-type (or null :string)\n               :json-key \"buildUrl\")\n    (gitlab-merge-request-iid :initarg :gitlab-merge-request-iid\n                              :initform nil\n                              :reader gitlab-merge-request-iid\n                              :json-type (or null :string)\n                              :json-key \"gitlabMergeRequestIID\")\n    (phabricator-diff-id :initarg :phabricator-diff-id\n                         :initform nil\n                         :reader phabricator-diff-id\n                         :json-type (or null :string)\n                         :json-key \"phabricatorDiff\")\n    (channel :initarg :channel\n             :initform nil\n             :reader channel\n             :json-type (or null :string)\n             :json-key \"channel\")\n    (override-commit-hash :initarg :override-commit-hash\n                          :initform nil\n                          :reader override-commit-hash\n                          :json-type (or null :string)\n                          :json-key \"overrideCommitHash\")\n    (commit-hash :initarg :commit-hash\n                 :initform nil\n                 :reader commit-hash\n                 :json-type (or null :string)\n                 :json-key \"commit\")\n    (merge-base :initarg :merge-base\n                :initform nil\n                :reader merge-base\n                :json-type (or null :string)\n                :json-key \"mergeBase\")\n    (repo-clean-p :initarg :repo-clean-p\n                  :reader repo-clean-p\n                  :documentation \"No initform!\"\n                  :json-type :boolean\n                  :json-key \"isClean\")\n    (compare-threshold :initarg :compare-threshold\n                       :initform nil\n                       :reader compare-threshold\n                       :json-type :number\n                       :json-key \"compareThreshold\")\n    (pixel-tolerance :initarg :pixel-tolerance\n                     :initform nil\n                     :reader pixel-tolerance\n                     :json-type :number\n                     :json-key \"pixelTolerance\")\n    (batch :initarg :batch\n           :initform nil\n           :reader batch\n           :json-type (or null :string)\n           :json-key \"batch\")\n    (shard-spec :initarg :shard-spec\n                :initform nil\n                :reader shard-spec\n                :documentation \"A dto:shard-spec object indicating the shard\"\n                :json-type (or null :string)\n                :json-key \"shardSpec\")\n    (metadata :initarg :metadata\n              :initform nil\n              :reader run-context-metadata\n              :documentation \"A list of dto:metadata objects\"\n              :json-type (:list dto:metadata)\n              :json-key \"metadata\")\n    (tags :initarg :tags\n          :initform nil\n          :reader tags\n          :json-type (:list :string)\n          :json-key \"tags\"))))\n\n(defclass env-reader-run-context ()\n  ((env :initarg :env\n        :reader env)))\n\n(defmethod %git-repo ((self env-reader-run-context) &key repo-url)\n  \"This function is here to break a cyclical dependency between git-repo and repo-url\"\n  (cond\n    ((git-root :errorp nil)\n     (make-instance 'git:git-repo :link repo-url))\n    (t\n     (log:warn \"This is not running inside a Git repo. Please contact support@screenshotbot.io for advice, since the behavior in this case can be very different.\")\n     (make-instance 'git:null-repo))))\n\n(defmethod git-repo ((self env-reader-run-context))\n  (%git-repo self :repo-url (repo-url self)))\n\n(defmethod author :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (with-slow-logging (\"Figuring out git author\")\n     (git:author (git-repo self)))))\n\n(defmethod build-url :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (e:build-url (env self))))\n\n(defmethod repo-url :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (e:repo-url (env self))\n   ;; The Git repository URL might have temporary authentication\n   ;; information, for example on GitLab CI. We might want to remove\n   ;; this before we default to the Git repository URL instead of from\n   ;; the environment.\n   (with-slow-logging (\"Getting git remote URL\")\n     (git:get-remote-url (%git-repo self)))))\n\n(defmethod work-branch :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (e:work-branch (env self))))\n\n(defmethod run-context-metadata :around ((self env-reader-run-context))\n  (append\n   (call-next-method)\n   (list\n    (make-instance 'dto:metadata\n                   :key \"pull-request-base-branch\"\n                   :value (or\n                           (e:pull-request-base-branch (env self))\n                           \"NA\"))\n    #+ (or linux darwin)\n    (make-instance 'dto:metadata\n                   :key \"uname\"\n                   :value\n                   (or\n                    (ignore-errors\n                     (uiop:run-program (list \"uname\" \"-a\")\n                                       :output 'string))\n                    \"uname failed\")))))\n\n(define-condition invalid-pull-request (condition)\n  ())\n\n(defun valid-pull-request-url-p (url)\n  \"Exported for convenience to reduce duplication. See sdk.lisp.\"\n  (str:starts-with-p \"https://\" url))\n\n(defun warn-invalid-pull-request-url ()\n  \"Exported for convenience to reduce duplication. See sdk.lisp.\"\n  (signal 'invalid-pull-request)\n  (log:warn \"The --pull-request argument you provided was invalid: `~a`. We're ignoring this.~%\"\n               flags:*pull-request*))\n\n\n(defun validate-pull-request (pull-request)\n  \"One of our customers is using an incorrect --pull-request arg. The\nincorrect arg breaks some logic, and additionally is not required\nsince we can determine the pull-request from the environment. We can\ndo a quick sanity check, and recover with a warning if the\npull-request looks incorrect.\"\n  (cond\n    ((and\n      pull-request\n      (not (valid-pull-request-url-p pull-request)))\n     (warn-invalid-pull-request-url)\n     nil)\n    (t\n     pull-request)))\n\n(defmethod pull-request-url :around ((self env-reader-run-context))\n  (or\n   (validate-pull-request\n    (call-next-method))\n   (e:pull-request-url (env self))))\n\n(defmethod channel :around ((self env-reader-run-context))\n  (let ((channel (call-next-method)))\n    (cond\n      ((and\n        (or\n         (not channel)\n         (equal \"unnamed-channel\" channel))\n        (let ((e-channel (e:guess-channel-name (env self))))\n          (cond\n            (e-channel e-channel)\n            (t channel)))))\n      (t\n       channel))))\n\n(defmethod main-branch :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (guess-master-branch (git-repo self))))\n\n(defmethod work-branch-is-release-branch-p ((self run-context))\n  (unless (str:emptyp (release-branch-regex self))\n    (handler-case\n        (cl-ppcre:parse-string (release-branch-regex self))\n      (cl-ppcre:ppcre-syntax-error (e)\n        (error \"Could not parse regex: ~% ~a~%Got error:~% ~a\" (release-branch-regex self) e)))\n    (let ((regex (format nil \"^~a$\" (release-branch-regex self))))\n      (not (null (cl-ppcre:scan regex\n                                (work-branch self)))))))\n\n(defmethod fix-commit-hash ((self env-reader-run-context)\n                            sha)\n  (cond\n    ((str:emptyp sha)\n     nil)\n    ((eql 40 (length sha))\n     sha)\n    (t\n     (let ((parsed (with-slow-logging (\"Fixing the git commit hash\")\n                     (git:rev-parse-local (git-repo self) sha))))\n       (when (str:emptyp parsed)\n         (warn \"Could not rev-parse ~a, please use the full hash instead\" sha)\n         sha)\n       parsed))))\n\n(defmethod override-commit-hash :around ((self env-reader-run-context))\n  (fix-commit-hash\n   self\n   (or\n    (call-next-method)\n    (e:sha1 (env self)))))\n\n(defmethod commit-hash :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (with-slow-logging (\"git rev-parse for current commit\")\n     (?. current-commit (git-repo self)))))\n\n(defmethod merge-base :around ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (when-let ((repo (git-repo self))\n              (main-branch-hash (main-branch-hash self)))\n     (handler-case\n         (with-slow-logging (\"git merge-base\")\n           (git:merge-base repo main-branch-hash (commit-hash self)))\n       (uiop:subprocess-error (e)\n         nil)))))\n\n(defmethod repo-clean-p :around ((self env-reader-run-context))\n  (cond\n    ((not (str:blankp (build-url self)))\n     ;; In CI, `git status` can be slow especially on large\n     ;; repos. Let's just assume that if we know for a fact that we're\n     ;; running on CI, then we always treat it as a clean repo\n     t)\n    ((slot-boundp self 'repo-clean-p)\n     (call-next-method))\n    (t\n     (with-slow-logging (\"git status --porcelain\")\n       (git:cleanp (git-repo self))))))\n\n(defmethod guess-master-branch (repo)\n  (flet ((check (x)\n           (with-slow-logging (\"Guessing main branch\")\n             (rev-parse repo x))))\n    (cond\n      ((check \"main\")\n       \"main\")\n      ((check \"master\")\n       \"master\")\n      (t\n       (error \"Could not guess the main branch, please use --main-branch argument\")))))\n\n(defmethod guess-master-branch ((repo git:null-repo))\n  nil)\n\n(defmethod main-branch ((self env-reader-run-context))\n  (or\n   (call-next-method)\n   (guess-master-branch (git-repo self))))\n\n(defmethod main-branch-hash ((Self env-reader-run-context))\n  (or\n   (call-next-method)\n   (let ((branch (main-branch self)))\n     (rev-parse (git-repo self) branch))))\n\n(defun parse-tags (tags-str)\n  (when tags-str\n    (str:split \",\" tags-str)))\n\n(defun parse-shard-spec (shard-spec)\n  \"Parses a string of the form <buildId>:<index>:<count> into a dto:shard-spec\"\n  ;; TODO: better error messages when this fails\n  (unless (str:emptyp shard-spec)\n    (destructuring-bind (key index count)\n        (str:rsplit \":\" shard-spec :limit 3)\n      (make-instance 'dto:shard-spec\n                     :key key\n                     :number (parse-integer index)\n                     :count (parse-integer count)))))\n\n(defun format-shard-spec (shard-spec)\n  (when shard-spec\n   (format nil \"~a:~a:~a\"\n           (dto:shard-spec-key shard-spec)\n           (dto:shard-spec-number shard-spec)\n           (dto:shard-spec-count shard-spec))))\n\n(defclass default-flags-run-context ()\n  ()\n  (:default-initargs\n   :main-branch flags:*main-branch*\n   :main-branch-hash flags:*main-branch-commit-hash*\n   :pull-request-url flags:*pull-request*\n   :repo-url flags:*repo-url*\n   :productionp flags:*production*\n   :build-url flags:*build-url*\n   :gitlab-merge-request-iid flags:*gitlab-merge-request-iid*\n   :work-branch flags:*work-branch*\n   :phabricator-diff-id flags:*phabricator-diff-id*\n   :channel flags:*channel*\n   :merge-base flags:*merge-base-commit-hash*\n   :override-commit-hash flags:*override-commit-hash*\n   :release-branch-regex flags:*release-branch-regex*\n   :author flags:*author*\n   :compare-threshold flags:*compare-threshold*\n   :pixel-tolerance flags:*pixel-tolerance*\n   :batch flags:*batch*\n   :shard-spec (parse-shard-spec flags:*shard*)\n   :tags (parse-tags flags:*tags*))\n  (:documentation \"Just uses the the flags as initargs\"))\n\n(defclass flags-run-context (default-flags-run-context\n                             env-reader-run-context\n                             run-context)\n  ())\n\n(def-easy-macro with-flags-from-run-context (self &fn fn)\n  \"Temporary hack to enable using both flags and run-context everywhere.\"\n  (check-type self run-context)\n  (let ((flags:*main-branch* (main-branch self))\n        (flags:*main-branch-commit-hash* (main-branch-hash self))\n        (flags:*pull-request* (pull-request-url self))\n        (flags:*repo-url* (repo-url self))\n        (flags:*production* (productionp self))\n        (flags:*build-url* (build-url self))\n        (flags:*gitlab-merge-request-iid* (gitlab-merge-request-iid self))\n        (flags:*phabricator-diff-id* (phabricator-diff-id self))\n        (flags:*channel* (channel self))\n        (flags:*override-commit-hash* (override-commit-hash self))\n        (flags:*compare-threshold* (compare-threshold self))\n        (flags:*pixel-tolerance* (pixel-tolerance self))\n        (flags:*batch* (batch self))\n        (flags:*shard* (format-shard-spec (shard-spec self))))\n    (fn)))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/screenshotbot.sdk.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defsystem :screenshotbot.sdk/library\n  :serial t\n  :version \"2.18.10\"\n  :depends-on (:com.google.flag\n               :pkg\n               :quri\n               :ironclad/core\n               :core.cli\n               :cl-json\n               :cxml\n               :log4cl\n               :util/request\n               :easy-macros\n               :screenshotbot.sdk/common-flags\n               #-screenshotbot-oss\n               :sentry\n               :sentry-client\n               :clingon\n               :alexandria\n               :auto-restart\n               :util.misc\n               :util/health-check\n               :cl-fad\n               :zip\n               :trivial-garbage\n               :trivial-features\n               :util.threading\n               :tmpdir\n               :semz.decompress\n               :ironclad/digests\n               :trivial-timeout\n               :serapeum\n               :imago\n               :imago/pngload\n               :md5\n               :screenshotbot/api-model\n               :dag\n               :anaphora\n               :str)\n  :components ((:file \"package\")\n               (:file \"flags\")\n               (:file \"hostname\")\n               (:file \"api-context\")\n               (:file \"backoff\")\n               (:file \"version-check\")\n               (:file \"bundle\")\n               (:file \"firebase\")\n               (:file \"android\")\n               (:file \"xcresult\")\n               (:file \"git\")\n               #+lispworks\n               (:file \"git-pack\")\n               (:file \"env\")\n               (:file \"run-context\")\n               (:file \"request\")\n               (:file \"commit-graph\")\n               (:file \"sdk\")\n               (:file \"server-log-appender\")\n               (:file \"active-run\")\n               (:file \"health-checks\")\n               (:file \"sentry\")\n               (:file \"install\")\n               (:file \"fetch-run\")\n               (:file \"clingon-api-context\")\n               (:file \"pull\")\n               (:file \"upload-commit-graph\")\n               (:file \"batch\")\n               (:file \"cli-common\")\n               (:module \"dev\"\n                :components ((:file \"record-verify\")\n                             (:file \"verify-against-ci\")\n                             (:file \"commands\")))\n               (:file \"failed-run\")\n               (:file \"unchanged-run\")\n               (:file \"finalized-commit\")))\n\n(defsystem :screenshotbot.sdk/gradle\n  :serial t\n  :depends-on (:screenshotbot.sdk/library\n               :tar/extract)\n  :components ((:file \"adb-puller\")\n               #+lispworks\n               (:file \"gradle\")))\n\n\n(defsystem :screenshotbot.sdk/static\n  :serial t\n  :depends-on (:screenshotbot.sdk/library\n               :file-lock\n               :util/random-port\n               :cl-store\n               :screenshotbot/replay-core)\n  :components ((:file \"static\")))\n\n(defsystem :screenshotbot.sdk\n  :serial t\n  :author \"Arnold Noronha <arnold@screenshotbot.io>\"\n  :license \"Mozilla Public License, v 2.0\"\n  :depends-on (:screenshotbot.sdk/library\n               :screenshotbot.sdk/static)\n  :components ((:file \"help\")\n               (:file \"main\")))\n\n(defsystem :screenshotbot.sdk/common-flags\n  :serial t\n  :depends-on (#:com.google.flag)\n  :components ((:file \"common-flags\")))\n\n\n(defsystem :screenshotbot.sdk/tests\n  :serial t\n  :depends-on (:screenshotbot.sdk\n               :fiveam\n               :fiveam-matchers\n               :screenshotbot\n               :screenshotbot/testing-lib\n               :util/hunchentoot-engine\n               :util/fake-clingon\n               :util/fiveam)\n  :components ((:file \"integration-fixture\")\n               (:file \"test-bundle\")\n               (:file \"test-server-log-appender\")\n               (:file \"test-backoff\")\n               (:file \"test-installer\")\n               (:file \"test-unchanged-run\")\n               (:file \"test-clingon-api-context\")\n               (:file \"test-version-check\")\n               (:file \"test-main\")\n               (:file \"test-xcresult\")\n               (:file \"test-active-run\")\n               (:file \"test-api-context\")\n               (:file \"test-flags\")\n               (:file \"test-git\")\n               (:file \"test-run-context\")\n               (:file \"test-commit-graph\")\n               #+lispworks\n               (:file \"test-git-pack\")\n               (:file \"test-fetch-run\")\n               (:file \"test-env\")\n               (:module \"dev\"\n                :components ((:file \"test-record-verify\")\n                             (:file \"test-verify-against-ci\")))\n               (:file \"test-sentry\")\n               (:file \"test-cli-common\")\n               (:file \"test-android\")\n               (:file \"test-request\")\n               (:file \"test-sdk\")\n               (:file \"test-firebase\")\n               (:file \"test-static\")))\n\n\n(defsystem :screenshotbot.sdk/deliver\n  :author \"Arnold Noronha <arnold@screenshotbot.io>\"\n  :license \"Mozilla Public License, v 2.0\"\n  :defsystem-depends-on (#:build-utils/deliver-script)\n  :depends-on (:screenshotbot.sdk)\n  :components ((\"build-utils/deliver-script:deliver-script\"\n                \"deliver-sdk\")\n               #- (or mswindows win32)\n               (\"build-utils/deliver-script:makeself-component\" \"installer\"\n                                   :depends-on (\"deliver-sdk\")\n                                   :type \"sh\"\n                                   :label \"screenshotbot-installer\"\n                                   :archive (\"deliver-sdk\"\n                                             \"installer\")\n                                   :startup-component \"installer\")))\n\n#+lispworks\n(defsystem :screenshotbot.sdk/deliver-java\n  :defsystem-depends-on (#:build-utils/deliver-script)\n  :depends-on (#:screenshotbot.sdk/gradle)\n  :components ((\"build-utils/deliver-script:deliver-script\"\n                \"deliver-java\")))\n\n(defsystem :screenshotbot.sdk/integration-tests\n  :depends-on (#:fiveam\n               #:screenshotbot.sdk/deliver\n               #:util.testing\n               #:screenshotbot/testing-lib\n               #:screenshotbot\n               #:screenshotbot.sdk)\n  :components ((:file \"integration-tests\")))\n\n(defsystem :screenshotbot.sdk/sdk-integration-tests-impl\n  :depends-on (#:alexandria\n               #:screenshotbot.sdk/deliver\n               #:secure-random\n               #:tmpdir\n               #:cl-fad)\n  :components ((:file \"sdk-integration-tests-impl\")))\n"
  },
  {
    "path": "src/screenshotbot/sdk/scripts/.gitignore",
    "content": "*.png"
  },
  {
    "path": "src/screenshotbot/sdk/scripts/gen-images.sh",
    "content": "#!/bin/bash\n## Generate a directory of n screenshots\n##  two arguments: directory and n\n\nset -e\nset -x\n\ndir=$1\nnum=$2\n\nmkdir -p $dir\n\nMAGICK=\"\"\n\nif [ `git rev-parse --abbrev-ref HEAD` = master ] ; then\n    sleep 0\n\nfi\n\nif magick --help ; then\n    MAGICK=magick\nfi\n\n\nfor i in `seq 0 $num`; do\n    $MAGICK convert -size 360x360 xc:white -font \"FreeMono\" -pointsize 12 -fill black -stroke black -draw \"text 15,15 \\\"$(date) $i\\\"\"  -strip $dir/image$i.png\ndone\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/sdk-integration-tests-impl.lisp",
    "content": "(defpackage :screenshotbot/sdk/sdk-integration-tests-impl\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/sdk/sdk-integration-tests-impl)\n\n\n(defvar *default-cmd-dir* \".\")\n\n(defun run (cmd &rest args &key (directory *default-cmd-dir*) &allow-other-keys)\n  (let* ((cmd (loop for x in cmd\n                    if (pathnamep x)\n                      collect (namestring x)\n                    else collect x))\n         (cmd (loop for x in cmd\n                    collect (uiop:escape-shell-command x)))\n         (cmd (str:join \" \" cmd))\n         (cmd (format nil \"cd ~a && ~a\"\n                      (uiop:escape-shell-command\n                       (cond\n                         ((pathnamep directory)\n                          (namestring directory))\n                         (t\n                          directory)))\n                      cmd)))\n    (log:info \"running: ~s\" cmd)\n    (apply #'uiop:run-program\n           (append\n            (cond\n              ((uiop:os-windows-p)\n               (list \"cmd\" \"/c\"))\n              (t\n               (list \"bash\" \"-c\")))\n            (list\n             cmd))\n           (append\n            (a:remove-from-plist args :directory)\n            (list :output t\n                  :error-output t)))))\n\n(defvar *sdk* (car (asdf:output-files 'asdf:compile-op\n                                       (asdf:find-component :screenshotbot.sdk/deliver\n                                                            \"deliver-sdk\"))))\n\n(when (str:emptyp (uiop:getenv \"SCREENSHOTBOT_API_KEY\"))\n  (error \"SCREENSHOTBOT_API_KEY not configured for running integration tests\"))\n\n(run (list *sdk* \"--help\"))\n\n(assert (str:containsp \"--static-website\" (run (list *sdk* \"--help\") :output 'string)))\n\n(defmacro with-repo (&body body)\n  `(tmpdir:with-tmpdir (dir)\n     (run (list \"git\" \"clone\" \"https://github.com/tdrhq/fast-example\" dir))\n     (unwind-protect\n          (let ((*default-cmd-dir* dir))\n            ,@body)\n\n       ;; On Windows, for some reason I'm unable to delete the .git\n       ;; directory. Well, not *some* reason. It's just because the\n       ;; directories are all marked as read-only. I don't know how\n       ;; to clear that up at the moment, so I'm using the nuclear\n       ;; option.\n       #+(or mswindows win32)\n       (run (list \"attrib\" \"-r\" (format nil \"~a\\\\.git\\\\*.*\" (namestring dir)) \"/s\")))))\n\n(defun run-gen.sh ()\n  (ensure-directories-exist (format nil \"~ascreenshots/\" *default-cmd-dir*))\n  (run (list \"magick\" \"convert\"\n             \"-size\" \"360x360\" \"xc:white\" \"-fill\" \"black\" \"-stroke\" \"black\" \"-draw\" \"@ascii.txt\" \"-strip\" \"screenshots/image.png\")))\n\n\n(defun test-crash ()\n  (log:info \"## TESTING CRASH REPORT TO SENTRY\")\n  (multiple-value-bind (out err res)\n      (run (list *sdk*\n                 \"--directory\"\n                 \"sdk-integration-test-dummy-dir\")\n           :ignore-error-status t)\n    (log:info \"Got error code: ~a\" res)\n    (assert (not (= res 0)))\n    (assert (= 1 res))))\n\n(defun test-large-file ()\n  (log:info \"Running Large file test\")\n  (uiop:with-temporary-file (:pathname p :stream s :element-type '(unsigned-byte 8))\n    (let ((buff (make-array 1024 :element-type '(unsigned-byte 8)\n                            :initial-element 0)))\n      (loop for i from 0 below (* 15 1024)\n            do\n               (write-sequence buff s))\n      (finish-output s)\n      (let ((start (get-internal-real-time)))\n        (let ((res (util/request:http-request\n                    (quri:render-uri\n                    (quri:merge-uris\n                     \"/api/upload-test\"\n                     (or\n                      (uiop:getenv \"SCREENSHOTBOT_API_HOSTNAME\")\n                      \"https://screenshotbot.io\")))\n                   :basic-authorization (list\n                                         (uiop:getenv \"SCREENSHOTBOT_API_KEY\")\n                                         (uiop:getenv \"SCREENSHOTBOT_API_SECRET\"))\n                   :method :put\n                   :content p\n                   :want-string t)))\n          (assert\n           (equal\n            \"15728640\" res)))\n        (assert\n         (< (- (get-internal-real-time) start)\n            5000))))))\n\n(defun test-mark-failed ()\n  (log:info \"## TESTING --mark-failed\")\n  (with-repo\n   (run (list *sdk*\n              \"--mark-failed\"\n              \"--channel=foo\"))))\n\n(defun test-finalize ()\n  (log:info \"## TESTING --finalize\")\n  (with-repo\n   (run (list *sdk*\n              \"--finalize\"))))\n\n(defun test-verbose-logs ()\n  (log:info \"## TESTING --verbose\")\n  (with-repo\n      (run (list *sdk*\n                 \"--verbose\"\n                 \"--finalize\"))))\n\n(defun test-static-website ()\n  (log:info \"## TESTING --static-website\")\n  (run (list *sdk*\n             \"--channel\" \"static-website-for-test\"\n             \"--static-website\"\n             \"src/screenshotbot/sdk/static-example/\")\n       :directory (uiop:getcwd)))\n\n(defun test-mark-unchanged ()\n  (log:info \"## TESTING --mark-unchanged-from\")\n  (with-repo\n      (run (list *sdk*\n                 \"--channel\" \"bleh\"\n                 \"--mark-unchanged-from\" \"abcd\")))\n\n  (log:info \"## TESTING `ci mark-unchanged`\")\n  (with-repo\n      (run (list *sdk*\n                 \"ci\" \"mark-unchanged\"\n                 \"--channel\" \"bleh\"\n                 \"--other-commit\" \"abcd\"))))\n\n(defun test-mark-unchanged-with-batch ()\n  (log:info \"## TESTING --mark-unchanged-from with batch\")\n  (with-repo\n      (run (list *sdk*\n                 \"--channel\" \"bleh\"\n                 \"--batch\" \"test-batch\"\n                 \"--mark-unchanged-from\" \"abcd\")))\n\n  (log:info \"## TESTING `ci mark-unchanged` with batch\")\n  (with-repo\n      (run (list *sdk*\n                 \"ci\" \"mark-unchanged\"\n                 \"--channel\" \"bleh\"\n                 \"--batch\" \"test-batch\"\n                 \"--other-commit\" \"abcd\"))))\n\n\n(defun run-self-tests ()\n  (log:info \"Running self tests\")\n  (run (list *sdk* \"--self-test\")))\n\n(defun test-stack-overflow ()\n  (log:info \"## TESTING STACK OVERFLOW\")\n  (unwind-protect\n       (progn\n         (setf (uiop:getenv \"SCREENSHOTBOT_TEST_STACK_OVERFLOW\") \"true\")\n         (handler-case\n             (multiple-value-bind (out err res)\n                 (uiop:with-temporary-file (:stream stream :direction :output)\n                   (run (list *sdk* \"--help\")\n                        :error-output stream\n                        :output stream\n                        :ignore-error-status t))\n               (log:info \"Got error code for stack overflow test: ~a\" res)\n               (assert (not (= res 0))))))\n    (setf (uiop:getenv \"SCREENSHOTBOT_TEST_STACK_OVERFLOW\") nil)))\n\n\n\n(defun run-tests ()\n  (setf (uiop:getenv \"TDRHQ_IGNORE_SENTRY\") \"true\")\n\n  (run-self-tests)\n\n  (dotimes (i 1)\n   (test-large-file))\n\n  (test-mark-failed)\n\n  (test-finalize)\n\n  (test-static-website)\n\n  (test-mark-unchanged)\n  (test-mark-unchanged-with-batch)\n\n  (test-verbose-logs)\n\n  (test-stack-overflow)\n\n  (with-repo\n    (test-crash)\n    ;; veryfy we're correctly cloning the repo\n    (log:info \"Finished testing crash\")\n    #-mswindows\n    (run (list \"test\" \"-e\" \"gen.sh\"))\n    (run-gen.sh)\n\n    (log:info \"Running sdk\")\n    (run (list *sdk*\n               \"--directory\" \"./screenshots\"\n               \"--channel\" \"sdk-integration-tests\"\n               \"--production=false\"))\n    #+darwin\n    (run (list \"arch\" \"-x86_64\"\n               *sdk*\n               \"--directory\" \"./screenshots\"\n               \"--production=false\")))\n\n  (with-repo\n      (with-open-file (stream (path:catfile *default-cmd-dir* \"ascii.txt\")\n                              :if-exists :supersede\n                              :direction :output)\n        (format stream\n                \"text 15,15 \\\"Random code: ~a\\\" \" (secure-random:number 10000000000000000000)))\n\n    (run-gen.sh)\n    (log:info \"final run with random code\")\n    (run (list *sdk*\n               \"--directory\" \"./screenshots\"\n               \"--channel\" \"integration-tests-with-random-code\"\n               \"--production=false\")))\n\n  #+darwin\n  (with-repo\n      (with-open-file (stream (path:catfile *default-cmd-dir* \"ascii.txt\") :if-exists :supersede\n                                          :direction :output)\n        (format stream\n                \"text 15,15 \\\"Random code: ~a\\\" \" (secure-random:number 10000000000000000000)))\n    (run-gen.sh)\n    (run (list \"arch\" \"-x86_64\" *sdk*\n               \"--main-branch\" \"master\" ;; For some reason, in a recent change this is not being detected.\n               \"--directory\" \"./screenshots\"\n               \"--production=false\"))))\n\n(defun run-tests-with-tmpdir ()\n  (log:config :sane :immediate-flush t :pattern \"[%D{%H:%M:%S}] %5p: %m%n\")\n  (tmpdir:with-tmpdir (dir)\n    (let ((*default-cmd-dir* dir))\n      (run-tests))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/sdk-integration-tests.lisp",
    "content": "(defpackage :screenshotbot/sdk/sdk-integration-tests\n  (:use #:cl))\n(in-package :screenshotbot/sdk/sdk-integration-tests)\n\n(uiop:setup-temporary-directory)\n\n(ql:quickload :screenshotbot.sdk/sdk-integration-tests-impl)\n\n(screenshotbot/sdk/sdk-integration-tests-impl::run-tests-with-tmpdir)\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/sdk.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/sdk\n  (:nicknames :screenshotbot-sdk)\n  (:use #:cl\n        #:alexandria\n        #:anaphora\n        #:screenshotbot/sdk/flags\n        #:screenshotbot/sdk/hostname)\n  (:import-from #:dag\n                #:add-commit\n                #:commit\n                #:merge-dag\n                #:get-commit\n                #:write-to-stream\n                #:read-from-stream)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:image-stream\n                #:md5-sum\n                #:list-images\n                #:close-image\n                #:image-name\n                #:image-directory\n                #:image-directory-with-diff-dir)\n  (:import-from #:screenshotbot/sdk/git\n                #:fetch-remote-branch\n                #:null-repo\n                #:git-root\n                #:git-repo\n                #:current-commit\n                #:rev-parse\n                #:read-graph\n                #:cleanp\n                #:repo-link\n                #:merge-base)\n  (:import-from #:util/request\n                #:engine\n                #:http-request)\n  (:import-from #:util/misc\n                #:?.\n                #:or-setf)\n  (:import-from #:screenshotbot/sdk/version-check\n                #:*client-version*\n                #:remote-supports-put-run\n                #:remote-supports-basic-auth-p)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*\n                #:encode-json)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class\n                #:json-mop-to-string)\n  (:import-from #:screenshotbot/sdk/backoff\n                #:maybe-retry-request\n                #:backoff)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:invalid-pull-request\n                #:flags-run-context)\n  (:import-from #:util/threading\n                #:with-extras)\n  (:import-from #:screenshotbot/sdk/request\n                #:request\n                #:%make-basic-auth)\n  (:import-from #:screenshotbot/sdk/commit-graph\n                #:commit-graph-updater\n                #:update-commit-graph)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:dto #:screenshotbot/api/model)\n                    (#:e #:screenshotbot/sdk/env)\n                    (#:api-context #:screenshotbot/sdk/api-context)\n                    (#:android   #:screenshotbot/sdk/android)\n                    (#:run-context #:screenshotbot/sdk/run-context))\n  (:export\n   #:single-directory-run\n   #:*request*\n   #:*put*\n   #:request\n   #:put-file\n   #:parse-org-defaults\n   #:run-prepare-directory-toplevel\n   #:absolute-pathname\n   #:update-commit-graph\n   #:validate-pull-request\n   #:upload-image-directory\n   #:make-run))\n\n(in-package :screenshotbot/sdk/sdk)\n\n(defmacro defopt (var &key params\n                        default\n                        boolean\n                        required\n                        (help \"undocumented\"))\n  (declare (ignore required))\n  (let ((params (or params\n                    (unless boolean\n                      `(list ,(str:replace-all \"*\" \"\" (string var)))))))\n    `(list ',var ,default ,help :params ,params)))\n\n\n(defclass model ()\n  (type))\n\n(defclass image (model)\n  ((id :type string)\n   (upload-url :type string)))\n\n\n(defun call-with-file-stream (non-file-stream fn)\n  \"See doc for with-file-stream\"\n  (handler-case\n      (file-length non-file-stream)\n    (type-error ()\n      (error \"Unimplemented non-file-stream: this is a bug, please ping support@screenshotbot.io\")))\n  (funcall fn non-file-stream))\n\n(defmacro with-file-stream ((stream non-file-stream) &body body)\n  \"This actually does nothing: it just calls the body with stream bound to non-file-stream.\n\nHowever: the intention is that we bind stream to a stream where we can\ncall file-length. Currently, it appears that the only streams we would\nactually call put-file/upload-image with are file streams, so we just\ndon't implement this behavior for other streams. However, this\nanalysis was made later, so we're keeping this code here and if we get\na stram on which file-length doesn't work we raise a more parseable\nerror.\"\n  `(call-with-file-stream\n    ,non-file-stream\n    (lambda (,stream) ,@body)))\n\n(auto-restart:with-auto-restart (:attempt attempt)\n  (defun put-file (api-context upload-url stream &key parameters)\n    ;; In case we're retrying put-file, let's make sure we reset the\n    ;; stream\n    (log:debug \"put file to: ~a\" upload-url)\n    (file-position stream 0)\n    (with-file-stream (stream stream)\n     (let ((file-length (file-length stream)))\n       (log:debug \"Got file length: ~a\" file-length)\n       (multiple-value-bind (result code)\n         (http-request\n          upload-url\n          :method :put\n          :parameters parameters\n          ;; Basic auth for image puts will be supported from API\n          ;; level 4, but in previous versions it should just be ignored.\n          :basic-authorization (%make-basic-auth api-context)\n          :content-type \"application/octet-stream\"\n          :content-length file-length\n          :engine (engine api-context)\n          :content stream\n          :want-string t ;; Required for the engine to be able to reuse connections\n          :read-timeout 40)\n\n         (log:debug \"Got image upload response: ~s\" result)\n\n         (maybe-retry-request\n          code\n          :attempt attempt\n          :restart 'retry-put-file)\n\n         (unless (eql 200 code)\n           (error \"Failed to upload image: code ~a\" code))\n         result)))))\n\n(defun build-screenshot-objects (images)\n  (loop for im in images\n        collect\n        (let ((name (assoc-value im :name)))\n          (make-instance 'dto:screenshot\n                         :name name\n                         :image-id (assoc-value im :id)))))\n\n(defun safe-parse-int (str)\n  (cond\n    ((not str)\n     nil)\n    ((stringp str)\n     (unless (str:emptyp str)\n       (parse-integer str :junk-allowed t)))\n    ((numberp str)\n     str)\n    (t\n     (error \"Not a type that can be convered to integer: ~s\" str))))\n\n(define-condition empty-run-error (error)\n  ((recursivep :initarg :recursivep\n               :initform nil\n               :reader empty-run-error-recursivep))\n  (:report (lambda (condition stream)\n             (format stream \"No screenshots were detected in this run.~@[ Perhaps you wanted to use the --recursive flag?~]\"\n                     (not (empty-run-error-recursivep condition))))))\n\n(defun create-run-context-from-args (&key repo channel is-trunk branch \n                                          github-repo commit has-commit-p\n                                          merge-base has-merge-base-p\n                                          branch-hash has-branch-hash-p)\n  \"Creates a run context from the given arguments, building extra args as needed.\"\n  (let ((extra-run-context-args nil))\n    (flet ((push-extra-arg (key value)\n             (setf extra-run-context-args\n                   (list* key value extra-run-context-args))))\n      (when has-commit-p\n        #+nil ;; T1708\n        (warn \":has-commit-p is still being used\")\n        (push-extra-arg :commit-hash commit))\n\n      (when has-merge-base-p\n        #+nil ;; T1708\n        (warn \":has-merge-base-p is still being used\")\n        (push-extra-arg :merge-base merge-base))\n\n      (when has-branch-hash-p\n        (push-extra-arg :main-branch-hash branch-hash))\n\n      ;; TODO: move out of make-run:\n      (apply #'make-instance 'run-context:flags-run-context\n             :repo-url (or (?. repo-link repo)\n                           github-repo)\n             :channel channel\n             :productionp is-trunk\n             :main-branch branch\n             :env (e:make-env-reader)\n             extra-run-context-args))))\n\n(auto-restart:with-auto-restart ()\n (defun make-run (api-context\n                  screenshots &rest args\n                  &key repo\n                    channel\n                    run-context\n                    (before #'identity)\n                    branch\n                    (branch-hash nil has-branch-hash-p)\n                    (commit nil has-commit-p)\n                    (merge-base nil has-merge-base-p)\n                    github-repo ;; probably only used by replay T1708\n                    periodic-job-p\n                    is-trunk)\n   \"BEFORE is a callback that is called before creating the run. It is\ncalled with the RUN-CONTEXT which is either the one passed or the\nrun-context that was created here.\"\n\n   (loop for screenshot in screenshots\n         ;; We expect it to be a dto:screenshot with name and image-id\n         do (check-type screenshot dto:screenshot))\n   \n   (let ((run-context (or\n                       run-context\n                       (create-run-context-from-args \n                        :repo repo\n                        :channel channel\n                        :is-trunk is-trunk\n                        :branch branch\n                        :github-repo github-repo\n                        :commit commit\n                        :has-commit-p has-commit-p\n                        :merge-base merge-base\n                        :has-merge-base-p has-merge-base-p\n                        :branch-hash branch-hash\n                        :has-branch-hash-p has-branch-hash-p))))\n     (funcall before run-context)\n     (unless (or screenshots (run-context:shard-spec run-context))\n       (error 'empty-run-error :recursivep flags:*recursive*))\n\n     (unless (str:emptyp branch)\n       (unless (equal branch (run-context:main-branch run-context))\n         (warn \"branch does not match run-context: ~a vs ~a\" branch (run-context:main-branch run-context))))\n \n     ;;(log:info \"screenshot records: ~s\" screenshots)\n     (put-run-with-run-context\n      api-context\n      run-context\n      screenshots\n      ;; TODO: cleanp should be able to come from run-context, but we\n      ;; should probably get rid of cleanp completely.\n      :cleanp (run-context:repo-clean-p run-context)\n      :periodic-job-p periodic-job-p))))\n\n(defun run-context-to-dto (run-context screenshots &key periodic-job-p cleanp)\n  (make-instance 'dto:run\n                 :channel (run-context:channel run-context)\n                 :screenshots screenshots\n                 :metadata (run-context:run-context-metadata run-context)\n                 :main-branch (run-context:main-branch run-context)\n                 :shard-spec (run-context:shard-spec run-context)\n                 :work-branch (run-context:work-branch run-context)\n                 :release-branch-p (run-context:work-branch-is-release-branch-p run-context)\n                 :main-branch-hash (run-context:main-branch-hash run-context)\n                 :github-repo (run-context:repo-url run-context)\n                 :merge-base (run-context:merge-base run-context)\n                 :author (run-context:author run-context)\n                 :periodic-job-p periodic-job-p\n                 :build-url (run-context:build-url run-context)\n                 :compare-threshold (run-context:compare-threshold run-context)\n                 :compare-pixel-tolerance (run-context:pixel-tolerance run-context)\n                 :batch (run-context:batch run-context)\n                 :pull-request (run-context:pull-request-url run-context)\n                 :commit-hash (run-context:commit-hash run-context)\n                 :override-commit-hash (run-context:override-commit-hash run-context)\n                 :cleanp cleanp\n                 :tags (run-context:tags run-context)\n                 :gitlab-merge-request-iid (safe-parse-int\n                                            (run-context:gitlab-merge-request-iid run-context))\n                 :phabricator-diff-id (safe-parse-int\n                                       (run-context:phabricator-diff-id run-context))\n                 :trunkp (run-context:productionp run-context)))\n\n(defmethod put-run-with-run-context (api-context run-context screenshots\n                                     &key\n                                       ;; periodic-job is only set\n                                       ;; from the server side at the\n                                       ;; moment, so it's not part of the run-context\n                                       periodic-job-p\n                                       ;; TODO: make cleanp come from the run-context instead.\n                                       cleanp)\n  (let* ((run (run-context-to-dto run-context screenshots :periodic-job-p periodic-job-p\n                                  :cleanp cleanp)))\n    (if (remote-supports-put-run api-context)\n        (put-run api-context run)\n        (put-run-via-old-api api-context run))))\n\n(auto-restart:with-auto-restart ()\n  (defmethod put-run ((api-context api-context) run)\n    (let ((result (request api-context\n                           \"/api/run\" :method :put\n                                      :content run)))\n      (let ((run-url (assoc-value result :url)))\n        (cond\n          (run-url\n           (log:info \"Created run: ~a\" run-url))\n          (t\n           (log:info \"Run wasn't created yet, probably because all shards weren't available\"))))\n      (make-instance 'dto:run\n                     :id (assoc-value result :id)))))\n\n(defun put-run-via-old-api (api-context run)\n  (flet ((bool (x) (if x \"true\" \"false\")))\n    (request\n     api-context\n     \"/api/run\"\n     :parameters `((\"channel\" . ,(dto:run-channel run))\n                   (\"screenshot-records\" . ,(json:encode-json-to-string\n                                             (dto:run-screenshots run)))\n                   (\"branch\" . ,(dto:main-branch run))\n                   (\"branch-hash\" . ,(dto:main-branch-hash run))\n                   (\"github-repo\" . ,(dto:run-repo run))\n                   (\"merge-base\" . ,(dto:merge-base run))\n                   (\"periodic-job-p\" . ,(bool (dto:periodic-job-p run)))\n                   (\"build-url\" . ,(dto:build-url run))\n                   (\"pull-request\" . ,(dto:pull-request-url run))\n                   (\"commit\" . ,(dto:run-commit run))\n                   (\"override-commit-hash\" . ,(dto:override-commit-hash run))\n                   (\"is-clean\" . ,(bool (dto:cleanp run)))\n                   (\"gitlab-merge-request-iid\" .\n                                               ,(dto:gitlab-merge-request-iid run))\n                   (\"phabricator-diff-id\" . ,(dto:phabricator-diff-id run))\n                   (\"is-trunk\" . ,(bool (dto:trunkp run)))))))\n\n\n(defun $! (&rest args)\n  (multiple-value-bind (out error res)\n      (uiop:run-program args\n                        :error-output :interactive\n                        :output :interactive\n                        :ignore-error-status t)\n    (declare (ignore out error))\n    (eql 0 res)))\n\n(defclass basic-directory-run ()\n  ((directory :initarg :directory)))\n\n(defmethod make-directory-run (api-context dir &rest args)\n  (log:debug \"Reading images from ~a\" dir)\n  (let ((images\n          (upload-image-directory api-context dir)))\n    (log:debug \"Creating run\")\n    (apply 'make-run\n           api-context\n           images\n           args)))\n\n(defun keyword-except-md5 (identifier)\n  ;; See unit tests for this with the same name to understand why we\n  ;; need this.\n  ;;\n  ;; The implementation is bug prone, but I know for a fact we don't\n  ;; have 32 character long identifiers in the json :/\n  (cond\n   ((eql 32 (length identifier))\n    (string identifier))\n   (t\n    (json:camel-case-to-lisp identifier))))\n\n(define-condition not-recent-file-warning (warning)\n  ((file :initarg :file))\n  (:report\n   (lambda (self output)\n     (with-slots (file) self\n      (format output \"The file ~a is older than 48hrs, and might be a stale screenshot from a previous build\"\n              file)))))\n\n(defun warn-if-not-recent-file (stream)\n  ;; Heads up: file-write-date can return NIL, see T1632. Unable to\n  ;; repro in tests though.\n  (when-let ((write-date (file-write-date stream)))\n    (when (< write-date\n             (- (get-universal-time) (* 48 3600)))\n      (with-extras ((\"file-ts\" write-date))\n        (warn 'not-recent-file-warning :file (pathname stream))))))\n\n(defmethod find-existing-images (api-context\n                                 hashes)\n  (let ((json:*json-identifier-name-to-lisp* #'keyword-except-md5))\n    (let ((result\n            (request\n             api-context\n             \"/api/screenshot\"\n             :parameters `((\"hash-list\"\n                            . ,(json:encode-json-to-string hashes))))))\n      ;; The response looks like:\n      ;;(:|82142ae81caba45bb76aa21fb6acf16d| (:TYPE . \"image\") (:ID . \"6748c0b00572379fe0c9d0d0\") (:UPLOAD-URL . \"http://NIL/api/image/blob?oid=6748c0b00572379fe0c9d0d0\"))\n      (loop for (key . body) in result\n            collect\n            (make-instance 'dto:image-upload-response\n                           :md5sum (str:downcase (string key))\n                           :image-id (assoc-value body :id)\n                           :upload-url (assoc-value body :upload-url))))))\n\n(defmethod upload-image-directory (api-context bundle)\n  (let ((images (list-images bundle)))\n    (let* ((hashes (mapcar 'md5-sum images))\n           (image-upload-responses\n             (find-existing-images api-context hashes)))\n      (let ((md5-to-response (make-hash-table :test #'equal)))\n        ;; Create a map from from the md5sum to the the response\n        ;; object for quick lookup.\n        (loop for image-upload-response in image-upload-responses\n              do (setf (gethash (str:downcase (dto:image-md5sum image-upload-response))\n                                md5-to-response)\n                       image-upload-response))\n        (loop for im in images\n              collect\n              (%upload-single-image api-context im md5-to-response))))))\n\n(defun %upload-single-image (api-context im md5-to-response)\n  \"This is a stateful function. In particular, as and when an upload URL\nis used up, we update md5-to-response to remove that upload URL.\"\n  (let* ((response (gethash (str:downcase (md5-sum im))\n                            md5-to-response))\n         (key (image-name im))\n         (upload-url (dto:image-upload-url response)))\n    (with-open-stream (stream (image-stream im))\n      (warn-if-not-recent-file stream)\n      (unwind-protect\n           (progn\n             (when upload-url\n               (log:info \"Uploading image for `~a`\" key)\n               (put-file api-context upload-url stream))\n\n             ;; This Upload-URL is used up, if there's another image\n             ;; with the same hash, we shouldn't upload it again\n             (setf (dto:image-upload-url response) nil)\n\n             (make-instance 'dto:screenshot\n                            :image-id (dto:image-id response)\n                            :name key))\n        (close-image im)))))\n\n(defun make-bundle (&key (metadata flags:*metadata*)\n                      (directory flags:*directory*)\n                      (recursivep flags:*recursive*))\n  (cond\n    (metadata\n     (log:info \"Looks like an Android run\")\n     (android:make-image-bundle :metadata metadata))\n    ((not (str:emptyp directory))\n     (unless (path:-d directory)\n       (error \"Not a directory: ~a\" directory))\n     (make-instance 'image-directory\n                    :directory directory\n                    :file-types (str:split \",\" flags:*image-file-types*)\n                    :recursivep recursivep))\n    (t\n     (error \"Unknown run type, maybe you missed --directory?\"))))\n\n(defun guess-master-branch (repo)\n  (flet ((check (x)\n           (rev-parse repo x)))\n    (cond\n      ((check \"main\")\n       \"main\")\n      ((check \"master\")\n       \"master\")\n      (t\n       (error \"Could not guess the main branch, please use --main-branch argument\")))))\n\n\n(defun parse-environment ()\n  (let* ((env (e:make-env-reader))\n         ;; TODO: we should use run-ctx for all our computations, and\n         ;; eventually remove the setf-ing of flags. See T795\n         (run-ctx (make-instance 'flags-run-context\n                                 :env env)))\n    (when flags:*branch*\n      (error \"--branch is no longer supported, please use --main-branch instead\"))\n\n    ;; TODO: we shouldn't refer to the flags from this point onwards,\n    ;; instead just using the run-context.\n\n    (setf flags:*build-url*\n          (run-context:build-url run-ctx))\n\n    (setf flags:*repo-url*\n          (run-context:repo-url run-ctx))\n\n    (setf flags:*work-branch*\n          (run-context:work-branch run-ctx))\n\n    (setf flags:*channel*\n          (run-context:channel run-ctx))\n\n    (setf\n     flags:*override-commit-hash*\n     (run-context:override-commit-hash run-ctx))\n\n    (setf\n     flags:*main-branch*\n     (run-context:main-branch run-ctx))))\n\n(defun link-to-github-pull-request (repo-url pull-id)\n  (let ((key (cond\n               ((str:containsp \"bitbucket\" repo-url)\n                \"pull-requests\")\n               (t\n                \"pulls\"))))\n   (format nil \"~a/~a/~a\"\n           repo-url\n           key\n           pull-id)))\n\n\n(defun parse-org-defaults ()\n  (parse-environment))\n\n(defun recursive-directories (directory)\n  (or\n   (loop for d in (fad:list-directory directory)\n         if (and (not (str:starts-with-p \".\" (car (last (pathname-directory d)))))\n                 (path:-d d))\n           appending (recursive-directories d))\n   (list directory)))\n\n(defun get-relative-path (dir parent)\n  (let ((dir-parts (pathname-directory dir))\n        (parent-parts (pathname-directory parent)))\n    (log:debug \"got parts: ~s ~s\" parent-parts dir-parts)\n    (assert (equal parent-parts\n                   (subseq dir-parts 0 (length parent-parts))))\n    (let ((res (make-pathname\n                :directory\n                `(:relative ,@(subseq dir-parts (length parent-parts)))\n                :defaults #P\"./\")))\n      (log:debug \"Relative path parts: ~S\" (pathname-directory res))\n      (log:debug \"Relative path is: ~S\" res)\n      res)))\n\n(defun git-repo ()\n  (let ((root (ignore-errors (git-root))))\n    (cond\n      (root\n       (make-instance 'git-repo\n                      :link flags:*repo-url*))\n      (t\n       (log:warn \"This is not running inside a Git repo. Please contact support@screenshotbot.io for advice, since the behavior in this case can be very different.\")\n       (make-instance 'null-repo\n                      :link flags:*repo-url*)))))\n\n(defun single-directory-run (api-context directory &key channel)\n  ;; Heads up! This logic is not unit-tested. If you're modifying this\n  ;; code, run `make sdk-integration-tests`.\n  (let ((repo (git-repo))\n        (branch flags:*main-branch*))\n    (log:info \"Uploading images from: ~a\" directory)\n    (make-directory-run api-context directory\n                        :channel channel\n                        :before (lambda (run-context)\n                                  (declare (ignore run-context))\n                                  ;; TODO[tidy]: this doesn't need to\n                                  ;; be in a :before anymore. But\n                                  ;; there's a chance we might need\n                                  ;; some more information from the\n                                  ;; run-context in the future (for\n                                  ;; the Pull-Request merge base)\n                                  (when (and flags:*production*\n                                             (> flags:*commit-limit* 0))\n                                    (let ((commit-graph-updater (make-instance 'commit-graph-updater :api-context api-context)))\n                                      (update-commit-graph commit-graph-updater repo branch))))\n                        :repo repo\n                        :is-trunk flags:*production*\n                        :branch branch)))\n\n(defun chdir-for-bin (path)\n  (uiop:chdir path)\n  (setf *default-pathname-defaults* (pathname path)))\n\n\n(defun absolute-pathname (p)\n  (fad:canonical-pathname (path:catdir (uiop:getcwd) p)))\n\n(defun run-prepare-directory-toplevel (api-context)\n  (let ((directory (make-bundle)))\n    (single-directory-run api-context directory :channel flags:*channel*)))\n\n(def-health-check verify-https-works ()\n  (util/request:http-request \"https://screenshotbot.io/api/version\"\n                             :ensure-success t))\n"
  },
  {
    "path": "src/screenshotbot/sdk/sentry.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/sentry\n  (:use #:cl)\n  (:import-from #:util/threading\n                #:with-tags\n                #:maybe-log-sentry\n                #:*warning-count*\n                #:with-extras\n                #:*extras*\n                #:funcall-with-sentry-logs)\n  (:import-from #:screenshotbot/sdk/version-check\n                #:*client-version*)\n  (:import-from #:util/health-check\n                #:def-health-check\n                #:run-health-checks)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:remote-version\n                #:api-context)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:api-hostname)\n  (:import-from #:screenshotbot/sdk/env\n                #:make-env-reader)\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:define-flag)\n  (:import-from #:com.google.flag\n                #:parse-command-line)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:e #:screenshotbot/sdk/env)\n                    (#:sdk #:screenshotbot/sdk/sdk))\n  ;; TODO: delete\n  #+lispworks\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:*api-key*\n                #:*hostname*\n                #:*api-secret*)\n  (:import-from #:core/cli/sentry\n                #:default-sentry-output-stream\n                #:with-cli-sentry))\n(in-package :screenshotbot/sdk/sentry)\n\n(def-easy-macro with-sentry (&key (on-error (lambda ()\n                                              (uiop:quit 1)))\n                                  (stream (default-sentry-output-stream))\n                                  (dry-run nil)\n                                  &fn fn)\n  (with-tags ((\"cli-client\" \"true\")\n              #+lispworks (\"api_hostname\" *hostname*)\n              #+lispworks (\"api_id\"  *api-key*)\n              #+lispworks (\"channel\" flags:*channel*)\n              (\"cli-version\" *client-version*))\n    (with-extras ((\"features\" *features*)\n                  (\"build_creator\"\n                   (uiop:getenv \"BUILDKITE_BUILD_CREATOR\"))\n                  (\"build-url\" flags:*build-url*))\n      (with-cli-sentry (:verbose flags:*verbose* :dry-run dry-run\n                        :stream stream\n                        :on-error on-error)\n        (funcall fn)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/server-log-appender.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/server-log-appender\n  (:use #:cl)\n  (:import-from #:log4cl-impl\n                #:appender-do-append)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context\n                #:api-feature-enabled-p)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:format-api-url)\n  (:import-from #:util/threading\n                #:make-thread\n                #:max-pool)\n  (:import-from #:util/reused-ssl\n                #:with-reused-ssl)\n  (:local-nicknames (#:api-context #:screenshotbot/sdk/api-context))\n  (:export\n   #:flush-server-appender))\n(in-package :screenshotbot/sdk/server-log-appender)\n\n\n(defclass server-log-appender (log4cl:stream-appender)\n  ((stream :initarg :stream\n           :reader log4cl:appender-stream))\n  (:default-initargs\n   :layout (make-instance 'log4cl:pattern-layout\n                          :conversion-pattern \"[%D{%H:%M:%S}] %p - %m%n\")))\n\n(defun make-server-log-appender (api-context)\n  (make-instance 'server-log-appender\n                 :stream (make-instance 'cli-log-stream :api-context api-context)))\n\n(defclass cli-log-stream (trivial-gray-streams:fundamental-character-output-stream)\n  ((api-context :initarg :api-context\n                :reader api-context)\n   (lock :initform (bt:make-lock)\n         :reader lock)\n   (buffer :initarg :buffer\n           :reader buffer\n           :initform (make-string-output-stream))))\n\n(defmethod trivial-gray-streams:stream-write-char ((self cli-log-stream) ch)\n  (bt:with-lock-held ((lock self))\n    (write-char ch (buffer self))))\n\n(defmethod trivial-gray-streams:stream-write-string ((Self cli-log-stream) string &optional start end)\n  (bt:with-lock-held ((lock self))\n    (write-string string (buffer self) :start start :end end)))\n\n(defmethod trivial-gray-streams:stream-line-column ((self cli-log-stream))\n  nil)\n\n(defmethod trivial-gray-streams:stream-finish-output ((self cli-log-stream)))\n\n\n(defmethod %flush-stream ((self cli-log-stream))\n  (ignore-errors\n   (let ((buffer (bt:with-lock-held ((lock self))\n                   (get-output-stream-string (buffer self)))))\n     (unless (str:emptyp buffer)\n       (%write-log\n        (api-context self)\n        buffer)))))\n\n(defmethod flush-server-appender ((self server-log-appender))\n  (%flush-stream (log4cl:appender-stream self)))\n\n(defun %write-log (api-context body)\n  (with-reused-ssl ((api-context:engine api-context)) ;; avoid a warning when called from background threads\n    (http-request\n     (format-api-url api-context  \"/api/cli-log\")\n     :method :post\n     :basic-authorization (list (api-context:key api-context)\n                                (api-context:secret api-context))\n     :content body\n     :engine (api-context:engine api-context )\n     :content-type \"application/text\")))\n\n\n(defparameter *stream*\n  (make-instance 'cli-log-stream\n                 :api-context (make-instance 'api-context\n                                             :key (uiop:getenv \"SCREENSHOTBOT_API_KEY\")\n                                             :secret (uiop:getenv \"SCREENSHOTBOT_API_SECRET\")\n                                             :hostname \"https://staging.screenshotbot.io\")))\n\n(defparameter *test-appender*\n  (make-server-log-appender\n   (make-instance 'api-context\n                  :key (uiop:getenv \"SCREENSHOTBOT_API_KEY\")\n                  :secret (uiop:getenv \"SCREENSHOTBOT_API_SECRET\")\n                  :hostname (uiop:getenv \"SCREENSHOTBOT_API_HOSTNAME\"))))\n\n\n\n#+nil\n(log4cl:add-appender log4cl:*root-logger*\n                            *test-appender*)\n\n;; (log:info \"hello\")\n;; (dotimes (i 10) (log:info \"hello\"))\n;; (flush-server-appender *test-appender*)\n;; (dotimes (i 1000) (format *stream* \"hello~%\"))\n;; (finish-output *stream*)\n;; (log4cl:flush-all-appenders)\n;; (log:config :debug)\n\n;; (format *stream* \"hello world~%\")\n;; (finish-output *stream*)\n\n;; (close *stream*)\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/static-example/index.html",
    "content": "<html>\n  <body>\n    hello world!\n  </body>\n</html>\n"
  },
  {
    "path": "src/screenshotbot/sdk/static.lisp",
    "content": "(defpackage :screenshotbot/sdk/static\n  (:use #:cl)\n  (:import-from #:screenshotbot/replay/browser-config\n                #:dimensions\n                #:browser-config)\n  (:import-from #:screenshotbot/replay/core\n                #:*cache*\n                #:context)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:guess-master-branch\n                #:request)\n  (:import-from #:util/digests\n                #:sha256-file)\n  (:import-from #:util/lru-cache\n                #:lru-cache)\n  (:import-from #:screenshotbot/sdk/common-flags\n                #:*sdk-flags*)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:file-lock\n                #:release-file-lock\n                #:file-lock)\n  (:import-from #:alexandria\n                #:remove-from-plist\n                #:assoc-value)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:clingon\n                #:make-option)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:register-root-command\n                #:common-run-options\n                #:*root-commands*\n                #:with-clingon-api-context)\n  (:import-from #:screenshotbot/sdk/commit-graph\n                #:commit-graph-updater)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:git #:screenshotbot/sdk/git)\n                    (#:e #:screenshotbot/sdk/env)\n                    (#:replay #:screenshotbot/replay/core)\n                    (#:api-context #:screenshotbot/sdk/api-context))\n  (:export\n   #:record-static-website))\n(in-package :screenshotbot/sdk/static)\n\n\n(defun md5-file (file)\n  (ironclad:byte-array-to-hex-string (sha256-file  file)))\n\n(defun upload-blob (api-context file)\n  \"Upload the blob, without checking if it has been previously uploaded\"\n  (log:info \"Uploading file: ~a\" file)\n  (with-open-file (stream file :direction  :input\n                               :element-type '(unsigned-byte 8))\n    (let ((uri (quri:make-uri\n                  :query `((\"hash\" . ,(md5-file file))\n                           (\"type\" . ,(pathname-type file)))\n                  :defaults\n                  (quri:uri (format nil \"~a/api/blob/upload\" (api-context:hostname api-context))))))\n      (sdk:put-file api-context\n                    (format nil \"~a\" uri)\n                    stream))))\n\n\n(defun blob-check (api-context files)\n  \"Check if the given list of files have already been uploaded in the paste\"\n  (let ((input (loop for file in files\n                     collect `((:file . ,(namestring file))\n                               (:hash . ,(md5-file file))\n                               (:type . ,(pathname-type file))))))\n    (sdk:request api-context\n                 \"/api/blob/check\"\n                 :method :post\n                 :parameters `((\"query\" . ,(json:encode-json-to-string input))))))\n\n(defun upload-multiple-files (api-context files)\n  \"Upload multiple blobs efficiently, checking to make sure we only\nupload blobs that haven't been uploaded before.\"\n  (let ((files (remove-if-not #'uiop:file-exists-p files)))\n    (let ((results (blob-check api-context files)))\n      (loop for result in results\n            for existsp = (a:assoc-value result :exists)\n            for file = (a:assoc-value result :file)\n            if (not existsp)\n              do\n                 (upload-blob api-context file)))))\n\n\n(defun upload-snapshot-assets (api-context snapshot)\n  \"Upload all the assets in the snapshot\"\n  (upload-multiple-files\n   api-context\n   (append\n    (loop for asset in (replay:assets snapshot)\n          collect\n          (replay:snapshot-asset-file snapshot asset)))))\n\n(defun parse-config-from-file (file)\n  (with-open-file (stream (pathname file))\n   (let ((configs (json:decode-json stream)))\n     (loop for config in configs\n           collect\n           (flet ((dim ()\n                    (a:when-let ((dim (assoc-value config :dimensions)))\n                      (str:split \"x\" dim))))\n             (make-instance 'browser-config\n                            :type (assoc-value config :type)\n                            :name (assoc-value config :name)\n                            :dimensions (when (dim)\n                                          (make-instance 'dimensions\n                                                         :width (parse-integer (first (dim)))\n                                                         :height (parse-integer (second (dim)))))\n                            :mobile-emulation (assoc-value config :mobile-emulation)))))))\n\n(defun read-browser-configs (file)\n  \"Currently we're not providing a way to modify the browser\n  config. We're hardcooding this to a value that we can initially work\n  with.\"\n  (cond\n    (file\n     (parse-config-from-file file))\n    (t\n     (list\n      (make-instance 'browser-config\n                      :type \"chrome\"\n                      :dimensions (make-instance 'dimensions\n                                                  :width 1280\n                                                  :height 800)\n                      :name \"Google Chrome Desktop\")\n      (make-instance 'browser-config\n                      :type \"chrome\"\n                      :name \"Nexus 5 (emulated)\"\n                      ;; yes, this inconsistency is intentional for now.\n                      :mobile-emulation \"Nexus 6P\")))))\n\n(defun get-sdk-flags ()\n  (loop for key being the hash-keys of *sdk-flags*\n        using (hash-value sym)\n        collect (cons key (symbol-value sym))))\n\n(defun schedule-snapshot (api-context snapshot &key channel\n                                                 repo-url\n                                                 browser-configs\n                                                 main-branch\n                                                 pull-request\n                                                 production)\n  \"Schedule a Replay build with the given snapshot\"\n  (declare (ignore production)) ;; TODO\n  (setf (replay:tmpdir snapshot) nil) ;; hack for json encoding\n  (let* ((repo (make-instance 'git:git-repo :link repo-url))\n         (branch-hash (ignore-errors (git:rev-parse repo main-branch)))\n         (commit (ignore-errors (git:current-commit repo))))\n   (let ((request (make-instance 'replay:snapshot-request\n                                 :snapshot snapshot\n                                 :channel-name channel\n                                 :pull-request pull-request\n                                 :main-branch main-branch\n                                 :repo-url repo-url\n                                 :browser-configs (read-browser-configs browser-configs)\n                                 :sdk-flags (get-sdk-flags)\n                                 :commit commit\n                                 :branch-hash branch-hash\n                                 :merge-base\n                                 (git:merge-base repo branch-hash commit))))\n     (uiop:with-temporary-file (:pathname p)\n       (cl-store:store request p)\n       (request\n        api-context\n        \"/api/replay/schedule\"\n        :method :post\n        :content p)))))\n\n(defun find-all-index.htmls (dir)\n  (declare (optimize (debug 3) (speed 0)))\n  (labels ((walk (subdir prefix)\n             (loop for item in (remove-if #'null (fad:list-directory subdir))\n                   if (and\n                       (string-equal \"html\" (pathname-type item)))\n                     collect (format nil \"~a/~a\" prefix (path:basename item))\n                   if (path:-d item)\n                     appending (progn\n                                 (assert item)\n                                 (let ((prefix (format nil \"~a/~a\" prefix\n                                                   (car (last (pathname-directory item))))))\n                                  (walk item prefix))))))\n    (walk dir \"\")))\n\n(defun ensure-cache-dir ()\n  (let ((path #+mswindows\n              (path:catdir (pathname (uiop:getenv \"APPDATA\"))\n                           #P\"screenshotbot/cache/replay/\")\n              #-mswindows\n              (pathname \"~/.cache/screenshotbot/replay/\")))\n    (ensure-directories-exist path)\n    (make-instance 'lru-cache\n                   :dir path)))\n\n(def-easy-macro with-cache-dir (&fn fn)\n  (with-global-binding ((*cache* (ensure-cache-dir)))\n    (let ((lock\n            (make-instance 'file-lock\n                           :file (path:catfile (util/lru-cache:dir *cache*)\n                                               \"lock\"))))\n      (unwind-protect\n           (fn)\n        (release-file-lock lock)))))\n\n(auto-restart:with-auto-restart ()\n  (defun record-static-website (api-context location\n                                &rest args\n                                &key production\n                                  repo-url\n                                  main-branch\n                                  (html-provider #'find-all-index.htmls) \n                                  assets-root\n                                &allow-other-keys)\n    \"HTML-PROVIDER is a function that takes a directory, and returns all\nthe HTML files in it that we will render. Defaults to FIND-ALL-INDEX.HTMLS.\"\n    (assert (path:-d location))\n    (let ((git-repo (make-instance 'git:git-repo :link repo-url))\n          (commit-graph-updater (make-instance 'commit-graph-updater :api-context api-context)))\n      (when production\n        (sdk:update-commit-graph\n         commit-graph-updater\n         git-repo\n         main-branch))\n      (let ((schedule-args\n              (remove-from-plist args\n                                 :main-branch :assets-root\n                                 :html-provider))\n            (main-branch (or main-branch\n                             (guess-master-branch git-repo))))\n        (with-cache-dir ()\n          (tmpdir:with-tmpdir (tmpdir)\n            (let* ((context (make-instance 'context))\n                   (port (util/random-port:random-port))\n                   (acceptor (make-instance 'hunchentoot:acceptor\n                                            :port port\n                                            :document-root location))\n                   (snapshot (make-instance 'replay:snapshot\n                                            :tmpdir tmpdir)))\n              (unwind-protect\n                   (progn\n                     (hunchentoot:start acceptor)\n                     (loop for index.html in (funcall html-provider location)\n                           do\n                              (replay:load-url-into\n                               context\n                               snapshot\n                               (format nil \"http://localhost:~a~a\" port index.html)\n                               tmpdir\n                               :actual-url\n                               (when assets-root\n                                 (format nil \"~a~a\"\n                                         assets-root\n                                         index.html))))\n\n                     (upload-snapshot-assets api-context snapshot)\n                     (let* ((result (apply #'schedule-snapshot api-context snapshot\n                                           :main-branch main-branch\n                                           schedule-args))\n                            (logs (a:assoc-value result :logs)))\n                       (log:info \"Screenshot job queued: ~a\" logs)))\n                (hunchentoot:stop acceptor)\n                #+mswindows\n                (progn\n                  (log:info \"[windows-only] Waiting 2s before cleanup\")\n                  (sleep 2))))))))\n))\n\n(defun static-website/command ()\n  (clingon:make-command\n   :name \"static-website\"\n   :options (list*\n             (make-option\n              :string\n              :long-name \"assets-root\"\n              :description \"The root for all image/CSS assets. If not provided it defaults to the directory\"\n              :key :assets-root)\n             (make-option\n              :string\n              :long-name \"directory\"\n              :required t\n              :description \"The directory that we scan for all HTML files.\"\n              :key :directory)\n             (make-option\n              :string\n              :long-name \"browser-configs\"\n              :description \"A JSON file that specifies which browsers to run this on.\"\n              :key :browser-configs)\n             (make-option\n              :string\n              :long-name \"html-list\"\n              :key :html-list\n              :description \"A file containing a newline separated list of relative pathnames to\nall the HTML files to snapshot. If not provided, we will scan through\ndirectory to find the html files. This might be useful to filter the\nHTML files to render.\")\n             (common-run-options))\n   :description \"Upload a directory of HTML assets with images/CSS and have Screenshotbot render them in browsers of your choice\"\n   :handler (lambda (cmd)\n              (with-clingon-api-context (api-context cmd)\n                (let ((env (e:make-env-reader)))\n                  (apply #'record-static-website\n                         api-context (getopt cmd :directory)\n                         :pull-request (or (getopt cmd :pull-request)\n                                           (e:pull-request-url env))\n                         :repo-url (or (getopt cmd :repo-url)\n                                       (e:repo-url env))\n                         :html-provider (make-html-provider (getopt cmd :html-list))\n                         :browser-configs (getopt cmd :browser-configs)\n                         :main-branch (getopt cmd :main-branch)\n                         (loop for key  in `(:assets-root :production\n                                             :channel)\n                               append (list key (getopt cmd key)))))))))\n\n(defun make-html-provider (html-list)\n  (cond\n    (html-list\n     (lambda (dir)\n       (declare (ignore dir))\n       (remove-if #'str:blankp\n        (uiop:read-file-lines html-list))))\n    (t\n     #'find-all-index.htmls)))\n\n(register-root-command 'static-website/command)\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-active-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-active-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/hunchentoot-engine\n                #:hunchentoot-engine)\n  (:import-from #:screenshotbot/server\n                #:acceptor)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/sdk/active-run\n                #:%get-active-runs)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key-secret-key\n                #:api-key-key\n                #:api-key)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:get-singleton-company)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:active-run\n                #:make-recorder-run)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json)\n  (:import-from #:screenshotbot/api/core\n                #:*wrap-internal-errors*))\n(in-package :screenshotbot/sdk/test-active-run)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n    (with-test-store ()\n      (let* ((company (get-singleton-company *installation*))\n             (user (make-user :email \"foo@gmail.com\"))\n             (channel (find-or-create-channel company \"foobar\"))\n             (*wrap-internal-errors* nil))\n        (roles:ensure-has-role company user 'roles:standard-member)\n        (let* ((api-key (make-instance 'api-key\n                                       :user user\n                                       :company company))\n               (acceptor (make-instance 'acceptor))\n               (engine (make-instance 'hunchentoot-engine\n                                      :acceptor acceptor))\n               (api-ctx (make-instance 'api-context\n                                       :engine engine\n                                       :key (api-key-key api-key)\n                                       :secret (api-key-secret-key api-key)\n                                       :hostname \"example.screenshotbot.io\")))\n          (&body))))))\n\n(test preconditions\n  (with-fixture state ()\n    (is (not (str:emptyp (api-key-key api-key))))\n    (is (not (str:emptyp (api-key-secret-key api-key))))))\n\n(test find-active-runs\n  (with-fixture state ()\n    (is\n     (eql\n      nil\n      (%get-active-runs api-ctx :channel \"foobar\")))))\n\n(test with-an-actual-active-run\n  (with-fixture state ()\n    (let ((run (make-recorder-run :channel channel)))\n      (setf (active-run channel \"main\")\n            run)\n      (let ((result (%get-active-runs api-ctx :channel \"foobar\")))\n        (assert-that result\n                     (has-length 1))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-android.lisp",
    "content": "(defpackage :screenshotbot/sdk/test-android\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/android\n                #:read-android-metadata\n                #:make-image-bundle)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:list-images)\n  (:import-from #:screenshotbot/sdk/firebase\n                #:find-file)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/sdk/test-android)\n\n(util/fiveam:def-suite)\n\n(test metadata-integration\n  (let* ((artifact-dir #.(asdf:system-relative-pathname\n                          :screenshotbot.sdk \"example-firebase-artifacts/artifacts/\"))\n         (metadata (path:catfile artifact-dir \"sdcard/screenshots/com.facebook.testing.screenshot.example.test/screenshots-default/metadata.json\")))\n    (let ((bundle\n           (make-image-bundle\n            :metadata (list metadata))))\n      (let ((images (list-images bundle)))\n        (pass)))))\n\n(test shot-integration\n  (let* ((artifact-dir #.(asdf:system-relative-pathname\n                          :screenshotbot.sdk \"example-shot-artifacts//\"))\n         (metadata (find-file artifact-dir \"metadata.json\"))\n         (metadata-compose (find-file artifact-dir \"metadata_compose.json\")))\n    (let ((bundle\n           (make-image-bundle\n            :metadata (list metadata\n                            metadata-compose))))\n      (let ((images (list-images bundle)))\n        (is (= 8 (length images)))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-api-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-api-context\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:extract-hostname-from-secret\n                #:fetch-remote-information\n                #:base-api-context\n                #:api-feature-enabled-p\n                #:hostname\n                #:key\n                #:secret\n                #:api-context)\n  (:import-from #:core/api/model/api-key\n                #:make-encoded-secret))\n(in-package :screenshotbot/sdk/test-api-context)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (&body))\n\n(defclass test-api-context (base-api-context)\n  ())\n\n(defmethod fetch-remote-information ((self test-api-context))\n  (values))\n\n(test default-api-hostname\n\n  (is\n   (equal \"https://api.screenshotbot.io\"\n          (hostname\n           (make-instance 'api-context\n                          :hostname nil))))\n  (is\n   (equal \"https://api.screenshotbot.io\"\n          (hostname\n           (make-instance 'api-context\n                          :hostname \"\"))))  \n  (is\n   (equal \"https://api.screenshotbot.io\"\n          (hostname\n           (make-instance 'api-context))))\n  (is\n   (equal \"https://example.com\"\n          (hostname\n           (make-instance 'api-context\n                          :hostname \"https://example.com\")))))\n\n(test api-feature-enabled-p\n  (is-true\n   (api-feature-enabled-p\n    (make-instance 'test-api-context\n                   :features (list \"foobar\"))\n    :foobar))\n  (is-false\n   (api-feature-enabled-p\n    (make-instance 'test-api-context\n                   :features (list \"carbar\"))\n    :foobar)))\n\n(test fixes-api-hostname\n  (is\n   (equal \"https://foo.screenshotbot.io\"\n          (hostname\n           (make-instance 'api-context\n                          :hostname \"foo.screenshotbot.io\")))))\n\n(test extract-hostname-from-secret\n  ;; Test extracting hostname from a valid encoded secret\n  (let* ((secret \"1234567890123456789012345678901234567890\") ;; 40 chars\n         (full-secret (make-encoded-secret \"TESTKEY123\" \"https://custom.example.com\" secret)))\n    (is (= 40 (length secret)))\n    (is (equal \"https://custom.example.com\"\n               (extract-hostname-from-secret full-secret))))\n\n  ;; Test that plain secrets (without encoding) return nil\n  (is (equal nil\n             (extract-hostname-from-secret \"plainSecretKey1234567890123456789012\")))\n\n  ;; Test nil secret\n  (is (equal nil\n             (extract-hostname-from-secret nil)))\n\n  ;; Test empty secret\n  (is (equal nil\n             (extract-hostname-from-secret \"\"))))\n\n(test api-context-uses-hostname-from-secret\n  ;; Test that api-context extracts hostname from the secret when no hostname is provided\n  (let* ((secret \"1234567890123456789012345678901234567890\")\n         (full-secret (make-encoded-secret \"TESTKEY123\" \"https://abcd.example.com\" secret)))\n    (is (= 40 (length secret)))\n    (is (equal \"https://abcd.example.com\"\n               (hostname\n                (make-instance 'api-context\n                               :key \"TESTKEY123\"\n                               :secret full-secret\n                               :hostname \"\"))))))\n\n(test api-context-prefers-explicit-hostname\n    ;; Test that explicit hostname takes precedence\n  (let* ((secret \"1234567890123456789012345678901234567890\")\n         (full-secret (make-encoded-secret \"TESTKEY123\" \"https://abcd.example.com\" secret)))\n    (is (= 40 (length secret)))\n    (is (equal \"https://override.example.com\"\n               (hostname\n                (make-instance 'api-context\n                               :key \"TESTKEY123\"\n                               :secret full-secret\n                               :hostname \"https://override.example.com\"))))))\n\n(test api-context-extracts-key-from-secret\n  ;; Test that api-context extracts api-key from the secret when no key is provided\n  (let* ((actual-secret \"1234567890123456789012345678901234567890\")\n         (full-secret (make-encoded-secret \"TESTKEY123\" \"https://custom.example.com\" actual-secret)))\n    (is (= 40 (length actual-secret)))\n    (let ((ctx (make-instance 'api-context\n                              :secret full-secret\n                              :hostname \"\")))\n      (is (equal \"TESTKEY123\" (key ctx)))\n      ;; The secret should remain the full encoded secret, not just the 40-char part\n      (is (equal full-secret (secret ctx)))\n      (is (equal \"https://custom.example.com\" (hostname ctx))))))\n\n(test api-context-prefers-explicit-key\n  ;; Test that explicit key takes precedence\n  (let* ((actual-secret \"1234567890123456789012345678901234567890\")\n         (full-secret (make-encoded-secret \"TESTKEY123\" \"https://custom.example.com\" actual-secret)))\n    (is (= 40 (length actual-secret)))\n    (let ((ctx (make-instance 'api-context\n                              :key \"OVERRIDE_KEY\"\n                              :secret full-secret\n                              :hostname \"\")))\n      (is (equal \"OVERRIDE_KEY\" (key ctx)))\n      ;; When key is provided, we don't extract, so secret stays as-is\n      (is (equal full-secret (secret ctx))))))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-backoff.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-backoff\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/backoff\n                #:server-unavailable\n                #:too-many-requests\n                #:maybe-retry-request))\n(in-package :screenshotbot/sdk/test-backoff)\n\n\n(util/fiveam:def-suite)\n\n(test maybe-retry-request-happy-path\n  \"Just that for each of the codes, we correctly invoke the restart,\nsince the logs might be different for each code.\"\n  (loop for code in '(429 502 503) do\n    (is\n     (eql :done\n          (restart-case\n              (maybe-retry-request code\n                                   :attempt 0\n                                   :backoff 0\n                                   :restart 'foobar)\n            (foobar ()\n              :done))))))\n\n(test if-weve-attempted-enough-times-crash\n  (dolist (code '(429 503))\n   (signals too-many-requests\n     (restart-case\n         (maybe-retry-request\n          code\n          :attempt 5\n          :backoff 0\n          :restart 'foobar)\n       (foobar ()\n         :done)))))\n\n(test if-weve-attempted-enough-times-crash-for-service-unavailable\n  (dolist (code '(408 502))\n   (signals server-unavailable\n     (restart-case\n         (maybe-retry-request\n          code\n          :attempt 5\n          :backoff 0\n          :restart 'foobar)\n       (foobar ()\n         :done)))))\n\n(test but-if-ERRORP-is-nil-then-we-dont-propagate\n  (dolist (code '(408 502))\n   (finishes\n     (restart-case\n         (maybe-retry-request\n          408\n          :attempt 5\n          :backoff 0\n          :errorp nil\n          :restart 'foobar)\n       (foobar ()\n         :done)))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-bundle.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-bundle\n  (:use :cl\n   :alexandria\n        :fiveam)\n  (:import-from :screenshotbot/sdk/bundle\n   :image-directory\n   :image-directory-with-diff-dir\n   :image-name\n   :image-pathname\n   :list-images)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string))\n(in-package :screenshotbot/sdk/test-bundle)\n\n(util/fiveam:def-suite)\n\n(defun %set-equal (x y)\n  (set-equal x y :test 'equalp))\n\n(def-fixture state ()\n  (tmpdir:with-tmpdir (dir)\n    (with-open-file (s1 (path:catfile dir \"foo1.png\")  :direction :output)\n      (with-open-file (s2 (path:catfile dir \"foo2.txt\") :direction :output)\n        (with-open-file (s3 (path:catfile dir \"foo3.png\") :direction :output)\n          (&body))))))\n\n(test test-bundle\n  (with-fixture state ()\n    (let ((bundle (make-instance 'image-directory\n                                 :directory dir)))\n      (let ((ims (list-images bundle)))\n        (is (eql 2 (length ims)))\n        (is (%set-equal\n             (list \"foo1\" \"foo3\")\n             (mapcar 'image-name ims)))))))\n\n(test test-bundle-does-not-recurse-by-default\n  (with-fixture state ()\n    (with-open-file (s4 (ensure-directories-exist (path:catfile dir \"blah/car.png\"))\n                        :direction :output))\n    (let ((bundle (make-instance 'image-directory\n                                 :directory dir)))\n      (let ((ims (list-images bundle)))\n        (is (eql 2 (length ims)))\n        (is (%set-equal\n             (list \"foo1\" \"foo3\")\n             (mapcar 'image-name ims)))))))\n\n(test test-bundle-with-recursion\n  (with-fixture state ()\n    (with-open-file (s4 (ensure-directories-exist (path:catfile dir \"blah/car.png\"))\n                        :direction :output))\n    (let ((bundle (make-instance 'image-directory\n                                 :directory dir\n                                 :recursivep t)))\n      (let ((ims (list-images bundle)))\n        (is (%set-equal\n             (list \"foo1\" \"foo3\" \"blah/car\")\n             (mapcar 'image-name ims)))))))\n\n(test test-bundle-with-recursion-deeper\n  (with-fixture state ()\n    (with-open-file (s4 (ensure-directories-exist (path:catfile dir \"blah/car.png\"))\n                        :direction :output))\n    (with-open-file (s4 (ensure-directories-exist (path:catfile dir \"blah/car/dar.png\"))\n                        :direction :output))\n    (with-open-file (s4 (ensure-directories-exist (path:catfile dir \"blah/car/dar2.png\"))\n                        :direction :output))\n    (let ((bundle (make-instance 'image-directory\n                                 :directory dir\n                                 :recursivep t)))\n      (let ((ims (list-images bundle)))\n        (is (%set-equal\n             (list \"foo1\" \"foo3\" \"blah/car\"\n                   \"blah/car/dar\" \"blah/car/dar2\")\n             (mapcar 'image-name ims)))))))\n\n(defun true-namestring (x)\n  \"On Mac, the temp directory uses a symbolic link so it's not\npossible to compare the namestring directly\"\n  (truename (namestring x)))\n\n;; The SDK supports specifying the images as a pair of \"static\" images\n;; (say checked into the repo), and the \"diffs\". In this situation,\n;; the actual images will be all the static images overriden by any\n;; that are in the diff-dir\n(test diff-dir\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (diff-dir)\n      (with-open-file (s3-fail (path:catfile diff-dir \"failed_foo3.png\") :direction :output)\n        (let ((bundle (make-instance 'image-directory-with-diff-dir\n                                     :directory dir\n                                     :diff-dir diff-dir)))\n          (let ((ims (list-images bundle)))\n            (is (%set-equal\n                 (list \"foo1\" \"foo3\")\n                 (mapcar 'image-name ims)))\n            (is (not (str:starts-with-p \"/private\" (namestring dir))))\n            (is (%set-equal\n                 (mapcar 'true-namestring\n                         (list (path:catfile dir \"foo1.png\")\n                               (path:catfile diff-dir \"failed_foo3.png\")))\n                 (mapcar 'true-namestring (mapcar 'image-pathname ims))))))))))\n\n(test invalid-file-type\n  (tmpdir:with-tmpdir (dir)\n    (finishes\n      (make-instance 'image-directory\n                     :directory dir\n                     :file-types (list \"png\")))\n    (signals simple-error\n      (make-instance 'image-directory\n                     :directory dir\n                     :file-types \"png\"))\n    (handler-case\n        (make-instance 'image-directory\n                       :directory dir\n                       :file-types (list \"png\" \"pdf\"))\n      (simple-error (e)\n        (assert-that (format nil \"~a\" e)\n                     (contains-string\n                      \"Invalid\")\n                     (contains-string\n                      \"pdf\")))\n      (:no-error (_)\n        (declare (ignore _))\n        (fail \"Expected exception\")))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-cli-common.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-cli-common\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:root/command)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:import-from #:screenshotbot/sdk/install\n                #:credential-file)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:secret\n                #:key\n                #:hostname)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:make-api-context))\n(in-package :screenshotbot/sdk/test-cli-common)\n\n\n(util/fiveam:def-suite)\n\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (&body)))\n\n(test can-create-root-command\n  (with-fixture state ()\n   (finishes (root/command))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-clingon-api-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-clingon-api-context\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:make-api-context)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:secret\n                #:key\n                #:hostname)\n  (:import-from #:screenshotbot/sdk/install\n                #:credential-file))\n(in-package :screenshotbot/sdk/test-clingon-api-context)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (&body)))\n\n(test make-api-context-reads-from-credential-file\n  (with-fixture state ()\n   (uiop:with-temporary-file (:pathname p :stream out)\n     (write-string \"{\\\"hostname\\\":\\\"foo\\\",\\\"apiKey\\\":\\\"bar\\\",\\\"apiSecretKey\\\":\\\"car\\\"}\" out)\n     (finish-output out)\n\n     (if-called 'credential-file\n                (lambda () p))\n     (let ((res (make-api-context :api-key nil\n                                  :api-secret nil)))\n       (is (equal \"foo\" (hostname res)))\n       (is (equal \"bar\" (key res)))\n       (is (equal \"car\" (secret res)))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-commit-graph\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/integration-fixture\n                #:with-sdk-integration)\n  (:import-from #:screenshotbot/sdk/commit-graph\n                #:find-missing-commits\n                #:remove-duplicate-commits\n                #:get-local-commits\n                #:update-commit-graph-new-style\n                #:all-remote-refs\n                #:commit-graph-updater\n                #+lispworks\n                #:want-remote-ref\n                #+lispworks\n                #:update-from-pack\n                #:new-flow-enabled-p\n                #:update-commit-graph\n                #:get-commit-graph-refs)\n  (:import-from #:screenshotbot/git-repo\n                #:commit-graph-dag\n                #:commit-graph)\n  (:import-from #:screenshotbot/model/commit-graph\n                #:find-commit-graph)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/sdk/git\n                #:git-repo\n                #:repo-link\n                #:get-remote-url\n                #:git-command\n                #:fetch-remote-branch)\n  (:import-from #:cl-mock\n                #:answer\n                #:if-called)\n  #+lispworks\n  (:import-from #:screenshotbot/sdk/git-pack\n                #:make-remote-upload-pack\n                #:local-upload-pack)\n  (:import-from #:screenshotbot/api/core\n                #:*wrap-internal-errors*)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context-prepared-p\n                #:api-feature-enabled-p\n                #:api-features)\n  (:import-from #:fiveam-matchers/misc\n                #:is-not-null)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/any-of\n                #:any-of)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)\n                    (#:test-git #:screenshotbot/sdk/test-git)\n                    (#:git #:screenshotbot/sdk/git)))\n(in-package :screenshotbot/sdk/test-commit-graph)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (let ((*wrap-internal-errors* nil)\n          (auto-restart:*global-enable-auto-retries-p* nil))\n      (with-sdk-integration (api-context :company company)\n        (gk:create :cli-shallow-clones)\n        (let ((self (make-instance 'commit-graph-updater :api-context api-context)))\n          (&body))))))\n\n(defvar *repo* \"https://github.com/tdrhq/fast-example.git\")\n\n(test happy-path-without-refs\n  (with-fixture state ()\n    (is (eql nil (get-commit-graph-refs\n                  self\n                  *repo*)))))\n\n(test case-with-repo-though\n  (with-fixture state ()\n    (make-instance 'commit-graph\n                   :company company\n                   :url *repo*\n                   :refs `((\"master\" . \"abcd\")))\n    (assert-that\n     (get-commit-graph-refs\n      self\n      *repo*)\n     (contains\n      (has-typep 'dto:git-ref)))))\n\n(test update-commit-graph-happy-path\n  (with-fixture state ()\n   (if-called 'fetch-remote-branch\n              (lambda (repo branch)\n                (values)))\n    (test-git:with-git-repo (repo :dir dir)\n      (test-git:make-commit repo \"foo\")\n      (test-git:make-commit repo \"bar\")\n      (finishes\n       (update-commit-graph self\n                            repo\n                            \"main\")))))\n\n(test gk-is-propagated--validating-assumption\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (is-false (api-context-prepared-p api-context))\n    (is-true (api-feature-enabled-p api-context :cli-shallow-clones))))\n\n(test new-flow-enabled-p-happy-path\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (is-true (api-feature-enabled-p api-context :cli-shallow-clones))\n    (test-git:with-git-repo (repo :dir dir)\n      (is-false (new-flow-enabled-p self repo))\n      (git::$ (git-command repo) \"remote\" \"add\" \"origin\" \"git@github.com:tdrhq/fast-example.git\")\n      (#+lispworks is-true #-lispworks is-false (new-flow-enabled-p self repo)))))\n\n#+lispworks\n(test update-from-pack\n  (with-fixture state ()\n    (test-git:with-git-repo (repo :dir dir)\n      (test-git:make-commit repo \"foo\")\n      (test-git:enable-server-features repo)\n      (let ((upload-pack (local-upload-pack repo)))\n        (finishes\n         (update-from-pack\n          self\n          upload-pack\n          \"git@github.com:tdrhq/fast-example.git\"\n          (list (git:current-branch repo))))\n\n        (let* ((commit-graph (first (bknr.datastore:class-instances 'commit-graph)))\n               (dag (commit-graph-dag commit-graph)))\n          (assert-that (dag:get-commit dag (git:current-commit repo))\n                       (is-not-null)))))))\n\n#+lispworks\n(test update-from-pack-twice\n  (with-fixture state ()\n    (test-git:with-git-repo (repo :dir dir)\n      (test-git:make-commit repo \"foo\")\n      (test-git:enable-server-features repo)\n      (let ((upload-pack (local-upload-pack repo)))\n        (update-from-pack\n         self\n         upload-pack\n         \"git@github.com:tdrhq/fast-example.git\"\n         (list (git:current-branch repo))))\n      (test-git:make-commit repo \"bar\")\n      (let ((upload-pack (local-upload-pack repo)))\n        (finishes\n         (update-from-pack\n          self\n          upload-pack\n          \"git@github.com:tdrhq/fast-example.git\"\n          (list (git:current-branch repo))))))))\n\n#+lispworks\n(test update-from-pack-twice-when-nothing-has-changed\n  (with-fixture state ()\n    (test-git:with-git-repo (repo :dir dir)\n      (test-git:make-commit repo \"foo\")\n      (test-git:make-commit repo \"bar\")\n      (test-git:enable-server-features repo)\n      (let ((upload-pack (local-upload-pack repo)))\n        (update-from-pack\n         self\n         upload-pack\n         \"git@github.com:tdrhq/fast-example.git\"\n         (list (git:current-branch repo))))\n      (let ((upload-pack (local-upload-pack repo)))\n        (finishes\n         (update-from-pack\n          self\n          upload-pack\n          \"git@github.com:tdrhq/fast-example.git\"\n          (list (git:current-branch repo))))))))\n\n#+lispworks\n(test new-flow-uses-repo-link-not-git-remote\n  \"When --repo-url is passed (e.g. GitHub URL), but the git remote points\nelsewhere (e.g. GitLab mirror), the commit graph should be uploaded to\nthe --repo-url, not the git remote. See T2224.\"\n  (with-fixture state ()\n    (let ((github-url \"https://github.com/example-org/my-project\")\n          (gitlab-url \"https://gitlab-ci-token:glcbt@gitlab.example.com/internal/my-project.git\"))\n      ;; Create repo with :link set to GitHub URL (simulating --repo-url flag)\n      (test-git:with-git-repo (repo :link github-url :dir dir)\n        (test-git:make-commit repo \"initial commit\")\n        (test-git:enable-server-features repo)\n        ;; Set git remote to GitLab URL (simulating GitLab CI clone)\n        (git::$ (git-command repo) \"remote\" \"add\" \"origin\" gitlab-url)\n        ;; Verify the setup: repo-link is GitHub, git remote is GitLab\n        (is (equal github-url (repo-link repo)))\n        (is (equal gitlab-url (get-remote-url repo)))\n        ;; Run the new flow\n        (let ((upload-pack (local-upload-pack repo)))\n          (update-from-pack\n           self\n           upload-pack\n           ;; BUG: Currently this function is called with git:get-remote-url\n           ;; but it should use git:repo-link\n           github-url\n           (list (git:current-branch repo))\n           :local-commits (list (list \"aaaa\" ;; <-- fake local commit\n                                       (git:current-commit repo)))))\n        ;; Verify: commit graph should exist for GitHub URL\n        (let ((github-commit-graph\n                (find-commit-graph\n                 company github-url)))\n          (is-true github-commit-graph\n                   \"Commit graph should be created for --repo-url (GitHub)\"))\n        ;; Verify: commit graph should NOT exist for GitLab URL\n        (let ((gitlab-commit-graph\n                (find-commit-graph\n                 company gitlab-url)))\n          (is-false gitlab-commit-graph\n                    \"Commit graph should NOT be created for git remote (GitLab)\"))))))\n\n#+lispworks\n(test update-commit-graph-new-style-uses-repo-link\n  \"Regression test for T2224: update-commit-graph-new-style should use\nrepo-link (--repo-url) not git:get-remote-url when identifying the repo\non the server. This test will FAIL until the bug is fixed.\"\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (let ((github-url \"https://github.com/example-org/my-project\")\n          (gitlab-url \"https://gitlab.example.com/internal/my-project.git\"))\n      (test-git:with-git-repo (repo :link github-url :dir dir)\n        (test-git:make-commit repo \"initial commit\")\n        (test-git:enable-server-features repo)\n        ;; Set git remote to GitLab (simulates GitLab CI environment)\n        (git::$ (git-command repo) \"remote\" \"add\" \"origin\" gitlab-url)\n        ;; Confirm new flow will be used\n        (is-true (new-flow-enabled-p self repo)\n                 \"New flow should be enabled for this test\")\n        ;; Mock fetch-remote-branch since we don't have a real remote\n        (cl-mock:if-called 'fetch-remote-branch\n                           (lambda (repo branch)\n                             (declare (ignore repo branch))\n                             (values)))\n        ;; Mock make-remote-upload-pack to use local pack instead of trying\n        ;; to connect to the fake GitLab URL\n        (cl-mock:if-called 'make-remote-upload-pack\n                           (lambda (repo)\n                             (local-upload-pack repo)))\n        ;; Run update-commit-graph (the high-level function)\n        (update-commit-graph self repo \"master\")\n        ;; The commit graph SHOULD be created for repo-link (GitHub URL)\n        (let ((github-cg (find-commit-graph\n                          company github-url)))\n          (is-true github-cg\n                   \"Commit graph should be created for --repo-url (GitHub), not git remote\"))\n        ;; The commit graph should NOT be created for the git remote (GitLab URL)\n        (let ((gitlab-cg (find-commit-graph\n                          company gitlab-url)))\n          (is-false gitlab-cg\n                    \"Commit graph should NOT be created for git remote (GitLab)\"))))))\n\n#+lispworks\n(test want-remote-ref\n  (is-true (want-remote-ref nil (list \"master\")\n                            \"abcd\"\n                            \"refs/heads/master\"))\n  (is-false (want-remote-ref nil (list \"master\")\n                             \"abcd\"\n                             \"refs/heads/boo\"))\n  (is-false\n   (want-remote-ref\n    (list\n     (make-instance 'dto:git-ref\n                    :sha \"abcd\"\n                    :name \"master\"))\n    (list \"master\")\n    \"abcd\"\n    \"refs/heads/master\"))\n\n  (is-true\n   (want-remote-ref\n    (list\n     (make-instance 'dto:git-ref\n                    :sha \"0011\"\n                    :name \"master\"))\n    (list \"master\")\n    \"abcd\"\n    \"refs/heads/master\")))\n\n\n(test stores-refs-after-updating\n  #+lispworks\n  (with-fixture state ()\n    (test-git:with-git-repo (repo :dir dir)\n      (test-git:make-commit repo \"foo\")\n      (test-git:enable-server-features repo)\n      (let ((upload-pack (local-upload-pack repo)))\n        (update-from-pack\n         self\n         upload-pack\n         (namestring dir)\n         (list (git:current-branch repo))))\n      (assert-that\n       (get-commit-graph-refs\n        self\n        (namestring dir))\n       (contains\n        (has-typep 'dto:git-ref)))\n\n      (is (equal (git:current-commit repo)\n                 (gethash \"refs/heads/master\" (all-remote-refs self)))))))\n\n(test clone-and-update-commit-graph\n  #+lispworks\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (test-git:with-git-repo (repo1 :dir dir1)\n      ;; Create initial repo with one commit\n      (test-git:make-commit repo1 \"initial commit\")\n      (test-git:enable-server-features repo1)\n\n      ;; Clone the repo to a new location\n      (tmpdir:with-tmpdir (dir)\n        (uiop:run-program\n         (list \"git\" \"clone\" (namestring dir1)\n               (namestring (path:catdir dir \"repo2/\"))))\n        (let ((repo2 (make-instance 'git-repo :dir (path:catdir dir \"repo2/\") :link (namestring dir1))))\n          ;; Add a new commit to the cloned repo\n          (test-git:make-commit repo2 \"second commit\")\n          (test-git:enable-server-features repo2)\n\n          (assert-that (get-local-commits repo2)\n                       (has-length 2))\n          ;; Set git remote to enable new flow\n          (finishes\n            (update-commit-graph-new-style self repo2 (git:current-branch repo2)))\n\n          ;; Verify the commit graph contains both commits\n          (let* ((commit-graph (find-commit-graph company (namestring dir1)))\n                 (dag (commit-graph-dag commit-graph)))\n            (assert-that (dag:get-commit dag (git:current-commit repo1))\n                         (is-not-null))\n            (assert-that (dag:get-commit dag (git:current-commit repo2))\n                         (is-not-null))))))))\n\n(test shallow-clone-and-update-commit-graph\n  #+lispworks\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (test-git:with-git-repo (repo1 :dir dir1)\n      ;; Create 10 commits in the original repo\n      (loop for i from 1 to 2\n            do (test-git:make-commit repo1 (format nil \"commit ~a\" i)))\n      (test-git:enable-server-features repo1)\n\n      ;; Shallow clone with depth 1\n      ;; Use file:// URL to prevent local clone optimizations (hardlinks/alternates)\n      (tmpdir:with-tmpdir (dir)\n        (uiop:run-program\n         (list \"git\" \"clone\" \"--no-local\" \"--depth\" \"1\" (namestring dir1)\n               (namestring (path:catdir dir \"repo2/\"))))\n        (let ((repo2 (make-instance 'git-repo :dir (path:catdir dir \"repo2/\") :link (namestring dir1))))\n          ;; Add a new commit to the shallow cloned repo\n          (test-git:make-commit repo2 \"new local commit\")\n          ;; Don't enable server features on shallow clone - it can affect shallow boundaries\n\n          ;; Shallow clone should only have 2 commits visible locally:\n          ;; the grafted commit from origin + our new commit\n          (assert-that (get-local-commits repo2)\n                       (has-length 2))\n\n          ;; Update the commit graph\n          (finishes\n            (update-commit-graph-new-style self repo2 (git:current-branch repo2)))\n\n          ;; Verify the commit graph contains both the latest from repo1 and our new commit\n          (let* ((commit-graph (find-commit-graph company (namestring dir1)))\n                 (dag (commit-graph-dag commit-graph)))\n            (assert-that (dag:get-commit dag (git:current-commit repo1))\n                         (is-not-null))\n            (assert-that (dag:get-commit dag (git:current-commit repo2))\n                         (is-not-null))))))))\n\n;;; --- Unit tests for find-missing-commits ---\n\n(test find-missing-commits-chain-with-missing-parent\n  \"A -> B -> C where C is not in the commit list\"\n  (let ((commits (list (list \"aaa\" \"bbb\")\n                       (list \"bbb\" \"ccc\"))))\n    (is (equal (list \"ccc\")\n               (find-missing-commits commits)))))\n\n(test find-missing-commits-all-parents-present\n  \"All parents are present in the commit list\"\n  (let ((commits (list (list \"aaa\" \"bbb\")\n                       (list \"bbb\"))))\n    (is (equal nil\n               (find-missing-commits commits)))))\n\n(test find-missing-commits-empty-input\n  (is (equal nil (find-missing-commits nil))))\n\n(test find-missing-commits-root-commits\n  \"Root commits have no parents -- should not produce nil in the missing list\"\n  (let ((commits (list (list \"aaa\")\n                       (list \"bbb\" \"aaa\"))))\n    (is (equal nil (find-missing-commits commits)))))\n\n;;; --- Unit tests for remove-duplicate-commits ---\n\n(test remove-duplicate-commits-keeps-first\n  (let ((commits (list (list \"aaa\" \"bbb\")\n                       (list \"ccc\" \"ddd\")\n                       (list \"aaa\" \"eee\"))))\n    (is (equal (list (list \"aaa\" \"bbb\")\n                     (list \"ccc\" \"ddd\"))\n               (remove-duplicate-commits commits)))))\n\n(test remove-duplicate-commits-no-dupes\n  (let ((commits (list (list \"aaa\" \"bbb\")\n                       (list \"ccc\" \"ddd\"))))\n    (is (equal commits\n               (remove-duplicate-commits commits)))))\n\n(test remove-duplicate-commits-empty\n  (is (equal nil (remove-duplicate-commits nil))))\n\n;;; --- update-from-pack with nil local-commits ---\n\n#+lispworks\n(test update-from-pack-with-nil-local-commits\n  (with-fixture state ()\n    (test-git:with-git-repo (repo :dir dir)\n      (test-git:make-commit repo \"foo\")\n      (test-git:enable-server-features repo)\n      (let ((upload-pack (local-upload-pack repo)))\n        (finishes\n         (update-from-pack\n          self\n          upload-pack\n          \"git@github.com:tdrhq/fast-example.git\"\n          (list (git:current-branch repo))\n          :local-commits nil))\n\n        (let* ((commit-graph (first (bknr.datastore:class-instances 'commit-graph)))\n               (dag (commit-graph-dag commit-graph)))\n          (assert-that (dag:get-commit dag (git:current-commit repo))\n                       (is-not-null)))))))\n\n;;; --- Second call to update-commit-graph-new-style after new local commit ---\n\n#+lispworks\n(test update-commit-graph-new-style-twice\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (test-git:with-git-repo (repo1 :dir dir1)\n      (test-git:make-commit repo1 \"initial commit\")\n      (test-git:enable-server-features repo1)\n\n      (tmpdir:with-tmpdir (dir)\n        (uiop:run-program\n         (list \"git\" \"clone\" (namestring dir1)\n               (namestring (path:catdir dir \"repo2/\"))))\n        (let ((repo2 (make-instance 'git-repo :dir (path:catdir dir \"repo2/\") :link (namestring dir1))))\n          (test-git:make-commit repo2 \"second commit\")\n          (test-git:enable-server-features repo2)\n\n          ;; First upload\n          (finishes\n            (update-commit-graph-new-style self repo2 (git:current-branch repo2)))\n\n          (let ((commit-after-first (git:current-commit repo2)))\n            ;; Make another local commit\n            (test-git:make-commit repo2 \"third commit\")\n\n            ;; Second upload should not fail\n            (finishes\n              (update-commit-graph-new-style self repo2 (git:current-branch repo2)))\n\n            ;; All three commits should be in the graph\n            (let* ((commit-graph (find-commit-graph company (namestring dir1)))\n                   (dag (commit-graph-dag commit-graph)))\n              (assert-that (dag:get-commit dag (git:current-commit repo1))\n                           (is-not-null))\n              (assert-that (dag:get-commit dag commit-after-first)\n                           (is-not-null))\n              (assert-that (dag:get-commit dag (git:current-commit repo2))\n                           (is-not-null)))))))))\n\n;;; --- Merge commits with multiple parents ---\n\n#+lispworks\n(test clone-and-update-commit-graph-with-merge\n  (with-fixture state ()\n    (gk:enable :cli-shallow-clones)\n    (test-git:with-git-repo (repo1 :dir dir1)\n      (test-git:make-commit repo1 \"initial commit\")\n      (test-git:enable-server-features repo1)\n\n      (tmpdir:with-tmpdir (dir)\n        (uiop:run-program\n         (list \"git\" \"clone\" (namestring dir1)\n               (namestring (path:catdir dir \"repo2/\"))))\n        (let* ((repo2 (make-instance 'git-repo :dir (path:catdir dir \"repo2/\") :link (namestring dir1)))\n               (master (git::$ (git-command repo2) \"rev-parse\" \"--abbrev-ref\" \"HEAD\")))\n\n          (assert-that master\n                       (any-of\n                        (is-equal-to \"master\")))\n\n          (test-git:enable-server-features repo2)\n\n          ;; Create a branch with a commit (use a different file to avoid merge conflict)\n          (git::$ (git-command repo2) \"checkout\" \"-b\" \"feature\")\n\n          (test-git:make-commit repo2 \"feature content\" :file \"feature.txt\")\n\n          (let ((feature-commit (git:current-commit repo2)))\n            ;; Go back to master and make a different commit (writes to file.txt)\n            (git::$ (git-command repo2) \"checkout\" \"master\")\n            (is-false (path:-e (path:catfile (git:repo-dir repo2) \"feature.txt\")))\n            (test-git:make-commit repo2 \"master commit\")\n\n            ;; Merge feature into master (creates a merge commit with 2 parents)\n            (test-git::run-in-dir\n             repo2\n             \"git merge --no-edit -m message feature\"\n             :error-output t\n             :output t)\n\n            (test-git::run-in-dir\n             repo2\n             \"test -f feature.txt\")\n\n            (let ((merge-commit (git:current-commit repo2)))\n\n              ;; Verify local commits include the merge commit with multiple parents\n              (let* ((local-commits (get-local-commits repo2))\n                     (merge-entry (find merge-commit local-commits\n                                        :key #'car :test #'equal)))\n                (is (>= (length (cdr merge-entry)) 2)\n                    \"Merge commit should have at least 2 parents\"))\n\n              ;; find-missing-commits should handle multiple parents\n              (finishes\n                (update-commit-graph-new-style self repo2 (git:current-branch repo2)))\n\n              ;; Verify the graph has the merge commit and the feature commit\n              (let* ((commit-graph (find-commit-graph company (namestring dir1)))\n                     (dag (commit-graph-dag commit-graph)))\n                (assert-that (dag:get-commit dag merge-commit)\n                             (is-not-null))\n                (assert-that (dag:get-commit dag feature-commit)\n                             (is-not-null))))))))))\n\n;;; --- find-missing-commits with merge commits ---\n\n(test find-missing-commits-with-merge\n  \"Merge commit has two parents, both should be checked\"\n  (let ((commits (list (list \"merge\" \"aaa\" \"bbb\")\n                       (list \"aaa\"))))\n    (is (equal (list \"bbb\")\n               (find-missing-commits commits)))))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-env.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-env\n  (:use #:cl\n        #:fiveam\n        #:screenshotbot/sdk/env)\n  (:import-from #:screenshotbot/sdk/env\n                #:pull-request-base-branch\n                #:read-java-property\n                #:teamcity-env-reader\n                #:xcode-cloud-env-reader\n                #:codemagic-env-reader\n                #:remove-.git\n                #:*all-readers*\n                #:gitlab-ci-env-reader\n                #:github-actions-env-reader\n                #:bitbucket-pipeline-env-reader\n                #:buildkite-env-reader\n                #:azure-env-reader\n                #:validp\n                #:netlify-env-reader\n                #:bitrise-env-reader\n                #:circleci-env-reader\n                #:env-reader)\n  (:import-from #:screenshotbot/sdk/git\n                #:git-message\n                #:git-repo)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer))\n(in-package :screenshotbot/sdk/test-env)\n\n(util/fiveam:def-suite)\n\n(defun test-happy-fns (env-reader)\n  (is (member (type-of env-reader)\n              (list*\n               'env-reader\n               *all-readers*)))\n  (validp env-reader)\n  (api-key env-reader)\n  (api-secret env-reader)\n  (api-hostname env-reader)\n  (pull-request-url env-reader)\n  (sha1 env-reader)\n  (build-url env-reader)\n  (guess-channel-name env-reader)\n  (repo-url env-reader)\n  (work-branch env-reader)\n  (pull-request-base-branch env-reader))\n\n(def-fixture state ()\n  (&body))\n\n(test base-reader\n  (finishes (test-happy-fns (make-instance 'env-reader :overrides nil))))\n\n(test circleci\n  (finishes (test-happy-fns (make-instance 'circleci-env-reader\n                                           :overrides nil))))\n\n(test bitrise\n  (finishes (test-happy-fns (make-instance 'bitrise-env-reader\n                                           :overrides nil))))\n\n(test netlify\n  (finishes (test-happy-fns (make-instance 'netlify-env-reader\n                                           :overrides nil))))\n\n(test bitrise-pull-request-url\n  (is (equal \"https://bitbucket.com/fast-example/pull-requests/2\"\n             (pull-request-url\n              (make-instance 'bitrise-env-reader\n                             :overrides `((\"GIT_REPOSITORY_URL\" . \"https://bitbucket.com/fast-example\")\n                                          (\"BITRISE_PULL_REQUEST\" . \"2\")))))))\n\n(test azure\n  (finishes (test-happy-fns (make-instance 'azure-env-reader\n                                           :overrides nil))))\n\n(test buildkite\n  (finishes (test-happy-fns (make-instance 'buildkite-env-reader\n                                           :overrides nil))))\n\n(test bitbucket-pipeline\n  (finishes (test-happy-fns (make-instance 'bitbucket-pipeline-env-reader\n                                           :overrides nil))))\n\n(test gitlab-ci-pipeline\n  (finishes (test-happy-fns (make-instance 'gitlab-ci-env-reader\n                                           :overrides nil))))\n\n(test github-actions\n  (finishes (test-happy-fns (make-instance 'github-actions-env-reader\n                                           :overrides nil))))\n\n\n\n(test buildkite-pull-request-is-not-nil-when-not-false\n  (let ((one (make-instance\n              'buildkite-env-reader\n              :overrides `((\"BUILDKITE_PULL_REQUEST\" . \"2\")\n                           (\"BUILDKITE_REPO\" . \"https://github.com/tdrhq/fast-example\")))))\n    (is (equal \"https://github.com/tdrhq/fast-example/pull/2\"\n               (pull-request-url one)))))\n\n(test buildkite-pull-request-is-nil-when-false\n  (let ((one (make-instance\n              'buildkite-env-reader\n              :overrides `((\"BUILDKITE_PULL_REQUEST\" . \"false\")\n                           (\"BUILDKITE_REPO\" . \"https://github.com/tdrhq/fast-example\")))))\n    (is (equal nil\n               (pull-request-url one)))))\n\n(test make-env-reader-happy-path\n  (finishes (make-env-reader)))\n\n(test all-readers-has-valid-types\n  (loop for reader-name in *all-readers*\n        do (is-true (find-class reader-name))))\n\n(test github-reads-env-from-git-repo\n  (cl-mock:with-mocks ()\n    (if-called 'git-message\n               (lambda (git-repo)\n                 \" Merge 02520edac7d38b71bacaee1c32d3c7f5cd880f8b into 38181385c139952159a3cf69950f8ff658395efb  \"))\n    (let ((reader (make-instance 'github-actions-env-reader\n                                 :overrides `((\"GITHUB_EVENT_NAME\" . \"pull_request\")\n                                              (\"GITHUB_SHA\" . \"bleh\")))))\n      (is (equal \"02520edac7d38b71bacaee1c32d3c7f5cd880f8b\" (sha1 reader))))))\n\n(test github-reads-env-when-not-pull-request\n  (cl-mock:with-mocks ()\n    (if-called 'git-message\n               (lambda (git-repo)\n                 \" Merge 02520edac7d38b71bacaee1c32d3c7f5cd880f8b into 38181385c139952159a3cf69950f8ff658395efb  \"))\n    (let ((reader (make-instance 'github-actions-env-reader\n                                 :overrides `((\"GITHUB_EVENT_NAME\" . \"push\")\n                                              (\"GITHUB_SHA\" . \"bleh\")))))\n      (is (equal \"bleh\" (sha1 reader)))\n      (is-false (pull-request-url reader)))))\n\n(test github-reads-pull-request-correctly\n  (cl-mock:with-mocks ()\n    (let ((reader (make-instance 'github-actions-env-reader\n                                 :overrides `((\"GITHUB_EVENT_NAME\" . \"pull_request\")\n                                              (\"GITHUB_SERVER_URL\" . \"https://github.com\")\n                                              (\"GITHUB_REPOSITORY\" . \"tdrhq/fast-example\")\n                                              (\"GITHUB_REF\" . \"refs/pull/22/merge\")))))\n      (is (equal \"https://github.com/tdrhq/fast-example/pull/22\" (pull-request-url reader))))))\n\n(test remove-.git\n  (is (equal \"foo\" (remove-.git \"foo\")))\n  (is (equal \"foo\" (remove-.git \"foo.git\")))\n  (is (equal \"foogit\" (remove-.git \"foogit\"))))\n\n(test guess-channel-name-from-repo-url\n  (flet ((guess-for (name)\n           (let ((reader (make-instance 'circleci-env-reader\n                                        :overrides `((\"CIRCLE_REPOSITORY_URL\" . ,name)))))\n             (guess-channel-name reader))))\n    (is (equal \"fast-example\" (guess-for \"https://github.com/tdrhq/fast-example.git\")))\n    (is (equal \"fast-example\" (guess-for \"https://github.com/tdrhq/fast-example\")))\n    (is (equal \"fast-example\" (guess-for \"https://github.com/tdrhq/fast-example/\")))\n\n    ;; We should never return an empty string\n    (is (equal nil (guess-for \"https://github.com/bad/.git\")))))\n\n(test pull-request-url-is-not-present-on-main-branch-on-gitlab\n  (let ((reader (make-instance 'gitlab-ci-env-reader)))\n    (is (eql nil (pull-request-url reader)))))\n\n(test pull-request-url-*is*-present-on-MR-on-gitlab\n  (let ((reader (make-instance 'gitlab-ci-env-reader\n                               :overrides `((\"CI_MERGE_REQUEST_PROJECT_URL\" . \"https://example.com\")\n                                            (\"CI_MERGE_REQUEST_IID\" . 1)))))\n    (is (equal \"https://example.com/-/merge_requests/1\" (pull-request-url reader)))))\n\n\n(test teamcity\n  (finishes (test-happy-fns (make-instance 'teamcity-env-reader\n                                           :overrides nil))))\n\n(test xcode-cloud\n  (finishes (test-happy-fns (make-instance 'xcode-cloud-env-reader\n                                           :overrides nil))))\n\n(test xcode-cloud-pull-request-url\n  (let ((reader (make-instance 'xcode-cloud-env-reader\n                               :overrides `((\"CI_PULL_REQUEST_NUMBER\" . \"42\")))))\n    ;; Without repo-url, pull-request-url should return nil\n    (is (equal nil (pull-request-url reader))))\n  ;; Note: Xcode Cloud doesn't provide CI_REPOSITORY_URL, so pull-request-url\n  ;; will be nil unless repo-url is provided through other means\n  )\n\n(test xcode-cloud-environment-variables\n  (let ((reader (make-instance 'xcode-cloud-env-reader\n                               :overrides `((\"CI_BUILD_ID\" . \"12345\")\n                                            (\"CI_XCODE_CLOUD\" . \"true\")\n                                            (\"CI_COMMIT\" . \"abc123def456\")\n                                            (\"CI_BRANCH\" . \"feature/new-feature\")\n                                            (\"CI_PULL_REQUEST_NUMBER\" . \"42\")))))\n    (is (equal t (validp reader)))\n    (is (equal \"abc123def456\" (sha1 reader)))\n    (is (equal \"feature/new-feature\" (work-branch reader)))\n    ;; pull-request-url returns nil without repo-url\n    (is (equal nil (pull-request-url reader)))\n    ;; build-url is not provided by Xcode Cloud\n    (is (equal nil (build-url reader)))\n    ;; repo-url is not provided by Xcode Cloud\n    (is (equal nil (repo-url reader)))))\n\n(test read-java-property\n  (let ((input (asdf:system-relative-pathname :screenshotbot.sdk\n                                              \"fixture/teamcity/teamcity.config.parameters\")))\n    (is (equal \"/home/buildagent\"\n               (read-java-property input \"teamcity.agent.jvm.user.home\")))\n    (is (equal \"https://github.com/tdrhq/fast-example.git\"\n               (read-java-property input \"vcsroot.url\")))\n    (is (eql nil (read-java-property input \"does.not.exist\")))))\n\n(test codemagic\n  (finishes (test-happy-fns (make-instance 'codemagic-env-reader\n                                           :overrides nil))))\n\n(test codemagic-environment-variables\n  (let ((reader (make-instance 'codemagic-env-reader\n                               :overrides `((\"CM_BUILD_ID\" . \"a1b2c3d4\")\n                                            (\"CM_PROJECT_ID\" . \"proj-123\")\n                                            (\"CM_COMMIT\" . \"abc123def456\")\n                                            (\"CM_BRANCH\" . \"feature/new-feature\")\n                                            (\"CM_PULL_REQUEST_NUMBER\" . \"42\")\n                                            (\"CM_PULL_REQUEST_DEST\" . \"main\")))))\n    (is (equal t (validp reader)))\n    (is (equal \"abc123def456\" (sha1 reader)))\n    (is (equal \"feature/new-feature\" (work-branch reader)))\n    (is (equal \"main\" (pull-request-base-branch reader)))\n    (is (equal \"https://codemagic.io/app/proj-123/build/a1b2c3d4\" (build-url reader)))\n    ;; repo-url is not provided by Codemagic (only CM_REPO_SLUG)\n    (is (equal nil (repo-url reader)))\n    ;; Server only parses pulls/<id> from the URL\n    (is (equal \"https://codemagic.io/pull/42\" (pull-request-url reader)))))\n\n(test happy-path-when-vcsroot.url-is-not-present\n  (let ((env-reader (make-instance 'teamcity-env-reader)))\n    (is (equal nil\n               (repo-url env-reader)))))\n\n(test buildkite-includes-job-id\n  (let ((env-reader (make-instance\n                     'buildkite-env-reader\n                     ;; These examples come from here: https://buildkite.com/docs/apis/rest-api/jobs\n                     :overrides `((\"BUILDKITE_BUILD_URL\" . \"https://buildkite.com/my-great-org/my-pipeline/builds/1\")\n                                  (\"BUILDKITE_JOB_ID\" . \"b63254c0-3271-4a98-8270-7cfbd6c2f14e\")))))\n    (is (equal\n         \"https://buildkite.com/my-great-org/my-pipeline/builds/1#b63254c0-3271-4a98-8270-7cfbd6c2f14e\"\n         (build-url env-reader)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-fetch-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-fetch-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/fetch-run\n                #:save-runs-from-commit\n                #:Safe-name-p\n                #:unsafe-screenshot-name\n                #:*download-engine*\n                #:%save-run)\n  (:import-from #:util/request\n                #:engine\n                #:http-request-impl)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/sdk/integration-fixture\n                #:with-sdk-integration)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/model/image\n                #:make-image-from-fixture)\n  (:import-from #:screenshotbot/model/company\n                #:find-or-create-channel\n                #:company)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:run-to-dto)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-engine)\n  (:import-from #:screenshotbot/user-api\n                #:%created-at)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/sdk/test-fetch-run)\n\n(util/fiveam:def-suite)\n\n(defclass fake-engine ()\n  ())\n\n(defmethod http-request-impl ((engine fake-engine) url &key &allow-other-keys)\n  (flex:make-in-memory-input-stream\n   (flex:string-to-octets \"foobar\")))\n\n(def-fixture state (&key (screenshot-name \"foo\"))\n  (with-sdk-integration (api-context :company company)\n    (let ((*download-engine* (make-instance 'fake-engine)))\n     (let* ((img (make-image-from-fixture :company company\n                                          :fixture \"rose.png\"))\n            (screenshot (make-screenshot\n                         :name screenshot-name\n                         :image img)))\n       (let* ((channel (find-or-create-channel\n                        company\n                        \"zoidberg\"))\n              (run (make-recorder-run\n                    :commit-hash \"deadbeef\"\n                    :company company\n                    :channel channel\n                    :screenshots\n                    (list\n                     screenshot)))\n              (run (run-to-dto run :include-screenshots t)))\n         (&body))))))\n\n(test simple-save-run\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (dir)\n      (%save-run run :output dir)\n      (is (uiop:file-exists-p (path:catfile  dir \"foo.png\")))\n      (is (equal \"foobar\" (uiop:read-file-string (path:catfile  dir \"foo.png\")))))))\n\n(test crashes-on-trying-to-write-to-different-directory\n  (with-fixture state (:screenshot-name \"/../car/bar\")\n    (tmpdir:with-tmpdir (dir)\n      (signals unsafe-screenshot-name\n        (%save-run run :output dir)))))\n\n(test safe-name-p-on-some-options\n  (with-fixture state ()\n    (is-true (Safe-name-p \"foobar\"))\n    (is-false (safe-name-p \"/bar/car\"))\n    (is-false (Safe-name-p \"../car/bar\"))))\n\n(test nested-directory\n  (with-fixture state (:screenshot-name \"foo/bar\")\n    (tmpdir:with-tmpdir (dir)\n      (%save-run run :output dir)\n      (is (uiop:file-exists-p (path:catfile  dir \"foo/bar.png\")))\n      (is (equal \"foobar\" (uiop:read-file-string (path:catfile  dir \"foo/bar.png\")))))))\n\n(test save-runs-from-commit--easiest-path\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (dir)\n      (save-runs-from-commit api-context \"deadbeef\" :output dir)\n      (is (uiop:file-exists-p (path:catfile dir \"zoidberg/foo.png\"))))))\n\n(test save-runs-from-commit--two-channels\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (dir)\n      (let* ((channel (find-or-create-channel\n                       company\n                       \"foobar\"))\n             (run (make-recorder-run\n                   :commit-hash \"deadbeef\"\n                   :company company\n                   :channel channel\n                   :screenshots\n                   (list\n                    screenshot))))\n        (save-runs-from-commit api-context \"deadbeef\" :output dir)\n        (is (uiop:file-exists-p (path:catfile dir \"zoidberg/foo.png\")))\n        (is (uiop:file-exists-p (path:catfile dir \"foobar/foo.png\")))))))\n\n(test channel-name-has-/\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (dir)\n      (let* ((channel (find-or-create-channel\n                       company\n                       \"foo/bar\"))\n             (run (make-recorder-run\n                   :commit-hash \"deadbeef\"\n                   :company company\n                   :channel channel\n                   :screenshots\n                   (list\n                    screenshot))))\n        (save-runs-from-commit api-context \"deadbeef\" :output dir)\n        (is (uiop:file-exists-p (path:catfile dir \"zoidberg/foo.png\")))\n        (is (uiop:file-exists-p (path:catfile dir \"foo/bar/foo.png\")))))))\n\n(test channel-name-ends-with-/\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (dir)\n      (let* ((channel (find-or-create-channel\n                       company\n                       \"foo/bar/\"))\n             (run (make-recorder-run\n                   :commit-hash \"deadbeef\"\n                   :company company\n                   :channel channel\n                   :screenshots\n                   (list\n                    screenshot))))\n        (save-runs-from-commit api-context \"deadbeef\" :output dir)\n        (is (uiop:file-exists-p (path:catfile dir \"zoidberg/foo.png\")))\n        (is (uiop:file-exists-p (path:catfile dir \"foo/bar/foo.png\")))))))\n\n(test two-runs-for-same-commit\n  (with-fixture state ()\n    (tmpdir:with-tmpdir (dir)\n      (let* ((channel (find-or-create-channel\n                       company\n                       \"zoidberg\"))\n             (run (make-recorder-run\n                   :commit-hash \"deadbeef\"\n                   :company company\n                   :channel channel\n                   :screenshots\n                   (list\n                    (make-screenshot\n                     :name \"other-screenshot\"\n                     :image img)))))\n        ;; Ensure the new run is actually *newer*\n        (setf (%created-at run)\n              (1+ (get-universal-time)))\n        (save-runs-from-commit api-context \"deadbeef\" :output dir)\n\n        (is (uiop:file-exists-p (path:catfile dir \"zoidberg/other-screenshot.png\")))\n        (is (not (uiop:file-exists-p (path:catfile dir \"zoidberg/foo.png\"))))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-firebase.lisp",
    "content": ";; -*- coding: utf-8 -*-\n(defpackage :screenshotbot/sdk/test-firebase\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/firebase\n                #:%parse-test-axis-line\n                #:parse-firebase-output\n                #:firebase-output-bucket)\n  (:import-from #:screenshotbot/sdk/firebase\n                #:firebase-output-test-axis)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/sdk/test-firebase)\n\n(util/fiveam:def-suite)\n\n(defvar *output* \"google.com/support/\n\nUploading [./build/outputs/apk/debug/project-debug.apk] to Firebase Test Lab...\nUploading [./build/outputs/apk/androidTest/debug/project-debug-androidTest.apk] to Firebase Test Lab...\nRaw results will be stored in your GCS bucket at [https://console.developers.google.com/storage/browser/cloud-test-screenshotbot-example/2022-09-19_18:03:36.359222_WSoF/]\n\nTest [matrix-fz9c7urm9xoya] has been created in the Google Cloud.\nFirebase Test Lab will execute your instrumentation test on 1 device(s).\n\nTest results will be streamed to [https://console.firebase.google.com/project/*********************/testlab/histories/bh.73ae3a33c4730f39/matrices/7660637650631969176].\n18:03:44 Test is Pending\n18:04:09 Starting attempt 1.\n18:04:09 Test is Running\n18:05:46 Started logcat recording.\n18:05:46 Started crash monitoring.\n18:05:46 Preparing device.\n18:06:23 Logging in to Google account on device.\n18:06:23 Installing apps.\n18:06:35 Retrieving Performance Environment information from the device.\n18:06:35 Setting up Android test.\n18:06:35 Started crash detection.\n18:06:35 Started Out of memory detection\n18:06:35 Started performance monitoring.\n18:06:48 Starting Android test.\n18:07:06 Completed Android test.\n18:07:06 Stopped performance monitoring.\n18:07:06 Tearing down Android test.\n18:07:48 Logging out of Google account on device.\n18:07:48 Stopped crash monitoring.\n18:07:48 Stopped logcat recording.\n18:07:48 Done. Test time = 12 (secs)\n18:07:48 Starting results processing. Attempt: 1\n18:08:01 Completed results processing. Time taken = 9 (secs)\n18:08:01 Test is Finished\n\nInstrumentation testing complete.\n\nMore details are available at [https://console.firebase.google.com/project/*********************/testlab/histories/bh.73ae3a33c4730f39/matrices/7660637650631969176].\n┌─────────┬───────────────────────┬──────────────────────┐\n│ OUTCOME │    TEST_AXIS_VALUE    │     TEST_DETAILS     │\n├─────────┼───────────────────────┼──────────────────────┤\n│ Passed  │ Pixel2-27-en-portrait │ 12 test cases passed │\n└─────────┴───────────────────────┴──────────────────────┘\n\nCircleCI\")\n\n(test parse-firebase-output\n  (let ((res (parse-firebase-output *output*)))\n    (is (equal \"cloud-test-screenshotbot-example\"\n               (firebase-output-bucket res)))))\n\n(test parse-test-axis\n  (let ((res (parse-firebase-output *output*)))\n    (is (equal \"Pixel2-27-en-portrait\"\n               (firebase-output-test-axis res)))))\n\n(test parse-test-axis-line\n  (is (equal \"Pixel2-27-en-portrait\"\n             (%parse-test-axis-line \"│ Passed  │ Pixel2-27-en-portrait │ 12 test cases passed │\"))))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-flags.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-flags\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/flags\n                #:*directory*\n                #:*sdk-flags*)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/sdk/test-flags)\n\n(util/fiveam:def-suite)\n\n(test recording-of-flag\n  (is (eql '*directory* (gethash :directory *sdk-flags*))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-git-pack.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-git-pack\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:starts-with)\n  (:import-from #:screenshotbot/sdk/git-pack\n                #:http-upload-pack\n                #:%get-auth\n                #:has-authorization-header-p\n                #:upload-pack-error\n                #:remove-auth-from-uri\n                #:read-netrc\n                #:supported-remote-repo-p\n                #:read-commits\n                #:parse-parents\n                #:make-upload-pack-command)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:local-nicknames (#:test-git #:screenshotbot/sdk/test-git)\n                    (#:git #:screenshotbot/sdk/git)))\n(in-package :screenshotbot/sdk/test-git-pack)\n\n(util/fiveam:def-suite)\n\n(test parse-remote-git-pack\n  (assert-that\n   (str:join \" \" (make-upload-pack-command \"/tmp/foo/bar.git\"))\n   (starts-with \"/usr/bin/env git-upload-pack\"))\n  (assert-that\n   (str:join \" \" (make-upload-pack-command \"user@host:tmp/foo.git\"))\n   (starts-with \"/usr/bin/env ssh user@host git-upload-pack \"))  \n  (assert-that\n   (str:join \" \" (make-upload-pack-command \"ssh://user@host/tmp/foo.git\"))\n   (starts-with \"/usr/bin/env ssh user@host git-upload-pack \"))\n  (assert-that\n   (str:join \" \"(make-upload-pack-command \"user@foo.host.com:tmp/foo.git\"))\n   (starts-with \"/usr/bin/env ssh user@foo.host.com git-upload-pack \"))  \n  (assert-that\n   (str:join \" \" (make-upload-pack-command \"ssh://user@foo.host.com/tmp/foo.git\"))\n   (starts-with \"/usr/bin/env ssh user@foo.host.com git-upload-pack \"))\n  (assert-that\n   (str:join \" \" (make-upload-pack-command \"ssh://user@foo.host.com/tmp/foo.git\"))\n   (starts-with \"/usr/bin/env ssh user@foo.host.com git-upload-pack '/tmp/foo.git'\"))\n  (assert-that\n   (str:join \" \" (make-upload-pack-command \"ssh://git@phabricator.tdrhq.com:2222/source/web.git\"))\n   (starts-with \"/usr/bin/env ssh git@phabricator.tdrhq.com -p 2222 git-upload-pack '/source/web.git'\")))\n\n(test parse-parents\n  (is (equal\n       (list\n        \"334730e618c03bf2989eda474fc600953e000ec0\"\n        \"9c2a4ee0b041d286a4d79ab42d15c68b87d0cccd\")\n       (parse-parents\n        \"tree f10b145f8bf50b0503c8becc6c3a202b2bdd4308\nparent 334730e618c03bf2989eda474fc600953e000ec0\nparent 9c2a4ee0b041d286a4d79ab42d15c68b87d0cccd\nauthor doc001 <wenffie@gmail.com> 1685501589 +0800\ncommitter GitHub <noreply@github.com> 1685501589 +0800\ngpgsig -----BEGIN PGP SIGNATURE-----\n \n wsBcBAABCAAQBQJkdraVCRBK7hj4Ov3rIwAAF5QIAFPvRp2NctvDOeNr45tqgjnT\n nuO7p1FUdsa1YExbP8GaJ+iuf+mJnTnUsxFXkTH95u/CUo2BLEEJHS/pe/2kR7v9\n XYW10/JQOD9nst7xKDM6PBDKKOjc2ljmQOeSroIDid0yQ5yedo7ENW2Vd8rJWmMT\n b0oVuEkb0nmU8FygktAl8YHB81oKYKo3xxeZQMtPzCY39geIjn27J9jrd1wqC+OA\n X1oVEU6bJV4U5r6c2jReh5txmuZAyyODSojsssztM/EH1+g8ivBYM2Qypcd5lRqS\n Qv71S/iil2Ta5oKq/F9Wj6Bfd8L3+skZ9JIyDhaiT1qW1Z+J7tzkjqKJLh3oSNw=\n =M9w9\n -----END PGP SIGNATURE-----\n \n\nMerge pull request #401 from nanblpc/set-idle-when-block-timeout\n\nset st to idle when block timeout\"))))\n\n(test simple-repo-get-commits-integration\n  (test-git:with-git-repo (repo :dir dir)\n    (test-git:make-commit repo \"foo\")\n    (test-git:make-commit repo \"bar\")\n    (test-git:enable-server-features repo)\n    (let ((commits\n            (read-commits (namestring dir) :wants (list \"main\" \"master\"))))\n      (assert-that\n       commits\n       (has-length 2)))))\n\n(test simple-repo-get-commits-with-one-have\n  (test-git:with-git-repo (repo :dir dir)\n    (test-git:make-commit repo \"foo\")\n    (test-git:make-commit repo \"bar\")\n    (test-git:enable-server-features repo)\n    (let ((commits\n            (read-commits (namestring dir) :wants (list \"main\" \"master\")\n                          :haves (list (git:current-commit repo)))))\n      (assert-that\n       commits\n       (has-length 0)))))\n\n(test simple-repo-get-commits-with-duplicate-haves\n  (test-git:with-git-repo (repo :dir dir)\n    (test-git:make-commit repo \"foo\")\n    (test-git:make-commit repo \"bar\")\n    (test-git:enable-server-features repo)\n    (let ((commits\n            (read-commits (namestring dir) :wants (list \"main\" \"master\")\n                                           :haves (list (git:current-commit repo)\n                                                        (git:current-commit repo)))))\n      (assert-that\n       commits\n       (has-length 0)))))\n\n(test simple-repo-get-commits-with-haves-one-after-the-other\n  (test-git:with-git-repo (repo :dir dir)\n    (test-git:make-commit repo \"foo\")\n    (test-git:make-commit repo \"bar\")\n    (test-git:enable-server-features repo)\n    (let ((commits\n            (read-commits (namestring dir) :wants (list \"main\" \"master\")\n                                           :haves (list (git:current-commit repo)\n                                                        (git:rev-parse-local repo \"HEAD^\")))))\n      (assert-that\n       commits\n       (has-length 0)))))\n\n(test simple-repo-get-commits-that-does-not-exist\n  (test-git:with-git-repo (repo :dir dir)\n    (test-git:make-commit repo \"foo\")\n    (test-git:make-commit repo \"bar\")\n    (test-git:enable-server-features repo)\n    (let ((commits\n            (read-commits (namestring dir) :wants (list \"main\" \"master\")\n                                           :haves (list \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"))))\n      (assert-that\n       commits\n       (has-length 2)))))\n\n\n\n(test azure-IS-supported\n  \"Used to say `is not supported`\"\n  (is-true (supported-remote-repo-p \"git@ssh.dev.azure.com:v3/testsbot/fast-example/fast-example\"))\n  (is-true (supported-remote-repo-p \"git@github.com:fast-example/fast-example\")))\n\n(test netrc\n  (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n    (format s \"machine github.com~%login foo  ~%  password bar_car~%~%\")\n    (finish-output s)\n    (is (equal\n         (list \"foo\" \"bar_car\")\n         (read-netrc \"github.com\" :pathname p))))\n\n  (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n    (format s \"  #hello ~%machine github.com~%login foo  ~%  password bar_car~%~%\")\n    (finish-output s)\n    (is (equal\n         (list \"foo\" \"bar_car\")\n         (read-netrc \"github.com\" :pathname p))))\n\n  (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n    (format s \"machine github.com~%login foo  ~%# login bar~%  password bar_car~%~%\")\n    (finish-output s)\n    (is (equal\n         (list \"foo\" \"bar_car\")\n         (read-netrc \"github.com\" :pathname p))))\n\n  (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n    (format s \"machine github.comx~%login foo  ~%# login bar~%  password bar_car~%~%\")\n    (finish-output s)\n    (is (equal\n         nil\n         (read-netrc \"github.com\" :pathname p))))\n\n  (tmpdir:with-tmpdir (dir)\n    (read-netrc \"github.com\" :pathname (path:catfile dir \".netrc\"))))\n\n(test remove-auth-from-uri\n  (is\n   (equal\n    (list \"https://gitlab.com/my-group/my-repo.git\"\n          (list \"gitlab-ci-token\" \"foo\"))\n    (multiple-value-list\n     (remove-auth-from-uri \"https://gitlab-ci-token:foo@gitlab.com/my-group/my-repo.git\")))))\n\n(test remove-auth-from-uri-when-theres-no-auth\n  (is\n   (equal\n    (list \"https://gitlab.com/my-group/my-repo.git\"\n          nil)\n    (multiple-value-list\n     (remove-auth-from-uri \"https://gitlab.com/my-group/my-repo.git\")))))\n\n(test not-our-ref-error\n  \"Reproduces the 'fatal: git upload-pack: not our ref' error.\nThis happens when requesting a commit SHA that doesn't exist in the repository,\ntypically because it was removed via force push or history rewrite.\"\n  (test-git:with-git-repo (repo :dir dir)\n    (test-git:make-commit repo \"foo\")\n    (test-git:make-commit repo \"bar\")\n    (test-git:enable-server-features repo)\n    (signals upload-pack-error\n      (read-commits (namestring dir)\n                    :wants (lambda (refs)\n                             ;; Return a fake SHA that doesn't exist\n                             (list \"458a81c522f6d7325c9d893624c926eaa8ed2cc0\"))\n                    :depth 30))))\n\n;; This will actually wait for user input, not ideal. Preventing deploys at the moment.\n#+nil \n(test git-credential-fill-basic\n  \"Test that git-credential-fill calls git credential fill with proper input\"\n  ;; This test checks the basic functionality without actually calling git\n  ;; In a real environment with git credential configured, this would return credentials\n  (let ((result (git:credential-fill \"https://github.com/org/repo.git\")))\n    ;; Result should either be nil (no credentials configured) or a list of (username password)\n    (is (or (null result)\n            (and (listp result)\n                 (= 2 (length result))\n                 (stringp (first result))\n                 (stringp (second result)))))))\n\n(test has-authorization-header-p\n  (is-true\n   (has-authorization-header-p\n    `((:authorization \"sdfdsfd\"))))\n  (is-false\n   (has-authorization-header-p\n    `((:fake-key \"sdfdsfd\")))))\n\n(test %get-auth\n  (cl-mock:with-mocks ()\n    (cl-mock:if-called 'read-netrc\n                       (lambda (&rest args)\n                         (error \"should not have been called\")))\n    (cl-mock:if-called 'git:credential-fill\n                       (lambda (&rest args)\n                         (error \"should not have been called\")))\n   (let ((upload-pack (make-instance 'http-upload-pack\n                                     :extra-headers `((\"Authorization\" . \"Bearer foo\"))\n                                     :repo \"https://arnold:bar@github.com/tdrhq/fast-example.git\")))\n     (is (equal nil (%get-auth upload-pack))))\n    (let ((upload-pack (make-instance 'http-upload-pack\n                                      :extra-headers `((\"X-car\" . \"foo\"))\n                                      :repo \"https://arnold:bar@github.com/tdrhq/fast-example.git\")))\n      (is (equal '(\"arnold\" \"bar\") (%get-auth upload-pack))))))\n\n(test %get-auth-does-not-call-netrc-at-all\n  (cl-mock:with-mocks ()\n    (cl-mock:if-called 'read-netrc\n                       (lambda (&rest args)\n                         (error \"should not have been called\")))\n    (cl-mock:if-called 'git:credential-fill\n                       (lambda (&rest args)\n                         (error \"should not have been called\")))\n    (let ((upload-pack (make-instance 'http-upload-pack\n                                      :extra-headers `((\"Authorization\" . \"Bearer foo\"))\n                                      :repo \"https://github.com/tdrhq/fast-example.git\")))\n      (is (equal nil (%get-auth upload-pack))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-git.lisp",
    "content": ";; -*- encoding: utf-8 -*-\n;;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-git\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/git\n                #:debug-git-config\n                #:parse-raw-git-log\n                #:repo-link\n                #:get-remote-url\n                #:author\n                #:fetch-remote-branch\n                #:git-root\n                #:git-message\n                #:read-graph\n                #:rev-parse\n                #:current-commit\n                #:current-branch\n                #:git-command\n                #:repo-dir\n                #:git-repo\n                #:$)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:export\n   #:with-git-repo\n   #:make-commit\n   #:enable-server-features))\n(in-package :screenshotbot/sdk/test-git)\n\n(util/fiveam:def-suite)\n\n(defun run-in-dir (repo cmd &rest args)\n  (declare (optimize (debug 3) (speed 0)))\n  (let ((cmd (cl-ppcre:regex-replace\n              \"^git \" cmd\n              \"git -c user.name=FooBar -c user.email=foo@example.com \")))\n    (apply #'uiop:run-program (format nil \"cd ~a && ~a\" (namestring (repo-dir repo)) cmd)\n           args)))\n\n(defun enable-server-features (repo)\n  (loop for feature in (list \"filter\"\n                             \"allowTipSHA1InWant\"\n                             \"allowReachableSHA1InWant\")\n        do\n        (run-in-dir repo (format nil \"git config uploadpack.~a true\" feature))))\n\n(defun make-commit (repo content &key (file \"file.txt\"))\n  (let ((path (path:catfile (repo-dir repo) file)))\n   (with-open-file (stream path\n                           :direction :output\n                           :if-exists :supersede)\n     (write-string content stream))\n    (run-in-dir repo (format nil \"git add ~a\" file))\n    (run-in-dir repo (format nil \"git commit -a -m Updating-~a\" file))))\n\n(defun make-directory-deletable (dir)\n  \"On windows, git might have read-only files\"\n  (declare (ignorable dir))\n  #+(or windows mswindows)\n  (uiop:run-program (list \"attrib\" \"-r\" (namestring (path:catfile dir \"*.*\")) \"/s\")))\n\n(def-easy-macro with-tmpdir (&binding dir &fn fn)\n  (tmpdir:with-tmpdir (dir)\n    (unwind-protect\n         (fn dir)\n      (make-directory-deletable dir))))\n\n(def-easy-macro with-git-repo (&binding repo &key link &binding dir &fn fn)\n  (with-tmpdir (dir)\n    (progn\n      (uiop:run-program (list \"git\" \"init\" (namestring dir)))\n      (let ((repo (make-instance 'git-repo :dir dir :link link)))\n        (fn repo dir)))))\n\n(def-fixture git-repo ()\n  #-screenshotbot-oss\n  (with-git-repo (repo :dir dir)\n    (&body)))\n\n(test get-current-commit\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (assert-that (current-commit repo)\n                 (has-length 40))))\n\n(test get-current-branch\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (run-in-dir repo \"git checkout -b carbar\")\n    (is (equal \"carbar\"\n               (current-branch repo)))))\n\n(test utf-8-characters-in-git-branch\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (run-in-dir repo \"git checkout -b léquipe\")\n    (let ((branch (current-branch repo)))\n      (is (equal \"léquipe\" (current-branch repo))))))\n\n(test rev-parse-happy-path\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (run-in-dir repo \"git checkout -b origin/car\")\n    (is-false (rev-parse repo \"bleh\"))\n    (is-true\n     (rev-parse repo \"car\"))))\n\n(def-easy-macro with-clone (&binding clone repo &key (args \"\") &fn fn)\n  (with-tmpdir (clone-root)\n    (run-in-dir repo \"git checkout -b master\")\n    (make-commit repo \"foobar\")\n    (uiop:run-program (format nil \"cd ~a && git clone ~a ~a cloned-repo\"\n                              clone-root\n                              args\n                              (namestring (repo-dir repo)))\n                      :error-output *standard-output*)\n    (let ((clone (make-instance 'git-repo :dir (path:catdir clone-root \"cloned-repo/\"))))\n      (fn clone))))\n\n(test fetch-remote-branch\n  (with-fixture git-repo ()\n    (with-clone (clone repo)\n      (make-commit repo \"new-commit\")\n      (let ((rev (str:trim (run-in-dir repo \"git rev-parse HEAD\"\n                                       :output 'string))))\n        (is (not (str:emptyp rev)))\n        (fetch-remote-branch clone \"master\")\n        (is (equal rev\n                   (rev-parse clone \"master\")))))))\n\n(test read-dag-from-repo\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (let ((dag\n            (read-graph repo)))\n      (assert-that (dag::all-commits dag)\n                   (has-length 1)))))\n\n(test git-message\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (is (equal \"Updating-file.txt\" (git-message repo)))))\n\n(test get-git-root\n  (with-tmpdir (dir)\n    (ensure-directories-exist (path:catdir dir \"foo/bar/car/\"))\n    (ensure-directories-exist (path:catdir dir \"foo/.git/\"))\n    (ensure-directories-exist (path:catdir dir \"car/dar/\"))\n    (is (equalp\n         (path:catdir dir \"foo/\")\n         (git-root :directory (path:catdir dir \"foo/bar/car/\") :errorp t)))\n    (is (equalp\n         (path:catdir dir \"foo/\")\n         (git-root :directory (path:catdir dir \"foo/\") :errorp t)))\n    (is (equalp\n         nil\n         (git-root :directory (path:catdir dir \"car/dar/\") :errorp nil)))\n    (signals-error-matching\n        ()\n        (git-root :directory (path:catdir dir \"car/dar/\") :errorp t)\n        (error-with-string-matching \"Could not find git root\"))))\n\n(test get-git-author\n  (with-fixture git-repo ()\n    (make-commit repo \"foobar\")\n    (make-commit repo \"bleh\")\n    (is (equal \"foo@example.com\" (author repo)))))\n\n(test get-remote-url\n  (with-fixture git-repo ()\n    (is (equal nil (get-remote-url repo)))\n    (with-clone (clone repo)\n      (is (equal (namestring dir)\n                 (namestring (get-remote-url clone)))))))\n\n(test shallow-clone-commits\n  \"Using --pretty='%H %P' would break this\"\n  (tmpdir:with-tmpdir (dir)\n    (uiop:run-program\n     (format nil\n             \"cd ~a && tar xzf ~a\"\n             (namestring dir)\n             (asdf:system-relative-pathname :screenshotbot.sdk \"fixture/shallow-fast-example.tar.gz\")))\n    (let ((dag\n            (read-graph\n             (make-instance 'git-repo\n                            :dir (path:catdir dir \"fake-fast-example/\")))))\n      (let ((commit (dag:get-commit dag \"64d2dae97dd117315bdd17c381047172ad3511a8\")))\n        (is-true commit)\n        (assert-that (dag:parents commit)\n                     (contains \"912c8d09ba092052d69481777304d039089111ea\"))))))\n\n(test parses-multiple-parents\n  (let ((input (uiop:read-file-string (asdf:system-relative-pathname\n                                       :screenshotbot.sdk\n                                       \"fixture/braft.log\")))\n        (expected-output (uiop:read-file-string (asdf:system-relative-pathname\n                                                 :screenshotbot.sdk\n                                                 \"fixture/braft-expected.log\"))))\n      (is\n       (equal expected-output\n              (parse-raw-git-log input)))))\n\n\n(test debug-git-config\n  (with-fixture git-repo ()\n    (assert-that\n     (debug-git-config repo)\n     (contains-string \"[core]\"))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-installer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;;;; This tests the installer.sh file. :/ So technically not a Lisp test.\n\n(defpackage :screenshotbot/sdk/test-installer\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:fiveam-matchers/core\n                #:does-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string))\n(in-package :screenshotbot/sdk/test-installer)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (tmpdir:with-tmpdir (dir)\n    (tmpdir:with-tmpdir (installer-dir)\n      (uiop:copy-file (asdf:system-relative-pathname :screenshotbot.sdk\n                                                     \"installer.sh\")\n                      (path:catfile installer-dir \"installer.sh\"))\n      (flet ((write-file (file content)\n               (with-open-file (stream (path:catfile installer-dir file) :direction :output)\n                 (write-string content stream))))\n        (write-file \"recorder\" \"\")\n        (write-file \"sdk\" \"\")\n        (when (uiop:os-macosx-p)\n         (write-file \"sdk.lwheap\" \"\")))\n      (&body))))\n\n(test simple-installer-test\n  (with-fixture state ()\n    (multiple-value-bind (output error-output)\n        (uiop:run-program\n            (list \"bash\" \"-c\"\n                  (format nil\n                          \"cd ~a && SCREENSHOTBOT_DIR='~a' sh ./installer.sh\"\n                          (pathname installer-dir)\n                          (pathname dir)))\n            :output 'string\n            :error-output 'string)\n      (assert-that output\n                   (does-not (contains-string \"No such file\")))\n      (assert-that error-output\n                   (does-not (contains-string \"No such file\")))\n      (is (path:-e (path:catfile dir \"recorder\")))\n      (when (uiop:os-macosx-p)\n        (is (path:-e (path:catfile dir \"recorder.lwheap\")))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-main.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-main\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:with-mocks)\n  (:import-from #:screenshotbot/sdk/main\n                #:warn-when-obsolete-flags\n                #:*hostname*\n                #:make-api-context\n                #:%main)\n  (:import-from #:screenshotbot/sdk/env\n                #:env-reader\n                #:api-hostname\n                #:make-env-reader)\n  (:import-from #+lispworks #:screenshotbot/sdk/common-flags\n                #-lispworks #:screenshotbot/sdk/main\n                #:*api-secret*\n                #:*api-key*)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:static #:screenshotbot/sdk/static)\n                    (#:api-context #:screenshotbot/sdk/api-context)\n                    (#:firebase #:screenshotbot/sdk/firebase)))\n(in-package :screenshotbot/sdk/test-main)\n\n\n(util/fiveam:def-suite)\n\n(define-condition quit-condition (error)\n  ((code :initarg :code)))\n\n(define-condition success-condition (error)\n  ())\n\n(def-fixture state ()\n  (with-mocks ()\n    (cl-mock:if-called 'log:config\n                       (lambda (&rest args)\n                         (declare (ignore args))))\n    (cl-mock:if-called 'uiop:quit\n                       (lambda (code)\n                         (case code\n                           (0\n                            (error 'success-condition))\n                           (otherwise\n                            (error 'quit-condition :code code)))))\n    (&body)))\n\n\n(test simple-parsing\n  (with-fixture state ()\n    (finishes\n      (%main (list \"./recorder\" \"--help\")))))\n\n(test unrecognized-command\n  (with-fixture state ()\n    (signals quit-condition\n      (%main (list \"./recorder\" \"--helpy\")))))\n\n\n(test simple-make-api-context\n  (with-fixture state ()\n    (let ((*hostname* \"https://bleh.com\")\n          (*api-key* \"foo\")\n          (*api-secret* \"bleh\"))\n      (finishes\n        (make-api-context)))))\n\n(test make-api-context-without-hostname\n  (with-fixture state ()\n    (let ((*api-key* \"foo\")\n          (*api-secret* \"bleh\"))\n      (let ((ctx (make-api-context\n                  :env (make-instance 'env-reader\n                                      :overrides `((:screenshotbot_api_hostname . nil))))))\n        (is (equal \"https://api.screenshotbot.io\"\n                   (api-context:hostname ctx)))))))\n\n(test simple-make-api-context-domain\n  (with-fixture state ()\n    (let ((*hostname* \"bleh.com\")\n          (*api-key* \"foo\")\n          (*api-secret* \"bleh\"))\n      (let ((ctx\n              (make-api-context)))\n        (is (equal \"https://bleh.com\"\n                   (api-context:hostname ctx)))))))\n\n(test reads-hostname-from-env\n  (with-fixture state ()\n    (cl-mock:answer (make-env-reader) :env)\n    (cl-mock:answer (api-hostname :env)\n      \"zoidberg.com\")\n    (let ((*api-key* \"foo\")\n          (*api-secret* \"bleh\"))\n      (let ((ctx\n              (make-api-context)))\n        (is (equal \"https://zoidberg.com\"\n                   (api-context:hostname ctx)))))))\n\n(test reads-hostname-from-env-2\n  (with-fixture state ()\n    (cl-mock:answer (make-env-reader) :env)\n    (cl-mock:answer (api-hostname :env)\n      \"https://staging.screenshotbot.io\")\n    (let ((*hostname* \"\")\n          (*api-key* \"foo\")\n          (*api-secret* \"bleh\"))\n      (let ((ctx\n              (make-api-context)))\n        (is (equal \"https://staging.screenshotbot.io\"\n                   (api-context:hostname ctx)))))))\n\n\n\n(test warn-when-obsolete-flags\n  (with-fixture state ()\n    (let ((saw nil))\n      (handler-bind ((warning (lambda (w)\n                                (setf saw w))))\n        (warn-when-obsolete-flags))\n      (is-false saw))\n    (let ((flags:*ios-multi-dir* t))\n      (signals simple-warning\n       (warn-when-obsolete-flags)))))\n\n\n(test make-api-key-context\n  (with-fixture state ()\n    (cl-mock:if-called 'uiop:getenv\n                       (lambda (name) nil))\n    (let ((*api-key* nil)\n          (*api-secret* nil))\n      (handler-case\n          (progn\n            (make-api-context)\n            (fail \"expected error\"))\n        (simple-error (e)\n          (assert-that (format nil \"~a\" e)\n                       (contains-string \"SCREENSHOTBOT_API_KEY\")))))\n\n    (let ((*api-key* \"foo\")\n          (*api-secret* nil))\n      (handler-case\n          (progn\n            (make-api-context)\n            (fail \"expected error\"))\n        (simple-error (e)\n          (assert-that (format nil \"~a\" e)\n                       (contains-string \"SCREENSHOTBOT_API_KEY\")))))\n\n    (let ((*api-key* \"foo\")\n          (*api-secret* \"bar\"))\n      (finishes\n        (make-api-context)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-request.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-request\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:fiveam-matchers/errors\n                #:error-with-string-matching\n                #:signals-error-matching)\n  (:import-from #:screenshotbot/sdk/request\n                #:api-error\n                #:ensure-api-success\n                #:%request)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:*synchronous-promotion*)\n  (:import-from #:screenshotbot/api/core\n                #:*wrap-internal-errors*)\n  (:import-from #:util/request\n                #:http-request)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:api-key #:core/api/model/api-key)\n                    (#:dto #:screenshotbot/api/model)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:run-context #:screenshotbot/sdk/run-context)))\n(in-package :screenshotbot/sdk/test-request)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*synchronous-promotion* t)\n        (*wrap-internal-errors* nil))\n    (cl-mock:with-mocks ()\n      (if-called 'warmup-image-caches\n                 (lambda (run)\n                   (declare (ignore run))))\n      (let ((auto-restart:*global-enable-auto-retries-p* nil))\n        (util:copying (flags:*pull-request*\n                       flags:*override-commit-hash*\n                       flags:*main-branch*)\n          (&body))))))\n\n\n(test retries-%request\n  (with-fixture state ()\n    (let ((auto-restart:*global-enable-auto-retries-p* t))\n     (let ((count 0))\n       (cl-mock:if-called 'http-request\n                          (lambda (url &rest args)\n                            (incf count)\n                            (cond\n                              ((<= count 3)\n                               (values (make-string-input-stream \"Bad\") 502))\n                              (t\n                               (values (make-string-input-stream \"Good\") 200)))))\n       (is\n        (equal \"Good\"\n               (%request (make-instance 'api-context\n                                        :remote-version *api-version*\n                                        :key \"foo\"\n                                        :secret \"bar\"\n                                        :hostname \"https://example.com\")\n                         \"/api/test\"\n                         :backoff 0)))))))\n\n(test retries-%request-for-error\n  (with-fixture state ()\n    (let ((auto-restart:*global-enable-auto-retries-p* t))\n     (let ((count 0))\n       (cl-mock:if-called 'http-request\n                          (lambda (url &rest args)\n                            (incf count)\n                            (cond\n                              ((<= count 3)\n                               (error \"some error happened\"))\n                              (t\n                               (values (make-string-input-stream \"Good\") 200)))))\n       (is\n        (equal \"Good\"\n               (%request (make-instance 'api-context\n                                        :remote-version *api-version*\n                                        :key \"foo\"\n                                        :secret \"bar\"\n                                        :hostname \"https://example.com\")\n                         \"/api/test\"\n                         :backoff 0)))))))\n\n(test retries-%request-will-finally-fail\n  (with-fixture state ()\n    (let ((auto-restart:*global-enable-auto-retries-p* t))\n     (let ((count 0))\n       (cl-mock:if-called 'http-request\n                          (lambda (url &rest args)\n                            (incf count)\n                            (cond\n                              ((<= count 10)\n                               (error \"some error happened\"))\n                              (t\n                               (values (make-string-input-stream \"Good\") 200)))))\n       (signals-error-matching ()\n            (%request (make-instance 'api-context\n                                     :remote-version *api-version*\n                                     :key \"foo\"\n                                     :secret \"bar\"\n                                     :hostname \"https://example.com\")\n                      \"/api/test\"\n                      :backoff 0)\n            (error-with-string-matching \"some error happened\"))))))\n\n(test ensure-api-success\n  (finishes\n    (ensure-api-success `((:result . \"foobar\"))))\n  (signals api-error\n   (ensure-api-success `((:error . \"foobar\")))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-run-context.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-run-context\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:git-repo\n                #:fix-commit-hash\n                #:run-context-dto\n                #:run-context-to-dto\n                #:work-branch-is-release-branch-p\n                #:run-context-metadata\n                #:parse-shard-spec\n                #:with-flags-from-run-context\n                #:flags-run-context\n                #:run-context\n                #:env-reader-run-context\n                #:invalid-pull-request\n                #:validate-pull-request)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:screenshotbot/sdk/env\n                #:env-reader\n                #:make-env-reader)\n  (:import-from #:cl-mock\n                #:answer\n                #:if-called)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex\n                #:contains-string)\n  (:local-nicknames (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:git #:screenshotbot/sdk/git)\n                    (#:dto #:screenshotbot/api/model)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:test-git #:screenshotbot/sdk/test-git)))\n(in-package :screenshotbot/sdk/test-run-context)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n   (&body)))\n\n(defvar *env-overrides* nil)\n\n(defun fake-getenv (name)\n  (assoc-value *env-overrides* name :test #'equal))\n\n(defmacro with-env (bindings &body body)\n  `(util:copying (*env-overrides*)\n     ,@ (loop for (name var) in bindings\n              collect\n              `(push (cons (str:replace-all \"-\" \"_\" (string ,name)) ,var)\n                     *env-overrides*))\n     (cl-mock:if-called 'uiop:getenv #'fake-getenv)\n     ,@body))\n\n(test validate-pull-request\n  (with-fixture state ()\n    (flet ((%run (url)\n             (validate-pull-request url)))\n      (handler-case\n          (%run nil)\n        (invalid-pull-request ()\n          (fail \"Saw warning for nil\")))\n      (signals invalid-pull-request\n        (%run \"git@github.com:trhq/fast-example.git\"))\n      (is (equal nil (%run \"git@github.com:tdrhq/fast-example.git\")))\n      (is (equal \"https://github.com/tdrhq/fast-example/pulls/1\" (%run \"https://github.com/tdrhq/fast-example/pulls/1\"))))))\n\n(defclass test-run-context (env-reader-run-context\n                            run-context)\n  ()\n  (:default-initargs :env (make-env-reader)))\n\n(test parse-override-commit-hash-in-run-context\n  (with-fixture state ()\n    (with-env ((:circle-pull-request \"http://foo\"))\n      (is (equal \"http://foo\"\n                 (run-context:pull-request-url\n                  (make-instance 'test-run-context\n                                 :env (make-env-reader)))))))\n  (with-fixture state ()\n    (with-env ((:circle-sha1 \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:circle-pull-request \"http://foo\"))\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\"\n                 (run-context:override-commit-hash\n                  (make-instance 'test-run-context\n                                 :env (make-env-reader)))))))\n  (with-fixture state ()\n    (with-env ((:circle-sha1 \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:circle-pull-request \"\"))\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\"\n                 (run-context:override-commit-hash\n                  (make-instance 'test-run-context\n                                 :env (make-env-reader))))))))\n\n\n(test parse-override-commit-hash-with-bitrise\n  (with-fixture state ()\n    (with-env ((:bitrise-git-commit \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:bitriseio-pull-request-repository-url \"https://bitbucket.org/tdrhq/fast-example\"))\n      (is (equal nil (run-context:pull-request-url\n                      (make-instance 'test-run-context))))\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\" (run-context:override-commit-hash\n                         (make-instance 'test-run-context))))))\n\n  (with-fixture state ()\n    (with-env ((:bitrise-git-commit \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:bitrise-pull-request \"1\")\n               (:bitriseio-pull-request-repository-url \"https://bitbucket.org/tdrhq/fast-example\"))\n\n      (is (equal \"https://bitbucket.org/tdrhq/fast-example/pull-requests/1\"\n                 (run-context:pull-request-url\n                  (make-instance 'test-run-context))))\n\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\"\n                 (run-context:override-commit-hash\n                  (make-instance 'test-run-context)))))))\n\n(defclass my-run-context (run-context\n                          env-reader-run-context)\n  ())\n\n(test env-reader-happy-paths\n  (with-fixture state ()\n    (if-called 'git:merge-base\n               (lambda (repo main commit)\n                 \"foo\"))\n    (let ((self (make-instance 'my-run-context\n                               :env (make-instance 'env-reader))))\n      (finishes (run-context:build-url self))\n      (finishes (run-context:repo-url self))\n      (finishes (run-context:work-branch self))\n      (finishes (run-context:pull-request-url self))\n      (finishes (run-context:channel self))\n      (finishes (run-context:main-branch self))\n      (finishes (run-context:override-commit-hash self))\n      (finishes (run-context:commit-hash self))\n      (finishes (run-context:merge-base self))\n      (finishes (run-context:repo-clean-p self))\n      (finishes (run-context:main-branch-hash self)))))\n\n(test main-branch-hash-for-flags\n  (with-fixture state ()\n    (let ((flags:*main-branch-commit-hash* \"foo\"))\n      (is (equal \"foo\"\n                 (run-context:main-branch-hash (make-instance 'flags-run-context)))))))\n\n(test with-flags\n  (with-fixture state ()\n    (let ((rc (make-instance 'run-context\n                             :channel \"foobar\")))\n      (with-flags-from-run-context (rc)\n        ;; just test one thing for now\n        (is (equal \"foobar\" flags:*channel*))))))\n\n(test copies-work-branch\n  (with-fixture state ()\n    (let ((flags:*work-branch* \"foo\"))\n      (is (equal \"foo\"\n                 (run-context:work-branch (make-instance 'flags-run-context)))))))\n\n(test parse-shard-spec\n  (with-fixture state ()\n    (let ((spec (parse-shard-spec \"foo:1:3\")))\n      (is (equal \"foo\" (dto:shard-spec-key spec)))\n      (is (equal 1 (dto:shard-spec-number spec)))\n      (is (equal 3 (dto:shard-spec-count spec))))))\n\n(test metadata-happy-path\n  (with-fixture state ()\n    (let* ((run-context (make-instance 'test-run-context))\n           (metadata (run-context-metadata run-context)))\n      #-windows\n      (assert-that\n       metadata\n       (has-length 2))\n      #+linux\n      (assert-that\n       (dto:metadata-value (second metadata))\n       (contains-string \"Linux\"))\n      #+darwin\n      (assert-that\n       (dto:metadata-value (second metadata))\n       (contains-string \"Darwin\")))))\n\n(test uname-should-not-crash\n  (with-fixture state ()\n    (let ((run-context (make-instance 'test-run-context)))\n      (if-called 'uiop:run-program\n                 (lambda (&rest args)\n                   (error \"uname crashed!\")))\n      (assert-that\n       (run-context-metadata run-context)\n       (has-length 2)))))\n\n(test release-branch-regex-should-fix-main-branch--old-test!\n  ;; There was a time before T1667, when we were converting the\n  ;; main-branch to release-branch on the CLI side, it should be done\n  ;; on the server side now.\n  (with-fixture state ()\n    (let ((run-context (make-instance 'test-run-context\n                                      :main-branch \"foo\")))\n      (is (equal \"foo\" (run-context:main-branch run-context))))\n    (let ((run-context (make-instance 'test-run-context\n                                      :main-branch \"foo\"\n                                      :work-branch \"bar\")))\n      (is (equal \"foo\" (run-context:main-branch run-context))))\n    (let ((run-context (make-instance 'test-run-context\n                                      :main-branch \"foo\"\n                                      :work-branch \"bar\"\n                                      :release-branch-regex \"b.*\")))\n      (is-true (run-context:work-branch-is-release-branch-p run-context))\n      (is (equal \"foo\" (run-context:main-branch run-context))))\n\n    (let ((run-context (make-instance 'test-run-context\n                                      :main-branch \"foo\"\n                                      :work-branch \"bar\"\n                                      :release-branch-regex \"c.*\")))\n      (is-false (run-context:work-branch-is-release-branch-p run-context))      \n      (is (equal \"foo\" (run-context:main-branch run-context))))))\n\n(test work-branch-is-release-branch-p\n  (let ((run-context (make-instance 'test-run-context\n                                    :work-branch \"foo\"\n                                    :release-branch-regex \"b.*\")))\n    (is-false (work-branch-is-release-branch-p run-context)))\n  (let ((run-context (make-instance 'test-run-context\n                                    :work-branch \"foo\"\n                                    :release-branch-regex \"f.*\")))\n    (is-true (work-branch-is-release-branch-p run-context)))\n  (let ((run-context (make-instance 'test-run-context\n                                    :work-branch \"foo\"\n                                    :release-branch-regex \"\")))\n    (is-false (work-branch-is-release-branch-p run-context))))\n\n(test work-branch-is-release-branch-p--matches-full-branch\n  (let ((run-context (make-instance 'test-run-context\n                                    :work-branch \"foo\"\n                                    :release-branch-regex \"o\")))\n    (is-false (work-branch-is-release-branch-p run-context))))\n\n(test invalid-regex\n  (let ((run-context (make-instance 'test-run-context\n                                    :work-branch \"foo\"\n                                    :release-branch-regex \"foo(\")))\n    (signals-error-matching ()\n       (work-branch-is-release-branch-p run-context)\n       (error-with-string-matching (matches-regex \"Could not parse regex\")))))\n\n(test regex-with-parenthesis\n  \"Keep this test in sync with the documentation for *release-branch-regex*, this is just\nmaking sure that the doc is giving a good example.\"\n  (let ((regex \"(release|long-feature)/.*\"))\n    (let ((run-context (make-instance 'test-run-context\n                                      :work-branch \"release/2025-02\"\n                                      :release-branch-regex regex)))\n      (is-true (work-branch-is-release-branch-p run-context)))\n    (let ((run-context (make-instance 'test-run-context\n                                      :work-branch \"long-feature/big-thing\"\n                                      :release-branch-regex regex)))\n      (is-true (work-branch-is-release-branch-p run-context)))\n    (let ((run-context (make-instance 'test-run-context\n                                      :work-branch \"main\"\n                                      :release-branch-regex regex)))\n      (is-false (work-branch-is-release-branch-p run-context)))))\n\n\n(test simple-dto-conversion\n  (let ((dto\n          (run-context-to-dto\n           (make-instance 'test-run-context\n                          :work-branch \"foo\"\n                          :env (make-env-reader)))))\n    (is (typep dto 'run-context-dto))\n    (is (equal \"foo\" (run-context:work-branch dto)))))\n\n(test merge-base-handles-subprocess-error\n  (with-fixture state ()\n    (let ((run-context (make-instance 'my-run-context\n                                      :merge-base nil\n                                      :main-branch-hash \"abcd\"\n                                      :env (make-env-reader)))\n          (repo (make-instance 'git:null-repo))\n          (calledp nil))\n      (answer (run-context:git-repo run-context)\n        repo)\n      (if-called 'git:merge-base\n                 (lambda (git one two)\n                   (setf calledp t)\n                   (error 'uiop:subprocess-error)))\n      (finishes\n        (run-context:merge-base run-context))\n      (is-true calledp))))\n\n(test fix-commit-hash\n  (with-fixture state ()\n    (let ((run-context (make-instance 'my-run-context))\n          (repo :fake-repo))\n      (answer (run-context:git-repo run-context) repo)\n      (is (equal nil (fix-commit-hash run-context \"\")))\n      (is (equal \"ce04ccee65b8af8ac13a07c21f22b887dedd88f9\" (fix-commit-hash run-context \"ce04ccee65b8af8ac13a07c21f22b887dedd88f9\")))\n      (if-called 'git:rev-parse-local\n                 (lambda (repo commit)\n                   (is (eql :fake-repo repo))\n                   (is (equal \"ce044\" commit))\n                   \"ce04ccee65b8af8ac13a07c21f22b887dedd88f9\"))\n      (is (equal \"ce04ccee65b8af8ac13a07c21f22b887dedd88f9\"\n                 (fix-commit-hash run-context \"ce044\"))))))\n\n(test fix-commit-hash-actually-fixes-commit-hashes\n  (with-fixture state ()\n    (test-git:with-git-repo (repo)\n      (test-git:make-commit repo \"foo\")\n      (let ((run-context (make-instance 'my-run-context)))\n        (answer (run-context:git-repo run-context) repo)\n        ;; a commit of length 40 doesn't change at all\n        (let ((head (git:rev-parse-local repo \"HEAD\")))\n          (assert-that head (has-length 40))\n          (assert-that\n           (fix-commit-hash run-context (str:substring 0 10 head))\n           (is-equal-to head)))))))\n\n(test pixel-tolerance-flag-propagated-to-flags-run-context\n  \"Test that the --pixel-tolerance flag is properly accessible in flags-run-context\"\n  (with-fixture state ()\n    (let ((flags:*pixel-tolerance* 3))\n      (let ((run-context (make-instance 'flags-run-context)))\n        (assert-that (run-context:pixel-tolerance run-context)\n                     (is-equal-to 3))))))\n\n(defclass run-context-with-fixed-git-repo (env-reader-run-context\n                                           run-context)\n  ((git-repo :initarg :git-repo\n             :reader git-repo)\n   (build-url :initarg :build-url\n              :initform \"\"\n              :reader run-context:build-url)))\n\n(test cleanp-doesnt-calls-git-status-when-build-url-is-not-present\n  (cl-mock:with-mocks ()\n    (with-fixture state ()\n      (test-git:with-git-repo (repo)\n        (test-git:make-commit repo \"foo\")\n\n        (is-true (git:cleanp repo))       \n        (let ((run-context (make-instance 'run-context-with-fixed-git-repo\n                                          :git-repo repo)))\n          (is-true (run-context:repo-clean-p run-context)))))))\n\n(defun dirty-the-repo (repo)\n  (with-open-file (stream (path:catfile (git:repo-dir repo) \"file.txt\") :direction :output\n                                                                        :if-exists :supersede)\n    (format stream \"dsfsdfdsfdsfdf\")))\n\n(test cleanp-doesnt-call-git-status-if-not-needed\n  (cl-mock:with-mocks ()\n    (with-fixture state ()\n      (test-git:with-git-repo (repo)\n        (test-git:make-commit repo \"foo\")\n\n        (dirty-the-repo repo)\n        (is-false (git:cleanp repo))\n\n        (let ((run-context (make-instance 'run-context-with-fixed-git-repo\n                                          :build-url \"https://example.com\"\n                                          :git-repo repo)))\n          (is-true (run-context:repo-clean-p run-context)))))))\n\n(test cleanp-is-called-if-its-a-blank-string-too\n  (cl-mock:with-mocks ()\n    (with-fixture state ()\n      (test-git:with-git-repo (repo)\n        (test-git:make-commit repo \"foo\")\n\n        (dirty-the-repo repo)\n        (is-false (git:cleanp repo))\n\n        (let ((run-context (make-instance 'run-context-with-fixed-git-repo\n                                          :build-url \"\"\n                                          :git-repo repo)))\n          (is-false (run-context:repo-clean-p run-context)))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-sdk.lisp",
    "content": ";; -*- coding: utf-8 -*-\n;; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-sdk\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:cl-mock\n                #:answer\n                #:if-called)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:is-equal-to)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/strings\n                #:is-not-empty)\n  (:import-from #:hunchentoot\n                #:define-easy-handler\n                #:easy-acceptor)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*\n                #:decode-json)\n  (:import-from #:screenshotbot/sdk/android\n                #:directory-image-bundle\n                #:image-bundles)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:import-from #:screenshotbot/sdk/backoff\n                #:server-unavailable\n                #:backoff)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:image-directory)\n  (:import-from #:screenshotbot/sdk/flags\n                #:*directory*\n                #:*metadata*)\n  (:import-from #:screenshotbot/sdk/git\n                #:cleanp\n                #:current-branch\n                #:current-commit\n                #:merge-base\n                #:null-repo\n                #:repo-link\n                #:rev-parse)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:format-api-url)\n  (:import-from #:screenshotbot/sdk/integration-fixture\n                #:with-sdk-integration)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:run-context-to-dto\n                #:put-run-with-run-context\n                #:empty-run-error\n                #:find-existing-images\n                #:get-relative-path\n                #:keyword-except-md5\n                #:make-bundle\n                #:make-directory-run\n                #:make-run\n                #:not-recent-file-warning\n                #:parse-environment\n                #:put-file\n                #:request\n                #:upload-image-directory\n                #:validate-pull-request\n                #:warn-if-not-recent-file)\n  (:import-from #:util/json-mop\n                #:json-mop-to-string)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:util/testing\n                #:with-local-acceptor)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:warmup-image-caches\n                #:*synchronous-promotion*)\n  (:import-from #:screenshotbot/api/core\n                #:*wrap-internal-errors*)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-branch\n                #:recorder-run-merge-base\n                #:recorder-run)\n  (:import-from #:screenshotbot/user-api\n                #:recorder-run-commit)\n  (:import-from #:screenshotbot/sdk/request\n                #:%request)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:invalid-pull-request)\n  (:import-from #:screenshotbot/sdk/env\n                #:make-env-reader)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:api-key #:core/api/model/api-key)\n                    (#:dto #:screenshotbot/api/model)\n                    (#:flags #:screenshotbot/sdk/flags)\n                    (#:run-context #:screenshotbot/sdk/run-context)))\n(in-package :screenshotbot/sdk/test-sdk)\n\n(util/fiveam:def-suite)\n\n\n(def-fixture state ()\n  (let ((*synchronous-promotion* t)\n        (*wrap-internal-errors* nil))\n    (cl-mock:with-mocks ()\n      (if-called 'warmup-image-caches\n                 (lambda (run)\n                   (declare (ignore run))))\n      (let ((auto-restart:*global-enable-auto-retries-p* nil))\n        (util:copying (flags:*pull-request*\n                       flags:*override-commit-hash*\n                       flags:*main-branch*)\n          (&body))))))\n\n(test read-directory-for-ios\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (s)\n     (let ((*directory* (namestring s)))\n       (is (typep (make-bundle)\n                  'image-directory))))))\n\n(test read-directory-for-android\n  (with-fixture state ()\n   (tmpdir:with-tmpdir (s)\n     (let ((*directory* (namestring s)))\n       (uiop:with-temporary-file (:pathname metadata :type \"json\")\n         (let ((*metadata* (namestring metadata)))\n           (is (typep (car (image-bundles (make-bundle)))\n                      'directory-image-bundle))))))))\n\n(test get-relative-path\n  (is (equal #P \"foo/\"\n             (get-relative-path #P \"/bar/car/foo/\" \"/bar/car/\")))\n  (is (equal #P \"foo/dar/\"\n             (get-relative-path #P \"/bar/car/foo/dar/\" \"/bar/car/\"))))\n\n\n(hunchentoot:define-easy-handler (get-md5-sum :uri \"/put\" :acceptor-names '(test-acceptor)) ()\n  (log:info \"Running fake PUT\")\n  (ironclad:byte-array-to-hex-string\n   (ironclad:digest-sequence\n    :md5\n    (hunchentoot:raw-post-data :force-binary t\n                               :want-stream nil))))\n\n\n#-darwin\n(test simple-put-image\n  (with-fixture state ()\n   (with-local-acceptor (host) ('hunchentoot:easy-acceptor\n                                 :name 'test-acceptor)\n     (with-open-file (s #.(asdf:system-relative-pathname\n                           :screenshotbot.sdk\n                           \"file-for-test.bin\")\n                        :direction :input\n                        :element-type 'flexi-streams:octet)\n       (is\n        (equal\n         \"4249fe0e72f21fd54dbb2f3325bec263\"\n         (put-file (make-instance 'api-context\n                                  :key \"\"\n                                  :secret \"\"\n                                  :hostname host)\n                   (format nil \"~a/put\" host) s)))))))\n\n(defvar *env-overrides* nil)\n\n(defun fake-getenv (name)\n  (a:assoc-value *env-overrides* name :test #'equal))\n\n(defmacro with-env (bindings &body body)\n  `(util:copying (*env-overrides*)\n     ,@ (loop for (name var) in bindings\n              collect\n              `(push (cons (str:replace-all \"-\" \"_\" (string ,name)) ,var)\n                     *env-overrides*))\n     (cl-mock:if-called 'uiop:getenv #'fake-getenv)\n     ,@body))\n\n(test parse-override-commit-hash\n  (with-fixture state ()\n    (with-env ((:circle-pull-request \"http://foo\"))\n      (parse-environment)\n      (is (equal \"http://foo\" (run-context:pull-request-url\n                               (make-instance 'run-context:flags-run-context\n                                              :env (make-env-reader)))))))\n  (is (eql 40 (length \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")))\n  (with-fixture state ()\n    (with-env ((:circle-sha1 \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:circle-pull-request \"http://foo\"))\n      (parse-environment)\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\" flags:*override-commit-hash*))))\n  (with-fixture state ()\n    (with-env ((:circle-sha1 \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:circle-pull-request \"\"))\n      (parse-environment)\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\" flags:*override-commit-hash*)))))\n\n(test parse-override-commit-hash-with-bitrise\n    (with-fixture state ()\n    (with-env ((:bitrise-git-commit \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:bitriseio-pull-request-repository-url \"https://bitbucket.org/tdrhq/fast-example\"))\n      (parse-environment)\n      (is (equal nil (run-context:pull-request-url\n                  (make-instance 'run-context:flags-run-context\n                                 :env (make-env-reader)))))\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\" flags:*override-commit-hash*))))\n\n  (with-fixture state ()\n    (with-env ((:bitrise-git-commit \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\")\n               (:bitrise-pull-request \"1\")\n               (:bitriseio-pull-request-repository-url \"https://bitbucket.org/tdrhq/fast-example\"))\n      (parse-environment)\n\n      (is (equal \"https://bitbucket.org/tdrhq/fast-example/pull-requests/1\"\n                 (run-context:pull-request-url\n                  (make-instance 'run-context:flags-run-context\n                                 :env (make-env-reader)))))\n\n      (is (equal \"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd\" flags:*override-commit-hash*)))))\n\n(test backoff-happy-path\n  (is (eql 10 (backoff 1)))\n  (is (eql nil (backoff 100))))\n\n(test make-run-happy-path\n  \"Does not test much, sadly\"\n  (with-fixture state ()\n    (let ((%content))\n      (if-called 'request\n                 (lambda (api-context uri &key method content)\n                   (setf %content content)\n                   nil))\n      (let ((repo :dummy-repo))\n        (answer (rev-parse repo \"main\") \"abcd\")\n        (answer (current-commit repo) \"bdfd\")\n        (answer (current-branch repo) \"my-branch\")\n        (if-called 'merge-base\n                   (lambda (git-repo main-branch commit-hash)\n                     \"0001\"))\n        (answer (repo-link repo) \"https://github.com/tdrhq/fast-example\")\n        (answer (cleanp repo) t)\n        (let ((context (make-instance 'api-context\n                                      :remote-version *api-version*)))\n          (finishes\n            (make-run context\n                      (list\n                       (make-instance 'dto:screenshot\n                                      :image-id \"foo\"\n                                      :name \"car\"))\n                      :branch \"main\"\n                      :repo repo))\n          (is-true (typep %content 'dto:run))\n          (is (equal \"0001\" (dto:merge-base %content)))\n          (is (equal \"bdfd\" (dto:run-commit %content)))\n          (is (equal \"https://github.com/tdrhq/fast-example\" (dto:run-repo %content))))))))\n\n\n(test make-run-with-context-uses-information-from-the-context\n  (with-fixture state ()\n    (let ((%content))\n      (if-called 'request\n                 (lambda (api-context uri &key method content)\n                   (setf %content content)\n                   nil))\n      (let ((repo :dummy-repo))\n        (answer (rev-parse repo \"main\") \"abcd\")\n        (answer (current-commit repo) \"bdfd\")\n        (answer (current-branch repo) \"my-branch\")\n        (if-called 'merge-base\n                   (lambda (git-repo main-branch commit-hash)\n                     \"0001\"))\n        (answer (repo-link repo) \"https://github.com/tdrhq/fast-example\")\n        (let ((context (make-instance 'api-context\n                                      :remote-version *api-version*)))\n          (finishes\n            (make-run context\n                      (list\n                       (make-instance 'dto:screenshot\n                                      :image-id \"foo\"\n                                      :name \"car\"))\n                      :branch \"main\"\n                      :repo repo\n                      :run-context (make-instance 'run-context:run-context\n                                                  :main-branch \"main\"\n                                                  :commit-hash \"0002\"\n                                                  :repo-clean-p t\n                                                  :merge-base \"0003\"\n                                                  :repo-url \"https://example.com/foo.git\")))\n          (is-true (typep %content 'dto:run))\n          (is (equal \"0003\" (dto:merge-base %content)))\n          (is (equal \"0002\" (dto:run-commit %content)))\n          (is (equal \"https://example.com/foo.git\" (dto:run-repo %content))))))))\n\n(test make-run-on-empty-directory-crashes-appropriately\n  (with-fixture state  ()\n    (if-called 'request\n               (lambda (&rest args)\n                 (error \"should not be called\")))\n    (let ((api-context (make-instance 'api-context)))\n     (signals empty-run-error\n       (make-run api-context nil :branch \"main\")))))\n\n(test empty-run-error-suggests-recursive-when-not-provided\n  (let ((err (make-condition 'empty-run-error :recursivep nil)))\n    (let ((message (with-output-to-string (s)\n                     (format s \"~A\" err))))\n      (is (str:containsp \"--recursive\" message))\n      (is (str:containsp \"Perhaps you wanted to use the --recursive flag?\" message)))))\n\n(test empty-run-error-does-not-suggest-recursive-when-already-provided\n  (let ((err (make-condition 'empty-run-error :recursivep t)))\n    (let ((message (with-output-to-string (s)\n                     (format s \"~A\" err))))\n      (is (not (str:containsp \"--recursive\" message)))\n      (is (not (str:containsp \"Perhaps you wanted to use the --recursive flag?\" message)))\n      (is (str:containsp \"No screenshots were detected\" message)))))\n\n#+lispworks\n(test |ensure we're not depending on cl+ssl|\n  (let ((seen (make-hash-table :test #'equal)))\n    (labels ((find-bad (component path)\n               (when (string-equal \"cl+ssl\" (asdf:component-name component))\n                 (fail \"Found cl+ssl in deps ~S\" path))\n               (unless (gethash component seen)\n                 (setf (gethash component seen) t)\n                 (dolist (dep (asdf:system-depends-on component))\n                   (unless (consp dep)\n                     (find-bad (asdf:find-system dep)\n                               (list* dep path)))))))\n      (find-bad (asdf:find-system :screenshotbot.sdk) nil))))\n\n(test encode-decode-utf-8-branchname\n  (let ((run (make-instance 'dto:run :work-branch \"léquipe\")))\n    (let ((json (decode-json (json-mop-to-string run) 'dto:run)))\n      (is (equal \"léquipe\" (dto:work-branch run))))))\n\n(defvar *result* nil)\n\n(define-easy-handler (test-handler :uri (lambda (request) request) :acceptor-names '(test-handler)) ()\n  (setf *result* (json:decode-json-from-string (hunchentoot:raw-post-data :force-text t)))\n  \"{}\")\n\n(test make-request-with-unicode\n  (with-global-binding ((*result* nil)\n                        (auto-restart:*global-enable-auto-retries-p* nil))\n    (with-local-acceptor (host) ('easy-acceptor :name 'test-handler)\n      (let ((api-context (make-instance 'api-context\n                                        :remote-version *api-version*\n                                        :key \"\"\n                                        :secret \"\"\n                                        :hostname host)))\n        (finishes\n          (request api-context\n                   \"/test-handler\"\n                   :method :put\n                   :content (make-instance 'dto:report :id \"foobar\")))\n        (is (equal \"foobar\" (assoc-value *result* :id)))\n        (finishes\n          (request api-context\n                   \"/test-handler\"\n                   :method :put\n                   :content (make-instance 'dto:report :id \"léquipe\")))\n        (is (equal \"léquipe\" (assoc-value *result* :id)))))))\n\n(test format-api-url\n  (is (equal \"https://api.screenshotbot.io/api/version\"\n             (format-api-url\n              (make-instance 'api-context\n                             :hostname \"https://api.screenshotbot.io\")\n              \"/api/version\")))\n  (is (equal \"https://foo.screenshotbot.io/api/version\"\n             (format-api-url\n              (make-instance 'api-context\n                             :hostname \"https://foo.screenshotbot.io/\")\n              \"/api/version\")))\n  (is (equal \"https://foo.screenshotbot.io/api/version\"\n             (format-api-url\n              (make-instance 'api-context\n                             :hostname \"foo.screenshotbot.io\")\n              \"/api/version\"))))\n\n\n(test warn-if-not-recent-file\n  (uiop:with-temporary-file (:stream s)\n    (handler-case\n        (warn-if-not-recent-file s)\n      (warning ()\n        (fail \"expected exception\"))\n      (:no-error (_)\n        (pass)))\n\n    #+linux ;; mostly for the `touch`\n    (uiop:with-temporary-file (:stream s :pathname p)\n      (uiop:run-program\n       (list \"touch\" \"-d\" \"3 days ago\" (namestring p)))\n      (signals not-recent-file-warning\n        (warn-if-not-recent-file s)))))\n\n(test warn-if-not-recent-file-should-handle-nil-write-date\n  (dotimes (i 10)\n   (uiop:with-temporary-file (:pathname p :type \"png\"\n                              :direction :output\n                              :element-type 'flexi-streams:octet)\n     (with-open-file (stream p :direction :output\n                               :if-exists :supersede)\n       (format stream \"hello\"))\n     ;; I can't repro T1632 locally sadly\n     (finishes\n       (warn-if-not-recent-file p)))))\n\n(test format-not-recent-file-warning\n  (finishes\n   (format nil \"~a\"\n           (make-condition 'not-recent-file-warning :file \"/tmp/foo\"))))\n\n(defun write-string-to-file (str file)\n  (with-open-file (s file :direction :output)\n    (write-string str s)\n    (finish-output s)))\n\n\n(test find-existing-images-happy-path\n  (with-fixture state ()\n    (with-sdk-integration (api-context)\n      (let ((result\n             (find-existing-images api-context\n                                   #(\"82142ae81caba45bb76aa21fb6acf16d\"))))\n        (assert-that result\n                     (has-length 1))\n\n        (assert-that (dto:image-md5sum (car result))\n                     (is-equal-to \"82142ae81caba45bb76aa21fb6acf16d\"))\n\n        (assert-that\n         (dto:image-upload-url (car result))\n         (is-not-empty))\n        (assert-that\n         (dto:image-id (car result))\n         (is-not-empty))))))\n\n(test simple-image-loading-happy-path\n  (with-fixture state ()\n    (with-sdk-integration (api-context)\n      (tmpdir:with-tmpdir (dir)\n        (write-string-to-file \"foobar\" (path:catfile dir \"one.png\"))\n        (write-string-to-file \"foobar2\" (path:catfile dir \"two.png\"))\n       (let ((bundle (make-instance 'image-directory\n                                    :directory dir)))\n         (finishes\n          (upload-image-directory api-context\n                                  bundle)))))))\n\n(test the-same-image-isnt-uploaded-twice\n  (with-fixture state ()\n    (with-sdk-integration (api-context)\n      (tmpdir:with-tmpdir (dir)\n        (write-string-to-file \"foobar\" (path:catfile dir \"one.png\"))\n        (write-string-to-file \"foobar\" (path:catfile dir \"two.png\"))\n        (let ((put-file-counter 0))\n         (cl-mock:with-mocks ()\n           (cl-mock:if-called 'put-file\n                              (lambda (api-context upload-url stream)\n                                (incf put-file-counter)))\n           (let ((bundle (make-instance 'image-directory\n                                        :directory dir)))\n             (finishes\n               (upload-image-directory api-context\n                                       bundle)))\n           (is (eql 1 put-file-counter))))))))\n\n(test the-same-image-isnt-uploaded-twice--more-complex\n  (with-fixture state ()\n    (with-sdk-integration (api-context)\n      (tmpdir:with-tmpdir (dir)\n        (write-string-to-file \"foobar\" (path:catfile dir \"one.png\"))\n        (write-string-to-file \"foobar\" (path:catfile dir \"two.png\"))\n        (write-string-to-file \"foobar3\" (path:catfile dir \"three.png\"))        \n        (let ((put-file-counter 0))\n         (cl-mock:with-mocks ()\n           (cl-mock:if-called 'put-file\n                              (lambda (api-context upload-url stream)\n                                (incf put-file-counter)))\n           (let ((bundle (make-instance 'image-directory\n                                        :directory dir)))\n             (finishes\n               (upload-image-directory api-context\n                                       bundle)))\n           (is (eql 2 put-file-counter))))))))\n\n(test simple-make-directory-run-happy-path\n  (with-fixture state ()\n    (with-sdk-integration (api-context)\n     (tmpdir:with-tmpdir (dir)\n       (write-string-to-file \"foobar\" (path:catfile dir \"one.png\"))\n       (write-string-to-file \"foobar2\" (path:catfile dir \"two.png\"))\n       (let ((bundle (make-instance 'image-directory\n                                    :directory dir)))\n         (finishes\n           (make-directory-run api-context bundle\n                               :repo (make-instance 'null-repo)\n                               :channel \"bleh\")))))))\n\n(def-easy-macro with-upload-image-directory (&binding api-context &binding images &fn fn)\n  (with-sdk-integration (api-context)\n    (tmpdir:with-tmpdir (dir)\n      (write-string-to-file \"foobar\" (path:catfile dir \"one.png\"))\n      (write-string-to-file \"foobar\" (path:catfile dir \"two.png\"))\n      (write-string-to-file \"foobar3\" (path:catfile dir \"three.png\"))        \n      (cl-mock:with-mocks ()\n        (let ((bundle (make-instance 'image-directory\n                                     :directory dir)))\n          (let ((images\n                  (upload-image-directory api-context\n                                          bundle)))\n            (fn api-context images)))))))\n\n(test make-run-integration-test-1\n  (with-fixture state ()\n    (with-upload-image-directory (api-context images)\n      (make-run api-context images\n                :repo (make-instance 'null-repo)\n                :channel \"blah\"))))\n\n(defun the-only-run ()\n  (car (class-instances 'recorder-run)))\n\n(test make-run-integration-test-propagates-the-right-branch\n  (with-fixture state ()\n    (with-upload-image-directory (api-context images)\n      (make-run api-context images\n                :repo (make-instance 'null-repo)\n                :branch \"develop\"\n                :channel \"blah\")\n      (is (equal \"develop\" (recorder-run-branch (the-only-run)))))))\n\n(test make-run-integration-with-a-release-branch-regex\n  (with-fixture state ()\n    (with-upload-image-directory (api-context images)\n      (put-run-with-run-context\n       api-context\n       (make-instance 'run-context:run-context\n                      :channel \"blah\"\n                      :main-branch \"master\"\n                      :work-branch \"release-2\"\n                      :release-branch-regex \"release.*\")\n       images)\n      (is (equal \"release-2\" (recorder-run-branch (the-only-run)))))))\n\n(test make-run-integration-test-with-commit\n  \"Possible not required, especially if we remove the :commit parameter\"\n  (with-fixture state ()\n    (with-upload-image-directory (api-context images)\n      (make-run api-context images\n                :repo (make-instance 'null-repo)\n                :channel \"blah\"\n                :commit \"abcd\"\n                :merge-base \"cbad\"\n                :branch-hash \"dabc\")\n      (let ((run (the-only-run)))\n        (is (equal \"abcd\" (recorder-run-commit run)))\n        (is (equal \"cbad\" (recorder-run-merge-base run)))))))\n\n(test keyword-except-md5\n  (is (eql 32 (length \"82142ae81caba45bb76aa21fb6acf16d\")))\n  (is (equal \"82142ae81caba45bb76aa21fb6acf16d\" \n             (keyword-except-md5 \"82142ae81caba45bb76aa21fb6acf16d\")))\n  ;; Notice that if we don't use a 32-byte string, then it gets\n  ;; encoded differently.\n  (is (equal \"82142-AE-81-CABA-45-BB-76-AA-21-FB-6-ACF-16\"\n             (keyword-except-md5 \"82142ae81caba45bb76aa21fb6acf16\"))))\n\n(test adds-pixel-tolerance\n  (is (eql 2 (dto:compare-pixel-tolerance\n              (run-context-to-dto\n               (make-instance 'run-context:run-context\n                              :pixel-tolerance 2)\n               nil #| screenshots |#)))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-sentry.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-sentry\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/sentry\n                #:with-sentry)\n  (:import-from #:util/threading\n                #:*tags*\n                #:*extras*))\n(in-package :screenshotbot/sdk/test-sentry)\n\n(util/fiveam:def-suite)\n\n(define-condition my-error (error)\n  ())\n\n(test happy-path\n  (finishes\n    (with-sentry (:dry-run t\n                      :on-error (lambda ()))\n      (+ 1 1)))\n  (signals my-error\n    (with-sentry (:dry-run t\n                  :stream (make-string-output-stream)\n                  :on-error (lambda ()))\n      (error 'my-error))))\n\n(test all-tags-and-extras-evaluate\n  (finishes\n    (with-sentry (:dry-run t\n                  :on-error (lambda ()))\n      (loop for fn in *extras*\n            do (funcall fn (make-condition 'my-error)))\n      (loop for fn in *tags*\n            do (funcall fn (make-condition 'my-error))))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-server-log-appender.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-server-log-appender\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/server-log-appender\n                #:cli-log-stream))\n(in-package :screenshotbot/sdk/test-server-log-appender)\n\n(util/fiveam:def-suite)\n\n\n(test simple-write-stuff\n  (let ((stream (make-instance 'cli-log-stream :api-context nil)))\n    (format stream \"hello world~%\")\n    (finish-output stream)\n    (pass)))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-static.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-static\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/server\n                #:acceptor)\n  (:import-from #:screenshotbot/sdk/static\n                #:make-html-provider\n                #:static-website/command\n                #:parse-config-from-file\n                #:find-all-index.htmls\n                #:upload-blob)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains\n                #:has-item)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:util/testing\n                #:with-local-acceptor)\n  (:import-from #:screenshotbot/replay/browser-config\n                #:browser-type\n                #:browser-config-name)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags :screenshotbot/sdk/flags)))\n(in-package :screenshotbot/sdk/test-static)\n\n(util/fiveam:def-suite)\n\n(defvar *hex* nil)\n(defvar *len* -1)\n\n(defun cleanup ()\n  (setf *hex* nil)\n  (setf *len* -1))\n\n(def-fixture state ()\n  (cleanup)\n  (let ((auto-restart:*global-enable-auto-retries-p* nil))\n   (with-local-acceptor (host) ('hunchentoot:easy-acceptor\n                                 :name 'test-acceptor)\n     (let ((api-context (make-instance 'api-context\n                                       :key \"\"\n                                       :secret \"\"\n                                       :hostname host)))\n       (unwind-protect\n            (progn\n              (&body))\n         (cleanup))))))\n\n(hunchentoot:define-easy-handler (fake-blob-upload :uri \"/api/blob/upload\"\n                                                   :acceptor-names '(test-acceptor))\n    (hash type api-key api-secret-key)\n  (let ((seq (hunchentoot:raw-post-data :force-binary t\n                                        :want-stream nil)))\n    (setf *len* (length seq))\n    (setf *hex* (ironclad:digest-sequence :md5 seq))))\n\n(test blob-upload\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname p :stream out)\n      (write-string \"zoidberg\" out)\n      (finish-output out)\n      (let ((md5 (md5-file p)))\n        (upload-blob api-context p)\n        (is (equalp *hex* md5))))))\n\n(test binary-blob\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname p :stream out\n                               :element-type '(unsigned-byte 8))\n      (loop for i from 0 to 255 do\n        (write-byte i out))\n      (finish-output out)\n      (is (eql 256 (trivial-file-size:file-size-in-octets p)))\n      (let ((md5 (md5-file p)))\n        (upload-blob api-context p)\n        (is (eql 256 *len*))\n        (is  (equalp *hex* md5))))))\n\n(defun touch (file)\n  (ensure-directories-exist file)\n  (with-open-file (o file :direction :output)\n    (declare (ignore o))))\n\n(test list-all-index.html\n  (tmpdir:with-tmpdir (dir)\n    (touch (path:catfile dir \"index.html\"))\n    (assert-that\n     (find-all-index.htmls dir)\n     (has-item \"/index.html\"))\n    (touch (path:catfile dir \"foo/index.html\"))\n    (assert-that\n     (find-all-index.htmls dir)\n     (has-item \"/index.html\")\n     (has-item \"/foo/index.html\"))\n    (touch (path:catfile dir \"bleh.png\"))\n    (assert-that\n     (find-all-index.htmls dir)\n     (is-not (has-item \"bleh.png\")))))\n\n(test parse-config-from-file\n  (uiop:with-temporary-file (:pathname p :stream s)\n    (write-string \"[{\n \\\"name\\\":\\\"Google Chrome\\\",\n \\\"type\\\":\\\"chrome\\\",\n \\\"dimensions\\\": \\\"24x2\\\"\n}\n]\" s)\n    (finish-output s)\n    (let ((configs (parse-config-from-file p)))\n      (is (eql 1 (length configs)))\n      (is (equal \"Google Chrome\"\n                 (browser-config-name (car configs))))\n      (is (equal \"chrome\"\n                 (browser-type (car configs)))))))\n\n\n(test simple-make-command\n  (finishes\n   (static-website/command)))\n\n(test make-html-provider\n  (is (equal #'find-all-index.htmls (make-html-provider nil)))\n  (uiop:with-temporary-file (:pathname html-list :stream stream :direction :output)\n    (format stream \"hello.html~%~%bar.html~%car.html\")\n    (finish-output stream)\n    (let ((fn (make-html-provider (namestring html-list))))\n      (assert-that\n       (funcall fn \"fake-dir\")\n       (contains\n        \"hello.html\"\n        \"bar.html\"\n        \"car.html\")))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-unchanged-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-unchanged-run\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:answer\n                #:with-mocks)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:request)\n  (:import-from #:util/fake-clingon\n                #:make-fake-clingon)\n  (:import-from #:screenshotbot/sdk/unchanged-run\n                #:handle-cmd\n                #:mark-unchanged-run\n                #:all-args)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:run-context)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:api-context\n                #:remote-version)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:root-options)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:make-api-context)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)\n                    (#:run-context #:screenshotbot/sdk/run-context)))\n(in-package :screenshotbot/sdk/test-unchanged-run)\n\n(util/fiveam:def-suite)\n\n(defclass fake-api-context (api-context)\n  ())\n\n(defvar *fake-api-context*\n  (make-instance 'fake-api-context))\n\n(def-fixture state ()\n  (with-mocks ()\n    (let ((called nil))\n      (cl-mock:if-called 'make-api-context\n                         (lambda (&rest args)\n                           *fake-api-context*))\n      (cl-mock:if-called 'request\n                         (lambda (api-ctx api &key method content)\n                           (setf called content)))\n      (answer (remote-version *fake-api-context*) 11)\n      (&body))))\n\n(test mark-unchanged-run-happy-path ()\n  (with-fixture state ()\n    (mark-unchanged-run\n     :fake-api-context\n     :other-commit \"abcd\"\n     :run-context (make-instance 'run-context\n                                 :channel \"bleh\"))\n    (is-true called)\n    (is (equal \"bleh\" (dto:unchanged-run-channel called)))\n    (is (equal \"abcd\" (dto:unchanged-run-other-commit called)))))\n\n(test handle-cmd-happy-path\n  (with-fixture state ()\n    (cl-mock:if-called 'run-context:merge-base\n                       (lambda (ctx)\n                         \"0001\"))\n    (finishes\n     (handle-cmd (make-fake-clingon (append\n                                     (root-options)\n                                     (all-args))\n                                    :channel \"bleh\"\n                                    :commit \"0001\"\n                                    :other-commit \"0002\")))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-version-check.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-version-check\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/version-check\n                #:bad-version-p\n                #:warn-for-bad-versions)\n  (:import-from #:util/request\n                #:*engine*\n                #:http-request)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:fetch-version\n                #:remote-version\n                #:api-context)\n  (:import-from #:screenshotbot/api/model\n                #:*api-version*)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:flags #:screenshotbot/sdk/flags)))\n(in-package :screenshotbot/sdk/test-version-check)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((auto-restart:*global-enable-auto-retries-p* nil))\n    (cl-mock:with-mocks ()\n      (&body))))\n\n(test get-version\n  (with-fixture state ()\n    (answer (http-request \"https://api.screenshotbot.io/api/version\"\n                          :basic-authorization nil\n                          :want-string t\n                          :engine *engine*)\n      (values \"{\\\"version\\\":1}\" 200))\n    (is (eql 1 (fetch-version\n                (make-instance 'api-context\n                               :hostname \"https://api.screenshotbot.io\"))))))\n\n(test get-version-with-key-secret\n  (with-fixture state ()\n    (answer (http-request \"https://api.screenshotbot.io/api/version\"\n                          :basic-authorization (list \"foo\" \"bar\")\n                          :want-string t\n                          :engine *engine*)\n      (values \"{\\\"version\\\":1}\" 200))\n    (is (eql 1 (fetch-version\n                (make-instance 'api-context\n                               :key \"foo\"\n                               :secret \"bar\"\n                               :hostname \"https://api.screenshotbot.io\"))))))\n\n#-sbcl ;; See D7222. Temporary fix until we can see what's going on with this.\n(test get-version-404\n  (with-fixture state ()\n    (answer (http-request \"https://www.google.com/api/version\"\n                          :basic-authorization nil\n                          :want-string t\n                          :engine *engine*)\n      (values \"\" 404))\n    (is (eql 1 (fetch-version\n                (make-instance 'api-context\n                               :key \"\"\n                               :secret \"\"\n                               :hostname \"https://www.google.com\"))))))\n\n(test with-version-check\n  (with-fixture state ()\n    (if-called 'fetch-version\n               (lambda (api-context)\n                 189))\n    (let ((ans))\n      (let ((api-context (make-instance 'api-context\n                                        :hostname \"...\")))\n        (is (eql 189 (remote-version api-context)))\n        (is (eql 189 (remote-version api-context)))))))\n\n(test bad-version-p ()\n  (let ((*api-version* 102))\n    (is-true (bad-version-p 99))\n    (is-true (bad-version-p 107))\n    (is-false (bad-version-p 102))\n    (is-false (bad-version-p 101))\n    (is-false (bad-version-p 103))))\n\n(test warn-for-bad-versions ()\n  (let ((*api-version* 102))\n    (finishes (warn-for-bad-versions 102))\n    (finishes (warn-for-bad-versions 95))\n    (finishes (warn-for-bad-versions 106))))\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/test-xcresult.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/test-xcresult\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/sdk/xcresult\n                #:make-xcresults-bundle\n                #:parse-name\n                #:xcresults-attachment-bundle)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:close-image\n                #:close-bundle\n                #:image-stream\n                #:image-name\n                #:list-images)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains))\n(in-package :screenshotbot/sdk/test-xcresult)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((bundle (make-instance 'xcresults-attachment-bundle :directory\n                               (asdf:system-relative-pathname :screenshotbot.sdk \"fixture/xcresults-attachments/\"))))\n    (&body)))\n\n(test simple-parsing\n  (with-fixture state ()\n    (assert-that (list-images bundle)\n                 (has-length 2))))\n\n\n(test parse-suggested-name\n  (is\n   (equal \"SimpleProjectTests/testLoginViewSnapshot.1_0\"\n    (parse-name\n     ;; see manifest.json\n     \"SnapshotTest_testLoginViewSnapshot.1_0_FA32D8D3-B6A9-4B7E-A958-921F53BBC2FE.png\"\n     \"SimpleProjectTests/testLoginViewSnapshot()\"))))\n\n(test image-name-works\n  (with-fixture state ()\n    (assert-that (mapcar #'image-name (list-images bundle))\n                 (has-item \"SimpleProjectTests/testLoginViewSnapshot.1_0\"))))\n\n#+darwin\n(test image-name-works-for-actual-xcresult\n  (with-fixture state ()\n    (let ((bundle (make-xcresults-bundle (asdf:system-relative-pathname :screenshotbot.sdk\n                                                                        \"fixture/result12.xcresult\"))))\n      (assert-that (mapcar #'image-name (list-images bundle))\n                   (has-item \"SimpleProjectTests/testLoginViewSnapshot.1_0\"))\n      (close-bundle bundle))))\n\n(defun find-image (bundle name)\n  (loop for image in (list-images bundle)\n        if (equal name (image-name image))\n          return image))\n\n(test image-stream\n  (with-fixture state ()\n    (let* ((image (find-image bundle \"SimpleProjectTests/testLoginViewSnapshot.1_0\"))\n           (stream (image-stream image)))\n      (is\n       (equal\n        \"32143da99ce89207d2aa0ecf85843349\"\n        (str:downcase\n         (ironclad:byte-array-to-hex-string (md5:md5sum-stream stream)))))\n      (close-image image))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/unchanged-run.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/unchanged-run\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/git\n                #:current-commit\n                #:git-repo)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:remote-version)\n  (:import-from #:screenshotbot/sdk/cli-common\n                #:register-root-command\n                #:with-clingon-api-context)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:flags-run-context\n                #:env-reader-run-context)\n  (:import-from #:screenshotbot/sdk/env\n                #:make-env-reader)\n  (:local-nicknames (#:flags #:screenshotbot/sdk/flags)\n                    (#:sdk #:screenshotbot/sdk/sdk)\n                    (#:version-check #:screenshotbot/sdk/version-check)\n                    (#:run-context #:screenshotbot/sdk/run-context)\n                    (#:dto #:screenshotbot/api/model))\n  (:export\n   #:mark-unchanged-run))\n(in-package :screenshotbot/sdk/unchanged-run)\n\n(defun mark-unchanged-run (api-context &key\n                                         (run-context (make-instance 'flags-run-context\n                                                                     :env (make-env-reader)))\n                                         (other-commit flags:*unchanged-from*))\n  (when (< (remote-version api-context) 6)\n    (error \"The remote Screenshotbot server does not support --mark-unchanged-from\"))\n\n  (when (equal (run-context:channel run-context) \"unnamed-channel\")\n    (error \"You must provide a --channel to use --mark-unchanged-from\"))\n\n  (log:info \"Marking commit as unchanged\")\n  (let* ((commit (run-context:commit-hash run-context)))\n    (sdk:request api-context\n                 \"/api/unchanged-run\"\n                 :method :post\n                 :content\n                 (make-instance 'dto:unchanged-run\n                                :commit commit\n                                :channel (run-context:channel run-context)\n                                :other-commit other-commit\n                                :work-branch (run-context:work-branch run-context)\n                                :batch (run-context:batch run-context)\n                                :github-repo (run-context:repo-url run-context)\n                                :override-commit-hash (run-context:override-commit-hash run-context)\n                                :merge-base (run-context:merge-base run-context)\n                                :pull-request (run-context:pull-request-url run-context)\n                                :phabricator-diff-id (run-context:phabricator-diff-id run-context)))))\n\n(defun suffix (arg)\n  (format nil \"~a This is currently only used if `--batch` is provided, and in most cases we can guess this automatically in your CI.\" arg))\n\n(defclass unchanged-run-run-context (run-context:env-reader-run-context\n                             run-context:run-context)\n  ())\n\n(defun batch-args ()\n  (list\n   (make-option\n    :string\n    :long-name \"batch\"\n    :description \"An optional batch name for batching runs.\"\n    :key :batch)\n   (make-option\n    :string\n    :long-name \"repo-url\"\n    :description (suffix \"The optional repository URL.\")\n    :key :repo-url)\n   (make-option\n    :string\n    :long-name \"pull-request\"\n    :description (suffix \"The pull request URL.\")\n    :key :pull-request)\n   (make-option\n    :string\n    :long-name \"phabricator-diff-id\"\n    :description (suffix \"A Diff ID for Phabricator.\")\n    :key :phabricator-diff-id)))\n\n(defun all-args ()\n  (list*\n   (make-option\n    :string\n    :long-name \"commit\"\n    :description \"The current commit, defaults to the commit of the current repository\"\n    :key :commit)\n   (make-option\n    :string\n    :long-name \"other-commit\"\n    :description \"The other commit that we're identifying this commit to be unchanged from\"\n    :key :other-commit)\n   (make-option\n    :string\n    :long-name \"channel\"\n    :description \"The channel name\"\n    :key :channel)\n   (batch-args)))\n\n(defun handle-cmd (cmd)\n  (with-clingon-api-context (api-context cmd)\n    (mark-unchanged-run\n     api-context\n     :other-commit (getopt cmd :other-commit)\n     :run-context (make-instance 'unchanged-run-run-context\n                                 :env (make-env-reader)\n                                 :commit-hash (getopt cmd :commit)\n                                 :channel (getopt cmd :channel)\n                                 :repo-url (getopt cmd :repo-url)\n                                 :batch (getopt cmd :batch)\n                                 :pull-request-url (getopt cmd :pull-request)\n                                 :phabricator-diff-id (getopt cmd :phabricator-diff-id)))))\n\n(defun mark-unchanged-from/command ()\n  (clingon:make-command\n   :name \"mark-unchanged\"\n   :description \"Mark a commit as having identical screenshots to another commit on the same channel.\"\n   :options (all-args)\n   :handler #'handle-cmd))\n\n(register-root-command 'mark-unchanged-from/command)\n"
  },
  {
    "path": "src/screenshotbot/sdk/upload-commit-graph.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/upload-commit-graph\n  (:use #:cl)\n  (:import-from #:screenshotbot/sdk/clingon-api-context\n                #:with-clingon-api-context)\n  (:import-from #:screenshotbot/sdk/sdk\n                #:update-commit-graph)\n  (:import-from #:clingon.command\n                #:getopt)\n  (:import-from #:screenshotbot/sdk/run-context\n                #:run-context)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:screenshotbot/sdk/env\n                #:make-env-reader\n                #:build-url)\n  (:import-from #:screenshotbot/sdk/commit-graph\n                #:commit-graph-updater)\n  (:import-from #:screenshotbot/sdk/request\n                #:request)\n  (:import-from #:screenshotbot/api/model\n                #:build-info)\n  (:import-from #:alexandria\n                #:when-let)\n  (:local-nicknames (#:run-context #:screenshotbot/sdk/run-context)))\n(in-package :screenshotbot/sdk/upload-commit-graph)\n\n\n(defclass commit-graph-run-context (run-context:run-context\n                                    run-context:env-reader-run-context)\n  ()\n  (:default-initargs\n   :env (make-env-reader)))\n\n(defun upload-commit-graph/command ()\n  (clingon:make-command\n   :name \"upload-commit-graph\"\n   :handler #'upload-commit-graph/handler\n   :options (list\n             (make-option\n              :string\n              :long-name \"repo-url\"\n              :initial-value nil\n              :description \"The URL of the repository (e.g. 'https://github.com/foo/bar')\"\n              :key :repo-url)\n             (make-option\n              :string\n              :long-name \"main-branch\"\n              :initial-value nil\n              :description \"The main branch to compare this run with. e.g. `main`, `master`, or `trunk`. The default is to first try `main`, and then `master` and pick the first such that origin/<name> exists.\"\n              :key :main-branch))\n   :description \"Uploads the current commit graph. Useful if you need to upload multiple channels in the same CI job, and want to avoid uploading commit graph multiple times.\"))\n\n(defun push-build-info-if-xcode-cloud (api-context run-context)\n  \"Push build-info if running in Xcode Cloud environment\"\n  (when (uiop:getenv \"CI_XCODE_CLOUD\")\n    (when-let ((build-url (run-context:build-url run-context))\n               (repo-url (run-context:repo-url run-context)))\n      (log:info \"Detected Xcode Cloud, pushing build-info with build-url: ~a and repo-url: ~a\" \n                build-url repo-url)\n      (let ((build-info-dto (make-instance 'build-info\n                                         :build-url build-url\n                                         :repo-url repo-url)))\n        (request api-context \"/api/build-info\"\n                 :method :put\n                 :content build-info-dto\n                 :decode-response nil)))))\n\n(defun upload-commit-graph/handler (cmd)\n  (with-clingon-api-context (api-context cmd)\n    (let ((run-context (make-instance 'commit-graph-run-context\n                                      :repo-url (getopt cmd :repo-url)\n                                      :main-branch (getopt cmd :main-branch))))\n      (unless (run-context:repo-url run-context)\n        (error \"Could not determine repository URL, please pass the --repo-url argument\"))\n      (log:info \"Updating commit graph for ~a\" (run-context:repo-url run-context))\n      (push-build-info-if-xcode-cloud api-context run-context)\n      (update-commit-graph (make-instance 'commit-graph-updater :api-context api-context)\n                           (run-context:git-repo run-context)\n                           (run-context:main-branch run-context)))))\n\n"
  },
  {
    "path": "src/screenshotbot/sdk/version-check.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sdk/version-check\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/model\n                #:decode-json\n                #:version-number\n                #:version\n                #:*api-version*)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:screenshotbot/sdk/backoff\n                #:maybe-retry-request\n                #:backoff)\n  (:import-from #:screenshotbot/sdk/hostname\n                #:format-api-url)\n  (:import-from #:screenshotbot/sdk/api-context\n                #:fetch-version\n                #:remote-version)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:api-context #:screenshotbot/sdk/api-context))\n  (:export\n   #:*client-version*\n   #:remote-supports-put-run\n   #:fetch-version))\n(in-package :screenshotbot/sdk/version-check)\n\n(defparameter *client-version* (asdf:system-version\n                                (asdf:find-system :screenshotbot.sdk/library))\n  \"The client version. Note that is different from the *api-version*.\")\n\n(defun remote-supports-basic-auth-p (api-context)\n  \"Prior to this version, the auth was passed as http parameters. That\nwasn't great for security since it might mean the plain-text secret\nmight get logged in the webserver logs.\"\n  (>= (remote-version api-context) 2))\n\n(defun remote-supports-put-run (api-context)\n  (>= (remote-version api-context) 4))\n\n(defvar *in-here* nil)\n(auto-restart:with-auto-restart (:attempt attempt)\n  (defmethod fetch-version (api-context)\n    (log:info \"Fetching remote version\")\n    (assert (not *in-here*))\n    (let ((*in-here* t))\n     (multiple-value-bind (body ret)\n         (http-request\n          (format-api-url api-context \"/api/version\")\n          :basic-authorization (unless (and\n                                        (str:emptyp (api-context:key api-context))\n                                        (str:emptyp (api-context:secret api-context)))\n                                   (list\n                                    (api-context:key api-context)\n                                    (api-context:secret api-context)))\n          :want-string t\n          :engine (api-context:engine api-context))\n       (maybe-retry-request\n        ret\n        :attempt attempt\n        :restart 'retry-fetch-version)\n       (let ((version (cond\n                        ((eql 200 ret)\n                         (decode-json body 'version))\n                        ((eql 404 ret)\n                         (log:warn \"/api/version responded 404, this is probably because of running an old version of OSS Screenshotbot service\")\n                         (make-instance 'version :version 1))\n                        (t\n                         (log:error \"/api/version failed with code ~a\" ret)\n                         (error \"/api/version failed\")))))\n         (warn-for-bad-versions (version-number version))\n         (values\n          (version-number version)\n          version))))))\n\n(defun bad-version-p (remote-version)\n  (>\n   (abs\n    (- remote-version *api-version*))\n   1))\n\n(defun warn-for-bad-versions (remote-version)\n  (when (not (bad-version-p remote-version))\n    (return-from warn-for-bad-versions nil))\n  (flet ((warning (reason)\n           (log:warn \"Server is running API version ~a, but this client uses version ~a. This is most likely supported, however, it's more likely to have bugs. ~a\" remote-version *api-version* reason)))\n    (cond\n      ((> *api-version* remote-version)\n       (warning \"If you're using OSS Screenshotbot, we suggest upgrading.\"))\n      ((< *api-version* remote-version)\n       (warning \"Consider upgrading the CLI tool.\")))))\n\n(def-health-check verify-can-decode-json ()\n  (eql 10 (version-number (decode-json \"{\\\"version\\\":10}\" 'version))))\n"
  },
  {
    "path": "src/screenshotbot/sdk/xcresult.lisp",
    "content": "(defpackage :screenshotbot/sdk/xcresult\n  (:use :cl)\n  (:import-from #:screenshotbot/sdk/bundle\n                #:local-image\n                #:abstract-image\n                #:close-bundle\n                #:image-stream\n                #:image-name\n                #:list-images)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:iterate\n                #:iter))\n(in-package :screenshotbot/sdk/xcresult)\n\n(defclass xcresults-attachment-bundle ()\n  ((directory :initarg :directory\n              :reader %directory)))\n\n(defclass xcresults-attachment (local-image)\n  ((bundle :initarg :bundle\n           :reader bundle)\n   (name :initarg :name\n         :reader image-name)\n   (file-name :initarg :file-name\n              :reader file-name)))\n\n(defun make-xcresults-bundle (xcresult)\n  ;; TODO: close-bundle. But we have to fix the tests to not\n  ;; accidently delete hardcoded directories.\n  (let ((dir (tmpdir:mkdtemp)))\n    (export-all-attachments xcresult dir)\n    (make-instance 'xcresults-attachment-bundle\n                   :directory dir)))\n\n(defmethod close-bundle ((self xcresults-attachment-bundle))\n  (tmpdir::%delete-directory (%directory self)))\n\n(defun export-all-attachments (xcresult-path output-path)\n  \"Export all attachments from the xcresult file to the given output directory.\n   Creates a manifest.json and all attachment files (PNGs, logs, etc.).\"\n  (multiple-value-bind (output error-output exit-code)\n      (uiop:run-program (list \"xcrun\" \"xcresulttool\" \"export\" \"attachments\"\n                              \"--path\" (namestring xcresult-path)\n                              \"--output-path\" (namestring output-path))\n                        :output :string \n                        :error-output :string \n                        :ignore-error-status t)\n    (if (zerop exit-code)\n        (values t output)  ; Return success and any output message\n        (error \"Failed to export attachments: ~A\" error-output))))\n\n(defun parse-name (suggested-name test-identifier)\n  (let ((parts (reverse (str:split \"/\" test-identifier))))\n    (str:join \"/\"\n     (reverse\n      (list*\n       (cl-ppcre:regex-replace-all \"^SnapshotTest_\"\n                                   (cl-ppcre:regex-replace-all \"_[0-9A-Z-]*.png$\" suggested-name \"\")\n                                   \"\")\n       (cdr parts))))))\n\n(defmethod list-images ((self xcresults-attachment-bundle))\n  (let ((manifest (json:decode-json-from-string\n                   (uiop:read-file-string (path:catfile (%directory self) \"manifest.json\")))))\n    (loop for entry in manifest\n          for test-identifier = (assoc-value entry :test-identifier)\n          appending\n          (loop for attachment in (assoc-value entry :attachments)\n                for suggested-name = (assoc-value attachment :suggested-human-readable-name)\n                for exported-file-name = (assoc-value attachment :exported-file-name)\n                if (and\n                    (str:starts-with-p \"SnapshotTest_\" suggested-name)\n                    (str:ends-with-p \".png\" suggested-name))\n                collect\n                (make-instance 'xcresults-attachment\n                               :bundle self\n                               :pathname (path:catfile\n                                                (%directory self)\n                                                exported-file-name)\n                               :name (parse-name suggested-name test-identifier))))))\n\n\n;; Example usage:\n;; (export-all-attachments #P\"~/Downloads/results.xcresult\" #P\"/tmp/attachments/\")\n\n"
  },
  {
    "path": "src/screenshotbot/secret.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/secret\n    (:use #:cl\n          #:alexandria)\n    (:import-from #:bknr.impex\n                  #:parse-xml-file)\n    (:import-from #:bknr.indices\n                  #:indexed-class\n                  #:base-indexed-object)\n  (:export #:defsecret\n           #:secret))\n(in-package :screenshotbot/secret)\n\n(defvar *secret-list* nil)\n(defvar *secrets* nil)\n\n(defclass secret (base-indexed-object)\n  ((name :initarg :name\n         :reader secret-name)\n   (value :initarg :value\n          :reader secret-value)\n   (public :initarg :public\n           :initform nil\n           :reader secret-public-id)\n   (environment :initarg :environment\n                :initform \"production\"\n                :reader secret-environment))\n  (:metaclass indexed-class))\n\n(defmethod print-object ((self secret) out)\n  (format out \"#<SECRET ~a [~a]>\" (secret-name self)\n          (secret-environment self)))\n\n(defmacro defsecret (name documentation)\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (pushnew ',name *secret-list*)))\n\n(defun secret (name)\n  (get name '%secret))\n\n(defun (setf secret) (value name)\n  (unless (member name *secret-list*)\n    (error \"Secret ~S is not defined\" name))\n  (setf\n   (get name '%secret)\n   value))\n\n(define-compiler-macro secret (&whole whole name)\n  (unless (member name *secret-list*)\n    (error \"Secret ~S is not defined\" name))\n  whole)\n"
  },
  {
    "path": "src/screenshotbot/server.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/server\n    (:use :cl :alexandria)\n  (:import-from #:screenshotbot/analytics\n                #:push-analytics-event)\n  (:import-from #:screenshotbot/secret\n                #:defsecret)\n  (:import-from #:screenshotbot/installation\n                #:installation-cdn\n                #:installation\n                #:default-logged-in-page)\n  (:import-from #:util/threading\n                #:with-tags\n                #:*warning-count*\n                #:log-sentry)\n  (:import-from #:hunchentoot-extensions\n                #:log-crash-extras)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:core/ui/template\n                #:*app-template*)\n  (:import-from #:core/installation/installation\n                #:installation-domain)\n  #+lispworks\n  (:import-from #:hunchentoot-extensions/existing-socket\n                #:acceptor-with-existing-socket)\n  (:import-from #:screenshotbot/raft-redirect\n                #:maybe-redirect-to-leader)\n  (:import-from #:util/throttler\n                #:throttled-error)\n  (:import-from #:auth\n                #:no-access-error\n                #:logged-in-p)\n  (:import-from #:screenshotbot/login/common\n                #:with-login)\n  (:import-from #:core/api/acceptor\n                #:api-acceptor-mixin)\n  (:import-from #:core/ui/fonts\n                #:fonts-acceptor-mixin)\n  (:import-from #:core/active-users/active-users\n                #:mark-active-user)\n  (:import-from #:util/events\n                #:push-event)\n  (:import-from #:core/installation/request\n                #:with-installation-for-request)\n  (:import-from #:screenshotbot/throttler\n                #:maybe-throttle-request)\n  (:import-from #:auth/login/sso\n                #:with-handle-needs-sso\n                #:needs-sso-condition-company\n                #:needs-sso-condition)\n  (:export\n   #:defhandler\n   #:with-login\n   #:acceptor\n   #:%handler-wrap\n   #:staging-p\n   #:*root*\n   #:*is-localhost*\n   #:document-root\n   #:*reuben-ip*\n   #:*seleniump*\n   #:*domain*\n   #:logged-in-p\n   #:dashboard\n   #:make-thread\n   #:*acceptor*\n   #:prepare-acceptor-plugins\n   #:*seleniump*\n   #:no-access-error-page\n   #:*init-hooks*\n   #:register-init-hook\n   #:call-init-hooks\n   #:request\n   #:redirect-home\n   #:home-url\n   #:needs-sso-condition-company)\n  (:local-nicknames (#:threading #:util/threading)))\n(in-package :screenshotbot/server)\n\n(defparameter *domain* \"https://screenshotbot.io\")\n(defvar *root* (util:relative-system-source-directory :screenshotbot))\n\n(defvar *is-localhost* nil)\n\n(defvar *seleniump* nil)\n\n(defmacro defvar-with-doc (name doc)\n  `(progn\n     (defvar ,name)\n     (setf (documentation ',name 'variable) ,doc)))\n\n(defsecret :trello-key\n  \"Trello Key used for OAuth. You must still connect your Organization\n  to Trello from /settings/trello.\")\n\n(defsecret :trello-secret\n  \"Trello Secret used for OAuth. You must still connect your\n  Organization to Trello from the /settings/trello\")\n\n(defclass screenshotbot-template ()\n  ())\n\n(defun document-root ()\n  (path:catdir *root* #p\"static/\"))\n\n(defclass acceptor (#+lispworks acceptor-with-existing-socket\n                    nibble:nibble-acceptor-mixin\n                    auth:auth-acceptor-mixin\n                    api-acceptor-mixin\n                    hex:clos-dispatcher\n                    util:base-acceptor\n                    fonts-acceptor-mixin\n                    hunchensocket:websocket-acceptor\n                    hex:acceptor-with-plugins) ()\n  (:default-initargs\n   :name 'screenshotbot-acceptor\n   :document-root (document-root)\n   :request-class 'request))\n\n(defclass request (auth:authenticated-request\n                   hunchensocket::websocket-request)\n  ())\n\n(defmethod auth:authenticate-request :after ((request request))\n  \"Tracks active users for analytics. Note: API calls (/api/*) don't\nget logged here because this runs before API authentication. For API\nrequests, user/company are nil at this point, and mark-active-user is\na no-op when either is nil.\"\n  (mark-active-user :user (auth:request-user request)\n                    :company (auth:request-account request)\n                    :ip (ignore-errors ;; might not be bound in tests\n                         (hunchentoot:real-remote-addr request))))\n\n(defvar *acceptor*\n  (make-instance 'acceptor\n                 :port #-screenshotbot-oss 4001\n                       #+screenshotbot-oss 4091 ;; Called directly from launch.lisp\n                 :name 'screenshotbot-acceptor\n                 :document-root (document-root)))\n\n(defvar *init-hooks* nil)\n\n(defun call-init-hooks ()\n  (mapc #'funcall (mapcar 'cdr (reverse *init-hooks*))))\n\n(defun register-init-hook (name function)\n  (setf (alexandria:assoc-value *init-hooks* name) function))\n\n(defmethod nibble:nibble-funcall :around ((self acceptor) nibble)\n  (%handler-wrap\n   (lambda ()\n     (call-next-method))))\n\n;; (hunchentoot:start *acceptor*)\n\n(defun staging-p ()\n  (declare (optimize (speed 3)))\n  (cond\n    (t\n     (or\n      (not (boundp 'hunchentoot:*request*))\n      (let ((host (hunchentoot:host)))\n        (or\n         #+screenshotbot-oss\n         t\n         (str:starts-with-p \"192.168.1.119\" host)\n         (str:starts-with-p \"localhost\" host)\n         (str:starts-with-p \"staging.\" host)))))))\n\n(defun pp (x)\n  (log:info \"~S\" x)\n  x)\n\n(def-easy-macro with-sentry-extras (&fn fn)\n  (let ((threading:*extras*\n              (list*\n               (lambda (e)\n                 (log-crash-extras hunchentoot:*acceptor* e))\n               threading:*extras*)))\n    (funcall fn)))\n\n\n(defun %handler-wrap (impl)\n  (with-sentry-extras ()\n    (handler-case\n        (with-handle-needs-sso ()\n          (funcall impl))\n      (no-access-error (e)\n        (push-event :no-access-error)\n        (no-access-error-page))\n      (throttled-error (e)\n        (declare (ignore e))\n        (setf (hunchentoot:return-code*) 429)\n        (when (= 0 (random 10))\n          (warn \"Too many request: ~a (warning fired only 10% of the time)\" e))\n        \"Too many requests\"))))\n\n(defmacro defhandler ((name &key uri method intern\n                              want-login) params &body body)\n  (multiple-value-bind (body decls) (uiop:parse-body body :documentation t)\n    `(util:better-easy-handler (,name :uri ,uri :method ,method :acceptor-names '(screenshotbot-acceptor)\n                                      :intern ,intern)\n        ,params\n        ,@ decls\n       (%handler-wrap (lambda ()\n                        ,@ (if want-login\n                               (list\n                                `(with-login ()\n                                   ,@body))\n                               body))))))\n\n(defparameter *asset-regex*\n  (cl-ppcre:create-scanner \"[.](js|css|woff|otf|woff2|png|jpg|jpeg|webp|svg)$\"))\n\n(defmethod hunchentoot:acceptor-dispatch-request ((acceptor acceptor) request)\n  (maybe-redirect-to-leader request)\n  (with-installation-for-request (request)\n    (handler-bind ((nibble:expired-nibble (lambda (w)\n                                            (push-event :expired-nibble\n                                                        :src (nibble:expired-nibble-src w))\n                                            (muffle-warning))))\n      (with-tags ((\"hostname\" (uiop:hostname)))\n        (or\n         (maybe-throttle-request (installation) request)\n         (let ((*app-template* (make-instance 'screenshotbot-template)))\n           (auth:with-sessions ()\n             (push-analytics-event)\n             (let ((script-name (hunchentoot:script-name request))\n                   (util.cdn:*cdn-domain*\n                     (installation-cdn (installation))))\n               (cond\n                 ((staging-p)\n                  (setf (hunchentoot:header-out \"Cache-Control\") \"no-cache\"))\n                 ((cl-ppcre:scan *asset-regex* script-name)\n                  (setf (hunchentoot:header-out \"Cache-Control\") \"max-age=3600000\")))\n               (setf (hunchentoot:header-out \"X-Frame-Options\") \"DENY\")\n               (setf (hunchentoot:header-out \"X-Content-Type-Options\") \"nosniff\")\n               (set-content-security-policy)\n               (when (and\n                      (str:starts-with-p \"/assets\" script-name)\n                      (not *is-localhost*))\n                 (setf (hunchentoot:header-out\n                        \"Access-Control-Allow-Origin\")\n                       (installation-domain (installation))))\n               (call-next-method)))))))))\n\n(defun set-content-security-policy ()\n  (setf (hunchentoot:header-out \"Reporting-Endpoints\")\n        \"csp-endpoint=\\\"/csp-report\\\"\")\n  (setf (hunchentoot:header-out \"Content-Security-Policy\")\n        (format nil \"~{~a~^; ~}\"\n                `(\"default-src 'self'\"\n                  ,(format nil \"script-src 'self' 'unsafe-inline' https://js.stripe.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://unpkg.com https://assets.calendly.com https://code.jquery.com https://js.hs-scripts.com https://js.hs-banner.com https://js.hs-analytics.net https://js.hscollectedforms.net https://js.usemessages.com https://www.googletagmanager.com https://googleads.g.doubleclick.net https://www.googleadservices.com https://testimonial.to https://cdn.gdprlocal.com https://*.hotjar.com https://*.cookiebot.com https://static.hsappstatic.net https://www.google.com https://www.gstatic.com https://plausible.io ~a\" util.cdn:*cdn-domain* )\n                  ,(format nil \"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://stackpath.bootstrapcdn.com https://assets.calendly.com https://fonts.googleapis.com ~a\" util.cdn:*cdn-domain*)\n                  ,(format nil \"font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net ~a\" util.cdn:*cdn-domain*)\n                  \"img-src 'self' data: blob: https:\"\n                  ,(format nil \"media-src 'self' ~a\" util.cdn:*cdn-domain*)\n                  \"connect-src 'self' https: wss:\"\n                  \"frame-src 'self' https://www.youtube.com https://assets.calendly.com https://embed-v2.testimonial.to https://www.googletagmanager.com https://app.hubspot.com https://meetings.hubspot.com https://*.cookiebot.com\"\n                  \"frame-ancestors 'none'\"\n                  \"base-uri 'self'\"\n                  ;; Chrome enforces form-action on redirect targets,\n                  ;; so nibble forms that redirect to OAuth providers get blocked.\n                  \"form-action 'self' https:\"\n                  \"report-uri /csp-report\"\n                  \"report-to csp-endpoint\"))))\n\n(defhandler (nil :uri \"/csp-report\" :method :post) ()\n  (let ((body (hunchentoot:raw-post-data :force-text t)))\n    (log:warn \"CSP violation\")\n    (warn \"CSP violation: ~a\" body)\n    \"\"))\n\n(defhandler (nil :uri \"/force-crash\") ()\n  (error \"ouch\"))\n\n(hex:def-clos-dispatch ((self acceptor) \"/force-crash2\") ()\n  (error \"ouch2\"))\n\n;; (ignore-and-log-errors ()  (error \"foo\"))\n\n\n(defhandler (nil :uri \"/nibble/:nibble-id\") (nibble-id)\n  ;; just in case there are old nibble links lying around. Unlikely\n  (hex:safe-redirect (format nil \"/n/~a\" nibble-id)))\n\n(defhandler (nil :uri \"/robots.txt\") ()\n  (Setf (hunchentoot:header-out :content-type) \"text/plain\")\n  (cond\n    ((staging-p)\n     \"User-agent: *\nDisallow: /\")\n    (t\n     \"User-agent: *\nDisallow: /n\nDisallow: /runs/by-tag\nDisallow: /badge\nDisallow: /active-run\n\")))\n\n(defhandler (nil :uri \"/\") ()\n  (cond\n    ((logged-in-p)\n     (default-logged-in-page (installation)))\n    (t\n     (render-landing-page (installation)))))\n\n(defmethod render-landing-page ((self installation))\n  (hex:safe-redirect \"/login\"))\n\n(defun home-url ()\n  (if (logged-in-p)\n      \"/runs\"\n      \"/\"))\n\n(defun redirect-home ()\n  (hex:safe-redirect \"/runs\"))\n\n(server:register-acceptor *acceptor*\n                          \"screenshotbot.io\"\n                          \"staging.screenshotbot.io\"\n                          \"kickstarter.screenshotbot.io\"\n                          \"mx.tdrhq.com\"\n                          \"api.screenshotbot.io\"\n                          \"blog.screenshotbot.io\")\n\n(defun make-thread (fn &rest args)\n  (apply\n   'util:make-thread\n   fn\n   args))\n\n(defhandler (nil :uri \"/test-headers\") ()\n  (format nil \":~a:\" (hunchentoot:header-in* :x-forwarded-proto)))\n\n\n(defhandler (nil :uri \"/test-nibble-redirect\") ()\n  (hex:safe-redirect\n   (nibble:nibble ()\n     \"hello world\")))\n\n(defhandler (nil :uri \"/confirm-email/:id/:code\" :method :get)\n            (code id)\n  (hex:safe-redirect \"/confirm-email\"\n                     :id id :code code))\n"
  },
  {
    "path": "src/screenshotbot/settings/general.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/settings/general\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:screenshotbot/model/user\n        #:screenshotbot/user-api\n        #:nibble\n        #:screenshotbot/settings/settings-template)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/login/signup\n                #:validate-name)\n  (:import-from #:util/form-errors\n                #:with-form-errors))\n(in-package :screenshotbot/settings/general)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun %general-settings ()\n  (let ((submit (nibble (full-name)\n                  (submit-form :full-name full-name))))\n    <settings-template>\n      <form method=\"post\" action=submit >\n        <div class= \"card mt-3\">\n          <div class= \"card-header\">\n            <h4>Account Details</h4>\n          </div>\n          <div class= \"card-body\">\n            <div class= \"row\">\n              <div class= \"col-md-6\">\n                <div class= \"setting-item-title\" >Name</div>\n                <div class= \"setting-item-subtitle\" >Your full name</div>\n              </div>\n              <div class= \"col-md-6\">\n                <div class= \"form-group setting-form-group\">\n                  <div class= \"row\">\n                    <input type= \"text\" name= \"full-name\" class= \"form-control\"\n                           value= (user-full-name (current-user)) />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div> <!-- /card-body -->\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Save changes\" />\n          </div>\n\n        </div>\n      </form>\n    </settings-template>))\n\n(defsettings general-settings\n  :name \"general\"\n  :title \"General\"\n  :section nil\n  :handler '%general-settings)\n\n(defun submit-form (&key full-name)\n  (let ((errors))\n   (flet ((check (name test message)\n            (unless test\n              (push (cons name message) errors))))\n     (validate-name #'check full-name)\n     (cond\n       (errors\n        (with-form-errors (:full-name full-name\n                           :errors errors\n                           :was-validated t)\n          (%general-settings)))\n       (t\n        (setf (user-full-name (current-user))\n              full-name)\n        (hex:safe-redirect \"/settings/general\"))))))\n"
  },
  {
    "path": "src/screenshotbot/settings/security.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/settings/security\n  (:use #:cl\n        #:alexandria\n        #:markup\n        #:screenshotbot/model/user\n        #:nibble\n        #:util/form-errors\n        #:screenshotbot/user-api\n        #:screenshotbot/settings/settings-template)\n  (:import-from #:bknr.datastore\n                #:with-transaction))\n(in-package :screenshotbot/settings/security)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag settings-card (children)\n  <div class= \"card mt-3\">\n    ,@children\n  </div>)\n\n(deftag settings-card-header (children)\n  <div class= \"card-header\">\n    ,@children\n  </div>)\n\n(deftag settings-card-footer (children)\n  <div class= \"card-footer pb-3\">\n    ,@children\n  </div>)\n\n(deftag settings-security-card-body (&key success)\n  <div>\n        <settings-card-body >\n          <div class= \"row\">\n            <div class= \"col-md-6\">\n              <div class= \"setting-item-title\">Current Password</div>\n              <div class= \"setting-item-subtitle\">Your current password</div>\n            </div>\n\n            <div class= \"col-md-6\">\n              <input type= \"password\" name= \"current-password\" class= \"form-control\"\n                     />\n            </div>\n          </div>\n        </settings-card-body>\n\n        <settings-card-body >\n          <div class= \"row\">\n            <div class= \"col-md-6\">\n              <div class= \"setting-item-title\">New Password</div>\n            </div>\n\n            <div class= \"col-md-6\">\n              <input type= \"password\" name= \"password\" class= \"form-control\"\n                     />\n            </div>\n          </div>\n        </settings-card-body>\n\n        <settings-card-body >\n          <div class= \"row\">\n            <div class= \"col-md-6\">\n              <div class= \"setting-item-title\">Verify Password</div>\n              <div class= \"setting-item-subtitle\">Verify your new password</div>\n            </div>\n\n            <div class= \"col-md-6\">\n              <input type= \"password\" name= \"verify-password\" class= \"form-control\"\n                     />\n            </div>\n          </div>\n        </settings-card-body>\n\n  <settings-card-footer >\n    <input type= \"submit\" class= \"btn btn-danger float-right\" value= \"Change Password\" >\n    ,(when success\n       <div class= \"float-right mt-3\" >Password was successfully changed</div>)\n\n        </settings-card-footer>\n\n  </div>)\n\n(deftag settings-card-body (children &key (class \"\"))\n  <div class= (format nil \"card-body ~a\" class) >\n    ,@children\n  </div>)\n\n(deftag settings-security-page (&key success)\n    <settings-template>\n    <form action= (nibble (current-password\n                           password\n                           verify-password)\n                    (settings-security-page-post current-password\n                                                password\n                                                verify-password)) method= \"post\">\n      <settings-card>\n        <settings-card-header>\n          <h4>Change Password</h4>\n        </settings-card-header>\n\n        ,(if (auth:password-hash (current-user))\n             <settings-security-card-body success=success />\n             <settings-card-body>\n               This account is associated with an OAuth external account. Please change the password with your OAuth provider.\n             </settings-card-body>)\n      </settings-card>\n    </form>\n  </settings-template>)\n\n(defsettings settings-security-page\n  :name \"security\"\n  :section nil\n  :title \"Security\"\n  :handler (lambda ()\n             (settings-security-page)))\n\n(defun settings-security-page-post (current-password\n                                    password\n                                    verify-password)\n  (let ((errors nil))\n    (flet ((check (name test message)\n             (unless test\n               (push (cons name message) errors))))\n      (check :current-password\n             (auth:check-password (current-user) current-password)\n             \"Password was incorrect\")\n      (check :password\n             (not (str:emptyp password))\n             \"Please enter a password\")\n      (check :password\n             (>= (length password) 8)\n             \"Password should have at least 8 letters\")\n      (check :verify-password\n             (equal password verify-password)\n             \"The two passwords you entered didn't match\"))\n    (cond\n      (errors\n       (with-form-errors (:errors errors\n                          :was-validated t)\n         (settings-security-page)))\n      (t\n       (with-transaction ()\n        (setf (auth:user-password (current-user))\n              password))\n       (hex:safe-redirect\n        (nibble ()\n          (settings-security-page :success t)))))))\n"
  },
  {
    "path": "src/screenshotbot/settings/settings-template.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/settings/settings-template\n  (:use #:cl #:alexandria #:markup)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/template\n                #:dashboard-template)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/api/core\n                #:defapi)\n  (:export #:settings-template #:defapi)\n  (:use-reexport\n   #:screenshotbot/settings-api))\n(in-package :screenshotbot/settings/settings-template)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag settings-menu-item (children &key href)\n  <li class= \"nav-item\" >\n    <a class= (format nil \"nav-link text-dark rounded ~a\" (if (equal (hunchentoot:script-name*) href) \"active bg-primary text-white\" \"\"))\n       href= href >\n      <span class= \"text\">,@children </span>\n    </a>\n  </li>)\n\n(defun settings-list-sections ()\n  (remove-duplicates\n   (loop for (nil . x) in (reverse (all-settings (installation)))\n         collect (settings-section x))))\n\n(deftag settings-render-section (&key title\n                                 section)\n  <markup:merge-tag>\n  <li class= \"nav-item settings-section-title\">\n    <h6 class= \"text-muted text-uppercase small fw-bold py-2\">,(progn title)</h6>\n  </li>\n  ,@ (loop for (nil . x)  in (reverse (all-settings (installation)))\n           if (eql (settings-section x) section)\n             collect <settings-menu-item href= (format nil \"/settings/~a\" (settings-name x))>,(settings-title x)</settings-menu-item>)\n  </markup:merge-tag>)\n\n(deftag settings-template (children &key title)\n  (let ((sections (mapcar 'settings-section (mapcar 'cdr (all-settings (installation))))))\n    (assert (equal nil\n                   (set-difference (remove-duplicates sections)\n                                   '(nil :vcs :tasks :organization :developers)\n                                   :test #'string=)))\n\n      <dashboard-template scripts= (list\n                                    \"/assets/js/settings.js\")\n                          title= (or title \"Screenshotbot: Settings\")\n                      >\n    \n    <div class= \"main-content\">\n      <!-- Mobile settings menu toggle -->\n      <div class= \"d-md-none bg-light border-bottom p-3\">\n        <button class= \"btn btn-outline-secondary\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#settingsNavbar\" aria-controls=\"settingsNavbar\" aria-expanded=\"false\" aria-label=\"Toggle settings navigation\">\n          <i class= \"bi bi-list\"></i> Settings Menu\n        </button>\n      </div>\n\n      <div class= \"row\" style= \"min-height: 100vh\" >\n        <div class= \"col-lg-3 col-md-4 collapse d-md-block\" id=\"settingsNavbar\">\n          <div class= \"settings-sidebar p-3 m-3\">\n            <h5 class= \"mb-3 text-dark d-none d-md-block\">Settings</h5>\n            <ul class= \"nav nav-pills flex-column\">\n              <settings-render-section title= \"Account\" section=nil />\n              <settings-render-section title= \"Organization\" section=:organization />\n              <settings-render-section title= \"VCS\" section=:vcs />\n              <settings-render-section title= \"Tasks\" section=:tasks />\n              <settings-render-section title= \"Developers\" section=:developers />\n            </ul>\n          </div>\n        </div>\n\n        <div class= \"col-lg-9 col-md-8\">\n          <div class= \"settings-content p-4 m-3\">\n            ,@children\n          </div>\n        </div>\n      </div>\n    </div>\n  </dashboard-template>))\n\n(defhandler (test-settings :uri \"/settings\") ()\n  <settings-template>\n    content\n  </settings-template>)\n\n\n(defhandler (render-single-settings-page :uri \"/settings/:name\"\n                                         :method :get\n                                         :want-login t) (name)\n  (let ((installation (installation)))\n   (loop for (nil . x) in (all-settings installation)\n         if (string= (settings-name x)\n                     name)\n           do (return\n                (funcall (settings-handler x)))\n         finally\n            (error \"Could not find settings page ~a\" name))))\n"
  },
  {
    "path": "src/screenshotbot/settings/shares.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/settings/shares\n  (:use #:cl\n        #:screenshotbot/settings/settings-template)\n  (:import-from #:screenshotbot/settings-api\n                #:defsettings)\n  (:import-from #:screenshotbot/user-api\n                #:current-user\n                #:user-full-name\n                #:current-company)\n  (:import-from #:screenshotbot/model/sharing\n                #:share-revoked-p\n                #:expiry-date\n                #:share-creator\n                #:shares-for-company)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:shared-report-page)\n  (:import-from #:util/object-id\n                #:oid-array)\n  (:import-from #:core/ui/taskie\n                #:taskie-list\n                #:taskie-page-title\n                #:taskie-row\n                #:timeago)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/settings/shares)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defsettings settings-shares-page\n  :name \"shares\"\n  :section :organization\n  :title \"Public Shares\"\n  :handler 'shares-page)\n\n(defun shares-page ()\n  <settings-template>\n    <taskie-page-title title= \"Public Shared URLs\" >\n    </taskie-page-title>\n\n       <p class= \"mt-3 mb-2 text-muted\" >Reports can be publicly shared from the report page. Use to share reports with stakeholders who are not on Screenshotbot.</p>\n\n      <p class= \"text-muted\" >Keep track of all the shared URLs here, and revoke them if necessary.</p>\n\n\n\n    ,(taskie-list\n      :items (shares-for-company (current-company))\n      :headers '(\"URL\" \"Shared by\" \"Expires\" \"Actions\")\n      :checkboxes nil\n      :empty-message \"No shares yet. Create a new share from any report.\"\n      :row-generator (lambda (share)\n                       (let* ((url (hex:make-full-url hunchentoot:*request*\n                                                      'shared-report-page\n                                                      :eoid (encrypt:encrypt-mongoid (oid-array share))))\n                              (expiry-date (expiry-date share))\n                              (expiry-ts (unless (str:emptyp expiry-date)\n                                          (local-time:parse-timestring expiry-date))))\n                         <taskie-row>\n                         <span><a href=url >,(reverse (str:shorten 40 (reverse url))) </a></span>\n                         <span>,(user-full-name (share-creator share))</span>\n                         <span>,(cond\n                                  ((share-revoked-p share)\n                                 <span class= \"text-warning\">Revoked</span>)\n                                ((null expiry-ts)\n                                 \"Never\")\n                                ((local-time:timestamp< expiry-ts\n                                                        (local-time:now))\n                                 <span>Expired ,(timeago :timestamp expiry-ts)</span>)\n                                (t\n                                 (timeago :timestamp expiry-ts))) </span>\n                         <span>\n                           ,(cond\n                              ((and expiry-ts\n                                    (local-time:timestamp< expiry-ts\n                                                           (local-time:now)))\n                               nil)\n                              ((not (share-revoked-p share))\n                               <a href= (nibble () (revoke-share share)) >Revoke</a>)\n                              (t\n                               <a href= (nibble () (undo-revoke-share share))>Undo Revoke</a>))\n                         </span>\n                         </taskie-row>\n                         )))\n\n  </settings-template>)\n\n(defun revoke-share (share)\n  (with-transaction ()\n    (setf (share-revoked-p share) t))\n  (hex:safe-redirect \"/settings/shares\"))\n\n(defun undo-revoke-share (share)\n  (with-transaction ()\n    (setf (share-revoked-p share) nil))\n  (hex:safe-redirect \"/settings/shares\"))\n"
  },
  {
    "path": "src/screenshotbot/settings/test-security.lisp",
    "content": "(defpackage :screenshotbot/settings/test-security\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:screenshot-static-page)\n  (:import-from #:screenshotbot/settings/security\n                #:settings-security-page)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/settings/test-security)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key oauth)\n  (let ((auth::*iterations* 100))\n   (with-installation ()\n     (with-test-store ()\n       (with-test-user (:user user :logged-in-p t)\n         (unless oauth\n           (with-transaction ()\n             (setf (auth:user-password user) \"foobar\")))\n         (&body))))))\n\n(test simple-screenshot\n  (with-fixture state ()\n   (screenshot-static-page\n    :screenshotbot\n    \"security-settings-page\"\n    (settings-security-page))))\n\n(test simple-screenshot-for-success\n  (with-fixture state ()\n   (screenshot-static-page\n    :screenshotbot\n    \"security-settings-page-for-success-changed\"\n    (settings-security-page :success t))))\n\n(test simple-screenshot-for-oauth\n  (with-fixture state (:oauth t)\n   (screenshot-static-page\n    :screenshotbot\n    \"security-settings-page-for-oauth\"\n    (settings-security-page))))\n"
  },
  {
    "path": "src/screenshotbot/settings-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/settings-api\n    (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/installation\n                #:plugin\n                #:installation\n                #:plugins)\n  (:import-from #:screenshotbot/server\n                #:staging-p)\n  (:export\n   #:defsettings\n   #:settings\n   #:all-settings\n   #:settings-name\n   #:settings-handler\n   #:plugin-settings\n   #:settings-title\n   #:settings-section)\n  (:export #:settings-template))\n(in-package :screenshotbot/settings-api)\n\n(defclass settings ()\n  ((name :type string\n         :initarg :name\n         :accessor settings-name)\n   (title :type string\n          :initarg :title\n          :accessor settings-title)\n   (plugin :initarg :plugin)\n   (staging-p :initarg :staging-p\n              :initform nil\n              :reader settings-only-staging-p)\n   (section :type symbol\n            :initarg :section\n            :accessor settings-section)\n   (handler :type (or symbol function)\n            :initarg :handler\n            :accessor settings-handler)))\n\n(defvar *settings* nil)\n\n(defmacro defsettings (name &rest body &key plugin &allow-other-keys)\n  (when plugin\n    (unless (eql 'quote (car plugin))\n      (error \"Expected form to be of type (quote foo): ~S\" plugin))\n    (assert (eql 2 (length plugin)))    )\n  (cond\n    (plugin\n     `(defmethod plugin-settings ((plugin ,(cadr plugin)))\n        (list\n         (cons\n          ',name\n          (make-instance 'settings\n                          ,@body)))))\n    (t\n     `(setf (assoc-value *settings* ',name)\n            (make-instance 'settings\n                            ,@body)))))\n\n(defmethod plugin-settings ((plugin plugin))\n  nil)\n\n(defmethod should-show-settings-p (installation setting-name)\n  t)\n\n(defmethod all-settings ((installation installation))\n  (remove-if\n   (lambda (settings)\n     (or\n      (not (should-show-settings-p installation (car settings)))\n      (and\n       (settings-only-staging-p (cdr settings))\n       (not (staging-p)))))\n   (remove-if #'null\n    (append\n     *settings*\n     (reverse\n      (loop for plugin in (plugins installation)\n            appending\n            (plugin-settings plugin)))))))\n"
  },
  {
    "path": "src/screenshotbot/setup-oss.sh",
    "content": "#!/bin/sh\n\napt-get update\napt-get update && apt-get install -y libyaml-dev git-core libpng-dev zlib1g-dev libpng16-16  zlib1g gcc makeself exiftool build-essential logrotate sbcl nginx certbot python3-certbot-nginx pkg-config\n\nwget https://screenshotbot-assets.s3.us-east-1.amazonaws.com/imagemagick7_7.1.1-39_amd64.deb\napt-get install -y ./imagemagick7_7.1.1-39_amd64.deb\napt-get install -y openjdk-17-jre-headless\n\nadduser --disabled-password --gecos \"\" screenshotbot\n\ncd /home/screenshotbot\n\nSB=\"sudo -u screenshotbot\"\n\n$SB git clone https://github.com/screenshotbot/screenshotbot-oss \n\ncat >/etc/systemd/system/screenshotbot.service <<EOF\n\n[Unit]\nDescription=Screenshotbot\nAfter=network.target\nStartLimitIntervalSec=0\n\n[Service]\nType=simple\nRestart=always\nRestartSec=5\nUser=screenshotbot\nNotifyAccess=all\nWorkingDirectory=/home/screenshotbot/screenshotbot-oss\nExecStart=/usr/bin/env sbcl --script launch.lisp --start-slynk --slynk-port 4005\n\n[Install]\nWantedBy=multi-user.target\n\n\nEOF\n\nsystemctl daemon-reload\nsystemctl enable screenshotbot\nsystemctl start screenshotbot\n\nIP=`curl -4 ident.me`\n\necho \"The IP address of this machine is: $IP\"\necho \"Use this to create a domain name that points to $IP\"\nprintf  \"Type the domain name here: \"\n\nread DOMAIN\n\necho \"We'll use domain '$DOMAIN' to set up HTTPS via Let's Encrypt.\"\n\ncat >/etc/nginx/sites-available/screenshotbot.conf <<EOF\nupstream screenshotbot {\n    server 127.0.0.1:4091;\n}\n\n\nserver {\n  server_name $DOMAIN;\n\n  proxy_set_header Host \\$host:\\$server_port;\n  proxy_set_header X-Real-IP \\$remote_addr;\n  proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n  # When using behind a ALB, this is the easiest way to ensure that\n  # proto is set correctly.\n  proxy_set_header X-Forwarded-Proto \\$scheme;\n  proxy_next_upstream error timeout http_502 http_503;\n\n  location /wsapp/ {\n      proxy_pass http://screenshotbot;\n      proxy_http_version 1.1;\n      proxy_set_header Upgrade \\$http_upgrade;\n      proxy_set_header Connection \"Upgrade\";\n      proxy_set_header Host \\$host;\n  }\n\n  location / {\n      proxy_pass http://screenshotbot;\n      proxy_redirect http://\\$proxy_host:\\$proxy_port /;\n      proxy_next_upstream error timeout http_502 http_503;\n  }\n\n  gzip on;\n  gzip_vary on;\n  gzip_proxied any;\n  gzip_min_length 256;\n  gzip_comp_level 6;\n  gzip_buffers 16 8k;\n  gzip_http_version 1.1;\n  gzip_types text/plain text/html text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\n\n  listen 80;\n  listen [::]:80;  \n}\nEOF\n\nln -s /etc/nginx/sites-available/screenshotbot.conf /etc/nginx/sites-enabled/screenshotbot.conf\n\nsystemctl restart nginx\n\necho\necho\necho \"We'll now run Certbot to set up HTTPS. Please follow the prompts.\"\necho \"If certbot fails, you don't have to re-run this script again. You can just run\"\necho \"   certbot run --domains $DOMAIN\"\necho\necho\n\ncertbot run --domains $DOMAIN\n\necho\necho\necho \"And we're done. You should soon be able to access https://$DOMAIN (it may take a few minutes for the the compilation to complete. You can use `journalctl -u screenshotbot -f` to track the progress.)\"\n"
  },
  {
    "path": "src/screenshotbot/showkase/.gitignore",
    "content": "lispout"
  },
  {
    "path": "src/screenshotbot/showkase/Makefile",
    "content": "include ../../../scripts/common.mk\n\nCHRONIC=cd ../../../ &&\nQEMUROOT=/opt/software/qemu\nLWANDROIDROOT=/opt/software/lw-android\nVERSION=8-1-0\n\nLISPOUT=instr\nMAIN=$(LISPOUT)/app/src/main\nLIB=libsbshowkase.so\n\nDELIVER=src/screenshotbot/showkase/deliver-showkase-libs.lisp\n\n.PHONY:\n\n$(MAIN): .PHONY\n\tmkdir -p $@\n\n$(TEST): .PHONY\n\tmkdir -p $@\n\n$(LISPOUT):$(MAIN) .PHONY\n\ttouch $(MAIN)/AndroidManifest.xml\n\ttouch $(LISPOUT)/build.gradle\n\ttouch $(LISPOUT)/app/build.gradle\n\n$(MAIN)/assets/$(LIB).x86_64.lwheap: $(LISPOUT) .PHONY\n\t@$(CHRONIC) $(LWIMAGEAMD64) -build $(DELIVER) && echo \"Built: android/x86_64\"\n\ttest -f $@\n\n$(MAIN)/jniLibs/x86_64/$(LIB): $(MAIN)/assets/$(LIB).x86_64.lwheap\n\n\n$(MAIN)/assets/$(LIB).x86.lwheap: $(LISPOUT) .PHONY\n\t@$(CHRONIC) $(LWIMAGEX86) -build $(DELIVER) && echo \"Built: android/x86\"\n\n\n$(MAIN)/jniLibs/x86/$(LIB): $(MAIN)/assets/$(LIB).x86.lwheap\n\n\n$(MAIN)/assets/$(LIB).armeabiv7a.lwheap: $(LISPOUT) .PHONY\n\t@$(CHRONIC) \"$(qemubinarm32)\" -L \"$(QEMUROOT)/arm-linux-libs\" \"$(LWIMAGEARM)\" -build $(DELIVER) && echo \"Built: android/arm32\" && echo \"Built: android/arm32\"\n\n\n$(MAIN)/jniLibs/armeabi-v7a/$(LIB): $(MAIN)/assets/$(LIB).armeabiv7a.lwheap\n\n\n$(MAIN)/assets/$(LIB).arm64v8a.lwheap: $(LISPOUT) .PHONY\n\t@$(CHRONIC) \"$(qemubinarm64)\" -L \"$(QEMUROOT)/arm-linux-libs\" \"$(LWIMAGEARM64)\" -build $(DELIVER) && echo \"Built: android/arm64\"\n\n$(MAIN)/jniLibs/arm64-v8a/$(LIB): $(MAIN)/assets/$(LIB).arm64v8a.lwheap\n\n$(MAIN)/jniLibs/x86_64/$(LIB): $(MAIN)/assets/$(LIB).x86_64.lwheap\n\nall-except-x86_64: $(MAIN)/jniLibs/x86/$(LIB) $(MAIN)/jniLibs/arm64-v8a/$(LIB) $(MAIN)/jniLibs/armeabi-v7a/$(LIB)\n\nx86_64: $(MAIN)/jniLibs/x86_64/$(LIB)\n\n$(TEST)/jniLibs/x86_64/$(LIB): $(TEST)/assets/$(LIB).x86_64.lwheap\n\nclean:\n\tfind $(LISPOUT) -name '*.lwheap' -delete\n\tfind $(LISPOUT) -name '*.so' -delete\n\trm -rf testout\n\ntest-imgs: $(TEST)/assets/$(LIB).x86_64.lwheap\n"
  },
  {
    "path": "src/screenshotbot/showkase/deliver-showkase-libs.lisp",
    "content": "(defpackage :screenshotbot/showkase/deliver-showkase-libs\n  (:use #:cl\n        #:lw))\n(in-package :screenshotbot/showkase/deliver-showkase-libs)\n\n(format t \"Features: ~S\" *features*)\n\n(load \"scripts/prepare-image.lisp\")\n(load \"scripts/init.lisp\")\n\n(ql:quickload :screenshotbot.showkase)\n\n(defvar *release-build* nil)\n(defvar *project-path*\n  (merge-pathnames #P\"src/screenshotbot/showkase/instr/\" (hcl:get-working-directory))\n  \"Points to the directory where the OthelloDemo project is\")\n\n(hcl:deliver-to-android-project 'screenshotbot/showkase/main:main\n                                *project-path*\n                                (if *release-build* 3 0)\n                                :keep-symbols nil\n                                :library-name \"sbshowkase\"\n                                :keep-macros t\n                                :keep-pretty-printer t\n                                :keep-debug-mode (unless *release-build* :all))\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\n.cxx\n*.lwheap\n*.so\n*.zaps"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/build.gradle",
    "content": "plugins {\n    id 'org.jetbrains.kotlin.android' version '1.9.10'\n}\n\napply plugin: 'com.android.application'\n\n\n\nandroid {\n    compileSdkVersion 34\n    buildToolsVersion \"33.0.2\"\n    defaultConfig {\n        applicationId \"com.example.project\"\n        minSdkVersion 28\n        targetSdkVersion 33\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility = 17\n        targetCompatibility = 17\n    }\n}\n\ndependencies {\n    implementation(\"androidx.activity:activity:1.8.0\")\n    implementation 'androidx.appcompat:appcompat:1.6.1'\n    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'\n    implementation 'com.facebook.testing.screenshot:core:0.15.0'\n    implementation 'androidx.compose.ui:ui-test-junit4:1.5.4'\n    implementation \"androidx.lifecycle:lifecycle-runtime-testing:2.7.0-alpha03\"\n    implementation(\"androidx.activity:activity-compose:1.8.0\")\n\n    implementation platform('androidx.compose:compose-bom:2023.03.00')\n\n    testImplementation 'junit:junit:4.13'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.1'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'\n    implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])\n    //implementation 'com.backblaze.b2:b2-sdk-core:3.1.0'\n    //implementation 'com.backblaze.b2:b2-sdk-okhttp:3.1.0'\n    implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'\n\n    implementation files('libs/lispworks.aar')\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.project\">\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n\n    <application\n        android:allowBackup=\"true\"\n        android:extractNativeLibs=\"true\"\n        android:debuggable=\"true\"\n        android:vmSafeMode=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\" android:exported=\"true\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n    <instrumentation android:name=\"io.screenshotbot.SbInstrumentation\"\n                     android:targetPackage=\"com.airbnb.android.showkasesample\" />\n\n</manifest>\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/ContainerFragment.java",
    "content": "// package io.screenshotbot;\n// import androidx.compose.ui.platform.ComposeView;\n\n// public class ContainerFragment extends Fragment {\n//     public View mView;\n\n//     @Override\n//     public View onCreateView() {\n//         if (mView != null) {\n//             mView = new ComposeView(getContext(), null, 0);\n//         }\n//         return mView;\n//     }\n// }\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/Factory.kt",
    "content": "package io.screenshotbot\n\nimport android.graphics.Bitmap\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.test.junit4.ComposeTestRule\nimport androidx.compose.ui.test.junit4.createComposeRule\nimport androidx.compose.ui.test.junit4.createEmptyComposeRule\nimport androidx.compose.ui.test.junit4.AndroidComposeTestRule.*\nimport org.junit.runners.model.Statement\nimport androidx.activity.compose.setContent\nimport com.facebook.testing.screenshot.Screenshot\nimport org.junit.runner.Description\nimport android.app.Activity\nimport android.view.*\nimport android.graphics.*\n\n\nfun createTestRule() : ComposeTestRule {\n    return createComposeRule()\n}\n\nfun createEmptyTestRule() : ComposeTestRule {\n    return createEmptyComposeRule()\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/LispworksManager.java",
    "content": "package io.screenshotbot;\n\nimport android.app.Activity;\nimport android.app.Instrumentation;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.os.Debug;\nimport android.util.Log;\n//import com.lispworks.Manager;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.lang.reflect.Constructor;\n\nimport android.content.Context;\nimport android.util.Log;\nimport com.lispworks.LispCalls;\nimport com.lispworks.Manager;\nimport com.lispworks.Manager.LispErrorReporter;\nimport java.io.File;\nimport java.io.InputStream;\nimport io.screenshotbot.LispworksManager;\n\npublic class LispworksManager {\n    public static void initLispworks(Context targetContext, Context context, Runnable callback) {\n        //System.loadLibrary(\"sbshowkase\");\n        File dataDir = targetContext.getDir(\"lispworks\", 0);\n        Manager.setRuntimeLispHeapDir(dataDir.toString());\n        Manager.setLispTempDir(targetContext.getCacheDir().toString());\n        Manager.setClassLoader(LispworksManager.class.getClassLoader());\n\n        assert(context != null);\n\n        Manager.setErrorReporter(new LispErrorReporter() {\n                @Override\n                public boolean report(String errorString, String filename)  {\n                    Log.i(\"SbInss\", \"LispWorks Error from \" + filename + \": \" + errorString);\n                    // System.exit(1);\n                    return false;\n                }\n            });\n\n        int ret = Manager.init(context, \"sbshowkase\", new Runnable () {\n                @Override\n                public void run() {\n                    if (Manager.status() < 0) {\n                        Log.i(\"SbInss\", \"LispWorks failed to load: \" + Manager.status() + \" \" + Manager.init_result_code());\n                    }\n\n                    Log.i(\"SbInss\", \"lispworks mInitString is: \" + String.valueOf(Manager.mInitErrorString));\n\n                    Log.i(\"SbInss\", \"callback called2\");\n                    try {\n                        Object res = LispCalls.callObjectV(\"SCREENSHOTBOT/SHOWKASE/MAIN::SAMPLE\");\n                        Log.i(\"SbInss\", \"after the call\");\n                        Log.i(\"SbInss\", \"Got first response from lisp: \" + res);\n                    } catch (Exception e) {\n                        Log.i(\"SbInss\", \"Got error\" + e);\n                    }\n\n                    callback.run();\n\n                }\n            });\n\n        Log.i(\"SbInss\", \"lispworks inited with \" + ret);\n        // try {\n        //     Thread.sleep(20);\n        // } catch (InterruptedException e) {\n        //     throw new RuntimeException(e);\n        // }\n\n        //Log.i(\"SbInss\", \"calling into lisp\");\n\n        //Object res = LispCalls.callObjectV(\"cl:+\", 2, 2);\n        //Log.i(\"SbInss\", \"Got first response from lisp: \" + res);\n\n        //startSwank(4005);\n    }\n\n    public static void startSwank(int port) {\n        Object res = LispCalls.callObjectV(\"screenshotbot/showkase/main:start-slynk\", port);\n        if (!\"success\".equals(res)) {\n            throw new RuntimeException(\"Could not load swank\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/MainActivity.java",
    "content": "package com.example.project;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\n\npublic class MainActivity extends AppCompatActivity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n\n     }\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/PrimitiveWrapper.java",
    "content": "// Copyright 2018-Present Modern Interpreters Inc.\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\npackage com.tdrhq;\n\npublic class PrimitiveWrapper {\n    private Object obj;\n    public PrimitiveWrapper(Object obj) {\n        this.obj = obj;\n    }\n\n    /**\n     *\n     */\n    public int getType() {\n        if (obj instanceof Boolean) {\n            return 1;\n        } else if (obj instanceof Character) {\n            return 2;\n        }\n\n        throw new RuntimeException(\"unsupported type:\" + obj);\n     }\n\n    public Boolean asBoolean() {\n        return (Boolean) obj;\n    }\n\n    public Character asCharacter() {\n        return (Character) obj;\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/SbInstrumentation.java",
    "content": "package io.screenshotbot;\n\nimport android.app.Activity;\nimport android.app.Instrumentation;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.os.Debug;\nimport android.util.Log;\n//import com.lispworks.Manager;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.lang.reflect.Constructor;\n\nimport android.content.Context;\nimport android.util.Log;\nimport com.lispworks.LispCalls;\nimport com.lispworks.Manager;\nimport com.lispworks.Manager.LispErrorReporter;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.io.*;\nimport android.os.*;\nimport android.graphics.*;\n\nimport com.facebook.testing.screenshot.ScreenshotRunner;\nimport org.junit.*;\nimport org.junit.runners.model.*;\nimport org.junit.runner.*;\nimport org.junit.rules.*;\nimport androidx.compose.ui.test.junit4.ComposeTestRule;\n\npublic class SbInstrumentation extends Instrumentation {\n    @Override\n    public void onCreate(Bundle arguments) {\n        Log.i(\"SbInss\", \"onCreate called\");\n        super.onCreate(arguments);\n\n        ScreenshotRunner.onCreate(this, arguments);\n\n        Context targetContext = getTargetContext();\n        Context context = getContext();\n        LispworksManager.initLispworks(getTargetContext(), getContext(),\n                                       new Runnable() {\n                                           @Override\n                                           public void run() {\n                                               if (Looper.myLooper() == Looper.getMainLooper()) {\n                                                   Log.i(\"SbInss\", \"callback is running on main thread\");\n                                               }\n                                               Thread th = new Thread() {\n                                                       @Override\n                                                       public void run() {\n                                                           LispCalls.callObjectV(\"SCREENSHOTBOT/SHOWKASE/MAIN::RUN\", context, targetContext, SbInstrumentation.this);\n                                                           Log.i(\"SbInss\", \"Done initing slynk\");\n                                                       }\n                                                   };\n                                               th.start();\n                                               //finish(0, new Bundle());\n                                           }\n                                       }\n\n                                       );\n\n        start();\n    }\n\n    public void writeBitmap(Bitmap bitmap, String file) throws IOException {\n        try (OutputStream os = new FileOutputStream(file)) {\n            bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        Log.i(\"SbInss\", \"onStart\");\n\n        Looper looper = Looper.getMainLooper();\n        Handler handler = new Handler(looper);\n        handler.post(new Runnable() {\n                @Override\n                public void run() {\n                    Log.i(\"SbInss\", \"handler called\");\n                    Log.i(\"SbInss\", \"waiting 2 s\");\n                }\n            });\n\n        try {\n            Thread.sleep(2000);\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        ScreenshotRunner.onDestroy();\n        super.onDestroy();\n    }\n\n    public void applyTestRule(TestRule testRule, Runnable runnable) throws Throwable {\n        Statement stmt = new Statement() {\n                @Override\n                public void evaluate() {\n                    runnable.run();\n                }\n            };\n        testRule.apply(stmt, Description.EMPTY).evaluate();\n    }\n\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/SimpleNativeLibrary.java",
    "content": "// Copyright 2018-Present Modern Interpreters Inc.\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\npackage com.tdrhq;\n\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Serializable;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.logging.Logger;\nimport org.apache.commons.lang3.reflect.ConstructorUtils;\nimport org.apache.commons.lang3.reflect.FieldUtils;\n\npublic class SimpleNativeLibrary implements Serializable {\n    private static final long serialVersionUID = -1372385985412438808L;\n    public static Object bfalse = new Object();\n    public static Object btrue = new Object();\n\n    public SimpleNativeLibrary() {\n    }\n\n    public static void copyResource(String resourceName, String outputFile) throws Exception {\n        InputStream is = SimpleNativeLibrary.class.getResourceAsStream(resourceName);\n        if (is == null) {\n            throw new RuntimeException(\"Could not find resource: \" + resourceName);\n        }\n        OutputStream os = new FileOutputStream(outputFile);\n\n        byte[] buff = new byte[4096];\n        int len;\n        while ((len = is.read(buff)) > 0) {\n            os.write(buff, 0, len);\n        }\n\n        os.close();\n        is.close();\n    }\n\n    public static Object send_static_method_wrapped(Class klass, String methodName, Object[] args) throws Exception {\n        Object ret = send_static_method(klass, methodName, args);\n        return wrapPrimitives(ret);\n    }\n\n    public static Object wrapPrimitives(Object ret) {\n        if (ret == null) {\n            return ret;\n        }\n\n        if (ret instanceof Boolean) {\n            return new PrimitiveWrapper(ret);\n        }\n\n        if (ret instanceof Character) {\n            return new PrimitiveWrapper(ret);\n        }\n\n        return ret;\n    }\n\n    public static Object send_static_method(Class klass, String methodName, Object[] args) throws Exception {\n        if (klass == null) {\n            throw new RuntimeException(\"null class for \" + methodName);\n        }\n        if (args == null) {\n            //            args = new Object[0];\n        }\n        try {\n            Object ret =  Whitebox.invokeStaticMethod(klass, methodName, args);\n            return ret;\n        } catch (NoSuchMethodException e) {\n            throw new RuntimeException(e);\n        } catch (InvocationTargetException e) {\n            if (e.getTargetException() instanceof Exception) {\n                throw (Exception) e.getTargetException();\n            }\n\n            e.printStackTrace();\n            throw new RuntimeException(e);\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n\n    public static Object send_method(Object o, String methodName, Object[] args) throws Throwable {\n        try {\n            return Whitebox.invokeMethod(o, methodName, args);\n        } catch (NoSuchMethodException e) {\n            e.printStackTrace();\n            throw new RuntimeException(e);\n        } catch (InvocationTargetException e) {\n            e.printStackTrace();\n            throw e.getCause();\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n            throw new RuntimeException(e);\n        }\n    }\n\n\n    public static Object send_method_wrapped(Object o, String methodName, Object[] args) throws Throwable {\n        return wrapPrimitives(send_method(o, methodName, args));\n    }\n\n    public static Object newInstance(Class klass, Object[] args)\n        throws Throwable {\n        try {\n            return ConstructorUtils.invokeConstructor(klass, args);\n        } catch (InvocationTargetException e) {\n            throw e.getCause();\n        }\n    }\n\n    public static Object newInstance_wrapped(Class klass, Object[] args)\n        throws Throwable {\n        if (args == null) {\n            System.out.println(\"got null args\");\n            args = new Object[0];\n        }\n        if (klass == null) {\n            throw new RuntimeException(\"klass is null\");\n        }\n\n        try {\n            return wrapPrimitives(newInstance(klass, args));\n        } catch (Exception e) {\n            e.printStackTrace();\n            throw e;\n        }\n    }\n\n    public static Object getLogger() {\n        return Logger.getLogger(\"io.jipr.lisp\");\n    }\n\n    public static Character charFromCodePoint(char ch) {\n        return Character.valueOf(ch);\n    }\n\n    public static Object readStaticField(Class klass, String field) throws Exception {\n        return FieldUtils.readStaticField(klass, field, true);\n    }\n\n    public static Object writeStaticField(Class klass, String field, Object val) throws Exception {\n        FieldUtils.writeStaticField(klass, field, val);\n        return val;\n    }\n\n    public static Object readField(Object o, String field) throws Exception {\n        return FieldUtils.readField(o, field, true);\n    }\n\n    public static Object writeField(Object o, String field, Object val) throws Exception {\n        FieldUtils.writeField(o, field, val);\n        return val;\n    }\n\n    public static void throwJavaException(Exception e) throws Exception {\n        throw e;\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/ViewOwner.java",
    "content": "// package io.screenshotbot;\n\n// import androidx.lifecycle.testing.*;\n// import androidx.savedstate.*;\n\n// class ViewOwner extends TestLifecycleOwner implements SavedStateRegistryOwner {\n//     @Override\n//     public SavedStateRegistry getSavedStateRegistry() {\n//         return null;\n//     }\n// }\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/ViewOwners.java",
    "content": "package io.screenshotbot;\n\nimport androidx.activity.OnBackPressedDispatcher;\nimport androidx.activity.OnBackPressedDispatcherOwner;\nimport androidx.lifecycle.Lifecycle;\nimport androidx.lifecycle.LifecycleOwner;\nimport androidx.lifecycle.LifecycleRegistry;\nimport androidx.savedstate.SavedStateRegistry;\nimport androidx.savedstate.SavedStateRegistryController;\nimport androidx.savedstate.SavedStateRegistryOwner;\n\npublic class ViewOwners {\n    public static class MyLifecycleOwner implements LifecycleOwner {\n        private Lifecycle registry = null;\n\n        public MyLifecycleOwner() {\n            registry = new LifecycleRegistry(this);\n        }\n\n        @Override\n        public Lifecycle getLifecycle() {\n            return registry;\n        }\n\n    }\n\n    public static class MySavedStateRegistryOwner implements SavedStateRegistryOwner, LifecycleOwner {\n        private SavedStateRegistryController controller;\n        private SavedStateRegistry registry;\n        private LifecycleOwner lifecycleOwner;\n\n        public MySavedStateRegistryOwner(LifecycleOwner _lifecycleOwner) {\n            this.lifecycleOwner = _lifecycleOwner;\n            controller = SavedStateRegistryController.create(this);\n            controller.performRestore(null);\n\n            registry = controller.getSavedStateRegistry();\n\n        }\n\n        @Override\n        public SavedStateRegistry getSavedStateRegistry() {\n            return registry;\n        }\n\n        @Override\n        public Lifecycle getLifecycle() {\n            return lifecycleOwner.getLifecycle();\n        }\n    }\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/java/io/screenshotbot/Whitebox.java",
    "content": "// Copyright 2018-Present Modern Interpreters Inc.\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\npackage com.tdrhq;\n\nimport java.lang.reflect.Array;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.ClassUtils;\nimport org.apache.commons.lang3.reflect.MethodUtils;\n\nclass Whitebox {\n    public final static Class[] EMPTY_CLASSES = new Class[0];\n\n    public static class CacheKey {\n        public final Class klass;\n        public final String method;\n        public final Class[] args;\n\n        public CacheKey(Class klass, String method, Class[] args) {\n            this.klass = klass;\n            this.method = method;\n            this.args = args;\n        }\n\n        @Override\n        public boolean equals(Object _other) {\n            CacheKey other = (CacheKey) _other;\n            return klass.equals(other.klass) &&\n                    method.equals(other.method) &&\n                    Arrays.equals(args, other.args);\n        }\n\n        @Override\n        public int hashCode() {\n            return klass.hashCode() | method.hashCode() | Arrays.hashCode(args);\n        }\n    }\n\n    static Map<CacheKey, Method> methodCache = new ConcurrentHashMap<>();\n\n    static Object invokeMethod(Object o, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n        Method method = getMatchingAccessibleMethod(o.getClass(), methodName, args);\n        if (method == null) {\n            throw new RuntimeException(\"couldn't find an appropriate method\");\n        }\n\n        // This *shouldn't* be needed, but the JVM causes some problems in particular\n        // with some methods in ArrayList$Itr, oh well.\n        method.setAccessible(true);\n\n        return method.invoke(o, toVarArgs(method, args));\n    }\n\n    static Method getMatchingAccessibleMethod(Class klass, String methodName, Object[] args) {\n        Class[] classes = EMPTY_CLASSES;\n\n        if (args.length > 0) {\n            classes = new Class[args.length];\n            for (int i = 0; i < args.length; i++) {\n                classes[i] = (args[i] == null) ? null : args[i].getClass();\n            }\n        }\n        CacheKey cacheKey = new CacheKey(klass, methodName, classes);\n        Method old = methodCache.get(cacheKey);\n        if (old == null) {\n            old = MethodUtils.getMatchingAccessibleMethod(klass, methodName, classes);\n            if (old == null) {\n                String str = \"could not find appropriate method for: \" + klass\n                        + \", \" + methodName + \" and args: \";\n                for (Class param : classes) {\n                    str += param.getName() + \", \";\n                }\n                str = str.substring(0, str.length() - 2);\n                throw new RuntimeException(str);\n            }\n            methodCache.put(cacheKey, old);\n        }\n        return old;\n    }\n\n    static Object invokeStaticMethod(Class klass, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n        Method method = getMatchingAccessibleMethod(klass, methodName, args);\n\n        if (method == null) {\n            throw new RuntimeException(\"couldn't find an appropriate method\");\n        }\n\n        return method.invoke(null, toVarArgs(method, args));\n    }\n\n\n\n    private static Object[] toVarArgs(final Method method, Object[] args) {\n        if (method.isVarArgs()) {\n            final Class<?>[] methodParameterTypes = method.getParameterTypes();\n            args = getVarArgs(args, methodParameterTypes);\n        }\n        return args;\n    }\n\n    /**\n     * <p>Given an arguments array passed to a varargs method, return an array of arguments in the canonical form,\n     * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type.\n     * </p>\n     *\n     * @param args the array of arguments passed to the varags method\n     * @param methodParameterTypes the declared array of method parameter types\n     * @return an array of the variadic arguments passed to the method\n     * @since 3.5\n     */\n    static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {\n        if (args.length == methodParameterTypes.length\n                && args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) {\n            // The args array is already in the canonical form for the method.\n            return args;\n        }\n\n        // Construct a new array matching the method's declared parameter types.\n        final Object[] newArgs = new Object[methodParameterTypes.length];\n\n        // Copy the normal (non-varargs) parameters\n        System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);\n\n        // Construct a new array for the variadic parameters\n        final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();\n        final int varArgLength = args.length - methodParameterTypes.length + 1;\n\n        Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);\n        // Copy the variadic arguments into the varargs array.\n        System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);\n\n        if(varArgComponentType.isPrimitive()) {\n            // unbox from wrapper type to primitive type\n            varArgsArray = ArrayUtils.toPrimitive(varArgsArray);\n        }\n\n        // Store the varargs array in the last position of the array to return\n        newArgs[methodParameterTypes.length - 1] = varArgsArray;\n\n        // Return the canonical varargs array.\n        return newArgs;\n    }\n\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n\n    <TextView\n        android:id=\"@+id/main_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Hello World!\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#F44336</color>\n    <color name=\"colorPrimaryDark\">#8A0B01</color>\n    <color name=\"colorAccent\">#FD8D8D</color>\n</resources>\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Example Project</string>\n</resources>\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/build.gradle",
    "content": "\nbuildscript {\n\n    repositories {\n        google()\n        jcenter()\n\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:7.4.0'\n\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n\n        maven { url 'https://jitpack.io' }\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionUrl=https\\://services.gradle.org/distributions/gradle-8.3-bin.zip\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/gradle.properties",
    "content": "android.useAndroidX=true"
  },
  {
    "path": "src/screenshotbot/showkase/instr/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\nif $JAVACMD --add-opens java.base/java.lang=ALL-UNNAMED -version ; then\n    DEFAULT_JVM_OPTS=\"--add-opens java.base/java.lang=ALL-UNNAMED $DEFAULT_JVM_OPTS\"\nfi\n\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "src/screenshotbot/showkase/instr/settings.gradle",
    "content": "rootProject.name='Example Project'\ninclude ':app'\n"
  },
  {
    "path": "src/screenshotbot/showkase/lib.lisp",
    "content": "(defpackage :screenshotbot/showkase/lib\n  (:use #:cl\n        #:iterate\n        #:util/java)\n  (:export\n   #:*context*\n   #:*target-context*\n   #:*instrumentation*))\n(in-package :screenshotbot/showkase/lib)\n\n(named-readtables:in-readtable java-syntax)\n\n(defvar *context*)\n(defvar *target-context*)\n(defvar *instrumentation*)\n\n(defun list-methods (class)\n  (mapcar #_getName (array->list (#_getMethods class))))\n\n\n(defun make-provider (module)\n  (new-instance (lw-ji:find-java-class (format nil \"~aCodegen\" module))))\n\n(defun get-metadata (module)\n  (#_metadata (make-provider module)))\n\n(defclass component ()\n  ((name :initarg :name\n         :reader name)\n   (height :initarg :height\n           :reader height)\n   (width :initarg :width\n          :reader width)\n   (compose-component :initarg :compose-component\n                      :reader compose-component)))\n\n(defmethod make-component-view ((self component))\n  (let ((view (new-instance (lw-ji:find-java-class \"androidx.compose.ui.platform.ComposeView\")\n                            *context*\n                            nil\n                            0 #| defStyleAttr? what goes here|#))\n        (frame (new-instance #,android.widget.FrameLayout\n                             *context*)))\n\n    (#_setContent view (compose-component self))\n    (#_addView frame view)\n\n    (values frame view)))\n\n(defun get-components (module)\n  (iter (for var in-java  (#_getComponentList (get-metadata module)))\n        (collect (make-instance 'component\n                                :name (#_getComponentName var)\n                                :height (#_getHeightDp var)\n                                :width (#_getWidthDp var)\n                                :compose-component (#_getComponent var)))))\n\n\n\n(defvar *f* 0)\n\n(defun runnable-run (user-data)\n  (funcall user-data))\n\n(lw-ji:define-lisp-proxy runnable\n  (\"java.lang.Runnable\"\n   (\"run\" runnable-run :with-user-data t)))\n\n(defun make-runnable (fn)\n  (lw-ji:jobject-ensure-global\n   (lw-ji:make-lisp-proxy 'runnable :user-data fn)))\n\n\n(defun on-ui-thread (fn)\n  (let* ((looper (#_getMainLooper #,android.os.Looper))\n         (handler (new-instance #,android.os.Handler looper)))\n    (assert looper)\n    (#_post handler (make-runnable fn))))\n\n\n(defun test-stuff ()\n  (on-ui-thread (lambda ()\n                  (#_i #,android.util.Log \"SbInss\" \"hello world\")\n                  (incf *f*)))\n  (hcl:android-funcall-in-main-thread\n   (lambda ()\n     (incf *f*))))\n\n#+nil\n(mapcar #_getName (array->list (#_getParameterTypes (second (array->list (#_getDeclaredMethods (lw-ji:find-java-class \"androidx.lifecycle.ViewTreeLifecycleOwner\")))))))\n\n(lw-ji:define-java-callers \"androidx.lifecycle.ViewTreeLifecycleOwner\"\n  (set-lifecycle-owner \"set\"))\n\n(lw-ji:define-java-callers \"androidx.savedstate.ViewTreeSavedStateRegistryOwner\"\n  (set-saved-state-registry-owner \"set\"))\n\n(defun init-lifecycle-owner (view)\n  (let ((lo (new-instance #,androidx.lifecycle.testing.TestLifecycleOwner)))\n    (set-lifecycle-owner view lo)))\n\n(defun make-lifecycle-owner ()\n  (new-instance #,io.screenshotbot.ViewOwners$MySavedStateRegistryOwner\n                (new-instance #,io.screenshotbot.ViewOwners$MyLifecycleOwner)))\n\n(defun lg (msg)\n  (#_i #,android.util.Log \"SbInss\" msg))\n\n(defun screenshot (component)\n  (let* ((lock (bt:make-lock))\n         (cv (bt:make-condition-variable))\n         (res nil))\n    (bt:with-lock-held (lock)\n      (hcl:android-funcall-in-main-thread\n       (lambda ()\n         (lg \"this is being called\")\n         (setf res :bar)\n         (bt:with-lock-held (lock)\n           (multiple-value-bind (frame view)\n               (make-component-view component)\n             (let ((lo (make-lifecycle-owner)))\n               (set-lifecycle-owner frame lo)\n               (set-saved-state-registry-owner frame lo)\n               #+nil(set-lifecycle-owner view lo))\n\n             ;;(#_setContentView fragment view)\n             ;;(init-lifecycle-owner view)\n             (let ((detacher (#_dispatchAttach #,com.facebook.testing.screenshot.WindowAttachment\n                                               frame))\n                   (view-helper (#_setupView #,com.facebook.testing.screenshot.ViewHelpers\n                                             frame)))\n               (unwind-protect\n                    (progn\n                      (lg \"before setting dim\")\n                      (#_setExactWidthDp view-helper\n                                         (or\n                                          (width component)\n                                          200))\n                      (#_setExactHeightDp view-helper\n                                          (or\n                                           (height component)\n                                           200))\n\n                      (lg \"After setting dims\")\n\n                      (setf res :car)\n                      (setf res (#_draw (#_layout view-helper)))\n\n                      ;;(Setf res :dar)\n                      (bt:condition-notify cv))\n                 (#_detach detacher)))))))\n      (bt:condition-wait cv lock))\n    res))\n\n(defun write-bitmap (bitmap file)\n  (log:info \"Writing bitmap to ~a\" file)\n  (#_writeBitmap *instrumentation* bitmap (namestring file)))\n\n(defun get-screenshot-dir ()\n  (format nil \"~a/\" (#_toString (#_getDataDir *target-context*))))\n\n(defun launch-activity ()\n  (let ((context *target-context*))\n    (let ((intent (new-instance #,android.content.Intent\n                                context (lw-ji:find-java-class \"com.airbnb.android.showkase.ui.ShowkaseBrowserActivity\"))))\n      (#_setFlags intent 268435456 )\n      (#_putExtra intent \"SHOKASE_ROOT_MODULE\"\n                  \"com.airbnb.android.showkasesample.RootModule\")\n      (#_startActivity context intent))))\n\n(defun test-component ()\n  (car (get-components \"com.airbnb.android.showkasesample.RootModule\")))\n\n(easy-macros:def-easy-macro with-compose-test-rule (&binding rule &fn fn)\n  (let ((rule (#_createTestRule #,io.screenshotbot.Factory)))\n    (fn rule)))\n\n\n;; (Test-component)\n\n;; (write-bitmap (screenshot (test-component) (path:catfile (get-screenshot-dir) \"hello.png\"))q\n"
  },
  {
    "path": "src/screenshotbot/showkase/main.lisp",
    "content": "(defpackage :screenshotbot/showkase/main\n  (:use #:cl)\n  (:import-from #:screenshotbot/showkase/lib\n                #:*context*\n                #:*target-context*\n                #:*instrumentation*)\n  (:export\n   #:main\n   #:run\n   #:start-slynk))\n(in-package :screenshotbot/showkase/main)\n\n(defun start-slynk (port)\n  (slynk:create-server :port port\n                       :dont-close t)\n  \"success\")\n\n(defun sample ()\n  \"FOO\")\n\n(defun run (context target-context instrumentation)\n  (setf *context* context)\n  (setf *target-context* target-context)\n  (setf *instrumentation* instrumentation)\n\n  (slynk:create-server :port 4005\n                       :dont-close t)\n  (sleep 3000)\n  (format t \"Lispworks internal call SbInss\")\n  nil)\n\n(defun main ()\n  nil)\n"
  },
  {
    "path": "src/screenshotbot/showkase/screenshotbot.showkase.asd",
    "content": "(defpackage :screenshotbot/showkase/screenshotbot.showkase.asd\n  (:use #:cl\n        #:asdf))\n(in-package :screenshotbot/showkase/screenshotbot.showkase.asd)\n\n(defsystem :screenshotbot.showkase\n    :serial t\n    :depends-on (:screenshotbot.sdk/library\n                 :slynk\n                 \"slynk/arglists\"\n                 \"slynk/fancy-inspector\"\n                 \"slynk/package-fu\"\n                 \"slynk/mrepl\"\n                 \"slynk/trace-dialog\"\n                 \"slynk/profiler\"\n                 \"util/posix\"\n                 \"slynk/stickers\"\n                 \"slynk/indentation\"\n                 \"slynk/retro\"\n                 \"slynk-named-readtables\"\n                 \"util.java\"\n                 :iterate)\n    :components ((:file \"lib\")\n                 (:file \"main\")))\n"
  },
  {
    "path": "src/screenshotbot/site-admin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/site-admin\n  (:use #:cl)\n  (:import-from #:auth/request\n                #:make-default-viewer-context)\n  (:import-from #:screenshotbot/server\n                #:request)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:user)\n  (:import-from #:auth/viewer-context\n                #:site-admin-viewer-context))\n(in-package :screenshotbot/site-admin)\n\n\n(defmethod make-default-viewer-context :around ((request request)\n                                                (user user))\n  (cond\n    ((and (adminp user)\n          (auth:session-value :site-admin-privileges-enabled))\n     (make-instance 'site-admin-viewer-context :user user))\n    (t\n     (call-next-method))))\n"
  },
  {
    "path": "src/screenshotbot/slack/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/slack\n    (:use #:cl\n          #:alexandria)\n  (:use-reexport #:screenshotbot/slack/plugin))\n"
  },
  {
    "path": "src/screenshotbot/slack/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/slack/core\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/user-api\n        #:screenshotbot/model/company)\n  (:import-from #:screenshotbot/slack/plugin\n                #:slack-plugin\n                #:client-id\n                #:client-secret)\n  (:import-from #:screenshotbot/installation\n                #:with-plugin)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:hash-index\n                #:with-transaction\n                #:persistent-class)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/secret\n                #:secret)\n  (:import-from #:screenshotbot/model/auto-cleanup\n                #:register-auto-cleanup)\n  (:import-from #:screenshotbot/events\n                #:with-event)\n  (:import-from #:screenshotbot/audit-log\n                #:audit-log-error\n                #:base-audit-log\n                #:audit-logs-for-company)\n  (:import-from #:util/store\n                #:with-class-validation)\n  (:import-from #:util/store/store\n                #:defindex)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:export\n   #:slack-token\n   #:latest-slack-token\n   #:slack-error-code\n   #:slack-message-failed\n   #:slack-post-on-channel\n   #:slack-methods\n   #:audit-log\n   #:slack-audit-logs-for-company\n   #:post-on-channel-audit-log\n   #:audit-log-error)\n  ;; forward decls\n  (:export #:find-or-create-slack-config)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/slack/core)\n\n(defindex +company-index+\n  'hash-index\n  :slot-name 'company\n  :test 'eql)\n\n(with-class-validation\n (defclass slack-token (store-object)\n   ((access-token\n     :initarg :access-token\n     :initform nil\n     :reader access-token)\n    (company\n     :initarg :company\n     :accessor slack-token-company\n     :index +company-index+\n     :index-reader slack-tokens-for-company)\n    (ts\n     :initform (get-universal-time)\n     :initarg :ts\n     :reader %created-at))\n   (:metaclass persistent-class)))\n\n(with-class-validation\n (defclass audit-log (base-audit-log)\n   ((company :index-type hash-index\n             :index-reader %slack-audit-logs-for-company\n             :documentation \"Deprecated slot for company\")\n    (err :initform nil\n         :accessor %audit-log-error))\n   (:metaclass persistent-class)))\n\n(defmethod audit-log-error ((audit-log audit-log))\n  (or\n   (%audit-log-error audit-log)\n   (call-next-method)))\n\n(defmethod audit-logs-for-company :around (company (type (eql 'audit-log)))\n  (append\n   (call-next-method)\n\n   ;; TODO: This is a migration, delete after thirty days!\n   (let ((logs (%slack-audit-logs-for-company company)))\n     ;; the BKNR datastore indices is super buggy for this\n     ;; index. Probably because of the inheritence.\n     (let ((hash-table (make-hash-table)))\n       (loop for log in logs\n             unless (bknr.datastore::object-destroyed-p log)\n               do (setf (gethash log hash-table) t))\n       (sort (alexandria:hash-table-keys hash-table)\n             #'> :key #'bknr.datastore:store-object-id)))))\n\n(defun slack-audit-logs-for-company (company)\n  (audit-logs-for-company company 'audit-log))\n\n(defclass post-on-channel-audit-log (audit-log)\n  ((slack-channel :initarg :channel\n            :reader slack-channel)\n   (text :initarg :text\n         :reader slack-text)\n   ;; TODO: this is unnecessary, it's already available in\n   ;; base-audit-log.\n   (ts :initarg :ts\n       :reader %created-at))\n  (:default-initargs :ts (get-universal-time))\n  (:metaclass persistent-class))\n\n(register-auto-cleanup 'audit-log :timestamp #'%created-at)\n\n(defun latest-slack-token (company)\n  (let ((token\n         (car (sort (copy-list (slack-tokens-for-company company))\n                    #'> :key '%created-at))))\n    (when token\n     (values (access-token token) token))))\n\n(define-condition slack-error (error)\n  ((response :initarg :response\n             :reader slack-error-response)))\n\n(defmethod print-object ((e slack-error) out)\n  (with-slots (response) e\n    (format out \"Slack error with response: ~a\" response)))\n\n(defun check-slack-ok (response &optional audit-log)\n  (unless (a:assoc-value response :ok)\n    (let ((response (a:assoc-value response :error)))\n      (when audit-log\n       (with-transaction ()\n         (setf (audit-log-error audit-log) response)))\n      (error 'slack-error :response response))))\n\n(defun slack-request (&key (method :post)\n                        token\n                        parameters\n                        url)\n  (multiple-value-bind (body ret)\n      (util/request:http-request\n       (format nil \"https://slack.com~a\" url)\n       :additional-headers (when token\n                             `((\"Authorization\" . ,(format nil \"Bearer ~a\" token))))\n       :method method\n       :ensure-success t\n       :parameters parameters\n       :want-string t)\n    (let ((body (json:decode-json-from-string body)))\n      (values\n       body\n       ret))))\n\n(defun slack-post-on-channel (&key channel text token company\n                                blocks\n                                (unfurl-urls nil))\n  (with-event (:slack)\n   (let ((audit-log (make-instance 'post-on-channel-audit-log\n                                   :company company\n                                   :channel channel\n                                   :text text)))\n     (let ((response (slack-request\n                      :url \"/api/chat.postMessage\"\n                      :token token\n                      :parameters\n                      (remove-if #'null `((\"channel\" . ,channel)\n                                          (\"unfurl_links\" . ,(if unfurl-urls \"true\" \"false\"))\n                                          ,(when text\n                                             `(\"text\" . ,text))\n                                          ,(when blocks\n                                             `(\"blocks\" . ,(json:encode-json-to-string blocks))))))))\n       (check-slack-ok response audit-log)))))\n\n(defun slack-app-redirect-uri ()\n  (quri:render-uri\n   (quri:make-uri\n    :path \"/slack-app-redirect\"\n    :defaults (quri:uri (installation-domain *installation*)))))\n\n(defhandler (nil :uri \"/slack-app-redirect\") (code state)\n  (declare (ignore state))\n  (with-plugin (slack-plugin)\n   (cond\n     ((null code)\n      (hex:safe-redirect \"/settings/slack\"))\n     (t\n      (multiple-value-bind (response)\n          (slack-request\n           :url \"/api/oauth.v2.access\"\n           :parameters `((\"client_id\" . ,(client-id slack-plugin))\n                         (\"client_secret\" . ,(client-secret slack-plugin))\n                         (\"code\" . ,code)\n                         (\"redirect_uri\" . ,(slack-app-redirect-uri))))\n        (check-slack-ok response)\n        (let ((access-token (assoc-value response :access--token)))\n          (assert access-token)\n          (let ((token (make-instance 'slack-token\n                                      :access-token access-token\n                                      :company (current-company))))\n            (with-transaction ()\n              (setf (access-token (find-or-create-slack-config (current-company)))\n                    token)))\n\n          (hex:safe-redirect \"/settings/slack\")))))))\n\n;; (slack-post-on-channel :channel \"#random\" :text \"foobar2\"\n;;                        :methods (slack-methods :token \"xoxb-1422927031269-1607469573089-WIpznshstwSK3yBTtnDhj4de\"))\n"
  },
  {
    "path": "src/screenshotbot/slack/plugin.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/slack/plugin\n  (:use #:cl #:alexandria)\n  (:import-from #:screenshotbot/installation\n                #:plugin\n                #:plugins\n                #:find-plugin\n                #:installation)\n  (:export\n   #:slack-plugin\n   #:client-id\n   #:client-secret))\n(in-package :screenshotbot/slack/plugin)\n\n(defclass slack-plugin (plugin)\n  ((client-id :initarg :client-id\n              :initform (error \"Need to provide :client-id for slack-plugin\")\n              :accessor client-id)\n   (client-secret :initarg :client-secret\n                  :initform (error \"Need to provide :client-secret for slack-plugin\")\n                  :accessor client-secret)))\n\n(defun slack-plugin (&key (installation (installation)))\n  (find-plugin installation 'slack-plugin))\n"
  },
  {
    "path": "src/screenshotbot/slack/rules-card.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/slack/rules-card\n  (:use #:cl)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/slack/rules\n                #:slack-channel\n                #:tag-rule-tag\n                #:tag-rule\n                #:tag-rules-for-company)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page\n                #:simple-card-page)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:util/copying\n                #:copying)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:util/form-errors\n                #:with-error-builder))\n(in-package :screenshotbot/slack/rules-card)\n\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag rules-list (&key test-slack-channel-callback)\n  <div class= \"card mt-3\">\n    <div class= \"card-header d-flex justify-content-between align-items-center\">\n      <h3>Notification Rules</h3>\n      <a class= \"btn btn-secondary\" href= (nibble () (create-rule)) >Add Rule</a>\n    </div>\n    <div class= \"card-body\">\n      ,(let ((rules (tag-rules-for-company (auth:current-company))))\n         (cond\n           ((fset:empty? rules)\n            <div class= \"d-flex justify-content-around text-muted\">\n              Click Add Rule above to create your first rule\n            </div>)\n           (t\n            (render-rules-table rules :test-slack-channel-callback test-slack-channel-callback))))\n    </div>\n  </div>)\n\n(markup:deftag delete-button (&key action)\n  <form class= \"d-inline\" >\n    <button type= \"submit\" class=\"btn btn-outline-danger\" value= \"Delete\" formaction=action >\n      <mdi name= \"delete\" /> Delete\n    </button>\n  </form>)\n\n(markup:deftag test-button (&key action)\n  <form class= \"d-inline\" >\n    <button type= \"submit\" class=\"btn btn-outline-secondary\" value= \"Test\" formaction=action >\n      <mdi name= \"play_arrow\" /> Test\n    </button>\n  </form>)\n\n\n(defun render-rules-table (rules &key test-slack-channel-callback)\n  <table class= \"table\" >\n    <thead>\n      <tr>\n        <th>Condition</th>\n        <th>Slack channel</th>\n        <th>Actions</th>\n      </tr>\n    </thead>\n    <tbody>\n    ,@(loop for rule in (fset:convert 'list rules)\n            collect\n            (copying (rule)\n              <tr>\n                <td class= \"align-middle\" >If the tag name is <tt>,(tag-rule-tag rule)</tt></td>\n                <td class= \"align-middle\" ><tt>,(str:ensure-prefix \"#\" (slack-channel rule)) </tt></td>\n                <td class= \"align-middle\" >\n                  <test-button action= (nibble () (funcall test-slack-channel-callback (slack-channel rule))) />\n                  <delete-button action= (nibble () (%delete rule)) />\n                </td>\n              </tr>))\n    </tbody>\n  </table>)\n\n(defun go-home ()\n  (hex:safe-redirect \"/settings/slack\"))\n\n(defun %delete (rule)\n  <confirmation-page yes= (nibble () (delete-object rule) (go-home))\n                     no= (nibble () (go-home))>\n    Are you sure you want to delete the rule for <tt>,(str:ensure-prefix \"#\" (slack-channel rule))</tt>?\n  </confirmation-page>)\n\n(defun create-rule ()\n  <simple-card-page form-action= (nibble (tag slack-channel :method :post) (%post tag slack-channel)) >\n    <div class= \"card-header\">\n      <h3>Add Slack Notification Rule</h3>\n    </div>\n    <div>\n      <div class= \"mb-3\">\n        <label for= \"tag\" class= \"form-label\" >If the run has the following tag:</label>\n        <input type= \"text\" name= \"tag\" id= \"tag\" class= \"form-control\" placeholder= \"my-tag\" />\n      </div>\n\n      <div class= \"mb-3\">\n        <label for= \"slack-channel\" class= \"form-label\" >... then ping to the following Slack channel:</label>\n        <input type= \"text\" name= \"slack-channel\" id= \"slack-channel\" class= \"form-control\" placeholder= \"#my-team\" />\n      </div>\n    </div>\n    <div class= \"card-footer d-flex align-items-center\">\n      <input type= \"submit\" class= \"btn btn-primary\" value= \"Save Rule\" />\n      <a href= \"#\" class= \"ms-3\" >Cancel</a>\n    </div>\n  </simple-card-page>)\n\n(defun %post (tag slack-channel)\n  (with-error-builder (:check check\n                       :errors errors\n                       :form-builder (create-rule)\n                       :form-args (:tag tag\n                                   :slack-channel slack-channel)\n                       :success (progn\n                                  (make-instance 'tag-rule\n                                                 :tag tag\n                                                 :company (auth:current-company)\n                                                 :slack-channel slack-channel)\n                                  (go-home)))\n    (check :tag (str:non-blank-string-p tag)\n           \"Must provide a tag name\")\n    (check :slack-channel (str:non-blank-string-p slack-channel)\n           \"Must provide a slack-channel name\")\n    (check :slack-channel (not (str:containsp \",\" slack-channel))\n           \"Only provide one slack-channel\")))\n"
  },
  {
    "path": "src/screenshotbot/slack/rules.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/slack/rules\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-company\n                #:recorder-run-tags\n                #:recorder-run)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index)\n  (:import-from #:util/store/store\n                #:defindex))\n(in-package :screenshotbot/slack/rules)\n\n(defindex +company-index+\n  'fset-set-index\n  :slot-name '%company)\n\n(defclass tag-rule (store-object)\n  ((%tag :initarg :tag\n         :accessor tag-rule-tag)\n   (%slack-channel :initarg :slack-channel\n                   :accessor slack-channel)\n   (%company :initarg :company\n             :reader company\n             :index +company-index+\n             :index-reader tag-rules-for-company))\n  (:metaclass persistent-class)\n  (:documentation \"If a run has a given tag, send a notification to the given channel\"))\n\n(defmethod auth:can-viewer-view (vc (self tag-rule))\n  (auth:can-viewer-view vc (company self)))\n\n(defmethod matches-rule ((self tag-rule) (run recorder-run))\n  (assert (eql (company self)\n               (recorder-run-company run)))\n  (str:s-member\n   (recorder-run-tags run)\n   (tag-rule-tag self)))\n\n(defmethod find-slack-channels-for-run ((run recorder-run))\n  \"Returns the list of slack channels. There might be duplicates if\nmultiple rules match.\"\n  (loop for tag-rule in (fset:convert 'list\n                                      (tag-rules-for-company\n                                       (recorder-run-company run)))\n        if (matches-rule tag-rule run)\n          collect (slack-channel tag-rule)))\n\n\n"
  },
  {
    "path": "src/screenshotbot/slack/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/slack/settings\n  (:use #:cl\n        #:alexandria\n        #:nibble\n        #:screenshotbot/user-api\n        #:util/form-errors\n        #:screenshotbot/installation\n        #:screenshotbot/model/company\n        #:markup\n        #:screenshotbot/slack/core\n        #:screenshotbot/settings-api)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:with-transaction)\n  (:import-from #:screenshotbot/slack/plugin\n                #:client-id\n                #:slack-plugin)\n  (:import-from #:screenshotbot/slack/core\n                #:slack-app-redirect-uri\n                #:slack-tokens-for-company\n                #:audit-log\n                #:audit-log-error\n                #:slack-channel\n                #:slack-error-response\n                #:slack-error)\n  (:import-from #:core/ui/taskie\n                #:timeago)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-page)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:core/ui/paginated\n                #:paginated)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:describe-audit-log\n                #:render-audit-logs)\n  (:import-from #:screenshotbot/slack/rules-card\n                #:rules-list)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:export #:post-settings-slack))\n(in-package :screenshotbot/slack/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun disconnect-slack (company)\n  (let ((slack-config (find-or-create-slack-config company)))\n    (mapc #'delete-object (slack-tokens-for-company company))\n    (setf (access-token slack-config) nil)))\n\n(defun slack-install-url (slack-plugin)\n  (quri:render-uri\n   (quri:make-uri\n    :query `((\"user_scope\" . \"\")\n             (\"client_id\" . ,(client-id slack-plugin))\n             (\"scope\" . \"chat:write.public,chat:write\")\n             (\"redirect_uri\" . ,(slack-app-redirect-uri)))\n    :defaults (quri:uri \"https://slack.com/oauth/v2/authorize\"))))\n\n\n(deftag add-to-slack (&key company)\n  (with-plugin (slack-plugin)\n   (let* ((slack-config (find-or-create-slack-config company))\n          (disconnect (nibble (:method :post)\n                        (confirmation-page\n                         :yes (nibble ()\n                                (disconnect-slack company)\n                                (hex:safe-redirect \"/settings/slack\"))\n                         :no \"/settings/slack\"\n                         <div>\n                           <p>Are you sure you want to disconnect the Slack connection?</p>\n\n                           <p>Reconnecting Slack will require access to your Slack workspace.</p>\n                         </div>))))\n     (cond\n       ((access-token slack-config)\n        <div class= \"form-group mb-3\">\n          <a href= disconnect class= \"btn btn-danger\" >Disconnect from Slack</a>\n        </div>)\n       (t\n        <div class= \"form-group mb-3\">\n          <a href= (slack-install-url slack-plugin) ><img alt=\"Add to Slack\" height=\"40\" width=\"139\" src=\"https://platform.slack-edge.com/img/add_to_slack.png\" srcSet=\"https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x\" /></a>\n        </div>)))))\n\n(defun slack-settings-test (&key slack-token channel)\n  (declare (optimize (debug 3)))\n  (handler-case\n      (progn\n        (slack-post-on-channel :channel channel\n                               :company (current-company)\n                               :token slack-token\n                               :text \"Test message to test Slack connection\")\n        <simple-card-page>\n          <p>Please verify that the message was sent to the slack channel!</p>\n          <a href= \"/settings/slack\">Back</a>\n        </simple-card-page>)\n    (slack-error (e)\n      <simple-card-page>\n        <p>Failed to post to slack: ,(slack-error-response e)</p>\n        <a href= \"/settings/slack\">Back</a>\n      </simple-card-page>)))\n\n(defun find-or-create-slack-config (company)\n  (util:or-setf\n   (default-slack-config company)\n   (make-instance 'slack-config\n                  :channel \"#general\")))\n\n(defun post-settings-slack (default-channel enabledp)\n  (multiple-value-bind (token-str last-token) (latest-slack-token (current-company))\n    (declare (ignore token-str))\n    (let ((errors nil))\n      (when last-token\n       (check-type last-token slack-token))\n      (flet ((check (field check message)\n               (unless check\n                 (push (cons field message)\n                       errors))))\n        (check :slack-token last-token\n               \"Please connect your Slack organization\")\n        (cond\n          (errors\n           (with-form-errors (:was-validated t\n                              :errors errors)\n             (get-settings-slack)))\n          (t\n           (with-transaction ()\n             (setf (default-slack-config\n                    (current-company))\n                   (make-instance 'slack-config\n                                   :access-token last-token\n                                   :channel default-channel\n                                   :enabledp (not (str:emptyp enabledp)))))\n           (hex:safe-redirect \"/settings/slack\")))))))\n\n\n(defun get-settings-slack ()\n  (let* ((slack-config\n           (find-or-create-slack-config (current-company)))\n         (result (nibble (slack-token default-channel enabledp :method :post)\n                   (declare (ignore slack-token))\n                   (post-settings-slack default-channel enabledp)))\n         (test-slack-channel-callback (lambda (channel)\n                                        (slack-settings-test\n                                         :slack-token (access-token (access-token slack-config))\n                                         :channel channel)))\n         (test-nibble (nibble (default-channel :method :post)\n                        (funcall test-slack-channel-callback default-channel))))\n    <settings-template>\n      <form action=result method= \"POST\">\n\n        <div class= \"card mt-3\">\n          <div class= \"card-header\">\n            <h3>Slack Integration</h3>\n          </div>\n          <div class= \"card-body\">\n\n            <add-to-slack company= (current-company) />\n\n            <div class= \"form-group mb-3\">\n              <label class= \"form-label\" for= \"default-channel\">Slack Default Channel</label>\n              <input type= \"text\" name= \"default-channel\" id= \"default-channel\"\n                     class= \"form-control\"\n                     value= (slack-config-channel slack-config) />\n            </div>\n\n            <div class= \"form-check\">\n              <input type= \"checkbox\" name= \"enabledp\" id= \"enabledp\"\n                     class= \"form-check-input\"\n                     value= (enabledp slack-config)\n                     checked= (if (enabledp slack-config) \"checked\") />\n              <label for= \"enabledp\" class= \"form-check-label\" >\n                Enable global Slack notifications\n              </label>\n\n              <p class= \"text-muted\">You can also enable Slack notifications on individual <a href= \"/channels\">Screenshotbot channels</a>.</p>\n            </div>\n\n          </div>\n\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n            <button type= \"submit\" class= \"btn btn-outline-secondary\"\n                    disabled= (unless (access-token slack-config) \"disabled\")\n                    formaction=test-nibble >\n              <mdi name= \"play_arrow\" /> Test\n            </button>\n          </div>\n\n\n        </div>\n        ,(when (gk:check :slack-rules (auth:current-company))\n           <rules-list test-slack-channel-callback=test-slack-channel-callback />)\n        \n\n        <audit-logs />\n      </form>\n    </settings-template>))\n\n(deftag audit-logs ()\n  (render-audit-logs\n   :type 'audit-log\n   :company (current-company)\n   :subtitle \"All API calls to Slack made by Screenshotbot in the last 30 days will be listed here\"))\n\n(defmethod describe-audit-log ((self post-on-channel-audit-log))\n  (cond\n    ((equal \"channel_not_found\" (audit-log-error self))\n     <span>Could not post on slack. This could be because of a typo in the name <tt>,(slack-channel self)</tt>, or because the channel is a private channel. For private channels, add the Screenshotbot Slack app to the channel and try again</span>)\n    (t\n     <span>Posted on ,(slack-channel self)</span>)))\n\n\n(defsettings slack\n  :name \"slack\"\n  :title \"Slack\"\n  :section :tasks\n  :plugin 'slack-plugin\n  :handler 'get-settings-slack)\n\n"
  },
  {
    "path": "src/screenshotbot/slack/task-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/slack/task-integration\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/task-integration-api\n        #:screenshotbot/model/channel\n        #:screenshotbot/model/report\n        #:screenshotbot/model/company\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/report\n        #:anaphora\n        #:screenshotbot/slack/core)\n  (:export\n   #:format-blame)\n  (:import-from #:screenshotbot/dashboard/reports\n                #:report-link)\n  (:import-from #:screenshotbot/slack/core\n                #:slack-error)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-slack-channels)\n  (:import-from #:screenshotbot/model/report\n                #:report-channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:recorder-run-tags)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/model/company\n                #:company-with-name)\n  (:import-from #:screenshotbot/model/user\n                #:user-personal-company)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:find-commit-path-to-ancestor)\n  (:import-from #:screenshotbot/user-api\n                #:commit-link)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:import-from #:core/installation/installation\n                #:*installation*\n                #:installation-domain)\n  (:import-from #:screenshotbot/slack/rules\n                #:find-slack-channels-for-run))\n(in-package :screenshotbot/slack/task-integration)\n\n(defclass slack-task-integration (task-integration)\n  ())\n\n(register-task-integration 'slack-task-integration)\n\n(defmethod enabledp ((inst slack-task-integration))\n  (let ((company (task-integration-company inst)))\n    (default-slack-config company)))\n\n(defun render-tags (report)\n  (let ((tags (recorder-run-tags (report-run report))))\n   (cond\n     (tags\n      (format nil \" (from run with tags ~a)\" (str:join \", \" tags)))\n     (t\n      \"\"))))\n\n(defun find-report-path (report)\n  (find-commit-path-to-ancestor\n   (report-run report)\n   (report-previous-run report)))\n\n(defun format-blame (report path)\n  (when-let* ((run (report-run report))\n              (channel (recorder-run-channel run))\n              (repo (channel-repo channel)))\n    (cond\n      ((eql 2 (length path))\n       (let ((hash (car path)))\n        (format nil \" | Blames to <~a|~a>\"\n                (commit-link repo hash)\n                (str:substring 0 8 hash))))\n      ((> (length path) 2)\n       (format nil \" | <~a|Blame commits>\"\n               (quri:merge-uris\n                (hex:make-url\n                 \"/blame/:run/to/:to\"\n                 :run (oid (report-run report))\n                 :to (oid\n                      ;; There *will* be a previous run since we found\n                      ;; a path.\n                      (report-previous-run report)))\n                (installation-domain *installation*)))))))\n\n(defun render-text (report\n                    &key (path (find-report-path report)))\n  (let ((run (report-run report))\n        (first-line (format nil \"Screenshots changed in *~a*~a\"\n                               (channel-name (report-channel report))\n                               (render-tags report)))\n        (second-line (format nil \"<~a|~a>\"\n                             (report-link report)\n                             (report-title report))))\n    (format nil \"~a~%~a~a\"\n            (cond\n              ((?. recorder-run-work-branch run)\n               (format nil \"~a on *~a*\" first-line (recorder-run-work-branch run)))\n              (t\n               first-line))\n            second-line\n            (or (format-blame report path) \"unknown\"))))\n\n(defmethod actually-post-on-channel (channel\n                                     report\n                                     &key company\n                                       (slack-config (default-slack-config company)))\n  (unless (str:emptyp channel)\n    (when-let ((token (?. access-token (?. access-token slack-config))))\n      (handler-case\n          (slack-post-on-channel\n           :channel channel\n           :company company\n           :token token\n           :blocks `#(\n                      ,(alexandria:alist-hash-table\n                        `((:type . \"section\")\n                          (:text . ((\"type\" . \"mrkdwn\")\n                                    (\"text\" . ,(render-text report))))))))\n        (slack-error (e)\n          ;; the slack API error has already been logged, so we should\n          ;; not propagate this.\n          (log:error \"Got error: ~a\" e)\n          (values))))))\n\n(defmethod send-task ((inst slack-task-integration) report)\n  (let ((company (task-integration-company inst))\n        (seen (make-hash-table :test #'equal)))\n    (assert (enabledp inst))\n    (let ((it (default-slack-config company)))\n      (flet ((post-on-channel (channel)\n               (symbol-macrolet ((key (gethash (str:ensure-prefix \"#\" channel) seen)))\n                 (unless key\n                   (setf key t)\n                   (actually-post-on-channel channel report\n                                             :company company)))))\n        (when (enabledp it)\n          (post-on-channel (slack-config-channel it)))\n\n        (mapc #'post-on-channel\n              (channel-slack-channels\n               (report-channel report)))\n\n        (mapc #'post-on-channel\n              (find-slack-channels-for-run (report-run report)))))))\n\n(defun test-message (report-id)\n  \"A function to test how a slack messages renders. This is an\nintegration test, and only works on Staging.\"\n  (let ((company (user-personal-company (%u:user-with-email \"arnold@tdrhq.com\"))))\n    (actually-post-on-channel\n     \"#test-channel\"\n     (util:find-by-oid report-id)\n     :company company)))\n\n"
  },
  {
    "path": "src/screenshotbot/slack/test-core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/slack/test-core\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/installation/installation\n                #:abstract-installation\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/slack/core\n                #:slack-app-redirect-uri))\n(in-package :screenshotbot/slack/test-core)\n\n\n(util/fiveam:def-suite)\n\n(test slack-app-redirect-uri\n  (let ((*installation*\n          (make-instance 'abstract-installation\n                         :domain \"https://foo.example.com\")))\n   (is (equal \"https://foo.example.com/slack-app-redirect\"\n              (slack-app-redirect-uri))))\n  (let ((*installation*\n          (make-instance 'abstract-installation\n                         :domain \"https://foo.example.com/\")))\n    (is (equal \"https://foo.example.com/slack-app-redirect\"\n               (slack-app-redirect-uri)))))\n\n"
  },
  {
    "path": "src/screenshotbot/slack/test-rules.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/slack/test-rules\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/slack/rules\n                #:find-slack-channels-for-run\n                #:tag-rule\n                #:matches-rule)\n  (:import-from #:auth/viewer-context\n                #:viewer-context)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that))\n(in-package :screenshotbot/slack/test-rules)\n\n\n(util/fiveam:def-suite)\n\n\n(def-fixture state ()\n  (with-test-store ()\n    (let* ((company (make-instance 'company))\n           (channel (make-instance 'channel :company company)))\n      (&body))))\n\n\n(test preconditions\n  (with-fixture state ()\n    (let ((run (make-recorder-run :channel channel\n                                  :screenshots nil)))\n     (pass))))\n\n(test try-matching-things\n  (with-fixture state ()\n    (let ((run (make-recorder-run :channel channel\n                                  :company company\n                                  :screenshots nil\n                                  :tags (list \"foo\" \"bar\")))\n          (tag-rule (make-instance 'tag-rule\n                                   :company company\n                                   :tag \"bar\"\n                                   :slack-channel \"#general\")))\n      (is-true (matches-rule tag-rule run)))))\n\n(test failed-matching\n  (with-fixture state ()\n    (let ((run (make-recorder-run :channel channel\n                                  :company company\n                                  :screenshots nil\n                                  :tags (list \"foo\" \"bar\")))\n          (tag-rule (make-instance 'tag-rule\n                                   :company company\n                                   :tag \"zoidberg\"\n                                   :slack-channel \"#general\")))\n      (is-false (matches-rule tag-rule run)))))\n\n\n\n(test find-slack-channels\n  (with-fixture state ()\n    (let ((run (make-recorder-run :channel channel\n                                  :company company\n                                  :tags (list \"bar\" \"foo\")\n                                  :screenshots nil)))\n      (is (eql nil (find-slack-channels-for-run run)))\n      (make-instance 'tag-rule\n                     :company company\n                     :tag \"foo\"\n                     :slack-channel \"#general\")\n      (assert-that\n       (find-slack-channels-for-run run)\n       (contains-in-any-order\n        \"#general\")))))\n\n(test find-slack-channels-is-restricted-to-company\n  (with-fixture state ()\n    (let ((run (make-recorder-run :channel channel\n                                  :company company\n                                  :tags (list \"bar\" \"foo\")\n                                  :screenshots nil)))\n      (is (eql nil (find-slack-channels-for-run run)))\n      (let ((other-company (make-instance 'company)))\n        (make-instance 'tag-rule\n                       :company other-company\n                       :tag \"foo\"\n                       :slack-channel \"#general\"))\n      (assert-that\n       (find-slack-channels-for-run run)\n       (contains-in-any-order)))))\n"
  },
  {
    "path": "src/screenshotbot/slack/test-settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/slack/test-settings\n  (:use :cl)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:is\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/model/company\n                #:slack-config\n                #:company\n                #:default-slack-config)\n  (:import-from #:screenshotbot/slack/core\n                #:slack-tokens-for-company\n                #:find-or-create-slack-config\n                #:slack-token)\n  (:import-from #:screenshotbot/slack/settings\n                #:disconnect-slack\n                #:post-settings-slack)\n  (:import-from #:screenshotbot/user-api\n                #:access-token)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :screenshotbot/slack/test-settings)\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'my-installation)))\n   (with-test-store ()\n     (with-fake-request ()\n       (let* ((company (make-instance 'company))\n              (token (make-instance 'slack-token\n                                     :access-token \"foo\"\n                                     :company company\n                                     :ts 34)))\n         (setf (auth:request-account hunchentoot:*request*)\n               company)\n         (&body))))))\n\n(test posting-when-nothing-is-available ()\n  (with-fixture state ()\n    (catch 'hunchentoot::handler-done\n      (post-settings-slack\n       \"#general\"\n       t))\n    (is (eql token (access-token (default-slack-config company))))))\n\n(test find-or-create-should-create-if-not-exists\n  (with-fixture state ()\n    (is (typep (find-or-create-slack-config company)\n               'slack-config))))\n\n(test delete-slack-tokens\n  (with-fixture state ()\n    (make-instance 'slack-token :company company\n                                :access-token \"bar\")\n    (make-instance 'slack-token :company company\n                                :access-token \"car\")\n    (disconnect-slack company)\n    (assert-that (slack-tokens-for-company company)\n                 (has-length 0))))\n\n(test delete-doesnt-delete-other-company-tokens\n  (with-fixture state ()\n    (let ((other-company (make-instance 'company)))\n      (make-instance 'slack-token :company other-company\n                                  :access-token \"bar\")\n      (make-instance 'slack-token :company company\n                                  :access-token \"bar\")\n      (disconnect-slack company)\n      (assert-that (slack-tokens-for-company company)\n                   (has-length 0))\n      (assert-that (slack-tokens-for-company other-company)\n                   (has-length 1)))))\n"
  },
  {
    "path": "src/screenshotbot/slack/test-task-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/slack/test-task-integration\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:if-called)\n  (:import-from #:screenshotbot/slack/core\n                #:slack-error\n                #:slack-token\n                #:find-or-create-slack-config\n                #:slack-post-on-channel)\n  (:import-from #:screenshotbot/slack/task-integration\n                #:format-blame\n                #:render-text\n                #:render-tags\n                #:slack-task-integration)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/report-api\n                #:report)\n  (:import-from #:screenshotbot/task-integration-api\n                #:enabledp\n                #:send-task)\n  (:import-from #:screenshotbot/model/company\n                #:slack-config-channel\n                #:default-slack-config\n                #:company)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/user-api\n                #:access-token)\n  (:import-from #:screenshotbot/model/channel\n                #:channel-slack-channels\n                #:channel)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/slack/test-task-integration)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key tags work-branch)\n  (with-installation ()\n   (with-test-store ()\n     (let ((posts))\n       (cl-mock:with-mocks ()\n         (if-called 'slack-post-on-channel\n                    (lambda (&rest args)\n                      (push args posts)))\n         (let* ((company (make-instance 'company))\n                (channel (make-instance 'channel :name \"foobar\"\n                                        :company company\n                                        :github-repo \"https://github.com/tdrhq/fast-example\"))\n                (run (make-recorder-run\n                      :channel channel\n                      :work-branch work-branch\n                      :screenshots nil\n                      :tags tags))\n                (report (make-instance 'report :channel channel\n                                       :run run\n                                       :title \"foobar\"))\n                (self (make-instance 'slack-task-integration\n                                     :company company))\n                (slack-token (make-instance 'slack-token\n                                            :access-token \"Foobar\")))\n\n           (let ((slack-config (find-or-create-slack-config company)))\n             (with-transaction ()\n               (setf (access-token slack-config) slack-token)\n               (setf (default-slack-config company) slack-config)\n               (setf (enabledp slack-config) t)))\n           (&body)))))))\n\n(test preconditions\n  (with-fixture state ()\n    (send-task self report)\n    (pass)))\n\n(test handles-slack-error\n  (with-fixture state ()\n    (let ((called nil))\n      (if-called 'slack-post-on-channel\n                  (lambda (&rest args)\n                    (setf called t)\n                    (error 'slack-error))\n                  :at-start t)\n      (send-task self report)\n      (is-true called))))\n\n(test render-tags\n  (with-fixture state (:tags (list \"foo\"))\n    (is\n     (equal \" (from run with tags foo)\"\n            (render-tags report))))\n  (with-fixture state (:tags (list \"foo\" \"bar\"))\n    (is\n     (equal \" (from run with tags foo, bar)\"\n            (render-tags report))))\n  (with-fixture state (:tags nil)\n    (is\n     (equal \"\"\n            (render-tags report))))  )\n\n(test render-text-without-branch\n  (with-fixture state ()\n    (assert-that\n     (render-text report)\n     (matches-regex\n      \"Screenshots changed in *foobar*<https://example.com/report/.*|foobar>\"))))\n\n(test render-text-with-branch-name\n  (with-fixture state (:work-branch \"release/24\")\n    (assert-that\n     (render-text report)\n     (matches-regex\n      \"Screenshots changed in *foobar* on .*release/24.*<https://example.com/report/.*|foobar>\"))))\n\n(test when-channels-are-set-without-slack-config\n  (with-fixture state ()\n    (setf (channel-slack-channels channel) (list \"foobar\"))\n    (setf (enabledp (default-slack-config company)) nil)\n    (setf (access-token (default-slack-config company)) nil)\n    (finishes\n      (send-task self report))\n    (assert-that posts\n                 (has-length 0))))\n\n(test when-channels-only-notification-set-global\n  (with-fixture state ()\n    (setf (channel-slack-channels channel) (list \"foobar\"))\n    (setf (enabledp (default-slack-config company)) nil)\n    (finishes\n      (send-task self report))\n    (assert-that posts\n                 (has-length 1))))\n\n(test dont-send-duplicates\n  (with-fixture state ()\n    (setf (channel-slack-channels channel) (list \"foobar\"))\n    (setf (enabledp (default-slack-config company)) t)\n    (setf (slack-config-channel (default-slack-config company)) \"foobar\")\n    (finishes\n      (send-task self report))\n    (assert-that posts\n                 (has-length 1))))\n\n(test dont-send-duplicates-for-#-too\n  (with-fixture state ()\n    (setf (channel-slack-channels channel) (list \"foobar\"))\n    (setf (enabledp (default-slack-config company)) t)\n    (setf (slack-config-channel (default-slack-config company)) \"#foobar\")\n    (finishes\n      (send-task self report))\n    (assert-that posts\n                 (has-length 1))))\n\n(test format-blame-with-single-commit\n  (with-fixture state ()\n    (let ((path (list \"abc123def456\" \"base-commit\")))\n      (assert-that\n       (format-blame report path)\n       (matches-regex \" \\\\| Blames to <.*\\\\|abc123de>\")))))\n\n(test format-blame-with-multiple-commits\n  (with-fixture state ()\n    (let* ((previous-run (make-recorder-run\n                          :channel channel\n                          :screenshots nil))\n           (report (make-instance 'report\n                                  :channel channel\n                                  :run run\n                                  :previous-run previous-run\n                                  :title \"foobar\"))\n           (path (list \"commit1\" \"commit2\" \"commit3\")))\n      (assert-that\n       (format-blame report path)\n       (matches-regex \" \\\\| <.*blame.*\\\\|Blame commits>\")))))\n"
  },
  {
    "path": "src/screenshotbot/sso/fake.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sso/fake\n  (:use #:cl\n        #:screenshotbot/sso/model)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:core/installation/auth-provider\n                #:call-with-company-login)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/sso/fake)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defmethod call-with-company-login ((sso fake-sso-auth-provider)\n                                    company\n                                    fn)\n  <html>\n    <body>\n      See page <a href= (nibble () (funcall fn)) >here.</a>\n    </body>\n  </html>)\n"
  },
  {
    "path": "src/screenshotbot/sso/model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sso/model\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store/store\n                #:with-class-validation)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:fake-sso-auth-provider))\n(in-package :screenshotbot/sso/model)\n\n(defclass abstract-sso-auth-provider (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(with-class-validation\n  (defclass fake-sso-auth-provider (abstract-sso-auth-provider)\n    ()\n    (:metaclass persistent-class)))\n\n(with-class-validation\n  (defclass basic-sso-auth-provider (abstract-sso-auth-provider)\n    ((issuer :initarg :issuer\n             :accessor auth-provider-issuer)\n     (client-id :initarg :client-id\n                :accessor auth-provider-client-id)\n     (client-secret :initarg :client-secret\n                    :accessor auth-provider-client-secret))\n    (:metaclass persistent-class)))\n"
  },
  {
    "path": "src/screenshotbot/sso/redirect.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sso/redirect\n  (:use #:cl)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:core/installation/auth-provider\n                #:company-sso-auth-provider)\n  (:import-from #:screenshotbot/login/oidc\n                #:oidc-provider)\n  (:import-from #:screenshotbot/sso/model\n                #:auth-provider-client-secret\n                #:auth-provider-client-id\n                #:auth-provider-issuer)\n  (:import-from #:alexandria\n                #:when-let*)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:screenshotbot/login/common\n                #:oauth-signin-link)\n  (:import-from #:auth/login/sso\n                #:maybe-redirect-for-sso))\n(in-package :screenshotbot/sso/redirect)\n\n(defmethod maybe-redirect-for-sso ((company company) final-redirect)\n  ;; 1. check if SSO will actually help\n  ;; 2. actually do the redirect.\n  (when-let* ((sso-auth-provider (company-sso-auth-provider company))\n              (oidc-provider (make-instance 'oidc-provider\n                                            :identifier 'self-service\n                                            :issuer (auth-provider-issuer sso-auth-provider)\n                                            :client-id (auth-provider-client-id sso-auth-provider)\n                                            :client-secret (auth-provider-client-secret sso-auth-provider)\n                                            :expiration-seconds (* 24 3600))))\n    (hex:safe-redirect\n     (oauth-signin-link oidc-provider\n                        (nibble:allow-user-change\n                         (nibble ()\n                           (hex:safe-redirect final-redirect)))))))\n\n"
  },
  {
    "path": "src/screenshotbot/sso/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/sso/settings\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:should-show-settings-p\n                #:defsettings\n                #:settings-template)\n  (:import-from #:screenshotbot/template\n                #:mailto)\n  (:import-from #:nibble\n                #:defnibble\n                #:nibble)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:screenshotbot/user-api\n                #:current-user\n                #:current-company)\n  (:import-from #:screenshotbot/model/company\n                #:company-admin-p)\n  (:import-from #:core/installation/auth-provider\n                #:company-sso-auth-provider)\n  (:import-from #:screenshotbot/sso/model\n                #:basic-sso-auth-provider\n                #:auth-provider-client-secret\n                #:auth-provider-client-id\n                #:auth-provider-issuer)\n  (:import-from #:util/misc\n                #:?.)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/sso/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *default-scope*\n  \"openid email profile\")\n\n(markup:deftag linput (&key label name value (type \"text\") placeholder\n                       readonly)\n  <div class= \"mb-3\" >\n    <label for=name class= \"form-label\" >,(progn label)</label>\n    <input class= \"form-control\" type=type name=name id=name value=value placeholder=placeholder\n           readonly=readonly />\n  </div>)\n\n(defnibble submit-sso (issuer client-id client-secret scope)\n  (let ((user (current-user))\n        (company (current-company)))\n   (let ((errors))\n     (flet ((check (name test message)\n              (unless test\n                (push (cons name message) errors))))\n\n       (check :issuer (not (str:emptyp issuer))\n              \"Must provide an issuer. You can find the issuer URL in your Identity Provider (IdP)\")\n       (check :client-id (not (str:emptyp client-id))\n              \"Must provide a Client ID, you can create this in your Identity Provider (IdP)\")\n       (check :client-secret (not (str:emptyp client-secret))\n              \"Must provide a Client secret, you can create this in your Identity Provider (IdP)\")\n       (check :scope (equal *default-scope* scope)\n              \"Do not modify the scope\")\n\n       (cond\n         ((not (company-admin-p company user))\n          (push \"You must be an admin to update SSO settings\" errors))\n         (t))\n\n       (cond\n         (errors\n          (with-form-errors (:errors errors\n                             :was-validated t\n                             :issuer issuer\n                             :client-id client-id\n                             :client-secret client-secret\n                             :scope scope)\n            (sso-settings)))\n         (t\n          (let ((auth-provider (or\n                                (company-sso-auth-provider company)\n                                (make-instance 'basic-sso-auth-provider))))\n            (setf (auth-provider-issuer auth-provider) issuer)\n            (setf (auth-provider-client-id auth-provider) client-id)\n            (setf (auth-provider-client-secret auth-provider) client-secret)\n            (setf (company-sso-auth-provider company) auth-provider))\n          (hex:safe-redirect \"/settings/sso\")))))))\n\n(defun sso-settings (&aux (auth-provider (company-sso-auth-provider (auth:current-company))))\n  <settings-template>\n\n    <form action= (nibble submit-sso) >\n      <div class= \"card mt-3\">\n        <div class= \"card-header\">\n          <h3 class= \"\" >Single-Sign-On</h3>\n          <p>SSO is included in every plan at no extra cost, we only charge by active users. Please contact <mailto>support@screenshotbot.io</mailto> if you need to use SAML authentication instead of OpenID Connect.</p>\n\n        </div>\n        <div class= \"card-body\">\n          <div class= \"alert alert-danger d-none\" />\n          <linput label= \"OpenID Connect Issuer\" name=\"issuer\" placeholder= \"https://auth.example.com/auth\"\n                  value= (?. auth-provider-issuer auth-provider) />\n          <linput label= \"Client ID\" name=\"client-id\"\n                  value= (?. auth-provider-client-id auth-provider) />\n          <linput label= \"Client Secret\" name=\"client-secret\"\n                  value= (?. auth-provider-client-secret auth-provider) />\n          <linput label= \"Scope\" name=\"scope\" value= *default-scope*\n                  readonly= \"readonly\" />\n\n\n        </div>\n\n        <div class= \"card-footer\">\n          <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n          <input type= \"button\" formaction=nil\n                 class= \"btn btn-secondary\" value= \"Test\" />\n        </div>\n      </div>\n    </form>\n\n\n  </settings-template>)\n\n(defsettings sso\n  :name \"sso\"\n  :title \"Single sign-on\"\n  :section nil\n  :handler 'sso-settings)\n\n(defmethod should-show-settings-p (installation (name (eql 'sso)))\n  (gk:check :self-service-sso (auth:current-company)))\n"
  },
  {
    "path": "src/screenshotbot/static/assets/css/aos.css",
    "content": "[data-aos][data-aos][data-aos-duration=\"50\"],body[data-aos-duration=\"50\"] [data-aos]{transition-duration:50ms}[data-aos][data-aos][data-aos-delay=\"50\"],body[data-aos-delay=\"50\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"50\"].aos-animate,body[data-aos-delay=\"50\"] [data-aos].aos-animate{transition-delay:50ms}[data-aos][data-aos][data-aos-duration=\"100\"],body[data-aos-duration=\"100\"] [data-aos]{transition-duration:.1s}[data-aos][data-aos][data-aos-delay=\"100\"],body[data-aos-delay=\"100\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"100\"].aos-animate,body[data-aos-delay=\"100\"] [data-aos].aos-animate{transition-delay:.1s}[data-aos][data-aos][data-aos-duration=\"150\"],body[data-aos-duration=\"150\"] [data-aos]{transition-duration:.15s}[data-aos][data-aos][data-aos-delay=\"150\"],body[data-aos-delay=\"150\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"150\"].aos-animate,body[data-aos-delay=\"150\"] [data-aos].aos-animate{transition-delay:.15s}[data-aos][data-aos][data-aos-duration=\"200\"],body[data-aos-duration=\"200\"] [data-aos]{transition-duration:.2s}[data-aos][data-aos][data-aos-delay=\"200\"],body[data-aos-delay=\"200\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"200\"].aos-animate,body[data-aos-delay=\"200\"] [data-aos].aos-animate{transition-delay:.2s}[data-aos][data-aos][data-aos-duration=\"250\"],body[data-aos-duration=\"250\"] [data-aos]{transition-duration:.25s}[data-aos][data-aos][data-aos-delay=\"250\"],body[data-aos-delay=\"250\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"250\"].aos-animate,body[data-aos-delay=\"250\"] [data-aos].aos-animate{transition-delay:.25s}[data-aos][data-aos][data-aos-duration=\"300\"],body[data-aos-duration=\"300\"] [data-aos]{transition-duration:.3s}[data-aos][data-aos][data-aos-delay=\"300\"],body[data-aos-delay=\"300\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"300\"].aos-animate,body[data-aos-delay=\"300\"] [data-aos].aos-animate{transition-delay:.3s}[data-aos][data-aos][data-aos-duration=\"350\"],body[data-aos-duration=\"350\"] [data-aos]{transition-duration:.35s}[data-aos][data-aos][data-aos-delay=\"350\"],body[data-aos-delay=\"350\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"350\"].aos-animate,body[data-aos-delay=\"350\"] [data-aos].aos-animate{transition-delay:.35s}[data-aos][data-aos][data-aos-duration=\"400\"],body[data-aos-duration=\"400\"] [data-aos]{transition-duration:.4s}[data-aos][data-aos][data-aos-delay=\"400\"],body[data-aos-delay=\"400\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"400\"].aos-animate,body[data-aos-delay=\"400\"] [data-aos].aos-animate{transition-delay:.4s}[data-aos][data-aos][data-aos-duration=\"450\"],body[data-aos-duration=\"450\"] [data-aos]{transition-duration:.45s}[data-aos][data-aos][data-aos-delay=\"450\"],body[data-aos-delay=\"450\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"450\"].aos-animate,body[data-aos-delay=\"450\"] [data-aos].aos-animate{transition-delay:.45s}[data-aos][data-aos][data-aos-duration=\"500\"],body[data-aos-duration=\"500\"] [data-aos]{transition-duration:.5s}[data-aos][data-aos][data-aos-delay=\"500\"],body[data-aos-delay=\"500\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"500\"].aos-animate,body[data-aos-delay=\"500\"] [data-aos].aos-animate{transition-delay:.5s}[data-aos][data-aos][data-aos-duration=\"550\"],body[data-aos-duration=\"550\"] [data-aos]{transition-duration:.55s}[data-aos][data-aos][data-aos-delay=\"550\"],body[data-aos-delay=\"550\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"550\"].aos-animate,body[data-aos-delay=\"550\"] [data-aos].aos-animate{transition-delay:.55s}[data-aos][data-aos][data-aos-duration=\"600\"],body[data-aos-duration=\"600\"] [data-aos]{transition-duration:.6s}[data-aos][data-aos][data-aos-delay=\"600\"],body[data-aos-delay=\"600\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"600\"].aos-animate,body[data-aos-delay=\"600\"] [data-aos].aos-animate{transition-delay:.6s}[data-aos][data-aos][data-aos-duration=\"650\"],body[data-aos-duration=\"650\"] [data-aos]{transition-duration:.65s}[data-aos][data-aos][data-aos-delay=\"650\"],body[data-aos-delay=\"650\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"650\"].aos-animate,body[data-aos-delay=\"650\"] [data-aos].aos-animate{transition-delay:.65s}[data-aos][data-aos][data-aos-duration=\"700\"],body[data-aos-duration=\"700\"] [data-aos]{transition-duration:.7s}[data-aos][data-aos][data-aos-delay=\"700\"],body[data-aos-delay=\"700\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"700\"].aos-animate,body[data-aos-delay=\"700\"] [data-aos].aos-animate{transition-delay:.7s}[data-aos][data-aos][data-aos-duration=\"750\"],body[data-aos-duration=\"750\"] [data-aos]{transition-duration:.75s}[data-aos][data-aos][data-aos-delay=\"750\"],body[data-aos-delay=\"750\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"750\"].aos-animate,body[data-aos-delay=\"750\"] [data-aos].aos-animate{transition-delay:.75s}[data-aos][data-aos][data-aos-duration=\"800\"],body[data-aos-duration=\"800\"] [data-aos]{transition-duration:.8s}[data-aos][data-aos][data-aos-delay=\"800\"],body[data-aos-delay=\"800\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"800\"].aos-animate,body[data-aos-delay=\"800\"] [data-aos].aos-animate{transition-delay:.8s}[data-aos][data-aos][data-aos-duration=\"850\"],body[data-aos-duration=\"850\"] [data-aos]{transition-duration:.85s}[data-aos][data-aos][data-aos-delay=\"850\"],body[data-aos-delay=\"850\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"850\"].aos-animate,body[data-aos-delay=\"850\"] [data-aos].aos-animate{transition-delay:.85s}[data-aos][data-aos][data-aos-duration=\"900\"],body[data-aos-duration=\"900\"] [data-aos]{transition-duration:.9s}[data-aos][data-aos][data-aos-delay=\"900\"],body[data-aos-delay=\"900\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"900\"].aos-animate,body[data-aos-delay=\"900\"] [data-aos].aos-animate{transition-delay:.9s}[data-aos][data-aos][data-aos-duration=\"950\"],body[data-aos-duration=\"950\"] [data-aos]{transition-duration:.95s}[data-aos][data-aos][data-aos-delay=\"950\"],body[data-aos-delay=\"950\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"950\"].aos-animate,body[data-aos-delay=\"950\"] [data-aos].aos-animate{transition-delay:.95s}[data-aos][data-aos][data-aos-duration=\"1000\"],body[data-aos-duration=\"1000\"] [data-aos]{transition-duration:1s}[data-aos][data-aos][data-aos-delay=\"1000\"],body[data-aos-delay=\"1000\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1000\"].aos-animate,body[data-aos-delay=\"1000\"] [data-aos].aos-animate{transition-delay:1s}[data-aos][data-aos][data-aos-duration=\"1050\"],body[data-aos-duration=\"1050\"] [data-aos]{transition-duration:1.05s}[data-aos][data-aos][data-aos-delay=\"1050\"],body[data-aos-delay=\"1050\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1050\"].aos-animate,body[data-aos-delay=\"1050\"] [data-aos].aos-animate{transition-delay:1.05s}[data-aos][data-aos][data-aos-duration=\"1100\"],body[data-aos-duration=\"1100\"] [data-aos]{transition-duration:1.1s}[data-aos][data-aos][data-aos-delay=\"1100\"],body[data-aos-delay=\"1100\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1100\"].aos-animate,body[data-aos-delay=\"1100\"] [data-aos].aos-animate{transition-delay:1.1s}[data-aos][data-aos][data-aos-duration=\"1150\"],body[data-aos-duration=\"1150\"] [data-aos]{transition-duration:1.15s}[data-aos][data-aos][data-aos-delay=\"1150\"],body[data-aos-delay=\"1150\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1150\"].aos-animate,body[data-aos-delay=\"1150\"] [data-aos].aos-animate{transition-delay:1.15s}[data-aos][data-aos][data-aos-duration=\"1200\"],body[data-aos-duration=\"1200\"] [data-aos]{transition-duration:1.2s}[data-aos][data-aos][data-aos-delay=\"1200\"],body[data-aos-delay=\"1200\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1200\"].aos-animate,body[data-aos-delay=\"1200\"] [data-aos].aos-animate{transition-delay:1.2s}[data-aos][data-aos][data-aos-duration=\"1250\"],body[data-aos-duration=\"1250\"] [data-aos]{transition-duration:1.25s}[data-aos][data-aos][data-aos-delay=\"1250\"],body[data-aos-delay=\"1250\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1250\"].aos-animate,body[data-aos-delay=\"1250\"] [data-aos].aos-animate{transition-delay:1.25s}[data-aos][data-aos][data-aos-duration=\"1300\"],body[data-aos-duration=\"1300\"] [data-aos]{transition-duration:1.3s}[data-aos][data-aos][data-aos-delay=\"1300\"],body[data-aos-delay=\"1300\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1300\"].aos-animate,body[data-aos-delay=\"1300\"] [data-aos].aos-animate{transition-delay:1.3s}[data-aos][data-aos][data-aos-duration=\"1350\"],body[data-aos-duration=\"1350\"] [data-aos]{transition-duration:1.35s}[data-aos][data-aos][data-aos-delay=\"1350\"],body[data-aos-delay=\"1350\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1350\"].aos-animate,body[data-aos-delay=\"1350\"] [data-aos].aos-animate{transition-delay:1.35s}[data-aos][data-aos][data-aos-duration=\"1400\"],body[data-aos-duration=\"1400\"] [data-aos]{transition-duration:1.4s}[data-aos][data-aos][data-aos-delay=\"1400\"],body[data-aos-delay=\"1400\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1400\"].aos-animate,body[data-aos-delay=\"1400\"] [data-aos].aos-animate{transition-delay:1.4s}[data-aos][data-aos][data-aos-duration=\"1450\"],body[data-aos-duration=\"1450\"] [data-aos]{transition-duration:1.45s}[data-aos][data-aos][data-aos-delay=\"1450\"],body[data-aos-delay=\"1450\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1450\"].aos-animate,body[data-aos-delay=\"1450\"] [data-aos].aos-animate{transition-delay:1.45s}[data-aos][data-aos][data-aos-duration=\"1500\"],body[data-aos-duration=\"1500\"] [data-aos]{transition-duration:1.5s}[data-aos][data-aos][data-aos-delay=\"1500\"],body[data-aos-delay=\"1500\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1500\"].aos-animate,body[data-aos-delay=\"1500\"] [data-aos].aos-animate{transition-delay:1.5s}[data-aos][data-aos][data-aos-duration=\"1550\"],body[data-aos-duration=\"1550\"] [data-aos]{transition-duration:1.55s}[data-aos][data-aos][data-aos-delay=\"1550\"],body[data-aos-delay=\"1550\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1550\"].aos-animate,body[data-aos-delay=\"1550\"] [data-aos].aos-animate{transition-delay:1.55s}[data-aos][data-aos][data-aos-duration=\"1600\"],body[data-aos-duration=\"1600\"] [data-aos]{transition-duration:1.6s}[data-aos][data-aos][data-aos-delay=\"1600\"],body[data-aos-delay=\"1600\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1600\"].aos-animate,body[data-aos-delay=\"1600\"] [data-aos].aos-animate{transition-delay:1.6s}[data-aos][data-aos][data-aos-duration=\"1650\"],body[data-aos-duration=\"1650\"] [data-aos]{transition-duration:1.65s}[data-aos][data-aos][data-aos-delay=\"1650\"],body[data-aos-delay=\"1650\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1650\"].aos-animate,body[data-aos-delay=\"1650\"] [data-aos].aos-animate{transition-delay:1.65s}[data-aos][data-aos][data-aos-duration=\"1700\"],body[data-aos-duration=\"1700\"] [data-aos]{transition-duration:1.7s}[data-aos][data-aos][data-aos-delay=\"1700\"],body[data-aos-delay=\"1700\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1700\"].aos-animate,body[data-aos-delay=\"1700\"] [data-aos].aos-animate{transition-delay:1.7s}[data-aos][data-aos][data-aos-duration=\"1750\"],body[data-aos-duration=\"1750\"] [data-aos]{transition-duration:1.75s}[data-aos][data-aos][data-aos-delay=\"1750\"],body[data-aos-delay=\"1750\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1750\"].aos-animate,body[data-aos-delay=\"1750\"] [data-aos].aos-animate{transition-delay:1.75s}[data-aos][data-aos][data-aos-duration=\"1800\"],body[data-aos-duration=\"1800\"] [data-aos]{transition-duration:1.8s}[data-aos][data-aos][data-aos-delay=\"1800\"],body[data-aos-delay=\"1800\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1800\"].aos-animate,body[data-aos-delay=\"1800\"] [data-aos].aos-animate{transition-delay:1.8s}[data-aos][data-aos][data-aos-duration=\"1850\"],body[data-aos-duration=\"1850\"] [data-aos]{transition-duration:1.85s}[data-aos][data-aos][data-aos-delay=\"1850\"],body[data-aos-delay=\"1850\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1850\"].aos-animate,body[data-aos-delay=\"1850\"] [data-aos].aos-animate{transition-delay:1.85s}[data-aos][data-aos][data-aos-duration=\"1900\"],body[data-aos-duration=\"1900\"] [data-aos]{transition-duration:1.9s}[data-aos][data-aos][data-aos-delay=\"1900\"],body[data-aos-delay=\"1900\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1900\"].aos-animate,body[data-aos-delay=\"1900\"] [data-aos].aos-animate{transition-delay:1.9s}[data-aos][data-aos][data-aos-duration=\"1950\"],body[data-aos-duration=\"1950\"] [data-aos]{transition-duration:1.95s}[data-aos][data-aos][data-aos-delay=\"1950\"],body[data-aos-delay=\"1950\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"1950\"].aos-animate,body[data-aos-delay=\"1950\"] [data-aos].aos-animate{transition-delay:1.95s}[data-aos][data-aos][data-aos-duration=\"2000\"],body[data-aos-duration=\"2000\"] [data-aos]{transition-duration:2s}[data-aos][data-aos][data-aos-delay=\"2000\"],body[data-aos-delay=\"2000\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2000\"].aos-animate,body[data-aos-delay=\"2000\"] [data-aos].aos-animate{transition-delay:2s}[data-aos][data-aos][data-aos-duration=\"2050\"],body[data-aos-duration=\"2050\"] [data-aos]{transition-duration:2.05s}[data-aos][data-aos][data-aos-delay=\"2050\"],body[data-aos-delay=\"2050\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2050\"].aos-animate,body[data-aos-delay=\"2050\"] [data-aos].aos-animate{transition-delay:2.05s}[data-aos][data-aos][data-aos-duration=\"2100\"],body[data-aos-duration=\"2100\"] [data-aos]{transition-duration:2.1s}[data-aos][data-aos][data-aos-delay=\"2100\"],body[data-aos-delay=\"2100\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2100\"].aos-animate,body[data-aos-delay=\"2100\"] [data-aos].aos-animate{transition-delay:2.1s}[data-aos][data-aos][data-aos-duration=\"2150\"],body[data-aos-duration=\"2150\"] [data-aos]{transition-duration:2.15s}[data-aos][data-aos][data-aos-delay=\"2150\"],body[data-aos-delay=\"2150\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2150\"].aos-animate,body[data-aos-delay=\"2150\"] [data-aos].aos-animate{transition-delay:2.15s}[data-aos][data-aos][data-aos-duration=\"2200\"],body[data-aos-duration=\"2200\"] [data-aos]{transition-duration:2.2s}[data-aos][data-aos][data-aos-delay=\"2200\"],body[data-aos-delay=\"2200\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2200\"].aos-animate,body[data-aos-delay=\"2200\"] [data-aos].aos-animate{transition-delay:2.2s}[data-aos][data-aos][data-aos-duration=\"2250\"],body[data-aos-duration=\"2250\"] [data-aos]{transition-duration:2.25s}[data-aos][data-aos][data-aos-delay=\"2250\"],body[data-aos-delay=\"2250\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2250\"].aos-animate,body[data-aos-delay=\"2250\"] [data-aos].aos-animate{transition-delay:2.25s}[data-aos][data-aos][data-aos-duration=\"2300\"],body[data-aos-duration=\"2300\"] [data-aos]{transition-duration:2.3s}[data-aos][data-aos][data-aos-delay=\"2300\"],body[data-aos-delay=\"2300\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2300\"].aos-animate,body[data-aos-delay=\"2300\"] [data-aos].aos-animate{transition-delay:2.3s}[data-aos][data-aos][data-aos-duration=\"2350\"],body[data-aos-duration=\"2350\"] [data-aos]{transition-duration:2.35s}[data-aos][data-aos][data-aos-delay=\"2350\"],body[data-aos-delay=\"2350\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2350\"].aos-animate,body[data-aos-delay=\"2350\"] [data-aos].aos-animate{transition-delay:2.35s}[data-aos][data-aos][data-aos-duration=\"2400\"],body[data-aos-duration=\"2400\"] [data-aos]{transition-duration:2.4s}[data-aos][data-aos][data-aos-delay=\"2400\"],body[data-aos-delay=\"2400\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2400\"].aos-animate,body[data-aos-delay=\"2400\"] [data-aos].aos-animate{transition-delay:2.4s}[data-aos][data-aos][data-aos-duration=\"2450\"],body[data-aos-duration=\"2450\"] [data-aos]{transition-duration:2.45s}[data-aos][data-aos][data-aos-delay=\"2450\"],body[data-aos-delay=\"2450\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2450\"].aos-animate,body[data-aos-delay=\"2450\"] [data-aos].aos-animate{transition-delay:2.45s}[data-aos][data-aos][data-aos-duration=\"2500\"],body[data-aos-duration=\"2500\"] [data-aos]{transition-duration:2.5s}[data-aos][data-aos][data-aos-delay=\"2500\"],body[data-aos-delay=\"2500\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2500\"].aos-animate,body[data-aos-delay=\"2500\"] [data-aos].aos-animate{transition-delay:2.5s}[data-aos][data-aos][data-aos-duration=\"2550\"],body[data-aos-duration=\"2550\"] [data-aos]{transition-duration:2.55s}[data-aos][data-aos][data-aos-delay=\"2550\"],body[data-aos-delay=\"2550\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2550\"].aos-animate,body[data-aos-delay=\"2550\"] [data-aos].aos-animate{transition-delay:2.55s}[data-aos][data-aos][data-aos-duration=\"2600\"],body[data-aos-duration=\"2600\"] [data-aos]{transition-duration:2.6s}[data-aos][data-aos][data-aos-delay=\"2600\"],body[data-aos-delay=\"2600\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2600\"].aos-animate,body[data-aos-delay=\"2600\"] [data-aos].aos-animate{transition-delay:2.6s}[data-aos][data-aos][data-aos-duration=\"2650\"],body[data-aos-duration=\"2650\"] [data-aos]{transition-duration:2.65s}[data-aos][data-aos][data-aos-delay=\"2650\"],body[data-aos-delay=\"2650\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2650\"].aos-animate,body[data-aos-delay=\"2650\"] [data-aos].aos-animate{transition-delay:2.65s}[data-aos][data-aos][data-aos-duration=\"2700\"],body[data-aos-duration=\"2700\"] [data-aos]{transition-duration:2.7s}[data-aos][data-aos][data-aos-delay=\"2700\"],body[data-aos-delay=\"2700\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2700\"].aos-animate,body[data-aos-delay=\"2700\"] [data-aos].aos-animate{transition-delay:2.7s}[data-aos][data-aos][data-aos-duration=\"2750\"],body[data-aos-duration=\"2750\"] [data-aos]{transition-duration:2.75s}[data-aos][data-aos][data-aos-delay=\"2750\"],body[data-aos-delay=\"2750\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2750\"].aos-animate,body[data-aos-delay=\"2750\"] [data-aos].aos-animate{transition-delay:2.75s}[data-aos][data-aos][data-aos-duration=\"2800\"],body[data-aos-duration=\"2800\"] [data-aos]{transition-duration:2.8s}[data-aos][data-aos][data-aos-delay=\"2800\"],body[data-aos-delay=\"2800\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2800\"].aos-animate,body[data-aos-delay=\"2800\"] [data-aos].aos-animate{transition-delay:2.8s}[data-aos][data-aos][data-aos-duration=\"2850\"],body[data-aos-duration=\"2850\"] [data-aos]{transition-duration:2.85s}[data-aos][data-aos][data-aos-delay=\"2850\"],body[data-aos-delay=\"2850\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2850\"].aos-animate,body[data-aos-delay=\"2850\"] [data-aos].aos-animate{transition-delay:2.85s}[data-aos][data-aos][data-aos-duration=\"2900\"],body[data-aos-duration=\"2900\"] [data-aos]{transition-duration:2.9s}[data-aos][data-aos][data-aos-delay=\"2900\"],body[data-aos-delay=\"2900\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2900\"].aos-animate,body[data-aos-delay=\"2900\"] [data-aos].aos-animate{transition-delay:2.9s}[data-aos][data-aos][data-aos-duration=\"2950\"],body[data-aos-duration=\"2950\"] [data-aos]{transition-duration:2.95s}[data-aos][data-aos][data-aos-delay=\"2950\"],body[data-aos-delay=\"2950\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"2950\"].aos-animate,body[data-aos-delay=\"2950\"] [data-aos].aos-animate{transition-delay:2.95s}[data-aos][data-aos][data-aos-duration=\"3000\"],body[data-aos-duration=\"3000\"] [data-aos]{transition-duration:3s}[data-aos][data-aos][data-aos-delay=\"3000\"],body[data-aos-delay=\"3000\"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay=\"3000\"].aos-animate,body[data-aos-delay=\"3000\"] [data-aos].aos-animate{transition-delay:3s}[data-aos][data-aos][data-aos-easing=linear],body[data-aos-easing=linear] [data-aos]{transition-timing-function:cubic-bezier(.25,.25,.75,.75)}[data-aos][data-aos][data-aos-easing=ease],body[data-aos-easing=ease] [data-aos]{transition-timing-function:ease}[data-aos][data-aos][data-aos-easing=ease-in],body[data-aos-easing=ease-in] [data-aos]{transition-timing-function:ease-in}[data-aos][data-aos][data-aos-easing=ease-out],body[data-aos-easing=ease-out] [data-aos]{transition-timing-function:ease-out}[data-aos][data-aos][data-aos-easing=ease-in-out],body[data-aos-easing=ease-in-out] [data-aos]{transition-timing-function:ease-in-out}[data-aos][data-aos][data-aos-easing=ease-in-back],body[data-aos-easing=ease-in-back] [data-aos]{transition-timing-function:cubic-bezier(.6,-.28,.735,.045)}[data-aos][data-aos][data-aos-easing=ease-out-back],body[data-aos-easing=ease-out-back] [data-aos]{transition-timing-function:cubic-bezier(.175,.885,.32,1.275)}[data-aos][data-aos][data-aos-easing=ease-in-out-back],body[data-aos-easing=ease-in-out-back] [data-aos]{transition-timing-function:cubic-bezier(.68,-.55,.265,1.55)}[data-aos][data-aos][data-aos-easing=ease-in-sine],body[data-aos-easing=ease-in-sine] [data-aos]{transition-timing-function:cubic-bezier(.47,0,.745,.715)}[data-aos][data-aos][data-aos-easing=ease-out-sine],body[data-aos-easing=ease-out-sine] [data-aos]{transition-timing-function:cubic-bezier(.39,.575,.565,1)}[data-aos][data-aos][data-aos-easing=ease-in-out-sine],body[data-aos-easing=ease-in-out-sine] [data-aos]{transition-timing-function:cubic-bezier(.445,.05,.55,.95)}[data-aos][data-aos][data-aos-easing=ease-in-quad],body[data-aos-easing=ease-in-quad] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-quad],body[data-aos-easing=ease-out-quad] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-quad],body[data-aos-easing=ease-in-out-quad] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos][data-aos][data-aos-easing=ease-in-cubic],body[data-aos-easing=ease-in-cubic] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-cubic],body[data-aos-easing=ease-out-cubic] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-cubic],body[data-aos-easing=ease-in-out-cubic] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos][data-aos][data-aos-easing=ease-in-quart],body[data-aos-easing=ease-in-quart] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-quart],body[data-aos-easing=ease-out-quart] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-quart],body[data-aos-easing=ease-in-out-quart] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos^=fade][data-aos^=fade]{opacity:0;transition-property:opacity,transform}[data-aos^=fade][data-aos^=fade].aos-animate{opacity:1;transform:translateZ(0)}[data-aos=fade-up]{transform:translate3d(0,100px,0)}[data-aos=fade-down]{transform:translate3d(0,-100px,0)}[data-aos=fade-right]{transform:translate3d(-100px,0,0)}[data-aos=fade-left]{transform:translate3d(100px,0,0)}[data-aos=fade-up-right]{transform:translate3d(-100px,100px,0)}[data-aos=fade-up-left]{transform:translate3d(100px,100px,0)}[data-aos=fade-down-right]{transform:translate3d(-100px,-100px,0)}[data-aos=fade-down-left]{transform:translate3d(100px,-100px,0)}[data-aos^=zoom][data-aos^=zoom]{opacity:0;transition-property:opacity,transform}[data-aos^=zoom][data-aos^=zoom].aos-animate{opacity:1;transform:translateZ(0) scale(1)}[data-aos=zoom-in]{transform:scale(.6)}[data-aos=zoom-in-up]{transform:translate3d(0,100px,0) scale(.6)}[data-aos=zoom-in-down]{transform:translate3d(0,-100px,0) scale(.6)}[data-aos=zoom-in-right]{transform:translate3d(-100px,0,0) scale(.6)}[data-aos=zoom-in-left]{transform:translate3d(100px,0,0) scale(.6)}[data-aos=zoom-out]{transform:scale(1.2)}[data-aos=zoom-out-up]{transform:translate3d(0,100px,0) scale(1.2)}[data-aos=zoom-out-down]{transform:translate3d(0,-100px,0) scale(1.2)}[data-aos=zoom-out-right]{transform:translate3d(-100px,0,0) scale(1.2)}[data-aos=zoom-out-left]{transform:translate3d(100px,0,0) scale(1.2)}[data-aos^=slide][data-aos^=slide]{transition-property:transform}[data-aos^=slide][data-aos^=slide].aos-animate{transform:translateZ(0)}[data-aos=slide-up]{transform:translate3d(0,100%,0)}[data-aos=slide-down]{transform:translate3d(0,-100%,0)}[data-aos=slide-right]{transform:translate3d(-100%,0,0)}[data-aos=slide-left]{transform:translate3d(100%,0,0)}[data-aos^=flip][data-aos^=flip]{backface-visibility:hidden;transition-property:transform}[data-aos=flip-left]{transform:perspective(2500px) rotateY(-100deg)}[data-aos=flip-left].aos-animate{transform:perspective(2500px) rotateY(0)}[data-aos=flip-right]{transform:perspective(2500px) rotateY(100deg)}[data-aos=flip-right].aos-animate{transform:perspective(2500px) rotateY(0)}[data-aos=flip-up]{transform:perspective(2500px) rotateX(-100deg)}[data-aos=flip-up].aos-animate{transform:perspective(2500px) rotateX(0)}[data-aos=flip-down]{transform:perspective(2500px) rotateX(100deg)}[data-aos=flip-down].aos-animate{transform:perspective(2500px) rotateX(0)}"
  },
  {
    "path": "src/screenshotbot/static/assets/css/selenium.css",
    "content": "/* This is not the same as pro/replay/static/css/replay.css! This one\n   is used for testing screenshotbot itself, whereas the other is for\n   testing external pages. */\n\n* {\n transition-property: none !important;\n -o-transition-property: none !important;\n -moz-transition-property: none !important;\n -ms-transition-property: none !important;\n -webkit-transition-property: none !important;\n\n transform: none !important;\n -o-transform: none !important;\n -moz-transform: none !important;\n -ms-transform: none !important;\n -webkit-transform: none !important;\n\n animation: none !important;\n -o-animation: none !important;\n -moz-animation: none !important;\n -ms-animation: none !important;\n -webkit-animation: none !important;\n}\n\n\n.sign-up-button {\n    box-shadow: none !important;\n}\n\nbody {\n    overflow: hidden !important;\n    pointer-events: none !important;\n}\n\nhtml {\n    scroll-behavior: auto !important;\n    overflow: hidden !important;\n}\n\n.github-star-notice {\n    /* rendering the github star SVG doesn't seem to be deterministic on\n   Selenium, so we're getting rid of it. */\n    display: none;\n}\n"
  },
  {
    "path": "src/screenshotbot/static/assets/js/annotate.js",
    "content": "window.addEventListener('DOMContentLoaded', function () {\n    var cvs = document.querySelector(\"#annotation-editor\");\n    if (cvs !== null) {\n        console.log(\"found Annotation canvas\");\n        var img = document.querySelector(\"#annotation-editor-img\");\n        console.log(\"using image: \", img);\n        cvs.height = img.height;\n        cvs.width = img.width;\n        cvs.style.width = img.width / 10 + \"rem\";\n        cvs.style.height = img.height / 10 + \"rem\";\n        var canvas = new fabric.Canvas(cvs);\n        var imgInstance = new fabric.Image(img, {\n            left: 0,\n            top: 0,\n            selectable: false,\n            opacity: 1.0,\n        });\n        canvas.add(imgInstance);\n\n        canvas.on('selection:created', function (options) {\n            console.log(\"options: \", options);\n        });\n\n        var startX, startY;\n        var isDragging = false;\n        canvas.on('mouse:down', function (e) {\n            console.log(e);\n            startX = e.pointer.x;\n            startY = e.pointer.y;\n            isDragging = true;\n        });\n\n        function updateImageData() {\n            canvas.requestRenderAll();\n            cvs.closest(\"form\").querySelector(\"input[name='image-data']\").value =\n                canvas.toDataURL();\n        }\n\n        function addRect(props) {\n            var rect = new fabric.Rect(Object.assign(\n                {},\n                props,\n                {\n                    lockRotation: true,\n                    centeredRotation: true,\n                    fill: \"#ff000000\",\n                    stroke: \"#ff0000ff\",\n                    strokeWidth: 5,\n                }\n            ));\n\n            rect.on('mousedown', function() {\n                isDragging = false;\n            });\n\n            rect.setControlVisible('mtr', false);\n\n            rect.on('object:moving', function () {\n                isDragging = false;\n            });\n\n            canvas.add(rect);\n\n            updateImageData();\n\n            return rect;\n        }\n\n\n        canvas.on('mouse:up', function (e) {\n            if (!isDragging)  {\n                return;\n            }\n\n            isDragging = false;\n            console.log(e);\n            var x = e.pointer.x, y = e.pointer.y;\n            console.log(\"will draw on: \", startX, startY, x, y);\n\n            var rect = addRect({\n                left: startX,\n                top: startY,\n                height: y - startY,\n                width: x - startX,\n            });\n\n            canvas.setActiveObject(rect);\n            console.log(\"added rectangle\");\n\n        });\n        console.log(\"all done\");\n        canvas.requestRenderAll();\n\n    }\n});\n"
  },
  {
    "path": "src/screenshotbot/static/assets/js/aos.js",
    "content": "!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof exports?exports.AOS=t():e.AOS=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={exports:{},id:o,loaded:!1};return e[o].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p=\"dist/\",t(0)}([function(e,t,n){\"use strict\";function o(e){return e&&e.__esModule?e:{default:e}}var i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},r=n(1),a=(o(r),n(6)),u=o(a),c=n(7),f=o(c),s=n(8),d=o(s),l=n(9),p=o(l),m=n(10),b=o(m),v=n(11),y=o(v),g=n(14),h=o(g),w=[],k=!1,x=document.all&&!window.atob,j={offset:120,delay:0,easing:\"ease\",duration:400,disable:!1,once:!1,startEvent:\"DOMContentLoaded\",throttleDelay:99,debounceDelay:50,disableMutationObserver:!1},O=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(e&&(k=!0),k)return w=(0,y.default)(w,j),(0,b.default)(w,j.once),w},_=function(){w=(0,h.default)(),O()},S=function(){w.forEach(function(e,t){e.node.removeAttribute(\"data-aos\"),e.node.removeAttribute(\"data-aos-easing\"),e.node.removeAttribute(\"data-aos-duration\"),e.node.removeAttribute(\"data-aos-delay\")})},z=function(e){return e===!0||\"mobile\"===e&&p.default.mobile()||\"phone\"===e&&p.default.phone()||\"tablet\"===e&&p.default.tablet()||\"function\"==typeof e&&e()===!0},A=function(e){return j=i(j,e),w=(0,h.default)(),z(j.disable)||x?S():(document.querySelector(\"body\").setAttribute(\"data-aos-easing\",j.easing),document.querySelector(\"body\").setAttribute(\"data-aos-duration\",j.duration),document.querySelector(\"body\").setAttribute(\"data-aos-delay\",j.delay),\"DOMContentLoaded\"===j.startEvent&&[\"complete\",\"interactive\"].indexOf(document.readyState)>-1?O(!0):\"load\"===j.startEvent?window.addEventListener(j.startEvent,function(){O(!0)}):document.addEventListener(j.startEvent,function(){O(!0)}),window.addEventListener(\"resize\",(0,f.default)(O,j.debounceDelay,!0)),window.addEventListener(\"orientationchange\",(0,f.default)(O,j.debounceDelay,!0)),window.addEventListener(\"scroll\",(0,u.default)(function(){(0,b.default)(w,j.once)},j.throttleDelay)),j.disableMutationObserver||(0,d.default)(\"[data-aos]\",_),w)};e.exports={init:A,refresh:O,refreshHard:_}},function(e,t){},,,,,function(e,t){(function(t){\"use strict\";function n(e,t,n){function o(t){var n=b,o=v;return b=v=void 0,k=t,g=e.apply(o,n)}function r(e){return k=e,h=setTimeout(s,t),_?o(e):g}function a(e){var n=e-w,o=e-k,i=t-n;return S?j(i,y-o):i}function c(e){var n=e-w,o=e-k;return void 0===w||n>=t||n<0||S&&o>=y}function s(){var e=O();return c(e)?d(e):void(h=setTimeout(s,a(e)))}function d(e){return h=void 0,z&&b?o(e):(b=v=void 0,g)}function l(){void 0!==h&&clearTimeout(h),k=0,b=w=v=h=void 0}function p(){return void 0===h?g:d(O())}function m(){var e=O(),n=c(e);if(b=arguments,v=this,w=e,n){if(void 0===h)return r(w);if(S)return h=setTimeout(s,t),o(w)}return void 0===h&&(h=setTimeout(s,t)),g}var b,v,y,g,h,w,k=0,_=!1,S=!1,z=!0;if(\"function\"!=typeof e)throw new TypeError(f);return t=u(t)||0,i(n)&&(_=!!n.leading,S=\"maxWait\"in n,y=S?x(u(n.maxWait)||0,t):y,z=\"trailing\"in n?!!n.trailing:z),m.cancel=l,m.flush=p,m}function o(e,t,o){var r=!0,a=!0;if(\"function\"!=typeof e)throw new TypeError(f);return i(o)&&(r=\"leading\"in o?!!o.leading:r,a=\"trailing\"in o?!!o.trailing:a),n(e,t,{leading:r,maxWait:t,trailing:a})}function i(e){var t=\"undefined\"==typeof e?\"undefined\":c(e);return!!e&&(\"object\"==t||\"function\"==t)}function r(e){return!!e&&\"object\"==(\"undefined\"==typeof e?\"undefined\":c(e))}function a(e){return\"symbol\"==(\"undefined\"==typeof e?\"undefined\":c(e))||r(e)&&k.call(e)==d}function u(e){if(\"number\"==typeof e)return e;if(a(e))return s;if(i(e)){var t=\"function\"==typeof e.valueOf?e.valueOf():e;e=i(t)?t+\"\":t}if(\"string\"!=typeof e)return 0===e?e:+e;e=e.replace(l,\"\");var n=m.test(e);return n||b.test(e)?v(e.slice(2),n?2:8):p.test(e)?s:+e}var c=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e},f=\"Expected a function\",s=NaN,d=\"[object Symbol]\",l=/^\\s+|\\s+$/g,p=/^[-+]0x[0-9a-f]+$/i,m=/^0b[01]+$/i,b=/^0o[0-7]+$/i,v=parseInt,y=\"object\"==(\"undefined\"==typeof t?\"undefined\":c(t))&&t&&t.Object===Object&&t,g=\"object\"==(\"undefined\"==typeof self?\"undefined\":c(self))&&self&&self.Object===Object&&self,h=y||g||Function(\"return this\")(),w=Object.prototype,k=w.toString,x=Math.max,j=Math.min,O=function(){return h.Date.now()};e.exports=o}).call(t,function(){return this}())},function(e,t){(function(t){\"use strict\";function n(e,t,n){function i(t){var n=b,o=v;return b=v=void 0,O=t,g=e.apply(o,n)}function r(e){return O=e,h=setTimeout(s,t),_?i(e):g}function u(e){var n=e-w,o=e-O,i=t-n;return S?x(i,y-o):i}function f(e){var n=e-w,o=e-O;return void 0===w||n>=t||n<0||S&&o>=y}function s(){var e=j();return f(e)?d(e):void(h=setTimeout(s,u(e)))}function d(e){return h=void 0,z&&b?i(e):(b=v=void 0,g)}function l(){void 0!==h&&clearTimeout(h),O=0,b=w=v=h=void 0}function p(){return void 0===h?g:d(j())}function m(){var e=j(),n=f(e);if(b=arguments,v=this,w=e,n){if(void 0===h)return r(w);if(S)return h=setTimeout(s,t),i(w)}return void 0===h&&(h=setTimeout(s,t)),g}var b,v,y,g,h,w,O=0,_=!1,S=!1,z=!0;if(\"function\"!=typeof e)throw new TypeError(c);return t=a(t)||0,o(n)&&(_=!!n.leading,S=\"maxWait\"in n,y=S?k(a(n.maxWait)||0,t):y,z=\"trailing\"in n?!!n.trailing:z),m.cancel=l,m.flush=p,m}function o(e){var t=\"undefined\"==typeof e?\"undefined\":u(e);return!!e&&(\"object\"==t||\"function\"==t)}function i(e){return!!e&&\"object\"==(\"undefined\"==typeof e?\"undefined\":u(e))}function r(e){return\"symbol\"==(\"undefined\"==typeof e?\"undefined\":u(e))||i(e)&&w.call(e)==s}function a(e){if(\"number\"==typeof e)return e;if(r(e))return f;if(o(e)){var t=\"function\"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+\"\":t}if(\"string\"!=typeof e)return 0===e?e:+e;e=e.replace(d,\"\");var n=p.test(e);return n||m.test(e)?b(e.slice(2),n?2:8):l.test(e)?f:+e}var u=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e},c=\"Expected a function\",f=NaN,s=\"[object Symbol]\",d=/^\\s+|\\s+$/g,l=/^[-+]0x[0-9a-f]+$/i,p=/^0b[01]+$/i,m=/^0o[0-7]+$/i,b=parseInt,v=\"object\"==(\"undefined\"==typeof t?\"undefined\":u(t))&&t&&t.Object===Object&&t,y=\"object\"==(\"undefined\"==typeof self?\"undefined\":u(self))&&self&&self.Object===Object&&self,g=v||y||Function(\"return this\")(),h=Object.prototype,w=h.toString,k=Math.max,x=Math.min,j=function(){return g.Date.now()};e.exports=n}).call(t,function(){return this}())},function(e,t){\"use strict\";function n(e,t){var n=new r(o);a=t,n.observe(i.documentElement,{childList:!0,subtree:!0,removedNodes:!0})}function o(e){e&&e.forEach(function(e){var t=Array.prototype.slice.call(e.addedNodes),n=Array.prototype.slice.call(e.removedNodes),o=t.concat(n).filter(function(e){return e.hasAttribute&&e.hasAttribute(\"data-aos\")}).length;o&&a()})}Object.defineProperty(t,\"__esModule\",{value:!0});var i=window.document,r=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,a=function(){};t.default=n},function(e,t){\"use strict\";function n(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function o(){return navigator.userAgent||navigator.vendor||window.opera||\"\"}Object.defineProperty(t,\"__esModule\",{value:!0});var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,\"value\"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,n,o){return n&&e(t.prototype,n),o&&e(t,o),t}}(),r=/(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i,a=/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-/i,u=/(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i,c=/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-/i,f=function(){function e(){n(this,e)}return i(e,[{key:\"phone\",value:function(){var e=o();return!(!r.test(e)&&!a.test(e.substr(0,4)))}},{key:\"mobile\",value:function(){var e=o();return!(!u.test(e)&&!c.test(e.substr(0,4)))}},{key:\"tablet\",value:function(){return this.mobile()&&!this.phone()}}]),e}();t.default=new f},function(e,t){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var n=function(e,t,n){var o=e.node.getAttribute(\"data-aos-once\");t>e.position?e.node.classList.add(\"aos-animate\"):\"undefined\"!=typeof o&&(\"false\"===o||!n&&\"true\"!==o)&&e.node.classList.remove(\"aos-animate\")},o=function(e,t){var o=window.pageYOffset,i=window.innerHeight;e.forEach(function(e,r){n(e,i+o,t)})};t.default=o},function(e,t,n){\"use strict\";function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,\"__esModule\",{value:!0});var i=n(12),r=o(i),a=function(e,t){return e.forEach(function(e,n){e.node.classList.add(\"aos-init\"),e.position=(0,r.default)(e.node,t.offset)}),e};t.default=a},function(e,t,n){\"use strict\";function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,\"__esModule\",{value:!0});var i=n(13),r=o(i),a=function(e,t){var n=0,o=0,i=window.innerHeight,a={offset:e.getAttribute(\"data-aos-offset\"),anchor:e.getAttribute(\"data-aos-anchor\"),anchorPlacement:e.getAttribute(\"data-aos-anchor-placement\")};switch(a.offset&&!isNaN(a.offset)&&(o=parseInt(a.offset)),a.anchor&&document.querySelectorAll(a.anchor)&&(e=document.querySelectorAll(a.anchor)[0]),n=(0,r.default)(e).top,a.anchorPlacement){case\"top-bottom\":break;case\"center-bottom\":n+=e.offsetHeight/2;break;case\"bottom-bottom\":n+=e.offsetHeight;break;case\"top-center\":n+=i/2;break;case\"bottom-center\":n+=i/2+e.offsetHeight;break;case\"center-center\":n+=i/2+e.offsetHeight/2;break;case\"top-top\":n+=i;break;case\"bottom-top\":n+=e.offsetHeight+i;break;case\"center-top\":n+=e.offsetHeight/2+i}return a.anchorPlacement||a.offset||isNaN(t)||(o=t),n+o};t.default=a},function(e,t){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var n=function(e){for(var t=0,n=0;e&&!isNaN(e.offsetLeft)&&!isNaN(e.offsetTop);)t+=e.offsetLeft-(\"BODY\"!=e.tagName?e.scrollLeft:0),n+=e.offsetTop-(\"BODY\"!=e.tagName?e.scrollTop:0),e=e.offsetParent;return{top:n,left:t}};t.default=n},function(e,t){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var n=function(e){return e=e||document.querySelectorAll(\"[data-aos]\"),Array.prototype.map.call(e,function(e){return{node:e}})};t.default=n}])});"
  },
  {
    "path": "src/screenshotbot/static/assets/js/browser-tests.js",
    "content": "\nfunction runBrowserTests() {\n    function log(msg) {\n        var $span = $(\"<div></div>\");\n        $span.append(msg);\n        $(\"#results\").append($span);\n    }\n\n    log(\"Starting up\");\n\n    var counter = 1;\n    const timeout = setInterval(function () {\n        counter ++;\n        log(\"Timeout log for \" + (counter * 100) + \"ms mark\");\n    }, 100);\n\n    var startTime = new Date();\n\n    function getElapsedTime() {\n        return ( (new Date()).getTime() - startTime.getTime())/1000;\n    }\n\n    function logElapsedTime() {\n        log(\"Took \" + getElapsedTime() + \" seconds\");\n        clearInterval(timeout);\n    }\n\n    var domain = $(\"body\").data(\"domain\");\n    log(\"Fetching \" + domain);\n\n    $.ajax({\n        url: domain,\n        method: \"GET\",\n        success: function (){\n            log(\"ERROR: We were able to fetch \" + domain);\n            logElapsedTime();\n\n            secondTest();\n        },\n        error: function (jqxhr, textStatus){\n            log(\"SUCCESS: \" + domain + \" was inaccessible because of: \" + textStatus);\n            logElapsedTime();\n            if (getElapsedTime() > 5) {\n                log(\"ERROR: Took a while to result in an failed ajax request\");\n            }\n        }\n\n    });\n}\n\nrunBrowserTests();\n"
  },
  {
    "path": "src/screenshotbot/task-integration-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/task-integration-api\n    (:use #:cl #:alexandria)\n  (:export\n   #:task-integration\n   #:send-task\n   #:enabledp\n   #:register-task-integration\n   #:task-integrations-list\n   #:task-integration-company\n   #:get-issue-content))\n(in-package :screenshotbot/task-integration-api)\n\n(defclass task-integration ()\n  ((company :initarg :company\n            :reader task-integration-company)))\n\n(defgeneric send-task (task-integration report))\n\n(defgeneric enabledp (task-integration))\n\n(defgeneric get-issue-content (task-integration report))\n\n(defvar *integrations* nil)\n\n(defun register-task-integration (name)\n  (assert (symbolp name))\n  (pushnew name *integrations*))\n\n(defun task-integrations-list ()\n  (reverse *integrations*))\n"
  },
  {
    "path": "src/screenshotbot/tasks/common.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/tasks/common\n  (:use #:cl\n        #:alexandria\n        #:screenshotbot/task-integration-api\n        #:screenshotbot/promote-api\n        #:screenshotbot/model/company\n        #:screenshotbot/api/promote\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/report\n        #:screenshotbot/model/screenshot\n        #:screenshotbot/dashboard/reports\n        #:screenshotbot/compare)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/compare\n                #:warmup-comparison-images)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-changes\n                #:make-diff-report\n                #:diff-report-empty-p\n                #:diff-report-title)\n  (:import-from #:screenshotbot/model/company\n                #:add-company-report)\n  (:import-from #:screenshotbot/webhook/model\n                #:webhook-payload)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:screenshotbot/user-api\n                #:channel-name)\n  (:import-from #:screenshotbot/api/recorder-run\n                #:run-to-dto)\n  (:import-from #:screenshotbot/webhook/webhook\n                #:send-webhook)\n  (:import-from #:screenshotbot/model/report\n                #:report-to-dto)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:warmup-report)\n  (:local-nicknames (#:dto #:screenshotbot/api/model)))\n(in-package :screenshotbot/tasks/common)\n\n(defclass noop-task-integration (task-integration)\n  ())\n\n(register-task-integration 'noop-task-integration)\n\n\n(defmethod get-enabled-task-integrations (company channel)\n  (remove-if-not\n   'enabledp\n    (loop for name in (task-integrations-list)\n          collect\n          (make-instance name :company company))))\n\n\n(defmethod get-issue-content ((inst task-integration) report)\n  (declare (ignore inst))\n  (let ((previous-run (report-previous-run report))\n        (run (report-run report)))\n   (format nil\n           \"Some screenshots have changed between commits ~a and ~a\n\nSee ~a to view the changes.\n\nDon't panic! This is might not be a regression! Usually screenshot changes are intentional, but it's still good to review the changes to make sure it's not a regression.\"\n           (recorder-run-commit previous-run)\n           (recorder-run-commit run)\n           (report-link report))))\n\n\n(defmethod send-task ((inst noop-task-integration) report)\n  nil)\n\n(defmethod enabledp ((inst noop-task-integration))\n  t)\n\n(defmethod maybe-send-tasks ((promoter master-promoter) run)\n  (%maybe-send-tasks run))\n\n(defclass channel-promoted-payload (webhook-payload)\n  ((channel :initarg :channel\n            :json-key \"channel\"\n            :json-type :string\n            :documentation \"The channel name on which this promotion happened.\")\n   (branch :initarg :branch\n           :json-key \"branch\"\n           :json-type (or null :string)\n           :documentation \"The git branch on which this promotion happened. e.g. `main`\")\n   (run :initarg :run\n        :json-key \"run\"\n        :json-type dto:run\n        :documentation \"The run that triggered this event\")\n   (previous-run :initarg :previous-run\n                 :json-key \"previousRun\"\n                 :json-type (or null dto:run)\n                 :documentation \"The previous promoted run\")\n   (report :initarg :report\n           :json-key \"report\"\n           :json-type dto:report\n           :documentation \"The report object that was generated\"))\n  (:documentation \"This event is dispatched when a promotion happens on a branch that\n    resulted in a report.\")\n  (:metaclass ext-json-serializable-class)\n  (:default-initargs :event \"channel.promotion\"))\n\n(defun %maybe-send-tasks (run)\n  \"Send any tasks if required, called from a background job without\n  any db.\"\n  (restart-case\n      (let* ((previous-run (recorder-previous-run run))\n             (channel (recorder-run-channel run))\n             (company (recorder-run-company run)))\n        (let ((diff-report (make-diff-report run previous-run)))\n          (cond\n            ((not (was-promoted-p run))\n             (log:info \"This run wasn't promoted, so we're not sending notifications for it\"))\n            ((diff-report-empty-p diff-report)\n             (log:info \"Diff report is empty, not sending any tasks\"))\n            (t\n             ;; Note that previous-run might be NIL!\n             (log:info \"Screenshotbot is ready to send out a task, fingers crossed\")\n             (let* ((report (let ((report (make-instance 'report\n                                                         :run run\n                                                         :promotion-report-p t\n                                                         :channel channel\n                                                         :num-changes (length (diff-report-changes diff-report))\n                                                         :previous-run previous-run\n                                                         :title (diff-report-title diff-report))))\n                              (add-company-report\n                               (recorder-run-company run)\n                               report)\n                              report)))\n               (warmup-report report)\n               (send-webhook company\n                             (make-instance 'channel-promoted-payload\n                                            :channel (channel-name channel)\n                                            :branch (recorder-run-branch run)\n                                            :run (run-to-dto run)\n                                            :previous-run (?. run-to-dto previous-run)\n                                            :report (report-to-dto report)))\n               (dolist (task-integration (get-enabled-task-integrations company channel))\n                 (let ((task-integration task-integration))\n                   (labels ((thread ()\n                              (restart-case\n                                  (send-task task-integration report)\n                                (retry-task-integration ()\n                                  (thread))\n                                (ignore-task-integation ()\n                                  nil))))\n                     (util:make-thread #'thread)))))))))\n      (dangerous-retry-full-send-task-flow ()\n        (%maybe-send-tasks run))))\n"
  },
  {
    "path": "src/screenshotbot/tasks/test-common.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/tasks/test-common\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run\n                #:recorder-run)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/api/promote\n                #:%maybe-send-tasks)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/testing\n                #:with-installation))\n(in-package :screenshotbot/tasks/test-common)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (let* ((company (make-instance 'company))\n            (img1 (make-image :pathname (asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\")))\n            (screenshot1 (make-screenshot :name \"foo\" :image img1))\n            (channel (make-instance 'channel\n                                    :name \"channel\"\n                                    :company company))\n            (run (make-recorder-run\n                  :channel channel\n                  :screenshots (list screenshot1)\n                  :company company)))\n       (&body)))))\n\n(test happy-path-sending-tasks\n  (with-fixture state ()\n    (finishes\n      (%maybe-send-tasks run))))\n\n(test happy-path-sending-tasks-with-previous-run\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :screenshots nil\n                :company company\n                :previous-run run)))\n     (finishes\n       (%maybe-send-tasks run)))))\n\n"
  },
  {
    "path": "src/screenshotbot/template.lisp",
    "content": ";;;; -*- coding: utf-8 -*-\n;;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/template\n    (:use #:cl\n          #:alexandria\n          #:screenshotbot/cdn\n          #:screenshotbot/user-api)\n  (:use-reexport #:screenshotbot/cdn\n                 #:core/ui/mdi)\n  (:export #:selenium-css\n           #:mdi\n           #:landing-head\n           #:analyticsp\n           #:render-landing-template\n           #:google-analytics\n           #:dashboard-template\n           #:render-extra-scripts\n           #:*favicon*\n           #:left-side-bar ;; todo: defined elsewhere\n           #:user-notice-list ;; todo: defined elsewhere\n           #:app-template\n           #:landing-template\n           #:dashboard-head\n           #:*template-override*\n           #:mailto)\n  (:import-from #:screenshotbot/installation\n                #:installation-cdn\n                #:installation)\n  (:import-from #:screenshotbot/user-api\n                #:adminp\n                #:current-company)\n  (:import-from #:markup\n                #:deftag)\n  (:import-from #:screenshotbot/server\n                #:home-url\n                #:screenshotbot-template\n                #:acceptor\n                #:defhandler\n                #:staging-p\n                #:*seleniump*\n                #:*reuben-ip*\n                #:logged-in-p)\n  (:import-from #:screenshotbot/user-api\n                #:user-email)\n  (:import-from #:util\n                #:oid)\n  (:import-from #:core/ui/template\n                #:render-template)\n  (:import-from #:core/installation/installation\n                #:site-alert\n                #:installation-domain)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util.cdn\n                #:make-cdn)\n  (:import-from #:screenshotbot/mailer\n                #:mailer*\n                #:send-mail))\n\n(named-readtables:in-readtable markup:syntax)\n\n(defparameter *favicon* \"/assets/images/logo-favicon.png\")\n\n(defun og-image ()\n  (make-cdn \"/assets/images/logos/text-logo-with-vertical-space.png\"))\n\n(defmethod render-extra-scripts ((installation installation))\n  nil)\n\n\n(deftag selenium-css ()\n  (when *seleniump*\n    ;; if you change this, change this link in default.js too!\n    <link rel= \"stylesheet\" href= \"/assets/css/selenium.css\" />))\n\n\n(defmethod analyticsp ((self installation))\n  nil)\n\n(defmethod analyticsp :around (self)\n  (and\n   (boundp 'hunchentoot:*request*) ;; for tests\n   (call-next-method)))\n\n(defmethod google-analytics-impl ((self installation))\n  nil)\n\n(markup:deftag google-analytics ()\n  (google-analytics-impl (installation)))\n\n(deftag og-details ()\n  <markup:merge-tag>\n    <meta name=\"image\" property=\"og:image\" content= (og-image) />\n  </markup:merge-tag>)\n\n(deftag dashboard-head (&key jquery-ui\n                        (title \"Screenshotbot\")\n                        codemirror)\n      <head>\n      <meta charset=\"utf-8\" />\n      <title>,(or title \"Screenshotbot\")</title>\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <!-- App favicon -->\n        <link rel=\"shortcut icon\" href= (util.cdn:make-cdn *favicon*) />\n\n        <og-details />\n  ,(when jquery-ui\n     <link rel= \"stylesheet\" href= \"https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css\" />)\n\n  ,@(when codemirror\n     (list\n      <:link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/codemirror.min.css\" />\n      <:link rel= \"stylesheeet\"\n            href= \"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/theme/blackboard.min.css\" /> ))\n  <link href=\"/assets/css/default.css\" rel=\"stylesheet\" type=\"text/css\" id=\"light-style\" />\n  <google-analytics />\n\n  <selenium-css />\n    </head>)\n\n(defmethod billing-banner (installation company user)\n  nil)\n\n(deftag dashboard-template (children &key stripe\n                            scripts\n                            (title \"Screenshotbot\")\n                            (body-class \"dashboard\")\n                            (user (current-user))\n                            (left-nav-bar t)\n                            codemirror\n                            (company (current-company))\n                            (content t)\n                            (script-name (hunchentoot:script-name hunchentoot:*request*))\n                            (left-nav-bar t)\n                            jquery-ui\n                            admin)\n  (declare (ignore scripts))\n  <html lang=\"en\">\n    <dashboard-head jquery-ui=jquery-ui codemirror=codemirror title=title />\n\n    <body class= body-class\n          data-user-id= (when user (oid user))\n          data-user-email= (when user (user-email user))\n          data-user-name= (when user (user-full-name user)) >\n      <!-- Begin page -->\n      \n      ,(or\n        ;; Don't show both site-alert and billing banner, just to\n        ;; avoid breakages\n        (let ((alert (site-alert (installation))))\n          (when alert\n            <div class=\"site-alert-container\">\n            ,(progn alert)\n            </div>))\n        (billing-banner (installation) (auth:current-company) user))\n\n\n      ,(when left-nav-bar\n         <left-side-bar user=user company=company script-name=script-name />)\n      <div class=\"content-page bg-light-lighten\">\n          ,(cond\n             (content\n              <!-- Start Content-->\n              <div class=\"content\">\n                ,@children\n              </div>)\n             (t\n              <div>\n                ,@children\n              </div>))        \n      </div> <!-- content-page -->\n\n      ,(when (and left-nav-bar user)\n         <user-notice-list user=user />)\n\n      <!-- bundle -->\n\n\n      <script src= \"/assets/js/dashboard.js\" />\n\n      ,@(when codemirror\n          (list\n           <:script src= \"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/codemirror.min.js\" />\n           <:script src= \"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/mode/yaml/yaml.min.js\" />))\n\n\n      ,(when stripe\n         <script src=\"https://js.stripe.com/v3/\"></script>)\n\n\n  ,(when admin\n     <script src= \"/assets/js/admin.js\" />)\n  ,(render-extra-scripts (installation))\n    </body>\n  </html>)\n\n(defvar *template-override* nil)\n\n(deftag app-template (children &key stripe\n                      transparent\n                      admin\n                      jquery-ui\n                      title\n                      codemirror\n                      (body-class \"dashboard\")\n                      scripts\n                      (content t)\n                      script-name\n                      (nav-bar-style :dark))\n  (declare (ignore nav-bar-style\n                   transparent))\n  (cond\n    (*template-override*\n     (funcall *template-override*\n              children))\n    (t\n     <dashboard-template admin=admin jquery-ui=jquery-ui stripe=stripe scripts=scripts\n                         codemirror=codemirror\n                         title=title\n                         body-class=body-class\n                         content=content\n                         script-name= (or script-name (hunchentoot:script-name*)) >,@children </dashboard-template>)))\n\n(defmethod hunchentoot:acceptor-status-message ((acceptor acceptor)\n                                                (http-status-code (eql hunchentoot:+http-not-found+))\n                                                &rest properties\n                                                &key\n                                                  &allow-other-keys)\n  (markup:write-html\n    <landing-template>\n      <section class=\"full-height\">\n        <div class= \"container mt-3\">\n\n          <a href= (home-url) ><img src= \"/assets/images/logo-dark.svg\" style=\n                            \"max-height: 2em; width: auto; margin-bottom: 1em; margin-top: 2em\" /></a>\n          <h1>The page you're looking for doesn't exist.</h1>\n\n          ,(progn\n             #-screenshotbot-oss\n             <p>If this doesn't look right, reach out at <a href=\"mailto:support@screenshotbot.io\">support@screenshotbot.io</a>, and we'll resolve it immediately.</p>)\n        </div>\n      </section>\n    </landing-template>))\n\n(defmethod hunchentoot:acceptor-status-message  ((acceptor acceptor)\n                                                 (http-status-code (eql 500))\n                                                 &rest properties\n                                                 &key &allow-other-keys)\n  ;; something went wrong, let's be very careful about rendering this,\n  ;; so we don't get into a secondary crash\n  (cond\n    ((staging-p)\n     (call-next-method))\n    (t\n     (something-went-wrong))))\n\n(defun something-went-wrong ()\n  (let ((util.cdn:*cdn-domain*\n          ;; This is outside of acceptor-dispatch-request, so we've\n          ;; lost our setting for cdn-domain.\n          (installation-cdn (installation))))\n    (markup:write-html\n     <html>\n       <landing-head />\n       <body>\n         <section class= \"error-500\" >\n           <div class= \"container full-height\">\n\n             <h1>Oh no! Something went wrong!</h1>\n             <p>We've been notified of this issue and will look into it as soon as we can.</p>\n\n             <p>If this is blocking you, please reach out to <a href= \"mailto:support@screenshotbot.io\">support@screenshotbot.io</a>.</p>\n\n             <p><a href= (home-url) >Home</a>  ,(progn \"|\")\n             <a href= \"javascript:history.back()\">Back to previous page</a></p>\n\n           </div>\n         </section>\n       </body>\n     </html>)))\n\n(Defhandler (get-started :uri \"/get-started\") ()\n  (hex:safe-redirect \"/documentation/getting-started\"))\n\n(deftag mailto (children)\n   <a href= (format nil \"mailto:~a\" (car children))>,@(progn children)</a>)\n\n\n(deftag landing-head (children &key\n                      (title \"Screenshotbot\")\n                      social-title\n                      social-description\n                      social-url\n                      canonical-url\n                      published-at\n                      (simple nil)\n                      (style))\n  (setf style (or style\n                  (if simple\n                      \"/assets/css/default.css\"\n                      #-screenshotbot-oss\n                      \"/assets/css/extended-dashboard.css\"\n                      #+screenshotbot-oss\n                      \"/assets/css/default.css\")))\n\n  (setf social-title (or social-title\n                         \"Scale up your screenshot tests, without the friction\"))\n\n  (setf social-description (or social-description\n                               \"Automated screenshot testing for Android, iOS, and web apps. Catch UI regressions in CI/CD with GitHub, GitLab integration. Better than Git LFS for screenshot storage.\"))\n\n  (setf social-url (or social-url canonical-url))\n\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>,(progn title)</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <meta content=\"Build Pixel Perfect Apps with Screenshot Tests\" name=\"description\" />\n      <meta content=\"Modern Interpreters Inc.\" name=\"author\" />\n      <google-analytics />\n      <!-- App favicon -->\n      <:link rel=\"shortcut icon\" href= (util.cdn:make-cdn *favicon*) />\n\n      <meta name= \"image\" property=\"og:image\"  content= (og-image) />\n      <meta property=\"og:title\" content=social-title />\n      <meta property= \"og:description\" content= social-description />\n\n      ,(when social-url\n         <meta property=\"og:url\" content=social-url />)\n\n      ,(when canonical-url\n         <:link rel= \"canonical\" href= canonical-url />)\n\n      ,(when published-at\n         <meta property= \"article:published_time\" content=published-at />)\n      \n      <meta property=\"og:type\" content=\"website\" />\n      <meta property= \"twitter:card\" content= \"summary\" />\n      <meta property= \"twitter:site\" content=\"@screenshotbotio\" />\n      <meta property= \"twitter:title\" content= social-title />\n      <meta property=\"twitter:image\"  content= (og-image) />\n      <meta property= \"twitter:description\" content= social-description />\n\n      <!-- App css -->\n      <link href=style rel=\"stylesheet\" type=\"text/css\" id=\"light-style\" />\n      <selenium-css />\n      ,@children\n  </head>)\n\n(defun send-escalation-email (user url)\n  (send-mail\n  (mailer*)\n  :from \"arnold@screenshotbot.io\"\n  :to \"arnold@screenshotbot.io\"\n  :subject (format nil \"~a has escalated privileges\"\n                   user)\n  :html-message\n  <html>\n    <body>\n      Escalated privilegs on this page: ,(progn url)\n    </body>\n  </html>))\n\n\n(defun screenshotbot/server:no-access-error-page ()\n  <dashboard-template>\n    <div class= \"main-content\">\n      <div class= \"card-page-container mt-3 mx-auto\">\n        <div class= \"card\">\n          <div class= \"card-body\">\n            You do not have permission to access this page. If you think this is an error please reach out to <a href= \"mailto:support@screenshotbot.io\">support@screenshotbot.io</a>.\n\n            ,(when (?. adminp (auth:current-user))\n               (let* ((url (hunchentoot:request-uri*))\n                      (escalate (nibble ()\n                                  (assert (adminp (auth:current-user)))\n                                  (send-escalation-email (auth:current-user)\n                                                         url)\n                                  (setf (auth:session-value :site-admin-privileges-enabled\n                                                            :expires-in 3600)\n                                        t)\n                                  (hex:safe-redirect url))))\n                 <form method= \"POST\" action=escalate >\n                   <input type= \"submit\" value= \"Escalate privileges\"\n                       class= \"btn btn-danger\" />\n                 </form>))\n          </div>\n        </div>\n      </div>\n    </div>\n  </dashboard-template>)\n\n\n(deftag landing-template (body)\n  (render-landing-template body))\n\n(defmethod render-landing-template (body)\n  <html>\n    <landing-head />\n    <body>\n      ,@body\n    </body>\n  </html>)\n\n(defmethod render-template ((self screenshotbot-template)\n                            children &key title stripe)\n  <app-template title=title stripe=stripe >\n    ,@children\n  </app-template>)\n"
  },
  {
    "path": "src/screenshotbot/test-abstract-pr-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-abstract-pr-promoter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:*logs*\n                #:same-pull-request-p\n                #:pr-merge-base\n                #:make-run-check\n                #:push-remote-check-via-batching\n                #:check-key\n                #:make-check\n                #:actual-sha\n                #:warn-if-not-merge-base\n                #:format-check-title\n                #:make-check-for-report\n                #:check-title\n                #:previous-review\n                #:make-acceptable\n                #:promoter-pull-id\n                #:check-status\n                #:make-check-result-from-diff-report\n                #:check\n                #:plugin-installed?\n                #:valid-repo?\n                #:push-remote-check\n                #:abstract-pr-promoter\n                #:format-updated-summary\n                #:retrieve-run\n                #:run-retriever)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/user-api\n                #:pull-request-url\n                #:%created-at\n                #:created-at\n                #:channel-repo\n                #:user\n                #:channel)\n  (:import-from #:screenshotbot/promote-api\n                #:maybe-promote)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:unchanged-run\n                #:make-recorder-run\n                #:compared-against\n                #:merge-base-failed-warning\n                #:recorder-run-warnings\n                #:recorder-run)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:is-not\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:screenshotbot/model/report\n                #:reports-for-run\n                #:report\n                #:base-acceptable)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-empty-p)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:screenshotbot/git-repo\n                #:commit-graph-dag\n                #:commit-graph\n                #:compute-merge-base\n                #:get-parent-commit)\n  (:import-from #:screenshotbot/model/failed-run\n                #:failed-run)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:util/logger\n                #:format-log)\n  (:import-from #:screenshotbot/model/finalized-commit\n                #:finalized-commit)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-empty-p)\n  (:import-from #:screenshotbot/model/batch\n                #:find-or-create-batch)\n  (:import-from #:util/threading\n                #:make-thread)\n  (:import-from #:screenshotbot/screenshot-api\n                #:make-screenshot)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:import-from #:screenshotbot/model/pr-rollout-rule\n                #:pr-rollout-rule-for-company\n                #:disable-pull-request-checks-p\n                #:pr-rollout-rule)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:screenshotbot/dashboard/compare\n                #:warmup-report)\n  (:import-from #:util/simple-queue\n                #:queue-length\n                #:queue-items\n                #:make-queue))\n(in-package :screenshotbot/test-abstract-pr-promoter)\n\n\n(util/fiveam:def-suite)\n\n(defvar *lock* (bt:make-lock))\n(defvar *cv* (bt:make-condition-variable))\n\n(defconstant +empty-logger+ nil)\n\n(defclass test-logger ()\n  ((stream :initform (make-string-output-stream)\n           :reader test-logger-stream)))\n\n(defmethod format-log ((logger test-logger)\n                       level msg &rest args)\n  (apply #'format (test-logger-stream logger)\n         msg\n         args)\n  (format (test-logger-stream logger) \"~%\"))\n\n(def-fixture state ()\n  (with-test-store (:globally t)\n    (cl-mock:with-mocks ()\n      (with-installation (:globally t)\n        (let* ((company (make-instance 'company))\n               (channel (make-instance 'channel\n                                       :name \"foobar-channel\"\n                                       :company company\n                                       :github-repo \"https://mysite.git/foo.git\"))\n               (run (make-recorder-run\n                     :company company\n                     :channel channel\n                     :commit-hash \"car\"\n                     :merge-base \"foo\"))\n               (user (make-instance 'user\n                                    :full-name \"Arnold\"))\n               (another-run (make-recorder-run\n                             :commit-hash \"foo\"))\n               (promoter (make-instance 'test-promoter)))\n          (&body))))))\n\n(test simple-run-retriever-test\n  (with-fixture state ()\n    (let ((retriever (make-instance 'run-retriever\n                                    :sleep-time 0)))\n      (is (equal nil (lparallel:force (retrieve-run retriever channel \"abcd\"\n                                                    +empty-logger+)))))))\n\n\n\n(test run-retriever-when-commit-has-not-failed-or-finalized\n  (with-fixture state ()\n    (cl-mock:answer (channel-repo channel) :git-repo)\n    (cl-mock:answer (get-parent-commit :git-repo \"abcd\")\n      \"foobar\")\n    (let ((retriever (make-instance 'run-retriever\n                                    :sleep-time 0))\n          (logger (make-instance 'test-logger)))\n      (is (equal nil (lparallel:force\n                      (retrieve-run retriever channel \"abcd\"\n                                    logger))))\n      (assert-that (get-output-stream-string\n                    (test-logger-stream logger))\n                   (contains-string \"Waiting 0s before checking again\")))))\n\n(test run-retriever-when-commit-has-failed\n  (with-fixture state ()\n    (cl-mock:answer (channel-repo channel) :git-repo)\n    (cl-mock:answer (get-parent-commit :git-repo \"abcd\")\n      \"foobar\")\n    (make-instance 'failed-run\n                   :channel channel\n                   :commit \"abcd\")\n    (let ((retriever (make-instance 'run-retriever\n                                    :sleep-time 0))\n          (logger (make-instance 'test-logger)))\n      (is (equal nil (lparallel:force (retrieve-run retriever channel \"abcd\"\n                                                    logger))))\n      (assert-that (get-output-stream-string\n                    (test-logger-stream logger))\n                   (is-not (contains-string \"Waiting 0s before checking again\"))))))\n\n(test run-retriever-when-commit-is-finalized\n  (with-fixture state ()\n    (cl-mock:answer (channel-repo channel) :git-repo)\n    (cl-mock:answer (get-parent-commit :git-repo \"abcd\")\n      \"foobar\")\n    (make-instance 'finalized-commit\n                   :company company\n                   :commit \"abcd\")\n    (let ((retriever (make-instance 'run-retriever\n                                    :sleep-time 0))\n          (logger (make-instance 'test-logger)))\n      (is (equal nil (lparallel:force (retrieve-run retriever channel \"abcd\"\n                                                    logger))))\n      (assert-that (get-output-stream-string\n                    (test-logger-stream logger))\n                   (is-not (contains-string \"Waiting 0s before checking again\"))))))\n\n(test run-retriever-when-commit-is-finalized-but-the-run-is-present\n  (with-fixture state ()\n    (cl-mock:answer (channel-repo channel) :git-repo)\n    (cl-mock:answer (get-parent-commit :git-repo \"abcd\")\n      \"foobar\")\n    (make-instance 'finalized-commit\n                   :company company\n                   :commit \"abcd\")\n\n    (let* ((run (make-recorder-run\n                 :commit-hash \"abcd\"\n                 :trunkp t\n                 :channel channel))\n           (retriever (make-instance 'run-retriever\n                                     :sleep-time 0))\n           (logger (make-instance 'test-logger)))\n      (is (equal run (lparallel:force (retrieve-run retriever channel \"abcd\"\n                                                    logger))))\n      (assert-that (get-output-stream-string\n                    (test-logger-stream logger))\n                   (is-not (contains-string \"Waiting 0s before checking again\"))))))\n\n\n(test format-updated-summary\n  (with-fixture state ()\n    (let ((user (make-instance 'user\n                               :full-name \"Arnold Noronha\")))\n      (is (equal \"accepted by Arnold Noronha\"\n                 (format-updated-summary\n                  :accepted user))))\n    (let ((user (make-instance 'user\n                               :email \"arnold@screenshotbot.io\")))\n      (is (equal \"rejected by arnold@screenshotbot.io\"\n                 (format-updated-summary\n                  :rejected user))))))\n\n(defclass controlled-run-retreiver ()\n  ((promise :initarg :promise\n            :reader %promise)))\n\n(defclass test-promoter (abstract-pr-promoter)\n  ((checks :accessor checks\n           :initform nil)))\n\n(defmethod push-remote-check ((promoter test-promoter)\n                              run check)\n  (bt:with-lock-held (*lock*)\n    (push check (checks promoter))\n    (bt:condition-notify *cv*)))\n\n(defmethod retrieve-run ((self controlled-run-retreiver)\n                         channel\n                         base-commit\n                         logger)\n  (%promise self))\n\n(defmethod valid-repo? ((self test-promoter) repo)\n  t)\n\n(defmethod plugin-installed? ((self test-promoter) company repo-url)\n  t)\n\n\n\n(test updates-pending-state\n  (with-fixture state ()\n    (let* ((promise (lparallel:promise))\n           (promoter (make-instance 'test-promoter\n                                    :run-retriever (make-instance 'controlled-run-retreiver\n                                                                  :promise promise)))\n           (done nil)\n           (thread (make-thread\n                    (lambda ()\n                      (maybe-promote promoter run)\n                      (setf done t)))))\n      (loop until (checks promoter)\n            for i from 0 to 100\n            do (sleep 0.1))\n\n      (assert-that (checks promoter)\n                   (contains (has-typep 'check)))\n\n      (lparallel:fulfill\n          promise\n        another-run)\n      (bt:join-thread thread)\n\n      (assert-that (checks promoter)\n                   (contains (has-typep 'check)\n                             (has-typep 'check)))\n      (is-true done))))\n\n\n(test previously-accepted-pr\n  (with-fixture state ()\n    (let* ((old-user (make-instance 'user))\n           (old-acc (make-instance 'base-acceptable\n                                   :state :rejected\n                                   :user old-user))\n           (promoter (make-instance 'test-promoter)))\n      (answer (warmup-report report))\n      (answer (promoter-pull-id promoter run)\n        20)\n      (answer (make-acceptable promoter report)\n        (make-instance 'base-acceptable))\n      (cl-mock:if-called 'diff-report-empty-p\n        (lambda (diff-report)\n          nil))\n      (answer (previous-review promoter run)\n        (make-instance 'base-acceptable\n                       :state :rejected\n                       :user user))\n      (let ((check\n              (make-check-result-from-diff-report\n               promoter\n               run\n               another-run)))\n        (is (eql :rejected (check-status check)))\n        (assert-that (check-title check)\n                     (contains-string \"previously rejected by Arnold\"))))))\n\n(defclass fake-github-promoter (abstract-pr-promoter)\n  ()\n  (:documentation \"Fake in the sense that it only implements promoter-pull-id\"))\n\n(defmethod promoter-pull-id ((self fake-github-promoter) run)\n  (pull-request-url run))\n\n(test same-pull-request-p-happy-path\n  (with-fixture state ()\n   (let ((promoter (make-instance 'fake-github-promoter)))\n     (is-true (same-pull-request-p\n               promoter\n               (make-recorder-run\n                :pull-request \"foo1\")\n               (make-recorder-run\n                :pull-request \"foo1\"))))))\n\n(test same-pull-request-p-when-pull-id-is-integer\n  \"The Phabricator promoter returns integers\"\n  (with-fixture state ()\n    (let ((promoter (make-instance 'fake-github-promoter)))\n      (cl-mock:if-called 'promoter-pull-id\n                         (lambda (promoter run)\n                           22))\n      (is-true (same-pull-request-p\n                promoter\n                (make-recorder-run\n                 :pull-request \"foo1\")\n                (make-recorder-run\n                 :pull-request \"foo1\"))))))\n\n(test same-pull-request-p-not-same!\n  (with-fixture state ()\n   (let ((promoter (make-instance 'fake-github-promoter)))\n     (is-false (same-pull-request-p\n                promoter\n                (make-recorder-run\n                 :pull-request \"foo2\")\n                (make-recorder-run\n                 :pull-request \"foo1\"))))))\n\n(test same-pull-request-p-when-pull-request-url-is-not-provided-but-branch-is-same\n  (with-fixture state ()\n    (let ((promoter (make-instance 'fake-github-promoter)))\n      (is-true (same-pull-request-p\n                promoter\n                (make-recorder-run\n                 :author \"foo@example.com\"\n                 :work-branch \"carbar\"\n                 :pull-request \"\")\n                (make-recorder-run\n                 :author \"foo@example.com\"\n                 :work-branch \"carbar\"\n                 :pull-request \"\"))))))\n\n\n(test if-the-pull-ids-dont-match-then-its-always-false\n  (with-fixture state ()\n    (let ((promoter (make-instance 'fake-github-promoter)))\n      (is-false (same-pull-request-p\n                 promoter\n                 (make-recorder-run\n                  :author \"foo@example.com\"\n                  :work-branch \"carbar\"\n                  :pull-request \"foo1\")\n                 (make-recorder-run\n                  :author \"foo@example.com\"\n                  :work-branch \"carbar\"\n                  :pull-request \"foo2\"))))))\n\n(test if-one-pull-request-url-is-unavable-then-we-still-match-by-branch\n  (with-fixture state ()\n    (let ((promoter (make-instance 'fake-github-promoter)))\n      (is-true (same-pull-request-p\n                promoter\n                (make-recorder-run\n                 :author \"foo@example.com\"\n                 :work-branch \"carbar\"\n                 :pull-request \"\")\n                (make-recorder-run\n                 :author \"foo@example.com\"\n                 :work-branch \"carbar\"\n                 :pull-request \"foo2\"))))))\n\n(test same-pull-request-p-when-pull-request-url-is-not-provided-but-branch-is-not-same\n  (with-fixture state ()\n    (let ((promoter (make-instance 'fake-github-promoter)))\n      (is-false (same-pull-request-p\n                 promoter\n                 (make-recorder-run\n                  :author \"foo@example.com\"\n                  :work-branch \"carbar2\"\n                  :pull-request \"\")\n                 (make-recorder-run\n                  :author \"foo@example.com\"\n                  :work-branch \"carbar\"\n                  :pull-request \"\"))))))\n\n(test same-pull-request-p-ignores-branch-name-when-empty\n  (with-fixture state ()\n    (let ((promoter (make-instance 'fake-github-promoter)))\n      (is-false (same-pull-request-p\n                 promoter\n                 (make-recorder-run\n                  :author \"foo@example.com\"\n                  :work-branch \"\"\n                  :pull-request \"\")\n                 (make-recorder-run\n                  :author \"foo@example.com\"\n                  :work-branch \"\"\n                  :pull-request \"\"))))))\n\n(test make-check-for-report\n  (with-fixture state ()\n    (is (equal \"1 changes, accepted by Arnold\"\n               (format-check-title \"1 changes\"\n                                   :status :accepted\n                                   :user user)))\n    (is (equal \"1 changes, previously rejected by Arnold\"\n               (format-check-title \"1 changes\"\n                                   :status :rejected\n                                   :previousp t\n                                   :user user)))\n    (is (equal \"1 changes\"\n               (format-check-title \"1 changes\"\n                                   :status :success)))))\n\n(test warn-if-not-merge-base\n  (with-fixture state ()\n    (let ((another-run (make-recorder-run\n                        :commit-hash \"bleh\")))\n      (warn-if-not-merge-base\n       promoter\n       run\n       another-run)\n      (assert-that (recorder-run-warnings run)\n                   (contains\n                    (has-typep 'merge-base-failed-warning)))\n      (assert-that (mapcar #'compared-against (recorder-run-warnings run))\n                   (contains\n                    another-run)))))\n\n\n(test warn-if-not-merge-base-does-nothing-in-the-good-path\n  (with-fixture state ()\n    (warn-if-not-merge-base\n     promoter\n     run\n     another-run)\n    (assert-that (recorder-run-warnings run)\n                 (has-length 0))))\n\n(test make-task-args-override-commit-hash\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :override-commit-hash \"foobar\"\n                :commit-hash \"zoidberg\")))\n      (is (equal \"foobar\" (actual-sha run))))))\n\n(test make-task-args-no-commit-hash\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :channel channel\n                :commit-hash \"zoidberg\")))\n      (is (equal \"zoidberg\" (actual-sha run))))))\n\n(test key-for-make-check\n  (with-fixture state ()\n    (let ((check (make-check run)))\n      (is (equal \"foobar-channel\" (check-key check))))))\n\n(test if-merge-base-is-null-then-do-nothing\n  \"If the git merge-base command failed on the CI server, then this will be null.\"\n  (with-fixture state ()\n    (let ((run (make-recorder-run\n                :company company\n                :channel channel\n                :merge-base nil\n                :commit-hash \"foo\")))\n      (cl-mock:if-called 'retrieve-run\n                         (lambda (retriver channel merge-base run)\n                           (error \"should not be called\")))\n      (maybe-promote promoter run))))\n\n(test make-run-check\n  (with-fixture state ()\n    (assert-that (make-run-check run\n                                 :status :success\n                                 :title \"foobar\")\n                 (has-typep 'check))))\n\n(test on-main-branch-push-a-green-check\n  \"If the git merge-base command failed on the CI server, then this will be null.\"\n  (with-fixture state ()\n    (let ((checks)\n          (run (make-recorder-run\n                :company company\n                :channel channel\n                :work-branch \"master\"\n                :branch \"master\"\n                :merge-base \"foo\"\n                :commit-hash \"foo\")))\n      (cl-mock:if-called 'retrieve-run\n                         (lambda (retriver channel merge-base run)\n                           (error \"should not be called\")))\n      (cl-mock:if-called 'push-remote-check\n                         (lambda (promoter run check)\n                           (push check checks)))\n      (maybe-promote promoter run)\n      (assert-that checks\n                   (contains (has-typep 'check))))))\n\n\n(test unchanged-run-without-batch-should-push-check\n  (with-fixture state ()\n    (let ((unchanged-run (make-instance 'unchanged-run\n                                        :channel channel\n                                        :commit \"abdc\"))\n          (checks))\n      (if-called 'push-remote-check\n                 (lambda (promoter run check)\n                   (push check checks)))\n      (maybe-promote promoter unchanged-run)\n      (assert-that checks (has-length 1))\n      (is (equal \"Nothing to review\" (check-title (car checks))))\n      (is (equal \"foobar-channel\" (check-key (car checks)))))))\n\n(def-fixture unchanged-run (&key (work-branch \"foobar\"))\n  (let* ((batch (find-or-create-batch\n                 :commit \"abcd\"\n                 :company company))\n         (unchanged-run (make-instance 'unchanged-run\n                                       :merge-base \"car\"\n                                       :commit \"abcd\"\n                                       :channel channel\n                                       :work-branch work-branch\n                                       :batch batch)))\n    (&body)))\n\n(test unchanged-run-happy-promotion-path\n  (with-fixture state ()\n    (with-fixture unchanged-run ()\n      (let ((promoted nil))\n        (if-called 'push-remote-check-via-batching\n                   (lambda (&rest args)\n                     (setf promoted t)))\n        (maybe-promote promoter unchanged-run)\n        (is-true promoted)))))\n\n(test unchanged-run-will-still-promote-even-on-master-branch\n  \"This was an accidental behavior, but is the correct behavior when\nsupporting merge queues. In particular, since unchanged-runs never\nresult in reviews, it is safe to promote on non-PR branches. See T1088.\"\n  (with-fixture state ()\n    (with-fixture unchanged-run (:work-branch \"master\")\n      (let ((checks))\n        (if-called 'push-remote-check\n                   (lambda (promoter run check)\n                     (push check checks)))\n        (maybe-promote promoter unchanged-run)\n        (is-true checks)\n        (assert-that checks (has-length 1))\n        (is (equal \"Nothing to review\" (check-title (car checks))))))))\n\n\n(test pr-merge-base-will-compute-from-graph-if-needed\n  (with-fixture state ()\n    (let ((merge-base-args))\n      (if-called 'compute-merge-base\n                 (lambda (&rest args)\n                   (setf merge-base-args args)\n                   \"bleh\"))\n      (let ((run (make-recorder-run\n                  :merge-base nil\n                  :channel channel\n                  :branch-hash \"bb\"\n                  :commit-hash \"aa\")))\n        (is\n         (equal \"bleh\"\n                (pr-merge-base\n                 promoter\n                 run)))\n        (is (equal (list '(\"bb\") \"aa\")\n                   (cdr merge-base-args)))))))\n\n(test pr-merge-base-uses-the-override-commit-hash-when-provided\n  (with-fixture state ()\n    (let ((merge-base-args))\n      (if-called 'compute-merge-base\n                 (lambda (&rest args)\n                   (setf merge-base-args args)\n                   \"bleh\"))\n      (let ((run (make-recorder-run\n                  :merge-base nil\n                  :channel channel\n                  :branch-hash \"bb\"\n                  :override-commit-hash \"cc\"\n                  :commit-hash \"aa\")))\n        (is\n         (equal \"bleh\"\n                (pr-merge-base\n                 promoter\n                 run)))\n        (is (equal (list '(\"bb\") \"aa\")\n                   (cdr merge-base-args)))))))\n\n(test computed-merge-base-will-be-preferred-over-provided-merge-base\n  (with-fixture state ()\n    (let ((merge-base-args))\n      (if-called 'compute-merge-base\n                 (lambda (&rest args)\n                   (setf merge-base-args args)\n                   \"computed-sha\"))\n      (let ((run (make-recorder-run\n                  :merge-base \"provided-sha\"\n                  :channel channel\n                  :branch-hash \"bb\"\n                  :commit-hash \"aa\")))\n        (is\n         (equal \"computed-sha\"\n                (pr-merge-base\n                 promoter\n                 run)))\n        (is (equal (list '(\"bb\") \"aa\")\n                   (cdr merge-base-args)))))))\n\n\n(test pr-merge-base-integration-happy-path\n  (with-fixture state ()\n    (let ((merge-base-args))\n      (let ((run (make-recorder-run\n                  :merge-base nil\n                  :channel channel\n                  :branch-hash \"bb\"\n                  :commit-hash \"aa\")))\n        (is\n         (equal nil\n                (pr-merge-base\n                 promoter\n                 run)))))))\n\n(test previous-review-returns-nil-if-theres-nothing\n  (with-fixture state ()\n    (let ((run1 (make-recorder-run\n                 :channel channel)))\n      (answer (promoter-pull-id promoter run1) \"foo\")\n      (is (eql nil (previous-review promoter run1))))))\n\n\n(test previous-review-returns-the-older-run\n  (with-fixture state ()\n    (flet ((make-recorder-run (&rest args &key created-at &allow-other-keys)\n             (let ((run (apply #'make-recorder-run (remove-from-plist args :created-at))))\n               ;; As of this writing, created-at is actually ignored\n               ;; because of the base has-created-at class.\n               (setf (%created-at run) (+ (get-universal-time)\n                                          -2000\n                                          created-at))\n               run)))\n     (let* ((screenshot (make-screenshot\n                         :name \"foo\"\n                         :image (make-image :pathname (asdf:system-relative-pathname\n                                                       :screenshotbot\n                                                       \"fixture/rose.png\"))))\n            (base-run (make-recorder-run\n                       :created-at 999\n                       :screenshots nil\n                       :channel channel))\n            (run1 (make-recorder-run\n                   :created-at 1000\n                   :screenshots (list screenshot)\n                   :channel channel))\n            (run2 (make-recorder-run\n                   :created-at 1003\n                   :screenshots (list screenshot)\n                   :channel channel))\n            (run3 (make-recorder-run\n                   :created-at 1005\n                   :screenshots (list screenshot)\n                   :channel channel)))\n\n       (let* ((acceptable (make-instance 'base-acceptable\n                                         :user (make-user)\n                                         :state :accepted))\n              (report (make-instance 'report\n                                     :acceptable acceptable\n                                     :run run2\n                                     :previous-run base-run)))\n         (assert-that (reports-for-run run2)\n                      (contains report))\n         (cl-mock:if-called 'promoter-pull-id\n                            (lambda (promoter run)\n                              \"foo\"))\n\n         (is (eql acceptable (previous-review promoter run3))))))))\n\n(test previous-review-returns-the-older-run-for-branch-too\n  (with-fixture state ()\n    (flet ((make-recorder-run (&rest args &key created-at &allow-other-keys)\n             (let ((run (apply #'make-recorder-run\n                               (remove-from-plist args :created-at))))\n               ;; As of this writing, created-at is actually ignored\n               ;; because of the base has-created-at class.\n               (setf (%created-at run) (+ (get-universal-time)\n                                          -2000\n                                          created-at))\n               run)))\n     (let* ((screenshot (make-screenshot\n                         :name \"foo\"\n                         :image (make-image :pathname (asdf:system-relative-pathname\n                                                       :screenshotbot\n                                                       \"fixture/rose.png\"))))\n            (base-run (make-recorder-run\n                       :created-at 999\n                       :screenshots nil\n                       :channel channel))\n            (run1 (make-recorder-run\n                   :created-at 1000\n                   :screenshots (list screenshot)\n                   :work-branch \"foo\"\n                   :channel channel))\n            (run2 (make-recorder-run\n                   :created-at 1003\n                   :work-branch \"foo\"\n                   :screenshots (list screenshot)\n                   :channel channel))\n            (run3 (make-recorder-run\n                   :created-at 1005\n                   :work-branch \"foo\"\n                   :screenshots (list screenshot)\n                   :channel channel)))\n\n       (let* ((acceptable (make-instance 'base-acceptable\n                                         :user (make-user)\n                                         :state :accepted))\n              (report (make-instance 'report\n                                     :acceptable acceptable\n                                     :run run2\n                                     :previous-run base-run)))\n         (assert-that (reports-for-run run2)\n                      (contains report))\n         (cl-mock:if-called 'promoter-pull-id\n                            (lambda (promoter run)\n                              nil))\n\n         (is (eql acceptable (previous-review promoter run3))))))))\n\n(defclass always-blocked-pr-rollout-rule (pr-rollout-rule)\n  ()\n  (:metaclass persistent-class))\n\n(defmethod disable-pull-request-checks-p ((self always-blocked-pr-rollout-rule)\n                                          run)\n  t)\n\n\n(test pr-rollout-rule-blocks-a-request\n  (with-fixture state ()\n    (let ((checks)\n          (run (make-recorder-run\n                :company company\n                :channel channel\n                :work-branch \"some-work\"\n                :branch \"master\"\n                :merge-base \"foo\"\n                :commit-hash \"foo\")))\n\n      (make-instance 'always-blocked-pr-rollout-rule\n                     :company company)\n\n      (is-true (pr-rollout-rule-for-company\n                company))\n\n      (cl-mock:if-called 'push-remote-check\n                         (lambda (promoter run check)\n                           (push check checks)))\n      (maybe-promote promoter run)\n      (assert-that checks\n                   (is-equal-to nil)))))\n\n(test push-remote-check-logs-to-queue\n  \"Test that push-remote-check :before adds entries to *logs* queue\"\n  (with-fixture state ()\n    (let* ((old-logs *logs*)\n           (*logs* (make-queue))\n           (check (make-instance 'check\n                                :sha \"test-sha\"\n                                :key \"test-key\"\n                                :title \"Test check\"\n                                :summary \"Test summary\"\n                                :status :success)))\n      (unwind-protect\n           (progn\n             ;; Push a remote check\n             (push-remote-check promoter run check)\n\n             ;; Verify the log entry was added\n             (assert-that (queue-length *logs*)\n                          (is-equal-to 1)))\n        ;; Restore the original *logs*\n        (setf *logs* old-logs)))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/test-analytics.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-analytics\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/analytics\n                #:analytics-event-ts\n                #:make-digest\n                #:map-analytics-events\n                #:push-analytics-event\n                #:event-session\n                #:analytics-event\n                #:%write-analytics-events\n                #:write-analytics-events\n                #:all-analytics-events\n                #:*events*)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/events\n                #:event-engine\n                #:with-db\n                #:db-engine)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:is-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/misc\n                #:is-null)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/every-item\n                #:every-item)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-analytics)\n\n(util/fiveam:def-suite)\n\n(defclass fake-installation ()\n  ((event-engine :initarg :event-engine\n                 :reader event-engine)\n   (domain :initform \"https://foo.screenshotbot.io\"\n           :reader installation-domain)))\n\n(def-fixture state ()\n  (uiop:with-temporary-file (:pathname pathname)\n    (with-test-store ()\n      (let* ((*installation*\n               (make-instance 'fake-installation\n                              :event-engine (make-instance 'db-engine\n                                                           :connection-spec\n                                                           `(,(namestring pathname))\n                               :database-type :sqlite3))))\n        (with-db (db (event-engine *installation*))\n          (clsql:query\n           \"create table analytics (ip_address text, session text, script_name text,\n                                    referrer text,\n                                    user_agent text,\n                                    cli_session_id text,\n                                    query_string text, ts datetime,\n                                    hostname text, domain text)\"\n           :database db))\n        (let ((*events* nil)\n              (ev1 (make-instance 'analytics-event\n                                  :session (make-digest \"foo\"))))\n          (&body))))))\n\n(test preconditions\n  (with-fixture state ()\n   (is (eql nil (all-analytics-events)))))\n\n(test read-write-analytics\n  (with-fixture state ()\n    (push ev1 *events*)\n    (assert-that (all-analytics-events)\n                 (described-as \"We should look up the uncached versions before we save it\"\n                  (contains ev1)))\n    (%write-analytics-events)\n\n    (assert-that *events*\n                 (described-as \"We should've reset the *events*\"\n                  (is-null)))\n\n    ;; the restored object is not EQL!\n    (assert-that (all-analytics-events)\n                 (described-as \"the objects returned are not EQL\"\n                   (is-not\n                    (has-item ev1)))\n                 (described-as \"the object should still in the saved list\"\n                  (has-length 1)))\n\n    ;; but it should be properly restored\n    (is (equalp (make-digest \"foo\") (event-session (car (all-analytics-events)))))))\n\n(test push-analytics-event\n  (with-fixture state ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (push-analytics-event)\n        (assert-that *events*\n                     (has-length 1))\n        (write-analytics-events)\n        (assert-that (all-analytics-events)\n                     (has-length 1))))))\n\n(test push-analytics-event-when-session-not-created\n  (with-fixture state ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (slot-makunbound (auth:current-session)\n                         'auth::session-key)\n        (push-analytics-event)\n        (assert-that *events*\n                     (has-length 1))\n        (write-analytics-events)\n        (assert-that (all-analytics-events)\n                     (has-length 1))))))\n\n(test push-a-lot-of-analytics-events\n  (with-fixture state ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (loop for i below 100\n              do (push-analytics-event))\n        (is (eql 100 (length *events*)))))))\n\n(test map-analytics-event\n  (with-fixture state ()\n    (push ev1 *events*)\n    (assert-that (map-analytics-events #'event-session)\n                 (described-as \"before being written\"\n                  (contains (make-digest \"foo\"))))\n    (is (equalp nil\n               (map-analytics-events #'event-session\n                                       :keep-if (lambda (x)\n                                                  (declare (ignore x))\n                                                  nil))))\n    (dotimes (i 100)\n      (push ev1 *events*))\n    (assert-that (map-analytics-events #'event-session\n                                       :limit 3)\n                 (described-as \"We should respect the limit\"\n                  (contains (make-digest \"foo\") (make-digest \"foo\") (make-digest \"foo\"))))\n\n    (write-analytics-events)\n    (is (equalp (list (make-digest \"foo\") (make-digest \"foo\") (make-digest \"foo\"))\n               (map-analytics-events #'event-session\n                                     :limit 3)))))\n\n(test serialized-timestamp-is-still-local-time\n  (with-fixture state ()\n    (push ev1 *events*)\n    (assert-that (mapcar #'analytics-event-ts (all-analytics-events))\n                 (described-as \"Before pushing to DB, obviously timestamps haven't been serialized\"\n                  (every-item (has-typep 'local-time:timestamp))))\n    (write-analytics-events)\n    (assert-that (mapcar #'analytics-event-ts (all-analytics-events))\n                 (described-as \"After pushing to DB we deserialize timestamps\"\n                  (every-item (has-typep 'local-time:timestamp))))))\n\n(test push-analytics-event-when-session-is-not-yet-created\n  (with-fixture state ()\n    (with-fake-request ()\n      (auth:with-sessions ()\n        (slot-makunbound\n         (auth:current-session)\n         'auth::session-key)\n        (finishes\n          (push-analytics-event))))))\n"
  },
  {
    "path": "src/screenshotbot/test-artifacts.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-artifacts\n  (:use :cl)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:finishes\n                #:is\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/artifacts\n                #:*artifact-hooks*\n                #:*in-test-p*\n                #:artifact-link\n                #:call-hooks\n                #:def-artifact-hook)\n  (:import-from #:util/store/store\n                #:*object-store*\n                #:object-store)\n  (:import-from #:util/testing\n                #:with-local-acceptor)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:core/installation/installation\n                #:abstract-installation\n                #:*installation*)\n  (:import-from #:screenshotbot/installation\n                #:installation-cdn)\n  (:import-from #:core/installation/auth\n                #:company-for-request)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-artifacts)\n\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (abstract-installation)\n  ())\n\n(defmethod installation-cdn ((self my-installation))\n  \"https://example.com\")\n\n(defmethod company-for-request ((self my-installation) request)\n  nil)\n\n(def-fixture state ()\n  (tmpdir:with-tmpdir (dir)\n    (assert (not (boundp '*object-store*)))\n    (let ((old-hooks *artifact-hooks*))\n      (with-global-binding ((*installation* (make-instance 'my-installation\n                                                           :domain \"foobar.com\")))\n       (unwind-protect\n            (progn\n              (setf *object-store* (namestring dir))\n              (setf old-hooks *artifact-hooks*)\n              (setf *in-test-p* t)\n              (with-local-acceptor (host) ('screenshotbot/server:acceptor)\n                (&body)))\n         (setf *in-test-p* nil)\n         (setf *artifact-hooks* old-hooks)\n         (makunbound '*object-store*))))))\n\n(test upload-and-download ()\n  (with-fixture state ()\n    (with-open-file (output\n                     (ensure-directories-exist\n                      (path:catfile\n                       (object-store)\n                       \"artifacts/test-art\"))\n                     :direction :output)\n      (write-string \"hello\" output)\n      (finish-output output)\n      (is\n       (equal \"hello\"\n              (util/request:http-request\n               (format nil \"~a/artifact/test-art\" host)\n               :want-string t))))))\n\n\n(test upload-and-download-with-ext ()\n  (with-fixture state ()\n    (with-open-file (output\n                     (ensure-directories-exist\n                      (path:catfile\n                       (object-store)\n                       \"artifacts/test-art.txt\"))\n                     :direction :output)\n      (write-string \"hello\" output)\n      (finish-output output)\n      (is\n       (equal \"hello\"\n              (util/request:http-request\n               (format nil \"~a/artifact/test-art.txt\" host)\n               :ensure-success t\n               :want-string t))))))\n\n(test artifact-link ()\n  (with-fixture state ()\n    (with-open-file (output\n                     (ensure-directories-exist\n                      (path:catfile\n                       (object-store)\n                       \"artifacts/test-art.txt\"))\n                     :direction :output)\n      (let ((link (artifact-link \"test-art.txt\")))\n        (is (str:starts-with-p \"/artifact/test-art.txt?\"\n                               link))\n        (finishes\n          (parse-integer\n           (car (last (str:split \"=\" link)))))))))\n\n(test hooks\n  (with-fixture state ()\n    (let ((count 0))\n      (def-artifact-hook ('foo \"bar\")\n        (incf count))\n      (call-hooks \"bar\")\n      (is (eql 1 count)))))\n"
  },
  {
    "path": "src/screenshotbot/test-assets.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-assets\n  (:use #:cl\n        #:fiveam-matchers\n        #:fiveam)\n  (:import-from #:screenshotbot/assets\n                #:define-platform-asset)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:util/store/store\n                #:object-store\n                #:with-test-store)\n  (:import-from #:screenshotbot/installation\n                #:installation))\n(in-package :screenshotbot/test-assets)\n\n(util/fiveam:def-suite)\n\n(test the-generate-fn-exists\n  (is (functionp #'screenshotbot/assets::generate-recorder-platform-assets)))\n\n(define-platform-asset \"dummy\")\n\n(test generate-.sh-uses-domain\n  (with-installation (:installation (make-instance\n                                     'installation\n                                     :domain \"https://example.com\"))\n   (with-test-store ()\n     (generate-dummy-platform-assets)\n     (assert-that (mapcar #'pathname-name\n                          (fad:list-directory (path:catdir (object-store) \"artifacts/\")))\n                  (has-item\n                   \"dummy\"))\n     (let ((contents (uiop:read-file-string (path:catfile (object-store) \"artifacts/dummy.sh\"))))\n       (assert-that contents\n                    (contains-string \"https://example.com/artifact/${VERSION}dummy-linux\"))))))\n\n(test generate-.sh-uses-cdn\n  (with-installation (:installation (make-instance\n                                     'installation\n                                     :cdn \"https://cdn.example.com\"\n                                     :domain \"https://example.com\"))\n   (with-test-store ()\n     (generate-dummy-platform-assets)\n     (let ((contents (uiop:read-file-string (path:catfile (object-store) \"artifacts/dummy.sh\"))))\n       (assert-that contents\n                    (contains-string \"https://cdn.example.com/artifact/${VERSION}dummy-linux\")\n                    (is-not (contains-string \"https://example.com/artifact/\")))))))\n"
  },
  {
    "path": "src/screenshotbot/test-async.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-async\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/async\n                #:magick-future\n                #:sb/future)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-async)\n\n\n(util/fiveam:def-suite)\n\n(test magick-future-happy-path\n  (is (eql 2\n           (lparallel:force\n            (magick-future () (+ 1 1))))))\n"
  },
  {
    "path": "src/screenshotbot/test-audit-log.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-audit-log\n  (:use #:cl\n        #:fiveam\n        #:fiveam-matchers\n        #:screenshotbot/audit-log)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/audit-log\n                #:base-audit-log\n                #:with-audit-log)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-audit-log)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(define-condition test-error (error)\n  ())\n\n(test with-audit-log-propagates-error\n  (with-fixture state ()\n    (signals test-error\n     (with-audit-log (audit-log (make-instance 'base-audit-log))\n\n       (error 'test-error)))\n    (assert-that (class-instances 'base-audit-log)\n                 (has-length 1))\n    (let ((audit-log (car (class-instances 'base-audit-log))))\n      (assert-that (audit-log-error audit-log)\n                   (contains-string \"TEST-ERROR\")))\n    (assert-that (with-audit-log (audit-log (make-instance 'base-audit-log))\n                   :pass)\n                 (equal-to :pass))))\n\n(test if-error-is-already-set-dont-reset\n  (with-fixture state ()\n    (signals test-error\n     (with-audit-log (audit-log (make-instance 'base-audit-log))\n       (with-transaction ()\n        (setf (audit-log-error audit-log) \"foobar\"))\n       (error 'test-error)))\n    (assert-that (class-instances 'base-audit-log)\n                 (has-length 1))\n    (let ((audit-log (car (class-instances 'base-audit-log))))\n      (assert-that (audit-log-error audit-log)\n                   (equal-to \"foobar\")))))\n"
  },
  {
    "path": "src/screenshotbot/test-batch-promoter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-batch-promoter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/batch\n                #:batch-items\n                #:state-invalidated-p\n                #:batch-item\n                #:batch)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:unchanged-run\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/abstract-pr-promoter\n                #:check\n                #:check-summary\n                #:check-title\n                #:check-status\n                #:push-remote-check\n                #:push-remote-check-via-batching\n                #:make-check\n                #:abstract-pr-promoter)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/batch-promoter\n                #:build-check-summary\n                #:compute-check-if-invalidated\n                #:compute-check\n                #:compute-title\n                #:compute-status)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains))\n(in-package :screenshotbot/test-batch-promoter)\n\n(util/fiveam:def-suite)\n\n(defclass test-promoter (abstract-pr-promoter)\n  ((checks :initform nil\n           :accessor checks)))\n\n(defmethod push-remote-check ((self test-promoter)\n                              (batch batch)\n                              check)\n  (push check (checks self)))\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (let* ((company (make-instance 'company))\n            (channel (make-instance 'channel\n                                    :company company\n                                    :name \"channel-1\"))\n            (batch (make-instance 'batch\n                                  :company company\n                                  :commit \"abcd\"\n                                  :name \"my-batch\"))\n            (run (make-recorder-run\n                  :channel channel\n                  :batch batch))\n            (promoter (make-instance 'test-promoter)))\n       (&body)))))\n\n(test simple-batching-test\n  (with-fixture state ()\n    (finishes\n     (push-remote-check-via-batching\n      promoter\n      batch\n      run\n      (make-check run\n                  :status :accepted\n                  :title \"bar\"\n                  :sha \"foo\")))\n    (is (eql 1 (length (checks promoter))))))\n\n(test simple-batching-test-for-unchanged-run\n  (with-fixture state ()\n    (let ((unchanged-run (make-instance 'unchanged-run\n                                        :channel channel\n                                        :batch batch)))\n      (finishes\n        (push-remote-check-via-batching\n         promoter\n         batch\n         unchanged-run\n         :ignored-check)))\n    (assert-that (checks promoter)\n                 (contains (has-typep 'check)))))\n\n(test multiple-unchanged-runs\n  (with-fixture state ()\n    (let ((unchanged-run (make-instance 'unchanged-run\n                                        :channel channel\n                                        :batch batch)))\n      (finishes\n        (push-remote-check-via-batching\n         promoter\n         batch\n         unchanged-run\n         :ignored-check))\n      (finishes\n        (push-remote-check-via-batching\n         promoter\n         batch\n         unchanged-run\n         :ignored-check)))\n    (assert-that (checks promoter)\n                 (contains (has-typep 'check)))))\n\n(test compute-status-happy-path\n  (with-fixture state ()\n    (is (eql :action-required\n             (compute-status\n              (fset:convert 'fset:set\n                            (list\n                             (make-instance 'batch-item\n                                            :status :success)\n                             (make-instance 'batch-item\n                                            :status :action-required))))))\n    (is (eql :accepted\n             (compute-status\n              (fset:convert 'fset:set\n                            (list\n                             (make-instance 'batch-item\n                                            :status :success)\n                             (make-instance 'batch-item\n                                            :status :accepted))))))\n    (is (eql :rejected\n             (compute-status\n              (fset:convert 'fset:set\n                            (list\n                             (make-instance 'batch-item\n                                            :status :rejected)\n                             (make-instance 'batch-item\n                                            :status :accepted))))))))\n\n(test compute-title\n  (with-fixture state ()\n    (is (equal \"some thing some thing\"\n               (compute-title\n                (fset:convert 'fset:set\n                              (list\n                               (make-instance 'batch-item\n                                              :title \"some thing some thing\"))))))))\n\n\n(test compute-check-if-invalidated\n  (with-fixture state ()\n    (let* ((batch (make-instance 'batch :commit \"abcd\"\n                                 :company company\n                                        :name \"FooBar\"))\n           (item (make-instance 'batch-item :batch batch\n                                :run run\n                                            :channel channel\n                                            :status :rejected)))\n      (setf (state-invalidated-p batch) t)\n      (let ((check (compute-check-if-invalidated batch :user)))\n        (is-true check)\n        (is (eql :rejected (check-status check))))\n      (is (eql nil (state-invalidated-p batch)))\n      (is (null (compute-check-if-invalidated batch :user))))))\n\n(test compute-status-for-empty-batch\n  (with-fixture state ()\n    (is (eql :success (compute-status (batch-items batch))))))\n\n(test compute-check-for-empty-batch\n  (with-fixture state ()\n    (let ((check\n            (compute-check batch :user :my-user)))\n      (is (equal :success (check-status check)))\n      (is (equal \"No screenshots changed\" (check-title check)))\n      (is (equal \"Nothing to review\" (check-summary check))))))\n\n\n(test ensure-no-newlines-in-summary\n  (with-fixture state ()\n    (dotimes (i 5)\n      (make-instance 'batch-item\n                     :batch batch\n                     :run run\n                     :channel channel\n                     :title \"Foobar\"\n                     :status :rejected))\n    (let ((summary (build-check-summary batch)))\n      (assert-that (mapcar #'str:trim (str:lines summary))\n                   (is-not (has-item \"\" ))))))\n\n(test if-everything-is-nothing-to-review-then-say-nothing-to-review\n  (with-fixture state ()\n    (dotimes (i 5)\n      (make-instance 'batch-item\n                     :batch batch\n                     :run run\n                     :channel channel\n                     :title \"Nothing to review\"\n                     :status :success))\n    (let ((check (compute-check batch :user :my-user)))\n      (is (equal \"Nothing to review\" (check-title check))))))\n\n(test some-nothing-to-review-but-some-reviews\n  (with-fixture state ()\n    (dotimes (i 5)\n      (make-instance 'batch-item\n                     :batch batch\n                     :run run\n                     :channel channel\n                     :title \"Nothing to review\"\n                     :status :success))\n    (make-instance 'batch-item\n                   :batch batch\n                   :run run\n                   :channel channel\n                   :title \"No screenshots changed\"\n                   :status :success)\n    (let ((check (compute-check batch :user :my-user)))\n      (is (equal \"No screenshots changed\" (check-title check))))))\n"
  },
  {
    "path": "src/screenshotbot/test-billing-meter.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-billing-meter\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-installation)\n  (:import-from #:screenshotbot/billing-meter\n                #:incr-billing-meter-impl\n                #:incr-billing-meter)\n  (:import-from #:core/installation/installation\n                #:*installation*))\n(in-package :screenshotbot/test-billing-meter)\n\n\n(util/fiveam:def-suite)\n\n(test unimpl-metric-happy-path\n  (with-installation ()\n    (finishes\n      (incr-billing-meter :company\n                           :my-metric-name\n                           20))))\n\n(test impl-metric-happy-path-\n  (with-installation ()\n    (finishes\n      (incr-billing-meter-impl\n       *installation*\n       :company\n       :my-metric-name\n       20))))\n"
  },
  {
    "path": "src/screenshotbot/test-cdn.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-cdn\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/cdn\n                #:cdn-for-image-url))\n(in-package :screenshotbot/test-cdn)\n\n(util/fiveam:def-suite)\n\n(test cdn-for-actual-image-blob\n  (let ((util.cdn:*cdn-cache-key* \"car\"))\n    (is (equal \"/assets/image/car.png?cache-key=car\"\n               (cdn-for-image-url \"/assets/image/car.png\")))\n    (is (equal \"/image/blob/car.png?cache-key=i6\"\n               (cdn-for-image-url \"/image/blob/car.png\")))))\n"
  },
  {
    "path": "src/screenshotbot/test-config.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-config\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/config\n                #:load-config\n                #:find-config.lisp)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-config)\n\n(util/fiveam:def-suite)\n\n(defvar *loaded* nil)\n\n(def-fixture state ()\n  (let ((*installation* nil))\n    (cl-mock:with-mocks ()\n      (unwind-protect\n           (&body)\n        (setf *loaded* nil)))))\n\n(test simple-load-config ()\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname config :stream s :type \"lisp\")\n      (let ((*package* (find-package :cl-user)))\n        (write '(setf *loaded* t) :stream s))\n      (finish-output s)\n      (answer (find-config.lisp)\n        config)\n      (load-config)\n      (is-true *loaded*))))\n\n(test setf-installation ()\n  (with-fixture state ()\n    (uiop:with-temporary-file (:pathname config :stream s :type \"lisp\")\n      (let ((*package* (find-package :cl-user)))\n        (format s\n                \"(setf (installation) (make-instance 'installation))~%\"))\n      (finish-output s)\n      (answer (find-config.lisp)\n        config)\n      (load-config)\n      (is (typep *installation* 'installation)))))\n\n(test installation-package\n  (is (eql (find-package :screenshotbot/config)\n           (symbol-package 'screenshotbot/config::installation))))\n\n(test no-config-lisp-found ()\n  (with-fixture state ()\n    (answer (find-config.lisp)\n      nil)\n    (load-config)\n    (is (typep *installation* 'installation))))\n"
  },
  {
    "path": "src/screenshotbot/test-diff-report.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-diff-report\n  (:use :cl)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that\n                #:equal-to)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:it.bese.fiveam\n                #:pass\n                #:def-fixture\n                #:is\n                #:test\n                #:with-fixture)\n  (:import-from #:screenshotbot/diff-report\n                #:diff-report-previous-run\n                #:diff-report-run\n                #:*cache*\n                #:added-groups\n                #:change\n                #:changes-groups\n                #:deleted-groups\n                #:diff-report\n                #:diff-report-added\n                #:diff-report-changes\n                #:diff-report-deleted\n                #:diff-report-title\n                #:group-renamed-p\n                #:group-title\n                #:make-diff-report\n                #:make-image-hashes)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:screenshotbot/model/image\n                #:make-image)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:group-separator\n                #:make-recorder-run)\n  (:import-from #:screenshotbot/screenshot-api\n                #:screenshot-image\n                #:make-screenshot)\n  (:import-from #:screenshotbot/user-api\n                #:channel)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:local-nicknames (#:diff-report\n                     #:screenshotbot/diff-report)))\n(in-package :screenshotbot/test-diff-report)\n\n(util/fiveam:def-suite)\n\n(defun static-asset (file)\n  (path:catfile\n   #.(asdf:system-relative-pathname :screenshotbot\n                                    \"static/\")\n   file))\n\n(def-fixture state ()\n  (let ((*installation* (make-instance 'installation))\n        (*cache* (make-hash-table :test #'equal)))\n   (with-test-store ()\n     (let* ((img (make-image\n                  :pathname (static-asset \"assets/images/example-view-square.svg.png\")))\n            (img2 (make-image\n                   :pathname (static-asset \"assets/images/example-view.svg.png\")))\n            (channel (make-instance 'channel))\n            (screenshot (make-screenshot :name \"foo\"\n                                         :image img))\n            (screenshot2 (make-screenshot :name \"foo\"\n                                          :image img2))\n            (run1 (make-recorder-run :screenshots (list screenshot)))\n            (run2 (make-recorder-run :screenshots (list screenshot2))))\n       (flet ((make-change (name)\n                (make-instance 'change\n                               :before (make-screenshot :name name)\n                               :after (make-screenshot :name name))))\n         (&body))))))\n\n(test the-order-of-changes--i.e.-before-and-after\n  \"At some point, we swapped the order in which this was returned.\"\n  (with-fixture state ()\n    (let ((diff-report (make-diff-report run2 run1)))\n      (let ((change (car (diff-report:diff-report-changes diff-report))))\n        (is (eql img\n                 (screenshot-image (diff-report:before change))))\n        (is (eql img2\n                 (screenshot-image (diff-report:after change))))))))\n\n(test 2-changed\n  (with-fixture state ()\n    (let ((changes (list\n                    (make-change \"1\")\n                    (make-change \"2\")\n                    (make-change \"3\")))\n          (added (list\n                  (make-screenshot :name \"foo\"))))\n     (is (equal \"3 changes\"\n                (diff-report-title (make-instance 'diff-report\n                                                   :changes changes))))\n      (is (equal \"3 changes, 1 deleted\"\n                 (diff-report-title (make-instance 'diff-report :changes changes\n                                                                :deleted added))))\n      (is (equal \"3 changes, 1 added\"\n                 (diff-report-title (make-instance 'diff-report :changes changes\n                                                              :added added)))))))\n\n\n\n(test make-diff-report\n  (with-fixture state ()\n    (let ((diff-report (make-diff-report run1 run2)))\n      (is (eql 1 (length (diff-report-changes diff-report))))\n      (is (eql run1 (diff-report-run diff-report)))\n      (is (eql run2 (diff-report-previous-run diff-report))))))\n\n(test make-diff-report-to-nil-run\n  (with-fixture state ()\n    (let ((diff-report (make-diff-report run1 nil)))\n      (is (eql 1 (length (diff-report-added diff-report)))))))\n\n(test make-diff-report-added\n  (with-fixture state ()\n    (let ((diff-report (make-diff-report run1 (make-recorder-run\n                                               :screenshots '()))))\n      (is (eql 0 (length (diff-report-changes diff-report))))\n      (is (eql 1 (length (diff-report-added diff-report)))))))\n\n\n(test make-diff-report-deleted\n  (with-fixture state ()\n    (let ((diff-report (make-diff-report (make-recorder-run\n                                          :screenshots '())\n                                         run2)))\n      (is (eql 0 (length (diff-report-changes diff-report))))\n      (is (eql 1 (length (diff-report-deleted diff-report)))))))\n\n\n(test make-image-hashes\n  (with-fixture state ()\n   (let ((screenshots\n           (list\n            (make-screenshot :image img :name \"foo\")\n            (make-screenshot :image img2 :name \"bar\"))))\n     (is (eql\n          2\n          (fset:size (make-image-hashes screenshots)))))))\n\n(test group-renamed-p\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img :name \"foo\"))))\n           (run2 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img :name \"renamed\"))))\n           (diff-report (make-diff-report run1 run2)))\n\n      (is (group-renamed-p (car (added-groups diff-report)))))))\n\n\n(test grouping-changed\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img :name \"foo--one\")\n                                     (make-screenshot :image img :name \"foo--two\"))))\n           (run2 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img2 :name \"foo--one\")\n                                     (make-screenshot :image img2 :name \"foo--two\"))))\n           (diff-report (make-diff-report run1 run2)))\n      (is (eql 1 (length (changes-groups diff-report)))))))\n\n(test grouping-not-grouped-with-different-separator\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :group-separator \"!!\"\n                  :screenshots (list (make-screenshot :image img :name \"foo--one\")\n                                     (make-screenshot :image img :name \"foo--two\"))))\n           (run2 (make-recorder-run\n                  :group-separator \"!!\"\n                  :screenshots (list (make-screenshot :image img2 :name \"foo--one\")\n                                     (make-screenshot :image img2 :name \"foo--two\"))))\n           (diff-report (make-diff-report run1 run2)))\n      (is (equal \"!!\" (group-separator run2)))\n      (is (equal \"!!\" (group-separator diff-report)))\n      (assert-that (changes-groups diff-report)\n                   (has-length 2)))))\n\n(test grouping-changed-with-different-separator\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :group-separator \"!!\"\n                  :screenshots (list (make-screenshot :image img :name \"foo!!one\")\n                                     (make-screenshot :image img :name \"foo!!two\"))))\n           (run2 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img2 :name \"foo!!one\")\n                                     (make-screenshot :image img2 :name \"foo!!two\"))))\n           (diff-report (make-diff-report run1 run2)))\n      (assert-that (changes-groups diff-report)\n                   (has-length 1)))))\n\n\n(test grouping-added-removed\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img :name \"foo--one\")\n                                     (make-screenshot :image img :name \"foo--two\"))))\n           (run2 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img2 :name \"bar--one\")\n                                     (make-screenshot :image img2 :name \"bar--two\"))))\n           (diff-report (make-diff-report run1 run2)))\n      (assert-that (added-groups diff-report)\n                   (has-length 1))\n      (assert-that (deleted-groups diff-report)\n                   (has-length 1)))))\n\n(test grouping-added-removed-not-grouped-with-different-separator\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :group-separator \"!!\"\n                  :screenshots (list (make-screenshot :image img :name \"foo--one\")\n                                     (make-screenshot :image img :name \"foo--two\"))))\n           (run2 (make-recorder-run\n                  :group-separator \"!!\"\n                  :screenshots (list (make-screenshot :image img2 :name \"bar--one\")\n                                     (make-screenshot :image img2 :name \"bar--two\"))))\n           (diff-report (make-diff-report run1 run2)))\n      (assert-that (added-groups diff-report)\n                   (has-length 2))\n      (assert-that (deleted-groups diff-report)\n                   (has-length 2)))))\n\n(test grouping-added-removed-with-diff-separator\n  (with-fixture state ()\n    (let* ((run1 (make-recorder-run\n                  :group-separator \"!!\"\n                  :screenshots (list (make-screenshot :image img :name \"foo!!one\")\n                                     (make-screenshot :image img :name \"foo!!two\"))))\n           (run2 (make-recorder-run\n                  :screenshots (list (make-screenshot :image img2 :name \"bar!!one\")\n                                     (make-screenshot :image img2 :name \"bar!!two\"))))\n           (diff-report (make-diff-report run1 run2)))\n      (assert-that (added-groups diff-report)\n                   (has-length 1))\n      (assert-that (group-title (car (added-groups diff-report)))\n                   (equal-to \"foo\"))\n      (assert-that (deleted-groups diff-report)\n                   (has-length 1))\n      (assert-that (group-title (car (deleted-groups diff-report)))\n                   (equal-to \"bar\")))))\n"
  },
  {
    "path": "src/screenshotbot/test-email-template.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-email-template\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:screenshotbot/email-template\n                #:templated-mailer\n                #:email-template)\n  (:import-from #:screenshotbot/mailer\n                #:wrap-template))\n(in-package :screenshotbot/test-email-template)\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(def-fixture state ()\n  (with-installation ()\n    (&body)))\n\n(screenshot-test basic-email-template-test\n  (with-fixture state ()\n    <email-template>\n      <p>Hello world!</p>\n    </email-template>))\n\n\n(test wrap-template-for-email-template\n  (with-fixture state ()\n    (let ((mailer (make-instance 'templated-mailer)))\n      (finishes\n        (wrap-template\n         mailer\n         <html>\n           <body>hello</body>\n         </html>))\n      (finishes\n        (wrap-template\n         mailer\n         <html>\n           <head></head>\n           <body>hello</body>\n         </html>))\n      (finishes\n        (wrap-template\n         mailer\n         <html>\n           <head />\n         </html>)))))\n"
  },
  {
    "path": "src/screenshotbot/test-git-repo.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-git-repo\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/git-repo\n                #:generic-git-repo\n                #:null-repo\n                #:public-repo-p))\n(in-package :screenshotbot/test-git-repo)\n\n\n(util/fiveam:def-suite)\n\n\n(def-fixture state ()\n  (&body))\n\n(test public-repo-p\n  (is-false (public-repo-p (make-instance 'generic-git-repo)))\n  (is-false (public-repo-p (make-instance 'null-repo))))\n"
  },
  {
    "path": "src/screenshotbot/test-installation.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/test-installation\n    (:use #:cl\n          #:alexandria\n          #:fiveam\n          #:screenshotbot/installation)\n  (:import-from #:screenshotbot/installation\n                #:installation)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:core/config/api\n                #:config))\n\n(util/fiveam:def-suite)\n\n(defclass my-plugin () ())\n\n(test find-plugin\n  (let ((plugin (make-instance 'my-plugin)))\n    (is (eql plugin (find-plugin (make-instance 'installation\n                                                 :plugins (list plugin))\n                                 'my-plugin)))\n    (signals simple-error\n      (find-plugin (make-instance 'installation)\n                   'my-plugin))))\n\n(test default-oidc-provider-happy-path-with-config\n  (with-test-store ()\n   (let ((installation (make-instance 'installation)))\n     (is (eql nil (default-oidc-provider installation)))\n     (setf (config \"sso.oidc.client-id\") \"abcd\")\n     (is (eql nil (default-oidc-provider installation)))\n     (setf (config \"sso.oidc.client-secret\") \"car\")\n     (setf (config \"sso.oidc.issuer\")\n           \"https://example.com/\")\n     (is (not (null (default-oidc-provider installation)))))))\n"
  },
  {
    "path": "src/screenshotbot/test-invite.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-invite\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user\n                #:screenshot-test\n                #:with-installation)\n  (:import-from #:screenshotbot/model/invite\n                #:invite)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/invite\n                #:invite-enabled-p\n                #:%user-count\n                #:invite-page\n                #:user-can-invite-p\n                #:invite-post\n                #:invite-signup-page\n                #:%accept-invite\n                #:accept-invite)\n  (:import-from #:fiveam-matchers/core\n                #:is-not\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item)\n  (:import-from #:screenshotbot/user-api\n                #:unaccepted-invites)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:auth/model/invite\n                #:invite-used-p\n                #:invite-code)\n  (:import-from #:core/installation/auth-provider\n                #:auth-providers\n                #:default-oidc-provider\n                #:company-sso-auth-provider)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:screenshotbot/login/common\n                #:standard-auth-provider))\n(in-package :screenshotbot/test-invite)\n\n(util/fiveam:def-suite)\n\n(defclass my-installation (multi-org-feature\n                           installation)\n  ())\n\n(def-fixture state ()\n  (with-installation (:installation (make-instance 'my-installation))\n   (with-test-store ()\n     (let* ((company (make-instance 'company))\n            (user (make-user))\n            (other-user (make-user :email \"bar@example.com\")))\n       (&body)))))\n\n(test simple-accept-invite\n  (with-fixture state ()\n    (let ((invite (make-instance 'invite\n                                 :inviter user\n                                 :company company\n                                 :email \"bar@example.com\")))\n      (setf (unaccepted-invites other-user)\n            (list invite))\n      (assert-that (roles:users-for-company company)\n                   (is-not (has-item other-user)))\n      (%accept-invite invite other-user)\n      (assert-that (roles:users-for-company company)\n                   (has-item other-user)))))\n\n(test if-invite-doesnt-exist-we-get-error-page\n  (with-fixture state ()\n    (with-fake-request ()\n      (with-installation ()\n        (auth:with-sessions ()\n          (assert-that\n           (markup:write-html\n            (invite-signup-page :invite-code \"foo\"))\n           (contains-string \"invite link has expired\" )))))))\n\n(screenshot-test invite-expired-page\n  (with-fixture state ()\n    (with-fake-request ()\n      (with-installation ()\n        (auth:with-sessions ()\n          (invite-signup-page :invite-code \"foo\"))))))\n\n(test already-a-member\n  (with-fixture state ()\n    (with-installation ()\n      (with-test-user (:user invitee :company company :logged-in-p t)\n        (let ((invite (make-instance 'invite\n                                     :inviter user\n                                     :company company)))\n          (catch 'hunchentoot::handler-done\n            (invite-signup-page :invite-code (invite-code invite)))\n          (assert-that\n           (hunchentoot:header-out :location)\n           (contains-string\n            \"/runs\")))))))\n\n(screenshot-test inviting-a-user-that-already-exists\n  (with-fixture state ()\n    (with-installation ()\n      (with-test-user (:user user :company company :logged-in-p t)\n        (let ((user-2 (make-user :email \"car@example.com\")))\n          (setf (roles:user-role company user-2) 'roles:standard-member)\n          (let ((page (invite-post :email \"car@example.com\")))\n            (assert-that\n             (markup:write-html\n              page)\n             (contains-string\n              \"A user with that email is already a part\"))\n            page))))))\n\n(screenshot-test no-permission-to-invite\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n     (with-installation ()\n       (with-test-user (:user user :company company :logged-in-p t)\n         (cl-mock:answer (user-can-invite-p company user) nil)\n         (let ((page (invite-page)))\n           (assert-that\n            (markup:write-html page)\n            (contains-string \"You do not have permission\"))\n           page))))))\n\n(screenshot-test validate-limit-on-number-of-invites\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n      (with-installation ()\n        (with-test-user (:user user :company company :logged-in-p t)\n          (roles:ensure-has-role company user\n                                 'roles:owner)\n          (finishes\n            ;; Essentiall test that no checks failed here\n            (catch 'hunchentoot::handler-done\n              (invite-post :email \"foo@example.com\")))\n          \n          (dotimes (i 5)\n            (make-instance 'invite\n                           :email \"dfdfd@gmail.com\"\n                           :company company))\n\n          (gk:create :limit-invites)\n          (gk:allow :limit-invites company)\n          \n          (let ((page (invite-post :email \"foo@example.com\")))\n            (assert-that\n             (markup:write-html page)\n             (contains-string \"You have reached the limit\"))\n            page))))))\n\n(screenshot-test if-gk-is-off-then-no-limit-on-invites\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n      (with-installation ()\n        (with-test-user (:user user :company company :logged-in-p t)\n          (roles:ensure-has-role company user\n                                 'roles:owner)\n          (finishes\n            ;; Essentiall test that no checks failed here\n            (catch 'hunchentoot::handler-done\n              (invite-post :email \"foo@example.com\")))\n          \n          (dotimes (i 5)\n            (make-instance 'invite\n                           :email \"dfdfd@gmail.com\"\n                           :company company))\n\n          (finishes\n            ;; Essentiall test that no checks failed here\n            (catch 'hunchentoot::handler-done\n              (invite-post :email \"foo@example.com\"))))))))\n\n(test user-count-doesnt-count-used-invites\n  (with-fixture state ()\n    (is (eql 0 (%user-count company)))\n    (let ((invites\n            (loop for i from 0 to 3\n                  collect\n                  (make-instance 'invite\n                                 :company company\n                                 :email \"foobar\"))))\n      (is (eql 4 (%user-count company)))\n      (setf (invite-used-p (elt invites 1)) t)\n      (is (eql 3 (%user-count company)))\n      (setf (invite-used-p (elt invites 2)) t)\n      (is (eql 2 (%user-count company))))))\n\n(test invite-enabled-p-happy-path\n  (with-fixture state ()\n    (is-true (invite-enabled-p company))\n    (setf (company-sso-auth-provider company) 'fake)\n    (is-false (invite-enabled-p company))))\n\n(test invite-enabled-p-for-installation-with-default-sso\n  (with-fixture state ()\n    (is-true (invite-enabled-p company))\n    (setf (default-oidc-provider *installation*) 'fake)\n    (is-false (invite-enabled-p company))))\n\n(test invite-enabled-p-with-company-provider\n  (with-fixture state ()\n    (is-true company)\n    (setf (auth-providers *installation*)\n          (list\n           (make-instance 'standard-auth-provider\n                          :verify-email-p t\n                          :company-provider (lambda ()\n                                              company))))\n    (is-false (invite-enabled-p company))))\n\n(test invite-enabled-p-without-company-provider\n  (with-fixture state ()\n    (is-true company)\n    (setf (auth-providers *installation*)\n          (list\n           (make-instance 'standard-auth-provider\n                          :verify-email-p t\n                          :company-provider nil)))\n    (is-true (invite-enabled-p company))))\n\n(test invite-enabled-p-with-nil-company-provider\n  (with-fixture state ()\n    (is-true company)\n    (setf (auth-providers *installation*)\n          (list\n           (make-instance 'standard-auth-provider\n                          :verify-email-p t\n                          :company-provider (lambda ()\n                                              nil))))\n    (is-true (invite-enabled-p company))))\n"
  },
  {
    "path": "src/screenshotbot/test-promote-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-promote-api\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/promote-api\n                #:plugin-promoter\n                #:register-promoter\n                #:list-promoters\n                #:*promoters*)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:screenshotbot/installation\n                #:*installation*\n                #:installation)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-promote-api)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key plugins)\n  (let ((*installation* (make-instance 'installation\n                                       :plugins plugins)))\n    (let ((*promoters* nil))\n      (&body))))\n\n(test empty-list\n  (with-fixture state ()\n    (is (eql nil (list-promoters)))))\n\n(defclass foo ()\n  ())\n\n(test list-promoters\n  (with-fixture state ()\n    (register-promoter 'foo)\n    (assert-that (list-promoters)\n                 (contains (has-typep 'foo)))))\n\n(defclass simple-plugin ()\n  ())\n\n(defmethod plugin-promoter ((plugin simple-plugin))\n  \"promoter\")\n\n(test list-promoters-with-plugins\n  (with-fixture state (:plugins (list (make-instance 'simple-plugin)))\n    (assert-that (list-promoters)\n                 (contains \"promoter\"))))\n"
  },
  {
    "path": "src/screenshotbot/test-raft-redirect.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-raft-redirect\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/raft-redirect\n                #:exclude-from-raft-redirect-p)\n  (:import-from #:util/testing\n                #:with-fake-request))\n(in-package :screenshotbot/test-raft-redirect)\n\n(util/fiveam:def-suite)\n\n(test exclude-from-raft-redirect-p-excludes-raft-state\n  (with-fake-request (:script-name \"/raft-state\")\n    (is-true (exclude-from-raft-redirect-p hunchentoot:*request*)))\n  (with-fake-request (:script-name \"/raft-state/foo\")\n    (is-true (exclude-from-raft-redirect-p hunchentoot:*request*))))\n\n(test exclude-from-raft-redirect-p-excludes-intern-rpc\n  (with-fake-request (:script-name \"/intern/rpc\")\n    (is-true (exclude-from-raft-redirect-p hunchentoot:*request*)))\n  (with-fake-request (:script-name \"/intern/rpc?foo=bar\")\n    (is-true (exclude-from-raft-redirect-p hunchentoot:*request*))))\n\n(test exclude-from-raft-redirect-p-allows-other-paths\n  (with-fake-request (:script-name \"/\")\n    (is-false (exclude-from-raft-redirect-p hunchentoot:*request*)))\n  (with-fake-request (:script-name \"/api/runs\")\n    (is-false (exclude-from-raft-redirect-p hunchentoot:*request*)))\n  (with-fake-request (:script-name \"/intern/other\")\n    (is-false (exclude-from-raft-redirect-p hunchentoot:*request*)))\n  (with-fake-request (:script-name \"/raft-something-else\")\n    (is-false (exclude-from-raft-redirect-p hunchentoot:*request*))))\n"
  },
  {
    "path": "src/screenshotbot/test-secret.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-secret\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-secret)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (&body))\n\n"
  },
  {
    "path": "src/screenshotbot/test-server.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-server\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/server\n                #:%handler-wrap\n                #:request)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user\n                #:with-installation)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:auth/login/sso\n                #:maybe-redirect-for-sso\n                #:needs-sso-condition)\n  (:import-from #:auth\n                #:no-access-error)  \n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-server)\n\n\n(util/fiveam:def-suite)\n\n(test authenticate-request-happy-path\n  (with-installation ()\n    (with-test-store ()\n      (with-fake-request ()\n        (let ((request (make-instance 'request\n                                      :uri \"/foo\"\n                                      :headers-in nil)))\n          (auth:with-sessions ()\n            (finishes\n              (auth:authenticate-request request))))))))\n\n(test no-access-error-happy-path\n  (with-test-store ()\n    (with-test-user (:user user :logged-in-p t)\n      (let ((body\n              (%handler-wrap\n               (lambda ()\n                 (error 'auth:no-access-error)))))\n        (assert-that\n         (markup:write-html body)\n         (contains-string \"You do not have permission to access this page\"))))))\n\n(test sso-condition-happy-path\n  (cl-mock:with-mocks ()\n    (with-test-store ()\n      (with-installation ()\n        (with-fake-request ()\n          (auth:with-sessions ()\n           (let ((redirected))\n             (cl-mock:if-called 'maybe-redirect-for-sso\n                                (lambda (company redirect)\n                                  (setf redirected company)))\n             (finishes\n               (%handler-wrap\n                (lambda ()\n                  (signal 'needs-sso-condition :company :foobar)\n                  (error 'no-access-error))))\n             (is (eql :foobar redirected)))))))))\n"
  },
  {
    "path": "src/screenshotbot/test-settings-api.lisp",
    "content": "(defpackage :screenshotbot/test-settings-api\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/settings-api\n                #:all-settings\n                #:defsettings\n                #:*settings*)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:screenshotbot/server\n                #:staging-p)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/test-settings-api)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (cl-mock:with-mocks ()\n    (let ((*settings* nil)\n          (*installation* (make-instance\n                           'installation)))\n      (&body))))\n\n(test filter-staging\n  (with-fixture state ()\n    (cl-mock:if-called 'staging-p\n                       (lambda ()\n                         nil))\n    (defsettings one\n      :name \"one\"\n      :handler 'one)\n    (assert-that (all-settings *installation*)\n                 (has-length 1))\n    (defsettings two\n      :name \"two\"\n      :staging-p t\n      :handler 'two)\n    (assert-that\n     (all-settings *installation*)\n     (has-length 1))))\n\n(test do-not-filter-staging-on-actual-staging\n  (with-fixture state ()\n    (cl-mock:if-called 'staging-p\n                       (lambda ()\n                         t))\n    (defsettings one\n      :name \"one\"\n      :handler 'one)\n    (assert-that (all-settings *installation*)\n                 (has-length 1))\n    (defsettings two\n      :name \"two\"\n      :staging-p t\n      :handler 'two)\n    (assert-that\n     (all-settings *installation*)\n     (has-length 2))))\n"
  },
  {
    "path": "src/screenshotbot/test-store.lisp",
    "content": ";;;; This test is valuable. As of 2026-04, it caught a bug with how we\n;;;; clear indices. See D12787\n\n(pkg:define-package :screenshotbot/test-store\n  (:use #:cl\n        #:alexandria\n        #:bknr.datastore\n        #:util\n        #:fiveam\n        #:screenshotbot/model/recorder-run\n        #:screenshotbot/model/core)\n  (:import-from #:screenshotbot/model/recorder-run\n                #:make-recorder-run))\n\n(defvar *tmpdir*)\n\n(defun reset-store (&optional why)\n  (declare (optimize (speed 0) (debug 3)))\n  (log:info \"Reset store: ~a\" why)\n  (when *store*\n    (assert (not (equal \"object-store\"\n                        (car (last (pathname-directory (bknr.datastore::store-directory *store*)))))))\n    (close-store)\n    (hcl:gc-generation 0))\n  (make-instance 'util:safe-mp-store\n                 :directory *tmpdir*\n                 :subsystems (util::store-subsystems)))\n\n(def-fixture state ()\n  (tmpdir:with-tmpdir (s)\n    (let ((*tmpdir* s)\n          (*store* nil))\n      (reset-store \"initial\")\n      (&body))))\n\n(test simple-creation\n  (with-fixture state ()\n    (let* ((run (make-recorder-run))\n           (id (store-object-id run))\n           (promo-log (promotion-log run))\n           (old-ts (%created-at run))\n           (old-oid (oid run)))\n      (log:info \"Before snapshot\")\n      (snapshot)\n      (log:info \"After snapshot\")\n      (sleep 2)\n      (reset-store \"after snapshot\")\n      (log:info \"After reset\")\n      (let ((updated-run (store-object-with-id id)))\n        (is (typep updated-run 'recorder-run))\n        (is (not (eql run updated-run)))\n        (is (not (eql promo-log (promotion-log updated-run))))\n        (is (eql\n             (%created-at run)\n             (%created-at updated-run)))\n        (is (equal old-oid\n                   (oid updated-run)))))))\n\n(test re-creation-without-snapshot\n  (with-fixture state ()\n    (let* ((run (make-recorder-run))\n           (id (store-object-id run))\n           (promo-log (promotion-log run))\n           (old-ts (%created-at run))\n           (old-oid (oid run)))\n      (is-true (%created-at run))\n      (sleep 1)\n      (reset-store \"after snapshot\")\n      (log:info \"After reset\")\n      (let ((updated-run (store-object-with-id id)))\n        (is (typep updated-run 'recorder-run))\n        (is (not (eql run updated-run)))\n        (is (not (eql promo-log (promotion-log updated-run))))\n        (is (eql\n             (%created-at run)\n             (%created-at updated-run)))\n        (is (equal\n             old-oid\n             (oid updated-run)))))))\n"
  },
  {
    "path": "src/screenshotbot/test-template.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/test-template\n    (:use #:cl\n          #:alexandria\n          #:screenshotbot/template\n          #:fiveam)\n  (:import-from #:screenshotbot/template\n                #:analyticsp\n                #:something-went-wrong)\n  (:import-from #:fiveam-matchers\n                #:is-string\n                #:has-typep\n                #:assert-that)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/server\n                #:no-access-error-page\n                #:acceptor)\n  (:import-from #:util/testing\n                #:with-fake-request\n                #:screenshot-static-page)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/user-api\n                #:user)\n  (:import-from #:core/installation/installation\n                #:site-alert))\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((*installation* (make-instance 'installation)))\n      (let ((company (make-instance 'company :name \"Test Company\"))\n            (user (make-instance 'user)))\n        (&body)))))\n\n(test simple-template\n  (with-fixture state ()\n   (screenshotbot/template:dashboard-template\n    :user user\n    :company company\n    :script-name \"/runs\"))\n  (pass))\n\n\n(test landing-template\n  (with-fixture state ()\n   (landing-template\n    \"foo\"))\n  (pass))\n\n(test something-went-wrong\n  (with-fixture state ()\n   (assert-that\n    (something-went-wrong)\n    (is-string))))\n\n(test screenshot-404-page\n  (with-fixture state ()\n    (screenshot-static-page\n      :screenshotbot\n      \"404-page\"\n      (hunchentoot:acceptor-status-message\n       (make-instance 'acceptor)\n       404))))\n\n(screenshot-test no-access-error-page\n  (with-fixture state ()\n    (with-fake-request ()\n     (auth:with-sessions ()\n       (no-access-error-page)))))\n\n(defclass test-installation-with-alert (installation)\n  ())\n\n(defmethod site-alert ((self test-installation-with-alert))\n  <div class= \"alert alert-warning\" >\n    <strong>Scheduled Maintenance:</strong> The system will undergo maintenance on December 31st from 2:00 AM to 4:00 AM EST.\n  </div>)\n\n(screenshot-test dashboard-with-site-alert\n  (with-test-store ()\n    (let ((*installation* (make-instance 'test-installation-with-alert)))\n      (let ((company (make-instance 'company :name \"Test Company\"))\n            (user (make-instance 'user)))\n        (with-fake-request ()\n          <dashboard-template user=user company=company script-name= \"/runs\">\n            hello world\n          </dashboard-template>)))))\n\n\n"
  },
  {
    "path": "src/screenshotbot/test-testing.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-testing\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/user-api\n                #:can-view!\n                #:can-view)\n  (:import-from #:screenshotbot/testing\n                #:multi-org-test-installation\n                #:with-installation\n                #:with-test-user)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:multi-org-feature)\n  (:import-from #:auth/viewer-context\n                #:normal-viewer-context))\n(in-package :screenshotbot/test-testing)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test ensure-user-can-view-company\n  (with-fixture state ()\n    (with-test-user (:user user :company company)\n      (is-true (auth:can-viewer-view (make-instance 'normal-viewer-context :user user)\n                                     company)))\n    (with-test-user (:user user :company company)\n      (is-true (auth:can-viewer-view (make-instance 'normal-viewer-context :user user)\n                                     company)))))\n\n(test ensure-user-can-view-company-multi-org\n  (with-installation (:installation (make-instance 'multi-org-test-installation))\n    (with-fixture state ()\n      (with-test-user (:user user :company company)\n        (is-true (auth:can-viewer-view (make-instance 'normal-viewer-context :user user) company))))))\n\n(test with-installation-doesnt-override\n  (with-installation (:installation (make-instance 'multi-org-test-installation))\n    (with-installation ()\n      (is (typep (installation) 'multi-org-test-installation)))))\n"
  },
  {
    "path": "src/screenshotbot/test-throttler.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/test-throttler\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/throttler\n                #:*global-request-throttler*\n                #:maybe-throttle-request)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:util/throttler\n                #:ip-throttler))\n(in-package :screenshotbot/test-throttler)\n\n\n(util/fiveam:def-suite)\n\n(defclass fake-request ()\n  ((script-name :initarg :script-name\n                :reader hunchentoot:script-name)))\n\n(test by-default-function-returns-nil\n  (is\n   (eql nil\n        (maybe-throttle-request :installation\n                                (make-instance 'fake-request\n                                               :script-name \"/api/run\"))))\n  (is\n   (eql nil\n        (maybe-throttle-request :installation\n                                (make-instance 'fake-request\n                                               :script-name \"/api/image\"))))\n\n  (is\n   (eql nil\n        (maybe-throttle-request :installation\n                                (make-instance 'fake-request\n                                               :script-name \"/image/blob\")))))\n\n(test eventual-throttling\n  (with-fake-request ()\n    (let ((*global-request-throttler* (make-instance 'ip-throttler\n                                                     :tokens 0)))\n      (is\n       (equal\n        \"Too Many Requests\"\n        (maybe-throttle-request\n         :installation\n         hunchentoot:*request*)))\n      (is (eql 429 (hunchentoot:return-code hunchentoot:*reply*))))))\n"
  },
  {
    "path": "src/screenshotbot/test-uname.lisp",
    "content": "(defpackage :screenshotbot/test-uname\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/uname\n                #:cpu-codename\n                #:parse-t-type\n                #:uname-arch\n                #:uname-kernel-version\n                #:uname-os\n                #:uname\n                #:parse-uname))\n(in-package :screenshotbot/test-uname)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (&body))\n\n(test simple-parsing ()\n  (let ((uname (parse-uname \"Darwin SVRNX6YQGQYR7 24.4.0 Darwin Kernel Version 24.4.0: Wed Mar 19 21:18:03 PDT 2025; root:xnu-11417.101.15~1/RELEASE_ARM64_T8112 arm6\")))\n    (is (typep uname 'uname))\n    (is (equal \"Darwin\" (uname-os uname)))\n    (is (equal \"24.4.0\" (uname-kernel-version uname)))\n    (is (equal \"arm6\" (uname-arch uname)))))\n\n(test parse-linux ()\n  (let ((uname (parse-uname \"Linux thecharmer 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64 GNU/Linux\")))\n    (is (equal \"Linux\" (uname-os uname)))\n    (is (equal \"6.1.0-29-amd64\" (uname-kernel-version uname)))\n    (is (equal \"x86_64\" (uname-arch uname)))))\n\n(test parse-mac-cpu-type\n  (let ((uname (parse-uname \"Darwin SVRNX6YQGQYR7 24.4.0 Darwin Kernel Version 24.4.0: Wed Mar 19 21:18:03 PDT 2025; root:xnu-11417.101.15~1/RELEASE_ARM64_T8112 arm6\")))\n    (is (equal \"T8112\" (parse-t-type uname)))\n    (is (equal \"M2\" (cpu-codename uname)))))\n\n(test parse-another-cpu-codename\n  (let ((uname (parse-uname \"Darwin SVRRKJ23YY4QX 24.4.0 Darwin Kernel Version 24.4.0: Wed Mar 19 21:17:35 PDT 2025; root:xnu-11417.101.15~1/RELEASE_ARM64_T6041 arm64\")))\n    (is (equal \"T6041\" (parse-t-type uname)))\n    (is (equal \"M4 Max\" (cpu-codename uname)))))\n\n(test do-some-parsing-on-a-bad-uname\n  (let ((uname (parse-uname \"Darwin blah blah\")))\n    (is (equal nil (parse-t-type uname)))\n    (is (equal nil (cpu-codename uname)))\n    (is (equal \"blah\" (uname-arch uname))))\n  \n  (let ((uname (parse-uname \"blah blah\")))\n    (is (equal nil (parse-t-type uname)))\n    (is (equal nil (cpu-codename uname)))\n    (is (equal nil (uname-arch uname)))))\n\n\n(test apple-vm\n  (let ((uname (parse-uname \"Darwin VM-ba26e239df 23.6.0 Darwin Kernel Version 23.6.0: Mon Jul 29 21:13:03 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_VMAPPLE arm64\")))\n    (is (equal \"VMAPPLE\" (parse-t-type uname)))))\n"
  },
  {
    "path": "src/screenshotbot/testing.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/testing\n    (:use #:cl\n          #:alexandria)\n  (:import-from #:screenshotbot/model/company\n                #:get-singleton-company\n                #:prepare-singleton-company\n                #:company)\n  (:import-from #:screenshotbot/model/user\n                #:user)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key)\n  (:import-from #:bknr.datastore\n                #:delete-object)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:import-from #:screenshotbot/user-api\n                #:current-company\n                #:current-user)\n  (:import-from #:util/testing\n                #:define-screenshot-test-init\n                #:with-global-binding\n                #:screenshot-static-page\n                #:with-fake-request)\n  (:import-from #:util/object-id\n                #:oid-array)\n  (:import-from #:screenshotbot/model/image\n                #:with-local-image\n                #:image)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:screenshotbot/installation\n                #:multi-org-feature\n                #:installation\n                #:*installation*)\n  (:import-from #:screenshotbot/server\n                #:screenshotbot-template)\n  (:import-from #:util/store/object-id\n                #:oid)\n  (:export\n   #:snap-image-blob\n   #:snap-all-images\n   #:fix-timestamps\n   #:screenshot-test\n   #:multi-org-test-installation))\n\n(defclass multi-org-test-installation (multi-org-feature\n                                       installation)\n  ())\n\n(def-easy-macro with-installation (&key globally\n                                        (installation (make-instance 'installation))\n                                        &fn fn)\n  (cond\n    ((boundp '*installation*)\n     (funcall fn))\n    (globally\n     (with-global-binding ((*installation* installation))\n       (funcall fn)))\n    (t\n     (let ((*installation* installation))\n       (funcall fn)))))\n\n(defmacro with-test-user ((&key (company (gensym \"company\"))\n                                (company-name \"dummy company\")\n                                (company-class '(quote company))\n                             (user (gensym \"user\"))\n                             (api-key (gensym \"api-key\"))\n                             (logged-in-p nil)\n                             (fake-request-args nil)) &body body)\n  `(with-installation ()\n     (let* ((,company\n              (typecase (installation)\n                (multi-org-feature\n                 (make-instance ,company-class\n                                :name ,company-name))\n                (t\n                 (prepare-singleton-company)\n                 (get-singleton-company (installation)))))\n            (,user (make-user :companies (list ,company)))\n            (,api-key (make-instance 'api-key :user ,user\n                                              :company ,company))\n            (*events* nil))\n       (declare (ignorable ,company ,user ,api-key))\n       (flet ((body ()\n                (unwind-protect\n                     (progn ,@body)\n                  (delete-object ,api-key))))\n         (cond\n           (,logged-in-p\n            (with-fake-request (,@fake-request-args)\n              (auth:with-sessions ()\n                (setf (auth:request-user hunchentoot:*request*) ,user)\n                (setf (auth:request-account hunchentoot:*request*)\n                      ,company)\n                (setf (auth:viewer-context hunchentoot:*request*)\n                      (make-instance 'auth/viewer-context:normal-viewer-context\n                                     :user ,user))\n                (body))))\n           (t\n            (body)))))))\n\n\n(let ((cached-dir nil))\n (defun snap-image-blob (im1)\n   \"Copy the givem image blob into the screenshot assets\"\n   (let ((dir (util:or-setf\n               cached-dir\n               (asdf:system-relative-pathname\n                :screenshotbot\n                \"static-web-output/image/blob/\"))))\n     (let ((output (path:catfile\n                    dir\n                    (format nil \"~a/default.webp\"\n                            (oid im1)))))\n       (with-local-image (file im1)\n         (uiop:copy-file file (ensure-directories-exist output)))))))\n\n(defun snap-all-images ()\n  (loop for image in (bknr.datastore:class-instances 'image)\n        do (snap-image-blob image)))\n\n(defun fix-timestamps (node)\n  (mquery:with-document (node)\n    (mquery:remove-class (mquery:$ \"time\") \"timeago\")\n    (dolist (time (mquery:$ \"time\"))\n     (setf (mquery:text time) \"Some timestamp\"))))\n\n(defmacro screenshot-test (name &body body)\n  `(fiveam:test ,name\n     (screenshot-static-page\n      :screenshotbot\n      (str:downcase ,(string name))\n      (let ((core/ui/template:*app-template*\n              (make-instance 'screenshotbot-template)))\n        (progn\n          ,@body)))))\n\n(define-screenshot-test-init :screenshotbot\n  :static-assets \"static/assets/\"\n  :generated-css-assets\n  `((:screenshotbot.css-assets . \"assets/css/default.css\")\n    #-screenshotbot-oss\n    (:screenshotbot.pro.css/extended-dashboard . \"assets/css/extended-dashboard.css\")\n    #-screenshotbot-oss\n    (:screenshotbot.pro.css . \"assets/css/new-landing.css\")))\n"
  },
  {
    "path": "src/screenshotbot/thresholds/dsl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/thresholds/dsl\n  (:use #:cl)\n  #+lispworks\n  (:import-from #:lw\n                #:whitespace-char-p)\n  (:export #:exact-images))\n(in-package :screenshotbot/thresholds/dsl)\n\n#-lispworks\n(defun whitespace-char-p (ch)\n  (ppcre:scan-to-strings \"\\\\s\" (string ch)))\n\n(defun %read-dsl-from-string (code)\n  (let ((input (make-string-input-stream code)))\n    (discard-whitespace input)\n    (let ((*package* (symbol-package 'foo)))\n      (%read-dsl input))))\n\n(defun read-list (stream &optional read-so-far)\n  (let ((next (peek-char nil stream)))\n    (cond\n      ((eql #\\) next)\n       (read-char stream)\n       (discard-whitespace stream)\n       (reverse read-so-far))\n      (t\n       (read-list\n        stream\n        (list* (%read-dsl stream) read-so-far))))))\n\n(defun read-symbol-as-string (stream)\n  (prog1\n      (coerce\n       (loop for next = (peek-char nil stream)\n             until (or\n                    (eql next #\\))\n                    (whitespace-char-p next))\n             collect (read-char stream))\n       'string)\n    (discard-whitespace stream)))\n\n(defparameter *symbols*\n  '(exact-images\n    + * / -))\n\n\n(defun discard-whitespace (stream)\n  (loop for ch = (peek-char nil stream nil nil)\n        while (and ch (whitespace-char-p ch))\n        do (read-char stream)))\n\n(defun parse-symbol (string)\n  (cond\n    ((ppcre:scan \"\\\\d+\" string)\n     (parse-integer string))\n    (t\n     (loop for sym in *symbols*\n           if (equal (string-downcase sym) string)\n             return sym\n           finally\n              (error \"Invalid symbol: ~a\" string)))))\n\n(defun %read-dsl (stream)\n  (let ((next (peek-char nil stream)))\n    (cond\n      ((eql #\\( next)\n       (read-char stream)\n       (discard-whitespace stream)\n       (read-list stream))\n      (t\n       (parse-symbol\n        (read-symbol-as-string stream))))))\n\n(defun eval-dsl (dsl)\n  \"Takes a DSL and returns a function that can be evaluated later.\"\n  (eval `(lambda () ,dsl)))\n\n\n\n\n"
  },
  {
    "path": "src/screenshotbot/thresholds/test-dsl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/thresholds/test-dsl\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/thresholds/dsl\n                #:eval-dsl\n                #:%read-dsl-from-string\n                #:%read-dsl)\n  (:local-nicknames\n   (#:dsl #:screenshotbot/thresholds/dsl)))\n(in-package :screenshotbot/thresholds/test-dsl)\n\n(util/fiveam:def-suite)\n\n(test read-exact\n  (is\n   (equal\n    '(dsl:exact-images)\n    (%read-dsl-from-string \"(exact-images)\"))))\n\n(test read-with-whitespace\n  (is\n   (equal\n    '(dsl:exact-images)\n    (%read-dsl-from-string \"( exact-images )\")))\n  (is\n   (equal\n    '(dsl:exact-images)\n    (%read-dsl-from-string \"  ( exact-images )  \"))))\n\n(test nested\n  (is\n   (equal\n    '((dsl:exact-images (dsl:exact-images)))\n    (%read-dsl-from-string \"(( exact-images (exact-images) ))\"))))\n\n(test numbers\n  (is\n   (equal\n    '(22)\n    (%read-dsl-from-string \"(22)\"))))\n\n(test evalate\n  (is\n   (equal 3\n          (funcall\n           (eval-dsl\n            (%read-dsl-from-string \"(+ 1 2)\"))))))\n\n"
  },
  {
    "path": "src/screenshotbot/throttler.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/throttler\n  (:use #:cl)\n  (:import-from #:util/throttler\n                #:throttled-error\n                #:throttle!\n                #:ip-throttler)\n  (:export\n   #:global-throttler))\n(in-package :screenshotbot/throttler)\n\n(defparameter *global-request-throttler*\n  (make-instance 'ip-throttler\n                 :tokens 3600))\n\n(defvar *image-request-throttler*\n  (make-instance 'ip-throttler\n                 :tokens 72000))\n\n(defun image-script-p (script-name)\n  \"We don't want to throttle image endpoints by the same level, since we\nmight have a burst of requests coming in per run\"\n  (or\n   (str:starts-with-p \"/api/image\"\n                      script-name)\n   (str:starts-with-p \"/image\"\n                      script-name)))\n\n(defmethod global-throttler (installation)\n  \"We want to be able to change the global throttler for enterprise\ninstalls.\"\n  *global-request-throttler*)\n\n(defmethod maybe-throttle-request (installation request)\n  (handler-case\n      (let ((script-name (hunchentoot:script-name request)))\n        (cond\n          ((image-script-p script-name)\n           (throttle! *image-request-throttler*))\n          (t\n           (throttle! (global-throttler installation))))\n        nil)\n    (throttled-error (e)\n      (setf (hunchentoot:return-code*) 429)\n      (warn \"Too many global requests\")\n      \"Too Many Requests\")))\n\n\n"
  },
  {
    "path": "src/screenshotbot/ui/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/ui\n  (:use #:cl #:alexandria)\n  (:use-reexport #:screenshotbot/ui/core\n                 #:core/ui/simple-card-page))\n(in-package :screenshotbot/ui)\n"
  },
  {
    "path": "src/screenshotbot/ui/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/ui/core\n  (:use #:cl #:alexandria)\n  (:import-from #:markup #:deftag)\n  (:export #:ui/a #:ui/div #:ui/span))\n(in-package :screenshotbot/ui/core)\n\n(named-readtables:in-readtable markup:syntax)\n\n(deftag ui/a (children &key href class btn id)\n  (let* ((class (or\n                 (when btn\n                   (format nil \"btn btn-~a ~a\" (string-downcase btn) class))\n                 class)))\n    <a href=href class=class id=id >,@(progn children)</a> ))\n\n(deftag ui/div (children &key class)\n  <div class=class >,@ (progn children)</div>)\n\n(deftag ui/span (children &key class)\n  <span class=class >,@ (progn children)</span>)\n"
  },
  {
    "path": "src/screenshotbot/uname.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/uname\n  (:use #:cl)\n  (:nicknames :%uname)\n  (:import-from #:alexandria\n                #:assoc-value))\n(in-package :screenshotbot/uname)\n\n(defclass uname ()\n  ((str :initarg :str\n        :reader uname-str)\n   (os :initarg :os\n       :reader uname-os)\n   (kernel-version\n    :initarg :kernel-version\n    :reader uname-kernel-version)))\n\n(defclass linux-uname (uname)\n  ())\n\n(defclass darwin-uname (uname)\n  ())\n\n(defmethod parse-os ((uname string))\n  (cond\n    ((str:starts-with-p \"Darwin\" uname)\n     \"Darwin\")\n    ((str:starts-with-p \"Linux\" uname)\n     \"Linux\")\n    (t\n     \"Other\")))\n\n(defmethod %scan (index regex string)\n  \"If the regex matchers, return the index-ed part of the matching\"\n  (multiple-value-bind (res parts)\n      (cl-ppcre:scan-to-strings regex string)\n    (when res\n      (elt parts index))))\n\n(defmethod %scan (index regex (uname uname))\n  (%scan index regex (uname-str uname)))\n\n(defmethod darwinp ((uname string))\n  (equal \"Darwin\" (parse-os uname)))\n\n(defmethod linuxp ((uname string))\n  (equal \"Linux\" (parse-os uname)))\n\n(defmethod parse-kernel-version ((uname string))\n  (cond\n    ((darwinp uname)\n     (%scan 0 \"Darwin .*? Darwin Kernel Version (.*): \" uname))\n    ((linuxp uname)\n     (%scan 0 \"Linux .*? ([1-9].*?) \" uname))))\n\n\n(defmethod parse-uname ((uname string))\n  \"Parses the output of `uname -a`\"\n  (make-instance (cond\n                   ((equal \"Darwin\" (parse-os uname))\n                    'darwin-uname)\n                   ((equal \"Linux\" (parse-os uname))\n                    'linux-uname)\n                   (t\n                    'uname))\n                 :os (parse-os uname)\n                 :str uname\n                 :kernel-version (parse-kernel-version uname)))\n\n(defgeneric uname-arch (uname)\n  (:method (uname)\n    nil)\n  (:method ((uname darwin-uname))\n    (car (last (str:split \" \" (uname-str uname)))))\n  (:method ((uname linux-uname))\n    ;; Technically, the man page says \"non-portable\". \n    (second (reverse (str:split \" \" (uname-str uname))))))\n\n;; This list was generated with the help of Gemini from the table\n;; https://en.wikipedia.org/wiki/Apple_silicon#Comparison_of_M-series_processors\n;; I first used the source of that table and asked Gemini to create a\n;; JSON map. Then asked it to create a common lisp alist out of it.\n(defparameter *processor-types*\n  `((\"T8103\" . \"M1\")\n    (\"T6000\" . \"M1 Pro\")\n    (\"T6001\" . \"M1 Max\")\n    (\"T6002\" . \"M1 Ultra\")\n    (\"T8112\" . \"M2\")\n    (\"T6020\" . \"M2 Pro\")\n    (\"T6021\" . \"M2 Max\")\n    (\"T6022\" . \"M2 Ultra\")\n    (\"T8122\" . \"M3\")\n    (\"T6030\" . \"M3 Pro\")\n    (\"T6034\" . \"M3 Max\")\n    (\"T6031\" . \"M3 Max\")\n    (\"T6032\" . \"M3 Ultra\")\n    (\"T8132\" . \"M4\")\n    (\"T6040\" . \"M4 Pro\")\n    (\"T6041\" . \"M4 Max\")\n\n    ;; This one was added manually\n    (\"VMAPPLE\" . \"VMAPPLE\")))\n\n(defgeneric parse-t-type (uname)\n  (:method (uname)\n    nil)\n  (:method ((uname darwin-uname))\n    (%scan 0 \"RELEASE_.*?_(T[0-9]*|VMAPPLE) \" uname)))\n\n(defgeneric cpu-codename (uname)\n  (:method (uname)\n    nil)\n  (:method ((uname darwin-uname))\n    (let ((result (assoc-value\n                   *processor-types*\n                   (parse-t-type uname)\n                   :test #'string-equal)))\n      (unless result\n        (warn \"unknown cpu codename: ~a\" (uname-str uname)))\n      result)))\n"
  },
  {
    "path": "src/screenshotbot/user-api.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :screenshotbot/user-api\n  (:use #:cl #:alexandria)\n  (:import-from #:auth\n                #:current-user #| T984 |#\n                #:can-view\n                #:can-view!\n                #:can-public-view\n                #:user-full-name\n                #:can-edit\n                #:can-edit!\n                #:user-email\n                #:current-company)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key-company\n                #:api-key-user\n                #:user-api-keys)\n  (:export\n   #:current-user\n   #:user\n   #:user-full-name\n   #:singletonp\n   #:user-companies\n   #:user-email\n   #:user-image-url\n   #:channel-name\n   #:activep\n   #:company-switch-page\n   #:adminp\n   #:can-view!\n   #:can-view\n   #:all-users\n   #:channel-repo\n   #:access-token\n   #:commit-link\n   #:company-runs\n   #:recorder-run-commit\n   #:activep\n   #:recorder-previous-run\n   #:pull-request-url\n   #:company-runs\n   #:%created-at\n   #:current-company\n   #:current-user\n   #:user-api-keys\n   #:commit-link\n   #:recorder-run-screenshots\n   #:recorder-run-channel\n   #:company-channels\n   #:channel-active-run\n   #:company-name\n   #:screenshot-name\n   #:company-reports\n   #:created-at\n   #:report-num-changes\n   #:channel\n   #:api-key-user\n   #:api-key-company\n   #:can-public-view\n   #:unaccepted-invites\n   #:user-notices\n   #:personalp))\n(in-package :screenshotbot/user-api)\n"
  },
  {
    "path": "src/screenshotbot/utils.lisp",
    "content": "(defpackage :screenshotbot-utils\n  (:nicknames :sb-util)\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:screenshotbot/secret\n                #:secret)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export #:upload-fasl\n           #:upload-sdk\n           #:deploy-fasls-main\n           #:load-tmp-fasl))\n(in-package :screenshotbot-utils)\n\n(defun md5-hex (f)\n  s#+lispworks\n  (comm:ensure-ssl)\n  (ironclad:byte-array-to-hex-string (md5-file f)))\n\n(def-easy-macro with-compression (&binding filename filename &key compress &fn fn)\n  (cond\n    (compress\n     (uiop:with-temporary-file (:type \"gz\" :pathname p)\n       (gzip-stream:gzip filename p)\n       (fn p)))\n    (t\n     (fn filename))))\n\n\n(defun load-tmp-fasl ()\n  (load \"../tmp.64ufasl\"))\n\n\n(defun deploy-fasls-main (system)\n  (log:info \"Uploading system: ~A\" system)\n  (upload-fasl nil system))\n"
  },
  {
    "path": "src/screenshotbot/web-build/browsers.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/web-build/browsers\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/web-build/browsers)\n  (:import-from #:screenshotbot/server\n                #:defhandler)\n  (:import-from #:screenshotbot/template\n                #:app-template)\n  (:import-from #:screenshotbot/web-build/project\n                #:height\n                #:browser-type\n                #:mobile-emulation\n                #:width\n                #:browser\n                #:get-browsers\n                #:browser-name)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:util/form-state\n                #:form-state-apply-edits\n                #:make-form-state\n                #:args-list-from-state\n                #:form-state-initargs\n                #:read-form-state\n                #:form-state-class)\n  (:import-from #:util/form-errors\n                #:update-form-values\n                #:with-form-errors)\n  (:import-from #:screenshotbot/user-api\n                #:current-company)\n  (:import-from #:screenshotbot/web-build/device-list\n                #:*device-list*)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/web-build/browsers)\n\n(named-readtables:in-readtable markup:syntax)\n\n\n(defhandler (nil :uri \"/browsers\") ()\n  <simple-card-page max-width=\"40em\" >\n    <div class= \"card-header\">\n      <h4>Browser configurations</h4>\n    </div>\n\n    <table class= \"table table-hover\">\n      <thead>\n        <tr>\n          <th>Name</th>\n          <th>Type</th>\n          <th>Config</th>\n        </tr>\n      </thead>\n      <tbody>\n    ,@ (loop for browser in (get-browsers)\n             collect\n             (util:copying (browser)\n               <tr>\n                 <td>\n                   <a href= (nibble () (edit-browser browser))>\n                     ,(browser-name browser)\n                   </a>\n                 </td>\n                 <td>\n                   ,(browser-type browser)\n                 </td>\n                 <td>\n                   ,(cond\n                      ((and (equal \"chrome\" (browser-type browser))\n                            (not (str:emptyp (mobile-emulation browser))))\n                       (mobile-emulation browser))\n                      (t\n                       (format nil \"~ax~a\" (width browser) (height browser))))\n                 </td>\n               </tr>))\n      </tbody>\n    </table>\n\n    <div class= \"card-footer\">\n      <a href= (nibble () (browser-form)) class= \"btn btn-primary\" >Add New</a>\n               </div>\n  </simple-card-page>)\n\n(defun browser-form (&key object (back \"/browsers\"))\n  <simple-card-page form-action= (nibble () (browser-form-submit :object object :back back)) >\n    <div class= \"card-header\">\n      <h4>Add new browser</h4>\n    </div>\n    <div class= \"form-group mb-3\">\n      <label for= \"name\" class= \"form-label\" >Friendly name</label>\n      <input type= \"text\" name= \"name\" id= \"name\" class= \"form-control\" />\n    </div>\n\n    <div class= \"form-group mb-3\">\n      <label for= \"type\" class= \"form-label\">Browser type</label>\n      <select class=\"form-select\" name= \"type\" id= \"type\">\n        <option value= \"chrome\">Google Chrome</option>\n        <option value= \"firefox\">Firefox</option>\n        <!-- <option value= \"safari\">Safari</option> -->\n      </select>\n    </div>\n\n    <div class= \"row\">\n      <div class= \"col-6\">\n        <div class= \"form-group mb-3\">\n          <label for= \"width\" class= \"form-label\">Width</label>\n          <input type= \"number\" name= \"width\" id= \"width\" class= \"form-control\" />\n        </div>\n      </div>\n\n      <div class= \"col-6\">\n        <div class= \"form-group mb-3\">\n          <label for= \"height\" class= \"form-label\">Height</label>\n          <input type= \"number\" name= \"height\" id= \"height\" class= \"form-control\" />\n        </div>\n      </div>\n    </div>\n\n    <div class= \"form-group\">\n      <label for= \"type\" class= \"form-label\">Mobile emulation mode (Chrome only)</label>\n      <select class= \"form-select\" name= \"mobile-emulation\" id= \"mobile-emulation\">\n        <option value= \"\">None</option>\n        ,@ (loop for device in *device-list*\n                 collect\n                 <option value=device >\n                   ,(progn device)\n                 </option>)\n      </select>\n    </div>\n\n    <div class= \"card-footer\">\n      <input type= \"submit\" value= (if object \"Update\" \"Create\") class= \"btn btn-primary\" />\n      <a class=\"btn btn-secondary\" href=back >Back</a>\n    </div>\n  </simple-card-page>)\n\n(defun if-not-empty (x)\n  (unless (str:emptyp x)\n    x))\n\n(defun parse-integer-or-nil (x)\n  (ignore-errors\n   (parse-integer x)))\n\n(defclass form-state ()\n  ((name :mapped-to browser-name)\n   (type :mapped-to browser-type)\n   (width :mapped-via parse-integer-or-nil)\n   (height :mapped-via parse-integer-or-nil)\n   (mobile-emulation :mapped-via if-not-empty))\n  (:metaclass form-state-class))\n\n(defun browser-form-submit (&key object\n                              back)\n  (let ((form-state (read-form-state 'form-state)))\n    (let ((errors))\n      (flet ((check (symbol check message)\n               (unless check\n                 (push (cons symbol message) errors)))\n             (safe-parse-integer (x)\n               (or\n                (ignore-errors\n                 (parse-integer x))\n                -1)))\n        (with-slots (name type width height mobile-emulation) form-state\n          (check :name (not (str:emptyp name))\n                 \"Please provide a name for this configuration\")\n          (check :type\n                 (member type '(\"chrome\" \"firefox\" \"safari\") :test #'equal)\n                 \"Invalid browser type\")\n          (check :mobile-emulation\n                 (or\n                  (str:emptyp mobile-emulation)\n                  (equal \"chrome\" type))\n                 \"Mobile emulation is only supported for Chrome\")\n          (when (str:emptyp mobile-emulation)\n            (check :width\n                   (<= 1 (safe-parse-integer width) 10000)\n                   \"Width should be between 1 and 10000\")\n            (check :height\n                   (<= 1 (safe-parse-integer height) 10000)\n                   \"Height should be between 1 and 10000\"))\n\n          (cond\n            (errors\n             (with-form-errors (:errors errors\n                                :args-list (args-list-from-state form-state)\n                                :was-validated t)\n               (browser-form :object object :back back)))\n            (object ;; edit\n             (with-transaction ()\n              (form-state-apply-edits form-state\n                                      object))\n             (hex:safe-redirect \"/browsers\"))\n            (t\n             (apply #'make-instance\n                      'browser\n                       :company (current-company)\n                      (form-state-initargs form-state))\n             (hex:safe-redirect \"/browsers\"))))))))\n\n(defun edit-browser (browser)\n  (let ((form-state (make-form-state 'form-state browser)))\n    (mquerY:with-document ((browser-form :object browser))\n      (update-form-values\n       (args-list-from-state form-state)))))\n"
  },
  {
    "path": "src/screenshotbot/web-build/device-list.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/web-build/device-list\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/web-build/device-list)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:*device-list*))\n(in-package :screenshotbot/web-build/device-list)\n\n(defun %%build-device-list ()\n  \"Build the list of emulation devices, by taking it from the local\n  Google Chrome installation. NOTE THAT THIS IS NOT MEANT TO BE RUN IN\n  PROD!\"\n  (let ((file \"~/.config/google-chrome/Default/Preferences\"))\n    (let ((data (json:decode-json-from-string (uiop:read-file-string file))))\n      (let ((items (json:decode-json-from-string\n                    (a:assoc-value\n                     (a:assoc-value (a:assoc-value data :devtools) :preferences)\n                     :standard-emulated-device-list))))\n        (loop for item in items\n              collect (a:assoc-value item :title))))))\n\n\n(defparameter *device-list*\n  (sort\n   (list \"iPhone SE\"\n         \"iPhone XR\"\n         \"iPhone 12 Pro\"\n         \"Pixel 3 XL\"\n         \"Pixel 5\"\n         \"Samsung Galaxy S8+\"\n         \"Samsung Galaxy S20 Ultra\"\n         \"iPad Air\"\n         \"iPad Mini\"\n         \"Surface Pro 7\"\n         \"Surface Duo\"\n         \"Galaxy Fold\"\n         \"Samsung Galaxy A51/71\"\n         \"Nest Hub Max\"\n         \"Nest Hub\"\n         \"iPhone 4\"\n         \"iPhone 5/SE\"\n         \"iPhone 6/7/8\"\n         \"iPhone 6/7/8 Plus\"\n         \"iPhone X\"\n         \"BlackBerry Z30\"\n         \"Nexus 4\"\n         \"Nexus 5\"\n         \"Nexus 5X\"\n         \"Nexus 6\"\n         \"Nexus 6P\"\n         \"Pixel 2\"\n         \"Pixel 2 XL\"\n         \"Pixel 3\"\n         \"Pixel 4\"\n         \"LG Optimus L70\"\n         \"Nokia N9\"\n         \"Nokia Lumia 520\"\n         \"Microsoft Lumia 550\"\n         \"Microsoft Lumia 950\"\n         \"Galaxy S III\"\n         \"Galaxy S5\"\n         \"Galaxy S8\"\n         \"Galaxy S9+\"\n         \"Galaxy Tab S4\"\n         \"JioPhone 2\"\n         \"Kindle Fire HDX\"\n         \"iPad Mini\"\n         \"iPad\"\n         \"iPad Pro\"\n         \"Blackberry PlayBook\"\n         \"Nexus 10\"\n         \"Nexus 7\"\n         \"Galaxy Note 3\"\n         \"Galaxy Note II\"\n         \"Moto G4\")\n   #'string-lessp))\n"
  },
  {
    "path": "src/screenshotbot/web-build/project.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/web-build/project\n  (:nicknames :screenshotbot/pro/web-build\n              :screenshotbot/pro/web-build/project)\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class\n                #:with-transaction\n                #:store-object-id\n                #:store-object-with-id)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:screenshotbot/template\n                #:mdi\n                #:app-template)\n  (:import-from #:screenshotbot/server\n                #:with-login\n                #:staging-p\n                #:defhandler)\n  (:import-from #:screenshotbot/model/core\n                #:ensure-slot-boundp\n                #:has-created-at)\n  (:import-from #:core/ui/taskie\n                #:timeago\n                #:taskie-timestamp\n                #:taskie-row\n                #:taskie-page-title\n                #:taskie-list)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:util/form-errors\n                #:with-form-errors)\n  (:import-from #:screenshotbot/user-api\n                #:can-view\n                #:user\n                #:can-view!\n                #:current-user\n                #:created-at\n                #:%created-at\n                #:current-company)\n  (:import-from #:screenshotbot/ui/core\n                #:ui/div\n                #:ui/a)\n  (:import-from #:core/ui/simple-card-page\n                #:confirmation-modal\n                #:confirmation-page)\n  (:import-from #:hunchentoot-extensions\n                #:get-request-domain-prefix)\n  (:import-from #:screenshotbot/model/api-key\n                #:api-key-secret\n                #:make-transient-key)\n  (:import-from #:screenshotbot/api-key-api\n                #:api-key-secret-key\n                #:api-key-key)\n  (:import-from #:screenshotbot/replay/remote\n                #:started-at\n                #:finished-at\n                #:run-thread-id\n                #:remote-runs-for-company\n                #:remote-run\n                #:log-file\n                #:remote-url\n                #:remote-run-status\n                #:donep\n                #:remote-oid\n                #:send-remote-run)\n  (:import-from #:screenshotbot/model/user\n                #:user-with-email)\n  (:import-from #:util/object-id\n                #:oid\n                #:find-by-oid)\n  (:import-from #:util/threading\n                #:safe-interrupt)\n  (:import-from #:screenshotbot/dashboard/explain\n                #:explain)\n  (:import-from #:scheduled-jobs\n                #:make-scheduled-job\n                #:make-scheduled-job)\n  (:import-from #:scheduled-jobs/model\n                #:cronexpr)\n  (:import-from #:scheduled-jobs/bindings\n                #:invalid-cron-expr-message\n                #:invalid-cron-expr\n                #:cron-parse-expr)\n  (:import-from #:screenshotbot/model/company\n                #:company-owner)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:screenshotbot/events\n                #:push-event)\n  (:import-from #:bknr.datastore\n                #:store-object-id)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/misc/lists\n                #:head)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:integration #:screenshotbot/replay/integration)\n                    (#:frontend #:screenshotbot/replay/frontend)\n                    (#:data #:bknr.datastore)\n                    (#:roles #:auth/model/roles))\n  (:export\n   #:browser\n   #:get-browsers\n   #:next-job-at\n   #:update-next-job-at))\n(in-package :screenshotbot/web-build/project)\n\n(named-readtables:in-readtable markup:syntax)\n\n(declaim (ftype (function (project) t) update-next-job-at))\n\n(defclass web-project (store-object)\n  ((name :initarg :name\n         :accessor web-project-name)\n   (urls :initarg :urls\n         :initform nil\n         :accessor urls)\n   (sitemap :initarg :sitemap\n            :initform nil\n            :accessor sitemap)\n   (browsers :initarg :browsers\n             :initform nil\n             :accessor browsers)\n   (company :initarg :company\n            :accessor company\n            :index-type hash-index\n            :index-reader web-projects-for-company)\n   (remote-runs :initform nil\n                :accessor remote-runs)\n   (exclusions :initform nil\n               :initarg :exclusions\n               :accessor exclusions)\n   (scheduled-job :initform nil\n                  :initarg :scheduled-job\n                  :accessor web-project-scheduled-job\n                  :relaxed-object-reference t)\n   (schedule-p :initform nil\n               :initarg :schedule-p\n               :accessor web-project-schedule-p)\n   (schedule-every :initform nil\n                   :initarg :schedule-every\n                   :accessor web-project-schedule-every)\n   (next-job-at :initform nil\n                :accessor next-job-at)\n   (custom-css :initform nil\n               :initarg :custom-css\n               :accessor custom-css)\n   (created-at :initarg :created-at\n               :initform 0\n               :reader %created-at))\n  (:metaclass persistent-class)\n  (:default-initargs :created-at (get-universal-time)))\n\n(defmethod auth:can-view ((web-project web-project) (user user))\n  (auth:can-view-with-normal-viewer-context\n   user web-project))\n\n(defmethod auth:can-viewer-view (vc (web-project web-project))\n  (let ((company (company web-project)))\n    (assert company)\n    (auth:can-viewer-view vc company)))\n\n(defmethod push-remote-run (build remote-run)\n  (with-transaction ()\n    (push remote-run (remote-runs build))))\n\n(defclass browser (store-object)\n  ((name :initarg :name\n         :accessor browser-name)\n   (width :initarg :width\n          :accessor width\n          :initform nil)\n   (height :initarg :height\n           :accessor height\n           :initform nil)\n   (type :initarg :type\n         :initform \"chrome\"\n         :accessor browser-type)\n   (company :initarg :company\n            :index-type hash-index\n            :index-reader %browsers-for-company\n            :reader company)\n   (mobile-emulation :initarg :mobile-emulation\n                     :accessor mobile-emulation\n                     :initform nil))\n  (:metaclass persistent-class))\n\n(defun get-browsers ()\n  (let ((company (current-company)))\n    (let ((browsers (%browsers-for-company company)))\n      (or\n       browsers\n       (list\n        (make-instance 'browser\n                        :name \"Google Chrome Desktop\"\n                        :width 1280\n                        :company company\n                        :height 800\n                        :type \"chrome\")\n        (make-instance 'browser\n                        :name \"Nexus 5 (emulated)\"\n                        :company company\n                        :mobile-emulation \"Nexus 5\"\n                        :type \"chrome\")\n        (make-instance 'browser\n                        :name \"Firefox Desktop\"\n                        :width 1280\n                        :company company\n                        :height 800\n                        :type \"firefox\"))))))\n\n(defun summary (build)\n  (format nil \"~a on ~a\"\n          (cond\n            ((sitemap build)\n             \"Sitemap\")\n            (t\n             (format nil \"~a urls \" (length (urls build)))))\n          (cond\n            ((= (length (browsers build)) 1)\n             (browser-name (car (browsers build))))\n            (t\n             (format nil \"~a browsers\"\n                     (length (browsers build)))))))\n\n(defhandler (web-project-recent-runs :uri \"/web-projects/:id/recent-runs\")\n            (id)\n  (let ((id (parse-integer id)))\n   (let ((web-project (store-object-with-id id)))\n     (check-type web-project web-project)\n     (can-view! web-project)\n     (view-build web-project))))\n\n(defhandler (nil :uri \"/web-projects\") ()\n  (with-login ()\n    (app-template\n     :title \"Screenshotbot: Web Projects\"\n     <div>\n       ,(taskie-page-title\n         :title \"Web projects\"\n         <a class= \"me-3\" href= (nibble () (recent-runs))>All runs</a>\n\n         <a href= \"/browsers\" class= \"btn btn-secondary btn-sm\">\n           Browser Configurations\n         </a>\n\n         <a href= (nibble () (new-project)) class= \"btn btn-primary btn-sm ms-1\">\n           New Project\n         </a>)\n       ,(taskie-list\n         :items (web-projects-for-company (current-company))\n         :empty-message \"No web projects\"\n         :headers (list \"Project Name\" \"Description\" \"Next run\")\n         :row-generator (lambda (build)\n                          (taskie-row\n                           :object build\n                           <span>\n                           <a href= (hex:make-url 'web-project-recent-runs\n                                                  :id (store-object-id build)) >\n                               ,(web-project-name build)\n                             </a>\n                           </span>\n\n                           (ui/div\n                            <span>,(summary build) </span>)\n                           (cond\n                             ((and\n                               (web-project-schedule-p build)\n                               (web-project-scheduled-job build))\n                              (taskie-timestamp\n                               :prefix \"\"\n                               :timestamp (scheduled-jobs:at (web-project-scheduled-job build))))\n\n                             ((and\n                               (web-project-schedule-p build)\n                               (next-job-at build))\n                              (taskie-timestamp\n                               :prefix \"Next run\"\n                               :timestamp (next-job-at build)))\n                             (t\n                              <em class= \"text-muted\" >Not scheduled</em>)))))\n     </div>)))\n\n(defun recent-runs ()\n  <app-template title=\"Recent Web runs\">\n    <render-runs runs= (head (remote-runs-for-company (current-company)) 100) />\n  </app-template>)\n\n(defun view-build (build)\n  <app-template title= (format nil \"Screenshotbot: ~a\" (web-project-name build)) >\n    ,(taskie-page-title :title (web-project-name build)\n        <a href= (nibble () (edit-build build)) class= \"btn btn-secondary btn-sm\" >Edit Project</a>\n        <a data-href= (nibble () (run-now build)) class=\"ms-1 btn btn-primary btn-sm modal-link\">Run Now</a>\n                        )\n\n    <render-runs runs=(remote-runs build) />\n  </app-template>)\n\n(defhandler (remote-run-logs-handler :uri \"/web-project-runs/:oid/logs\")\n            (oid)\n  (let ((remote-run (util:find-by-oid oid)))\n    (check-type remote-run remote-run)\n    (can-view! remote-run)\n    (remote-run-logs remote-run)))\n\n(defun make-cancel-nibble (run &key (redirect (hunchentoot:script-name hunchentoot:*request*)))\n  (nibble (:name :interrupt-web-project)\n    (confirmation-modal\n     :yes\n     (nibble (:name :interrupt-web-project-confirm)\n       (setf (remote-run-status run) :cancelled)\n       #+lispworks\n       (multiple-value-bind (thread-id thread)\n           (run-thread-id run)\n         (declare (ignore thread-id))\n         (when thread\n           (safe-interrupt thread)))\n       (hex:safe-redirect redirect))\n     <span>Interrupt job?</span>)))\n\n(markup:deftag render-runs (&key runs)\n  <markup:merge-tag>\n    ,(taskie-list\n      :items runs\n      :checkboxes nil\n      :empty-message \"No previous runs\"\n      :headers (list \"Run date\" \"Status\" \"Running time\")\n      :row-generator (lambda (run)\n                       (taskie-row\n                        :object run\n                        <span>\n                        <a href= (hex:make-url 'remote-run-logs-handler\n                                               :oid (oid run))\n                        >\n                        ,(timeago :timestamp (created-at run))\n                        </a>\n                        </span>\n                        <span>\n\n                        ,(cond\n                           ((donep run)\n                            (case (remote-run-status run)\n                              (:success\n                               \"Finished\")\n                              (:user-aborted\n                               \"Aborted by user\")\n                              (:cancelled\n                               \"Cancelled\")\n                              (otherwise\n                               \"Unknown error\")))\n                           (t\n                            (case (remote-run-status run)\n                              (:unknown\n                               \"Unknown state\")\n                              (:cancelled\n                               \"Cancelled\")\n                              (:queued\n                               <span>\n                                 Queue\n                                 <a href= \"#\" class= \"modal-link\" data-href= (make-cancel-nibble run) >\n                                   Cancel\n                                 </a>\n                               </span>)\n                              (otherwise\n                               <span>\n                                 Running\n                                 <a href= \"#\" class= \"modal-link\" data-href= (make-cancel-nibble run) >\n                                   Cancel\n                                 </a>\n                               </span>))))\n\n                        </span>\n                        <span>\n                        ,(let ((f (or (ignore-errors (finished-at run)) 0))\n                               (s (or (ignore-errors (started-at run)) 0)))\n                           (flet ((%render-end-time (end-time)\n                                    (let* ((secs (- end-time s)))\n                                      (render-time secs))))\n                            (cond\n                              ((or\n                                (not (eql :success (remote-run-status run)))\n                                (and\n                                 (eql f 0)\n                                 (eql s 0)))\n                               \"\")\n                              ((eql f 0)\n                               (%render-end-time (get-universal-time)))\n                              (t\n                               (%render-end-time f)))))\n\n                        </span>\n                        )))\n  </markup:merge-tag>)\n\n(defun render-time (secs)\n  (cond\n    ((< secs 3600)\n     (let ((minute (format nil \"~dm:~2,'0ds\"\n                           (floor secs 60)\n                           (mod secs 60))))\n       minute))\n    (t\n     (format nil \"~dh:~2,'0dm:~2,'0ds\"\n             (floor secs 3600)\n             (floor (mod secs 3600) 60)\n             (mod secs 60)))))\n\n(defun https? ()\n  (string-equal \"https\" (hunchentoot:header-in* :x-forwarded-proto)))\n\n(defun remote-run-log-endpoint (remote-run)\n  (cond\n    ((log-file remote-run)\n     (format nil \"~a://~a/wsapp/replay/logs/~a\"\n             (if (https?)\n                 \"wss\"\n                 \"ws\")\n             (if (https?)\n                 (car (str:split \":\" (hunchentoot:host)))\n                 (hunchentoot:host))\n             (util:oid remote-run)))\n    (t\n     (format nil \"~a/wsapp/replay/logs/~a\"\n             (str:replace-all \"https:\" \"wss:\" (remote-url remote-run))\n             (remote-oid remote-run)))))\n\n(defun remote-run-logs (remote-run)\n  <app-template>\n    <textarea data-websocket-stream= (remote-run-log-endpoint remote-run)\n              disabled= \"disabled\"\n              style=\"height:100vh; width: 100%\" />\n  </app-template>)\n\n(defhandler (nil :uri \"/replay/logs/:oid\") (oid)\n  (with-login ()\n   (let ((remote-run (find-by-oid oid)))\n     (format nil \"Got ~s\" remote-run)\n     (check-type remote-run remote-run)\n     (can-view! remote-run)\n     (remote-run-logs remote-run))))\n\n(defun fix-cronexpr (name cronexpr)\n  (let ((hash (md5:md5sum-string name)))\n    (format nil \"~a ~a ~a\"\n            ;; not truly random, but good enough\n            (mod (elt hash 0) 60)\n            (mod (elt hash 1) 60)\n            cronexpr)))\n\n(defun unfix-cronexpr (cronexpr)\n  (third (str:split \" \" cronexpr :limit 3)))\n\n(defun form (&key submit browsers\n               name\n               urls\n               custom-css\n               exclusions\n               sitemap\n               schedule-p\n               cronexpr)\n  (flet ((option (value title)\n           <option value=value\n                   selected= (if (member value (mapcar #'store-object-id browsers)) markup:+empty+)\n                   data-b=(mapcar #'store-object-id browsers) >\n             ,(progn title)\n           </option>))\n   <simple-card-page max-width= \"50em\" form-action=submit >\n     <div class= \"card-header\">\n       <h3>\n         Update Web Project configuration\n       </h3>\n     </div>\n      <div class= \"form-group mb-3\">\n        <label for= \"name\" class= \"form-label\">Project Name</label>\n        <input type= \"text\" id= \"name\" name= \"name\" class= \"form-control\"\n               value=name />\n      </div>\n\n      <div class= \"form-group mb-3\">\n        <label for= \"urls\" class= \"form-label\">URLs (separate with newline)</label>\n        <textarea id= \"urls\" name= \"urls\" class= \"form-control\" >,(progn urls)</textarea>\n      </div>\n\n      <div class= \"form-group mb-3\">\n        <label for= \"sitemap\" class= \"form-label\">Link to sitemap (optional)</label>\n        <div class= \"row\">\n          <div class= \"col-9\">\n            <input type= \"text\" id= \"sitemap\" name= \"sitemap\" class= \"form-control\"\n               value=sitemap />\n          </div>\n          <div class= \"col-3\">\n            <a href= \"#\" class= \"btn btn-outline-secondary\" style= \"width: 100%\"\n                    data-bs-toggle=\"collapse\" data-bs-target= \"#advanced-sitemap-options\">\n              <mdi name= \"add\" />\n              Advanced\n            </a>\n          </div>\n        </div>\n      </div>\n\n      <div class= (format nil \"form-group mb-3 ~a ms-3\" (when (str:emptyp exclusions) \"collapse\"))\n           id= \"advanced-sitemap-options\" >\n        <div class= \"form-group mb-3\">\n          <label for= \"exclusions\" class= \"form-label\">Exclude URLs that match</label>\n          <textarea class= \"form-control\" name= \"exclusions\" >,(progn exclusions)</textarea>\n        </div>\n      </div>\n\n      <div class= \"form-group mb-3\">\n        <label for= \"browsers\" class= \"form-label\">\n          Pick upto 5 browsers to run on\n          ,(when (staging-p)\n             <span>\n               (<a href= \"/browsers\">Add or modify Browsers</a>)\n             </span>)\n        </label>\n        <select id= \"browsers\" name= \"browsers\" class= \"form-select\" multiple >\n          ,@(loop for browser in (get-browsers)\n                  collect (option (store-object-id browser)\n                                  (format nil \"~a\" (browser-name browser))))\n        </select>\n      </div>\n\n      <div class= \"form-group mb-3\">\n        <label for= \"custom-css\" class= \"form-label\">Override CSS (Advanced) </label>\n        <textarea id= \"custom-css\" name= \"custom-css\" class= \"form-control\" >,(progn custom-css)</textarea>\n      </div>\n\n      <div class= \"form-group mb-3\">\n        <input type= \"checkbox\" id= \"schedule-p\" name= \"schedule-p\" class= \"form-check-input\"\n               checked= (when schedule-p \"checked\") />\n        <label for= \"schedule-p\" class= \"form-check-label\" >Schedule <explain-cron-expr />\n        </label>\n\n        <input type= \"input\" name= \"cronexpr\" id= \"cronexpr\"\n               class= \"form-control mt-1\"\n               placeholder= \"*/3 * * *\"\n               value= cronexpr />\n\n      </div>\n\n\n      <div class= \"card-footer\">\n        <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n        <a href= \"/web-projects\" class= \"btn btn-secondary\">Cancel</a>\n      </div>\n  </simple-card-page>))\n\n(defun run-now (build)\n  \"The name of this function is important, it's stored in the scheduled jobs on disk\"\n  (restart-case\n      (confirmation-modal\n       :title (web-project-name build)\n       :yes (nibble ()  (actually-run-now build)\n              (hex:safe-redirect (nibble ()\n                                   (view-build build))))\n       <p>Schedule a run for ,(web-project-name build)?</p>)\n    (retry-run-now ()\n      (run-now build))))\n\n(defun scheduled-job-run-now (project)\n  (cond\n    ((and scheduled-jobs:*scheduled-job*\n          (not\n           (eql scheduled-jobs:*scheduled-job* (web-project-scheduled-job project))))\n     (warn \"Stale scheduled job for project: ~a\" scheduled-jobs:*scheduled-job*))\n    (t\n     (let ((company (company project)))\n       (assert (company-owner company))\n       (actually-run-now\n        project\n        :user (company-owner company)\n        :company company\n        :host (installation-domain (installation)))))))\n\n(defun actually-run-now (build\n                         &key (user (current-user))\n                           (company (current-company))\n                           (host (get-request-domain-prefix)))\n  (assert user)\n  (assert company)\n  (restart-case\n      (let* ((run (make-instance 'integration:run\n                                 :user user\n                                 :company company\n                                 :sitemap (unless (str:emptyp (sitemap build))\n                                            (sitemap build))\n                                 :exclusions (exclusions build)\n                                 :urls (loop for url in (urls build)\n                                             collect (cons url url))\n                                 :custom-css (custom-css build)\n                                 :channel (web-project-name build)\n                                 :host host\n                                 :sleep (if (gk:check :replay-long-sleep company)\n                                            5\n                                            0.5)\n                                 :browser-configs\n                                 (loop for browser in (browsers build)\n                                       collect\n                                       (make-instance 'frontend:browser-config\n                                                      :type (browser-type browser)\n                                                      :mobile-emulation (mobile-emulation browser)\n                                                      :dimensions\n                                                      (when (width browser)\n                                                        (make-instance 'frontend:dimensions\n                                                                       :width (width browser)\n                                                                       :height (height browser)))\n                                                      :name (browser-name browser))))))\n        (check-type (urls build) list)\n        (let ((remote-run\n                (send-remote-run run :company company)))\n          (push-remote-run build remote-run)\n          \"OK\"))\n    (retry-actually-run-now ()\n      (actually-run-now build))))\n\n(defun new-project ()\n  (let (submit)\n    (setf submit (nibble (name\n                          urls\n                          sitemap\n                          schedule-p\n                          exclusions\n                          cronexpr)\n                   (form-submit :name name\n                                :urls urls\n                                :sitemap sitemap\n                                :schedule-p schedule-p\n                                :cronexpr cronexpr\n                                :exclusions exclusions\n                                :submit submit)))\n    (form :submit submit)))\n\n(defun multiple-parameters (name)\n  (loop for (key . value) in (hunchentoot:post-parameters*)\n        if (string-equal name key)\n          collect value))\n\n(defun fix-cron-message (message)\n  \"Fixes the cron message from cron-parse-expr to be user-friendly\"\n  (cond\n    ((str:containsp \"Invalid number\" message)\n     (str:replace-all \"6 fields\" \"4 fields\" message))\n    (t (format nil \"Error from cron parser: ~A\" message))))\n\n(markup:deftag explain-cron-expr ()\n  <explain>\n    <p>\n      Use a cron like syntax to schedule your jobs. e.g. to schedule every every day at 8am, do\n      <span class= \"text-monospace\" >8 * * *</span>\n    </p>\n\n    <p>\n      Unlike Cron you can't specify minutes, Screenshotbot will pick a random minute.\n    </p>\n\n    <p>\n      The fields are respectively: Hour, Day of Month, Month, Day of Week.\n    </p>\n\n    <h6>Examples</h6>\n\n    <p>\n      To schedule every three hours: */3 * * *. To schedule 8am and 4pm on weekdays do: 8,4 * * MON-FRI.\n    </p>\n  </explain>)\n\n(defun validate-url (url)\n  (when-let ((uri (quri:uri url)))\n    (and\n     (quri:uri-scheme uri))))\n\n(defun form-submit (&rest args &key name urls sitemap submit\n                                 edit\n                                 schedule-p\n                                 exclusions\n                                 custom-css\n                                 cronexpr\n                                 (browsers (multiple-parameters \"browsers\")))\n  (restart-case\n      (let ((errors))\n        (flet ((check (test field message)\n                 (unless test\n                   (push (cons field message) errors))))\n          (check (not (str:emptyp name)) :name \"Please provide a project name\")\n          (let ((urls (remove-if #'str:emptyp (mapcar #'str:trim (str:lines urls))))\n                (exclusions (remove-if #'str:emptyp (mapcar #'str:trim (str:lines exclusions))))\n                (sitemap (str:trim sitemap))\n                (browsers (loop for browser in browsers\n                                for obj = (store-object-with-id (parse-integer browser))\n                                if (eql (current-company)\n                                        (company obj))\n                                  collect obj)))\n            (cond\n              ((not (str:emptyp sitemap))\n               (check (ignore-errors (quri:parse-uri sitemap))\n                      :sitemap \"Invalid URL\"))\n              (t\n               (check urls :urls \"Must provide at least one URL\")))\n            (check browsers\n                   :browsers\n                   \"Please select at least one browser\")\n\n            (loop for url in urls\n                  do (check (validate-url url)\n                            :urls\n                            (format nil \"Invalid URL: ~a\" url)))\n            (check (or\n                    (str:emptyp sitemap)\n                    (null urls))\n                   :sitemap\n                   \"Please provide only one of URLs or sitemap\")\n            (check (< (length browsers) 5)\n                   :browsers\n                   \"Too many browsers selected\")\n            (when exclusions\n              (check (not (str:emptyp sitemap))\n                     :exclusions\n                     \"Exclusions are only available if you use a sitemap\")\n              (loop for exclusion in exclusions\n                    for line from 1\n                    do\n                       (check (ignore-errors (cl-ppcre:create-scanner exclusion))\n                              :exclusions\n                              (format nil \"Regular expression on line ~a could not be parsed\" line))))\n\n            (when schedule-p\n              (handler-case\n                  (cron-parse-expr (fix-cronexpr name  cronexpr))\n                (invalid-cron-expr (e)\n                  (let ((message (invalid-cron-expr-message e)))\n                    (check nil\n                           :cronexpr\n                           (fix-cron-message message))))))\n            (cond\n              (errors\n               (let ((exclusions (str:join #\\Newline exclusions)))\n                 (with-form-errors (:errors errors\n                                    :name name\n                                    :cronexpr cronexpr\n                                    :schedule-p :schedule-p\n                                    :urls (str:join #\\Newline urls)\n                                    :sitemap sitemap\n                                    :exclusions exclusions\n                                    :was-validated t)\n                   (form :submit submit\n                         :exclusions exclusions\n                         :browsers browsers))))\n              ((not edit)\n               (let ((project\n                      (make-instance 'web-project\n                                      :name name\n                                      :company (current-company)\n                                      :schedule-p schedule-p\n                                      :urls urls\n                                      :exclusions exclusions\n                                      :custom-css custom-css\n                                      :browsers browsers\n                                      :sitemap sitemap)))\n                 (push-event :new-web-build)\n                 (update-scheduled-job project\n                                       schedule-p\n                                       cronexpr))\n\n               (hex:safe-redirect \"/web-projects\"))\n              (edit\n               (with-transaction ()\n                 (setf (web-project-name edit) name)\n                 (setf (urls edit) urls)\n                 (setf (browsers edit) browsers)\n                 (Setf (web-project-schedule-p edit) schedule-p)\n                 (setf (exclusions edit) exclusions)\n                 (setf (custom-css edit) custom-css)\n                 (setf (sitemap edit) sitemap))\n               (update-scheduled-job edit\n                                     schedule-p\n                                     cronexpr)\n               (hex:safe-redirect \"/web-projects\"))))))\n    (retry-form-submit ()\n      (apply #'form-submit args))))\n\n(defun update-scheduled-job (self schedule-p cronexpr)\n  (a:when-let ((scheduled-job (web-project-scheduled-job self)))\n    (with-transaction ()\n     (setf (web-project-scheduled-job self) nil))\n    (unless (bknr.datastore::object-destroyed-p scheduled-job)\n      (bknr.datastore:delete-object scheduled-job)))\n  (when schedule-p\n    (let ((job (make-scheduled-job :cronexpr (fix-cronexpr (web-project-name self) cronexpr)\n                                   :tzname \"America/New_York\" ;; TODO\n                                   :function 'scheduled-job-run-now\n                                   :args (list self))))\n      (with-transaction ()\n        (setf (web-project-scheduled-job self) job)))))\n\n(defun edit-build (build)\n  (let (submit)\n    (setf submit\n          (nibble (name urls sitemap\n                        schedule-p\n                        custom-css\n                        exclusions\n                        cronexpr)\n            (form-submit :name name\n                         :urls urls\n                         :sitemap sitemap\n                         :edit build\n                         :schedule-p schedule-p\n                         :custom-css custom-css\n                         :exclusions exclusions\n                         :cronexpr cronexpr\n                         :submit submit)))\n   (form :name (web-project-name build)\n         :urls (str:join #\\Newline (urls build))\n         :sitemap (sitemap build)\n         :browsers (browsers build)\n         :custom-css (custom-css build)\n         :exclusions (str:join #\\Newline (exclusions build))\n         :submit submit\n         :schedule-p (web-project-schedule-p build)\n         :cronexpr (a:when-let (scheduled-job\n                                (web-project-scheduled-job build))\n                     (unless (bknr.datastore::object-destroyed-p scheduled-job)\n                      (unfix-cronexpr (cronexpr  scheduled-job)))))))\n\n#+nil\n(defun migrate-to-cronexpr ()\n  (loop for build in (bknr.datastore:store-objects-with-class 'web-project)\n        if (and\n            (not (web-project-scheduled-job build))\n            (web-project-schedule-p build)\n            (web-project-schedule-every build))\n          collect build))\n"
  },
  {
    "path": "src/screenshotbot/web-build/scheduler.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/web-build/scheduler\n  (:use #:cl)\n  (:nicknames :screenshotbot/pro/web-build/scheduler)\n  (:import-from #:screenshotbot/web-build/project\n                #:web-project-scheduled-job\n                #:update-next-job-at\n                #:web-project-name\n                #:web-project-schedule-p\n                #:actually-run-now\n                #:company\n                #:web-project-schedule-every\n                #:next-job-at\n                #:web-project)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:screenshotbot/model/company\n                #:company-owner)\n  (:import-from #:screenshotbot/server\n                #:*domain*)\n  (:import-from #:screenshotbot/installation\n                #:installation\n                #:installation-domain)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:update-next-job-at))\n(in-package :screenshotbot/web-build/scheduler)\n\n(defun start-ts (id)\n  (mod\n   (reduce\n    (lambda (a b) (+ b (* 256 a)))\n    (md5:md5sum-string (format nil \"~a\" id))\n    :initial-value 0)\n   (* 24 3600)))\n\n(defvar *lock* (bt:make-lock \"scheduler\"))\n\n(defun next-runtime (now start-ts schedule)\n  \"Find the lowest start-ts + k * schedule * 3600 that's greater than\n   now\"\n  (assert (> schedule 0))\n  (let ((k (ceiling (- now start-ts) (* schedule 3600))))\n    (+ start-ts (* k schedule 3600))))\n\n(defun %run-now (project)\n  (let ((company (company project)))\n    (assert (company-owner company))\n    (actually-run-now\n     project\n     :user (company-owner company)\n     :company company\n     :host (installation-domain (installation)))))\n\n(defun update-next-job-at (project)\n  (with-transaction ()\n    (setf (next-job-at project) nil))\n  (maybe-run-project project))\n\n\n(with-auto-restart ()\n (defun maybe-run-project (project &key\n                                     (now (get-universal-time))\n                                     (start-ts nil))\n   \"Maybe run the project if needed. As an edge case, if the\nnext-runtime is not set, then it is calculated and updated.\"\n   (unless (web-project-scheduled-job project) ;; new style\n    (let ((start-ts (or start-ts\n                        (start-ts (web-project-name project)))))\n      (bt:with-lock-held (*lock*)\n        (when (web-project-schedule-p project)\n          (flet ((update-next-job-at ()\n                   (let ((update (next-runtime now start-ts\n                                               (web-project-schedule-every project))))\n                     (with-transaction ()\n                       (setf (next-job-at project)\n                             update)))))\n            (let ((next-job-at (next-job-at project)))\n              (when (and next-job-at (< next-job-at now))\n                (%run-now project))\n              (update-next-job-at)))))))))\n\n\n(defun run-web-projects ()\n  (log:debug \"Searching for web projects that are ready to run\")\n  (loop for project in (bknr.datastore:store-objects-with-class 'web-project) do\n    (maybe-run-project project)))\n\n\n(def-cron run-web-projects ()\n  (run-web-projects))\n"
  },
  {
    "path": "src/screenshotbot/web-build/test-project.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/web-build/test-project\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/web-build/project\n                #:render-time\n                #:form\n                #:scheduled-job-run-now\n                #:actually-run-now\n                #:web-project-scheduled-job\n                #:update-scheduled-job\n                #:web-project)\n  (:import-from #:bknr.datastore\n                #:object-destroyed-p)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:screenshotbot/testing\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:screenshotbot/model/user\n                #:make-user)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/web-build/test-project)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-installation ()\n   (with-test-store ()\n     (cl-mock:with-mocks ()\n       (let* ((user (make-user))\n              (company (make-instance 'company :owner user))\n              (project (make-instance 'web-project :name \"foobar\"\n                                                   :company company)))\n         (&body))))))\n\n(test update-scheduled-job\n  (with-fixture state ()\n    (update-scheduled-job project t \"*/3 * * *\")\n    (let ((first-job (web-project-scheduled-job project)))\n      (is-true first-job)\n      (update-scheduled-job project t \"*/4 * * *\")\n      (let ((second-job (web-project-scheduled-job project)))\n        (is-true second-job)\n        (is (not (eql first-job second-job)))\n        (is (object-destroyed-p first-job))))))\n\n(def-fixture mocked-actually-run-now ()\n  (let ((calls nil))\n    (cl-mock:if-called 'actually-run-now\n                        (lambda (project &rest args)\n                          (declare (ignore args))\n                          (push project calls)))\n    (&body)))\n\n(test scheduled-job-run-now-running-in-correct-job\n  (with-fixture state ()\n   (with-fixture mocked-actually-run-now ()\n     (update-scheduled-job project t \"* * * *\")\n     (let ((scheduled-jobs:*scheduled-job*\n             (web-project-scheduled-job project)))\n       (scheduled-job-run-now project))\n     (is (equal (list project)\n                calls)))))\n\n(test if-scheduled-job-is-incorrect-dont-run\n  (with-fixture state ()\n    (with-fixture mocked-actually-run-now ()\n      (update-scheduled-job project t \"* * * *\")\n      (let ((old-job (web-project-scheduled-job project)))\n        (update-scheduled-job project t \"* * * *\")\n        (let ((scheduled-jobs:*scheduled-job*\n                old-job))\n          (scheduled-job-run-now project)))\n      (is (equal nil calls)))))\n\n(def-fixture screenshots ()\n  (with-fake-request ()\n    (auth:with-sessions ()\n      (&body))))\n\n(screenshot-test web-build-new-form\n  (with-fixture state ()\n    (with-fixture screenshots ()\n      (form :submit \"\"))))\n\n(screenshot-test web-form-with-exclusions\n  (with-fixture state ()\n    (with-fixture screenshots ()\n      (form :submit \"\" :exclusions \"https://goog.gl/foo.txt\"))))\n\n(test render-time\n  (is (equal \"2m:01s\"\n             (render-time 121)))\n  (is (equal \"2m:11s\"\n             (render-time 131)))\n    (is (equal \"1h:02m:11s\"\n             (render-time (+ 3600 131)))))\n"
  },
  {
    "path": "src/screenshotbot/web-build/test-scheduler.lisp",
    "content": "(defpackage :screenshotbot/web-build/test-scheduler\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/web-build/scheduler\n                #:%run-now\n                #:next-runtime\n                #:maybe-run-project\n                #:project\n                #:start-ts)\n  (:import-from #:screenshotbot/pro/web-build/project\n                #:next-job-at\n                #:web-project)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:with-transaction)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/web-build/test-scheduler)\n\n\n(util/fiveam:def-suite)\n\n(test start-ts\n  (is (equal 82174 (start-ts 980))))\n\n(def-fixture state (&key (schedule-p t))\n  (with-test-store ()\n   (cl-mock:with-mocks ()\n     (let ((project (make-instance 'web-project\n                                    :schedule-p schedule-p\n                                    :schedule-every 2)))\n       (unwind-protect\n            (progn (&body))\n         (delete-object project))))))\n\n(test maybe-run-project-on-project-without-ts\n  (with-fixture state ()\n    (cl-mock:answer\n     (next-runtime now 0 2)\n     45)\n   (let ((now (* 10 24 3600)))\n     (maybe-run-project project\n                        :now now\n                        :start-ts 0)\n     (is (equal 45\n                (next-job-at project))))))\n\n(test next-runtime ()\n  (is (equal 3600\n             (next-runtime 3600 0 1)))\n  (is (equal 3602\n             (next-runtime 3600 2 1)))\n  (is (equal 7202\n             (next-runtime 3600 2 2))))\n\n(test maybe-run-project-on-project-with-ts\n  (with-fixture state ()\n    (cl-mock:answer\n     (next-runtime now 0 2)\n      45)\n    (cl-mock:answer\n        (%run-now project)\n      nil)\n    (with-transaction ()\n      (setf (next-job-at project) 0))\n    (let ((now (* 10 24 3600)))\n      (maybe-run-project project\n                         :now now\n                         :start-ts 0)\n      (is (equal 45\n                 (next-job-at project))))))\n\n(test does-not-do-anything-if-schedule-p-not-set\n  (with-fixture state (:schedule-p\n                       nil)\n    (cl-mock:if-called\n     'next-runtime\n     (lambda (&rest args)\n       (error \"should not be called\")))\n    (let ((now (* 10 24 3600)))\n      (maybe-run-project project\n                         :now now\n                         :start-ts 0)\n      (is (equal nil\n                 (next-job-at project))))))\n"
  },
  {
    "path": "src/screenshotbot/webdriver/all.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :screenshotbot/webdriver\n    (:use-reexport #:screenshotbot/webdriver/impl))\n"
  },
  {
    "path": "src/screenshotbot/webdriver/impl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package #:screenshotbot/webdriver/impl\n    (:use #:cl\n          #:alexandria)\n  (:import-from #:webdriver-client\n                #:ignore-session-deletion\n                #:window-resize\n                #:screenshot\n                #:with-session)\n  (:export   #:with-webdriver\n             #:take-screenshot\n             #:chrome\n             #:firefox\n             #:call-with-webdriver))\n(in-package #:screenshotbot/webdriver/impl)\n\n\n(defun make-dict (alist)\n  (let ((ret (make-hash-table)))\n    (loop for (x . y) in alist do\n      (setf (gethash x ret) y))\n    ret))\n\n\n(defclass driver ()\n  ((proxy :initarg :proxy\n          :reader driver-proxy)))\n\n(defclass chrome (driver)\n  ())\n\n(defclass firefox (driver)\n  ())\n\n(defclass safari (driver)\n  ())\n\n(defmethod extra-driver-options (driver &key &allow-other-keys)\n  )\n\n#+nil\n(defun proxy ()\n  \"squid:3128\")\n\n(defmethod extra-driver-options ((driver firefox) &key &allow-other-keys)\n  (make-default-proxy-settings driver))\n\n(defun make-default-proxy-settings (driver)\n  (let ((proxy (format nil \"~a\" (driver-proxy driver))))\n    `((\"proxy\" . ,(make-dict\n                   `((\"proxyType\" . \"manual\")\n                     (\"httpProxy\" . ,proxy)\n                     (\"sslProxy\" . ,proxy)))))))\n\n(defmethod extra-driver-options ((driver safari) &key &allow-other-keys)\n  ;; Safari doesn't support specifying proxy in capabilities. Instead\n  ;; you need to physically set the proxy on the machine.\n  #+nil\n  (make-default-proxy-settings driver))\n\n(defmethod extra-driver-options ((driver chrome)\n                                 &key mobile-emulation\n                                 &allow-other-keys)\n  (declare (ignore dimensions))\n  (let ((options `((\"args\" . (\"--no-sandbox\"\n                              \"--disable-gpu\"\n                              \"--deterministic-mode\"\n                              \"--hide-scrollbars\"\n                              ,(format nil \"--proxy-server=~a\" (driver-proxy driver)))))))\n    (when mobile-emulation\n      (push `(\"mobileEmulation\" . ,(make-dict `((\"deviceName\" . ,mobile-emulation))))\n            options))\n    `((\"goog:chromeOptions\" . ,(make-dict options)))))\n\n(defun make-driver (browser &rest args &key (proxy (error \"must specify :proxy\")))\n  (cond\n    ((symbolp browser)\n     (apply #'make-instance browser args))\n    (t\n     (apply #'make-instance\n            (find browser '(chrome firefox safari) :test #'string-equal)\n            args))))\n\n(defun call-with-webdriver (fn &rest args\n                            &key browser\n                              mobile-emulation\n                              (proxy (error \"must specify proxy\"))\n                              dimensions)\n  (restart-case\n      (let ((driver (make-driver browser :proxy proxy)))\n        (handler-bind\n            ((error (lambda (e)\n                      (declare (ignore e))\n                      (alexandria:when-let ((restart (find-restart 'ignore-session-deletion)))\n                        (log:warn \"Ignore error when deleting session\")\n                        (invoke-restart restart)))))\n         (with-session `(:always-match ((\"browserName\" . ,(string-downcase browser))\n                                        ,@ (extra-driver-options driver\n                                                                 :mobile-emulation mobile-emulation\n                                                                 :dimension dimensions)))\n           (when dimensions\n             (window-resize :width (car dimensions)\n                            :height (cdr dimensions)))\n           (funcall fn driver))))\n    (retry-webdriver-execution ()\n      (apply #'call-with-webdriver fn args))))\n\n(defmacro with-webdriver ((driver &rest args) &body body)\n  `(call-with-webdriver (lambda (,driver)\n                          ,@body)\n                        ,@args))\n"
  },
  {
    "path": "src/screenshotbot/webdriver/runner.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webdriver/runner\n  (:use #:cl\n        #:util/java)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:*runner*\n   #:runner\n   #:driver\n   #:runner-take-screenshot\n   #:finalize-runner))\n(in-package :screenshotbot/webdriver/runner)\n\n(named-readtables:in-readtable java-syntax)\n\n(defvar *runner*)\n\n(defun output-type-file ()\n  (read-java-field #,org.openqa.selenium.OutputType \"FILE\"))\n\n(defclass runner ()\n  ((driver :initarg :driver\n           :reader driver)))\n\n(defmethod runner-take-screenshot ((runner runner) output)\n  (let* ((driver (driver runner))\n         (file (#_getScreenshotAs driver (output-type-file))))\n    (ensure-directories-exist output)\n    (uiop:rename-file-overwriting-target\n     (#_getAbsolutePath file)\n     output)))\n\n(defmethod finalize-runner ((runner runner))\n  (values))\n"
  },
  {
    "path": "src/screenshotbot/webdriver/screenshot.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webdriver/screenshot\n  (:use #:cl)\n  (:import-from #:screenshotbot/webdriver/impl\n                #:chrome\n                #:firefox\n                #:take-screenshot)\n  (:import-from #:webdriver-client\n                #:http-post-value\n                #:*session*\n                #:session-path\n                #:screenshot\n                #:http-get\n                #:http-get-value)\n  (:import-from #:auto-restart\n                #:with-auto-restart)\n  (:local-nicknames (#:a #:alexandria)\n                    #-lispworks\n                    (#:fli #:util/fake-fli))\n  (:export\n   #:full-page-screenshot))\n(in-package :screenshotbot/webdriver/screenshot)\n\n(defun decode-image (encoded-image output)\n  (when output\n   (with-open-file (out-stream output :direction :output :element-type 'flexi-streams:octet)\n     (base64:base64-string-to-stream encoded-image\n                                     :stream out-stream))))\n\n(defun take-screenshot (output)\n  (when output\n   (ensure-directories-exist output))\n  (let ((encoded-image (screenshot)))\n    (when output\n     (decode-image encoded-image output))))\n\n(defmethod full-page-screenshot (driver file)\n  (take-screenshot file))\n\n(with-auto-restart (:retries 3 :sleep 3)\n  (defmethod full-page-screenshot :around (driver file)\n    (call-next-method)))\n\n(defmethod full-page-screenshot ((driver firefox) file)\n  (with-open-stream (stream (drakma:http-request\n                             (format nil \"~a\"\n                              (webdriver-client::make-uri\n                               (session-path *session* \"/moz/screenshot/full\")))\n                             :want-stream t))\n    (flet ((decode-image (file)\n             (decode-file-from-json-stream\n              stream\n              \"value\"\n              :output file)))\n     (cond\n       ((or (null file) (string-equal \"png\" (pathname-type file)))\n        (decode-image  file))\n       (t\n        (error \"type ~a not supported\" (pathname-type file)))))))\n\n(defmethod execute-cdp-command ((driver chrome) command args)\n  (http-post-value\n   (session-path *session* \"/goog/cdp/execute\")\n   :cmd command\n   :params args))\n\n#+nil\n(defparameter *buf-size* 1024\n  \"A buffer size for base64 decoding\")\n\n#-(and lispworks linux)\n (defun read-base64-stream-to-file (stream output)\n  (with-open-file (output output\n                          :direction :output\n                          :element-type '(unsigned-byte 8))\n    (let ((output\n            #+sbcl\n            (flexi-streams:make-flexi-stream output :external-format :latin-1)\n            #-sbcl\n            output))\n     (read-base64-stream-to-stream\n      stream output))))\n\n#-(and lispworks linux)\n(defun read-base64-stream-to-stream (input output)\n  ;; Can we do this faster?\n  (handler-case\n      (progn\n        (base64:base64-stream-to-stream\n         input\n         :stream output))\n    (base64:bad-base64-character (e)\n      (assert (eql (char-code #\\\") (base64::bad-base64-character-code e))))))\n\n\n#+(and lispworks linux #| See the Mac test failures on, e.g., D8933 |#)\n(defun read-base64-stream-to-file (stream output)\n  (with-open-file (output output :element-type '(unsigned-byte 8)\n                                 :direction :output)\n    (let ((evp-ctx (evp-encode-ctx-new)))\n      (unwind-protect\n           (let* ((len (* 64 100))\n                  (inbuff (make-array len :element-type 'base-char :allocation :pinnable))\n                  (outbuff (make-array len :element-type '(unsigned-byte 8) :allocation :pinnable)))\n             (evp-decode-init evp-ctx)\n             (loop for bytes = (read-sequence inbuff stream)\n                   for pass from 0\n                   for bytes-2 = (find-double-quote inbuff bytes)\n                   while (> bytes-2 0)\n                   do\n                      (multiple-value-bind (result out-size)\n                          (evp-decode-update\n                           evp-ctx\n                           outbuff\n                           (length outbuff)\n                           inbuff\n                           bytes-2)\n                        (assert (<= 0 result 1))\n                        (write-sequence\n                         outbuff\n                         output\n                         :end out-size)))\n             (multiple-value-bind (result out-size)\n                 (evp-decode-final\n                  evp-ctx\n                  outbuff\n                  (length outbuff))\n               (assert (= result 1))\n               (write-sequence\n                outbuff\n                output\n                :end out-size)))\n        (evp-encode-ctx-free evp-ctx)))))\n\n(defun find-double-quote (arr len)\n  (declare (type (array base-char) arr)\n           (type fixnum len)\n           (optimize (speed 3) (debug 0)))\n  (loop for i fixnum from 0 below len\n        if (eql (aref arr i) #\\\")\n          do (return-from find-double-quote i)\n        finally (return len)))\n\n(fli:define-c-struct evp-encode-ctx)\n\n(fli:define-foreign-function (evp-encode-ctx-new \"EVP_ENCODE_CTX_new\")\n    ()\n  :result-type (:pointer evp-encode-ctx))\n\n(fli:define-foreign-function (evp-encode-ctx-free \"EVP_ENCODE_CTX_free\")\n    ((ctx (:pointer evp-encode-ctx)))\n  :result-type :void)\n\n(fli:define-foreign-function (evp-decode-init \"EVP_DecodeInit\")\n    ((ctx (:pointer evp-encode-ctx)))\n  :result-type :void)\n\n#+lispworks\n(fli:define-foreign-function (evp-decode-update \"EVP_DecodeUpdate\")\n    ((ctx (:pointer evp-encode-ctx))\n     (out :lisp-simple-1d-array)\n     (out-size (:reference-return :int))\n     (in :lisp-simple-1d-array)\n     (in-size :int))\n  :result-type :int)\n\n#+lispworks\n(fli:define-foreign-function (evp-decode-final \"EVP_DecodeFinal\")\n    ((ctx (:pointer evp-encode-ctx))\n     (out :lisp-simple-1d-array)\n     (out-size (:reference-return :int)))\n  :result-type :int)\n\n(defun decode-file-from-json-stream (stream key &key (output (error \"must provide :output\")))\n  \"Given a json, with a value that is a large base64 encoded file, decode the base64 file and save it \"\n  (let* ((found-data-p nil)\n         (decoder (cl-json:custom-decoder\n                   :beginning-of-string\n                   (lambda ()\n                     (cond\n                       (found-data-p\n                        ;; We're going to treat the rest of the\n                        ;; stream as just a Base64 stream.\n                        (when output\n                          (read-base64-stream-to-file stream output))\n                        (return-from decode-file-from-json-stream nil))\n                       (t\n                        (json::init-string-stream-accumulator))))\n                   :end-of-string\n                   (lambda ()\n                     (let ((ret (json::string-stream-accumulator-get)))\n                       (when (equal key ret)\n                         (setf found-data-p t))\n                       ret)))))\n    (funcall decoder stream)\n    ;; The decode shoul\n    d return from the toplevel function\n    (error \"Did not find a :data attribute in the response json\")))\n\n\n(defmethod execute-streaming-cdp-command ((driver chrome) command args &key output)\n  (with-open-stream (stream (drakma:http-request\n                             (format nil \"~a\"\n                                     (webdriver-client::make-uri (session-path webdriver-client::*session* \"/goog/cdp/execute\")))\n                             :content (cl-json:encode-json-plist-to-string\n                                       `(:cmd ,command :params ,args))\n                             :content-type \"application/json:charset=UTF-8\"\n                             :method :post\n                             :want-stream t))\n    (decode-file-from-json-stream\n     stream\n     \"data\"\n     :output output)))\n\n(defmethod full-page-screenshot ((driver chrome) file)\n  ;; This is documented in some places, for example:\n  ;; https://stackoverflow.com/questions/41721734/take-screenshot-of-full-page-with-selenium-python-with-chromedriver\n  ;; ... but as of now, not in the official selenium code\n  (let ((rect (execute-cdp-command driver \"Page.getLayoutMetrics\" (make-hash-table))))\n\n    ;; Let's try to limit the screenshot to 25 million pixels (5000x5000).\n    (let* ((max-pixels 12500000)\n           (additional-scale-factor 1)\n           (visual-viewport (a:assoc-value rect :css-visual-viewport))\n           (zoom (a:assoc-value visual-viewport :zoom))\n           (pixel-ratio (webdriver-client:execute-script \"return window.devicePixelRatio\" nil))\n           (content-size (a:assoc-value rect :content-size))\n           (width (a:assoc-value content-size :width))\n           (height (min\n                    (a:assoc-value content-size :height)\n                    (floor max-pixels (max 1 width)))))\n\n\n      (assert (= zoom 1))\n      #+nil\n      (assert (= pixel-ratio 1))\n\n\n      (let ((screenshot (execute-streaming-cdp-command driver \"Page.captureScreenshot\"\n                                                       `((\"captureBeyondViewport\" . t)\n                                                         (\"clip\" . ((:x . 0)\n                                                                    (:y . 0)\n                                                                    (:height . ,height)\n                                                                    (:width . ,width)\n                                                                    (:scale . ,(/ 1.0 pixel-ratio\n                                                                                  additional-scale-factor))))\n                                                         (:format . \"png\"))\n                                                       :output file)))\n        (when file\n         (assert (< (trivial-file-size:file-size-in-octets file)\n                    25000000)))))))\n"
  },
  {
    "path": "src/screenshotbot/webdriver/test-screenshot.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webdriver/test-screenshot\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/webdriver/screenshot\n                #:decode-file-from-json-stream)\n  (:import-from #:util/digests\n                #:md5-file)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :screenshotbot/webdriver/test-screenshot)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let* ((input-file #.(asdf:system-relative-pathname :screenshotbot \"fixture/rose.png\"))\n         (original-md5 (ironclad:byte-array-to-hex-string (md5-file input-file))))\n    (&body)))\n\n(defun read-file-bytes (input-file)\n  (flexi-streams:with-output-to-sequence (output)\n    (with-open-file (input input-file :element-type '(unsigned-byte 8))\n      (uiop:copy-stream-to-stream input output :element-type '(unsigned-byte 8)))))\n\n#-ccl\n(test simple-base64-decoding\n  (with-fixture state ()\n    (let* ((binary (read-file-bytes input-file))\n           (base64 (base64:usb8-array-to-base64-string binary))\n           (input (format nil \"{\\\"data\\\":\\\"~a\\\"}\" base64)))\n      (is (eql 6975 (length binary)))\n      (is (eql (* 4/3 6975) (length base64)))\n      (uiop:with-temporary-file (:pathname output)\n        (delete-file output)\n        (let ((stream (make-string-input-stream input)))\n          (decode-file-from-json-stream\n           stream\n           \"data\"\n           :output output))\n        (is (path:-e output))\n        (is (equal (length binary) (trivial-file-size:file-size-in-octets input-file)))\n        (loop for e1 across (read-file-bytes input-file)\n              for e2 across (read-file-bytes output)\n              for i from 0\n              if (not (eql e1 e2))\n                do (Fail \"At position ~a: expected ~a, got ~a\" i e1 e2)\n                (return))\n        (is (Equalp (read-file-bytes input-file)\n                    (read-file-bytes output)))\n        (is (equal (trivial-file-size:file-size-in-octets input-file)\n                   (trivial-file-size:file-size-in-octets output)))\n        (is (equal\n             original-md5\n             (ironclad:byte-array-to-hex-string (md5:md5sum-file output))))))))\n"
  },
  {
    "path": "src/screenshotbot/webhook/model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webhook/model\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-class-validation)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index\n                #:fset-unique-index)\n  (:import-from #:screenshotbot/model/company\n                #:company\n                #:company-with-name)\n  (:import-from #:util/store/object-id\n                #:find-by-oid)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:screenshotbot/model/core\n                #:generate-api-key)\n  (:import-from #:screenshotbot/audit-log\n                #:base-audit-log)\n  (:export\n   #:signing-key\n   #:update-config\n   #:webhook-event))\n(in-package :screenshotbot/webhook/model)\n\n(defvar *lock* (bt:make-lock))\n\n(defindex +company-event-index+ 'fset-set-index :slot-name '%company)\n\n(with-class-validation\n  (defclass webhook-event (base-audit-log)\n    ((event :initarg :event\n            :reader event\n            :documentation \"The name of the event being sent. This will also be in the payload, but we use this for rendering information.\")\n     (%payload :initarg :payload\n               :reader event-payload))\n    (:metaclass persistent-class)))\n\n(defindex +company-index+ 'fset-unique-index :slot-name '%company)\n\n\n(with-class-validation\n  (defclass webhook-company-config (store-object)\n    ((%company :initarg :company\n               :reader config-company\n               :index +company-index+\n               :index-reader webhook-config-for-company)\n     (endpoint :initarg :endpoint\n               :accessor endpoint\n               :initform \"https://tdrhq.com/sb-webhook\")\n     (signing-key\n      :initarg :signing-key\n      :reader signing-key\n      :initform \"foobarCardar\")\n     (enabledp\n      :initarg :enabledp\n      :reader enabledp))\n    (:metaclass persistent-class)))\n\n#+nil\n(make-instance 'webhook-company-config\n               :company (find-by-oid \"5fd16bcf4f4b3822fd0000e1\"))\n\n(defmethod ensure-webhook-config ((company company))\n  (bt:with-lock-held (*lock*)\n    (let ((config\n            (webhook-config-for-company company)))\n      (or\n       config\n       (make-instance 'webhook-company-config\n                      :company company\n                      :endpoint \"\"\n                      :enabledp nil\n                      :signing-key (generate-api-key))))))\n\n(defclass webhook-payload ()\n  ((event :initarg :event\n          :json-key \"event\"\n          :json-type :string\n          :reader event))\n  (:metaclass ext-json-serializable-class))\n\n(defun update-config (&key company endpoint signing-key enabled)\n  (bt:with-lock-held (*lock*)\n    (when-let ((prev (webhook-config-for-company company)))\n      (delete-object prev))\n    (make-instance 'webhook-company-config\n                   :company company\n                   :endpoint endpoint\n                   :signing-key signing-key\n                   :enabledp enabled)\n    (hex:safe-redirect \"/settings/webhook\")))\n"
  },
  {
    "path": "src/screenshotbot/webhook/settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webhook/settings\n  (:use #:cl)\n  (:import-from #:screenshotbot/settings-api\n                #:settings-template\n                #:defsettings)\n  (:import-from #:nibble\n                #:nibble)\n  (:import-from #:util/form-errors\n                #:with-error-builder\n                #:with-form-errors)\n  (:import-from #:screenshotbot/model/company\n                #:company-admin-p)\n  (:import-from #:screenshotbot/user-api\n                #:created-at\n                #:current-user\n                #:current-company)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:deftransaction)\n  (:import-from #:screenshotbot/webhook/model\n                #:event-payload\n                #:event\n                #:webhook-event\n                #:update-config\n                #:ensure-webhook-config\n                #:enabledp\n                #:signing-key\n                #:endpoint\n                #:webhook-company-config\n                #:webhook-config-for-company)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/misc\n                #:?.)\n  (:import-from #:core/ui/mdi\n                #:mdi)\n  (:import-from #:parenscript\n                #:ps)\n  (:import-from #:ps\n                #:@)\n  (:import-from #:screenshotbot/dashboard/audit-log\n                #:render-audit-logs\n                #:render-audit-log)\n  (:import-from #:screenshotbot/audit-log\n                #:audit-log-error)\n  (:import-from #:screenshotbot/webhook/webhook\n                #:force-resend-webhook\n                #:payload)\n  (:import-from #:core/ui/simple-card-page\n                #:simple-card-page)\n  (:import-from #:core/ui/post\n                #:post-a)\n  (:import-from #:util/timeago\n                #:timeago))\n(in-package :screenshotbot/webhook/settings)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun post-webhook-settings (endpoint signing-key enabled)\n  (with-error-builder (:check check :errors errors\n                       :form-builder (get-webhook-settings)\n                       :form-args (:endpoint endpoint\n                                   :signing-key signing-key\n                                   :enabled enabled)\n                       :success\n                       (update-config\n                        :company (current-company)\n                        :endpoint endpoint\n                        :signing-key signing-key\n                        :enabled (string-equal \"on\" enabled)))\n    (check :endpoint\n           (ignore-errors (quri:parse-uri endpoint))\n           \"The URL must be valid\")\n    (check :signing-key\n           (>= (length signing-key) 8)\n           \"Insecure signing key, must be at least 8 characters long.\")\n    (check nil (company-admin-p\n                (current-company)\n                (current-user))\n           \"You must be an admin to update this setting\")))\n\n(defun show-signing-key-on-click ()\n  (ps\n    (let* ((input (get-element-by-id \"signing-key\"))\n           (verb (get-element-by-id \"verb\"))\n           (type (@ input type)))\n      (flet ((set (type verb-val)\n               (setf (@ input type) type)\n               (setf (@ verb inner-h-t-m-l) verb-val)\n               ))\n        (cond\n          ((= type \"text\")\n           (set \"password\" \"Show\"))\n          (t\n           (set \"text\" \"Hide\")))))))\n\n(defun get-webhook-settings ()\n  (let ((config (ensure-webhook-config (current-company)))\n        (post (nibble (endpoint signing-key enabled)\n                (post-webhook-settings endpoint signing-key enabled))))\n    <settings-template>\n      <form method= \"post\" action= post >\n        <div class= \"card mt-3\">\n          <div class= \"card-header\">\n            <h3>Webhooks</h3>\n          </div>\n          <div class= \"card-body\">\n            <div class= \"alert alert-danger d-none\" />\n            <div class= \"form-group mb-3\">\n              <label for= \"endpoint\" class= \"form-label\" >Webhook Endpoint</label>\n              <input type= \"text\" class= \"form-control\" placeholder= \"https://example.com/screenshotbot/webhook\" id= \"endpoint\" name= \"endpoint\" value= (?. endpoint config) />\n            </div>\n\n            <div class= \"form-group mb-3\">\n              <label class= \"form-label\" for= \"signing-key\" >Signing Secret Key</label>\n              <div class= \"input-group mb-2\">\n                <input type= \"password\" name= \"signing-key\" id= \"signing-key\" class= \"form-control\"\n\n                       value= (?. signing-key config) />\n                <button type= \"button\" class=\"btn btn-danger\"\n                        onclick= (show-signing-key-on-click) >\n                  <mdi name= \"visibility\" /> <span id= \"verb\">Show</span> Signing Key\n                </button>\n              </div>\n\n              <div class= \"text-muted\">\n                The signing key can be any string. It will be used to compute an SHA256 HMAC which\n                will be sent along with the payload to verify that this was generated by Screenshotbot.\n              </div>\n            </div>\n\n            <div class= \"form-group mb-3\">\n              <input type= \"checkbox\" name= \"enabled\" class= \"form-check-input\" id= \"enabled\"\n                     checked= (if (?. enabledp config) \"checked\" nil) />\n              <label for= \"enabled\" class= \"form-check-label\">Enable webhooks</label>\n            </div>\n          </div>\n\n          <div class= \"card-footer\">\n            <input type= \"submit\" class= \"btn btn-primary\" value= \"Save\" />\n          </div>\n        </div>\n      </form>\n\n      ,(render-audit-logs :type 'webhook-event\n                          :company (current-company)\n                          :title \"Webhook Event History\")\n    </settings-template>))\n\n(defmethod render-audit-log ((self webhook-event))\n  (let ((failedp (audit-log-error self))\n        (details (nibble ()\n                   (setf (hunchentoot:content-type*) \"application/json\")\n                   (event-payload self)))\n        (retry (nibble (:once t)\n                 (force-resend-webhook self)\n                 (hex:safe-redirect \"/settings/webhook\"))))\n    <span class= (if failedp \"text-danger\" \"text-success\") >\n      <mdi name= (if failedp \"close\" \"done\") />\n      ,(event self) at ,(timeago :timestamp (created-at self))\n      <a href=details target= \"_blank\" >\n        <mdi name= \"open_in_new\" /> Show details\n      </a>\n\n      <post-a href= retry title= \"Resend Webhook\" >\n        <mdi name= \"refresh\" /> Retry\n      </post-a>\n\n    </span>) )\n\n(defsettings webhook\n  :name \"webhook\"\n  :title \"Webhooks\"\n  :section :developers\n  :handler 'get-webhook-settings)\n"
  },
  {
    "path": "src/screenshotbot/webhook/test-model.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webhook/test-model\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/webhook/model\n                #:signing-key\n                #:ensure-webhook-config)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:import-from #:fiveam-matchers/misc\n                #:is-not-null)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:is-not-empty))\n(in-package :screenshotbot/webhook/test-model)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((company (make-instance 'company)))\n      (&body))))\n\n(test ensure-webhook-config\n  (with-fixture state ()\n    (let ((config (ensure-webhook-config company)))\n      (assert-that config\n                   (is-not-null))\n      (is (eql (ensure-webhook-config company)\n               config))\n      (assert-that (signing-key config)\n                   (is-not-empty)))))\n"
  },
  {
    "path": "src/screenshotbot/webhook/test-settings.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webhook/test-settings\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user\n                #:with-installation\n                #:screenshot-test)\n  (:import-from #:screenshotbot/webhook/settings\n                #:get-webhook-settings)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:auth\n                #:with-sessions)\n  (:import-from #:util/store/store\n                #:with-test-store))\n(in-package :screenshotbot/webhook/test-settings)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n   (with-installation ()\n     (with-test-user (:logged-in-p t)\n       (with-sessions ()\n         (&body))))))\n\n(screenshot-test webhook-settings-page\n  (with-fixture state ()\n    (get-webhook-settings)))\n"
  },
  {
    "path": "src/screenshotbot/webhook/test-webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webhook/test-webhook\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:screenshotbot/webhook/webhook\n                #:actually-send-webhook\n                #:sign-payload)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:screenshotbot/testing\n                #:with-test-user)\n  (:import-from #:screenshotbot/webhook/model\n                #:endpoint\n                #:ensure-webhook-config)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:import-from #:cl-mock\n                #:if-called))\n(in-package :screenshotbot/webhook/test-webhook)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-test-store ()\n    (with-test-user (:company company)\n      (let ((config (ensure-webhook-config company)))\n        (with-transaction ()\n         (setf (endpoint config)  \"fakehttp://example.com\"))\n        (&body)))))\n\n(test sign-payload\n  (is (equal \"t=1686193117,signature=5d6ec12164ab98b79d4c53e5ede481c2cd283e2035fca7cc6206a4b275e06056\"\n             (sign-payload \"blehbleh\"\n                           :time\n                           (local-time:universal-to-timestamp\n                            3895181917)\n                           :key \"zoidberg\"))))\n\n(test actually-send-webhook-happy-path\n  (with-fixture state ()\n    (cl-mock:with-mocks ()\n      (if-called 'http-request\n                 (lambda (url &rest args)))\n      (actually-send-webhook config \"foo.bar\" \"{}\"))))\n"
  },
  {
    "path": "src/screenshotbot/webhook/webhook.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :screenshotbot/webhook/webhook\n  (:use #:cl)\n  (:import-from #:screenshotbot/api/model\n                #:encode-json)\n  (:import-from #:screenshotbot/webhook/model\n                #:event-payload\n                #:config-company\n                #:webhook-payload\n                #:event\n                #:webhook-event\n                #:enabledp\n                #:signing-key\n                #:endpoint\n                #:webhook-config-for-company)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:alexandria\n                #:assoc-value\n                #:when-let*)\n  (:import-from #:util/threading\n                #:make-thread)\n  (:import-from #:screenshotbot/audit-log\n                #:audit-log-company\n                #:with-audit-log)\n  (:import-from #:screenshotbot/model/company\n                #:company)\n  (:export\n   #:send-webhook))\n(in-package :screenshotbot/webhook/webhook)\n\n(defmethod send-webhook (company (payload webhook-payload))\n  (when-let* ((event-name (event payload))\n              (payload (encode-json payload))\n              (config (webhook-config-for-company company)))\n    (when (enabledp config)\n     (make-thread\n      (lambda ()\n        (actually-send-webhook config event-name payload))))))\n\n(defmethod force-resend-webhook ((ev webhook-event))\n  (when-let* ((company (audit-log-company ev))\n              (config (webhook-config-for-company company)))\n    (actually-send-webhook config (assoc-value\n                                   (json:decode-json-from-string (event-payload ev))\n                                   :event)\n                           (event-payload ev))))\n\n(defmethod actually-send-webhook (config event-name (payload string))\n  (log:info \"Sending payload\")\n  (ignore-errors\n   (with-audit-log (audit-log (make-instance 'webhook-event\n                                             :company (config-company config)\n                                             :event event-name\n                                             :payload payload))\n     (declare (ignore audit-log))\n     (let ((signature (sign-payload payload :key (signing-key config))))\n       (http-request\n        (endpoint config)\n        :content payload\n        :additional-headers `((\"Screenshotbot-Signature\"\n                               . ,signature))\n        :ensure-success t\n        :method :post)))))\n\n(defun sign-payload (payload &key (time (local-time:now))\n                               key)\n  (let* ((unix (local-time:timestamp-to-unix time))\n         (payload (format nil \"~a.~a\"\n                          unix\n                          payload))\n         (mac (ironclad:make-mac :hmac\n                                 (flex:string-to-octets key)\n                                 :sha256)))\n    (ironclad:update-mac mac (flex:string-to-octets payload))\n    (format nil\n            \"t=~a,signature=~a\"\n            unix\n            (ironclad:byte-array-to-hex-string (ironclad:produce-mac mac)))))\n"
  },
  {
    "path": "src/screenshotbot.oss.tests.asd",
    "content": "(defsystem :screenshotbot.oss.tests\n  :serial t\n  :depends-on (:hunchentoot-extensions/tests\n               :fiveam))\n"
  },
  {
    "path": "src/server/.gitignore",
    "content": "launch"
  },
  {
    "path": "src/server/Makefile",
    "content": "\nlaunch: launch.c\n\tgcc launch.c -l systemd -o launch\n"
  },
  {
    "path": "src/server/acceptor-override.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n\n(defpackage :server/acceptor-override\n  (:use #:cl\n        #:hunchentoot)\n  (:import-from #:hunchentoot\n                #:acceptor-process\n                #:start-listening\n                #:acceptor-taskmaster\n                #:acceptor-listen-backlog\n                #:handle-incoming-connection\n                #:acceptor-shutdown-p\n                #:acceptor-address\n                #:acceptor-port)\n  (:export\n   #:ipv6-acceptor))\n(in-package :server/acceptor-override)\n\n(defclass ipv6-acceptor (acceptor)\n  ()\n  (:default-initargs\n   :address \"::\"))\n\n(defmethod start-listening ((acceptor ipv6-acceptor))\n  (multiple-value-bind (listener-process startup-condition)\n      (comm:start-up-server :service (acceptor-port acceptor)\n                            :address (acceptor-address acceptor)\n                            :process-name (format nil \"Hunchentoot listener \\(~A:~A)\"\n                                                  (or (acceptor-address acceptor) \"*\")\n                                                  (acceptor-port acceptor))\n                            #-(or :lispworks4 :lispworks5 :lispworks6)\n                            :backlog\n                            #-(or :lispworks4 :lispworks5 :lispworks6)\n                            (acceptor-listen-backlog acceptor)\n                            ;; this function is called once on startup - we\n                            ;; use it to check for errors and random port\n                            :announce (lambda (socket &optional condition)\n                                        (when condition\n                                          (error condition))\n                                        (when (or (null (acceptor-port acceptor))\n                                                  (zerop (acceptor-port acceptor)))\n                                          (multiple-value-bind (address port)\n                                              (comm:get-socket-address socket)\n                                            (declare (ignore address))\n                                            (setf (slot-value acceptor 'port) port))))\n                            ;; this function is called whenever a connection\n                            ;; is made\n                            :function (lambda (handle)\n                                        (unless (acceptor-shutdown-p acceptor)\n                                          (handle-incoming-connection\n                                           (acceptor-taskmaster acceptor) handle)))\n                            ;; wait until the acceptor was successfully started\n                            ;; or an error condition is returned\n                            :ipv6 :both\n                            :wait t)\n    (when startup-condition\n      (error startup-condition))\n    (mp:process-stop listener-process)\n    (setf (acceptor-process acceptor) listener-process)\n    (values)))\n\n"
  },
  {
    "path": "src/server/cli.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/cli\n  (:use #:cl)\n  (:import-from #:clingon\n                #:make-option)\n  (:import-from #:server\n                #:init-multi-acceptor\n                #:*multi-acceptor*\n                #:*slynk-loopback-interface*\n                #:%run\n                #:%verify\n                #:*slynk-port*\n                #:*start-slynk*\n                #:with-common-setup)\n  (:import-from #:util/health-check\n                #:run-health-checks)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:util/store\n                #:*object-store*)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:server/config\n                #:*config-file*)\n  (:import-from #:util/threading\n                #:make-thread)\n  (:import-from #:server/cluster/status\n                #:cluster-status/command)\n  (:import-from #:server/cluster/leadership\n                #:cluster-leadership/command)\n  (:import-from #:server/cluster/peers\n                #:add-peer/command\n                #:remove-peer/command)\n  #+lispworks\n  (:import-from #:hunchentoot-extensions/existing-socket\n                #:existing-socket)\n  (:import-from #:server/cluster/cluster-init\n                #:cluster-init/command)\n  #+lispworks\n  (:import-from #:server/config-cli\n                #:config/command)\n  (:export\n   #:main))\n(in-package :server/cli)\n\n(def-easy-macro with-store (cmd &fn fn)\n  (with-global-binding ((*object-store* (serapeum:ensure-suffix (namestring (clingon:getopt cmd :store)) \"/\"))\n                        (*start-slynk* (clingon:getopt cmd :start-slynk))\n                        (*slynk-loopback-interface* (clingon:getopt cmd :slynk-loopback-interface))\n                        (*slynk-port* (format nil \"~a\" (clingon:getopt cmd :slynk-port))))\n    (fn)))\n\n(defun self-test/command (&key enable-store jvm)\n  (clingon:make-command\n   :name \"self-test\"\n   :handler (lambda (cmd)\n              (with-store (cmd)\n               (with-common-setup (:enable-store enable-store :jvm jvm)\n                 (run-health-checks))))\n   :options (list* (common-options))))\n\n(def-easy-macro with-screenshotbot-config (cmd\n                                           &key (config-loader\n                                                 (error \"must provide :config-loader\"))\n                                           &fn fn)\n  (cond\n    (config-loader\n     (unless (clingon:getopt cmd :config)\n       (error \"Must provide a --config file\"))\n     (with-global-binding ((*config-file* (clingon:getopt cmd :config)))\n       (funcall config-loader)\n       (fn)))\n    (t\n     (when (clingon:getopt cmd :config)\n       (error \"--config is not supported here\"))\n     (fn))))\n\n(defun car-bar ()\n  ())\n\n(defun foo-bar (&key (def (car-bar))))\n\n(def-easy-macro with-run-or-verify-setup (cmd &key enable-store jvm\n                                              (config-loader (error \"missing :config-loader\"))\n                                              &fn fn)\n  (process-common-options cmd)\n  (with-screenshotbot-config (cmd :config-loader config-loader)\n    (with-store (cmd)\n      (when-let ((secrets (clingon:getopt cmd :secrets)))\n        (log:info \"Loading secrets from ~a\" secrets)\n        (setf util/phabricator/passphrase:*secret-file* secrets)\n        (util/phabricator/passphrase:reload-secrets))\n      (with-common-setup (:enable-store enable-store :jvm jvm)\n        (fn)))))\n\n(defun process-common-options (cmd)\n  (when (clingon:getopt cmd :verbose)\n    (log:config :debug)))\n\n(defun verify/command (&key enable-store jvm (config-loader (error \"must provider :config-loader\")))\n  (clingon:make-command\n   :name \"verify\"\n   :handler (lambda (cmd)\n              (with-run-or-verify-setup (cmd :enable-store enable-store :jvm jvm\n                                             :config-loader config-loader)\n                (%verify :profile-store (clingon:getopt cmd :profile))))\n   :options (list*\n             (make-option\n              :flag\n              :description \"Whether to run the profiler when verifying the store\"\n              :initial-value nil\n              :long-name \"profile\"\n              :key :profile)\n             (common-options))))\n\n(defvar *signal-lock* (bt:make-lock))\n\n#+lispworks\n(defun sigusr1-handler (&rest x)\n  (declare (ignore x))\n  (make-thread\n   (lambda ()\n     (let ((stream (sys:make-stderr-stream)))\n       (bt:with-lock-held (*signal-lock*)\n         (log:info \"SIGUSR1: Started\")\n         (format stream \"SIGUSR1: Started~%\")\n         (finish-output stream)\n         (asdf:load-system :screenshotbot.pro)\n         (util:validate-indices)\n         (format stream \"SIGUSR1: Success ~a.~%\"\n                 (uiop:read-file-string \"release_timestamp\"))\n         (finish-output stream))))\n   :name \"SIGUSR1-thread\"))\n\n(defun apply-socket (acceptor socket)\n  #+lispworks\n  (when socket\n    (setf (existing-socket acceptor) socket))\n  acceptor)\n\n(defun run/command (&key enable-store jvm acceptor\n                      (config-loader (error \"must provide :config-loader)\")))\n  (clingon:make-command\n   :name \"run\"\n   :handler (lambda (cmd)\n              (with-run-or-verify-setup (cmd :enable-store enable-store :jvm jvm\n                                             :config-loader config-loader)\n                #+lispworks\n                (sys:set-signal-handler 10\n                                        'sigusr1-handler)\n                (%run :enable-store enable-store\n                      :early-hunchentoot (clingon:getopt cmd :early-hunchentoot)\n                      :acceptor\n                      (apply-socket\n                       (cond\n                         (acceptor\n                          acceptor)\n                         ((clingon:getopt cmd :only-screenshotbot-p)\n                          (symbol-value\n                           (uiop:find-symbol* :*acceptor* :screenshotbot/server)))\n                         (t\n                          (init-multi-acceptor :port (clingon:getopt cmd :port))\n                          *multi-acceptor*))\n                       (clingon:getopt cmd :socket))\n                      :port (clingon:getopt cmd :port)\n                      :shell nil)))\n   :options (list*\n             (make-option\n              :integer\n              :description \"HTTP access port\"\n              :long-name \"port\"\n              :initial-value 4001\n              :key :port)\n             (make-option\n              :integer\n              :description \"HTTP listening socket, usually set up my systemd\"\n              :long-name \"socket\"\n              :key :socket)\n             (make-option\n              :flag\n              :description \"Only load the screenshotbot acceptor, instead of the multiacceptor\"\n              :initial-value nil\n              :long-name \"only-screenshotbot\"\n              :key :only-screenshotbot-p)\n             (make-option\n              :flag\n              :description \"By default, we start Hunchentoot after the store is loaded. If you\nhave forwarding code enabled (which we do for Screenshotbot with\nBraft), then pass this argument to start Hunchentoot first. This way\nthe server will respond\"\n              :initial-value nil\n              :long-name \"start-hunchentoot-before-store\"\n              :key :early-hunchentoot)             \n             (make-option\n              :string\n              :description \"The environment name used for raft, this is not required if we're using raft-config.lisp\"\n              :initial-value nil\n              :long-name \"raft-environment\"\n              :key :raft-environment)\n             (common-options))))\n\n(defun save-passphrases/command ()\n  (clingon:make-command\n   :name \"save-passphrases\"\n   :handler (lambda (cmd)\n              (let ((output (clingon:getopt cmd :output)))\n                (uiop:call-function\n                 \"util/phabricator/passphrase::save-passphrases\"\n                 output)))\n   :options (list\n             (make-option\n              :filepath\n              :description \"Where to save the passphrases\"\n              :long-name \"output\"\n              :key :output))))\n\n(defun main/handler (cmd)\n  (clingon:print-usage-and-exit cmd t))\n\n(defun common-options ()\n  (list\n   (make-option\n    :filepath\n    :description \"The object store location\"\n    :short-name #\\s\n    :long-name \"store\"\n    :key :store)\n   (make-option\n    :filepath\n    :description \"Path to secrets file. Ignore on OSS.\"\n    :long-name \"secrets\"\n    :key :secrets)\n   (make-option\n    :flag\n    :description \"Whether to start slynk\"\n    :long-name \"start-slynk\"\n    :initial-value :true\n    :key :start-slynk)\n   (make-option\n    :string\n    :description \"The IP address to listen on for slynk\"\n    :long-name \"slynk-loopback-interface\"\n    :initial-value \"127.0.0.1\"\n    :key :slynk-loopback-interface)\n   (make-option\n    :filepath\n    :description \"Config file to use (not for OSS)\"\n    :long-name \"config\"\n    :key :config)\n   (make-option\n    :flag\n    :description \"whether to enable verbose logs\"\n    :long-name \"verbose\"\n    :key :verbose)\n   (make-option\n    :integer\n    :description \"the port to start slynk on\"\n    :long-name \"slynk-port\"\n    :initial-value 4005\n    :key :slynk-port)))\n\n(defun benchmarks/command ()\n  (clingon:make-command :name \"benchmarks\"\n                        :handler (lambda (cmd)\n                                   (uiop:with-temporary-file (:stream s :pathname p :direction :output :keep t)\n                                     (delete-file p)\n                                     (log:config :warn)\n                                     (setf *standard-output* s)\n                                     (setf *trace-output* s)\n                                     (setf *error-output* s)\n                                     (setf *terminal-io* s)\n                                     (benchmark:run-all)\n                                     (uiop:quit 0)))))\n\n(defun cluster/command ()\n  (clingon:make-command :name \"cluster\"\n                        :description \"Cluster management commands\"\n                        :handler (lambda (cmd)\n                                   (clingon:print-usage-and-exit cmd t))\n                        :sub-commands (list\n                                       (cluster-status/command)\n                                       (cluster-leadership/command)\n                                       (add-peer/command)\n                                       (cluster-init/command)\n                                       (remove-peer/command))))\n\n\n(defun main/command (&key enable-store jvm acceptor\n                       (config-loader (error \"must provide :config-loader\")))\n  (clingon:make-command :name \"App Server\"\n                        :handler #'main/handler\n                        :sub-commands\n                        (remove-if #'null\n                         (list\n                          (self-test/command :enable-store enable-store\n                                             :jvm jvm)\n                          (verify/command :enable-store enable-store\n                                          :config-loader config-loader\n                                          :jvm jvm)\n                          #-screenshotbot-oss\n                          (when (find-package :screenshotbot/pro/installation)\n                            (uiop:call-function\n                             \"screenshotbot/pro/installation:gen-config/command\"))\n                          #-screenshotbot-oss\n                          (when (find-package :screenshotbot/pro/company-cli)\n                            (uiop:call-function\n                             \"screenshotbot/pro/company-cli:company/command\"))\n                          #-screenshotbot-oss\n                          (when (find-package :screenshotbot/pro/sso-cli)\n                            (uiop:call-function\n                             \"screenshotbot/pro/sso-cli:sso/command\"))\n                          (save-passphrases/command)\n                          (benchmarks/command)\n                          (run/command :enable-store enable-store\n                                       :config-loader config-loader\n                                       :jvm jvm\n                                       :acceptor acceptor)\n                          #+lispworks\n                          (server/eval:eval/command)\n                          #+lispworks\n                          (config/command)\n                          (cluster/command)))))\n\n(defun legacy-mode-p (args)\n  (and (second args)\n       (eql #\\- (elt (second args) 0))))\n\n(defun main (&key (jvm t) acceptor (enable-store t)\n               (config-loader #'server/config:load-config))\n  (handler-bind ((error (lambda (e)\n                          (format t \"Got error during init process: ~a~%\" e)\n                          #+lispworks\n                          (dbg:output-backtrace :bug-form t)\n                          (uiop:quit 1))))\n   (cond\n     ((legacy-mode-p sys:*line-arguments-list*)\n      (warn \"Using legacy mode for command line parsing\")\n      (server:main :jvm jvm :acceptor acceptor :enable-store enable-store))\n     (t\n      (let ((args #-lispworks (cdr (uiop:raw-command-line-arguments))\n                  #+lispworks (cdr sys:*line-arguments-list*)))\n        (let ((app (main/command :jvm jvm :enable-store enable-store\n                                 :config-loader config-loader\n                                 :acceptor acceptor)))\n          (clingon:run app args)\n          (uiop:quit 0)))))))\n"
  },
  {
    "path": "src/server/cluster/cluster-init.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/cluster/cluster-init\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:ec2-get-local-ipv4\n                #:ec2-store\n                #:make-default-store))\n(in-package :server/cluster/cluster-init)\n\n(defun cluster-init/command ()\n  (clingon:make-command\n   :name \"init\"\n   :description \"Creates a raft-config.lisp in the given store directory, with only the current machine.\"\n   :handler 'cluster-init/handler\n   :options (list\n             (clingon:make-option\n              :string\n              :key :store\n              :description \"The store directory to create the raft-config.lisp in\"\n              :long-name \"store\")\n             (clingon:make-option\n              :string\n              :key :other-peers\n              :long-name \"other-peers\"\n              :description \"The other peers (excluding this one). Command separated IP addresses without port numbers.\"))))\n\n(defun cluster-init/handler (cmd)\n (let ((store (str:ensure-suffix \"/\" (clingon:getopt cmd :store))))\n   (init-raft-config store\n                     (remove-if #'str:emptyp\n                                (str:split \",\" (or (clingon:getopt cmd :other-peers)\n                                                   \"\"))))))\n\n\n(defun init-raft-config (store other-peers)\n  (let ((raft-config (path:catfile store \"raft-config.lisp\")))\n    (uiop:with-staging-pathname (raft-config raft-config)\n      (with-open-file (stream raft-config :direction :output :if-exists :append)\n        (let ((*package* (find-package :cl-user)))\n          (format stream \"~s\"\n                  `(make-default-store\n                    'ec2-store\n                    :group \"screenshotbot\"\n                    :data-path ,(namestring \"~/raft-data/\")\n                    :port 7070\n                    :ips (list\n                          ,(ec2-get-local-ipv4)\n                          ,@(loop for peer in other-peers\n                                  collect (format nil \"~a\" peer))))))))\n    (log:info \"Updated ~a\" raft-config)))\n\n"
  },
  {
    "path": "src/server/cluster/leadership.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/cluster/leadership\n  (:use #:cl)\n  (:import-from #:clingon\n                #:getopt\n                #:make-option)\n  (:import-from #:server/cluster/status\n                #:get-pid\n                #:eval-on-pid)\n  (:export #:cluster-leadership/command))\n(in-package :server/cluster/leadership)\n\n(defun cluster-leadership/command ()\n  (clingon:make-command :name \"leadership\"\n                        :description \"Cluster leadership management\"\n                        :handler (lambda (cmd)\n                                   (clingon:print-usage-and-exit cmd t))\n                        :sub-commands (list\n                                       (leadership-acquire/command)\n                                       (leadership-release/command))))\n\n(defun leadership-acquire/command ()\n  (clingon:make-command :name \"acquire\"\n                        :description \"Acquire cluster leadership for this node\"\n                        :handler #'leadership-acquire/handler\n                        :options nil))\n\n(defun leadership-acquire/handler (cmd)\n  (declare (ignore cmd))\n  (let ((pid (get-pid)))\n    (cond\n      (pid\n       #+lispworks\n       (handler-case\n           (progn\n             (format t \"Requesting leadership for this node (PID ~a)...~%\" pid)\n             (eval-on-pid pid\n                          '(let ((store bknr.datastore:*store*))\n                            (bknr.cluster/server::bknr-vote store 1000)\n                            (format t \"Vote initiated.~%\"))))\n         (error (e)\n           (format t \"Error: ~a~%\" e)))\n       #-lispworks\n       (format t \"Leadership commands only supported on LispWorks~%\"))\n      (t\n       (format t \"No running server found.~%\"))))\n  (values))\n\n(defun leadership-release/command ()\n  (clingon:make-command :name \"release\"\n                        :description \"Release cluster leadership (transfer to another node)\"\n                        :handler #'leadership-release/handler\n                        :options nil))\n\n(defun leadership-release/handler (cmd)\n  (declare (ignore cmd))\n  (let ((pid (get-pid)))\n    (cond\n      (pid\n       #+lispworks\n       (handler-case\n           (progn\n             (format t \"Releasing leadership for PID ~a...~%\" pid)\n             (eval-on-pid pid\n                          '(let ((store bknr.datastore:*store*))\n                            (bknr.cluster/server::bknr-transfer-leader store)\n                            (format t \"Leadership released.~%\"))))\n         (error (e)\n           (format t \"Error: ~a~%\" e)))\n       #-lispworks\n       (format t \"Leadership commands only supported on LispWorks~%\"))\n      (t\n       (format t \"No running server found.~%\"))))\n  (values))\n"
  },
  {
    "path": "src/server/cluster/peers.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/cluster/peers\n  (:use #:cl)\n  (:import-from #:clingon\n                #:getopt\n                #:make-option\n                #:command-arguments)\n  (:import-from #:server/cluster/status\n                #:get-pid\n                #:eval-on-pid)\n  (:import-from #:str\n                #:rsplit\n                #:starts-with-p\n                #:ends-with-p)\n  (:export #:add-peer/command\n           #:remove-peer/command\n           #:peer-to-ip))\n(in-package :server/cluster/peers)\n\n(defun ip-to-peer (ip)\n  \"Convert an IP address to a braft peer string by appending the default port and index.\nIf the IP is IPv6 (contains a colon), it is wrapped in square brackets.\nThe suffix :7070:0:0 is used to match the observed cluster configuration.\"\n  (if (find #\\: ip)\n      (format nil \"[~a]:7070:0:0\" ip)\n      (format nil \"~a:7070:0:0\" ip)))\n\n(defun add-peer/command ()\n  (clingon:make-command\n   :name \"add-peer\"\n   :description \"Add a peer to the cluster\"\n   :usage \"PEER-IP\"\n   :handler #'add-peer/handler\n   :options nil))\n\n(defun add-peer/handler (cmd)\n  (let ((args (command-arguments cmd)))\n    (unless (= 1 (length args))\n      (format t \"Usage: screenshotbot cluster add-peer <peer-ip>~%\")\n      (uiop:quit 1))\n    (let ((peer (ip-to-peer (first args)))\n          (pid (get-pid)))\n      (cond\n        (pid\n         #+lispworks\n         (handler-case\n             (eval-on-pid\n              pid\n              `(let* ((store bknr.datastore:*store*))\n                 (let ((peers (bknr.cluster/server:list-peers store)))\n                   (if (member ,peer peers :test #'string=)\n                       (format t \"Peer ~a is already in the cluster.~%\" ,peer)\n                       (progn\n                         (bknr.cluster/server:update-conf store (cons ,peer peers))\n                         (format t \"Added peer ~a.~%\" ,peer))))))\n           (error (e)\n             (format t \"Error: ~a~%\" e)))\n         #-lispworks\n         (format t \"Cluster commands only supported on LispWorks~%\"))\n        (t (format t \"No running server found.~%\"))))))\n\n(defun remove-peer/command ()\n  (clingon:make-command\n   :name \"remove-peer\"\n   :description \"Remove a peer from the cluster\"\n   :usage \"PEER-IP\"\n   :handler #'remove-peer/handler\n   :options nil))\n\n(defun remove-peer/handler (cmd)\n  (let ((args (command-arguments cmd)))\n    (unless (= 1 (length args))\n      (format t \"Usage: screenshotbot cluster remove-peer <peer-ip>~%\")\n      (uiop:quit 1))\n    (let ((peer (ip-to-peer (first args)))\n          (pid (get-pid)))\n      (cond\n        (pid\n         #+lispworks\n         (handler-case\n             (eval-on-pid\n              pid\n              `(let* ((store bknr.datastore:*store*))\n                 (let ((peers (bknr.cluster/server:list-peers store)))\n                   (if (not (member ,peer peers :test #'string=))\n                       (format t \"Peer ~a is not in the cluster.~%\" ,peer)\n                       (progn\n                         (bknr.cluster/server:update-conf store (remove ,peer peers :test #'string=))\n                         (format t \"Removed peer ~a.~%\" ,peer))))))\n           (error (e)\n             (format t \"Error: ~a~%\" e)))\n         #-lispworks\n         (format t \"Cluster commands only supported on LispWorks~%\"))\n        (t (format t \"No running server found.~%\"))))))\n"
  },
  {
    "path": "src/server/cluster/status.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/cluster/status\n  (:use #:cl)\n  (:import-from #:clingon\n                #:getopt\n                #:make-option)\n  #+lispworks\n  (:import-from #:unix-sockets\n                #:connect-unix-socket\n                #:unix-socket-stream)\n  (:export #:cluster-status/command\n           #:eval-on-pid\n           #:get-pid))\n(in-package :server/cluster/status)\n\n(defun peer-to-ip (peer)\n  \"Convert a braft peer string back to an IP address.\nStrips the :port:index:index suffix and any surrounding square brackets for IPv6.\"\n  (let ((ip (first (str:rsplit \":\" peer :limit 4))))\n    (if (and (str:starts-with-p \"[\" ip)\n             (str:ends-with-p \"]\" ip))\n        (subseq ip 1 (1- (length ip)))\n        ip)))\n\n\n(defun service-name ()\n  (let ((user (uiop:getenv \"USER\")))\n    (cond\n      ((equal \"arnold\" user)\n       \"screenshotbot\")\n      (t\n       (format nil \"screenshotbot-~a\" user)))))\n\n(defun get-pid ()\n  \"Get the PID of the running screenshotbot service from systemd.\"\n  (let* ((output (uiop:run-program\n                  (list \"systemctl\" \"show\" \"--property\" \"MainPID\" \"--value\" (service-name))\n                  :output :string))\n         (pid-string (string-trim '(#\\Space #\\Newline #\\Return) output)))\n    (let ((pid (parse-integer pid-string :junk-allowed t)))\n      (when (and pid (> pid 0))\n        pid))))\n\n(defun sockets-dir ()\n  \"Return the directory where control sockets are stored.\"\n  (namestring (pathname \"~/sockets/\")))\n\n(defun get-socket-file (pid)\n  \"Return the socket file path for a given PID.\"\n  (path:catfile (sockets-dir) (format nil \"~a.sock\" pid)))\n\n#+lispworks\n(defun eval-on-pid (pid expr)\n  \"Evaluate EXPR on the server running with the given PID via its control socket.\nReturns the result of the evaluation.\"\n  (let* ((socket-file (get-socket-file pid))\n         (socket (connect-unix-socket (namestring socket-file))))\n    (unwind-protect\n         (let ((conn (dbg:create-ide-remote-debugging-connection\n                      \"ClusterStatus\"\n                      :stream (unix-socket-stream socket))))\n           (unwind-protect\n                (multiple-value-bind (result status)\n                    (dbg:ide-eval-form-in-remote\n                     expr\n                     :output-stream *standard-output*\n                     :timeout 30\n                     :connection conn)\n                  (format t \"Got result: ~a, ~a~%\" result status)\n                  (values result status))\n             (dbg:close-remote-debugging-connection conn)))\n      (close (unix-socket-stream socket)))))\n\n#-lispworks\n(defun eval-on-pid (pid expr)\n  (declare (ignore pid expr))\n  (error \"eval-on-pid is only supported on LispWorks\"))\n\n(defun cluster-status/command ()\n  (clingon:make-command :name \"status\"\n                        :description \"Show cluster status\"\n                        :handler #'cluster-status/handler\n                        :options nil))\n\n(defun cluster-status/handler (cmd)\n  (declare (ignore cmd))\n  (let ((pid (get-pid)))\n    (cond\n      (pid\n       (show-status-for-pid pid))\n      (t\n       (format t \"No running server found (service: ~a)~%\" (service-name)))))\n  (values))\n\n(defun show-status-for-pid (pid)\n  \"Show the status of the server with the given PID.\"\n  #+lispworks\n  (handler-case\n      (progn\n        (format t \"Server PID: ~a~%\" pid)\n        (eval-on-pid pid\n                     '(let ((store bknr.datastore:*store*))\n                       (format t \"Leader: ~a~%\"\n                        (peer-to-ip (bknr.cluster/server:leader-id store)))\n                       (format t \"Is current node Leader?: ~a~%\"\n                        (bknr.cluster/server:leaderp store))\n                       (format t \"Peers:~%\")\n                       (loop for peer in (bknr.cluster/server:list-peers store)\n                             do (format t \"  ~a~%\" (peer-to-ip peer))))))\n    (error (e)\n      (format t \"Error connecting to PID ~a: ~a~%\" pid e)))\n  #-lispworks\n  (format t \"PID: ~a (status checking only supported on LispWorks)~%\" pid))\n"
  },
  {
    "path": "src/server/config-cli.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/config-cli\n  (:use #:cl)\n  (:import-from #:clingon\n                #:make-command)\n  (:import-from #:server/cluster/status\n                #:get-pid\n                #:eval-on-pid)\n  (:import-from #:core/config/model\n                #:config)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:clingon.command\n                #:getopt))\n(in-package :server/config-cli)\n\n(defun config/command ()\n  (make-command :name \"config\"\n                        :description \"Configure your Screenshotbot installation\"\n                        :handler (lambda (cmd)\n                                   (clingon:print-usage-and-exit cmd t))\n                        :sub-commands (list\n                                       (get/command)\n                                       (set/command)\n                                       (setup-sso/command))))\n\n\n(defun get/command ()\n  (make-command :name \"get\"\n                :description \"Get a config value\"\n                :handler #'get/handler\n                :options (list\n                          (make-option\n                           :string\n                           :description \"The key to retrieve\"\n                           :long-name \"key\"\n                           :key :key))))\n\n(defun get/handler (cmd)\n  (let ((key (getopt cmd :key)))\n    (when (str:emptyp key)\n      (error \"Must provide --key\"))\n    (eval-on-pid (get-pid)\n                 `(format t \"~a~%\" (config ,key)))))\n\n(defun set/command ()\n  (make-command :name \"set\"\n                :description \"Set a config value\"\n                :handler #'set/handler\n                :options (list\n                          (make-option\n                           :string\n                           :description \"The key to set\"\n                           :long-name \"key\"\n                           :key :key)\n                          (make-option\n                           :string\n                           :description \"The value to set\"\n                           :long-name \"value\"\n                           :key :value))))\n\n(defun set/handler (cmd)\n  (let ((key (getopt cmd :key))\n        (value (getopt cmd :value)))\n    (when (str:emptyp key)\n      (error \"Must provide --key\"))\n    (unless value\n      (error \"Must provide --value\"))\n\n    (eval-on-pid (get-pid)\n                 `(setf (config ,key) ,value))))\n\n(defun setup-sso/command ()\n  (make-command :name \"setup-sso\"\n                :description \"A helper tool to setup SSO via OpenID Connect\"\n                :handler #'setup-sso/handler))\n\n(defun setup-sso/handler (cmd)\n  (declare (ignore cmd))\n  (let ((args))\n   (labels ((read-config (config input)\n              (format t \"~a [~a]: \" input config)\n              (finish-output t)\n              (let ((arg (read-line)))\n                (cond\n                  ((str:blankp arg)\n                   (read-config config input))\n                  (t\n                   (push (list config (str:trim arg)) args))))))\n     (read-config \"sso.oidc.issuer\" \"Issuer URL\")\n     (read-config \"sso.oidc.client-id\" \"Client ID\")\n     (read-config \"sso.oidc.client-secret\" \"Client Secret\")\n     (loop for (key value) in args\n           do\n              (log:info \"Setting ~a\" key)\n              (eval-on-pid\n               (get-pid)\n               `(setf (config ,key) ,value)))\n     (log:info \"SSO has been configured, try testing it\"))))\n"
  },
  {
    "path": "src/server/config.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/config\n  (:use #:cl)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:export\n   #:load-config))\n(in-package :server/config)\n\n(defvar *config-file* nil)\n\n(defgeneric after-config-loaded (installation)\n  (:method (self)\n    nil)\n  (:documentation \"A hook that's called after the config is loaded and an installation\nis generated.\"))\n\n(defmethod load-config ()\n  (when *config-file*\n    (setf *installation*\n          (with-open-file (stream *config-file*)\n            (let ((*package* (find-package :cl-user)))\n              (eval (read stream)))))\n    (after-config-loaded *installation*)\n    *installation*))\n"
  },
  {
    "path": "src/server/control-socket.lisp",
    "content": "(defpackage :server/control-socket\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/store\n                #:object-store)\n  (:import-from #:control-socket/server\n                #:control-socket-stop\n                #:make-control-socket))\n(in-package :server/control-socket)\n\n(def-easy-macro with-control-socket  (&key store-dir &fn fn)\n  (let* ((socket-file (ensure-directories-exist\n                       (path:catfile (user-homedir-pathname)\n                                     \"sockets/\"\n                                     (format nil \"~a.sock\" (util/posix:getpid))))))\n\n    (when (probe-file socket-file)\n      (delete-file socket-file))\n\n    (log:info \"Start control-socket at ~a\" socket-file)\n    (let ((cs (make-control-socket socket-file)))\n      (unwind-protect\n           (funcall fn)\n        (log:info \"Shut down control socket\")\n        (control-socket-stop cs)))))\n"
  },
  {
    "path": "src/server/eval.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/eval\n  (:use #:cl)\n  (:import-from #:clingon\n                #:getopt\n                #:make-option)\n  (:import-from #:unix-sockets\n                #:with-unix-socket)\n  (:import-from #:util/misc\n                #:safe-with-open-file)\n  (:export\n   #:eval/command))\n(in-package :server/eval)\n\n(defun eval/command ()\n  (clingon:make-command\n   :name \"eval\"\n   :handler (lambda (cmd)\n              (let ((socket (getopt cmd :socket))\n                    (expr (getopt cmd :expr)))\n                (perform-eval socket expr)))\n   :options (list\n             (make-option\n              :string\n              :description \"The Unix domain socket\"\n              :long-name \"socket\"\n              :key :socket)\n             (make-option\n              :string\n              :description \"The Lisp expression\"\n              :long-name \"expression\"\n              :key :expr))))\n\n(defun safely-eval (expr tmp)\n  (format *standard-output* \"Using tmpfile: ~s\" tmp)\n  (safe-with-open-file (file tmp :direction :output\n                                 :if-does-not-exist :create\n                                 :if-exists :supersede)\n    (block nil\n      (format *standard-output* \"Inside block~%\")\n      (handler-bind ((error (lambda (e)\n                              (declare (ignore e))\n                              (format t \"Got error: ~a~%\" e)\n                              (dbg:output-backtrace :bug-form *standard-output*)\n                              (return-from nil :fail))))\n        (eval (read-from-string expr))\n        (format *standard-output* \"End of block~%\")\n        :good))))\n\n(defun perform-eval (socket expr)\n  (log:info \"Opening socket connection v2.\")\n  (let ((socket\n          ;; NOTE: dbg:create-ide-remote-debugging-connection requires\n          ;; us to NOT close the socket/stream.\n          (unix-sockets:connect-unix-socket socket)))\n    (log:info \"Creating debugging connection\")\n    (let ((conn (dbg:create-ide-remote-debugging-connection\n                 \"Eval\"\n                 :stream (unix-sockets:unix-socket-stream socket))))\n      (unwind-protect\n           (progn\n             (log:info \"Evaluating expression\")\n             (uiop:with-temporary-file (:pathname tmp :keep t :prefix \"deploy-eval\")\n               (delete-file tmp)\n               (log:info \"Logging output to: ~a\" tmp)\n               (let ((final-expr\n                       `(safely-eval ,expr ,(namestring tmp))))\n                 (log:info \"Full expression ~S\" final-expr)\n                 (multiple-value-bind (status reason)\n                     (dbg:ide-eval-form-in-remote\n                      final-expr\n                      :output-stream *standard-output*\n                      :timeout 1200\n                      :connection conn)\n                   (ignore-errors\n                    (write-string (uiop:read-file-string tmp)))\n                   (case status\n                     (:fail\n                      (log:info \"Command failed: ~a\" expr)\n                      (uiop:quit 1))\n                     (:good\n                      (delete-file tmp)\n                      (log:info \"Command ran without errors\"))\n                     (otherwise\n                      (log:info \"Command failed with ~a: ~a [~a]\" status expr reason)\n                      (uiop:quit 1)))))))\n        (dbg:close-remote-debugging-connection conn)))))\n"
  },
  {
    "path": "src/server/health-checks.lisp",
    "content": "(defpackage :server/health-checks\n  (:use #:cl\n        #:util/health-check)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :server/health-checks)\n\n#+(and linux lispworks)\n(def-health-check test-compile-file-pathname ()\n  (uiop:with-temporary-file (:pathname p :type \"lisp\")\n    (assert (equal \"64ufasl\"\n                   (pathname-type (compile-file-pathname p))))))\n"
  },
  {
    "path": "src/server/interrupts.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/interrupts\n  (:use #:cl)\n  #+lispworks\n  (:import-from #:system\n                #:set-signal-handler)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:unwind-on-interrupt))\n(in-package :server/interrupts)\n\n;; Currently we don't support signal handlers on\n;; non-Lispworks.. temporary though. It's just because we haven't\n;; tested it enough.\n#-lispworks\n(defmethod set-signal-handler (signal (handler (eql nil)))\n  \"Reset signal handler to default\"\n  (values))\n\n#-lispworks\n(defmethod set-signal-handler (signal handler)\n  \"Set a signal handler\"\n  (values))\n\n(define-condition interrupted-by-signal-handler (error)\n  ()\n  (:report \"Interrupted by signal handler\"))\n\n(defun on-interrupt (thread)\n  ;; This interrupt could be called in any thread. In particular, it\n  ;; might be called inside wait-for-interrupt when we already have\n  ;; the lock on handler. So let's start a new thread for this purpose.\n  (bt:make-thread\n   (lambda ()\n     (bt:interrupt-thread thread\n                          (lambda ()\n                            #+lispworks\n                            (progn\n                              (format t \"Interrupting at: ~%\")\n                              (dbg:output-backtrace :brief))\n                            (error 'interrupted-by-signal-handler))))))\n\n(defun call-unwind-on-interrupt (expr)\n  (let ((signals (list 15 #|SIGTERM|#\n                       2 #|SIGINT|#))\n        (thread (bt:current-thread) #| The thread we'll interrupt |#))\n    (flet ((prepare-interrupts ()\n             (dolist (sig signals)\n               (set-signal-handler sig (lambda (&rest args)\n                                         (declare (ignore args))\n                                         (format t \"Got signal~%\")\n                                         (on-interrupt thread))))))\n      (handler-case\n          (unwind-protect\n               (progn\n                 (prepare-interrupts)\n                 ;; Notice that expr won't be interrupted midway, we'll only\n                 ;; interrupt at the end of this call.\n                 (funcall expr)\n                 (log:info \"Waiting for interrupts\")\n\n                 (prepare-interrupts))\n            (dolist (sig signals)\n              (set-signal-handler sig nil)))\n        (interrupted-by-signal-handler (e)\n          (format t \"Unwinded from signal handler~%\")\n          (values))))))\n\n(defmacro unwind-on-interrupt (() &body expr)\n  `(call-unwind-on-interrupt\n    (lambda ()\n      ,@expr)))\n"
  },
  {
    "path": "src/server/launch.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n#include <sys/wait.h>\n#include <stdlib.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <string.h>\n#include <sys/param.h>\n#include <signal.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <time.h>\n#include <systemd/sd-daemon.h>\n\n#define BUF_SIZE 800\n#define KILL_INTERVAL 3 /* time after TERM to send a KILL */\n#define KEY \"Opening transaction log lock\"\n\ntypedef struct _process {\n        int pid;\n        int fd;\n        char buf[BUF_SIZE + 10];\n        size_t pos;\n\n        /* whether we've seen the KEY */\n        int ready;\n\n        /* whether we're the primary process */\n        int primary;\n\n        /* time at which we sent a SIGTERM */\n        time_t term_time;\n} process;\n\n#define PCOUNT 2\n\nprocess processes[PCOUNT];\n\nvoid init_process(process *p) {\n        memset(p, 0, sizeof(*p));\n        p->fd = -1;\n}\n\nint stream_child_tick(process *p) {\n        if (p->pos == BUF_SIZE) {\n                // Long line, can't do anything about it.\n                p->pos = 0;\n        }\n\n        ssize_t nread = read(p->fd, p->buf + p->pos, BUF_SIZE - p->pos);\n        if (nread < 0) {\n                if (errno == EAGAIN || errno == EWOULDBLOCK\n                    || errno == EINTR) {\n                        return -1; // Try again\n                } else {\n                        perror(\"Failed to read from child\");\n                        exit(1);\n                }\n        }\n\n        if (nread == 0) {\n                return 0;\n        }\n\n        write(STDOUT_FILENO, p->buf + p->pos, nread);\n\n        p->pos += nread;\n        p->buf[p->pos] = 0;\n        char *newline = strrchr(p->buf, '\\n');\n        if (newline) {\n                newline[0] = 0;\n\n                //printf(\"\\n\\nline is: %s\\n\\n\", buf);\n                if (strstr(p->buf, KEY)) {\n                        printf(\"Found key!\\n\");\n                        p->ready = 1;\n                        fflush(stdout);\n                }\n\n                int newlen = p->pos - (newline - p->buf) - 1;\n                memmove(p->buf, newline+1, newlen);\n                p->pos = newlen;\n\n        }\n\n        return nread;\n}\n\nvoid cleanup(process* ps) {\n        printf(\"Cleaning up %d\\n\", ps->pid);\n        close(ps->fd);\n        ps->fd = -1; // Indicator that we're dead.\n\n        int wstatus = 0;\n        pid_t wpid = waitpid(ps->pid, &wstatus, 0);\n        if (wpid < 0) {\n                perror(\"waitpid failed\");\n                exit(1);\n        }\n\n        int status = WEXITSTATUS(wstatus);\n        if (status != 0) {\n                fprintf(stderr, \"Child process %d crashed with %d\\n\", ps->pid, status);\n        }\n\n        init_process(ps);\n}\n\nvoid stream_children(process* ps, int pcount) {\n        fd_set rfds;\n        FD_ZERO(&rfds);\n        int maxfd = 0;\n        for (int i = 0; i < pcount; i++) {\n                if (ps[i].fd >= 0) {\n                        FD_SET(ps[i].fd, &rfds);\n                        maxfd = MAX(ps[i].fd, maxfd);\n                }\n        }\n\n        struct timeval tv;\n        tv.tv_sec = 5;\n        tv.tv_usec = 0;\n\n        int retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);\n\n        if (retval == -1) {\n                if (errno == EINTR) {\n                        return;\n                }\n                perror(\"select() failed\");\n                exit(1);\n        } else {\n                //printf(\"Got %d ready fds\\n\", retval);\n                // retval is the count of fds\n                for (int i = 0; i < pcount; i++) {\n                        if (FD_ISSET(ps[i].fd, &rfds)) {\n                                int ret = stream_child_tick(&ps[i]);\n                                if (ret == 0) {\n                                        cleanup(&ps[i]);\n                                }\n                        }\n                }\n        }\n\n}\n\nint count_active() {\n        int count = 0;\n        for (int i = 0; i < PCOUNT; i ++) {\n                if (processes[i].fd >= 0) {\n                        count ++;\n                }\n        }\n        return count;\n}\n\nint child(int pipefd[2], char** argv) {\n        close(pipefd[0]);\n        printf(\"Message from child process 1\\n\");\n        FILE *f = fdopen(pipefd[1], \"w\");\n        if (!f) {\n                perror(\"Failed to fdopen\");\n                return 1;\n        }\n\n        fprintf(f, \"Inner message1\\n\");\n        fflush(f);\n\n        // Redirect stdout to the pipe\n        if (dup2(pipefd[1], STDOUT_FILENO) < 0) {\n                perror(\"Could not dup2\");\n                return -1;\n        };\n        /*\n        if (dup2(pipefd[1], STDERR_FILENO) < 0) {\n                perror(\"Could not dup2 on stderr\");\n                return -1;\n                };*/\n\n        close(pipefd[1]);\n\n        printf(\"Message from child process\\n\");\n\n        execvp(argv[1], argv + 1);\n        perror(\"execvp failed\");\n        return 1;\n}\n\nint unused_slot() {\n        for (int i = 0; i < PCOUNT; i++) {\n                if (processes[i].fd < 0) {\n                        return i;\n                }\n        }\n        return -1;\n}\n\nvoid launch(int argc, char** argv) {\n        if (unused_slot() < 0) {\n                fprintf(stderr, \"Attempting to launch without unused slot\\n\");\n                return;\n        }\n        printf(\"Launching command %s\\n\", argv[1]);\n        fflush(stdout);\n\n        int pipefd[2];\n        if (pipe(pipefd) != 0) {\n                perror(\"pipe failed\");\n                exit(1);\n        }\n        //printf(\"Got pipe!: %d, %d, %d\\n\", pipefd[0], pipefd[1], getpid());\n\n\n        pid_t pid = fork();\n        if (pid == -1) {\n                perror(\"fork failed\");\n                exit(1);\n        }\n\n        if (pid == 0) {  // child\n                child(pipefd, argv);\n                return;\n        }\n\n        close(pipefd[1]);\n\n        fcntl(pipefd[0], F_SETFL, O_NONBLOCK);\n\n        int slot = unused_slot();\n\n        processes[slot].pos = 0;\n        processes[slot].fd = pipefd[0];\n        processes[slot].pid = pid;\n}\n\nint hup_called = 0;\nint _argc;\nchar** _argv;\n\nvoid hup_handler(int) {\n        printf(\"Got SIGHUP!\\n\");\n        hup_called = 1;\n}\n\nvoid setup_signals() {\n        struct sigaction act = { 0 };\n        act.sa_flags = SA_SIGINFO ;\n        act.sa_handler = &hup_handler;\n        if (sigaction(SIGHUP, &act, NULL) == -1) {\n                perror(\"sigaction failed\");\n                exit(1);\n        }\n\n}\n\nvoid promote_one() {\n        for (int i = 0; i < PCOUNT; i++) {\n                if (processes[i].fd < 0) {\n                        continue;\n                }\n\n                if (processes[i].ready) {\n                        printf(\"Promoted: %d\\n\", processes[i].pid);\n                        processes[i].primary = 1;\n                        char s[1000];\n                        snprintf(s, sizeof(s),\n                                 \"MAINPID=%d\",\n                                 processes[i].pid);\n                        sd_notify(0, s);\n                        return;\n                }\n        }\n}\n\n\nint is_valid(process* p) {\n        return p->fd >= 0;\n}\n\nint ready_count() {\n        int readyCount = 0;\n        for (int i = 0; i < PCOUNT; i++) {\n                if (!is_valid(&processes[i])) {\n                        continue;\n                }\n                if (processes[i].ready) {\n                        readyCount++;\n                }\n        }\n\n        return readyCount;\n}\n\nint primary_count() {\n        int primaryCount = 0;\n        for (int i = 0; i < PCOUNT; i++) {\n                if (!is_valid(&processes[i])) {\n                        continue;\n                }\n                if (processes[i].primary) {\n                        primaryCount++;\n                }\n        }\n\n        return primaryCount;\n}\n\nprocess* primary() {\n        for (int i = 0; i < PCOUNT; i++) {\n                process *p = &processes[i];\n                if (is_valid(p) && p->primary) {\n                        return p;\n                }\n        }\n        return NULL;\n}\n\nvoid maybe_promote() {\n        if (ready_count() > 0 && primary_count() == 0) {\n                promote_one();\n        }\n}\n\n/* If there's a ready process, and a primary process, we kill the primary. We first attempt a TERM, */\nvoid maybe_kill_primary() {\n        if (ready_count() > 1 && primary_count() == 1) {\n                /* There's more than one ready instance, but one primary */\n                process *p = primary();\n                if (!p->term_time) {\n                        printf(\"Sending SIGTERM to %d\\n\", p->pid);\n                        kill(p->pid, SIGTERM);\n                        p->term_time = time(NULL);\n                } else {\n                        if (time(NULL) - p->term_time > KILL_INTERVAL) {\n                                printf(\"Sending SIGKILL to %d\\n\", p->pid);\n                                kill(p->pid, SIGKILL);\n                        }\n                }\n        }\n}\n\n\nint main(int argc, char** argv) {\n        _argc = argc;\n        _argv = argv;\n        for (int i = 0; i < PCOUNT; i++) {\n                init_process(&processes[i]);\n        }\n\n        if (argc < 2) {\n                fprintf(stderr, \"Need arguments to launch\\n\");\n                return -1;\n        }\n\n        setup_signals();\n\n        launch(argc, argv);\n\n        while (count_active()) {\n                if (hup_called) {\n                        launch(argc, argv);\n                        hup_called = 0;\n                }\n                maybe_kill_primary();\n                maybe_promote();\n                stream_children(processes, PCOUNT);\n        }\n}\n"
  },
  {
    "path": "src/server/server.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem \"server/interrupts\"\n  :depends-on (:alexandria\n               :bordeaux-threads)\n  :serial t\n  :components ((:file \"interrupts\")))\n\n(defsystem \"server/util\"\n  :depends-on (:bordeaux-threads\n               :easy-macros)\n  :components ((:file \"util\" :if-feature :lispworks))\n  :description \"This system gets reloaded before we reload any complex code, which let's us put reloading logic in here. It also means we must keep the dependencies minimal\")\n\n(defsystem \"server\"\n    :depends-on (\"cl-cli\"\n                 \"util.store\"\n                 \"util.store/benchmarks\"\n                 \"cl-cron\"\n                 \"server/interrupts\"\n                 #+ (or ccl lispworks)\n                 \"jvm\"\n                 #+lispworks\n                 \"util/remote-debugging\"\n                 \"bordeaux-threads\"\n                 \"lparallel\"\n                 \"serapeum\"\n                 (:feature (:and :lispworks (:not :mswindows)) \"control-socket\")\n                 \"easy-macros\"\n                 \"util/health-check\"\n                 \"server/util\"\n                 \"util.threading\"\n                 \"util/phabricator\"\n                 \"hunchentoot-extensions\"\n                 \"bknr.datastore\"\n                 \"hunchentoot-multi-acceptor\")\n    :serial t\n    :components ((:file \"interrupts\")\n                 (:file \"health-checks\")\n                 (:file \"acceptor-override\" :if-feature :lispworks)\n                 (:file \"control-socket\" :if-feature (:and :lispworks (:not :mswindows)))\n                 (:file \"setup\")))\n\n(defsystem #:server/config\n  :depends-on ()\n  :components ((:file \"config\")))\n\n(defsystem #:server/cluster\n  :depends-on (#:server/config\n               #:core.config\n               #:clingon)\n  :serial t\n  :components ((:file \"eval\" :if-feature :lispworks)\n               (:module \"cluster\"\n                :components ((:file \"status\")\n                             (:file \"leadership\")\n                             (:file \"cluster-init\")\n                             (:file \"peers\"))\n                :if-feature (:and :lispworks :linux))\n               (:file \"config-cli\" :if-feature (:and :lispworks :linux))))\n\n(defsystem #:server/cli\n  :depends-on (#:server\n               #:server/config\n               #:server/cluster\n               #:clingon)\n  :serial t\n  :components ((:file \"cli\")))\n\n;; For slynk support, load this before calling server:main. The reason\n;; we separate this into a separate system is for support with SLIME,\n;; which is needed if you're using Atom's SLIMA.\n(defsystem \"server/slynk\"\n  :serial t\n  :depends-on (\"server\"\n               \"slynk\"\n               \"slynk/arglists\"\n               \"slynk/fancy-inspector\"\n               \"slynk/package-fu\"\n               \"slynk/mrepl\"\n               \"slynk/trace-dialog\"\n               \"slynk/profiler\"\n               \"util/posix\"\n               \"slynk/stickers\"\n               \"slynk/indentation\"\n               \"slynk/retro\"\n               \"slynk-named-readtables\")\n  :components ((:file \"slynk-preparer\")))\n\n(defsystem :server/tests\n  :depends-on (:server\n               :server/config\n               :cl-mock\n               :util/fiveam\n               :fiveam-matchers\n               :server/slynk)\n  :serial t\n  :components ((:file \"test-server\")\n               (:file \"test-config\")\n               (:file \"test-util\" :if-feature :lispworks)\n               (:file \"test-slynk-preparer\")))\n"
  },
  {
    "path": "src/server/setup.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:server\n  (:use #:cl\n        #:bknr.datastore\n        #:server/interrupts)\n  (:import-from #:util/health-check\n                #:run-health-checks)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:lparallel.kernel\n                #:*lisp-exiting-p*)\n  #+(:and :lispworks (:not :mswindows))\n  (:import-from #:server/control-socket\n                #:with-control-socket)\n  (:import-from #:util/threading\n                #:funcall-with-sentry-logs)\n  (:import-from #:util/store/store-migrations\n                #:run-migrations)\n  #+lispworks\n  (:import-from #:hunchentoot-extensions/existing-socket\n                #:acceptor-with-existing-socket)\n  (:export #:main\n           #:register-acceptor\n           #:slynk-loop\n           #:*slynk-preparer*\n           #:slynk-prepare\n           #:*slynk-loopback-interface*\n           #:*slynk-port*\n           #:slynk-teardown\n           #:*shutdown-hooks*\n           #:with-cron))\n(in-package #:server)\n\n(defvar *port*)\n(defvar *slynk-port*)\n(defvar *verify-store*)\n(defvar *profile-store*)\n(defvar *verify-snapshots*)\n(defvar *shell*)\n(defvar *start-slynk*)\n\n;; Reminder you can generate a self-signed pair like so:\n;; openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=localhost'\n\n(defvar *slynk-loopback-interface* \"localhost\")\n(defvar *health-check*)\n\n#-(:and :lispworks (:not :mswindows))\n(def-easy-macro with-control-socket (&fn fn)\n  (funcall fn))\n\n(defparameter *options*\n  `((*port* #+screenshotbot-oss \"4091\"\n            #-screenshotbot-oss \"4001\" \"\" :params (\"PORT\"))\n    (*shell* nil \"\")\n    (*slynk-port* #+screenshotbot-oss \"4095\"\n                  #-screenshotbot-oss \"4005\"\n                  \"\" :params (\"SLYNK-PORT\"))\n    (*slynk-loopback-interface* \"localhost\" \"The interface on which we bind the slynk port\"\n                                :params (\"SLYNK-LOOPBACK-INTERFACE\"))\n    (*start-slynk* #+screenshotbot-oss nil\n                   #-screenshotbot-oss t\n                   \"\")\n    (util/store:*object-store*\n     #+screenshotbot-oss \"~/.config/screenshotbot/object-store/\"\n     #-screenshotbot-oss \"/data/arnold/object-store/\" \"\" :params (\"OBJECT-STORE\"))\n    (*verify-store* nil \"\")\n    (*health-check* nil \"\")\n    #+lispworks\n    (*profile-store* nil \"When used with --verify-store, profiles the store load\")\n    (*verify-snapshots* nil \"\")\n    #-sbcl\n    (jvm:*libjvm* nil \"Location of libjvm.so\" :params (\"LIBJVM\"))\n    (*debugger*\n     nil\n     \"Enable the debugger on the main thread, this lets us debug issues with loading snapshots and transaction logs.\"\n     :params (\"DEBUGGER\"))))\n\n(defclass my-default-acceptor (hex:secure-acceptor\n                               hunchentoot:easy-acceptor)\n  ())\n\n(defclass my-taskmaster (hunchentoot:one-thread-per-connection-taskmaster)\n  ())\n\n(defclass my-acceptor (#+lispworks acceptor-with-existing-socket\n                       #+lispworks\n                       server/acceptor-override:ipv6-acceptor\n                       hunchentoot-multi-acceptor:multi-acceptor)\n  ()\n  (:default-initargs\n   :listen-backlog 500\n   :taskmaster (make-instance 'my-taskmaster)\n   :default-acceptor (make-instance 'my-default-acceptor\n                                    :port 1\n                                    :name 'hunchentoot-multi-acceptor:default-acceptor)))\n\n(defvar *multi-acceptor*)\n\n(defvar *init-hooks* nil)\n\n(defun register-acceptor (acceptor &rest hostnames)\n  (loop for host in hostnames do\n    (let ((host host))\n     (push\n      (lambda ()\n        (log:info \"Adding acceptor ~a\" acceptor)\n        (hunchentoot-multi-acceptor:add-acceptor *multi-acceptor* host acceptor))\n      *init-hooks*)\n      ;; Immediately register the acceptor if we're executing this live\n      (when (boundp '*multi-acceptor*)\n        (init-sub-acceptors)))))\n\n(defvar *shutdown-hooks* nil)\n\n(defun init-multi-acceptor (&key (port (parse-integer *port*)))\n  (log:info \"Acceptor on ~a\" port)\n  (setf *multi-acceptor* (make-instance 'my-acceptor :port port :name 'multi-acceptor))\n  (init-sub-acceptors))\n\n(defun init-sub-acceptors ()\n  (mapc 'funcall *init-hooks*)\n  (setf *init-hooks* nil))\n\n(defvar *slynk-preparer* nil)\n\n(defmethod slynk-prepare (preparer)\n  (values))\n\n(defmethod slynk-teardown (preparer)\n  (values))\n\n(defvar *multi-server*)\n\n(defclass utf-8-daily-file-appender (log4cl:daily-file-appender)\n  ())\n\n(defmethod slot-unbound (class (appender utf-8-daily-file-appender)\n                         (slot-name (eql 'log4cl::stream)))\n  (declare (ignore class slot-name))\n  (create-appender-file appender))\n\n(defun create-appender-file (appender)\n  (let ((filename (log4cl::appender-filename appender)))\n    (log4cl::maybe-close-stream appender)\n    (setf (slot-value appender 'stream)\n          (flexi-streams:make-flexi-stream\n           (open (ensure-directories-exist filename)\n                 #+ccl :sharing #+ccl :external\n                 :direction :output\n                 :if-exists :append\n                 :element-type '(unsigned-byte 8)\n                 :if-does-not-exist :create)\n           :external-format :utf-8\n           :element-type 'character))))\n\n(defun setup-appenders (&key clear)\n  (let ((log-file (path:catfile \"log/logs\")))\n    (when clear\n     (log4cl:clear-logging-configuration))\n    (log:config :info)\n    (log4cl:add-appender log4cl:*root-logger*\n                         (make-instance 'utf-8-daily-file-appender\n                                         :name-format log-file\n                                         :backup-name-format\n                                         \"logs.%Y%m%d\"\n                                         :filter 4\n                                         :layout (make-instance 'log4cl:simple-layout)))))\n\n\n(defvar *debugger* nil)\n\n(def-easy-macro with-hidden-warnings (&fn fn)\n  (let ((old *error-output*))\n   (with-output-to-string (out)\n     (setf *error-output* out)\n     (unwind-protect\n          (funcall fn)\n       (setf *error-output* old)))))\n\n(def-easy-macro with-lparallel-kernel (&key (threads 20)\n                                            &fn fn)\n  (let ((shutting-down-p nil))\n    (flet ((context (fn)\n             (handler-bind ((simple-warning (lambda (w)\n                                              (when shutting-down-p\n                                                (muffle-warning w)))))\n               (funcall fn))))\n\n      ;; Better default, and there's not many ways we can safely make\n      ;; this T, unless we change lparallel itself. (T2133)\n      (setf lparallel:*debug-tasks-p* nil)\n\n      (setf lparallel:*kernel* (lparallel:make-kernel threads\n                                                      :context #'context))\n      (unwind-protect\n           (funcall fn)\n        (safe-log \"lparallel: shutting down~%\")\n        (setf shutting-down-p t)\n        (lparallel:end-kernel :wait t)\n        (safe-log \"lparallel: shutdown complete!~%\")))))\n\n(def-easy-macro maybe-with-debugger (&fn fn)\n  (handler-bind ((error (lambda (e)\n                          (safe-log \"Got error: ~a (*debugger* is set to ~a)\" e\n                                    *debugger*)\n                          #+lispworks\n                          (dbg:output-backtrace :brief)\n                          ;; So by this point, *debugger* is most\n                          ;; likely already read from the command line\n                          ;; options.\n                          (unless *debugger*\n                            ;; Invoking a restart here might be\n                            ;; unsafe, since we don't know if all the\n                            ;; unwind-protects are going to behave\n                            ;; correctly.\n                            (safe-log \"Aborting because of error~%\")\n                            (uiop:quit 1)))))\n    (fn))\n  (safe-log \"Out of maybe-with-debugger~%\"))\n\n(def-easy-macro with-sentry-logging (&fn fn)\n  (funcall-with-sentry-logs fn))\n\n(def-easy-macro with-slynk (&fn fn)\n  (when *start-slynk*\n    (slynk-prepare *slynk-preparer*))\n  (unwind-protect\n       (fn)\n    (safe-log \"Shutting down slynk~%\")\n    (slynk-teardown *slynk-preparer*)))\n\n(def-easy-macro with-running-acceptor (acceptor &fn fn)\n  (hunchentoot:start acceptor)\n  (unwind-protect\n       (fn)\n    (safe-log \"Shutting down hunchentoot~%\")\n    (hunchentoot:stop acceptor)))\n\n(def-easy-macro with-cron (&fn fn)\n  (bt:make-thread\n   (lambda ()\n     ;; Wait a bit for the server to stabilize before starting any\n     ;; cron jobs. This might be particularly important in Raft, where\n     ;; we might still be replaying transactions.\n     (sleep 300)\n     (cl-cron:start-cron)))\n  (unwind-protect\n       (funcall fn)\n    (safe-log \"Shutting down cron~%\")\n    (cl-cron:stop-cron)))\n\n(def-easy-macro with-cl-cli-processed (&key &binding verify-store\n                                            &binding profile-store\n                                            &binding health-check\n                                            &fn fn)\n  (let ((args #-lispworks (cons \"<arg0>\"(uiop:command-line-arguments))\n                    #+lispworks sys:*line-arguments-list*))\n          (safe-log \"CLI args: ~s~%\" args)\n    (multiple-value-bind (vars vals matched dispatch rest)\n              (cl-cli:parse-cli args\n                                *options*)\n            (declare (ignore matched dispatch rest))\n            (loop for var in vars\n                  for val in vals\n                  do (setf (symbol-value var) val))\n      (fn *verify-store*\n        (or #+lispworks *profile-store*)\n        *health-check*))))\n\n(defun safe-log (&rest args)\n  (apply #'format t args)\n  (finish-output t))\n\n(def-easy-macro maybe-with-control-socket (&key enable-store &fn fn)\n  (cond\n    (enable-store\n     (with-control-socket ()\n       (fn)))\n    (t\n     (fn))))\n\n(def-easy-macro with-common-setup (&key enable-store jvm &fn fn)\n  (maybe-with-debugger ()\n    (with-lparallel-kernel ()\n      (maybe-with-control-socket (:enable-store enable-store)\n        (with-slynk ()\n          #+lispworks\n          (when jvm\n            ;; Note: The jvm-init overrides SIGTERM. So it must be\n            ;; called before UNWIND-ON-INTERRUPT, which sets its own\n            ;; SIGTERM.\n            (jvm:jvm-init))\n\n          (unwind-on-interrupt ()\n            (fn))))\n      ;; unwind if an interrupt happens\n      (log:config :sane :immediate-flush t)\n      (log:config :info)\n      (safe-log \"shutting down~%\")\n      (safe-log \"calling shutdown hooks~%\")\n      (mapc 'funcall *shutdown-hooks*)\n\n      ;; ;; don't snapshot the store, if the process is killed while the\n      ;; ;; snapshot is happening, we have to manually recover the store\n      ;; (bknr.datastore:snapshot)\n\n      (when enable-store\n        (bknr.datastore:close-store))\n\n      (safe-log \"all services down~%\")\n      (finish-output t)))\n\n  #+lispworks\n  (wait-for-processes)\n  (safe-log \"all threads before exiting: ~s~%\" (bt:all-threads))\n  (log4cl:flush-all-appenders)\n  (log4cl:stop-hierarchy-watcher-thread))\n\n(defun main (&key (enable-store t)\n               (jvm t)\n               acceptor)\n  \"called from launch scripts, either web-bin or launch.lisp\"\n\n  (with-cl-cli-processed (:verify-store verify-store\n                          :profile-store profile-store\n                          :health-check health-check)\n    (declare (ignorable profile-store))\n    (cond\n      (verify-store\n       (with-common-setup (:enable-store t :jvm jvm)\n         (%verify :profile-store profile-store)))\n      (health-check\n       (with-common-setup (:enable-store enable-store :jvm jvm)\n         (run-health-checks)\n         (uiop:quit 0)))\n      (t\n       (with-common-setup (:enable-store enable-store :jvm jvm)\n         (%run :enable-store enable-store :acceptor acceptor))))))\n\n(defun %verify (&key profile-store)\n  (log:config :info)\n  (time\n   (cond\n     #+lispworks\n     (profile-store\n      (hcl:profile\n       (util/store:verify-store)))\n     (t\n      (util/store:verify-store\n       :callback (lambda ()\n                   (log:info \"Done verifying store, running health checks...\")\n                   (unless (run-health-checks)\n                     (log:info \"Crashing, since health checks failed\")\n                     (uiop:quit 1))\n                   (run-migrations :snapshot nil))))))\n\n  (uiop:quit 0))\n\n(defun required (&optional arg)\n  (error \"Missing required argument: ~a\" arg))\n\n(defun %run (&key (enable-store (required))\n               (acceptor (required))\n               (port (parse-integer *port*))\n               (early-hunchentoot nil)\n               (shell *shell*))\n  \"If EARLY-HUNCHENTOOT is set, then the hunchentoot server is started\nbefore the store is loaded. Only do this if you already have a\nmechanism to redirect requests, otherwise you'll be responding to HTTP\nrequests from an incomplete data source, and could potentially lead to\ncorruption.\"\n  ;; set this to t for 404 page. :/\n  (setf hunchentoot:*show-lisp-errors-p* t)\n\n  (setf hunchentoot:*rewrite-for-session-urls* nil)\n\n  ;; In the OSS version, we don't have bknr.cluster, so we always load the store first to avoid \n  (unless early-hunchentoot\n    (%prepare-store :enable-store enable-store))\n\n  (cond\n    (shell\n     (when early-hunchentoot\n       (%prepare-store :enable-store enable-store))     \n     (log:info \"Slynk has started up, but we're not going to start hunchentoot. Call (QUIT) from slynk when done.\"))\n    (t\n     (unless acceptor\n       (init-multi-acceptor :port port)\n       (setf acceptor *multi-acceptor*))\n     (with-running-acceptor (acceptor)\n       (log:info \"The web server is live at port ~a. See logs/logs for more logs~%\"\n                 (hunchentoot:acceptor-port acceptor))\n\n       (log:info \"Now we wait indefinitely for shutdown notifications\")\n\n       (when early-hunchentoot\n         (%prepare-store :enable-store enable-store))\n       ;; Cron should only be started once the store is up and running\n       (with-cron ()\n         (loop (sleep 60)))))))\n\n(defun %prepare-store (&key enable-store)\n  (when enable-store\n    (util/store:prepare-store))\n  (log:info \"Store is prepared, moving on...\")\n  \n  #+screenshotbot-oss\n  (run-migrations))\n\n#+lispworks\n(defun wait-for-processes ()\n  (let ((wait-time 10))\n   (dotimes (i wait-time)\n     (safe-log \"[~a/~a] Wait for processes\" i wait-time)\n     (let* ((processes\n              (set-difference (mp:list-all-processes) mp:*initial-processes*))\n            (processes\n              (loop for p in processes\n                    unless (or\n                            (eql p (mp:get-current-process))\n                            (let ((name (mp:process-name p)))\n                              (some\n                               (lambda (substring)\n                                 (str:containsp substring name))\n                               '(\"Hierarchy Watcher\"\n                                 \"The idle process\"\n                                 \"Initial delivery process\"\n                                 \"Restart Function Process\"\n                                 \"Process for thread created by foreign code\"))))\n                      collect p)))\n\n       (cond\n         (processes\n          (log:info \"Threads remaining: ~S\" processes)\n          (log4cl:flush-all-appenders)\n          (sleep 1))\n         (t\n          ;; nothing left!\n          (log:info \"Should be a safe shutdown!\")\n          (return-from wait-for-processes nil))))))\n  (log:info \"We waited for threads to cleanup but nothing happened, so we're going for a force uiop:quit\")\n  (log4cl:flush-all-appenders)\n  (uiop:quit))\n"
  },
  {
    "path": "src/server/slynk-preparer.lisp",
    "content": "(defpackage :server/slynk-preparer\n  (:use #:cl)\n  (:import-from #:server\n                #:defmethod\n                #:slynk-prepare\n                #:slynk-teardown\n                #:*slynk-port*\n                #:*slynk-loopback-interface*)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:*actual-slynk-port*))\n(in-package :server/slynk-preparer)\n\n(defclass slynk-preparer ()\n  ())\n\n(setf server:*slynk-preparer* (make-instance 'slynk-preparer))\n\n(defvar *actual-slynk-port* nil)\n\n(defmethod slynk-prepare ((self slynk-preparer))\n\n  (log:info \"starting up slynk\")\n  (slynk-loop))\n\n(defun slynk-loop ()\n  (log:info \"Using port for slynk: ~a on ~a\" *slynk-port* *slynk-loopback-interface*)\n  (setf slynk:*loopback-interface* *slynk-loopback-interface*)\n  (try-different-ports))\n\n(define-condition no-available-port (error)\n  ()\n  (:report \"Could not open slynk port\"))\n\n(defun try-different-ports ()\n  (loop for i from 0 to 20\n        for port from (parse-integer *slynk-port*)\n        do\n           (handler-case\n               (progn\n                 (log:info \"Trying port: ~a\" port)\n                 (slynk:create-server :port port\n                                      ;; if non-nil the connection won't be closed\n                                      ;; after connecting\n                                      :dont-close t)\n                 (setf *actual-slynk-port* port)\n                 (return t))\n             (error (e)\n               (log:info \"Failed to create slynk on port ~a: ~a\"\n                         port\n                         e)))\n        finally\n        (error 'no-available-port)))\n\n(defmethod slynk-teardown ((self slynk-preparer))\n  (slynk:stop-server *actual-slynk-port*))\n"
  },
  {
    "path": "src/server/test-config.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/test-config\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:server/config\n                #:load-config\n                #:*config-file*)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that))\n(in-package :server/test-config)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*installation* :none)\n        (*config-file* nil))\n    (&body)))\n\n(defclass fake-installation ()\n  ())\n\n(test simple-load-config ()\n  (with-fixture state ()\n   (uiop:with-temporary-file (:pathname p :stream s)\n     (let ((*package* (find-package :CL)))\n       (write `(make-instance 'fake-installation) :stream s))\n     (finish-output s)\n     (let ((*config-file* p))\n       (load-config))\n     (assert-that *installation*\n                  (has-typep 'fake-installation)))))\n"
  },
  {
    "path": "src/server/test-server.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/test-server\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:server\n                #:with-lparallel-kernel)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :server/test-server)\n\n(util/fiveam:def-suite)\n\n(test with-lparallel-kernel\n  (let (val warning)\n    (with-lparallel-kernel ()\n      (setf val (lparallel:force\n                 (lparallel:future :done))))\n    (is (eql :done val))))\n\n(test with-lparallel-kernel-when-no-jobs-were-created\n  (let (val warning)\n    (with-lparallel-kernel ()\n      nil)\n    (pass)))\n"
  },
  {
    "path": "src/server/test-slynk-preparer.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/test-slynk-preparer\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:cl-mock\n                #:if-called\n                #:answer\n                #:with-mocks)\n  (:import-from #:server\n                #:*slynk-port*)\n  (:import-from #:server/slynk-preparer\n                #:no-available-port\n                #:*actual-slynk-port*\n                #:try-different-ports))\n(in-package :server/test-slynk-preparer)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (with-mocks ()\n    (let ((*actual-slynk-port* nil)\n          (*slynk-port* \"4001\"))\n      (&body))))\n\n(test try-different-ports-happy-path\n  (with-fixture state ()\n    (answer (slynk:create-server\n             :port 4001\n             :dont-close t)\n      t)\n    (try-different-ports)\n    (is (equal 4001 *actual-slynk-port*))))\n\n(test try-different-ports-with-a-failure\n  (with-fixture state ()\n    (if-called 'slynk:create-server\n               (lambda (&key port dont-close)\n                 (cond\n                   ((eql 4001 port)\n                    (error \"bad\"))\n                   (t\n                    t))))\n    (try-different-ports)\n    (is (equal 4002 *actual-slynk-port*))))\n\n\n(test try-all-ports-failed\n  (with-fixture state ()\n    (if-called 'slynk:create-server\n               (lambda (&key port dont-close)\n                 (error \"bad\")))\n    (signals no-available-port\n     (try-different-ports))))\n"
  },
  {
    "path": "src/server/test-util.lisp",
    "content": "(defpackage :server/test-util\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:server/util\n                #:with-error-handlers))\n(in-package :server/test-util)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (eval `(defun foo (x)))\n  (&body))\n\n(test simple-invocation ()\n  (with-fixture state ()\n    (finishes\n      (with-error-handlers ()\n       (eval `(defmethod foo (x) :reset))))\n    (is (eql :reset (foo nil)))))\n"
  },
  {
    "path": "src/server/util.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :server/util\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro))\n(in-package :server/util)\n\n#+lispworks\n(def-easy-macro with-error-handlers (&fn fn)\n  (let ((counter 0))\n   (handler-bind ((error\n                    (lambda (e)\n                      (when (and\n                             ;; Could we loop forever?\n                             (< counter 5)\n                             (loop for (type substr) in\n                                                     (list\n                                                      (list 'simple-error \"is not congruent with\")\n                                                      (list 'conditions:simple-program-error \"is defined as an ordinary function\"))\n                                   if (and\n                                       (typep e type)\n                                       (str:containsp substr (format nil \"~a\" e)))\n                                     return t))\n                        (incf counter)\n                        ;; Usually this means \"modify the lambda list and delete existing methods\"\n                        ;; For is defined as an ordinary function, this would be \"Discard existing definition and create generic function\"\n                        (invoke-restart 'continue)))))\n     (fn))))\n\n#+(and lispworks linux)\n(defun safe-load-system (name &key fix-by-default)\n  (bt:with-lock-held ((symbol-value (read-from-string \"bknr.cluster/server::*commit-lock*\")))\n    (cond\n      (fix-by-default\n       (with-error-handlers ()\n         (asdf:load-system name)))\n      (t\n       (asdf:load-system name)))))\n"
  },
  {
    "path": "src/test-runner/affected-systems.lisp",
    "content": "(defpackage :test-runner/affected-systems\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:filter-affected-systems))\n(in-package :test-runner/affected-systems)\n\n(defun action-deps (action)\n  (destructuring-bind (op . component) action\n    (asdf::direct-dependencies op component)))\n\n(defparameter *bad-files*\n  (list \"jenkins.lisp\"\n        \"scripts/init.lisp\"\n        \"scripts/prepare-image.lisp\"\n        \"Makefile\"))\n\n(defun needs-rebuild-all (files)\n  (loop for f in files\n        if (or (equal \"asd\" (pathname-type f))\n               (not (path:-e f))\n               (member f *bad-files* :test #'equal))\n          return t))\n\n(defun filter-affected-systems (systems files\n                                &optional\n                                  (root (asdf:system-relative-pathname :web.all \"../\")))\n  \"Keep only the systems in `systems` that are going to be affected by\n  the changed `files`. Files are relative to the git root, and may\n  point to a non-existant file, in which case we consider the file\n  to be deleted.\"\n  (let ((files (loop for x in files collect (path:catfile root x)))\n        (roots (loop for x in systems\n                     collect `(asdf:load-op . ,x)))\n        (cache (make-hash-table :test #'equal)))\n    (cond\n      ((needs-rebuild-all files)\n       (log:info \"We'll be rebuilding all the input targets\")\n       (mapcar #'cdr roots))\n      (t\n\n       (dolist (f files)\n         (setf (gethash f cache) t))\n       (let ((results (graphs:dfs roots\n                                  :children #'action-deps\n                                  :node-test #'equal\n                                  :after-hook (lambda (action results)\n                                                (let ((res\n                                                        (or\n                                                         (some #'identity results)\n                                                         (destructuring-bind (op . component) action\n                                                           (declare (ignore op))\n                                                           (when (typep component 'asdf:file-component)\n                                                             (gethash (asdf:component-pathname component) cache))))))\n                                                  #+nil\n                                                  (log:info\n                                                   \"Looking at ~s and got ~s (results: ~s for ~s)\"\n                                                   action res results (action-deps action))\n                                                  res)))))\n         (values\n          (loop for x in roots\n                for y in results\n                if y\n                  collect (cdr x))\n          results))))))\n"
  },
  {
    "path": "src/test-runner/build-test-runner.lisp",
    "content": "\n(ql:quickload :test-runner)\n\n(test-runner:init)\n\n(hcl:save-image \"build/t\"\n                :console t\n                :restart-function #'test-runner:image-main\n                :multiprocessing t\n                :environment nil)\n"
  },
  {
    "path": "src/test-runner/test-runner.asd",
    "content": "(defsystem :test-runner\n  :serial t\n  ;; Most of these dependencies are because we need these to be loaded\n  ;; before we can load any test deps with\n  ;; dspect:*redefinition-action*.\n  :depends-on (#-(or jipr eaase-oss)\n               :jvm\n               :trivial-features\n               :log4cl\n               :fiveam\n               #-(:or :jipr :screenshotbot-oss :eaase-oss)\n               :sentry\n               :graphs\n               :util.testing\n               :str\n               :util.misc\n               :util.threading\n               :tmpdir\n               :cl-fad)\n  :components ((:file \"affected-systems\")\n               (:file \"test-runner\")))\n"
  },
  {
    "path": "src/test-runner/test-runner.lisp",
    "content": "(defpackage :test-runner/test-runner\n  (:nicknames :test-runner)\n  (:use #:cl)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:util/threading\n                #:*log-sentry-p*)\n  (:import-from #:util/testing\n                #:clean-up-static-web-outputs)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:init\n   #:main\n   #:image-main))\n(in-package :test-runner/test-runner)\n\n(defvar *test-trace-io* nil)\n(defun init-jvm-for-ccl ()\n  (progn\n  (funcall (find-symbol \"JVM-INIT\" \"JVM\"))\n  (setf *debugger-hook* nil)))\n\n#-(or jipr screenshotbot-oss eaase-oss)\n(setf sentry:*disabledp* t)\n\n(defun hide-outputs ()\n  (setf fiveam:*test-dribble* *standard-output*)\n  (setf *test-trace-io* *trace-output*)\n  #-ccl ;; for some reason breaks some tests on ccl\n  (let ((null-file\n          (open \"build/test-logs\"\n                :direction :output\n                :if-exists :supersede)))\n    (setf *standard-output* null-file)\n    (setf *error-output* null-file)\n    (setf *trace-output* null-file)\n    (log:config :fatal)))\n\n(defun init ()\n  #+ccl\n  (init-jvm-for-ccl)\n  ;; On Windows, we need an extended stack. This isn't just for the\n  ;; tests, we need it just to even load all the systems.\n  #+(and lispworks :mswindows)\n  (hcl:extend-current-stack)\n  (load-systems)\n  (clean-up-static-web-outputs))\n\n(defun find-tests ()\n  (append\n   (%find-tests \"src/\")\n   (%find-tests \"third-party/bknr.datastore/\")\n   (%find-tests \"local-projects/cl-mongo-id/\")))\n\n(defun %find-tests (pathname)\n  (when (path:-d pathname)\n   (let* ((index (ql::ensure-system-index pathname)))\n     (when index\n       (with-open-file (stream index)\n         (remove-if 'null\n                    (loop for line = (read-line stream nil)\n                          while line\n                          collect\n                          (let ((system (pathname-name (pathname line))))\n                            (cond\n                              ((member \"build\" (pathname-directory line)\n                                       :test 'equal)\n                               ;; bad test, discard\n                               (values))\n                              ((str:ends-with-p \".tests\" system)\n                               system)\n                              ((str:ends-with-p \".test\" system)\n                               system)\n                              (t\n                               (let ((test-name (format nil \"~a/tests\" system)))\n\n                                 (let ((x\n                                         (handler-case\n                                             (ql:quickfind test-name)\n                                           (asdf:missing-component ()\n                                             nil))))\n                                   (when x\n                                     (asdf:component-name x))))))))))))))\n\n\n(defun screenshot-tests ()\n  \"Tests that generate screenshots should always be run\"\n  (list\n   #- (or eaase-oss)\n   \"screenshotbot/tests\"\n   #- (or screenshotbot-oss eaase-oss)\n   \"screenshotbot.pro/tests\"))\n\n(defun load-systems ()\n  (let ((systems (or\n                  (let ((pos (position \"-system\" (uiop:raw-command-line-arguments) :test 'equal)))\n                    (when pos\n                      (str:split \",\" (elt (uiop:raw-command-line-arguments) (1+ pos)))))\n                  (union\n                   (find-tests)\n                   (screenshot-tests)\n                   :test #'string-equal)))\n        (test-redefinitions-p (not (str:emptyp (uiop:getenv \"TDRHQ_TEST_REDEF\")))))\n    #- (or screenshotbot-oss eaase-oss)\n    (progn\n      #-ccl\n      (when (uiop:getenv \"JENKINS_URL\")\n        (let ((files (uiop:read-file-lines \"build/affected-files.txt\")))\n          (log:info \"Got affected files ~S\" files)\n          (setf systems (test-runner/affected-systems:filter-affected-systems\n                         systems\n                         files)))))\n\n    #+lispworks\n    (when test-redefinitions-p\n      (ql:quickload :hu.dwim.def+hu.dwim.common)\n      (ql:quickload :hu.dwim.def/namespace)\n      (ql:quickload :slynk/fancy-inspector)\n      (ql:quickload :slynk/indentation)\n      ;; We might not need this in the future\n      (ql:quickload :adw-charting-vecto)\n      (ql:quickload :osicat)\n      (ql:quickload :colorize)\n      (ql:quickload :clsql)\n      (ql:quickload :zpb-exif)\n\n      (setf dspec:*redefinition-action* :error))\n    (dolist (system systems)\n      (log:info \"Loading: ~s\" system)\n      (ql:quickload system :silent t))\n    #+lispworks\n    (setf dspec:*redefinition-action* :warn)))\n\n(defun maybe-hide-outputs ()\n  (unless (equal \"1\" (uiop:getenv \"TDRHQ_TEST_DEBUG\"))\n    (hide-outputs)))\n\n(defun call-with-main-wrapper (fn)\n  (tmpdir:with-tmpdir (tmpdir)\n    ;; on CCL, the JVM is already loaded before the main systems\n    #+(and lispworks (not jipr) (not eaase-oss))\n    (unless (position \"-no-jvm\" system:*line-arguments-list* :test #'equal)\n      (when (jvm:jvm-supported-p)\n        (jvm:jvm-init)))\n    (funcall fn))\n  (uiop:quit 0))\n\n(defun maybe-profile (fn)\n  #-lispworks\n  (funcall fn)\n  #+lispworks\n  (if (position \"-profile\" system:*line-arguments-list*)\n      (hcl:profile\n       (funcall fn))\n      (funcall fn)))\n\n(defun safely-run-all-tests ()\n  (fiveam:test foo-bar\n    (fiveam:is-true (equal \"foo\" \"foo\")))\n  (if (not (maybe-profile\n            (lambda ()\n             (fiveam:run-all-tests))))\n      (uiop:quit 1)))\n\n(defun main ()\n  (setup-backtrace)\n  (with-global-binding ((*log-sentry-p* nil))\n    (call-with-main-wrapper\n     (lambda ()\n       (maybe-hide-outputs)\n       (safely-run-all-tests)))))\n\n(defun fix-system-name (system)\n  (let ((system (if (str:starts-with-p \":\" system)\n                    (str:upcase (str:substring 1 nil system))\n                    system)))\n    system))\n\n(defun guess-fiveam-suite (system)\n  (let ((system (str:upcase system)))\n    (let ((suite-name\n            (str:replace-all \"BKNR/CLUSTER\" \"BKNR.CLUSTER\"\n                             (str:replace-all \".\" \"/\"\n                                              (if (str:ends-with-p \"/TESTS\" system)\n                                                  (str:replace-all \"/TESTS\" \"\" system)\n                                                  system)))))\n      (let ((guessed (intern suite-name \"KEYWORD\")))\n        (format t \"Guessed suite name as: ~a\" guessed)\n        guessed))))\n\n(defun setup-backtrace ()\n  \"Ensure we're showing the backtrace on error\"\n  (setf fiveam:*on-error* :backtrace))\n\n(defun parse-system-and-suite-args (args)\n  \"Parse command-line arguments into (system suite) pairs.\n   Args can be: system-name [suite-name] system-name2 [suite-name2] ...\n   Returns a list of (system . suite) cons cells.\"\n  (let (result)\n    (loop while args\n          do (let* ((system (pop args))\n                    ;; Check if next arg exists and doesn't start with -\n                    (suite (when (and args (not (eql #\\- (elt (car args) 0))))\n                             (pop args))))\n               (push (cons system suite) result)))\n    (nreverse result)))\n\n(defun test-missing-dep-for (test)\n  (log:info \"Trying out ~a\" test)\n  (restart-case \n      (uiop:run-program\n       (list \"./build/lw-console-8-1-0\"\n             \"-eval\"\n             (format nil \"(progn (ql:quickload :~a) (uiop:quit 0))\" test))\n       :error-output t\n       :output t)\n    (ignore-error-and-continue ()\n      (values))\n    (retry-system ()\n      (test-missing-dep-for test))))\n\n(defun test-missing-deps ()\n  (loop for test in (find-tests)\n        do\n        (test-missing-dep-for test)))\n\n(defun image-main ()\n  \"The main function when called the test-runner is saved to build/t\"\n  (cond\n    #+lispworks\n    ((str:s-member system:*line-arguments-list* \"-list-tests\")\n     (format t \"~s~%\" (find-tests))\n     (uiop:quit 0))\n    #+lispworks\n    ((str:s-member system:*line-arguments-list* \"-test-missing-deps\")\n     (test-missing-deps)\n     (uiop:quit 0))\n    (t\n     (call-with-main-wrapper\n      (lambda ()\n        #+lispworks\n        (let* ((non-flag-args (loop for name in (cdr system:*line-arguments-list*)\n                                    if (not (eql #\\- (elt name 0)))\n                                      collect name))\n               (system-suite-pairs (parse-system-and-suite-args non-flag-args))\n               (systems (mapcar #'car system-suite-pairs)))\n          (setup-backtrace)\n          (format t \"Running tests: ~S~%\" systems)\n          (handler-bind ((error (lambda (e)\n                                  (format t \"Error: ~a\" e)\n                                  (dbg:output-backtrace :verbose)\n                                  (uiop:quit 1))))\n            (progn\n              (dolist (system systems)\n                (ql:quickload (fix-system-name system)))\n              (format t \"Loaded tests~%\" systems)\n              (maybe-hide-outputs)\n              (loop for (system . suite) in system-suite-pairs\n                    for suite-to-run = (if suite\n                                           (intern (str:upcase suite) \"KEYWORD\")\n                                           (guess-fiveam-suite system))\n                    do (format t \"Running suite ~a for system ~a~%\" suite-to-run system)\n                    collect (fiveam:run suite-to-run)\n                      into all-results\n                    finally\n                       (unless (fiveam:explain! (alexandria:flatten all-results))\n                         (uiop:quit 1)))))))))))\n"
  },
  {
    "path": "src/util/.gitattributes",
    "content": "# We're using this as a binary for the purpose of the test.\ntest-file.txt binary"
  },
  {
    "path": "src/util/acceptor.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :util)\n\n(defvar *package-to-acceptors* nil)\n\n(defun %register-acceptor (acceptor-name package)\n  (let ((package (find-package package)))\n    (assert (symbolp acceptor-name))\n    (setf (alexandria:assoc-value *package-to-acceptors* package)\n          acceptor-name)))\n\n(defmacro register-acceptor (acceptor-name package)\n  `(eval-when (:compile-top-level :execute)\n     (%register-acceptor ,acceptor-name ,package)))\n\n(defun package-acceptor-name (&optional (package *package*))\n  (assoc-value *package-to-acceptors* (find-package package)))\n"
  },
  {
    "path": "src/util/asdf.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :util)\n\n(defvar *delivered-image* nil)\n\n(defun system-source-directory (system)\n  (declare (optimize (speed 0) (debug 3)))\n  ;; A version of asdf:system-source-directory that will also work in\n  ;; delivered images.\n  (cond\n    (*delivered-image*\n     (cond\n       ((equal (string :web.all) (str:upcase system))\n        #+lispworks\n        (hcl:get-working-directory))\n       (t\n        (pathname (format nil \"~a/\" (str:downcase system))))))\n    (t\n     (asdf:system-source-directory system))))\n\n(defun %asdf-relpath (path)\n  (make-pathname\n   :name (pathname-name path)\n   :type (pathname-type path)\n   :device nil\n   :host nil\n   :version nil\n   :defaults\n   (util/misc:relpath\n    ;; The actual file may not exist at this point\n    (truename (ensure-directories-exist (cl-fad:pathname-directory-pathname path)))\n    (truename (path:catdir (asdf:system-source-directory :util) \"../../\")))))\n\n(defun relative-system-source-directory (system)\n  (%asdf-relpath\n   (asdf:system-source-directory system)))\n\n(defun relative-output-files (op component)\n  (mapcar #'%asdf-relpath\n          (asdf:output-files op (asdf:find-component component nil))))\n"
  },
  {
    "path": "src/util/atomics.lisp",
    "content": "(defpackage :util/atomics\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:atomic-exchange))\n(in-package :util/atomics)\n\n(defmacro atomic-exchange (place new-val)\n  #+lispworks\n  `(system:atomic-exchange ,place ,new-val)\n  #-lispworks\n  `(let (prev)\n     ;; This is not technically the same as atomic-exchange, but it's\n     ;; close enough for our needs.\n     (atomics:atomic-update ,place\n                            (lambda (old)\n                              (setf prev old)\n                              ,new-val))\n     prev))\n"
  },
  {
    "path": "src/util/benchmark.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/benchmark\n  (:nicknames :benchmark)\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:export #:def-benchmark\n           #:measure\n           #:run\n           #:run-all))\n(in-package :util/benchmark)\n\n(defun clock-ns ()\n  \"Get a time unit in nanoseconds. Might not be accurate all the way\nupto nanoseconds though.\"\n  (*\n   (get-internal-real-time)\n   (floor 1000000000 internal-time-units-per-second)))\n\n(defparameter *min-benchmark-time* (* 4 1000 1000 1000))\n\n(defvar *in-benchmark-p* nil)\n\n(defvar *benchmark-output* *trace-output*)\n\n(defclass measurement ()\n  ((time :initarg :time\n         :reader measurement-ns)))\n\n(defmacro measure (&body body)\n  (let ((iter (gensym \"iter\"))\n        (i (gensym \"i\")))\n    `(call-measure\n      (lambda (,iter)\n        (declare (type fixnum ,iter))\n        (dotimes (,i ,iter)\n          (declare (ignore ,i))\n          ,@body)))))\n\n(defun call-measure (fn)\n  (unless *in-benchmark-p*\n    (warn \"Calling MEASURE outside of a def-benchmark can lead to measurement issues, since the code may not be compiled\"))\n  ;; warmup\n  (funcall fn 1)\n  (let ((start-time (clock-ns))\n        (iterations 0)\n        (next-count 1))\n    (declare (type fixnum iterations next-count\n                   start-time))\n    (loop\n      (funcall fn next-count)\n      (incf iterations next-count)\n      (setf next-count (* 2 next-count))\n      (let ((time-spent (- (clock-ns) start-time)))\n        (when (> time-spent *min-benchmark-time*)\n          (let ((time (ceiling time-spent iterations)))\n            (return\n              (values time (make-instance 'measurement\n                                          :time time)))))))))\n\n(defvar *benchmarks* nil)\n\n(defclass benchmark ()\n  ((name :initarg :name\n         :reader benchmark-name)\n   (impl :initarg :impl\n         :reader benchmark-impl)))\n\n(defmacro def-benchmark (name &body body)\n  `(setf\n    (assoc-value *benchmarks* ',name)\n    (make-instance 'benchmark\n                   :name ',name\n                   :impl (lambda ()\n                           ,@body))))\n\n(defun format-time (ns)\n  (cond\n    ((< ns 5000)\n     (format nil \"~4dns\" ns))\n    ((< ns 1000000)\n     (format nil \"~7,2fus\" (/ ns 1000.0)))\n    ((< ns 1000000000)\n     (format nil \"~7,2fms\" (/ ns 1000000.0)))\n    (t\n     (format nil \"~7,2fs \" (/ ns 1000000000.0)))))\n\n(defmethod print-result ((self benchmark) measurement)\n  (format *benchmark-output*\n          \"~40a ~a~%\" (benchmark-name self)\n          (format-time (measurement-ns measurement)))\n  (format *benchmark-output*\n          \"===============================================~%\"))\n\n(defmethod run ((self benchmark))\n  (let ((*in-benchmark-p* t))\n    (multiple-value-bind (time measurement)\n        (funcall (benchmark-impl self))\n      (print-result self measurement)\n      (values time measurement))))\n\n(defmethod run ((name symbol))\n  (let ((benchmark (assoc-value *benchmarks* name)))\n    (unless benchmark\n      (error \"could not find benchmark named: ~a\" name))\n    (run\n     benchmark)))\n\n(defun run-all ()\n  (loop for (nil . benchmark) in *benchmarks*\n        do (run benchmark)))\n"
  },
  {
    "path": "src/util/bind-form.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/bind-form\n  (:use #:cl\n\t#:alexandria)\n  (:export #:bind-form))\n(in-package :util/bind-form)\n\n(markup:deftag bind-form (children &key data name)\n  (declare (debug 3))\n  (setf children (remove-if 'stringp children))\n\n  (unless data\n    (return-from bind-form (markup:make-merge-tag children)))\n\n  (loop for x being the hash-keys of data do\n       (unless (stringp x)\n         (error \"expected string hash keys got ~S\" x)))\n  (markup:make-merge-tag\n   (markup:walk children\n                (lambda (xml-tag)\n                  (cond\n                    ((not xml-tag) (error \"shouldn't have seen nil here\"))\n                    ((equal :input (markup:xml-tag-name xml-tag))\n                     (symbol-macrolet ((name-attr  (assoc-value (markup:xml-tag-attributes xml-tag) \"name\" :test 'equal))\n                                       (value-attr (assoc-value (markup:xml-tag-attributes xml-tag) \"value\" :test 'equal)))\n\n\n                       (multiple-value-bind (res parts)\n                           (cl-ppcre:scan-to-strings \"^(.*)[{](.*)[}]$\" name-attr)\n                         (cond\n                           ((and res\n                                 (equal name (aref parts 0)))\n                            (let ((new-val (gethash (aref parts 1) data)))\n                              (setf value-attr new-val))\n))))\n                     xml-tag)\n                    (t\n                     xml-tag))))))\n"
  },
  {
    "path": "src/util/bknr-slynk.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/bknr-slynk\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:slynk\n                #:read-from-minibuffer-in-emacs\n                #:do-checklist\n                #:all-slots-for-inspector)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/bknr-slynk)\n\n#+broken\n(defun query-and-set-slot (class object slot)\n  (let* ((slot-name (slynk-mop:slot-definition-name slot))\n         (value-string (read-from-minibuffer-in-emacs\n                        (format nil \"Set slot ~S to (evaluated) : \"\n                                slot-name))))\n    (when (and value-string (not (string= value-string \"\")))\n      (with-simple-restart (abort \"Abort setting slot ~S\" slot-name)\n        (bknr.datastore:with-transaction ()\n         (setf (slynk-mop:slot-value-using-class class object slot)\n               (eval (read-from-string value-string))))))))\n\n#+broken\n(defmethod all-slots-for-inspector ((object store-object))\n  (let* ((ret (call-next-method))\n         (class (class-of object))\n         (effective-slots (slynk-mop:class-slots class)))\n    (append\n     ret\n     `((:action \"[set bknr value]\"\n                ,(lambda ()\n                   (do-checklist (idx checklist)\n                     (query-and-set-slot class object\n                                         (nth idx effective-slots)))))))))\n"
  },
  {
    "path": "src/util/cdn.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:util.cdn\n  (:use #:cl )\n  (:export\n   :make-cdn\n   :*cdn-domain*\n   :update-key\n   :*cdn-cache-key*))\n(in-package #:util.cdn)\n\n;; to invalidate the cache reload\n\n(progn\n  (defvar *cdn-cache-key* nil)\n  (defun update-key ()\n    (setf *cdn-cache-key* (secure-random:number 1000000000)))\n  (unless *cdn-cache-key*\n   (update-key)))\n\n(defvar *cdn-domain* nil)\n\n(defun make-cdn (href)\n  (cond\n    ((and *cdn-domain*\n          (str:starts-with? \"/\" href)\n          (not (str:starts-with? \"//\" href)))\n     (hex:add-get-param-to-url\n      (format nil \"~a~a\" *cdn-domain* href)\n      \"cache-key\"\n      (format nil \"~a\" *cdn-cache-key*)))\n    (t\n     (hex:add-get-param-to-url\n      href\n      \"cache-key\"\n      (format nil \"~a\" *cdn-cache-key*)))))\n"
  },
  {
    "path": "src/util/clsql/clsql.lisp",
    "content": "(defpackage :util/clsql/clsql\n  (:use #:cl)\n  #+lispworks\n  (:import-from #:deliver-utils/common\n                #:guess-root)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/clsql/clsql)\n\n(setf clsql:*default-caching* nil)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n\n  (defun update-search-path ()\n    (flet ((%push (path)\n             (pushnew path\n                      clsql:*foreign-library-search-paths*\n                      :test #'equal)))\n      (%push\n       (cond\n         #+lispworks\n         ((hcl:delivered-image-p)\n          (path:catdir (guess-root) \"util/clsql/clsql/\"))\n         (t\n          (asdf:system-relative-pathname\n           :util/clsql\n           \"clsql/\"))))\n\n      ;; Only for Homebrew on Mac. Technically only for ARM64.\n      (%push #p\"/opt/homebrew/opt/mysql-client/lib/\")))\n\n  (update-search-path))\n\n#-(or screenshotbot-oss windows)\n(eval-when (:compile-toplevel)\n  (asdf:compile-system :clsql-mysql)\n  (asdf:compile-system :clsql-sqlite3))\n\n#+lispworks\n(unless (hcl:delivered-image-p)\n  (lw:define-action \"Delivery Actions\" \"Make immutable systems\"\n    (lambda ()\n      (asdf:load-system :clsql-mysql)\n      (asdf:register-immutable-system :clsql-mysql))))\n\n#+lispworks\n(lw:define-action \"When starting image\" \"Update search paths for mysql\"\n  #'update-search-path)\n"
  },
  {
    "path": "src/util/cookies.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/cookies\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:cookies\n   #:get-cookie\n   #:set-cookie))\n(in-package :util/cookies)\n\n(defclass cookies ()\n  ((request :initform hunchentoot:*request* ;; todo: does it make sense to inject?\n            :initarg :request\n            :reader request)\n   (reply :initform hunchentoot:*reply*\n          :initarg :reply\n          :reader reply)\n   (host :initform (hunchentoot:host)\n         :initarg :host\n         :reader host)\n   (proto :initform (hunchentoot:header-in* :x-forwarded-proto)\n          :initarg :proto\n          :reader proto)))\n\n(defun host-without-port (self)\n  (car (str:split \":\" (host self))))\n\n(defmethod get-cookie ((self cookies) name)\n  (hunchentoot:cookie-in name (request self)))\n\n(defmethod set-cookie ((self cookies) name value &key)\n  (let ((domain (host-without-port self)))\n    (hunchentoot:set-cookie\n     name :value value :domain domain :expires (+ (get-universal-time) 1000000)\n          :path \"/\" :secure (string=\n                             \"https\"\n                             (proto self))\n          :reply (reply self))))\n"
  },
  {
    "path": "src/util/copy-file.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/copy-file\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:copy-file-fast)\n  #-lispworks\n  (:local-nicknames (#:fli #:util/fake-fli)))\n(in-package :util/copy-file)\n\n(fli:define-foreign-function link\n    ((src (:reference-pass :ef-mb-string))\n     (dest (:reference-pass :ef-mb-string)))\n  :result-type :int)\n\n(defparameter +exdev+ 18\n  \"A hard link to another file system... this constant is correct at\n least on Linux and Darwin.\")\n\n(defun copy-file-fast (src dest)\n  \"Copies the file from src to dest, if it's possible to use a hard link\n then it will do so.\"\n  #+windows\n  (uiop:copy-file src dest)\n  #-windows\n  (let ((ret (link (namestring src) (namestring dest))))\n    (unless (= ret 0)\n      (let* ((errno\n               (util/posix:errno))\n             (message (util/posix:get-errno-string errno)))\n        (cond\n          ((= errno +exdev+)\n           (log:warn \"Could not hard link from ~a to ~a\" src dest)\n           (warn \"Could not create hard link, falling back to manual copy, reason: ~a\"\n                 message)\n           (uiop:copy-file src dest))\n          (t\n           (error \"Could not create hard link: ~a\" message)))))))\n"
  },
  {
    "path": "src/util/copying.lisp",
    "content": "(uiop:define-package :util/copying\n  (:use #:cl\n        #:alexandria)\n  (:export\n   #:copying))\n(in-package :util/copying)\n\n\n(defmacro copying ((&rest bindings) &body body)\n  \"Creates a closure copy of each of the bindings. LOOP/ITERATE/DOLIST\n  don't create a new local copy of the bindings, so this is useful inside loops. e.g.\n\n  (loop for x in (list ...)\n        collect\n           (copying (x)\n             (lambda ()\n               ;; ... do things with x ...)))\n\"\n  `(let ,(loop for var in bindings\n               collect `(,var ,var))\n     ,@body))\n"
  },
  {
    "path": "src/util/countries.csv",
    "content": "id,value\nAF,Afghanistan\nAX,\"Åland Islands\"\nAL,Albania\nDZ,Algeria\nAS,\"American Samoa\"\nAD,Andorra\nAO,Angola\nAI,Anguilla\nAQ,Antarctica\nAG,\"Antigua & Barbuda\"\nAR,Argentina\nAM,Armenia\nAW,Aruba\nAC,\"Ascension Island\"\nAU,Australia\nAT,Austria\nAZ,Azerbaijan\nBS,Bahamas\nBH,Bahrain\nBD,Bangladesh\nBB,Barbados\nBY,Belarus\nBE,Belgium\nBZ,Belize\nBJ,Benin\nBM,Bermuda\nBT,Bhutan\nBO,Bolivia\nBA,\"Bosnia & Herzegovina\"\nBW,Botswana\nBR,Brazil\nIO,\"British Indian Ocean Territory\"\nVG,\"British Virgin Islands\"\nBN,Brunei\nBG,Bulgaria\nBF,\"Burkina Faso\"\nBI,Burundi\nKH,Cambodia\nCM,Cameroon\nCA,Canada\nIC,\"Canary Islands\"\nCV,\"Cape Verde\"\nBQ,\"Caribbean Netherlands\"\nKY,\"Cayman Islands\"\nCF,\"Central African Republic\"\nEA,\"Ceuta & Melilla\"\nTD,Chad\nCL,Chile\nCN,China\nCX,\"Christmas Island\"\nCC,\"Cocos (Keeling) Islands\"\nCO,Colombia\nKM,Comoros\nCG,\"Congo - Brazzaville\"\nCD,\"Congo - Kinshasa\"\nCK,\"Cook Islands\"\nCR,\"Costa Rica\"\nCI,\"Côte d’Ivoire\"\nHR,Croatia\nCU,Cuba\nCW,Curaçao\nCY,Cyprus\nCZ,Czechia\nDK,Denmark\nDG,\"Diego Garcia\"\nDJ,Djibouti\nDM,Dominica\nDO,\"Dominican Republic\"\nEC,Ecuador\nEG,Egypt\nSV,\"El Salvador\"\nGQ,\"Equatorial Guinea\"\nER,Eritrea\nEE,Estonia\nSZ,Eswatini\nET,Ethiopia\nFK,\"Falkland Islands\"\nFO,\"Faroe Islands\"\nFJ,Fiji\nFI,Finland\nFR,France\nGF,\"French Guiana\"\nPF,\"French Polynesia\"\nTF,\"French Southern Territories\"\nGA,Gabon\nGM,Gambia\nGE,Georgia\nDE,Germany\nGH,Ghana\nGI,Gibraltar\nGR,Greece\nGL,Greenland\nGD,Grenada\nGP,Guadeloupe\nGU,Guam\nGT,Guatemala\nGG,Guernsey\nGN,Guinea\nGW,Guinea-Bissau\nGY,Guyana\nHT,Haiti\nHN,Honduras\nHK,\"Hong Kong SAR China\"\nHU,Hungary\nIS,Iceland\nIN,India\nID,Indonesia\nIR,Iran\nIQ,Iraq\nIE,Ireland\nIM,\"Isle of Man\"\nIL,Israel\nIT,Italy\nJM,Jamaica\nJP,Japan\nJE,Jersey\nJO,Jordan\nKZ,Kazakhstan\nKE,Kenya\nKI,Kiribati\nXK,Kosovo\nKW,Kuwait\nKG,Kyrgyzstan\nLA,Laos\nLV,Latvia\nLB,Lebanon\nLS,Lesotho\nLR,Liberia\nLY,Libya\nLI,Liechtenstein\nLT,Lithuania\nLU,Luxembourg\nMO,\"Macao SAR China\"\nMG,Madagascar\nMW,Malawi\nMY,Malaysia\nMV,Maldives\nML,Mali\nMT,Malta\nMH,\"Marshall Islands\"\nMQ,Martinique\nMR,Mauritania\nMU,Mauritius\nYT,Mayotte\nMX,Mexico\nFM,Micronesia\nMD,Moldova\nMC,Monaco\nMN,Mongolia\nME,Montenegro\nMS,Montserrat\nMA,Morocco\nMZ,Mozambique\nMM,\"Myanmar (Burma)\"\nNA,Namibia\nNR,Nauru\nNP,Nepal\nNL,Netherlands\nNC,\"New Caledonia\"\nNZ,\"New Zealand\"\nNI,Nicaragua\nNE,Niger\nNG,Nigeria\nNU,Niue\nNF,\"Norfolk Island\"\nKP,\"North Korea\"\nMK,\"North Macedonia\"\nMP,\"Northern Mariana Islands\"\nNO,Norway\nOM,Oman\nPK,Pakistan\nPW,Palau\nPS,\"Palestinian Territories\"\nPA,Panama\nPG,\"Papua New Guinea\"\nPY,Paraguay\nPE,Peru\nPH,Philippines\nPN,\"Pitcairn Islands\"\nPL,Poland\nPT,Portugal\nXA,Pseudo-Accents\nXB,Pseudo-Bidi\nPR,\"Puerto Rico\"\nQA,Qatar\nRE,Réunion\nRO,Romania\nRU,Russia\nRW,Rwanda\nWS,Samoa\nSM,\"San Marino\"\nST,\"São Tomé & Príncipe\"\nSA,\"Saudi Arabia\"\nSN,Senegal\nRS,Serbia\nSC,Seychelles\nSL,\"Sierra Leone\"\nSG,Singapore\nSX,\"Sint Maarten\"\nSK,Slovakia\nSI,Slovenia\nSB,\"Solomon Islands\"\nSO,Somalia\nZA,\"South Africa\"\nGS,\"South Georgia & South Sandwich Islands\"\nKR,\"South Korea\"\nSS,\"South Sudan\"\nES,Spain\nLK,\"Sri Lanka\"\nBL,\"St. Barthélemy\"\nSH,\"St. Helena\"\nKN,\"St. Kitts & Nevis\"\nLC,\"St. Lucia\"\nMF,\"St. Martin\"\nPM,\"St. Pierre & Miquelon\"\nVC,\"St. Vincent & Grenadines\"\nSD,Sudan\nSR,Suriname\nSJ,\"Svalbard & Jan Mayen\"\nSE,Sweden\nCH,Switzerland\nSY,Syria\nTW,Taiwan\nTJ,Tajikistan\nTZ,Tanzania\nTH,Thailand\nTL,Timor-Leste\nTG,Togo\nTK,Tokelau\nTO,Tonga\nTT,\"Trinidad & Tobago\"\nTA,\"Tristan da Cunha\"\nTN,Tunisia\nTR,Turkey\nTM,Turkmenistan\nTC,\"Turks & Caicos Islands\"\nTV,Tuvalu\nUM,\"U.S. Outlying Islands\"\nVI,\"U.S. Virgin Islands\"\nUG,Uganda\nUA,Ukraine\nAE,\"United Arab Emirates\"\nGB,\"United Kingdom\"\nUS,\"United States\"\nUY,Uruguay\nUZ,Uzbekistan\nVU,Vanuatu\nVA,\"Vatican City\"\nVE,Venezuela\nVN,Vietnam\nWF,\"Wallis & Futuna\"\nEH,\"Western Sahara\"\nYE,Yemen\nZM,Zambia\nZW,Zimbabwe\n"
  },
  {
    "path": "src/util/countries.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :util)\n\n(defun load-countries ()\n  (let* ((csv (path:catfile (util:system-source-directory :util) \"./countries.csv\"))\n         (values (cl-csv:read-csv csv)))\n    (loop for val in (cdr values)\n       collect (cadr val))))\n\n(defun load-states ()\n  (let* ((csv (path:catfile (util:system-source-directory :util) \"./states.csv\"))\n         (values (cl-csv:read-csv csv)))\n    (loop for value in (cdr values)\n         collect\n         (format nil \"~A ~A\" (cadr value) (car value)))))\n"
  },
  {
    "path": "src/util/cron.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/cron\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:import-from #:util/threading\n                #:with-tags\n                #:ignore-and-log-errors)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:def-cron\n   #:cron-enabled-on-store-p))\n(in-package :util/cron)\n\n#+nil\n(defun funcall-with-wrapper (body)\n  (restart-case\n      (handler-bind ((error (lambda (e)\n                              (cond\n                                (*debugger-hook*\n                                 (invoke-debugger e))\n                                (t\n                                 (invoke-restart 'cl:abort))))))\n        (funcall body))\n    (cl:abort ()\n      (format t \"abort called~%\")\n      (values))))\n\n(defun call-with-cron-wrapper (fn)\n  (ignore-and-log-errors ()\n    (with-tags ((\"hostname\" (uiop:hostname)))\n      (util/threading:call-with-thread-fixes fn))))\n\n(defmethod cron-enabled-on-store-p (store)\n  t)\n\n(defmacro def-cron (name (&rest args &key (only-on-leader t) &allow-other-keys) &body body)\n  \"If ONLY-ON-LEADER is true then we only run this cron job on the leader.\"\n  (let ((args (remove-from-plist args :only-on-leader)))\n   `(cl-cron:make-cron-job\n     (lambda ()\n       (when (and\n              (boundp 'bknr.datastore:*store*)\n              (or\n               (not ,only-on-leader)\n               (cron-enabled-on-store-p bknr.datastore:*store*)))\n         (flet ((body () ,@body))\n           (call-with-cron-wrapper #'body))))\n     :hash-key ',name\n     ,@args)))\n\n#+lispworks\n(def-cron gc (:minute 14 :hour 6)\n  (hcl:gc-generation t))\n"
  },
  {
    "path": "src/util/debugger-hook.lisp",
    "content": "(defpackage :util/debugger-hook\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:log4cl-debugger-hook))\n(in-package :util/debugger-hook)\n\n(defun log4cl-debugger-hook (condition debugger-hook)\n  (declare (ignore debugger-hook))\n  (handler-case\n      (let ((output (with-output-to-string (out)\n                      (trivial-backtrace:print-condition\n                       condition\n                       out)\n                      #+lispworks\n                      (dbg:output-backtrace\n                       :brief\n                       :stream out))))\n        (log:error \"~a\" output))\n    (error (e)\n      (format t \"FATAL: log4cl-debugger-hook failed so we can't log crashes, DIE DIE DIE! Please report to arnold@screenshotbot.io: ~a\" e)\n      (uiop:quit 1))))\n\n#+nil\n(log4cl-debugger-hook (make-condition 'error)\n                      #'log4cl-debugger-hook)\n"
  },
  {
    "path": "src/util/digest.c",
    "content": "#include <openssl/sha.h>\n#include <openssl/md5.h>\n#include <stdlib.h>\n#include <string.h>\n\nint tdrhq_sizeof_SHA256_CTX() {\n        return sizeof(SHA256_CTX);\n}\n\nint tdrhq_sizeof_MD5_CTX() {\n        return sizeof(MD5_CTX);\n}\n"
  },
  {
    "path": "src/util/digests-non-lw.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n;; for non Lispworks only\n\n(defpackage :util/digests\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:sha256-file\n   #:md5-file))\n(in-package :util/digests)\n\n(defun sha256-file (file)\n  (ironclad:digest-file :sha256 file))\n\n(defun md5-file (file)\n  (md5:md5sum-file file))\n"
  },
  {
    "path": "src/util/digests.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/digests\n  (:use #:cl)\n  (:import-from #:util/health-check\n                #:def-health-check)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:sha256-file\n   #:md5-file))\n(in-package :util/digests)\n\n(fli:define-c-struct evp-md-ctx)\n(fli:define-c-struct evp-md)\n\n(defun crypto-module ()\n  \"On Mac, it appears there's a separate comm::crypto-library. Not sure\nif there's a situation where we need to reference that module\ndirectly.\"\n  #-mswindows\n  'comm::openssl-library\n  #+mswindows\n  'comm::crypto-library)\n\n(fli:define-foreign-function (evp-md-ctx-new \"EVP_MD_CTX_new\")\n    ()\n  :module (crypto-module)\n  :result-type (:pointer evp-md-ctx))\n\n\n(fli:define-foreign-function (evp-md-ctx-free \"EVP_MD_CTX_free\")\n    ((ctx (:pointer evp-md-ctx)))\n  :module (crypto-module)\n  :result-type :void)\n\n(def-easy-macro with-evp-md-ctx (&binding ctx &fn fn)\n  (let ((ctx (evp-md-ctx-new)))\n    (unwind-protect\n         (funcall fn ctx)\n      (evp-md-ctx-free ctx))))\n\n(fli:define-foreign-function (evp-get-digestbyname \"EVP_get_digestbyname\")\n    ((name (:reference :ef-mb-string)))\n  :module (crypto-module)\n  :result-type (:pointer evp-md))\n\n(fli:define-foreign-function (digest-init \"EVP_DigestInit_ex\")\n    ((ctx (:pointer evp-md-ctx))\n     (evp-md (:pointer evp-md))\n     (engine :pointer))\n  :module (crypto-module)\n  :result-type :int)\n\n(fli:define-foreign-function (digest-update \"EVP_DigestUpdate\")\n    ((ctx (:pointer evp-md-ctx))\n     (data :lisp-simple-1d-array)\n     (len :size-t))\n  :module (crypto-module)\n  :result-type :int)\n\n(fli:define-foreign-function (digest-final \"EVP_DigestFinal_ex\")\n    ((ctx (:pointer evp-md-ctx))\n     (md :lisp-simple-1d-array)\n     (size (:reference-return (:unsigned :int))))\n  :module (crypto-module)\n  :result-type :int)\n\n(defun digest-file (file &key digest-name\n                           digest-length #| EVP_MD_get_size is hard to port. See variations in OpenSSL 1.1, 3.0, and LibreSSL. |#)\n  (comm:ensure-ssl :implementation :openssl)\n  (let ((evp-md (evp-get-digestbyname digest-name)))\n    (with-evp-md-ctx (ctx)\n      (digest-init ctx evp-md nil)\n      (let* ((buf-size (* 16 1024))\n             (buffer (make-array buf-size :element-type '(unsigned-byte 8)\n                                          :allocation :pinnable))\n             (result (make-array digest-length :element-type '(unsigned-byte 8)\n                                               :initial-element 0\n                                               :allocation :pinnable)))\n        (with-open-file (stream file :direction :input\n                                     :element-type '(unsigned-byte 8))\n          (loop named inner\n                for bytes = (read-sequence buffer stream)\n                while t\n                do\n                   (if (= bytes 0)\n                       (return-from inner))\n                   (unless (= 1\n                              (digest-update ctx\n                                             buffer\n                                             bytes))\n                     (error \"Could not update digest\"))))\n        (multiple-value-bind (result size)\n            (digest-final ctx result nil)\n          (unless (= 1 result)\n            (error \"Could not finalize SHA digest\"))\n          (unless (eql digest-length size)\n            (error \"Digest length mismatch, this could mean corruption in memory\")))\n        result))))\n\n\n(defun sha256-file (file)\n  (digest-file file\n               :digest-name \"sha256\"\n               :digest-length 32))\n\n(defun md5-file (file)\n  (digest-file file\n               :digest-name \"md5\"\n               :digest-length 16))\n\n(def-health-check verify-libcrypto-digests ()\n  (uiop:with-temporary-file (:pathname p :stream s)\n    (write-string \"foobar\" s)\n    (finish-output s)\n    (flet ((ensure (expected actual)\n             (unless (equalp expected actual)\n               (error \"Expected digest of ~a, got ~a\"\n                      expected actual))))\n      (ensure #(195 171 143 241 55 32 232 173 144 71 221 57 70\n                107 60 137 116 229 146 194 250 56 61 74 57 96\n                113 76 174 240 196 242)\n        (sha256-file p))\n      (ensure #(56 88 246 34 48 172 60 145 95 48 12 102 67 18 198 63)\n        (md5-file p)))))\n"
  },
  {
    "path": "src/util/disk-size.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/disk-size\n  (:use #:cl)\n  (:import-from #:util/sizeof\n                #:sizeof\n                #:def-uint-type)\n  (:local-nicknames #-lispworks\n                    (:fli #:util/fake-fli)))\n(in-package :util/disk-size)\n\n#+linux\n(def-uint-type fsblkcnt-t\n  \"fsblkcnt_t\"\n  :imports (\"sys/statvfs.h\"))\n\n#+linux\n(def-uint-type fsfilcnt-t\n  \"fsfilcnt_t\"\n  :imports (\"sys/statvfs.h\"))\n\n#+linux\n(fli:define-c-struct statvfs\n    (bsize :unsigned-long)\n  (frsize :unsigned-long)\n  (blocks fsblkcnt-t)\n  (bfree fsblkcnt-t)\n  (bavail fsblkcnt-t)\n  (files fsfilcnt-t)\n  (ffree fsfilcnt-t)\n  (favail fsfilcnt-t)\n\n  ;; There's more... but we need only until this much. Allocate using\n  ;; sizeof!\n  )\n\n#+linux\n(defconstant +statvfs-size+\n  #. (+ 8 ;; extra buf, why not\n        (sizeof \"struct statvfs\" :imports '(\"sys/statvfs.h\"))))\n\n\n#+linux\n(fli:define-foreign-function (statvfs \"statvfs\")\n    ((path (:reference-pass :ef-mb-string))\n     (buf (:pointer statvfs)))\n  :result-type :int)\n\n(defun free-space (pathname)\n  #-linux\n  10\n  #+linux\n  (fli:with-dynamic-foreign-objects ((output :char :nelems +statvfs-size+))\n    (fli:with-coerced-pointer (output :type 'statvfs) output\n      (statvfs (namestring pathname) output)\n      (* (fli:foreign-slot-value output\n                                 'bavail)\n         (fli:foreign-slot-value output\n                                 'bsize)))))\n"
  },
  {
    "path": "src/util/download-file.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :util)\n\n(defun download-file (url pathname)\n  (multiple-value-bind (input resp args) (drakma:http-request url\n                                                              :force-binary t\n                                                              :want-stream t)\n    (unless (eql 200 resp)\n      (error \"bad code\"))\n    (with-open-file (output pathname :direction :output :if-exists :supersede :if-does-not-exist :create\n                            :element-type '(unsigned-byte 8))\n      (fad:copy-stream  input output))))\n"
  },
  {
    "path": "src/util/emacs.lisp",
    "content": "(defpackage :util/emacs\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:list-packages-for-symbol))\n(in-package :util/emacs)\n\n(defparameter *overrides*\n  `((:markup/markup . :markup)))\n\n(defun override-if-needed (package-name )\n  (loop for (from . to) in *overrides*\n        if (string= from package-name)\n          do (return (string to))\n        finally\n        (return package-name)))\n\n(defun list-packages-for-symbol (name current-package)\n  (let ((current-package (if (str:starts-with-p \":\" current-package)\n                             (str:substring 1 nil current-package)\n                             current-package)))\n    (log:info \"Got package : ~s\" current-package)\n    (mapcar\n     #'override-if-needed\n     (mapcar\n      #'package-name\n        (sort\n         (let ((bad-packages (mapcar #'find-package\n                                     `(:keyword :common-lisp-user))))\n          (loop for package in (list-all-packages)\n                if (and\n                    (not (member package bad-packages))\n                    (unless (string-equal current-package (package-name package))\n                      (a:when-let (sym (find-symbol (string-upcase name)\n                                                    package))\n                          (eql package (symbol-package sym)))))\n                    collect package))\n       #'> :key (lambda (package)\n                  (let ((package-name (package-name package)))\n                    (loop for i from 0 below (length current-package)\n                          while (and (< i (length package-name))\n                                     (char-equal (elt current-package i)\n                                                 (elt package-name i)))\n                          finally\n                             (progn\n                               (log:info \"For ~s, got ~d\" package i)\n                               (return i))))))))))\n"
  },
  {
    "path": "src/util/engines.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/engines\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request-impl))\n(in-package :util/engines)\n\n(defconstant +empty-headers+ nil)\n\n(defclass handle-misbehaving-engine ()\n  ()\n  (:documentation \"An engine that handles a misbehaving server, and responds with a 502\ninstead.\"))\n\n(defmethod http-request-impl ((self handle-misbehaving-engine)\n                              url &rest args)\n  (flet ((respond-500 (e)\n           (log:warn \"Faking 500 response code for ~a because of ~a\" url e)\n           (return-from http-request-impl\n             (values\n              (flexi-streams:make-in-memory-input-stream\n               (flexi-streams:string-to-octets\n                \"<html><body>404 Not found (generated by Screenshotbot)</body></html>\"))\n              500\n              +empty-headers+))))\n    (handler-bind ((drakma::drakma-simple-error #'respond-500)\n                   (simple-error\n                     (lambda (e)\n                       (when (equal \"maybe invalid header\"\n                                    (format nil \"~a\" e))\n                         (respond-500 e)))))\n      (call-next-method))))\n"
  },
  {
    "path": "src/util/events.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/events\n  (:use #:cl)\n  (:nicknames :screenshotbot/events)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:clsql-sys\n                #:sql-ident\n                #:*sql-stream*)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/misc/lists\n                #:with-batches)\n  (:import-from #:util/threading\n                #:ignore-and-log-errors)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:import-from #:util/misc\n                #:?.)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:event-engine\n   #:with-tracing\n   #:delete-old-data\n   #:db-engine\n   #:push-counter-event))\n(in-package :util/events)\n\n(defvar *events* nil)\n\n(defun safe-installation ()\n  ;; We want the event code to be as fixtureless as possible to all\n  ;; callers, so we handle unbound installations right here\n  (when (boundp '*installation*)\n    *installation*))\n\n(defgeneric event-engine (installation)\n  (:method (any)\n    nil))\n\n(defclass db-engine ()\n  ((connection-spec :initarg :connection-spec\n                    :reader connection-spec)\n   (database-type :initarg :database-type\n                  :reader database-type)))\n\n\n(defmethod push-event-impl (engine name &rest args)\n  nil)\n\n(defmethod delete-old-data (engine)\n  nil)\n\n(defclass event ()\n  ((name :initarg :name\n         :reader event-name)\n   (extras :initarg :extras\n           :initform nil\n           :reader event-extras)\n   (created-at\n    :accessor created-at\n    :initform (local-time:now))))\n\n(def-easy-macro with-db (&binding db engine &fn fn)\n  (let ((db (clsql:connect (connection-spec engine)\n                           :database-type (database-type engine)\n                           :make-default nil\n                           :if-exists :new\n                           :pool nil\n                           :make-default nil)))\n    (unwind-protect\n         (funcall fn db)\n      (clsql:disconnect :database db))))\n\n(defclass sql-value-list ()\n  ((values :initarg :values\n           :reader %values)))\n\n(defmethod clsql-sys::output-sql ((vl sql-value-list) database)\n  (loop for val in (%values vl)\n        for i from 0\n        if (/= i 0)\n          do\n             (format *sql-stream* \",\")\n        do\n           (clsql-sys::output-sql val database)))\n\n(defun format-ts (ts)\n  (local-time:format-timestring\n   nil ts :format\n   '((:year 4) \"-\"\n     (:month  2) \"-\"\n     (:day  2) \" \"\n     (:hour 2) \":\"\n     (:min  2) \":\"\n     (:sec  2) \".\"\n     (:msec 3))\n   :timezone local-time:+utc-zone+))\n\n(defun insert-multiple-items (db table events columns row-builder)\n  (when events\n    (clsql:insert-records\n     :into table\n     :attributes (loop for name in columns\n                       collect\n                       (make-instance 'sql-ident :name name))\n     :values (make-instance\n              'sql-value-list\n              :values\n              (loop for ev in events\n                    collect\n                    (loop for item in (funcall row-builder ev)\n                          if (typep item 'local-time:timestamp)\n                            collect (format-ts item)\n                          else\n                            collect item)))\n     :database db)))\n\n(defun insert-events (events db)\n  (let ((hostname\n          (uiop:hostname))\n        (domain\n          (?. installation-domain (safe-installation))))\n   (insert-multiple-items db\n                          \"event\"\n                          events\n                          '(\"name\" \"extras\" \"created_at\" \"domain\" \"hostname\")\n                          (lambda (ev)\n                            (list\n                             (event-name ev)\n                             (event-extras ev)\n                             (created-at ev)\n                             domain\n                             hostname)))))\n\n(defmethod push-event-impl ((engine db-engine) name &rest args)\n  (let ((ev (make-instance 'event\n                           :name (str:downcase name)\n                           :extras\n                           (json:encode-json-plist-to-string\n                            args))))\n    (atomics:atomic-push\n     ev *events*)))\n\n(defmethod delete-old-data ((engine db-engine))\n  (with-db (db engine)\n    (clsql:execute-command\n     \"delete from event where created_at < date_sub(now(), interval 1 month) \"\n     :database db)))\n\n(defmethod flush-events (engine)\n  (values))\n\n(defmethod flush-events ((engine db-engine))\n  (when *events*\n    (let ((events (util/atomics:atomic-exchange\n                   *events* nil)))\n      ;; if the connection fails, then we'll drop the events and that's okay\n      (with-db (db engine)\n        (with-batches (events events :batch-size 1000)\n          (insert-events events db))))))\n\n(defmethod push-event (name &rest args)\n  (apply #'push-event-impl (event-engine (safe-installation)) name args))\n\n(def-easy-macro with-event (name &rest args &fn fn)\n  (flet ((push-type (type)\n           (apply #'push-event (intern (format nil \"~a.~a\" name type) \"KEYWORD\")\n                  args)))\n   (handler-bind ((error (lambda (e)\n                           (declare (ignore e))\n                           (push-type \"FAILURE\"))))\n     (let ((ret (funcall fn)))\n       (push-type \"SUCCESS\")\n       ret))))\n\n\n(def-cron flush-events (:step-min 2 :only-on-leader nil)\n  (ignore-and-log-errors ()\n   (flush-events (event-engine (safe-installation)))))\n\n;; (push-event :test-sdf)\n\n(def-easy-macro with-tracing (name &rest extra-args &fn fn)\n  (let ((start-time (local-time:now)))\n    (unwind-protect\n         (funcall fn)\n      (apply #'push-event :trace :name (string name)\n                                 :time (local-time:timestamp-difference\n                                        (local-time:now) start-time)\n                                 extra-args))))\n\n(defstruct counter-event\n  name value)\n\n(defvar *counter-events* nil)\n\n(defun push-counter-event (name &key (value 1))\n  (atomics:atomic-push\n   (make-counter-event :name name :value value)\n   *counter-events*))\n\n(defun flush-counter-events ()\n  (let ((counter-events (util/atomics:atomic-exchange *counter-events* nil))\n        (counters (make-hash-table :test #'equal)))\n    (loop for event in counter-events do\n      (incf (gethash (counter-event-name event) counters 0)\n            (counter-event-value event)))\n\n    (loop for name being the hash-keys of counters\n            using (hash-value v)\n          do\n             (push-event name :value v))))\n\n(def-cron flush-counter-events (:step-min 5)\n  (flush-counter-events))\n\n(def-cron delete-old-data (:minute 0 :hour 7)\n  (delete-old-data (event-engine (safe-installation))))\n"
  },
  {
    "path": "src/util/fake-clingon.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/fake-clingon\n  (:use #:cl)\n  (:import-from #:clingon\n                #:option-key)\n  (:export\n   #:make-fake-clingon))\n(in-package :util/fake-clingon)\n\n(defclass fake-clingon ()\n  ((options :initarg :options\n            :reader options)\n   (args :initarg :args\n         :reader args)))\n\n(defun make-fake-clingon (options &rest args &key &allow-other-keys)\n  (make-instance 'fake-clingon\n                 :options options\n                 :args args))\n\n(defmethod clingon:getopt ((cmd fake-clingon) arg &optional default)\n  (loop for opt in (options cmd)\n        if (eql (option-key opt) arg)\n          do (return)\n        finally\n           (error \"~a is not a valid option\" arg))\n  (or\n   (getf (args cmd) arg)\n   default))\n"
  },
  {
    "path": "src/util/fake-fli.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/fake-fli\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:define-c-typedef\n   #:register-module\n   #:define-c-struct\n   #:define-foreign-function\n   #:define-c-enum\n   #:convert-from-foreign-string\n   #:null-pointer-p\n   #:with-dynamic-foreign-objects\n   #:foreign-slot-value\n   #:incf-pointer\n   #:malloc\n   #:free\n   #:dereference\n   #:with-coerced-pointer\n   #:*null-pointer*\n   #:make-pointer\n   #:copy-pointer\n   #:pointer-address\n   #:define-foreign-converter\n   #:disconnect-module\n   #:define-foreign-callable))\n(in-package :util/fake-fli)\n\n(defvar *type-map* (trivial-garbage:make-weak-hash-table :weakness :key\n                                                         #+sbcl\n                                                                   :synchronized\n                                                         #+sbcl\n                                                         t))\n\n(defvar *funcall-cleanups* nil\n  \"When we're calling a foreign function this is all the cleanups that\nhave to be called just before returning.\")\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *converters* (make-hash-table\n                        #+sbcl #+sbcl\n                        :synchronized t)))\n\n(defun make-typed-pointer (&key ptr type)\n  (let ((ptr (cffi:make-pointer (cffi:pointer-address ptr))))\n    (setf (gethash ptr *type-map*) type)\n    ptr))\n\n\n\n(defun typed-pointer-type (ptr)\n  (or\n   (gethash ptr *type-map*)\n   :void))\n\n(defun typed-pointer-ptr (ptr)\n  ptr)\n\n(defun fix-type (x &key (allow-count t))\n  \"Returns two values the converted type, and the count, in case of an array\"\n  ;;(log:info \"fixing type: ~S\" x)\n  (cond\n    ((not x)\n     (error \"this looks like a bad type: ~a\" x))\n    ((eql :size-t x)\n     :uint64)\n    ((eql :ssize-t x)\n     :int64)\n    ((eql :time-t x)\n     :uint64)\n    ((gethash x *converters*)\n     (converter-foreign-type (gethash x *converters*)))\n    ((not (listp x))\n     x)\n    ((equal '(:reference-pass :ef-mb-string) x)\n     :string)\n    ((equal '(:reference :ef-mb-string) x)\n     :string)\n    ((equal :c-array (car x))\n     (unless allow-count\n       (Error \"count not supported in this location\"))\n     (assert (fix-type (cadr x)))\n     (values (fix-type (cadr x)) (caddr x)))\n    ((equal :pointer (car x))\n     :pointer)\n    (t\n     x)))\n\n(defmacro define-c-typedef (name type)\n  (multiple-value-bind (type count) (fix-type type)\n    (assert (not count))\n   `(cffi:defctype ,name ,type)))\n\n(defun register-module (name &key real-name file-name connection-style)\n  \"REAL-NAME is deprecated.\"\n  (declare (ignore name))\n  (assert (or file-name real-name))\n  (cffi:load-foreign-library (or file-name real-name)))\n\n(defmacro define-c-struct (name &rest fields)\n  `(cffi:defcstruct ,name\n     ,@ (loop for (name type) in fields\n              collect (multiple-value-bind (type count) (fix-type type)\n                        (list* name type\n                               (when count\n                                 (list :count count)))))))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n (defclass converter ()\n   ((name :initarg :name)\n    (foreign-type :initarg :foreign-type\n                  :reader converter-foreign-type)\n    (foreign-to-lisp :initarg :foreign-to-lisp\n                     :reader converter-foreign-to-lisp)\n    (lisp-to-foreign :initarg :lisp-to-foreign\n                     :reader converter-lisp-to-foreign))))\n\n\n(defmacro define-foreign-converter (name () var &key foreign-type\n                                                  foreign-to-lisp\n                                                  lisp-to-foreign)\n  (let ((real-var (gensym (string name))))\n   `(eval-when (:compile-toplevel :load-toplevel :execute)\n      (setf (gethash ',name *converters*)\n            (make-instance 'converter\n                           :name ',name\n                           :foreign-type ,foreign-type\n                           :foreign-to-lisp\n                           (lambda (,real-var)\n                             ,(eval\n                               `(let ((,var ',real-var))\n                                  ,foreign-to-lisp)))\n                           :lisp-to-foreign\n                           '(lambda (,real-var)\n                             ,(eval\n                               `(let ((,var ',real-var))\n                                  ,lisp-to-foreign))))))))\n\n(defun funcall-with-native-error-handling (fn &rest args)\n  #-sbcl\n  (apply fn args)\n  #+sbcl\n  (sb-int:with-float-traps-masked (:divide-by-zero)\n    (apply fn args)))\n\n(defmacro define-foreign-function (name args &key result-type documentation module)\n  (declare (ignore module))\n  (assert result-type)\n  (destructuring-bind (name-var name) (cond\n                                        ((symbolp name)\n                                         (list name (str:replace-all\n                                                     \"-\" \"_\"\n                                                     (str:downcase (string name)))))\n                                        (t\n                                         name))\n   (multiple-value-bind (args reference-returns) (parse-reference-returns args)\n     (let ((cffi-name (intern (format nil \"%~a-NATIVE\" name-var))))\n       `(progn\n          (cffi:defcfun (,name ,cffi-name) ,(fix-type result-type :allow-count nil)\n            ,@(loop for arg in args\n                    collect\n                    (destructuring-bind (name type) arg\n                      (list name (fix-type type :allow-count nil)))))\n          (defun ,name-var (,@ (mapcar #'car args))\n            ,(wrap-reference-returns\n              reference-returns\n              (let ((returns (loop for x in reference-returns\n                                   collect\n                                   `(cffi:mem-ref ,(car x) ',(cadr (cadr x)))\n                                   )))\n                `(let ((*funcall-cleanups* nil))\n                   (let ((ret (funcall-with-native-error-handling #',cffi-name\n                               ,@(loop for (name type) in args\n                                       collect `(,(%find-lisp-to-foreign-converter type)\n                                                 ,name)))))\n                     (mapcar #'funcall *funcall-cleanups*)\n                     (values\n                      ret\n                      ,@ returns)))))))))))\n\n(defun wrap-reference-returns (reference-returns content)\n  (cond\n    ((null reference-returns)\n     content)\n    (t\n     (destructuring-bind (next &rest rest) reference-returns\n       (let ((type (cadr (cadr next))))\n        `(let ((original-value ,(car next)))\n           (cffi:with-foreign-object (,(car next)\n                                      ',type)\n             (setf (cffi:mem-ref ,(car next) ',type)\n                   original-value)\n             ,(wrap-reference-returns rest content))))))))\n\n\n(defun parse-reference-returns (args)\n  (let (final-args\n        final-rets)\n    (dolist (arg args)\n      (destructuring-bind (name type) arg\n          (cond\n            ((and (listp type) (eql :reference-return (first type)))\n             (push (list name :pointer) final-args)\n             (push arg final-rets))\n            (t\n             (push arg final-args)))))\n    (values (reverse final-args)\n            (reverse final-rets))))\n\n(defmacro define-c-enum (&rest rest)\n  `(cffi:defcenum ,@rest))\n\n(defun convert-from-foreign-string (x)\n  (cffi:convert-from-foreign x :string))\n\n(defun null-pointer-p (x)\n  (cffi:null-pointer-p (typed-pointer-ptr x)))\n\n(defun make-pointer (&key address type symbol-name)\n  (declare (optimize (speed 0) (debug 3)))\n  (cond\n    (symbol-name\n     (let ((str (str:replace-all \"-\" \"_\" (str:downcase symbol-name))))\n       (make-typed-pointer\n        :ptr (let ((ptr (cffi:foreign-symbol-pointer str)))\n               (if (not ptr) ;; returns null, not null-pointer\n                   (eval `(cffi:callback ,symbol-name))\n                   ptr))\n        :type type)))\n    ((not address)\n     (cffi:null-pointer))\n    (t\n     (make-typed-pointer\n      :ptr\n      (cffi:make-pointer address)\n      :type type))))\n\n(defmacro %with-dynamic-foreign-objects (((output type &key (nelems 1) (fill 0))) &body body)\n  ;; fill is ignored!\n  `(cffi:with-foreign-object (,output ',type ,nelems)\n     (let ((,output (make-typed-pointer :ptr ,output :type ',type)))\n      ,@body)))\n\n(defmacro with-dynamic-foreign-objects (exprs &body body)\n  (cond\n    (exprs\n     `(%with-dynamic-foreign-objects (,(car exprs))\n        (with-dynamic-foreign-objects ,(cdr exprs)\n          ,@body)))\n    (t\n     `(progn ,@body))))\n\n(defmacro foreign-slot-value (obj slot)\n  `(cffi:foreign-slot-value (typed-pointer-ptr ,obj)\n                            (typed-pointer-type ,obj)\n                            ,slot))\n\n(defmacro incf-pointer (pointer)\n  `(setf\n    ,pointer\n    (let ((pointer ,pointer))\n     (make-typed-pointer\n      :ptr (cffi:inc-pointer (typed-pointer-ptr pointer)\n                                 (cffi:foreign-type-size (typed-pointer-type pointer)))\n      :type (typed-pointer-type pointer)))))\n\n(defun copy-pointer (pointer)\n  (make-typed-pointer\n   :ptr (typed-pointer-ptr pointer)\n   :type (typed-pointer-type pointer)))\n\n(defun dereference (obj)\n  (cffi:mem-ref (typed-pointer-ptr obj) (typed-pointer-type obj)))\n\n(defun malloc (&key type)\n  (make-typed-pointer\n   :ptr (cffi:foreign-alloc (fix-type type :allow-count nil))\n   :type type))\n\n(Defun free (obj)\n  (cffi:foreign-free\n   (typed-pointer-ptr obj)))\n\n(defmacro with-coerced-pointer ((output &key type) input &body body)\n  `(let ((,output (make-typed-pointer\n                   :ptr (typed-pointer-ptr ,output)\n                   :type ,type)))\n     ,@body))\n\n(defun pointer-address (x)\n  (cffi:pointer-address x))\n\n(defun disconnect-module (name)\n  ;; TODO\n  nil)\n\n(defun %find-converter (type)\n  (or\n   (alexandria:when-let ((converter (gethash type *converters*)))\n     (converter-foreign-to-lisp converter))\n   #'identity))\n\n(defun %find-lisp-to-foreign-converter (type)\n  (or\n   (alexandria:when-let ((converter (gethash type *converters*)))\n     (converter-lisp-to-foreign converter))\n   'identity))\n\n(defmacro define-foreign-callable ((name &key result-type) args &body body)\n  `(cffi:defcallback ,name\n       ,(fix-type result-type)\n       ,(loop for (name type) in args\n              collect `(,name ,(fix-type type)))\n     (let ,(loop for (name type) in args\n                 collect `(,name\n                           (funcall (%find-converter ',type)\n                                    ,name)))\n       ,@body)))\n\n\n(define-foreign-converter :lisp-simple-1d-array ()\n  h\n  :foreign-type '(:pointer :uint8)\n  :foreign-to-lisp `(progn\n                      (error \" not supported for ~a\" ,h))\n  :lisp-to-foreign `(let ((arr (make-typed-pointer\n                                :ptr (cffi:foreign-alloc :uint8\n                                                         :count (+ 10 (length ,h))\n                                                         :initial-contents ,h)\n                                :type :uint8)))\n                      (push (lambda ()\n                              (dotimes (i (length ,h))\n                                (setf (aref ,h i)\n                                      (cffi:mem-aref arr :uint8 i)))\n                              (free arr))\n                            *funcall-cleanups*)\n                      arr))\n"
  },
  {
    "path": "src/util/fiveam.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :util/fiveam\n    (:use #:cl\n          #:alexandria)\n    (:export #:def-suite))\n(in-package :util/fiveam)\n\n;;(setf fiveam::*toplevel-suites* nil)\n;; (setf fiveam::*tests* nil)\n\n(defun %def (parts)\n  (let ((keywords (symbol-package :foo)))\n   (cond\n     ((null (cdr parts))\n      (let ((name (intern (car parts) keywords)))\n       (or\n        (fiveam::get-test name)\n        (setf (fiveam::get-test name)\n              (fiveam:make-suite name)))))\n     (t\n      (let ((parent (%def (butlast parts))))\n        (assert parent)\n        (let* ((name (intern (str:join \"/\" parts) keywords)))\n          (or\n           (let ((suite (fiveam::get-test name)))\n             (when suite\n               (check-type suite fiveam::test-suite)\n               suite))\n           (let ((ret\n                   (fiveam:make-suite name\n                                      :in\n                                      (fiveam::name parent))))\n             ;; hold on one sec. Let's be very sure that the parent has the child\n             (assert (gethash name (fiveam::tests parent)))\n             ret))))))))\n\n\n(defun def-suite-recursive (name)\n  (let ((parts (str:split \"/\" (string name))))\n    (%def parts)))\n\n(defmacro def-suite ()\n  `(eval-when (:load-toplevel :execute)\n     (let ((suite-name ,(guess-suite-name)))\n       (def-suite-recursive suite-name)\n       (eval `(fiveam::%in-suite ,suite-name)))))\n\n(defun guess-suite-name ()\n  (intern (string (package-name *package*))\n          (symbol-package :foo)))\n"
  },
  {
    "path": "src/util/fixture/file.txt",
    "content": "hello\n"
  },
  {
    "path": "src/util/form-errors.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop/package:define-package :util/form-errors\n    (:use #:cl #:alexandria #:mquery)\n  (:import-from #:markup\n                #:xml-tag-children)\n  (:export #:with-form-errors\n           #:update-form-values\n           #:with-error-builder))\n(in-package :util/form-errors)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun update-form-values (args)\n  (dolist (arg args)\n    (destructuring-bind (name . val) arg\n      (log:info \"Setting ~S\" arg)\n      (setf (mquery:val ($ (mquery:namequery name)))\n            val))))\n\n(defun input-is-button-p (input)\n  (str:s-member '(\"button\" \"submit\")\n                (mquery:attr input \"type\")))\n\n(defun %with-form-errors (html &key errors args args-list was-validated tooltip)\n  (let ((args (append args args-list)))\n   (mquery:with-document (html)\n     (when was-validated\n       (mquery:add-class (remove-if #'input-is-button-p ($ \"input\")) \"is-valid\")\n       (update-form-values\n        args)\n       (dolist (err errors)\n         (cond\n           ((and (consp err) (car err))\n            (destructuring-bind (name . msg) err\n              (let ((input ($ (mquery:namequery name))))\n                (assert input)\n                (mquery:remove-class input \"is-valid\")\n                (mquery:add-class input \"is-invalid\")\n                (let ((input-group (let ((parent (mquery:parent input)))\n                                     (when (mquery:has-class-p parent \"input-group\")\n                                       parent))))\n                  (when input-group\n                    (mquery:add-class input-group \"has-validation\"))\n                  (setf (mquery:after\n                         (cond\n                           ((equal \"checkbox\" (mquery:attr input \"type\"))\n                            ;; for checkbox there's a label after the input form\n                            (mquery:after input))\n                           (input-group\n                            (car (last (xml-tag-children (mquery:parent input)))))\n                           (t\n                            input)))\n                        <div class= (if tooltip \"invalid-tooltip\" \"invalid-feedback\") >,(progn msg)</div>)))))\n           (t\n            ;; Add error message to a alert\n            (let ((alert ($ \".alert\")))\n              (unless alert\n                (warn \"Tried to update an alert, but did not find it\"))\n              (when alert\n                (mquery:remove-class alert \"d-none\")\n                (mquery:mqappend alert\n                                 <div>,(progn (if (consp err) (cdr err) err))</div>)))))))))\n  (values html errors))\n\n(defmacro with-form-errors ((&rest args &key errors args-list was-validated tooltip &allow-other-keys) &body body)\n  \"Update the body to add bootstrap based error validation to every field. Note that global errors, need a div with class `alert alert-danger`.\"\n  (let* ((args (plist-alist args)))\n    `(%with-form-errors (progn ,@body)\n                        :errors ,errors\n                        :was-validated ,was-validated\n                        :tooltip ,tooltip\n                        :args (list ,@(loop for x in args collect\n                                            `(cons ,(car x) ,(cdr x))))\n                        :args-list ,args-list)))\n\n(defmacro with-error-builder ((&key (check (error \"must provide :check function name\"))\n                                 (errors (error \"must provide a variable for the errors\"))\n                                 form-builder\n                                 form-args\n                                 success)\n                              &body body)\n  `(let ((,errors nil))\n     (flet ((,check (field expr value)\n              (unless expr\n                (cond\n                  (field\n                   (push (cons field value)\n                         ,errors))\n                  (t\n                   (push value ,errors))))))\n       ,@body\n       (cond\n         (,errors\n          (with-form-errors (:errors ,errors\n                             ,@form-args\n                             :was-validated t)\n            (values\n             (progn ,form-builder)\n             ;; Pass errors for testing reasons\n             ,errors)))\n         (t\n          ,success)))))\n"
  },
  {
    "path": "src/util/form-state.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :util/form-state\n  (:nicknames #:jsearch/form-state)\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:closer-mop\n                #:class-direct-slots\n                #:slot-definition-name\n                #:validate-superclass\n                #:standard-direct-slot-definition\n                #:direct-slot-definition-class)\n  (:export\n   #:form-state-class\n   #:form-state-initargs\n   #:read-form-state\n   #:args-list-from-state\n   #:form-state-validate\n   #:make-form-state\n   #:form-state-apply-edits))\n(in-package :jsearch/form-state)\n\n(defclass form-state-direct-slot-definition (standard-direct-slot-definition)\n  ((transient :initarg :transient\n              :initform nil\n              :reader slot-transient-p)\n   (mapped-to :initarg :mapped-to\n              :initform nil\n              :reader slot-mapped-to)\n   (mapped-to-initarg :initarg :mapped-to-initarg\n                      :initform nil\n                      :reader slot-mapped-to-initarg)\n   (mapped-via :initarg :mapped-via\n               :initform #'identity\n               :reader slot-mapped-via)\n   (mapped-back-via :initarg :mapped-back-via\n                    :initform #'identity\n                    :reader slot-mapped-back-via)))\n\n(defclass form-state-class (standard-class)\n  ())\n\n(defmethod validate-superclass ((class form-state-class) (superclass standard-class))\n  t)\n\n(defmethod direct-slot-definition-class ((class form-state-class)\n                                         &key &allow-other-keys)\n  'form-state-direct-slot-definition)\n\n(defmethod form-state-initargs (form-state)\n  (loop for slot in (closer-mop:class-direct-slots (class-of form-state))\n        for name = (closer-mop:slot-definition-name slot)\n        for mapped-to-initarg = (or\n                                 (slot-mapped-to-initarg slot)\n                                 (intern (string name) \"KEYWORD\"))\n        if (not (slot-transient-p slot))\n        appending\n        (list mapped-to-initarg\n              (funcall (slot-mapped-via slot)\n                       (slot-value form-state name)))))\n\n(defun read-form-state (class-name)\n  (let ((state (make-instance class-name)))\n    (loop for slot in (closer-mop:class-direct-slots (find-class class-name))\n          for name = (closer-mop:slot-definition-name slot)\n          for val = (hunchentoot:parameter (string-downcase name))\n          do\n\n             (setf (slot-value state name)\n                   (if (eql 'number (closer-mop:slot-definition-type slot))\n                       ;; What happens if it's invalid?\n                       (if (str:emptyp val)\n                           -1\n                           (parse-integer val))\n                       val)))\n    state))\n\n(defun safe-slot-mapped-to (slot)\n  (or\n   (slot-mapped-to slot)\n   (slot-definition-name slot)))\n\n(defmethod populate-form-state (form-state obj)\n  (loop for slot in (class-direct-slots (class-of form-state))\n        for name = (slot-definition-name slot)\n        if (not\n            (slot-transient-p slot))\n        do\n           (setf (slot-value form-state name)\n                 (funcall\n                  (slot-mapped-back-via slot)\n                  (funcall (safe-slot-mapped-to slot)\n                           obj)))))\n\n(defmethod make-form-state (class-name obj &rest initargs)\n  (let ((ret (apply #'make-instance class-name initargs)))\n    (populate-form-state ret obj)\n    ret))\n\n(defmethod form-state-validate (state obj)\n  nil)\n\n(defmethod validate-slots (state)\n  ;; todo: what kind of validations would actually make sense here?\n  nil)\n\n(defmethod form-state-validate :around (state obj)\n  (append\n   (validate-slots state)\n   (call-next-method)))\n\n(defun args-list-from-state (state)\n  (loop for slot in (closer-mop:class-direct-slots (class-of state))\n        for name = (closer-mop:slot-definition-name slot)\n        collect\n        (cons\n         (intern (string-upcase name)  \"KEYWORD\")\n         (slot-value state name))))\n\n(defun original-slot-setter (slot)\n  \"Returns a funcation that can be called to set the value of the\n  given slot in the *original* data object.\"\n  (fdefinition `(setf ,(or\n                        (slot-mapped-to slot)\n                        (slot-definition-name slot)))))\n\n(defmethod form-state-apply-edits (state obj)\n  (loop for slot in (class-direct-slots (class-of state))\n        for name = (slot-definition-name slot)\n        if (not (slot-transient-p slot))\n        do\n           (funcall (original-slot-setter slot)\n                    (funcall\n                     (slot-mapped-via slot)\n                     (slot-value state name))\n                    obj)))\n"
  },
  {
    "path": "src/util/fset.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/fset\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:do-reverse-set))\n(in-package :util/fset)\n\n(defun call-do-reverse-set (set fn)\n  (loop until (fset:empty? set) do\n    (let ((next (fset:greatest set)))\n      (funcall fn next)\n      (setf set (fset:less set next))))\n  nil)\n\n(defmacro do-reverse-set ((val set) &body body)\n  \"Loops across a set in the reverse order. Wrapped in an implicit NIL\nblock.\"\n  `(block nil\n     (call-do-reverse-set\n      ,set\n      (lambda (,val)\n        ,@body))))\n"
  },
  {
    "path": "src/util/gcloud.lisp",
    "content": "(defpackage :util/gcloud\n  (:use #:cl)\n  (:shadow #:machine-type))\n(in-package :util/gcloud)\n\n(defclass gcloud-vm-provider ()\n  ((zone :initarg :zone\n         :initform \"us-east4-c\"\n         :reader zone)\n   (machine-type :initarg :machine-type\n                 :initform \"n2-highcpu-8\"\n                 :reader machine-type)\n   (prefix :initarg :prefix\n           :initform \"auto-vm\"\n           :reader prefix)\n   (project :initarg :project\n            :reader project)\n   (metadata :initarg :metadata\n             :reader metadata)\n   (source-machine-imagoe :initarg :source-machine-image\n                         :reader source-machine-image)))\n\n(defclass gcloud-vm ()\n  ((provider :initarg :provider\n             :reader gcloud-vm-provider)\n   (vm-name :initarg :vm-name\n            :reader vm-name)))\n\n(defmethod start-instance ((self gcloud-vm-provider))\n  (let* ((id (mongoid:oid-str (mongoid:oid)))\n         (vm-name (str:downcase (format nil \"~a-~a\" (prefix self) id))))\n    (uiop:run-program\n     (remove-if\n      #'null\n      (list*\n       \"gcloud\" \"compute\" \"instances\" \"create\"\n       vm-name\n       \"--zone\" (zone self)\n       \"--min-cpu-platform\" \"Intel Cascadelake\"\n       (when (metadata self)\n         \"--metadata\")\n       (when (metadata self)\n         (str:join\n          \",\"\n          (loop for (key . value) in (metadata self)\n                collect (format nil \"~a=~a\" key value))))\n       \"--machine-type\"\n       (machine-type self)\n       (image-args self)))\n     :error-output *standard-output*\n     :output *standard-output*)\n    (make-instance 'gcloud-vm\n                   :provider self\n                   :vm-name vm-name )))\n\n(defmethod stop-instance ((self gcloud-vm))\n  (uiop:run-program\n   (list\n    \"gcloud\" \"compute\" \"instances\" \"delete\"\n    (vm-name self)\n    \"--zone\" (zone (gcloud-vm-provider self))\n    \"-q\")\n   :output *standard-output*\n   :error-output *standard-output*))\n\n;; (time (setf *vm* (start-instance *provider*)))\n;; (stop-instance *vm*)\n\n\n(defmethod image-args ((Self gcloud-vm-provider))\n  (list\n   \"--source-machine-image\"\n   (format\n    nil\n    \"https://www.googleapis.com/compute/v1/projects/~a/global/machineImages/~a\"\n    (project self)\n    (source-machine-image self))))\n"
  },
  {
    "path": "src/util/google-analytics.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package #:util)\n(named-readtables:in-readtable markup:syntax)\n\n(markup:deftag google-analytics (&key tracking (site-speed-sample-rate 100))\n  (markup:make-merge-tag\n   (list\n   <script async=\"\" src=(format nil \"https://www.googletagmanager.com/gtag/js?id=~A\" tracking)></script>\n   <script>\n   window.dataLayer = window.dataLayer || [];\n   function gtag(){dataLayer.push(arguments);}\n   gtag('js', new Date());\n\n   gtag('config', ',(progn tracking)', {'site_speed_sample_rate':,(progn site-speed-sample-rate) });\n   </script>)))\n\n\n(markup:deftag tag-manager-body (&key tracking)\n  \"Put it early in the <body>\"\n  <noscript><iframe src= (format nil \"https://www.googletagmanager.com/ns.html?id=~a\" tracking)\n  height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe></noscript>\n  )\n\n(markup:deftag tag-manager-head (&key tracking)\n  \"Put this early in the <head>\"\n  <script>(function(w,d,s,l,i){w[l]=w[l]||[];\n                   w[l].push({'gtm.start':\n                             new Date().getTime(),event:'gtm.js'});\n                   var f=d.getElementsByTagName(s)[0],\n                   j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';\n                   j.async=true;\n                   j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;\n                   f.parentNode.insertBefore(j,f);\n})(window,document,'script','dataLayer',',(progn tracking)');</script>\n)\n"
  },
  {
    "path": "src/util/hash-lock.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/hash-lock\n  (:use :cl)\n  (:export\n   #:hash-lock\n   #:with-hash-lock-held)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/hash-lock)\n\n(defclass object-state ()\n  ((item-lock :initform (bt:make-lock)\n              :reader item-lock)\n   (count :initform 0\n          :accessor %count\n          :documentation \"Total number of threads that are waiting or running.\")))\n\n(defclass hash-lock ()\n  ((lock :initform (bt:make-lock \"hash-lock\")\n         :reader %lock)\n   (hash-table :accessor %hash-table)))\n\n(defmethod initialize-instance :after ((self hash-lock) &key (test #'eql) &allow-other-keys)\n  (setf (%hash-table self)\n        (make-hash-table :test test)))\n\n(defmacro with-hash-lock-held ((obj hash-lock) &body body)\n  `(call-with-hash-lock-held\n    ,obj\n    ,hash-lock\n    (lambda () ,@body)))\n\n(defun object-state (obj hash-lock)\n  \"Get the object state for the given object. Must be called under the\n  lock!\"\n  (symbol-macrolet ((x (gethash obj (%hash-table hash-lock))))\n    (or x\n        (setf x (make-instance 'object-state)))))\n\n(defun call-with-hash-lock-held (obj hash-lock fn)\n  (let (object-state)\n    (bt:with-lock-held ((%lock hash-lock))\n      (setf object-state (object-state obj hash-lock))\n      ;; number of threads waiting on this\n      (incf (%count object-state)))\n    (unwind-protect\n         (bt:with-lock-held ((item-lock object-state))\n           (funcall fn))\n      (bt:with-lock-held ((%lock hash-lock))\n        (decf (%count object-state))\n        (cond\n          ((<= (%count object-state) 0)\n           ;; we were the last one waiting on this, we can free up all\n           ;; the resource associated with this object\n           (remhash obj (%hash-table hash-lock))))))))\n"
  },
  {
    "path": "src/util/health-check.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/health-check\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:def-health-check\n   #:run-health-checks))\n(in-package :util/health-check)\n\n(defvar *checks* nil)\n\n(defclass health-check ()\n  ((name :initarg :name\n         :reader health-check-name)\n   (slow :initarg :slow)\n   (function :initarg :function\n             :reader health-check-function)))\n\n(defmacro def-health-check (name (&key slow) &body body)\n  `(setf (a:assoc-value *checks* ',name)\n         (make-instance 'health-check\n                         :name ',name\n                         :slow ',slow\n                         :function (lambda ()\n                                     ,@body))))\n\n(defun call-health-check (health-check &key out)\n  (handler-case\n      (progn\n        (format out \"Checking ~a... \" (health-check-name health-check))\n        (finish-output out)\n        (funcall (health-check-function health-check))\n        (format out \"SUCCESS~%\")\n        (finish-output out)\n        t)\n    (error (e)\n      (format out \"FAILURE [~a]~%\" e)\n      (finish-output out)\n      nil)))\n\n\n(defun run-health-checks (&key (out *terminal-io*))\n  (loop for health-check in (mapcar 'cdr (reverse *checks*))\n        if (not (call-health-check health-check :out out))\n          collect (health-check-name health-check) into failed\n        finally\n           (progn\n             (cond\n               (failed\n                (format out \"=======================~%\")\n                (format out \"HEALTH CHECKS FAILED!!!~%\")\n                (format out \"=======================~%\"))\n               (t\n                (format out \"All health checks done~%\")))\n             (return\n               (values (not failed) failed)))))\n\n(def-health-check sanity-test ()\n  (values))\n\n;; ;; (run-health-checks)\n"
  },
  {
    "path": "src/util/html2text.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:util/html2text\n  (:use #:cl)\n  (:export #:html2text))\n(in-package #:util/html2text)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun html2text (markup)\n  (let* ((markup (if (stringp markup) markup\n                     (markup:write-html markup))))\n    (html2text:html2text markup)))\n"
  },
  {
    "path": "src/util/http-cache.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/http-cache\n  (:use #:cl)\n  (:export\n   #:http-cache\n   #:parse-max-age))\n(in-package :util/http-cache)\n\n(defclass http-cache ()\n  ((lru-cache :initarg :lru-cache\n              :reader lru-cache)))\n\n(defun parse-max-age (cache-control)\n  (let ((parts (str:split \",\" cache-control)))\n    (or\n     (loop for part in parts\n           do\n              (destructuring-bind (key &optional value) (str:split \"=\" (str:trim part))\n                (when (string-equal \"max-age\" key)\n                  (return\n                    ;; Sometimes you'll see things like 30s\n                    (parse-integer value :junk-allowed t)))))\n     0)))\n"
  },
  {
    "path": "src/util/http-ping.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/http-ping\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request)\n  (:export\n   #:http-ping))\n(in-package :util/http-ping)\n\n(defun http-ping (url)\n  (format t \"===========================================~%\")\n  (format t \"~%\")\n  (format t \"  REMINDER: use /test-headers as a convenience endpoint for enterprise\n  installs, otherwise it tries to redirect to an OIDC nibble.~%~%\")\n  (format t \"  Hacky: To exit, C-c and then type (quit)~%~%\")\n  (format t \"===========================================~%\")\n  (when (str:emptyp url)\n    (error \"Need a url argument\"))\n  (let ((good 0)\n        (bad 0)\n        (all 0)\n        (lock (bt:make-lock)))\n    (loop for i below 100000\n          do\n             (sleep 0.25)\n             (incf all)\n             (bt:with-lock-held (lock)\n               (format t \"All: ~a / Good: ~a / Bad: ~a~%\" all good bad))\n             (finish-output t)\n             (bt:make-thread\n              (lambda ()\n                (handler-case\n                    (progn\n                      (let ((content (http-request\n                                      url\n                                      :ensure-success t)))\n                        ;;(log:info \"Got content: ~a\" content)\n                        )\n                      (bt:with-lock-held (lock)\n                        (incf good)))\n                  (error (e)\n                    (log:error \"Got error: ~a for ~a\" e url)\n                    (bt:with-lock-held (lock)\n                      (incf bad)))))))))\n"
  },
  {
    "path": "src/util/hunchentoot-engine.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/hunchentoot-engine\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request-impl)\n  (:import-from #:hunchentoot\n                #:acceptor-reply-class\n                #:acceptor-request-class))\n(in-package :util/hunchentoot-engine)\n\n(defclass hunchentoot-engine ()\n  ((acceptor :initarg :acceptor\n             :reader acceptor))\n  (:documentation \"A request engine that dispatches to a hunchentoot acceptor instead.\"))\n\n(defun compute-headers-in (&key basic-authorization content additional-headers)\n  (append\n   additional-headers\n   (remove-if\n    #'null\n    `(,(when (stringp content)\n         `(:content-length . ,(format nil \"~a\" (length content))))\n      ,(when basic-authorization\n         `(:authorization . ,(format nil \"Basic ~a\"\n                                     (base64:string-to-base64-string\n                                      (format nil \"~a:~a\"\n                                              (first basic-authorization)\n                                              (second basic-authorization))))))))))\n\n(defun %make-uri (uri &key parameters)\n  (let* ((uri (quri:uri uri))\n         (params (quri:uri-query-params uri)))\n    (setf (quri:uri-query-params uri)\n          (append\n           parameters\n           params))\n    (quri:render-uri uri)))\n\n(defmethod http-request-impl ((self hunchentoot-engine)\n                              url &key method basic-authorization want-stream\n                                    parameters\n                                    content\n                                    force-binary\n                                    additional-headers\n                              &allow-other-keys)\n  (let* ((acceptor (acceptor self))\n         (hunchentoot:*acceptor* acceptor)\n         (content-stream (cond\n                           ((and\n                             (streamp content)\n                             (equal '(unsigned-byte 8) (stream-element-type content)))\n                            content)\n                           ((stringp content)\n                            (flex:make-in-memory-input-stream\n                             (flex:string-to-octets content\n                                                    :external-format :utf-8)))\n                           (content\n                            (error \"Content of type ~a, not supported by hunchentoot-engine\"\n                                   content))\n                           (t\n                            (flex:make-in-memory-input-stream\n                             (make-array 0 :element-type 'flex:octet)))))\n         (request (make-instance (acceptor-request-class acceptor)\n                                 :acceptor acceptor\n                                 :local-addr \"127.0.0.1\"\n                                 :local-port 9999\n                                 :remote-addr \"127.0.0.1\"\n                                 :remote-port 9998\n                                 :headers-in (compute-headers-in\n                                              :content content\n                                              :basic-authorization basic-authorization\n                                              :additional-headers additional-headers)\n                                 :content-stream content-stream\n                                 :uri (%make-uri\n                                       url\n                                       :parameters parameters)\n                                 :method method\n                                 :server-protocol :http))\n         (hunchentoot:*request* request)\n         (reply (make-instance (acceptor-reply-class acceptor)))\n         (hunchentoot:*reply* reply))\n    (let ((body\n            (let ((hunchentoot::*hunchentoot-stream* content-stream))\n             (hunchentoot:acceptor-dispatch-request acceptor request))))\n      (values\n       (cond\n         ((and want-stream\n               force-binary)\n          (flex:make-in-memory-input-stream\n           (flex:string-to-octets body)))\n         (want-stream\n          (make-string-input-stream\n           body))\n         (t\n          body))\n       (hunchentoot:return-code reply)\n       (hunchentoot:headers-out* reply)))))\n\n\n"
  },
  {
    "path": "src/util/java/all.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :util/java\n    (:use-reexport\n     #+ (or ccl lispworks)\n     :util/java/java\n     :util/java/reader\n     #+ (or ccl lispworks)\n     :util/java/binding))\n"
  },
  {
    "path": "src/util/java/binding.lisp",
    "content": "(pkg:define-package :util/java/binding\n    (:use #:cl\n          #:alexandria)\n  (:import-from #:util/java/java\n                #:invoke)\n  (:export\n   #:bind-instance))\n\n(defun javafy-name (name)\n  (let ((name (string name)))\n    (cond\n      ((str:ends-with-p \"-P\" name)\n       (str:camel-case (format nil \"is-~a\" (cl-ppcre:regex-replace-all \"-P$\" name \"\"))))\n      (t\n       (str:camel-case (format nil \"get-~A\" name))))))\n\n(defun bind-instance (type java-object)\n  (let ((ret (make-instance type)))\n    (loop for slot in (closer-mop:class-slots (find-class type))\n          do\n             (let ((slot-name (closer-mop:slot-definition-name slot)))\n              (setf (slot-value ret slot-name)\n                    (invoke java-object\n                            (javafy-name slot-name)))))\n    ret))\n"
  },
  {
    "path": "src/util/java/iterate.lisp",
    "content": "(pkg:define-package :util/java/iterate\n    (:use #:cl\n          #:iterate\n          #:alexandria)\n  (:import-from #:util/java/reader\n                #:java-syntax)\n  (:import-from #:util/java/java\n                #:define-java-callers\n                #:java-equals\n                #:*btrue*)\n  (:export\n   #:in-java))\n\n(named-readtables:in-readtable java-syntax)\n\n(defun p (x)\n  (log:info \"Got: ~S\" x)\n  x)\n\n(define-java-callers \"java.util.Iterator\"\n  (has-next-p \"hasNext\" :return-type :boolean)\n  (jnext \"next\"))\n\n(defmacro-driver (for var in-java x)\n  (alexandria:with-gensyms (xv)\n    (let ((kwd (if generate 'generate 'for)))\n      `(progn\n         (with ,xv = (#_iterator ,x))\n         (,kwd ,var next (if (has-next-p ,xv)\n                             (jnext ,xv)\n                             (terminate)))))))\n"
  },
  {
    "path": "src/util/java/java.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :util/java/java\n    (:use :cl)\n  (:export #:*bfalse*\n           #:*btrue*\n           #:safe-jvref\n           #:invoke\n           #:java-equals\n           #:classp\n           #:jclass-of\n           #:read-java-field\n           #:new-instance\n           #:define-java-callers\n           #:jclass-of!\n           #:array->list)\n  #+lispworks\n  (:import-from :lw-ji\n                #:find-java-class\n                #:java-objects-eq\n                #:java-array-length)\n  #+ccl\n  (:import-from :cl+j))\n\n#+lispworks\n(require \"java-interface\")\n\n(defvar *bfalse*)\n(defvar *btrue*)\n\n#+ccl\n(declaim (optimize (speed 0) (debug 3)))\n\n#+ccl\n(defun java-objects-eq (x y)\n  (cond\n    ((not (eql (type-of x) (type-of y)))\n     nil)\n    ((typep x 'cl+j::java-class)\n     (java-objects-eq (cl+j::java-class-real x)\n                      (cl+j::java-class-real y)))\n    (t\n     (cl+j:jeq (fix-arg-for-jmethod-invoke x) (fix-arg-for-jmethod-invoke y)))))\n\n#+ccl\n(defun find-java-class (name)\n  (case name\n    (:int\n     cl+j::java-int-prim-type)\n    (otherwise\n     ;; this doesn't match the find-java-class on Lispworks, so we're\n     ;; assuming we're only using this internally within this java\n     ;; package.\n     (cl+j:find-java-class name))))\n\n(defun %invoke-static (class method &rest args)\n  (if (symbolp method)\n      (setf method (symbol-name method)))\n  (apply 'send_static_method class method args))\n\n(defun send_static_method (klass method-name &rest args)\n  (init-booleans)\n  (let ((ret\n         (%send_static_method klass method-name (%make-java-array args))))\n    (unwrap-primitive ret)))\n\n(defun jobject-p (obj)\n  (#+lispworks lw-ji:jobject-p\n   #+ccl cl+j::jobjectp\n   #- (or ccl lispworks) (lambda (x) (error \"unsupported\"))\n   obj))\n\n#+ccl\n(defun safe-jtol (x)\n  (cond\n    ((jobject-of-class-p x \"java.lang.Integer\")\n     (cl+j:jtol (cl+j:jmethod x \"intValue\")))\n    ((jobject-of-class-p x \"java.lang.String\")\n     (cl+j::java-string-to-lisp (cl+j::jref-it x)))\n    ((jobject-p x) x)\n    ((stringp x) (cl+j:jtol x))\n    ((eql cl+j:jnull x)\n     nil)\n    (t\n     x)))\n\n(defun unwrap-primitive (ret)\n  (#+lispworks identity\n   #+ccl safe-jtol\n   (cond\n     ((not (jobject-p ret))\n      ret)\n     ((jobject-of-class-p ret \"com.tdrhq.PrimitiveWrapper\")\n      (let ((type (pm-get-type ret)))\n        (case type\n          (1 (pm-as-boolean ret))\n          (2 (pm-as-character ret))\n          (otherwise (error \"unimplemented\")))))\n     (t ret))))\n\n#+ccl\n(defun fix-arg-for-jmethod-invoke (arg)\n  ;;(format t \"fixing arg: ~S~%\" arg)\n  (finish-output t)\n  (cond\n    ((stringp arg)\n     (cl+j:jstring  arg))\n    ((numberp arg)\n     ;; I think the java side handles converting this to Integer if\n     ;; needed when used with reflection.\n     (cl+j:jnew \"java.lang.Long\" (cl+j:jprim-long arg)))\n    ((null arg)\n     cl+j:jnull)\n    (t\n     (assert (jobject-p arg))\n     arg)))\n\n(defun fix-return-type (val return-type)\n  (case return-type\n    (:boolean\n     (> val 0))\n    (t\n     val)))\n\n#+ccl\n(defmacro define-single-java-caller (class-name fn-name name &key signatures staticp\n                                                               return-type)\n  (declare (ignore signatures))\n  `(defun ,fn-name (&rest args)\n     (declare (optimize (speed 0) (debug 3)))\n     (#+ccl fix-return-type\n      #+lispworks identity\n      (#+ccl safe-jtol\n       #+lispworks identity\n       ,(if staticp\n            `(apply 'cl+j::jmethod-static (cl+j::Find-java-class ,class-name)\n                     ,name (mapcar 'fix-arg-for-jmethod-invoke args))\n            `(apply 'cl+j::jmethod-instance (fix-arg-for-jmethod-invoke (car args)) ,name (mapcar 'fix-arg-for-jmethod-invoke (cdr args)))))\n      ,return-type)))\n\n#+ccl\n(defmacro define-java-callers (class-name &rest args)\n  (declare (ignore class-name))\n  `(progn\n     ,@ (loop for arg in args\n              collect\n              `(define-single-java-caller ,class-name ,@arg))))\n\n#+lispworks\n(defmacro define-java-callers (class-name &rest args)\n  `(lw-ji:define-java-callers ,class-name\n     ,@ (loop for arg in args\n              collect\n              (destructuring-bind (name real-name &key signatures staticp return-type)\n                  arg\n                (declare (ignore signatures staticp))\n                `(,name ,real-name)))))\n\n(define-java-callers \"java.lang.ClassLoader\"\n    (load-class \"loadClass\" :signatures (\"java.lang.String\")))\n\n(define-java-callers \"com.tdrhq.PrimitiveWrapper\"\n  (pm-get-type \"getType\")\n  (pm-as-boolean \"asBoolean\")\n  (pm-as-character \"asCharacter\"))\n\n(define-java-callers \"java.lang.Object\"\n  (java-get-class \"getClass\"))\n\n(define-java-callers \"java.lang.Class\"\n  (java-class-is-array \"isArray\"))\n\n\n\n(defun jvref (arr i)\n  #+lispworks\n  (lw-ji:jvref arr i)\n  #+ccl\n  (safe-jtol (cl+j:jaref arr i)))\n\n(defun (setf jvref) (value arr i)\n  #+lispworks\n  (setf (lw-ji:jvref arr i)\n        value)\n  #+ccl\n  (cl+j::jaref-set arr (fix-arg-for-jmethod-invoke value) i)\n  #- (or ccl lispworks)\n  (error \"unimplemented\"))\n\n(defun safe-jvref (arr i)\n  (let ((ret (jvref arr i)))\n    ret))\n\n(defun %make-java-array (args)\n  ;;(format t \"Creating array out of ~S~%\" args)\n  (let (#+ccl (args (mapcar 'fix-arg-for-jmethod-invoke args)))\n   (let ((ret (#+lispworks lw-ji:make-java-array\n               #+ccl cl+j::jnew[]\n               \"java.lang.Object\" (length args))))\n     (loop for i from 0 to 100000\n           for x in args\n           do\n              (when x ;; we don't have a good way of storing nulls on LW :/\n               (setf (jvref ret i) x)))\n     (assert ret)\n     ret)))\n\n(define-java-callers \"com.tdrhq.SimpleNativeLibrary\"\n    (%send_static_method \"send_static_method_wrapped\"\n                         :signatures (\"java.lang.Class\" \"java.lang.String\" \"[java.lang.Object\")\n                         :staticp t)\n    (%send_method \"send_method_wrapped\"\n                  :signatures (\"java.lang.Object\" \"java.lang.String\" \"[java.lang.Object\")\n                  :staticp t)\n    (%%new-instance \"newInstance_wrapped\"\n                    :signatures (\"java.lang.Class\" \"[java.lang.Object\")\n                    :staticp t)\n    (%%read-java-field \"readField\" :staticp t)\n  (%%read-static-field \"readStaticField\" :staticp t)\n  (%%write-field \"writeField\" :staticp t)\n  (%%write-static-field \"writeStaticField\" :staticp t)\n    (%get-logger \"getLogger\" :staticp t))\n\n(defun %new-instance (klass &rest args)\n  ;;(format t \"Creating new instance ~S, ~S~%\" klass args)\n  (unwrap-primitive (%%new-instance klass (%make-java-array args))))\n\n\n(defun init-booleans ()\n  (unless (boundp '*btrue*)\n    ;;(format t \"init-booleans~%\")\n    (setf *btrue* (read-java-field (intern \"com.tdrhq.SimpleNativeLibrary\" :keyword) \"btrue\"))\n    (setf *bfalse* (read-java-field (intern \"com.tdrhq.SimpleNativeLibrary\" :keyword) \"bfalse\"))))\n\n(defun send_method (obj method-name &rest args)\n  (init-booleans)\n  (unwrap-primitive (%send_method obj method-name (%make-java-array args))))\n\n#+lispworks\n(defun invoke (obj method &rest args)\n  (let ((method (string method)))\n   (let ((is-static (symbolp obj)))\n     (if is-static\n         (let ((class (jclass-of! obj)))\n           (apply #'%invoke-static class method args))\n         (apply #'invoke-instance obj method args)))))\n\n#+ccl\n(defun invoke (obj method &rest args)\n  (safe-jtol\n   (let ((args (mapcar 'fix-arg-for-jmethod-invoke args)))\n     (cond\n       ((symbolp obj)\n        (apply 'cl+j::jmethod-static (jclass-of! obj) method args))\n       (t\n        (apply 'cl+j::jmethod-instance (fix-arg-for-jmethod-invoke obj)\n                method args))))))\n\n#+lispworks\n(defun read-java-field (obj field)\n  ;;(format t \"read-java-field: ~S, ~S~%\" obj field)\n  (finish-output t)\n  (let ((field (string field)))\n   (let ((staticp (symbolp obj)))\n     (if staticp\n         (let ((class (jclass-of! obj)))\n           ;;(format t \"class is ~S, ~S~%\" class field)\n           (finish-output t)\n           (%%read-static-field class field))\n         (%%read-java-field obj field)))))\n\n#+ccl\n(defun read-java-field (obj field)\n  (let ((field (string field)))\n   (if (symbolp obj)\n       (cl+j::jfield-static (string obj) field)\n       (progn\n         (assert (jobject-p obj))\n         (cl+j::jfield-instance obj field)))))\n\n\n\n(defun (setf read-java-field) (value obj field)\n  (let ((field (string field)))\n   (let ((staticp (symbolp obj)))\n     (if staticp\n         (let ((class (jclass-of! obj)))\n           (%%write-static-field class field value))\n         (%%write-field obj field value)))))\n\n(defun invoke-instance (obj method &rest args)\n  ;; always invoke the instance method on obj, even if it's a class or\n  ;; symbol.\n  (if (symbolp method)\n      (setf method (symbol-name method)))\n  (unless obj\n    (error (format nil \"could not apply ~a on null\" method)))\n  (apply #'send_method obj method args))\n\n#+lispworks\n(defun new-instance (class &rest args)\n  (let ((class (jclass-of! class)))\n    (assert class)\n    (apply '%new-instance class args)))\n\n#+ccl\n(defun new-instance (class &rest args)\n  (safe-jtol\n   (apply 'cl+j:jnew (str:replace-all \"/\" \".\" (cl+j::java-class-name (jclass-of! class)))\n           (mapcar 'fix-arg-for-jmethod-invoke args))))\n\n(define-java-callers \"java.lang.Object\"\n  (java-equals \"equals\" :signatures (\"java.lang.Object\")\n               :return-type :boolean))\n\n(defun jclass-of (sym)\n  (if (classp sym)\n      sym\n      (when *type-locator*\n        (%%locate *type-locator* sym))))\n\n(defun jclass-of! (sym)\n  (let ((ret (jclass-of sym)))\n    (if ret\n        ret\n        (error (format nil \"null class for ~a\" sym)))))\n\n(defun jobject-of-class-p (obj class-name)\n  ;;(format t \"testing jobject with ~s, ~s~%\" obj class-name)\n  (finish-output t)\n  (#+lispworks lw-ji:jobject-of-class-p\n   #+ccl (lambda (obj class-name)\n           (fix-return-type\n            (cl+j::jmethod-instance (cl+j::java-class-real (find-java-class class-name))\n                                    \"isInstance\"\n                                    (fix-arg-for-jmethod-invoke obj))\n            :boolean))\n   obj class-name))\n\n(defun classp (obj)\n  (and\n   (jobject-p obj)\n   (jobject-of-class-p obj \"java.lang.Class\")))\n\n#+ccl\n(defun java-array-length (array) (cl+j::jfield-int array \"length\")) ;;todo: this is probably wrong\n\n(defun array->list (arr)\n  #+nil\n  (lw-ji:map-java-object-array 'identity arr :convert t :write-back t)\n  (when arr\n   (loop for i from 0 to (- (java-array-length arr) 1)\n      collect (safe-jvref arr i))))\n\n#+lispworks\n(defun safe-make-java-array (type dims)\n  (let ((type (jclass-of! type)))\n    (lw-ji:make-java-array (class-to-lispworks-locator type) dims)))\n\n#+ccl\n(defun safe-make-java-array (type dims)\n  (cl+j::jnew[] (cl+j::java-class-name (jclass-of! type)) dims))\n\n\n(defun primitive-locator (sym)\n  (if (equal (string-downcase sym) (symbol-name sym))\n      (setf sym (intern (string-upcase sym) \"KEYWORD\")))\n  (flet ((unsupported () (error \"unsupported type ~a\" sym)))\n   (unless (str:contains? \".\" (symbol-name sym))\n     (let ((name (intern (symbol-name sym) \"KEYWORD\")))\n       (loop for primitive in '(:byte :char :double :float :int :long :short :boolean :void)\n          if (eq primitive name)\n            do (return (find-java-class name)))))))\n\n(defun fqn-locator (sym)\n  (ignore-errors\n   (find_class2 (symbol-name sym))))\n\n(defclass type-locator ()\n  ((cache :initform (make-hash-table :test 'equal))\n   (package :initarg :package)\n   (imports :initarg :imports)\n   (class :initarg :class)))\n\n(defun make-type-locator (&key package imports class)\n  (unless (or (null class) (stringp class))\n    (error \"expected string, got ~a\" class))\n  (unless (or (null package) (stringp package))\n    (error \"expected string, got ~a\" package))\n  (make-instance 'type-locator\n                 :package package\n                 :imports imports\n                 :class class))\n\n(defun java-lang-locator (sym)\n  (package-locate \"java.lang\" sym))\n\n(defun package-locate (package sym)\n  (find_class2 (format nil \"~a.~a\" package (symbol-name sym))))\n\n(defun msubstring (start end s)\n  \"Too lazy to refactor this out.\"\n  (subseq s start end))\n\n(defun locate-from-imports (imports suffix)\n  (when imports\n    (let ((first (car imports))\n          (rest (cdr imports)))\n      (cond\n        ((str:ends-with-p suffix first)\n         (find_class2 first))\n        (t\n         (or\n          (when (str:ends-with-p  \".*\" first)\n            (let ((prefix (msubstring 0 (+ (length first) -2) first)))\n              (let ((new-name (format nil \"~a~a\" prefix suffix)))\n                (find_class2 new-name))))\n          (locate-from-imports rest suffix)))))))\n\n(defun find_class2 (obj)\n  #+ccl\n  (declare (optimize (speed 0) (debug 3)))\n  (or\n   (primitive-locator (intern obj \"KEYWORD\"))\n   (ignore-errors\n    (handler-case\n        (find-java-class (string obj))\n      #+ccl\n      (jni:thrown-from-java (e)\n        nil)))))\n\n(defparameter *default-locator-impl* (make-type-locator :package \"\"\n                                                  :imports '(\"java.lang.*\")))\n\n(defparameter *type-locator* *default-locator-impl*)\n\n(defun %pp (x expr)\n  ;;(log:info \"Got: ~S for ~S\" x expr)\n  x)\n\n(defmacro pp (x)\n  `(%pp ,x ',x))\n\n(defun %%locate-uncached (type-locator sym)\n  (when type-locator\n   (with-slots (package imports class) type-locator\n     (or\n      (pp (fqn-locator sym))\n      (pp (java-lang-locator sym))\n      (pp (package-locate package sym))\n      (pp (locate-from-imports imports (format nil \".~a\" (string sym))))))))\n\n(defun %%locate (type-locator sym)\n  #+ccl ;; haven't debugged this enough to use caching\n  (%%locate-uncached type-locator sym)\n\n  #+lispworks\n  (with-slots (cache) type-locator\n    (symbol-macrolet ((place (gethash sym cache)))\n      (multiple-value-bind (ret) place\n        (or ret\n            (setf place (%%locate-uncached type-locator sym)))))))\n\n(defun wait-for-java ()\n  #+lispworks\n  (loop for i from 0 to 300\n        if (ignore-errors (lw-ji:find-java-class \"java.lang.String\"))\n          return t\n        else do\n          (progn\n            (log:info \"Java not ready yet, sleeping for 1s\")\n            (sleep 1))\n        finally\n        (error \"JVM did not load in five minutes\")))\n\n(defun class-to-lispworks-locator (typename)\n  (if (and\n       (jobject-p typename)\n       (jobject-of-class-p typename \"java.lang.Class\"))\n      (setf typename (intern (invoke typename :|getName|) \"KEYWORD\")))\n  (let ((primitives (list :int :char :boolean :short :long  :double :float :byte)))\n    (dolist (prim primitives)\n      (if (equal (string-upcase prim) (string-upcase typename))\n          (return-from class-to-lispworks-locator prim)))\n    (symbol-name typename)))\n\n(defun java-array-p (x)\n  (and\n   (jobject-p x)\n   (java-class-is-array (java-get-class x))))\n\n\n(defun list->array (type list)\n  (let ((ret (safe-make-java-array type (length list)))\n        (idx 0))\n    (loop for e in list\n       do\n         (setf (safe-jvref ret idx) e)\n         (setf idx (+ 1 idx)))\n    ret))\n\n\n(defun (setf safe-jvref) (val arr i)\n  (setf (jvref arr i) val))\n"
  },
  {
    "path": "src/util/java/reader.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :util/java/reader\n  (:use #:cl)\n  #+ (or ccl lispworks)\n  (:import-from #:util/java/java\n                #:invoke\n                #:%%read-java-field\n                #:array->list)\n  (:export #:java-list->list\n           #:java-syntax))\n\n(defun read-case-sensitive-symbol (stream char recursive)\n  (declare (ignore recursive char))\n  (let ((*readtable* (copy-readtable nil))\n        (*package* (find-package \"KEYWORD\")))\n    (setf (readtable-case *readtable*) :preserve)\n    (read stream t t)))\n\n(defun read-invoke (stream char recursive)\n  (declare (ignore recursive))\n  (let ((name (read-case-sensitive-symbol stream char t)))\n    `(lambda (obj &rest args)\n       (apply 'invoke obj ',name args))))\n\n(defun read-comma-syntax (stream char recursive)\n  `(quote ,(read-case-sensitive-symbol stream char recursive)))\n\n(named-readtables:defreadtable java-syntax\n  (:merge :standard)\n  (:dispatch-macro-char #\\# #\\_ 'read-invoke)\n  (:dispatch-macro-char #\\# #\\, 'read-comma-syntax))\n\n(defun java-list->list (java-list)\n  (array->list (invoke java-list :|toArray|)))\n\n(defun read-java-field (obj name)\n  (%%read-java-field obj (string name)))\n"
  },
  {
    "path": "src/util/java/test-binding.lisp",
    "content": "(pkg:define-package :util/java/test-binding\n    (:use #:cl\n          #:fiveam\n          #:fiveam-matchers)\n  (:import-from #:util/java\n                #:java-syntax\n                #:new-instance)\n  (:import-from #:util/java/binding\n                #:javafy-name\n                #:bind-instance))\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable java-syntax)\n\n(defclass user ()\n  ((url :initarg :url\n        :accessor user-url)\n   (login :initarg :login\n          :accessor login)))\n\n(defclass dummy () ())\n\n(def-fixture state ()\n  (let ((user (new-instance #,org.eclipse.egit.github.core.User)))\n    (#_setUrl user \"https://github.com/foo\")\n    (#_setLogin user \"tdrhq\")\n    (&body)))\n\n(test javafy-name\n  (assert-that\n   (javafy-name 'foo-bar)\n   (equal-to \"getFooBar\"))\n  (assert-that\n   (javafy-name 'foo-bar-p)\n   (equal-to \"isFooBar\")))\n\n(test simple-binding\n  (with-fixture state ()\n    (let ((user (bind-instance 'user user)))\n      (assert-that\n       user\n       (has-typep 'user))\n      (assert-that\n       (user-url user)\n       (equal-to \"https://github.com/foo\"))\n      (assert-that\n       (login user)\n       (equal-to \"tdrhq\")))))\n"
  },
  {
    "path": "src/util/java/test-iterate.lisp",
    "content": "(pkg:define-package :util/java/test-iterate\n    (:use #:cl\n          #:fiveam\n          #:iterate\n          #:alexandria)\n  (:import-from #:util/java/java\n                #:new-instance))\n\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable util/java:java-syntax)\n\n(test simple-iterate\n  (let ((list (new-instance #,java.util.ArrayList)))\n    (#_add list \"one\")\n    (#_add list \"two\")\n    (#_add list \"three\")\n    (is (equal (list \"one\" \"two\" \"three\")\n               (iter (for x in-java list)\n                 (collect x))))))\n"
  },
  {
    "path": "src/util/java/test-java.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package #:util/java/test-java\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/java/java\n                #:*type-locator*\n                #+ccl\n                #:fix-arg-for-jmethod-invoke\n                #:safe-make-java-array\n                #:java-objects-eq\n                #:invoke\n                #:define-java-callers\n                #:jvref\n                #:java-array-p\n                #:list->array\n                #:java-equals\n                #:new-instance\n                #:find-java-class\n                #:find_class2\n                #:jclass-of\n                #:primitive-locator\n                #:make-type-locator))\n\n(defvar *jippo-locator*\n  (make-type-locator\n   :package \"com.tdrhq\"\n   :imports '(\"java.util.*\"\n              \"java.io.*\"\n              \"java.lang.reflect.*\"\n              \"com.tdrhq.*\"\n              \"com.tdrhq.core.*\"\n              \"com.tdrhq2.*\"\n              \"io.jipr.eyepatch.iface.*\"\n              \"org.apache.commons.lang3.reflect.*\")))\n\n(defmacro assert-jeq (x y)\n  `(is (java-objects-eq ,x ,y)))\n\n(defmacro assert-jequal (x y)\n  `(is (java-equals ,x ,y)))\n\n(defvar *java-lang-locator* (make-type-locator :package \"\" :imports nil :class \"FooBar\"))\n\n(test simple-location\n  (let ((*type-locator* *java-lang-locator*))\n    #+ccl\n    (is (eq 'cl+j::java-class (type-of (jclass-of '|String|))))\n    (is (java-objects-eq (find_class2 \"java.lang.String\") (jclass-of '|String|)))\n    (is (java-objects-eq (find_class2 \"java.lang.Long\") (jclass-of '|Long|)))))\n\n(test without-locator\n  (is (java-objects-eq (find_class2 \"java.lang.String\") (jclass-of '|java.lang.String|))))\n\n(test with-package\n  (let ((*type-locator* (make-type-locator :package \"com.tdrhq\")))\n    (is (java-objects-eq (find_class2 \"com.tdrhq.SimpleNativeLibrary\") (jclass-of '|SimpleNativeLibrary|)))\n    (is (java-objects-eq (find_class2 \"java.lang.String\") (jclass-of '|String|)))))\n\n(test with-imports\n  (let ((*type-locator* (make-type-locator\n                         :package \"com.tdrhq\"\n                         :imports '(\"java.io.File\" \"java.io.IOException\"))))\n    (is (java-objects-eq (find_class2 \"com.tdrhq.SimpleNativeLibrary\") (jclass-of '|SimpleNativeLibrary|)))\n    (is (java-objects-eq (find_class2 \"java.lang.String\") (jclass-of '|String|)))\n    (is (java-objects-eq (find_class2 \"java.io.IOException\") (jclass-of '|IOException|)))\n    (is (java-objects-eq (find_class2 \"java.io.File\") (jclass-of '|File|)))))\n\n(test with-wildcards\n  (let ((*type-locator* (make-type-locator\n                         :package \"com.tdrhq\"\n                         :imports '(\"java.io.*\"))))\n    (assert-jeq (find_class2 \"com.tdrhq.SimpleNativeLibrary\") (jclass-of '|SimpleNativeLibrary|))\n    (assert-jeq (find_class2 \"java.lang.String\") (jclass-of '|String|))\n    (assert-jeq (find_class2 \"java.io.IOException\") (jclass-of '|IOException|))\n    (assert-jeq (find_class2 \"java.io.File\") (jclass-of '|File|))\n    (assert-jeq (find_class2 \"int\") (jclass-of :int))))\n\n(test new-instance\n  (let ((*type-locator* *jippo-locator*))\n    (format t \"trying String~%\")\n    (is (equal \"\" (new-instance '|String|)))\n    (format t \"trying String foo~%\")\n    (is (equal \"foo\" (new-instance '|String| \"foo\")))\n    (format t \"trying Integer 20~%\")\n    (is (equal 20 (new-instance '|Integer| \"20\")))))\n\n(test invoke\n  (let ((*type-locator* *jippo-locator*))\n    (is (equal 6 (invoke \"foobar\" '|length|)))\n    (is (equal 4 (invoke \"foobar\" '|indexOf| \"a\")))\n    (is (equal \"4\" (invoke '|String| '|valueOf| 4)))))\n\n(test use-keywords\n  (let ((*type-locator* *jippo-locator*))\n    (is (equal 6 (invoke \"foobar\" :|length|)))\n    (is (equal 4 (invoke \"foobar\" \"indexOf\" \"a\")))\n    (is (equal \"4\" (invoke :|String| \"valueOf\" 4)))))\n\n\n;; todo: finish before landing\n(test read-field\n  (let ((*type-locator* *jippo-locator*))\n    (is (equal 64 (util/java:read-java-field '|java.lang.Long| '|SIZE|)))\n    (assert-jequal (new-instance '|java.lang.Boolean| \"true\") (util/java:read-java-field '|java.lang.Boolean| '|TRUE|))))\n\n(test make-hash-table\n  (let ((ht (make-hash-table)))\n    (setf (gethash 'foo ht) 'bar)\n    (is (eq 'bar (gethash 'foo ht)))\n    (is (eq nil (gethash 'car ht)))))\n\n\n#+lispworks\n(define-java-callers \"java.lang.Integer\"\n  (int-value \"valueOf\" :signatures (\"int\")))\n\n#+ccl\n(defun int-value (x)\n  (cl+j:jmethod \"java.lang.Integer\" \"valueOf\" (cl+j:jprim-int x)))\n\n(test make-java-array\n  (let ((arr (safe-make-java-array '|java.lang.String| 10)))\n    (setf (jvref arr 0) \"foo\")\n    (is (equal \"foo\" (jvref arr 0))))\n\n  (let ((arr (list->array '|java.lang.Integer| (mapcar 'int-value '(1 2 3)))))\n    ;; there's a small behaviour difference that I'm too lazy to\n    ;; fix. Hopefully this doesn't affect anything in screenshotbot\n    ;; itself. It's rare that I call jvref directly. This was mostly\n    ;; for JIPR.\n    #+ccl\n    (progn\n      (is (eql 1 (jvref arr 0)))\n      (is (eql 2 (jvref arr 1)))\n      (is (eql 3 (jvref arr 2))))\n    #+lispworks\n    (progn\n      (assert-jequal (int-value 1) (jvref arr 0))\n      (assert-jequal (int-value 2) (jvref arr 1))\n      (assert-jequal (int-value 3) (jvref arr 2)))))\n\n(test list->array\n  (log:debug \"class is ~a~%\"\n          (invoke\n           (invoke (list->array '|java.lang.Integer| (mapcar 'int-value '(1 2 3))) '|getClass|)\n           '|isArray|))\n  (is-true (java-array-p (list->array '|java.lang.Integer| (mapcar 'int-value '(1 2 3)))))\n  (is-false (java-array-p '(1 2 3))))\n\n(test primitive-lookup\n  (is (java-objects-eq (find-java-class :int) (primitive-locator :int))))\n\n#+ccl\n(test fix-arg-for-jmethod-invoek\n  (is (not (stringp (fix-arg-for-jmethod-invoke \"foo\")))))\n"
  },
  {
    "path": "src/util/java/util.java.asd",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :util.java\n  :author \"Arnold Noronha <arnold@screenshotbot.io>\"\n  :license \"Mozilla Public License, v 2.0\"\n  :serial t\n  :depends-on (:str\n               :pkg\n               :log4cl\n               :iterate\n               :closer-mop\n               ;; We never want to load cl+j through quicklisp, always\n               ;; call jvm:jvm-init instead.\n               ;; :cl+j\n               :named-readtables)\n  :components (#+ (or ccl lispworks)\n               (:file \"java\")\n               (:file \"reader\")\n               #+ (or ccl lispworks)\n               (:file \"binding\")\n               #+ (or ccl lispworks)\n               (:file \"iterate\")\n               (:file \"all\")))\n\n#-eaase-oss\n(defsystem :util.java/tests\n    :serial t\n  :depends-on (:fiveam\n               :fiveam-matchers\n               :util.java)\n  :components (#+jvm-supported-p\n               (:file \"test-binding\")\n               #+:jvm-supported-p\n               (:file \"test-java\")\n               #+:jvm-supported-p\n               (:file \"test-iterate\")))\n"
  },
  {
    "path": "src/util/json-mop.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/json-mop\n  (:use #:cl)\n  (:import-from #:json-mop\n                #:to-lisp-value\n                #:to-json-value\n                #:json-type)\n  (:export\n   #:json-mop-to-string\n   #:ext-json-serializable-class))\n(in-package :util/json-mop)\n\n(defclass ext-json-serializable-class (json-mop:json-serializable-class)\n  ())\n\n(defmethod closer-mop:validate-superclass ((class ext-json-serializable-class)\n                                           (super closer-mop:standard-class)) t)\n\n(defmethod closer-mop:validate-superclass ((class standard-class)\n                                           (super ext-json-serializable-class)) t)\n\n(defclass ext-json-serializable-slot (json-mop::json-serializable-slot)\n  ())\n\n(defmethod closer-mop:direct-slot-definition-class ((class ext-json-serializable-class)\n                                                    &rest initargs)\n  (declare (ignore initargs))\n  (find-class 'ext-json-serializable-slot))\n\n(defclass nullable ()\n  ((type :initarg :type\n         :reader nullable-type)))\n\n(defmethod json-type ((slot ext-json-serializable-slot))\n  (let ((type (call-next-method)))\n    (cond\n      ((and\n        (consp type)\n        (eql 'or (first type))\n        (eql 'null (second type)))\n       (make-instance 'nullable :type (third type)))\n      (t\n       type))))\n\n(defmethod to-json-value ((value null)\n                          (type nullable))\n  value)\n\n(defmethod to-json-value (value\n                          (type nullable))\n  (to-json-value value (nullable-type type)))\n\n(defmethod to-lisp-value ((value null)\n                          (type nullable))\n  nil)\n\n(defmethod to-lisp-value (value\n                          (type nullable))\n  (to-lisp-value value (nullable-type type)))\n\n;; Note that this changes the behavior of json-mop everywhere.. not\n;; just when using with ext-json-serializable-class.\n(defmethod to-json-value :around ((value null) (json-type cons))\n  \"Return the homogeneous sequence VALUE\"\n  (cond\n    ((member (first json-type)\n             '(:list :vector))\n     #())\n    (t\n     (call-next-method))))\n\n(defun json-mop-to-string (obj)\n  (with-output-to-string (out)\n    (yason:encode obj out)))\n"
  },
  {
    "path": "src/util/logger.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/logger\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:alexandria\n                #:when-let)\n  (:export\n   #:logger\n   #:format-log\n   #:noop-logger))\n(in-package :util/logger)\n\n(defclass logger ()\n  ((lock :initform (bt:make-lock \"logger lock\"))\n   (file :initarg :file)))\n\n(defclass noop-logger ()\n  ())\n\n(def-easy-macro with-logger-stream (logger &binding stream &fn fn)\n  (with-slots (file lock) logger\n   (bt:with-lock-held (lock)\n     (with-open-file (stream file :if-exists :append :direction :output\n                             :if-does-not-exist :create)\n       (fn stream)))))\n\n(defun format-ts (stream ts)\n  (local-time:format-timestring\n   stream\n   ts :format `(\"[\" (:hour 2) \":\" (:min 2) \":\" (:sec 2) \"] \")))\n\n(defmethod format-log  ((logger logger)\n                        level\n                        message\n                        &rest args)\n  (let ((time (format nil \"~a\" (local-time:now)))\n        (str (apply #'format nil message args)))\n    (with-logger-stream (logger stream)\n      ;; While the log is held, avoid crashes\n      (format stream \"~a: \" level)\n      (write-string time stream)\n      (write-string \" \" stream)\n      (write-string str stream)\n      (format stream \"~%\"))))\n\n(defmethod format-log  ((logger null)\n                        level\n                        message\n                        &rest args)\n  ;; Sanity check to make sure our format args are correct in tests\n  (apply #'format nil message args))\n\n(defmethod format-log  ((logger noop-logger)\n                        level\n                        message\n                        &rest args)\n  (log:info \"~a\"\n            (apply #'format nil message args)))\n"
  },
  {
    "path": "src/util/logrotate.lisp",
    "content": "(defpackage :util/logrotate\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/logrotate)\n\n(defun logrotate (file\n                  &key (max-size 10 #| max size in MB |#)\n                    (rotate 5))\n  (let ((state-file (pathname \"~/.logrotate.status\")))\n\n    (uiop:with-temporary-file (:stream config\n                               :pathname config-pathname)\n      (format config\n              \"~a {\n   rotate ~a\n   weekly\n   size ~ak\n   nocompress\n}\n\"\n             (namestring file)\n             rotate\n             (* max-size 1024))\n     (close config)\n     (multiple-value-bind (out err ret)\n         (uiop:run-program (list \"/sbin/logrotate\"\n                                 \"--state\" (namestring state-file)\n                                 (namestring config-pathname))\n                           :output t\n                           :error-output 'string\n                           :ignore-error-status t)\n       (declare (ignore out))\n       (unless (= ret 0)\n         (error \"logrotate error: ~a\" err))))))\n"
  },
  {
    "path": "src/util/lparallel.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/lparallel\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:immediate-promise\n   #:with-temp-lparallel-kernel))\n(in-package :util/lparallel)\n\n(defvar *sleep-time* 1)\n\n(defun immediate-promise (val)\n  (let ((promise (lparallel:promise)))\n    (lparallel:fulfill promise val)\n    promise))\n\n(def-easy-macro with-temp-lparallel-kernel (&key (threads 20)\n                                                 &fn fn)\n  (let ((shutting-down-p nil))\n    (let ((lparallel:*kernel* (lparallel:make-kernel threads\n                                                     #+nil #+nil\n                                                     :context #'context)))\n      \n      (unwind-protect\n           (funcall fn)\n        (lparallel:end-kernel :wait t)))))\n\n(defvar *kernel-for-tests* nil\n  \"A kernel that is only used during tests, but is cached across\ntests. It's the tests responsibility to clean up any background jobs.\")\n\n(def-easy-macro with-test-lparallel-kernel (&fn fn)\n  (util/misc:or-setf\n   *kernel-for-tests*\n   (lparallel:make-kernel 20))\n  (assert (not lparallel:*kernel*))\n  (setf lparallel:*kernel* *kernel-for-tests*)\n  (unwind-protect\n       (fn)\n    (setf lparallel:*kernel* nil)))\n\n\n;; temporary: just needed for a hot-reload before using lparallel more rigorously\n(setf lparallel:*debug-tasks-p* nil)\n"
  },
  {
    "path": "src/util/lru-cache.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/lru-cache\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/misc\n                #:parse-size)\n  (:export\n   #:lru-cache\n   #:with-cache-file\n   #:dir))\n(in-package :util/lru-cache)\n\n(defclass lru-cache ()\n  ((dir :initarg :dir\n        :reader dir)\n   (lock :initform (bt:make-lock)\n         :reader lock)\n   (queue-head :initform nil\n               :accessor queue-head)\n   (queue-tail :initform nil\n               :accessor queue-tail)\n   (queue-count :initform 0\n                :accessor queue-count)\n   (queue-length :initform 0\n                 :accessor queue-length)\n   (cache-size :initform 0\n               :accessor cache-size\n               :documentation \"The size of the cache in bytes\")\n   (cons-map :initform (make-hash-table :test #'equal)\n             :reader cons-map)\n   (max-size :initform (parse-size \"4GB\")\n             :reader max-size))\n  (:documentation \"An LRU cache for items stored on disk\")\n  (:default-initargs :max-size \"4GB\"))\n\n(defclass item ()\n  ((key :initarg :key\n        :reader item-key)\n   (size :initarg :size\n         :reader item-size)))\n\n(defmethod file-atime ((self lru-cache) file)\n  (or\n   #+(and lispworks (not mswindows))\n   (let ((stat (sys:get-file-stat file)))\n     (sys:file-stat-last-access stat))\n   (file-write-date file)))\n\n(defmethod read-all-files ((self lru-cache) directory)\n  (let (ret)\n    (labels ((dfs (dir parts)\n              (loop for item in (fad:list-directory dir\n                                                    :follow-symlinks nil)\n                    do\n                       (cond\n                         ((path:-d item)\n                          (dfs item (list*\n                                     (car (last (pathname-directory item)))\n                                     parts)))\n                         (t\n                          (push\n                           (cons\n                            (file-atime self item)\n                            (make-instance\n                             'item\n                             :key (str:join \"/\"\n                                            (reverse\n                                             (list*\n                                              (format nil \"~a.~a\"\n                                                      (pathname-name item)\n                                                      (pathname-type item))\n                                              parts)))\n                             :size (trivial-file-size:file-size-in-octets item)))\n                           ret))))))\n     (dfs directory nil)\n     ret)))\n\n(defmethod initialize-instance :after ((self lru-cache)\n                                       &key dir\n                                         max-size\n                                       &allow-other-keys)\n  (setf (slot-value self 'max-size)\n        (parse-size max-size))\n  (let ((files (read-all-files self dir)))\n    (setf (queue-head self)\n          (mapcar #'cdr\n                  (sort files #'< :key #'car)))\n    (loop for item in (queue-head self)\n          do (incf-cache-size self item))\n    (reset-state self)))\n\n(defmethod incf-cache-size ((self lru-cache)\n                            (item item))\n  (incf (cache-size self)\n        (item-size item)))\n\n(defmethod decf-cache-size ((self lru-cache)\n                            (item item))\n  (decf (cache-size self)\n        (item-size item)))\n\n\n(defmethod reset-state ((self lru-cache))\n  (setf (queue-tail self)\n        (last (queue-head self)))\n  (loop for cons on (queue-head self)\n        do\n           (setf (gethash (item-key (car cons)) (cons-map self))\n                 cons))\n  (let ((len (length (queue-head self))))\n\n    (setf (queue-length self) len\n          (queue-count self) len)))\n\n(def-easy-macro with-cache-file (&binding pathname cache key &fn fn)\n  (assert key)\n  (let ((pathname (path:catfile (dir cache) key)))\n   (unwind-protect\n        (funcall fn pathname)\n     (bump-key cache pathname key))))\n\n(defmethod maybe-purge-entries ((self lru-cache))\n  \"If required, purge old entries\"\n  (loop\n    for item in (queue-head self)\n    while (> (cache-size self)\n             (max-size self))\n    if item\n      do\n         (purge-file-on-disk self (item-key item))\n         (remove-item self (item-key item))))\n\n(defmethod purge-file-on-disk ((self lru-cache)\n                               key)\n  (let ((pathname (path:catfile (dir self) key)))\n    (delete-file pathname)))\n\n(defmethod remove-item ((cache lru-cache)\n                        key)\n  (let ((key (namestring key)))\n   (let ((prev-cons (gethash key (cons-map cache))))\n     (when prev-cons\n       (let ((item (car prev-cons)))\n         (setf (car prev-cons) nil)\n         (remhash key (cons-map cache))\n         (decf (queue-count cache))\n         (decf-cache-size cache item))))))\n\n(defun bump-key (cache pathname key)\n  (let ((key (namestring key)))\n   (bt:with-lock-held ((lock cache))\n     ;; remove the item if it exists in the queue\n     (remove-item cache key)\n     (let* ((item (make-instance 'item\n                                 :key key\n                                 :size (or\n                                        (ignore-errors\n                                         (trivial-file-size:file-size-in-octets pathname))\n                                        0)))\n            (new-tail (cons\n                       item\n                       nil)))\n       (incf-cache-size cache item)\n       (setf (gethash key (cons-map cache)) new-tail)\n       (cond\n         ((queue-tail cache)\n          (setf (cdr (queue-tail cache)) new-tail)\n          (setf (queue-tail cache) new-tail)\n          (incf (queue-length cache))\n          (incf (queue-count cache)))\n         (t\n          (setf (queue-head cache) new-tail)\n          (setf (queue-tail cache) new-tail)\n          (reset-state cache)))\n       (maybe-trim-queue cache)\n       (maybe-purge-entries cache)))))\n\n(defmethod maybe-trim-queue ((cache lru-cache))\n  (when (> (queue-length cache)\n           (max\n            10\n            (* 2 (queue-count cache))))\n    (setf (queue-head cache)\n          (remove-if #'null (queue-head cache)))\n    (reset-state cache)))\n"
  },
  {
    "path": "src/util/mail.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :util)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defun parse-email-list (s)\n  (cond\n    ((stringp s)\n     (mapcar 'str:trim (str:split \",\" s)))\n    (t s)))\n\n(defun token-safe-for-email-p (token)\n  \"Check if the given token is safe to use in an email. For instance,\nthe token \\\"https://example.com\\\" is unsafe, because Gmail will format\nthat as a link which can be used for phishing.\"\n  (not\n   (or\n    (str:containsp \"https:\" token)\n    (str:containsp \"http:\" token)\n    (str:containsp \"www.\" token))))\n\n(defun make-mail-message-id ()\n  (format nil \"<~a@tdrhq.com>\"\n          (mongoid:oid-str (mongoid:oid))))\n\n(defun send-mail (&key to bcc (subject \"\") message (from (error \"specify from\")) html-message\n                    attachments\n                    (message-id (make-mail-message-id))\n                    reply-to)\n  \"HTML message must be a markup object. Returns the Message-id of the message\"\n  (check-type subject string)\n  (loop for a in (alexandria:flatten (list attachments))\n        if (or (pathnamep a) (stringp a))\n          do (assert (path:-e a)))\n  (restart-case\n     (progn\n       (cl-smtp:send-email\n        \"localhost\"\n        from\n        to\n        subject\n        (or message (html2text html-message))\n        :bcc bcc\n        :port (if (equal \"thecharmer\" (uiop:hostname))\n                  2025\n                  25)\n        :reply-to reply-to\n        :attachments attachments\n        :extra-headers `((\"Message-ID\" ,message-id))\n        :html-message (if html-message\n                          (markup:write-html html-message)))\n       (values message-id))\n    (dont-send-the-mail ()\n      nil)))\n"
  },
  {
    "path": "src/util/make-instance-with-accessors.lisp",
    "content": "(uiop:define-package :util/make-instance-with-accessors\n  (:use #:cl\n        #:alexandria)\n  (:export\n   #:make-instance-with-accessors))\n(in-package :util/make-instance-with-accessors)\n\n(defun rename-accessor (class accessor)\n  (declare (optimize debug))\n  (loop for slot in (closer-mop:class-direct-slots class)\n        for readers = (closer-mop:slot-definition-readers slot)\n        if (member accessor readers)\n          do (return (car (closer-mop:slot-definition-initargs slot)))\n        finally\n         (error \"Could not find initarg for ~s\" accessor)))\n\n(defun make-instance-with-accessors (class &rest args)\n\n  (let ((args (loop for (x y) on args by #'cddr\n                    appending (list (rename-accessor (find-class class) x) y))))\n    (apply #'make-instance class args)))\n"
  },
  {
    "path": "src/util/memory.lisp",
    "content": "(defpackage :util/memory\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:process-mem-usage\n   #:arena-size\n   #:histogram))\n(in-package :util/memory)\n\n(defun safe-type-of (x)\n  (let ((type (type-of x)))\n    (cond\n      ((listp type)\n       (Car type))\n      (t\n       type))))\n\n(defun histogram (&key by-size)\n  (let ((map (make-hash-table :test #'eql))\n        (sizes (make-hash-table :test #'eql)))\n    (hcl:sweep-all-objects\n     (lambda (obj)\n       (incf (gethash (safe-type-of obj) map 0))\n       (incf (gethash (safe-type-of obj) sizes 0)\n             (hcl:find-object-size obj))))\n    (let ((list (loop for k being the hash-keys in map\n                        using (hash-value v)\n                      collect (cons k v))))\n      (let ((ret (sort list #'>\n                       :key (cond\n                              (by-size\n                               (lambda (x)\n                                 (gethash (car x) sizes)))\n                              (t #'cdr)))))\n        (loop for (k . v) in ret\n              for i below 100\n              do (format t \"~a ~s ~a~%\" v k (gethash k sizes)))))))\n\n(defun objects-of-type (type)\n  (let ((ret))\n    (hcl:sweep-all-objects\n     (lambda (obj)\n       (when (typep obj type)\n         (push obj ret))))\n    ret))\n\n(defun weighted-random-sample (objects weight-fn)\n  (let* ((total (loop for obj in objects\n                      summing (funcall weight-fn obj)))\n         (pt (random total)))\n    (let ((curr 0))\n      (dolist (obj objects)\n        (incf curr (funcall weight-fn obj))\n        (when (> curr pt)\n          (return obj))))))\n\n(defun process-mem-usage ()\n  (let ((ret 0))\n    #+linux\n    (let ((pid (util/posix:getpid)))\n      (with-open-file (stream (format nil \"/proc/~d/smaps\" pid))\n        (loop for line = (read-line stream nil nil)\n              while line\n              for parts = (str:split \":\" line)\n              do\n                 (when (string= \"Pss\" (first parts))\n                   (incf ret (parse-integer (str:trim (second parts))  :junk-allowed t))))))\n    ret))\n\n\n;; (histogram)\n;; (random-sample:random-sample (objects-of-type 'lw:simple-text-string) 100)\n;; (weighted-random-sample (objects-of-type 'lw:simple-text-string) #'hcl:find-object-size)\n\n\n(fli:define-foreign-function fmemopen\n    ((buf :lisp-simple-1d-array)\n     (size :size-t)\n     (open-type (:reference-pass :ef-mb-string)))\n  :result-type :pointer)\n\n(fli:define-foreign-function fclose\n    ((buf :pointer))\n  :result-type :int)\n\n(fli:define-foreign-function (%malloc-info \"malloc_info\")\n    ((options :int)\n     (string :pointer))\n  :result-type :int)\n\n;; mallinfo2 is the same as mallinfo but uses :size-t. However, it's\n;; introduced in glibc 2.33, which is currently not what I'm using. In\n;; the future we can change this.\n(fli:define-c-struct mallinfo\n    (arena :int\n           :documentation \"Non-mmaped space allocated (bytes)\")\n  (ordblks :int)\n  (smblks :int)\n  (hblks :int\n         :documentation \"Number of mmapped regions\")\n  (hblkhd :int\n          :documentation \"Space allocation in mmaped regions (bytes)\")\n  (usmblks :int)\n  (fsmblks :int)\n  (uordblks :int)\n  (fordblks :int)\n  (keepcost :int))\n\n(fli:define-foreign-function mallinfo\n    ()\n  :result-type mallinfo)\n\n(defun arena-size ()\n  (fli:with-dynamic-foreign-objects ((mallinfo mallinfo))\n    (mallinfo :result-pointer mallinfo)\n    (values\n     (fli:foreign-slot-value mallinfo 'arena)\n     (fli:foreign-slot-value mallinfo 'hblkhd))))\n\n(defun malloc-info ()\n  (let* ((size 202400)\n         (str (make-array (+ 100 size)\n                          :element-type '(unsigned-byte 8)\n                          :allocation :pinnable)))\n    (let ((file (fmemopen str size \"w\")))\n      (unwind-protect\n           (let ((err (%malloc-info 0 file)))\n             (unless (= err 0)\n               (error \"malloc-info failed ~a\" err))\n             (flex:octets-to-string str\n                                    :end (position 0 str)))\n        (fclose file)))))\n\n;; (format t \"~a\" (malloc-info))\n"
  },
  {
    "path": "src/util/misc/lists.lisp",
    "content": "(defpackage #:util/misc/lists\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export #:head\n\t   #:tail\n       #:make-batches\n       #:with-batches))\n(in-package #:util/misc/lists)\n\n;; See also 'last' and 'butlast'\n\n(defun fix-n (list n)\n  (if (< n 0)\n      (+ (length list) n)\n      n))\n\n(defun head (list n &key (filter (lambda (x) (declare (ignore x)) t)))\n  \"Returns the first n elements of the list, or the entire list if the list has fewer than n elements.\n\n If filter is provided, it returns only the first n elements that matches the filter.\n\nIn either case, we also return a second value which the all the\nremaining elements (unfiltered)\"\n  (let ((n (fix-n list n)))\n    (labels ((head (list n prefix)\n               (cond\n                 ((or\n                   (eql list nil)\n                   (eql n 0))\n                  (values (nreverse prefix) list))\n                 ((funcall filter (car list))\n                  (head (cdr list) (-  n 1)\n                        (list* (car list) prefix)))\n                 (t\n                  (head (cdr list) n prefix)))))\n\n      (head list n nil))))\n\n(defun tail (list n)\n  \"Returns all the elements from the (n+1)th element\"\n  (let ((n (fix-n list n)))\n    (cond\n      ((eq list nil) nil)\n      ((<= n 0) list)\n      (t (tail (cdr list) (- n 1))))))\n\n\n(def-easy-macro with-batches (&binding batch\n                                       list &fn fn &key (batch-size 10)\n                                       &binding index)\n\n  (labels ((wrapped-fn (batch ctr)\n             (restart-case\n                 (funcall fn batch ctr)\n               (restart-batch ()\n                 (wrapped-fn batch ctr))))\n           (call-next (list ctr)\n             (multiple-value-bind (batch rest)\n                 (util/misc/lists:head list batch-size)\n               (when batch\n                 (wrapped-fn batch ctr)\n                 (call-next rest (+ ctr (length batch)))))))\n    (call-next list 0)))\n\n(defun make-batches (list &key (batch-size 10))\n  (let (res\n        indexes)\n    (with-batches (batch list :batch-size batch-size\n                   :index index)\n      (push batch res)\n      (push index indexes))\n    (values\n     (reverse res)\n     (reverse indexes))))\n"
  },
  {
    "path": "src/util/misc/misc.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/misc\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:export\n   #:funcall-if\n   #:?.\n   #:or-setf\n   #:not-null!\n   #:uniq\n   #:safe-ensure-directories-exist\n   #:make-mp-hash-table\n   #:relpath\n   #:safe-with-open-file\n   #:nested-assoc-value\n   #:ntrim-list\n   #:s-or))\n(in-package :util/misc)\n\n\n(defun funcall-if (fn arg &rest rest)\n  \"If arg is not nil, then call the function fn with the args (cons arg rest)\"\n  (when arg\n    (apply fn arg rest)))\n\n(defmacro ?. (fn arg &rest rest)\n  (alexandria:with-gensyms (arg-val)\n    `(let ((,arg-val ,arg))\n       (funcall-if (function ,fn)\n                   ,arg-val\n                   ,@rest))))\n\n(local-time:reread-timezone-repository\n :timezone-repository (asdf:system-relative-pathname\n                       :local-time\n                       \"zoneinfo/\"))\n\n(defvar *lock* (bt:make-lock \"or-setf\"))\n\n(defmacro or-setf (place expr &key (thread-safe nil)\n                                (lock '*lock*))\n  (cond\n    (thread-safe\n     `(or\n       ,place\n       (bt:with-lock-held (,lock)\n         (or\n          ,place\n          (setf ,place ,expr)))))\n    (t\n     `(or\n       ,place\n       (setf ,place ,expr)))))\n\n\n(defun %not-null (x expr)\n  (cond\n    (x x)\n    (t (error \"Expected ~s to eval to a not null value\" expr))))\n\n(defmacro not-null! (x)\n  `(%not-null ,x ',x))\n\n(defun %not-null-or-empty (x expr)\n  (cond\n    ((not (str:emptyp  x)) x)\n    (t (error \"Expected ~s to eval to a non-empty value\" expr))))\n\n(defmacro not-empty! (x)\n  `(%not-null-or-empty ,x ',x))\n\n(defun uniq (list &key (test #'eql))\n  \"Given a sorted list, remove adjacent duplicates\"\n  (let ((ret nil))\n    (loop for x in list\n          if (or (null ret)\n                 (not (funcall test x (car ret))))\n            do (push x ret))\n    (nreverse ret)))\n\n(defun safe-ensure-directories-exist (x)\n  \"cl:ensure-directories-exist is not thread safe. This version is\n thread-safe for all practical purposes, without too much overhead.\n\nIn the error case, it's hard to distinguish between a real error (e.g.\n the directory is not writeable\"\n  (handler-case\n      (ensure-directories-exist x)\n    (file-error (e)\n      (dotimes (i 10)\n        ;; The lock isn't doing much here though, the idea is just to\n        ;; reduce the possibility of collisions.\n        (bt:with-lock-held (*lock*)\n          (handler-case\n              (return-from safe-ensure-directories-exist\n                (ensure-directories-exist x))\n            (file-error (next)\n              ;; If an error happens, the file-error-pathname is\n              ;; filled out. If the second round is tsi\n              (when (equal (file-error-pathname e)\n                           (file-error-pathname next))\n                (error next))\n              (setf e next)))))\n      (error \"Could not not create the directory even after multiple attempts\"))))\n\n\n(defmethod parse-size ((size number))\n  size)\n\n(defmethod parse-size ((size string))\n  (multiple-value-bind (num suffix-pos)\n      (parse-integer size :junk-allowed t)\n    (let ((suffix (str:substring suffix-pos nil size)))\n     (cond\n       ((string-equal \"GB\" suffix)\n        (* num 1024 1024 1024))\n       ((string-equal \"MB\" suffix)\n        (* num 1024 1024))\n       ((string-equal \"kB\" suffix)\n        (* num 1024))\n       ((or\n         (string-equal \"\" suffix)\n         (string-equal \"b\" suffix)\n         (string-equal \"bytes\" suffix))\n        num)\n       (t\n        (error \"could not parse size suffix: ~a in ~a\" suffix size))))))\n\n(defun make-mp-hash-table (&rest args)\n  (apply #'make-hash-table\n         #+sbcl :synchronized #+sbcl t\n         args))\n\n(defmacro with-global-binding (((sym value) &rest others) &body body)\n  `(call-with-global-binding ',sym ,value\n                             ,(cond\n                                ((null others)\n                                 `(lambda () ,@body))\n                                (t\n                                 `(lambda ()\n                                    (with-global-binding ,others\n                                      ,@body))))))\n\n(defun call-with-global-binding (sym value fn)\n  (let* ((boundp (boundp sym))\n         (old-val (when boundp  (symbol-value sym))))\n    (unwind-protect\n         (progn\n           (setf (symbol-value sym) value)\n           (funcall fn))\n      (cond\n        (boundp\n         (setf (symbol-value sym) old-val))\n        (t\n         (makunbound sym))))))\n\n;; Sync with bknr.cluster\n(defun relpath (path start)\n  (assert (fad:pathname-absolute-p path))\n  (assert (fad:pathname-absolute-p start))\n\n  (labels ((compute (path start)\n             (cond\n               ((equal (car path) (car start))\n                (compute (cdr path) (cdr start)))\n               ((not start)\n                path)\n               (t\n                (compute (list* \"..\" path)\n                         (cdr start))))))\n    (make-pathname\n     :directory\n     (list* :relative\n      (compute (pathname-directory path) (pathname-directory start)))\n     :defaults path)))\n\n\n(def-easy-macro safe-with-open-file (&binding stream &rest open-args &fn fn)\n  \"CL:WITH-OPEN-FILE has dynamic extent and is dangerous in threaded code.\"\n  (let ((stream (apply #'open open-args)))\n    (unwind-protect\n         (fn stream)\n      (close stream))))\n\n(defun nested-assoc-value (obj &rest nesting)\n  (cond\n    ((not nesting)\n     obj)\n    (t\n     (apply #'nested-assoc-value (cdr (assoc (car nesting) obj :test #'equal))\n            (cdr nesting)))))\n\n(defun random-selection (objs &key (weight (lambda (obj) (declare (ignore obj)) 1)))\n  (let ((total-weight (loop for obj in objs\n                            summing (funcall weight obj))))\n    (let ((pick (random total-weight)))\n      (loop for obj in objs\n            if (< pick (funcall weight obj))\n              return obj\n            else\n              do\n                 (decf pick (funcall weight obj))))))\n\n(defun ntrim-list (list n)\n  \"An in-place trimming of a list. Useful to clear an in-memory log list\"\n  (assert (> n 0))\n  (loop for cell on list\n        for i from 1 below n\n        finally\n           (when cell\n             (setf (cdr cell) nil))))\n\n(def-easy-macro with-slow-logging (message &fn fn)\n  (let ((start-time (get-internal-real-time)))\n    (prog1\n        (fn)\n      (let ((diff (/ (- (get-internal-real-time) start-time)\n                     internal-time-units-per-second)))\n        (when (> diff 0.2)\n          (log:warn \"~a was slow: ~as\" message (* 1.0 diff)))))))\n\n\n;; This is taken from: https://github.com/vindarel/cl-str/pull/133 temporarily\n(defmacro s-or (&rest args)\n  \"Similar to CL:OR, but returns the first non-empty string. If all the\nstrings are empty or NIL, then we return NIL.\n\nLike OR, expressions are evaluated from left to right, until the first\nexpression returns a non-empty string.\"\n  (let ((var (gensym)))\n   `(or\n     ,@ (loop for arg in args\n              collect `(let ((,var ,arg))\n                         (unless (str:emptyp ,var)\n                           ,var))))))\n"
  },
  {
    "path": "src/util/misc/test-lists.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/misc/tests/test-lists\n  (:use #:cl\n        #:util/misc/lists\n        #:fiveam)\n  (:import-from #:util/misc/lists\n                #:with-batches\n                #:make-batches)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/misc/tests/test-lists)\n\n(util/fiveam:def-suite)\n\n(test head\n  (is (equal '(1 2) (head '(1 2 3) 2)))\n  (is (equal nil (head '(1 2 3) 0)))\n  (is (equal '(1 2 3) (head '(1 2 3) 3)))\n  (is (equal '(1 2 3) (head '(1 2 3) 5))))\n\n(test head-negative\n  (is (equal '(1 2) (head '(1 2 3) -1)))\n  (is (equal '(1 2 3) (head '(1 2 3) -10)))\n  (is (equal '(1) (head '(1 2 3) -2))))\n\n(test tail\n  (is (equal '(3) (tail '(1 2 3) 2)))\n  (is (equal nil (tail '(1 2 3) 3)))\n  (is (equal '(1 2 3) (tail '(1 2 3) 0)))\n  (is (equal nil (tail '(1 2 3) 5))))\n\n\n(test tail-negative\n  (is (equal '(3) (tail '(1 2 3) -1)))\n  (is (equal '(2 3) (tail '(1 2 3) -2)))\n  (is (equal '(1 2 3) (tail '(1 2 3) -3)))\n  (is (equal '(1 2 3) (tail '(1 2 3) -10))))\n\n\n(test make-batches\n  (is (equal '((1 2 3)\n               (4 5 6)\n               (7 8))\n             (make-batches '(1 2 3 4 5 6 7 8)\n                           :batch-size 3)))\n  (is (equal '(0 3 6)\n             (nth-value\n              1\n              (make-batches '(1 2 3 4 5 6 7 8)\n                            :batch-size 3)))))\n\n(test with-batches-is-tail-call-optimized\n  (let ((list (loop for i from 0 below 1000000\n                    collect i)))\n    (let ((num 0))\n      (with-batches (batch list)\n        (incf num))\n      (is (eql 100000 num)))))\n"
  },
  {
    "path": "src/util/misc/test-misc.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/misc/tests/test-misc\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/misc\n                #:with-slow-logging\n                #:ntrim-list\n                #:random-selection\n                #:safe-with-open-file\n                #:relpath\n                #:safe-ensure-directories-exist\n                #:or-setf)\n  (:import-from #:tmpdir\n                #:with-tmpdir)\n  (:import-from #:fiveam-matchers\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/misc/tests/test-misc)\n\n\n(util/fiveam:def-suite)\n\n(test or-setf-happy-path\n  (let ((var nil))\n    (is\n     (equal \"foo\"\n            (or-setf\n             var\n             \"foo\"\n             :thread-safe t)))\n    (is (equal \"foo\" var))\n    (is\n     (equal \"foo\"\n      (or-setf\n       var\n       \"car\")))\n    (is (equal \"foo\" var))))\n\n(test or-setf-thread-safety\n  (loop for i from 0 to 100\n        do\n           (let ((ctr 0)\n                 (val nil))\n             (let ((threads (loop for i from 0 to 10\n                                  collect (bt:make-thread\n                                           (lambda ()\n                                             (util/misc:or-setf\n                                              val\n                                              (incf ctr)\n                                              :thread-safe t))))))\n               (mapc #'bt:join-thread threads)\n               (is (eql 1 ctr))))))\n\n\n(test safe-ensure-directories-exist\n  (with-tmpdir (dir)\n    (dotimes (i 10)\n     (let ((errors))\n       (let ((threads\n               (loop for i from 0 to 50\n                     collect\n                     (bt:make-thread\n                      (lambda ()\n                        (handler-case\n                            (safe-ensure-directories-exist\n                             (path:catfile dir \"a/b/c/d/e/f/g/hi/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z.txt\"))\n                          (error (e)\n                            ;; not even bothered with locks! We just need to\n                            ;; cache one error\n                            (push e errors))))))))\n         (mapc #'bt:join-thread threads))\n       (assert-that\n        errors\n        (has-length 0))))))\n\n#-screenshotbot-oss\n(test safe-ensure-directories-exist-when-actually-cant-write-dir\n  #-windows\n  (unless (equal \"root\" (uiop:getenv \"USER\"))\n    (signals\n        file-error\n      (safe-ensure-directories-exist \"/foo/car/bar.txt\"))))\n\n(defun pathname-equal (a b)\n  (string= (namestring a) (namestring b)))\n\n(test relpath\n  (let ((dir #P \"/etc/bar/\"))\n    (let ((subdir (path:catdir dir \"foo/\")))\n      (is (pathname-equal #P\"foo/\" (relpath subdir dir)))\n      (is (pathname-equal #P \"../foo/\" (relpath subdir (path:catdir dir \"bleh/\"))))\n      (is (pathname-equal #P \"../../foo/\" (relpath subdir (path:catdir dir \"bleh/dfd/\"))))\n      (is (pathname-equal #P \"../foo.txt\" (relpath \"/etc/bar/foo.txt\" \"/etc/bar/car/\" ))))))\n\n(test safe-with-open-file\n  (tmpdir:with-tmpdir (dir)\n    (safe-with-open-file (stream (path:catfile dir \"foo.txt\") :direction :output)\n      (write-string \"arnold\" stream))\n    (safe-with-open-file (stream (path:catfile dir \"foo.txt\") :direction :input)\n      (is (equal \"arnold\" (read-line stream))))))\n\n(test random-select\n  (dotimes (i 100)\n    (assert\n     (random-selection (list 1 2 3 4 5 6 7 8 9 10) :weight (lambda (x) (* x x))))))\n\n(test ntrim-list\n  (let ((x (list 1 2 3 4 5)))\n    (ntrim-list x 3)\n    (is (equal (list 1 2 3) x))\n    (finishes (ntrim-list nil 3))))\n\n(test with-slow-logging-happy-path\n  (is(equal\n      2\n      (with-slow-logging (\"hello\")\n        2))))\n"
  },
  {
    "path": "src/util/misc/util.misc.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :util.misc\n  :serial t\n  :depends-on (:local-time\n               :cl-fad\n               :easy-macros\n               :log4cl\n               :str)\n  :components ((:file \"misc\")\n               (:file \"lists\")))\n\n(defsystem :util.misc/tests\n  :depends-on (:util.misc\n               :fiveam\n               :fiveam-matchers\n               :util/fiveam\n               :tmpdir\n               :alexandria)\n  :serial t\n  :components ((:file \"test-misc\")\n               (:file \"test-lists\")))\n"
  },
  {
    "path": "src/util/mock-recording.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :util/mock-recording\n    (:use #:cl\n          #:alexandria)\n  (:import-from #:cl-mock\n                #:with-mocks\n                #:call-previous\n                #:if-called)\n  (:export\n   #:with-recording\n   #:track\n   #:recording-mode-p))\n(in-package :util/mock-recording)\n\n(defclass function-call ()\n  ((function-name :initarg :function-name\n             :reader function-name)\n   (arguments :initarg :arguments\n              :reader arguments)\n   (response :initarg :response\n             :reader response)\n   (other-values :initarg :other-values\n                 :initform nil\n                 :reader other-values)))\n\n;; Keeps track of the recording of each call\n(defvar *recording*)\n\n;; Keeps the configuration\n(defvar *runtime-config*)\n\n;; Are we in recording or replay mode? If unbound, we are in neither!\n(defvar *recording-mode*)\n\n(defvar *in-recorded-function-p* nil\n  \"True if we're already inside a recorded function (for instance, nested\ntracked functions. In this case we shouldn't record any nested functions.\")\n\n(defun track-during-recording (mocked-function &key skip-args)\n  (if-called mocked-function\n             (lambda (&rest args)\n               (log:debug \"Bypassing any recorded values\")\n               (let ((res (multiple-value-list\n                           (let ((*in-recorded-function-p* t))\n                            (call-previous)))))\n                 (let ((function-call (make-instance 'function-call\n                                                     :function-name mocked-function\n                                                     :arguments (remove-skip-args args skip-args)\n                                                     :response (car res)\n                                                     :other-values (cdr res))))\n                   (unless *in-recorded-function-p*\n                     (push function-call *recording*)))\n                 (apply #'values res)))))\n\n(defun remove-skip-args (args skip-args)\n  (loop for x in args\n        for i from 0 to 10000000\n        if (not (member i skip-args))\n          collect x))\n\n(defun track-during-replay (mocked-function &key skip-args)\n  (if-called mocked-function\n             (lambda (&rest args)\n               (log:debug \"Running recorded value\")\n               (let ((next (pop *recording*)))\n                 (unless next\n                   (error \"No more recorded calls\"))\n                 (unless (eql (function-name next) mocked-function)\n                   (error \"Function requested is ~a, but we recorded ~a\"\n                          (function-name next)\n                          mocked-function))\n                 (let ((args (remove-skip-args args skip-args)))\n                  (unless (equalp (arguments next) args)\n                    (error \"Next args in recording is ~S but got ~S\"\n                           (arguments next) args)))\n                 (apply #'values\n                        (response next)\n                        (other-values next))))))\n\n(defun recording-mode-p ()\n  *recording-mode*)\n\n(defun track (mocked-function &rest args)\n  (cond\n    (*recording-mode*\n     (apply 'track-during-recording mocked-function args))\n    (t\n     (apply 'track-during-replay mocked-function args))))\n\n(defun call-with-recording (file fn &key record)\n  (ensure-directories-exist file)\n  (let ((*recording-mode* record))\n    (with-mocks ()\n      (cond\n        (record\n         (let ((*recording* nil))\n           (let ((res (funcall fn)))\n             (cl-store:store (reverse *recording*) (pathname file))\n             res)))\n        (t\n         (let ((*recording* (cl-store:restore (pathname file))))\n           (funcall fn)))))))\n\n(defmacro with-recording ((file &key record) &body body)\n  `(call-with-recording ,file\n                        (lambda () ,@body)\n                        :record ,record))\n"
  },
  {
    "path": "src/util/mockable.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:mockable\n  (:use #:cl)\n  (:export #:defmockable\n\t   #:with-single-mock\n\t   #:d-with-single-mock))\n(in-package #:mockable)\n\n(defvar *enable-lookups* nil)\n\n(defun find-mock (sym)\n  (cdr (assoc sym *enable-lookups*)))\n\n(defmacro defmockable (name (&rest lambda-list) &body body)\n  `(defun ,name (&rest defmockable-args)\n     (if (and mockable::*enable-lookups*\n\t      (mockable::find-mock ',name))\n\t (apply (find-mock ',name)\n\t\tdefmockable-args)\n\t (progn\n\t   (destructuring-bind ,lambda-list defmockable-args\n\t     ,@body)))))\n\n(defmacro with-single-mock ((name value) &body body)\n  `(progn\n     (let ((mockable::*enable-lookups*\n\t    (acons ',name ,value mockable::*enable-lookups*)))\n       ,@body)))\n\n(defmacro d-with-single-mock ((name value) &body body)\n  `(progn ,@body))\n"
  },
  {
    "path": "src/util/mquery.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :mquery\n  (:use #:cl\n        #:markup\n        #:alexandria)\n  (:import-from #:markup/markup\n                #:abstract-xml-tag)\n  (:export #:$\n           #:attr\n           #:add-class\n           #:remove-class\n           #:has-class-p\n           #:namequery\n           #:text\n           #:parent\n           #:after\n           #:val\n           #:with-document\n           #:mappend\n           #:mqappend))\n(in-package :mquery)\n\n(defvar *document*)\n\n(defun split-query-components (query)\n  ;; technically this regex won't account for\n  (flet ((invalid-query () (error \"invalid query: ~s\" query))\n         (make-response (parts)\n           `(,(elt parts 0)\n              ,(elt parts 1))))\n    (cond\n     ((eql #\\[ (elt query 0))\n      ;; if the first letter is [, then we find the closing\n      ;; tag. Ideally, we should also try to read into the string for\n      ;; escaped ], but we'll skip that for now.\n      (multiple-value-bind (res parts) (cl-ppcre:scan-to-strings \"^([^\\\\]]*\\\\])(.*)?$\" query)\n        (unless res\n          (invalid-query))\n        (make-response parts)))\n     (t\n      (multiple-value-bind (res parts) (cl-ppcre:scan-to-strings \"^([.]?[^.\\\\[]+)([.\\\\[](.*))?$\" query)\n        (unless res\n          (invalid-query))\n        (make-response parts))))))\n\n(defun build-matcher (query)\n  \"Returns a lambda function that matches a given expression\"\n  (cond\n    ((functionp query)\n     query)\n    ((str:emptyp query)\n     (lambda (x)\n       (declare (ignore x))\n       t))\n    (t\n     (destructuring-bind (next rest) (split-query-components query)\n       (let ((rest-matcher (build-matcher rest))\n             (next-matcher (cond\n                             ((str:starts-with-p \"[\" next)\n                              (multiple-value-bind (res parts) (cl-ppcre:scan-to-strings \"^\\\\[(.*)='(.*)'\\\\]$\" next)\n                                (unless res\n                                  (error \"invalid query: ~s\" next))\n                                (let ((parts (loop for p across parts collect p)))\n                                  (destructuring-bind (name val) parts\n                                   (lambda (x)\n                                     (equal val (mquery:attr x name)))))))\n                             ((str:starts-with-p \".\" next)\n                              (let ((class-name (str:substring 1 nil next)))\n                               (lambda (x)\n                                 (member class-name (css-classes x) :test 'equal))))\n                             (t\n                              (let ((upcase (string-upcase next)))\n                               (lambda (x)\n                                 (equal upcase\n                                        (string-upcase (xml-tag-name x)))))))))\n         (lambda (x)\n          (and\n           (funcall next-matcher x)\n           (funcall rest-matcher x))))))))\n\n(defun mqappend (parent child)\n  (let ((parent (only parent))\n        (child (only child)))\n    (check-type child abstract-xml-tag)\n    (push child\n          (xml-tag-children parent))\n    parent))\n\n(defun namequery (name)\n  (let ((name (string-downcase name)))\n   (lambda (x)\n     (equal name (attr x \"name\")))))\n\n(defun $ (query &optional (root *document*))\n  (cond\n    ((typep query 'abstract-xml-tag)\n     query)\n    (t\n     (let ((query (build-matcher query)))\n      (let ((root (listify root)))\n        (let ((ret nil))\n          (loop for root in root do\n               (markup:walk\n                root\n                (lambda (x)\n                  (assert x)\n                  (when (funcall query x)\n                    (push x ret))\n                  x)))\n          (nreverse ret)))))))\n\n(defun parent (x &optional (root *document*))\n  (let ((x (only x)))\n   (markup:walk\n    root\n    (lambda (inner)\n      (when (member x (xml-tag-children inner))\n        (return-from parent inner))\n      inner))))\n\n(defun findc (elm list &key (test 'eql))\n  (declare (optimize (speed 3)))\n  (cond\n    ((not list) nil)\n    ((funcall test elm (car list)) list)\n    (t (findc elm (cdr list) :test test))))\n\n(macrolet ((with-vars (&body body)\n             `(let* ((x (only x))\n                     (parent (parent x))\n                     (children (xml-tag-children parent))\n                     (cell (findc x children)))\n                ,@body)))\n (defun after (x)\n   (with-vars\n       (loop for x in (cdr cell) do\n            (when (typep x 'abstract-xml-tag)\n              (return-from after x)))))\n (defun (setf after) (val x)\n   (with-vars\n       (assert parent)\n       (assert children)\n       (assert cell)\n     (setf (cdr cell)\n           (cons val (cdr cell))))))\n\n\n(defmacro with-document ((doc) &body body)\n  `(let ((*document* ,doc))\n     ,@body\n     *document*))\n\n(defun only (x)\n  (if (listp x)\n      (first x)\n      x))\n\n(defun listify (root)\n  (if (listp root) root (list root)))\n\n(defun attr (x name)\n  (let ((x (only x))\n        (name (string-downcase name)))\n    (when x\n     (assoc-value (markup:xml-tag-attributes x) name :test 'equal))))\n\n(defun (setf attr) (val x name)\n  (let ((name (string-downcase name)))\n   (loop for x in (listify x) do\n        (setf (assoc-value (markup:xml-tag-attributes x) name :test 'equal)\n              val)))\n  val)\n\n(defun css-classes (x)\n  (let ((classes (str:split \" \" (or (attr x \"class\") \"\"))))\n    classes))\n\n(defun has-class-p (x class-name)\n  (member class-name (css-classes x)\n          :test 'string=))\n\n(defun (setf css-classes) (classes x)\n  (setf (attr x \"class\") (str:join \" \" classes))\n  classes)\n\n(defun add-class (x class-name)\n  (dolist (x (listify x))\n   (let ((classes (nreverse (css-classes x))))\n     (pushnew class-name classes :test 'equal)\n     (setf (css-classes x) (nreverse classes)))))\n\n(defun remove-class (x class-name)\n  (dolist (x (listify x))\n   (let ((classes (css-classes x)))\n     (setf (css-classes x) (delete class-name classes :test 'equal)))))\n\n(defun val-is-attr-p (tag)\n  (member (xml-tag-name tag)\n          '(:input :button)))\n\n(defun val-is-inner-html (tag)\n  (member (xml-tag-name tag)\n          '(:textarea)))\n\n(defun val-is-checkbox (tag)\n  (and (eql :input (xml-tag-name tag))\n       (string= \"checkbox\" (string-downcase (attr tag \"type\")))))\n\n(defun val (x)\n  (let ((x (only x)))\n    (cond\n      ((val-is-checkbox x)\n       (not (str:empty? (attr x \"checked\"))))\n      ((val-is-attr-p x)\n       (attr x \"value\"))\n      ((val-is-inner-html x)\n       (text x))\n      (t\n       (error \"unsupported val type: ~s\" x)))))\n\n\n(defun (setf val) (value x)\n  (let ((x (only x)))\n    (cond\n      ((null x)\n       nil)\n      ((val-is-checkbox x)\n       (setf (attr x \"checked\")\n             (if value \"checked\" nil)))\n      ((val-is-attr-p x)\n       (setf (attr x \"value\") value))\n      ((val-is-inner-html x)\n       (setf (text x) value))\n      ((eql :select (xml-tag-name x))\n       (setf (select-option-val x) value))\n      (t\n       (error \"unsupport (Setf val) type: ~s\" x)))))\n\n(defun %expand-children (children)\n  \"Sometimes a child might be a merge tag. It's annoying to deal with,\n  so let's expand it before processing it. Only expands the first\n  level.\"\n  (loop for child in children\n        if (typep child 'xml-merge-tag)\n          appending (xml-merge-tag-children child)\n        else\n          collect child))\n\n(defun (setf select-option-val) (value x)\n  (when value\n    (loop for child in (%expand-children (xml-tag-children x))\n          if (and\n              (typep child 'abstract-xml-tag)\n              (string= value (attr child \"value\")))\n            do\n               (setf (attr child \"selected\") \"\"))))\n\n(defun text (x)\n  (let ((stream (make-string-output-stream)))\n   (let ((x (only x)))\n     (labels ((write-stream (x)\n                (typecase x\n                  (markup:abstract-xml-tag\n                   (mapc #'write-stream (xml-tag-children x)))\n                  (t\n                   (format stream \"~a\" x)))))\n       (write-stream x)))\n   (get-output-stream-string stream)))\n\n(defun (Setf text) (value x)\n  (let ((x (only x)))\n    (setf (xml-tag-children x)\n          (list value))))\n"
  },
  {
    "path": "src/util/native-module.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/native-module\n  (:use #:cl)\n  (:import-from #:alexandria\n                #:remove-from-plist)\n  (:export\n   #:make-native-module\n   #:load-module\n   #:embed-module\n   #:make-system-module\n   #:*lib-cache-dir*\n   #:define-embedded-module)\n  (:local-nicknames #-lispworks (#:fli #:util/fake-fli)))\n(in-package :util/native-module)\n\n(defvar *lib-cache-dir* nil)\n\n(defclass native-module ()\n  ((name :initarg :name\n         :reader name)\n   (lock :initform (bt:make-lock)\n         :reader lock)\n   (pathname-provider :initarg :pathname-provider\n                      :accessor pathname-provider)\n   (pathname-flag :initarg :pathname-flag\n                  :reader pathname-flag\n                  :initform :real-name)\n   (embedded-p :initform nil\n               :accessor embedded-p)\n   (loaded-p :initform nil\n             :accessor loaded-p)\n   (verify :initarg :verify\n           :initform (lambda ())\n           :accessor verify)\n   (module-data :initform nil\n                :accessor module-data\n                :documentation \"The actual binary data of the module, when installing it manually.\")))\n\n(defmethod print-object ((self native-module) out)\n  (format out \"#<NATIVE-MODULE ~a>\" (name self)))\n\n(defun make-native-module (name system component-name &key (verify (lambda ())))\n  (make-instance 'native-module\n                 :name name\n                 :verify verify\n                 :pathname-provider (lambda ()\n                                      (asdf:output-file\n                                       'asdf:compile-op\n                                       (asdf:find-component system\n                                                            (list component-name))))))\n\n(defun make-system-module (name &rest args &key file-name &allow-other-keys)\n  (cond\n    (file-name\n     (make-instance 'native-module\n                    :name name\n                    :pathname-provider (lambda () file-name)\n                    :pathname-flag :file-name))\n    (t\n     (apply #'make-instance 'native-module\n            :name name\n            (remove-from-plist args :file-name)))))\n\n(defmethod module-pathname ((self native-module))\n  (funcall (pathname-provider self)))\n\n(defun ensure-pathname-type (defaults)\n  (let ((type #+darwin \"dylib\"\n              #+linux \"so\"\n              #+mswindows \"dll\"))\n   (cond\n     ((equal \"so\" (pathname-type defaults))\n      (make-pathname :type type :defaults defaults))\n     (t\n      defaults))))\n\n(defun ensure-right-lib-type (lib)\n  )\n\n(defun find-module (pathname)\n  \"During the embed step, we need the absolute pathname\"\n  (let ((path (list\n               \"/usr/lib/\"\n               \"/usr/local/lib/\"\n               \"/usr/lib/x86_64-linux-gnu/\"\n               \"/opt/homebrew/lib/\")))\n\n     (loop for p in path\n           for abs =\n             (ensure-pathname-type\n              (path:catfile p pathname))\n           if (path:-e abs)\n             return abs)))\n\n(defun read-file-content (pathname)\n  (with-open-file (stream pathname\n                          :direction :input\n                          :element-type '(unsigned-byte 8))\n    (let ((res (make-array (file-length stream) :element-type '(unsigned-byte 8))))\n      (read-sequence res stream)\n      res)))\n\n(defmethod embed-module ((self native-module))\n  (setf (pathname-provider self)\n        (let ((pathname (funcall (pathname-provider self))))\n          (lambda () pathname)))\n  (setf (module-data self)\n        (read-file-content\n         (find-module (module-pathname self))))\n  (setf (embedded-p self) t))\n\n(defmethod load-embedded-module ((self native-module) &key (load t))\n  (let ((output (path:catfile *lib-cache-dir* (make-pathname\n                                               :directory `(:relative)\n                                               :defaults (module-pathname self)))))\n    (delete-file output)\n    (log:debug \"Loading module ~a, ~a\" (name self) output)\n    (with-open-file (stream output\n                            :direction :output\n                            :element-type '(unsigned-byte 8))\n      (write-sequence (module-data self)\n                      stream))\n    (when load\n      (fli:register-module (name self)\n                           ;; Not sure why I need the #+lispworks\n                           ;; here, but basically the file names have\n                           ;; a .so, and Lispworks on Mac seems to\n                           ;; handle that fine.\n                           :real-name #+lispworks output\n                                      #-lispworks (find-module output)\n                           :connection-style :immediate))))\n\n(defmethod load-module :around (self &key force)\n  (handler-bind ((error (lambda (e)\n                          ;; If a module does fail, it's probably very\n                          ;; early on, so we want to print some\n                          ;; additional debugging information.\n                          (log:error \"Error loading module: ~a with ~a\" self e))))\n    (call-next-method)))\n\n(defmethod load-module ((self native-module) &key force)\n  #+linux\n  (when force\n    #+lispworks\n    (fli:disconnect-module (name self))\n    (setf (loaded-p self) nil))\n  (let ((needs-verify nil))\n   (util/misc:or-setf\n    (loaded-p self)\n    (progn\n      (cond\n        ((embedded-p self)\n         (load-embedded-module self))\n        (t\n         (fli:register-module\n          (name self)\n          (pathname-flag self)\n          (ensure-pathname-type (module-pathname self)))))\n      (setf needs-verify t)\n      t)\n    :thread-safe t\n    :lock (lock self))\n    (when needs-verify\n      (funcall (verify self)))\n    t))\n\n(defmacro define-embedded-module (module)\n  #+lispworks\n  (let ((action-name (format nil \"Loading embeded module ~a in ~a\" module (package-name *package*))))\n    `(unless (hcl:delivered-image-p)\n       (lw:define-action \"Delivery actions\" ,action-name\n         (lambda ()\n           (embed-module ,module))))))\n"
  },
  {
    "path": "src/util/package.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package #:util\n  (:use #:cl\n        #:bknr.datastore\n        #:hex)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:hex\n                #:acceptor-with-plugins\n                #:make-url\n                #:make-prefix-matcher\n                #:acceptor-plugins\n                #:make-full-url)\n  (:import-from #:util/threading\n                #:make-thread)\n  (:use-reexport #:util/random-port)\n  (:use-reexport #:util/ret-let\n                 #:util/copying\n                 #:util/store\n                 #:util/store/validate\n                 #:util/misc\n                 #:util/bind-form\n                 #:util/html2text\n                 #:util/object-id)\n  (:use-reexport #:util/make-instance-with-accessors)\n  (:export #:head\n           #:google-analytics\n           #:safe-redirect\n           #:olark\n           #:make-thread\n           #:get-request-domain-prefix\n           #:access-log\n           #:make-url\n           #:make-full-url\n           #:*disable-sentry*\n           #:load-countries\n           #:load-states\n           #:dont-send-the-mail\n           #:prepare-store-for-test\n           #:prepare-store\n           #:add-get-param-to-url\n           #:javascript\n           #:base-acceptor\n           #:html2text\n           #:download-file\n           #:bind-form\n           #:send-mail\n           #:parse-email-list\n           #:make-access-log\n           #:is-intern?\n           #:acceptor-funcall-handler\n           #:defview\n           #:acceptor-plugin\n           #:acceptor-plugins\n           #:acceptor-with-plugins\n           #:wrap-template\n           #:define-plugin-handler\n           #:vpush\n           #:oid\n           #:oid-bytes\n           #:object-with-oid\n           #:object-with-unindexed-oid\n           #:file-lock\n           #:release-file-lock\n           #:find-by-oid\n           #:system-source-directory\n           #:*acceptor-plugin*\n           #:acceptor-plugin-prefix\n           #:*object-store*\n           #:verify-store\n           #:define-css\n           #:*delivered-image*\n           #:render-view\n           #:with-view-builder\n           #:tag-manager-head\n           #:make-secret-code\n           #:tag-manager-body\n           #:acceptor-db-config\n           #:safe-mp-store\n           #:prod-request?\n           #:with-html-output\n           #:jvm-init\n           #:safe-uuid\n           #:better-easy-handler\n           #:tail\n           #:*in-test-p*\n           #:in-test-p\n           #:add-datastore-hook\n           #:validate-indices\n           #:funcall-if\n           #:?.\n           #:or-setf\n           #:relative-system-source-directory\n           #:relative-output-files\n           #:token-safe-for-email-p))\n"
  },
  {
    "path": "src/util/payment-method.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package #:stripe)\n\n(define-object payment-method ()\n  id\n  billing-details\n  customer\n  card\n  (type :reader payment-method-type))\n\n(defmethod initialize-instance :after ((instance payment-method) &key data\n                                       &allow-other-keys)\n  (destructuring-bind (&key card &allow-other-keys)\n      data\n    (reinitialize-instance\n     instance\n     :card (make-instance 'card :data card))))\n\n(define-query list-payment-methods (:type list)\n  (:get \"payment_methods\")\n  customer\n  type)\n\n(define-query detach-payment-method ()\n  (:post \"payment_methods/~a/detach\" payment-method))\n\n(define-query attach-payment-method ()\n  (:post \"payment_methods/~a/attach\" payment-method)\n  customer)\n\n(define-query retrieve-payment-method (:type payment-method)\n  (:get \"payment_methods/~a\" payment-method))\n\n(define-query create-payment-method (:type payment-method)\n  (:post \"payment_methods\")\n  type\n  card)\n"
  },
  {
    "path": "src/util/phabricator/conduit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :util/phabricator/conduit\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:alexandria\n                #:alist-hash-table)\n  (:export #:phab-instance\n           #:call-conduit\n           #:url\n           #:api-key\n           #:make-phab-instance-from-arcrc\n           #:whoami))\n\n(defclass phab-instance ()\n  ((url :initarg :url\n        :accessor url)\n   (api-key :initarg :api-key\n            :accessor api-key)))\n\n(defun make-phab-instance-from-arcrc (url)\n  (let* ((api-url (quri:render-uri (quri:merge-uris\n                                    (quri:uri \"/api/\")\n                                    (quri:uri url)))))\n   (with-open-file (file \"~/.arcrc\")\n     (let* ((json:*json-identifier-name-to-lisp* #'string)\n            (arcrc (json:decode-json file))\n            (hosts (assoc-value arcrc :|hosts|))\n            (host\n              ;; Surely there's a better way to do this.\n              (assoc-value hosts (intern api-url \"KEYWORD\")))\n            (token\n              (assoc-value host :|token|)))\n       (assert token)\n       (make-instance 'phab-instance\n                       :url url\n                       :api-key token)))))\n\n(defun phab-test ()\n  ;; useful for testing things\n  (make-phab-instance-from-arcrc \"https://phabricator.tdrhq.com\"))\n\n\n(defmethod call-conduit ((phab phab-instance) name params)\n  #+nil(log:debug \"initial params: ~s\" params)\n  (let* ((params (alist-hash-table\n                  `(,@params\n                    (\"__conduit__\"\n                     .\n                     ,(alist-hash-table\n                       `((\"token\" . ,(api-key phab)))))))))\n    (log:debug \"using params: ~S\" params)\n    (let* ((encoded (json:encode-json-to-string params))\n           (res\n             (progn\n               (log:info \"Running ~a\" encoded)\n               (http-request\n                (format nil \"~a/api/~a\" (url phab) name)\n                :method :post\n                :want-string t\n                :form-data t\n                :parameters `((\"params\" . ,encoded)\n                              (\"output\" . \"json\")\n                             (\"__conduit__\" . \"1\"))))))\n     (let* ((res\n              (json:decode-json-from-string res))\n            (error-info\n              (assoc-value res :error--info)))\n       (when error-info\n         (error \"Got conduit error: ~A \" (str:shorten 500 error-info)))\n       res))))\n\n\n(defmethod whoami ((phab phab-instance))\n  (let ((body\n         (call-conduit\n          phab\n          \"user.whoami\"\n          nil)))\n    (assoc-value (assoc-value body :result) :phid)))\n\n;; (whoami (phab-test))\n"
  },
  {
    "path": "src/util/phabricator/harbormaster.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/phabricator/harbormaster\n  (:use #:cl)\n  (:import-from #:util/phabricator/conduit\n                #:call-conduit\n                #:phab-instance)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:local-time\n                #:timestamp+\n                #:timestamp-to-unix)\n  (:export\n   #:download-file\n   #:upload-file\n   #:create-artifact))\n(in-package :util/phabricator/harbormaster)\n\n(defun delete-after-epoch ()\n  (timestamp-to-unix\n   (timestamp+\n    (local-time:now)\n    1 :day)))\n\n(defun p (x)\n  #+nil\n  (log:info \"result: ~s\" x)\n  x)\n\n(defmethod file-allocate ((phab phab-instance) file-size &key (name (Error \"needs name\")))\n  (log:info \"allocating file\")\n  (assoc-value\n   (assoc-value\n    (call-conduit\n     phab\n     \"file.allocate\"\n     `((\"name\" . ,name)\n       (\"contentLength\" . ,(p file-size))\n       (\"deleteAfterEpoch\" . ,(delete-after-epoch))))\n    :result)\n   :file-+phid+))\n\n(defmethod upload-file ((phab phab-instance) pathname\n                        &key (name \"unnamed\"))\n  \"Upload a file and return the PHID\"\n\n  (with-open-file (stream pathname :direction :input\n                                   :element-type 'flex:octet)\n    (let ((phid (file-allocate phab (file-length stream) :name name)))\n      (cond\n        (phid\n         (upload-file-chunked phab phid stream)\n         phid)\n        (t\n         (let ((arr (make-array (file-length stream)\n                                :element-type 'flex:octet)))\n           (read-sequence arr stream)\n           (let ((response\n                   (call-conduit\n                    phab\n                    \"file.upload\"\n                    `((\"name\" . ,name)\n                      (\"data_base64\" . ,(base64:usb8-array-to-base64-string arr))))))\n             (assoc-value response :result))))))))\n\n(defmethod upload-file-chunked ((phab phab-instance)\n                                phid\n                                stream)\n  (let ((chunks (reverse (file-query-chunks phab phid))))\n    (let ((buf (make-array 1 :element-type '(unsigned-byte 8)\n                               :adjustable t\n                               :fill-pointer t)))\n     (dolist (chunk chunks)\n       (assert (not (assoc-value chunk :complete)))\n       (let* ((start (parse-integer (assoc-value chunk :byte-start)))\n              (end (parse-integer (assoc-value chunk :byte-end))))\n         (adjust-array buf (- end start)\n                       :fill-pointer (-  end start))\n         (file-position stream start)\n         (read-sequence buf stream)\n         (file-upload-chunk phab phid start buf))))))\n\n(defmethod file-query-chunks ((phab phab-instance)\n                              phid)\n  (assoc-value\n   (call-conduit\n    phab\n    \"file.querychunks\"\n    `((\"filePHID\" . ,phid)))\n   :result))\n\n(defmethod file-upload-chunk ((phab phab-instance)\n                              phid\n                              pos\n                              seq)\n  (log:info \"Uploading chunk at pos ~a\" pos)\n  (call-conduit\n   phab\n   \"file.uploadchunk\"\n   `((\"filePHID\" . ,phid)\n     (\"byteStart\" . ,pos)\n     (\"data\" . ,(base64:usb8-array-to-base64-string seq))\n     (\"dataEncoding\" . \"base64\"))))\n\n(defmethod download-file ((phab phab-instance) phid output)\n  (let ((response\n          (call-conduit\n           phab\n           \"file.download\"\n           `((\"phid\" . ,phid)))))\n    (assert (not (assoc-value response :error--code)))\n    (let ((base64 (assoc-value response :result)))\n      (with-open-file (stream output :direction :output\n                                     :element-type '(unsigned-byte 8)\n                                     :if-exists :supersede)\n        (base64:base64-string-to-stream base64\n                                        :stream\n                                        #-sbcl\n                                        stream\n                                        #+sbcl\n                                        (flex:make-flexi-stream\n                                         stream\n                                         :external-format :latin-1))))))\n\n(defmethod create-artifact ((phab phab-instance)\n                            phid\n                            file\n                            &key (name (error \"must provide artifact name\")))\n  (let ((file-phid (upload-file phab file)))\n    (let ((data (make-hash-table :test #'equal)))\n      (setf (gethash \"filePHID\" data) file-phid)\n      (call-conduit\n       phab\n       \"harbormaster.createartifact\"\n       `((\"buildTargetPHID\" . ,phid)\n         (\"artifactKey\" . ,name)\n         (\"artifactType\" . \"file\")\n         (\"artifactData\" . ,data))))))\n"
  },
  {
    "path": "src/util/phabricator/maniphest.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/phabricator/maniphest\n  (:use #:cl)\n  (:import-from #:util/phabricator/conduit\n                #:phab-test\n                #:whoami\n                #:phab-instance\n                #:call-conduit)\n  (:import-from #:util/phabricator/project\n                #:project-phid)\n  (:import-from #:alexandria\n                #:assoc-value))\n(in-package :util/phabricator/maniphest)\n\n(defmethod create-task ((phab phab-instance)\n                        &key title description project)\n  (let ((result (call-conduit\n                 phab\n                 \"maniphest.createtask\"\n                 `((\"title\" . ,title)\n                   (\"description\" . ,description)\n                   (\"ownerPHID\" . ,(whoami phab))\n                   (\"projectPHIDs\" . #(,(project-phid phab project)))))))\n    (assoc-value (assoc-value result :result) :id)))\n\n;; (create-task (phab-test) :title \"foobar\" :description \"stuff here\" :project \"screenshotbot\")\n\n\n"
  },
  {
    "path": "src/util/phabricator/passphrase.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/phabricator/passphrase\n  (:use #:cl)\n  (:import-from #:json-mop\n                #:json-serializable-class)\n  (:import-from #:util/phabricator/conduit\n                #:call-conduit\n                #:make-phab-instance-from-arcrc)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:export\n   #:*secret-file*\n   #:reload-secrets))\n(in-package :util/phabricator/passphrase)\n\n(defvar *secret-file* nil\n  \"A *cache* of the secrets. The source of truth is still Phabricator\")\n\n(defun reload-secrets ()\n  (load-passphrases *secret-file*))\n\n(defclass passphrase ()\n  ((name :initarg :name\n         :reader passphrase-name)\n   (id :initarg :id\n       :json-key \"id\"\n       :json-type :number\n       :reader passphrase-id)\n   (type :initarg :type)\n   (token :initarg :token\n          :json-key \"token\"\n          :json-type :string\n          :initform nil\n          :accessor token))\n  (:metaclass json-serializable-class))\n\n(defvar *secrets* nil)\n\n(defmacro def-passphrase-token (name &key id)\n  `(unless (assoc-value *secrets* ',name)\n     (let ((passphrase (make-instance 'passphrase\n                                      :name (string-downcase ',name)\n                                      :id ,id\n                                      :type :token)))\n       (setf (assoc-value *secrets* ',name)\n             passphrase)\n       (defun ,name ()\n         (read-passphrase passphrase)))))\n\n(def-passphrase-token stripe-prod-webhook-signing-key :id 14)\n\n;; (stripe-prod-webhook-signing-key)\n\n(defun save-passphrases (output)\n  (let ((secrets (mapcar #'cdr *secrets*)))\n    (mapc #'read-passphrase secrets)\n    (with-open-file (output output :if-exists :supersede\n                                   :direction :output)\n      (yason:encode secrets output))))\n\n(defun load-passphrases (input)\n  (with-open-file (input input :direction :input)\n    (let ((secrets (json:decode-json input)))\n      (flet ((%find (id)\n               (loop for secret in secrets\n                     if (eql (assoc-value secret :id)\n                             id)\n                       return secret)))\n        (loop for (nil . secret) in *secrets*\n              do (setf (token secret)\n                       (assoc-value (%find (passphrase-id secret)) :token)))))))\n\n(defmethod read-passphrase ((self passphrase))\n  (util/misc:or-setf\n   (token self)\n   (let ((phab (make-phab-instance-from-arcrc \"https://phabricator.tdrhq.com\")))\n     (let ((response (call-conduit phab \"passphrase.query\"\n                                   `((:ids . ,(list (passphrase-id self)))\n                                     (:need-secrets . t)\n                                     (:limit . 1)))))\n       (let ((data (cdar (assoc-value (assoc-value response :result) :data))))\n         (unless (string-equal (passphrase-name self)\n                               (assoc-value data :name))\n           (error \"Expected key: ~a, but got ~a\"\n                  (passphrase-name self)\n                  (assoc-value data :name)))\n         (assoc-value (assoc-value data :material) :token))))))\n"
  },
  {
    "path": "src/util/phabricator/phabricator.el",
    "content": "(provide 'differential)\n\n(define-derived-mode differential-mode tabulated-list-mode\n  \"Phabricator differential list\"\n  \"\"\n  (setq tabulated-list-format\n        [(\"Revision\" 5 t)\n         (\"Branch\" 10 t)\n         (\"Status\" 10 t)\n         (\"Build Status\" 10 t)\n         (\"Title\" 10 t)]))\n\n(defun differential--buffer ()\n  (get-buffer-create \"*phabricator-revisions*\"))\n\n(defun differential--good (message)\n  \"The constant PASS with font-face.\"\n  (propertize message\n              :foreground \"green\"))\n\n\n(defun differential--bad (message)\n  \"The constant FAIL with font-face.\"\n  (propertize message\n              :background  \"red\"\n              :foreground  \"white\"\n              :weight \"bold\"))\n\n(defun differential-query-all ()\n  (let ((response (maniphest-call-conduit\n                   \"differential.query\"\n                   (let ((dict (make-hash-table)))\n                     (puthash \"status\" \"status-open\" dict)\n                     dict))))\n    (gethash\n     \"response\"\n     response)))\n\n(defun differential--format-status (status)\n  (cond\n   ((equal \"Draft\" status)\n    (differential--good status))\n   (t\n    (differential--bad status))))\n\n(defun differential--build-status (revision)\n  (let ((buildables (gethash \"buildables\" (gethash \"properties\" revision))))\n    (cond\n     (buildables\n      (let ((failed 0)\n            (waiting 0)\n            (passed 0))\n        (cl-loop for res being the hash-values of buildables\n                 for status = (gethash \"status\" res)\n                 if (equal \"failed\" status)\n                 do (cl-incf failed)\n                 if (equal \"pending\" status)\n                 do (cl-incf waiting)\n                 if (equal \"passed\" status)\n                 do (cl-incf passed))\n        (cond\n         ((> failed 0)\n          \"failed\")\n         ((> waiting 0)\n          \"waiting\")\n         ((> passed 0)\n          \"passed\")\n         (t\n          \"unknown\"))))\n     (t\n      \"No buildables\"))))\n\n(defun differential--set-contents ()\n  (with-current-buffer (differential--buffer)\n    (setq tabulated-list-entries\n          (cl-loop for rev across (differential-query-all)\n                   for i from 1\n                   collect\n                   (list (gethash \"id\" rev)\n                         (vector\n                          (format \"D%s\" (gethash \"id\" rev))\n                          (gethash \"branch\" rev)\n                          (differential--format-status (gethash \"statusName\" rev))\n                          (differential--build-status rev)\n                          (gethash \"title\" rev)))))\n    (tabulated-list-init-header)\n    (tabulated-list-print)))\n\n(defun differential-revision-list ()\n  (interactive)\n  (let ((buffer (differential--buffer)))\n    (with-current-buffer buffer\n      (differential-mode)\n      (differential--set-contents)\n      (differential--define-keybindings)\n      (display-buffer buffer))))\n\n(defun differential--reload ()\n  (interactive)\n  (differential--set-contents)\n  (message \"Reloaded.\"))\n\n(defun differential--define-keybindings ()\n  (define-key differential-mode-map\n    (kbd \"g\") #'differential--reload))\n\n(defun arc-paste ()\n  \"Takes the current region and pipes it to 'arc paste' command in ~/builds/web directory.\"\n  (interactive)\n  (if (use-region-p)\n      (let ((region-text (buffer-substring-no-properties (region-beginning) (region-end)))\n            (default-directory \"~/builds/web/\"))\n        (let ((response (maniphest-call-conduit\n                         \"paste.create\"\n                         (let ((dict (make-hash-table)))\n                           (puthash \"content\" region-text dict)\n                           dict))))\n          (let ((paste-id (gethash \"id\" (gethash \"response\" response))))\n            (message \"Region pasted: P%s\" paste-id))))\n    (message \"No region selected\")))\n\n"
  },
  {
    "path": "src/util/phabricator/project.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/phabricator/project\n  (:use #:cl)\n  (:import-from #:util/phabricator/conduit\n                #:call-conduit\n                #:phab-instance)\n  (:import-from #:alexandria\n                #:assoc-value))\n(in-package :util/phabricator/project)\n\n(defmethod project-phid ((phab phab-instance)\n                         name)\n  (assoc-value\n   (cdr\n    (first\n     (assoc-value\n      (assoc-value\n       (call-conduit phab\n                     \"project.query\"\n                     `((\"names\" . #(,name))))\n       :result)\n      :data)))\n   :phid))\n\n;; (project-phid (util/phabricator/conduit::phab-test) \"screenshotbot\")\n\n\n"
  },
  {
    "path": "src/util/phabricator/test-conduit.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/phabricator/test-conduit\n  (:use #:cl\n        #:fiveam)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/phabricator/test-conduit)\n\n(util/fiveam:def-suite)\n"
  },
  {
    "path": "src/util/phabricator/test-harbormaster.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/phabricator/test-harbormaster\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/phabricator/harbormaster\n                #:file-upload-chunk\n                #:delete-after-epoch\n                #:create-artifact\n                #:download-file\n                #:upload-file)\n  (:import-from #:util/phabricator/conduit\n                #:phab-instance\n                #:call-conduit\n                #:make-phab-instance-from-arcrc)\n  (:import-from #:util/mock-recording\n                #:recording-mode-p\n                #:track\n                #:with-recording)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:local-time\n                #:timestamp-to-universal))\n(in-package :util/phabricator/test-harbormaster)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (name &key (record nil))\n  (with-recording ((path:catfile\n                    #.(asdf:system-relative-pathname :util/phabricator\n                                                     \"phabricator/fixture/\")\n                    (format nil\n                            \"~a.rec\"\n                            name))\n                   :record record)\n    (track 'local-time:clock-now)\n    (track 'call-conduit\n           :skip-args '(0))\n    (let ((phab\n            (if (recording-mode-p)\n                (make-phab-instance-from-arcrc \"https://phabricator.tdrhq.com\")\n                (make-instance 'phab-instance))))\n      (&body))))\n\n(test upload-file\n  (with-fixture state (\"upload-file\")\n   (uiop:with-temporary-file (:pathname p :stream s :direction :output\n                              :element-type 'character\n                              :prefix \"input-file\")\n     (write-string \"hello world\" s)\n     (finish-output s)\n     (let ((phid (upload-file phab p)))\n       (is (stringp phid))\n       (uiop:with-temporary-file (:pathname output :prefix \"final-download\")\n         (download-file phab phid output)\n         #+nil(is (equal \"hello world\" (uiop:read-file-string output))))))))\n\n(test upload-large-file\n  (with-fixture state (\"upload-large-file\")\n    (let ((buf (make-array 1000 :initial-element 0))\n          (uploaded 0))\n      (cl-mock:if-called 'file-upload-chunk\n                         (lambda (phab phid pos seq)\n                           ;; don't actually upload the chunk, the\n                           ;; recording is too big.\n                           (is (eql pos uploaded))\n                           (incf uploaded (length seq))\n                           (values)))\n      (uiop:with-temporary-file (:pathname large-file\n                                 :stream stream\n                                 :element-type '(unsigned-byte 8))\n        (loop for i from 0 below (* 10 1000)\n              do (write-sequence buf stream))\n        (finish-output stream)\n        (let ((phid (upload-file phab large-file\n                                 :name (format nil \"unnamed ~a\"\n                                               (timestamp-to-universal (local-time:now))))))\n          (is (eql uploaded 10000000)))))))\n\n(test create-artifact\n  (with-fixture state (\"create-artifact\")\n    (track 'call-conduit\n           :skip-args '(0))\n    (track 'upload-file\n           :skip-args '(0 1))\n    (uiop:with-temporary-file (:pathname p :stream s)\n      (write-string \"hello world\" s)\n      (finish-output s)\n      (finishes\n        (create-artifact phab\n                         \"PHID-HMBT-6fznwctbnptklhw36y63\"\n                         p :name (format nil \"Test Artifact ~a\"\n                                         (timestamp-to-universal (local-time:now))))))))\n"
  },
  {
    "path": "src/util/posix.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/posix\n  (:use #:cl)\n  (:local-nicknames #-lispworks\n                    (#:fli #:util/fake-fli))\n  (:export\n   #:getpid\n   #:errno\n   #:get-errno-string))\n(in-package :util/posix)\n\n(fli:define-foreign-function getpid\n    ()\n  :result-type :int)\n\n(defun errno ()\n  #+lispworks\n  (lw:errno-value)\n  #+sbcl\n  (sb-alien:get-errno)\n  #-(:or :lispworks :sbcl)\n  0)\n\n(defun get-errno-string (errno)\n  #+lispworks\n  (lw:get-unix-error errno)\n  #-lispworks\n  \"get-errno-string unsupported on this platform\")\n"
  },
  {
    "path": "src/util/random-port.lisp",
    "content": "(uiop:define-package :util/random-port\n  (:use #:cl\n        #:alexandria)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/misc\n                #:make-mp-hash-table)\n  (:export #:random-port\n           #:with-random-port))\n(in-package :util/random-port)\n\n(defvar *ports* (make-mp-hash-table\n                 #+lispworks #+lispworks\n                 :weak-kind :value))\n\n(defvar *lock* (bt:make-lock))\n\n(defun random-port (&key (referenced-from\n                          ;; Agressively garbage collect by default\n                          (make-instance 'standard-object)))\n  (flet ((get-port ()\n           (let ((socket (usocket:socket-listen \"127.0.0.1\" 0)))\n             (unwind-protect\n                  (usocket:get-local-port socket)\n               (usocket:socket-close socket)))))\n    (loop for x = (get-port)\n          if (and\n              (> x 20000)\n              (ensure-port-set x referenced-from))\n            return x)))\n\n(defun ensure-port-set (port referenced-from)\n  \"Sets the port in *ports*, if it's already set return nil\"\n  (bt:with-lock-held (*lock*)\n    (cond\n      ((gethash port *ports*)\n       nil)\n      (t\n       (setf (gethash port *ports*) referenced-from)\n       t))))\n\n(def-easy-macro with-random-port (&binding port &fn fn)\n  \"A version of random-port, while ensuring that no two threads get the\nsame port within the current application.\"\n  (let ((port (random-port :referenced-from :never-remove)))\n    (unwind-protect\n         (fn port)\n      (remhash port *ports*))))\n"
  },
  {
    "path": "src/util/rb-tree.lisp",
    "content": "(defpackage :util/rb-tree\n  (:use #:cl))\n(in-package :util/rb-tree)\n\n\n(defconstant +red+ t)\n(defconstant +black+ nil)\n\n(defclass tree-set ()\n  ())\n\n(defstruct rb-tree\n  sentinel\n  root\n  cmp)\n\n\n(defstruct node\n  color\n  key\n  left\n  right\n\n  ;; in CLRS it's called just `p`\n  parent)\n\n(defun make-tree (&key (cmp #'<))\n  (let ((rb-tree (make-rb-tree)))\n    (let ((sentinel (make-node :color +black+\n                               :key nil\n                               :left nil\n                               :right nil)))\n      ;;(setf (node-parent sentinel) sentinel)\n      (setf (rb-tree-sentinel rb-tree)\n            sentinel)\n      (setf (rb-tree-root rb-tree)\n            sentinel)\n      (setf (rb-tree-cmp rb-tree)\n            cmp))\n    rb-tree))\n\n(defun key< (tree x y)\n  (funcall (rb-tree-cmp tree)\n           x y))\n\n(defun rb-insert (Tree z)\n  (%rb-insert-only Tree z)\n  (rb-insert-fixup Tree z))\n\n(defun %rb-insert-only (Tree z)\n  (let* ((T.nil (rb-tree-sentinel Tree))\n         (y T.nil)\n         (x (rb-tree-root tree)))\n    (loop while (not (eq x T.nil))\n          do\n             (progn\n               (setf y x)\n               (if (key< Tree\n                         (node-key z)\n                         (node-key x))\n                   (setf x (node-left x))\n                   (setf x (node-right x)))))\n\n    ;; At this point y is the left node under which we can legally put\n    ;; z. But we don't know where under y this needs to be.\n\n    (setf (node-parent z) y)\n    (cond\n      ((eq y T.nil)\n       (setf (rb-tree-root Tree) z))\n      ((key< Tree (node-key z) (node-key y))\n       (setf (node-left y) z))\n      (t\n       (setf (node-right y) z)))\n    (setf\n     (node-left z) T.nil\n     (node-right z) T.nil\n     (node-color z) +red+)))\n\n\n(defun red? (node)\n  (eq (node-color node) +red+))\n\n(defun black? (node)\n  (eq (node-color node) +black+))\n\n\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defun make-symmetric-symbol (symbol)\n    (let ((package (symbol-package symbol))\n          (name (symbol-name symbol)))\n      (setf name (str:replace-all \"LEFT\" \"TMPL3FT\" name))\n      (setf name (str:replace-all \"RIGHT\" \"LEFT\" name))\n      (setf name (str:replace-all \"TMPL3FT\" \"RIGHT\" name))\n      (intern name package)))\n\n  \n  (defun %make-symmetric (expr)\n    (cond\n      ((symbolp expr)\n       (make-symmetric-symbol expr))\n      (t\n       (loop for x in expr\n             collect (%make-symmetric x))))))\n\n(defmacro with-symmetric (&body body)\n  `(progn\n     ,@body\n     ,@(%make-symmetric body)))\n\n(with-symmetric\n  (defmacro %rb-insert-helper-left (Tree z)\n    ;; Note that setf z means that this must be a macro!\n    `(let ((y (node-right (node-parent (node-parent z)))))\n       (cond\n         ((red? y)\n          (setf (node-color (node-parent z))  +black+)\n          (setf (node-color y) +black+)\n          (setf (node-color (node-parent (node-parent z)))\n                +red+)\n          (setf z\n                (node-parent (node-parent z))))\n         (t\n          ;; Note that the CLRS algorithm is not printed very well here.\n          (when (eq z (node-right (node-parent z)))\n            (setf z (node-parent z))\n            (left-rotate Tree z))\n          (setf (node-color (node-parent z)) +black+)\n          (setf (node-color (node-parent (node-parent z))) +red+)\n          (right-rotate Tree (node-parent (node-parent z))))))))\n\n(defun rb-insert-fixup (Tree z)\n  (loop while (red? (node-parent z))\n        do\n           (cond\n             ((eq (node-parent z)\n                  (node-left (node-parent (node-parent z))))\n              (%rb-insert-helper-left Tree z))\n             (t\n              (%rb-insert-helper-right Tree z))))\n  (setf (node-color\n         (rb-tree-root Tree))\n        +black+))\n\n\n\n(with-symmetric ;; exactly symeteric for right-rotate\n (defun left-rotate (Tree x)\n   ;; See P. 313 CLRS\n  \n   (let ((y (node-right x))\n         (T.nil (rb-tree-sentinel Tree))) ;; set y\n\n     ;; turn y's left subtree into x's right subtree\n     (setf (node-right x) (node-left y)) \n     (if (not (eq (node-left y) T.nil))\n         (setf (node-parent\n                (node-left y))\n               x))\n\n     ;; Link x's parent to y\n     (setf (node-parent y) (node-parent x))\n\n     (cond\n       ((eq (node-parent x) T.nil)\n        (setf (rb-tree-root Tree) y))\n       ((eq x (node-left (node-parent x)))\n        (setf (node-left (node-parent x)) y))\n       (t\n        (setf (node-right (node-parent x))\n              y)))\n\n     ;; Put x on y's left\n     (setf (node-left y) x)\n     (setf (node-parent x) y))))\n\n;; Deletion\n\n(defun rb-transplant (Tree u v)\n  (let ((T.nil (rb-tree-sentinel Tree)))\n    (cond\n      ((eq (node-parent u) T.nil)\n       (setf (rb-tree-root  Tree) v))\n      ((eq u (node-left (node-parent u)))\n       (setf (node-left (node-parent u))\n             v))\n      (t\n       (setf (node-right (node-parent u)) v)))\n    (setf (node-parent v) (node-parent u))))\n\n(defun rb-delete (Tree z)\n  (let* ((T.nil (rb-tree-sentinel Tree))\n         (y z)\n         (y-original-color (node-color y))\n         x)\n    (cond\n      ((eq (node-left z) T.nil)\n       (setf x (node-right z))\n       (rb-transplant Tree z (node-right z)))\n      ((eq (node-right z) T.nil)\n       (setf x (node-left z))\n       (rb-transplant Tree z (node-left z)))\n      (t\n       (setf y (tree-minimum Tree (node-right z)))\n       (setf y-original-color (node-color y))\n       (setf x (node-right y))\n       (cond\n         ((eq (node-parent y) z)\n          (setf (node-parent x) y))\n         (t\n          (rb-transplant Tree y (node-right y))\n          (setf (node-right y) (node-right z))\n          (setf (node-parent (node-right y)) y)))\n       (rb-transplant Tree z y)\n       (setf (node-left y) (node-left z))\n       (setf (node-parent (node-left y)) y)\n       (setf (node-color y) (node-color z))))\n\n    (if (eq y-original-color +black+)\n        (rb-delete-fixup Tree x))))\n\n(with-symmetric\n  (defmacro %rb-delete-fixup-helper-left ()\n    `(progn\n       (setf w (node-right (node-parent x)))\n       (when (red? w)\n         (setf (node-color w) +black+)\n         (setf (node-color (node-parent x)) +red+)\n         (left-rotate Tree (node-parent x))\n         (setf w (node-right (node-parent x))))\n\n       (cond\n         ((and (black? (node-left w))\n               (black? (node-right w)))\n          (setf (node-color w) +red+)\n          (setf x (node-parent x)))\n         (t\n          (when (black? (node-right w))\n            (setf (node-color (node-left w)) +black+)\n            (setf (node-color w) +red+)\n            (right-rotate Tree w)\n            (setf w (node-right (node-parent x))))\n          (setf (node-color w) (node-color (node-parent x)))\n          (setf (node-color (node-parent x)) +black+)\n          (setf (node-color (node-right w)) +black+)\n          (left-rotate  Tree (node-parent x))\n          (setf x (rb-tree-root Tree)))))))\n\n(defun rb-delete-fixup (Tree x)\n  (let (w)\n   (loop while (and\n                (not (eq x (rb-tree-root Tree)))\n                (black? x))\n         do\n            (cond\n              ((eq x (node-left (node-parent x)))\n               (%rb-delete-fixup-helper-left))\n              (t\n               (%rb-delete-fixup-helper-right)))))\n  (setf (node-color x) +black+))\n\n\n\n(defun validate! (tree)\n  (let ((sentinel (rb-tree-sentinel tree))\n        (root (rb-tree-root tree)))\n    ;; Property 1: Every node is either red or black (implicit in representation)\n    \n    ;; Property 2: The root is black\n    (assert (black? root) () \"Root must be black\")\n    \n    ;; Property 3: All leaves (NIL/sentinel) are black\n    (assert (black? sentinel) () \"Sentinel must be black\")\n    \n    ;; Helper function to validate properties 4 and 5\n    (labels ((validate-node (node)\n               (cond\n                 ((eq node sentinel) 1) ; Black height of sentinel is 1\n                 (t\n                  ;; Property 4: If a node is red, both its children are black\n                  (when (red? node)\n                    (assert (black? (node-left node)) () \n                            \"Red node must have black left child\")\n                    (assert (black? (node-right node)) () \n                            \"Red node must have black right child\"))\n                  \n                  ;; Recursively validate children and check black heights\n                  (let ((left-height (validate-node (node-left node)))\n                        (right-height (validate-node (node-right node))))\n                    ;; Property 5: All paths have same black height\n                    (assert (= left-height right-height) () \n                            \"Black height mismatch at node ~A\" (node-key node))\n                    \n                    ;; Return black height of current subtree\n                    (if (black? node)\n                        (1+ left-height)\n                        left-height))))))\n      \n      (validate-node root))\n    \n    t))\n\n(defun sorted-keys (tree)\n  (let ((result))\n    (labels ((dfs (node)\n               (unless (eq node (rb-tree-sentinel tree))\n                 (dfs (node-left node))\n                 (push (node-key node) result)\n                 (dfs (node-right node)))))\n      (dfs (rb-tree-root tree)))\n    (nreverse result)))\n\n(defun tree-minimum (Tree node)\n  (cond\n    ((or\n      (eq (rb-tree-sentinel Tree) (node-left node)))\n     node)\n    (t\n     (tree-minimum Tree (node-left node)))))\n"
  },
  {
    "path": "src/util/recaptcha.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/recaptcha\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:markup\n                #:xml-tag-children)\n  (:export\n   #:recaptcha))\n(in-package :util/recaptcha)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defclass recaptcha ()\n  ((site-key :initarg :site-key\n          :initform nil\n          :reader recaptcha-site-key\n          :documentation \"This token will be used on web pages, so will be public.\")\n   (api-key :initarg :api-key\n            :initform nil\n            :reader recaptcha-api-key\n            :documentation \"This is the Google Cloud API key, different from the token, and is meant to be private.\")))\n\n(defclass installation-with-recaptcha ()\n  ((recaptcha :initarg :recaptcha\n              :initform nil\n              :reader recaptcha)))\n\n(defmethod recaptcha (installation)\n  nil)\n\n(defmethod recaptcha-verify-token ((self recaptcha)\n                                   (token string))\n    (let ((body `((:event . ((:token . ,token)\n                             (:expected-action . \"signup\")\n                             (:site-key . ,(recaptcha-site-key self)))))))\n      (multiple-value-bind (body res)\n        (http-request\n         (format nil \"https://recaptchaenterprise.googleapis.com/v1/projects/screenshotbot/assessments?key=~a\"\n                 (recaptcha-api-key self))\n         :content-type \"application/json\"\n         :method :post\n         :content (json:encode-json-to-string body))\n      (unless (eql 200 res)\n        (error \"Recaptcha failed with: ~a\" body))\n      (let* ((obj (json:decode-json-from-string body))\n             (score (assoc-value (assoc-value obj :risk-analysis) :score)))\n        score))))\n\n(defmethod recaptcha-verify-token ((self null) (token null))\n  0.9)\n\n(defmethod recaptcha-verify-token ((self recaptcha) (token null))\n  \"What if a bot tried to call the endpoint without the token?\"\n  0.001)\n\n(defmethod recaptcha-annotate-form ((self null) form)\n  form)\n\n(auto-restart:with-auto-restart ()\n  (defmethod recaptcha-annotate-form ((self recaptcha)\n                                      form)\n    (mquery:with-document (form)\n      (let ((id (mquery:attr (mquery:$ \"form\") \"id\")))\n        (assert (not (str:emptyp id)))\n        (setf (xml-tag-children form)\n              (list*\n               <script>\n               function onStandardSignupSubmit(token) {\n               document.getElementById(\",(progn id)\").submit();\n               }\n               </script>\n               (xml-tag-children form)))\n\n        (let ((submit (mquery:$ \"[type='submit']\")))\n          (assert submit)\n          (setf (mquery:attr submit \"type\") nil)\n          (setf (mquery:attr submit \"data-sitekey\")\n                (recaptcha-site-key self))\n          (setf (mquery:attr submit \"data-callback\")\n                \"onStandardSignupSubmit\")\n          (setf (mquery:attr submit \"data-action\")\n                \"submit\")\n          (mquery:add-class submit \"g-recaptcha\"))))))\n\n(defmethod recaptcha-add-head-script ((self null) x)\n  x)\n\n(auto-restart:with-auto-restart ()\n  (defmethod recaptcha-add-head-script ((self recaptcha) x)\n    (mquery:with-document (x)\n      (let ((head (mquery:$ \"head\")))\n        (mquery:mqappend\n         head\n         <script\n           src=(format nil \"https://www.google.com/recaptcha/enterprise.js?render=~a\" (recaptcha-site-key self))\n           ></script>\n)))))\n"
  },
  {
    "path": "src/util/remote-debugging.lisp",
    "content": "(defpackage :util/remote-debugging\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:start-client-remote-debugging-server))\n(in-package :util/remote-debugging)\n\n(defun server-connect-callback (ssl password conn)\n  (log:info \"Incoming connection\")\n  (let ((stream (comm:create-ssl-socket-stream conn\n                                               ssl\n                                               :errorp t)))\n    (let ((bytes (make-array 32 :element-type '(unsigned-byte 8))))\n      (read-sequence bytes stream)\n      (unless (equalp bytes (encode-password password))\n        (error \"Password mismatch, got ~s\" bytes)))\n    (log:info \"Creating connection\")\n    (dbg:create-client-remote-debugging-connection \"tdrhq-primary-connection\"\n                                                   :stream stream)))\n\n(defun encode-password (password)\n  (ironclad:digest-sequence :sha256 (flexi-streams:string-to-octets password) ))\n\n(defun start-client-remote-debugging-server (&key port ssl password)\n  (let ((server (comm:start-up-server\n                 :function (a:curry #'server-connect-callback ssl password)\n                 :service port)))\n    server))\n\n(defun connect-ide-remote-debugging-server (&key host port password)\n  (let ((stream (comm:open-tcp-stream host port :direction :io :keepalive t\n                                              :read-timeout nil\n                                              :ssl-ctx t\n                                              :write-timeout 300)))\n    (write-sequence (encode-password password) stream)\n    (dbg:create-ide-remote-debugging-connection \"client\"\n                                                :stream stream)))\n\n;; (setf *conn* (connect-ide-remote-debugging-server :host \"localhost\" :port  9090 :password \"foobar\"))\n;; (dbg:ide-eval-form-in-remote `(progn system:*line-arguments-list*) :connection *conn*)\n"
  },
  {
    "path": "src/util/request.lisp",
    "content": "(defpackage :util/request\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:common-lisp\n                #:with-open-stream)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:http-request\n   #:http-success-response?\n   #:engine\n   #:proxy-engine))\n(in-package :util/request)\n\n(defun http-success-response? (response-code)\n  (<= 200 response-code 204))\n\n(defun fix-bad-chars (url)\n  (cond\n    ((stringp url)\n     (str:replace-all \"|\" \"%7C\" url))\n    (t  url)))\n\n;; NOT BEING USED\n(define-condition request-error (error)\n  ())\n\n;; NOT BEING USED\n(define-condition network-error (request-error)\n  ()\n  (:documentation \"Errors that typically correspond to network failures, as opposed to\nserver failures.\"))\n\n;; NOT BEING USED\n(define-condition timeout-error (network-error)\n  ())\n\n\n(def-easy-macro wrap-ssl-errors (&key ensure-success want-stream &fn fn)\n  \"When calling http-request, most code expects it not crash if the\n network request fails. However if there's an SSL error it will lead\n to a crash.\n\n   If ensure-success is not set, we'll wrap errors to make them look\n like a 502 error.\"\n  (cond\n    (ensure-success\n     ;; We don't need to handle SSL errors\n     (funcall fn))\n    (t\n     (flet ((handle-error (e)\n              (log:warn \"Got error: ~a\" e)\n              (values\n               (cond\n                 (want-stream\n                  (make-string-input-stream \"\"))\n                 (t\n                  \"\"))\n               502 nil)))\n       (macrolet ((handle-errors (errors &body body)\n                    `(handler-case\n                         (progn ,@body)\n                       ,@ (loop for error in errors\n                                collect\n                                `(,error (e)\n                                         (handle-error e))))))\n         (handle-errors #+lispworks (comm:ssl-verification-failure\n                                     comm:ssl-failure\n                                     comm:socket-create-error\n                                     comm:ssl-closed)\n                        #-lispworks ()\n                        (funcall fn)))))))\n\n(defclass force-ip-address-engine ()\n  ())\n\n(defclass core-engine ()\n  ())\n\n(defclass engine (core-engine)\n  ())\n\n\n(defvar *engine* (make-instance 'engine)\n  \"Default request engine. Does not cache, does not use cookies, does not\nuse stream pools.\")\n\n(defmethod http-request-impl ((engine force-ip-address-engine) url\n                              &rest args\n                              &key force-address (force-port 80)\n                                (connection-timeout 20)\n                                (read-timeout 20)\n                                (write-timeout 20)\n                              &allow-other-keys)\n  (cond\n    (force-address\n     (let ((stream (flex:make-flexi-stream\n                    (chunga:make-chunked-stream\n                     #+lispworks\n                     (comm:open-tcp-stream\n                      force-address force-port\n                      :element-type 'flex:octet\n                      :timeout connection-timeout\n                      :read-timeout read-timeout\n                      :write-timeout write-timeout\n                      :errorp t)\n                     #-lispworks\n                     (usocket:socket-stream\n                      (usocket:socket-connect host port\n                                              :element-type 'octet\n                                              :timeout connection-timeout\n                                              :nodelay :if-supported)))\n                    :external-format drakma::+latin-1+)))\n       (with-open-stream (stream stream)\n         (apply #'call-next-method\n                engine url\n                :stream stream\n                (alexandria:remove-from-plist args :force-address :force-port)))))\n    (t\n     (call-next-method))))\n\n(defmethod http-request-impl ((engine engine) url &key &allow-other-keys)\n  (call-next-method))\n\n(defmethod low-level-request ((engine core-engine)\n                              url\n                              &rest args\n                                &key &allow-other-keys)\n  (apply #'drakma:http-request url\n         args))\n\n(define-condition bad-response-code (error)\n  ((code :initarg :code)\n   (url :initarg :url\n        :initform nil)\n   (body :initarg :body\n         :reader bad-response-code-body))\n  (:report (lambda (e output)\n             (with-slots (code url) e\n               (format output \"Got response code ~a when requesting ~a\" code url)))))\n\n\n(defmethod http-request-impl ((engine core-engine)\n                              url &rest args &key headers-as-hash-table\n                                               want-string\n                                               ensure-success\n                                               additional-headers\n                                               accept-gzip\n                                               want-stream\n                                               (verify #-(or mswindows win32) :required\n                                                       #+(or mswindows win32) nil)\n                              &allow-other-keys)\n  \"WANT-STRING just means we will never return a binary array, it will\nalways be converted to a UTF-8 string even if the encoding format is\nnot specified.\"\n  (let* ((old-body-format-function drakma:*body-format-function*)\n         (drakma:*body-format-function* old-body-format-function))\n    (let* ((url (fix-bad-chars url))\n           (args (a:remove-from-plist args :headers-as-hash-table\n                                      :accept-gzip\n                                           :ensure-success\n                                      :want-string\n                                           #+ccl :connection-timeout\n                                      #-lispworks :read-timeout)))\n      \n      (when accept-gzip\n        (push\n         (cons :accept-encoding \"gzip\")\n         additional-headers))\n      (when want-string\n        (setf drakma:*body-format-function*\n              (lambda (headers external-format-in)\n                (or\n                 (funcall old-body-format-function headers external-format-in)\n                 :utf-8))))\n      \n      (multiple-value-bind (res status headers)\n          (wrap-ssl-errors (:ensure-success ensure-success :want-stream want-stream)\n            (let ((drakma:*text-content-types*\n                    (list*\n                     (cons \"application\" \"json\")\n                     drakma:*text-content-types*)))\n              (apply #'low-level-request engine\n                     url\n                     :want-stream want-stream\n                     :additional-headers additional-headers\n                     :verify verify\n                     args)))\n        (flet ((maybe-ensure-success (&optional response)\n                 (declare (ignore response))\n                 (when (and ensure-success\n                            (not (http-success-response? status)))\n                   (log:info \"headers: ~a\" headers)\n                   (error 'bad-response-code\n                          :code status\n                          :url url\n                          :body (when want-string\n                                  ;; otherwise, res is a stream and we can't do much with it\n                                  res)))))\n          (handler-bind ((error (lambda (e)\n                                  (declare (ignore e))\n                                  ;; We're not going to actually return the stream\n                                  (when (streamp res)\n                                    (close res)))))\n            (maybe-ensure-success res)\n            (values\n             res\n             status\n             (cond\n               (headers-as-hash-table\n                (make-header-hash-table headers))\n               (t\n                headers)))))))))\n\n(defun http-request (url &rest args &key (engine *engine*) &allow-other-keys)\n  (apply #'http-request-impl\n         engine\n         url\n         (a:remove-from-plist args :engine)))\n\n(defun make-header-hash-table (headers)\n  (let ((ret (make-hash-table :test #'equal)))\n    (loop for (key . value) in headers\n          do (setf (gethash (string-downcase key) ret) value))\n    ret))\n\n(defclass proxy-engine ()\n  ((proxy :initarg :proxy\n          :initform nil\n          :reader proxy-url\n          :documentation \"A list with two values, an IP address and a port, which designates the HTTP proxy, or NIL if no proxy should be used\")))\n\n(defmethod http-request-impl ((self proxy-engine) url &rest args)\n  (cond\n    ((proxy-url self)\n     (apply #'call-next-method\n            self\n            url\n            :proxy (proxy-url self)\n            args))\n    (t\n     (call-next-method))))\n"
  },
  {
    "path": "src/util/ret-let.lisp",
    "content": "(uiop:define-package :util/ret-let\n    (:use #:cl)\n  (:export\n   #:ret-let))\n(in-package :util/ret-let)\n\n(defmacro ret-let ((val expr) &body body)\n  \"Evaluates expr and stores in val. Evaluate body with val\n  bound. Returns val.\"\n  `(let ((,val ,expr))\n     ,@body\n     ,val))\n"
  },
  {
    "path": "src/util/reused-ssl.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/reused-ssl\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request\n                #:low-level-request\n                #:engine)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:util/truncated-stream\n                #:delegate\n                #:truncated-stream)\n  (:export\n   #:with-reused-ssl)\n  (:local-nicknames #-lispworks\n                    (#:stream #:trivial-gray-streams)))\n(in-package :util/reused-ssl)\n\n(defvar *timeout* 10)\n\n(defclass connection ()\n  ((last-use-time :initarg :last-use-time\n                  :accessor last-use-time\n                  :initform (get-universal-time))\n   (stream :initarg :stream\n           :reader connection-stream)\n   (domain :initarg :domain\n           :reader connection-domain))\n  (:documentation \"A connection that has been kept-alive\"))\n\n(defclass reuse-context ()\n  ((connections :initarg :connections\n                :accessor connections\n                :initform (make-hash-table :test #'equal))))\n\n(defmethod find-connection ((self reuse-context) domain)\n  (trim-old-connections self)\n  (symbol-macrolet ((stack (gethash domain (connections self))))\n    (log:debug \"hash-table is ~S\" (connections self))\n    (prog1\n        (pop stack)\n      (log:debug \"Length of connections is ~a\" (length stack)))))\n\n(defmethod trim-old-connections ((self reuse-context))\n  (let ((now (get-universal-time)))\n   (loop for key being the hash-keys of (connections self)\n           using (hash-value connections)\n         do\n            (setf\n             (gethash key (connections self))\n             (delete-if (lambda (connection)\n                          (< (last-use-time connection) (- now *timeout*)))\n                        connections)))))\n\n(defmethod find-connection ((self null) domain)\n  nil)\n\n(defmethod %cleanup ((self reuse-context))\n  (loop for domain being the hash-keys of (connections self)\n        using (hash-value connections) do\n    (loop for connection in connections\n          do\n             (log:debug \"Cleaning up: ~a\" (connection-stream connection))\n             (close (connection-stream connection))))\n  (setf (connections self) nil))\n\n(defvar *reuse-contexts* nil)\n\n(defclass reused-ssl-mixin ()\n  ())\n\n(defmethod reuse-context (engine)\n  (assoc-value *reuse-contexts* engine))\n\n(defclass reused-ssl-engine (reused-ssl-mixin\n                             engine)\n  ())\n\n(defvar *default-reused-ssl-engine*\n  (make-instance 'reused-ssl-engine))\n\n\n(defclass tracked-stream (truncated-stream)\n  ((reusable-stream :initarg :reusable-stream\n                    :initform (error \"must provide :reusable-stream\")\n                    :reader reuseable-stream\n                    :documentation \"The underlying stream that can be reused when closed, this might be different from the delegate!\")\n   (closedp :initform nil\n            :accessor closedp)\n   (domain :initarg :domain\n           :reader domain)\n   (reuse-context :initarg :reuse-context\n                  :reader reuse-context))\n  (:documentation \"A stream that once closed, we'll reuse the underlying stream\"))\n\n(def-easy-macro with-reused-ssl (engine &key &binding reuse-context &fn fn)\n  (let ((reuse-context (make-instance 'reuse-context)))\n    (unwind-protect\n         (let ((*reuse-contexts*\n                 (acons\n                  engine reuse-context\n                  *reuse-contexts*)))\n          (fn reuse-context))\n      (%cleanup reuse-context))))\n\n(defmethod stream::stream-element-type ((self tracked-stream))\n  ;; Delete\n  (call-next-method))\n\n(defmethod stream:stream-read-sequence ((self tracked-stream)\n                                        sequence\n                                        start\n                                        end\n                                        #-lispworks #-lispworks\n                                                    &key &allow-other-keys)\n  ;; Delete\n  (call-next-method))\n\n#+nil\n(defmethod stream:stream-check-eof-no-hang ((self tracked-stream))\n  (stream:stream-check-eof-no-hang (delegate self)))\n\n#+nil\n(defmethod stream:stream-read-char ((self tracked-stream))\n  (stream:stream-read-char (delegate self)))\n\n#+nil\n(defmethod stream:stream-read-char-no-hang ((self tracked-stream))\n  (stream:stream-read-char-no-hang self))\n\n(defmethod stream:stream-read-sequence ((self tracked-stream)\n                                        sequence\n                                        start\n                                        end\n                                        #-lispworks #-lispworks\n                                                    &key &allow-other-keys)\n  (cond\n    ((closedp self)\n     (error \"The stream is already closed\"))\n    (t\n     (call-next-method))))\n\n(defun push-connection (reuse-context domain stream)\n  (push\n   (make-instance 'connection\n                  :domain domain\n                  :stream stream)\n   (gethash domain (connections reuse-context))))\n\n(defmethod close ((self tracked-stream) &key abort)\n  (declare (ignore abort))\n  ;; Rather than really closing, push connection\n  (cond\n    ((not (closedp self))\n     (log:debug \"Closing a tracked-stream\")\n     (setf (closedp self) t)\n     (push-connection\n      (reuse-context self)\n      (domain self)\n      (delegate self)))\n    (t\n     (log:debug \"The tracked-stream is already closed\"))))\n\n(defmethod stream:stream-finish-output ((self tracked-stream))\n  ;; Delete\n  (call-next-method))\n\n(defmethod stream:stream-force-output ((self tracked-stream))\n  ;; Delete\n  (call-next-method))\n\n(defmethod low-level-request ((self reused-ssl-mixin)\n                              url &rest args\n                              &key want-stream\n                              &allow-other-keys)\n  (let ((reuse-context (assoc-value *reuse-contexts* self)))\n    (cond\n     ((not reuse-context)\n      (warn \"no reuse-context available\")\n      (call-next-method))\n     (t\n      (multiple-value-bind (stream code headers\n                            uri\n                            underlying-stream\n                            should-close)\n          (anaphora:acond\n            ((find-connection (assoc-value *reuse-contexts* self)\n                              (tracked-stream-key\n                               (puri:uri url)))\n             (let* ((connection anaphora:it))\n               (log:debug \"Reusing an existing stream ~a\" (connection-stream connection))\n               (apply #'call-next-method\n                      self\n                      url\n                      :keep-alive t\n                      :close nil\n                      :stream (connection-stream connection)\n                      args)))\n            (t\n             (log:debug \"Creating a new stream\")\n             (apply #'call-next-method\n                    self\n                    url\n                    :keep-alive t\n                    :close nil\n                    args)))\n        (cond\n          (should-close\n           (warn \"Server is making us close the stream\")\n           (values stream code headers))\n          (t\n           (unless reuse-context\n             (error \"Could not find reuse-context in ~s\"\n                    *reuse-contexts*))\n           (values\n            (cond\n              (want-stream\n               (make-instance 'tracked-stream\n                              :reuse-context reuse-context\n                              :bytes-left (parse-integer (assoc-value headers :content-length))\n                              :reusable-stream underlying-stream\n                              :domain (tracked-stream-key uri)\n                              :delegate stream))\n              (t\n               (push-connection reuse-context\n                                (tracked-stream-key uri)\n                                underlying-stream)\n               ;; will be an object!\n               stream))\n            code\n            headers))))))))\n\n(defun tracked-stream-key (uri)\n  (format nil \"~a://~a\"\n          (string-downcase (puri:uri-scheme uri))\n          (puri:uri-host uri)))\n\n#+nil\n(hcl:profile\n (with-reused-ssl (*default-reused-ssl-engine*)\n   (time\n    (loop for i from 0 to 10\n          collect (http-request\n                   \"https://screenshotbot.io/api/version\"\n                   :want-string t\n                   :engine *default-reused-ssl-engine*)))))\n\n#+nil\n(hcl:profile\n (with-reused-ssl (*default-reused-ssl-engine*)\n   (time\n    (loop for i from 0 to 10\n          collect (uiop:slurp-input-stream\n                   'string\n                   (http-request\n                    \"http://ident.me\"\n                    :want-stream t\n                    :engine *default-reused-ssl-engine*))))))\n"
  },
  {
    "path": "src/util/schema/core.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n;;;;\n;;;; The plan with this file is to document the entire history of the\n;;;; schema. Each version will specify what changes have been made. In\n;;;; the future we might use this to do sanity checks\n\n(pkg:define-package :screenshotbot/schema/core\n    (:use #:cl\n          #:alexandria)\n  (:export #:defschema\n           #:validate-schemas))\n\n(defvar *schemas* nil)\n(defvar *validatedp* nil)\n\n(defclass schema ()\n  ((version :initarg :version)))\n\n(defmacro defschema (&key version)\n  `(progn\n     (setf (assoc-value *schemas* ,version)\n           (make-instance 'schema :version ,version))))\n\n(defun validate-schemas ()\n  (setf *validatedp* t))\n\n\n(defun slot-schema (class)\n  (loop for slot in (closer-mop:class-slots class)\n        collect\n        (let ((sym (closer-mop:slot-definition-name slot)))\n          `((\"name\" . ,(string sym))\n            (\"package\" . ,(package-name (symbol-package sym)))))))\n\n(defun schema-of-class (class)\n  `((\"name\" . ,(string (class-name class)))\n    (\"package\" . ,(string (package-name (symbol-package (class-name class)))))\n    (\"slots\" . ,(slot-schema class))))\n\n(defun full-schema ()\n  (loop for parent in '(bknr.datastore:store-object util:object-with-oid)\n        appending\n        (loop for class in (closer-mop:class-direct-subclasses (find-class parent))\n              collect (schema-of-class class))))\n"
  },
  {
    "path": "src/util/schema/history.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(pkg:define-package :screenshotbot/schema/history\n    (:use #:cl\n          #:./core\n          #:alexandria))\n\n(defschema :version 1)\n\n(validate-schemas)\n"
  },
  {
    "path": "src/util/simple-queue.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/simple-queue\n  (:use #:cl)\n  (:export\n   #:make-queue\n   #:dequeue\n   #:enqueue\n   #:queue-emptyp\n   #:queue-length\n   #:queue-items))\n(in-package :util/simple-queue)\n\n(defvar *lock* (bt:make-lock)\n  \"Only used for enqueue-with-max-length\")\n\n(defclass queue ()\n  ((head :accessor head\n         :initform nil)\n   (tail :accessor tail\n         :initform nil)\n   (length :initform 0\n           :reader queue-length\n           :accessor %queue-length)))\n\n(defun make-queue ()\n  (make-instance 'queue))\n\n(defmethod enqueue (value (queue queue))\n  (let ((new-tail (cons value nil)))\n    (incf (%queue-length queue))\n    (cond\n      ((not (head queue))\n       (setf (head queue) new-tail)\n       (setf (tail queue) new-tail))\n      (t\n       (setf (cdr (tail queue)) new-tail)\n       (setf (tail queue) new-tail)))))\n\n(defmethod enqueue-with-max-length (value (queue queue) &key max-length)\n  \"Enqueues a new element, and keeps dequeing until the max-length is\nreached.\n\nThread safe is enqueue-with-max-length is the only way you're\naccessing the queue.\n\nConvenient for keeping in-memory logs.\"\n  (bt:with-lock-held (*lock*)\n    (enqueue value queue)\n    (loop while (> (queue-length queue) max-length)\n          do\n             (dequeue queue))))\n\n(defun dequeue (queue)\n  (let ((head (head queue)))\n    (setf (head queue) (cdr head))\n    (unless (head queue)\n      (decf (%queue-length queue))\n      (setf (tail queue) nil))\n    (car head)))\n\n(defun queue-emptyp (queue)\n  (null (head queue)))\n\n(defun queue-items (queue)\n  (copy-seq (head queue)))\n"
  },
  {
    "path": "src/util/sizeof.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/sizeof\n  (:use #:cl)\n  (:export\n   #:sizeof\n   #:def-int-type\n   #:def-uint-type)\n  (:local-nicknames #-lispworks\n                    (:fli #:util/fake-fli)))\n(in-package :util/sizeof)\n\n(defun sizeof (type &key imports)\n  (tmpdir:with-tmpdir (tmpdir)\n    (let ((input-file (make-pathname\n                       :name \"input\"\n                       :type \"c\"\n                       :defaults tmpdir))\n          (output (make-pathname\n                   :name \"output\"\n                   :defaults tmpdir)))\n\n      (with-open-file (content input-file\n                               :direction :output)\n        (loop for import in (list* \"stdio.h\" imports)\n              do (format content \"#include <~a>~%\" import))\n        (format content \"int main() {\nprintf(\\\"%d\\\\n\\\", sizeof(~a));\nreturn 0;\n}\" type))\n\n      (uiop:run-program\n       (list \"gcc\" (namestring input-file)\n             \"-o\" (namestring output))\n       :error-output t\n       :output t)\n      (parse-integer\n       (uiop:run-program\n        (list (namestring output))\n        :output 'string)))))\n\n\n(defmacro %def-int-type (name type &key imports\n                                      (prefix \"\"))\n  (let ((size (sizeof type :imports imports)))\n    (flet ((get-type (type)\n             (intern\n              (format nil \"~a~a\"\n                      (string prefix)\n                      (string type))\n              (symbol-package type))))\n     `(fli:define-c-typedef ,name\n          ,(ecase size\n             (8 (get-type :int64))\n             (4 (get-type :int32))\n             (2 (get-type :int16))\n             (1 (get-type :int8)))))))\n\n(defmacro def-int-type (&rest args)\n  `(%def-int-type ,@args))\n\n(defmacro def-uint-type (&rest args)\n  `(%def-int-type ,@args :prefix \"U\"))\n"
  },
  {
    "path": "src/util/ssl.lisp",
    "content": "(defpackage :util/ssl\n  (:use #:cl)\n  (:export\n   #:*intern-ssl-context*))\n(in-package :util/ssl)\n\n(defvar *certificate*\n  \"-----BEGIN CERTIFICATE-----\nMIIDWzCCAkOgAwIBAgIUWzoGdurqWqw5e0eQh43pJkOBgAYwDQYJKoZIhvcNAQEL\nBQAwPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5KMSEwHwYDVQQKDBhJbnRlcm5l\ndCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMxMTIwMTg1NTU1WhcNMjQxMTE5MTg1NTU1\nWjA9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkoxITAfBgNVBAoMGEludGVybmV0\nIFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nALBpkNcm73b6db+kiZJL/GqHvKo+QK2ZuvunyoMtsCdQ2DXYc4Xr1+FySo9rMuQE\ntbCrhn8YAffalAWWXU664XaIOwRATEQPc5yvfW7WYcmsSK3tE6+Aq99mKqpT0X7A\npCQxr2b/OjPdv3d5iE9+nPKatsPwMvk+A32eKzxDRSpxTPUp4aSqvAz6xGw955Oe\nb7uj/Mwy+8+nYQzAkuLb95p0dbRaQrrY0gVXA7G5rdOMY32ivixhtKVAz6iVbWsf\ngb7zJbU/pQ/ajhgS9fmZ5VPqcobRP2ib1B3l7Yybh1vBJeV+XCZtY+R7VtwM3tmP\nrQyNSpxftnSmCD1GttE4JUkCAwEAAaNTMFEwHQYDVR0OBBYEFNk1fEuOj2faM73c\nHmUf9N0iDWHrMB8GA1UdIwQYMBaAFNk1fEuOj2faM73cHmUf9N0iDWHrMA8GA1Ud\nEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFAq9Ck3pYiAJ/fxlOMmjuBN\nenh6KnkwGy5f5PYgQw8l/LpCv9iHDBzJjc4mFULXp+H+wdK8ohQDwdd7brpIBdTi\nx9almhC7lVucjs7SaNUDg8h0O+1B/iUDst8X1ixOa4osGZRf2g9/c3BrBknUvuQ8\nVMPXbGznmwXZS969g3zEmhMfRxl/4O5G5Hd2n9uYj0nWD5N9L3B8cuJd6etI6ZZC\n1P0L+QIlEW9Y8DuaKlDC/VjxqZmmAKmzAISEDK5VZ/kV/RhpD8OavWfIQS8vZhTM\na1yOubCHt09QsDkyvBTWP3VX541g1voHDCcYMOyzrdv/KQHfwl/ojnEAg1t9HzA=\n-----END CERTIFICATE-----\")\n\n(defvar *local-cert*)\n\n(defun read-x509-from-file (file)\n  (comm:pem-read \"X509\" file))\n\n(defun read-x509-from-string (str)\n  (uiop:with-temporary-file (:pathname p :stream s)\n    (write-string str s)\n    (finish-output s)\n    \n    (fli:make-pointer :address (fli:pointer-address (comm:pem-read \"X509\" p)) :pointer-type 'comm:x509-pointer)))\n\n(defun local-cert ()\n  (if (boundp '*local-cert*)\n      *local-cert*\n      (setf\n       *local-cert*\n       (read-x509-from-file \"~/.tunnel/certificate.pem\"))))\n\n(defvar *cert-x509* nil)\n\n(defun cert-x509 ()\n  (util/misc:or-setf\n   *cert-x509*\n   (read-x509-from-string *certificate*)))\n\n(defun certificates-equal-p (cert1 cert2)\n  \"Securely compare X509 certificates for pinning by comparing public keys.\n   This prevents accepting different certificates with forged metadata.\n   For certificate pinning (no CA chain), metadata like serial/issuer can be\n   forged, but the public key is cryptographically bound to the private key.\"\n  (let ((data1 (comm:get-certificate-data cert1))\n        (data2 (comm:get-certificate-data cert2)))\n    ;; Compare public keys - cryptographically unique and unforgeable\n    ;;(log:debug \"Got data1\" data1)\n    ;;(log:debug \"Got data2\" data2)\n    (equalp (assoc :public-key data1)\n            (assoc :public-key data2))))\n\n(defun allowed-cert-p (cert &key allowed-certs )\n  (let ((allowed-certs (or allowed-certs\n                           (list\n                            (cert-x509)\n                            (local-cert)))))\n    (some\n    (lambda (allowed)\n      (when allowed\n        (certificates-equal-p allowed cert)))\n    (remove-if #'null\n               allowed-certs))))\n\n(defun verify-callback (socket-stream &key allowed-certs)\n  (let ((certs (comm:ssl-connection-copy-peer-certificates socket-stream)))\n    (unwind-protect\n        (when certs\n          (loop for cert across certs\n               if (not (allowed-cert-p cert :allowed-certs allowed-certs))\n                 return nil\n               finally\n                  (return t)))\n      (comm:release-certificates-vector certs))))\n\n(defvar *intern-ssl-context*\n  (comm:create-ssl-client-context :verify-callback #'verify-callback\n                                  :implementation :openssl\n                                  :openssl-trusted-file :default\n                                  :openssl-trusted-directory :default))\n\n(defvar *context-cache* (make-hash-table :test #'equal)\n  \"For a given SSL certificate, cache of the corresponding context\")\n\n(defun make-ssl-context (&key certificate)\n  (util/misc:or-setf\n   (gethash certificate *context-cache*)\n   (let ((x509 (read-x509-from-string certificate)))\n     (comm:create-ssl-client-context\n      :implementation :openssl\n      :verify-callback (lambda (socket-stream)\n                         (verify-callback socket-stream\n                                          :allowed-certs\n                                          (list x509)))\n      :openssl-trusted-file :default\n      :openssl-trusted-directory :default))))\n"
  },
  {
    "path": "src/util/states.csv",
    "content": "\"State\",\"Abbreviation\"\n\"Alabama\",\"AL\"\n\"Alaska\",\"AK\"\n\"Arizona\",\"AZ\"\n\"Arkansas\",\"AR\"\n\"California\",\"CA\"\n\"Colorado\",\"CO\"\n\"Connecticut\",\"CT\"\n\"Delaware\",\"DE\"\n\"District of Columbia\",\"DC\"\n\"Florida\",\"FL\"\n\"Georgia\",\"GA\"\n\"Hawaii\",\"HI\"\n\"Idaho\",\"ID\"\n\"Illinois\",\"IL\"\n\"Indiana\",\"IN\"\n\"Iowa\",\"IA\"\n\"Kansas\",\"KS\"\n\"Kentucky\",\"KY\"\n\"Louisiana\",\"LA\"\n\"Maine\",\"ME\"\n\"Montana\",\"MT\"\n\"Nebraska\",\"NE\"\n\"Nevada\",\"NV\"\n\"New Hampshire\",\"NH\"\n\"New Jersey\",\"NJ\"\n\"New Mexico\",\"NM\"\n\"New York\",\"NY\"\n\"North Carolina\",\"NC\"\n\"North Dakota\",\"ND\"\n\"Ohio\",\"OH\"\n\"Oklahoma\",\"OK\"\n\"Oregon\",\"OR\"\n\"Maryland\",\"MD\"\n\"Massachusetts\",\"MA\"\n\"Michigan\",\"MI\"\n\"Minnesota\",\"MN\"\n\"Mississippi\",\"MS\"\n\"Missouri\",\"MO\"\n\"Pennsylvania\",\"PA\"\n\"Rhode Island\",\"RI\"\n\"South Carolina\",\"SC\"\n\"South Dakota\",\"SD\"\n\"Tennessee\",\"TN\"\n\"Texas\",\"TX\"\n\"Utah\",\"UT\"\n\"Vermont\",\"VT\"\n\"Virginia\",\"VA\"\n\"Washington\",\"WA\"\n\"West Virginia\",\"WV\"\n\"Wisconsin\",\"WI\"\n\"Wyoming\",\"WY\"\n"
  },
  {
    "path": "src/util/statsig/statsig.lisp",
    "content": "(defpackage :util/statsig\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:alexandria\n                #:when-let\n                #:assoc-value)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:local-nicknames (#:json #:yason))\n  (:export\n   #:statsig-client\n   #:make-statsig-client\n   #:check-gate\n   #:log-event\n   #:get-config\n   #:get-layer\n   #:make-statsig-user\n   #:push-event)\n  (:nicknames :statsig))\n(in-package :util/statsig)\n\n(defclass statsig-client ()\n  ((api-key :initarg :api-key\n            :reader api-key\n            :documentation \"Server-side secret key or client SDK key for Statsig\")\n   (base-url :initarg :base-url\n             :initform \"https://api.statsig.com/v1\"\n             :reader base-url\n             :documentation \"Base URL for Statsig API\")\n   (events-url :initarg :events-url\n               :initform \"https://events.statsigapi.net/v1\"\n               :reader events-url\n               :documentation \"Events URL for Statsig API\")))\n\n(defun make-statsig-client (api-key &key (base-url \"https://api.statsig.com/v1\")\n                                         (events-url \"https://events.statsigapi.net/v1\"))\n  \"Create a new Statsig client with the given API key.\"\n  (make-instance 'statsig-client\n                 :api-key api-key\n                 :base-url base-url\n                 :events-url events-url))\n\n(defun make-statsig-user (&key user-id email ip user-agent country\n                               environment custom-ids custom\n                               private-attributes)\n  \"Create a Statsig user object for API requests.\"\n  (let ((user (make-hash-table :test 'equal)))\n    (when user-id\n      (setf (gethash \"userID\" user) user-id))\n    (when email\n      (setf (gethash \"email\" user) email))\n    (when ip\n      (setf (gethash \"ip\" user) ip))\n    (when user-agent\n      (setf (gethash \"userAgent\" user) user-agent))\n    (when country\n      (setf (gethash \"country\" user) country))\n    (when environment\n      (setf (gethash \"environment\" user) environment))\n    (when custom-ids\n      (setf (gethash \"customIDs\" user) custom-ids))\n    (when custom\n      (setf (gethash \"custom\" user) custom))\n    (when private-attributes\n      (setf (gethash \"privateAttributes\" user) private-attributes))\n    user))\n\n(defun make-api-headers (client &optional client-time)\n  \"Create standard headers for Statsig API requests.\"\n  (let ((headers `((\"statsig-api-key\" . ,(api-key client))\n                   (\"Content-Type\" . \"application/json\"))))\n    (when client-time\n      (push `(\"STATSIG-CLIENT-TIME\" . ,(write-to-string client-time)) headers))\n    headers))\n\n(defun make-api-request (client endpoint payload &key (use-events-url nil))\n  \"Make a POST request to the Statsig API.\"\n  (let* ((url (format nil \"~a/~a\"\n                      (if use-events-url\n                          (events-url client)\n                          (base-url client))\n                      endpoint))\n         (headers (make-api-headers client))\n         (json-payload (json:with-output-to-string* ()\n                         (json:encode payload))))\n    (log:debug \"Payload is: ~a\" json-payload)\n    (multiple-value-bind (response status response-headers)\n        (http-request url\n                      :method :post\n                      :content json-payload\n                      :additional-headers headers\n                      :want-string t\n                      :ensure-success t)\n      (values (json:parse response) status response-headers))))\n\n(defun check-gate-impl (client gate-name user)\n  \"Check if a feature gate is enabled for the given user.\n   Returns a hash table with gate evaluation result.\"\n  (let ((payload (make-hash-table :test 'equal)))\n    (setf (gethash \"gateName\" payload) (string-downcase gate-name))\n    (setf (gethash \"user\" payload) user)\n    (make-api-request client \"check_gate\" payload)))\n\n(defun check-gate (gate-name\n                   &key (client (statsig-client *installation*))\n                     (user (make-ip-user)))\n  (check-gate-impl client gate-name user))\n\n(defun make-event (event-name user &key value metadata)\n  \"Create an event object for Statsig analytics.\n   EVENT-NAME: Name of the event\n   USER: Statsig user object\n   VALUE: Optional numeric value \n   METADATA: Optional hash table of additional properties\"\n  (let ((event (make-hash-table :test 'equal)))\n    (setf (gethash \"eventName\" event) event-name)\n    (setf (gethash \"user\" event) user)\n    (setf (gethash \"time\" event) (round (/ (- (get-universal-time) 2208988800) 1)))\n    (when value\n      (setf (gethash \"value\" event) value))\n    (when metadata\n      (setf (gethash \"metadata\" event) metadata))\n    event))\n\n(defun log-events (client events)\n  \"Log a list of events to Statsig analytics.\n   CLIENT: Statsig client instance\n   EVENTS: List of event objects\"\n  (let ((payload (make-hash-table :test 'equal)))\n    (setf (gethash \"events\" payload) events)\n    (make-api-request client \"log_event\" payload :use-events-url t)))\n\n(defun log-event-now (client event-name user &key value metadata)\n  \"Log an event to Statsig analytics.\n   EVENT-NAME: Name of the event\n   USER: Statsig user object\n   VALUE: Optional numeric value \n   METADATA: Optional hash table of additional properties\"\n  (let ((event (make-event event-name user :value value :metadata metadata)))\n    (log-events client (list event))))\n\n(defun get-config (client config-name user)\n  \"Get dynamic config values for the given user.\n   Returns a hash table with config values.\"\n  (let ((payload (make-hash-table :test 'equal)))\n    (setf (gethash \"configName\" payload) config-name)\n    (setf (gethash \"user\" payload) user)\n    (make-api-request client \"get_config\" payload)))\n\n(defun get-layer (client layer-name user)\n  \"Get layer values for the given user.\n   Returns a hash table with layer parameter values.\"\n  (let ((payload (make-hash-table :test 'equal)))\n    (setf (gethash \"layerName\" payload) layer-name)\n    (setf (gethash \"user\" payload) user)\n    (make-api-request client \"get_layer\" payload)))\n\n(defgeneric statsig-client (installation)\n  (:documentation \"Expected to set on installations\")\n  (:method (installation)\n    nil))\n\n(defvar *events* nil)\n\n(defun make-ip-user ()\n  (make-statsig-user\n   :ip (ignore-errors (hunchentoot:real-remote-addr))\n   :user-id (when (auth:current-user)\n              (format nil\n                      \"~a.~a\"\n                      (installation-domain *installation*)\n                      (bknr.datastore:store-object-id (auth:current-user ))))))\n\n(defun push-event (event-name &key (client (statsig-client *installation*))\n                                user\n                                value\n                                metadata)\n  (when client\n    (atomics:atomic-push\n     (make-event\n      (string-downcase event-name)\n      (or user (make-ip-user))\n      :value value\n      :metadata metadata)\n     *events*)))\n\n(defun flush-events ()\n  (when-let ((events (util/atomics:atomic-exchange *events* nil)))\n    (when-let ((client (statsig-client *installation*)))\n      (log-events client events))))\n\n(def-cron flush-events ()\n  (flush-events))\n"
  },
  {
    "path": "src/util/statsig/test-statsig.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/statsig/test-statsig\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/statsig\n                #:*events*)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:core/installation/installation\n                #:*installation*)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :util/statsig/test-statsig)\n\n(util/fiveam:def-suite)\n\n(defmethod statsig:statsig-client ((install (eql :installation)))\n  (make-instance 'statsig:statsig-client))\n\n(def-fixture state ()\n  (let ((*installation* :installation))\n   (unwind-protect\n        (&body)\n     (setf *events* nil))))\n\n(test simple-push-event\n  (with-fixture state ()\n    (with-fake-request ()\n      (finishes\n        (statsig:push-event :hello-world))\n      (assert-that\n       *events*\n       (has-length 1)))))\n"
  },
  {
    "path": "src/util/statsig/util.statsig.asd",
    "content": "(defsystem :util.statsig\n  :depends-on (:util/request\n               :yason\n               :core.installation\n               :auth\n               :util/atomics\n               :util/cron\n               :atomics\n               :log4cl\n               :alexandria)\n  :serial t\n  :components ((:file \"statsig\")))\n\n(defsystem :util.statsig/tests\n  :depends-on (:util.statsig\n               :fiveam-matchers\n               :util.testing\n               :util/fiveam)\n  :components ((:file \"test-statsig\")))\n"
  },
  {
    "path": "src/util/store/aws-store.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/aws-store\n  (:use #:cl)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:util/store/store\n                #:ec2-get-local-ipv4)\n  (:export\n   #:make-aws-store))\n(in-package :util/store/aws-store)\n\n;; This isn't meant to be used outside of Modern Interpreters. This is\n;; highly tied to our deployment infrastructure.\n\n(defun make-aws-store (&key private-ips\n                         (group (error \"must provide group, and it must be equal to the name of the current USER.\"))\n                         port)\n  (assert (not (equal \"root\" (uiop:getenv \"USER\"))))\n  (assert (equal group (uiop:getenv \"USER\")))\n  (let ((current-ip (ec2-get-local-ipv4)))\n    (util/store/store:make-default-store\n     'util/store/store:raft-store-final\n     :data-path (path:catdir\n                 (user-homedir-pathname)\n                 \"raft-data/\")\n     :port port\n     :ip current-ip\n     :priority (cond\n                 ((equal current-ip (first private-ips))\n                  1)\n                 (t\n                  0))\n     :config (str:join \",\"\n                       (loop for ip in private-ips\n                             collect (format nil \"~a:~a:0\" ip port)))\n     :group group)))\n"
  },
  {
    "path": "src/util/store/benchmarks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/benchmarks\n  (:use #:cl)\n  (:import-from #:util/benchmark\n                #:def-benchmark)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class))\n(in-package :util/store/benchmarks)\n\n(defclass foo (store-object)\n  ((a :initarg :a) b c d e f (g :initarg :g) h i j k l m (n :initarg :n))\n  (:metaclass persistent-class))\n\n(defclass standard-foo ()\n  (a b c d e f (g :initarg :g :reader g) h i j k l m n))\n\n(def-benchmark object-destroyed-p-benchmark ()\n  (with-test-store ()\n    (let ((obj (make-instance 'foo)))\n      (benchmark:measure\n        (bknr.datastore::object-destroyed-p obj)))))\n\n(def-benchmark slot-value-benchmark ()\n  (with-test-store ()\n    (let ((obj (make-instance 'foo :g 42)))\n      (benchmark:measure\n        (slot-value obj 'g)))))\n\n(def-benchmark slot-value-early ()\n  (with-test-store ()\n    (let ((obj (make-instance 'foo :a 42)))\n      (benchmark:measure\n        (slot-value obj 'a)))))\n\n(def-benchmark standard-slot-access ()\n  (let ((obj (make-instance 'standard-foo :g 42)))\n    (let ((num 0))\n     (benchmark:measure\n       (incf num (g obj))))))\n\n(defclass some-object (store-object)\n  ((a) (b) (c) (d) (e) (f) (g) (h))\n  (:metaclass persistent-class))\n\n(benchmark:def-benchmark benchmark-snapshots\n  (with-test-store ()\n    (loop for i from 0 to 10000\n          do (make-instance 'some-object))\n    (benchmark:measure\n      (bknr.datastore:snapshot))))\n\n(def-benchmark encode-integer ()\n  (uiop:with-temporary-file (:stream s :element-type '(unsigned-byte 8))\n    (benchmark:measure\n      (bknr.datastore::%encode-integer 51 s))))\n\n\n(def-benchmark encode-nil ()\n  (uiop:with-temporary-file (:stream s :element-type '(unsigned-byte 8))\n    (benchmark:measure\n     (bknr.datastore::encode-symbol 'nil s))))\n"
  },
  {
    "path": "src/util/store/checksums.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/checksums\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:%write-tag)\n  (:import-from #:bknr.datastore\n                #:encode-object)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:decode-object)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:bknr.datastore\n                #:%decode-integer)\n  (:import-from #:flexi-streams\n                #:make-in-memory-input-stream)\n  (:import-from #:bknr.datastore\n                #:transaction)\n  (:import-from #:util/store/store\n                #:checksumed-mp-store)\n  (:import-from #:bknr.datastore\n                #:restore-transaction-log)\n  (:import-from #:bknr.datastore\n                #:%encode-integer)\n  (:import-from #:bknr.datastore\n                #:%decode-integer))\n(in-package :util/store/checksums)\n\n(defparameter *max-transaction-size* 16777216\n  \"A generous max transaction size\")\n\n(define-condition base-error (error)\n  ())\n\n(define-condition invalid-transaction-length-error (base-error)\n  ())\n\n(define-condition end-of-file-error (base-error)\n  ())\n\n(define-condition could-not-read-length (end-of-file-error)\n  ())\n\n(define-condition could-not-read-checksum (end-of-file-error)\n  ())\n\n(define-condition checksum-failure (base-error)\n  ())\n\n(defmethod decode-object ((tag (eql #\\C)) stream)\n  (let ((length\n          (handler-case\n              (%decode-integer stream)\n            (end-of-file ()\n              (error 'could-not-read-length))))\n        (digest (make-array 4 :element-type '(unsigned-byte 8))))\n    (when (>= length *max-transaction-size*)\n      ;; This can also happen when the length is corrupted\n      (error 'invalid-transaction-length-error))\n\n    (unless (= 4 (read-sequence digest stream))\n      (error 'could-not-read-checksum))\n\n    (let ((buff (make-array length :element-type '(unsigned-byte 8))))\n      (let ((bytes-read (read-sequence buff stream)))\n        (when (< bytes-read length)\n          (error 'end-of-file-error)))\n      (let ((actual-digest (ironclad:digest-sequence :crc32 buff)))\n        (unless (equalp digest actual-digest)\n          (error 'checksum-failure)))\n      (let ((stream (make-in-memory-input-stream buff)))\n       (decode stream)))))\n\n(defmethod encode-checksumed-object (object stream &optional\n                                                     (next-method (lambda (object stream)\n                                                                    (encode-object object stream))))\n  (%write-tag #\\C stream)\n  (let ((tmp (flex:make-in-memory-output-stream)))\n    (funcall next-method object tmp)\n    (let ((buff (flex:get-output-stream-sequence tmp)))\n      (when (> (length buff) *max-transaction-size*)\n        (error 'invalid-transaction-length-error))\n      (%encode-integer (length buff) stream)\n      (let ((digest (ironclad:digest-sequence :crc32 buff)))\n        (write-sequence digest stream))\n      (write-sequence buff stream))))\n\n\n(defmethod encode-object :around ((transaction transaction) stream)\n  (call-next-method)\n  #+nil\n  (encode-checksumed-object transaction stream\n                            (lambda (transaction stream)\n                              (call-next-method transaction stream))))\n\n(defun maybe-invoke-restart (restart)\n  (cond\n    ((find-restart restart)\n     (invoke-restart restart))\n    (t\n     (warn \"Could not find restart: ~a\" restart))))\n\n(defmethod restore-transaction-log :around ((store checksumed-mp-store)\n                                            transaction-log\n                                            &key until)\n  (declare (ignore until))\n  (let ((errorp nil))\n    (prog1\n        (handler-bind ((base-error (lambda (e)\n                                     (declare (ignore e))\n                                     (setf errorp t)\n                                     (maybe-invoke-restart 'bknr.datastore::discard))))\n          (call-next-method))\n      (cond\n        (errorp\n         (log:warn \"LOADED WITH TRUNCATED TRANSACTION LOG!\"))\n        (t\n         (log:info \"Transaction log was loaded successfully without truncation\"))))))\n"
  },
  {
    "path": "src/util/store/clone-logs-store.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/clone-logs-store\n  (:use #:cl)\n  (:import-from #:bknr.cluster/store\n                #:on-snapshot-save-impl)\n  (:import-from #:bknr.cluster/server\n                #:data-path)\n  (:import-from #:util/copy-file\n                #:copy-file-fast))\n(in-package :util/store/clone-logs-store)\n\n(defclass clone-logs-store ()\n  ()\n  (:documentation \"A fixture that ensures that all the old log files are kept during the\nsnapshot phase, useful for backing up purposes.\"))\n\n(defmethod on-snapshot-save-impl :around ((store clone-logs-store) snapshot-writer done)\n  (save-transaction-logs store)\n  (call-next-method))\n\n(defun save-transaction-logs (store)\n  (let ((dir (ensure-directories-exist\n              (path:catdir (data-path store) \"old-logs/\"))))\n    (log:info \"Saving transaction logs to ~a\" dir)\n    (clear-directory dir)\n    (let ((files (fad:list-directory (path:catdir (data-path store) \"log/\"))))\n      (loop for file in files\n            do\n               (let ((dest (make-pathname\n                            :name (pathname-name file)\n                            :defaults dir)))\n                 (log:info \"Linking ~a to ~a\" file dest)\n                 (copy-file-fast\n                  file\n                  dest))))))\n\n(defun clear-directory (dir)\n  (let ((files (fad:list-directory dir)))\n    (loop for file in files\n          do\n             (log:info \"Deleting ~a\" file)\n             (delete-file file))))\n"
  },
  {
    "path": "src/util/store/delayed-accessors.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/delayed-accessors\n  (:nicknames #:util/delayed-accessors)\n  (:use #:cl)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:bknr.datastore\n                #:with-transaction))\n(in-package :util/delayed-accessors)\n\n(defvar *lock* (bt:make-lock \"delayed-acc\"))\n\n(defmacro def-delayed-accessor (name accessor &key (step-min 1))\n  (let ((cache-var (intern (format nil \"*~a-CACHE*\" (string name))))\n        (cron-name (intern (format nil \"FLUSH-~a\" (string name)))))\n   `(progn\n      (defvar ,cache-var nil)\n      (defmethod ,name (obj)\n        (bt:with-lock-held (*lock*)\n         (let ((assoc (assoc obj ,cache-var)))\n           (cond\n             (assoc\n              (cdr assoc))\n             (t\n              (,accessor obj))))))\n\n      (defmethod (setf ,name) (val obj)\n        (bt:with-lock-held (*lock*)\n         (push\n          (cons obj val)\n          ,cache-var)))\n\n      (defun ,cron-name ()\n        (let ((cache\n                (bt:with-lock-held (*lock*)\n                  (prog1\n                      (remove-duplicates\n                       ,cache-var\n                       :key #'car\n                       :from-end t)\n                    (setf ,cache-var nil)))))\n          (with-transaction ()\n            (loop for (obj . value) in cache\n                  do (setf (,accessor obj)\n                           value)))))\n\n      (def-cron ,cron-name (:step-min ,step-min)\n        (,cron-name)))))\n"
  },
  {
    "path": "src/util/store/elb-store.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/elb-store\n  (:use #:cl)\n  (:import-from #:bknr.cluster/server\n                #:on-leader-start\n                #:on-leader-stop)\n  (:import-from #:util/threading\n                #:make-thread\n                #:ignore-and-log-errors)\n  (:export\n   #:elb-store-mixin))\n(in-package :util/store/elb-store)\n\n(defclass elb-store-mixin ()\n  ((elb-arn :initarg :elb-arn\n            :reader elb-arn\n            :initform nil)))\n\n(defun instance-id ()\n  (str:trim\n   (uiop:run-program\n    (list \"ec2metadata\" \"--instance-id\")\n    :output 'string)))\n\n\n(auto-restart:with-auto-restart (:retries 3 :sleep 1)\n  (defmethod  %run-target-cmd (self cmd)\n    (when (elb-arn self)\n      (uiop:run-program\n       (list \"aws\" \"elbv2\" cmd \"--target-group-arn\"\n             (elb-arn self)\n             \"--targets\"\n             (format nil \"Id=~a\" (instance-id)))))))\n\n(defun run-target-cmd (self cmd)\n  (make-thread\n   (lambda ()\n     (%run-target-cmd self cmd))))\n\n(defmethod on-leader-start :after ((self elb-store-mixin))\n  (ignore-and-log-errors ()\n   (run-target-cmd self \"register-targets\")))\n\n(defmethod on-leader-stop :after ((self elb-store-mixin))\n  (ignore-and-log-errors ()\n   (run-target-cmd self \"deregister-targets\")))\n"
  },
  {
    "path": "src/util/store/encodable.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/encodable\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:find-slot-name-with-automatic-rename\n                #:%read-tag\n                #:decode\n                #:encode\n                #:%write-tag\n                #:decode-object\n                #:encode-object))\n(in-package :util/store/encodable)\n\n(defclass encodable ()\n  ())\n\n(defmethod encode-object ((self encodable) stream)\n  (%write-tag #\\E stream)\n  (encode (type-of self) stream)\n  (%write-tag #\\0 stream)\n  (encode\n   (loop for slot in (closer-mop:class-slots (class-of self))\n         for slot-name = (closer-mop:slot-definition-name slot)\n         if (slot-boundp self slot-name)\n         collect (cons\n                  slot-name\n                  (slot-value self slot-name)))\n   stream))\n\n(defmethod decode-object ((tag (eql #\\E)) stream)\n  (let* ((type (decode stream))\n         (version (%read-tag stream))\n         (class (find-class type)))\n    (let* ((obj (allocate-instance class))\n           (slot-pairs (decode stream)))\n      (loop for (slot-name . value) in slot-pairs\n            do (setf (slot-value\n                      obj\n                      (find-slot-name-with-automatic-rename\n                       class\n                       slot-name))\n                     value))\n      obj)))\n"
  },
  {
    "path": "src/util/store/export.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/export\n  (:nicknames :util/export)\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:bknr.datastore\n                #:with-transaction))\n(in-package :util/export)\n\n(defmethod export-slots (class (slot-name symbol))\n  (loop for obj in (bknr.datastore:class-instances class)\n        if (slot-boundp obj slot-name)\n        collect (list\n                 (bknr.datastore:store-object-id obj)\n                 (string slot-name)\n                 (slot-value obj slot-name))))\n\n(defmethod find-slot (class (slot-name string))\n  (loop for slot in (closer-mop:class-slots (find-class class))\n        for slot-def-name = (closer-mop:slot-definition-name slot)\n        if (string-equal slot-def-name slot-name)\n          ;; Convert name to symbol :/\n          return slot-def-name))\n\n(defmethod export-slots (class (slot-name string))\n  (export-slots class (find-slot class slot-name)))\n\n(defmethod export-slots-to-file (class slot-name file)\n  (with-open-file (stream file :direction :output\n                               :element-type 'flex:octet)\n    (encode\n     (export-slots class slot-name)\n     stream)))\n\n(defun import-slots (slots)\n  (loop for (id slot-name res) in slots\n        for obj = (bknr.datastore:store-object-with-id id)\n        do (import-slot-for-obj obj\n                                (find-slot (type-of obj) slot-name)\n                                res)))\n\n(defun import-slot-for-obj (obj slot-def-name res)\n  (log:info \"importing ~a ~a\" obj slot-def-name)\n  (restart-case\n      (cond\n        ((slot-boundp obj slot-def-name)\n         (error \"Slot already bound for ~a\" obj))\n        (t\n         (with-transaction ()\n           (setf (slot-value obj slot-def-name) res))))\n    (continue ()\n      nil)))\n\n(defmethod import-slots-from-file (class slot-name file)\n  (with-open-file (stream file :direction :input\n                                :element-type 'flex:octet)\n    (import-slots (decode stream))))\n\n\n#+nil\n(export-slots\n 'screenshotbot/bitbucket/promoter::bitbucket-acceptable\n \"send-task-args\")\n"
  },
  {
    "path": "src/util/store/fset-index.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/fset-index\n  (:use #:cl)\n  (:import-from #:bknr.indices\n                #:index-reinitialize)\n  (:import-from #:bknr.indices\n                #:index-add)\n  (:import-from #:bknr.indices\n                #:index-get)\n  (:import-from #:bknr.indices\n                #:index-clear)\n  (:import-from #:bknr.indices\n                #:index-remove)\n  (:import-from #:bknr.indices\n                #:index-values)\n  (:import-from #:bknr.indices\n                #:slot-index)\n  (:import-from #:alexandria\n                #:when-let\n                #:curry)\n  (:import-from #:util/store/store\n                #:validate-index-values)\n  (:import-from #:bknr.indices\n                #:index-existing-error)\n  (:export\n   #:fset-set-index\n   #:fset-unique-index\n   #:fset-set-compat-index\n   #:index-least\n   #:fset-many-to-many-index))\n(in-package :util/store/fset-index)\n\n(defvar *index-id* 0)\n\n(defclass abstract-fset-index ()\n  ((slot-name :initarg :slots\n              :initform nil\n              :accessor %slots)\n   (map :initform (fset:empty-map)\n        :accessor %map)\n   (index-id :initform #+lispworks (atomics:atomic-incf *index-id*) #-lispworks 0\n             :reader index-id\n             :documentation \"A unique index to help debug index failures\")))\n\n(defmethod print-object ((self abstract-fset-index) out)\n  (format out\n          \"#<~a ~a on ~a size {~d}>\"\n          (string (type-of self))\n          (index-id self)\n          (%slots self)\n          (fset:size (%map self))))\n\n(defmacro update-map (self (map) &body expr)\n  `(atomics:atomic-update\n    (slot-value ,self 'map)\n    (lambda (,map)\n      ,@expr)))\n\n(defun %slot-name (self)\n  (car (%slots self)))\n\n(defmethod initialize-instance :after ((self abstract-fset-index)\n                                       &rest args\n                                       &key slot-name slots)\n  (assert (not (and slot-name slots)))\n  (log:debug \"Created new index: ~a\" self)\n  (if slot-name\n      (setf (%slots self) (list slot-name))))\n\n(defmethod index-reinitialize :before ((new-index abstract-fset-index) old-index)\n  (log:debug \"Reinitializing index ~a from ~a\" new-index\n             old-index))\n\n(defmethod index-reinitialize ((new-index abstract-fset-index)\n                               (old-index abstract-fset-index))\n  (update-map new-index (map)\n    (declare (ignore map))\n    (fset:map-union\n     map\n     (%map old-index)\n     (curry #'merge-map-values new-index)))\n  new-index)\n\n\n(define-condition index-values-dont-match (warning)\n  ((val1 :initarg :val1)\n   (val2 :initarg :val2))\n  (:report (lambda (w out)\n             (with-slots (val1 val2) w\n               (format out \"Reinitializing index with values that don't match: ~a, ~a\"\n                       val1 val2)))))\n\n(defmethod index-reinitialize ((new-index abstract-fset-index)\n                               (old-index slot-index))\n  (warn \"Recreating index from slot-index\")\n  (mapcar (curry #'index-add new-index)\n          (index-values old-index))\n  new-index)\n\n\n(defclass fset-unique-index (abstract-fset-index)\n  ())\n\n(defclass fset-set-index (abstract-fset-index)\n  ((map :initform (fset:empty-map (fset:empty-set)))))\n\n(defmethod index-reinitialize ((new-index fset-set-index)\n                               (old-index fset-unique-index))\n  (mapcar\n   (lambda (obj)\n     (restart-case\n         (index-add new-index obj)\n       (ignore-adding-object ()\n         nil)))\n   (index-values old-index))\n  new-index)\n\n(defclass fset-many-to-many-index (fset-set-index)\n  ()\n  (:documentation \"An index where a slot can have multiple values, and you can\nlook up every record that has one of those slot values.\"))\n\n(defclass fset-set-compat-index (fset-set-index)\n  ()\n  (:documentation \"Like fset-set-index, but behaves similarly to hash-index. For example,\nthe index reader returns a list in reverse sorted order instead of a set.\"))\n\n(defmethod index-object-key ((self abstract-fset-index) obj)\n  (if (= 1 (length (%slots self)))\n      (slot-value obj (%slot-name self))\n      (loop for slot in (%slots self)\n            collect (slot-value obj slot))))\n\n(defun every-indexed-slot-boundp (self obj)\n  ;;(every (curry #'slot-boundp obj) (%slots self))\n  (loop for slot in (%slots self)\n        if (not (slot-boundp obj slot))\n          return nil\n        finally\n           (return t)))\n\n(defmethod index-add :around ((self abstract-fset-index) obj)\n  (when (and\n         (every-indexed-slot-boundp self obj)\n         (index-object-key self obj))\n    (call-next-method)))\n\n(defmethod index-remove :around ((self abstract-fset-index) obj)\n  (when (and\n         (every-indexed-slot-boundp self obj)\n         (index-object-key self obj))\n    (call-next-method)))\n\n(defmethod merge-map-values ((self fset-set-index)\n                             val1 val2)\n  (fset:union val1 val2))\n\n(defmethod merge-map-values ((self fset-unique-index)\n                             val1 val2)\n  (unless (eql val1 val2)\n   (warn 'index-values-dont-match))\n  val1)\n\n(defmethod index-add ((self fset-unique-index)\n                      obj)\n  (update-map self (map)\n    (let ((key (index-object-key self obj)))\n      (cond\n        ((when-let ((prev (fset:lookup map key)))\n           (not (eql prev obj)))\n         (error 'index-existing-error\n                :index self\n                :key key\n                :value obj))\n        (t\n         (fset:with map\n                    key\n                    obj))))))\n\n(defmethod index-add ((self fset-many-to-many-index)\n                      obj)\n  (update-map self (map)\n    (reduce\n     (lambda (map key)\n       (fset:with map\n                  key\n                  (fset:with\n                   (fset:lookup map key)\n                   obj)))\n     (index-object-key self obj)\n     :initial-value map)))\n\n(defmethod index-add ((self fset-set-index)\n                      obj)\n  (let ((key (index-object-key self obj)))\n    (update-map self (map)\n      (fset:with map\n                 key\n                 (fset:with\n                  (fset:lookup map key)\n                  obj)))))\n\n(defmethod index-get ((self abstract-fset-index) key)\n  (fset:lookup (%map self) key))\n\n(defmethod index-clear ((self abstract-fset-index))\n  (update-map self (map)\n    (declare (ignore map))\n    (fset:empty-map)))\n\n(defmethod index-get :around ((self fset-set-compat-index) key)\n  (reverse\n   (fset:convert 'list (call-next-method))))\n\n(defmethod index-clear ((self fset-set-index))\n  (update-map self (map)\n    (declare (ignore map))\n    (fset:empty-map (fset:empty-set))))\n\n(defmethod index-remove ((self fset-unique-index)\n                         obj)\n  (update-map self (map)\n    (let ((key (index-object-key self obj)))\n      (cond\n        ((eql obj (fset:lookup map key))\n         (fset:less map key))\n        (t\n         map)))))\n\n(defmethod index-remove ((self fset-many-to-many-index)\n                         obj)\n  (update-map self (map)\n    (reduce\n     (lambda (map key)\n       (fset:with map\n                  key\n                  (fset:less\n                   (fset:lookup map key)\n                   obj)))\n     (index-object-key self obj)\n     :initial-value map)))\n\n(defmethod index-remove ((self fset-set-index) obj)\n  (let ((key (index-object-key self obj)))\n    (update-map self (map)\n      (let ((new-val (fset:less\n                      (fset:lookup map key)\n                      obj)))\n        (cond\n          ((fset:empty? new-val)\n           (fset:less map key))\n          (t\n           (fset:with map key new-val)))))))\n\n(defmethod index-least ((self fset-set-index))\n  (let ((set (nth-value 1 (fset:least (%map self)))))\n    (when set\n     (fset:least set))))\n\n(defmethod index-values ((self abstract-fset-index))\n  (labels ((build-values (map result)\n             (cond\n               ((fset:empty? map)\n                result)\n               (t\n                (multiple-value-bind (key val) (fset:arb map)\n                  (build-values\n                   (fset:less map key)\n                   (fset:union\n                    (%index-values-for-key self val)\n                    result)))))))\n    (fset:convert 'list (build-values (%map self) (fset:empty-set)))))\n\n(defmethod %index-values-for-key ((self fset-unique-index) val)\n  (fset:with (fset:empty-set) val))\n\n(defmethod %index-values-for-key ((self fset-set-index) val)\n  val)\n\n(define-condition corrupted-index (error)\n  ((index :initarg :index)\n   (all-elts :initarg :all-elts)\n   (slot-name :initarg :slot-name)\n   (actual-map :initarg :actual-map)\n   (expected-map :initarg :expected-map))\n  (:report (lambda (e out)\n             (with-slots (index) e\n               (format out \"fset index does not match on ~a\"\n                       index)))))\n\n(defmethod validate-index-values ((index abstract-fset-index) all-elts slot-name)\n  (declare (optimize (debug 3) (speed 0)))\n  (let ((tmp (make-instance (type-of index) :slots (%slots index))))\n    (loop for elt in all-elts\n          do (index-add tmp elt))\n    (unless (fset:equal? (%map tmp)\n                         (%map index))\n      (restart-case\n          (error 'corrupted-index\n                 :index index\n                 :all-elts all-elts\n                 :slot-name slot-name\n                 :actual-map (%map index)\n                 :expected-map (%map tmp))\n        (util/store/store::fix-the-index ()\n          (setf (%map index) (%map tmp)))))))\n\n(defmethod validate-index-values ((self fset-unique-index) all-elts slot-name)\n  \"See T2282\"\n  nil)\n"
  },
  {
    "path": "src/util/store/fset.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/fset\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:decode\n                #:encode\n                #:%read-tag\n                #:%write-tag\n                #:encode-object\n                #:decode-object)\n  (:import-from #:util/store/store\n                #:object-neighbors))\n(in-package :util/store/fset)\n\n(defmethod decode-object ((code (eql #\\F)) stream)\n  (decode-fset-object (%read-tag stream)\n                      stream))\n\n(defmethod decode-fset-object ((code (eql #\\m)) stream)\n  (let ((length (decode stream)))\n    (fset:convert\n     'fset:map\n     (loop for i below length\n           collect\n           (cons\n            (decode stream)\n            (decode stream)))\n     :input-sorted? t)))\n\n(defmethod encode-object ((map fset:map) stream)\n  (%write-tag #\\F stream)\n  (%write-tag #\\m stream)\n  (encode (fset:size map) stream)\n  (fset:do-map (key val map)\n    (encode key stream)\n    (encode val stream)))\n\n(defmethod encode-object ((set fset:set) stream)\n  (%write-tag #\\F stream)\n  (%write-tag #\\s stream)\n  (encode (fset:size set) stream)\n  (fset:do-set (val set)\n    (encode val stream)))\n\n(defmethod decode-fset-object ((code (eql #\\s)) stream)\n  (let ((length (decode stream)))\n    (fset:convert\n     'fset:set\n     (loop for i below length\n           collect\n           (decode stream))\n     :input-sorted? t)))\n\n\n(defmethod object-neighbors ((map fset:map))\n  (let ((ret))\n    (fset:do-map (key val map)\n      (push key ret)\n      (push val ret))\n    ret))\n\n(defmethod object-neighbors ((set fset:set))\n  (fset:convert 'list set))\n"
  },
  {
    "path": "src/util/store/migrations.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/migrations\n  (:nicknames :util/migrations)\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:with-transaction)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:ensure-symbol-in-package\n   #:clone-slot))\n(in-package :util/migrations)\n\n(defvar *moved-syms* nil\n  \"A store of moved symbols, in case we need to recover something bad.\")\n\n(defmacro ensure-symbol-in-package (name &key old new export)\n  \"Ensure the given symbol named by NAME is in the NEW package and not in\n the OLD package. If it's only in OLD it moves it. If it's only in NEW,\n it does\n nothing. If it does not exist at all, it creates it in NEW.\n\n  It the same symbols exists in both packages, we only keep the NEW package reference.\n\n   If a different symbol by that name exists in both packages, then an\n error is signaled.\n\"\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (call-ensure-symbol-in-package ',name ',old ',new :export ,export)))\n\n(define-condition symbol-in-both-packages (error)\n  ())\n\n(defun find-symbol* (name package)\n  (when (find-package package)\n    (find-symbol name package)))\n\n(defun call-ensure-symbol-in-package (name old new &key export)\n  (let ((old-sym (find-symbol* (string name) old))\n        (new-sym (find-symbol* (string name) new)))\n    (pushnew (list old-sym new-sym)\n             *moved-syms*\n             :test #'equal)\n    (let ((sym (cond\n                 ((and\n                   (not old-sym)\n                   (not new-sym))\n                  (let ((sym (intern (string name) new)))\n                    sym))\n                 ((and\n                   old-sym\n                   (not new-sym))\n                  (unintern old-sym old)\n                  (import old-sym new)\n                  old-sym)\n                 ((and\n                   (not old-sym)\n                   new-sym)\n                  (assert (eql\n                           (symbol-package new-sym)\n                           (find-package new)))\n                  new-sym)\n                 ((eql old-sym new-sym)\n                  (unintern old-sym old)\n                  (intern (string name) new)\n                  new-sym)\n                 (t\n                  (error 'symbol-in-both-packages)))))\n      (when export\n        (export sym new))\n      sym)))\n\n(defmacro clone-slot (class &key from to)\n  ;; This code will do nothing if run from a bootup\n  ;; (i.e. before bknr.datastore has loaded). So it must be run as\n  ;; part of a live migration.\n  `(call-clone-slot ',class\n                    :from ',from\n                    :to ',to))\n\n(defun call-clone-slot (class &key from to)\n  (dolist (inst (bknr.datastore:class-instances class))\n    (cond\n      ((and\n        (not (slot-boundp inst to))\n        (slot-boundp inst from))\n       (with-transaction ()\n         (setf (slot-value inst to)\n               (slot-value inst from)))))))\n"
  },
  {
    "path": "src/util/store/object-id.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage #:util/store/object-id\n  (:nicknames #:util/object-id)\n  (:use #:cl\n        #:bknr.datastore)\n  (:import-from #:bknr.indices\n                #:should-index-objects-for-class-p\n                #:unique-index)\n  (:import-from #:util/store\n                #:location-for-oid\n                #:defindex)\n  (:import-from #:bknr.datastore\n                #:in-transaction-p)\n  (:import-from #:util/store/store\n                #:generate-sync-test-for-object)\n  (:export #:object-with-oid\n\t   #:object-with-unindexed-oid\n\t   #:find-by-oid\n\t   #:oid\n       #:oid-array\n       #:creation-time-from-oid\n       #:oid-struct-or-array))\n(in-package #:util/object-id)\n\n(defstruct oid\n  \"An object to efficiently represent a Mongo Object-ID. As of 11/27/22,\n this isn't being used but will be soon.\"\n  (arr))\n\n(defmethod fset:compare ((a oid) (b oid))\n  (fset:compare-slots\n   a b\n   #'oid-arr))\n\n(defmethod print-object ((self oid) stream)\n  (cond\n    ((or\n      *print-readably*\n      *print-escape*)\n     (format stream \"#<OID ~a>\" (fast-oid-str (oid-arr self))))\n    (t\n     (format stream (fast-oid-str (oid-arr self))))))\n\n(defmethod bknr.datastore::encode-object ((self oid) stream)\n  ;; M for MongoId, O is being used!\n  (bknr.datastore::%write-tag #\\M stream)\n  (write-sequence (oid-arr self) stream))\n\n(defmethod bknr.datastore::decode-object ((tag (eql #\\M)) stream)\n  (let ((arr (make-array 12 :element-type '(unsigned-byte 8))))\n    (read-sequence arr stream)\n    (make-oid :arr arr)))\n\n;; TODO: rename to new-oid\n(defun %make-oid ()\n  (when (in-transaction-p)\n    (warn \"Making OID in transaction\"))\n  (make-oid\n   :arr\n   (mongoid:oid)))\n\n(defindex +oid-index+ 'unique-index\n  :test 'equalp\n  :slot-name 'oid)\n\n;;;; reloading this object is bad. I thought I had fixed this, but\n;;;; it's still buggy inside of my patched version of bknr.datastore\n(defclass object-with-oid (store-object)\n  ((oid\n    :initarg :oid\n    :accessor oid-struct-or-array\n    :index +oid-index+\n    :index-reader %find-by-oid))\n  (:metaclass persistent-class)\n  (:default-initargs :oid (%make-oid)))\n\n(defun all-object-with-oids ()\n  \"object-with-oid is not class-indexed, so we look it up inefficiently\"\n  (loop for object in (bknr.datastore:all-store-objects)\n        if (typep object 'object-with-oid)\n          collect object))\n\n(defun migrate-oids ()\n  (loop for obj in (all-object-with-oids)\n        do (let ((oid (oid-struct-or-array obj)))\n             (unless (or (stringp oid) (oid-p oid))\n               (assert (vectorp oid))\n               (with-transaction ()\n                (setf (oid-struct-or-array obj) (make-oid :arr (oid-array obj))))))))\n\n(defmethod oid-array (self)\n  (let ((ret (oid-struct-or-array self)))\n    (cond\n      ((oid-p ret) (oid-arr ret))\n      ((stringp ret) (mongoid:oid ret))\n      (t ret))))\n\n(defclass object-with-unindexed-oid (store-object)\n  ((oid\n    :initform (%make-oid)\n    :accessor oid-bytes))\n  (:metaclass persistent-class))\n\n(defun find-by-oid (oid &optional type)\n  \"oid can be an array, a string, or an object of type OID\"\n  (let* ((arr (if (oid-p oid)\n                  (oid-arr oid)\n                  (mongoid:oid oid)))\n         (obj (or\n               (%find-by-oid\n                (make-oid :arr arr))\n               ;; For backward compatibility\n               (%find-by-oid arr))))\n    (when type\n      (unless (typep obj type)\n        (error \"Object ~s isn't of type ~s\" obj type)))\n    obj))\n\n(#+lispworks defconstant\n #-lispworks defparameter +e+ \"0123456789abcdef\")\n\n(defun fast-oid-str (oid)\n  (let ((hex-string (make-string 24)))\n    (loop for i fixnum from 0 below 12\n          do (let ((out (* 2 i)))\n               (multiple-value-bind (top left) (floor (aref oid i) 16)\n                 (setf (aref hex-string out)\n                       (aref +e+ top))\n                 (setf (aref hex-string (1+ out))\n                       (aref +e+ left)))))\n    hex-string))\n\n;; COPYPASTA from scheduled-jobs\n;;;;;;;;;;;;;;;;;;;;;;\n;; https://lisptips.com/post/11649360174/the-common-lisp-and-unix-epochs\n(defvar *unix-epoch-difference*\n  (encode-universal-time 0 0 0 1 1 1970 0))\n\n(defun universal-to-unix-time (universal-time)\n  (- universal-time *unix-epoch-difference*))\n\n(defun unix-to-universal-time (unix-time)\n  (+ unix-time *unix-epoch-difference*))\n\n(defun get-unix-time ()\n  (universal-to-unix-time (get-universal-time)))\n;;;;;;;;;;;;;;;;;;;;;;;\n\n(defmethod creation-time-from-oid ((object object-with-oid))\n  (let* ((oid-arr (oid-array object))\n         (unix (cl-mongo-id:get-timestamp oid-arr)))\n    (unix-to-universal-time unix)))\n\n(defmethod is-recent-p ((object object-with-oid) &key (days 14))\n  (> (creation-time-from-oid object)\n     (- (get-universal-time)\n        (* days 24 3600))))\n\n(defgeneric oid (obj &key stringp))\n\n(defmethod oid (obj &key (stringp t))\n  \"Returns the oid of the object. If :STRINGP is T, then we convert the\noid to a string. Otherwise we return the OID object as is.\"\n  (cond\n    (stringp\n     (fast-oid-str (oid-array obj)))\n    (t\n     (oid-struct-or-array obj))))\n\n(defmethod location-for-oid ((root pathname) (oid oid) &rest args &key &allow-other-keys)\n  (apply #'location-for-oid\n         root\n         (oid-arr oid)\n         args))\n\n(defmethod generate-sync-test-for-object ((obj object-with-oid) output)\n  (format output \"oid:~a \" (store-object-id obj) (oid obj)))\n\n(defmethod should-index-objects-for-class-p ((class (eql (find-class 'object-with-oid))))\n  nil)\n"
  },
  {
    "path": "src/util/store/permissive-persistent-class.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/permissive-persistent-class\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:last-change\n                #:id\n                #:persistent-effective-slot-definition\n                #:persistent-class)\n  (:import-from #:bknr.indices\n                #:destroyed-p\n                #:clear-slot-indices)\n  (:import-from #:util/store/store\n                #:slot-key-for-verification)\n  (:local-nicknames #+sbcl\n                    (#:clos #:closer-mop))\n  (:export\n   #:permissive-persistent-class\n   #:value-map))\n(in-package :util/store/permissive-persistent-class)\n\n(defclass base-permissive-persistent-object ()\n  ())\n\n(defclass permissive-persistent-class (persistent-class)\n  ()\n  (:documentation \"A persistent-class, where the slots aren't stored in the :instance\nstorage. Instead the slot values are stored in a hash-map, where the\nkeys are (symbol-name symbol). This is 'permissive' in the sense that\nyou can change the name of the slot, and everything will still just\nwork.\"))\n\n(defmethod closer-mop:compute-class-precedence-list ((class permissive-persistent-class))\n  (let ((classes (reverse (call-next-method))))\n    (pushnew (find-class 'base-permissive-persistent-object) classes)\n    (reverse classes)))\n\n(defclass value-map-slot (clos:standard-effective-slot-definition)\n  ((original-slots :initarg :original-slots\n                   :reader original-slots)))\n\n(defun ignorable-slot-p (slot)\n  \"Slots for which we should just use the underlying non-virtual allocation\"\n  (member (clos:slot-definition-name slot)\n          '(id last-change bknr.indices::object-destroyed-p-v2\n            bknr.datastore::%id-cache)))\n\n(defmethod copy-slot (class (slot persistent-effective-slot-definition) &rest args)\n  (apply #'make-instance 'persistent-effective-slot-definition\n         (append\n          args\n          (list\n           :class class\n           :name (clos:slot-definition-name slot)\n           :relaxed-object-reference (slot-value slot 'bknr.datastore::relaxed-object-reference)\n           :transient (slot-value slot 'bknr.datastore::transient)\n           :initform (clos:slot-definition-initform slot)\n           :initfunction (clos:slot-definition-initfunction slot)\n           :initargs (clos:slot-definition-initargs slot)\n           :type (clos:slot-definition-type slot)\n           :indices (slot-value slot 'bknr.indices::indices)))))\n\n(defun should-original-slot-be-virtual-p (slot)\n  (and\n   (typep slot 'persistent-effective-slot-definition)\n   (not (ignorable-slot-p slot))))\n\n(defun find-value-map-slot (obj)\n  (loop for slotd in (closer-mop:class-slots (class-of obj))\n        if (typep slotd 'value-map-slot)\n          return slotd))\n\n(defun maybe-initialize-value-map (obj)\n  (unless (slot-boundp obj 'value-map)\n    (setf (slot-value obj 'value-map)\n          (initialize-hash-table (original-slots (find-value-map-slot obj))))))\n\n(defmethod clos:compute-slots ((class permissive-persistent-class))\n  (let ((original-slots (call-next-method)))\n    (append\n     (loop for slot in original-slots\n           if (should-original-slot-be-virtual-p slot)\n             collect\n             (copy-slot class slot\n                        :allocation :virtual\n                        :initform nil\n                        :initfunction nil)\n           else\n             collect slot)\n     (list\n      (make-instance 'value-map-slot\n                     :name 'value-map\n                     :original-slots original-slots)))))\n\n(defun initialize-hash-table (slots)\n  (let ((res (make-hash-table :test #'equal)))\n    (loop for slot in slots\n          if (should-original-slot-be-virtual-p slot)\n            do\n               (let ((initfunction\n                       (or\n                        (clos:slot-definition-initfunction slot)\n                        (when (clos:slot-definition-initform slot)\n                          (lambda ()\n                            (eval (clos:slot-definition-initform slot)))))))\n                 (log:info \"Calling initfunction ~a on ~a\" slot initfunction)\n                 (when initfunction\n                   (setf (gethash (slot-key slot) res)\n                         (funcall initfunction)))))\n    res))\n\n(defmethod clear-slot-indices ((slot value-map-slot))\n  nil)\n\n(defun slot-key-from-name (symbol)\n  (let ((key (symbol-name symbol)))\n    (cond\n      ((str:starts-with-p \"%\" key)\n       (str:substring 1 nil key))\n      (t\n       key))))\n\n(defun slot-key (slot)\n  (slot-key-from-name (clos:slot-definition-name slot)))\n\n(defmethod (setf clos:slot-value-using-class) (new-value\n                                               (class permissive-persistent-class)\n                                               obj\n                                               (slot persistent-effective-slot-definition))\n  (cond\n    ((ignorable-slot-p slot)\n     (call-next-method))\n    (t\n     (setf (gethash (slot-key slot) (value-map obj))\n           new-value))))\n\n(defmethod clos:slot-value-using-class ((class permissive-persistent-class)\n                                        obj\n                                        (slot persistent-effective-slot-definition))\n  (cond\n    ((ignorable-slot-p slot)\n     (call-next-method))\n    (t\n     (multiple-value-bind (res exists-p)\n         (gethash (slot-key slot) (value-map obj))\n       (cond\n         (exists-p\n          res)\n         (t\n          (error 'unbound-slot\n                 :name (clos:slot-definition-name slot)\n                 :instance obj)))))))\n\n(defmethod value-map (self)\n  (maybe-initialize-value-map self)\n  (slot-value self 'value-map))\n\n(defmethod clos:slot-boundp-using-class ((class permissive-persistent-class)\n                                         obj\n                                         (slot persistent-effective-slot-definition))\n  (cond\n    ((ignorable-slot-p slot)\n     (call-next-method))\n    (t\n     (nth-value 1 (gethash (slot-key slot) (value-map obj))))))\n\n(defmethod clos:slot-makunbound-using-class ((class permissive-persistent-class)\n                                             obj\n                                             #+lispworks\n                                             (slot-name symbol)\n                                             #-lispworks\n                                             (slot persistent-effective-slot-definition))\n  (let (#+lispworks (slot (clos:find-slot-definition slot-name class)))\n   (cond\n     ((ignorable-slot-p slot)\n      (call-next-method))\n     (t\n      (remhash (slot-key slot) (value-map obj))))))\n\n(defmethod slot-key-for-verification ((metaclass (eql 'permissive-persistent-class))\n                                      slot)\n  (slot-key-from-name slot))\n"
  },
  {
    "path": "src/util/store/raft-state-http.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/raft-state-http\n  (:use #:cl)\n  #+lispworks\n  (:import-from #:bknr.cluster/server\n                #:leaderp))\n(in-package :util/store/raft-state-http)\n\n(defun raft-state-request-p (request)\n  ;; We want to be handle things like\n  ;; `/raft-state/production` so that we can run multiple\n  ;; servers on the same machine, and have nginx proxy\n  ;; correctly.\n  (str:starts-with-p \"/raft-state\"\n                     (hunchentoot:script-name request)))\n\n(defun %response ()\n  #+lispworks\n  (cond\n    ((leaderp bknr.datastore:*store*)\n     \"leader\")\n    (t\n     (setf (hunchentoot:return-code*) 400)\n     \"other\")))\n\n(hunchentoot:define-easy-handler (raft-state\n                                  :uri #'raft-state-request-p) ()\n  (%response))\n"
  },
  {
    "path": "src/util/store/simple-object-snapshot.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/simple-object-snapshot\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:encode\n                #:class-layout-slots)\n  (:export\n   #:simple-object-snapshot))\n(in-package :util/store/simple-object-snapshot)\n\n(defclass simple-object-snapshot ()\n  ((object :initarg :object\n           :reader %object)\n   (except-slots :initarg :except-slots\n                 :initform nil\n                 :reader except-slots\n                 :documentation \"The except slots must be bound. If it's unbound it will crash.\")\n   (slot-values :accessor %slot-values))\n  (:documentation \"We assume that the object is immutable, or if it did mutate, that the mutations are reasonable. If a reference is deleted, then the snapshot will crash\"))\n\n(defmethod snapshot-slot-value (obj slot-name)\n  \"Override this to 'snapshot' a specific slot. This is assuming a\nsimple copy is not good enough.\"\n  (slot-value obj slot-name))\n\n(defmethod initialize-instance :after ((self simple-object-snapshot) &key except-slots object)\n  (declare (ignore object))\n  (setf (%slot-values self)\n        (loop for slot in except-slots\n              collect (cons slot (snapshot-slot-value (%object self) slot)))))\n\n;; Some of the tests for this are in test-image :/\n(defmethod bknr.datastore:encode-slots-for-object (class-layout (self simple-object-snapshot) stream)\n  (let ((object (%object self)))\n    (loop for slot in (class-layout-slots class-layout)\n          do (encode\n              (cond\n                ((member slot (except-slots self))\n                 (alexandria:assoc-value (%slot-values self) slot))\n                ((slot-boundp object slot)\n                 (slot-value object slot))\n                (t\n                 'bknr.datastore::unbound))\n              stream))))\n"
  },
  {
    "path": "src/util/store/single.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/single\n  (:nicknames #:util/single)\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:%encode-symbol)\n  (:import-from #:bknr.datastore\n                #:%decode-symbol)\n  (:import-from #:bknr.datastore\n                #:find-class-with-interactive-renaming)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:bknr.datastore\n                #:find-slot-name-with-interactive-rename)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/single)\n\n(defvar *magick* (list\n                  (char-code #\\S)\n                  (char-code #\\G)))\n(defparameter *version* 1)\n\n(defmethod serialize (obj stream)\n  (write-sequence *magick* stream)\n  (write-byte *version* stream)\n  (encode (type-of obj)\n                  stream)\n  (let ((slots (closer-mop:class-slots (class-of obj))))\n    (dolist (slot slots)\n      (let ((slot (closer-mop:slot-definition-name slot)))\n       (when (slot-boundp obj slot)\n         (encode slot stream)\n         (encode (slot-value obj slot) stream)))))\n  (encode nil stream))\n\n(defmethod deserialize (stream)\n  (let ((magick (list nil nil)))\n    (read-sequence magick stream)\n    (assert (equal magick *magick*))\n    ;; version\n    (let ((ver (read-byte stream)))\n      (assert (eql *version* ver)))\n    (let* ((class-name (decode stream)))\n      (let ((class (find-class-with-interactive-renaming class-name)))\n        (let ((instance (allocate-instance class)))\n          (loop for slot = (decode stream)\n                while slot\n                do\n                   (let ((slot (find-slot-name-with-interactive-rename class slot))\n                         (value (decode stream)))\n                     (setf (slot-value instance slot) value)))\n          instance)))))\n"
  },
  {
    "path": "src/util/store/slot-subsystem.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/slot-subsystem\n  (:use #:cl)\n  (:import-from #:util/store/store\n                #:defsubsystem)\n  (:import-from #:bknr.datastore\n                #:decode\n                #:store-subsystem-snapshot-pathname\n                #:encode\n                #:snapshot-subsystem-async\n                #:deftransaction\n                #:restore-subsystem))\n(in-package :util/store/slot-subsystem)\n\n(defvar *map* (fset:empty-map)\n  \"A map from (cons obj slot) to object\")\n\n(defvar *lock* (bt:make-lock))\n\n(defclass slot-subsystem ()\n  ()\n  (:documentation \"A subsystem that stores slots values using a functional maps. This is\nO(log (nm)) access time, but it allows for very fast snapshots.\"))\n\n(defsubsystem slot-subsystem\n  ;; Load after store-object-subsystem, because we'll reference store\n  ;; objects.\n  :priority 12)\n\n(defmethod externalized-slot-value (obj slot-name &optional (default-value nil))\n  (or\n   (fset:lookup *map*\n                (cons obj slot-name))\n   default-value))\n\n(deftransaction tx-set-slot-value (value obj slot-name)\n  (bt:with-lock-held (*lock*)\n   (setf\n    *map*\n    (fset:with\n     *map*\n     (cons obj slot-name)\n     value))))\n\n(defmethod (setf externalized-slot-value) (value obj slot-name &optional default-value)\n  (declare (ignore default-value))\n  (tx-set-slot-value value obj slot-name))\n\n(defmethod restore-subsystem ((store bknr.datastore:store)\n                              (self slot-subsystem)\n                              &key until)\n  (declare (ignore until))\n  (log:info \"Restoring SLOT-SUBSYSTEM\")\n  (setf *map* (fset:empty-map))\n  (let ((pathname (store-subsystem-snapshot-pathname store self)))\n    (cond\n      ((probe-file pathname)\n       (with-open-file (stream pathname :direction :input :element-type '(unsigned-byte 8))\n         ;; read the header \n         (decode stream)\n         (setf *map* (decode stream))))\n      (t\n       (setf *map* (fset:empty-map))))))\n\n(defmethod snapshot-subsystem-async ((store bknr.datastore:store)\n                                     (self slot-subsystem))\n  (let ((copy *map*)\n        (pathname (store-subsystem-snapshot-pathname store self)))\n    (lambda ()\n      (log:info \"Snapshotting slot-subsystem (background)\")\n      (with-open-file (stream pathname :direction :output :element-type '(unsigned-byte 8)\n                                       :if-exists :supersede)\n        (encode `(:version 1) stream)\n        (encode copy stream)))))\n\n\n\n"
  },
  {
    "path": "src/util/store/store-migrations.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/store-migrations\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:alexandria\n                #:when-let\n                #:assoc-value)\n  (:import-from #:bknr.datastore\n                #:deftransaction)\n  (:import-from #:util/store/store-version\n                #:*snapshot-store-version*\n                #:*store-version*)\n  (:export\n   #:def-store-migration\n   #:run-migrations\n   #:run-migrations-async\n   #:migration-running-p\n   #:migration-failed-p\n   #:migration-error))\n(in-package :util/store/store-migrations)\n\n(defclass migration ()\n  ((version :initarg :version)\n   (name :initarg :name\n         :reader name)\n   (body :initarg :body\n         :reader body)))\n\n(defvar *migrations* nil)\n\n(def-easy-macro def-store-migration (name &key version &fn fn)\n  (setf\n   (assoc-value *migrations* version)\n   (make-instance 'migration\n                  :version version\n                  :name name\n                  :body fn)))\n\n(deftransaction set-snapshot-version (version)\n  (setf *snapshot-store-version* version))\n\n(defun run-migration-for-version (version)\n  (format t \"Current thread is: ~a~%\" (bt:current-thread))\n  (let ((migration (assoc-value *migrations* version)))\n    (cond\n      (migration\n       (format t \"Running migration: ~a~%\" (name migration))\n       (funcall (body migration)))\n      (t\n       (format t \"No migrations to run for version: ~a~%\" version)))))\n\n(defun bump-version ()\n  (let ((version *snapshot-store-version*))\n    (assert (< version *store-version*))\n    (let ((version (1+ version)))\n      (run-migration-for-version version)\n      (set-snapshot-version version))))\n\n(defun needs-work-p ()\n  (< *snapshot-store-version* *store-version*))\n\n(defun run-migrations (&key (snapshot t))\n  (when (needs-work-p)\n    (format t \"After running migrations (current version: ~a)~%\" *snapshot-store-version*)\n    (when snapshot\n      (util/store:safe-snapshot\n       (format nil \"Before running migrations (current version: ~a)~%\" *snapshot-store-version*)))\n    (loop while (needs-work-p)\n          do\n             (progn\n               (format t \"Current store version is ~a~%\" *snapshot-store-version*)\n               (bump-version)))\n    (format t \"After running migrations (current version: ~a)~%\" *snapshot-store-version*)\n    (when snapshot\n     (util/store:safe-snapshot\n      (format nil \"After running migrations (current version: ~a)~%\" *snapshot-store-version*)))))\n\n(def-store-migration (\"Dummy migration for version test\" :version 2)\n  (log:info \"Nothing to do in this migration\"))\n\n;;; Async migration support\n(defvar *migration-thread* nil\n  \"The current migration thread, or NIL if no migration is running\")\n\n(defvar *migration-error* nil\n  \"If the migration failed, this will contain the error condition\")\n\n(defvar *migration-lock* (bt:make-lock \"migration-lock\")\n  \"Lock to protect migration state\")\n\n(defun migration-running-p ()\n  \"Returns T if a migration is currently running\"\n  (bt:with-lock-held (*migration-lock*)\n    (and *migration-thread*\n         (bt:thread-alive-p *migration-thread*))))\n\n(defun migration-failed-p ()\n  \"Returns T if the last migration failed\"\n  (bt:with-lock-held (*migration-lock*)\n    (not (null *migration-error*))))\n\n(defun migration-error ()\n  \"Returns the error from the last failed migration, or NIL\"\n  (bt:with-lock-held (*migration-lock*)\n    *migration-error*))\n\n(defun run-migrations-async (&key (snapshot t))\n  \"Run migrations in a background thread. Returns immediately.\n   Use migration-running-p to check if still running,\n   migration-failed-p to check if it failed, and\n   migration-error to get the error if it failed.\"\n  (bt:with-lock-held (*migration-lock*)\n    (when (and *migration-thread* (bt:thread-alive-p *migration-thread*))\n      (error \"A migration is already running\"))\n    (setf *migration-error* nil)\n    (setf *migration-thread*\n          (bt:make-thread\n           (lambda ()\n             (handler-case\n                 (progn\n                   (format t \"Starting migrations in background thread~%\")\n                   (run-migrations :snapshot snapshot)\n                   (format t \"Migrations completed successfully~%\"))\n               (error (e)\n                 (bt:with-lock-held (*migration-lock*)\n                   (setf *migration-error* e))\n                 (format t \"Migration failed with error: ~a~%\" e)\n                 (trivial-backtrace:print-backtrace e))))\n           :name \"migration-thread\")))\n  (values))\n"
  },
  {
    "path": "src/util/store/store-version.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/store-version\n  (:nicknames :util/store-version)\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:restore-subsystem\n                #:snapshot-subsystem\n                #:store-subsystem-snapshot-pathname\n                #:snapshot-store)\n  (:import-from #:bknr.datastore\n                #:store-current-directory)\n  (:import-from #:bknr.datastore\n                #:ensure-store-current-directory)\n  (:import-from #:bknr.datastore\n                #:restore-subsystem)\n  (:import-from #:bknr.datastore\n                #:initialize-subsystem)\n  (:import-from #:bknr.datastore\n                #:store-directory)\n  (:import-from #:bknr.datastore\n                #:snapshot-subsystem)\n  (:import-from #:util/store\n                #:defsubsystem))\n(in-package :util/store-version)\n\n(defparameter *store-version* 42\n  \"The current version of the store being used.\")\n\n(defvar *snapshot-store-version* *store-version*\n  \"The version of the snapshot that was read in. This is the one we\nmigrate.\")\n\n(defvar *min-store-version* 1\n  \"The minimum supported version of the store, for loading purposes.\")\n\n(defclass version-subsystem ()\n  ()\n  (:documentation \"A subsystem that also saves the store-version, and verifies the store\nversion on loads\"))\n\n(defmethod initialize-subsystem :after ((self version-subsystem) store store-existed-p)\n  (log:info \"Store directory: ~a\" (bknr.datastore::store-directory store))\n  (setf *snapshot-store-version* *store-version*))\n\n\n(defmethod restore-subsystem (store (self version-subsystem) &key until)\n  (declare (ignore until))\n  (setf *snapshot-store-version*\n        (or\n         (read-current-version store self)\n         *store-version*)))\n\n(defmethod snapshot-subsystem (store (self version-subsystem))\n  (write-current-version store self))\n\n(defmethod read-current-version (store self)\n  (let ((snapshot-pathname (store-subsystem-snapshot-pathname store self)))\n    (loop for ver-file in (list snapshot-pathname\n                                ;; old version:\n                                (make-pathname :name \"store-version\" :defaults snapshot-pathname))\n          if (path:-e ver-file)\n            return (parse-integer (str:trim (uiop:read-file-string ver-file))))))\n\n(defmethod write-current-version (store self)\n  (with-open-file (store-version (ensure-directories-exist\n                                  (store-subsystem-snapshot-pathname store self))\n                                 :direction :output\n                                 :if-exists :supersede)\n    (format store-version \"~a\" *snapshot-store-version*)))\n\n(defsubsystem version-subsystem :priority 5)\n"
  },
  {
    "path": "src/util/store/store.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/store\n  (:nicknames :util/store)\n  (:use #:cl\n        #:bknr.datastore\n        #:file-lock)\n  (:import-from #:bknr.datastore\n                #:restore-subsystem\n                #:close-subsystem\n                #:deftransaction\n                #:close-store)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:bknr.datastore\n                #:without-sync)\n  (:import-from #:bknr.datastore\n                #:store-transaction-log-stream)\n  (:import-from #:bknr.datastore\n                #:store-transaction-log-pathname)\n  (:import-from #:bknr.datastore\n                #:close-transaction-log-stream)\n  (:import-from #:bknr.datastore\n                #:close-transaction-log-stream)\n  (:import-from #:file-lock\n                #:make-file-lock)\n  (:import-from #:bknr.datastore\n                #:store-directory)\n  (:import-from #:bknr.datastore\n                #:restore-transaction-log)\n  (:import-from #:alexandria\n                #:when-let\n                #:assoc-value)\n  (:import-from #:bknr.indices\n                #:*indexed-class-override*)\n  (:import-from #:bknr.indices\n                #:slot-index)\n  (:import-from #:bknr.indices\n                #:clear-slot-indices)\n  (:import-from #:util/misc/lists\n                #:head)\n  #+bknr.cluster\n  (:import-from #:bknr.cluster/store\n                #:on-snapshot-save-impl\n                #:cluster-store-mixin\n                #:backward-compatibility-mixin)\n  #+bknr.cluster\n  (:import-from #:bknr.cluster/server\n                #:on-snapshot-load\n                #:log-transaction-error\n                #:data-path\n                #:leaderp\n                #:with-leadership-priority)\n  (:import-from #:util/cron\n                #:cron-enabled-on-store-p)\n  (:import-from #:util/threading\n                #:wait-for-zero-threads\n                #:make-thread\n                #:ignore-and-log-errors\n                #:with-extras\n                #:log-sentry)\n  (:import-from #:util/misc\n                #:safe-ensure-directories-exist)\n  (:import-from #:util/events\n                #:with-tracing)\n  #+bknr.cluster\n  (:import-from #:util/store/clone-logs-store\n                #:clone-logs-store)\n  (:import-from #:util/threading\n                #:*thread-count*)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:prepare-store-for-test\n   #:prepare-store\n   #:verify-store\n   #:add-datastore-hook\n   #:object-store\n   #:safe-mp-store\n   #:with-test-store\n   #:*object-store*\n   #:store-subsystems\n   #:validate-indices\n   #:register-ref\n   #:find-any-refs\n   #:safe-snapshot\n   #:defindex\n   #:validate-class\n   #:with-class-validation\n   #:def-store-local\n   #:location-for-oid\n   #:add-datastore-cleanup-hook\n   #:*snapshot-hooks*\n   #:raft-store\n   #:fix-the-index\n   #:make-default-store\n   #:raft-store-final\n   #:nsort-store-objects))\n(in-package :util/store)\n\n(defvar *object-store*)\n\n(defvar *datastore-hooks* nil)\n\n(defvar *datastore-cleanup-hooks* nil)\n\n(defvar *calledp* nil)\n\n(defvar *snapshot-hooks* nil\n  \"A snapshot hook is called with two arguments: the store, and the path\nto the directory that was just snapshotted.\")\n\n(defparameter *enable-txn-log-lock* t)\n\n(defun add-datastore-hook (fn &key immediate)\n  \"Add a hook, if :immediate is set, and the store is already active the\n callback is called immediately.\"\n  (cond\n   ((not *calledp*)\n    (pushnew fn *datastore-hooks*))\n   (immediate\n    (funcall fn))))\n\n(defun add-datastore-cleanup-hook (fn)\n  (pushnew fn *datastore-cleanup-hooks*))\n\n(defun dispatch-datastore-hooks ()\n  (mapc 'funcall *datastore-hooks*)\n  (setf *calledp* t))\n\n(defun dispatch-datastore-cleanup-hooks ()\n  (mapc 'funcall *datastore-cleanup-hooks*))\n\n(defvar *ensure-directories-cache* (fset:empty-set))\n\n(defun fast-ensure-directories-exist (path)\n  (let ((dir (pathname-directory path)))\n    (cond\n      ((fset:@ *ensure-directories-cache* dir)\n       path)\n      (t\n       (prog1\n           (safe-ensure-directories-exist path)\n         (setf *ensure-directories-cache* (fset:with *ensure-directories-cache*\n                                                     dir)))))))\n\n(defun object-store ()\n  (let* ((dir *object-store*)\n         (dir (if (or\n                   (str:ends-with-p \"/\" dir)\n                   (and (uiop:os-windows-p) (str:ends-with-p \"\\\\\" dir)))\n                  dir (format nil \"~a/\" dir))))\n   (let ((path (pathname dir)))\n     (fast-ensure-directories-exist path)\n     path)))\n\n(defclass checksumed-mp-store ()\n  ())\n\n(defclass common-mp-store (bknr.datastore:mp-store)\n  ())\n\n(defclass safe-mp-store (common-mp-store\n                         checksumed-mp-store)\n  ((transaction-log-lock :initform nil)))\n\n#+bknr.cluster\n(defclass base-raft-store (util/store/elb-store:elb-store-mixin\n                           clone-logs-store)\n  ())\n\n#+bknr.cluster\n(defclass raft-store (backward-compatibility-mixin\n                      with-leadership-priority\n                      cluster-store-mixin\n                      ;; Hopefully braft takes care of the locking here\n                      bknr.datastore:store\n                      base-raft-store)\n  ())\n\n(defclass raft-store-final (with-leadership-priority\n                            cluster-store-mixin\n                            ;; Hopefully braft takes care of the locking here\n                             bknr.datastore:store\n                             base-raft-store)\n  ()\n  (:documentation \"The final raft store we'll be using\")\n  (:default-initargs :election-timeout-ms 2000))\n\n(defclass ec2-store (raft-store-final)\n  ()\n  (:documentation \"A store that's just configured with hostnames and group names, and\nuses sane defaults.\"))\n\n(defun ec2-get-local-ipv4 ()\n  (cond\n    ((equal 0(nth-value 2 (uiop:run-program \"which ec2metadata\" :ignore-error-status t)))\n     ;; We're actually running in EC2\n     (str:trim\n      (uiop:run-program \"ec2metadata --local-ipv4\"\n                        :output 'string)))\n    (t\n     ;; At the time of writing, this probably means we're running in Vagrant.\n     (log:warn \"ec2metadata not available, using `ip addr` instead\")\n     (multiple-value-bind (result parts)\n         (cl-ppcre:scan-to-strings \"inet ([0-9.]*)/24\"\n                                   (uiop:run-program \"ip -4 addr show scope global\"\n                                                     :output 'string))\n       (unless result\n         (error \"Could not figure out IP address\"))\n       (log:info \"Using IP address ~a\" (elt parts 0))\n       (elt parts 0)))))\n\n(defmethod bknr.datastore::snapshot-store :around ((store base-raft-store))\n  (let ((start-time (local-time:now)))\n    (with-extras ((\"snapshot-start-time\" start-time))\n      (call-next-method))))\n\n(defmethod initialize-instance :around ((self ec2-store) &rest args &key ips group port data-path &allow-other-keys)\n  (apply\n   #'call-next-method\n   self\n   :data-path (or data-path (format nil \"/home/~a/raft-data/\" group))\n   :ip (ec2-get-local-ipv4)\n   :priority\n   (cond\n     ((equal (first ips) (ec2-get-local-ipv4))\n      1)\n     (t\n      0))\n   :config (str:join \",\"\n                     (loop for ip in ips\n                           collect (format nil \"~a:~a:0\" ip port)))\n   :group group\n   :port port\n   args))\n\n\n(defmethod on-snapshot-load :after ((store base-raft-store) snapshot-reader))\n\n(defmethod on-snapshot-load :around ((store base-raft-store) snapshot-reader)\n  (with-tracing (:snapshot-load)\n    (call-next-method)\n    #+lispworks\n    (progn\n      (log:info \"Running GC :coalesce\")\n      (hcl:gc-generation 4 :coalesce t))))\n\n#+bknr.cluster\n(defmethod log-transaction-error ((sm base-raft-store) trans e)\n  (log:info \"logging issue to sentry\")\n  (with-extras ((\"transaction\" trans))\n    (log-sentry e)))\n\n(defclass store-for-test (common-mp-store)\n  ())\n\n(defmethod initialize-instance :before ((store safe-mp-store) &key directory &allow-other-keys))\n\n(defun ensure-transaction-log-lock (store)\n  (with-slots (transaction-log-lock) store\n    (unless transaction-log-lock\n      (when *enable-txn-log-lock*\n        (log:info \"Opening transaction log lock\")\n        (setf transaction-log-lock\n              (make-file-lock\n               :file\n               (ensure-directories-exist\n                (make-pathname\n                 :type \"lock\"\n                 :defaults\n                 (store-transaction-log-pathname store)))))))))\n\n(defun clear-transaction-log-lock (store)\n  (with-slots (transaction-log-lock) store\n    (when transaction-log-lock\n      (log:info \"Closing transaction log lock\")\n      (restart-case\n          (release-file-lock transaction-log-lock)\n        (ignore-release-file-lock ()\n          (values)))\n      (setf transaction-log-lock nil))))\n\n(defmethod restore-transaction-log :before ((store safe-mp-store)\n                                            transaction-log\n                                            &key until)\n  (declare (ignore until))\n  (ensure-transaction-log-lock store))\n\n\n(defmethod bknr.datastore::close-store-object :before ((store safe-mp-store))\n  (dispatch-datastore-cleanup-hooks))\n\n(defmethod bknr.datastore::close-store-object :after ((store safe-mp-store))\n  (clear-transaction-log-lock store))\n\n(defvar *subsystems* `((bknr.datastore:store-object-subsystem 10)\n                       (bknr.datastore:blob-subsystem 11)))\n\n(defmacro defsubsystem (name &key (priority 15))\n  \"Define a subsystem with the specified priority for store operations.\n\nPriority determines the order of subsystem operations:\n\nIn bknr.cluster:\n- Lower priority numbers are processed first during initialization, restoration, and snapshotting\n- Higher priority numbers are processed first during closing (reverse order)\n- Default priority is 15\n\nExample priority ordering:\n  :priority -1  -> processed first during init/restore, last during close\n  :priority 15  -> processed second during init/restore, second-to-last during close  \n  :priority 20  -> processed last during init/restore, first during close\n\nIn original bknr.datastore:\n- Lower priorities numbers are processed first, even during closing.\n\"\n  `(progn\n     (setf (assoc-value *subsystems* ',name)\n           (list ,priority))))\n\n(defun store-subsystems ()\n  (mapcar #'make-instance\n          (mapcar #'first\n                  (sort (copy-list *subsystems*) #'< :key #'second))))\n\n\n(defun prepare-store-for-test (&key (dir \"~/test-store/\")\n                                 (store-class 'store-for-test))\n  (assert store-class)\n  (make-instance store-class\n                 :directory dir\n                 :subsystems (store-subsystems)))\n\n(def-easy-macro with-test-store (&key (globally nil)\n                                      (store-class 'store-for-test)\n                                      dir\n                                      &fn body)\n  (%%call-with-test-store body :globally globally :store-class store-class\n                               :dir dir))\n\n(def-easy-macro with-snapshot-lock (store  &fn fn)\n  (log:info \"Waiting for store snapshot lock\")\n  (let ((file-lock (make-file-lock\n                    :file (path:catfile\n                           (store-directory store)\n                           \"snapshot.lock\")\n                    ;; Fail immediately if we can't get the lock\n                    :timeout -10)))\n    (unwind-protect\n         (funcall fn)\n      (release-file-lock file-lock))))\n\n(def-easy-macro with-wait-for-threads (&fn fn)\n  (unwind-protect\n       (fn)\n    (wait-for-zero-threads)))\n\n(defun %%call-with-test-store (fn &key (globally nil)\n                                store-class\n                                dir)\n  (when (boundp 'bknr.datastore:*store*)\n    (error \"Don't run this test in a live program with an existing store\"))\n  (unless (eql 0 *thread-count*)\n    ;; We need this so we know how to clean up\n    (error \"There's some stale background threads from a previous test, crashing early\"))\n  (flet ((inner-work ()\n           (labels ((all-objects ()\n                      (bknr.datastore:all-store-objects))\n                    (maybe-ensure-empty ()\n                      (unless dir\n                        (assert (null (all-objects)))))\n                    (call-with-dir (dir)\n                      (prepare-store-for-test :dir dir :store-class store-class)\n                      (setf util/store:*object-store* (namestring dir))\n                      (assert bknr.datastore:*store*)\n                      (maybe-ensure-empty)\n                      (let ((classes-to-clean nil))\n                        (unwind-protect\n                             (progn\n                               (without-sync ()\n                                 (with-wait-for-threads ()\n                                   (funcall fn)))\n                               #+nil\n                               (let ((objs (all-objects)))\n                                 (when objs\n                                   (error \"At the end of the test some objects were not deleted: ~s\" objs))))\n                          (clear-indices-for-tests)))))\n             (cond\n               (dir\n                (call-with-dir dir))\n               (t\n                (tmpdir:with-tmpdir (dir)\n                  (call-with-dir dir)))))))\n    (cond\n      (globally\n       (unwind-protect\n            (progn\n              (inner-work))\n         (makunbound 'util/store::*object-store*)\n         (makunbound '*store*)))\n      (t\n       (let ((*store* nil)\n             (util/store::*object-store* nil))\n         (inner-work))))))\n\n(defun clear-indices-for-tests ()\n  (let ((classes-to-clean\n          (fset:convert\n           'fset:set\n           (mapcar #'class-of (bknr.datastore:all-store-objects)))))\n    (close-store)\n\n    (fset:do-set (class classes-to-clean)\n      (mapc\n       #'clear-slot-indices\n       (closer-mop:class-slots class)))))\n\n(defun read-raft-config ()\n  (let ((raft-config (path:catfile (object-store) \"raft-config.lisp\")))\n    (when (path:-e raft-config)\n      (read-from-string\n       (uiop:read-file-string raft-config)))))\n\n(defun make-default-store (&rest args)\n  (apply #'make-instance\n         (append\n          args\n          (list\n           :directory (object-store)\n           :subsystems (store-subsystems)))))\n\n(defun prepare-store ()\n  (let ((raft-config (read-raft-config)))\n    (cond\n      (raft-config\n       (eval raft-config))\n      (t\n       (make-default-store 'safe-mp-store))))\n  (dispatch-datastore-hooks))\n\n(defun verify-store (&key (callback (lambda ())))\n  (let ((store-dir (object-store)))\n    (tmpdir:with-tmpdir (dir)\n      (let ((out-current (path:catdir dir \"current/\")))\n        (log:info \"Copyin file ~a to ~a\" store-dir dir)\n        (copy-directory:copy (path:catdir store-dir \"current/\")\n                             out-current)\n        (assert (path:-d out-current))\n        (let ((raft-config (read-raft-config)))\n          (cond\n            (raft-config\n             (error \"verify unimplemented yet.\"))\n            (t\n             (make-instance 'safe-mp-store\n                            :directory dir\n                            :subsystems (store-subsystems)))))\n        (log:info \"Got ~d objects\" (length (bknr.datastore:all-store-objects)))\n        (funcall callback)))))\n\n(defun parse-timetag (timetag)\n  \"timetag is what bknr.datastore calls it. See utils.lisp in\n  bknr. This function converts the timetag into a valid timestamp.\"\n  (multiple-value-bind (full parts)\n      (cl-ppcre:scan-to-strings\n       \"(\\\\d\\\\d\\\\d\\\\d)(\\\\d\\\\d)(\\\\d\\\\d)T(\\\\d\\\\d)(\\\\d\\\\d)(\\\\d\\\\d)\"\n       timetag)\n    (when full\n     (apply #'local-time:encode-timestamp\n            0 ;; nsec\n            (reverse\n             (loop for x across parts\n                   collect (parse-integer x)))))))\n\n(defun all-snapshots-sorted (dir)\n  (let ((list (directory dir)))\n    ;; remove any directories that don't look like timestamps\n    (let ((list\n            (loop for x in list\n                  for dir-name = (car (last (pathname-directory x)))\n                  for ts = (parse-timetag dir-name)\n                  if ts\n                    collect (cons ts x))))\n      (sort\n       list\n       #'local-time:timestamp>\n       :key 'car))))\n\n\n(defun delete-snapshot-dir (dir)\n  (assert (path:-d dir))\n  (log:info \"Deleting snapshot dir: ~a\" dir)\n  (fad:delete-directory-and-files dir))\n\n(defun delete-old-snapshots ()\n  ;; always keep at least 7 snapshots even if they are old\n  (ignore-and-log-errors ()\n   (loop for (ts . dir) in (nthcdr 7 (all-snapshots-sorted (object-store)))\n         if (local-time:timestamp< ts (local-time:timestamp- (local-time:now)\n                                                             14 :day))\n           do\n              (delete-snapshot-dir dir))))\n\n#+bknr.cluster\n(defmethod cron-enabled-on-store-p ((store cluster-store-mixin))\n  (leaderp store))\n\n(defun cron-snapshot ()\n  (make-thread\n   (lambda ()\n    (when (and\n           (boundp 'bknr.datastore:*store*))\n      #+bknr.cluster\n      (unless (leaderp bknr.datastore:*store*)\n        ;; TODO: Coordinate snapshots via RPC from the leader, so that\n        ;; we never have to worry about two snapshots happening at the\n        ;; same time.\n        (sleep (+ 300 (random (* 2 3600)))))\n      (log:info \"Snapshotting bknr.datastore\")\n      (safe-snapshot \"cron-job\" nil)))\n   :name \"snapshot-thread\"))\n\n(cl-cron:make-cron-job 'cron-snapshot\n                        :minute 0\n                        :hour 6\n                        :hash-key 'cron-snapshot)\n\n(defmethod all-subsystem-objects (subsystem)\n  nil)\n\n(defmethod all-subsystem-objects ((self store-object-subsystem))\n  (bknr.datastore:class-instances 'store-object))\n\n(defmethod all-global-objects (store)\n  (loop for subsystem in (bknr.datastore::store-subsystems *store*)\n        appending (all-subsystem-objects subsystem)))\n\n(defmethod object-neighbors ((x store-object))\n  \"For a given object, find all neighboring objects. i,e, the objects\nthat this object directly references.\"\n  (loop for slotd in (closer-mop:class-slots (class-of x))\n        if (and\n            (slot-boundp x (closer-mop:slot-definition-name slotd))\n            (not (bknr.datastore::transient-slot-p slotd))\n            (not (bknr.datastore::relaxed-object-reference-slot-p slotd)))\n          collect\n          (let ((slot-value\n                  (slot-value x\n                              (closer-mop:slot-definition-name slotd))))\n            slot-value)))\n\n(defmethod object-neighbors ((x cons))\n  (loop for item on x\n        if (consp item)\n          collect (car item)\n        else\n          collect item))\n\n(defmethod object-neighbors ((x null))\n  nil)\n\n(defmethod object-neighbors (x)\n  nil)\n\n(defmethod object-neighbors ((x vector))\n  (loop for a across x\n        collect a))\n\n(defmethod object-neighbors ((x standard-object))\n  (let ((slots (closer-mop:class-slots (class-of x))))\n    (loop for slot in slots\n          for slot-name = (closer-mop:slot-definition-name slot)\n          if (slot-boundp x slot-name)\n            collect (slot-value x slot-name))))\n\n(defmethod find-any-refs (objects)\n  \"Similar to BKNR.DATASTORE:FIND-REFS, but all elements in the transitive paths from U - O to O.\n\nThis means you can find all unreferenced objects by doing\nset-differences on O and the returned value from this.\"\n\n  ;; Use this to stress-test find-any-refs in prod:\n  ;; (length (find-any-refs (class-instances 'store-object)))\n\n  (let ((original-objects (make-hash-table))\n        (seen (make-hash-table))\n        (ret (make-hash-table)))\n    (loop for obj in objects do\n      (setf (gethash obj original-objects) t))\n    (labels ((dfs (x)\n               (etypecase x\n                 (standard-object\n                  (unless (gethash x seen)\n                    (setf (gethash x seen) t)\n                    (when\n                        ;; Notice that even if x is in original-objects,\n                        ;; we must still traverse it, otherwise we\n                        ;; might miss some paths.\n                        (or\n                         (loop for neighbor in (object-neighbors x)\n                               if (dfs neighbor)\n                                 collect t)\n                         (gethash x original-objects))\n                      ;; on the way back on the DFS, mark all the\n                      ;; nodes as part of the result\n                      (setf (gethash x ret) t)))\n                  ;; But we still need to propagate the last cached\n                  ;; result if we have already dfs-ed this node.\n                  (gethash x ret))\n                 (null\n                  nil)\n                 (cons\n                  ;; Make sure we always travers in both paths. But we\n                  ;; need tail call optimization, since the lists can\n                  ;; be huge.\n                  (labels ((cons-dfs (x default)\n                             (typecase x\n                               (cons\n                                (let ((left (cons-dfs (car x) default)))\n                                  (cons-dfs (cdr x) left)))\n                               (otherwise\n                                (or (dfs x) default)))))\n                    (cons-dfs x nil)))\n                 (vector\n                  (loop for y across x\n                        if (dfs y)\n                          collect t))\n                 (symbol nil)\n                 (character nil)\n                 (number nil)\n                 (string nil))))\n      (loop for x in (all-global-objects *store*)\n            if (not (gethash x original-objects))\n              do\n                 (dfs x))\n      (sort\n       (loop for x being the hash-keys of ret\n             collect x)\n       #'< :key #'bknr.datastore:store-object-id))))\n\n(defmethod bknr.datastore::snapshot-store :around ((store safe-mp-store))\n  (with-snapshot-lock (store)\n    (clear-transaction-log-lock store)\n    (let ((ret (call-next-method)))\n      (ensure-transaction-log-lock store)\n      ret)))\n\n(defmethod bknr.datastore::restore-store :around ((store safe-mp-store) &key until)\n  (with-snapshot-lock (store)\n    (call-next-method)))\n\n(defun safe-snapshot (&optional (comment \"default-comment\") (force t))\n  (safe-snapshot-impl *store* :comment comment\n                      :force force))\n\n(defmethod safe-snapshot-impl (store &key comment\n                                       (force t))\n  (log:info \"Going into snapshot, write access will be locked\")\n  (time\n   (snapshot)))\n\n(defmethod safe-snapshot-impl ((store safe-mp-store) &key comment\n                                                       (force t))\n  (let ((directories (fad:list-directory *object-store*)))\n    (call-next-method)\n    (let* ((new-directories (fad:list-directory *object-store*))\n           (directories (set-difference new-directories directories :test #'equal)))\n      (unless (= 1 (length directories))\n        (error \"Expected to see one new directory, but saw: ~S\" directories))\n      (%write-comment (car directories) comment)\n      (let ((dir (car directories)))\n        (dispatch-snapshot-hooks dir)\n        dir))))\n\n(defun %write-comment (dir comment)\n  (with-open-file (file (path:catfile dir \"comment.txt\")\n                        :direction :output\n                        ;; If we recovered from an existing\n                        ;; snapshot, then the comment.txt might\n                        ;; already be here.\n                        :if-exists :supersede)\n    (write-string comment file)))\n\n(deftransaction fake-transaction ()\n  nil)\n\n#+bknr.cluster\n(defmethod safe-snapshot-impl ((store cluster-store-mixin) &key comment\n                                                             (force t))\n  (when (and force #+bknr.cluster (leaderp store))\n    ;; bknr.cluster will not take a snapshot if there have been no new\n    ;; transactions. But if we're doing a reload from code, we\n    ;; probably want to force a new snapshot.\n    (fake-transaction))\n\n  (call-next-method)\n  (let ((dir (path:catdir (data-path store) \"snapshot\")))\n    (%write-comment dir comment)\n    (dispatch-snapshot-hooks dir)))\n\n(defun dispatch-snapshot-hooks (dir)\n  (loop for hook in *snapshot-hooks* do\n    (log:info \"Calling snapshot hook: ~a\" hook)\n    (funcall hook *store* dir)))\n\n(defmethod slot-key-for-verification (metaclass slot)\n  slot)\n\n(defun verify-old-class (class-name slots metaclass)\n  (flet ((key (x)\n           (slot-key-for-verification metaclass x)))\n   (let ((old-class (find-class class-name nil)))\n     (when old-class\n       (let ((old-slots\n               (mapcar #'key\n                       (mapcar #'closer-mop:slot-definition-name\n                               (closer-mop:class-direct-slots old-class)))))\n         (a:when-let ((diff (set-difference\n                             old-slots\n                             (mapcar #'key\n                                     (mapcar #'car slots))\n                             :test #'equal)))\n\n           (cerror \"Dangerous continue and lose data\" \"missing slots: ~a\"\n                   diff)))))))\n\n(defmacro with-class-validation (&body body)\n  \"Wrap a defclass for store objects to add extra validations\"\n  (assert (= 1 (length body)))\n  (let* ((def (car body))\n         (class-name (elt def 1))\n         (slots (elt def 3))\n         (metaclass (loop for option in def\n                          if (and\n                              (listp option)\n                              (eql :metaclass (first option)))\n                            return (second option))))\n\n    ;; verify both at compile time and load time, just how it is. The\n    ;; compiler updates the classes unfortunately, which will make the\n    ;; load-time verification pass.\n    (verify-old-class class-name slots metaclass)\n\n    `(progn\n       (verify-old-class ',class-name\n                         ',slots\n                         ',metaclass)\n       ,def)))\n\n;; (validate-indices)\n\n(defmacro defindex (name class &rest args &key slot-name\n                                            slots\n                    &allow-other-keys)\n  (unless (or slots slot-name)\n    (error \"Must provide :slots or :slot-name\"))\n  (when (and slots slot-name)\n    (error \"Must provide only one of :slots or :slot-name\"))\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (defvar ,name\n       (make-instance ,class ,@args))))\n\n\n(defvar *store-local-lock* (bt:make-lock))\n\n(defvar *store-local-map* (make-hash-table\n                           #+lispworks :weak-kind\n                           #+lispworks :key)\n  \"A map from store to the alist of all store local variables\")\n\n\n(symbol-macrolet ((place (gethash bknr.datastore:*store* *store-local-map*)))\n  (defun get-store-local (name)\n    (util/misc:or-setf\n     (a:assoc-value place name)\n     (funcall (get name 'store-local-init-fn))\n     :thread-safe t))\n  (defun (setf get-store-local) (value name)\n    (setf (a:assoc-value place name) value)))\n\n(defclass store-local-subsystem ()\n  ()\n  (:documentation \"A subsystem to keep track of store locals (in particular to get a\ncallback when the store needs to reset.\"))\n\n(defmethod restore-subsystem ((store bknr.datastore:store)\n                              (self store-local-subsystem) &key until)\n  (assert (= 0 (hash-table-count *store-local-map*)))\n  (log:info \"Restoring store-local-subsystem\")\n  (clrhash *store-local-map*))\n\n(defmethod close-subsystem ((store bknr.datastore:store)\n                            (self store-local-subsystem))\n  (log:info \"closing store-local-subsystem\")  \n  (clrhash *store-local-map*))\n\n(defmethod bknr.datastore:snapshot-subsystem ((store bknr.datastore:store)\n                                              (self store-local-subsystem)))\n\n(defsubsystem store-local-subsystem :priority -1000)\n\n(defmacro def-store-local (name initform &optional documentation)\n  \"Defines a variable thats local to the current store. You cannot use\nthis variable in LET forms, but you can SETF it if you like.\"\n  `(progn\n     (setf (get ',name 'store-local-init-fn)\n           (lambda () ,initform))\n     (define-symbol-macro\n         ,name\n         (get-store-local ',name))))\n\n(defmethod location-for-oid ((root pathname) (oid array)\n                             &key suffix\n                               type)\n  \"Returns two values: the absolute pathname for the given oid and root,\n and the relative pathname relative to the object store. The second\n value is useful for propagating to s3-store.\"\n  (let* ((oid (coerce oid '(vector (unsigned-byte 8))))\n         (store-dir (bknr.datastore::store-directory\n                                     bknr.datastore:*store*))\n         (p1 (ironclad:byte-array-to-hex-string oid :start 0 :end 1))\n         (p2 (ironclad:byte-array-to-hex-string oid :start 1 :end 2))\n         (p4 (ironclad:byte-array-to-hex-string oid :start 2)))\n    ;; The first two bytes change approximately once every 0.7 days,\n    ;; so each directory has enough space for all files generated in\n    ;; one day. That should be good enough for anybody!\n    (let* ((relpath (make-pathname\n                     :directory (list :relative p1 p2)\n                     :type type\n                     :name (cond\n                             (suffix\n                              (format nil \"~a-~a\" p4 suffix))\n                             (t p4))))\n           (file (path:catfile store-dir\n                               root\n                               relpath)))\n      (fast-ensure-directories-exist file)\n      (values file\n              (namestring\n               (path:catfile root relpath))))))\n\n(defmethod fset:compare ((a store-object) (b store-object))\n  (let ((*indexed-class-override* t))\n    (fset:compare (store-object-id a) (store-object-id b))))\n\n(deftransaction tx-force-error (msg)\n  ;;(error \"force crash with ~a\" msg)\n  )\n\n(defun generate-sync-test (&optional (output *standard-output*))\n  (dolist (subsystem (bknr.datastore::store-subsystems *store*))\n    (generate-sync-test-for-subsystem *store* subsystem output)))\n\n(defmethod generate-sync-test-for-subsystem (store subsystem output)\n  (values))\n\n(defmethod generate-sync-test-for-subsystem (store (subsystem store-object-subsystem) output)\n  (dolist (obj (sort (copy-list (all-store-objects)) #'< :key #'store-object-id))\n    (format output \"~a \" obj)\n    (generate-sync-test-for-object obj output)\n    (format output \"~%\"))\n  (format output \"~a~%\" (bknr.datastore:class-instances 'bknr.datastore:store-object)))\n\n(defmethod generate-sync-test-for-object (obj output))\n\n(defmethod generate-sync-test-for-object :around ((obj store-object) output)\n  (format output \"id: ~a\" (store-object-id obj))\n  (call-next-method))\n\n(defgeneric validate-index-values (index all-elts slot-name))\n\n(defvar *profilep* nil)\n\n(defmethod on-snapshot-save-impl :around ((store base-raft-store) snapshot-writer done)\n  \"Add timing for snapshots\"\n  (with-tracing (:snapshot)\n    (cond\n      #+lispworks\n      (*profilep*\n       (hcl:profile\n        (call-next-method)))\n      (t\n       (call-next-method)))))\n\n(defun find-deleted-references ()\n  (let ((seen (make-hash-table))\n        (queue (make-array 0 :adjustable t :fill-pointer t))\n        (start 0))\n    (loop for obj in (bknr.datastore:all-store-objects)\n          do (vector-push-extend obj queue))\n    (loop while (< start (length queue))\n          do\n             (let ((next (elt queue (1- (incf start)))))\n               (unless (gethash next seen)\n                 (setf (gethash next seen) t)\n                 (block nil\n                  (loop for neighbor in (object-neighbors next)\n                        if (typep neighbor 'store-object)\n                          do\n                             (cond\n                               ((bknr.datastore::object-destroyed-p neighbor)\n                                (restart-case\n                                    (error \"Object ~a references a destroyed object ~a\"  next neighbor)\n                                  (delete-this-object ()\n                                    (bknr.datastore:delete-object next)\n                                    ;; We don't want to keep accessing this object\n                                    (return))))\n                               (t\n                                (vector-push-extend neighbor queue))))))))))\n\n(defvar *index-caches* (make-hash-table)\n  \"See T1322. Don't use this for anything. The main goal for this is to\nhold on to a copy of the big indices in cases they get replaced, so\nthat we don't lose data and can restore ourself to a state where\nsnapshots can happen again\")\n\n(defun backup-indices ()\n  (setf (gethash bknr.datastore::*id-index* *index-caches*) (get-universal-time))\n  (setf (gethash bknr.datastore::*class-skip-index* *index-caches*) (get-universal-time)))\n\n(cl-cron:make-cron-job\n 'backup-indices\n :step-min 5\n :hash-key 'backup-indices)\n\n\n(defun nsort-store-objects (objects)\n  \"Convenience function to implement custom class-instances overrides\"\n  (sort objects #'< :key #'bknr.datastore:store-object-id))\n"
  },
  {
    "path": "src/util/store/summing-index.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/summing-index\n  (:use #:cl)\n  (:import-from #:bknr.indices\n                #:index-remove\n                #:index-get\n                #:index-clear\n                #:index-add\n                #:index-reinitialize)\n  (:import-from #:alexandria\n                #:when-let))\n(in-package :util/store/summing-index)\n\n(defclass summing-index ()\n  ((key :initarg :key\n             :reader key-fn)\n   (value :initarg :value\n          :reader value-fn)\n   (map :initform (make-hash-table :test #'eql)\n        :reader %map))\n  (:documentation \"An index that sums a particular field by a particular key\"))\n\n(defmethod index-reinitialize ((new-index summing-index)\n                               (old-index summing-index))\n  (loop for key being the hash-keys of (%map old-index)\n          using (hash-value v)\n        do (setf (gethash key (%map new-index)) v)))\n\n\n(defmethod index-add ((self summing-index)\n                      obj)\n  (when-let ((key (funcall (key-fn self) obj))\n             (value (funcall (value-fn self) obj)))\n    (incf (gethash key (%map self) 0)\n          value)))\n\n(defmethod index-clear ((self summing-index))\n  (clrhash (%map self)))\n\n(defmethod index-get ((self summing-index)\n                      key)\n  (gethash key (%map self) 0))\n\n(defmethod index-remove ((self summing-index)\n                         obj)\n  (when-let ((key (funcall (key-fn self) obj))\n             (value (funcall (value-fn self) obj)))\n    (decf (gethash key (%map self) 0)\n          value)))\n\n\n"
  },
  {
    "path": "src/util/store/sync.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/sync\n  (:use #:cl)\n  (:import-from #:util/store/encodable\n                #:encodable)\n  (:import-from #:core/rpc/rpc\n                #:call-rpc)\n  (:import-from #:util/store/store\n                #:generate-sync-test)\n  (:import-from #:util/cron\n                #:def-cron)\n  (:import-from #:util/events\n                #:with-tracing\n                #:push-event))\n(in-package :util/store/sync)\n\n(defclass sync-sha-request (encodable)\n  ())\n\n(defmethod call-rpc ((self sync-sha-request))\n  (with-tracing (:sync-sha-request)\n    (uiop:with-temporary-file (:stream s :pathname p :direction :io)\n      (generate-sync-test s)\n      (finish-output s)\n      (file-position s 0)\n      (ironclad:byte-array-to-hex-string\n       (ironclad:digest-file :sha256 p)))))\n\n(def-cron test-sync (:minute 0 :step-hour 6)\n  #+nil\n  (let ((shas (core/rpc/rpc::map-rpc (make-instance 'sync-sha-request))))\n    (push-event\n     :sync-state\n     :count (length (remove-duplicates shas :test #'equal))\n     :shas shas)))\n"
  },
  {
    "path": "src/util/store/test-checksums.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-checksums\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:flexi-streams\n                #:get-output-stream-sequence\n                #:make-in-memory-input-stream\n                #:make-in-memory-output-stream)\n  (:import-from #:util/store/checksums\n                #:invalid-transaction-length-error\n                #:*max-transaction-size*\n                #:could-not-read-checksum\n                #:could-not-read-length\n                #:end-of-file-error\n                #:checksum-failure\n                #:encode-checksumed-object)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:util/store/object-id\n                #:make-oid)\n  (:import-from #:bknr.datastore\n                #:%write-tag)\n  (:import-from #:bknr.datastore\n                #:encode-integer)\n  (:import-from #:bknr.datastore\n                #:%encode-integer)\n  (:import-from #:bknr.datastore\n                #:transaction)\n  (:import-from #:bknr.datastore\n                #:encode))\n(in-package :util/store/test-checksums)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((output (make-in-memory-output-stream))\n        (test-oid (make-oid :arr #(1 2 3 4 5 6 7 8 9 10 11 12))))\n    (&body)))\n\n(test encode-and-decode\n  (with-fixture state ()\n    (encode-checksumed-object test-oid output)\n    (is (equalp test-oid\n                (decode\n                 (make-in-memory-input-stream (get-output-stream-sequence output)))))))\n\n(test checksum-failure-condition\n  (with-fixture state ()\n    (encode-checksumed-object test-oid output)\n    (let ((buff (get-output-stream-sequence output)))\n      (setf (elt buff 11) (mod (1+ (elt buff 11)) 128))\n      (signals checksum-failure\n        (decode (make-in-memory-input-stream buff))))))\n\n\n(test checksum-failure-condition-when-checksum-is-tweaked\n  (with-fixture state ()\n    (encode-checksumed-object test-oid output)\n    (let ((buff (get-output-stream-sequence output)))\n      (setf (elt buff 4) 3)\n      (signals checksum-failure\n        (decode (make-in-memory-input-stream buff))))))\n\n\n(test truncated-file\n  (with-fixture state ()\n    (encode-checksumed-object test-oid output)\n    (let ((buff (get-output-stream-sequence output)))\n      (adjust-array buff (1- (length buff))\n                    :fill-pointer (1- (length buff)))\n      (setf (elt buff 4) 3)\n      (signals end-of-file-error\n        (decode (make-in-memory-input-stream buff))))))\n\n(test truncated-length\n  \"When the length itself gets truncated\"\n  (let ((stream (make-in-memory-input-stream #(67 #| C |#))))\n    (signals could-not-read-length\n      (decode stream))))\n\n(test truncated-crc32\n  \"When the length itself gets truncated\"\n  (let ((builder (make-in-memory-output-stream)))\n    (%write-tag #\\C builder)\n    (%encode-integer 10 builder)\n\n    ;; Write a partial CRC32\n    (write-byte 32 builder)\n    (write-byte 42 builder)\n   (let ((stream (make-in-memory-input-stream (get-output-stream-sequence builder))))\n     (signals could-not-read-checksum\n       (decode stream)))))\n"
  },
  {
    "path": "src/util/store/test-delayed-accessors.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-delayed-accessors\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:util/delayed-accessors\n                #:def-delayed-accessor))\n(in-package :util/store/test-delayed-accessors)\n\n(util/fiveam:def-suite)\n\n(defclass my-obj (store-object)\n  ((my-slot :accessor %my-slot))\n  (:metaclass persistent-class))\n\n(def-delayed-accessor my-slot %my-slot)\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((obj (make-instance 'my-obj)))\n     (&body))))\n\n(test simple-test\n  (with-fixture state ()\n    (setf (my-slot obj) 2)\n    (is (eql 2 (my-slot obj)))\n    (setf (my-slot obj) 3)\n    (is (eql 3 (my-slot obj)))))\n\n(test simple-flush\n  (with-fixture state ()\n    (setf (my-slot obj) 2)\n    (is (not (slot-boundp obj 'my-slot)))\n    (flush-my-slot)\n    (is (eql 2 (my-slot obj)))\n    (is (eql 2 (%my-slot obj)))\n    (is (eql nil *my-slot-cache*))))\n"
  },
  {
    "path": "src/util/store/test-encodable.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-encodable\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/encodable\n                #:encodable)\n  (:import-from #:bknr.datastore\n                #:decode\n                #:encode\n                #:decode-object\n                #:encode-object)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that))\n(in-package :util/store/test-encodable)\n\n\n(util/fiveam:def-suite)\n\n(defclass simple-obj (encodable)\n  ((slot1 :initarg :slot1)\n   (slot2 :initarg :slot2)))\n\n(defun recreate (obj)\n  (let ((stream (flex:make-in-memory-output-stream)))\n    (encode obj stream)\n    (let ((stream (flex:make-in-memory-input-stream\n                   (flex:get-output-stream-sequence stream))))\n      (decode stream))))\n\n(test preconditions\n  (is (equal \"foo\" (recreate \"foo\")))\n  (assert-that (recreate (make-instance 'simple-obj))\n               (has-typep 'simple-obj)))\n\n(test slots-are-saved\n  (let ((obj (recreate (make-instance 'simple-obj :slot1 \"foo\"))))\n    (is (equal \"foo\" (slot-value obj 'slot1)))))\n"
  },
  {
    "path": "src/util/store/test-fset-index.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-fset-index\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.indices\n                #:base-indexed-object\n                #:index-remove\n                #:indexed-class)\n  (:import-from #:util/store/fset-index\n                #:fset-many-to-many-index\n                #:index-object-key\n                #:index-least\n                #:%map\n                #:corrupted-index\n                #:fset-set-index\n                #:fset-unique-index)\n  (:import-from #:util/store/store\n                #:validate-index-values\n                #:with-test-store)\n  (:import-from #:bknr.indices\n                #:index-clear)\n  (:import-from #:bknr.indices\n                #:clear-slot-indices)\n  (:import-from #:bknr.indices\n                #:destroy-object)\n  (:import-from #:bknr.indices\n                #:destroy-object)\n  (:import-from #:bknr.indices\n                #:index-existing-error)\n  (:import-from #:bknr.indices\n                #:index-add)\n  (:import-from #:bknr.indices\n                #:index-reinitialize)\n  (:import-from #:bknr.indices\n                #:index-values)\n  (:import-from #:bknr.indices\n                #:index-get))\n(in-package :util/store/test-fset-index)\n\n(util/fiveam:def-suite)\n\n(defclass test-object (base-indexed-object)\n  ((arg :initarg :arg\n        :accessor %arg\n        :index-type fset-unique-index\n        :index-reader search-by-arg\n        :index-values first-index-values))\n  (:metaclass indexed-class))\n\n(defclass test-object-2 (base-indexed-object)\n  ((arg :initarg :arg\n        :index-type fset-set-index\n        :index-reader second-by-arg\n        :index-values second-index-values))\n  (:metaclass indexed-class))\n\n(defclass test-object-3 (base-indexed-object)\n  ((tags :initarg :tags\n         :index-type fset-many-to-many-index\n         :index-reader object-by-tag\n         :index-values test-object-3-values))\n  (:metaclass indexed-class))\n\n(def-fixture state ()\n  (with-test-store ()\n    (flet ((clear-indices ()\n             (loop for class in '(test-object test-object-2 test-object-3)\n                   do\n                      (mapcar\n                       #'clear-slot-indices\n                       (closer-mop:class-slots (find-class class))))))\n     (unwind-protect\n          (&body)\n       (clear-indices)))))\n\n(test simple-create-and-load\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object\n                              :arg \"foo\")))\n      (is (eql obj\n               (search-by-arg \"foo\")))\n      (is (eql nil\n               (search-by-arg \"bar\")))))\n  (with-fixture state ()\n    (is (eql nil\n             (search-by-arg \"foo\")))))\n\n(test destroy-object\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object\n                              :arg \"foo\")))\n      (is (eql obj\n               (search-by-arg \"foo\")))\n      (destroy-object obj)\n      (is (eql nil (search-by-arg \"foo\"))))))\n\n\n(defun make-set (&rest args)\n  (fset:convert 'fset:set args))\n\n(test simple-create-and-load-set\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object-2\n                              :arg \"foo\")))\n      (is (fset:equal? (make-set obj)\n                       (second-by-arg \"foo\")))\n      (is (fset:equal? (make-set)\n                       (second-by-arg \"car\")))\n      (let ((obj2 (make-instance 'test-object-2\n                                 :arg \"foo\")))\n        (is (fset:equal? (make-set obj obj2)\n                         (second-by-arg \"foo\")))\n        (destroy-object obj2)\n        (is (fset:equal? (make-set obj)\n                         (second-by-arg \"foo\")))\n        (destroy-object obj)\n        (is (fset:equal? (make-set)\n                         (second-by-arg \"foo\")))))))\n\n(test unbound-slot\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object)))\n      (is (eql nil\n               (search-by-arg \"foo\"))))\n    (let ((obj (make-instance 'test-object-2)))\n      (is (fset:equal? (fset:empty-set)\n                       (second-by-arg \"foo\"))))))\n\n(test remove-unbound-slot\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object)))\n      (finishes\n       (destroy-object obj)))))\n\n(test update-slot\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object\n                              :arg \"foo\")))\n      (setf (%arg obj) \"bar\")\n      (is (eql obj (search-by-arg \"bar\")))\n      (is (eql nil (search-by-arg \"foo\")))\n      (setf (%arg obj) \"car\")\n      (is (eql obj (search-by-arg \"car\")))\n      (is (eql nil (search-by-arg \"foo\"))))))\n\n\n(test index-values\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg \"foo\"))\n          (obj-2 (make-instance 'test-object))\n          (obj-3 (make-instance 'test-object :arg \"bar\")))\n      (is (equal\n           (list obj obj-3)\n           (first-index-values))))))\n\n(test index-values-for-set-mode\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object-2 :arg \"foo\"))\n          (obj-2 (make-instance 'test-object-2))\n          (obj-3 (make-instance 'test-object-2 :arg \"bar\")))\n      (is (equal\n           (list obj obj-3)\n           (second-index-values))))))\n\n(test nil-is-not-added-to-index\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg nil)))\n      (is (equal nil (first-index-values))))))\n\n(test deleting-an-equivalent-object\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg \"foo\")))\n      (clear-indices) ;; make obj not part of an index\n      (let ((obj-2 (make-instance 'test-object :arg \"foo\")))\n        (destroy-object obj)\n        (is (eql obj-2 (search-by-arg \"foo\")))))))\n\n(test uniqueness\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg \"foo\")))\n      (signals index-existing-error\n        (make-instance 'test-object :arg \"foo\"))\n      ;; Make sure the index hasn't been modified\n      (is (eql obj (search-by-arg \"foo\"))))))\n\n(test adding-the-same-object-twice-doesnt-show-uniqueness-error\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg \"foo\"))\n          (index (make-instance 'fset-unique-index :slots '(arg))))\n      (index-add index obj)\n      (finishes (index-add index obj)))))\n\n(test validate-index-happy-path\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg \"foo\")))\n      (let ((index-1 (make-instance 'fset-unique-index :slots '(arg))))\n        (index-add index-1 obj)\n        (validate-index-values index-1 (list obj)\n                               'arg)))))\n\n(test validate-index-unhappy-path\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object :arg \"foo\")))\n      (let ((index-1 (make-instance 'fset-set-index :slots '(arg))))\n        (signals corrupted-index\n          (validate-index-values index-1 (list obj)\n                                 'arg))))))\n\n\n(test index-reinitialize-for-fset-indices\n  (with-fixture state ()\n   (let ((index1 (make-instance 'fset-set-index :slots '(arg)))\n         (index2 (make-instance 'fset-set-index :slots '(arg))))\n     (let ((obj-1 (make-instance 'test-object-2 :arg \"foo\"))\n           (obj-2 (make-instance 'test-object-2 :arg \"foo\")))\n       (index-add index2 obj-2)\n       (index-add index1 obj-1)\n       (index-reinitialize index1 index2)\n       (is (fset:equal? (make-set obj-1 obj-2)\n                        (apply #'make-set (index-values index1))))\n       (is (fset:equal? (make-set obj-1 obj-2)\n                        (index-get index1 \"foo\")))))))\n\n(test removes-empty-sets\n  (with-fixture state ()\n    (let ((index1 (make-instance 'fset-set-index :slots '(arg))))\n      (let ((obj-1 (make-instance 'test-object-2 :arg \"foo\")))\n        (index-add index1 obj-1)\n        (is (not (fset:empty? (%map index1))))\n        (index-remove index1 obj-1)\n        (is (fset:empty? (%map index1)))))))\n\n(test gets-least\n  (with-fixture state ()\n    (let ((index1 (make-instance 'fset-set-index :slots '(arg))))\n      (let ((obj-1 (make-instance 'test-object-2 :arg \"foo\")))\n        (is (eql nil (index-least index1)))\n        (index-add index1 obj-1)\n        (is (eql obj-1 (index-least index1)))\n        (index-remove index1 obj-1)\n        (is (eql nil (index-least index1)))))))\n\n(test index-reinitialize-for-unique-index\n  (with-fixture state ()\n   (let ((index1 (make-instance 'fset-unique-index :slots '(arg)))\n         (index2 (make-instance 'fset-unique-index :slots '(arg))))\n     (let ((obj-1 (make-instance 'test-object :arg \"foo\"))\n           (obj-2 (make-instance 'test-object :arg \"bar\")))\n       (index-add index2 obj-2)\n       (index-add index1 obj-1)\n       (index-reinitialize index1 index2)\n       (is (fset:equal? (make-set obj-1 obj-2)\n                        (apply #'make-set (index-values index1))))\n       (is (fset:equal? obj-1\n                        (index-get index1 \"foo\")))\n       (is (fset:equal? obj-2\n                        (index-get index1 \"bar\")))))))\n\n(test index-fails-with-bad-initarg\n  (with-fixture state ()\n    (signals\n        #+lispworks conditions:unknown-keyword-error #-lispworks error\n     (make-instance 'fset-unique-index :slots '(arg) :bleh 2))))\n\n(test index-key-does-not-use-list-for-single\n  (with-fixture state ()\n    (let ((obj (make-instance 'test-object\n                              :arg \"foo\")))\n      (is (equal \"foo\"\n                 (index-object-key (make-instance 'fset-unique-index :slots '(arg))\n                                   obj)))\n      (is (equal \"foo\"\n                 (index-object-key (make-instance 'fset-set-index :slots '(arg))\n                                   obj)))\n      (is (equal (list \"foo\" \"foo\")\n                 (index-object-key (make-instance 'fset-unique-index :slots '(arg arg))\n                                   obj)))\n      (is (equal (list \"foo\" \"foo\")\n                 (index-object-key (make-instance 'fset-set-index :slots '(arg arg))\n                                   obj))))))\n\n(test simple-many-to-many-index\n  (with-fixture state ()\n    (let ((one (make-instance 'test-object-3\n                              :tags (list \"foo\" \"bar\"))))\n      (is (fset:equal? (fset:convert 'fset:set (list one))\n                       (object-by-tag \"foo\")))\n      (is (fset:equal? (fset:convert 'fset:set (list one))\n                       (object-by-tag \"bar\")))\n      (let ((two (make-instance 'test-object-3\n                                :tags (list \"car\" \"bar\"))))\n        (is (fset:equal? (fset:convert 'fset:set (list one two))\n                         (object-by-tag \"bar\")))\n        (is (fset:equal? (fset:convert 'fset:set (list one))\n                         (object-by-tag \"foo\")))\n        (destroy-object two)\n        (is (fset:equal? (fset:convert 'fset:set (list one))\n                         (object-by-tag \"bar\")))))))\n"
  },
  {
    "path": "src/util/store/test-fset.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-fset\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.datastore\n                #:decode\n                #:encode))\n(in-package :util/store/test-fset)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((map (fset:with\n              (fset:with\n               (fset:empty-map)\n               \"foo\" 2)\n              \"bar\" 3))\n        (stream (flex:make-in-memory-output-stream)))\n    (flet ((decode-back ()\n             (let ((stream (flex:make-in-memory-input-stream\n                            (flex:get-output-stream-sequence stream))))\n               (decode stream))))\n     (&body))))\n\n(test preconditions\n  (with-fixture state ()\n    (encode map stream)\n    (is (fset:equal?\n         (decode-back)\n         map))))\n\n(test empty-map\n  (with-fixture state ()\n    (encode (fset:empty-map) stream)\n    (is (fset:equal?\n         (decode-back)\n         (fset:empty-map)))))\n\n(test large-map-decoding\n  \"I couldn't reliably get this test to fail when using TCO\"\n  (let ((large-map (loop with map = (fset:empty-map)\n                        for i from 1 to 1000\n                        do (setf map (fset:with map i (* i 2)))\n                        finally (return map)))\n        (stream (flex:make-in-memory-output-stream)))\n    (encode large-map stream)\n    (let ((stream (flex:make-in-memory-input-stream\n                   (flex:get-output-stream-sequence stream))))\n      (is (fset:equal?\n           (decode stream)\n           large-map)))))\n\n(test empty-set\n  (with-fixture state ()\n    (encode (fset:empty-set) stream)\n    (is (fset:equal?\n         (decode-back)\n         (fset:empty-set)))))\n\n(test set-with-stuff\n  (with-fixture state ()\n    (encode (fset:convert 'fset:set (list 1 2 3)) stream)\n    (is (fset:equal?\n         (fset:convert 'fset:set (list 1 2 3))\n         (decode-back)))))\n\n"
  },
  {
    "path": "src/util/store/test-migrations.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-migrations\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/migrations\n                #:clone-slot\n                #:*moved-syms*\n                #:symbol-in-both-packages\n                #:ensure-symbol-in-package)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:store-object)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/store/test-migrations)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*moved-syms* nil))\n   (let ((package (make-package :%dum1))\n         (package2 (make-package :%dum2)))\n     (unwind-protect\n          (&body)\n       (ignore-errors\n        (delete-package package))\n       (ignore-errors\n        (delete-package package2))))))\n\n(defun ensure-foo ()\n  (is-false (util/migrations::find-symbol* \"FOO\" :%dum1))\n  (is-true (util/migrations::find-symbol* \"FOO\" :%dum2)))\n\n(test simple-migration-when-symbol-does-not-exist\n  \"This is the situation of restarting an image with this migration.\"\n\n  (with-fixture state ()\n    (ensure-symbol-in-package\n     #:foo\n     :old #:%dum1\n     :new #:%dum2)\n    (is-true (uiop:find-symbol* :foo :%dum2))\n\n    (ensure-foo)\n\n    (is (eql\n         (find-package :%dum2)\n         (symbol-package (uiop:find-symbol* :foo :%dum2))))))\n\n(test simple-migration-when-package-does-not-exist\n  (with-fixture state ()\n    (delete-package :%dum1)\n    (ensure-symbol-in-package\n     #:foo\n     :old #:%dum1\n     :new #:%dum2)\n    (is-true (uiop:find-symbol* :foo :%dum2))\n\n    (ensure-foo)\n\n    (is (eql\n         (find-package :%dum2)\n         (symbol-package (uiop:find-symbol* :foo :%dum2))))))\n\n\n\n(test simple-migration-when-symbol-is-in-old-package\n  (with-fixture state ()\n    (intern \"FOO\" :%dum1)\n    (ensure-symbol-in-package\n     #:foo\n     :old #:%dum1\n     :new #:%dum2)\n\n    (is (eql\n         (find-package :%dum2)\n         (symbol-package (uiop:find-symbol* :foo :%dum2))))))\n\n\n(test simple-migration-when-symbol-is-in-new-package\n  (with-fixture state ()\n    (intern \"FOO\" :%dum2)\n    (ensure-symbol-in-package\n     #:foo\n     :old #:%dum1\n     :new #:%dum2)\n\n    (ensure-foo)\n    (is-true (find-symbol \"FOO\" :%dum2))\n    (is-false (find-symbol \"FOO\" :%dum1))\n\n    (is (eql\n         (find-package :%dum2)\n         (symbol-package (uiop:find-symbol* :foo :%dum2))))))\n\n\n(test simple-migration-when-symbol-is-in-both-packages\n  (with-fixture state ()\n    (intern \"FOO\" :%dum2)\n    (intern \"FOO\" :%dum1)\n    (signals symbol-in-both-packages\n     (ensure-symbol-in-package\n      #:foo\n      :old #:%dum1\n      :new #:%dum2))))\n\n(test the-same-symbol-is-in-both-packages\n  (with-fixture state ()\n    (let ((sym (intern \"FOO\" :%dum2)))\n      (import sym :%dum1))\n\n    (ensure-symbol-in-package\n     #:foo\n     :old #:%dum1\n     :new #:%dum2)\n\n    (ensure-foo)))\n\n(defclass test-class (store-object)\n  ((foo :initarg :foo\n        :reader foo)\n   (%foo :reader %foo))\n  (:metaclass persistent-class))\n\n(def-fixture clone-state ()\n  (with-test-store ()\n    (&body)))\n\n(test clone-slot\n  (with-fixture clone-state ()\n    (let ((inst (make-instance 'test-class :foo \"bar\")))\n      (is (not (slot-boundp inst '%foo)))\n      (clone-slot test-class :from foo :to %foo)\n      (is (equal \"bar\"\n                 (%foo inst))))\n    (finishes\n      (clone-slot test-class :from foo :to %foo))\n    (let ((inst (make-instance 'test-class)))\n      (finishes\n        (clone-slot test-class :from foo :to %foo)))))\n\n(defclass test-class-2 (store-object)\n  ((foo :initarg :foo\n        :reader foo)\n   (%foo :reader %foo\n         :initarg :foo))\n  (:metaclass persistent-class))\n\n(test demonstrate-initarg-for-multiple-slots\n  \"This test is just to give future-me some confidence that using the\n same initarg for multiple slots works as expected.\"\n  (with-fixture clone-state ()\n    (let ((inst (make-instance 'test-class-2 :foo \"bar\")))\n      (is (equal \"bar\" (foo inst)))\n      (is (equal \"bar\" (%foo inst))))))\n"
  },
  {
    "path": "src/util/store/test-objectid.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-object-id\n  (:use #:cl\n\t#:alexandria\n        #:fiveam)\n  (:import-from #:util/object-id\n                #:fast-oid-str\n                #:oid-arr\n                #:migrate-oids\n                #:oid-p\n                #:oid-struct-or-array\n                #:make-oid\n                #:%make-oid\n\t\t#:object-with-oid\n                #:oid\n                #:find-by-oid)\n  (:import-from #:util/store\n                #:with-test-store)\n  (:import-from #:bknr.indices\n                #:hash-index)\n  (:import-from #:bknr.datastore\n                #:persistent-class)\n  (:import-from #:bknr.datastore\n                #:encode-object)\n  (:import-from #:bknr.datastore\n                #:decode-object)\n  (:import-from #:bknr.datastore\n                #:encode)\n  (:import-from #:bknr.datastore\n                #:decode)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:bknr.datastore\n                #:class-instances)\n  (:import-from #:util/store/object-id\n                #:all-object-with-oids))\n(in-package :util/store/test-object-id)\n\n(util/fiveam:def-suite)\n\n#-buck\n(test simple-creation-and-finding\n  (with-test-store ()\n   (let ((obj (make-instance 'object-with-oid)))\n     (is (eql obj\n              (find-by-oid (oid obj)))))))\n\n(test indices-dont-fail\n  (with-test-store ()\n    (let ((name (intern \"dummy-name\")))\n      (unwind-protect\n           (let* ((expr `(defclass ,name (object-with-oid)\n                           ((test-key :initarg :key\n                                      :index-type hash-index\n                                      :index-values all-test-classes))\n                           (:metaclass persistent-class))))\n\n             (eval expr)\n             (let ((obj (make-instance name :key 2)))\n               (is (equal (list obj)\n                          (all-test-classes)))\n               (eval expr)\n               (is (equal (list obj)\n                          (all-test-classes))))))\n      (unintern name))))\n\n(test encode-decode-oid\n  (uiop:with-temporary-file (:stream s :element-type '(unsigned-byte 8)\n                             :direction :io)\n    (let ((oid-arr (mongoid:oid)))\n      (let ((oid (make-oid :arr oid-arr))\n            (oid2 (make-oid :arr oid-arr)))\n        (is (equalp oid oid2))\n        (encode oid s)\n        (is (= 13 (file-position s)))\n        (file-position s 0)\n        (let ((oid-read (decode s)))\n          (is (equalp oid oid-read)))))))\n\n(test print-object-for-oid\n  (let ((oid (make-oid\n              :arr (make-array 12\n                          :initial-contents '(1 1 1 1\n                                              1 1 1 1\n                                              1 1 1 1)))))\n    (is (equal \"/foo/010101010101010101010101\"\n               (format nil \"/foo/~a\" oid)))\n    (is (equal \"/foo/010101010101010101010101\"\n               (let ((*print-escape* t))\n                 ;; In prod, for whatever reason *print-escape* is on\n                 ;; globally. We still want to make sure\n                 ;; it's rendered as a string here.\n                 (format nil \"/foo/~a\" oid))))\n    (is (equal \"/foo/#<OID 010101010101010101010101>\"\n               (format nil \"/foo/~S\" oid)))))\n\n(test migrate-objects\n  (with-test-store ()\n    (let* ((oid-1 (mongoid:oid))\n           (oid-2 (%make-oid))\n           (oid-3 (fast-oid-str (mongoid:oid)))\n           (obj1 (make-instance 'object-with-oid\n                                :oid oid-1))\n           (obj2 (make-instance 'object-with-oid\n                                :oid oid-2))\n           (obj3 (make-instance 'object-with-oid\n                                :oid oid-3)))\n      (is (oid-p (oid-struct-or-array obj2)))\n      (is (not (oid-p (oid-struct-or-array obj1))))\n      (is (not (oid-p (oid-struct-or-array obj1))))\n      (migrate-oids)\n      (is (oid-p (oid-struct-or-array obj2)))\n      (is (not (oid-p (oid-arr (oid-struct-or-array obj2)))))\n      (is (oid-p (oid-struct-or-array obj1)))\n      (is (not (oid-p (oid-arr (oid-struct-or-array obj1)))))\n      (is (oid-p (oid-struct-or-array obj1))))))\n\n\n(test object-ids-are-maintained\n  (let ((oid))\n   (tmpdir:with-tmpdir (dir)\n     (with-test-store (:dir dir)\n       (setf oid (oid (make-instance 'object-with-oid))))\n     (with-test-store (:dir dir)\n       (is (equalp oid\n                   (oid (car\n                         (all-object-with-oids)))))))))\n"
  },
  {
    "path": "src/util/store/test-permissive-persistent-class.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-permissive-persistent-class\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/permissive-persistent-class\n                #:base-permissive-persistent-object\n                #:value-map\n                #:permissive-persistent-class)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:delete-object\n                #:store-object)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:bknr.indices\n                #:unique-index))\n(in-package :util/store/test-permissive-persistent-class)\n\n(util/fiveam:def-suite)\n\n(defclass my-object (store-object)\n  ((arg :initarg :arg\n        :accessor arg))\n  (:metaclass permissive-persistent-class))\n\n(defclass my-object-old (store-object)\n  ((arg :initarg :arg\n        :accessor arg))\n  (:metaclass persistent-class))\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(defmacro test* (name () &body body)\n  `(test ,name\n     (with-fixture state ()\n       ,@body)))\n\n(test* simple-create-etc ()\n  (finishes\n    (make-instance 'my-object))\n  (finishes\n    (delete-object (make-instance 'my-object))))\n\n(test* set-slot ()\n  (let ((obj (make-instance 'my-object :arg \"foo\")))\n    (is (equal \"foo\" (arg obj)))\n    (setf (arg obj) \"bleh\")\n    (is (equal \"bleh\" (arg obj)))))\n\n(test* must-be-a-hash-table ()\n  (let ((obj (make-instance 'my-object)))\n    (is (hash-table-p (value-map obj)))))\n\n(test* set-a-slot-value ()\n  (let ((obj (make-instance 'my-object)))\n    (setf (arg obj) \"foo\")\n    (is (equal \"foo\" (gethash \"ARG\" (value-map obj))))))\n\n(test* simple-snapshot ()\n  (let ((obj (make-instance 'my-object)))\n    (finishes\n      (util:safe-snapshot))))\n\n\n(defclass two-slots-with-same-name (store-object)\n  ((cl-user::arg :accessor arg1)\n   (arg :accessor arg2))\n  (:metaclass permissive-persistent-class))\n\n(test* different-slot-with-same-symbol-name-have-the-same-value ()\n  (let ((obj (make-instance 'two-slots-with-same-name)))\n    (setf (arg1 obj) \"hello\")\n    (is (equal \"hello\" (arg2 obj)))))\n\n(defclass two-slots-with-%-name (store-object)\n  ((arg :accessor arg1)\n   (%arg :accessor arg2))\n  (:metaclass permissive-persistent-class))\n\n(test* different-slot-with-%-prefix ()\n  (let ((obj (make-instance 'two-slots-with-%-name)))\n    (setf (arg1 obj) \"hello\")\n    (is (equal \"hello\" (arg2 obj)))))\n\n(test reload-object\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (make-instance 'my-object :arg \"car\")\n      (util:safe-snapshot))\n    (assert-that (bknr.datastore:all-store-objects)\n                 (has-length 0))\n    (with-test-store (:dir dir)\n      (assert-that (bknr.datastore:all-store-objects)\n                   (has-length 1))\n      (assert-that (arg (car (bknr.datastore:all-store-objects)))\n                   (is-equal-to \"car\")))))\n\n(test* makunbound ()\n  (let ((obj (make-instance 'my-object :arg \"car\")))\n    (slot-makunbound obj 'arg)\n    (is-false (slot-boundp obj 'arg))\n    (is (equal 0 (hash-table-count (value-map obj))))))\n\n(test saves-transactions\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (let ((obj (make-instance 'my-object :arg \"car\")))\n        (util:safe-snapshot)\n        (setf (arg obj) \"foo\")))\n    (assert-that (bknr.datastore:all-store-objects)\n                 (has-length 0))\n    (with-test-store (:dir dir)\n      (assert-that (bknr.datastore:all-store-objects)\n                   (has-length 1))\n      (assert-that (arg (car (bknr.datastore:all-store-objects)))\n                   (is-equal-to \"foo\")))))\n\n\n#+nil ;; Test is broken, bug in core bknr.datastore\n(test saves-transactions-for-makunbound\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (let ((obj (make-instance 'my-object :arg \"car\")))\n        (util:safe-snapshot)\n        (slot-makunbound obj 'arg)))\n    (assert-that (bknr.datastore:all-store-objects)\n                 (has-length 0))\n    (with-test-store (:dir dir)\n      (let ((obj (car (bknr.datastore:all-store-objects))))\n        (is-false (slot-boundp obj 'arg))))))\n\n#+nil ;; Test is broken, bug in core bknr.datastore\n(test saves-transactions-for-makunbound-on-regular-persistent-class\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (let ((obj (make-instance 'my-object-old :arg \"car\")))\n        (util:safe-snapshot)\n        (slot-makunbound obj 'arg)))\n    (assert-that (bknr.datastore:all-store-objects)\n                 (has-length 0))\n    (with-test-store (:dir dir)\n      (let ((obj (car (bknr.datastore:all-store-objects))))\n        (is-false (slot-boundp obj 'arg))))))\n\n(defclass obj-with-index (store-object)\n  ((arg :initarg :arg\n        :accessor arg\n        :index-type unique-index\n        :index-initargs (:test #'equal)\n        :index-reader obj-for-arg))\n  (:metaclass permissive-persistent-class))\n\n(test* indices-still-works ()\n  (let ((obj (make-instance 'obj-with-index :arg \"foo\")))\n    (is (eql obj (obj-for-arg \"foo\")))\n    (setf (arg obj) \"bar\")\n    (is (eql obj (obj-for-arg \"bar\")))\n    (is-false (obj-for-arg \"foo\"))))\n\n(defvar *counter*)\n\n(defun incf-counter ()\n  (incf *counter*))\n\n(defclass obj-with-initform (store-object)\n  ((arg :initarg :arg\n        :initform (incf-counter)\n        :accessor arg))\n  (:metaclass permissive-persistent-class))\n\n(test* obj-with-initform ()\n  (let ((*counter* 1))\n    (let ((obj (make-instance 'obj-with-initform)))\n      (is (eql (arg obj) 2)))))\n\n(defclass old-obj-with-initform (store-object)\n  ((arg :initarg :arg\n        :initform (progn\n                    (incf *counter*))\n        :accessor arg))\n  (:metaclass permissive-persistent-class))\n\n(test* old-obj-with-initform ()\n  \"This is just making sure that the standard persistent-class only\ncalls the initform once. (We were seeing a bug with the\npermissive-persistent-class showing the initform twice.)\"\n  (let ((*counter* 1))\n    (let ((obj (make-instance 'old-obj-with-initform)))\n      (is (> (arg obj) 1))\n      (is (= (arg obj) 2)))))\n\n(test* unbound-slot-crashes-as-expected ()\n  (let ((obj (make-instance 'my-object)))\n    (signals unbound-slot\n      (arg obj))))\n\n(defclass obj-with-nil-initform (store-object)\n  ((arg :initarg :arg\n        :initform nil\n        :accessor arg)\n   (arg2 :initarg :arg2\n         :initform nil\n         :accessor arg2))\n  (:metaclass permissive-persistent-class))\n\n(test* obj-with-nil-initform ()\n  (let ((obj (make-instance 'obj-with-nil-initform)))\n    (is (eql nil (arg obj))))\n  (let ((obj (make-instance 'obj-with-nil-initform\n                            :arg \"foo\")))\n    (is (equal \"foo\" (arg obj))))\n  (let ((obj (make-instance 'obj-with-nil-initform\n                            :arg2 \"foo\")))\n    (is (eql nil (arg obj))))\n  (let ((obj (make-instance 'obj-with-nil-initform\n                            :arg \"foo\")))\n    (is (eql nil (arg2 obj)))))\n\n\n(test slot-location-for-destroyed-p\n  (let ((destroyedp-slot\n          (loop for slot in (closer-mop:class-slots (find-class 'obj-with-nil-initform))\n                if (eql (closer-mop:slot-definition-name slot) 'bknr.indices::destroyed-p)\n                  return slot)))\n    (is-true destroyedp-slot)\n    (is (eql 0 (closer-mop:slot-definition-location destroyedp-slot)))))\n\n(test slot-location-for-destroyed-p-for-store-object\n  (let ((destroyedp-slot\n          (loop for slot in (closer-mop:class-slots (find-class 'store-object))\n                if (eql (closer-mop:slot-definition-name slot) 'bknr.indices::destroyed-p)\n                  return slot)))\n    (is-true destroyedp-slot)\n    (is (eql 0 (closer-mop:slot-definition-location destroyedp-slot)))))\n\n(test instance-type\n  (with-test-store ()\n   (assert-that (make-instance 'obj-with-nil-initform)\n                (has-typep 'base-permissive-persistent-object))))\n"
  },
  {
    "path": "src/util/store/test-raft-state-http.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-raft-state-http\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/testing\n                #:with-fake-request)\n  (:import-from #:util/store/raft-state-http\n                #:%response\n                #:raft-state-request-p)\n  (:import-from #:cl-mock\n                #:answer)\n  (:import-from #:bknr.cluster/server\n                #:leaderp))\n(in-package :util/store/test-raft-state-http)\n\n(util/fiveam:def-suite)\n\n(test raft-state-request-p\n  (with-fake-request ()\n    (is-true (raft-state-request-p\n              (make-instance 'hunchentoot:request :uri \"/raft-state/foobar\"\n                             :headers-in nil)))\n    (is-false (raft-state-request-p\n               (make-instance 'hunchentoot:request :uri \"/sdfsdfds\"\n                                                   :headers-in nil)))))\n\n(test response-happy-path\n  (cl-mock:with-mocks ()\n    (let ((bknr.datastore:*store* 'my-store))\n      (answer (leaderp 'my-store) t)\n      (is (equal \"leader\" (%response)))))\n  (cl-mock:with-mocks ()\n    (with-fake-request ()\n     (let ((bknr.datastore:*store* 'my-store))\n       (answer (leaderp 'my-store) nil)\n       (is (equal \"other\" (%response)))\n       (is (eql 400 (hunchentoot:return-code hunchentoot:*reply*)))))))\n"
  },
  {
    "path": "src/util/store/test-simple-object-snapshot.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-simple-object-snapshot\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/store/simple-object-snapshot\n                #:snapshot-slot-value\n                #:simple-object-snapshot)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object\n                #:class-instances)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that))\n(in-package :util/store/test-simple-object-snapshot)\n\n(util/fiveam:def-suite)\n\n(def-fixture state (&key dir)\n  (with-test-store (:dir dir)\n    (&body)))\n\n(defclass some-object (store-object)\n  ((slot1 :initarg :slot1)\n   (slot2 :initarg :slot2))\n  (:metaclass persistent-class))\n\n(defmethod bknr.datastore:make-object-snapshot ((self some-object))\n  (make-instance 'simple-object-snapshot\n                 :object self\n                 :except-slots '(slot2)))\n\n(test happy-snapshot-restore-path\n  (tmpdir:with-tmpdir (dir)\n    (with-fixture state (:dir dir)\n      (make-instance 'some-object\n                     :slot1 \"foo\"\n                     :slot2 \"bar\")\n      (bknr.datastore:snapshot))\n    (with-fixture state (:dir dir)\n      (assert-that (class-instances 'some-object)\n                   (has-length 1))\n      (let ((obj (car (class-instances 'some-object))))\n        (is (equal \"foo\" (slot-value obj 'slot1)))\n        (is (equal \"bar\" (slot-value obj 'slot2)))))))\n\n(defclass some-object-2 (store-object)\n  ((slot1 :initarg :slot1)\n   (slot2 :initarg :slot2))\n  (:metaclass persistent-class))\n\n(defmethod snapshot-slot-value ((self some-object-2) (slot (eql 'slot2)))\n  (format nil \"~a world\"\n          (slot-value self 'slot2)))\n\n(defmethod snapshot-slot-value ((self some-object-2) (slot (eql 'slot1)))\n  \"This should never get invoked!\"\n  (format nil \"~a world\"\n          (slot-value self 'slot2)))\n\n(defmethod bknr.datastore:make-object-snapshot ((self some-object-2))\n  (make-instance 'simple-object-snapshot\n                 :object self\n                 :except-slots '(slot2)))\n\n(test manually-copy-over-a-slot\n  (tmpdir:with-tmpdir (dir)\n    (with-fixture state (:dir dir)\n      (make-instance 'some-object-2\n                     :slot1 \"foo\"\n                     :slot2 \"bar\")\n      (bknr.datastore:snapshot))\n    (with-fixture state (:dir dir)\n      (assert-that (class-instances 'some-object-2)\n                   (has-length 1))\n      (let ((obj (car (class-instances 'some-object-2))))\n        (is (equal \"foo\" (slot-value obj 'slot1)))\n        (is (equal \"bar world\" (slot-value obj 'slot2)))))))\n"
  },
  {
    "path": "src/util/store/test-single.lisp",
    "content": "(defpackage :util/tests/test-single\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/single\n                #:deserialize\n                #:serialize)\n  (:import-from #:fiveam-matchers/core\n                #:equal-to\n                #:has-typep\n                #:assert-that)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-single)\n\n\n(util/fiveam:def-suite)\n\n(defclass dummy-1 ()\n  ((foo :initarg :foo)\n   (bar :initarg :bar)))\n\n(def-fixture state ()\n  (uiop:with-temporary-file (:stream s :direction :io :element-type '(unsigned-byte 8))\n    (&body)))\n\n(test preconditions ()\n  (with-fixture state ()\n    (let ((obj (make-instance 'dummy-1 :foo \"bar\")))\n      (finishes\n        (serialize obj s))\n      (file-position s 0)\n      (let ((result (deserialize s)))\n        (assert-that result\n                     (has-typep 'dummy-1))\n        (assert-that (slot-value result 'foo)\n                     (equal-to \"bar\"))\n        (is-false (slot-boundp result 'bar))))))\n"
  },
  {
    "path": "src/util/store/test-slot-subsystem.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-slot-subsystem\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:util/store/slot-subsystem\n                #:externalized-slot-value))\n(in-package :util/store/test-slot-subsystem)\n\n\n(util/fiveam:def-suite)\n\n(defclass fake-class (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(def-fixture state ()\n  (with-test-store ()\n    (let ((obj (make-instance 'fake-class)))\n      (&body))))\n\n(test preconditions\n  (with-fixture state ()\n    (pass)))\n\n(test set-and-get-value\n  (with-fixture state ()\n    (setf (externalized-slot-value obj 'foobar) 2)\n    (is (eql 2 (externalized-slot-value obj 'foobar)))))\n\n(test save-and-restore-from-transactions\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (let ((obj (make-instance 'fake-class)))\n        (setf (externalized-slot-value obj 'foobar) 3)\n        (Setf (externalized-slot-value obj 'car) 4)))\n    (with-test-store (:dir dir)\n      (let ((obj (first (bknr.datastore:class-instances 'fake-class))))\n        (is (eql 3 (externalized-slot-value obj 'foobar)))\n        (is (eql 4 (externalized-slot-value obj 'car)))))))\n\n(test save-and-restore-from-snapshot\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (let ((obj (make-instance 'fake-class)))\n        (setf (externalized-slot-value obj 'foobar) 3)\n        (Setf (externalized-slot-value obj 'car) 4))\n      (util:safe-snapshot))\n    (with-test-store (:dir dir)\n      (let ((obj (first (bknr.datastore:class-instances 'fake-class))))\n        (is (eql 3 (externalized-slot-value obj 'foobar)))\n        (is (eql 4 (externalized-slot-value obj 'car)))))))\n\n"
  },
  {
    "path": "src/util/store/test-store-version.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-store-version\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store\n                #:*object-store*\n                #:with-test-store)\n  (:import-from #:util/store-version\n                #:version-subsystem\n                #:*store-version*)\n  (:import-from #:bknr.datastore\n                #:store-directory)\n  (:import-from #:bknr.datastore\n                #:*store*)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:bknr.datastore\n                #:*store*)\n  (:import-from #:bknr.datastore\n                #:mp-store)\n  (:import-from #:bknr.datastore\n                #:close-store)\n  (:import-from #:bknr.datastore\n                #:snapshot)\n  (:import-from #:bknr.datastore\n                #:*store*)\n  (:import-from #:bknr.datastore\n                #:store-subsystems)\n  (:import-from #:util/store/store-version\n                #:*snapshot-store-version*))\n(in-package :util/store/test-store-version)\n\n\n(util/fiveam:def-suite)\n\n(def-easy-macro with-store (dir &fn fn)\n  (when (boundp 'bknr.datastore:*store*)\n    (error \"Don't run this test in a live program with an existing store\"))\n\n  (let ((*store* nil))\n    (unwind-protect\n         (fn)\n      (close-store))))\n\n\n(def-fixture state ()\n  (tmpdir:with-tmpdir (dir)\n    (labels ((open-store ()\n             (make-instance 'mp-store\n                            :directory dir\n                            :subsystems (list\n                                         (make-instance 'version-subsystem))))\n             (version-file ()\n               (ensure-directories-exist\n                (path:catfile dir \"current/version-subsystem-snapshot\")))\n             (read-version ()\n               (cond\n                 ((path:-e (version-file))\n                          (parse-integer (uiop:read-file-string (version-file))))\n                 (t\n                  :does-not-exist)))\n             (get-subsystem ()\n               (loop for subsystem in (store-subsystems *store*)\n                     if (typep subsystem 'version-subsystem)\n                       return subsystem))\n             (write-version (ver)\n               (with-open-file (s (version-file)\n                                  :direction :output\n                                  :if-exists :supersede\n                                  :if-does-not-exist :create)\n                 (format s \"~a\" ver))))\n     (&body))))\n\n(test simple-open ()\n  (with-fixture state ()\n    (with-store (dir)\n      (open-store)\n      (is (eql :does-not-exist\n               (read-version)))\n      (bknr.datastore:snapshot)\n      (is (eql *store-version*\n               (read-version)))\n      (is (eql *store-version*\n               *snapshot-store-version*)))))\n\n(test open-existing-store ()\n  (with-fixture state ()\n    (open-store)\n    (bknr.datastore:snapshot)\n    (close-store)\n    (write-version 2)\n    (with-store (dir)\n      (open-store)\n      (is (eql 2 (read-version)))\n      (is (eql 2 *snapshot-store-version*)))))\n\n(test snapshot-updates-store-version\n  (with-fixture state ()\n    (with-store (dir)\n      (open-store)\n      (is (eql :does-not-exist (read-version)))\n      (let ((*snapshot-store-version* 199990))\n        (snapshot))\n      (is (eql 199990 (read-version))))))\n"
  },
  {
    "path": "src/util/store/test-store.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-store\n  (:use :cl)\n  (:import-from #:bknr.datastore\n                #:close-subsystem\n                #:*store*\n                #:all-store-objects\n                #:deftransaction\n                #:delete-object\n                #:persistent-class\n                #:store-object\n                #:truncate-log)\n  (:import-from #:bknr.indices\n                #:base-indexed-object\n                #:object-destroyed-p\n                #:hash-index)\n  (:import-from #:fiveam-matchers/errors\n                #:signals-error-matching\n                #:error-with-string-matching)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:fail\n                #:finishes\n                #:is\n                #:is-false\n                #:is-true\n                #:pass\n                #:signals\n                #:test)\n  (:import-from #:local-time\n                #:timestamp=)\n  (:import-from #:util/store/permissive-persistent-class\n                #:permissive-persistent-class)\n  (:import-from #:util/store/store\n                #:object-neighbors\n                #:*ensure-directories-cache*\n                #:*object-store*\n                #:*snapshot-hooks*\n                #:def-store-local\n                #:dispatch-snapshot-hooks\n                #:fast-ensure-directories-exist\n                #:find-any-refs\n                #:location-for-oid\n                #:object-store\n                #:parse-timetag\n                #:safe-mp-store\n                #:verify-old-class\n                #:with-snapshot-lock\n                #:with-test-store)\n  (:import-from #:fiveam-matchers/lists\n                #:contains-in-any-order)\n  (:import-from #:util/benchmark\n                #:def-benchmark))\n(in-package :util/store/test-store)\n\n\n(util/fiveam:def-suite)\n\n(test object-store\n  (tmpdir:with-tmpdir (tmp)\n    (let ((*object-store* (namestring (path:catfile tmp \"dummy/\"))))\n      (is (pathnamep (object-store)))\n      (is (path:-d (object-store)))))\n  (tmpdir:with-tmpdir (tmp)\n    (let ((*object-store* (namestring (path:catfile tmp \"dummy\"))))\n      (is (pathnamep (object-store)))\n      (is (path:-d (object-store)))))\n\n  #+nil ;; bad test that modifies global state\n  (let ((*object-store* \"~/zoidberg/bar/\"))\n    (is (equal #P \"~/zoidberg/bar/\" (object-store)))\n    (is (path:-d (object-store)))))\n\n\n#+lispworks\n(test parse-timetag\n  (is\n   (timestamp=\n    (local-time:parse-timestring \"2021-10-09T06:00:00\")\n    (parse-timetag \"20211009T060000\"))))\n\n(defclass dummy-class (store-object)\n  ((name :initarg :name\n         :index-type hash-index\n         :index-initargs (:test #'equal)\n         :index-reader dummy-class-for-name))\n  (:metaclass persistent-class))\n\n\n(test with-test-store\n  (with-test-store ()\n    (make-instance 'dummy-class)\n    (make-instance 'dummy-class :name \"bleh\")\n    (is (eql 2 (length (all-store-objects))))\n    (is (eql 1 (length (dummy-class-for-name \"bleh\")))))\n  (is (eql 0 (length (all-store-objects))))\n  (is (eql 0 (length (dummy-class-for-name \"bleh\"))))\n  (with-test-store ()\n    (is (eql 0 (length (all-store-objects)))))\n    (is (eql 0 (length (dummy-class-for-name \"bleh\")))))\n\n\n(defclass xyz (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(defclass abc (store-object)\n  ((ref :initarg :ref))\n  (:metaclass persistent-class))\n\n(def-fixture refs ()\n  (with-test-store ()\n    (&body)))\n\n(test create-object-and-reference\n  (with-test-store ()\n    (let* ((xyz (make-instance 'xyz))\n           (xyz-2 (make-instance 'xyz))\n           (abc (make-instance 'abc :ref xyz)))\n      (is (equal (list xyz abc) (find-any-refs (list xyz)))))\n    (let* ((xyz (make-instance 'xyz))\n           (abc (make-instance 'abc :ref (list xyz))))\n      (is (equal (list xyz abc) (find-any-refs (list xyz)))))\n    (let* ((xyz (make-instance 'xyz))\n           (abc (make-instance 'abc :ref (make-array 1 :initial-contents (list xyz)))))\n      (is (equal (list xyz abc) (find-any-refs (list xyz)))))))\n\n(test transitive-refs\n  (with-test-store ()\n    (let* ((xyz (make-instance 'xyz))\n           (abc-1 (make-instance 'abc :ref xyz))\n           (abc (make-instance 'abc :ref abc-1)))\n      (is (equal (list xyz abc-1 abc) (find-any-refs (list xyz))))))\n  (with-test-store ()\n    (let* ((xyz (make-instance 'xyz))\n           (abc-1 (make-instance 'abc :ref xyz))\n           (abc (make-instance 'abc :ref abc-1)))\n      (is (equal (list xyz abc-1 abc) (find-any-refs (list abc-1 xyz)))))))\n\n(test nested-in-list-refs\n  (with-test-store ()\n    (let* ((xyz (make-instance 'xyz))\n           (abc-1 (make-instance 'abc :ref xyz))\n           (abc (make-instance 'abc :ref (list 1 2 abc-1))))\n      (is (equal (list xyz abc-1 abc) (find-any-refs (list xyz))))))\n  (with-test-store ()\n    (let* ((xyz (make-instance 'xyz))\n           (abc-1 (make-instance 'abc :ref xyz))\n           (abc (make-instance 'abc :ref (list xyz 2 abc-1))))\n      (is (equal (list xyz abc-1 abc) (find-any-refs (list xyz))))))\n  (with-test-store ()\n    (let* ((xyz (make-instance 'xyz))\n           (abc-1 (make-instance 'abc :ref xyz))\n           (abc (make-instance 'abc :ref (list abc-1 2 xyz))))\n      (is (equal (list xyz abc-1 abc) (find-any-refs (list xyz)))))))\n\n(def-store-local *store-local* (make-hash-table)\n  \"A test cache\")\n\n\n(test store-local\n  (let (one two)\n    (with-test-store ()\n      (setf one *store-local*)\n      (is (eql one *store-local*)))\n    (with-test-store ()\n      (setf two *store-local*)\n      (is (not (eql one two))))))\n\n(test setf-store-local\n  (let (one two)\n    (with-test-store ()\n      (setf *store-local* 'foo)\n      (is (eql *store-local* 'foo)))\n    (with-test-store ()\n      (is (not (eql 'foo *store-local*)))\n      (setf *store-local* 'bar)\n      (is (eql 'bar *store-local*)))))\n\n(test store-locals-get-cleared-on-snapshot-load\n  (with-test-store ()\n    (setf *store-local* 'foo)\n    (is (eql 'foo *store-local*))\n    (loop for subsystem in (bknr.datastore::store-subsystems *store*)\n          do (close-subsystem bknr.datastore:*store* subsystem))\n    (is (equalp (make-hash-table) *store-local*))\n    (setf *store-local* 'bar)\n    (is (eql 'bar *store-local*))))\n\n(defclass test-instance (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(defmethod bknr.datastore:initialize-transient-instance :after ((self test-instance))\n  (log:info \"Calling initialize transi...\")\n  (setf (gethash self *store-local*) t))\n\n(test ensure-initialize-transient-instance-is-reset\n  (with-test-store ()\n    (finishes (make-instance 'test-instance))\n    (is (eql 1 (hash-table-count *store-local*))))\n  (with-test-store ()\n    (is (eql 0 (hash-table-count *store-local*)))))\n\n(test 3-ensure-initialize-transient-instance-is-not-lost\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (log:info \"Subsystems: ~S\" (bknr.datastore::store-subsystems *store*))\n      (make-instance 'test-instance)\n      (is (eql 1 (hash-table-count *store-local*))))\n    (with-test-store (:dir dir)\n      (is (eql 1 (hash-table-count *store-local*))))))\n\n(test ensure-initialize-transient-instance-is-not-lost-even-with-snapshot\n  (tmpdir:with-tmpdir (dir)\n    (with-test-store (:dir dir)\n      (log:info \"Subsystems: ~S\" (bknr.datastore::store-subsystems *store*))\n      (make-instance 'test-instance)\n      (is (eql 1 (hash-table-count *store-local*)))\n      (util:safe-snapshot \"...\"))\n    (with-test-store (:dir dir)\n      (is (eql 1 (hash-table-count *store-local*))))))\n\n\n(test store-local-with-a-store\n  (signals unbound-variable *store-local*))\n\n(defun fix-for-windows (x)\n  (cond\n   ((uiop:os-windows-p)\n    (str:replace-all \"/\" \"\\\\\" x))\n   (t\n    x)))\n\n(test location-for-oid--ensure-directory-is-writeable\n  (with-test-store ()\n    (with-open-file (file (location-for-oid  #P\"foo-bar/\" (mongoid:oid))\n                          :direction :output)\n      (write-string \"hello\" file))\n    (pass)))\n\n(test location-for-oid/expected-pathname\n  (with-test-store ()\n    (let ((pathname (location-for-oid #P \"foo-bar/\" #(#xab #xbc #x01 #x23\n                                                      #xab #xbc #x01 #x23\n                                                      #xab #xbc #x01 #x23))))\n      (is (str:ends-with-p\n           (fix-for-windows \"foo-bar/ab/bc/0123abbc0123abbc0123\")\n           (namestring pathname)))\n      ;; Ensure absolute\n      (is (not (str:starts-with-p \"foo-bar\" (namestring pathname)))))))\n\n(test location-for-oid/expected-relative-path\n  (with-test-store ()\n    (let ((pathname (nth-value\n                     1\n                     (location-for-oid #P \"foo-bar/\" #(#xab #xbc #x01 #x23\n                                                       #xab #xbc #x01 #x23\n                                                       #xab #xbc #x01 #x23)))))\n      (is (string=\n           (fix-for-windows \"foo-bar/ab/bc/0123abbc0123abbc0123\")\n           (namestring pathname))))))\n\n(test location-for-oid/with-suffix\n  (with-test-store ()\n    (let ((pathname (location-for-oid #P \"foo-bar/\" #(#xab #xbc #x01 #x23\n                                                      #xab #xbc #x01 #x23\n                                                      #xab #xbc #x01 #x23)\n                                      :suffix \"metadata\")))\n      (is (str:ends-with-p\n           (fix-for-windows \"foo-bar/ab/bc/0123abbc0123abbc0123-metadata\")\n           (namestring pathname))))))\n\n(test cant-access-deleted-object-slots\n  \"This is checking some changes we have in bknr.datastore. In particular,\n making sure our slot-value-using-class is correctly wired. (See\n indexed-class.lisp or D5784)\"\n  (with-test-store ()\n    (let ((obj (make-instance 'dummy-class :name \"foo\")))\n      (is (equal \"foo\" (slot-value obj 'name)))\n      (is-false (object-destroyed-p obj))\n      (delete-object obj)\n      (is (typep obj 'base-indexed-object))\n      (is-true (object-destroyed-p obj))\n      (block nil\n        (handler-case\n            (slot-value obj 'name)\n          (error (e)\n            (is (str:containsp \"Can not get slot NAME of destroyed\"\n                               (format nil \"~a\" e)))\n            (return nil)))\n        (fail \"expected error\")))))\n\n(test dispatch-snapshot-hooks\n  (with-test-store ()\n   (let (ret)\n     (flet ((hook (store dir)\n              (push dir ret)))\n       (let ((*snapshot-hooks* (list #'hook)))\n         (dispatch-snapshot-hooks :foo))\n       (is (equal (list :foo) ret))))))\n\n(test safe-snapshot-happy-path\n  (with-test-store ()\n    (let ((*snapshot-hooks* nil))\n      (util:safe-snapshot \"dummy\"))))\n\n(test |body get's called :/|\n  (let ((calledp nil))\n    (with-test-store ()\n      (setf calledp t))\n    (is-true calledp)))\n\n(test snapshot-lock\n  (with-test-store ()\n    (is\n     (eql :foo\n      (with-snapshot-lock (*store*)\n        :foo)))))\n\n(test can-compare-deleted-objects\n  (with-test-store ()\n    (let ((obj-1 (make-instance 'dummy-class))\n          (obj-2 (make-instance 'dummy-class)))\n      (flet ((checks ()\n               (is (eql :less (fset:compare obj-1 obj-2)))\n               (is (eql :equal (fset:compare obj-1 obj-1)))\n               (is (eql :greater (fset:compare obj-2 obj-1)))))\n        (checks)\n        (delete-object obj-1)\n        (checks)))))\n\n(defvar *state* 0)\n\n(deftransaction dummy-transaction (arg)\n  (incf *state*))\n\n(test fast-ensure-directories-exist\n  (tmpdir:with-tmpdir (dir)\n    (let ((path (path:catdir dir \"foo/bar/car\")))\n      (is (equalp path (fast-ensure-directories-exist path)))\n      (is (path:-d (path:catdir dir \"foo/bar/\")))\n      (is-true (fset:@ *ensure-directories-cache* (pathname-directory path)))\n      (is (equalp path (fast-ensure-directories-exist path)))\n\n      ;; Make sure we're not hitting the disk.. so let's delete the\n      ;; directory and try again.\n      #+lispworks\n      (progn\n        (lw:delete-directory (path:catdir dir \"foo/bar/\"))\n        (is (equalp path (fast-ensure-directories-exist path)))\n        ;; We shouldn't have actually hit the disk to do this\n        (is-false (path:-d (path:catdir dir \"foo/bar/\")))))))\n\n(defclass dummy-class-2 ()\n  ((a) (b) (c)))\n\n\n(test verify-old-class-with-persistent-class\n  (finishes\n   (verify-old-class 'dummy-class-2\n                     '((a) (b) (c))\n                     'persistent-class))\n  (signals-error-matching ()\n   (verify-old-class 'dummy-class-2\n                     '((a) (b) (cl-user::c))\n                     'persistent-class)\n   (error-with-string-matching\n    \"missing slots: (C)\"))\n  (finishes\n    (verify-old-class 'dummy-class-2\n                      '((a) (cl-user::b) (c))\n                      'permissive-persistent-class))\n  (signals-error-matching ()\n    (verify-old-class 'dummy-class-2\n                      '((cl-user::b) (c))\n                      'permissive-persistent-class)\n    (error-with-string-matching\n     \"missing slots: (A)\")))\n\n\n(test object-neighbors-for-list\n  (assert-that\n   (object-neighbors '(1 2 3))\n   (contains-in-any-order\n    1 2 3)))\n\n"
  },
  {
    "path": "src/util/store/test-summing-index.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-summing-index\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:bknr.datastore\n                #:delete-object\n                #:store-object\n                #:persistent-class)\n  (:import-from #:util/store/summing-index\n                #:summing-index)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:bknr.indices\n                #:index-reinitialize\n                #:index-clear\n                #:index-get))\n(in-package :util/store/test-summing-index)\n\n\n(util/fiveam:def-suite)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *summing-index*\n    (make-instance 'summing-index\n                   ;; Note that #' syntax doesn't quite work here\n                   ;; unless we also move the defgeneric to\n                   ;; eval-when. Not worth it.\n                   :key '%company\n                   :value '%size)))\n\n(defclass fake-image (store-object)\n  ((company :initarg :company\n            :initform nil\n            :accessor %company)\n   (size :initarg :size\n         :initform nil\n         :accessor %size))\n  (:metaclass persistent-class)\n  (:class-indices\n   (summing-index\n    :index *summing-index*\n    :slots (company size))))\n\n(def-fixture state ()\n  (with-test-store ()\n    (&body)))\n\n(test simple-creation\n  (with-fixture state ()\n    (finishes\n     (make-instance 'fake-image\n                    :company :company-1\n                    :size 20))\n    (is (eql 20 (index-get *summing-index* :company-1)))\n    (make-instance 'fake-image\n                   :company :company-1\n                   :size 10)\n    (is (eql 30 (index-get *summing-index* :company-1)))\n    (index-clear *summing-index*)\n    (is (eql 0 (index-get *summing-index* :company-1)))))\n\n(test index-reinialize\n  (with-fixture state ()\n    (make-instance 'fake-image\n                   :company :company-1\n                   :size 20)\n    (let ((other-index (make-instance 'summing-index\n                                      :key #'%company\n                                      :value #'%size)))\n      (index-reinitialize other-index *summing-index*))\n    (is (eql 20 (index-get *summing-index* :company-1)))))\n\n(test when-key-is-NIL\n  (with-fixture state ()\n    (make-instance 'fake-image\n                   :company :company-1\n                   :size 20)\n    (finishes\n      (make-instance 'fake-image\n                     :company :company-1))\n    (finishes\n      (make-instance 'fake-image\n                     :size 30))))\n\n(test deleting-objects\n  (with-fixture state ()\n    (let ((im1 (make-instance 'fake-image\n                              :company :company-1\n                              :size 20))\n          (im2 (make-instance 'fake-image\n                              :company :company-1\n                              :size 10)))\n      (is (eql 30 (index-get *summing-index* :company-1)))\n      (delete-object im2)\n      (is (eql 20 (index-get *summing-index* :company-1)))\n      (delete-object im1))))\n\n(test can-delete-slots-with-NIL\n  (with-fixture state ()\n    (make-instance 'fake-image\n                   :company :company-1\n                   :size 20)\n    (finishes\n      (delete-object (make-instance 'fake-image\n                                    :company :company-1)))\n    (finishes\n      (delete-object (make-instance 'fake-image\n                                    :size 30)))))\n\n\n\n(test setting-key\n  (with-fixture state ()\n    (finishes\n     (make-instance 'fake-image\n                    :company :company-1\n                    :size 20))\n    (is (eql 20 (index-get *summing-index* :company-1)))\n    (let ((im2 (make-instance 'fake-image\n                              :company :company-1\n                              :size 10)))\n      (is (eql 30 (index-get *summing-index* :company-1)))\n      (setf (%company im2) :company-2)\n      (is (eql 20 (index-get *summing-index* :company-1))))))\n\n\n(test setting-value\n  (with-fixture state ()\n    (finishes\n     (make-instance 'fake-image\n                    :company :company-1\n                    :size 20))\n    (is (eql 20 (index-get *summing-index* :company-1)))\n    (let ((im2 (make-instance 'fake-image\n                   :company :company-1\n                   :size 10)))\n      (is (eql 30 (index-get *summing-index* :company-1)))\n      (setf (%size im2) 5)\n      (is (eql 25 (index-get *summing-index* :company-1))))))\n"
  },
  {
    "path": "src/util/store/test-sync.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-sync\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/store/store\n                #:with-test-store)\n  (:import-from #:bknr.datastore\n                #:persistent-class\n                #:store-object)\n  (:import-from #:core/rpc/rpc\n                #:call-rpc)\n  (:import-from #:util/store/sync\n                #:sync-sha-request)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length))\n(in-package :util/store/test-sync)\n\n(util/fiveam:def-suite)\n\n(defclass foobar (store-object)\n  ()\n  (:metaclass persistent-class))\n\n(test sync-happy-path\n  (with-test-store ()\n    (make-instance 'foobar)\n    (let ((output (call-rpc (make-instance 'sync-sha-request))))\n      (assert-that output\n                   (has-length 64)))))\n"
  },
  {
    "path": "src/util/store/test-validate.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/test-validate\n  (:use :cl)\n  (:import-from #:bknr.indices\n                #:index-remove\n                #:index-values)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:import-from #:it.bese.fiveam\n                #:def-fixture\n                #:finishes\n                #:is-false\n                #:signals\n                #:test\n                #:with-fixture)\n  (:import-from #:util/store/fset-index\n                #:fset-set-index\n                #:corrupted-index)\n  (:import-from #:util/store/store\n                #:defindex\n                #:with-test-store)\n  (:import-from #:util/store/validate\n                #:validate-indices)\n  (:import-from #:bknr.datastore\n                #:store-object\n                #:persistent-class))\n(in-package :util/store/test-validate)\n\n\n(util/fiveam:def-suite)\n\n\n(defclass my-object (store-object)\n  ((key :initarg :key\n        :index-type fset-set-index\n        :index-var +my-object-index+\n        :accessor key))\n  (:metaclass persistent-class))\n\n(def-fixture validate-indices ()\n  (with-test-store ()\n    (&body)))\n\n(test validate-indices-happy-path\n  (with-fixture validate-indices ()\n    (finishes (validate-indices))\n    (let ((obj (make-instance 'my-object :key \"foo\")))\n      (finishes (validate-indices))\n      (index-remove +my-object-index+ obj)\n      (let ((auto-restart:*global-enable-auto-retries-p* nil))\n        (signals corrupted-index\n          (validate-indices)))\n\n      (is-false (index-values +my-object-index+))\n      (validate-indices :fix-by-default t)\n      (assert-that (index-values +my-object-index+)\n                   (contains obj)))))\n"
  },
  {
    "path": "src/util/store/util.store.asd",
    "content": "(defsystem :util.store\n  :serial t\n  :depends-on (:bknr.datastore\n               :util.misc\n               :trivial-features\n               :util/cron\n               :file-lock\n               :tmpdir\n               :str\n               (:feature (:and :lispworks :linux) :bknr.cluster)\n               :easy-macros\n               :auto-restart\n               :util.threading\n               :util/copy-file\n               :util/events\n               :local-time\n               :atomics\n               :util.store/encodable\n               :alexandria\n               :fset\n               :cl-mongo-id\n               :copy-directory\n               :ironclad/core\n               (:feature (:not :lispworks) :util/fake-fli)\n               :cffi\n               :cl-cron)\n  :components ((:file \"elb-store\" :if-feature (:and :lispworks :linux))\n               (:file \"clone-logs-store\" :if-feature (:and :lispworks :linux))\n               (:file \"store\")\n               (:file \"store-version\")\n               (:file \"object-id\")\n               (:file \"single\")\n               (:file \"migrations\")\n               (:file \"delayed-accessors\")\n               (:file \"slot-subsystem\")\n               (:file \"simple-object-snapshot\")\n               (:file \"checksums\")\n               (:file \"export\")\n               (:file \"fset\")\n               (:file \"fset-index\")\n               (:file \"summing-index\")\n               (:file \"permissive-persistent-class\")\n               (:file \"store-migrations\")\n               (:file \"validate\")))\n\n(defsystem :util.store/sync\n  :serial t\n  :depends-on (:core.rpc\n               :util.store)\n  :components ((:file \"sync\")))\n\n(defsystem :util.store/encodable\n  :serial t\n  :depends-on (:bknr.datastore)\n  :components ((:file \"encodable\")))\n\n(defsystem :util.store/tests\n  :serial t\n  :depends-on (:util.store\n               :fiveam-matchers\n               :util.testing\n               :util.store/aws\n               :util.store/raft-state\n               :util.store/sync\n               :util.store/benchmarks\n               :util/fiveam)\n  :components ((:file \"test-store-version\")\n               (:file \"test-store\")\n               (:file \"test-slot-subsystem\")\n               (:file \"test-objectid\")\n               (:file \"test-single\")\n               (:file \"test-migrations\")\n               (:file \"test-simple-object-snapshot\")\n               (:file \"test-delayed-accessors\")\n               (:file \"test-raft-state-http\" :if-feature (:and :lispworks :linux))\n               (:file \"test-checksums\")\n               (:file \"test-fset\")\n               (:file \"test-validate\")\n               (:file \"test-sync\")\n               (:file \"test-fset-index\")\n               (:file \"test-encodable\")\n               (:file \"test-summing-index\")\n               (:file \"test-permissive-persistent-class\")))\n\n(defsystem :util.store/aws\n  :serial t\n  :depends-on (:util.store\n               :util/request)\n  :components ((:file \"aws-store\")))\n\n(defsystem :util.store/raft-state\n  :serial t\n  :depends-on (:util.store\n               :hunchentoot)\n  :components ((:file \"raft-state-http\" :if-feature (:and :lispworks :linux))))\n\n\n(defsystem :util.store/benchmarks\n  :serial t\n  :depends-on (:util.store\n               :util/benchmark)\n  :components ((:file \"benchmarks\")))\n"
  },
  {
    "path": "src/util/store/validate.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/store/validate\n  (:use #:cl)\n  (:import-from #:bknr.datastore\n                #:class-instances\n                #:base-store-object\n                #:%id-cache\n                #:store-objects-with-class\n                #:store-object-id)\n  (:import-from #:alexandria\n                #:when-let)\n  (:import-from #:util/misc/lists\n                #:head)\n  (:import-from #:bknr.indices\n                #:base-indexed-object\n                #:object-destroyed-p-v2\n                #:index-direct-slot-definition-index\n                #:slot-index)\n  (:import-from #:util/store/store\n                #:validate-indices ;; To make it easier to deploy\n                #:fix-the-index\n                #:validate-index-values)\n  (:import-from #:util/store/fset-index\n                #:abstract-fset-index)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/store/validate)\n\n(defvar *recent-validation-errors* nil)\n\n(defun build-hash-table (objects slot &key test unique-index-p)\n  (let ((hash-table (make-hash-table :test test)))\n    (loop for obj in objects\n          if (slot-boundp obj slot)\n            do\n               (let ((slot-value (slot-value obj slot)))\n                 ;;(assert (not (eql :png slot-value)))\n                 (cond\n                   (unique-index-p\n                    (when slot-value\n                      (setf (gethash slot-value hash-table)\n                            obj)))\n                   (t\n                    (when slot-value\n                     (push obj (gethash slot-value hash-table)))))))\n    hash-table))\n\n(defun find-effective-slot (class slot-name)\n  (loop for slot in (closer-mop:class-slots class)\n        if (eql slot-name (closer-mop:slot-definition-name slot))\n          return slot\n        finally (error \"could not find slot\")))\n\n(defun find-direct-slot (class slot-name)\n  (loop for slot in (closer-mop:class-direct-slots class)\n        if (eql slot-name (closer-mop:slot-definition-name slot))\n          return slot\n        finally (error \"could not find slot\")))\n\n(defun atomp (x)\n  (or\n   (null x)\n   (not (listp x))))\n\n(defun hash-set-difference (left right &key test)\n  \"Similar to set-\"\n  (let ((table (make-hash-table :test test)))\n    (dolist (x left)\n      (setf (gethash x table) t))\n    (dolist (x right)\n      (remhash x table))\n    (alexandria:hash-table-keys table)))\n\n(defun unordered-equalp (list1 list2 &key (test #'eql))\n  (declare (optimize (debug 3)))\n  (cond\n    ((and (atomp list1)\n          (atomp list2))\n     (equal list1 list2))\n    ((or (atomp list1)\n         (atomp list2))\n     ;; this could also be the case that one of the lists are nil, but\n     ;; it's correct to send false in that case.\n     nil)\n    (t\n     (let ((diff-1 (hash-set-difference list1 list2 :test test))\n           (diff-2 (hash-set-difference list2 list1 :test test)))\n       (values\n        (and\n         (eql nil diff-1)\n         (eql nil diff-2))\n        diff-1\n        diff-2)))))\n\n\n(define-condition hash-tables-keys-dont-match (error)\n  ((keys1 :initarg :keys1)\n   (keys2 :initarg :keys2)\n   (diff-1 :initarg :diff-1)\n   (diff-2 :initarg :diff-2))\n  (:report (lambda (e out)\n             (with-slots (diff-1 diff-2) e\n               (format out \"The two hash tables have different keys. ~%Missing keys in new-hash-table: ~s~% Missing keys in old hash-table: ~s~%\"\n                       (head diff-1 100)\n                       (head diff-2 100))))))\n\n(defun assert-hash-tables= (h1 h2)\n  (unless (eql (hash-table-test h1)\n               (hash-table-test h2))\n    (error \"the two hash tables have different test functions\"))\n  (let ((keys1 (alexandria:hash-table-keys h1))\n        (keys2 (alexandria:hash-table-keys h2)))\n    (multiple-value-bind (res diff-1 diff-2)\n        (unordered-equalp\n         keys1\n         keys2\n         :test (hash-table-test h1))\n      (unless res\n        (error 'hash-tables-keys-dont-match\n               :keys1 keys1\n               :keys2 keys2\n               :diff-1 diff-1\n               :diff-2 diff-2))))\n  (loop for k being the hash-keys of h1\n        for value1 = (gethash k h1)\n        for value2 = (gethash k h2)\n        do\n           (multiple-value-bind (res diff-1 diff-2)\n               (unordered-equalp  value1 value2)\n             (unless res\n               (error \"the two hash tables have different values for key ~a~%Missing values in new hash-table:~S~%Missing values in old hash-table:~s\" k diff-1 diff-2)))))\n\n\n(defmethod validate-index-values :around (index all-elts slot-name)\n  (handler-bind ((error (lambda (e)\n                          (push e *recent-validation-errors*)\n                          (setf *recent-validation-errors*\n                                (head *recent-validation-errors* 10)))))\n    (call-next-method)))\n\n(defmethod validate-index-values ((index slot-index) all-elts\n                                  slot-name)\n  (let* ((unique-index-p (typep index 'bknr.indices:unique-index)))\n    (let* ((hash-table (bknr.indices::slot-index-hash-table index))\n           (test (hash-table-test hash-table))\n           (new-hash-table (build-hash-table all-elts\n                                             slot-name\n                                             :test test\n                                             :unique-index-p unique-index-p)))\n      (format t \"Total number of elements: ~d~%\" (length all-elts))\n      (restart-case\n          (progn\n            (assert-hash-tables= hash-table\n                                 new-hash-table))\n        (fix-the-index ()\n          (setf (bknr.indices::slot-index-hash-table index)\n                new-hash-table))))))\n\n(auto-restart:with-auto-restart (:retries 4)\n  (defun validate-class-index (class-name slot-name)\n    (declare (optimize (debug 3)))\n    (format t \"Testing ~a, ~a~%\" class-name slot-name)\n    (restart-case\n        (let* ((class (find-class class-name))\n               (direct-slot (find-direct-slot class slot-name))\n               (slot (find-effective-slot class slot-name))\n               (indices (bknr.indices::index-effective-slot-definition-indices slot)))\n          (unless (and\n                   ;; If there's only one index, and that index use a\n                   ;; def-index, then we don't need to validate it.\n                   (= (length indices) 1)\n                   (index-direct-slot-definition-index direct-slot))\n           (dolist (index indices)\n             (let ((all-elts (class-instances class-name)))\n               (handler-bind ((error (lambda (e)\n                                       (declare (ignore e))\n                                       (format t \"Errors while processing index for ~a ~a ~a~%\" class slot indices))))\n                 (restart-case\n                     (validate-index-values index all-elts slot-name)\n                   (continue-testing-other-indices ()\n                     (values))))))))\n      (retry--validate-class-index ()\n        (validate-class-index class-name slot-name)))))\n\n(defun all-store-objects-in-memory (&key full)\n  (flet ((make-sorted (x)\n           (sort (copy-list x) #'< :key #'store-object-id)))\n    (let ((from-bknr (make-sorted (bknr.datastore:all-store-objects))))\n      (cond\n        ((null full)\n         from-bknr)\n        (t\n         #-lispworks\n         from-bknr\n         #+lispworks\n         (let ((rest nil))\n           (hcl:sweep-all-objects\n            (lambda (obj)\n              (when (and\n                     (typep obj 'store-object)\n                     (not\n                      ;; Under certain circumstances (it looks like when I\n                      ;; update a class and there are deleted objects in\n                      ;; memory? Not sure), object-destroyed-p can fail on\n                      ;; actually destroyed objects. If that happens wrap\n                      ;; this next part in an ignore-errors. I don't want\n                      ;; to keep it by default since that's risky when\n                      ;; going about rewriting existing indices.\n                      (bknr.datastore::object-destroyed-p obj))\n                     (ignore-errors\n                      (store-object-id obj)))\n                (push obj rest)))\n            t)\n           (let ((sorted (make-sorted rest)))\n             (restart-case\n                 (progn\n                   (unless (equal sorted from-bknr)\n                     (error \"The objects in memory and bknr index is not in sync, ~a vs ~a objects\"\n                            (length sorted)\n                            (length from-bknr)))\n                   sorted)\n               (return-the-list-from-memory ()\n                 sorted)\n               (return-the-list-from-bknr ()\n                 from-bknr))))        )))))\n\n\n(defun fast-remove-duplicates (list)\n  (let ((hash-table (make-hash-table)))\n    (loop for x in list\n          do (setf (gethash x hash-table) t))\n    (a:hash-table-keys hash-table)))\n\n(defun validate-indices (&key (full nil) (fix-by-default nil))\n  (flet ((work ()\n           (let* ((objects (all-store-objects-in-memory :full full))\n                  (classes (fast-remove-duplicates\n                            (loop for class in (fast-remove-duplicates (mapcar 'class-of objects))\n                                  append (closer-mop:class-precedence-list class)))))\n             (log:info \"Got ~a objects and ~a classes\"\n                       (length objects)\n                       (length classes))\n             (loop for class in classes\n                   if (and\n                       (not (eql class (find-class 'base-indexed-object)))\n                       (not (eql class (find-class 'base-store-object))))\n                   do\n                      (loop for direct-slot in (closer-mop:class-direct-slots class)\n                            for slot-name = (closer-mop:slot-definition-name direct-slot)\n                            for slot = (find-effective-slot class slot-name)\n                            if (and\n                                (not (eql '%id-cache slot-name))                                \n                                (not (eql 'object-destroyed-p-v2 slot-name))\n                                (or\n                                 (bknr.indices::index-direct-slot-definition-index direct-slot)\n                                 (bknr.indices::index-direct-slot-definition-index-type direct-slot)))\n                              if (not (eql 'bknr.datastore::id slot-name))\n                                do\n                                   (let ((indices (bknr.indices::index-effective-slot-definition-indices slot)))\n                                     (assert indices)\n                                     (validate-class-index (class-name class)\n                                                           slot-name))))\n             t)))\n    (cond\n      (fix-by-default\n       (handler-bind ((error (lambda (e)\n                               (when-let ((restart (find-restart 'fix-the-index)))\n                                 ;; Note: log:info will not show up\n                                 ;; in Capistrano output.\n                                 (format t \"Calling restart for ~a~%\" e)\n                                 (invoke-restart restart)))))\n         (work)))\n      (t\n       (work)))))\n"
  },
  {
    "path": "src/util/symbol-detector.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/symbol-detector\n  (:use #:cl)\n  (:export\n   #:detect))\n(in-package :util/symbol-detector)\n\n(defun fix-content (content)\n  (let* ((content (str:replace-all \"#_\" \"__\" content) #| java |#)\n         (content (str:replace-all \"#:\" \"__\" content))\n         (content (str:replace-all \":\" \"_\" content)))\n    content))\n\n(defun detect (&key filename content)\n  (let* ((result (make-hash-table))\n         (package (find-package \"CL\"))\n         (readtable *readtable*)\n         (content (or\n                   content\n                   (uiop:read-file-string filename))))\n    (labels ((process-symbols (expr)\n               (cond\n                 ((symbolp expr)\n                  (unless (or\n                           (eql package (symbol-package expr))\n                           (str:containsp \"_\" (symbol-name expr))\n                           (member expr\n                                   '(markup/markup::make-escaped\n                                     markup/markup::make-merge-tag\n                                     markup/markup::make-toplevel-node\n                                     markup/markup::make-xml-tag\n                                     uiop:define-package\n                                     system::bq-list\n                                     system::bq-append\n                                     system::bq-list*)))\n                    (setf (gethash (symbol-package expr) result)\n                          (list*\n                           expr\n                           (gethash (symbol-package expr) result)))))\n                 ((consp expr)\n                  ;;(format t \"processing: ~S~%\" expr)\n                  (process-symbols (car expr))\n                  (process-symbols (cdr expr))))))\n\n      (let ((stream (make-string-input-stream content)))\n        (loop for expr = (let ((*package* package)\n                               (*readtable* readtable))\n                           (read stream nil nil))\n              while expr\n              if (member (car expr)\n                         '(defpackage uiop:define-package))\n                do\n                   (log:info \"using package ~s\" (second expr))\n                   (setf package (find-package (second expr)))\n                   (process-symbols (get-external-symbols package))\n                   (setf stream (make-string-input-stream\n                                 (fix-content\n                                  (uiop:slurp-input-stream 'string stream))))\n              if (or\n                  (member (car expr)\n                          '(named-readtables:in-readtable\n                            named-readtables:in-readtable markup:syntax))\n                  (string-equal\n                   \"markup_enable-reader\" (string-downcase (symbol-name (car expr))))\n                  (string-equal\n                   \"named-readtables_in-readtable\" (string-downcase (symbol-name (car expr)))))\n                do\n                   (setf readtable (named-readtables::ensure-readtable\n                                    (let ((rt (second expr)))\n                                      (cond\n                                        ((string-equal \"markup_syntax\" (string rt))\n                                         'markup:syntax)\n                                        (t\n                                         'markup:syntax)))))\n              else\n                do\n                   (process-symbols expr)))\n      (values\n       (loop for package being the hash-keys of result\n             if (and package\n                     (not (equal \"KEYWORD\" (package-name package)))\n                     (not (equal \"COMMON-LISP\" (package-name package))))\n             collect (cons package (remove-duplicates (gethash package result))))\n       package))))\n\n;; (detect \"/home/arnold/builds/web/src/screenshotbot/login/common.lisp\")\n\n(defun get-external-symbols (package)\n  (let ((res))\n    (do-external-symbols (sym package)\n      (push sym res))\n    (sort res #'string<)))\n\n(defun generate-defpackage (filename)\n  (with-output-to-string (*standard-output*)\n    (multiple-value-bind (package-map package)\n        (detect :filename filename)\n      (format t \"(defpackage :~a\n  (:use :cl)~%\" (string-downcase (package-name package)))\n      (loop for (package . symbols) in (sort (loop for (p . x) in package-map\n                                                   if p\n                                                     collect (cons p x))\n                                             #'string<\n                                             :key (lambda (x) (package-name (car x))))\n            unless (or\n                    (eql (find-package :keyword)\n                         package)\n                    (eql (find-package :cl)\n                         package))\n            do\n               (format t \"(:import-from #:~a\" (string-downcase (package-name package)))\n               (loop for symbol in (sort symbols #'string< :key #'symbol-name)\n                     do (format t \"~%   #:~a\" (string-downcase (symbol-name symbol))))\n               (format t \")~%\"))\n      (cond\n        ((get-external-symbols package)\n         (format t \"(:export \")\n         (dolist (sym (get-external-symbols package))\n           (format t \"~%   #:~a\" (string-downcase (symbol-name sym))))\n         (format t \")\"))\n        (t\n         (file-position *standard-output* (1- (file-position *standard-output*)))))\n      #+lispworks\n      (alexandria:when-let ((nicknames (hcl:package-local-nicknames package)))\n        (setf nicknames (sort (copy-seq nicknames) #'string< :key #'car))\n        (format t \"~%\")\n        (format t \"  (:local-nicknames \")\n        (format t \"~a\"\n                (str:join #\\Newline\n                          (loop for (key . package) in nicknames\n                                collect (format nil \"(#:~a #:~a)\" (string-downcase key)\n                                                (string-downcase (package-name package))))))\n        (format t \")\")))\n\n    (format t \")~%\")))\n\n;; (format t \"~a\" (generate-defpackage \"/home/arnold/builds/web/src/screenshotbot/login/forgot-password.lisp\"))\n"
  },
  {
    "path": "src/util/test-file.txt",
    "content": "hello-world\n"
  },
  {
    "path": "src/util/testing/test-testing.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/testing/test-testing\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/testing\n                #:with-global-binding)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/testing/test-testing)\n\n\n(util/fiveam:def-suite)\n\n(defvar *var*)\n(defvar *var2* :foo)\n\n(test with-global-binding ()\n  (with-global-binding ((*var* :bar))\n    (is (eql :bar *var*)))\n  (is (not (boundp '*var*)))\n  (with-global-binding ((*var2* :bar))\n    (is (eql :bar *var2*)))\n  (is (eql :foo *var2*))\n  (with-global-binding ((*var* 1)\n                        (*var2* 2))\n    (is (eql 1 *var*))\n    (is (eql 2 *var2*)))\n  (is (eql :foo *var2*))\n  (is (not (boundp '*var*))))\n"
  },
  {
    "path": "src/util/testing/testing.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/testing\n  (:use #:cl)\n  (:import-from #:hunchentoot\n                #:acceptor)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:util/misc\n                #:with-global-binding)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:with-fake-request\n   #:in-test-p\n   #:screenshot-static-page\n   #:with-local-acceptor\n   #:with-global-binding\n   #:define-screenshot-test-init\n   #:clean-up-static-web-outputs))\n(in-package :util/testing)\n\n(defvar *in-test-p* nil)\n\n(defun in-test-p ()\n  *in-test-p*)\n\n(defclass custom-request (auth:authenticated-request)\n  ((additional-post-params :initform nil)))\n\n(defmethod (setf hunchentoot:post-parameter) (val (request custom-request) key)\n  (with-slots (additional-post-params) request\n    (setf (a:assoc-value additional-post-params key :test 'equal)\n          val)))\n\n(defmethod hunchentoot:post-parameters ((request custom-request))\n  (with-slots (additional-post-params) request\n   (append\n    additional-post-params\n    (call-next-method))))\n\n(defclass test-acceptor (nibble:nibble-acceptor-mixin\n                         hex:base-acceptor\n                         hex:acceptor-with-plugins)\n  ())\n\n(defmacro with-fake-request ((&key  (acceptor '(quote test-acceptor)) (host \"localhost\")\n                                (script-name \"/\")\n                                (params)) &body body)\n `(let* ((hunchentoot::*hunchentoot-stream*)\n         (hunchentoot:*catch-errors-p* nil)\n         (hunchentoot:*acceptor* (make-instance ,acceptor))\n         (hunchentoot:*reply* (make-instance 'hunchentoot:reply))\n         (hunchentoot:*request* (make-instance 'custom-request\n                                               :acceptor hunchentoot:*acceptor*\n                                               :headers-in (list (cons :host ,host))\n                                               :content-stream nil\n                                               :uri ,script-name\n                                               :method :get\n                                               :server-protocol :https\n                                               :remote-addr \"127.0.0.1\")))\n    ,@body))\n\n\n(defvar *prepared* (make-hash-table))\n\n(defun maybe-prepare-screenshot-assets (name dir\n                                        &key static-assets\n                                          generated-css-assets)\n  (or\n   (gethash name *prepared*)\n   (setf\n    (gethash name *prepared*)\n    (prog1\n        t\n      (copy-directory:copy\n       static-assets\n       (path:catdir dir \"assets/\"))\n      (copy-directory:copy\n       (asdf:system-relative-pathname :core.ui\n                                      \"assets/fonts/\")\n       (path:catdir dir \"assets/fonts/\"))\n      (flet ((copy-css (target output)\n               (asdf:compile-system target)\n               (uiop:copy-file\n                (car (asdf:output-files 'asdf:compile-op target))\n                (path:catfile dir output))))\n        (loop for (key . val) in generated-css-assets do\n          (copy-css key val)))))))\n\n(defvar *screenshot-test-inits* nil)\n\n(defmacro define-screenshot-test-init (project &key static-assets generated-css-assets)\n  `(let ((static-assets (asdf:system-relative-pathname ',project ,static-assets)))\n       (setf (assoc-value *screenshot-test-inits* ',project :test #'string-equal)\n             (lambda ()\n               (maybe-prepare-screenshot-assets\n                ',project\n                (static-web-output-dir ',project)\n                :static-assets static-assets\n                :generated-css-assets ,generated-css-assets)))))\n\n\n(let ((cache (make-hash-table :test #'equal)))\n  (defun static-web-output-dir (project)\n    (util/misc:or-setf\n     (gethash project cache)\n     (asdf:system-relative-pathname project \"static-web-output/\"))))\n\n(defun screenshot-static-page (project name content)\n  (let ((output (static-web-output-dir project)))\n    (let ((output-file (path:catfile output (format nil \"~a/index.html\" name))))\n      (ensure-directories-exist output-file)\n      (funcall\n       (assoc-value *screenshot-test-inits* project :test #'string-equal))\n      (with-open-file (file output-file\n                            :direction :output\n                            :external-format :utf-8\n                            :if-exists :supersede)\n        (let ((content (typecase content\n                         (string content)\n                         (t\n                          (cl-mock:with-mocks ()\n                            (cl-mock:if-called 'nibble:nibble-url (lambda (nibble)\n                                                                    \"#\"))\n                            (markup:write-html content))))))\n          (write-string content file))\n        (fiveam:pass \"Screenshot written\")))))\n\n(defun clean-up-static-web-outputs ()\n  (dolist (project (mapcar #'car *screenshot-test-inits*)) do\n    (let ((dir (asdf:system-relative-pathname project \"static-web-output/\")))\n      (when (path:-d dir)\n        (uiop:delete-directory-tree\n         dir\n         :validate (lambda (x)\n                     (declare (ignore x))\n                     t))))))\n\n\n\n(defmacro with-local-acceptor ((host &key prepare-acceptor-callback (acceptor (gensym))) (name &rest args) &body body)\n  \"Create a debuggable single threaded acceptor for running tests\"\n  `(flet ((fn (,host ,acceptor)\n            (declare (ignorable ,acceptor))\n            ,@body))\n     (call-with-local-acceptor #'fn ,prepare-acceptor-callback ,name (list ,@args))))\n\n(defmethod safe-start ((acceptor acceptor) &key listen-callback)\n  ;; this is copied from hunchentoot:start except for listen-callback\n  (setf (hunchentoot::acceptor-shutdown-p acceptor) nil)\n  (let ((taskmaster (hunchentoot::acceptor-taskmaster acceptor)))\n    (setf (hunchentoot:taskmaster-acceptor taskmaster) acceptor)\n    (hunchentoot:start-listening acceptor)\n    (funcall listen-callback)\n    (hunchentoot:execute-acceptor taskmaster))\n  acceptor)\n\n(defclass debuggable-taskmaster (hunchentoot:single-threaded-taskmaster)\n  ())\n\n(defun call-with-local-acceptor (fn prepare-acceptor-callback name args)\n  (let ((port (util/random-port:random-port)))\n    (let ((acceptor (apply #'make-instance name\n                           :message-log-destination *standard-output*\n                           :port port\n                           :taskmaster (make-instance 'debuggable-taskmaster)\n                           args)))\n     (when prepare-acceptor-callback\n       (funcall prepare-acceptor-callback acceptor))\n     (let ((lock (bt:make-lock))\n           (acceptor-ready (bt:make-condition-variable))\n           (thread-crashed? nil)\n           (catch-errors? hunchentoot:*catch-errors-p*))\n       (bt:with-lock-held (lock)\n         (let ((thread (bt:make-thread\n                        (lambda ()\n                         (restart-case\n                             (handler-bind ((error (lambda (e)\n                                                     (when catch-errors? ;; in non interactive mode\n                                                       (trivial-backtrace:print-backtrace e)\n                                                       (invoke-restart 'exit-acceptor)))))\n                               (let ((hunchentoot:*catch-errors-p* nil))\n                                 (safe-start\n                                  acceptor\n                                  :listen-callback\n                                  (lambda ()\n                                    (bt:with-lock-held (lock)\n                                      (bt:condition-notify acceptor-ready))))))\n                           (exit-acceptor ()\n                             nil))))))\n           (bt:condition-wait acceptor-ready lock)\n           (unwind-protect\n                (funcall fn (format nil \"http://127.0.0.1:~d\" port)\n                         acceptor)\n             (hunchentoot:stop acceptor)\n             (bt:join-thread thread)\n             (when thread-crashed?\n               (error \"The acceptor thread crashed, see logs\")))))))))\n"
  },
  {
    "path": "src/util/testing/util.testing.asd",
    "content": "(defsystem :util.testing\n  :serial t\n  :depends-on (:fiveam\n               :cl-mock\n               :lparallel\n               :nibble\n               :util/random-port\n               :easy-macros\n               :auth)\n  :components ((:file \"testing\")))\n\n(defsystem :util.testing/tests\n  :depends-on (:util.testing\n               :util/fiveam\n               :fiveam)\n  :components ((:file \"test-testing\")))\n"
  },
  {
    "path": "src/util/tests/test-asdf.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-asdf\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util\n                #:%asdf-relpath))\n(in-package :util/tests/test-asdf)\n\n(util/fiveam:def-suite)\n\n(test asdf-relpath-for-file-that-exists\n  (let ((res (%asdf-relpath\n               (asdf:system-relative-pathname\n                :util\n                \"fixture/file.txt\"))))\n    (is\n     (equalp\n      (make-pathname\n       :host nil\n       :device nil\n       :version nil\n       :defaults #P\"src/util/fixture/file.txt\")\n      res))))\n\n(test asdf-relpath-for-file-that-does-not-exists\n  (let ((res (%asdf-relpath\n               (asdf:system-relative-pathname\n                :util\n                \"fixture/file-does-not-exist.txt\"))))\n    (is\n     (equalp\n      (make-pathname\n       :host nil\n       :device nil\n       :version nil\n       :defaults #P\"src/util/fixture/file-does-not-exist.txt\")\n      res))))\n\n\n"
  },
  {
    "path": "src/util/tests/test-benchmark.lisp",
    "content": "(defpackage :util/tests/test-benchmark\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/benchmark\n                #:format-time\n                #:*min-benchmark-time*\n                #:benchmark)\n  (:import-from #:easy-macros\n                #:def-easy-macro))\n(in-package :util/tests/test-benchmark)\n\n(util/fiveam:def-suite)\n\n(benchmark:def-benchmark foobar ()\n  (benchmark:measure\n    (sxhash \"foobar\")))\n\n(def-easy-macro make-benchmark (&fn fn)\n  (make-instance 'benchmark\n                 :name 'foo\n                 :impl (lambda ()\n                         (funcall fn))))\n\n(test simple-invocation ()\n  (let ((result\n          (benchmark:run\n           (make-benchmark ()\n             (let ((*min-benchmark-time* 1000))\n               (benchmark:measure\n                 (sxhash (concatenate 'string \"foo\" \"bar\"))\n                 nil))))))\n    (is-true result)\n    (is (numberp result))))\n\n(defun foobar ()\n  (benchmark:measure\n    ;;(log:info \"hello ~a\" (incf ctr))\n    (sxhash (concatenate 'string \"foo\" \"bar\"))\n    nil))\n\n(test run-existing-benchmark ()\n  (let ((*min-benchmark-time* 1000))\n    (is (numberp\n         (benchmark:run 'foobar)))))\n\n\n(test format-time ()\n  (is (equal \"   5ns\" (format-time 5)))\n  (is (equal \"   5.23us\" (format-time  5231)))\n  (is (equal \"  15.00us\" (format-time  15000)))\n  (is (equal \"   5.23ms\" (format-time  5231000)))\n  (is (equal \"   5.23s \" (format-time  5231000000))))\n"
  },
  {
    "path": "src/util/tests/test-bind-form.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util.test-bind-form\n  (:use #:cl\n        #:fiveam\n\t#:util/bind-form)\n  (:export))\n(in-package :util.test-bind-form)\n\n(named-readtables:in-readtable markup:syntax)\n\n(Def-suite* :util.test-bind-form)\n\n\n(test simple-rename\n  (is\n   (equal\n    (markup:write-html <input value= \"car\" name= \"foo{bar}\" >)\n    (markup:write-html\n     (let ((data (alexandria:plist-hash-table '(\"bar\" \"car\") :test 'equal)))\n       <bind-form data=data name= \"foo\">\n       <input name= \"foo{bar}\" >\n       </bind-form>)))))\n\n(test simple-rewrite-existing-arg\n  (is\n   (equal\n    (markup:write-html <input  name= \"foo{bar}\" value= \"car\">)\n    (markup:write-html\n     (let ((data (alexandria:plist-hash-table '(\"bar\" \"car\") :test 'equal)))\n       <bind-form data=data name= \"foo\">\n       <input name= \"foo{bar}\" value= \"duh\" >\n       </bind-form>)))))\n\n\n(test nested-rename\n  (let ((data (alexandria:plist-hash-table '(:bar \"car\"))))\n   (is\n    (markup:write-html <h3><input value= \"car\" name= \"foo{bar}\"  ></h3>)\n    (markup:write-html\n     <bind-form data=data name= \"foo\">\n     <h3><input name= \"foo{bar}\" ></h3>\n     </bind-form>))))\n"
  },
  {
    "path": "src/util/tests/test-cdn.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util.test-cdn\n  (:use :cl\n   :alexandria\n        :fiveam))\n(in-package :util.test-cdn)\n\n(def-suite* :util.test-cdn)\n\n(test should-be-set-up-by-default\n  (is (numberp util.cdn:*cdn-cache-key*)))\n"
  },
  {
    "path": "src/util/tests/test-cookies.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-cookies\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/cookies\n                #:set-cookie\n                #:get-cookie\n                #:cookies)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-cookies)\n\n\n(util/fiveam:def-suite)\n\n(defclass fake-request ()\n  ((cookies :initarg :cookies\n            :reader cookies)))\n\n\n(defmethod hunchentoot:cookies-in ((request fake-request))\n  (cookies request))\n\n(def-fixture state ()\n  (let ((self (make-instance 'cookies\n                             :request (make-instance 'fake-request\n                                                     :cookies `((\"zoid\" . \"berg\")))\n                             :reply (make-instance 'hunchentoot:reply)\n                             :host \"screenshotbot.io:2343\"\n                             :proto \"https\")))\n    (&body)))\n\n(test preconditions\n  (with-fixture state ()\n    (is-false (get-cookie self \"foo\"))\n    (is (equal \"berg\" (get-cookie self \"zoid\")))))\n\n(test write-cookie\n  (with-fixture state ()\n    (set-cookie self \"foo\" \"bar\")\n    (pass)))\n"
  },
  {
    "path": "src/util/tests/test-copy-file.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-copy-file\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/copy-file\n                #:copy-file-fast)\n  (:import-from #:tmpdir\n                #:with-tmpdir)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-copy-file)\n\n\n(util/fiveam:def-suite)\n\n(defun write-file (src text)\n  (with-open-file (src src\n                       :direction :output)\n    (format src text)))\n\n(def-fixture state ()\n  (with-tmpdir (dir)\n    (let ((src (path:catfile dir \"foo.txt\"))\n          (dest (path:catfile dir \"bar.txt\")))\n      (write-file src \"hello world\")\n      (&body))))\n\n#+(and linux lispworks)\n(defun inode (file)\n  (sys:file-stat-inode\n   (sys:get-file-stat file)))\n\n(test simple-copy-file\n  (with-fixture state ()\n    (copy-file-fast src dest)\n    (is (equal \"hello world\"\n               (uiop:read-file-string dest)))\n    #+(and linux lispworks)\n    (is (eql (inode src) (inode dest)))))\n\n(test if-dest-exists-we-fail\n  ;; This guards against the possibility that both src and dest point\n  ;; to the same file. If that happens, then we'll rewrite the file,\n  ;; and during the rewrite the file might be in a bad state.\n  #-windows\n  (with-fixture state ()\n    (write-file dest \"foo\")\n    (signals error\n      (copy-file-fast src dest))\n    (is (equal \"foo\" (uiop:read-file-string dest)))))\n\n\n;; This test depends on a very specific directory layout, so it's\n;; disable for now.\n#+nil\n(test copy-file-to-temp\n  (uiop:with-temporary-file (:pathname p :directory \"/data/arnold/\")\n    (delete-file p)\n    (finishes\n     (copy-file-fast (asdf:system-relative-pathname :util \"copy-file.lisp\")\n                     p))))\n"
  },
  {
    "path": "src/util/tests/test-digests.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-digests\n  (:use #:cl\n        #:fiveam\n        #:util/digests)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-digests)\n\n\n(util/fiveam:def-suite)\n\n(defvar *fixture* (asdf:system-relative-pathname :util \"tests/rose.png\"))\n\n(test sha256\n  (is (equalp #(196 177 210 34 213 217 70 217 240 191 165\n                243 62 242 252 170 162 253 198 228 161 32 225\n                213 222 199 176 176 220 223 116 116)\n              (sha256-file *fixture*))))\n\n(test md5\n  (is (equalp #(145 139 141 103 193 215 81 218 8 73 97 248 57 178 154 224)\n              (md5-file *fixture*))))\n"
  },
  {
    "path": "src/util/tests/test-disk-size.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-disk-size\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/disk-size\n                #:free-space))\n(in-package :util/tests/test-disk-size)\n\n(util/fiveam:def-suite)\n\n(test free-space ()\n  (uiop:with-temporary-file (:pathname p)\n    (is (> (free-space p) 0))))\n"
  },
  {
    "path": "src/util/tests/test-events.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-events\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/events\n                #:format-ts\n                #:event-extras\n                #:event-name\n                #:flush-counter-events\n                #:*counter-events*\n                #:push-counter-event\n                #:with-tracing\n                #:event-engine\n                #:with-event\n                #:*events*\n                #:flush-events\n                #:event\n                #:insert-events\n                #:with-db\n                #:push-event\n                #:db-engine)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as)\n  (:import-from #:core/installation/installation\n                #:installation-domain\n                #:*installation*)\n  (:import-from #:fiveam-matchers/lists\n                #:has-item\n                #:contains)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-events)\n\n(util/fiveam:def-suite)\n\n(defclass fake-installation ()\n  ((event-engine :initarg :event-engine\n                 :reader event-engine)\n   (domain :initform \"example.com\"\n           :reader installation-domain)))\n\n\n(def-fixture state ()\n  (uiop:with-temporary-file (:pathname pathname)\n   (cl-mock:with-mocks ()\n     (setf *events* nil)\n     (setf *counter-events* nil)\n     (let* ((*installation*\n              (make-instance 'fake-installation\n                             :event-engine\n                             (make-instance 'db-engine\n                                            :connection-spec\n                                            `(,(namestring pathname))\n                              :database-type :sqlite3))))\n       (with-db (db (event-engine *installation*))\n         (clsql:query \"create table event (name string, extras string, domain, string, hostname string, created_at datetime)\"\n                      :database db))\n       (&body)))))\n\n(defun row-count (db)\n  (caar\n   (clsql:query \"select count(*) from event\"\n                :database db)))\n\n(test insert-multiple-events\n  (with-fixture state ()\n    (with-db (db (event-engine *installation*))\n      (insert-events\n       (list\n        (make-instance 'event :name :foo)\n        (make-instance 'event :name :bar)\n        (make-instance 'event :name :car))\n       db)\n      (is (= 3\n             (row-count db))))))\n\n(test preconditions\n  (with-fixture state ()\n    (finishes\n      (push-event :foobar))\n    (with-db (db (event-engine *installation*))\n      (is (= 0 (row-count db))))\n    (flush-events (event-engine *installation*))\n    (with-db (db (event-engine *installation*))\n      (is (= 1 (row-count db))))\n\n    (flush-events (event-engine *installation*))\n    (with-db (db (event-engine *installation*))\n      (assert-that (row-count db)\n                   (described-as \"We shouln't insert again\"\n                     (is-equal-to 1))))))\n\n(test with-event\n  (with-fixture state ()\n    (let ((events))\n     (cl-mock:if-called 'push-event\n                        (lambda (name)\n                          (push name events)))\n      (with-event (:test-stuff)\n        (values))\n      (is (equal '(:test-stuff.success)\n                 events))\n      (setf events nil)\n      (signals error\n        (with-event (:test-stuff)\n          (error \"bad\")))\n      (is (equal '(:test-stuff.failure)\n                 events))\n      (is (eql 2 (with-event (:test-stuff)\n                   2))))))\n\n(test with-tracing-happy-path\n  (with-fixture state ()\n   (let ((events))\n     (cl-mock:if-called 'push-event\n                        (lambda (name &rest args)\n                          (push name events)))\n     (is (eql 'result\n              (with-tracing (:foobar)\n                'result)))\n     (assert-that events\n                  (has-length 1)))))\n\n(test with-tracing-happy-path-with-args\n  (with-fixture state ()\n   (let ((events))\n     (cl-mock:if-called 'push-event\n                        (lambda (name &rest args)\n                          (push (list* name args) events)))\n     (is (eql 'result\n              (with-tracing (:foobar :extra-tag 2)\n                'result)))\n     (assert-that events\n                  (has-length 1))\n     (assert-that events\n                  (contains\n                   (has-item :extra-tag))))))\n\n(test counter-events\n  (with-fixture state ()\n    (push-counter-event :foobar :value 3)\n    (assert-that *counter-events*\n                 (has-length 1))\n    (flush-counter-events)\n    (assert-that *counter-events*\n                 (has-length 0))\n    (assert-that *events*\n                 (has-length 1))))\n\n(test counter-events-get-added-up\n  (with-fixture state ()\n    (push-counter-event :foobar :value 3)\n    (push-counter-event :foobar :value 6)\n    (flush-counter-events)\n    (assert-that *counter-events*\n                 (has-length 0))\n    (let ((event (first *events*)))\n      (is (equal \"foobar\" (event-name event)))\n      (is (equal \"{\\\"value\\\":9}\" (event-extras event))))\n    (assert-that *events*\n                 (has-length 1))))\n\n(test format-ts-tests\n  (let ((time (local-time:parse-timestring \"2026-04-05T18:16:26.315579Z\")))\n    (is (equal\n         \"2026-04-05 18:16:26.315\"\n         (format-ts time))))\n  (let ((time (local-time:parse-timestring \"2026-04-05T18:16:26.015579Z\")))\n    (is (equal\n         \"2026-04-05 18:16:26.015\"\n         (format-ts time))))\n  (let ((time (local-time:parse-timestring \"2026-04-05T18:16:26.010579Z\")))\n    (is (equal\n         \"2026-04-05 18:16:26.010\"\n         (format-ts time))))\n    (let ((time (local-time:parse-timestring \"2026-04-05T18:16:26.00000Z\")))\n    (is (equal\n         \"2026-04-05 18:16:26.000\"\n         (format-ts time)))))\n"
  },
  {
    "path": "src/util/tests/test-fake-clingon.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-fake-clingon\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:clingon.options\n                #:make-option)\n  (:import-from #:util/fake-clingon\n                #:make-fake-clingon))\n(in-package :util/tests/test-fake-clingon)\n\n(util/fiveam:def-suite)\n\n(defun batch-args ()\n  (list\n   (make-option\n    :string\n    :long-name \"batch\"\n    :description \"sdfsdfds\"\n    :key :batch)\n   (make-option\n    :string\n    :long-name \"repo-url\"\n    :description \"dfdfd\"\n    :key :repo-url)\n   (make-option\n    :string\n    :long-name \"pull-request\"\n    :description \"xdfdfd\"\n    :key :pull-request)))\n\n(test simple-invocation\n  (let ((cmd (make-fake-clingon (batch-args)\n                                :repo-url \"foo\")))\n    (is (equal \"foo\" (clingon:getopt cmd :repo-url)))\n    (signals simple-error\n      (clingon:getopt cmd :foo))))\n"
  },
  {
    "path": "src/util/tests/test-fake-fli.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-fake-fli\n  (:use #:cl\n        #:fiveam)\n  (:local-nicknames (#:fake-fli #:util/fake-fli)))\n(in-package :util/tests/test-fake-fli)\n\n(util/fiveam:def-suite)\n\n\n\n(fake-fli:define-foreign-function strlen\n    ((s (:reference-pass :ef-mb-string)))\n  :result-type :size-t)\n\n(fake-fli:define-foreign-function (strlen-v2 \"strlen\")\n    ((s :lisp-simple-1d-array))\n  :result-type :size-t)\n\n(fake-fli:define-foreign-function memcpy\n    ((dest :lisp-simple-1d-array)\n     (src (:pointer :uint8))\n     (n :size-t))\n    :result-type :void)\n\n(fake-fli:define-foreign-function (memcpy-v2 \"memcpy\")\n    ((dest :lisp-simple-1d-array)\n     (src :lisp-simple-1d-array)\n     (n :size-t))\n  :result-type :void)\n\n(test simple-foreign-function\n  (is (equal 6 (strlen \"foobar\"))))\n\n(Test lisp-simple-1d-array\n  (let ((arr (make-array 10\n                         :element-type '(unsigned-byte 8)\n                         :initial-contents #(2 3 4 5 5 6 0 1 1 1))))\n    (dotimes (i 10)\n      (is (equal 6 (strlen-v2 arr))))))\n\n(test modify-lisp-simple-1d-array\n  (let ((arr (cffi:foreign-alloc\n              :uint8\n              :count 10\n              :initial-element 1))\n        (arr2 (make-array 10\n                          :element-type '(unsigned-byte 8)\n                          :initial-element 9)))\n    (setf (cffi:mem-aref arr :uint8 6) 0)\n    (memcpy arr2 arr 10)\n    (is (equalp #(1 1 1 1 1 1 0 1 1 1)\n                arr2))\n    (cffi:foreign-free arr)))\n\n(test modify-lisp-simple-1d-array-while-reading-from-it-too\n  (let ((arr (make-array 10\n                         :element-type '(unsigned-byte 8)\n                         :initial-element 1))\n        (arr2 (make-array 10\n                          :element-type '(unsigned-byte 8)\n                          :initial-element 9)))\n    (setf (aref arr  6) 0)\n    (memcpy-v2 arr2 arr 10)\n    (is (equalp #(1 1 1 1 1 1 0 1 1 1)\n                arr2))))\n\n(fake-fli:define-foreign-callable (dumb-stuff :result-type :void)\n    ((status :int))\n  (values))\n\n(test pointer-check\n  (is-true (fake-fli:null-pointer-p\n            (fake-fli:make-pointer :address 0)))\n  (is-false (fake-fli:null-pointer-p\n             (fake-fli:make-pointer :address 1))))\n\n(test get-ptr-for-callable\n  (signals error\n   (fake-fli:make-pointer :symbol-name 'does-not-exist!))\n  (let ((ptr (fake-fli:make-pointer :symbol-name 'dumb-stuff)))\n    (is-false (fake-fli:null-pointer-p ptr))))\n\n"
  },
  {
    "path": "src/util/tests/test-fiveam.lisp",
    "content": "(pkg:define-package :util/tests/test-fiveam\n    (:use #:cl\n          #:fiveam\n          #:alexandria)\n  (:import-from #:util/fiveam\n                #:def-suite-recursive\n                #:guess-suite-name)\n  (:import-from #:fiveam\n                #:get-test))\n\n(def-suite* :util/tests/test-fiveam)\n\n(test def-suite-recursive ()\n  (let ((fiveam::*toplevel-suites* nil)\n        (fiveam::*test* (make-hash-table :test #'eql)))\n    (setf (fiveam::get-test 'nil) (make-suite 'nil :description \"Global suite\"))\n    (def-suite-recursive :foo/bar/car)\n    (let ((test (get-test :foo/bar/car)))\n      (is (typep test 'fiveam::test-suite))\n      (is (not (null test)))\n      (def-suite-recursive :foo/bar/car)\n\n      (is (eql test (get-test :foo/bar/car))\n          \"Test-suite should not be recreated\")\n\n      (def-suite-recursive :foo/bar/dar)\n      (is (eql test (get-test :foo/bar/car))\n          \"Tests stored on parent test should not change\")\n      (def-suite-recursive :mar)\n      (is (eql test (get-test :foo/bar/car)))\n      (is (not (null (get-test :foo/bar))))\n      (is (null (get-test :car))))))\n\n(test package-to-suite-name\n  (let ((*package* (find-package :util/tests/test-fiveam)))\n    (is (eql :util/tests/test-fiveam\n             (guess-suite-name)))))\n\n(test def-suite-sets-*suite*\n  (let ((fiveam::*toplevel-suites* nil)\n        (fiveam::*suite* nil)\n        (fiveam::*test* (make-hash-table)))\n    (util/fiveam:def-suite)\n    (is (eql (fiveam::get-test :util/tests/test-fiveam)\n             fiveam::*suite*))\n    (pass)))\n"
  },
  {
    "path": "src/util/tests/test-fset.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-fset\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:serapeum\n                #:collecting)\n  (:import-from #:util/fset\n                #:do-reverse-set)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains))\n(in-package :util/tests/test-fset)\n\n(util/fiveam:def-suite)\n\n(test in-reverse-order\n  (is (eql :greater (fset:compare \"three\" \"two\")))\n  (let ((set (fset:convert 'fset:set (list 1 3 2))))\n    (assert-that\n     (collecting\n       (do-reverse-set (var set)\n         (collect var)))\n     (contains\n      3 2 1))))\n\n(test return-from-block\n  (let ((set (fset:convert 'fset:set (list 1 3 4 2 5))))\n    (is (eql\n         4\n         (do-reverse-set (var set)\n           (when (= 0 (mod var 2))\n             (return var)))))))\n"
  },
  {
    "path": "src/util/tests/test-hash-lock.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-hash-lock\n  (:use #:cl\n        #:util/hash-lock\n        #:fiveam)\n  (:import-from #:util/hash-lock\n                #:%hash-table)\n  (:import-from #:lparallel\n                #:force)\n  (:import-from #:lparallel.promise\n                #:future)\n  (:import-from #:lparallel.kernel\n                #:*debug-tasks-p*)\n  (:import-from #:lparallel.promise\n                #:promise)\n  (:import-from #:lparallel.kernel\n                #:wrap-error)\n  (:import-from #:lparallel.promise\n                #:fulfill)\n  (:import-from #:lparallel.promise\n                #:chain)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-hash-lock)\n\n(util/fiveam:def-suite)\n\n(defmacro with-mp (&body body)\n  #-buck\n  `(progn ,@body)\n  #+buck\n  `(mp:initialize-multiprocessing\n    \"test-thread\"\n    nil\n    (lambda ()\n      ,@body)))\n\n(test happy-path\n  (let ((hash-lock (make-instance 'hash-lock)))\n    (let (ret)\n      (with-hash-lock-held (2 hash-lock)\n        (setf ret t))\n      (is (eql t ret))))\n  (pass))\n\n(test stress-test\n  (with-mp\n   (let* ((times 1000)\n          (threads 8)\n          (lock (bt:make-lock))\n          (hash-lock (make-instance 'hash-lock))\n          (ref 0)\n          (threads (loop for x below threads\n                         collect\n                         (bt:make-thread\n                          (lambda ()\n                            (loop for i below times\n                                  do\n                                     (with-hash-lock-held ('foo hash-lock)\n                                       (setf ref (+ 10 ref)))))))))\n     (loop for th in threads\n           do (bt:join-thread th))\n     (is (eql 0 (hash-table-count (%hash-table hash-lock))))\n     (is (eql 80000 ref)))))\n\n(defvar *dummy* \"berg\")\n\n(test stress-test-with-equal\n  (with-mp\n   (let* ((times 100)\n          (threads 3)\n          (hash-lock (make-instance 'hash-lock :test #'equal))\n          (ref 0)\n          (threads (loop for x below threads\n                         collect\n                         (bt:make-thread\n                          (lambda ()\n                            (sleep 0.1)\n                            (loop for i below times\n                                  do\n                                     (with-hash-lock-held ((format nil \"zoid~a\" *dummy*) hash-lock)\n                                       (setf ref (+ 10 ref)))))))))\n     (loop for th in threads\n           do (bt:join-thread th))\n     (is (eql 3000 ref)))))\n\n(define-condition test-error (error)\n  ())\n\n(test unwind\n  (let ((hash-lock (make-instance 'hash-lock)))\n    (unwind-protect\n         (signals test-error\n          (with-hash-lock-held (t hash-lock)\n            (error 'test-error)))\n      (is (eql 0 (hash-table-count (%hash-table hash-lock)))))))\n\n(def-fixture lparallel ()\n  (let ((lparallel:*kernel* (lparallel:make-kernel 10))\n        (debug-tasks-p *debug-tasks-p* ))\n    (setf *debug-tasks-p* nil)\n    (unwind-protect\n         (&body)\n      (setf *debug-tasks-p* debug-tasks-p)\n      (lparallel:end-kernel :wait t))))\n"
  },
  {
    "path": "src/util/tests/test-health-check.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-health-check\n  (:use #:cl\n        #:fiveam\n        #:util/health-check)\n  (:import-from #:util/health-check\n                #:*checks*)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-health-check)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((*checks* nil))\n    (uiop:with-temporary-file (:pathname tmp-output\n                               :stream tmp)\n      (&body))))\n\n(test simple-definition\n  (with-fixture state ()\n    (def-health-check foo-bar ()\n      (values))\n    (is (eql 1 (length *checks*)))\n    (def-health-check car ()\n      (values))\n    (is (eql 2 (length *checks*)))\n    (def-health-check foo-bar ()\n      (values))\n    (is (eql 2 (length *checks*)))))\n\n(test call-health-checks\n  (with-fixture state ()\n    (def-health-check foo-bar ()\n      (values))\n    (is-true (run-health-checks :out tmp))\n    (def-health-check bad ()\n      (error \"what\"))\n    (multiple-value-bind (res failures) (run-health-checks :out tmp)\n      (is-false res)\n      (is (equal '(bad) failures)))))\n"
  },
  {
    "path": "src/util/tests/test-html2text.lisp",
    "content": ";; -*- coding: utf-8 -*-\n;; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-html2text\n  (:use #:cl\n        #:util/html2text\n        #:fiveam)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-html2text)\n\n(util/fiveam:def-suite)\n\n(named-readtables:in-readtable markup:syntax)\n\n(test simple-check ()\n  (is (equal \"hello world\"\n             (html2text <html><body>hello world</body></html>))))\n\n(test with-utf-8 ()\n      (is (equal \"hello हिन्दी,\"\n                 (html2text <html><body>hello हिन्दी,</body></html>))))\n"
  },
  {
    "path": "src/util/tests/test-hunchentoot-engine.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-hunchentoot-engine\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:hunchentoot\n                #:define-easy-handler)\n  (:import-from #:util/hunchentoot-engine\n                #:hunchentoot-engine)\n  (:import-from #:util/request\n                #:http-request)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:alexandria\n                #:assoc-value))\n(in-package :util/tests/test-hunchentoot-engine)\n\n\n(util/fiveam:def-suite)\n\n(define-easy-handler (test-handler :uri \"/dummy/test-h-e\" :acceptor-names '(foo)) ()\n  (setf (hunchentoot:header-out :x-blah)  \"22\")\n  \"foobar\")\n\n(define-easy-handler (test-handler-2 :uri \"/dummy/test-auth\" :acceptor-names '(foo)) ()\n  (multiple-value-bind (user pass) (hunchentoot:authorization)\n   (format nil \"~a/~a\"\n           user pass)))\n\n(define-easy-handler (test-content-handler\n                      :uri \"/dummy/test-content-handler\"\n                      :acceptor-names '(foo))\n    ()\n  (format nil \"~a\" (hunchentoot:raw-post-data :force-text t)))\n\n(def-fixture state ()\n  (let* ((acceptor (make-instance 'hunchentoot:easy-acceptor\n                                  :name 'foo))\n         (engine (make-instance 'hunchentoot-engine\n                                :acceptor acceptor)))\n    (&body)))\n\n(test simple-invocation ()\n  (with-fixture state ()\n    (assert-that\n     (http-request\n      \"/dummy/test-h-e\"\n      :want-string t\n      :engine engine)\n     (is-equal-to \"foobar\"))))\n\n(test code-and-headers ()\n  (with-fixture state ()\n    (multiple-value-bind (resp code headers)\n        (http-request\n         \"/dummy/test-h-e\"\n         :want-string t\n         :engine engine)\n      (assert-that\n       resp\n       (is-equal-to \"foobar\"))\n      (is (eql 200 code))\n      (is (equal \"22\" (assoc-value headers :x-blah))))))\n\n(test authentication\n  (with-fixture state ()\n    (let ((result (http-request\n                   \"/dummy/test-auth\"\n                   :want-string t\n                   :engine engine\n                   :basic-authorization '(\"foo\" \"bar\"))))\n      (is (equal \"foo/bar\" result)))))\n\n\n(test want-stream\n  (with-fixture state ()\n    (let ((result (http-request\n                   \"/dummy/test-h-e\"\n                   :want-stream t\n                   :engine engine)))\n      (let ((sequence (make-array 6)))\n        (read-sequence sequence result)\n        (is (equalp\n             \"foobar\"\n             sequence))))))\n\n\n(test want-stream-binary\n  (with-fixture state ()\n    (let ((result (http-request\n                   \"/dummy/test-h-e\"\n                   :want-stream t\n                   :force-binary t\n                   :engine engine)))\n      (let ((sequence (make-array 6)))\n        (read-sequence sequence result)\n        (is (equalp\n              #(102 111 111 98 97 114)\n              sequence))))))\n\n(test content-as-string\n  (with-fixture state ()\n    (let ((result (http-request\n                   \"/dummy/test-content-handler\"\n                   :content \"foobar\"\n                   :content-type \"application/text\"\n                   :want-string t\n                   :engine engine)))\n      (is (equal \"foobar\" result)))))\n\n"
  },
  {
    "path": "src/util/tests/test-json-mop.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-json-mop\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/json-mop\n                #:ext-json-serializable-class)\n  (:import-from #:fiveam-matchers/core\n                #:has-typep\n                #:assert-that)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string))\n(in-package :util/tests/test-json-mop)\n\n\n(util/fiveam:def-suite)\n\n(defclass simple-obj ()\n  ((arg1 :initarg :arg1\n         :json-key \"first\"\n         :json-type :string\n         :accessor arg1)\n   (arg2 :initarg :arg2\n         :json-key \"second\"\n         :json-type (or null :string)\n         :accessor arg2))\n  (:metaclass ext-json-serializable-class))\n\n(defun %encode (obj)\n  (with-output-to-string (out)\n    (yason:encode obj out)))\n\n(test simple-encoding\n  (let ((obj (make-instance 'simple-obj :arg1 \"foo\")))\n    (assert-that (%encode obj)\n                 (contains-string \"foo\"))))\n\n(test simple-encoding-with-null\n  (let ((obj (make-instance 'simple-obj :arg1 \"foo\" :arg2 nil)))\n    (assert-that (%encode obj)\n                 (contains-string \"foo\"))))\n\n(test simple-encoding-without-null\n  (let ((obj (make-instance 'simple-obj :arg1 \"foo\" :arg2 \"bleh\")))\n    (assert-that (%encode obj)\n                 (contains-string \"bleh\"))\n    (finishes\n      (json-mop:json-to-clos (%encode obj)\n                             'simple-obj))))\n\n(test ensure-parent-class\n  (assert-that (make-instance 'simple-obj)\n               (has-typep 'json-mop:json-serializable)))\n\n(defclass simple-list-slot ()\n  ((list :initarg :list\n         :json-key \"first\"\n         :json-type (:list :string)\n         :accessor arg1))\n  (:metaclass ext-json-serializable-class))\n\n(test ensure-can-encode-empty-list\n  (let ((obj (make-instance 'simple-list-slot)))\n    (finishes (json-mop:json-to-clos (%encode obj)\n                                     'simple-list-slot)))\n  (let ((obj (make-instance 'simple-list-slot\n                            :list nil)))\n    (finishes (json-mop:json-to-clos (%encode obj)\n                                     'simple-list-slot))))\n"
  },
  {
    "path": "src/util/tests/test-lispworks.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-lispworks\n  (:use #:cl\n        #:fiveam))\n(in-package :util/tests/test-lispworks)\n\n;; Tests for bugs in Lispworks, to make sure it's properly fixed in all\n;; versions\n\n(util/fiveam:def-suite)\n\n\n(test ipv6-is-correctly-parsed\n  \"T1822\"\n  (is\n   (equal\n    (comm:ip-address-string (comm:parse-ipv6-address\n                             \"2600:1f18:153:1805:cbf0:efd:1758:b510\"))\n    \"2600:1f18:153:1805:cbf0:efd:1758:b510\"))\n  (is\n   (equal\n    (comm:ip-address-string (comm:parse-ipv6-address\n                             \"2600:1f18:153:1805:cbf0:efd:1758::\"))\n    \"2600:1f18:153:1805:cbf0:efd:1758::\"))\n  (is\n   (equal\n    (comm:ip-address-string (comm:parse-ipv6-address\n                             \"2600:1f18:153:1805:cbf0:efd:1758:b500\"))\n    \"2600:1f18:153:1805:cbf0:efd:1758:b500\"))\n  (is\n   (equal\n    (comm:ip-address-string (comm:parse-ipv6-address\n                             \"2600:1f18:153:1805:cbf0:efd:1758:0000\"))\n    \"2600:1f18:153:1805:cbf0:efd:1758::\")))\n"
  },
  {
    "path": "src/util/tests/test-logger.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-logger\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/logger\n                #:format-ts\n                #:format-log\n                #:logger)\n  (:import-from #:fiveam-matchers/strings\n                #:contains-string)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that))\n(in-package :util/tests/test-logger)\n\n(util/fiveam:def-suite)\n\n(test simple-logger\n  (uiop:with-temporary-file (:pathname p)\n    (let ((logger (make-instance 'logger :file p)))\n      (format-log logger\n                  :info \"hello world ~a\" 1)\n      (assert-that (uiop:read-file-string p)\n                   (contains-string \"hello world 1\"))\n      (format-log logger\n                  :info \"second\")\n      (assert-that (uiop:read-file-string p)\n                   (contains-string (format nil \"hello world 1~%\")))\n      (assert-that (uiop:read-file-string p)\n                   (contains-string \"second\")))))\n\n(test nil-logger\n  (is (equal\n       \"hello world 1\"\n       (format-log nil\n                   :info \"hello world ~a\" 1))))\n\n(test format-ts\n  (let ((local-time:*default-timezone* local-time:+utc-zone+))\n    (is (equal \"[01:00:11] \"\n               (format-ts nil (make-instance 'local-time:timestamp\n                                             :sec 3611))))))\n"
  },
  {
    "path": "src/util/tests/test-lparallel.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-lparallel\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/lparallel\n                #:with-test-lparallel-kernel\n                #:with-temp-lparallel-kernel)\n  (:import-from #:lparallel.promise\n                #:force\n                #:future)\n  (:import-from #:lparallel.promise\n                #:force))\n(in-package :util/tests/test-lparallel)\n\n(util/fiveam:def-suite)\n\n(test lparallel-reliability-with-only-creation\n  (dotimes (i 100) ;; increase this if needed\n    (when (= 0 (mod i 100))\n      (log:info \"count: ~a\" i))\n    (finishes\n      \n      (with-temp-lparallel-kernel ()\n       (values)))))\n\n(test cached-test-kernel\n  (finishes\n    (with-test-lparallel-kernel ()\n      (is (eql :done (force (future :done)))))))\n"
  },
  {
    "path": "src/util/tests/test-lru-cache.lisp",
    "content": "(defpackage :util/tests/test-lru-cache\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/lru-cache\n                #:cache-size\n                #:with-cache-file\n                #:item-key\n                #:cons-map\n                #:queue-tail\n                #:item-size\n                #:file-atime\n                #:queue-length\n                #:queue-count\n                #:queue-head\n                #:lru-cache)\n  (:import-from #:fiveam-matchers/core\n                #:is-equal-to\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:~ #:fiveam-matchers)))\n(in-package :util/tests/test-lru-cache)\n\n(util/fiveam:def-suite)\n\n(defvar *atimes*)\n\n(def-fixture state ()\n  (let ((*atimes* (make-hash-table :test #'equal)))\n    (tmpdir:with-tmpdir (dir)\n      (&body))))\n\n(defclass test-lru-cache (lru-cache)\n  ())\n\n(defmethod file-atime ((cache test-lru-cache) file)\n  (let ((val\n          (gethash (namestring file) *atimes*)))\n    (assert val)\n    val))\n\n\n(test preconditions\n  (with-fixture state ()\n    (let ((cache (make-instance 'lru-cache\n                                :dir dir)))\n      (is (equal '()\n                 (queue-head cache)))\n      (is (equal 0 (queue-count cache)))\n      (is (equal 0 (queue-length cache))))))\n\n(defun touch (file)\n  (ensure-directories-exist file)\n  (with-open-file (stream file :direction :output)\n    (declare (ignore stream))))\n\n(defun pathname= (a b)\n  (string= (namestring a)\n           (namestring b)))\n\n(defun set-atime (file atime)\n  (setf (gethash (namestring file) *atimes*) atime))\n\n(test some-files\n  (with-fixture state ()\n    (let ((file-1 (path:catfile dir \"foo.txt\")))\n      (touch file-1)\n      (set-atime file-1 20)\n      (let ((cache (make-instance 'test-lru-cache\n                                  :dir dir)))\n        (~:assert-that\n         (queue-head cache)\n         (~:has-length 1))\n        (let ((item\n                (car (queue-head cache))))\n          (is (eql 0 (item-size item)))\n          (is (equal \"foo.txt\" (item-key item)))\n          (is (equal 1 (hash-table-count\n                        (cons-map cache))))\n          (is (equal item\n                     (car\n                      (gethash \"foo.txt\"\n                               (cons-map cache))))))))))\n\n(test sub-dir\n  (with-fixture state ()\n    (let ((file-1 (path:catfile dir \"blah/foo.txt\")))\n      (touch file-1)\n      (set-atime file-1 20)\n      (let ((cache (make-instance 'test-lru-cache\n                                  :dir dir)))\n        (~:assert-that\n         (queue-head cache)\n         (~:has-length 1))\n        (let ((item\n                (car (queue-head cache))))\n          (is (eql 0 (item-size item)))\n          (is (equal \"blah/foo.txt\" (item-key item)))\n          (is (equal 1 (hash-table-count\n                        (cons-map cache))))\n          (is (equal item\n                     (car\n                      (gethash \"blah/foo.txt\"\n                               (cons-map cache))))))))))\n\n(defun write-test-string (file)\n  (with-open-file (stream file :direction :output)\n    (write-string \"arnold\" stream)))\n\n(test file-sizes\n  (with-fixture state ()\n    (let ((file-1 (path:catfile dir \"foo.txt\")))\n      (write-test-string file-1)\n      (set-atime file-1 20)\n      (let ((cache (make-instance 'test-lru-cache\n                                  :dir dir)))\n        (~:assert-that\n         (queue-head cache)\n         (~:has-length 1))\n        (let ((item\n                (car (queue-head cache))))\n          (is (eql 6 (item-size item)))\n          (is (eql 6 (cache-size cache)))\n          (is (equal \"foo.txt\" (item-key item))))))))\n\n(test ordering\n  (with-fixture state ()\n    (let ((keys (loop for i from 0 to 20\n                       for key = (format nil \"file-~d.txt\"  (+ (* 100 (random 100)) i))\n                       for file = (path:catfile\n                                   dir key)\n                       do\n                          (progn\n                            (touch file)\n                            (set-atime file i))\n                       collect\n                      key)))\n      (let ((cache (make-instance 'test-lru-cache\n                                  :dir dir)))\n        (assert-that\n         (loop for item in (queue-head cache)\n               collect (item-key item))\n         (apply #'contains keys))\n        (assert-that\n         (namestring (item-key (car (queue-tail cache))))\n         (is-equal-to (car (last keys))))\n        (is (equal 21 (queue-length cache)))\n        (is (equal 21 (queue-count cache)))))))\n\n(def-fixture defaults ()\n  (let ((cache (make-instance 'test-lru-cache\n                              :dir dir)))\n    (&body)))\n\n(test with-cache-file\n  (with-fixture state ()\n    (with-fixture defaults ()\n      (with-cache-file (pathname cache \"foo.txt\")\n        (write-test-string pathname))\n      (is (equal 1 (length (queue-head cache))))\n      (is (equal 1 (queue-length cache)))\n      (is (equal 1 (queue-count cache)))\n      (is (equal 6 (cache-size cache)))\n      (with-cache-file (pathname cache \"foo.txt\")\n        nil)\n      (is (equal 2 (length (queue-head cache))))\n      (is (equal 2 (queue-length cache)))\n      (is (equal 1 (queue-count cache)))\n      (is (equal 6 (cache-size cache)))\n      (with-cache-file (pathname cache \"foo.txt\")\n        nil)\n      (is (equal 3 (length (queue-head cache))))\n      (is (equal 3 (queue-length cache)))\n      (is (equal 1 (queue-count cache)))\n      (is (equal 1 (length (remove-if #'null (queue-head cache))))))))\n\n\n(test trim-queue\n  (with-fixture state ()\n    (with-fixture defaults ()\n      (dotimes (i 1000)\n        (with-cache-file (pathname cache \"foo.txt\")\n          (ignore-errors\n           (touch pathname))))\n      (is (>= 20 (length (queue-head cache))))\n      (is (>= 20 (queue-length cache)))\n      (is (= 1 (queue-count cache))))))\n\n(test delete-old-entries\n  (with-fixture state ()\n    (let ((cache (make-instance 'lru-cache\n                                :dir dir\n                                :max-size 10)))\n      (dotimes (i 2)\n        (with-cache-file (pathname cache (format nil \"foo-~d.txt\" i))\n          (write-test-string pathname)))\n      (is (equal 1 (queue-count cache)))\n      (is (equal 6 (cache-size cache)))\n      (is-true (path:-e (path:catfile dir \"foo-1.txt\")))\n      (is-false (path:-e (path:catfile dir \"foo-0.txt\"))))))\n\n(test can-use-gb\n  (with-fixture state ()\n   (let ((cache (make-instance 'lru-cache\n                               :dir dir\n                               :max-size \"4GB\")))\n     (with-cache-file (pathname cache \"foo.txt\")))))\n\n(test can-use-pathname-key\n  (with-fixture state ()\n    (let ((cache (make-instance 'lru-cache\n                                :dir dir\n                                :max-size 10)))\n      (dotimes (i 2)\n        (with-cache-file (pathname cache (pathname (format nil \"foo-~d.txt\" i)))\n          (write-test-string pathname)))\n      (is (equal 1 (queue-count cache)))\n      (is (equal 6 (cache-size cache)))\n      (is-true (path:-e (path:catfile dir \"foo-1.txt\")))\n      (is-false (path:-e (path:catfile dir \"foo-0.txt\"))))))\n\n(test file-atime\n  (with-fixture state ()\n    (let ((cache (make-instance 'lru-cache\n                                :dir dir\n                                :max-size 10)))\n      (uiop:with-temporary-file (:pathname p)\n        (is (numberp\n             (file-atime cache p)))))))\n"
  },
  {
    "path": "src/util/tests/test-mail.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-mail\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util\n                #:token-safe-for-email-p))\n(in-package :util/tests/test-mail)\n\n(util/fiveam:def-suite)\n\n(test token-safe-for-email-p\n  (is-true (token-safe-for-email-p \"this is a test\"))\n  (is-false (token-safe-for-email-p \"www.google.com\"))\n  (is-false (token-safe-for-email-p \"this is https://google.com a test\")))\n"
  },
  {
    "path": "src/util/tests/test-make-instance-with-accessors.lisp",
    "content": "(uiop:define-package :util/tests/test-make-instance-with-accessors\n  (:use #:cl\n        #:fiveam\n        #:alexandria\n        #:util/make-instance-with-accessors))\n(in-package :util/tests/test-make-instance-with-accessors)\n\n(util/fiveam:def-suite)\n\n(defclass test-class ()\n  ((foo :initarg :foo\n        :accessor test-class-foo)\n   (car :initarg :carrot\n        :accessor test-class-carrot)))\n\n(test preconditions\n  (let ((obj (make-instance-with-accessors 'test-class\n                                           'test-class-foo 22\n                                           'test-class-carrot \"bleh\")))\n    (is (eql 22 (test-class-foo obj)))\n    (is (equal \"bleh\" (test-class-carrot obj)))))\n"
  },
  {
    "path": "src/util/tests/test-memory.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-memory\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/memory\n                #:arena-size\n                #:malloc-info\n                #:process-mem-usage)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-memory)\n\n\n(util/fiveam:def-suite)\n\n(test process-mem-usage\n  (is (> (process-mem-usage) 0)))\n\n(test malloc-info\n  (finishes\n    (malloc-info)))\n\n(test arena-size\n  (multiple-value-bind (arena hblkhd)\n      (arena-size)\n    (is (>= arena 0))\n    (is (>= hblkhd 0))))\n"
  },
  {
    "path": "src/util/tests/test-mock-recording.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(uiop:define-package :util/tests/test-mock-recording\n    (:use #:cl\n          #:fiveam\n          #:alexandria)\n  (:import-from #:util/mock-recording\n                #:track\n                #:response\n                #:arguments\n                #:with-recording))\n(in-package :util/tests/test-mock-recording)\n\n(util/fiveam:def-suite)\n\n(defvar *ans* nil)\n\n(defun foo (x)\n  (or\n   *ans*\n   (+  1 x)))\n\n(defun bar (x)\n  (or\n   *ans*\n   (+  2 x)))\n\n(defun zoidberg (x)\n  (+ (bar x) 3))\n\n(test recording-mode\n  (uiop:with-temporary-file (:pathname p)\n    (is (eql 3 (with-recording (p :record t)\n                 (track 'foo)\n                 (foo 2))))\n    (is (eql 10 (with-recording (p :record t)\n                  (track 'foo)\n                  (foo 3)\n                  (foo 9))))\n    (let ((recording (cl-store:restore p)))\n      (is (equal\n           `(((3) . 4)\n             ((9) . 10))\n           (loop for function-call in recording\n                 collect (cons\n                          (arguments function-call)\n                          (response function-call))))))))\n\n(test replay-mode\n  (uiop:with-temporary-file (:pathname p)\n    (with-recording (p :record t)\n      (track 'foo)\n      (let ((*ans* 9))\n        (foo 3)))\n    (with-recording (p)\n      (track 'foo)\n      (is (eql 9 (foo 3))))))\n\n(test skip-args\n  (uiop:with-temporary-file (:pathname p)\n    (with-recording (p :record t)\n      (track 'foo :skip-args (list 0))\n      (foo 3))\n    (with-recording (p)\n      (track 'foo :skip-args (list 0))\n      (is (eql 4 (foo 30))))))\n\n\n(test multiple-functions\n  (uiop:with-temporary-file (:pathname p)\n    (with-recording (p :record t)\n      (track 'foo)\n      (track 'bar)\n      (is (equal 3 (foo 2)))\n      (is (equal 5 (bar 3))))\n    (let ((*ans* 20))\n     (with-recording (p)\n       (track 'foo)\n       (track 'bar)\n       (is (equal 3 (foo 2)))\n       (is (equal 5 (bar 3)))))))\n\n(test different-function-order\n  (uiop:with-temporary-file (:pathname p)\n    (with-recording (p :record t)\n      (track 'foo)\n      (track 'bar)\n      (is (equal 3 (foo 2)))\n      (is (equal 5 (bar 3))))\n    (let ((*ans* 20))\n     (with-recording (p)\n       (track 'foo)\n       (track 'bar)\n       (is (equal 3 (foo 2)))\n       (signals error\n         (foo 3))))))\n\n(test nested-function-order\n  (uiop:with-temporary-file (:pathname p)\n    (with-recording (p :record t)\n      (track 'bar)\n      (track 'zoidberg)\n      (is (equal 6 (zoidberg 1))))\n    (with-recording (p)\n      (track 'bar)\n      (track 'zoidberg)\n      (is (equal 6 (zoidberg 1))))))\n"
  },
  {
    "path": "src/util/tests/test-mockable.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :test-mockable\n  (:use :cl\n        :mockable\n        :fiveam)\n  (:export))\n(in-package :test-mockable)\n\n(defmockable func ()\n  1)\n\n(test without-mocks\n  (is (equal 1 (func))))\n\n(test with-mocks\n  (flet ((make-call () (func)))\n    (with-single-mock (func (lambda () 2))\n      (is (equal 2 (make-call))))))\n\n(test directly\n  (with-single-mock (func (lambda () 2))\n    (is (equal 2 (func)))))\n\n(defmockable single-arg (str)\n  (length str))\n\n(test single-arg\n  (is (equal 3 (single-arg \"foo\")))\n  (with-single-mock (single-arg (lambda (str) (* 2 (length str))))\n    (is (equal 6 (single-arg \"foo\")))))\n\n(defmockable two-args (a b)\n  (+ (length a) (length b)))\n\n(test two-args\n  (is (equal 5 (two-args \"foo\" \"ba\")))\n  (with-single-mock (two-args (lambda (a b) (+ (* 2 (length a)) (length b))))\n    (is (equal 8 (two-args \"foo\" \"ba\")))))\n\n(defmockable key-method (a &key b)\n  (+ (length a) (length b)))\n\n(test key-method\n  (is (equal 5 (key-method \"foo\" :b \"ba\")))\n  (with-single-mock (key-method (lambda (a &key b) (+ (* 2 (length a)) (length b))))\n    (is (equal 8 (key-method \"foo\" :b \"ba\")))))\n"
  },
  {
    "path": "src/util/tests/test-models.lisp",
    "content": "(defpackage :util.test-model\n  (:use :cl)\n  (:export))\n(in-package :util.test-model)\n"
  },
  {
    "path": "src/util/tests/test-mquery.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util.test-mquery\n  (:use :cl\n        :fiveam\n        :mquery\n        :markup)\n  (:import-from :mquery\n                :split-query-components))\n(in-package :util.test-mquery)\n\n(def-suite* :util.test-mquery)\n\n(named-readtables:in-readtable markup:syntax)\n\n(def-fixture state ()\n  (let* ((inner-div <div class= \"trouble\" name= \"zoid\" ></div>)\n         (complex-body <html><h1 name= \"foo\" >hello</h1>\n                      ,(progn inner-div)</html>))\n    (with-document (complex-body)\n     (&body))))\n\n(test simple-query\n  (with-fixture state ()\n    (is-true ($ \"h1\"))\n    (is (equal :h1 (xml-tag-name (car  ($ \"h1\")))))\n    (is (equal \"foo\" (attr ($ \"h1\")  \"name\")))\n    (is (equal \"foo\" (attr ($ \"h1\")  :name)))))\n\n(test set-attr\n  (with-fixture state ()\n    (setf (attr ($ \"h1\") \"name\") \"bar\")\n    (is (equal \"bar\" (attr ($ \"h1\") \"name\")))))\n\n(test find-div\n  (with-fixture state ()\n    (is (eql inner-div (car ($ \"div\"))))))\n\n(test find-by-classname\n  (with-fixture state ()\n    (is (eql inner-div (car ($ \".trouble\"))))))\n\n(test find-by-classname-and-tag\n  (with-fixture state ()\n    (is (eql inner-div (car ($ \"div.trouble\"))))\n    (is (eql inner-div (car ($ \"div.trouble.trouble\"))))))\n\n(test add-class\n  (with-fixture state ()\n    (add-class ($ \"div\") \"maker\")\n    (is (equal \"trouble maker\"\n               (attr ($ \"div\") \"class\")))))\n\n(test remove-class\n  (with-fixture state ()\n    (remove-class ($ \"div\") \"trouble\")\n    (is (equal \"\" (attr ($ \"div\") \"class\")))))\n\n(test name\n  (with-fixture state ()\n    (is (eql inner-div\n             (car ($ (namequery \"zoid\")))))))\n\n(test name-by-query\n  (with-fixture state ()\n    (is (eql inner-div\n             (car ($ \"div[name='zoid']\"))))))\n\n(test parent\n  (with-fixture state ()\n    (is (eql complex-body\n             (parent ($ \"div\"))))))\n\n(test after\n  (with-fixture state ()\n    (is (eql inner-div (after ($ \"h1\"))))))\n\n(test insert-after\n  (with-fixture state ()\n    (let ((new-tag <div></div>))\n      (setf (after ($ \"h1\"))  new-tag)\n      (is (eql new-tag (after ($ \"h1\")))))))\n\n(test text\n  (let ((newtag <h1>hello</h1>))\n    (is (equal \"hello\" (text newtag))))\n  (let ((newtag <html><h1>hello</h1></html>))\n    (is (equal \"hello\" (text ($ \"h1\" newtag)))))\n  (let ((newtag <html><h1>hello <b>world</b></h1></html>))\n    (is (equal \"hello world\" (text ($ \"h1\" newtag))))))\n\n(test split-query-components\n  (is (equal (list \"foo\" nil)\n             (split-query-components \"foo\")))\n  (is (equal (list \".foo\" nil)\n             (split-query-components \".foo\")))\n  (is (equal (list \"foo\" \".bar\")\n             (split-query-components \"foo.bar\")))\n  (is (equal (list \"foo\" \".bar.car\")\n             (split-query-components \"foo.bar.car\")))\n  (is (equal (list \"foo\" \"[attr='val']\")\n             (split-query-components \"foo[attr='val']\"))))\n\n(test finds-under-merge-tags-too\n  (let ((doc <div>\n  <markup:merge-tag>\n    <div class= \"foobar\" />\n  </markup:merge-tag>\n             </div>))\n    (with-document (doc)\n      (is (eql 0 (length (mquery:$ \".car\"))))\n      (is (eql 1 (length (mquery:$ \".foobar\")))))))\n"
  },
  {
    "path": "src/util/tests/test-random-port.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-random-port\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/random-port\n                #:*ports*\n                #:with-random-port))\n(in-package :util/tests/test-random-port)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (clrhash *ports*)\n  (&body))\n\n(test test-happy-path ()\n  (with-fixture state ()\n   (let ((calledp nil))\n     (with-random-port (port)\n       (is (> port 10000))\n       (setf calledp t))\n     (is-true calledp)\n     (is (eql 0 (hash-table-count *ports*))))))\n\n(test test-we-dont-pick-something-weve-seen ()\n  (with-fixture state ()\n   (dotimes (i 5)\n     (let ((*ports* (make-hash-table)))\n\n       (loop for i from 0 to 35000\n             do (setf (gethash i *ports*) t))\n\n       (let ((calledp nil))\n         (with-random-port (port)\n           (is (> port 35000))\n           (setf calledp t))\n         (is-true calledp))))))\n"
  },
  {
    "path": "src/util/tests/test-rb-tree.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-rb-tree\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/rb-tree\n                #:rb-delete\n                #:node-key\n                #:sorted-keys\n                #:validate!\n                #:+black+\n                #:+red+\n                #:%rb-insert-only\n                #:rb-insert\n                #:rb-tree-root\n                #:rb-tree-sentinel\n                #:node-parent\n                #:node-left\n                #:node-right\n                #:make-node\n                #:make-tree))\n(in-package :util/tests/test-rb-tree)\n\n\n(util/fiveam:def-suite)\n\n(defun fix-color (color)\n  (ecase color\n    (red\n     +red+)\n    (black\n     +black+)))\n\n(defun %make-test-node (tree node-expr)\n  \"Node expr looks like: (color value <left node expr> <right node expr>)\"\n  (if (null node-expr)\n      (rb-tree-sentinel tree)\n      (let ((color (first node-expr))\n            (value (second node-expr))\n            (left-expr (third node-expr))\n            (right-expr (fourth node-expr)))\n        (let ((node (make-node :color (fix-color color)\n                               :key value\n                               :parent nil)))\n          (setf (node-left node) (%make-test-node tree left-expr))\n          (setf (node-right node) (%make-test-node tree right-expr))\n          (unless (eq (node-left node) (rb-tree-sentinel tree))\n            (setf (node-parent (node-left node)) node))\n          (unless (eq (node-right node) (rb-tree-sentinel tree))\n            (setf (node-parent (node-right node)) node))\n          node))))\n\n(defun tree= (tree1 tree2)\n  (labels ((nodes-equal (node1 node2)\n             (cond\n               ((or (not node1)\n                    (not node2))\n                (error \"Found a NIL node (not sentinel!) in the tree\"))\n               ((and (eq node1 (rb-tree-sentinel tree1))\n                     (eq node2 (rb-tree-sentinel tree2)))\n                t)\n               ((or (eq node1 (rb-tree-sentinel tree1))\n                    (eq node2 (rb-tree-sentinel tree2)))\n                nil)\n               (t\n                (and (eq (slot-value node1 'util/rb-tree::color)\n                         (slot-value node2 'util/rb-tree::color))\n                     (equal (slot-value node1 'util/rb-tree::key)\n                            (slot-value node2 'util/rb-tree::key))\n                     (nodes-equal (node-left node1) (node-left node2))\n                     (nodes-equal (node-right node1) (node-right node2)))))))\n    (nodes-equal (rb-tree-root tree1) (rb-tree-root tree2))))\n\n(defmacro tree (body)\n  `(let ((tree (make-tree)))\n     (setf (rb-tree-root tree)\n           (%make-test-node tree ',body))\n     tree))\n\n(test construction-of-test-trees\n  (finishes\n   (tree\n    (black 2)))\n  (finishes\n   (tree\n    (black 2\n           (red 1)\n           (red 4)))))\n\n(defun make-big-tree ()\n  )\n\n(test tree=\n  (is-true\n   (tree=\n       (tree\n        (black 2\n               (red 1)\n               (red 4)))\n       (tree\n        (black 2\n               (red 1)\n               (red 4))))))\n\n(test simple-construction\n  (finishes\n    (make-tree)))\n\n(test insert-when-nothing-is-present-without-fixup\n  (let ((tree (make-tree)))\n    (%rb-insert-only tree (make-node :key 2))\n    (is-true\n     (tree=\n      (tree\n       (red 2))\n      tree))))\n\n(test insert-when-nothing-is-present\n  (let ((tree (make-tree)))\n    (rb-insert tree (make-node :key 2))\n    (is-true\n     (tree=\n      (tree\n       (black 2))\n      tree))))\n\n(test insert-in-deterministic-order\n  (let ((tree (make-tree)))\n    (rb-insert tree (make-node :key 1))\n    (rb-insert tree (make-node :key 2))\n    (rb-insert tree (make-node :key 3))\n    (rb-insert tree (make-node :key 4))\n    (rb-insert tree (make-node :key 5))\n    (rb-insert tree (make-node :key 6))    \n    (rb-insert tree (make-node :key -1))\n    (finishes (validate! tree))))\n\n(test insert-a-bunch-of-random-elements\n  (let ((tree (make-tree)))\n    (rb-insert tree (make-node :key 42))\n    (rb-insert tree (make-node :key 17))\n    (rb-insert tree (make-node :key 89))\n    (rb-insert tree (make-node :key 3))\n    (rb-insert tree (make-node :key 56))\n    (finishes (validate! tree))\n    (rb-insert tree (make-node :key 91))\n    (rb-insert tree (make-node :key 24))\n    (rb-insert tree (make-node :key 7))\n    (rb-insert tree (make-node :key 68))\n    (rb-insert tree (make-node :key 35))\n    (finishes (validate! tree))\n    (rb-insert tree (make-node :key 12))\n    (rb-insert tree (make-node :key 73))\n    (rb-insert tree (make-node :key 46))\n    (rb-insert tree (make-node :key 81))\n    (rb-insert tree (make-node :key 29))\n    (rb-insert tree (make-node :key 64))\n    (rb-insert tree (make-node :key 15))\n    (rb-insert tree (make-node :key 97))\n    (rb-insert tree (make-node :key 52))\n    (rb-insert tree (make-node :key 38))\n    (finishes (validate! tree))))\n\n(test ordering\n  (let ((tree (make-tree)))\n    (rb-insert tree (make-node :key 42))\n    (rb-insert tree (make-node :key 17))\n    (rb-insert tree (make-node :key 89))\n    (rb-insert tree (make-node :key 3))\n    (rb-insert tree (make-node :key 56))\n    (is (equal\n         '(3 17 42 56 89)\n         (sorted-keys tree)))))\n\n(test respects-the-comparison-operator\n  (let ((tree (make-tree :cmp #'>)))\n    (rb-insert tree (make-node :key 42))\n    (rb-insert tree (make-node :key 17))\n    (rb-insert tree (make-node :key 89))\n    (rb-insert tree (make-node :key 3))\n    (rb-insert tree (make-node :key 56))\n    (is (equal\n         (reverse '(3 17 42 56 89))\n         (sorted-keys tree)))))\n\n\n(test insert-100-random-elements\n  (let ((tree (make-tree))\n        (elements (make-array 100)))\n    ;; Generate 100 random unique elements\n    (loop for i from 0 below 100\n          do (setf (aref elements i) (random 10000)))\n    ;; Remove duplicates by converting to list, removing duplicates, and back\n    (let ((unique-elements (remove-duplicates (coerce elements 'list))))\n      ;; Insert all elements\n      (dolist (element unique-elements)\n        (rb-insert tree (make-node :key element)))\n      ;; Validate the tree structure using existing validate! function\n      (finishes (validate! tree)))))\n\n(test insert-and-delete-20-numbers-random-order\n  (let ((tree (make-tree))\n        (numbers (loop for i from 1 to 20 collect i)))\n    ;; Insert 20 numbers in order\n    (dolist (num numbers)\n      (rb-insert tree (make-node :key num)))\n    \n    ;; Validate tree after all insertions\n    (finishes (validate! tree))\n    (is (equal numbers (sorted-keys tree)))\n    \n    ;; Create a shuffled copy for deletion order\n    (let ((delete-order (copy-list numbers)))\n      ;; Shuffle the list using Fisher-Yates algorithm\n      (loop for i from (1- (length delete-order)) downto 1 do\n        (let ((j (random (1+ i))))\n          (rotatef (nth i delete-order) (nth j delete-order))))\n      \n      ;; Delete numbers in random order\n      (let ((remaining (copy-list numbers)))\n        (dolist (num delete-order)\n          ;; Find and delete the node with this key\n          (labels ((find-node (node)\n                     (cond\n                       ((eq node (rb-tree-sentinel tree)) nil)\n                       ((= (node-key node) num) node)\n                       ((< num (node-key node)) (find-node (node-left node)))\n                       (t (find-node (node-right node))))))\n            (let ((node-to-delete (find-node (rb-tree-root tree))))\n              (is-true node-to-delete)\n              (when node-to-delete\n                (rb-delete tree node-to-delete)\n                (setf remaining (remove num remaining))\n                ;; Validate tree structure after each deletion\n                (finishes (validate! tree))\n                ;; Check that remaining keys are still in sorted order\n                (is (equal remaining (sorted-keys tree)))))))))))\n\n(test tree-minimum\n  (let ((tree (make-tree)))\n    ;; Test empty tree\n    ;;(is (eq nil (util/rb-tree::tree-minimum tree (rb-tree-root tree))))\n    \n    ;; Test single node tree\n    (rb-insert tree (make-node :key 42))\n    (is (= 42 (node-key (util/rb-tree::tree-minimum tree (rb-tree-root tree)))))\n    \n    ;; Test tree with multiple nodes\n    (rb-insert tree (make-node :key 17))\n    (rb-insert tree (make-node :key 89))\n    (rb-insert tree (make-node :key 3))\n    (rb-insert tree (make-node :key 56))\n    (is (= 3 (node-key (util/rb-tree::tree-minimum tree (rb-tree-root tree)))))\n    \n    ;; Test with negative numbers\n    (rb-insert tree (make-node :key -10))\n    (rb-insert tree (make-node :key -5))\n    (is (= -10 (node-key (util/rb-tree::tree-minimum tree (rb-tree-root tree)))))\n    \n    ;; Test with custom comparator (reverse order)\n    (let ((reverse-tree (make-tree :cmp #'>)))\n      (rb-insert reverse-tree (make-node :key 42))\n      (rb-insert reverse-tree (make-node :key 17))\n      (rb-insert reverse-tree (make-node :key 89))\n      (rb-insert reverse-tree (make-node :key 3))\n      (is (= 89 (node-key (util/rb-tree::tree-minimum reverse-tree (rb-tree-root reverse-tree))))))))\n\n"
  },
  {
    "path": "src/util/tests/test-request-integration.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-request-integration\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/request\n                #:http-request\n                #:timeout-error))\n(in-package :util/tests/test-request-integration)\n\n(util/fiveam:def-suite)\n\n;; AT TIME OF WRITING, these tests don't work. I'm putting it in here\n;; to start building the infra for these.\n\n(defparameter *enablep* nil\n  \"By default, we don't run these tests since they might be flaky and\nexpensive.\")\n\n(def-fixture state ()\n  (when *enablep*\n    (&body)))\n\n(test timeout\n  (with-fixture state ()\n    (signals timeout-error\n     (util/request:http-request\n      \"https://staging.screenshotbot.io/test-timeout\"\n      :read-timeout 5\n      :write-timeout 5\n      :connection-timeout 5))))\n\n(test timeout-without-ssl\n  (with-fixture state ()\n    (multiple-value-bind (output err)\n        (util/request:http-request\n         \"https://localhost:4001/test-timeout\"\n         :read-timeout 5\n         :write-timeout 5\n         :connection-timeout 5)\n      (Error \"here\"))))\n\n\n(test timeout-while-sending\n  (with-fixture state ()\n    (let ((res (http-request\n                \"https://staging.screenshotbot.io/test-timeout-while-sending\"\n                :read-timeout 5\n                :write-timeout 5\n                :connection-timeout 5)))\n      (is (equal \"arnoldfoobar\" res)))))\n\n(defvar +crlf+\n  (format nil \"~a~a\" #\\Return #\\Linefeed))\n\n#+lispworks\n(test manual-test-for-lispworks-reproduction\n  (with-fixture state ()\n    (let ((stream (comm:open-tcp-stream \"staging.screenshotbot.io\" 443 :ssl-ctx t\n                                                                       :direction :io\n                                                                       :read-timeout 5)))\n      (format stream \"GET /test-timeout-while-sending HTTP/1.1\")\n      (format stream +crlf+)\n      (format stream \"Host: staging.screenshotbot.io\")\n      (format stream +crlf+)\n      (format stream +crlf+)\n      (force-output stream)\n      (let ((lines \n              (loop for line = (read-line stream nil)\n                    while line\n                    collect line)))))))\n"
  },
  {
    "path": "src/util/tests/test-request.lisp",
    "content": "(defpackage :util/tests/test-request\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/request\n                #:wrap-ssl-errors\n                #:fix-bad-chars\n                #:http-request\n                #:make-header-hash-table)\n  (:import-from #:hunchentoot\n                #:easy-acceptor\n                #:define-easy-handler)\n  (:import-from #:util/testing\n                #:with-local-acceptor)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-request)\n\n(util/fiveam:def-suite)\n\n(test make-header-hash-table\n  (let ((headers (list\n                  (cons \"Foo\" \"bar\"))))\n    (let ((res (make-header-hash-table headers)))\n      (is (hash-table-p res))\n      (is (equal \"bar\" (gethash \"foo\" res))))))\n\n(defvar *body* nil)\n(defvar *gzip* nil)\n\n(define-easy-handler (dummy-request :uri \"/test-dummy-content\"\n                                    :acceptor-names '(dummy)) ()\n  (cond\n    (*gzip*\n     (setf (hunchentoot:header-out :content-encoding)\n           \"gzip\")\n     *body*)\n    (t\n     *body*)))\n\n(def-fixture state ()\n  (unwind-protect\n       (with-local-acceptor (url) ('test-acceptor)\n         (&body))\n    (setf *body* nil)\n    (setf *gzip* nil)))\n\n(defclass test-acceptor (easy-acceptor)\n  ()\n  (:default-initargs :name 'dummy))\n\n(defun read-file-seq (file)\n  (with-open-file (stream\n                   (path:catfile\n                    #.(asdf:system-source-directory :util)\n                    file)\n                   :direction :input\n                   :element-type '(unsigned-byte 8))\n    (let ((output (make-array (file-length stream)\n                              :element-type '(unsigned-byte 8))))\n      (read-sequence output stream)\n      output)))\n\n(test decoding-gzip\n  (with-fixture state ()\n    (setf *body* (read-file-seq \"test-file.txt\"))\n    (multiple-value-bind (content ret headers)\n        (http-request\n         (format nil \"~a~a\" url \"/test-dummy-content\")\n         :force-binary t)\n      (is (equalp content\n                  (read-file-seq \"test-file.txt\")))\n      (is\n       (equal \"12\" (a:assoc-value headers :content-length))))\n\n    (setf *gzip* t)\n    (setf *body* (read-file-seq \"test-file-compressed.txt.gz\"))\n\n    (multiple-value-bind (content ret headers)\n        (http-request\n         (format nil \"~a~a\" url \"/test-dummy-content\")\n         :accept-gzip t\n         :decode-content t\n         :force-binary t)\n      (is (equalp content\n                  (read-file-seq \"test-file.txt\")))\n      (is (equal \"gzip\"\n                 (a:assoc-value headers :content-encoding)))\n      (is\n       (equal \"32\" (a:assoc-value headers :content-length))))))\n\n(test if-content-encoding-is-not-set-we-still-decode\n  (with-fixture state ()\n    (setf *gzip* t)\n    (setf *body* (read-file-seq \"test-file-compressed.txt.gz\"))\n    (multiple-value-bind (content ret headers)\n        (http-request\n         (format nil \"~a~a\" url \"/test-dummy-content\")\n         :decode-content t\n         :force-binary t)\n      (is (equalp content\n                  (read-file-seq \"test-file.txt\")))\n      (is (equal \"gzip\"\n                 (a:assoc-value headers :content-encoding)))\n      (is\n       (equal \"32\" (a:assoc-value headers :content-length))))))\n\n(test if-decode-content-is-nil-we-dont-decode\n  (with-fixture state ()\n    (setf *gzip* t)\n    (setf *body* (read-file-seq \"test-file-compressed.txt.gz\"))\n    (multiple-value-bind (content ret headers)\n        (http-request\n         (format nil \"~a~a\" url \"/test-dummy-content\")\n         :decode-content nil\n         :force-binary t)\n      (is (equalp content\n                  (read-file-seq \"test-file-compressed.txt.gz\")))\n      (is (equal \"gzip\"\n                 (a:assoc-value headers :content-encoding)))\n      (is\n       (equal \"32\" (a:assoc-value headers :content-length))))))\n\n(test fix-bad-chars\n  (is (equal \"https://www.google.com?foo=bar\"\n             (fix-bad-chars \"https://www.google.com?foo=bar\")))\n    (is (equal \"https://www.google.com?foo=bar%7Ccar\"\n             (fix-bad-chars \"https://www.google.com?foo=bar|car\"))))\n\n#+lispworks\n(test wrap-ssl-errors\n  (multiple-value-bind (body ret)\n      (wrap-ssl-errors ()\n        (error 'comm:ssl-closed))\n    (is (eql 502 ret)))\n  (signals comm:ssl-closed\n    (wrap-ssl-errors (:ensure-success t)\n      (error 'comm:ssl-closed))))\n\n"
  },
  {
    "path": "src/util/tests/test-ret-let.lisp",
    "content": "(pkg:define-package :util/test-ret-let\n    (:use #:cl\n          #:fiveam\n          #:alexandria)\n  (:import-from #:util/ret-let\n                #:ret-let))\n\n\n(util/fiveam:def-suite)\n\n(test simple-ret-let\n  (is (eql 3 (ret-let (x (+  1 2))\n               \"two\")))\n  (is (equal \"arn0ld\"\n             (ret-let (x (format nil \"~a~a\" \"arn\" \"old\"))\n               (setf (elt x 3) #\\0)))))\n"
  },
  {
    "path": "src/util/tests/test-reused-ssl.lisp",
    "content": "(defpackage :util/tests/test-reused-ssl\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/reused-ssl\n                #:tracked-stream-key\n                #:trim-old-connections\n                #:connection\n                #:connections\n                #:find-connection\n                #:*reuse-contexts*\n                #:tracked-stream\n                #:with-reused-ssl\n                #:reused-ssl-engine)\n  (:import-from #:alexandria\n                #:assoc-value)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/has-length\n                #:has-length)\n  (:import-from #:util/request\n                #:http-request))\n(in-package :util/tests/test-reused-ssl)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((engine (make-instance 'reused-ssl-engine)))\n    (&body)))\n\n(test simple-invocation\n  (with-fixture state ()\n    (finishes\n      (with-reused-ssl (engine)))))\n\n(test ensure-the-stream-gets-reused\n  (with-fixture state ()\n   (with-reused-ssl (engine)\n     (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n       (write-string \"foobar\ncarbar\" s)\n       (close s)\n       (let ((reuse-context (assoc-value *reuse-contexts* engine)))\n        (with-open-file (input p)\n          (let ((stream (make-instance 'tracked-stream\n                                       :reuse-context reuse-context\n                                       :domain \"example.com\"\n                                       :delegate input\n                                       :reusable-stream input)))\n            (is (equal \"foobar\" (read-line input)))\n            (close stream))\n          (is-false (find-connection reuse-context \"foobar.com\"))\n          (is-true (find-connection reuse-context \"example.com\"))))))))\n\n(test cleanup-old-connections\n  (with-fixture state ()\n    (with-reused-ssl (engine :reuse-context reuse-context)\n      (uiop:with-temporary-file (:stream s)\n        (push\n         (make-instance 'connection\n                        :last-use-time (- (get-universal-time) 30)\n                        :stream s\n                        :domain \"example.com\")\n         (gethash \"example.com\" (connections reuse-context)))\n        (trim-old-connections reuse-context)\n        (assert-that\n         (gethash \"example.com\" (connections reuse-context))\n         (has-length 0))))))\n\n(defvar *enable-network-tests-p* nil)\n;; (setf *enable-network-tests-p* t)\n\n(def-fixture network-state ()\n  (when *enable-network-tests-p*\n    (let ((engine (make-instance 'reused-ssl-engine)))\n     (&body))))\n\n(test making-requests-without-reuse-uss\n  (with-fixture network-state ()\n    (dotimes (i 10)\n     (is (equal \"leader\"\n                (http-request\n                 \"https://screenshotbot.io/raft-state\"\n                 :want-string t\n                 :engine engine))))))\n \n(test making-requests-with-reuse-ssl\n  (with-fixture network-state ()\n    (with-reused-ssl (engine)\n      (dotimes (i 10)\n       (is (equal \"leader\"\n                  (http-request\n                   \"https://screenshotbot.io/raft-state\"\n                   :want-string t\n                   :engine engine)))))))\n\n(test tracked-stream-is-only-closed-once\n  (with-fixture state ()\n    (with-reused-ssl (engine)\n      (uiop:with-temporary-file (:pathname p :stream s :direction :output)\n        (write-string \"foobar\ncarbar\" s)\n        (close s)\n        (let ((reuse-context (assoc-value *reuse-contexts* engine)))\n          (with-open-file (input p)\n            (let ((stream (make-instance 'tracked-stream\n                                         :reuse-context reuse-context\n                                         :domain \"example.com\"\n                                         :delegate input\n                                         :reusable-stream input)))\n              (is (equal \"foobar\" (read-line input)))\n              (close stream)\n              (close stream))\n            (is-true (find-connection reuse-context \"example.com\"))\n            (is-false (find-connection reuse-context \"example.com\"))))))))\n\n(test tracked-stream-key\n  (is (equal \"https://example.com\"\n             (tracked-stream-key\n              (puri:uri \"https://example.com/dfdf/sdfd\")))))\n\n(test want-string-test\n  (with-fixture network-state ()\n    (with-reused-ssl (engine)\n      (multiple-value-bind (response status-code)\n          (http-request\n           \"https://screenshotbot.io/raft-state\"\n           :want-string t\n           :engine engine)\n        (is (stringp response))\n        (is (equal \"leader\" response))\n        (is (= 200 status-code))))))\n\n\n"
  },
  {
    "path": "src/util/tests/test-simple-queue.lisp",
    "content": "(defpackage :util/tests/test-simple-queue\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/simple-queue\n                #:enqueue-with-max-length\n                #:queue-emptyp\n                #:tail\n                #:head\n                #:dequeue\n                #:enqueue\n                #:make-queue))\n(in-package :util/tests/test-simple-queue)\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let ((q (make-queue)))\n    (&body)))\n\n(test preconditions\n  (with-fixture state ()\n    (pass)))\n\n(test enqueue-dequeue\n  (with-fixture state ()\n    (enqueue 1 q)\n    (is (eql 1 (dequeue q)))))\n\n(test removes-from-queue-though\n  (with-fixture state ()\n    (enqueue 1 q)\n    (is (eql 1 (dequeue q)))\n    (is (eql nil (dequeue q)))))\n\n(test insert-two-and-delete-two\n  (with-fixture state ()\n    (enqueue 1 q)\n    (enqueue 2 q)\n    (is (eql 1 (dequeue q)))\n    (is (eql 2 (dequeue q)))\n    (is (eql nil (dequeue q)))))\n\n(test clear-up-the-tail-too\n  (with-fixture state ()\n    (enqueue 1 q)\n    (is (eql 1 (dequeue q)))\n    (enqueue 2 q)\n    (is (eql 2 (dequeue q)))\n    (is (eql nil (dequeue q)))))\n\n(test internal-consistency-after-removing-everythin\n  \"This doesn't affect the correctness, but is useful to avoid any future bugs\"\n  (with-fixture state ()\n    (enqueue 1 q)\n    (is (eql 1 (dequeue q)))\n    (is (eql nil (head q)))\n    (is (eql nil (tail q)))))\n\n(test emptyp\n  (with-fixture state ()\n    (is-true (queue-emptyp q))\n    (enqueue 1 q)\n    (is-false (queue-emptyp q))\n    (dequeue q)\n    (is-true (queue-emptyp q))))\n\n(test enqueue-with-max-length\n  (with-fixture state ()\n    (enqueue 1 q)\n    (enqueue 2 q)\n    (enqueue 3 q)\n    (enqueue 4 q)\n    (enqueue-with-max-length 5 q :max-length 2)))\n"
  },
  {
    "path": "src/util/tests/test-sizeof.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-sizeof\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/sizeof\n                #:def-uint-type\n                #:def-int-type\n                #:sizeof)\n  (:local-nicknames #-lispworks\n                    (:fli #:util/fake-fli)))\n(in-package :util/tests/test-sizeof)\n\n(util/fiveam:def-suite)\n\n(test simple-sizeof\n  (is (eql 8 (sizeof \"int64_t\"\n                     :imports (list \"stdint.h\")))))\n\n#+(and x86-64 linux)\n(test sizeof-with-imports\n  (is (eql 8\n           (sizeof \"fsblkcnt_t\"\n                   :imports (list \"sys/statvfs.h\")))))\n\n(test def-int-type\n  (is (equal\n       `(fli:define-c-typedef my-int64-t :int64)\n       (macroexpand-1\n        (macroexpand-1\n         '(def-int-type my-int64-t \"int64_t\" :imports (\"stdint.h\"))))))\n  (is (equal\n       `(fli:define-c-typedef my-uint64-t :uint64)\n       (macroexpand-1\n        (macroexpand-1\n         '(def-uint-type my-uint64-t \"uint64_t\" :imports (\"stdint.h\")))))))\n\n(test def-int-type-happy-path\n  (finishes\n    (def-int-type my-int64-t \"int64_t\" :imports (\"stdint.h\")))\n  (finishes\n   (def-uint-type my-int64-t \"uint64_t\" :imports (\"stdint.h\"))))\n"
  },
  {
    "path": "src/util/tests/test-ssl.lisp",
    "content": "(defpackage :util/tests/test-ssl\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/ssl\n                #:make-ssl-context)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-ssl)\n\n(defvar *test-cert*\n  \"-----BEGIN CERTIFICATE-----\nMIIF7zCCA9egAwIBAgIUA2MBbH5NagtI0Rf+UH/Nb4mJp3YwDQYJKoZIhvcNAQEL\nBQAwgYYxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcM\nCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFu\neVNlY3Rpb25OYW1lMR0wGwYDVQQDDBRDb21tb25OYW1lT3JIb3N0bmFtZTAeFw0y\nMzEyMjAyMjI1NThaFw0zMzEyMTcyMjI1NThaMIGGMQswCQYDVQQGEwJYWDESMBAG\nA1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29t\ncGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTEdMBsGA1UEAwwU\nQ29tbW9uTmFtZU9ySG9zdG5hbWUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\nAoICAQCxe6RSCLJnIxgEhT7Soh709AxY+1fyyWQjdE+D65Pu+RB9O3EZuXw3dr5s\nTMPOT8r3rA/rQ4fMdC7d96p6124LruH0Smyy10udzbaPltqQeN8YIsCoTX/YdiwQ\n/c4oTb+bZKAg6+5h6kDO3u8lpGAA9PF2yRzUnnNxoteKjE1RYiuKl4MR+C8VyidJ\nl/oTsX01TLT/A7yP8ZMJ3tfYmR2aPcb2N8MESsWLtnqNzaEeWfYyu//nfHgE+fNe\nNUjXqaWVjs3rG8L0Wxfnt1T6eCewk7Z3LkFNgja0/BcN929OhdipMq2RciDjOgPk\nzI73eXJuZd9ngNketHzswQr0ppLdXdYCHKzboM6faWVfh8dYcdR/QF6tkPPXAmGU\nFydJjFsyd0S6IcoTkhZVOF3M7+BsOyq7236KFe8BhZxEuhpMCkOCTyJqZCFveZ+i\nLAW4fSmx6FN4vavroUSOxzOqpBnRx3b+HwmI1SWmV10yfY1Dql5SWteyVaaIjAmr\nZvd2QtFbHS03rKBz/WcTulWZfTFBTdvrZfIn2lUsHxxPe3dKuWqvobi3MJYCDHa+\nrhAXPe6c7YE0vMAZgqv7TVoQD/I+U4I2H8z/is2B1/lAz1UOp7v8NdD0D/ZT/C+8\nR+Nutc5u3RFLsuZcNc1FsdvOS7EQlBkEeZq0raTJCTFLCqKgVwIDAQABo1MwUTAd\nBgNVHQ4EFgQUAxWa8BUAU0jwJNtyDRx9SvbW1mswHwYDVR0jBBgwFoAUAxWa8BUA\nU0jwJNtyDRx9SvbW1mswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\nAgEADRsoRHmyr1/H8thS2Bfi2gtnhbe6IT0DS9tKuFVVoNG50/907qgX+hTRzV3G\n4wf9XkB3PFS/tQVQwKzLcbioSnNdVNVozJs+yA8c/DAdtUSZ7hrrA/JOKjlGdSQH\npPE7zT2KAZQl6OJ8WxiQMAR/EAG7dGHgX5qfK+DBDfhudRkSAhCW5YTacVdY0ZpK\nSo7xrrf2Km8lSkZSXbZA+OehgyeTtAqOvP0Lm7AV10S5BcaAPylefBc/+nJAxbnC\nQh5qYB7x1g2o9AdqJ4M4UDub75mjw7rP7GHmpxyEYgwplV+4zUK6ruS+ryM6kDZH\nHJdrj7HZq25koXnO+v2CsXlCDYAHtyupST23plrASG/HV44RBqzXz44JUyJVhAqe\nsNMmXcuqEEFIdagL6ZuNziuSZ5QW2Kbvabca4TZ92mzaFkapEHYlAn8v4+r9CcMb\nVnYMz0g+P6RemIocaacITMT3d5oPoXO4roM0p79Fr9zSvIumVaf5ZeHqaO6P59Ao\n6041AD6QEFjkq09aoi2lLv6Y0eeL0o8ha853npA7aewAxUxn0dE64ceHBz5J71nt\nX7F8H51KKqNN0Wm2CQVQIb+SGvy+7g0quDkl5lxlMqGw66dyuaQRzOnMnbiPo51B\nj6i56GUaQDbtWA0JgaRVtcWRM+w2W4jjfecc/E3EnYlW0m8=\n-----END CERTIFICATE-----\")\n\n(defvar *test-key*\n  \"-----BEGIN PRIVATE KEY-----\nMIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCxe6RSCLJnIxgE\nhT7Soh709AxY+1fyyWQjdE+D65Pu+RB9O3EZuXw3dr5sTMPOT8r3rA/rQ4fMdC7d\n96p6124LruH0Smyy10udzbaPltqQeN8YIsCoTX/YdiwQ/c4oTb+bZKAg6+5h6kDO\n3u8lpGAA9PF2yRzUnnNxoteKjE1RYiuKl4MR+C8VyidJl/oTsX01TLT/A7yP8ZMJ\n3tfYmR2aPcb2N8MESsWLtnqNzaEeWfYyu//nfHgE+fNeNUjXqaWVjs3rG8L0Wxfn\nt1T6eCewk7Z3LkFNgja0/BcN929OhdipMq2RciDjOgPkzI73eXJuZd9ngNketHzs\nwQr0ppLdXdYCHKzboM6faWVfh8dYcdR/QF6tkPPXAmGUFydJjFsyd0S6IcoTkhZV\nOF3M7+BsOyq7236KFe8BhZxEuhpMCkOCTyJqZCFveZ+iLAW4fSmx6FN4vavroUSO\nxzOqpBnRx3b+HwmI1SWmV10yfY1Dql5SWteyVaaIjAmrZvd2QtFbHS03rKBz/WcT\nulWZfTFBTdvrZfIn2lUsHxxPe3dKuWqvobi3MJYCDHa+rhAXPe6c7YE0vMAZgqv7\nTVoQD/I+U4I2H8z/is2B1/lAz1UOp7v8NdD0D/ZT/C+8R+Nutc5u3RFLsuZcNc1F\nsdvOS7EQlBkEeZq0raTJCTFLCqKgVwIDAQABAoICABB/ecrWXB8SOA1ThX926oHC\nndM3qfk7lz9kdG/C2kGAjXPWrrDBeTZ+pzzR9fGboTZcnC42XM04j5K6jvJUTDpx\nyzc2I9yL/s9wa+P1FeQQGEzaDiaW7adldLsvnJZKg+Eh/XCR/drEN7oDJx/Mo8/y\n9O8hyrhI8lpB1N9gI1/JTFrZsqlc9KOF4xkIM4rZGNZ3huudoU2QGybzvAS6VvIb\ng/8nN82SVcKi98lur+duXWBh8WvHOjDcOy8qrNa/QlEgsSuFrR2hyhsUA6Y0vRvA\n31k8x304+XThahM2SPZ3oqg3ucKnZT86CVqfWTrP4z834QhyBbzA8kvftfD/+u7J\nQ6NkTCdHitWGlsYSsQbdtxPp7a8LrCqWHwUJHFBJBzCi2gHsy+v0LDjuqDqT8ryD\n/I2OkKnUyJLGu5qPRTh90uEIQfoUoFnqg39cYpZ2R3r44ePbIlr9rrCPILllpk3I\n3mbSm7NB1oIpy5r/Pv9juf4FH3bcUL3VLZxUjXcHSh6qQ08MX2UU/1tWasbkEYF9\nH3B3cYg5r2tA4CdXdEq9scYpXM5FDQ5eCYo6yqflqsD/WqRlnusneHVuHIE9MJ0C\nQxeE0lfp9XGYegPTtaDDuXq7xmiEPyIBB0lIHrweDGDr/r2LeQjtGyNifAXE6S6T\nHvqQRgd0CNDbAQ3KoBdBAoIBAQDZDkScILf4rkQNPY9iC5MJZ+wbehFQUdfz8ipG\nrkmDMPM3dwYHvscrgghajOro0WnXK8dzj9wJFl+ib/beeZLh3jTAC7bhPw8irN67\n0TYmjOxm7UL1Mm88mmxEizU/zHQ5p3dLg2RZliKZ2ZIiojMof/ikP7zW1X+cbn0E\nYyE530H+2UDXvjlcpY9woV27UNp9QwAa08IDU9XP2ylNWkXPbTCz91aazcrAPbeM\nPmprXaHXf7d8MrkIpQZYlqsOz5IOYTqSpFHtIPDUGfc5/3TP2Xzjmy/GAU9xJZwT\nawLKt1t/A/FUBh/x0wI1m5v/hzqdz00n32p3tNEwDD4hRloJAoIBAQDRU7smzo1l\nz3YGwOojwRWjrY6QPCTaIaY1iFNz94NB1UrC+F2WBDUIRhehFkB8f3EMClHmlBAn\nTaQaCcqgo0EupDdoTTti2ForBICLL4PQkEfCaeCEz0pND7wRT9p7V8+Non3gBlD2\nMQxiVd9v2HbCW1XiZwuxQpKwQojk8PeLq8ExEi+ovdxQOQOgA1k4svO7NgQMrHJ6\nvTi2p4GjHnCTZw/bpSdbULqdE95wyKdDEyNWvD83Vgr8phl918K6H7kXkolBGrIk\nQa4ulVC9HEM4cRoUIxi561eSjKJUvisryJ8IrU8vB7g5uLArwsqMFNijhdI+tm1B\nMCQQMqHaJz9fAoIBAQDG89wRm6/lop4/4KhXfzJ0UaxKdzX9gDdIpDT5+nDpbmnQ\n8ik12jmneJX9oeMEKkcwcjFsjHVsYvSf6K7It3jZzZpeWZ50kh9mcjvqvdY7ubpK\nkblpFKR/UTBiF5NkehwiaIzhS3sk3oeyq4nWcwQfYEVhEAcgiCtjEKdI6TAgYrKU\nTUCxP+xGLn7vBwnqUy8h19L1xBm2gRafYkxWWaNZgMU+gD0CwhTQ5wEh7GgRJ47b\n/3YIwll2QgUyGFCMz9gZlCdjGHj7uNDmKTLCF5RTnA9sdOdyP+s4U/femJzDgRO9\ntbhzgvWu6/G/f3Wa37HryoL3RELLnJKNzvr39ws5AoIBAQDN9efIGCWyDf2gSYJX\nKa1D/gmuyy6rXb5vH7KVAO0qAlZsHfnfGEah3G11dzJ+DNrLMQBCsl5ufYtAf2/a\nvKbu4G8P9iW/bQbTGrvrtxWoSb4BgTGDG36M8jVmhz5+a/jw7/eQTEau5bW8r6eI\nIeE//KQ1fpRXlhxEx0JwmNPInncY9D7mdeDnIiH5+DF6g0Eja9NyMN72+2Vo+smo\nGNRFhHtq70YZKAZldV5BdHx2l8cGmXRN2yA2VKvyUS/s+DejBPB9mWm8GM/sT8hA\nOiW9zDMPqzSyAeiJbkxuuyo5C03HONcXfC38xUa52BB44i4CPzKNt+sp39csBNWR\nk1pbAoIBAQCve/u1n2K5yvx4q1IWnR1RdC2xXuFm4U1ZLfg+7iBQZmL8c0bNXx+q\n9cX5rKp7P+dkVlNG2B99jdJuzWTY3KxvGOBdfiRlRHV1LFv8I40KwddloIsfKSAI\nNQXW7EwTp3D3GvL1ikY2Ac3oCH4soLUP/VKgj+qLGaE3FEYvEat4uDkL87jX3nLl\n9bEiF4ejsQ22zH99X3Dl2cL767MleQvQno9n8JtntKRLusOU8emQm7G6HIGSwyrq\n6pn3S2rrCp/1fGGFHKv/FAZ6/6UAi+NnI5abnKN2tl7jjSzFIH1o/sVQBJtoE+A3\njFLQEGjR6Nr63A4PRTlAVsCZTxb5EMiS\n-----END PRIVATE KEY-----\")\n\n(util/fiveam:def-suite)\n\n(test make-ssl-context\n  (finishes\n   (make-ssl-context\n    :certificate *test-cert*))\n  (is\n   (eql\n    (make-ssl-context\n     :certificate *test-cert*)\n    (make-ssl-context\n     :certificate *test-cert*))))\n\n(def-fixture state ()\n  (let ((port (util/random-port:random-port)))\n   (uiop:with-temporary-file (:pathname key :stream s)\n     (write-string *test-key* s)\n     (finish-output s)\n     (uiop:with-temporary-file (:pathname cert :stream s)\n       (write-string *test-cert* s)\n       (finish-output s)\n       (&body)))))\n\n(test happy-path-attaching-stream\n  (with-fixture state ()\n    (let ((server-context (comm:create-ssl-server-context\n                           :key-file key\n                           :cert-file cert))\n          (client-context (make-ssl-context :certificate *test-cert*)))\n     (let ((process (comm:start-up-server\n                     :function (lambda (socket)\n                                 (let ((stream\n                                         (comm:create-ssl-socket-stream\n                                          socket server-context)))\n                                   (format stream \"hello world~%\")\n                                   (finish-output stream)))\n                     :service port)))\n       (unwind-protect\n            (let ((stream (comm:open-tcp-stream \"localhost\" port\n                                                :ssl-ctx client-context)))\n              (is (equal \"hello world\" (read-line stream))))\n         (comm:server-terminate process))))))\n"
  },
  {
    "path": "src/util/tests/test-throttler.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-throttler\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/throttler\n                #:close-to-throttling-limit\n                #:keyed-throttler\n                #:%tokens\n                #:throttled-error\n                #:throttled-funcall\n                #:throttler\n                #:with-throttling)\n  (:import-from #:local-time\n                #:universal-to-timestamp))\n(in-package :util/tests/test-throttler)\n\n\n(util/fiveam:def-suite)\n\n(defclass my-throttler (throttler)\n  ())\n\n(def-fixture state ()\n  (let ((throttler (make-instance 'my-throttler :tokens 20)))\n    (&body)))\n\n(test simple-call\n  (with-fixture state ()\n    (is (eql :done\n             (with-throttling (throttler)\n               :done)))))\n\n(test simple-call-with-key\n  (with-fixture state ()\n    (let ((throttler (make-instance 'keyed-throttler\n                                    :tokens 100)))\n      (is (eql :done\n               (with-throttling (throttler :key 20)\n                 :done))))))\n\n(test throttles\n  (with-fixture state ()\n    (let ((throttler (make-instance 'my-throttler :tokens 10\n                                                  :period 1000\n                                                  :now 1000))\n          (ctr 0))\n      (dotimes (i 10)\n        (throttled-funcall throttler (lambda ()\n                                       (incf ctr))\n                           :now (+ 1000 i)))\n      (is (eql 10 ctr))\n      (is (< (%tokens throttler) 1))\n      (signals throttled-error\n       (throttled-funcall throttler (lambda ()\n                                      (incf ctr))\n                          :now 1013))\n      (throttled-funcall throttler (lambda ()\n                                     (incf ctr))\n                         :now 1102))))\n\n(test we-gracefully-add-more-tokens\n  \"This is different from the strict N request per T time.\"\n  (with-fixture state ()\n    (let ((throttler (make-instance 'my-throttler :tokens 10\n                                                  :period 100\n                                                  :now 1000))\n          (ctr 0))\n      (dotimes (i 10)\n        (throttled-funcall throttler (lambda ()\n                                       (incf ctr))\n                           :now (+ 1000 i)))\n      (is (eql 10 ctr))\n      (is (< (%tokens throttler) 1))\n\n      (throttled-funcall throttler (lambda ()\n                                     (incf ctr))\n                         :now 1030))))\n\n\n(test we-dont-exceed-token-limit\n  (with-fixture state ()\n    (let ((throttler (make-instance 'my-throttler :tokens 10\n                                                  :period 1000\n                                                  :now 0))\n          (ctr 0))\n      (dotimes (i 10)\n        (throttled-funcall throttler (lambda ()\n                                       (incf ctr))\n                           :now (+ 1000 i)))\n      (is (eql 10 ctr))\n      (is (< (%tokens throttler) 1))\n      (signals throttled-error\n       (throttled-funcall throttler (lambda ()\n                                      (incf ctr))\n                          :now 1013))\n      (throttled-funcall throttler (lambda ()\n                                     (incf ctr))\n                         :now 1102))))\n\n(test we-start-raising-a-warning-when-we-get-close-to-the-limit\n  (with-fixture state ()\n    (let ((throttler (make-instance 'my-throttler :tokens 10\n                                                  :period 1000\n                                                  :now 0))\n          (ctr 0)\n          (saw-warning-p nil))\n      (dotimes (i 6)\n        (throttled-funcall throttler (lambda ()\n                                       (incf ctr))\n                           :now (+ 1000 i)))\n      (handler-bind ((close-to-throttling-limit (lambda (w)\n                                                  (finishes\n                                                    (format nil \"~a\" w))\n                                                  (setf saw-warning-p t))))\n        (throttled-funcall throttler (lambda ()\n                                       (incf ctr))\n                           :now (+ 1000 8)))\n      (is-true saw-warning-p)\n      (is (eql 7 ctr)))))\n\n\n(test throttles-with-keyed-throttler\n  (with-fixture state ()\n    (let ((throttler (make-instance 'keyed-throttler :tokens 10\n                                                     :period 1000))\n          (ctr 0))\n      (dotimes (i 10)\n        (throttled-funcall throttler (lambda ()\n                                       (incf ctr))\n                           :now (+ 1000 i)\n                           :key \"foo\"))\n      (is (eql 10 ctr))\n      (signals throttled-error\n       (throttled-funcall throttler (lambda ()\n                                      (incf ctr))\n                          :now 1013\n                          :key \"foo\"))\n      (throttled-funcall throttler (lambda ()\n                                     (incf ctr))\n                         :now 1013\n                         :key \"bar\")\n      (throttled-funcall throttler (lambda ()\n                                     (incf ctr))\n                         :now 1102\n                         :key \"foo\"))))\n"
  },
  {
    "path": "src/util/tests/test-timeago.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-timeago\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/timeago\n                #:timeago))\n(in-package :util/tests/test-timeago)\n\n(util/fiveam:def-suite)\n\n(test timeago-works-on-nil\n  (is (equal nil\n             (timeago :timestamp nil))))\n"
  },
  {
    "path": "src/util/tests/test-truncated-stream.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-truncated-stream\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:util/truncated-stream\n                #:truncated-stream))\n(in-package :util/tests/test-truncated-stream)\n\n\n(util/fiveam:def-suite)\n\n(def-fixture state ()\n  (let* ((input (make-array 100 :initial-element 0 :element-type '(unsigned-byte 8)))\n         (input-stream (flex:make-in-memory-input-stream input)))\n    (&body)))\n\n(test simple-invocation ()\n  (with-fixture state ()\n    (let ((stream (make-instance\n                   'truncated-stream\n                   :delegate input-stream\n                   :bytes-left 10)))\n      (let* ((output (make-array 200)))\n        (is (eql 10 (read-sequence output stream)))))))\n\n(test longer-truncation-than-data ()\n  (with-fixture state ()\n    (let ((stream (make-instance\n                   'truncated-stream\n                   :delegate input-stream\n                   :bytes-left 1000)))\n      (let* ((output (make-array 200)))\n        (is (eql 100 (read-sequence output stream)))))))\n\n(test double-invocation ()\n  (with-fixture state ()\n    (let ((stream (make-instance\n                   'truncated-stream\n                   :delegate input-stream\n                   :bytes-left 10)))\n      (let* ((output (make-array 3)))\n        (is (eql 3 (read-sequence output stream)))\n        (is (eql 3 (read-sequence output stream)))\n        (is (eql 3 (read-sequence output stream)))\n        (is (eql 1 (read-sequence output stream)))))))\n"
  },
  {
    "path": "src/util/threading/fake-mp.lisp",
    "content": "(defpackage :util/fake-mp\n  (:use #:cl)\n  (:import-from #:util/misc\n                #:or-setf)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:process-p\n   #:process-wait-for-event\n   #:process-send\n   #:process-mailbox\n   #:process-wait))\n(in-package :util/fake-mp)\n\n\n(defvar *mailboxes* (trivial-garbage:make-weak-hash-table\n                     #+sbcl\n                     :synchronized #+sbcl t\n                     :weakness :key))\n\n(defun process-mailbox (process)\n  (gethash process *mailboxes*))\n\n(defun (setf process-mailbox) (mailbox process)\n  (setf (gethash process *mailboxes*) mailbox))\n\n(defun ensure-mailbox (process)\n  (or-setf\n   (process-mailbox process)\n   (mailbox:make-mailbox)\n   :thread-safe t))\n\n(defun process-p (process)\n  (bt:threadp process))\n\n(defun process-wait-for-event (&key no-hang-p)\n  (let ((mailbox (ensure-mailbox (bt:current-thread))))\n    (assert no-hang-p)\n    (mailbox:read-mail mailbox)))\n\n(defun process-send (process message)\n  (let ((mailbox (ensure-mailbox process)))\n    (mailbox:post-mail message mailbox)))\n\n(defun process-wait (wait-reason wait-fn)\n  (loop while (not wait-fn)\n        do (sleep 0.1)))\n"
  },
  {
    "path": "src/util/threading/tests/test-threading.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/tests/test-threading\n  (:use #:cl\n        #:fiveam\n        #:util/threading)\n  (:import-from #:util/threading\n                #:wait-for-zero-threads\n                #:*thread-count*\n                #:*propagated-symbols*\n                #:scheduled-future\n                #:schedule-timer\n                #:*trace-stream*\n                #:max-pool\n                #:build-extras\n                #:with-extras\n                #:*log-sentry-p*\n                #:ignore-error\n                #:%invoke-debugger\n                #:handle-error\n                #:*catch-errors-p*\n                #:funcall-with-sentry-logs)\n  (:import-from #:util/testing\n                #:with-global-binding)\n  (:import-from #:fiveam-matchers/core\n                #:assert-that)\n  (:import-from #:fiveam-matchers/lists\n                #:contains)\n  (:local-nicknames (#:a #:alexandria)))\n(in-package :util/tests/test-threading)\n\n\n(util/fiveam:def-suite)\n\n(defvar *ctr* 0)\n\n(def-fixture state ()\n  (let ((stream (make-string-output-stream)))\n    (with-global-binding ((*catch-errors-p* t)\n                          (*log-sentry-p* t)\n                          (*debugger-hook* *debugger-hook*)\n                          (*trace-stream* stream))\n      (unwind-protect\n           (&body)\n        (setf *ctr* 0)))))\n\n(test simple-create-thread\n  (with-fixture state ()\n   (let ((var nil))\n     (let ((thread (util/threading:make-thread\n                    (lambda ()\n                      (setf var t)))))\n       (bt:join-thread thread)\n       (is-true var)))))\n\n\n#+nil ;; Very flaky test\n(test safe-interrupt\n  (with-fixture state ()\n   (let* ((ctr 0)\n          (max-ctr 300)\n          (lock (bt:make-lock))\n          (cv (bt:make-condition-variable))\n          (callback-called-p nil))\n\n     (bt:with-lock-held (lock)\n      (let ((thread (bt:make-thread\n                     (lambda ()\n                       (with-safe-interruptable (:on-quit (lambda ()\n                                                            (setf callback-called-p t)))\n                         (bt:with-lock-held (lock)\n                           (bt:condition-notify cv))\n                         (loop for i below max-ctr\n                               do\n                                  (incf ctr)\n                                  (safe-interrupt-checkpoint)\n                                  (sleep 0.1)))))))\n        (bt:condition-wait cv lock)\n        (safe-interrupt thread)\n        (bt:join-thread thread)\n        (is-true callback-called-p)\n        (is (< ctr (/ max-ctr 2))))))))\n\n(def-fixture sentry-mocks ()\n  (cl-mock:with-mocks ()\n    ;; don't print the stack traces to the test output\n    (cl-mock:if-called 'trivial-backtrace:print-backtrace\n                        (lambda (e &key output)))\n\n    (let ((hunchentoot:*catch-errors-p* t))\n      (let ((sentry-logs nil))\n        #-screenshotbot-oss\n        (cl-mock:if-called\n         'sentry-client:capture-exception\n         (lambda (e &key extras tags)\n           (declare (ignore extras))\n           (push e sentry-logs)))\n       (&body)))))\n\n(define-condition my-simple-warning (warning)\n  ())\n\n(define-condition my-simple-error (error)\n  ())\n\n#-screenshotbot-oss\n(test sentry-logging-for-make-thread\n  (with-fixture state ()\n   (with-fixture sentry-mocks ()\n     (funcall-with-sentry-logs\n      (lambda ()\n        (warn 'my-simple-warning)))\n     (is (eql 1 (length sentry-logs)))\n     (signals my-simple-error\n       (funcall-with-sentry-logs\n        (lambda ()\n          (error 'my-simple-error))))\n     (is (eql 2 (length sentry-logs))))))\n\n#-screenshotbot-oss\n(test large-number-of-warnings\n  (with-fixture state ()\n   (with-fixture sentry-mocks ()\n     (funcall-with-sentry-logs\n      (lambda ()\n        (loop for i below 100 do\n          (warn\n           'my-simple-warning))))\n     (is (eql 5 (length sentry-logs))))))\n\n\n(test handle-error-if-not-catch-errors-p\n  (with-fixture state ()\n    (with-fixture sentry-mocks ()\n      (let (debugger-hook-called-p)\n        (setf *catch-errors-p* nil)\n        (setf *debugger-hook* (lambda (e old-debugger-hook)\n                                (error \"never called\")))\n        (cl-mock:if-called '%invoke-debugger\n                            (lambda (e)\n                              (setf debugger-hook-called-p :seen-1)))\n        (restart-case\n            (handle-error (make-instance 'error))\n          (ignore-error ()\n            (fail \"got abort, when we shouldn't have\")))\n        (is-true debugger-hook-called-p)))))\n\n(test handle-error-if-catch-errors-p\n  (with-fixture state ()\n    (with-fixture sentry-mocks ()\n      (let (debugger-hook-called-p\n            abort-called-p)\n        (setf *catch-errors-p* t)\n        (setf *debugger-hook* (lambda (e h)))\n        (cl-mock:if-called '%invoke-debugger\n                            (lambda (e)\n                              (setf debugger-hook-called-p :seen)))\n        (restart-case\n            (handle-error (make-instance 'error))\n          (ignore-error ()\n            (setf abort-called-p t)))\n        (is-false debugger-hook-called-p)\n        (is-true abort-called-p)))))\n\n\n(test ignore-and-log-errors-when-not-used-at-toplevel\n  (with-fixture state ()\n    (with-global-binding ((*log-sentry-p* nil))\n     (let ((called nil))\n       (let ((thread (bt:make-thread\n                      (lambda ()\n                        (ignore-and-log-errors ()\n                          (error \"blah blah from test-threading\"))\n                        (setf called t)))))\n         (bt:join-thread thread)\n         (is-true called))))))\n\n\n(test with-extras-does-not-crash\n  (with-fixture state ()\n   (let ((body-called-p nil))\n     (with-extras ((\"name\" (error \"foo\")))\n       (finishes\n         (build-extras nil))))))\n\n\n(test with-extras\n  (with-fixture state ()\n   (let ((body-called-p nil))\n     (with-extras ((\"name\" \"bleh\"))\n       (setf body-called-p t)\n       (assert-that\n        (build-extras nil)\n        (contains '(\"name\" . \"bleh\"))))\n     (is-true body-called-p))))\n\n(test extras-are-bound-late\n  (with-fixture state ()\n    (let ((count 0))\n      (with-extras ((\"name\" count))\n        (incf count)\n        (assert-that (build-extras nil)\n                     (contains '(\"name\" . \"1\")))\n        (incf count)\n        (assert-that (build-extras nil)\n                     (contains '(\"name\" . \"2\")))))))\n\n(defvar *lock* (bt:make-lock))\n\n(test max-pool\n  (with-fixture state ()\n    (let ((max-pool (make-instance 'max-pool :max 3))\n          (thread-count 100))\n      (let* ((promises (loop for i from 0 below thread-count\n                             collect (make-thread\n                                      (lambda ()\n                                        (bt:with-lock-held (*lock*)\n                                          (incf *ctr*)))\n                                      :pool max-pool)))\n             (threads (loop for promise in promises\n                            collect (lparallel:force promise))))\n        (dolist (thread threads)\n          (bt:join-thread thread))\n        (is (eql thread-count *ctr*))))))\n\n\n(test max-pool-with-errors\n  (with-fixture state ()\n    (with-global-binding ((*log-sentry-p* nil))\n     (let ((max-pool (make-instance 'max-pool :max 3))\n           (thread-count 100))\n       (let* ((promises (loop for i from 0 below thread-count\n                              collect (make-thread\n                                       (lambda ()\n                                         (bt:with-lock-held (*lock*)\n                                           (incf *ctr*))\n                                         (error \"foobar\"))\n                                       :pool max-pool)))\n              (threads (loop for promise in promises\n                             collect (lparallel:force promise))))\n         (dolist (thread threads)\n           (bt:join-thread thread))\n         (is (eql thread-count *ctr*)))))))\n\n(defclass fake-sentry ()\n  ((captured :initform nil\n             :accessor captured)))\n\n(defmethod sentry-client::client-capture-exception ((self fake-sentry) e &rest args &key &allow-other-keys)\n  (push e (captured self)))\n\n#-(or screenshotbot-oss eaase-oss)\n(test max-pool-logs-errors\n  (with-fixture state ()\n    (with-global-binding ((*log-sentry-p* t)\n                          (*catch-errors-p* t)\n                          (sentry-client::*sentry-client* (make-instance 'fake-sentry))\n                          (ctr 0))\n      (let ((max-pool (make-instance 'max-pool :max 3)))\n        (let ((future-thread (make-thread\n                              (lambda ()\n                                (incf ctr)\n                                (error \"foobar1\")\n                                (incf ctr))\n                              :pool max-pool)))\n          (bt:join-thread (lparallel:force future-thread)))\n        (is (eql 1 ctr))\n        (is (eql 1 (length (captured sentry-client::*sentry-client*))))\n        (finishes\n         (wait-for-pool max-pool))))))\n\n(test simple-timer-test\n  (dotimes (i 3)\n    (let ((promise (lparallel:promise)))\n      (schedule-timer 0\n                      (lambda ()\n                        (lparallel:fulfill\n                            promise\n                          :done)))\n      (is (eql :done (lparallel:force promise))))))\n\n(test simple-timer-test-in-parallel\n  (let* ((count 10)\n         (promises (loop for i below count collect (lparallel:promise))))\n    (dotimes (i count)\n      (util:copying (i)\n       (schedule-timer 0\n                       (lambda ()\n                         (lparallel:fulfill\n                             (elt promises i)\n                           :done)))))\n    (dotimes (i count)\n      (is (eql :done (lparallel:force (elt promises i)))))))\n\n(test scheduled-future\n  (is (eql\n       :done\n       (lparallel:force\n        (scheduled-future (0)\n          :done)))))\n\n(test scheduled-future-negative\n  (is (eql\n       :done\n       (lparallel:force\n        (scheduled-future (-100)\n          :done)))))\n\n\n(defvar *x*)\n\n(defvar *x-with-default* :global-value)\n\n(test propagated\n  (with-fixture state ()\n    (let ((*propagated-symbols* '(*x*)))\n      (let ((*x* :from-test)\n            (got-val))\n        (let ((thread (make-thread\n                       (lambda ()\n                         (setf got-val *x*)))))\n          (bt:join-thread thread))\n        (is (eql :from-test got-val))))))\n\n(test propagated-even-with-defaults\n  (with-fixture state ()\n    (let ((*propagated-symbols* '(*x-with-default*)))\n      (let ((*x-with-default* :from-test)\n            (got-val))\n        (let ((thread (make-thread\n                       (lambda ()\n                         (setf got-val *x-with-default*)))))\n          (bt:join-thread thread))\n        (is (eql :from-test got-val))))))\n\n(test test-threading-count\n  (with-fixture state ()\n    (wait-for-zero-threads)\n    (let ((inner-count))\n      (let ((thread (make-thread\n                     (lambda ()\n                       (setf inner-count *thread-count*)))))\n        (Bt:join-thread thread))\n      (is (eql 1 inner-count)))))\n\n(test wait-for-zero-threads\n  (with-fixture state ()\n    (dotimes (i 10)\n      (make-thread\n       (lambda ())))\n    (wait-for-zero-threads)\n    (is (eql 0 *thread-count*))))\n"
  },
  {
    "path": "src/util/threading/threading.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/threading\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:local-nicknames (#:a #:alexandria)\n                    #-lispworks\n                    (#:mp :util/fake-mp))\n  (:export\n   #:call-with-thread-fixes\n   #:make-thread\n   #:with-safe-interruptable\n   #:safe-interrupt-checkpoint\n   #:safe-interrupt\n   #:log-sentry\n   #:*catch-errors-p*\n   #:*log-sentry-p*\n   #:*extras*\n   #:ignore-and-log-errors\n   #:*warning-count*\n   #:with-extras\n   #:with-tags\n   #:wait-for-pool\n   #:*propagated-symbols*)\n)\n(in-package :util/threading)\n\n(defvar *trace-stream* *debug-io*)\n(defvar *catch-errors-p* t)\n(defvar *log-sentry-p* t)\n\n(defvar *message-handlers* nil)\n\n(defmacro with-message-handlers (name-and-fns &body body)\n  (cond\n    (name-and-fns\n     `(call-with-message-handler\n       ',(caar name-and-fns)\n        ,(cadar name-and-fns)\n       (lambda ()\n         (with-message-handlers ,(cdr name-and-fns)\n           ,@body))))\n    (t\n     `(progn ,@body))))\n\n(defun call-with-message-handler (name fn body)\n  (let ((*message-handlers*\n          (list* (cons name fn)\n                 *message-handlers*)))\n    (funcall body)))\n\n(defmacro with-safe-interruptable ((&key on-quit) &body body)\n  \"Create a safe interruptable code. The interrupt can only happen at\ncheckpoints called by `(safe-interrupte-checkpoint)`\"\n  `(call-with-safe-interruptable (lambda () ,@body)\n                                 :on-quit ,on-quit))\n\n(define-condition safe-interrupt ()\n  ())\n\n(defun call-with-safe-interruptable (fn &key on-quit)\n  (handler-case\n      (with-message-handlers ((quit (lambda ()\n                                      (signal 'safe-interrupt))))\n          (funcall fn))\n    (safe-interrupt ()\n      (when on-quit\n        (funcall on-quit)))))\n\n(defun safe-interrupt (process)\n  (assert (mp:process-p process))\n  (send-mail process 'quit))\n\n\n(defun safe-interrupt-checkpoint ()\n  (loop for msg = (mp:process-wait-for-event\n                   :no-hang-p t)\n        while msg\n        do (process-mail msg)))\n\n(defun process-mail (mail)\n  (log:info \"processing mail: ~A\" mail)\n  (destructuring-bind (message &rest args) mail\n    (let ((handler (a:assoc-value *message-handlers* message)))\n      (cond\n        (handler\n         (apply handler args))\n        (t\n         (log:warn \"No message handler for ~a\" message))))))\n\n\n\n(defmethod send-mail (process message &rest args)\n  (mp:process-send process\n                   `(,message ,@args)))\n\n(defun call-with-thread-fixes (fn)\n  (funcall fn))\n\n(defvar *warning-count* 0)\n\n(defvar *extras* nil)\n(defvar *tags* nil)\n\n(defun build-extras (condition &key (extras *extras*))\n  ;; If the extras are nil, it probably means some calback is not\n  ;; returning a list\n  (ignore-errors\n   (loop for extra in extras\n         appending (ignore-errors (funcall extra condition)))))\n\n(defmacro with-extras-impl (var (&rest pairs) &body body)\n  `(let* ((,var (list*\n                 ,@ (loop for (name body) in pairs\n                          collect\n                          `(lambda (e)\n                             (declare (ignore e))\n                             (list\n                              (cons ,name (format nil \"~a\"\n                                                  (progn\n                                                    ,body))))))\n                    ,var)))\n     (progn ,@body)))\n\n(defmacro with-extras ((&rest pairs) &body body)\n  `(with-extras-impl *extras* ,pairs ,@body))\n\n(defmacro with-tags ((&rest pairs) &body body)\n  `(with-extras-impl *tags* ,pairs ,@body))\n\n(defun %log-sentry (condition)\n  #-screenshotbot-oss\n  (let ((tags (build-extras condition :extras *tags*)))\n    (sentry-client:capture-exception condition :extras (build-extras condition)\n                                               :tags tags)))\n\n(defmethod log-sentry (condition)\n  (when *catch-errors-p*\n    (%log-sentry condition)))\n\n(defmethod log-sentry :around ((warning warning))\n  (when (<= (incf *warning-count*) 5)\n    (call-next-method)))\n\n(defun maybe-log-sentry (condition)\n  (when *log-sentry-p*\n    (log-sentry condition)))\n\n\n(defun funcall-with-sentry-logs (fn)\n  (let ((*warning-count* 0))\n   (handler-bind ((error #'maybe-log-sentry)\n                  (warning #'maybe-log-sentry))\n     (funcall fn))))\n\n\n(defun %invoke-debugger (e)\n  ;; mockable version of invoke-debugger\n  (invoke-debugger e))\n\n(defun handle-error (e)\n  (cond\n    ((and\n      (not *catch-errors-p*)\n      *debugger-hook*)\n     ;; Small edge case: SWANK/SLYNK might still try\n     ;; to redelegate to the\n     ;; default debugger which can\n     ;; cause the process to crash\n     (%invoke-debugger e))\n    (t\n     (trivial-backtrace:print-backtrace\n      e\n      :output *trace-stream*)\n     (invoke-restart 'ignore-error))))\n\n(def-easy-macro ignore-and-log-errors (&fn fn)\n  (restart-case\n      (handler-bind ((error #'handle-error))\n        (funcall-with-sentry-logs fn))\n    (ignore-error ()\n      (values))))\n\n(defclass unlimited-pool ()\n  ())\n\n(defvar *thread-count* 0\n  \"Keep track of active threads, just for testing purposes.\")\n(defvar *thread-count-lock* (bt:make-lock))\n(defvar *thread-count-cv* (bt:make-condition-variable))\n\n(defvar *unlimited-pool* (make-instance' unlimited-pool))\n\n(defvar *propagated-symbols* nil\n  \"A list of symbols whose value will always be propagated to child\nthreads.\")\n\n(defun incf-thread-count (delta)\n  (bt:with-lock-held (*thread-count-lock*)\n    (incf *thread-count* delta)\n    (bt:condition-notify *thread-count-cv*)))\n\n(defun wait-for-zero-threads ()\n  (bt:with-lock-held (*thread-count-lock*)\n    (loop until (eql *thread-count* 0)\n          do (unless (bt:condition-wait *thread-count-cv* *thread-count-lock* :timeout 60)\n               (error \"Thread count did not drop in time, there's probably some background threads blocking in a test\")))))\n\n(defun make-thread (body &key (pool *unlimited-pool*) name)\n  (let ((bindings (loop for sym in *propagated-symbols*\n                        if (boundp sym)\n                          collect (cons sym (symbol-value sym)))))\n    (incf-thread-count 1)\n    (make-thread-impl\n     pool\n     (lambda ()\n       (unwind-protect\n            (ignore-and-log-errors ()\n              (with-tags ((\"hostname\" (uiop:hostname)))\n                (progv\n                    (mapcar #'car bindings)\n                    (mapcar #'cdr bindings)\n                  (funcall body))))\n         (incf-thread-count -1)))\n     :name name)))\n\n(defmethod make-thread-impl ((self unlimited-pool) fn &key name)\n  (bt:make-thread\n   fn\n   :name name))\n\n(defclass max-pool-job ()\n  ((fn :initarg :fn\n     :reader fn)\n   (name :initarg :name\n         :reader job-name)\n   (promise :initarg :promise\n            :reader job-promise)))\n\n(defclass max-pool ()\n  ((max :initarg :max\n        :initform 100\n        :reader max-count)\n   (thread-count :initform 0\n                 :reader thread-count)\n   (queue :accessor queue\n          :initform (fset:empty-seq))\n   (lock :initform (bt:make-lock)\n         :reader lock))\n  (:documentation \"A thread pool that has a maximum number of parallel threads running.\"))\n\n(defmethod wait-for-pool ((self max-pool) &key (timeout 30))\n  \"Mostly for testing purposes, wait until the pool is inactive\"\n  (let ((end-time (+ timeout (get-universal-time))))\n    (loop until (bt:with-lock-held ((lock self))\n                  (= 0 (slot-value self 'thread-count)))\n          do\n             (when (> (get-universal-time) end-time)\n               (error \"Pool didn't end in time\"))\n             (sleep 0.05))))\n\n(defmethod maybe-process-queue ((self max-pool))\n  (bt:with-lock-held ((lock self))\n    (when (and\n           (< (thread-count self) (max-count self))\n           (not (fset:empty? (queue self))))\n      (let ((next (fset:first (queue self))))\n        (incf (slot-value self 'thread-count))\n        (lparallel:fulfill (job-promise next)\n          (bt:make-thread (lambda ()\n                            (unwind-protect\n                                 (funcall (fn next))\n                              (bt:with-lock-held ((lock self))\n                                (decf (slot-value self 'thread-count)))\n                              (maybe-process-queue self)))\n                          :name (job-name next)))\n        (setf (queue self) (fset:less-first (queue self)))))))\n\n(defmethod make-thread-impl ((self max-pool) fn &key name)\n  \"Unlike the unlimited pool, in this case we'll return a promise of a\nthread, not a thread itself.\"\n  (let* ((promise (lparallel:promise))\n         (job (make-instance 'max-pool-job\n                             :fn fn\n                             :name name\n                             :promise promise)))\n    (bt:with-lock-held ((lock self))\n      (setf (queue self)\n            (fset:with-last (queue self) job)))\n    (maybe-process-queue self)\n    promise))\n\n(defvar *timer-lock* (bt:make-lock))\n(defvar *timer-cv* (bt:make-condition-variable))\n(defvar *timers* (fset:empty-set))\n(defvar *next-timer-id* 0)\n\n(defclass timer-entry ()\n  ((ts :initarg :ts\n       :reader ts)\n   (timer-id :initform (incf *next-timer-id*))\n   (args :initarg :args\n         :reader args)))\n\n(defmethod fset:compare ((a timer-entry) (b timer-entry))\n  (fset:compare-slots a b\n                      'ts 'timer-id))\n\n(defun schedule-timer (timeout fn &key (pool *unlimited-pool*))\n  (bt:with-lock-held (*timer-lock*)\n    (let ((at-time (+ (get-universal-time) timeout)))\n      (log:debug \"Scheduling at ~a\" at-time)\n      (setf\n       *timers*\n       (fset:with *timers*\n                  (make-instance 'timer-entry\n                                 :ts at-time\n                                 :args (list fn :pool pool))))\n      (when (= 1 (fset:size *timers*))\n        ;; We're the first to be added, so create a thread\n        (make-thread\n         (lambda ()\n           (log:debug \"Creating timer thread\")\n           (timer-thread))\n         :name \"Timer thread\"))\n      (bt:condition-notify *timer-cv*))))\n\n(defun timer-thread ()\n  (bt:with-lock-held (*timer-lock*)\n    (loop\n      (let ((current-time (get-universal-time)))\n        (cond\n          ((fset:empty? *timers*)\n           ;; We're done for now\n           (return-from timer-thread nil))\n          (t\n           (let ((next (fset:least *timers*)))\n             (cond\n               ((<= (ts next) current-time)\n                (log:debug \"Timestamp is ready: ~a for ~a\"\n                           (ts next) current-time)\n                (apply #'make-thread (args next))\n                (setf *timers* (fset:less *timers* next)))\n               (t\n                (let ((secs (- (ts next) current-time)))\n                  (log:debug \"Sleeping for ~a\" secs)\n                  (bt:condition-wait *timer-cv* *timer-lock* :timeout secs)))))))))))\n\n(def-easy-macro scheduled-future (timeout &rest args &fn fn)\n  (let ((promise (lparallel:promise)))\n    (apply #'schedule-timer timeout\n           (lambda ()\n             (lparallel:fulfill promise\n               (funcall fn)))\n           args)\n    promise))\n"
  },
  {
    "path": "src/util/threading/util.threading.asd",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defsystem :util.threading\n  :depends-on (:bordeaux-threads\n               :mailbox\n               :str\n               :fset\n               :lparallel\n               :atomics\n               :log4cl\n               :trivial-backtrace\n               :sentry-client\n               :trivial-garbage\n               :easy-macros\n               :util.misc)\n  :serial t\n  :components ((:file \"fake-mp\")\n               (:file \"threading\")))\n\n(defsystem :util.threading/tests\n  :depends-on (:util.threading\n               :fiveam\n               :fiveam-matchers\n               :util/fiveam\n               :util.testing\n               :util\n               :cl-mock\n               :util.threading\n               :alexandria\n               :serapeum)\n  :serial t\n  :components ((:module \"tests\"\n                :components ((:file \"test-threading\")))))\n"
  },
  {
    "path": "src/util/throttler.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/throttler\n  (:use #:cl)\n  (:import-from #:easy-macros\n                #:def-easy-macro)\n  (:import-from #:local-time\n                #:timestamp-difference)\n  (:export\n   #:keyed-throttler\n   #:throttle!\n   #:ip-throttler))\n(in-package :util/throttler)\n\n(defclass throttler ()\n  ((max-tokens :initarg :tokens\n               :reader max-tokens\n               :documentation \"The max tokens per period\")\n   (period :initarg :period\n           :reader period\n           :initform 3600\n           :documentation \"interval in seconds\")\n   (name :initform \"unnamed-throttler\"\n         :initarg :name\n         :reader name)\n   ;; Internal\n   (tokens :initarg :tokens\n           :accessor %tokens\n           :documentation \"internal slot, for current number of tokens.\")\n   (last-updated :initform (get-universal-time)\n                 :initarg :now\n                 :accessor %last-updated)\n   (lock :initform (bt:make-lock)\n         :reader lock))\n  (:documentation \"A throttler using the Token Bucket algorithm. You specify the number\nof requests allowed (or tokens) per certain period. For instance you\nmight say 1000 requests per hour.\n\nIf the request is throttled, then an error is signaled.\"))\n\n(define-condition throttled-error (error)\n  ())\n\n(define-condition close-to-throttling-limit (warning)\n  ((name :initarg :name\n         :initform nil\n         :reader name))\n  (:report (lambda (e out)\n             (format out \"Close to throttling limit in throttler: ~a\" (name e)))))\n\n\n(defclass keyed-throttler ()\n  ((max-tokens :initarg :tokens\n               :initform (error \"must provide :tokens\")\n               :reader max-tokens)\n   (period :initarg :period\n           :reader period\n           :initform 3600\n           :documentation \"interval in seconds, defaults to an hour\")\n   (throttler-map :accessor throttler-map\n                  :initform (fset:empty-map))))\n\n(defmethod throttled-funcall ((self throttler) fn &key (now (get-universal-time))\n                                                    key)\n  (throttle! self :now now :key key)\n  (funcall fn))\n\n(defmethod throttle! ((self throttler) &key key (now (get-universal-time)))\n  (declare (ignore key))\n  (bt:with-lock-held ((lock self))\n    (incf (%tokens self)\n          (* (- now (%last-updated self)) (/ (max-tokens self) (period self))))\n    (setf (%last-updated self) now)\n    (when (< (%tokens self) 1)\n      (error 'throttled-error))\n    (when (> (%tokens self) (max-tokens self))\n      (setf (%tokens self) (max-tokens self)))\n    (when (< (%tokens self) (floor (max-tokens self) 2))\n      (warn 'close-to-throttling-limit :name (name self)))\n    (decf (%tokens self))))\n\n(defmethod throttler-for-key ((self keyed-throttler) key &key now)\n  (with-slots (throttler-map) self\n    (let ((existing (fset:@ throttler-map key)))\n      (cond\n        (existing\n         existing)\n        (t\n         (atomics:atomic-update\n          throttler-map\n          (lambda (old)\n           (fset:with\n            old key (make-instance 'throttler\n                                   :tokens (max-tokens self)\n                                   :period (period self)\n                                   :now now))))\n         (fset:@ throttler-map key))))))\n\n(defmethod throttle! ((self keyed-throttler) &key (now (get-universal-time))\n                                               key)\n  (unless key\n    (error \"Must provide :key for a keyed-throttler\"))\n  (let ((throttler (throttler-for-key self key :now now)))\n    (throttle! throttler :now now)))\n\n(defmethod throttle! :around (self &key now key)\n  (declare (ignore now key))\n  (restart-case\n      (call-next-method)\n    (ignore-throttling ()\n      (values))))\n\n(defmethod throttled-funcall ((self keyed-throttler) fn &key (now (get-universal-time))\n                                                          key)\n  (throttle! self :now now :key key)\n  (funcall fn))\n\n(def-easy-macro with-throttling (throttler &key key &fn fn)\n  (throttled-funcall throttler #'fn :key key))\n\n(defclass ip-throttler (keyed-throttler)\n  ()\n  (:default-initargs :tokens 10))\n\n(defmethod throttle! ((self ip-throttler) &key key &allow-other-keys)\n  (call-next-method\n   self\n   ;; For test convenience, we ignore when requests are unbound.\n   :key (or\n         (ignore-errors\n          (hunchentoot:real-remote-addr))\n         \"empty\")))\n"
  },
  {
    "path": "src/util/timeago.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/timeago\n  (:use #:cl)\n  (:import-from #:local-time\n                #:format-timestring)\n  (:local-nicknames (#:a #:alexandria))\n  (:export\n   #:timeago\n   #:human-render-local-time))\n(in-package :util/timeago)\n\n(named-readtables:in-readtable markup:syntax)\n\n(defvar *ts-format-cache* (make-hash-table))\n\n(defvar *current-year*\n  (local-time:timestamp-year (local-time:now)))\n\n(defun format-ts (timestamp)\n  (let ((key (cond\n               ((numberp timestamp)\n                timestamp)\n               (t\n                (local-time:timestamp-to-universal timestamp)))))\n   (util:or-setf\n    (gethash key *ts-format-cache*)\n    (format nil\n            (format nil \"~a\"\n                    (local-time:universal-to-timestamp key))))))\n\n\n(defun human-render-local-time (local-time)\n  (cond\n    ((eql *current-year*\n          (local-time:timestamp-year local-time))\n     (format-timestring nil local-time\n                        :format '(:long-month \" \" :day)))\n    (t\n     (format-timestring nil local-time\n                        :format '(:long-month \" \" :day \" \" :year)))))\n\n(markup:deftag timeago (&key timestamp)\n  (when timestamp\n   (multiple-value-bind (key local-time)\n       (cond\n         ((numberp timestamp)\n          (values timestamp (local-time:universal-to-timestamp timestamp)))\n         (t\n          (values (local-time:timestamp-to-universal timestamp) timestamp)))\n     (let* ((timestamp (format-ts key)))\n       <:time class= (when (> key (- (get-universal-time) (* 30 24 3600))) \"timeago\") datetime= timestamp title= (format-timestring nil local-time :format local-time:+rfc-1123-format+) >,(human-render-local-time local-time)</:time>))))\n"
  },
  {
    "path": "src/util/tools.el",
    "content": "\n\n(defun arnold-inspect-objects-of-type ()\n  (interactive)\n  (let ((type (thing-at-point 'sexp)))\n    (sly-inspect (format \"(reverse (bknr.datastore:class-instances '%s)))\" type))))\n\n(defun arnold-delete-bknr-object ()\n  (interactive)\n  (when (yes-or-no-p \"Delete the BKNR object? \")\n    (sly-inspector-eval \"(bknr.datastore:delete-object *)\")\n    (message \"Deleted.\")))\n\n(define-key sly-inspector-mode-map\n  (kbd \"<delete>\") 'arnold-delete-bknr-object)\n\n(defun arnold-update-bknr-slot ()\n  )\n\n(defun arnold-compile-function-at-point ()\n  (interactive)\n  (let ((fn-name (thing-at-point 'sexp)))\n    (sly-eval `(cl:let ((sym (cl:find-symbol ,(upcase fn-name)\n                                             ,(read (sly-current-package)))))\n                       (cl:cond\n                        (sym\n                         (cl:compile sym))\n                        (cl:t\n                         (cl:error \"function not defined: ~a (~a ~a)\" sym ,fn-name ,(read (sly-current-package)))))))\n    (message \"Compiled function\")))\n\n(defun arnold-inspect-useful-pointers ()\n  (interactive)\n  (sly-inspector-eval\n   \"(cl:progn\n     (system::x-find-useful-pointer *)\nnil)\")\n  (message \"Done finding useful pointers\")\n  (sly-inspect \"system::*d*\")\n  ;;(sly-eval `(cl:setf system::*d* nil))\n  )\n\n(defun arnold--lisp-mode-hook ()\n\n  ;; (hs-minor-mode t)\n  ;; (save-excursion\n  ;;   (goto-char 0)\n  ;;   (hs-hide-block)\n  ;;   (search-forward \"(\")\n  ;;   (hs-hide-block))\n  )\n\n(add-hook 'lisp-mode-hook\n          'arnold--lisp-mode-hook)\n\n(defun arnold/clean-up-defpackage ()\n  (interactive)\n  (sly-eval `(ql:quickload :util/symbol-detector))\n  (save-excursion\n    (let* ((filename (buffer-file-name))\n           (package\n            (sly-eval\n             `(util/symbol-detector::generate-defpackage\n               ,filename))))\n      (goto-char 0)\n      (re-search-forward \"\\\\(defpackage\\\\|uiop:define-package\\\\)\")\n      (goto-char (line-beginning-position))\n      (paredit-kill)\n      (save-excursion\n        (insert package)\n        ;; Delete the inserted whitespace\n        (paredit-backward-delete))\n      (indent-sexp))))\n\n(defun arnold/inspect-symbol ()\n  (interactive)\n  (sly-inspect (format \"'%s\" (upcase (thing-at-point 'symbol)))))\n"
  },
  {
    "path": "src/util/truncated-stream.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/truncated-stream\n  (:use #:cl)\n  (:local-nicknames #-lispworks\n                    (#:stream #:trivial-gray-streams)))\n(in-package :util/truncated-stream)\n\n(defclass truncated-stream (stream:fundamental-input-stream)\n  ((delegate :initarg :delegate\n             :reader delegate)\n   (bytes-left :initarg :bytes-left\n               :accessor bytes-left))\n  (:documentation \"A stream that is truncated to BYTES-LEFT bytes. Reading beyond this\nwill return EOF. Closing the stream closes the underlying stream.\"))\n\n(defmethod stream::stream-element-type ((self truncated-stream))\n  (stream::stream-element-type (delegate self)))\n\n(defmethod stream:stream-read-sequence ((self truncated-stream)\n                                        sequence\n                                        start\n                                        end\n                                        #-lispworks #-lispworks\n                                                    &key &allow-other-keys)\n  (cond\n    ((= 0 (bytes-left self))\n     start)\n    (t\n     (let ((result (stream:stream-read-sequence (delegate self) sequence\n                                                start\n                                                (min\n                                                 end\n                                                 (+ start (bytes-left self))))))\n       (decf (bytes-left self)\n             (- result start))\n       result))))\n\n\n(defmethod stream:stream-finish-output ((self truncated-stream))\n  (log:debug \"finishing output\")\n  (stream:stream-finish-output (delegate self)))\n\n(defmethod stream:stream-force-output ((self truncated-stream))\n  (log:debug \"forcing output\")\n  (stream:stream-force-output (delegate self)))\n\n\n\n\n\n"
  },
  {
    "path": "src/util/utf-8.lisp",
    "content": ";;;; Copyright 2018-Present Modern Interpreters Inc.\n;;;;\n;;;; This Source Code Form is subject to the terms of the Mozilla Public\n;;;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;;;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(defpackage :util/utf-8\n  (:use #:cl))\n(in-package :util/utf-8)\n\n(lw:set-default-character-element-type 'character)\n\n(defun use-utf-8-for-all-lisp-files (pathname ext-format-spec first-byte max-extent)\n  (cond\n    ((equal \"lisp\" (pathname-type pathname))\n     :utf-8)\n    (t ext-format-spec)))\n\n(push 'use-utf-8-for-all-lisp-files system:*file-encoding-detection-algorithm*)\n"
  },
  {
    "path": "src/util/util.asd",
    "content": "(defpackage :util-system\n  (:use :cl\n   :asdf))\n(in-package :util-system)\n\n(defclass lib-source-file (c-source-file)\n  ((extra-args :initarg :extra-args\n               :initform nil\n               :reader extra-args)))\n\n(defun default-foreign-library-type ()\n  \"Returns string naming default library type for platform\"\n  #+(or win32 win64 cygwin mswindows windows) \"dll\"\n  #+(or macosx darwin ccl-5.0) \"dylib\"\n  #-(or win32 win64 cygwin mswindows windows macosx darwin ccl-5.0) \"so\"\n)\n\n(defmethod output-files ((o compile-op) (c lib-source-file))\n  (let ((library-file-type\n          (default-foreign-library-type)))\n    (list (make-pathname :name (component-name c)\n                         :type library-file-type\n                         :defaults (asdf:component-pathname c)))))\n\n(defmethod perform ((o load-op) (c lib-source-file))\n  t)\n\n(defmethod perform ((o compile-op) (c lib-source-file))\n  (uiop:run-program (list* \"gcc\" \"-shared\"\n                           \"-o\" (namestring (car (output-files o c)))\n                           \"-Werror\"\n                           \"-fPIC\"\n                           (namestring\n                            (asdf:component-pathname c))\n                           (extra-args c))\n                    :output t\n                    :error-output t))\n\n\n\n(defsystem \"util\"\n  :depends-on (\"hunchentoot\"\n               \"markup\"\n               \"nibble\"\n               \"easy-macros\"\n               \"alexandria\"\n               \"uuid\"\n               \"tmpdir\"\n               \"cl-mop\"\n               \"secure-random\"\n               \"cl-base32\"\n               \"bknr.datastore\"\n               \"cl-csv\"\n               \"cl-smtp\"\n               \"util/cron\"\n               \"drakma\"\n               \"cl-json\"\n               \"hunchentoot-extensions\"\n               #-(or screenshotbot-oss eaase-oss)\n               \"stripe\"\n               \"log4cl\"\n               \"util.threading\"\n               \"util.store\"\n               \"util/emacs\"\n               \"util.misc\"\n               \"util/html2text\"\n               \"util/random-port\"\n               (:feature (:and :lispworks (:or :darwin :linux)) \"util/memory\")\n               \"cl-cron\")\n  :serial t\n  :components ((:file \"ret-let\")\n               (:file \"copying\")\n               (:file \"make-instance-with-accessors\")\n               (:file \"cookies\")\n               (:file \"bind-form\")\n               (:file \"cdn\")\n               (:file \"package\")\n               (:file \"mockable\")\n               (:file \"asdf\")\n               #- (or screenshotbot-oss eaase-oss)\n               (:file \"payment-method\")\n               (:file \"countries\")\n               (:file \"google-analytics\")\n               (:file \"mail\")\n               (:file \"download-file\")\n               (:file \"uuid\")\n               (:file \"acceptor\")\n               (:file \"mquery\")\n               (:file \"form-errors\")\n               (:file \"debugger-hook\")\n               (:file \"rb-tree\")))\n\n(defsystem \"util/emacs\"\n  :depends-on (#:alexandria)\n  :serial t\n  :components ((:file \"emacs\")))\n\n(defsystem :util/copy-file\n  :serial t\n  :defsystem-depends-on (:trivial-features)\n  :depends-on (:uiop\n               #-lispworks\n               :util/fake-fli\n               :util/posix)\n  :components ((:file \"copy-file\")))\n\n(defsystem :util/timeago\n  :serial t\n  :depends-on (:markup\n               :local-time)\n  :components ((:file \"timeago\")))\n\n(defsystem :util/html2text\n  :serial t\n  :depends-on (:html2text\n               :markup)\n  :components ((:file \"html2text\")))\n\n#+lispworks\n(defsystem :util/memory\n    :serial t\n  :depends-on (:util/posix)\n  :components ((:file \"memory\")))\n\n(defsystem :util/logrotate\n  :serial t\n  :depends-on ()\n  :components ((:file \"logrotate\")))\n\n\n(defsystem :util/atomics\n  :serial t\n  :depends-on (:atomics)\n  :components ((:file \"atomics\")))\n\n#+lispworks\n(defsystem :util/remote-debugging\n  :serial t\n  :depends-on ((:require \"remote-debugger-client\"))\n  :components ((:file \"remote-debugging\")))\n\n\n(defsystem :util/random-port\n  :serial t\n  :depends-on (:usocket\n               :util.misc\n               :easy-macros)\n  :components ((:file \"random-port\")))\n\n(defsystem :util/form-state\n  :serial t\n  :depends-on (:closer-mop\n               :alexandria)\n  :components ((:file \"form-state\")))\n\n\n(defsystem :util/lru-cache\n  :serial t\n  :depends-on (:easy-macros\n               :util.misc\n               :trivial-file-size\n               :cl-fad)\n  :components ((:file \"lru-cache\")))\n\n(defsystem :util/bknr-slynk\n  :serial t\n  :depends-on (:slynk)\n  :components ((:file \"bknr-slynk\")))\n\n(defsystem :util/fiveam\n  :depends-on (:fiveam\n               :pkg\n               :cl-mock\n               :log4cl\n               :cl-store\n               :str)\n  :serial t\n  :components ((:file \"fiveam\")\n               (:file \"mock-recording\")))\n\n(defsystem :util/posix\n  :depends-on (#-lispworks\n               :util/fake-fli)\n  :components ((:file \"posix\")))\n\n(defsystem :util/digests\n  :depends-on ((:feature (:not (:and :lispworks (:or :linux))) \"md5\")\n               (:feature (:not (:and :lispworks (:or :linux))) \"ironclad\")\n               :util/health-check\n               :easy-macros)\n  :serial t\n  :components ((lib-source-file\n                \"digest\"\n                :if-feature (:and :lispworks (:or :linux)))\n               (:file \"digests\" :if-feature (:and :lispworks (:or :linux)))\n               (:file \"digests-non-lw\" :if-feature (:not (:and :lispworks (:or :linux))))))\n\n(defsystem :util/sizeof\n  :serial t\n  :depends-on (#-lispworks\n               :util/fake-fli)\n  :components ((:file \"sizeof\")))\n\n\n(defsystem :util/cron\n  :depends-on (:cl-cron\n               :bknr.datastore\n               :util.threading)\n  :components ((:file \"cron\")))\n\n(defsystem :util/request\n  :depends-on (:drakma\n               :str\n               :log4cl\n               :quri\n               :anaphora\n               :trivial-gray-streams\n               :easy-macros)\n  :serial t\n  :components ((:file \"http-cache\")\n               (:file \"truncated-stream\")\n               (:file \"request\")\n               (:file \"engines\")\n               (:file \"reused-ssl\")))\n\n(defsystem :util/hunchentoot-engine\n  :depends-on (:util/request\n               :hunchentoot)\n  :serial t\n  :components ((:file \"hunchentoot-engine\")))\n\n(defsystem :util/disk-size\n  :depends-on (:util/sizeof)\n  :components ((:file \"disk-size\")))\n\n(defsystem :util/health-check\n  :depends-on (:str\n               :alexandria)\n  :serial t\n  :components ((:file \"health-check\")))\n\n(defsystem :util/hash-lock\n  :depends-on (:bordeaux-threads\n               :lparallel\n               :alexandria)\n  :serial t\n  :components ((:file \"hash-lock\")))\n\n(defsystem :util/lparallel\n  :depends-on (:lparallel\n               :util.misc\n               :easy-macros)\n  :serial t\n  :components ((:file \"lparallel\")))\n\n(defsystem :util/native-module\n  :depends-on (:util/fake-fli\n               :log4cl\n               :util.misc)\n  :components ((:file \"native-module\")))\n\n(defsystem :util/fake-fli\n  :depends-on (:cffi\n               :str)\n  :serial t\n  :components (#-lispworksc (:file \"fake-fli\")))\n\n(defsystem :util/phabricator\n  :depends-on (:dexador\n               :alexandria\n               :cl-json\n               :fset\n               :json-mop\n               :pkg\n               :util/request\n               :util.misc\n               :quri)\n  :components ((:module \"phabricator\"\n                :components ((:file \"conduit\")\n                             (:file \"harbormaster\")\n                             (:file \"project\")\n                             (:file \"maniphest\")\n                             (:file \"passphrase\")))))\n\n(defsystem :util/clsql\n  :depends-on (:clsql\n               (:feature :lispworks :deliver-utils)\n               :trivial-features)\n  :components ((:module \"clsql\"\n                :components ((:file \"clsql\")))))\n\n(defsystem :util/logger\n  :depends-on (:log4cl\n               :easy-macros\n               :alexandria\n               :bordeaux-threads)\n  :components ((:file \"logger\")))\n\n(defsystem :util/json-mop\n  :depends-on (:json-mop)\n  :serial t\n  :components ((:file \"json-mop\")))\n\n(defsystem :util/throttler\n  :depends-on (:local-time\n               :fset\n               :atomics\n               :hunchentoot\n               :easy-macros\n               :cl-fad)\n  :serial t\n  :components ((:file \"throttler\")))\n\n(defsystem :util/simple-queue\n  :depends-on (:bordeaux-threads)\n  :components ((:file \"simple-queue\")))\n\n(defsystem :util/tests\n  :depends-on (:util\n               :util/simple-queue\n               :util/hunchentoot-engine\n               :util/hash-lock\n               :util/lparallel\n               :util/health-check\n               :util/gcloud\n               :util/lru-cache\n               :util/copy-file\n               :fiveam-matchers\n               :easy-macros\n               :util/digests\n               :util/sizeof\n               :util/disk-size\n               :util/phabricator\n               :util/logger\n               :util/timeago\n               :util/json-mop\n               :util/throttler\n               :util.testing\n               :util/events\n               :util/request\n               :util/fake-clingon\n               :util/fiveam\n               :util/benchmark\n               :util/fset\n               (:feature :lispworks :util/ssl)\n               :serapeum)\n  :defsystem-depends-on (:trivial-features)\n  :serial t\n  :components ((:module \"tests\"\n                :components ((:static-file \"test-file\" :type \"txt\")\n                             (:static-file \"test-file-compressed\" :type \"txt.gz\")\n                             (:file \"test-ret-let\")\n                             (:file \"test-mail\")\n                             (:file \"test-hunchentoot-engine\")\n                             (:file \"test-rb-tree\")\n                             (:file \"test-fset\")\n                             (:file \"test-reused-ssl\")\n                             (:file \"test-truncated-stream\")\n                             (:file \"test-asdf\")\n                             (:file \"test-fake-clingon\")\n                             (:file \"test-throttler\")\n                             (:file \"test-fake-fli\" :if-feature (:not :lispworks))\n                             (:file \"test-logger\")\n                             (:file \"test-copy-file\")\n                             (:file \"test-request\")\n                             (:file \"test-request-integration\")\n                             (:file \"test-timeago\")\n                             (:file \"test-random-port\")\n                             (:file \"test-json-mop\")\n                             (:file \"test-lparallel\")\n                             (:file \"test-hash-lock\")\n                             (:file \"test-cookies\")\n                             (:file \"test-fiveam\")\n                             (:file \"test-mock-recording\")\n                             (:file \"test-sizeof\")\n                             (:file \"test-models\")\n                             (:file \"test-disk-size\")\n                             (:file \"test-events\" :if-feature (:not :windows))\n                             (:file \"test-cdn\")\n                             (:file \"test-bind-form\")\n                             (:file \"test-simple-queue\")\n                             (:file \"test-lru-cache\")\n                             #-(or darwin mswindows)\n                             (:file \"test-html2text\")\n                             (:file \"test-mockable\")\n                             (:file \"test-health-check\")\n                             (:file \"test-mquery\")\n                             #+ (and lispworks linux)\n                             (:file \"test-memory\")\n                             (:file \"test-make-instance-with-accessors\")\n                             (:file \"test-digests\")\n                             (:file \"test-benchmark\")\n                             #+(and lispworks (not darwin) (not arm64))\n                             (:file \"test-ssl\")\n                             #+lispworks\n                             (:file \"test-lispworks\")))\n               (:module \"phabricator\"\n                :components ((:file \"test-conduit\")\n                             (:file \"test-harbormaster\")))))\n\n(defsystem :util/events\n  :serial t\n  :defsystem-depends-on (:trivial-features)\n  :depends-on (:clsql\n               :util/clsql\n               :util/cron\n               :core.installation\n               :util.misc\n               :util/atomics\n               :atomics)\n  :components ((:file \"events\")))\n\n(defsystem :util/gcloud\n  :serial t\n  :depends-on (:cl-mongo-id)\n  :components ((:file \"gcloud\")))\n\n#+lispworks\n(defsystem :util/ssl\n    :serial t\n    :depends-on (:util.misc)\n    :components ((:file \"ssl\")))\n\n(defsystem :util/symbol-detector\n  :serial t\n  :depends-on (:markup\n               :named-readtables)\n  :components ((:file \"symbol-detector\")))\n\n(defsystem :util/http-ping\n  :serial t\n  :depends-on (:util/request)\n  :components ((:file \"http-ping\")))\n\n(defsystem :util/fake-clingon\n  :serial t\n  :depends-on (:clingon)\n  :components ((:file \"fake-clingon\")))\n\n\n(defsystem :util/fset\n  :serial t\n  :depends-on (:fset\n               :easy-macros)\n  :components ((:file \"fset\")))\n\n(defsystem :util/recaptcha\n  :serial t\n  :depends-on (:util/request\n               :auto-restart)\n  :components ((:file \"recaptcha\")))\n\n(defsystem :util/benchmark\n  :serial t\n  :depends-on (:alexandria)\n  :components ((:file \"benchmark\")))\n"
  },
  {
    "path": "src/util/uuid.lisp",
    "content": ";; Copyright 2018-Present Modern Interpreters Inc.\n;;\n;; This Source Code Form is subject to the terms of the Mozilla Public\n;; License, v. 2.0. If a copy of the MPL was not distributed with this\n;; file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\n(in-package :util)\n\n(defun safe-uuid ()\n  (uuid:print-bytes nil (uuid:make-v4-uuid)))\n\n(defun make-secret-code ()\n  (base32:bytes-to-base32 (secure-random:bytes 32\n                                               secure-random:*generator*)))\n"
  },
  {
    "path": "third-party/asn1/README.md",
    "content": "# asn1\n\nASN.1 encoder/decoder.\n\n## Usage\n\n### Decoding from a Base64 string\n\n```common-lisp\n(ql:quickload '(:asn1 :cl-base64))\n\n(defvar *public-key*\n  (base64:base64-string-to-usb8-array\n   \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAum9xmq7qBsjYU3gNFB6z\n2DyQypeGvwR3MqbA5x4sevYjeqRunFRq+oo6CyEjzC/zR8xh7NvLFwXImSmyYadU\nd+jstH1Kn5MJtBfCwlGSAXRfn6QV8wr+oweWvyDNUgCkgM+6X7Q7wyH8pib9J2WA\nR6QcY3GRD+P+c/ZNwlgDSBVWzSUE2Sw1GBXadgEDdTMq/DnGmGmsMIdgCMxJ+szA\nAv+dWJhuUPlp5zoFhyxayyJMCAND3llFpmv85bIKfQb8EDkQjtFLOEbU0KIY4pPj\nKL01P4pDiqFFo6PWOJUHO5vyeLDWWCl1itOKeGxHvyxNQG/0BvQquxpjNjHZYCk0\ncwIDAQAB\"))\n\n(asn1:decode *public-key*)\n;=> ((:SEQUENCE (:SEQUENCE (:OBJECT-IDENTIFIER . #(1 2 840 113549 1 1 1)) (:NULL))\n;     (:BIT-STRING\n;      . #(48 130 1 10 2 130 1 1 0 186 111 113 154 174 234 6 200 216 83 120 13 20\n;          30 179 216 60 144 202 151 134 191 4 119 50 166 192 231 30 44 122 246 35\n;          122 164 110 156 84 106 250 138 58 11 33 35 204 47 243 71 204 97 236 219\n;          203 23 5 200 153 41 178 97 167 84 119 232 236 180 125 74 159 147 9 180\n;          23 194 194 81 146 1 116 95 159 164 21 243 10 254 163 7 150 191 32 205 82\n;          0 164 128 207 186 95 180 59 195 33 252 166 38 253 39 101 128 71 164 28\n;          99 113 145 15 227 254 115 246 77 194 88 3 72 21 86 205 37 4 217 44 53 24\n;          21 218 118 1 3 117 51 42 252 57 198 152 105 172 48 135 96 8 204 73 250\n;          204 192 2 255 157 88 152 110 80 249 105 231 58 5 135 44 90 203 34 76 8 3\n;          67 222 89 69 166 107 252 229 178 10 125 6 252 16 57 16 142 209 75 56 70\n;          212 208 162 24 226 147 227 40 189 53 63 138 67 138 161 69 163 163 214 56\n;          149 7 59 155 242 120 176 214 88 41 117 138 211 138 120 108 71 191 44 77\n;          64 111 244 6 244 42 187 26 99 54 49 217 96 41 52 115 2 3 1 0 1))))\n```\n\n### Using with Optima & Ironclad\n\n```common-lisp\n(ql:quickload '(:optima :ironclad))\n\n(optima:match (asn1:decode *public-key*)\n  ((asn1:rsa-public-key-info n e)\n   (ironclad:make-public-key :rsa :n n :e e)))\n;=> #<IRONCLAD::RSA-PUBLIC-KEY {1004C2BA63}>\n```\n\n## Author\n\n* Eitaro Fukamachi (e.arrows@gmail.com)\n\n## Copyright\n\nCopyright (c) 2017 Eitaro Fukamachi (e.arrows@gmail.com)\n\n## License\n\nLicensed under the BSD 2-Clause License.\n"
  },
  {
    "path": "third-party/asn1/asn1.asd",
    "content": "(defsystem \"asn1\"\n  :class :package-inferred-system\n  :version \"0.1.0\"\n  :author \"Eitaro Fukamachi\"\n  :license \"BSD 2-Clause\"\n  :description \"ASN.1 encoder/decoder\"\n  :depends-on (\"asn1/main\")\n  :in-order-to ((test-op (test-op \"asn1/tests\"))))\n\n(defsystem \"asn1/tests\"\n  :class :package-inferred-system\n  :depends-on (\"asn1/tests/main\")\n  :perform (test-op (o c) (symbol-call :rove '#:run c)))\n\n(register-system-packages \"optima\" '(#:optima.core))\n"
  },
  {
    "path": "third-party/asn1/decode.lisp",
    "content": "(defpackage #:asn1/decode\n  (:use #:cl)\n  (:export #:decode))\n(in-package #:asn1/decode)\n\n(declaim (type (simple-array symbol (29)) +tag-types+))\n(defparameter +tag-types+\n  #(nil\n    :boolean\n    :integer\n    :bit-string\n    :octet-string\n    :null\n    :object-identifier\n    :object-descriptor\n    :external\n    :real\n    :enumerated\n    nil\n    :utf8-string\n    nil\n    nil\n    nil\n    :sequence\n    :set\n    :numeric-string\n    :printable-string\n    :teletex-string\n    :videotext-string\n    :ia5-string\n    :utc-time\n    :generalized-time\n    :graphic-string\n    :visible-string\n    :general-string\n    :character-string))\n\n(deftype octets (&optional (len '*)) `(simple-array (unsigned-byte 8) (,len)))\n\n(declaim (ftype (function (octets fixnum fixnum) integer) bytes-to-integer))\n(defun bytes-to-integer (data start end)\n  (declare (optimize speed))\n  (loop with result = 0\n        for p from start below end\n        for byte = (aref data p)\n        do (setf result (+ (* result 256) byte))\n        finally (return result)))\n\n(declaim (ftype (function (octets fixnum fixnum) (simple-array integer (*))) bytes-to-oid))\n(defun bytes-to-oid (data start end)\n  (declare (optimize speed))\n  (let ((1st-byte (aref data start)))\n    (concatenate 'vector\n                 (multiple-value-list\n                  (truncate 1st-byte 40))\n                 (loop with p = (1+ start)\n                       collect\n                       (loop with result = 0\n                             for id = (aref data p)\n                             while (< 128 id)\n                             do (setf result (+ (* result 128) (logand id 127)))\n                                (incf p)\n                             finally (return (+ (* result 128) id)))\n                       do (incf p)\n                       while (< p end)))))\n\n(defun read-block (data &key (start 0))\n  (declare (optimize speed))\n  (declare (type octets data)\n           (type fixnum start))\n  (let ((p start))\n    (declare (type fixnum p))\n    (let* ((1st-byte (aref data p))\n           (is-component (not (zerop (logand 1st-byte 32))))\n           (tag-class (ash 1st-byte -6))\n           (tag-num (logand 1st-byte 31))\n           (tag\n             (ecase tag-class\n               (0 (and (< tag-num (length +tag-types+))\n                       (aref +tag-types+ tag-num)))\n               ;; context\n               (2 tag-num))))\n      (unless tag\n        (warn \"Unknown tag: ~S (~S)\" tag-num (subseq data p)))\n      (incf p)\n      (let ((2nd-byte (aref data p)))\n        (incf p)\n        (cond\n          ((zerop (logand 2nd-byte 128))\n           (let ((end (+ p 2nd-byte)))\n             (declare (type fixnum end))\n             (values p end tag is-component)))\n          (t\n           (let* ((len-bytes (logand 2nd-byte 127))\n                  (len (bytes-to-integer data p (+ len-bytes p))))\n             (declare (type fixnum len-bytes len))\n             (incf p len-bytes)\n             (let ((end (+ p len)))\n               (declare (type fixnum end))\n               (values p end tag is-component)))))))))\n\n(defun decode (data &key (start 0) (end (length data)))\n  (declare (optimize speed))\n  (declare (type octets data)\n           (type fixnum start end))\n  (multiple-value-bind (chunk-start chunk-end tag recursivep)\n      (read-block data :start start)\n    (declare (type fixnum chunk-start chunk-end))\n    (cons\n     (cons tag\n           (if recursivep\n               (decode data :start chunk-start :end chunk-end)\n               (case tag\n                 (:integer\n                  (bytes-to-integer data chunk-start chunk-end))\n                 (:sequence\n                  (decode data :start chunk-start :end chunk-end))\n                 (:bit-string\n                  (let ((unused-bits (aref data chunk-start))\n                        (res (subseq data (1+ chunk-start) chunk-end)))\n                    (unless (= unused-bits 0)\n                      (setf (aref res (1- chunk-end))\n                            (logxor (aref res (1- chunk-end))\n                                    (1- (expt 2 unused-bits)))))\n                    res))\n                 (:octet-string\n                  (subseq data chunk-start chunk-end))\n                 (:object-identifier\n                  (bytes-to-oid data chunk-start chunk-end))\n                 (:boolean\n                  (unless (= 1 (- chunk-end chunk-start))\n                    (error \"Too long boolean\"))\n                  (/= 0 (aref data chunk-start)))\n                 (:null)\n                 (otherwise\n                  (subseq data chunk-start chunk-end)))))\n     (when (< chunk-end end)\n       (decode data :start chunk-end :end end)))))\n"
  },
  {
    "path": "third-party/asn1/encode.lisp",
    "content": "(defpackage #:asn1/encode\n  (:use #:cl)\n  (:import-from #:fast-io\n                #:with-fast-output\n                #:fast-write-byte\n                #:fast-write-sequence)\n  (:import-from #:ironclad)\n  (:export #:encode))\n(in-package #:asn1/encode)\n\n(declaim (type (simple-array symbol (29)) +tag-types+))\n(defparameter +tag-types+\n  #(nil\n    :boolean\n    :integer\n    :bit-string\n    :octet-string\n    :null\n    :object-identifier\n    :object-descriptor\n    :external\n    :real\n    :enumerated\n    nil\n    :utf8-string\n    nil\n    nil\n    nil\n    :sequence\n    :set\n    :numeric-string\n    :printable-string\n    :teletex-string\n    :videotext-string\n    :ia5-string\n    :utc-time\n    :generalized-time\n    :graphic-string\n    :visible-string\n    :general-string\n    :character-string))\n\n(defparameter *buffer* nil)\n\n(defun oid-to-octets (oid)\n  (check-type oid vector)\n  (coerce\n   (cons\n    (+ (* (aref oid 0) 40)\n       (aref oid 1))\n    (labels ((to-chunk (i)\n               (multiple-value-bind (quotinent remainder)\n                   (truncate i 128)\n                 (cond\n                   ((<= 128 quotinent)\n                    (let* ((data (to-chunk quotinent))\n                           (last (last data)))\n                      (rplaca last (+ (car last) 128))\n                      (rplacd last (list remainder))\n                      data))\n                   ((= quotinent 0) (list remainder))\n                   (t (list (+ 128 quotinent) remainder))))))\n      (loop for i from 2 below (length oid)\n            append (to-chunk (aref oid i)))))\n   '(simple-array (unsigned-byte 8) (*))))\n\n(defun length-to-octets (len)\n  (with-fast-output (buffer)\n    (if (< len 128)\n        (fast-write-byte len buffer)\n        (let ((octets (ironclad:integer-to-octets len)))\n          (fast-write-byte (+ (length octets) 128) buffer)\n          (fast-write-sequence octets buffer)))))\n\n(defun write-block (asn1 buffer)\n  (check-type asn1 cons)\n  (let* ((is-component (consp (cdr asn1)))\n         (tag (car asn1))\n         (tag-num (if (integerp tag)\n                      tag\n                      (position tag +tag-types+ :test 'eq))))\n    (unless tag-num\n      (error \"Invalid tag: ~A\" tag))\n\n    (let ((1st-byte (+ (logand tag-num 31)\n                       (if is-component\n                           32\n                           0))))\n      (fast-write-byte 1st-byte buffer)\n      (if is-component\n          (let ((data (encode (cdr asn1))))\n            (fast-write-sequence\n             (length-to-octets (length data))\n             buffer)\n            (fast-write-sequence data buffer))\n          (let ((data (case tag\n                        (:integer (ironclad:integer-to-octets (cdr asn1)))\n                        (:sequence)\n                        (:bit-string\n                         (let ((res (make-array (1+ (length (cdr asn1)))\n                                                :element-type '(unsigned-byte 8)\n                                                :initial-element 0)))\n                           (replace res (cdr asn1) :start1 1)\n                           res))\n                        (:octet-string (cdr asn1))\n                        (:object-identifier\n                         (oid-to-octets (cdr asn1)))\n                        (:boolean (make-array 1 :element-type '(unsigned-byte 8)\n                                                :initial-contents (list (if (cdr asn1)\n                                                                            1\n                                                                            0))))\n                        (:null (make-array 0 :element-type '(unsigned-byte 8)))\n                        (otherwise\n                         (cdr asn1)))))\n            (fast-write-sequence\n             (length-to-octets (length data))\n             buffer)\n            (fast-write-sequence data buffer))))))\n\n(defun encode (asn1)\n  (with-fast-output (buffer)\n    (dolist (kv asn1)\n      (write-block kv buffer))))\n"
  },
  {
    "path": "third-party/asn1/format/public-key.lisp",
    "content": "(defpackage #:asn1/format/public-key\n  (:use #:cl)\n  (:import-from #:optima.core\n                #:%assoc\n                #:constructor-pattern\n                #:make-destructor\n                #:constructor-pattern-subpatterns\n                #:subpatterns\n                #:constructor-pattern-destructor-sharable-p\n                #:constructor-pattern-make-destructor \n                #:parse-constructor-pattern\n                #:parse-pattern\n                #:unparse-pattern)\n  (:export #:subject-public-key-info\n           #:algorithm-identifier\n           #:subject-public-key))\n(in-package #:asn1/format/public-key)\n\n(defstruct (subject-public-key-info (:include constructor-pattern)\n                                    (:constructor make-subject-public-key-info (algorithm-pattern subject-public-key\n                                                                                &aux (subpatterns\n                                                                                      (list algorithm-pattern\n                                                                                            subject-public-key))))))\n\n(defmethod constructor-pattern-destructor-sharable-p ((x subject-public-key-info) (y subject-public-key-info))\n  t)\n\n(defmethod constructor-pattern-make-destructor ((pattern subject-public-key-info) var)\n  (make-destructor :predicate-form `(and (consp ,var)\n                                         (consp (car ,var))\n                                         (null (cdr ,var))\n                                         (eq (car (car ,var)) :sequence)\n                                         (consp (cdr (car ,var))))\n                   :accessor-forms (list `(list (first (cdr (car ,var))))\n                                         `(list (second (cdr (car ,var)))))))\n\n(defmethod parse-constructor-pattern ((name (eql 'subject-public-key-info)) &rest args)\n  (apply #'make-subject-public-key-info (mapcar #'parse-pattern args)))\n\n(defmethod unparse-pattern ((pattern subject-public-key-info))\n  (destructuring-bind (alg key)\n      (constructor-pattern-subpatterns pattern)\n    `(list (list :sequence (list :sequence ,(unparse-pattern alg) ,(unparse-pattern key))))))\n\n\n(defstruct (algorithm-identifier (:include constructor-pattern)\n                                 (:constructor make-algorithm-identifier (algorithm parameters\n                                                                          &aux (subpatterns (list algorithm parameters))))))\n\n(defmethod constructor-pattern-destructor-sharable-p ((x algorithm-identifier) (y algorithm-identifier))\n  t)\n\n(defmethod constructor-pattern-make-destructor ((pattern algorithm-identifier) var)\n  (make-destructor :predicate-form `(and (consp ,var)\n                                         (cdr (%assoc :sequence ,var))\n                                         (vectorp (cdr (%assoc :object-identifier (cdr (%assoc :sequence ,var))))))\n                   :accessor-forms (list `(cdr (%assoc :object-identifier (cdr (%assoc :sequence ,var))))\n                                         `(second (cdr (%assoc :sequence ,var))))))\n\n(defmethod parse-constructor-pattern ((name (eql 'algorithm-identifier)) &rest args)\n  (apply #'make-algorithm-identifier (mapcar #'parse-pattern args)))\n\n(defmethod unparse-pattern ((pattern algorithm-identifier))\n  (destructuring-bind (alg params)\n      (constructor-pattern-subpatterns pattern)\n    `(list (list :sequence\n                 (cons :object-identifier ,(unparse-pattern alg))\n                 ,(unparse-pattern params)))))\n\n\n(defstruct (subject-public-key (:include constructor-pattern)\n                               (:constructor make-subject-public-key (key\n                                                                      &aux (subpatterns (list key))))))\n\n(defmethod constructor-pattern-destructor-sharable-p ((x subject-public-key) (y subject-public-key))\n  t)\n\n(defmethod constructor-pattern-make-destructor ((pattern subject-public-key) var)\n  (make-destructor :predicate-form `(vectorp (cdr (%assoc :bit-string ,var)))\n                   :accessor-forms (list `(cdr (%assoc :bit-string ,var)))))\n\n(defmethod parse-constructor-pattern ((name (eql 'subject-public-key)) &rest args)\n  (apply #'make-subject-public-key (mapcar #'parse-pattern args)))\n\n(defmethod unparse-pattern ((pattern subject-public-key))\n  `(list (cons :bit-string ,(unparse-pattern (first (constructor-pattern-subpatterns pattern))))))\n"
  },
  {
    "path": "third-party/asn1/format/rsa.lisp",
    "content": "(defpackage #:asn1/format/rsa\n  (:use #:cl\n        #:asn1/format/public-key)\n  (:import-from #:asn1/decode\n                #:decode)\n  (:import-from #:optima\n                #:defpattern)\n  (:import-from #:optima.core\n                #:%assoc\n                #:constructor-pattern\n                #:make-destructor\n                #:constructor-pattern-subpatterns\n                #:subpatterns\n                #:constructor-pattern-destructor-sharable-p\n                #:constructor-pattern-make-destructor \n                #:parse-constructor-pattern\n                #:parse-pattern\n                #:unparse-pattern)\n  (:export #:rsa-public-key\n           #:rsa-public-key-info\n           #:rsa-private-key))\n(in-package #:asn1/format/rsa)\n\n(defstruct (rsa-public-key (:include constructor-pattern)\n                           (:constructor make-rsa-public-key (modulus public-exponent\n                                                              &aux (subpatterns\n                                                                    (list modulus public-exponent))))))\n\n(defmethod constructor-pattern-destructor-sharable-p ((x rsa-public-key) (y rsa-public-key))\n  t)\n\n(defmethod constructor-pattern-make-destructor ((pattern rsa-public-key) var)\n  (let ((it (gensym)))\n    (make-destructor :bindings `((,it (cdr (%assoc :sequence (decode ,var)))))\n                     :predicate-form it\n                     :accessor-forms (list `(cdr (first ,it))\n                                           `(cdr (second ,it))))))\n\n(defmethod parse-constructor-pattern ((name (eql 'rsa-public-key)) &rest args)\n  (apply #'make-rsa-public-key (mapcar #'parse-pattern args)))\n\n(defmethod unparse-pattern ((pattern rsa-public-key))\n  (destructuring-bind (modulus public-exponent)\n      (constructor-pattern-subpatterns pattern)\n    ;; FIXME: have to encode into ASN.1\n    `(list (list :sequence\n                 (cons :integer ,(unparse-pattern modulus))\n                 (cons :integer ,(unparse-pattern public-exponent))))))\n\n(defstruct (rsa-public-key-info (:include constructor-pattern)\n                                (:constructor make-rsa-public-key-info (modulus public-exponent\n                                                                        &aux (subpatterns\n                                                                              (list modulus public-exponent))))))\n\n(defmethod constructor-pattern-destructor-sharable-p ((x rsa-public-key-info) (y rsa-public-key-info))\n  t)\n\n(defmethod constructor-pattern-make-destructor ((pattern rsa-public-key-info) var)\n  (let ((it (gensym))\n        (key (gensym))\n        (m (gensym))\n        (e (gensym)))\n    (make-destructor :bindings `((,it (optima:match ,var\n                                        ((subject-public-key-info\n                                          (algorithm-identifier (equalp #(1 2 840 113549 1 1 1)) (equal '(:null)))\n                                          (subject-public-key ,key))\n                                         (optima:match ,key\n                                           ((rsa-public-key ,m ,e)\n                                            (list ,m ,e)))))))\n                     :predicate-form it\n                     :accessor-forms (list `(first ,it)\n                                           `(second ,it)))))\n\n(defmethod parse-constructor-pattern ((name (eql 'rsa-public-key-info)) &rest args)\n  (apply #'make-rsa-public-key-info (mapcar #'parse-pattern args)))\n\n(defmethod unparse-pattern ((pattern rsa-public-key-info))\n  (destructuring-bind (modulus public-exponent)\n      (constructor-pattern-subpatterns pattern)\n    `(subject-public-key-info\n      (algorithm-identifier (equalp #(1 2 840 113549 1 1 1)) (equal '(:null)))\n      (subject-public-key (rsa-public-key ,modulus ,public-exponent)))))\n\n(optima:defpattern rsa-private-key (&key version modulus public-exponent private-exponent prime1 prime2 exponent1 exponent2 coefficient other-prime-infos)\n  `(list (cons :sequence\n               (list* (cons :integer ,(or version '(satisfies integerp)))\n                      (cons :integer ,(or modulus '(satisfies integerp)))\n                      (cons :integer ,(or public-exponent '(satisfies integerp)))\n                      (cons :integer ,(or private-exponent '(satisfies integerp)))\n                      (cons :integer ,(or prime1 '(satisfies integerp)))\n                      (cons :integer ,(or prime2 '(satisfies integerp)))\n                      (cons :integer ,(or exponent1 '(satisfies integerp)))\n                      (cons :integer ,(or exponent2 '(satisfies integerp)))\n                      (cons :integer ,(or coefficient '(satisfies integerp)))\n                      (or null (list ,other-prime-infos))))))\n"
  },
  {
    "path": "third-party/asn1/main.lisp",
    "content": "(uiop:define-package #:asn1\n    (:nicknames #:asn1/main)\n  (:use-reexport #:asn1/decode\n                 #:asn1/encode\n                 #:asn1/format/rsa))\n"
  },
  {
    "path": "third-party/asn1/tests/main.lisp",
    "content": "(defpackage #:asn1/tests/main\n  (:use #:cl\n        #:rove\n        #:asn1\n        #:cl-base64))\n(in-package #:asn1/tests/main)\n\n(defvar *rsa-pub*\n  \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAum9xmq7qBsjYU3gNFB6z\n2DyQypeGvwR3MqbA5x4sevYjeqRunFRq+oo6CyEjzC/zR8xh7NvLFwXImSmyYadU\nd+jstH1Kn5MJtBfCwlGSAXRfn6QV8wr+oweWvyDNUgCkgM+6X7Q7wyH8pib9J2WA\nR6QcY3GRD+P+c/ZNwlgDSBVWzSUE2Sw1GBXadgEDdTMq/DnGmGmsMIdgCMxJ+szA\nAv+dWJhuUPlp5zoFhyxayyJMCAND3llFpmv85bIKfQb8EDkQjtFLOEbU0KIY4pPj\nKL01P4pDiqFFo6PWOJUHO5vyeLDWWCl1itOKeGxHvyxNQG/0BvQquxpjNjHZYCk0\ncwIDAQAB\")\n(defvar *rsa-priv*\n  \"MIIEogIBAAKCAQEAum9xmq7qBsjYU3gNFB6z2DyQypeGvwR3MqbA5x4sevYjeqRu\nnFRq+oo6CyEjzC/zR8xh7NvLFwXImSmyYadUd+jstH1Kn5MJtBfCwlGSAXRfn6QV\n8wr+oweWvyDNUgCkgM+6X7Q7wyH8pib9J2WAR6QcY3GRD+P+c/ZNwlgDSBVWzSUE\n2Sw1GBXadgEDdTMq/DnGmGmsMIdgCMxJ+szAAv+dWJhuUPlp5zoFhyxayyJMCAND\n3llFpmv85bIKfQb8EDkQjtFLOEbU0KIY4pPjKL01P4pDiqFFo6PWOJUHO5vyeLDW\nWCl1itOKeGxHvyxNQG/0BvQquxpjNjHZYCk0cwIDAQABAoIBABkE+a7ziE6Ox5E0\nDDVGBYagYiH+AcRCuihe/oZFo1yBCbPcu0dZgN3MjQuPT/mH+dMJ155sxK17Rjdf\nxCOczBYneRSjt88AcY3snmNrhPeTAX4wDA4IzLFeRFmz8jnuAiWTOwS68EY4mmpF\n0zVlRrjWikTCKeCDDVPMmxTYsOAMWl03csr4tyJu9t83fCnDoIc5tH1WwiB+nnSl\n2wOz3vpDf/CL7ZPqBcW2lINE0OD7sb5mlhBfU66Fli9fFlfO+a0YUfPCBqY8gOso\nfjqOvZcqdLlj0f66Xj+86uWlHp9hLGgJ6zgIIsca+yPVrOKlP+sYwDs65WQyhkkd\nHvzp+uECgYEA5PVso7D06+cMSTG4QB8tQFBo2b8VXjepV3BLuGukmbw7z2YQQxTV\n0NH2hTTvQ9CY6yy6uhwAwfAP35mRMEzlgzMHhhAc29gvFhWpGAJBz0sC/+O9/v8m\n5VREiLyT76Xk3kz99GbWwNwFIdXkyelSfvxiBO7vsDSPw2zwYyNpj1ECgYEA0HRS\n4a7whcn3C9U6mxK7mMvT2B+HfbN0FXuIADWgKziEdfRIPsy6NYOg/OBcWMvjy0VI\nd2TBAje8D6WvG9QN/DHeAFHSX/DW8oW5IWNGolIY/EiKw8lhBjgrNgHl5eJGD5DH\nipe4U0+4Imdq26OqZwL61HYH5SwqUpqONxorfoMCgYBv+QL3jwxI7ocYqMM2QMkN\nogWVMBlQKaKcy6OMfsBSGzeY945OcDsdVAHfJYM6RCL1KLvtVtKcBj6NGPpjh8fb\nATLVwr2KWtC0WUWII1px+XpvEL8TnU81ap/Vy3wCALzMZxTv2Pd//FpaMNQiVwRs\nbBu30+7O2vXQGk/5/BCc8QKBgBAEoEnPU5Q0TNOP8wzvh5LaNtEouxShsY3lDDJX\n7JLlqOgXeWW5/aUXFEvaQb5hDIQWMtdZ2qr89WqOZMJSrTBv9Is5vly4+Qtx0yQJ\nqOfYPytDt8YLt3Tu5AMmajAcDx4rFepEdlmQiqm6IK/4B6QayoOA/mJR3n6yebMq\nQ6VZAoGALlPZ7+FNkAFnXK0m9phXPfQm2H6HyEf8X1gbhimkfh5R/v3BD92CQ35N\nRQ7o1mhljwtlH6qDNqfHtuh30m/Hm5mokK/9knUnTza3wmNq6ZA+K1o2LRnlGb+u\nu2FIFS1oyxICmSOLsS0x+SRPSJA5vTgxPoM4TYVUESWWl4TNMWg=\n\")\n\n(deftest decode-encode-tests\n  (testing \"Public key\"\n    (let ((pub (base64:base64-string-to-usb8-array *rsa-pub*)))\n      (ok (equalp (asn1:encode (asn1:decode pub)) pub))))\n  (testing \"Private key\"\n    (let ((priv (base64:base64-string-to-usb8-array *rsa-priv*)))\n      (ok (equalp (asn1:decode (asn1:encode (asn1:decode priv)))\n                  (asn1:decode priv))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/.gitignore",
    "content": "*~\n*.*f*sl\n"
  },
  {
    "path": "third-party/bknr.datastore/README.mkdn",
    "content": "# bknr.datastore\n\nAn in-memory CLOS based data store. This is a significant fork of the\nvery fantastic bknr.datastore.\n\n# Changes from OG version\n\n* The data format on disk is unchanged (for now)\n\n* Lispworks support: we've been running this in LW for several years now.\n\n* Multiple hook points to support things like file locks, and multiple datastore reading but only one writing.\n\n* Slot accessors on STORE-OBJECTs no longer need to be wrapped in WITH-TRANSACTION.\n\n* Snapshot for object-subsystem is parallel: i.e. it runs on multiple threads to speed up the encoding process.\n\n* WITH-TRANSACTION is a no-op\n  - In particular, we no longer support rollbacks\n  - WITH-TRANSACTION is generally complicated, difficult for a n00b to understand, and has a lot of rough edges (for instance, it does not rollback changes to indices)\n  - But, it also enabled an easy way to batch multiple IO requests. Without WITH-TRANSACTION, the only way to batch IO requests is to create an explicit named transaction.\n  - Removing the ability of rollbacks also allows us to implement things like Raft consensus protocol for High Availability situations.\n\n* indexed-class now requires a superclass of base-indexed-object\n\n* There are probably bunch of other small changes in here that have accumulated over time.\n\n* Strings are considered immutable. i.e. when decoding strings we\n  deduplicate copies of the same string as a memory\n  optimization. However this means it is illegal and unsafe to have a \n  transaction that modifies a string that is being used by the datastore.\n  \n* We use lparallel to parallelize some parts of the snapshotting process\n\n# Planned changes from OG version\n\n* Asynchronous snapshots\n* Getting rid of skip-lists and replacing with fset. (required for async snapshots)\n* There's a bug somewhere that makes indexes fail during hot reloads. This bug is non-deterministic and has been bugging me for years. The workaround is to never define an index on a slot using `:index-type ...`. Instead define an index in a `defvar` and then use `:index *my-index*`.\n* bknr.cluster... will be a separate repository but we'll continually make changes to bknr.datastore to support it.\n\n# installing through quicklisp\n\nThis fork is not available in Quicklisp. The bknr.datastore in Quicklisp is the OG version.\n\nTo install you can use quick-patch;\n\n```\n    (ql:quickload :quick-patch)\n    (quick-patch:register \"https://github.com/tdrhq/bknr-datastore\" \"main\")\n    (quick-patch:checkout-all \"build/\")\n```\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/1_introduction.txt",
    "content": "1. Introduction\n\n[ BKNR satelliten icon ]\n\nBKNR is a software launch platform for LISP satellites. You could\nreplace \"launch platform\" with framework and \"satellites\" with\n\"applications\", but that would be too many buzzwords.\n\nBKNR is made of facilities that are not very useful on their own, but\nthey can be used to quickly build shiny and elegant LISP\nsatellites. For example, a very important component of BKNR is its\ndatastore, which brings persistence to CLOS in a very simple way. By\nadding a few declarations to your class definitions, you can have\npersistent objects. You can also add XML import/export to your objects\nin a similar way. I think this is the single most attractive feature\nof BKNR: no more mapping from a relational database to LISP objects,\nno more XML parsing and XML generation, you just write plain\napplication code.\n\nAnother interesting feature of BKNR is its web framework, built on top\nof the Portable Allegroserve webserver. The web framework has a simple\nobject-oriented handler hierarchy, with sessions, authorization and\nall the features you are used to from other frameworks. It also\ngathers usage information, stores it in the datastore, generates\nstatistics, maps sessions to persistent users. Furthermore, a very\nuseful feature is the HTML templater, which enables you to call LISP\ncode from XML templates. The LISP template callbacks are simple LISP\nfunctions that can work on the XML DOM representation. This eases\nworking with web developers, who can still use their standard editors\nto develop the layout of the webpage. Dynamic content is easy to\nintegrate.\n\n[screenshot von einer bildergallery von eboy.com]\n\nThe application which started BKNR was the website for the graphic\ndesigners eboy. A lot of work went into the manipulation of images and\nthe integration of images into the LISP framework. So another big part\nof the BKNR web framework is the image manipulation code and the image\nlayout code, based on the CL-GD library by Edi Weitz.\n\nWe have started developing BKNR in March 2004, and it is used in 2 big\nweb applications (the Eboy dynasite \"eboy.com\", and the BOS website\n\"BOS creates rainforest\"), and has been used to implement a few\npersonal websites (the website for the hacker gathering GPN in 2004,\nwhich featured an interactive LISP music-dj, the temporary conference\nwebsite for the European Lisp Workshop and the BKNR website). The code\nwas opensourced right from the start, but we didn't put a lot of\neffort into making it accessible for other developers. This is the\nfirst try at releasing some kind of public version of the BKNR\ncodebase, with (we hope) decent documentation.\n\nIf you would like to look at some of the BKNR features in a little\nmore detail, take the guided tour in Chapter 2. Chapters 3, 4, 5, 6\nthen show how to use the different facilities in short tutorials,\nrespectively object indices, the datastore, the web framework and the\ntemplater. These chapters are slightly modified versions of the\ntutorials that can be found in each of the facilities. Finally,\nChapter 7 shows how to build a full web photo album application using\nBKNR.\n\nWe would like to thank the eboys for their support while developing\nBKNR, their cool graphics and their enthusiasm :) We would also like\nto thank Edi Weitz for his impressive libraries, and his\nsupport. Also, I would like to thank Steffen Hurrle for his cool\nwebsite designs (he also has a new design for the BKNR website, which\nsadly nobody has the time to update). The BKNR developers are Hans\nHuebner, David Lichteblau and Manuel Odendahl.\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/2_guided_tour.txt",
    "content": "2. Guided Tour of the BKNR Launch Platform\n\n[Icon mit Leine]\n\nLet's take a guided tour of the BKNR sourcecode. Most facilities are\nindependent of each other, and can be used to build completely\ndifferent types of applications. However, all use the common toolbox\ncalled `BKNR.UTILS', which contains a lot of small lisp\nfunctions. These functions are not very important, and we will leave\nthem behind to get to the first important facility of the BKNR launch\nplatform.\n\n\nThe Datastore [container]\n\nThe datastore is one of the oldest facilities in BKNR, its role is to\nstore the application data, so that this data is persistent between\nLISP sessions. Most existing applications use a back-end\ndatabase to store persistent objects, for example a relational\ndatabase or an object-oriented database. Despite the immediate\nadvantages of having an existing database (reliability, speed,\nmanagement tools), this approach is quite cumbersome: every\napplication data structure has to be mapped onto a data structure in\nthe database. This is not trivial, as can be seen by the gazillions\nattempts to write automatic conversion tools. Another fundamental\nproblem is the integration of the database into the language itself,\nand into the development workflow. Errors in the database are\n\"external\" to the LISP process, rollback, error-catching, transactions\nare not as easy to write as they would be by having an \"internal\"\ndatabase. In the end, we believe that the advantages of having an\nexternal database are outweighed by the disadvantages.\n\nWith the decreasing hardware costs nowadays, it is quite feasible for\nmost applications to keep their entire dataset in memory, and indeed\nthis is the approach taken by the BKNR datastore. Even with a growing\ndata set, memory costs decrease faster than the data grows, so that\nevolution is not a problem. This approach is known as \"prevalence\",\nand the BKNR datastore was initially based on the prevalence solution\nby Sven Van Caekenberghe (also see\n`http://homepage.mac.com/svc/prevalence/readme.html'). The key points\nof the prevalence model are that:\n\n  - All data is held in RAM\n\n  - Changes to persistent data are written to a transaction log\n   file. When loading the datastore, the transaction log is read, and\n   all changes are applied to the persistent data. All changes ever\n   made to the persistent data are executed in order, and the data is\n   recovered.\n\n - If the data model supports it, the persistent data state can be\n   captured and written to a file, which is called a \"snapshot\n   file\". The snapshot file is read when loading the datastore, and\n   the snapshotted data is used as a \"starting point\" for the\n   transaction log.\n\nIn the datastore, transactions which are logged to the transaction log\nare LISP functions defined using the `DEFTRANSACTION' form. Thus, all\ntransactions transforming persistent data are made explicit in the\nsource code. This if different from object-oriented databases, where\nthe fundamental transactions are object creation, object deletion and\nslot access, which are not special cases in the prevalence model at\nall. The main problem with this approach is that it is possible to\nmodify the persistent data in a way that is not logged into the\ntransaction log, which leads to lost data after reloading the\ndatabase. However, the BKNR datastore has a lot of development and\ndebugging helps, warning the user when he is making dangerous changes\n(or forbidding them alltogether).\n\nThe datastore has gone through a few development iterations, which are\nactually quite interesting to explain, as they show which compromises\nhave been made, and how the datastore can be tweaked. After having\nused the prevalence solution of Sven van Caekenberghe and deciding\nthat it didn't quite fit our needs, we wrote a simple datastore\nclosely modelled on Sven's approach. However, the datastore featured\nhelpers to create and modify indexed CLOS objects. Also, the\ntransaction log consisted of SEXPs, and the loading of the transaction\nlog consisted of using the `LOAD' LISP function. The main part of the\nobject datastore consisted of a really big `DEFINE-PERSISTENT-CLASS'\nmacro, which generated a `DEFCLASS', and overrided a lot of methods\nmaking up a store generic method protocol. Special slot options could\nbe used to tell the store to index objects. For example, you could\nspecify that the slot `NAME' of the class `USER' was stored in an\nindex called `USER-NAME-INDEX'. The persistent objects could be\nsnapshotted and written to a snapshot file, which again was a LISP\nfile.\n\n\nThe XML Import/Export\n\nThe Webhandlers\n\nThe Templater\n\neboy.com\n\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/Makefile",
    "content": "PBOOK=../../tools/pbook.py\nDEST=/home/bknr/public_html/pdf\n\nall: manual.pdf\n\nmanual.pdf: manual.tex introduction.tex guidedtour.tex indices.tex datastore.tex impex.tex \n\tpdflatex manual && pdflatex manual\n\tcp manual.pdf $(DEST)/datastore-manual.pdf\n\nindices.tex: ../src/indices/tutorial.lisp\n\t$(PBOOK) -c BknrTexFile -t \"BKNR Indices\" -o indices.tex ../src/indices/tutorial.lisp\n\ndatastore.tex: ../src/data/tutorial.lisp\n\t$(PBOOK) -c BknrTexFile -t \"BKNR Datastore\" -o datastore.tex ../src/data/tutorial.lisp\n\nimpex.tex: ../src/xml-impex/tutorial.lisp\n\t$(PBOOK) -c BknrTexFile -t \"BKNR XML Import/Export\" -o impex.tex ../src/xml-impex/tutorial.lisp\n\nclean:\n\t- rm -rf indices.tex manual.toc impex.tex guidedtour.aux manual.aux datastore.aux manual.pdf datastore.tex indices.aux introduction.aux impex.aux manual.log\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/README-orig",
    "content": "BKNR CODENAME: SPUTNIK\n\nHans Huebner, David Lichteblau, Manuel Odendahl\n\n1. Introduction\n\nBKNR is a software launch platform for LISP satellites. You could\nreplace ``launch platform'' with framework and ``satellites'' with\n``applications'', but that would be too many buzzwords.\n\nBKNR is made of facilities that are not very useful on their own, but\nthey can be used to quickly build shiny and elegant LISP\nsatellites. For example, a very important component of BKNR is its\ndatastore, which brings persistence to CLOS in a very simple way. By\nadding a few declarations to your class definitions, you can have\npersistent objects. You can also add XML import/export to your objects\nin a similar way. I think this is the single most attractive feature\nof BKNR: no more mapping from a relational database to LISP objects,\nno more XML parsing and XML generation, you just write plain\napplication code.\n\n2. Installation\n\nBKNR has been developed with CMUCL 19a under FreeBSD, and has been\ntested with Allegro Common Lisp 6.2 under Windows and Freebsd. Install\nthe BKNR sourcecode and the thirdparty sourcecode.\n\nThen configure the pathnames in bknr/init.lisp, and load\nbknr/init.lisp. Afterwards, you can use ASDF to load the BKNR\nfacilities.\n\nTo load the BKNR indices facility:\n(asdf:oos 'asdf:load-op :bknr-indices)\n\nTo load the BKNR datastore facility:\n(asdf:oos 'asdf:load-op :bknr-indices)\n\nTo load the BKNR impex facility:\n(asdf:oos 'asdf:load-op :bknr-indices)\n\nTo load the BKNR framework:\n(asdf:oos 'asdf:load-op :bknr)\n\n3. Further documentation\n\nYou can read the BKNR manual in bknr/doc/ . You can also browse the\nsourcecode for the tutorials in bknr/src/indices/tutorial.lisp,\nbknr/src/data/tutorial.lisp and bknr/src/xml-impex/tutorial.lisp.\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/TODO",
    "content": "BKNR Manual:\n Frontseite : Dickes BKNR logo\n\n 1. Introduction\n 2. Guided tour\n 3. BKNR Indices\n 4. BKNR Datastore\n 5. BKNR Web Framework\n 6. BKNR Templates\n 7. BKNR Example (photo album)\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/example.tex",
    "content": "\\chapter{Application Example}\n\n\\begin{figure}[htbp]\n    \\centering\n\\includegraphics{applicationicon}\n\\end{figure}\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/guidedtour.tex",
    "content": "\\chapter{Guided Tour}\n\n\\vbox{\n\\centering\n\\includegraphics{guidedtouricon}\n\\vspace{1cm}\n}\n\nLet's take a guided tour of the BKNR sourcecode. Most facilities are\nindependent of each other, and can be used to build completely\ndifferent types of applications. However, all use the common toolbox\ncalled `BKNR-UTILS', which contains a lot of small lisp\nfunctions. These functions are not very important, and we will leave\nthem behind to get to the first important facility of the BKNR launch\nplatform.\n\n\\section{The Datastore}\n\nThe datastore is one of the oldest facilities in BKNR, its role is to\nstore the application data, so that this data is persistent between\nLISP sessions. Most existing applications use a back-end\ndatabase to store persistent objects, for example a relational\ndatabase or an object-oriented database. Despite the immediate\nadvantages of having an existing database (reliability, speed,\nmanagement tools), this approach is quite cumbersome: every\napplication data structure has to be mapped onto a data structure in\nthe database. This is not trivial, as can be seen by the gazillions\nattempts to write automatic conversion tools. Another fundamental\nproblem is the integration of the database into the language itself,\nand into the development workflow. Errors in the database are\n\"external\" to the LISP process, rollback, error-catching, transactions\nare not as easy to write as they would be by having an \"internal\"\ndatabase. In the end, we believe that the advantages of having an\nexternal database are outweighed by the disadvantages.\n\nWith the decreasing hardware costs nowadays, it is quite feasible for\nmost applications to keep their entire dataset in memory, and indeed\nthis is the approach taken by the BKNR datastore. Even with a growing\ndata set, memory costs decrease faster than the data grows, so that\nevolution is not a problem. This approach is known as \"prevalence\",\nand the BKNR datastore was initially based on the prevalence solution\nby Sven Van Caekenberghe (also see\n`http://homepage.mac.com/svc/prevalence/readme.html'). The key points\nof the prevalence model are:\n\n\\begin{itemize}\n\\item All data is held in RAM\n\n\\item Changes to persistent data are written to a transaction log\n   file. When loading the datastore, the transaction log is read, and\n   all changes are applied to the persistent data. All changes ever\n   made to the persistent data are executed in order, and the data is\n   recovered.\n\n\\item If the data model supports it, the persistent data state can be\n   captured and written to a file, which is called a \"snapshot\n   file\". The snapshot file is read when loading the datastore, and\n   the snapshotted data is used as a \"starting point\" for the\n   transaction log.\n\\end{itemize}\n\nIn the datastore, transactions which are logged to the transaction log\nare LISP functions defined using the `DEFTRANSACTION' form. Thus, all\ntransactions transforming persistent data are made explicit in the\nsource code. This if different from object-oriented databases, where\nthe fundamental transactions are object creation, object deletion and\nslot access, which are not special cases in the prevalence model at\nall. The main problem with this approach is that it is possible to\nmodify the persistent data in a way that is not logged into the\ntransaction log, which leads to lost data after reloading the\ndatabase. However, the BKNR datastore has a lot of development and\ndebugging helps, warning the user when he is making dangerous changes\n(or forbidding them alltogether).\n\nThe datastore has gone through a few development iterations, which are\nactually quite interesting to explain, as they show which compromises\nhave been made, and how the datastore can be tweaked. After having\nused the prevalence solution of Sven van Caekenberghe and deciding\nthat it didn't quite fit our needs, we wrote a simple datastore\nclosely modelled on Sven's approach. However, the datastore featured\nhelpers to create and modify indexed CLOS objects. Also, the\ntransaction log consisted of SEXPs, and the loading of the transaction\nlog consisted of using the `LOAD' LISP function. The main part of the\nobject datastore consisted of a really big `DEFINE-PERSISTENT-CLASS'\nmacro, which generated a `DEFCLASS' form, and overrided a lot of\nmethods, making up a store generic method protocol. Special slot\noptions could be used to tell the store to index objects. For example,\nyou could specify that the slot `NAME' of the class `USER' was stored\nin an index called `USER-NAME-INDEX'. The persistent objects could be\nsnapshotted and written to a snapshot file, which again was a LISP\nfile.\n\nThe main problem with this first approach was the lack of real CLOS\nsupport. For example, you could modify an object using standard CLOS\nmethods like `SETF SLOT-VALUE' without ever getting a warning that\nmodifying the persistent state outside a transaction is\ndangerous. Moreover, the `DEFINE-PERSISTENT-CLASS' macro was difficult\nto modify, and the slot options for indices could not be modified\neasily. This lead to the development of a separate index layer, a\nseparate transaction system, and of an object datastore combining the\ntwo systems with additional CLOS support built using the Metaobject\nProtocol.\n\n\\subsection{The transaction layer}\n\nThe transaction layer is now separate from the object oriented\ndatastore. It provides the functionality to declare explicit\ntransactions (still using the `DEFTRANSACTION' form), and to serialize\nthe transactions coming from multiple threads to a transaction log in\nthe filesystem. Using the transaction system is done by instantiating\nan instance of the `STORE' class (or `MP-STORE' to serialize\nconcurrent transactions) and specifying a directory where the\ntransaction logs has to be stored. Only a single transaction system\ncan be open in a LISP session. When a transaction is executed, the\nstore serializes the transaction call (the transaction name and its\narguments) to the transaction log. It also stores the timestamp at\nwhich the transaction was executed. This allows the user to restore\nthe transaction system state until a certain time (for debugging his\napplication or reverting to an old data state). When a datastore is\ncreated or restored, the transaction log is read, and all the\ntransaction calls are re-executed. After all the transactions have\nbeen executed, the persistent state is restored.\n\nA transaction store can have multiple subsystems, which control a\ncertain subset of the persistent data. For example, the object\ndatastore which controls persistent CLOS objects is realized as a\nsubsystem of the transaction layer. A subsystem can snapshot the\ncurrent persistent state, and write it to disk. When the persistent\ndata is snapshotted, the transaction log can be discarded. Restoring\nthis state then consists of loading the snapshot file, and then\nexecuting the transactions stored in the new transaction log. This\nallows for a much faster restore procedure. The snapshotting procedure\ntakes care to backup the old transaction log, so that a restore until\na certain timestamp is still possible.\n\n\\subsection{The object datastore}\n\nThe object datastore combines several aspects:\n\\begin{itemize}\n  \\item It is a subsystem of the transaction layer, and can snapshot\npersistent CLOS objects to a snapshot file, saving their slot values.\n  \\item It provides a CLOS Metaclass for persistent objects. Using\nthis metaclass, you can specify additional slot options for persistent\nobjects. For example, you can specify that a certain slot is\ntransient, which means that it will not be snapshotted and that its\nvalue can be changed outside of a transaction.\n  \\item The persistent object metaclass also provides the index\nfacility of `BKNR-INDICES', so that persistent objects automatically\nget indexed when restored from the store. The index facility is also\nused by the store itself to keep track of the instantiated objects of\neach class, and to index the objects by their unique ID.\n\\end{itemize}\n\nThe object datastore also provides a few helpers for developers. For\nexample, you cannot change the value of a slot when you are outside of\na transaction. The store throws an error when such an illegal\noperation is made. The store also warns you when you change a class\ndefinition. As the class definitions are not stored in the datastore,\na snapshot is necessary after schema evolution.\n\n\\section{The XML Import/Export Facility}\n\nBKNR applications often have to communicate with the external world,\nfor example getting data from external sources, or exporting the data\nfrom the object datastore to external sources. In order to reduce the\namount of conversion and parsing code that has to be written to\naccomodate these tasks, the BKNR framework provides an automatic\nmapping from CLOS classes to XML data. Using a special Metaclass for\nXML objects, you can map a CLOS class to XML according to a XML\nDTD.\n\nIn the following paragraphs, we will use the CLOS class `USER' to show\nhow the XML import/export facility works. The class `USER' has a slot\n`PARENT' which points to another `USER', a slot `NAME' which contains\na string (the name of the user, obviously) and a slot `AGE' which\ncontains an integer (the age of the user).\n\nA DTD consists of element declaration and of attribute declarations\n(we will leave entity declarations aside for now). Most of the time,\nthe mapping between the XML data and the CLOS representation is quite\nstraightforward. An object is represented as an XML element, and its\nslots are represented as either attributes of the element, or child\nelements. All the data in the XML file is encoded as a string, and\nread as such by the CXML parser used by BKNR. You can however specify\ncustom parsing and serializing routines for slots.\n\nAfter the CLOS class has been annotated using specific slot-options,\nobjects can be read from an existing XML file (which validates against\nthe DTD file), or CLOS objects can be serialized to XML. Furthermore,\nparent-child relations can be directly created by the parsing code, or\nfollowing by the serializing code. XML impex slot options can also be\ncombined with BKNR indices, which makes browsing XML data very simple.\n\nExporting and importing XML data now is a breeze!\n\n\\section{The BKNR Web Framework}\n\nBKNR also features a web framework, providing an object oriented web\nhandler architecture. Adding handlers for special kinds of data (for\nexample RSS feeds for a custom data structure) can easily be achieved\nusing multiple inheritance. Furthermore, a template handler is\navailable, making it possible to access LISP functions from an XHTML\nfile. Sadly, as the web framework has been heavily rewritten in the\nlast months, we were not able to document it. However, you can have a\nlook at the example sourcecode for different applications on the bknr\nwebpage.\n\n\n%\\section{The Templater}\n\n\n%\\section{eboy.com}\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/introduction.tex",
    "content": "\\chapter{Introduction}\n\\label{sec:introduction}\n\n\\vbox{\n\\centering\n\\includegraphics{satelliteicon}\n\\vspace{1cm}\n}\n\nBKNR is a software launch platform for Lisp satellites. You could\nreplace ``launch platform'' with framework and ``satellites'' with\n``applications'', but that would be too many buzzwords.\n\nBKNR is made of facilities that are not very useful on their own, but\nthey can be used to quickly build shiny and elegant Lisp\nsatellites. For example, a very important component of BKNR is its\ndatastore, which brings persistence to CLOS in a very simple way. By\nadding a few declarations to your class definitions, you can have\npersistent objects. You can also add XML import/export to your objects\nin a similar way. I think this is the single most attractive feature\nof BKNR: no more mapping from a relational database to Lisp objects,\nno more XML parsing and XML generation, you just write plain\napplication code.\n\n\nAnother interesting feature of BKNR is its web framework, built on top\nof the Hunchentoot webserver. The web framework has a simple\nobject-oriented handler hierarchy, with sessions, authorization and\nall the features you are used to from other frameworks. It also\ngathers usage information, stores it in the datastore, generates\nstatistics, maps sessions to persistent users. Furthermore, a very\nuseful feature is the HTML templater, which enables you to call Lisp\ncode from XML templates. The Lisp template callbacks are simple Lisp\nfunctions that can work on the XML DOM representation. This eases\nworking with web developers, who can still use their standard editors\nto develop the layout of the webpage. Dynamic content is easy to\nintegrate.\n\n\\begin{figure}[htbp]\n    \\centering\n\\includegraphics[scale=0.4]{eboyshot1}\n\\caption{Screenshot of eboy.com}\n\\end{figure}\n\nThe application which started BKNR was the website for the graphic\ndesigners eboy. A lot of work went into the manipulation of images and\nthe integration of images into the Lisp framework. So another big part\nof the BKNR web framework is the image manipulation code and the image\nlayout code, based on the CL-GD library by Edi Weitz.\n\nWe have started developing BKNR in March 2004, and it is used in 2 big\nweb applications (the Eboy dynasite ``eboy.com'', and the BOS website\n``BOS creates rainforest''), and has been used to implement a few\npersonal websites (the website for the hacker gathering GPN in 2004,\nwhich featured an interactive Lisp music-dj, the temporary conference\nwebsite for the European Lisp Workshop and the BKNR website). The code\nwas opensourced right from the start, but we didn't put a lot of\neffort into making it accessible for other developers. This is the\nfirst try at releasing some kind of public version of the BKNR\ncodebase, with (we hope) decent documentation.\n\nIf you would like to look at some of the BKNR features in a little\nmore detail, take the guided tour in Chapter 2. Chapters 3, 4, 5, 6\nthen show how to use the different facilities in short tutorials,\nrespectively object indices, the datastore, the web framework and the\ntemplater. These chapters are slightly modified versions of the\ntutorials that can be found in each of the facilities. Finally,\nChapter 7 shows how to build a full web photo album application using\nBKNR. Due to time constraints, the last three chapters are not\navailable for the release of BKNR Sputnik. We are sorry for the\ninconvenience. The sourcecode is in a working state, but not\ndocumented and cleaned up yet.\n\nWe would like to thank the eboys for their support while developing\nBKNR, their cool graphics and their enthusiasm :) We would also like\nto thank Edi Weitz for his impressive libraries, and his\nsupport. Also, I would like to thank Steffen Hurrle for his cool\nwebsite designs (he also has a new design for the BKNR website, which\nsadly nobody has the time to update). The BKNR developers are Hans\nHuebner, David Lichteblau and Manuel Odendahl.\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/manual.tex",
    "content": "\\documentclass[a4paper,11pt,titlepage]{book}\n\\usepackage{amssymb}\n\\usepackage{fancyvrb,color,palatino}\n\\definecolor{gray}{gray}{0.6}\n\n\n\\newif\\ifpdf\n\\ifx\\pdfoutput\\undefined\n\\pdffalse % es wird kein PDFLaTeX benutzt\n\\else\n\\pdfoutput=1 % es wird PDFLaTeX benutzt\n\\pdftrue\n\\fi\n\n\\ifpdf\n\\usepackage[pdftex]{graphicx}\n\\else\n\\usepackage{graphicx}\n\\fi\n\n\\upshape\n\\frenchspacing\n\\title{BKNR Manual}\n\\author{BKNR}\n\n\\begin{document}\n\n\\begin{titlepage}\n\\begin{center}\n\n  \\vspace*{6cm}\n  \\begin{figure}[htbp]\n    \\centering\n    \\includegraphics[scale=0.6]{bknrlogo}\n  \\end{figure}\n  \\vspace{2cm}\n  \\Large\n  CODENAME: VOSTOK\n  (THIS MANUAL IS UNDER EDIT, PLEASE DO NOT PRINT OR DISTRIBUTE)\n\n\\end{center}\n\\end{titlepage}\n\n\\tableofcontents\n\n\\include{introduction}\n\\include{guidedtour}\n\\include{indices}\n\\include{datastore}\n\\include{impex}\n%\\include{web}\n%\\include{templates}\n%\\include{example}\n\n\\end{document}"
  },
  {
    "path": "third-party/bknr.datastore/doc/schleuder-artikel.txt",
    "content": "BKNR - Lisp Satelliten\n\nvon manuel <manuel@bknr.net>\n\nIntro.\n\nHeute wimmelt es im grossen Internet nur so von Content-Management\nSystemen, von Datenbanken, von Template-Systemen und anderen\nbuzzword-kompatiblen Webanwendungen. \n\n\nBKNR Datastore: RAM ist ja soooo billig.\n\nMOP: Alles Meta oder was?\n\n\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/templates.tex",
    "content": "\\chapter{BKNR Templates}\n\n\\begin{figure}[htbp]\n    \\centering\n\\includegraphics{templatesicon}\n\\end{figure}\n\n"
  },
  {
    "path": "third-party/bknr.datastore/doc/web.tex",
    "content": "\\chapter{BKNR Web Framework}\n\n\\vbox{    \\centering\n\\includegraphics[scale=0.25]{bknrlogo}\n}\n\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/dump-core.lisp",
    "content": "(in-package :bknr.datastore)\n\n(defun save-cmucl-clean-slime-debugger ()\n  \"Called in *after-save-initializations* because cores dumped\nwhen slime is running has this bound. TODO\"\n  (format t \"~&clearing debugger hook (~A)\" cl:*debugger-hook*)\n  (setf cl:*debugger-hook* nil))\n\n(defun save-cmucl-close-fd-handlers ()\n  (loop for handler in lisp::*descriptor-handlers*\n     when (> (lisp::handler-descriptor handler) 2)\n     do (SYSTEM:REMOVE-FD-HANDLER handler)))\n\n(defun save-cmucl-inits (corefilepath)\n  \"called in the child process\"\n  (save-cmucl-close-fd-handlers)\n  (mp::shutdown-multi-processing)\n  (when cl:*debugger-hook*\n    (warn \"CHILD: setting debugger-hook to NIL\")\n    (setf cl:*debugger-hook* nil)\t; does not work!\n    (pushnew 'save-cmucl-clean-slime-debugger ext:*after-save-initializations*))\n  (pushnew 'system::reinitialize-global-table ext:*after-save-initializations*)\n  (ext:save-lisp corefilepath)\n  (warn \"CHILD: strangely survived. killing.\")\n  (unix:unix-exit 1))\n\n(defun snapshot-core (&optional (corefilepath  \"/tmp/bknr.core\"))\n  (cond ((zerop (unix:unix-fork))\n\t (save-cmucl-inits corefilepath))\n\t(t (alien:alien-funcall\n\t    (alien:extern-alien \"wait\"\n\t\t\t\t(alien:function alien:unsigned alien:unsigned))\n\t    0)))\n  (warn \"PARENT saved\"))\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/fswrap/fsd.pl",
    "content": "#!/usr/bin/perl -w\n\nuse IO::Socket;\nuse Net::hostent;              # for OO version of gethostbyaddr\n\n$PORT = 2931;                  # pick something not in use\n\n$server = IO::Socket::INET->new( Proto     => 'tcp',\n\t\t\t\t LocalPort => $PORT,\n\t\t\t\t Listen    => SOMAXCONN,\n\t\t\t\t Reuse     => 1);\n\ndie \"can't setup server\" unless $server;\nprint \"[Server $0 accepting clients]\\n\";\n\nwhile ($client = $server->accept()) {\n    $client->autoflush(1);\n    $hostinfo = gethostbyaddr($client->peeraddr);\n    printf \"[Connect from %s]\\n\", $hostinfo->name || $client->peerhost;\n    while ( <$client>) {\n\tchomp;\n\tprint \"command: [$_]\\n\";\n\tnext unless /\\S/;       # blank line\n\tif (/translate (.*)/i) {\n\t    syswrite($client, \"$1\\n\");\n\t} else {\n\t    print \"unknown command ignored: $_\\n\";\n\t}\n    }\n    close $client;\n}\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/fswrap/fswrap.c",
    "content": "\n/* this is only a prototype */\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/syscall.h>\n#include <sys/syslimits.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n\nconst char* bknr_prefix = \"/bknr\";\nconst unsigned short server_port = 2931;\n\nstatic int in_bknr = 0;\nstatic sockfd = -1;\n\nvoid\nopen_socket()\n{\n  if (sockfd == -1) {\n    struct sockaddr_in serv_name;\n\n    sockfd = socket(AF_INET, SOCK_STREAM, 0);\n\n    if (sockfd == -1) {\n      perror(\"socket creation for pathname translation failed\");\n      abort();\n    }\n\n    serv_name.sin_family = AF_INET;\n    serv_name.sin_addr.s_addr = inet_addr(\"127.0.0.1\");\n    serv_name.sin_port = htons(server_port);\n\n    if (connect(sockfd, (struct sockaddr*) &serv_name, sizeof serv_name) < 0) {\n      perror(\"socket connect failed for pathname translation\");\n      abort();\n    }\n  }\n}\n\nchar*\nsend_command(const char* command, const char* argument)\n{\n  open_socket();\n\n#define DO_WRITE(buf) \\\n\tif (write(sockfd, buf, strlen(buf)) != strlen(buf)) { \\\n \t\tperror(\"write error to pathname translation server\"); \\\n\t\tabort(); \\\n\t}\n\n  DO_WRITE(command);\n  DO_WRITE(\" \");\n  DO_WRITE(argument);\n  DO_WRITE(\"\\n\");\n\n  /* xxx read must be much better, we can't assume to receive the whole \n   * answer in one read()\n   */\n  {\n    size_t buflen = 2*PATH_MAX; /* there be buffer overflows */\n    char* reply = calloc(1, buflen);\n    int read_len = read(sockfd, reply, buflen);\n\n    char* lf = strchr(reply, '\\n');\n\n    if (!lf) {\n      fprintf(stderr, \"received incomplete reply [%s] from pathname \"\n\t      \"translation server\\n\");\n      abort();\n    }\n\n    *lf = 0;\n\n    return reply;\n  }\n}\n\nstatic int\nis_bknr_path(const char* path)\n{\n  return\n    (strncmp(path, bknr_prefix, strlen(bknr_prefix)) == 0)\n    || (*path != '/' && in_bknr);\n}\n\nstatic char*\ntranslate_path(const char* path)\n{\n  char* translated_path = 0;\n\n  if (!is_bknr_path(path)) {\n    /* none of our business */\n    translated_path = strdup(path);\n\n    if (!translated_path) {\n      fprintf(stderr, \"no memory available for pathname translation\\n\");\n      abort();\n    }\n  } else {\n    /* translate this path */\n    translated_path = send_command(\"translate\", path);\n  }\n\n  fprintf(stderr, \"*************** translated %s to %s\\n\",\n\t  path, translated_path);\n\n  return translated_path;\n}\n\nint\nopen(const char* path, int flags, int mode)\n{\n  char* translated_path = translate_path(path);\n  int retval = syscall(SYS_open, translated_path, flags, mode);\n  free(translated_path);\n  return retval;\n}\n\nint\nchdir(const char* path)\n{\n  if (*path == '/') {\n    if (is_bknr_path(path)) {\n      in_bknr = 1;\n    } else {\n      in_bknr = 0;\n    }\n  } else {\n    if (in_bknr) {\n      errno = ENOTDIR;\n      fprintf(stderr, \"*************** can't chdir in %s yet\\n\",\n\t      bknr_prefix);\n      return -1;\n    }\n  }\n\n  return syscall(SYS_chdir, path);\n}\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/fswrap/fswrap.lisp",
    "content": "\n(in-package :bknr.web)\n\n(use-package :acl-compat.socket)\n\n(defun fswrap-server (&key (port 2931))\n  (let ((listener-socket (make-socket :local-port port :connect :passive)))\n    (unwind-protect\n\t (loop for client = (acl-compat.socket:accept-connection listener-socket)\n\t       do (format t \"new client~%\")\n\t       do (loop for request = (read-line client nil nil)\n\t\t\twhile request\n\t\t\tdo (format t \"got request ~a~%\" request)\n\t\t\tdo (destructuring-bind\n\t\t\t\t (command arg)\n\t\t\t       (split \" \" request :limit 2)\n\t\t\t     (cond\n\t\t\t       ((equal command \"translate\")\n\t\t\t\t(format t \"translate ~a~%\" arg)\n\t\t\t\t(format client \"~a~%\" arg)\n\t\t\t\t(finish-output client))\n\t\t\t       (t\n\t\t\t\t(format t \"unknown command~%\"))))\n\t\t\tfinally (close client)))\n      (close listener-socket))))"
  },
  {
    "path": "third-party/bknr.datastore/experimental/mop-bug.lisp",
    "content": "(eval-when (:compile-toplevel :load-toplevel :execute)\n  (or (find-package :sarace)\n      (defpackage :sarace (:use :cl :pcl))))\n(in-package :sarace)\n\n(defclass bug-metaclass (standard-class)\n  ())\n\n(defmethod validate-superclass ((sub bug-metaclass) (super standard-class))\n  t)\n\n(defclass bug-direct-slot-definition (standard-direct-slot-definition)\n  ())\n\n(defclass bug-effective-slot-definition (standard-effective-slot-definition)\n  ())\n\n(defmethod direct-slot-definition-class ((class bug-metaclass) &rest initargs)\n  (declare (ignore initargs))\n  'bug-direct-slot-definition)\n\n(defmethod effective-slot-definition-class ((class bug-metaclass) &rest initargs)\n  (declare (ignore initargs))\n  'bug-effective-slot-definition)\n\n#+nil\n(defmethod initialize-instance :after ((class bug-metaclass) &rest initargs)\n  (declare (ignore initargs))\n  (format t \"SLOTS: ~S~%\" (class-slots class)))\n\n#+nil\n(defmethod reinitialize-instance :around ((class bug-metaclass) &rest initargs)\n  (declare (ignore initargs))\n  (let* ((old-slots (class-slots class))\n\t (new-class (call-next-method))\n\t (new-slots (class-slots new-class)))\n    (format t \"OLD-slots: ~S new-slots ~S~%\" old-slots new-slots)\n    new-class))\n\n#+nil\n(defmethod compute-slots ((class bug-metaclass))\n  (let ((normal-slots (call-next-method)))\n    (cons (make-instance 'bug-effective-slot-definition\n\t\t\t :name 'bug-slot\n\t\t\t :initform nil\n\t\t\t :class class\n\t\t\t :initfunction #'(lambda () nil))\n\t  normal-slots)))\n\n(defmethod (setf slot-value-using-class) :before\n    (newvalue (class bug-metaclass) object slot)\n  (format t \"BEFORE method for slot setting~%\"))\n\n(defmethod (setf slot-value-using-class) :around\n    (newvalue (class bug-metaclass) object slot)\n  (format t \"before slot setting~%\")\n  (let ((result (call-next-method)))\n    (format t \"after slot setting~%\")\n    result))\n\n(defmacro with-traced-functions ((&rest functions) &body body)\n  `(unwind-protect (progn (trace ,@functions) ,@body)\n    (untrace ,@functions)))\n\n;;; BUG BUG with 2 classes, before and around \n  (defclass test ()\n    ((a :initarg :a :initform 0))\n    (:metaclass bug-metaclass))\n\n\n(defclass nobug-class (standard-class)\n  ())\n\n(defmethod validate-superclass ((sub nobug-class) (super standard-class))\n  t)\n\n(defclass nobug-direct-slot-definition (standard-direct-slot-definition)\n  ())\n\n(defclass nobug-effective-slot-definition (standard-effective-slot-definition)\n  ())\n\n(defmethod direct-slot-definition-class ((class nobug-class) &rest initargs)\n  (declare (ignore initargs))\n  'nobug-direct-slot-definition)\n\n(defmethod effective-slot-definition-class ((class nobug-class) &rest initargs)\n  (declare (ignore initargs))\n  'nobug-effective-slot-definition)\n\n(defmethod (setf slot-value-using-class) :around (newval (class nobug-class) object slot)\n  (call-next-method))\n\n\n(defclass test2 ()\n  ((b :initarg :a :initform 0))\n  (:metaclass nobug-class))\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/shop/money.lisp",
    "content": "(in-package :shop)\n\n(defclass money ()\n  ((currency :initarg :currency\n             :reader currency\n             :type symbol)\n   (amount :initarg :amount\n           :accessor amount\n           :type integer))\n  (:documentation\n   \"Represent monetary value, including a currency.\"))\n\n(defun make-money (amount &optional (currency :eur))\n  (make-instance 'money :currency currency :amount amount))\n\n(defmethod print-object ((money money) stream)\n  (print-unreadable-object (money stream :type t)\n    (format stream \"~D.~2,'0D ~A\"\n            (floor (amount money) 100)\n            (mod (amount money) 100)\n            (currency money))))\n\n(defmethod bknr.datastore::encode-object ((money money) stream)\n  (bknr.datastore::%write-char #\\C stream)\n  (bknr.datastore::%encode-symbol (currency money) stream)\n  (bknr.datastore::%encode-integer (amount money) stream))\n\n(defmethod bknr.datastore::decode-object ((tag (eql #\\C)) stream)\n  (make-instance 'money\n                 :currency (bknr.datastore::%decode-symbol stream)\n                 :amount (bknr.datastore::%decode-integer stream)))\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/shop/shop.lisp",
    "content": "(in-package :shop)\n\n;; Shopping system.\n\n;; The shop offers both downloadable as well as mail-order type\n;; products.\n\n;; While browsing the shop web site, customers put products they want\n;; to buy into a shopping cart.  The shopping cart is stored a cookie\n;; in the browser.\n\n;; Once finished with selecting products, the user is asked to either\n;; log in or create a new customer object.  The amount of data that\n;; the user is required to supply for the order depends on the type of\n;; the order.  To buy download products, only the email address is\n;; mandatory.\n\n;; An order object is created in the database from the shopping cart\n;; cookie data.\n\n(define-persistent-class product ()\n  ((price\n    :update\n    :type money\n    :documentation\n    \"Price of the product, not including any taxes.\")\n   (name\n    :update\n    :type string\n    :index-type string-unique-index\n    :documentation\n    \"Short name of the product, must be unique, should be identifier\")\n   (description\n    :update\n    :type string\n    :documentation\n    \"Textual description of the product\")))\n\n(defgeneric sell (product)\n  (:documentation\n   \"Sell PRODUCT, adjusting the stock count if needed.  Returns the product sold.\"))\n\n(defgeneric product-stock-count (product)\n  (:documentation\n   \"Return the number of instances of PRODUCT available, or NIL if the\n    product can be sold in infinite amounts.\")\n  (:method (product)\n    \"By default, assume infinite supply\"\n    nil))\n\n(define-persistent-class download-product (product)\n  ()\n  (:documentation\n   \"A product that can be directly downloaded from the system.  To buy\n    such a product, the customer needs to supply no shipping\n    address.  Once paid, the system makes the product available to the\n    customer for download.\"))\n\n(define-persistent-class emailable-product (product)\n  ()\n  (:documentation\n   \"A product that can be sent to the customer by email \\(i.e. an\n    archive product). To buy such a product, an email address needs to\n    be supplied by the customer.  Once paid, the system sends the\n    order to the store personnel for fulfillment.\"))\n\n(define-persistent-class mailable-product (product)\n  ((stock-count :update\n                :type integer\n                :accessor product-stock-count\n                :documentation\n                \"Number of instances of this product that are\n                available to be sold, including reserved amounts in\n                shopping carts.\")\n   (reserved-count :update\n                   :type integer\n                   :initform 0))\n  (:documentation\n   \"A product that is sent to the customer by regular mail \\(i.e. a\n    t-shirt or poster).  Once paid, the system sends the order to the\n    store personell for fulfillment.\"))\n\n(defgeneric available-p (product count)\n  (:documentation \"Return a true value if COUNT units of PRODUCT are\n  currently available.  Should be called with the store guard locked.\")\n  (:method (product count)\n    (or (null (product-stock-count product))\n        (<= count (product-stock-count product)))))\n\n(defmethod product-stock-count ((product mailable-product))\n  \"The available stock count for a mailable product is reduced by the reserved count and returned.\"\n  (- (slot-value product 'stock-count)\n     (mailable-product-reserved-count product)))\n\n(defmethod (setf product-stock-count) (newval (product mailable-product))\n  \"The available sock count for a mailable product is set to NEWVAL.\"\n  (when (< newval (mailable-product-reserved-count product))\n    (error \"cannot reduce the available stock count below the reserved count\"))\n  (setf (slot-value product 'stock-count) newval))\n\n(define-persistent-class shipping-address ()\n  ((country :read))\n  (:documentation\n   \"Abstract base class for shipping addresses.  The child classes of\n    SHIPPING-ADDRESS implement a particular surface address structure,\n    as required by the country.\"))\n\n(define-persistent-class customer (user)\n  ((name :update\n         :documentation\n         \"Full name of the customer.\")\n   (invoice-addresses :update\n                      :documentation\n                      \"List of invoice addresses with the preferred address being the\n                      CAR of the list.\")\n   (shipping-addresses :update\n                       :documentation\n                       \"List of shipping addresses with the preferred address being the\n                       CAR of the list.\")))\n\n(define-persistent-class number-generator ()\n  ((name :read\n         :type symbol\n         :initform (error \"cannot make number-generator instance without name\")\n         :index-type string-unique-index\n         :index-reader number-generator-with-name)\n   (next :update\n         :type integer\n         :initarg :next\n         :initform 1)))\n\n(defun get-next-number (name)\n  (with-transaction (:get-next-number)\n    (let* ((number-generator (or (number-generator-with-name name)\n                                 (make-instance 'number-generator :name name)))\n           (number (number-generator-next number-generator)))\n      (incf (number-generator-next number-generator))\n      number)))\n\n(define-persistent-class order ()\n  ((number :read\n           :initform (get-next-number 'orders))\n   (customer :read)\n   (items :update)))\n\n(defgeneric make-order (customer shopping-cart)\n  (:documentation\n   \"Create a new ORDER instance, initialized from the CUSTOMER and\n    SHOPPING-CART objects supplied.  Returns the order created.\"))\n\n(define-persistent-class invoice ()\n  ((number :read\n           :initform (get-next-number 'invoices))\n   (items :update)))\n\n(define-persistent-class lease ()\n  ((product :read\n            :initform (error \"missing :product initarg to lease creation\")\n            :documentation \"product that has been leased\")\n   (count :read\n          :initform (error \"missing :count initarg to lease creation\")\n          :documentation \"number of units of product held by this lease\")\n   (fulfilled :update\n              :initform nil\n              :documentation \"Set to a true value when the lease has\n              been fulfilled.  Used during lease descruction in order\n              to determine whether to return the leased inventory to\n              the product stock.\"))\n  (:documentation \"Instance representing a lease for a product.\"))\n\n(defgeneric update-reserved-stock (product count)\n  (:documentation \"Update the reserved counter of PRODUCT by COUNT units\")\n  (:method (product count)\n    (declare (ignore product count))))\n\n(defgeneric note-sale (product count)\n  (:documentation \"Update the stock count of the PRODUCT by COUNT\n  units after a sale has been done\")\n  (:method (product count)\n    (declare (ignore product count))))\n\n(defmethod initialize-instance :after ((lease lease) &key)\n  (update-reserved-stock (lease-product lease) (lease-count lease)))\n\n(defmethod destroy-object :before ((lease lease))\n  (unless (lease-fulfilled lease)\n    (update-reserved-stock (lease-product lease) (- (lease-count lease)))))\n\n(defmethod update-reserved-stock ((product mailable-product) count)\n  (incf (mailable-product-reserved-count product) count))\n\n(defmethod note-sale ((product mailable-product) count)\n  (decf (slot-value product 'stock-count) count)\n  (update-reserved-stock product (- count)))\n\n(define-persistent-class shopping-cart ()\n  ((leases :update\n           :initform nil)\n   (expires :read\n            :initform (error \"missing :expires initarg to shopping cart creation\")\n            :documentation \"universal time at which this shopping cart expires\"))\n  (:documentation \"Represents the intent to buy goods, in the form of LEASE objects\"))\n\n(defmethod destroy-object :before ((shopping-cart shopping-cart))\n  (mapc #'delete-object (shopping-cart-leases shopping-cart)))\n\n(define-condition insufficient-inventory (error)\n  ((product :initarg :product\n            :reader product)\n   (requested :initarg :requested\n              :reader requested)\n   (available :initarg :available\n              :reader available))\n  (:report (lambda (c stream)\n             (format stream \"Insufficient inventory for product ~A - Requested ~A, but~[~; only~]~:* ~A available\"\n                     (product c) (requested c) (available c))\n             c)))\n\n(define-condition product-already-in-shopping-cart (error)\n  ((product :initarg :product\n            :reader product))\n  (:report (lambda (c stream)\n             (format stream \"Product ~A is already in shopping cart\"\n                     (product c)))))\n\n(defun put-to-shopping-cart (count product shopping-cart)\n  \"Reserve COUNT units of PRODUCT, signalling a INSUFFICIENT-INVENTORY\n  error if not enough inventory of PRODUCT is available.  Returns a\n  LEASE object.\"\n  (with-store-guard ()\n    (unless (available-p product count)\n      (error 'insufficient-inventory\n             :product product\n             :requested count\n             :available (product-stock-count product)))\n    (when (find product (shopping-cart-leases shopping-cart)\n                :key #'lease-product)\n      (error 'product-already-in-shopping-cart\n             :product product))\n    (with-transaction (:make-lease)\n      (push (make-instance 'lease\n                           :product product\n                           :count count)\n            (shopping-cart-leases shopping-cart)))))\n\n(defun fulfill (shopping-cart)\n  \"Fulfill the given shopping cart.\"\n  (with-transaction (:fulfill)\n    (dolist (lease (shopping-cart-leases shopping-cart))\n      (let ((product (lease-product lease))\n            (count (lease-count lease)))\n        (setf (lease-fulfilled lease) t)\n        (note-sale product count)))\n    (delete-object shopping-cart)))\n\n;;; TESTING\n\n(defun getpid ()\n  #+openmcl\n  (ccl::getpid)\n  #+sbcl\n  (sb-posix:getpid)\n  #+(not (or sbcl openmcl))\n  (random 10000))\n\n(defmacro with-temporary-directory ((pathname) &body body)\n  `(let ((,pathname (pathname (format nil \"/tmp/store-test-~A/\" (getpid)))))\n     (asdf:run-shell-command \"rm -rf ~A\" ,pathname)\n     (prog1\n         (progn ,@body)\n       (asdf:run-shell-command \"rm -rf ~A\" ,pathname))))\n\n(defun do-with-test-store (thunk)\n  (when (and (boundp '*store*) *store*)\n    (warn \"closing open store *store* to run tests\")\n    (close-store))\n  (with-temporary-directory (store-directory)\n    (make-instance 'mp-store\n                   :subsystems (list (make-instance 'store-object-subsystem))\n                   :directory store-directory)\n    (funcall thunk)\n    (close-store)))\n\n(defmacro with-test-store (() &body body)\n  `(do-with-test-store (lambda () ,@body)))\n\n(unit-test:deftest :shop \"lease and cart tests\"\n  (with-test-store ()\n    (let* ((t-shirt (make-instance 'mailable-product :name 't-shirt :stock-count 10))\n           (file (make-instance 'download-product :name 'file))\n           (shopping-cart (make-instance 'shopping-cart :expires (+ (* 60 10) (get-universal-time)))))\n      (unit-test:test-equal 10 (product-stock-count t-shirt))\n      (put-to-shopping-cart 10 t-shirt shopping-cart)\n      (unit-test:test-equal 0 (product-stock-count t-shirt))\n      (unit-test:test-assert (product-stock-count t-shirt))\n      (with-transaction (:add-to-inventory)\n        (incf (slot-value t-shirt 'stock-count) 10))\n      (put-to-shopping-cart 5 t-shirt shopping-cart)\n      (unit-test:test-equal 5 (product-stock-count t-shirt))\n      (delete-object shopping-cart)\n      (unit-test:test-equal 20 (product-stock-count t-shirt))\n      (setf shopping-cart (make-instance 'shopping-cart :expires (+ (* 60 10) (get-universal-time))))\n      (put-to-shopping-cart 5 t-shirt shopping-cart)\n      (put-to-shopping-cart 500 file shopping-cart)\n      (unit-test:test-equal 15 (product-stock-count t-shirt)))))\n\n(unit-test:deftest :shop \"fulfill test\"\n  (with-test-store ()\n    (let* ((t-shirt (make-instance 'mailable-product :name 't-shirt :stock-count 10))\n           (file (make-instance 'download-product :name 'file))\n           (shopping-cart (make-instance 'shopping-cart :expires (+ (* 60 10) (get-universal-time)))))\n      (put-to-shopping-cart 3 t-shirt shopping-cart)\n      (put-to-shopping-cart 7 file shopping-cart)\n      (fulfill shopping-cart)\n      (unit-test:test-equal 7 (product-stock-count t-shirt))\n      (unit-test:test-equal 0 (length (class-instances 'shopping-cart)))\n      (unit-test:test-equal 0 (length (class-instances 'lease)))\n      (unit-test:test-equal 0 (mailable-product-reserved-count t-shirt)))))"
  },
  {
    "path": "third-party/bknr.datastore/experimental/slot-attributes.lisp",
    "content": "(in-package :cl-user)\n(use-package :aclmop)\n\n;;; compute-slots returns a set of effective slot definitions.\n\n(defun mapappend (function list)\n  (let (res)\n    (dolist (l list)\n      (push (funcall function l) res))\n    (nreverse res)))\n\n(defmacro push-on-end (value location)\n  `(setf ,location (nconc ,location (list ,value))))\n\n;;; (setf getf*) is like (setf getf) except that it always changes the\n;;; list, which must be non-nil\n(defun (setf getf*) (new-value plist key)\n  (block body\n    (do ((x plist (cddr x)))\n\t((null x))\n      (when (eq (car x) key)\n\t(setf (car (cdr x)) new-value)\n\t(return-from body new-value)))\n    (push-on-end key plist)\n    (push-on-end new-value plist)\n    new-value))\n\n(defclass attributes-direct-slot-definition\n    (standard-direct-slot-definition)\n  ((attributes :initform nil :initarg :attributes\n\t       :accessor attributes-direct-slot-definition-attributes)))\n\n(defclass attributes-effective-slot-definition\n    (standard-effective-slot-definition)\n  ((attributes :initform nil :initarg :attributes\n\t       :accessor attributes-effective-slot-definition-attributes)))\n\n(defclass attributes-class (standard-class)\n  ())\n\n(defmethod direct-slot-definition-class ((class attributes-class)\n\t\t\t\t\t &rest initargs)\n  'attributes-direct-slot-definition)\n\n(defmethod effective-slot-definition-class ((class attributes-class)\n\t\t\t\t\t &rest initargs)\n  'attributes-effective-slot-definition)\n\n(defmethod compute-effective-slot-definition ((class attributes-class) name\n\t\t\t\t\t      direct-slots)\n  (declare (ignore name))\n  (let ((normal-slot (call-next-method)))\n    (setf (attributes-effective-slot-definition-attributes normal-slot)\n\t  (remove-duplicates\n\t   (mapappend #'attributes-direct-slot-definition-attributes direct-slots)))\n    normal-slot))\n\n(defun make-effective-slot-definition (&rest initargs)\n  (apply #'make-instance 'standard-effective-slot-definition initargs))\n\n(defmethod compute-slots ((class attributes-class))\n  (let* ((normal-slots (call-next-method))\n\t (alist (mapcar #'(lambda (slot)\n\t\t\t    (cons (slot-definition-name slot)\n\t\t\t\t  (mapcar #'(lambda (attr) (cons attr nil))\n\t\t\t\t\t  (attributes-effective-slot-definition-attributes\n\t\t\t\t\t   slot))))\n\t\t\tnormal-slots)))\n    (cons (make-effective-slot-definition\n\t   :name 'all-attributes\n\t   :initform `',alist\n\t   :initfunction #'(lambda () alist))\n\t  normal-slots)))\n\n(defun slot-attribute-bucket (instance slot-name attribute)\n  (let* ((all-buckets (slot-value instance 'all-attributes))\n\t (slot-bucket (assoc slot-name all-buckets)))\n    (unless slot-bucket\n      (error \"The slot named ~S of ~S has no attributes.\"\n\t     slot-name instance))\n    (let ((attr-bucket (assoc attribute (cdr slot-bucket))))\n      (unless attr-bucket\n\t(error \"The slot named ~S of ~S has no attribute~@\n                named ~S.\" slot-name instance attribute))\n      attr-bucket)))\n\n(defun slot-attribute (instance slot-name attribute)\n  (cdr (slot-attribute-bucket instance slot-name attribute)))\n\n(defun (setf slot-attribute) (new-value instance slot-name attribute)\n  (setf (cdr (slot-attribute-bucket instance slot-name attribute))\n\tnew-value))\n\n;;; test\n\n(defclass credit-rating ()\n  ((level :attributes (date-set time-set)))\n  (:metaclass attributes-class))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/xml-schema/examples/test-schema.xml",
    "content": "<?xml version=\"1.0\"?>\n<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n  <xs:element name=\"name\" type=\"xs:string\"/>\n  <xs:element name=\"qualification\" type=\"xs:string\"/>\n  <xs:element name=\"born\" type=\"xs:date\"/>\n  <xs:element name=\"dead\" type=\"xs:date\"/>\n  <xs:element name=\"isbn\" type=\"xs:string\"/>\n  <xs:attribute name=\"id\" type=\"xs:ID\"/>\n  <xs:attribute name=\"available\" type=\"xs:boolean\"/>\n  <xs:attribute name=\"lang\" type=\"xs:language\"/>\n\n  <xs:element name=\"title\">\n    <xs:complexType>\n      <xs:simpleContent>\n        <xs:extension base=\"xs:string\">\n\t  <xs:attribute ref=\"lang\"/>\n\t</xs:extension>\n      </xs:simpleContent>\n    </xs:complexType>\n  </xs:element>\n\n  <xs:element name=\"library\">\n    <xs:complexType>\n      <xs:sequence>\n\t<xs:element ref=\"book\" maxOccurs=\"unbounded\"/>\n      </xs:sequence>\n    </xs:complexType>\n  </xs:element>\n\n  <xs:element name=\"author\">\n    <xs:complexType>\n      <xs:sequence>\n\t<xs:element ref=\"name\"/>\n\t<xs:element ref=\"born\"/>\n\t<xs:element ref=\"dead\" minOccurs=\"0\"/>\n      </xs:sequence>\n      <xs:attribute ref=\"id\"/>\n    </xs:complexType>\n  </xs:element>\n\n  <xs:element name=\"book\">\n    <xs:complexType>\n      <xs:sequence>\n\t<xs:element ref=\"isbn\"/>\n\t<xs:element ref=\"title\"/>\n\t<xs:element ref=\"author\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n\t<xs:element ref=\"character\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n      </xs:sequence>\n      <xs:attribute ref=\"id\"/>\n      <xs:attribute ref=\"available\"/>\n    </xs:complexType>\n  </xs:element>\n\n  <xs:element name=\"character\">\n    <xs:complexType>\n      <xs:sequence>\n\t<xs:element ref=\"name\"/>\n\t<xs:element ref=\"born\"/>\n\t<xs:element ref=\"qualification\"/>\n      </xs:sequence>\n      <xs:attribute ref=\"id\"/>\n    </xs:complexType>\n  </xs:element>\n  \n</xs:schema>"
  },
  {
    "path": "third-party/bknr.datastore/experimental/xml-schema/examples/test-schema2.xml",
    "content": "<?xml version=\"1.0\"?>\n<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n  <xs:element name=\"library\">\n    <xs:complexType>\n      <xs:sequence>\n\t<xs:element name=\"book\" maxOccurs=\"unbounded\">\n\t  <xs:complexType>\n\t    <xs:sequence>\n\t      <xs:element name=\"isbn\" type=\"xs:integer\"/>\n\t      <xs:element name=\"title\">\n\t\t<xs:complexType>\n\t\t  <xs:simpleContent>\n\t\t    <xs:extension base=\"xs:string\">\n\t\t      <xs:attribute name=\"lang\" type=\"xs:language\"/>\n\t\t    </xs:extension>\n\t\t  </xs:simpleContent>\n\t\t</xs:complexType>\n\t      </xs:element>\n\t      <xs:element name=\"author\" minOccurs=\"0\" maxOccurs=\"unbounded\">\n\t\t<xs:complexType>\n\t\t  <xs:sequence>\n\t\t    <xs:element name=\"name\" type=\"xs:string\"/>\n\t\t    <xs:element name=\"born\" type=\"xs:date\"/>\n\t\t    <xs:element name=\"dead\" type=\"xs:date\"/>\n\t\t  </xs:sequence>\n\t\t  <xs:attribute name=\"id\" type=\"xs:ID\"/>\n\t\t</xs:complexType>\n\t      </xs:element>\n\t      <xs:element name=\"character\" minOccurs=\"0\" maxOccurs=\"unbounded\">\n\t\t<xs:complexType>\n\t\t  <xs:sequence>\n\t\t    <xs:element name=\"name\" type=\"xs:string\"/>\n\t\t    <xs:element name=\"born\" type=\"xs:date\"/>\n\t\t    <xs:element name=\"qualification\" type=\"xs:string\"/>\n\t\t  </xs:sequence>\n\t\t  <xs:attribute name=\"id\" type=\"xs:ID\"/>\n\t\t</xs:complexType>\n\t      </xs:element>\n\t    </xs:sequence>\n\t    <xs:attribute name=\"id\" type=\"xs:ID\"/>\n\t    <xs:attribute name=\"available\" type=\"xs:boolean\"/>\n\t  </xs:complexType>\n\t</xs:element>\n      </xs:sequence>\n    </xs:complexType>\n  </xs:element>\n</xs:schema>\n"
  },
  {
    "path": "third-party/bknr.datastore/experimental/xml-schema/xml-schema.lisp",
    "content": "(in-package :cl-user)\n\n;;; general helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n(defmacro awhen (test-form &rest then-forms)\n  `(let ((it ,test-form))\n     (when it ,@then-forms)))\n\n(defmacro aif (pred then-form &optional else-form)\n  `(let ((it ,pred)) (if it ,then-form ,else-form)))\n\n(defun string-null (string)\n  (string-equal string \"\"))\n\n(defconstant +whitespace-chars+\n  '(#\\Space #\\Newline #\\Tab #\\Linefeed))\n\n(defun whitespace-char-p (c)\n  (member c +whitespace-chars+))\n\n(defun whitespace-p (c-or-s)\n  (cond ((stringp c-or-s)\n\t (every #'whitespace-char-p c-or-s))\n\t((characterp c-or-s)\n\t (whitespace-char-p c-or-s))\n\t(t nil)))\n\n(defun make-keyword-from-string (string)\n  (if (keywordp string)\n      string\n      (nth-value 0 (intern (string-upcase\n\t\t\t    (substitute-if #\\- #'(lambda (char)\n\t\t\t\t\t\t   (or (whitespace-char-p char)\n\t\t\t\t\t\t       (eql #\\: char)))\n\t\t\t\t\t   string)) 'keyword))))\n\n\n;;; cxml helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n(defun child-elements (node)\n  (coerce (remove-if-not #'dom:element-p (dom:child-nodes node)) 'list))\n\n(defmacro with-attributes (attributes node &rest body)\n  `(let ,(loop for attr in attributes\n\t       when (symbolp attr)\n\t       collect `(,attr (dom:get-attribute ,node ,(string-downcase (symbol-name attr))))\n\t       when (listp attr)\n\t       collect `(,(car attr) (dom:get-attribute ,node ,(cadr attr))))\n    ,@(loop for attr in attributes\n\t    when (symbolp attr)\n\t    collect `(when (string-null ,attr)\n\t\t      (error ,(format nil \"Attribute ~S is empty.\"\n\t\t\t\t      (string-downcase (symbol-name attr)))))\n\t    when (listp attr)\n\t    collect `(when (string-null ,(car attr))\n\t\t      (error ,(format nil \"Attribute ~S is empty.\" (cadr attr)))))\n    ,@body))\n\n\n;;; xml schema parser ;;;;;;;;;;;;;;;;;;;;;;;;;\n\n;;; element and attribute environment\n\n(defvar *xml-schema-envs* nil\n  \"This special variables holds the list of the current xml schema\nelement definition environments. Environments can be nested, the list\nholds them in top to bottom order (the toplevel environment is first.\")\n\n(defun get-xml-schema-ref (ref)\n  \"Get the definition of REF from the current environment stack.\"\n  (dolist (env *xml-schema-envs*)\n    (awhen (gethash ref env)\n\t   (return it))))\n\n(defun (setf get-xml-schema-ref) (newvalue ref)\n  \"Set the definition of REF in the current environment.\"\n  (let ((env (first *xml-schema-envs*)))\n    (awhen (gethash ref env)\n      (error \"There already is an XML Schema element named ~A: ~A.\" ref it))\n    (setf (gethash ref env) newvalue)))\n\n;;; xml schema types\n\n(defgeneric parse-xs-type (type elt)\n  (:documentation \"Parse ELT according to TYPE. TYPE can be a keyword\nto identify base datatypes, or a class derived from XS-TYPE.\"))\n\n(defmacro define-xs-type (name (elt) &rest body)\n  \"Define a base XML Schema type, named by a keyword. For example,\n\\\"xs:string\\\" is identified by :XS-STRING.\"\n  (let ((n (gensym)))\n    `(defmethod parse-xs-type (,(if (keywordp name)\n\t\t\t\t    `(,n (eql ,name))\n\t\t\t\t    name)\n\t\t\t       ,elt)\n      ,@body)))\n\n(defmacro define-xs-type-error (name (elt) &rest body)\n  \"Define the default error function called when ELT could not be\nparsed as a value of type NAME.\"\n  `(define-xs-type ,name ((,elt t))\n    ,@body))\n\n;;; Einfach XML Schema typen, wie primitive Types, einfach Elements\n;;; und Attributes werden direkt zu Lisp primitive geparst. \n\n(define-xs-type :xs-string ((elt dom-impl::text))\n    (dom:node-value elt))\n\n(define-xs-type :xs-string ((elt dom-impl::node))\n    (let ((children (dom:child-nodes elt)))\n      (if (and (= (length children) 1)\n\t       (dom:text-node-p (aref children 0)))\n\t  (dom:node-value (aref children 0))\n\t  \"\")))\n\n(define-xs-type-error :xs-string (elt)\n  (error \"~s could not be parsed as xs:string.\" elt))\n\n(defclass xs-elt ()\n  ((name :initarg :name :initform nil :reader xs-elt-name)\n   (type :initarg :type :initform nil :reader xs-elt-type)))\n\n(defun create-xs-elt (node)\n  (unless (= (length (dom:child-nodes node)) 0)\n    (error \"~a is not a simple XML Scheme element node.\" node))\n  (with-attributes (name type) node\n    (setf (get-xml-schema-ref name)\n\t  (make-instance 'xs-elt\n\t\t\t :name name\n\t\t\t :type (make-keyword-from-string type)))))\n\n(defclass xs-attribute (xs-elt)\n  ())\n\n(defun create-xs-attribute (node)\n  (unless (= (length (dom:child-nodes node)) 0)\n    (error \"~a is not an XML Scheme attribute node.\" node))\n  (with-attributes (name type) node\n    (setf (get-xml-schema-ref name)\n\t  (make-instance 'xs-attribute\n\t\t\t :name name\n\t\t\t :type (make-keyword-from-string type)))))\n\n(define-xs-type (type xs-elt) (elt)\n  (parse-xs-type (xs-elt-type type) elt))\n\n\n(defclass xs-complex-type (xs-type)\n  ((attrs :initarg :attrs :reader xs-ctype-attrs)\n   (children :initarg :children :reader xs-ctype-children)\n   (content :initarg :content :reader xs-ctype-content)))\n\n\n(defclass xs-element ()\n  ((name :initarg :name :reader xs-type-name)\n   (type :initarg :type :reader xs-type-type)))\n\n(defun xs-attribute-p (node)\n  (string-equal (dom:node-name node) \"xs:attribute\"))\n\n(defun xs-element-p (node)\n  (string-equal (dom:node-name node) \"xs:element\"))\n\n(defun xs-simple-type-p (node)\n  (or (xs-attribute-p node)\n      (and (xs-element-p node)\n\t   (null (child-elements node)))))\n\n(defun xs-complex-type-p (node)\n  (let ((children (child-elements node)))\n    (and (xs-element-p node)\n\t (not (null children))\n\t (let ((child (first children)))\n\t   (string-equal (dom:node-name node)\n\t\t\t \"xs:complexType\")))))\n\n(defun parse-schema-node (elt)\n  (cond ((xs-attribute-p elt)\n\t (create-xs-attribute elt))\n\t((xs-simple-type-p elt)\n\t (create-xs-simple-type elt))\n\t#+nil\n\t((xs-complex-type-p elt)\n\t (create-xs-complex-type elt))\n\t(t (error \"Unknown top-level XML Schema node: ~A.\" (dom:node-name elt)))))\n\n(defun parse-schema-file (filename)\n  \"Returns the toplevel XML schema environment.\"\n  (let* ((dom (cxml:parse-file filename (dom:make-dom-builder)))\n\t (root (dom:document-element dom))\n\t (*xml-schema-envs* (list (make-hash-table))))\n    (unless (string-equal (dom:node-name root) \"xs:schema\")\n      (error \"Document is not an XML Schema document.\"))\n    (dolist (elt (child-elements root))\n      (parse-schema-node elt))\n    (pop *xml-schema-envs*)))"
  },
  {
    "path": "third-party/bknr.datastore/license.txt",
    "content": "Copyright (C) 2019 by Hans Hübner <hans.huebner@gmail.com>\n\nPermission to use, copy, modify, and/or distribute this software for\nany purpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\nWARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\nAUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\nDAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR\nPROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "third-party/bknr.datastore/patches/patch-around-mop-cmucl19.lisp",
    "content": ";;; This patch fixes the problem with get-accessor-method-function\n;;; throwing an internal error in cmucl 19\n;;;\n;;; Not yet in cmucl\n\n(in-package :pcl)\n\n(#+cmu19 ext:without-package-locks\n       #-cmu19 progn\n  (defun get-accessor-method-function (gf type class slotd)\n    (let* ((std-method (standard-svuc-method type))\n\t   (str-method (structure-svuc-method type))\n\t   (types1 `((eql ,class) (class-eq ,class) (eql ,slotd)))\n\t   (types (if (eq type 'writer) `(t ,@types1) types1))\n\t   (methods (compute-applicable-methods-using-types gf types))\n\t ;;; XXX hack??\n\t   (std-p (or (null (cdr methods))\n\t\t      (every #'(lambda (method) (memq (car (method-qualifiers method))\n\t\t\t\t\t\t      '(:around :before :after)))\n\t\t\t     methods)))\n\t   (old-std-p (null (cdr methods))))\n      #+nil\n      (unless (eql old-std-p std-p)\n\t(format t \"methods ~s std-p ~A old-std-p ~A~%\"\n\t\tmethods std-p old-std-p)\n\t(format t \"qualifiers ~S~%\" (mapcar #'method-qualifiers methods)))\n      (values\n       (if std-p\n\t   (get-optimized-std-accessor-method-function class slotd type)\n\t   (let* ((optimized-std-fn\n\t\t   (get-optimized-std-slot-value-using-class-method-function\n\t\t    class slotd type))\n\t\t  (method-alist\n\t\t   `((,(car (or (member std-method methods)\n\t\t\t\t(member str-method methods)\n\t\t\t\t(internal-error \"In get-accessor-method-function.\")))\n\t\t      ,optimized-std-fn)))\n\t\t  (wrappers\n\t\t   ;;\n\t\t   ;; This used to be wrapped in \n\t\t   ;; \n\t\t   ;; (unless (and (eq type 'writer)\n\t\t   ;;\t      (every (lambda (x)\n\t\t   ;;\t\t      (eq (car (method-specializers x))\n\t\t   ;;\t\t\t  *the-class-t*))\n\t\t   ;;\t\t    methods))\n\t\t   ;;\n\t\t   ;; but that looks wrong because WRAPPERS nil signals\n\t\t   ;; to GET-SECONDARY-DISPATCH-FUNCTION that we are NOT\n\t\t   ;; generating code for an emf, which is wrong because\n\t\t   ;; we are.\n\t\t   ;;\n\t\t   ;; See the message from Kevin Rosenberg <kevin@rosenberg.net>\n\t\t   ;; to cmucl-imp from Tue, 22 Apr 2003 13:28:23 -0600\n\t\t   ;; and the following thread for a test case where this\n\t\t   ;; causes problems.\n\t\t   ;;\n\t\t   ;; gerd, 2003-04-25\n\t\t   (let ((wrappers (list (wrapper-of class)\n\t\t\t\t\t (class-wrapper class)\n\t\t\t\t\t (wrapper-of slotd))))\n\t\t     (if (eq type 'writer)\n\t\t\t (cons (class-wrapper *the-class-t*) wrappers)\n\t\t\t wrappers)))\n\t\t  (sdfun (get-secondary-dispatch-function \n\t\t\t  gf methods types method-alist wrappers)))\n\t     (get-accessor-from-svuc-method-function class slotd sdfun type)))\n       std-p))))"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.data.impex.asd",
    "content": "\n(in-package :cl-user)\n\n(defpackage :bknr.data.impex.system\n  (:use :cl :asdf))\n\n(in-package :bknr.data.impex.system)\n\n(defsystem :bknr.data.impex\n    :name \"baikonour datastore with xml impex\"\n    :author \"Hans Huebner <hans@huebner.org>\"\n    :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :version \"0\"\n    :maintainer \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :licence \"BSD\"\n    :description \"baikonour - launchpad for lisp satellites\"\n\n    :depends-on (:cl-interpol :unit-test :bknr.utils :bknr.indices\n\t\t\t      :bknr.datastore :bknr.impex)\n\n    :components ((:module \"data\" :components ((:file \"xml-object\")))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.datastore.asd",
    "content": ";; -*-Lisp-*-\n\n(in-package :cl-user)\n\n(defpackage :bknr.datastore.system\n  (:use :cl :asdf))\n\n(in-package :bknr.datastore.system)\n\n(defsystem :bknr.datastore\n  :name \"baikonour datastore\"\n  :author \"Hans Huebner <hans@huebner.org>\"\n  :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n  :version \"0\"\n  :maintainer \"Hans Huebner <hans@huebner.org>\"\n  :licence \"BSD\"\n  :description \"baikonour - launchpad for lisp satellites\"\n\n  :depends-on (:cl-interpol\n               :closer-mop\n               :alexandria\n               :bknr.utils\n               :bknr.indices\n               :yason\n               :trivial-backtrace\n               :lparallel\n               :trivial-utf-8\n               #+sbcl :sb-posix\n               #+lispworks :float-features\n               #+lispworks :cffi)\n\n  :components ((:module \"data\" :components ((:file \"package\")\n                                            (:file \"encoding\" :depends-on (\"package\"))\n                                            (:file \"txn\" :depends-on (\"encoding\" \"package\"))\n                                            (:file \"object\" :depends-on (\"txn\" \"package\"))\n                                            (:file \"json\" :depends-on (\"object\"))\n                                            (:file \"blob\" :depends-on (\"txn\" \"object\" \"package\"))))))\n\n(defsystem :bknr.datastore/tests\n  :depends-on (:bknr.datastore\n               :fiveam\n               :cl-store\n               :bknr.utils\n               :cl-fad\n               :fiveam-matchers)\n  :components ((:module \"data\" :components ((:file \"encoding-test\")\n                                              (:file \"object-tests\")))))\n\n(defmethod asdf:perform ((op asdf:test-op) (system (eql (find-system :bknr.datastore))))\n  (asdf:oos 'asdf:load-op :bknr.datastore/tests)\n  (eval (read-from-string \"(it.bese.fiveam:run! :bknr.datastore)\")))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.impex.asd",
    "content": ";;;; -*- Mode: LISP -*-\n\n(in-package :cl-user)\n\n(defpackage :bknr.impex.system\n  (:use :cl :asdf))\n\n(in-package :bknr.impex.system)\n\n(defsystem :bknr.impex\n  :name \"BKNR impex\"\n  :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n  :version \"0\"\n  :maintainer \"Manuel Odendahl <manuel@bl0rg.net>\"\n  :licence \"BSD\"\n  :description \"BKNR XML import/export\"\n  :long-description \"\"\n\n  :depends-on (:cl-interpol :cxml :closer-mop :bknr.utils :bknr.xml :bknr.indices)\n\n  :components ((:module \"xml-impex\"\n\t\t\t:components\n\t\t\t((:file \"package\")\n\t\t\t (:file \"xml-class\" :depends-on (\"package\"))\n\t\t\t (:file \"xml-import\"\n\t\t\t\t:depends-on (\"package\" \"xml-class\"))\n\t\t\t (:file \"xml-export\"\n\t\t\t\t:depends-on (\"package\" \"xml-class\"))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.indices.asd",
    "content": ";;;; -*- Mode: LISP -*-\n\n(in-package :cl-user)\n\n(defpackage :bknr.indices.system\n  (:use :cl :asdf))\n\n(in-package :bknr.indices.system)\n\n(defsystem :bknr.indices\n  :name \"bknr indices\"\n  :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n  :version \"0\"\n  :maintainer \"Manuel Odendahl <manuel@bl0rg.net>\"\n  :licence \"BSD\"\n  :description \"CLOS class indices\"\n  :long-description \"\"\n\n  :depends-on (:cl-interpol :bknr.utils :bknr.skip-list :closer-mop)\n\n  :components ((:module \"indices\"\n\t\t\t:components\n\t\t\t((:file \"package\")\n\t\t\t (:file \"protocol\" :depends-on (\"package\"))\n\t\t\t (:file \"indices\" :depends-on (\"package\" \"protocol\"))\n\t\t\t (:file \"indexed-class\" :depends-on (\"package\" \"indices\"))\n\t\t\t (:file \"category-index\" :depends-on (\"package\" \"protocol\" \"indices\"))))))\n\n(defsystem :bknr.indices/tests\n  :depends-on (:bknr.indices\n               :bknr.datastore\n               :fiveam\n               :fiveam-matchers)\n  :components ((:module \"indices\"\n                        :components ((:file \"indices-tests\")))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.skip-list.asd",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.skip-list.system\n  (:use :cl :asdf))\n\n(in-package :bknr.skip-list.system)\n\n(defsystem :bknr.skip-list\n    :name \"skip-list\"\n    :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :version \"0\"\n    :maintainer \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :licence \"BSD\"\n    :description \"Skiplist implementation for bknr\"\n\n    :components ((:module \"skip-list\" :components\n\t\t\t  ((:file \"package\")\n\t\t\t   (:file \"skip-list\" :depends-on (\"package\"))))))\n\n(defsystem :bknr.skip-list/tests\n  :depends-on (:fiveam :bknr.skip-list)\n  :components ((:module \"skip-list\" :components\n                        ((:file \"skip-list-tests\")))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.utils.asd",
    "content": ";; -*-Lisp-*-\n\n(in-package :cl-user)\n\n(defpackage :bknr.utils.system\n  (:use :cl :asdf))\n\n(in-package :bknr.utils.system)\n\n(defsystem :bknr.utils\n    :name \"baikonour\"\n    :author \"Hans Huebner <hans@huebner.org>\"\n    :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :version \"0\"\n    :maintainer \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :licence \"BSD\"\n    :description \"baikonour - launchpad for lisp satellites\"\n\n    :depends-on (:cl-interpol :cl-ppcre\n\t\t\t      :md5\n                              :flexi-streams\n                              :alexandria\n                              :bordeaux-threads)\n\n    :components ((:module \"statistics\" :components ((:file \"package\")\n\t\t\t\t\t\t    (:file \"runtime-statistics\" :depends-on (\"package\"))))\n\n\t\t (:module \"utils\" :components ((:file \"package\")\n\t\t\t\t\t       (:file \"utils\" :depends-on (\"package\"))\n\t\t\t\t\t       (:file \"class\" :depends-on (\"package\" \"utils\"))\n\t\t\t\t\t       #+(or cmu allegro openmcl sbcl)\n\t\t\t\t\t       (:file \"smbpasswd\" :depends-on (\"utils\"))\n\t\t\t\t\t       (:file \"actor\" :depends-on (\"utils\" \"acl-mp-compat\"))\n\t\t\t\t\t       (:file \"reader\" :depends-on (\"utils\"))\n\t\t\t\t\t       (:file \"crypt-md5\" :depends-on (\"utils\"))\n\t\t\t\t\t       (:file \"capability\" :depends-on (\"utils\"))\n\t\t\t\t\t       (:file \"make-fdf-file\" :depends-on (\"utils\"))\n\t\t\t\t\t       (:file \"date-calc\")\n\t\t\t\t\t       (:file \"parse-time\")\n\t\t\t\t\t       (:file \"acl-mp-compat\" :depends-on (\"package\"))))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/bknr.xml.asd",
    "content": ";; -*-Lisp-*-\n\n(in-package :cl-user)\n\n(defpackage :bknr.xml.system\n  (:use :cl :asdf))\n\n(in-package :bknr.xml.system)\n\n(defsystem :bknr.xml\n    :name \"baikonour\"\n    :author \"Hans Huebner <hans@huebner.org>\"\n    :author \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :version \"0\"\n    :maintainer \"Manuel Odendahl <manuel@bl0rg.net>\"\n    :licence \"BSD\"\n    :description \"baikonour - launchpad for lisp satellites\"\n    :depends-on (:cl-interpol :cxml)\n    :components ((:module \"xml\" :components ((:file \"package\")\n\t\t\t\t\t     (:file \"xml\" :depends-on (\"package\"))))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/TODO",
    "content": "- text based encoding wieder fuer angenehmeres debuggen vielleicht (?)\n\nx rollforward until, restore until, usw...\n\n- tutorial fertig schreiben\n\n- import-image anschauen, nicht mehr failsafe\n\n- tx-persistent-change-class does not maintain indices\n\n- XXXX broken initialize-persistent-instance (?)\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/blob.lisp",
    "content": "(in-package :bknr.datastore)\n\n(cl-interpol:enable-interpol-syntax)\n\n;;; blob\n\n(defclass blob (store-object)\n  ((type :initarg :type\n         :reader blob-type\n         :reader blob-mime-type\n         :index-type hash-index\n         :index-initargs (:test #'equal)\n         :index-reader blobs-with-type)\n   (timestamp :initarg :timestamp :reader blob-timestamp\n              :initform (get-universal-time)))\n  (:metaclass persistent-class))\n\n#+nil\n(define-persistent-class blob ()\n  ((type :read)\n   (timestamp :read))\n  (:default-initargs :timestamp (get-universal-time)))\n\n(defmethod print-object ((object blob) stream)\n  (print-unreadable-object (object stream :type t)\n    (format stream \"ID: ~D, TYPE: ~A\"\n            (store-object-id object)\n            (if (slot-boundp object 'type) (blob-type object) \"<not yet known>\"))))\n\n(defclass blob-subsystem ()\n  ((n-blobs-per-directory\n    :initform nil\n    :initarg :n-blobs-per-directory\n    :accessor n-blobs-per-directory\n    :documentation \"The number of blobs to store in each subdirectory of\nblob-root.  If this is NIL, do not create subdirectories.  This parameter\nmust be specified when the when the store is created and is stored in the\ndatastore root to ensure that the correct value is used later.\")))\n\n(defun store-blob-root-pathname (&optional (store *store*))\n  (merge-pathnames #p\"blob-root/\" (store-directory store)))\n\n(defun store-blob-root-tempdir (&optional (store *store*))\n  (merge-pathnames #p\"temp/\" (store-blob-root-pathname store)))\n\n(defmethod initialize-subsystem ((subsystem blob-subsystem) store store-existed-p)\n  (let* ((store-dir (store-current-directory store))\n         (nblobs-pathname\n          (make-pathname :name \"n-blobs-per-directory\" :defaults store-dir)))\n    (if store-existed-p\n        (if (probe-file nblobs-pathname)\n            (unless (eql (n-blobs-per-directory subsystem)\n                         (with-open-file (s nblobs-pathname)\n                           (read s)))\n              (error \"BLOB configuration file ~A disagrees with user configuration\"\n                     nblobs-pathname))\n            (progn\n              (warn \"Could not find stored number of blobs per directory, writing current value: ~S\"\n                    (n-blobs-per-directory subsystem))\n              (with-open-file (s nblobs-pathname :direction :output)\n                (write (n-blobs-per-directory subsystem) :stream s))))\n        (with-open-file (s nblobs-pathname :direction :output)\n          (write (n-blobs-per-directory subsystem) :stream s)))))\n\n(defun blob-subsystem ()\n  (or (find-if (lambda (subsystem)\n                 (typep subsystem 'blob-subsystem))\n               (store-subsystems *store*))\n      (error \"store ~A does not have a BLOB subsysten\" *store*)))\n\n(defmethod initialize-instance :before ((blob blob) &rest args)\n  (declare (ignore args))\n  (unless (blob-subsystem)\n    (error \"Can't create a BLOB in a datastore without BLOB subsystem.\")))\n\n(defmethod blob-relative-pathname (id)\n  (let ((n-files (n-blobs-per-directory (blob-subsystem))))\n    (if n-files\n        (make-pathname\n         :directory (list :relative (write-to-string (truncate id n-files)))\n         :name (write-to-string id))\n        (make-pathname :name (write-to-string id)))))\n\n(defgeneric blob-pathname (blob-or-blob-id))\n\n(defmethod blob-pathname ((id integer))\n  (ensure-directories-exist (merge-pathnames (blob-relative-pathname id)\n                                             (store-blob-root-pathname *store*)) :verbose t))\n\n(defmethod blob-pathname ((blob blob))\n  (blob-pathname (store-object-id blob)))\n\n(defmacro with-open-blob ((s blob &rest args) &rest body)\n  `(with-open-file (,s (blob-pathname ,blob) ,@args)\n     ,@body))\n\n(defgeneric blob-size (blob))\n\n(defmethod blob-size ((blob blob))\n  (with-open-blob (s blob)\n    (file-length s)))\n\n(defgeneric blob-to-stream (blob s))\n\n(defmethod blob-to-stream ((blob blob) out)\n  (with-open-blob (in blob :direction :input\n                      :element-type '(unsigned-byte 8))\n    (copy-stream in out)))\n\n(defgeneric blob-to-file (blob pathname))\n\n(defmethod blob-to-file ((blob blob) pathname)\n  (with-open-file (out pathname :direction :output :element-type '(unsigned-byte 8))\n    (blob-to-stream blob out)))\n\n(defgeneric blob-from-stream (blob stream))\n\n(defmethod blob-from-stream ((blob blob) in)\n  (with-open-blob (out blob :direction :output\n                       :element-type '(unsigned-byte 8)\n                       :if-exists :overwrite\n                       :if-does-not-exist :create)\n    (copy-stream in out)))\n\n(defgeneric blob-from-string (blob string))\n\n(defmethod blob-from-string ((blob blob) string)\n  (with-open-blob (out blob :direction :output\n                       :if-exists :overwrite\n                       :if-does-not-exist :create)\n    (write-string string out)))\n\n(defgeneric blob-from-array (blob array))\n\n(defmethod blob-from-array ((blob blob) in)\n  (with-open-blob (out blob :direction :output\n                       :element-type '(unsigned-byte 8)\n                       :if-exists :overwrite\n                       :if-does-not-exist :create)\n    (write-sequence in out)))\n\n(defgeneric blob-from-file (blob pathname))\n\n(defmethod blob-from-file ((blob blob) pathname)\n  (with-open-file (in pathname :direction :input :element-type '(unsigned-byte 8))\n    (blob-from-stream blob in)))\n\n(defun make-blob-from-file (pathname &optional (class 'blob) &rest initargs)\n  (unless (getf initargs :type)\n    (setf (getf initargs :type)\n          (pathname-type pathname)))\n  (let ((blob (apply #'make-instance class initargs)))\n    (blob-from-file blob pathname)\n    blob))\n\n(defmethod rename-file-to-blob ((blob blob) pathname)\n  (move-file pathname (blob-pathname blob)))\n\n(defmethod restore-subsystem ((store store) (subsystem blob-subsystem) &key until)\n  (declare (ignore until))\n  ;; the blob subsystem does not do anything upon restore\n  )\n\n(defmethod snapshot-subsystem ((store store) (subsystem blob-subsystem))\n  (let* ((store-dir (ensure-store-current-directory store))\n         (nblobs-pathname\n          (make-pathname :name \"n-blobs-per-directory\" :defaults store-dir)))\n    (with-open-file (s nblobs-pathname :direction :output)\n      (write (n-blobs-per-directory subsystem) :stream s))))\n\n(defun delete-orphaned-blob-files (&optional (cold-run t))\n  (dolist (blob-pathname (directory (merge-pathnames (make-pathname :name :wild :directory '(:relative :wild-inferiors))\n                                                     (store-blob-root-pathname))))\n    (handler-case\n        (when (pathname-name blob-pathname)\n          (let* ((object-id (parse-integer (pathname-name blob-pathname)))\n                 (object (find-store-object object-id)))\n            (labels ((delete-orphan (pathname)\n                       (handler-case\n                           (if cold-run\n                               (format t \"cold run, not deleting ~A~%\" pathname)\n                               (delete-file pathname))\n                         (error (e)\n                           (warn \"can't delete file ~A: ~A\" pathname e)))))\n              (cond\n                ((null object)\n                 (format t \"; file ~A does not have a corresponding blob object - deleted~%\" blob-pathname)\n                 (delete-orphan blob-pathname))\n                ((not (subtypep (type-of object) 'blob))\n                 (format t \"; file ~A has an object id of an object which does not have a subtype of blob (~A) - deleted~%\"\n                         blob-pathname (type-of object))\n                 (delete-orphan blob-pathname))))))\n      (error (e)\n        (error \"~A checking blob pathname ~A\" e blob-pathname)))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/convert.lisp",
    "content": ";;;; Konvertierung vom Sexpr-Snapshot-Format in das Binaerformat.\n;;;; Anleitung:\n;;;;\n;;;; Im laufenden Server mit altem Code:\n;;;;   - Einen Snapshot erstellen.  Transaktionsfiles koennen wir nicht laden.\n;;;;\n;;;; Die Konvertierung findet dann offline statt:\n;;;;   - BOS-Branch kompilieren, dieses File laden.\n;;;;   - Der Anwendungscode kann geladen sein, muss aber nicht.  Es\n;;;;     werden aber die im Snapshot referenzierten Pakete benoetigt.\n;;;;     Falls der eigentliche Code nicht geladen ist, einfach ein\n;;;;     MAKE-PACKAGE auf alle fehlenden Pakete machen.\n;;;;   - (CONVERT-SNAPSHOT \"/path/to/datastore/snapshot\") aufrufen\n;;;;   - Heraus kommt \"snapshot.new\", das kopiere man nach erfolgreicher\n;;;;     Konvertierung in \"snapshot\" um.\n;;;;\n;;;; Das hier erstellte Snapshotfile ist noch \"unkomprimert\".  Nach\n;;;; erfolglichem Start der Anwendung teste man den normalen Snapshotvorgang,\n;;;; das dann erstelle File muesste etwas kleiner sein.\n\n(in-package :bknr.datastore)\n\n(defvar *layout-counter* 0)\n\n(defun convert-snapshot/encode-layout (class-name slots stream)\n  (let ((id (incf *layout-counter*)))\n    (%write-tag #\\L stream)\n    (%encode-integer id stream)\n    (%encode-symbol class-name stream)\n    (%encode-integer (length slots) stream)\n    (dolist (slot slots)\n      (%encode-symbol slot stream))\n    id))\n\n(defun convert-snapshot/create-object (class objid slots values stream)\n  (let ((layout-id (convert-snapshot/encode-layout class nil stream)))\n    (%write-tag #\\O stream)\n    (%encode-integer layout-id stream)\n    (%encode-integer objid stream)\n    (convert-snapshot/set-slots objid slots values stream)))\n\n(defun convert-snapshot/set-slots (objid slots values stream)\n  (let ((layout-id (convert-snapshot/encode-layout 'dummy slots stream)))\n    (%write-tag #\\S stream)\n    (%encode-integer layout-id stream)\n    (%encode-integer objid stream)\n    (dolist (value values)\n      (encode value stream))))\n\n(defun convert-snapshot/exp (exp out)\n  (declare (optimize (speed 3)))\n  (if (consp exp)\n      (case (car exp)\n        (create-object\n         (destructuring-bind (class &rest initargs) (cdr exp)\n           (loop with id = nil\n              for (slot value) on initargs by #'cddr\n              collect slot into slots\n              collect (convert-snapshot/exp value out) into values\n              do\n              (when (eq slot :id)\n                (setf id value))\n              finally (convert-snapshot/create-object class id slots values out))))\n        (set-slots\n         (destructuring-bind (obj &rest initargs) (cdr exp)\n           (loop for (slot value) on initargs by #'cddr\n              collect slot into slots\n              collect (convert-snapshot/exp value out) into values\n              finally (convert-snapshot/set-slots obj slots values out))))\n        (store-object-with-id\n         (let ((o (allocate-instance (find-class 'store-object))))\n           (setf (slot-value o 'id) (second exp))\n           o))\n        (t\n         (eval exp)))\n      exp))\n\n(defun convert-snapshot (file)\n  (with-store-state (:restore)\n    (with-open-file (in file)\n      (with-open-file (out (make-pathname :type \"new\" :defaults file)\n                           :direction :output\n                           :element-type '(unsigned-byte 8))\n        (let ((*package* #.*package*))\n          (loop for exp = (read in nil 'eof nil)\n             until (eql exp 'eof)\n             do (convert-snapshot/exp exp out)))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/encoding-test.lisp",
    "content": "(defpackage :bknr.datastore/encoding-test\n  (:use #:cl\n        #:bknr.datastore\n        #:fiveam)\n  (:import-from #:alexandria\n                #:ensure-list)\n  (:import-from #:bknr.datastore\n                #:*string-decoding-cache*\n                #:*max-string-decoding-cache-size*\n                #:decode\n                #:encode))\n(in-package :bknr.datastore/encoding-test)\n\n(5am:def-suite :bknr.datastore/encoding-test)\n(5am:in-suite :bknr.datastore/encoding-test)\n\n(defun files-identical-content-p (path-a path-b)\n  \"Are files of PATH-A and PATH-B byte per byte identical?\"\n  (with-open-file (in-a path-a :element-type '(unsigned-byte 8))\n    (with-open-file (in-b path-b :element-type '(unsigned-byte 8))\n      (loop\n         for byte-a = (read-byte in-a nil nil)\n         for byte-b = (read-byte in-b nil nil)\n         while (or byte-a byte-b)\n         unless (and byte-a byte-b (= byte-a byte-b))\n         return nil\n         finally (return t)))))\n\n(defun congruent-p (a b)\n  \"Are lisp value A and B (deeply) congruent?\"\n  (bknr.utils:with-temporary-file (path-a)\n    (bknr.utils:with-temporary-file (path-b)\n      (cl-store:store a path-a)\n      (cl-store:store b path-b)\n      (prog1\n          (files-identical-content-p path-a path-b)\n        (delete-file path-a)\n        (delete-file path-b)))))\n\n(defun copy-by-encoding (value)\n  (bknr.utils:with-temporary-file (path)\n    (with-open-file (out path :direction :output :if-exists :supersede\n                         :element-type '(unsigned-byte 8))\n      (encode value out))\n    (with-open-file (in path :element-type '(unsigned-byte 8))\n      (decode in))))\n\n(defmacro test-encoding (name value)\n  (let ((options (ensure-list name)))\n    (destructuring-bind (name &key skip) options\n      `(5am:test ,name\n                  ,(if skip\n                       `(5am:skip ,skip)\n                       `(5am:is (congruent-p ,value (copy-by-encoding ,value))))))))\n\n(test-encoding list.1 '(1 2 3))\n(test-encoding list.len.30 (loop repeat 30 collect 'x))\n(test-encoding list.len.254 (loop repeat 254 collect 'x))\n(test-encoding list.len.255 (loop repeat 255 collect 'x))\n(test-encoding list.len.256 (loop repeat 256 collect 'x))\n(test-encoding list.len.257 (loop repeat 257 collect 'x))\n(test-encoding list.len.3000 (loop repeat 3000 collect 'x))\n(test-encoding improper-list.1 '(1 2 3 4 . 5))\n\n(test-encoding cons.1 '(1 . 2))\n\n;;; from cl-store :)\n(test-encoding integer.1 1)\n(test-encoding integer.2 0)\n(test-encoding integer.3 23423333333333333333333333423102334)\n(test-encoding integer.4 -2322993)\n(test-encoding integer.5 most-positive-fixnum)\n(test-encoding integer.6 most-negative-fixnum)\n\n;; ratios\n(test-encoding ratio.1 1/2)\n(test-encoding ratio.2 234232/23434)\n(test-encoding ratio.3 -12/2)\n(test-encoding ratio.4 -6/11)\n(test-encoding ratio.5 23222/13)\n\n;; complex numbers - currently not supported\n;; (test-encoding complex.1 #C(0 1))\n;; (test-encoding complex.2 #C(0.0 1.0))\n;; (test-encoding complex.3 #C(32 -23455))\n;; (test-encoding complex.4 #C(-222.32 2322.21))\n;; (test-encoding complex.5 #C(-111 -1123))\n;; (test-encoding complex.6 #C(-11.2 -34.5))\n\n;; single-float\n(test-encoding single-float.1 3244.32)\n(test-encoding single-float.2 0.12)\n(test-encoding single-float.3 -233.001)\n(test-encoding single-float.4 most-positive-single-float)\n(test-encoding single-float.5 most-negative-single-float)\n\n;; double-float\n(test-encoding double-float.1 2343.3d0)\n(test-encoding double-float.2 -1211111.3343d0)\n(test-encoding double-float.3 99999999999123456789012345678222222222222290.0987654321d0)\n(test-encoding double-float.4 -99999999999123456789012345678222222222222290.0987654321d0)\n(test-encoding double-float.5 most-positive-double-float)\n(test-encoding double-float.6 most-negative-double-float)\n\n;; characters\n(test-encoding char.1 #\\Space)\n(test-encoding char.2 #\\f )\n(test-encoding char.3 #\\Rubout)\n(test-encoding char.4 (code-char 255))\n\n(5am:test char.random\n           (5am:for-all ((char (5am:gen-character)))\n                         (5am:is (char= char (copy-by-encoding char)))))\n\n;; strings\n(5am:test string.random\n           (5am:for-all ((string (5am:gen-string)))\n                         (5am:is (string= string (copy-by-encoding string)))))\n\n(5am:test string.random.code-limited\n           (5am:for-all ((string (5am:gen-string :elements (5am:gen-character :code-limit 10000))))\n                         (5am:is (string= string (copy-by-encoding string)))))\n\n(5am:test string.decode-utf-8\n           (labels ((decode-string-from-octets (octets)\n                      (flexi-streams:with-input-from-sequence (in octets)\n                        (bknr.datastore::%decode-string in))))\n             (5am:is (string-equal \"<=>\" (decode-string-from-octets #(1 3 60 61 62))))\n             ;; #\\? is the substitution char\n             (string-equal \"<?>\" (decode-string-from-octets #(1 3 60 188 62)))\n             ;; kilian 2008-03-20: the following for-all test failed on ccl,\n             ;; because the correct utf-8 sequence could produce a char-code\n             ;; above char-code-limit - bknr.datastore::%decode-string should\n             ;; throw an error in this case, but I dont know how to test this\n             ;; (5am:for-all ((octets (5am:gen-buffer)))\n             ;;       (5am:finishes (decode-string-from-octets (concatenate 'vector (vector 1 (length octets)) octets))))\n             ))\n\n;; #+(or (and sbcl sb-unicode) lispworks clisp acl)\n;; (progn\n;;   (test-encoding unicode.1 (map #-lispworks 'string\n;;                             #+lispworks 'lw:text-string\n;;                             #'code-char (list #X20AC #X3BB)))\n;;   (test-encoding unicode.2 (intern (map #-lispworks 'string\n;;                                     #+lispworks 'lw:text-string\n;;                                     #'code-char (list #X20AC #X3BB))\n;;                                :pwgl-test-suite)))\n;; vectors\n(test-encoding vector.1 #(1 2 3 4))\n\n\n(test-encoding vector.2 (make-array 5 :element-type 'fixnum\n                                      :initial-contents (list 1 2 3 4 5)))\n\n(test-encoding vector.4 #*101101101110)\n(test-encoding vector.3\n               (make-array 5\n                           :element-type 'fixnum\n                           :fill-pointer 2\n                           :initial-contents (list 1 2 3 4 5)))\n\n\n\n(test-encoding vector.5 #*)\n(test-encoding vector.6 #())\n\n\n;; arrays\n(test-encoding array.1\n               (make-array '(2 2) :initial-contents '((1 2) (3 4))))\n\n(test-encoding array.2\n               (make-array '(2 2) :initial-contents '((1 1) (1 1))))\n\n(test-encoding array.3\n               (make-array '(2 2) :element-type 'fixnum :initial-element 3))\n\n(test-encoding (array.3b)\n               (make-array '(2 2) :element-type '(mod 10) :initial-element 3))\n\n(test-encoding array.4\n               (make-array  '(2 3 5)\n                            :initial-contents\n                            '(((1 2 #\\f 5 12.0) (#\\Space 0 4 1 0) ('d 0 #() 3 -1))\n                              ((0 #\\a #\\b 4 #\\q) (12.0d0 0 '(d) 4 1)\n                               (#\\Newline 1 7 #\\4 #\\0)))))\n\n;; (test-encoding array.5\n;;                (let* ((a1 (make-array 5))\n;;                       (a2 (make-array 4 :displaced-to a1\n;;                                       :displaced-index-offset 1))\n;;                       (a3 (make-array 2 :displaced-to a2\n;;                                       :displaced-index-offset 2)))\n;;                  a3))\n\n\n\n\n\n;; symbols\n\n(test-encoding symbol.1  t)\n(test-encoding symbol.2  nil)\n(test-encoding symbol.3  :foo)\n(test-encoding symbol.4  'bknr.datastore::foo)\n(test-encoding symbol.5  'make-hash-table)\n(test-encoding symbol.6 '|foo bar|)\n(test-encoding symbol.7 'foo\\ bar\\ baz)\n\n;; (deftest gensym.1 (progn\n;;                     (store (gensym \"Foobar\") *test-file*)\n;;                     (let ((new (restore *test-file*)))\n;;                       (list (symbol-package new)\n;;                             (mismatch \"Foobar\" (symbol-name new)))))\n;;          (nil 6))\n\n;; This failed in cl-store < 0.5.5\n;; (deftest gensym.2 (let ((x (gensym)))\n;;                     (store (list x x) *test-file*)\n;;                     (let ((new (restore *test-file*)))\n;;                       (eql (car new) (cadr new))))\n;;          t)\n\n\n;; cons\n\n(test-encoding cons.1.1 '(1 2 3))\n(test-encoding cons.2 '((1 2 3)))\n(test-encoding cons.3 '(#\\Space 1 1.2 1.3 #(1 2 3)))\n\n(test-encoding cons.4  '(1 . 2))\n(test-encoding cons.5  '(t . nil))\n(test-encoding cons.6 '(1 2 3 . 5))\n;; (deftest cons.7 (let ((list (cons nil nil))) ;  '#1=(#1#)))\n;;                   (setf (car list) list)\n;;                   (store list *test-file*)\n;;                   (let ((ret (restore *test-file*)))\n;;                     (eq ret (car ret))))\n;;          t)\n\n\n;; hash tables\n;; for some reason (make-hash-table) is not equalp\n;; to (make-hash-table) with ecl.\n\n#-openmcl(test-encoding hash.1 (make-hash-table :size 0))\n#+openmcl(5am:test hash.1 (5am:skip \"the hash-table-size is not preserved - do we need to fix this?\"))\n#-openmcl(test-encoding hash.2 (make-hash-table :size 0 :test #'equal))\n#+openmcl(5am:test hash.2 (5am:skip \"the hash-table-size is not preserved - do we need to fix this?\"))\n\n;; ;; packages\n;; (test-encoding package.1 (find-package :cl-store))\n\n;; (defpackage foo\n;;   (:nicknames foobar)\n;;   (:use :cl)\n;;   (:shadow cl:format)\n;;   (:export bar))\n\n;; (defun package-restores ()\n;;   (let (( *nuke-existing-packages* t))\n;;     (store (find-package :foo) *test-file*)\n;;     (delete-package :foo)\n;;     (restore *test-file*)\n;;     (list (package-name (find-package :foo))\n;;           (mapcar #'package-name (package-use-list :foo))\n;;           (package-nicknames :foo)\n;;           (equalp (remove-duplicates (package-shadowing-symbols :foo))\n;;                   (list (find-symbol \"FORMAT\" \"FOO\")))\n;;           (equalp (cl-store::external-symbols (find-package :foo))\n;;                   (make-array 1 :initial-element (find-symbol \"BAR\" \"FOO\"))))))\n\n\n;; ; unfortunately it's difficult to portably test the internal symbols\n;; ; in a package so we just assume that it's OK.\n;; (deftest package.2\n;;          (package-restores)\n;;          (\"FOO\" (\"COMMON-LISP\") (\"FOOBAR\") t t))\n\n;; ;; objects\n(define-persistent-class foo ()\n  ((x :update)))\n\n(define-persistent-class bar (foo)\n  ((y :update)))\n\n;; (deftest standard-object.1\n;;   (let ((val (store (make-instance 'foo :x 3) *test-file*)))\n;;     (= (get-x val) (get-x (restore *test-file*))))\n;;   t)\n\n;; (deftest standard-object.2\n;;   (let ((val (store (make-instance 'bar\n;;                                    :x (list 1 \"foo\" 1.0)\n;;                                    :y (vector 1 2 3 4))\n;;                     *test-file*)))\n;;     (let ((ret (restore *test-file*)))\n;;       (and (equalp (get-x val) (get-x ret))\n;;            (equalp (get-y val) (get-y ret)))))\n;;   t)\n\n;; (deftest standard-object.3\n;;   (let ((*store-class-slots* nil)\n;;         (val (make-instance 'baz :z 9)))\n;;     (store val *test-file*)\n;;     (make-instance 'baz :z 2)\n;;     (= (get-z (restore *test-file*))\n;;        2))\n;;   t)\n\n;; (deftest standard-object.4\n;;   (let ((*store-class-slots* t)\n;;         (val (make-instance 'baz :z 9)))\n;;     (store val *test-file*)\n;;     (make-instance 'baz :z 2)\n;;     (let ((ret (restore *test-file*)))\n;;       (= (get-z ret )\n;;          9)))\n;;   t)\n\n;; ;; classes\n;; (deftest standard-class.1 (progn (store (find-class 'foo) *test-file*)\n;;                                  (restore *test-file*)\n;;                                  t)\n;;   t)\n\n;; (deftest standard-class.2 (progn (store (find-class 'bar) *test-file*)\n;;                                  (restore *test-file*)\n;;                                  t)\n;;   t)\n\n;; (deftest standard-class.3 (progn (store (find-class 'baz) *test-file*)\n;;                                  (restore *test-file*)\n;;                                  t)\n;;   t)\n\n\n\n;; ;; conditions\n;; (deftest condition.1\n;;   (handler-case (/ 1 0)\n;;     (division-by-zero (c)\n;;       (store c *test-file*)\n;;       (typep (restore *test-file*) 'division-by-zero)))\n;;   t)\n\n;; (deftest condition.2\n;;   (handler-case (car (read-from-string \"3\"))\n;;     ;; allegro pre 7.0 signalled a simple-error here\n;;     ((or type-error simple-error) (c)\n;;       (store c *test-file*)\n;;       (typep (restore *test-file*)\n;;              '(or type-error simple-error))))\n;;   t)\n\n;; ;; structure-object\n\n;; (defstruct a\n;;   a b c)\n\n;; (defstruct (b (:include a))\n;;   d e f)\n\n;; #+(or sbcl cmu lispworks openmcl)\n;; (test-encoding structure-object.1 (make-a :a 1 :b 2 :c 3))\n;; #+(or sbcl cmu lispworks openmcl)\n;; (test-encoding structure-object.2 (make-b :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))\n;; #+(or sbcl cmu lispworks openmcl)\n;; (test-encoding structure-object.3 (make-b :a 1 :b (make-a :a 1 :b 3 :c 2)\n;;                                       :c #\\Space :d #(1 2 3) :e (list 1 2 3)\n;;                                       :f (make-hash-table)))\n\n;; ;; setf test\n;; (test-encoding setf.1 (setf (restore *test-file*) 0))\n;; (test-encoding setf.2 (incf (restore *test-file*)))\n;; (test-encoding setf.3 (decf (restore *test-file*) 2))\n\n;; (test-encoding pathname.1 #P\"/home/foo\")\n;; (test-encoding pathname.2 (make-pathname :name \"foo\"))\n;; (test-encoding pathname.3 (make-pathname :name \"foo\" :type \"bar\"))\n\n\n;; ; built-in classes\n;; (test-encoding built-in.1 (find-class 'hash-table))\n;; (test-encoding built-in.2 (find-class 'integer))\n\n\n;; ;; find-backend tests\n;; (deftest find-backend.1\n;;     (and (find-backend 'cl-store) t)\n;;   t)\n\n;; (deftest find-backend.2\n;;     (find-backend (gensym))\n;;   nil)\n\n;; (deftest find-backend.3\n;;     (handler-case (find-backend (gensym) t)\n;;       (error (c) (and c t))\n;;       (:no-error (val) (and val nil)))\n;;   t)\n\n\n\n;; ;; circular objects\n;; (defvar circ1 (let ((x (list 1 2 3 4)))\n;;                 (setf (cdr (last x)) x)))\n;; (deftest circ.1 (progn (store circ1 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (eql (cddddr x) x)))\n;;   t)\n\n;; (defvar circ2 (let ((x (list 2 3 4 4 5)))\n;;                 (setf (second x) x)))\n;; (deftest circ.2 (progn (store circ2 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (eql (second x) x)))\n;;   t)\n\n\n\n;; (defvar circ3 (let ((x (list (list 1 2 3 4 )\n;;                              (list 5 6 7 8)\n;;                              9)))\n;;                 (setf (second x) (car x))\n;;                 (setf (cdr (last x)) x)\n;;                 x))\n\n;; (deftest circ.3 (progn (store circ3 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (and (eql (second x) (car x))\n;;                               (eql (cdddr x) x))))\n;;   t)\n\n\n;; (defvar circ4 (let ((x (make-hash-table)))\n;;                 (setf (gethash 'first x) (make-hash-table))\n;;                 (setf (gethash 'second x) (gethash 'first x))\n;;                 (setf (gethash 'inner (gethash 'first x)) x)\n;;                 x))\n\n;; (deftest circ.4 (progn (store circ4 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (and (eql (gethash 'first x)\n;;                                   (gethash 'second x))\n;;                               (eql x\n;;                                   (gethash 'inner\n;;                                            (gethash 'first x))))))\n;;   t)\n\n;; (deftest circ.5  (let ((circ5 (make-instance 'bar)))\n;;                    (setf (get-y circ5) circ5)\n;;                    (store circ5 *test-file*)\n;;                    (let ((x (restore *test-file*)))\n;;                      (eql x (get-y x))))\n;;   t)\n\n\n;; (defvar circ6 (let ((y (make-array '(2 2 2)\n;;                                    :initial-contents '(((\"foo\" \"bar\")\n;;                                                         (\"me\" \"you\"))\n;;                                                        ((5 6) (7 8))))))\n;;                 (setf (aref y 1 1 1) y)\n;;                 (setf (aref y 0 0 0) (aref y 1 1 1))\n;;                 y))\n\n\n;; (deftest circ.6 (progn (store circ6 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (and (eql (aref x 1 1 1) x)\n;;                               (eql (aref x 0 0 0) (aref x 1 1 1)))))\n;;   t)\n\n\n\n;; (defvar circ7 (let ((x (make-a)))\n;;                 (setf (a-a x) x)))\n\n;; #+(or sbcl cmu lispworks)\n;; (deftest circ.7 (progn (store circ7 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (eql (a-a x) x)))\n;;   t)\n\n;; (defvar circ.8 (let ((x \"foo\"))\n;;                  (make-pathname :name x :type x)))\n\n\n;; ;; clisp apparently creates a copy of the strings in a pathname\n;; ;; so a test for eqness is pointless.\n;; #-clisp\n;; (deftest circ.8 (progn (store circ.8 *test-file*)\n;;                        (let ((x (restore *test-file*)))\n;;                          (eql (pathname-name x)\n;;                               (pathname-type x))))\n;;   t)\n\n\n;; (deftest circ.9 (let ((val (vector \"foo\" \"bar\" \"baz\" 1 2)))\n;;                   (setf (aref val 3) val)\n;;                   (setf (aref val 4) (aref val 0))\n;;                   (store val *test-file*)\n;;                   (let ((rest (restore *test-file*)))\n;;                     (and (eql rest (aref rest 3))\n;;                          (eql (aref rest 4) (aref rest 0)))))\n;;   t)\n\n;; (deftest circ.10 (let* ((a1 (make-array 5))\n;;                         (a2 (make-array 4 :displaced-to a1\n;;                                         :displaced-index-offset 1))\n;;                         (a3 (make-array 2 :displaced-to a2\n;;                                         :displaced-index-offset 2)))\n;;                    (setf (aref a3 1) a3)\n;;                    (store a3 *test-file*)\n;;                    (let ((ret (restore *test-file*)))\n;;                      (eql a3 (aref a3 1))))\n;;   t)\n\n;; (defvar circ.11 (let ((x (make-hash-table)))\n;;                   (setf (gethash x x) x)\n;;                   x))\n\n;; (deftest circ.11 (progn (store circ.11 *test-file*)\n;;                         (let ((val (restore *test-file*)))\n;;                           (eql val (gethash val val))))\n;;   t)\n\n;; (deftest circ.12 (let ((x (vector 1 2 \"foo\" 4 5)))\n;;                    (setf (aref x 0) x)\n;;                    (setf (aref x 1) (aref x 2))\n;;                    (store x *test-file*)\n;;                    (let ((ret (restore *test-file*)))\n;;                      (and (eql (aref ret 0) ret)\n;;                           (eql (aref ret 1) (aref ret 2)))))\n;;   t)\n\n;; (defclass foo.1 ()\n;;   ((a :accessor foo1-a)))\n\n;; ;; a test from Robert Sedgwick which crashed in earlier\n;; ;; versions (pre 0.2)\n;; (deftest circ.13 (let ((foo (make-instance 'foo.1))\n;;                        (bar (make-instance 'foo.1)))\n;;                    (setf (foo1-a foo) bar)\n;;                    (setf (foo1-a bar) foo)\n;;                    (store (list foo) *test-file*)\n;;                    (let ((ret (car (restore *test-file*))))\n;;                      (and (eql ret (foo1-a (foo1-a ret)))\n;;                           (eql (foo1-a ret)\n;;                               (foo1-a (foo1-a (foo1-a ret)))))))\n;;   t)\n\n\n;; (deftest circ.14 (let ((list '#1=(1 2 3 #1# . #1#)))\n;;                    (store list *test-file*)\n;;                    (let ((ret (restore *test-file*)))\n;;                      (and (eq ret (cddddr ret))\n;;                           (eq (fourth ret) ret))))\n;;          t)\n\n\n\n\n;; (deftest circ.15 (let ((list '#1=(1 2 3 #2=(#2#) . #1#)))\n;;                    (store list *test-file*)\n;;                    (let ((ret (restore *test-file*)))\n;;                      (and (eq ret (cddddr ret))\n;;                           (eq (fourth ret)\n;;                               (car (fourth ret))))))\n;;          t)\n\n\n\n;; ;; this had me confused for a while since what was\n;; ;; restored #1=(1 (#1#) #1#) looks nothing like this list,\n;; ;; but it turns out that it is correct\n;; (deftest circ.16  (let ((list '#1=(1 #2=(#1#) . #2#)))\n;;                     (store list *test-file*)\n;;                     (let ((ret (restore *test-file*)))\n;;                       (and (eq ret (caadr ret))\n;;                            (eq ret (third ret)))))\n;;          t)\n\n;; ;; large circular lists\n;; (deftest large.1 (let ((list (make-list 100000)))\n;;                    (setf (cdr (last list)) list)\n;;                    (store list *test-file*)\n;;                    (let ((ret (restore *test-file*)))\n;;                      (eq (nthcdr 100000 ret) ret)))\n;;          t)\n\n;; ;; large dotted lists\n;; (test-encoding large.2 (let ((list (make-list 100000)))\n;;                      (setf (cdr (last list)) 'foo)\n;;                      list))\n\n\n\n;; ;; custom storing\n;; (defclass random-obj () ((size :accessor size :initarg :size)))\n\n;; (defvar *random-obj-code* (register-code 100 'random-obj))\n\n;; (defstore-cl-store (obj random-obj buff)\n;;   (output-type-code *random-obj-code* buff)\n;;   (store-object (size obj) buff))\n\n;; (defrestore-cl-store (random-obj buff)\n;;   (random (restore-object buff)))\n\n\n;; (deftest custom.1\n;;   (progn (store (make-instance 'random-obj :size 5) *test-file* )\n;;          (typep (restore *test-file*) '(integer 0 4)))\n;;   t)\n\n\n\n;; (test-encoding function.1 #'restores)\n;; (test-encoding function.2 #'car)\n\n;; (test-encoding gfunction.1 #'cl-store:restore)\n;; (test-encoding gfunction.2 #'cl-store:store)\n;; #-clisp\n;; (test-encoding gfunction.3 #'(setf get-y))\n\n\n;; (deftest nocirc.1\n;;     (let* ((string \"FOO\")\n;;            (list `(,string . ,string))\n;;            (*check-for-circs* nil))\n;;       (store list *test-file*)\n;;       (let ((res (restore *test-file*)))\n;;         (and (not (eql (car res) (cdr res)))\n;;              (string= (car res) (cdr res)))))\n;;   t)\n\n\n;; (defstruct st.bar x)\n;; (defstruct (st.foo (:conc-name f-)\n;;                    (:constructor fooo (z y x))\n;;                    (:copier cp-foo)\n;;                    (:include st.bar)\n;;                    (:predicate is-foo)\n;;                    (:print-function (lambda (obj st dep)\n;;                                       (declare (ignore dep))\n;;                                       (print-unreadable-object (obj st :type t)\n;;                                         (format st \"~A\" (f-x obj))))))\n;;   (y 0 :type integer) (z nil :type simple-string))\n\n\n;; #+(or sbcl cmu)\n;; (deftest struct-class.1\n;;     (let* ((obj (fooo \"Z\" 2 3))\n;;            (string (format nil \"~A\" obj)))\n;;       (let ((*nuke-existing-classes* t))\n;;         (store (find-class 'st.foo) *test-file*)\n;;         (fmakunbound 'cp-foo)\n;;         (fmakunbound 'is-foo)\n;;         (fmakunbound 'fooo)\n;;         (fmakunbound 'f-x)\n;;         (fmakunbound 'f-y)\n;;         (fmakunbound 'f-z)\n;;         (restore *test-file*)\n;;         (let* ((new-obj (cp-foo (fooo \"Z\" 2 3)))\n;;                (new-string (format nil \"~A\" new-obj)))\n;;           (list (is-foo new-obj) (equalp obj new-obj)\n;;                 (string= new-string string)\n;;                 (f-x new-obj) (f-y new-obj) (f-z new-obj)))))\n;;   (t t t 3 2 \"Z\"))\n\n;; (defun run-tests (backend)\n;;   (with-backend backend\n;;     (regression-5am:do-tests))\n;;   (when (probe-file *test-file*)\n;;     (ignore-errors (delete-file *test-file*))))\n\n(test strings-are-the-same-over-multiple-decodings\n  (clrhash *string-decoding-cache*)\n  (let ((str (copy-seq \"hello world\")))\n    (let ((str1 (copy-by-encoding str))\n          (str2 (copy-by-encoding str)))\n      (is (equal str1 str2))\n      (is (eq str1 str2))\n      (is (eq str1 (copy-by-encoding \"hello world\")))\n      (is (equal \"bar\" (copy-by-encoding \"bar\"))))))\n\n(test strings-are-not-the-same-once-were-above-the-max-size\n  (clrhash *string-decoding-cache*)\n  (let ((*max-string-decoding-cache-size* 3))\n    (let ((str (copy-seq \"hello world\")))\n      (let ((str1 (copy-by-encoding str)))\n        (copy-by-encoding \"one\")\n        (is (eql str1 (copy-by-encoding str)))\n        (copy-by-encoding \"two\")\n        (is (not (eql str1 (copy-by-encoding str))))))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/encoding.lisp",
    "content": ";;;; Reading and writing Lisp objects in a binary format.\n\n;;; Design:\n;;;\n;;;   - compact storage requirements\n;;;   - no arbitary limits (e.g. integers may be arbitarily large)\n;;;   - high read and write performance, thus no checking for cyclic data\n\n;;; For every supported data type, a character is defined as tag denoting\n;;; the type when reading.\n;;;\n;;; The functions ENCODE and DECODE encode and decode an arbitary object.\n;;; Upon write, ENCODE determines the data type from the lisp data type.\n;;; Upon read, DECODE determines the data type of the object by looking at\n;;; the tag character.\n;;;\n;;; If the data type is known upfron, the respective coder function can be\n;;; called directly.  ENCODE-INTEGER encodes an integer, for example.\n;;;\n;;; At certain file positions, only one datatype makes sense (i.e. when\n;;; writing a structure with a fixed layout).  In this case, the tag need\n;;; not be written.  For this purpose, a low-level function, i.e.\n;;; %ENCODE-INTEGER, exists to write the object without writing a tag,\n;;; and a matching decode function DECODE-INTEGER to read such untagged\n;;; data.\n\n;;; Format:\n\n;;;    Field    Format     Comment\n;;; ----------------------------------------------------------------\n;;; Integer\n;;;     tag     #\\i\n;;;     n       byte       Number of bytes that follow\n;;;     data    byte[n]    The actual data, a big endian number\n;;;\n;;; ----------------------------------------------------------------\n;;; Rational\n;;;     tag     #\\r\n;;;     n       byte       Number of bytes that follow\n;;;     data    byte[n]    The numerator, a big endian number\n;;;     n       byte       Number of bytes that follow\n;;;     data    byte[n]    The denominator, a big endian number\n;;;\n;;; ----------------------------------------------------------------\n;;; Reference to a STORE-OBJECT\n;;;     tag     #\\o\n;;;     ID      %integer   ID of the referenced object\n;;;\n;;; ----------------------------------------------------------------\n;;; List\n;;;     tag     #\\l\n;;;     n       %integer   Number of bytes that follow\n;;;     data    object[n]  Objects including tag\n;;;     tail    object     If n != 0: CDR of the last cons\n;;;\n;;; ----------------------------------------------------------------\n;;; Char\n;;;     tag     #\\c\n;;;     data    char       Character, written with WRITE-CHAR\n;;; ----------------------------------------------------------------\n;;; String\n;;;     tag     #\\s\n;;;     n       %integer   Number of bytes that follow\n;;;     data    char[n]    Characters, written with WRITE-CHAR\n;;; Note that the layout of strings will change to not use WRITE-CHAR\n;;;\n;;; ----------------------------------------------------------------\n;;; Symbol\n;;;     tag     #\\y\n;;;     package %string    Name of the home package of the symbol\n;;;     name    %string    Name of the symbol\n;;;\n;;; ----------------------------------------------------------------\n;;; Hash-Table\n;;;     tag     #\\#\n;;;     test    %symbol    hash-table-test-function\n;;;     r.-size %double    hash-table-rehash-size\n;;;     n       %integer   Number of value pairs that follow\n;;;     data    pair[n]    Value pairs in the following format\n;;;\n;;;   pair:\n;;;     key     object     Objekt with tag\n;;;     value   object     Objekt with tag\n;;;\n;;; ----------------------------------------------------------------\n;;; Single-Float\n;;;     tag     #\\f\n;;;     data    byte[4]    IEEE representation (big endian)\n;;;\n;;; ----------------------------------------------------------------\n;;; Double-Float\n;;;     tag     #\\d\n;;;     data    byte[8]    IEEE representation (big endian)\n;;;\n;;; ----------------------------------------------------------------\n;;; Array (saves all standard array attributes except for displacedness)\n;;;     tag     #\\a\n;;;     type    %symbol           ARRAY-ELEMENT-TYPE\n;;;     type-list %list\n;;;               present only if %symbol is 'new-list-type, if so it's\n;;;               the actualy ARRAY-ELEMENT-TYPE to use.\n;;;     flags   byte\n;;;               bit 0: array is a vector\n;;;               bit 1: array is adjustable\n;;;               bit 2: vector has a fill-pointer\n;;;               other bits reserved\n;;;\n;;;   if (flags has bit 0 set) {\n;;;     length  %integer          Length of fector\n;;;   } else {\n;;;     n       %integer          Number of dimensions following\n;;;     dims    %integer[n]       ARRAY-DIMENSIONS\n;;;   }\n;;;\n;;;   if (flags has bit 2 set) {\n;;;     fp      %integer          fill-pointer\n;;;   }\n;;;\n;;;     data    object[\\Pi dims]  Data in row-major-order\n;;;\n;;; ----------------------------------------------------------------\n\n(in-package :bknr.datastore)\n\n;;;; workaround\n\n(declaim (inline %read-char %write-char))\n(defun %read-char (stream)\n  (code-char (%decode-uint32 stream)))\n\n(defun %write-char (char stream)\n  (%encode-int32 (char-code char) stream))\n\n;;;; tags\n(declaim (inline %read-tag %write-tag))\n(defun %read-tag (stream &optional (eof-error-p t) eof-value)\n  (let ((b (read-byte stream eof-error-p -1)))\n    (if (= b -1)\n        eof-value\n        (code-char b))))\n\n(defun %write-tag (char stream)\n  (write-byte (char-code char) stream))\n\n;;;; binary encoding\n\n(defun %encode-int32 (object stream)\n  (write-byte (ldb (byte 8 24) object) stream)\n  (write-byte (ldb (byte 8 16) object) stream)\n  (write-byte (ldb (byte 8 08) object) stream)\n  (write-byte (ldb (byte 8 00) object) stream))\n\n(defun %encode-int16 (object stream)\n  (write-byte (ldb (byte 8 08) object) stream)\n  (write-byte (ldb (byte 8 00) object) stream))\n\n(defun %encode-integer (object stream)\n  (let ((n (ceiling (1+ (integer-length object)) 8)))\n    (write-byte n stream)\n    (loop\n       for i from (- (* n 8) 8) downto 0 by 8\n       do (write-byte (ldb (byte 8 i) object) stream))))\n\n(defun %encode-rational (object stream)\n  (%encode-integer (numerator object) stream)\n  (%encode-integer (denominator object) stream))\n\n(defun encode-integer (object stream)\n  (%write-tag #\\i stream)\n  (%encode-integer object stream))\n\n(defun encode-rational (object stream)\n  (%write-tag #\\r stream)\n  (%encode-rational object stream))\n\n(defun count-conses (list)\n  ;; Vorsicht, CMUCL LOOP hat einen Bug mit dotted lists.\n  ;; Daher nicht FOR-ON verwenden.\n  (loop for l = list then (cdr l)\n     while (consp l)\n     count 1))\n\n(defun %encode-list (object stream)\n  (let ((len (count-conses object)))\n    (%encode-integer len stream)\n    ;; Vorsicht, CMUCL LOOP hat einen Bug mit dotted lists.\n    ;; Daher nicht FOR-ON verwenden.\n    (when (> len 0)\n      (loop for l = object then (cdr l)\n         while (consp l)\n         do (encode (car l) stream)\n         finally (encode l stream)))))\n\n(defun encode-list (object stream)\n  (%write-tag #\\l stream)\n  (%encode-list object stream))\n\n(defun encode-char (object stream)\n  (%write-tag #\\c stream)\n  (%write-char object stream))\n\n(defun %encode-string (object stream)\n  (let ((byte-length (trivial-utf-8:utf-8-byte-length object)))\n    (%encode-integer byte-length stream)\n    (trivial-utf-8::write-utf-8-bytes object stream)))\n\n(defun encode-string (object stream)\n  (%write-tag #\\s stream)\n  (%encode-string object stream))\n\n(defun %encode-symbol (object stream)\n  (%encode-string (package-name (symbol-package object)) stream)\n  (%encode-string (symbol-name object) stream))\n\n(defun encode-symbol (object stream)\n  (%write-tag #\\y stream)\n  (%encode-symbol object stream))\n\n(defun encode-hash-table (object stream)\n  (%write-tag #\\# stream)\n  (%encode-symbol (hash-table-test object) stream)\n  (%encode-double-float (float (hash-table-rehash-size object) 1.0d0) stream)\n  (%encode-integer (hash-table-count object) stream)\n  (maphash (lambda (k v)\n             (encode k stream)\n             (encode v stream))\n           object))\n\n(defun %encode-single-float (object stream)\n  #+allegro\n  (map nil #'(lambda (short)\n               (%encode-int16 short stream))\n       (multiple-value-list (excl::single-float-to-shorts object)))\n  #+cmu\n  (%encode-int32 (kernel:single-float-bits object) stream)\n  #+openmcl\n  (%encode-int32 (ccl::single-float-bits object) stream)\n  #+sbcl\n  (%encode-int32 (sb-kernel:single-float-bits object) stream)\n  #+lispworks\n  (%encode-int32 (float-features:single-float-bits object) stream))\n\n(defun encode-single-float (object stream)\n  (%write-tag #\\f stream)\n  (%encode-single-float object stream))\n\n(defun %encode-double-float (object stream)\n  #+cmucl\n  (map nil #'(lambda (short)\n               (%encode-int16 short stream))\n       (multiple-value-list (excl::double-float-to-shorts object)))\n  #+cmu\n  (progn (%encode-int32 (kernel:double-float-high-bits object) stream)\n         (%encode-int32 (kernel:double-float-low-bits object) stream))\n  #+openmcl\n  (multiple-value-bind (hi lo) (ccl::double-float-bits object)\n    (%encode-int32 hi stream)\n    (%encode-int32 lo stream))\n  #+sbcl\n  (progn (%encode-int32 (sb-kernel:double-float-high-bits object) stream)\n         (%encode-int32 (sb-kernel:double-float-low-bits object) stream))\n  #+lispworks\n  (let* ((int (float-features:double-float-bits object)))\n    (%encode-int32 (ldb (byte 32 32) int) stream)\n    (%encode-int32 (ldb (byte 32 0) int) stream)))\n\n(defun encode-double-float (object stream)\n  (%write-tag #\\d stream)\n  (%encode-double-float object stream))\n\n(defun %encode-array (object stream)\n  (let ((type (array-element-type object)))\n   (cond\n     ((symbolp type)\n      (%encode-symbol type stream))\n     (type\n      ;; backward compatiblity for types that are not symbols\n      (%encode-symbol 'new-list-type stream)\n      (%encode-list type stream))))\n  (let* ((vectorp (typep object 'vector))\n         (fill-pointer-p (array-has-fill-pointer-p object))\n         (flags (logior (if vectorp 1 0)\n                        (if (adjustable-array-p object) 2 0)\n                        (if fill-pointer-p 4 0)))\n         (dims (array-dimensions object)))\n    (write-byte flags stream)\n    (cond\n      (vectorp\n       (%encode-integer (car dims) stream))\n      (t\n       (%encode-integer (length dims) stream)\n       (dolist (d dims)\n         (%encode-integer d stream))))\n    (when fill-pointer-p\n      (%encode-integer (fill-pointer object) stream))\n    (dotimes (i (reduce #'* dims))\n      (encode (row-major-aref object i) stream))))\n\n(defun encode-array (object stream)\n  (%write-tag #\\a stream)\n  (%encode-array object stream))\n\n(defun encode (object stream)\n  (typecase object\n    (integer (encode-integer object stream))\n    (rational (encode-rational object stream))\n    (symbol (encode-symbol object stream))\n    (character (encode-char object stream))\n    (string (encode-string object stream))\n    (list (encode-list object stream))\n    (array (encode-array object stream))\n    (hash-table (encode-hash-table object stream))\n    (single-float (encode-single-float object stream))\n    (double-float (encode-double-float object stream))\n    (t (encode-object object stream))))\n\n(defgeneric encode-object (object stream))\n\n;;;; decoding\n\n(defun %decode-integer/fixed (stream n)\n  (let* ((initial (read-byte stream))\n         (result (if (logbitp 7 initial) -1 0)))\n    (setf result (logior (ash result 8) initial))\n    (dotimes (x (1- n))\n      (setf result (logior (ash result 8) (read-byte stream))))\n    result))\n\n(defun %decode-uint16 (stream)\n  (logior (ash (read-byte stream) 08)\n          (read-byte stream)))\n\n(defun %decode-sint32 (stream)\n  (%decode-integer/fixed stream 4))\n\n(defun %decode-uint32 (stream)\n  (logior (ash (read-byte stream) 24)\n          (ash (read-byte stream) 16)\n          (ash (read-byte stream) 08)\n          (read-byte stream)))\n\n(defun %decode-integer (stream)\n  (let ((n (read-byte stream)))\n    (assert (plusp n))                  ;n==0 geben wir nicht aus\n    (%decode-integer/fixed stream n)))\n\n(defun %decode-rational (stream)\n  (/ (%decode-integer stream)\n     (%decode-integer stream)))\n\n(defun %decode-char (stream)\n  (%read-char stream))\n\n(defvar *max-string-decoding-cache-size* 100000000)\n\n(defvar *string-decoding-cache* (make-hash-table :test #'equalp))\n\n(defun octets-to-string (octets)\n  (labels ((octets-to-string-safe (octets) ; safe and portable\n              (let ((flexi-streams:*substitution-char* #\\?))\n                (handler-case\n                    (flexi-streams:octets-to-string octets :external-format :utf-8)\n                  (flexi-streams:external-format-condition (e)\n                    (declare (ignore e))\n                    (let ((string (flexi-streams:octets-to-string octets :external-format :ascii)))\n                      (warn \"could not decode string ~S as utf-8, decoded as ASCII\" string)\n                      string))))))\n    (handler-case\n        (trivial-utf-8:utf-8-bytes-to-string octets)\n      (trivial-utf-8:utf-8-decoding-error ()\n        (octets-to-string-safe octets)))))\n\n(defun octets-to-string-deduped (octets)\n  (when (>= (hash-table-count *string-decoding-cache*) *max-string-decoding-cache-size*)\n    (clrhash *string-decoding-cache*))\n\n  (let ((existing (gethash octets *string-decoding-cache*)))\n    (or\n     existing\n     (setf (gethash octets *string-decoding-cache*)\n           (octets-to-string octets)))))\n\n(defun %decode-string (stream)\n  (let* ((n (%decode-integer stream))\n         (buffer (make-array n :element-type '(unsigned-byte 8))))\n    (assert (= n (read-sequence buffer stream)))\n    (octets-to-string-deduped buffer)))\n\n(defun find-symbol-in-all-packages (name)\n  (let (symbols)\n    (do-all-symbols (symbol symbols)\n      (when (string-equal symbol name)\n        (pushnew symbol symbols)))))\n\n(defun find-symbol-interactively (package-name symbol-name usage)\n  (let ((intern (or (string-equal package-name \"KEYWORD\")\n                    (null usage))))\n    (restart-case\n        (multiple-value-bind (symbol status)\n            (funcall (if intern\n                         #'intern\n                         #'find-symbol)\n                     symbol-name\n                     (or (find-package package-name)\n                         (error \"package ~A for symbol ~A~@[ naming ~A~] not found\" package-name symbol-name usage)))\n          (if (or intern status)\n              symbol\n              (error \"symbol ~A~@[ naming ~A~] not found in package ~A\" symbol-name usage package-name)))\n      (use-other-symbol (new-symbol)\n        :interactive (lambda ()\n                       (format t \"Enter symbol~@[ (homonyms: ~{~S~^, ~})~]: \" (find-symbol-in-all-packages symbol-name))\n                       (let ((new-symbol (ignore-errors (read))))\n                         (list new-symbol)))\n        :report (lambda (stream) (format stream \"Use another symbol~@[, homonyms: ~S~]\" (find-symbol-in-all-packages symbol-name)))\n        new-symbol)\n      (read-as-nil ()\n        :report \"Read symbol as NIL\"\n        nil))))\n\n(defun %decode-symbol (stream &key (intern t) usage)\n  (let ((package-name (%decode-string stream))\n        (symbol-name (%decode-string stream)))\n    (when intern\n      (find-symbol-interactively package-name symbol-name usage))))\n\n(defun %decode-list (stream)\n  (let* ((n (%decode-integer stream))\n         (result (loop repeat n collect (decode stream)))\n         (tail (and (plusp n) (decode stream))))\n    (when tail\n      (setf (cdr (last result)) tail))\n    result))\n\n(defun %decode-hash-table (stream)\n  (let* ((test (%decode-symbol stream :usage \"hash table test\"))\n         (rehash-size (%decode-double-float stream))\n         (n (%decode-integer stream))\n         (result (make-hash-table :test test :size n :rehash-size rehash-size)))\n    (dotimes (x n)\n      (let ((key (decode stream))\n            (value (decode stream)))\n        (setf (gethash key result) value)))\n    result))\n\n(defun %decode-single-float (stream)\n  #+allegro\n  (excl::shorts-to-single-float (%decode-uint16 stream)\n                                (%decode-uint16 stream))\n  #+cmu\n  (kernel:make-single-float (%decode-sint32 stream))\n  #+openmcl\n  (make-single-float (%decode-sint32 stream))\n  #+sbcl\n  (sb-kernel:make-single-float (%decode-sint32 stream))\n  #+lispworks\n  (float-features:bits-single-float (%decode-sint32 stream)))\n\n(defun %decode-double-float (stream)\n  #+allegro\n  (excl::shorts-to-double-float (%decode-uint16 stream)\n                                (%decode-uint16 stream)\n                                (%decode-uint16 stream)\n                                (%decode-uint16 stream))\n  #+cmu\n  (kernel:make-double-float (%decode-sint32 stream)\n                            (%decode-uint32 stream))\n  #+openmcl\n  (make-double-float (%decode-sint32 stream)\n                     (%decode-uint32 stream))\n  #+sbcl\n  (sb-kernel:make-double-float (%decode-sint32 stream)\n                               (%decode-uint32 stream))\n  #+lispworks\n  (let ((hi (%decode-uint32 stream))\n        (lo (%decode-uint32 stream)))\n    (float-features:bits-double-float (logior (ash hi 32) lo))))\n\n(defun %decode-array (stream)\n  (let* ((element-type\n           (let ((type (%decode-symbol stream :usage \"array element type\")))\n             (cond\n               ((eql 'new-list-type type)\n                (%decode-list stream))\n               (t type))))\n         (flags (read-byte stream))\n         (vectorp (logbitp 0 flags))\n         (adjustablep (logbitp 1 flags))\n         (fill-pointer-p (logbitp 2 flags))\n         (dimensions\n          (if vectorp\n              (list (%decode-integer stream))\n              (loop repeat (%decode-integer stream)\n                 collect (%decode-integer stream))))\n         (fill-pointer\n          (if fill-pointer-p\n              (%decode-integer stream)\n              nil))\n         (result (make-array dimensions\n                             :element-type element-type\n                             :adjustable adjustablep\n                             :fill-pointer fill-pointer)))\n    (dotimes (i (reduce #'* dimensions))\n      (setf (row-major-aref result i) (decode stream)))\n    result))\n\n(defun decode (stream)\n  (let ((tag (%read-tag stream)))\n    (case tag\n      (#\\a (%decode-array stream))\n      (#\\i (%decode-integer stream))\n      (#\\y (%decode-symbol stream))\n      (#\\c (%decode-char stream))\n      (#\\s (%decode-string stream))\n      (#\\l (%decode-list stream))\n      (#\\# (%decode-hash-table stream))\n      (#\\f (%decode-single-float stream))\n      (#\\d (%decode-double-float stream))\n      (#\\r (%decode-rational stream))\n      (t (decode-object tag stream)))))\n\n(defgeneric decode-object (tag stream))\n\n;;;; OpenMCL does not have these functions\n(defun make-single-float (bits)\n  (cond\n    ;; IEEE float special cases\n    ((zerop bits) 0.0)\n    ((= bits #x-80000000) -0.0)\n    (t (let* ((sign (ecase (ldb (byte 1 31) bits)\n                      (0  1.0)\n                      (1 -1.0)))\n              (iexpt (ldb (byte 8 23) bits))\n              (expt (if (zerop iexpt)   ; denormalized\n                        -126\n                        (- iexpt 127)))\n              (mant (* (logior (ldb (byte 23 0) bits)\n                               (if (zerop iexpt)\n                                   0\n                                   (ash 1 23)))\n                       (expt 0.5 23))))\n         (* sign (expt 2.0 expt) mant)))))\n\n#+openmcl\n(defun make-double-float (hi lo)\n  (cond\n    ;; IEEE float special cases\n    ((and (zerop hi) (zerop lo)) 0.0d0)\n    ((and (= hi #x-80000000) (zerop lo)) -0.0d0)\n    (t (let* ((bits (logior (ash hi 32) lo))\n              (sign (ecase (ldb (byte 1 63) bits)\n                      (0  1.0d0)\n                      (1 -1.0d0)))\n              (iexpt (ldb (byte 11 52) bits))\n              (expt (if (zerop iexpt)   ; denormalized\n                        -1022\n                        (- iexpt 1023)))\n              (mant (* (logior (ldb (byte 52 0) bits)\n                               (if (zerop iexpt)\n                                   0\n                                   (ash 1 52)))\n                       (expt 0.5d0 52))))\n         (* sign (expt 2.0d0 expt) mant)))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/json.lisp",
    "content": "(in-package :bknr.datastore)\n\n(defparameter *json-ignore-slots* '(bknr.datastore::id bknr.indices::destroyed-p))\n\n(defmacro with-json-ignore-slots ((&rest slots) &body body)\n  `(let ((*json-ignore-slots* (append *json-ignore-slots* ,slots)))\n     ,@body))\n\n(defmethod yason:encode ((object store-object) &optional (stream *standard-output*))\n  (yason:with-output (stream)\n    (yason:with-object ()\n      (dolist (slotdef (closer-mop:class-slots (class-of object)))\n        (when (and (slot-boundp object (closer-mop:slot-definition-name slotdef))\n                   (not (find (closer-mop:slot-definition-name slotdef) *json-ignore-slots*)))\n          (yason:encode-object-element (string-downcase (closer-mop:slot-definition-name slotdef))\n                                      (slot-value object (closer-mop:slot-definition-name slotdef))))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/object-tests.lisp",
    "content": "(defpackage :bknr.datastore.tests\n  (:use #:cl #:bknr.datastore #:bknr.indices\n        #:fiveam\n        #:fiveam-matchers)\n  (:import-from #:bknr.datastore\n                #:*crash-output-stream*\n                #:persistent-effective-slot-definition\n                #:%id-cache\n                #:*in-restore-p*\n                #:next-object-id\n                #:store-object-subsystem\n                #:with-store-state\n                #:encode\n                #:%encode-string\n                #:%encode-integer\n                #:encode-slots-for-object\n                #:make-object-snapshot\n                #:class-layout-slots\n                #:class-layout\n                #:encode-create-object\n                #:%log-crash)\n  (:import-from #:bknr.indices\n                #:base-indexed-object)\n  (:import-from #:fiveam-matchers\n                #:error-with-string-matching\n                #:signals-error-matching)\n  (:import-from #:fiveam-matchers/strings\n                #:matches-regex)\n  (:import-from #:fiveam-matchers/described-as\n                #:described-as))\n(in-package :bknr.datastore.tests)\n\n(def-suite* :bknr.datastore.tests)\n\n(defun delete-directory (pathname)\n  (when (probe-file pathname)\n    (fad:delete-directory-and-files pathname)))\n\n(defvar *test-datastore* nil)\n\n(defmacro test-equal (&rest args)\n  `(is (equal ,@args)))\n\n(defmacro test-assert (&rest args)\n  `(is-true ,@args))\n\n(defmacro test-condition (expr (%quote condition))\n  (declare (ignore %quote))\n  `(signals ,condition\n     ,expr))\n\n(defun make-test-store-directory ()\n  ;; bknr.utils:make-temporary-pathname does not really return a new\n  ;; directory pathname that is guaranteed to be distinct.  Chances\n  ;; that it returns the name of an existing directory are so slim\n  ;; that we accept the risk.\n  (ensure-directories-exist (format nil \"~A/\" (bknr.utils:make-temporary-pathname :defaults \"/tmp/\" :name \"store-test\"))))\n\n(def-fixture datastore-test-class ()\n  (let ((directory (make-test-store-directory))\n        error)\n    (make-instance 'mp-store :directory directory)\n    (unwind-protect\n         (handler-bind ((error (lambda (e)\n                                 (declare (ignore e))\n                                 (setf error t))))\n           (&body))\n      (close-store)\n      (if error\n          (format t \";; store directory ~A not deleted~%\" directory)\n          (delete-directory directory)))))\n\n(defvar *tests* (make-hash-table))\n\n(defun do-run-test (thunk)\n  \"Run the test in THUNK, then verify that the store contains the\n`same' objects after a restore and after snapshot and a restore.\"\n  (let ((bknr.datastore::*store-verbose* nil) initial-objects)\n    (funcall thunk)\n    (let ((next-object-id (bknr.datastore::next-object-id (bknr.datastore::store-object-subsystem))))\n      (setf initial-objects (object-classes-and-ids))\n      (restore)\n      (test-equal initial-objects (object-classes-and-ids))\n      (test-equal next-object-id (bknr.datastore::next-object-id (bknr.datastore::store-object-subsystem)))\n      (snapshot)\n      (restore)\n      (test-equal initial-objects (object-classes-and-ids))\n      (test-equal next-object-id (bknr.datastore::next-object-id (bknr.datastore::store-object-subsystem))))))\n\n(defmacro defdstest (name args &body body)\n  (when args\n    (error \"unexpected arguments ~A to defdstest ~A\" args name))\n  `(test ,name\n     (with-fixture datastore-test-class ()\n       ,@body)))\n\n\n(defdstest store-setup ()\n  (test-assert *store*))\n\n(defdstest create-object ()\n  (let ((obj (make-instance 'store-object)))\n    (test-assert obj)\n    (test-equal (list obj) (all-store-objects))))\n\n(defdstest read-object-id ()\n  (let ((obj (make-instance 'store-object)))\n    (is (eql 0 (store-object-id obj))))\n  (let ((obj (make-instance 'store-object)))\n    (is (eql 1 (store-object-id obj)))))\n\n(defdstest find-by-object-id ()\n  (let ((obj1 (make-instance 'store-object))\n        (obj2 (make-instance 'store-object)))\n    (is (eql obj2 (store-object-with-id 1)))\n    (is (eql obj1 (store-object-with-id 0)))))\n\n(defdstest create-multiple-objects ()\n  (let ((o1 (make-instance 'store-object))\n        (o2 (make-instance 'store-object)))\n    (test-assert o1)\n    (test-assert o2)\n    (test-equal (length (all-store-objects)) 2)\n    (test-assert (subsetp (list o1 o2) (all-store-objects)))))\n\n(defdstest delete-multiple-objects ()\n  (let ((o1 (make-instance 'store-object))\n        (o2 (make-instance 'store-object)))\n    (test-assert o1)\n    (test-assert o2)\n    (test-equal (length (all-store-objects)) 2)\n    (test-assert (subsetp (list o1 o2) (all-store-objects)))\n    (delete-object o1)\n    (test-equal (all-store-objects) (list o2))\n    (delete-object o2)\n    (test-equal (all-store-objects) nil)))\n\n(defdstest restore ()\n  (let ((object-id (store-object-id (make-instance 'store-object))))\n    (restore)\n    (test-equal 1 (length (all-store-objects)))\n    (test-equal object-id (store-object-id (first (all-store-objects))))))\n\n(defdstest snapshot-and-restore ()\n  (let ((object-id (store-object-id (make-instance 'store-object))))\n    (snapshot)\n    (restore)\n    (test-equal 1 (length (all-store-objects)))\n    (test-equal object-id (store-object-id (first (all-store-objects))))))\n\n(defdstest restore-multiple-objects ()\n  (dotimes (i 10)\n    (make-instance 'store-object))\n  (restore)\n  (test-equal 10 (length (all-store-objects))))\n\n(defdstest snapshot-restore-multiple-objects ()\n  (dotimes (i 10)\n    (make-instance 'store-object))\n  (snapshot)\n  (restore)\n  (test-equal (length (all-store-objects)) 10))\n\n(defconstant +stress-size+ 10000)\n\n(defdstest stress-test ()\n  (format t \"Creating ~A objects in two threads~%\" +stress-size+)\n  (time (bknr.datastore::without-sync ()\n          (labels ((stress ()\n                     (dotimes (i +stress-size+)\n                       (make-instance 'store-object))))\n            (let ((threads (list (bt:make-thread #'stress)\n                                 (bt:make-thread #'stress))))\n              (loop while (some #'bt:thread-alive-p threads)\n                 do (sleep 1))))))\n  (test-equal (length (all-store-objects)) (* 2 +stress-size+)))\n\n(defdstest stress-test-2 ()\n  (bknr.datastore::without-sync ()\n    (format t \"Creating ~A objects~%\" +stress-size+)\n    (time (dotimes (i +stress-size+)\n            (make-instance 'store-object)))\n    (format t \"Deleting ~A objects~%\" (length (all-store-objects)))\n    (time (map-store-objects #'delete-object))\n    (test-equal (all-store-objects) nil)))\n\n(defdstest holes-test ()\n  (dotimes (i +stress-size+)\n    (let ((delete (zerop (random 2))))\n      (with-transaction (:foo)\n        (funcall (if delete #'delete-object #'identity)\n                 (make-instance 'store-object))))))\n\n(defdstest make-instance-in-anon-txn ()\n  (with-transaction ()\n    (make-instance 'store-object))\n  (restore)\n  (test-equal 1 (length (class-instances 'store-object))))\n\n(defdstest make-instance-in-anon-txn-1 ()\n  (with-transaction ()\n    (test-assert (make-instance 'store-object))))\n\n\n(define-persistent-class parent ()\n  ((child :update :initform nil :initarg nil)))\n\n(define-persistent-class child ()\n                         ())\n\n(defclass child-with-index (store-object)\n  ((foo :initarg :foo\n        :index-type unique-index\n        :accessor %foo\n        :index-reader child-by-foo))\n  (:metaclass persistent-class))\n\n(defun object-classes-and-ids ()\n  \"Return a list of conses with the car being a class name and the cdr\n  being the object id for all persistent objects in the store\"\n  (sort (mapcar (lambda (object)\n                  (cons (class-name (class-of object))\n                        (store-object-id object)))\n                (all-store-objects))\n        #'< :key #'cdr))\n\n(defdstest delete-indexed-object ()\n  (let ((obj (make-instance 'child-with-index\n                            :foo :bar)))\n    (is (eql obj (child-by-foo :bar)))\n    (delete-object obj)\n    (is (eql nil (child-by-foo :bar)))))\n\n(defdstest set-indexed-slot-in-object ()\n  (let ((obj (make-instance 'child-with-index\n                            :foo :bar)))\n    (is (eql obj (child-by-foo :bar)))\n    (setf (%foo obj) :car)\n    (is (eql nil (child-by-foo :bar)))\n    (is (eql obj (child-by-foo :car)))))\n\n(defdstest make-referenced-object-in-anon-tx ()\n  (with-transaction (:make)\n    (make-instance 'parent :child (make-instance 'child))))\n\n\n(defdstest circular-in-anon-txn-without-serialization ()\n  (let ((parent (make-instance 'parent)))\n    (with-transaction (:circular)\n      (setf (parent-child parent) (make-instance 'child))))\n  (test-equal (find-class 'child)\n              (class-of (parent-child (first (class-instances 'parent))))))\n\n(defdstest serialize-circular-in-anon-txn ()\n  (let ((parent (make-instance 'parent)))\n    (with-transaction (:circular)\n      (setf (parent-child parent) (make-instance 'child))))\n  (restore)\n  (test-equal (find-class 'child)\n              (class-of (parent-child (first (class-instances 'parent))))))\n\n(defdstest serialize-self-circular-in-anon-txn ()\n  (let ((object (make-instance 'parent)))\n    (with-transaction (:circular)\n      (setf (parent-child object) object)))\n  (restore)\n  (let ((object (first (class-instances 'store-object))))\n    (test-assert object)\n    (test-equal object (parent-child object)))\n  (snapshot)\n  (restore)\n  (let ((object (first (class-instances 'store-object))))\n    (test-assert object)\n    (test-equal object (parent-child object))))\n\n(defdstest delete-object-in-anon-txn ()\n  (let (object)\n    (with-transaction (:make)\n      (setf object (make-instance 'child)))\n    (with-transaction (:delete)\n      (delete-object object))\n    (restore)\n    (test-assert (object-destroyed-p object))))\n\n(defdstest delete-object-and-check-object-id-of-next-1 ()\n  (let (object-id)\n    (with-transaction (:make)\n      (let ((object (make-instance 'store-object)))\n        (setf object-id (store-object-id object))\n        (delete-object object)))\n    (restore)\n    (test-assert (< object-id (store-object-id (make-instance 'store-object))))))\n\n(defdstest delete-object-and-check-object-id-of-next-2 ()\n  (let (object-id)\n    (with-transaction (:make)\n      (let ((object (make-instance 'store-object)))\n        (setf object-id (store-object-id object))))\n    (snapshot)\n    (restore)\n    (test-assert (< object-id (store-object-id (make-instance 'store-object))))))\n\n(defdstest delete-object-and-check-object-id-of-next-3 ()\n  (let (object-id)\n    (with-transaction (:make)\n      (let ((object (make-instance 'store-object)))\n        (setf object-id (store-object-id object))\n        (delete-object object)))\n    (snapshot)\n    (restore)\n    (test-assert (< object-id (store-object-id (make-instance 'store-object))))))\n\n(define-persistent-class class-with-transient-slot ()\n  ((slot :update\n         :transient t\n         :initform 0)))\n\n(defdstest test-transient-slots ()\n  (let ((object-id (store-object-id (make-instance 'class-with-transient-slot))))\n    (restore)\n    (test-equal 0 (class-with-transient-slot-slot (find-store-object object-id)))\n    (setf (class-with-transient-slot-slot (find-store-object object-id)) 1)\n    (restore)\n    (test-equal 0 (class-with-transient-slot-slot (find-store-object object-id)))\n    (snapshot)\n    (restore)\n    (test-equal 0 (class-with-transient-slot-slot (find-store-object object-id)))))\n\n(define-persistent-class persistent-mixin ()\n  ((mixin-slot :update\n               :initform 2)))\n\n(define-persistent-class inherit-multiple (persistent-mixin parent)\n  ())\n\n(defdstest multiple-inheritance-test ()\n  (let* ((o1 (make-instance 'inherit-multiple :child (make-instance 'child)))\n         (o2 (make-instance 'inherit-multiple :child o1)))\n    (test-equal o1 (parent-child o2))))\n\n(defdstest abort-anonymous-transaction ()\n  (let ((parent (make-instance 'parent :child nil)))\n    (ignore-errors\n      (with-transaction (:abort)\n        (setf (parent-child parent) (make-instance 'child))\n        (error \"abort\")))\n    ;; The old behavior was to to abort the whole transaction, the new\n    ;; one will still play out any intermediate transactions.\n    (is-true (parent-child parent))\n    (assert-that (class-instances 'child)\n                 (has-length 1))))\n\n(defdstest ensure-setf-slot-value-returns-newval ()\n  (let ((parent (make-instance 'parent :child nil)))\n    (let ((child (setf (parent-child parent) (make-instance 'child))))\n      (is (eql child (car (class-instances 'child)))))))\n\n#+nil\n(defdstest abort-anonymous-transaction-for-indices ()\n  (let ((parent (make-instance 'parent :child nil)))\n    (ignore-errors\n      (with-transaction (:abort)\n        (setf (parent-child parent) (make-instance 'child-with-index :foo 2))\n        (error \"abort\")))\n    (test-equal nil (parent-child parent))\n    (test-equal nil (class-instances 'child-with-index))\n    ;; This fails: Even though we've rolled back the creation of\n    ;; object, we haven't rolled back the indices to their previous\n    ;; state. -- Arnold\n    #+nil\n    (test-equal nil (child-by-foo 2))))\n\n(bknr.datastore:deftransaction tx-set-child (parent child)\n  (setf (parent-child parent) child))\n\n(defdstest set-slot-from-within-txn ()\n  (let ((parent (make-instance 'parent))\n        (child (make-instance 'child)))\n    (tx-set-child parent child)\n    (is (eql child (parent-child parent)))\n    (restore)\n    (is (eql child (parent-child parent)))))\n\n\n(defclass object-with-init (store-object)\n  ((arg :initarg :arg\n        :reader arg))\n  (:metaclass persistent-class)\n  (:default-initargs :arg (+ 1 1)))\n\n(deftransaction tx-make-object ()\n  (make-instance 'object-with-init))\n\n(deftransaction tx-make-object-with-arg (arg)\n  (make-instance 'object-with-init :arg arg))\n\n(defdstest default-initargs-is-parsed ()\n  (let ((obj (make-instance 'object-with-init)))\n    (is (eql 2 (arg obj))))\n  (let ((obj (tx-make-object)))\n    (is (eql 2 (arg obj))))\n  (assert-that (mapcar #'arg (class-instances 'object-with-init))\n               (contains 2 2))\n  (restore)\n  (assert-that (mapcar #'arg (class-instances 'object-with-init))\n               (contains 2 2)))\n\n(defdstest default-initargs-doesnt-ignore-args ()\n  (let ((obj (make-instance 'object-with-init :arg 5)))\n    (is (eql 5 (arg obj))))\n  (let ((obj (tx-make-object-with-arg 5)))\n    (is (eql 5 (arg obj))))\n  (assert-that (mapcar #'arg (class-instances 'object-with-init))\n               (contains 5 5))\n  (restore)\n  (assert-that (mapcar #'arg (class-instances 'object-with-init))\n               (contains 5 5)))\n\n(defclass object-with-ensure-init (store-object)\n  ((arg :initarg :arg\n        :reader arg))\n  (:metaclass persistent-class)\n  (:default-initargs :arg (error \"must specify :arg\")))\n\n(deftransaction tx-make-object-with-ensure-init (arg)\n  (make-instance 'object-with-ensure-init :arg arg))\n\n(defdstest default-initargs-with-overrident-must-behave ()\n  (let ((obj (make-instance 'object-with-ensure-init :arg 5)))\n    (is (eql 5 (arg obj))))\n  (let ((obj (tx-make-object-with-ensure-init 5)))\n    (is (eql 5 (arg obj))))\n\n  (assert-that (mapcar #'arg (class-instances 'object-with-ensure-init))\n               (contains 5 5))\n  (restore)\n  (assert-that (mapcar #'arg (class-instances 'object-with-ensure-init))\n               (contains 5 5))\n  (signals error\n    (make-instance 'object-with-ensure-init))\n  (assert-that (mapcar #'arg (class-instances 'object-with-ensure-init))\n               (contains 5 5)))\n\n(define-condition fake-error (error)\n  ())\n\n(defdstest %log-crash-happy-path ()\n  (let ((*crash-output-stream* (make-string-output-stream)))\n    (signals fake-error ;; as opposed to any errors from log-crash\n      (handler-bind ((error #'%log-crash))\n        (error 'fake-error)))))\n\n\n(defdstest encode-create-object-updates-the-class-layouts ()\n  (let ((class-layouts (make-hash-table)))\n    (let ((stream (flex:make-in-memory-output-stream)))\n      (encode-create-object class-layouts\n                            ;; the class-name doesn't matter here\n                            (make-instance 'object-with-init)\n                            stream)\n      (assert-that\n       (alexandria:hash-table-keys class-layouts)\n       (contains (find-class 'object-with-init)))\n      (let ((layouts (alexandria:hash-table-values class-layouts)))\n        (assert-that\n         layouts\n         (contains\n          (has-typep 'class-layout)))\n        (assert-that\n         (class-layout-slots (first layouts))\n         (contains 'arg 'bknr.datastore::last-change))))))\n\n(defclass object-with-relaxed-slot (store-object)\n  ((reference :initarg :reference\n              :relaxed-object-reference t)\n   (other-reference :initarg :other-reference))\n  (:metaclass persistent-class))\n\n(defdstest can-save-when-reference-is-killed ()\n  (let ((inner (make-instance 'object-with-init)))\n    (make-instance 'object-with-relaxed-slot\n                   :reference inner)\n    (delete-object inner)\n    (finishes\n      (snapshot))))\n\n(defdstest but-cannot-save-when-other-reference-is-killed ()\n  (let ((inner (make-instance 'object-with-init)))\n    (make-instance 'object-with-relaxed-slot\n                   :other-reference inner)\n    (delete-object inner)\n    (signals error\n      (snapshot))))\n\n(defclass async-object-with-slot (store-object)\n  ((slot1 :initarg :slot1)\n   (slot2 :initarg :slot2))\n  (:metaclass persistent-class))\n\n(defclass async-object-snapshot ()\n  ((original :initarg :original)))\n\n(defmethod make-object-snapshot ((self async-object-with-slot))\n  (make-instance 'async-object-snapshot :original self))\n\n(defmethod encode-slots-for-object (class-layout (self async-object-snapshot)\n                                    stream)\n  (assert (equal '(bknr.datastore::last-change slot1 slot2)\n                 (class-layout-slots class-layout)))\n  (encode 20 stream)\n  (encode \"bar\" stream)\n  (encode 45 stream))\n\n(defdstest asynchronously-save-object ()\n  (make-instance 'async-object-with-slot\n                 :slot1 \"foo\"\n                 :slot2 22)\n  (snapshot)\n  (restore)\n  (let ((result (first (bknr.datastore:class-instances 'async-object-with-slot))))\n    (is (equal \"bar\" (slot-value result 'slot1)))\n    (is (equal 45 (slot-value result 'slot2)))))\n\n\n(defdstest cant-finalize-class-without-store-object ()\n  (signals bknr.datastore::must-inherit-store-object\n    (eval\n     `(defclass foo-dfwersfsdfds (base-indexed-object)\n        ()\n        (:metaclass persistent-class)))))\n\n;; This test causes SBCL to crash and burn\n#+nil\n(defdstest cant-finalize-class-without-any-base-class-at-all ()\n  (sleep 2)\n  (signals bknr.datastore::must-inherit-store-object\n    (eval\n     `(defclass foo-dfwersfsdfdsdfdf ()\n        ()\n        (:metaclass persistent-class)))))\n\n\n(defdstest make-instance-fails-while-restore-is-in-progress-on-another-thread ()\n  (with-store-state (:restore)\n    (let ((*in-restore-p* nil))\n      (is (equal 0 (next-object-id (store-object-subsystem))))\n      (signals-error-matching ()\n         (make-instance 'object-with-init)\n         (error-with-string-matching\n          (matches-regex \".*Restore is in progress.*\")))\n      (is (equal 0 (next-object-id (store-object-subsystem)))))))\n\n(defdstest set-slot-value-fails-while-restore-is-in-progress-on-another-thread ()\n  (with-store-state (:restore)\n    (is (equal 0 (next-object-id (store-object-subsystem))))\n    (let ((obj (let ((*in-restore-p* t)) (make-instance 'object-with-init))))\n      (finishes\n        (let ((*in-restore-p* t))\n          (setf (slot-value obj 'arg) 2)))\n\n      (let ((*in-restore-p* nil))\n        (signals-error-matching ()\n          (setf (slot-value obj 'arg) 3)\n          (error-with-string-matching\n           (matches-regex \".*Restore is in progress.*\"))))\n      (is (eql 2 (slot-value obj 'arg))))\n    (is (equal 1 (next-object-id (store-object-subsystem))))))\n\n(defdstest can-access-id-cache-without-issues ()\n  (let ((obj (make-instance 'parent)))\n    (setf (%id-cache obj) 22)\n    (is (eql 22 (%id-cache obj)))\n\n    (setf (%id-cache obj) 0)\n    (delete-object obj)\n    (is (eql 0 (%id-cache obj)))\n    (setf (%id-cache obj) 23)\n    (is (eql 23 (%id-cache obj)))))\n\n(defdstest check-id-cache-slot-type ()\n  (let* ((obj (make-instance 'parent))\n         (old-id (store-object-id obj)))\n    (setf (%id-cache obj) 22)\n    (is (eql 22 (%id-cache obj)))\n\n    (let ((slot (loop for slot in                       \n                               (closer-mop:class-slots (find-class 'parent))\n                      if (eql '%id-cache (closer-mop:slot-definition-name slot))\n                        return slot)))\n      (assert-that (type-of slot)\n                   (described-as \"ha! it's not standard-slot-definition, deal with it.\"\n                     ;; ^ but Lispworks still seems to optimize the accessors\n                     (is-equal-to 'persistent-effective-slot-definition))))\n\n    ;; Fix the id again so that we can safely delete it\n    (setf (%id-cache obj) old-id)\n    (is (eql old-id (%id-cache obj)))    \n    (delete-object obj)\n\n    (is (eql old-id (%id-cache obj)))\n    (is (eql old-id (slot-value obj '%id-cache)))\n    (setf (%id-cache obj) 23)\n    (is (eql 23 (%id-cache obj)))\n    (setf (slot-value obj '%id-cache) 34)\n    (is (eql 34 (%id-cache obj)))))\n\n\n(defdstest snapshotting-deleted-objects ()\n  (let ((*crash-output-stream* (make-string-output-stream)))    \n   (let* ((one (make-instance 'parent))\n          (two (make-instance 'parent :child one)))\n     (finishes\n       (snapshot))\n     (delete-object one)\n     (signals error\n       (snapshot)))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/object.lisp",
    "content": ";;; MOP based object subsystem for the BKNR datastore\n\n;; Internal slots should have a different slot descriptor class, (setf\n;; slot-value-using-class) should only be defined for\n;; application-defined slots, not internal slots (like ID, maybe\n;; others).\n\n;; get-internal-real-time, get-internal-run-time, get-universal-time\n;; need to be shadowed and disallowed.\n\n(in-package :bknr.datastore)\n\n(defvar *datastore-lparallel-kernel* nil\n  \"We use a dedicated lparallel kernel to avoid deadlocks if a shared\nkernel is trying to write to bknr.datastore\")\n\n(defvar *datastore-lparallel-kernel-lock* (bt:make-lock))\n\n(defun datastore-lparallel-kernel ()\n  (bt:with-lock-held (*datastore-lparallel-kernel-lock*)\n    (or\n     *datastore-lparallel-kernel*\n     (setf *datastore-lparallel-kernel* (lparallel:make-kernel 20 :name \"bknr.datastore-lparallel-kernel\")))))\n\n(define-condition inconsistent-slot-persistence-definition (store-error)\n  ((class :initarg :class)\n   (slot-name :initarg :slot-name))\n  (:report (lambda (e stream)\n             (with-slots (slot-name class) e\n               (format stream \"Slot ~A in class ~A declared as both transient and persistent\"\n                       slot-name class)))))\n\n(define-condition object-subsystem-not-found-in-store (store-error)\n  ((store :initarg :store))\n  (:report (lambda (e stream)\n             (with-slots (store) e\n               (format stream \"Could not find a store-object-subsystem in the current store ~A\" store)))))\n\n(define-condition persistent-slot-modified-outside-of-transaction (store-error)\n  ((slot-name :initarg :slot-name)\n   (object :initarg :object))\n  (:report (lambda (e stream)\n             (with-slots (slot-name object) e\n               (format stream \"Attempt to modify persistent slot ~A of ~A outside of a transaction\"\n                       slot-name object)))))\n\n(defclass store-object-subsystem ()\n  ((next-object-id :initform 0\n                   :accessor next-object-id\n                   :documentation \"Next object ID to assign to a new object\")\n   (snapshot-threads :initarg :snapshot-threads\n                     :initform 4\n                     :reader snapshot-threads\n                     :documentation \"Number of threads to use when snapshotting.\")))\n\n(defun store-object-subsystem ()\n  (or\n   (%store-object-subsystem-cache *store*)\n   (setf (%store-object-subsystem-cache *store*)\n         (let ((subsystem (find-if (alexandria:rcurry #'typep 'store-object-subsystem)\n                                   (store-subsystems *store*))))\n           (unless subsystem\n             (error 'object-subsystem-not-found-in-store :store *store*))\n           subsystem))))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (finalize-inheritance\n   (defclass persistent-class (indexed-class)\n     ())))\n\n(defmethod validate-superclass ((sub persistent-class) (super indexed-class))\n  t)\n\n(defvar *suppress-schema-warnings* nil)\n\n(define-condition must-inherit-store-object (error)\n  ()\n  (:report \"A persistent-class must inherit STORE-OBJECT\"))\n\n(defmethod closer-mop:finalize-inheritance :before ((obj persistent-class))\n  (handler-case\n      (let ((class-precedence-list (closer-mop:compute-class-precedence-list obj)))\n        (loop for class in class-precedence-list\n              if (eql 'store-object (class-name class))\n                return t\n              finally\n                 (error 'must-inherit-store-object)))\n    (bknr.indices::base-indexed-object-required (e)\n      (error 'must-inherit-store-object))))\n\n\n(defvar *wait-for-tx-p* t\n  \"Unused.\")\n\n(defmethod reinitialize-instance :after ((class persistent-class) &key)\n  (when (and (boundp '*store*) *store*)\n    (unless *suppress-schema-warnings*\n      (report-progress \"~&; class ~A has been changed. To ensure correct schema ~\n                              evolution, please snapshot your datastore.~%\"\n                       (class-name class)))))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defclass persistent-direct-slot-definition (index-direct-slot-definition)\n    ((relaxed-object-reference :initarg :relaxed-object-reference\n                               :initform nil)\n     (transient :initarg :transient\n                :initform nil)))\n\n  (defclass persistent-effective-slot-definition (index-effective-slot-definition)\n    ((relaxed-object-reference :initarg :relaxed-object-reference\n                               :initform nil)\n     (transient :initarg :transient\n                :initform nil)))\n\n  (defgeneric transient-slot-p (slotd)\n    (:method ((slotd t))\n      t)\n    (:method ((slotd persistent-direct-slot-definition))\n      (slot-value slotd 'transient))\n    (:method ((slotd persistent-effective-slot-definition))\n      (slot-value slotd 'transient)))\n\n  (defgeneric relaxed-object-reference-slot-p (slotd)\n    (:method ((slotd t))\n      nil)\n    (:method ((slotd persistent-effective-slot-definition))\n      (slot-value slotd 'relaxed-object-reference))\n    (:method ((slotd persistent-direct-slot-definition))\n      (slot-value slotd 'relaxed-object-reference))\n    (:documentation \"Return whether the given slot definition specifies\nthat the slot is relaxed.  If a relaxed slot holds a pointer to\nanother persistent object and the pointed-to object is deleted, slot\nreads will return nil.\")))\n\n\n(defun undo-set-slot (object slot-name value)\n  (if (eq value 'unbound)\n      (slot-makunbound object slot-name)\n      (setf (slot-value object slot-name) value)))\n\n(defmethod (setf slot-value-using-class) :around ((newval t)\n                                                  (class persistent-class)\n                                                  object\n                                                  (slotd persistent-effective-slot-definition))\n  (let ((slot-name (slot-definition-name slotd)))\n    (cond\n      ((or\n        (in-transaction-p)\n        (transient-slot-p slotd)\n        (member slot-name '(last-change id))\n        ;; If we're restoring then we don't need to create a new transaction\n        (in-restore-p))\n       (call-next-method))\n      (t\n       ;; If we're not in a transaction, or this is not a transient\n       ;; slot, we don't make the change directly, instead we just\n       ;; create a transaction for it. The transaction should call\n       ;; slot-value-using-class again.\n       (execute (make-instance 'transaction\n                               :function-symbol 'tx-change-slot-values\n                               :timestamp (get-universal-time)\n                               :args (list object (slot-definition-name slotd) newval)))\n       newval))))\n\n\n(define-condition transient-slot-cannot-have-initarg (store-error)\n  ((class :initarg :class)\n   (slot-name :initarg :slot-name))\n  (:documentation \"A transient slot may not have an :initarg\n  specified, as initialize-instance is only used for persistent\n  initialization.\")\n  (:report (lambda (e stream)\n             (with-slots (class slot-name) e\n               (format stream \"The transient slot ~A in class ~A was defined as having an initarg, which is not supported\"\n                       slot-name (class-name class))))))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defmethod direct-slot-definition-class ((class persistent-class) &key initargs transient name &allow-other-keys)\n    ;; It might be better to do the error checking in an\n    ;; initialize-instance method of persistent-direct-slot-definition\n    (when (and initargs transient)\n      (error 'transient-slot-cannot-have-initarg :class class  :slot-name name))\n    'persistent-direct-slot-definition))\n\n(defmethod effective-slot-definition-class ((class persistent-class) &key &allow-other-keys)\n  'persistent-effective-slot-definition)\n\n(defmethod compute-effective-slot-definition :around ((class persistent-class) name direct-slots)\n  (unless (or (every #'transient-slot-p direct-slots)\n              (notany #'transient-slot-p direct-slots))\n    (error 'inconsistent-slot-persistence-definition :class class :slot-name name))\n  (let ((effective-slot-definition (call-next-method)))\n    (when (typep effective-slot-definition 'persistent-effective-slot-definition)\n      (with-slots (relaxed-object-reference transient) effective-slot-definition\n        (setf relaxed-object-reference (some #'relaxed-object-reference-slot-p direct-slots)\n              transient (transient-slot-p (first direct-slots)))))\n    effective-slot-definition))\n\n(defmethod class-persistent-slots ((class standard-class))\n  (remove-if #'transient-slot-p (class-slots class)))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *id-index*\n    (make-instance 'unique-index :test #'eql\n                                 :slot-name 'id))\n  (defvar *class-skip-index*\n    (make-instance 'class-skip-index\n                   :index-superclasses t\n                   :slots '(id))))\n\n(defclass base-store-object ()\n  ((%id-cache\n    :initarg :id\n    :accessor %id-cache))\n  (:documentation \"A regular class with optimized slot access, for storing fast slots.\"))\n\n(defmethod bknr.indices::allow-destroyed-access-p ((class persistent-class)\n                                                   (slot (eql '%id-cache)))\n  t)\n\n(defclass store-object (base-indexed-object\n                        base-store-object)\n  ((id :initarg :id\n       :type integer\n       :index *id-index*\n       :index-reader store-object-with-id :index-values all-store-objects\n       :index-mapvalues map-store-objects)\n   (last-change :initform (get-universal-time)\n                :initarg :last-change))\n  (:metaclass persistent-class)\n  (:class-indices (all-class :index *class-skip-index*\n                             :index-subclasses t\n                             :index-initargs (:index-superclasses t)\n                             :index-keys all-store-classes\n                             :index-reader store-objects-with-class\n                             :slots (id))))\n\n(defmethod class-instances (class)\n  (find-class class)                 ; make sure that the class exists\n  (store-objects-with-class class))\n\n(deftransaction store-object-touch (object)\n  \"Update the LAST-CHANGE slot to reflect the current transaction timestamp.\"\n  (setf (slot-value object 'last-change) (current-transaction-timestamp)))\n\n(defgeneric store-object-last-change (object depth)\n  (:documentation \"Return the last change time of the OBJECT.  DEPTH\n  determines how deep the object graph will be traversed.\")\n\n  (:method ((object t) (depth integer))\n    0)\n\n  (:method ((object store-object) (depth (eql 0)))\n    (slot-value object 'last-change))\n\n  (:method ((object store-object) depth)\n    (let ((last-change (slot-value object 'last-change)))\n      (dolist (slotd (class-slots (class-of object)))\n        (let* ((slot-name (slot-definition-name slotd))\n               (child (and (slot-boundp object slot-name)\n                           (slot-value object slot-name))))\n          (setf last-change\n                (cond\n                  ((null child)\n                   last-change)\n                  ((typep child 'store-object)\n                   (max last-change (store-object-last-change child (1- depth))))\n                  ((listp child)\n                   (reduce #'max child\n                           :key (alexandria:rcurry 'store-object-last-change (1- depth))\n                           :initial-value last-change))\n                  (t\n                   last-change)))))\n      last-change)))\n\n#+allegro\n(aclmop::finalize-inheritance (find-class 'store-object))\n\n(defmethod fix-make-instance-args ((class persistent-class)\n                                   initargs)\n  (list*\n   class\n   (append\n    initargs\n    (let ((used-keys (loop for key in initargs by #'cddr\n                           collect key)))\n      (loop for initarg in (closer-mop:class-default-initargs class)\n            unless (member (first initarg)\n                           used-keys)\n              appending (list\n                         (first initarg)\n                         (funcall (third initarg))))))))\n\n(defmethod update-instance-for-redefined-class ((instance store-object)\n                                                added-slots\n                                                discarded-slots\n                                                property-list\n                                                &rest initargs\n                                                &key &allow-other-keys)\n  \"We never reinitialize slots on existing objects. Doing so creates\ncomplicated and expensive semantics with respect to\ntransactions. Newer objects will still be initialized though. (So when\nusing bknr.cluster, you might still have to be careful with :initform\nwhen doing a rolling deploy)\"\n  (apply #'call-next-method\n         instance\n         nil\n         discarded-slots\n         property-list\n         initargs))\n\n(defmethod make-instance :around ((class persistent-class) &rest initargs\n                                  &key\n                                    id)\n  \"There are three ways make-instance can be called:\n\n   * Outside of a transaction, in which case we have to encode a\n     transaction, but also use default-initargs\n\n   * Inside a deftransaction, in which case we use default-initargs\n     immediately, but don't encode a transaction\n\n   * When commiting or replaying a transaction. As with the last\n     case (in-transaction-p) will be true here, but :id will truthy\n     since we have already processed default-initargs.\n\"\n  (cond\n    ((or\n      (in-transaction-p)\n      (in-restore-p))\n     (cond\n       (id\n        (call-next-method))\n       (t\n        (apply #'tx-make-instance\n               (fix-make-instance-args class initargs)))))\n    (t\n     (with-store-guard ()\n       (let ((transaction\n               (destructuring-bind (class . args)\n                   (fix-make-instance-args\n                    class initargs)\n                 (make-instance 'transaction\n                                :function-symbol 'tx-make-instance\n                                :timestamp (get-universal-time)\n                                :args (list*\n                                       (class-name class)\n                                       args)))))\n         (execute transaction))))))\n\n(defun tx-make-instance (class &rest args)\n  ;; class might either be a symbol, or a class depending on whether\n  ;; this was a toplevel transaction or called inside another\n  ;; transaction.\n  (apply #'make-instance class\n         :id (allocate-next-object-id)\n         args))\n\n(defvar *allocate-object-id-lock* (bt:make-lock \"Persistent Object ID Creation\"))\n\n(defun allocate-next-object-id ()\n  (mp-with-lock-held (*allocate-object-id-lock*)\n    (let ((id (next-object-id (store-object-subsystem))))\n      (incf (next-object-id (store-object-subsystem)))\n      id)))\n\n(defun initialize-transient-slots (object)\n  (dolist (slotd (class-slots (class-of object)))\n    (when (and (typep slotd 'persistent-effective-slot-definition)\n               (transient-slot-p slotd)\n               (slot-definition-initfunction slotd))\n      (setf (slot-value object (slot-definition-name slotd))\n            (funcall (slot-definition-initfunction slotd))))))\n\n(defmethod initialize-instance :after ((object store-object) &key)\n  ;; This is called only when initially creating the (persistent)\n  ;; instance, not during restore.  During restore, the\n  ;; INITIALIZE-TRANSIENT-INSTANCE function is called for all\n  ;; persistent objects after the snapshot has been read, but before\n  ;; running the transaction log.\n  (initialize-transient-instance object))\n\n(defmacro print-store-object ((object stream &key type) &body body)\n  ;; variable capture accepted here.\n  `(print-unreadable-object (,object ,stream :type ,type)\n     (format stream \"ID: ~D \" (store-object-id ,object))\n     ,@body))\n\n(defmethod print-object ((object store-object) stream)\n  (print-unreadable-object (object stream :type t)\n    (format stream \"ID: ~D\" (store-object-id object))))\n\n(defmethod print-object :around ((object store-object) stream)\n  (if (object-destroyed-p object)\n      (print-unreadable-object (object stream :type t)\n        (princ \"DESTROYED\" stream))\n      (call-next-method)))\n\n(defmethod change-class :before ((object store-object) class &rest args)\n  (declare (ignore class args))\n  (when (not (in-transaction-p))\n    (error \"Can't change class of persistent object ~A using change-class ~\n            outside of transaction, please use PERSISTENT-CHANGE-CLASS instead\" object)))\n\n(defun tx-persistent-change-class (object class-name &rest args)\n  (warn \"TX-PERSISTENT-CHANGE-CLASS does not maintain class indices, ~\n         please snapshot and restore to recover indices\")\n  (apply #'change-class object (find-class class-name) args))\n\n(defun persistent-change-class (object class &rest args)\n  (execute (make-instance 'transaction :function-symbol 'tx-persistent-change-class\n                                       :timestamp (get-universal-time)\n                                       :args (append (list object (if (symbolp class) class (class-name class))) args))))\n\n(defgeneric initialize-transient-instance (store-object)\n  (:documentation\n   \"Initializes the transient aspects of a persistent object. This\nmethod is called after a persistent object has been initialized, also\nwhen the object is loaded from a snapshot, but before reading the\ntransaction log.\"))\n\n(defmethod initialize-transient-instance ((object store-object)))\n\n(defmethod store-object-persistent-slots ((object store-object))\n  (mapcar #'slot-definition-name (class-persistent-slots (class-of object))))\n\n(defmacro define-persistent-class (class (&rest superclasses) slots &rest class-options)\n  (let ((superclasses (or superclasses '(store-object)))\n        (metaclass (cadr (assoc :metaclass class-options))))\n    (when (and metaclass\n               (not (validate-superclass (find-class metaclass)\n                                         (find-class 'persistent-class))))\n      (error \"Can not define a persistent class with metaclass ~A.\" metaclass))\n    `(define-bknr-class ,class ,superclasses ,slots\n                        ,@(unless metaclass '((:metaclass persistent-class)))\n                        ,@class-options)))\n\n(defmacro defpersistent-class (class (&rest superclasses) slots &rest class-options)\n  (let ((superclasses (or superclasses '(store-object)))\n        (metaclass (cadr (assoc :metaclass class-options))))\n    (when (and metaclass\n               (not (validate-superclass (find-class metaclass)\n                                         (find-class 'persistent-class))))\n      (error \"Can not define a persistent class with metaclass ~A.\" metaclass))\n    `(eval-when (:compile-toplevel :load-toplevel :execute)\n       (defclass ,class ,superclasses ,slots\n         ,@(unless metaclass '((:metaclass persistent-class)))\n         ,@class-options))))\n\n;;; binary snapshot\n\n(defvar *current-object-slot* nil)\n(defvar *current-slot-relaxed-p* nil)\n\n(defun encode-layout (class-layout class stream)\n  (%write-tag #\\L stream)\n  (%encode-integer (class-layout-id class-layout) stream)\n  (%encode-symbol (class-name class) stream)\n  (%encode-integer (length (class-layout-slots class-layout)) stream)\n  (dolist (slot (class-layout-slots class-layout))\n    (%encode-symbol slot stream)))\n\n(defun %encode-set-slots (class-layout object stream)\n  \"Given an object (or snapshot), encode each of the slots in the stream\nin the order established by class-layout. If a specific slot is not\nbound, encode 'bknr.datastore::unbound.\"\n  (let* ((current-object-slot (list nil nil))\n         ;; In the past we were creating a new list for every slot, to\n         ;; avoid consing we're caching and reusing this list.\n         (*current-object-slot* current-object-slot))\n    (dolist (slot-info (class-layout-slot-infos class-layout))\n      (let* ((slot (slot-info-slot slot-info))\n             (*current-slot-relaxed-p* (slot-info-relaxed-object-reference-p slot-info)))\n        (setf (first current-object-slot) object)\n        (setf (second current-object-slot) slot)\n        (encode (if (slot-boundp object slot)\n                    (slot-value object slot)\n                    'unbound)\n                stream)))))\n\n(defun encode-create-object (class-layouts object stream)\n  (let* ((class (class-of object))\n         (layout (gethash class class-layouts)))\n    (unless layout\n      (setf layout\n            (make-instance 'class-layout\n                           :id (hash-table-count class-layouts)\n                           :class class ;; Not really used while writing\n                           ;; XXX layout muss konstant sein\n                           :slots (sort\n                                   (remove 'id\n                                           (copy-seq\n                                            (store-object-persistent-slots object)))\n                                   #'string< :key #'symbol-name)))\n      (encode-layout layout class stream)\n      (setf (gethash class class-layouts) layout))\n    (%write-tag #\\O stream)\n    (%encode-integer (class-layout-id layout) stream)\n    (%encode-integer (store-object-id object) stream)))\n\n(defstruct object-snapshot-pair\n  object\n  snapshot)\n\n(defun encode-set-slots (class-layouts object stream)\n  (let ((class-layout (gethash (class-of object) class-layouts)))\n    (%write-tag #\\S stream)\n    (%encode-integer (class-layout-id class-layout) stream)\n    (%encode-integer (store-object-id object) stream)\n    (%encode-set-slots class-layout object stream)))\n\n(defgeneric encode-slots-for-object (class-layout snapshot stream)\n  (:documentation \"Only used for encoding slots from Snapshot\"))\n\n(defun encode-set-slots-for-snapshot (class-layouts object-snapshot-pair stream)\n  (let* ((object (object-snapshot-pair-object object-snapshot-pair))\n         (class-layout (gethash (class-of object)\n                                class-layouts)))\n    (%write-tag #\\S stream)\n    (%encode-integer (class-layout-id class-layout) stream)\n    (%encode-integer (store-object-id object) stream)\n    (encode-slots-for-object class-layout (object-snapshot-pair-snapshot object-snapshot-pair)\n                             stream)))\n\n(defun find-class-with-interactive-renaming (class-name)\n  (loop until (or (null class-name)\n                  (find-class class-name nil))\n        do (progn\n             (format *query-io* \"Class ~A not found, enter new class or enter ~\n                              NIL to ignore objects of this class: \"\n                     class-name)\n             (finish-output *query-io*)\n             (setq class-name (read *query-io*))))\n  (and class-name\n       (find-class class-name)))\n\n(defun find-slot-name-with-interactive-rename (class slot-name)\n  (loop until (find slot-name (class-slots class) :key #'slot-definition-name)\n        do (format *query-io* \"Slot ~S not found in class ~S, enter new slot name: \"\n                   slot-name (class-name class))\n        do (setq slot-name (read *query-io*))\n        finally (return slot-name)))\n\n(defvar *slot-name-map*)\n\n(defun rename-slot (class slot-name)\n  (or (caddr (find (list (class-name class) slot-name) *slot-name-map*\n                   :key #'(lambda (entry) (subseq entry 0 2)) :test #'equal))\n      (find (symbol-name slot-name)\n            (mapcar #'slot-definition-name (class-slots class)) :key #'symbol-name :test #'equal)))\n\n(defgeneric convert-slot-value-while-restoring (object slot-name value)\n  (:documentation \"Generic function to be called to convert a slot's\n  value from a previous snapshot layout.  OBJECT is the object that is\n  being restored, SLOT-NAME is the name of the slot in the old schema,\n  VALUE is the value of the slot in the old schema.\")\n  (:method (object slot-name value)\n    (setf (slot-value object slot-name) value)))\n\n(defun find-slot-name-with-automatic-rename (class slot-name)\n  (if (find slot-name (class-slots class) :key #'slot-definition-name)\n      slot-name\n      (restart-case\n          (let ((new-slot-name (rename-slot class slot-name)))\n            (cond\n              (new-slot-name\n               (warn \"slot ~S not found in class ~S, automatically renamed to ~S\"\n                     slot-name (class-name class) new-slot-name)\n               new-slot-name)\n              (t\n               (error \"can't find a slot in class ~A which matches the name ~A used in the store snapshot\"\n                      (class-name class) slot-name))))\n        (convert-values ()\n          :report \"Convert slot values using CONVERT-SLOT-VALUE-WHILE-RESTORING\"\n          (cons 'convert-slot-values slot-name))\n        (ignore-slot ()\n          :report \"Ignore slot, discarding values found in the snapshot file\"\n          nil))))\n\n(defun find-class-slots-with-interactive-renaming (class slot-names)\n  #+(or)\n  (format t \"; verifying class layout for class ~A~%; slots:~{ ~S~}~%\" (class-name class)\n          (mapcar #'slot-definition-name (class-slots class)))\n  (loop for slot-name in slot-names\n        collect (find-slot-name-with-automatic-rename class slot-name)))\n\n(defun snapshot-read-layout (stream layouts)\n  (let* ((id (%decode-integer stream))\n         (class-name (%decode-symbol stream :usage \"class\"))\n         (nslots (%decode-integer stream))\n         (class (find-class-with-interactive-renaming class-name))\n         (slot-names (loop repeat nslots collect (%decode-symbol stream\n                                                                 :intern (not (null class))\n                                                                 :usage \"slot\")))\n         (slots (if class\n                    (find-class-slots-with-interactive-renaming class slot-names)\n                    slot-names)))\n    (setf (gethash id layouts)\n          (make-instance 'class-layout\n                         :id id\n                         :class class\n                         :slots slots))))\n\n(defun %read-slots (stream object class-layout)\n  \"Read the OBJECT from STREAM.  The individual slots of the object\nare expected in the order of the list SLOTS.  If the OBJECT is NIL,\nthe slots are read from the snapshot and ignored.\"\n  (declare (optimize (speed 3)))\n  (dolist (slot-info (class-layout-slot-infos class-layout))\n    (let ((slot-name (slot-info-slot slot-info))\n          (value (decode stream)))\n      (cond\n        ((consp slot-name)\n         (assert (eq 'convert-slot-values (car slot-name)))\n         (convert-slot-value-while-restoring object (cdr slot-name) value))\n        ((null slot-name)\n         ;; ignore value\n         )\n        (t\n         (restart-case\n             (let ((*current-object-slot* (list object slot-name))\n                   (*current-slot-relaxed-p* (or (null object)\n                                                 (slot-info-relaxed-object-reference-p slot-info))))\n               (when object\n                 (let ((bknr.indices::*indices-remove-p* nil))\n                   (if (eq value 'unbound)\n                       (slot-makunbound object slot-name)\n                       (convert-slot-value-while-restoring object slot-name value)))))\n           (set-slot-nil ()\n             :report \"Set slot to NIL.\"\n             (setf (slot-value object slot-name) nil))\n           (make-slot-unbound ()\n             :report \"Make slot unbound.\"\n             (slot-makunbound object slot-name))))))))\n\n(defun snapshot-read-object (stream layouts)\n  (declare (optimize (speed 3)))\n  (with-simple-restart (skip-object \"Skip the object.\")\n    (let* ((layout-id (%decode-integer stream))\n           (object-id (%decode-integer stream))\n           (class (class-layout-class (gethash layout-id layouts))))\n      ;; If the class is NIL, it was not found in the currently\n      ;; running Lisp image and objects of this class will be ignored.\n      (when class\n        (let ((object (allocate-instance class)))\n          (setf\n           (object-destroyed-p-v2 object) nil\n           (slot-value object 'id) object-id\n           (%id-cache object) object-id\n           (next-object-id (store-object-subsystem)) (max (1+ object-id)\n                                                               (next-object-id (store-object-subsystem))))\n          (parallel-index-add object (class-slot-indices class 'id)))))))\n\n(defun snapshot-read-slots (stream layouts)\n  (let* ((layout-id (%decode-integer stream))\n         (object-id (%decode-integer stream))\n         (object (store-object-with-id object-id)))\n    (restart-case\n        (%read-slots stream object (gethash layout-id layouts))\n      (skip-object-initialization ()\n        :report \"Skip object initialization.\")\n      (delete-object ()\n        :report \"Delete the object.\"\n        (delete-object object)))))\n\n(define-condition encoding-destroyed-object (error)\n  ((id :initarg :id)\n   (slot :initarg :slot)\n   (container :initarg :container))\n  (:report (lambda (e out)\n             (with-slots (slot container id) e\n               (format out\n                       \"Encoding reference to destroyed object with ID ~A from slot ~A of object ~A with ID ~A.\"\n                       id slot (type-of container) (store-object-id container))))))\n\n(defmethod encode-object ((object store-object) stream)\n  (if (object-destroyed-p object)\n      (let* ((*indexed-class-override* t)\n             (id (store-object-id object))\n             (container (first *current-object-slot*))\n             (slot (second *current-object-slot*)))\n\n        ;; if we are not encoding slot values, something has gone\n        ;; wrong with the indices\n        (unless (and container slot)\n          (warn \"Encoding destroyed object with ID ~A.\" id)\n          (%write-tag #\\o stream)\n          (%encode-integer id stream)\n          (return-from encode-object))\n\n        (flet ((encode-relaxed ()\n                 (warn \"Encoding reference to destroyed object with ID ~A from slot ~A of object ~A with ID ~A.\"\n                       id slot (type-of container) (store-object-id container))\n                 (%write-tag #\\o stream)\n                 (%encode-integer id stream)))\n          (if *current-slot-relaxed-p*\n              ;; the slot can contain references to deleted objects, just warn\n              (encode-relaxed)\n              ;; the slot can't contain references to deleted objects, throw an error\n              (restart-case\n                  (error 'encoding-destroyed-object\n                         :id id\n                         :slot slot\n                         :container container)\n                (encode-relaxed-slot ()\n                  (encode-relaxed))\n                (make-slot-unbound-and-encode-relaxed ()\n                  (encode-relaxed)\n                  (slot-makunbound container slot))))))\n\n      ;; Go ahead and serialize the object reference\n      (progn (%write-tag #\\o stream)\n             (%encode-integer (store-object-id object) stream))))\n\n(defmethod decode-object ((tag (eql #\\o)) stream)\n  (let ((*current-object-slot* nil))\n    (%decode-store-object stream)))\n\n(define-condition invalid-reference (warning)\n  ((id :initarg :id))\n  (:report (lambda (e stream)\n             (format stream \"internal inconsistency during restore - store object with ID ~A could not be found\"\n                     (slot-value e 'id)))))\n\n(defun %decode-store-object (stream)\n  ;; This is actually called in two contexts, when a slot-value is to\n  ;; be filled with a reference to a store object and when a list of\n  ;; store objects is read from the transaction log (%decode-list).\n  ;; In the former case, references two deleted objects are accepted\n  ;; when the slot pointing to the object is marked as being a\n  ;; \"relaxed-object-reference\", in the latter case, no such\n  ;; information is available.  To ensure maximum restorability of\n  ;; transaction logs, object references stored in lists are always\n  ;; considered to be relaxed references, which means that references\n  ;; to deleted objects are restored as NIL.  Applications must be\n  ;; prepared to cope with NIL entries in such object lists (usually\n  ;; lists in slots).\n  (let* ((id (%decode-integer stream))\n         (object (or (store-object-with-id id)\n                     (warn 'invalid-reference :id id)))\n         (container (first *current-object-slot*))\n         (slot-name (second *current-object-slot*)))\n    (cond (object object)\n\n          ((or *current-slot-relaxed-p* (not container))\n           (if container\n               (warn \"Reference to inexistent object with id ~A in relaxed slot ~A of object ~\n                      with class ~A with ID ~A.\"\n                     id slot-name (type-of container) (store-object-id container))\n               (warn \"Reference to inexistent object with id ~A from unnamed container, returning NIL.\" id))\n\n           (when (>= id (next-object-id (store-object-subsystem)))\n             ;; In Hans' store, we just update the\n             ;; next-object-id. However, this shouldn't really happen.\n             (error \"Decoded an object with an invalid object-id (>= next-object-id): ~a\" id))\n           nil)\n\n          (t (error \"Reference to inexistent object with id ~A from slot ~A of object ~A with ID ~A.\"\n                    id slot-name (type-of container)\n                    (if container (store-object-id container) \"unknown object\"))))))\n\n(defun encode-current-object-id (stream)\n  (%write-tag #\\I stream)\n  (%encode-integer (next-object-id (store-object-subsystem)) stream))\n\n(defmethod snapshot-subsystem-async ((store store) (subsystem store-object-subsystem))\n  (let ((snapshot-pathname (store-subsystem-snapshot-pathname store subsystem)))\n    (snapshot-subsystem-helper subsystem snapshot-pathname)))\n\n(defmethod snapshot-subsystem ((store store) (subsystem store-object-subsystem))\n  (error \"Unimplemented: call snapshot-subsystem-async instead!\"))\n\n(defclass class-layout-slot-info ()\n  ((slot :initarg :slot\n         :reader slot-info-slot)\n   (class :initarg :class\n          :reader slot-info-class)\n   (relaxed-object-reference-p\n    :initarg :relaxed-object-reference-p\n    :reader slot-info-relaxed-object-reference-p)))\n\n(defclass class-layout ()\n  ((id :initarg :id\n       :reader class-layout-id\n       :documentation \"Typically only used while writing a snapshot\")\n   (class :initarg :class\n          :reader class-layout-class\n          :documentation \"Typically only used while reading a snapshot\")\n   (slots :initarg :slots\n          :reader class-layout-slots)\n   (slot-infos :reader class-layout-slot-infos)))\n\n(defmethod initialize-instance :after ((self class-layout) &key slots class id)\n  (setf\n   (slot-value self 'slot-infos)\n   (loop for slot in slots\n         collect (make-instance\n                  'class-layout-slot-info\n                  :slot slot\n                  :class class\n                  :relaxed-object-reference-p\n                  (let ((slot (find slot (class-slots class) :key #'slot-definition-name)))\n                    (when slot\n                      (relaxed-object-reference-slot-p slot)))))))\n\n(defun snapshot-subsystem-helper (subsystem snapshot-pathname\n                                  &key (map-store-objects #'map-store-objects))\n  (let ((class-layouts\n          ;; class-layouts is a map from the class object to the\n          ;; layout, which currently is (list* id ...slots)\n          ;; See the corresponding test.\n          (make-hash-table)))\n    (with-open-file (stream snapshot-pathname\n                            :direction :output\n                            :element-type '(unsigned-byte 8)\n                            :if-does-not-exist :create\n                            :if-exists :supersede)\n      (with-transaction (:prepare-for-snapshot)\n        (funcall map-store-objects #'prepare-for-snapshot))\n      (encode-current-object-id stream)\n      (funcall map-store-objects\n               (lambda (object)\n                 (when (subtypep (type-of object) 'store-object)\n                   (encode-create-object class-layouts object stream)))))\n\n    (let ((objects))\n      (funcall map-store-objects\n               (lambda (object)\n                 (when (subtypep (type-of object) 'store-object)\n                   (push object objects))))\n\n      ;; Will return a lambda!\n      (encode-object-slots subsystem class-layouts (nreverse objects) snapshot-pathname))))\n\n(defvar *crash-output-stream* t)\n\n(defun %log-crash (e)\n  (format *crash-output-stream* \"Error in background snapshot thread: ~a~%\" e)\n  #+lispworks\n  (dbg:output-backtrace :brief :stream *crash-output-stream*)\n  #-lispworks\n  (trivial-backtrace:print-backtrace e))\n\n(defun safe-make-thread (fn)\n  (bt:make-thread\n   (lambda ()\n     (ignore-errors\n      (handler-bind ((error #'%log-crash))\n        (funcall fn))))))\n\n(defmethod make-object-snapshot (object)\n  nil)\n\n(defun split-objects-into-snapshots (objects)\n  (let ((lparallel:*kernel* (datastore-lparallel-kernel)))\n    (let ((snapshots (lparallel:pmapcar #'make-object-snapshot objects)))\n      (assert (eql (length snapshots)\n                   (length objects)))\n      (loop for obj in objects\n            for snapshot in snapshots\n            if (not snapshot)\n              collect obj into objs\n            else\n              collect  (make-object-snapshot-pair\n                        :object obj\n                        :snapshot snapshot)\n                into snaps\n            finally\n               (return (values objs snaps))))))\n\n(defun encode-object-slots (subsystem class-layouts objects snapshot-pathname)\n  (labels ((make-batches (objects batch-size)\n             (if (<= (length objects) batch-size)\n                 (list objects)\n                 (list*\n                  (subseq objects 0 batch-size)\n                  (make-batches\n                   (subseq objects batch-size)\n                   batch-size)))))\n    (multiple-value-bind\n          (objects snapshots)\n        (split-objects-into-snapshots objects)\n      (let* ((batch-size (ceiling (length objects) (snapshot-threads subsystem)))\n             (batches (make-batches objects batch-size))\n             (streams (loop for nil in batches\n                            collect (uiop:with-temporary-file (:pathname p)\n                                      (open p :direction :io\n                                              :if-exists :supersede\n                                              :element-type '(unsigned-byte 8)))))\n             (lock (bt:make-lock))\n             (count 0))\n\n        (flet ((close-streams ()\n                 \"Close all the temporary streams that we've opened\"\n                 (mapc #'close streams)))\n          (unwind-protect\n               (let ((threads\n                       (loop for batch in batches\n                             for s in streams\n                             collect\n                             (let ((s s)\n                                   (batch batch))\n                               (safe-make-thread\n                                (lambda ()\n                                  (let ((*in-restore-p* t))\n                                    (loop for object in batch\n                                          do (encode-set-slots class-layouts object s))\n                                    (bt:with-lock-held (lock)\n                                      (incf count)))))))))\n                 (mapc #'bt:join-thread threads)\n\n                 (unless (= count (length threads))\n                   (close-streams)\n                   (error \"Some threads failed to complete\"))\n\n                 ;; Finally combine all the streams together\n                 (lambda ()\n                   (unwind-protect\n                        (with-open-file (stream snapshot-pathname\n                                                :direction :output\n                                                :element-type '(unsigned-byte 8)\n                                                :if-exists :append)\n                          (loop for s in streams do\n                            (file-position s 0)\n                            (uiop:copy-stream-to-stream s stream :element-type '(unsigned-byte 8)))\n\n                          (format t \"Encoding background snapshots~%\")\n                          ;; Encode the background snapshots\n                          (loop for object-snapshot-pair in snapshots\n                                do (encode-set-slots-for-snapshot class-layouts object-snapshot-pair\n                                                                  stream))\n\n                          (finish-output stream))\n                     (close-streams))))))))))\n\n(defmethod close-subsystem ((store store) (subsystem store-object-subsystem))\n  (dolist (class-name (all-store-classes))\n    (clear-class-indices (find-class class-name))))\n\n(defmethod restore-subsystem ((store store) (subsystem store-object-subsystem) &key until)\n  ;; XXX check that until > snapshot time\n  (declare (ignore until))\n  (let ((snapshot (store-subsystem-snapshot-pathname store subsystem)))\n    ;; not all indices that should be cleared are cleared. maybe\n    ;; check on first instatiation of a class?\n    (dolist (class-name (cons 'store-object (all-store-classes)))\n      (clear-class-indices (find-class class-name)))\n    (setf (next-object-id subsystem) 0)\n    (when (probe-file snapshot)\n      (report-progress \"~&; loading snapshot file ~A~%\" snapshot)\n      (with-open-file (s snapshot\n                         :element-type '(unsigned-byte 8)\n                         :direction :input)\n        (let ((class-layouts\n                ;; Map from class-id -> class-layout\n                (make-hash-table))\n              (created-objects 0)\n              (reported-objects-count 0)\n              (read-slots 0)\n              (error t)\n              (*slot-name-map* nil))\n          (unwind-protect\n               (progn\n                 (with-simple-restart\n                     (finalize-object-subsystem \"Finalize the object subsystem.\")\n                   (loop\n                     (when (and (plusp created-objects)\n                                (zerop (mod created-objects 10000))\n                                (not (= created-objects reported-objects-count)))\n                       #+nil (format t \"Snapshot position ~A~%\" (file-position s))\n                       (report-progress \"~A objects created.~%\" created-objects)\n                       (setf reported-objects-count created-objects)\n                       (force-output))\n                     (when (and (plusp read-slots)\n                                (zerop (mod read-slots 10000)))\n                       (report-progress \"~A of ~A objects initialized.~%\" read-slots created-objects)\n                       (force-output))\n                     (let ((char (%read-tag s nil nil)))\n                       (unless (member char '(#\\I #\\L #\\O #\\S nil))\n                         (error \"unknown char ~A at offset ~A~%\" char (file-position s)))\n                       (ecase char\n                         ((nil) (return))\n                         (#\\I (setf (next-object-id (store-object-subsystem)) (%decode-integer s)))\n                         (#\\L (snapshot-read-layout s class-layouts))\n                         (#\\O (snapshot-read-object s class-layouts) (incf created-objects))\n                         (#\\S (snapshot-read-slots s class-layouts) (incf read-slots))))))\n                 (map-store-objects #'initialize-transient-slots)\n                 (map-store-objects #'initialize-transient-instance)\n                 (setf error nil))\n            (when error\n              (maphash #'(lambda (key val)\n                           (declare (ignore key))\n                           (let ((class-name (car val)))\n                             (report-progress \"clearing indices for class ~A~%\" (class-name class-name))\n                             (clear-class-indices class-name)))\n                       class-layouts))))))))\n\n(defun tx-delete-object (id)\n  (destroy-object (store-object-with-id id)))\n\n(defun delete-object (object)\n  (if (in-transaction-p)\n      (destroy-object object)\n      (execute (make-instance 'transaction :function-symbol 'tx-delete-object\n                                           :timestamp (get-universal-time)\n                                           :args (list (store-object-id object))))))\n\n(defun tx-delete-objects (&rest object-ids)\n  (mapc #'(lambda (id) (destroy-object (store-object-with-id id))) object-ids))\n\n(defun delete-objects (&rest objects)\n  (if (in-transaction-p)\n      (mapc #'destroy-object objects)\n      (execute (make-instance 'transaction :function-symbol 'tx-delete-objects\n                                           :timestamp (get-universal-time)\n                                           :args (mapcar #'store-object-id objects)))))\n\n(defgeneric cascade-delete-p (object referencing-object)\n  (:method (object referencing-object)\n    (declare (ignore object referencing-object))\n    nil)\n  (:documentation \"return non-nil if the REFERENCING-OBJECT should be deleted when the OBJECT is deleted\"))\n\n(defun partition-list (list predicate)\n  \"Return two list values, the first containing all elements from LIST\nthat satisfy PREDICATE, the second those that don't\"\n  (let (do dont)\n    (dolist (element list)\n      (if (funcall predicate element)\n          (push element do)\n          (push element dont)))\n    (values do dont)))\n\n(defun cascading-delete-object (object)\n  \"Delete the OBJECT and all objects that reference it and that are eligible to cascading deletes, as indicated by\nthe result of calling CASCADE-DELETE-P.  Generate error if there are references to the objects that are not eligible\nto cascading deletes.\"\n  (multiple-value-bind (cascading-delete-refs\n                        remaining-refs)\n      (partition-list (find-refs object) (alexandria:curry #'cascade-delete-p object))\n    (when remaining-refs\n      (error \"Cannot delete object ~A because there are references ~\n              to this object in the system, please consult a system administrator!\"\n             object))\n    (apply #'delete-objects object cascading-delete-refs)))\n\n(defun tx-change-slot-values (object &rest slots-and-values)\n  \"Called by the MOP to change a persistent slot's value.\"\n  (unless (in-transaction-p)\n    (error 'not-in-transaction))\n  (when object\n    (loop for (slot value) on slots-and-values by #'cddr\n          do (setf (slot-value object slot) value))))\n\n(defun change-slot-values (object &rest slots-and-values)\n  \"This function is the deprecated way to set slots of persistent\n   objects.\"\n  (warn \"CHANGE-SLOT-VALUES is deprecated - use WITH-TRANSACTION and standard accessors!\")\n  (execute (make-instance 'transaction\n                          :function-symbol 'tx-change-slot-values\n                          :timestamp (get-universal-time)\n                          :args (list* object slots-and-values))))\n\n(defgeneric prepare-for-snapshot (object)\n  (:method ((object store-object))\n    nil)\n  (:documentation \"Called for every store object before a snapshot is\n  written.\"))\n\n(defun find-store-object (id-or-name &key (class 'store-object) query-function key-slot-name)\n  \"Mock up implementation of find-store-object API as in the old datastore.\nNote: QUERY-FUNCTION will only be used if ID-OR-NAME is neither an integer nor a\nstring designating an integer.\"\n  (unless id-or-name\n    (error \"can't search a store object with null key\"))\n  (when (stringp id-or-name)\n    (multiple-value-bind (value end) (parse-integer id-or-name :junk-allowed t)\n      (when (and value\n                 (eql end (length id-or-name)))\n        (setq id-or-name value))))\n  (let ((result (cond\n                  ((numberp id-or-name)\n                   (store-object-with-id id-or-name))\n                  (t\n                   (cond\n                     (query-function\n                      (funcall query-function id-or-name))\n                     ((eq class 't)\n                      (error \"can't search for store object by name without class specified\"))\n                     (t\n                      (let ((index (bknr.indices::class-slot-index (find-class class) key-slot-name)))\n                        (when index\n                          (index-get index id-or-name)))))))))\n    (unless (or (null result)\n                (typep result class))\n      (error \"Object ~A is not of wanted type ~A.\" result class))\n    result))\n\n(deftransaction store-object-add-keywords (object slot keywords)\n  (setf (slot-value object slot)\n        (union (slot-value object slot)\n               keywords)))\n\n(deftransaction store-object-remove-keywords (object slot keywords)\n  (setf (slot-value object slot)\n        (set-difference (slot-value object slot) keywords)))\n\n(deftransaction store-object-set-keywords (object slot keywords)\n  (setf (slot-value object slot) keywords))\n\n(defmethod find-refs ((object store-object))\n  \"Find references to the given OBJECT in all store-objects, traversing both single valued and list valued slots.\"\n  (remove-if-not\n   (lambda (candidate)\n     (find-if (lambda (slotd)\n                (and (slot-boundp candidate (slot-definition-name slotd))\n                     (let ((slot-value (slot-value candidate (slot-definition-name slotd))))\n                       (or (eq object slot-value)\n                           (and (alexandria:proper-list-p slot-value)\n                                (find object slot-value))))))\n              (class-slots (class-of candidate))))\n   (class-instances 'store-object)))\n\n(pushnew :mop-store cl:*features*)\n\n(defmethod store-object-id (object)\n  (or\n   (%id-cache object)\n   (progn\n     (warn \"shouldn't attempt to use the old slot value!\")\n     (slot-value object 'id))))\n\n(defmethod should-index-objects-for-class-p ((class (eql (find-class 'store-object))))\n  nil)\n\n(defmethod class-instances ((name (eql 'store-object)))\n  (sort\n   (all-store-objects)\n   #'<\n   :key #'store-object-id))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.datastore\n  (:use :closer-common-lisp\n        :bknr.utils :cl-interpol :cl-ppcre\n        :bknr.indices :bknr.statistics\n        :alexandria)\n  (:import-from #:bknr.indices\n                #:object-destroyed-p-v2\n                #:parallel-index-add\n                #:allow-destroyed-access-p\n                #:base-indexed-object-required\n                #:base-indexed-object)\n  #+cmu\n  (:shadowing-import-from :common-lisp #:subtypep #:typep)\n  (:shadowing-import-from :cl-interpol quote-meta-chars)\n  (:shadowing-import-from :bknr.indices array-index)\n  #|  (:shadow :cl #:get-internal-run-time #:get-internal-real-time #:get-universal-time #:sleep) |#\n  (:export #:*store-debug*\n           #:*store*\n           #+(or) #:with-store ; currently not exported, does not work with indices\n\n           #|\n           ;; COMMON-LISP overloads to make sure that transaction\n           ;; don't access time information\n           #:get-internal-run-time\n           #:get-internal-real-time\n           #:get-universal-time\n           #:sleep\n           |#\n\n           ;; store\n           #:store\n           #:mp-store\n           #:store-guard\n           #:store-state\n           #:open-store\n           #:close-store\n\n           ;; transaction\n           #:transaction\n           #:transaction-function-symbol\n           #:transaction-args\n           #:transaction-timestamp\n           #:current-transaction-timestamp\n           #:in-transaction-p\n           #:deftransaction\n\n           ;; store-object\n           #:persistent-class\n           #:persistent-xml-class\n           #:persistent-xml-class-importer\n           #:define-persistent-class\n           #:define-persistent-xml-class\n           #:defpersistent-class\n\n           #:store-object\n           #:store-object-store\n           #:store-object-id\n           #:store-object-last-change\n           #:store-object-touch\n           #:print-store-object\n\n           #:delete-object\n           #:destroy-object\n           #:delete-objects\n           #:cascade-delete-p\n           #:cascading-delete-object\n\n           #:initialize-transient-instance\n\n           #:store-object-with-id\n           #:store-objects-with-class\n           #:class-instances            ; convenience alias\n           #:store-objects-of-class\n           #:all-store-objects\n           #:map-store-objects\n           #:prepare-for-snapshot\n           #:find-store-object\n           #:create-object-transaction\n           #:tx-change-slot-values\n           #:change-slot-values\n           #:store-object-add-keywords\n           #:store-object-remove-keywords\n           #:store-object-set-keywords\n\n           #:convert-slot-value-while-restoring\n\n           #:persistent-change-class\n\n           #:map-class-instances\n\n           #:store-object-add-keywords\n           #:store-object-remove-keywords\n           #:store-object-set-keywords\n\n           ;; operations\n           #:execute\n           #:restore\n           #:snapshot\n           #:with-store-guard\n           #:with-transaction\n           #:store-objects\n           #:store-stats\n\n           #:blob\n           #:blob-type\n           #:blob-mime-type\n           #:blob-timestamp\n           #:blob-pathname\n           #:with-open-blob\n           #:blob-size\n           #:blob-to-stream\n           #:blob-to-file\n           #:blob-from-stream\n           #:blob-from-string\n           #:blob-from-file\n           #:blob-from-array\n           #:make-blob-from-file\n           #:rename-file-to-blob\n           #:store-blob-root-tempdir\n\n           #:find-refs\n\n           ;; Subsystems and subsystem API\n           #:store-object-subsystem\n           #:blob-subsystem\n\n           #:initialize-subsystem\n           #:snapshot-subsystem\n           #:restore-subsystem\n           #:ensure-store-current-directory\n\n           ;; JSON serialization\n           #:with-json-ignore-slots\n           #:*json-ignore-slots*)\n  (:export\n   #:make-object-snapshot\n   #:encode-slots-for-object))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/tests.lisp",
    "content": ";; hier mal unit tests reintun, im moment nur rumgeteste\n(in-package :bknr.datastore)\n\n(define-persistent-class boeses-object ()\n  ((a :read)))\n\n(define-persistent-class foobar ()\n  ((a :read :index-type unique-index :index-values all-foobars)))\n\n(deftransaction boese-tx ()\n  (format t \"BOESE BOESE!~%\"))\n\n(defmethod initialize-transient-instance ((boese boeses-object))\n  (boese-tx))\n\n(make-instance 'mp-store :directory \"/tmp/spackstore/\"\n                         :subsystems (list (make-instance 'store-object-subsystem)))\n\n(defmethod reinitialize-instance :around ((class persistent-class) &rest args)\n  (declare (ignore args))\n  (format t \"reinit~%\")\n  (call-next-method))\n\n;;; muesste fehler werfen weil transaction in initialize-transient-instance\n(make-instance 'boeses-object)\n\n(make-instance 'foobar :a 2)\n\n;;; jetzt foobar bitte nicht mehr persistent\n(defclass foobar ()\n  ((a :initarg :a :reader foobar-a)))\n\n;;; jetzt muesste foobar ID:2 nicht mehr in all-store-objects drin\n;;; sein, soll das destroyed werden?\n\n(define-persistent-class foobar ()\n  ((a :read :index-type unique-index :index-values all-foobars)))\n\n(define-persistent-class foobar ()\n  ((a :read :index-type unique-index :index-values all-foobars)\n   (b :read :initform t)))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/tutorial.lisp",
    "content": ";;; BKNR Datastore\n\n;;;# Introduction\n;;;\n;;;## The prevalence model\n;;;\n;;; The BKNR datastore is a persistence solution for Lisp data. It\n;;; uses the prevalence model, which is based on the following\n;;; assumptions:\n;;;\n;;; All data is held in RAM.\n;;;\n;;; Data can be saved to disk at once into a snapshot file and is read\n;;; from that file at startup time.\n;;;\n;;; Changes to persistent data are written to a transaction log file\n;;; immediately, which can be replayed to restore all changes that\n;;; occured since the last snapshot was saved.\n;;;\n;;; Every kind of operation that needs to be logged is called a\n;;; \"transaction\", and such transactions are made explicit in the\n;;; program code. This is different from object-oriented databases,\n;;; where the fundamental transactions are object creation, object\n;;; deletion and slot access, which are not special cases in the\n;;; prevalence model at all.\n;;;\n;;; Isolation of transactions is achieved using thread locks. In the\n;;; simplest model used by the `mp-store', transactions are serialized\n;;; using a global lock.\n;;;\n;;; The transaction system is responsible for providing replay of\n;;; committed transactions after a server crash, but not for rollback\n;;; of failed transactions in a running server, except that failing\n;;; transactions are simply not logged onto disk. To roll back\n;;; transactions at points where exceptions might be excepted, use\n;;; ordinary Lisp programming techniques involving `unwind-protect'\n;;; and similar.\n;;;\n;;;## BKNR Datastore Design\n;;;\n;;; The design of the datastore aims to make explicit the\n;;; orthogonality of object system access (unlogged) and logging of\n;;; transactions (essentially independent of the object system). The\n;;; interface between transaction system and object system is\n;;; documented and allows for the implementation of alternative object\n;;; systems. For example, the blob subsystem is using the same\n;;; interface as the object subsystem.\n;;;\n;;; Previous versions of the BKNR Datastore allowed the creation of\n;;; multiple datastores in a single LISP process. However, this\n;;; feature was seldom used, and could be very confusing while\n;;; developing applications. The new version of the BKNR Datastore\n;;; supports only a single datastore, which is referenced by the\n;;; special variable `*STORE*'.\n;;;\n;;;## BKNR Object Datastore\n;;;\n;;; In addition to the transaction layer (in the file `txn.lisp'), the\n;;; BKNR datastore provides persistent CLOS objects using the\n;;; Metaobject Protocol. It provides a metaclass with which slots can\n;;; be defined as persistent (stored on snapshot) or transient. The\n;;; metaclass also prohibits slot accesses outside transactions,\n;;; provides unique IDs for all objects, and provides standard query\n;;; functions like `STORE-OBJECTS-WITH-CLASS' and\n;;; `STORE-OBJECTS-OF-CLASS'. The object datastore can be seamlessly\n;;; combined with BKNR indices and XML import/export.\n\n;;;# Obtaining and loading BKNR Datastore\n;;;\n;;; You can obtain the current CVS sources of BKNR by following the\n;;; instructions at `http://bknr.net/blog/bknr-devel'. Add the `experimental'\n;;; directory of BKNR to your `asdf:*central-registry*', and load the\n;;; indices module by evaluating the following form:\n\n(asdf:oos 'asdf:load-op :bknr.datastore)\n\n;;; Then switch to the `bknr.datastore' package to try out the tutorial.\n\n(in-package :bknr.datastore)\n\n;;;# A transaction system example\n\n;;; The first datastore we will build is very simple. We have a\n;;; counter variable for the store, and this counter variable can be\n;;; decremented and indecremented. We want this variable to be\n;;; persistent, so decrementing and incrementing it has to be done\n;;; through transactions that will be logged by the datastore. We also\n;;; define a `:BEFORE' method for the generic function `RESTORE-STORE'\n;;; to set the counter to `0' initially. This method will be called\n;;; every time the store is created or restored from disk.\n\n(defclass tutorial-store (mp-store)\n  ((counter :initform 0 :accessor tutorial-store-counter)))\n\n(defmethod restore-store :before ((store tutorial-store) &key until)\n  (declare (ignore until))\n  (setf (tutorial-store-counter store) 0))\n\n;;; The two transactions are declared like normal functions, but using\n;;; the `DEFTRANSACTION' macro. \n\n(deftransaction incf-counter ()\n  (incf (tutorial-store-counter *store*)))\n\n(deftransaction decf-counter ()\n  (decf (tutorial-store-counter *store*)))\n\n;;; When looking at the macro-expanded form of `DEFTRANSACTION', we\n;;; see that `DEFTRANSACTION' defines two functions, a toplevel\n;;; function that creates a transaction object and calls the method\n;;; `EXECUTE' on it, and a function that contains the actual\n;;; transaction code and that will be called in the context of the\n;;; transaction, and logged to disk.\n\n(PROGN (DEFUN TX-DECF-COUNTER ()\n         (UNLESS (IN-TRANSACTION-P) (ERROR 'NOT-IN-TRANSACTION))\n         (DECF (TUTORIAL-STORE-COUNTER *STORE*)))\n       (DEFUN DECF-COUNTER ()\n         (EXECUTE (MAKE-INSTANCE 'TRANSACTION\n                                 :FUNCTION-SYMBOL 'TX-DECF-COUNTER\n                                 :TIMESTAMP (GET-UNIVERSAL-TIME)\n                                 :ARGS (LIST)))))\n\n;;; The new datastore only supports a single datastore instance per\n;;; LISP instance. When creating a `STORE' object, the `*STORE*'\n;;; special variable is modified to point to the datastore. Thus, we\n;;; can create our simple datastore by creating an object of type\n;;; `TUTORIAL-STORE'. The transaction log will be store in the\n;;; directory \"/tmp/tutorial-store\".\n\n(close-store)\n(make-instance 'tutorial-store :directory \"/tmp/tutorial-store/\"\n                               :subsystems nil)\n; Warning:  restoring #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; => #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n\n(tutorial-store-counter *store*)\n; => 0\n(incf-counter)\n; => 1\n(incf-counter)\n; => 2\n(decf-counter)\n; => 1\n\n;;; The three transactions have been logged to the transaction log in\n;;; \"/tmp/tutorial-store/\", as we can see:\n\n(with-open-file (s \"/tmp/tutorial-store/current/transaction-log\"\n                   :direction :input)\n  (file-length s))\n; => 126\n(incf-counter)\n; => 2\n(with-open-file (s \"/tmp/tutorial-store/current/transaction-log\"\n                   :direction :input)\n  (file-length s))\n; => 168\n\n;;; The transaction log is kept in a directory called \"current\", which\n;;; is where the currently active version of the snapshots and log\n;;; files are kept. When a datastore is snapshotted, the \"current\"\n;;; directory is backupped to another directory with the current date,\n;;; and snapshots are created in the new \"current\" directory. However,\n;;; we cannot snapshot our tutorial datastore, as we cannot snapshot\n;;; the persistent data (the counter value). \n\n(snapshot)\n; => Error in function (METHOD SNAPSHOT-STORE NIL (STORE)):\n; => Cannot snapshot store without subsystems...\n; => [Condition of type SIMPLE-ERROR]\n\n;;; We can close the store by using the function `CLOSE-STORE'.\n*store*\n; => #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n(close-store)\n; => NIL\n(boundp '*store*)\n; => NIL\n\n;;; The store can then be recreated, and the transaction log will be\n;;; read and executed upon restore.\n(make-instance 'tutorial-store :directory \"/tmp/tutorial-store/\"\n                               :subsystems nil)\n\n; Warning:  restoring #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; Warning:  loading transaction log\n; /tmp/tutorial-store/current/transaction-log\n; => #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n(tutorial-store-counter *store*)\n; => 2\n\n;;; The store can also be restored in a later LISP session. Make sure\n;;; that all the code necessary to the execution of the transaction\n;;; log has been loaded before restoring the datastore. A later\n;;; version of the datastore will log all the code necessary in the\n;;; datastore itself, so that code and data are synchronized.\n\n;;;## Debugging the datastore\n;;;\n;;; By setting the `*STORE-DEBUG*' special variable to `T', the\n;;; datastore prints a lot of useful warnings. For example\n\n;;; You can also restore to a certain point in time, by specifying the\n;;; `UNTIL' argument of `RESTORE-STORE'.\n\n(setf *store-debug* t)\n; => T\n(restore-store *store*)\n; restoring #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; loading transaction log /tmp/tutorial-store/current/transaction-log\n; executing transaction #<TRANSACTION 21.04.2008 07:08:22 TX-INCF-COUNTER > at timestamp 3417743302\n; executing transaction #<TRANSACTION 21.04.2008 07:08:25 TX-INCF-COUNTER > at timestamp 3417743305\n; executing transaction #<TRANSACTION 21.04.2008 07:08:26 TX-DECF-COUNTER > at timestamp 3417743306\n; executing transaction #<TRANSACTION 21.04.2008 07:08:34 TX-INCF-COUNTER > at timestamp 3417743314\n; => NIL\n(tutorial-store-counter *store*)\n; => 2\n; !! Update the timestamp below to correspond to the fist transaction executed above !!\n(restore-store *store* :until 3417743302) ...\n; restoring #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; loading transaction log /tmp/tutorial-store/current/transaction-log\n; executing transaction #<TRANSACTION 21.04.2008 07:08:22 TX-INCF-COUNTER > at timestamp 3417743302\n; creating log file backup: /tmp/tutorial-store/current/transaction-log.backup\n; truncating transaction log at position 42.\n(tutorial-store-counter *store*)\n; => 1\n\n;;;## Adding a subsystem\n\n;;; Now that we can restore the counter state by loading the\n;;; transaction log, we want to add a subsystem to be able to snapshot\n;;; the state of the counter. Thus, we won't need to execute every\n;;; single incrementing or decrementing transaction to restore our\n;;; persistent state.\n\n;;; To do this, we have to create a store-subsystem that will be able\n;;; to write the counter number to a file and to reload it on restore.\n\n(defclass counter-subsystem ()\n  ())\n\n;;; Three methods are used to interact with the subsystem.\n;;; The first method is `INITIALIZE-SUBSYSTEM', which is called after\n;;; the store has been created and restored. It is used to initialize\n;;; certain parameters of the subsystem. We won't use this method\n;;; here, as our subsystem is very simple.\n;;; The second method is `SNAPSHOT-SUBSYSTEM', which is called when\n;;; the store is snapshotted. The subsystem has to store the\n;;; persistent data it handles to a snapshot file inside the current\n;;; directory of the store. Our `COUNTER-SUBSYSTEM' writes the current\n;;; value of the counter to a file named \"counter\" in the current\n;;; directory of the store (the old directory has been renamed).\n\n(defmethod snapshot-subsystem ((store tutorial-store)\n\t\t\t       (subsystem counter-subsystem))\n  (let* ((store-dir (ensure-store-current-directory store))\n\t (counter-pathname\n\t  (make-pathname :name \"counter\" :defaults store-dir)))\n    (with-open-file (s counter-pathname :direction :output)\n      (write (tutorial-store-counter store) :stream s))))\n\n;;; Finally, the method `RESTORE-SUBSYSTEM' is called at restore time\n;;; to tell the subsystem to read back its persistent state from the\n;;; current directory of the store. Our `COUNTER-SUBSYSTEM' reads back\n;;; the counter value from the file named \"counter\". If it can't find\n;;; the file (for example if this is the first time that our datastore\n;;; is created, the file won't be there, so we issue a warning and set\n;;; the counter value to 0.\n\n(defmethod restore-subsystem ((store tutorial-store)\n\t\t\t      (subsystem counter-subsystem) &key\n\t\t\t      until)\n  (declare (ignore until))\n  (let* ((store-dir (ensure-store-current-directory store))\n\t (counter-pathname\n\t  (make-pathname :name \"counter\" :defaults store-dir)))\n    (if (probe-file counter-pathname)\n\t(with-open-file (s counter-pathname :direction :input)\n\t  (let ((counter (read s)))\n\t    (setf (tutorial-store-counter store) counter)))\n\t(progn\n\t  (warn \"Could not find store counter value, setting to 0.\")\n\t  (setf (tutorial-store-counter store) 0)))))\n\n;;; Now we can close our current store, and instantiate it anew with a\n;;; `COUNTER-SUBSYSTEM'.\n\n(close-store)\n; => NIL\n(make-instance 'tutorial-store :directory \"/tmp/tutorial-store/\"\n                               :subsystems (list (make-instance 'counter-subsystem)))\n; Warning:  restoring #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; Warning:  Could not find store counter value, setting to 0.\n; Warning:  loading transaction log\n; /tmp/tutorial-store/current/transaction-log\n; => #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n(snapshot)\n; Snapshotting subsystem #<COUNTER-SUBSYSTEM #xE65F866> of #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; Successfully snapshotted #<COUNTER-SUBSYSTEM #xE65F866> of #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; => NIL\n(restore)\n; restoring #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; Restoring the subsystem #<COUNTER-SUBSYSTEM #xE65F866> of #<TUTORIAL-STORE DIR: \"/tmp/tutorial-store/\">\n; => NUL\n\n;;;# An object store example\n\n;;; The BKNR object datastore is implemented using a special subsystem\n;;; `STORE-OBJECT-SUBSYSTEM'. Every object referenced by the store\n;;; object subsystem has a unique ID, and must be of the class\n;;; `STORE-OBJECT'. The ID counter in the store-object subsystem is\n;;; incremented on every object creation.\n;;;\n;;; All store objects have to be of the metaclass `PERSISTENT-CLASS',\n;;; which will ensure the object is referenced in the base indices of\n;;; the object datastore, and that slot access is only done inside a\n;;; transaction. The subsystem makes heavy use of BKNR indices, and\n;;; indexes object by ID and by class.\n\n;;; The ID index can be queried using the functions\n;;; `STORE-OBJECT-WITH-ID', which returns the object with the\n;;; requested ID, `ALL-STORE-OBJECTS' which returns all current store\n;;; objects, and `MAP-STORE-OBJECTS', which applies a function\n;;; iteratively to each store object. The class index can be queried\n;;; using the functions `ALL-STORE-CLASSES', which returns the names\n;;; of all the classes currently present in the datastore, and\n;;; `STORE-OBJECTS-WITH-CLASS', which returns all the objects of a\n;;; specific class (across superclasses also, so\n;;; `(STORE-OBJECTS-WITH-CLASS \\'STORE-OBJECT)' returns all the\n;;; existing store objects.\n\n;;;## Store and object creation\n\n;;; We can create an object datastore by creating a `STORE' with the\n;;; subsystem `STORE-OBJECT-SUBSYSTEM'.\n\n(close-store)\n(make-instance 'mp-store :directory \"/tmp/object-store/\"\n                         :subsystems (list\n                                      (make-instance 'store-object-subsystem)))\n\n; Warning:  restoring #<MP-STORE DIR: \"/tmp/object-store/\">\n; restoring #<MP-STORE DIR: \"/tmp/object-store/\">\n; Restoring the subsystem #<STORE-OBJECT-SUBSYSTEM #xE63F866> of #<MP-STORE DIR: \"/tmp/object-store/\">\n(all-store-objects)\n; => NIL\n\n;;; We can now create a few store objects (which is not very\n;;; interesting in itself). Store objects have to be created inside a\n;;; transaction so that the object creation is logged into the\n;;; transaction log.  If `MAKE-INSTANCE' is used outside of a\n;;; transaction, the datastore will create the object in a new,\n;;; separate transaction.\n\n(make-instance 'store-object)\n; => #<STORE-OBJECT ID: 0>\n(make-instance 'store-object)\n; => #<STORE-OBJECT ID: 1>\n(all-store-objects)\n; => (#<STORE-OBJECT ID: 0> #<STORE-OBJECT ID: 1>)\n(all-store-classes)\n; => (STORE-OBJECT)\n\n;;; Object deletion also has to be done through the transaction\n;;; `DELETE-OBJECT', which will log the deletion of the object in the\n;;; transaction log, and remove the object from all its indices.\n\n(make-instance 'store-object)\n; executing transaction #<TRANSACTION 21.04.2008 08:02:10 MAKE-INSTANCE STORE-OBJECT ID 2> at timestamp 3417746530\n; => #<STORE-OBJECT ID: 12>\n(store-object-with-id 2)\n; => #<STORE-OBJECT ID: 2>\n(delete-object (store-object-with-id 2))\n; executing transaction #<TRANSACTION 21.04.2008 08:52:14 TX-DELETE-OBJECT 2> at timestamp 3417749534\n; => T\n(store-object-with-id 2)\n; => NIL\n\n;;;## Defining persistent classes\n\n;;; A more interesting thing is to create our own persistent class,\n;;; which we will call `TUTORIAL-OBJECT'.\n\n(defclass tutorial-object (store-object)\n  ((a :initarg :a :reader tutorial-object-a))\n  (:metaclass persistent-class))\n\n;;; We can also use the `DEFINE-PERSISTENT-CLASS' to define the class\n;;; `TUTORIAL-OBJECT':\n\n(define-persistent-class tutorial-object ()\n  ((a :read)))\n\n;;; This gets macroexpanded to the following form.\n\n(DEFINE-BKNR-CLASS TUTORIAL-OBJECT\n    (STORE-OBJECT)\n  ((A :READ))\n  (:METACLASS PERSISTENT-CLASS))\n\n;;; The macro DEFINE-BKNR-CLASS is a short form of the macro DEFCLASS,\n;;; it expands to the following code. The `EVAL-WHEN' is\n;;; there to ensure timely definition of the accessor methods.\n\n(EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)\n  (DEFCLASS TUTORIAL-OBJECT\n      (STORE-OBJECT)\n    ((A :READER TUTORIAL-OBJECT-A :INITARG :A))\n    (:METACLASS PERSISTENT-CLASS)))\n\n;;; We can now create a few instance of `TUTORIAL-OBJECT':\n\n(make-instance 'tutorial-object :a 2)\n; => #<TUTORIAL-OBJECT ID: 3>\n(make-instance 'tutorial-object :a 2)\n; => #<TUTORIAL-OBJECT ID: 4>\n(make-instance 'tutorial-object :a 2)\n; => #<TUTORIAL-OBJECT ID: 5>\n\n(store-object-with-id 5)\n; => #<TUTORIAL-OBJECT ID: 5>\n\n(all-store-classes)\n; => (STORE-OBJECT TUTORIAL-OBJECT)\n\n(store-objects-with-class 'tutorial-object)\n; => (#<TUTORIAL-OBJECT ID: 3> #<TUTORIAL-OBJECT ID: 4>\n;     #<TUTORIAL-OBJECT ID: 5>)\n(store-objects-with-class 'store-object)\n; => (#<STORE-OBJECT ID: 0> #<STORE-OBJECT ID: 1>\n;  #<FOO ID: 2> #<TUTORIAL-OBJECT ID: 3>\n;  #<TUTORIAL-OBJECT ID: 4> #<TUTORIAL-OBJECT ID: 5>)\n\n;;; In order to change the slot values of persistent object, the\n;;; application needs to be in a transaction context.  This can be\n;;; done either by invoking a named transaction as above, or by\n;;; creating an anonymous transaction.  In an anonymous transaction,\n;;; all write accesses to persistent objects are logged.\n\n(define-persistent-class tutorial-object2 ()\n  ((b :update)))\n\n(make-instance 'tutorial-object2 :b 3)\n; executing transaction #<TRANSACTION 21.04.2008 08:03:27 MAKE-INSTANCE TUTORIAL-OBJECT2 ID 6 B 3> at timestamp 3417746607\n; => #<TUTORIAL-OBJECT2 ID: 6>\n(setf (slot-value (store-object-with-id 6) 'b) 4)\n; => Error\n; Attempt to set persistent slot B of #<TUTORIAL-OBJECT2 ID: 6> outside of a transaction\n(with-transaction ()\n  (setf (slot-value (store-object-with-id 6) 'b) 4))\n; => 4\n(tutorial-object2-b (store-object-with-id 6))\n; => 4\n\n;;;## Object creation and deletion protocol\n\n;;; Persistent objects have the metaclass `PERSISTENT-CLASS'.  They\n;;; are created using `MAKE-INSTANCE' just like any other CLOS object.\n;;; The datastore allocates a unique ID for the object and initializes\n;;; it using the usual CLOS protocol.  In order to perform any\n;;; additional actions during initialization of the persistent\n;;; instance, the `INITIALIZE-INSTANCE' function must be specialized\n;;; for the persistent class.  In addition, the datastore calls the\n;;; `INITIALIZE-TRANSIENT-INSTANCE' for the new object when it is\n;;; initially created as well as when it is restored from the\n;;; transaction log or snapshot file.  It may be used to initialize\n;;; transient aspects of the objects, but it must not alter the\n;;; persistent state of the object.  `INITIALIZE-TRANSIENT-INSTANCE'\n;;; does not have access to the initargs used to initially create the\n;;; persistent instance.\n;;;\n;;; We can define the following class with a transient and a\n;;; persistent slot.\n\n(define-persistent-class protocol-object ()\n  ((a :update :transient t)\n   (b :update)))\n\n;;; We can modify the slot `A' outside a transaction:\n(make-instance 'protocol-object :a 1 :b 2)\n; executing transaction #<TRANSACTION 21.04.2008 08:10:49 MAKE-INSTANCE PROTOCOL-OBJECT ID 7 A 1 B 2> at timestamp 3417747049\n; => #<PROTOCOL-OBJECT ID: 7>\n(setf (protocol-object-a (store-object-with-id 7)) 2)\n; => 2\n\n;;; However, we cannot modify the slot `B', as it is persistent and\n;;; has to be changed inside a transaction.\n\n(setf (protocol-object-b (store-object-with-id 7)) 4)\n; => Error\n; Attempt to set persistent slot B of #<PROTOCOL-OBJECT ID: 7> outside of a transaction\n\n;;; An object can be removed from the datastore using the transaction\n;;; `DELETE-OBJECT', which calls the method `DESTROY-OBJECT' on the\n;;; object. Special actions at deletion time have to be added by\n;;; overriding `DESTROY-OBJECT'. The basic action is to remove the\n;;; object from all its indices.\n\n;;;## Snapshotting an object datastore\n\n;;; We can snapshot the persistent state of all created objects by\n;;; using `SNAPSHOT'.\n(snapshot)\n; Snapshotting subsystem #<STORE-OBJECT-SUBSYSTEM #xE54991E> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; Successfully snapshotted #<STORE-OBJECT-SUBSYSTEM #xE54991E> of #<MP-STORE DIR: \"/tmp/object-store/\">\n\n;;; This will create a backup directory containing the old transaction\n;;; log, and the creation of a snapshot file in the \"current\"\n;;; directory.\n\n(directory \"/tmp/object-store/**/*.*\")\n; => (#P\"/tmp/object-store/20080421T061210/random-state\"\n;     #P\"/tmp/object-store/20080421T061210/transaction-log\"\n;     #P\"/tmp/object-store/current/random-state\"\n;     #P\"/tmp/object-store/current/store-object-subsystem-snapshot\"\n;     #P\"/tmp/object-store/current/transaction-log\")\n\n;;; The snapshot file contains all persistent objects present at\n;;; snapshotting time, and the value of their persistent\n;;; slots. Further transaction are recorded in a new transaction log.\n\n;;;## Adding indices to store objects\n\n;;; The object datastore builds upon the functionality of the BKNR\n;;; indices system. All store objects are of the metaclass\n;;; `INDEXED-CLASS', so adding indices is seamless. Indices are\n;;; transient, and are rebuilt every time the datastore is\n;;; restored. Adding an index on a transient slot or on a persistent\n;;; slot makes no difference.\n\n(define-persistent-class gorilla ()\n  ((name :read :index-type string-unique-index\n               :index-reader gorilla-with-name\n               :index-values all-gorillas)\n   (mood :read :index-type hash-index\n               :index-reader gorillas-with-mood\n               :index-keys all-gorilla-moods)))\n\n(make-instance 'gorilla :name \"lucy\" :mood :aggressive)\n; => #<GORILLA ID: 8>\n(make-instance 'gorilla :name \"john\" :mood :playful)\n; => #<GORILLA ID: 9>\n(make-instance 'gorilla :name \"peter\" :mood :playful)\n; => #<GORILLA ID: 10>\n(gorilla-with-name \"lucy\")\n; => #<GORILLA ID: 8>\n(gorillas-with-mood :playful)\n; => (#<GORILLA ID: 10> #<GORILLA ID: 9>)\n\n;;;## Adding blobs\n\n;;; A blob is a Binary Large OBject, that means it is a normal\n;;; persistent object with an associated binary data (that most of the\n;;; time is quite large). The object datastore supports storing this\n;;; large binary data outside the transaction log and the snapshot\n;;; file in order not to strain the store memory footprint too much,\n;;; and to be able to access the binary data from outside the LISP\n;;; session. This can be useful in order to copy the binary data using\n;;; the operating system calls directly. Blobs are used to store\n;;; images in the BKNR Web Framework (in fact, eboy.com contains more\n;;; than 40000 images). They have also been used to store MP3 files\n;;; for the GPN interactive DJ.\n;;;\n;;; In addition to the binary data, a blob object also holds a `TYPE'\n;;; and a `TIMESTAMP'. The type of a blob object is a keyword somehow\n;;; identifying the type of binary data it stores. For example, for\n;;; the images of the eboy datastore, we used the keywords `:JPEG',\n;;; `:PNG', `:GIF' to identity the different file formats used to\n;;; store images. The timestamp identifies the time of creation of the\n;;; blob object (this can be useful to cache binary data of blob\n;;; objects in a web server context).\n;;;\n;;; Stores are implemented in a custom subsystem, which takes as key\n;;; argument `:DIRECTORY' the name of a directory where the binary\n;;; data of the blob objects is stored as a simple file. This\n;;; directory can be further partitioned dynamically by the datastore,\n;;; when provided with the argument `:N-BLOBS-PER-DIRECTORY'. The\n;;; value of this argument is stored in the directory of the\n;;; datastore, so that a future instance of the blob subsystem is\n;;; initialised correctly.\n;;;\n;;; We can now add blob support to our existing object datastore by\n;;; adding the blob subsystem to its list of subsystems.\n\n(close-store)\n(make-instance 'mp-store :directory \"/tmp/object-store/\"\n                         :subsystems (list\n                                      (make-instance 'store-object-subsystem)\n                                      (make-instance 'blob-subsystem)))\n\n;;; The blob subsystem provides a few functions and transactions to\n;;; work with blobs. To show how to use these functions, we will\n;;; define a blob class in our example store. A photo is simply a\n;;; binary object with a name.\n\n(define-persistent-class photo (blob)\n  ((name :read)))\n\n;;; A blob can be created using the function `MAKE-BLOB-FROM-FILE',\n;;; which is a wrapper around `MAKE-INSTANCE' and the function\n;;; `BLOB-FROM-FILE'. The method `BLOB-FROM-FILE' fills the binary\n;;; data of a blob object by reading the content of a file. This\n;;; binary data is then stored in a file named after the ID of the\n;;; object in the blob root directory of the blob subsystem.\n\n(make-blob-from-file \"/tmp/bla.jpg\" 'photo :name \"foobar\"\n                                           :type :jpg)\n; => #<PHOTO ID: 11, TYPE: jpg>\n\n;;; We can work with the photo object in the same way as when we work\n;;; with a normal object. However, we can access the binary data using\n;;; the methods `BLOB-PATHNAME', which returns the pathname to the\n;;; file in the blob root that holds the binary data of the\n;;; object.\n\n(blob-pathname (store-object-with-id 11))\n; => #P\"/tmp/object-store/blob-root/11\"\n\n;;; The method `BLOB-TO-FILE' and `BLOB-TO-STREAM' write the binary\n;;; data of the object to the specified file or stream (the stream has\n;;; to be of the type `(UNSIGNED-BYTE 8)'). The macro `WITH-OPEN-BLOB'\n;;; is provided as wrapper around the `WITH-OPEN-FILE' macro.\n\n;;;## Relaxed references\n\n;;; It sometimes happens that a persistent object is deleted while it\n;;; still is referenced by another object. This can lead to problems\n;;; when snapshotting and restoring the datastore, as the referenced\n;;; object is not available anymore.\n;;;\n;;; When a slot is specified as being a relaxed object reference slot\n;;; using the slot option `:RELAXED-OBJECT-REFERENCE', a reference to\n;;; an unexistent object can be encoded during snapshot. The object\n;;; subsystem issues a warning when a reference to a non-existent\n;;; object is encoded. When a reference to a deleted object is decoded\n;;; form the snapshot file, a `NIL' value is returned if the slot from\n;;; where the object is referenced supports relaxed references. Else,\n;;; an error is thrown.\n\n(define-persistent-class relaxed-object ()\n  ((a :update :relaxed-object-reference t)))\n\n(make-instance 'relaxed-object)\n; => #<RELAXED-OBJECT ID: 12>\n(make-instance 'relaxed-object)\n; => #<RELAXED-OBJECT ID: 13>\n(with-transaction ()\n  (setf (slot-value (store-object-with-id 12) 'a) (store-object-with-id 13)))\n; => #<RELAXED-OBJECT ID: 13>\n(delete-object (store-object-with-id 13))\n; => T\n(snapshot)\n; Warning: Backup of the datastore in /tmp/object-store/20080421T064811/.\n; While executing: (:INTERNAL (SNAPSHOT-STORE (STORE))), in process worker(1750).\n; Snapshotting subsystem #<STORE-OBJECT-SUBSYSTEM #xE6069EE> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; Warning: Encoding reference to destroyed object with ID 13 from slot A of object RELAXED-OBJECT with ID 12.\n; While executing: #<STANDARD-METHOD ENCODE-OBJECT (STORE-OBJECT T)>, in process worker(1750).\n; Successfully snapshotted #<STORE-OBJECT-SUBSYSTEM #xE6069EE> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; Snapshotting subsystem #<BLOB-SUBSYSTEM #xE6069CE> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; Successfully snapshotted #<BLOB-SUBSYSTEM #xE6069CE> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; => NIL\n(restore)\n; restoring #<MP-STORE DIR: \"/tmp/object-store/\">\n; Restoring the subsystem #<STORE-OBJECT-SUBSYSTEM #xE6069EE> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; loading snapshot file /tmp/object-store/current/store-object-subsystem-snapshot\n; Warning: internal inconsistency during restore: can't find store object 13 in loaded store\n; While executing: %DECODE-STORE-OBJECT, in process worker(1754).\n; Warning: Reference to inexistent object with id 13 from unnamed container, returning NIL.\n; While executing: %DECODE-STORE-OBJECT, in process worker(1754).\n; Restoring the subsystem #<BLOB-SUBSYSTEM #xE6069CE> of #<MP-STORE DIR: \"/tmp/object-store/\">\n; loading transaction log /tmp/object-store/current/transaction-log\n; executing transaction #<ANONYMOUS-TRANSACTION 21.04.2008 08:48:48 PREPARE-FOR-SNAPSHOT NIL> at timestamp 3417749328\n(relaxed-object-a (store-object-with-id 12))\n; => NIL\n\n;;;# Store internals\n\n;;;## Binary data files\n;;;\n;;; This implementation of the BKNR datastore uses a binary encoding\n;;; of Lisp data. The encoding library is used by both the transaction\n;;; system and the object system and is mostly independent of\n;;; them. Users need not be aware of the details of this encoding,\n;;; except that (1) primitive data stored needs to be supported by the\n;;; encoding library and (2) user-defined object systems need to\n;;; register their own encoder and decoder methods to allow their\n;;; objects to be used as part of transaction arguments.\n\nFunction ENCODE (OBJECT STREAM)\n\nFunction DECODE (STREAM) =<  OBJECT\n\n;;; The `STREAM' must be specialized on `(unsigned-byte 8)'.\n\n;;; The object store subsystem uses the encoding library to encode the\n;;; persistent state of all the objects in the store. It does this by\n;;; first serializing the layout of a class (which is a list of\n;;; slot-names), then by first serializing the class and the id of\n;;; each object, and finally by serializing the slots of each\n;;; object. This two-step system is necessary to correctly serialize\n;;; circular of forward references.\n\n;;; When the snapshot is loaded, an empty instance of each object is\n;;; created, and can be referenced only using the `ID'. After each\n;;; object has been instantiated, it can be referenced by another\n;;; object. The objects are serialized in the order they have been\n;;; created.\n\n;;;## Datastore state\n\n;;; The store always is in one of the following states:\n\n;;; :closed - No store is open, no persistent operations are\n;;; possible.  It is an error, which will eventually be signaled, to\n;;; execute transactions in this state.\n\n;;; :restore - The store is currently recovering from a restart\n\n;;; :idle - The store has been opened and is prepared to execute\n;;; transactions.\n\n;;; :transaction - The store is currently executing a transaction\n\n;;; Note that all transactions are serialized and thus only one\n;;; transaction can be active at any time.\n\n;;; :snapshot - The store is currently writing a snapshot.\n\n;;;## Transactions\n\n;;; Transactions are objects of the class `TRANSACTION', and have a\n;;; slot containing the symbol of their transaction function, as well\n;;; as a list of the arguments that have to be passed to this\n;;; function. When a transaction is executed, a timestamp\n;;; of the execution time is stored in the object.\n\n;;; Transactions can be grouped by enclosing them with a\n;;; with-transaction block.  All subtransactions will be logged to the\n;;; transaction log as a group and only re-executed on restore time\n;;; when the complete group could be read from the file.\n\n(defun foo-bar ()\n  (with-transaction ()\n    (invoke-transaction-a)\n    (invoke-transaction-b)))\n\n;;; The persistent object store further supports the use of anonymous\n;;; transactions by automatically converting instance creation and\n;;; slot writes to transaction invocations which are atomically\n;;; executed upon restore time.\n\n;;;## Snapshot and restore procedures\n\n;;; When the datastore is snapshotted, the transaction layer ensures\n;;; that the store is opened, and that there are subsystems in the\n;;; store. Without subsystems, the transaction log is the only way for\n;;; the store to achieve persistence, and no snapshot can be made. The\n;;; store is then switched to read-only, and a backup directory is\n;;; created, containing the current transaction log and previous\n;;; snapshot files. This way, the older state of the datastore is not\n;;; lost. Then, each subsystem is asked to save its persistent state\n;;; by calling the method `SNAPSHOT-SUBSYSTEM'. When an error is\n;;; thrown during the snapshot of the subsystems, the backup directory\n;;; is renamed to be the current directory, and the store is\n;;; reopened.\n\n;;; When the datastore is restored, the store is switched to read\n;;; only, and each subsystem is asked to restore its persistent\n;;; state. Note that the subsystems are restored in the order in which\n;;; they are listed in the `SUBSYSTEMS' slot of the store, so that\n;;; dependent subsystems are restored last. When an error is thrown\n;;; while restoring the subsystems, the store is closed, and already\n;;; opened subsystems are closed using the method\n;;; `CLOSE-SUBSYSTEM'. After the restoring of all the subsystems, the\n;;; transaction log file is read, and each transaction recorded is\n;;; executed. This is where the `UNTIL' parameter comes into\n;;; play. Transactions that have been executed after the time of\n;;; `UNTIL' are discarded.\n\n;;;## Filesystem syncing\n\n;;; By default, the transaction log file is synced after a transaction\n;;; has been executed, so that all the data is correctly written on\n;;; disk. However, this can be a major performance stopper when\n;;; executing a big batch of transactions (for example, deleting a few\n;;; thousands objects). You can disable the mandatory syncing by\n;;; executing your transactions inside the form `WITHOUT-SYNC'.\n\n(without-sync ()\n  (execute-a-lot-of-transactions))\n\n;;;## Snapshotting and restoring the object subsystem\n\n;;; Snapshotting and restoring the object subsystem is a bit tricky,\n;;; as additional systems come into play. When the object subsystem is\n;;; snapshotted using the method `SNAPSHOT-SUBSYSTEM', a snapshot file\n;;; containing a binary dump of all the current store objects is\n;;; created. First, the layouts of the objects (the name of their\n;;; slots) is stored, and minimal information about each object is\n;;; stored in the order of their creation (the minimal information\n;;; consists of the class of the object, and its ID). After this\n;;; information has been stored for each object, the slot values of\n;;; the store objects (again in the order of their creation) are\n;;; stored in the snapshot file. These two phases are necessary to\n;;; allow the snapshotting of circular or forward-referencing\n;;; structures.\n\n;;; When the object subsystem is restored, all the indices for classes\n;;; contained in the store are cleared in order to accomodate for the\n;;; new objects. Be very careful when using class indices that are not\n;;; related to store objects. The ID counter of the store subsystem is\n;;; reset to 0, and the class-layouts are read from the snapshot\n;;; file. Then, the minimal information for each object is read, and\n;;; an \"empty version\" of each object is instantiated. Thus, the\n;;; objects can be referenced by their ID. Then, the slot values for\n;;; each object are read from the snapshot file, references are\n;;; resolved (check the section about relaxed references). Finally,\n;;; after each slot value has been set, the method\n;;; `INITIALIZE-TRANSIENT-INSTANCE' is called for each created\n;;; object.\n\n;;;## Garbage collecting blobs\n\n;;; The binary data of deleted blob objects is kept in the blob root\n;;; directory by default. If you want to purge the binary data of\n;;; deleted objects, you can use the function\n;;; `DELETE-ORPHANED-BLOB-FILES'. However, take note that you won't be\n;;; able to restore the persistent state anteriour to the deletion of\n;;; the blobs, as their binary data is not stored in the transaction\n;;; log and not backed up by the snapshot method of the blob\n;;; subsystem.\n\n;;;## Schema evolution in the datastore\n\n;;; The transaction log only stores when a transaction is called, and\n;;; with which arguments. However, it doesn't store the definition of\n;;; the transaction itself. When the transaction definition is\n;;; changed, the transaction log may be restored in a different way,\n;;; according to the changes made in the code.\n;;;\n;;; In the same way, class definition changes are not recorded in the\n;;; transaction log. When a class definition is changed (for example a\n;;; slot initform is changed), the existing instances of the class are\n;;; updated accordingly. However, when the snapshot is restored in a\n;;; future session, the objects may be different than those created at\n;;; the last restore.\n;;;\n;;; The only way to cleanly upgrade transaction definitions and class\n;;; definitions is to make a snapshot after the changes have been\n;;; made. In a future version of the datastore, we hope to store all\n;;; the application sourcecode, so that a restore to a certain point\n;;; in time does not depend on the latest version of the code. The\n;;; object subsystem warns when a class definition is changed, and\n;;; urges the developer to make a snapshot of the database. Please be\n;;; careful, this can be a pretty tricky source of bugs.\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/txn.lisp",
    "content": "(in-package :bknr.datastore)\n\n(cl-interpol:enable-interpol-syntax)\n\n(defvar *store-debug* nil\n  \"Trace and time execution of transactions\")\n\n;;; conditions\n\n(define-condition store-error (error)\n  ())\n\n(define-condition not-in-transaction (store-error)\n  ()\n  (:documentation\n   \"Signaled when an operation on persistent slots is executed outside\n   a transaction context\"))\n\n(define-condition store-not-open (store-error)\n  ()\n  (:documentation\n   \"Signaled when a transaction is executed on a store that is not\n   opened\"))\n\n(define-condition store-already-open (store-error)\n  ()\n  (:documentation\n   \"Signaled when an attempt is made to open a store with another\n   store being open\"))\n\n(define-condition invalid-store-random-state (store-error)\n  ()\n  (:documentation\n   \"Signaled when the on-disk store random state cannot be read,\n   typically because it has been written with another Lisp\"))\n\n(define-condition unsupported-lambda-list-option (store-error)\n  ((option :initarg :option :reader option))\n  (:documentation\n   \"Signaled when DEFTRANSACTION is used with an unsupported option in\n   its lambda list\"))\n\n(define-condition default-arguments-unsupported (store-error)\n  ((tx-name :initarg :tx-name :reader tx-name)\n   (argument :initarg :argument :reader argument))\n  (:report (lambda (c stream)\n             (format stream \"argument ~A defaulted in DEFTRANSACTION ~S\"\n                     (argument c) (tx-name c))))\n  (:documentation\n   \"Signaled when an argument in a DEFTRANSACTION definition has a\n   default declaration\"))\n\n(define-condition undefined-transaction (store-error)\n  ((tx-name :initarg :tx-name :reader tx-name))\n  (:report (lambda (c stream)\n             (format stream \"undefined transaction ~A in transaction log, please ensure that all the necessary code is loaded.\"\n                     (tx-name c))))\n  (:documentation\n   \"Signaled when a named transaction is loaded from the transaction\n   log and no matching function definition could be found\"))\n\n(define-condition invalid-transaction-nesting (store-error)\n  ()\n  (:documentation\n   \"Signaled when WITH-TRANSACTION forms are nested.\"))\n\n\n(define-condition no-subsystems (store-error)\n  ()\n  (:documentation\n   \"Signaled when an attempt is made to snapshot a store without subsystems\"))\n\n(define-condition invalid-environment-access (store-error)\n  ((function :initarg function))\n  (:report (lambda (e stream)\n             (with-slots (function) e\n               (format stream \"A transaction function attempted to access the function ~A which ~\n                               would make execution of the transaction non-repeatable.\"\n                       function)))))\n\n;;; Verbose progress reporting of store operations\n\n(defvar *store-verbose* t)\n\n(defun report-progress (fmt &rest args)\n  (when *store-verbose*\n    (apply #'format *trace-output* fmt args)))\n\n;;; store\n\n(defvar *store*)\n\n(defmacro with-store ((store &key) &body body)\n  `(let ((*store* ,store))\n     ,@body))\n\n(defclass store ()\n  ((directory :initarg :directory\n              :accessor store-directory)\n   (state :accessor store-state\n          :initform :closed\n          :documentation \"State of the datastore, can be either :closed, :opened or :read-only\")\n   (transaction-log-stream :accessor store-transaction-log-stream :initform nil)\n   (random-state :accessor store-random-state\n                 :initform nil)\n   (guard :reader store-guard\n          :initarg :guard)\n   (log-guard :reader store-log-guard\n              :initarg :log-guard)\n   (subsystems :reader store-subsystems\n               :initarg :subsystems)\n   (store-object-subsystem-cache :accessor %store-object-subsystem-cache\n                                 :initform nil\n                                 :documentation \"A special cache to look up the store-object-subsystem quickly from the\nlist of subsystems\")\n   (transaction-run-time :accessor store-transaction-run-time\n                         :initform 0\n                         :documentation \"The total run time of all application transaction code since last snapshot\"))\n  (:default-initargs\n   :guard #'funcall\n    :log-guard #'funcall\n    :subsystems (list (make-instance 'store-object-subsystem))))\n\n(defclass mp-store (store)\n  ()\n  (:default-initargs :guard (let ((lock (mp-make-lock)))\n                              (lambda (thunk)\n                                (mp-with-recursive-lock-held (lock)\n                                  (funcall thunk))))\n    :log-guard (let ((lock (mp-make-lock)))\n                 (lambda (thunk)\n                   (mp-with-recursive-lock-held (lock)\n                     (funcall thunk)))))\n  (:documentation\n   \"Store in which every transaction and operation is protected by a giant lock.\"))\n\n(defmethod print-object ((store store) stream)\n  (print-unreadable-object (store stream :type t)\n    (format stream \"DIR: \\\"~a\\\"\" (namestring (store-directory store)))))\n\n(defgeneric initialize-subsystem (subsystem store store-existed-p))\n(defmethod initialize-subsystem ((subsystem t) store store-existed-p)\n  (declare (ignore store store-existed-p)))\n\n(defmethod initialize-instance :before ((store store) &key (make-default t) directory)\n  (assert (and (not (null directory))\n               (not (pathname-type directory))\n               (not (pathname-name directory)))\n          () (format nil \"the store :directory argument ~S that was supplied is invalid.  A directory pathname must be specified.\" directory))\n  (when make-default\n    (restart-case\n        (when (and (boundp '*store*)\n                   *store*)\n          (error 'store-already-open))\n      (close-store ()\n        :report \"Close the opened store.\"\n        (close-store)))))\n\n(defmethod initialize-instance :after ((store store) &key (make-default t))\n  (when (stringp (store-directory store))\n    (setf (store-directory store) (pathname (store-directory store))))\n  (when make-default\n    (setf *store* store))\n  (with-store (store)\n    (let ((store-existed-p (probe-file (store-current-directory store))))\n      (ensure-store-current-directory store)\n      (dolist (subsystem (store-subsystems store))\n        (when *store-debug*\n          (report-progress \"Initializing subsystem ~A of ~A~%\" subsystem store))\n        (initialize-subsystem subsystem store store-existed-p))\n      (restore-store store))\n    (setf (store-state store) :opened)))\n\n(defmethod close-store-object ((store store))\n  (close-transaction-log-stream store)\n  (dolist (subsystem (store-subsystems store))\n    (close-subsystem store subsystem))\n  (setf (store-state store) :closed))\n\n(defun open-store (directory &key (class-name #-mp 'store #+mp 'mp-store) (subsystems (list (make-instance 'store-object-subsystem))))\n  (close-store)\n  (make-instance class-name :directory directory :subsystems subsystems))\n\n(defun close-store ()\n  (let ((store *store*))\n    (makunbound '*store*)\n    (close-store-object store)))\n\n(defmacro with-store-guard ((&optional (store '*store*)) &body body)\n  \"Execute BODY in the context of the guard of STORE.\"\n  `(funcall (store-guard ,store) #'(lambda () ,@body)))\n\n(defmacro with-log-guard ((&optional (store '*store*)) &body body)\n  \"Execute BODY in the context of the log file guard of STORE.\"\n  `(funcall (store-log-guard ,store) #'(lambda () ,@body)))\n\n(defmacro with-store-state ((state &optional (store '*store*)) &body body)\n  (let ((old-state (gensym)))\n    `(let ((,old-state (store-state ,store)))\n       (setf (store-state ,store) ,state)\n       (unwind-protect (progn ,@body)\n         (setf (store-state ,store) ,old-state)))))\n\n;; datastore pathnames\n\n(defgeneric store-current-directory (store)\n  (:documentation \"Returns the name of the current datastore directory.\"))\n\n(defmethod store-current-directory ((store store))\n  (merge-pathnames (make-pathname :directory '(:relative \"current\"))\n                   (store-directory store)))\n\n(defmethod ensure-store-current-directory ((store store))\n  (ensure-directories-exist (store-current-directory store)))\n\n(defmethod store-random-state-pathname ((store store))\n  (merge-pathnames #P\"random-state\" (store-current-directory store)))\n\n(defun initialize-store-random-state (store)\n  (with-open-file (f (store-random-state-pathname store)\n                     :direction :output :if-does-not-exist :create :if-exists :supersede)\n    (report-progress \"initializing store random state~%\")\n    (with-standard-io-syntax\n      (prin1 (setf (store-random-state store) (make-random-state t)) f))))\n\n(defmethod ensure-store-random-state ((store store))\n  (if (probe-file (store-random-state-pathname store))\n      (with-open-file (f (store-random-state-pathname store))\n        (restart-case\n            (setf (store-random-state store)\n                  (handler-case\n                      (read f)\n                    (error (e)\n                      (declare (ignore e))\n                      (error 'invalid-store-random-state))))\n          (initialize-store-random-state ()\n            :report \"Initialize the random state of the store.  Use\nthis to reinitialize the random state of the store when porting over a\nstore from another compiler. When transactions of the application\ndepend on the random state, you must snapshot your store before\nporting to the new compiler.\"\n            (initialize-store-random-state store))\n          (ignore-store-random-state ()\n            :report \"Ignore the on-disk random state of the store.\nUse this if you want to test a store with another compiler, but do not\nwant to change the store permanently.\"\n            (setf (store-random-state store) (make-random-state t)))))\n      (initialize-store-random-state store)))\n\n(defmethod update-store-random-state ((store store))\n  (with-open-file (f (store-random-state-pathname store)\n                     :direction :output :if-does-not-exist :create :if-exists :supersede)\n    (with-standard-io-syntax\n      (prin1 (store-random-state store) f))))\n\n(defgeneric store-transaction-log-pathname (store-or-directory)\n  (:documentation \"Return the pathname of the current transaction log of STORE\"))\n\n(defmethod store-transaction-log-pathname ((directory pathname))\n  (merge-pathnames \"transaction-log\" directory))\n\n(defmethod store-transaction-log-pathname ((store store))\n  (store-transaction-log-pathname (store-current-directory store)))\n\n(defgeneric store-subsystem-snapshot-pathname (store-or-directory subsystem)\n  (:documentation \"Return the pathname of the snapshot of SUBSYSTEM of STORE\"))\n\n(defmethod store-subsystem-snapshot-pathname ((directory pathname) subsystem)\n  (let ((name (string-downcase (symbol-name (class-name (class-of subsystem))))))\n    (merge-pathnames (format nil \"~a-snapshot\" name) directory)))\n\n(defmethod store-subsystem-snapshot-pathname ((store store) subsystem)\n  (store-subsystem-snapshot-pathname (store-current-directory store) subsystem))\n\n(defgeneric close-transaction-log-stream (store))\n(defgeneric store-transaction-log-stream (store))\n\n(defmethod store-transaction-log-stream :before ((store store))\n  (with-slots (transaction-log-stream) store\n    (unless transaction-log-stream\n      (setf transaction-log-stream (open (store-transaction-log-pathname store)\n                                         :element-type '(unsigned-byte 8)\n                                         :direction :output\n                                         :if-does-not-exist :create\n                                         :if-exists :append\n                                         #+openmcl :sharing #+openmcl :lock)))))\n\n(defmethod close-transaction-log-stream ((store store))\n  (with-slots (transaction-log-stream) store\n    (when transaction-log-stream\n      (close transaction-log-stream)\n      (setf transaction-log-stream nil))))\n\n;;; transaction\n\n;;; named transactions - These transactions carry a name and are logged\n;;; to the transaction log file as soon as the transaction function\n;;; returns.  They are usually defined using 'deftransaction'.\n\n(defclass transaction ()\n  ((function-symbol :initarg :function-symbol\n                    :reader transaction-function-symbol\n                    :documentation\n                    \"Symbol of the function called when executing the transaction\")\n   (args :initarg :args\n         :reader transaction-args\n         :initform nil)\n   (timestamp :initarg :timestamp\n              :accessor transaction-timestamp\n              :initform (get-universal-time))))\n\n(defvar *current-transaction* nil)\n\n(defvar *in-restore-p* nil\n  \"Dynamically set in code which is restoring. Be careful that this is\nset in threads.\n\nAt the moment this is not being used, but we want to replace the\nchecks of `(eq :restore store-state)` with *in-restore-p*, since the\nformer check is not very thread safe and error prone from background\nthreads.\")\n\n(defun in-transaction-p ()\n  (or *current-transaction*\n      ;; TODO: do we need to check in-restore-p? I think we can get\n      ;; rid of it (T1658)\n      (in-restore-p)))\n\n(defun in-restore-p ()\n  \"The plan: we keep this warning here, if that's not caused an issue\nthen we make it an error. Once that doesn't fire for a while, we then\nwe switch to just *in-restore-p*. (T1659)\"\n  (let ((response (eq :restore (store-state *store*))))\n    (unless (eql response *in-restore-p*)\n      (warn \"*in-restore-p* doesn't match :restore in ~a\" (bt:current-thread))\n      (error \"Restore is in progress is another thread\" ))\n    response))\n\n\n(defun current-transaction-timestamp ()\n  (transaction-timestamp *current-transaction*))\n\n(defun store-open-p ()\n  (not (eq :closed (store-state *store*))))\n\n;;; All transactions are executed by an 'executor', which is the store\n;;; itself or, in the case of a nested transaction, the parent\n;;; transaction.  Named transactions do not explicitly log the nested\n;;; transactions as the nesting is implicit, meaning that any repeated\n;;; execution of the transactions while rolling forward the\n;;; transaction log will automatically repeat the sequence of nested\n;;; transaction executions by the program code executed.  Contrasted\n;;; to that, an anonymous transaction has no implicit nesting, so any\n;;; nested transactions which are called are explicitly logged.\n\n(defgeneric execute-transaction (executor transaction)\n  (:documentation \"Execute TRANSACTION on EXECUTOR (which may be a store or a transaction scope).\")\n\n  (:method :before ((executor t) (transaction t))\n           (unless (store-open-p)\n             (error 'store-not-open)))\n\n  (:method ((executor transaction) transaction)\n    (execute-unlogged transaction)))\n\n(defun find-doc (body)\n  \"Given a function definition BODY, extract the docstring, if any.\nSkips over any declarations that precede the docstring.  See also CLHS\n3.4.11\"\n  (do ((body body (cdr body)))\n      ((or (not (listp (car body)))\n           (not (eq 'declare (caar body))))\n       (when (and (stringp (car body))\n                  (cdr body))\n         (car body)))))\n\n(defun insert-after-declarations (body forms-to-insert)\n  \"Given a function definition body, insert FORMS-TO-INSERT after all\ndeclarations and documentation in BODY.\"\n  (loop for rest on body\n     for form = (car rest)\n     with decls\n     with doc\n     while (or (and (listp form) (eq 'declare (car form)))\n               (and (not doc) (cdr rest) (stringp form)))\n     when (stringp form)\n     do (setf doc form)\n     do (push form decls)\n     finally (return-from insert-after-declarations (append (nreverse decls) forms-to-insert rest))))\n\n(defun make-args (args)\n  \"Parse the lambda list ARGS, returning a list that contains the\narguments in the lambda list prepared so that the list can be applied\nto a function accepting that lambda list.\n\nFor example:\n\n (MAKE-ARGS '(A B &OPTIONAL C &REST D &KEY E F)) => (A B C :E E :F F)\n\nIt is used to forward arguments to a transaction wrapper generated by\nDEFTRANSACTION to the actual transaction so that the wrapper function\ncan be declared with the lambda list of the transaction function\nitself,\"\n  (do ((args args (cdr args))\n       result\n       in-keywords-p)\n      ((not args)\n       (nreverse result))\n    (let ((arg (funcall (if (listp (car args)) #'caar #'car) args)))\n      (cond\n        ((eql #\\& (aref (symbol-name arg) 0))\n         (case arg\n           (&optional)\n           (&rest (setf args (cdr args))) ; skip argument, too\n           (&key (setf in-keywords-p t))\n           (otherwise (error 'unsupported-lambda-list-option :option arg))))\n        (t\n         (when in-keywords-p\n           (push (intern (symbol-name arg) :keyword) result))\n         (push arg result))))))\n\n(defmacro deftransaction (name (&rest args) &body body)\n  \"Define a transaction function tx-NAME and a function NAME executing\ntx-NAME in the context of the current store. The arguments to NAME\nwill be serialized to the transaction-log, and must be supported by\nthe binary encoder. tx-NAME will be called during a roll-forward to\nrepeat any effects that the transaction function had on the persistent\nstore.\"\n  (let ((name name)\n        (args args)\n        (body body))\n    (dolist (arg args)\n      (when (listp arg)\n        (error 'default-arguments-unsupported :tx-name name :argument (car arg))))\n    (let ((tx-name (intern (format nil \"TX-~A\" name)\n                           (symbol-package name))))\n      `(progn\n         (defun ,tx-name ,args\n           ,@(insert-after-declarations body\n                                        '((unless (in-transaction-p)\n                                            (error 'not-in-transaction)))))\n         (defun ,name ,args\n           ,@(let ((doc (find-doc body)))\n                  (when doc (list (format nil \"[Transaction function wrapper ~A invokes a store transaction]~%~A\" name doc))))\n           ,@(let ((rest (member '&rest args)))\n                  (when rest `((declare (ignore ,(second rest))))))\n           (execute (make-instance 'transaction\n                                   :function-symbol ',tx-name\n                                   :timestamp (get-universal-time)\n                                   :args (list ,@(make-args args)))))))))\n\n(defmethod encode-object ((object transaction) stream)\n  (%write-tag #\\T stream)\n  (%encode-symbol (transaction-function-symbol object) stream)\n  (%encode-integer (transaction-timestamp object) stream)\n  (%encode-list (transaction-args object) stream))\n\n(defmethod decode-object ((tag (eql #\\T)) stream)\n  (make-instance 'transaction\n                 :function-symbol (%decode-symbol stream)\n                 :timestamp (%decode-integer stream)\n                 :args (%decode-list stream)))\n\n(defmethod print-object ((transaction transaction) stream)\n  (print-unreadable-object (transaction stream :type t)\n    (format stream \"~A ~A ~{~A~^ ~}\"\n            (format-date-time (transaction-timestamp transaction))\n            (transaction-function-symbol transaction)\n            (transaction-args transaction))))\n\n;;; operations on transactions\n\n(defgeneric execute-unlogged (transaction)\n  (:documentation \"Invokes the transaction application code\nby calling the function named by the transactions' function-symbol.\nCalled at transaction execution and restore time within different\nenvironment.  May be overridden by specialized transaction types,\ne.g. the anonymous transaction which writes a set of transactions\nto the log file in an atomic group\"))\n\n(defmethod execute-unlogged :around ((transaction transaction))\n  \"Execute transaction unsafely, catching errors\"\n  (let (retval\n        (execution-time 0))\n    (tagbody\n     again\n       (restart-case\n           (let ((start-time (common-lisp::get-internal-run-time))\n                 (*random-state* (store-random-state *store*)))\n             (setf retval (call-next-method))\n             (setf execution-time (- (common-lisp::get-internal-run-time) start-time)))\n         (retry-transaction ()\n           :report (lambda (stream) (format stream \"Retry the transaction ~A.\" transaction))\n           (go again))\n\t (skip-transaction ()\n\t   :report (lambda (stream) (format stream \"Skip the transaction ~A.\" transaction)))))\n    (incf (store-transaction-run-time *store*) execution-time)\n    retval))\n\n(defmethod execute-unlogged :before ((transaction transaction))\n  (when *store-debug*\n    (report-progress \"executing transaction ~A at timestamp ~A~%\" transaction\n                     (transaction-timestamp transaction))))\n\n(defmethod execute-unlogged ((transaction transaction))\n  (with-store-guard ()\n    (let ((*current-transaction* transaction))\n      (apply (or (symbol-function (transaction-function-symbol transaction))\n                 (error 'undefined-transaction\n                        :tx-name (transaction-function-symbol transaction)))\n             (transaction-args transaction)))))\n\n(defun fsync (stream)\n  ;; FINISH-OUTPUT macht leider auch nichts anderes als FORCE-OUTPUT,\n  ;; dabei waere sync()-Semantik zu erwarten.\n  (finish-output stream)\n  #+cmu\n  (unix:unix-fsync (kernel::fd-stream-fd stream))\n  #+(and sbcl (not :win32))\n  (sb-posix:fsync (sb-sys:fd-stream-fd stream)))\n\n(defvar *disable-sync* nil)\n\n(defmacro without-sync (() &body body)\n  ;; Bei laengeren Importvorgaengen benoetigt das syncen des Transaktionslogs\n  ;; viel Zeit, ist aber an der Stelle nicht notwendig.\n  ` (unwind-protect\n         (let ((*disable-sync* t))\n           ,@body)\n      (with-log-guard ()\n        (fsync (store-transaction-log-stream *store*)))))\n\n(defmacro with-transaction-log ((transaction) &body body)\n  (check-type transaction symbol) ; otherwise care for multiple evaluation\n  `(with-store-guard ()\n     (when (in-transaction-p)\n       (error 'invalid-transaction-nesting))\n     (with-store-state (:transaction)\n       (prog1\n           (let ((*current-transaction* ,transaction))\n             ,@body)\n         (with-log-guard ()\n           (let ((out (store-transaction-log-stream *store*)))\n             (encode ,transaction out)\n             (unless *disable-sync*\n               (fsync out))))))))\n\n(defvar *transaction-statistics* (make-statistics-table))\n\n(defmethod execute-transaction ((store store) (transaction transaction))\n  (with-transaction-log (transaction)\n    (execute-unlogged transaction)))\n\n(defun execute (transaction)\n  \"Interface routine to execute a transaction, called through\nthe deftransaction macro and by subsystems.  Executes the\ntransaction either with the store or with the currently active\ntransaction, if any.\"\n  (execute-transaction (if (in-transaction-p)\n                           *current-transaction*\n                           *store*)\n                       transaction))\n\n\n(defun do-with-transaction (label thunk)\n  (with-store-guard ()\n    (funcall thunk)))\n\n(defmacro with-transaction ((&optional label) &body body)\n  `(do-with-transaction ,(if (symbolp label) (symbol-name label) label)\n     (lambda () ,@body)))\n\n;;; Subsystems\n\n(defgeneric snapshot-subsystem (store subsystem))\n\n(defgeneric snapshot-subsystem-async (store subsystem)\n  (:documentation \"A two-phase snapshot: the first phase runs in a lock, and then it\nreturns a lambda which is called in a background thread\nafterwards. This is only supported in bknr.cluster. The default behavior is just to call snapshot-subsystem and return an no-op lambda.\")\n  (:method ((store t) (subsystem t))\n    (snapshot-subsystem store subsystem)\n    (lambda ())))\n\n(defgeneric close-subsystem (store subsystem))\n\n(defmethod close-subsystem ((store store) (subsystem t)))\n\n(defun snapshot ()\n  (snapshot-store *store*))\n\n(defun make-backup-directory (store)\n  \"Create directory pathname to place backup for STORE in.  By\ndefault, the current time stamp is used.  If that directory already\nexists, attach a dot and an incrementing number to the directory\npathname until a non-existant directory name has been found.\"\n  (loop with timetag = (timetag)\n     for i = nil then (if i (incf i) 1)\n     for directory = (merge-pathnames (make-pathname :directory (list :relative (format nil \"~A~@[.~A~]\" timetag i)))\n                                      (store-directory store))\n     unless (probe-file directory)\n     return directory))\n\n(defmethod snapshot-store ((store store))\n  (unless (store-open-p)\n    (error 'store-not-open))\n  (when (null (store-subsystems store))\n    (error 'no-subsystems))\n  (ensure-store-current-directory store)\n  (with-store-state (:read-only store)\n    (with-store-guard ()\n      (with-log-guard ()\n        (let ((backup-directory (make-backup-directory store)))\n          (close-transaction-log-stream store)\n\n          ;; CMUCL will, dass das directory existiert, ACL nicht\n          #+(or cmu sbcl)\n          (ensure-directories-exist backup-directory)\n\n          (when *store-debug*\n            (warn \"Backup of the datastore in ~A.\"\n                  backup-directory))\n          (rename-file (store-current-directory store) backup-directory)\n          (ensure-store-current-directory store)\n\n          (let ((error t))\n            (unwind-protect\n                 (with-store-state (:snapshot)\n                   (update-store-random-state store)\n                   (dolist (subsystem (store-subsystems store))\n                     (when *store-debug*\n                       (report-progress \"Snapshotting subsystem ~A of ~A~%\" subsystem store))\n                     (funcall (snapshot-subsystem-async store subsystem))\n                     (when *store-debug*\n                       (report-progress \"Successfully snapshotted ~A of ~A~%\" subsystem store)))\n                   (setf (store-transaction-run-time store) 0)\n                   (setf error nil))\n              (when error\n                (warn \"Restoring backup ~A to current.\" backup-directory)\n                (rename-file backup-directory (store-current-directory store))))))))))\n\n(defvar *show-transactions* nil)\n\n#+ (and lispworks (or linux darwin))\n(cffi:defcfun (ffi-truncate \"truncate\") :int\n  (path :string)\n  ;; this next one is off_t, which is __darwin_off_t on Mac, and\n  ;; __kernel_off_t on Linux. On Darwin that is int64, on Linux that is long.\n  ;; (which should both be the same on 64 bit systems, but being careful anyway).\n  ;; https://opensource.apple.com/source/xnu/xnu-1504.9.17/bsd/sys/_types.h.auto.html\n  ;;\n  (pos #+darwin :int64 #+linux :long))\n\n(defun truncate-log (pathname position)\n  (let ((backup (make-pathname :type (format nil \"backup-~a-~a-~a\"\n                                             (get-universal-time)\n                                             position\n                                             (random 1000))\n                               :defaults pathname)))\n    (warn \"Truncating transaction-log file ~a to ~a\" pathname position)\n    (report-progress \"~&; creating log file backup: ~A~%\" backup)\n    (with-open-file (s pathname\n                       :element-type '(unsigned-byte 8)\n                       :direction :input)\n      (with-open-file (r backup\n                         :element-type '(unsigned-byte 8)\n                         :direction :output)\n        (copy-stream s r))))\n  (report-progress \"~&; truncating transaction log at position ~D.~%\" position)\n  #+cmu\n  (unix:unix-truncate (ext:unix-namestring pathname) position)\n  #+(and sbcl (not :win32))\n  (sb-posix:truncate (namestring pathname) position)\n  #+(or openmcl (and lispworks (or linux darwin)))\n  (ffi-truncate (namestring pathname) position)\n  #-(or cmu (and sbcl (not :win32)) openmcl (and lispworks (or linux darwin)))\n  (error \"don't know how to truncate files on this platform\"))\n\n(defun load-transaction-log (pathname &key until)\n  (let (length position txn)\n    (restart-case\n        (with-open-file (s pathname\n                           :element-type '(unsigned-byte 8)\n                           :direction :input)\n          (setf length (file-length s))\n          (loop\n             (setf position (file-position s))\n             (unless (< position length)\n               (return))\n             (setf txn (decode s))\n             (cond\n               ((and until\n                     (> (transaction-timestamp txn) until))\n                (truncate-log pathname position)\n                (return-from load-transaction-log))\n               (t\n                (when *show-transactions*\n                  (report-progress \"~&;;; ~A txn @~D: ~A~%\" (transaction-timestamp txn) position txn))\n                (let ((*txn-log-stream* s))\n                  (execute-unlogged txn))))))\n      (discard ()\n        :report (lambda (stream) (format stream \"Discard rest of the transaction log\" txn))\n        (truncate-log pathname position)))))\n\n(defgeneric restore-subsystem (store subsystem &key until))\n\n(defun restore (&optional until)\n  (restore-store *store* :until until))\n\n\n(defmethod restore-transaction-log ((store store) transaction-log &key until)\n  (when (probe-file transaction-log)\n    (report-progress \"loading transaction log ~A~%\" transaction-log)\n    (setf (store-transaction-run-time store) 0)\n    (load-transaction-log transaction-log :until until)))\n\n(defmethod restore-store ((store store) &key until)\n  (ensure-store-random-state store)\n  (report-progress \"restoring ~A~%\" store)\n  (let ((*store* store))\n    (setf (store-state store) :opened)\n    (with-store-state (:restore)\n      (let ((*in-restore-p* t))\n        (with-store-guard ()\n          (with-log-guard ()\n            (close-transaction-log-stream store)\n            (let ((transaction-log (store-transaction-log-pathname store))\n                  (error t))\n              ;; restore the subsystems\n              (unwind-protect\n                   (progn\n                     ;; Subsystems may not do any persistent changes when restoring.\n                     (dolist (subsystem (store-subsystems store))\n                       ;; check that UNTIL > snapshot date\n                       (when *store-debug*\n                         (report-progress \"Restoring the subsystem ~A of ~A~%\" subsystem store))\n                       (restore-subsystem store subsystem :until until))\n                     (restore-transaction-log store transaction-log :until until)\n                     (setf error nil))\n                (when error\n                  (dolist (subsystem (store-subsystems store))\n                    (when *store-debug*\n                      (report-progress \"Closing the subsystem ~A of ~A~%\"\n                                       subsystem store))\n                    (close-subsystem store subsystem)\n                    (setf (store-state store) :closed)))))))))))\n\n#|\n(defmacro disallow-cl-function-in-transaction (function)\n  `(defun ,function (&rest args)\n     (when (in-transaction-p)\n       (error 'invalid-environment-access :function ',function))\n     (apply (find-symbol ,(symbol-name function) :common-lisp) args)))\n\n(disallow-cl-function-in-transaction get-internal-run-time)\n(disallow-cl-function-in-transaction get-internal-real-time)\n(disallow-cl-function-in-transaction sleep)\n\n(defun get-universal-time ()\n  (if (in-transaction-p)\n      (transaction-timestamp *current-transaction*)\n      (common-lisp::get-universal-time)))\n|#\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/xml-object.lisp",
    "content": "(in-package :bknr.datastore)\n\n(defclass persistent-xml-class (bknr.impex::xml-class\n                                bknr.datastore::persistent-class)\n  ())\n\n(defmethod validate-superclass ((sub persistent-xml-class) (super bknr.impex::xml-class))\n  t)\n\n(defmethod validate-superclass ((sub persistent-xml-class)\n                                (super bknr.datastore::persistent-class))\n  t)\n\n(defclass persistent-xml-direct-slot-definition\n    (bknr.impex::xml-direct-slot-definition\n     bknr.datastore::persistent-direct-slot-definition)\n  ())\n\n(defclass persistent-xml-effective-slot-definition\n    (bknr.impex::xml-effective-slot-definition\n     bknr.datastore::persistent-effective-slot-definition)\n  ())\n\n(defmethod direct-slot-definition-class ((class persistent-xml-class) &key &allow-other-keys)\n  'persistent-xml-direct-slot-definition)\n\n(defmethod effective-slot-definition-class ((class persistent-xml-class)\n                                            &key &allow-other-keys)\n  'persistent-xml-effective-slot-definition)\n\n(defclass persistent-xml-class-importer (bknr.impex:xml-class-importer)\n  ())\n\n(defmethod bknr.impex::create-instance ((importer persistent-xml-class-importer) class-name &rest initforms)\n  (apply #'make-instance class-name initforms))\n\n(defmethod bknr.impex::set-slot-value ((handler persistent-xml-class-importer) object slot-name value)\n  (change-slot-values object slot-name value))\n\n(export '(persistent-xml-class))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/data/xml-tutorial.lisp",
    "content": "(in-package :bknr.datastore)\n\n(make-instance 'mp-store :directory \"/tmp/object-store/\"\n                         :subsystems (list\n                                      (make-instance 'store-object-subsystem)))\n\n(defvar *tutorial-dtd*\n  (cxml:parse-dtd-file \"xml-impex/tutorial.dtd\"))\n\n(defclass book (store-object)\n  ((author :initarg :author :reader book-author\n           :element \"author\")\n   (id :initarg :id :reader book-id :type integer\n       :attribute \"id\" :parser #'parse-integer)\n   (isbn :initarg :isbn :reader book-isbn\n         :attribute \"isbn\")\n   (title :initarg :title :reader book-title\n          :element \"title\"))\n  (:metaclass persistent-xml-class)\n  (:dtd *tutorial-dtd*)\n  (:element \"book\"))\n\n(bknr.impex:parse-xml-file \"xml-impex/tutorial.xml\" (list (find-class 'book))\n                           :importer-class 'persistent-xml-class-importer)\n(bknr.impex:write-to-xml (all-store-objects) :name \"books\")\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/TODO",
    "content": "- name von den class-indices soll weg. praeferierte weg ist\n(defvar *broesel* (create-index ...))\n\n(defclass ...\n  (:class-indices (:index *broesel* ...)))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/category-index.lisp",
    "content": "(in-package :bknr.indices)\n\n;;; category tree structure\n\n(defun make-node (name children)\n  (cons name children))\n\n(defun node-name (node)\n  (car node))\n\n(defun (setf node-name) (new-value node)\n  (setf (car node) new-value))\n\n(defun node-children (node)\n  (cdr node))\n\n(defun node-children-empty-p (node)\n  (null (node-children node)))\n\n(defun (setf node-children) (new-value node)\n  (setf (cdr node) new-value))\n\n(defstruct category-tree\n  (test #'eql)\n  (root-node (make-node :root nil)))\n\n(defun node-to-categories (node &optional parent-category)\n  (let ((category (append parent-category (list (node-name node)))))\n    (cons category (mapcan #'(lambda (child) (node-to-categories child category))\n\t\t\t   (node-children node)))))\n\n(defun nodes-to-categories (nodes &optional parent-category)\n  (mapcan #'(lambda (node) (node-to-categories node parent-category)) nodes))\n\n(defun tree-categories (tree &optional category)\n  (nodes-to-categories (node-children (category-tree-root-node tree)) category))\n\n(defun tree-find-node (tree category)\n  (unless (listp category)\n    (setf category (list category)))\n  (do* ((curnode (category-tree-root-node tree)\n\t\t (find catname (node-children curnode)\n\t\t       :key #'node-name\n\t\t       :test (category-tree-test tree)))\n\t(curcat category (cdr curcat))\n\t(catname (car curcat) (car curcat)))\n       ((or (null curnode)\n\t    (null curcat))\n\tcurnode)))\n\n(defun category-to-node (category)\n  (if (null category)\n      nil\n      (let ((child (category-to-node (cdr category))))\n\t    (make-node (first category)\n\t\t       (when child (list child))))))\n\n(defun tree-add-category (tree category)\n  (unless (listp category)\n    (setf category (list category)))\n  (do* ((curnode (category-tree-root-node tree))\n\t(curcat category (cdr curcat))\n\t(catname (car curcat) (car curcat)))\n       ((or (null curnode)\n\t    (null curcat))\n\ttree)\n    (let ((node (find catname (node-children curnode)\n\t\t      :key #'node-name\n\t\t      :test (category-tree-test tree))))\n      (if node\n\t  (setf curnode node)\n\t  (progn\n\t    (push (category-to-node curcat)\n\t\t  (node-children curnode))\n\t    (return-from tree-add-category tree))))))\n\n(defun tree-remove-category (tree category)\n  (unless (listp category)\n    (setf category (list category)))\n  (when category\n    (let* ((parent-category (parent-category category))\n\t   (parent-node (tree-find-node tree parent-category)))\n      (when parent-node\n\t(setf (node-children parent-node)\n\t      (remove (category-name category)\n\t\t      (node-children parent-node)\n\t\t      :key #'car\n\t\t      :test (category-tree-test tree)))\n\t(when (node-children-empty-p parent-node)\n\t  (tree-remove-category tree parent-category)))))\n  tree)\n\n(defun parent-categories (category)\n  (let (res)\n    (dotimes (i (1-  (length category)))\n      (push (butlast category (1+ i)) res))\n    res))\n\n(defun parent-category (category)\n  (butlast category 1))\n\n(defun category-name (category)\n  (car (last category)))\n\n(defun tree-find-children (tree category)\n  (nodes-to-categories (node-children (tree-find-node tree category)) category))\n\n(defun tree-find-siblings (tree category)\n  (let ((len (length category)))\n    (if (<= len 1)\n\ttree\n\t(let ((sib-cat (subseq category 0 (1- (length category)))))\n\t  (nodes-to-categories (tree-find-children tree sib-cat) sib-cat)))))\n\n;;; category index\n\n(defclass category-index (hash-index)\n  ((tree :initform (make-category-tree)\n\t :initarg :tree\n\t :accessor category-index-tree))\n  (:default-initargs :test #'equal))\n\n(defmethod initialize-instance :after ((index category-index) &key (tree-test #'eql))\n  (with-slots (tree) index\n    (setf tree (make-category-tree :test tree-test))))\n\n(defmethod index-get ((index category-index) category)\n  (let* ((tree (category-index-tree index))\n\t (hash (slot-index-hash-table index))\n\t (categories (cons category\n\t\t\t   (tree-find-children tree category))))\n    (mapcan #'(lambda (category)\n\t\t(copy-list (gethash category hash))) categories)))\n\n(defmethod index-add ((index category-index) object)\n  (unless (slot-boundp object (slot-index-slot-name index))\n    (return-from index-add))\n  (let ((key (slot-value object (slot-index-slot-name index)))\n\t(hash-table (slot-index-hash-table index))\n\t(tree (category-index-tree index)))\n    (when (and (not (slot-index-index-nil index))\n\t       (null key))\n      (return-from index-add))\n    (if (nth-value 1 (gethash key hash-table))\n        (push object (gethash key hash-table))\n        (progn\n          (tree-add-category tree key)\n          (setf (gethash key hash-table) (list object))))))\n\n(defmethod index-remove ((index category-index) object)\n  (let ((key (slot-value object (slot-index-slot-name index)))\n\t(hash-table (slot-index-hash-table index))\n\t(tree (category-index-tree index)))\n    (let ((new-value (delete-first object (gethash key hash-table))))\n      (if (null new-value)\n\t  (progn\n\t    (tree-remove-category tree key)\n\t    (remhash key hash-table))\n\t  (setf (gethash key hash-table) new-value)))))\n\n(defmethod index-keys ((index category-index))\n  (tree-categories (category-index-tree index)))\n\n(defmethod index-reinitialize :around ((new-index category-index)\n\t\t\t\t       (old-index category-index))\n  (let* ((new-index (call-next-method))\n\t (tree (category-index-tree new-index))\n\t (new-hash (slot-index-hash-table new-index)))\n    (loop for key being the hash-key of new-hash\n\t  do (tree-add-category tree key))\n    new-index))\n\n#|\n\n(defclass image ()\n  ((category :index-type category-index\n\t     :index-reader images-with-category\n\t     :index-keys all-image-categories\n\t     :index-var *image-category-index*\n\t     :initarg :category\n\t     :reader image-category))\n  (:metaclass indexed-class))\n\n(make-instance 'image :category '(:photo :stills :nature))\n(make-instance 'image :category '(:photo :naked :woman))\n(make-instance 'image :category '(:painting :abstract :cubist))\n\n(defclass track ()\n  ((category :index-type category-index\n\t     :index-initargs (:tree-test #'equal)\n\t     :index-reader tracks-with-category\n\t     :index-keys all-track-categories\n\t     :index-var *track-category-index*\n\t     :initarg :category\n\t     :reader track-category))\n  (:metaclass indexed-class))\n\n(make-instance 'track :category '(\"Rock\" \"New-Age\" \"Noise\"))\n(make-instance 'track :category '(\"Rock\" \"New-Age\" \"Techno\"))\n\t     \n\n|#\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/indexed-class.lisp",
    "content": "(in-package :bknr.indices)\n\n;; XXX slots from inherited class indices\n\n;;; XXX update-instance-for-different-class\n;;; XXX update-instance-for-redefined-class\n;;; XXX index-object als toplevel class einfuehren\n;;; XXX existierende objekte in die indexe eintrage (geht nicht :( )\n;;; ...\n;;; restarts mal richtig machen\n\n\n(defclass indexed-class (standard-class)\n  ((indices :initarg :indices :initform nil\n            :accessor indexed-class-indices)\n   (old-indices :initarg :old-indices :initform nil\n                :accessor indexed-class-old-indices)\n   (index-definitions :initarg :class-indices :initform nil\n                      :accessor indexed-class-index-definitions)))\n\n(defclass base-indexed-object ()\n  ((object-destroyed-p-v2\n    :initform nil\n    :accessor object-destroyed-p-v2\n    :documentation \"This slot will eventually replace object-destroyed-p\")))\n\n(defstruct index-holder\n  class slots name index index-subclasses)\n\n(defmethod indexed-class-index-named ((class indexed-class) index-name)\n  (let ((index-holder (find index-name (indexed-class-indices class)\n                            :key #'index-holder-name)))\n    (when index-holder\n      (index-holder-index index-holder))))\n\n(define-condition base-indexed-object-required ()\n  ()\n  (:report \"indexed-class objects must inherit BASE-INDEXED-OBJET\"))\n\n(defmethod closer-mop:compute-class-precedence-list ((class indexed-class))\n  (let ((classes (call-next-method)))\n    (unless (find (find-class 'base-indexed-object)\n                  classes)\n      (error 'base-indexed-object-required))\n    (call-next-method)))\n\n(defmethod validate-superclass ((sub indexed-class) (super standard-class))\n  t)\n\n(defclass index-direct-slot-definition (standard-direct-slot-definition)\n  ((index :initarg :index :initform nil\n          :reader index-direct-slot-definition-index\n          :documentation \"Slot keyword for an already existing index\")\n\n   (index-var :initarg :index-var :initform nil\n              :reader index-direct-slot-definition-index-var\n              :documentation \"Symbol that will be bound to the index\")\n\n   (index-type :initarg :index-type :initform nil\n               :reader index-direct-slot-definition-index-type\n               :documentation \"Slot keyword to specify the class of a new slot index\")\n   (index-initargs :initarg :index-initargs :initform nil\n                   :reader index-direct-slot-definition-index-initargs\n                   :documentation \"Arguments that will be passed to\nINDEX-CREATE when creating a new slot index\")\n\n   (index-reader :initform nil\n                 :initarg :index-reader\n                 :accessor index-direct-slot-definition-index-reader\n                 :documentation \"Name of a function that will be created to query the slot index\")\n   (index-values :initform nil\n                 :initarg :index-values\n                 :accessor index-direct-slot-definition-index-values\n                 :documentation \"Name of a function that will be\ncreated to get the values stored in the index\")\n   (index-mapvalues :initform nil\n                    :initarg :index-mapvalues\n                    :accessor index-direct-slot-definition-index-mapvalues\n                    :documentation \"Name of a function that will be\ncreated to map over the values stored in the index\")\n   (index-keys :initform nil\n               :initarg :index-keys\n               :accessor index-direct-slot-definition-index-keys\n               :documentation \"Name of a function that will be created\nto get the keys stored in the index\")\n\n   (index-subclasses :initarg :index-subclasses :initform t\n                     :accessor index-direct-slot-definition-index-subclasses\n                     :documentation \"Specify if the slot index will\nalso index subclasses of the class to which the slot belongs, default is T\")\n\n   (class :initform nil\n          :accessor index-direct-slot-definition-class)))\n\n(defclass index-effective-slot-definition (standard-effective-slot-definition)\n  ((indices :initarg :indices :initform nil\n            :accessor index-effective-slot-definition-indices)\n   #+lispworks ;; :class is not an arg for standard-effective-slot-definition\n   (%class :initarg :class)))\n\n\n(defmethod class-all-indexed-superclasses ((class indexed-class))\n  (let (result)\n    (labels ((superclasses (class)\n               (let ((classes (remove-if-not #'(lambda (class)\n                                                 (typep class 'indexed-class))\n                                             (class-direct-superclasses class))))\n                 (dolist (class classes)\n                   (unless (class-finalized-p class)\n                     (finalize-inheritance class))\n                   (push class result)\n                   (superclasses class)))))\n      (superclasses class))\n    (nreverse result)))\n\n(defmethod direct-slot-definition-class ((class indexed-class) &key index index-type\n                                         &allow-other-keys)\n  (if (or index index-type)\n      'index-direct-slot-definition\n      (call-next-method)))\n\n(defmethod effective-slot-definition-class ((class indexed-class) &rest initargs)\n  (declare (ignore initargs))\n  'index-effective-slot-definition)\n\n(defun defun-and-compile (defun)\n  (let ((function (second defun)))\n    (when function\n      (eval\n       `(let (#+lispworks (dspec:*redefinition-action* :warn))\n          ,defun))\n      (compile function))))\n\n(defun create-index-access-functions (index &key index-reader index-values\n                                              index-mapvalues index-keys index-var)\n  (defun-and-compile\n      `(defun ,index-reader (key) (index-get ,index key)))\n  (defun-and-compile\n      `(defun ,index-values ()\n         (index-values ,index)))\n  (defun-and-compile\n      `(defun ,index-mapvalues (fun)\n         (index-mapvalues ,index fun)))\n  (defun-and-compile\n      `(defun ,index-keys ()\n         (index-keys ,index)))\n  (when index-var\n    (when (boundp index-var)\n      (warn \"~A is already bound to ~A, rebinding to ~A\"\n            index-var (eval index-var) index))\n    (eval `(defparameter ,index-var ,index))))\n\n(defun make-index-object (&key index type initargs reader values mapvalues slots keys var)\n  (let ((index-object (if index\n                          (eval index)\n                          (apply #'index-create\n                                 (append (cons type (eval-initargs initargs))\n                                         (list :slots slots))))))\n    (when index-object\n      (create-index-access-functions index-object :index-reader reader\n                                                  :index-values values\n                                                  :index-mapvalues mapvalues\n                                                  :index-keys keys\n                                                  :index-var var))\n    index-object))\n\n(defmethod compute-effective-slot-definition :around ((class indexed-class)\n                                                      name direct-slots)\n  (declare (ignore name))\n  (let* ((normal-slot (call-next-method))\n         (direct-slots (remove-if-not #'(lambda (slot)\n                                          (typep slot 'index-direct-slot-definition))\n                                      direct-slots))\n         (direct-slot (first direct-slots)))\n    (when (and (typep normal-slot 'index-effective-slot-definition)\n               direct-slot\n               (or (not (index-direct-slot-definition-class direct-slot))\n                   (eql (index-direct-slot-definition-class direct-slot) class)))\n      (setf (index-direct-slot-definition-class direct-slot) class)\n      (with-slots (index index-type index-initargs index-subclasses index-keys\n                   index-reader index-values index-mapvalues index-var) direct-slot\n        (when (or index index-type)\n          (let* ((name (slot-definition-name direct-slot))\n                 (index-object (make-index-object :index index\n                                                  :type index-type\n                                                  :initargs index-initargs\n                                                  :reader index-reader\n                                                  :keys index-keys\n                                                  :values index-values\n                                                  :mapvalues index-mapvalues\n                                                  :var index-var\n                                                  :slots (list name))))\n            (when index-object\n              (push (make-index-holder :class class :slots (list name)\n                                       :name name :index index-object\n                                       :index-subclasses index-subclasses)\n                    (indexed-class-indices class)))))))\n    normal-slot))\n\n(defmethod compute-class-indices ((class indexed-class) class-indices)\n  (unless (class-finalized-p class)\n    (finalize-inheritance class))\n  (let* ((class-slots (class-slots class))\n         (slot-names (mapcar #'slot-definition-name class-slots)))\n\n    ;;; create new class indices\n    (dolist (class-index class-indices)\n      #+nil\n      (format t \"class-index ~A~%\" class-index)\n\n      (destructuring-bind (name &key index-reader index-values index-mapvalues\n                                  index-keys (index-subclasses t) index-initargs\n                                  (slots :all-slots) index-type\n                                  index) class-index\n        (when (eql slots :all-slots)\n          (setf slots slot-names))\n\n        (let ((index-object (make-index-object :index index\n                                               :type index-type\n                                               :initargs index-initargs\n                                               :reader index-reader\n                                               :values index-values\n                                               :keys index-keys\n                                               :mapvalues index-mapvalues\n                                               :slots slots)))\n          (when index-object\n            (push (make-index-holder :class class :slots slots\n                                     :name name :index index-object\n                                     :index-subclasses index-subclasses)\n                  (indexed-class-indices class))))))\n\n    #+nil\n    (format t \"superclasses ~A~%\" (class-all-indexed-superclasses class))\n\n    ;;; class indices from superclasses\n    (dolist (superclass (class-all-indexed-superclasses class))\n      (setf (indexed-class-indices class)\n            (remove-duplicates\n             (append (indexed-class-indices class)\n                     (remove nil (indexed-class-indices superclass)\n                             :key #'index-holder-index-subclasses))\n             :key #'index-holder-index)))\n\n    (dolist (holder (indexed-class-indices class))\n      (dolist (slot-name (index-holder-slots holder))\n        (let ((slot (find slot-name class-slots :key #'slot-definition-name)))\n          #+nil\n          (format t \"slot ~A indx ~A~%\" slot holder)\n          (unless (and slot\n                       (typep slot 'index-effective-slot-definition ))\n            (error \"Could not find slot ~A to store index ~A~%\" slot-name holder))\n          (pushnew (index-holder-index holder)\n                   (index-effective-slot-definition-indices slot)))))))\n\n#+(or allegro lispworks)\n(defmethod finalize-inheritance :after ((class indexed-class))\n  (compute-class-indices class (indexed-class-index-definitions class))\n  (reinitialize-class-indices class))\n\n(defun validate-index-declaration (class indices)\n  (dolist (index indices)\n    (when (and (getf (cdr index) :index)\n               (getf (cdr index) :index-type))\n      (error \"Can't have both :INDEX and :INDEX-TYPE in index ~A of ~A\" (car index) class))))\n\n(defmethod initialize-instance :before ((class indexed-class) &key class-indices)\n  (validate-index-declaration class class-indices))\n\n(defmethod reinitialize-instance :before ((class indexed-class) &key class-indices)\n  (validate-index-declaration class class-indices))\n\n;;; avoid late instantiation\n\n#+(or allegro cmu openmcl sbcl lispworks)\n(defmethod initialize-instance :after ((class indexed-class) &key)\n  (compute-class-indices class (indexed-class-index-definitions class))\n  (reinitialize-class-indices class))\n\n#+(or allegro cmu openmcl sbcl lispworks)\n(defmethod reinitialize-instance :after ((class indexed-class) &key)\n  (compute-class-indices class (indexed-class-index-definitions class))\n  (reinitialize-class-indices class))\n\n(defmethod reinitialize-class-indices\n    ((class indexed-class))\n  (let ((old-indices (remove class (indexed-class-old-indices class)\n                             :test-not #'eql :key #'index-holder-class))\n        (indices (remove class (indexed-class-indices class)\n                         :test-not #'eql :key #'index-holder-class)))\n    (when old-indices\n      (dolist (holder indices)\n        (let ((old-holder (find (index-holder-name holder) old-indices\n                                :key #'index-holder-name)))\n          (when old-holder\n            (index-reinitialize (index-holder-index holder)\n                                (index-holder-index old-holder))))))))\n\n(defmethod reinitialize-instance :before ((class indexed-class) &key)\n  (setf (indexed-class-old-indices class) (indexed-class-indices class)\n        (indexed-class-indices class) nil))\n\n;;; Hier koennen wir keine :AROUND method fuer COMPUTE-SLOTS bauen,\n;;; weil die LISP-Implementierung die Allocation von dem neuen\n;;; DESTROYED-P Slot bestimmen muss, und zwar auch im :AROUND. Das\n;;; koennen wir leider nicht uebernehmen.\n\n(defmethod compute-slots ((class indexed-class))\n  (let* ((normal-slots (call-next-method))\n         ;; old, unused slot!\n         (destroyed-p-slot #.`(make-instance\n                               'index-effective-slot-definition\n                               :name 'destroyed-p\n                               :initform nil\n                               :class class\n                               #+cmu\n                               ,@'(:readers nil :writers nil)\n                               :initfunction #'(lambda () nil))))\n    (cons destroyed-p-slot normal-slots)))\n\n(defvar *indexed-class-override* nil)\n\n(defmethod allow-destroyed-access-p ((class indexed-class)\n                                     slot-name)\n  nil)\n\n(defmethod allow-destroyed-access-p ((class indexed-class)\n                                     (slot-name (eql 'object-destroyed-p-v2)))\n  t)\n\n(defmethod slot-value-using-class :before ((class indexed-class) object\n                                           #-lispworks\n                                           (slot index-effective-slot-definition)\n                                           #+lispworks\n                                           (slot-name symbol))\n  #+lispworks\n  (assert (symbolp slot-name))\n  (let (#-lispworks (slot-name (slot-definition-name slot)))\n    (when (and (not *indexed-class-override*)\n               (not (allow-destroyed-access-p class slot-name))\n               (object-destroyed-p object))\n      (error \"Can not get slot ~A of destroyed object of class ~a.\"\n             slot-name (class-name (class-of object))))))\n\n(defmethod (setf slot-value-using-class) :before\n    (newvalue (class indexed-class) object\n     #-lispworks\n     (slot index-effective-slot-definition)\n     #+lispworks\n     (slot-name symbol))\n  (declare (ignore newvalue))\n  (let (#-lispworks (slot-name (slot-definition-name slot)))\n    (when (and (not (allow-destroyed-access-p class slot-name))\n               (object-destroyed-p object)\n               (not *indexed-class-override*))\n      (error \"Can not set slot ~A of destroyed object ~a.\"\n             slot-name (class-name (class-of object))))))\n\n(defmethod slot-makunbound-using-class :before ((class indexed-class) object\n                                                (slot slot-definition))\n  (when (and (not (allow-destroyed-access-p\n                   class\n                   (if (symbolp slot)\n                       slot\n                       (slot-definition-name slot))))\n             (object-destroyed-p object)\n             (not *indexed-class-override*))\n    (error \"Can not MAKUNBOUND slot ~A of destroyed object ~a.\"\n           (slot-definition-name slot) (class-name (class-of object)))))\n\n#+lispworks\n(defmethod slot-makunbound-using-class ((class indexed-class) object\n                                        (slot symbol))\n  (let ((slot-def (clos:find-slot-definition slot class)))\n    (unless slot-def\n      (error \"Did not find slot ~S in ~S\"\n             slot class))\n    (slot-makunbound-using-class class object\n                                 slot-def)))\n\n(defvar *in-make-instance-p* nil)\n\n(defvar *indices-remove-p* t)\n\n(defmethod make-instance :around ((class indexed-class) &key)\n  (let* ((*in-make-instance-p* t)\n         (object (call-next-method)))\n    (parallel-index-add object (mapcar #'index-holder-index (indexed-class-indices class)))\n    object))\n\n\n(defmethod (setf slot-value-using-class) :around\n    (newvalue (class indexed-class) object (slot index-effective-slot-definition))\n  (declare (ignore newvalue))\n\n  (when (allow-destroyed-access-p class (slot-definition-name slot))\n    (return-from slot-value-using-class  (call-next-method)))\n\n  (when *in-make-instance-p*\n    (return-from slot-value-using-class (call-next-method)))\n\n  (let* ((indices (index-effective-slot-definition-indices slot))\n         (slot-name (slot-definition-name slot))\n         (previous-slot-boundp (slot-boundp object slot-name))\n         (previous-slot-value (when previous-slot-boundp\n                                (slot-value object slot-name))))\n\n    #+nil\n    (format t \"indices ~A~%\" indices)\n\n    (when (and previous-slot-boundp\n               *indices-remove-p*)\n      (let ((changed-indices)\n            (error t))\n        (unwind-protect\n             (progn\n               (dolist (index indices)\n                 (index-remove index object)\n                 (push index changed-indices))\n               (setf error nil))\n          (when error\n            (dolist (index changed-indices)\n              (index-add index object))))))\n\n    (let ((result (call-next-method)))\n      #+nil\n      (format t \"set slot ~A of ~a to ~A, value is ~a~%\"\n              (slot-definition-name slot)\n              object newvalue\n              (slot-value object (slot-definition-name slot)))\n\n      (when (slot-boundp object (slot-definition-name slot))\n        (let ((error t)\n              (changed-indices nil))\n          (unwind-protect\n               (progn\n                 (dolist (index indices)\n                   (index-add index object)\n                   (push index changed-indices))\n                 (setf error nil))\n            (when error\n              (dolist (index changed-indices)\n                (index-remove index object))\n              (let ((*indices-remove-p* nil))\n                (if previous-slot-boundp\n                    (setf (slot-value object slot-name) previous-slot-value)\n                    (slot-makunbound object slot-name)))))))\n      result)))\n\n(defmethod slot-makunbound-using-class\n    ((class indexed-class) object (slot index-effective-slot-definition))\n  (let* ((slot-name (slot-definition-name slot))\n         (previous-slot-boundp (slot-boundp object slot-name))\n         (indices (index-effective-slot-definition-indices slot)))\n    (when (and previous-slot-boundp\n               *indices-remove-p*)\n      (let ((changed-indices nil)\n            (error t))\n        (unwind-protect\n             (progn\n               (dolist (index indices)\n                 (index-remove index object)\n                 (push index changed-indices))\n               (setf error nil))\n          (when error\n            (dolist (index changed-indices)\n              (index-add index object))))))\n    (call-next-method)))\n\n(defmethod clear-class-indices ((class indexed-class))\n  (map nil #'(lambda (holder) (index-clear (index-holder-index holder)))\n       (indexed-class-indices class)))\n\n(defmethod clear-slot-indices ((slot index-effective-slot-definition))\n  (map nil #'index-clear (index-effective-slot-definition-indices slot)))\n\n(defmethod class-slot-indices ((class indexed-class) slot-name)\n  (index-effective-slot-definition-indices (find slot-name (class-slots class)\n                                                 :key #'slot-definition-name)))\n\n(defmethod class-slot-index ((class indexed-class) slot-name)\n  (let ((holder (find-if #'(lambda (holder) (and (eql (index-holder-class holder) class)\n                                                 (eql (index-holder-name holder) slot-name)))\n                         (indexed-class-indices class))))\n    (when holder\n      (index-holder-index holder))))\n\n;;; destroy object mechanic\n\n(defgeneric destroy-object-with-class (class object))\n(defgeneric destroy-object (object)\n  (:documentation \"Destroy the given object, and delete it from the indices.\"))\n\n(defmethod destroy-object-with-class ((class standard-class) object)\n  (declare (ignore object))\n  (error \"Can not destroy an object that is not indexed.\"))\n\n(defmethod destroy-object-with-class ((class indexed-class) object)\n  (dolist (index (mapcar #'index-holder-index (indexed-class-indices class)))\n    (index-remove index object))\n  (setf (object-destroyed-p-v2 object) t))\n\n(defmethod destroy-object ((object t))\n  (destroy-object-with-class (class-of object) object))\n\n(defmethod object-destroyed-p ((object t))\n  nil)\n\n(defmethod object-destroyed-p ((object null))\n  nil)\n\n(defun parallel-index-add (object indices)\n  (dolist (index indices)\n    (index-add index object)))\n\n(defmethod object-destroyed-p ((object base-indexed-object))\n  (and\n   #+sbcl ;; Tests break without this on SBCL, very weird, might be related to allocate-instance.\n   (slot-boundp object 'object-destroyed-p-v2)\n   (handler-case\n       (object-destroyed-p-v2 object)\n     (unbound-slot ()\n       ;; This typically doesn't happen on Lispworks, only on SBCL.\n       ;; But the cost of working with the condition is quite high\n       ;; (with allocation), so we want to set it to NIL as soon as\n       ;; possible.\n       (setf (object-destroyed-p-v2 object) nil)\n       nil))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/indices-tests.lisp",
    "content": "(defpackage :bknr.indices.tests\n  (:use :cl :bknr.indices\n        :fiveam-matchers\n        :fiveam)\n  (:import-from #:bknr.indices\n                #:index-effective-slot-definition\n                #:object-destroyed-p-v2\n                #:base-indexed-object)\n  (:import-from #:bknr.datastore\n                #:transient-slot-p))\n(in-package :bknr.indices.tests)\n\n(def-suite* :bknr.indices)\n\n(defvar *test-index* nil)\n\n\n(def-fixture index-test-class (index &optional (output *debug-io*))\n  (declare (ignore output))\n  (let ((*test-index* index))\n    (index-clear *test-index*)\n    (unwind-protect\n         (&body)\n      (index-clear *test-index*))))\n\n(defmacro define-index-test (name (&rest index-initargs) &rest body)\n  `(test ,name\n     (with-fixture index-test-class ((index-create ,@index-initargs))\n       ,@body)))\n\n(defmacro test-equal (&rest args)\n  `(is (equal ,@args)))\n\n(defmacro test-assert (&rest args)\n  `(is-true ,@args))\n\n(defmacro test-condition (expr (%quote condition))\n  (declare (ignore %quote))\n  `(signals ,condition\n     ,expr))\n\n(defclass indexed-object ()\n  ((a :initarg :a :reader indexed-object-a)\n   (b :initarg :b :reader indexed-object-b)\n   (c :initarg :c :reader indexed-object-c)\n   (d :initarg :d :reader indexed-object-d)))\n\n(defun test-index-add ()\n  (let ((a-obj (make-instance 'indexed-object :a 3 :b 3 :c 3 :d 3)))\n    (index-add *test-index* a-obj)\n    (test-equal (index-get *test-index* 3) a-obj)\n    (index-add *test-index* a-obj)\n    (test-equal (index-get *test-index* 3) a-obj)\n    (test-equal (index-values *test-index*) (list a-obj))))\n\n(defun test-index-existing ()\n  (let ((a (make-instance 'indexed-object :a 3 :b 3 :c 3 :d 3))\n\t(b (make-instance 'indexed-object :a 3 :b 3 :c 3 :d 3)))\n    (index-add *test-index* a)\n    (test-condition (index-add *test-index* b)\n\t\t    'index-existing-error)))\n\n(defun test-index-remove ()\n  (let ((a (make-instance 'indexed-object :a 3 :b 3 :c 3 :d 3)))\n    (index-add *test-index* a)\n    (test-equal (index-get *test-index* 3) a)\n    (index-remove *test-index* a)\n    (test-equal (index-get *test-index* 3) nil)\n    (index-add *test-index* a)\n    (test-equal (index-get *test-index* 3) a)\n    (index-remove *test-index* a)\n    (test-equal (index-get *test-index* 3) nil)\n    (let ((b (make-instance 'indexed-object :a 4 :b 4 :c 4 :d 4)))\n      (index-add *test-index* b)\n      (test-equal (index-get *test-index* 4) b)\n      (index-remove *test-index* b)\n      (test-equal (index-get *test-index* 4) nil))))\n\n(define-index-test unique-index-create ('unique-index :slots '(a))\n  (test-assert *test-index*))\n\n(define-index-test unique-index-add ('unique-index :slots '(a))\n  (test-index-add))\n\n(define-index-test unique-index-index-existing ('unique-index :slots '(a))\n  (test-index-existing))\n\n(define-index-test unique-index-remove ('unique-index :slots '(a))\n  (test-index-remove))\n\n(define-index-test unique-index-add2 ('unique-index :slots '(b))\n  (test-index-add))\n\n(define-index-test unique-index-index-existing2 ('unique-index :slots '(b))\n  (test-index-existing))\n\n(define-index-test unique-index-remove2 ('unique-index :slots '(b))\n  (test-index-remove))\n\n(define-index-test unique-index-reinitialize ('unique-index :slots '(a))\n  (let ((a-obj (make-instance 'indexed-object :a 3 :b 3 :c 3 :d 3)))\n    (index-add *test-index* a-obj)\n    (test-equal (index-get *test-index* 3) a-obj)\n    (let ((new-index (index-create 'unique-index :slots '(a))))\n      (index-reinitialize new-index *test-index*)\n      (test-equal (index-get *test-index* 3) a-obj)\n      (test-equal (index-values *test-index*)\n\t\t  (index-values new-index)))\n    (let ((new-index (index-create 'unique-index :slots '(a) :test #'eq)))\n      (index-reinitialize new-index *test-index*)\n      (test-equal (index-get *test-index* 3) a-obj)\n      (test-equal (index-values *test-index*)\n\t\t  (index-values new-index)))))\n\n(define-index-test unique-index-stress ('unique-index :slots '(a))\n  (dotimes (i 10000)\n    (index-add *test-index* (make-instance 'indexed-object :a i :b i :c i :d i)))\n  (test-equal (length (index-values *test-index*)) 10000)\n  (index-mapvalues *test-index* #'(lambda (obj) (index-remove *test-index* obj)))\n  (test-equal (index-values *test-index*) nil))\n\n(def-fixture indexed-class-test-class (classes)\n  (flet ((clear ()\n           (map nil #'clear-class-indices (mapcar #'find-class classes))))\n   (unwind-protect\n        (progn\n          (clear)\n          (&body))\n     (clear))))\n\n(defmacro define-indexed-class-test (name (&rest classes) &rest body)\n  `(test ,name\n     (with-fixture indexed-class-test-class (',classes)\n       ,@body)))\n\n(defclass gorilla (base-indexed-object)\n  ((name :initarg :name :reader gorilla-name\n\t :index-type unique-index :index-initargs (:test #'equal)\n\t :index-reader gorilla-with-name :index-values all-gorillas)\n   (description :initarg :description :accessor gorilla-description\n\t\t:index-type hash-index\n\t\t:index-reader gorillas-with-description))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test gorilla-create (gorilla)\n  (let ((john (make-instance 'gorilla :name \"John\" :description :aggressive))\n\t(lucy (make-instance 'gorilla :name \"Lucy\" :description :aggressive))\n\t(robert (make-instance 'gorilla :name \"Robert\" :description :playful)))\n    (test-equal (length (all-gorillas)) 3)\n    (is (member john (all-gorillas)))\n    (is (member lucy (all-gorillas)))\n    (test-assert (member robert (all-gorillas)))\n\n    (test-equal (length (gorillas-with-description :aggressive)) 2)\n    (test-assert (member lucy (gorillas-with-description :aggressive)))\n    (test-assert (member john (gorillas-with-description :aggressive)))\n\n    (test-equal (gorillas-with-description :playful) (list robert))))\n\n(define-indexed-class-test gorilla-setf (gorilla)\n  (let ((john (make-instance 'gorilla :name \"John\" :description :aggressive))\n\t(lucy (make-instance 'gorilla :name \"Lucy\" :description :aggressive))\n\t(robert (make-instance 'gorilla :name \"Robert\" :description :playful)))\n    (test-equal (length (all-gorillas)) 3)\n    (test-assert (member john (all-gorillas)))\n    (test-assert (member lucy (all-gorillas)))\n    (test-assert (member robert (all-gorillas)))\n\n    (test-equal (length (gorillas-with-description :aggressive)) 2)\n    (test-assert (member lucy (gorillas-with-description :aggressive)))\n    (test-assert (member john (gorillas-with-description :aggressive)))\n\n    (test-equal (gorillas-with-description :playful) (list robert))\n\n    (setf (gorilla-description lucy) :playful)\n\n    (test-equal (length (gorillas-with-description :playful)) 2)\n    (test-assert (member lucy (gorillas-with-description :playful)))\n    (test-assert (member robert (gorillas-with-description :playful)))\n\n    (test-equal (gorillas-with-description :aggressive) (list john))\n\n    (test-condition (setf (slot-value lucy 'name) \"Robert\") 'index-existing-error)))\n\n(define-indexed-class-test gorilla-destroy (gorilla)\n  (let ((john (make-instance 'gorilla :name \"John\" :description :aggressive))\n\t(lucy (make-instance 'gorilla :name \"Lucy\" :description :aggressive))\n\t(robert (make-instance 'gorilla :name \"Robert\" :description :playful)))\n    (test-equal (length (all-gorillas)) 3)\n    (test-assert (member john (all-gorillas)))\n    (test-assert (member lucy (all-gorillas)))\n    (test-assert (member robert (all-gorillas)))\n\n    (test-equal (length (gorillas-with-description :aggressive)) 2)\n    (test-assert (member lucy (gorillas-with-description :aggressive)))\n    (test-assert (member john (gorillas-with-description :aggressive)))\n\n    (test-equal (gorillas-with-description :playful) (list robert))\n\n    (destroy-object lucy)\n    (test-equal (gorillas-with-description :playful) (list robert))\n    (test-equal (gorillas-with-description :aggressive) (list john))\n    (test-equal (length (all-gorillas)) 2)\n    (test-assert (member john (all-gorillas)))\n    (test-assert (member robert (all-gorillas)))\n\n    (test-condition (gorilla-name lucy) 'error)))\n\n(defclass gorilla2 (base-indexed-object)\n  ((name :initarg :name :reader gorilla2-name)\n   (description :initarg :description :reader gorilla2-description)\n   (x :initarg :x :reader gorilla2-x)\n   (y :initarg :y :reader gorilla2-y))\n  (:metaclass indexed-class)\n  (:class-indices (coords :index-type array-index\n\t\t\t  :slots (x y)\n\t\t\t  :index-reader gorilla2-with-coords\n\t\t\t  :index-initargs (:dimensions '(256 256)))))\n\n(define-indexed-class-test gorilla2-create (gorilla2)\n  (let ((john (make-instance 'gorilla2 :name \"John\" :description :aggressive :x 5 :y 8))\n\t(lucy (make-instance 'gorilla2 :name \"Lucy\" :description :aggressive :x 6 :y 9))\n\t(robert (make-instance 'gorilla2 :name \"Robert\" :description :playful :x 7 :y 10)))\n    (test-equal john (gorilla2-with-coords '(5 8)))\n    (test-equal lucy (gorilla2-with-coords '(6 9)))\n    (test-equal robert (gorilla2-with-coords '(7 10)))\n    (with-slots (x y) lucy\n      (setf x 0 y 0))\n    (test-equal lucy (gorilla2-with-coords '(0 0)))\n\n    (test-condition (with-slots (x y) lucy\n\t\t      (setf x 7 y 10)) 'index-existing-error)\n\n    (destroy-object john)\n    (test-equal (gorilla2-with-coords '(5 8)) nil)))\n\n(defclass test-slot (base-indexed-object)\n  ((a :initarg :a :index-type unique-index\n      :reader test-slot-a\n      :index-reader test-slot-with-a\n      :index-values all-test-slots)\n   (b :initarg :b :index-type unique-index\n      :index-reader test-slot-with-b\n      :index-subclasses nil\n      :index-values all-test-slots-bs))\n  (:metaclass indexed-class))\n\n(defclass test-slot2 (test-slot)\n  ((b :initarg :b :index-type unique-index\n      :index-reader test-slot2-with-b\n      :index-subclasses nil\n      :index-mapvalues map-test-slot2s\n      :index-values all-test-slot2s-bs))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test test-slot-indices (test-slot test-slot2)\n  (let ((t1 (make-instance 'test-slot :a 1 :b 2))\n\t(t2 (make-instance 'test-slot :a 2 :b 3))\n\t(t3 (make-instance 'test-slot2 :a 3 :b 4))\n\t(t4 (make-instance 'test-slot2 :a 4 :b 2))\n\t(t5 (make-instance 'test-slot2 :a 5 :b 9)))\n    (test-equal (length (all-test-slots)) 5)\n    (test-assert (subsetp (list t1 t2 t3 t4 t5) (all-test-slots)))\n    (test-equal (length (all-test-slots-bs)) 2)\n    (test-assert (subsetp (list t1 t2) (all-test-slots-bs)))\n    (test-equal (test-slot-with-a 2) t2)\n    (test-equal (length (all-test-slot2s-bs)) 3)\n    (test-assert (subsetp (list t3 t4 t5) (all-test-slot2s-bs)))))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *existing-unique-index*\n    (index-create 'unique-index :slots '(a))))\n\n(defclass test-slot3 (base-indexed-object)\n  ((a :initarg :a :index *existing-unique-index*))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test existing-unique-index (test-slot3)\n  (let ((t1 (make-instance 'test-slot3 :a 3))\n\t(t2 (make-instance 'test-slot3 :a 4)))\n    (test-equal (index-get *existing-unique-index* 4) t2)\n    (test-equal (length (index-values *existing-unique-index*)) 2)\n    (test-assert (subsetp (list t1 t2) (index-values *existing-unique-index*)))))\n\n(defclass test-slot4 (test-slot)\n  ((a :initarg :a :index-type unique-index :index-reader test-slot4-wit-a\n      :index-values all-test-slot4s))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test test-slot-indices2 (test-slot test-slot2 test-slot4)\n  (let ((t1 (make-instance 'test-slot :a 1 :b 2))\n\t(t2 (make-instance 'test-slot :a 2 :b 3))\n\t(t3 (make-instance 'test-slot2 :a 3 :b 4))\n\t(t4 (make-instance 'test-slot2 :a 4 :b 2))\n\t(t5 (make-instance 'test-slot2 :a 5 :b 9)))\n    (test-equal (length (all-test-slots)) 5)\n    (test-assert (subsetp (list t1 t2 t3 t4 t5) (all-test-slots)))\n    (test-equal (length (all-test-slots-bs)) 2)\n    (test-assert (subsetp (list t1 t2) (all-test-slots-bs)))\n    (test-equal (test-slot-with-a 2) t2)\n    (test-equal (length (all-test-slot2s-bs)) 3)\n    (test-assert (subsetp (list t3 t4 t5) (all-test-slot2s-bs)))\n    (let ((t6 (make-instance 'test-slot4 :a 6 :b 9)))\n      (test-equal (length (all-test-slots)) 6)\n      (test-assert (subsetp (list t1 t2 t3 t4 t5 t6) (all-test-slots)))\n      (test-equal (all-test-slot4s) (list t6)))))\n\n(defclass test-class (base-indexed-object)\n  ((x :initarg :x :reader test-class-x)\n   (y :initarg :y :reader test-class-y)\n   (z :initarg :z :reader test-class-z))\n  (:metaclass indexed-class)\n  (:class-indices (2d-coords :index-type array-index :slots (x y)\n\t\t\t     :index-initargs (:dimensions '(256 256))\n\t\t\t     :index-reader test-with-2d-coords)\n\t\t  (3d-coords :index-type array-index :slots (x y z)\n\t\t\t     :index-reader test-with-3d-coords\n\t\t\t     :index-initargs (:dimensions '(256 256 2)))))\n\n(define-indexed-class-test test-class-indices (test-class)\n  (let ((t1 (make-instance 'test-class :x 1 :y 1 :z 0))\n\t(t2 (make-instance 'test-class :x 1 :y 3 :z 1))\n\t(t3 (make-instance 'test-class :x 1 :y 2 :z 0)))\n    (test-equal (test-with-3d-coords '(1 1 0)) t1)\n    (test-equal (test-with-2d-coords '(1 1)) t1)\n    (test-equal (test-with-2d-coords '(1 2)) t3)))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *class-index*\n    (index-create 'class-index)))\n\n(defclass base-object (base-indexed-object)\n  ()\n  (:metaclass indexed-class)\n  (:class-indices (class :index *class-index*\n\t\t\t :slots nil\n\t\t\t :index-reader objects-of-class\n\t\t\t :index-values all-objects\n\t\t\t :index-subclasses t\n\t\t\t :index-keys all-class-names)\n\t\t  (classes :index-type class-index\n\t\t\t   :index-initargs (:index-superclasses t)\n\t\t\t   :slots nil\n\t\t\t   :index-subclasses t\n\t\t\t   :index-reader objects-with-class)))\n\n(defclass child1 (base-object)\n  ()\n  (:metaclass indexed-class))\n\n(defclass child2 (base-object)\n  ((a :initarg :a))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test test-class-index (child1 child2 base-object)\n  (let ((c1 (make-instance 'child1))\n        (c2 (make-instance 'child1))\n        (c3 (make-instance 'child1))\n        (c4 (make-instance 'child2))\n        (c5 (make-instance 'child2)))\n    (assert-that (all-objects)\n                 (has-length 5))\n    (is (subsetp (list c1 c2 c3 c4 c5) (all-objects)))\n    (assert-that (objects-with-class 'child1)\n                 (has-length 3))\n    (is (subsetp (list c1 c2 c3) (objects-with-class 'child1)))\n    (assert-that (objects-with-class 'child2)\n                 (has-length 2))\n    (is (subsetp (list c4 c5) (objects-with-class 'child2)))\n    (assert-that (objects-with-class 'base-object)\n                 (has-length 5))\n    (is (subsetp (list c1 c2 c3 c4 c5) (objects-with-class 'base-object)))\n    (assert-that (objects-of-class 'child1)\n                 (has-length 3))\n    (is (subsetp (list c1 c2 c3) (objects-of-class 'child1)))\n    (assert-that (objects-of-class 'child2)\n                 (has-length  2))\n    (is (subsetp (list c4 c5) (objects-of-class 'child2)))\n    (is-false (objects-of-class 'base-object))\n    (assert-that (all-class-names)\n                (has-length 2))\n    (is (member 'child1 (all-class-names)))\n    (is (member 'child2 (all-class-names)))))\n\n(defclass var-test (base-indexed-object)\n  ((blorg :index-type string-unique-index\n\t  :initarg :blorg\n\t  :index-var *var-test-blorg-index*))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test test-index-var (var-test)\n  (let ((c1 (make-instance  'var-test :blorg \"blorg\")))\n    (test-equal c1 (index-get *var-test-blorg-index* \"blorg\"))))\n\n(defclass category-image (base-indexed-object)\n  ((category :index-type category-index\n\t     :index-reader images-with-category\n\t     :index-keys all-image-categories\n\t     :initarg :category\n\t     :reader image-category))\n  (:metaclass indexed-class))\n\n(defclass category-track (base-indexed-object)\n  ((category :index-type category-index\n\t     :index-initargs (:tree-test #'equal)\n\t     :index-reader tracks-with-category\n\t     :index-keys all-track-categories\n\t     :initarg :category\n\t     :reader track-category))\n  (:metaclass indexed-class))\n\n(define-indexed-class-test test-category-index (category-image category-track)\n  (let ((i1 (make-instance 'category-image :category '(:photo :stills :nature)))\n\t(i2 (make-instance 'category-image :category '(:photo :stills :nature)))\n\t(i3 (make-instance 'category-image :category '(:photo :naked :woman)))\n\t(i4 (make-instance 'category-image :category '(:photo :naked :man)))\n\t(i5 (make-instance 'category-image :category '(:painting :abstract :cubist))))\n    (test-equal 4 (length (images-with-category '(:photo))))\n    (test-equal 2 (length (images-with-category '(:photo :stills))))\n    (test-equal 2 (length (images-with-category '(:photo :stills :nature))))\n    (test-equal 2 (length (images-with-category '(:photo :naked))))\n    (test-equal 1 (length (images-with-category '(:photo :naked :woman))))\n    (test-equal 1 (length (images-with-category '(:photo :naked :man))))\n    (test-equal 0 (length (images-with-category '(:foobar))))\n    (test-equal (list i4) (images-with-category '(:photo :naked :man)))\n    (test-equal (list i4) (images-with-category '(:photo :naked :man)))\n    (test-equal (list i5) (images-with-category '(:painting)))\n    (test-equal (list i5) (images-with-category '(:painting :abstract)))\n    (test-equal (list i5) (images-with-category '(:painting :abstract :cubist)))\n\n    (test-assert (subsetp (list i1 i2 i3 i4)\n\t\t\t  (images-with-category '(:photo))))\n    (test-assert (subsetp (list i1 i2)\n\t\t\t  (images-with-category '(:photo :stills :nature))))\n    (test-assert (subsetp '((:photo) (:photo :stills) (:photo :stills :nature)\n\t\t\t    (:photo :naked) (:photo :naked :man) (:photo :naked :woman)\n\t\t\t    (:painting) (:painting :abstract) (:painting :abstract :cubist))\n\t\t\t  (all-image-categories) :test #'equal))\n\n    (destroy-object i5)\n    (test-equal 0 (length (images-with-category '(:painting))))\n    (test-equal 0 (length (images-with-category '(:painting :abstract))))\n    (test-equal 0 (length (images-with-category '(:painting :abstract :cubist))))\n\n    (test-assert (subsetp '((:photo) (:photo :stills) (:photo :stills :nature)\n\t\t\t    (:photo :naked) (:photo :naked :man) (:photo :naked :woman))\n\t\t\t  (all-image-categories) :test #'equal))\n\n    (destroy-object i4)\n    (test-equal 3 (length (images-with-category '(:photo))))\n    (test-equal 1 (length (images-with-category '(:photo :naked))))\n    (test-equal (list i3) (images-with-category '(:photo :naked)))\n\n    (test-assert (subsetp '((:photo) (:photo :stills) (:photo :stills :nature)\n\t\t\t    (:photo :naked) (:photo :naked :woman))\n\t\t\t  (all-image-categories) :test #'equal))))\n\n(define-indexed-class-test test-track-category-index (category-track)\n  (let ((t1 (make-instance 'category-track :category '(\"Rock\" \"Metal\" \"Trash\")))\n\t(t2 (make-instance 'category-track :category '(\"Rock\" \"Metal\" \"Death\")))\n\t(t3 (make-instance 'category-track :category '(\"Rock\" \"Metal\" \"Heavy\")))\n\t(t4 (make-instance 'category-track :category '(\"Reggae\" \"Dub\"))))\n    (test-equal 3 (length (tracks-with-category '(\"Rock\"))))\n    (test-equal 3 (length (tracks-with-category '(\"Rock\" \"Metal\"))))\n    (test-assert (subsetp (list t1 t2 t3)\n\t\t\t  (tracks-with-category '(\"Rock\"))))\n    (test-assert (subsetp (list t1)\n\t\t\t  (tracks-with-category '(\"Rock\" \"Metal\" \"Trash\"))))\n    (test-assert (subsetp (tracks-with-category '(\"Rock\"))\n\t\t\t  (tracks-with-category '(\"Rock\" \"Metal\"))))\n    (test-equal 1 (length (tracks-with-category '(\"Reggae\"))))\n    (test-assert (subsetp '((\"Rock\") (\"Rock\" \"Metal\") (\"Rock\" \"Metal\" \"Death\")\n\t\t\t    (\"Rock\" \"Metal\" \"Trash\") (\"Rock\" \"Metal\" \"Heavy\")\n\t\t\t    (\"Reggae\") (\"Reggae\" \"Dub\"))\n\t\t\t  (all-track-categories) :test #'equal))\n    (destroy-object t1)\n    (test-equal 2 (length (tracks-with-category '(\"Rock\"))))\n    (test-equal 2 (length (tracks-with-category '(\"Rock\" \"Metal\"))))\n    (test-assert (subsetp '((\"Rock\") (\"Rock\" \"Metal\") (\"Rock\" \"Metal\" \"Death\")\n\t\t\t    (\"Rock\" \"Metal\" \"Heavy\") (\"Reggae\") (\"Reggae\" \"Dub\"))\n\t\t\t  (all-track-categories) :test #'equal))\n    (test-equal nil (tracks-with-category '(\"Rock\" \"Metal\" \"Trash\")))))\n\n(defclass another-indexed-object (gorilla)\n  (dfdfdf)\n  (:metaclass indexed-class))\n\n\n(define-indexed-class-test test-class-has-proper-type ()\n  (let ((obj (make-instance 'another-indexed-object)))\n    (is (typep obj 'bknr.indices::base-indexed-object)))\n  (let ((obj (make-instance 'gorilla)))\n    (is (typep obj 'bknr.indices::base-indexed-object))))\n\n(defmethod special-fn ((x t))\n  :one)\n\n(defmethod special-fn ((x base-indexed-object))\n  :two)\n\n(define-indexed-class-test ensure-that-we-can-specialize-on-base-indexed-object\n    ()\n    (is (eql :two (special-fn (make-instance 'another-indexed-object)))))\n\n(define-indexed-class-test simple-slot-access-for-new-destroyed-slot ()\n  (let ((obj (make-instance 'another-indexed-object)))\n    (is (eql nil (slot-value obj 'object-destroyed-p-v2)))\n    (is (eql nil (object-destroyed-p-v2 obj)))\n    (setf (slot-value obj 'object-destroyed-p-v2) t)\n    (is (eql t (slot-value obj 'object-destroyed-p-v2)))\n    (is (eql t (object-destroyed-p-v2 obj)))))\n\n(define-indexed-class-test new-object-destroyed-p-v2-has-the-right-slot-type ()\n  (let ((class-slots (closer-mop:class-slots (find-class 'another-indexed-object))))\n    (let ((new-slot (loop for slotd in class-slots\n                          if (eql 'object-destroyed-p-v2 (closer-mop:slot-definition-name slotd))\n                            return slotd))\n          (old-slot (loop for slotd in class-slots\n                          if (eql 'dfdfdf (closer-mop:slot-definition-name slotd))\n                            return slotd)))\n      (is (typep new-slot 'closer-mop:standard-slot-definition))\n      (is (typep old-slot 'index-effective-slot-definition)))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/indices.lisp",
    "content": ";;; XXX protokoll erweitern, das es auch eine funktion gibt, die die\n;;; keys aus der klasse extrahiert, dann koennte man viel\n;;; zusammenfaktorn\n\n(in-package :bknr.indices)\n\n;;;;;;;;;;;;;;;;;;;;\n;;; Slot-bound index\n\n;;; A slot-bound index is based on a hash-table. Objects are added to\n;;; the hash-table using the slot-value of the specified slot as key.\n\n(defclass slot-index ()\n  ((hash-table :initarg :hash-table :accessor slot-index-hash-table\n\t       :documentation \"The internal hash table used to index\nobjects.\")\n   (slot-name :initarg :slot-name :reader slot-index-slot-name\n\t      :documentation \"The value of the slot with name\nSLOT-NAME is used as a key to the internal hash-table.\")\n   (index-nil :initarg :index-nil :reader slot-index-index-nil\n\t      :initform nil\n\t      :documentation \"If T, NIL is used as a valid slot value, else slots with NIL value are treated as unbound slots.\")))\n\n(defmethod initialize-instance :after ((index slot-index) &key (test #'eql) slot-name slots index-nil)\n  (unless slots\n    (setf slots (list slot-name)))\n  (unless (= (length slots) 1)\n    (error \"Exactly one slot name in :SLOTS initarg required to create a SLOT-INDEX\"))\n  (with-slots (hash-table slot-name) index\n    (setf hash-table (make-hash-table :test test #+sbcl #+sbcl :synchronized t)\n\t  slot-name (first slots)\n\t  (slot-value index 'index-nil) index-nil)))\n\n(defmethod print-object ((object slot-index) stream)\n  (print-unreadable-object (object stream :type t :identity t)\n    (format stream \"SLOT: ~S SIZE: ~D\"\n\t    (slot-index-slot-name object)\n\t    (hash-table-count (slot-index-hash-table object)))))\n\n(defmethod index-get ((index slot-index) key)\n  (gethash key (slot-index-hash-table index)))\n\n(defmethod index-remove :around ((index slot-index) object)\n  (let ((slot-name (slot-index-slot-name index)))\n    (if (slot-boundp object slot-name)\n\t(call-next-method)\n\t(ignore-errors ;; guard against access to unbound slots in print method\n\t  (warn \"Ignoring request to remove object ~A with unbound slot ~A.\"\n\t\tobject slot-name)))))\n\n(defmethod index-remove ((index slot-index) object)\n  (remhash (slot-value object (slot-index-slot-name index)) (slot-index-hash-table index)))\n\n(defmethod index-keys ((index slot-index))\n  (loop for key being the hash-keys of (slot-index-hash-table index)\n\tcollect key))\n\n(defmethod index-values ((index slot-index))\n  (loop for value being the hash-values of (slot-index-hash-table index)\n\tcollect value))\n\n(defmethod index-mapvalues ((index slot-index) fun)\n  (maphash (lambda (key val) (declare (ignore key)) (funcall fun val))\n\t   (slot-index-hash-table index)))\n\n(defmethod index-clear ((index slot-index))\n  (with-slots (hash-table) index\n    (setf hash-table (make-hash-table :test (hash-table-test hash-table) #+sbcl #+sbcl :synchronized t))))\n\n(defmethod index-reinitialize ((new-index slot-index)\n\t\t\t       (old-index slot-index))\n  \"Reinitialize the slot-bound index from the old index by copying the\ninternal hash-table if the hash-table test is the same, or by\niterating over the values of the old-table and reentering them into\nthe new hash-table.\"\n  (let ((new-hash (slot-index-hash-table new-index))\n\t(old-hash (slot-index-hash-table old-index)))\n    (if (eql (hash-table-test new-hash)\n\t     (hash-table-test old-hash))\n\t(setf (slot-index-hash-table new-index)\n\t      old-hash)\n\t(loop for key being the hash-keys of old-hash using (hash-value value)\n\t      do (setf (gethash key new-hash) value)))\n    new-index))\n\n(defclass unique-index (slot-index)\n  ())\n\n(defmethod index-add ((index unique-index) object)\n  \"Add an object using the value of the specified slot as key. When\nthe hash-table entry already contains a value, an error is signalled.\"\n  (unless (slot-boundp object (slot-index-slot-name index))\n    (return-from index-add))\n  (let* ((key (slot-value object (slot-index-slot-name index)))\n\t (hash-table (slot-index-hash-table index)))\n    (when (and (not (slot-index-index-nil index))\n\t       (null key))\n      (return-from index-add))\n    (multiple-value-bind (value presentp)\n\t(gethash key hash-table)\n      (when (and presentp\n\t\t (not (eql value object)))\n\t(error (make-condition 'index-existing-error\n\t\t\t       :index index :key key :value value)))\n      (setf (gethash key hash-table) object))))\n\n\n(defclass string-unique-index (unique-index)\n  ())\n\n(defmethod initialize-instance :after ((index string-unique-index) &key (test #'equal))\n  (with-slots (hash-table) index\n    (setf hash-table (make-hash-table :test test #+sbcl #+sbcl :synchronized t))))\n\n(defmethod index-add :around ((index string-unique-index) object)\n  (unless (slot-boundp object (slot-index-slot-name index))\n    (return-from index-add))\n  (let* ((key (slot-value object (slot-index-slot-name index))))\n    (unless (and (not (slot-index-index-nil index))\n\t\t (string-equal key \"\"))\n      (call-next-method))))\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;; Slot-bound keyword index\n\n;;; A slot-bound index storing multiple objects under one key.\n\n(defclass hash-index (slot-index)\n  ())\n\n(defmethod index-add ((index hash-index) object)\n  (unless (slot-boundp object (slot-index-slot-name index))\n    (return-from index-add))\n  (let ((key (slot-value object (slot-index-slot-name index)))\n\t(hash-table (slot-index-hash-table index)))\n    (when (and (not (slot-index-index-nil index))\n\t       (null key))\n      (return-from index-add))\n    (if (nth-value 1 (gethash key hash-table))\n        (push object (gethash key hash-table))\n        (setf (gethash key hash-table) (list object)))))\n\n(defmethod index-remove ((index hash-index) object)\n  (let ((key (slot-value object (slot-index-slot-name index)))\n\t(hash-table (slot-index-hash-table index)))\n    (let ((new-value (delete-first object (gethash key hash-table))))\n      (if (null new-value)\n\t  (remhash key hash-table)\n\t  (setf (gethash key hash-table) new-value)))))\n\n(defmethod index-values ((index hash-index))\n  (loop for value being the hash-values of (slot-index-hash-table index)\n\tappending value))\n\n(defmethod index-mapvalues ((index hash-index) fun)\n  (maphash (lambda (key val) (declare (ignore key))\n\t\t   (dolist (obj val) (funcall fun obj)))\n\t   (slot-index-hash-table index)))\n\n;;; Index objects by their class\n\n(defclass class-index (hash-index)\n  ((index-superclasses :initarg :index-superclasses :initform nil\n\t\t       :reader class-index-index-superclasses)))\n\n(defmethod initialize-instance :after ((index class-index) &key index-superclasses)\n  (setf (slot-value index 'index-superclasses)\n\tindex-superclasses))\n\n(defmethod index-add ((index class-index) object)\n  (labels ((index-object (object class)\n\t     (let ((key (class-name class))\n\t\t   (hash-table (slot-index-hash-table index)))\n               (if (nth-value 1 (gethash key hash-table))\n                   (push object (gethash key hash-table))\n                   (setf (gethash key hash-table) (list object))))))\n\n    (if (class-index-index-superclasses index)\n\t(dolist (class (cons (class-of object)\n\t\t\t     (class-all-indexed-superclasses (class-of object))))\n\t  (index-object object class))\n\t(index-object object (class-of object)))))\n\n(defmethod index-remove ((index class-index) object)\n    (flet ((remove-object (object class)\n\t     (let ((key (class-name class))\n\t\t   (hash-table (slot-index-hash-table index)))\n\t       (let ((new-value (delete-first object (gethash key hash-table))))\n\t\t (if (null new-value)\n\t\t     (remhash key hash-table)\n\t\t     (setf (gethash key hash-table) new-value))))))\n      (if (class-index-index-superclasses index)\n\t  (dolist (class (cons (class-of object)\n\t\t\t       (class-all-indexed-superclasses (class-of object))))\n\t  (remove-object object class))\n\t  (remove-object object (class-of object)))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;; Slot-bound keyword list index\n\n;;; A keyword index, where the slot-value is a list of keys.\n\n(defclass hash-list-index (slot-index)\n  ())\n\n(defmethod index-add ((index hash-list-index) object)\n  (unless (slot-boundp object (slot-index-slot-name index))\n    (return-from index-add))\n  (let ((keys (slot-value object (slot-index-slot-name index)))\n\t(hash-table (slot-index-hash-table index)))\n    (dolist (key keys)\n      (if (nth-value 1 (gethash key hash-table))\n          (push object (gethash key hash-table))\n          (setf (gethash key hash-table) (list object))))))\n\n(defmethod index-remove ((index hash-list-index) object)\n  (let ((keys (slot-value object (slot-index-slot-name index)))\n\t(hash-table (slot-index-hash-table index)))\n    (dolist (key keys)\n      (let ((new-value (delete-first object (gethash key hash-table))))\n\t(if (null new-value)\n\t    (remhash key hash-table)\n\t    (setf (gethash key hash-table) new-value))))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;; Multiple-slots array index\n\n(defclass array-index ()\n  ((slot-names :initarg :slot-names :accessor array-index-slot-names\n\t       :initform nil)\n   (array :initarg :array :accessor array-index-array)))\n\n(defmethod initialize-instance :after ((index array-index) &key slots dimensions)\n  (setf (array-index-array index) (make-array dimensions :initial-element nil)\n\t(array-index-slot-names index) slots))\n\n(defmethod print-object ((object array-index) stream)\n  (print-unreadable-object (object stream :type t :identity t)\n    (format stream \"SLOTS: ~S (~S)\"\n\t    (array-index-slot-names object)\n\t    (array-dimensions (array-index-array object)))))\n\n(defmethod index-add ((index array-index) object)\n  (let* ((slot-values\n\t  (mapcar #'(lambda (slot-name)\n\t\t      ;; return when not all slots are set\n\t\t      ;;\n\t\t      ;; - 18.10.04 not needed because of\n\t\t      ;; make-instance around method\n\t\t      ;;\n\t\t      ;; - 19.10.04 in fact this is needed because\n\t\t      ;; when adding a class index, the existing\n\t\t      ;; instances are not reinitailized using\n\t\t      ;; make-instnace, so we have to catch this...\n\t\t      (unless (slot-boundp object slot-name)\n\t\t\t\t    (return-from index-add nil))\n\t\t\t\t  (slot-value object slot-name))\n\t\t\t      (array-index-slot-names index)))\n\t (array (array-index-array index))\n\t (dimensions (array-dimensions array)))\n    (loop for slot-value in slot-values\n\t  for dimension in dimensions\n\t  when (>= slot-value dimension)\n\t  do (error \"Could not add ~a to array-index ~a because the coordinates ~a are out of bound.\" object index slot-values))\n    (let ((value (apply #'aref array slot-values)))\n      (when (and value\n\t\t (not (eql value object)))\n\t(error (make-condition 'index-existing-error\n\t\t\t       :index index :key slot-values :value value))))\n    (setf (apply #'aref array slot-values)\n\t  object)))\n\n(defmethod index-get ((index array-index) coords)\n  (apply #'aref (array-index-array index) coords))\n\n(defmethod index-remove ((index array-index) object)\n  (let* ((slot-values (mapcar #'(lambda (slot-name)\n\t\t\t\t ;;; return when not all slots are set\n\t\t\t\t  (unless (slot-boundp object slot-name)\n\t\t\t\t    (return-from index-remove nil))\n\t\t\t\t  (slot-value object slot-name))\n\t\t\t      (array-index-slot-names index)))\n\t (array (array-index-array index))\n\t (dimensions (array-dimensions array)))\n    (loop for slot-value in slot-values\n\t  for dimension in dimensions\n\t  when (>= slot-value dimension)\n\t  do (error \"Could not remove ~a from array-index ~a because the coordinates ~a are out of bound.\" object index slot-values))\n    (setf (apply #'aref array slot-values) nil)))\n\n(defmethod index-keys ((index array-index))\n  (error \"An ARRAY-INDEX has no keys.\"))\n\n(defmethod index-values ((index array-index))\n  (error \"An ARRAY-INDEX cannot enumerate its values.\"))\n\n(defmethod index-mapvalues ((index array-index) (fun function))\n  (error \"An ARRAY-INDEX cannot enumerate its values.\"))\n\n(defmethod index-reinitialize ((new-index array-index) (old-index array-index))\n  (when (equal (array-dimensions (array-index-array new-index))\n\t       (array-dimensions (array-index-array old-index)))\n    (setf (array-index-array new-index)\n\t  (array-index-array old-index))\n    new-index))\n\n(defmethod index-clear ((index array-index))\n  (with-slots (array) index\n    (setf array (make-array (array-dimensions array) :initial-element nil))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;; Ordered skip list index\n\n(defclass skip-list-index ()\n  ((skip-list :initarg :skip-list\n\t      :accessor skip-list-index-skip-list)\n   (slot-name :initarg :slot-name\n\t      :accessor skip-list-index-slot-name)\n   (index-nil :initarg :index-nil :initform nil\n\t      :accessor skip-list-index-index-nil)))\n\n(defmethod initialize-instance :after ((index skip-list-index) &key slots index-nil)\n  (unless (<= (length slots) 1)\n    (error \"Can not create slot-index with more than one slot.\"))\n  (with-slots (skip-list slot-name) index\n    (setf skip-list (make-instance 'skip-list)\n\t  slot-name (first slots)\n\t  (slot-value index 'index-nil) index-nil)))\n\n(defmethod print-object ((object skip-list-index) stream)\n  (print-unreadable-object (object stream :type t :identity t)\n    (format stream \"SLOT: ~S SIZE: ~D\"\n\t    (skip-list-index-slot-name object)\n\t    (skip-list-length (skip-list-index-skip-list object)))))\n\n(defmethod index-add ((index skip-list-index) object)\n  \"Add an object using the value of the specified slot as key. When\nthe hash-table entry already contains a value, an error is thrown.\"\n  (unless (slot-boundp object (skip-list-index-slot-name index))\n    (return-from index-add))\n  (let* ((key (slot-value object (skip-list-index-slot-name index)))\n\t (skip-list (skip-list-index-skip-list index)))\n    (when (and (not (skip-list-index-index-nil index))\n\t       (null key))\n      (return-from index-add))\n    (let ((value (skip-list-get key skip-list)))\n      (when (and value\n\t\t (not (eql value object)))\n\t(error (make-condition 'index-existing-error\n\t\t\t       :index index :key key :value value)))\n      (setf (skip-list-get key skip-list) object))))\n\n(defmethod index-get ((index skip-list-index) key)\n  (skip-list-get key (skip-list-index-skip-list index)))\n\n(defmethod index-remove ((index skip-list-index) object)\n  (skip-list-delete (slot-value object (skip-list-index-slot-name index))\n\t\t    (skip-list-index-skip-list index)))\n\n(defmethod index-keys ((index skip-list-index))\n  (let ((keys))\n    (map-skip-list #'(lambda (key val) (declare (ignore val))\n\t\t\t     (push key keys))\n\t\t   (skip-list-index-skip-list index))\n    (nreverse keys)))\n\n(defmethod index-values ((index skip-list-index))\n  (let ((vals))\n    (map-skip-list #'(lambda (key val) (declare (ignore key))\n\t\t\t     (push val vals))\n\t\t   (skip-list-index-skip-list index))\n    (nreverse vals)))\n\n(defmethod cursor-next ((slc skip-list-cursor) &optional eoc)\n  (declare (ignore eoc))\n  (sl-cursor-next slc))\n\n(defmethod cursor-prev ((slc skip-list-cursor) &optional eoc)\n  (declare (ignore eoc))\n  (sl-cursor-prev slc))\n\n(defmethod index-values-cursor ((index skip-list-index))\n  (skip-list-values-cursor (skip-list-index-skip-list index)))\n\n(defmethod index-keys-cursor ((index skip-list-index))\n  (skip-list-keys-cursor (skip-list-index-skip-list index)))\n\n(defmethod index-mapvalues ((index skip-list-index) fun)\n  (map-skip-list #'(lambda (key val) (declare (ignore key))\n\t\t\t   (funcall fun val))\n\t\t (skip-list-index-skip-list index)))\n\n(defmethod index-clear ((index skip-list-index))\n  (with-slots (skip-list) index\n    (setf skip-list (make-instance 'skip-list))))\n\n(defmethod index-reinitialize ((new-index skip-list-index)\n\t\t\t       (old-index skip-list-index))\n  \"Reinitialize the slot-bound index from the old index by copying the\ninternal skip-list if the skip-list order function is the same, or by\niterating over the values of the old-list and reentering them into\nthe new skip-list.\"\n  (let ((new-list (skip-list-index-skip-list new-index))\n\t(old-list (skip-list-index-skip-list old-index)))\n    (setf (skip-list-index-skip-list new-list) old-list)\n    new-index))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;\n;;; class skip list index\n\n(defclass class-skip-index ()\n  ((index-superclasses :initarg :index-superclasses :initform nil\n\t\t               :reader class-skip-index-index-superclasses\n                       :documentation \"When non-nil, index objects under their superclasses as well as their direct class\")\n   (slot-name :initarg :slot-name\n\t          :accessor class-skip-index-slot-name\n              :documentation \"The name of the slot whose value is used as the key in the skip lists\")\n   (hash-table :accessor class-skip-index-hash-table\n               :documentation \"Hash table mapping class names to skip-list instances containing objects of that class\")))\n\n(defmethod initialize-instance :after ((index class-skip-index)\n                                       &key (test #'eql) slots index-superclasses)\n  (unless (<= (length slots) 1)\n    (error \"Can not create slot-index with more than one slot.\"))\n  (with-slots (hash-table slot-name) index\n    (setf hash-table (make-hash-table :test test #+sbcl #+sbcl :synchronized t)\n\t  slot-name (first slots)\n\t  (slot-value index 'index-superclasses) index-superclasses)))\n\n(defmethod should-index-objects-for-class-p (class)\n  \"Set this to false if you don't care\nabout (bknr.datastore:class-instances 'class-name). This takes the\nactual class (not the name) as the argument.\"\n  t)\n\n(defmethod index-add ((index class-skip-index) object)\n  (let ((id-key\n          ;; Avoid calling slot-value for each skip-list. We're very\n          ;; likely to need it at least once for store-object.\n          (slot-value object (class-skip-index-slot-name index))))\n    (labels ((index-object (object class)\n               (let ((key (class-name class))\n                     (hash-table (class-skip-index-hash-table index)))\n                 (cond\n                   ((should-index-objects-for-class-p class)\n                    (multiple-value-bind (skip-list presentp)\n                       (gethash key hash-table)\n                     (if presentp\n                         (setf (skip-list-get id-key skip-list) object)\n                         (let ((skip-list\n                                 (setf (gethash key hash-table)\n                                       (make-instance 'skip-list))))\n                           (setf (skip-list-get id-key skip-list) object)))))\n                  (t\n                   ;; We still need the keys to discover all the class\n                   ;; types to clear indices later.\n                   (unless (gethash key hash-table)\n                     (setf (gethash key hash-table) (make-instance 'skip-list))))))))\n\n     (if (class-skip-index-index-superclasses index)\n\t     (dolist (class (cons (class-of object)\n\t\t\t                  (class-all-indexed-superclasses (class-of object))))\n\t       (index-object object class))\n\t     (index-object object (class-of object))))))\n\n(defmethod index-remove ((index class-skip-index) object)\n  (flet ((remove-object (object class)\n           (let* ((key (class-name class))\n                  (hash-table (class-skip-index-hash-table index))\n                  (id-key (slot-value object (class-skip-index-slot-name index)))\n                  (skip-list (gethash key hash-table)))\n             (when skip-list\n               (skip-list-remove id-key skip-list)))))\n    (if (class-skip-index-index-superclasses index)\n\t    (dolist (class (cons (class-of object)\n\t\t\t                 (class-all-indexed-superclasses (class-of object))))\n\t      (remove-object object class))\n\t    (remove-object object (class-of object)))))\n\n(defmethod index-get ((index class-skip-index) key)\n  (let* ((hash-table (class-skip-index-hash-table index))\n\t     (skip-list (gethash key hash-table)))\n    (when skip-list\n      (let ((res))\n\t    (map-skip-list #'(lambda (key val) (declare (ignore key))\n\t\t\t\t           (push val res))\n\t\t               skip-list)\n\t    (nreverse res)))))\n\n(defmethod index-reinitialize ((new-index class-skip-index)\n\t\t\t                   (old-index class-skip-index))\n  (let* ((new-hash (class-skip-index-hash-table new-index))\n\t     (old-hash (class-skip-index-hash-table old-index)))\n    (if (eql (hash-table-test old-hash)\n\t         (hash-table-test new-hash))\n\t    (setf (class-skip-index-hash-table new-index) old-hash)\n\t    (maphash #'(lambda (key value)\n\t\t             (setf (gethash key new-hash) value))\n\t\t         old-hash))\n    new-index))\n\n(defmethod index-clear ((index class-skip-index))\n  (with-slots (hash-table) index\n    (setf hash-table (make-hash-table :test (hash-table-test hash-table) #+sbcl #+sbcl :synchronized t))))\n\n(defmethod index-keys ((index class-skip-index))\n  (loop for key being the hash-keys of (class-skip-index-hash-table index)\n\tcollect key))\n\n;;; XXX class-skip-index set 2 times for every store-object\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.indices\n  (:use :closer-common-lisp\n        :bknr.utils\n\t:bknr.skip-list)\n  (:export #:index-add\n\t   #:index-get\n\t   #:index-remove\n\t   #:index-keys\n\t   #:index-values\n\t   #:index-reinitialize\n\t   #:index-clear\n\t   #:index-create\n\t   #:index-mapvalues\n\t   #:index-existing-error\n\n\t   #:initialize-indexed-instance\n\t   #:*initialized-indexed-instance*\n\t   #:*indexed-class-override*\n\n\t   #:slot-index\n\t   #:unique-index\n\t   #:string-unique-index\n\t   #:hash-index\n\t   #:hash-list-index\n\t   #:array-index\n\t   #:class-index\n\t   #:skip-list-index\n\t   #:class-skip-index\n\n\t   #:clear-class-indices\n\t   #:clear-slot-indices\n\t   #:class-slot-indices\n\t   #:class-slot-index\n\n\t   #:indexed-class\n\t   #:indexed-class-index-named\n\t   #:index-direct-slot-definition\n\t   #:index-effective-slot-definition\n\t   #:destroy-object\n\t   #:object-destroyed-p\n\n\t   #:category-index\n\t   #:tree-find-children\n\t   #:tree-find-siblings\n\t   #:parent-category\n\t   #:parent-categories\n\t   #:tree-categories\n       #:should-index-objects-for-class-p))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/protocol.lisp",
    "content": "(in-package :bknr.indices)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;; CLOS Protocol for index objects\n\n;;; A CLOS cursor is a CLOS class that complies to the following\n;;; protocol (note that a cursor doesn't have to support both\n;;; CURSOR-NEXT and CURSOR-PREVIOUS):\n\n(defgeneric cursor-next (cursor &optional eoc)\n  (:documentation \"Return the current value and advance the\ncursor. Returns EOC if the cursor is at its end.\"))\n\n(defgeneric cursor-previous (cursor &optional eoc)\n  (:documentation \"Return the current value and advance the\ncursor. Returns EOC if the cursor is at its end.\"))\n\n;;; A CLOS index is a CLOS class that complies to the following\n;;; protocol (note that an index doesn't have to support all the\n;;; protocol methods):\n\n(defgeneric index-add (index object)\n  (:documentation \"Add OBJECT to the INDEX. Throws an ERROR if a\nproblem happened while inserting OBJECT.\"))\n\n(defgeneric index-get (index key)\n  (:documentation \"Get the object (or the objects) stored under the index-key KEY.\"))\n\n(defgeneric index-remove (index object)\n  (:documentation \"Remove OBJECT from the INDEX.\"))\n\n(defgeneric index-keys (index)\n  (:documentation \"Returns all the KEYs of the INDEX.\"))\n\n(defgeneric index-values (index)\n  (:documentation \"Returns all the objects (or object lists) stored in\nINDEX.\"))\n\n(defgeneric index-values-cursor (index)\n  (:documentation \"Returns a cursor that walks all the objects stored in INDEX.\"))\n\n(defgeneric index-keys-cursor (index)\n  (:documentation \"Returns a cursor that walks all the keys stored in INDEX.\"))\n\n(defgeneric index-mapvalues (index fun)\n  (:documentation \"Apply FUN to every object in the INDEX.\"))\n\n(defgeneric index-reinitialize (new-index old-index)\n  (:documentation \"Called when the definition of an index is changed.\"))\n\n(defgeneric index-clear (index)\n  (:documentation \"Remove all indexed objects from the index.\"))\n\n(defun index-create (class-name &rest initargs)\n  \"Instantiate the index object with class CLASS-NAME with INITARGS.\"\n  (apply #'make-instance class-name initargs))\n\n;;; Adding to an index can throw the following errors:\n\n(define-condition index-existing-error (error)\n  ((index :initarg :index :reader condition-index)\n   (key :initarg :key :reader condition-key)\n   (value :initarg :value :reader condition-value)))\n\n(defmethod print-object ((obj index-existing-error) stream)\n  (print-unreadable-object (obj stream :type t)\n    (format stream \"INDEX: ~A KEY: ~S VALUE: ~S\"\n\t    (condition-index obj) (condition-key obj) (condition-value obj))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/indices/tutorial.lisp",
    "content": ";;; Slot indexes for Common Lisp\n\n;;;# Introduction\n;;;\n;;; In the framework we built as a backend for the `eboy.com' website,\n;;; we built a prevalence layer that could handle CLOS objects. These\n;;; CLOS objects all had an `ID', and could be indexed over other\n;;; slots as well. For example, we heavily used \"keyword indices\" that\n;;; could give back all objects that had a certain keyword stored in a\n;;; slot. However, the slot indices were built into a very big\n;;; `define-persistent-class' macro, and could not easily be extended\n;;; or used on their own.\n;;;\n;;; This index layer is now built using the metaobject protocol, and\n;;; has a CLOS method protocol to access indices, so that new index\n;;; classes can easily be added.\n;;;\n;;; This tutorial will show you how to create CLOS classes with slot\n;;; indices, class indices, and how to create custom indices and use\n;;; them with your classes.\n\n\n;;;# Obtaining and loading BKNR slot indices\n;;;\n;;; You can obtain the current CVS sources of BKNR by following the\n;;; instructions at `http://bknr.net/'. Add the `src' directory of\n;;; BKNR to your `asdf:*central-registry*', and load the indices\n;;; module by evaluating the following form:\n\n(asdf:oos 'asdf:load-op :bknr.indices)\n\n;;; Then switch to the `bknr.indices' package to try out the tutorial.\n\n(in-package :bknr.indices)\n\n;;;# A simple indexed class\n;;;\n;;;## A standard non-indexed class\n;;;\n;;; We begin by defining a simple class called GORILLA. Gorillas have\n;;; a name, and a description keyword.\n\n(defclass gorilla ()\n  ((name        :initarg :name\n\t\t:reader gorilla-name\n\t\t:type string)\n   (description :initarg :description\n\t\t:reader gorilla-description)))\n\n(defmethod print-object ((gorilla gorilla) stream)\n  (print-unreadable-object (gorilla stream :type t)\n    (format stream \"~S\" (gorilla-name gorilla))))\n  \n\n;;; We can create a few gorillas to test the class. To refer to these\n;;; gorillas later on, we have to store them in a list. We can then\n;;; write functions to search for gorillas.\n\n(defvar *gorillas* nil)\n\n(setf *gorillas*\n      (list\n       (make-instance 'gorilla :name \"Lucy\"\n\t\t      :description :aggressive)\n       (make-instance 'gorilla :name \"Robert\"\n\t\t      :description :playful)\n       (make-instance 'gorilla :name \"John\"\n\t\t      :description :aggressive)))\n\n(defun all-gorillas ()\n  (copy-list *gorillas*))\n\n(defun gorilla-with-name (name)\n  (find name *gorillas* :test #'string-equal\n\t:key #'gorilla-name))\n\n(defun gorillas-with-description (description)\n  (remove description *gorillas* :test-not #'eql :key\n\t  #'gorilla-description))\n\n(all-gorillas)\n; => (#<GORILLA \"Lucy\"> #<GORILLA \"Robert\"> #<GORILLA \"John\">)\n(gorilla-with-name \"Lucy\")\n; => #<GORILLA \"Lucy\">\n(gorillas-with-description :aggressive)\n; => (#<GORILLA \"Lucy\"> #<GORILLA \"John\">)\n(gorilla-with-name \"Manuel\")\n; => NIL\n\n;;; What we would like to do however, is have the object system index\n;;; these objects for us. This is achieved by using INDEXED-CLASS as\n;;; the metaclass for the gorilla class. The `INDEXED-CLASS' has its\n;;; own slot-definition objects called `INDEX-DIRECT-SLOT-DEFINITION'\n;;; and `INDEX-EFFECTIVE-SLOT-DEFINITION'. Using these classes, we can\n;;; specify additional initargs to our slot definitions.\n;;;\n;;;## Additional slot initargs\n;;;\n;;; The following additional initargs are available:\n;;;\n;;; `INDEX' - A class name that specifies the class of the index to\n;;; use. For example `UNIQUE-INDEX', `HASH-INDEX' or\n;;; `HASH-LIST-INDEX'.\n;;;\n;;; `INDEX-INITARGS' - Additional arguments that are passed to\n;;; `INDEX-CREATE' when creating the index.\n;;;\n;;; `INDEX-READER' - A symbol under which a query function for the\n;;; index will be stored.\n;;;\n;;; `INDEX-KEYS' - A symbol under which a function returning all the\n;;; values of the index will be stored.\n;;;\n;;; `INDEX-SUBCLASSES' - Determines if instances of subclasses of this\n;;; class will be indexed in the slot index also. Defaults to `T'.\n;;;\n;;;## A simple indexed class\n;;;\n;;; Using the `INDEXED-CLASS', we can redefine our gorilla example.\n\n;;; Before we are able to refine GORILLA with a new metaclass, we need\n;;; to delete the old class definition:\n\n(setf (find-class 'gorilla) nil)\n\n(defclass gorilla ()\n  ((name :initarg :name :reader gorilla-name\n\t :index-type unique-index\n\t :index-initargs (:test #'equal)\n\t :index-reader gorilla-with-name\n\t :index-values all-gorillas)\n   (description :initarg :description\n\t\t:reader gorilla-description\n\t\t:index-type hash-index\n\t\t:index-reader gorillas-with-description))\n  (:metaclass indexed-class))\n\n(defmethod print-object ((gorilla gorilla) stream)\n  (print-unreadable-object (gorilla stream :type t)\n    (format stream \"~S\" (gorilla-name gorilla))))\n\n;;; We have to recreate the gorillas though, as the old instances\n;;; don't get updated for now.\n\n(make-instance 'gorilla :name \"Lucy\" :description :aggressive)\n(make-instance 'gorilla :name \"Robert\" :description :playful)\n(make-instance 'gorilla :name \"John\" :description :aggressive)\n\n(all-gorillas)\n; => (#<GORILLA \"Lucy\"> #<GORILLA \"Robert\"> #<GORILLA \"John\">)\n(gorilla-with-name \"Lucy\")\n; => #<GORILLA \"Lucy\">\n; T\n(gorillas-with-description :aggressive)\n; => (#<GORILLA \"John\"> #<GORILLA \"Lucy\">)\n; T\n\n;;;## Class indices\n;;;\n;;; We can also create indices that are not bound to a single\n;;; slot. These indices are called `CLASS-INDICES'. For example, we\n;;; can add two slots for the coordinates of the gorilla, and a class\n;;; index of type `ARRAY-INDEX' that will index the two slots `X' and\n;;; `Y' of the gorilla in an array of dimensions `256x256'. Note that\n;;; redefining the class conserves the existing indices.\n\n(defclass gorilla ()\n  ((name :initarg :name :reader gorilla-name\n\t :index-type unique-index\n\t :index-initargs (:test #'equal)\n\t :index-reader gorilla-with-name\n\t :index-values all-gorillas)\n   (description :initarg :description\n\t\t:reader gorilla-description\n\t\t:index-type hash-index\n\t\t:index-reader gorillas-with-description)\n   (x :initarg :x :reader gorilla-x)\n   (y :initarg :y :reader gorilla-y))\n  (:metaclass indexed-class)\n  (:class-indices (coords :index-type array-index\n\t\t\t  :slots (x y)\n\t\t\t  :index-reader gorilla-with-coords\n\t\t\t  :index-initargs (:dimensions '(256 256)))))\n\n(make-instance 'gorilla :name \"Pete\" :description\n\t       :playful :x 5 :y 8)\n\n(gorilla-with-coords '(5 8))\n; => #<GORILLA \"Pete\">\n(all-gorillas)\n; => (#<GORILLA \"Lucy\"> #<GORILLA \"Robert\">\n;     #<GORILLA \"John\"> #<GORILLA \"Pete\">)\n(gorillas-with-description :playful)\n; => (#<GORILLA \"Pete\"> #<GORILLA \"Robert\">)\n; T\n\n(let ((lucy (gorilla-with-name \"Lucy\")))\n  (with-slots (x y) lucy\n    (setf x 0 y 0)))\n\n(gorilla-with-name \"Lucy\")\n; => #<GORILLA \"Lucy\">\n; T\n(gorilla-with-coords '(0 0))\n; => #<GORILLA \"Lucy\">\n\n;;;# Creating indexed classes\n;;;\n;;; Adding indexes to a class is very simple. The class has to have\n;;; the metaclass `INDEXED-CLASS', or a class deriving from\n;;; `INDEXED-CLASS'.\n;;;\n\n;;;## Slot indices\n;;;\n;;; `INDEXED-CLASS' uses its own `EFFECTIVE-SLOT-DEFINITION' and\n;;; `DIRECT-SLOT-DEFINITION' which add indices to slots. A slot\n;;; definition in the `DEFCLASS' form supports additional keyword\n;;; arguments:\n;;;\n;;; `:INDEX' - Specifies an existing index to use as slot-index for this slot.\n;;; \n;;; `:INDEX-TYPE' - Specifies the class of the index to be used for this\n;;; slot.\n;;;\n;;; `:INDEX-INITARGS' - Specifies additional initargs to be given to\n;;; `INDEX-CREATE' when creating the index. The slot-name is given as\n;;; the `:SLOT' keyword argument to `INDEX-CREATE'.\n;;;\n;;; `:INDEX-READER' - Specifies the name under which a query function\n;;; for the created index will be saved.\n;;;\n;;; `:INDEX-VALUES' - Specifies the name under which a function returning\n;;; all the objects stored in the created index will be saved.\n;;;\n;;; `:INDEX-MAPVALUES' - Specifies the name under which a function\n;;; applying a function to all the objects stored in the created index\n;;; will be saved.\n;;;\n;;; `:INDEX-SUBCLASSES' - Specifies if subclasses of the class will\n;;; also be indexing in this index. Default is `T'.\n;;;\n;;; For each `DIRECT-SLOT-DEFINITION' of an indexed class with the\n;;; `:INDEX' keyword, an index is created and stored in the\n;;; `DIRECT-SLOT-DEFINITION'. All the direct indexes are then stored\n;;; in the `EFFECTIVE-SLOT-DEFINITION' (indexes with\n;;; `INDEX-SUBCLASSES = NIL' will not).\n;;;\n;;; Every access to the slot will update the indices stored in the\n;;; `EFFECTIVE-SLOT-DEFINITION'. When the slot is changed, the object\n;;; is removed from all the slot indices, and added after the slot\n;;; value has been changed. When a slot is made unbound, the object is\n;;; removed from the slot indices.\n\n(defclass test-slot ()\n  ((a :initarg :a :index-type unique-index\n      :reader test-slot-a\n      :index-reader test-slot-with-a\n      :index-values all-test-slots)\n   (b :initarg :b :index-type unique-index\n      :index-reader test-slot-with-b\n      :index-subclasses nil\n      :index-values all-test-slots-bs))\n  (:metaclass indexed-class))\n\n(defclass test-slot2 (test-slot)\n  ((b :initarg :b :index-type unique-index\n      :index-reader test-slot2-with-b\n      :index-subclasses nil\n      :index-mapvalues map-test-slot2s\n      :index-values all-test-slot2s-bs))\n  (:metaclass indexed-class))\n\n(defmethod print-object ((object test-slot) stream)\n  (print-unreadable-object (object stream :type t)\n    (format stream \"~S\" (test-slot-a object))))\n\n(make-instance 'test-slot :a 1 :b 2)\n(make-instance 'test-slot :a 2 :b 3)\n(make-instance 'test-slot2 :a 3 :b 4)\n(make-instance 'test-slot2 :a 4 :b 2)\n(make-instance 'test-slot2 :a 5 :b 9)\n\n(all-test-slots)\n; => (#<TEST-SLOT 1> #<TEST-SLOT 2> #<TEST-SLOT2 3>\n;     #<TEST-SLOT2 4> #<TEST-SLOT2 5>)\n(test-slot-with-a 2)\n; => #<TEST-SLOT 2>\n(all-test-slots-bs)\n; => (#<TEST-SLOT 1> #<TEST-SLOT 2>)\n(all-test-slot2s-bs)\n; (#<TEST-SLOT2 3> #<TEST-SLOT2 4> #<TEST-SLOT2 5>)\n(map-test-slot2s (lambda (obj) (print obj)))\n; \n; #<TEST-SLOT2 3> \n; #<TEST-SLOT2 4> \n; #<TEST-SLOT2 5> \n; \n; NIL\n\n;;; Here is an example of a slot index using an already existing index.\n\n(defvar *existing-unique-index*\n  (index-create 'unique-index :slots '(a)))\n\n(defclass test-slot3 ()\n  ((a :initarg :a :index *existing-unique-index*))\n  (:metaclass indexed-class))\n\n(make-instance 'test-slot3 :a 3)\n(make-instance 'test-slot3 :a 4)\n\n(index-get *existing-unique-index* 4)\n; => #<TEST-SLOT3 {493B9655}>\n; T\n(index-values *existing-unique-index*)\n; => (#<TEST-SLOT3 {493A0CBD}> #<TEST-SLOT3 {493B9655}>)\n\n;;; The slot indices of a class can be examined using\n;;; `CLASS-SLOT-INDICES'.\n\n(class-slot-indices (find-class 'test-slot) 'a)\n; => (#<UNIQUE-INDEX SLOT: A SIZE: 5 {599FA9F5}>)\n(class-slot-indices (find-class 'test-slot) 'b)\n; => (#<UNIQUE-INDEX SLOT: B SIZE: 2 {59A038BD}>)\n(class-slot-indices (find-class 'test-slot2) 'a)\n; => (#<UNIQUE-INDEX SLOT: A SIZE: 5 {599FA9F5}>)\n(class-slot-indices (find-class 'test-slot2) 'b)\n; => (#<UNIQUE-INDEX SLOT: B SIZE: 3 {59A0D6A5}>)\n\n;;; Note that a slot can have multiple indices.\n\n(defclass test-slot4 (test-slot)\n  ((a :initarg :a :index-type unique-index\n      :index-reader test-slot4-with-a\n      :index-values all-test-slot4s))\n  (:metaclass indexed-class))\n\n(make-instance 'test-slot4 :a 6 :b 9)\n\n(all-test-slots)\n; => (#<TEST-SLOT 1> #<TEST-SLOT 2> #<TEST-SLOT2 3>\n;     #<TEST-SLOT2 4> #<TEST-SLOT2 5>\n;     #<TEST-SLOT4 6>)\n(all-test-slot4s)\n; => (#<TEST-SLOT4 6>)\n(class-slot-indices (find-class 'test-slot4) 'a)\n; => (#<UNIQUE-INDEX SLOT: A SIZE: 6 {599FA9F5}>\n;  #<UNIQUE-INDEX SLOT: A SIZE: 1 {59079E25}>)\n\n\n;;;## Class indices\n;;;\n;;; In addition to slot indices, an indexed class supports class\n;;; indices which react when one of several slots is changing. For\n;;; example, in the `GORILLA' class above, the `COORDS' index reacts\n;;; on slots `X' and `Y'. By default, a class index reacts on all\n;;; slots.\n;;;\n;;; A class index is created by adding a class option `CLASS-INDICES'\n;;; followed by a list of class index specifications.\n\n(defclass test-class ()\n  ((x :initarg :x :reader test-class-x)\n   (y :initarg :y :reader test-class-y)\n   (z :initarg :z :reader test-class-z))\n  (:metaclass indexed-class)\n  (:class-indices (2d-coords :index-type array-index :slots (x y)\n\t\t\t     :index-initargs (:dimensions '(256 256))\n\t\t\t     :index-reader test-with-2d-coords)\n\t\t  (3d-coords :index-type array-index :slots (x y z)\n\t\t\t     :index-reader test-with-3d-coords\n\t\t\t     :index-initargs (:dimensions '(256 256 2)))))\n\n(defmethod print-object ((object test-class) stream)\n  (print-unreadable-object (object stream :type t)\n    (with-slots (x y z) object\n      (format stream \"~d,~d,~d\" x y z))))\n\n(make-instance 'test-class :x 1 :y 1 :z 0)\n(make-instance 'test-class :x 1 :y 3 :z 1)\n(make-instance 'test-class :x 1 :y 2 :z 0)\n\n(test-with-3d-coords '(1 1 0))\n; => #<TEST-CLASS 1,1,0>\n(test-with-2d-coords '(1 1))\n; => #<TEST-CLASS 1,1,0>\n(test-with-2d-coords '(1 2))\n; => #<TEST-CLASS 1,2,0>\n\n;;; A class index specification has to comply with the following\n;;; lambda-list `(NAME &REST ARGS &KEY INDEX-READER INDEX-VALUES SLOTS\n;;; TYPE INDEX &ALLOW-OTHER-KEYS)'. The key arguments `:INDEX-TYPE',\n;;; `:INDEX', `:INDEX-READER' and `:INDEX-VALUES' are then removed from\n;;; the initargs, and the rest is passed to `INDEX-CREATE' to create\n;;; the class index.\n;;;\n;;; `:INDEX-TYPE' - specifies the type of the class index.\n;;;\n;;; `:INDEX' - (optional) specifies an already existing index object\n;;; to use.\n;;;\n;;; `:INDEX-READER' - Like `:INDEX-READER' for slot\n;;; indices.\n;;;\n;;; `:INDEX-VALUES' - Like `:INDEX-VALUES' for slot indices.\n;;;\n;;; Using `:INDEX', we can use already existing indices as class\n;;; indices.\n\n(defvar *array-index*\n  (index-create 'array-index :slots '(x y z)\n\t\t:dimensions '(256 256 2)))\n\n(defclass test-class2 (test-class)\n  ()\n  (:metaclass indexed-class)\n  (:class-indices (coords :index *array-index* :slots (x y z)\n\t\t\t  :index-reader test-with-coords)))\n\n(make-instance 'test-class2 :x 5 :y 5 :z 0)\n\n*array-index*\n; => #<ARRAY-INDEX SLOTS: (X Y Z) ((256 256 2)) {593F383D}>\n(index-get *array-index* '( 5 5 0))\n; => #<TEST-CLASS2 5,5,0>\n(test-with-coords '(5 5 0))\n; => #<TEST-CLASS2 5,5,0>\n\n;;; XXX the class index tutorial needs updating, please skip to next section!\n\n;;; Another example of a class index is the `CLASS-INDEX' index.\n\n(defvar *class-index* (index-create 'class-index))\n\n(defclass base-object ()\n  ()\n  (:metaclass indexed-class)\n  (:class-indices (class :index *class-index*\n\t\t\t :slots nil\n\t\t\t :index-reader objects-of-class\n\t\t\t :index-values all-objects\n\t\t\t :index-subclasses t\n\t\t\t :index-keys all-class-names)\n\t\t  (classes :index-type class-index\n\t\t\t   :index-initargs (:index-superclasses t)\n\t\t\t   :slots nil\n\t\t\t   :index-subclasses t\n\t\t\t   :index-reader objects-with-class)))\n\n(defclass child1 (base-object)\n  ()\n  (:metaclass indexed-class))\n\n(defclass child2 (base-object)\n  ((a :initarg :a))\n  (:metaclass indexed-class))\n\n(make-instance 'child1)\n(make-instance 'child1)\n(make-instance 'child1)\n(make-instance 'child2)\n(make-instance 'child2)\n\n(all-objects)\n; => (#<CHILD1 {48E5CB3D}> #<CHILD1 {48E51395}> #<CHILD1 {48E453DD}>\n;  #<CHILD2 {48E82F55}> #<CHILD2 {48E7746D}>)\n(objects-with-class 'child1)\n; => (#<CHILD1 {48E5CB3D}> #<CHILD1 {48E51395}> #<CHILD1 {48E453DD}>)\n; T\n(objects-with-class 'child2)\n; => (#<CHILD2 {48E82F55}> #<CHILD2 {48E7746D}>)\n; T\n(objects-with-class 'base-object)\n; => (#<CHILD2 {48E82F55}> #<CHILD2 {48E7746D}> #<CHILD1 {48E5CB3D}>\n;  #<CHILD1 {48E51395}> #<CHILD1 {48E453DD}>)\n; T\n(objects-of-class 'child1)\n; => (#<CHILD1 {48E5CB3D}> #<CHILD1 {48E51395}> #<CHILD1 {48E453DD}>)\n; T\n(objects-of-class 'child2)\n; => (#<CHILD2 {48E82F55}> #<CHILD2 {48E7746D}>)\n; T\n(objects-of-class 'base-object)\n; => NIL\n; NIL\n\n;;;## Destroying objects\n;;;\n;;; Indexed objects will not be garbage collected until they are\n;;; removed from the indices. This is done by calling the\n;;; `DESTROY-OBJECT' method on the object. This removes the object\n;;; from all its indices, and sets the slot `DESTROYED-P' to `T', so\n;;; that not slot-access is possible anymore on the object.\n\n(make-instance 'test-class2 :x 5 :y 5 :z 0)\n\n(let ((obj (test-with-coords '(5 5 0))))\n  (destroy-object obj)\n\n;;; This will throw an error:\n;;;   Can not get slot X of destroyed object of class TEST-CLASS.\n\n  (test-class-x obj))\n\n;;;## Class and object reinitialization\n;;;\n;;; When a class is redefined, the indexed-class code tries to map\n;;; the new slot-indices to the old-indices. If it finds a slot-index\n;;; in the old `EFFECTIVE-SLOT-DEFINITION' and a slot-index in the new\n;;; `EFFECTIVE-SLOT-DEFINITION', it calls `INDEX-REINITIALIZE' on the\n;;; two indices to copy the values form the old index to the new\n;;; one. Afterwards, the same is done for the class\n;;; indices. `INDEX-REINITIALIZE' will not be called with the\n;;; old-index being the same as the new-index, so that explicitly\n;;; instantiated class indices don't get reinitialized with\n;;; themselves.\n;;;\n;;; Indices for new slots or new class indices are obviously empty on\n;;; creation, and will be filled when the existing instances are\n;;; updated. For now, `SHARED-INITIALIZE' is not overloaded, so the\n;;; instance updates are noticed through `(SETF SLOT-VALUE-USING-CLASS)'.\n\n;;;# Creating a custom index\n;;;\n;;; The main reason to write indexed slots was to be able to use\n;;; custom indices that are appropriate for the task at hand. Indices\n;;; are CLOS objects that follow the index method protocol. The\n;;; methods that have to be implemented are:\n;;;\n;;; `INDEX-ADD (INDEX OBJECT)' - Add OBJECT to the INDEX. Throws an\n;;; ERROR if a problem happened while inserting OBJECT.\"\n;;;\n;;; `INDEX-GET (INDEX KEY)' - Get the object (or the objects) stored\n;;; under the index-key KEY.\n;;;\n;;; `INDEX-REMOVE (INDEX OBJECT)' - Remove OBJECT from the INDEX.\n;;;\n;;; `INDEX-KEYS (INDEX)' - Returns all the keys of the index.\n;;;\n;;; `INDEX-VALUES (INDEX)' - Returns all the objects stored in INDEX.\n;;;\n;;; `INDEX-REINITIALIZE (NEW-INDEX OLD-INDEX)' - Called when the\n;;; definition of an index is changed.\n;;;\n;;; `INDEX-CLEAR (INDEX)' - Remove all indexed objects from the index.\n;;;\n;;; In addition to these methods, there is the function `INDEX-CREATE'\n;;; that instantiates an index object.  It passes the `INDEX-INITARGS'\n;;; provided to the `MAKE-INSTANCE' call that creates the index.\n;;;\n;;; The best way to see how this methods are used is to have at look\n;;; at the basic index `SLOT-INDEX'. A unique index indexes an object\n;;; under a key stored in a slot of this object, so a slot index is\n;;; initialized using two arguments: the slot-name where the key is\n;;; stored, and a test to create the underlying hash-table.\n\n(defclass slot-index ()\n  ((hash-table :initarg :hash-table :accessor slot-index-hash-table\n\t       :documentation \"The internal hash table used to index\nobjects.\")\n   (slot-name :initarg :slot-name :reader slot-index-slot-name\n\t      :documentation \"The value of the slot with name\nSLOT-NAME is used as a key to the internal hash-table.\")\n   (index-nil :initarg :index-nil :reader slot-index-index-nil\n\t      :initform nil\n\t      :documentation \"If T, NIL is used as a valid slot\n value, else slots with NIL value are treated as unbound slots.\")))\n\n(defmethod initialize-instance :after ((index slot-index) &key (test #'eql) slots index-nil)\n  (unless (<= (length slots) 1)\n    (error \"Can not create slot-index with more than one slot.\"))\n  (with-slots (hash-table slot-name) index\n    (setf hash-table (make-hash-table :test test)\n\t  slot-name (first slots)\n\t  (slot-value index 'index-nil) index-nil)))\n\n;;; When a class is redefined, the indices are re-created. However, we\n;;; still want our existing objects to be indexed by the new index,\n;;; therefore `INDEX-REINITIALIZE' copies the hash-table when the\n;;; hash-table test is the same, or else copies all the stored objects\n;;; into the new hash-table.\n\n(defmethod index-reinitialize ((new-index slot-index)\n\t\t\t       (old-index slot-index))\n  \"Reinitialize the slot-bound index from the old index by copying the\ninternal hash-table if the hash-table test is the same, or by\niterating over the values of the old-table and reentering them into\nthe new hash-table.\"\n  (let ((new-hash (slot-index-hash-table new-index))\n\t(old-hash (slot-index-hash-table old-index)))\n    (if (eql (hash-table-test new-hash)\n\t     (hash-table-test old-hash))\n\t(setf (slot-index-hash-table new-index)\n\t      old-hash)\n\t(loop for key being the hash-keys of old-hash\n\t      using (hash-value value)\n\t      do (setf (gethash key new-hash) value)))\n    new-index))\n\n;;; `INDEX-CLEAR' just creates an empty hash-table to replace the\n;;; existing hash-table.\n\n(defmethod index-clear ((index slot-index))\n  (with-slots (hash-table) index\n    (setf hash-table (make-hash-table\n\t\t      :test (hash-table-test hash-table)))))\n\n;;; `INDEX-ADD' and `INDEX-REMOVE' both use the slot-name to get the\n;;; key value, and use this key to query the underlying\n;;; hash-table. `INDEX-ADD' is not defined for the base class\n;;; `SLOT-INDEX', however it is defined in the simple child class\n;;; `UNIQUE-INDEX'. When another object is stored under the key, an\n;;; error is thrown.\n\n(defclass unique-index (slot-index)\n  ())\n\n(defmethod index-add ((index unique-index) object)\n  \"Add an object using the value of the specified slot as key.\nWhen the hash-table entry already contains a value, an error\nis thrown.\"\n  (unless (slot-boundp object (slot-index-slot-name index))\n    (return-from index-add))\n  (let* ((key (slot-value object (slot-index-slot-name index)))\n\t (hash-table (slot-index-hash-table index)))\n    (when (and (not (slot-index-index-nil index))\n\t       (null key))\n      (return-from index-add))\n    (multiple-value-bind (value presentp)\n\t(gethash key hash-table)\n      (when (and presentp\n\t\t (not (eql value object)))\n\t(error (make-condition 'index-existing-error\n\t\t\t       :index index :key key :value value)))\n      (setf (gethash key hash-table) object))))\n\n(defmethod index-remove ((index slot-index) object)\n  (let ((slot-name (slot-index-slot-name index)))\n    (if (slot-boundp object slot-name)\n\t(remhash (slot-value object slot-name)\n\t\t (slot-index-hash-table index))\n\t(warn \"Ignoring request to remove object ~a\nwith unbound slot ~A.\"\n\t      object slot-name))))\n\n;;; The rest of the methods are straightforward.\n\n(defmethod index-get ((index slot-index) key)\n  (gethash key (slot-index-hash-table index)))\n\n(defmethod index-keys ((index slot-index))\n  (loop for key being the hash-keys\n\tof (slot-index-hash-table index)\n\tcollect key))\n\n(defmethod index-values ((index slot-index))\n  (loop for value being the hash-values\n\tof (slot-index-hash-table index)\n\tcollect value))\n\n;;;# Creating an index using multiple slots\n;;;\n;;; When creating an index using multiple slots, you have to take care\n;;; of a few things. It can happen that a slot-value used by the index\n;;; is updated, but that the other slots that are needed are\n;;; unbound. However, this is not always an error, so a class index\n;;; has to check that all the slots it needs are bound. This is the\n;;; `INDEX-ADD' method for an array index.\n\n(defmethod index-add ((index array-index) object)\n  (let* ((slot-values\n\t  (mapcar #'(lambda (slot-name)\n\t\t      ;; return when not all slots are set\n\t\t      ;;\n\t\t      ;; - 18.10.04 not needed because of\n\t\t      ;; make-instance around method\n\t\t      ;;\n\t\t      ;; - 19.10.04 in fact this is needed because\n\t\t      ;; when adding a class index, the existing\n\t\t      ;; instances are not reinitailized using\n\t\t      ;; make-instnace, so we have to catch this...\n\t\t      (unless (slot-boundp object slot-name)\n\t\t\t\t    (return-from index-add nil))\n\t\t\t\t  (slot-value object slot-name))\n\t\t\t      (array-index-slot-names index)))\n\t (array (array-index-array index))\n\t (dimensions (array-dimensions array)))\n    (loop for slot-value in slot-values\n\t  for dimension in dimensions\n\t  when (>= slot-value dimension)\n\t  do (error \"Could not add ~a to array-index ~a\nbecause the coordinates ~a are out of bound.\"\n\t\t    object index slot-values))\n    (let ((value (apply #'aref array slot-values)))\n      (when (and value\n\t\t (not (eql value object)))\n\t(error (make-condition 'index-existing-error\n\t\t\t       :index index :key slot-values\n\t\t\t       :value value))))\n    (setf (apply #'aref array slot-values)\n\t  object)))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/skip-list/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.skip-list\n  (:use :cl)\n  (:export\n   #:skip-list\n   #:skip-list-length\n   #:skip-list-insert\n   #:skip-list-get\n   #:skip-list-delete\n   #:skip-list-remove\n   #:skip-list-search\n\n   #:sl-cursor-next\n   #:sl-cursor-prev\n   #:skip-list-cursor\n   #:skip-list-value-cursor\n   #:skip-list-key-cursor\n   #:skip-list-values-cursor\n   #:skip-list-keys-cursor\n   #:skip-list-range-cursor\n   #:map-skip-list\n   #:map-skip-list-values))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/skip-list/skip-list-tests.lisp",
    "content": "(defpackage :bknr.skip-list-tests\n  (:use #:cl\n        #:fiveam\n        #:bknr.skip-list)\n  (:import-from #:bknr.skip-list\n                #:make-node\n                #:node-level\n                #:node-forward\n                #:skip-list-to-list))\n(in-package :bknr.skip-list-tests)\n\n(def-suite* :bknr.skip-list)\n\n(defmacro define-skip-list-test (name &rest body)\n  `(test ,(intern (string name))\n     ,@body))\n\n(defmacro test-equal (&rest args)\n  `(is (equal ,@args)))\n\n(defmacro test-assert (&rest args)\n  `(is-true ,@args))\n\n(defmacro test-condition (expr (%quote condition))\n  (declare (ignore %quote))\n  `(signals ,condition\n     ,expr))\n\n(define-skip-list-test \"Node test\"\n    (let ((node (make-node 0 0 23)))\n      (dotimes (i (node-level node))\n\t(test-equal (node-forward node i) nil))\n      (dotimes (i (node-level node))\n\t(setf (node-forward node i) i))\n      (dotimes (i (node-level node))\n\t(test-equal (node-forward node i) i))))\n\n(define-skip-list-test \"Skiplist test\"\n    (let ((sl (make-instance 'skip-list)))\n      (dotimes (i 10000)\n\t(let ((value (random 10000))\n\t      (key (random 10000)))\n\t  (skip-list-insert sl key value)\n      ;; Avoiding fiveam here for the noise\n\t  (assert (equal (skip-list-search sl key) value)))))\n  (pass))\n\n(define-skip-list-test \"Hashcompare\"\n    (let ((sl (make-instance 'skip-list))\n\t  (hash (make-hash-table)))\n      (dotimes (i 10000)\n\t(let ((value (random 10000))\n\t      (key (random 10000)))\n\t  (setf (gethash key hash) value)\n\t  (skip-list-insert sl key value)))\n      (loop for key being the hash-key of hash\n\t    do (assert (equal (gethash key hash)\n                          (skip-list-search sl key)))))\n  (pass))\n\n(define-skip-list-test \"Length of skiplist\"\n    (let ((sl (make-instance 'skip-list)))\n      (dotimes (i 100)\n\t    (skip-list-insert sl i i)\n\t    (test-equal (skip-list-search sl i) i)\n\t    (test-equal (skip-list-search sl (1+ i) :not-found) :not-found)\n\t    (dotimes (j i)\n\t      (assert (equal (skip-list-search sl j) j)))\n\t    (test-equal (skip-list-length sl) (1+ i)))\n      (dotimes (i 100)\n\t    (test-equal (skip-list-length sl) (- 100 i))\n\t    (skip-list-delete sl i)\n\t    (test-equal (skip-list-length sl) (- 100 (1+ i)))\n\t    (test-equal (skip-list-search sl i :not-found) :not-found)\n\t    (loop for j from (1+ i) to 99\n\t          do (assert (equal (skip-list-search sl j) j))))\n      (test-equal (skip-list-length sl) 0)\n      (test-equal (skip-list-to-list sl) nil)))\n\n\n;; (defun perf ()\n;;   (let ((sl (make-instance 'skip-list)))\n;;     (time (prof:with-profiling ()\n;; \t    (dotimes (i 100000)\n;; \t      (skip-list-insert sl i i))))\n;;     (prof:show-flat-profile)\n;;     (format t \"~%~%\")\n;;     (time (prof:with-profiling ()\n;; \t    (dotimes (i 100000)\n;; \t      (skip-list-search sl i))))\n;;     (prof:show-flat-profile)))\n\n;;; Cursor tests\n\n(define-skip-list-test \"Basic cursor - empty list\"\n    (let* ((sl (make-instance 'skip-list))\n           (cursor (skip-list-cursor sl)))\n      (test-equal (sl-cursor-next cursor) nil)))\n\n(define-skip-list-test \"Basic cursor - single element\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (let ((cursor (skip-list-cursor sl)))\n        (test-equal (sl-cursor-next cursor) '(1 :one))\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Basic cursor - multiple elements\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n      (let ((cursor (skip-list-cursor sl)))\n        (test-equal (sl-cursor-next cursor) '(1 :one))\n        (test-equal (sl-cursor-next cursor) '(2 :two))\n        (test-equal (sl-cursor-next cursor) '(3 :three))\n        (test-equal (sl-cursor-next cursor) nil)\n        (test-equal (sl-cursor-next cursor) nil)))) ; Multiple calls after end return nil\n\n(define-skip-list-test \"Basic cursor - sorted order\"\n    (let* ((sl (make-instance 'skip-list)))\n      ;; Insert in random order\n      (skip-list-insert sl 5 :five)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 8 :eight)\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 3 :three)\n\n      ;; Cursor should return in sorted key order\n      (let ((cursor (skip-list-cursor sl)))\n        (test-equal (sl-cursor-next cursor) '(1 :one))\n        (test-equal (sl-cursor-next cursor) '(2 :two))\n        (test-equal (sl-cursor-next cursor) '(3 :three))\n        (test-equal (sl-cursor-next cursor) '(5 :five))\n        (test-equal (sl-cursor-next cursor) '(8 :eight))\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Cursor prev - not supported\"\n    (let* ((sl (make-instance 'skip-list))\n           (cursor (skip-list-cursor sl)))\n      (test-condition (sl-cursor-prev cursor) 'error)))\n\n(define-skip-list-test \"Keys cursor - returns only keys\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      (let ((cursor (skip-list-keys-cursor sl)))\n        (test-equal (sl-cursor-next cursor) 1)\n        (test-equal (sl-cursor-next cursor) 2)\n        (test-equal (sl-cursor-next cursor) 3)\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Values cursor - returns only values\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      (let ((cursor (skip-list-values-cursor sl)))\n        (test-equal (sl-cursor-next cursor) :one)\n        (test-equal (sl-cursor-next cursor) :two)\n        (test-equal (sl-cursor-next cursor) :three)\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Range cursor - basic range\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n      (skip-list-insert sl 4 :four)\n      (skip-list-insert sl 5 :five)\n\n      ;; Range [2, 4) should return 2 and 3\n      (let ((cursor (skip-list-range-cursor sl 2 4)))\n        (test-equal (sl-cursor-next cursor) '(2 :two))\n        (test-equal (sl-cursor-next cursor) '(3 :three))\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Range cursor - start before first\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 5 :five)\n      (skip-list-insert sl 10 :ten)\n\n      ;; Range [0, 7) should return only 5\n      (let ((cursor (skip-list-range-cursor sl 0 7)))\n        (test-equal (sl-cursor-next cursor) '(5 :five))\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Range cursor - end after last\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 5 :five)\n      (skip-list-insert sl 10 :ten)\n\n      ;; Range [5, 100) should return both\n      (let ((cursor (skip-list-range-cursor sl 5 100)))\n        (test-equal (sl-cursor-next cursor) '(5 :five))\n        (test-equal (sl-cursor-next cursor) '(10 :ten))\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Range cursor - empty range\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 5 :five)\n\n      ;; Range [2, 4) has no elements\n      (let ((cursor (skip-list-range-cursor sl 2 4)))\n        (test-equal (sl-cursor-next cursor) nil))))\n\n(define-skip-list-test \"Range cursor - no matching start\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n\n      ;; Range [10, 20) has no elements\n      (let ((cursor (skip-list-range-cursor sl 10 20)))\n        (test-equal cursor nil))))\n\n(define-skip-list-test \"map-skip-list - basic iteration\"\n    (let* ((sl (make-instance 'skip-list))\n           (results nil))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      (map-skip-list (lambda (key val)\n                       (push (list key val) results))\n                     sl)\n\n      ;; Results should be in reverse order (due to push)\n      (test-equal (nreverse results) '((1 :one) (2 :two) (3 :three)))))\n\n(define-skip-list-test \"map-skip-list - empty list\"\n    (let* ((sl (make-instance 'skip-list))\n           (count 0))\n      (map-skip-list (lambda (key val)\n                       (declare (ignore key val))\n                       (incf count))\n                     sl)\n      (test-equal count 0)))\n\n(define-skip-list-test \"map-skip-list-values - returns only values\"\n    (let* ((sl (make-instance 'skip-list))\n           (results nil))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      (map-skip-list-values (lambda (val)\n                              (push val results))\n                            sl)\n\n      (test-equal (nreverse results) '(:one :two :three))))\n\n(define-skip-list-test \"Cursor independence - multiple cursors\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      (let ((cursor1 (skip-list-cursor sl))\n            (cursor2 (skip-list-cursor sl)))\n        ;; Advance cursor1 twice\n        (sl-cursor-next cursor1)\n        (sl-cursor-next cursor1)\n\n        ;; cursor2 should still be at the beginning\n        (test-equal (sl-cursor-next cursor2) '(1 :one))\n\n        ;; cursor1 should be at position 3\n        (test-equal (sl-cursor-next cursor1) '(3 :three)))))\n\n(define-skip-list-test \"Cursor with EOC marker\"\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n\n      (let ((cursor (skip-list-cursor sl)))\n        (test-equal (sl-cursor-next cursor) '(1 :one))\n        (test-equal (sl-cursor-next cursor :eoc) :eoc)\n        (test-equal (sl-cursor-next cursor :end-of-list) :end-of-list))))\n\n(define-skip-list-test \"Cursor result list identity - mutation test\"\n    ;; This test verifies current behavior: each call returns the same\n    ;; list object. This test might a bit brittle to implementation\n    ;; detail though.\n    (let* ((sl (make-instance 'skip-list)))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n\n      (let ((cursor (skip-list-cursor sl)))\n        (let ((result1 (sl-cursor-next cursor))\n              (result2 (sl-cursor-next cursor)))\n\n          ;; Currently, results are identical cons cells\n          (test-assert (eq result1 result2))))))\n\n(define-skip-list-test \"Cursor result storage - safety test\"\n    ;; This test verifies that storing cursor results works correctly\n    ;; REQUIRES copy-list because the same list object is reused\n    (let* ((sl (make-instance 'skip-list))\n           (stored-results nil))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      (let ((cursor (skip-list-cursor sl)))\n        (loop for result = (sl-cursor-next cursor)\n              while result\n              do (push (copy-list result) stored-results)))\n\n      ;; All stored results should maintain their original values\n      (test-equal (length stored-results) 3)\n      (test-equal (first (third stored-results)) 1)\n      (test-equal (second (third stored-results)) :one)\n      (test-equal (first (second stored-results)) 2)\n      (test-equal (second (second stored-results)) :two)\n      (test-equal (first (first stored-results)) 3)\n      (test-equal (second (first stored-results)) :three)))\n\n(define-skip-list-test \"Cursor result storage - WITHOUT copy demonstrates the problem\"\n    ;; This test demonstrates what happens if you DON'T copy the result\n    ;; All stored references point to the same list with the FINAL values\n    (let* ((sl (make-instance 'skip-list))\n           (stored-results nil))\n      (skip-list-insert sl 1 :one)\n      (skip-list-insert sl 2 :two)\n      (skip-list-insert sl 3 :three)\n\n      ;; Store WITHOUT copying - this is WRONG but demonstrates the behavior\n      (let ((cursor (skip-list-cursor sl)))\n        (loop for result = (sl-cursor-next cursor)\n              while result\n              do (push result stored-results)))\n\n      ;; All three stored \"results\" point to the SAME list object\n      (test-equal (length stored-results) 3)\n      (test-assert (eq (first stored-results) (second stored-results)))\n      (test-assert (eq (second stored-results) (third stored-results)))\n\n      ;; And they all contain the FINAL values (3 :three)\n      (test-equal (first (first stored-results)) 3)\n      (test-equal (second (first stored-results)) :three)\n      (test-equal (first (second stored-results)) 3)\n      (test-equal (second (second stored-results)) :three)\n      (test-equal (first (third stored-results)) 3)\n      (test-equal (second (third stored-results)) :three)))\n\n(define-skip-list-test \"Large list cursor iteration\"\n    (let* ((sl (make-instance 'skip-list))\n           (count 0))\n      ;; Insert 1000 elements\n      (dotimes (i 1000)\n        (skip-list-insert sl i (* i 10)))\n\n      ;; Verify cursor iterates over all elements in order\n      (let ((cursor (skip-list-cursor sl)))\n        (loop for result = (sl-cursor-next cursor)\n              while result\n              do (progn\n                   (test-equal (first result) count)\n                   (test-equal (second result) (* count 10))\n                   (incf count))))\n\n      (test-equal count 1000)))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/skip-list/skip-list.lisp",
    "content": "(in-package :bknr.skip-list)\n\n;;; TODO:\n;;; index-protokoll fuer cursor\n;;; range-queries\n\n(defparameter *sl-random-state*\n  (make-random-state t)\n  \"Internal status of the random number generator.\")\n\n(defun sl-random ()\n  \"Pseudo-random number generator, returns NIL 3/4 of the time.\"\n  (declare (optimize (speed 3)))\n  (< 2 (random 4 *sl-random-state*)))\n\n(defconstant +max-level+ (the fixnum 32)\n  \"Maximum level of skip-list, should be enough for 2^32 elements.\")\n\n(defun random-level ()\n  \"Returns a random level for a new skip-list node, with SL-RANDOM p for each level.\"\n  (declare (optimize speed))\n  (do ((level 1 (1+ level)))\n      ((or (= level +max-level+)\n           (sl-random)) level)\n    (declare (type fixnum level))))\n\n;;; A node is a SIMPLE-VECTOR containing KEY, VALUE and the forward pointers\n\n(defmacro node-key (node)\n  `(aref (the simple-vector ,node) 0))\n\n(defmacro node-value (node)\n  `(aref ,node 1))\n\n(defmacro node-forward (node &optional (i 0))\n  `(aref (the simple-vector ,node) (the fixnum (+ ,i 2))))\n\n(defun make-node (key value size &key initial-element)\n  (let ((node (make-array (+ 2 size) :initial-element initial-element)))\n    (setf (node-key node) key\n          (node-value node) value)\n    node))\n\n(defun node-level (node)\n  (declare (type simple-vector node))\n  (- (length node) 2))\n\n(defun make-header (&key initial-element)\n  (make-node nil nil +max-level+ :initial-element initial-element))\n\n(defclass skip-list ()\n  ((header :initform (make-header)\n           :reader skip-list-header :type simple-vector)\n   #+nil\n   (finger :initform (make-header)\n           :reader skip-list-finger :type simple-vector)\n   (length :initform 0 :accessor skip-list-length :type fixnum)\n   (level :initform 0 :accessor skip-list-level :type fixnum))\n  (:documentation \"Skip-list class, access to elements is done through the header node.\"))\n\n(defmethod print-object ((sl skip-list) stream)\n  (print-unreadable-object (sl stream :type t :identity t)\n    (format stream \"length = ~A\" (skip-list-length sl))))\n\n(defmethod skip-list-empty-p ((sl skip-list))\n  (= (skip-list-length sl) 0))\n\n(defun follow-node (node key level)\n  \"Follow a skip-list node at level LEVEL, stopping when there is no\nnext node or when the next node has a larger key.\"\n  (declare (type (or null simple-vector) node)\n           (type integer key)\n           (type integer level)\n           (optimize (speed 3)))\n  (do ((next (node-forward node level) (node-forward node level)))\n      ((not (and next (< (node-key next) key))) node)\n    (declare (type (or null simple-vector) next))\n    (setf node next)))\n\n(defmethod make-update ((sl skip-list) key)\n  \"Search the list for the node before KEY, and construct an update\narray storing the previous nodes at all the levels.\"\n  (declare (type integer key)\n           (optimize (speed 3)))\n  (let ((node (skip-list-header sl))\n        (update (make-header :initial-element (skip-list-header sl))))\n    (declare (type (or null simple-vector) node)\n             (type simple-vector update)\n             (optimize (speed 3)))\n    \n    (do ((level (1- (skip-list-level sl)) (1- level)))\n        ((< level 0) (values update (node-forward node 0)))\n      (declare (type integer level))\n\n      (setf (node-forward update level)\n            (setf node (follow-node node key level))))))\n\n\n(defmethod skip-list-insert ((sl skip-list) key value)\n  \"Insert VALUE under KEY in the skip-list. Replaces the existing node\nwith KEY if present, or inserts a new node with random level.\"\n\n  (multiple-value-bind (update node)\n      (make-update sl key)\n    #+nil\n    (format t \"update after first search: ~A~%\" update)\n    \n    (if (and node (= (node-key node) key))\n        ;; ein knoten mit dem KEY existiert schon, nur VALUE veraendern\n        (setf (node-value node) value)\n\n        ;; es muss ein neuer knoten mit zufaelligem LEVEL eingefuegt werden\n        (let* ((new-level (random-level))\n               (new-node (make-node key value new-level)))\n\n          #+nil\n          (format t \"new-node ~A~%\" new-node)\n\n          (when (> new-level (skip-list-level sl))\n            (setf (skip-list-level sl) new-level))\n          (incf (skip-list-length sl))\n\n          ;; einfuegen des neuen knoten, indem der knoten in die\n          ;; jeweiligen previous knoten an naechster stelle\n          ;; eingetragen wird (dazu war UPDATE gut)\n          (do ((level 0 (1+ level)))\n              ((= level new-level) new-node)\n            \n            (let ((next (node-forward update level)))\n              (when next\n                (setf (node-forward new-node level) (node-forward next level)\n                      (node-forward next level)      new-node))))))\n\n    value))\n\n(defmethod skip-list-reduce-header ((sl skip-list))\n  \"Sets the LEVEL slot of the skip-list according to the length of the header node.\"\n  ;; level slot der skip-liste anpassen an die header struktur\n  (do ((level (1- (skip-list-level sl)) (1- level))\n       (header (skip-list-header sl)))\n      ((or (< level 0)\n           (node-forward header level))\n       (setf (skip-list-level sl) (1+ level))\n       sl)))\n\n(defmethod skip-list-delete ((sl skip-list) key)\n  \"Delete the node with KEY in the skip-list.\"\n  (multiple-value-bind (update node)\n      (make-update sl key)\n\n    (when (and node (= (node-key node) key))\n      (do ((level 0 (1+ level)))\n          ((= level (skip-list-level sl)))\n        (let ((next (node-forward update level)))\n          (when (and next (eql (node-forward next level) node))\n            (setf (node-forward next level) (node-forward node level)))))\n\n      (decf (skip-list-length sl))\n      (skip-list-reduce-header sl))\n    \n    sl))\n\n;;; compatibility to old API\n\n(defmethod skip-list-get (key (sl skip-list))\n  (skip-list-search sl key))\n\n(defmethod (setf skip-list-get) (new-value key (sl skip-list))\n  (skip-list-insert sl key new-value))\n\n(defmethod skip-list-remove (key (sl skip-list))\n  (skip-list-delete sl key))\n\n(defmethod skip-list-search-node ((sl skip-list) key)\n  \"Search for the node with KEY in the skip-list.\"\n  (declare (type integer key)\n           (optimize (speed 3)))\n  (do ((level (1- (skip-list-level sl)) (1- level))\n       (node (skip-list-header sl) (follow-node node key level)))\n      ((< level 0)\n       (let ((result (node-forward node)))\n         (if (and result (= (node-key result) key))\n             result\n             nil)))\n    (declare (type fixnum level)\n             (type simple-vector node))))\n\n(defmethod skip-list-after-node ((sl skip-list) key)\n  \"Search for the node with key biffer or equal to KEY in the skip-list (for range queries).\"\n  (declare (type integer key)\n           (optimize (speed 3)))\n  (do ((level (1- (skip-list-level sl)) (1- level))\n       (node (skip-list-header sl) (follow-node node key level)))\n      ((< level 0)\n       (let ((result (node-forward node)))\n         (if (and result (>= (node-key result) key))\n             result\n             nil)))\n    (declare (type fixnum level)\n             (type simple-vector node))))\n\n(defmethod skip-list-search ((sl skip-list) key &optional not-found)\n  (let ((result (skip-list-search-node sl key)))\n    (if result\n        (node-value result)\n        not-found)))\n\n(defun node-before (node key &optional (level 0))\n  (let ((next (node-forward node level)))\n    (and next (< (node-key next) key))))\n\n;; broken, and not necessarily faster\n#+nil\n(defmethod skip-list-search ((sl skip-list) key &optional not-found)\n  (let ((finger (skip-list-finger sl))\n        node\n        lvl)\n\n    (if (node-before finger key 0)\n\n        (do ((level 1 (1+ level)))\n            ((or (>= level (skip-list-level sl))\n                 (not (node-before finger key level)))\n             (setf node (node-forward finger (1- level))\n                   lvl (1- level))))\n\n        (do ((level 1 (1+ level)))\n            ((or (>= level (skip-list-level sl))\n                 (node-before finger key level))\n             \n             (if (>= level (skip-list-level sl))\n                 (setf node (skip-list-header sl)\n                       lvl (1- (skip-list-level sl)))\n                 (setf node (node-forward finger level)\n                       lvl level)))\n          #+nil(format t \"level: ~A~%\" level)))\n\n    #+nil(format t \"node: ~A, level ~A~%\" node lvl)\n\n    (do ((level lvl (1- level)))\n        ((or (null node)\n             (< level 0))\n         (when node (setf node (node-forward node 0))))\n\n      (setf node (follow-node node key level))\n      (unless (eql node (skip-list-header sl))\n        #+nil(format t \"storing in finger~%\")\n        (setf (node-forward finger level) node)))\n\n    (if (and node (= (node-key node) key))\n        (node-value node)\n        not-found)))\n\n(defmethod skip-list-to-list ((sl skip-list))\n  (let ((node (skip-list-header sl)))\n    (loop for next = (node-forward node) then (node-forward next)\n          while next\n          collect (list (node-key next) (node-value next)))))\n\n;;; cursors\n\n(defclass skip-list-cursor ()\n  ((node :initarg :node :accessor skip-list-cursor-node)\n   (result :initform (list nil nil)\n           :reader result\n           :documentation \"A cached list that will be used to avoid consing\"))\n  (:documentation \"Base cursor class for iterating through a skip-list. Maintains a reference\nto the current node and supports forward traversal via sl-cursor-next.\n\nIMPORTANT: For performance, sl-cursor-next returns a REUSED mutable list. The same list object\nis returned on each call with updated contents. If you need to store cursor results, you MUST\ncopy them first using (copy-list result). The specialized cursors (skip-list-keys-cursor and\nskip-list-values-cursor) return scalar values and do not have this limitation.\"))\n\n(defmethod sl-cursor-next ((slc skip-list-cursor) &optional eoc)\n  \"Advance the cursor and return (KEY VALUE) for the current node.\nIf the cursor has reached the end or is nil, returns EOC (end-of-cursor marker).\nMoves the cursor forward to the next node as a side effect.\n\nIMPORTANT: Returns a REUSED mutable list for performance (avoids allocation). The same list\nobject is returned on every call with its contents updated. The list contents are only valid\nuntil the next call to sl-cursor-next.\n\nIf you need to store the result beyond the next cursor operation, you MUST copy it:\n  (push (copy-list (sl-cursor-next cursor)) stored-results)\n\nFor immediate use (e.g., in map-skip-list with apply), no copy is needed. If you only need\nkeys or values, use skip-list-keys-cursor or skip-list-values-cursor which return scalars.\"\n  (with-slots (node result) slc\n    (if node\n        (progn\n          (setf (first result) (bknr.skip-list::node-key node)\n                (second result) (bknr.skip-list::node-value node))\n          (setf node (bknr.skip-list::node-forward node))\n          result)\n        eoc)))\n\n(defmethod sl-cursor-prev ((slc skip-list-cursor) &optional eoc)\n  \"Attempt to move the cursor backwards. Not supported for skip-lists - always signals an error.\nSkip-lists only support forward iteration due to their unidirectional pointer structure.\"\n  (declare (ignore eoc))\n  (error \"Can not move backward in skip-list\"))\n\n(defclass skip-list-value-cursor (skip-list-cursor)\n  ()\n  (:documentation \"Cursor that returns only values (not keys) when calling sl-cursor-next.\nWraps the base skip-list-cursor to extract just the value portion from each node.\"))\n\n(defmethod sl-cursor-next :around ((slc skip-list-value-cursor) &optional eoc)\n  \"Returns only the value portion from the base cursor's (KEY VALUE) result.\nIf at end of cursor, returns EOC unchanged.\"\n  (let ((result (call-next-method)))\n    (if (eql result eoc)\n        eoc\n        (second result))))\n\n(defclass skip-list-key-cursor (skip-list-cursor)\n  ()\n  (:documentation \"Cursor that returns only keys (not values) when calling sl-cursor-next.\nWraps the base skip-list-cursor to extract just the key portion from each node.\"))\n\n(defmethod sl-cursor-next :around ((slc skip-list-key-cursor) &optional eoc)\n  \"Returns only the key portion from the base cursor's (KEY VALUE) result.\nIf at end of cursor, returns EOC unchanged.\"\n  (let ((result (call-next-method)))\n    (if (eql result eoc)\n        eoc\n        (first result))))\n\n(defmethod skip-list-cursor ((sl skip-list) &key cursor (class 'skip-list-cursor))\n  \"Create or reinitialize a cursor for skip-list SL.\nIf CURSOR is provided, resets it to the beginning of the list and returns it.\nIf CURSOR is nil (default), creates a new cursor instance of CLASS (defaults to 'skip-list-cursor).\nThe cursor starts positioned at the first element of the skip-list.\"\n  (if cursor\n      (progn (setf (skip-list-cursor-node cursor)\n                   (bknr.skip-list::node-forward (bknr.skip-list::skip-list-header sl)))\n             cursor)\n      (make-instance class :node (bknr.skip-list::node-forward (bknr.skip-list::skip-list-header sl)))))\n\n(defmethod skip-list-values-cursor ((sl skip-list))\n  \"Create a cursor that returns only values (not keys) from skip-list SL.\nConvenience wrapper around skip-list-cursor with class 'skip-list-value-cursor.\"\n  (skip-list-cursor sl :class 'skip-list-value-cursor))\n\n(defmethod skip-list-keys-cursor ((sl skip-list))\n  \"Create a cursor that returns only keys (not values) from skip-list SL.\nConvenience wrapper around skip-list-cursor with class 'skip-list-key-cursor.\"\n  (skip-list-cursor sl :class 'skip-list-key-cursor))\n\n(defclass skip-list-range-cursor (skip-list-cursor)\n  ((end :initarg :end :reader slrc-end))\n  (:documentation \"Cursor for iterating over a key range [START, END) in a skip-list.\nStops iteration when encountering a node with key >= END.\"))\n\n(defmethod sl-cursor-next :around ((slc skip-list-range-cursor) &optional eoc)\n  \"Returns the next (KEY VALUE) pair if the current node's key is less than END.\nOnce a node with key >= END is encountered, returns EOC to signal end of range.\"\n  (with-slots (node end) slc\n    (if (and node (< (bknr.skip-list::node-key node) end))\n        (call-next-method)\n        eoc)))\n\n(defmethod skip-list-range-cursor ((sl skip-list) start end)\n  \"Create a cursor for iterating over keys in range [START, END) of skip-list SL.\nReturns nil if no node with key >= START exists. Otherwise returns a cursor\npositioned at the first node with key >= START, which will iterate until key >= END.\"\n  (let ((node (bknr.skip-list::skip-list-after-node sl start)))\n    (when node\n      (make-instance 'skip-list-range-cursor :node node :end end))))\n\n(defmethod map-skip-list (fun (sl skip-list))\n  \"Iterate over skip-list SL, calling FUN with two arguments (key value) for each node.\nUses a cursor to traverse the list from beginning to end.\"\n  (let ((cursor (skip-list-cursor sl)))\n    (do ((val (sl-cursor-next cursor) (sl-cursor-next cursor)))\n        ((null val))\n      (apply fun val))))\n\n(defmethod map-skip-list-values (fun (sl skip-list))\n  \"Iterate over skip-list SL, calling FUN with one argument (value) for each node.\nUses a values-cursor to traverse only the values, ignoring keys.\"\n  (let ((cursor (skip-list-values-cursor sl)))\n    (do ((val (sl-cursor-next cursor) (sl-cursor-next cursor)))\n        ((null val))\n      (funcall fun val))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/statistics/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.statistics\n  (:use :cl :cl-user)\n  (:export #:make-statistics-table\n\t   #:note-statistic\n\t   #:show-statistics\n\t   #:clear-statistics\n\t   #:with-statistics-log))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/statistics/runtime-statistics.lisp",
    "content": "(in-package :bknr.statistics)\n\n(defclass statistics-table ()\n  ((table :initform (make-hash-table))))\n\n(defun make-statistics-table ()\n  (make-instance 'statistics-table))\n\n(defmethod note-statistic ((table statistics-table) name elapsed)\n  (incf (gethash name (slot-value table 'table) 0) elapsed))\n\n(defmethod show-statistic ((table statistics-table) name)\n  (format t \"~A: ~A~%\" name (gethash name (slot-value table 'table))))\n\n(defmethod show-statistics ((table statistics-table))\n  (loop for name being the hash-keys of (slot-value table 'table)\n\tdo (show-statistic table name)))\n\n(defmethod clear-statistics ((table statistics-table))\n  (setf (slot-value table 'table) (make-hash-table)))\n\n(defmacro with-elapsed-run-time ((elapsed-var form) &body body)\n  (let ((start-time (gensym)))\n    `(let ((,start-time (get-internal-run-time)))\n      (prog1\n\t  ,form\n\t(let ((,elapsed-var (- (get-internal-run-time) ,start-time)))\n\t  ,@body)))))\n\n(defmacro with-statistics-log ((table &rest statistic-names) &rest body)\n  (let ((elapsed (gensym))\n\t(name (gensym))\n\t(result (gensym)))\n    `(let (,result)\n      (with-elapsed-run-time (,elapsed (setq ,result (progn ,@body)))\n\t(dolist (,name (list ,@statistic-names))\n\t  (note-statistic ,table ,name ,elapsed))\n\t,result))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/Makefile",
    "content": "\nall: smbpasswd-wrapper\n\ninstall:\n\tinstall -c -m 4111 smbpasswd-wrapper /usr/local/bin"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/acl-mp-compat.lisp",
    "content": "(in-package :bknr.utils)\n\n#+(not (or allegro sbcl cmu openmcl lispworks))\n(error \"missing port for this compiler, please provide for locking primitives for this compiler in ~A\" *load-pathname*)\n\n(defun mp-make-lock (&optional (name \"Anonymous\"))\n  #+allegro\n  (mp:make-process-lock :name name)\n  #+sbcl\n  (sb-thread:make-mutex :name name)\n  #+(and cmu x86)\n  (mp:make-lock name)\n  #+(and cmu (not x86))\n  (declare (ignore name))\n  #+openmcl\n  (ccl:make-lock name)\n  #+lispworks\n  (mp:make-lock :name name))\n\n(defmacro mp-with-lock-held ((lock) &body body)\n  #+allegro\n  `(mp:with-process-lock (,lock)\n    ,@body)\n  #+sbcl\n  `(sb-thread:with-mutex (,lock)\n     ,@body)\n  #+cmu\n  `(mp:with-lock-held (,lock)\n    ,@body)\n  #+openmcl\n  `(ccl:with-lock-grabbed (,lock)\n    ,@body)\n  #+lispworks\n  `(mp:with-lock (,lock)\n    ,@body))\n\n(defmacro mp-with-recursive-lock-held ((lock) &body body)\n  #+allegro\n  `(mp:with-process-lock (,lock)\n    ,@body)\n  #+sbcl\n  `(sb-thread:with-recursive-lock (,lock)\n     ,@body)\n  #+cmu\n  `(mp:with-lock-held (,lock)\n    ,@body)\n  #+openmcl\n  `(ccl:with-lock-grabbed (,lock)\n    ,@body)\n  #+lispworks\n  `(mp:with-lock (,lock)\n    ,@body))\n\n(defun make-process (function &key name)\n  (bt:make-thread function :name name))\n\n(defun destroy-process (process)\n  (bt:destroy-thread process))\n\n(defun process-active-p (process)\n  (bt:thread-alive-p process))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/actor.lisp",
    "content": "(in-package :bknr.utils)\n\n(defclass bknr-actor ()\n  ((name :initarg :name :accessor bknr-actor-name)\n   (process :accessor bknr-actor-process))\n  (:default-initargs :name \"anonymous actor\"))\n\n(defgeneric run-function (actor))\n\n(defmethod print-object ((actor bknr-actor) stream)\n  (format stream \n\t  \"#<~a \\\"~a\\\" (~:[no process~;process running~])>\"\n\t  (class-name (class-of actor))\n\t  (bknr-actor-name actor)\n\t  (slot-boundp actor 'process))\n  actor)\n\n(defmethod actor-start ((actor bknr-actor))\n  (actor-stop actor)\n  (setf (slot-value actor 'process)\n    (make-process (lambda ()\n                    (funcall #'run-function actor))\n                  :name (bknr-actor-name actor))))\n\n(defmethod actor-running-p ((actor bknr-actor))\n  (and (slot-boundp actor 'process)\n       (process-active-p (bknr-actor-process actor))))\n\n(defmethod actor-stop ((actor bknr-actor))\n  (when (slot-boundp actor 'process)\n    (destroy-process (bknr-actor-process actor))\n    (slot-makunbound actor 'process)))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/capability.lisp",
    "content": "(in-package :bknr.utils)\n\n(defvar *capability-count* 0)\n\n(defun make-capability-string ()\n  \"return new unique capabilty as a string\"\n  (md5-string (format nil \"~a.~a\" (get-universal-time)\n\t\t      (incf *capability-count*))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/class.lisp",
    "content": "(in-package :bknr.utils)\n\n;;; short form for DEFCLASS\n\n(defun compute-bknr-slot (class slot)\n  (destructuring-bind (name access &rest rest) slot\n    (let* ((initarg (make-keyword-from-string (symbol-name name)))\n\t   (accessor (intern (concatenate 'string (symbol-name class) \"-\"\n\t\t\t\t\t  (symbol-name name)) *package*)))\n      (unless (getf rest :transient)\n        (push initarg rest)\n        (push :initarg rest))\n      (case access\n\t(:read\n\t (push accessor rest)\n\t (push :reader rest))\n\t(:update\n\t (push accessor rest)\n\t (push :accessor rest))\n\t(:none)\n\t(t (error \"unknown access option ~A in slot ~A of class ~A.\"\n\t\t  access slot class)))\n      (cons name rest))))\n\n(defmacro define-bknr-class (class (&rest superclasses) slots &rest class-options)\n  (let ((slots (mapcar (lambda (slot) (compute-bknr-slot class slot)) slots)))\n    ;; the eval-when is there to create the index access functions at compile time\n    `(eval-when (:compile-toplevel :load-toplevel :execute)\n      (defclass ,class ,superclasses\n\t,slots\n\t,@class-options))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/crypt-md5.lisp",
    "content": "(in-package :bknr.utils)\n\n(define-constant +itoa64+\n  \"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\")\n\n(defun itoa64 (int num)\n  (loop for i below num\n\tcollect (char +itoa64+ (ldb (byte 6 (* 6 i)) int))))\n\n(defun to64 (md5)\n  (let ((res (mapcan #'(lambda (indices)\n\t\t\t  (itoa64 (loop with ret = 0\n\t\t\t\t\tfor i from 0 by 8\n\t\t\t\t\tfor idx in indices\n\t\t\t\t\tdo (setf (ldb (byte 8 i) ret) (elt md5 idx))\n\t\t\t\t\tfinally (return ret)) 4))\n\t\t      '((12 6 0)\n\t\t\t(13 7 1)\n\t\t\t(14 8 2)\n\t\t\t(15 9 3)\n\t\t\t(5 10 4)))))\n    (coerce (nconc res (itoa64 (elt md5 11) 2)) 'string)))\n\n(defun extract-salt (saltpw)\n  (coerce (loop for ch across (subseq saltpw 3)\n\t\tuntil (eql ch #\\$)\n\t\tcollect ch) 'string))\n\n(defun crypt-md5 (pw salt)\n  (let ((pw (if (typep pw 'simple-array)\n                pw\n                (make-array (length pw)\n                            :element-type (array-element-type pw)\n                            :initial-contents pw))))\n    (when (string-equal (subseq salt 0 3) \"$1$\")\n      (setf salt (extract-salt salt)))\n  \n    (let ((s (make-md5-state))\n          (s2 (make-md5-state)))\n\n      (update-md5-state s pw)\n      (update-md5-state s \"$1$\")\n      (update-md5-state s salt)\n\n      (update-md5-state s2 pw)\n      (update-md5-state s2 salt)\n      (update-md5-state s2 pw)\n\n      (let ((str (finalize-md5-state s2)))\n        (update-md5-state s (subseq str 0 (min (length pw) 16)))\n        (setf (elt str 0) 0)\n        (loop for i = (length pw) then (ash i -1)\n           until (<= i 0)\n           do (update-md5-state s (if (oddp i) str pw) :end 1))\n        (let ((seq (finalize-md5-state s)))\n\n          (dotimes (i 1000)\n            (let ((s3 (make-md5-state)))\n              (update-md5-state s3 (if (oddp i) pw seq))\n              (unless (= (mod i 3) 0)\n                (update-md5-state s3 salt))\n              (unless (= (mod i 7) 0)\n                (update-md5-state s3 pw))\n              (if (oddp i)\n                  (update-md5-state s3 seq)\n                  (update-md5-state s3 pw))\n              (setf seq (finalize-md5-state s3))))\n          (concatenate 'string \"$1$\" salt \"$\" (to64 seq)))))))\n\n(defun verify-md5-password (password saltpw)\n  (unless (string-equal (subseq saltpw 0 3) \"$1$\")\n    (error \"not a md5 password ~a\" saltpw))\n  (let ((salt (extract-salt saltpw)))\n    (string-equal (crypt-md5 (coerce password 'simple-string) salt) saltpw)))\n\n;; 0 6 12 (4)\n;; 1 7 13 (4)\n;; 2 8 14 (4)\n;; 3 9 15 (4)\n;; 4 10 5 (4)\n;; 11     (2)\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/date-calc.lisp",
    "content": ";;; Package: date-calc.lisp\n;;; Heiko Schroeter, Dec 2005\n;;;\n;;; Ver 0.2 ALPHA\n;;; License: GNU, Version 2, June 1991\n;;;\n;;; Legal issues:\n;;; -------------\n;;; This package with all its parts is\n;;; Copyright (c) 2005 by Heiko Schroeter.\n\n;;; This package is free software; you can use, modify and redistribute\n;;; under the \"GNU General Public License\" and the \"Artistic License\".\n\n;;; This package is intended as a date-calc module for \"everyday\" purposes. It is not intended\n;;; , nor claims to be,\n;;; a bullet proofed implementation of 'scientific' datum calculus.\n\n;;; Parts taken from DateCalc.el (EMACS, Doug Alcorn, <doug@lathi.net>, Ver. 0.1, 2003)\n;;; and the\n;;; Perl Package \"Date::Calc\" Version 5.4,Copyright (c) 1995 - 2004 by Steffen Beyer.\n\n;;; Some Documentation strings are only slightly edited from DateCalc.el\n\n;;; THIS PACKAGE IS PROVIDED \"AS IS\" AND WITHOUT ANY EXPRESS OR\n;;; IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED\n;;; WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.\n\n;;; The following routines are a sidestep for CL Day Of Week (DOW) conformance.\n;;; (See Hyperspec 25.1.4.1 X3J13).\n;;; \"An integer between 0 and 6, inclusive; 0 means Monday, 1 means Tuesday, and so on; 6 means Sunday.\"\n;;; PERLs Date::Calc module range is from 1(Monday) to 7(Sunday).\n\n\n;;;  CL conform                     ,Perl' Conform\n;;;  ----------                     --------------\n;;;  cl-day-of-week                 day-of-week\n;;;  cl-weeks-in-year               weeks-in-year\n;;;  cl-check-business-p            check-business-p\n;;;  cl-nth-weekday-of-month-year   nth-weekday-of-month-year\n;;;  cl-standard-to-business        standard-to-business\n;;;  cl-business-to-standard        business-to-standard\n;;;  cl-system-clock                system-clock\n;;;  cl-decode-day-of-week          decode-day-of-week\n\n;;; Pls report bugs to schroete @ iup physik uni-bremen de\n\n\n(in-package #:cl-user)\n\n(defpackage #:date-calc\n  (:use #:cl)\n  (:export #:*language*\n\t   #:decode-day-of-week\n\t   #:cl-decode-day-of-week\n           #:decode-month\n\t   #:decode-language\n\t   #:iso-lc\n\t   #:iso-uc\n\t   #:year-to-days\n\t   #:fixed-window\n\t   #:center\n\t   #:valid-year-p\n\t   #:valid-month-p\n\t   #:leap-year\n\t   #:leap-year-p\n\t   #:days-in-month\n\t   #:days-in-year\n\t   #:check-date\n\t   #:check-business-p\n\t   #:check-time-p\n\t   #:day-of-year\n\t   #:date-to-days\n\t   #:day-of-week\n\t   #:weeks-in-year\n\t   #:delta-days\n\t   #:week-number\n\t   #:week-of-year\n\t   #:add-delta-days\n\t   #:monday-of-week\n\t   #:nth-weekday-of-month-year\n\t   #:standard-to-business\n\t   #:business-to-standard\n\t   #:delta-hms\n\t   #:delta-dhms\n\t   #:delta-ymd\n\t   #:delta-ymdhms\n\t   #:normalize-dhms\n\t   #:add-delta-dhms\n\t   #:add-year-month\n\t   #:add-delta-ym\n\t   #:add-delta-ymd\n\t   #:add-delta-ymdhms\n\t   #:system-clock\n\t   #:cl-system-clock\n\t   #:gmtime\n\t   #:localtime\n\t   #:today\n\t   #:yesterday\n\t   #:tomorrow\n\t   #:now\n\t   #:today-and-now\n\t   #:this-year\n\t   #:date-to-text\n\t   #:date-to-text-long\n\t   #:cl-day-of-week\n\t   #:cl-weeks-in-year\n\t   #:cl-check-business-p\n\t   #:cl-nth-weekday-of-month-year\n\t   #:cl-standard-to-business\n\t   #:cl-business-to-standard))\n\n(pushnew :date-calc *features*)\n(in-package #:date-calc)\n\n;;;; Parameters\n(defparameter year-of-epoc 70 \"Year of reference (epoc)\")\n(defparameter century-of-epoc 1900 \"Century of reference (epoc)\")\n(defparameter eopoc (+ year-of-epoc century-of-epoc) \"reference year (epoc)\")\n\n(defparameter days-in-year-arr (make-array '(2 13) :initial-contents\n\t\t\t\t\t\t     '((0 31 59 90 120 151 181 212 243 273 304 334 365)\n\t\t\t\t\t\t       (0 31 60 91 121 152 182 213 244 274 305 335 366))))\n\n(defparameter days-in-month-arr (make-array '(2 13) :initial-contents\n\t\t\t\t\t\t      '((0 31 28 31 30 31 30 31 31 30 31 30 31)\n\t\t\t\t\t\t\t(0 31 29 31 30 31 30 31 31 30 31 30 31))))\n\n(defparameter languages 11)\n(defparameter *language* 1) ; Default English\n\n;; (defconstant num-of-lingos (1+ languages))\n\n(defparameter month-to-text (make-hash-table))\n(setf (gethash 0 month-to-text)\n      #(\"???\" \"???\" \"???\" \"???\"\n\t\"???\" \"???\" \"???\" \"???\"\n\t\"???\" \"???\" \"???\" \"???\" \"???\"))\n(setf (gethash 1 month-to-text)\n      #(\"???\" \"January\" \"February\" \"March\"\n\t\"April\" \"May\" \"June\" \"July\" \"August\"\n\t\"September\" \"October\" \"November\" \"December\"))\n(setf (gethash 2 month-to-text)\n      #(\"???\" \"janvier\" \"fevrier\" \"mars\"\n\t\"avril\" \"mai\" \"juin\" \"juillet\" \"aout\"\n\t\"septembre\" \"octobre\" \"novembre\" \"decembre\"))\n(setf (gethash 3 month-to-text)\n      #(\"???\" \"Januar\" \"Februar\" \"Maerz\"\n\t\"April\" \"Mai\" \"Juni\" \"Juli\" \"August\"\n\t\"September\" \"Oktober\" \"November\" \"Dezember\"))\n(setf (gethash 4 month-to-text)\n      #(\"???\" \"enero\" \"febrero\" \"marzo\"\n\t\"abril\" \"mayo\" \"junio\" \"julio\" \"agosto\"\n\t\"septiembre\" \"octubre\" \"noviembre\" \"diciembre\"))\n(setf (gethash 5 month-to-text)\n      #(\"???\" \"janeiro\" \"fevereiro\" \"marco\"\n\t\"abril\" \"maio\" \"junho\" \"julho\" \"agosto\"\n\t\"setembro\" \"outubro\" \"novembro\" \"dezembro\"))\n(setf (gethash 6 month-to-text)\n      #(\"???\" \"januari\" \"februari\" \"maart\"\n\t\"april\" \"mei\" \"juni\" \"juli\" \"augustus\"\n\t\"september\" \"october\" \"november\" \"december\"))\n(setf (gethash 7 month-to-text)\n      #(\"???\" \"Gennaio\" \"Febbraio\" \"Marzo\"\n\t\"Aprile\" \"Maggio\" \"Giugno\" \"Luglio\" \"Agosto\"\n\t\"Settembre\" \"Ottobre\" \"Novembre\" \"Dicembre\"))\n(setf (gethash 8 month-to-text)\n      #(\"???\" \"januar\" \"februar\" \"mars\"\n\t\"april\" \"mai\" \"juni\" \"juli\" \"august\"\n\t\"september\" \"oktober\" \"november\" \"desember\"))\n(setf (gethash 9 month-to-text)\n      #(\"???\" \"januari\" \"februari\" \"mars\"\n\t\"april\" \"maj\" \"juni\" \"juli\" \"augusti\"\n\t\"september\" \"oktober\" \"november\" \"december\"))\n(setf (gethash 10 month-to-text)\n      #(\"???\" \"januar\" \"februar\" \"marts\"\n\t\"april\" \"maj\" \"juni\" \"juli\" \"august\"\n\t\"september\" \"oktober\" \"november\" \"december\"))\n(setf (gethash 11 month-to-text)\n      #(\"???\" \"tammikuu\" \"helmikuu\" \"maaliskuu\"\n\t\"huhtikuu\" \"toukokuu\" \"kesaekuu\" \"heinaekuu\"\n\t\"elokuu\" \"syyskuu\" \"lokakuu\" \"marraskuu\" \"joulukuu\"))\n\n(defparameter day-of-week-to-text (make-hash-table))\n(setf (gethash 0 day-of-week-to-text)\n      #(\"???\" \"???\" \"???\" \"???\" \"???\" \"???\" \"???\" \"???\"))\n(setf (gethash 1 day-of-week-to-text)\n      #(\"???\" \"Monday\" \"Tuesday\" \"Wednesday\" \"Thursday\" \"Friday\" \"Saturday\" \"Sunday\"))\n(setf (gethash 2 day-of-week-to-text)\n      #(\"???\" \"Lundi\" \"Mardi\" \"Mercredi\" \"Jeudi\" \"Vendredi\" \"Samedi\" \"Dimanche\"))\n(setf (gethash 3 day-of-week-to-text)\n      #(\"???\" \"Montag\" \"Dienstag\" \"Mittwoch\" \"Donnerstag\" \"Freitag\" \"Samstag\" \"Sonntag\"))\n(setf (gethash 4 day-of-week-to-text)\n      #(\"???\" \"Lunes\" \"Martes\" \"Miercoles\" \"Jueves\" \"Viernes\" \"Sabado\" \"Domingo\"))\n(setf (gethash 5 day-of-week-to-text)\n      #(\"???\" \"Segunda-feira\" \"Terca-feira\" \"Quarta-feira\" \"Quinta-feira\" \"Sexta-feira\" \"Sabado\" \"Domingo\"))\n(setf (gethash 6 day-of-week-to-text)\n      #(\"???\" \"Maandag\" \"Dinsdag\" \"Woensdag\" \"Donderdag\" \"Vrijdag\" \"Zaterdag\" \"Zondag\"))\n(setf (gethash 7 day-of-week-to-text)\n      #(\"???\" \"Lunedi\" \"Martedi\" \"Mercoledi\" \"Giovedi\" \"Venerdi\" \"Sabato\" \"Domenica\"))\n(setf (gethash 8 day-of-week-to-text)\n      #(\"???\" \"mandag\" \"tirsdag\" \"onsdag\" \"torsdag\" \"fredag\" \"loerdag\" \"soendag\"))\n(setf (gethash 9 day-of-week-to-text)\n      #(\"???\" \"mandag\" \"tisdag\" \"onsdag\" \"torsdag\" \"fredag\" \"loerdag\" \"soendag\"))\n(setf (gethash 10 day-of-week-to-text)\n      #(\"???\" \"mandag\" \"tirsdag\" \"onsdag\" \"torsdag\" \"fredag\" \"loerdag\" \"soendag\"))\n(setf (gethash 11 day-of-week-to-text)\n      #(\"???\" \"maanantai\" \"tiistai\" \"keskiviikko\" \"torstai\" \"perjantai\" \"lauantai\" \"sunnuntai\"))\n\n(defparameter day-of-week-abbreviation (make-hash-table))\n(setf (gethash  0 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash  1 day-of-week-abbreviation) #(\"??\" \"Mon\" \"Tue\" \"Wen\" \"Thu\" \"Fri\" \"Sat\" \"Sun\"))\n(setf (gethash  2 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash  3 day-of-week-abbreviation) #(\"??\" \"Mo\" \"Di\" \"Mi\" \"Do\" \"Fr\" \"Sa\" \"So\"))\n(setf (gethash  4 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash  5 day-of-week-abbreviation) #(\"???\" \"2\" \"3\" \"4\" \"5\" \"6\" \"Sam\" \"Dom\"))\n(setf (gethash  6 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash  7 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash  8 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash  9 day-of-week-abbreviation) #(\"??\" \"Mo\" \"Ti\" \"On\" \"To\" \"Fr\" \"Lo\" \"So\"))\n(setf (gethash 10 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n(setf (gethash 11 day-of-week-abbreviation) #(\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\"))\n\n(defparameter long-format (make-array '(12) :initial-contents\n  '((\"~A, ~A ~A ~A\" 10)                     ;   0  Default, the second value describes order:\n    (\"~A, ~A ~A ~A\" 10)                     ;   1  English    11=DMY 10=MDY see #'date-to-text-long     \n    (\"~A ~A ~A ~A\"  10)                     ;   2  Francais    \n    (\"~A, den ~A ~A ~A\" 11)                 ;   3  Deutsch     \n    (\"~A, ~A de ~A de ~A\" 10)               ;   4  Espanol     \n    (\"~A, dia ~A de ~A de ~A\" 10)           ;   5  Portugues   \n    (\"~A, ~A ~A ~A\" 10)                     ;   6  Nederlands  \n    (\"~A, ~A ~A ~A\" 10)                     ;   7  Italiano    \n    (\"~A, ~A. ~A ~A\" 10)                    ;   8  Norsk       \n    (\"~A, ~A ~A ~A\" 10)                     ;   9  Svenska     \n    (\"~A, ~A. ~A ~A\" 10)                    ;  10  Dansk       \n    (\"~A, ~A. ~A ta ~A\" 10))))              ;  11  suomi       \n\n(defparameter language-to-text\n  (vector \"???\" \"English\" \"Francais\" \"Deutsch\" \"Espanol\"\n\t  \"Portugues\" \"Nederlands\" \"Italiano\" \"Norsk\"\n\t  \"Svenska\" \"Dansk\" \"suomi\"))\n\n;;;; Functions\n(defun decode-day-of-week (str)\n  \"Returns number of weekday. STR can partially name the Weekday. DOW is not CL conform.\"\n  (let ((week-vector (gethash *language* day-of-week-to-text))\n\t(i 0))\n    (loop for weekday across week-vector\n\t  until (search str weekday :test #'char-equal)\n\t  do (incf i)\n\t  finally (return (if (<= i 7) i nil)))))\n\n(defun cl-decode-day-of-week (str)\n  \"Returns number of weekday. STR can partially name the Weekday. DOW is CL conform.\"\n  (let ((week-vector (gethash *language* day-of-week-to-text))\n\t(i 0))\n    (loop for weekday across week-vector\n\t  until (search str weekday :test #'char-equal)\n\t  do (incf i)\n\t  finally (return (if (<= i 7) (1- i) nil)))))\n\n(defun decode-month (str)\n  \"Returns number of month. STR can partially name the month. Computes a (search ...:test #'char-equal).\"\n  (let ((month-vector (gethash *language* month-to-text))\n\t(i 0))\n    (loop for month across month-vector\n\t  until (search str month :test #'char-equal)\n\t  do (incf i)\n\t  finally (return (if (<= i 12) i nil)))))\n\n(defun decode-language (num)\n  \"Returns the Language of number NUM.\"\n  (svref language-to-text num))\n\n(defun iso-lc (char)\n  \"Returns lower case CHAR.\"\n  (char-downcase char))\n\n(defun iso-uc (char)\n  \"Returns upper case CHAR.\"\n  (char-upcase char))\n\n(defun year-to-days (year)\n  \"Returns the number of days for YEAR since 1 Jan 1.\"\n  (+ (- (+ (* year 365) (ash year -2))\n\t(floor (/ (ash year -2) 25)))\n     (ash (floor (/ (ash year -2) 25)) -2)))\n\n(defun fixed-window (year)\n  \"Convert two digit YEAR to four digit YEAR; YEAR<=70 -> 2000+YEAR; YEAR<100&&>70  -> 1900+YEAR.\"\n  (if (and (> year 70) (< year 100))\n      (+ 1900 year)\n      (+ 2000 year)))\n\n(defun center (string width)\n  \"Return a string that is WIDTH long with STRING centered in it.\"\n  (let* ((pad (- width (length string)))\n\t (lpad (truncate pad 2))\n\t (rpad (- pad (truncate pad 2))))\n    (if (<= pad 0)\n\tstring\n\t(concatenate 'string (make-string lpad :initial-element #\\Space) string (make-string rpad :initial-element #\\Space)))))\n\n(defun normalize-time (dd dh dm ds)\n\"Internal fn for normalize-dhms. Returns the normalized (values DD DH DM DS).\"\n  (values (+ dd (floor (+ dh (floor (+ dm (floor ds 60)) 60)) 24)) ; dd\n\t  (- (+ dh (floor (+ dm (floor ds 60)) 60))\n\t     (* (floor (+ dh (floor (+ dm (floor ds 60)) 60)) 24) 24)) ; dh\n\t  (- (+ dm (floor ds 60)) (* (floor (+ dm (floor ds 60)) 60) 60)) ;dm\n\t  (- ds (* (floor ds 60) 60)))) ;ds\n\n(defun normalize-ranges (dd dh dm ds)\n\"Internal fn for normalize-dhms. Returns the normalized (values DD DH DM DS). This function prevents overflow errors on systems with short longs (e.g. 32-bits) (If need be for CL ???).\"\n  (normalize-time (+ dd (floor dh 24))\n\t\t\t      (+ (- dh (* (floor dh 24) 24)) (floor dm 60))\n\t\t\t      (- dm (* (floor dm 60) 60))\n\t\t\t      ds))\n\n(defun normalize-signs (dd dh dm ds)\n\"Internal fn for normalize-dhms.\"\n  (let* ((quot (floor ds 86400))\n\t (ds1 (- ds (* quot 86400)))\n\t (dd1 (+ dd quot)))\n    (setq dh 0 dm 0)\n    (if (not (= dd1 0))\n\t(if (> dd1 0)\n\t    (when (< ds 0)\n\t      (setq ds1 (+ ds 86400)\n\t\t    dd1 (1- dd1)))\n\t    (when (> ds 0)\n\t      (setq ds1 (- ds 86400)\n\t\t    dd1 (1+ dd1)))))\n    (if (not (= ds1 0))\n\t(normalize-time dd1 dh dm ds1)\n\t(values dd1 dh dm ds1))))\n\n(defun valid-year-p (year) (>= year 1))\n(defun valid-month-p (month) (and month (>= month 1) (<= month 12)))\n\n(defun leap-year (year)\n  \"This function returns 1 if the given YEAR is a leap year and 0 otherwise.\"\n  (if (or (and (zerop (mod year 4))\n\t       (not (zerop (mod year 100))))\n\t  (zerop (mod year 400)))\n      1\n      0))\n\n(defun leap-year-p (year)\n  \"This function returns t if the given YEAR is a leap year and nil otherwise.\"\n  (if (or (and (zerop (mod year 4))\n\t       (not (zerop (mod year 100))))\n\t  (zerop (mod year 400)))\n      t\n      nil))\n\n(defun days-in-month (year month)\n  \"This function returns the number of days in the given MONTH of the given YEAR.\"\n  (if (and (valid-year-p year)\n\t   (valid-month-p month))\n      (aref days-in-month-arr (leap-year year) month)))\n\n(defun days-in-year (year &optional month)\n  \"This function returns the number of days in the given YEAR and optional MONTH. If MONTH is [1..12], return the number of days in that YEAR as of the last of that MONTH.\"\n  (aref days-in-year-arr (leap-year year) (if (and month (>= month 0) (<= month 12))\n\t\t\t\t\t\t\t\t  month\n\t\t\t\t\t\t\t\t  12)))\n\n(defun check-date (year month day)\n  \"This function returns t if the given three numerical values YEAR MONTH DAY constitute a valid date, and nil otherwise.\"\n  (and (valid-year-p year)\n       (valid-month-p month)\n       (>= day 1)\n       (<= day (days-in-month year month))))\n\n(defun check-time-p (hour min sec)\n  \"This function returns t if the given three numerical values HOUR MIN SEC constitute a valid time, and nil otherwise.\"\n    (and (>= hour 0) (< hour 24)\n\t (>= min 0) (< min 60)\n\t (>= sec 0) (< sec 60)))\n\n(defun day-of-year (year month day)\n  \"This function returns the sum of the number of days in the months starting with January up to and including MONTH in\n    the given year YEAR. 0 on failure.\"\n  (if (check-date year month day)\n      (+ day (aref days-in-year-arr (leap-year year) (1- month)))\n      0))\n\n(defun date-to-days (year month day)\n  \"This function returns the (absolute) number of the day of the given date, where counting starts at the 1.Jan 1.\"\n  (if (check-date year month day)\n      (+ (year-to-days (1- year))\n\t (day-of-year year month day))\n      0))\n\n(defun day-of-week (year month day)\n  \"This function returns the DOW of YEAR MONTH DAY. DOW not CL conform.\"\n  (let ((days (date-to-days year month day)))\n    (if (> days 0)\n\t(1+ (mod (1- days) 7))\n\tdays)))\n\n(defun cl-day-of-week (year month day)\n  \"This function returns the DOW of YEAR MONTH DAY. DOW CL conform.\"\n  (let ((days (date-to-days year month day)))\n    (if (> days 0)\n\t(mod (1- days) 7)\n\tdays)))\n\n(defun weeks-in-year (year)\n  \"This function returns the number of weeks in the given YEAR, i.e., either 52 or 53.\"\n  (if (or (= 4 (day-of-week year 1 1))\n\t  (= 4 (day-of-week year 12 31)))\n      53 52))\n\n(defun cl-weeks-in-year (year)\n  \"This function returns the number of weeks in the given YEAR for CL DOW conform numbering (Monday=0)., i.e., either 52 or 53.\"\n  (if (or (= 3 (cl-day-of-week year 1 1))\n\t  (= 3 (cl-day-of-week year 12 31)))\n      53 52))\n\n(defun check-business-p (year week dow)\n    \"This function returns true if the given three numerical values YEAR WEEK DOW constitute a valid date in business format, and nil otherwise. Beware that this function does NOT compute whether a given date is a business day (i.e., Monday to Friday)! To do so, use (< (day-of-week year month day) 6) instead. DOW not CL conform.\"\n  (and (>= year 1)\n       (>= week 1)\n       (<= week (weeks-in-year year))\n       (>= dow 1)\n       (<= dow 7)))\n\n(defun cl-check-business-p (year week dow)\n    \"This function returns true if the given three numerical values YEAR WEEK DOW constitute a valid date in business format for CL (Monday=0), and nil otherwise. DOW is CL conform.\"\n  (and (>= year 1)\n       (>= week 1)\n       (<= week (weeks-in-year year))\n       (>= dow 0)\n       (<= dow 6)))\n\n(defun delta-days (year1 month1 day1 year2 month2 day2)\n  \"This function returns the difference in days between Y1 M1 D1 and Y2 M2 D2.\"\n  (- (date-to-days year2 month2 day2)\n     (date-to-days year1 month1 day1)))\n\n(defun week-number (year month day)\n  \"This function returns the number of the week of the given Y M D lies in. If the given date lies in the LAST week of the PREVIOUS year, 0 is returned.\"\n  (let ((first-jan (1- (day-of-week year 1 1))))\n    (if (< first-jan 4)\n\t(1+ (truncate (+ first-jan (delta-days year 1 1 year month day)) 7))\n\t(+ 0 (truncate (+ first-jan (delta-days year 1 1 year month day)) 7))))) ; + 0..-> only return one value\n\n(defun week-of-year (year month day)\n  \"Return (values week year) where week is the week number of YEAR\"\n    (if (not (check-date year month day))\n\tnil\n\t(progn\n\t  (let ((week (week-number year month day)))\n\t    (if (= week 0)\n\t\t(values (weeks-in-year (1- year)) year)\n\t\t(progn\n\t\t  (if (> week (weeks-in-year year))\n\t\t      (values 1 (1+ year))\n\t\t      (values week year))))))))\n\n(defun add-delta-days (year month day delta)\n  \"This function returns (values year month day) such that it is YEAR MONTH DAY plus DELTA days\"\n;; Be careful when changing things in this fn ! Side effects !\n;; Fairly direct port from the PERL routine. Pretty imperative style.\n  (let* ((days (+ (date-to-days year month day) delta))\n\t (y1 (round (/ days 365.2425)))\n\t (d1 (- days (year-to-days y1))))\n    (when (> days 0)\n      (progn\n\t(if (< d1 1)\n\t    (setf d1 (- days (year-to-days (1- y1)))) ; then\n\t    (setf y1 (1+ y1)))\t\t; else\n\t(if (> d1 (days-in-year y1))\n\t    (setf d1 (- d1 (days-in-year y1))\n\t\t  y1 (1+ y1)))\n\t(loop for index downfrom 12 to 1\n\t      until (> d1 (days-in-year y1 index))\n\t      finally (return (values y1 (1+ index) (- d1 (days-in-year y1 index))))))))) ; index=month just one to low here after until, thats why (1+ index) as return value\n\n(defun monday-of-week (week year)\n  \"Return (values year month day) where month and day correspond to the Monday of WEEK in YEAR\"\n  (let ((erst (1- (day-of-week year 1 1))))\n    (if (< erst 4)\n\t(add-delta-days year 1 1 (- (* (1- week) 7) erst))\n\t(add-delta-days year 1 1 (- (* week 7) erst)))))\n\n(defun nth-weekday-of-month-year (year month dow n)\n  \"This function returns the (year month day) of the N-th day of week DOW in the given MONTH and YEAR; such as, for example, the 3rd Thursday of a given month and year. DOW is not CL conform.\"\n  (when (and (check-date year month 1) ; check params\n\t     (>= dow 1) (<= dow 7)\n\t     (> n 0) (< n 5))\n    (let* ((erst (day-of-week year month 1))\n\t   (tow (if (< dow erst)\n\t\t    (+ dow 7)\n\t\t    dow)))\n      (multiple-value-bind (y m d)\n\t  (add-delta-days year month 1 (+ (- tow erst) (* (1- n) 7)))\n\t(when (= month m)\n\t  (values y m d))))))\n\n(defun cl-nth-weekday-of-month-year (year month dow n)\n  \"This function returns the (year month day) of the N-th day of week DOW in the given MONTH and YEAR; such as, for example, the 3rd Thursday of a given month and year. DOW is CL conform.\"\n  (when (and (check-date year month 1) ; check params\n\t     (>= dow 0) (<= dow 6)\n\t     (> n 0) (< n 5))\n    (let* ((erst (cl-day-of-week year month 1))\n\t   (tow (if (< dow erst)\n\t\t    (+ dow 7)\n\t\t    dow)))\n      (multiple-value-bind (y m d)\n\t  (add-delta-days year month 1 (+ (- tow erst) (* (1- n) 7)))\n\t(when (= month m)\n\t  (values y m d))))))\n\n(defun standard-to-business (year month day)\n  \"This function converts a given date from standard notation YEAR MONTH DAY to business notation year week dow. DOW is not CL conform.\"\n  (multiple-value-bind (week y) (week-of-year year month day)\n    (when week\n      (values y week (day-of-week year month day)))))\n\n(defun cl-standard-to-business (year month day)\n  \"This function converts a given date from standard notation YEAR MONTH DAY to business notation year week day of week. DOW is CL conform.\"\n  (multiple-value-bind (week y) (week-of-year year month day)\n    (when week\n      (values y week (cl-day-of-week year month day)))))\n\n\n(defun business-to-standard (year week dow)\n  \"This function converts a given date from business notation YEAR WEEK DOW to standard notation year month day. DOW is not CL conform.\"\n  (when (check-business-p year week dow)\n    (let* ((erst (day-of-week year 1 1))\n\t   (delta (+ (- dow erst) (* 7 (1- (+ week (if (> erst 4) 1 0)))))))\n      (add-delta-days year 1 1 delta))))\n\n(defun cl-business-to-standard (year week dow)\n  \"This function converts a given date from business notation YEAR WEEK DOW to standard notation year month day. DOW is CL conform.\"\n  (when (cl-check-business-p year week dow)\n    (let* ((erst (cl-day-of-week year 1 1))\n\t   (delta (+ (- dow erst) (* 7 (1- (+ week (if (> erst 4) 1 0)))))))\n      (add-delta-days year 1 1 delta))))\n\n(defun delta-hms (hour1 min1 sec1 hour2 min2 sec2)\n  \"This function returns the difference of H1 M1 S1 and H2 M2 S2 in (values d h m s).\"\n  (when (and (check-time-p hour1 min1 sec1)\n\t     (check-time-p hour2 min2 sec2))\n    (normalize-signs 0 0 0 (- (+ sec2 (* 60 (+ min2 (* 60 hour2))))\n\t\t\t\t\t(+ sec1 (* 60 (+ min1 (* 60 hour1))))))))\n\n(defun delta-dhms (year1 month1 day1 hour1 min1 sec1 year2 month2 day2 hour2 min2 sec2)\n  \"Returns the difference in (values d h m s) between the two given dates with times (Y1 M1 D1 H1 MIN1 SEC1 and Y2 M2 D2 H2 MIN2 SEC2).\"\n  (let ((dd (delta-days year1 month1 day1 year2 month2 day2)))\n    (multiple-value-bind (d h m s) (delta-hms hour1 min1 sec1 hour2 min2 sec2)\n      (if d\n\t  (values (+ d dd) h m s)\n\t  (values d h m s)))))\n\n(defun delta-ymd (year1 month1 day1 year2 month2 day2)\n  \"This function returns the difference (values YEAR MONTH DAYS) between the two dates Y1M1D1 and Y2M2D2.\"\n  (if (and (check-date year1 month1 day1)\n\t   (check-date year2 month2 day2))\n      (values (- year2 year1)(- month2 month1)(- day2 day1))\n      nil))\n\n(defun delta-ymdhms (year1 month1 day1 hour1 min1 sec1\n\t\t\t\t year2 month2 day2 hour2 min2 sec2)\n  \"This function returns the difference (values YEAR MONTH DAYS HOUR MINUTE SEC) between\nthe two dates Y1 M1 D1 H1 MI1 S1 and Y2 M2 D2 H2 MI2 S2.\"\n  (multiple-value-bind (y m d) (delta-ymd year1 month1 day1 year2 month2 day2)\n    (when y\n      (multiple-value-bind (dd hh mm ss)\n\t  (delta-hms hour1 min1 sec1 hour2 min2 sec2)\n\t(when dd\n\t  (values y m (+ dd d) hh mm ss))))))\n\n(defun normalize-dhms (day hour min sec)\n  \"This function takes four arbitrary values for days, hours, minutes and seconds (which may have different signs) and renormalizes them so that the values for hours, minutes and seconds will lie in the ranges [-23..23], [-59..59] and [-59..59], respectively, and so that they have the same sign.\"\n  (multiple-value-bind (dd dh dm ds) (normalize-ranges day hour min sec)\n    (when ds\n      (normalize-signs dd dm dh (+ ds (* 60 (+ dm (* 60 dh))))))))\n\n(defun add-delta-dhms (year month day hour min sec dd dh dm ds)\n  \"This function serves to add a days, hours, minutes and seconds offset to a given date and time (YEAR MONTH DAY HOUR MINUTE SECOND DDAY DHOUR DMINUTE DSECOND), in order to answer questions like \\\"today and now plus 7 days but minus 5 hours and then plus 30 minutes, what date and time gives that?\\\". Returns: (values y m d h min sec)\"\n  (when (and (check-date year month day)\n\t     (check-time-p hour min sec))\n    (multiple-value-bind (d1 h1 m1 s1) (normalize-ranges dd dh dm ds)\n      (when d1\n\t(progn\n\t  (let ((s2 (+ s1 (* 60 (+ m1 (* 60 h1))) (+ sec (* 60 (+ min (* 60 hour)))))))\n\t    (when (= 0 s2)\n\t\t(multiple-value-bind (yy mm ddd) (add-delta-days year month day d1)\n\t\t  (values yy mm ddd 0 0 0)))\n\t    (when (< s2 0)\n\t      (multiple-value-bind (dd1 ss2) (truncate s2 86400)\n\t\t(multiple-value-bind (ddd hh mm ss) (normalize-time (+ d1 dd1) 0 0 ss2)\n\t\t  (multiple-value-bind (yy mmm dddd) (add-delta-days year month day ddd)\n\t\t    (values yy mmm dddd hh mm ss)))))\n\t    (when (> s2 0)\n\t      (multiple-value-bind (ddd hh mm ss) (normalize-time d1 0 0 s2)\n\t\t(multiple-value-bind (yy mmm dddd) (add-delta-days year month day ddd)\n\t\t  (values yy mmm dddd hh mm ss))))))))))\n\n(defun add-year-month (year month dy dm)\n  \"This function adds DYEAR and DMONTH offset to YEAR and MONTH.\"\n  (let ((mt (+ month dm)))\n    (if (> mt 0)\n\t(multiple-value-bind (jahre monate) (truncate (1- mt) 12)\n\t  (values (+ jahre (+ year dy)) (1+ monate)))\n\t(multiple-value-bind (jahre monate) (truncate mt 12)\n\t  (values (+ (+ year dy) jahre -1) (+ 12 monate))))))\n\n(defun add-delta-ym (year month day dy dm)\n  \"This function adds DYEAR and DMONTH offset to YEAR MONTH DAY.\"\n  (when (check-date year month day)\n    (multiple-value-bind (jahr monat) (add-year-month year month dy dm)\n      (values jahr monat day))))\n\n(defun add-delta-ymd (year month day dy dm dd)\n  \"This function adds DYEAR DMONTH and DDAY offset to YEAR MONTH DAY.\"\n  (when (check-date year month day)\n      (multiple-value-bind (jahr monat tag) (add-delta-ym year month day dy dm)\n\t(when jahr\n\t  (add-delta-days jahr monat tag dd)))))\n\n(defun add-delta-ymdhms (year month day hour min sec dyear dmonth dday dh dm ds)\n  \"This function is the same as add-delta-ymd except that a time offset may be given in addition to the year, month and day offset\"\n  (multiple-value-bind (jahr monat) (add-year-month year month dyear dmonth)\n    (when jahr\n      (add-delta-dhms jahr monat 1 hour min sec (+ dday (1- day)) dh dm ds))))\n\n(defun system-clock (gmt time)\n  \"This function returns (values year month day hour min sec doy dow dst) based on current system clock. DOW is not CL conform.\"\n  (multiple-value-bind (second minute hour day month year dow daylight-p dst)\n      (decode-universal-time time)\n    (declare (ignorable daylight-p))\n    (let ((doy (day-of-year year month day)))\n      (if gmt\n\t  (multiple-value-bind (jahr monat tag std min sek)\n\t      (add-delta-dhms year month day hour minute second 0 0 dst 0)\n\t    (values jahr monat tag std min sek doy (1+ dow) dst))\n\t  (values year month day hour minute second doy (1+ dow) dst)))))\n\n(defun cl-system-clock (gmt time)\n  \"This function returns (values year month day hour min sec doy dow dst) based on current system clock. DOW is CL conform.\"\n  (multiple-value-bind (second minute hour day month year dow daylight-p dst)\n      (decode-universal-time time)\n    (declare (ignorable daylight-p))\n    (let ((doy (day-of-year year month day)))\n      (if gmt\n\t  (multiple-value-bind (jahr monat tag std min sek)\n\t      (add-delta-dhms year month day hour minute second 0 0 dst 0)\n\t    (values jahr monat tag std min sek doy dow dst))\n\t  (values year month day hour minute second doy dow dst)))))\n\n;;;;;;; Add gmt flag\n(defun gmtime ()\n  (system-clock t (get-universal-time)))\n\n(defun localtime ()\n  (system-clock nil (get-universal-time)))\n\n(defun today ()\n  \"This function returns (year month day) for today.\"\n  (multiple-value-bind (sec minute hour day month year) (get-decoded-time)\n    (declare (ignorable sec minute hour))\n    (values year month day)))\n\n(defun yesterday ()\n  (multiple-value-bind (jahr monat tag) (today)\n    (add-delta-days jahr monat tag -1)))\n\n(defun tomorrow ()\n  (multiple-value-bind (jahr monat tag) (today)\n    (add-delta-days jahr monat tag 1)))\n\n(defun now ()\n  \"This function returns (hour minute second) for right now.\"\n  (multiple-value-bind (second minute hour) (get-decoded-time)\n    (values hour minute second)))\n\n(defun today-and-now ()\n  \"This function returns (year month day hour minute second) for the current date and time.\"\n  (multiple-value-bind (second minute hour day month year) (get-decoded-time)\n    (values year month day hour minute second)))\n\n(defun this-year ()\n  \"This function returns the current year in localtime.\"\n  (multiple-value-bind (second minute hour day month year) (get-decoded-time)\n    (declare (ignorable second minute hour day month))\n    year))\n\n(defun date-to-text (year month day)\n  \"Return a pretty print string of YEAR MONTH DAY in DOW-TXT(SHORT) DAY MONTH YEAR with a little bit of sorting for language.\"\n  (let ((prn (first (aref long-format *language*)))) ; get print format\n    (multiple-value-bind (a b c) ; What order is the date DMY , MDY ....\n\t(let ((k (second (aref long-format *language*))))\n\t  (case k   ; return the order of DMY\n\t    (10 (values month day year))\n\t    (11 (values day month year))\n\t    (otherwise (values month day year)))) ; return english by default\n      (format nil prn  ; make the return string\n\t      (svref (gethash *language* day-of-week-abbreviation) ; Get Name of Weekday\n\t\t     (day-of-week year month day))\n\t      a b c))))\n\n(defun date-to-text-long (year month day)\n  \"Return a pretty print string of YEAR MONTH DAY in DOW-TXT(LONG) DAY MONTH YEAR with a little bit of sorting for language.\"\n  (let ((prn (first (aref long-format *language*)))) ; get print format\n    (multiple-value-bind (a b c) ; What order is the date DMY , MDY ....\n\t(let ((k (second (aref long-format *language*))))\n\t  (case k   ; return the order of DMY\n\t    (10 (values month day year))\n\t    (11 (values day month year))\n\t    (otherwise (values month day year)))) ; return english by default\n      (format nil prn  ; make the return string\n\t      (svref (gethash *language* day-of-week-to-text) ; Get Name of Weekday\n\t\t     (day-of-week year month day))\n\t      a b c))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/make-fdf-file.lisp",
    "content": ";; -*- Lisp -*-\n\n;; make-fdf-file.lisp\n\n;; Funktion zum Erstellen von FDF-Dateien.  Diese können mit Hilfe von\n;; pdftk verwendet werden, um PDF-Formulare auszufuellen.  Das\n;; FDF-Format ist dabei ein Unterformat von Adobe PDF und wird in der\n;; PDF-Spezifikation beschrieben.\n\n(in-package :bknr.utils)\n\n(enable-interpol-syntax)\n\n(defun pdf-quote-string (string)\n  (regex-replace-all #?r\"([\\(\\)\\\\])\" string #?r\"\\\\\\1\"))\n\n(defun make-fdf-file (file-name &rest keys-and-values)\n  (with-open-file (stream file-name :direction :output :if-does-not-exist :create :if-exists :supersede :external-format :latin-1)\n    (format stream \"%FDF-1.2\n1 0 obj\n<</FDF\n <</Fields\n  [\n\")\n    (loop for (key value) on keys-and-values by #'cddr\n       do (format stream \"   <</T(~(~a~))/V(~a)>>~%\" key\n                  (pdf-quote-string (if (stringp value)\n                                        value\n                                        (format nil \"~a\" value)))))\n    (format stream \"  ]\n >>\n>>\nendobj\ntrailer\n<</Root 1 0 R>>\n%%EOF\")))\n    \n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.utils\n  (:use :cl\n        :cl-ppcre\n        :cl-interpol\n        :md5\n        #+sbcl :sb-ext\n        #+openmcl :ccl)\n  #+openmcl\n  (:shadow :ccl #:copy-file #:make-process)\n  (:shadowing-import-from :cl-interpol quote-meta-chars)\n  (:export #:define-bknr-class\n\n           ;; byte size formatting\n           #:scale-bytes\n   \n           ;; date format\n           #:format-date-time\n           #:format-time-interval\n           #:format-duration\n           #:year-interval\n           #:month-interval\n           #:day-interval\n           #:timetag\n           #:daytag\n           #:get-daytime\n           #:get-hourtime\n           #:get-monthtime\n           #:previous-day\n           #:next-day\n           #:month-num-days\n\n\t   ;; shell functions\n\t   #:run-shell-command-to-string\n\t   #:run-shell-command\n\t   \n           #:hostname\n           #:parse-time\n\n           ;; filesystem functions\n           #:move-file\n           #:directory-empty-p\n           #:subdir-p\n           #:make-temporary-pathname\n           #:with-temporary-file\n           #:file-contents\n           #:parent-directory\n   \n           ;; list functions\n           #:delete-first\n           #:make-keyword-from-string\n\n           #:assoc-values\n           #:assoc-to-keywords\n           #:insert-at-index\n           #:find-neighbourhood\n           #:group-by\n           #:group-on\n           #:find-all\n           #:genlist\n           #:nrotate\n           #:shift-until\n           #:count-multiple\n\n           ;; hash table\n           #:hash-to-list\n           #:hash-values\n           #:hash-keys\n           #:incf-hash\n\n           ;; randomize\n           #:random-elts\n           #:randomize-list\n\n           ;; md5\n           #:hash-to-hex\n           #:md5-string\n\n           ;; capabilty\n           #:make-capability-string\n\n           ;; content-types\n           #:pathname-type-symbol\n           #:image-content-type\n           #:pathname-content-type\n           #:image-type-symbol\n\n           ;; utf-8\n           #:convert-utf8-to-latin1\n\n           ;; strings\n           #:find-matching-strings\n           #:make-extendable-string\n\n           ;; stream\n           #:read-delimited\n           #:read-file\n\n           ;; smbpasswd\n           #:set-smb-password\n           #:smb-password-error\n\n           ;; actor\n           #:bknr-actor\n           #:bknr-actor-name\n           #:run-function\n           #:actor-start\n           #:actor-stop\n           #:actor-running-p\n\n           ;; cron\n           #:cron-actor\n\n           ;; reader\n           #:whitespace-char-p\n           #:whitespace-p\n           #:bknr-read-delimited-list\n           #:bknr-read\n           #:string-beginning-with-p\n           #:string-delimited-by-p\n\n           ;; crypt-md5\n           #:crypt-md5\n           #:verify-md5-password\n\n           ;; FDF creation\n           #:make-fdf-file\n\n           #:remove-keys\n           #:eval-initargs\n\n           ;; Package cleaning for the build process\n           #:within-temporary-package\n\n           ;; mp compatibility\n           #:mp-make-lock\n           #:mp-with-lock-held\n           #:mp-with-recursive-lock-held\n\n           ;; class utils\n           #:class-subclasses\n\n           ;; norvig\n           #:find-all\n\n           ;; misc\n           #:subseq*))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/parse-time.lisp",
    "content": "(in-package :bknr.utils)\n\n;;; **********************************************************************\n;;; This code was written as part of the CMU Common Lisp project at\n;;; Carnegie Mellon University, and has been placed in the public domain.\n;;;\n\n;;; It was subsequently borrowed and modified slightly by Daniel\n;;; Barlow <dan@telent.net> to become part of the net-telent-date\n;;; package.  Daniel, Tue May 22 05:45:27 BST 2001\n\n;;; **********************************************************************\n\n;;; Parsing routines for time and date strings. PARSE-TIME returns the\n;;; universal time integer for the time and/or date given in the string.\n\n;;; Written by Jim Healy, June 1987.\n\n;;; **********************************************************************\n\n(defvar whitespace-chars '(#\\space #\\tab #\\newline #\\, #\\' #\\`))\n(defvar time-dividers '(#\\: #\\.))\n(defvar date-dividers '(#\\\\ #\\/ #\\-))\n\n(defvar *error-on-mismatch* nil\n  \"If t, an error will be signalled if parse-time is unable\n   to determine the time/date format of the string.\")\n\n;;; Set up hash tables for month, weekday, zone, and special strings.\n;;; Provides quick, easy access to associated information for these items.\n\n;;; Hashlist takes an association list and hashes each pair into the\n;;; specified tables using the car of the pair as the key and the cdr as\n;;; the data object.\n\n(defmacro hashlist (list table)\n  `(dolist (item ,list)\n     (setf (gethash (car item) ,table) (cdr item))))\n\n(defparameter weekday-table-size 23)\n(defparameter month-table-size 31)\n(defparameter zone-table-size 11)\n(defparameter special-table-size 11)\n\n(defvar *weekday-strings* (make-hash-table :test #'equal\n\t\t\t\t\t :size weekday-table-size))\n\n(defvar *month-strings* (make-hash-table :test #'equal\n\t\t\t\t       :size month-table-size))\n\n(defvar *zone-strings* (make-hash-table :test #'equal\n\t\t\t\t      :size zone-table-size))\n\n(defvar *special-strings* (make-hash-table :test #'equal\n\t\t\t\t\t :size special-table-size))\n\f\n;;; Load-time creation of the hash tables.\n\n(hashlist '((\"monday\" . 0)    (\"mon\" . 0)\n\t    (\"tuesday\" . 1)   (\"tues\" . 1)   (\"tue\" . 1)\n\t    (\"wednesday\" . 2) (\"wednes\" . 2) (\"wed\" . 2)\n\t    (\"thursday\" . 3)  (\"thurs\" . 3)  (\"thu\" . 3)\n\t    (\"friday\" . 4)    (\"fri\" . 4)\n\t    (\"saturday\" . 5)  (\"sat\" . 5)\n\t    (\"sunday\" . 6)    (\"sun\" . 6))\n\t  *weekday-strings*)\n\n(hashlist '((\"january\" . 1)   (\"jan\" . 1)\n\t    (\"february\" . 2)  (\"feb\" . 2)\n\t    (\"march\" . 3)     (\"mar\" . 3)\n\t    (\"april\" . 4)     (\"apr\" . 4)\n\t    (\"may\" . 5)       (\"june\" . 6)\n\t    (\"jun\" . 6)       (\"july\" . 7)\n\t    (\"jul\" . 7)\t      (\"august\" . 8)\n\t    (\"aug\" . 8)       (\"september\" . 9)\n\t    (\"sept\" . 9)      (\"sep\" . 9)\n\t    (\"october\" . 10)  (\"oct\" . 10)\n\t    (\"november\" . 11) (\"nov\" . 11)\n\t    (\"december\" . 12) (\"dec\" . 12))\n\t  *month-strings*)\n\n(hashlist '((\"gmt\" . 0) (\"est\" . 5)\n\t    (\"edt\" . 4) (\"cst\" . 6)\n\t    (\"cdt\" . 5) (\"mst\" . 7)\n\t    (\"mdt\" . 6)\t(\"pst\" . 8)\n\t    (\"pdt\" . 7)) \n\t  *zone-strings*)\n\n(hashlist '((\"yesterday\" . yesterday)  (\"today\" . today)\n\t    (\"tomorrow\" . tomorrow)   (\"now\" . now))\n\t  *special-strings*)\n\f\n;;; Time/date format patterns are specified as lists of symbols repre-\n;;; senting the elements.  Optional elements can be specified by\n;;; enclosing them in parentheses.  Note that the order in which the\n;;; patterns are specified below determines the order of search.\n\n;;; Choices of pattern symbols are: second, minute, hour, day, month,\n;;; year, time-divider, date-divider, am-pm, zone, izone, weekday,\n;;; noon-midn, and any special symbol.\n\n(defparameter *default-date-time-patterns*\n  '( \n     ;; Date formats.\n    ((weekday) month (date-divider) day (date-divider) year (noon-midn))\n    ((weekday) day (date-divider) month (date-divider) year (noon-midn))\n    ((weekday) month (date-divider) day (noon-midn))\n    (year (date-divider) month (date-divider) day (noon-midn))\n    (month (date-divider) year (noon-midn))\n    (year (date-divider) month (noon-midn))\n\n    ((noon-midn) (weekday) month (date-divider) day (date-divider) year)\n    ((noon-midn) (weekday) day (date-divider) month (date-divider) year)\n    ((noon-midn) (weekday) month (date-divider) day)\n    ((noon-midn) year (date-divider) month (date-divider) day)\n    ((noon-midn) month (date-divider) year)\n    ((noon-midn) year (date-divider) month)\n\n     ;; Time formats.\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm) \n\t  (date-divider) (zone))\n    (noon-midn)\n    (hour (noon-midn))\n\n     ;; Time/date combined formats.\n    ((weekday) month (date-divider) day (date-divider) year\n\t   hour (time-divider) (minute) (time-divider) (secondp)\n\t   (am-pm) (date-divider) (zone))\n    ((weekday) day (date-divider) month (date-divider) year\n\t hour (time-divider) (minute) (time-divider) (secondp)\n\t (am-pm) (date-divider) (zone))\n    ((weekday) month (date-divider) day\n\t   hour (time-divider) (minute) (time-divider) (secondp)\n\t   (am-pm) (date-divider) (zone))\n    (year (date-divider) month (date-divider) day\n\t  hour (time-divider) (minute) (time-divider) (secondp)\n\t  (am-pm) (date-divider) (zone))\n    (month (date-divider) year\n\t   hour (time-divider) (minute) (time-divider) (secondp)\n\t   (am-pm) (date-divider) (zone))\n    (year (date-divider) month\n\t  hour (time-divider) (minute) (time-divider) (secondp)\n\t  (am-pm) (date-divider) (zone))\n\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm)\n\t  (date-divider) (zone) (weekday) month (date-divider)\n\t  day (date-divider) year)\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm)\n\t  (date-divider) (zone) (weekday) day (date-divider)\n\t  month (date-divider) year)\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm)\n\t  (date-divider) (zone) (weekday) month (date-divider)\n\t  day)\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm)\n\t  (date-divider) (zone) year (date-divider) month\n\t  (date-divider) day)\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm)\n\t  (date-divider) (zone) month (date-divider) year)\n    (hour (time-divider) (minute) (time-divider) (secondp) (am-pm)\n\t  (date-divider) (zone) year (date-divider) month)\n\n     ;; Weird, non-standard formats.\n    (weekday month day hour (time-divider) minute (time-divider)\n\t     secondp (am-pm)\n\t     (zone) year)\n    ((weekday) day (date-divider) month (date-divider) year hour\n     (time-divider) minute (time-divider) (secondp) (am-pm)\n     (date-divider) (zone))\n    ((weekday) month (date-divider) day (date-divider) year hour\n     (time-divider) minute (time-divider) (secondp) (am-pm)\n     (date-divider) (zone))\n\n    ;; Special-string formats.\n    (now (yesterday))\n    ((yesterday) now)\n    (now (today))\n    ((today) now)\n    (now (tomorrow))\n    ((tomorrow) now)\n    (yesterday (noon-midn))\n    ((noon-midn) yesterday)\n    (today (noon-midn))\n    ((noon-midn) today)\n    (tomorrow (noon-midn))\n    ((noon-midn) tomorrow)\n))\n\n;;; HTTP header style date/time patterns: RFC1123/RFC822, RFC850, ANSI-C.\n(defparameter *http-date-time-patterns*\n  '( \n     ;; RFC1123/RFC822 and RFC850.\n    ((weekday) day (date-divider) month (date-divider) year\n     hour time-divider minute (time-divider) (secondp) izone)\n    ((weekday) day (date-divider) month (date-divider) year\n     hour time-divider minute (time-divider) (secondp) (zone))\n\n     ;; ANSI-C.\n    ((weekday) month day\n     hour time-divider minute (time-divider) (secondp) year)))\n\n\f\n;;; The decoded-time structure holds the time/date values which are\n;;; eventually passed to 'encode-universal-time' after parsing.\n\n;;; Note: Currently nothing is done with the day of the week.  It might\n;;; be appropriate to add a function to see if it matches the date.\n\n(defstruct decoded-time\n  (second 0    :type integer)    ; Value between 0 and 59.\n  (minute 0    :type integer)    ; Value between 0 and 59.\n  (hour   0    :type integer)    ; Value between 0 and 23.\n  (day    1    :type integer)    ; Value between 1 and 31.\n  (month  1    :type integer)    ; Value between 1 and 12.\n  (year   1900 :type integer)    ; Value above 1899 or between 0 and 99.\n  (zone   0    :type rational)   ; Value between -24 and 24 inclusive.\n  (dotw   0    :type integer))   ; Value between 0 and 6.\n\n;;; Make-default-time returns a decoded-time structure with the default\n;;; time values already set.  The default time is currently 00:00 on\n;;; the current day, current month, current year, and current time-zone.\n\n(defun make-default-time (def-sec def-min def-hour def-day\n\t\t\t   def-mon def-year def-zone def-dotw)\n  (let ((default-time (make-decoded-time)))\n    (multiple-value-bind (sec min hour day mon year dotw dst zone)\n\t\t\t (get-decoded-time)\n      (declare (ignore dst))\n      (if def-sec\n\t  (if (eq def-sec :current)\n\t      (setf (decoded-time-second default-time) sec)\n\t      (setf (decoded-time-second default-time) def-sec))\n\t  (setf (decoded-time-second default-time) 0))\n      (if def-min\n\t  (if (eq def-min :current)\n\t      (setf (decoded-time-minute default-time) min)\n\t      (setf (decoded-time-minute default-time) def-min))\n\t  (setf (decoded-time-minute default-time) 0))\n      (if def-hour\n\t  (if (eq def-hour :current)\n\t      (setf (decoded-time-hour default-time) hour)\n\t      (setf (decoded-time-hour default-time) def-hour))\n\t  (setf (decoded-time-hour default-time) 0))\n      (if def-day\n\t  (if (eq def-day :current)\n\t      (setf (decoded-time-day default-time) day)\n\t      (setf (decoded-time-day default-time) def-day))\n\t  (setf (decoded-time-day default-time) day))\n      (if def-mon\n\t  (if (eq def-mon :current)\n\t      (setf (decoded-time-month default-time) mon)\n\t      (setf (decoded-time-month default-time) def-mon))\n\t  (setf (decoded-time-month default-time) mon))\n      (if def-year\n\t  (if (eq def-year :current)\n\t      (setf (decoded-time-year default-time) year)\n\t      (setf (decoded-time-year default-time) def-year))\n\t  (setf (decoded-time-year default-time) year))\n      (if def-zone\n\t  (if (eq def-zone :current)\n\t      (setf (decoded-time-zone default-time) zone)\n\t      (setf (decoded-time-zone default-time) def-zone))\n\t  (setf (decoded-time-zone default-time) zone))\n      (if def-dotw\n\t  (if (eq def-dotw :current)\n\t      (setf (decoded-time-dotw default-time) dotw)\n\t      (setf (decoded-time-dotw default-time) def-dotw))\n\t  (setf (decoded-time-dotw default-time) dotw))\n      default-time)))\n\n;;; Converts the values in the decoded-time structure to universal time\n;;; by calling encode-universal-time.\n;;; If zone is in numerical form, tweeks it appropriately.\n\n(defun convert-to-unitime (parsed-values)\n  (let ((zone (decoded-time-zone parsed-values)))\n    (encode-universal-time (decoded-time-second parsed-values)\n\t\t\t   (decoded-time-minute parsed-values)\n\t\t\t   (decoded-time-hour parsed-values)\n\t\t\t   (decoded-time-day parsed-values)\n\t\t\t   (decoded-time-month parsed-values)\n\t\t\t   (decoded-time-year parsed-values)\n\t\t\t   (if (or (> zone 24) (< zone -24))\n\t\t\t       (let ((new-zone (/ zone 100)))\n\t\t\t\t (cond ((minusp new-zone) (- new-zone))\n\t\t\t\t       ((plusp new-zone) (- 24 new-zone))\n\t\t\t\t       ;; must be zero (GMT)\n\t\t\t\t       (t new-zone)))\n\t\t\t       zone))))\n\n;;; Sets the current values for the time and/or date parts of the \n;;; decoded time structure.\n\n(defun set-current-value (values-structure &key (time nil) (date nil)\n\t\t\t\t\t\t(zone nil))\n  (multiple-value-bind (sec min hour day mon year dotw dst tz)\n      (get-decoded-time)\n    (declare (ignore dst))\n    (when time\n      (setf (decoded-time-second values-structure) sec)\n      (setf (decoded-time-minute values-structure) min)\n      (setf (decoded-time-hour values-structure) hour))\n    (when date\n      (setf (decoded-time-day values-structure) day)\n      (setf (decoded-time-month values-structure) mon)\n      (setf (decoded-time-year values-structure) year)\n      (setf (decoded-time-dotw values-structure) dotw))\n    (when zone\n      (setf (decoded-time-zone values-structure) tz))))\n\f\n;;; Special function definitions.  To define a special substring, add\n;;; a dotted pair consisting of the substring and a symbol in the\n;;; *special-strings* hashlist statement above.  Then define a function\n;;; here which takes one argument- the decoded time structure- and\n;;; sets the values of the structure to whatever is necessary.  Also,\n;;; add a some patterns to the patterns list using whatever combinations\n;;; of special and pre-existing symbols desired.\n\n(defun yesterday (parsed-values)\n  (set-current-value parsed-values :date t :zone t)\n  (setf (decoded-time-day parsed-values)\n\t(1- (decoded-time-day parsed-values))))\n\n(defun today (parsed-values)\n  (set-current-value parsed-values :date t :zone t))\n\n(defun tomorrow (parsed-values)\n  (set-current-value parsed-values :date t :zone t)\n  (setf (decoded-time-day parsed-values)\n\t(1+ (decoded-time-day parsed-values))))\n\n(defun now (parsed-values)\n  (set-current-value parsed-values :time t))\n\f\n;;; Predicates for symbols.  Each symbol has a corresponding function\n;;; defined here which is applied to a part of the datum to see if\n;;; it matches the qualifications.\n\n(defun am-pm (string)\n  (and (simple-string-p string)\n       (cond ((string= string \"am\") 'am)\n\t     ((string= string \"pm\") 'pm)\n\t     (t nil))))\n\n(defun noon-midn (string)\n  (and (simple-string-p string)\n       (cond ((string= string \"noon\") 'noon)\n\t     ((string= string \"midnight\") 'midn)\n\t     (t nil))))\n\n(defun weekday (string)\n  (and (simple-string-p string) (gethash string *weekday-strings*)))\n\n(defun month (thing)\n  (or (and (simple-string-p thing) (gethash thing *month-strings*))\n      (and (integerp thing) (<= 1 thing 12))))\n\n(defun zone (thing)\n  (or (and (simple-string-p thing) (gethash thing *zone-strings*))\n      (if (integerp thing)\n\t  (let ((zone (/ thing 100)))\n\t    (and (integerp zone) (<= -24 zone 24))))))\n\n;;; Internet numerical time zone, e.g. RFC1123, in hours and minutes.\n(defun izone (thing)\n  (if (integerp thing)\n      (multiple-value-bind (hours mins)\n\t  (truncate thing 100)\n\t(and (<= -24 hours 24) (<= -59 mins 59)))))\n\n(defun special-string-p (string)\n  (and (simple-string-p string) (gethash string *special-strings*)))\n\n(defun secondp (number)\n  (and (integerp number) (<= 0 number 59)))\n\n(defun minute (number)\n  (and (integerp number) (<= 0 number 59)))\n\n(defun hour (number)\n  (and (integerp number) (<= 0 number 23)))\n\n(defun day (number)\n  (and (integerp number) (<= 1 number 31)))\n\n(defun year (number)\n  (and (integerp number)\n       (or (<= 0 number 99)\n\t   (<= 1900 number))))\n\n(defun time-divider (character)\n  (and (characterp character)\n       (member character time-dividers :test #'char=)))\n\n(defun date-divider (character)\n  (and (characterp character)\n       (member character date-dividers :test #'char=)))\n\f\n;;; Match-substring takes a string argument and tries to match it with\n;;; the strings in one of the four hash tables: *weekday-strings*, *month-\n;;; strings*, *zone-strings*, *special-strings*.  It returns a specific\n;;; keyword and/or the object it finds in the hash table.  If no match\n;;; is made then it immediately signals an error.\n\n(defun match-substring (substring)\n  (let ((substring (nstring-downcase substring)))\n    (or (let ((test-value (month substring)))\n\t  (if test-value (cons 'month test-value)))\n\t(let ((test-value (weekday substring)))\n\t  (if test-value (cons 'weekday test-value)))\n\t(let ((test-value (am-pm substring)))\n\t  (if test-value (cons 'am-pm test-value)))\n\t(let ((test-value (noon-midn substring)))\n\t  (if test-value (cons 'noon-midn test-value)))\n\t(let ((test-value (zone substring)))\n\t  (if test-value (cons 'zone test-value)))\n\t(let ((test-value (special-string-p substring)))\n\t  (if test-value  (cons 'special test-value)))\n\t(if *error-on-mismatch*\n\t    (error \"\\\"~A\\\" is not a recognized word or abbreviation.\"\n\t\t   substring)\n\t    (return-from match-substring nil)))))\n\f\n;;; Decompose-string takes the time/date string and decomposes it into a\n;;; list of alphabetic substrings, numbers, and special divider characters.\n;;; It matches whatever strings it can and replaces them with a dotted pair\n;;; containing a symbol and value.\n\n(defun decompose-string (string &key (start 0) (end (length string)) (radix 10))\n  (do ((string-index start)\n       (next-negative nil)\n       (parts-list nil))\n      ((eq string-index end) (nreverse parts-list))\n    (let ((next-char (char string string-index))\n\t  (prev-char (if (= string-index start)\n\t\t\t nil\n\t\t\t (char string (1- string-index)))))\n      (cond ((alpha-char-p next-char)\n\t     ;; Alphabetic character - scan to the end of the substring.\n\t     (do ((scan-index (1+ string-index) (1+ scan-index)))\n\t\t ((or (eq scan-index end)\n\t\t      (not (alpha-char-p (char string scan-index))))\n\t\t  (let ((match-symbol (match-substring\n\t\t\t\t       (subseq string string-index scan-index))))\n\t\t    (if match-symbol\n\t\t\t(push match-symbol parts-list)\n\t\t\t(return-from decompose-string nil)))\n\t\t  (setf string-index scan-index))))\n\t    ((digit-char-p next-char radix)\n\t     ;; Numeric digit - convert digit-string to a decimal value.\n\t     (do ((scan-index string-index (1+ scan-index))\n\t\t  (numeric-value 0 (+ (* numeric-value radix)\n\t\t\t\t      (digit-char-p (char string scan-index) radix))))\n\t\t ((or (eq scan-index end)\n\t\t      (not (digit-char-p (char string scan-index) radix)))\n\t\t  ;; If next-negative is t, set the numeric value to it's\n\t\t  ;; opposite and reset next-negative to nil.\n\t\t  (when next-negative\n\t\t    (setf next-negative nil)\n\t\t    (setf numeric-value (- numeric-value)))\n\t\t  (push numeric-value parts-list)\n\t\t  (setf string-index scan-index))))\n\t    ((and (char= next-char #\\-)\n\t\t  (or (not prev-char)\n\t\t      (member prev-char whitespace-chars :test #'char=)))\n\t     ;; If we see a minus sign before a number, but not after one,\n\t     ;; it is not a date divider, but a negative offset from GMT, so\n\t     ;; set next-negative to t and continue.\n\t     (setf next-negative t)\n\t     (incf string-index))\t     \n\t    ((member next-char time-dividers :test #'char=)\n \t     ;; Time-divider - add it to the parts-list with symbol.\n\t     (push (cons 'time-divider next-char) parts-list)\n\t     (incf string-index))\n\t    ((member next-char date-dividers :test #'char=)\n\t     ;; Date-divider - add it to the parts-list with symbol.\n\t     (push (cons 'date-divider next-char) parts-list)\n\t     (incf string-index))\n\t    ((member next-char whitespace-chars :test #'char=)\n\t     ;; Whitespace character - ignore it completely.\n\t     (incf string-index))\n\t    ((char= next-char #\\()\n\t     ;; Parenthesized string - scan to the end and ignore it.\n\t     (do ((scan-index string-index (1+ scan-index)))\n\t\t ((or (eq scan-index end)\n\t\t      (char= (char string scan-index) #\\)))\n \t\t  (setf string-index (1+ scan-index)))))\n\t    (t\n\t     ;; Unrecognized character - barf voraciously.\n\t     (if *error-on-mismatch*\n\t\t (error\n\t\t  'simple-error\n\t\t  :format-control \"Can't parse time/date string.~%>>> ~A~\n\t\t\t\t   ~%~VT^-- Bogus character encountered here.\"\n\t\t  :format-arguments (list string (+ string-index 4)))\n\t\t (return-from decompose-string nil)))))))\n\f\n;;; Match-pattern-element tries to match a pattern element with a datum\n;;; element and returns the symbol associated with the datum element if\n;;; successful.  Otherwise nil is returned.\n\n(defun match-pattern-element (pattern-element datum-element)\n  (cond ((listp datum-element)\n\t (let ((datum-type (if (eq (car datum-element) 'special)\n\t\t\t       (cdr datum-element)\n\t\t\t       (car datum-element))))\n\t   (if (eq datum-type pattern-element) datum-element)))\n\t((funcall pattern-element datum-element)\n\t (cons pattern-element datum-element))\n\t(t nil)))\n\n;;; Match-pattern matches a pattern against a datum, returning the\n;;; pattern if successful and nil otherwise.\n\n(defun match-pattern (pattern datum datum-length)\n  (if (>= (length pattern) datum-length)\n      (let ((form-list nil))\n\t(do ((pattern pattern (cdr pattern))\n\t     (datum datum (cdr datum)))\n\t    ((or (null pattern) (null datum))\n\t     (cond ((and (null pattern) (null datum))\n\t\t    (nreverse form-list))\n\t\t   ((null pattern) nil)\n\t\t   ((null datum) (dolist (element pattern\n\t\t\t\t\t\t  (nreverse form-list))\n\t\t\t\t   (if (not (listp element))\n\t\t\t\t       (return nil))))))\n\t  (let* ((pattern-element (car pattern))\n\t\t (datum-element (car datum))\n\t\t (optional (listp pattern-element))\n\t\t (matching (match-pattern-element (if optional\n\t\t\t\t\t\t      (car pattern-element)\n\t\t\t\t\t\t      pattern-element)\n\t\t\t\t\t\t  datum-element)))\n\t    (cond (matching (let ((form-type (car matching)))\n\t\t\t      (unless (or (eq form-type 'time-divider)\n\t\t\t\t\t  (eq form-type 'date-divider))\n\t\t\t\t(push matching form-list))))\n\t\t  (optional (push datum-element datum))\n\t\t  (t (return-from match-pattern nil))))))))\n\f\n;;; Deal-with-noon-midn sets the decoded-time values to either noon\n;;; or midnight depending on the argument form-value.  Form-value\n;;; can be either 'noon or 'midn.\n\n(defun deal-with-noon-midn (form-value parsed-values)\n  (cond ((eq form-value 'noon)\n\t (setf (decoded-time-hour parsed-values) 12))\n\t((eq form-value 'midn)\n\t (setf (decoded-time-hour parsed-values) 0))\n\t(t (error \"Unrecognized symbol: ~A\" form-value)))\n  (setf (decoded-time-minute parsed-values) 0)\n  (setf (decoded-time-second parsed-values) 0))\n\n;;; Deal-with-am-pm sets the decoded-time values to be in the am\n;;; or pm depending on the argument form-value.  Form-value can\n;;; be either 'am or 'pm.\n\n(defun deal-with-am-pm (form-value parsed-values)\n  (let ((hour (decoded-time-hour parsed-values)))\n    (cond ((eq form-value 'am)\n\t   (cond ((eq hour 12)\n\t\t  (setf (decoded-time-hour parsed-values) 0))\n\t\t ((not (<= 0 hour 12))\n\t\t  (if *error-on-mismatch*\n\t\t      (error \"~D is not an AM hour, dummy.\" hour)))))\n\t  ((eq form-value 'pm)\n\t   (if (<= 0 hour 11)\n\t       (setf (decoded-time-hour parsed-values)\n\t\t     (mod (+ hour 12) 24))))\n\t  (t (error \"~A isn't AM/PM - this shouldn't happen.\" form-value)))))\n\n;;; Internet numerical time zone, e.g. RFC1123, in hours and minutes.\n(defun deal-with-izone (form-value parsed-values)\n  (multiple-value-bind (hours mins)\n      (truncate form-value 100)\n    (setf (decoded-time-zone parsed-values) (- (+ hours (/ mins 60))))))\n\n;;; Set-time-values uses the association list of symbols and values\n;;; to set the time in the decoded-time structure.\n\n(defun set-time-values (string-form parsed-values)\n  (dolist (form-part string-form t)\n    (let ((form-type (car form-part))\n\t  (form-value (cdr form-part)))\n      (case form-type\n\t(secondp (setf (decoded-time-second parsed-values) form-value))\n\t(minute (setf (decoded-time-minute parsed-values) form-value))\n\t(hour (setf (decoded-time-hour parsed-values) form-value))\n\t(day (setf (decoded-time-day parsed-values) form-value))\n\t(month (setf (decoded-time-month parsed-values) form-value))\n\t(year (setf (decoded-time-year parsed-values) form-value))\n\t(zone (setf (decoded-time-zone parsed-values) form-value))\n\t(izone (deal-with-izone form-value parsed-values))\n\t(weekday (setf (decoded-time-dotw parsed-values) form-value))\n\t(am-pm (deal-with-am-pm form-value parsed-values))\n\t(noon-midn (deal-with-noon-midn form-value parsed-values))\n\t(special (funcall form-value parsed-values))\n\t(t (error \"Unrecognized symbol in form list: ~A.\" form-type))))))\n\f\n(defun parse-time (time-string &key (start 0) (end (length time-string))\n\t\t\t       (error-on-mismatch nil)\n\t\t\t       (patterns *default-date-time-patterns*)\n\t\t\t       (default-seconds nil) (default-minutes nil)\n\t\t\t       (default-hours nil) (default-day nil)\n\t\t\t       (default-month nil) (default-year nil)\n\t\t\t       (default-zone nil) (default-weekday nil))\n  \"Tries very hard to make sense out of the argument time-string and\n   returns a single integer representing the universal time if\n   successful.  If not, it returns nil.  If the :error-on-mismatch\n   keyword is true, parse-time will signal an error instead of\n   returning nil.  Default values for each part of the time/date\n   can be specified by the appropriate :default- keyword.  These\n   keywords can be given a numeric value or the keyword :current\n   to set them to the current value.  The default-default values\n   are 00:00:00 on the current date, current time-zone.\"\n  (setq *error-on-mismatch* error-on-mismatch)\n  (let* ((string-parts (decompose-string time-string :start start :end end))\n\t (parts-length (length string-parts))\n\t (string-form (dolist (pattern patterns)\n\t\t\t(let ((match-result (match-pattern pattern\n\t\t\t\t\t\t\t   string-parts\n\t\t\t\t\t\t\t   parts-length)))\n\t\t\t  (if match-result (return match-result))))))\n    (if string-form\n\t(let ((parsed-values (make-default-time default-seconds default-minutes\n\t\t\t\t\t\tdefault-hours default-day\n\t\t\t\t\t\tdefault-month default-year\n\t\t\t\t\t\tdefault-zone default-weekday)))\n\t  (set-time-values string-form parsed-values)\n\t  (convert-to-unitime parsed-values))\n\t(if *error-on-mismatch*\n\t  (error \"\\\"~A\\\" is not a recognized time/date format.\" time-string)\n\t  nil))))\n\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/pathnames.lisp",
    "content": ""
  },
  {
    "path": "third-party/bknr.datastore/src/utils/reader.lisp",
    "content": "(in-package :bknr.utils)\n\n(define-constant +whitespace-chars+\n  '(#\\Space #\\Newline #\\Tab #\\Linefeed))\n\n(defun whitespace-char-p (c)\n  (member c +whitespace-chars+))\n\n(defun whitespace-p (c-or-s)\n  (cond ((stringp c-or-s)\n\t (every #'whitespace-char-p c-or-s))\n\t((characterp c-or-s)\n\t (whitespace-char-p c-or-s))\n\t(t nil)))\n\n(defun bknr-read-string-until (s endchar &key (start nil)\n\t\t\t       (test #'eql) test-not (unread-endchar nil))\n  (do ((c (peek-char nil s nil 'eof)\n\t  (peek-char nil s nil 'eof))\n       (ret (copy-list start))\n       (escaped nil (and (not escaped) (eql c #\\\\))))\n      ((or (eq c 'eof)\n\t   (and (not escaped)\n\t\t(if test-not\n\t\t    (not (funcall test-not c endchar))\n\t\t    (funcall test c endchar))))\n       (unless (or unread-endchar\n\t\t   (eq c 'eof))\n\t (push (read-char s) ret))\n       (coerce (nreverse ret) 'string))\n    (push (read-char s) ret)))\n\n(defun bknr-read-string (s)\n  (bknr-read-string-until s #\\\" :start '(#\\\")))\n\n(defun bknr-read-comment (s)\n  (bknr-read-string-until s '(#\\Newline #\\Linefeed) :test #'member :unread-endchar t))\n\n(defun bknr-read-whitespace (s)\n  (bknr-read-string-until s +whitespace-chars+ :test-not #'member :unread-endchar t))\n\n(defun bknr-read-something (s)\n  (bknr-read-string-until s (append +whitespace-chars+ '(#\\( #\\\" #\\))) :test #'member\n\t\t\t:unread-endchar t))\n\n(defun bknr-read-delimited-list (s endchar &optional eof-error-p eof-value collect-whitespace)\n  (do ((c (peek-char nil s nil 'eof)\n\t  (peek-char nil s nil 'eof))\n       ret)\n      ((or (eq c 'eof)\n\t   (eq c endchar))\n       (when (eq c endchar)\n\t (read-char s))\n       (nreverse ret))\n    (push (bknr-read s eof-error-p eof-value collect-whitespace) ret)))\n\n(defun bknr-read (s &optional (eof-error-p t) eof-value (collect-whitespace t))\n  (prog ()\n   again\n   (let ((c (peek-char nil s eof-error-p eof-value)))\n     (cond ((and eof-value\n\t\t (eq c eof-value))\n\t    (return eof-value))\n\t   ((eq c #\\()\n\t    (read-char s)\n\t    (return \n\t      (if collect-whitespace\n\t\t  (collect-whitespace (bknr-read-delimited-list\n\t\t\t\t       s #\\) eof-error-p eof-value collect-whitespace))\n\t\t  (bknr-read-delimited-list\n\t\t   s #\\) eof-error-p eof-value collect-whitespace))))\n\t   ((eq c #\\\")\n\t      (read-char s)\n\t    (return (bknr-read-string s)))\n\t   ((eq c #\\;)\n\t    (return (bknr-read-comment s)))\n\t   ((eq c #\\))\n\t    (read-char s)\n\t    (return \")\"))\n\t   ((whitespace-char-p c)\n\t    (let ((whitespace (bknr-read-whitespace s)))\n\t\t(if collect-whitespace\n\t\t    (return whitespace)\n\t\t    (go again))))\n\t   (t (return (bknr-read-something s)))))))\n\n(defun collect-whitespace (list)\n  (do ((l list (cdr l))\n       whitespace)\n      ((or (null l)\n\t   (not (whitespace-p (car l))))\n       (cond ((and (null whitespace)\n\t\t   (null l))\n\t      nil)\n\t     (t (cons (apply #'concatenate 'string (nreverse whitespace))\n\t\t      (unless (null l)\n\t\t\t(cons (car l) (collect-whitespace (cdr l))))))))\n    (when (> (length (car l)) 0)\n      (push (car l) whitespace))))\n\n(defun string-beginning-with-p (string beginning)\n  (let ((beginlen (length beginning)))\n    (and (stringp string)\n\t (and (>= (length string) beginlen)\n\t      (string-equal (subseq string 0 beginlen) beginning)))))\n\n(defun string-delimited-by-p (string char)\n  (and (stringp string)\n       (let ((len (length string)))\n\t (and (> len 2)\n\t      (eql (char string 0) char)\n\t      (eql (char string (1- len)) char)))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/smbpasswd-wrapper.c",
    "content": "\n#include <stdio.h>\n\n/* this wrapper is meant to be setuid root */\n\n#define SMBPASSWD \"/usr/local/bin/smbpasswd\"\n\nmain(int argc, char* argv[])\n{\n\tsetuid(geteuid());\n\targv++;\n\texecvp(SMBPASSWD, argv);\n\tperror(*argv);\n}\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/smbpasswd.lisp",
    "content": "(in-package :bknr.utils)\n\n(defvar +smb-wrapper-program+ \"/usr/local/bin/smbpasswd-wrapper\")\n\n(define-condition smb-password-error ()\n  ((message :initarg :message :accessor smb-password-error-message)))\n\n(defmethod print-object ((error smb-password-error) stream)\n  (format stream \"#<~a ~a>\"\n\t  (class-name (class-of error))\n\t  (smb-password-error-message error))\n  error)\n\n(defun set-smb-password (username password &key (create t))\n  (unless (and username password)\n    (error (make-condition 'smb-password-error :message \"please specify both username and password\")))\n  (let ((args (list username password)))\n    (when create\n      (push \"-a\" args))\n    (push \"smbpasswd\" args)\n    (with-output-to-string (stream)\n      #+allegro\n      (excl:run-shell-command (apply #'concatenate\n\t\t\t\t'string\n\t\t\t\t+smb-wrapper-program+\n\t\t\t\targs)\n\t\t\t :output stream :error-output stream)\n      #+cmu\n      (let ((process\n\t     (ext:run-program +smb-wrapper-program+ args :output stream :error :output)))\n\t(unwind-protect\n\t     (unless (zerop (ext:process-exit-code process))\n\t       (error (make-condition 'smb-password-error :message (get-output-stream-string stream))))\n\t  (ext:process-close process)))\n      #+openmcl\n      (ccl::run-program +smb-wrapper-program+\n\t\t\targs\n\t\t\t:output stream)\n      #+sbcl\n      (let ((process\n\t     (sb-ext:run-program +smb-wrapper-program+ args :output stream :error :output)))\n\t(unwind-protect\n\t     (unless (zerop (process-exit-code process))\n\t       (error (make-condition 'smb-password-error :message (get-output-stream-string stream))))\n\t  (process-close process))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/utils.lisp",
    "content": "(in-package :bknr.utils)\n\n(defmacro define-constant (name value &optional doc)\n  \"Macro for use in place of defconstant in order to  make SBCL compiler happy\"\n  `(defconstant ,name (if (boundp ',name) (symbol-value ',name) ,value)\n                           ,@(when doc (list doc))))\n;;; date format\n\n;; Zeitzone fuer Mail-Zeitstempel\n(defparameter *mail-timezone* \"+0100\")\n\n(defun month-name (month)\n  (elt #(\"Jan\" \"Feb\" \"Mar\" \"Apr\" \"May\" \"Jun\" \"Jul\" \"Aug\" \"Sep\" \"Oct\" \"Nov\" \"Dec\") (1- month)))\n\n(defun format-date-time (&optional universal-time &key stream\n\t\t\t (show-year t) (show-month t)\n\t\t\t (show-date t) (show-time t) (show-weekday nil)\n\t\t\t (show-seconds t)\n\t\t\t vms-style us-style mail-style xml-style js-style)\n  (or show-date show-time\n      (warn \"format-date-time: show-date and show-time are nil, nothing printed\"))\n  (multiple-value-bind (sec min hour day month year weekday)\n      (decode-universal-time (or universal-time (get-universal-time)))\n    (when (equal show-year :short)\n      (setq year (mod year 100)))\n    (when show-weekday\n      (setf weekday (nth weekday '(\"MON\" \"TUE\" \"WED\" \"THU\" \"FRI\" \"SA\" \"SO\"))))\n    (let ((s (if stream stream (make-string-output-stream))))\n      (cond\n\t(mail-style\n\t (format s \"~A, ~2,'0D ~A ~4D ~2,'0D:~2,'0D:~2,'0D ~A\"\n\t\t (elt #(\"Mon\" \"Tue\" \"Wed\" \"Thu\" \"Fri\" \"Sat\" \"Sun\") weekday)\n\t\t day (month-name month) year\n\t\t hour min sec *mail-timezone*))\n\t(us-style\n\t (format s \"~A ~2,'0D, ~A\"\n\t\t (month-name month) day year))\n\t(vms-style\n\t (when show-date\n\t   (setf month (nth (- month 1) '(\"JAN\" \"FEB\" \"MAR\" \"APR\" \"MAY\" \"JUN\" \"JUL\" \"AUG\" \"SEP\" \"OCT\" \"NOV\" \"DEC\")))\n\t   (format s \"~2,' d-~a-~d\" day month year))\n\t (when (and show-date show-time)\n\t   (princ #\\Space s))\n\t (when show-time\n\t   (format s \"~2,' d:~2,'0d:~2,'0d\" hour min sec)))\n\t(xml-style\n\t (format s \"~4,'0d~2,'0d~2,'0dT~2,'0d~2,'0d~2,'0d\"\n\t\t year month day hour min sec))\n\t(js-style\n\t (format s \"new Date(~D, ~D, ~D, ~D, ~D, ~D)\"\n\t\t year (1- month) day hour min sec))\n\t(t\n\t (when show-weekday\n\t   (format s \"~a \" weekday))\n\t (when show-date\n\t   (format s \"~2,'0d.\" day)\n\t   (when show-month\n\t     (format s \"~2,'0d.\" month))\n\t   (when show-year\n\t     (format s \"~4,'0d\" year))\n\t   (when (and show-date show-time)\n\t     (princ #\\Space s)))\n\t (when show-time\n\t   (format s \"~2,'0d:~2,'0d\" hour min))\n\t (when (and show-seconds show-time)\n\t   (format s \":~2,'0d\" sec))))\n      (unless stream\n\t(get-output-stream-string s)))))\n\n(defun format-time-interval (seconds)\n  (format nil \"~d:~2,'0d\" (floor (/ seconds 60)) (mod seconds 60)))\n\n(defun format-duration (duration)\n  (cond\n    ((> duration (* 24 3600)) (format nil \"~ad\" (round (/ duration (* 24 3600)))))\n    ((> duration 3600)        (format nil \"~dh\" (round (/ duration 3600))))\n    ((> duration 60)          (format nil \"~am\" (round (/ duration 60))))\n    (t\t                      (format nil \"~as\" duration))))\n\n(defun month-interval (month year)\n  \"Returns two values, the first and last second of the given month\"\n  (values\n   (encode-universal-time 0 0 0 1 month year)\n   (- (if (= 12 month)\n\t  (encode-universal-time 0 0 0 1 1 (+ 1 year))\n\t  (encode-universal-time 0 0 0 1 (+ 1 month) year))\n      1)))\n\n(defun day-interval (day month year)\n  \"Returns two values, the first and last second of the given day\"\n  (values\n   (encode-universal-time 0 0 0 day month year)\n   (encode-universal-time 59 59 23 day month year)))\n\n(defun year-interval (year)\n  (values\n   (encode-universal-time 0 0 0 1 1 year)\n   (encode-universal-time 59 59 23 31 12 year)))\n\n(defun get-hourtime (time)\n  (multiple-value-bind (second minute hour)\n      (decode-universal-time time)\n    (declare (ignore second minute))\n    hour))\n\n(defun get-daytime (time)\n  (multiple-value-bind (second minute hour date month year day)\n      (decode-universal-time time)\n    (declare (ignore second minute hour day))\n    (nth-value 0 (day-interval date month year))))\n\n(defun get-monthtime (time)\n  (multiple-value-bind (second minute hour date month year day)\n      (decode-universal-time time)\n    (declare (ignore second minute hour date day))\n    (nth-value 0 (month-interval month year))))\n\n(defun previous-day (count &key (start (get-universal-time)))\n  (- start (* count (* 24 3600))))\n\n(defun next-day (count &key (start (get-universal-time)))\n  (+ start (* count (* 24 3600))))\n\n(defun month-num-days (month year)\n  (multiple-value-bind (start end) (month-interval month year)\n    (nth-value 0 (round (/ (- end start) (* 24 3600))))))\n\n(defun timetag ()\n  (multiple-value-bind (second minute hour date month year)\n      (decode-universal-time (get-universal-time) 0)\n    (format nil\n\t    \"~d~2,'0d~2,'0dT~2,'0d~2,'0d~2,'0d\"\n\t    year month date hour minute second)))\n\n(defun daytag ()\n  (multiple-value-bind (second minute hour date month year)\n      (decode-universal-time (get-universal-time) 0)\n    (declare (ignore second minute hour))\n    (format nil\n\t    \"~d~2,'0d~2,'0d\"\n\t    year month date)))\n\n;;; run shell command and return output as string\n;;; (lifted out of ASDF)\n\n(defun run-shell-command-to-string (control-string &rest args)\n  \"Interpolate ARGS into CONTROL-STRING as if by FORMAT, and\nsynchronously execute the result using a Bourne-compatible shell.\n  Returns the output and the shell's exit code.\"\n  (let* ((result)\n\t (command (apply #'format nil control-string args))\n\t (str (with-output-to-string (s)\n\t\t(setf result (run-shell-command command s)))))\n    (values str result)))\n\n(defun run-shell-command (command s)\n  #+abcl\n  (ext:run-shell-command command :output s)\n\n  #+allegro\n  ;; will this fail if command has embedded quotes - it seems to work\n  (multiple-value-bind (stdout stderr exit-code)\n      (excl.osi:command-output\n       (format nil \"~a -c \\\"~a\\\"\"\n\t       #+mswindows \"sh\" #-mswindows \"/bin/sh\" command)\n       :input nil :whole nil\n       #+mswindows :show-window #+mswindows :hide)\n    (format s \"~{~&; ~a~%~}~%\" stderr)\n    (format s \"~{~&; ~a~%~}~%\" stdout)\n    exit-code)\n\n  #+clisp                     ;XXX not exactly s, I know\n  (ext:run-shell-command  command :output :terminal :wait t)\n\n  #+clozure\n  (nth-value 1\n\t     (ccl:external-process-status\n\t      (ccl:run-program \"/bin/sh\" (list \"-c\" command)\n\t\t\t       :input nil :output s\n\t\t\t       :wait t)))\n\n  #+ecl ;; courtesy of Juan Jose Garcia Ripoll\n  (si:system command)\n\n  #+gcl\n  (lisp:system command)\n\n  #+lispworks\n  (system:call-system-showing-output\n   command\n   :shell-type \"/bin/sh\"\n   :show-cmd nil\n   :prefix \"\"\n   :output-stream s)\n\n  #+sbcl\n  (sb-ext:process-exit-code\n   (apply #'sb-ext:run-program\n\t  #+win32 \"sh\" #-win32 \"/bin/sh\"\n\t  (list  \"-c\" command)\n\t  :input nil :output s\n\t  #+win32 '(:search t) #-win32 nil))\n\n  #+(or cmu scl)\n  (ext:process-exit-code\n   (ext:run-program\n    \"/bin/sh\"\n    (list  \"-c\" command)\n    :input nil :output s))\n\n  #-(or abcl allegro clisp clozure cmu ecl gcl lispworks sbcl scl)\n  (error \"RUN-SHELL-COMMAND not implemented for this Lisp\")\n  )\n\n\n;;; local hostname\n\n(defun hostname (&key (strip-domain t))\n  (let ((hostname\n\t #+allegro (sys:getenv \"HOST\")\n\t #+cmu (cdr (assoc :host ext:*environment-list*))\n\t #+openmcl (ccl::getenv \"HOST\")\n\t #+sbcl (sb-ext:posix-getenv \"HOST\")))\n    (unless hostname\n      (error \"HOST environment variable not set, can't continue\"))\n    (if strip-domain\n\t(regex-replace \"\\\\..*$\" hostname \"\")\n\thostname)))\n\n\n;;; filesystem functions\n\n(defun directory-empty-p (pathname)\n  (zerop (length (directory pathname))))\n\n(defun subdir-p (subdir dir)\n  (let ((subdir (probe-file subdir))\n\t(dir (probe-file dir)))\n    (when (and subdir dir)\n      (equal (subseq (pathname-directory subdir)\n\t\t     0 (length (pathname-directory dir)))\n\t     (pathname-directory dir)))))\n\n(defun move-file (file1 file2)\n  #+(or allegro openmcl)\n  (rename-file file1 file2)\n  #+cmu\n  (unix:unix-rename (namestring file1)\n\t\t    (namestring file2))\n  #+sbcl\n  (sb-unix:unix-rename (namestring file1)\n\t\t       (namestring file2)))\n\n(defun make-temporary-pathname (&key (defaults nil) (name \"tmp\"))\n  (let ((args (when defaults `(:defaults ,defaults))))\n   (loop for file = (apply 'make-pathname :name (format nil \"~A-~A-~A\"\n\t\t\t\t\t\t                                name\n                                                        (get-universal-time)\n                                                        (random most-positive-fixnum))\n                            args)\n         while (probe-file file)\n         finally (return file))))\n\n(defmacro with-temporary-file ((var &rest args) &body body)\n  `(let ((,var (make-temporary-pathname ,@args)))\n     (unwind-protect\n\t  (progn ,@body)\n       (when (probe-file ,var)\n\t (delete-file ,var)))))\n\n(defun parent-directory (pathname)\n  (make-pathname :directory (butlast (pathname-directory pathname))\n\t\t :defaults pathname))\n\n;;; list functions\n\n(defun delete-first (obj list &key (test #'eql))\n  (if (funcall test (first list) obj)\n      (cdr list)\n      (do ((l list (cdr l))\n\t   (last nil l))\n\t  ((null l) list)\n\t(when (funcall test (car l) obj)\n\t  (rplacd last (cdr l))\n\t  (return list)))))\n\n(defun make-keyword-from-string (string)\n  (if (keywordp string)\n      string\n      (nth-value 0 (intern (string-upcase (regex-replace-all \"\\\\s+\" string \"-\")) 'keyword))))\n\n(defun assoc-values (item alist &key (test #'equal))\n  (mapcan #'(lambda (x) (and (funcall test item (car x))\n\t\t\t     (list (cdr x))))\n\t  alist))\n\n(defun insert-at-index (idx l elt)\n  (cond ((= idx 0)\n\t (cons elt l))\n\t((= idx (1- (length l)))\n\t (append l (list elt)))\n\t(t (append (subseq l 0 idx)\n\t\t   (list elt)\n\t\t   (subseq l idx)))))\n\n(defun find-neighbourhood (elt list depth &key (test #'eql))\n  (loop for rest on list\n\twith seen = list and i = 0\n\twhen (funcall test elt (car rest))\n\tdo (return (subseq seen 0 (+ 1 depth i)))\n\tdo (if (>= i depth) (setf seen (cdr seen)) (incf i))))\n\n(defun assoc-to-keywords (args)\n  (loop for (key . value) in args\n\tnconc (list (make-keyword-from-string key) value)))\n\n(defun group-by (list num)\n  (loop for group on list by #'(lambda (seq) (subseq seq num))\n\tcollect (subseq group 0 num)))\n\n(defun group-on (list &key (test #'eql) (key #'identity) (include-key t))\n  (let ((hash (make-hash-table :test test))\n        keys)\n    (dolist (el list)\n      (let ((key (funcall key el)))\n        (unless (nth-value 1 (gethash key hash))\n          (push key keys))\n        (push el (gethash key hash))))\n    (mapcar (lambda (key) (let ((keys (nreverse (gethash key hash))))\n                            (if include-key\n                                (cons key keys)\n                                keys)))\n            (nreverse keys))))\n\n(defun count-multiple (objects &rest keys)\n  (let ((hash-tables (loop for i from 1 to (length keys)\n\t\t\t   collect (make-hash-table :test #'equal)))\n\t(sum 0))\n    (dolist (object objects)\n      (incf sum)\n      (loop for key in keys\n\t    for i from 0\n\t    do (incf-hash (funcall key object) (nth i hash-tables))))\n    (apply #'values sum hash-tables)))\n\n#+no-alexandria\n(defun rotate (list)\n  (when list\n    (append (cdr list) (list (car list)))))\n\n(defun nrotate (list)\n  (when list\n    (let ((first (pop list)))\n      (rplacd (last list) (list first))\n      list)))\n\n(defun genlist (from to)\n  (loop for i from from to to\n\tcollect i))\n\n(defun shift-until (list num &key (test #'>=))\n  (do* ((l list (cdr l))\n\t(smaller nil (cons i smaller))\n\t(i (car l) (car l)))\n       ((funcall test i num)\n\t(append l (nreverse smaller)))))\n\n;;; from norvig\n(defun find-all (item sequence &rest keyword-args\n                 &key (test #'eql) test-not &allow-other-keys)\n  \"Find all those elements of sequence that match item,\n  according to the keywords.  Doesn't alter sequence.\"\n  (if test-not\n      (apply #'remove item sequence\n             :test-not (complement test-not) keyword-args)\n      (apply #'remove item sequence\n             :test (complement test) keyword-args)))\n\n;;; hash table\n(defun hash-to-list (hash &key (key #'cdr) (compare #'>) num)\n  (let ((results (sort (loop for key being the hash-key of hash using (hash-value val)\n\t\t\t     collect (cons key val))\n\t\t       compare :key key)))\n    (if num\n\t(subseq results 0 num)\n\tresults)))\n\n(defun hash-values (hash)\n  (loop for value being the hash-values of hash\n\tcollect value))\n\n(defun hash-keys (hash)\n  (loop for key being the hash-keys of hash\n\tcollect key))\n\n(defun incf-hash (key hash &optional (delta 1))\n  (if (gethash key hash)\n      (incf (gethash key hash) delta)\n      (setf (gethash key hash) delta)))\n\n\n;;; randomize\n\n(defun randomize-list (l)\n  (let ((len (length l)))\n    (flet ((randomize (l)\n\t     (let ((x (random len))\n\t\t   (mov (pop l)))\n\t       (insert-at-index x l mov))))\n      (dotimes (x len)\n\t(setf l (randomize l)))))\n  l)\n\n(defun random-elts (choices num)\n  (subseq (randomize-list choices) 0 num))\n\n;;; hashes\n(defun hash-to-hex (vector)\n  (format nil \"~{~2,'0X~}\" (coerce vector 'list)))\n\n(defun md5-string (input-string)\n  (apply #'concatenate 'string (mapcar #'(lambda (c)\n\t\t\t\t\t   (format nil \"~2,'0X\" c))\n\t\t\t\t       (coerce (#+(or cmu ccl) md5sum-sequence #+sbcl md5sum-string input-string) 'list))))\n\n#+(or)\n(defun md5-string (string)\n  (hash-to-hex (digest-string :md5 string)))\n\n;;; content-types\n\n(defvar *image-type<->content-type* '((:jpg . \"image/jpeg\")\n\t\t\t\t      (:png . \"image/png\")\n\t\t\t\t      (:gif . \"image/gif\")))\n\n(defun pathname-type-symbol (pathname)\n  (intern (string-upcase (pathname-type pathname)) 'keyword))\n\n(defun image-content-type (type-symbol)\n  \"Return the MIME type of the image - If the type-symbol is a string,\nit is assumed that the string specifies the MIME type.\"\n  (if (keywordp type-symbol)\n      (cdr (find type-symbol *image-type<->content-type* :test #'equal :key #'car))\n      type-symbol))\n\n(defun image-type-symbol (content-type)\n  (car (find content-type *image-type<->content-type* :test #'equal :key #'cdr)))\n\n(defun pathname-content-type (pathname)\n  (image-content-type (pathname-type-symbol pathname)))\n\n;;; utf08\n(defun convert-utf8-to-latin1 (string &key (ignore-errors t))\n  (declare (string string) (optimize (speed 3)))\n  (with-output-to-string (stream)\n    (let ((length (length string))\n          (index 0))\n      (declare (fixnum length index))\n      (loop\n       (unless (< index length) (return nil))\n\t   (let* ((char (char string index))\n\t\t  (code (char-code char)))\n\t     (restart-case\n\t\t (handler-bind\n\t\t     ((error #'(lambda (c)\n\t\t\t\t (if ignore-errors\n\t\t\t\t     (invoke-restart 'ignore-byte)\n\t\t\t\t     (error c)))))\n\t\t   (cond\n\t\t     ((< code #x80) ; ASCII\n\t\t      (write-char char stream)\n\t\t      (incf index 1))\n\t\t     ((< code #xC0)\n\n\t\t      ;; We are in the middle of a multi-byte sequence!\n\t\t      ;; This should never happen, so we raise an error.\n\t\t      (error \"Encountered illegal multi-byte sequence.\"))\n\t\t     ((< code #xC4)\n\t\t      ;; Two byte sequence in Latin-1 range\n\t\t      (unless (< (1+ index) length)\n\t\t\t(error \"Encountered incomplete two-byte sequence.\"))\n\t\t      (let* ((char2 (char string (1+ index)))\n\t\t\t     (code2 (char-code char2)))\n\t\t\t(unless (and (logbitp 7 code2) (not (logbitp 6 code2)))\n\t\t\t  (error \"Second byte in sequence is not a continuation.\"))\n\t\t\t(let* ((upper-bits (ldb (byte 2 0) code))\n\t\t\t       (lower-bits (ldb (byte 6 0) code2))\n\t\t\t       (new-code (dpb upper-bits (byte 2 6) lower-bits)))\n\t\t\t  (write-char (code-char new-code) stream)))\n\t\t      (incf index 2))\n\t\t     ((>= code #xFE)\n\t\t      ;; Ignore stray byte-order markers\n\t\t      (incf index 1))\n\t\t     (t\n\t\t      (error (format nil \"Multi-byte sequence ~d (~d) outside Latin-1 range.\"\n\t\t\t\t     code char)))))\n\t       (ignore-byte ()\n\t\t :report \"Ignore byte\"\n\t\t (incf index 1))\n\t       (ignore-n-bytes (n)\n\t\t :report \"Ignore some bytes\"\n\t\t :interactive (lambda () (format t \"Enter a new value: \")\n\t\t\t\t      (multiple-value-list (eval (read))))\n\t\t (incf index n))\n\t       (write-another-char (b)\n\t\t :report \"Write a new char\"\n\t\t :interactive (lambda () (format t \"Enter a new char: \")\n\t\t\t\t      (multiple-value-list (eval (read))))\n\t\t (write-char b stream)\n\t\t (incf index 1))\n\t       (write-char ()\n\t\t :report \"Write byte to latin-1 string\"\n\t\t (write-char char stream)\n\t\t (incf index 1))))))))\n\n;;; stirng functions\n(defun find-matching-strings (regexp strings &key case-sensitive)\n  (let ((scanner (create-scanner regexp :case-insensitive-mode (not case-sensitive))))\n    (remove-if-not #'(lambda (str)\n\t\t       (scan scanner str)) strings)))\n\n;;; stream functions\n;;; from macho (by Miles Egan)\n(defun make-extendable-string ()\n  \"Creates a resizable string.\"\n  (make-array 0 :fill-pointer t :adjustable t :element-type 'base-char))\n\n(defun read-delimited (stream token)\n  \"Reads stream up to delimiter.\"\n  (let ((string (make-extendable-string)))\n    (handler-case\n        (loop with tok-length = (length token)\n              with state = 0\n              initially (vector-push-extend (read-char stream) string) ;; skip first char\n              for c = (read-char stream)\n              while (< state tok-length)\n              do (let ((match (char= c (aref token state))))\n                   (cond\n                     ((and (> state 0) (not match))\n                      (unread-char c stream)\n                      (setf state 0))\n                     (t\n                      (if match (incf state))\n                      (vector-push-extend c string))))\n              finally (progn\n                        (file-position stream (- (file-position stream) tok-length))\n                        (adjust-array string (- (length string) tok-length) :fill-pointer t)\n                        (return (values string t))))\n      (end-of-file () (values (if (> (length string) 0) string nil)\n                              nil)))))\n\n(defun read-file (stream)\n  \"Reads entire contents of stream into a string.\"\n  (loop with result = (make-extendable-string)\n        for c = (read-char stream nil)\n        while c\n        do (vector-push-extend c result)\n        finally (return result)))\n\n(defun remove-keys (keys args)\n  (loop for (name val) on args by #'cddr\n\tunless (member name keys)\n\tappend (list name val)))\n\n(defun eval-initargs (initargs)\n  (loop for (key value) on initargs by #'cddr\n\tnconc (list key (eval value))))\n\n#-allegro\n(defun file-contents (pathname &key (element-type '(unsigned-byte 8)) (external-format :utf-8))\n  (cond\n    ((equal element-type '(unsigned-byte 8))\n     (with-open-file (s pathname :element-type element-type)\n       (let ((result\n              (make-array (file-length s) :element-type element-type)))\n         (read-sequence result s)\n         result)))\n    ((equal element-type 'character)\n     (alexandria:read-file-into-string pathname :external-format external-format))\n    (t\n     (error \"unsupported element type ~A for file-contents\" element-type))))\n\n(defun class-subclasses (class)\n  \"Return a list of the names of all subclasses of a given class.\"\n  (labels ((collect-subclasses (class)\n\t     (let ((subclasses\n\t\t    #+allegro\n\t\t     (aclmop:class-direct-subclasses class)\n\t\t     #+cmu\n\t\t     (pcl:class-direct-subclasses class)\n\t\t     #+openmcl\n\t\t     (openmcl-mop:class-direct-subclasses class)\n\t\t     #+sbcl\n\t\t     (sb-mop:class-direct-subclasses class)))\n\t       (apply #'append subclasses\n\t\t      (mapcar #'collect-subclasses subclasses)))))\n    (mapcar #'class-name (remove-duplicates (collect-subclasses (if (symbolp class) (find-class class) class))))))\n\n(defun scale-bytes (byte-count)\n  (cond\n    ((> byte-count (* 1024 1024 1024 1024))\n     (format nil \"~3,1F TB\" (/ byte-count (* 1024 1024 1024 1024))))\n    ((> byte-count (* 1024 1024 1024))\n     (format nil \"~3,1F GB\" (/ byte-count (* 1024 1024 1024))))\n    ((> byte-count (* 1024 1024))\n     (format nil \"~3,1F MB\" (/ byte-count (* 1024 1024))))\n    ((> byte-count 1024)\n     (format nil \"~3,1F KB\" (/ byte-count 1024)))\n    (t\n     (format nil \"~A\" byte-count))))\n\n(defun subseq* (sequence start &optional end)\n  \"Like SUBSEQ, but limit END to the length of SEQUENCE\"\n  (subseq sequence start (when end\n                           (min end (length sequence)))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/utils/xml.lisp",
    "content": ""
  },
  {
    "path": "third-party/bknr.datastore/src/xml/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.xml\n  (:use :cl\n\t:cl-ppcre\n        :cl-interpol\n\t:cxml-xmls)\n  (:shadowing-import-from :cl-interpol \"QUOTE-META-CHARS\")\n  (:export\n   #:node-children-nodes\n   #:find-child\n   #:find-children\n   #:node-string-body\n   #:node-attribute\n   #:node-child-string-body\n   #:node-to-html))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml/xml.lisp",
    "content": "(in-package :bknr.xml)\n\n(defun node-children-nodes (xml)\n  (remove-if-not #'consp (node-children xml)))\n\n(defun find-child (xml node-name)\n  (let ((children (node-children-nodes xml)))\n    (find node-name children :test #'string-equal :key #'node-name)))\n\n(defun find-children (xml node-name)\n  (let ((children (node-children-nodes xml)))\n    (find-all node-name children :test #'string-equal :key #'node-name)))\n\n(defun node-string-body (xml)\n  (let ((children (remove-if #'consp (node-children xml))))\n    (if (every #'stringp children)\n\t(apply #'concatenate 'string children)\n\t(error \"Some children are not strings\"))))\n\n(defun node-attribute (xml attribute-name)\n  (cadr (assoc attribute-name (node-attrs xml) :test #'equal)))\n\n(defun node-child-string-body (xml node-name)\n  (let ((child (find-child xml node-name)))\n    (if (and child (consp child))\n\t(node-string-body child)\n\tnil)))\n\n(defun node-to-html (node &optional (stream *standard-output*))\n  (when (stringp node)\n    (write-string node)\n    (return-from node-to-html))\n  (write-char #\\< stream)\n  (when (node-ns node)\n    (write-string (node-ns node) stream)\n    (write-char #\\: stream))\n  (write-string (node-name node) stream)\n  (loop for (key value) in (node-attrs node)\n\tdo (write-char #\\Space stream)\n\t(write-string key stream)\n\t(write-char #\\= stream)\n\t(write-char #\\\" stream)\n\t(write-string value stream)\n\t(write-char #\\\" stream))\n  (if (node-children node)\n      (progn \n\t(write-char #\\> stream)\n\t(write-char #\\Newline stream)\n\t(dolist (child (node-children node))\n\t  (node-to-html child stream))\n\t(write-char #\\< stream)\n\t(write-char #\\/ stream)\n\t(when (node-ns node)\n\t  (write-string (node-ns node) stream)\n\t  (write-char #\\: stream))\n\t(write-string (node-name node) stream)\n\t(write-char #\\> stream)\n\t(write-char #\\Newline stream))\n      (progn (write-char #\\Space stream)\n\t     (write-char #\\/ stream)\n\t     (write-char #\\> stream)\n\t     (write-char #\\Newline stream))))\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/package.lisp",
    "content": "(in-package :cl-user)\n\n(defpackage :bknr.impex\n  (:use :cxml\n        :closer-common-lisp\n        :bknr.utils\n        :bknr.xml\n        :bknr.indices)\n  #+cmu\n  (:shadowing-import-from :common-lisp #:subtypep #:typep)\n\n  (:export #:xml-class\n\t   #:parse-xml-file\n\t   #:write-to-xml\n\t   #:xml-class-importer\n\n           #:with-xml-export\n           #:with-xml-export*\n           #:write-to-xml\n\n\t   #:create-instance\n\t   #:set-slot-value))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial.dtd",
    "content": "<!ELEMENT books (book)*>\n<!ELEMENT book (author,title)>\n<!ELEMENT author (#PCDATA)>\n<!ELEMENT title (#PCDATA)>\n\n<!ATTLIST book id   ID    #REQUIRED\n               isbn CDATA #REQUIRED>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial.lisp",
    "content": ";;; XML Import Export for Common Lisp\n\n;;;# Introduction\n;;;\n;;; We often have to communicate data with the external world, for\n;;; example sharing data with external programs or getting information\n;;; from a third-party. The XML format has been pretty successful as a\n;;; common base format for digital information. However, parsing and\n;;; handling and serializing XML data is not very practical, though\n;;; not very difficult, and most of the time just plain\n;;; boring. Imagine you have a XML file containing information about\n;;; books in a library. Most of the time, you will parse the XML data,\n;;; and instantiate CLOS objects in order to use standard LISP\n;;; functions on the data. The parsing and serializing code is most of\n;;; the time trivial. The XML import/export module in BKNR enables you\n;;; to annotate standard CLOS class definitions in order to make\n;;; parsing and serializing XML data automatic.\n;;;\n;;; The XML import/export modules works by using DTDs to represent the\n;;; format of the XML file. DTDs are quite simple and pretty\n;;; widespread. A DTD specifies which elements are valid in a XML\n;;; file, which attributes these elements have, and how the elements\n;;; are nested. When defining a CLOS class, you can specify a DTD, and\n;;; annotate the slot definitions with information about which slot is\n;;; mapped to which attribute or element in the DTD.\n;;;\n;;; This tutorial will show you how to parse and serialize CLOS\n;;; classes to XML, and how to use features like specifying relations\n;;; on IDs.\n\n;;;# Obtaining and loading BKNR XML import/export\n;;;\n;;; You can obtain the current CVS sources of BKNR by following the\n;;; instructions at `http://bknr.net/'. Add the `src' directory of\n;;; BKNR to your `asdf:*central-registry*', and load the indices\n;;; module by evaluating the following form:\n\n(asdf:oos 'asdf:load-op :bknr.impex)\n\n;;; Then switch to the `bknr.impex' package to try out the tutorial.\n\n(in-package :bknr.impex)\n\n;;;# A simple example\n;;;\n;;; We start with our book example. We have to manipulate informations\n;;; about the books present in a library, and the library provides us\n;;; with a XML file containing all the books present in the library. A\n;;; sample XML file has the following format:\n\n<books>\n  <book id=\"1\" isbn=\"234114\">\n    <author>J.R.R Tolkien</author>\n    <title>The Lord of the Rings</title>\n  </book>\n  <book id=\"2\" isbn=\"235431\">\n    <author>William Gibson</author>\n    <title>Neuromancer</title>\n  </book>\n</books>\n\n;;; The DTD for the sample above is pretty simple too:\n\n<!ELEMENT books (book)*>\n<!ELEMENT book (author,title)>\n<!ELEMENT author (#PCDATA)>\n<!ELEMENT title (#PCDATA)>\n\n<!ATTLIST book id   ID    #REQUIRED\n               isbn CDATA #REQUIRED>\n\n;;; We now specify our CLOS class book without XML annotations. The\n;;; common approach to XML parsing would now implement either a SAX\n;;; parser filling a `BOOK' class from a `book' XML element, or a DOM\n;;; parser walking down the DOM tree to instantiate `BOOK' objects at\n;;; `book' leaves.\n\n(defclass book ()\n  ((author :initarg :author :reader book-author)\n   (id :initarg :id :reader book-id :type integer)\n   (isbn :initarg :isbn :reader book-isbn)\n   (title :initarg :title :reader book-title)))\n\n(defmethod print-object ((book book) stream)\n  (print-unreadable-object (book stream :type t :identity t)\n    (format stream \"~S\" (book-title book))))\n\n;;; We can however extend the `BOOK' class definitions by giving it\n;;; the metaclass `XML-CLASS', by specifying the DTD to be used by the\n;;; class, and by specifying the XML element corresponding to the\n;;; class. We also annotate the slot definitions.\n\n;;; If we have evaluated the previous book definition we must reset it now\n;;; before proceeding:\n(setf (find-class 'book) nil)\n\n(defvar *tutorial-dtd* \"../xml-impex/tutorial.dtd\")\n\n(defclass book ()\n  ((author :initarg :author :reader book-author\n           :element \"author\")\n   (id :initarg :id :reader book-id :type integer\n       :attribute \"id\" :parser #'parse-integer)\n   (isbn :initarg :isbn :reader book-isbn\n         :attribute \"isbn\")\n   (title :initarg :title :reader book-title\n          :element \"title\"))\n  (:metaclass xml-class)\n  (:dtd-name *tutorial-dtd*)\n  (:element \"book\"))\n\n;;; We can now read the XML file containing the book definitions. As\n;;; we haven't specified a CLOS class for the root element `books', we\n;;; get back a list containing all the children nodes of `books' under\n;;; the keyword `:BOOK'.\n\n(parse-xml-file \"../xml-impex/tutorial.xml\" (list (find-class 'book)))\n; => (:BOOK\n;     (#<BOOK (\"The Lord of the Rings\") {4922BCD5}>\n;      #<BOOK (\"Neuromancer\") {4922E0B5}>))\n(setf *books*\n      (getf (parse-xml-file \"../xml-impex/tutorial.xml\"\n                            (list (find-class 'book)))\n            :BOOK))\n; => (#<BOOK (\"The Lord of the Rings\") {4922BCD5}>\n;     #<BOOK (\"Neuromancer\") {4922E0B5}>)\n\n;;; In the same way, we can serialize our books back to XML using the\n;;; function `WRITE-TO-XML'. As we still do not have a class for the\n;;; root element (and we never will), we can specify the name of the\n;;; root element as an argument.\n\n(write-to-xml *books* :name \"books\")\n; => <books>\n;     <book id=\"1\" isbn=\"234114\">\n;        <author>J.R.R Tolkien</author>\n;        <title>The Lord of the Rings</title>\n;     </book>\n;     <book id=\"2\" isbn=\"235431\">\n;        <author>William Gibson</author>\n;        <title>Neuromancer</title>\n;     </book>\n;    </books>\n\n;;; As you have noticed, we use an in ID in the book class. It would\n;;; be nice to have an index for all the books. This is very simple,\n;;; as a class with metaclass `XML-CLASS' also support BKNR\n;;; indices. Thus, you can write:\n\n(defclass book ()\n  ((author :initarg :author :reader book-author\n           :element \"author\"\n           :index-type hash-index :index-initargs (:test #'equal)\n           :index-reader books-with-author\n           :index-keys all-authors)\n   (id :initarg :id :reader book-id :type integer\n       :attribute \"id\" :parser #'parse-integer\n       :index-type unique-index :index-reader book-with-id\n       :index-values all-books)\n   (isbn :initarg :isbn :reader book-isbn\n         :attribute \"isbn\"\n         :index-type unique-index :index-initargs (:test #'equal)\n         :index-reader book-with-isbn)\n   (title :initarg :title :reader book-title\n          :element \"title\"))\n  (:metaclass xml-class)\n  (:dtd-name *tutorial-dtd*)\n  (:element \"book\"))\n\n;;; We can now import our XML file and the indices will automatically\n;;; get filled. The indices change nothing to the serialization.\n\n(parse-xml-file \"../xml-impex/tutorial.xml\" (list (find-class 'book)))\n; => (:BOOK\n;     (#<BOOK \"The Lord of the Rings\" {49224D25}>\n;      #<BOOK \"Neuromancer\" {492272CD}>))\n(all-authors)\n; => (\"J.R.R Tolkien\" \"William Gibson\")\n(all-books)\n; => (#<BOOK \"The Lord of the Rings\" {49224D25}>\n;     #<BOOK \"Neuromancer\" {492272CD}>)\n(books-with-author (first (all-authors)))\n; => (#<BOOK \"The Lord of the Rings\" {49224D25}>)\n; T\n(book-with-id 1)\n; => #<BOOK \"The Lord of the Rings\" {49224D25}>\n;  T\n(write-to-xml *books* :name \"books\")\n; => <books>\n;       <book id=\"1\" isbn=\"234114\">\n;          <author>J.R.R Tolkien</author>\n;          <title>The Lord of the Rings</title>\n;       </book>\n;       <book id=\"2\" isbn=\"235431\">\n;          <author>William Gibson</author>\n;          <title>Neuromancer</title>\n;       </book>\n;    </books>\n\n;;; Note that you have to clear the indices if you reload the same XML\n;;; file multiple times.\n\n(clear-class-indices (find-class 'book))\n; => NIL\n\n;;;# Specifying a mapping from CLOS to XML\n;;;\n;;; In the following text, \"XML class\" refers to a CLOS class with\n;;; metaclass `XML-CLASS', and \"XML object\" refers to an instance of\n;;; an \"XML class\".\n;;;\n;;;## Class options\n;;;\n;;; Specifying `XML-CLASS' as a metaclass for a CLOS class has two\n;;; effects. First, you can use slot indices and class indices for\n;;; your class, as `XML-CLASS' inherits from `INDEXED-CLASS' (see the\n;;; BKNR Indices tutorial for more information). Second, you can\n;;; specify a DTD to use with your class, and an optional element name\n;;; to which the class will be mapped. Please note that when\n;;; you have multiple XML classes, it is best to specify the same DTD\n;;; object, as `EQL' is used to reduce the list of DTDs when exporting\n;;; XML objects.\n;;;\n;;; A CLOS class is mapped to an element of the DTD. In our example\n;;; above, the class `BOOK' was mapped to the DTD element `book'. This\n;;; is done by specifying the `:ELEMENT' class option. The XML\n;;; import/export code then searchs the element definition in the DTD,\n;;; and stores it in the class object. It then maps the slots of the\n;;; class to the attributes and child element of the element\n;;; definition in the DTD.\n;;;\n;;;### Specifying an empty class element\n;;;\n;;; The `:ELEMENT' class option is optional. The optional element\n;;; specification is there to be able to derive objects which have the\n;;; same attributes without having to rewrite everything. For example,\n;;; suppose we have the following DTD:\n\n<!ELEMENT test EMPTY>\n<!ELEMENT test2 EMPTY>\n<!ELEMENT test3 EMPTY>\n\n<!ATTLIST test id ID #REQUIRED>\n<!ATTLIST test2 id ID #REQUIRED>\n<!ATTLIST test3 id ID #REQUIRED>\n\n;;; We can then write the following class definitions:\n\n(defvar *test-dtd* \"../xml-impex/tutorial2.dtd\")\n\n(defclass test-object ()\n  ((id :initarg :id :attribute \"id\"\n       :parser #'parse-integer\n       :index-type unique-index :index-reader object-with-id\n       :index-values all-objects))\n  (:metaclass xml-class)\n  (:dtd-name *test-dtd*)\n  (:element nil))\n\n(defmethod print-object ((object test-object) stream)\n  (print-unreadable-object (object stream :type t)\n    (format stream \"~A\" (slot-value object 'id))))\n\n(defclass test (test-object)\n  ((id :index-type unique-index\n       :index-reader test-with-id\n       :index-values all-tests))\n  (:metaclass xml-class)\n  (:dtd-name *test-dtd*)\n  (:element \"test\"))\n\n(defclass test2 (test-object)\n  ((id :index-type unique-index\n       :index-reader test2-with-id\n       :index-values all-test2s))\n  (:metaclass xml-class)\n  (:dtd-name *test-dtd*)\n  (:element \"test2\"))\n\n(defclass test3 (test-object)\n  ((id :index-type unique-index\n       :index-reader test3-with-id\n       :index-values all-test3s))\n  (:metaclass xml-class)\n  (:dtd-name *test-dtd*)\n  (:element \"test3\"))\n\n;;; When we parse a sample file, we get the following results:\n\n(parse-xml-file \"../xml-impex/tutorial2.xml\"\n                (mapcar #'find-class '(test test2 test3)))\n; => (:TEST3 (#<TEST3 3>) :TEST2 (#<TEST2 2>)\n;     :TEST (#<TEST 1>))\n(all-objects)\n; => (#<TEST 1> #<TEST2 2> #<TEST3 3>)\n(all-tests)\n; => (#<TEST 1>)\n(object-with-id 1)\n; => #<TEST 1>\n;    T\n(test2-with-id 2)\n; => #<TEST2 2>\n;    T\n\n;;;## Slot options\n;;;\n;;; The slots of a class can be mapped to different structures of the\n;;; DTD. A slot can be:\n;;;\n;;;### The :attribute slot option\n;;;\n;;; an \"attribute\" - the value of the slot is stored as an attribute\n;;; of the element corresponding to the class. For example, in the\n;;; book example, both the slot `ID' and the slot `ISBN' are stored as\n;;; attribute of the element \"book\". The import/export code verifies\n;;; that the attributes are specified in the DTD.\n;;;\n;;;### The :element slot option\n;;;\n;;; an \"element\" - the value of the slot is stored as the CDATA body\n;;; of a child element. For example, in the book example, both the\n;;; slot `TITLE' and the slot `AUTOR' are stored as child elements of\n;;; the element \"book\". The element has to be specified as a single or\n;;; optional child of the class element. When the element is specified\n;;; as multiple children (either \"*\" or \"+\" in the DTD), then the slot\n;;; value is assumed to be a list of children. For example, if we have\n;;; the DTD:\n\n<!ELEMENT adult (child)*>\n<!ELEMENT child EMPTY>\n<!ATTLIST adult name CDATA #REQUIRED>\n<!ATTLIST child name CDATA #REQUIRED>\n\n;;; we can write the following class definition:\n\n(defvar *adult-dtd* \"../xml-impex/tutorial3.dtd\")\n\n(defclass adult ()\n  ((name :initarg :name :attribute \"name\"\n         :reader adult-name)\n   (children :initarg :children :element \"child\"\n             :reader adult-children :containment :*))\n\n  (:metaclass xml-class)\n  (:dtd-name *adult-dtd*)\n  (:element \"adult\"))\n\n(defmethod print-object ((adult adult) stream)\n  (print-unreadable-object (adult stream :type t)\n    (princ (adult-name adult) stream)))\n\n(defclass child ()\n  ((name :initarg :name :attribute \"name\"\n         :reader child-name))\n  (:metaclass xml-class)\n  (:dtd-name *adult-dtd*)\n  (:element \"child\"))\n\n(defmethod print-object ((child child) stream)\n  (print-unreadable-object (child stream :type t)\n    (princ (child-name child) stream)))\n\n;;; We can then parse the following XML file:\n\n<family>\n  <adult name=\"Clara\">\n    <child name=\"Robert\"/>\n    <child name=\"Anton\"/>\n  </adult>\n  <adult name=\"Joseph\">\n    <child name=\"Ludwig\"/>\n    <child name=\"Benediktine\"/>\n  </adult>\n</family>\n\n(setf *adults* \n      (getf (parse-xml-file \"../xml-impex/tutorial3.xml\"\n                            (mapcar #'find-class '(adult child)))\n            :adult))\n; => (#<ADULT Clara> #<ADULT Joseph>)\n(adult-children (first *adults*))\n\n; => (#<CHILD Robert> #<CHILD Anton>)\n(write-to-xml *adults* :name \"family\")\n; => <family>\n;       <adult name=\"Joseph\">\n;          <child name=\"Benediktine\"/>\n;          <child name=\"Ludwig\"/>\n;       </adult>\n;       <adult name=\"Clara\">\n;          <child name=\"Anton\"/>\n;          <child name=\"Robert\"/>\n;       </adult>\n;    </family>\n\n;;;### The :parent slot option\n;;;\n;;; Assume that we want to know who the parent of a child is. We can\n;;; do this by creating a slot with the `:PARENT' slot option. This\n;;; will automatically get filled by the object representing the\n;;; parent element in the XML file.\n\n(defclass child ()\n  ((name :initarg :name :attribute \"name\"\n         :reader child-name)\n   (parent :initarg :parent :parent t\n           :reader child-parent))\n  (:metaclass xml-class)\n  (:dtd-name *adult-dtd*)\n  (:element \"child\"))\n\n(setf *adults* \n      (getf (parse-xml-file \"../xml-impex/tutorial3.xml\"\n                            (mapcar #'find-class '(adult child)))\n            :adult))\n; => (#<ADULT Joseph> #<ADULT Clara>)\n(adult-children (first *adults*))\n; => (#<CHILD Benediktine> #<CHILD Ludwig>)\n(child-parent (first (adult-children (first *adults*))))\n; => #<ADULT Joseph>\n\n;;;### The :body slot option\n;;;\n;;; Sometimes, an XML element has a important string as body, and we\n;;; would like to store this string in a slot. This can be done by\n;;; setting the `:BODY' slot option. Note that only one PCDATA body is\n;;; permitted when using this option. For example, we could use this\n;;; code to read in the DTD:\n\n<!ELEMENT book-resume (#PCDATA)>\n<!ATTLIST book-resume id ID #REQUIRED\n                      book-id CDATA #REQUIRED\n                      reviewer CDATA #REQUIRED>\n\n(defvar *resume-dtd* \"../xml-impex/tutorial4.dtd\")\n\n(defclass book-resume ()\n  ((id :initarg :id :attribute \"id\"\n       :parser #'parse-integer\n       :reader book-resume-id)\n   (book-id :initarg :book-id :attribute \"book-id\"\n            :parser #'parse-integer\n            :reader book-resume-book-id)\n   (reviewer :initarg :reviewer :attribute \"reviewer\"\n             :reader book-resume-reviewer)\n   (review :initarg :review :body t\n           :reader book-resume-review))\n  (:metaclass xml-class)\n  (:dtd-name *resume-dtd*)\n  (:element \"book-resume\"))\n\n;;; Parsing the following file gives the results:\n\n<resumes>\n  <book-resume id=\"1\" book-id=\"45\"\n               reviewer=\"Henry von der Grande\">\nBla bla bla. Resume highlight blablabla foobar blorg.\n  </book-resume>\n  <book-resume id=\"2\" book-id=\"1337\"\n               reviewer=\"L0rd 3v1l\">\nBl4 bl4 bl4. R3s\\/m3 h1ghl1ght bl4bl4bl4 f00b4r bl0rg.\n  </book-resume>\n</resumes>\n\n(setf *resumes*\n      (getf (parse-xml-file \"../xml-impex/tutorial4.xml\"\n                            (list (find-class 'book-resume)))\n            :book-resume))\n; => (#<BOOK-RESUME {4947F22D}> #<BOOK-RESUME {4947DB95}>)\n(book-resume-review (first *resumes*))\n; => \"\n; Bl4 bl4 bl4. R3s\\\\/m3 h1ghl1ght bl4bl4bl4 f00b4r bl0rg.\n; \"\n(write-to-xml *resumes* :name \"resumes\")\n; => <resumes>\n;       <book-resume book-id=\"1337\" id=\"2\"\n;                    reviewer=\"L0rd 3v1l\">\n;    Bl4 bl4 bl4. R3s\\/m3 h1ghl1ght bl4bl4bl4 f00b4r bl0rg.\n;       </book-resume>\n;       <book-resume book-id=\"45\" id=\"1\"\n;                    reviewer=\"Henry von der Grande\">\n;    Bla bla bla. Resume highlight blablabla foobar blorg.\n;      </book-resume>\n;    </resumes>\n\n;;;## Using IDs in XML import/export\n;;;\n;;; Often, you want to reference objects by ID in the XML file. For\n;;; example, we could specify both authors and books on the toplevel,\n;;; and reference the author by ID inside the document definition. The\n;;; DTD would look like this:\n\n<!ELEMENT book (title)>\n<!ATTLIST book id ID #REQUIRED\n               autor CDATA #REQUIRED>\n<!ELEMENT title #PCDATA>\n<!ELEMENT author (name)>\n<!ATTLIST author id ID #REQUIRED>\n<!ELEMENT name #PCDATA>\n\n;;; We can write the following class definitions:\n\n(defparameter *book2-dtd* \"../xml-impex/tutorial5.dtd\")\n\n(defclass author ()\n  ((id :initarg :id :reader author-id\n       :attribute \"id\" :parser #'parse-integer\n       :index-type unique-index :index-reader author-with-id\n       :index-values all-authors)\n   (name :initarg :name :reader author-name\n         :element \"name\"))\n  (:metaclass xml-class)\n  (:dtd-name *book2-dtd*)\n  (:element \"author\"))\n\n(defmethod print-object ((author author) stream)\n  (print-unreadable-object (author stream :type t)\n    (princ (author-name author) stream)))\n\n(defclass book ()\n  ((id :initarg :id :reader book-id\n       :attribute \"id\" :parser #'parse-integer\n       :index-type unique-index :index-reader book-with-id\n       :index-values all-books)\n   (author :initarg :author :reader book-author\n          :attribute \"author\" :parser #'parse-integer\n          :id-to-object #'author-with-id\n          :object-to-id #'author-id)\n   (title :initarg :title :reader book-title\n          :element \"title\"))\n  (:metaclass xml-class)\n  (:dtd-name *book2-dtd*)\n  (:element \"book\"))\n       \n;;; We can then read the following XML file:\n\n<library>\n  <author id=\"1\">\n    <name>J.R.R Tolkien</name>\n  </author>\n  <author id=\"2\">\n     <name>William Gibson</name>\n  </author>\n  <book id=\"3\" author=\"1\">\n    <title>Lord of the Rings</title>\n  </book>\n  <book id=\"4\" author=\"2\">\n    <title>Neuromancer</title>\n  </book>\n</library>\n\n(map nil #'clear-class-indices\n     (mapcar #'find-class '(book author)))\n; => NIL\n(parse-xml-file \"../xml-impex/tutorial5.xml\"\n                (mapcar #'find-class '(book author)))\n; => (:BOOK\n;     (#<BOOK \"Neuromancer\" {494AEFC5}>\n;      #<BOOK \"Lord of the Rings\" {494AD48D}>)\n;     :AUTHOR (#<AUTHOR William Gibson>\n;              #<AUTHOR J.R.R Tolkien>))\n(all-authors)\n; => (#<AUTHOR J.R.R Tolkien> #<AUTHOR William Gibson>)\n(all-books)\n; => (#<BOOK \"Lord of the Rings\" {494AD48D}>\n;     #<BOOK \"Neuromancer\" {494AEFC5}>)\n(book-author (first (all-books)))\n; => #<AUTHOR J.R.R Tolkien>\n(write-to-xml (append (all-books) (all-authors))\n              :name \"library\")\n; => <library>\n;       <book author=\"1\" id=\"3\">\n;          <title>Lord of the Rings</title>\n;       </book>\n;       <book author=\"2\" id=\"4\">\n;          <title>Neuromancer</title>\n;       </book>\n;       <author id=\"1\">\n;          <name>J.R.R Tolkien</name>\n;       </author>\n;       <author id=\"2\">\n;          <name>William Gibson</name>\n;       </author>\n;    </library>\n\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial.xml",
    "content": "<books>\n  <book id=\"1\" isbn=\"234114\">\n    <author>J.R.R Tolkien</author>\n    <title>The Lord of the Rings</title>\n  </book>\n  <book id=\"2\" isbn=\"235431\">\n    <author>William Gibson</author>\n    <title>Neuromancer</title>\n  </book>\n</books>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial2.dtd",
    "content": "<!ELEMENT test EMPTY>\n<!ELEMENT test2 EMPTY>\n<!ELEMENT test3 EMPTY>\n\n<!ATTLIST test id ID #REQUIRED>\n<!ATTLIST test2 id ID #REQUIRED>\n<!ATTLIST test3 id ID #REQUIRED>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial2.xml",
    "content": "<tests>\n  <test id=\"1\"/>\n  <test2 id=\"2\"/>\n  <test3 id=\"3\"/>\n</tests>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial3.dtd",
    "content": "<!ELEMENT adult (child)*>\n<!ELEMENT child EMPTY>\n<!ATTLIST adult name CDATA #REQUIRED>\n<!ATTLIST child name CDATA #REQUIRED>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial3.xml",
    "content": "<family>\n  <adult name=\"Clara\">\n    <child name=\"Robert\"/>\n    <child name=\"Anton\"/>\n  </adult>\n  <adult name=\"Joseph\">\n    <child name=\"Ludwig\"/>\n    <child name=\"Benediktine\"/>\n  </adult>\n</family>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial4.dtd",
    "content": "<!ELEMENT book-resume (#PCDATA)>\n<!ATTLIST book-resume id ID #REQUIRED\n                      book-id CDATA #REQUIRED\n                      reviewer CDATA #REQUIRED>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial4.xml",
    "content": "<resumes>\n  <book-resume id=\"1\" book-id=\"45\" reviewer=\"Henry von der Grande\">\nBla bla bla. Resume highlight blablabla foobar blorg.\n  </book-resume>\n  <book-resume id=\"2\" book-id=\"1337\" reviewer=\"L0rd 3v1l\">\nBl4 bl4 bl4. R3s\\/m3 h1gh|1ght bl4bl4bl4 f00b4r bl0rg.\n  </book-resume>\n</resumes>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial5.dtd",
    "content": "<!ELEMENT book (title)>\n<!ATTLIST book id ID #REQUIRED\n               author CDATA #REQUIRED>\n<!ELEMENT title #PCDATA>\n<!ELEMENT author (name)>\n<!ATTLIST author id ID #REQUIRED>\n<!ELEMENT name #PCDATA>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/tutorial5.xml",
    "content": "<library>\n  <author id=\"1\">\n    <name>J.R.R Tolkien</name>\n  </author>\n  <author id=\"2\">\n     <name>William Gibson</name>\n  </author>\n  <book id=\"3\" author=\"1\">\n    <title>Lord of the Rings</title>\n  </book>\n  <book id=\"4\" author=\"2\">\n    <title>Neuromancer</title>\n  </book>\n</library>\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/xml-class.lisp",
    "content": "(in-package :bknr.impex)\n\n(defclass xml-class (indexed-class)\n  ((element :initarg :element :initform nil :accessor xml-class-element)\n   (unique-id-slot   :initarg :unique-id-slot :initform nil\n                     :documentation \"if set to a slot name, this\nsignals that the slot can be used as a unique id to refer to an\ninstance of the object in a n XML update operation\")\n   (unique-id-reader :initarg :unique-id-reader :initform nil\n                     :documentation \"if set to a function, this\nsignals that the function can be used as a unique index-reader when\nused in XML update operations.\")\n   (dtd-name :reader xml-class-dtd-name)))\n\n(defmethod xml-class-unique-id-slot ((class xml-class))\n  (first (slot-value class 'unique-id-slot)))\n\n(defmethod xml-class-unique-id-reader ((class xml-class))\n  (eval (first (slot-value class 'unique-id-reader))))\n\n(defmethod validate-superclass ((sub xml-class) (super indexed-class))\n  t)\n\n(defun princ-to-string-1 (object)\n  (when object\n    (princ-to-string object)))\n\n(defclass xml-direct-slot-definition (bknr.indices::index-direct-slot-definition)\n  ((attribute :initarg :attribute\n              :initform nil\n              :documentation \"Name of attribute to use to impex the slot.\")\n   (element :initarg :element\n            :initform nil\n            :documentation \"Name of the element to use to impex the slot.\")\n   (body :initarg :body\n         :initform nil\n         :documentation \"Whether the value of the slot has to be stored in the body of the class element.\")\n   (containment :initarg :containment\n                :initform nil\n                :documentation \"Containment specification for this slot, either nil, :* or :+\")\n   (parser :initarg :parser\n           :initform #'identity\n           :documentation \"Function used to parse the slot value from the XML string.\")\n   (serializer :initarg :serializer\n               :initform #'princ-to-string-1\n               :documentation \"Function used to serialize the slot back to XML.\")\n\n   (object-id-slot :initarg :object-id-slot\n                   :initform nil\n                   :documentation \"If this slot is non-nil, the slot's\nvalue is considered to be the unique object id of the object.  During\nexport, objects which have an object-id-slot will only be serialized\nonce.  Further occurances of the same object will be referenced\nthrough the object-id-slot (either an element or an attribute)\")\n\n   (id-to-object :initarg :id-to-object\n                 :initform nil\n                 :documentation \"Function used to get the value pointed to by the ID.\")\n   (object-to-id :initarg :object-to-id\n                 :initform nil\n                 :documentation \"Function used to get the ID of the object stored in the slot.\")\n\n   (parent :initarg :parent\n           :initform nil\n           :documentation \"Slot is a pointer to the parent object.\")))\n\n(defclass xml-effective-slot-definition (bknr.indices::index-effective-slot-definition)\n  ((body :initform nil)\n   (element :initform nil :reader xml-effective-slot-definition-element)\n   (attribute :initform nil :reader xml-effective-slot-definition-attribute)\n\n   (parser :initform nil :reader xml-effective-slot-definition-parser)\n   (serializer :initform nil :reader xml-effective-slot-definition-serializer)\n\n   (object-id-slot :initform nil :reader xml-effective-slot-definition-object-id-slot)\n   \n   (id-to-object :initform nil)\n   (object-to-id :initform nil)\n   (parent :initform nil)\n\n   (containment :initform nil :reader xml-effective-slot-definition-containment)\n   (required-p :initform nil :reader xml-effective-slot-definition-required-p)))\n\n(defmethod print-object ((slot xml-effective-slot-definition) stream)\n  (print-unreadable-object (slot stream :type t :identity t)\n    (with-slots (attribute element body parent) slot\n      (format stream \"~A (~A~@[ ~S~])\" (slot-definition-name slot)\n              (cond (attribute \"ATTRIBUTE\")\n                    (element \"ELEMENT\")\n                    (body \"BODY\")\n                    (parent \"PARENT\")\n                    (t \"UNKNOWN\"))\n              (or attribute element)))))\n\n(defmethod xml-class-attribute-slots ((class xml-class))\n  (remove-if #'(lambda (slot)\n                 (or (not (typep slot 'xml-effective-slot-definition))\n                     (not (slot-value slot 'attribute)))) (class-slots class)))\n\n(defmethod xml-class-element-slots ((class xml-class))\n  (remove-if #'(lambda (slot)\n                 (or (not (typep slot 'xml-effective-slot-definition))\n                     (not (slot-value slot 'element)))) (class-slots class)))\n\n(defmethod xml-class-body-slot ((class xml-class))\n  (let ((body-slots\n         (remove-if #'(lambda (slot)\n                        (or (not (typep slot 'xml-effective-slot-definition))\n                            (not (slot-value slot 'body)))) (class-slots class))))\n    (when (> (length body-slots) 1)\n      (error \"Class ~A has more than one body slot: ~A.\" class body-slots))\n    (first body-slots)))\n\n(defmethod xml-class-find-attribute-slot ((class xml-class) attribute)\n  (find attribute (xml-class-attribute-slots class)\n        :test #'string-equal\n        :key #'(lambda (slot) (slot-value slot 'attribute))))\n\n(defmethod xml-class-find-element-slot ((class xml-class) element)\n  (find element (xml-class-element-slots class)\n        :test #'string-equal\n        :key #'(lambda (slot) (slot-value slot 'element))))\n\n(defmethod xml-class-parent-slot ((class xml-class))\n  (let ((parent-slots\n         (remove-if #'(lambda (slot)\n                        (or (not (typep slot 'xml-effective-slot-definition))\n                            (not (slot-value slot 'parent))))\n                    (class-slots class))))\n    (when (> (length parent-slots) 1)\n      (error \"Class ~A has more than one parent slot: ~A.\" class parent-slots))\n    (first parent-slots)))\n\n(defmethod initialize-instance :after ((class xml-class) &key element dtd-name)\n  (setf (slot-value class 'dtd-name) (symbol-value (first dtd-name))\n        (xml-class-element class) (or (first element) (string-downcase (class-name class))))\n  (xml-class-finalize class))\n\n(defmethod reinitialize-instance :after ((class xml-class) &key element dtd-name)\n  (setf (slot-value class 'dtd-name) (symbol-value (first dtd-name))\n        (xml-class-element class) (or (first element) (string-downcase (class-name class))))\n  (xml-class-finalize class))\n\n(defun get-dtd (dtd)\n  (cond ((or (stringp dtd)\n             (pathnamep dtd))\n         (cxml:parse-dtd-file dtd))\n        ((typep dtd 'cxml::dtd) dtd)\n        (t (let ((dtd (eval dtd)))\n             (unless (typep dtd 'cxml::dtd)\n;               (error \"DTD ~A is not a CXML dtd.\" dtd))\n               (warn \"DTD ~A is not a CXML dtd.\" dtd))\n             dtd))))\n\n(defun get-dtd-elmdef (dtd elmdef)\n  \"Finds an element definition in a DTD. Returns a cxml:elmdef\"\n  (typecase elmdef\n    (string (unless dtd\n              (error \"Can not find elmdef ~a in dtd ~A.\" elmdef dtd))\n            (cxml::find-element (cxml::string-rod elmdef) dtd))\n    (cxml::elmdef elmdef)\n    (t (let ((elmdef (eval elmdef)))\n         (unless (typep elmdef 'cxml::elmdef)\n           (error \"Elmdef ~A is not a CXML elmdef.\" elmdef))\n         elmdef))))\n\n(defmethod elmdef-children ((elmdef cxml::elmdef))\n  \"Analyses the content field of a given elmdef and returns a list of element/containment\npairs, representing the childs and their containment definition\"\n  (let (result)\n    (labels ((elmdef-children-rec (content containment)\n               (format t \"~S content containmnt ~S~%\" content containment)\n               (cond ((and (listp content)\n                           (member (first content) '(cxml::and cxml::or)))\n                      (dolist (child (cdr content))\n                        (elmdef-children-rec child containment)))\n                     ((and (listp content)\n                           (eql (first content) 'cxml::+))\n                      (dolist (child (cdr content))\n                        (elmdef-children-rec child :+)))\n                     ((and (listp content)\n                           (eql (first content) 'cxml::*))\n                      (dolist (child (cdr content))\n                        (elmdef-children-rec child :*)))\n                     ((and (listp content)\n                           (eql (first content) 'cxml::?))\n                      (dolist (child (cdr content))\n                        (elmdef-children-rec child :optional)))\n                     ((listp content)\n                      (error \"Unknown content form ~S (missing element declaration for ~S in DTD?).\" content (cxml::elmdef-name elmdef)))\n                     ((eql content :pcdata))\n                     ((eql content :empty))\n                     (t (push (list content containment) result)))))\n      (elmdef-children-rec (cxml::elmdef-content elmdef) :single)\n      (nreverse result))))\n\n(defmethod xml-class-finalize ((class xml-class))\n  (unless (class-finalized-p class)\n    (finalize-inheritance class))\n  \n  (let* ((slots (class-slots class))\n         (dtd (get-dtd (xml-class-dtd-name class))) \n         (elmdef (when dtd (get-dtd-elmdef dtd (xml-class-element class)))))\n    \n    (unless elmdef\n      (return-from xml-class-finalize))\n    ;;; check attributes\n    (dolist (attr (cxml::elmdef-attributes elmdef))\n      (let ((attr-name (cxml::rod-string (cxml::attdef-name attr))))\n        (when (eql (cxml::attdef-default attr) :required)\n          (let ((slot (xml-class-find-attribute-slot class attr-name)))\n            (when (not slot)\n              (warn \"Could not find slot for required attribute ~A.\" attr-name))))))\n    ;;; check elements\n    (dolist (child (elmdef-children elmdef))\n      (let* ((child-name (cxml::rod-string (first child)))\n             (child-containment (second child))\n             (slot (xml-class-find-element-slot class child-name)))\n        (if slot\n            (with-slots (containment required-p) slot\n              (if containment\n                  (when (not (eql containment child-containment))\n                    (error \"Slot containment ~A is not the same as the child containment ~A.\"\n                           containment child-containment))\n                  (setf containment child-containment))\n              (when (member child-containment '(:single :+))\n                (setf required-p t)))\n            (when (member child-containment '(:single :+))\n              (warn \"Could not find a slot for the child element ~A with containment ~A.\"\n                    child-name child-containment))))))\n  (class-slots class))\n\n(defmethod direct-slot-definition-class ((class xml-class) &key parent attribute element body &allow-other-keys)\n  (if (or attribute element body parent)\n      'xml-direct-slot-definition\n      (call-next-method)))\n\n(defmethod effective-slot-definition-class ((class xml-class) &rest initargs)\n  (declare (ignore initargs))\n  'xml-effective-slot-definition)\n\n(defmethod compute-effective-slot-definition :around ((class xml-class) name direct-slots)\n  (let* ((xml-directs (remove-if-not #'(lambda (class) (typep class 'xml-direct-slot-definition))\n                                     direct-slots))\n         (xml-direct (first xml-directs)))\n\n    ;; Commented out this check because I could not determine what it does and it warned me.\n    #+(or)\n    (when (> (length xml-directs) 1)\n      (dolist (slot-def (class-slots (class-of (first xml-directs))))\n        (unless (apply #'equal (mapcar #'(lambda (slot) (slot-value slot (slot-definition-name slot-def))) xml-directs))\n          (warn \"Possibly conflicting slot options for overloaded slot ~A.\" (slot-definition-name slot-def)))))\n\n    (let ((normal-slot (call-next-method)))\n      (when (and xml-direct\n                 (typep normal-slot 'xml-effective-slot-definition))\n        (with-slots (attribute element body parent) xml-direct\n          (when (> (length (remove nil (list parent element attribute body))) 1)\n            (error \"Only one of ELEMENT, ATTRIBUTE, PARENT or BODY is possible for a slot definition.\"))\n          (unless (or body parent)\n            (unless (or element attribute)\n              (setf element (string-downcase name)))\n            (when element\n              (setf element (if (eq t element) (string-downcase name) element)))\n            (when attribute\n              (setf attribute (if (eq t attribute) (string-downcase name) attribute)))\n            (unless (or element attribute)\n              (error \"Could not find element or attribute for slot ~A.\" name))))\n\n        ;; copy direct-slot-definition slots to effective-slot-definition\n        (dolist (slot '(parser serializer body id-to-object object-to-id\n                        parent attribute element containment))\n          (setf (slot-value normal-slot slot)\n                (slot-value xml-direct slot))))\n\n      (dolist (slot '(parser serializer object-id-slot object-to-id id-to-object) normal-slot)\n        (let ((value (slot-value normal-slot slot)))\n          (when value\n            (setf (slot-value normal-slot slot)\n                  (eval value)))))\n        \n      normal-slot)))\n\n(defmethod xml-object-check-validity (object)\n  (let ((class (class-of object)))\n    (unless (typep class 'xml-class)\n      (error \"Object ~a is not of metaclass XML-CLASS.\" object))\n    (dolist (slot (class-slots class))\n      (when (typep slot 'xml-effective-slot-definition)\n        (when (and (xml-effective-slot-definition-required-p slot)\n                   (not (slot-boundp object (slot-definition-name slot))))\n          (error \"Required slot ~A is not bound in ~a.\"\n                 (slot-definition-name slot) object))\n        (let ((containment (xml-effective-slot-definition-containment slot)))\n          (when (and containment\n                     (eql containment :+)\n                     (null (slot-value object (slot-definition-name slot))))\n            (error \"Slot ~a with containment :+ has no value.\"\n                   (slot-definition-name slot))))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/xml-export.lisp",
    "content": "(in-package :bknr.impex)\n\n(defmethod slot-serialize-value ((slot xml-effective-slot-definition) value)\n  (with-slots (serializer object-to-id) slot\n    (when object-to-id\n      (setf value (funcall object-to-id value)))\n    (when serializer\n      (setf value (funcall serializer value)))))\n\n(defvar *objects-written*)\n\n(defmacro write-to-xml (object &key name (output '*standard-output*) (indentation 3) (canonical nil))\n  `(let ((*objects-written* (make-hash-table :test #'equal))\n         (cxml::*current-element* nil)\n         (sink (cxml:make-character-stream-sink ,output :indentation ,indentation :canonical ,canonical)))\n     (with-xml-output sink\n       (build-xml ,object :name ,name))))\n\n(defmacro write-to-xml-string (object &key name (indentation 3) (canonical nil))\n  `(let ((output-string (make-string-output-stream)))\n     (get-output-stream-string\n      (write-to-xml ,object :name ,name :output output-string :indentation ,indentation :canonical ,canonical))))\n\n(defmacro with-xml-export* ((&key output indentation canonical) &body body)\n  `(let ((*objects-written* (make-hash-table :test #'equal))\n         (cxml::*current-element* nil)\n         (cxml::*sink* (cxml:make-character-stream-sink ,output :indentation ,indentation :canonical ,canonical)))\n     (with-xml-output cxml::*sink*\n       ,@body)))\n\n(defmacro with-xml-export (nil &body body)\n  `(with-xml-export* (:output *standard-output* :indentation 3 :canonical nil)\n     ,@body))\n\n(defun write-object-reference (class object unique-id-slot-name name)\n  (let ((slotdef (find unique-id-slot-name (class-slots class) :key #'slot-definition-name)))\n    (unless (xml-effective-slot-definition-attribute slotdef)\n      (error \"Slot ~A is not defined as :attribute slot and cannot be used as unique-id slot for class ~A\" unique-id-slot-name (class-name class)))\n    (sax:start-element cxml::*sink* nil nil name\n                       (list (sax:make-attribute :qname (cxml::string-rod (xml-effective-slot-definition-attribute slotdef))\n                                                 :value (cxml::string-rod (slot-serialize-value slotdef (slot-value object unique-id-slot-name))))))\n    (sax:end-element cxml::*sink* nil nil name)))\n\n(defgeneric build-xml (object &key)\n  (:documentation \"Write OBJECT to XML stream\")\n\n\n  (:method ((object (eql nil)) &key))\n\n  (:method ((object list) &key (name (error \"Can not serialize list to XML without an element name~%\")))\n      (sax:start-element cxml::*sink* nil nil (cxml::string-rod name) nil)\n      (dolist (obj object)\n        (build-xml obj))\n      (sax:end-element cxml::*sink* nil nil (cxml::string-rod name)))\n  \n  (:method ((object string) &key (name (error \"Can not serialize string ~A to XML without an element name.\" object)))\n      (sax:start-element cxml::*sink* nil nil (cxml::string-rod name) nil)\n      (sax:characters cxml::*sink* (cxml::string-rod object))\n      (sax:end-element cxml::*sink* nil nil (cxml::string-rod name)))\n  \n  (:method ((object standard-object) &key name)\n    (if (typep (class-of object) 'xml-class)\n        (progn\n          (xml-object-check-validity object)\n          (let* ((class (class-of object))\n                 (qname (cxml::string-rod (or name (xml-class-element class)))))\n            ;; If this object has been serialized to the XML stream,\n            ;; write a reference to the object and return.\n            (with-slots (unique-id-slot) class\n              (when unique-id-slot\n                (if (gethash (slot-value object (first unique-id-slot)) *objects-written*)\n                    (progn\n                      (write-object-reference class object (first unique-id-slot) qname)\n                      (return-from build-xml))\n                    (setf (gethash (slot-value object (first unique-id-slot)) *objects-written*) t))))\n            \n            ;; Object has not been written to the XML file or no\n            ;; unique-id-slot is defined for this class.\n            (let* ((attr-slots (xml-class-attribute-slots class))\n                   (elt-slots (xml-class-element-slots class))\n                   (body-slot (xml-class-body-slot class))\n                   ;; attributes\n                   (attributes (loop for slot in attr-slots\n                                  for name = (slot-definition-name slot)\n                                  for attdef = (cxml::string-rod (xml-effective-slot-definition-attribute slot))\n                                  when (and (slot-boundp object name)\n                                            (slot-value object name))\n                                  collect (sax:make-attribute\n                                           :qname attdef\n                                           :value\n                                           (cxml::string-rod\n                                            (slot-serialize-value slot (slot-value object name)))))))\n              (sax:start-element cxml::*sink* nil nil qname attributes)\n              \n              ;; elements\n              (dolist (slot elt-slots)\n                (let ((name (slot-definition-name slot))\n                      (element-name (xml-effective-slot-definition-element slot)))\n                  (when (slot-boundp object name)\n                    (if (consp (slot-value object name))\n                        (dolist (child (slot-value object name))\n                          (if (typep (class-of child) 'xml-class)\n                              (build-xml child)\n                              (build-xml (slot-serialize-value slot child) :name element-name)))\n                  (let ((child (slot-value object name)))\n                    (if (typep (class-of child) 'xml-class)\n                        (build-xml child)\n                        (build-xml (slot-serialize-value slot child) :name element-name)))))))\n              \n              ;; body slot\n              (when body-slot\n                (let ((name (slot-definition-name body-slot)))\n                  (when (slot-boundp object name)\n                    (sax:characters\n                     cxml::*sink*\n                     (cxml::string-rod\n                      (funcall (xml-effective-slot-definition-serializer body-slot)\n                               (slot-value object name)))))))\n        \n              (sax:end-element cxml::*sink* nil nil qname))))\n        (cxml:with-element (string-downcase (class-name (class-of object)))\n          (dolist (slot (class-slots (class-of object)))\n            (cxml:with-element (string-downcase (symbol-name (slot-definition-name slot)))\n              (let ((value (slot-value object (slot-definition-name slot))))\n                (when value\n                  (cxml:text (handler-case\n                                 (cxml::utf8-string-to-rod (princ-to-string value))\n                               (error (e)\n                                 (declare (ignore e))\n                                 (cxml::utf8-string-to-rod \"[unprintable]\"))))))))))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/xml-import.lisp",
    "content": "(in-package :bknr.impex)\n\n(defclass xml-class-importer (sax:default-handler)\n  ((dtd        :initarg :dtd :initform nil :reader importer-dtd)\n   (class-hash :initarg :class-hash :accessor importer-class-hash)\n   (root-elt   :initform nil :accessor importer-root-elt)\n   (parent-elts :initform nil :accessor importer-parent-elts)))\n\n(defmethod slot-parse-value ((slot xml-effective-slot-definition) value)\n  (with-slots (parser id-to-object) slot\n    (when parser\n      (setf value (funcall parser value)))\n    (when id-to-object\n      (setf value (funcall id-to-object value)))\n    value))\n\n;;; description for an object instance to be created from the xml. The\n;;; data is gathered while parsing the XML, and at the end of an\n;;; element, the corresponding object is instanciated.\n\n(defclass xml-node ()\n  ((element :initarg :element :accessor node-element)\n   (children :initarg :children :initform (make-hash-table) :accessor node-children)\n   (elmdef    :initarg :elmdef :accessor instance-elmdef)\n   (attributes :initarg :attributes :accessor node-attributes)\n   (data :initarg :data :initform nil :accessor node-data)))\n\n(defmethod print-object ((node xml-node) stream)\n  (print-unreadable-object (node stream :type t)\n    (format stream \"~a\" (node-element node))))\n\n(defclass xml-class-instance (xml-node)\n  ((slots :initform (make-hash-table :test #'equal) :accessor instance-slots)\n   (class     :initarg :class :initform nil :accessor instance-class)))\n\n(defmethod print-object ((instance xml-class-instance) stream)\n  (print-unreadable-object (instance stream :type t)\n    (format stream \"~a\" (instance-class instance))))\n\n(defgeneric importer-add-attribute (handler node attr))\n(defgeneric importer-add-characters (handler node data))\n(defgeneric importer-add-element (handler node element value))\n(defgeneric importer-finalize (handler node))\n\n(defmethod importer-add-attribute ((handler xml-class-importer)\n                                 (class-instance xml-class-instance) attr)\n  (with-slots (class slots) class-instance\n    (let ((slot (xml-class-find-attribute-slot class (sax:attribute-qname attr))))\n      (when slot\n        (setf (gethash slot slots) (slot-parse-value slot (sax:attribute-value attr)))))))\n\n(defmethod importer-add-attribute ((handler xml-class-importer)\n                                 (node xml-node) attr)\n  nil)\n\n(defmethod importer-add-characters ((handler xml-class-importer)\n                                  (node xml-node) characters)\n  (unless (whitespace-p characters)\n    (setf characters (string-trim bknr.utils::+whitespace-chars+ characters))\n    (with-slots (data) node\n      (setf data (if data\n                     (concatenate 'string data characters)\n                     characters)))))\n\n(defmethod importer-add-characters ((handler xml-class-importer)\n                                  (instance xml-class-instance) characters)\n  (with-slots (class elmdef slots children) instance\n    (let ((slot (xml-class-body-slot class)))\n      (when slot\n        (setf (gethash slot slots)\n              (concatenate 'string\n                           (gethash slot slots)\n                           (slot-parse-value slot characters)))))))\n\n(defmethod importer-add-element ((handler xml-class-importer)\n                               (node xml-node) element value)\n  (with-slots (children) node\n    (push value (gethash (make-keyword-from-string element) children))))\n\n(defmethod importer-add-element ((handler xml-class-importer)\n                               (instance xml-class-instance) element value)\n  (with-slots (slots elmdef class children) instance\n    (let ((slot (xml-class-find-element-slot class element)))\n      (when slot\n          ;; parse the value if necessary\n          (setf value (slot-parse-value slot value))\n          (let ((containment (xml-effective-slot-definition-containment slot)))\n            (if (member containment '(:* :+))\n                ;; if it has a plural containment, push the\n                ;; created instance into the initargs hash\n                (push value (gethash slot slots))\n                ;; else set the initarg hash to the new instance\n                (setf (gethash slot slots) value)))))))\n\n(defmethod importer-finalize ((handler xml-class-importer)\n                              (node xml-node))\n  (with-slots (data children) node\n    (cond\n      ((and data\n            (= (hash-table-count children) 0))\n       data)\n      ((> (hash-table-count children) 0)\n       (children-to-initforms children))\n      (t nil))))\n\n(defun add-parent (handler parent child)\n  (let* ((class (class-of child))\n         (parent-slot (when (typep class 'xml-class)\n                        (xml-class-parent-slot class))))\n    (when parent-slot\n      (set-slot-value handler child (slot-definition-name parent-slot) parent))))\n\n(defun slots-to-initforms (slots)\n  (let (initforms)\n    (loop for slot being the hash-keys of slots using (hash-value value)\n       when (listp value)\n       do (push (reverse value) initforms)\n       else do (push value initforms)\n       do (push (first (slot-definition-initargs slot)) initforms))\n    initforms))\n\n(defun children-to-initforms (children)\n  (let (initforms)\n    (loop for child being the hash-keys of children using (hash-value value)\n       when (listp value)\n       do (push (reverse value) initforms)\n       else do (push value initforms)\n       do (push child initforms))\n    initforms))\n\n(defmethod importer-finalize ((handler xml-class-importer)\n                            (instance xml-class-instance))\n  (with-slots (class elmdef children slots) instance\n    (let* ((initforms (slots-to-initforms slots))\n           (object (apply #'create-instance handler (class-name class) initforms)))\n\n      (loop for objs being the hash-values of slots\n         when (listp objs)\n         do (dolist (child objs)\n              (add-parent handler object child))\n         else do (add-parent handler object objs))\n\n      object)))\n\n(defmethod sax:start-document ((handler xml-class-importer))\n  (setf (importer-root-elt handler) nil))\n\n(defmethod sax:start-element ((handler xml-class-importer) namespace-uri local-name qname attrs)\n  (declare (ignore namespace-uri local-name))\n  (let ((class (gethash qname (importer-class-hash handler)))\n        (element (cxml::string-rod qname))\n        instance)\n    (if class\n        (setf instance\n              (make-instance 'xml-class-instance\n                             :element element\n                             :elmdef (xml-class-element class)\n                             :class class))\n        (setf instance\n              (make-instance 'xml-node\n                             :element element\n                             :elmdef (cxml::find-element element (importer-dtd handler)))))\n\n    (dolist (attr attrs)\n      (importer-add-attribute handler instance attr))\n\n    (push instance (importer-parent-elts handler))))\n\n(defmethod sax:characters ((handler xml-class-importer) data)\n  (unless (importer-parent-elts handler)\n    (error \"Can not parse SAX:CHARACTERS without a parent element.\"))\n  (importer-add-characters handler (first (importer-parent-elts handler)) data))\n\n(defmethod create-instance ((handler xml-class-importer) class-name &rest initargs)\n  (apply #'make-instance class-name initargs))\n\n(defmethod set-slot-value ((handler xml-class-importer) object slot-name value)\n  (setf (slot-value object slot-name) value))\n\n(defmethod sax:end-element ((handler xml-class-importer) namespace-uri local-name qname)\n  (declare (ignore namespace-uri local-name))\n\n  (let* ((instance (pop (importer-parent-elts handler)))\n         (final (importer-finalize handler instance))\n         (parent (first (importer-parent-elts handler))))\n\n    (when parent\n      (importer-add-element handler parent qname final))\n\n    (setf (importer-root-elt handler) final)))\n\n(defun parse-xml-file (xml-file classes &key (recoder #'cxml::rod-string)\n                       (importer-class 'xml-class-importer))\n  (let ((class-hash (make-hash-table :test #'equal)))\n    (dolist (class classes)\n      (setf (gethash (xml-class-element class) class-hash) class))\n    (let ((importer (make-instance importer-class\n                                   :dtd (parse-dtd-file (xml-class-dtd-name (first classes)))\n                                   :class-hash class-hash)))\n      (cxml:parse-file xml-file (cxml:make-recoder importer recoder))\n      (importer-root-elt importer))))\n"
  },
  {
    "path": "third-party/bknr.datastore/src/xml-impex/xml-update.lisp",
    "content": "(in-package :bknr.impex)\n\n;;; sax parser for xml impex updater, reads updates to objects from an xml file\n\n(defclass xml-class-updater (xml-class-importer)\n  ())\n\n(defun class-find-slot (class slot-name)\n  (find-if #'(lambda (slot)\n\t       (equal (slot-definition-name slot) slot-name))\n\t   (mop:class-slots class)))\n\n(defmethod importer-finalize ((handler xml-class-updater)\n\t\t\t      (instance xml-class-instance))\n  (with-slots (class slots) instance\n    (if (and (xml-class-unique-id-slot class)\n\t     (xml-class-unique-id-reader class))\n\t(let* ((id-slot (class-find-slot class (xml-class-unique-id-slot class)))\n\t       (id-value (gethash id-slot slots))\n\t       (obj (when id-value (funcall (xml-class-unique-id-reader class) id-value))))\n\t  (if (and obj id-value)\n\t      (progn\n\t\t(loop for slot being the hash-keys of slots using (hash-value value)\n\t\t   when (not (equal (slot-definition-name slot) (xml-class-unique-id-slot class)))\n\t\t   do\n\t\t     (format t \"updating slot ~A with ~S~%\" (slot-definition-name slot)\n\t\t\t     value)\n\t\t     (setf (slot-value obj (slot-definition-name slot))\n\t\t\t    value))\n\t\tobj)\n\t      (progn\n\t\t(warn \"no id-value or object found, creating new~%\")\n\t\t(call-next-method))))\n\t\n\t(call-next-method))))\n\n(defun parse-xml-update-file (xml-file classes &key (recoder #'cxml::rod-string)\n\t\t\t      (importer-class 'xml-class-updater))\n  (parse-xml-file xml-file classes :recoder recoder :importer-class importer-class))\n"
  },
  {
    "path": "third-party/cl+j-0.4/Copyright",
    "content": "Copyright (c) 2009,2017, Jean-Claude Beaudoin\nAll rights reserved by the author.\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use, copy,\nmodify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/cl+j.asd",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009,2017, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n;;#-(or sbcl cmu clisp mkcl ecl ccl scl)\n;;(error \"Sorry, CL+J 0.4 does not support this Lisp.\")\n\n(defpackage #:cl+j-system\n  (:use #:cl #:asdf))\n(in-package #:cl+j-system)\n\n(defsystem cl+j\n  :description \"CL+J: A Common Lisp + Java Interface.\"\n  :version \"0.4\"\n  :author \"Jean-Claude Beaudoin <jean.claude.beaudoin@gmail.com>\"\n  :licence \"MIT style license, Copyright (c) Jean-Claude Beaudoin 2009,2017.\"\n  :components ((:file \"cl+j_pkg\")\n\t       (:file \"reference\" :depends-on (\"cl+j_pkg\"))\n\t       (:file \"vtable\" :depends-on (\"reference\"))\n\t       (:file \"jni\" :depends-on (\"vtable\"))\n\t       (:file \"cl+j\" :depends-on (\"jni\"))\n\t       (:file \"java_callback\" :depends-on (\"cl+j\"))\n\t       (:file \"java_adapter\" :depends-on (\"java_callback\"))\n\t       #+sbcl (:file \"sbcl_repl\")\n\t       )\n  :depends-on (cffi trivial-garbage bordeaux-threads)\n  )\n"
  },
  {
    "path": "third-party/cl+j-0.4/cl+j.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009,2017,  Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n\n(in-package :cl+j)\n\n;(declaim (optimize debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n(eval-when (:compile-toplevel :load-toplevel)\n  (defparameter *debug* t))\n\n(defmacro debug-progn (&body body)\n  (if *debug*\n      `(progn ,@body)\n      nil))\n\n\n(defmacro str+ (str1 &rest strs)\n  `(concatenate 'simple-string ,str1 ,@strs)\n  )\n\n(defmacro empty-str-p (str)\n  `(eql 0 (array-total-size ,str)))\n\n\n;;;\n;;;  Some basic predicates and constants.\n;;;\n\n(defconstant jtrue JNI_TRUE)\n(defconstant jfalse JNI_FALSE)\n\n(defun jtrue-p (val)\n  (= JNI_TRUE val))\n\n(defun jfalse-p (val)\n  (= JNI_FALSE val))\n\n(defun jobjectp (arg)\n  (when (jref-p arg) (setq arg (jref-it arg))) ;; unbox it first.\n  (cffi::pointerp arg))\n\n(defparameter jnull (cffi::null-pointer))\n\n(defun jnull (arg)\n  (when (jref-p arg) (setq arg (jref-it arg))) ;; unbox it first.\n  (and (cffi::pointerp arg) (cffi::null-pointer-p arg)))\n\n(defmacro with-pinned-values ((&body vars) &body body)\n  (let (pin-vars\n\tpin-bindings\n\t)\n    (dolist (var vars)\n      (let ((pin-var (gensym \"pinned-\")))\n\t(push pin-var pin-vars)\n\t(push (list pin-var var) pin-bindings)\n\t)\n      )\n    `(let ,pin-bindings\n       (declare (special ,@pin-vars))\n       ,@body\n       )\n    )\n  )\n\n\n\n\n;;;\n;;; Some wrappers on Local/Global/WeakGlobal References.\n;;;\n\n;;; A weak reference should always be converted to a global or local reference\n;;; before access due to the risk of asynchronous garbage collection in the JVM.\n;;;\n\n(defstruct weak-jref\n  it\n  )\n\n(defun delete-java-weak-ref (weak-java-ref)\n#|\n  #-ecl (debug-progn (format t \"~%Inside delete-java-weak-ref!~%\") (finish-output))\n|#\n  (handler-case\n      (unless (java-vm-destroyed)\n\t(DeleteWeakGlobalRef weak-java-ref))\n    (jni::jni-error (condition)\n      (declare (ignorable condition))\n      ;;(debug-progn (signal condition))\n      )\n    )\n  )\n\n(defun weak-jref (ref)\n  (let* ((java-weak-ref (NewWeakGlobalRef ref))\n\t (obj (make-weak-jref :it java-weak-ref)))\n    #-sbcl (tg:finalize obj #'(lambda () (delete-java-weak-ref java-weak-ref)))\n    obj))\n\n\n(defstruct jref\n  it\n  )\n\n#+ecl (defparameter +delete-java-global-ref-has-run+ nil)\n#+ecl (defvar *finalizer-process* nil)\n#+ecl (defvar *deleted-ref* nil)\n\n(defun delete-java-global-ref (global-java-ref)\n  ;;(declare (ignore global-java-ref))\n#|\n  #-ecl (debug-progn\n\t  #-sbcl (format t \"~%Inside delete-java-global-ref!~%\")\n\t  #+sbcl (format t \"~%Inside delete-java-global-ref! Thread = ~S.~%\"\n\t\t\t (sb-thread:thread-name sb-thread:*current-thread*))\n\t  (finish-output))\n|#\n\n  (handler-case\n      (unless (java-vm-destroyed)\n\t#+ecl (setq *deleted-ref* global-java-ref)\n\t(DeleteGlobalRef global-java-ref))\n    (jni::jni-error (condition)\n      (declare (ignorable condition))\n#|\n      (format t \"~%In delete-java-global-ref: condition = ~S~%\" condition)\n      (apply #'format t (simple-condition-format-control condition)\n\t     (simple-condition-format-arguments condition))\n      (fresh-line)\n      (finish-output)\n      ;;(break)\n|#\n      ;;(debug-progn (signal condition))\n      )\n    )\n  #+ecl (setq +delete-java-global-ref-has-run+ t)\n  #+ecl (setq *finalizer-process* mp:*current-process*)\n  )\n\n(defun jref (ref)\n  ;;(break \"in jref\")\n  (when (weak-jref-p ref) (setq ref (weak-jref-it ref))) ;; unbox it.\n  (let* ((global-java-ref (NewGlobalRef ref))\n\t ;;(cons-global-java-ref (cons global-java-ref nil))\n\t (obj (make-jref :it global-java-ref)))\n    #-sbcl (tg:finalize obj #'(lambda () (delete-java-global-ref global-java-ref)))\n    obj))\n\n(defun as-jref-value (lref)\n  (prog1\n      (jref lref)\n    (DeleteLocalRef lref)\n    )\n  )\n\n(defun new-local-ref (ref)\n  (cond ((weak-jref-p ref) (setq ref (weak-jref-it ref))) ;; unbox it.\n\t((jref-p ref) (setq ref (jref-it ref))) ;; unbox it.\n\t)\n  (NewLocalRef ref)\n  )\n\n(defun delete-local-ref (lref)\n  (DeleteLocalRef lref))\n\n\n(defmacro with-jvm-local-frame ((&optional (capacity 16) &key local-ref-value) &body body)\n  (if local-ref-value\n      `(let (result)\n\t (unwind-protect\n\t      (progn\n\t\t(PushLocalFrame ,capacity)\n\t\t(setq result (progn ,@body))\n\t\t)\n\t   (if (pointerp result)\n\t       (setq result (PopLocalFrame result))\n\t       (PopLocalFrame (null-pointer)))\n\t   )\n\t result\n\t )\n      `(unwind-protect\n\t    (progn\n\t      (PushLocalFrame ,capacity)\n\t      ,@body)\n\t (PopLocalFrame (null-pointer)))\n      )\n  )\n\n;;;\n;;;\n;;;\n\n(defun %jeq (jobj1 jobj2) (= JNI_TRUE (IsSameObject jobj1 jobj2)))\n\n(defun jeq (jobj1 jobj2)\n  (with-pinned-values (jobj1 jobj2)\n    (when (jref-p jobj1) (setq jobj1 (jref-it jobj1)))\n    (when (jref-p jobj2) (setq jobj2 (jref-it jobj2)))\n    (= JNI_TRUE (IsSameObject jobj1 jobj2))\n    )\n  )\n\n(defun jinstanceof (ref jclazz)\n  (with-pinned-values (ref jclazz)\n    (when (jref-p ref) (setq ref (jref-it ref)))\n    (when (jref-p jclazz) (setq jclazz (jref-it jclazz)))\n    (cond ((not (pointerp ref))\n\t   (error \"First argument to jinstanceof must be a foreign reference\")\n\t   )\n;;; \t  ((not (pointerp jclazz)) ;; not really needed on SBCL, CFFI does the validation.\n;;; \t   (error \"Second argument to jinstanceof must be a foreign reference\")\n;;; \t   )\n\t  ((null-pointer-p jclazz)\n\t   (error \"Second argument to jinstanceof cannot be a foreign null reference\")\n\t   )\n\t  ((null-pointer-p ref) nil)\n\t  (t\n\t   (eql JNI_TRUE (IsInstanceOf ref jclazz))))))\n\n\n;;;\n;;; Some basic Java primitive JNI type wrapper.\n;;;\n\n(defstruct (jprim (:constructor make-jprim-1))\n  type\n  value\n  )\n\n#+(or)\n(defun free-jprim (this) ;; Either we free manually with this one or we use a finalizer.\n  (foreign-free (jprim-value this)))\n\n(defun make-jprim (&key type value)\n  (let ((it (make-jprim-1 :type type :value value)))\n    #+(and) (tg:finalize it #'(lambda () (foreign-free (jprim-value it))))\n    it))\n\n(defun jprim-boolean (val)\n  (make-jprim :type 'jboolean :value (foreign-alloc 'jboolean :initial-element val))\n  )\n\n(defun jprim-char (val)\n  (make-jprim :type 'jchar :value (foreign-alloc 'jchar :initial-element val))\n  )\n\n(defun jprim-byte (val)\n  (make-jprim :type 'jbyte :value (foreign-alloc 'jbyte :initial-element val))\n  )\n\n(defun jprim-short (val)\n  (make-jprim :type 'jshort :value (foreign-alloc 'jshort :initial-element val))\n  )\n\n(defun jprim-int (val)\n  (make-jprim :type 'jint :value (foreign-alloc 'jint :initial-element val))\n  )\n\n(defun jprim-long (val)\n  (make-jprim :type 'jlong :value (foreign-alloc 'jlong :initial-element val))\n  )\n\n(defun jprim-float (val)\n  (make-jprim :type 'jfloat :value (foreign-alloc 'jfloat :initial-element val))\n  )\n\n(defun jprim-double (val)\n  (make-jprim :type 'jdouble :value (foreign-alloc 'jdouble :initial-element val))\n  )\n\n\n;;;\n;;; Some basic primitives to support Java method calls\n;;;\n\n(defmacro descriptor-step (sig index length)\n  `(if (< ,index ,length)\n      (incf ,index)\n      (error \"Malformed Java descriptor: ~S~%\" ,sig))\n  )\n\n(defmacro skip-object-spec (sig index length)\n  `(do ()\n       ((char= #\\; (schar ,sig ,index)))\n     (descriptor-step ,sig ,index ,length)\n     )\n  )\n\n(defmacro skip-array-spec (sig index length)\n  `(progn\n     ;; First we skip the \"[\"\n     (do ()\n\t ((char/= #\\[ (schar ,sig ,index)))\n       (descriptor-step ,sig ,index ,length)\n       )\n     ;; Second we swallow the component descriptor.\n     (case (schar ,sig ,index)\n       (#\\L (skip-object-spec ,sig ,index ,length))\n       ((#\\I #\\J #\\Z #\\C #\\B #\\S #\\F #\\D))\n       (t (error \"Malformed Java descriptor: ~S~%\" ,sig))\n       )\n     )\n  )\n\n(defmacro fill-args (sig jargs &rest args)\n  (let (body arg)\n    `(let ((i 0)\n\t   (len (length ,sig))\n\t   )\n       ,@(dotimes (rank (length args) (nreverse body))\n\t  (setq arg (nth rank args))\n\t  (push\n\t   `(let ((arg-val-buf\n\t\t   (if (jprim-p ,arg)\n\t\t       (mem-ref (jprim-value ,arg) (jprim-type ,arg))\n\t\t       ,arg)))\n\t      (case (schar ,sig (descriptor-step ,sig i len))\n\t\t(#\\I (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'i) arg-val-buf))\n\t\t(#\\L (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'l) arg-val-buf)\n\t\t     (skip-object-spec ,sig i len))\n\t\t(#\\[ (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'l) arg-val-buf)\n\t\t     (skip-array-spec ,sig i len))\n\t\t(#\\J (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'j) arg-val-buf))\n\t\t(#\\Z (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'z) arg-val-buf))\n\t\t(#\\C (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'c) arg-val-buf))\n\t\t(#\\B (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'b) arg-val-buf))\n\t\t(#\\S (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 's) arg-val-buf))\n\t\t(#\\F (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'f) arg-val-buf))\n\t\t(#\\D (setf (foreign-slot-value\n\t\t\t    (mem-aptr! ,jargs (union-jvalue) ,rank) (union-jvalue) 'd) arg-val-buf))\n\t\t)\n\t      )\n\t   body)\n\t  )\n       )\n    )\n  )\n\n(defun dyn-fill-args (sig jargs args)\n  (let ((i 0)\n\t(len (length sig)))\n    (do ((arg (car args) (car args)) ;;times (rank (length args))\n\t (rank 0 (1+ rank)))\n        ((endp args))\n      ;(break)\n      (when (null arg) (setq arg jnull))\n      (when (jref-p arg) (setq arg (jref-it arg))) ;; unbox it first.\n\n      (when (jprim-p arg)\n\t(setq arg (mem-ref (jprim-value arg) (jprim-type arg))))\n      (case (schar sig (descriptor-step sig i len))\n\t(#\\I (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'i) arg))\n\t(#\\L (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'l) arg)\n\t     (skip-object-spec sig i len))\n\t(#\\[ (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'l) arg)\n\t     (skip-array-spec sig i len))\n\t(#\\J (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'j) arg))\n\t(#\\Z (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'z) arg))\n\t(#\\C (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'c) arg))\n\t(#\\B (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'b) arg))\n\t(#\\S (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 's) arg))\n\t(#\\F (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'f) arg))\n\t(#\\D (setf (foreign-slot-value\n\t\t    (mem-aptr! jargs (union-jvalue) rank) (union-jvalue) 'd) arg))\n\t(t (error \"Unknown java descriptor value ~S.~%\" (schar sig i)))\n\t)\n      (pop args)\n      )\n    )\n  )\n\n\n(defun jmethod-void-1 (this method-name arg sig)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name sig))\n\t )\n    (with-foreign-object (args (union-jvalue) 2)\n      (fill-args sig args arg)\n      (CallVoidMethodA this method-id args)\n      (PopLocalFrame (null-pointer))\n      )\n    )\n  )\n\n(defun jmethod-boolean-0 (this method-name)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name \"()Z\"))\n\t (result (CallBooleanMethodA this method-id (null-pointer)))\n\t )\n    (PopLocalFrame (null-pointer))\n    result\n    )\n  )\n\n(defun jmethod-boolean-1 (this method-name arg sig)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name sig))\n\t result\n\t )\n    (with-foreign-object (args (union-jvalue) 2)\n      (fill-args sig args arg)\n      (setq result (CallBooleanMethodA this method-id args))\n      (PopLocalFrame (null-pointer))\n      result\n      )\n    )\n  )\n\n(defun jmethod-static-boolean-1 (class method-name arg sig)\n  (PushLocalFrame 4)\n  (let* ((method-id (GetStaticMethodID class method-name sig))\n\t result\n\t )\n    (with-foreign-object (args (union-jvalue) 2)\n      (fill-args sig args arg)\n      (setq result (CallStaticBooleanMethodA class method-id args))\n      (PopLocalFrame (null-pointer))\n      result\n\n\n      )\n    )\n  )\n\n(defun jmethod-int-0 (this method-name)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name \"()I\"))\n\t (result (CallIntMethodA this method-id (null-pointer)))\n\t )\n    (PopLocalFrame (null-pointer))\n    result\n    )\n  )\n\n(defun jmethod-object-0 (this method-name sig)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name sig))\n\t )\n    (PopLocalFrame (CallObjectMethodA this method-id (null-pointer)))\n    )\n  )\n\n(defun jmethod-object-1 (this method-name arg sig)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name sig))\n\t )\n    (with-foreign-object (args (union-jvalue) 2)\n      (fill-args sig args arg)\n      (PopLocalFrame (CallObjectMethodA this method-id args))\n      )\n    )\n  )\n\n(defun jmethod-object-2 (this method-name arg1 arg2 sig)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name sig))\n\t )\n    (with-foreign-object (args (union-jvalue) 3)\n      (fill-args sig args arg1 arg2)\n      (PopLocalFrame (CallObjectMethodA this method-id args))\n      )\n    )\n  )\n\n(defun jfield-static-int (class-designation name)\n  (PushLocalFrame 4)\n  (let* ((class (FindClass class-designation))\n\t (field-id (GetStaticFieldID class name \"I\"))\n\t result\n\t )\n    (setq result (GetStaticObjectField class field-id))\n    (PopLocalFrame (null-pointer))\n    result\n    )\n  )\n\n(defun jfield-static-object (class-designation name sig)\n  (PushLocalFrame 4)\n  (let* ((class (FindClass class-designation))\n\t (field-id (GetStaticFieldID class name sig))\n\t )\n    (PopLocalFrame (GetStaticObjectField class field-id))\n    )\n  )\n\n(defun jfield-int (object name)\n  (PushLocalFrame 4)\n  (let* ((class (GetObjectClass object))\n\t (field-id (GetFieldID class name \"I\"))\n\t )\n    (prog1\n\t(GetIntField object field-id)\n      (PopLocalFrame (null-pointer))\n      )\n    )\n  )\n\n(defun jfield-object (object name sig)\n  (PushLocalFrame 4)\n  (let* ((class (GetObjectClass object))\n\t (field-id (GetFieldID class name sig))\n\t )\n    (PopLocalFrame (GetObjectField object field-id))\n    )\n  )\n\n\n;;;\n;;;  Java Reflection caching support\n;;;\n\n(defvar java-String nil)\n(defvar java-Class nil)\n(defvar java-Modifier nil)\n\n\n(defun init-java-basic-classes ()\n  (setq java-String\n\t(find-java-class \"java/lang/String\"))\n  (setq java-Class\n\t(find-java-class \"java/lang/Class\"))\n  (setq java-Modifier\n\t(find-java-class \"java/lang/reflect/Modifier\"))\n  )\n\n(defvar java-NoClassDefFoundError nil)\n(defvar java-ClassFormatError nil)\n(defvar java-ClassCircularityError nil)\n(defvar java-ExceptionInInitializerError nil)\n(defvar java-OutOfMemoryError nil)\n\n\n(defun init-java-exceptions ()\n  (setq java-NoClassDefFoundError\n\t(find-java-class \"java/lang/NoClassDefFoundError\"))\n  (setq java-ClassFormatError\n\t(find-java-class \"java/lang/ClassFormatError\"))\n  (setq java-ClassCircularityError\n\t(find-java-class \"java/lang/ClassCircularityError\"))\n  (setq java-ExceptionInInitializerError\n\t(find-java-class \"java/lang/ExceptionInInitializerError\"))\n  (setq java-OutOfMemoryError\n\t(find-java-class \"java/lang/OutOfMemoryError\"))\n  )\n\n(defmacro handle-java-exception (form &rest handlers)\n  `(handler-case ,form\n     (thrown-from-java (condi)\n       ;(ExceptionClear)\n       (let* ((j-exception (thrown-from-java-this condi))\n\t      (j-exception-class (GetObjectClass j-exception))) ;; lref\n\t (cond\n\t   ,@(let (code)\n\t\t  (dolist (handler handlers (nreverse code))\n\t\t    (destructuring-bind (target (&optional arg) &rest body) handler\n\t\t      (let* ((t-body (if arg\n\t\t\t\t\t `((let ((,arg j-exception)) ,@body))\n\t\t\t\t\t body))\n\t\t\t     (th (cond ((listp target)\n\t\t\t\t\t(let (selector)\n\t\t\t\t\t  (dolist (tar target)\n\t\t\t\t\t    (push\n\t\t\t\t\t     (if (stringp tar)\n\t\t\t\t\t\t `(%jeq j-exception-class\n\t\t\t\t\t\t       (java-class-real\n\t\t\t\t\t\t\t(find-java-class ,tar)))\n\t\t\t\t\t\t `(%jeq j-exception-class\n\t\t\t\t\t\t       (java-class-real ,tar))\n\t\t\t\t\t\t )\n\t\t\t\t\t     selector)\n\t\t\t\t\t    )\n\t\t\t\t\t  `((or ,@(nreverse selector))\n\t\t\t\t\t    ,@t-body)))\n\t\t\t\t       ((stringp target)\n\t\t\t\t\t`((%jeq j-exception-class\n\t\t\t\t\t       (java-class-real\n\t\t\t\t\t\t(find-java-class ,target)))\n\t\t\t\t\t  ,@t-body))\n\t\t\t\t       (t\n\t\t\t\t\t`((%jeq j-exception-class\n\t\t\t\t\t       (java-class-real ,target))\n\t\t\t\t\t  ,@t-body))\n\t\t\t\t       )))\n\t\t\t(push th code)))))\n\t   (t ;; if not handled we signal again.\n\t    ;(break \"This is the resignaling case.\")\n\t    (error condi)))))))\n\n\n;;;\n;;; Test case for handle-java-exception\n;;; This use of handle-java-exception should produce what just follows it.\n;;;\n\n;; (handle-java-exception\n;;  (FindClass \"java/lang/Froboz\")\n;;  (java-NoClassDefFoundError\n;;   (exception)\n;;   (go 'next-try)\n;;   )\n;;  ((\"java.lang.ClassFormatError\"\n;;    \"java.lang.ClassCircularityError\"\n;;    \"java.lang.ExceptionInInitializerError\")\n;;   (exception)\n;;   (error \"JVM confused! It said: ~S.~%\" exception)\n;;   )\n;;  (\"java.lang.OutOfMemoryError\"\n;;   ()\n;;   (format t \"Game Over! JVM said we are out of memory!\")\n;;   (quit)\n;;   )\n;;  )\n;;\n;; (handler-case (FindClass \"java/lang/Froboz\")\n;;   (java-exception (condi)\n;;     ;(ExceptionClear)\n;;     (let* ((j-exception (java-exception-thrown condi))\n;; \t   )\n;;       (cond ((%jeq j-exception (java-class-real java-NoClassDefFoundError))\n;; \t     (let ((exception j-exception))\n;; \t       (go 'next-try)\n;; \t       )\n;; \t     )\n;; \t    ((or (%jeq j-exception\n;;                    (java-class-real\n;; \t\t       (find-java-class \"java.lang.ClassFormatError\"))\n;; \t\t      )\n;; \t\t (%jeq j-exception\n;;                    (java-class-real\n;; \t\t       (find-java-class \"java.lang.ClassCircularityError\"))\n;; \t\t      )\n;; \t\t (%jeq j-exception\n;;                    (java-class-real\n;; \t\t       (find-java-class \"java.lang.ExceptionInInitializerError\"))\n;; \t\t      )\n;; \t\t )\n;; \t     (let ((exception j-exception))\n;; \t       (error \"JVM confused! It said: ~S.~%\" exception)\n;; \t       )\n;; \t     )\n;; \t    ((%jeq j-exception\n;;                (java-class-real\n;; \t\t   (find-java-class \"java.lang.OutOfMemoryError\"))\n;; \t\t  )\n;; \t     (format t \"Game Over! JVM said we are out of memory!\")\n;; \t     (quit)\n;; \t     )\n;; \t    )\n;;       )\n;;     )\n;;   )\n\n\n(defmacro jsynchronized (object &body body)\n  `(let ((sync-obj ,object)\n\t (lock-acquired nil))\n     (unwind-protect\n\t  (progn (MonitorEnter sync-obj)\n\t\t (setq lock-acquired t)\n\t\t (let (sync-obj lock-acquired) ;; mask variables above\n\t\t   ,@body))\n       (when lock-acquired\n\t (MonitorExit sync-obj))))\n  )\n\n;;;\n;;; Java contexts\n;;;\n;;; A Java context tries to adapt the idea of Java packages\n;;; in a Common Lisp environment. Thus it is patterned a bit\n;;; along the line of Common Lisp packages in its outer\n;;; interface but implements the Java semantics on Java name lookup.\n;;;\n\n(defvar *all-java-contexts* nil)\n\n(defvar +java-contexts-lock+ (bt:make-lock \"All Java contexts\"))\n\n(defstruct (java-context (:constructor create-java-context))\n  name\n  single-type-imports  ;; a list of \"Single import\" Java types declarations.\n  on-demand-imports ;; a list of \"On demand\" import declarations\n  java-type-names ;; a cache of resolved class names (a hashtable).\n  mutation-lock\n  )\n\n(defun make-java-context (name)\n  (bt:with-lock-held (+java-contexts-lock+)\n    (let ((context (find name *all-java-contexts*\n\t\t\t :key #'java-context-name\n\t\t\t :test #'string=)))\n      (unless context\n\t(setq context (create-java-context :name name\n\t\t\t\t\t   :on-demand-imports '(\"java.lang.*\")\n\t\t\t\t\t   :java-type-names\n\t\t\t\t\t   (make-hash-table :test #'equal)\n\t\t\t\t\t   :mutation-lock\n\t\t\t\t\t   (bt:make-lock (str+ \"Java context: \" name))))\n\t(push context *all-java-contexts*)\n\t)\n      context\n      )\n    )\n  )\n\n(defvar *java-context* (make-java-context \"java-user\"))\n\n(defun split-full-type-name (full-name)\n  (let ((split-point (position #\\. full-name :from-end t))\n\t)\n    (if (null split-point)\n\t(values nil full-name)\n\t(values (subseq full-name 0 split-point)\n\t\t(subseq full-name (1+ split-point))))\n    )\n  )\n\n(defmacro lookup-simple-type-name (context name)\n  `(gethash ,name (java-context-java-type-names ,context))\n  )\n\n(defun register-simple-type-name (context full-name)\n  (multiple-value-bind (package-part simple-name)\n      (split-full-type-name full-name)\n    (declare (ignore package-part))\n    (let ((known (lookup-simple-type-name context simple-name)))\n      (cond (known\n\t     (unless (string= full-name known)\n\t       (error \"Java type import failure!~%   Attempt to import: ~S~%    clashes with: ~S~%\"\n\t\t      full-name known)))\n\t    (t (setf (lookup-simple-type-name context simple-name) full-name))))))\n\n\n(defun find-java-context (context-name)\n  (declare (type string context-name))\n  (find context-name *all-java-contexts* :key #'java-context-name :test #'string=))\n\n(defmacro in-java-context (context-name)\n  (let ((context (find-java-context context-name)))\n    (unless context\n      (setq context (make-java-context context-name))\n      )\n    (setq *java-context* context)\n    )\n  `(let ((context (find-java-context ,context-name)))\n     (unless context\n       (setq context (make-java-context ,context-name)))\n     (setq *java-context* context)\n     )\n  )\n\n(defmacro with-java-context (context-name &body body)\n  `(let ((*java-context* (find-java-context ,context-name)))\n     (declare (special *java-context*))\n     ,@body\n     )\n  )\n\n\n\n(defun delete-java-context (context)\n  (declare (ignore context))\n  (break \"Not implemented yet: delete-java-context\")\n  )\n\n(defun suffix-p (str suffix)\n  (let* ((len-str (length str))\n\t (len-suf (length suffix))\n\t )\n    (dotimes (i len-suf t)\n      (unless (char= (char str (- len-str i 1)) (char suffix (- len-suf i 1)))\n\t(return-from suffix-p nil)\n\t)\n      )\n    )\n  )\n\n(defun is-on-demand-type-import-p (import-spec)\n  (suffix-p import-spec \".*\")\n  )\n\n\n(defmacro java-import (import-spec)\n  (declare (string import-spec))\n  (let ((*java-context* *java-context*))\n    (unless *java-context*\n      (error \"No java context defined!~%\"))\n    (bt:with-lock-held ((java-context-mutation-lock *java-context*))\n      (cond ((is-on-demand-type-import-p import-spec)\n\t     (pushnew import-spec\n\t\t      (java-context-on-demand-imports *java-context*)\n\t\t      :test #'string=)\n\t     )\n\t    (t ;; this must be  \"Single Type Import Declaration\".\n\t     (pushnew import-spec\n\t\t      (java-context-single-type-imports *java-context*)\n\t\t      :test #'string=)\n\t     ;; here we should also fill the hashtable of resolved names.\n\t     (register-simple-type-name *java-context* import-spec)\n\t     )\n\t    )\n      )\n    )\n  `(let ((*java-context* *java-context*))\n     (unless *java-context*\n       (error \"No java context defined!~%\"))\n     (bt:with-lock-held ((java-context-mutation-lock *java-context*))\n       (cond ((is-on-demand-type-import-p ,import-spec)\n\t      (pushnew ,import-spec\n\t\t       (java-context-on-demand-imports *java-context*)\n\t\t       :test #'string=)\n\t      )\n\t     (t ;; this must be  \"Single Type Import Declaration\".\n\t      (pushnew ,import-spec\n\t\t       (java-context-single-type-imports *java-context*)\n\t\t       :test #'string=)\n\t      ;; here we should also fill the hashtable of resolved names.\n\t      (register-simple-type-name *java-context* ,import-spec)\n\t      )\n\t     )\n       )\n     )\n  )\n\n;;;\n;;;  Java Reflection\n;;;\n\n;(defun java-array-length (array) (jfield-int array \"length\"))\n\n(defun convert-java-array (jarray)\n  (PushLocalFrame 2)\n  (let* ((length (GetArrayLength jarray))\n\t (vec (make-array (list length) :fill-pointer 0))\n\t )\n    (dotimes (i length)\n      (let ((lref (GetObjectArrayElement jarray i)))\n\t(vector-push (NewGlobalRef lref) vec)\n\t(DeleteLocalRef lref)\n\t)\n      )\n    (PopLocalFrame (null-pointer))\n    vec\n    )\n  )\n\n(defun free-converted-java-array (an-array)\n  (dotimes (i (length an-array))\n    (when (aref an-array i)\n      (DeleteGlobalRef (aref an-array i))\n      (setf (aref an-array i) nil)\n      )\n    )\n  )\n\n\n;;;\n;;;  Java Reflection caching\n;;;\n\n(defvar java-boolean-prim-type)\n(defvar java-char-prim-type)\n(defvar java-byte-prim-type)\n(defvar java-short-prim-type)\n(defvar java-int-prim-type)\n(defvar java-long-prim-type)\n(defvar java-float-prim-type)\n(defvar java-double-prim-type)\n\n\n(defun init-java-primitive-types ()\n  (flet ((get-primitive-class (class-name)\n\t   (let ((lref (jfield-static-object class-name \"TYPE\" \"Ljava/lang/Class;\")))\n\t     (prog1 (NewGlobalRef lref) (DeleteLocalRef lref))))\n\t )\n    (setq java-boolean-prim-type (get-primitive-class \"java/lang/Boolean\"))\n    (setq java-char-prim-type (get-primitive-class \"java/lang/Character\"))\n    (setq java-byte-prim-type (get-primitive-class \"java/lang/Byte\"))\n    (setq java-short-prim-type (get-primitive-class \"java/lang/Short\"))\n    (setq java-int-prim-type (get-primitive-class \"java/lang/Integer\"))\n    (setq java-long-prim-type (get-primitive-class \"java/lang/Long\"))\n    (setq java-float-prim-type (get-primitive-class \"java/lang/Float\"))\n    (setq java-double-prim-type (get-primitive-class \"java/lang/Double\"))\n    )\n  )\n\n(defvar +java-reflection-lock+ (bt:make-lock \"Java Reflection caching\"))\n;;;\n;;; The global variable +java-types+ is the root of\n;;; our Java Reflection caching.\n;;;\n;;; This is a true global and should not be dynamically bound.\n;;;\n;;; Its value is a hash-table that maps a class descriptor (aka: internal name)\n;;; to its proper class proxy. It is a one-to-one mapping.\n;;;\n(defvar +java-types+ (make-hash-table :test #'equal))\n\n\n(defstruct (java-field (:print-object print-java-field))\n  name\n  static\n  real\n  real-type\n  id\n  sig\n  primitive-type\n)\n\n(defun print-java-field (jfield stream)\n  (format stream \"#S(JAVA-FIELD ~S ~S\" (java-field-name jfield) (java-field-sig jfield))\n  (when (java-field-static jfield)\n    (format stream \" static\"))\n  (format stream \")\")\n  )\n\n(defstruct (java-method (:print-object print-java-method))\n  name\n  static\n  real\n  id\n  sig\n  return-ptype\n  param-types\n)\n\n(defun print-java-method (jmethod stream)\n  (format stream \"#S(JAVA-METHOD ~S ~S\"\n\t  (java-method-name jmethod)\n\t  (java-method-sig jmethod))\n  (when (java-method-static jmethod)\n    (format stream \" static\"))\n  (format stream \")\")\n  )\n\n;; (defstruct java-constructor\n;;   name\n;;   real\n;;   id\n;;   sig\n;;   param-types\n;;   )\n\n(defstruct (java-class (:print-object print-java-class))\n  name\n  real\n  fields\n  methods\n  static-fields\n  static-methods\n  constructors\n  methods-fetched\n  constructors-fetched\n  )\n\n(defun print-java-class (jclass stream)\n  (format stream \"#S(JAVA-CLASS ~S)\" (java-class-name jclass))\n  )\n\n(defun java-string-to-lisp (jstr)\n  (if (null-pointer-p jstr)\n      nil\n      (let ((len (GetStringLength jstr))\n\t    (utf-len (GetStringUTFLength jstr)))\n\t(debug-progn\n\t (unless (= len utf-len)\n\t   (format t \"~%Got a Java string with len = ~D and utf-len = ~D~%\" len utf-len)\n\t   (finish-output)\n\t   (break)\n\t   )\n\t )\n\t(with-foreign-object (buf :char (1+ utf-len))\n\t  (GetStringUTFRegion jstr 0 len buf)\n\t  (cffi:foreign-string-to-lisp buf :count utf-len) ;; this is post CFFI 0.10.X\n\t  )\n\t)\n      ;; There is an alternative approach to this Java String convertion business.\n      ;; We could have used Get/ReleaseStringCritical and get access to Unicode-16 chars\n      ;; or even used Get/ReleaseStringUTFChars to do somewhat the same job in UTF.\n      )\n  )\n\n(defun get-real-class-name (real)\n  (PushLocalFrame 2)\n  (let* ((jname (jmethod-object-0 real \"getName\" \"()Ljava/lang/String;\"))\n\t )\n    (prog1\n\t(java-string-to-lisp jname)\n      (PopLocalFrame (null-pointer))) ;; Pops jname.\n    )\n  )\n\n\n(defun Member-isStatic-p (member)\n  (let* ((modif (jmethod-int-0 member \"getModifiers\"))\n\t )\n    (= JNI_TRUE (jmethod-static-boolean-1\n\t\t (java-class-real java-Modifier) \"isStatic\" modif \"(I)Z\"))\n    )\n  )\n\n(defun member-getName (member)\n  (PushLocalFrame 2)\n  (let* ((jname (jmethod-object-0 member \"getName\" \"()Ljava/lang/String;\"))\n;;;\t (len (GetStringUTFLength jname))\n;;;\t (buf (foreign-alloc :char :count (1+ len)))\n\t )\n;;;    (GetStringUTFRegion jname 0 len buf)\n;;;    (PopLocalFrame (null-pointer))\n;;;    (prog1 (foreign-string-to-lisp buf) (foreign-free buf))\n    (prog1\n\t(java-string-to-lisp jname)\n      (PopLocalFrame (null-pointer)))\n    )\n  )\n\n(defun method-getParameterTypes (real)\n  (PushLocalFrame 2)\n  (prog1\n      (convert-java-array\n       (jmethod-object-0 real \"getParameterTypes\" \"()[Ljava/lang/Class;\"))\n    (PopLocalFrame (null-pointer)))\n  )\n\n(defun method-getReturnType (real)\n  (jmethod-object-0 real \"getReturnType\" \"()Ljava/lang/Class;\")\n  )\n\n(defun build-sig-for-method (method &key constructor)\n  (PushLocalFrame 2)\n  (let* ((real-method (java-method-real method))\n\t (jarg-types (method-getParameterTypes real-method))\n\t (jreturn-type (unless constructor\n\t\t\t (method-getReturnType real-method)))\n\t (jreturn-type-name (if constructor\n\t\t\t\t\"void\"\n\t\t\t\t(get-real-class-name jreturn-type)))\n\t (args-sig \"\")\n\t return-sig\n\t )\n    (cond ((string= \"void\" jreturn-type-name)\n\t   (setf return-sig \"V\" (java-method-return-ptype method) :void)\n\t   )\n\t  ((string= \"int\" jreturn-type-name)\n\t   (setf return-sig \"I\" (java-method-return-ptype method) 'jint)\n\t   )\n\t  ((string= \"boolean\" jreturn-type-name)\n\t   (setf return-sig \"Z\" (java-method-return-ptype method) 'jboolean)\n\t   )\n\t  ((string= \"byte\" jreturn-type-name)\n\t   (setf return-sig \"B\" (java-method-return-ptype method) 'jbyte)\n\t   )\n\t  ((string= \"char\" jreturn-type-name)\n\t   (setf return-sig \"C\" (java-method-return-ptype method) 'jchar)\n\t   )\n\t  ((string= \"short\" jreturn-type-name)\n\t   (setf return-sig \"S\" (java-method-return-ptype method) 'jshort)\n\t   )\n\t  ((string= \"long\" jreturn-type-name)\n\t   (setf return-sig \"J\" (java-method-return-ptype method) 'jlong)\n\t   )\n\t  ((string= \"float\" jreturn-type-name)\n\t   (setf return-sig \"F\" (java-method-return-ptype method) 'jfloat)\n\t   )\n\t  ((string= \"double\" jreturn-type-name)\n\t   (setf return-sig \"D\" (java-method-return-ptype method) 'jdouble)\n\t   )\n\t  (t ;; Here we got either an object reference or an array.\n\t   (replace-dots jreturn-type-name)\n\t   (unless (char= #\\[ (schar jreturn-type-name 0)) ; this identifies an array!\n\t     ;; here we turn this object name into a cannonical class descriptor.\n\t     (setq jreturn-type-name (str+ \"L\" jreturn-type-name \";\"))\n\t     )\n\t   (setf return-sig jreturn-type-name\n\t\t (java-method-return-ptype method) 'jobject)\n\t   )\n\t  )\n    (dotimes (i (length jarg-types))\n      (let ((jarg-type-name (get-real-class-name (aref jarg-types i)))\n\t    )\n\t(cond ((string= \"int\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"I\"))\n\t       )\n\t      ((string= \"boolean\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"Z\"))\n\t       )\n\t      ((string= \"byte\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"B\"))\n\t       )\n\t      ((string= \"char\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"C\"))\n\t       )\n\t      ((string= \"short\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"S\"))\n\t       )\n\t      ((string= \"long\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"J\"))\n\t       )\n\t      ((string= \"float\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"F\"))\n\t       )\n\t      ((string= \"double\" jarg-type-name)\n\t       (setq args-sig (str+ args-sig \"D\"))\n\t       )\n\t      (t ;; Here we got either an object reference or an array.\n\t       (replace-dots jarg-type-name)\n\t       (unless (char= #\\[ (schar jarg-type-name 0)) ; this identifies an array!\n\t\t ;; here we turn this object name into a cannonical class descriptor.\n\t\t (setq jarg-type-name (str+ \"L\" jarg-type-name \";\"))\n\t\t )\n\t       (setq args-sig (str+ args-sig jarg-type-name))\n\t       )\n\t      ))\n      )\n    (setf (java-method-sig method) (str+ \"(\" args-sig \")\" return-sig))\n    (setf (java-method-param-types method) jarg-types)\n    (PopLocalFrame (null-pointer))\n    )\n  )\n\n(defun fetch-methods-of-class (class)\n  (bt:with-lock-held (+java-reflection-lock+)\n    (unless (java-class-methods-fetched class)\n      (PushLocalFrame 2)\n      (let* ((jmethods (jmethod-object-0 (java-class-real class)\n\t\t\t\t\t \"getMethods\"\n\t\t\t\t\t \"()[Ljava/lang/reflect/Method;\"))\n\t     (methods (convert-java-array jmethods))\n\t     (max (length methods))\n\t     instance-m\n\t     static-m\n\t     )\n\t(dotimes (i max)\n\t  (let ((jmethod (aref methods i))\n\t\tmethod\n\t\t)\n\t    (setq method (make-java-method :real jmethod\n\t\t\t\t\t   :name (member-getName jmethod)))\n\t    (setf (java-method-static method) (member-isStatic-p jmethod))\n\t    (build-sig-for-method method)\n\t    (setf (java-method-id method) (FromReflectedMethod jmethod))\n\t    (if (java-method-static method)\n\t\t(push method static-m)\n\t\t(push method instance-m)\n\t\t)\n\t    )\n\t  )\n\t(setf (java-class-methods class) instance-m)\n\t(setf (java-class-static-methods class) static-m)\n\t(setf (java-class-methods-fetched class) t)\n\t)\n      (PopLocalFrame (null-pointer))\n      )\n    )\n  )\n\n(defun fetch-constructors-of-class (class)\n  (bt:with-lock-held (+java-reflection-lock+)\n    (unless (java-class-constructors-fetched class)\n      (PushLocalFrame 2)\n      (let* ((jconstructors (jmethod-object-0 (java-class-real class)\n\t\t\t\t\t      \"getConstructors\"\n\t\t\t\t\t      \"()[Ljava/lang/reflect/Constructor;\"))\n\t     (constructors (convert-java-array jconstructors))\n\t     set\n\t     )\n\t(dotimes (i (length constructors))\n\t  (let ((jconstructor (aref constructors i))\n\t\tconstructor\n\t\t)\n\t    (setq constructor (make-java-method :real jconstructor\n\t\t\t\t\t\t:name (member-getName jconstructor)))\n\t    (setf (java-method-static constructor) nil)\n\t    (build-sig-for-method constructor :constructor t)\n\t    (setf (java-method-id constructor) (FromReflectedMethod jconstructor))\n\t    (push constructor set)\n\t    )\n\t  )\n\t(setf (java-class-constructors class) set)\n\t(setf (java-class-constructors-fetched class) t)\n\t)\n      (PopLocalFrame (null-pointer))\n      )\n    )\n  )\n\n(defun fetch-java-class-from-jvm (internal-name)\n  (let* ((real-class (FindClass internal-name))\n\t (real-class-internal-name (replace-dots (get-real-class-name real-class)))\n\t (prox-class (make-java-class :name real-class-internal-name\n\t\t\t\t      :real (NewGlobalRef real-class)))\n\t )\n    ;;(push prox-class +java-types+)\n    (unless (string= internal-name real-class-internal-name)\n      (break \"Tried to find class ~S but found instead: ~S.~%\"\n\t     internal-name real-class-internal-name))\n    (setf (gethash real-class-internal-name +java-types+) prox-class)\n    (DeleteLocalRef real-class)\n    prox-class\n    )\n  )\n\n(defun find-java-class-proxy-from-real (real)\n  (let* ((class-internal-name (replace-dots (get-real-class-name real)))\n\t (this ;;(find real +java-types+ :key #'java-class-real :test #'%jeq)\n\t  (gethash class-internal-name +java-types+)\n\t   )\n\t )\n    (unless this\n      (bt:with-lock-held (+java-reflection-lock+)\n\t(unless (setq this ;;(find real +java-types+ :key #'java-class-real :test #'%jeq)\n\t\t      (gethash class-internal-name +java-types+)\n\t\t      )\n\t  (let* ((prox-class (make-java-class :name class-internal-name\n\t\t\t\t\t      ;;(get-real-class-name real)\n\t\t\t\t\t      :real (NewGlobalRef real)))\n\t\t )\n\t    ;;(push prox-class +java-types+)\n\t    (setf (gethash class-internal-name +java-types+) prox-class)\n\t    (setq this prox-class)\n\t    )\n\t  )\n\t)\n      )\n    this\n    )\n  )\n\n(defun find-java-class-proxy (internal-name)\n  ;;(find internal-name +java-types+ :key #'java-class-name :test #'string=)\n  (gethash internal-name +java-types+)\n  )\n\n(defun java-internal-full-type-name-p (name)\n  (and (not (empty-str-p name))\n       (or (not (null (position #\\/ name))) (char= #\\[ (char name 0))))\n  )\n\n(defun fetch-java-class-in-context (resolved-name name)\n  (cond (resolved-name\n\t (values (fetch-java-class-from-jvm resolved-name) resolved-name)\n\t )\n\t((java-internal-full-type-name-p name)\n\t (values (fetch-java-class-from-jvm name) name)\n\t )\n\t(t\n\t (let (try-name this this-name clashes)\n\t   (dolist (pkg-prefix (java-context-on-demand-imports *java-context*))\n\t     (setq pkg-prefix\n\t\t   (delete #\\* (replace-dots (copy-seq pkg-prefix)) :from-end t))\n\t     (setq try-name (str+ pkg-prefix name))\n\t     ;;(break)\n\t     (handle-java-exception\n\t      (let ((class-found (find-java-class-proxy try-name))\n\t\t    ;; lookup proxy cache first because\n\t\t    ;; we may have already accessed the class\n\t\t    ;; by its full name without simple name resolution.\n\t\t    )\n\t\t(unless class-found\n\t\t  ;; Ok, we really don't know this one so we go to the JVM.\n\t\t  (setq class-found (fetch-java-class-from-jvm try-name)))\n\t\t;;(break)\n\t\t(if this\n\t\t    (push class-found clashes)\n\t\t    (setq this class-found this-name try-name)\n\t\t    )\n\t\t)\n\t      (java-NoClassDefFoundError\n\t       ()\n\t       ;;(break \"First java handler.\")\n\t       ;; This is a normal case. Let's loop to the next import spec.\n\t       )\n;;; I don't think we should really handle this here.\n;;; In fact, we would only be in the way or the real handler.\n;;;\n\t      ;; \t\t((\"java.lang.ClassFormatError\"\n\t      ;; \t\t  \"java.lang.ClassCircularityError\"\n\t      ;; \t\t  \"java.lang.ExceptionInInitializerError\")\n\t      ;; \t\t (exception)\n\t      ;; \t\t (error \"JVM internal error! It said: ~S.~%\" exception)\n\t      ;; \t\t )\n\t      ;; \t\t(\"java.lang.OutOfMemoryError\"\n\t      ;; \t\t ()\n\t      ;; \t\t (error \"Game Over! JVM said we are out of memory!\")\n\t      ;; \t\t )\n\n\t      )\n\t     )\n\t   (when clashes\n\t     (error \"Ambiguous reference to ~S.~%Possible candidates are:~%~S~%\"\n\t\t    name (mapcar #'get-real-class-name (cons this clashes)))\n\t     )\n\t   (unless this\n\t     (error \"No Java class found for ~S.~%\" name)\n\t     )\n\t   (values this this-name)\n\t   )\n\t )\n\t)\n;;;    )\n  )\n\n(defun java-full-type-name-p (name)\n  (and (not (empty-str-p name))\n       (or (not (null (position #\\. name))) (char= #\\[ (char name 0))))\n  )\n\n(defun find-java-class (name)\n  (declare (string name))\n  (unless (stringp name)\n    (error \"Value '~S' is not a valid Java class name.\" name))\n  (let* ((*java-context* *java-context*) ;; to assure consistency through locks duration.\n\t (internal-name (if (java-full-type-name-p name)\n\t\t\t    (replace-dots (copy-seq name))\n\t\t\t    (lookup-simple-type-name *java-context* name)))\n\t (prox-class (find-java-class-proxy internal-name))\n\t )\n    (unless prox-class\n      (let (full-internal-name)\n\t(bt:with-lock-held (+java-reflection-lock+)\n\t  (block java-reflection-update\n\t    (when (setq prox-class (find-java-class-proxy internal-name))\n\t      (return-from java-reflection-update)) ;; got beaten to it.\n\n\t    (multiple-value-setq (prox-class full-internal-name)\n\t      (fetch-java-class-in-context internal-name name))\n\t    )\n\t  )\n\n\t(unless internal-name\n\t  ;; this is the case of a simple name that is resolved for the first time.\n\t  ;; explicit array names go through this case too.\n\t  (unless (java-internal-full-type-name-p name)\n\t    (bt:with-lock-held ((java-context-mutation-lock *java-context*))\n\t      (let ((internal-name (lookup-simple-type-name *java-context* name)))\n\t\t(cond ((null internal-name)\n\t\t       (setf (lookup-simple-type-name *java-context* name)\n\t\t\t     full-internal-name)\n\t\t       )\n\t\t      ((string= internal-name full-internal-name)\n\t\t       ;; nothing to do, somebody else did it already.\n\t\t       )\n\t\t      (t\n\t\t       (error \"Inconsistent Java class name mapping.~@\n                               Name ~S is mapped to ~S and would become ~S.~%\"\n\t\t\t      name internal-name full-internal-name)\n\t\t       )\n\t\t      )\n\t\t))\n\t    )\n\t  )\n\t)\n      )\n    prox-class\n    )\n  )\n\n(defun arg-val-is-assignable-to (arg param)\n  (when (jref-p arg) (setq arg (jref-it arg))) ;; unbox it first.\n  (if (pointerp arg)\n      (if (null-pointer-p arg)\n\t  ;; arg is a reference to a Java object.\n\t  (= JNI_FALSE (jmethod-boolean-0 param \"isPrimitive\"))\n\t  (prog2\n\t      (PushLocalFrame 2)\n\t      (= JNI_TRUE (IsAssignableFrom (GetObjectClass arg) param))\n\t    (PopLocalFrame (null-pointer)))\n\t  )\n      (if (null arg)\n\t  (= JNI_FALSE (jmethod-boolean-0 param \"isPrimitive\"))\n\t  (typecase arg\n\t    (float\n\t     (or (%jeq param java-float-prim-type) (%jeq param java-double-prim-type))\n\t     )\n\t    (rational\n\t     ;; this is not very precise. Could be better! Depends on CFFI behaviour...\n\t     (or (%jeq param java-int-prim-type)\n\t\t (%jeq param java-long-prim-type)\n\t\t (%jeq param java-short-prim-type)\n\t\t (%jeq param java-byte-prim-type)\n\t\t (%jeq param java-float-prim-type)\n\t\t (%jeq param java-double-prim-type)\n\t\t )\n\t     )\n\t    (character\n\t     (or (%jeq param java-char-prim-type)\n\t\t (%jeq param java-int-prim-type)\n\t\t (%jeq param java-long-prim-type)\n\t\t (%jeq param java-float-prim-type)\n\t\t (%jeq param java-double-prim-type)\n\t\t )\n\t     )\n\t    (jprim\n\t     (case (jprim-type arg) ;; This is the usual \"widening\" conversions.\n\t       (jboolean\n\t\t(%jeq param java-boolean-prim-type)\n\t\t)\n\t       (jchar\n\t\t(or (%jeq param java-char-prim-type)\n\t\t    (%jeq param java-int-prim-type)\n\t\t    (%jeq param java-long-prim-type)\n\t\t    (%jeq param java-float-prim-type)\n\t\t    (%jeq param java-double-prim-type)\n\t\t    )\n\t\t)\n\t       (jbyte\n\t\t(or (%jeq param java-int-prim-type)\n\t\t    (%jeq param java-long-prim-type)\n\t\t    (%jeq param java-short-prim-type)\n\t\t    (%jeq param java-byte-prim-type)\n\t\t    (%jeq param java-float-prim-type)\n\t\t    (%jeq param java-double-prim-type)\n\t\t    )\n\t\t)\n\t       (jshort\n\t\t(or (%jeq param java-int-prim-type)\n\t\t    (%jeq param java-long-prim-type)\n\t\t    (%jeq param java-short-prim-type)\n\t\t    (%jeq param java-float-prim-type)\n\t\t    (%jeq param java-double-prim-type)\n\t\t    )\n\t\t)\n\t       (jint\n\t\t(or (%jeq param java-int-prim-type)\n\t\t    (%jeq param java-long-prim-type)\n\t\t    (%jeq param java-float-prim-type)\n\t\t    (%jeq param java-double-prim-type)\n\t\t    )\n\t\t)\n\t       (jlong\n\t\t(or (%jeq param java-long-prim-type)\n\t\t    (%jeq param java-float-prim-type)\n\t\t    (%jeq param java-double-prim-type)\n\t\t    )\n\t\t)\n\t       (jfloat\n\t\t(or (%jeq param java-float-prim-type)\n\t\t    (%jeq param java-double-prim-type)\n\t\t    )\n\t\t)\n\t       (jdouble\n\t\t(%jeq param java-double-prim-type)\n\t\t)\n\t       )\n\t     )\n\t    (t\n\t     ;;(break \"type is not assignable to a Java variable\")\n\t     nil ;; this is not a type that can be assigned to a Java variable.\n\t     )\n\t    )\n\t  )\n      )\n  )\n\n(defun is-applicable-method-p (method args)\n  (let* ((params (java-method-param-types method))\n\t (nb-params (array-total-size params))\n\t (i 0)\n\t)\n    (do ()\n\t((or (>= i nb-params) (null args)))\n      (let ((param (aref params i))\n\t    (arg (car args))\n\t    )\n\t(unless (arg-val-is-assignable-to arg param)\n\t  ;;(break \"reject method\")\n\t  (return-from is-applicable-method-p nil)) ;; arg and param do not match.\n\t)\n      (incf i)\n      (pop args)\n      )\n    ;;(break \"out of is-applicable-method-p\")\n    (if (and (= i nb-params) (endp args))\n      (return-from is-applicable-method-p t) ;; everything matched.\n      (return-from is-applicable-method-p nil) ;; ran out of either params or args.\n      )\n    )\n  )\n\n(defun is-more-specific-type (t1 t2)\n  (cond ((%jeq t1 java-boolean-prim-type)\n\t (%jeq t2 java-boolean-prim-type)\n\t )\n\t((%jeq t1 java-char-prim-type)\n\t (or (%jeq t2 java-char-prim-type)\n\t     (%jeq t2 java-int-prim-type)\n\t     (%jeq t2 java-long-prim-type)\n\t     (%jeq t2 java-float-prim-type)\n\t     (%jeq t2 java-double-prim-type)\n\t     )\n\t )\n\t((%jeq t1 java-byte-prim-type)\n\t (or (%jeq t2 java-int-prim-type)\n\t     (%jeq t2 java-long-prim-type)\n\t     (%jeq t2 java-short-prim-type)\n\t     (%jeq t2 java-byte-prim-type)\n\t     (%jeq t2 java-float-prim-type)\n\t     (%jeq t2 java-double-prim-type)\n\t     )\n\t )\n\t((%jeq t1 java-short-prim-type)\n\t (or (%jeq t2 java-int-prim-type)\n\t     (%jeq t2 java-long-prim-type)\n\t     (%jeq t2 java-short-prim-type)\n\t     (%jeq t2 java-float-prim-type)\n\t     (%jeq t2 java-double-prim-type)\n\t     )\n\t )\n\t((%jeq t1 java-int-prim-type)\n\t (or (%jeq t2 java-int-prim-type)\n\t     (%jeq t2 java-long-prim-type)\n\t     (%jeq t2 java-float-prim-type)\n\t     (%jeq t2 java-double-prim-type)\n\t     )\n\t )\n\t((%jeq t1 java-long-prim-type)\n\t (or (%jeq t2 java-long-prim-type)\n\t     (%jeq t2 java-float-prim-type)\n\t     (%jeq t2 java-double-prim-type)\n\t     )\n\t )\n\t((%jeq t1 java-float-prim-type)\n\t (or (%jeq t2 java-float-prim-type)\n\t     (%jeq t2 java-double-prim-type)\n\t     )\n\t )\n\t((%jeq t1 java-double-prim-type)\n\t (%jeq t2 java-double-prim-type)\n\t )\n\t(t ;; this has to be an object reference\n\t (= JNI_TRUE (IsAssignableFrom t1 t2))\n\t )\n\t)\n  )\n\n(defun is-more-specific-method (m1 m2)\n  (let ((params-1 (java-method-param-types m1))\n\t(params-2 (java-method-param-types m2))\n\t)\n    (dotimes (i (array-total-size params-1))\n      (let ((par1 (aref params-1 i))\n\t    (par2 (aref params-2 i)))\n\t(unless (is-more-specific-type par1 par2)\n\t  ;(break)\n\t  (return-from is-more-specific-method nil))\n\t)\n      )\n    ;(break)\n    (return-from is-more-specific-method t)\n    )\n  )\n\n(defun remove-from-list (this set)\n  (if (eq this (car set))\n      (cdr set)\n      (do ((root set (cdr root))\n\t   )\n\t  ((endp (cdr root)) set)\n\t(when (eq this (cadr root))\n\t  (rplacd root (cddr root))\n\t  )\n\t)\n      )\n  )\n\n(defun choose-most-specific-method (set)\n  (do ((shrank t )\n       )\n      ((not shrank))\n    (setq shrank nil)\n    (do* ((head1 set (cdr head1))\n\t  (head2 set set)\n\t  (m1 (car head1) (car head1))\n\t  )\n\t ((endp head1))\n      (do* ((m2 (pop head2) (pop head2))\n\t    )\n\t   ((null m2))\n\t(when (and (not (eq m1 m2))\n\t\t   (is-more-specific-method m1 m2))\n\t  ;(break)\n\t  (setq set (remove-from-list m2 set))\n\t  (setq shrank t)\n\t  )\n\t)\n      )\n    )\n\n  (when (cdr set)\n    ;; this set is not a singleton at this point!\n    ;;(break \"Ambiguous invocation for method ~S.\" (java-method-name (car set)))\n    (warn \"Arbitrary resolution for method ~S.~%\" (java-method-name (car set)))\n    )\n  ;(break)\n  (car set)\n  )\n\n(defun choose-method-in-overload-set (set args)\n  (let ((nb-args (length args))\n\tcandidates\n\t)\n    ;; first, select on the number of parameters.\n    (dolist (method set)\n      (when (= nb-args (length (java-method-param-types method)))\n\t(push method candidates)\n\t)\n      )\n    ;(break)\n    (setq set candidates candidates nil)\n    ;; second, match parameter types.\n    (dolist (method set)\n      (when (is-applicable-method-p method args)\n\t;;(break)\n\t(push method candidates))\n\t)\n    ;(break)\n    ;; there should be only one!\n    (case (length candidates)\n      (1 (car  candidates))\n      (0 (error \"No appropriate method found.~%\"))\n      (t (choose-most-specific-method candidates))\n      )\n    )\n  )\n\n(defun get-object-class (this)\n  (unless (null-pointer-p this)\n    (PushLocalFrame 2)\n    (prog1\n\t(find-java-class-proxy-from-real (GetObjectClass this))\n      (PopLocalFrame (null-pointer)))\n    )\n  )\n\n(defun find-java-method (this name args)\n  (let* ((class (get-object-class this))\n\t (methods (java-class-methods class)))\n    (unless methods\n      (unless (java-class-methods-fetched class)\n\t(fetch-methods-of-class class))\n      (setq methods (java-class-methods class))\n      )\n    (let (set)\n      (dolist (method methods (setq set (nreverse set)))\n\t(when (string= name (java-method-name method))\n\t  ;(format t \"Selecting method ~S as candidate.\" name)\n\t  (push method set))\n\t)\n      (unless set\n\t(error \"No instance method of name ~S on object of type ~S.~%\"\n\t       name class)\n\t)\n      (if (cdr set)\n\t  (choose-method-in-overload-set set args) ; Overloaded!\n\t  (car set)) ; Unambiguous.\n      )\n    )\n  )\n\n(defun find-java-static-method (this-class name args)\n  (let* ((methods (java-class-static-methods this-class))\n\t )\n    (unless methods\n      (unless (java-class-methods-fetched this-class)\n\t(fetch-methods-of-class this-class))\n      (setq methods (java-class-static-methods this-class))\n      )\n    (let (set)\n      (dolist (method methods (setq set (nreverse set)))\n\t(when (string= name (java-method-name method))\n\t  (push method set))\n\t)\n      (unless set\n\t(error \"No method of name ~S on class ~S.~%\" name this-class)\n\t)\n      (if (cdr set)\n\t  (choose-method-in-overload-set set args) ; Overloaded!\n\t  (car set)) ; Unambiguous.\n      )\n    )\n  )\n\n(defun find-java-constructor (class args)\n  (let* ((constructors (java-class-constructors class))\n\t ;;(name \"<init>\")\n\t )\n    (unless constructors\n      (unless (java-class-constructors-fetched class)\n\t(fetch-constructors-of-class class))\n      (setq constructors (java-class-constructors class)))\n    (unless constructors\n      (error \"No constructor found for class ~S.~%\" class)\n      )\n    (if (cdr constructors)\n\t(choose-method-in-overload-set constructors args)\n\t(car constructors))\n    )\n  )\n\n\n#|\n(defun fetch-java-field (real-class field-name)\n;;   (PushLocalFrame 2)\n;;   (PopLocalFrame\n;;    (jmethod-object-1 real-class \"getField\" (NewStringUTF field-name)\n;; \t\t     \"(Ljava/lang/String;)Ljava/lang/reflect/Field;\"))\n  (with-jvm-local-frame (2 :local-ref-value t)\n    (jmethod-object-1 real-class \"getField\" (NewStringUTF field-name)\n\t\t     \"(Ljava/lang/String;)Ljava/lang/reflect/Field;\"))\n  )\n|#\n\n(defun Class-getField (real-class field-name)\n  (jmethod-object-1 real-class \"getField\" field-name\n\t\t    \"(Ljava/lang/String;)Ljava/lang/reflect/Field;\")\n  )\n\n(defun replace-dots (descrip)\n  (let ((len (length descrip)))\n    (dotimes (i len)\n      (when (char= #\\. (schar descrip i))\n\t(setf (schar descrip i) #\\/)\n\t)\n      )\n    )\n  descrip\n  )\n\n(defun build-sig-for-field (field)\n  (PushLocalFrame 2)\n  (let* ((jfield (java-field-real field))\n\t (jtype (jmethod-object-0 jfield \"getType\" \"()Ljava/lang/Class;\"))\n\t (jtype-name (get-real-class-name jtype))\n\t )\n    (setf (java-field-real-type field) (NewGlobalRef jtype))\n    (PopLocalFrame (null-pointer)) ;; Pops jtype\n    (cond ((string= \"int\" jtype-name)\n\t   (setf (java-field-sig field) \"I\"\n\t\t (java-field-primitive-type field) 'jint)\n\t   )\n\t  ((string= \"boolean\" jtype-name)\n\t   (setf (java-field-sig field) \"Z\"\n\t\t (java-field-primitive-type field) 'jboolean)\n\t   )\n\t  ((string= \"byte\" jtype-name)\n\t   (setf (java-field-sig field) \"B\"\n\t\t (java-field-primitive-type field) 'jbyte)\n\t   )\n\t  ((string= \"char\" jtype-name)\n\t   (setf (java-field-sig field) \"C\"\n\t\t (java-field-primitive-type field) 'jchar)\n\t   )\n\t  ((string= \"short\" jtype-name)\n\t   (setf (java-field-sig field) \"S\"\n\t\t (java-field-primitive-type field) 'jshort)\n\t   )\n\t  ((string= \"long\" jtype-name)\n\t   (setf (java-field-sig field) \"J\"\n\t\t (java-field-primitive-type field) 'jlong)\n\t   )\n\t  ((string= \"float\" jtype-name)\n\t   (setf (java-field-sig field) \"F\"\n\t\t (java-field-primitive-type field) 'jfloat)\n\t   )\n\t  ((string= \"double\" jtype-name)\n\t   (setf (java-field-sig field) \"D\"\n\t\t (java-field-primitive-type field) 'jdouble)\n\t   )\n\t  (t ;; Here we got either an object reference or an array.\n\t   (replace-dots jtype-name)\n\t   (unless (char= #\\[ (schar jtype-name 0)) ; this identifies an array!\n\t     ;; here we turn this object name into a cannonical class descriptor.\n\t     (setq jtype-name (str+ \"L\" jtype-name \";\"))\n\t     )\n\t   (setf (java-field-sig field) jtype-name\n\t\t (java-field-primitive-type field) 'jobject)\n\t   )\n\t  )\n    )\n  )\n\n(defun find-java-static-field (class name)\n  (let ((fields (java-class-static-fields class)))\n    (let ((field (find name fields :key #'java-field-name :test #'string=)))\n      (unless field\n\t(bt:with-lock-held (+java-reflection-lock+)\n\t  (block java-reflection-update\n\t    (when (setq field (find name fields :key #'java-field-name :test #'string=))\n\t      (return-from java-reflection-update)) ;; got beaten to it.\n\n\t    (with-jvm-local-frame (4)\n\t      (let ((jfield ;;(fetch-java-field (java-class-real class) name)))\n\t\t     (Class-getField (java-class-real class) (NewStringUTF name)));;2 lref\n\t\t    )\n\t\t(setq field (make-java-field :name name\n\t\t\t\t\t     :static (Member-isStatic-p jfield)\n\t\t\t\t\t     :real (NewGlobalRef jfield)\n\t\t\t\t\t     :id (FromReflectedField jfield)))\n\t\t(build-sig-for-field field)\n\t\t(if (java-field-static field)\n\t\t    (push field (java-class-static-fields class))\n\t\t    (push field (java-class-fields class)))\n\t\t)\n\t      )\n\t    )\n\t  )\n\t)\n      field\n      )\n    )\n  )\n\n(defun find-java-field (class name)\n  (let ((fields (java-class-fields class)))\n    (let ((field (find name fields :key #'java-field-name :test #'string=)))\n      (unless field\n\t(bt:with-lock-held (+java-reflection-lock+)\n\t  (block java-reflection-update\n\t    (when (setq field (find name fields :key #'java-field-name :test #'string=))\n\t      (return-from java-reflection-update)) ;; got beaten to it.\n\n\t    (with-jvm-local-frame (4)\n\t      (let ((jfield ;;(fetch-java-field (java-class-real class) name)))\n\t\t     (Class-getField (java-class-real class) (NewStringUTF name)));;2 lref\n\t\t    )\n\t\t(setq field (make-java-field :name name\n\t\t\t\t\t     :static (Member-isStatic-p jfield)\n\t\t\t\t\t     :real (NewGlobalRef jfield)\n\t\t\t\t\t     :id (FromReflectedField jfield)))\n\t\t(build-sig-for-field field)\n\t\t(if (java-field-static field)\n\t\t    (push field (java-class-static-fields class))\n\t\t    (push field (java-class-fields class)))\n\t\t)\n\t      )\n\t    )\n\t  )\n\t)\n      field\n      )\n    )\n  )\n\n(defun jfield-static (class-designation name)\n  (PushLocalFrame 2)\n  (let* ((class (find-java-class class-designation))\n\t (field (find-java-static-field class name))\n\t (real-class (java-class-real class))\n\t (field-id (java-field-id field))\n\t result\n\t )\n    (unless (java-field-static field)\n      (PopLocalFrame (null-pointer))\n      (error \"Field ~A is non-static but accessed as static.\" name))\n    (case (java-field-primitive-type field)\n      (jobject (setq result\n\t\t     (as-jref-value\n\t\t      (PopLocalFrame (GetStaticObjectField real-class field-id)))))\n      (jint (setq result (GetStaticIntField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jboolean (setq result (GetStaticBooleanField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jbyte (setq result (GetStaticByteField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jchar (setq result (GetStaticCharField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jshort (setq result (GetStaticShortField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jlong (setq result (GetStaticLongField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jfloat (setq result (GetStaticFloatField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (jdouble (setq result (GetStaticDoubleField real-class field-id))\n\t    (PopLocalFrame (null-pointer)))\n      (t (error \"CL+J Internal error!\"))\n      )\n    result\n    )\n  )\n\n(defun jfield-instance (object name)\n  (with-pinned-values (object)\n    (when (jref-p object) (setq object (jref-it object)))\n    (PushLocalFrame 2)\n    (let* ((class (get-object-class object))\n\t   (field (find-java-field class name))\n\t   (field-id (java-field-id field))\n\t   result\n\t   )\n      (when (java-field-static field)\n\t(PopLocalFrame (null-pointer))\n\t(error \"Field ~A is static but accessed as non-static.\" name))\n      (case (java-field-primitive-type field)\n\t(jobject (setq result\n\t\t       (as-jref-value\n\t\t\t(PopLocalFrame (GetObjectField object field-id)))))\n\t(jint (setq result (GetIntField object field-id))\n\t      (PopLocalFrame (null-pointer)))\n\t(jboolean (setq result (GetBooleanField object field-id))\n\t\t  (PopLocalFrame (null-pointer)))\n\t(jbyte (setq result (GetByteField object field-id))\n\t       (PopLocalFrame (null-pointer)))\n\t(jchar (setq result (GetCharField object field-id))\n\t       (PopLocalFrame (null-pointer)))\n\t(jshort (setq result (GetShortField object field-id))\n\t\t(PopLocalFrame (null-pointer)))\n\t(jlong (setq result (GetLongField object field-id))\n\t       (PopLocalFrame (null-pointer)))\n\t(jfloat (setq result (GetFloatField object field-id))\n\t\t(PopLocalFrame (null-pointer)))\n\t(jdouble (setq result (GetDoubleField object field-id))\n\t\t (PopLocalFrame (null-pointer)))\n\t(t (error \"CL+J Internal error!\"))\n\t)\n      result\n      )\n    )\n  )\n\n;; ;; Is there any real use of this jfield macro?\n;; ;; It seems not but let's keep it for a while. (2009-04-25)\n;; (defmacro jfield (java-object-ref name)\n;;   (if (stringp java-object-ref)\n;;       `(jfield-static ,java-object-ref ,name)\n;;       `(jfield-instance ,java-object-ref ,name)\n;;       )\n;;   )\n\n(defun jfield-static-set (class-designation name value)\n  (let* ((class (find-java-class class-designation))\n\t (field (find-java-static-field class name))\n\t (real-class (java-class-real class))\n\t (field-id (java-field-id field))\n\t )\n    (unless (java-field-static field)\n      (error \"Field ~A is non-static but accessed as static.\" name))\n    (case (java-field-primitive-type field)\n      (jobject\n       (with-pinned-values (value)\n\t (PushLocalFrame 4)\n\t (when (jref-p value) (setq value (jref-it value)))\n\t (if (and (pointerp value)\n\t\t  (or (null-pointer-p value)\n\t\t      (= JNI_TRUE (IsAssignableFrom (GetObjectClass value)\n\t\t\t\t\t\t    (java-field-real-type field)))))\n\t     (SetStaticObjectField real-class field-id value)\n\t     (progn\n\t       (PopLocalFrame (null-pointer))\n\t       (error \"Static field ~A of class ~A cannot be assigned value: ~S.\"\n\t\t      name (java-class-name class) value)))\n\t (PopLocalFrame (null-pointer))\n\t )\n       )\n      (jint (SetStaticIntField real-class field-id value))\n      (jboolean (SetStaticBooleanField real-class field-id value))\n      (jbyte (SetStaticByteField real-class field-id value))\n      (jchar (SetStaticCharField real-class field-id value))\n      (jshort (SetStaticShortField real-class field-id value))\n      (jlong (SetStaticLongField real-class field-id value))\n      (jfloat (SetStaticFloatField real-class field-id value))\n      (jdouble (SetStaticDoubleField real-class field-id value))\n      (t (error \"CL+J Internal error!\"))\n      )\n    value\n    )\n  )\n\n(defun jfield-instance-set (java-object name value)\n  (with-pinned-values (java-object)\n    (when (jref-p java-object) (setq java-object (jref-it java-object)))\n    (let* ((class (get-object-class java-object))\n\t   (field (find-java-field class name))\n\t   (field-id (java-field-id field))\n\t   )\n      (when (java-field-static field)\n\t(error \"Field ~A is static but accessed as non-static.\" name))\n      (case (java-field-primitive-type field)\n\t(jobject\n\t (with-pinned-values (value)\n\t   (PushLocalFrame 4)\n\t   (when (jref-p value) (setq value (jref-it value)))\n\t   (if (and (pointerp value)\n\t\t    (or (null-pointer-p value)\n\t\t\t(= JNI_TRUE (IsAssignableFrom (GetObjectClass value)\n\t\t\t\t\t\t      (java-field-real-type field)))))\n\t       (SetObjectField java-object field-id value)\n\t       (progn\n\t\t (PopLocalFrame (null-pointer))\n\t\t (error \"Instance field ~A of object of class ~A ~\n                         cannot be assigned value: ~S.\"\n\t\t\tname (java-class-name class) value)\n\t\t )\n\t       )\n\t   (PopLocalFrame (null-pointer))\n\t   )\n\t )\n\t(jint (SetIntField java-object field-id value))\n\t(jboolean (SetBooleanField java-object field-id value))\n\t(jbyte (SetByteField java-object field-id value))\n\t(jchar (SetCharField java-object field-id value))\n\t(jshort (SetShortField java-object field-id value))\n\t(jlong (SetLongField java-object field-id value))\n\t(jfloat (SetFloatField java-object field-id value))\n\t(jdouble (SetDoubleField java-object field-id value))\n\t(t (error \"CL+J Internal error!\"))\n\t)\n      value\n      )\n    )\n  )\n\n\n\n;;;;;\n\n\n(defun jmethod-instance (this method-name &rest args)\n  ;(declare (ignore this method-name args))\n  ;;(declare (special this args)) ;; to pin them againt GC for the rest of the function.\n  ;;(let ((this this) (args args)) ;; to be free to modify them without unpinning them.\n  (with-pinned-values (this args)\n    (when (jref-p this)\n      (setq this (jref-it this)))\n    (unless (cffi:pointerp this)\n      (error \"Cannot invoke Java method ~A on Lisp object ~S.\" method-name this))\n    (when (cffi:null-pointer-p this)\n      (error \"While invoking Java method ~S; No method can be invoked on Java object reference null.\" method-name))\n    (let* ((method (find-java-method this method-name args))\n\t   (id (java-method-id method))\n\t   (return-ptype (java-method-return-ptype method))\n\t   (sig (java-method-sig method))\n\t   )\n      (unless return-ptype\n\t(build-sig-for-method method)\n\t(setq return-ptype (java-method-return-ptype method))\n\t(setq sig (java-method-sig method))\n\t)\n      (with-foreign-object (jargs (union-jvalue) (+ 2 (length args)))\n\t(dyn-fill-args sig jargs args)\n\t(case return-ptype\n\t  (jobject (as-jref-value (CallObjectMethodA this id jargs)))\n\t  (jint (CallIntMethodA this id jargs))\n\t  (:void (CallVoidMethodA this id jargs))\n\t  (jboolean (CallBooleanMethodA this id jargs))\n\t  (jbyte (CallByteMethodA this id jargs))\n\t  (jchar (CallCharMethodA this id jargs))\n\t  (jshort (CallShortMethodA this id jargs))\n\t  (jlong (CallLongMethodA this id jargs))\n\t  (jfloat (CallFloatMethodA this id jargs))\n\t  (jdouble (CallDoubleMethodA this id jargs))\n\t  (t (error \"CL+J: Internal error while calling ~S.~%\" method-name))\n\t  )\n\t)\n      )\n    )\n  )\n\n(defun jmethod-static (this-class method-name &rest args)\n  ;;(declare (special args)) ;; to pin them against GC for the rest of the function.\n  ;;(let ((args args)) ;; to be free to modify it without unpinning it.\n  (with-pinned-values (args)\n    (let* ((method (find-java-static-method this-class method-name args))\n\t   (this-real-class (java-class-real this-class))\n\t   (id (java-method-id method))\n\t   (return-ptype (java-method-return-ptype method))\n\t   (sig (java-method-sig method))\n\t   )\n      (unless return-ptype\n\t(build-sig-for-method method)\n\t(setq return-ptype (java-method-return-ptype method))\n\t(setq sig (java-method-sig method))\n\t)\n      (with-foreign-object (jargs (union-jvalue) (length args))\n\t(dyn-fill-args sig jargs args)\n\t(case return-ptype\n\t  (jobject (as-jref-value (CallStaticObjectMethodA this-real-class id jargs)))\n\t  (jint (CallStaticIntMethodA this-real-class id jargs))\n\t  (:void (CallStaticVoidMethodA this-real-class id jargs))\n\t  (jboolean (CallStaticBooleanMethodA this-real-class id jargs))\n\t  (jbyte (CallStaticByteMethodA this-real-class id jargs))\n\t  (jchar (CallStaticCharMethodA this-real-class id jargs))\n\t  (jshort (CallStaticShortMethodA this-real-class id jargs))\n\t  (jlong (CallStaticLongMethodA this-real-class id jargs))\n\t  (jfloat (CallStaticFloatMethodA this-real-class id jargs))\n\t  (jdouble (CallStaticDoubleMethodA this-real-class id jargs))\n\t  (t (error \"CL+J: Internal error while calling ~S.~%\" method-name))\n\t  )\n\t)\n      )\n    )\n  )\n\n(defmacro jmethod (this method-name &rest args)\n  (cond ((stringp this)\n\t `(jmethod-static (find-java-class ,this) ,method-name ,@args)\n\t )\n\t(t\n\t `(jmethod-instance ,this ,method-name ,@args)\n\t )\n\t)\n  )\n\n;;;\n;;;\n;;;\n\n(defun jalloc-and-construct (class args)\n  (let* ((constructor (find-java-constructor class args))\n\t (real-class (java-class-real class))\n\t (id (java-method-id constructor))\n\t (sig (java-method-sig constructor))\n\t )\n    (with-foreign-object (jargs (union-jvalue) (length args))\n      (dyn-fill-args sig jargs args)\n      (as-jref-value (NewObjectA real-class id jargs))\n      )\n    )\n  )\n\n(defun jnew (java-class &rest args)\n  (cond\n    ((stringp java-class)\n     (jalloc-and-construct (find-java-class java-class) args)\n     )\n    ((pointerp java-class)\n     (jalloc-and-construct (find-java-class-proxy-from-real java-class) args)\n     )\n    ((jref-p java-class)\n     (jalloc-and-construct (find-java-class-proxy-from-real (jref-it java-class)) args)\n     )\n    (t\n     (error \"Invalid first argument to jnew.\"))\n    )\n  )\n\n(defun jstr (some-lisp-string)\n  (declare (string some-lisp-string))\n  (as-jref-value (NewStringUTF some-lisp-string))\n  )\n\n(defmacro jstring (x) `(jstr ,x)) ;; just for convenience.\n\n(defun jstring-length (jstr)\n  (with-pinned-values (jstr) ;;let ((pinned-jstr jstr)) (declare (special pinned-jstr))\n\n    (when (jref-p jstr) (setq jstr (jref-it jstr))) ;; unbox it first.\n\n    ;; We must make sure 'jstr' is a valid\n    ;; Java String, otherwise the JVM blows up!\n    (unless (pointerp jstr)\n      (error \"Argument to jstring-length is not a Java object.\")\n      )\n    (unless (with-jvm-local-frame (2)\n\t      (%jeq (GetObjectClass jstr) (java-class-real java-String)))\n      (error \"Argument to jstring-length is not a java.lang.String\")\n      )\n    (GetStringLength jstr)\n    )\n  )\n\n(defun jnew-object[] (component-type dim)\n  ;; component-type must be a Java Class reference.\n  ;;(PushLocalFrame 6)\n  (with-jvm-local-frame (6)\n    (unless (and (pointerp component-type)\n\t\t (%jeq (java-class-real java-Class) (GetObjectClass component-type)))\n      (error \"Java array component type must be a proper class.~% Got this instead: ~S~%\"\n\t     (prog1 (java-string-to-lisp\n\t\t     (jmethod-object-0 component-type \"toString\" \"()Ljava/lang/String;\"))\n\t       #+nil (PopLocalFrame (null-pointer))))\n      )\n    (let ((vec (NewObjectArray dim component-type (null-pointer))))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-boolean[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewBooleanArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-byte[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewByteArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-char[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewCharArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-short[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewShortArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-int[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewIntArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-long[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewLongArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-float[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewFloatArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n(defun jnew-double[] (dim)\n  ;;(PushLocalFrame 2)\n  (with-jvm-local-frame (2)\n    (let ((vec (NewDoubleArray dim)))\n      (prog1 (jref vec) #+nil (PopLocalFrame (null-pointer))))\n    )\n  )\n\n\n\n(defun jnew[] (component-type dims)\n  (let (dim) ;; vec)\n    (cond ((listp dims)\n\t   (setq dim (pop dims))\n\t   (unless (integerp dim)\n\t     (error \"Invalid Java array dimension: ~S~% Dimension must be an integer.~%\"\n\t\t    dim))\n\t   )\n\t  ((integerp dims)\n\t   (setq dim dims dims nil)\n\t   )\n\t  (t\n\t   (error \"Invalid second argument to jnew. ~S~% ~\n                   Dimensions must be an integer or a list of integer.~%\"\n\t\t  dims)\n\t   )\n\t  )\n\n    (cond ((endp dims)\n\t   ;; this is a vector.\n\t   (cond ((stringp component-type)\n\t\t  ;; This must be a type name.\n\t\t  (cond ((string= component-type \"boolean\") (jnew-boolean[] dim))\n\t\t\t((string= component-type \"byte\") (jnew-byte[] dim))\n\t\t\t((string= component-type \"char\") (jnew-char[] dim))\n\t\t\t((string= component-type \"short\") (jnew-short[] dim))\n\t\t\t((string= component-type \"int\") (jnew-int[] dim))\n\t\t\t((string= component-type \"long\") (jnew-long[] dim))\n\t\t\t((string= component-type \"float\") (jnew-float[] dim))\n\t\t\t((string= component-type \"double\") (jnew-double[] dim))\n\t\t\t(t\n\t\t\t (let ((comp-class (find-java-class component-type)))\n\t\t\t   (if comp-class\n\t\t\t       (jnew-object[] (java-class-real comp-class) dim)\n\t\t\t       (error \"Unknown array component class: ~S~%\"\n\t\t\t\t      component-type))\n\t\t\t   )\n\t\t\t )\n\t\t\t)\n\t\t  )\n\t\t (t\n\t\t  ;; This must be a type reference.\n\t\t  (cond\n\t\t    ((%jeq component-type java-boolean-prim-type) (jnew-boolean[] dim))\n\t\t    ((%jeq component-type java-char-prim-type) (jnew-char[] dim))\n\t\t    ((%jeq component-type java-byte-prim-type) (jnew-byte[] dim))\n\t\t    ((%jeq component-type java-short-prim-type) (jnew-short[] dim))\n\t\t    ((%jeq component-type java-int-prim-type) (jnew-int[] dim))\n\t\t    ((%jeq component-type java-long-prim-type) (jnew-long[] dim))\n\t\t    ((%jeq component-type java-float-prim-type) (jnew-float[] dim))\n\t\t    ((%jeq component-type java-double-prim-type) (jnew-double[] dim))\n\t\t    (t (jnew-object[] component-type dim))\n\t\t    )\n\t\t  )\n\t\t )\n\t   )\n\t  (t\n\t   ;; this is a multi-dimensional array.\n\t   (let (vec)\n\t     ;;(PushLocalFrame 4)\n\t     (with-jvm-local-frame (4) ;; the depth is 2 but as usual we double.\n\t       (dotimes (i dim)\n\t\t (let ((comp (jnew[] component-type dims)))\n\t\t   (unless vec\n\t\t     (let ((comp-class (GetObjectClass (jref-it comp)))) ;; lref\n\t\t       (setq vec (NewObjectArray dim comp-class (null-pointer))) ;; lref\n\t\t       (DeleteLocalRef comp-class)\n\t\t       )\n\t\t     )\n\t\t   (SetObjectArrayElement vec i (jref-it comp))\n\t\t   )\n\t\t )\n\t       (setq vec (jref vec))\n\t       )\n\t     ;;(PopLocalFrame (null-pointer))\n\t     vec\n\t     )\n\t   )\n\t  )\n    ;;vec\n    )\n  )\n\n(defun jaref (java-array &rest subscripts)\n  (with-pinned-values (java-array)\n    ;;let ((pinned-java-array java-array)) (declare (special pinned-java-array))\n\n    (when (jref-p java-array) (setq java-array (jref-it java-array))) ;; unbox it first.\n\n    (when (null-pointer-p java-array)\n      (return-from jaref nil))\n\n    (PushLocalFrame 6)\n\n    (let* ((subscript (pop subscripts))\n\t   (array-class (GetObjectClass java-array)) ;; lref\n\t   comp-type\n\t   val\n\t   sub-array\n\t   ;;(depth 0)\n\t   )\n      (declare (integer subscript))\n      (unless (= JNI_TRUE (jmethod-instance array-class \"isArray\"))\n\t(PopLocalFrame (null-pointer))\n\t(error \"First argument to jaref is not a Java Array object.\")\n\t)\n\n      (setq comp-type (jmethod-object-0 array-class\n\t\t\t\t\t\"getComponentType\"\n\t\t\t\t\t\"()Ljava/lang/Class;\")) ;; lref\n\n      (do ()\n\t  ((= JNI_TRUE (jmethod-boolean-0 comp-type \"isPrimitive\")))\n\n\t;;(format t \"~%depth = ~D, index = ~D.\" depth subscript) (finish-output)\n\t;;(incf depth)\n\n\t;; Here we know that the component type must be Object.\n\n\t;;(format t \"~%About to get array element.\") (finish-output)\n\t(setq val (GetObjectArrayElement java-array subscript)) ;; this is a LocalRef.\n\t(when sub-array\n\t  (DeleteLocalRef sub-array)) ;; clean left-overs of the previous iteration.\n\n\t(cond ((and (= JNI_TRUE (jmethod-boolean-0 comp-type \"isArray\"))\n\t\t    (not (endp subscripts)))\n\t       ;; Prepare next iteration\n\t       (setq sub-array val\n\t\t     java-array sub-array\n\t\t     array-class comp-type\n\t\t     comp-type (prog1\n\t\t\t\t   (jmethod-object-0 comp-type\n\t\t\t\t\t\t     \"getComponentType\"\n\t\t\t\t\t\t     \"()Ljava/lang/Class;\")\n\t\t\t\t (DeleteLocalRef comp-type))\n\t\t     subscript (pop subscripts))\n\t       )\n\t      (t\n\t       (unless (endp subscripts)\n\t\t (PopLocalFrame (null-pointer))\n\t\t (error \"Too many subscripts on Java array.\")\n\t\t )\n\t       (setq val (jref val))\n\t       (PopLocalFrame (null-pointer))\n\t       (return-from jaref val)\n\t       )\n\t      )\n\t)\n\n      ;;(format t \"~%depth = ~D, index = ~D final.\" depth subscript) (finish-output)\n\n      ;; If we get here we know that comp-type is a Java primitive type.\n\n      (cond ((%jeq comp-type java-boolean-prim-type)\n\t     (with-foreign-object (buf 'jboolean 2)\n\t       (GetBooleanArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jboolean))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-char-prim-type)\n\t     (with-foreign-object (buf 'jchar 2)\n\t       (GetCharArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jchar))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-byte-prim-type)\n\t     (with-foreign-object (buf 'jbyte 2)\n\t       (GetByteArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jbyte))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-short-prim-type)\n\t     (with-foreign-object (buf 'jshort 2)\n\t       (GetShortArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jshort))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-int-prim-type)\n\t     (with-foreign-object (buf 'jint 2)\n\t       (GetIntArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jint))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-long-prim-type)\n\t     (with-foreign-object (buf 'jlong 2)\n\t       (GetLongArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jlong))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-float-prim-type)\n\t     (with-foreign-object (buf 'jfloat 2)\n\t       (GetFloatArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jfloat))\n\t       )\n\t     )\n\t    ((%jeq comp-type java-double-prim-type)\n\t     (with-foreign-object (buf 'jdouble 2)\n\t       (GetDoubleArrayRegion java-array subscript 1 buf)\n\t       (setq val (mem-ref buf 'jdouble))\n\t       )\n\t     )\n\t    (t\n\t     (PopLocalFrame (null-pointer))\n\t     (error \"Internal cl+j error! Unknown Java primitive type in array reference.\")\n\t     )\n\t    )\n      (PopLocalFrame (null-pointer))\n      (return-from jaref val)\n      )\n    )\n  )\n\n(defun jaref-set (java-array value &rest subscripts)\n  (with-pinned-values (java-array)\n    (let ((pre-subscripts (butlast subscripts))\n\t  (subscript (car (last subscripts)))\n\t  ;;(pinned-java-array java-array)\n\t  ;;(pinned-value value)\n\t  (value value)\n\t  )\n      ;;(declare (special pinned-java-array pinned-value))\n\n      (when (jref-p java-array) (setq java-array (jref-it java-array))) ;; unbox it first.\n      (when (jref-p value) (setq value (jref-it value)))\n\n      (when pre-subscripts\n\t(let ((target-vec (apply #'jaref java-array pre-subscripts))) ;; jref\n\t  (if target-vec\n\t      (setq java-array (jref-it target-vec))\n\t      (error \"Invalid Java array update location.\")\n\t      )\n\t  )\n\t)\n\n      (with-jvm-local-frame (6)\n\t(let* ((vec-class (GetObjectClass java-array)) ;; lref\n\t       (comp-type (jmethod-object-0 vec-class\n\t\t\t\t\t    \"getComponentType\"\n\t\t\t\t\t    \"()Ljava/lang/Class;\")) ;; lref\n\t       ;;value-type\n\t       )\n#|\n\t  It turns out that SetObjectArrayElement does the assignment\n\t  compatibility check already. We would only do double duty here!\n\n\t  ;; We must make sure that the \"value\" type and the component type\n\t  ;; are assignment compatible.\n\t  (cond ((pointerp value)\n\t\t (if (null-pointer-p value)\n\t\t     (setq value-type (null-pointer))\n\t\t     (setq value-type (GetObjectClass value)) ;; lref\n\t\t     )\n\t\t )\n\t\t((jprim-p value)\n\t\t (setq value-type\n\t\t       (case (jprim-type value)\n\t\t\t (jboolean java-boolean-prim-type)\n\t\t\t (jchar java-char-prim-type)\n\t\t\t (jbyte java-byte-prim-type)\n\t\t\t (jshort java-short-prim-type)\n\t\t\t (jint java-int-prim-type)\n\t\t\t (jlong java-long-prim-type)\n\t\t\t (jfloat java-float-prim-type)\n\t\t\t (jdouble java-double-prim-type)\n\t\t\t (t\n\t\t\t  (PopLocalFrame (null-pointer))\n\t\t\t  (error \"Internal CL+J error. Invalid Java primitive type.\")\n\t\t\t  )\n\t\t\t )\n\t\t       )\n\t\t )\n\t\t((numberp value) ;; byte, short, int, long, float, double\n\t\t )\n\t\t((characterp value) ;; char\n\t\t )\n\t\t((or (null value) (eq t value)) ;; boolean\n\t\t )\n\t\t(t\n\t\t (PopLocalFrame (null-pointer))\n\t\t (error\n\t\t  \"This value cannot be a meaningfull element of a Java array: ~S~%.\"\n\t\t  value)\n\t\t )\n\t\t)\n\t  #+ ccl (identity value-type) ;; just to silence CCL.\n|#\n\t  (cond ((/= JNI_TRUE (jmethod-boolean-0 comp-type \"isPrimitive\"))\n\t\t (SetObjectArrayElement java-array subscript value)\n\t\t )\n\t\t((%jeq comp-type java-boolean-prim-type)\n\t\t (with-foreign-object (buf 'jboolean 2)\n\t\t   (setf (mem-aref buf 'jboolean) value)\n\t\t   (SetBooleanArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-char-prim-type)\n\t\t (with-foreign-object (buf 'jchar 2)\n\t\t   (setf (mem-aref buf 'jchar) value)\n\t\t   (SetCharArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-byte-prim-type)\n\t\t (with-foreign-object (buf 'jbyte 2)\n\t\t   (setf (mem-aref buf 'jbyte) value)\n\t\t   (SetByteArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-short-prim-type)\n\t\t (with-foreign-object (buf 'jshort 2)\n\t\t   (setf (mem-aref buf 'jshort) value)\n\t\t   (SetShortArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-int-prim-type)\n\t\t (with-foreign-object (buf 'jint 2)\n\t\t   (setf (mem-aref buf 'jint) value)\n\t\t   (SetIntArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-long-prim-type)\n\t\t (with-foreign-object (buf 'jlong 2)\n\t\t   (setf (mem-aref buf 'jlong) value)\n\t\t   (SetLongArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-float-prim-type)\n\t\t (with-foreign-object (buf 'jfloat 2)\n\t\t   (setf (mem-aref buf 'jfloat) value)\n\t\t   (SetFloatArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t((%jeq comp-type java-double-prim-type)\n\t\t (with-foreign-object (buf 'jdouble 2)\n\t\t   (setf (mem-aref buf 'jdouble) value)\n\t\t   (SetDoubleArrayRegion java-array subscript 1 buf)\n\t\t   )\n\t\t )\n\t\t(t\n\t\t (error \"Internal CL+J error. Unknown Java primitive type.\")\n\t\t )\n\t\t)\n\t  )\n\t)\n      )\n    )\n  value\n  )\n\n(defsetf jaref (java-array &rest subscripts) (val)\n  `(jaref-set ,java-array ,val ,@subscripts)\n  )\n\n;;; A test case for the defsetf above: (setf (jaref foobar 2 3 4 ) 1)\n\n(defun jlength (java-array)\n  (with-pinned-values (java-array)\n    ;;let ((pinned-java-array java-array)) (declare (special pinned-java-array))\n\n    (when (jref-p java-array) (setq java-array (jref-it java-array))) ;; unbox it first.\n\n    ;; We must make sure 'java-array' is a valid\n    ;; Java Array object, otherwise the JVM blows up!\n    (unless (pointerp java-array)\n      (error \"Argument to jlength is not a Java object.\"))\n    (unless (with-jvm-local-frame (2)\n\t\t(= JNI_TRUE (jmethod-instance (GetObjectClass java-array) \"isArray\")))\n      (error \"Argument to jlength is not a Java Array object.\"))\n    (GetArrayLength java-array)\n    )\n  )\n\n;;;\n;;;\n;;;\n\n(defvar +java-to-lisp-converters+ (make-hash-table))\n(defvar +converters-lock+ (bt:make-lock \"CL+J Converters\"))\n;;(defvar *lisp-to-java-converters* (make-hash-table))\n\n(defun add-jtol-mapping (jclass converter)\n  (when (weak-jref-p jclass) (setq jclass (jref jclass)))\n  (when (jref-p jclass) (setq jclass (jref-it jclass)))\n  (when (cffi:pointerp jclass)\n    (setq jclass (find-java-class-proxy-from-real jclass)))\n  (when (java-class-p jclass)\n    (bt:with-lock-held (+converters-lock+)\n      (setf (gethash jclass +java-to-lisp-converters+) converter)))\n  )\n\n(defun delete-jtol-mapping (jclass)\n  (when (weak-jref-p jclass) (setq jclass (jref jclass)))\n  (when (jref-p jclass) (setq jclass (jref-it jclass)))\n  (when (cffi:pointerp jclass)\n    (setq jclass (find-java-class-proxy-from-real jclass)))\n  (when (java-class-p jclass)\n    (bt:with-lock-held (+converters-lock+)\n      (remhash jclass +java-to-lisp-converters+)))\n  )\n\n(defun jtol (jobj)\n  (when (weak-jref-p jobj) (setq jobj (jref jobj)))\n  (when (jref-p jobj) (setq jobj (jref-it jobj)))\n  (if (cffi::pointerp jobj)\n      (if (cffi::null-pointer-p jobj)\n\t  nil\n\t  (let* ((jclass (get-object-class jobj))\n\t\t (converter (gethash jclass +java-to-lisp-converters+))\n\t\t )\n\t    (if converter\n\t\t(funcall converter jobj)\n\t\t(error \"Don't know how to convert Java objects of class ~S.~%~\n                        Try to add a new converter to jtol using add-jtol-mapping.\" jclass))\n\t    )\n\t  )\n      (if (jprim-p jobj)\n\t  (mem-ref (jprim-value jobj) (jprim-type jobj))\n\t  jobj))\n  )\n\n\n(defgeneric ltoj (lobj))\n\n(defmethod ltoj ((lobj t))\n  (if (null lobj)\n      jnull\n      (let ((suggest (typecase lobj\n\t\t       ;; (mkcl:integer8 \"using jprim-byte\")\n\t\t       ;; (mkcl:integer16 \"using jprim-short\")\n\t\t       ;; (mkcl:integer32 \"using jprim-int\")\n\t\t       ;; (mkcl:integer64 \"using jprim-long\")\n\t\t       ((signed-byte 8) \"using jprim-byte\")\n\t\t       ((signed-byte 16) \"using jprim-short\")\n\t\t       ((signed-byte 32) \"using jprim-int\")\n\t\t       ((signed-byte 64) \"using jprim-long\")\n\t\t       (single-float \"using jprim-float\")\n\t\t       (double-float \"using jprim-double\")\n\t\t       (character \"using jprim-char\")\n\t\t       (t (if (eq lobj t)\n\t\t\t      \"using jprim-boolean\"\n\t\t\t      \"coding a new method for generic function ltoj\")))))\n\t(error \"Don't know how to convert Lisp object ~S to a Java object. Consider ~A.\" lobj suggest)\n\t)\n      )\n  )\n\n(defmethod ltoj ((str string))\n  (jstring str))\n\n\n(defun jtol-init ()\n  (add-jtol-mapping (find-java-class \"java.lang.String\") #'java-string-to-lisp)\n\n  ;; later on, once this file gets loaded you could do this instead:\n  ;; (add-jtol-mapping #?java.lang.String.class #'java-string-to-lisp)\n  )\n\n;;;\n;;;\n;;;\n\n(define-condition java-throwable (condition)\n  ((thrown :initarg :thrown\n\t   :reader java-throwable-thrown))\n  ;;(:report print-java-throwable)\n  (:report (lambda (condition stream)\n\t     (format stream \"Java throwable: ~S.~%\"\n\t\t     (jtol (jmethod-instance\n\t\t\t    (java-throwable-thrown condition) \"toString\")))\n\t     )\n\t   )\n  )\n\n(export '(java-throwable java-throwable-thrown))\n\n\n;; (defun print-java-throwable (condition stream)\n;;   ;;(ExceptionClear) ;; Do we really need to do this here?\n;;   (format stream \"Java exception: ~S.~%\"\n;; \t  (throwable-toString\n;; \t   (java-throwable-thrown condition)))\n;;   )\n\n\n(defmacro throw-java-exception (jclass &rest args)\n  `(let* ((j-excep (jnew ,jclass ,@args))\n\t  (cond-wrapper (make-condition 'java-throwable :thrown j-excep))\n\t  )\n\n     (signal cond-wrapper)\n     (error \"Java exception ~S was not handled!~%\" j-excep)\n     )\n  )\n\n\n;;;\n;;;\n;;;\n\n(defun java-alpha-char-p (ch)\n  (or (alpha-char-p ch) (char= ch #\\_) (char= ch #\\$)))\n\n(defun java-alphanumericp (ch)\n  (or (alphanumericp ch) (char= ch #\\_) (char= ch #\\$)))\n\n\n(defun read-java-method-name (s)\n  \"Returns a string that names a java method\"\n  ;; I decided to implement my own reading function\n  ;; instead of using (read) with :PRESERVE on the readtable\n  ;; in order to follow more closely Java's syntax on method names.\n  (let ((ch (peek-char nil s t nil t))\n\t(i 0)\n\tbuf\n\tlast-dot\n\tnext-to-last-dot\n\t)\n    (when (java-alpha-char-p ch)\n      (setq buf (make-array '(50) :element-type 'character\n\t\t\t    :adjustable t\n\t\t\t    :fill-pointer 0))\n      (do ((ch (read-char s t nil t) (read-char s t nil t))\n\t   )\n\t  ((not (or (java-alphanumericp ch) (char= ch #\\.))) (unread-char ch s))\n\t(vector-push-extend ch buf 50)\n\t(when (char= ch #\\.)\n\t  (setq next-to-last-dot last-dot\n\t\tlast-dot i)\n\t  )\n\t(incf i)\n\t;;(print ch)\n\t)\n      (let (class-name field-name method-name)\n\t(cond ((and (null last-dot) (null next-to-last-dot))\n\t       ;; this is a basic instance method call.\n\t       (setq method-name buf class-name nil field-name nil)\n\t       )\n\t      ((null next-to-last-dot)\n\t       ;; this is a static method call on an on-demand import class name.\n\t       (setq method-name (subseq buf (1+ last-dot)))\n\t       (setq class-name (subseq buf 0 last-dot))\n\t       (setq field-name nil)\n\t       )\n\t      (t\n\t       (setq method-name (subseq buf (1+ last-dot)))\n\t       (setq field-name (subseq buf (1+ next-to-last-dot) last-dot))\n\t       (setq class-name (subseq buf 0 next-to-last-dot))\n\t       )\n\t      )\n\t(values buf class-name field-name method-name)\n\t)\n      )\n    )\n  )\n\n(defun read-java-method-call (stream sub-char num-arg)\n  (declare (ignore num-arg sub-char))\n  (multiple-value-bind (whole class-name field-name method-name)\n\t(read-java-method-name stream)\n    (declare  (ignore whole))\n    (cond (field-name\n\t   ;; instance method on a static field or static method.\n\t   `(lambda (&rest args)\n\t      (let ((class (find-java-class ,class-name)))\n\t\t(cond (class\n\t\t       ;; This check is probably redundant and incomplete.\n\t\t       ;; Maybe it should be removed. The call to jfield-static\n\t\t       ;; will do the work anyway.\n\t\t       (unless (find-java-static-field class ,field-name)\n\t\t\t (error \"Field ~S does not exist on Java class ~S.~%\"\n\t\t\t\t,field-name ,class-name)\n\t\t\t )\n\t\t       (apply #'jmethod-instance\n\t\t\t      ;;(jfield ,class-name ,field-name)\n\t\t\t      (jfield-static ,class-name ,field-name)\n\t\t\t      ,method-name\n\t\t\t      args)\n\t\t       )\n\t\t      (t\n\t\t       (setq class\n\t\t\t     (find-java-class\n\t\t\t      ,(setq class-name (str+ class-name \".\" field-name))))\n\t\t       (unless class\n\t\t\t (error \"Unknown Java class ~S.~%\" ,class-name))\n\t\t       (apply #'jmethod-static\n\t\t\t      class\n\t\t\t      ,method-name\n\t\t\t      args)\n\t\t       )\n\t\t      )\n\t\t)\n\t      )\n\t   )\n\t  (class-name\n\t   ;; static method on on-demand import class.\n\t   `(lambda (&rest args)\n\t      (apply #'jmethod-static\n\t\t     (find-java-class ,class-name) ,method-name args))\n\t   )\n\t  (method-name\n\t   ;; basic instance method.\n\t   `(lambda (java-object &rest args)\n\t      (apply #'jmethod-instance java-object ,method-name args))\n\t   )\n\t  (t\n\t   ;; this case is not really possible since \"method-name\"\n\t   ;; will always be at least an empty string, which is not nil.\n\t   (warn \"Empty java method invocation! Generating empty function.\")\n\t   `(lambda (&rest args)\n\t      (declare (ignore args))\n\t      nil)\n\t   )\n\t  )\n\n    )\n  )\n\n(defun read-java-field-name (s)\n  \"Returns a string that names a java method\"\n  ;; I decided to implement my own reading function\n  ;; instead of using (read) with :PRESERVE on the readtable\n  ;; in order to follow more closely Java's syntax on method names.\n  (let ((ch (peek-char nil s t nil t))\n\t(i 0)\n\tbuf\n\tlast-dot\n\t)\n    (when (java-alpha-char-p ch)\n      (setq buf (make-array '(50) :element-type 'character\n\t\t\t    :adjustable t\n\t\t\t    :fill-pointer 0))\n      (do ((ch (read-char s t nil t) (read-char s t nil t))\n\t   )\n\t  ((not (or (java-alphanumericp ch) (char= ch #\\.))) (unread-char ch s))\n\t(vector-push-extend ch buf 50)\n\t(when (char= ch #\\.)\n\t  (setq last-dot i)\n\t  )\n\t(incf i)\n\t;;(print ch)\n\t)\n      (let (class-name field-name)\n\t(cond ((null last-dot)\n\t       ;; this is a basic instance field access.\n\t       (setq field-name buf class-name nil)\n\t       )\n\t      (t\n\t       ;; this is a static field access on a Java class name.\n\t       (setq field-name (subseq buf (1+ last-dot)))\n\t       (setq class-name (subseq buf 0 last-dot))\n\t       )\n\t      )\n\t(values buf class-name field-name)\n\t)\n      )\n    )\n  )\n\n(defun read-java-field-get (stream sub-char num-arg)\n  (declare (ignore num-arg sub-char))\n  (multiple-value-bind (whole class-name field-name)\n\t(read-java-field-name stream)\n    (declare  (ignore whole))\n    (cond (class-name\n\t   (cond ((string= field-name \"class\")\n\t\t  `(java-class-real (find-java-class ,class-name))\n\t\t  )\n\t\t (t\n\t\t  ;; static field on on-demand import class.\n\t\t  `(jfield-static ,class-name ,field-name)\n\t\t  )\n\t\t )\n\t   )\n\t  (field-name\n\t   ;; basic instance field.\n\t   `(lambda (java-object)\n\t      (jfield-instance java-object ,field-name))\n\t   )\n\t  (t\n\t   ;; this case is not really possible since \"field-name\"\n\t   ;; will always be at least an empty string, which is not nil.\n\t   (warn \"Empty java method invocation! Generating empty function.\")\n\t   `(lambda (&rest args)\n\t      (declare (ignore args))\n\t      nil)\n\t   )\n\t  )\n\n    )\n  )\n\n(defun read-java-field-set (stream sub-char num-arg)\n  (declare (ignore num-arg sub-char))\n  (multiple-value-bind (whole class-name field-name)\n      (read-java-field-name stream)\n    (declare  (ignore whole))\n    (cond (class-name\n\t   ;; static field on on-demand import class.\n\t   `(lambda (value)\n\t      (jfield-static-set ,class-name ,field-name value))\n\t   )\n\t  (field-name\n\t   ;; basic instance field.\n\t   `(lambda (java-object value)\n\t      (jfield-instance-set java-object ,field-name value))\n\t   )\n\t  (t\n\t   ;; this case is not really possible since \"field-name\"\n\t   ;; will always be at least an empty string, which is not nil.\n\t   (warn \"Empty java method invocation! Generating empty function.\")\n\t   `(lambda (&rest args)\n\t      (declare (ignore args))\n\t      nil)\n\t   )\n\t  )\n    )\n  )\n\n(defvar method-dispatch-macro-sub-character #\\_)\n(defvar field-getter-dispatch-macro-sub-character #\\?)\n(defvar field-setter-dispatch-macro-sub-character #\\!)\n\n(defun setup-readtable-for-cl+j (&optional (readtable *readtable*))\n  (flet ((setup-reader (sub-char reader)\n\t   (set-dispatch-macro-character #\\# sub-char reader readtable)\n\t   )\n\t )\n    (setup-reader method-dispatch-macro-sub-character #'read-java-method-call)\n    (setup-reader field-getter-dispatch-macro-sub-character #'read-java-field-get)\n    (setup-reader field-setter-dispatch-macro-sub-character #'read-java-field-set)\n    )\n  readtable\n  )\n\n(defvar +preserved-readtable+ nil)\n\n(unless +preserved-readtable+\n  (setq +preserved-readtable+ (copy-readtable *readtable*)))\n\n(defvar *cl+j-readtable* (setup-readtable-for-cl+j (copy-readtable)))\n\n#-ccl (setup-readtable-for-cl+j)\n\n#+ccl (let ((method-dispatch-macro-sub-character #\\]))\n\t;;(break \"about to setup readtable for CL+J.\")\n\t(setup-readtable-for-cl+j))\n\n#+ccl (defvar *cl+j-readtable-for-ccl* (copy-readtable))\n\n;;;\n;;;\n\n(defvar +java-init-lock+ (bt:make-lock \"CL+J initialization\"))\n(defvar +java-init-done+ nil)\n\n(defun java-init (&key force)\n  (declare (ignorable force))\n\n  (when +java-init-done+ (return-from java-init nil))\n\n  (bt:with-lock-held (+java-init-lock+)\n\n    (when +java-init-done+  ;; somebody else had beaten us to it.\n      (return-from java-init nil))\n\n    #+(and sbcl linux)\n    (unless (or force jni::sbcl-can-use-initial-thread)\n      (when (jni::in-initial-thread-p)\n\t(format t \"~%****~%In SBCL on Linux, the 'initial thread' cannot be used to~%initialize or interact with the Java virtual machine.~%As a work around you could call (spawn-repl) first!~%****~%\")\n\t(return-from java-init nil)\n\t)\n      )\n\n    (unless (java-vm-initialized) ;;jni::*jvm-foreign-ptr*\n      (load-libjvm)\n      (create-java-vm)\n      )\n    (init-java-primitive-types)\n    (init-java-basic-classes)\n    (init-java-exceptions)\n\n    (register-pending-java-natives)\n    (jtol-init)\n    (setq +java-init-done+ t)\n    )\n  )\n\n(defun java-destroy ()\n  (destroy-java-vm)\n  )\n\n\n\n;;;\n;;;\n\n\n(defun java-type-of (ref)\n  (with-pinned-values (ref)\n    (when (jref-p ref) (setq ref (jref-it ref)))\n    (with-jvm-local-frame (4)\n      (java-string-to-lisp\n       (jmethod-object-0\n\t(GetObjectClass ref)\n\t\"toString\" \"()Ljava/lang/String;\"))\n      )\n    )\n  )\n\n(export 'java-type-of)\n"
  },
  {
    "path": "third-party/cl+j-0.4/cl+j_pkg.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n(defpackage #:cl+j-boot\n  (:use #:cl #:asdf))\n(in-package #:cl+j-boot)\n\n\n#|\n;;; this is very SBCLish.\n#+sbcl\n(eval-when (:execute :compile-toplevel :load-toplevel)\n  (require 'cffi))\n#+clisp\n(eval-when (:execute :compile-toplevel :load-toplevel)\n  (asdf:oos 'asdf:load-op :cffi))\n|#\n\n(defun cffi-version ()\n  (asdf:component-version (asdf:find-system 'cffi)))\n\n(defpackage jni\n  (:use \"CL\" \"CFFI\")\n  (:export create-java-vm\n\t   jstring\n\t   ;;\n\t   new-lisp-reference\n\t   free-lisp-reference\n\t   lisp-reference-value\n\t   lisp-reference-eq\n\t   new-lref\n\t   free-lref\n\t   lref-val\n\t   lref-eq\n\t   ))\n\n(defpackage cl+j\n  (:use \"CL\" \"JNI\" \"CFFI\")\n  (:export java-init\n\t   java-destroy\n\t   jfield ;; ?? not really needed.\n\t   jmethod ;; ?? not really needed\n\t   jeq\n\t   jtrue\n\t   jtrue-p\n\t   jfalse\n\t   jfalse-p\n\t   jnull\n\t   jinstanceof\n\t   jref\n\t   weak-jref\n\t   new-local-ref\n\t   with-jvm-local-frame\n\t   jtol\n\t   add-jtol-mapping\n\t   delete-jtol-mapping\n\t   ltoj\n\t   jprim-boolean\n\t   jprim-char\n\t   jprim-byte\n\t   jprim-short\n\t   jprim-int\n\t   jprim-long\n\t   jprim-float\n\t   jprim-double\n\t   jstr\n\t   ;;jstring\n\t   jstring-length\n\t   jnew\n\t   jnew[]\n\t   jaref\n\t   jlength\n\t   *java-context*\n\t   in-java-context\n\t   make-java-context\n\t   delete-java-context\n\t   find-java-context\n\t   with-java-context\n\t   find-java-class\n\t   java-import\n\t   throw-java-exception\n\t   handle-java-exception ;; needed?\n\t   def-java-native\n\t   ;;register-pending-java-natives ;; needed?\n\t   ;;\n\t   new-lisp-reference ;; re-export\n\t   free-lisp-reference ;; re-export\n\t   lisp-reference-value ;; re-export\n\t   lisp-reference-eq  ;; re-export\n\t   new-lref\n\t   free-lref\n\t   lref-val\n\t   lref-eq\n\t   ))\n\n(in-package cl+j)\n\n(export 'jstring)\n\n\n#|\n\n(defun build ()\n  (compile-file \"vtable.lisp\")\n  (load \"vtable\" :verbose t)\n  (compile-file \"jni.lisp\")\n  (load \"jni\" :verbose t)\n  (compile-file \"cl+j.lisp\")\n  (load \"cl+j\" :verbose t)\n  )\n\n;(eval-when (:load-toplevel)\n;  (build))\n\n|#\n\n(delete-package '#:cl+j-boot)\n"
  },
  {
    "path": "third-party/cl+j-0.4/cl+swt.asd",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n;;#-(or sbcl cmu clisp mkcl ecl ccl scl)\n;; (error \"Sorry, CL+SWT 0.1 does not support this Lisp.\")\n\n(defsystem #:cl+swt\n  :description \"CL+SWT: A Common Lisp + SWT Interface.\"\n  :version \"0.1\"\n  :author \"Jean-Claude Beaudoin <jean.claude.beaudoin@gmail.com>\"\n  :licence \"MIT style license, Copyright (c) Jean-Claude Beaudoin 2010.\"\n  :components ((:file #-ccl \"swt_adapter\" #+ccl \"swt_adapter_for_ccl\")\n\t       )\n  :depends-on (#:cl+j)\n  )\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/cl_j/LispCondition.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\npackage cl_j;\n\npublic class LispCondition extends Throwable\n{\n    int condition; // a \"lisp reference\" bound to a Lisp Condition object.\n   \n    public LispCondition(int condi) {\n\tcondition = condi;\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/cl_j/RunInLisp.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\npackage cl_j;\n\npublic class RunInLisp implements Runnable\n{\n    int lisp_fun_ref; // a \"lisp reference\" bound to a Lisp functional object.\n   \n    public RunInLisp(int fun_ref) {\n\tlisp_fun_ref = fun_ref;\n    }\n\n    native int funcall(int fun_ref);\n    native void freeLispReference(int ref);\n\n    public void run() {\n\tfuncall(lisp_fun_ref);\n    }\n\n    protected void finalize() throws Throwable {\n\tSystem.out.println(\"RunInLisp finalized: \" + lisp_fun_ref);\n\tfreeLispReference(lisp_fun_ref);\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/cl_j/swt/LispListener.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\npackage cl_j.swt;\n\nimport org.eclipse.swt.widgets.*;\n\npublic class LispListener implements Listener\n{\n    int ldata; // a \"lisp reference\" bound to some Lisp data object.\n    int lcallback; // a \"lisp reference\" bound to \n   \n    public LispListener(int fun, int data) {\n\tlcallback = fun;\n\tldata = data;\n    }\n\n    public LispListener(int fun) {\n\tlcallback = fun;\n\tldata = 0;\n    }\n   \n    native void funcall(int fun, org.eclipse.swt.widgets.Event event, int data);\n    //native void funcall(int fun);\n    native void freeLispReference(int ref);\n\n    protected void finalize() throws Throwable {\n\tfreeLispReference(ldata);\n\tfreeLispReference(lcallback);\n    }\n\n    public void handleEvent(Event event)\n    {\n\tfuncall(lcallback, event, ldata);\n\t//funcall(lcallback);\n    }\n}\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/hello_swing_on_ccl.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n;(java-init) ;; if not already done.\n\n\n\n(in-java-context \"hello-swing\")\n\n(java-import \"javax.swing.*\")\n\n;(defvar a-frame)\n;(defvar a-label)\n\n(defun hello-swing ()\n  (with-java-context \"hello-swing\"\n    (let (a-frame a-label a-font)\n      (setq a-frame (jnew \"JFrame\" (jstring \"Hello brave Swing World\")))\n      (#]setDefaultCloseOperation a-frame #?WindowConstants.DISPOSE_ON_CLOSE)\n      (setq a-label (jnew \"JLabel\" (jstring \"Hello brave Swing World from Common Lisp!\")))\n      (setq a-font (jnew \"java.awt.Font\" (jstr \"Dialog\") #?java.awt.Font.BOLD 20))\n      (#]setFont a-label a-font)\n      (#]add (#]getContentPane a-frame) a-label)\n      (#]pack a-frame)\n      (#]setVisible a-frame jtrue)\n      )\n    )\n  )\n\n(hello-swing)\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/hello_swt.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n(java-init) ;; if not already done.\n\n\n\n(in-java-context \"hello-swt\")\n\n(java-import \"org.eclipse.swt.*\")\n(java-import \"org.eclipse.swt.widgets.*\")\n\n\n#|\n;; Version 1.  Event loop and application model creation all rolled into one function.\n(defun hello-swt ()\n  (with-java-context \"hello-swt\"\n    (let (swt-display shell hello-text)\n      (setq swt-display (jnew \"Display\"))\n      (setq shell (jnew \"Shell\" swt-display))\n      (setq hello-text (jnew \"Text\" shell #?SWT.CENTER))\n\n      (#]setText hello-text (jstr \"Hello SWT!\"))\n      (#]pack hello-text)\n      (#]pack shell)\n      (#]open shell)\n\n      (format t \"~%Entering the application event loop.~%\")\n      (do ()\n\t  ((jtrue-p (#]isDisposed shell)))\n\t(unless (jtrue-p (#]readAndDispatch swt-display))\n\t  ;;(format t \"About to sleep.~%\")\n\t  (#]sleep swt-display))\n\t)\n      (format t \"About to dispose of the whole thing!~%\")\n      (#]dispose swt-display)\n      )\n    )\n  )\n\n(defun say-hello-swt ()\n  (bt:make-thread #'hello-swt :name \"CL+J demos: Hello SWT!\"))\n\n(say-hello-swt)\n|#\n\n\n#|\n;; Version 2. Event loop and application model creation in seperate yet explicit functions.\n(defvar *swt-display* nil)\n\n(let (#|swt-display|# end-swt-event-loop)\n\n  (defun hello-swt ()\n    (with-java-context \"hello-swt\"\n      (let (shell hello-text)\n\t(unless *swt-display*\n\t  (setq *swt-display* (jnew \"Display\")))\n\t(setq shell (jnew \"Shell\" *swt-display*))\n\t(setq hello-text (jnew \"Text\" shell #?SWT.CENTER))\n\n\t(#]setText hello-text (jstr \"Hello SWT!\"))\n\t(#]pack hello-text)\n\t(#]pack shell)\n\t(#]open shell)\n\n\t)\n      )\n    )\n\n  (defun swt-event-loop ()\n    (format t \"~%Entering the application event loop.~%\")\n    (do ()\n\t(end-swt-event-loop)\n      (unless (jtrue-p (#]readAndDispatch *swt-display*))\n\t(#]sleep *swt-display*))\n      )\n    (format t \"About to dispose of the whole thing!~%\")\n    (#]dispose *swt-display*)\n    (setq *swt-display* nil end-swt-event-loop nil)\n    )\n\n  (defun stop-swt-event-loop ()\n    (setq end-swt-event-loop t)\n    (#]wake *swt-display*)\n    )\n\n  )\n\n\n(defun say-hello-swt ()\n  (if *swt-display*\n      (#]asyncExec *swt-display* (jnew \"cl_j.RunInLisp\" (new-lisp-reference #'hello-swt)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n(defun start-to-say-hello-to-swt ()\n  (bt:make-thread #'(lambda () (hello-swt) (swt-event-loop))\n\t\t  :name \n\t\t  ;;\"CL+J demos: Hello SWT!\" \n\t\t  '(:name \"CL+J demos: Hello SWT!\" :initial-bindings nil)\n\t\t  ))\n\n(start-to-say-hello-to-swt)\n|#\n\n;; Version 3. Event loop in a CL+J package. Only application model defined in a function here.\n(defun hello-swt ()\n  (with-java-context \"hello-swt\"\n    (let (shell hello-text)\n      (setq shell (jnew \"Shell\" (swt:display)))\n      (setq hello-text (jnew \"Text\" shell #?SWT.CENTER))\n\n      (#]setText shell (jstr \"CL+J Demos: Hello SWT!\"))\n      (#]setText hello-text (jstr \"Hello SWT!\"))\n      (#]pack hello-text)\n      (#]pack shell)\n      (#]open shell)\n      )\n    )\n  )\n\n(swt:start-swt-event-loop)\n\n(swt::wait-on-swt-event-loop-startup 10)\n\n(swt:In-UI-thread-do #'hello-swt)\n\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/EndOfReplException.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\n\npublic class EndOfReplException extends Exception\n{\n    public EndOfReplException(String msg) {\n\tsuper(msg);\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/EvalAbortedException.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\npublic class EvalAbortedException extends Exception\n{\n    public EvalAbortedException(String msg) {\n\tsuper(msg);\n    }\n\n    public EvalAbortedException() {\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/HelloLispWorld.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\nclass HelloLispWorld\n{\n    //int [    ][]   [   ]  a;\n    public static native void talkToLisp(String message);\n\n    public static void receiveMessageFromLisp(String lispMessage)\n    {\n\tSystem.out.println(\"Lisp said: \" + lispMessage);\n\ttalkToLisp(\"Greetings to the Lisp World!\");\n    }\n}\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/LispRef.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\nclass LispRef\n{\n    public static native int newRef(int ref);\n\n    public static native void freeRef(int ref);\n\n    // we should also offer some conversion operations\n    // for primitive types (such as boolean, char, byte,\n    // int, long, float, double, String).\n}\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/ReplFromJava.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\nclass ReplFromJava\n{\n    public static native int read();\n    public static native int eval(int ref) throws EndOfReplException,\n\t\t\t\t\t\t  EvalAbortedException;\n    public static native int print(int ref);\n    public static native void prompt(String prompt);\n\n    public static void repl()\n    {\n\tfor (;;)\n\t    {\n\t\tint valueRead = 0;\n\t\tint evalResult = 0;\n\t\tint printResult = 0;\n\n\t\t// Throwable foo = new cl_j.LispCondition(0);\n\n\t\ttry {\n\t\t    prompt(\"ReplFromJava> \");\n\t\t    valueRead = read();\n\t\t    evalResult = eval(valueRead);\n\t\t    printResult = print(evalResult);\n\t\t} catch (EndOfReplException ex) {\n\t\t    System.out.println(\"Lisp said: \" + ex.getMessage());\n\t\t    break;\n\t\t} catch (EvalAbortedException ex) {\n\t\t    prompt(\"; ReplFromJava: Evaluation aborted.\");\n\t\t} finally {\n\n\t\t    LispRef.freeRef(valueRead);\n\t\t    LispRef.freeRef(evalResult);\n\t\t    LispRef.freeRef(printResult);\n\t\t    // System.out.println(\"\\nDone finally.\");\n\t\t}\n\t    }\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/java_lisp_ref.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n\n(declaim (optimize safety debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n(def-java-native\n    \"LispRef\"\n  (\"int\" \"newRef\" ((\"int\" ref)) ()\n\t(new-lisp-reference (lisp-reference-value ref))\n\t)\n  (\"void\" \"freeRef\" ((\"int\" ref)) ()\n\t(free-lisp-reference ref)\n\t)\n;;   (\"boolean\" \"eqRefs\" ((\"\" ref1) (\"\" ref2)) ()\n;; \t     (if (lisp-reference-eq ref1 ref2)\n;; \t\t JNI_TRUE\n;; \t\t JNI_FALSE)\n;; \t     )\n  )\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/java_repl.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n(declaim (optimize safety debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n(java-import \"EndOfReplException\")\n(java-import \"ReplFromJava\")\n(java-import \"LispRef\")\n\n(defun quit-java-repl ()\n  (throw-java-exception \"EndOfReplException\" (jstring \"Goodbye!\"))\n  )\n\n\n(def-java-native\n    \"ReplFromJava\"\n  (\"int\" \"read\" () ()\n\t (new-lisp-reference (read)))\n  (\"int\" \"eval\" ((\"int\" ref)) ()\n\t ;;(break \"In eval native.\")\n\t (new-lisp-reference (eval (lisp-reference-value ref))))\n  (\"int\" \"print\" ((\"int\" ref)) ()\n\t ;;(break \"In print native.\")\n\t (new-lisp-reference \n\t  (prog1 (print (lisp-reference-value ref))\n\t    (finish-output))))\n  (\"void\" \"prompt\" ((\"String\" prompt)) ()\n\t  ;;(break \"In prompt native.\")\n\t  (format t \"~%~A\" (jtol prompt))\n\t  (finish-output)\n\t  )\n  )\n\n\n(load \"java_lisp_ref\")\n\n(java-init)\n\n(#]ReplFromJava.repl)\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/java_REPL/test_java_callback.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n;;\n;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n\n(declaim (optimize safety debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n\n\n(defcallback get-message-from-java \n    :void ((env :pointer) (this jobject) (msg jobject))\n  ;(declare (ignore env this))\n  (let ((*jenv-foreign-ptr* env))\n    (declare (special *jenv-foreign-ptr*))\n\n    (let ((message (cl+j::java-string-to-lisp msg)))\n      ;;(break)\n      (format t \"Java object ~S sent this message: ~S.~%\" this message)\n      (terpri)\n      )\n    )\n  )\n\n(defun register-my-native-method ()\n  (with-foreign-object (method-spec 'JNINativeMethod)\n    (setf (foreign-slot-value method-spec 'JNINativeMethod 'name)\n\t  \"talkToLisp\")\n    (setf (foreign-slot-value method-spec 'JNINativeMethod 'signature)\n\t  \"(Ljava/lang/String;)V\")\n    (setf (foreign-slot-value method-spec 'JNINativeMethod 'fnPtr)\n\t  (callback get-message-from-java))\n\n    (RegisterNatives (FindClass \"HelloLispWorld\") method-spec 1)\n    )\n  )\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/CCL/swt_buttons.lisp",
    "content": ";;;\n\n(in-java-context \"hello-swt\")\n\n(java-import \"org.eclipse.swt.*\")\n(java-import \"org.eclipse.swt.widgets.*\")\n(java-import \"org.eclipse.swt.layout.*\")\n\n\n(defun button1-pushed (event data)\n  (declare (ignore event data))\n  (format t \"~&Button 1 was pushed.~%\")\n  )\n\n(defun button2-pushed (event data)\n  (declare (ignore event data))\n  (format t \"~&Button 2 was pushed.~%\")\n  )\n\n(defun button3-pushed (event data)\n  (declare (ignore event data))\n  (format t \"~&Button 3 was pushed.~%\")\n  )\n\n\n(defun build-button-set ()\n  (with-java-context \"hello-swt\"\n    (let* ((shell (jnew \"Shell\" (swt:display)))\n\t   (fillLayout (jnew \"FillLayout\" #?SWT.VERTICAL))\n\t   (button1 (jnew \"Button\" shell #?SWT.PUSH))\n\t   (button2 (jnew \"Button\" shell #?SWT.PUSH))\n\t   (button3 (jnew \"Button\" shell #?SWT.PUSH))\n\t  )\n\n      (#]setText shell (jstr \"CL+J Demos: SWT button set\"))\n\n      (#]setLayout shell fillLayout)\n      (#]setText button1 (jstr \"button1\"))\n      (#]addListener button1\n\t\t     #?SWT.Selection (swt:make-listener #'button1-pushed))\n      (#]setText button2 (jstr \"button number 2\"))\n      (#]addListener button2\n\t\t     #?SWT.Selection (swt:make-listener #'button2-pushed))\n      (#]setText button3 (jstr \"3\"))\n      (#]addListener button3\n\t\t     #?SWT.Selection (swt:make-listener #'button3-pushed))\n      (#]pack shell)\n      (#]open shell)\n      )\n    )\n  )\n\n\n(swt:start-swt-event-loop)\n\n(swt::wait-on-swt-event-loop-startup 10)\n\n(swt:In-UI-thread-do #'build-button-set)\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/HelloSWT.java",
    "content": "\n//package com.swtjface.Ch2;\n\nimport org.eclipse.swt.*;\nimport org.eclipse.swt.widgets.*;\n\npublic class HelloSWT\n{\n    public static void main(String[] args)\n    {\n\tDisplay display = new Display();\n\tShell shell = new Shell(display);\n\t\n\tText helloText = new Text(shell, SWT.CENTER);\n\thelloText.setText(\"Hello SWT!\");\n\thelloText.pack();\n\n\tshell.pack();\n\tshell.open();\n\twhile (!shell.isDisposed())\n\t    {\n\t\tif (!display.readAndDispatch())\n\t\t    display.sleep();\n\t    }\n\tdisplay.dispose();\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/HelloSwing.java",
    "content": "\nimport javax.swing.*;\n\npublic class HelloSwing {\n\n    private static void sayHelloSwing () {\n\n\tJFrame frame = new JFrame(\"Hello Swing World\");\n\t//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\n\n\tJLabel label = new JLabel(\"Hello Swing World from Java!\");\n\n\tjava.awt.Font font = new java.awt.Font(\"Dialog\", java.awt.Font.BOLD, 20);\n\tlabel.setFont(font);\n\n\tframe.getContentPane().add(label);\n\n\tframe.pack();\n\tframe.setVisible(true);\n    }\n\n    public static void main(String[] args) {\n\tsayHelloSwing();\n    }\n}\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/Jabber.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n\n;; You have to adjust the Java classpath according to your need.\n(setq *jvm-options* \n      (append *jvm-options* \n\t      '(\"-Djava.class.path=.:/home/Jean-Claude/Jabberwocky/Jabberwocky.jar:/home/Jean-Claude/Projects/cl+j/cl_j.jar\")))\n\n(java-init) ;; must not have been called before \n\n(defvar *jabber-main-args* nil)\n\n(setq *jabber-main-args* (jnew[] \"String\" '(2)))\n\n(defvar arg0)\n(setq arg0 (jstring \"-i\"))\n\n(defvar arg1)\n(setq arg1 (jstring  \"/home/Jean-Claude/Jabberwocky\"))\n\n(setf (jaref *jabber-main-args* 0) arg0)\n(setf (jaref *jabber-main-args* 1) arg1)\n\n(jmethod \"IDE.Main\" \"main\" *jabber-main-args*)\n\n;;;\n;;; Beware that Jabberwocky calls System.exit() when it quits.\n;;;   That is quite a show stopper!\n;;;"
  },
  {
    "path": "third-party/cl+j-0.4/demos/hello_swing.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n;(java-init) ;; if not already done.\n\n\n\n(in-java-context \"hello-swing\")\n\n(java-import \"javax.swing.*\")\n\n;(defvar a-frame)\n;(defvar a-label)\n\n(defun hello-swing ()\n  (with-java-context \"hello-swing\"\n    (let (a-frame a-label a-font)\n      (setq a-frame (jnew \"JFrame\" (jstring \"Hello brave Swing World\")))\n      (#_setDefaultCloseOperation a-frame #?WindowConstants.DISPOSE_ON_CLOSE)\n      (setq a-label (jnew \"JLabel\" (jstring \"Hello brave Swing World from Common Lisp!\")))\n      (setq a-font (jnew \"java.awt.Font\" (jstr \"Dialog\") #?java.awt.Font.BOLD 20))\n      (#_setFont a-label a-font)\n      (#_add (#_getContentPane a-frame) a-label)\n      (#_pack a-frame)\n      (#_setVisible a-frame jtrue)\n      )\n    )\n  )\n\n(hello-swing)\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/hello_swt.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n(java-init) ;; if not already done.\n\n\n\n(in-java-context \"hello-swt\")\n\n(java-import \"org.eclipse.swt.*\")\n(java-import \"org.eclipse.swt.widgets.*\")\n\n\n#|\n;; Version 1.  Event loop and application model creation all rolled into one function.\n(defun hello-swt ()\n  (with-java-context \"hello-swt\"\n    (let (swt-display shell hello-text)\n      (setq swt-display (jnew \"Display\"))\n      (setq shell (jnew \"Shell\" swt-display))\n      (setq hello-text (jnew \"Text\" shell #?SWT.CENTER))\n\n      (#_setText hello-text (jstr \"Hello SWT!\"))\n      (#_pack hello-text)\n      (#_pack shell)\n      (#_open shell)\n\n      (format t \"~%Entering the application event loop.~%\")\n      (do ()\n\t  ((jtrue-p (#_isDisposed shell)))\n\t(unless (jtrue-p (#_readAndDispatch swt-display))\n\t  ;;(format t \"About to sleep.~%\")\n\t  (#_sleep swt-display))\n\t)\n      (format t \"About to dispose of the whole thing!~%\")\n      (#_dispose swt-display)\n      )\n    )\n  )\n\n(defun say-hello-swt ()\n  (bt:make-thread #'hello-swt :name \"CL+J demos: Hello SWT!\"))\n\n(say-hello-swt)\n|#\n\n\n#|\n;; Version 2. Event loop and application model creation in seperate yet explicit functions.\n(defvar *swt-display* nil)\n\n(let (#|swt-display|# end-swt-event-loop)\n\n  (defun hello-swt ()\n    (with-java-context \"hello-swt\"\n      (let (shell hello-text)\n\t(unless *swt-display*\n\t  (setq *swt-display* (jnew \"Display\")))\n\t(setq shell (jnew \"Shell\" *swt-display*))\n\t(setq hello-text (jnew \"Text\" shell #?SWT.CENTER))\n\n\t(#_setText hello-text (jstr \"Hello SWT!\"))\n\t(#_pack hello-text)\n\t(#_pack shell)\n\t(#_open shell)\n\n\t)\n      )\n    )\n\n  (defun swt-event-loop ()\n    (format t \"~%Entering the application event loop.~%\")\n    (do ()\n\t(end-swt-event-loop)\n      (unless (jtrue-p (#_readAndDispatch *swt-display*))\n\t(#_sleep *swt-display*))\n      )\n    (format t \"About to dispose of the whole thing!~%\")\n    (#_dispose *swt-display*)\n    (setq *swt-display* nil end-swt-event-loop nil)\n    )\n\n  (defun stop-swt-event-loop ()\n    (setq end-swt-event-loop t)\n    (#_wake *swt-display*)\n    )\n\n  )\n\n\n(defun say-hello-swt ()\n  (if *swt-display*\n      (#_asyncExec *swt-display* (jnew \"cl_j.RunInLisp\" (new-lisp-reference #'hello-swt)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n(defun start-to-say-hello-to-swt ()\n  (bt:make-thread #'(lambda () (hello-swt) (swt-event-loop))\n\t\t  :name \n\t\t  ;;\"CL+J demos: Hello SWT!\" \n\t\t  '(:name \"CL+J demos: Hello SWT!\" :initial-bindings nil)\n\t\t  ))\n\n(start-to-say-hello-to-swt)\n|#\n\n;; Version 3. Event loop in a CL+J package. Only application model defined in a function here.\n(defun hello-swt ()\n  (with-java-context \"hello-swt\"\n    (let (shell hello-text)\n      (setq shell (jnew \"Shell\" (swt:display)))\n      (setq hello-text (jnew \"Text\" shell #?SWT.CENTER))\n\n      (#_setText shell (jstr \"CL+J Demos: Hello SWT!\"))\n      (#_setText hello-text (jstr \"Hello SWT!\"))\n      (#_pack hello-text)\n      (#_pack shell)\n      (#_open shell)\n      )\n    )\n  )\n\n(swt:start-swt-event-loop)\n\n(swt::wait-on-swt-event-loop-startup 10)\n\n(swt:In-UI-thread-do #'hello-swt)\n\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/EndOfReplException.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\n\npublic class EndOfReplException extends Exception\n{\n    public EndOfReplException(String msg) {\n\tsuper(msg);\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/EvalAbortedException.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\npublic class EvalAbortedException extends Exception\n{\n    public EvalAbortedException(String msg) {\n\tsuper(msg);\n    }\n\n    public EvalAbortedException() {\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/HelloLispWorld.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\nclass HelloLispWorld\n{\n    //int [    ][]   [   ]  a;\n    public static native void talkToLisp(String message);\n\n    public static void receiveMessageFromLisp(String lispMessage)\n    {\n\tSystem.out.println(\"Lisp said: \" + lispMessage);\n\ttalkToLisp(\"Greetings to the Lisp World!\");\n    }\n}\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/LispRef.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\nclass LispRef\n{\n    public static native int newRef(int ref);\n\n    public static native void freeRef(int ref);\n\n    // we should also offer some conversion operations\n    // for primitive types (such as boolean, char, byte,\n    // int, long, float, double, String).\n}\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/ReplFromJava.java",
    "content": "//\n// Copyright (c) 2009, Jean-Claude Beaudoin\n// All rights reserved by the author.\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation\n// files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy,\n// modify, merge, publish, distribute, sublicense, and/or sell copies\n// of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n//\n\nclass ReplFromJava\n{\n    public static native int read();\n    public static native int eval(int ref) throws EndOfReplException,\n\t\t\t\t\t\t  EvalAbortedException;\n    public static native int print(int ref);\n    public static native void prompt(String prompt);\n\n    public static void repl()\n    {\n\tfor (;;)\n\t    {\n\t\tint valueRead = 0;\n\t\tint evalResult = 0;\n\t\tint printResult = 0;\n\n\t\t// Throwable foo = new cl_j.LispCondition(0);\n\n\t\ttry {\n\t\t    prompt(\"ReplFromJava> \");\n\t\t    valueRead = read();\n\t\t    evalResult = eval(valueRead);\n\t\t    printResult = print(evalResult);\n\t\t} catch (EndOfReplException ex) {\n\t\t    System.out.println(\"Lisp said: \" + ex.getMessage());\n\t\t    break;\n\t\t} catch (EvalAbortedException ex) {\n\t\t    prompt(\"; ReplFromJava: Evaluation aborted.\");\n\t\t} finally {\n\n\t\t    LispRef.freeRef(valueRead);\n\t\t    LispRef.freeRef(evalResult);\n\t\t    LispRef.freeRef(printResult);\n\t\t    // System.out.println(\"\\nDone finally.\");\n\t\t}\n\t    }\n    }\n}"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/java_lisp_ref.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n\n(declaim (optimize safety debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n(def-java-native\n    \"LispRef\"\n  (\"int\" \"newRef\" ((\"int\" ref)) ()\n\t(new-lisp-reference (lisp-reference-value ref))\n\t)\n  (\"void\" \"freeRef\" ((\"int\" ref)) ()\n\t(free-lisp-reference ref)\n\t)\n;;   (\"boolean\" \"eqRefs\" ((\"\" ref1) (\"\" ref2)) ()\n;; \t     (if (lisp-reference-eq ref1 ref2)\n;; \t\t JNI_TRUE\n;; \t\t JNI_FALSE)\n;; \t     )\n  )\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/java_repl.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n(declaim (optimize safety debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n(java-import \"EndOfReplException\")\n(java-import \"ReplFromJava\")\n(java-import \"LispRef\")\n\n(defun quit-java-repl ()\n  (throw-java-exception \"EndOfReplException\" (jstring \"Goodbye!\"))\n  )\n\n\n(def-java-native\n    \"ReplFromJava\"\n  (\"int\" \"read\" () ()\n\t (new-lisp-reference (read)))\n  (\"int\" \"eval\" ((\"int\" ref)) ()\n\t ;;(break \"In eval native.\")\n\t (new-lisp-reference (eval (lisp-reference-value ref))))\n  (\"int\" \"print\" ((\"int\" ref)) ()\n\t ;;(break \"In print native.\")\n\t (new-lisp-reference \n\t  (prog1 (print (lisp-reference-value ref))\n\t    (finish-output))))\n  (\"void\" \"prompt\" ((\"String\" prompt)) ()\n\t  ;;(break \"In prompt native.\")\n\t  (format t \"~%~A\" (jtol prompt))\n\t  (finish-output)\n\t  )\n  )\n\n\n(load \"java_lisp_ref\")\n\n(java-init)\n\n(#_ReplFromJava.repl)\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/java_REPL/test_java_callback.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n;;\n;;\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n\n(declaim (optimize safety debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n\n\n(defcallback get-message-from-java \n    :void ((env :pointer) (this jobject) (msg jobject))\n  ;(declare (ignore env this))\n  (let ((*jenv-foreign-ptr* env))\n    (declare (special *jenv-foreign-ptr*))\n\n    (let ((message (cl+j::java-string-to-lisp msg)))\n      ;;(break)\n      (format t \"Java object ~S sent this message: ~S.~%\" this message)\n      (terpri)\n      )\n    )\n  )\n\n(defun register-my-native-method ()\n  (with-foreign-object (method-spec 'JNINativeMethod)\n    (setf (foreign-slot-value method-spec 'JNINativeMethod 'name)\n\t  \"talkToLisp\")\n    (setf (foreign-slot-value method-spec 'JNINativeMethod 'signature)\n\t  \"(Ljava/lang/String;)V\")\n    (setf (foreign-slot-value method-spec 'JNINativeMethod 'fnPtr)\n\t  (callback get-message-from-java))\n\n    (RegisterNatives (FindClass \"HelloLispWorld\") method-spec 1)\n    )\n  )\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/jfli_example.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n\n(in-package :cl-user)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (unless (find (find-package :cl+j) (package-use-list *package*))\n    (use-package :cl+j)))\n\n;(java-init) ;; if not already done.\n\n(java-import \"java.util.*\")\n\n(defvar p (jnew \"Properties\"))\n\n(#_setProperty p (jstring \"fred\") (jstring \"ethel\"))\n(#_setProperty p (jstring \"ricky\") (jstring \"lucy\"))\n\n(defmacro doenum ((var enum) &body body)\n  (let ((genum (gensym)))\n    `(let ((,genum ,enum))\n       (do ()\n\t   ((jfalse-p (#_hasMoreElements ,genum)))\n\t (let ((,var (#_nextElement ,genum)))\n\t   ,@body)))))\n\n(doenum (prop (#_elements p))\n  (print (jtol (#_toString prop))))\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/demos/swt_buttons.lisp",
    "content": ";;;\n\n(in-java-context \"hello-swt\")\n\n(java-import \"org.eclipse.swt.*\")\n(java-import \"org.eclipse.swt.widgets.*\")\n(java-import \"org.eclipse.swt.layout.*\")\n\n\n(defun button1-pushed (event data)\n  (declare (ignore event data))\n  (format t \"~&Button 1 was pushed.~%\")\n  )\n\n(defun button2-pushed (event data)\n  (declare (ignore event data))\n  (format t \"~&Button 2 was pushed.~%\")\n  )\n\n(defun button3-pushed (event data)\n  (declare (ignore event data))\n  (format t \"~&Button 3 was pushed.~%\")\n  )\n\n\n(defun build-button-set ()\n  (with-java-context \"hello-swt\"\n    (let* ((shell (jnew \"Shell\" (swt:display)))\n\t   (fillLayout (jnew \"FillLayout\" #?SWT.VERTICAL))\n\t   (button1 (jnew \"Button\" shell #?SWT.PUSH))\n\t   (button2 (jnew \"Button\" shell #?SWT.PUSH))\n\t   (button3 (jnew \"Button\" shell #?SWT.PUSH))\n\t  )\n\n      (#_setText shell (jstr \"CL+J Demos: SWT button set\"))\n\n      (#_setLayout shell fillLayout)\n      (#_setText button1 (jstr \"button1\"))\n      (#_addListener button1\n\t\t     #?SWT.Selection (swt:make-listener #'button1-pushed))\n      (#_setText button2 (jstr \"button number 2\"))\n      (#_addListener button2\n\t\t     #?SWT.Selection (swt:make-listener #'button2-pushed))\n      (#_setText button3 (jstr \"3\"))\n      (#_addListener button3\n\t\t     #?SWT.Selection (swt:make-listener #'button3-pushed))\n      (#_pack shell)\n      (#_open shell)\n      )\n    )\n  )\n\n\n(swt:start-swt-event-loop)\n\n(swt::wait-on-swt-event-loop-startup 10)\n\n(swt:In-UI-thread-do #'build-button-set)\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/java_adapter.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n\n(in-package :cl+j)\n\n\n;(declaim (optimize debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n;;; Some standard callbacks.\n\n(def-java-native\n    \"cl_j.RunInLisp\"\n  (\"int\" \"funcall\" ((\"int\" fun-ref)) ()\n\t (new-lisp-reference (funcall (lisp-reference-value fun-ref))))\n  (\"void\" \"freeLispReference\" ((\"int\" ref)) ()\n\t  (free-lisp-reference ref))\n  )\n\n\n;;;\n"
  },
  {
    "path": "third-party/cl+j-0.4/java_callback.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n\n(in-package :cl+j)\n\n\n;(declaim (optimize debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n#|\n(defcallback get-message-from-java \n    :void ((env :pointer) (this jobject) (msg jobject))\n  ;(declare (ignore env this))\n  (let ((*jenv-foreign-ptr* env))\n    (declare (special *jenv-foreign-ptr*))\n\n    (let ((message (cl+j::java-string-to-lisp msg)))\n      ;;(break)\n      (format t \"Java object ~S sent this message: ~S.~%\" this message)\n      (terpri)\n      )\n    )\n  )\n\n(defun register-my-native-method ()\n  (with-foreign-object (method-spec (jni::struct-JNINativeMethod))\n    (setf (foreign-slot-value method-spec (jni::struct-JNINativeMethod) 'name)\n\t  \"talkToLisp\")\n    (setf (foreign-slot-value method-spec (jni::struct-JNINativeMethod) 'signature)\n\t  \"(Ljava/lang/String;)V\")\n    (setf (foreign-slot-value method-spec (jni::struct-JNINativeMethod) 'fnPtr)\n\t  (callback get-message-from-java))\n\n    (RegisterNatives (FindClass \"HelloLispWorld\") method-spec 1)\n    )\n  )\n|#\n\n;;;\n;;;\n;;;\n\n(defstruct java-native\n  name\n  class-name\n  return-type\n  arg-list\n  callback-function-pointer\n  )\n\n(defvar *registered-java-natives* nil)\n\n(defvar *pending-java-natives* nil)\n\n(defvar +java-natives-lock+ (bt:make-lock \"Java natives registration\"))\n\n;;;\n;;;\n\n(defmacro skip-spaces (buf head max)\n  `(do () ((>= ,head ,max))\n     (let ((ch (char ,buf ,head)))\n       (cond ((char= ch #\\space))\n\t     (t (return))))\n     (incf ,head)))\n\n(defun parse-java-class-or-array-type-name (java-type-name)\n  (let ((i 0)\n\t(len (length java-type-name)) \n\tbase-name rank)\n    (do ()\n\t((>= i len))\n      (let ((ch (char java-type-name  i)))\n\t(cond ((or (alphanumericp ch) (char= ch #\\.)))\n\t      (t (return)))\n\t)\n      (incf i)\n      )\n    (setq base-name (subseq java-type-name 0 i))\n    (cond ((>= i len) ;; this was not an array.\n\t   (return-from parse-java-class-or-array-type-name (values base-name nil))\n\t   )\n\t  (t\n\t   (skip-spaces java-type-name i len)\n\t   )\n\t  )\n    (cond ((>= i len) ;; this is still no more array.\n\t   (return-from parse-java-class-or-array-type-name (values base-name nil))\n\t   )\n\t  (t\n\t   (unless (char= #\\[ (char java-type-name i))\n\t     (error \"Invalid Java type name: ~S~%\" java-type-name))\n\t   (incf i)\n\t   (skip-spaces java-type-name i len)\n\t   (unless (and (< i len) (char= #\\] (char java-type-name i)))\n\t     (error \"Invalid Java type name: ~S~%\" java-type-name)\n\t     )\n\t   (setq rank 1)\n\t   (incf i)\n\t   )\n\t  )\n    (cond ((>= i len)\n\t   (return-from  parse-java-class-or-array-type-name (values base-name rank))\n\t   )\n\t  (t\n\t   (do ()\n\t       ((>= i len))\n\t     (let ((ch (char java-type-name i)))\n\t       (cond ((char= ch #\\space))\n\t\t     (t\n\t\t      (unless (char= #\\[ ch)\n\t\t\t(error \"Invalid Java type name: ~S~%\" java-type-name))\n\t\t      (incf i)\n\t\t      (skip-spaces java-type-name i len)\n\t\t      (unless (and (< i len) (char= #\\] (char java-type-name i)))\n\t\t\t(error \"Invalid Java type name: ~S~%\" java-type-name)\n\t\t\t)\n\t\t      (incf rank)\n\t\t      )\n\t\t     )\n\t       )\n\t     (incf i)\n\t     )\n\t   \n\t   )\n\t  )\n    (values base-name rank)\n    )\n  )\n\n(defun map-java-type-to-descriptor (java-type-name)\n  (cond ((string= \"void\" java-type-name)\n\t \"V\"\n\t )\n\t((string= \"int\" java-type-name)\n\t \"I\"\n\t )\n\t((string= \"boolean\" java-type-name)\n\t \"Z\"\n\t )\n\t((string= \"byte\" java-type-name)\n\t \"B\"\n\t )\n\t((string= \"char\" java-type-name)\n\t \"C\"\n\t )\n\t((string= \"short\" java-type-name)\n\t \"S\"\n\t )\n\t((string= \"long\" java-type-name)\n\t \"J\"\n\t )\n\t((string= \"float\" java-type-name)\n\t \"F\"\n\t )\n\t((string= \"double\" java-type-name)\n\t \"D\"\n\t )\n\t(t ;; Here we got either an object reference or an array.\n\t (multiple-value-bind (base-type-name rank)\n\t     (parse-java-class-or-array-type-name java-type-name)\n\t   (cond (rank ;; this is an array type\n\t\t  (let ((base-descriptor (map-java-type-to-descriptor base-type-name))\n\t\t\t(descriptor-prefix \"\"))\n\t\t    (dotimes (i rank)\n\t\t      (setq descriptor-prefix (str+ descriptor-prefix \"[\"))\n\t\t      )\n\t\t    (str+ descriptor-prefix base-descriptor)\n\t\t    )\n\t\t  )\n\t\t (t ;; this is a reference type\n\t\t  (setq base-type-name \n\t\t\t(java-class-name\n\t\t\t (find-java-class base-type-name)))\n\t\t  (str+ \"L\" base-type-name \";\")\n\t\t  )\n\t\t )\n\t   )\n\t )\n\t)\n  )\n\n(defun build-sig-for-callback (jreturn-type-name args)\n  ;(PushLocalFrame 2)\n  (let* ((args-sig \"\")\n\t return-descriptor\n\t )\n    (setq jreturn-type-name (string-trim '(#\\space) jreturn-type-name))\n    (setq return-descriptor (map-java-type-to-descriptor jreturn-type-name))\n\n    (dolist (arg-spec args)\n      (let ((arg-jtype (car arg-spec)))\n\t(setq arg-jtype (string-trim '(#\\space) arg-jtype))\n\n\t(setq args-sig (str+ args-sig (map-java-type-to-descriptor arg-jtype)))\n\t)\n      )\n\n    (str+ \"(\" args-sig \")\" return-descriptor)\n    ;(PopLocalFrame (null-pointer))\n    )\n  )\n\n(defun map-to-cffi-type (java-type)\n  (when (stringp java-type)\n    (let ((java-type-name (string-trim '(#\\space) java-type)))\n      (cond ((string= \"void\" java-type-name)\n\t     :void\n\t     )\n\t    ((string= \"int\" java-type-name)\n\t     'jint\n\t     )\n\t    ((string= \"boolean\" java-type-name)\n\t     'jboolean\n\t     )\n\t    ((string= \"byte\" java-type-name)\n\t     'jbyte\n\t     )\n\t    ((string= \"char\" java-type-name) ;; Shouldn't it be \"unsigned short\" instead?\n\t     'jchar\n\t     )\n\t    ((string= \"short\" java-type-name)\n\t     'jshort\n\t     )\n\t    ((string= \"long\" java-type-name)\n\t     'jlong\n\t     )\n\t    ((string= \"float\" java-type-name)\n\t     'jfloat\n\t     )\n\t    ((string= \"double\" java-type-name)\n\t     'jdouble\n\t     )\n\t    ((string= \"\" java-type-name)\n\t     (error \"Missing Java type specification.\"))\n\t    (t ;; Here we got either an object reference or an array.\n\t     (multiple-value-bind (base-name rank)\n\t\t (parse-java-class-or-array-type-name java-type-name)\n\t       (declare (ignore base-name rank))\n\t       'jobject\n\t       )\n\t     )\n\t    )\n      )\n    )\n  )\n\n(defmacro with-condition-handler (&body body)\n  `(handler-case (progn ,@body)\n     (java-throwable (condi)\n       (let ((j-throwable (java-throwable-thrown condi)))\n\t ;;(break \"About to throw Java exception from Lisp.\")\n\t (JNI_Throw (jref-it j-throwable))\n\t ;; Do not do any other JNI calls between this point \n\t ;; and the imminent return to Java, else the JVM will blow up!\n\t )\n       )\n     (jni::thrown-from-java (condi)\n       (let ((this (jni::thrown-from-java-this condi)))\n\t (JNI_Throw this) ;; it was just passing by.\n\t ;; Do not do any other JNI calls between this point \n\t ;; and the imminent return to Java, else the JVM will blow up!\n\t )\n       )\n     (condition (condi)\n       (let ((j-wrapper \n\t      (jnew \"cl_j/LispCondition\"\n\t\t    ;;(java-class-real (find-java-class \"cl_j.LispCondition\"))\n\t\t    (new-lisp-reference condi))))\n\t ;;(break \"About to throw wrapped lisp condition!\")\n\t (JNI_Throw (jref-it j-wrapper))\n\t ;; Do not do any other JNI calls between this point \n\t ;; and the imminent return to Java, else the JVM will blow up!\n\t )\n       )\n     )\n  )\n\n(defmacro def-java-native (java-class-name &body method-defs)\n;;   (when (stringp java-class)\n;;     (setq java-class (find-java-class java-class))\n;;     )\n  (let (callbacks)\n    (bt:with-lock-held (+java-natives-lock+)\n      (dolist (method-def method-defs)\n\t(destructuring-bind \n\t      (return-type method-name \n\t\t\t   (&rest args)\n\t\t\t   (&key (this nil this-p) \n\t\t\t\t (jclass nil jclass-p)\n\t\t\t\t (super nil super-p))\n\t\t\t   &body body)\n\t    method-def\n\t  (declare (ignorable jclass super jclass-p super-p))\n\t  (let (cffi-return \n\t\t(cffi-args (list '(this jobject) '(env :pointer))))\n\t    ;; The argument names \"this\" and \"env\" can too easily clash! Fix it!\n\t    (setq cffi-return (map-to-cffi-type return-type))\n\t    (dolist (arg args (setq cffi-args (nreverse cffi-args)))\n\t      (destructuring-bind (arg-type arg-name) arg\n\t\t(push (list arg-name (map-to-cffi-type arg-type)) cffi-args)\n\t\t)\n\t      )\n\t    (push\n\t     (let ((callback-name \n\t\t    (intern (str+ \"Java_\" java-class-name \"_\" method-name))))\n\t       `(progn\n\t\t  (cffi:defcallback\n\t\t      ,callback-name ,cffi-return ,cffi-args\n\t\t      ,@(cond (this-p\n\t\t\t       `((let ((,this this))\n\t\t\t\t   (let ((jni::*jenv-foreign-ptr* env))\n\t\t\t\t     (declare (special jni::*jenv-foreign-ptr*))\n\t\t\t\t     (with-condition-handler\n\t\t\t\t\t ,@body\n\t\t\t\t       )\n\t\t\t\t     )\n\t\t\t\t   ))\n\t\t\t       )\n\t\t\t      (t\n\t\t\t       `((declare (ignore this))\n\t\t\t\t (let ((jni::*jenv-foreign-ptr* env))\n\t\t\t\t   (declare (special jni::*jenv-foreign-ptr*))\n\t\t\t\t   (with-condition-handler\n\t\t\t\t       ,@body\n\t\t\t\t     )\n\t\t\t\t   )\n\t\t\t\t )\n\t\t\t       )\n\t\t\t      )\n\t\t\t\t    )\n\t\t  (eval-when (:load-toplevel :execute)\n\t\t    (let ((native-method\n\t\t\t   (make-java-native\n\t\t\t    :name ,method-name\n\t\t\t    :class-name ,java-class-name\n\t\t\t    :return-type ,return-type\n\t\t\t    :arg-list ',args\n\t\t\t    :callback-function-pointer (callback ,callback-name))))\n\t\t      (if +java-init-done+\n\t\t\t  (progn\n\t\t\t    ;;(break \"About to register native.\")\n\t\t\t    (register-java-native native-method)\n\t\t\t    (push native-method *registered-java-natives*))\n\t\t\t  (push native-method *pending-java-natives*)))\n\t\t    )\n\t\t  t\n\t\t  )\n\t       )\n\t     callbacks\n\t     )\n\t    )\n\t  )\n\t)\n      )\n    (cons 'progn (nreverse callbacks))\n    )\n  )\n\n(defun register-java-native (this-one)\n  (with-foreign-object (method-spec (jni::struct-JNINativeMethod))\n    (setf (foreign-slot-value method-spec (jni::struct-JNINativeMethod) 'jni::name)\n\t  (java-native-name this-one))\n    (setf (foreign-slot-value method-spec (jni::struct-JNINativeMethod) 'jni::signature)\n\t  (build-sig-for-callback (java-native-return-type this-one)\n\t\t\t\t  (java-native-arg-list this-one)))\n    (setf (foreign-slot-value method-spec (jni::struct-JNINativeMethod) 'jni::fnPtr)\n\t  (java-native-callback-function-pointer this-one))\n    (let ((real-class (java-class-real \n\t\t       (find-java-class (java-native-class-name this-one)))))\n      (RegisterNatives real-class method-spec 1)\n      )\n    )\n  )\n\n(defun register-pending-java-natives ()\n  (bt:with-lock-held (+java-natives-lock+)\n    (let ((pending (reverse *pending-java-natives*)))\n      (do ()\n\t  ((endp pending))\n\t(let ((this-one (car pending)))\n\t  (register-java-native this-one)\n\t  (push this-one *registered-java-natives*)\n\t  (pop pending))\n\t)\n      (setq *pending-java-natives* (reverse pending)) ;; what??? is that for unwind-protect?\n      )\n    )\n  )\n\n\n\n;;;\n;;\n;;\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/jni.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009,2017, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n;;(asdf:component-version (asdf::find-system :cffi))\n\n(in-package :jni)\n\n;(declaim (optimize debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n(defconstant JNI_VERSION_1_1 #X10001)\n(defconstant JNI_VERSION_1_2 #X10002)\n(defconstant JNI_VERSION_1_3 #X10003)\n(defconstant JNI_VERSION_1_4 #X10004)\n(defconstant JNI_VERSION_1_5 #X10005)\n(defconstant JNI_VERSION_1_6 #X10006)\n(defconstant JNI_VERSION_1_8 #X10008)\n\n(export '(JNI_VERSION_1_1 JNI_VERSION_1_2 JNI_VERSION_1_3 \n\t  JNI_VERSION_1_4 JNI_VERSION_1_5 JNI_VERSION_1_6\n          JNI_VERSION_1_8))\n\n(defparameter *jni-version* JNI_VERSION_1_6)\n\n;;;\n;;;\n;;;\n\n#|\n(eval-when (:compile-toplevel :load-toplevel)\n  (defun :mkcl-version->= (major &optional minor patch)\n    (let* ((si-pkg (find-package :SI))\n\t   (version-sym (and si-pkg (find-symbol \"+MKCL-VERSION-NUMBER+\" si-pkg)))\n\t   (req-version-num (+ (* 1000000 major) (if minor (* 10000 minor) 0) (if patch patch 0))))\n      (if version-sym\n\t  (let ((version-num (symbol-value version-sym)))\n\t    (if (>= version-num req-version-num) '(:and) '(:or)))\n\t'(:or))\n      )\n    )\n  )\n|#\n\n(defmacro mem-aptr! (ptr type &optional (index 0))\n  (if (uiop:version< (cffi-version) \"0.11.0\")\n      `(mem-aref ,ptr ,type ,index)\n    `(mem-aptr ,ptr ,type ,index)))\n(export 'mem-aptr!)\n\n\n#+mkcl\n(defmacro mkcl-getenv (env-var-name)\n  `(,(or (find-symbol \"GETENV\" \"SI\") (find-symbol \"GETENV\" \"MKCL\")) ,env-var-name))\n\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n\n  #+cmu19 (defcfun (\"getenv\" posix-getenv) :string (name :string))\n\n  #+scl (defun posix-getenv (var)\n\t  (cdr (assoc var ext:*environment-list* :test #'string=)))\n\n  (defvar *libjvm-path*)\n\n  (setq *libjvm-path*\n    (let ((*jvm-path*-sym (find-symbol \"*JVM-PATH*\" :cl-user))\n\t  (*jre-home*-sym (find-symbol \"*JRE-HOME*\" :cl-user))\n\t  )\n      (cond ((and *jvm-path*-sym (boundp *jvm-path*-sym))\n\t     (symbol-value *jvm-path*-sym))\n\t    ((and *jre-home*-sym (boundp *jre-home*-sym))\n\t     (concatenate 'string\n\t\t\t  (symbol-value *jre-home*-sym)\n\n\t\t\t  #+(and unix x86)\n\t\t\t  \"/lib/i386/client/libjvm.so\"\n\t\t\t  #+(and unix x86-64)\n\t\t\t  \"/lib/amd64/server/libjvm.so\"\n\t\t\t  #+windows\n\t\t\t  \"/bin/client/jvm.dll\"))\n\t    (t\n\t     (concatenate 'string \n\t\t\t  (or\n\t\t\t   #+sbcl (SB-EXT:POSIX-GETENV \"JRE_HOME\")\n\t\t\t   #+clisp (EXT:GETENV \"JRE_HOME\")\n\t\t\t   #+cmu19 (posix-getenv \"JRE_HOME\")\n\t\t\t   #+scl (posix-getenv \"JRE_HOME\")\n\t\t\t   #+ecl (si:getenv \"JRE_HOME\")\n\t\t\t   ;;#+(and mkcl #.(mkcl-version->= 1 1)) (mk-ext:getenv \"JRE_HOME\")\n\t\t\t   ;;#+(and mkcl (not #.(mkcl-version->= 1 1))) (si:getenv \"JRE_HOME\")\n\t\t\t   #+mkcl (mkcl-getenv \"JRE_HOME\")\n\t\t\t   #+ccl (ccl:getenv \"JRE_HOME\")\n\t\t\t   \"/usr/lib/jvm/jre\")\n\t\t\t  \n\t\t\t  #+(and unix x86)\n\t\t\t  \"/lib/i386/client/libjvm.so\"\n\t\t\t  #+(and unix x86-64)\n\t\t\t  \"/lib/amd64/server/libjvm.so\"\n\t\t\t  #+windows\n\t\t\t  \"/bin/client/jvm.dll\"))\n\t    )\n      )\n    )\n  (let ((*jre-version*-sym (find-symbol \"*JRE-VERSION*\" :cl-user)))\n    (when (and *jre-version*-sym (boundp *jre-version*-sym))\n      (setq *jni-version* (symbol-value *jre-version*-sym))\n      )\n    )\n  ) ;; eval-when\n\n\n;;;\n\n;(define-foreign-library libpthread (t (:default \"/lib/libpthread-2.5\")))\n\n;(use-foreign-library libpthread)\n\n;;;\n\n(defvar *libjvm-loaded* nil)\n\n;;#+cmu19 (define-foreign-library libjvm (t *libjvm-path*))\n\n;;#+cmu19 (use-foreign-library libjvm)\n\n#+cmu19 (load-foreign-library *libjvm-path*)\n\n(defun load-libjvm (&key force)\n  (when *libjvm-loaded*\n    #+cmu19 (return-from load-libjvm) ;; cmucl 19 does not suffer reloading gladly...\n\n    #-cmu19 \n    (if force\n\t(warn \"Reloading Java JVM library. This may not work properly on some platforms.\")\n\t(return-from load-libjvm))\n    )\n  #-cmu19\n  (handler-case\n      (load-foreign-library *libjvm-path*)\n    #+(and windows x86)\n    (cffi:load-foreign-library-error (load-error)\n      (let ((msvcr71.dll (merge-pathnames \"../msvcr71.dll\" *libjvm-path*)))\n\t(cond ((probe-file msvcr71.dll) ;; this happens with Java 1.6 JRE on Win32.\n\t       (load-foreign-library msvcr71.dll)\n\t       (load-foreign-library *libjvm-path*))\n\t      (t (signal load-error))))))\n  (setq *libjvm-loaded* t)\n  )\n\n(export 'load-libjvm)\n\n#+clisp (load-libjvm)\n#+cmu19 (load-libjvm)\n#+scl (load-libjvm)\n#+sbcl (load-libjvm)\n#+ecl (load-libjvm) ;;??\n#+mkcl (load-libjvm) ;;??\n#+ccl (load-libjvm)\n\n;;; The following 3 types are said to be Linux dependent.\n(defctype jint :int)\n(defctype jlong :long-long)\n(defctype jbyte :char) ;; should be a :signed-char if it existed.\n\n(defctype jboolean :unsigned-char)\n(defctype jchar :unsigned-short)\n(defctype jshort :short)\n(defctype jfloat :float)\n(defctype jdouble :double)\n\n(export '(jint jlong jbyte jboolean jchar jshort jfloat jdouble))\n\n(defctype jboolean* :pointer) ;; a \"jboolean *\"\n(defctype jbyte* :pointer) ;; a \"jbyte *\"\n(defctype jchar* :pointer) ;; a \"jchar *\"\n(defctype jshort* :pointer) ;; a \"jshort *\"\n(defctype jint* :pointer) ;; a \"jint *\"\n(defctype jlong* :pointer) ;; a \"jlong *\"\n(defctype jfloat* :pointer) ;; a \"jfloat *\"\n(defctype jdouble* :pointer) ;; a \"jdouble *\"\n\n(export '(jboolean* jbyte* jchar* jshort* jint* jlong* jfloat* jdouble*))\n\n;(defctype jsize jint)\n(defctype jsize :int)\n\n(defctype jobject :pointer)  ;; opaque type\n\n(defctype jclass jobject)\n(defctype jthrowable jobject)\n(defctype jweak jobject)\n(defctype jstring jobject)\n(defctype jarray jobject)\n\n(defctype jbooleanArray jarray)\n(defctype jbyteArray jarray)\n(defctype jcharArray jarray)\n(defctype jshortArray jarray)\n(defctype jintArray jarray)\n(defctype jlongArray jarray)\n(defctype jfloatArray jarray)\n(defctype jdoubleArray jarray)\n(defctype jobjectArray jarray)\n\n(export '(jobject jsize jclass jthrowable jweak jstring jarray\n\t  jbooleanArray jbyteArray jcharArray jshortArray\n\t  jintArray jlongArray jfloatArray jdoubleArray\n\t  jobjectArray))\n\n\n(cffi:defcunion jvalue\n\t(z jboolean)\n\t(b jbyte)\n\t(c jchar)\n\t(s jshort)\n\t(i jint)\n\t(j jlong)\n\t(f jfloat)\n\t(d jdouble)\n\t(l jobject))\n\n;;(defctype jvalue jvalue) ;; typedef ;; commented out for cffi 0.18.0\n;;(defctype jvalue (:union jvalue)) ;; typedef ;; commented out for cffi 0.18.0\n(defmacro union-jvalue ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'jvalue `'(:union jvalue)))\n\n(defctype jvalue* :pointer) ;; A \"jvalue *\", usually the base of an argument vector.\n\n(defctype jfieldID :pointer) ;; opaque type\n(defctype jmethodID :pointer) ;; opaque type\n\n(export '(jvalue z b c s i j f d l union-jvalue jvalue* jfieldID jmethodID))\n\n\n;;; new in JDK 1.6\n\n;; typedef enum _jobjectType {\n;;      JNIInvalidRefType    = 0,\n;;      JNILocalRefType      = 1,\n;;      JNIGlobalRefType     = 2,\n;;      JNIWeakGlobalRefType = 3 \n;; } jobjectRefType;\n\n(defctype jobjectRefType :int)\n\n(defconstant JNIInvalidRefType 0)\n(defconstant JNILocalRefType 1)\n(defconstant JNIGlobalRefType 2)\n(defconstant JNIWeakGlobalRefType 3)\n\n(defconstant JNI_OK 0)\n\n(defconstant JNI_NULL 'JNI_NULL)\n\n(defconstant JNI_FALSE 0)\n(defconstant JNI_TRUE 1)\n\n(export '(JNI_OK JNI_NULL JNI_FALSE JNI_TRUE))\n\n(defconstant JNI_ERR -1)\n(defconstant JNI_EDETACHED -2)\n(defconstant JNI_EVERSION -3)\n(defconstant JNI_ENOMEM -4)\n(defconstant JNI_EEXIST -5)\n(defconstant JNI_EINVAL -6)\n(defconstant JNI_COMMIT 1)\n(defconstant JNI_ABORT 2)\n\n(defcstruct JNINativeMethod\n  (name :string)\n  (signature :string)\n  (fnPtr :pointer))\n\n;(defctype JNINativeMethod (:struct JNINativeMethod)) ;; cffi 0.18.0\n(defmacro struct-JNINativeMethod ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'JNINativeMethod `'(:struct JNINativeMethod)))\n\n(export '(JNINativeMethod struct-JNINativeMethod))\n\n(defctype JNINativeMethod* :pointer)\n\n;;;\n;;; In C, JavaVM and JNIEnv are just a pointer\n;;; to a fake C++ vtable.\n(defctype JavaVM :pointer) ;; \"struct JNIInvokeInterface *\"\n(defctype JNIEnv :pointer) ;; \"struct JNINativeInterface *\"\n\n(defctype JavaVM* :pointer) ;; \"JavaVM *\"\n(defctype JNIEnv* :pointer) ;; \"JNIEnv *\"\n\n;;;\n;;;\n;;; Here comes the JNIEnv vtable definition\n;;;\n\n\n(defvtable JNINativeInterface_ (jenv) (:export-all t)\n  reserved0\n  reserved1\n  reserved2\n\n  reserved3\n  (GetVersion jint ())\n\n  (DefineClass jclass ((name :string) (loader jobject) (buf :pointer) (len jsize))\n    :not-ok JNI_NULL\n    :throws (ClassFormatError \n\t     NoClassDefFoundError \n\t     ClassCircularityError\n\t     OutOfMemoryError))\n  (FindClass jclass ((name :string))\n\t     :not-ok JNI_NULL\n\t     :throws (ClassFormatError \n\t\t      NoClassDefFoundError \n\t\t      ClassCircularityError\n\t\t      OutOfMemoryError\n\t\t      ExceptionInInitializerError))\n\n  (FromReflectedMethod jmethodID ((method jobject))\n\t\t       :not-ok JNI_NULL\n\t\t       :throws OutOfMemoryError)\n  (FromReflectedField jfieldID ((field jobject))\n\t\t      :not-ok JNI_NULL\n\t\t      :throws OutOfMemoryError)\n\n  (ToReflectedMethod \n   jobject ((cls jclass) (methodID jmethodID) (isStatic jboolean))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n\n  (GetSuperClass jclass ((sub jclass)))\n  (IsAssignableFrom jboolean ((sub jclass) (sup jclass)))\n\n  (ToReflectedField \n   jobject ((cls jclass) (fieldID jfieldID) (isStatic jboolean))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n\n  ;; The following two (JNI_Throw and JNI_ThrowNew) are dangerous\n  ;; little things to use.  Once you have called any of them\n  ;; you have to run to the closest return to Java point and\n  ;; not call most of the other JNI functions on your way,\n  ;; otherwise you run the risk of seeing the JVM blow up right under you.\n  ;; Please read the fine print in the JNI specification about\n  ;; pending exceptions and (the few) JNI functions safe to call is such situation.\n  (JNI_Throw jint ((obj jthrowable)) :ok JNI_OK)\n  (JNI_ThrowNew jint ((clazz jclass) (msg :string)) :ok JNI_OK)\n\n  ;; The recommendation above about reading the fine print\n  ;; on pending exceptions applies also to the following three.\n  (ExceptionOccurred jthrowable ())\n  (ExceptionDescribe :void ())\n  (ExceptionClear :void ())\n\n  (FatalError :void ((msg :string)))\n\n  (PushLocalFrame jint ((capacity jint)) :ok JNI_OK :throws OutOfMemoryError)\n  (PopLocalFrame jobject ((result jobject)))\n\n  (NewGlobalRef jobject ((lobj jobject)))\n  (DeleteGlobalRef :void ((gref jobject)))\n  (DeleteLocalRef :void ((obj jobject)))\n  (IsSameObject jboolean ((obj1 jobject) (obj2 jobject)))\n  (NewLocalRef jobject ((ref jobject)))\n  (EnsureLocalCapacity jint ((capacity jint))\n\t\t       :throws OutOfMemoryError :ok JNI_OK)\n\n  (AllocObject jobject ((clazz jclass))\n\t       :not-ok JNI_NULL\n\t       :throws (InstantiationException OutOfMemoryError))\n  NewObject ;; stub\n  NewObjectV ;; stub\n  (NewObjectA \n   jobject ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :not-ok JNI_NULL\n;;   :throws (InstantiationException OutOfMemoryError Exception))\n   :throws (InstantiationException OutOfMemoryError)\n   :throws-any-exception t)\n\n  (GetObjectClass jclass ((obj jobject)))\n  (IsInstanceOf jboolean ((obj jobject) (clazz jclass)))\n\n  (GetMethodID jmethodID ((clazz jclass) (name :string) (sig :string))\n\t       :not-ok JNI_NULL\n\t       :throws (NoSuchMethodError\n\t\t\tExceptionInInitializerError\n\t\t\tOutOfMemoryError))\n  \n  CallObjectMethod ;; stub\n  CallObjectMethodV ;; stub\n  (CallObjectMethodA \n   jobject ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallBooleanMethod ;; stub\n  CallBooleanMethodV ;; stub\n  (CallBooleanMethodA \n   jBoolean ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallByteMethod ;; stub\n  CallByteMethodV ;; stub\n  (CallByteMethodA \n   jbyte ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallCharMethod ;; stub\n  CallCharMethodV ;; stub\n  (CallCharMethodA \n   jchar ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallShortMethod ;; stub\n  CallShortMethodV ;; stub\n  (CallShortMethodA \n   jshort ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallIntMethod ;; stub\n  CallIntMethodV ;; stub\n  (CallIntMethodA \n   jint ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallLongMethod ;; stub\n  CallLongMethodV ;; stub\n  (CallLongMethodA \n   jlong ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallFloatMethod ;; stub\n  CallFloatMethodV ;; stub\n  (CallFloatMethodA \n   jfloat ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallDoubleMethod ;; stub\n  CallDoubleMethodV ;; stub\n  (CallDoubleMethodA \n   jdouble ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallVoidMethod ;; stub\n  CallVoidMethodV ;; stub\n  (CallVoidMethodA \n   :void ((obj jobject) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualObjectMethod ;; stub\n  CallNonvirtualObjectMethodV ;; stub\n  (CallNonvirtualObjectMethodA \n   jobject \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualBooleanMethod ;; stub\n  CallNonvirtualBooleanMethodV ;; stub\n  (CallNonvirtualBooleanMethodA \n   jboolean \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualByteMethod ;; stub\n  CallNonvirtualByteMethodV ;; stub\n  (CallNonvirtualByteMethodA \n   jbyte \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualCharMethod ;; stub\n  CallNonvirtualCharMethodV ;; stub\n  (CallNonvirtualCharMethodA \n   jchar \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualShortMethod ;; stub\n  CallNonvirtualShortMethodV ;; stub\n  (CallNonvirtualShortMethodA \n   jshort \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualIntMethod ;; stub\n  CallNonvirtualIntMethodV ;; stub\n  (CallNonvirtualIntMethodA \n   jint \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualLongMethod ;; stub\n  CallNonvirtualLongMethodV ;; stub\n  (CallNonvirtualLongMethodA \n   jlong \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualFloatMethod ;; stub\n  CallNonvirtualFloatMethodV ;; stub\n  (CallNonvirtualFloatMethodA \n   jfloat \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualDoubleMethod ;; stub\n  CallNonvirtualDoubleMethodV ;; stub\n  (CallNonvirtualDoubleMethodA \n   jdouble \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallNonvirtualVoidMethod ;; stub\n  CallNonvirtualVoidMethodV ;; stub\n  (CallNonvirtualVoidMethodA \n   :void \n   ((obj jobject) (clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  (GetFieldID jfieldID ((clazz jclass) (name :string) (sig :string))\n\t      :not-ok JNI_NULL\n\t      :throws (NoSuchFieldError ExceptionInInitializerError OutOfMemoryError))\n\n  (GetObjectField jobject ((obj jobject) (fieldID jfieldID)))\n  (GetBooleanField jboolean ((obj jobject) (fieldID jfieldID)))\n  (GetByteField jbyte ((obj jobject) (fieldID jfieldID)))\n  (GetCharField jchar ((obj jobject) (fieldID jfieldID)))\n  (GetShortField jshort ((obj jobject) (fieldID jfieldID)))\n  (GetIntField jint ((obj jobject) (fieldID jfieldID)))\n  (GetLongField jlong ((obj jobject) (fieldID jfieldID)))\n  (GetFloatField jfloat ((obj jobject) (fieldID jfieldID)))\n  (GetDoubleField jdouble ((obj jobject) (fieldID jfieldID)))\n\n  (SetObjectField \n   :void ((obj jobject) (fieldID jfieldID) (val jobject)))\n  (SetBooleanField\n   :void ((obj jobject) (fieldID jfieldID) (val jboolean)))\n  (SetByteField\n   :void ((obj jobject) (fieldID jfieldID) (val jbyte)))\n  (SetCharField\n   :void ((obj jobject) (fieldID jfieldID) (val jchar)))\n  (SetShortField\n   :void ((obj jobject) (fieldID jfieldID) (val jshort)))\n  (SetIntField\n   :void ((obj jobject) (fieldID jfieldID) (val jint)))\n  (SetLongField\n   :void ((obj jobject) (fieldID jfieldID) (val jlong)))\n  (SetFloatField\n   :void ((obj jobject) (fieldID jfieldID) (val jfloat)))\n  (SetDoubleField\n   :void ((obj jobject) (fieldID jfieldID) (val jdouble)))\n\n  (GetStaticMethodID\n   jmethodID ((clazz jclass) (name :string) (sig :string))\n   :not-ok JNI_NULL\n   :throws (NoSuchMethodError ExceptionInInitializerError OutOfMemoryError))\n  \n  CallStaticObjectMethod ;; stub\n  CallStaticObjectMethodV ;; stub\n  (CallStaticObjectMethodA \n   jobject \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticBooleanMethod ;; stub\n  CallStaticBooleanMethodV ;; stub\n  (CallStaticBooleanMethodA \n   jboolean \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticByteMethod ;; stub\n  CallStaticByteMethodV ;; stub\n  (CallStaticByteMethodA \n   jbyte \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticCharMethod ;; stub\n  CallStaticCharMethodV ;; stub\n  (CallStaticCharMethodA \n   jchar \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticShortMethod ;; stub\n  CallStaticShortMethodV ;; stub\n  (CallStaticShortMethodA \n   jshort \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticIntMethod ;; stub\n  CallStaticIntMethodV ;; stub\n  (CallStaticIntMethodA \n   jint \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticLongMethod ;; stub\n  CallStaticLongMethodV ;; stub\n  (CallStaticLongMethodA \n   jlong \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticFloatMethod ;; stub\n  CallStaticFloatMethodV ;; stub\n  (CallStaticFloatMethodA \n   jfloat \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticDoubleMethod ;; stub\n  CallStaticDoubleMethodV ;; stub\n  (CallStaticDoubleMethodA \n   jdouble \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  CallStaticVoidMethod ;; stub\n  CallStaticVoidMethodV ;; stub\n  (CallStaticVoidMethodA \n   :void \n   ((clazz jclass) (methodID jmethodID) (args jvalue*))\n   :throws-any-exception t)\n  \n  (GetStaticFieldID jfieldID ((clazz jclass) (name :string) (sig :string))\n\t\t    :not-ok JNI_NULL\n\t\t    :throws (NoSuchFieldError\n\t\t\t     ExceptionInInitializerError\n\t\t\t     OutOfMemoryError))\n\n  (GetStaticObjectField jobject ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticBooleanField jboolean ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticByteField jbyte ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticCharField jchar ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticShortField jshort ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticIntField jint ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticLongField jlong ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticFloatField jfloat ((clazz jclass) (fieldID jfieldID)))\n  (GetStaticDoubleField jdouble ((clazz jclass) (fieldID jfieldID)))\n\n  (SetStaticObjectField \n   :void ((clazz jclass) (fieldID jfieldID) (val jobject)))\n  (SetStaticBooleanField\n   :void ((clazz jclass) (fieldID jfieldID) (val jboolean)))\n  (SetStaticByteField\n   :void ((clazz jclass) (fieldID jfieldID) (val jbyte)))\n  (SetStaticCharField\n   :void ((clazz jclass) (fieldID jfieldID) (val jchar)))\n  (SetStaticShortField\n   :void ((clazz jclass) (fieldID jfieldID) (val jshort)))\n  (SetStaticIntField\n   :void ((clazz jclass) (fieldID jfieldID) (val jint)))\n  (SetStaticLongField\n   :void ((clazz jclass) (fieldID jfieldID) (val jlong)))\n  (SetStaticFloatField\n   :void ((clazz jclass) (fieldID jfieldID) (val jfloat)))\n  (SetStaticDoubleField\n   :void ((clazz jclass) (fieldID jfieldID) (val jdouble)))\n\n  (NewString jstring ((unicode :string) (len jsize))\n\t     :not-ok JNI_NULL :throws OutOfMemoryError)\n  (GetStringLength jsize ((str jstring)))\n  (GetStringChars jchar* ((str jstring) (isCopy jboolean*))\n\t\t  :not-ok JNI_NULL :throws OutOfMemoryError)\n  (ReleaseStringChars :void ((str jstring) (chars jchar*)))\n\n  (NewStringUTF jstring ((utf :string)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (GetStringUTFLength jsize ((str jstring)))\n  (GetStringUTFChars :string ((str jstring) (isCopy jboolean*))\n\t\t     :not-ok JNI_NULL\n\t\t     :throws OutOfMemoryError)\n  (ReleaseStringUTFChars :void ((str jstring) (chars :string)))\n\n  (GetArrayLength jsize ((array jarray)))\n\n  (NewObjectArray \n   jobjectArray ((len jsize) (clazz jclass) (init jobject))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetObjectArrayElement\n   jobject ((array jobjectArray) (index jsize))\n   ;; :not-ok JNI_NULL  ;; why is this not said in the spec?\n   :throws ArrayIndexOutOfBoundsException)\n  (SetObjectArrayElement\n   :void ((array jobjectArray) (index jsize) (val jobject))\n   :throws (ArrayIndexOutOfBoundsException ArrayStoreException))\n\n  (NewBooleanArray jbooleanArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewByteArray jbyteArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewCharArray jcharArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewShortArray jshortArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewIntArray jintArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewLongArray jlongArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewFloatArray jfloatArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n  (NewDoubleArray jdoubleArray ((len jsize)) :not-ok JNI_NULL :throws OutOfMemoryError)\n\n  (GetBooleanArrayElements \n   jboolean* ((array jbooleanArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetByteArrayElements\n   jbyte* ((array jbyteArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetCharArrayElements\n   jchar* ((array jcharArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetShortArrayElements\n   jshort* ((array jshortArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetIntArrayElements\n   jint* ((array jintArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetLongArrayElements\n   jlong* ((array jlongArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetFloatArrayElements\n   jfloat* ((array jfloatArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (GetDoubleArrayElements\n   jdouble* ((array jdoubleArray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n\n  (ReleaseBooleanArrayElements \n   :void ((array jbooleanArray) (elems jboolean*) (mode jint)))\n  (ReleaseByteArrayElements\n   :void ((array jbyteArray) (elems jbyte*) (mode jint)))\n  (ReleaseCharArrayElements\n   :void ((array jcharArray) (elems jchar*) (mode jint)))\n  (ReleaseShortArrayElements\n   :void ((array jshortArray) (elems jshort*) (mode jint)))\n  (ReleaseIntArrayElements\n   :void ((array jintArray) (elems jint*) (mode jint)))\n  (ReleaseLongArrayElements\n   :void ((array jlongArray) (elems jlong*) (mode jint)))\n  (ReleaseFloatArrayElements\n   :void ((array jfloatArray) (elems jfloat*) (mode jint)))\n  (ReleaseDoubleArrayElements\n   :void ((array jdoubleArray) (elems jdouble*) (mode jint)))\n\n  (GetBooleanArrayRegion \n   :void ((array jbooleanArray) (start jsize) (len jsize) (buf jboolean*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetByteArrayRegion\n   :void ((array jbyteArray) (start jsize) (len jsize) (buf jbyte*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetCharArrayRegion\n   :void ((array jcharArray) (start jsize) (len jsize) (buf jchar*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetShortArrayRegion\n   :void ((array jshortArray) (start jsize) (len jsize) (buf jshort*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetIntArrayRegion\n   :void ((array jintArray) (start jsize) (len jsize) (buf jint*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetLongArrayRegion\n   :void ((array jlongArray) (start jsize) (len jsize) (buf jlong*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetFloatArrayRegion\n   :void ((array jfloatArray) (start jsize) (len jsize) (buf jfloat*))\n   :throws ArrayIndexOutOfBoundsException)\n  (GetDoubleArrayRegion\n   :void ((array jdoubleArray) (start jsize) (len jsize) (buf jdouble*))\n   :throws ArrayIndexOutOfBoundsException)\n\n  (SetBooleanArrayRegion \n   :void ((array jbooleanArray) (start jsize) (len jsize) (buf jboolean*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetByteArrayRegion\n   :void ((array jbyteArray) (start jsize) (len jsize) (buf jbyte*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetCharArrayRegion\n   :void ((array jcharArray) (start jsize) (len jsize) (buf jchar*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetShortArrayRegion\n   :void ((array jshortArray) (start jsize) (len jsize) (buf jshort*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetIntArrayRegion\n   :void ((array jintArray) (start jsize) (len jsize) (buf jint*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetLongArrayRegion\n   :void ((array jlongArray) (start jsize) (len jsize) (buf jlong*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetFloatArrayRegion\n   :void ((array jfloatArray) (start jsize) (len jsize) (buf jfloat*))\n   :throws ArrayIndexOutOfBoundsException)\n  (SetDoubleArrayRegion\n   :void ((array jdoubleArray) (start jsize) (len jsize) (buf jdouble*))\n   :throws ArrayIndexOutOfBoundsException)\n\n  (RegisterNatives \n   jint ((clazz jclass) (methods JNINativeMethod*) (nMethods jint))\n   :throws NoSuchMethodError :ok JNI_OK)\n  (UnregisterNatives jint ((clazz jclass)) :ok JNI_OK)\n\n  (MonitorEnter jint ((obj jobject)) :throws OutOfMemoryError :ok JNI_OK)\n  (MonitorExit jint ((obj jobject)) :ok JNI_OK\n\t       :throws (OutOfMemoryError IllegalMonitorStateException))\n\n  (GetJavaVM jint ((vm :pointer)) ;; vm is in fact a \"JavaVM **\"\n\t     :ok JNI_OK :throws-any-exception t)\n\n  (GetStringRegion \n   :void ((str jstring) (start jsize) (len jsize) (buf jchar*))\n   :throws StringIndexOutOfBoundsException)\n  (GetStringUTFRegion \n   :void ((str jstring) (start jsize) (len jsize) (buf :string))\n   :throws StringIndexOutOfBoundsException)\n\n  (GetPrimitiveArrayCritical\n   :pointer ((array jarray) (isCopy jboolean*))\n   :not-ok JNI_NULL\n   :throws OutOfMemoryError)\n  (ReleasePrimitiveArrayCritical\n   :void ((array jarray) (carray :pointer) (mode jint)))\n\n  (GetStringCritical\n   jchar* ((string jstring) (isCopy jboolean*))\n   :not-ok JNI_NULL  :throws OutOfMemoryError)\n  (ReleaseStringCritical\n   :void ((string jstring) (cstring jchar*)))\n\n  (NewWeakGlobalRef jweak ((obj jobject)) :throws OutOfMemoryError)\n  (DeleteWeakGlobalRef :void ((ref jweak)))\n\n  (ExceptionCheck jboolean ()) ;; since at least JDK 1.2\n\n  (NewDirectByteBuffer jobject ((address :pointer) (capacity jlong)) ;; new in JDK 1.4\n\t\t       :not-ok JNI_NULL :throws OutOfMemoryError)\n  (GetDirectBufferAddress :pointer ((buf jobject)) :not-ok JNI_NULL) ;; new in JDK 1.4\n  (GetDirectBufferCapacity jlong ((buf jobject)) :not-ok JNI_ERR) ;; new in JDK 1.4\n\n  (GetObjectRefType jobjectRefType ((obj jobject))) ;; new in JDK 1.6\n  )\n\n;;;\n;;;\n;;;  Invocation interface\n;;;\n\n\n(defcstruct JavaVMOption\n  (optionString :string)\n  (extraInfo :pointer)) ;; \"void *\"\n\n;;(defctype JavaVMOption JavaVMOption) ; cffi 0.18.0\n;;(defctype JavaVMOption (:struct JavaVMOption)) ; cffi 0.18.0\n(defmacro struct-JavaVMOption ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'JavaVMOption `'(:struct JavaVMOption)))\n\n\n(defcstruct JavaVMInitArgs\n  (version jint)\n  (nOptions jint)\n  (options :pointer) ;; \"JavaVMOption *\"\n  (ignoreUnrecognized jboolean))\n\n;;(defctype JavaVMInitArgs JavaVMInitArgs) ; cffi 0.18.0\n;;(defctype JavaVMInitArgs (:struct JavaVMInitArgs)) ; cffi 0.18.0\n(defmacro struct-JavaVMInitArgs ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'JavaVMInitArgs `'(:struct JavaVMInitArgs)))\n\n\n(defcstruct JavaVMAttachArgs\n  (version jint)\n  (name :string)\n  (group jobject))\n\n;;(defctype JavaVMAttachArgs JavaVMAttachArgs) ; cffi 0.18.0\n;;(defctype JavaVMAttachArgs (:struct JavaVMAttachArgs)) ; cffi 0.18.0\n(defmacro struct-JavaVMAttachArgs ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'JavaVMAttachArgs `'(:struct JavaVMAttachArgs)))\n\n\n\n(defcstruct JDK1_1InitArgs\n  (version jint)\n  (properties :pointer)\n  (checkSource jint)\n  (nativeStackSize jint)\n  (javaStackSize jint)\n  (minHeapSize jint)\n  (maxHeapSize jint)\n  (verifyMode jint)\n  (classpath :string)\n  (vprintf :pointer) ;; function pointer \"jint (*)(File *, const char *, va_list)\"\n  (exit :pointer) ;; function pointer \"void (*)(jint)\"\n  (abort :pointer) ;; function pointer \"void (*)(void)\"\n  (enableClassGC jint)\n  (enableVerboseGC jint)\n  (disableAsyncGC jint)\n  (verbose jint)\n  (debugging jboolean)\n  (debugPort jint)\n  )\n\n;;(defctype JDK1_1InitArgs JDK1_1InitArgs) ;; typedef of above struct ;; cffi 0.18.0\n;;(defctype JDK1_1InitArgs (:struct JDK1_1InitArgs)) ;; typedef of above struct ;; cffi 0.18.0\n(defmacro struct-JDK1_1InitArgs ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'JDK1_1InitArgs `'(:struct JDK1_1InitArgs)))\n\n\n;;;\n;;; Here we declare the JavaVM vtable\n;;;\n\n;;; This is bound to a \"foreign pointer\" to a \"JavaVM\"\n(defvar *jvm-foreign-ptr* nil)\n\n(defmacro java-vm-initialized ()\n  `*jvm-foreign-ptr*)\n\n(export 'java-vm-initialized)\n\n(defun jvm ()\n  (declare (special *jvm-foreign-ptr*)) ;; not really needed but confirms intent.\n  ;(mem-ref *jvm-foreign-ptr* 'JavaVM*)\n  (or *jvm-foreign-ptr*\n      (error \"~%*** Java VM is not initialized! ***~%\"))\n  )\n\n(defvar *jenv-foreign-ptr* nil) ;; We need one *jenv* per thread!\n;; And it should be associated with the proper *jvm*.\n\n(defvar *JNIEnv-registry* nil)\n;; Since this global a-list is not thread-protected we could\n;; end up loosing some of the conses pushed on it. But since\n;; this is only some kind of a cache the only damage from this\n;; is some minor loss of performance. And we would loose even\n;; more is we had to acquire a lock.\n\n(defun register-current-thread (penv)\n  (setq *JNIEnv-registry* \n\t(acons #+sbcl sb-thread:*current-thread*\n\t       #+clisp t\n\t       #+cmu19 t\n\t       #+scl thread:*thread*\n               #+ecl mp:*current-process*\n               #+mkcl mp:*thread*\n\t       #+ccl ccl:*current-process*\n\t       penv\n\t       *JNIEnv-registry*))\n  )\n\n#|\n(defun cleanup-dead-threads ()\n  ;; Beware of race conditions!\n  ;; The ice is really thin in here.\n  ;;(sb-thread:thread-alive-p (caar JNI::*JNIEnv-registry*))\n  )\n\n;;(sb-ext:unschedule-timer (car (sb-ext:list-all-timers)))\n\n#+(and sbcl (not win32))\n(defconstant dead-thread-cleaner-name \n  (if (boundp 'dead-thread-cleaner-name)\n      dead-thread-cleaner-name\n    \"CL+J dead thread cleanup\"))\n\n#+(and sbcl (not win32))\n(do ((timer (find dead-thread-cleaner-name (sb-ext:list-all-timers)\n\t\t  :test #'string= :key #'sb-ext:timer-name)\n\t    (find dead-thread-cleaner-name (sb-ext:list-all-timers)\n\t\t  :test #'string= :key #'sb-ext:timer-name)))\n    ((null timer))\n    (sb-ext:unschedule-timer timer))\n\n#+(and sbcl (not win32))\n(sb-ext:schedule-timer (sb-ext:make-timer \n\t\t\t#'cleanup-dead-threads \n\t\t\t:name dead-thread-cleaner-name\n\t\t\t:thread t) \n\t\t       120 :repeat-interval 120)\n|#\n\n(defun promote-current-thread (cell)\n  (unless (eq cell (car *JNIEnv-registry*))\n    ;;(setq *JNIEnv-registry* (cons cell *JNIEnv-registry*))\n    (push cell *JNIEnv-registry*)\n    (do ((regis *JNIEnv-registry* (cdr regis)))\n\t((null (cdr regis)))\n      (when (eq cell (cadr regis))\n\t(rplacd regis (cddr regis))\n\t(return)\n\t)\n      )\n    )\n  )\n\n(define-condition jni-error (simple-error) ()\n#|\n  (:report (lambda (condition stream)\n\t     (format stream \"~%Inside JNI-ERROR reporter.~%\")\n\t     (apply #'format stream\n\t\t    (simple-condition-format-control condition)\n\t\t    (simple-condition-format-arguments condition))))\n|#\n  )\n\n(defmethod print-object :after ((condition jni-error) stream)\n  (format stream \" \")\n  (apply #'format stream\n\t (simple-condition-format-control condition)\n\t (simple-condition-format-arguments condition))\n  )\n\n#+(and sbcl linux)\n(progn\n  (defvar sbcl-can-use-initial-thread nil)\n\n  (defun in-initial-thread-p ()\n    (string= \"initial thread\" (sb-thread:thread-name sb-thread:*current-thread*))\n    )\n\n  (defmacro guard-against-initial-thread ()\n    `(unless sbcl-can-use-initial-thread\n       (when (in-initial-thread-p)\n\t (error 'jni-error :format-control \"In SBCL on Linux, the 'initial thread' cannot be attached to a Java Virtual Machine!\")\n\t )\n       )\n    )\n  )\n\n(defun jenv ()\n  (declare (special *jenv-foreign-ptr*)) ;; not really needed but confirms intent.\n  (cond ((null *jenv-foreign-ptr*)\n\t ;; global level value\n\t (let* ((cell (assoc #+sbcl sb-thread:*current-thread* \n\t\t\t     #+clisp t\n\t\t\t     #+cmu19 t\n\t\t\t     #+scl thread:*thread*\n\t\t\t     #+ecl mp:*current-process*\n\t\t\t     #+mkcl mp:*thread*\n\t\t\t     #+ccl ccl:*current-process*\n\t\t\t     *JNIEnv-registry*))\n\t\t(env (cdr cell)))\n\t   (unless env\n\t     (let* ((penv (foreign-alloc 'JNIEnv*))\n\t\t    (result (GetEnv penv JNI_VERSION_1_4)))\n\t       (cond ((= JNI_OK result)\n\t\t       ;; we don't believe getenv.\n\t\t      #+(and sbcl linux) (guard-against-initial-thread)\n\t\t      (AttachCurrentThread penv (null-pointer))\n\t\t      ;;(register-current-thread penv)\n\t\t      (setq env (mem-ref penv 'JNIEnv*))\n\t\t      (register-current-thread env)\n\t\t      )\n\t\t     ((= JNI_EDETACHED result)\n\t\t      #+(and sbcl linux) (guard-against-initial-thread)\n\t\t      (AttachCurrentThread penv (null-pointer))\n\t\t      ;;(register-current-thread penv)\n\t\t      (setq env (mem-ref penv 'JNIEnv*))\n\t\t      (register-current-thread env)\n\t\t      )\n\t\t     ((= JNI_EVERSION result)\n\t\t      (foreign-free penv)\n\t\t      (error \"JVM complained of an incompatible version number: ~S.~%\"\n\t\t\t     JNI_VERSION_1_4)\n\t\t      )\n\t\t     (t\n\t\t      (error \"GetEnv unknown result = ~S.\" result))\n\t\t     )\n\t       (foreign-free penv)\n\t       )\n\t     )\n\t   (when cell\n\t     (promote-current-thread cell))\n\t   ;;(mem-ref penv 'JNIEnv*)\n\t   env\n\t   )\n\t )\n\t(t\n\t ;; if we are here it is because somebody\n\t ;; has bound *jenv-foreign-ptr* somewhere above\n\t ;; on the stack.\n\t ;;(mem-ref *jenv-foreign-ptr* 'JNIEnv*)\n\t ;;(break \"In (jenv).\")\n\t *jenv-foreign-ptr*\n\t )\n\t)\n  )\n\n(defun handle-JNI_EDETACHED (result)\n  (declare (ignore result))\n  )\n\n(defun handle-JNI_EVERSION (result)\n  (declare (ignore result))\n  )\n\n(defvtable JNIInvokeInterface_ (jvm) ()\n  reserved0 ;; (reserved0 :pointer)\n  reserved1 ;; (reserved1 :pointer)\n  reserved2 ;; (reserved2 :pointer)\n  ;;(DestroyJavaVM jint () :ok JNI_OK :export t)\n  (DestroyJavaVM jint () :ok JNI_OK)\n  (AttachCurrentThread jint ((penv :pointer) (args :pointer)) :ok JNI_OK)\n  (DetachCurrentThread jint () :ok JNI_OK)\n  (GetEnv jint ((penv :pointer) (version jint))\n\t  :ok JNI_OK :error ((JNI_EDETACHED #'handle-JNI_EDETACHED)\n\t\t\t     (JNI_EVERSION #'handle-JNI_EVERSION)))\n#|\n  (GetEnv jint ((penv :pointer) (version jint))\n\t  :ok JNI_OK)\n|#\n  (AttachCurrentThreadDaemon jint ((penv :pointer) (args :pointer)) :ok JNI_OK)\n  )\n\n;;(export 'DestroyJavaVM)\n\n(defvar +java-vm-destroyed+ nil)\n\n(defun java-vm-destroyed ()\n  +java-vm-destroyed+)\n\n(defun destroy-java-vm ()\n  (setq +java-vm-destroyed+ t)\n  (handler-case (DestroyJavaVM)\n    (simple-error (condition)\n      (declare (ignorable condition))\n      (error \"Java Virtual Machine destruction reported a failure!~%  condition = ~S~%\"\n\t     condition)))\n;;  (unless (= JNI_OK (DestroyJavaVM))\n;;    (error \"Java Virtual Machine destruction reported a failure!\"))\n  (setq *jvm-foreign-ptr* nil)\n  )\n\n(export '(java-vm-destroyed destroy-java-vm))\n\n\n#|\n(defcstruct JavaVM_vtable\n  (reserved0 :pointer)\n  (reserved1 :pointer)\n  (reserved2 :pointer)\n  (DestroyJavaVM :pointer)\n  (AttachCurrentThread :pointer)\n  (DetachCurrentThread :pointer)\n  (GetEnv :pointer)\n  (AttachCurrentThreadDaemon :pointer)\n  )\n\n(defmacro struct-JavaVM_vtable ()\n  (if (uiop:version< (cffi-version) \"0.11.0\") `'JavaVM_vtable `'(:struct JavaVM_vtable)))\n\n\n\n(defun DestroyJavaVM (vm)\n  (let ((func-ptr ;;(JavaVM_vtable-DestroyJavaVM *jvm*)))\n\t (foreign-slot-value (mem-ref (jvm) :pointer)\n\t\t\t     (struct-JavaVM_vtable) 'DestroyJavaVM)))\n    (let ((result (foreign-funcall func-ptr JavaVM* vm jint)))\n      (unless (= JNI_OK result)\n\t(error \"DestroyJavaVM failed. result = ~S.\" result))\n      (debug-progn\n\t(format nil \"DestroyJavaVM: result = ~S.\" result))\n      result\n      )\n    )\n  )\n\n|#\n\n\n;;; args is in fact a \"JDK1_1InitArgs *\"\n(defcfun (\"JNI_GetDefaultJavaVMInitArgs\" JNI_GetDefaultJavaVMInitArgs)\n    jint (args :pointer))\n;; returns 0 if requested version is supported, otherwise returns a negative number.\n\n(defcfun (\"JNI_CreateJavaVM\" JNI_CreateJavaVM)\n    jint\n  (pvm :pointer)   ;; \"JavaVM **\"\n  (penv :pointer)  ;; \"void **\"\n  (args :pointer)) ;; \"void *\"\n\n\n;;(defvar *jvm-options*)\n\n(defun get-jvm-options ()\n;;  (setq *jvm-options*\n\t(let ((*jvm-options*-sym (find-symbol \"*JVM-OPTIONS*\" :cl-user))\n\t      )\n\t  (cond ((and *jvm-options*-sym (boundp *jvm-options*-sym))\n\t\t (symbol-value *jvm-options*-sym))\n\t\t(t\n\t\t #+(or sbcl clisp mkcl ecl ccl scl)\n\t\t '(\"-Djava.class.path=.\")\n\t\t #+cmu19\n\t\t '(\"-Djava.class.path=.\" \"-Xmx32M\")\n\t\t )\n\t\t)\n\t  )\n;;\t)\n  )\n\n\n(defvar *jvm-exit-hook* nil)\n\n(defcallback jvm-exit-handler :void ((exit-code jint))\n  (debug-progn\n    (format t \"Inside jvm-exit-handler! exit-code = ~D~%\" exit-code)\n    (finish-output))\n\n  (dolist (handler (reverse *jvm-exit-hook*)) ;; any condition handling here?\n    (funcall handler exit-code))              ;;  a handler could be buggy...\n  ;;(break \"Inside jvm-exit-handler!~%\")\n\n  (debug-progn\n    (format t \"  Ran *jvm-exit-hook*!~%~%\")\n    (finish-output)\n    (finish-output) ;; I really mean it!\n    )\n  )\n\n(defvar *jvm-abort-hook* nil)\n\n(defcallback jvm-abort-handler :void ()\n  (debug-progn\n    (format t \"Inside jvm-abort-handler!~%\")\n    (finish-output))\n\n  (dolist (handler (reverse *jvm-abort-hook*))\n    (funcall handler))\n  ;;(break \"Inside jvm-abort-handler.\")\n\n  (debug-progn\n    (format t \"  Ran *jvm-abort-hook*!~%~%\")\n    (finish-output)\n    (finish-output) ;; I really mean it!\n    )\n  )\n\n\n(defun create-java-vm ()\n  (let* ((pvm (foreign-alloc 'JavaVM*))\n\t (penv (foreign-alloc 'JNIEnv*))\n\t (jvm-options (get-jvm-options))\n\t (nb-options (list-length jvm-options)))\n    (with-foreign-object (options (struct-JavaVMOption) (+ nb-options 2)) ;; 5)\n      (dotimes (i nb-options)\n\t(setf (foreign-slot-value (mem-aptr! options (struct-JavaVMOption) i)\n\t\t\t\t  (struct-JavaVMOption) 'optionString) (nth i jvm-options)))\n\n      (setf (foreign-slot-value (mem-aptr! options (struct-JavaVMOption) nb-options)\n\t\t\t\t(struct-JavaVMOption) 'optionString) \"exit\")\n      (setf (foreign-slot-value (mem-aptr! options (struct-JavaVMOption) nb-options)\n\t\t\t\t(struct-JavaVMOption) 'extraInfo)\n\t    (callback jvm-exit-handler))\n\n      (setf (foreign-slot-value (mem-aptr! options (struct-JavaVMOption) (+ 1 nb-options))\n\t\t\t\t(struct-JavaVMOption) 'optionString) \"abort\")\n      (setf (foreign-slot-value (mem-aptr! options (struct-JavaVMOption) (+ 1 nb-options))\n\t\t\t\t(struct-JavaVMOption) 'extraInfo)\n\t    (callback jvm-abort-handler))\n\n      (with-foreign-object (args (struct-JavaVMInitArgs))\n\t(setf (foreign-slot-value args (struct-JavaVMInitArgs) 'version) JNI_VERSION_1_4)\n\t(setf (foreign-slot-value args (struct-JavaVMInitArgs) 'options) options)\n\t(setf (foreign-slot-value args (struct-JavaVMInitArgs) 'nOptions)\n\t      (+ 2 nb-options) ;; with exit and abort handlers.\n\t      ;;nb-options ;; no exit or abort handlers.\n\t      )\n\t(setf (foreign-slot-value args (struct-JavaVMInitArgs) 'ignoreUnrecognized) JNI_FALSE)\n;\t(setf (foreign-slot-value args (struct-JavaVMInitArgs) 'ignoreUnrecognized) JNI_TRUE)\n\n\n\t(let ((result (JNI_CreateJavaVM pvm penv args)))\n\t  (if (> JNI_OK result)\n\t      (if +java-vm-destroyed+\n\t\t  (error \"JNI_CreateJavaVM failed. result = ~S~@\n                          You most likely tried to resurrect an already destroyed VM!~%\"\n\t\t\t result)\n\t\t  (error \"JNI_CreateJavaVM failed. result = ~S\" result)\n\t\t  )\n\t      (progn\n\t\t;(setq *jvm-foreign-ptr* pvm)\n\t\t(setq *jvm-foreign-ptr* (mem-ref pvm 'JavaVM*)\n\t\t      +java-vm-destroyed+ nil)\n\n\t\t;(register-current-thread penv)\n\t\t(register-current-thread (mem-ref penv 'JNIEnv*))\n\t\t)\n\t      )\n\t  )\n\t)\n      )\n    )\n  )\n\n(defcfun (\"JNI_GetCreatedJavaVMs\" JNI_GetCreatedJavaVMs) jint ;; 0 on success, otherwise negative\n  (arg0 :pointer)   ;; \"JavaVM **\"\n  (arg1 jsize)      ;;\n  (arg2 :pointer))  ;; \"jsize *\"\n\n\n;(print sb-thread:*current-thread*)\n\n\n(defmacro with-thread-attached-to-JavaVM ((&key (version JNI_VERSION_1_4 version-p) \n\t\t\t\t\t\t(name nil name-p)\n\t\t\t\t\t\t(group nil group-p))\n\t\t\t\t\t  &body body)\n  (declare (ignore version name group))\n  (let ((use-attach-args (or version-p name-p group-p)))\n    (cond \n      (use-attach-args\n       `(let ((penv (foreign-alloc 'JNIEnv*)))\n\t  #+(and sbcl linux) (guard-against-initial-thread)\n\t  (unwind-protect\n\t       (with-foreign-object (args (struct-JavaVMAttachArgs) 1)\n\t\t (setf (foreign-slot-value args (struct-JavaVMAttachArgs) 'version) version)\n\t\t (setf (foreign-slot-value args (struct-JavaVMAttachArgs) 'name) name)\n\t\t (setf (foreign-slot-value args (struct-JavaVMAttachArgs) 'group) group)\n\t\t (AttachCurrentThread penv args)\n\t\t (let ((*jenv-foreign-ptr* (mem-ref penv 'JNIEnv*)))\n\t\t   (declare (special *jenv-foreign-ptr*))\n\t\t   ,@body)\n\t\t )\n\t    ;; do some clean-up here\n\t    (DetachCurrentThread)\n\t    (foreign-free penv)\n\t    )\n\t )\n       )\n      (t\n       `(let ((penv (foreign-alloc 'JNIEnv*)))\n\t  #+(and sbcl linux) (guard-against-initial-thread)\n\t  (unwind-protect\n\t       (progn\n\t\t (AttachCurrentThread penv nil)\n\t\t (let ((*jenv-foreign-ptr* (mem-ref penv 'JNIEnv*)))\n\t\t   (declare (special *jenv-foreign-ptr*))\n\t\t   ,@body)\n\t\t )\n\t    (DetachCurrentThread)\n\t    (foreign-free penv)\n\t    )\n\t )\n       )\n      )\n    )\n  )\n\n(export 'with-thread-attached-to-JavaVM)\n\n;;;\n\n\n\n(defun jmethod-object-0 (this method-name sig)\n  (PushLocalFrame 4)\n  (let* ((this-class (GetObjectClass this))\n\t (method-id (GetMethodID this-class method-name sig))\n\t )\n    (PopLocalFrame ;; Pops this-class\n     (CallObjectMethodA this method-id (null-pointer)))\n    )\n  )\n\n(defun throwable-toString (exception)\n  (PushLocalFrame 2)\n  (let* ((jname (jmethod-object-0 exception \"toString\" \"()Ljava/lang/String;\"))\n\t (len (GetStringLength jname))\n\t (utf-len (GetStringUTFLength jname))\n\t (buf (foreign-alloc :char :count (1+ utf-len)))\n\t )\n    (GetStringUTFRegion jname 0 len buf)\n    (PopLocalFrame (null-pointer)) ;; Pops jname\n    (prog1 \n\t(cffi:foreign-string-to-lisp buf :count utf-len) ;; This is post CFFI 0.10.X\n      (foreign-free buf))\n    )\n  )\n\n\n(defun print-thrown-from-java (condition stream)\n  (declare (type condition condition) (type stream stream))\n  ;;(ExceptionClear) ;; Do we really need to do this here?\n  (format stream \"Java exception: ~S.~%\"\n\t  (throwable-toString\n\t   (thrown-from-java-this condition)))\n  )\n\n;; (defun print-java-throwable (condition stream)\n;;   ;;(ExceptionClear) ;; Do we really need to do this here?\n;;   (format stream \"Java exception: ~S.~%\"\n;; \t  (throwable-toString\n;; \t   (java-throwable-thrown condition)))\n;;   )\n\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/reference.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n(in-package :jni)\n\n\n;(declaim (optimize debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n(defvar *lisp-external-references* \n  (let ((array (make-array '(1000) :adjustable t :fill-pointer 1)))\n    (setf (aref array 0) nil)  ;; we reserve index 0 for special use.\n    array\n    )\n  )\n\n(defvar *lisp-ext-ref-free-head* nil) ;; should be an integer, index of the first\n;; free cell in the allocated portion of *lisp-external-references*. If nil it means\n;; that the free cell chain is empty.\n\n(defvar +lisp-ext-ref-lock+ (bt:make-lock \"jni:*lisp-external-references*\"))\n\n\n(defun new-lisp-reference (obj)\n  (bt:with-lock-held (+lisp-ext-ref-lock+)\n    (cond ((eq nil obj)\n\t   0\n\t   )\n\t  (*lisp-ext-ref-free-head*\n\t   (let* ((head *lisp-ext-ref-free-head*)\n\t\t  (next (aref *lisp-external-references* head)))\n\t     (setf (aref *lisp-external-references* head) obj)\n\t     (setq *lisp-ext-ref-free-head* next)\n\t     head\n\t     )\n\t   )\n\t  (t\n\t   (vector-push-extend obj *lisp-external-references*)\n\t   )\n\t  )\n    )\n  )\n\n(defun free-lisp-reference (index)\n  (unless (eql 0 index)\n    (bt:with-lock-held (+lisp-ext-ref-lock+)\n      (setf (aref *lisp-external-references* index) *lisp-ext-ref-free-head*)\n      (setq *lisp-ext-ref-free-head* index)\n      )\n    )\n  )\n\n(defun lisp-reference-value (obj)\n  (aref *lisp-external-references* obj)\n  )\n\n(defun lisp-reference-eq (ref1 ref2)\n  (eq (lisp-reference-value ref1) (lisp-reference-value ref2))\n  )\n\n#|\n\n(export '(new-lisp-reference\n\t  free-lisp-reference\n\t  lisp-reference-value\n\t  lisp-reference-eq))\n\n|#\n\n\n(defmacro new-lref (obj) `(new-lisp-reference ,obj))\n(defmacro free-lref (index) `(free-lisp-reference ,index))\n(defmacro lref-val (obj) `(lisp-reference-value ,obj))\n(defmacro lref-eq (ref1 ref2) `(lisp-reference-eq ,ref1 ,ref2))\n"
  },
  {
    "path": "third-party/cl+j-0.4/sbcl_repl.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n\n(defun quit-repl (&key (status 0))\n  (declare (type (signed-byte 32) status))\n  (throw 'quit-secondary-repl status))\n\n(defun spawn-repl ()\n  (let* ((initial-thread sb-thread:*current-thread*)\n\t (repl\n\t  (sb-thread:make-thread\n\t   #'(lambda ()\n\t       (let ((sb-int:*repl-prompt-fun*\n\t\t      #'(lambda (stream)\n\t\t\t  (fresh-line stream)\n\t\t\t  (write-string \"> \" stream)))\n\t\t     status\n\t\t     )\n\t\t (format t \"~%You should exit this REPL by calling (~S).~%~%\" 'quit-repl)\n\t\t (setq status\n\t\t       (catch 'quit-secondary-repl\n\t\t\t (sb-impl::toplevel-repl nil)))\n\t\t (sb-thread:release-foreground initial-thread) ;\n\t\t status\n\t\t )\n\t       )\n\t   :name \"Secondary REPL\")))\n    (sb-thread:release-foreground repl)\n    (handler-case \n\t(let ((status (sb-thread:join-thread repl)))\n\t  ;;(format t \"Secondary REPL returned with status = ~S~%\" status)\n\t  (sb-ext:quit :unix-status status)\n\t  )\n      (sb-thread:join-thread-error (condition)\n\t(declare (ignorable condition))\n\t;;(format t \"~%Secondary REPL terminated abnormally!~% condition = ~S~%\"\n\t;;        condition)\n\t(sb-ext:quit :unix-status -1)\n\t)\n      )\n      \n    ;;(format t \"~%Back to Primary REPL!~%\")\n    ;;(values)\n    )\n  )\n"
  },
  {
    "path": "third-party/cl+j-0.4/swt_adapter.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n\n(defpackage \"SWT\"\n  (:use \"CL\" \"CL+J\")\n  (:export make-listener\n\t   display\n\t   swt-init\n\t   start-swt-event-loop\n\t   stop-event-loop\n\t   In-UI-thread-do\n\t   In-UI-thread-do-and-wait\n\t   In-UI-thread-do-delayed\n\t   ))\n\n(in-package :swt)\n\n(defun make-listener (handler &key data)\n  (let ((listener (jnew \"cl_j.swt.LispListener\" (new-lref handler) (new-lref data))))\n    listener)\n  )\n\n(def-java-native\n    \"cl_j.swt.LispListener\"\n  (\"void\" \"funcall\" ((\"int\" fun-ref) (\"org.eclipse.swt.widgets.Event\" event) (\"int\" data)) ()\n\t (funcall (lref-val fun-ref) event data))\n;;   (\"void\" \"funcall\" ((\"int\" fun-ref)) ()\n;; \t (funcall (lref-val fun-ref) nil nil))\n  (\"void\" \"freeLispReference\" ((\"int\" ref)) ()\n\t  (free-lref ref))\n  )\n\n\n\n(defvar *display* nil)\n\n(defvar *end-event-loop* nil)\n\n(defvar *event-loop-thread* nil)\n\n(defun display () *display*)\n\n(defun event-loop ()\n  ;;(format t \"~%Entering SWT's UI application event loop.~%\") ;; debug\n  (do ()\n      (*end-event-loop*)\n    (unless (jtrue-p (#_readAndDispatch *display*))\n      (#_sleep *display*))\n    )\n  ;;(format t \"About to dispose of SWT's event loop!~%\") ;; debug\n  (setq *end-event-loop* nil)\n )\n\n(defun stop-event-loop ()\n  (setq *end-event-loop* t)\n  (#_wake *display*)\n  )\n\n(defun swt-init ()\n  (unless (and (boundp '*display*) *display*)\n    (setq *display* (jnew \"org.eclipse.swt.widgets.Display\"))))\n\n(defun start-swt-event-loop ()\n  ;;(format t \"~&In start-swt-event-loop.~%\") (finish-output) ;; debug\n  (unless *event-loop-thread* \n    (setq *event-loop-thread*\n\t  (bt:make-thread\n\t   #'(lambda () \n               ;;(format t \"~&Start of SWT event-loop thread init-function.~%\") (finish-output) ;; debug\n\n\t       #+mkcl (mp:thread-detach mp:*thread*)\n\t       (unwind-protect \n\t\t    (progn\n\t\t      (swt-init)\n\t\t      (event-loop))\n\t\t ;;(format t \"~&Cleaning up on SWT event loop shutdown! *display* = ~S.~%\" *display*) (finish-output) ;; debug JCB\n\t\t (when *display* (#_dispose *display*))\n\t\t (setq *display* nil *end-event-loop* nil)\n \t\t (setq *event-loop-thread* nil)\n\t\t )\n\t       )\n\t   :name \n\t   \"SWT event loop\" \n\t   ;;'(:name \"SWT event loop\" :initial-bindings nil) ;; mkcl specific\n\t   )))\n  ;;(format t \"~&Done with start-swt-event-loop.~%\") (finish-output) ;; debug\n  )\n\n(defun wait-on-swt-event-loop-startup (max_seconds)\n  (do ((waited 0 (+ waited 0.1)))\n      ((or (>= waited max_seconds) (and *event-loop-thread* *display*))) (sleep 0.1)))\n\n\n(defun In-UI-thread-do (fun)\n  (if *display*\n      (#_asyncExec *display* (jnew \"cl_j.RunInLisp\" (new-lref fun)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n\n(defun In-UI-thread-do-and-wait (fun)\n  (if *display*\n      (#_syncExec *display* (jnew \"cl_j.RunInLisp\" (new-lref fun)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n(defun In-UI-thread-do-delayed (milliseconds fun)\n  (if *display*\n      (#_timerExec *display* milliseconds (jnew \"cl_j.RunInLisp\" (new-lref fun)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/swt_adapter_for_ccl.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n\n\n(defpackage \"SWT\"\n  (:use \"CL\" \"CL+J\")\n  (:export make-listener\n\t   display\n\t   swt-init\n\t   start-swt-event-loop\n\t   stop-event-loop\n\t   In-UI-thread-do\n\t   In-UI-thread-do-and-wait\n\t   In-UI-thread-do-delayed\n\t   ))\n\n(in-package :swt)\n\n(defun make-listener (handler &key data)\n  (let ((listener (jnew \"cl_j.swt.LispListener\" (new-lref handler) (new-lref data))))\n    listener)\n  )\n\n(def-java-native\n    \"cl_j.swt.LispListener\"\n  (\"void\" \"funcall\" ((\"int\" fun-ref) (\"org.eclipse.swt.widgets.Event\" event) (\"int\" data)) ()\n\t (funcall (lref-val fun-ref) event data))\n;;   (\"void\" \"funcall\" ((\"int\" fun-ref)) ()\n;; \t (funcall (lref-val fun-ref) nil nil))\n  (\"void\" \"freeLispReference\" ((\"int\" ref)) ()\n\t  (free-lref ref))\n  )\n\n\n\n(defvar *display* nil)\n\n(defvar *end-event-loop* nil)\n\n(defvar *event-loop-thread* nil)\n\n(defun display () *display*)\n\n(defun event-loop ()\n  (format t \"~%Entering SWT's UI application event loop.~%\") ;; debug\n  (do ()\n      (*end-event-loop*)\n    (unless (jtrue-p (#]readAndDispatch *display*))\n      (#]sleep *display*))\n    )\n  (format t \"About to dispose of SWT's event loop!~%\") ;; debug\n  (setq *end-event-loop* nil)\n )\n\n(defun stop-event-loop ()\n  (setq *end-event-loop* t)\n  (#]wake *display*)\n  )\n\n(defun swt-init ()\n  (unless (and (boundp *display*) *display*)\n    (setq *display* (jnew \"org.eclipse.swt.widgets.Display\"))))\n\n(defun start-swt-event-loop ()\n  (unless *event-loop-thread* \n    (setq *event-loop-thread*\n\t  (bt:make-thread\n\t   #'(lambda () \n\t       #+mkcl (mp:thread-detach mp:*thread*)\n\t       (unwind-protect \n\t\t    (progn\n\t\t      (swt-init)\n\t\t      (event-loop))\n\t\t ;;(format t \"~&Cleaning up on SWT event loop shutdown! *display* = ~S.~%\" *display*) (finish-output) ;; debug JCB\n\t\t (when *display* (#]dispose *display*))\n\t\t (setq *display* nil *end-event-loop* nil)\n \t\t (setq *event-loop-thread* nil)\n\t\t )\n\t       )\n\t   :name \n\t   \"SWT event loop\" \n\t   ;;'(:name \"SWT event loop\" :initial-bindings nil) ;; mkcl specific\n\t   ))))\n\n(defun wait-on-swt-event-loop-startup (max_seconds)\n  (do ((waited 0 (+ waited 0.1)))\n      ((or (>= waited max_seconds) (and *event-loop-thread* *display*))) (sleep 0.1)))\n\n\n(defun In-UI-thread-do (fun)\n  (if *display*\n      (#]asyncExec *display* (jnew \"cl_j.RunInLisp\" (new-lref fun)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n\n(defun In-UI-thread-do-and-wait (fun)\n  (if *display*\n      (#]syncExec *display* (jnew \"cl_j.RunInLisp\" (new-lref fun)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n(defun In-UI-thread-do-delayed (milliseconds fun)\n  (if *display*\n      (#]timerExec *display* milliseconds (jnew \"cl_j.RunInLisp\" (new-lref fun)))\n      (format t \"~&There is no SWT event loop!~%\"))\n  )\n\n\n"
  },
  {
    "path": "third-party/cl+j-0.4/vtable.lisp",
    "content": ";;;\n;;;\n;;; Copyright (c) 2009,2017, Jean-Claude Beaudoin\n;;; All rights reserved by the author.\n;;;\n;;; Permission is hereby granted, free of charge, to any person\n;;; obtaining a copy of this software and associated documentation\n;;; files (the \"Software\"), to deal in the Software without\n;;; restriction, including without limitation the rights to use, copy,\n;;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;;; of the Software, and to permit persons to whom the Software is\n;;; furnished to do so, subject to the following conditions:\n;;;\n;;; The above copyright notice and this permission notice shall be\n;;; included in all copies or substantial portions of the Software.\n;;;\n;;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n;;; DEALINGS IN THE SOFTWARE.\n;;;\n;;;\n;;;\n\n(in-package :jni)\n\n;(declaim (optimize debug))\n;(declaim (optimize (debug 0) (speed 3)))\n\n\n(eval-when (:compile-toplevel :load-toplevel)\n  (defparameter *debug* t))\n\n(defmacro debug-progn (&body body)\n  (if *debug*\n      `(progn ,@body)\n      nil))\n\n;;(defparameter *jni-trace* t) ;#+cmu19 t #+(or sbcl clisp) nil)\n(defparameter *jni-trace* #+scl t\n\t                  #+cmu19 nil\n\t\t\t  #+ccl nil\n\t\t\t  #+clisp t\n\t\t\t  #+(or sbcl mkcl ecl) nil)\n(defparameter *jni-describe-exception* nil)\n\n\n;;;\n;;;\n;;;\n\n\n;(eval-when (#| :execute :load-toplevel |# :compile-toplevel)\n  (defun build-vtable-dispatch-vector (slots)\n    (mapcar\n     #'(lambda (slot)\n\t (cond ((symbolp slot)\n\t\t(list slot :pointer)\n\t\t)\n\t       ((not (listp slot))\n\t\t(error \"Invalid slot definition.\")\n\t\t)\n\t       (t\n\t\t(let ((slot-name (car slot)))\n\t\t  (list slot-name :pointer))\n\t\t)\n\t       )\n\t )\n     slots)\n    )\n\n  (defun nflatten-1 (al)\n    (do ((p al))\n\t((null p) al)\n      (let ((q (car p)))\n\t(cond ((null q)\n\t       (cond ((null (cdr p))\n\t\t      (setq al (butlast al))\n\t\t      (pop p))\n\t\t     (t\n\t\t      (rplaca p (cadr p))\n\t\t      (rplacd p (cddr p))\n\t\t      ))\n\t       )\n\t      ((listp q)\n\t       (nconc q (cdr p))\n\t       (rplacd p (cdr q))\n\t       (rplaca p (car q))\n\t       (pop p)\n\t       )\n\t      (t \n\t       (pop p)\n\t       )\n\t      )\n\t)))\n\n\n;;  We define our condition on \"error\"\n;;  but we should handle on \"serious-condition\" when going back to Java.\n\n(declaim (ftype (function (condition stream) t) print-thrown-from-java))\n;;(declaim (ftype (function (condition stream) t) print-java-throwable))\n\n(define-condition thrown-from-java (condition)\n  ((this :initarg :this :reader thrown-from-java-this))\n  (:report print-thrown-from-java)\n  )\n\n(export '(thrown-from-java thrown-from-java-this))\n\n;; (define-condition java-throwable (condition)\n;;   ((thrown :initarg :thrown\n;; \t   :reader java-throwable-thrown))\n;;   (:report print-java-throwable)\n;;   )\n\n;; (export '(java-throwable java-throwable-thrown))\n\n(defun delete-thrown-java-global-ref (global-java-ref)\n#|\n  (debug-progn \n    #-sbcl (format t \"~%Inside delete-thrown-java-global-ref!~%\") \n    #+sbcl (format t \"~%Inside delete-thrown-java-global-ref! Thread = ~S.~%\"\n\t\t   (sb-thread:thread-name sb-thread:*current-thread*))\n    (finish-output))\n|#\n\n  (handler-case \n      (unless (java-vm-destroyed)\n\t(DeleteGlobalRef global-java-ref))\n    (jni::jni-error (condition)\n      (declare (ignorable condition))\n#|\n      (format t \"~%In delete-thrown-java-global-ref: condition = ~S~%\" condition)\n      (apply #'format t (simple-condition-format-control condition)\n\t     (simple-condition-format-arguments condition))\n      (fresh-line)\n      (finish-output)\n      ;;(break)\n|#\n      ;;(debug-progn (signal condition))\n      )\n    )\n  )\n\n(defun wrap-throwable (lref-to-throwable)\n  (let* ((gref (NewGlobalRef lref-to-throwable))\n\t (wrapper (make-condition 'thrown-from-java :this gref)\n\t  ))\n    (DeleteLocalRef lref-to-throwable)\n    #-sbcl (tg:finalize wrapper #'(lambda () (delete-thrown-java-global-ref gref)))\n    wrapper\n    )\n  )\n\n\n;;;\n;;;\n;;;\n\n(defun build-error-handling-block-for-void (throws)\n  (declare (ignore throws))\n  )\n\n(defun build-error-handling-block (ret-c-type throws ok not-ok error)\n  (declare (ignore ret-c-type throws ok not-ok error))\n  )\n\n\n(defun jfield-int (object name)\n  (PushLocalFrame 4)\n  (let* ((class (GetObjectClass object))\n\t (field-id (GetFieldID class name \"I\"))\n\t )\n    (prog1\n\t(GetIntField object field-id)\n      (PopLocalFrame (null-pointer)))\n    )\n  )\n\n\n(defparameter +LispCondition-class+ nil)\n;; +LispCondition-class+ is to be bound to a global ref to the Java wrapper class.\n\n(defun find-class-LispCondition ()\n  (setq +LispCondition-class+ \n\t(let ((lref (FindClass \"cl_j/LispCondition\")))\n\t  (prog1 (NewGlobalRef lref) (DeleteLocalRef lref)))))\n\n(defmacro LispCondition-class ()\n  `(or +LispCondition-class+ (find-class-LispCondition)))\n\n(defmacro relay-lisp-condition-in-transit (j-excep)\n  `(let* ((throwable-gref (thrown-from-java-this ,j-excep))\n\t  (j-excep-class (GetObjectClass throwable-gref)) ;; This one generates a lref!\n\t  (is-same (IsSameObject j-excep-class (LispCondition-class)))\n\t  )\n     (DeleteLocalRef j-excep-class) ;; dispose of j-excep-class lref.\n     (when (eql JNI_TRUE is-same)\n       ;;(break \"Received a Lisp Condition in a Java Wrapper.\")\n       (let* ((condi-ref (jfield-int throwable-gref \"condition\"))\n\t      (condi (lisp-reference-value condi-ref))\n\t      )\n\t ;;(break \"About to re-signal a condition.\")\n\t (error condi)\n\t (error \"!! Came back from re-signalling a Lisp condition transmitted by Java! !!\")\n\t )\n       )\n     )\n  )\n;;\n;;\n;;\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defun cffi-version ()\n    (asdf:component-version (asdf:find-system 'cffi)))\n  (defun cffi-ver->=-0.11.0 ()\n    (if (uiop:version< (cffi-version) \"0.11.0\") '#.(gensym) :common))\n  )\n\n\n;;; This macro was required by a backward incompatible change\n;;; that occurred in CFFI around Feb 14, 2007 according to its ChangeLog.\n;;; Version 0.9.2 pre-dates the change. We hope the other versions\n;;; we use to post-date it.\n(defmacro my-foreign-funcall (name-or-pointer &rest args)\n  (if (uiop:version<= \"0.9.2\" (cffi-version))\n      `(foreign-funcall ,name-or-pointer ,@args)\n      `(foreign-funcall-pointer ,name-or-pointer () ,@args))\n  )\n\n\n\n(defun build-vtable-function-wrappers (vname this-accessor slots export-all)\n  ;;(declare (ignore export-all))\n  ;;(format t \"Inside build-vtable-function-wrappers.~%\")\n  ;;(format t \" Value of slots: ~S~%\" slots)\n  ;;(force-output)\n  (let (defuns)\n    (dolist (slot slots (nreverse defuns))\n      (unless (symbolp slot) ;; just skip over placeholders.\n\t(let (defun f-funcall-sig args throws-p)\n\t  (unless (and (listp slot) (<= 3 (length slot)))\n\t    ;;(break)\n\t    (error \"Invalid vtable slot specification. ~S\" slot))\n\t  (destructuring-bind (slot-name \n\t\t\t       ret-c-type \n\t\t\t       ( &rest c-args )\n\t\t\t       &key \n\t\t\t       (throws nil throws-specific-p)\n\t\t\t       (throws-any-exception nil)\n\t\t\t       (ok nil ok-p) \n\t\t\t       (not-ok nil not-ok-p)\n\t\t\t       (error nil error-p)\n\t\t\t       (export nil export-p))\n\t      slot\n\t    (declare (ignorable throws error) (ignorable export-p))\n\t    ;;(declare (ignore #|throws ok error|# export))\n\t    ;;(format t \"c-args = ~S~%\" c-args)\n\t    (debug-progn\n\t      (when *jni-trace*\n\t\t(format t \"In ~S~%\" slot-name)\n\t\t(format t \"  :throws ~S~%\" throws)\n\t\t(if throws-any-exception\n\t\t    (format t \"  :throws-any-exception ~S~%\" throws-any-exception))\n\t\t(format t \"  :ok ~S,  :not-ok ~S~%\" ok not-ok)\n\t\t(format t \"  :error ~S~%\" error)))\n\t    (setq throws-p (or throws-specific-p throws-any-exception))\n\t    (dolist (c-arg c-args)\n\t      ;; a c-arg must be a list of 2 elements\n\t      (unless (null c-arg) ;; if it is empty we ignore it.\n\t\t(push (car c-arg) args)\t;; collect arg names\n\t\t(push (reverse c-arg) f-funcall-sig))\n\t      )\n\t    (setq args (nreverse args))\n\t    (setq f-funcall-sig (nflatten-1 (nreverse f-funcall-sig)))\n\t    (setq \n\t     defun\n\t     `(defun ,slot-name ,args\n\t\t(let ((func-ptr \n\t\t       (foreign-slot-value (mem-ref ,this-accessor :pointer)\n\t\t\t\t\t   ',vname ',slot-name)))\n\t\t  ,(if (eq :void ret-c-type)\n\t\t       `(progn\n\t\t\t  (my-foreign-funcall func-ptr\n\t\t\t\t\t      :pointer ,this-accessor\n\t\t\t\t\t      ,@f-funcall-sig\n\t\t\t\t\t      ,ret-c-type)\n\t\t\t  ,@(when throws-p\n\t\t\t\t  `((when (= JNI_TRUE (ExceptionCheck))\n\t\t\t\t      (let ((j-excep \n\t\t\t\t\t     (wrap-throwable\n\t\t\t\t\t      (prog1\n\t\t\t\t\t\t  (ExceptionOccurred)\n\t\t\t\t\t\t(debug-progn\n\t\t\t\t\t\t (when *jni-describe-exception*\n\t\t\t\t\t\t   (ExceptionDescribe)))\n\t\t\t\t\t\t(ExceptionClear)\n\t\t\t\t\t\t))))\n\t\t\t\t\t,(when throws-any-exception\n\t\t\t\t\t       `(relay-lisp-condition-in-transit j-excep))\n;; \t\t\t\t\t(error\n;; \t\t\t\t\t 'thrown-from-java\n;; \t\t\t\t\t :thrown j-excep)\n\t\t\t\t\t(error j-excep)\n\t\t\t\t\t)\n\n\t\t\t\t      ;; Belt and suspenders.\n\t\t\t\t      (debug-progn\n\t\t\t\t\t(when *jni-describe-exception*\n\t\t\t\t\t  (ExceptionDescribe)))\n\t\t\t\t      (error \"~S failed.\" ',slot-name)))\n\t\t\t\t  )\n\t\t\t  (debug-progn\n\t\t\t    (when *jni-trace*\n\t\t\t      (format t \"~S.~%\" ',slot-name)))\n\t\t\t  nil\n\t\t\t  )\n\t\t       `(let ((result (my-foreign-funcall func-ptr \n\t\t\t\t\t\t\t  :pointer ,this-accessor\n\t\t\t\t\t\t\t  ,@f-funcall-sig \n\t\t\t\t\t\t\t  ,ret-c-type)))\n\t\t\t  ,@(when \n\t\t\t     ok-p\n\t\t\t     (cond\n\t\t\t       (error-p\n\t\t\t\t;; This one should be a lot more\n\t\t\t\t;; elaborate. Will do for now.\n\t\t\t\t;; It is meant to implement support for the :error\n\t\t\t\t;; vtable entry option. So don't use :error for now...\n\t\t\t\t`((debug-progn\n\t\t\t\t    (if *jni-trace*\n\t\t\t\t\t(break \"Keyword :error in jni:defvtable is not implemented yet!\")))\n\t\t\t\t  )\n\t\t\t\t)\n\t\t\t       (t\n\t\t\t\t`((unless (eql ,ok result)\n\t\t\t\t    ,@(when throws-p\n\t\t\t\t\t    `((let ((j-excep\n\t\t\t\t\t\t     (wrap-throwable\n\t\t\t\t\t\t      (prog1\n\t\t\t\t\t\t\t  (ExceptionOccurred)\n\t\t\t\t\t\t\t(debug-progn\n\t\t\t\t\t\t\t (when *jni-describe-exception*\n\t\t\t\t\t\t\t   (ExceptionDescribe)))\n\t\t\t\t\t\t\t(ExceptionClear)\n\t\t\t\t\t\t\t))))\n\t\t\t\t\t\t,(when throws-any-exception\n\t\t\t\t\t\t       `(relay-lisp-condition-in-transit\n\t\t\t\t\t\t\t j-excep))\n;; \t\t\t\t\t\t(error\n;; \t\t\t\t\t\t 'thrown-from-java\n;; \t\t\t\t\t\t :thrown j-excep)\n\t\t\t\t\t\t(error j-excep)\n\t\t\t\t\t\t)\n\t\t\t\t\t      ;; Belt and suspenders.\n\t\t\t\t\t      (debug-progn\n\t\t\t\t\t\t(when *jni-describe-exception*\n\t\t\t\t\t\t  (ExceptionDescribe)))\n\t\t\t\t\t      (error \"~S failed.\" ',slot-name)))\n\t\t\t\t    ,@(when (not (or throws-p error-p))\n\t\t\t\t\t    `((error \"~S failed. result = ~S.~%\"\n\t\t\t\t\t\t     ',slot-name result)))))\n\t\t\t\t)\n\t\t\t       )\n\t\t\t     )\n\t\t\t  ,@(when \n\t\t\t     not-ok-p\n\t\t\t     (cond\n\t\t\t       ((eq not-ok 'JNI_NULL)\n\t\t\t\t`((when (null-pointer-p result)\n\t\t\t\t    ,@(when throws-p\n\t\t\t\t\t    `((let ((j-excep\n\t\t\t\t\t\t     (wrap-throwable\n\t\t\t\t\t\t      (prog1\n\t\t\t\t\t\t\t  (ExceptionOccurred)\n\t\t\t\t\t\t\t(debug-progn\n\t\t\t\t\t\t\t (when *jni-describe-exception*\n\t\t\t\t\t\t\t   (ExceptionDescribe)))\n\t\t\t\t\t\t\t(ExceptionClear)\n\t\t\t\t\t\t\t))))\n\t\t\t\t\t\t,(when throws-any-exception\n\t\t\t\t\t\t       `(relay-lisp-condition-in-transit\n\t\t\t\t\t\t\t j-excep))\n;; \t\t\t\t\t\t(error\n;; \t\t\t\t\t\t 'thrown-from-java\n;; \t\t\t\t\t\t :thrown j-excep)\n\t\t\t\t\t\t(error j-excep)\n\t\t\t\t\t\t)\n\t\t\t\t\t      ;; Belt and suspenders.\n\t\t\t\t\t      (debug-progn\n\t\t\t\t\t\t(when *jni-describe-exception*\n\t\t\t\t\t\t  (ExceptionDescribe)))))\n\t\t\t\t    (error \"~S failed. result = ~S.~%\"\n\t\t\t\t\t   ',slot-name result)\n\t\t\t\t    ))\n\t\t\t\t)\n\t\t\t       (t\n\t\t\t\t`((when (eql ,not-ok result)\n\t\t\t\t    ,@(when throws-p\n\t\t\t\t\t    `((let ((j-excep\n\t\t\t\t\t\t     (wrap-throwable\n\t\t\t\t\t\t      (prog1\n\t\t\t\t\t\t\t  (ExceptionOccurred)\n\t\t\t\t\t\t\t(debug-progn\n\t\t\t\t\t\t\t (when *jni-describe-exception*\n\t\t\t\t\t\t\t   (ExceptionDescribe)))\n\t\t\t\t\t\t\t(ExceptionClear)\n\t\t\t\t\t\t\t))))\n\t\t\t\t\t\t,(when throws-any-exception\n\t\t\t\t\t\t       `(relay-lisp-condition-in-transit\n\t\t\t\t\t\t\t j-excep))\n;; \t\t\t\t\t\t(error\n;; \t\t\t\t\t\t 'thrown-from-java\n;; \t\t\t\t\t\t :thrown j-excep)\n\t\t\t\t\t\t(error j-excep)\n\t\t\t\t\t\t)\n\t\t\t\t\t      ;; Belt and suspenders.\n\t\t\t\t\t      (debug-progn\n\t\t\t\t\t\t(when *jni-describe-exception*\n\t\t\t\t\t\t  (ExceptionDescribe)))))\n\t\t\t\t    (error \"~S failed. result = ~S.~%\"\n\t\t\t\t\t   ',slot-name result)))\n\t\t\t\t)\n\t\t\t       )\n\t\t\t     )\n\t\t\t  ,@(when (and throws-p (not ok-p) (not not-ok-p))\n\t\t\t\t  `((when (= JNI_TRUE (ExceptionCheck))\n\t\t\t\t      (let ((j-excep \n\t\t\t\t\t     (wrap-throwable\n\t\t\t\t\t      (prog1\n\t\t\t\t\t\t  (ExceptionOccurred)\n\t\t\t\t\t\t(debug-progn\n\t\t\t\t\t\t (when *jni-describe-exception*\n\t\t\t\t\t\t   (ExceptionDescribe)))\n\t\t\t\t\t\t(ExceptionClear)\n\t\t\t\t\t\t))))\n\t\t\t\t\t,(when throws-any-exception\n\t\t\t\t\t       `(relay-lisp-condition-in-transit\n\t\t\t\t\t\t j-excep))\n;; \t\t\t\t\t(error\n;; \t\t\t\t\t 'thrown-from-java\n;; \t\t\t\t\t :thrown j-excep)\n\t\t\t\t\t(error j-excep)\n\t\t\t\t\t)\n\t\t\t\t      ;; Belt and suspenders.\n\t\t\t\t      (debug-progn\n\t\t\t\t\t(when *jni-describe-exception*\n\t\t\t\t\t  (ExceptionDescribe)))\n\t\t\t\t      (error \"~S failed. result = ~S~%\"\n\t\t\t\t\t     ',slot-name result)))\n\t\t\t\t  )\n;;; \t\t\t   ,@(WHEN (and throws-p (not ok-p) (not not-ok-p))\n;;; \t\t\t\t   `((case (ExceptionCheck)\n;;; \t\t\t\t       (JNI_TRUE\n;;; \t\t\t\t\t(debug-progn\n;;;                                       (when *jni-describe-exception* \n;;;                                         (ExceptionDescribe)))\n;;; \t\t\t\t\t(error \"~S failed. result = ~S~%\"\n;;; \t\t\t\t\t       ',slot-name result))\n;;; \t\t\t\t       (JNI_FALSE)\n;;; \t\t\t\t       (otherwise\n;;; \t\t\t\t\t(error \"JNI:ExceptionCheck failed!~%\"))\n;;; \t\t\t\t       )\n;;; \t\t\t\t     )\n;;; \t\t\t\t   )\n\t\t\t  (debug-progn\n\t\t\t    (when *jni-trace*\n\t\t\t      (format t \"~S: result = ~S.~%\" ',slot-name  result)))\n\t\t\t  result\n\t\t\t  )))))\n\t    ;;(format t \"Defined slot function: ~S~%\" defun)\n\t    (push defun defuns)\n\t    (when (or export-all export) (push `(export ',slot-name) defuns))\n\t    )\n\t  )\n\t)\n      )\n    )\n  )\n;  ) ;; eval-when\n\n(defmacro defvtable (name this (&key export-all) &rest slots)\n  `(progn\n    (defcstruct ,name ,@(build-vtable-dispatch-vector slots))\n    ,@(if (uiop:version< (cffi-version) \"0.11.0\")\n          (build-vtable-function-wrappers name this slots export-all)\n          (build-vtable-function-wrappers `(:struct ,name) this slots export-all)))\n  )\n"
  },
  {
    "path": "third-party/cl-gravatar/LICENSE.txt",
    "content": "Copyright 2011 Greg Pfeil <greg@technomadic.org>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "third-party/cl-gravatar/README.mkdn",
    "content": "# CL-Gravatar\n\nCommon Lisp bindings to the popular [Gravatar](https://secure.gravatar.com/) service.\n\n### (image-url _email_ &key _size default force-default-p rating_)\n\n* _size_ is an integer for the number of pixels along a side. Gravatar images are always square.\n* _default_ may be either a URL to your own image, or one of `:404`, `:mm`, `:identicon`, `:monsterid`, `:wavatar`, or `:retro`.\n* _force-default-p_ indicates whether to always use the default, even if another image is available. This is useful when you use a hash-based default like `:identicon` that is unique per user.\n* _rating_ may be one of `:g`, `:pg`, `:r`, or `:x` and indicates the level of \"vulgarity\" that you allow on your site. The default is `:g`.\n\n### (profile _email_)\n\nThis returns the cl-json formatted profile of the user. Eventually we'll parse this into better structures, but that is TBD.\n\n### (profile-url _email_ _js-callback_)\n\nIf you want to process the profile data using client-side JavaScript, this generates a URL that you can use as a `src` value to the HTML `script` element. And the JavaScript function named by _js-callback_ will be called on the resulting JSON data.\n\n### (qr-code-url _email_ &key _size_)\n\nThis returns an image URL for a QR code that encodes the URL of the user's profile page.\n"
  },
  {
    "path": "third-party/cl-gravatar/gravatar.asd",
    "content": "(defpackage gravatar.system\n  (:use #:cl #:asdf))\n\n(in-package #:gravatar.system)\n\n(defsystem gravatar\n  :author \"Greg Pfeil <greg@technomadic.org>\"\n  :license \"Apache 2.0\"\n  :version \"0.0.1\"\n  :depends-on (md5 drakma puri cl-json babel)\n  :components ((:file \"package\")\n               (:file \"gravatar\" :depends-on (\"package\"))))\n"
  },
  {
    "path": "third-party/cl-gravatar/gravatar.lisp",
    "content": "(in-package #:gravatar)\n\n(defvar +base-uri+ (puri:uri \"https://secure.gravatar.com/\")\n  \"Why would we ever _not_ use SSL?\")\n\n(defun hash (email)\n  (string-downcase (format nil \"~{~2,'0x~}\"\n                           (coerce (md5:md5sum-sequence\n                                    (string-downcase (string-trim '(#\\space)\n                                                                  email)))\n                                   'list))))\n\n(defun image-url (email &key size default force-default-p rating)\n  \"DEFAULT may be either a URL to your own image, or one of :404, :mm,\n   :identicon, :monsterid, :wavatar, or :retro. RATING may be one of :g, :pg,\n   :r, or :x.\"\n  (let ((parameters ()))\n    (when size (push `(\"s\" . ,(format nil \"~d\" size)) parameters))\n    (typecase default\n      (keyword (push `(\"d\" . ,(string-downcase default)) parameters))\n      (string (push `(\"d\" . ,default) parameters)))\n    (when force-default-p (push '(\"f\" . \"y\") parameters))\n    (when rating (push `(\"r\" . ,(string-downcase rating)) parameters))\n    (puri:merge-uris (format nil \"avatar/~a~@[?~a~]\"\n                             (hash email)\n                             (drakma::alist-to-url-encoded-string parameters\n                                                                  :utf-8\n                                                                  'drakma:url-encode))\n                     +base-uri+)))\n\n(defun generate-profile-url (email type parameters)\n  (puri:merge-uris (format nil \"g2-~a.~a~@[?~a~]\"\n                           (hash email)\n                           (string-downcase type)\n                           (drakma::alist-to-url-encoded-string parameters\n                                                                :utf-8\n                                                                'drakma:url-encode))\n                   +base-uri+))\n\n(defun profile-url (email js-callback)\n  (generate-profile-url email\n                        :json\n                        (when js-callback `((\"callback\" . ,js-callback)))))\n\n(defun profile (email)\n  (json:decode-json-from-string\n   (babel:octets-to-string (drakma:http-request (profile-url email nil)))))\n\n(defun qr-code-url (email &key size)\n  (generate-profile-url email\n                        :qr\n                        (when size `((\"s\" . ,(format nil \"~d\" size))))))\n"
  },
  {
    "path": "third-party/cl-gravatar/package.lisp",
    "content": "(defpackage gravatar\n  (:use #:cl)\n  (:export #:image-url #:profile #:profile-url #:qr-code-url))\n"
  },
  {
    "path": "third-party/cl-libssh2/.gitignore",
    "content": "*.FASL\n*.fasl\n*.lisp-temp\n/.quicklisp-path\n"
  },
  {
    "path": "third-party/cl-libssh2/.travis.yml",
    "content": "language: common-lisp\n\nenv:\n  matrix:\n    - LISP=sbcl\n\ninstall:\n  # Install cl-travis\n  - curl https://raw.githubusercontent.com/luismbo/cl-travis/master/install.sh | bash\n  # Install the SSH server, openssl tools, and libssh2\n  - sudo apt-get install -y openssh-server openssl libssh2-1\n  # create a group for testing\n  - sudo groupadd ssh2test\n  - sudo useradd -g ssh2test -p $(openssl passwd \"test1\") test1\n  - sudo useradd -g ssh2test -p $(openssl passwd \"test2\") test2\n\nscript:\n  - cl -e '(ql:quickload :hu.dwim.stefil)'\n       -e '(ql:quickload :cffi-grovel)'\n       -e '(ql:quickload :libssh2.test)'\n       -e '(setf libssh2.test::*user1* \"test1\" libssh2.test::*password1* \"test1\")'\n       -e '(setf libssh2.test::*user2* \"test2\" libssh2.test::*password2* \"test2\")'\n       -e '(libssh2.test:run-all-tests)'\n  - ls -al /tmp/"
  },
  {
    "path": "third-party/cl-libssh2/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2012-2015 Oleksii Shevchuk\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "third-party/cl-libssh2/examples.lisp",
    "content": "(libssh2:with-ssh-connection sshc\n    (\"192.168.30.193\"\n     (libssh2:make-password-auth \"root\" \"12345677\")\n     :hosts-db (namestring\n                (merge-pathnames\n                 (make-pathname :directory '(:relative \".ssh\")\n                                :name \"libssh2-known_hosts\")\n                 (user-homedir-pathname))))\n  (let ((local/remote \"/tmp/image\")\n        (remote/local \"/os-devel/BUILDER/builds/np1U/root.img\")\n        (stream/type  '(unsigned-byte 8)))\n    (print \"---1---\")\n    (libssh2:with-scp-input (in sshc remote/local stat)\n      (with-open-file (out local/remote\n                           :direction :output\n                           :if-exists :supersede\n                           :if-does-not-exist :create\n                           :element-type stream/type)\n        (cl-fad:copy-stream in out)))\n    (print \"---2--\")\n    (with-open-file (in local/remote\n                        :direction :input\n                        :element-type stream/type)\n      (libssh2:with-scp-output (out sshc local/remote\n                                    (file-length in))\n        (cl-fad:copy-stream in out)))\n    (print \"---3---\")\n    (libssh2:with-execute* (in sshc (format nil \"md5sum ~a ~a\"\n                                            remote/local\n                                            local/remote))\n      (loop for line = (read-line in nil)\n         while line do (print line)))))\n"
  },
  {
    "path": "third-party/cl-libssh2/libssh2.asd",
    "content": ";;; -*- mode: Lisp; syntax: Common-Lisp; -*-\n\n(in-package :asdf)\n\n(cl:eval-when (:load-toplevel :execute)\n  (asdf:operate 'asdf:load-op 'cffi-grovel))\n\n(defsystem :libssh2\n  :description \"Trivial libssh2 bindings\"\n  :version      \"0.1\"\n  :author       \"Oleksii Shevchuk <alxchk@gmail.com>\"\n  :license      \"Public Domain\"\n  :depends-on   (:babel\n                 :cffi\n                 :log4cl\n                 :cl-fad\n                 :hu.dwim.logger\n                 :split-sequence\n                 :trivial-gray-streams\n                 :usocket)\n  :serial       t\n  :components   ((:module \"src\"\n                  :serial t\n                  :components ((:file \"package\")\n                               (:file \"logging\")\n                               (:file \"types\")\n                               (cffi-grovel:grovel-file \"libssh2-libc-cffi\")\n                               (:file \"util\")\n                               (:file \"libssh2-cffi\")\n                               (:file \"streams\")\n                               (:file \"scp\")\n                               (:file \"sftp\")\n                               (:file \"solutions\")))))\n"
  },
  {
    "path": "third-party/cl-libssh2/libssh2.test.asd",
    "content": ";;; -*- mode: lisp; syntax: common-lisp; indent-tabs-mode: nil -*-\n\n(in-package :asdf)\n\n(defsystem :libssh2.test\n  :description \"cl-libssh2 tests\"\n  :author       \"Oleksii Shevchuk <alxchk@gmail.com>\"\n  :license      \"Public Domain\"\n  :depends-on   (#:libssh2\n                 #:hu.dwim.stefil)\n  :serial       t\n  :components   ((:module \"test\"\n                  :components\n                  ((:file \"package\")\n                   (:file \"scp\")\n                   (:file \"sftp\")))))\n\n(defmethod perform ((o test-op) (c (eql (find-system :libssh2.test))))\n  (funcall (intern (string '#:run-unit-tests) '#:libssh2.test)))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/libssh2-cffi.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n(defparameter *ssh-connection* nil\n  \"Dynamic variable which holds an instance of SSH-CONNECTION which is\n  a wrapper around the libssh2 'session' pointer, and contains\n  additional information about host, port etc.\")\n\n(defparameter *sftp-session* nil\n  \"Dynamic variable which is bound to a foreign reference representing\n  the SFTP session (in libssh2 terms).\")\n\n(defmacro result-or-error (&body body)\n  `(let* ((results (multiple-value-list (progn ,@body)))\n          (throwable-errors *errors-list*)\n          (result (car results))\n          (result-keyword (typecase result\n                            (keyword result)\n                            (integer (foreign-enum-keyword '+ERROR-CODE+ result :errorp nil))\n                            (t (session-last-errno (session *ssh-connection*))))))\n     (ssh2.dribble \"Result of ~A: ~A keyword: ~A\" ',(caar body) results result-keyword)\n     (if (find result-keyword throwable-errors)\n         (error 'ssh-generic-error\n                 :code result-keyword\n                 :message (if (eql result-keyword :error-sftp-protocol)\n                              (libssh2-sftp-last-error *sftp-session*)\n                              (session-last-error (session *ssh-connection*))))\n         (values-list results))))\n\n(defun print-memory (addr size)\n  (format t \"~{~x ~}\"\n          (loop for i below size\n                collect (mem-aref addr :unsigned-char i))))\n\n(define-foreign-library libssh2\n  (:darwin \"libssh2.dylib\")\n  (:unix  \"libssh2.so.1\")\n  (:win32 \"libssh2.dll\")\n  (t (:default \"libssh2\")))\n\n(use-foreign-library libssh2)\n\n(defcfun (\"libssh2_init\" %library-init) +ERROR-CODE+)\n\n#+mswindows\n(pushnew #P\"C:/Program Files (x86)/libssh2/bin/\"\n         cffi:*foreign-library-directories* :test #'equal)\n\n(defun library-init ()\n  (result-or-error\n    (%library-init)))\n\n(defcfun (\"libssh2_version\" %library-version) :string\n  (required :int))\n\n(defcfun (\"libssh2_exit\" library-exit) :void)\n\n(defcfun (\"libssh2_session_init_ex\" session-init-ex) +session+\n  (alloc :pointer) (free :pointer) (realloc :pointer) (abstract :pointer))\n(defcfun (\"libssh2_session_free\" %session-free) +ERROR-CODE+\n  (session +session+))\n(defun session-free (session)\n  (%session-free session))\n\n(defcfun (\"libssh2_session_last_error\" %session-last-error) +ERROR-CODE+\n  (session +session+)\n  (error-message :pointer) (error-message-buffer-size :pointer)\n  (ownership :int))\n\n(defun session-last-error (session)\n  (with-foreign-objects ((fo-error-message-buffer-ptr   :pointer 1)\n                         (fo-error-message-buffer-size  :int     1))\n    (let ((retval (%session-last-error session\n                                       fo-error-message-buffer-ptr\n                                       fo-error-message-buffer-size\n                                       0)))\n      (let ((error-message-ptr  (mem-aref fo-error-message-buffer-ptr :pointer 0)))\n        (values-list (list (convert-from-foreign error-message-ptr :string)\n                           retval))))))\n\n\n(defcfun (\"libssh2_session_last_errno\" session-last-errno) +ERROR-CODE+\n  (session +session+))\n\n(defcfun (\"libssh2_trace\" library-trace) :void\n  (session +session+) (options +TRACE-OPTIONS+))\n\n(defcfun (\"libssh2_session_set_blocking\" session-set-blocking) :void\n  (session +session+) (blocking +BLOCKING+))\n\n(defun session-init ()\n  (let ((session (session-init-ex (null-pointer)\n                                  (null-pointer)\n                                  (null-pointer)\n                                  (null-pointer))))\n    (if (null-pointer-p session)\n        (error 'ssh-generic-error :code :UNKNOWN :message \"Could not initialise a session object with session-init-ex\")\n        (progn\n          (session-set-blocking session :NON-BLOCKING)\n          session))))\n\n(defcfun (\"libssh2_session_disconnect_ex\" %session-disconnect) +ERROR-CODE+\n  (session +session+) (reason +DISCONNECT-CODE+) (description :string) (lang :string))\n\n(defun session-disconnect (session &key\n                           (reason :AUTH-CANCELLED-BY-USER)\n                           (description \"\")\n                           (lang \"\"))\n  (with-foreign-strings ((fs-description description)\n                         (fs-lang        lang))\n    (result-or-error\n      (%session-disconnect session reason fs-description fs-lang))))\n\n(defmacro with-session ( (session) &body body )\n  `(let ((,session (session-init)))\n     (unwind-protect\n          (progn\n            ,@body)\n       (session-free ,session))))\n\n(if (foreign-symbol-pointer \"libssh2_session_handshake\")\n    (defcfun (\"libssh2_session_handshake\" %session-handshake) +ERROR-CODE+\n      (session +session+) (socket :int))\n    (defcfun (\"libssh2_session_startup\" %session-handshake) +ERROR-CODE+\n      (session +session+) (socket :int)))\n\n(defun session-handshake (session socket)\n  (result-or-error\n    (%session-handshake session socket)))\n\n(defcfun (\"libssh2_userauth_list\" %session-auth-methods-list) :string\n  (session +session+) (username :string) (username-length :unsigned-int))\n\n(defun session-auth-methods-list (session username)\n  (with-foreign-string ((fs-username fs-username-size) username)\n    (let ((result  (%session-auth-methods-list\n                    session fs-username (- fs-username-size 1))))\n      (if result\n          (mapcar (lambda (item) (intern (string-upcase item) 'keyword))\n                  (split-sequence:split-sequence\n                   #\\, result))\n          (result-or-error\n            (session-last-errno session))))))\n\n(defcfun (\"libssh2_agent_init\" %agent-init) +ssh-agent+\n  (session +session+))\n\n(defmacro with-agent ((agent session) &body body)\n  `(let ((,agent (agent-init ,session)))\n     (unwind-protect\n          (progn ,@body)\n       (unless (null-pointer-p ,agent)\n         (agent-free ,agent)))))\n\n(defun agent-init (session)\n  (let ((agent (%agent-init session)))\n    (if (null-pointer-p agent)\n        (result-or-error\n          (session-last-errno session))\n        agent)))\n\n(defcfun (\"libssh2_agent_free\" agent-free) :void\n  (agent +ssh-agent+))\n\n(defcfun (\"libssh2_agent_connect\" %agent-connect) +ERROR-CODE+\n  (agent +ssh-agent+))\n(defun agent-connect (agent)\n  (result-or-error\n    (%agent-connect agent)))\n\n(defcfun (\"libssh2_agent_disconnect\" %agent-disconnect) +ERROR-CODE+\n  (agent +ssh-agent+))\n(defun agent-disconnect (agent)\n  (result-or-error\n    (%agent-disconnect agent)))\n\n(defcfun (\"libssh2_agent_list_identities\" %agent-list-identies) +ERROR-CODE+\n  (agent +ssh-agent+))\n(defun agent-list-identies (agent)\n  (result-or-error\n    (%agent-list-identies agent)))\n\n(defcfun (\"libssh2_agent_get_identity\" %agent-get-identity) +IDENTITY-AMOUNT+\n  (agent +ssh-agent+)\n  (store :pointer) (previous-public-key :pointer))\n\n(defun agent-identities-iterator (agent)\n  (when (eq  (agent-list-identies agent) :ERROR-NONE)\n    (let ((agent agent)\n          (prev  (null-pointer)))\n      (lambda ()\n        (with-foreign-object (store :pointer)\n          (unless (eq (%agent-get-identity agent store prev)\n                      :END)\n            (setf prev\n                  (mem-aref store :pointer 0))))))))\n\n(defmacro foreach-agent-identity ((identy agent) &body body)\n  `(let ((agent ,agent)\n         (list-identies (agent-list-indenties ,agent))\n         (prev (null-pointer)))\n     (if (eq list-identies :ERROR-NONE)\n         (with-foreign-object (store :pointer)\n           (labels\n               ((process-next-identity ()\n                  (unless (eq (--agent-get-identity agent store prev)\n                              :END)\n                    (let ((,identy (setf prev\n                                         (mem-aref store :pointer 0))))\n                      ,@body\n                      (process-next-identity)))))\n             (process-next-identity))))))\n\n(defcfun (\"libssh2_knownhost_init\" %known-hosts-init) +known-hosts+\n  (session +session+))\n(defun known-hosts-init (session)\n  (let ((known-hosts (%known-hosts-init session)))\n    (if (null-pointer-p known-hosts)\n        (result-or-error\n          (session-last-errno session))\n        known-hosts)))\n\n(defcfun (\"libssh2_knownhost_free\" known-hosts-free) :void\n  (known-hosts +known-hosts+))\n\n(defcfun (\"libssh2_knownhost_readfile\" %known-hosts-readfile) :int\n  (known-hosts +known-hosts+) (filename :string) (type :int))\n\n(defcfun (\"libssh2_knownhost_writefile\" %known-hosts-writefile) :int\n  (known-hosts +known-hosts+) (filename :string) (type :int))\n\n(defun known-hosts-readfile (hosts file)\n  (with-foreign-string (foreign-file file)\n    (let ((ret (%known-hosts-readfile hosts foreign-file 1)))\n      (if (>= ret 0)\n          (convert-from-foreign 0 '+ERROR-CODE+)\n          (result-or-error\n           (convert-from-foreign ret '+ERROR-CODE+))))))\n\n(defun known-hosts-writefile (hosts file)\n  (with-foreign-string (foreign-file file)\n    (let ((ret (%known-hosts-writefile hosts foreign-file 1)))\n      (if (>= ret 0)\n          (convert-from-foreign 0 '+ERROR-CODE+)\n          (result-or-error\n            (convert-from-foreign ret '+ERROR-CODE+))))))\n\n(defcfun (\"libssh2_session_hostkey\" %session-hostkey)  +key+\n  (session +session+) (len :pointer) (type :pointer))\n\n(defun session-hostkey (session)\n  (with-foreign-objects ((len :unsigned-int 1)\n                        (type :int 1))\n    (let ((result (%session-hostkey session len type)))\n      (make-key :data result\n                :size (mem-aref len :long 0)\n                :type (mem-aref type :int 0)))))\n\n(defcfun (\"libssh2_hostkey_hash\" session-hostkey-hash) +keyhash+\n  (session +session+) (hash-type +HASH-TYPE+))\n\n(defun session-hostkey-fingerprint (session &optional (type :SHA1))\n  (let ((hash (session-hostkey-hash session type)))\n    (format nil \"~{~2,'0X~^:~}\"\n            (loop for i below (if (eq type :SHA1) 20 16)\n               collect (mem-aref hash :unsigned-char i)))))\n\n(defcfun (\"libssh2_knownhost_checkp\" %known-hosts-checkp) +CHECK-VERDICT+\n  (known-hosts +known-hosts+) (hostname :string) (port :int)\n  (key +key+) (key-data-size :unsigned-int)\n  (type :int)  (known-host :pointer))\n\n(defcfun (\"libssh2_knownhost_check\" %known-hosts-check) +CHECK-VERDICT+\n  (known-hosts +known-hosts+) (hostname :string)\n  (key +key+) (key-data-size :unsigned-int)\n  (type :int)  (known-host :pointer))\n\n(defun known-hosts-check (known-hosts hostname key\n                          &key\n                            (port nil)\n                            (flags '(.type-plain. .raw.))\n                            (known-host (null-pointer)))\n  (let ((fp (key-data key)))\n    (if (null-pointer-p fp)\n        (error 'ssh-generic-error :code :UNKNOWN :message \"Host key is null\")\n        (with-foreign-string (fs-hostname hostname)\n          (with-foreign-object (hostinfo :pointer 1)\n            (setf (mem-aref hostinfo :pointer 0) known-host)\n            (if port\n                (%known-hosts-checkp known-hosts fs-hostname port\n                                     fp\n                                     (key-size key)\n                                     (foreign-bitfield-value '+known-hosts-flags+ flags)\n                                     hostinfo)\n                (%known-hosts-check known-hosts fs-hostname\n                                    fp\n                                    (key-size key)\n                                    (foreign-bitfield-value '+known-hosts-flags+ flags)\n                                    hostinfo)))))))\n\n(define-condition known-hosts-reading-error (ssh-generic-error)\n  ((file :type     string\n         :initarg  :file\n         :accessor file)))\n\n(defmethod print-object :after ((khre known-hosts-reading-error) stream)\n  (format stream \"// ~a\" (file khre)))\n\n(defmacro with-known-hosts ( ( known-hosts (session known-hosts-filename)) &body body)\n  `(let ((,known-hosts (known-hosts-init ,session))\n         (*errors-list* (remove :ERROR-FILE *default-errors-list*)))\n     (unwind-protect\n          (if (and (not (null-pointer-p ,known-hosts))\n                   (or (eq :error-none (known-hosts-readfile ,known-hosts ,known-hosts-filename))\n                       (eq :error-none (known-hosts-writefile ,known-hosts ,known-hosts-filename))))\n              (progn\n                ,@body)\n              (with-last-error (,session known-hosts-reading-error) :file ,known-hosts-filename)))\n     (unless (null-pointer-p ,known-hosts)\n       (known-hosts-free ,known-hosts))))\n\n(defcfun (\"libssh2_knownhost_addc\" %known-hosts-addc) +ERROR-CODE+\n  (known-hosts (:pointer (:struct +known-host+)))\n  (host :string) (salt :string) (key :pointer) (key-length :unsigned-int)\n  (comment :string) (comment-length :unsigned-int)\n  (typemask :int) (known-host (:pointer (:struct +known-host+))))\n\n(defun known-hosts-add (known-hosts host-full-string key\n                        &key\n                          (comment \"\")\n                          (flags '(.type-plain. .raw. .ssh.))\n                          (salt  \"\")\n                          (store (null-pointer)))\n  (if (and (not (null-pointer-p known-hosts))\n           (not (null-pointer-p (key-data key)))\n           (stringp host-full-string))\n      (with-foreign-strings ((fs-host-full-string host-full-string)\n                             (fs-salt     salt)\n                             ((fs-comment fs-comment-size) comment))\n        (result-or-error\n          (%known-hosts-addc known-hosts\n                             fs-host-full-string fs-salt\n                             (key-data key) (key-size key)\n                             fs-comment (- fs-comment-size 1)\n                             (foreign-bitfield-value '+known-hosts-flags+ flags)\n                             store)))))\n\n(defcfun (\"libssh2_agent_userauth\" %agent-userauth) +ERROR-CODE+\n  (agent +ssh-agent+) (username :string) (identity :pointer))\n\n(defun user-auth-agent (agent username identity)\n  (with-foreign-string (fs-username username)\n    (result-or-error\n      (%agent-userauth agent fs-username identity))))\n\n(defcfun (\"libssh2_userauth_password_ex\" %user-auth-password) +ERROR-CODE+\n  (session +session+)\n  (username :string) (username-length :unsigned-int)\n  (password :string) (password-length :unsigned-int)\n  (password-change :pointer))\n\n(defun user-auth-password (session username password &optional (callback (null-pointer)))\n  (with-foreign-strings (((fs-username fs-username-size) username)\n                         ((fs-password fs-password-size) password))\n    (result-or-error\n      (%user-auth-password session\n                           fs-username (- fs-username-size 1)\n                           fs-password (- fs-password-size 1)\n                           callback))))\n\n(defcfun (\"libssh2_userauth_keyboard_interactive_ex\" %user-auth-interactive) +ERROR-CODE+\n  (session +session+)\n  (username :string) (username-length :unsigned-int)\n  (callback :pointer))\n\n(defun user-auth-interactive (session username callback)\n  (with-foreign-string ((fs-username fs-username-size) username)\n    (%user-auth-interactive session\n                            fs-username\n                            (- fs-username-size 1)\n                            callback)))\n\n(defvar *keyboard-interactive-password* \"\")\n(defcallback trivial-keyboard-interactive-emulation :void\n    ((login :pointer)      (login-length       :unsigned-int)\n     (instruction :string) (instruction-length :unsigned-int)\n     (num-prompts :int)\n     (prompts   (:pointer (:struct +kbd-prompt+)))\n     (responses (:pointer (:struct +kbd-response+)))\n     (abstract  (:pointer :pointer)))\n  ;; Just don't care about input. Only send password\n  ;; Please, write you'r own callback, if you care\n  (declare\n   (ignore login)       (ignore login-length)\n   (ignore instruction) (ignore instruction-length)\n   (ignore prompts)     (ignore abstract))\n  (loop for i below num-prompts\n     do\n       (with-foreign-slots ((text length)\n                            (mem-aref responses '+kbd-response+ i)\n                            +kbd-response+)\n         (setf text   (foreign-string-alloc *keyboard-interactive-password*))\n         (setf length (foreign-funcall \"strlen\" :pointer text :unsigned-int)))))\n\n(defun user-auth-interactive-trivial (session username password)\n  (let ((*keyboard-interactive-password* password))\n    (user-auth-interactive session username\n                           (callback trivial-keyboard-interactive-emulation))))\n\n(defcfun (\"libssh2_userauth_publickey_fromfile_ex\" %user-auth-publickey) +ERROR-CODE+\n  (session +session+)\n  (username :string) (username-len :unsigned-int)\n  (public-key :string)\n  (private-key :string) (password :string))\n\n(defun user-auth-publickey (session username public-key private-key password)\n  (with-foreign-strings (((fs-username fs-username-size) username)\n                         (fs-public-key  public-key)\n                         (fs-private-key private-key)\n                         (fs-password    password))\n    (result-or-error\n      (%user-auth-publickey session fs-username (- fs-username-size 1)\n                            fs-public-key fs-private-key fs-password))))\n\n(defcfun (\"libssh2_channel_open_ex\" %channel-open-ex) +channel+\n  (session +session+) (channel-type :string) (channel-type-length :unsigned-int)\n  (window-size :unsigned-int) (packet-size :unsigned-int)\n  (message :string) (message-length :unsigned-int))\n\n(defun channel-open (session &key (channel-type \"session\")\n                               (window-size 262144)\n                               (packet-size 32768)\n                               (message \"\"))\n  (loop while t do\n   (with-foreign-strings (((fs-channel-type fs-channel-type-size) channel-type)\n                          ((fs-message      fs-message-size)      message))\n     (let* ((pass-message (if (string= message \"\")\n                              (null-pointer)\n                              fs-message))\n            (pass-message-size (if (string= message \"\")\n                                   0\n                                   (- fs-message-size 1)))\n            (new-channel\n              (%channel-open-ex session\n                                fs-channel-type (- fs-channel-type-size 1)\n                                window-size packet-size\n                                pass-message\n                                pass-message-size)))\n       (cond\n         ((not (null-pointer-p new-channel))\n          (return new-channel))\n         (t\n          (let ((err (session-last-errno session)))\n            (case err\n              (:error-eagain\n               (wait-for-fd-output session))\n              (otherwise\n               (return\n                 (result-or-error (identity err))))))))))))\n\n(defcfun (\"libssh2_channel_close\" %channel-close) +ERROR-CODE+\n  (channel +channel+))\n(defun channel-close (channel)\n  (result-or-error\n    (%channel-close channel)))\n\n(defcfun (\"libssh2_channel_free\" %channel-free) +ERROR-CODE+\n  (channel +channel+))\n\n(defun channel-free (channel)\n  (loop while t do\n    (let ((err (%channel-free channel)))\n      (cond\n        ((eql :error-eagain err)\n         (log:trace \"wait-for-fd channel-free\")\n         (wait-for-fd-output channel))\n       (t\n        (return (result-or-error\n                  (identity err))))))))\n\n(defcfun (\"libssh2_channel_wait_closed\" %channel-wait-closed) +ERROR-CODE+\n  (channel +channel+))\n\n(defun channel-wait-closed (channel)\n  (loop while t do\n    (let ((err (%channel-wait-closed channel)))\n      (cond\n        ((eql :error-eagain err)\n         (log:trace \"wait-for-fd channel-wait-closed\")\n         (wait-for-fd-output channel))\n        (t\n         (return (result-or-error\n                   (identity err))))))))\n\n(defcfun (\"libssh2_channel_wait_eof\" %channel-wait-eof) +ERROR-CODE+\n  (channel +channel+))\n(defun channel-wait-eof (channel)\n  (loop while t do\n    (let ((err (%channel-wait-eof channel)))\n      (case err\n        (:error-eagain\n         (log:trace \"wait-for-fd channel-wait-eof\")\n         (wait-for-fd channel))\n        (otherwise\n         (return\n          (result-or-error (identity err))))))))\n\n\n(defcfun (\"libssh2_channel_process_startup\" %channel-process-startup) +ERROR-CODE+\n  (channel +channel+)\n  (request :string) (request-length :unsigned-int)\n  (message :string) (message-length :unsigned-int))\n\n(defcfun (\"libssh2_channel_setenv_ex\" %channel-setenv-ex) +ERROR-CODE+\n  (channel +channel+)\n  (varname :string) (varname-len :int)\n  (value :string) (value-len :int))\n\n(defun channel-setenv (channel name value)\n  (with-foreign-strings (((fs-name  fs-name-size)  name)\n                         ((fs-value fs-value-size) value))\n    (result-or-error\n      (%channel-setenv-ex channel\n                          fs-name  (- fs-name-size 1)\n                          fs-value (- fs-value-size 1)))))\n\n(defun channel-process-start (channel request message)\n  (loop while t do\n   (with-foreign-strings (((fs-request fs-request-size) request)\n                          ((fs-message fs-message-size) message))\n     (let ((err (%channel-process-startup channel\n                                          fs-request (- fs-request-size 1)\n                                          fs-message (- fs-message-size 1))))\n       (case err\n         (:error-eagain\n          (wait-for-fd-output channel))\n         (otherwise\n          (return\n           (result-or-error\n             (identity err)))))))))\n\n(defun channel-exec (channel cmd)\n  (channel-process-start channel \"exec\" cmd))\n\n(defun channel-shell (channel cmd)\n  (channel-process-start channel \"shell\" cmd))\n\n(defun channel-subsystem (channel cmd)\n  (channel-process-start channel \"subsystem\" cmd))\n\n(defcfun (\"libssh2_channel_read_ex\" %channel-read-ex) :int\n  (channel +CHANNEL+) (stream +STREAM-ID+)\n  (buffer :pointer) (buffer-length :unsigned-int))\n\n(defcfun (\"libssh2_channel_flush_ex\" %channel-flush-ex) :int\n  (channel +CHANNEL+) (stream +STREAM-ID+))\n\n(defun channel-flush (channel)\n  (loop while t do\n    (let ((ret (%channel-flush-ex channel :ALL)))\n      (cond\n        ((> ret 0)\n         (return :ERROR-NONE))\n        ((eql +eagain+ ret)\n         (wait-for-fd-output channel))\n        (t\n         (return\n          (result-or-error\n            (convert-from-foreign ret '+ERROR-CODE+))))))))\n\n(defvar *channel-read-type* :STDOUT)\n(defvar *channel-read-zero-as-eof* nil)\n(defun channel-read (channel output-buffer &key (start 0) (end nil) (type *channel-read-type*)\n                                             (non-blocking nil))\n  (with-pointer-to-vector-data (buffer output-buffer)\n    (loop while t do\n      (let ((ret (%channel-read-ex channel type\n                                   (inc-pointer buffer start)\n                                   (if end\n                                       (- (min end (length output-buffer))\n                                          start)\n                                       (- (length output-buffer)\n                                          start)))))\n        (cond\n          ((>= ret 0)\n            (return\n              (values\n               ret\n               (cond\n                 #+nil\n                 ((and (= ret 0) *channel-read-zero-as-eof*) t)\n                 ((= ret 0)\n                  t\n                  #+nil\n                  (let ((eofp (channel-eofp channel)))\n                    (log:info \"Got eofp: ~a\" eofp)\n                    eofp))\n                 (t nil)))))\n          ((eql +eagain+ ret)\n           (cond\n             (non-blocking\n              (return +eagain+))\n             (t\n              (wait-for-fd channel))))\n          (t\n           (return\n             (result-or-error\n               (convert-from-foreign ret '+ERROR-CODE+)))))))))\n\n(defcfun (\"libssh2_channel_write_ex\" %channel-write-ex) :int\n  (channel +CHANNEL+) (stream +STREAM-ID+)\n  (buffer :pointer) (buffer-length :unsigned-int))\n\n(defun wait-for-fd (channel)\n  (declare (ignore channel))\n  (log:trace \"wait for fd called\")\n  (sleep 0.01))\n\n(defun wait-for-fd-output (channel)\n  (declare (ignore channel))\n  (log:trace \"wait for fd output called\")\n  (sleep 0.01))\n\n(defmacro channel-write-with-conv (name conv)\n  `(defun ,name (channel data &key (start 0) (end nil) (type *channel-read-type*))\n     (,conv (buffer data)\n            (loop while t do\n              (let ((ret (%channel-write-ex channel type\n                                            (inc-pointer buffer start)\n                                            (if end\n                                                (- (min end (length data))\n                                                   start)\n                                                (- (length data)\n                                                   start)))))\n                (cond\n                  ((>= ret 0)\n                   (log:trace \"Wrote ~a bytes\" ret)\n                   (return ret))\n                  ((eql +eagain+ ret)\n                   (log:trace \"Wait-for-fd in write-with-conv\")\n                   (wait-for-fd-output channel))\n                  (t\n                   (return\n                    (result-or-error\n                      (convert-from-foreign ret '+ERROR-CODE+))))))))))\n\n(channel-write-with-conv channel-write with-pointer-to-vector-data)\n(channel-write-with-conv channel-write-string with-foreign-string)\n\n(defcfun (\"libssh2_channel_send_eof\" %channel-send-eof) +ERROR-CODE+\n  (channel +channel+))\n\n(defun channel-send-eof (channel)\n  (loop while t do\n    (let ((err (%channel-send-eof channel)))\n      (case err\n        (+eagain+\n         (log:trace \"wait-for-fd channel-send-eof\")\n         (wait-for-fd-output channel))\n        (otherwise\n         (return\n          (result-or-error (identity err))))))))\n\n(defcfun (\"libssh2_channel_eof\" %channel-eofp) +CHANNEL-EOF+\n  (channel +channel+))\n(defun channel-eofp (channel)\n  (eq (%channel-eofp channel) :EOF))\n\n(defcfun (\"libssh2_channel_get_exit_status\" channel-exit-status) :int\n  (channel +channel+))\n\n;; (defcfun (\"libssh2_channel_get_exit_signal\" --channel-exit-signal) +ERROR-CODE+\n;;  (channel +channel+)\n\n(defcfun (\"libssh2_scp_recv\" %scp-recv) +channel+\n  (session +session+) (path :string) (stat +stat+))\n\n(defun channel-scp-recv (session path)\n  (with-foreign-string (fs-path path)\n    (with-foreign-object (stat '+stat+ 1)\n      (let ((result (%scp-recv session path stat)))\n        (if (null-pointer-p result)\n            (result-or-error\n              (session-last-errno session))\n            (progn\n              (channel-send-eof result)\n              (values result\n                      (with-foreign-slots ((mode mtime atime) stat +stat+)\n                        (list :mode  mode\n                              :mtime mtime\n                              :atime atime)))))))))\n\n(defcfun (\"libssh2_scp_send_ex\" %scp-send-ex) +channel+\n  (session +session+) (path :string) (mode :int) (size :unsigned-int)\n  (mtime :long) (atime :long))\n\n(defun get-universal-unix-time ()\n  (- (get-universal-time)\n     (encode-universal-time 0 0 0 1 1 1970 0)))\n\n(defun channel-scp-send (session path size\n                         &key mode mtime atime)\n  (declare (optimize speed))\n  (unless mode  (setq mode #b110100000))\n  (unless mtime (setq mtime (get-universal-unix-time)))\n  (unless atime (setq atime mtime))\n  (with-foreign-string (fs-path path)\n   (loop while t do\n     (let ((result (%scp-send-ex session fs-path\n                                 mode size mtime\n                                 atime)))\n       (if (null-pointer-p result)\n           (let ((errno (session-last-errno session)))\n             (case errno\n               (:error-eagain\n                (wait-for-fd session))\n               (otherwise\n                (return\n                  (result-or-error\n                    (session-last-errno session))))))\n           (return result))))))\n\n(defcfun (\"libssh2_channel_direct_tcpip_ex\" %channel-direct-tcpip-ex) +channel+\n  (session +session+)\n  (host :string)\n  (port :int)\n  (shost :string)\n  (sport :int))\n\n\n;;; SFTP related\n\n\n(defmacro defcfun-error-check ((c-function-name lisp-function-name) result-type &rest rest)\n  (let ((internal-name (alexandria:symbolicate \"%\" (symbol-name lisp-function-name)))\n        (args (mapcar #'first rest)))\n    `(progn\n       (defcfun (,c-function-name ,internal-name) ,result-type\n         ,@rest)\n       (defun ,lisp-function-name ,args\n         (result-or-error (,internal-name ,@args))))))\n\n(defcstruct _libssh2-sftp-handle)\n\n(defctype libssh2-sftp-handle (:pointer (:struct _libssh2-sftp-handle)))\n\n(defctype size-t :unsigned-long)\n\n(defctype libssh2-uint-64-t :unsigned-long-long)\n\n(defcstruct _libssh2-sftp-attributes\n  (flags :unsigned-long)\n  (filesize libssh2-uint-64-t)\n  (uid :unsigned-long)\n  (gid :unsigned-long)\n  (permissions :unsigned-long)\n  (atime :unsigned-long)\n  (mtime :unsigned-long))\n\n(defctype libssh2-sftp-attributes (:pointer (:struct _libssh2-sftp-attributes)))\n\n(defcfun-error-check (\"libssh2_sftp_readdir_ex\" libssh2-sftp-readdir-ex) :int\n  (handle :pointer)\n  (buffer (:pointer :char))\n  (buffer-maxlen size-t)\n  (longentry (:pointer :char))\n  (longentry-maxlen size-t)\n  (attrs libssh2-sftp-attributes))\n\n(defcstruct _libssh2-sftp)\n\n(defctype libssh2-sftp (:pointer (:struct _libssh2-sftp)))\n\n(defcstruct _libssh2-sftp-statvfs\n  (f-bsize libssh2-uint-64-t)\n  (f-frsize libssh2-uint-64-t)\n  (f-blocks libssh2-uint-64-t)\n  (f-bfree libssh2-uint-64-t)\n  (f-bavail libssh2-uint-64-t)\n  (f-files libssh2-uint-64-t)\n  (f-ffree libssh2-uint-64-t)\n  (f-favail libssh2-uint-64-t)\n  (f-fsid libssh2-uint-64-t)\n  (f-flag libssh2-uint-64-t)\n  (f-namemax libssh2-uint-64-t))\n\n(defctype libssh2-sftp-statvfs (:pointer (:struct _libssh2-sftp-statvfs)))\n\n(defcfun (\"libssh2_sftp_statvfs\" %libssh2-sftp-statvfs) :int\n  (sftp :pointer)\n  (path :string)\n  (path-len size-t)\n  (st :pointer))\n\n(defun libssh2-sftp-statvfs (sftp path st)\n  (with-foreign-strings (((fs-path fs-path-len) path))\n    (result-or-error\n      (%libssh2-sftp-statvfs sftp fs-path (- fs-path-len 1) st))))\n\n(defcfun-error-check (\"libssh2_sftp_read\" libssh2-sftp-read) size-t\n                     (handle :pointer)\n                     (buffer (:pointer :char))\n                     (buffer-maxlen size-t))\n\n(defcfun (\"libssh2_sftp_symlink_ex\" %libssh2-sftp-symlink-ex) :int\n  (sftp :pointer)\n  (path :string)\n  (path-len :unsigned-int)\n  (target :string)\n  (target-len :unsigned-int)\n  (link-type :int))\n\n(defun libssh2-sftp-symlink-ex (sftp source target link-type)\n  (with-foreign-strings (((fs-source fs-source-len) source)\n                         ((fs-target fs-target-len) target))\n    (result-or-error\n      (%libssh2-sftp-symlink-ex sftp fs-source (- fs-source-len 1) fs-target (- fs-target-len 1) link-type))))\n\n(defcfun-error-check (\"libssh2_sftp_shutdown\" libssh2-sftp-shutdown) :int\n                     (sftp :pointer))\n\n(defcfun-error-check (\"libssh2_sftp_close_handle\" libssh2-sftp-close-handle) :int\n                     (handle :pointer))\n\n(defcfun-error-check (\"libssh2_sftp_tell64\" libssh2-sftp-tell-64) libssh2-uint-64-t\n                     (handle :pointer))\n\n(defcfun-error-check (\"libssh2_sftp_write\" libssh2-sftp-write) size-t\n                     (handle :pointer) (buffer :pointer)\n                     (count size-t))\n\n(defcfun (\"libssh2_sftp_rmdir_ex\" %libssh2-sftp-rmdir-ex) :int\n  (sftp :pointer)\n  (path :string)\n  (path-len :unsigned-int))\n\n(defun libssh2-sftp-rmdir-ex (sftp path)\n  (with-foreign-strings (((fs-path fs-path-len) path))\n    (result-or-error\n      (%libssh2-sftp-rmdir-ex sftp fs-path (- fs-path-len 1)))))\n\n(defcfun (\"libssh2_sftp_last_error\" libssh2-sftp-last-error) sftp-error-code\n  (sftp :pointer))\n\n(defcfun (\"libssh2_sftp_rename_ex\" %libssh2-sftp-rename-ex) :int\n  (sftp :pointer)\n  (source-filename :string)\n  (source-filename-len :unsigned-int)\n  (dest-filename :string)\n  (dest-filename-len :unsigned-int)\n  (flags :long))\n\n(defun libssh2-sftp-rename-ex (sftp source dest flags)\n  (with-foreign-strings (((fs-source fs-source-len) source)\n                         ((fs-dest fs-dest-len) dest))\n    (result-or-error\n      (%libssh2-sftp-rename-ex sftp fs-source (- fs-source-len 1) fs-dest (- fs-dest-len 1) flags))))\n\n(defcfun-error-check (\"libssh2_sftp_seek64\" libssh2-sftp-seek-64) :void\n  (handle :pointer)\n  (offset libssh2-uint-64-t))\n\n(defcfun-error-check (\"libssh2_sftp_init\" libssh2-sftp-init) :pointer\n                     (session +session+))\n\n(defcfun-error-check (\"libssh2_sftp_seek\" libssh2-sftp-seek) :void\n                     (handle :pointer)\n                     (offset size-t))\n\n(defcfun-error-check (\"libssh2_sftp_fstat_ex\" libssh2-sftp-fstat-ex) :int\n                     (handle :pointer)\n                     (attrs :pointer)\n                     (setstat :int))\n\n(defcfun-error-check (\"libssh2_sftp_get_channel\" libssh2-sftp-get-channel) :pointer\n                     (sftp :pointer))\n\n(defcfun-error-check (\"libssh2_sftp_fstatvfs\" libssh2-sftp-fstatvfs) :int\n                     (handle :pointer)\n                     (st :pointer))\n\n(defcfun (\"libssh2_sftp_stat_ex\" %libssh2-sftp-stat-ex) :int\n  (sftp :pointer)\n  (path :string)\n  (path-len :unsigned-int)\n  (stat-type :int)\n  (attrs :pointer))\n\n(defun libssh2-sftp-stat-ex (sftp path stat-type attrs)\n  (with-foreign-strings (((fs-path fs-path-len) path))\n    (result-or-error\n      (%libssh2-sftp-stat-ex sftp fs-path (- fs-path-len 1) stat-type attrs))))\n\n(defcfun-error-check (\"libssh2_sftp_tell\" libssh2-sftp-tell) size-t\n                     (handle :pointer))\n\n(defcfun (\"libssh2_sftp_mkdir_ex\" %libssh2-sftp-mkdir-ex) :int\n  (sftp :pointer)\n  (path :string)\n  (path-len :unsigned-int)\n  (mode :long))\n\n(defun libssh2-sftp-mkdir-ex (sftp path mode)\n  (with-foreign-strings (((fs-path fs-path-len) path))\n    (result-or-error\n      (%libssh2-sftp-mkdir-ex sftp fs-path (- fs-path-len 1) mode))))\n\n(defcfun (\"libssh2_sftp_unlink_ex\" %libssh2-sftp-unlink-ex) :int\n  (sftp :pointer)\n  (filename :string)\n  (filename-len :unsigned-int))\n\n(defun libssh2-sftp-unlink-ex (sftp filename)\n  (with-foreign-strings (((fs-filename fs-filename-len) filename))\n    (result-or-error\n      (%libssh2-sftp-unlink-ex sftp fs-filename (- fs-filename-len 1)))))\n\n(defcfun (\"libssh2_sftp_open_ex\" %libssh2-sftp-open-ex) :pointer\n  (sftp :pointer)\n  (filename :string)\n  (filename-len :unsigned-int)\n  (flags :unsigned-long)\n  (mode :long)\n  (open-type sftp-open-types))\n\n(defun libssh2-sftp-open-ex (sftp filename flags mode open-type)\n  (with-foreign-strings (((fs-filename fs-filename-len) filename))\n    (result-or-error\n      (%libssh2-sftp-open-ex sftp fs-filename (- fs-filename-len 1) flags mode open-type))))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/libssh2-libc-cffi.lisp",
    "content": "(in-package :libssh2)\n\n(include \"sys/types.h\" \"sys/stat.h\")\n(cstruct +stat+ \"struct stat\"\n         (dev \"st_dev\" :type :unsigned-int)\n         (ino \"st_ino\" :type :unsigned-int)\n         (mode \"st_mode\" :type :unsigned-int)\n         (nlink \"st_nlink\" :type :unsigned-int)\n         (uid \"st_uid\" :type :unsigned-int)\n         (gid \"st_gid\" :type :unsigned-int)\n         (rdev \"st_rdev\" :type :unsigned-int)\n         (size \"st_size\" :type :unsigned-int)\n         (atime \"st_atime\" :type :unsigned-int)\n         (mtime \"st_mtime\" :type :unsigned-int)\n         (ctime \"st_ctime\" :type :unsigned-int))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/logging.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n(deflogger ssh2 ()\n  :runtime-level +info+\n  :compile-time-level +debug+\n  :documentation \"The logger for this library, use the macros\n  ssh2.{dribble|debug|info|warn|error|fatal} for logging.\n\nTo increase the verbosity:\n\n (setf (hu.dwim.logger::log-level (hu.dwim.rdbms::find-logger 'libssh2::ssh2)) hu.dwim.logger:+debug+)\")\n"
  },
  {
    "path": "third-party/cl-libssh2/src/package.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(defpackage :libssh2\n  (:use :cffi :cl :trivial-gray-streams)\n  (:export ;; LIBSSH2 API\n           :with-session\n           :session-init\n           :session-disconnect\n           :session-handshake\n           :session-last-error\n           :session-auth-methods-list\n\n           :with-agent\n           :agent-init\n           :agent-free\n           :agent-connect\n           :agent-disconnect\n           :agent-list-identies\n           :agent-identities-iterator\n           :foreach-agent-identity\n\n           :with-known-hosts\n           :known-hosts-add\n           :known-hosts-init\n           :known-hosts-free\n           :known-hosts-readfile\n           :known-hosts-writefile\n           :known-hosts-check\n           :session-hostkey\n           :session-hostkey-fingerprint\n\n           :user-auth-agent\n           :user-auth-password\n           :user-auth-publickey\n\n           :channel-open\n           :channel-close\n           :channel-free\n           :channel-wait-closed\n           :channel-setenv\n           :channel-process-start\n           :channel-exec\n           :channel-shell\n           :channel-subsystem\n           :channel-flush\n           :channel-read\n           :channel-write\n           :channel-write-string\n           :channel-eofp\n           :channel-send-eof\n           :channel-exit-status\n           :channel-scp-recv\n           :+eagain+\n           :wait-for-fd\n\n           ;; SFTP\n           :sftp-list-directory\n\n           ;; STREAMS API // BLOCKING\n\n           :AUTH-DATA\n           :SSH-CONNECTION\n           :SESSION\n           :SOCKET\n           :HOST\n           :PORT\n           :HOSTS-DB\n           :AUTH-PASSED\n           :SSH-HANDSHAKE-ERROR\n           :SSH-BAD-HOSTKEY\n           :REASON\n           :HASH\n           :CREATE-SSH-CONNECTION\n           :DESTROY-SSH-CONNECTION\n           :WITH-SSH-CONNECTION\n           :SSH-SESSION-KEY\n           :AUTH-PASSWORD\n           :SSH-VERIFY-SESSION\n           :AUTHENTICATION-METHODS\n           :AUTHENTICATION\n           :AUTH-PUBLICKEY\n           :PUBLIC-KEY\n           :PRIVATE-KEY\n           :PASSWORD\n           :AUTH-AGENT\n           :MAKE-PUBLICKEY-AUTH\n           :MAKE-AGENT-AUTH\n           :MAKE-PASSWORD-AUTH\n           :MAKE-PASSWORD-EMUL-AUTH\n           :MAKE-AUTH-DATA\n           :SSH-CHANNEL-STREAM\n           :SSH-CHANNEL-STREAM-OUTPUT\n           :SSH-CHANNEL-STREAM-INPUT\n           :SSH-CHANNEL-STREAM-INPUT/OUTPUT\n           :SSH-CHANNEL-EXEC\n           :SSH-CHANNEL-RECV\n           :SSH-CHANNEL-SEND\n           :CHANNEL\n           :INPUT-BUFFER\n           :INPUT-SIZE\n           :OUTPUT-BUFFER\n           :OUTPUT-SIZE\n           :INTPUT-POS\n           :OUTPUT-POS\n           :STREAM-ELEMENT-TYPE\n           :OPEN-STREAM-P\n           :STREAM-LISTEN\n           :STREAM-READ-BYTE\n           :STREAM-READ-SEQUENCE\n           :STREAM-READ-LINE\n           :STREAM-FORCE-OUTPUT\n           :STREAM-FINISH-OUTPUT*\n           :STREAM-FINISH-OUTPUT\n           :STREAM-WRITE-BYTE\n           :STREAM-WRITE-CHAR\n           :STREAM-WRITE-SEQUENCE\n           :CLOSE\n           :EXECUTE\n           :SCP-INPUT\n           :SCP-OUTPUT\n           :WITH-EXECUTE\n           :WITH-EXECUTE*\n           :WITH-SCP-INPUT\n           :WITH-SCP-OUTPUT\n           :scp-get\n           :scp-put\n\n           ;; CONDITIONS & SLOTS\n           :KNOWN-HOSTS-READING-ERROR\n           :HOST-NOT-ALLOWED-TO-CONNECT\n           :+TRACE-OPTIONS+\n           :+DISCONNECT-CODE+\n           :+ERROR-CODE+\n           :+BLOCKING+\n           :+IDENTITY-AMOUNT+\n           :+STREAM-ID+\n           :+HASH-TYPE+\n           :+CHECK-VERDICT+\n           :+SESSION+\n           :+KEY+\n           :+SSH-AGENT+\n           :+KNOWN-HOSTS+\n           :+KEYHASH+\n           :+CHANNEL+\n           :+KNOWN-HOSTS-FLAGS+\n           :+KNOWN-HOST+\n           :KEY\n           :SSH-GENERIC-ERROR\n           :MESSAGE\n           :CODE :FILE\n\n           ;; Restarts\n           :TRY-CREATE-FILE\n           :ACCEPT\n           :ACCEPT-ONCE\n           :ACCEPT-ALWAYS\n           :DROP\n\n           ;; Dynamic customizations\n           :*CHANNEL-READ-TYPE*\n           :*CHANNEL-READ-ZERO-AS-EOF*\n           :*ERRORS-LIST*\n           :*DEFAULT-ERRORS-LIST*\n           :channel-direct-tcpip)\n    (:import-from #:hu.dwim.logger\n                #:+dribble+\n                #:+debug+\n                #:+info+\n                #:+warn+\n                #:+error+\n                #:+fatal+\n                #:deflogger\n                #:find-logger\n                #:log-level))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/scp.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n(defun scp-get (remote-name local-name &optional (connection *ssh-connection*))\n  (with-scp-input (in connection remote-name stat)\n    (with-open-file (out local-name\n                         :direction :output\n                         :if-exists :supersede\n                         :if-does-not-exist :create\n                         :element-type '(unsigned-byte 8))\n      (cl-fad:copy-stream in out))))\n\n(defun scp-put (local-name remote-name &optional (connection *ssh-connection*))\n  (with-open-file (in local-name\n                      :direction :input\n                      :element-type '(unsigned-byte 8))\n    (with-scp-output (out connection remote-name\n                      (file-length in))\n      (cl-fad:copy-stream in out))))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/sftp.lisp",
    "content": ";; -*- mode: lisp; tab-width: 4; ident-tabs-mode: nil -*-\n\n(in-package :libssh2)\n\n(defmacro with-sftp ((sftp-session ssh-connection) &body body)\n  `(let* ((,sftp-session (libssh2-sftp-init (session ,ssh-connection)))\n          (*sftp-session* ,sftp-session))\n     (if (or (null ,sftp-session) (null-pointer-p ,sftp-session))\n         (error \"Cannot establish an SFTP session for SSH connection to ~A (port ~A)\" (host,ssh-connection) (port ,ssh-connection))\n         (unwind-protect\n              (handler-bind ((libssh2-invalid-error-code\n                              (lambda (condition)\n                                (declare (ignore condition))\n                                (throw-last-error (session ,ssh-connection)))))\n                ,@body)\n           (libssh2-sftp-shutdown ,sftp-session)))))\n\n\n(defun ends-with? (name extension)\n  (assert (> (length extension) 0))\n  (assert (> (length name) 0))\n  (let ((pos (search extension name :from-end t)))\n    (when (and pos (= pos (- (length name) (length extension))))\n      t)))\n\n(defun ends-with-any? (name extensions)\n  (dolist (ext extensions)\n    (when (ends-with? name ext)\n      (return t))))\n\n(defun sftp-list-directory (ssh-connection path &key (maxfiles most-positive-fixnum) (extensions nil))\n  \"Return a list of files in directory `PATH' on the server to which we are connected with `SSH-CONNECTION'.\nRestrict the number of files to retrieve by providing\n  - `MAXFILES' - a fixnum (which is by default set to an insanely large number)\n  - `EXTENSIONS' - a list of strings (default: nil)\n\nIt is possible to combine `MAXFILES' and `EXTENSIONS' (retrieve 5 files with extensions '(\\\".json\\\", \\\".js\\\")) \"\n\n  (with-sftp (sftp ssh-connection)\n    (let* ((handle (libssh2-sftp-open-ex sftp path 0 0 (foreign-enum-value 'sftp-open-types :dir)))\n           (buffer (foreign-alloc :char :count 1024 :initial-element 0))\n           (longentry (foreign-alloc :char :count 1024 :initial-element 0)))\n      (unwind-protect\n           (with-foreign-object (attrs '(:struct _libssh2-sftp-attributes))\n             (loop while (> (libssh2-sftp-readdir-ex handle buffer 1024 longentry 1024 attrs) 0)\n                   while (> maxfiles (length files))\n                   for attr-plist = (convert-from-foreign attrs '(:struct _libssh2-sftp-attributes))\n                   do (ssh2.dribble \"Attributes of ~A: ~A, permissions: ~A\" (foreign-string-to-lisp buffer) attr-plist (foreign-bitfield-symbols 'sftp-modes (getf attr-plist 'permissions)))\n                   when (or (null extensions)\n                            (ends-with-any? (foreign-string-to-lisp buffer) extensions)) collect (foreign-string-to-lisp buffer) into files\n                   finally (return files)))\n        (when handle (libssh2-sftp-close-handle handle))\n        (when buffer (foreign-free buffer))\n        (when longentry (foreign-free longentry))))))\n\n\n(defconstant +sftp-read-buffer-size+ 1000000)\n\n;; TODO: refactor some of the commonalities into a macro?\n(defun sftp-get (ssh-connection remote-path local-path)\n  \"Receive a remote file `PATH' on the server to which we are connected with `SSH-CONNECTION' to a local file at `LOCAL-PATH'.\"\n  (with-sftp (sftp ssh-connection)\n    (let ((handle)\n          (buffer))\n      (unwind-protect\n        (progn\n          (ssh2.debug \"Trying to retrieve remote file ~A to local file ~A\" remote-path local-path))\n          (setf handle (libssh2-sftp-open-ex sftp remote-path (foreign-bitfield-value 'sftp-flags '(:read)) 0 :file))\n          (setf buffer (foreign-alloc :char :count +sftp-read-buffer-size+ :initial-element 0))\n          (with-open-file (out local-path :direction :output :if-exists :supersede :element-type '(signed-byte 8))\n            (loop for numbytes = (libssh2-sftp-read handle buffer +sftp-read-buffer-size+)\n                  do (ssh2.dribble \"libssh2-sftp-read returned numbytes=~A\" numbytes)\n                  while (> numbytes 0)\n                  do\n                  (write-sequence (cffi:convert-from-foreign buffer `(:array :char ,numbytes)) out)))\n          (ssh2.debug \"Remote file ~A was written to ~A\" remote-path local-path))\n        (when handle (libssh2-sftp-close-handle handle))\n        (when buffer (foreign-free buffer)))))\n\n(defun sftp-delete (ssh-connection remote-path)\n  \"Delete a remote file `PATH' on the server to which we are connected with `SSH-CONNECTION'.\"\n  (with-sftp (sftp ssh-connection)\n    (ssh2.debug \"Trying to delete remote file ~A\" remote-path)\n    (let ((result (libssh2-sftp-unlink-ex sftp remote-path)))\n      (ssh2.debug \"Deleting ~A resulted in ~A.\" remote-path result))))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/solutions.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n(defvar *session* nil)\n\n(defmethod method-of ((auth auth-password))  :PASSWORD)\n(defmethod method-of ((auth auth-publickey)) :PUBLICKEY)\n(defmethod method-of ((auth auth-agent))     :PUBLICKEY)\n\n(defmethod authentication ((ssh ssh-connection) (auth-list list))\n  (unless (or (null auth-list) (not (listp auth-list)))\n    (let ((auth-methods (authentication-methods ssh (login (car auth-list))))\n          (*errors-list* (remove :ERROR-AUTHENTICATION-FAILED *errors-list*)))\n      (loop for auth in auth-list\n         do (when (and (find (method-of auth)\n                             auth-methods)\n                       (authentication ssh auth))\n              (return t))))))\n\n(defun make-auth-data (login &key\n                               (key-directories\n                                (list (default-config-directory)))\n                               (keys '((\"id_rsa\") (\"id_dsa\")))\n                               (passwords '()))\n  (unless (or (null keys)\n              (not  (listp keys))\n              (null (car keys))\n              (not  (listp (car keys)))\n              (null key-directories)\n              (not  (listp key-directories)))\n    (cons (make-agent-auth login)\n          (apply #'concatenate\n                 (append\n                  (cons 'list\n                        (loop for dir in key-directories\n                           collect (loop for key in keys\n                                      when (let ((path\n                                                  (merge-pathnames (car key)\n                                                                   dir)))\n                                             (when (probe-file path)\n                                               (make-publickey-auth\n                                                login (namestring dir)\n                                                (car key) (if (cdr key)\n                                                              (cdr key) \"\"))))\n                                      collect it)))\n                  (list\n                   (when (and passwords\n                              (listp passwords))\n                     (loop for password in passwords\n                        collect (make-password-auth login\n                                                    password)))))))))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/streams.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n;; clos facade: for blocking streams!! ;;\n\n(defun  throw-last-error (session)\n  (multiple-value-bind (message code)\n      (session-last-error session)\n    (error 'ssh-generic-error\n           :message message\n           :code    code)))\n\n(defmacro with-last-error ((session error-type) &rest args)\n  `(multiple-value-bind (message code)\n       (session-last-error ,session)\n     (error (quote ,error-type)\n            :message message\n            :code    code\n            ,@args)))\n\n(defclass auth-data ()\n  ((login    :type      string\n             :initarg   :login\n             :initform  \"\"\n             :reader    login)))\n\n(defclass ssh-connection ()\n  ((session     :type     +session+\n                :initarg  :session\n                :initform (null-pointer)\n                :reader   session)\n   (socket      :type     usocket:socket\n                :initarg  :socket\n                :accessor socket)\n   (host        :type     string\n                :initarg  :host\n                :accessor host)\n   (port        :type     int\n                :initarg  :port\n                :accessor port)\n   (hosts-db    :type     string\n                :initarg  :hosts-db\n                :accessor hosts-db)\n   (auth-passed :type     boolean\n                :initform nil\n                :accessor auth-passed)))\n\n\n(define-condition ssh-authentication-failure (ssh-generic-error)\n  ()\n  (:documentation \"is thrown in case authentication failed without specific error code~%\"))\n\n\n(defmethod create-ssh-connection (host\n                  &key\n                  (hosts-db (default-known-hosts))\n                  (port 22)\n                  (read-timeout 5)\n                  (write-timeout 5))\n  (let ((new-session nil)\n    (new-socket  nil)\n    (retval      :error-none))\n  (unwind-protect\n     (progn\n       (setq new-session (session-init))\n       (setq new-socket (usocket:socket-connect host port))\n       (set-timeouts new-socket read-timeout write-timeout)\n       (session-set-blocking new-session :blocking)\n\n       (setq retval\n         (session-handshake new-session (usocket-get-fd new-socket)))\n\n       (if (eq retval :error-none)\n         (make-instance 'ssh-connection\n                :session  new-session\n                :socket   new-socket\n                :host     host\n                :port     port\n                :hosts-db hosts-db)\n         (throw-last-error new-session)))\n    (unless (eq retval :error-none)\n    (unless (null-pointer-p new-session)\n      (session-free new-session))\n    (unless (null new-socket)\n      (usocket:socket-close new-socket))\n    nil))))\n\n\n(defmethod destroy-ssh-connection ((ssh ssh-connection) &key (description \"\") (lang \"\"))\n  (unwind-protect\n       (session-disconnect (session ssh)\n                           :description description\n                           :lang   lang)\n    (progn\n      (usocket:socket-close (socket ssh))\n      (session-free (session ssh)))))\n\n(defmacro with-ssh-connection (session (host auth-data &rest connection-args) &body body)\n  `(let* ((,session (create-ssh-connection ,host ,@connection-args))\n          (*ssh-connection* ,session))\n     (unwind-protect\n          (if (authentication ,session ,auth-data)\n              (handler-bind ((libssh2-invalid-error-code\n                               (lambda (condition)\n                                 (declare (ignore condition))\n                                 (throw-last-error (session ,session)))))\n                ,@body)\n              (error 'ssh-authentication-failure))\n       (destroy-ssh-connection ,session))))\n\n(defmethod ssh-session-key ((ssh ssh-connection))\n  (session-hostkey (session ssh)))\n\n(defmethod ssh-host+port-format ((ssh ssh-connection))\n  (format nil \"[~a]:~a\"\n          (host ssh)\n          (port ssh)))\n\n(defclass auth-password (auth-data)\n  ((password :type      string\n             :initarg   :password\n             :initform  \"\"\n             :reader    password)))\n\n(defmethod ssh-verify-session ((ssh ssh-connection))\n  (with-known-hosts (known-hosts ((session ssh) (hosts-db ssh)))\n    (let* ((host-key        (ssh-session-key ssh))\n           (host-key-status (known-hosts-check known-hosts\n                                               (host ssh)\n                                               host-key\n                                               :port (port ssh))))\n      (restart-case\n          (case host-key-status\n            (:match (return-from ssh-verify-session t))\n            (:not-found (error 'ssh-unknown-hostkey\n                               :host (host ssh)\n                               :hash (session-hostkey-fingerprint (session ssh))))\n            (t (error 'ssh-bad-hostkey\n                      :host (host ssh)\n                      :reason host-key-status\n                      :hash (session-hostkey-fingerprint (session ssh)))))\n        (accept () t)\n        (drop () nil)\n        (accept-once  (&optional (comment \"\"))\n          (progn\n            (known-hosts-add known-hosts (ssh-host+port-format ssh) host-key\n                             :comment comment)\n            t))\n        (accept-always (&optional (comment \"\"))\n          (progn\n            (known-hosts-add known-hosts (ssh-host+port-format ssh) host-key\n                             :comment comment)\n            (known-hosts-writefile known-hosts (hosts-db ssh))\n            t))))))\n\n(defmethod authentication-methods ((ssh ssh-connection) (login string))\n  (session-auth-methods-list (session ssh) login))\n\n(defmethod authentication :around ((ssh ssh-connection) (auth auth-data))\n  (let ((*errors-list* (remove :ERROR-AUTHENTICATION-FAILED *errors-list*)))\n    (if (auth-passed ssh)\n        t\n        (if (ssh-verify-session ssh)\n            (setf (auth-passed ssh)\n                  (call-next-method))))))\n\n(defmethod authentication ((ssh ssh-connection) (auth auth-password))\n  (eq (user-auth-password (session  ssh)\n                          (login    auth)\n                          (password auth))\n      :ERROR-NONE))\n\n(defclass auth-password-emul (auth-data)\n  ((password :type      string\n             :initarg   :password\n             :initform  \"\"\n             :reader    password)))\n\n(defmethod authentication ((ssh ssh-connection) (auth auth-password-emul))\n  (eq (user-auth-interactive-trivial\n       (session  ssh)\n       (login    auth)\n       (password auth))\n      :ERROR-NONE))\n\n(defclass auth-publickey (auth-data)\n  ((public-key  :type     string\n                :initarg  :public-key\n                :initform \"id_rsa.pub\"\n                :accessor public-key)\n   (private-key :type     string\n                :initarg  :private-key\n                :initform \"id_rsa\"\n                :accessor private-key)\n   (password    :type     string\n                :initarg  :password\n                :initform \"\"\n                :accessor password)))\n\n(defmethod authentication ((ssh ssh-connection) (auth auth-publickey))\n  (with-slots (login public-key private-key password) auth\n    (eq (user-auth-publickey (session ssh)\n                             login public-key private-key password)\n        :ERROR-NONE)))\n\n(defclass auth-agent (auth-data) ())\n\n(defmethod authentication ((ssh ssh-connection) (auth auth-agent))\n  (let ((agent (agent-init (session ssh)))\n        (username (login auth)))\n  (unwind-protect\n       (if (and agent (eq (agent-connect agent)\n                          :ERROR-NONE))\n           (let ((next-identity (agent-identities-iterator agent)))\n             (when next-identity\n               (with-foreign-string (fs-username username)\n                 (loop for identity = (funcall next-identity)\n                    while identity do\n                      (if (eq\n                           (%agent-userauth agent fs-username identity)\n                           :ERROR-NONE)\n                          (return t))))))\n           (throw-last-error (session ssh)))\n    (when agent\n      (agent-free agent)))))\n\n(defun make-publickey-auth (login directory private-key-name &optional (password \"\"))\n  (let ((private-key\n         (namestring (make-pathname :directory directory\n                                    :name private-key-name)))\n        (public-key\n         (namestring (make-pathname :directory directory\n                                    :name private-key-name\n                                    :type \"pub\"))))\n    (make-instance 'auth-publickey\n                   :login       login\n                   :public-key  public-key\n                   :private-key private-key\n                   :password    password)))\n\n(defun make-agent-auth (login)\n  (make-instance 'auth-agent\n                 :login login))\n\n(defun make-password-auth (login password)\n  (make-instance 'auth-password\n                 :login    login\n                 :password password))\n\n(defun make-password-emul-auth (login password)\n  (make-instance 'auth-password-emul\n                 :login    login\n                 :password password))\n\n(defvar *ssh-channel-buffer-size* 8196)\n\n(defclass ssh-channel-stream\n    (trivial-gray-stream-mixin)\n  ((socket        :initarg  :socket\n                  :accessor socket)\n   (channel       :type     +CHANNEL+\n                  :initarg  :channel\n                  :accessor channel)))\n\n(defclass ssh-channel-stream-output\n    (ssh-channel-stream)\n  ((output-buffer :initform (make-shareable-byte-vector\n                             *ssh-channel-buffer-size*)\n                  :accessor output-buffer)\n   (output-size   :initform 0\n                  :accessor output-size\n                  :type     int)\n   (output-pos    :type     int\n                  :initform 0\n                  :accessor output-pos)))\n\n(defclass ssh-channel-stream-input\n    (ssh-channel-stream)\n  ((input-buffer  :initform (make-shareable-byte-vector\n                             *ssh-channel-buffer-size*)\n                  :accessor input-buffer)\n   (input-size    :initform 0\n                  :accessor input-size\n                  :type     int)\n   (input-pos     :type     int\n                  :initform 0\n                  :accessor input-pos)))\n\n(defclass ssh-channel-stream-input/output\n  (ssh-channel-stream-input\n   ssh-channel-stream-output)\n  ())\n\n(defclass ssh-channel-exec\n  (ssh-channel-stream-input/output\n   fundamental-binary-output-stream\n   fundamental-character-output-stream\n   fundamental-binary-input-stream\n   fundamental-character-input-stream)\n  ())\n\n(defclass ssh-channel-recv\n  (ssh-channel-stream-input\n   fundamental-binary-input-stream)\n  ())\n\n(defclass ssh-channel-send\n  (ssh-channel-stream-output\n   fundamental-binary-output-stream)\n  ())\n\n(defmethod stream-element-type ((stream ssh-channel-stream))\n  (declare (ignore stream))\n  '(unsigned-byte 8))\n\n(defmethod open-stream-p ((stream ssh-channel-stream))\n  (not (null-pointer-p (channel stream))))\n\n(defmethod stream-listen ((stream ssh-channel-stream))\n  (listen (usocket:socket-stream (socket stream))))\n\n(defmethod stream-read-byte ((stream ssh-channel-stream-input))\n  (cond\n    ((< (input-pos stream) (input-size stream))\n     (prog1 (elt (input-buffer stream) (input-pos stream))\n       (incf (input-pos stream))))\n    (t (progn\n         (multiple-value-bind (amount eof)\n             (channel-read (channel stream)\n                           (input-buffer stream))\n           (if eof\n               (progn\n                 (log:info \"Got EOF\")\n                 :eof)\n               (progn\n                 (setf (input-pos   stream)  1)\n                 (setf (input-size  stream)  amount)\n                 (elt  (input-buffer stream) 0))))))))\n\n;; Looks like libssh2 sends 0 byte as EOF. Crazy shit :]\n(defmethod stream-read-sequence ((stream ssh-channel-recv) thing start end &key)\n  (multiple-value-bind (start eof)\n      (call-next-method)\n    (values\n     (- start (if (and (> start 0)\n                       eof)\n                  1 0))\n     eof)))\n\n(defmethod stream-read-sequence ((stream ssh-channel-stream-input) thing start end &key)\n  (let ((request-size (- end start))\n        (this-eof     nil))\n  (with-slots (channel input-size input-buffer input-pos) stream\n    (labels\n      ((buffer-to-output ()\n         (let* ((buffered-portion-size (- input-size input-pos ))\n                (replaced-size (min buffered-portion-size request-size)))\n           (when (> replaced-size 0)\n             (replace thing input-buffer\n                      :start1 start     :end1 (+ start\n                                                 replaced-size)\n                      :start2 input-pos :end2 (+ input-pos\n                                                 replaced-size))\n             (incf input-pos    replaced-size)\n             (incf start        replaced-size)\n             (decf request-size replaced-size))))\n\n       (fill-buffer-and-output ()\n         (multiple-value-bind (amount eof)\n             (channel-read channel input-buffer)\n\n           (setf input-size amount)\n           (setf input-pos  0)\n\n           (buffer-to-output)\n           (unless (or (= request-size 0) eof)\n             (fill-buffer-and-output))\n           (when eof (setq this-eof t)))))\n\n    (buffer-to-output)\n    (when (> request-size 0)\n      (fill-buffer-and-output))\n    (values\n     start\n     this-eof)))))\n\n(defmethod stream-read-line ((stream ssh-channel-stream-input))\n  (let ((output '()))\n    (labels\n        ((repeat-not-wait ()\n           ;; Search for new line in cached tail\n           (let* ((nl-pos (position (char-code '#\\Newline)\n                                    (input-buffer stream)\n                                    :start (input-pos  stream)\n                                    :end   (input-size stream)))\n                  (co-end (if nl-pos nl-pos (input-size stream))))\n             ;; Save substring or whole vector if any\n             (when (> (input-size stream) 0)\n               (push (subseq (input-buffer stream)\n                             (input-pos stream)\n                             co-end)\n                     output))\n\n             (if nl-pos\n                 ;; If newline found - save position and return concatenated string\n                 (prog1\n                     (babel:octets-to-string\n                      (apply #'concatenate\n                             (cons '(VECTOR\n                                     (UNSIGNED-BYTE\n                                      8))\n                                   (reverse output))))\n                   (setf (input-pos stream) (+ 1 co-end))\n                   (setf output '()))\n\n                 ;; If not - try to catch next portion\n                 (multiple-value-bind (amount eof)\n                     (channel-read (channel stream) (input-buffer stream))\n                   (cond\n                     ((not eof)\n                      (progn\n                        (setf (input-pos  stream) 0)\n                        (setf (input-size stream) amount)\n                        (repeat-not-wait)))\n                     (t\n                      (if (not (null output))\n                          ;; Return last cached data\n                          (let ((result\n                                 (babel:octets-to-string\n                                  (apply #'concatenate\n                                         (cons '(VECTOR\n                                                 (UNSIGNED-BYTE\n                                                  8))\n                                               (reverse output))))))\n                            (setf (input-size stream) 0\n                                  (input-pos  stream) 0)\n                            (setf output '())\n                            (values result t))\n                          ;; Time to return nil\n                          (values nil t)))))))))\n      (repeat-not-wait))))\n\n(defmethod stream-finish-output* ((stream ssh-channel-stream-output) &key (dont-send-eof nil))\n  (with-slots (socket channel output-buffer output-pos output-size) stream\n    (let ((retsize 0))\n      (do () ((= output-size output-pos))\n        (let ((amount\n                (channel-write channel\n                               output-buffer\n                               :start output-pos\n                               :end   output-size)))\n          (incf output-pos amount)\n          (incf retsize    amount)))\n\n      (setf output-pos  0\n            output-size 0)\n\n      (if dont-send-eof\n          retsize\n          (progn (channel-send-eof    channel)\n                 (channel-flush       channel)\n                 (channel-wait-eof    channel)\n                 retsize)))))\n\n(defmethod stream-finish-output ((stream ssh-channel-stream))\n  0)\n\n(defmethod stream-force-output ((stream ssh-channel-stream))\n  0)\n\n(defmethod stream-finish-output ((stream ssh-channel-stream-output))\n  (log:trace \"finish output called on ~a\" stream)\n  (stream-finish-output* stream\n                         ;; the original implementation didn't have\n                         ;; this, but that would be incorrect I\n                         ;; think.\n                         :dont-send-eof t))\n\n(defmethod stream-force-output ((stream ssh-channel-stream-output))\n  (log:trace \"force output called on ~a\" stream)\n  (stream-finish-output* stream :dont-send-eof t))\n\n(defmethod stream-write-byte ((stream ssh-channel-stream-output) byte)\n  (with-slots (output-size output-buffer) stream\n    (if (>= output-size (length output-buffer))\n      (stream-finish-output* stream :dont-send-eof t))\n    (when (< output-size (length output-buffer))\n    (prog1\n      (setf (aref output-buffer output-size) byte)\n      (incf output-size 1))))\n  (stream-finish-output* stream :dont-send-eof t))\n\n(defmethod stream-write-char ((stream ssh-channel-stream-output) char)\n  (stream-write-byte stream (char-code char)))\n\n(defmethod stream-write-sequence ((stream ssh-channel-stream-output) (sharable-sequence string) start end &key)\n  ;; If string passed, then flush previous buffer if any\n  ;; Then directly write this one\n  (stream-finish-output* stream :dont-send-eof t)\n  (channel-write-string (channel stream)\n                        sharable-sequence\n                        :start start\n                        :end   end)\n  (stream-finish-output* stream :dont-send-eof t))\n\n(defmethod stream-write-sequence ((stream ssh-channel-stream-output) sequence start end &key)\n  (with-slots (output-pos output-size output-buffer) stream\n    (let ((request-size (- end start))\n          (buffer-size  (length output-buffer)))\n      (labels ((push-to-stream ()\n                 ;; If no room in internal buffer, then flush it\n                 (when (>= output-size buffer-size)\n                   (stream-finish-output* stream :dont-send-eof t))\n                 ;; Get next portion of data\n                 (let ((portion (min request-size\n                                     (- buffer-size output-size))))\n                   ;; Save portion\n                   (replace output-buffer sequence\n                            :start1 output-size :end1 (+ output-size\n                                                         portion)\n                            :start2 start       :end2 (+ start\n                                                         portion))\n                   ;; Change stare\n                   (incf output-size  portion)\n                   (incf start        portion)\n                   (decf request-size portion))\n\n                 ;; Repeat, if not all sequence sended\n                 (when (> request-size 0)\n                   (push-to-stream))))\n\n        ;; Start iterations\n        (push-to-stream))))\n  (stream-finish-output* stream :dont-send-eof t))\n\n(defmethod close ((stream ssh-channel-stream) &key abort)\n  (let ((channel (channel stream)))\n    (when (not (null-pointer-p channel))\n      (unwind-protect\n           (progn\n             (unless abort\n               (stream-finish-output* stream)\n               (channel-wait-closed  channel))\n             (channel-close channel)\n             t)\n        (channel-free channel)\n        (setf (channel stream) (cffi:null-pointer))))))\n\n(defmethod execute ((ssh ssh-connection) (command string))\n  (with-slots (socket session) ssh\n  (let ((new-channel\n       (channel-open session)))\n    (if (pointerp new-channel)\n      (if (not (null-pointer-p new-channel))\n        (let ((retval (channel-exec new-channel command)))\n        (if (eq retval :ERROR-NONE)\n          (make-instance 'ssh-channel-exec\n                   :socket  socket\n                   :channel new-channel)\n          (throw-last-error session)))\n        (throw-last-error session))\n      (throw-last-error session)))))\n\n(defmethod scp-input ((ssh ssh-connection) (path string))\n  (multiple-value-bind (new-channel stat)\n    (channel-scp-recv (session ssh) path)\n  (unless (null-pointer-p new-channel)\n    (values\n     (make-instance 'ssh-channel-recv\n            :socket  (socket ssh)\n            :channel new-channel)\n     stat))))\n\n(defmethod scp-output ((ssh ssh-connection) (path string) size\n             &key mode mtime atime)\n  (let ((new-channel\n     (channel-scp-send (session ssh) path size\n               :mode  mode\n               :mtime mtime\n               :atime atime)))\n  (unless (null-pointer-p new-channel)\n    (make-instance 'ssh-channel-send\n           :socket  (socket ssh)\n           :channel new-channel))))\n\n(defmacro with-execute ((stdio-stream ssh-connection command)\n            &body body)\n  `(let ((,stdio-stream (execute ,ssh-connection ,command)))\n   (unwind-protect\n      (let ((body-retval\n         (progn ,@body)))\n      (values-list\n       (list body-retval\n           (channel-exit-status (channel ,stdio-stream)))))\n     (close ,stdio-stream))))\n\n(defmacro with-execute* ((stdio-stream ssh-connection command)\n                         &body body)\n  `(with-execute (,stdio-stream ,ssh-connection\n                                (concatenate 'string ,command \" 2>&1\"))\n     (let ((*channel-read-zero-as-eof* t))\n       ,@body)))\n\n(defmacro with-scp-input ((istream ssh-connection path object-stat)\n              &body body)\n  `(multiple-value-bind (,istream ,object-stat)\n     (scp-input ,ssh-connection ,path)\n   (declare (ignore ,object-stat))\n   (unwind-protect\n      (progn\n      ,@body)\n     (close ,istream))))\n\n(defmacro with-scp-output ((ostream ssh-connection path size &key\n                    mtime atime mode) &body body)\n  `(let ((,ostream (scp-output ,ssh-connection ,path ,size\n                 :mode ,mode :atime ,atime, :mtime ,mtime)))\n   (unwind-protect\n      (progn\n      ,@body)\n     (close ,ostream))))\n\n(defmethod channel-direct-tcpip ((ssh ssh-connection) host port\n                             &optional\n                               (source-host \"localhost\")\n                               (source-port 9000))\n  (with-foreign-strings ((fs-host host)\n                         (fs-source-host source-host))\n    (let ((session (session ssh)))\n     (loop while t do\n       (let ((result (%channel-direct-tcpip-ex session\n                                               fs-host port\n                                               fs-source-host source-port)))\n         (cond\n           ((not (null-pointer-p result))\n            (return (make-instance\n                     'ssh-channel-exec\n                     :socket (socket ssh)\n                     :channel result)))\n           ((eql :error-eagain (session-last-errno session))\n            (log:trace \"eagain while opening session\")\n            (wait-for-fd session))\n           (t\n            (result-or-error (session-last-errno session)))))))))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/types.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n(defcenum +DISCONNECT-CODE+\n  (:HOST-NOT-ALLOWED-TO-CONNECT          1)\n  (:PROTOCOL-ERROR                       2)\n  (:KEY-EXCHANGE-FAILED                  3)\n  (:RESERVED                             4)\n  (:MAC-ERROR                            5)\n  (:COMPRESSION-ERROR                    6)\n  (:SERVICE-NOT-AVAILABLE                7)\n  (:PROTOCOL-VERSION-NOT-SUPPORTED       8)\n  (:HOST-KEY-NOT-VERIFIABLE              9)\n  (:CONNECTION-LOST                      10)\n  (:BY-APPLICATION                       11)\n  (:TOO-MANY-CONNECTIONS                 12)\n  (:AUTH-CANCELLED-BY-USER               13)\n  (:NO-MORE-AUTH-METHODS-AVAILABLE       14)\n  (:ILLEGAL-USER-NAME                    15))\n\n(defconstant +eagain+ -37)\n\n(defcenum +ERROR-CODE+\n  (:ERROR-NONE                     0)\n  (:ERROR-SOCKET-NONE              -1)\n  (:ERROR-BANNER-RECV              -2)\n  (:ERROR-BANNER-SEND              -3)\n  (:ERROR-INVALID-MAC              -4)\n  (:ERROR-KEX-FAILURE              -5)\n  (:ERROR-ALLOC                    -6)\n  (:ERROR-SOCKET-SEND              -7)\n  (:ERROR-KEY-EXCHANGE-FAILURE     -8)\n  (:ERROR-TIMEOUT                  -9)\n  (:ERROR-HOSTKEY-INIT             -10)\n  (:ERROR-HOSTKEY-SIGN             -11)\n  (:ERROR-DECRYPT                  -12)\n  (:ERROR-SOCKET-DISCONNECT        -13)\n  (:ERROR-PROTO                    -14)\n  (:ERROR-PASSWORD-EXPIRED         -15)\n  (:ERROR-FILE                     -16)\n  (:ERROR-METHOD-NONE              -17)\n  (:ERROR-AUTHENTICATION-FAILED    -18)\n  (:ERROR-PUBLICKEY-UNVERIFIED     -19)\n  (:ERROR-CHANNEL-OUTOFORDER       -20)\n  (:ERROR-CHANNEL-FAILURE          -21)\n  (:ERROR-CHANNEL-REQUEST-DENIED   -22)\n  (:ERROR-CHANNEL-UNKNOWN          -23)\n  (:ERROR-CHANNEL-WINDOW-EXCEEDED  -24)\n  (:ERROR-CHANNEL-PACKET-EXCEEDED  -25)\n  (:ERROR-CHANNEL-CLOSED           -26)\n  (:ERROR-CHANNEL-EOF-SENT         -27)\n  (:ERROR-SCP-PROTOCOL             -28)\n  (:ERROR-ZLIB                     -29)\n  (:ERROR-SOCKET-TIMEOUT           -30)\n  (:ERROR-SFTP-PROTOCOL            -31)\n  (:ERROR-REQUEST-DENIED           -32)\n  (:ERROR-METHOD-NOT-SUPPORTED     -33)\n  (:ERROR-INVAL                    -34)\n  (:ERROR-INVALID-POLL-TYPE        -35)\n  (:ERROR-PUBLICKEY-PROTOCOL       -36)\n  (:ERROR-EAGAIN                   -37)\n  (:ERROR-BUFFER-TOO-SMALL         -38)\n  (:ERROR-BAD-USE                  -39)\n  (:ERROR-COMPRESS                 -40)\n  (:ERROR-OUT-OF-BOUNDARY          -41)\n  (:ERROR-AGENT-PROTOCOL           -42)\n  (:ERROR-SOCKET-RECV              -43)\n  (:ERROR-ENCRYPT                  -44)\n  (:ERROR-BAD-SOCKET               -45)\n  (:ERROR-KNOWN-HOSTS              -46))\n\n(defcenum +BLOCKING+\n  (:BLOCKING     1)\n  (:NON-BLOCKING 0))\n\n(defcenum +IDENTITY-AMOUNT+\n  (:MORE 0)\n  (:END  1))\n\n(defcenum +CHANNEL-EOF+\n  (:NOT-EOF 0)\n  (:EOF     1))\n\n(defcenum +STREAM-ID+\n  (:STDOUT    0)\n  (:STDERR    1)\n  (:EXTENDED -1)\n  (:ALL      -2))\n\n(defcenum +HASH-TYPE+\n  (:MD5  1)\n  (:SHA1 2))\n\n(defcenum +CHECK-VERDICT+\n  (:FAILURE    3)\n  (:NOT-FOUND  2)\n  (:MISMATCH   1)\n  (:MATCH      0))\n\n(defctype +session+     :pointer)\n(defctype +key+         :pointer)\n(defctype +ssh-agent+   :pointer)\n(defctype +known-hosts+ :pointer)\n(defctype +keyhash+     :pointer)\n(defctype +channel+     :pointer)\n\n(defbitfield +TRACE-OPTIONS+\n  (.TRANS.    2)\n  (.KEX.      4)\n  (.AUTH.     8)\n  (.CONN.     16)\n  (.SCP.      32)\n  (.SFTP.     64)\n  (.ERROR.    128)\n  (.PUBLICKEY 256)\n  (.SOCKET    512))\n\n(defbitfield +known-hosts-flags+\n  (.type-plain. 1)\n  (.type-sha1.  2)\n  (.raw.        65536)\n  (.base64.     131072)\n  (.rsa1.       262144)\n  (.ssh.        524288))\n\n(defcstruct +known-host+\n  (magic :unsigned-int)\n  (node  :pointer)\n  (name  :string)\n  (key   :string)\n  (type  +known-hosts-flags+))\n\n(defcstruct +kbd-prompt+\n  (text    :pointer)\n  (length  :unsigned-int)\n  (echo    :unsigned-char))\n\n(defcstruct +kbd-response+\n  (text    :pointer)\n  (length  :unsigned-int))\n\n(defstruct key\n  (data 0 :read-only t)\n  (size 0 :read-only t)\n  (type 0 :read-only t))\n\n(define-condition ssh-condition (condition)\n  ((message :type     string\n            :initarg  :message\n            :accessor message)\n   (code    :type     +ERROR-CODE+\n            :accessor code\n            :initarg  :code))\n  (:report (lambda (c stream)\n             (format stream \"An SSH error occurred (code: ~A): ~A.\" (code c) (message c))))\n  (:documentation \"Parent condition for all situations where a libssh2\n  call yields a non-zero return value. `CODE' and `MESSAGE' are used\n  to transport the error code and message from C.\"))\n\n(define-condition ssh-generic-error (ssh-condition error)\n  ()\n  (:documentation \"Signalled when a non-correctable condition occurs\n  during a libssh2 call.\"))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defvar *default-errors-list*\n    (cons :UNKNOWN\n     (remove :ERROR-NONE\n             (foreign-enum-keyword-list '+ERROR-CODE+)))))\n\n(defvar *errors-list* *default-errors-list*)\n\n(define-condition libssh2-invalid-error-code (ssh-generic-error)\n  ()\n  (:documentation \"Signalled when an error code is returned by libssh2\n  which is unknown (or better: not yet known) to this library; this\n  situation can arise when libssh2 adds a new error, but the lisp code\n  is not yet updated to reflect the change. The `MESSAGE' slot is set\n  to 'Received unknown error code from libssh2; please contact the\n  cl-libssh2 authors.'\"))\n\n(defmethod initialize-instance :after ((e libssh2-invalid-error-code) &key)\n  (setf (message e) \"Received unknown error code from libssh2; please contact the cl-libssh2 authors.\"))\n\n(define-condition ssh-handshake-error (ssh-generic-error) ())\n\n(define-condition ssh-hostkey-condition (ssh-condition)\n  ((host   :type      string\n           :accessor  host\n           :initarg   :host)\n   (hash   :type      string\n           :accessor  hash\n           :initarg   :hash)))\n\n(define-condition ssh-unknown-hostkey (ssh-hostkey-condition)\n  ()\n  (:report (lambda (c stream)\n             (format stream \"Unknown key for host ~A (hash: ~A)\" (host c) (hash c)))))\n\n(define-condition ssh-bad-hostkey (ssh-hostkey-condition ssh-generic-error)\n  ((reason :type      +check-verdict+\n           :accessor  reason\n           :initarg   :reason))\n  (:report (lambda (c stream)\n             (format stream \"Verification of key for host ~A (hash: ~A) failed with reason ~A\" (host c) (hash c) (reason c)))))\n\n;;; SFTP related types\n\n(defcenum (sftp-open-types :int)\n  (:file 0)\n  (:dir 1))\n\n(defbitfield (sftp-flags :unsigned-long)\n  (:none #x0)\n  (:read #x1)\n  (:write #x2)\n  (:append #x4)\n  (:creat #x8)\n  (:trunc #x10)\n  (:excl  #x20))\n\n(defbitfield (sftp-modes :long)\n  (:named-pipe #x10000)\n  (:character-special #x20000)\n  (:directory #x40000)\n  (:block-special #x60000)\n  (:regular #x100000)\n  (:symbolic-link #x120000)\n  (:socket #x140000)\n  (:user-read #x400)\n  (:user-write #x200)\n  (:user-execute #x100)\n  (:group-read #x40)\n  (:group-write #x20)\n  (:group-execute #x10)\n  (:other-read #x4)\n  (:other-write #x2)\n  (:other-execute #x1))\n\n(defcenum (sftp-error-code :unsigned-long)\n  (:ok                       0)\n  (:error-sftp-eof                      1)\n  (:error-sftp-no_such_file             2)\n  (:error-sftp-permission_denied        3)\n  (:error-sftp-failure                  4)\n  (:error-sftp-bad_message              5)\n  (:error-sftp-no_connection            6)\n  (:error-sftp-connection_lost          7)\n  (:error-sftp-op_unsupported           8)\n  (:error-sftp-invalid_handle           9)\n  (:error-sftp-no_such_path             10)\n  (:error-sftp-file_already_exists      11)\n  (:error-sftp-write_protect            12)\n  (:error-sftp-no_media                 13)\n  (:error-sftp-no_space_on_filesystem   14)\n  (:error-sftp-quota_exceeded           15)\n  (:error-sftp-unknown_principal        16)\n  (:error-sftp-lock_conflict            17)\n  (:error-sftp-dir_not_empty            18)\n  (:error-sftp-not_a_directory          19)\n  (:error-sftp-invalid_filename         20)\n  (:error-sftp-link_loop                21))\n"
  },
  {
    "path": "third-party/cl-libssh2/src/util.lisp",
    "content": ";; -*- mode: lisp; syntax: common-lisp -*-\n\n(in-package :libssh2)\n\n;; From:\n;;  http://common-lisp.net/~loliveira/ediware/hunchentoot/set-timeouts.lisp\n(defun set-timeouts (usocket read-timeout write-timeout)\n  \"Sets up timeouts on the given USOCKET object.  READ-TIMEOUT is the\nread timeout period, WRITE-TIMEOUT is the write timeout, specified in\n\\(fractional) seconds.  The timeouts can either be implemented using\nthe low-level socket options SO_RCVTIMEO and SO_SNDTIMEO or some\nother, implementation specific mechanism.  On platforms that do not\nsupport separate read and write timeouts, both must be equal or an\nerror will be signaled.  READ-TIMEOUT and WRITE-TIMEOUT may be NIL,\nwhich means that the corresponding socket timeout value will not be\nset.\"\n  (declare (ignorable usocket read-timeout write-timeout))\n  ;; add other Lisps here if necessary\n  #+(or :sbcl :cmu)\n  (unless (eql read-timeout write-timeout)\n    (parameter-error \"Read and write timeouts for socket must be equal.\"))\n  #+:clisp\n  (when read-timeout\n    (socket:socket-options (usocket:socket usocket) :SO-RCVTIMEO read-timeout))\n  #+:clisp\n  (when write-timeout\n    (socket:socket-options (usocket:socket usocket) :SO-SNDTIMEO write-timeout))\n  #+:openmcl\n  (when read-timeout\n    (setf (ccl:stream-input-timeout (usocket:socket usocket))\n          read-timeout))\n  #+:openmcl\n  (when write-timeout\n    (setf (ccl:stream-output-timeout (usocket:socket usocket))\n          write-timeout))\n  #+:sbcl\n  (when read-timeout\n    (setf (sb-impl::fd-stream-timeout (usocket:socket-stream usocket))\n          (coerce read-timeout 'single-float)))\n  #+:cmu\n  (setf (lisp::fd-stream-timeout (usocket:socket-stream usocket))\n        (coerce read-timeout 'integer))\n  #-(or :clisp :allegro :openmcl :sbcl :lispworks :cmu)\n  (not-implemented 'set-timeouts))\n\n\n(defun usocket-get-fd (uso)\n  #+lispworks\n  (usocket:socket uso)\n  #+:sbcl\n  (slot-value (usocket:socket uso) 'sb-bsd-sockets::file-descriptor)\n  #+:ccl\n  (ccl:socket-os-fd (usocket:socket uso)))\n\n(defvar *config-directory* nil)\n(defun default-config-directory ()\n  (if *config-directory* *config-directory*\n      (namestring (merge-pathnames \".ssh/\" (user-homedir-pathname)))))\n\n(defun default-known-hosts ()\n  (namestring (merge-pathnames \"known_hosts\"\n                               (default-config-directory))))\n\n(defun default-private-key ()\n  (namestring (merge-pathnames \"id_rsa\"\n                               (default-config-directory))))\n\n(defun default-public-key ()\n  (namestring (merge-pathnames \"id_rsa.pub\"\n                               (default-config-directory))))\n"
  },
  {
    "path": "third-party/cl-libssh2/test/package.lisp",
    "content": ";;; -*- mode: lisp; syntax: common-lisp; indent-tabs-mode: nil -*-\n\n(in-package #:cl-user)\n\n(defpackage #:libssh2.test\n  (:use #:common-lisp #:libssh2 #:hu.dwim.stefil)\n  (:export #:run-unit-tests\n           #:run-integration-tests\n           #:run-all-tests))\n\n(in-package #:libssh2.test)\n\n;; The suite 'unit' is used for real unit tests which just test the code\n;; and don't depend on the presence of an SSH server\n(defsuite* (unit :in root-suite))\n\n;; The suite 'integration' is used for integration tests which depend on an\n;; SSH server and some previously created users\n(defsuite* (integration :in root-suite))\n\n(defun run-all-tests ()\n  (handler-bind ((libssh2::known-hosts-reading-error (lambda (c) (declare (ignore c)) (invoke-restart 'accept-always) t))\n                 (libssh2::ssh-unknown-hostkey (lambda (c) (declare (ignore c)) (invoke-restart 'accept-always) t))\n                 (libssh2::ssh-authentication-failure (lambda (c) (declare (ignore c)) (invoke-restart 'accept-always) t)))\n    (progn\n      (unit)\n      (integration))))\n\n(defparameter *known-hosts-path* (namestring\n                                  (merge-pathnames\n                                   (make-pathname :directory '(:relative \".ssh\")\n                                                  :name \"libssh2-known_hosts\")\n                                   (user-homedir-pathname))))\n\n(defparameter *test-host* \"localhost\"\n  \"Host name or IP of the SSH server which is used for integration testing. Default value is 'localhost'; set this via command line (-e) before starting tests.\")\n\n(defparameter *user1* nil\n  \"Login name of the first user for integration testing; set this via command line (-e) before starting tests. \")\n\n(defparameter *password1* nil\n  \"Password of the first user for integration testing; set this via command line (-e) before starting tests. \")\n\n(defparameter *user2* nil\n  \"Login name of the second user for integration testing; set this via command line (-e) before starting tests. \")\n\n(defparameter *password2* nil\n  \"Password of the second user for integration testing; set this via command line (-e) before starting tests. \")\n"
  },
  {
    "path": "third-party/cl-libssh2/test/scp.lisp",
    "content": ";;; -*- mode: lisp; syntax: common-lisp; indent-tabs-mode: nil -*-\n\n(in-package #:libssh2.test)\n\n(in-suite integration)\n\n(deftest scp-copy-back-and-forth ()\n  (with-ssh-connection sshc\n     (*test-host*\n     (libssh2:make-password-auth *user1* *password1*)\n     :hosts-db *known-hosts-path*)\n    (let ((test-file (asdf:system-relative-pathname (asdf:find-system :libssh2) \"test/data/testfile.tgz\"))\n          (remote-name \"/tmp/copied-to-remote.tgz\")\n          (final \"/tmp/copied-back-from-remote.tgz\")\n          (md5 \"3fee5a92e7d3a2c716e922434911aa7c\"))\n      ;; copy file to ssh host\n      (scp-put test-file remote-name)\n      ;; copy back from ssh host\n      (scp-get remote-name final)\n      ;; calculate remote md5\n      (libssh2:with-execute* (in sshc (format nil \"md5sum ~a\" test-file))\n        (let ((sums (loop for line = (read-line in nil)\n                          while line\n                          do (format t \"~%<~A>~%\" (split-sequence:split-sequence #\\Space line))\n                          collect (car (split-sequence:split-sequence #\\Space line)))))\n          (is (every (lambda (s) (equal s md5)) sums) \"MD5 sums of local and remote files differ\"))))))\n"
  },
  {
    "path": "third-party/cl-libssh2/test/sftp.lisp",
    "content": ";;; -*- mode: lisp; syntax: common-lisp; indent-tabs-mode: nil -*-\n\n(in-package #:libssh2.test)\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/.gitignore",
    "content": "*.FASL\n*.fasl\n*.lisp-temp\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/README.md",
    "content": "# Damn Fast Priority Queue\n\nA heap-based priority queue whose first and foremost priority is [**speed**](https://www.youtube.com/watch?v=AkagvXwDsYU). Optionally comes in a stable flavor.\n\nBlame [@mfiano](https://github.com/mfiano/) for the existence of this library. He's the one who wanted a priority queue that's going to run as fast as possible in one of the hottest loops of his game engine ~~and then figured out that hey, he actually doesn't need a prio queue there~~.\n\n## License\n\nMIT.\n\n## Systems\n\n* This repository contains two systems:\n  * `damn-fast-priority-queue`, a faster but unstable priority queue (elements with the same priority are dequeued in unspecified order),\n  * `damn-fast-stable-priority-queue`, a fast and stable priority queue (elements with the same priority are dequeued in FIFO order).\n* The queues have identical APIs.\n  * These APIs are not generic, i.e. operators for one queue type must not be used on a queue instance of the other type.\n\n## Description\n\n* The queue enqueues objects along with their priorities.\n  * The stored objects may be of arbitrary type.\n  * The objects' priorities must be of type `(unsigned-byte 32)`.\n* The queue is a minimum queue (i.e. smallest priorities are dequeued first).\n* The queue is unbounded by default.\n  * The queue's storage automatically expands (which reallocates the queue storage).\n  * The queue's storage can be manually trimmed (which reallocates the queue storage).\n  * The queue can instead be configured to signal an error upon reaching its size limit.\n* The queue is **not** thread-safe.\n* The queue is **not** reentrant.\n\n## Implementation details\n\n* The queue internally uses two simple vectors: one for data, specialized on `t`, and another for priorities, specialized on `(unsigned-byte 32)`.\n  * The stable queue also uses a third simple vector for storing element insertion order, specialized on `(unsigned-byte 32)`.\n* The queue's storage has its initial storage size set to `256`. This value is customizable in the constructor.\n* Each time the queue runs out of storage, the storage is reallocated via `adjust-array` and its size is expanded by the `extension-factor` value provided at queue instantiation.\n* We assume that using simple vectors, calling `adjust-array` on them, and manually setting queue slots to the new vectors is faster than using adjustable vectors.\n\n## Optimization settings\n\n* The code uses structure classes in favor of standard classes.\n* The code uses standard, `inline`-proclaimed functions in favor of generic functions.\n* All functions are optimized for maximum `speed`.\n* By default, the code retains the default values of `debug`, `safety`, `space`, and `compilation-speed` optimize qualities. To set them all to 0, pray to your favorite deity and push a feature into `*features*` before compiling the respective system.\n  * for `damn-fast-priority-queue`, push `:real-damn-fast-priority-queue`,\n  * for `damn-fast-stable-priority-queue`, push `:real-damn-fast-stable-priority-queue`.\n\n## Exports\n\nAll exported functions are proclaimed `inline` by default.\n\n* **Classes**\n  * `queue` - names the priority queue structure class.\n* **Functions**\n  * `(make-queue &optional initial-storage-size extension-factor extend-queue-p)` - make a priority queue.\n    * The initial storage size must be a non-negative integer. Its default value is `256`.\n    * The extension factor value must be a positive integer between `2` and `256`. Its default value is `2`.\n    * The queue can be configured to signal an error of type `queue-size-limit-reached` when its size is reached, instead of extending its storage. It is possible to retrieve the queue via the `queue-size-limit-reached-queue` reader and the object that was attempted to be stored via `queue-size-limit-reached-object`.\n  * `(copy-queue queue)` - makes a deep copy of the provided queue (including its storage vectors).\n  * `(enqueue queue object priority)` - enqueue an object.\n  * `(dequeue queue)` - dequeue an object.\n    * Secondary return value is true if the object was found and false if the queue was empty.\n  * `(peek queue)` - peek at an object that is first to be dequeued.\n    * Secondary return value is true if the object was found and false if the queue was empty.\n  * `(size queue)` - get the current element count of the queue.\n  * `(trim queue)` - trim the queue's storage by calling `adjust-array` on it with the current queue size.\n  * `(map queue function)` - calls the function on each element of `queue` in unspecified order and returns `nil`.\n* **Macros**\n  * `(do-queue (object queue &optional result) &body body)` - evaluates `body` with `object` bound to each element of `queue` in unspecified order and returns `result`.\n\n## Tests\n\n* For `damn-fast-priority-queue`:\n  * Non-verbose test: `(asdf:test-system :damn-fast-priority-queue)`\n  * Verbose test: `(damn-fast-priority-queue/test:run t)`\n* For `damn-fast-stable-priority-queue`:\n  * Non-verbose test: `(asdf:test-system :damn-fast-stable-priority-queue)`\n  * Verbose test: `(damn-fast-stable-priority-queue/test:run t)`\n\n## Performance tests\n\nSee [the Priority Queue Benchmark README](priority-queue-benchmark/README.md).\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-priority-queue/damn-fast-priority-queue.asd",
    "content": ";;;; damn-fast-priority-queue.asd\n\n(asdf:defsystem #:damn-fast-priority-queue\n  :description \"A heap-based priority queue whose first and foremost priority is speed.\"\n  :author \"Michał \\\"phoe\\\" Herda <phoe@disroot.org>\"\n  :license  \"MIT\"\n  :version \"0.0.2\"\n  :serial t\n  :depends-on (#:alexandria)\n  :components ((:file \"src\"))\n  :in-order-to ((test-op (load-op #:damn-fast-priority-queue/test)))\n  :perform (test-op (o c) (symbol-call \"DAMN-FAST-PRIORITY-QUEUE/TEST\" \"RUN\")))\n\n(asdf:defsystem #:damn-fast-priority-queue/test\n  :description \"Tests for Damn Fast Priority Queue\"\n  :author \"Michał \\\"phoe\\\" Herda <phoe@disroot.org>\"\n  :license \"MIT\"\n  :version \"0.0.2\"\n  :serial t\n  :depends-on (#:alexandria #:damn-fast-priority-queue)\n  :components ((:file \"test\")))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-priority-queue/src.lisp",
    "content": ";;;; damn-fast-priority-queue.lisp\n\n(defpackage #:damn-fast-priority-queue\n  (:use #:cl)\n  (:shadow #:map)\n  (:export #:queue #:make-queue #:copy-queue\n           #:enqueue #:dequeue #:peek #:size #:trim #:map #:do-queue\n           #:queue-size-limit-reached\n           #:queue-size-limit-reached-queue #:queue-size-limit-reached-object))\n\n(in-package #:damn-fast-priority-queue)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Read-time variables\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defparameter *optimize-qualities*\n    #+real-damn-fast-priority-queue\n    ;; Good luck.\n    `(optimize (speed 3) (debug 0) (safety 0) (space 0) (compilation-speed 0))\n    #-real-damn-fast-priority-queue\n    `(optimize (speed 3))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Type definitions\n\n(deftype data-type () 't)\n\n(deftype data-vector-type () '(simple-array data-type (*)))\n\n(deftype prio-type () '(unsigned-byte 32))\n\n(deftype prio-vector-type () '(simple-array prio-type (*)))\n\n(deftype extension-factor-type () '(integer 2 256))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Structure definition\n\n(declaim (inline %make %data-vector %prio-vector %size %extension-factor))\n\n(defstruct (queue (:conc-name #:%) (:constructor %make)\n                  (:predicate nil) (:copier nil))\n  (data-vector (make-array 256 :element-type 'data-type) :type data-vector-type)\n  (prio-vector (make-array 256 :element-type 'prio-type) :type prio-vector-type)\n  (size 0 :type alexandria:array-length)\n  (extension-factor 2 :type extension-factor-type)\n  (extend-queue-p t :type boolean))\n\n(declaim (inline make-queue copy-queue))\n\n(declaim (ftype (function\n                 (&optional alexandria:array-index extension-factor-type boolean)\n                 (values queue &optional))\n                make-queue))\n(defun make-queue (&optional\n                     (initial-storage-size 256)\n                     (extension-factor 2)\n                     (extend-queue-p t))\n  (declare (type extension-factor-type extension-factor))\n  (declare #.*optimize-qualities*)\n  (%make :extension-factor extension-factor\n         :data-vector (make-array initial-storage-size\n                                  :element-type 'data-type)\n         :prio-vector (make-array initial-storage-size\n                                  :element-type 'prio-type)\n         :extend-queue-p extend-queue-p))\n\n(defmethod print-object ((object queue) stream)\n  (print-unreadable-object (object stream :type t :identity t)\n    (format stream \"(~D)\" (%size object))))\n\n(declaim (ftype (function (queue) (values queue &optional)) copy-queue))\n(defun copy-queue (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (%make :extension-factor (%extension-factor queue)\n         :size (%size queue)\n         :extend-queue-p (%extend-queue-p queue)\n         :data-vector (copy-seq (%data-vector queue))\n         :prio-vector (copy-seq (%prio-vector queue))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Enqueueing\n\n(declaim (inline heapify-upwards enqueue))\n\n(declaim (ftype (function (data-vector-type prio-vector-type alexandria:array-length)\n                          (values null &optional))\n                heapify-upwards))\n(defun heapify-upwards (data-vector prio-vector index)\n  (declare (type data-vector-type data-vector))\n  (declare (type prio-vector-type prio-vector))\n  (declare (type alexandria:array-length index))\n  (declare #.*optimize-qualities*)\n  (do ((child-index index parent-index)\n       (parent-index (ash (1- index) -1) (ash (1- parent-index) -1)))\n      ((= child-index 0))\n    (let ((child-priority (aref prio-vector child-index))\n          (parent-priority (aref prio-vector parent-index)))\n      (cond ((< child-priority parent-priority)\n             (rotatef (aref prio-vector parent-index)\n                      (aref prio-vector child-index))\n             (rotatef (aref data-vector parent-index)\n                      (aref data-vector child-index)))\n            (t (return))))))\n\n(declaim (ftype (function (queue t fixnum) (values null &optional)) enqueue))\n(defun enqueue (queue object priority)\n  (declare (type queue queue))\n  (declare (type fixnum priority))\n  (declare #.*optimize-qualities*)\n  (symbol-macrolet ((data-vector (%data-vector queue))\n                    (prio-vector (%prio-vector queue)))\n    (let ((size (%size queue))\n          (extension-factor (%extension-factor queue))\n          (length (array-total-size data-vector)))\n      (when (>= size length)\n        (unless (%extend-queue-p queue)\n          (error 'queue-size-limit-reached :queue queue :element object))\n        (let ((new-length (max 1 (mod (* length extension-factor)\n                                      (ash 1 64)))))\n          (declare (type alexandria:array-length new-length))\n          (when (<= new-length length)\n            (error \"Integer overflow while resizing array: new-length ~D is ~\n                    smaller than old length ~D\" new-length length))\n          (setf data-vector (adjust-array data-vector new-length)\n                prio-vector (adjust-array prio-vector new-length))))\n      (setf (aref data-vector size) object\n            (aref prio-vector size) priority)\n      (heapify-upwards data-vector prio-vector (%size queue))\n      (incf (%size queue))\n      nil)))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Dequeueing\n\n(declaim (inline heapify-downwards dequeue))\n\n(declaim (ftype (function (data-vector-type prio-vector-type alexandria:array-index)\n                          (values null &optional))\n                heapify-downwards))\n(defun heapify-downwards (data-vector prio-vector size)\n  (declare (type data-vector-type data-vector))\n  (declare (type prio-vector-type prio-vector))\n  (declare #.*optimize-qualities*)\n  (let ((parent-index 0))\n    (loop\n      (let* ((left-index (+ (* parent-index 2) 1))\n             (left-index-validp (< left-index size))\n             (right-index (+ (* parent-index 2) 2))\n             (right-index-validp (< right-index size)))\n        (flet ((swap-left ()\n                 (rotatef (aref prio-vector parent-index)\n                          (aref prio-vector left-index))\n                 (rotatef (aref data-vector parent-index)\n                          (aref data-vector left-index))\n                 (setf parent-index left-index))\n               (swap-right ()\n                 (rotatef (aref prio-vector parent-index)\n                          (aref prio-vector right-index))\n                 (rotatef (aref data-vector parent-index)\n                          (aref data-vector right-index))\n                 (setf parent-index right-index)))\n          (declare (inline swap-left swap-right))\n          (when (and (not left-index-validp)\n                     (not right-index-validp))\n            (return))\n          (when (and left-index-validp\n                     (< (aref prio-vector parent-index)\n                        (aref prio-vector left-index))\n                     (or (not right-index-validp)\n                         (< (aref prio-vector parent-index)\n                            (aref prio-vector right-index))))\n            (return))\n          (if (and right-index-validp\n                   (<= (aref prio-vector right-index)\n                       (aref prio-vector left-index)))\n              (swap-right)\n              (swap-left)))))))\n\n(declaim (ftype (function (queue) (values t boolean &optional)) dequeue))\n(defun dequeue (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (if (= 0 (%size queue))\n      (values nil nil)\n      (let ((data-vector (%data-vector queue))\n            (prio-vector (%prio-vector queue)))\n        (multiple-value-prog1 (values (aref data-vector 0) t)\n          (decf (%size queue))\n          (let ((old-data (aref data-vector (%size queue)))\n                (old-prio (aref prio-vector (%size queue))))\n            (setf (aref data-vector 0) old-data\n                  (aref prio-vector 0) old-prio))\n          (heapify-downwards data-vector prio-vector (%size queue))))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Introspection and maintenance\n\n(declaim (inline peek size trim map))\n\n(declaim (ftype (function (queue) (values t boolean &optional)) peek))\n(defun peek (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (if (= 0 (%size queue))\n      (values nil nil)\n      (values (aref (%data-vector queue) 0) t)))\n\n(declaim (ftype (function (queue) (values alexandria:array-length &optional)) size))\n(defun size (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (%size queue))\n\n(declaim (ftype (function (queue) (values null &optional)) trim))\n(defun trim (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (let ((size (%size queue)))\n    (setf (%data-vector queue) (adjust-array (%data-vector queue) size)\n          (%prio-vector queue) (adjust-array (%prio-vector queue) size))\n    nil))\n\n(declaim (ftype (function (queue (function (t) t)) (values null &optional))\n                map))\n(defun map (queue function)\n  (loop repeat (%size queue)\n        for data across (%data-vector queue)\n        do (funcall function data)))\n\n(defmacro do-queue ((object queue &optional result) &body body)\n  (multiple-value-bind (forms declarations) (alexandria:parse-body body)\n    (alexandria:once-only (queue)\n      `(loop repeat (%size ,queue)\n             for ,object across (%data-vector ,queue)\n             do (locally ,@declarations (tagbody ,@forms))\n             finally (return ,result)))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Conditions\n\n(defun report-queue-size-limit-reached (condition stream)\n  (let ((queue (queue-size-limit-reached-queue condition))\n        (element (queue-size-limit-reached-object condition)))\n    (format stream \"Size limit (~D) reached for non-extensible ~\n                    queue ~S while trying to enqueue element ~S onto it.\"\n            (length (%data-vector queue)) queue element)))\n\n(define-condition queue-size-limit-reached (error)\n  ((%queue :reader queue-size-limit-reached-queue :initarg :queue)\n   (%object :reader queue-size-limit-reached-object :initarg :element))\n  (:default-initargs :queue (alexandria:required-argument :queue)\n                     :object (alexandria:required-argument :object))\n  (:report report-queue-size-limit-reached))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-priority-queue/test.lisp",
    "content": ";;;; damn-fast-priority-queue-test.lisp\n\n(defpackage #:damn-fast-priority-queue/test\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:q #:damn-fast-priority-queue))\n  (:export #:run))\n\n(in-package #:damn-fast-priority-queue/test)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Utilities\n\n(defun verify-heap-property (vector)\n  (loop with length = (length vector)\n        for parent from 0 below (truncate length 2)\n        for left = (+ (* parent 2) 1)\n        for right = (+ (* parent 2) 2)\n        do (assert (< (aref vector parent) (aref vector left)) ()\n                   \"VERIFY-HEAP-PROPERTY: Invalid left child: ~D -> ~D\"\n                   (aref vector parent) (aref vector left))\n        when (oddp length)\n          do (assert (< (aref vector parent) (aref vector right)) ()\n                     \"VERIFY-HEAP-PROPERTY: Invalid right child: ~D -> ~D\"\n                     (aref vector parent) (aref vector right))))\n\n(defun stringify (i) (format nil \"~D\" i))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Main interface\n\n(defvar *verbose* nil)\n\n(defun run (&optional *verbose*)\n  (dolist (length (nconc (a:iota 64 :start 1) '(256 1024 4096)))\n    (when *verbose* (format t \"~&Testing with ~4,' D elements\" length))\n    (let ((queue (q:make-queue (max 1 (ash length -4)))))\n      (perform-test queue (a:iota length))\n      (perform-test queue (nreverse (a:iota length)))\n      (dotimes (i 100)\n        (perform-test queue (a:shuffle (a:iota length))))))\n  (perform-error-test)\n  (perform-copy-test))\n\n(defun perform-test (queue list)\n  (when *verbose* (princ \".\"))\n  (test-enqueue queue list)\n  (test-map queue list)\n  (test-do-queue queue list)\n  (test-dequeue-and-peek queue list)\n  (test-dequeue-and-peek-empty queue)\n  (test-trim queue list))\n\n(defun perform-error-test ()\n  (let ((queue (q:make-queue 4 2 nil)))\n    (dotimes (i 4) (q:enqueue queue (princ-to-string i) i))\n    (flet ((perform ()\n             (multiple-value-bind (value error)\n                 (ignore-errors (q:enqueue queue \"4\" 4))\n               (assert (null value))\n               (assert (typep error 'q:queue-size-limit-reached))\n               (assert (eq queue (q:queue-size-limit-reached-queue error)))\n               (assert (string= \"4\"\n                                (q:queue-size-limit-reached-object error))))))\n      (dotimes (i 4) (perform)))))\n\n(defun perform-copy-test ()\n  (let ((queue-1 (q:make-queue)))\n    (q:enqueue queue-1 42 1)\n    (let ((queue-2 (q:copy-queue queue-1)))\n      (q:enqueue queue-2 24 0)\n      ;; Check QUEUE-1\n      (multiple-value-bind (value foundp) (q:dequeue queue-1)\n        (assert (= 42 value))\n        (assert (eq t foundp)))\n      (multiple-value-bind (value foundp) (q:dequeue queue-1)\n        (assert (null value))\n        (assert (null foundp)))\n      ;; Check QUEUE-2\n      (multiple-value-bind (value foundp) (q:dequeue queue-2)\n        (assert (= 24 value))\n        (assert (eq t foundp)))\n      (multiple-value-bind (value foundp) (q:dequeue queue-2)\n        (assert (= 42 value))\n        (assert (eq t foundp)))\n      (multiple-value-bind (value foundp) (q:dequeue queue-2)\n        (assert (null value))\n        (assert (null foundp))))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Subtests\n\n(defun test-enqueue (queue list)\n  (let ((counter 0))\n    (dolist (i list)\n      (q:enqueue queue (stringify i) i)\n      (assert (= (incf counter) (q:size queue)))\n      (verify-heap-property (subseq (q::%prio-vector queue)\n                                    0 (q:size queue))))))\n\n(defun test-map (queue list)\n  (let ((expected (reduce #'+ list))\n        (actual 0))\n    (q:map queue (lambda (x) (incf actual (parse-integer x))))\n    (assert (= expected actual))))\n\n(defun test-do-queue (queue list)\n  (let ((expected (reduce #'+ list))\n        (actual 0))\n    (q:do-queue (x queue) (incf actual (parse-integer x)))\n    (assert (= expected actual))))\n\n(defun test-dequeue (queue expected-value expected-foundp)\n  (multiple-value-bind (value foundp) (q:dequeue queue)\n    (assert (equal expected-value value))\n    (assert (eql expected-foundp foundp))))\n\n(defun test-peek (queue expected-value expected-foundp)\n  (multiple-value-bind (value foundp) (q:peek queue)\n    (assert (equal expected-value value))\n    (assert (eql expected-foundp foundp))))\n\n(defun test-dequeue-and-peek (queue list)\n  (let ((counter (q:size queue)))\n    (dotimes (i (length list))\n      (test-peek queue (stringify i) t)\n      (assert (= counter (q:size queue)))\n      (test-dequeue queue (stringify i) t)\n      (assert (= (decf counter) (q:size queue))))))\n\n(defun test-dequeue-and-peek-empty (queue)\n  (test-peek queue nil nil)\n  (assert (= 0 (q:size queue)))\n  (test-dequeue queue nil nil)\n  (assert (= 0 (q:size queue))))\n\n(defun test-trim (queue list)\n  (assert (<= (length list) (length (q::%prio-vector queue))))\n  (assert (<= (length list) (length (q::%data-vector queue))))\n  (q:trim queue)\n  (assert (= 0 (length (q::%prio-vector queue))))\n  (assert (= 0 (length (q::%data-vector queue)))))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-stable-priority-queue/damn-fast-stable-priority-queue.asd",
    "content": ";;;; damn-fast-stable-priority-queue.asd\n\n(asdf:defsystem #:damn-fast-stable-priority-queue\n  :description \"A heap-based stable priority queue whose first and foremost priority is speed.\"\n  :author \"Michał \\\"phoe\\\" Herda <phoe@disroot.org>\"\n  :license  \"MIT\"\n  :version \"0.0.2\"\n  :serial t\n  :depends-on (#:alexandria)\n  :components ((:file \"src\"))\n  :in-order-to ((test-op (load-op #:damn-fast-stable-priority-queue/test)))\n  :perform (test-op (o c) (symbol-call \"DAMN-FAST-STABLE-PRIORITY-QUEUE/TEST\" \"RUN\")))\n\n(asdf:defsystem #:damn-fast-stable-priority-queue/test\n  :description \"Tests for Damn Fast Stable Priority Queue\"\n  :author \"Michał \\\"phoe\\\" Herda <phoe@disroot.org>\"\n  :license \"MIT\"\n  :version \"0.0.2\"\n  :serial t\n  :depends-on (#:alexandria #:damn-fast-stable-priority-queue)\n  :components ((:file \"test-distinct\")\n               (:file \"test-same\")\n               (:file \"test\")))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-stable-priority-queue/src.lisp",
    "content": ";;;; damn-fast-stable-priority-queue.lisp\n\n(defpackage #:damn-fast-stable-priority-queue\n  (:use #:cl)\n  (:shadow #:map)\n  (:local-nicknames (#:a #:alexandria))\n  (:export #:queue #:make-queue #:copy-queue\n           #:enqueue #:dequeue #:peek #:size #:trim #:map #:do-queue\n           #:queue-size-limit-reached\n           #:queue-size-limit-reached-queue\n           #:queue-size-limit-reached-object))\n\n(in-package #:damn-fast-stable-priority-queue)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Read-time variables\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defparameter *optimize-qualities*\n    #+real-damn-fast-stable-priority-queue\n    ;; Good luck.\n    `(optimize (speed 3) (debug 0) (safety 0) (space 0) (compilation-speed 0))\n    #-real-damn-fast-stable-priority-queue\n    `(optimize (speed 3))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Type definitions\n\n(deftype data-type () 't)\n\n(deftype data-vector-type () '(simple-array data-type (*)))\n\n(deftype prio-type () '(unsigned-byte 32))\n\n(deftype prio-vector-type () '(simple-array prio-type (*)))\n\n(deftype count-type () '(unsigned-byte 32))\n\n(deftype count-vector-type () '(simple-array count-type (*)))\n\n(deftype extension-factor-type () '(integer 2 256))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Structure definition\n\n(declaim (inline %make %data-vector %prio-vector %count-vector %count %size\n                 %extension-factor))\n\n(macrolet ((array (type)\n             `(make-array 256 :element-type ,type)))\n  (defstruct (queue (:conc-name #:%) (:constructor %make)\n                    (:predicate nil) (:copier nil))\n    (data-vector (array 'data-type) :type data-vector-type)\n    (prio-vector (array 'prio-type) :type prio-vector-type)\n    (count-vector (array 'count-type) :type count-vector-type)\n    (count 0 :type count-type)\n    (size 0 :type a:array-length)\n    (extension-factor 2 :type extension-factor-type)\n    (extend-queue-p t :type boolean)))\n\n(declaim (inline make-queue copy-queue))\n\n(declaim (ftype (function\n                 (&optional a:array-index extension-factor-type boolean)\n                 (values queue &optional))\n                make-queue))\n(defun make-queue (&optional\n                     (initial-storage-size 256)\n                     (extension-factor 2)\n                     (extend-queue-p t))\n  (declare (type extension-factor-type extension-factor))\n  (declare #.*optimize-qualities*)\n  (%make :extension-factor extension-factor\n         :data-vector (make-array initial-storage-size\n                                  :element-type 'data-type)\n         :prio-vector (make-array initial-storage-size\n                                  :element-type 'prio-type)\n         :count-vector (make-array initial-storage-size\n                                   :element-type 'count-type)\n         :extend-queue-p extend-queue-p))\n\n(defmethod print-object ((object queue) stream)\n  (print-unreadable-object (object stream :type t :identity t)\n    (format stream \"(~D)\" (%size object))))\n\n(declaim (ftype (function (queue) (values queue &optional)) copy-queue))\n(defun copy-queue (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (%make :extension-factor (%extension-factor queue)\n         :size (%size queue)\n         :count (%count queue)\n         :extend-queue-p (%extend-queue-p queue)\n         :data-vector (copy-seq (%data-vector queue))\n         :prio-vector (copy-seq (%prio-vector queue))\n         :count-vector (copy-seq (%count-vector queue))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Enqueueing\n\n(declaim (inline heapify-upwards enqueue))\n\n(declaim (ftype (function (data-vector-type prio-vector-type count-vector-type\n                                            a:array-length)\n                          (values null &optional))\n                heapify-upwards))\n(defun heapify-upwards (data-vector prio-vector count-vector index)\n  (declare (type data-vector-type data-vector))\n  (declare (type prio-vector-type prio-vector))\n  (declare (type count-vector-type count-vector))\n  (declare (type a:array-length index))\n  (declare #.*optimize-qualities*)\n  (do ((child-index index parent-index)\n       (parent-index (ash (1- index) -1) (ash (1- parent-index) -1)))\n      ((= child-index 0))\n    (let ((child-priority (aref prio-vector child-index))\n          (parent-priority (aref prio-vector parent-index)))\n      (cond ((or (< child-priority parent-priority)\n                 (and (= child-priority parent-priority)\n                      (let ((child-count (aref count-vector child-index))\n                            (parent-count (aref count-vector parent-index)))\n                        (< child-count parent-count))))\n             (rotatef (aref prio-vector parent-index)\n                      (aref prio-vector child-index))\n             (rotatef (aref data-vector parent-index)\n                      (aref data-vector child-index))\n             (rotatef (aref count-vector parent-index)\n                      (aref count-vector child-index)))\n            (t (return))))))\n\n(declaim (ftype (function (queue t fixnum) (values null &optional)) enqueue))\n(defun enqueue (queue object priority)\n  (declare (type queue queue))\n  (declare (type fixnum priority))\n  (declare #.*optimize-qualities*)\n  (symbol-macrolet ((data-vector (%data-vector queue))\n                    (prio-vector (%prio-vector queue))\n                    (count-vector (%count-vector queue)))\n    (let ((size (%size queue))\n          (count (%count queue))\n          (extension-factor (%extension-factor queue))\n          (length (array-total-size data-vector)))\n      (when (>= size length)\n        (unless (%extend-queue-p queue)\n          (error 'queue-size-limit-reached :queue queue :element object))\n        (let ((new-length (max 1 (mod (* length extension-factor)\n                                      (ash 1 64)))))\n          (declare (type a:array-length new-length))\n          (when (<= new-length length)\n            (error \"Integer overflow while resizing array: new-length ~D is ~\n                    smaller than old length ~D\" new-length length))\n          (setf data-vector (adjust-array data-vector new-length)\n                prio-vector (adjust-array prio-vector new-length)\n                count-vector (adjust-array count-vector new-length))))\n      (setf (aref data-vector size) object\n            (aref prio-vector size) priority\n            (aref count-vector size) count)\n      (heapify-upwards data-vector prio-vector count-vector (%size queue))\n      (incf (%size queue))\n      (incf (%count queue))\n      nil)))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Dequeueing\n\n(declaim (inline heapify-downwards dequeue))\n\n(declaim (ftype (function (data-vector-type prio-vector-type count-vector-type\n                                            a:array-index)\n                          (values null &optional))\n                heapify-downwards))\n(defun heapify-downwards (data-vector prio-vector count-vector size)\n  (declare (type data-vector-type data-vector))\n  (declare (type prio-vector-type prio-vector))\n  (declare (type count-vector-type count-vector))\n  (declare #.*optimize-qualities*)\n  (let ((parent-index 0))\n    (declare (type (unsigned-byte 48) parent-index))\n    (loop\n      (let* ((left-index (+ (* parent-index 2) 1))\n             (left-index-validp (< left-index size))\n             (right-index (+ (* parent-index 2) 2))\n             (right-index-validp (< right-index size)))\n        (flet ((swap-left ()\n                 ;;(print \"swap-left\")\n                 (rotatef (aref prio-vector parent-index)\n                          (aref prio-vector left-index))\n                 (rotatef (aref data-vector parent-index)\n                          (aref data-vector left-index))\n                 (rotatef (aref count-vector parent-index)\n                          (aref count-vector left-index))\n                 (setf parent-index left-index))\n               (swap-right ()\n                 ;;(print \"swap-right\")\n                 (rotatef (aref prio-vector parent-index)\n                          (aref prio-vector right-index))\n                 (rotatef (aref data-vector parent-index)\n                          (aref data-vector right-index))\n                 (rotatef (aref count-vector parent-index)\n                          (aref count-vector right-index))\n                 (setf parent-index right-index)))\n          (declare (inline swap-left swap-right))\n          (when (and (not left-index-validp)\n                     (not right-index-validp))\n            (return))\n          (when (and left-index-validp\n                     (or (< (aref prio-vector parent-index)\n                            (aref prio-vector left-index))\n                         (and (= (aref prio-vector parent-index)\n                                 (aref prio-vector left-index))\n                              (< (aref count-vector parent-index)\n                                 (aref count-vector left-index))))\n                     (or (not right-index-validp)\n                         (< (aref prio-vector parent-index)\n                            (aref prio-vector right-index))\n                         (and (= (aref prio-vector parent-index)\n                                 (aref prio-vector right-index))\n                              (< (aref count-vector parent-index)\n                                 (aref count-vector right-index)))))\n            (return))\n          (if (and right-index-validp\n                   (or (< (aref prio-vector right-index)\n                          (aref prio-vector left-index))\n                       (and (= (aref prio-vector right-index)\n                               (aref prio-vector left-index))\n                            (< (aref count-vector right-index)\n                               (aref count-vector left-index)))))\n              (swap-right)\n              (swap-left)))))))\n\n(declaim (ftype (function (queue) (values t boolean &optional)) dequeue))\n(defun dequeue (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (if (= 0 (%size queue))\n      (values nil nil)\n      (let ((data-vector (%data-vector queue))\n            (prio-vector (%prio-vector queue))\n            (count-vector (%count-vector queue)))\n        (multiple-value-prog1 (values (aref data-vector 0) t)\n          (decf (%size queue))\n          (let ((old-data (aref data-vector (%size queue)))\n                (old-prio (aref prio-vector (%size queue)))\n                (old-count (aref count-vector (%size queue))))\n            (setf (aref data-vector 0) old-data\n                  (aref prio-vector 0) old-prio\n                  (aref count-vector 0) old-count))\n          (heapify-downwards data-vector prio-vector count-vector\n                             (%size queue))))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Introspection and maintenance\n\n(declaim (inline peek size trim))\n\n(declaim (ftype (function (queue) (values t boolean &optional)) peek))\n(defun peek (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (if (= 0 (%size queue))\n      (values nil nil)\n      (values (aref (%data-vector queue) 0) t)))\n\n(declaim (ftype (function (queue) (values a:array-length &optional)) size))\n(defun size (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (%size queue))\n\n(declaim (ftype (function (queue) (values null &optional)) trim))\n(defun trim (queue)\n  (declare (type queue queue))\n  (declare #.*optimize-qualities*)\n  (let ((size (%size queue)))\n    (setf (%data-vector queue) (adjust-array (%data-vector queue) size)\n          (%prio-vector queue) (adjust-array (%prio-vector queue) size)\n          (%count-vector queue) (adjust-array (%count-vector queue) size))\n    nil))\n\n(declaim (ftype (function (queue (function (t) t)) (values null &optional))\n                map))\n(defun map (queue function)\n  (loop repeat (%size queue)\n        for data across (%data-vector queue)\n        do (funcall function data)))\n\n(defmacro do-queue ((object queue &optional result) &body body)\n  (multiple-value-bind (forms declarations) (a:parse-body body)\n    (a:once-only (queue)\n      `(loop repeat (%size ,queue)\n             for ,object across (%data-vector ,queue)\n             do (locally ,@declarations (tagbody ,@forms))\n             finally (return ,result)))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Conditions\n\n(defun report-queue-size-limit-reached (condition stream)\n  (let ((queue (queue-size-limit-reached-queue condition))\n        (element (queue-size-limit-reached-object condition)))\n    (format stream \"Size limit (~D) reached for non-extensible ~\n                    queue ~S while trying to enqueue element ~S onto it.\"\n            (length (%data-vector queue)) queue element)))\n\n(define-condition queue-size-limit-reached (error)\n  ((%queue :reader queue-size-limit-reached-queue :initarg :queue)\n   (%object :reader queue-size-limit-reached-object :initarg :element))\n  (:default-initargs :queue (a:required-argument :queue)\n                     :object (a:required-argument :object))\n  (:report report-queue-size-limit-reached))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-stable-priority-queue/test-distinct.lisp",
    "content": ";;;; damn-fast-stable-priority-queue-test-distinct.lisp\n\n(defpackage #:damn-fast-stable-priority-queue/test-distinct-priorities\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:q #:damn-fast-stable-priority-queue))\n  (:export #:run))\n\n(in-package #:damn-fast-stable-priority-queue/test-distinct-priorities)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Utilities\n\n(defun verify-heap-property (vector)\n  (loop with length = (length vector)\n        for parent from 0 below (truncate length 2)\n        for left = (+ (* parent 2) 1)\n        for right = (+ (* parent 2) 2)\n        do (assert (< (aref vector parent) (aref vector left)) ()\n                   \"VERIFY-HEAP-PROPERTY: Invalid left child: ~D -> ~D\"\n                   (aref vector parent) (aref vector left))\n        when (oddp length)\n          do (assert (< (aref vector parent) (aref vector right)) ()\n                     \"VERIFY-HEAP-PROPERTY: Invalid right child: ~D -> ~D\"\n                     (aref vector parent) (aref vector right))))\n\n(defun stringify (i) (format nil \"~D\" i))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Main interface\n\n(defvar *verbose* nil)\n\n(defun run (&optional *verbose*)\n  (dolist (length (nconc (a:iota 64 :start 1) '(256 1024 4096)))\n    (when *verbose* (format t \"~&Testing with ~4,' D elements\" length))\n    (let ((queue (q:make-queue (max 1 (ash length -4)))))\n      (perform-test queue (a:iota length))\n      (perform-test queue (nreverse (a:iota length)))\n      (dotimes (i 100)\n        (perform-test queue (a:shuffle (a:iota length)))))))\n\n(defun perform-test (queue list)\n  (when *verbose* (princ \".\"))\n  (test-enqueue queue list)\n  (test-map queue list)\n  (test-do-queue queue list)\n  (test-dequeue-and-peek queue list)\n  (test-dequeue-and-peek-empty queue)\n  (test-trim queue list))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Subtests\n\n(defun test-enqueue (queue list)\n  (let ((counter 0))\n    (dolist (i list)\n      (q:enqueue queue (stringify i) i)\n      (assert (= (incf counter) (q:size queue)))\n      (verify-heap-property (subseq (q::%prio-vector queue)\n                                    0 (q:size queue))))))\n\n(defun test-map (queue list)\n  (let ((expected (reduce #'+ list))\n        (actual 0))\n    (q:map queue (lambda (x) (incf actual (parse-integer x))))\n    (assert (= expected actual))))\n\n(defun test-do-queue (queue list)\n  (let ((expected (reduce #'+ list))\n        (actual 0))\n    (q:do-queue (x queue) (incf actual (parse-integer x)))\n    (assert (= expected actual))))\n\n(defun test-dequeue (queue expected-value expected-foundp)\n  (multiple-value-bind (value foundp) (q:dequeue queue)\n    (assert (equal expected-value value))\n    (assert (eql expected-foundp foundp))))\n\n(defun test-peek (queue expected-value expected-foundp)\n  (multiple-value-bind (value foundp) (q:peek queue)\n    (assert (equal expected-value value))\n    (assert (eql expected-foundp foundp))))\n\n(defun test-dequeue-and-peek (queue list)\n  (let ((counter (q:size queue)))\n    (dotimes (i (length list))\n      (test-peek queue (stringify i) t)\n      (assert (= counter (q:size queue)))\n      (test-dequeue queue (stringify i) t)\n      (assert (= (decf counter) (q:size queue))))))\n\n(defun test-dequeue-and-peek-empty (queue)\n  (test-peek queue nil nil)\n  (assert (= 0 (q:size queue)))\n  (test-dequeue queue nil nil)\n  (assert (= 0 (q:size queue))))\n\n(defun test-trim (queue list)\n  (assert (<= (length list) (length (q::%prio-vector queue))))\n  (assert (<= (length list) (length (q::%data-vector queue))))\n  (assert (<= (length list) (length (q::%count-vector queue))))\n  (q:trim queue)\n  (assert (= 0 (length (q::%prio-vector queue))))\n  (assert (= 0 (length (q::%data-vector queue))))\n  (assert (= 0 (length (q::%count-vector queue)))))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-stable-priority-queue/test-same.lisp",
    "content": ";;;; damn-fast-stable-priority-queue-test-same.lisp\n\n(defpackage #:damn-fast-stable-priority-queue/test-same-priorities\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria)\n                    (#:q #:damn-fast-stable-priority-queue))\n  (:export #:run))\n\n(in-package #:damn-fast-stable-priority-queue/test-same-priorities)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Utilities\n\n(defun verify-heap-property (vector)\n  (loop with length = (length vector)\n        for parent from 0 below (truncate length 2)\n        for left = (+ (* parent 2) 1)\n        for right = (+ (* parent 2) 2)\n        do (assert (< (aref vector parent) (aref vector left)) ()\n                   \"VERIFY-HEAP-PROPERTY: Invalid left child: ~D -> ~D\"\n                   (aref vector parent) (aref vector left))\n        when (oddp length)\n          do (assert (< (aref vector parent) (aref vector right)) ()\n                     \"VERIFY-HEAP-PROPERTY: Invalid right child: ~D -> ~D\"\n                     (aref vector parent) (aref vector right))))\n\n(defun stringify (i) (format nil \"~D\" i))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Main interface\n\n(defvar *verbose* nil)\n\n(defun run (&optional *verbose*)\n  (dolist (length (nconc (a:iota 64 :start 1) '(256 1024 4096)))\n    (when *verbose* (format t \"~&Testing with ~4,' D elements\" length))\n    (let ((queue (q:make-queue (max 1 (ash length -4)))))\n      (perform-test queue (a:iota length))\n      (perform-test queue (nreverse (a:iota length)))\n      (dotimes (i 100)\n        (perform-test queue (a:shuffle (a:iota length)))))))\n\n(defun perform-test (queue list)\n  (when *verbose* (princ \".\"))\n  (test-enqueue queue list)\n  (test-map queue list)\n  (test-do-queue queue list)\n  (test-dequeue-and-peek queue list)\n  (test-dequeue-and-peek-empty queue)\n  (test-trim queue list)\n  (setf (q::%count queue) 0))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Subtests\n\n(defun test-enqueue (queue list)\n  (let ((counter 0))\n    (dolist (i list)\n      (q:enqueue queue (stringify i) 0)\n      (assert (= (incf counter) (q:size queue)))\n      (verify-heap-property (subseq (q::%count-vector queue)\n                                    0 (q:size queue))))))\n\n(defun test-map (queue list)\n  (let ((expected (reduce #'+ list))\n        (actual 0))\n    (q:map queue (lambda (x) (incf actual (parse-integer x))))\n    (assert (= expected actual))))\n\n(defun test-do-queue (queue list)\n  (let ((expected (reduce #'+ list))\n        (actual 0))\n    (q:do-queue (x queue) (incf actual (parse-integer x)))\n    (assert (= expected actual))))\n\n(defun test-dequeue (queue expected-value expected-foundp)\n  (multiple-value-bind (value foundp) (q:dequeue queue)\n    (assert (equal expected-value value))\n    (assert (eql expected-foundp foundp))))\n\n(defun test-peek (queue expected-value expected-foundp)\n  (multiple-value-bind (value foundp) (q:peek queue)\n    (assert (equal expected-value value))\n    (assert (eql expected-foundp foundp))))\n\n(defun test-dequeue-and-peek (queue list)\n  (let ((counter (q:size queue)))\n    (loop for i from 0\n          for elt in list\n          do (test-peek queue (stringify elt) t)\n             (assert (= counter (q:size queue)))\n             (test-dequeue queue (stringify elt) t)\n             (assert (= (decf counter) (q:size queue))))))\n\n(defun test-dequeue-and-peek-empty (queue)\n  (test-peek queue nil nil)\n  (assert (= 0 (q:size queue)))\n  (test-dequeue queue nil nil)\n  (assert (= 0 (q:size queue))))\n\n(defun test-trim (queue list)\n  (assert (<= (length list) (length (q::%prio-vector queue))))\n  (assert (<= (length list) (length (q::%data-vector queue))))\n  (assert (<= (length list) (length (q::%count-vector queue))))\n  (q:trim queue)\n  (assert (= 0 (length (q::%prio-vector queue))))\n  (assert (= 0 (length (q::%data-vector queue))))\n  (assert (= 0 (length (q::%count-vector queue)))))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/damn-fast-stable-priority-queue/test.lisp",
    "content": ";;;; damn-fast-stable-priority-queue-test.lisp\n\n(defpackage #:damn-fast-stable-priority-queue/test\n  (:use #:cl)\n  (:local-nicknames (#:q #:damn-fast-stable-priority-queue))\n  (:export #:run))\n\n(in-package #:damn-fast-stable-priority-queue/test)\n\n(defun run (&optional verbose)\n  (when verbose (format t \"~&;;; Testing with the same priorities.\"))\n  (damn-fast-stable-priority-queue/test-same-priorities:run verbose)\n  (when verbose (format t \"~&;;; Testing with distinct priorities.\"))\n  (damn-fast-stable-priority-queue/test-distinct-priorities:run verbose)\n  (when verbose (format t \"~&;;; Testing semantics.\"))\n  (perform-error-test)\n  (perform-copy-test)\n  (when verbose (format t \"~&;;; Test complete.\")))\n\n(defun perform-error-test ()\n  (let ((queue (q:make-queue 4 2 nil)))\n    (dotimes (i 4) (q:enqueue queue (princ-to-string i) i))\n    (flet ((perform ()\n             (multiple-value-bind (value error)\n                 (ignore-errors (q:enqueue queue \"4\" 4))\n               (assert (null value))\n               (assert (typep error 'q:queue-size-limit-reached))\n               (assert (eq queue (q:queue-size-limit-reached-queue error)))\n               (assert (string= \"4\"\n                                (q:queue-size-limit-reached-object error))))))\n      (dotimes (i 4) (perform)))))\n\n(defun perform-copy-test ()\n  (let ((queue-1 (q:make-queue)))\n    (q:enqueue queue-1 42 1)\n    (let ((queue-2 (q:copy-queue queue-1)))\n      (q:enqueue queue-2 24 0)\n      ;; Check QUEUE-1\n      (multiple-value-bind (value foundp) (q:dequeue queue-1)\n        (assert (= 42 value))\n        (assert (eq t foundp)))\n      (multiple-value-bind (value foundp) (q:dequeue queue-1)\n        (assert (null value))\n        (assert (null foundp)))\n      ;; Check QUEUE-2\n      (multiple-value-bind (value foundp) (q:dequeue queue-2)\n        (assert (= 24 value))\n        (assert (eq t foundp)))\n      (multiple-value-bind (value foundp) (q:dequeue queue-2)\n        (assert (= 42 value))\n        (assert (eq t foundp)))\n      (multiple-value-bind (value foundp) (q:dequeue queue-2)\n        (assert (null value))\n        (assert (null foundp))))))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/priority-queue-benchmark/README.md",
    "content": "# Priority Queue Benchmark\n\nThe ASDF system `priority-queue-benchmark` contains a simple performance test with three tweakable parameters:\n* `+capacity+` - how many elements will be pushed into the queue,\n* `+repeat-count+` - how many times the test will be repeated,\n* `+pass-capacity-p+` - should the test pass the value of `+capacity+` into the queue?\n  * Note: not all tested queue libraries support passing the initial capacity or extension factor as parameters when constructing the queue. Therefore, for some libraries, this parameter is a no-op.\n\nThe performance test includes multiple priority queue/heap libraries available on Quicklisp, tested against four synthetic datasets:\n* an array of unique numbers from `0` to `n`, in increasing order,\n* an array of unique numbers from `0` to `n`, in decreasing order,\n* an array of unique numbers from `0` to `n`, in shuffled order,\n* an array of `n` zeroes.\n\nAll test functions are compiled with `(optimize speed)`.\n\n`:real-damn-fast-priority-queue` is `:damn-fast-priority-queue` compiled with `:real-damn-fast-priority-queue` pushed into `*features*`.\n\n`:real-damn-fast-stable-priority-queue` is `:damn-fast-stable-priority-queue` compiled with `:real-damn-fast-stable-priority-queue` pushed into `*features*`.\n\nThe listed timing does not include the time required to prepare the test vectors or to construct the priority queue object.\n\nPlease feel free to question, verify, and improve the code and results of this benchmark.\n\n## 409600 elements, 10 repeats, capacity passed\n\n| Library \\ Vector                      | :increasing | :decreasing | :shuffled |     :zero |\n|---------------------------------------|-------------|-------------|-----------|-----------|\n| :pettomato-indexed-priority-queue     |       4.323 |       5.943 |     6.463 |     0.687 |\n| :priority-queue                       |       6.335 |      10.195 |     7.251 |     0.911 |\n| :queues.priority-queue                |       7.663 |       7.295 |    13.539 |     3.463 |\n| :pileup                               |       2.019 |       2.207 |     2.143 |     2.083 |\n| :bodge-heap                           |       5.703 |      12.203 |     6.359 |     5.039 |\n| :cl-heap                              |       9.483 |      10.471 |    28.119 |    29.563 |\n| :heap                                 |       9.491 |      12.167 |     9.287 |     0.895 |\n| :minheap                              |       6.335 |       7.951 |     7.663 | **0.551** |\n| :damn-fast-priority-queue             |   **0.599** |   **0.719** | **0.819** |     0.631 |\n| :real-damn-fast-priority-queue        |   **0.543** |   **0.647** | **0.739** |     0.599 |\n| :damn-fast-stable-priority-queue      |       0.663 |       0.807 |     1.031 |     0.791 |\n| :real-damn-fast-stable-priority-queue |       0.543 |       0.723 |     0.899 |     0.599 |\n\n## 409600 elements, 10 repeats, capacity not passed\n\n| Library \\ Vector                      | :increasing | :decreasing | :shuffled |     :zero |\n|---------------------------------------|-------------|-------------|-----------|-----------|\n| :pettomato-indexed-priority-queue     |       4.759 |       5.963 |     7.311 |     0.671 |\n| :priority-queue                       |       6.339 |      10.067 |     7.427 |     0.915 |\n| :queues.priority-queue                |       7.223 |       7.207 |    11.807 |     3.487 |\n| :pileup                               |       1.851 |       2.187 |     2.111 |     2.103 |\n| :bodge-heap                           |       5.423 |      12.403 |     8.051 |     5.067 |\n| :cl-heap                              |       9.035 |      10.715 |    31.211 |    28.767 |\n| :heap                                 |       8.583 |      12.263 |     8.999 |     0.875 |\n| :minheap                              |       5.523 |       7.789 |    11.335 | **0.535** |\n| :damn-fast-priority-queue             |   **0.575** |   **0.727** | **0.931** |     0.627 |\n| :real-damn-fast-priority-queue        |   **0.519** |   **0.623** | **0.715** |     0.599 |\n| :damn-fast-stable-priority-queue      |       0.635 |       0.803 |     1.007 |     0.795 |\n| :real-damn-fast-stable-priority-queue |       0.555 |       0.727 |     0.887 |     0.639 |\n\n## 4096 elements, 1000 repeats, capacity passed\n\n| Library \\ Vector                      | :increasing | :decreasing | :shuffled |     :zero |\n|---------------------------------------|-------------|-------------|-----------|-----------|\n| :pettomato-indexed-priority-queue     |       2.567 |       3.599 |     2.887 |     0.675 |\n| :priority-queue                       |       3.863 |       5.995 |     4.359 |     0.863 |\n| :queues.priority-queue                |       5.491 |       4.963 |     5.231 |     2.583 |\n| :pileup                               |       1.391 |       1.591 |     1.471 |     1.487 |\n| :bodge-heap                           |       3.471 |       8.271 |     3.991 |     3.127 |\n| :cl-heap                              |       6.467 |      10.795 |    13.807 |    13.807 |\n| :heap                                 |       5.187 |       8.339 |     5.587 |     0.879 |\n| :minheap                              |       3.135 |       5.767 |     3.531 |     0.527 |\n| :damn-fast-priority-queue             |   **0.375** |   **0.455** | **0.471** | **0.267** |\n| :real-damn-fast-priority-queue        |   **0.327** |   **0.371** | **0.411** | **0.215** |\n| :damn-fast-stable-priority-queue      |       0.435 |       0.491 |     0.511 |     0.491 |\n| :real-damn-fast-stable-priority-queue |       0.351 |       0.419 |     0.435 |     0.419 |\n\n## 4096 elements, 1000 repeats, capacity not passed\n\n| Library \\ Vector                      | :increasing | :decreasing | :shuffled |     :zero |\n|---------------------------------------|-------------|-------------|-----------|-----------|\n| :pettomato-indexed-priority-queue     |       2.555 |       3.607 |     2.943 |     0.699 |\n| :priority-queue                       |       3.831 |       6.647 |     4.311 |     0.867 |\n| :queues.priority-queue                |       4.855 |       6.579 |     5.231 |     2.567 |\n| :pileup                               |       1.399 |       1.611 |     1.503 |     1.479 |\n| :bodge-heap                           |       3.419 |       8.199 |     4.171 |     3.107 |\n| :cl-heap                              |       6.479 |       8.527 |    13.967 |    13.987 |\n| :heap                                 |       5.243 |       7.539 |     5.619 |     0.915 |\n| :minheap                              |       3.215 |       4.487 |     3.471 |     0.547 |\n| :damn-fast-priority-queue             |   **0.379** |   **0.467** | **0.467** | **0.271** |\n| :real-damn-fast-priority-queue        |   **0.323** |   **0.387** | **0.415** | **0.215** |\n| :damn-fast-stable-priority-queue      |       0.403 |       0.491 |     0.495 |     0.491 |\n| :real-damn-fast-stable-priority-queue |       0.347 |       0.427 |     0.431 |     0.411 |\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/priority-queue-benchmark/benchmark.lisp",
    "content": ";;;; benchmark.lisp\n\n(defpackage #:priority-queue-benchmark\n  (:use #:cl)\n  (:local-nicknames (#:a #:alexandria))\n  (:export #:run))\n\n(in-package #:priority-queue-benchmark)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Test parameters\n\n(defconstant +capacity+ 409600)\n(defconstant +repeat-count+ 10)\n(defconstant +pass-capacity-p+ t)\n\n(defparameter *test-vectors*\n  '(:increasing :decreasing :shuffled :zero))\n\n(defparameter *test-functions*\n  (list 'test-pettomato-indexed-priority-queue\n        'test-priority-queue\n        'test-queues\n        'test-pileup\n        'test-bodge-heap\n        'test-cl-heap\n        'test-heap\n        'test-minheap\n        'test-damn-fast-priority-queue\n        'test-damn-fast-stable-priority-queue))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Performance testq\n\n(defun perform-test (name vector-name vector\n                     &key make-fn push-fn peek-fn pop-fn)\n  (declare (type function make-fn push-fn peek-fn pop-fn))\n  (declare (optimize speed))\n  (let* ((queue (funcall make-fn)))\n    (format t \"~&;;; Library: ~A\" name)\n    (format t \"~&;;; Element order: ~A~%~%\" vector-name)\n    (trivial-garbage:gc :full t)\n    (time (dotimes (i +repeat-count+)\n            (map nil (lambda (i) (funcall push-fn queue i)) vector)\n            (if (eq vector-name :zero)\n                (dotimes (i +capacity+)\n                  (assert (= 0 (the fixnum (funcall peek-fn queue))))\n                  (assert (= 0 (the fixnum (funcall pop-fn queue)))))\n                (dotimes (i +capacity+)\n                  (assert (= i (the fixnum (funcall peek-fn queue))))\n                  (assert (= i (the fixnum (funcall pop-fn queue))))))))))\n\n(defun make-test-vectors ()\n  (declare (optimize speed))\n  (let ((zero (make-array +capacity+\n                          :element-type `(integer 0 ,(1- +capacity+))\n                          :initial-element 0))\n        (increasing (make-array +capacity+\n                                :element-type `(integer 0 ,(1- +capacity+)))))\n    (loop for i from 0 below +capacity+ do (setf (aref increasing i) i))\n    (let ((decreasing (nreverse (copy-seq increasing)))\n          (shuffled (a:shuffle (copy-seq increasing))))\n      `(,(when (member :increasing *test-vectors*) `(:increasing ,increasing))\n        ,(when (member :decreasing *test-vectors*) `(:decreasing ,decreasing))\n        ,(when (member :shuffled *test-vectors*) `(:shuffled ,shuffled))\n        ,(when (member :zero *test-vectors*) `(:zero ,zero))))))\n\n(defun run ()\n  (declare (optimize speed))\n  (format t \"~&;;; Starting a priority queue performance test.\")\n  (format t \"~&;;; Testing with ~D elements and ~D repeats.\"\n          +capacity+ +repeat-count+)\n  (format t \"~&;;; ~:[Not p~;P~]assing capacity to constructors.~%~%\"\n          +pass-capacity-p+)\n  (dolist (data (make-test-vectors))\n    (dolist (function *test-functions*)\n      (apply (the function function) data)))\n  (format t \"~&;;; Performance test complete.~%\"))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;;;; Implementations\n\n(defun test-pettomato-indexed-priority-queue (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :pettomato-indexed-priority-queue\n   vector-name vector\n   :make-fn (lambda ()\n              (if +pass-capacity-p+\n                  (let* ((hash (make-hash-table :test 'eql)))\n                    (pettomato-indexed-priority-queue:make-empty-queue\n                     #'<\n                     (lambda (item index) (setf (gethash item hash) index))\n                     (lambda (item) (gethash item hash -1))\n                     :size +capacity+))\n                  (let* ((hash (make-hash-table :test 'eql)))\n                    (pettomato-indexed-priority-queue:make-empty-queue\n                     #'<\n                     (lambda (item index) (setf (gethash item hash) index))\n                     (lambda (item) (gethash item hash -1))))))\n   :push-fn (lambda (q i) (pettomato-indexed-priority-queue:queue-insert q i))\n   :peek-fn (lambda (q) (pettomato-indexed-priority-queue:queue-peek q))\n   :pop-fn (lambda (q) (pettomato-indexed-priority-queue:queue-pop q))))\n\n(defun test-priority-queue (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :priority-queue\n   vector-name vector\n   ;; No way to pass the starting capacity.\n   :make-fn (lambda () (priority-queue:make-pqueue #'<))\n   :push-fn (lambda (q i) (priority-queue:pqueue-push i i q))\n   :peek-fn (lambda (q) (priority-queue:pqueue-front q))\n   :pop-fn (lambda (q) (priority-queue:pqueue-pop q))))\n\n(defun test-queues (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :queues\n   vector-name vector\n   ;; No way to pass the starting capacity.\n   :make-fn (lambda () (queues:make-queue :priority-queue))\n   :push-fn (lambda (q i) (queues:qpush q i))\n   :peek-fn (lambda (q) (queues:qtop q))\n   :pop-fn (lambda (q) (queues:qpop q))))\n\n(defun test-pileup (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :pileup\n   vector-name vector\n   :make-fn (lambda () (if +pass-capacity-p+\n                           (pileup:make-heap #'< :size +capacity+)\n                           (pileup:make-heap #'<)))\n   :push-fn (lambda (q i) (pileup:heap-insert i q))\n   :peek-fn (lambda (q) (pileup:heap-top q))\n   :pop-fn (lambda (q) (pileup:heap-pop q))))\n\n(defun test-bodge-heap (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :bodge-heap\n   vector-name vector\n   :make-fn (lambda () (if +pass-capacity-p+\n                           (bodge-heap:make-binary-heap\n                            :expansion-factor +capacity+)\n                           (bodge-heap:make-binary-heap)))\n   :push-fn (lambda (q i) (bodge-heap:binary-heap-push q i))\n   :peek-fn (lambda (q) (bodge-heap:binary-heap-peek q))\n   :pop-fn (lambda (q) (bodge-heap:binary-heap-pop q))))\n\n(defun test-cl-heap (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :cl-heap\n   vector-name vector\n   :make-fn (lambda () (make-instance 'cl-heap:priority-queue))\n   :push-fn (lambda (q i) (cl-heap:enqueue q i i))\n   :peek-fn (lambda (q) (cl-heap:peep-at-queue q))\n   :pop-fn (lambda (q) (cl-heap:dequeue q))))\n\n(defun test-heap (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :heap\n   vector-name vector\n   ;; No way to pass the starting capacity.\n   :make-fn (lambda () (heap:make-heap #'<))\n   :push-fn (lambda (q i) (heap:heap-push i q))\n   :peek-fn (lambda (q) (heap:heap-peek q))\n   :pop-fn (lambda (q) (heap:heap-pop q))))\n\n(defun test-minheap (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :minheap\n   vector-name vector\n   ;; No way to pass the starting capacity.\n   :make-fn (lambda () (make-instance 'binary-heap:binary-heap))\n   :push-fn (lambda (q i) (binary-heap:insert q i i))\n   :peek-fn (lambda (q) (binary-heap:peek-min q))\n   :pop-fn (lambda (q) (binary-heap:extract-min q))))\n\n(defun test-damn-fast-priority-queue (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :damn-fast-priority-queue\n   vector-name vector\n   :make-fn (lambda () (if +pass-capacity-p+\n                           (damn-fast-priority-queue:make-queue +capacity+)\n                           (damn-fast-priority-queue:make-queue)))\n   :push-fn (lambda (q i) (damn-fast-priority-queue:enqueue q i i))\n   :peek-fn (lambda (q) (damn-fast-priority-queue:peek q))\n   :pop-fn (lambda (q) (damn-fast-priority-queue:dequeue q))))\n\n(defun test-damn-fast-stable-priority-queue (vector-name vector)\n  (declare (optimize speed))\n  (perform-test\n   :damn-fast-stable-priority-queue\n   vector-name vector\n   :make-fn (lambda ()\n              (if +pass-capacity-p+\n                  (damn-fast-stable-priority-queue:make-queue +capacity+)\n                  (damn-fast-stable-priority-queue:make-queue)))\n   :push-fn (lambda (q i) (damn-fast-stable-priority-queue:enqueue q i i))\n   :peek-fn (lambda (q) (damn-fast-stable-priority-queue:peek q))\n   :pop-fn (lambda (q) (damn-fast-stable-priority-queue:dequeue q))))\n"
  },
  {
    "path": "third-party/damn-fast-priority-queue/priority-queue-benchmark/priority-queue-benchmark.asd",
    "content": ";;;; priority-queue-benchmark.asd\n\n(asdf:defsystem #:priority-queue-benchmark\n  :description \"Figure out the fastest priority queue implementation yourself.\"\n  :author \"Michał \\\"phoe\\\" Herda <phoe@disroot.org>\"\n  :license  \"MIT\"\n  :version \"0.0.1\"\n  :serial t\n  :depends-on\n  (;; Benchmark dependencies\n   #:alexandria #:trivial-garbage\n   ;; Contestants\n   #:pettomato-indexed-priority-queue #:priority-queue #:queues.priority-queue\n   #:pileup #:bodge-heap #:cl-heap #:heap #:minheap\n   #:damn-fast-priority-queue #:damn-fast-stable-priority-queue)\n  :components ((:file \"benchmark\"))\n  :perform (test-op (o c) (symbol-call \"PRIORITY-QUEUE-BENCHMARK\" \"RUN\")))\n"
  },
  {
    "path": "third-party/fiveam/.boring",
    "content": "# Boring file regexps:\n\\#\n~$\n(^|/)_darcs($|/)\n\\.dfsl$\n\\.ppcf$\n\\.fasl$\n\\.x86f$\n\\.fas$\n\\.lib$\n^docs/html($|/)\n^docs/pdf($|/)\n^\\{arch\\}$\n(^|/).arch-ids($|/)\n"
  },
  {
    "path": "third-party/fiveam/.travis.yml",
    "content": "os: linux\ndist: bionic\nlanguage: generic\n\nenv:\n  jobs:\n    - LISP=sbcl\n    - LISP=ccl\n    - LISP=ecl\n    - LISP=abcl\n    - LISP=clisp\n    - LISP=allegro\n    - LISP=sbcl32\n    - LISP=ccl32\n    # - LISP=cmucl\n\njobs:\n  allow_failures:\n    - env: LISP=sbcl32\n    - env: LISP=ccl32\n    # - env: LISP=cmucl\n\nnotifications:\n  email:\n    on_success: change\n    on_failure: always\n  irc:\n    channels:\n      - \"chat.freenode.net#iolib\"\n    on_success: change\n    on_failure: always\n    use_notice: true\n    skip_join: true\n\ninstall:\n  - curl -L https://raw.githubusercontent.com/lispci/cl-travis/master/install.sh | sh\n\nscript:\n  - cl -e \"(prin1 (lisp-implementation-type)) (terpri) (prin1 (lisp-implementation-version)) (terpri)\n           (ql:quickload :fiveam/test :verbose t)\n           (uiop:quit (if (5am:run! :it.bese.fiveam) 0 -1))\"\n"
  },
  {
    "path": "third-party/fiveam/COPYING",
    "content": "Copyright (c) 2003-2006, Edward Marco Baringer\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n- Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n  \n- Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n    \n- Neither the name of Edward Marco Baringer, nor BESE, nor the names\nof its contributors may be used to endorse or promote products derived\nfrom this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "third-party/fiveam/README",
    "content": "This is FiveAM, a common lisp testing framework.\n\nThe documentation can be found in the docstrings, start with the\npackage :it.bese.fiveam (nicknamed 5AM).\n\nThe mailing list for FiveAM is fiveam-devel@common-lisp.net\n\nAll the code is Copyright (C) 2002-2006 Edward Marco Baringer.\n"
  },
  {
    "path": "third-party/fiveam/docs/make-qbook.lisp",
    "content": "(asdf:oos 'asdf:load-op :FiveAM)\n(asdf:oos 'asdf:load-op :qbook)\n\n(asdf:oos 'qbook:publish-op :FiveAM\n          :generator (make-instance 'qbook:html-generator\n                                    :title \"FiveAM\"\n                                    :output-directory\n                                    (merge-pathnames\n                                        (make-pathname :directory '(:relative \"docs\" \"html\"))\n                                        (asdf:component-pathname (asdf:find-system :FiveAM)))))\n\n          \n\n"
  },
  {
    "path": "third-party/fiveam/fiveam.asd",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Base: 10; -*-\n\n#.(unless (or #+asdf3.1 (version<= \"3.1\" (asdf-version)))\n    (error \"You need ASDF >= 3.1 to load this system correctly.\"))\n\n(defsystem :fiveam\n  :author \"Edward Marco Baringer <mb@bese.it>\"\n  :version (:read-file-form \"version.sexp\")\n  :description \"A simple regression testing framework\"\n  :license \"BSD\"\n  :depends-on (:alexandria :net.didierverna.asdf-flv  :trivial-backtrace)\n  :pathname \"src/\"\n  :components ((:file \"package\")\n               (:file \"utils\" :depends-on (\"package\"))\n               (:file \"check\" :depends-on (\"package\" \"utils\"))\n               (:file \"fixture\" :depends-on (\"package\"))\n               (:file \"classes\" :depends-on (\"package\"))\n               (:file \"random\" :depends-on (\"package\" \"check\"))\n               (:file \"test\" :depends-on (\"package\" \"fixture\" \"classes\"))\n               (:file \"explain\" :depends-on (\"package\" \"utils\" \"check\" \"classes\" \"random\"))\n               (:file \"suite\" :depends-on (\"package\" \"test\" \"classes\"))\n               (:file \"run\" :depends-on (\"package\" \"check\" \"classes\" \"test\" \"explain\" \"suite\")))\n  :in-order-to ((test-op (test-op :fiveam/test))))\n\n(defsystem :fiveam/test\n  :author \"Edward Marco Baringer <mb@bese.it>\"\n  :description \"FiveAM's own test suite\"\n  :license \"BSD\"\n  :depends-on (:fiveam)\n  :pathname \"t/\"\n  :components ((:file \"tests\"))\n  :perform (test-op (o c) (symbol-call :5am :run! :it.bese.fiveam)))\n\n;;;;@include \"src/package.lisp\"\n\n;;;;@include \"t/example.lisp\"\n"
  },
  {
    "path": "third-party/fiveam/src/check.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; * Checks\n\n;;;; At the lowest level testing the system requires that certain\n;;;; forms be evaluated and that certain post conditions are met: the\n;;;; value returned must satisfy a certain predicate, the form must\n;;;; (or must not) signal a certain condition, etc. In FiveAM these\n;;;; low level operations are called 'checks' and are defined using\n;;;; the various checking macros.\n\n;;;; Checks are the basic operators for collecting results. Tests and\n;;;; test suites on the other hand allow grouping multiple checks into\n;;;; logic collections.\n\n(defvar *test-dribble* t)\n\n(defmacro with-*test-dribble* (stream &body body)\n  `(let ((*test-dribble* ,stream))\n     (declare (special *test-dribble*))\n     ,@body))\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (def-special-environment run-state ()\n    result-list\n    current-test))\n\n;;;; ** Types of test results\n\n;;;; Every check produces a result object.\n\n(defclass test-result ()\n  ((reason :accessor reason :initarg :reason :initform \"no reason given\")\n   (test-case :accessor test-case :initarg :test-case)\n   (test-expr :accessor test-expr :initarg :test-expr))\n  (:documentation \"All checking macros will generate an object of\n type TEST-RESULT.\"))\n\n(defclass test-passed (test-result)\n  ()\n  (:documentation \"Class for successful checks.\"))\n\n(defgeneric test-passed-p (object)\n  (:method ((o t)) nil)\n  (:method ((o test-passed)) t))\n\n(define-condition check-failure (error)\n  ((reason :accessor reason :initarg :reason :initform \"no reason given\")\n   (test-case :accessor test-case :initarg :test-case)\n   (test-expr :accessor test-expr :initarg :test-expr))\n  (:documentation \"Signaled when a check fails.\")\n  (:report  (lambda (c stream)\n              (format stream \"The following check failed: ~S~%~A.\"\n                      (test-expr c)\n                      (reason c)))))\n\n(defun process-failure (test-expr &optional reason-format &rest format-args)\n  (let ((reason (and reason-format\n                     (apply #'format nil reason-format format-args))))\n    (with-simple-restart (ignore-failure \"Continue the test run.\")\n      (error 'check-failure :test-expr test-expr\n                            :reason reason))\n    (add-result 'test-failure :test-expr test-expr\n                              :reason reason)))\n\n(defclass test-failure (test-result)\n  ()\n  (:documentation \"Class for unsuccessful checks.\"))\n\n(defgeneric test-failure-p (object)\n  (:method ((o t)) nil)\n  (:method ((o test-failure)) t))\n\n(defclass unexpected-test-failure (test-failure)\n  ((actual-condition :accessor actual-condition :initarg :condition))\n  (:documentation \"Represents the result of a test which neither\npassed nor failed, but signaled an error we couldn't deal\nwith.\n\nNote: This is very different than a SIGNALS check which instead\ncreates a TEST-PASSED or TEST-FAILURE object.\"))\n\n(defclass test-skipped (test-result)\n  ()\n  (:documentation \"A test which was not run. Usually this is due to\nunsatisfied dependencies, but users can decide to skip the test when\nappropriate.\"))\n\n(defgeneric test-skipped-p (object)\n  (:method ((o t)) nil)\n  (:method ((o test-skipped)) t))\n\n(defun add-result (result-type &rest make-instance-args)\n  \"Create a TEST-RESULT object of type RESULT-TYPE passing it the\n  initialize args MAKE-INSTANCE-ARGS and add the resulting\n  object to the list of test results.\"\n  (with-run-state (result-list current-test)\n    (let ((result (apply #'make-instance result-type\n                         (append make-instance-args (list :test-case current-test)))))\n      (etypecase result\n        (test-passed  (format *test-dribble* \".\"))\n        (unexpected-test-failure (format *test-dribble* \"X\"))\n        (test-failure (format *test-dribble* \"f\"))\n        (test-skipped (format *test-dribble* \"s\")))\n      (push result result-list))))\n\n;;;; ** The check operators\n\n;;;; *** The IS check\n\n(defmacro is (test &rest reason-args)\n  \"The DWIM checking operator.\n\nIf TEST returns a true value a test-passed result is generated,\notherwise a test-failure result is generated. The reason, unless\nREASON-ARGS is provided, is generated based on the form of TEST:\n\n (predicate expected actual) - Means that we want to check\n whether, according to PREDICATE, the ACTUAL value is\n in fact what we EXPECTED.\n\n (predicate value) - Means that we want to ensure that VALUE\n satisfies PREDICATE.\n\n Wrapping the TEST form in a NOT simply produces a negated reason\n string.\"\n  (assert (listp test)\n          (test)\n          \"Argument to IS must be a list, not ~S\" test)\n  (let (bindings effective-test default-reason-args)\n    (with-gensyms (e a v)\n      (flet ((process-entry (predicate expected actual &optional negatedp)\n               ;; make sure EXPECTED is holding the entry that starts with 'values\n               (when (and (consp actual)\n                          (eq (car actual) 'values))\n                 (assert (not (and (consp expected)\n                                   (eq (car expected) 'values))) ()\n                                   \"Both the expected and actual part is a values expression.\")\n                 (rotatef expected actual))\n               (let ((setf-forms))\n                 (if (and (consp expected)\n                          (eq (car expected) 'values))\n                     (progn\n                       (setf expected (copy-list expected))\n                       (setf setf-forms (loop for cell = (rest expected) then (cdr cell)\n                                              for i from 0\n                                              while cell\n                                              when (eq (car cell) '*)\n                                              collect `(setf (elt ,a ,i) nil)\n                                              and do (setf (car cell) nil)))\n                       (setf bindings (list (list e `(list ,@(rest expected)))\n                                            (list a `(multiple-value-list ,actual)))))\n                     (setf bindings (list (list e expected)\n                                          (list a actual))))\n                 (setf effective-test `(progn\n                                         ,@setf-forms\n                                         ,(if negatedp\n                                              `(not (,predicate ,e ,a))\n                                              `(,predicate ,e ,a)))))))\n        (list-match-case test\n          ((not (?predicate ?expected ?actual))\n           (process-entry ?predicate ?expected ?actual t)\n           (setf default-reason-args\n                 (list \"~2&~S~2% evaluated to ~2&~S~2% which is ~2&~S~2%to ~2&~S~2% (it should not be)\"\n                       `',?actual a `',?predicate e)))\n          ((not (?satisfies ?value))\n           (setf bindings (list (list v ?value))\n                 effective-test `(not (,?satisfies ,v))\n                 default-reason-args\n                 (list \"~2&~S~2% evaluated to ~2&~S~2% which satisfies ~2&~S~2% (it should not)\"\n                       `',?value v `',?satisfies)))\n          ((?predicate ?expected ?actual)\n           (process-entry ?predicate ?expected ?actual)\n           (setf default-reason-args\n                 (list \"~2&~S~2% evaluated to ~2&~S~2% which is not ~2&~S~2% to ~2&~S~2%\"\n                       `',?actual a `',?predicate e)))\n          ((?satisfies ?value)\n           (setf bindings (list (list v ?value))\n                 effective-test `(,?satisfies ,v)\n                 default-reason-args\n                 (list \"~2&~S~2% evaluated to ~2&~S~2% which does not satisfy ~2&~S~2%\"\n                       `',?value v `',?satisfies)))\n          (?_\n           (setf bindings '()\n                 effective-test test\n                 default-reason-args (list \"~2&~S~2% was NIL.\" `',test)))))\n      `(let ,bindings\n         (if ,effective-test\n             (add-result 'test-passed :test-expr ',test)\n             (process-failure ',test\n                              ,@(or reason-args default-reason-args)))))))\n\n;;;; *** Other checks\n\n(defmacro skip (&rest reason)\n  \"Generates a TEST-SKIPPED result.\"\n  `(progn\n     (format *test-dribble* \"s\")\n     (add-result 'test-skipped :reason (format nil ,@reason))))\n\n(defmacro is-every (predicate &body clauses)\n  \"The input is either a list of lists, or a list of pairs. Generates (is (,predicate ,expr ,value))\n   for each pair of elements or (is (,predicate ,expr ,value) ,@reason) for each list.\"\n  `(progn\n     ,@(if (every #'consp clauses)\n           (loop for (expected actual . reason) in clauses\n                 collect `(is (,predicate ,expected ,actual) ,@reason))\n           (progn\n             (assert (evenp (list-length clauses)))\n             (loop for (expr value) on clauses by #'cddr\n                   collect `(is (,predicate ,expr ,value)))))))\n\n(defmacro is-true (condition &rest reason-args)\n  \"Like IS this check generates a pass if CONDITION returns true\n  and a failure if CONDITION returns false. Unlike IS this check\n  does not inspect CONDITION to determine how to report the\n  failure.\"\n  `(if ,condition\n       (add-result 'test-passed :test-expr ',condition)\n       (process-failure ',condition\n                        ,@(or reason-args\n                              `(\"~S did not return a true value\" ',condition)))))\n\n(defmacro is-false (condition &rest reason-args)\n  \"Generates a pass if CONDITION returns false, generates a\n  failure otherwise. Like IS-TRUE, and unlike IS, IS-FALSE does\n  not inspect CONDITION to determine what reason to give it case\n  of test failure\"\n  (with-gensyms (value)\n    `(let ((,value ,condition))\n       (if ,value\n           (process-failure ',condition\n                            ,@(or reason-args\n                                  `(\"~S returned the value ~S, which is true\" ',condition ,value)))\n           (add-result 'test-passed :test-expr ',condition)))))\n\n(defmacro signals (condition-spec\n                   &body body)\n  \"Generates a pass if BODY signals a condition of type\nCONDITION. BODY is evaluated in a block named NIL, CONDITION is\nnot evaluated.\"\n  (let ((block-name (gensym)))\n    (destructuring-bind (condition &optional reason-control &rest reason-args)\n        (ensure-list condition-spec)\n      `(block ,block-name\n         (handler-bind ((,condition (lambda (c)\n                                      (declare (ignore c))\n                                      ;; ok, body threw condition\n                                      (add-result 'test-passed\n                                                  :test-expr ',condition)\n                                      (return-from ,block-name t))))\n           (block nil\n             ,@body))\n         (process-failure\n           ',condition\n           ,@(if reason-control\n                 `(,reason-control ,@reason-args)\n                 `(\"Failed to signal a ~S\" ',condition)))\n         (return-from ,block-name nil)))))\n\n(defmacro finishes (&body body)\n  \"Generates a pass if BODY executes to normal completion. In\nother words if body does signal, return-from or throw this test\nfails.\"\n  `(unwind-protect-case () (progn ,@body)\n     (:normal (add-result 'test-passed :test-expr ',body))\n     (:abort (process-failure ',body \"Test didn't finish\"))))\n\n(defmacro pass (&rest message-args)\n  \"Simply generate a PASS.\"\n  `(add-result 'test-passed\n               :test-expr ',message-args\n               ,@(when message-args\n                   `(:reason (format nil ,@message-args)))))\n\n(defmacro fail (&rest message-args)\n  \"Simply generate a FAIL.\"\n  `(process-failure ',message-args\n                    ,@message-args))\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\n"
  },
  {
    "path": "third-party/fiveam/src/classes.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n(defclass testable-object ()\n  ((name :initarg :name :accessor name\n         :documentation \"A symbol naming this test object.\")\n   (description :initarg :description :accessor description :initform nil\n                :documentation \"The textual description of this test object.\")\n   (depends-on :initarg :depends-on :accessor depends-on :initform nil\n               :documentation \"The list of AND, OR, NOT forms specifying when to run this test.\")\n   (status :initarg :status :accessor status :initform :unknown\n           :documentation \"A symbol specifying the current status\n\t   of this test. Either: T - this test (and all its\n\t   dependencies, have passed. NIL - this test\n\t   failed (either it failed or its dependecies weren't\n\t   met. :circular this test has a circular dependency\n\t   and was skipped. Or :depends-not-satisfied or :resolving\")\n   (profiling-info :accessor profiling-info\n                   :initform nil\n                   :documentation \"An object representing how\n                   much time and memory where used by the\n                   test.\")\n   (collect-profiling-info :accessor collect-profiling-info\n                           :initarg :collect-profiling-info\n                           :initform nil\n                           :documentation \"When T profiling\n                           information will be collected when the\n                           test is run.\")))\n\n(defmethod print-object ((test testable-object) stream)\n  (print-unreadable-object (test stream :type t :identity t)\n    (format stream \"~S\" (name test))))\n\n(defclass test-suite (testable-object)\n  ((tests :accessor tests :initform (make-hash-table :test 'eql)\n          :documentation \"The hash table mapping names to test\n\t  objects in this suite. The values in this hash table\n\t  can be either test-cases or other test-suites.\"))\n  (:documentation \"A test suite is a collection of tests or test suites.\n\nTest suites serve to organize tests into groups so that the\ndeveloper can chose to run some tests and not just one or\nall. Like tests test suites have a name and a description.\n\nTest suites, like tests, can be part of other test suites, this\nallows the developer to create a hierarchy of tests where sub\ntrees can be singularly run.\n\nRunning a test suite has the effect of running every test (or\nsuite) in the suite.\"))\n\n(defclass test-case (testable-object)\n  ((test-lambda :initarg :test-lambda :accessor test-lambda\n                :documentation \"The function to run.\")\n   (runtime-package :initarg :runtime-package :accessor runtime-package\n                    :documentation \"By default it stores *package* from the time this test was defined (macroexpanded).\")\n   (test-suite :initarg :test-suite\n               :accessor test-suite\n               :initform nil\n               :documentation \"The test-suite associated with this test\"))\n  (:documentation \"A test case is a single, named, collection of\nchecks.\n\nA test case is the smallest organizational element which can be\nrun individually. Every test case has a name, which is a symbol,\na description and a test lambda. The test lambda is a regular\nfuncall'able function which should use the various checking\nmacros to collect results.\n\nEvery test case is part of a suite, when a suite is not\nexplicitly specified (either via the :SUITE parameter to the TEST\nmacro or the global variable *SUITE*) the test is inserted into\nthe global suite named NIL.\n\nSometimes we want to run a certain test only if another test has\npassed. FiveAM allows us to specify the ways in which one test is\ndependent on another.\n\n- AND Run this test only if all the named tests passed.\n\n- OR Run this test if at least one of the named tests passed.\n\n- NOT Run this test only if another test has failed.\n\nFiveAM considers a test to have passed if all the checks executed\nwere successful, otherwise we consider the test a failure.\n\nWhen a test is not run due to it's dependencies having failed a\ntest-skipped result is added to the results.\"))\n\n(defclass explainer ()\n  ())\n\n(defclass text-explainer (explainer)\n  ())\n\n(defclass simple-text-explainer (text-explainer)\n  ())\n\n(defclass detailed-text-explainer (text-explainer)\n  ())\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\n"
  },
  {
    "path": "third-party/fiveam/src/explain.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; * Analyzing the results\n\n(defparameter *verbose-failures* nil\n  \"T if we should print the expression failing, NIL otherwise.\")\n\n;;;; Just as important as defining and runnig the tests is\n;;;; understanding the results. FiveAM provides the function EXPLAIN\n;;;; which prints a human readable summary (number passed, number\n;;;; failed, what failed and why, etc.) of a list of test results.\n\n(defgeneric explain (explainer results &optional stream recursive-depth)\n  (:documentation \"Given a list of test results report write to stream detailed\n human readable statistics regarding the results.\"))\n\n(defmethod explain ((exp detailed-text-explainer) results\n                    &optional (stream *test-dribble*) (recursive-depth 0))\n  (multiple-value-bind (num-checks passed num-passed passed%\n                                   skipped num-skipped skipped%\n                                   failed num-failed failed%\n                                   unknown num-unknown unknown%)\n      (partition-results results)\n    (declare (ignore passed))\n    (flet ((output (&rest format-args)\n             (format stream \"~&~vT\" recursive-depth)\n             (apply #'format stream format-args)))\n\n      (when (zerop num-checks)\n        (output \"Didn't run anything...huh?\")\n        (return-from explain nil))\n      (output \"Did ~D check~P.~%\" num-checks num-checks)\n      (output \"   Pass: ~D (~2D%)~%\" num-passed passed%)\n      (output \"   Skip: ~D (~2D%)~%\" num-skipped skipped%)\n      (output \"   Fail: ~D (~2D%)~%\" num-failed failed%)\n      (when unknown\n        (output \"   UNKNOWN RESULTS: ~D (~2D)~%\" num-unknown unknown%))\n      (terpri stream)\n      (when failed\n        (output \"Failure Details:~%\")\n        (dolist (f (reverse failed))\n          (output \"--------------------------------~%\")\n          (output \"~A ~A~@{[~A]~}: ~%\"\n                  (name (test-case f))\n                  (let ((suite (name (test-suite (test-case f)))))\n                    (if suite\n                        (format nil \"in ~A \" suite)\n                        \"\"))\n                  (description (test-case f)))\n          (output \"     ~A~%\" (reason f))\n          (when (for-all-test-failed-p f)\n            (output \"Results collected with failure data:~%\")\n            (explain exp (slot-value f 'result-list)\n                     stream (+ 4 recursive-depth)))\n          (when (and *verbose-failures* (test-expr f))\n            (output \"    ~S~%\" (test-expr f)))\n          (output \"--------------------------------~%\"))\n        (terpri stream))\n      (when skipped\n        (output \"Skip Details:~%\")\n        (dolist (f skipped)\n          (output \"~A ~@{[~A]~}: ~%\"\n                  (name (test-case f))\n                  (description (test-case f)))\n          (output \"    ~A~%\" (reason f)))\n        (terpri stream)))))\n\n(defmethod explain ((exp simple-text-explainer) results\n                    &optional (stream *test-dribble*) (recursive-depth 0))\n  (multiple-value-bind (num-checks passed num-passed passed%\n                                   skipped num-skipped skipped%\n                                   failed num-failed failed%\n                                   unknown num-unknown unknown%)\n      (partition-results results)\n    (declare (ignore passed passed% skipped skipped% failed failed% unknown unknown%))\n    (format stream \"~&~vTRan ~D checks, ~D passed\" recursive-depth num-checks num-passed)\n    (when (plusp num-skipped)\n      (format stream \", ~D skipped \" num-skipped))\n    (format stream \" and ~D failed.~%\" num-failed)\n    (when (plusp num-unknown)\n      (format stream \"~vT~D UNKNOWN RESULTS.~%\" recursive-depth num-unknown))))\n\n(defun partition-results (results-list)\n  (let ((num-checks (length results-list)))\n    (destructuring-bind (passed skipped failed unknown)\n        (partitionx results-list\n                    (lambda (res)\n                      (typep res 'test-passed))\n                    (lambda (res)\n                      (typep res 'test-skipped))\n                    (lambda (res)\n                      (typep res 'test-failure))\n                    t)\n      (if (zerop num-checks)\n          (values 0\n                  nil 0 0\n                  nil 0 0\n                  nil 0 0\n                  nil 0 0)\n          (values\n           num-checks\n           passed (length passed) (floor (* 100 (/ (length passed) num-checks)))\n           skipped (length skipped) (floor (* 100 (/ (length skipped) num-checks)))\n           failed (length failed) (floor (* 100 (/ (length failed) num-checks)))\n           unknown (length unknown) (floor (* 100 (/ (length failed) num-checks))))))))\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\n"
  },
  {
    "path": "third-party/fiveam/src/fixture.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; ** Fixtures\n\n;;;; When running tests we often need to setup some kind of context\n;;;; (create dummy db connections, simulate an http request,\n;;;; etc.). Fixtures provide a way to conviently hide this context\n;;;; into a macro and allow the test to focus on testing.\n\n;;;; NB: A FiveAM fixture is nothing more than a macro. Since the term\n;;;; 'fixture' is so common in testing frameworks we've provided a\n;;;; wrapper around defmacro for this purpose.\n\n(defvar *fixture*\n  (make-hash-table :test 'eql)\n  \"Lookup table mapping fixture names to fixture\n  objects.\")\n\n(defun get-fixture (key &optional default)\n  (gethash key *fixture* default))\n\n(defun (setf get-fixture) (value key)\n  (setf (gethash key *fixture*) value))\n\n(defun rem-fixture (key)\n  (remhash key *fixture*))\n\n(defmacro def-fixture (name (&rest args) &body body)\n  \"Defines a fixture named NAME. A fixture is very much like a\nmacro but is used only for simple templating. A fixture created\nwith DEF-FIXTURE is a macro which can use the special macrolet\n&BODY to specify where the body should go.\n\nSee Also: WITH-FIXTURE\n\"\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (setf (get-fixture ',name) (cons ',args ',body))\n     ',name))\n\n(defmacro with-fixture (fixture-name (&rest args) &body body)\n  \"Insert BODY into the fixture named FIXTURE-NAME.\n\nSee Also: DEF-FIXTURE\"\n  (assert (get-fixture fixture-name)\n          (fixture-name)\n          \"Unknown fixture ~S.\" fixture-name)\n  (destructuring-bind ((&rest largs) &rest lbody)\n      (get-fixture fixture-name)\n    `(macrolet ((&body () '(progn ,@body)))\n       (funcall (lambda (,@largs) ,@lbody) ,@args))))\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "third-party/fiveam/src/package.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: CL-USER; Base: 10; -*-\n\n;;;; * Introduction\n\n;;;; FiveAM is a testing framework. It takes care of all the boring\n;;;; bookkeeping associated with managing a test framework allowing\n;;;; the developer to focus on writing tests and code.\n\n;;;; FiveAM was designed with the following premises:\n\n;;;; - Defining tests should be about writing tests, not\n;;;; infrastructure. The developer should be able to focus on what\n;;;; they're testing, not the testing framework.\n\n;;;; - Interactive testing is the norm. Common Lisp is an interactive\n;;;; development environment, the testing environment should allow the\n;;;; developer to quickly and easily redefine, change, remove and run\n;;;; tests.\n\n(defpackage :it.bese.fiveam\n  (:use :common-lisp :alexandria)\n  (:nicknames :5am :fiveam)\n  #+sb-package-locks\n  (:lock t)\n  (:export\n   ;; creating tests and test-suites\n   #:make-suite\n   #:def-suite\n   #:def-suite*\n   #:in-suite\n   #:in-suite*\n   #:test\n   #:def-test\n   #:get-test\n   #:rem-test\n   #:test-names\n   #:*default-test-compilation-time*\n   ;; fixtures\n   #:def-fixture\n   #:with-fixture\n   #:get-fixture\n   #:rem-fixture\n   ;; running checks\n   #:is\n   #:is-every\n   #:is-true\n   #:is-false\n   #:signals\n   #:finishes\n   #:skip\n   #:pass\n   #:fail\n   #:*test-dribble*\n   #:for-all\n   #:*num-trials*\n   #:*max-trials*\n   #:gen-integer\n   #:gen-float\n   #:gen-character\n   #:gen-string\n   #:gen-list\n   #:gen-tree\n   #:gen-buffer\n   #:gen-one-element\n   ;; running tests\n   #:run\n   #:run-all-tests\n   #:explain\n   #:explain!\n   #:run!\n   #:debug!\n   #:!\n   #:!!\n   #:!!!\n   #:*run-test-when-defined*\n   #:*debug-on-error*\n   #:*debug-on-failure*\n   #:*on-error*\n   #:*on-failure*\n   #:*verbose-failures*\n   #:*print-names*\n   #:results-status))\n\n;;;; You can use #+5am to put your test-defining code inline with your\n;;;; other code - and not require people to have fiveam to run your\n;;;; package.\n\n(pushnew :5am *features*)\n\n;;;;@include \"check.lisp\"\n\n;;;;@include \"random.lisp\"\n\n;;;;@include \"fixture.lisp\"\n\n;;;;@include \"test.lisp\"\n\n;;;;@include \"suite.lisp\"\n\n;;;;@include \"run.lisp\"\n\n;;;;@include \"explain.lisp\"\n\n;;;; * Colophon\n\n;;;; This documentaion was written by Edward Marco Baringer\n;;;; <mb@bese.it> and generated by qbook.\n\n;;;; ** COPYRIGHT\n\n;;;; Copyright (c) 2002-2003, Edward Marco Baringer\n;;;; All rights reserved.\n\n;;;; Redistribution and use in source and binary forms, with or without\n;;;; modification, are permitted provided that the following conditions are\n;;;; met:\n\n;;;;  - Redistributions of source code must retain the above copyright\n;;;;    notice, this list of conditions and the following disclaimer.\n\n;;;;  - Redistributions in binary form must reproduce the above copyright\n;;;;    notice, this list of conditions and the following disclaimer in the\n;;;;    documentation and/or other materials provided with the distribution.\n\n;;;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;;;    of its contributors may be used to endorse or promote products\n;;;;    derived from this software without specific prior written permission.\n\n;;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;;;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;;;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;;;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;;;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;;;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;;;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;;;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;;;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;;;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;;;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\n"
  },
  {
    "path": "third-party/fiveam/src/random.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; ** Random (QuickCheck-ish) testing\n\n;;;; FiveAM provides the ability to automatically generate a\n;;;; collection of random input data for a specific test and run a\n;;;; test multiple times.\n\n;;;; Specification testing is done through the FOR-ALL macro. This\n;;;; macro will bind variables to random data and run a test body a\n;;;; certain number of times. Should the test body ever signal a\n;;;; failure we stop running and report what values of the variables\n;;;; caused the code to fail.\n\n;;;; The generation of the random data is done using \"generator\n;;;; functions\" (see below for details). A generator function is a\n;;;; function which creates, based on user supplied parameters, a\n;;;; function which returns random data. In order to facilitate\n;;;; generating good random data the FOR-ALL macro also supports guard\n;;;; conditions and creating one random input based on the values of\n;;;; another (see the FOR-ALL macro for details).\n\n;;;; *** Public Interface to the Random Tester\n\n(defparameter *num-trials* 100\n  \"Number of times we attempt to run the body of the FOR-ALL test.\")\n\n(defparameter *max-trials* 10000\n  \"Number of total times we attempt to run the body of the\n  FOR-ALL test including when the body is skipped due to failed\n  guard conditions.\n\nSince we have guard conditions we may get into infinite loops\nwhere the test code is never run due to the guards never\nreturning true. This second run limit prevents that.\")\n\n(defmacro for-all (bindings &body body)\n  \"Bind BINDINGS to random variables and test BODY *num-trials* times.\n\nBINDINGS is a list of binding forms, each element is a list\nof (BINDING VALUE &optional GUARD). Value, which is evaluated\nonce when the for-all is evaluated, must return a generator which\nbe called each time BODY is evaluated. BINDING is either a symbol\nor a list which will be passed to destructuring-bind. GUARD is a\nform which, if present, stops BODY from executing when IT returns\nNIL. The GUARDS are evaluated after all the random data has been\ngenerated and they can refer to the current value of any\nbinding. NB: Generator forms, unlike guard forms, can not contain\nreferences to the bound variables.\n\nExamples:\n\n  (for-all ((a (gen-integer)))\n    (is (integerp a)))\n\n  (for-all ((a (gen-integer) (plusp a)))\n    (is (integerp a))\n    (is (plusp a)))\n\n  (for-all ((less (gen-integer))\n            (more (gen-integer) (< less more)))\n    (is (<= less more)))\n\n  (for-all (((a b) (gen-two-integers)))\n    (is (integerp a))\n    (is (integerp b)))\"\n  (with-gensyms (test-lambda-args)\n    `(perform-random-testing\n      (list ,@(mapcar #'second bindings))\n      (lambda (,test-lambda-args)\n        (destructuring-bind ,(mapcar #'first bindings)\n            ,test-lambda-args\n          (if (and ,@(delete-if #'null (mapcar #'third bindings)))\n              (progn ,@body)\n              (throw 'run-once\n                (list :guard-conditions-failed))))))))\n\n;;;; *** Implementation\n\n;;;; We could just make FOR-ALL a monster macro, but having FOR-ALL be\n;;;; a preproccessor for the perform-random-testing function is\n;;;; actually much easier.\n\n(defun perform-random-testing (generators body)\n  (loop\n     with random-state = *random-state*\n     with total-counter = *max-trials*\n     with counter = *num-trials*\n     with run-at-least-once = nil\n     until (or (zerop total-counter)\n               (zerop counter))\n     do (let ((result (perform-random-testing/run-once generators body)))\n          (ecase (first result)\n            (:pass\n             (decf counter)\n             (decf total-counter)\n             (setf run-at-least-once t))\n            (:no-tests\n             (add-result 'for-all-test-no-tests\n                         :reason \"No tests\"\n                         :random-state random-state)\n             (return-from perform-random-testing nil))\n            (:guard-conditions-failed\n             (decf total-counter))\n            (:fail\n             (add-result 'for-all-test-failed\n                         :reason \"Found failing test data\"\n                         :random-state random-state\n                         :failure-values (second result)\n                         :result-list (third result))\n             (return-from perform-random-testing nil))))\n     finally (if run-at-least-once\n                 (add-result 'for-all-test-passed)\n                 (add-result 'for-all-test-never-run\n                             :reason \"Guard conditions never passed\"))))\n\n(defun perform-random-testing/run-once (generators body)\n  (catch 'run-once\n    (bind-run-state ((result-list '()))\n      (let ((values (mapcar #'funcall generators)))\n        (funcall body values)\n        (cond\n          ((null result-list)\n           (throw 'run-once (list :no-tests)))\n          ((every #'test-passed-p result-list)\n           (throw 'run-once (list :pass)))\n          ((notevery #'test-passed-p result-list)\n           (throw 'run-once (list :fail values result-list))))))))\n\n(defclass for-all-test-result ()\n  ((random-state :initarg :random-state)))\n\n(defclass for-all-test-passed (test-passed for-all-test-result)\n  ())\n\n(defclass for-all-test-failed (test-failure for-all-test-result)\n  ((failure-values :initarg :failure-values)\n   (result-list :initarg :result-list)))\n\n(defgeneric for-all-test-failed-p (object)\n  (:method ((object for-all-test-failed)) t)\n  (:method ((object t)) nil))\n\n(defmethod reason ((result for-all-test-failed))\n  (format nil \"Falsifiable with ~S\" (slot-value result 'failure-values)))\n\n(defclass for-all-test-no-tests (test-failure for-all-test-result)\n  ())\n\n(defclass for-all-test-never-run (test-failure for-all-test-result)\n  ())\n\n;;;; *** Generators\n\n;;;; Since this is random testing we need some way of creating random\n;;;; data to feed to our code. Generators are regular functions which\n;;;; create this random data.\n\n;;;; We provide a set of built-in generators.\n\n(defun gen-integer (&key (max (1+ most-positive-fixnum))\n                         (min (1- most-negative-fixnum)))\n  \"Returns a generator which produces random integers greater\nthan or equal to MIN and less than or equal to MAX.\"\n  (lambda ()\n    (+ min (random (1+ (- max min))))))\n\n(defun gen-float (&key bound (type 'short-float))\n  \"Returns a generator which produces floats of type TYPE. BOUND,\nif specified, constrains the results to be in the range (-BOUND,\nBOUND).\"\n  (lambda ()\n    (let* ((most-negative (ecase type\n                            (short-float most-negative-short-float)\n                            (single-float most-negative-single-float)\n                            (double-float most-negative-double-float)\n                            (long-float most-negative-long-float)))\n           (most-positive (ecase type\n                            (short-float most-positive-short-float)\n                            (single-float most-positive-single-float)\n                            (double-float most-positive-double-float)\n                            (long-float most-positive-long-float)))\n           (bound (or bound (max most-positive (- most-negative)))))\n      (coerce\n       (ecase (random 2)\n         (0 ;; generate a positive number\n          (random (min most-positive bound)))\n         (1 ;; generate a negative number\n          (- (random (min (- most-negative) bound)))))\n       type))))\n\n(defun gen-character (&key (code-limit char-code-limit)\n                           (code (gen-integer :min 0 :max (1- code-limit)))\n                           (alphanumericp nil))\n  \"Returns a generator of characters.\n\nCODE must be a generator of random integers. ALPHANUMERICP, if\nnon-NIL, limits the returned chars to those which pass\nalphanumericp.\"\n  (lambda ()\n    (loop\n       for count upfrom 0\n       for char = (code-char (funcall code))\n       until (and char\n                  (or (not alphanumericp)\n                      (alphanumericp char)))\n       when (= 1000 count)\n       do (error \"After 1000 iterations ~S has still not generated ~:[a valid~;an alphanumeric~] character :(.\"\n                 code alphanumericp)\n       finally (return char))))\n\n(defun gen-string (&key (length (gen-integer :min 0 :max 80))\n                        (elements (gen-character))\n                        (element-type 'character))\n  \"Returns a generator which produces random strings. LENGTH must\nbe a generator which produces integers, ELEMENTS must be a\ngenerator which produces characters of type ELEMENT-TYPE.\"\n  (lambda ()\n    (loop\n       with length = (funcall length)\n       with string = (make-string length :element-type element-type)\n       for index below length\n       do (setf (aref string index) (funcall elements))\n       finally (return string))))\n\n(defun gen-list (&key (length (gen-integer :min 0 :max 10))\n                      (elements (gen-integer :min -10 :max 10)))\n  \"Returns a generator which produces random lists. LENGTH must be\nan integer generator and ELEMENTS must be a generator which\nproduces objects.\"\n  (lambda ()\n    (loop\n       repeat (funcall length)\n       collect (funcall elements))))\n\n(defun gen-tree (&key (size 20)\n                      (elements (gen-integer :min -10 :max 10)))\n  \"Returns a generator which produces random trees. SIZE controls\nthe approximate size of the tree, but don't try anything above\n 30, you have been warned. ELEMENTS must be a generator which\nwill produce the elements.\"\n  (labels ((rec (&optional (current-depth 0))\n             (let ((key (random (+ 3 (- size current-depth)))))\n               (cond ((> key 2)\n                      (list (rec (+ current-depth 1))\n                            (rec (+ current-depth 1))))\n                     (t (funcall elements))))))\n    (lambda ()\n      (rec))))\n\n(defun gen-buffer (&key (length (gen-integer :min 0 :max 50))\n                        (element-type '(unsigned-byte 8))\n                        (elements (gen-integer :min 0 :max (1- (expt 2 8)))))\n  (lambda ()\n    (let ((buffer (make-array (funcall length) :element-type element-type)))\n      (map-into buffer elements))))\n\n(defun gen-one-element (&rest elements)\n  (lambda ()\n    (nth (random (length elements)) elements)))\n\n;;;; The trivial always-produce-the-same-thing generator is done using\n;;;; cl:constantly.\n"
  },
  {
    "path": "third-party/fiveam/src/run.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; * Running Tests\n\n;;;; Once the programmer has defined what the tests are these need to\n;;;; be run and the expected effects should be compared with the\n;;;; actual effects. FiveAM provides the function RUN for this\n;;;; purpose, RUN executes a number of tests and collects the results\n;;;; of each individual check into a list which is then\n;;;; returned. There are three types of test results: passed, failed\n;;;; and skipped, these are represented by TEST-RESULT objects.\n\n;;;; Generally running a test will return normally, but there are two\n;;;; exceptional situations which can occur:\n\n;;;; - An exception is signaled while running the test. If the\n;;;;   variable *on-error* is :DEBUG than FiveAM will enter the\n;;;;   debugger, otherwise a test failure (of type\n;;;;   unexpected-test-failure) is returned. When entering the\n;;;;   debugger two restarts are made available, one simply reruns the\n;;;;   current test and another signals a test-failure and continues\n;;;;   with the remaining tests.\n\n;;;; - A circular dependency is detected. An error is signaled and a\n;;;;   restart is made available which signals a test-skipped and\n;;;;   continues with the remaining tests. This restart also sets the\n;;;;   dependency status of the test to nil, so any tests which depend\n;;;;   on this one (even if the dependency is not circular) will be\n;;;;   skipped.\n\n;;;; The functions RUN!, !, !! and !!! are convenient wrappers around\n;;;; RUN and EXPLAIN.\n\n(deftype on-problem-action ()\n  '(member :debug :backtrace nil))\n\n(declaim (type on-problem-action *on-error* *on-failure*))\n\n(defvar *on-error* nil\n  \"The action to perform on error:\n- :DEBUG if we should drop into the debugger\n- :BACKTRACE to print a backtrace\n- NIL to simply continue\")\n\n(defvar *on-failure* nil\n  \"The action to perform on check failure:\n- :DEBUG if we should drop into the debugger\n- :BACKTRACE to print a backtrace\n- NIL to simply continue\")\n\n(defvar *debug-on-error* nil\n  \"T if we should drop into the debugger on error, NIL otherwise.\nOBSOLETE: superseded by *ON-ERROR*\")\n\n(defvar *debug-on-failure* nil\n  \"T if we should drop into the debugger on a failing check, NIL otherwise.\nOBSOLETE: superseded by *ON-FAILURE*\")\n\n(defparameter *print-names* t\n  \"T if we should print test running progress, NIL otherwise.\")\n\n(defparameter *test-dribble-indent* (make-array 0\n                                        :element-type 'character\n                                        :fill-pointer 0\n                                        :adjustable t)\n  \"Used to indent tests and test suites in their parent suite\")\n\n(defun import-testing-symbols (package-designator)\n  (import '(5am::is 5am::is-true 5am::is-false 5am::signals 5am::finishes)\n          package-designator))\n\n(defparameter *run-queue* '()\n  \"List of test waiting to be run.\")\n\n(define-condition circular-dependency (error)\n  ((test-case :initarg :test-case))\n  (:report (lambda (cd stream)\n             (format stream \"A circular dependency wes detected in ~S.\" (slot-value cd 'test-case))))\n  (:documentation \"Condition signaled when a circular dependency\nbetween test-cases has been detected.\"))\n\n(defgeneric run-resolving-dependencies (test)\n  (:documentation \"Given a dependency spec determine if the spec\nis satisfied or not, this will generally involve running other\ntests. If the dependency spec can be satisfied the test is also\nrun.\"))\n\n(defmethod run-resolving-dependencies ((test test-case))\n  \"Return true if this test, and its dependencies, are satisfied,\n  NIL otherwise.\"\n  (case (status test)\n    (:unknown\n     (setf (status test) :resolving)\n     (if (or (not (depends-on test))\n             (eql t (resolve-dependencies (depends-on test))))\n         (progn\n           (run-test-lambda test)\n           (status test))\n         (with-run-state (result-list)\n           (unless (eql :circular (status test))\n             (push (make-instance 'test-skipped\n                                  :test-case test\n                                  :reason \"Dependencies not satisfied\")\n                   result-list)\n             (setf (status test) :depends-not-satisfied)))))\n    (:resolving\n     (restart-case\n         (error 'circular-dependency :test-case test)\n       (skip ()\n         :report (lambda (s)\n                   (format s \"Skip the test ~S and all its dependencies.\" (name test)))\n         (with-run-state (result-list)\n           (push (make-instance 'test-skipped :reason \"Circular dependencies\" :test-case test)\n                 result-list))\n         (setf (status test) :circular))))\n    (t (status test))))\n\n(defgeneric resolve-dependencies (depends-on))\n\n(defmethod resolve-dependencies ((depends-on symbol))\n  \"A test which depends on a symbol is interpreted as `(AND\n  ,DEPENDS-ON).\"\n  (run-resolving-dependencies (get-test depends-on)))\n\n(defmethod resolve-dependencies ((depends-on list))\n  \"Return true if the dependency spec DEPENDS-ON is satisfied,\n  nil otherwise.\"\n  (if (null depends-on)\n      t\n      (flet ((satisfies-depends-p (test)\n               (funcall test (lambda (dep)\n                               (eql t (resolve-dependencies dep)))\n                        (cdr depends-on))))\n        (ecase (car depends-on)\n          (and (satisfies-depends-p #'every))\n          (or  (satisfies-depends-p #'some))\n          (not (satisfies-depends-p #'notany))\n          (:before (every #'(lambda (dep)\n                              (let ((status (status (get-test dep))))\n                                (if (eql :unknown status)\n                                    (run-resolving-dependencies (get-test dep))\n                                    status)))\n                          (cdr depends-on)))))))\n\n(defun results-status (result-list)\n  \"Given a list of test results (generated while running a test)\n  return true if no results are of type TEST-FAILURE.  Returns second\n  and third values, which are the set of failed tests and skipped\n  tests respectively.\"\n  (let ((failed-tests\n          (remove-if-not #'test-failure-p result-list))\n        (skipped-tests\n          (remove-if-not #'test-skipped-p result-list)))\n    (values (null failed-tests)\n            failed-tests\n            skipped-tests)))\n\n(defun return-result-list (test-lambda)\n  \"Run the test function TEST-LAMBDA and return a list of all\n  test results generated, does not modify the special environment\n  variable RESULT-LIST.\"\n  (bind-run-state ((result-list '()))\n    (funcall test-lambda)\n    result-list))\n\n(defgeneric run-test-lambda (test))\n\n(defmethod run-test-lambda ((test test-case))\n  (with-run-state (result-list)\n    (bind-run-state ((current-test test))\n      (labels ((abort-test (e &optional (reason (format nil \"Unexpected Error: ~S~%~A.\" e e)))\n                 (add-result 'unexpected-test-failure\n                             :test-expr nil\n                             :test-case test\n                             :reason reason\n                             :condition e))\n               (run-it ()\n                 (let ((result-list '()))\n                   (declare (special result-list))\n                   (handler-bind ((check-failure (lambda (e)\n                                                   (declare (ignore e))\n                                                   (cond\n                                                     ((eql *on-failure* :debug)\n                                                      nil)\n                                                     (t\n                                                      (when (eql *on-failure* :backtrace)\n                                                        (trivial-backtrace:print-backtrace-to-stream\n                                                         *test-dribble*))\n                                                      (invoke-restart\n                                                       (find-restart 'ignore-failure))))))\n                                  (error (lambda (e)\n                                           (unless (or (eql *on-error* :debug)\n                                                       (typep e 'check-failure))\n                                             (when (eql *on-error* :backtrace)\n                                               (trivial-backtrace:print-backtrace-to-stream\n                                                *test-dribble*))\n                                             (abort-test e)\n                                             (return-from run-it result-list)))))\n                     (restart-case\n                         (handler-case\n                             (let ((*readtable* (copy-readtable))\n                                   (*package* (runtime-package test)))\n                               (when *print-names*\n                                   (format *test-dribble* \"~%~ARunning test ~A \" *test-dribble-indent* (name test)))\n                               (if (collect-profiling-info test)\n                                   ;; Timing info doesn't get collected ATM, we need a portable library\n                                   ;; (setf (profiling-info test) (collect-timing (test-lambda test)))\n                                   (funcall (test-lambda test))\n                                   (funcall (test-lambda test))))\n                           (storage-condition (e)\n                             ;; heap-exhausted/constrol-stack-exhausted\n                             ;; handler-case unwinds the stack (unlike handler-bind)\n                             (abort-test e (format nil \"STORAGE-CONDITION: aborted for safety. ~S~%~A.\" e e))\n                             (return-from run-it result-list)))\n                       (retest ()\n                         :report (lambda (stream)\n                                   (format stream \"~@<Rerun the test ~S~@:>\" test))\n                         (return-from run-it (run-it)))\n                       (ignore ()\n                         :report (lambda (stream)\n                                   (format stream \"~@<Signal an exceptional test failure and abort the test ~S.~@:>\" test))\n                         (abort-test (make-instance 'test-failure :test-case test\n                                                                  :reason \"Failure restart.\"))))\n                     result-list))))\n        (let ((results (run-it)))\n          (setf (status test) (results-status results)\n                result-list (nconc result-list results)))))))\n\n(defgeneric %run (test-spec)\n  (:documentation \"Internal method for running a test. Does not\n  update the status of the tests nor the special variables !,\n  !!, !!!\"))\n\n(defmethod %run ((test test-case))\n  (run-resolving-dependencies test))\n\n(defmethod %run ((tests list))\n  (mapc #'%run tests))\n\n(defmethod %run ((suite test-suite))\n  (when *print-names*\n    (format *test-dribble* \"~%~ARunning test suite ~A\" *test-dribble-indent* (name suite)))\n  (let ((suite-results '()))\n    (flet ((run-tests ()\n             (loop\n                for test being the hash-values of (tests suite)\n                do (%run test))))\n      (vector-push-extend #\\space *test-dribble-indent*)\n      (unwind-protect\n           (bind-run-state ((result-list '()))\n             (unwind-protect\n                  (if (collect-profiling-info suite)\n                      ;; Timing info doesn't get collected ATM, we need a portable library\n                      ;; (setf (profiling-info suite) (collect-timing #'run-tests))\n                      (run-tests)\n                      (run-tests)))\n             (setf suite-results result-list\n                   (status suite) (every #'test-passed-p suite-results)))\n        (vector-pop *test-dribble-indent*)\n        (with-run-state (result-list)\n          (setf result-list (nconc result-list suite-results)))))))\n\n(defmethod %run ((test-name symbol))\n  (when-let (test (get-test test-name))\n    (%run test)))\n\n(defvar *initial-!* (lambda () (format t \"Haven't run that many tests yet.~%\")))\n\n(defvar *!* *initial-!*)\n(defvar *!!* *initial-!*)\n(defvar *!!!* *initial-!*)\n\n;;;; ** Public entry points\n\n(defun run! (&optional (test-spec *suite*)\n             &key ((:print-names *print-names*) *print-names*))\n  \"Equivalent to (explain! (run TEST-SPEC)).\"\n  (explain! (run test-spec)))\n\n(defun explain! (result-list)\n  \"Explain the results of RESULT-LIST using a\ndetailed-text-explainer with output going to *test-dribble*.\nReturn a boolean indicating whether no tests failed.\"\n  (explain (make-instance 'detailed-text-explainer) result-list *test-dribble*)\n  (results-status result-list))\n\n(defun debug! (&optional (test-spec *suite*))\n  \"Calls (run! test-spec) but enters the debugger if any kind of error happens.\"\n  (let ((*on-error* :debug)\n        (*on-failure* :debug))\n    (run! test-spec)))\n\n(defun run (test-spec &key ((:print-names *print-names*) *print-names*))\n  \"Run the test specified by TEST-SPEC.\n\nTEST-SPEC can be either a symbol naming a test or test suite, or\na testable-object object. This function changes the operations\nperformed by the !, !! and !!! functions.\"\n  (psetf *!* (lambda ()\n               (loop :for test :being :the :hash-keys :of *test*\n                     :do (setf (status (get-test test)) :unknown))\n               (bind-run-state ((result-list '()))\n                 (with-simple-restart (explain \"Ignore the rest of the tests and explain current results\")\n                   (%run test-spec))\n                 result-list))\n         *!!* *!*\n         *!!!* *!!*)\n  (let ((*on-error*\n          (or *on-error* (cond\n                           (*debug-on-error*\n                            (format *test-dribble* \"*DEBUG-ON-ERROR* is obsolete. Use *ON-ERROR*.\")\n                            :debug)\n                           (t nil))))\n        (*on-failure*\n          (or *on-failure* (cond\n                           (*debug-on-failure*\n                            (format *test-dribble* \"*DEBUG-ON-FAILURE* is obsolete. Use *ON-FAILURE*.\")\n                            :debug)\n                           (t nil)))))\n    (funcall *!*)))\n\n(defun ! ()\n  \"Rerun the most recently run test and explain the results.\"\n  (explain! (funcall *!*)))\n\n(defun !! ()\n  \"Rerun the second most recently run test and explain the results.\"\n  (explain! (funcall *!!*)))\n\n(defun !!! ()\n  \"Rerun the third most recently run test and explain the results.\"\n  (explain! (funcall *!!!*)))\n\n(defun run-all-tests (&key (summary :end))\n  \"Runs all defined test suites, T if all tests passed and NIL otherwise.\nSUMMARY can be :END to print a summary at the end, :SUITE to print it\nafter each suite or NIL to skip explanations.\"\n  (check-type summary (member nil :suite :end))\n  (loop :for suite :in (cons 'nil (sort (copy-list *toplevel-suites*) #'string<=))\n        :for results := (if (suite-emptyp suite) nil (run suite))\n        :when (consp results)\n          :collect results :into all-results\n        :do (cond\n              ((not (eql summary :suite))\n               nil)\n              (results\n               (explain! results))\n              (suite\n               (format *test-dribble* \"Suite ~A is empty~%\" suite)))\n        :finally (progn\n                   (when (eql summary :end)\n                     (explain! (alexandria:flatten all-results)))\n                   (return (every #'results-status all-results)))))\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "third-party/fiveam/src/style.css",
    "content": "body {\n  background-color: #FFFFFF;\n  color: #000000;\n  padding: 0px; margin: 0px;\n}\n\n.qbook { width: 600px; background-color: #FFFFFF; margin: 0px; \n         border-left: 3em solid #660000; padding: 3px; }\n\nh1 { text-align: center; margin: 0px;\n     color: #333333; \n     border-bottom: 0.3em solid #660000; \n}\n\np { padding-left: 1em; }\n\nh2 { border-bottom: 0.2em solid #000000; font-family: verdana; }\n\nh3 { border-bottom: 0.1em solid #000000; }\n\npre.code {\n\tbackground-color: #eeeeee;\n\tborder: solid 1px #d0d0d0;\n        overflow: auto;\n}\n\npre.code * .paren { color: #666666; } \n\npre.code a:active  { color: #000000; }\npre.code a:link    { color: #000000; }\npre.code a:visited { color: #000000; }\n\npre.code .first-line { font-weight: bold; }\n\ndiv.contents { font-family: verdana; }\n\ndiv.contents a:active  { color: #000000; }\ndiv.contents a:link    { color: #000000; }\ndiv.contents a:visited { color: #000000; }\n\ndiv.contents div.contents-heading-1 { padding-left: 0.5em; font-weight: bold; }\ndiv.contents div.contents-heading-1 a:active  { color: #660000; }\ndiv.contents div.contents-heading-1 a:link    { color: #660000; }\ndiv.contents div.contents-heading-1 a:visited { color: #660000; }\n\ndiv.contents div.contents-heading-2 { padding-left: 1.0em; }\ndiv.contents div.contents-heading-2 a:active  { color: #660000; }\ndiv.contents div.contents-heading-2 a:link    { color: #660000; }\ndiv.contents div.contents-heading-2 a:visited { color: #660000; }\n\ndiv.contents div.contents-heading-3 { padding-left: 1.5em; }\ndiv.contents div.contents-heading-3 a:active  { color: #660000; }\ndiv.contents div.contents-heading-3 a:link    { color: #660000; }\ndiv.contents div.contents-heading-3 a:visited { color: #660000; }\n\ndiv.contents div.contents-heading-4 { padding-left: 2em; }\ndiv.contents div.contents-heading-4 a:active  { color: #660000; }\ndiv.contents div.contents-heading-4 a:link    { color: #660000; }\ndiv.contents div.contents-heading-4 a:visited { color: #660000; }\n\ndiv.contents div.contents-heading-5 { padding-left: 2.5em; }\ndiv.contents div.contents-heading-5 a:active  { color: #660000; }\ndiv.contents div.contents-heading-5 a:link    { color: #660000; }\ndiv.contents div.contents-heading-5 a:visited { color: #660000; }\n"
  },
  {
    "path": "third-party/fiveam/src/suite.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; * Test Suites\n\n;;;; Test suites allow us to collect multiple tests into a single\n;;;; object and run them all using asingle name. Test suites do not\n;;;; affect the way test are run nor the way the results are handled,\n;;;; they are simply a test organizing group.\n\n;;;; Test suites can contain both tests and other test suites. Running\n;;;; a test suite causes all of its tests and test suites to be\n;;;; run. Suites do not affect test dependencies, running a test suite\n;;;; can cause tests which are not in the suite to be run.\n\n;;;; ** Current Suite\n\n(defvar *suite* nil\n  \"The current test suite object\")\n;; Only when compiling under ASDF, not under Bazel.\n#-bazel\n(net.didierverna.asdf-flv:set-file-local-variable *suite*)\n\n;;;; ** Creating Suits\n\n;; Suites that have no parent suites.\n(defvar *toplevel-suites* nil)\n\n(defgeneric suite-emptyp (suite)\n  (:method ((suite symbol))\n    (suite-emptyp (get-test suite)))\n  (:method ((suite test-suite))\n    (= 0 (hash-table-count (tests suite)))))\n\n(defmacro def-suite (name &key description in)\n  \"Define a new test-suite named NAME.\n\nIN (a symbol), if provided, causes this suite te be nested in the\nsuite named by IN. NB: This macro is built on top of make-suite,\nas such it, like make-suite, will overrwrite any existing suite\nnamed NAME.\"\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (make-suite ',name\n                 ,@(when description `(:description ,description))\n                 ,@(when in `(:in ',in)))\n     ',name))\n\n(defmacro def-suite* (name &rest def-suite-args)\n  `(progn\n     (def-suite ,name ,@def-suite-args)\n     (in-suite ,name)))\n\n(defun make-suite (name &key description ((:in parent-suite)))\n  \"Create a new test suite object.\n\nOverrides any existing suite named NAME.\"\n  (let ((suite (make-instance 'test-suite :name name)))\n    (when description\n      (setf (description suite) description))\n    (when (and name\n               (null parent-suite))\n      (pushnew name *toplevel-suites*))\n    (loop for i in (ensure-list parent-suite)\n          for in-suite = (get-test i)\n          do (progn\n               (when (null in-suite)\n                 (cerror \"Create a new suite named ~A.\" \"Unknown suite ~A.\" i)\n                 (setf (get-test in-suite) (make-suite i)\n                       in-suite (get-test in-suite)))\n               (setf (gethash name (tests in-suite)) suite)))\n    (setf (get-test name) suite)\n    suite))\n\n(eval-when (:load-toplevel :execute)\n  (setf *suite*\n        (setf (get-test 'nil)\n              (make-suite 'nil :description \"Global Suite\"))))\n\n(defun list-all-suites ()\n  \"Returns an unordered LIST of all suites.\"\n  (hash-table-values *suite*))\n\n;;;; ** Managing the Current Suite\n\n(defmacro in-suite (suite-name)\n  \"Set the *suite* special variable so that all tests defined\nafter the execution of this form are, unless specified otherwise,\nin the test-suite named SUITE-NAME.\n\nSee also: DEF-SUITE *SUITE*\"\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (%in-suite ,suite-name)))\n\n(defmacro in-suite* (suite-name &key in)\n  \"Just like in-suite, but silently creates missing suites.\"\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (%in-suite ,suite-name :in ,in :fail-on-error nil)))\n\n(defmacro %in-suite (suite-name &key (fail-on-error t) in)\n  (with-gensyms (suite)\n    `(progn\n       (if-let (,suite (get-test ',suite-name))\n         (setf *suite* ,suite)\n         (progn\n           (when ,fail-on-error\n             (cerror \"Create a new suite named ~A.\"\n                     \"Unknown suite ~A.\" ',suite-name))\n           (setf (get-test ',suite-name) (make-suite ',suite-name :in ',in)\n                 *suite* (get-test ',suite-name))))\n       ',suite-name)))\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\n"
  },
  {
    "path": "third-party/fiveam/src/test.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n;;;; * Tests\n\n;;;; While executing checks and collecting the results is the core job\n;;;; of a testing framework it is also important to be able to\n;;;; organize checks into groups, fiveam provides two mechanisms for\n;;;; organizing checks: tests and test suites. A test is a named\n;;;; collection of checks which can be run and a test suite is a named\n;;;; collection of tests and test suites.\n\n(declaim (special *suite*))\n\n(defvar *test*\n  (make-hash-table :test 'eql)\n  \"Lookup table mapping test (and test suite)\n  names to objects.\")\n\n(defun get-test (key &optional default)\n  (gethash key *test* default))\n\n(defun (setf get-test) (value key)\n  (setf (gethash key *test*) value))\n\n(defun rem-test (key)\n  (remhash key *test*))\n\n(defun test-names ()\n  (hash-table-keys *test*))\n\n(defmacro test (name &body body)\n  \"Create a test named NAME. If NAME is a list it must be of the\nform:\n\n  (name &key depends-on suite fixture compile-at profile)\n\nNAME is the symbol which names the test.\n\nDEPENDS-ON is a list of the form:\n\n (AND . test-names) - This test is run only if all of the tests\n in TEST-NAMES have passed, otherwise a single test-skipped\n result is generated.\n\n (OR . test-names) - If any of TEST-NAMES has passed this test is\n run, otherwise a test-skipped result is generated.\n\n (NOT test-name) - This is test is run only if TEST-NAME failed.\n\nAND, OR and NOT can be combined to produce complex dependencies.\n\nIf DEPENDS-ON is a symbol it is interpreted as `(AND\n,depends-on), this is accomadate the common case of one test\ndepending on another.\n\nFIXTURE specifies a fixture to wrap the body in.\n\nIf PROFILE is T profiling information will be collected as well.\"\n  (destructuring-bind (name &rest args)\n      (ensure-list name)\n    `(def-test ,name (,@args) ,@body)))\n\n#+lispworks\n(dspec:define-dspec-class def-test nil\n  \"A fiveam test\")\n\n#+lispworks\n(dspec:define-form-parser def-test (name &rest rest)\n  `(def-test ,name))\n\n#+lispworks\n(dspec:define-form-parser\n  (def-test (:parser def-test-form-parser)))\n\n#+lispworks\n(dspec:define-form-parser\n  (test (:parser def-test-form-parser)))\n\n(defvar *default-test-compilation-time* :definition-time)\n\n(defmacro def-test (name (&key depends-on (suite '*suite* suite-p) fixture\n                            (compile-at *default-test-compilation-time*) profile)\n                    &body body)\n  \"Create a test named NAME.\n\nNAME is the symbol which names the test.\n\nDEPENDS-ON is a list of the form:\n\n (AND . test-names) - This test is run only if all of the tests\n in TEST-NAMES have passed, otherwise a single test-skipped\n result is generated.\n\n (OR . test-names) - If any of TEST-NAMES has passed this test is\n run, otherwise a test-skipped result is generated.\n\n (NOT test-name) - This is test is run only if TEST-NAME failed.\n\nAND, OR and NOT can be combined to produce complex dependencies.\n\nIf DEPENDS-ON is a symbol it is interpreted as `(AND\n,depends-on), this is accomadate the common case of one test\ndepending on another.\n\nFIXTURE specifies a fixture to wrap the body in.\n\nIf PROFILE is T profiling information will be collected as well.\"\n  (check-type compile-at (member :run-time :definition-time))\n  (multiple-value-bind (forms decls docstring)\n      (parse-body body :documentation t :whole name)\n    (let* ((description (or docstring \"\"))\n           (body-forms (append decls forms))\n           (suite-form (if suite-p\n                           `(get-test ',suite)\n                           (or suite '*suite*)))\n           (effective-body (if fixture\n                               (destructuring-bind (name &rest args)\n                                   (ensure-list fixture)\n                                 `((with-fixture ,name ,args ,@body-forms)))\n                               body-forms)))\n      `(progn\n         #+lispworks\n         (dspec:record-definition `(def-test ,',name) (dspec:location))\n\n         (register-test ',name ,description ',effective-body ,suite-form ',depends-on ,compile-at ,profile)\n         (when *run-test-when-defined*\n           (run! ',name))\n         ',name))))\n\n(defun register-test (name description body suite depends-on compile-at profile)\n  (let ((lambda-name\n          (format-symbol t \"%~A-~A\" '#:test name))\n        (inner-lambda-name\n          (format-symbol t \"%~A-~A\" '#:inner-test name)))\n    (setf (get-test name)\n          (make-instance 'test-case\n                         :name name\n                         :runtime-package (find-package (package-name *package*))\n                         :test-lambda\n                         (eval\n                          `(named-lambda ,lambda-name ()\n                             #+lispworks\n                             (declare (hcl:lambda-name (def-test ,name)))\n                             ,@(ecase compile-at\n                                 (:run-time `((funcall\n                                               (let ((*package* (find-package ',(package-name *package*))))\n                                                 (compile ',inner-lambda-name\n                                                          '(lambda () ,@body))))))\n                                 (:definition-time body))))\n                         :description description\n                         :depends-on depends-on\n                         :collect-profiling-info profile\n                         :test-suite suite))\n    (setf (gethash name (tests suite)) name)))\n\n(defvar *run-test-when-defined* nil\n  \"When non-NIL tests are run as soon as they are defined.\")\n\n;; Copyright (c) 2002-2003, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "third-party/fiveam/src/utils.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n(defmacro dolist* ((iterator list &optional return-value) &body body)\n  \"Like DOLIST but destructuring-binds the elements of LIST.\n\nIf ITERATOR is a symbol then dolist* is just like dolist EXCEPT\nthat it creates a fresh binding.\"\n  (if (listp iterator)\n      (let ((i (gensym \"DOLIST*-I-\")))\n        `(dolist (,i ,list ,return-value)\n           (destructuring-bind ,iterator ,i\n             ,@body)))\n      `(dolist (,iterator ,list ,return-value)\n         (let ((,iterator ,iterator))\n           ,@body))))\n\n(defun make-collector (&optional initial-value)\n  \"Create a collector function.\n\nA Collector function will collect, into a list, all the values\npassed to it in the order in which they were passed. If the\ncallector function is called without arguments it returns the\ncurrent list of values.\"\n  (let ((value initial-value)\n        (cdr (last initial-value)))\n    (lambda (&rest items)\n      (if items\n          (progn\n            (if value\n                (if cdr\n                    (setf (cdr cdr) items\n                          cdr (last items))\n                    (setf cdr (last items)))\n                (setf value items\n                      cdr (last items)))\n            items)\n          value))))\n\n(defun partitionx (list &rest lambdas)\n  (let ((collectors (mapcar (lambda (l)\n                              (cons (if (and (symbolp l)\n                                             (member l (list :otherwise t)\n                                                     :test #'string=))\n                                        (constantly t)\n                                        l)\n                                    (make-collector)))\n                            lambdas)))\n    (dolist (item list)\n      (block item\n        (dolist* ((test-func . collector-func) collectors)\n          (when (funcall test-func item)\n            (funcall collector-func item)\n            (return-from item)))))\n    (mapcar #'funcall (mapcar #'cdr collectors))))\n\n;;;; ** Anaphoric conditionals\n\n(defmacro if-bind (var test &body then/else)\n  \"Anaphoric IF control structure.\n\nVAR (a symbol) will be bound to the primary value of TEST. If\nTEST returns a true value then THEN will be executed, otherwise\nELSE will be executed.\"\n  (assert (first then/else)\n          (then/else)\n          \"IF-BIND missing THEN clause.\")\n  (destructuring-bind (then &optional else)\n      then/else\n    `(let ((,var ,test))\n       (if ,var ,then ,else))))\n\n(defmacro aif (test then &optional else)\n  \"Just like IF-BIND but the var is always IT.\"\n  `(if-bind it ,test ,then ,else))\n\n;;;; ** Simple list matching based on code from Paul Graham's On Lisp.\n\n(defmacro acond2 (&rest clauses)\n  (if (null clauses)\n      nil\n      (with-gensyms (val foundp)\n        (destructuring-bind ((test &rest progn) &rest others)\n            clauses\n          `(multiple-value-bind (,val ,foundp)\n               ,test\n             (if (or ,val ,foundp)\n                 (let ((it ,val))\n                   (declare (ignorable it))\n                   ,@progn)\n                 (acond2 ,@others)))))))\n\n(defun varsymp (x)\n  (and (symbolp x)\n       (let ((name (symbol-name x)))\n         (and (>= (length name) 2)\n              (char= (char name 0) #\\?)))))\n\n(defun binding (x binds)\n  (labels ((recbind (x binds)\n             (aif (assoc x binds)\n                  (or (recbind (cdr it) binds)\n                      it))))\n    (let ((b (recbind x binds)))\n      (values (cdr b) b))))\n\n(defun list-match (x y &optional binds)\n  (acond2\n    ((or (eql x y) (eql x '_) (eql y '_))\n     (values binds t))\n    ((binding x binds) (list-match it y binds))\n    ((binding y binds) (list-match x it binds))\n    ((varsymp x) (values (cons (cons x y) binds) t))\n    ((varsymp y) (values (cons (cons y x) binds) t))\n    ((and (consp x) (consp y) (list-match (car x) (car y) binds))\n     (list-match (cdr x) (cdr y) it))\n    (t (values nil nil))))\n\n(defun vars (match-spec)\n  (let ((vars nil))\n    (labels ((find-vars (spec)\n               (cond\n                 ((null spec) nil)\n                 ((varsymp spec) (push spec vars))\n                 ((consp spec)\n                  (find-vars (car spec))\n                  (find-vars (cdr spec))))))\n      (find-vars match-spec))\n    (delete-duplicates vars)))\n\n(defmacro list-match-case (target &body clauses)\n  (if clauses\n      (destructuring-bind ((test &rest progn) &rest others)\n          clauses\n        (with-gensyms (tgt binds success)\n          `(let ((,tgt ,target))\n             (multiple-value-bind (,binds ,success)\n                 (list-match ,tgt ',test)\n               (declare (ignorable ,binds))\n               (if ,success\n                   (let ,(mapcar (lambda (var)\n                                   `(,var (cdr (assoc ',var ,binds))))\n                                 (vars test))\n                     (declare (ignorable ,@(vars test)))\n                     ,@progn)\n                   (list-match-case ,tgt ,@others))))))\n      nil))\n\n;;;; * def-special-environment\n\n(defun check-required (name vars required)\n  (dolist (var required)\n    (assert (member var vars)\n            (var)\n            \"Unrecognized symbol ~S in ~S.\" var name)))\n\n(defmacro def-special-environment (name (&key accessor binder binder*)\n                                  &rest vars)\n  \"Define two macros for dealing with groups or related special variables.\n\nACCESSOR is defined as a macro: (defmacro ACCESSOR (VARS &rest\nBODY)).  Each element of VARS will be bound to the\ncurrent (dynamic) value of the special variable.\n\nBINDER is defined as a macro for introducing (and binding new)\nspecial variables. It is basically a readable LET form with the\nprorpe declarations appended to the body. The first argument to\nBINDER must be a form suitable as the first argument to LET.\n\nACCESSOR defaults to a new symbol in the same package as NAME\nwhich is the concatenation of \\\"WITH-\\\" NAME. BINDER is built as\n\\\"BIND-\\\" and BINDER* is BINDER \\\"*\\\".\"\n  (unless accessor\n    (setf accessor (format-symbol (symbol-package name) \"~A-~A\" '#:with name)))\n  (unless binder\n    (setf binder   (format-symbol (symbol-package name) \"~A-~A\" '#:bind name)))\n  (unless binder*\n    (setf binder*  (format-symbol (symbol-package binder) \"~A~A\" binder '#:*)))\n  `(eval-when (:compile-toplevel :load-toplevel :execute)\n     (flet ()\n       (defmacro ,binder (requested-vars &body body)\n         (check-required ',name ',vars (mapcar #'car requested-vars))\n         `(let ,requested-vars\n            (declare (special ,@(mapcar #'car requested-vars)))\n            ,@body))\n       (defmacro ,binder* (requested-vars &body body)\n         (check-required ',name ',vars (mapcar #'car requested-vars))\n         `(let* ,requested-vars\n            (declare (special ,@(mapcar #'car requested-vars)))\n            ,@body))\n       (defmacro ,accessor (requested-vars &body body)\n         (check-required ',name ',vars requested-vars)\n         `(locally (declare (special ,@requested-vars))\n            ,@body))\n       ',name)))\n\n;; Copyright (c) 2002-2006, Edward Marco Baringer\n;; All rights reserved.\n;;\n;; Redistribution and use in source and binary forms, with or without\n;; modification, are permitted provided that the following conditions are\n;; met:\n;;\n;;  - Redistributions of source code must retain the above copyright\n;;    notice, this list of conditions and the following disclaimer.\n;;\n;;  - Redistributions in binary form must reproduce the above copyright\n;;    notice, this list of conditions and the following disclaimer in the\n;;    documentation and/or other materials provided with the distribution.\n;;\n;;  - Neither the name of Edward Marco Baringer, nor BESE, nor the names\n;;    of its contributors may be used to endorse or promote products\n;;    derived from this software without specific prior written permission.\n;;\n;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n;; \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n;; A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\n"
  },
  {
    "path": "third-party/fiveam/t/example.lisp",
    "content": ";;;; -*- Mode: Lisp; indent-tabs-mode: nil -*-\n\n;;;; * FiveAM Example (poor man's tutorial)\n\n(asdf:oos 'asdf:load-op :fiveam)\n\n(defpackage :it.bese.fiveam.example\n  (:use :common-lisp\n\t:it.bese.fiveam))\n\n(in-package :it.bese.fiveam.example)\n\n;;;; First we need some functions to test.\n\n(defun add-2 (n)\n  (+ n 2))\n\n(defun add-4 (n) \n  (+ n 4))\n\n;;;; Now we need to create a test which makes sure that add-2 and add-4\n;;;; work as specified.\n\n;;;; we create a test named ADD-2 and supply a short description.\n(test add-2\n \"Test the ADD-2 function\" ;; a short description\n ;; the checks\n (is (= 2 (add-2 0)))\n (is (= 0 (add-2 -2))))\n\n;;;; we can already run add-2. This will return the list of test\n;;;; results, it should be a list of two test-passed objects.\n\n(run 'add-2) \n\n;;;; since we'd like to have some kind of readbale output we'll explain\n;;;; the results\n\n(explain! (run 'add-2))\n\n;;;; or we could do both at once:\n\n(run! 'add-2)\n\n;;;; So now we've defined and run a single test. Since we plan on\n;;;; having more than one test and we'd like to run them together let's\n;;;; create a simple test suite.\n\n(def-suite example-suite :description \"The example test suite.\")\n\n;;;; we could explictly specify that every test we create is in the the\n;;;; example-suite suite, but it's easier to just change the default\n;;;; suite:\n\n(in-suite example-suite)\n\n;;;; now we'll create a new test for the add-4 function.\n\n(test add-4\n  (is (= 0 (add-4 -4))))\n\n;;;; now let's run the test\n\n(run! 'add-4)\n\n;;;; we can get the same effect by running the suite:\n\n(run! 'example-suite)\n\n;;;; since we'd like both add-2 and add-4 to be in the same suite, let's\n;;;; redefine add-2 to be in this suite:\n\n(test add-2 \"Test the ADD-2 function\"\n (is (= 2 (add-2 0)))\n (is (= 0 (add-2 -2))))\n\n;;;; now we can run the suite and we'll see that both add-2 and add-4\n;;;; have been run (we know this since we no get 4 checks as opposed to\n;;;; 2 as before.\n\n(run! 'example-suite)\n\n;;;; Just for fun let's see what happens when a test fails. Again we'll\n;;;; redefine add-2, but add in a third, failing, check:\n\n(test add-2 \"Test the ADD-2 function\"\n (is (= 2 (add-2 0)))\n (is (= 0 (add-2 -2)))\n (is (= 0 (add-2 0))))\n\n;;;; Finally let's try out the specification based testing.\n\n(defun dummy-add (a b)\n  (+ a b))\n\n(defun dummy-strcat (a b)\n  (concatenate 'string a b))\n\n(test dummy-add\n  (for-all ((a (gen-integer))\n            (b (gen-integer)))\n    ;; assuming we have an \"oracle\" to compare our function results to\n    ;; we can use it:\n    (is (= (+ a b) (dummy-add a b)))\n    ;; if we don't have an oracle (as in most cases) we just ensure\n    ;; that certain properties hold:\n    (is (= (dummy-add a b)\n           (dummy-add b a)))\n    (is (= a (dummy-add a 0)))\n    (is (= 0 (dummy-add a (- a))))\n    (is (< a (dummy-add a 1)))\n    (is (= (* 2 a) (dummy-add a a)))))\n\n(test dummy-strcat\n  (for-all ((result (gen-string))\n            (split-point (gen-integer :min 0 :max 10000)\n                         (< split-point (length result))))\n    (is (string= result (dummy-strcat (subseq result 0 split-point)\n                                      (subseq result split-point))))))\n\n(test random-failure\n  (for-all ((result (gen-integer :min 0 :max 1)))\n    (is (plusp result))\n    (is (= result 0))))\n\n(run! 'example-suite)\n"
  },
  {
    "path": "third-party/fiveam/t/tests.lisp",
    "content": ";;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Package: FIVEAM; Base: 10; -*-\n\n(in-package :it.bese.fiveam)\n\n(in-suite* :it.bese.fiveam)\n\n(def-suite test-suite :description \"Suite for tests which should fail.\")\n\n(defmacro with-test-results ((results test-name) &body body)\n  `(let ((,results (with-*test-dribble* nil (run ',test-name))))\n     ,@body))\n\n(def-fixture null-fixture ()\n  `(progn ,@(&body)))\n\n;;;; Test the checks\n\n(def-test is1 (:suite test-suite)\n  (is (plusp 1))\n  (is (< 0 1))\n  (is (not (plusp -1)))\n  (is (not (< 1 0)))\n  (is-true t)\n  (is-false nil))\n\n(def-test is2 (:suite test-suite :fixture null-fixture)\n  (is (plusp 0))\n  (is (< 0 -1))\n  (is (not (plusp 1)))\n  (is (not (< 0 1)))\n  (is-true nil)\n  (is-false t))\n\n(def-test is (:profile t)\n  (with-test-results (results is1)\n    (is (= 6 (length results)))\n    (is (every #'test-passed-p results)))\n  (with-test-results (results is2)\n    (is (= 6 (length results)))\n    (is (every #'test-failure-p results))))\n\n(def-test signals/finishes ()\n  (signals error\n    (error \"an error\"))\n  (signals (error \"The form ~S is expected to signal an ~S\"\n                  '(error \"an error\") 'error)\n    (error \"an error\"))\n  (finishes\n   (signals error\n    (error \"an error\"))))\n\n(def-test pass ()\n  (pass))\n\n(def-test fail1 (:suite test-suite)\n  (fail \"This is supposed to fail\"))\n\n(def-test fail ()\n  (with-test-results (results fail1)\n    (is (= 1 (length results)))\n    (is (test-failure-p (first results)))))\n\n;;;; non top level checks\n\n(def-test foo-bar ()\n  (let ((state 0))\n    (is (= 0 state))\n    (is (= 1 (incf state)))))\n\n;;;; Test dependencies\n\n(def-test ok (:suite test-suite)\n  (pass))\n\n(def-test not-ok (:suite test-suite)\n  (fail \"This is supposed to fail.\"))\n\n(def-test and1 (:depends-on (and ok not-ok) :suite test-suite)\n  (fail))\n\n(def-test and2 (:depends-on (and ok) :suite test-suite)\n  (pass))\n\n(def-test dep-and ()\n  (with-test-results (results and1)\n    (is (= 3 (length results)))\n    ;; we should have one skippedw one failed and one passed\n    (is (some #'test-passed-p results))\n    (is (some #'test-skipped-p results))\n    (is (some #'test-failure-p results)))\n  (with-test-results (results and2)\n    (is (= 2 (length results)))\n    (is (every #'test-passed-p results))))\n\n(def-test or1 (:depends-on (or ok not-ok) :suite test-suite)\n  (pass))\n\n(def-test or2 (:depends-on (or not-ok ok) :suite test-suite)\n  (pass))\n\n(def-test dep-or ()\n  (with-test-results (results or1)\n    (is (= 2 (length results)))\n    (is (every #'test-passed-p results)))\n  (with-test-results (results or2)\n    (is (= 3 (length results)))\n    (is (= 2 (length (remove-if-not #'test-passed-p results))))))\n\n(def-test not1 (:depends-on (not not-ok) :suite test-suite)\n  (pass))\n\n(def-test not2 (:depends-on (not ok) :suite test-suite)\n  (fail))\n\n(def-test not ()\n  (with-test-results (results not1)\n    (is (= 2 (length results)))\n    (is (some #'test-passed-p results))\n    (is (some #'test-failure-p results)))\n  (with-test-results (results not2)\n    (is (= 2 (length results)))\n    (is (some #'test-passed-p results))\n    (is (some #'test-skipped-p results))))\n\n(def-test nested-logic (:depends-on (and ok (not not-ok) (not not-ok))\n                        :suite test-suite)\n  (pass))\n\n(def-test dep-nested ()\n  (with-test-results (results nested-logic)\n    (is (= 3 (length results)))\n    (is (= 2 (length (remove-if-not #'test-passed-p results))))\n    (is (= 1 (length (remove-if-not #'test-failure-p results))))))\n\n(def-test circular-0 (:depends-on (and circular-1 circular-2 or1)\n                      :suite test-suite)\n  (fail \"we depend on a circular dependency, we should not be tested.\"))\n\n(def-test circular-1 (:depends-on (and circular-2)\n                      :suite test-suite)\n  (fail \"we have a circular depednency, we should not be tested.\"))\n\n(def-test circular-2 (:depends-on (and circular-1)\n                      :suite test-suite)\n  (fail \"we have a circular depednency, we should not be tested.\"))\n\n(def-test circular ()\n  (signals circular-dependency\n    (run 'circular-0))\n  (signals circular-dependency\n    (run 'circular-1))\n  (signals circular-dependency\n    (run 'circular-2)))\n\n\n(defun stack-exhaust ()\n  (declare (optimize (debug 3) (speed 0) (space 0) (safety 3)))\n  (cons 42 (stack-exhaust)))\n\n;; Disable until we determine on which implementations it's actually safe\n;; to exhaust the stack.\n#|\n(def-test stack-exhaust (:suite test-suite)\n  (stack-exhaust))\n\n(def-test test-stack-exhaust ()\n  (with-test-results (results stack-exhaust)\n    (is (= 1 (length results)))\n    (is (test-failure-p (first results)))))\n|#\n\n(def-suite before-test-suite :description \"Suite for before test\")\n\n(def-test before-0 (:suite before-test-suite)\n  (fail))\n\n(def-test before-1 (:depends-on (:before before-0)\n                    :suite before-test-suite)\n  (pass))\n\n(def-suite before-test-suite-2 :description \"Suite for before test\")\n\n(def-test before-2 (:depends-on (:before before-3)\n                    :suite before-test-suite-2)\n  (pass))\n\n(def-test before-3 (:suite before-test-suite-2)\n  (pass))\n\n(def-test before ()\n  (with-test-results (results before-test-suite)\n    (is (some #'test-skipped-p results)))\n\n  (with-test-results (results before-test-suite-2)\n    (is (every #'test-passed-p results))))\n\n\n;;;; dependencies with symbol\n(def-test dep-with-symbol-first (:suite test-suite)\n  (pass))\n\n(def-test dep-with-symbol-dependencies-not-met (:depends-on (not dep-with-symbol-first)\n                                                :suite test-suite)\n  (fail \"Error in the test of the test, this should not ever happen\"))\n\n(def-test dep-with-symbol-depends-on-ok (:depends-on dep-with-symbol-first :suite test-suite)\n  (pass))\n\n(def-test dep-with-symbol-depends-on-failed-dependency (:depends-on dep-with-symbol-dependencies-not-met\n                                                        :suite test-suite)\n  (fail \"No, I should not be tested because I depend on a test that in its turn has a failed dependecy.\"))\n\n(def-test dependencies-with-symbol ()\n  (with-test-results (results dep-with-symbol-first)\n    (is (some #'test-passed-p results)))\n\n  (with-test-results (results dep-with-symbol-depends-on-ok)\n    (is (some #'test-passed-p results)))\n\n  (with-test-results (results dep-with-symbol-dependencies-not-met)\n    (is (some #'test-skipped-p results)))\n\n  ;; No failure here, because it means the test was run.\n  (with-test-results (results dep-with-symbol-depends-on-failed-dependency)\n    (is (not (some #'test-failure-p results)))))\n\n\n;;;; test for-all\n\n(def-test gen-integer ()\n  (for-all ((a (gen-integer)))\n    (is (integerp a))))\n\n(def-test for-all-guarded ()\n  (for-all ((less (gen-integer))\n            (more (gen-integer) (< less more)))\n    (is (< less more))))\n\n(def-test gen-float ()\n  (macrolet ((test-gen-float (type)\n               `(for-all ((unbounded (gen-float :type ',type))\n                          (bounded   (gen-float :type ',type :bound 42)))\n                  (is (typep unbounded ',type))\n                  (is (typep bounded ',type))\n                  (is (<= (abs bounded) 42)))))\n    (test-gen-float single-float)\n    (test-gen-float short-float)\n    (test-gen-float double-float)\n    (test-gen-float long-float)))\n\n(def-test gen-character ()\n  (for-all ((c (gen-character)))\n    (is (characterp c)))\n  (for-all ((c (gen-character :code (gen-integer :min 32 :max 40))))\n    (is (characterp c))\n    (member c (list #\\Space #\\! #\\\" #\\# #\\$ #\\% #\\& #\\' #\\())))\n\n(def-test gen-string ()\n  (for-all ((s (gen-string)))\n    (is (stringp s)))\n  (for-all ((s (gen-string :length (gen-integer :min 0 :max 2))))\n    (is (<= (length s) 2)))\n  (for-all ((s (gen-string :elements (gen-character :code (gen-integer :min 0 :max 0))\n                           :length (constantly 2))))\n    (is (= 2 (length s)))\n    (is (every (curry #'char= #\\Null) s))))\n\n(defun dummy-mv-generator ()\n  (lambda ()\n    (list 1 1)))\n\n(def-test for-all-destructuring-bind ()\n  (for-all (((a b) (dummy-mv-generator)))\n    (is (= 1 a))\n    (is (= 1 b))))\n\n(def-test return-values ()\n  \"Return values indicate test failures.\"\n  (is-true (with-*test-dribble* nil (explain! (run 'is1))))\n  (is-true (with-*test-dribble* nil (run! 'is1)))\n\n  (is-false (with-*test-dribble* nil (explain! (run 'is2))))\n  (is-false (with-*test-dribble* nil (run! 'is2))))\n\n(def-test dont-discard-suite ()\n  (let ((*suite* (make-suite 'nil))\n        (*toplevel-suites* nil))\n    (def-suite* :one-test-suite)\n    (def-suite* :two-test-suite)\n    (is (= 2 (length *toplevel-suites*)))))\n"
  },
  {
    "path": "third-party/fiveam/version.sexp",
    "content": ";; -*- lisp -*-\n\"1.4.2\"\n"
  },
  {
    "path": "third-party/hunchensocket/.travis.yml",
    "content": "language: lisp\nsudo: required\n\nenv:\n  matrix:\n#    - LISP=abcl\n#    - LISP=allegro\n    - LISP=sbcl\n#    - LISP=sbcl32\n    - LISP=ccl\n#    - LISP=ccl32\n#    - LISP=clisp\n#    - LISP=clisp32\n#    - LISP=cmucl\n#    - LISP=ecl\n\nmatrix:\n  allow_failures:\n    # CIM not available for CMUCL\n    - env: LISP=cmucl\n\ninstall:\n  - curl https://raw.githubusercontent.com/luismbo/cl-travis/master/install.sh | sh;\n\nscript:\n  - cl -e '(push *default-pathname-defaults* ql:*local-project-directories*)\n           (ql:quickload :fiasco)\n           (ql:quickload :hunchensocket)\n           (ql:quickload :hunchensocket-tests)\n           (defparameter *result* (fiasco:run-package-tests :packages\n                                     (quote (:hunchensocket-tests))))\n           (unless *result* (uiop:quit 1))\n\n           ;; Latest fiasco handles this much better, but the current verion in\n           ;; quicklisp returns a list of test results (global contexts)\n           ;;\n           (when (and (consp *result*)\n                      (notevery (function zerop)\n                         (mapcar (function length)\n                            (mapcar (function fiasco::failure-descriptions-of)\n                               *result*))))\n                 (uiop:quit 1))\n            '"
  },
  {
    "path": "third-party/hunchensocket/COPYING",
    "content": "Copyright (C) 2011  Alexander Kahl <e-user@fsfe.org>\n              2014  João Távora\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n  * Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n\n  * Redistributions in binary form must reproduce the above\n    copyright notice, this list of conditions and the following\n    disclaimer in the documentation and/or other materials\n    provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED\nOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE\nGOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "third-party/hunchensocket/README.md",
    "content": "[![Build Status](https://travis-ci.org/joaotavora/hunchensocket.svg?branch=master)](https://travis-ci.org/joaotavora/hunchensocket)\nHunchensocket - WebSockets for Hunchentoot\n==========================================\n\nHunchensocket is a Common Lisp implementation of [WebSocket]s realized\nas an extension to [Edi Weitz'] [edi] excellent [Hunchentoot] web\nserver. Hunchensocket implements a compliant [RFC6455][RFC6455] server. \n\nNote that Alexander Kahl, the original author, has desactivated his \n[old version][kahl] that only supports the drafts of the protocol.\n\nInstallation\n------------\n\nHunchensocket is in [Quicklisp][Quicklisp], so if you have that\nsetup just do `(ql:quickload :hunchensocket)`.\n\nQuicklisp is also good to use the trunk alongside with other \ndependencies, perhaps to test a new feature or a bugfix:\n\n```\n$ cd ~/Source/Lisp/\n$ git clone https://github.com/joaotavora/hunchensocket.git\n```\n\n```lisp\n(push \"~/Source/Lisp\" ql:*local-project-directories*)\n(ql:quickload :hunchensocket) ;; use local hunchensocket and pull\n                              ;; dependencies from quicklisp\n```\n\nA chat server in 30 lines\n-------------------------\n\nFirst define classes for rooms and users. Make these subclasses of\n`websocket-resource` and `websocket-client`.\n\n```lisp\n(defpackage :my-chat (:use :cl))\n(in-package :my-chat)\n\n(defclass chat-room (hunchensocket:websocket-resource)\n  ((name :initarg :name :initform (error \"Name this room!\") :reader name))\n  (:default-initargs :client-class 'user))\n\n(defclass user (hunchensocket:websocket-client)\n  ((name :initarg :user-agent :reader name :initform (error \"Name this user!\"))))\n```\n\nDefine a list of rooms. Notice that\n`hunchensocket:*websocket-dispatch-table*` works just like\n`hunchentoot:*dispatch-table*`, but for websocket specific resources.\n\n```lisp\n(defvar *chat-rooms* (list (make-instance 'chat-room :name \"/bongo\")\n                           (make-instance 'chat-room :name \"/fury\")))\n\n(defun find-room (request)\n  (find (hunchentoot:script-name request) *chat-rooms* :test #'string= :key #'name))\n\n(pushnew 'find-room hunchensocket:*websocket-dispatch-table*)\n```\n\nOK, now a helper function and the dynamics of a chat room.\n\n```lisp\n(defun broadcast (room message &rest args)\n  (loop for peer in (hunchensocket:clients room)\n        do (hunchensocket:send-text-message peer (apply #'format nil message args))))\n\n(defmethod hunchensocket:client-connected ((room chat-room) user)\n  (broadcast room \"~a has joined ~a\" (name user) (name room)))\n\n(defmethod hunchensocket:client-disconnected ((room chat-room) user)\n  (broadcast room \"~a has left ~a\" (name user) (name room)))\n\n(defmethod hunchensocket:text-message-received ((room chat-room) user message)\n  (broadcast room \"~a says ~a\" (name user) message))  \n```\n\nFinally, start the server. `hunchensocket:websocket-acceptor` works\njust like `hunchentoot:acceptor`, and you can probably also use\n`hunchensocket:websocket-ssl-acceptor`.\n\n\n```lisp\n(defvar *server* (make-instance 'hunchensocket:websocket-acceptor :port 12345))\n(hunchentoot:start *server*)\n```\n\nNow open two browser windows on http://www.websocket.org/echo.html,\nenter `ws://localhost:12345/bongo` as the host and play around chatting with\nyourself.\n\nLicense\n-------\n\nSee [COPYING][copying] for license details.\n\nDesign\n------\n\nMain sources of inspiration:\n\n* Original implementation by Alexander Kahl, which cleverly hijacks\n  the Hunchentoot connection after the HTTP response and keeps the\n  connection alive, just like in a Head request.\n* [clws][clws]'s API because it explicitly defines websocket \"resources\"\n* [Hunchentoot's][Hunchentoot]'s API because it uses CLOS\n\n\n[WebSocket]: http://en.wikipedia.org/wiki/WebSocket  \n[edi]: http://weitz.de/\n[kahl]: https://github.com/e-user/hunchensocket\n[RFC6455]: https://tools.ietf.org/html/rfc6455\n[clws]: https://github.com/3b/clws\n[copying]: https://github.com/joaotavora/hunchensocket/blob/master/COPYING\n[Hunchentoot]: http://weitz.de/hunchentoot/\n[Quicklisp]: http://www.quicklisp.org/  \n"
  },
  {
    "path": "third-party/hunchensocket/VERSION",
    "content": "1.0.0"
  },
  {
    "path": "third-party/hunchensocket/demo.lisp",
    "content": ";; A chat server in 30 lines\n;; -------------------------\n\n;; First define classes for rooms and users. Make these subclasses of\n;; `websocket-resource` and `websocket-client`.\n\n(defpackage :my-chat (:use :cl))\n(in-package :my-chat)\n\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (ql:quickload :hunchensocket))\n\n(defclass chat-room (hunchensocket:websocket-resource)\n  ((name :initarg :name :initform (error \"Name this room!\") :reader name))\n  (:default-initargs :client-class 'user))\n\n(defclass user (hunchensocket:websocket-client)\n  ((name :initarg :user-agent :reader name :initform (error \"Name this user!\"))))\n\n;; Define a list of rooms. Notice that\n;; `hunchensocket:*websocket-dispatch-table*` works just like\n;; `hunchentoot:*dispatch-table*`, but for websocket specific resources.\n\n(defvar *chat-rooms* (list (make-instance 'chat-room :name \"/bongo\")\n                           (make-instance 'chat-room :name \"/fury\")))\n\n(defun find-room (request)\n  (find (hunchentoot:script-name request) *chat-rooms* :test #'string= :key #'name))\n\n(pushnew 'find-room hunchensocket:*websocket-dispatch-table*)\n\n;; OK, now a helper function and the dynamics of a chat room.\n\n(defun broadcast (room message &rest args)\n  (loop for peer in (hunchensocket:clients room)\n        do (hunchensocket:send-text-message peer (apply #'format nil message args))))\n\n(defmethod hunchensocket:client-connected ((room chat-room) user)\n  (broadcast room \"~a has joined ~a\" (name user) (name room)))\n\n(defmethod hunchensocket:client-disconnected ((room chat-room) user)\n  (broadcast room \"~a has left ~a\" (name user) (name room)))\n\n(defmethod hunchensocket:text-message-received ((room chat-room) user message)\n  (broadcast room \"~a says ~a\" (name user) message))  \n\n;; Finally, start the server. `hunchensocket:websocket-acceptor` works\n;; just like `hunchentoot:acceptor`, and you can probably also use\n;; `hunchensocket:websocket-ssl-acceptor`.\n\n(defvar *server* (make-instance 'hunchensocket:websocket-acceptor :port 12345))\n\n(unless (hunchentoot::acceptor-listen-socket acceptor) ; should be\n                                                       ; hunchentoot:listening-p\n                                                       ; if it existed\n  (hunchentoot:start *server*))\n\n;; Now open two browser windows on http://www.websocket.org/echo.html,\n;; enter `ws://localhost:12345/bongo` as the host and play around chatting with\n;; yourself.\n\n"
  },
  {
    "path": "third-party/hunchensocket/hunchensocket-tests.lisp",
    "content": "(fiasco:define-test-package :hunchensocket-tests\n  (:use :cl :hunchensocket :fiasco :alexandria)\n  (:import-from :hunchensocket\n                #:with-new-client-for-resource\n                #:handle-frame\n                #:read-frame\n                #:read-frame-from-client\n                #:read-handle-loop\n                #:read-application-data\n                #:opcode\n                #:state\n                #:data\n                #:+text-frame+\n                #:+binary-frame+\n                #:+continuation-frame+\n                #:+ping+\n                #:+pong+\n                #:websocket-error\n                #:websocket-error-status\n                #:mask-unmask))\n(in-package :hunchensocket-tests)\n\n\f\n;;; unit tests based on RFC examples\n;;;\n(defun test-frame-reader-1 (expected-opcode expected-string input)\n  (flex:with-input-from-sequence\n      (stream input)\n    (let ((frame (read-frame stream :read-payload-p t)))\n      (with-slots (opcode data) frame\n        (is (= opcode expected-opcode))\n        (is (string= expected-string\n                     (flex:octets-to-string data\n                                            :external-format :utf-8)))\n        frame))))\n\n(deftest frame-reader ()\n  \"Test reading unfragmented non-binary frames\"\n  (loop for (opcode contains input)\n          in '(;; A single-frame unmasked text message\n               (#.+text-frame+ \"Hello\" #(#x81 #x05 #x48 #x65\n                                         #x6c #x6c #x6f))\n               ;; A single-frame masked text message\n               (#.+text-frame+ \"Hello\" #(#x81 #x85 #x37 #xfa\n                                         #x21 #x3d #x7f #x9f\n                                         #x4d #x51 #x58))\n               ;; Unmasked Ping request and masked Ping response\n               (#.+ping+       \"Hello\" #(#x89 #x05 #x48 #x65\n                                         #x6c #x6c #x6f))\n               (#.+pong+       \"Hello\" #(#x8a #x85 #x37 #xfa\n                                         #x21 #x3d #x7f #x9f\n                                         #x4d #x51 #x58))\n               ;; Me being clever\n               (#.+ping+       \"Cello\" #(#x89 #x05 #x43 #x65\n                                         #x6c #x6c #x6f)))\n        do (test-frame-reader-1 opcode contains input)))\n\n\f\n;;; This tests a lot more machinery\n;;;\n(defvar *expected-messages*)\n(defvar *expected-files*)\n\n(defclass mock-request ()\n  ((headers-in :initform '((:origin . \"mock\")\n                           (:user-agent . \"moremock\"))\n               :reader hunchentoot:headers-in)))\n\n(defclass mock-resource (websocket-resource)\n  ()\n  (:default-initargs :client-class 'mock-client))\n\n(defclass mock-client (websocket-client)\n  ((received-messages :initform nil)\n   (received-files    :initform nil)))\n\n(defmethod text-message-received ((resource mock-resource)\n                                  (client mock-client) message)\n  (is *expected-messages* \"Didn't expect a message at all but got one\")\n  (when *expected-messages*\n    (is (string= (pop *expected-messages*) message))\n    (with-slots (received-messages) client\n      (push message received-messages))))\n\n(defmethod binary-message-received ((resource mock-resource)\n                                    (client mock-client) file)\n  (is *expected-files*\n      \"Didn't expect a binary file at all but got one\")\n  (when *expected-files*\n    (let ((file-contents\n            (with-open-file (fstream file :direction :input\n                                          :element-type '(unsigned-byte 8))\n              (let ((seq (make-array (file-length fstream)\n                                     :element-type '(unsigned-byte 8)\n                                     :fill-pointer t)))\n                (setf (fill-pointer seq) (read-sequence seq fstream))\n                seq))))\n      (is (equalp (pop *expected-files*) file-contents))\n      (with-slots (received-files) client\n        (push file-contents received-files)))))\n\n(defmacro with-resource-and-client ((res-sym client-sym)\n                                    (mock-input mock-output) &body body)\n  `(let ((,res-sym (make-instance 'mock-resource)))\n     (with-new-client-for-resource\n         (,client-sym :input-stream ,mock-input\n                      :output-stream ,mock-output\n                      :resource ,res-sym\n                      :request  (make-instance 'mock-request))\n       ,@body)))\n\n(defun test-frame-handler-1 (input &key expected-messages expected-files)\n  (flex:with-input-from-sequence (mock-input (apply #'concatenate 'vector\n                                                    (ensure-list input)))\n    (let ((*expected-messages* expected-messages)\n          (*expected-files* expected-files))\n      (with-resource-and-client (resource client) (mock-input nil)\n        (with-slots (state) client\n          (handler-case\n              (signals end-of-file\n                (loop do (handle-frame resource\n                                       client\n                                       (read-frame-from-client client))))\n            (end-of-file (e) (declare (ignore e)))))\n        (with-slots (received-messages received-files) client\n          (is (equalp expected-messages (reverse received-messages)))\n          (is (equalp expected-files (reverse received-files))))\n        client))))\n\n(deftest frame-handler ()\n  (loop for (text . vectors)\n          in '(;; A single-frame unmasked text message\n               ((\"Hello\") #(#x81 #x05 #x48 #x65\n                            #x6c #x6c #x6f))\n               ;; A single-frame masked text message\n               ((\"Hello\") #(#x81 #x85 #x37 #xfa\n                            #x21 #x3d #x7f #x9f\n                            #x4d #x51 #x58))\n               ((\"Hello\")\n                ;; contains \"Hel\"\n                #(#x01 #x03 #x48 #x65 #x6c) \n                ;; contains \"lo\"\n                #(#x80 #x02 #x6c #x6f)))\n        do (test-frame-handler-1 vectors :expected-messages text)))\n\n(defvar *max-fragment-size* 1024)\n(defvar *max-total-size*    4096)\n\n(defmethod check-message ((resource mock-resource)\n                          (client mock-client) (opcode (eql +binary-frame+))\n                          length total)\n  (cond ((> length *max-fragment-size*)\n         (websocket-error 1009 \"Message fragment too big\"))\n        ((> total *max-total-size*)\n         (websocket-error 1009 \"Total message too big\"))))\n\n(defmacro signals-with-lambda ((condition-type lambda) &body body)\n  `(signals ,condition-type\n     (handler-bind\n         ((,condition-type ,lambda))\n       ,@body)))\n\n(deftest binary-message ()\n  (let* ((length 128)\n         (42-array (make-array length :initial-element 42))\n         (masking-key #(#x01 #x02 #x03 #x04)))\n    (test-frame-handler-1 (list #(#x82 #xFE)\n                                  (coerce `(,(ash (ldb (byte 8 8) length) 8)\n                                            ,(ldb (byte 8 0) length)) 'vector)\n                                  masking-key\n                                  (mask-unmask\n                                   (copy-sequence 'vector 42-array)\n                                   masking-key))\n                            :expected-files (list 42-array))))\n\n(defun test-fragmented-binaries (&rest first-bytes-of-each-fragment)\n  (let* ((42-array (make-array 256 :initial-element 42))\n         (masking-key #(#x01 #x02 #x03 #x04)))\n    (test-frame-handler-1\n     (loop for first-byte in first-bytes-of-each-fragment\n           append (list (make-array 1 :initial-element first-byte)\n                        #(#xFE #x01 #x00)\n                        masking-key\n                        (mask-unmask\n                         (copy-sequence 'vector 42-array)\n                         masking-key)))\n     :expected-files (list\n                      (apply #'concatenate 'vector\n                             (loop repeat\n                                   (length first-bytes-of-each-fragment)\n                                   collect 42-array))))))\n\n(deftest fragmented-binary-1024 ()\n  (test-fragmented-binaries #x02 #x00 #x00 #x80))\n\n(deftest incorrect-fragmentation-1024 ()\n  (flet ((check-error (e status regexp)\n           (is (= (websocket-error-status e) status))\n           (is (ppcre:scan regexp (princ-to-string e)))))\n    (signals-with-lambda (websocket-error\n                          (alexandria:rcurry #'check-error\n                                             1002\n                                             \"Not discarding initiated fragment sequence\"))\n      (test-fragmented-binaries #x02 #x00 #x01 #x80))\n    (signals-with-lambda (websocket-error\n                          (alexandria:rcurry #'check-error\n                                             1002\n                                             \"Unexpected continuation frame\"))\n      (test-fragmented-binaries #x00 #x00 #x01 #x80))\n    (signals-with-lambda (websocket-error\n                          (alexandria:rcurry #'check-error\n                                             1009\n                                             \"Total message too big\"))\n      (let ((*max-total-size* 780)\n            (*max-fragment-size* 512))\n        (test-fragmented-binaries #x02 #x00 #x00 #x80)))\n    (signals-with-lambda (websocket-error\n                          (alexandria:rcurry #'check-error\n                                             1009\n                                             \"Message fragment too big\"))\n      (let ((*max-total-size* 2048)\n            (*max-fragment-size* 128))\n        (test-fragmented-binaries #x02 #x00 #x00 #x80)))))\n\n\n\n\n\n\n  \n  \n\n\n\n\n\n"
  },
  {
    "path": "third-party/hunchensocket/hunchensocket.asd",
    "content": "(asdf:defsystem :hunchensocket\n  :description \"WebSockets for Hunchentoot\"\n  :author \"capitaomorte <https://github.com/capitaomorte>\"\n  :license \"MIT\"\n  :version #.(with-open-file (f \"VERSION\") (string (read f)))\n  :depends-on (:hunchentoot\n               :alexandria\n               :ironclad\n               :flexi-streams\n               :chunga\n               :trivial-utf-8\n               :trivial-backtrace\n               :bordeaux-threads\n               :cl-fad)\n  :serial t\n  :components\n  ((:file \"package\")\n   (:file \"hunchensocket\")))\n\n(asdf:defsystem :hunchensocket-tests\n  :description \"Tests for Hunchensocket\"\n  :version #.(with-open-file (f \"VERSION\") (string (read f)))\n  :depends-on (:fiasco\n               :hunchensocket)\n  :serial t\n  :components\n  ((:file \"hunchensocket-tests\")))\n\n\n"
  },
  {
    "path": "third-party/hunchensocket/hunchensocket.lisp",
    "content": "(in-package :hunchensocket)\n\n(define-constant +websocket-magic-key+\n  \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n  :test #'string=\n  :documentation \"Fixed magic WebSocket UUIDv4 key use in handshakes\")\n\n(define-constant +continuation-frame+    #x0)\n(define-constant +text-frame+            #x1)\n(define-constant +binary-frame+          #x2)\n(define-constant +connection-close+      #x8)\n(define-constant +ping+                  #x9)\n(define-constant +pong+                  #xA)\n\n(defun control-frame-p (opcode)\n  (plusp (logand #x8 opcode)))\n\n(defvar *websocket-socket* nil\n  \"The currently active WebSocket socket\")\n\n\f\n;;; Mandatory API\n;;;\n(defvar *websocket-dispatch-table* nil\n  \"List of handler closures that will be queried for new connections\")\n\n(defclass websocket-acceptor (acceptor)\n  ((websocket-timeout :initarg :websocket-timeout\n                      :accessor websocket-timeout\n                      :initform 300\n                      :documentation \"Custom WebSocket timeout override.\"))\n  (:default-initargs :request-class 'websocket-request\n                     :reply-class 'websocket-reply))\n\n(defclass websocket-ssl-acceptor (websocket-acceptor ssl-acceptor) ()\n  (:documentation \"Special WebSocket SSL acceptor\"))\n\n(defclass websocket-client ()\n  ((input-stream     :initarg input-stream\n                     :initform (error \"Must make clients with input streams\"))\n   (output-stream    :initarg output-stream\n                     :initform (error \"Must make clients with output streams\"))\n   (request    :initarg request\n               :reader client-request\n               :initform (error \"Must make clients with requests\"))\n   (write-lock :initform (make-lock))\n   (state      :initform :disconnected)\n   (pending-fragments :initform nil)\n   (pending-opcode    :initform nil)))\n\n(defmethod initialize-instance :after ((client websocket-client)\n                                       &key &allow-other-keys)\n  \"Allows CLIENT to be passed more keywords on MAKE-INSTANCE.\")\n\n(defclass websocket-resource ()\n  ((clients :initform nil :reader clients)\n   (client-class :initarg :client-class :initform 'websocket-client)\n   (lock :initform (make-lock))))\n\n(defmethod print-object ((obj websocket-resource) stream)\n  (print-unreadable-object (obj stream :type t)\n    (with-slots (clients client-class) obj\n        (format stream \"(~a connected clients of class ~a)\"\n                (length clients)\n                client-class))))\n\n(defgeneric text-message-received (resource client message))\n\n(defgeneric binary-message-received  (resource client binary))\n\n;; Optional API\n;;\n(defgeneric client-connected (resource client)\n  (:method (resource client)\n    (declare (ignore resource client))))\n\n(defgeneric client-disconnected (resource client)\n  (:method (resource client)\n    (declare (ignore resource client))))\n\n(defgeneric check-message (resource client opcode fragment-length total-length)\n  (:method ((resource websocket-resource)\n            (client websocket-client) opcode length total)\n    (declare (ignore resource client))\n    (cond ((> length #xffff) ; 65KiB\n           (websocket-error 1009 \"Message fragment too big\"))\n          ((> total #xfffff) ; 1 MiB\n           (websocket-error 1009 \"Total message too big\"))))\n  (:method ((resource websocket-resource)\n            (client websocket-client)\n            (opcode (eql +binary-frame+)) length total)\n    (websocket-error 1003 \"Binaries not accepted\")))\n\n;; Convenience API\n;;\n(defun send-text-message (client message)\n  \"MESSAGE is a string\"\n  (send-frame client +text-frame+\n              (flexi-streams:string-to-octets message\n                                              :external-format :utf-8)))\n\n(defun send-binary-message (client message)\n  \"MESSAGE is an array of octets\"\n  (send-frame client +binary-frame+\n              message))\n\n(defun send-ping (client &optional (message #()))\n  (send-frame client +ping+ message))\n\n(defun close-connection (client &key (data nil data-supplied-p)\n                                     (status 1000)\n                                     (reason \"Normal close\"))\n  (send-frame client\n              +connection-close+\n              (if data-supplied-p\n                  data\n                  (concatenate 'vector\n                               (coerce (list (logand (ash status -8) #xff)\n                                             (logand status #xff))\n                                       'vector)\n                               (flexi-streams:string-to-octets\n                                reason\n                                :external-format :utf-8))))\n  (setf (slot-value client 'state) :awaiting-close))\n\n\n(defun send-frame (client opcode data)\n  (with-slots (write-lock output-stream) client\n    (with-lock-held (write-lock)\n      (write-frame output-stream opcode data))))\n\n\f\n;;; Request/reply Hunchentoot overrides\n;;;\n(defclass websocket-request (request)\n  ((handler :accessor websocket-resource\n            :initform nil\n            :documentation \"Message handler of the current request\")))\n\n(defclass websocket-reply (reply) ())\n\n(defmethod initialize-instance :after ((reply websocket-reply) &rest initargs)\n  \"Set the reply's external format to Unix EOL / UTF-8 explicitly.\"\n  (declare (ignore initargs))\n  (setf (reply-external-format reply)\n        (make-external-format :utf8 :eol-style :lf)))\n\n\f\n;;; Conditions\n\n(define-condition websocket-error (simple-error)\n  ((error-status :initarg :status :reader websocket-error-status\n                 :initform nil))\n  (:documentation \"Superclass for all errors related to Websocket.\"))\n\n(defun websocket-error (status format-control &rest format-arguments)\n  \"Signals an error of type HUNCHENTOOT-SIMPLE-ERROR with the provided\nformat control and arguments.\"\n  (error 'websocket-error\n         :status status\n         :format-control format-control\n         :format-arguments format-arguments))\n\n\f\n;;; Client and resource machinery\n;;;\n(defmethod initialize-instance :after ((resource websocket-resource)\n                                       &key client-class)\n  (assert (subtypep client-class 'websocket-client)))\n\n(defun call-with-new-client-for-resource (client resource fn)\n  (with-slots (clients lock) resource\n    (unwind-protect\n         (progn\n           (bt:with-lock-held (lock)\n             (push client clients))\n           (setf (slot-value client 'state) :connected)\n           (client-connected resource client)\n           (funcall fn))\n      (bt:with-lock-held (lock)\n        (with-slots (write-lock) client\n          (bt:with-lock-held (write-lock)\n            (setq clients (remove client clients)))))\n      (client-disconnected resource client))))\n\n(defmacro with-new-client-for-resource ((client-sym &key input-stream\n                                                      output-stream\n                                                      resource\n                                                      request)\n                                        &body body)\n  (alexandria:once-only (resource request)\n    `(let ((,client-sym (apply #'make-instance\n                               (slot-value ,resource 'client-class)\n                               'input-stream ,input-stream\n                               'output-stream ,output-stream\n                               'request ,request\n                               :request ,request\n                               (loop for (header . value)\n                                       in (headers-in ,request)\n                                     collect header collect value))))\n       (call-with-new-client-for-resource ,client-sym\n                                          ,resource\n                                          #'(lambda () ,@body)))))\n\n(defun websocket-uri (request host &optional ssl)\n  \"Form WebSocket URL (ws:// or wss://) URL.\"\n  (format nil \"~:[ws~;wss~]://~a~a\" ssl host (script-name request)))\n\n\f\n;;; Binary reading/writing machinery\n;;;\n(defun read-unsigned-big-endian (stream n)\n  \"Read N bytes from stream and return the big-endian number\"\n  (loop for i from (1- n) downto 0\n        sum (* (read-byte stream) (expt 256 i))))\n\n(defun read-n-bytes-into-sequence (stream n)\n  \"Return an array of N bytes read from stream\"\n  (let* ((array (make-array n :element-type '(unsigned-byte 8)))\n         (read (read-sequence array stream)))\n    (assert (= read n) nil\n            \"Expected to read ~a bytes, but read ~a\" n read)\n    array))\n\n(defclass frame ()\n  ((opcode          :initarg :opcode :accessor frame-opcode)\n   (data                             :accessor frame-data)\n   (finp            :initarg :finp)\n   (payload-length  :initarg :payload-length :accessor frame-payload-length)\n   (masking-key     :initarg :masking-key)))\n\n(defun read-frame (stream &key read-payload-p)\n  (let* ((first-byte       (read-byte stream))\n         (fin              (ldb (byte 1 7) first-byte))\n         (extensions       (ldb (byte 3 4) first-byte))\n         (opcode           (ldb (byte 4 0) first-byte))\n         (second-byte      (read-byte stream))\n         (mask-p           (plusp (ldb(byte 1 7) second-byte)))\n         (payload-length   (ldb (byte 7 0) second-byte))\n         (payload-length   (cond ((<= 0 payload-length 125)\n                                  payload-length)\n                                 (t\n                                  (read-unsigned-big-endian\n                                   stream (case payload-length\n                                            (126 2)\n                                            (127 8))))))\n         (masking-key      (if mask-p (read-n-bytes-into-sequence stream 4)))\n         (extension-data   nil))\n    (declare (ignore extension-data))\n    (when (and (control-frame-p opcode)\n               (> payload-length 125))\n      (websocket-error\n       1002 \"Control frame is too large\" extensions))\n    (when (plusp extensions)\n      (websocket-error\n       1002 \"No extensions negotiated, but client sends ~a!\" extensions))\n    (let ((frame\n            (make-instance 'frame :opcode opcode\n                                  :finp (plusp fin)\n                                  :masking-key masking-key\n                                  :payload-length payload-length)))\n      (when (or (control-frame-p opcode)\n                read-payload-p)\n        (read-application-data stream frame))\n      frame)))\n\n(defun read-frame-from-client (client)\n  \"Read a text or binary message from CLIENT.\"\n  (with-slots (input-stream) client\n    (read-frame input-stream)))\n\n(defun mask-unmask (data masking-key)\n  ;; RFC6455 Masking\n  ;;\n  ;; Octet i of the transformed data\n  ;; (\"transformed-octet-i\") is the XOR of octet i\n  ;; of the original data (\"original-octet-i\")\n  ;; with octet at index i modulo 4 of the masking\n  ;; key (\"masking-key-octet-j\"):\n  (loop for i from 0 below (length data)\n        do (setf (aref data i)\n                 (logxor (aref data i)\n                         (aref masking-key\n                               (mod i 4)))))\n  data)\n\n(defun read-application-data (stream frame)\n  (with-slots (masking-key payload-length data) frame\n    (setq data (read-n-bytes-into-sequence stream\n                                           payload-length))\n    (when masking-key\n      (mask-unmask data masking-key))))\n\n(defun write-frame (stream opcode &optional data)\n  (let* ((first-byte     #x00)\n         (second-byte    #x00)\n         (len            (if data (length data) 0))\n         (payload-length (cond ((<= len 125)        len)\n                               ((< len (expt 2 16)) 126)\n                               (t                   127)))\n         (mask-p         nil))\n    (setf (ldb (byte 1 7) first-byte)  1\n          (ldb (byte 3 4) first-byte)  0\n          (ldb (byte 4 0) first-byte)  opcode\n          (ldb (byte 1 7) second-byte) (if mask-p 1 0)\n          (ldb (byte 7 0) second-byte) payload-length)\n    (write-byte first-byte stream)\n    (write-byte second-byte stream)\n    (loop for i from  (1- (cond ((= payload-length 126) 2)\n                                ((= payload-length 127) 8)\n                                (t                      0)))\n          downto 0\n          for out = (ash len (- (* 8 i)))\n          do (write-byte (logand out #xff) stream))\n    ;; (if mask-p\n    ;;     (error \"sending masked messages not implemented yet\"))\n    (if data (write-sequence data stream))\n    (force-output stream)))\n\n\f\n;;; State machine and main websocket loop\n;;;\n(defun handle-frame (resource client frame)\n  (with-slots (state pending-fragments pending-opcode input-stream) client\n    (with-slots (opcode finp payload-length masking-key) frame\n      (flet ((maybe-accept-data-frame ()\n               (check-message resource client (or pending-opcode\n                                                  opcode)\n                              payload-length\n                              (+ payload-length\n                                 (reduce #'+ (mapcar\n                                              #'frame-payload-length\n                                              pending-fragments))))\n               (read-application-data input-stream frame)))\n        (cond\n          ((eq +pong+ opcode)\n           ;; Probably just a heartbeat, don't do anything.\n           )\n          ((eq :awaiting-close state)\n           ;; We're waiting a close because we explicitly sent one to the\n           ;; client. Error out if the next message is not a close.\n           ;;\n           (unless (eq opcode +connection-close+)\n             (websocket-error\n              1002 \"Expected connection close from client, got 0x~x\" opcode))\n           (setq state :closed))\n          ((not finp)\n           ;; This is a non-FIN fragment Check opcode, append to client's\n           ;; fragments.\n           ;;\n           (cond ((and (= opcode +continuation-frame+)\n                       (not pending-fragments))\n                  (websocket-error\n                   1002 \"Unexpected continuation frame\"))\n                 ((control-frame-p opcode)\n                  (websocket-error\n                   1002 \"Control frames can't be fragmented\"))\n                 ((and pending-fragments\n                       (/= opcode +continuation-frame+))\n                  (websocket-error\n                   1002 \"Not discarding initiated fragment sequence\"))\n                 (t\n                  ;; A data frame, is either initiaing a new fragment sequence\n                  ;; or continuing one\n                  ;;\n                  (maybe-accept-data-frame)\n                  (cond ((= opcode +continuation-frame+)\n                         (push frame pending-fragments))\n                        (t\n                         (setq pending-opcode opcode\n                               pending-fragments (list frame)))))))\n          ((and pending-fragments\n                (not (or (control-frame-p opcode)\n                         (= opcode +continuation-frame+))))\n           ;; This is a FIN fragment and (1) there are pending fragments and (2)\n           ;; this isn't a control or continuation frame. Error out.\n           ;;\n           (websocket-error\n            1002 \"Only control frames can interleave fragment sequences.\"))\n          (t\n           ;; This is a final, FIN fragment. So first read the fragment's data\n           ;; into the `data' slot.\n           ;;\n           (cond\n             ((not (control-frame-p opcode))\n              ;; This is either a single-fragment data frame or a continuation\n              ;; frame. Join the fragments and keep on processing. Join any\n              ;; outstanding fragments and process the message.\n              ;;\n              (maybe-accept-data-frame)\n              (unless pending-opcode\n                (setq pending-opcode opcode))\n              (let ((ordered-frames\n                      (reverse (cons frame pending-fragments))))\n                (cond ((eq +text-frame+ pending-opcode)\n                       ;; A text message was received\n                       ;;\n                       (text-message-received\n                        resource client\n                        (flexi-streams:octets-to-string\n                         (apply #'concatenate 'vector\n                                (mapcar #'frame-data\n                                        ordered-frames))\n                         :external-format :utf-8)))\n                      ((eq +binary-frame+ pending-opcode)\n                       ;; A binary message was received\n                       ;;\n                       (let ((temp-file\n                               (fad:with-output-to-temporary-file\n                                   (fstream :element-type '(unsigned-byte 8))\n                                 (loop for fragment in ordered-frames\n                                       do (write-sequence (frame-data frame)\n                                                          fstream)))))\n                         (unwind-protect\n                              (binary-message-received resource client\n                                                       temp-file)\n                           (delete-file temp-file))))\n                      (t\n                       (websocket-error\n                        1002 \"Client sent unknown opcode ~a\" opcode))))\n              (setf pending-fragments nil))\n             ((eq +ping+ opcode)\n              ;; Reply to client-initiated ping with a server-pong with the\n              ;; same data\n              (send-frame client +pong+ (frame-data frame)))\n             ((eq +connection-close+ opcode)\n              ;; Reply to client-initiated close with a server-close with the\n              ;; same data\n              ;;\n              (close-connection client :data (frame-data frame))\n              (setq state :closed))\n             (t\n              (websocket-error\n               1002 \"Client sent unknown opcode ~a\" opcode)))))))))\n\n\n(defun read-handle-loop (resource client\n                         &optional (version :rfc-6455))\n  \"Implements the main WebSocket loop for supported protocol\nversions. Framing is handled automatically, CLIENT handles the actual\npayloads.\"\n  (ecase version\n    (:rfc-6455\n     (handler-bind ((websocket-error\n                      #'(lambda (error)\n                          (with-slots (error-status format-control format-arguments)\n                              error\n                            (close-connection\n                             client\n                             :status error-status\n                             :reason (princ-to-string error)))))\n                    (flexi-streams:external-format-error\n                      #'(lambda (e)\n                          (declare (ignore e))\n                          (close-connection client :status 1007\n                                                   :reason \"Bad UTF-8\")))\n                    (error\n                      #'(lambda (e)\n                          (declare (ignore e))\n                          (close-connection client :status 1011\n                                                   :reason \"Internal error\"))))\n       (with-slots (state) client\n         (loop do (handle-frame resource\n                                client\n                                (read-frame-from-client client))\n               while (not (eq :closed state))))))))\n\n\f\n;;; Hook onto normal Hunchentoot processing\n;;;\n;;; The `:after' specilization of `process-request' will happen after\n;;; the main Hunchentoot one. It is hunchentoot which eventually calls\n;;; our specialization of `acceptor-dispatch-request', who will, in\n;;; turn, try to figure out if the client is requesting\n;;; websockets. Hunchentoot's `process-request' will also eventually\n;;; reply to the client. In the `:after' specialization we might enter\n;;; into `read-handle-loop' and thus keep the socket alive. That happens\n;;; if:\n;;;\n;;; 1. There are suitable \"Connection\" and \"Upgrade\" headers and\n;;;    `websocket-resource' object is found for request.\n;;;\n;;; 2. The websocket handshake completes sucessfully, whereby the\n;;;    callees of `acceptor-dispatch-request' will have set\n;;;    `+http-switching-protocols+' accordingly.\n;;;\n;;; If any of these steps fail, errors might be signalled, but normal\n;;; hunchentoot processing of the HTTP request still happens.\n\n(defmethod process-connection :around ((*acceptor* websocket-acceptor)\n                                       (socket t))\n  \"Sprinkle the current connection with dynamic bindings.\"\n  (let ((*websocket-socket* socket))\n    (call-next-method)))\n\n(defmethod process-request :after ((request websocket-request))\n  \"After HTTP processing REQUEST, maybe hijack into WebSocket loop.\"\n  ;; HACK! ask upstream Hunchentoot for this.\n  (let ((stream (hunchentoot::content-stream request)))\n    (when (= +http-switching-protocols+ (return-code*))\n      (force-output stream)\n      (let* ((timeout (websocket-timeout (request-acceptor request)))\n             (resource (websocket-resource request)))\n        (with-new-client-for-resource (client :input-stream stream\n                                              :output-stream stream\n                                              :resource resource\n                                              :request request)\n          ;; See https://github.com/joaotavora/hunchensocket/pull/23\n          ;; LispWorks in Hunchentoot passes around a USOCKET:USOCKET\n          ;; handle, not an actual such object. Unfortunately, abusing\n          ;; Hunchentoot's internals consequently forces us to tend to\n          ;; this LispWorks particularity.\n          #-lispworks\n          (hunchentoot::set-timeouts *websocket-socket* timeout timeout)\n          #+lispworks\n          (setf (stream:stream-read-timeout stream) timeout\n                (stream:stream-write-timeout stream) timeout)\n\n          (catch 'websocket-done\n            (handler-bind ((error #'(lambda (e)\n                                      (maybe-invoke-debugger e)\n                                      (log-message* :error \"Error: ~a\" e)\n                                      (throw 'websocket-done nil))))\n              (read-handle-loop resource client))))))))\n\n(defmethod handle-handshake ((acceptor websocket-acceptor) request reply)\n  \"Analyse REQUEST for WebSocket handshake.\n\nDestructively modify REPLY accordingly in case of success, exit\nnon-locally with an error instead.\"\n  ;; Implements 4.2.2.  Sending the Server's Opening Handshake\n  (let ((requested-version (header-in* :sec-websocket-version request)))\n    (cond ((not (equal \"13\" requested-version))\n           (websocket-error 1002\n            \"Unsupported websocket version ~a\" requested-version))\n          ((header-in :sec-websocket-draft request)\n           (websocket-error 1002\n            \"Websocket draft is unsupported\"))\n          ((header-in :sec-websocket-key request)\n           (let ((sec-websocket-key+magic\n                   (concatenate 'string (header-in :sec-websocket-key request)\n                                \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")))\n             (setf (header-out :sec-websocket-accept reply)\n                   (base64:usb8-array-to-base64-string\n                    (ironclad:digest-sequence\n                     'ironclad:sha1\n                     (ironclad:ascii-string-to-byte-array\n                      sec-websocket-key+magic))))\n             (setf (header-out :sec-websocket-origin reply)\n                   (header-in :origin request))\n             (setf (header-out :sec-websocket-location reply)\n                   (or (websocket-uri request (header-in :host request)\n                                      (ssl-p (request-acceptor request)))))\n             (setf (header-out :sec-websocket-protocol reply)\n                   (first (split \"\\\\s*,\\\\s*\" (header-in :sec-websocket-protocol\n                                                        request))))\n             ;; A (possibly empty) list representing the\n             ;; protocol-level extensions the server is ready to use.\n             ;;\n             (setf (header-out :sec-websocket-extensions reply) nil)\n             (setf (return-code* reply) +http-switching-protocols+\n                   (header-out :upgrade reply) \"WebSocket\"\n                   (header-out :connection reply) \"Upgrade\"\n                   (content-type* reply) \"application/octet-stream\")\n             ;; HACK! but a decent one I think. Notice that we set both\n             ;; in and out \"Connection\" headers to \"Upgrade\". The out\n             ;; header is per RFC, but the in-header is for a different\n             ;; reason. If the client additionally send \"Keep-Alive\" in\n             ;; that header, hunchentoot will eventually take it as a\n             ;; reason to clobber our out-header value of \"Upgrade\" with\n             ;; \"Keep-Alive <timeout>\".\n             (setf (cdr (find :connection (headers-in request) :key #'car))\n                   \"Upgrade\")))\n          (t (websocket-error 1002 \"Unsupported unknown websocket version\")))))\n\n(defun find-websocket-resource (request)\n  \"Find the resource for REQUEST by looking up *WEBSOCKET-DISPATCH-TABLE*.\"\n  (some #'(lambda (dispatcher)\n            (funcall dispatcher request))\n        *websocket-dispatch-table*))\n\n(defmethod acceptor-dispatch-request ((acceptor websocket-acceptor)\n                                      (request websocket-request))\n  \"Attempt WebSocket connection, else fall back to HTTP\"\n  (cond ((and (member \"upgrade\" (split \"\\\\s*,\\\\s*\" (header-in* :connection))\n                      :test #'string-equal)\n              (string= \"websocket\" (string-downcase (header-in* :upgrade))))\n         (cond ((setf (websocket-resource *request*)\n                      (find-websocket-resource *request*))\n                ;; Found the websocket resource\n                (handle-handshake acceptor *request* *reply*)\n                ;; HACK! the empty string is also important because if there's\n                ;; no content Hunchentoot will declare the connection closed and\n                ;; set \"Connection: Closed\". But there can't be any actual\n                ;; content since otherwise it will piggyback onto the first\n                ;; websocket frame, which is interpreted as invalid by the\n                ;; client. It's also forbidden by the HTTP RFC2616:\n                ;;\n                ;;    [...] All 1xx (informational), 204 (no content), and 304\n                ;;    (not modified) responses MUST NOT include a\n                ;;    message-body. [...]\n                ;;\n                ;; There is a slight non-conformance here: this trick makes\n                ;; Hunchentoot send \"Content-length: 0\". Most browsers don't\n                ;; seem to care, but RFC2616 kind of implies that is forbidden,\n                ;; since it says the\n                ;;\n                ;;    [...] the presence of a message-body is signaled by the\n                ;;    inclusion of a Content-Length or Transfer-Encoding header\n                ;;    field in the requests's message-headers [...]\n                ;;\n                ;; Note however, we're sending a response, not a request.\n                ;;\n                (values \"\" nil nil))\n               (t\n                ;; Didn't find the websocket-specific resource, return 404.\n                (setf (return-code *reply*) +http-not-found+)\n                (values nil nil nil))))\n        (t\n         ;; Client is not requesting websockets, let Hunchentoot do its HTTP\n         ;; thing undisturbed.\n         (call-next-method))))\n\n;; Local Variables:\n;; coding: utf-8-unix\n;; End:\n"
  },
  {
    "path": "third-party/hunchensocket/package.lisp",
    "content": "(cl:defpackage :hunchensocket\n  (:use :cl :alexandria :hunchentoot :cl-ppcre :alexandria\n   :flexi-streams :trivial-utf-8 :bordeaux-threads)\n  (:import-from :ironclad :digest-sequence)\n  (:import-from :chunga :read-char*)\n  (:import-from :trivial-backtrace :print-backtrace)\n  (:import-from :hunchentoot :log-message*)\n  (:export\n   ;; acceptor classes\n   #:websocket-acceptor\n   #:websocket-ssl-acceptor\n\n   ;; dispatch table\n   #:*websocket-dispatch-table*\n\n   ;; resource and client classes\n   #:websocket-resource\n   #:websocket-client\n   #:client-request\n   #:close-connection\n\n   ;; message validation\n   #:check-message\n\n   ;; resource accessors\n   #:clients\n\n   ;; status updates\n   ;;\n   #:client-connected\n   #:client-disconnected\n\n   ;; receiving and sending messages\n   ;;\n   #:binary-message-received\n   #:text-message-received\n   #:send-ping\n   #:send-text-message\n   #:send-binary-message))\n"
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/.gitignore",
    "content": ""
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/README.md",
    "content": "# hunchentoot-multi-acceptor\n### Arnold Noronha <arnold@jipr.io>\n\nTypically each hunchentoot acceptor listens on a single port, and\ntypically serves a single domain. A typical configuration is to have a\nfrontend webserver like Nginx to route requests to the appropriate\nport.\n\nThis is hard to manage when the number of domains keep increasing\n(which is typical for small companies trying to iterate on different\nproducts). hunchentoot-multi-acceptor simplifies the configurations of\nsuch systems.\n\n## Installation\n\nAs an example, I personally run tdrhq.com (my personal website) and\njipr.io (a project I'm working on.) These have two different\nhunchentoot acceptors:\n\n```lisp\n(defvar *tdrhq-acceptor* (make-instance 'hunchentoot:easy-acceptor\n                                         :port nil\n                                         :name 'tdrhq))\n\n(defvar *jipr-acceptor*\n  (make-instance 'hunchentoot:easy-acceptor\n                 :port nil\n                 :name 'jipr))\n```\n\nThese definitions could be in different packages and systems, it\ndoesn't matter. Also the `:port` argument is useful when developing\nlocally and want to test only one of the websites, but it won't be\nused by multi-acceptor.\n\nNow, we have a multi-acceptor setup as follows:\n\n```lisp\n(defparameter *multi-acceptor* (make-instance 'hunchentoot-multi-acceptor:multi-acceptor :port 4001 :name 'multi-acceptor))\n(hunchentoot-multi-acceptor:add-acceptor *multi-acceptor* \"tdrhq.com\" *tdrhq-acceptor*)\n(hunchentoot-multi-acceptor:add-acceptor *multi-acceptor* \"www.jipr.io\" *jipr-acceptor*)\n\n(hunchentoot:start *multi-acceptor*)\n```\n\nAt this point, you can configure your Nginx frontend for both websites\nto point to `localhost:4001`. Make sure the \"Host\" header is\nappropriately set. (`proxy_set_header Host $host:$server_port`). After\nthat hunchentoot-multi-acceptor will take care of routing to the\nappropriate acceptor. Only one port will be opened.\n\n## License\n\nApache License, Version 2.0\n"
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/hunchentoot-multi-acceptor.asd",
    "content": ";; Copyright 2019 Modern Interpreters Inc.\n\n(asdf:defsystem #:hunchentoot-multi-acceptor\n  :description \"Multiple domain support under single hunchentoot acceptor\"\n  :author \"Arnold Noronha <arnold@jipr.io>\"\n  :license  \"Apache License, Version 2.0\"\n  :version \"0.0.1\"\n  :serial t\n  :depends-on (#:hunchentoot\n               #:usocket\n               #:alexandria\n               #:str)\n  :components ((:file \"package\")\n               (:file \"hunchentoot-multi-acceptor\")))\n\n(defsystem #:hunchentoot-multi-acceptor/tests\n  :depends-on (#:fiveam\n               #:hunchentoot-multi-acceptor)\n  :components ((:file \"test-hunchentoot-multi-acceptor\")))\n"
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/hunchentoot-multi-acceptor.lisp",
    "content": ";;;; Copyright 2019 Modern Interpreters Inc.\n\n(in-package #:hunchentoot-multi-acceptor)\n\n(defclass multi-request (request)\n  ())\n\n(defvar *default-acceptor*\n  (make-instance 'hunchentoot:easy-acceptor\n                 :port 1\n                 :name 'default-acceptor))\n\n(defvar *top-level-acceptor* nil\n  \"This will be bound to the multi-acceptor before binding\nhunchentoot:*acceptor*. This let's your code get access to the\ntop-level acceptor (e.g. you might want to get the acceptor-port)\")\n\n(defclass multi-acceptor (acceptor)\n  ((sub-acceptors :initform nil :accessor sub-acceptors)\n   (default-acceptor :initform *default-acceptor*\n                     :initarg :default-acceptor\n                     :accessor default-acceptor)\n   (listen-fd :initarg :listen-fd\n              :initform nil\n              :accessor listen-fd)))\n\n(defmethod initialize-instance :after ((acceptor multi-acceptor) &key &allow-other-keys)\n  (setf (acceptor-request-class acceptor) 'multi-request))\n\n(hunchentoot:define-easy-handler (default-route :uri \"/\" :acceptor-names (list 'default-acceptor)) ()\n  (format nil \"Default Acceptor (for: ~a)\" (hunchentoot:host)))\n\n;; (define-easy-handler (s-default-message :uri \"/foo\" :acceptor-names '(default-acceptor)) ()\n;;   (format nil \"error, hostname not set up for: ~a\" (host *request*)))\n\n(defun copy-request (request acceptor)\n  (let ((*acceptor* acceptor))\n    (make-instance (acceptor-request-class acceptor)\n                   :acceptor acceptor\n                   :local-addr (local-addr request)\n                   :local-port (local-port request)\n                   :remote-addr (remote-addr request)\n                   :remote-port (remote-port request)\n                   :headers-in (headers-in request)\n                   :content-stream (hunchentoot::content-stream request)\n                   :method (request-method request)\n                   :uri (request-uri request)\n                   :server-protocol (server-protocol request))))\n\n#+nil (copy-request\n (let ((*acceptor* (make-instance 'acceptor))\n       (*reply* (make-instance 'reply)))\n   (make-instance 'request\n                  :local-addr \"343\"\n                  :local-port 20\n                  :method nil\n                  :uri nil\n                  :server-protocol nil\n                  :remote-addr \"dfdf\"\n                  :remote-port 30\n                  :headers-in nil\n                  :content-stream nil)) (make-instance 'acceptor))\n\n(defun %compute-start2 (expression host)\n  \"Compute the position of the suffix for the wildcard expression (see\nDOMAIN-MATCHES, extracted only for testability)\"\n  (1+ (- (length host) (length expression))))\n\n(defun domain-matches (expression host)\n  (or\n   (equal expression host)\n   (when (eql #\\* (aref expression 0))\n     ;; doing this without allocation\n     (let ((start2 (%compute-start2 expression host)))\n       (when (>= start2 0)\n        (string=\n         expression\n         host\n         :start1 1\n         :start2 start2))))))\n\n(defmethod process-request ((request multi-request))\n   (flet ((dispatch-acceptor (acceptor)\n            (return-from process-request\n              (let* ((*top-level-acceptor* *acceptor*)\n                     (*acceptor* acceptor)\n                     (*request* (copy-request request *acceptor*)))\n                (process-request *request*)))))\n\n   (let ((acceptor (request-acceptor request)))\n     (let ((host (car (str:split \":\" (host request)))))\n       (loop for sub in (sub-acceptors acceptor)\n             if (domain-matches (car sub) host)\n               do (dispatch-acceptor (cdr sub)))\n\n       (dispatch-acceptor (default-acceptor acceptor))))))\n\n(defun listen-on-fd (fd &key element-type)\n  #+sbcl(let ((sock (make-instance 'sb-bsd-sockets:inet-socket\n                                   :type :stream\n                             :protocol :tcp\n                             :descriptor fd)))\n          (usocket::make-stream-server-socket sock :element-type element-type))\n  #-sbcl (error \"Can't listen on file descriptor; this feature is only supported on SBCL currently. It's a specific feature used to restart the webserver without downtime, but in most cases you don't need this.\"))\n\n\n#-lispworks\n(defmethod start-listening ((acceptor multi-acceptor))\n  (when (hunchentoot::acceptor-listen-socket acceptor)\n    (hunchentoot-error \"acceptor ~A is already listening\" acceptor))\n  (setf (hunchentoot::acceptor-listen-socket acceptor)\n        (cond\n          ((listen-fd acceptor)\n           (listen-on-fd (listen-fd acceptor) :element-type '(unsigned-byte 8)))\n          (t (usocket:socket-listen (or (acceptor-address acceptor)\n                                 usocket:*wildcard-host*)\n                             (acceptor-port acceptor)\n                             :reuseaddress t\n                             :backlog (acceptor-listen-backlog acceptor)\n                             :element-type '(unsigned-byte 8)))))\n  (values))\n\n;; (setf *ma* (make-instance 'multi-acceptor :port 5001))\n;; (start *ma*)\n\n(defun add-acceptor (multi-acceptor host acceptor)\n  (setf (alexandria:assoc-value\n         (sub-acceptors multi-acceptor)\n         host :test 'equal)\n        acceptor))\n"
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/package.lisp",
    "content": ";; Copyright 2019 Modern Interpreters Inc.\n\n(defpackage #:hunchentoot-multi-acceptor\n  (:use #:cl\n        #:hunchentoot)\n  (:export #:multi-acceptor\n           #:*default-acceptor*\n           :listen-fd\n           #:add-acceptor\n           #:default-acceptor\n           #:*top-level-acceptor*))\n"
  },
  {
    "path": "third-party/hunchentoot-multi-acceptor/test-hunchentoot-multi-acceptor.lisp",
    "content": "(defpackage :hunchentoot-multi-acceptor/tests\n  (:use #:cl\n        #:fiveam)\n  (:import-from #:hunchentoot-multi-acceptor\n                #:%compute-start2\n                #:domain-matches))\n(in-package :hunchentoot-multi-acceptor/tests)\n\n\n(def-suite* :hunchentoot-multi-acceptor/tests)\n\n(test domain-matches\n  (is-true (domain-matches \"example.com\" \"example.com\"))\n  (is-false (domain-matches \"example.com\" \"bar.example.com\"))\n  (is-false (domain-matches \"example.com\" \"car.com\")))\n\n(test domain-matches-for-wildcard\n  (is-true (domain-matches \"*.example.com\" \"bar.example.com\"))\n  (is-false (domain-matches \"*.example.com\" \"example.com\"))\n  (is-false (domain-matches \"*.example.com\" \"bar.car.com\")))\n\n(test compute-start2\n  (is (= 3 (%compute-start2 \"*.example.com\" \"foo.example.com\")))\n  (is (= 1 (%compute-start2 \"*.com\" \"a.com\")))\n  (is (< (%compute-start2 \"*.example.com\" \"short\") 0))\n  (is (= 1 (%compute-start2 \"*.org\" \"x.org\"))))\n\n"
  },
  {
    "path": "third-party/json-mop/.github/workflows/CI.yml",
    "content": "# Based on https://github.com/3b/ci-example\n\n# Copyright (c) 2020 3b\n# Copyright (c) 2021 Grim Schjetne\n\n# This source code is licensed under the MIT license found in the\n# COPYING file in the root directory of this source tree.\n\nname: CI\n\n# Controls when the action will run. Triggers the workflow on push for any branch, and\n# pull requests to master\non:\n  push:\n  pull_request:\n    branches: [ master ]\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  test:\n    name: ${{ matrix.lisp }} on ${{ matrix.os }}\n    strategy:\n      matrix:\n        lisp: [sbcl-bin, ccl]\n        os: [ ubuntu-latest ]\n         \n    # run the job on every combination of \"lisp\" and \"os\" above\n    runs-on: ${{ matrix.os }}\n  \n    steps:\n    # tell git not to convert line endings\n    # change roswell install dir and add it to path\n    - name: windows specific settings\n      if: matrix.os == 'windows-latest'\n      run: |\n        git config --global core.autocrlf false\n        echo \"ROSWELL_INSTALL_DIR=$HOME/ros\" >> $GITHUB_ENV\n        echo \"$HOME/ros/bin\" >> $GITHUB_PATH\n\n    # Check out your repository under $GITHUB_WORKSPACE, so your job can access it\n    - uses: actions/checkout@v2\n\n    - name: cache .roswell\n      id: cache-dot-roswell\n      uses: actions/cache@v1\n      with:\n        path: ~/.roswell\n        key: ${{ runner.os }}-dot-roswell-${{ matrix.lisp }}-${{ hashFiles('**/*.asd') }}\n        restore-keys: |\n          ${{ runner.os }}-dot-roswell-${{ matrix.lisp }}-\n          ${{ runner.os }}-dot-roswell-\n    - name: install roswell\n      shell: bash\n      # always run install, since it does some global installs and setup that isn't cached\n      env:\n       LISP: ${{ matrix.lisp }}\n      run: curl -L https://raw.githubusercontent.com/roswell/roswell/a8fd8a3c33078d6f06e6cda9d099dcba6fbefcb7/scripts/install-for-ci.sh | sh -x\n    - name: run lisp\n      continue-on-error: true\n      shell: bash\n      run: |\n        ros -e '(format t \"~a:~a on ~a~%...~%~%\" (lisp-implementation-type) (lisp-implementation-version) (machine-type))'\n        ros -e '(format t \" fixnum bits:~a~%\" (integer-length most-positive-fixnum))'\n        ros -e \"(ql:quickload 'trivial-features)\" -e '(format t \"features = ~s~%\" *features*)'\n    - name: update ql dist if we have one cached\n      shell: bash\n      run: ros -e \"(ql:update-all-dists :prompt nil)\"\n\n    - name: load code and run tests\n      shell: bash\n      run: |\n        ros -e \"(ql:quickload 'json-mop-tests)\" -e \"(unless (it.bese.fiveam:run-all-tests) (uiop:quit 1))\"\n"
  },
  {
    "path": "third-party/json-mop/COPYING",
    "content": "Copyright (c) 2016 Grim Schjetne\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "third-party/json-mop/README.md",
    "content": "# JSON-MOP\n\n[![Quicklisp dist](http://quickdocs.org/badge/json-mop.svg)](http://quickdocs.org/json-mop/)\n\n[![CI](https://github.com/gschjetne/json-mop/actions/workflows/CI.yml/badge.svg)](https://github.com/gschjetne/json-mop/actions/workflows/CI.yml)\n\n## Introduction\n\nJSON-MOP is a small library aiming to cut down time spent moving data\nbetween CLOS and JSON objects. It depends on\n[YASON](https://github.com/hanshuebner/yason) and it should be\npossible to use it alongside straight calls to functions from YASON.\n\n## Quick Start\n\nTo use JSON-MOP, define your classes with the class option\n`(:metaclass json-serializable-class)`. For slots that you want to appear in\nthe JSON representation of your class, add the slot option `:json-key`\nwith the string to use as the attribute name. The option `:json-type`\ndefaults to `:any`, but you can control how each slot value is\ntransformed to and from JSON with one of the following:\n\n### JSON type specifiers\n\nType          | Remarks\n--------------|--------------------------------------------\n`:any`        | Guesses the way to encode and decode the value \n`:string`     | Enforces a string value\n`:number`     | Enforces a number value\n`:hash-table` | Enforces a hash table value\n`:vector`     | Enforces a vector value\n`:list`       | Enforces a list value\n`:bool`       | Maps `T` and `NIL` with `true` and `false`\n`<symbol>`    | Uses a `(:metaclass json-serializable-class)` class definition to direct the transformation of the value\n\n### Homogeneous sequences\n\nIn addition, the type specifier may be a list of two elements, first\nelement is one of `:list`, `:vector`; the second is any JSON type\nspecifier that is to be applied to the elements of the list.\n\n### NIL and null semantics\n\nJSON `null` is treated as an unbound slot in CLOS. Unbound slots are\nignored when encoding objects, unless `*encode-unbound-slots*` is\nbound to `T`, in which case they are represented as JSON `null`.\n\nSlots bound to `NIL` with JSON types other `:bool` will signal an\nerror, but this may change in the future.\n\n### Encoding and decoding JSON\n\nTurning an object into JSON is done with the `yason:encode` generic\nfunction. Turning it back into an object is slightly more involved,\nusing `json-to-clos` on a stream, string or hash table; a class name;\nand optional initargs for the class. Values decoded from the JSON will\noverride values specified in the initargs.\n\n### Example\n\nFirst, define your classes:\n\n```lisp\n(defclass book ()\n  ((title :initarg :title\n          :json-type :string\n          :json-key \"title\")\n   (published-year :initarg :year\n         :json-type :number\n         :json-key \"year_published\")\n   (fiction :initarg :fiction\n            :json-type :bool\n            :json-key \"is_fiction\"))\n  (:metaclass json-serializable-class))\n\n(defclass author ()\n  ((name :initarg :name\n         :json-type :string\n         :json-key \"name\")\n   (birth-year :initarg :year\n               :json-type :number\n               :json-key \"year_birth\")\n   (bibliography :initarg :bibliography\n                 :json-type (:list book)\n                 :json-key \"bibliography\"))\n  (:metaclass json-serializable-class))\n```\n\nLet's try creating an instance:\n\n```lisp\n(defparameter *author*\n  (make-instance 'author\n                 :name \"Mark Twain\"\n                 :year 1835\n                 :bibliography\n                 (list\n                  (make-instance 'book\n                                 :title \"The Gilded Age: A Tale of Today\"\n                                 :year 1873\n                                 :fiction t)\n                  (make-instance 'book\n                                 :title \"Life on the Mississippi\"\n                                 :year 1883\n                                 :fiction nil)\n                  (make-instance 'book\n                                 :title \"Adventures of Huckleberry Finn\"\n                                 :year 1884\n                                 :fiction t))))\n```\n\nTo turn it into JSON, `encode` it:\n\n```lisp\n(encode *author*)\n```\n\nThis will print the following:\n\n```javascript\n{\"name\":\"Mark Twain\",\"year_birth\":1835,\"bibliography\":[{\"title\":\"The Gilded Age: A Tale of Today\",\"year_published\":1873,\"is_fiction\":true},{\"title\":\"Life on the Mississippi\",\"year_published\":1883,\"is_fiction\":false},{\"title\":\"Adventures of Huckleberry Finn\",\"year_published\":1884,\"is_fiction\":true}]}\n```\n\nThe same can be turned back into a CLOS object with `(json-to-clos input 'author)`\n\n## Licence\n\nCopyright (c) 2015 Grim Schjetne\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "third-party/json-mop/json-mop.asd",
    "content": ";; Copyright (c) 2016 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(asdf:defsystem #:json-mop\n  :description \"A metaclass for bridging CLOS and JSON\"\n  :author \"Grim Schjetne\"\n  :license \"MIT\"\n  :depends-on (#:closer-mop\n               #:yason\n               #:anaphora)\n  :serial t\n  :components ((:module \"src\"\n                :serial t\n                :components\n                ((:file \"package\")\n                 (:file \"conditions\")\n                 (:file \"json-mop\")\n                 (:file \"to-lisp\")\n                 (:file \"to-json\")))))\n"
  },
  {
    "path": "third-party/json-mop/src/conditions.lisp",
    "content": ";; Copyright (c) 2015 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(in-package #:json-mop)\n\n(define-condition slot-not-serializable (warning)\n  ((slot-name :initarg :slot-name\n              :reader slot-name))\n  (:report (lambda (condition stream)\n             (format stream \"Slot ~A has no JSON metadata associated with it.\"\n                     (slot-name condition)))))\n\n;; TODO: inherit TYPE-ERROR and change all occurences to specify\n;; :expected-type, likewise change relevant occurrences of TYPE-ERROR\n;; to JSON-TYPE-ERROR\n(define-condition json-type-error (error)\n  ((json-type :initarg :json-type\n              :reader json-type)))\n\n(define-condition null-value (json-type-error) ())\n\n(define-condition null-in-homogeneous-sequence (json-type-error) ()\n  (:report (lambda (condition stream)\n             (format stream \"null encountered in a homogeneous sequence of type ~S\"\n                     (json-type condition)))))\n\n(define-condition no-values-parsed (warning)\n  ((hash-table :initarg :hash-table\n               :reader no-values-hash-table)\n   (class-name :initarg :class-name\n               :reader no-values-class))\n  (:report (lambda (condition stream)\n             (format stream \"No keys corresponding to slots in ~A found in ~A\"\n                     (no-values-class condition)\n                     (no-values-hash-table condition)))))\n\n(defun read-eval-query ()\n  (format *query-io* \"Eval: \")\n  (list (eval (read *query-io*))))\n"
  },
  {
    "path": "third-party/json-mop/src/json-mop.lisp",
    "content": ";; Copyright (c) 2015 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(in-package #:json-mop)\n\n(defclass json-serializable-class (closer-mop:standard-class) ())\n\n(defmethod closer-mop:validate-superclass ((class json-serializable-class)\n                                           (super closer-mop:standard-class)) t)\n\n(defmethod closer-mop:validate-superclass ((class standard-class)\n                                           (super json-serializable-class)) t)\n\n(defclass json-serializable-slot (closer-mop:standard-direct-slot-definition)\n  ((json-key :initarg :json-key\n             :initform nil\n             :reader json-key-name)\n   (json-type :initarg :json-type\n              :initform :any\n              :reader json-type)))\n\n(defmethod json-key-name ((slot closer-mop:standard-direct-slot-definition))\n  nil)\n\n(defmethod closer-mop:direct-slot-definition-class ((class json-serializable-class)\n                                                    &rest initargs)\n  (declare (ignore class initargs))\n  (find-class 'json-serializable-slot))\n\n(defclass json-serializable () ())\n\n(defmethod initialize-instance :around ((class json-serializable-class)\n                                        &rest rest &key direct-superclasses)\n  (apply #'call-next-method\n         class\n         :direct-superclasses\n         (append direct-superclasses (list (find-class 'json-serializable)))\n         rest))\n\n(defmethod reinitialize-instance :around ((class json-serializable-class)\n                                          &rest rest &key direct-superclasses)\n  (apply #'call-next-method\n         class\n         :direct-superclasses\n         (append direct-superclasses (list (find-class 'json-serializable)))\n         rest))\n"
  },
  {
    "path": "third-party/json-mop/src/package.lisp",
    "content": ";; Copyright (c) 2015 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(defpackage #:json-mop\n  (:use #:cl)\n  (:import-from #:yason\n                #:true\n                #:false\n                #:parse\n                #:encode\n                #:encode-array-element\n                #:encode-object-element\n                #:with-output\n                #:with-array\n                #:with-object)\n  (:import-from #:anaphora\n                #:awhen\n                #:it)\n  (:export #:json-serializable\n           #:json-serializable-class\n           #:to-lisp-value\n           #:to-json-value\n           #:json-to-clos\n           ;; Re-export yason:encode\n           #:encode\n           ;; Conditions\n           #:slot-not-serializable\n           #:slot-name\n           #:json-type-error\n           #:json-type\n           #:null-value\n           #:null-in-homogenous-sequence\n           #:no-values-parsed\n           #:no-values-hash-table\n           #:no-values-class))\n"
  },
  {
    "path": "third-party/json-mop/src/to-json.lisp",
    "content": ";; Copyright (c) 2015 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(in-package #:json-mop)\n\n(defvar *encode-unbound-slots* nil)\n\n(defgeneric to-json-value (value json-type)\n  (:documentation\n   \"Turns a VALUE into a form appropriate for consumption by Yason\"))\n\n(defmethod to-json-value (value (json-type (eql :any)))\n  \"When the JSON type is :ANY, Pass the VALUE unchanged\"\n  value)\n\n(defmethod to-json-value ((value null) json-type)\n  (error 'null-value :json-type json-type))\n\n(defmethod to-json-value ((value string) (json-type (eql :string)))\n  \"Return the string VALUE\"\n  value)\n\n(defmethod to-json-value ((value number) (json-type (eql :number)))\n  \"Return the number VALUE\"\n  value)\n\n(defmethod to-json-value ((value hash-table) (json-type (eql :hash-table)))\n  \"Return the hash-table VALUE\"\n  value)\n\n(defmethod to-json-value ((value vector) (json-type (eql :vector)))\n  \"Return the vector VALUE\"\n  value)\n\n(defmethod to-json-value ((value list) (json-type (eql :list)))\n  \"Return the list VALUE\"\n  value)\n\n(defmethod to-json-value ((value null) (json-type (eql :list)))\n  \"Return the empty list VALUE\"\n  #())\n\n(defmethod to-json-value (value (json-type (eql :bool)))\n  \"Return the boolean true\"\n  'true)\n\n(defmethod to-json-value ((value null) (json-type (eql :bool)))\n  \"Return the boolean false\"\n  'false)\n\n(defmethod to-json-value ((value sequence) (json-type cons))\n  \"Return the homogeneous sequence VALUE\"\n  (ecase (first json-type)\n    (:list (check-type value list))\n    (:vector (check-type value vector)))\n  (make-instance 'homogeneous-sequence-intermediate-class\n                 :values value\n                 :sequence-json-type (first json-type)\n                 :element-json-type (second json-type)))\n\n(defclass homogeneous-sequence-intermediate-class ()\n  ((values :initarg :values)\n   (sequence-json-type :initarg :sequence-json-type)\n   (element-json-type :initarg :element-json-type)))\n\n(defmethod to-json-value (value (json-type symbol))\n  (if (eql (class-of value) (find-class json-type))\n      value\n      (error 'json-type-error :json-type json-type)))\n\n(defmethod encode ((sequence homogeneous-sequence-intermediate-class)\n                   &optional (stream *standard-output*))\n  (with-output (stream)\n    (with-array ()\n      (with-slots (values sequence-json-type element-json-type) sequence\n        (map nil (lambda (element)\n                   (handler-case\n                       (encode-array-element (to-json-value element element-json-type))\n                     (null-value (condition)\n                       (declare (ignore condition))\n                       (restart-case (error 'null-in-homogeneous-sequence\n                                            :json-type (list sequence-json-type\n                                                             element-json-type))\n                         (use-value (value)\n                           :report \"Specify a value to use in place of the null\"\n                           :interactive read-eval-query\n                           (encode-array-element value))))))\n             values))))\n  sequence)\n\n(defmethod encode ((object json-serializable)\n                   &optional (stream *standard-output*))\n  (with-output (stream)\n    (with-object ()\n      (loop for class in (closer-mop:class-precedence-list (class-of object))\n         do (loop for slot in (closer-mop:class-direct-slots class)\n               when (typep slot 'json-serializable-slot)\n               do (awhen (json-key-name slot)\n                    (handler-case\n                        (encode-object-element\n                         it\n                         (to-json-value\n                          (slot-value object (closer-mop:slot-definition-name slot))\n                          (json-type slot)))\n                      (unbound-slot (condition)\n                        (declare (ignore condition))\n                        (when *encode-unbound-slots*\n                          (encode-object-element it nil)))))))))\n  object)\n"
  },
  {
    "path": "third-party/json-mop/src/to-lisp.lisp",
    "content": ";; Copyright (c) 2015 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(in-package #:json-mop)\n\n(defgeneric to-lisp-value (value json-type)\n  (:documentation\n   \"Turns a value passed by Yason into the appropriate\n  Lisp type as specified by JSON-TYPE\"))\n\n(defmethod to-lisp-value ((value (eql :null)) json-type)\n  \"When the value is JSON null, signal NULL-VALUE error\"\n  (error 'null-value :json-type json-type))\n\n(defmethod to-lisp-value (value (json-type (eql :any)))\n  \"When the JSON type is :ANY, Pass the VALUE unchanged\"\n  value)\n\n(defmethod to-lisp-value ((value string) (json-type (eql :string)))\n  \"Return the string VALUE\"\n  value)\n\n(defmethod to-lisp-value ((value number) (json-type (eql :number)))\n  \"Return the number VALUE\"\n  value)\n\n(defmethod to-lisp-value ((value hash-table) (json-type (eql :hash-table)))\n  \"Return the hash-table VALUE\"\n  value)\n\n(defmethod to-lisp-value ((value vector) (json-type (eql :vector)))\n  \"Return the vector VALUE\"\n  value)\n\n(defmethod to-lisp-value ((value vector) (json-type (eql :list)))\n  \"Return the list VALUE\"\n  (coerce value 'list))\n\n(defmethod to-lisp-value (value (json-type (eql :bool)))\n  \"Return the boolean VALUE\"\n  (ecase value (true t) (false nil)))\n\n(defmethod to-lisp-value ((value vector) (json-type cons))\n  \"Return the homogeneous sequence VALUE\"\n  (map (ecase (first json-type)\n         (:vector 'vector)\n         (:list 'list))\n       (lambda (item)\n         (handler-case (to-lisp-value item (second json-type))\n           (null-value (condition)\n             (declare (ignore condition))\n             (restart-case (error 'null-in-homogenous-sequence\n                                  :json-type json-type)\n               (use-value (value)\n                 :report \"Specify a value to use in place of the null\"\n                 :interactive read-eval-query\n                 value)))))\n       value))\n\n(defmethod to-lisp-value ((value hash-table) (json-type symbol))\n  \"Return the CLOS object VALUE\"\n  (json-to-clos value json-type))\n\n(defgeneric json-to-clos (input class &rest initargs))\n\n(defmethod json-to-clos ((input hash-table) class &rest initargs)\n  (let ((lisp-object (apply #'make-instance class initargs))\n        (key-count 0))\n    ;; Arnold added this next line. See\n    ;; https://github.com/gschjetne/json-mop/pull/2, which handled\n    ;; to-json, but not to-lisp.\n    (loop for class in (closer-mop:class-precedence-list (find-class class)) do\n      (loop for slot in (closer-mop:class-direct-slots class)\n            do (awhen (json-key-name slot)\n                 (handler-case\n                     (progn\n                       (setf (slot-value lisp-object\n                                         (closer-mop:slot-definition-name slot))\n                             (to-lisp-value (gethash it input :null)\n                                            (json-type slot)))\n                       (incf key-count))\n                   (null-value (condition)\n                     (declare (ignore condition)) nil)))))\n    #+nil ;; Arnold disabled this warning,\n    (when (zerop key-count) (warn 'no-values-parsed\n                                  :hash-table input\n                                  :class-name class))\n    (values lisp-object key-count)))\n\n(defmethod json-to-clos ((input stream) class &rest initargs)\n  (apply #'json-to-clos\n         (parse input\n                      :object-as :hash-table\n                      :json-arrays-as-vectors t\n                      :json-booleans-as-symbols t\n                      :json-nulls-as-keyword t)\n         class initargs))\n\n(defmethod json-to-clos ((input pathname) class &rest initargs)\n  (with-open-file (stream input)\n    (apply #'json-to-clos stream class initargs)))\n\n(defmethod json-to-clos ((input string) class &rest initargs)\n  (apply #'json-to-clos (make-string-input-stream input) class initargs))\n"
  },
  {
    "path": "third-party/json-mop/tests/encode-decode.lisp",
    "content": ";; Copyright (c) 2016 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(in-package #:json-mop-tests)\n\n(def-suite encode-decode\n  :in test-all\n  :description \"Test encoding and decoding slots\")\n\n(in-suite encode-decode)\n\n(test string\n  (for-all ((obj (gen-object)))\n    (is (equal (get-string obj)\n               (get-string (obj-rt obj))))))\n\n(test number\n  (for-all ((obj (gen-object)))\n    (is (= (get-number obj)\n           (get-number (obj-rt obj))))))\n\n;; TODO: test hash table\n\n(test vector\n  (for-all ((obj (gen-object)))\n    (is (equalp (get-vector obj)\n                (get-vector (obj-rt obj))))))\n\n(test list\n  (for-all ((obj (gen-object)))\n    (is (equal (get-list obj)\n               (get-list (obj-rt obj))))))\n\n(test bool\n  (for-all ((obj (gen-object)))\n    (is (eql (get-bool obj)\n             (get-bool (obj-rt obj))))))\n\n(test object\n  (for-all ((obj (gen-object)))\n    (is (= (get-number (get-object obj))\n           (get-number (get-object (obj-rt obj)))))))\n\n(test inheritance\n  (let ((child (make-instance 'child))\n        (parent-only (make-instance 'parent)))\n    (is (string= (with-output-to-string (s) (encode child s))\n                 (with-output-to-string (s) (encode parent-only s))))))\n"
  },
  {
    "path": "third-party/json-mop/tests/json-mop-tests.asd",
    "content": ";; Copyright (c) 2016 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(asdf:defsystem #:json-mop-tests\n  :description \"Test suite for JSON-MOP\"\n  :author \"Grim Schjetne\"\n  :license \"LGPLv3+\"\n  :depends-on (#:json-mop\n               #:fiveam)\n  :serial t\n  :components ((:file \"package\")\n               (:file \"tests\")\n               (:file \"encode-decode\")))\n"
  },
  {
    "path": "third-party/json-mop/tests/package.lisp",
    "content": ";; Copyright (c) 2016 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(defpackage #:json-mop-tests\n  (:use #:cl\n        #:json-mop\n        #:fiveam)\n  (:export #:test-all\n           #:encode-decode))\n"
  },
  {
    "path": "third-party/json-mop/tests/tests.lisp",
    "content": ";; Copyright (c) 2016 Grim Schjetne\n;;\n;; Permission is hereby granted, free of charge, to any person\n;; obtaining a copy of this software and associated documentation\n;; files (the \"Software\"), to deal in the Software without\n;; restriction, including without limitation the rights to use, copy,\n;; modify, merge, publish, distribute, sublicense, and/or sell copies\n;; of the Software, and to permit persons to whom the Software is\n;; furnished to do so, subject to the following conditions:\n;;\n;; The above copyright notice and this permission notice shall be\n;; included in all copies or substantial portions of the Software.\n;;\n;; THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n;; SOFTWARE.\n\n(in-package #:json-mop-tests)\n\n(defclass test-class ()\n  ((string :initarg :string\n           :reader get-string\n           :json-type :string\n           :json-key \"str\")\n   (number :initarg :number\n           :reader get-number\n           :json-type :number\n           :json-key \"num\")\n   (hash :initarg :hash-table\n         :reader get-hash-table\n         :json-type :hash-table\n         :json-key \"hash\")\n   (vector :initarg :vector\n           :reader get-vector\n           :json-type :vector\n           :json-key \"vect\")\n   (list :initarg :list\n         :reader get-list\n         :json-type :list\n         :json-key \"list\")\n   (bool :initarg :bool\n         :reader get-bool\n         :json-type :bool\n         :json-key \"bool\")\n   (object :initarg :object\n           :reader get-object\n           :json-type test-class\n           :json-key \"obj\"))\n  (:metaclass json-serializable-class))\n\n;;; as per https://github.com/gschjetne/json-mop/issues/1\n(defclass parent ()\n  ((foo :accessor foo :initarg :foo\n          :initform \"Foo\"\n          :json-key \"foo\"))\n  (:metaclass json-serializable-class))\n\n(defclass child (parent)\n    ((bar :accessor bar :initarg :bar\n      :json-key \"bar\"))\n  (:metaclass json-serializable-class))\n\n(defun json-string (object)\n  (with-output-to-string (s)\n    (encode object s)))\n\n(defun obj-rt (object)\n  (json-to-clos (json-string object)\n                (class-name (class-of object))))\n\n(defun gen-vector (&key\n                     (length (gen-integer :min 0 :max 10))\n                     (elements (gen-integer :min -10 :max 10)))\n  (lambda ()\n    (let* ((l (funcall length))\n           (vector (make-array l)))\n      (loop for i from 0 to (1- l) do\n            (setf (aref vector i) (funcall elements)))\n      vector)))\n\n(defun gen-bool ()\n  (lambda () (zerop (random 2))))\n\n(defun gen-object (&key\n                     (string (gen-string))\n                     (number (gen-float))\n                     (hash-table (lambda () (make-hash-table)))\n                     (vector (gen-vector))\n                     (list (gen-list))\n                     (bool (gen-bool))\n                     (object (lambda () (make-instance\n                                    'test-class\n                                    :number (funcall (gen-integer))))))\n  (lambda ()\n    (make-instance 'test-class\n                   :string (funcall string)\n                   :number (funcall number)\n                   :hash-table (funcall hash-table)\n                   :vector (funcall vector)\n                   :list (funcall list)\n                   :bool (funcall bool)\n                   :object (funcall object))))\n\n(def-suite test-all)\n"
  },
  {
    "path": "third-party/pem/README.md",
    "content": "# pem\n\nPEM parser.\n\n## Usage\n\n### Parsing a PEM file\n\n```common-lisp\n(ql:quickload :pem)\n\n(pem:parse-file #P\"rsa-pub.pem\")\n;=> ((\"PUBLIC KEY\"\n;     . \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAum9xmq7qBsjYU3gNFB6z\n;   2DyQypeGvwR3MqbA5x4sevYjeqRunFRq+oo6CyEjzC/zR8xh7NvLFwXImSmyYadU\n;   d+jstH1Kn5MJtBfCwlGSAXRfn6QV8wr+oweWvyDNUgCkgM+6X7Q7wyH8pib9J2WA\n;   R6QcY3GRD+P+c/ZNwlgDSBVWzSUE2Sw1GBXadgEDdTMq/DnGmGmsMIdgCMxJ+szA\n;   Av+dWJhuUPlp5zoFhyxayyJMCAND3llFpmv85bIKfQb8EDkQjtFLOEbU0KIY4pPj\n;   KL01P4pDiqFFo6PWOJUHO5vyeLDWWCl1itOKeGxHvyxNQG/0BvQquxpjNjHZYCk0\n;   cwIDAQAB\"))\n```\n\n### Reading an RSA public/private keys\n\n```common-lisp\n(pem:read-from-file #P\"rsa-pub.pem\")\n;=> #<IRONCLAD::RSA-PUBLIC-KEY {1004FD26B3}>\n\n(pem:read-from-file #P\"rsa-priv.pem\")\n;=> #<IRONCLAD::RSA-PRIVATE-KEY {10050CDB03}>\n```\n"
  },
  {
    "path": "third-party/pem/main.lisp",
    "content": "(defpackage #:pem\n  (:use #:cl\n        #:pem/parser\n        #:pem/pkey)\n  (:export #:parse\n           #:parse-file\n           #:read-from-file))\n(in-package #:pem)\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "third-party/pem/parser.lisp",
    "content": "(defpackage #:pem/parser\n  (:use #:cl)\n  (:import-from #:cl-ppcre\n                #:create-scanner\n                #:register-groups-bind)\n  (:export #:parse\n           #:parse-file))\n(in-package #:pem/parser)\n\n(defun parse (pem)\n  (assert (eq (stream-element-type pem) #+lispworks 'base-char\n                                        #-lispworks 'character))\n  (loop for line = (read-line pem nil nil)\n        while line\n        collect (ppcre:register-groups-bind (label)\n                    (\"^-----BEGIN (.+)-----\" line)\n                  (let ((end (ppcre:create-scanner (format nil \"-----END ~A-----\" label))))\n                    (loop for line = (read-line pem)\n                          if (ppcre:scan end line)\n                            do (return (cons label\n                                             (format nil \"~{~A~^~%~}\" data)))\n                          else\n                            collect line into data)))))\n\n(defun parse-file (pem)\n  (check-type pem pathname)\n  (with-open-file (in pem)\n    (parse in)))\n"
  },
  {
    "path": "third-party/pem/pem.asd",
    "content": "(defsystem \"pem\"\n  :class :package-inferred-system\n  :version \"0.1.0\"\n  :author \"Eitaro Fukamachi\"\n  :license \"BSD 2-Clause\"\n  :description \"PEM parser\"\n  :depends-on (\"pem/main\"))\n"
  },
  {
    "path": "third-party/pem/pkey.lisp",
    "content": "(defpackage #:pem/pkey\n  (:use #:cl)\n  (:import-from #:pem/parser\n                #:parse-file)\n  (:import-from #:asn1\n                #:decode\n                #:rsa-public-key-info)\n  (:import-from #:cl-base64\n                #:base64-string-to-usb8-array)\n  (:import-from #:ironclad)\n  (:export #:read-from-file))\n(in-package #:pem/pkey)\n\n(defun read-public-key (key)\n  (let* ((der (base64:base64-string-to-usb8-array key))\n         (der (asn1:decode der)))\n    (optima:match der\n      ((asn1:rsa-public-key-info n e)\n       (ironclad:make-public-key :rsa :n n :e e))\n      (otherwise (error \"Unexpected format: ~S\" key)))))\n\n(defun read-private-key (key)\n  (let* ((der (base64:base64-string-to-usb8-array key))\n         (der (asn1:decode der)))\n    (optima:match der\n      ((asn1:rsa-private-key :private-exponent d :modulus n)\n       (ironclad:make-private-key :rsa :d d :n n))\n      (otherwise (error \"Unexpected format: ~S\" key)))))\n\n(defun read-from-file (pem)\n  (let ((data (pem/parser:parse-file pem)))\n    (let ((public-key (cdr (assoc \"PUBLIC KEY\" data :test #'string=)))\n          (private-key (cdr (assoc \"RSA PRIVATE KEY\" data :test #'string=))))\n      (cond\n        (public-key (read-public-key public-key))\n        (private-key (read-private-key private-key))))))\n"
  },
  {
    "path": "third-party/sentry/sentry-js.asd",
    "content": "(defpackage :sentry-js-asdf\n  (:use :cl :asdf))\n(in-package :sentry-js-asdf)\n\n(defsystem sentry-js\n  :class \"build-utils:js-library\"\n  :defsystem-depends-on (:build-utils)\n  :components ((\"build-utils:js-file\" \"bundle.min\")\n               #-screenshotbot-oss\n               (\"build-utils:js-file\" \"config\")))\n"
  }
]